summaryrefslogtreecommitdiffstats
path: root/storage/rocksdb
diff options
context:
space:
mode:
Diffstat (limited to 'storage/rocksdb')
-rw-r--r--storage/rocksdb/.clang-format137
-rw-r--r--storage/rocksdb/.gitignore2
-rw-r--r--storage/rocksdb/CMakeLists.txt276
-rw-r--r--storage/rocksdb/README50
-rw-r--r--storage/rocksdb/atomic_stat.h94
-rw-r--r--storage/rocksdb/build_rocksdb.cmake506
-rw-r--r--storage/rocksdb/event_listener.cc96
-rw-r--r--storage/rocksdb/event_listener.h49
-rwxr-xr-xstorage/rocksdb/get_rocksdb_files.sh27
-rw-r--r--storage/rocksdb/ha_rocksdb.cc14638
-rw-r--r--storage/rocksdb/ha_rocksdb.h1069
-rw-r--r--storage/rocksdb/ha_rocksdb_proto.h103
-rw-r--r--storage/rocksdb/logger.h85
-rwxr-xr-xstorage/rocksdb/myrocks_hotbackup.py697
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/combinations5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/autoinc_crash_safe.inc150
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/bulk_load.inc165
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/bulk_load_unsorted.inc143
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/bypass_create_table.inc298
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority.inc174
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority_module.inc141
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/deadlock_stats.inc52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/dup_key_update.inc69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/group_min_max.inc1438
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_direct_io.inc23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.inc10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.opt12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_default.inc10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_replication.inc11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_write_committed.inc3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/have_write_prepared.inc3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/index_merge1.inc910
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/index_merge2.inc520
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/index_merge_2sweeps.inc80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror.inc463
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror_cpk.inc178
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_1.inc51
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_2.inc48
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case2.inc97
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case3.inc71
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case4.inc69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case5.inc77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case6.inc77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case7.inc89
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/prefix_index_only_query_check.inc21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_invalid_option.inc8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_option.inc31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/rocksdb_icp.inc199
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/simple_deadlock.inc29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/start_mysqld_with_option.inc14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/include/use_direct_io_option.inc24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/my.cnf19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/1st.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/2pc_group_commit.result101
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace.result505
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_cardinality.result24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_crash.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_sstfilewriter.result79
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/add_unique_index_inplace.result111
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/allow_no_pk_concurrent_insert.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key.result298
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key_with_sk.result806
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/allow_to_start_after_corruption.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/alter_table.result183
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/analyze_table.result61
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/apply_changes_iter.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result132
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result132
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_debug.result99
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_secondary.result17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result199
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread.result39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread_2.result98
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/binlog_rotate_crash.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rc.result87
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rr.result87
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter.result2042
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter2.result71
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter3.result143
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter4.result32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter5.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_bulk_load.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_skip.result2042
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load.result81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_drop_table.result11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_errors.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf.result81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf_and_data.result81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_data.result81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_sk.result229
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted.result111
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_errors.result4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_rev.result111
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic.result693
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic_bloom.result693
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/bytes_written.result10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/cardinality.result104
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/check_flags.result66
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/check_ignore_unknown_options.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/check_table.result68
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/checkpoint.result59
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/checksum_table.result92
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/checksum_table_live.result20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/col_opt_default.result20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/col_opt_not_null.result2613
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/col_opt_null.result2268
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/col_opt_unsigned.result749
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/col_opt_zerofill.result731
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/collation.result144
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/collation_exception.result25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/com_rpc_tx.result21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/commit_in_the_middle_ddl.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/compact_deletes.result78
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/compression_zstd.result3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/concurrent_alter.result12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_read_committed.result151
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_repeatable_read.result144
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_serializable.result24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/corrupted_data_reads_debug.result74
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/covered_unpack_info_format.result73
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/create_no_primary_key_table.result52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/create_table.result165
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ddl_high_priority.result1058
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/deadlock.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/deadlock_stats.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/deadlock_tracking.result488
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/delete.result166
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/delete_ignore.result59
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/delete_quick.result24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/delete_with_keys.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/describe.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/drop_database.result6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/drop_index_inplace.result158
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/drop_table.result79
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/drop_table2.result66
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/drop_table3.result25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/dup_key_update.result366
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/duplicate_table.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/explicit_snapshot.result265
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/fail_system_cf.result4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/fast_prefix_index_fetch.result80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/force_shutdown.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/foreign_key.result25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/gap_lock_issue254.result9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/gap_lock_raise_error.result504
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/get_error_message.result8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/group_min_max.result3526
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ha_extra_keyread.result10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/handler_basic.result128
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/hermitage.result648
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/i_s.result159
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/i_s_ddl.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/i_s_deadlock.result216
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index.result89
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_file_map.result31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_key_block_size.result53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb.result49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb2.result1418
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_primary.result71
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_type_btree.result69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/index_type_hash.result69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/information_schema.result83
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result326
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/insert.result202
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/insert_optimized_config.result16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/insert_with_keys.result262
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue100.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue100_delete.result17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue111.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue243_transactionStatus.result161
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue255.result68
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue290.result28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue314.result12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue495.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue884.result80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue896.result17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/issue900.result11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/iterator_bounds.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/kill.result6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/level_read_committed.result111
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/level_read_uncommitted.result116
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/level_repeatable_read.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/level_serializable.result56
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/loaddata.result239
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/lock.result121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/lock_info.result31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/lock_rows_not_exist.result40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/lock_wait_timeout_stats.result35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues.result1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rc.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rr.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rc.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rr.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc.result50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc_lsr.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr.result50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr_lsr.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rc.result25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rr.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rc.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rr.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rc.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rr.result28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rc.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rr.result28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc.result41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc_lsr.result45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr.result41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr_lsr.result45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mariadb_ignore_dirs.result9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mariadb_misc_binlog.result33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mariadb_plugin.result35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_fixes.result119
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_rpl.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/max_open_files.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/misc.result94
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/multi_varchar_sk_lookup.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_blind_replace.result128
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_gtid_skip_empty_trans_rocksdb.result143
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mysqldump.result246
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/mysqldump2.result16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/native_procedure.result397
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/negative_stats.result9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/no_merge_sort.result123
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_base.result98
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_lock.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/optimize_table.result77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/optimizer_loose_index_scans.result281
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/partition.result689
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/percona_nonflushing_analyze_debug.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/perf_context.result191
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/persistent_cache.result11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/prefix_extractor_override.result82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/read_only_tx.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/records_in_range.result211
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/repair_table.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/replace.result32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb.result2643
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_options.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_per_partition.result425
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_reverse.result120
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_checksums.result129
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_concurrent_delete.result671
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_datadir.result2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rc.result89
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rr.result89
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rc.result8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rr.result8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_debug.result11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp.result257
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp_rev.result223
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_locks.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_parts.result152
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_qcache.result45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range.result293
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range2.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl.result335
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl_stress.result35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_row_stats.result66
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_table_stats_sampling_pct_change.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rocksdb_timeout_rollback.result84
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rollback_savepoint.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found.result56
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found_rc.result56
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_row_rocksdb.result45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_row_stats.result98
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_row_triggers.result286
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_savepoint.result110
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_statement.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rpl_statement_not_found.result70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rqg_examples.result3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rqg_runtime.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/rqg_transactions.result11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/secondary_key_update_lock.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/select.result380
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/select_for_update.result35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/select_for_update_skip_locked_nowait.result28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/select_lock_in_share_mode.result37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/show_engine.result491
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/show_table_status.result136
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/shutdown.result9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/singledelete.result86
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/skip_core_dump_on_error.result31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/skip_validate_tmp_table.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/slow_query_log.result10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/statistics.result73
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/table_stats.result12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_avg_row_length.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_checksum.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_connection.result26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_data_index_dir.result41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_delay_key_write.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_insert_method.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_key_block_size.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_max_rows.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_min_rows.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_pack_keys.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_password.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_row_format.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_union.result16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tbl_standard_opts.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/tmpdir.result32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/transaction.result977
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/truncate_partition.result620
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/truncate_table.result33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/truncate_table3.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/trx_info.result13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/trx_info_rpl.result16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_primary.result489
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_read_filtering.result283
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_with_partitions.result256
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_rows_examined.result45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary.result709
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering.result511
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering_multiple_index.result82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_with_partitions.result389
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_binary.result48
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_binary_indexes.result80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_bit.result53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_bit_indexes.result58
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_blob.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_blob_indexes.result198
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_bool.result73
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_char.result76
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes.result73
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes_collation.result91
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_date_time.result56
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_date_time_indexes.result119
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_decimal.result105
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_enum.result47
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_enum_indexes.result69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_fixed.result131
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_fixed_indexes.result129
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_float.result314
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_float_indexes.result189
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_int.result212
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_int_indexes.result99
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_set.result49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_set_indexes.result115
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_text.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_text_indexes.result181
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_varbinary.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/type_varchar.result775
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/unique_check.result117
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/unique_sec.result221
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/unique_sec_rev_cf.result177
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/unsupported_tx_isolations.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/update.result121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/update_ignore.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/update_multi.result691
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/update_with_keys.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/use_direct_io_for_flush_and_compaction.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads.result18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads_writes.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/validate_datadic.result9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/varbinary_format.result260
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/write_sync.result30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/r/xa.result70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/slow_query_log.awk25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/suite.pm28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/1st.test36
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit.test168
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace.test417
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_crash.test118
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_sstfilewriter.test113
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/add_unique_index_inplace.test101
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/allow_no_pk_concurrent_insert.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key.test126
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key_with_sk.test149
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption.test77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/alter_table.test94
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/analyze_table.test57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/apply_changes_iter.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.cnf8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.test9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.cnf8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.test10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug.test121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_secondary.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars.test171
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread.test65
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread_2.test142
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/binlog_rotate_crash.test31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.cnf11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.cnf11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/blind_delete_without_tx_api.inc132
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.inc78
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2.test103
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3-master.opt4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3.test136
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4.test52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5.test86
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load.test35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_load_select.inc190
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_table_def.inc33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load.test11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_drop_table.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_errors.test168
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf.test10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf_and_data.test10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_data.test10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_sk.test119
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_errors.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_rev.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.inc213
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/bytes_written.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cardinality-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cardinality.test119
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/check_flags.test117
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/check_ignore_unknown_options.test56
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/check_log_for_xa.py31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/check_table.inc54
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/check_table.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/checkpoint.test107
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/checksum_table.test84
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/checksum_table_live.test24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_not_null.inc55
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_not_null_timestamp.inc70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_null.inc34
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_opt_default.test27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_opt_not_null.test229
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_opt_null.test220
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_opt_unsigned.test74
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/col_opt_zerofill.test67
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/collation-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/collation.test211
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/collation_exception-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/collation_exception.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.cnf4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.test90
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/commit_in_the_middle_ddl.test27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/compact_deletes-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/compact_deletes.test88
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/compact_deletes_test.inc72
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/compression_zstd.test14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/concurrent_alter.test39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/consistent_snapshot.inc136
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/corrupted_data_reads_debug.test80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/covered_unpack_info_format.test79
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table.test63
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/create_table.test192
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ddl_high_priority.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/deadlock.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/deadlock_stats.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/deadlock_tracking.test185
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/delete.test101
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/delete_ignore.test37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/delete_quick.test32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/delete_with_keys.test39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/describe.test24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/disabled.def98
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_database.test11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_index_inplace.test116
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_stats_procedure.inc3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table.test145
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table2.test131
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table3-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table3.inc52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table3.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table3_repopulate_table.inc15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table_repopulate_table.inc15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/drop_table_sync.inc6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/dup_key_update.test45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/duplicate_table.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot.test263
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/fail_system_cf.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/fast_prefix_index_fetch.test120
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/force_shutdown.test97
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/foreign_key.test47
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254.test14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/gap_lock_raise_error.test37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/get_error_message.test27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/group_min_max-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/group_min_max.test9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ha_extra_keyread.test15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/handler_basic.test53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/hermitage.inc257
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/hermitage.test10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/hermitage_init.inc8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/i_s.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/i_s_ddl.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/i_s_deadlock.test158
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index.inc155
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_file_map-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_file_map.test54
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_key_block_size.test70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb.test110
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2.test72
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_primary.test96
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_type_btree.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/index_type_hash.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/information_schema-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/information_schema.test89
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/init_stats_procedure.inc40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled-master.opt30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled.test43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/insert.test99
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/insert_optimized_config.test51
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/insert_with_keys.test169
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue100.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue100_delete-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue100_delete.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue111.test38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue243_transactionStatus.test80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue255.test52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue290.test40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue314.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue495.test34
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue884.test43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue896.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/issue900.test13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/kill.test9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/level_read_committed.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/level_repeatable_read.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/level_serializable.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/loaddata.inc117
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/loaddata.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/lock.test224
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/lock_info.test31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/lock_rows_not_exist.test110
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/lock_wait_timeout_stats.test38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc_lsr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr_lsr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rr.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc_lsr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr_lsr.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_ignore_dirs.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog.test40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin.test59
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_fixes.test114
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_rpl.test14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/max_open_files.test53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/misc.test45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/multi_varchar_sk_lookup.test49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_blind_replace.test62
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqldump-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test67
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqldump2-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/native_procedure-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/native_procedure.test2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/negative_stats.test26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/no_merge_sort.test32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/no_primary_key_basic_ops.inc65
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_base.test96
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_lock.test88
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimize_table-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimize_table.inc20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimize_table.test81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/optimizer_loose_index_scans.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/partition.test762
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/percona_nonflushing_analyze_debug.test11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/perf_context.test96
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/persistent_cache.test41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override.test96
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/read_only_tx-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/read_only_tx.test70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/records_in_range-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/records_in_range.test146
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/repair_table.inc38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/repair_table.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/replace.test54
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb.test1974
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.opt1
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.sh5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options.test75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_per_partition.test513
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse.test71
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums.test152
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.inc109
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.test38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_main.inc30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_range.inc85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_sk.inc82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_insert.py95
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_datadir.test33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect.inc123
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rr.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.inc18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.py94
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rr.test1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_debug.test14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks.test94
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts.test160
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache.test43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range.test196
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range2.test33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.cnf16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.test414
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.cnf17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.inc69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_row_stats.test57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_table_stats_sampling_pct_change.test80
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback.test78
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rollback_savepoint.test33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.cnf9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.inc98
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.cnf11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.cnf1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.test48
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.cnf1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.test47
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.cnf19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.test262
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.cnf1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.test91
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.cnf7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.test59
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.cnf9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg.inc44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_examples-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_examples.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime.test58
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions.test14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/se-innodb.out1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/secondary_key_update_lock.test26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/select.test202
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/select_for_update.test55
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/select_for_update_skip_locked_nowait.test48
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/select_lock_in_share_mode.test54
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/set_checkpoint.inc30
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/show_engine.test103
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/show_table_status-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/show_table_status.test175
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/shutdown-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/shutdown.test36
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/singledelete-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/singledelete.test105
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error.test53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/skip_validate_tmp_table.test39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/slow_query_log-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/slow_query_log.test37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/statistics-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/statistics.test82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/table_stats-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/table_stats.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_ai.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_avg_row_length.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_checksum.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_connection.test32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_data_index_dir.test60
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_delay_key_write.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_insert_method.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_key_block_size.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_max_rows.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_min_rows.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_pack_keys.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_password.test27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_row_format.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_union.test28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tbl_standard_opts.test42
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/tmpdir.test35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/transaction.test158
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/transaction_isolation.inc150
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/transaction_select.inc14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.inc102
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.test83
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/truncate_table.test74
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/truncate_table3-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/truncate_table3.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/trx_info.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.cnf11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary.test545
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering.test388
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions.test254
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_rows_examined.test57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary.test780
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering.test503
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering_multiple_index.test87
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions.test300
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_binary.inc45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_binary.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes.test99
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bit.inc53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bit.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes.test113
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_blob.inc49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_blob.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes.test176
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bool.inc64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_bool.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char.inc45
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes.test107
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation.test126
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_date_time.inc47
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_date_time.test9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes.test157
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_decimal-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_decimal.test88
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_enum.inc50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_enum.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes.test93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_fixed.inc85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_fixed.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes.test107
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_float.inc121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_float.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes.test175
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_int.inc68
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_int.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes.test75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_set.inc49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_set.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes.test100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_text.inc49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_text.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes.test171
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.inc75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varchar-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varchar.inc77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varchar.test82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/type_varchar_endspace.inc85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/unique_check.test173
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/unique_sec.inc198
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/unique_sec.test51
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/unique_sec_rev_cf.test4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/unsupported_tx_isolations.test25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update.test82
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update_ignore-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update_ignore.test35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update_multi.test15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update_multi_exec.inc27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/update_with_keys.test78
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/use_direct_io_for_flush_and_compaction.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads_writes.test62
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/validate_datadic.test108
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/varbinary_format.test131
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/write_sync.test41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/xa-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb/t/xa.test73
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/base.cnf25
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/clean_tmpfiles.sh8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/include/cleanup.inc3
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_slocket_socket.sh4
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_table.sh18
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data.sh45
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_and_run.sh11
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_slocket.sh45
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/remove_slocket_socket.sh4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup.inc16
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid.sh22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid_and_sync.inc4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_slocket.inc10
-rwxr-xr-xstorage/rocksdb/mysql-test/rocksdb_hotbackup/include/stream_run.sh81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/my.cnf2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/gtid.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/slocket.result41
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/stream.result20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/wdt.result20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream.result21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_direct.result21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_socket.result20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid.test47
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/slocket.test46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/stream.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/wdt.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.inc25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_socket.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/combinations7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.inc10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.opt12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_gtid_crash_safe.inc37
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_no_unique_check_on_lag.inc72
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/my.cnf17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/consistent_snapshot_mixed_engines.result68
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/mdev12179.result283
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/multiclient_2pc.result26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/optimize_myrocks_replace_into.result282
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rocksdb_slave_check_before_image_consistency.result165
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_binlog_xid_count.result204
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_crash_safe_wal_corrupt.result135
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_ddl_high_priority.result39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe.result361
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_optimized.result361
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_wal_corrupt.result140
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_rocksdb_sys_header.result16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_missing_columns_sk_update.result62
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_mts_dependency_unique_key_conflicts.result44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag.result34
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag_mts.result31
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_2pc_crash_recover.result44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_slave_gtid_info_optimized.result43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot.result222
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot_without_gtid.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_stress_crash.result28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_skip_trx_api_binlog_format.result27
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_xa.result61
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_recovery.result25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_table.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/rpl_1slave_base.cnf51
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/suite.pm25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines.test81
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/disabled.def34
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/mdev12179.test317
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc.test77
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/optimize_myrocks_replace_into.test149
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count-master.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_check_for_binlog_info.pl19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.cnf13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_ddl_high_priority.test2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-slave.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe.test11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-slave.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized.test11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.cnf18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc154
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.test12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header.test40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.cnf13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.test69
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_mts_dependency_unique_key_conflicts.test64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts.test3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover.test57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized.test51
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-slave.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot.test374
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot_without_gtid.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-slave.opt3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash.test26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-master.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-slave.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format.test52
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.inc84
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.cnf15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.test78
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.cnf11
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.test44
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/combinations5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.inc10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.opt12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/include/rocksdb_stress.inc57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/my.cnf9
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress_crash.result23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/suite.opt2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/suite.pm28
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/t/disabled.def2
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/t/load_generator.py1042
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress.test33
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress_crash.test34
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/include/correctboolvalue.inc25
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.inc10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.opt12
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/include/rocksdb_sys_var.inc124
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/my.cnf10
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/all_vars.result13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_access_hint_on_compaction_start_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_advise_random_on_open_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_concurrent_memtable_write_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_reads_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_writes_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_to_start_after_corruption_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_blind_delete_primary_key_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_cache_size_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_restart_interval_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_deviation_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_sk_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_unsorted_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_size_basic.result72
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bytes_per_sync_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_dump_basic.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_high_pri_pool_ratio_basic.result22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_blocks_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_with_high_priority_basic.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_checksums_pct_basic.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_collect_sst_properties_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_in_the_middle_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_time_batch_for_recovery_basic.result121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compact_cf_basic.result40
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_readahead_size_basic.result70
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_count_sd_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_file_size_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_window_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_checkpoint_basic.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_if_missing_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_missing_column_families_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_datadir_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_db_write_buffer_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_basic.result121
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_depth_basic.result79
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_manual_compaction_delay_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_optimizer_no_zero_cardinality_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_ignore_pk_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_read_filter_ts_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_rec_ts_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_snapshot_ts_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_default_cf_options_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delayed_write_rate_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_cf_basic.result6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_obsolete_files_period_micros_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_2pc_basic.result75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_bulk_load_api_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_insert_with_update_caching_basic.result75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_thread_tracking_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_read_filtering_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_write_thread_adaptive_yield_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_if_exists_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_on_suboptimal_collation_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_flush_log_at_trx_commit_basic.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_basic.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_cachetime_basic.result68
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_and_lzero_now_basic.result50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_now_basic.result50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_index_records_in_range_basic.result106
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_git_hash_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_hash_index_allow_collision_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_ignore_unknown_options_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_index_type_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_info_log_level_basic.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_io_write_timeout_basic.result86
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_is_fd_close_on_exec_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_keep_log_file_num_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_large_prefix_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_scanned_rows_basic.result170
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_wait_timeout_basic.result72
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_log_file_time_to_roll_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manifest_preallocation_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_compaction_threads_basic.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_wal_flush_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_master_skip_tx_api_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_background_jobs_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_latest_deadlocks_basic.result53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_log_file_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manifest_file_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manual_compactions_basic.result57
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_open_files_basic.result3
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_row_locks_basic.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_subcompactions_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_total_wal_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_buf_size_basic.result43
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_combine_read_size_basic.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_tmp_file_removal_delay_ms_basic.result93
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_new_table_reader_for_compaction_inputs_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_no_block_cache_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_override_cf_options_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_paranoid_checks_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pause_background_work_basic.result75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_perf_context_level_basic.result114
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_path_basic.result13
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_size_mb_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_print_snapshot_conflict_queries_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rate_limiter_bytes_per_sec_basic.result101
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_basic.result58
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_tables_basic.result49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_records_in_range_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_remove_mariabackup_checkpoint_basic.result4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_reset_stats_basic.result97
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rollback_on_timeout_basic.result97
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_seconds_between_stat_computes_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_signal_drop_index_thread_basic.result64
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sim_cache_size_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_bloom_filter_on_read_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_fill_cache_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_unique_check_tables_basic.result67
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sst_mgr_rate_bytes_per_sec_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_dump_period_sec_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_level_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_recalc_rate_basic.result53
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_store_row_debug_checksums_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_check_basic.result75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_exceptions_basic.result36
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_supported_compression_types_basic.result4
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_cache_numshardbits_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_stats_sampling_pct_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_tmpdir_basic.result29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_trace_sst_api_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_two_write_queues_basic.result14
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_unsafe_for_binlog_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options.result38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options_basic.result126
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_adaptive_mutex_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_clock_cache_basic.result19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_io_for_flush_and_compaction_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_reads_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_fsync_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_validate_tables_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_verify_row_debug_checksums_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_bytes_per_sync_basic.result85
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_dir_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_recovery_mode_basic.result46
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_size_limit_mb_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_ttl_seconds_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_whole_key_filtering_basic.result7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_batch_max_bytes_basic.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_disable_wal_basic.result114
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_ignore_missing_column_families_basic.result100
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_policy_basic.result15
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.pm21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/all_vars.test39
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/disabled.def5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_access_hint_on_compaction_start_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_advise_random_on_open_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_concurrent_memtable_write_basic.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_reads_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_writes_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_to_start_after_corruption_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_blind_delete_primary_key_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_cache_size_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_restart_interval_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_deviation_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_sk_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_unsorted_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_size_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bytes_per_sync_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_dump_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_high_pri_pool_ratio_basic.test24
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_blocks_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_with_high_priority_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_checksums_pct_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_collect_sst_properties_basic.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_in_the_middle_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_time_batch_for_recovery_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compact_cf_basic.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_readahead_size_basic.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_count_sd_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_file_size_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_window_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_checkpoint_basic.test29
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_if_missing_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_missing_column_families_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_datadir_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_db_write_buffer_size_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_depth_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_manual_compaction_delay_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_optimizer_no_zero_cardinality_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_ignore_pk_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_read_filter_ts_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_rec_ts_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_snapshot_ts_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_default_cf_options_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delayed_write_rate_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic-master.opt1
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic.test75
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_obsolete_files_period_micros_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_2pc_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_bulk_load_api_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_insert_with_update_caching_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_thread_tracking_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_read_filtering_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_write_thread_adaptive_yield_basic.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_if_exists_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_on_suboptimal_collation_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_flush_log_at_trx_commit_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_basic.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_cachetime_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_and_lzero_now_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_now_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_index_records_in_range_basic.test23
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_git_hash_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_hash_index_allow_collision_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_ignore_unknown_options_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_index_type_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_info_log_level_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_io_write_timeout_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_is_fd_close_on_exec_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_keep_log_file_num_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_large_prefix_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_scanned_rows_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_wait_timeout_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_log_file_time_to_roll_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manifest_preallocation_size_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_compaction_threads_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_wal_flush_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_master_skip_tx_api_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_background_jobs_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_latest_deadlocks_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_log_file_size_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manifest_file_size_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manual_compactions_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_open_files_basic.test8
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_row_locks_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_subcompactions_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_total_wal_size_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_buf_size_basic.test50
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_combine_read_size_basic.test32
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_tmp_file_removal_delay_ms_basic.test49
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_new_table_reader_for_compaction_inputs_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_no_block_cache_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_override_cf_options_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_paranoid_checks_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pause_background_work_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_perf_context_level_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_path_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_size_mb_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_print_snapshot_conflict_queries_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rate_limiter_bytes_per_sec_basic.test63
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_basic.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_tables_basic.test20
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_records_in_range_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_remove_mariabackup_checkpoint_basic.test5
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_reset_stats_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rollback_on_timeout_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_seconds_between_stat_computes_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_signal_drop_index_thread_basic.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sim_cache_size_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_bloom_filter_on_read_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_fill_cache_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_unique_check_tables_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sst_mgr_rate_bytes_per_sec_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_dump_period_sec_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_level_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_recalc_rate_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_store_row_debug_checksums_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_check_basic.test19
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_exceptions_basic.test35
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_supported_compression_types_basic.test7
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_cache_numshardbits_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_stats_sampling_pct_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_tmpdir_basic.test38
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_trace_sst_api_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_two_write_queues_basic.test16
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_unsafe_for_binlog_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options_basic.test119
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_adaptive_mutex_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_clock_cache_basic.test21
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_io_for_flush_and_compaction_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_reads_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_fsync_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_validate_tables_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_verify_row_debug_checksums_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_bytes_per_sync_basic.test22
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_dir_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_recovery_mode_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_size_limit_mb_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_ttl_seconds_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_whole_key_filtering_basic.test6
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_batch_max_bytes_basic.test26
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_disable_wal_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_ignore_missing_column_families_basic.test18
-rw-r--r--storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_policy_basic.test17
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/cache_index.rdiff71
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/checksum_table_live.rdiff13
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/cleanup_engine.inc25
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/define_engine.inc45
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/disabled.def27
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/index.rdiff60
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/index_type_btree.rdiff60
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/index_type_hash.rdiff60
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/mask_engine.inc15
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/misc.rdiff34
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/parts/checksum_table.rdiff13
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/parts/create_table.rdiff20
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/parts/disabled.def3
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/parts/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/show_engine.rdiff15
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/show_table_status.rdiff20
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/tbl_opt_insert_method.rdiff11
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/tbl_opt_union.rdiff16
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/tbl_temporary.rdiff24
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/truncate_table.rdiff24
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/delete.rdiff10
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/disabled.def4
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/insert.rdiff24
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/level_read_committed.rdiff10
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/level_repeatable_read.rdiff35
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/suite.opt1
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/trx/update.rdiff38
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/type_binary_indexes.rdiff11
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/type_bit_indexes.rdiff20
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/type_enum_indexes.rdiff11
-rw-r--r--storage/rocksdb/mysql-test/storage_engine/type_set_indexes.rdiff20
-rw-r--r--storage/rocksdb/nosql_access.cc53
-rw-r--r--storage/rocksdb/nosql_access.h36
-rw-r--r--storage/rocksdb/properties_collector.cc546
-rw-r--r--storage/rocksdb/properties_collector.h215
-rw-r--r--storage/rocksdb/rdb_buff.h549
-rw-r--r--storage/rocksdb/rdb_cf_manager.cc273
-rw-r--r--storage/rocksdb/rdb_cf_manager.h108
-rw-r--r--storage/rocksdb/rdb_cf_options.cc341
-rw-r--r--storage/rocksdb/rdb_cf_options.h104
-rw-r--r--storage/rocksdb/rdb_compact_filter.h220
-rw-r--r--storage/rocksdb/rdb_comparator.h85
-rw-r--r--storage/rocksdb/rdb_converter.cc838
-rw-r--r--storage/rocksdb/rdb_converter.h247
-rw-r--r--storage/rocksdb/rdb_datadic.cc5411
-rw-r--r--storage/rocksdb/rdb_datadic.h1637
-rw-r--r--storage/rocksdb/rdb_global.h392
-rw-r--r--storage/rocksdb/rdb_i_s.cc1997
-rw-r--r--storage/rocksdb/rdb_i_s.h37
-rw-r--r--storage/rocksdb/rdb_index_merge.cc630
-rw-r--r--storage/rocksdb/rdb_index_merge.h227
-rw-r--r--storage/rocksdb/rdb_io_watchdog.cc240
-rw-r--r--storage/rocksdb/rdb_io_watchdog.h119
-rw-r--r--storage/rocksdb/rdb_mariadb_port.h55
-rw-r--r--storage/rocksdb/rdb_mariadb_server_port.cc122
-rw-r--r--storage/rocksdb/rdb_mariadb_server_port.h76
-rw-r--r--storage/rocksdb/rdb_mutex_wrapper.cc214
-rw-r--r--storage/rocksdb/rdb_mutex_wrapper.h143
-rw-r--r--storage/rocksdb/rdb_perf_context.cc285
-rw-r--r--storage/rocksdb/rdb_perf_context.h168
-rw-r--r--storage/rocksdb/rdb_psi.cc115
-rw-r--r--storage/rocksdb/rdb_psi.h58
-rw-r--r--storage/rocksdb/rdb_source_revision.h1
-rw-r--r--storage/rocksdb/rdb_source_revision.h.in1
-rw-r--r--storage/rocksdb/rdb_sst_info.cc562
-rw-r--r--storage/rocksdb/rdb_sst_info.h265
-rw-r--r--storage/rocksdb/rdb_threads.cc83
-rw-r--r--storage/rocksdb/rdb_threads.h195
-rw-r--r--storage/rocksdb/rdb_utils.cc369
-rw-r--r--storage/rocksdb/rdb_utils.h335
-rw-r--r--storage/rocksdb/rocksdb-range-access.txt292
-rw-r--r--storage/rocksdb/rocksdb/.clang-format5
-rw-r--r--storage/rocksdb/rocksdb/.gitignore84
-rw-r--r--storage/rocksdb/rocksdb/.lgtm.yml4
-rw-r--r--storage/rocksdb/rocksdb/.travis.yml135
-rw-r--r--storage/rocksdb/rocksdb/.watchmanconfig6
-rw-r--r--storage/rocksdb/rocksdb/AUTHORS12
-rw-r--r--storage/rocksdb/rocksdb/CMakeLists.txt1178
-rw-r--r--storage/rocksdb/rocksdb/CODE_OF_CONDUCT.md77
-rw-r--r--storage/rocksdb/rocksdb/CONTRIBUTING.md17
-rw-r--r--storage/rocksdb/rocksdb/COPYING339
-rw-r--r--storage/rocksdb/rocksdb/DEFAULT_OPTIONS_HISTORY.md24
-rw-r--r--storage/rocksdb/rocksdb/DUMP_FORMAT.md16
-rw-r--r--storage/rocksdb/rocksdb/HISTORY.md1137
-rw-r--r--storage/rocksdb/rocksdb/INSTALL.md202
-rw-r--r--storage/rocksdb/rocksdb/LANGUAGE-BINDINGS.md22
-rw-r--r--storage/rocksdb/rocksdb/LICENSE.Apache202
-rw-r--r--storage/rocksdb/rocksdb/LICENSE.leveldb29
-rw-r--r--storage/rocksdb/rocksdb/Makefile2185
-rw-r--r--storage/rocksdb/rocksdb/README.md31
-rw-r--r--storage/rocksdb/rocksdb/ROCKSDB_LITE.md21
-rw-r--r--storage/rocksdb/rocksdb/TARGETS1530
-rw-r--r--storage/rocksdb/rocksdb/USERS.md108
-rw-r--r--storage/rocksdb/rocksdb/Vagrantfile39
-rw-r--r--storage/rocksdb/rocksdb/WINDOWS_PORT.md228
-rw-r--r--storage/rocksdb/rocksdb/appveyor.yml75
-rw-r--r--storage/rocksdb/rocksdb/buckifier/buckify_rocksdb.py236
-rwxr-xr-xstorage/rocksdb/rocksdb/buckifier/rocks_test_runner.sh6
-rw-r--r--storage/rocksdb/rocksdb/buckifier/targets_builder.py80
-rw-r--r--storage/rocksdb/rocksdb/buckifier/targets_cfg.py181
-rw-r--r--storage/rocksdb/rocksdb/buckifier/util.py119
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/amalgamate.py111
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/build_detect_platform730
-rw-r--r--storage/rocksdb/rocksdb/build_tools/dependencies.sh19
-rw-r--r--storage/rocksdb/rocksdb/build_tools/dependencies_4.8.1.sh20
-rw-r--r--storage/rocksdb/rocksdb/build_tools/dependencies_platform007.sh20
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/dockerbuild.sh3
-rw-r--r--storage/rocksdb/rocksdb/build_tools/error_filter.py177
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/fb_compile_mongo.sh55
-rw-r--r--storage/rocksdb/rocksdb/build_tools/fbcode_config.sh165
-rw-r--r--storage/rocksdb/rocksdb/build_tools/fbcode_config4.8.1.sh118
-rw-r--r--storage/rocksdb/rocksdb/build_tools/fbcode_config_platform007.sh170
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/format-diff.sh138
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/gnu_parallel7936
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/make_package.sh134
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/precommit_checker.py209
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/regression_build_test.sh414
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/rocksdb-lego-determinator1068
-rw-r--r--storage/rocksdb/rocksdb/build_tools/run_ci_db_test.ps1487
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/setup_centos7.sh44
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/update_dependencies.sh182
-rwxr-xr-xstorage/rocksdb/rocksdb/build_tools/version.sh23
-rw-r--r--storage/rocksdb/rocksdb/cache/cache_bench.cc281
-rw-r--r--storage/rocksdb/rocksdb/cache/cache_test.cc773
-rw-r--r--storage/rocksdb/rocksdb/cache/clock_cache.cc761
-rw-r--r--storage/rocksdb/rocksdb/cache/clock_cache.h16
-rw-r--r--storage/rocksdb/rocksdb/cache/lru_cache.cc574
-rw-r--r--storage/rocksdb/rocksdb/cache/lru_cache.h339
-rw-r--r--storage/rocksdb/rocksdb/cache/lru_cache_test.cc198
-rw-r--r--storage/rocksdb/rocksdb/cache/sharded_cache.cc162
-rw-r--r--storage/rocksdb/rocksdb/cache/sharded_cache.h111
-rw-r--r--storage/rocksdb/rocksdb/cmake/RocksDBConfig.cmake.in3
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/FindJeMalloc.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/FindNUMA.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/FindTBB.cmake33
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/Findgflags.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/Findlz4.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/Findsnappy.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/Findzstd.cmake29
-rw-r--r--storage/rocksdb/rocksdb/cmake/modules/ReadVersion.cmake10
-rwxr-xr-xstorage/rocksdb/rocksdb/coverage/coverage_test.sh79
-rw-r--r--storage/rocksdb/rocksdb/coverage/parse_gcov_output.py119
-rw-r--r--storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.cc106
-rw-r--r--storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.h112
-rw-r--r--storage/rocksdb/rocksdb/db/blob_index.h179
-rw-r--r--storage/rocksdb/rocksdb/db/builder.cc263
-rw-r--r--storage/rocksdb/rocksdb/db/builder.h88
-rw-r--r--storage/rocksdb/rocksdb/db/c.cc4451
-rw-r--r--storage/rocksdb/rocksdb/db/c_test.c1866
-rw-r--r--storage/rocksdb/rocksdb/db/column_family.cc1523
-rw-r--r--storage/rocksdb/rocksdb/db/column_family.h757
-rw-r--r--storage/rocksdb/rocksdb/db/column_family_test.cc3387
-rw-r--r--storage/rocksdb/rocksdb/db/compact_files_test.cc421
-rw-r--r--storage/rocksdb/rocksdb/db/compacted_db_impl.cc160
-rw-r--r--storage/rocksdb/rocksdb/db/compacted_db_impl.h113
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction.cc564
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction.h384
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_iteration_stats.h37
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_iterator.cc774
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_iterator.h240
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc976
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_job.cc1700
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_job.h198
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_job_stats_test.cc1043
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_job_test.cc1082
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker.cc1131
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker.h313
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc242
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.h53
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc558
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.h32
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc1741
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc1105
-rw-r--r--storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.h31
-rw-r--r--storage/rocksdb/rocksdb/db/comparator_db_test.cc660
-rw-r--r--storage/rocksdb/rocksdb/db/convenience.cc77
-rw-r--r--storage/rocksdb/rocksdb/db/corruption_test.cc613
-rw-r--r--storage/rocksdb/rocksdb/db/cuckoo_table_db_test.cc351
-rw-r--r--storage/rocksdb/rocksdb/db/db_basic_test.cc2545
-rw-r--r--storage/rocksdb/rocksdb/db/db_blob_index_test.cc436
-rw-r--r--storage/rocksdb/rocksdb/db/db_block_cache_test.cc761
-rw-r--r--storage/rocksdb/rocksdb/db/db_bloom_filter_test.cc1910
-rw-r--r--storage/rocksdb/rocksdb/db/db_compaction_filter_test.cc872
-rw-r--r--storage/rocksdb/rocksdb/db/db_compaction_test.cc5167
-rw-r--r--storage/rocksdb/rocksdb/db/db_dynamic_level_test.cc505
-rw-r--r--storage/rocksdb/rocksdb/db/db_encryption_test.cc122
-rw-r--r--storage/rocksdb/rocksdb/db/db_filesnapshot.cc177
-rw-r--r--storage/rocksdb/rocksdb/db/db_flush_test.cc784
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl.cc4550
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl.h2107
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc3116
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_debug.cc294
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_experimental.cc151
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_files.cc667
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_open.cc1651
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.cc221
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.h137
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc671
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h333
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_impl_write.cc1839
-rw-r--r--storage/rocksdb/rocksdb/db/db_impl/db_secondary_test.cc869
-rw-r--r--storage/rocksdb/rocksdb/db/db_info_dumper.cc123
-rw-r--r--storage/rocksdb/rocksdb/db/db_info_dumper.h14
-rw-r--r--storage/rocksdb/rocksdb/db/db_inplace_update_test.cc177
-rw-r--r--storage/rocksdb/rocksdb/db/db_io_failure_test.cc568
-rw-r--r--storage/rocksdb/rocksdb/db/db_iter.cc1310
-rw-r--r--storage/rocksdb/rocksdb/db/db_iter.h344
-rw-r--r--storage/rocksdb/rocksdb/db/db_iter_stress_test.cc654
-rw-r--r--storage/rocksdb/rocksdb/db/db_iter_test.cc3175
-rw-r--r--storage/rocksdb/rocksdb/db/db_iterator_test.cc2998
-rw-r--r--storage/rocksdb/rocksdb/db/db_log_iter_test.cc294
-rw-r--r--storage/rocksdb/rocksdb/db/db_memtable_test.cc340
-rw-r--r--storage/rocksdb/rocksdb/db/db_merge_operand_test.cc240
-rw-r--r--storage/rocksdb/rocksdb/db/db_merge_operator_test.cc666
-rw-r--r--storage/rocksdb/rocksdb/db/db_options_test.cc870
-rw-r--r--storage/rocksdb/rocksdb/db/db_properties_test.cc1711
-rw-r--r--storage/rocksdb/rocksdb/db/db_range_del_test.cc1660
-rw-r--r--storage/rocksdb/rocksdb/db/db_sst_test.cc1227
-rw-r--r--storage/rocksdb/rocksdb/db/db_statistics_test.cc149
-rw-r--r--storage/rocksdb/rocksdb/db/db_table_properties_test.cc336
-rw-r--r--storage/rocksdb/rocksdb/db/db_tailing_iter_test.cc547
-rw-r--r--storage/rocksdb/rocksdb/db/db_test.cc6605
-rw-r--r--storage/rocksdb/rocksdb/db/db_test2.cc4695
-rw-r--r--storage/rocksdb/rocksdb/db/db_test_util.cc1564
-rw-r--r--storage/rocksdb/rocksdb/db/db_test_util.h1000
-rw-r--r--storage/rocksdb/rocksdb/db/db_universal_compaction_test.cc2254
-rw-r--r--storage/rocksdb/rocksdb/db/db_wal_test.cc1586
-rw-r--r--storage/rocksdb/rocksdb/db/db_write_test.cc329
-rw-r--r--storage/rocksdb/rocksdb/db/dbformat.cc197
-rw-r--r--storage/rocksdb/rocksdb/db/dbformat.h671
-rw-r--r--storage/rocksdb/rocksdb/db/dbformat_test.cc207
-rw-r--r--storage/rocksdb/rocksdb/db/deletefile_test.cc571
-rw-r--r--storage/rocksdb/rocksdb/db/error_handler.cc344
-rw-r--r--storage/rocksdb/rocksdb/db/error_handler.h75
-rw-r--r--storage/rocksdb/rocksdb/db/error_handler_test.cc871
-rw-r--r--storage/rocksdb/rocksdb/db/event_helpers.cc223
-rw-r--r--storage/rocksdb/rocksdb/db/event_helpers.h55
-rw-r--r--storage/rocksdb/rocksdb/db/experimental.cc50
-rw-r--r--storage/rocksdb/rocksdb/db/external_sst_file_basic_test.cc1128
-rw-r--r--storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.cc731
-rw-r--r--storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.h180
-rw-r--r--storage/rocksdb/rocksdb/db/external_sst_file_test.cc2832
-rw-r--r--storage/rocksdb/rocksdb/db/fault_injection_test.cc555
-rw-r--r--storage/rocksdb/rocksdb/db/file_indexer.cc216
-rw-r--r--storage/rocksdb/rocksdb/db/file_indexer.h142
-rw-r--r--storage/rocksdb/rocksdb/db/file_indexer_test.cc350
-rw-r--r--storage/rocksdb/rocksdb/db/filename_test.cc180
-rw-r--r--storage/rocksdb/rocksdb/db/flush_job.cc462
-rw-r--r--storage/rocksdb/rocksdb/db/flush_job.h158
-rw-r--r--storage/rocksdb/rocksdb/db/flush_job_test.cc498
-rw-r--r--storage/rocksdb/rocksdb/db/flush_scheduler.cc86
-rw-r--r--storage/rocksdb/rocksdb/db/flush_scheduler.h54
-rw-r--r--storage/rocksdb/rocksdb/db/forward_iterator.cc975
-rw-r--r--storage/rocksdb/rocksdb/db/forward_iterator.h160
-rw-r--r--storage/rocksdb/rocksdb/db/forward_iterator_bench.cc377
-rw-r--r--storage/rocksdb/rocksdb/db/import_column_family_job.cc276
-rw-r--r--storage/rocksdb/rocksdb/db/import_column_family_job.h72
-rw-r--r--storage/rocksdb/rocksdb/db/import_column_family_test.cc567
-rw-r--r--storage/rocksdb/rocksdb/db/internal_stats.cc1424
-rw-r--r--storage/rocksdb/rocksdb/db/internal_stats.h697
-rw-r--r--storage/rocksdb/rocksdb/db/job_context.h219
-rw-r--r--storage/rocksdb/rocksdb/db/listener_test.cc1042
-rw-r--r--storage/rocksdb/rocksdb/db/log_format.h48
-rw-r--r--storage/rocksdb/rocksdb/db/log_reader.cc624
-rw-r--r--storage/rocksdb/rocksdb/db/log_reader.h189
-rw-r--r--storage/rocksdb/rocksdb/db/log_test.cc928
-rw-r--r--storage/rocksdb/rocksdb/db/log_writer.cc162
-rw-r--r--storage/rocksdb/rocksdb/db/log_writer.h114
-rw-r--r--storage/rocksdb/rocksdb/db/logs_with_prep_tracker.cc67
-rw-r--r--storage/rocksdb/rocksdb/db/logs_with_prep_tracker.h63
-rw-r--r--storage/rocksdb/rocksdb/db/lookup_key.h66
-rw-r--r--storage/rocksdb/rocksdb/db/malloc_stats.cc54
-rw-r--r--storage/rocksdb/rocksdb/db/malloc_stats.h24
-rw-r--r--storage/rocksdb/rocksdb/db/manual_compaction_test.cc160
-rw-r--r--storage/rocksdb/rocksdb/db/memtable.cc1122
-rw-r--r--storage/rocksdb/rocksdb/db/memtable.h542
-rw-r--r--storage/rocksdb/rocksdb/db/memtable_list.cc771
-rw-r--r--storage/rocksdb/rocksdb/db/memtable_list.h422
-rw-r--r--storage/rocksdb/rocksdb/db/memtable_list_test.cc922
-rw-r--r--storage/rocksdb/rocksdb/db/merge_context.h134
-rw-r--r--storage/rocksdb/rocksdb/db/merge_helper.cc417
-rw-r--r--storage/rocksdb/rocksdb/db/merge_helper.h194
-rw-r--r--storage/rocksdb/rocksdb/db/merge_helper_test.cc290
-rw-r--r--storage/rocksdb/rocksdb/db/merge_operator.cc86
-rw-r--r--storage/rocksdb/rocksdb/db/merge_test.cc504
-rw-r--r--storage/rocksdb/rocksdb/db/obsolete_files_test.cc222
-rw-r--r--storage/rocksdb/rocksdb/db/options_file_test.cc119
-rw-r--r--storage/rocksdb/rocksdb/db/perf_context_test.cc981
-rw-r--r--storage/rocksdb/rocksdb/db/pinned_iterators_manager.h87
-rw-r--r--storage/rocksdb/rocksdb/db/plain_table_db_test.cc1375
-rw-r--r--storage/rocksdb/rocksdb/db/pre_release_callback.h38
-rw-r--r--storage/rocksdb/rocksdb/db/prefix_test.cc895
-rw-r--r--storage/rocksdb/rocksdb/db/range_del_aggregator.cc484
-rw-r--r--storage/rocksdb/rocksdb/db/range_del_aggregator.h441
-rw-r--r--storage/rocksdb/rocksdb/db/range_del_aggregator_bench.cc260
-rw-r--r--storage/rocksdb/rocksdb/db/range_del_aggregator_test.cc709
-rw-r--r--storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.cc439
-rw-r--r--storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.h256
-rw-r--r--storage/rocksdb/rocksdb/db/range_tombstone_fragmenter_test.cc552
-rw-r--r--storage/rocksdb/rocksdb/db/read_callback.h53
-rw-r--r--storage/rocksdb/rocksdb/db/repair.cc691
-rw-r--r--storage/rocksdb/rocksdb/db/repair_test.cc369
-rw-r--r--storage/rocksdb/rocksdb/db/snapshot_checker.h61
-rw-r--r--storage/rocksdb/rocksdb/db/snapshot_impl.cc26
-rw-r--r--storage/rocksdb/rocksdb/db/snapshot_impl.h167
-rw-r--r--storage/rocksdb/rocksdb/db/table_cache.cc668
-rw-r--r--storage/rocksdb/rocksdb/db/table_cache.h226
-rw-r--r--storage/rocksdb/rocksdb/db/table_properties_collector.cc74
-rw-r--r--storage/rocksdb/rocksdb/db/table_properties_collector.h107
-rw-r--r--storage/rocksdb/rocksdb/db/table_properties_collector_test.cc515
-rw-r--r--storage/rocksdb/rocksdb/db/transaction_log_impl.cc315
-rw-r--r--storage/rocksdb/rocksdb/db/transaction_log_impl.h127
-rw-r--r--storage/rocksdb/rocksdb/db/trim_history_scheduler.cc54
-rw-r--r--storage/rocksdb/rocksdb/db/trim_history_scheduler.h44
-rw-r--r--storage/rocksdb/rocksdb/db/version_builder.cc545
-rw-r--r--storage/rocksdb/rocksdb/db/version_builder.h48
-rw-r--r--storage/rocksdb/rocksdb/db/version_builder_test.cc349
-rw-r--r--storage/rocksdb/rocksdb/db/version_edit.cc802
-rw-r--r--storage/rocksdb/rocksdb/db/version_edit.h438
-rw-r--r--storage/rocksdb/rocksdb/db/version_edit_test.cc286
-rw-r--r--storage/rocksdb/rocksdb/db/version_set.cc6005
-rw-r--r--storage/rocksdb/rocksdb/db/version_set.h1251
-rw-r--r--storage/rocksdb/rocksdb/db/version_set_test.cc1287
-rw-r--r--storage/rocksdb/rocksdb/db/wal_manager.cc510
-rw-r--r--storage/rocksdb/rocksdb/db/wal_manager.h114
-rw-r--r--storage/rocksdb/rocksdb/db/wal_manager_test.cc338
-rw-r--r--storage/rocksdb/rocksdb/db/write_batch.cc2092
-rw-r--r--storage/rocksdb/rocksdb/db/write_batch_base.cc94
-rw-r--r--storage/rocksdb/rocksdb/db/write_batch_internal.h250
-rw-r--r--storage/rocksdb/rocksdb/db/write_batch_test.cc888
-rw-r--r--storage/rocksdb/rocksdb/db/write_callback.h27
-rw-r--r--storage/rocksdb/rocksdb/db/write_callback_test.cc452
-rw-r--r--storage/rocksdb/rocksdb/db/write_controller.cc128
-rw-r--r--storage/rocksdb/rocksdb/db/write_controller.h144
-rw-r--r--storage/rocksdb/rocksdb/db/write_controller_test.cc135
-rw-r--r--storage/rocksdb/rocksdb/db/write_thread.cc777
-rw-r--r--storage/rocksdb/rocksdb/db/write_thread.h431
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/CMakeLists.txt14
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/batched_ops_stress.cc341
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/cf_consistency_stress.cc583
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress.cc23
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc225
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.h511
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc163
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.h17
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_env_wrapper.h36
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc655
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_listener.h222
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.cc18
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h390
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_stat.h213
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc2133
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h231
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/db_stress_tool.cc252
-rw-r--r--storage/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc631
-rw-r--r--storage/rocksdb/rocksdb/defs.bzl42
-rw-r--r--storage/rocksdb/rocksdb/docs/.gitignore8
-rw-r--r--storage/rocksdb/rocksdb/docs/CNAME1
-rw-r--r--storage/rocksdb/rocksdb/docs/CONTRIBUTING.md115
-rw-r--r--storage/rocksdb/rocksdb/docs/Gemfile2
-rw-r--r--storage/rocksdb/rocksdb/docs/Gemfile.lock146
-rw-r--r--storage/rocksdb/rocksdb/docs/LICENSE-DOCUMENTATION385
-rw-r--r--storage/rocksdb/rocksdb/docs/README.md80
-rw-r--r--storage/rocksdb/rocksdb/docs/TEMPLATE-INFORMATION.md17
-rw-r--r--storage/rocksdb/rocksdb/docs/_config.yml85
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/authors.yml70
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/features.yml19
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/nav.yml30
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/nav_docs.yml3
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/powered_by.yml1
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/powered_by_highlight.yml1
-rw-r--r--storage/rocksdb/rocksdb/docs/_data/promo.yml6
-rw-r--r--storage/rocksdb/rocksdb/docs/_docs/faq.md48
-rw-r--r--storage/rocksdb/rocksdb/docs/_docs/getting-started.md78
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/blog_pagination.html28
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/content/gridblocks.html5
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/content/items/gridblock.html37
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/doc.html25
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/doc_paging.html0
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/footer.html33
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/head.html23
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/header.html19
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/hero.html0
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/home_header.html22
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/katex_import.html3
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/katex_render.html210
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav.html37
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav.html64
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group.html19
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group_item.html1
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav/header_nav.html30
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/nav_search.html15
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/all_share.html3
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/ascii_cinema.html2
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/button.html6
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/github_star.html4
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/github_watch.html4
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/google_share.html5
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/iframe.html6
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/like_button.html18
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/plugin_row.html5
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/post_social_plugins.html41
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/slideshow.html88
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_follow.html12
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_share.html11
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/post.html40
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/powered_by.html28
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/social_plugins.html31
-rw-r--r--storage/rocksdb/rocksdb/docs/_includes/ui/button.html1
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/basic.html12
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/blog.html11
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/blog_default.html14
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/default.html12
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/doc_default.html14
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/doc_page.html10
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/docs.html5
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/home.html17
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/page.html3
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/plain.html10
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/post.html8
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/redirect.html6
-rw-r--r--storage/rocksdb/rocksdb/docs/_layouts/top-level.html10
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown135
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown54
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown53
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown40
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown28
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-05-14-lock.markdown88
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown24
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown20
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown47
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown89
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown30
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown34
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown74
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown52
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown38
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown112
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown41
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown20
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown16
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown10
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown92
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown78
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown30
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown29
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown193
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown45
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown244
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown51
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown41
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown20
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown24
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown60
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown48
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown49
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown26
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown15
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown50
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown22
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown34
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown106
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown39
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown60
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown22
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown22
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown37
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-08-25-flushwal.markdown26
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown25
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown28
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown41
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown22
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown58
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown118
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2018-11-21-delete-range.markdown292
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown36
-rw-r--r--storage/rocksdb/rocksdb/docs/_posts/2019-08-15-unordered-write.markdown56
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_base.scss492
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_blog.scss47
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_buttons.scss47
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_footer.scss82
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_gridBlock.scss115
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_header.scss138
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_poweredby.scss69
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_promo.scss55
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_react_docs_nav.scss332
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_react_header_nav.scss141
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_reset.scss43
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_search.scss142
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_slideshow.scss48
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_syntax-highlighting.scss129
-rw-r--r--storage/rocksdb/rocksdb/docs/_sass/_tables.scss47
-rw-r--r--storage/rocksdb/rocksdb/docs/_top-level/support.md22
-rw-r--r--storage/rocksdb/rocksdb/docs/blog/all.html20
-rw-r--r--storage/rocksdb/rocksdb/docs/blog/index.html12
-rw-r--r--storage/rocksdb/rocksdb/docs/css/main.scss149
-rw-r--r--storage/rocksdb/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md21
-rw-r--r--storage/rocksdb/rocksdb/docs/doc-type-examples/docs-hello-world.md12
-rw-r--r--storage/rocksdb/rocksdb/docs/doc-type-examples/top-level-example.md8
-rw-r--r--storage/rocksdb/rocksdb/docs/docs/index.html6
-rw-r--r--storage/rocksdb/rocksdb/docs/feed.xml30
-rw-r--r--storage/rocksdb/rocksdb/docs/index.md9
-rw-r--r--storage/rocksdb/rocksdb/docs/static/favicon.pngbin0 -> 3927 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woffbin0 -> 70460 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff2bin0 -> 43456 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woffbin0 -> 72372 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2bin0 -> 44316 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woffbin0 -> 74708 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2bin0 -> 45388 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woffbin0 -> 72604 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff2bin0 -> 43468 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woffbin0 -> 72456 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2bin0 -> 43760 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpgbin0 -> 26670 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/binaryseek.pngbin0 -> 68892 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/bloom_fp_vs_bpk.pngbin0 -> 51924 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/compaction/full-range.pngbin0 -> 193353 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/compaction/l0-l1-contend.pngbin0 -> 203828 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/compaction/l1-l2-contend.pngbin0 -> 230195 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/compaction/part-range-old.pngbin0 -> 165547 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.pngbin0 -> 68892 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.pngbin0 -> 31288 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.pngbin0 -> 84389 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.pngbin0 -> 44540 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.pngbin0 -> 35170 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_collapsed.pngbin0 -> 29265 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_key_schema.pngbin0 -> 55178 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_sst_blocks.pngbin0 -> 25596 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_uncollapsed.pngbin0 -> 25358 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_write_path.pngbin0 -> 109609 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-blockindex.jpgbin0 -> 55324 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-fileindex.jpgbin0 -> 54922 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-filelayout.jpgbin0 -> 47197 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-readiopath.jpgbin0 -> 16381 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-tieredstorage.jpgbin0 -> 78208 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/pcache-writeiopath.jpgbin0 -> 22616 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/promo-adapt.svg8
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/promo-flash.svg28
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/promo-operations.svg6
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/promo-performance.svg134
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.pngbin0 -> 176624 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.pngbin0 -> 80439 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-series.pngbin0 -> 310422 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/images/tree_example1.pngbin0 -> 17804 bytes
-rw-r--r--storage/rocksdb/rocksdb/docs/static/logo.svg76
-rw-r--r--storage/rocksdb/rocksdb/docs/static/og_image.pngbin0 -> 17639 bytes
-rw-r--r--storage/rocksdb/rocksdb/env/composite_env_wrapper.h1117
-rw-r--r--storage/rocksdb/rocksdb/env/env.cc475
-rw-r--r--storage/rocksdb/rocksdb/env/env_basic_test.cc354
-rw-r--r--storage/rocksdb/rocksdb/env/env_chroot.cc321
-rw-r--r--storage/rocksdb/rocksdb/env/env_chroot.h22
-rw-r--r--storage/rocksdb/rocksdb/env/env_encryption.cc937
-rw-r--r--storage/rocksdb/rocksdb/env/env_hdfs.cc636
-rw-r--r--storage/rocksdb/rocksdb/env/env_posix.cc527
-rw-r--r--storage/rocksdb/rocksdb/env/env_test.cc1895
-rw-r--r--storage/rocksdb/rocksdb/env/file_system.cc110
-rw-r--r--storage/rocksdb/rocksdb/env/fs_posix.cc913
-rw-r--r--storage/rocksdb/rocksdb/env/io_posix.cc1352
-rw-r--r--storage/rocksdb/rocksdb/env/io_posix.h326
-rw-r--r--storage/rocksdb/rocksdb/env/mock_env.cc774
-rw-r--r--storage/rocksdb/rocksdb/env/mock_env.h114
-rw-r--r--storage/rocksdb/rocksdb/env/mock_env_test.cc85
-rw-r--r--storage/rocksdb/rocksdb/examples/.gitignore9
-rw-r--r--storage/rocksdb/rocksdb/examples/Makefile53
-rw-r--r--storage/rocksdb/rocksdb/examples/README.md2
-rw-r--r--storage/rocksdb/rocksdb/examples/c_simple_example.c79
-rw-r--r--storage/rocksdb/rocksdb/examples/column_families_example.cc72
-rw-r--r--storage/rocksdb/rocksdb/examples/compact_files_example.cc171
-rw-r--r--storage/rocksdb/rocksdb/examples/compaction_filter_example.cc88
-rw-r--r--storage/rocksdb/rocksdb/examples/multi_processes_example.cc395
-rw-r--r--storage/rocksdb/rocksdb/examples/optimistic_transaction_example.cc180
-rw-r--r--storage/rocksdb/rocksdb/examples/options_file_example.cc113
-rw-r--r--storage/rocksdb/rocksdb/examples/rocksdb_option_file_example.ini144
-rw-r--r--storage/rocksdb/rocksdb/examples/simple_example.cc83
-rw-r--r--storage/rocksdb/rocksdb/examples/transaction_example.cc186
-rw-r--r--storage/rocksdb/rocksdb/file/delete_scheduler.cc357
-rw-r--r--storage/rocksdb/rocksdb/file/delete_scheduler.h141
-rw-r--r--storage/rocksdb/rocksdb/file/delete_scheduler_test.cc693
-rw-r--r--storage/rocksdb/rocksdb/file/file_prefetch_buffer.cc136
-rw-r--r--storage/rocksdb/rocksdb/file/file_prefetch_buffer.h97
-rw-r--r--storage/rocksdb/rocksdb/file/file_util.cc124
-rw-r--r--storage/rocksdb/rocksdb/file/file_util.h33
-rw-r--r--storage/rocksdb/rocksdb/file/filename.cc456
-rw-r--r--storage/rocksdb/rocksdb/file/filename.h185
-rw-r--r--storage/rocksdb/rocksdb/file/random_access_file_reader.cc189
-rw-r--r--storage/rocksdb/rocksdb/file/random_access_file_reader.h120
-rw-r--r--storage/rocksdb/rocksdb/file/read_write_util.cc67
-rw-r--r--storage/rocksdb/rocksdb/file/read_write_util.h34
-rw-r--r--storage/rocksdb/rocksdb/file/readahead_raf.cc162
-rw-r--r--storage/rocksdb/rocksdb/file/readahead_raf.h27
-rw-r--r--storage/rocksdb/rocksdb/file/sequence_file_reader.cc237
-rw-r--r--storage/rocksdb/rocksdb/file/sequence_file_reader.h67
-rw-r--r--storage/rocksdb/rocksdb/file/sst_file_manager_impl.cc558
-rw-r--r--storage/rocksdb/rocksdb/file/sst_file_manager_impl.h197
-rw-r--r--storage/rocksdb/rocksdb/file/writable_file_writer.cc429
-rw-r--r--storage/rocksdb/rocksdb/file/writable_file_writer.h171
-rw-r--r--storage/rocksdb/rocksdb/hdfs/README23
-rw-r--r--storage/rocksdb/rocksdb/hdfs/env_hdfs.h384
-rwxr-xr-xstorage/rocksdb/rocksdb/hdfs/setup.sh9
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/advanced_options.h731
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/c.h1801
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/cache.h278
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/cleanable.h71
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/compaction_filter.h212
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h96
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/comparator.h122
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/concurrent_task_limiter.h46
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/convenience.h351
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/db.h1525
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/db_bench_tool.h11
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/db_dump_tool.h45
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/db_stress_tool.h11
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/env.h1589
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/env_encryption.h206
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/experimental.h29
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/file_checksum.h86
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/file_system.h1358
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/filter_policy.h200
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/flush_block_policy.h61
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/io_status.h232
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/iostats_context.h56
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/iterator.h119
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/ldb_tool.h43
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/listener.h491
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/memory_allocator.h77
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/memtablerep.h385
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/merge_operator.h257
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/metadata.h151
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/options.h1587
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/perf_context.h232
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/perf_level.h35
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/persistent_cache.h67
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/rate_limiter.h139
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/rocksdb_namespace.h10
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/slice.h269
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/slice_transform.h103
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/snapshot.h48
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/sst_dump_tool.h19
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/sst_file_manager.h132
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/sst_file_reader.h47
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/sst_file_writer.h139
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/statistics.h548
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/stats_history.h69
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/status.h386
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/table.h607
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/table_properties.h250
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/thread_status.h188
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/threadpool.h58
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/trace_reader_writer.h48
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/transaction_log.h121
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/types.h54
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/universal_compaction.h86
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/backupable_db.h341
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/checkpoint.h57
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/convenience.h10
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/db_ttl.h72
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/debug.h49
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/env_librados.h175
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/env_mirror.h180
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/info_log_finder.h19
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd.h277
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h71
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/leveldb_options.h146
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h43
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_util.h55
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/memory_util.h50
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/object_registry.h205
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h98
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/option_change_migration.h19
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/options_util.h102
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/sim_cache.h94
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h465
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/table_properties_collectors.h74
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction.h540
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db.h309
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db_mutex.h92
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/utility_db.h34
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/utilities/write_batch_with_index.h278
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/version.h16
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/wal_filter.h102
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/write_batch.h377
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/write_batch_base.h127
-rw-r--r--storage/rocksdb/rocksdb/include/rocksdb/write_buffer_manager.h102
-rw-r--r--storage/rocksdb/rocksdb/issue_template.md7
-rw-r--r--storage/rocksdb/rocksdb/java/CMakeLists.txt502
-rw-r--r--storage/rocksdb/rocksdb/java/HISTORY-JAVA.md86
-rw-r--r--storage/rocksdb/rocksdb/java/Makefile321
-rw-r--r--storage/rocksdb/rocksdb/java/RELEASE.md59
-rw-r--r--storage/rocksdb/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java1653
-rw-r--r--storage/rocksdb/rocksdb/java/crossbuild/Vagrantfile51
-rwxr-xr-xstorage/rocksdb/rocksdb/java/crossbuild/build-linux-alpine.sh70
-rwxr-xr-xstorage/rocksdb/rocksdb/java/crossbuild/build-linux-centos.sh38
-rwxr-xr-xstorage/rocksdb/rocksdb/java/crossbuild/build-linux.sh15
-rwxr-xr-xstorage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-alpine.sh18
-rwxr-xr-xstorage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-centos.sh34
-rwxr-xr-xstorage/rocksdb/rocksdb/java/jdb_bench.sh13
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/LICENSE-HEADER.txt5
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/README.md18
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/pom.xml138
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java139
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java139
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java158
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java112
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java59
-rw-r--r--storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java58
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni.pom150
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/backupablejni.cc363
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/backupenginejni.cc277
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc24
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/cassandra_value_operator.cc48
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/checkpoint.cc68
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/clock_cache.cc40
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/columnfamilyhandle.cc72
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compact_range_options.cc211
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_filter.cc28
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory.cc40
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc76
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h35
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_job_info.cc231
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_job_stats.cc361
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_options.cc116
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_options_fifo.cc81
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compaction_options_universal.cc209
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/comparator.cc57
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.cc638
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.h137
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/compression_options.cc164
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/env.cc238
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/env_options.cc298
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/filter.cc45
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/ingest_external_file_options.cc196
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/iterator.cc252
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/jnicallback.cc53
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/jnicallback.h31
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.cc297
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.h49
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/lru_cache.cc43
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/memory_util.cc107
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/memtablejni.cc93
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/merge_operator.cc81
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc44
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_db.cc284
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_options.cc78
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/options.cc7240
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/options_util.cc134
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/persistent_cache.cc57
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/portal.h7534
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/ratelimiterjni.cc127
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc23
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/restorejni.cc40
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/rocks_callback_object.cc31
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/rocksdb_exception_test.cc82
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/rocksjni.cc3406
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/slice.cc360
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/snapshot.cc27
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/sst_file_manager.cc247
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/sst_file_reader_iterator.cc253
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/sst_file_readerjni.cc116
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/sst_file_writerjni.cc308
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/statistics.cc264
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.cc32
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.h34
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/table.cc150
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/table_filter.cc25
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.cc66
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.h36
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/thread_status.cc125
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/trace_writer.cc23
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.cc115
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.h36
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction.cc1646
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_db.cc463
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_db_options.cc170
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_log.cc79
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier.cc43
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc39
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h42
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/transaction_options.cc191
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/ttl.cc207
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/wal_filter.cc23
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.cc144
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.h42
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/write_batch.cc674
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/write_batch_test.cc198
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/write_batch_with_index.cc862
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/write_buffer_manager.cc42
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc548
-rw-r--r--storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h89
-rw-r--r--storage/rocksdb/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java184
-rw-r--r--storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBColumnFamilySample.java78
-rw-r--r--storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBSample.java303
-rw-r--r--storage/rocksdb/rocksdb/java/samples/src/main/java/TransactionSample.java183
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java59
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java77
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java124
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java125
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java67
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java256
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractNativeReference.java76
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java126
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractSlice.java191
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java20
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java70
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java54
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java49
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java216
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AccessHint.java53
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java465
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java464
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java259
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java76
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupableDBOptions.java465
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java987
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BloomFilter.java79
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BuiltinComparator.java20
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Cache.java13
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java19
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java25
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Checkpoint.java66
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ChecksumType.java39
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ClockCache.java59
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java109
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java115
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java70
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java1001
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java449
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java237
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java159
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java295
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java121
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java89
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java273
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionPriority.java73
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java115
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStopStyle.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java80
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorOptions.java133
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java48
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java151
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionType.java99
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java1403
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java1564
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java32
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DbPath.java47
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DirectSlice.java132
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EncodingType.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Env.java167
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java366
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Experimental.java23
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Filter.java36
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java90
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java174
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java106
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java27
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java75
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java198
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Holder.java46
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IndexType.java41
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/InfoLogLevel.java49
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java227
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LRUCache.java82
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java56
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LogFile.java75
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Logger.java122
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemTableConfig.java29
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java72
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java60
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MergeOperator.java18
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java469
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java158
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java325
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java443
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java16
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java376
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java59
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeLibraryLoader.java125
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java59
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationType.java54
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java226
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java53
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Options.java2183
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java142
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java26
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PlainTableConfig.java251
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Priority.java49
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Range.java19
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java227
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java52
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java622
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java49
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java18
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RestoreOptions.java32
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java65
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java50
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java4522
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDBException.java44
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java32
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java118
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java117
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java39
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMutableObject.java87
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksObject.java41
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java31
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java51
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Slice.java136
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java41
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java251
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java162
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReader.java82
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReaderIterator.java120
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java290
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StateType.java53
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Statistics.java152
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java111
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java32
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsCollectorInput.java35
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java65
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Status.java138
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java24
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java21
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFormatConfig.java22
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java366
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java224
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java65
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TickerType.java760
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java30
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java32
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java36
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Transaction.java2012
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java404
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java217
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionLogIterator.java112
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java189
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java68
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java31
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java245
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java62
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java19
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/VectorMemTableConfig.java46
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java83
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java197
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java87
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java54
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java394
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java305
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java318
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java33
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java219
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ByteUtil.java46
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/BytewiseComparator.java121
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java152
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/IntComparator.java67
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java88
-rw-r--r--storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/SizeUnit.java16
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java902
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java261
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java351
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java393
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java145
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CheckPointTest.java83
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ClockCacheTest.java26
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java625
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java734
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java98
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java68
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java114
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java196
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java35
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java52
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java80
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionPriorityTest.java31
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java31
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java58
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java71
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionTypesTest.java20
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java813
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java113
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DirectSliceTest.java93
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java145
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FilterTest.java39
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java31
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushTest.java49
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java45
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java109
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java107
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java192
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LRUCacheTest.java27
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LoggerTest.java239
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemTableTest.java111
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java143
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java440
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MixedOptionsTest.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java88
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java85
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java92
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java41
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java131
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java38
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java350
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java1311
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java126
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlainTableConfigTest.java89
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlatformRandomHelper.java58
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java65
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOnlyTest.java305
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java323
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java115
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java1669
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java203
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java146
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java18
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SliceTest.java80
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SnapshotTest.java169
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java66
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileReaderTest.java155
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileWriterTest.java241
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java55
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java168
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatsCallbackMock.java20
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java106
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java43
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java64
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java178
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java139
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java72
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java308
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TtlDBTest.java112
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/Types.java43
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java22
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java165
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java76
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java528
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java104
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java566
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java69
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java21
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java174
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java267
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java519
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java172
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/EnvironmentTest.java259
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/IntComparatorTest.java266
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java174
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java270
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/SizeUnitTest.java27
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java61
-rw-r--r--storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java134
-rw-r--r--storage/rocksdb/rocksdb/logging/auto_roll_logger.cc292
-rw-r--r--storage/rocksdb/rocksdb/logging/auto_roll_logger.h164
-rw-r--r--storage/rocksdb/rocksdb/logging/auto_roll_logger_test.cc685
-rw-r--r--storage/rocksdb/rocksdb/logging/env_logger.h165
-rw-r--r--storage/rocksdb/rocksdb/logging/env_logger_test.cc162
-rw-r--r--storage/rocksdb/rocksdb/logging/event_logger.cc71
-rw-r--r--storage/rocksdb/rocksdb/logging/event_logger.h203
-rw-r--r--storage/rocksdb/rocksdb/logging/event_logger_test.cc43
-rw-r--r--storage/rocksdb/rocksdb/logging/log_buffer.cc92
-rw-r--r--storage/rocksdb/rocksdb/logging/log_buffer.h56
-rw-r--r--storage/rocksdb/rocksdb/logging/logging.h68
-rw-r--r--storage/rocksdb/rocksdb/logging/posix_logger.h185
-rw-r--r--storage/rocksdb/rocksdb/memory/allocator.h57
-rw-r--r--storage/rocksdb/rocksdb/memory/arena.cc233
-rw-r--r--storage/rocksdb/rocksdb/memory/arena.h141
-rw-r--r--storage/rocksdb/rocksdb/memory/arena_test.cc204
-rw-r--r--storage/rocksdb/rocksdb/memory/concurrent_arena.cc47
-rw-r--r--storage/rocksdb/rocksdb/memory/concurrent_arena.h215
-rw-r--r--storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.cc206
-rw-r--r--storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.h78
-rw-r--r--storage/rocksdb/rocksdb/memory/memory_allocator.h38
-rw-r--r--storage/rocksdb/rocksdb/memory/memory_usage.h25
-rw-r--r--storage/rocksdb/rocksdb/memtable/alloc_tracker.cc62
-rw-r--r--storage/rocksdb/rocksdb/memtable/hash_linklist_rep.cc844
-rw-r--r--storage/rocksdb/rocksdb/memtable/hash_linklist_rep.h49
-rw-r--r--storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.cc349
-rw-r--r--storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.h44
-rw-r--r--storage/rocksdb/rocksdb/memtable/inlineskiplist.h997
-rw-r--r--storage/rocksdb/rocksdb/memtable/inlineskiplist_test.cc663
-rw-r--r--storage/rocksdb/rocksdb/memtable/memtablerep_bench.cc678
-rw-r--r--storage/rocksdb/rocksdb/memtable/skiplist.h496
-rw-r--r--storage/rocksdb/rocksdb/memtable/skiplist_test.cc388
-rw-r--r--storage/rocksdb/rocksdb/memtable/skiplistrep.cc280
-rw-r--r--storage/rocksdb/rocksdb/memtable/stl_wrappers.h33
-rw-r--r--storage/rocksdb/rocksdb/memtable/vectorrep.cc301
-rw-r--r--storage/rocksdb/rocksdb/memtable/write_buffer_manager.cc130
-rw-r--r--storage/rocksdb/rocksdb/memtable/write_buffer_manager_test.cc155
-rw-r--r--storage/rocksdb/rocksdb/monitoring/file_read_sample.h23
-rw-r--r--storage/rocksdb/rocksdb/monitoring/histogram.cc288
-rw-r--r--storage/rocksdb/rocksdb/monitoring/histogram.h149
-rw-r--r--storage/rocksdb/rocksdb/monitoring/histogram_test.cc221
-rw-r--r--storage/rocksdb/rocksdb/monitoring/histogram_windowing.cc202
-rw-r--r--storage/rocksdb/rocksdb/monitoring/histogram_windowing.h80
-rw-r--r--storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.cc49
-rw-r--r--storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.h74
-rw-r--r--storage/rocksdb/rocksdb/monitoring/instrumented_mutex.cc69
-rw-r--r--storage/rocksdb/rocksdb/monitoring/instrumented_mutex.h98
-rw-r--r--storage/rocksdb/rocksdb/monitoring/iostats_context.cc62
-rw-r--r--storage/rocksdb/rocksdb/monitoring/iostats_context_imp.h60
-rw-r--r--storage/rocksdb/rocksdb/monitoring/iostats_context_test.cc29
-rw-r--r--storage/rocksdb/rocksdb/monitoring/perf_context.cc559
-rw-r--r--storage/rocksdb/rocksdb/monitoring/perf_context_imp.h97
-rw-r--r--storage/rocksdb/rocksdb/monitoring/perf_level.cc28
-rw-r--r--storage/rocksdb/rocksdb/monitoring/perf_level_imp.h18
-rw-r--r--storage/rocksdb/rocksdb/monitoring/perf_step_timer.h79
-rw-r--r--storage/rocksdb/rocksdb/monitoring/persistent_stats_history.cc170
-rw-r--r--storage/rocksdb/rocksdb/monitoring/persistent_stats_history.h83
-rw-r--r--storage/rocksdb/rocksdb/monitoring/statistics.cc406
-rw-r--r--storage/rocksdb/rocksdb/monitoring/statistics.h138
-rw-r--r--storage/rocksdb/rocksdb/monitoring/statistics_test.cc47
-rw-r--r--storage/rocksdb/rocksdb/monitoring/stats_history_test.cc653
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_impl.cc163
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc314
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_updater.h233
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_updater_debug.cc42
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_util.cc206
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_util.h134
-rw-r--r--storage/rocksdb/rocksdb/monitoring/thread_status_util_debug.cc32
-rw-r--r--storage/rocksdb/rocksdb/options/cf_options.cc231
-rw-r--r--storage/rocksdb/rocksdb/options/cf_options.h269
-rw-r--r--storage/rocksdb/rocksdb/options/db_options.cc333
-rw-r--r--storage/rocksdb/rocksdb/options/db_options.h118
-rw-r--r--storage/rocksdb/rocksdb/options/options.cc623
-rw-r--r--storage/rocksdb/rocksdb/options/options_helper.cc2124
-rw-r--r--storage/rocksdb/rocksdb/options/options_helper.h233
-rw-r--r--storage/rocksdb/rocksdb/options/options_parser.cc839
-rw-r--r--storage/rocksdb/rocksdb/options/options_parser.h147
-rw-r--r--storage/rocksdb/rocksdb/options/options_sanity_check.cc38
-rw-r--r--storage/rocksdb/rocksdb/options/options_sanity_check.h50
-rw-r--r--storage/rocksdb/rocksdb/options/options_settable_test.cc492
-rw-r--r--storage/rocksdb/rocksdb/options/options_test.cc2004
-rw-r--r--storage/rocksdb/rocksdb/port/README10
-rw-r--r--storage/rocksdb/rocksdb/port/jemalloc_helper.h77
-rw-r--r--storage/rocksdb/rocksdb/port/likely.h18
-rw-r--r--storage/rocksdb/rocksdb/port/malloc.h17
-rw-r--r--storage/rocksdb/rocksdb/port/port.h21
-rw-r--r--storage/rocksdb/rocksdb/port/port_dirent.h44
-rw-r--r--storage/rocksdb/rocksdb/port/port_example.h101
-rw-r--r--storage/rocksdb/rocksdb/port/port_posix.cc234
-rw-r--r--storage/rocksdb/rocksdb/port/port_posix.h218
-rw-r--r--storage/rocksdb/rocksdb/port/stack_trace.cc138
-rw-r--r--storage/rocksdb/rocksdb/port/stack_trace.h22
-rw-r--r--storage/rocksdb/rocksdb/port/sys_time.h47
-rw-r--r--storage/rocksdb/rocksdb/port/util_logger.h20
-rw-r--r--storage/rocksdb/rocksdb/port/win/env_default.cc41
-rw-r--r--storage/rocksdb/rocksdb/port/win/env_win.cc1540
-rw-r--r--storage/rocksdb/rocksdb/port/win/env_win.h347
-rw-r--r--storage/rocksdb/rocksdb/port/win/io_win.cc1069
-rw-r--r--storage/rocksdb/rocksdb/port/win/io_win.h456
-rw-r--r--storage/rocksdb/rocksdb/port/win/port_win.cc269
-rw-r--r--storage/rocksdb/rocksdb/port/win/port_win.h398
-rw-r--r--storage/rocksdb/rocksdb/port/win/win_jemalloc.cc75
-rw-r--r--storage/rocksdb/rocksdb/port/win/win_logger.cc192
-rw-r--r--storage/rocksdb/rocksdb/port/win/win_logger.h66
-rw-r--r--storage/rocksdb/rocksdb/port/win/win_thread.cc179
-rw-r--r--storage/rocksdb/rocksdb/port/win/win_thread.h122
-rw-r--r--storage/rocksdb/rocksdb/port/win/xpress_win.cc226
-rw-r--r--storage/rocksdb/rocksdb/port/win/xpress_win.h27
-rw-r--r--storage/rocksdb/rocksdb/port/xpress.h17
-rw-r--r--storage/rocksdb/rocksdb/src.mk527
-rw-r--r--storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.cc124
-rw-r--r--storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.h63
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block.cc1004
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block.h631
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.cc347
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.h119
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_filter_block_test.cc434
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc1217
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.h157
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.cc649
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.h195
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc4531
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.h824
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_builder.cc196
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_builder.h75
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_prefix_index.cc232
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_prefix_index.h66
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_test.cc627
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/block_type.h30
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/cachable_entry.h220
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/data_block_footer.cc59
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/data_block_footer.h25
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.cc93
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.h136
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/data_block_hash_index_test.cc719
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/filter_block.h176
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.cc102
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.h55
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/filter_policy.cc759
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/filter_policy_internal.h142
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/flush_block_policy.cc88
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/flush_block_policy.h41
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/full_filter_block.cc338
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/full_filter_block.h139
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/full_filter_block_test.cc333
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/index_builder.cc222
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/index_builder.h443
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/mock_block_based_table.h56
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.cc22
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.h40
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc388
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.h122
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block_test.cc424
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.cc120
-rw-r--r--storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.h59
-rw-r--r--storage/rocksdb/rocksdb/table/block_fetcher.cc284
-rw-r--r--storage/rocksdb/rocksdb/table/block_fetcher.h109
-rw-r--r--storage/rocksdb/rocksdb/table/cleanable_test.cc277
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.cc528
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.h136
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder_test.cc662
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.cc72
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.h92
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.cc399
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.h100
-rw-r--r--storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader_test.cc578
-rw-r--r--storage/rocksdb/rocksdb/table/format.cc465
-rw-r--r--storage/rocksdb/rocksdb/table/format.h344
-rw-r--r--storage/rocksdb/rocksdb/table/get_context.cc366
-rw-r--r--storage/rocksdb/rocksdb/table/get_context.h191
-rw-r--r--storage/rocksdb/rocksdb/table/internal_iterator.h182
-rw-r--r--storage/rocksdb/rocksdb/table/iter_heap.h42
-rw-r--r--storage/rocksdb/rocksdb/table/iterator.cc210
-rw-r--r--storage/rocksdb/rocksdb/table/iterator_wrapper.h149
-rw-r--r--storage/rocksdb/rocksdb/table/merger_test.cc180
-rw-r--r--storage/rocksdb/rocksdb/table/merging_iterator.cc468
-rw-r--r--storage/rocksdb/rocksdb/table/merging_iterator.h64
-rw-r--r--storage/rocksdb/rocksdb/table/meta_blocks.cc525
-rw-r--r--storage/rocksdb/rocksdb/table/meta_blocks.h152
-rw-r--r--storage/rocksdb/rocksdb/table/mock_table.cc148
-rw-r--r--storage/rocksdb/rocksdb/table/mock_table.h214
-rw-r--r--storage/rocksdb/rocksdb/table/multiget_context.h259
-rw-r--r--storage/rocksdb/rocksdb/table/persistent_cache_helper.cc113
-rw-r--r--storage/rocksdb/rocksdb/table/persistent_cache_helper.h44
-rw-r--r--storage/rocksdb/rocksdb/table/persistent_cache_options.h34
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_bloom.cc78
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_bloom.h135
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_builder.cc314
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_builder.h151
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_factory.cc235
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_factory.h223
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_index.cc211
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_index.h249
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.cc498
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.h193
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_reader.cc775
-rw-r--r--storage/rocksdb/rocksdb/table/plain/plain_table_reader.h246
-rw-r--r--storage/rocksdb/rocksdb/table/scoped_arena_iterator.h61
-rw-r--r--storage/rocksdb/rocksdb/table/sst_file_reader.cc91
-rw-r--r--storage/rocksdb/rocksdb/table/sst_file_reader_test.cc174
-rw-r--r--storage/rocksdb/rocksdb/table/sst_file_writer.cc319
-rw-r--r--storage/rocksdb/rocksdb/table/sst_file_writer_collectors.h94
-rw-r--r--storage/rocksdb/rocksdb/table/table_builder.h170
-rw-r--r--storage/rocksdb/rocksdb/table/table_properties.cc272
-rw-r--r--storage/rocksdb/rocksdb/table/table_properties_internal.h30
-rw-r--r--storage/rocksdb/rocksdb/table/table_reader.h137
-rw-r--r--storage/rocksdb/rocksdb/table/table_reader_bench.cc347
-rw-r--r--storage/rocksdb/rocksdb/table/table_reader_caller.h39
-rw-r--r--storage/rocksdb/rocksdb/table/table_test.cc4651
-rw-r--r--storage/rocksdb/rocksdb/table/two_level_iterator.cc211
-rw-r--r--storage/rocksdb/rocksdb/table/two_level_iterator.h43
-rw-r--r--storage/rocksdb/rocksdb/test_util/fault_injection_test_env.cc437
-rw-r--r--storage/rocksdb/rocksdb/test_util/fault_injection_test_env.h225
-rw-r--r--storage/rocksdb/rocksdb/test_util/mock_time_env.h45
-rw-r--r--storage/rocksdb/rocksdb/test_util/sync_point.cc66
-rw-r--r--storage/rocksdb/rocksdb/test_util/sync_point.h144
-rw-r--r--storage/rocksdb/rocksdb/test_util/sync_point_impl.cc129
-rw-r--r--storage/rocksdb/rocksdb/test_util/sync_point_impl.h74
-rw-r--r--storage/rocksdb/rocksdb/test_util/testharness.cc56
-rw-r--r--storage/rocksdb/rocksdb/test_util/testharness.h47
-rw-r--r--storage/rocksdb/rocksdb/test_util/testutil.cc454
-rw-r--r--storage/rocksdb/rocksdb/test_util/testutil.h802
-rw-r--r--storage/rocksdb/rocksdb/test_util/transaction_test_util.cc387
-rw-r--r--storage/rocksdb/rocksdb/test_util/transaction_test_util.h132
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/CPortability.h27
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/ConstexprMath.h45
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Indestructible.h166
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Optional.h570
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Portability.h84
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/ScopeGuard.h54
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Traits.h152
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Unit.h59
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/Utility.h141
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/chrono/Hardware.h33
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/container/Array.h74
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex-inl.h117
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.cpp263
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.h96
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/functional/Invoke.h40
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/hash/Hash.h29
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/lang/Align.h38
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/lang/Bits.h30
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/lang/Launder.h51
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/portability/Asm.h28
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysSyscall.h10
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysTypes.h26
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification-inl.h138
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.cpp23
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.h57
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil-inl.h260
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil.h52
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/Baton.h327
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex-inl.h1703
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.cpp16
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.h304
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutexSpecializations.h39
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.cpp26
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.h318
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.cpp12
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.h57
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/InlineFunctionRef.h219
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable-inl.h207
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable.h164
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Sleeper.h57
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Spin.h77
-rw-r--r--storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp1142
-rw-r--r--storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/CMakeLists.txt1
-rw-r--r--storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc11394
-rw-r--r--storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest.h22109
-rw-r--r--storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest_main.cc37
-rw-r--r--storage/rocksdb/rocksdb/thirdparty.inc268
-rw-r--r--storage/rocksdb/rocksdb/tools/CMakeLists.txt21
-rw-r--r--storage/rocksdb/rocksdb/tools/Dockerfile5
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/README.md96
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/__init__.py0
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/bench_runner.py39
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/config_optimizer_example.py134
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/db_bench_runner.py245
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/db_config_optimizer.py282
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/db_log_parser.py131
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/db_options_parser.py358
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/advisor/advisor/db_stats_fetcher.py338
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/db_timeseries_parser.py208
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/ini_parser.py76
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser.py528
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser_example.py89
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/advisor/rules.ini214
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/__init__.py0
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-030
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-125
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/OPTIONS-00000549
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/log_stats_parser_keys_ts3
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err1.ini56
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err2.ini15
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err3.ini15
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err4.ini15
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/test_rules.ini47
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/input_files/triggered_rules.ini83
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/test_db_bench_runner.py147
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/test_db_log_parser.py103
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/test_db_options_parser.py216
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/test_db_stats_fetcher.py126
-rw-r--r--storage/rocksdb/rocksdb/tools/advisor/test/test_rule_parser.py234
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/analyze_txn_stress_test.sh77
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/auto_sanity_test.sh93
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/benchmark.sh525
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/benchmark_leveldb.sh187
-rw-r--r--storage/rocksdb/rocksdb/tools/blob_dump.cc110
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/__init__.py2
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.py2000
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.sh156
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim_test.py734
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.cc2308
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.h393
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_plot.py721
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc717
-rw-r--r--storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc25
-rw-r--r--storage/rocksdb/rocksdb/tools/check_all_python.py22
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/check_format_compatible.sh191
-rw-r--r--storage/rocksdb/rocksdb/tools/db_bench.cc21
-rw-r--r--storage/rocksdb/rocksdb/tools/db_bench_tool.cc7048
-rw-r--r--storage/rocksdb/rocksdb/tools/db_bench_tool_test.cc320
-rw-r--r--storage/rocksdb/rocksdb/tools/db_crashtest.py499
-rw-r--r--storage/rocksdb/rocksdb/tools/db_repl_stress.cc159
-rw-r--r--storage/rocksdb/rocksdb/tools/db_sanity_test.cc297
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/dbench_monitor102
-rw-r--r--storage/rocksdb/rocksdb/tools/dump/db_dump_tool.cc259
-rw-r--r--storage/rocksdb/rocksdb/tools/dump/rocksdb_dump.cc63
-rw-r--r--storage/rocksdb/rocksdb/tools/dump/rocksdb_undump.cc62
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/generate_random_db.sh31
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/ingest_external_sst.sh18
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb.cc21
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb_cmd.cc3437
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb_cmd_impl.h628
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb_cmd_test.cc585
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb_test.py595
-rw-r--r--storage/rocksdb/rocksdb/tools/ldb_tool.cc140
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/pflag217
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/.gitignore1
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/API.md178
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/README.md40
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/binding.gyp25
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/db_wrapper.cc526
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/db_wrapper.h60
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/rdb/rdb3
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/rdb.cc16
-rw-r--r--storage/rocksdb/rocksdb/tools/rdb/unit_test.js125
-rw-r--r--storage/rocksdb/rocksdb/tools/reduce_levels_test.cc220
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/regression_test.sh470
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/report_lite_binary_size.sh42
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/rocksdb_dump_test.sh9
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/run_flash_bench.sh359
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/run_leveldb.sh175
-rw-r--r--storage/rocksdb/rocksdb/tools/sample-dump.dmpbin0 -> 100 bytes
-rw-r--r--storage/rocksdb/rocksdb/tools/sst_dump.cc21
-rw-r--r--storage/rocksdb/rocksdb/tools/sst_dump_test.cc282
-rw-r--r--storage/rocksdb/rocksdb/tools/sst_dump_tool.cc778
-rw-r--r--storage/rocksdb/rocksdb/tools/sst_dump_tool_imp.h87
-rw-r--r--storage/rocksdb/rocksdb/tools/trace_analyzer.cc25
-rw-r--r--storage/rocksdb/rocksdb/tools/trace_analyzer_test.cc727
-rw-r--r--storage/rocksdb/rocksdb/tools/trace_analyzer_tool.cc2001
-rw-r--r--storage/rocksdb/rocksdb/tools/trace_analyzer_tool.h292
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/verify_random_db.sh39
-rwxr-xr-xstorage/rocksdb/rocksdb/tools/write_external_sst.sh25
-rw-r--r--storage/rocksdb/rocksdb/tools/write_stress.cc305
-rw-r--r--storage/rocksdb/rocksdb/tools/write_stress_runner.py74
-rw-r--r--storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.cc497
-rw-r--r--storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.h294
-rw-r--r--storage/rocksdb/rocksdb/trace_replay/block_cache_tracer_test.cc378
-rw-r--r--storage/rocksdb/rocksdb/trace_replay/trace_replay.cc485
-rw-r--r--storage/rocksdb/rocksdb/trace_replay/trace_replay.h189
-rw-r--r--storage/rocksdb/rocksdb/util/aligned_buffer.h248
-rw-r--r--storage/rocksdb/rocksdb/util/autovector.h361
-rw-r--r--storage/rocksdb/rocksdb/util/autovector_test.cc330
-rw-r--r--storage/rocksdb/rocksdb/util/bloom_impl.h483
-rw-r--r--storage/rocksdb/rocksdb/util/bloom_test.cc912
-rw-r--r--storage/rocksdb/rocksdb/util/build_version.cc.in5
-rw-r--r--storage/rocksdb/rocksdb/util/build_version.h15
-rw-r--r--storage/rocksdb/rocksdb/util/cast_util.h21
-rw-r--r--storage/rocksdb/rocksdb/util/channel.h67
-rw-r--r--storage/rocksdb/rocksdb/util/coding.cc89
-rw-r--r--storage/rocksdb/rocksdb/util/coding.h480
-rw-r--r--storage/rocksdb/rocksdb/util/coding_test.cc217
-rw-r--r--storage/rocksdb/rocksdb/util/compaction_job_stats_impl.cc91
-rw-r--r--storage/rocksdb/rocksdb/util/comparator.cc216
-rw-r--r--storage/rocksdb/rocksdb/util/compression.h1407
-rw-r--r--storage/rocksdb/rocksdb/util/compression_context_cache.cc108
-rw-r--r--storage/rocksdb/rocksdb/util/compression_context_cache.h47
-rw-r--r--storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.cc67
-rw-r--r--storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.h67
-rw-r--r--storage/rocksdb/rocksdb/util/core_local.h83
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c.cc1255
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c.h51
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_arm64.cc129
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_arm64.h47
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_ppc.c94
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_ppc.h19
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_ppc_asm.S752
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_ppc_constants.h900
-rw-r--r--storage/rocksdb/rocksdb/util/crc32c_test.cc180
-rw-r--r--storage/rocksdb/rocksdb/util/defer.h52
-rw-r--r--storage/rocksdb/rocksdb/util/defer_test.cc39
-rw-r--r--storage/rocksdb/rocksdb/util/duplicate_detector.h68
-rw-r--r--storage/rocksdb/rocksdb/util/dynamic_bloom.cc70
-rw-r--r--storage/rocksdb/rocksdb/util/dynamic_bloom.h214
-rw-r--r--storage/rocksdb/rocksdb/util/dynamic_bloom_test.cc324
-rw-r--r--storage/rocksdb/rocksdb/util/file_checksum_helper.cc85
-rw-r--r--storage/rocksdb/rocksdb/util/file_checksum_helper.h117
-rw-r--r--storage/rocksdb/rocksdb/util/file_reader_writer_test.cc444
-rw-r--r--storage/rocksdb/rocksdb/util/filelock_test.cc141
-rw-r--r--storage/rocksdb/rocksdb/util/filter_bench.cc751
-rw-r--r--storage/rocksdb/rocksdb/util/gflags_compat.h19
-rw-r--r--storage/rocksdb/rocksdb/util/hash.cc83
-rw-r--r--storage/rocksdb/rocksdb/util/hash.h120
-rw-r--r--storage/rocksdb/rocksdb/util/hash_map.h67
-rw-r--r--storage/rocksdb/rocksdb/util/hash_test.cc377
-rw-r--r--storage/rocksdb/rocksdb/util/heap.h166
-rw-r--r--storage/rocksdb/rocksdb/util/heap_test.cc139
-rw-r--r--storage/rocksdb/rocksdb/util/kv_map.h33
-rw-r--r--storage/rocksdb/rocksdb/util/log_write_bench.cc86
-rw-r--r--storage/rocksdb/rocksdb/util/murmurhash.cc191
-rw-r--r--storage/rocksdb/rocksdb/util/murmurhash.h42
-rw-r--r--storage/rocksdb/rocksdb/util/mutexlock.h135
-rw-r--r--storage/rocksdb/rocksdb/util/ppc-opcode.h27
-rw-r--r--storage/rocksdb/rocksdb/util/random.cc38
-rw-r--r--storage/rocksdb/rocksdb/util/random.h166
-rw-r--r--storage/rocksdb/rocksdb/util/random_test.cc105
-rw-r--r--storage/rocksdb/rocksdb/util/rate_limiter.cc339
-rw-r--r--storage/rocksdb/rocksdb/util/rate_limiter.h113
-rw-r--r--storage/rocksdb/rocksdb/util/rate_limiter_test.cc235
-rw-r--r--storage/rocksdb/rocksdb/util/repeatable_thread.h149
-rw-r--r--storage/rocksdb/rocksdb/util/repeatable_thread_test.cc107
-rw-r--r--storage/rocksdb/rocksdb/util/set_comparator.h22
-rw-r--r--storage/rocksdb/rocksdb/util/slice.cc243
-rw-r--r--storage/rocksdb/rocksdb/util/slice_test.cc163
-rw-r--r--storage/rocksdb/rocksdb/util/slice_transform_test.cc153
-rw-r--r--storage/rocksdb/rocksdb/util/status.cc143
-rw-r--r--storage/rocksdb/rocksdb/util/stderr_logger.h31
-rw-r--r--storage/rocksdb/rocksdb/util/stop_watch.h118
-rw-r--r--storage/rocksdb/rocksdb/util/string_util.cc409
-rw-r--r--storage/rocksdb/rocksdb/util/string_util.h138
-rw-r--r--storage/rocksdb/rocksdb/util/thread_list_test.cc352
-rw-r--r--storage/rocksdb/rocksdb/util/thread_local.cc554
-rw-r--r--storage/rocksdb/rocksdb/util/thread_local.h101
-rw-r--r--storage/rocksdb/rocksdb/util/thread_local_test.cc580
-rw-r--r--storage/rocksdb/rocksdb/util/thread_operation.h121
-rw-r--r--storage/rocksdb/rocksdb/util/threadpool_imp.cc507
-rw-r--r--storage/rocksdb/rocksdb/util/threadpool_imp.h112
-rw-r--r--storage/rocksdb/rocksdb/util/timer_queue.h230
-rw-r--r--storage/rocksdb/rocksdb/util/timer_queue_test.cc72
-rw-r--r--storage/rocksdb/rocksdb/util/user_comparator_wrapper.h65
-rw-r--r--storage/rocksdb/rocksdb/util/util.h16
-rw-r--r--storage/rocksdb/rocksdb/util/vector_iterator.h101
-rw-r--r--storage/rocksdb/rocksdb/util/xxh3p.h1648
-rw-r--r--storage/rocksdb/rocksdb/util/xxhash.cc1160
-rw-r--r--storage/rocksdb/rocksdb/util/xxhash.h598
-rw-r--r--storage/rocksdb/rocksdb/utilities/backupable/backupable_db.cc1989
-rw-r--r--storage/rocksdb/rocksdb/utilities/backupable/backupable_db_test.cc1863
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.cc329
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.h168
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db.cc102
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db.h261
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_gc_stats.h52
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.cc2116
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.h495
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl_filesnapshot.cc109
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_iterator.h147
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_listener.h66
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_db_test.cc1992
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.cc278
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.h57
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_file.cc320
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_file.h252
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.cc149
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.h133
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.cc105
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.h82
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.cc139
-rw-r--r--storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.h94
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.cc47
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.h42
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_format_test.cc367
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_functional_test.cc311
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_row_merge_test.cc112
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/cassandra_serialize_test.cc188
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/format.cc390
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/format.h197
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.cc67
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.h44
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/serialize.h75
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/test_utils.cc75
-rw-r--r--storage/rocksdb/rocksdb/utilities/cassandra/test_utils.h46
-rw-r--r--storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.cc516
-rw-r--r--storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.h79
-rw-r--r--storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_test.cc829
-rw-r--r--storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc29
-rw-r--r--storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.h27
-rw-r--r--storage/rocksdb/rocksdb/utilities/convenience/info_log_finder.cc25
-rw-r--r--storage/rocksdb/rocksdb/utilities/debug.cc80
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_librados.cc1497
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_librados.md122
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_librados_test.cc1146
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_mirror.cc262
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_mirror_test.cc223
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_timed.cc145
-rw-r--r--storage/rocksdb/rocksdb/utilities/env_timed_test.cc44
-rw-r--r--storage/rocksdb/rocksdb/utilities/leveldb_options/leveldb_options.cc56
-rw-r--r--storage/rocksdb/rocksdb/utilities/memory/memory_test.cc278
-rw-r--r--storage/rocksdb/rocksdb/utilities/memory/memory_util.cc52
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators.h55
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.cc59
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.h39
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/max.cc77
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/put.cc83
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.cc100
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.h38
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.cc59
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.h31
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.cc117
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.h49
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend_test.cc601
-rw-r--r--storage/rocksdb/rocksdb/utilities/merge_operators/uint64add.cc69
-rw-r--r--storage/rocksdb/rocksdb/utilities/object_registry.cc87
-rw-r--r--storage/rocksdb/rocksdb/utilities/object_registry_test.cc174
-rw-r--r--storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration.cc168
-rw-r--r--storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration_test.cc425
-rw-r--r--storage/rocksdb/rocksdb/utilities/options/options_util.cc114
-rw-r--r--storage/rocksdb/rocksdb/utilities/options/options_util_test.cc363
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.cc425
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.h156
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.cc608
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.h296
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file_buffer.h127
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.cc86
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.h125
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table.h238
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_bench.cc308
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_evictable.h168
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_test.cc160
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/lrulist.h174
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_bench.cc360
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.cc474
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.h285
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.cc163
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.h336
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_util.h67
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.cc138
-rw-r--r--storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.h142
-rw-r--r--storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.cc274
-rw-r--r--storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.h231
-rw-r--r--storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator_test.cc494
-rw-r--r--storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache.cc354
-rw-r--r--storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc225
-rw-r--r--storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.cc90
-rw-r--r--storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.h72
-rw-r--r--storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector_test.cc178
-rw-r--r--storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.cc123
-rw-r--r--storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.h48
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.cc187
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.h101
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.cc111
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.h71
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_test.cc1535
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.cc723
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.h225
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.cc632
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.h220
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/snapshot_checker.cc49
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_base.cc837
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_base.h374
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.cc135
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.h26
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.cc745
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.h158
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_test.cc6224
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_test.h517
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_util.cc182
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/transaction_util.h103
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_prepared_transaction_test.cc3524
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.cc473
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.h119
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.cc998
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.h1111
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_transaction_test.cc727
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.cc999
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.h341
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.cc468
-rw-r--r--storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.h148
-rw-r--r--storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.cc335
-rw-r--r--storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.h361
-rw-r--r--storage/rocksdb/rocksdb/utilities/ttl/ttl_test.cc693
-rw-r--r--storage/rocksdb/rocksdb/utilities/util_merge_operators_test.cc99
-rw-r--r--storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index.cc1065
-rw-r--r--storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.cc288
-rw-r--r--storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.h145
-rw-r--r--storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_test.cc1846
-rw-r--r--storage/rocksdb/tools/mysql_ldb.cc18
-rw-r--r--storage/rocksdb/unittest/CMakeLists.txt22
-rw-r--r--storage/rocksdb/unittest/test_properties_collector.cc54
-rw-r--r--storage/rocksdb/ut0counter.h203
2924 files changed, 665879 insertions, 0 deletions
diff --git a/storage/rocksdb/.clang-format b/storage/rocksdb/.clang-format
new file mode 100644
index 00000000..b1df76bd
--- /dev/null
+++ b/storage/rocksdb/.clang-format
@@ -0,0 +1,137 @@
+# Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2.0,
+# as published by the Free Software Foundation.
+#
+# This program is also distributed with certain software (including
+# but not limited to OpenSSL) that is licensed under separate terms,
+# as designated in a particular file or component or in included license
+# documentation. The authors of MySQL hereby grant you an additional
+# permission to link the program and your derivative works with the
+# separately licensed software that they have included with MySQL.
+#
+# 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, version 2.0, for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# This is the output of clang-format-5.0 --style=google --dump-config,
+# except for changes mentioned below. We lock the style so that any newer
+# version of clang-format will give the same result; as time goes, we may
+# update this list, requiring newer versions of clang-format.
+
+Language: Cpp
+# BasedOnStyle: Google
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeCategories:
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ - Regex: '^<.*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentCaseLabels: true
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: false
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 8
+UseTab: Never
+
+# We declare one specific pointer style since right alignment is dominant in
+# the MySQL code base (default --style=google has DerivePointerAlignment true).
+DerivePointerAlignment: false
+PointerAlignment: Right
+
+# MySQL source code is allowed to use C++11 features.
+Standard: Cpp11
diff --git a/storage/rocksdb/.gitignore b/storage/rocksdb/.gitignore
new file mode 100644
index 00000000..adf3e154
--- /dev/null
+++ b/storage/rocksdb/.gitignore
@@ -0,0 +1,2 @@
+build_version.cc
+.*
diff --git a/storage/rocksdb/CMakeLists.txt b/storage/rocksdb/CMakeLists.txt
new file mode 100644
index 00000000..d5bbefbd
--- /dev/null
+++ b/storage/rocksdb/CMakeLists.txt
@@ -0,0 +1,276 @@
+# TODO: Copyrights
+
+MACRO(SKIP_ROCKSDB_PLUGIN msg)
+ MESSAGE_ONCE(SKIP_ROCKSDB_PLUGIN "Can't build rocksdb engine - ${msg}")
+ ADD_FEATURE_INFO(ROCKSDB "OFF" "Storage Engine")
+ RETURN()
+ENDMACRO()
+
+IF (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/CMakeLists.txt")
+ SKIP_ROCKSDB_PLUGIN("Missing CMakeLists.txt in rocksdb directory. Try \"git submodule update\".")
+ENDIF()
+
+CHECK_LIBRARY_EXISTS(rt timer_delete "" HAVE_TIMER_DELETE)
+IF (HAVE_TIMER_DELETE)
+ ADD_DEFINITIONS(-DHAVE_TIMER_DELETE)
+ENDIF(HAVE_TIMER_DELETE)
+
+CHECK_FUNCTION_EXISTS(sched_getcpu HAVE_SCHED_GETCPU)
+IF(HAVE_SCHED_GETCPU)
+ ADD_DEFINITIONS(-DHAVE_SCHED_GETCPU=1 -DROCKSDB_SCHED_GETCPU_PRESENT)
+ENDIF()
+
+IF(WITH_VALGRIND)
+ ADD_DEFINITIONS(-DROCKSDB_VALGRIND_RUN=1)
+ENDIF()
+
+# We've had our builders hang during the build process. This prevents MariaRocks
+# to be built on 32 bit intel OS kernels.
+IF(CMAKE_SYSTEM_PROCESSOR MATCHES "i[36]86")
+ SKIP_ROCKSDB_PLUGIN("Intel 32 bit not supported.")
+ENDIF()
+
+# Due to retrieved data being incorrect endian
+include(TestBigEndian)
+test_big_endian(BIG_ENDIAN)
+if(BIG_ENDIAN)
+ SKIP_ROCKSDB_PLUGIN("Big Endian not supported.")
+endif()
+
+#
+# Also, disable building on 32-bit Windows
+#
+IF (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
+ SKIP_ROCKSDB_PLUGIN("32-Bit Windows are temporarily disabled")
+ENDIF()
+
+# This plugin needs recent C++ compilers (it is using C++11 features)
+# Skip build for the old compilers
+SET(CXX11_FLAGS)
+SET(OLD_COMPILER_MSG "requires c++11 -capable compiler (minimal supported versions are g++ 4.8, clang 3.3, VS2015)")
+
+IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+ EXECUTE_PROCESS(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
+ IF (GCC_VERSION VERSION_LESS 4.8)
+ SKIP_ROCKSDB_PLUGIN("${OLD_COMPILER_MSG}")
+ ENDIF()
+ SET(CXX11_FLAGS "-std=c++11")
+ IF (GCC_VERSION VERSION_LESS 5.0)
+ SET(CXX11_FLAGS "-std=c++11 -Wno-missing-field-initializers")
+ ENDIF()
+ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ IF ((CMAKE_CXX_COMPILER_VERSION AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3) OR
+ (CLANG_VERSION_STRING AND CLANG_VERSION_STRING VERSION_LESS 3.3))
+ SKIP_ROCKSDB_PLUGIN("${OLD_COMPILER_MSG}")
+ ENDIF()
+ SET(CXX11_FLAGS "-std=c++11 -stdlib=libstdc++")
+ IF(MSVC)
+ # clang-cl does not work yet
+ SKIP_ROCKSDB_PLUGIN("Clang-cl is not supported")
+ ENDIF()
+ELSEIF(MSVC)
+ IF (MSVC_VERSION LESS 1900)
+ SKIP_ROCKSDB_PLUGIN("${OLD_COMPILER_MSG}")
+ ENDIF()
+ELSE()
+ SKIP_ROCKSDB_PLUGIN("Compiler not supported")
+ENDIF()
+
+IF(CMAKE_VERSION GREATER 3.0)
+ SET(CMAKE_CXX_STANDARD 11)
+ELSEIF(CXX11_FLAGS)
+ ADD_DEFINITIONS(${CXX11_FLAGS})
+ENDIF()
+
+SET(ROCKSDB_SE_SOURCES
+ rdb_mariadb_server_port.cc
+ rdb_mariadb_server_port.h
+ ha_rocksdb.cc
+ ha_rocksdb.h
+ rdb_i_s.cc
+ rdb_i_s.h
+ rdb_io_watchdog.h
+ rdb_io_watchdog.cc
+ rdb_mutex_wrapper.cc
+ rdb_mutex_wrapper.h
+ rdb_index_merge.cc
+ rdb_index_merge.h
+ properties_collector.cc
+ properties_collector.h
+ rdb_datadic.cc
+ rdb_datadic.h
+ rdb_cf_manager.cc
+ rdb_cf_manager.h
+ rdb_utils.cc rdb_utils.h
+ rdb_threads.cc
+ rdb_threads.h
+ rdb_psi.h
+ rdb_psi.cc
+ rdb_sst_info.cc
+ rdb_sst_info.h
+ rdb_converter.cc
+ rdb_converter.h
+)
+
+# MariaDB: the following is added in build_rocksdb.cmake, when appropriate:
+# This is a strong requirement coming from RocksDB. No conditional checks here.
+#ADD_DEFINITIONS(-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX
+#)
+if(CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
+ SET(ATOMIC_EXTRA_LIBS -latomic)
+else()
+ SET(ATOMIC_EXTRA_LIBS)
+endif()
+
+MYSQL_ADD_PLUGIN(rocksdb ${ROCKSDB_SE_SOURCES} MODULE_ONLY STORAGE_ENGINE
+ MODULE_OUTPUT_NAME ha_rocksdb
+ LINK_LIBRARIES ${ATOMIC_EXTRA_LIBS}
+ COMPONENT rocksdb-engine)
+
+IF(NOT TARGET rocksdb)
+ # Bail out if compilation with rocksdb engine is not requested
+ RETURN()
+ENDIF()
+
+
+
+CHECK_CXX_SOURCE_COMPILES("
+#if defined(_MSC_VER) && !defined(__thread)
+#define __thread __declspec(thread)
+#endif
+int main() {
+ static __thread int tls;
+ tls=0;
+ return tls;
+}
+" HAVE_THREAD_LOCAL)
+if(HAVE_THREAD_LOCAL)
+ ADD_DEFINITIONS(-DROCKSDB_SUPPORT_THREAD_LOCAL)
+else()
+ MESSAGE(SEND_ERROR "The compiler failed the check for ROCKSDB_SUPPORT_THREAD_LOCAL. "
+ "MyRocks requires that feature.")
+endif()
+
+INCLUDE(build_rocksdb.cmake)
+
+ADD_CONVENIENCE_LIBRARY(rocksdb_aux_lib
+ ha_rocksdb_proto.h
+ logger.h
+ rdb_comparator.h
+ rdb_cf_options.cc
+ rdb_cf_options.h
+ event_listener.cc
+ event_listener.h
+ rdb_perf_context.cc
+ rdb_perf_context.h
+ rdb_buff.h
+ rdb_mariadb_port.h
+ nosql_access.cc nosql_access.h
+)
+
+ADD_DEPENDENCIES(rocksdb_aux_lib GenError)
+
+# MARIAROCKS-TODO: how to properly depend on -lrt ?
+TARGET_LINK_LIBRARIES(rocksdb_aux_lib rocksdblib ${ZLIB_LIBRARY})
+if (UNIX AND NOT APPLE)
+ TARGET_LINK_LIBRARIES(rocksdb_aux_lib -lrt)
+endif()
+TARGET_LINK_LIBRARIES(rocksdb_aux_lib ${ATOMIC_EXTRA_LIBS})
+
+# IF (WITH_JEMALLOC)
+# FIND_LIBRARY(JEMALLOC_LIBRARY
+# NAMES libjemalloc${PIC_EXT}.a jemalloc
+# HINTS ${WITH_JEMALLOC}/lib)
+# SET(rocksdb_static_libs ${rocksdb_static_libs}
+# ${JEMALLOC_LIBRARY})
+# ADD_DEFINITIONS(-DROCKSDB_JEMALLOC)
+# ADD_DEFINITIONS(-DROCKSDB_MALLOC_USABLE_SIZE)
+# ENDIF()
+
+# MariaDB: Q: why does the upstream add libunwind for a particular
+# storage engine?
+#IF (WITH_UNWIND)
+# FIND_LIBRARY(UNWIND_LIBRARY
+# NAMES libunwind${PIC_EXT}.a unwind
+# HINTS ${WITH_UNWIND}/lib)
+# SET(rocksdb_static_libs ${rocksdb_static_libs}
+# ${UNWIND_LIBRARY})
+#ENDIF()
+
+
+TARGET_LINK_LIBRARIES(rocksdb rocksdb_aux_lib)
+
+CHECK_FUNCTION_EXISTS(sched_getcpu HAVE_SCHED_GETCPU)
+IF(HAVE_SCHED_GETCPU)
+ ADD_DEFINITIONS(-DHAVE_SCHED_GETCPU=1)
+# MariaDB: don't do this:
+# ADD_DEFINITIONS(-DZSTD_STATIC_LINKING_ONLY)
+ENDIF()
+
+IF (WITH_TBB)
+ FIND_LIBRARY(TBB_LIBRARY
+ NAMES libtbb${PIC_EXT}.a tbb
+ HINTS ${WITH_TBB}/lib)
+ SET(rocksdb_static_libs ${rocksdb_static_libs}
+ ${TBB_LIBRARY})
+ ADD_DEFINITIONS(-DTBB)
+ENDIF()
+
+#
+# MariaDB: Dynamic plugin build is not suitable with unittest ATM
+#
+#IF(WITH_UNIT_TESTS AND WITH_EMBEDDED_SERVER)
+# ADD_SUBDIRECTORY(unittest)
+#ENDIF()
+
+if (UNIX AND NOT APPLE)
+ SET(rocksdb_static_libs ${rocksdb_static_libs} "-lrt")
+endif()
+
+
+ADD_LIBRARY(rocksdb_tools STATIC
+ rocksdb/tools/ldb_tool.cc
+ rocksdb/tools/ldb_cmd.cc
+ rocksdb/tools/sst_dump_tool.cc
+)
+
+MYSQL_ADD_EXECUTABLE(sst_dump rocksdb/tools/sst_dump.cc COMPONENT rocksdb-engine)
+TARGET_LINK_LIBRARIES(sst_dump rocksdblib)
+
+MYSQL_ADD_EXECUTABLE(mariadb-ldb tools/mysql_ldb.cc COMPONENT rocksdb-engine)
+TARGET_LINK_LIBRARIES(mariadb-ldb rocksdb_tools rocksdb_aux_lib dbug)
+
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/myrocks_hotbackup.py
+ ${CMAKE_CURRENT_BINARY_DIR}/myrocks_hotbackup @ONLY)
+INSTALL_SCRIPT(${CMAKE_CURRENT_BINARY_DIR}/myrocks_hotbackup COMPONENT rocksdb-engine)
+
+IF(MSVC)
+ # RocksDB, the storage engine, overdoes "const" by adding
+ # additional const qualifiers to parameters of the overriden virtual functions
+ # This creates a lot of warnings, that we silence here.
+ ADD_DEFINITIONS(/wd4373)
+ # Some checks in C++ runtime that make debug build much slower
+ ADD_DEFINITIONS(-D_ITERATOR_DEBUG_LEVEL=0)
+
+ # Temporarily disable "conversion from size_t .." warnings
+ IF(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267")
+ ENDIF()
+ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ SET_TARGET_PROPERTIES(rocksdb_tools sst_dump mariadb-ldb PROPERTIES COMPILE_FLAGS "-Wno-error")
+ENDIF()
+
+IF(GIT_EXECUTABLE AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/.git)
+ EXECUTE_PROCESS(
+ COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb
+ OUTPUT_VARIABLE OUT RESULT_VARIABLE RES)
+ IF(RES EQUAL 0)
+ STRING(REGEX REPLACE "\n$" "" ROCKSDB_GIT_HASH "${OUT}")
+ ENDIF()
+ENDIF()
+IF(ROCKSDB_GIT_HASH OR
+ (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/rdb_source_revision.h))
+ CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/rdb_source_revision.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rdb_source_revision.h )
+ENDIF()
diff --git a/storage/rocksdb/README b/storage/rocksdb/README
new file mode 100644
index 00000000..3af45592
--- /dev/null
+++ b/storage/rocksdb/README
@@ -0,0 +1,50 @@
+== Summary ==
+This directory contains RocksDB-based Storage Engine (RDBSE) for MySQL,
+also known as "MyRocks".
+
+== Resources ==
+https://github.com/facebook/mysql-5.6/wiki/Getting-Started-with-MyRocks
+https://www.facebook.com/groups/MyRocks/
+
+== Coding Conventions ==
+The baseline for MyRocks coding conventions for the code in storage/rocksdb/
+is based on the default clang format with a few minor changes. The file
+storage/rocksdb/.clang-format describes conventions and can be integrated
+with Vim or Emacs as described here:
+http://releases.llvm.org/3.6.0/tools/clang/docs/ClangFormat.html#vim-integration
+
+All code outside of storage/rocksdb/ should conform to the MySQL coding
+conventions:
+http://dev.mysql.com/doc/internals/en/coding-guidelines.html.
+
+Several refinements:
+ 0. There is an umbrella C++ namespace named "myrocks" for all MyRocks code.
+ 1. We introduced "RDB" as the super-short abbreviation for "RocksDB". We will
+ use it as a name prefix, with different capitalization (see below), to ease
+ up code navigation with ctags and grep.
+ N.B. For ease of matching, we'll keep the variables and functions dealing
+ with sysvars as close as possible to the outside visible names of
+ sysvars, which start with "rocksdb_" prefix, the outward storage
+ engine name.
+ 2. The names for classes, interfaces, and C++ structures (which act as
+ classes), start with prefix "Rdb_".
+ NB: For historical reasons, we'll keep the "ha_<storage_engine_name>" class
+ name for ha_rocksdb class, which is an exception to the rule.
+ 3. The names for global objects and functions start with prefix "rdb_".
+ 4. The names for macros and constants start with prefix "RDB_".
+ 5. Regular class member names start with "m_".
+ 6. Static class member names start with "s_".
+ 7. Given the 80 character per line limit, we'll not always use full English
+ words in names, when a well known or easily recognizable abbreviation
+ exists (like "tx" for "transaction" or "param" for "parameter" etc).
+ 8. When needing to disambiguate, we use different suffixes for that, like
+ "_arg" for a function argument/parameter, "_arr" for a C style array, and
+ "_vect" for a std::vector etc.
+
+== Running Tests ==
+To run tests from rocksdb, rocksdb_rpl or other rocksdb_* packages, use the
+following parameters:
+ --default-storage-engine=rocksdb
+ --skip-innodb
+ --default-tmp-storage-engine=MyISAM
+ --rocksdb
diff --git a/storage/rocksdb/atomic_stat.h b/storage/rocksdb/atomic_stat.h
new file mode 100644
index 00000000..04e59bd9
--- /dev/null
+++ b/storage/rocksdb/atomic_stat.h
@@ -0,0 +1,94 @@
+/* This is an atomic integer abstract data type, for high-performance
+ tracking of a single stat. It intentionally permits inconsistent
+ atomic operations and reads, for better performance. This means
+ that, though no data should ever be lost by this stat, reads of it
+ at any time may not include all changes up to any particular point.
+
+ So, values read from these may only be approximately correct.
+
+ If your use-case will fail under these conditions, do not use this.
+
+ Copyright (C) 2012 - 2014 Steaphan Greene <steaphan@gmail.com>
+
+ 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 2
+ 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, write to the
+ Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _atomic_stat_h_
+#define _atomic_stat_h_
+
+#include <atomic>
+
+template < typename TYPE >
+class atomic_stat {
+public:
+ // Initialize value to the default for the type
+ atomic_stat() : value_(TYPE()) {};
+
+ // This enforces a strict order, as all absolute sets should
+ void clear() {
+ value_.store(TYPE(), std::memory_order_seq_cst);
+ };
+
+ // Reads can get any valid value, it doesn't matter which, exactly
+ TYPE load() const {
+ return value_.load(std::memory_order_relaxed);
+ };
+
+ // This only supplies relative arithmetic operations
+ // These are all done atomically, and so can show up in any order
+ void inc(const TYPE &other) {
+ value_.fetch_add(other, std::memory_order_relaxed);
+ };
+
+ void dec(const TYPE &other) {
+ value_.fetch_sub(other, std::memory_order_relaxed);
+ };
+
+ void inc() {
+ value_.fetch_add(1, std::memory_order_relaxed);
+ };
+
+ void dec() {
+ value_.fetch_sub(1, std::memory_order_relaxed);
+ };
+
+ // This will make one attempt to set the value to the max of
+ // the current value, and the passed-in value. It can fail
+ // for any reason, and we only try it once.
+ void set_max_maybe(const TYPE &new_val) {
+ TYPE old_val = value_;
+ if (new_val > old_val) {
+ value_.compare_exchange_weak(old_val, new_val,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+ };
+
+ // This will make one attempt to assign the value to the passed-in
+ // value. It can fail for any reason, and we only try it once.
+ void set_maybe(const TYPE &new_val) {
+ TYPE old_val = value_;
+ value_.compare_exchange_weak(old_val, new_val,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ };
+
+private:
+ std::atomic<TYPE> value_;
+};
+
+#endif // _atomic_stat_h_
diff --git a/storage/rocksdb/build_rocksdb.cmake b/storage/rocksdb/build_rocksdb.cmake
new file mode 100644
index 00000000..762368b4
--- /dev/null
+++ b/storage/rocksdb/build_rocksdb.cmake
@@ -0,0 +1,506 @@
+
+if(POLICY CMP0042)
+ cmake_policy(SET CMP0042 NEW)
+endif()
+
+SET(ROCKSDB_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb)
+
+INCLUDE_DIRECTORIES(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${ROCKSDB_SOURCE_DIR}
+ ${ROCKSDB_SOURCE_DIR}/include
+ ${ROCKSDB_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src
+)
+
+list(APPEND CMAKE_MODULE_PATH "${ROCKSDB_SOURCE_DIR}/cmake/modules/")
+
+if(WIN32)
+ # include(${ROCKSDB_SOURCE_DIR}/thirdparty.inc)
+else()
+ option(WITH_ROCKSDB_JEMALLOC "build RocksDB with JeMalloc" OFF)
+ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ # FreeBSD has jemaloc as default malloc
+ add_definitions(-DROCKSDB_JEMALLOC)
+ ADD_DEFINITIONS(-DROCKSDB_MALLOC_USABLE_SIZE)
+ set(WITH_JEMALLOC ON)
+ elseif(WITH_ROCKSDB_JEMALLOC)
+ find_package(JeMalloc REQUIRED)
+ add_definitions(-DROCKSDB_JEMALLOC)
+ ADD_DEFINITIONS(-DROCKSDB_MALLOC_USABLE_SIZE)
+ include_directories(${JEMALLOC_INCLUDE_DIR})
+ endif()
+endif()
+
+
+# Optional compression libraries.
+
+include(CheckFunctionExists)
+macro(check_lib package var)
+ STRING(TOUPPER ${package} PACKAGE_NAME)
+ SET(WITH_ROCKSDB_${package} AUTO CACHE STRING
+ "Build RocksDB with ${package} compression. Possible values are 'ON', 'OFF', 'AUTO' and default is 'AUTO'")
+
+ IF (NOT ${WITH_ROCKSDB_${package}} STREQUAL "OFF")
+ FIND_PACKAGE(${package} QUIET)
+ SET(HAVE_ROCKSDB_${PACKAGE_NAME} TRUE)
+ IF (${${PACKAGE_NAME}_FOUND})
+ IF(${ARGC} GREATER 2)
+ SET(CMAKE_REQUIRED_LIBRARIES ${${var}_LIBRARIES})
+ CHECK_FUNCTION_EXISTS(${ARGV2} ${var}_VALID)
+ UNSET(CMAKE_REQUIRED_LIBRARIES)
+ ELSE()
+ SET(${var}_VALID TRUE)
+ ENDIF()
+ ENDIF()
+ ENDIF()
+ ADD_FEATURE_INFO(ROCKSDB_${PACKAGE_NAME} HAVE_ROCKSDB_${PACKAGE_NAME} "${package} Compression in the RocksDB storage engine")
+
+ IF(${${var}_VALID})
+ MESSAGE_ONCE(rocksdb_${var} "Found ${package}: ${${var}_LIBRARIES}")
+ add_definitions(-D${PACKAGE_NAME})
+ include_directories(${${var}_INCLUDE_DIR})
+ list(APPEND THIRDPARTY_LIBS ${${var}_LIBRARIES})
+ ELSEIF(${${PACKAGE_NAME}_FOUND})
+ MESSAGE_ONCE(rocksdb_${var} "Found unusable ${package}: ${${var}_LIBRARIES} [${ARGV2}]")
+ ELSE()
+ MESSAGE_ONCE(rocksdb_${var} "Could NOT find ${package}")
+ ENDIF()
+
+ IF (${WITH_ROCKSDB_${package}} STREQUAL "ON" AND NOT ${${PACKAGE_NAME}_FOUND})
+ MESSAGE(FATAL_ERROR
+ "${package} library was not found, but WITH_ROCKSDB_${package} option is ON.\
+ Either set WITH_ROCKSDB_${package} to OFF, or make sure ${package} is installed")
+ endif()
+endmacro()
+
+check_lib(LZ4 LZ4)
+check_lib(BZip2 BZIP2)
+check_lib(snappy snappy) # rocksdb/cmake/modules/Findsnappy.cmake violates the convention
+check_lib(ZSTD ZSTD ZDICT_trainFromBuffer)
+
+add_definitions(-DZLIB)
+list(APPEND THIRDPARTY_LIBS ${ZLIB_LIBRARY})
+ADD_FEATURE_INFO(ROCKSDB_ZLIB "ON" "zlib Compression in the RocksDB storage engine")
+
+if(CMAKE_SYSTEM_NAME MATCHES "Cygwin")
+ add_definitions(-fno-builtin-memcmp -DCYGWIN)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ add_definitions(-DOS_MACOSX)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ add_definitions(-DOS_LINUX)
+elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
+ add_definitions(-DOS_SOLARIS)
+elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ add_definitions(-DOS_FREEBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
+ add_definitions(-DOS_NETBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ add_definitions(-DOS_OPENBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly")
+ add_definitions(-DOS_DRAGONFLYBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Android")
+ add_definitions(-DOS_ANDROID)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ add_definitions(-DOS_WIN)
+endif()
+
+IF(MSVC)
+ add_definitions(/wd4244)
+ENDIF()
+if(NOT WIN32)
+ add_definitions(-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX)
+endif()
+
+include(CheckCCompilerFlag)
+# ppc64 or ppc64le or powerpc64 (BSD)
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64|powerpc64")
+ CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
+ if(HAS_ALTIVEC)
+ message(STATUS " HAS_ALTIVEC yes")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
+ endif(HAS_ALTIVEC)
+ if(NOT CMAKE_C_FLAGS MATCHES "m(cpu|tune)")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8")
+ endif()
+ if(NOT CMAKE_CXX_FLAGS MATCHES "m(cpu|tune)")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8")
+ endif()
+ ADD_DEFINITIONS(-DHAVE_POWER8 -DHAS_ALTIVEC)
+endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64|powerpc64")
+
+if(CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
+ set(SYSTEM_LIBS ${SYSTEM_LIBS} -latomic)
+endif()
+
+option(WITH_FALLOCATE "build with fallocate" ON)
+
+if(WITH_FALLOCATE AND UNIX)
+ include(CheckCSourceCompiles)
+ CHECK_C_SOURCE_COMPILES("
+#include <fcntl.h>
+#include <linux/falloc.h>
+int main() {
+ int fd = open(\"/dev/null\", 0);
+ fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024);
+}
+" HAVE_FALLOCATE)
+ if(HAVE_FALLOCATE)
+ add_definitions(-DROCKSDB_FALLOCATE_PRESENT)
+ endif()
+endif()
+
+CHECK_FUNCTION_EXISTS(malloc_usable_size HAVE_MALLOC_USABLE_SIZE)
+if(HAVE_MALLOC_USABLE_SIZE)
+ add_definitions(-DROCKSDB_MALLOC_USABLE_SIZE)
+endif()
+
+include_directories(${ROCKSDB_SOURCE_DIR})
+include_directories(${ROCKSDB_SOURCE_DIR}/include)
+include_directories(SYSTEM ${ROCKSDB_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
+
+find_package(Threads REQUIRED)
+if(WIN32)
+ set(SYSTEM_LIBS ${SYSTEM_LIBS} Shlwapi.lib Rpcrt4.lib)
+else()
+ set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT} ${LIBRT} ${CMAKE_DL_LIBS})
+endif()
+
+set(ROCKSDB_LIBS rocksdblib})
+set(LIBS ${ROCKSDB_LIBS} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
+
+#add_subdirectory(${ROCKSDB_SOURCE_DIR}/tools)
+
+# Main library source code
+# Note : RocksDB has a lot of unittests. We should not include these files
+# in the build, because 1. they are not needed and 2. gtest causes warnings
+# in windows build, which are treated as errors and cause the build to fail.
+#
+# Unit tests themselves:
+# - *_test.cc
+# - *_bench.cc
+#
+# - table/mock_table.cc
+# - utilities/cassandra/cassandra_compaction_filter.cc
+# - utilities/cassandra/format.cc
+# - utilities/cassandra/merge_operator.cc
+# - utilities/cassandra/test_utils.cc
+#
+set(ROCKSDB_SOURCES
+ cache/clock_cache.cc
+ cache/lru_cache.cc
+ cache/sharded_cache.cc
+ db/arena_wrapped_db_iter.cc
+ db/builder.cc
+ db/c.cc
+ db/column_family.cc
+ db/compacted_db_impl.cc
+ db/compaction/compaction.cc
+ db/compaction/compaction_iterator.cc
+ db/compaction/compaction_picker.cc
+ db/compaction/compaction_job.cc
+ db/compaction/compaction_picker_fifo.cc
+ db/compaction/compaction_picker_level.cc
+ db/compaction/compaction_picker_universal.cc
+ db/convenience.cc
+ db/db_filesnapshot.cc
+ db/db_impl/db_impl.cc
+ db/db_impl/db_impl_write.cc
+ db/db_impl/db_impl_compaction_flush.cc
+ db/db_impl/db_impl_files.cc
+ db/db_impl/db_impl_open.cc
+ db/db_impl/db_impl_debug.cc
+ db/db_impl/db_impl_experimental.cc
+ db/db_impl/db_impl_readonly.cc
+ db/db_impl/db_impl_secondary.cc
+ db/db_info_dumper.cc
+ db/db_iter.cc
+ db/dbformat.cc
+ db/error_handler.cc
+ db/event_helpers.cc
+ db/experimental.cc
+ db/external_sst_file_ingestion_job.cc
+ db/file_indexer.cc
+ db/flush_job.cc
+ db/flush_scheduler.cc
+ db/forward_iterator.cc
+ db/import_column_family_job.cc
+ db/internal_stats.cc
+ db/logs_with_prep_tracker.cc
+ db/log_reader.cc
+ db/log_writer.cc
+ db/malloc_stats.cc
+ db/memtable.cc
+ db/memtable_list.cc
+ db/merge_helper.cc
+ db/merge_operator.cc
+ db/range_del_aggregator.cc
+ db/range_tombstone_fragmenter.cc
+ db/repair.cc
+ db/snapshot_impl.cc
+ db/table_cache.cc
+ db/table_properties_collector.cc
+ db/transaction_log_impl.cc
+ db/trim_history_scheduler.cc
+ db/version_builder.cc
+ db/version_edit.cc
+ db/version_set.cc
+ db/wal_manager.cc
+ db/write_batch.cc
+ db/write_batch_base.cc
+ db/write_controller.cc
+ db/write_thread.cc
+ env/env.cc
+ env/env_chroot.cc
+ env/env_encryption.cc
+ env/env_hdfs.cc
+ env/file_system.cc
+ env/mock_env.cc
+ file/delete_scheduler.cc
+ file/file_prefetch_buffer.cc
+ file/file_util.cc
+ file/filename.cc
+ file/random_access_file_reader.cc
+ file/read_write_util.cc
+ file/readahead_raf.cc
+ file/sequence_file_reader.cc
+ file/sst_file_manager_impl.cc
+ file/writable_file_writer.cc
+ logging/auto_roll_logger.cc
+ logging/event_logger.cc
+ logging/log_buffer.cc
+ memory/arena.cc
+ memory/concurrent_arena.cc
+ memory/jemalloc_nodump_allocator.cc
+ memtable/alloc_tracker.cc
+ memtable/hash_linklist_rep.cc
+ memtable/hash_skiplist_rep.cc
+ memtable/skiplistrep.cc
+ memtable/vectorrep.cc
+ memtable/write_buffer_manager.cc
+ monitoring/histogram.cc
+ monitoring/histogram_windowing.cc
+ monitoring/in_memory_stats_history.cc
+ monitoring/instrumented_mutex.cc
+ monitoring/iostats_context.cc
+ monitoring/perf_context.cc
+ monitoring/perf_level.cc
+ monitoring/persistent_stats_history.cc
+ monitoring/statistics.cc
+ monitoring/thread_status_impl.cc
+ monitoring/thread_status_updater.cc
+ monitoring/thread_status_util.cc
+ monitoring/thread_status_util_debug.cc
+ options/cf_options.cc
+ options/db_options.cc
+ options/options.cc
+ options/options_helper.cc
+ options/options_parser.cc
+ options/options_sanity_check.cc
+ port/stack_trace.cc
+ table/adaptive/adaptive_table_factory.cc
+ table/block_based/block.cc
+ table/block_based/block_based_filter_block.cc
+ table/block_based/block_based_table_builder.cc
+ table/block_based/block_based_table_factory.cc
+ table/block_based/block_based_table_reader.cc
+ table/block_based/block_builder.cc
+ table/block_based/block_prefix_index.cc
+ table/block_based/data_block_hash_index.cc
+ table/block_based/data_block_footer.cc
+ table/block_based/filter_block_reader_common.cc
+ table/block_based/filter_policy.cc
+ table/block_based/flush_block_policy.cc
+ table/block_based/full_filter_block.cc
+ table/block_based/index_builder.cc
+ table/block_based/parsed_full_filter_block.cc
+ table/block_based/partitioned_filter_block.cc
+ table/block_based/uncompression_dict_reader.cc
+ table/block_fetcher.cc
+ table/cuckoo/cuckoo_table_builder.cc
+ table/cuckoo/cuckoo_table_factory.cc
+ table/cuckoo/cuckoo_table_reader.cc
+ table/format.cc
+ table/get_context.cc
+ table/iterator.cc
+ table/merging_iterator.cc
+ table/meta_blocks.cc
+ table/persistent_cache_helper.cc
+ table/plain/plain_table_bloom.cc
+ table/plain/plain_table_builder.cc
+ table/plain/plain_table_factory.cc
+ table/plain/plain_table_index.cc
+ table/plain/plain_table_key_coding.cc
+ table/plain/plain_table_reader.cc
+ table/sst_file_reader.cc
+ table/sst_file_writer.cc
+ table/table_properties.cc
+ table/two_level_iterator.cc
+ test_util/sync_point.cc
+ test_util/sync_point_impl.cc
+ test_util/testutil.cc
+ test_util/transaction_test_util.cc
+ tools/block_cache_analyzer/block_cache_trace_analyzer.cc
+ tools/dump/db_dump_tool.cc
+ tools/ldb_cmd.cc
+ tools/ldb_tool.cc
+ tools/sst_dump_tool.cc
+ tools/trace_analyzer_tool.cc
+ trace_replay/trace_replay.cc
+ trace_replay/block_cache_tracer.cc
+ util/coding.cc
+ util/compaction_job_stats_impl.cc
+ util/comparator.cc
+ util/compression_context_cache.cc
+ util/concurrent_task_limiter_impl.cc
+ util/crc32c.cc
+ util/dynamic_bloom.cc
+ util/hash.cc
+ util/murmurhash.cc
+ util/random.cc
+ util/rate_limiter.cc
+ util/slice.cc
+ util/file_checksum_helper.cc
+ util/status.cc
+ util/string_util.cc
+ util/thread_local.cc
+ util/threadpool_imp.cc
+ util/xxhash.cc
+ utilities/backupable/backupable_db.cc
+ utilities/blob_db/blob_compaction_filter.cc
+ utilities/blob_db/blob_db.cc
+ utilities/blob_db/blob_db_impl.cc
+ utilities/blob_db/blob_db_impl_filesnapshot.cc
+ utilities/blob_db/blob_dump_tool.cc
+ utilities/blob_db/blob_file.cc
+ utilities/blob_db/blob_log_reader.cc
+ utilities/blob_db/blob_log_writer.cc
+ utilities/blob_db/blob_log_format.cc
+ utilities/checkpoint/checkpoint_impl.cc
+ utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
+ utilities/debug.cc
+ utilities/env_mirror.cc
+ utilities/env_timed.cc
+ utilities/leveldb_options/leveldb_options.cc
+ utilities/memory/memory_util.cc
+ utilities/merge_operators/bytesxor.cc
+ utilities/merge_operators/max.cc
+ utilities/merge_operators/put.cc
+ utilities/merge_operators/sortlist.cc
+ utilities/merge_operators/string_append/stringappend.cc
+ utilities/merge_operators/string_append/stringappend2.cc
+ utilities/merge_operators/uint64add.cc
+ utilities/object_registry.cc
+ utilities/option_change_migration/option_change_migration.cc
+ utilities/options/options_util.cc
+ utilities/persistent_cache/block_cache_tier.cc
+ utilities/persistent_cache/block_cache_tier_file.cc
+ utilities/persistent_cache/block_cache_tier_metadata.cc
+ utilities/persistent_cache/persistent_cache_tier.cc
+ utilities/persistent_cache/volatile_tier_impl.cc
+ utilities/simulator_cache/cache_simulator.cc
+ utilities/simulator_cache/sim_cache.cc
+ utilities/table_properties_collectors/compact_on_deletion_collector.cc
+ utilities/trace/file_trace_reader_writer.cc
+ utilities/transactions/optimistic_transaction_db_impl.cc
+ utilities/transactions/optimistic_transaction.cc
+ utilities/transactions/pessimistic_transaction.cc
+ utilities/transactions/pessimistic_transaction_db.cc
+ utilities/transactions/snapshot_checker.cc
+ utilities/transactions/transaction_base.cc
+ utilities/transactions/transaction_db_mutex_impl.cc
+ utilities/transactions/transaction_lock_mgr.cc
+ utilities/transactions/transaction_util.cc
+ utilities/transactions/write_prepared_txn.cc
+ utilities/transactions/write_prepared_txn_db.cc
+ utilities/transactions/write_unprepared_txn.cc
+ utilities/transactions/write_unprepared_txn_db.cc
+ utilities/ttl/db_ttl_impl.cc
+ utilities/write_batch_with_index/write_batch_with_index.cc
+ utilities/write_batch_with_index/write_batch_with_index_internal.cc
+)
+
+
+if(WIN32)
+ list(APPEND ROCKSDB_SOURCES
+ port/win/io_win.cc
+ port/win/env_win.cc
+ port/win/env_default.cc
+ port/win/port_win.cc
+ port/win/win_logger.cc
+ port/win/win_thread.cc
+ port/win/xpress_win.cc)
+else()
+ list(APPEND ROCKSDB_SOURCES
+ port/port_posix.cc
+ env/env_posix.cc
+ env/io_posix.cc
+ env/fs_posix.cc)
+ # ppc64 or ppc64le
+ if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64")
+ enable_language(ASM)
+ list(APPEND ROCKSDB_SOURCES
+ util/crc32c_ppc.c
+ util/crc32c_ppc_asm.S)
+ endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64")
+ # aarch
+ if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
+ INCLUDE(CheckCXXCompilerFlag)
+ CHECK_CXX_COMPILER_FLAG("-march=armv8-a+crc+crypto" HAS_ARMV8_CRC)
+ if(HAS_ARMV8_CRC)
+ message(STATUS " HAS_ARMV8_CRC yes")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
+ list(APPEND ROCKSDB_SOURCES
+ util/crc32c_arm64.cc)
+ endif(HAS_ARMV8_CRC)
+ endif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
+endif()
+SET(SOURCES)
+FOREACH(s ${ROCKSDB_SOURCES})
+ list(APPEND SOURCES ${ROCKSDB_SOURCE_DIR}/${s})
+ENDFOREACH()
+
+if(MSVC)
+ add_definitions(-DHAVE_SSE42 -DHAVE_PCLMUL)
+ # Workaround broken compilation with -DWIN32_LEAN_AND_MEAN
+ # (https://github.com/facebook/rocksdb/issues/4344)
+ set_source_files_properties(${ROCKSDB_SOURCE_DIR}/port/win/env_win.cc
+ PROPERTIES COMPILE_FLAGS "/FI\"windows.h\" /FI\"winioctl.h\"")
+
+ # Workaround Win8.1 SDK bug, that breaks /permissive-
+ string(REPLACE "/permissive-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+else()
+ set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul ${CXX11_FLAGS}")
+
+ CHECK_CXX_SOURCE_COMPILES("
+#include <cstdint>
+#include <nmmintrin.h>
+#include <wmmintrin.h>
+int main() {
+ volatile uint32_t x = _mm_crc32_u32(0, 0);
+ 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);
+}
+" HAVE_SSE42)
+ if(HAVE_SSE42)
+ set_source_files_properties(${ROCKSDB_SOURCE_DIR}/util/crc32c.cc
+ PROPERTIES COMPILE_FLAGS "-DHAVE_SSE42 -DHAVE_PCLMUL -msse4.2 -mpclmul")
+ endif()
+ unset(CMAKE_REQUIRED_FLAGS)
+endif()
+
+IF(CMAKE_VERSION VERSION_GREATER "2.8.10")
+ STRING(TIMESTAMP GIT_DATE_TIME "%Y-%m-%d %H:%M:%S")
+ENDIF()
+
+CONFIGURE_FILE(${ROCKSDB_SOURCE_DIR}/util/build_version.cc.in build_version.cc @ONLY)
+INCLUDE_DIRECTORIES(${ROCKSDB_SOURCE_DIR}/util)
+list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/build_version.cc)
+
+ADD_CONVENIENCE_LIBRARY(rocksdblib ${SOURCES})
+target_link_libraries(rocksdblib ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
+IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set_target_properties(rocksdblib PROPERTIES COMPILE_FLAGS "-fPIC -fno-builtin-memcmp -Wno-error")
+endif()
diff --git a/storage/rocksdb/event_listener.cc b/storage/rocksdb/event_listener.cc
new file mode 100644
index 00000000..4995e407
--- /dev/null
+++ b/storage/rocksdb/event_listener.cc
@@ -0,0 +1,96 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+/* The C++ file's header */
+#include "./event_listener.h"
+
+/* C++ standard header files */
+#include <string>
+#include <vector>
+
+/* MySQL includes */
+#include <mysql/plugin.h>
+
+/* MyRocks includes */
+#include "./ha_rocksdb.h"
+#include "./properties_collector.h"
+#include "./rdb_datadic.h"
+
+namespace myrocks {
+
+static std::vector<Rdb_index_stats> extract_index_stats(
+ const std::vector<std::string> &files,
+ const rocksdb::TablePropertiesCollection &props) {
+ std::vector<Rdb_index_stats> ret;
+ for (auto fn : files) {
+ const auto it = props.find(fn);
+ DBUG_ASSERT(it != props.end());
+ std::vector<Rdb_index_stats> stats;
+ Rdb_tbl_prop_coll::read_stats_from_tbl_props(it->second, &stats);
+ ret.insert(ret.end(), stats.begin(), stats.end());
+ }
+ return ret;
+}
+
+void Rdb_event_listener::update_index_stats(
+ const rocksdb::TableProperties &props) {
+ DBUG_ASSERT(m_ddl_manager != nullptr);
+ const auto tbl_props =
+ std::make_shared<const rocksdb::TableProperties>(props);
+
+ std::vector<Rdb_index_stats> stats;
+ Rdb_tbl_prop_coll::read_stats_from_tbl_props(tbl_props, &stats);
+
+ m_ddl_manager->adjust_stats(stats);
+}
+
+void Rdb_event_listener::OnCompactionCompleted(
+ rocksdb::DB *db, const rocksdb::CompactionJobInfo &ci) {
+ DBUG_ASSERT(db != nullptr);
+ DBUG_ASSERT(m_ddl_manager != nullptr);
+
+ if (ci.status.ok()) {
+ m_ddl_manager->adjust_stats(
+ extract_index_stats(ci.output_files, ci.table_properties),
+ extract_index_stats(ci.input_files, ci.table_properties));
+ }
+}
+
+void Rdb_event_listener::OnFlushCompleted(
+ rocksdb::DB *db, const rocksdb::FlushJobInfo &flush_job_info) {
+ DBUG_ASSERT(db != nullptr);
+ update_index_stats(flush_job_info.table_properties);
+}
+
+void Rdb_event_listener::OnExternalFileIngested(
+ rocksdb::DB *db, const rocksdb::ExternalFileIngestionInfo &info) {
+ DBUG_ASSERT(db != nullptr);
+ update_index_stats(info.table_properties);
+}
+
+void Rdb_event_listener::OnBackgroundError(
+ rocksdb::BackgroundErrorReason reason, rocksdb::Status *status) {
+ rdb_log_status_error(*status, "Error detected in background");
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: BackgroundErrorReason: %d", (int)reason);
+ if (status->IsCorruption()) {
+ rdb_persist_corruption_marker();
+ abort();
+ }
+}
+} // namespace myrocks
diff --git a/storage/rocksdb/event_listener.h b/storage/rocksdb/event_listener.h
new file mode 100644
index 00000000..737973eb
--- /dev/null
+++ b/storage/rocksdb/event_listener.h
@@ -0,0 +1,49 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#include "rocksdb/listener.h"
+
+namespace myrocks {
+
+class Rdb_ddl_manager;
+
+class Rdb_event_listener : public rocksdb::EventListener {
+ public:
+ Rdb_event_listener(const Rdb_event_listener &) = delete;
+ Rdb_event_listener &operator=(const Rdb_event_listener &) = delete;
+
+ explicit Rdb_event_listener(Rdb_ddl_manager *const ddl_manager)
+ : m_ddl_manager(ddl_manager) {}
+
+ void OnCompactionCompleted(rocksdb::DB *db,
+ const rocksdb::CompactionJobInfo &ci) override;
+ void OnFlushCompleted(rocksdb::DB *db,
+ const rocksdb::FlushJobInfo &flush_job_info) override;
+ void OnExternalFileIngested(
+ rocksdb::DB *db,
+ const rocksdb::ExternalFileIngestionInfo &ingestion_info) override;
+
+ void OnBackgroundError(rocksdb::BackgroundErrorReason reason,
+ rocksdb::Status *status) override;
+
+ private:
+ Rdb_ddl_manager *m_ddl_manager;
+
+ void update_index_stats(const rocksdb::TableProperties &props);
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/get_rocksdb_files.sh b/storage/rocksdb/get_rocksdb_files.sh
new file mode 100755
index 00000000..bd5128a8
--- /dev/null
+++ b/storage/rocksdb/get_rocksdb_files.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+MKFILE=`mktemp`
+# create and run a simple makefile
+# include rocksdb make file relative to the path of this script
+echo "include ./storage/rocksdb/rocksdb/src.mk
+all:
+ @echo \$(LIB_SOURCES)" > $MKFILE
+for f in `make --makefile $MKFILE`
+do
+ echo ./rocksdb/$f
+done
+rm $MKFILE
+
+# create build_version.cc file. Only create one if it doesn't exists or if it is different
+# this is so that we don't rebuild mysqld every time
+bv=storage/rocksdb/rocksdb/util/build_version.cc
+date=$(date +%F)
+git_sha=$(pushd storage/rocksdb/rocksdb >/dev/null && git rev-parse HEAD 2>/dev/null && popd >/dev/null)
+if [ ! -f $bv ] || [ -z $git_sha ] || [ ! `grep $git_sha $bv` ]
+then
+echo "#include \"build_version.h\"
+const char* rocksdb_build_git_sha =
+\"rocksdb_build_git_sha:$git_sha\";
+const char* rocksdb_build_git_date =
+\"rocksdb_build_git_date:$date\";
+const char* rocksdb_build_compile_date = __DATE__;" > $bv
+fi
diff --git a/storage/rocksdb/ha_rocksdb.cc b/storage/rocksdb/ha_rocksdb.cc
new file mode 100644
index 00000000..f89601b1
--- /dev/null
+++ b/storage/rocksdb/ha_rocksdb.cc
@@ -0,0 +1,14638 @@
+/*
+ Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#define MYSQL_SERVER 1
+
+/* For use of 'PRIu64': */
+#define __STDC_FORMAT_MACROS
+
+#include <my_global.h>
+
+#include <inttypes.h>
+
+/* The C++ file's header */
+#include "./ha_rocksdb.h"
+
+/* C++ standard header files */
+#include <inttypes.h>
+#include <algorithm>
+#include <limits>
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+#include <vector>
+
+/* MySQL includes */
+#include <mysql/psi/mysql_table.h>
+// #include <thread_pool_priv.h>
+#include <mysys_err.h>
+#include "./debug_sync.h"
+#include "./my_bit.h"
+#include "./my_stacktrace.h"
+#include "./my_sys.h"
+#include "./sql_audit.h"
+#include "./sql_table.h"
+#include "./sql_hset.h"
+#ifdef MARIAROCKS_NOT_YET
+#endif
+
+/* RocksDB includes */
+#include "monitoring/histogram.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/env.h"
+#include "rocksdb/memory_allocator.h"
+#include "rocksdb/persistent_cache.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/thread_status.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "rocksdb/utilities/convenience.h"
+#include "rocksdb/utilities/memory_util.h"
+#include "rocksdb/utilities/sim_cache.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/stop_watch.h"
+#include "./rdb_source_revision.h"
+
+// MariaRocks: this is needed to access RocksDB debug syncpoints:
+#include "test_util/sync_point.h"
+
+/* MyRocks includes */
+#include "./event_listener.h"
+#include "./ha_rocksdb_proto.h"
+#include "./logger.h"
+#include "./nosql_access.h"
+#include "./rdb_cf_manager.h"
+#include "./rdb_cf_options.h"
+#include "./rdb_converter.h"
+#include "./rdb_datadic.h"
+#include "./rdb_i_s.h"
+#include "./rdb_index_merge.h"
+#include "./rdb_mutex_wrapper.h"
+#include "./rdb_psi.h"
+#include "./rdb_threads.h"
+#include "./rdb_mariadb_server_port.h"
+
+// Internal MySQL APIs not exposed in any header.
+extern "C" {
+/**
+ Mark transaction to rollback and mark error as fatal to a sub-statement.
+ @param thd Thread handle
+ @param all TRUE <=> rollback main transaction.
+*/
+void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all);
+
+/**
+ * Get the user thread's binary logging format
+ * @param thd user thread
+ * @return Value to be used as index into the binlog_format_names array
+ */
+int thd_binlog_format(const MYSQL_THD thd);
+
+/**
+ * Check if binary logging is filtered for thread's current db.
+ * @param thd Thread handle
+ * @retval 1 the query is not filtered, 0 otherwise.
+ */
+bool thd_binlog_filter_ok(const MYSQL_THD thd);
+}
+
+extern my_bool opt_core_file;
+
+// Needed in rocksdb_init_func
+void ignore_db_dirs_append(const char *dirname_arg);
+
+
+namespace myrocks {
+
+static st_global_stats global_stats;
+static st_export_stats export_stats;
+static st_memory_stats memory_stats;
+static st_io_stall_stats io_stall_stats;
+
+const std::string DEFAULT_CF_NAME("default");
+const std::string DEFAULT_SYSTEM_CF_NAME("__system__");
+const std::string PER_INDEX_CF_NAME("$per_index_cf");
+
+static std::vector<GL_INDEX_ID> rdb_indexes_to_recalc;
+
+#ifdef MARIADB_NOT_YET
+class Rdb_explicit_snapshot : public explicit_snapshot {
+ public:
+ static std::shared_ptr<Rdb_explicit_snapshot> create(
+ snapshot_info_st *ss_info, rocksdb::DB *db,
+ const rocksdb::Snapshot *snapshot) {
+ std::lock_guard<std::mutex> lock(explicit_snapshot_mutex);
+ auto s = std::unique_ptr<rocksdb::ManagedSnapshot>(
+ new rocksdb::ManagedSnapshot(db, snapshot));
+ if (!s) {
+ return nullptr;
+ }
+ ss_info->snapshot_id = ++explicit_snapshot_counter;
+ auto ret = std::make_shared<Rdb_explicit_snapshot>(*ss_info, std::move(s));
+ if (!ret) {
+ return nullptr;
+ }
+ explicit_snapshots[ss_info->snapshot_id] = ret;
+ return ret;
+ }
+
+ static std::string dump_snapshots() {
+ std::string str;
+ std::lock_guard<std::mutex> lock(explicit_snapshot_mutex);
+ for (const auto &elem : explicit_snapshots) {
+ const auto &ss = elem.second.lock();
+ DBUG_ASSERT(ss != nullptr);
+ const auto &info = ss->ss_info;
+ str += "\nSnapshot ID: " + std::to_string(info.snapshot_id) +
+ "\nBinlog File: " + info.binlog_file +
+ "\nBinlog Pos: " + std::to_string(info.binlog_pos) +
+ "\nGtid Executed: " + info.gtid_executed + "\n";
+ }
+
+ return str;
+ }
+
+ static std::shared_ptr<Rdb_explicit_snapshot> get(
+ const ulonglong snapshot_id) {
+ std::lock_guard<std::mutex> lock(explicit_snapshot_mutex);
+ auto elem = explicit_snapshots.find(snapshot_id);
+ if (elem == explicit_snapshots.end()) {
+ return nullptr;
+ }
+ return elem->second.lock();
+ }
+
+ rocksdb::ManagedSnapshot *get_snapshot() { return snapshot.get(); }
+
+ Rdb_explicit_snapshot(snapshot_info_st ss_info,
+ std::unique_ptr<rocksdb::ManagedSnapshot> &&snapshot)
+ : explicit_snapshot(ss_info), snapshot(std::move(snapshot)) {}
+
+ virtual ~Rdb_explicit_snapshot() {
+ std::lock_guard<std::mutex> lock(explicit_snapshot_mutex);
+ explicit_snapshots.erase(ss_info.snapshot_id);
+ }
+
+ private:
+ std::unique_ptr<rocksdb::ManagedSnapshot> snapshot;
+
+ static std::mutex explicit_snapshot_mutex;
+ static ulonglong explicit_snapshot_counter;
+ static std::unordered_map<ulonglong, std::weak_ptr<Rdb_explicit_snapshot>>
+ explicit_snapshots;
+};
+
+std::mutex Rdb_explicit_snapshot::explicit_snapshot_mutex;
+ulonglong Rdb_explicit_snapshot::explicit_snapshot_counter = 0;
+std::unordered_map<ulonglong, std::weak_ptr<Rdb_explicit_snapshot>>
+ Rdb_explicit_snapshot::explicit_snapshots;
+#endif
+
+/**
+ Updates row counters based on the table type and operation type.
+*/
+void ha_rocksdb::update_row_stats(const operation_type &type) {
+ DBUG_ASSERT(type < ROWS_MAX);
+ // Find if we are modifying system databases.
+ if (table->s && m_tbl_def->m_is_mysql_system_table) {
+ global_stats.system_rows[type].inc();
+ } else {
+ global_stats.rows[type].inc();
+ }
+}
+
+void dbug_dump_database(rocksdb::DB *db);
+static handler *rocksdb_create_handler(my_core::handlerton *hton,
+ my_core::TABLE_SHARE *table_arg,
+ my_core::MEM_ROOT *mem_root);
+
+static rocksdb::CompactRangeOptions getCompactRangeOptions(
+ int concurrency = 0) {
+ rocksdb::CompactRangeOptions compact_range_options;
+ compact_range_options.bottommost_level_compaction =
+ rocksdb::BottommostLevelCompaction::kForce;
+ compact_range_options.exclusive_manual_compaction = false;
+ if (concurrency > 0) {
+ compact_range_options.max_subcompactions = concurrency;
+ }
+ return compact_range_options;
+}
+
+///////////////////////////////////////////////////////////
+// Parameters and settings
+///////////////////////////////////////////////////////////
+static char *rocksdb_default_cf_options = nullptr;
+static char *rocksdb_override_cf_options = nullptr;
+static char *rocksdb_update_cf_options = nullptr;
+
+///////////////////////////////////////////////////////////
+// Globals
+///////////////////////////////////////////////////////////
+handlerton *rocksdb_hton;
+
+rocksdb::TransactionDB *rdb = nullptr;
+rocksdb::HistogramImpl *commit_latency_stats = nullptr;
+
+static std::shared_ptr<rocksdb::Statistics> rocksdb_stats;
+static std::unique_ptr<rocksdb::Env> flashcache_aware_env;
+static std::shared_ptr<Rdb_tbl_prop_coll_factory> properties_collector_factory;
+
+Rdb_dict_manager dict_manager;
+Rdb_cf_manager cf_manager;
+Rdb_ddl_manager ddl_manager;
+Rdb_binlog_manager binlog_manager;
+
+#if !defined(_WIN32) && !defined(__APPLE__)
+Rdb_io_watchdog *io_watchdog = nullptr;
+#endif
+/**
+ MyRocks background thread control
+ N.B. This is besides RocksDB's own background threads
+ (@see rocksdb::CancelAllBackgroundWork())
+*/
+
+static Rdb_background_thread rdb_bg_thread;
+
+static Rdb_manual_compaction_thread rdb_mc_thread;
+
+// List of table names (using regex) that are exceptions to the strict
+// collation check requirement.
+Regex_list_handler *rdb_collation_exceptions;
+
+static const char **rdb_get_error_messages(int nr);
+
+static void rocksdb_flush_all_memtables() {
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ rdb->Flush(rocksdb::FlushOptions(), cf_handle);
+ }
+}
+
+static void rocksdb_delete_column_family_stub(
+ THD *const /* thd */, struct st_mysql_sys_var *const /* var */,
+ void *const /* var_ptr */, const void *const /* save */) {}
+
+static int rocksdb_delete_column_family(
+ THD *const /* thd */, struct st_mysql_sys_var *const /* var */,
+ void *const /* var_ptr */, struct st_mysql_value *const value) {
+ // Return failure for now until the race condition between creating
+ // CF and deleting CF is resolved
+ return HA_EXIT_FAILURE;
+
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ int len = sizeof(buff);
+
+ DBUG_ASSERT(value != nullptr);
+
+ if (const char *const cf = value->val_str(value, buff, &len)) {
+ auto &cf_manager = rdb_get_cf_manager();
+ auto ret = cf_manager.drop_cf(cf);
+ if (ret == HA_EXIT_SUCCESS) {
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Dropped column family: %s\n", cf);
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Failed to drop column family: %s, error: %d\n",
+ cf, ret);
+ }
+
+ return ret;
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////
+// Hash map: table name => open table handler
+///////////////////////////////////////////////////////////
+
+namespace // anonymous namespace = not visible outside this source file
+{
+
+typedef Hash_set<Rdb_table_handler> Rdb_table_set;
+
+class Rdb_open_tables_map {
+ private:
+ /* Hash table used to track the handlers of open tables */
+ std::unordered_map<std::string, Rdb_table_handler *> m_table_map;
+
+ /* The mutex used to protect the hash table */
+ mutable mysql_mutex_t m_mutex;
+
+ public:
+ void init() {
+ m_table_map.clear();
+ mysql_mutex_init(rdb_psi_open_tbls_mutex_key, &m_mutex, MY_MUTEX_INIT_FAST);
+ }
+
+ void free() {
+ m_table_map.clear();
+ mysql_mutex_destroy(&m_mutex);
+ }
+ size_t count() { return m_table_map.size(); }
+
+ Rdb_table_handler *get_table_handler(const char *const table_name);
+ void release_table_handler(Rdb_table_handler *const table_handler);
+
+ std::vector<std::string> get_table_names(void) const;
+};
+
+} // anonymous namespace
+
+static Rdb_open_tables_map rdb_open_tables;
+
+static std::string rdb_normalize_dir(std::string dir) {
+ while (dir.size() > 0 && dir.back() == '/') {
+ dir.resize(dir.size() - 1);
+ }
+ return dir;
+}
+
+static int rocksdb_create_checkpoint(
+ THD *const thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const save MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_value *const value) {
+ char buf[FN_REFLEN];
+ int len = sizeof(buf);
+ const char *const checkpoint_dir_raw = value->val_str(value, buf, &len);
+ if (checkpoint_dir_raw) {
+ if (rdb != nullptr) {
+ std::string checkpoint_dir = rdb_normalize_dir(checkpoint_dir_raw);
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: creating checkpoint in directory : %s\n",
+ checkpoint_dir.c_str());
+ rocksdb::Checkpoint *checkpoint;
+ auto status = rocksdb::Checkpoint::Create(rdb, &checkpoint);
+ // We can only return HA_EXIT_FAILURE/HA_EXIT_SUCCESS here which is why
+ // the return code is ignored, but by calling into rdb_error_to_mysql,
+ // it will call my_error for us, which will propogate up to the client.
+ int rc __attribute__((__unused__));
+ if (status.ok()) {
+ status = checkpoint->CreateCheckpoint(checkpoint_dir.c_str());
+ delete checkpoint;
+ if (status.ok()) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: created checkpoint in directory : %s\n",
+ checkpoint_dir.c_str());
+ return HA_EXIT_SUCCESS;
+ } else {
+ rc = ha_rocksdb::rdb_error_to_mysql(status);
+ }
+ } else {
+ rc = ha_rocksdb::rdb_error_to_mysql(status);
+ }
+ }
+ }
+ return HA_EXIT_FAILURE;
+}
+
+/* This method is needed to indicate that the
+ ROCKSDB_CREATE_CHECKPOINT command is not read-only */
+static void rocksdb_create_checkpoint_stub(THD *const thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save) {}
+
+static void rocksdb_force_flush_memtable_now_stub(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ const void *const save) {}
+
+static int rocksdb_force_flush_memtable_now(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ struct st_mysql_value *const value) {
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Manual memtable flush.");
+ rocksdb_flush_all_memtables();
+ return HA_EXIT_SUCCESS;
+}
+
+static void rocksdb_force_flush_memtable_and_lzero_now_stub(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ const void *const save) {}
+
+static int rocksdb_force_flush_memtable_and_lzero_now(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ struct st_mysql_value *const value) {
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Manual memtable and L0 flush.");
+ rocksdb_flush_all_memtables();
+
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+ rocksdb::CompactionOptions c_options = rocksdb::CompactionOptions();
+ rocksdb::ColumnFamilyMetaData metadata;
+ rocksdb::ColumnFamilyDescriptor cf_descr;
+
+ int i, max_attempts = 3, num_errors = 0;
+
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ for (i = 0; i < max_attempts; i++) {
+ rdb->GetColumnFamilyMetaData(cf_handle, &metadata);
+ cf_handle->GetDescriptor(&cf_descr);
+ c_options.output_file_size_limit = cf_descr.options.target_file_size_base;
+
+ DBUG_ASSERT(metadata.levels[0].level == 0);
+ std::vector<std::string> file_names;
+ for (auto &file : metadata.levels[0].files) {
+ file_names.emplace_back(file.db_path + file.name);
+ }
+
+ if (file_names.empty()) {
+ break;
+ }
+
+ rocksdb::Status s;
+ s = rdb->CompactFiles(c_options, cf_handle, file_names, 1);
+
+ // Due to a race, it's possible for CompactFiles to collide
+ // with auto compaction, causing an error to return
+ // regarding file not found. In that case, retry.
+ if (s.IsInvalidArgument()) {
+ continue;
+ }
+
+ if (!s.ok() && !s.IsAborted()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_GENERAL);
+ return HA_EXIT_FAILURE;
+ }
+ break;
+ }
+ if (i == max_attempts) {
+ num_errors++;
+ }
+ }
+
+ return num_errors == 0 ? HA_EXIT_SUCCESS : HA_EXIT_FAILURE;
+}
+
+static void rocksdb_drop_index_wakeup_thread(
+ my_core::THD *const thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save);
+
+static my_bool rocksdb_pause_background_work = 0;
+static mysql_mutex_t rdb_sysvars_mutex;
+static mysql_mutex_t rdb_block_cache_resize_mutex;
+
+static void rocksdb_set_pause_background_work(
+ my_core::THD *const,
+ struct st_mysql_sys_var *const,
+ void *const, const void *const save) {
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+ const my_bool pause_requested = *static_cast<const my_bool *>(save);
+ if (rocksdb_pause_background_work != pause_requested) {
+ if (pause_requested) {
+ rdb->PauseBackgroundWork();
+ } else {
+ rdb->ContinueBackgroundWork();
+ }
+ rocksdb_pause_background_work = pause_requested;
+ }
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_compaction_options(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save);
+
+static void rocksdb_set_table_stats_sampling_pct(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr,
+ const void *save);
+
+static void rocksdb_set_rate_limiter_bytes_per_sec(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr,
+ const void *save);
+
+static void rocksdb_set_sst_mgr_rate_bytes_per_sec(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr,
+ const void *save);
+
+static void rocksdb_set_delayed_write_rate(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save);
+
+static void rocksdb_set_max_latest_deadlocks(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save);
+
+static void rdb_set_collation_exception_list(const char *exception_list);
+static void rocksdb_set_collation_exception_list(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr,
+ const void *save);
+
+static int rocksdb_validate_update_cf_options(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *save,
+ st_mysql_value *value);
+
+static void rocksdb_set_update_cf_options(THD *thd,
+ struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save);
+
+static int rocksdb_check_bulk_load(
+ THD *const thd, struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)),
+ void *save, struct st_mysql_value *value);
+
+static int rocksdb_check_bulk_load_allow_unsorted(
+ THD *const thd, struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)),
+ void *save, struct st_mysql_value *value);
+
+static void rocksdb_set_max_background_jobs(THD *thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save);
+static void rocksdb_set_bytes_per_sync(THD *thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save);
+static void rocksdb_set_wal_bytes_per_sync(THD *thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save);
+static int rocksdb_validate_set_block_cache_size(
+ THD *thd, struct st_mysql_sys_var *const var, void *var_ptr,
+ struct st_mysql_value *value);
+//////////////////////////////////////////////////////////////////////////////
+// Options definitions
+//////////////////////////////////////////////////////////////////////////////
+static long long rocksdb_block_cache_size;
+static long long rocksdb_sim_cache_size;
+static my_bool rocksdb_use_clock_cache;
+static double rocksdb_cache_high_pri_pool_ratio;
+static my_bool rocksdb_cache_dump;
+/* Use unsigned long long instead of uint64_t because of MySQL compatibility */
+static unsigned long long // NOLINT(runtime/int)
+ rocksdb_rate_limiter_bytes_per_sec;
+static unsigned long long // NOLINT(runtime/int)
+ rocksdb_sst_mgr_rate_bytes_per_sec;
+static unsigned long long rocksdb_delayed_write_rate;
+static uint32_t rocksdb_max_latest_deadlocks;
+static unsigned long // NOLINT(runtime/int)
+ rocksdb_persistent_cache_size_mb;
+static ulong rocksdb_info_log_level;
+static char *rocksdb_wal_dir;
+static char *rocksdb_persistent_cache_path;
+static ulong rocksdb_index_type;
+static uint32_t rocksdb_flush_log_at_trx_commit;
+static uint32_t rocksdb_debug_optimizer_n_rows;
+static my_bool rocksdb_force_compute_memtable_stats;
+static uint32_t rocksdb_force_compute_memtable_stats_cachetime;
+static my_bool rocksdb_debug_optimizer_no_zero_cardinality;
+static uint32_t rocksdb_wal_recovery_mode;
+static uint32_t rocksdb_stats_level;
+static uint32_t rocksdb_access_hint_on_compaction_start;
+static char *rocksdb_compact_cf_name;
+static char *rocksdb_delete_cf_name;
+static char *rocksdb_checkpoint_name;
+static my_bool rocksdb_signal_drop_index_thread;
+static my_bool rocksdb_signal_remove_mariabackup_checkpoint;
+static my_bool rocksdb_strict_collation_check = 1;
+static my_bool rocksdb_ignore_unknown_options = 1;
+static my_bool rocksdb_enable_2pc = 0;
+static char *rocksdb_strict_collation_exceptions;
+static my_bool rocksdb_collect_sst_properties = 1;
+static my_bool rocksdb_force_flush_memtable_now_var = 0;
+static my_bool rocksdb_force_flush_memtable_and_lzero_now_var = 0;
+static my_bool rocksdb_enable_ttl = 1;
+static my_bool rocksdb_enable_ttl_read_filtering = 1;
+static int rocksdb_debug_ttl_rec_ts = 0;
+static int rocksdb_debug_ttl_snapshot_ts = 0;
+static int rocksdb_debug_ttl_read_filter_ts = 0;
+static my_bool rocksdb_debug_ttl_ignore_pk = 0;
+static my_bool rocksdb_reset_stats = 0;
+static uint32_t rocksdb_io_write_timeout_secs = 0;
+static uint32_t rocksdb_seconds_between_stat_computes = 3600;
+static long long rocksdb_compaction_sequential_deletes = 0l;
+static long long rocksdb_compaction_sequential_deletes_window = 0l;
+static long long rocksdb_compaction_sequential_deletes_file_size = 0l;
+static uint32_t rocksdb_validate_tables = 1;
+static char *rocksdb_datadir;
+static uint32_t rocksdb_table_stats_sampling_pct;
+static my_bool rocksdb_enable_bulk_load_api = 1;
+static my_bool rocksdb_print_snapshot_conflict_queries = 0;
+static my_bool rocksdb_large_prefix = 0;
+static my_bool rocksdb_allow_to_start_after_corruption = 0;
+static char* rocksdb_git_hash;
+
+char *compression_types_val=
+ const_cast<char*>(get_rocksdb_supported_compression_types());
+static unsigned long rocksdb_write_policy =
+ rocksdb::TxnDBWritePolicy::WRITE_COMMITTED;
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+char *rocksdb_read_free_rpl_tables;
+std::mutex rocksdb_read_free_rpl_tables_mutex;
+#if defined(HAVE_PSI_INTERFACE)
+Regex_list_handler rdb_read_free_regex_handler(key_rwlock_read_free_rpl_tables);
+#else
+Regex_list_handler rdb_read_free_regex_handler;
+#endif
+enum read_free_rpl_type { OFF = 0, PK_ONLY, PK_SK };
+static unsigned long rocksdb_read_free_rpl = read_free_rpl_type::OFF;
+#endif
+
+static my_bool rocksdb_error_on_suboptimal_collation = 1;
+static uint32_t rocksdb_stats_recalc_rate = 0;
+static uint32_t rocksdb_debug_manual_compaction_delay = 0;
+static uint32_t rocksdb_max_manual_compactions = 0;
+static my_bool rocksdb_rollback_on_timeout = FALSE;
+static my_bool rocksdb_enable_insert_with_update_caching = TRUE;
+
+std::atomic<uint64_t> rocksdb_row_lock_deadlocks(0);
+std::atomic<uint64_t> rocksdb_row_lock_wait_timeouts(0);
+std::atomic<uint64_t> rocksdb_snapshot_conflict_errors(0);
+std::atomic<uint64_t> rocksdb_wal_group_syncs(0);
+std::atomic<uint64_t> rocksdb_manual_compactions_processed(0);
+std::atomic<uint64_t> rocksdb_manual_compactions_running(0);
+#ifndef DBUG_OFF
+std::atomic<uint64_t> rocksdb_num_get_for_update_calls(0);
+#endif
+
+
+
+/*
+ Remove directory with files in it.
+ Used to remove checkpoint created by mariabackup.
+*/
+#ifdef _WIN32
+#include <direct.h> /* unlink*/
+#ifndef F_OK
+#define F_OK 0
+#endif
+#endif
+
+static int rmdir_force(const char *dir) {
+ if (access(dir, F_OK))
+ return true;
+
+ char path[FN_REFLEN];
+ char sep[] = {FN_LIBCHAR, 0};
+ int err = 0;
+
+ MY_DIR *dir_info = my_dir(dir, MYF(MY_DONT_SORT | MY_WANT_STAT));
+ if (!dir_info)
+ return 1;
+
+ for (uint i = 0; i < dir_info->number_of_files; i++) {
+ FILEINFO *file = dir_info->dir_entry + i;
+
+ strxnmov(path, sizeof(path), dir, sep, file->name, NULL);
+
+ err = my_delete(path, 0);
+
+ if (err) {
+ break;
+ }
+ }
+
+ my_dirend(dir_info);
+
+ if (!err)
+ err = rmdir(dir);
+
+ return (err == 0) ? HA_EXIT_SUCCESS : HA_EXIT_FAILURE;
+}
+
+
+static void rocksdb_remove_mariabackup_checkpoint(
+ my_core::THD *const,
+ struct st_mysql_sys_var *const ,
+ void *const var_ptr, const void *const) {
+ std::string mariabackup_checkpoint_dir(rocksdb_datadir);
+
+ mariabackup_checkpoint_dir.append("/mariabackup-checkpoint");
+
+ if (unlink(mariabackup_checkpoint_dir.c_str()) == 0)
+ return;
+
+ rmdir_force(mariabackup_checkpoint_dir.c_str());
+}
+
+
+static std::unique_ptr<rocksdb::DBOptions> rdb_init_rocksdb_db_options(void) {
+ auto o = std::unique_ptr<rocksdb::DBOptions>(new rocksdb::DBOptions());
+
+ o->create_if_missing = true;
+ o->listeners.push_back(std::make_shared<Rdb_event_listener>(&ddl_manager));
+ o->info_log_level = rocksdb::InfoLogLevel::INFO_LEVEL;
+ o->max_subcompactions = DEFAULT_SUBCOMPACTIONS;
+ o->max_open_files = -2; // auto-tune to 50% open_files_limit
+
+ o->two_write_queues = true;
+ o->manual_wal_flush = true;
+ return o;
+}
+
+/* DBOptions contains Statistics and needs to be destructed last */
+static std::unique_ptr<rocksdb::BlockBasedTableOptions> rocksdb_tbl_options =
+ std::unique_ptr<rocksdb::BlockBasedTableOptions>(
+ new rocksdb::BlockBasedTableOptions());
+static std::unique_ptr<rocksdb::DBOptions> rocksdb_db_options =
+ rdb_init_rocksdb_db_options();
+
+static std::shared_ptr<rocksdb::RateLimiter> rocksdb_rate_limiter;
+
+/* This enum needs to be kept up to date with rocksdb::TxnDBWritePolicy */
+static const char *write_policy_names[] = {"write_committed", "write_prepared",
+ "write_unprepared", NullS};
+
+static TYPELIB write_policy_typelib = {array_elements(write_policy_names) - 1,
+ "write_policy_typelib",
+ write_policy_names, nullptr};
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+/* This array needs to be kept up to date with myrocks::read_free_rpl_type */
+static const char *read_free_rpl_names[] = {"OFF", "PK_ONLY", "PK_SK", NullS};
+
+static TYPELIB read_free_rpl_typelib = {array_elements(read_free_rpl_names) - 1,
+ "read_free_rpl_typelib",
+ read_free_rpl_names, nullptr};
+#endif
+
+/* This enum needs to be kept up to date with rocksdb::InfoLogLevel */
+static const char *info_log_level_names[] = {"debug_level", "info_level",
+ "warn_level", "error_level",
+ "fatal_level", NullS};
+
+static TYPELIB info_log_level_typelib = {
+ array_elements(info_log_level_names) - 1, "info_log_level_typelib",
+ info_log_level_names, nullptr};
+
+static void rocksdb_set_rocksdb_info_log_level(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+ rocksdb_info_log_level = *static_cast<const uint64_t *>(save);
+ rocksdb_db_options->info_log->SetInfoLogLevel(
+ static_cast<rocksdb::InfoLogLevel>(rocksdb_info_log_level));
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_rocksdb_stats_level(THD *const thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+ rocksdb_db_options->statistics->set_stats_level(
+ static_cast<rocksdb::StatsLevel>(
+ *static_cast<const uint64_t *>(save)));
+ // Actual stats level is defined at rocksdb dbopt::statistics::stats_level_
+ // so adjusting rocksdb_stats_level here to make sure it points to
+ // the correct stats level.
+ rocksdb_stats_level = rocksdb_db_options->statistics->get_stats_level();
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_reset_stats(
+ my_core::THD *const /* unused */,
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr, const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+ DBUG_ASSERT(rdb != nullptr);
+ DBUG_ASSERT(rocksdb_stats != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ *static_cast<bool *>(var_ptr) = *static_cast<const bool *>(save);
+
+ if (rocksdb_reset_stats) {
+ rocksdb::Status s = rdb->ResetStats();
+
+ // RocksDB will always return success. Let's document this assumption here
+ // as well so that we'll get immediately notified when contract changes.
+ DBUG_ASSERT(s == rocksdb::Status::OK());
+
+ s = rocksdb_stats->Reset();
+ DBUG_ASSERT(s == rocksdb::Status::OK());
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_io_write_timeout(
+ my_core::THD *const thd MY_ATTRIBUTE((__unused__)),
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+ DBUG_ASSERT(rdb != nullptr);
+#if !defined(_WIN32) && !defined(__APPLE__)
+ DBUG_ASSERT(io_watchdog != nullptr);
+#endif
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const uint32_t new_val = *static_cast<const uint32_t *>(save);
+
+ rocksdb_io_write_timeout_secs = new_val;
+#if !defined(_WIN32) && !defined(__APPLE__)
+ io_watchdog->reset_timeout(rocksdb_io_write_timeout_secs);
+#endif
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+enum rocksdb_flush_log_at_trx_commit_type : unsigned int {
+ FLUSH_LOG_NEVER = 0,
+ FLUSH_LOG_SYNC,
+ FLUSH_LOG_BACKGROUND,
+ FLUSH_LOG_MAX /* must be last */
+};
+
+static int rocksdb_validate_flush_log_at_trx_commit(
+ THD *const thd,
+ struct st_mysql_sys_var *const var, /* in: pointer to system variable */
+ void *var_ptr, /* out: immediate result for update function */
+ struct st_mysql_value *const value /* in: incoming value */) {
+ long long new_value;
+
+ /* value is NULL */
+ if (value->val_int(value, &new_value)) {
+ return HA_EXIT_FAILURE;
+ }
+
+ if (rocksdb_db_options->allow_mmap_writes && new_value != FLUSH_LOG_NEVER) {
+ return HA_EXIT_FAILURE;
+ }
+
+ *static_cast<uint32_t *>(var_ptr) = static_cast<uint32_t>(new_value);
+ return HA_EXIT_SUCCESS;
+}
+static void rocksdb_compact_column_family_stub(
+ THD *const thd, struct st_mysql_sys_var *const var, void *const var_ptr,
+ const void *const save) {}
+
+static int rocksdb_compact_column_family(THD *const thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ struct st_mysql_value *const value);
+
+static const char *index_type_names[] = {"kBinarySearch", "kHashSearch", NullS};
+
+static TYPELIB index_type_typelib = {array_elements(index_type_names) - 1,
+ "index_type_typelib", index_type_names,
+ nullptr};
+
+const ulong RDB_MAX_LOCK_WAIT_SECONDS = 1024 * 1024 * 1024;
+const ulong RDB_DEFAULT_MAX_ROW_LOCKS = 1024 * 1024;
+const ulong RDB_MAX_ROW_LOCKS = 1024 * 1024 * 1024;
+const ulong RDB_DEFAULT_BULK_LOAD_SIZE = 1000;
+const ulong RDB_MAX_BULK_LOAD_SIZE = 1024 * 1024 * 1024;
+const size_t RDB_DEFAULT_MERGE_BUF_SIZE = 64 * 1024 * 1024;
+const size_t RDB_MIN_MERGE_BUF_SIZE = 100;
+const size_t RDB_DEFAULT_MERGE_COMBINE_READ_SIZE = 1024 * 1024 * 1024;
+const size_t RDB_MIN_MERGE_COMBINE_READ_SIZE = 100;
+const size_t RDB_DEFAULT_MERGE_TMP_FILE_REMOVAL_DELAY = 0;
+const size_t RDB_MIN_MERGE_TMP_FILE_REMOVAL_DELAY = 0;
+const int64 RDB_DEFAULT_BLOCK_CACHE_SIZE = 512 * 1024 * 1024;
+const int64 RDB_MIN_BLOCK_CACHE_SIZE = 1024;
+const int RDB_MAX_CHECKSUMS_PCT = 100;
+const ulong RDB_DEADLOCK_DETECT_DEPTH = 50;
+
+// TODO: 0 means don't wait at all, and we don't support it yet?
+static MYSQL_THDVAR_ULONG(lock_wait_timeout, PLUGIN_VAR_RQCMDARG,
+ "Number of seconds to wait for lock", nullptr,
+ nullptr, /*default*/ 1, /*min*/ 1,
+ /*max*/ RDB_MAX_LOCK_WAIT_SECONDS, 0);
+
+static MYSQL_THDVAR_BOOL(deadlock_detect, PLUGIN_VAR_RQCMDARG,
+ "Enables deadlock detection", nullptr, nullptr, FALSE);
+
+static MYSQL_THDVAR_ULONG(deadlock_detect_depth, PLUGIN_VAR_RQCMDARG,
+ "Number of transactions deadlock detection will "
+ "traverse through before assuming deadlock",
+ nullptr, nullptr,
+ /*default*/ RDB_DEADLOCK_DETECT_DEPTH,
+ /*min*/ 2,
+ /*max*/ ULONG_MAX, 0);
+
+static MYSQL_THDVAR_BOOL(
+ commit_time_batch_for_recovery, PLUGIN_VAR_RQCMDARG,
+ "TransactionOptions::commit_time_batch_for_recovery for RocksDB", nullptr,
+ nullptr, TRUE);
+
+static MYSQL_THDVAR_BOOL(
+ trace_sst_api, PLUGIN_VAR_RQCMDARG,
+ "Generate trace output in the log for each call to the SstFileWriter",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_THDVAR_BOOL(
+ bulk_load, PLUGIN_VAR_RQCMDARG,
+ "Use bulk-load mode for inserts. This disables "
+ "unique_checks and enables rocksdb_commit_in_the_middle.",
+ rocksdb_check_bulk_load, nullptr, FALSE);
+
+static MYSQL_THDVAR_BOOL(bulk_load_allow_sk, PLUGIN_VAR_RQCMDARG,
+ "Allow bulk loading of sk keys during bulk-load. "
+ "Can be changed only when bulk load is disabled.",
+ /* Intentionally reuse unsorted's check function */
+ rocksdb_check_bulk_load_allow_unsorted, nullptr,
+ FALSE);
+
+static MYSQL_THDVAR_BOOL(bulk_load_allow_unsorted, PLUGIN_VAR_RQCMDARG,
+ "Allow unsorted input during bulk-load. "
+ "Can be changed only when bulk load is disabled.",
+ rocksdb_check_bulk_load_allow_unsorted, nullptr,
+ FALSE);
+
+static MYSQL_SYSVAR_BOOL(enable_bulk_load_api, rocksdb_enable_bulk_load_api,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Enables using SstFileWriter for bulk loading",
+ nullptr, nullptr, rocksdb_enable_bulk_load_api);
+
+static MYSQL_SYSVAR_STR(git_hash, rocksdb_git_hash,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Git revision of the RocksDB library used by MyRocks",
+ nullptr, nullptr, ROCKSDB_GIT_HASH);
+
+static MYSQL_THDVAR_STR(tmpdir, PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC,
+ "Directory for temporary files during DDL operations.",
+ nullptr, nullptr, "");
+
+#define DEFAULT_SKIP_UNIQUE_CHECK_TABLES ".*"
+static MYSQL_THDVAR_STR(
+ skip_unique_check_tables, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
+ "Skip unique constraint checking for the specified tables", nullptr,
+ nullptr, DEFAULT_SKIP_UNIQUE_CHECK_TABLES);
+
+static MYSQL_THDVAR_BOOL(
+ commit_in_the_middle, PLUGIN_VAR_RQCMDARG,
+ "Commit rows implicitly every rocksdb_bulk_load_size, on bulk load/insert, "
+ "update and delete",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_THDVAR_BOOL(
+ blind_delete_primary_key, PLUGIN_VAR_RQCMDARG,
+ "Deleting rows by primary key lookup, without reading rows (Blind Deletes)."
+ " Blind delete is disabled if the table has secondary key",
+ nullptr, nullptr, FALSE);
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+
+static const char *DEFAULT_READ_FREE_RPL_TABLES = ".*";
+
+static int rocksdb_validate_read_free_rpl_tables(
+ THD *thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)), void *save,
+ struct st_mysql_value *value) {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ int length = sizeof(buff);
+ const char *wlist_buf = value->val_str(value, buff, &length);
+ const auto wlist = wlist_buf ? wlist_buf : DEFAULT_READ_FREE_RPL_TABLES;
+
+#if defined(HAVE_PSI_INTERFACE)
+ Regex_list_handler regex_handler(key_rwlock_read_free_rpl_tables);
+#else
+ Regex_list_handler regex_handler;
+#endif
+
+ if (!regex_handler.set_patterns(wlist)) {
+ warn_about_bad_patterns(&regex_handler, "rocksdb_read_free_rpl_tables");
+ return HA_EXIT_FAILURE;
+ }
+
+ *static_cast<const char **>(save) = my_strdup(wlist, MYF(MY_WME));
+ return HA_EXIT_SUCCESS;
+}
+
+static void rocksdb_update_read_free_rpl_tables(
+ THD *thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)), void *var_ptr,
+ const void *save) {
+ const auto wlist = *static_cast<const char *const *>(save);
+ DBUG_ASSERT(wlist != nullptr);
+
+ // This is bound to succeed since we've already checked for bad patterns in
+ // rocksdb_validate_read_free_rpl_tables
+ rdb_read_free_regex_handler.set_patterns(wlist);
+
+ // update all table defs
+ struct Rdb_read_free_rpl_updater : public Rdb_tables_scanner {
+ int add_table(Rdb_tbl_def *tdef) override {
+ tdef->check_and_set_read_free_rpl_table();
+ return HA_EXIT_SUCCESS;
+ }
+ } updater;
+ ddl_manager.scan_for_tables(&updater);
+
+ if (wlist == DEFAULT_READ_FREE_RPL_TABLES) {
+ // If running SET var = DEFAULT, then rocksdb_validate_read_free_rpl_tables
+ // isn't called, and memory is never allocated for the value. Allocate it
+ // here.
+ *static_cast<const char **>(var_ptr) = my_strdup(wlist, MYF(MY_WME));
+ } else {
+ // Otherwise, we just reuse the value allocated from
+ // rocksdb_validate_read_free_rpl_tables.
+ *static_cast<const char **>(var_ptr) = wlist;
+ }
+}
+
+static MYSQL_SYSVAR_STR(
+ read_free_rpl_tables, rocksdb_read_free_rpl_tables,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC /*| PLUGIN_VAR_ALLOCATED*/,
+ "List of tables that will use read-free replication on the slave "
+ "(i.e. not lookup a row during replication)",
+ rocksdb_validate_read_free_rpl_tables, rocksdb_update_read_free_rpl_tables,
+ DEFAULT_READ_FREE_RPL_TABLES);
+
+static MYSQL_SYSVAR_ENUM(
+ read_free_rpl, rocksdb_read_free_rpl,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
+ "Use read-free replication on the slave (i.e. no row lookup during "
+ "replication). Default is OFF, PK_SK will enable it on all tables with "
+ "primary key. PK_ONLY will enable it on tables where the only key is the "
+ "primary key (i.e. no secondary keys).",
+ nullptr, nullptr, read_free_rpl_type::OFF, &read_free_rpl_typelib);
+#endif
+
+static MYSQL_THDVAR_BOOL(skip_bloom_filter_on_read, PLUGIN_VAR_RQCMDARG,
+ "Skip using bloom filter for reads", nullptr, nullptr,
+ FALSE);
+
+static MYSQL_THDVAR_ULONG(max_row_locks, PLUGIN_VAR_RQCMDARG,
+ "Maximum number of locks a transaction can have",
+ nullptr, nullptr,
+ /*default*/ RDB_DEFAULT_MAX_ROW_LOCKS,
+ /*min*/ 1,
+ /*max*/ RDB_MAX_ROW_LOCKS, 0);
+
+static MYSQL_THDVAR_ULONGLONG(
+ write_batch_max_bytes, PLUGIN_VAR_RQCMDARG,
+ "Maximum size of write batch in bytes. 0 means no limit.", nullptr, nullptr,
+ /* default */ 0, /* min */ 0, /* max */ SIZE_T_MAX, 1);
+
+static MYSQL_THDVAR_BOOL(
+ lock_scanned_rows, PLUGIN_VAR_RQCMDARG,
+ "Take and hold locks on rows that are scanned but not updated", nullptr,
+ nullptr, FALSE);
+
+static MYSQL_THDVAR_ULONG(bulk_load_size, PLUGIN_VAR_RQCMDARG,
+ "Max #records in a batch for bulk-load mode", nullptr,
+ nullptr,
+ /*default*/ RDB_DEFAULT_BULK_LOAD_SIZE,
+ /*min*/ 1,
+ /*max*/ RDB_MAX_BULK_LOAD_SIZE, 0);
+
+static MYSQL_THDVAR_ULONGLONG(
+ merge_buf_size, PLUGIN_VAR_RQCMDARG,
+ "Size to allocate for merge sort buffers written out to disk "
+ "during inplace index creation.",
+ nullptr, nullptr,
+ /* default (64MB) */ RDB_DEFAULT_MERGE_BUF_SIZE,
+ /* min (100B) */ RDB_MIN_MERGE_BUF_SIZE,
+ /* max */ SIZE_T_MAX, 1);
+
+static MYSQL_THDVAR_ULONGLONG(
+ merge_combine_read_size, PLUGIN_VAR_RQCMDARG,
+ "Size that we have to work with during combine (reading from disk) phase "
+ "of "
+ "external sort during fast index creation.",
+ nullptr, nullptr,
+ /* default (1GB) */ RDB_DEFAULT_MERGE_COMBINE_READ_SIZE,
+ /* min (100B) */ RDB_MIN_MERGE_COMBINE_READ_SIZE,
+ /* max */ SIZE_T_MAX, 1);
+
+static MYSQL_THDVAR_ULONGLONG(
+ merge_tmp_file_removal_delay_ms, PLUGIN_VAR_RQCMDARG,
+ "Fast index creation creates a large tmp file on disk during index "
+ "creation. Removing this large file all at once when index creation is "
+ "complete can cause trim stalls on Flash. This variable specifies a "
+ "duration to sleep (in milliseconds) between calling chsize() to truncate "
+ "the file in chunks. The chunk size is the same as merge_buf_size.",
+ nullptr, nullptr,
+ /* default (0ms) */ RDB_DEFAULT_MERGE_TMP_FILE_REMOVAL_DELAY,
+ /* min (0ms) */ RDB_MIN_MERGE_TMP_FILE_REMOVAL_DELAY,
+ /* max */ SIZE_T_MAX, 1);
+
+static MYSQL_THDVAR_INT(
+ manual_compaction_threads, PLUGIN_VAR_RQCMDARG,
+ "How many rocksdb threads to run for manual compactions", nullptr, nullptr,
+ /* default rocksdb.dboption max_subcompactions */ 0,
+ /* min */ 0, /* max */ 128, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ create_if_missing,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->create_if_missing),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::create_if_missing for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->create_if_missing);
+
+static MYSQL_SYSVAR_BOOL(
+ two_write_queues,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->two_write_queues),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::two_write_queues for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->two_write_queues);
+
+static MYSQL_SYSVAR_BOOL(
+ manual_wal_flush,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->manual_wal_flush),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::manual_wal_flush for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->manual_wal_flush);
+
+static MYSQL_SYSVAR_ENUM(write_policy, rocksdb_write_policy,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::write_policy for RocksDB", nullptr,
+ nullptr, rocksdb::TxnDBWritePolicy::WRITE_COMMITTED,
+ &write_policy_typelib);
+
+static MYSQL_SYSVAR_BOOL(
+ create_missing_column_families,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_db_options->create_missing_column_families),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::create_missing_column_families for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->create_missing_column_families);
+
+static MYSQL_SYSVAR_BOOL(
+ error_if_exists,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->error_if_exists),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::error_if_exists for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->error_if_exists);
+
+static MYSQL_SYSVAR_BOOL(
+ paranoid_checks,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->paranoid_checks),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::paranoid_checks for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->paranoid_checks);
+
+static MYSQL_SYSVAR_ULONGLONG(
+ rate_limiter_bytes_per_sec, rocksdb_rate_limiter_bytes_per_sec,
+ PLUGIN_VAR_RQCMDARG, "DBOptions::rate_limiter bytes_per_sec for RocksDB",
+ nullptr, rocksdb_set_rate_limiter_bytes_per_sec, /* default */ 0L,
+ /* min */ 0L, /* max */ MAX_RATE_LIMITER_BYTES_PER_SEC, 0);
+
+static MYSQL_SYSVAR_ULONGLONG(
+ sst_mgr_rate_bytes_per_sec, rocksdb_sst_mgr_rate_bytes_per_sec,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::sst_file_manager rate_bytes_per_sec for RocksDB", nullptr,
+ rocksdb_set_sst_mgr_rate_bytes_per_sec,
+ /* default */ DEFAULT_SST_MGR_RATE_BYTES_PER_SEC,
+ /* min */ 0L, /* max */ UINT64_MAX, 0);
+
+static MYSQL_SYSVAR_ULONGLONG(delayed_write_rate, rocksdb_delayed_write_rate,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::delayed_write_rate", nullptr,
+ rocksdb_set_delayed_write_rate,
+ rocksdb_db_options->delayed_write_rate, 0,
+ UINT64_MAX, 0);
+
+static MYSQL_SYSVAR_UINT(max_latest_deadlocks, rocksdb_max_latest_deadlocks,
+ PLUGIN_VAR_RQCMDARG,
+ "Maximum number of recent "
+ "deadlocks to store",
+ nullptr, rocksdb_set_max_latest_deadlocks,
+ rocksdb::kInitialMaxDeadlocks, 0, UINT32_MAX, 0);
+
+static MYSQL_SYSVAR_ENUM(
+ info_log_level, rocksdb_info_log_level, PLUGIN_VAR_RQCMDARG,
+ "Filter level for info logs to be written mysqld error log. "
+ "Valid values include 'debug_level', 'info_level', 'warn_level'"
+ "'error_level' and 'fatal_level'.",
+ nullptr, rocksdb_set_rocksdb_info_log_level,
+ rocksdb::InfoLogLevel::ERROR_LEVEL, &info_log_level_typelib);
+
+static MYSQL_THDVAR_INT(
+ perf_context_level, PLUGIN_VAR_RQCMDARG,
+ "Perf Context Level for rocksdb internal timer stat collection", nullptr,
+ nullptr,
+ /* default */ rocksdb::PerfLevel::kUninitialized,
+ /* min */ rocksdb::PerfLevel::kUninitialized,
+ /* max */ rocksdb::PerfLevel::kOutOfBounds - 1, 0);
+
+static MYSQL_SYSVAR_UINT(
+ wal_recovery_mode, rocksdb_wal_recovery_mode, PLUGIN_VAR_RQCMDARG,
+ "DBOptions::wal_recovery_mode for RocksDB. Default is kAbsoluteConsistency",
+ nullptr, nullptr,
+ /* default */ (uint)rocksdb::WALRecoveryMode::kAbsoluteConsistency,
+ /* min */ (uint)rocksdb::WALRecoveryMode::kTolerateCorruptedTailRecords,
+ /* max */ (uint)rocksdb::WALRecoveryMode::kSkipAnyCorruptedRecords, 0);
+
+static MYSQL_SYSVAR_UINT(
+ stats_level, rocksdb_stats_level, PLUGIN_VAR_RQCMDARG,
+ "Statistics Level for RocksDB. Default is 0 (kExceptHistogramOrTimers)",
+ nullptr, rocksdb_set_rocksdb_stats_level,
+ /* default */ (uint)rocksdb::StatsLevel::kExceptHistogramOrTimers,
+ /* min */ (uint)rocksdb::StatsLevel::kExceptHistogramOrTimers,
+ /* max */ (uint)rocksdb::StatsLevel::kAll, 0);
+
+static MYSQL_SYSVAR_SIZE_T(compaction_readahead_size,
+ rocksdb_db_options->compaction_readahead_size,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::compaction_readahead_size for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->compaction_readahead_size,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ new_table_reader_for_compaction_inputs,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_db_options->new_table_reader_for_compaction_inputs),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::new_table_reader_for_compaction_inputs for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->new_table_reader_for_compaction_inputs);
+
+static MYSQL_SYSVAR_UINT(
+ access_hint_on_compaction_start, rocksdb_access_hint_on_compaction_start,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::access_hint_on_compaction_start for RocksDB", nullptr, nullptr,
+ /* default */ (uint)rocksdb::Options::AccessHint::NORMAL,
+ /* min */ (uint)rocksdb::Options::AccessHint::NONE,
+ /* max */ (uint)rocksdb::Options::AccessHint::WILLNEED, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ allow_concurrent_memtable_write,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_db_options->allow_concurrent_memtable_write),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::allow_concurrent_memtable_write for RocksDB", nullptr, nullptr,
+ false);
+
+static MYSQL_SYSVAR_BOOL(
+ enable_write_thread_adaptive_yield,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_db_options->enable_write_thread_adaptive_yield),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::enable_write_thread_adaptive_yield for RocksDB", nullptr,
+ nullptr, false);
+
+static MYSQL_SYSVAR_INT(max_open_files, rocksdb_db_options->max_open_files,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::max_open_files for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->max_open_files,
+ /* min */ -2, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_UINT64_T(max_total_wal_size,
+ rocksdb_db_options->max_total_wal_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::max_total_wal_size for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->max_total_wal_size,
+ /* min */ 0, /* max */ LONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ use_fsync, *reinterpret_cast<my_bool *>(&rocksdb_db_options->use_fsync),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::use_fsync for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->use_fsync);
+
+static MYSQL_SYSVAR_STR(wal_dir, rocksdb_wal_dir,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::wal_dir for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->wal_dir.c_str());
+
+static MYSQL_SYSVAR_STR(
+ persistent_cache_path, rocksdb_persistent_cache_path,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Path for BlockBasedTableOptions::persistent_cache for RocksDB", nullptr,
+ nullptr, "");
+
+static MYSQL_SYSVAR_ULONG(
+ persistent_cache_size_mb, rocksdb_persistent_cache_size_mb,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Size of cache in MB for BlockBasedTableOptions::persistent_cache "
+ "for RocksDB",
+ nullptr, nullptr, rocksdb_persistent_cache_size_mb,
+ /* min */ 0L, /* max */ ULONG_MAX, 0);
+
+static MYSQL_SYSVAR_UINT64_T(
+ delete_obsolete_files_period_micros,
+ rocksdb_db_options->delete_obsolete_files_period_micros,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::delete_obsolete_files_period_micros for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->delete_obsolete_files_period_micros,
+ /* min */ 0, /* max */ LONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_INT(max_background_jobs,
+ rocksdb_db_options->max_background_jobs,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::max_background_jobs for RocksDB", nullptr,
+ rocksdb_set_max_background_jobs,
+ rocksdb_db_options->max_background_jobs,
+ /* min */ -1, /* max */ MAX_BACKGROUND_JOBS, 0);
+
+static MYSQL_SYSVAR_UINT(max_subcompactions,
+ rocksdb_db_options->max_subcompactions,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::max_subcompactions for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->max_subcompactions,
+ /* min */ 1, /* max */ MAX_SUBCOMPACTIONS, 0);
+
+static MYSQL_SYSVAR_SIZE_T(max_log_file_size,
+ rocksdb_db_options->max_log_file_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::max_log_file_size for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->max_log_file_size,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_SIZE_T(log_file_time_to_roll,
+ rocksdb_db_options->log_file_time_to_roll,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::log_file_time_to_roll for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->log_file_time_to_roll,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_SIZE_T(keep_log_file_num,
+ rocksdb_db_options->keep_log_file_num,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::keep_log_file_num for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->keep_log_file_num,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_UINT64_T(max_manifest_file_size,
+ rocksdb_db_options->max_manifest_file_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::max_manifest_file_size for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->max_manifest_file_size,
+ /* min */ 0L, /* max */ ULONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_INT(table_cache_numshardbits,
+ rocksdb_db_options->table_cache_numshardbits,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::table_cache_numshardbits for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->table_cache_numshardbits,
+ // LRUCache limits this to 19 bits, anything greater
+ // fails to create a cache and returns a nullptr
+ /* min */ 0, /* max */ 19, 0);
+
+static MYSQL_SYSVAR_UINT64_T(wal_ttl_seconds, rocksdb_db_options->WAL_ttl_seconds,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::WAL_ttl_seconds for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->WAL_ttl_seconds,
+ /* min */ 0L, /* max */ LONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_UINT64_T(wal_size_limit_mb,
+ rocksdb_db_options->WAL_size_limit_MB,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::WAL_size_limit_MB for RocksDB", nullptr,
+ nullptr, rocksdb_db_options->WAL_size_limit_MB,
+ /* min */ 0L, /* max */ LONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_SIZE_T(manifest_preallocation_size,
+ rocksdb_db_options->manifest_preallocation_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::manifest_preallocation_size for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->manifest_preallocation_size,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ use_direct_reads,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->use_direct_reads),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::use_direct_reads for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->use_direct_reads);
+
+static MYSQL_SYSVAR_BOOL(
+ use_direct_io_for_flush_and_compaction,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->use_direct_io_for_flush_and_compaction),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::use_direct_io_for_flush_and_compaction for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->use_direct_io_for_flush_and_compaction);
+
+static MYSQL_SYSVAR_BOOL(
+ allow_mmap_reads,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->allow_mmap_reads),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::allow_mmap_reads for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->allow_mmap_reads);
+
+static MYSQL_SYSVAR_BOOL(
+ allow_mmap_writes,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->allow_mmap_writes),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::allow_mmap_writes for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->allow_mmap_writes);
+
+static MYSQL_SYSVAR_BOOL(
+ is_fd_close_on_exec,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->is_fd_close_on_exec),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::is_fd_close_on_exec for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->is_fd_close_on_exec);
+
+static MYSQL_SYSVAR_UINT(stats_dump_period_sec,
+ rocksdb_db_options->stats_dump_period_sec,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::stats_dump_period_sec for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->stats_dump_period_sec,
+ /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ advise_random_on_open,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->advise_random_on_open),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::advise_random_on_open for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->advise_random_on_open);
+
+static MYSQL_SYSVAR_SIZE_T(db_write_buffer_size,
+ rocksdb_db_options->db_write_buffer_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::db_write_buffer_size for RocksDB",
+ nullptr, nullptr,
+ rocksdb_db_options->db_write_buffer_size,
+ /* min */ 0L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ use_adaptive_mutex,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->use_adaptive_mutex),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::use_adaptive_mutex for RocksDB", nullptr, nullptr,
+ rocksdb_db_options->use_adaptive_mutex);
+
+static MYSQL_SYSVAR_UINT64_T(bytes_per_sync, rocksdb_db_options->bytes_per_sync,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::bytes_per_sync for RocksDB", nullptr,
+ rocksdb_set_bytes_per_sync,
+ rocksdb_db_options->bytes_per_sync,
+ /* min */ 0L, /* max */ ULONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_UINT64_T(wal_bytes_per_sync,
+ rocksdb_db_options->wal_bytes_per_sync,
+ PLUGIN_VAR_RQCMDARG,
+ "DBOptions::wal_bytes_per_sync for RocksDB", nullptr,
+ rocksdb_set_wal_bytes_per_sync,
+ rocksdb_db_options->wal_bytes_per_sync,
+ /* min */ 0L, /* max */ ULONGLONG_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ enable_thread_tracking,
+ *reinterpret_cast<my_bool *>(&rocksdb_db_options->enable_thread_tracking),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "DBOptions::enable_thread_tracking for RocksDB", nullptr, nullptr, true);
+
+static MYSQL_SYSVAR_LONGLONG(block_cache_size, rocksdb_block_cache_size,
+ PLUGIN_VAR_RQCMDARG,
+ "block_cache size for RocksDB",
+ rocksdb_validate_set_block_cache_size, nullptr,
+ /* default */ RDB_DEFAULT_BLOCK_CACHE_SIZE,
+ /* min */ RDB_MIN_BLOCK_CACHE_SIZE,
+ /* max */ LLONG_MAX,
+ /* Block size */ RDB_MIN_BLOCK_CACHE_SIZE);
+
+static MYSQL_SYSVAR_LONGLONG(sim_cache_size, rocksdb_sim_cache_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Simulated cache size for RocksDB", nullptr,
+ nullptr,
+ /* default */ 0,
+ /* min */ 0,
+ /* max */ LLONG_MAX,
+ /* Block size */ 0);
+
+static MYSQL_SYSVAR_BOOL(
+ use_clock_cache, rocksdb_use_clock_cache,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Use ClockCache instead of default LRUCache for RocksDB", nullptr, nullptr,
+ false);
+
+static MYSQL_SYSVAR_BOOL(cache_dump, rocksdb_cache_dump,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Include RocksDB block cache content in core dump.",
+ nullptr, nullptr, true);
+
+static MYSQL_SYSVAR_DOUBLE(cache_high_pri_pool_ratio,
+ rocksdb_cache_high_pri_pool_ratio,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Specify the size of block cache high-pri pool",
+ nullptr, nullptr, /* default */ 0.0, /* min */ 0.0,
+ /* max */ 1.0, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ cache_index_and_filter_blocks,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_tbl_options->cache_index_and_filter_blocks),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::cache_index_and_filter_blocks for RocksDB",
+ nullptr, nullptr, true);
+
+static MYSQL_SYSVAR_BOOL(
+ cache_index_and_filter_with_high_priority,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_tbl_options->cache_index_and_filter_blocks_with_high_priority),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "cache_index_and_filter_blocks_with_high_priority for RocksDB", nullptr,
+ nullptr, true);
+
+// When pin_l0_filter_and_index_blocks_in_cache is true, RocksDB will use the
+// LRU cache, but will always keep the filter & idndex block's handle checked
+// out (=won't call ShardedLRUCache::Release), plus the parsed out objects
+// the LRU cache will never push flush them out, hence they're pinned.
+//
+// This fixes the mutex contention between :ShardedLRUCache::Lookup and
+// ShardedLRUCache::Release which reduced the QPS ratio (QPS using secondary
+// index / QPS using PK).
+static MYSQL_SYSVAR_BOOL(
+ pin_l0_filter_and_index_blocks_in_cache,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_tbl_options->pin_l0_filter_and_index_blocks_in_cache),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "pin_l0_filter_and_index_blocks_in_cache for RocksDB", nullptr, nullptr,
+ true);
+
+static MYSQL_SYSVAR_ENUM(index_type, rocksdb_index_type,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::index_type for RocksDB",
+ nullptr, nullptr,
+ (ulong)rocksdb_tbl_options->index_type,
+ &index_type_typelib);
+
+static MYSQL_SYSVAR_BOOL(
+ hash_index_allow_collision,
+ *reinterpret_cast<my_bool *>(
+ &rocksdb_tbl_options->hash_index_allow_collision),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::hash_index_allow_collision for RocksDB", nullptr,
+ nullptr, rocksdb_tbl_options->hash_index_allow_collision);
+
+static MYSQL_SYSVAR_BOOL(
+ no_block_cache,
+ *reinterpret_cast<my_bool *>(&rocksdb_tbl_options->no_block_cache),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::no_block_cache for RocksDB", nullptr, nullptr,
+ rocksdb_tbl_options->no_block_cache);
+
+static MYSQL_SYSVAR_SIZE_T(block_size, rocksdb_tbl_options->block_size,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::block_size for RocksDB",
+ nullptr, nullptr, rocksdb_tbl_options->block_size,
+ /* min */ 1L, /* max */ SIZE_T_MAX, 0);
+
+static MYSQL_SYSVAR_INT(
+ block_size_deviation, rocksdb_tbl_options->block_size_deviation,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::block_size_deviation for RocksDB", nullptr,
+ nullptr, rocksdb_tbl_options->block_size_deviation,
+ /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_INT(
+ block_restart_interval, rocksdb_tbl_options->block_restart_interval,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::block_restart_interval for RocksDB", nullptr,
+ nullptr, rocksdb_tbl_options->block_restart_interval,
+ /* min */ 1, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ whole_key_filtering,
+ *reinterpret_cast<my_bool *>(&rocksdb_tbl_options->whole_key_filtering),
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "BlockBasedTableOptions::whole_key_filtering for RocksDB", nullptr, nullptr,
+ rocksdb_tbl_options->whole_key_filtering);
+
+static MYSQL_SYSVAR_STR(default_cf_options, rocksdb_default_cf_options,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "default cf options for RocksDB", nullptr, nullptr, "");
+
+static MYSQL_SYSVAR_STR(override_cf_options, rocksdb_override_cf_options,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "option overrides per cf for RocksDB", nullptr, nullptr,
+ "");
+
+static MYSQL_SYSVAR_STR(update_cf_options, rocksdb_update_cf_options,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC
+ /* psergey-merge: need this? : PLUGIN_VAR_ALLOCATED*/,
+ "Option updates per column family for RocksDB",
+ rocksdb_validate_update_cf_options,
+ rocksdb_set_update_cf_options, nullptr);
+
+static MYSQL_SYSVAR_UINT(flush_log_at_trx_commit,
+ rocksdb_flush_log_at_trx_commit, PLUGIN_VAR_RQCMDARG,
+ "Sync on transaction commit. Similar to "
+ "innodb_flush_log_at_trx_commit. 1: sync on commit, "
+ "0,2: not sync on commit",
+ rocksdb_validate_flush_log_at_trx_commit, nullptr,
+ /* default */ FLUSH_LOG_SYNC,
+ /* min */ FLUSH_LOG_NEVER,
+ /* max */ FLUSH_LOG_BACKGROUND, 0);
+
+static MYSQL_THDVAR_BOOL(write_disable_wal, PLUGIN_VAR_RQCMDARG,
+ "WriteOptions::disableWAL for RocksDB", nullptr,
+ nullptr, rocksdb::WriteOptions().disableWAL);
+
+static MYSQL_THDVAR_BOOL(
+ write_ignore_missing_column_families, PLUGIN_VAR_RQCMDARG,
+ "WriteOptions::ignore_missing_column_families for RocksDB", nullptr,
+ nullptr, rocksdb::WriteOptions().ignore_missing_column_families);
+
+static MYSQL_THDVAR_BOOL(skip_fill_cache, PLUGIN_VAR_RQCMDARG,
+ "Skip filling block cache on read requests", nullptr,
+ nullptr, FALSE);
+
+static MYSQL_THDVAR_BOOL(
+ unsafe_for_binlog, PLUGIN_VAR_RQCMDARG,
+ "Allowing statement based binary logging which may break consistency",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_THDVAR_UINT(records_in_range, PLUGIN_VAR_RQCMDARG,
+ "Used to override the result of records_in_range(). "
+ "Set to a positive number to override",
+ nullptr, nullptr, 0,
+ /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_THDVAR_UINT(force_index_records_in_range, PLUGIN_VAR_RQCMDARG,
+ "Used to override the result of records_in_range() "
+ "when FORCE INDEX is used.",
+ nullptr, nullptr, 0,
+ /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_UINT(
+ debug_optimizer_n_rows, rocksdb_debug_optimizer_n_rows,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR,
+ "Test only to override rocksdb estimates of table size in a memtable",
+ nullptr, nullptr, 0, /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(force_compute_memtable_stats,
+ rocksdb_force_compute_memtable_stats,
+ PLUGIN_VAR_RQCMDARG,
+ "Force to always compute memtable stats", nullptr,
+ nullptr, TRUE);
+
+static MYSQL_SYSVAR_UINT(force_compute_memtable_stats_cachetime,
+ rocksdb_force_compute_memtable_stats_cachetime,
+ PLUGIN_VAR_RQCMDARG,
+ "Time in usecs to cache memtable estimates", nullptr,
+ nullptr, /* default */ 60 * 1000 * 1000,
+ /* min */ 0, /* max */ INT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ debug_optimizer_no_zero_cardinality,
+ rocksdb_debug_optimizer_no_zero_cardinality, PLUGIN_VAR_RQCMDARG,
+ "In case if cardinality is zero, overrides it with some value", nullptr,
+ nullptr, TRUE);
+
+static MYSQL_SYSVAR_STR(compact_cf, rocksdb_compact_cf_name,
+ PLUGIN_VAR_RQCMDARG, "Compact column family",
+ rocksdb_compact_column_family,
+ rocksdb_compact_column_family_stub, "");
+
+static MYSQL_SYSVAR_STR(delete_cf, rocksdb_delete_cf_name, PLUGIN_VAR_RQCMDARG,
+ "Delete column family", rocksdb_delete_column_family,
+ rocksdb_delete_column_family_stub, "");
+
+static MYSQL_SYSVAR_STR(create_checkpoint, rocksdb_checkpoint_name,
+ PLUGIN_VAR_RQCMDARG, "Checkpoint directory",
+ rocksdb_create_checkpoint,
+ rocksdb_create_checkpoint_stub, "");
+
+static MYSQL_SYSVAR_BOOL(remove_mariabackup_checkpoint,
+ rocksdb_signal_remove_mariabackup_checkpoint,
+ PLUGIN_VAR_RQCMDARG, "Remove mariabackup checkpoint",
+ nullptr, rocksdb_remove_mariabackup_checkpoint, FALSE);
+
+static MYSQL_SYSVAR_BOOL(signal_drop_index_thread,
+ rocksdb_signal_drop_index_thread, PLUGIN_VAR_RQCMDARG,
+ "Wake up drop index thread", nullptr,
+ rocksdb_drop_index_wakeup_thread, FALSE);
+
+static MYSQL_SYSVAR_BOOL(pause_background_work, rocksdb_pause_background_work,
+ PLUGIN_VAR_RQCMDARG,
+ "Disable all rocksdb background operations", nullptr,
+ rocksdb_set_pause_background_work, FALSE);
+
+static MYSQL_SYSVAR_BOOL(
+ enable_ttl, rocksdb_enable_ttl, PLUGIN_VAR_RQCMDARG,
+ "Enable expired TTL records to be dropped during compaction.", nullptr,
+ nullptr, TRUE);
+
+static MYSQL_SYSVAR_BOOL(
+ enable_ttl_read_filtering, rocksdb_enable_ttl_read_filtering,
+ PLUGIN_VAR_RQCMDARG,
+ "For tables with TTL, expired records are skipped/filtered out during "
+ "processing and in query results. Disabling this will allow these records "
+ "to be seen, but as a result rows may disappear in the middle of "
+ "transactions as they are dropped during compaction. Use with caution.",
+ nullptr, nullptr, TRUE);
+
+static MYSQL_SYSVAR_INT(
+ debug_ttl_rec_ts, rocksdb_debug_ttl_rec_ts, PLUGIN_VAR_RQCMDARG,
+ "For debugging purposes only. Overrides the TTL of records to "
+ "now() + debug_ttl_rec_ts. The value can be +/- to simulate "
+ "a record inserted in the past vs a record inserted in the 'future'. "
+ "A value of 0 denotes that the variable is not set. This variable is a "
+ "no-op in non-debug builds.",
+ nullptr, nullptr, 0, /* min */ -3600, /* max */ 3600, 0);
+
+static MYSQL_SYSVAR_INT(
+ debug_ttl_snapshot_ts, rocksdb_debug_ttl_snapshot_ts, PLUGIN_VAR_RQCMDARG,
+ "For debugging purposes only. Sets the snapshot during compaction to "
+ "now() + debug_set_ttl_snapshot_ts. The value can be +/- to simulate "
+ "a snapshot in the past vs a snapshot created in the 'future'. "
+ "A value of 0 denotes that the variable is not set. This variable is a "
+ "no-op in non-debug builds.",
+ nullptr, nullptr, 0, /* min */ -3600, /* max */ 3600, 0);
+
+static MYSQL_SYSVAR_INT(
+ debug_ttl_read_filter_ts, rocksdb_debug_ttl_read_filter_ts,
+ PLUGIN_VAR_RQCMDARG,
+ "For debugging purposes only. Overrides the TTL read filtering time to "
+ "time + debug_ttl_read_filter_ts. A value of 0 denotes that the variable "
+ "is not set. This variable is a no-op in non-debug builds.",
+ nullptr, nullptr, 0, /* min */ -3600, /* max */ 3600, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ debug_ttl_ignore_pk, rocksdb_debug_ttl_ignore_pk, PLUGIN_VAR_RQCMDARG,
+ "For debugging purposes only. If true, compaction filtering will not occur "
+ "on PK TTL data. This variable is a no-op in non-debug builds.",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_SYSVAR_UINT(
+ max_manual_compactions, rocksdb_max_manual_compactions, PLUGIN_VAR_RQCMDARG,
+ "Maximum number of pending + ongoing number of manual compactions.",
+ nullptr, nullptr, /* default */ 10, /* min */ 0, /* max */ UINT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ rollback_on_timeout, rocksdb_rollback_on_timeout, PLUGIN_VAR_OPCMDARG,
+ "Whether to roll back the complete transaction or a single statement on "
+ "lock wait timeout (a single statement by default)",
+ NULL, NULL, FALSE);
+
+static MYSQL_SYSVAR_UINT(
+ debug_manual_compaction_delay, rocksdb_debug_manual_compaction_delay,
+ PLUGIN_VAR_RQCMDARG,
+ "For debugging purposes only. Sleeping specified seconds "
+ "for simulating long running compactions.",
+ nullptr, nullptr, 0, /* min */ 0, /* max */ UINT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ reset_stats, rocksdb_reset_stats, PLUGIN_VAR_RQCMDARG,
+ "Reset the RocksDB internal statistics without restarting the DB.", nullptr,
+ rocksdb_set_reset_stats, FALSE);
+
+static MYSQL_SYSVAR_UINT(io_write_timeout, rocksdb_io_write_timeout_secs,
+ PLUGIN_VAR_RQCMDARG,
+ "Timeout for experimental I/O watchdog.", nullptr,
+ rocksdb_set_io_write_timeout, /* default */ 0,
+ /* min */ 0L,
+ /* max */ UINT_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(enable_2pc, rocksdb_enable_2pc, PLUGIN_VAR_RQCMDARG,
+ "Enable two phase commit for MyRocks", nullptr,
+ nullptr, TRUE);
+
+static MYSQL_SYSVAR_BOOL(ignore_unknown_options, rocksdb_ignore_unknown_options,
+ PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY,
+ "Enable ignoring unknown options passed to RocksDB",
+ nullptr, nullptr, TRUE);
+
+static MYSQL_SYSVAR_BOOL(strict_collation_check, rocksdb_strict_collation_check,
+ PLUGIN_VAR_RQCMDARG,
+ "Enforce case sensitive collation for MyRocks indexes",
+ nullptr, nullptr, TRUE);
+
+static MYSQL_SYSVAR_STR(strict_collation_exceptions,
+ rocksdb_strict_collation_exceptions,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
+ "List of tables (using regex) that are excluded "
+ "from the case sensitive collation enforcement",
+ nullptr, rocksdb_set_collation_exception_list, "");
+
+static MYSQL_SYSVAR_BOOL(collect_sst_properties, rocksdb_collect_sst_properties,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Enables collecting SST file properties on each flush",
+ nullptr, nullptr, rocksdb_collect_sst_properties);
+
+static MYSQL_SYSVAR_BOOL(
+ force_flush_memtable_now, rocksdb_force_flush_memtable_now_var,
+ PLUGIN_VAR_RQCMDARG,
+ "Forces memstore flush which may block all write requests so be careful",
+ rocksdb_force_flush_memtable_now, rocksdb_force_flush_memtable_now_stub,
+ FALSE);
+
+static MYSQL_SYSVAR_BOOL(
+ force_flush_memtable_and_lzero_now,
+ rocksdb_force_flush_memtable_and_lzero_now_var, PLUGIN_VAR_RQCMDARG,
+ "Acts similar to force_flush_memtable_now, but also compacts all L0 files.",
+ rocksdb_force_flush_memtable_and_lzero_now,
+ rocksdb_force_flush_memtable_and_lzero_now_stub, FALSE);
+
+static MYSQL_SYSVAR_UINT(
+ seconds_between_stat_computes, rocksdb_seconds_between_stat_computes,
+ PLUGIN_VAR_RQCMDARG,
+ "Sets a number of seconds to wait between optimizer stats recomputation. "
+ "Only changed indexes will be refreshed.",
+ nullptr, nullptr, rocksdb_seconds_between_stat_computes,
+ /* min */ 0L, /* max */ UINT_MAX, 0);
+
+static MYSQL_SYSVAR_LONGLONG(compaction_sequential_deletes,
+ rocksdb_compaction_sequential_deletes,
+ PLUGIN_VAR_RQCMDARG,
+ "RocksDB will trigger compaction for the file if "
+ "it has more than this number sequential deletes "
+ "per window",
+ nullptr, rocksdb_set_compaction_options,
+ DEFAULT_COMPACTION_SEQUENTIAL_DELETES,
+ /* min */ 0L,
+ /* max */ MAX_COMPACTION_SEQUENTIAL_DELETES, 0);
+
+static MYSQL_SYSVAR_LONGLONG(
+ compaction_sequential_deletes_window,
+ rocksdb_compaction_sequential_deletes_window, PLUGIN_VAR_RQCMDARG,
+ "Size of the window for counting rocksdb_compaction_sequential_deletes",
+ nullptr, rocksdb_set_compaction_options,
+ DEFAULT_COMPACTION_SEQUENTIAL_DELETES_WINDOW,
+ /* min */ 0L, /* max */ MAX_COMPACTION_SEQUENTIAL_DELETES_WINDOW, 0);
+
+static MYSQL_SYSVAR_LONGLONG(
+ compaction_sequential_deletes_file_size,
+ rocksdb_compaction_sequential_deletes_file_size, PLUGIN_VAR_RQCMDARG,
+ "Minimum file size required for compaction_sequential_deletes", nullptr,
+ rocksdb_set_compaction_options, 0L,
+ /* min */ -1L, /* max */ LLONG_MAX, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ compaction_sequential_deletes_count_sd,
+ rocksdb_compaction_sequential_deletes_count_sd, PLUGIN_VAR_RQCMDARG,
+ "Counting SingleDelete as rocksdb_compaction_sequential_deletes", nullptr,
+ nullptr, rocksdb_compaction_sequential_deletes_count_sd);
+
+static MYSQL_SYSVAR_BOOL(
+ print_snapshot_conflict_queries, rocksdb_print_snapshot_conflict_queries,
+ PLUGIN_VAR_RQCMDARG,
+ "Logging queries that got snapshot conflict errors into *.err log", nullptr,
+ nullptr, rocksdb_print_snapshot_conflict_queries);
+
+static MYSQL_THDVAR_INT(checksums_pct, PLUGIN_VAR_RQCMDARG,
+ "How many percentages of rows to be checksummed",
+ nullptr, nullptr, RDB_MAX_CHECKSUMS_PCT,
+ /* min */ 0, /* max */ RDB_MAX_CHECKSUMS_PCT, 0);
+
+static MYSQL_THDVAR_BOOL(store_row_debug_checksums, PLUGIN_VAR_RQCMDARG,
+ "Include checksums when writing index/table records",
+ nullptr, nullptr, false /* default value */);
+
+static MYSQL_THDVAR_BOOL(verify_row_debug_checksums, PLUGIN_VAR_RQCMDARG,
+ "Verify checksums when reading index/table records",
+ nullptr, nullptr, false /* default value */);
+
+static MYSQL_THDVAR_BOOL(master_skip_tx_api, PLUGIN_VAR_RQCMDARG,
+ "Skipping holding any lock on row access. "
+ "Not effective on slave.",
+ nullptr, nullptr, false);
+
+static MYSQL_SYSVAR_UINT(
+ validate_tables, rocksdb_validate_tables,
+ PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+ "Verify all .frm files match all RocksDB tables (0 means no verification, "
+ "1 means verify and fail on error, and 2 means verify but continue",
+ nullptr, nullptr, 1 /* default value */, 0 /* min value */,
+ 2 /* max value */, 0);
+
+static MYSQL_SYSVAR_STR(datadir, rocksdb_datadir,
+ PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY,
+ "RocksDB data directory", nullptr, nullptr,
+ "./#rocksdb");
+
+static MYSQL_SYSVAR_STR(supported_compression_types,
+ compression_types_val,
+ PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_READONLY,
+ "Compression algorithms supported by RocksDB",
+ nullptr, nullptr,
+ compression_types_val);
+
+static MYSQL_SYSVAR_UINT(
+ table_stats_sampling_pct, rocksdb_table_stats_sampling_pct,
+ PLUGIN_VAR_RQCMDARG,
+ "Percentage of entries to sample when collecting statistics about table "
+ "properties. Specify either 0 to sample everything or percentage "
+ "[" STRINGIFY_ARG(RDB_TBL_STATS_SAMPLE_PCT_MIN) ".." STRINGIFY_ARG(
+ RDB_TBL_STATS_SAMPLE_PCT_MAX) "]. "
+ "By default " STRINGIFY_ARG(
+ RDB_DEFAULT_TBL_STATS_SAMPLE_PCT) "% "
+ "of"
+ " e"
+ "nt"
+ "ri"
+ "es"
+ " a"
+ "re"
+ " "
+ "sa"
+ "mp"
+ "le"
+ "d"
+ ".",
+ nullptr, rocksdb_set_table_stats_sampling_pct, /* default */
+ RDB_DEFAULT_TBL_STATS_SAMPLE_PCT, /* everything */ 0,
+ /* max */ RDB_TBL_STATS_SAMPLE_PCT_MAX, 0);
+
+static MYSQL_SYSVAR_UINT(
+ stats_recalc_rate, rocksdb_stats_recalc_rate, PLUGIN_VAR_RQCMDARG,
+ "The number of indexes per second to recalculate statistics for. 0 to "
+ "disable background recalculation.",
+ nullptr, nullptr, 0 /* default value */, 0 /* min value */,
+ UINT_MAX /* max value */, 0);
+
+static MYSQL_SYSVAR_BOOL(
+ large_prefix, rocksdb_large_prefix, PLUGIN_VAR_RQCMDARG,
+ "Support large index prefix length of 3072 bytes. If off, the maximum "
+ "index prefix length is 767.",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_SYSVAR_BOOL(
+ allow_to_start_after_corruption, rocksdb_allow_to_start_after_corruption,
+ PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY,
+ "Allow server still to start successfully even if RocksDB corruption is "
+ "detected.",
+ nullptr, nullptr, FALSE);
+
+static MYSQL_SYSVAR_BOOL(error_on_suboptimal_collation,
+ rocksdb_error_on_suboptimal_collation,
+ PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY,
+ "Raise an error instead of warning if a sub-optimal "
+ "collation is used",
+ nullptr, nullptr, TRUE);
+
+static MYSQL_SYSVAR_BOOL(
+ enable_insert_with_update_caching,
+ rocksdb_enable_insert_with_update_caching, PLUGIN_VAR_OPCMDARG,
+ "Whether to enable optimization where we cache the read from a failed "
+ "insertion attempt in INSERT ON DUPLICATE KEY UPDATE",
+ nullptr, nullptr, TRUE);
+
+static const int ROCKSDB_ASSUMED_KEY_VALUE_DISK_SIZE = 100;
+
+static struct st_mysql_sys_var *rocksdb_system_variables[] = {
+ MYSQL_SYSVAR(lock_wait_timeout),
+ MYSQL_SYSVAR(deadlock_detect),
+ MYSQL_SYSVAR(deadlock_detect_depth),
+ MYSQL_SYSVAR(commit_time_batch_for_recovery),
+ MYSQL_SYSVAR(max_row_locks),
+ MYSQL_SYSVAR(write_batch_max_bytes),
+ MYSQL_SYSVAR(lock_scanned_rows),
+ MYSQL_SYSVAR(bulk_load),
+ MYSQL_SYSVAR(bulk_load_allow_sk),
+ MYSQL_SYSVAR(bulk_load_allow_unsorted),
+ MYSQL_SYSVAR(skip_unique_check_tables),
+ MYSQL_SYSVAR(trace_sst_api),
+ MYSQL_SYSVAR(commit_in_the_middle),
+ MYSQL_SYSVAR(blind_delete_primary_key),
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+ MYSQL_SYSVAR(read_free_rpl_tables),
+ MYSQL_SYSVAR(read_free_rpl),
+#endif
+ MYSQL_SYSVAR(bulk_load_size),
+ MYSQL_SYSVAR(merge_buf_size),
+ MYSQL_SYSVAR(enable_bulk_load_api),
+ MYSQL_SYSVAR(tmpdir),
+ MYSQL_SYSVAR(merge_combine_read_size),
+ MYSQL_SYSVAR(merge_tmp_file_removal_delay_ms),
+ MYSQL_SYSVAR(skip_bloom_filter_on_read),
+
+ MYSQL_SYSVAR(create_if_missing),
+ MYSQL_SYSVAR(two_write_queues),
+ MYSQL_SYSVAR(manual_wal_flush),
+ MYSQL_SYSVAR(write_policy),
+ MYSQL_SYSVAR(create_missing_column_families),
+ MYSQL_SYSVAR(error_if_exists),
+ MYSQL_SYSVAR(paranoid_checks),
+ MYSQL_SYSVAR(rate_limiter_bytes_per_sec),
+ MYSQL_SYSVAR(sst_mgr_rate_bytes_per_sec),
+ MYSQL_SYSVAR(delayed_write_rate),
+ MYSQL_SYSVAR(max_latest_deadlocks),
+ MYSQL_SYSVAR(info_log_level),
+ MYSQL_SYSVAR(max_open_files),
+ MYSQL_SYSVAR(max_total_wal_size),
+ MYSQL_SYSVAR(use_fsync),
+ MYSQL_SYSVAR(wal_dir),
+ MYSQL_SYSVAR(persistent_cache_path),
+ MYSQL_SYSVAR(persistent_cache_size_mb),
+ MYSQL_SYSVAR(delete_obsolete_files_period_micros),
+ MYSQL_SYSVAR(max_background_jobs),
+ MYSQL_SYSVAR(max_log_file_size),
+ MYSQL_SYSVAR(max_subcompactions),
+ MYSQL_SYSVAR(log_file_time_to_roll),
+ MYSQL_SYSVAR(keep_log_file_num),
+ MYSQL_SYSVAR(max_manifest_file_size),
+ MYSQL_SYSVAR(table_cache_numshardbits),
+ MYSQL_SYSVAR(wal_ttl_seconds),
+ MYSQL_SYSVAR(wal_size_limit_mb),
+ MYSQL_SYSVAR(manifest_preallocation_size),
+ MYSQL_SYSVAR(use_direct_reads),
+ MYSQL_SYSVAR(use_direct_io_for_flush_and_compaction),
+ MYSQL_SYSVAR(allow_mmap_reads),
+ MYSQL_SYSVAR(allow_mmap_writes),
+ MYSQL_SYSVAR(is_fd_close_on_exec),
+ MYSQL_SYSVAR(stats_dump_period_sec),
+ MYSQL_SYSVAR(advise_random_on_open),
+ MYSQL_SYSVAR(db_write_buffer_size),
+ MYSQL_SYSVAR(use_adaptive_mutex),
+ MYSQL_SYSVAR(bytes_per_sync),
+ MYSQL_SYSVAR(wal_bytes_per_sync),
+ MYSQL_SYSVAR(enable_thread_tracking),
+ MYSQL_SYSVAR(perf_context_level),
+ MYSQL_SYSVAR(wal_recovery_mode),
+ MYSQL_SYSVAR(stats_level),
+ MYSQL_SYSVAR(access_hint_on_compaction_start),
+ MYSQL_SYSVAR(new_table_reader_for_compaction_inputs),
+ MYSQL_SYSVAR(compaction_readahead_size),
+ MYSQL_SYSVAR(allow_concurrent_memtable_write),
+ MYSQL_SYSVAR(enable_write_thread_adaptive_yield),
+
+ MYSQL_SYSVAR(block_cache_size),
+ MYSQL_SYSVAR(sim_cache_size),
+ MYSQL_SYSVAR(use_clock_cache),
+ MYSQL_SYSVAR(cache_high_pri_pool_ratio),
+ MYSQL_SYSVAR(cache_dump),
+ MYSQL_SYSVAR(cache_index_and_filter_blocks),
+ MYSQL_SYSVAR(cache_index_and_filter_with_high_priority),
+ MYSQL_SYSVAR(pin_l0_filter_and_index_blocks_in_cache),
+ MYSQL_SYSVAR(index_type),
+ MYSQL_SYSVAR(hash_index_allow_collision),
+ MYSQL_SYSVAR(no_block_cache),
+ MYSQL_SYSVAR(block_size),
+ MYSQL_SYSVAR(block_size_deviation),
+ MYSQL_SYSVAR(block_restart_interval),
+ MYSQL_SYSVAR(whole_key_filtering),
+
+ MYSQL_SYSVAR(default_cf_options),
+ MYSQL_SYSVAR(override_cf_options),
+ MYSQL_SYSVAR(update_cf_options),
+
+ MYSQL_SYSVAR(flush_log_at_trx_commit),
+ MYSQL_SYSVAR(write_disable_wal),
+ MYSQL_SYSVAR(write_ignore_missing_column_families),
+
+ MYSQL_SYSVAR(skip_fill_cache),
+ MYSQL_SYSVAR(unsafe_for_binlog),
+
+ MYSQL_SYSVAR(records_in_range),
+ MYSQL_SYSVAR(force_index_records_in_range),
+ MYSQL_SYSVAR(debug_optimizer_n_rows),
+ MYSQL_SYSVAR(force_compute_memtable_stats),
+ MYSQL_SYSVAR(force_compute_memtable_stats_cachetime),
+ MYSQL_SYSVAR(debug_optimizer_no_zero_cardinality),
+
+ MYSQL_SYSVAR(compact_cf),
+ MYSQL_SYSVAR(delete_cf),
+ MYSQL_SYSVAR(signal_drop_index_thread),
+ MYSQL_SYSVAR(pause_background_work),
+ MYSQL_SYSVAR(enable_2pc),
+ MYSQL_SYSVAR(ignore_unknown_options),
+ MYSQL_SYSVAR(strict_collation_check),
+ MYSQL_SYSVAR(strict_collation_exceptions),
+ MYSQL_SYSVAR(collect_sst_properties),
+ MYSQL_SYSVAR(force_flush_memtable_now),
+ MYSQL_SYSVAR(force_flush_memtable_and_lzero_now),
+ MYSQL_SYSVAR(enable_ttl),
+ MYSQL_SYSVAR(enable_ttl_read_filtering),
+ MYSQL_SYSVAR(debug_ttl_rec_ts),
+ MYSQL_SYSVAR(debug_ttl_snapshot_ts),
+ MYSQL_SYSVAR(debug_ttl_read_filter_ts),
+ MYSQL_SYSVAR(debug_ttl_ignore_pk),
+ MYSQL_SYSVAR(reset_stats),
+ MYSQL_SYSVAR(io_write_timeout),
+ MYSQL_SYSVAR(seconds_between_stat_computes),
+
+ MYSQL_SYSVAR(compaction_sequential_deletes),
+ MYSQL_SYSVAR(compaction_sequential_deletes_window),
+ MYSQL_SYSVAR(compaction_sequential_deletes_file_size),
+ MYSQL_SYSVAR(compaction_sequential_deletes_count_sd),
+ MYSQL_SYSVAR(print_snapshot_conflict_queries),
+
+ MYSQL_SYSVAR(datadir),
+ MYSQL_SYSVAR(supported_compression_types),
+ MYSQL_SYSVAR(create_checkpoint),
+ MYSQL_SYSVAR(remove_mariabackup_checkpoint),
+ MYSQL_SYSVAR(checksums_pct),
+ MYSQL_SYSVAR(store_row_debug_checksums),
+ MYSQL_SYSVAR(verify_row_debug_checksums),
+ MYSQL_SYSVAR(master_skip_tx_api),
+
+ MYSQL_SYSVAR(validate_tables),
+ MYSQL_SYSVAR(table_stats_sampling_pct),
+
+ MYSQL_SYSVAR(large_prefix),
+ MYSQL_SYSVAR(allow_to_start_after_corruption),
+ MYSQL_SYSVAR(git_hash),
+ MYSQL_SYSVAR(error_on_suboptimal_collation),
+ MYSQL_SYSVAR(stats_recalc_rate),
+ MYSQL_SYSVAR(debug_manual_compaction_delay),
+ MYSQL_SYSVAR(max_manual_compactions),
+ MYSQL_SYSVAR(manual_compaction_threads),
+ MYSQL_SYSVAR(rollback_on_timeout),
+
+ MYSQL_SYSVAR(enable_insert_with_update_caching),
+ nullptr};
+
+static rocksdb::WriteOptions rdb_get_rocksdb_write_options(
+ my_core::THD *const thd) {
+ rocksdb::WriteOptions opt;
+
+ opt.sync = (rocksdb_flush_log_at_trx_commit == FLUSH_LOG_SYNC);
+ opt.disableWAL = THDVAR(thd, write_disable_wal);
+ opt.ignore_missing_column_families =
+ THDVAR(thd, write_ignore_missing_column_families);
+
+ return opt;
+}
+
+static int rocksdb_compact_column_family(THD *const thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ struct st_mysql_value *const value) {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ int len = sizeof(buff);
+
+ DBUG_ASSERT(value != nullptr);
+
+ if (const char *const cf = value->val_str(value, buff, &len)) {
+ auto cfh = cf_manager.get_cf(cf);
+ if (cfh != nullptr && rdb != nullptr) {
+ int mc_id = rdb_mc_thread.request_manual_compaction(
+ cfh, nullptr, nullptr, THDVAR(thd, manual_compaction_threads));
+ if (mc_id == -1) {
+ my_error(ER_INTERNAL_ERROR, MYF(0),
+ "Can't schedule more manual compactions. "
+ "Increase rocksdb_max_manual_compactions or stop issuing "
+ "more manual compactions.");
+ return HA_EXIT_FAILURE;
+ } else if (mc_id < 0) {
+ return HA_EXIT_FAILURE;
+ }
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Manual compaction of column family: %s\n",
+ cf);
+ // Checking thd state every short cycle (100ms). This is for allowing to
+ // exiting this function without waiting for CompactRange to finish.
+ do {
+ my_sleep(100000);
+ } while (!thd->killed &&
+ !rdb_mc_thread.is_manual_compaction_finished(mc_id));
+
+ if (thd->killed) {
+ // This cancels if requested compaction state is INITED.
+ // TODO(yoshinorim): Cancel running compaction as well once
+ // it is supported in RocksDB.
+ rdb_mc_thread.clear_manual_compaction_request(mc_id, true);
+ }
+ }
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ Drop index thread's control
+*/
+
+static Rdb_drop_index_thread rdb_drop_idx_thread;
+
+static void rocksdb_drop_index_wakeup_thread(
+ my_core::THD *const thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ if (*static_cast<const bool *>(save)) {
+ rdb_drop_idx_thread.signal();
+ }
+}
+
+static inline uint32_t rocksdb_perf_context_level(THD *const thd) {
+ DBUG_ASSERT(thd != nullptr);
+
+ const int session_perf_context_level = THDVAR(thd, perf_context_level);
+ if (session_perf_context_level > rocksdb::PerfLevel::kUninitialized) {
+ return session_perf_context_level;
+ }
+
+ /*
+ Fallback to global thdvar, if session specific one was not set to a valid
+ value.
+ */
+
+ const int global_perf_context_level = THDVAR(nullptr, perf_context_level);
+ if (global_perf_context_level > rocksdb::PerfLevel::kUninitialized) {
+ return global_perf_context_level;
+ }
+
+ return rocksdb::PerfLevel::kDisable;
+}
+
+/*
+ Very short (functor-like) interface to be passed to
+ Rdb_transaction::walk_tx_list()
+*/
+
+interface Rdb_tx_list_walker {
+ virtual ~Rdb_tx_list_walker() {}
+ virtual void process_tran(const Rdb_transaction *const) = 0;
+};
+
+/*
+ This is a helper class that is passed to RocksDB to get notifications when
+ a snapshot gets created.
+*/
+
+class Rdb_snapshot_notifier : public rocksdb::TransactionNotifier {
+ Rdb_transaction *m_owning_tx;
+
+ void SnapshotCreated(const rocksdb::Snapshot *snapshot) override;
+
+ public:
+ Rdb_snapshot_notifier(const Rdb_snapshot_notifier &) = delete;
+ Rdb_snapshot_notifier &operator=(const Rdb_snapshot_notifier &) = delete;
+
+ explicit Rdb_snapshot_notifier(Rdb_transaction *const owning_tx)
+ : m_owning_tx(owning_tx) {}
+
+ // If the owning Rdb_transaction gets destructed we need to not reference
+ // it anymore.
+ void detach() { m_owning_tx = nullptr; }
+};
+
+
+#ifdef MARIAROCKS_NOT_YET
+// ER_LOCK_WAIT_TIMEOUT error also has a reason in facebook/mysql-5.6
+#endif
+String timeout_message(const char *command, const char *name1,
+ const char *name2)
+{
+ String msg;
+ msg.append("Timeout on ");
+ msg.append(command);
+ msg.append(": ");
+ msg.append(name1);
+ if (name2 && name2[0])
+ {
+ msg.append(".");
+ msg.append(name2);
+ }
+ return msg;
+}
+
+
+/* This is the base class for transactions when interacting with rocksdb.
+ */
+class Rdb_transaction {
+ protected:
+ ulonglong m_write_count = 0;
+ ulonglong m_insert_count = 0;
+ ulonglong m_update_count = 0;
+ ulonglong m_delete_count = 0;
+ ulonglong m_lock_count = 0;
+ std::unordered_map<GL_INDEX_ID, ulonglong> m_auto_incr_map;
+
+ bool m_is_delayed_snapshot = false;
+ bool m_is_two_phase = false;
+
+ std::unordered_set<Rdb_tbl_def*> modified_tables;
+
+ private:
+ /*
+ Number of write operations this transaction had when we took the last
+ savepoint (the idea is not to take another savepoint if we haven't made
+ any changes)
+ */
+ ulonglong m_writes_at_last_savepoint;
+
+ protected:
+
+protected:
+ THD *m_thd = nullptr;
+
+ static std::multiset<Rdb_transaction *> s_tx_list;
+ static mysql_mutex_t s_tx_list_mutex;
+
+ Rdb_io_perf *m_tbl_io_perf;
+
+ bool m_tx_read_only = false;
+
+ int m_timeout_sec; /* Cached value of @@rocksdb_lock_wait_timeout */
+
+ /* Maximum number of locks the transaction can have */
+ ulonglong m_max_row_locks;
+
+ bool m_is_tx_failed = false;
+ bool m_rollback_only = false;
+
+ std::shared_ptr<Rdb_snapshot_notifier> m_notifier;
+
+ // This should be used only when updating binlog information.
+ virtual rocksdb::WriteBatchBase *get_write_batch() = 0;
+ virtual bool commit_no_binlog() = 0;
+ virtual rocksdb::Iterator *get_iterator(
+ const rocksdb::ReadOptions &options,
+ rocksdb::ColumnFamilyHandle *column_family) = 0;
+
+protected:
+ /*
+ The following two are helper functions to be overloaded by child classes.
+ They should provide RocksDB's savepoint semantics.
+ */
+ virtual void do_set_savepoint() = 0;
+ virtual void do_rollback_to_savepoint() = 0;
+
+ /*
+ @detail
+ This function takes in the WriteBatch of the transaction to add
+ all the AUTO_INCREMENT merges. It does so by iterating through
+ m_auto_incr_map and then constructing key/value pairs to call merge upon.
+
+ @param wb
+ */
+ rocksdb::Status merge_auto_incr_map(rocksdb::WriteBatchBase *const wb) {
+ DBUG_EXECUTE_IF("myrocks_autoinc_upgrade", return rocksdb::Status::OK(););
+
+ // Iterate through the merge map merging all keys into data dictionary.
+ rocksdb::Status s;
+ for (auto &it : m_auto_incr_map) {
+ s = dict_manager.put_auto_incr_val(wb, it.first, it.second);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ m_auto_incr_map.clear();
+ return s;
+ }
+
+ public:
+ rocksdb::ReadOptions m_read_opts;
+ const char *m_mysql_log_file_name;
+ my_off_t m_mysql_log_offset;
+#ifdef MARIAROCKS_NOT_YET
+ // TODO: MariaDB probably doesn't need these at all:
+ const char *m_mysql_gtid;
+ const char *m_mysql_max_gtid;
+#endif
+ String m_detailed_error;
+ int64_t m_snapshot_timestamp = 0;
+ bool m_ddl_transaction;
+#ifdef MARIAROCKS_NOT_YET
+ std::shared_ptr<Rdb_explicit_snapshot> m_explicit_snapshot;
+#endif
+
+ /*
+ Tracks the number of tables in use through external_lock.
+ This should not be reset during start_tx().
+ */
+ int64_t m_n_mysql_tables_in_use = 0;
+
+ /*
+ MariaDB's group commit:
+ */
+ bool commit_ordered_done;
+ bool commit_ordered_res;
+
+ /*
+ for distinction between rdb_transaction_impl and rdb_writebatch_impl
+ when using walk tx list
+ */
+ virtual bool is_writebatch_trx() const = 0;
+
+ static void init_mutex() {
+ mysql_mutex_init(key_mutex_tx_list, &s_tx_list_mutex, MY_MUTEX_INIT_FAST);
+ }
+
+ static void term_mutex() {
+ DBUG_ASSERT(s_tx_list.size() == 0);
+ mysql_mutex_destroy(&s_tx_list_mutex);
+ }
+
+ static void walk_tx_list(Rdb_tx_list_walker *walker) {
+ DBUG_ASSERT(walker != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(s_tx_list_mutex);
+
+ for (auto it : s_tx_list) {
+ walker->process_tran(it);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(s_tx_list_mutex);
+ }
+
+ int set_status_error(THD *const thd, const rocksdb::Status &s,
+ const Rdb_key_def &kd, Rdb_tbl_def *const tbl_def,
+ Rdb_table_handler *const table_handler) {
+ DBUG_ASSERT(!s.ok());
+ DBUG_ASSERT(tbl_def != nullptr);
+
+ if (s.IsTimedOut()) {
+ /*
+ SQL layer has weird expectations. If we return an error when
+ doing a read in DELETE IGNORE, it will ignore the error ("because it's
+ an IGNORE command!) but then will fail an assert, because "error code
+ was returned, but no error happened". Do what InnoDB's
+ convert_error_code_to_mysql() does: force a statement
+ rollback before returning HA_ERR_LOCK_WAIT_TIMEOUT:
+ */
+ my_core::thd_mark_transaction_to_rollback(
+ thd, static_cast<bool>(rocksdb_rollback_on_timeout));
+ m_detailed_error.copy(timeout_message(
+ "index", tbl_def->full_tablename().c_str(), kd.get_name().c_str()));
+ table_handler->m_lock_wait_timeout_counter.inc();
+ rocksdb_row_lock_wait_timeouts++;
+
+ return HA_ERR_LOCK_WAIT_TIMEOUT;
+ }
+
+ if (s.IsDeadlock()) {
+ my_core::thd_mark_transaction_to_rollback(thd,
+ true /* whole transaction */);
+ m_detailed_error = String();
+ table_handler->m_deadlock_counter.inc();
+ rocksdb_row_lock_deadlocks++;
+ return HA_ERR_LOCK_DEADLOCK;
+ } else if (s.IsBusy()) {
+ rocksdb_snapshot_conflict_errors++;
+ if (rocksdb_print_snapshot_conflict_queries) {
+ char user_host_buff[MAX_USER_HOST_SIZE + 1];
+ make_user_name(thd, user_host_buff);
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "Got snapshot conflict errors: User: %s "
+ "Query: %s",
+ user_host_buff, thd->query());
+ }
+ m_detailed_error = String(" (snapshot conflict)", system_charset_info);
+ table_handler->m_deadlock_counter.inc();
+ return HA_ERR_ROCKSDB_STATUS_BUSY;
+ }
+
+ if (s.IsIOError() || s.IsCorruption()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_GENERAL);
+ }
+
+ return ha_rocksdb::rdb_error_to_mysql(s);
+ }
+
+ THD *get_thd() const { return m_thd; }
+
+ /* Used for tracking io_perf counters */
+ void io_perf_start(Rdb_io_perf *const io_perf) {
+ /*
+ Since perf_context is tracked per thread, it is difficult and expensive
+ to maintain perf_context on a per table basis. Therefore, roll all
+ perf_context data into the first table used in a query. This works well
+ for single table queries and is probably good enough for queries that hit
+ multiple tables.
+
+ perf_context stats gathering is started when the table lock is acquired
+ or when ha_rocksdb::start_stmt is called in case of LOCK TABLES. They
+ are recorded when the table lock is released, or when commit/rollback
+ is called on the transaction, whichever comes first. Table lock release
+ and commit/rollback can happen in different orders. In the case where
+ the lock is released before commit/rollback is called, an extra step to
+ gather stats during commit/rollback is needed.
+ */
+ if (m_tbl_io_perf == nullptr &&
+ io_perf->start(rocksdb_perf_context_level(m_thd))) {
+ m_tbl_io_perf = io_perf;
+ }
+ }
+
+ void io_perf_end_and_record(void) {
+ if (m_tbl_io_perf != nullptr) {
+ m_tbl_io_perf->end_and_record(rocksdb_perf_context_level(m_thd));
+ m_tbl_io_perf = nullptr;
+ }
+ }
+
+ void io_perf_end_and_record(Rdb_io_perf *const io_perf) {
+ if (m_tbl_io_perf == io_perf) {
+ io_perf_end_and_record();
+ }
+ }
+
+ void update_bytes_written(ulonglong bytes_written) {
+ if (m_tbl_io_perf != nullptr) {
+ m_tbl_io_perf->update_bytes_written(rocksdb_perf_context_level(m_thd),
+ bytes_written);
+ }
+ }
+
+ void set_params(int timeout_sec_arg, int max_row_locks_arg) {
+ m_timeout_sec = timeout_sec_arg;
+ m_max_row_locks = max_row_locks_arg;
+ set_lock_timeout(timeout_sec_arg);
+ }
+
+ virtual void set_lock_timeout(int timeout_sec_arg) = 0;
+
+ ulonglong get_write_count() const { return m_write_count; }
+
+ ulonglong get_insert_count() const { return m_insert_count; }
+
+ ulonglong get_update_count() const { return m_update_count; }
+
+ ulonglong get_delete_count() const { return m_delete_count; }
+
+ void incr_insert_count() { ++m_insert_count; }
+
+ void incr_update_count() { ++m_update_count; }
+
+ void incr_delete_count() { ++m_delete_count; }
+
+ int get_timeout_sec() const { return m_timeout_sec; }
+
+ ulonglong get_lock_count() const { return m_lock_count; }
+
+ virtual void set_sync(bool sync) = 0;
+
+ virtual void release_lock(rocksdb::ColumnFamilyHandle *const column_family,
+ const std::string &rowkey) = 0;
+
+ virtual bool prepare(const rocksdb::TransactionName &name) = 0;
+
+ bool commit_or_rollback() {
+ bool res;
+ if (m_is_tx_failed) {
+ rollback();
+ res = false;
+ } else {
+ res = commit();
+ }
+ return res;
+ }
+
+ bool commit() {
+ if (get_write_count() == 0) {
+ rollback();
+ return false;
+ } else if (m_rollback_only) {
+ /*
+ Transactions marked as rollback_only are expected to be rolled back at
+ prepare(). But there are some exceptions like below that prepare() is
+ never called and commit() is called instead.
+ 1. Binlog is disabled
+ 2. No modification exists in binlog cache for the transaction (#195)
+ In both cases, rolling back transaction is safe. Nothing is written to
+ binlog.
+ */
+ my_error(ER_ROLLBACK_ONLY, MYF(0));
+ rollback();
+ return true;
+ } else {
+#ifdef MARIAROCKS_NOT_YET
+ /*
+ Storing binlog position inside MyRocks is needed only for restoring
+ MyRocks from backups. This feature is not supported yet.
+ */
+ mysql_bin_log_commit_pos(m_thd, &m_mysql_log_offset,
+ &m_mysql_log_file_name);
+ binlog_manager.update(m_mysql_log_file_name, m_mysql_log_offset,
+ get_write_batch());
+#endif
+ return commit_no_binlog();
+ }
+ }
+
+ virtual void rollback() = 0;
+
+ void snapshot_created(const rocksdb::Snapshot *const snapshot) {
+ DBUG_ASSERT(snapshot != nullptr);
+
+ m_read_opts.snapshot = snapshot;
+ rdb->GetEnv()->GetCurrentTime(&m_snapshot_timestamp);
+ m_is_delayed_snapshot = false;
+ }
+
+ virtual void acquire_snapshot(bool acquire_now) = 0;
+ virtual void release_snapshot() = 0;
+
+ bool has_snapshot() const { return m_read_opts.snapshot != nullptr; }
+
+ private:
+ // The Rdb_sst_info structures we are currently loading. In a partitioned
+ // table this can have more than one entry
+ std::vector<std::shared_ptr<Rdb_sst_info>> m_curr_bulk_load;
+ std::string m_curr_bulk_load_tablename;
+
+ /* External merge sorts for bulk load: key ID -> merge sort instance */
+ std::unordered_map<GL_INDEX_ID, Rdb_index_merge> m_key_merge;
+
+ public:
+ int get_key_merge(GL_INDEX_ID kd_gl_id, rocksdb::ColumnFamilyHandle *cf,
+ Rdb_index_merge **key_merge) {
+ int res;
+ auto it = m_key_merge.find(kd_gl_id);
+ if (it == m_key_merge.end()) {
+ m_key_merge.emplace(
+ std::piecewise_construct, std::make_tuple(kd_gl_id),
+ std::make_tuple(
+ get_rocksdb_tmpdir(), THDVAR(get_thd(), merge_buf_size),
+ THDVAR(get_thd(), merge_combine_read_size),
+ THDVAR(get_thd(), merge_tmp_file_removal_delay_ms), cf));
+ it = m_key_merge.find(kd_gl_id);
+ if ((res = it->second.init()) != 0) {
+ return res;
+ }
+ }
+ *key_merge = &it->second;
+ return HA_EXIT_SUCCESS;
+ }
+
+ /* Finish bulk loading for all table handlers belongs to one connection */
+ int finish_bulk_load(bool *is_critical_error = nullptr,
+ int print_client_error = true) {
+ Ensure_cleanup cleanup([&]() {
+ // Always clear everything regardless of success/failure
+ m_curr_bulk_load.clear();
+ m_curr_bulk_load_tablename.clear();
+ m_key_merge.clear();
+ });
+
+ int rc = 0;
+ if (is_critical_error) {
+ *is_critical_error = true;
+ }
+
+ // PREPARE phase: finish all on-going bulk loading Rdb_sst_info and
+ // collect all Rdb_sst_commit_info containing (SST files, cf)
+ int rc2 = 0;
+ std::vector<Rdb_sst_info::Rdb_sst_commit_info> sst_commit_list;
+ sst_commit_list.reserve(m_curr_bulk_load.size());
+
+ for (auto &sst_info : m_curr_bulk_load) {
+ Rdb_sst_info::Rdb_sst_commit_info commit_info;
+
+ // Commit the list of SST files and move it to the end of
+ // sst_commit_list, effectively transfer the ownership over
+ rc2 = sst_info->finish(&commit_info, print_client_error);
+ if (rc2 && rc == 0) {
+ // Don't return yet - make sure we finish all the SST infos
+ rc = rc2;
+ }
+
+ // Make sure we have work to do - we might be losing the race
+ if (rc2 == 0 && commit_info.has_work()) {
+ sst_commit_list.emplace_back(std::move(commit_info));
+ DBUG_ASSERT(!commit_info.has_work());
+ }
+ }
+
+ if (rc) {
+ return rc;
+ }
+
+ // MERGING Phase: Flush the index_merge sort buffers into SST files in
+ // Rdb_sst_info and collect all Rdb_sst_commit_info containing
+ // (SST files, cf)
+ if (!m_key_merge.empty()) {
+ Ensure_cleanup malloc_cleanup([]() {
+ /*
+ Explicitly tell jemalloc to clean up any unused dirty pages at this
+ point.
+ See https://reviews.facebook.net/D63723 for more details.
+ */
+ purge_all_jemalloc_arenas();
+ });
+
+ rocksdb::Slice merge_key;
+ rocksdb::Slice merge_val;
+ for (auto it = m_key_merge.begin(); it != m_key_merge.end(); it++) {
+ GL_INDEX_ID index_id = it->first;
+ std::shared_ptr<const Rdb_key_def> keydef =
+ ddl_manager.safe_find(index_id);
+ std::string table_name = ddl_manager.safe_get_table_name(index_id);
+
+ // Unable to find key definition or table name since the
+ // table could have been dropped.
+ // TODO(herman): there is a race here between dropping the table
+ // and detecting a drop here. If the table is dropped while bulk
+ // loading is finishing, these keys being added here may
+ // be missed by the compaction filter and not be marked for
+ // removal. It is unclear how to lock the sql table from the storage
+ // engine to prevent modifications to it while bulk load is occurring.
+ if (keydef == nullptr) {
+ if (is_critical_error) {
+ // We used to set the error but simply ignores it. This follows
+ // current behavior and we should revisit this later
+ *is_critical_error = false;
+ }
+ return HA_ERR_KEY_NOT_FOUND;
+ } else if (table_name.empty()) {
+ if (is_critical_error) {
+ // We used to set the error but simply ignores it. This follows
+ // current behavior and we should revisit this later
+ *is_critical_error = false;
+ }
+ return HA_ERR_NO_SUCH_TABLE;
+ }
+ const std::string &index_name = keydef->get_name();
+ Rdb_index_merge &rdb_merge = it->second;
+
+ // Rdb_sst_info expects a denormalized table name in the form of
+ // "./database/table"
+ std::replace(table_name.begin(), table_name.end(), '.', '/');
+ table_name = "./" + table_name;
+ auto sst_info = std::make_shared<Rdb_sst_info>(
+ rdb, table_name, index_name, rdb_merge.get_cf(),
+ *rocksdb_db_options, THDVAR(get_thd(), trace_sst_api));
+
+ while ((rc2 = rdb_merge.next(&merge_key, &merge_val)) == 0) {
+ if ((rc2 = sst_info->put(merge_key, merge_val)) != 0) {
+ rc = rc2;
+
+ // Don't return yet - make sure we finish the sst_info
+ break;
+ }
+ }
+
+ // -1 => no more items
+ if (rc2 != -1 && rc != 0) {
+ rc = rc2;
+ }
+
+ Rdb_sst_info::Rdb_sst_commit_info commit_info;
+ rc2 = sst_info->finish(&commit_info, print_client_error);
+ if (rc2 != 0 && rc == 0) {
+ // Only set the error from sst_info->finish if finish failed and we
+ // didn't fail before. In other words, we don't have finish's
+ // success mask earlier failures
+ rc = rc2;
+ }
+
+ if (rc) {
+ return rc;
+ }
+
+ if (commit_info.has_work()) {
+ sst_commit_list.emplace_back(std::move(commit_info));
+ DBUG_ASSERT(!commit_info.has_work());
+ }
+ }
+ }
+
+ // Early return in case we lost the race completely and end up with no
+ // work at all
+ if (sst_commit_list.size() == 0) {
+ return rc;
+ }
+
+ // INGEST phase: Group all Rdb_sst_commit_info by cf (as they might
+ // have the same cf across different indexes) and call out to RocksDB
+ // to ingest all SST files in one atomic operation
+ rocksdb::IngestExternalFileOptions options;
+ options.move_files = true;
+ options.snapshot_consistency = false;
+ options.allow_global_seqno = false;
+ options.allow_blocking_flush = false;
+
+ std::map<rocksdb::ColumnFamilyHandle *, rocksdb::IngestExternalFileArg>
+ arg_map;
+
+ // Group by column_family
+ for (auto &commit_info : sst_commit_list) {
+ if (arg_map.find(commit_info.get_cf()) == arg_map.end()) {
+ rocksdb::IngestExternalFileArg arg;
+ arg.column_family = commit_info.get_cf(),
+ arg.external_files = commit_info.get_committed_files(),
+ arg.options = options;
+
+ arg_map.emplace(commit_info.get_cf(), arg);
+ } else {
+ auto &files = arg_map[commit_info.get_cf()].external_files;
+ files.insert(files.end(), commit_info.get_committed_files().begin(),
+ commit_info.get_committed_files().end());
+ }
+ }
+
+ std::vector<rocksdb::IngestExternalFileArg> args;
+ size_t file_count = 0;
+ for (auto &cf_files_pair : arg_map) {
+ args.push_back(cf_files_pair.second);
+ file_count += cf_files_pair.second.external_files.size();
+ }
+
+ const rocksdb::Status s = rdb->IngestExternalFiles(args);
+ if (THDVAR(m_thd, trace_sst_api)) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "SST Tracing: IngestExternalFile '%zu' files returned %s", file_count,
+ s.ok() ? "ok" : "not ok");
+ }
+
+ if (!s.ok()) {
+ if (print_client_error) {
+ Rdb_sst_info::report_error_msg(s, nullptr);
+ }
+ return HA_ERR_ROCKSDB_BULK_LOAD;
+ }
+
+ // COMMIT phase: mark everything as completed. This avoids SST file
+ // deletion kicking in. Otherwise SST files would get deleted if this
+ // entire operation is aborted
+ for (auto &commit_info : sst_commit_list) {
+ commit_info.commit();
+ }
+
+ return rc;
+ }
+
+ int start_bulk_load(ha_rocksdb *const bulk_load,
+ std::shared_ptr<Rdb_sst_info> sst_info) {
+ /*
+ If we already have an open bulk load of a table and the name doesn't
+ match the current one, close out the currently running one. This allows
+ multiple bulk loads to occur on a partitioned table, but then closes
+ them all out when we switch to another table.
+ */
+ DBUG_ASSERT(bulk_load != nullptr);
+
+ if (!m_curr_bulk_load.empty() &&
+ bulk_load->get_table_basename() != m_curr_bulk_load_tablename) {
+ const auto res = finish_bulk_load();
+ if (res != HA_EXIT_SUCCESS) {
+ return res;
+ }
+ }
+
+ /*
+ This used to track ha_rocksdb handler objects, but those can be
+ freed by the table cache while this was referencing them. Instead
+ of tracking ha_rocksdb handler objects, this now tracks the
+ Rdb_sst_info allocated, and both the ha_rocksdb handler and the
+ Rdb_transaction both have shared pointers to them.
+
+ On transaction complete, it will commit each Rdb_sst_info structure found.
+ If the ha_rocksdb object is freed, etc., it will also commit
+ the Rdb_sst_info. The Rdb_sst_info commit path needs to be idempotent.
+ */
+ m_curr_bulk_load.push_back(sst_info);
+ m_curr_bulk_load_tablename = bulk_load->get_table_basename();
+ return HA_EXIT_SUCCESS;
+ }
+
+ int num_ongoing_bulk_load() const { return m_curr_bulk_load.size(); }
+
+ const char *get_rocksdb_tmpdir() const {
+ const char *tmp_dir = THDVAR(get_thd(), tmpdir);
+
+ /*
+ We want to treat an empty string as nullptr, in these cases DDL operations
+ will use the default --tmpdir passed to mysql instead.
+ */
+ if (tmp_dir != nullptr && *tmp_dir == '\0') {
+ tmp_dir = nullptr;
+ }
+ return (tmp_dir);
+ }
+
+ /*
+ Flush the data accumulated so far. This assumes we're doing a bulk insert.
+
+ @detail
+ This should work like transaction commit, except that we don't
+ synchronize with the binlog (there is no API that would allow to have
+ binlog flush the changes accumulated so far and return its current
+ position)
+
+ @todo
+ Add test coverage for what happens when somebody attempts to do bulk
+ inserts while inside a multi-statement transaction.
+ */
+ bool flush_batch() {
+ if (get_write_count() == 0) return false;
+
+ /* Commit the current transaction */
+ if (commit_no_binlog()) return true;
+
+ /* Start another one */
+ start_tx();
+ return false;
+ }
+
+ void set_auto_incr(const GL_INDEX_ID &gl_index_id, ulonglong curr_id) {
+ m_auto_incr_map[gl_index_id] =
+ std::max(m_auto_incr_map[gl_index_id], curr_id);
+ }
+
+#ifndef DBUG_OFF
+ ulonglong get_auto_incr(const GL_INDEX_ID &gl_index_id) {
+ if (m_auto_incr_map.count(gl_index_id) > 0) {
+ return m_auto_incr_map[gl_index_id];
+ }
+ return 0;
+ }
+#endif
+
+ virtual rocksdb::Status put(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ const rocksdb::Slice &value,
+ const bool assume_tracked) = 0;
+ virtual rocksdb::Status delete_key(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const bool assume_tracked) = 0;
+ virtual rocksdb::Status single_delete(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const bool assume_tracked) = 0;
+
+ virtual bool has_modifications() const = 0;
+
+ virtual rocksdb::WriteBatchBase *get_indexed_write_batch() = 0;
+ /*
+ Return a WriteBatch that one can write to. The writes will skip any
+ transaction locking. The writes will NOT be visible to the transaction.
+ */
+ rocksdb::WriteBatchBase *get_blind_write_batch() {
+ return get_indexed_write_batch()->GetWriteBatch();
+ }
+
+ virtual rocksdb::Status get(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ rocksdb::PinnableSlice *const value) const = 0;
+ virtual rocksdb::Status get_for_update(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, rocksdb::PinnableSlice *const value,
+ bool exclusive, const bool do_validate) = 0;
+
+ rocksdb::Iterator *get_iterator(
+ rocksdb::ColumnFamilyHandle *const column_family, bool skip_bloom_filter,
+ bool fill_cache, const rocksdb::Slice &eq_cond_lower_bound,
+ const rocksdb::Slice &eq_cond_upper_bound, bool read_current = false,
+ bool create_snapshot = true) {
+ // Make sure we are not doing both read_current (which implies we don't
+ // want a snapshot) and create_snapshot which makes sure we create
+ // a snapshot
+ DBUG_ASSERT(column_family != nullptr);
+ DBUG_ASSERT(!read_current || !create_snapshot);
+
+ if (create_snapshot) acquire_snapshot(true);
+
+ rocksdb::ReadOptions options = m_read_opts;
+
+ if (skip_bloom_filter) {
+ options.total_order_seek = true;
+ options.iterate_lower_bound = &eq_cond_lower_bound;
+ options.iterate_upper_bound = &eq_cond_upper_bound;
+ } else {
+ // With this option, Iterator::Valid() returns false if key
+ // is outside of the prefix bloom filter range set at Seek().
+ // Must not be set to true if not using bloom filter.
+ options.prefix_same_as_start = true;
+ }
+ options.fill_cache = fill_cache;
+ if (read_current) {
+ options.snapshot = nullptr;
+ }
+ return get_iterator(options, column_family);
+ }
+
+ virtual bool is_tx_started() const = 0;
+ virtual void start_tx() = 0;
+ virtual void start_stmt() = 0;
+
+ protected:
+ // Non-virtual functions with actions to be done on transaction start and
+ // commit.
+ void on_commit() {
+ time_t tm;
+ tm = time(nullptr);
+ for (auto &it : modified_tables) {
+ it->m_update_time = tm;
+ }
+ modified_tables.clear();
+ }
+ void on_rollback() {
+ modified_tables.clear();
+ }
+ public:
+ // Inform the transaction that this table was modified
+ void log_table_write_op(Rdb_tbl_def *tbl) {
+ modified_tables.insert(tbl);
+ }
+
+ void set_initial_savepoint() {
+ /*
+ Set the initial savepoint. If the first statement in the transaction
+ fails, we need something to roll back to, without rolling back the
+ entire transaction.
+ */
+ do_set_savepoint();
+ m_writes_at_last_savepoint = m_write_count;
+ }
+
+ /*
+ Called when a "top-level" statement inside a transaction completes
+ successfully and its changes become part of the transaction's changes.
+ */
+ int make_stmt_savepoint_permanent() {
+ // Take another RocksDB savepoint only if we had changes since the last
+ // one. This is very important for long transactions doing lots of
+ // SELECTs.
+ if (m_writes_at_last_savepoint != m_write_count) {
+ rocksdb::WriteBatchBase *batch = get_write_batch();
+ rocksdb::Status status = rocksdb::Status::NotFound();
+ while ((status = batch->PopSavePoint()) == rocksdb::Status::OK()) {
+ }
+
+ if (status != rocksdb::Status::NotFound()) {
+ return HA_EXIT_FAILURE;
+ }
+
+ do_set_savepoint();
+ m_writes_at_last_savepoint = m_write_count;
+ }
+
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ Rollback to the savepoint we've set before the last statement
+ */
+ void rollback_to_stmt_savepoint() {
+ if (m_writes_at_last_savepoint != m_write_count) {
+ do_rollback_to_savepoint();
+ /*
+ RollbackToSavePoint "removes the most recent SetSavePoint()", so
+ we need to set it again so that next statement can roll back to this
+ stage.
+ It's ok to do it here at statement end (instead of doing it at next
+ statement start) because setting a savepoint is cheap.
+ */
+ do_set_savepoint();
+ m_writes_at_last_savepoint = m_write_count;
+ }
+ }
+
+ virtual void rollback_stmt() = 0;
+
+ void set_tx_failed(bool failed_arg) { m_is_tx_failed = failed_arg; }
+
+ bool can_prepare() const {
+ if (m_rollback_only) {
+ my_error(ER_ROLLBACK_ONLY, MYF(0));
+ return false;
+ }
+ return true;
+ }
+
+ int rollback_to_savepoint(void *const savepoint) {
+ if (has_modifications()) {
+ my_error(ER_ROLLBACK_TO_SAVEPOINT, MYF(0));
+ m_rollback_only = true;
+ return HA_EXIT_FAILURE;
+ }
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ This is used by transactions started with "START TRANSACTION WITH "
+ "CONSISTENT [ROCKSDB] SNAPSHOT". When tx_read_only is turned on,
+ snapshot has to be created via DB::GetSnapshot(), not via Transaction
+ API.
+ */
+ bool is_tx_read_only() const { return m_tx_read_only; }
+
+ bool is_two_phase() const { return m_is_two_phase; }
+
+ void set_tx_read_only(bool val) { m_tx_read_only = val; }
+
+ explicit Rdb_transaction(THD *const thd)
+ : m_thd(thd), m_tbl_io_perf(nullptr) {
+ RDB_MUTEX_LOCK_CHECK(s_tx_list_mutex);
+ s_tx_list.insert(this);
+ RDB_MUTEX_UNLOCK_CHECK(s_tx_list_mutex);
+ }
+
+ virtual ~Rdb_transaction() {
+ RDB_MUTEX_LOCK_CHECK(s_tx_list_mutex);
+ s_tx_list.erase(this);
+ RDB_MUTEX_UNLOCK_CHECK(s_tx_list_mutex);
+ }
+ virtual bool is_prepared() { return false; };
+ virtual void detach_prepared_tx() {};
+};
+
+/*
+ This is a rocksdb transaction. Its members represent the current transaction,
+ which consists of:
+ - the snapshot
+ - the changes we've made but are not seeing yet.
+
+ The changes are made to individual tables, which store them here and then
+ this object commits them on commit.
+*/
+class Rdb_transaction_impl : public Rdb_transaction {
+ rocksdb::Transaction *m_rocksdb_tx = nullptr;
+ rocksdb::Transaction *m_rocksdb_reuse_tx = nullptr;
+
+ public:
+ void set_lock_timeout(int timeout_sec_arg) override {
+ if (m_rocksdb_tx) {
+ m_rocksdb_tx->SetLockTimeout(rdb_convert_sec_to_ms(m_timeout_sec));
+ }
+ }
+
+ void set_sync(bool sync) override {
+ if (m_rocksdb_tx)
+ m_rocksdb_tx->GetWriteOptions()->sync = sync;
+ }
+
+ void release_lock(rocksdb::ColumnFamilyHandle *const column_family,
+ const std::string &rowkey) override {
+ if (!THDVAR(m_thd, lock_scanned_rows)) {
+ m_rocksdb_tx->UndoGetForUpdate(column_family, rocksdb::Slice(rowkey));
+ }
+ }
+
+ virtual bool is_writebatch_trx() const override { return false; }
+
+ bool is_prepared() override {
+ return m_rocksdb_tx && rocksdb::Transaction::PREPARED == m_rocksdb_tx->GetState();
+ }
+
+ void detach_prepared_tx() override {
+ DBUG_ASSERT(rocksdb::Transaction::PREPARED == m_rocksdb_tx->GetState());
+ m_rocksdb_tx = nullptr;
+ }
+
+private:
+ void release_tx(void) {
+ // We are done with the current active transaction object. Preserve it
+ // for later reuse.
+ DBUG_ASSERT(m_rocksdb_reuse_tx == nullptr);
+ m_rocksdb_reuse_tx = m_rocksdb_tx;
+ m_rocksdb_tx = nullptr;
+ }
+
+ bool prepare(const rocksdb::TransactionName &name) override {
+ rocksdb::Status s;
+ s = m_rocksdb_tx->SetName(name);
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ return false;
+ }
+
+ s = merge_auto_incr_map(m_rocksdb_tx->GetWriteBatch()->GetWriteBatch());
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ return false;
+ }
+
+ s = m_rocksdb_tx->Prepare();
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ return false;
+ }
+ return true;
+ }
+
+ bool commit_no_binlog() override {
+ bool res = false;
+ rocksdb::Status s;
+
+ s = merge_auto_incr_map(m_rocksdb_tx->GetWriteBatch()->GetWriteBatch());
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ res = true;
+ goto error;
+ }
+
+ release_snapshot();
+ s = m_rocksdb_tx->Commit();
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ res = true;
+ goto error;
+ }
+
+ on_commit();
+ error:
+ on_rollback();
+ /* Save the transaction object to be reused */
+ release_tx();
+
+ m_write_count = 0;
+ m_insert_count = 0;
+ m_update_count = 0;
+ m_delete_count = 0;
+ m_lock_count = 0;
+ set_tx_read_only(false);
+ m_rollback_only = false;
+ return res;
+ }
+
+ public:
+ void rollback() override {
+ on_rollback();
+ m_write_count = 0;
+ m_insert_count = 0;
+ m_update_count = 0;
+ m_delete_count = 0;
+ m_lock_count = 0;
+ m_auto_incr_map.clear();
+ m_ddl_transaction = false;
+ if (m_rocksdb_tx) {
+ release_snapshot();
+ /* This will also release all of the locks: */
+ m_rocksdb_tx->Rollback();
+
+ /* Save the transaction object to be reused */
+ release_tx();
+
+ set_tx_read_only(false);
+ m_rollback_only = false;
+ }
+ }
+
+ void acquire_snapshot(bool acquire_now) override {
+ if (m_read_opts.snapshot == nullptr) {
+#ifdef MARIAROCKS_NOT_YET
+ const auto thd_ss = std::static_pointer_cast<Rdb_explicit_snapshot>(
+ m_thd->get_explicit_snapshot());
+ if (thd_ss) {
+ m_explicit_snapshot = thd_ss;
+ }
+ if (m_explicit_snapshot) {
+ auto snapshot = m_explicit_snapshot->get_snapshot()->snapshot();
+ snapshot_created(snapshot);
+ } else
+#endif
+ if (is_tx_read_only()) {
+ snapshot_created(rdb->GetSnapshot());
+ } else if (acquire_now) {
+ m_rocksdb_tx->SetSnapshot();
+ snapshot_created(m_rocksdb_tx->GetSnapshot());
+ } else if (!m_is_delayed_snapshot) {
+ m_rocksdb_tx->SetSnapshotOnNextOperation(m_notifier);
+ m_is_delayed_snapshot = true;
+ }
+ }
+ }
+
+ void release_snapshot() override {
+ bool need_clear = m_is_delayed_snapshot;
+
+ if (m_read_opts.snapshot != nullptr) {
+ m_snapshot_timestamp = 0;
+#ifdef MARIAROCKS_NOT_YET
+ if (m_explicit_snapshot) {
+ m_explicit_snapshot.reset();
+ need_clear = false;
+ } else
+#endif
+ if (is_tx_read_only()) {
+ rdb->ReleaseSnapshot(m_read_opts.snapshot);
+ need_clear = false;
+ } else {
+ need_clear = true;
+ }
+ m_read_opts.snapshot = nullptr;
+ }
+
+ if (need_clear && m_rocksdb_tx != nullptr) m_rocksdb_tx->ClearSnapshot();
+ }
+
+ bool has_snapshot() { return m_read_opts.snapshot != nullptr; }
+
+ rocksdb::Status put(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const rocksdb::Slice &value,
+ const bool assume_tracked) override {
+ ++m_write_count;
+ ++m_lock_count;
+ if (m_write_count > m_max_row_locks || m_lock_count > m_max_row_locks) {
+ return rocksdb::Status::Aborted(rocksdb::Status::kLockLimit);
+ }
+ return m_rocksdb_tx->Put(column_family, key, value, assume_tracked);
+ }
+
+ rocksdb::Status delete_key(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ const bool assume_tracked) override {
+ ++m_write_count;
+ ++m_lock_count;
+ if (m_write_count > m_max_row_locks || m_lock_count > m_max_row_locks) {
+ return rocksdb::Status::Aborted(rocksdb::Status::kLockLimit);
+ }
+ return m_rocksdb_tx->Delete(column_family, key, assume_tracked);
+ }
+
+ rocksdb::Status single_delete(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const bool assume_tracked) override {
+ ++m_write_count;
+ ++m_lock_count;
+ if (m_write_count > m_max_row_locks || m_lock_count > m_max_row_locks) {
+ return rocksdb::Status::Aborted(rocksdb::Status::kLockLimit);
+ }
+ return m_rocksdb_tx->SingleDelete(column_family, key, assume_tracked);
+ }
+
+ bool has_modifications() const override {
+ return m_rocksdb_tx->GetWriteBatch() &&
+ m_rocksdb_tx->GetWriteBatch()->GetWriteBatch() &&
+ m_rocksdb_tx->GetWriteBatch()->GetWriteBatch()->Count() > 0;
+ }
+
+ rocksdb::WriteBatchBase *get_write_batch() override {
+ if (is_two_phase()) {
+ return m_rocksdb_tx->GetCommitTimeWriteBatch();
+ }
+ return m_rocksdb_tx->GetWriteBatch()->GetWriteBatch();
+ }
+
+ /*
+ Return a WriteBatch that one can write to. The writes will skip any
+ transaction locking. The writes WILL be visible to the transaction.
+ */
+ rocksdb::WriteBatchBase *get_indexed_write_batch() override {
+ ++m_write_count;
+ return m_rocksdb_tx->GetWriteBatch();
+ }
+
+ rocksdb::Status get(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ rocksdb::PinnableSlice *const value) const override {
+ // clean PinnableSlice right begfore Get() for multiple gets per statement
+ // the resources after the last Get in a statement are cleared in
+ // handler::reset call
+ value->Reset();
+ global_stats.queries[QUERIES_POINT].inc();
+ return m_rocksdb_tx->Get(m_read_opts, column_family, key, value);
+ }
+
+ rocksdb::Status get_for_update(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, rocksdb::PinnableSlice *const value,
+ bool exclusive, const bool do_validate) override {
+ if (++m_lock_count > m_max_row_locks) {
+ return rocksdb::Status::Aborted(rocksdb::Status::kLockLimit);
+ }
+
+ if (value != nullptr) {
+ value->Reset();
+ }
+ rocksdb::Status s;
+ // If snapshot is null, pass it to GetForUpdate and snapshot is
+ // initialized there. Snapshot validation is skipped in that case.
+ if (m_read_opts.snapshot == nullptr || do_validate) {
+ s = m_rocksdb_tx->GetForUpdate(
+ m_read_opts, column_family, key, value, exclusive,
+ m_read_opts.snapshot ? do_validate : false);
+ } else {
+ // If snapshot is set, and if skipping validation,
+ // call GetForUpdate without validation and set back old snapshot
+ auto saved_snapshot = m_read_opts.snapshot;
+ m_read_opts.snapshot = nullptr;
+ s = m_rocksdb_tx->GetForUpdate(m_read_opts, column_family, key, value,
+ exclusive, false);
+ m_read_opts.snapshot = saved_snapshot;
+ }
+ return s;
+ }
+
+ rocksdb::Iterator *get_iterator(
+ const rocksdb::ReadOptions &options,
+ rocksdb::ColumnFamilyHandle *const column_family) override {
+ global_stats.queries[QUERIES_RANGE].inc();
+ return m_rocksdb_tx->GetIterator(options, column_family);
+ }
+
+ const rocksdb::Transaction *get_rdb_trx() const { return m_rocksdb_tx; }
+
+ bool is_tx_started() const override { return (m_rocksdb_tx != nullptr); }
+
+ void start_tx() override {
+ rocksdb::TransactionOptions tx_opts;
+ rocksdb::WriteOptions write_opts;
+ tx_opts.set_snapshot = false;
+ tx_opts.lock_timeout = rdb_convert_sec_to_ms(m_timeout_sec);
+ tx_opts.deadlock_detect = THDVAR(m_thd, deadlock_detect);
+ tx_opts.deadlock_detect_depth = THDVAR(m_thd, deadlock_detect_depth);
+ // If this variable is set, this will write commit time write batch
+ // information on recovery or memtable flush.
+ tx_opts.use_only_the_last_commit_time_batch_for_recovery =
+ THDVAR(m_thd, commit_time_batch_for_recovery);
+ tx_opts.max_write_batch_size = THDVAR(m_thd, write_batch_max_bytes);
+
+ write_opts.sync = (rocksdb_flush_log_at_trx_commit == FLUSH_LOG_SYNC);
+ write_opts.disableWAL = THDVAR(m_thd, write_disable_wal);
+ write_opts.ignore_missing_column_families =
+ THDVAR(m_thd, write_ignore_missing_column_families);
+ m_is_two_phase = rocksdb_enable_2pc;
+
+ commit_ordered_done= false;
+
+ /*
+ If m_rocksdb_reuse_tx is null this will create a new transaction object.
+ Otherwise it will reuse the existing one.
+ */
+ m_rocksdb_tx =
+ rdb->BeginTransaction(write_opts, tx_opts, m_rocksdb_reuse_tx);
+ m_rocksdb_reuse_tx = nullptr;
+
+ m_read_opts = rocksdb::ReadOptions();
+
+ set_initial_savepoint();
+
+ m_ddl_transaction = false;
+ }
+
+ /* Implementations of do_*savepoint based on rocksdB::Transaction savepoints
+ */
+ void do_set_savepoint() override { m_rocksdb_tx->SetSavePoint(); }
+
+ void do_rollback_to_savepoint() override {
+ m_rocksdb_tx->RollbackToSavePoint();
+ }
+
+ /*
+ Start a statement inside a multi-statement transaction.
+
+ @todo: are we sure this is called once (and not several times) per
+ statement start?
+
+ For hooking to start of statement that is its own transaction, see
+ ha_rocksdb::external_lock().
+ */
+ void start_stmt() override {
+ // Set the snapshot to delayed acquisition (SetSnapshotOnNextOperation)
+ acquire_snapshot(false);
+ }
+
+ /*
+ This must be called when last statement is rolled back, but the transaction
+ continues
+ */
+ void rollback_stmt() override {
+ /* TODO: here we must release the locks taken since the start_stmt() call */
+ if (m_rocksdb_tx) {
+ const rocksdb::Snapshot *const org_snapshot = m_rocksdb_tx->GetSnapshot();
+ rollback_to_stmt_savepoint();
+
+ const rocksdb::Snapshot *const cur_snapshot = m_rocksdb_tx->GetSnapshot();
+ if (org_snapshot != cur_snapshot) {
+ if (org_snapshot != nullptr) m_snapshot_timestamp = 0;
+
+ m_read_opts.snapshot = cur_snapshot;
+ if (cur_snapshot != nullptr) {
+ rdb->GetEnv()->GetCurrentTime(&m_snapshot_timestamp);
+ } else {
+ m_is_delayed_snapshot = true;
+ }
+ }
+ }
+ }
+
+ explicit Rdb_transaction_impl(THD *const thd)
+ : Rdb_transaction(thd), m_rocksdb_tx(nullptr) {
+ // Create a notifier that can be called when a snapshot gets generated.
+ m_notifier = std::make_shared<Rdb_snapshot_notifier>(this);
+ }
+
+ virtual ~Rdb_transaction_impl() override {
+ rollback();
+
+ // Theoretically the notifier could outlive the Rdb_transaction_impl
+ // (because of the shared_ptr), so let it know it can't reference
+ // the transaction anymore.
+ m_notifier->detach();
+
+ // Free any transaction memory that is still hanging around.
+ delete m_rocksdb_reuse_tx;
+ DBUG_ASSERT(m_rocksdb_tx == nullptr);
+ }
+};
+
+/* This is a rocksdb write batch. This class doesn't hold or wait on any
+ transaction locks (skips rocksdb transaction API) thus giving better
+ performance.
+
+ Currently this is only used for replication threads which are guaranteed
+ to be non-conflicting. Any further usage of this class should completely
+ be thought thoroughly.
+*/
+class Rdb_writebatch_impl : public Rdb_transaction {
+ rocksdb::WriteBatchWithIndex *m_batch;
+ rocksdb::WriteOptions write_opts;
+ // Called after commit/rollback.
+ void reset() {
+ m_batch->Clear();
+ m_read_opts = rocksdb::ReadOptions();
+ m_ddl_transaction = false;
+ }
+
+ private:
+ bool prepare(const rocksdb::TransactionName &name) override { return true; }
+
+ bool commit_no_binlog() override {
+ bool res = false;
+ rocksdb::Status s;
+ rocksdb::TransactionDBWriteOptimizations optimize;
+ optimize.skip_concurrency_control = true;
+
+ s = merge_auto_incr_map(m_batch->GetWriteBatch());
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ res = true;
+ goto error;
+ }
+
+ release_snapshot();
+
+ s = rdb->Write(write_opts, optimize, m_batch->GetWriteBatch());
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_TX_COMMIT);
+ res = true;
+ goto error;
+ }
+ on_commit();
+ error:
+ on_rollback();
+ reset();
+
+ m_write_count = 0;
+ m_insert_count = 0;
+ m_update_count = 0;
+ m_delete_count = 0;
+ set_tx_read_only(false);
+ m_rollback_only = false;
+ return res;
+ }
+
+ /* Implementations of do_*savepoint based on rocksdB::WriteBatch savepoints */
+ void do_set_savepoint() override { m_batch->SetSavePoint(); }
+
+ void do_rollback_to_savepoint() override { m_batch->RollbackToSavePoint(); }
+
+
+ public:
+ bool is_writebatch_trx() const override { return true; }
+
+ void set_lock_timeout(int timeout_sec_arg) override {
+ // Nothing to do here.
+ }
+
+ void set_sync(bool sync) override { write_opts.sync = sync; }
+
+ void release_lock(rocksdb::ColumnFamilyHandle *const column_family,
+ const std::string &rowkey) override {
+ // Nothing to do here since we don't hold any row locks.
+ }
+
+ void rollback() override {
+ on_rollback();
+ m_write_count = 0;
+ m_insert_count = 0;
+ m_update_count = 0;
+ m_delete_count = 0;
+ m_lock_count = 0;
+ release_snapshot();
+
+ reset();
+ set_tx_read_only(false);
+ m_rollback_only = false;
+ }
+
+ void acquire_snapshot(bool acquire_now) override {
+ if (m_read_opts.snapshot == nullptr) snapshot_created(rdb->GetSnapshot());
+ }
+
+ void release_snapshot() override {
+ if (m_read_opts.snapshot != nullptr) {
+ rdb->ReleaseSnapshot(m_read_opts.snapshot);
+ m_read_opts.snapshot = nullptr;
+ }
+ }
+
+ rocksdb::Status put(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const rocksdb::Slice &value,
+ const bool assume_tracked) override {
+ ++m_write_count;
+ m_batch->Put(column_family, key, value);
+ // Note Put/Delete in write batch doesn't return any error code. We simply
+ // return OK here.
+ return rocksdb::Status::OK();
+ }
+
+ rocksdb::Status delete_key(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ const bool assume_tracked) override {
+ ++m_write_count;
+ m_batch->Delete(column_family, key);
+ return rocksdb::Status::OK();
+ }
+
+ rocksdb::Status single_delete(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, const bool /* assume_tracked */) override {
+ ++m_write_count;
+ m_batch->SingleDelete(column_family, key);
+ return rocksdb::Status::OK();
+ }
+
+ bool has_modifications() const override {
+ return m_batch->GetWriteBatch()->Count() > 0;
+ }
+
+ rocksdb::WriteBatchBase *get_write_batch() override { return m_batch; }
+
+ rocksdb::WriteBatchBase *get_indexed_write_batch() override {
+ ++m_write_count;
+ return m_batch;
+ }
+
+ rocksdb::Status get(rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key,
+ rocksdb::PinnableSlice *const value) const override {
+ value->Reset();
+ return m_batch->GetFromBatchAndDB(rdb, m_read_opts, column_family, key,
+ value);
+ }
+
+ rocksdb::Status get_for_update(
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, rocksdb::PinnableSlice *const value,
+ bool /* exclusive */, const bool /* do_validate */) override {
+ if (value == nullptr) {
+ rocksdb::PinnableSlice pin_val;
+ rocksdb::Status s = get(column_family, key, &pin_val);
+ pin_val.Reset();
+ return s;
+ }
+
+ return get(column_family, key, value);
+ }
+
+ rocksdb::Iterator *get_iterator(
+ const rocksdb::ReadOptions &options,
+ rocksdb::ColumnFamilyHandle *const /* column_family */) override {
+ const auto it = rdb->NewIterator(options);
+ return m_batch->NewIteratorWithBase(it);
+ }
+
+ bool is_tx_started() const override { return (m_batch != nullptr); }
+
+ void start_tx() override {
+ commit_ordered_done= false; // Do we need this here?
+ reset();
+ write_opts.sync = (rocksdb_flush_log_at_trx_commit == FLUSH_LOG_SYNC);
+ write_opts.disableWAL = THDVAR(m_thd, write_disable_wal);
+ write_opts.ignore_missing_column_families =
+ THDVAR(m_thd, write_ignore_missing_column_families);
+
+ set_initial_savepoint();
+ }
+
+ void start_stmt() override {}
+
+ void rollback_stmt() override {
+ if (m_batch) rollback_to_stmt_savepoint();
+ }
+
+ explicit Rdb_writebatch_impl(THD *const thd)
+ : Rdb_transaction(thd), m_batch(nullptr) {
+ m_batch = new rocksdb::WriteBatchWithIndex(rocksdb::BytewiseComparator(), 0,
+ true);
+ }
+
+ virtual ~Rdb_writebatch_impl() override {
+ rollback();
+ delete m_batch;
+ }
+};
+
+void Rdb_snapshot_notifier::SnapshotCreated(
+ const rocksdb::Snapshot *const snapshot) {
+ if (m_owning_tx != nullptr) {
+ m_owning_tx->snapshot_created(snapshot);
+ }
+}
+
+std::multiset<Rdb_transaction *> Rdb_transaction::s_tx_list;
+mysql_mutex_t Rdb_transaction::s_tx_list_mutex;
+
+static Rdb_transaction *get_tx_from_thd(THD *const thd) {
+ return reinterpret_cast<Rdb_transaction *>(
+ my_core::thd_get_ha_data(thd, rocksdb_hton));
+}
+
+namespace {
+
+class Rdb_perf_context_guard {
+ Rdb_io_perf m_io_perf;
+ Rdb_io_perf *m_io_perf_ptr;
+ Rdb_transaction *m_tx;
+ uint m_level;
+
+ public:
+ Rdb_perf_context_guard(const Rdb_perf_context_guard &) = delete;
+ Rdb_perf_context_guard &operator=(const Rdb_perf_context_guard &) = delete;
+
+ explicit Rdb_perf_context_guard(Rdb_io_perf *io_perf, uint level)
+ : m_io_perf_ptr(io_perf), m_tx(nullptr), m_level(level) {
+ m_io_perf_ptr->start(m_level);
+ }
+
+ explicit Rdb_perf_context_guard(Rdb_transaction *tx, uint level)
+ : m_io_perf_ptr(nullptr), m_tx(tx), m_level(level) {
+ /*
+ if perf_context information is already being recorded, this becomes a
+ no-op
+ */
+ if (tx != nullptr) {
+ tx->io_perf_start(&m_io_perf);
+ }
+ }
+
+ ~Rdb_perf_context_guard() {
+ if (m_tx != nullptr) {
+ m_tx->io_perf_end_and_record();
+ } else if (m_io_perf_ptr != nullptr) {
+ m_io_perf_ptr->end_and_record(m_level);
+ }
+ }
+};
+
+} // anonymous namespace
+
+/*
+ TODO: maybe, call this in external_lock() and store in ha_rocksdb..
+*/
+
+static Rdb_transaction *get_or_create_tx(THD *const thd) {
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ // TODO: this is called too many times.. O(#rows)
+ if (tx == nullptr) {
+ bool rpl_skip_tx_api= false; // MARIAROCKS_NOT_YET.
+ if ((rpl_skip_tx_api && thd->rgi_slave) ||
+ (THDVAR(thd, master_skip_tx_api) && !thd->rgi_slave))
+ {
+ tx = new Rdb_writebatch_impl(thd);
+ } else {
+ tx = new Rdb_transaction_impl(thd);
+ }
+ tx->set_params(THDVAR(thd, lock_wait_timeout), THDVAR(thd, max_row_locks));
+ tx->start_tx();
+ my_core::thd_set_ha_data(thd, rocksdb_hton, tx);
+ } else {
+ tx->set_params(THDVAR(thd, lock_wait_timeout), THDVAR(thd, max_row_locks));
+ if (!tx->is_tx_started()) {
+ tx->start_tx();
+ }
+ }
+
+ return tx;
+}
+
+static int rocksdb_close_connection(handlerton *const hton, THD *const thd) {
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ if (tx != nullptr) {
+ bool is_critical_error;
+ int rc = tx->finish_bulk_load(&is_critical_error, false);
+ if (rc != 0 && is_critical_error) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Error %d finalizing last SST file while "
+ "disconnecting",
+ rc);
+ }
+ if (tx->is_prepared())
+ tx->detach_prepared_tx();
+ delete tx;
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ * Serializes an xid to a string so that it can
+ * be used as a rocksdb transaction name
+ */
+static std::string rdb_xid_to_string(const XID &src) {
+ DBUG_ASSERT(src.gtrid_length >= 0 && src.gtrid_length <= MAXGTRIDSIZE);
+ DBUG_ASSERT(src.bqual_length >= 0 && src.bqual_length <= MAXBQUALSIZE);
+
+ std::string buf;
+ buf.reserve(RDB_XIDHDR_LEN + src.gtrid_length + src.bqual_length);
+
+ /*
+ * expand formatID to fill 8 bytes if it doesn't already
+ * then reinterpret bit pattern as unsigned and store in network order
+ */
+ uchar fidbuf[RDB_FORMATID_SZ];
+ int64 signed_fid8 = src.formatID;
+ const uint64 raw_fid8 = *reinterpret_cast<uint64 *>(&signed_fid8);
+ rdb_netbuf_store_uint64(fidbuf, raw_fid8);
+ buf.append(reinterpret_cast<const char *>(fidbuf), RDB_FORMATID_SZ);
+
+ buf.push_back(src.gtrid_length);
+ buf.push_back(src.bqual_length);
+ buf.append(src.data, (src.gtrid_length) + (src.bqual_length));
+ return buf;
+}
+
+#if 0
+// MARIAROCKS: MariaDB doesn't have flush_wal method
+/**
+ Called by hton->flush_logs after MySQL group commit prepares a set of
+ transactions.
+*/
+static bool rocksdb_flush_wal(handlerton* hton __attribute__((__unused__)))
+ DBUG_ASSERT(rdb != nullptr);
+
+ rocksdb::Status s;
+ /*
+ target_lsn is set to 0 when MySQL wants to sync the wal files
+ */
+ if ((target_lsn == 0 && !rocksdb_db_options->allow_mmap_writes) ||
+ rocksdb_flush_log_at_trx_commit != FLUSH_LOG_NEVER) {
+ rocksdb_wal_group_syncs++;
+ s = rdb->FlushWAL(target_lsn == 0 ||
+ rocksdb_flush_log_at_trx_commit == FLUSH_LOG_SYNC);
+ }
+
+ if (!s.ok()) {
+ rdb_log_status_error(s);
+ return HA_EXIT_FAILURE;
+ }
+ return HA_EXIT_SUCCESS;
+}
+#endif
+
+/**
+ For a slave, prepare() updates the slave_gtid_info table which tracks the
+ replication progress.
+*/
+static int rocksdb_prepare(handlerton* hton, THD* thd, bool prepare_tx)
+{
+ bool async=false; // This is "ASYNC_COMMIT" feature which is only present in webscalesql
+
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ if (!tx->can_prepare()) {
+ return HA_EXIT_FAILURE;
+ }
+ if (prepare_tx ||
+ (!my_core::thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
+ /* We were instructed to prepare the whole transaction, or
+ this is an SQL statement end and autocommit is on */
+
+#ifdef MARIAROCKS_NOT_YET
+ /*
+ Storing binlog position inside MyRocks is needed only for restoring
+ MyRocks from backups. This feature is not supported yet.
+ */
+ std::vector<st_slave_gtid_info> slave_gtid_info;
+ my_core::thd_slave_gtid_info(thd, &slave_gtid_info);
+ for (const auto &it : slave_gtid_info) {
+ rocksdb::WriteBatchBase *const write_batch = tx->get_blind_write_batch();
+ binlog_manager.update_slave_gtid_info(it.id, it.db, it.gtid, write_batch);
+ }
+#endif
+
+ if (tx->is_two_phase()) {
+
+ /*
+ MariaDB: the following branch is never taken.
+ We always flush at Prepare and rely on RocksDB's internal Group Commit
+ to do some grouping.
+ */
+ if (thd->durability_property == HA_IGNORE_DURABILITY || async) {
+ tx->set_sync(false);
+ }
+
+ /*
+ MariaDB: do not flush logs if we are running in a non-crash-safe mode.
+ */
+ if (!rocksdb_flush_log_at_trx_commit)
+ tx->set_sync(false);
+
+ XID xid;
+ thd_get_xid(thd, reinterpret_cast<MYSQL_XID *>(&xid));
+ if (!tx->prepare(rdb_xid_to_string(xid))) {
+ return HA_EXIT_FAILURE;
+ }
+
+ /*
+ MariaDB: our Group Commit implementation does not use the
+ hton->flush_logs call (at least currently) so the following is not
+ needed (TODO: will we need this for binlog rotation?)
+ */
+#ifdef MARIAROCKS_NOT_YET
+ if (thd->durability_property == HA_IGNORE_DURABILITY )
+ (rocksdb_flush_log_at_trx_commit != FLUSH_LOG_NEVER))
+ &&
+ THDVAR(thd, flush_log_at_trx_commit))
+#endif
+#ifdef MARIAROCKS_NOT_YET
+ {
+ // MariaRocks: disable the
+ // "write/sync redo log before flushing binlog cache to file"
+ // feature. See a869c56d361bb44f46c0efeb11a8f03561676247
+ /**
+ we set the log sequence as '1' just to trigger hton->flush_logs
+ */
+ thd_store_lsn(thd, 1, DB_TYPE_ROCKSDB);
+ }
+#endif
+ }
+
+ DEBUG_SYNC(thd, "rocksdb.prepared");
+ } else {
+ tx->make_stmt_savepoint_permanent();
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ do nothing for prepare/commit by xid
+ this is needed to avoid crashes in XA scenarios
+*/
+static int rocksdb_commit_by_xid(handlerton *const hton, XID *const xid) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(hton != nullptr);
+ DBUG_ASSERT(xid != nullptr);
+ DBUG_ASSERT(commit_latency_stats != nullptr);
+
+ rocksdb::StopWatchNano timer(rocksdb::Env::Default(), true);
+
+ const auto name = rdb_xid_to_string(*xid);
+ DBUG_ASSERT(!name.empty());
+
+ rocksdb::Transaction *const trx = rdb->GetTransactionByName(name);
+
+ if (trx == nullptr) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ const rocksdb::Status s = trx->Commit();
+
+ if (!s.ok()) {
+ rdb_log_status_error(s);
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ delete trx;
+
+ // `Add()` is implemented in a thread-safe manner.
+ commit_latency_stats->Add(timer.ElapsedNanos() / 1000);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+static int rocksdb_rollback_by_xid(
+ handlerton *const hton MY_ATTRIBUTE((__unused__)), XID *const xid) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(hton != nullptr);
+ DBUG_ASSERT(xid != nullptr);
+ DBUG_ASSERT(rdb != nullptr);
+
+ const auto name = rdb_xid_to_string(*xid);
+
+ rocksdb::Transaction *const trx = rdb->GetTransactionByName(name);
+
+ if (trx == nullptr) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ const rocksdb::Status s = trx->Rollback();
+
+ if (!s.ok()) {
+ rdb_log_status_error(s);
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ delete trx;
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ Rebuilds an XID from a serialized version stored in a string.
+*/
+static void rdb_xid_from_string(const std::string &src, XID *const dst) {
+ DBUG_ASSERT(dst != nullptr);
+ uint offset = 0;
+ uint64 raw_fid8 =
+ rdb_netbuf_to_uint64(reinterpret_cast<const uchar *>(src.data()));
+ const int64 signed_fid8 = *reinterpret_cast<int64 *>(&raw_fid8);
+ dst->formatID = signed_fid8;
+ offset += RDB_FORMATID_SZ;
+ dst->gtrid_length = src.at(offset);
+ offset += RDB_GTRID_SZ;
+ dst->bqual_length = src.at(offset);
+ offset += RDB_BQUAL_SZ;
+
+ DBUG_ASSERT(dst->gtrid_length >= 0 && dst->gtrid_length <= MAXGTRIDSIZE);
+ DBUG_ASSERT(dst->bqual_length >= 0 && dst->bqual_length <= MAXBQUALSIZE);
+
+ memset(dst->data, 0, XIDDATASIZE);
+ src.copy(dst->data, (dst->gtrid_length) + (dst->bqual_length),
+ RDB_XIDHDR_LEN);
+}
+
+/**
+ Reading last committed binary log info from RocksDB system row.
+ The info is needed for crash safe slave/master to work.
+*/
+static int rocksdb_recover(handlerton* hton, XID* xid_list, uint len)
+#ifdef MARIAROCKS_NOT_YET
+ char* const binlog_file,
+ my_off_t *const binlog_pos,
+ Gtid *const binlog_max_gtid) {
+#endif
+{
+#ifdef MARIAROCKS_NOT_YET
+ if (binlog_file && binlog_pos) {
+ char file_buf[FN_REFLEN + 1] = {0};
+ my_off_t pos;
+ char gtid_buf[FN_REFLEN + 1] = {0};
+ if (binlog_manager.read(file_buf, &pos, gtid_buf)) {
+ if (is_binlog_advanced(binlog_file, *binlog_pos, file_buf, pos)) {
+ memcpy(binlog_file, file_buf, FN_REFLEN + 1);
+ *binlog_pos = pos;
+ // NO_LINT_DEBUG
+ fprintf(stderr,
+ "RocksDB: Last binlog file position %llu,"
+ " file name %s\n",
+ pos, file_buf);
+ if (*gtid_buf) {
+ global_sid_lock->rdlock();
+ binlog_max_gtid->parse(global_sid_map, gtid_buf);
+ global_sid_lock->unlock();
+ // NO_LINT_DEBUG
+ fprintf(stderr, "RocksDB: Last MySQL Gtid %s\n", gtid_buf);
+ }
+ }
+ }
+ }
+#endif
+
+ if (len == 0 || xid_list == nullptr) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ std::vector<rocksdb::Transaction *> trans_list;
+ rdb->GetAllPreparedTransactions(&trans_list);
+
+ uint count = 0;
+ for (auto &trans : trans_list) {
+ if (count >= len) {
+ break;
+ }
+ auto name = trans->GetName();
+ rdb_xid_from_string(name, &xid_list[count]);
+ count++;
+ }
+ return count;
+}
+
+
+/*
+ Handle a commit checkpoint request from server layer.
+
+ InnoDB does this:
+ We put the request in a queue, so that we can notify upper layer about
+ checkpoint complete when we have flushed the redo log.
+ If we have already flushed all relevant redo log, we notify immediately.
+
+ MariaRocks just flushes everything right away ATM
+*/
+
+static void rocksdb_checkpoint_request(void *cookie)
+{
+ const rocksdb::Status s= rdb->FlushWAL(true);
+ //TODO: what to do on error?
+ if (s.ok())
+ {
+ rocksdb_wal_group_syncs++;
+ commit_checkpoint_notify_ha(cookie);
+ }
+}
+
+/*
+ @param all: TRUE - commit the transaction
+ FALSE - SQL statement ended
+*/
+static void rocksdb_commit_ordered(handlerton *hton, THD* thd, bool all)
+{
+ // Same assert as InnoDB has
+ DBUG_ASSERT(all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT |
+ OPTION_BEGIN)));
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ if (!tx->is_two_phase()) {
+ /*
+ ordered_commit is supposedly slower as it is done sequentially
+ in order to preserve commit order.
+
+ if we are not required do 2-phase commit with the binlog, do not do
+ anything here.
+ */
+ return;
+ }
+
+ tx->set_sync(false);
+
+ /* This will note the master position also */
+ tx->commit_ordered_res= tx->commit();
+ tx->commit_ordered_done= true;
+
+}
+
+
+static int rocksdb_commit(handlerton* hton, THD* thd, bool commit_tx)
+{
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(hton != nullptr);
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(commit_latency_stats != nullptr);
+
+ rocksdb::StopWatchNano timer(rocksdb::Env::Default(), true);
+
+ /* note: h->external_lock(F_UNLCK) is called after this function is called) */
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+
+ /* this will trigger saving of perf_context information */
+ Rdb_perf_context_guard guard(tx, rocksdb_perf_context_level(thd));
+
+ if (tx != nullptr) {
+ if (commit_tx || (!my_core::thd_test_options(
+ thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
+ /*
+ This will not add anything to commit_latency_stats, and this is correct
+ right?
+ */
+ if (tx->commit_ordered_done)
+ {
+ thd_wakeup_subsequent_commits(thd, 0);
+ DBUG_RETURN((tx->commit_ordered_res? HA_ERR_INTERNAL_ERROR: 0));
+ }
+
+ /*
+ We get here
+ - For a COMMIT statement that finishes a multi-statement transaction
+ - For a statement that has its own transaction
+ */
+ if (thd->slave_thread)
+ {
+ // An attempt to make parallel slave performant (not fully successful,
+ // see MDEV-15372):
+
+ // First, commit without syncing. This establishes the commit order
+ tx->set_sync(false);
+ bool tx_had_writes = tx->get_write_count()? true : false ;
+ if (tx->commit()) {
+ DBUG_RETURN(HA_ERR_ROCKSDB_COMMIT_FAILED);
+ }
+ thd_wakeup_subsequent_commits(thd, 0);
+
+ if (tx_had_writes && rocksdb_flush_log_at_trx_commit == FLUSH_LOG_SYNC)
+ {
+ rocksdb::Status s= rdb->FlushWAL(true);
+ if (!s.ok())
+ DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
+ }
+ }
+ else
+ {
+ /* Not a slave thread */
+ if (tx->commit()) {
+ DBUG_RETURN(HA_ERR_ROCKSDB_COMMIT_FAILED);
+ }
+ }
+ } else {
+ /*
+ We get here when committing a statement within a transaction.
+ */
+ tx->make_stmt_savepoint_permanent();
+ }
+
+ if (my_core::thd_tx_isolation(thd) <= ISO_READ_COMMITTED) {
+ // For READ_COMMITTED, we release any existing snapshot so that we will
+ // see any changes that occurred since the last statement.
+ tx->release_snapshot();
+ }
+ }
+
+ // `Add()` is implemented in a thread-safe manner.
+ commit_latency_stats->Add(timer.ElapsedNanos() / 1000);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+
+static int rocksdb_rollback(handlerton *const hton, THD *const thd,
+ bool rollback_tx) {
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ Rdb_perf_context_guard guard(tx, rocksdb_perf_context_level(thd));
+
+ if (tx != nullptr) {
+ if (rollback_tx) {
+ /*
+ We get here, when
+ - ROLLBACK statement is issued.
+
+ Discard the changes made by the transaction
+ */
+ tx->rollback();
+ } else {
+ /*
+ We get here when
+ - a statement with AUTOCOMMIT=1 is being rolled back (because of some
+ error)
+ - a statement inside a transaction is rolled back
+ */
+
+ tx->rollback_stmt();
+ tx->set_tx_failed(true);
+ }
+
+ if (my_core::thd_tx_isolation(thd) <= ISO_READ_COMMITTED) {
+ // For READ_COMMITTED, we release any existing snapshot so that we will
+ // see any changes that occurred since the last statement.
+ tx->release_snapshot();
+ }
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+static bool print_stats(THD *const thd, std::string const &type,
+ std::string const &name, std::string const &status,
+ stat_print_fn *stat_print) {
+ return stat_print(thd, type.c_str(), type.size(), name.c_str(), name.size(),
+ status.c_str(), status.size());
+}
+
+static std::string format_string(const char *const format, ...) {
+ std::string res;
+ va_list args;
+ va_list args_copy;
+ char static_buff[256];
+
+ DBUG_ASSERT(format != nullptr);
+
+ va_start(args, format);
+ va_copy(args_copy, args);
+
+ // Calculate how much space we will need
+ int len = vsnprintf(nullptr, 0, format, args);
+ va_end(args);
+
+ if (len < 0) {
+ res = std::string("<format error>");
+ } else if (len == 0) {
+ // Shortcut for an empty string
+ res = std::string("");
+ } else {
+ // For short enough output use a static buffer
+ char *buff = static_buff;
+ std::unique_ptr<char[]> dynamic_buff = nullptr;
+
+ len++; // Add one for null terminator
+
+ // for longer output use an allocated buffer
+ if (static_cast<uint>(len) > sizeof(static_buff)) {
+ dynamic_buff.reset(new char[len]);
+ buff = dynamic_buff.get();
+ }
+
+ // Now re-do the vsnprintf with the buffer which is now large enough
+ (void)vsnprintf(buff, len, format, args_copy);
+
+ // Convert to a std::string. Note we could have created a std::string
+ // large enough and then converted the buffer to a 'char*' and created
+ // the output in place. This would probably work but feels like a hack.
+ // Since this isn't code that needs to be super-performant we are going
+ // with this 'safer' method.
+ res = std::string(buff);
+ }
+
+ va_end(args_copy);
+
+ return res;
+}
+
+class Rdb_snapshot_status : public Rdb_tx_list_walker {
+ private:
+ std::string m_data;
+
+ static std::string current_timestamp(void) {
+ static const char *const format = "%d-%02d-%02d %02d:%02d:%02d";
+ time_t currtime;
+ struct tm currtm;
+
+ time(&currtime);
+
+ localtime_r(&currtime, &currtm);
+
+ return format_string(format, currtm.tm_year + 1900, currtm.tm_mon + 1,
+ currtm.tm_mday, currtm.tm_hour, currtm.tm_min,
+ currtm.tm_sec);
+ }
+
+ static std::string get_header(void) {
+ return "\n============================================================\n" +
+ current_timestamp() +
+ " ROCKSDB TRANSACTION MONITOR OUTPUT\n"
+ "============================================================\n"
+ "---------\n"
+ "SNAPSHOTS\n"
+ "---------\n"
+ "LIST OF SNAPSHOTS FOR EACH SESSION:\n";
+ }
+
+ static std::string get_footer(void) {
+ return "-----------------------------------------\n"
+ "END OF ROCKSDB TRANSACTION MONITOR OUTPUT\n"
+ "=========================================\n";
+ }
+
+ static Rdb_deadlock_info::Rdb_dl_trx_info get_dl_txn_info(
+ const rocksdb::DeadlockInfo &txn, const GL_INDEX_ID &gl_index_id) {
+ Rdb_deadlock_info::Rdb_dl_trx_info txn_data;
+
+ txn_data.trx_id = txn.m_txn_id;
+
+ txn_data.table_name = ddl_manager.safe_get_table_name(gl_index_id);
+ if (txn_data.table_name.empty()) {
+ txn_data.table_name =
+ "NOT FOUND; INDEX_ID: " + std::to_string(gl_index_id.index_id);
+ }
+
+ auto kd = ddl_manager.safe_find(gl_index_id);
+ txn_data.index_name =
+ (kd) ? kd->get_name()
+ : "NOT FOUND; INDEX_ID: " + std::to_string(gl_index_id.index_id);
+
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(txn.m_cf_id);
+ txn_data.cf_name = cfh->GetName();
+
+ txn_data.waiting_key =
+ rdb_hexdump(txn.m_waiting_key.c_str(), txn.m_waiting_key.length());
+
+ txn_data.exclusive_lock = txn.m_exclusive;
+
+ return txn_data;
+ }
+
+ static Rdb_deadlock_info get_dl_path_trx_info(
+ const rocksdb::DeadlockPath &path_entry) {
+ Rdb_deadlock_info deadlock_info;
+
+ for (auto it = path_entry.path.begin(); it != path_entry.path.end(); it++) {
+ const auto &txn = *it;
+ const GL_INDEX_ID gl_index_id = {
+ txn.m_cf_id, rdb_netbuf_to_uint32(reinterpret_cast<const uchar *>(
+ txn.m_waiting_key.c_str()))};
+ deadlock_info.path.push_back(get_dl_txn_info(txn, gl_index_id));
+ }
+ DBUG_ASSERT_IFF(path_entry.limit_exceeded, path_entry.path.empty());
+ /* print the first txn in the path to display the full deadlock cycle */
+ if (!path_entry.path.empty() && !path_entry.limit_exceeded) {
+ const auto &deadlocking_txn = *(path_entry.path.end() - 1);
+ deadlock_info.victim_trx_id = deadlocking_txn.m_txn_id;
+ deadlock_info.deadlock_time = path_entry.deadlock_time;
+ }
+ return deadlock_info;
+ }
+
+ public:
+ Rdb_snapshot_status() : m_data(get_header()) {}
+
+ std::string getResult() { return m_data + get_footer(); }
+
+ /* Implement Rdb_transaction interface */
+ /* Create one row in the snapshot status table */
+ void process_tran(const Rdb_transaction *const tx) override {
+ DBUG_ASSERT(tx != nullptr);
+
+ /* Calculate the duration the snapshot has existed */
+ int64_t snapshot_timestamp = tx->m_snapshot_timestamp;
+ if (snapshot_timestamp != 0) {
+ int64_t curr_time;
+ rdb->GetEnv()->GetCurrentTime(&curr_time);
+
+ char buffer[1024];
+#ifdef MARIAROCKS_NOT_YET
+ thd_security_context(tx->get_thd(), buffer, sizeof buffer, 0);
+#endif
+ m_data += format_string(
+ "---SNAPSHOT, ACTIVE %lld sec\n"
+ "%s\n"
+ "lock count %llu, write count %llu\n"
+ "insert count %llu, update count %llu, delete count %llu\n",
+ (longlong)(curr_time - snapshot_timestamp), buffer, tx->get_lock_count(),
+ tx->get_write_count(), tx->get_insert_count(), tx->get_update_count(),
+ tx->get_delete_count());
+ }
+ }
+
+ void populate_deadlock_buffer() {
+ auto dlock_buffer = rdb->GetDeadlockInfoBuffer();
+ m_data += "----------LATEST DETECTED DEADLOCKS----------\n";
+
+ for (const auto &path_entry : dlock_buffer) {
+ std::string path_data;
+ if (path_entry.limit_exceeded) {
+ path_data += "\n-------DEADLOCK EXCEEDED MAX DEPTH-------\n";
+ } else {
+ path_data +=
+ "\n*** DEADLOCK PATH\n"
+ "=========================================\n";
+ const auto dl_info = get_dl_path_trx_info(path_entry);
+ const auto deadlock_time = dl_info.deadlock_time;
+ for (auto it = dl_info.path.begin(); it != dl_info.path.end(); it++) {
+ const auto &trx_info = *it;
+ path_data += format_string(
+ "TIMESTAMP: %" PRId64
+ "\n"
+ "TRANSACTION ID: %u\n"
+ "COLUMN FAMILY NAME: %s\n"
+ "WAITING KEY: %s\n"
+ "LOCK TYPE: %s\n"
+ "INDEX NAME: %s\n"
+ "TABLE NAME: %s\n",
+ deadlock_time, trx_info.trx_id, trx_info.cf_name.c_str(),
+ trx_info.waiting_key.c_str(),
+ trx_info.exclusive_lock ? "EXCLUSIVE" : "SHARED",
+ trx_info.index_name.c_str(), trx_info.table_name.c_str());
+ if (it != dl_info.path.end() - 1) {
+ path_data += "---------------WAITING FOR---------------\n";
+ }
+ }
+ path_data += format_string(
+ "\n--------TRANSACTION ID: %u GOT DEADLOCK---------\n",
+ dl_info.victim_trx_id);
+ }
+ m_data += path_data;
+ }
+ }
+
+ std::vector<Rdb_deadlock_info> get_deadlock_info() {
+ std::vector<Rdb_deadlock_info> deadlock_info;
+ auto dlock_buffer = rdb->GetDeadlockInfoBuffer();
+ for (const auto &path_entry : dlock_buffer) {
+ if (!path_entry.limit_exceeded) {
+ deadlock_info.push_back(get_dl_path_trx_info(path_entry));
+ }
+ }
+ return deadlock_info;
+ }
+};
+
+/**
+ * @brief
+ * walks through all non-replication transactions and copies
+ * out relevant information for information_schema.rocksdb_trx
+ */
+class Rdb_trx_info_aggregator : public Rdb_tx_list_walker {
+ private:
+ std::vector<Rdb_trx_info> *m_trx_info;
+
+ public:
+ explicit Rdb_trx_info_aggregator(std::vector<Rdb_trx_info> *const trx_info)
+ : m_trx_info(trx_info) {}
+
+ void process_tran(const Rdb_transaction *const tx) override {
+ static const std::map<int, std::string> state_map = {
+ {rocksdb::Transaction::STARTED, "STARTED"},
+ {rocksdb::Transaction::AWAITING_PREPARE, "AWAITING_PREPARE"},
+ {rocksdb::Transaction::PREPARED, "PREPARED"},
+ {rocksdb::Transaction::AWAITING_COMMIT, "AWAITING_COMMIT"},
+ {rocksdb::Transaction::COMMITED, "COMMITED"},
+ {rocksdb::Transaction::AWAITING_ROLLBACK, "AWAITING_ROLLBACK"},
+ {rocksdb::Transaction::ROLLEDBACK, "ROLLEDBACK"},
+ };
+
+ DBUG_ASSERT(tx != nullptr);
+
+ THD *const thd = tx->get_thd();
+ ulong thread_id = thd_get_thread_id(thd);
+
+ if (tx->is_writebatch_trx()) {
+ const auto wb_impl = static_cast<const Rdb_writebatch_impl *>(tx);
+ DBUG_ASSERT(wb_impl);
+ m_trx_info->push_back(
+ {"", /* name */
+ 0, /* trx_id */
+ wb_impl->get_write_count(), 0, /* lock_count */
+ 0, /* timeout_sec */
+ "", /* state */
+ "", /* waiting_key */
+ 0, /* waiting_cf_id */
+ 1, /*is_replication */
+ 1, /* skip_trx_api */
+ wb_impl->is_tx_read_only(), 0, /* deadlock detection */
+ wb_impl->num_ongoing_bulk_load(), thread_id, "" /* query string */});
+ } else {
+ const auto tx_impl = static_cast<const Rdb_transaction_impl *>(tx);
+ DBUG_ASSERT(tx_impl);
+ const rocksdb::Transaction *rdb_trx = tx_impl->get_rdb_trx();
+
+ if (rdb_trx == nullptr) {
+ return;
+ }
+
+ char query_buf[NAME_LEN+1];
+ thd_query_safe(thd, query_buf, sizeof(query_buf));
+ std::string query_str(query_buf);
+
+ const auto state_it = state_map.find(rdb_trx->GetState());
+ DBUG_ASSERT(state_it != state_map.end());
+ const int is_replication = (thd->rgi_slave != nullptr);
+ uint32_t waiting_cf_id;
+ std::string waiting_key;
+ rdb_trx->GetWaitingTxns(&waiting_cf_id, &waiting_key),
+
+ m_trx_info->push_back(
+ {rdb_trx->GetName(), rdb_trx->GetID(), tx_impl->get_write_count(),
+ tx_impl->get_lock_count(), tx_impl->get_timeout_sec(),
+ state_it->second, waiting_key, waiting_cf_id, is_replication,
+ 0, /* skip_trx_api */
+ tx_impl->is_tx_read_only(), rdb_trx->IsDeadlockDetect(),
+ tx_impl->num_ongoing_bulk_load(), thread_id, query_str});
+ }
+ }
+};
+
+/*
+ returns a vector of info for all non-replication threads
+ for use by information_schema.rocksdb_trx
+*/
+std::vector<Rdb_trx_info> rdb_get_all_trx_info() {
+ std::vector<Rdb_trx_info> trx_info;
+ Rdb_trx_info_aggregator trx_info_agg(&trx_info);
+ Rdb_transaction::walk_tx_list(&trx_info_agg);
+ return trx_info;
+}
+
+
+/*
+ returns a vector of info of recent deadlocks
+ for use by information_schema.rocksdb_deadlock
+*/
+std::vector<Rdb_deadlock_info> rdb_get_deadlock_info() {
+ Rdb_snapshot_status showStatus;
+ Rdb_transaction::walk_tx_list(&showStatus);
+ return showStatus.get_deadlock_info();
+}
+
+#ifdef MARIAROCKS_NOT_YET
+/* Generate the snapshot status table */
+static bool rocksdb_show_snapshot_status(handlerton *const hton, THD *const thd,
+ stat_print_fn *const stat_print) {
+ Rdb_snapshot_status showStatus;
+
+ Rdb_transaction::walk_tx_list(&showStatus);
+ showStatus.populate_deadlock_buffer();
+
+ /* Send the result data back to MySQL */
+ return print_stats(thd, "rocksdb", "", showStatus.getResult(), stat_print);
+}
+#endif
+
+/*
+ This is called for SHOW ENGINE ROCKSDB STATUS | LOGS | etc.
+
+ For now, produce info about live files (which gives an imprecise idea about
+ what column families are there).
+*/
+static bool rocksdb_show_status(handlerton *const hton, THD *const thd,
+ stat_print_fn *const stat_print,
+ enum ha_stat_type stat_type) {
+ DBUG_ASSERT(hton != nullptr);
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(stat_print != nullptr);
+
+ bool res = false;
+ char buf[100] = {'\0'};
+
+ if (stat_type == HA_ENGINE_STATUS) {
+ DBUG_ASSERT(rdb != nullptr);
+
+ std::string str;
+
+ /* Global DB Statistics */
+ if (rocksdb_stats) {
+ str = rocksdb_stats->ToString();
+
+ // Use the same format as internal RocksDB statistics entries to make
+ // sure that output will look unified.
+ DBUG_ASSERT(commit_latency_stats != nullptr);
+
+ snprintf(buf, sizeof(buf),
+ "rocksdb.commit_latency statistics "
+ "Percentiles :=> 50 : %.2f 95 : %.2f "
+ "99 : %.2f 100 : %.2f\n",
+ commit_latency_stats->Percentile(50),
+ commit_latency_stats->Percentile(95),
+ commit_latency_stats->Percentile(99),
+ commit_latency_stats->Percentile(100));
+ str.append(buf);
+
+ uint64_t v = 0;
+
+ // Retrieve additional stalling related numbers from RocksDB and append
+ // them to the buffer meant for displaying detailed statistics. The intent
+ // here is to avoid adding another row to the query output because of
+ // just two numbers.
+ //
+ // NB! We're replacing hyphens with underscores in output to better match
+ // the existing naming convention.
+ if (rdb->GetIntProperty("rocksdb.is-write-stopped", &v)) {
+ snprintf(buf, sizeof(buf), "rocksdb.is_write_stopped COUNT : %llu\n", (ulonglong)v);
+ str.append(buf);
+ }
+
+ if (rdb->GetIntProperty("rocksdb.actual-delayed-write-rate", &v)) {
+ snprintf(buf, sizeof(buf),
+ "COUNT : %llu\n",
+ (ulonglong)v);
+ str.append(buf);
+ }
+
+ res |= print_stats(thd, "STATISTICS", "rocksdb", str, stat_print);
+ }
+
+ /* Per DB stats */
+ if (rdb->GetProperty("rocksdb.dbstats", &str)) {
+ res |= print_stats(thd, "DBSTATS", "rocksdb", str, stat_print);
+ }
+
+ /* Per column family stats */
+ for (const auto &cf_name : cf_manager.get_cf_names()) {
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(cf_name);
+ if (cfh == nullptr) {
+ continue;
+ }
+
+ if (!rdb->GetProperty(cfh, "rocksdb.cfstats", &str)) {
+ continue;
+ }
+
+ res |= print_stats(thd, "CF_COMPACTION", cf_name, str, stat_print);
+ }
+
+ /* Memory Statistics */
+ std::vector<rocksdb::DB *> dbs;
+ std::unordered_set<const rocksdb::Cache *> cache_set;
+ size_t internal_cache_count = 0;
+ size_t kDefaultInternalCacheSize = 8 * 1024 * 1024;
+
+ dbs.push_back(rdb);
+ cache_set.insert(rocksdb_tbl_options->block_cache.get());
+
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ rocksdb::ColumnFamilyDescriptor cf_desc;
+ cf_handle->GetDescriptor(&cf_desc);
+ auto *const table_factory = cf_desc.options.table_factory.get();
+
+ if (table_factory != nullptr) {
+ std::string tf_name = table_factory->Name();
+
+ if (tf_name.find("BlockBasedTable") != std::string::npos) {
+ const rocksdb::BlockBasedTableOptions *const bbt_opt =
+ reinterpret_cast<rocksdb::BlockBasedTableOptions *>(
+ table_factory->GetOptions());
+
+ if (bbt_opt != nullptr) {
+ if (bbt_opt->block_cache.get() != nullptr) {
+ cache_set.insert(bbt_opt->block_cache.get());
+ } else {
+ internal_cache_count++;
+ }
+ cache_set.insert(bbt_opt->block_cache_compressed.get());
+ }
+ }
+ }
+ }
+
+ std::map<rocksdb::MemoryUtil::UsageType, uint64_t> temp_usage_by_type;
+ str.clear();
+ rocksdb::MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set,
+ &temp_usage_by_type);
+ snprintf(buf, sizeof(buf), "\nMemTable Total: %llu",
+ (ulonglong)temp_usage_by_type[rocksdb::MemoryUtil::kMemTableTotal]);
+ str.append(buf);
+ snprintf(buf, sizeof(buf), "\nMemTable Unflushed: %llu",
+ (ulonglong)temp_usage_by_type[rocksdb::MemoryUtil::kMemTableUnFlushed]);
+ str.append(buf);
+ snprintf(buf, sizeof(buf), "\nTable Readers Total: %llu",
+ (ulonglong)temp_usage_by_type[rocksdb::MemoryUtil::kTableReadersTotal]);
+ str.append(buf);
+ snprintf(buf, sizeof(buf), "\nCache Total: %llu",
+ (ulonglong)temp_usage_by_type[rocksdb::MemoryUtil::kCacheTotal]);
+ str.append(buf);
+ snprintf(buf, sizeof(buf), "\nDefault Cache Capacity: %llu",
+ (ulonglong)internal_cache_count * kDefaultInternalCacheSize);
+ str.append(buf);
+ res |= print_stats(thd, "MEMORY_STATS", "rocksdb", str, stat_print);
+
+ /* Show the background thread status */
+ std::vector<rocksdb::ThreadStatus> thread_list;
+ rocksdb::Status s = rdb->GetEnv()->GetThreadList(&thread_list);
+
+ if (!s.ok()) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Returned error (%s) from GetThreadList.\n",
+ s.ToString().c_str());
+ res |= true;
+ } else {
+ /* For each background thread retrieved, print out its information */
+ for (auto &it : thread_list) {
+ /* Only look at background threads. Ignore user threads, if any. */
+ if (it.thread_type > rocksdb::ThreadStatus::LOW_PRIORITY) {
+ continue;
+ }
+
+ str = "\nthread_type: " + it.GetThreadTypeName(it.thread_type) +
+ "\ncf_name: " + it.cf_name +
+ "\noperation_type: " + it.GetOperationName(it.operation_type) +
+ "\noperation_stage: " +
+ it.GetOperationStageName(it.operation_stage) +
+ "\nelapsed_time_ms: " + it.MicrosToString(it.op_elapsed_micros);
+
+ for (auto &it_props : it.InterpretOperationProperties(
+ it.operation_type, it.op_properties)) {
+ str += "\n" + it_props.first + ": " + std::to_string(it_props.second);
+ }
+
+ str += "\nstate_type: " + it.GetStateName(it.state_type);
+
+ res |= print_stats(thd, "BG_THREADS", std::to_string(it.thread_id), str,
+ stat_print);
+ }
+ }
+
+#ifdef MARIAROCKS_NOT_YET
+ /* Explicit snapshot information */
+ str = Rdb_explicit_snapshot::dump_snapshots();
+#endif
+
+ if (!str.empty()) {
+ res |= print_stats(thd, "EXPLICIT_SNAPSHOTS", "rocksdb", str, stat_print);
+ }
+#ifdef MARIAROCKS_NOT_YET
+ } else if (stat_type == HA_ENGINE_TRX) {
+ /* Handle the SHOW ENGINE ROCKSDB TRANSACTION STATUS command */
+ res |= rocksdb_show_snapshot_status(hton, thd, stat_print);
+#endif
+ }
+ return res;
+}
+
+static inline void rocksdb_register_tx(handlerton *const hton, THD *const thd,
+ Rdb_transaction *const tx) {
+ DBUG_ASSERT(tx != nullptr);
+
+ trans_register_ha(thd, FALSE, rocksdb_hton, 0);
+ if (my_core::thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
+ tx->start_stmt();
+ trans_register_ha(thd, TRUE, rocksdb_hton, 0);
+ }
+}
+
+static const char *ha_rocksdb_exts[] = {NullS};
+
+#ifdef MARIAROCKS_NOT_YET
+static bool rocksdb_explicit_snapshot(
+ handlerton *const /* hton */, /*!< in: RocksDB handlerton */
+ THD *const thd, /*!< in: MySQL thread handle */
+ snapshot_info_st *ss_info) /*!< out: Snapshot information */
+{
+ switch (ss_info->op) {
+ case snapshot_operation::SNAPSHOT_CREATE: {
+ if (mysql_bin_log_is_open()) {
+ mysql_bin_log_lock_commits(ss_info);
+ }
+ auto s = Rdb_explicit_snapshot::create(ss_info, rdb, rdb->GetSnapshot());
+ if (mysql_bin_log_is_open()) {
+ mysql_bin_log_unlock_commits(ss_info);
+ }
+
+ thd->set_explicit_snapshot(s);
+ return s == nullptr;
+ }
+ case snapshot_operation::SNAPSHOT_ATTACH: {
+ auto s = Rdb_explicit_snapshot::get(ss_info->snapshot_id);
+ if (!s) {
+ return true;
+ }
+ *ss_info = s->ss_info;
+ thd->set_explicit_snapshot(s);
+ return false;
+ }
+ case snapshot_operation::SNAPSHOT_RELEASE: {
+ if (!thd->get_explicit_snapshot()) {
+ return true;
+ }
+ *ss_info = thd->get_explicit_snapshot()->ss_info;
+ thd->set_explicit_snapshot(nullptr);
+ return false;
+ }
+ default:
+ DBUG_ASSERT(false);
+ return true;
+ }
+ return true;
+}
+#endif
+
+/*
+ Supporting START TRANSACTION WITH CONSISTENT [ROCKSDB] SNAPSHOT
+
+ Features:
+ 1. Supporting START TRANSACTION WITH CONSISTENT SNAPSHOT
+ 2. Getting current binlog position in addition to #1.
+
+ The second feature is done by START TRANSACTION WITH
+ CONSISTENT ROCKSDB SNAPSHOT. This is Facebook's extension, and
+ it works like existing START TRANSACTION WITH CONSISTENT INNODB SNAPSHOT.
+
+ - When not setting engine, START TRANSACTION WITH CONSISTENT SNAPSHOT
+ takes both InnoDB and RocksDB snapshots, and both InnoDB and RocksDB
+ participate in transaction. When executing COMMIT, both InnoDB and
+ RocksDB modifications are committed. Remember that XA is not supported yet,
+ so mixing engines is not recommended anyway.
+
+ - When setting engine, START TRANSACTION WITH CONSISTENT.. takes
+ snapshot for the specified engine only. But it starts both
+ InnoDB and RocksDB transactions.
+*/
+static int rocksdb_start_tx_and_assign_read_view(
+ handlerton *const hton, /*!< in: RocksDB handlerton */
+ THD *const thd /*!< in: MySQL thread handle of the
+ user for whom the transaction should
+ be committed */
+)
+#ifdef MARIAROCKS_NOT_YET
+ snapshot_info_st *ss_info) /*!< in/out: Snapshot info like binlog file, pos,
+ gtid executed and snapshot ID */
+#endif
+{
+ ulong const tx_isolation = my_core::thd_tx_isolation(thd);
+
+ if (tx_isolation != ISO_REPEATABLE_READ) {
+ my_error(ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT, MYF(0));
+ return HA_EXIT_FAILURE;
+ }
+
+#ifdef MARIADB_NOT_YET
+ if (ss_info) {
+ if (mysql_bin_log_is_open()) {
+ mysql_bin_log_lock_commits(ss_info);
+ } else {
+ return HA_EXIT_FAILURE;
+ }
+#endif
+
+ /*
+ MariaDB: there is no need to call mysql_bin_log_lock_commits and then
+ unlock back.
+ SQL layer calls start_consistent_snapshot() for all engines, including the
+ binlog under LOCK_commit_ordered mutex.
+
+ The mutex prevents binlog commits from happening (right?) while the storage
+ engine(s) allocate read snapshots. That way, each storage engine is
+ synchronized with current binlog position.
+ */
+ mysql_mutex_assert_owner(&LOCK_commit_ordered);
+
+ Rdb_transaction *const tx = get_or_create_tx(thd);
+ Rdb_perf_context_guard guard(tx, rocksdb_perf_context_level(thd));
+
+ DBUG_ASSERT(!tx->has_snapshot());
+ tx->set_tx_read_only(true);
+ rocksdb_register_tx(hton, thd, tx);
+ tx->acquire_snapshot(true);
+
+#ifdef MARIADB_NOT_YET
+ if (ss_info) {
+ mysql_bin_log_unlock_commits(ss_info);
+ }
+#endif
+ return HA_EXIT_SUCCESS;
+}
+
+#ifdef MARIADB_NOT_YET
+static int rocksdb_start_tx_with_shared_read_view(
+ handlerton *const hton, /*!< in: RocksDB handlerton */
+ THD *const thd) /*!< in: MySQL thread handle of the
+ user for whom the transaction should
+ be committed */
+#ifdef MARIADB_NOT_YET
+ snapshot_info_st *ss_info) /*!< out: Snapshot info like binlog file, pos,
+ gtid executed and snapshot ID */
+#endif
+{
+ DBUG_ASSERT(thd != nullptr);
+
+ int error = HA_EXIT_SUCCESS;
+
+ ulong const tx_isolation = my_core::thd_tx_isolation(thd);
+ if (tx_isolation != ISO_REPEATABLE_READ) {
+ my_error(ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT, MYF(0));
+ return HA_EXIT_FAILURE;
+ }
+
+ Rdb_transaction *tx = nullptr;
+#ifdef MARIADB_NOT_YET
+ std::shared_ptr<Rdb_explicit_snapshot> explicit_snapshot;
+ const auto op = ss_info->op;
+
+ DBUG_ASSERT(op == snapshot_operation::SNAPSHOT_CREATE ||
+ op == snapshot_operation::SNAPSHOT_ATTACH);
+
+ // case: if binlogs are available get binlog file/pos and gtid info
+ if (op == snapshot_operation::SNAPSHOT_CREATE && mysql_bin_log_is_open()) {
+ mysql_bin_log_lock_commits(ss_info);
+ }
+
+ if (op == snapshot_operation::SNAPSHOT_ATTACH) {
+ explicit_snapshot = Rdb_explicit_snapshot::get(ss_info->snapshot_id);
+ if (!explicit_snapshot) {
+ my_printf_error(ER_UNKNOWN_ERROR, "Snapshot %llu does not exist", MYF(0),
+ ss_info->snapshot_id);
+ error = HA_EXIT_FAILURE;
+ }
+ }
+#endif
+
+ // case: all good till now
+ if (error == HA_EXIT_SUCCESS) {
+ tx = get_or_create_tx(thd);
+ Rdb_perf_context_guard guard(tx, rocksdb_perf_context_level(thd));
+
+#ifdef MARIADB_NOT_YET
+ if (explicit_snapshot) {
+ tx->m_explicit_snapshot = explicit_snapshot;
+ }
+#endif
+
+ DBUG_ASSERT(!tx->has_snapshot());
+ tx->set_tx_read_only(true);
+ rocksdb_register_tx(hton, thd, tx);
+ tx->acquire_snapshot(true);
+
+#ifdef MARIADB_NOT_YET
+ // case: an explicit snapshot was not assigned to this transaction
+ if (!tx->m_explicit_snapshot) {
+ tx->m_explicit_snapshot =
+ Rdb_explicit_snapshot::create(ss_info, rdb, tx->m_read_opts.snapshot);
+ if (!tx->m_explicit_snapshot) {
+ my_printf_error(ER_UNKNOWN_ERROR, "Could not create snapshot", MYF(0));
+ error = HA_EXIT_FAILURE;
+ }
+ }
+#endif
+ }
+
+#ifdef MARIADB_NOT_YET
+ // case: unlock the binlog
+ if (op == snapshot_operation::SNAPSHOT_CREATE && mysql_bin_log_is_open()) {
+ mysql_bin_log_unlock_commits(ss_info);
+ }
+
+ DBUG_ASSERT(error == HA_EXIT_FAILURE || tx->m_explicit_snapshot);
+
+ // copy over the snapshot details to pass to the upper layers
+ if (tx->m_explicit_snapshot) {
+ *ss_info = tx->m_explicit_snapshot->ss_info;
+ ss_info->op = op;
+ }
+#endif
+
+ return error;
+}
+#endif
+
+/* Dummy SAVEPOINT support. This is needed for long running transactions
+ * like mysqldump (https://bugs.mysql.com/bug.php?id=71017).
+ * Current SAVEPOINT does not correctly handle ROLLBACK and does not return
+ * errors. This needs to be addressed in future versions (Issue#96).
+ */
+static int rocksdb_savepoint(handlerton *const hton, THD *const thd,
+ void *const savepoint) {
+ return HA_EXIT_SUCCESS;
+}
+
+static int rocksdb_rollback_to_savepoint(handlerton *const hton, THD *const thd,
+ void *const savepoint) {
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ return tx->rollback_to_savepoint(savepoint);
+}
+
+static bool rocksdb_rollback_to_savepoint_can_release_mdl(
+ handlerton *const /* hton */, THD *const /* thd */) {
+ return true;
+}
+
+#ifdef MARIAROCKS_NOT_YET
+/*
+ This is called for INFORMATION_SCHEMA
+*/
+static void rocksdb_update_table_stats(
+ /* per-table stats callback */
+ void (*cb)(const char *db, const char *tbl, bool is_partition,
+ my_io_perf_t *r, my_io_perf_t *w, my_io_perf_t *r_blob,
+ my_io_perf_t *r_primary, my_io_perf_t *r_secondary,
+ page_stats_t *page_stats, comp_stats_t *comp_stats,
+ int n_lock_wait, int n_lock_wait_timeout, int n_lock_deadlock,
+ const char *engine)) {
+ my_io_perf_t io_perf_read;
+ my_io_perf_t io_perf_write;
+ my_io_perf_t io_perf;
+ page_stats_t page_stats;
+ comp_stats_t comp_stats;
+ uint lock_wait_timeout_stats;
+ uint deadlock_stats;
+ uint lock_wait_stats;
+ std::vector<std::string> tablenames;
+
+ /*
+ Most of these are for innodb, so setting them to 0.
+ TODO: possibly separate out primary vs. secondary index reads
+ */
+ memset(&io_perf, 0, sizeof(io_perf));
+ memset(&page_stats, 0, sizeof(page_stats));
+ memset(&comp_stats, 0, sizeof(comp_stats));
+ memset(&io_perf_write, 0, sizeof(io_perf_write));
+
+ tablenames = rdb_open_tables.get_table_names();
+
+ for (const auto &it : tablenames) {
+ Rdb_table_handler *table_handler;
+ std::string str, dbname, tablename, partname;
+ char dbname_sys[NAME_LEN + 1];
+ char tablename_sys[NAME_LEN + 1];
+ bool is_partition;
+
+ if (rdb_normalize_tablename(it, &str) != HA_EXIT_SUCCESS) {
+ /* Function needs to return void because of the interface and we've
+ * detected an error which shouldn't happen. There's no way to let
+ * caller know that something failed.
+ */
+ SHIP_ASSERT(false);
+ return;
+ }
+
+ if (rdb_split_normalized_tablename(str, &dbname, &tablename, &partname)) {
+ continue;
+ }
+
+ is_partition = (partname.size() != 0);
+
+ table_handler = rdb_open_tables.get_table_handler(it.c_str());
+ if (table_handler == nullptr) {
+ continue;
+ }
+
+ io_perf_read.bytes = table_handler->m_io_perf_read.bytes.load();
+ io_perf_read.requests = table_handler->m_io_perf_read.requests.load();
+ io_perf_write.bytes = table_handler->m_io_perf_write.bytes.load();
+ io_perf_write.requests = table_handler->m_io_perf_write.requests.load();
+ lock_wait_timeout_stats = table_handler->m_lock_wait_timeout_counter.load();
+ deadlock_stats = table_handler->m_deadlock_counter.load();
+ lock_wait_stats =
+ table_handler->m_table_perf_context.m_value[PC_KEY_LOCK_WAIT_COUNT]
+ .load();
+
+ /*
+ Convert from rocksdb timer to mysql timer. RocksDB values are
+ in nanoseconds, but table statistics expect the value to be
+ in my_timer format.
+ */
+ io_perf_read.svc_time = my_core::microseconds_to_my_timer(
+ table_handler->m_io_perf_read.svc_time.load() / 1000);
+ io_perf_read.svc_time_max = my_core::microseconds_to_my_timer(
+ table_handler->m_io_perf_read.svc_time_max.load() / 1000);
+ io_perf_read.wait_time = my_core::microseconds_to_my_timer(
+ table_handler->m_io_perf_read.wait_time.load() / 1000);
+ io_perf_read.wait_time_max = my_core::microseconds_to_my_timer(
+ table_handler->m_io_perf_read.wait_time_max.load() / 1000);
+ io_perf_read.slow_ios = table_handler->m_io_perf_read.slow_ios.load();
+ rdb_open_tables.release_table_handler(table_handler);
+
+ /*
+ Table stats expects our database and table name to be in system encoding,
+ not filename format. Convert before calling callback.
+ */
+ my_core::filename_to_tablename(dbname.c_str(), dbname_sys,
+ sizeof(dbname_sys));
+ my_core::filename_to_tablename(tablename.c_str(), tablename_sys,
+ sizeof(tablename_sys));
+ (*cb)(dbname_sys, tablename_sys, is_partition, &io_perf_read,
+ &io_perf_write, &io_perf, &io_perf, &io_perf, &page_stats,
+ &comp_stats, lock_wait_stats, lock_wait_timeout_stats, deadlock_stats,
+ rocksdb_hton_name);
+ }
+}
+#endif
+static rocksdb::Status check_rocksdb_options_compatibility(
+ const char *const dbpath, const rocksdb::Options &main_opts,
+ const std::vector<rocksdb::ColumnFamilyDescriptor> &cf_descr) {
+ DBUG_ASSERT(rocksdb_datadir != nullptr);
+
+ rocksdb::DBOptions loaded_db_opt;
+ std::vector<rocksdb::ColumnFamilyDescriptor> loaded_cf_descs;
+ rocksdb::Status status =
+ LoadLatestOptions(dbpath, rocksdb::Env::Default(), &loaded_db_opt,
+ &loaded_cf_descs, rocksdb_ignore_unknown_options);
+
+ // If we're starting from scratch and there are no options saved yet then this
+ // is a valid case. Therefore we can't compare the current set of options to
+ // anything.
+ if (status.IsNotFound()) {
+ return rocksdb::Status::OK();
+ }
+
+ if (!status.ok()) {
+ return status;
+ }
+
+ if (loaded_cf_descs.size() != cf_descr.size()) {
+ return rocksdb::Status::NotSupported(
+ "Mismatched size of column family "
+ "descriptors.");
+ }
+
+ // Please see RocksDB documentation for more context about why we need to set
+ // user-defined functions and pointer-typed options manually.
+ for (size_t i = 0; i < loaded_cf_descs.size(); i++) {
+ loaded_cf_descs[i].options.compaction_filter =
+ cf_descr[i].options.compaction_filter;
+ loaded_cf_descs[i].options.compaction_filter_factory =
+ cf_descr[i].options.compaction_filter_factory;
+ loaded_cf_descs[i].options.comparator = cf_descr[i].options.comparator;
+ loaded_cf_descs[i].options.memtable_factory =
+ cf_descr[i].options.memtable_factory;
+ loaded_cf_descs[i].options.merge_operator =
+ cf_descr[i].options.merge_operator;
+ loaded_cf_descs[i].options.prefix_extractor =
+ cf_descr[i].options.prefix_extractor;
+ loaded_cf_descs[i].options.table_factory =
+ cf_descr[i].options.table_factory;
+ }
+
+ // This is the essence of the function - determine if it's safe to open the
+ // database or not.
+ status = CheckOptionsCompatibility(dbpath, rocksdb::Env::Default(), main_opts,
+ loaded_cf_descs,
+ rocksdb_ignore_unknown_options);
+
+ return status;
+}
+
+bool prevent_myrocks_loading= false;
+
+
+/*
+ Storage Engine initialization function, invoked when plugin is loaded.
+*/
+
+static int rocksdb_init_func(void *const p) {
+
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ {
+ my_error(ER_INTERNAL_ERROR, MYF(0),
+ "Loading MyRocks plugin after it has been unloaded is not "
+ "supported. Please restart mysqld");
+ DBUG_RETURN(1);
+ }
+
+ if (rdb_check_rocksdb_corruption()) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: There was a corruption detected in RockDB files. "
+ "Check error log emitted earlier for more details.");
+ if (rocksdb_allow_to_start_after_corruption) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: Remove rocksdb_allow_to_start_after_corruption to prevent "
+ "server operating if RocksDB corruption is detected.");
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: The server will exit normally and stop restart "
+ "attempts. Remove %s file from data directory and "
+ "start mysqld manually.",
+ rdb_corruption_marker_file_name().c_str());
+ exit(0);
+ }
+ }
+
+ // Validate the assumption about the size of ROCKSDB_SIZEOF_HIDDEN_PK_COLUMN.
+ static_assert(sizeof(longlong) == 8, "Assuming that longlong is 8 bytes.");
+
+ init_rocksdb_psi_keys();
+
+ rocksdb_hton = (handlerton *)p;
+
+ rdb_open_tables.init();
+ Ensure_cleanup rdb_open_tables_cleanup([]() { rdb_open_tables.free(); });
+
+#ifdef HAVE_PSI_INTERFACE
+ rdb_bg_thread.init(rdb_signal_bg_psi_mutex_key, rdb_signal_bg_psi_cond_key);
+ rdb_drop_idx_thread.init(rdb_signal_drop_idx_psi_mutex_key,
+ rdb_signal_drop_idx_psi_cond_key);
+ rdb_mc_thread.init(rdb_signal_mc_psi_mutex_key, rdb_signal_mc_psi_cond_key);
+#else
+ rdb_bg_thread.init();
+ rdb_drop_idx_thread.init();
+ rdb_mc_thread.init();
+#endif
+ mysql_mutex_init(rdb_collation_data_mutex_key, &rdb_collation_data_mutex,
+ MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(rdb_mem_cmp_space_mutex_key, &rdb_mem_cmp_space_mutex,
+ MY_MUTEX_INIT_FAST);
+
+ const char* initial_rocksdb_datadir_for_ignore_dirs= rocksdb_datadir;
+ if (!strncmp(rocksdb_datadir, "./", 2))
+ initial_rocksdb_datadir_for_ignore_dirs += 2;
+ ignore_db_dirs_append(initial_rocksdb_datadir_for_ignore_dirs);
+
+#if defined(HAVE_PSI_INTERFACE)
+ rdb_collation_exceptions =
+ new Regex_list_handler(key_rwlock_collation_exception_list);
+#else
+ rdb_collation_exceptions = new Regex_list_handler();
+#endif
+
+ mysql_mutex_init(rdb_sysvars_psi_mutex_key, &rdb_sysvars_mutex,
+ MY_MUTEX_INIT_FAST);
+ mysql_mutex_init(rdb_block_cache_resize_mutex_key,
+ &rdb_block_cache_resize_mutex, MY_MUTEX_INIT_FAST);
+ Rdb_transaction::init_mutex();
+
+ rocksdb_hton->create = rocksdb_create_handler;
+ rocksdb_hton->close_connection = rocksdb_close_connection;
+
+ rocksdb_hton->prepare = rocksdb_prepare;
+ rocksdb_hton->prepare_ordered = NULL; // Do not need it
+
+ rocksdb_hton->commit_by_xid = rocksdb_commit_by_xid;
+ rocksdb_hton->rollback_by_xid = rocksdb_rollback_by_xid;
+ rocksdb_hton->recover = rocksdb_recover;
+
+ rocksdb_hton->commit_ordered= rocksdb_commit_ordered;
+ rocksdb_hton->commit = rocksdb_commit;
+
+ rocksdb_hton->commit_checkpoint_request= rocksdb_checkpoint_request;
+
+ rocksdb_hton->rollback = rocksdb_rollback;
+ rocksdb_hton->show_status = rocksdb_show_status;
+#ifdef MARIADB_NOT_YET
+ rocksdb_hton->explicit_snapshot = rocksdb_explicit_snapshot;
+#endif
+ rocksdb_hton->start_consistent_snapshot =
+ rocksdb_start_tx_and_assign_read_view;
+#ifdef MARIADB_NOT_YET
+ rocksdb_hton->start_shared_snapshot = rocksdb_start_tx_with_shared_read_view;
+#endif
+ rocksdb_hton->savepoint_set = rocksdb_savepoint;
+ rocksdb_hton->savepoint_rollback = rocksdb_rollback_to_savepoint;
+ rocksdb_hton->savepoint_rollback_can_release_mdl =
+ rocksdb_rollback_to_savepoint_can_release_mdl;
+#ifdef MARIAROCKS_NOT_YET
+ rocksdb_hton->update_table_stats = rocksdb_update_table_stats;
+#endif // MARIAROCKS_NOT_YET
+
+ /*
+ Not needed in MariaDB:
+ rocksdb_hton->flush_logs = rocksdb_flush_wal;
+ rocksdb_hton->handle_single_table_select = rocksdb_handle_single_table_select;
+
+ */
+
+ rocksdb_hton->flags = HTON_TEMPORARY_NOT_SUPPORTED |
+ HTON_SUPPORTS_EXTENDED_KEYS | HTON_CAN_RECREATE;
+
+ rocksdb_hton->tablefile_extensions= ha_rocksdb_exts;
+ DBUG_ASSERT(!mysqld_embedded);
+
+ if (rocksdb_db_options->max_open_files > (long)open_files_limit) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: rocksdb_max_open_files should not be "
+ "greater than the open_files_limit, effective value "
+ "of rocksdb_max_open_files is being set to "
+ "open_files_limit / 2.");
+ rocksdb_db_options->max_open_files = open_files_limit / 2;
+ } else if (rocksdb_db_options->max_open_files == -2) {
+ rocksdb_db_options->max_open_files = open_files_limit / 2;
+ }
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+ rdb_read_free_regex_handler.set_patterns(DEFAULT_READ_FREE_RPL_TABLES);
+#endif
+
+ rocksdb_stats = rocksdb::CreateDBStatistics();
+ rocksdb_stats->set_stats_level(
+ static_cast<rocksdb::StatsLevel>(rocksdb_stats_level));
+ rocksdb_stats_level = rocksdb_stats->get_stats_level();
+ rocksdb_db_options->statistics = rocksdb_stats;
+
+ if (rocksdb_rate_limiter_bytes_per_sec != 0) {
+ rocksdb_rate_limiter.reset(
+ rocksdb::NewGenericRateLimiter(rocksdb_rate_limiter_bytes_per_sec));
+ rocksdb_db_options->rate_limiter = rocksdb_rate_limiter;
+ }
+
+ rocksdb_db_options->delayed_write_rate = rocksdb_delayed_write_rate;
+
+ std::shared_ptr<Rdb_logger> myrocks_logger = std::make_shared<Rdb_logger>();
+ rocksdb::Status s = rocksdb::CreateLoggerFromOptions(
+ rocksdb_datadir, *rocksdb_db_options, &rocksdb_db_options->info_log);
+ if (s.ok()) {
+ myrocks_logger->SetRocksDBLogger(rocksdb_db_options->info_log);
+ }
+
+ rocksdb_db_options->info_log = myrocks_logger;
+ myrocks_logger->SetInfoLogLevel(
+ static_cast<rocksdb::InfoLogLevel>(rocksdb_info_log_level));
+ rocksdb_db_options->wal_dir = rocksdb_wal_dir;
+
+ rocksdb_db_options->wal_recovery_mode =
+ static_cast<rocksdb::WALRecoveryMode>(rocksdb_wal_recovery_mode);
+
+ rocksdb_db_options->access_hint_on_compaction_start =
+ static_cast<rocksdb::Options::AccessHint>(
+ rocksdb_access_hint_on_compaction_start);
+
+ if (rocksdb_db_options->allow_mmap_reads &&
+ rocksdb_db_options->use_direct_reads) {
+ // allow_mmap_reads implies !use_direct_reads and RocksDB will not open if
+ // mmap_reads and direct_reads are both on. (NO_LINT_DEBUG)
+ sql_print_error(
+ "RocksDB: Can't enable both use_direct_reads "
+ "and allow_mmap_reads\n");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ // Check whether the filesystem backing rocksdb_datadir allows O_DIRECT
+ if (rocksdb_db_options->use_direct_reads ||
+ rocksdb_db_options->use_direct_io_for_flush_and_compaction) {
+ rocksdb::EnvOptions soptions;
+ rocksdb::Status check_status;
+ rocksdb::Env *const env = rocksdb_db_options->env;
+
+ std::string fname = format_string("%s/DIRECT_CHECK", rocksdb_datadir);
+ if (env->FileExists(fname).ok()) {
+ std::unique_ptr<rocksdb::SequentialFile> file;
+ soptions.use_direct_reads = true;
+ check_status = env->NewSequentialFile(fname, &file, soptions);
+ } else {
+ std::unique_ptr<rocksdb::WritableFile> file;
+ soptions.use_direct_writes = true;
+ check_status = env->ReopenWritableFile(fname, &file, soptions);
+ if (file != nullptr) {
+ file->Close();
+ }
+ env->DeleteFile(fname);
+ }
+
+ if (!check_status.ok()) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Unable to use direct io in rocksdb-datadir:"
+ "(%s)",
+ check_status.getState());
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+
+ if (rocksdb_db_options->allow_mmap_writes &&
+ rocksdb_db_options->use_direct_io_for_flush_and_compaction) {
+ // See above comment for allow_mmap_reads. (NO_LINT_DEBUG)
+ sql_print_error(
+ "RocksDB: Can't enable both "
+ "use_direct_io_for_flush_and_compaction and "
+ "allow_mmap_writes\n");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (rocksdb_db_options->allow_mmap_writes &&
+ rocksdb_flush_log_at_trx_commit != FLUSH_LOG_NEVER) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: rocksdb_flush_log_at_trx_commit needs to be 0 "
+ "to use allow_mmap_writes");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ // sst_file_manager will move deleted rocksdb sst files to trash_dir
+ // to be deleted in a background thread.
+ std::string trash_dir = std::string(rocksdb_datadir) + "/trash";
+ rocksdb_db_options->sst_file_manager.reset(NewSstFileManager(
+ rocksdb_db_options->env, myrocks_logger, trash_dir,
+ rocksdb_sst_mgr_rate_bytes_per_sec, true /* delete_existing_trash */));
+
+ std::vector<std::string> cf_names;
+ rocksdb::Status status;
+ status = rocksdb::DB::ListColumnFamilies(*rocksdb_db_options, rocksdb_datadir,
+ &cf_names);
+ if (!status.ok()) {
+ /*
+ When we start on an empty datadir, ListColumnFamilies returns IOError,
+ and RocksDB doesn't provide any way to check what kind of error it was.
+ Checking system errno happens to work right now.
+ */
+ if (status.IsIOError()
+#ifndef _WIN32
+ && errno == ENOENT
+#endif
+ ) {
+ sql_print_information("RocksDB: Got ENOENT when listing column families");
+
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: assuming that we're creating a new database");
+ } else {
+ rdb_log_status_error(status, "Error listing column families");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: %ld column families found",
+ cf_names.size());
+ }
+
+ std::vector<rocksdb::ColumnFamilyDescriptor> cf_descr;
+ std::vector<rocksdb::ColumnFamilyHandle *> cf_handles;
+
+ rocksdb_tbl_options->index_type =
+ (rocksdb::BlockBasedTableOptions::IndexType)rocksdb_index_type;
+
+ if (!rocksdb_tbl_options->no_block_cache) {
+ std::shared_ptr<rocksdb::MemoryAllocator> memory_allocator;
+ if (!rocksdb_cache_dump) {
+ size_t block_size = rocksdb_tbl_options->block_size;
+ rocksdb::JemallocAllocatorOptions alloc_opt;
+ // Limit jemalloc tcache memory usage. The range
+ // [block_size/4, block_size] should be enough to cover most of
+ // block cache allocation sizes.
+ alloc_opt.limit_tcache_size = true;
+ alloc_opt.tcache_size_lower_bound = block_size / 4;
+ alloc_opt.tcache_size_upper_bound = block_size;
+ rocksdb::Status new_alloc_status =
+ rocksdb::NewJemallocNodumpAllocator(alloc_opt, &memory_allocator);
+ if (!new_alloc_status.ok()) {
+ // Fallback to use default malloc/free.
+ rdb_log_status_error(new_alloc_status,
+ "Error excluding block cache from core dump");
+ memory_allocator = nullptr;
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+ std::shared_ptr<rocksdb::Cache> block_cache =
+ rocksdb_use_clock_cache
+ ? rocksdb::NewClockCache(rocksdb_block_cache_size)
+ : rocksdb::NewLRUCache(
+ rocksdb_block_cache_size, -1 /*num_shard_bits*/,
+ false /*strict_capcity_limit*/,
+ rocksdb_cache_high_pri_pool_ratio, memory_allocator);
+ if (rocksdb_sim_cache_size > 0) {
+ // Simulated cache enabled
+ // Wrap block cache inside a simulated cache and pass it to RocksDB
+ rocksdb_tbl_options->block_cache =
+ rocksdb::NewSimCache(block_cache, rocksdb_sim_cache_size, 6);
+ } else {
+ // Pass block cache to RocksDB
+ rocksdb_tbl_options->block_cache = block_cache;
+ }
+ }
+ // Using newer BlockBasedTable format version for better compression
+ // and better memory allocation.
+ // See:
+ // https://github.com/facebook/rocksdb/commit/9ab5adfc59a621d12357580c94451d9f7320c2dd
+ rocksdb_tbl_options->format_version = 2;
+
+ if (rocksdb_collect_sst_properties) {
+ properties_collector_factory =
+ std::make_shared<Rdb_tbl_prop_coll_factory>(&ddl_manager);
+
+ rocksdb_set_compaction_options(nullptr, nullptr, nullptr, nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ DBUG_ASSERT(rocksdb_table_stats_sampling_pct <=
+ RDB_TBL_STATS_SAMPLE_PCT_MAX);
+ properties_collector_factory->SetTableStatsSamplingPct(
+ rocksdb_table_stats_sampling_pct);
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+ }
+
+ if (rocksdb_persistent_cache_size_mb > 0) {
+ std::shared_ptr<rocksdb::PersistentCache> pcache;
+ uint64_t cache_size_bytes = rocksdb_persistent_cache_size_mb * 1024 * 1024;
+ status = rocksdb::NewPersistentCache(
+ rocksdb::Env::Default(), std::string(rocksdb_persistent_cache_path),
+ cache_size_bytes, myrocks_logger, true, &pcache);
+ if (!status.ok()) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Persistent cache returned error: (%s)",
+ status.getState());
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ rocksdb_tbl_options->persistent_cache = pcache;
+ } else if (strlen(rocksdb_persistent_cache_path)) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Must specify rocksdb_persistent_cache_size_mb");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ std::unique_ptr<Rdb_cf_options> cf_options_map(new Rdb_cf_options());
+ if (!cf_options_map->init(*rocksdb_tbl_options, properties_collector_factory,
+ rocksdb_default_cf_options,
+ rocksdb_override_cf_options)) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Failed to initialize CF options map.");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /*
+ If there are no column families, we're creating the new database.
+ Create one column family named "default".
+ */
+ if (cf_names.size() == 0) cf_names.push_back(DEFAULT_CF_NAME);
+
+ std::vector<int> compaction_enabled_cf_indices;
+
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Column Families at start:");
+ for (size_t i = 0; i < cf_names.size(); ++i) {
+ rocksdb::ColumnFamilyOptions opts;
+ cf_options_map->get_cf_options(cf_names[i], &opts);
+
+ // NO_LINT_DEBUG
+ sql_print_information(" cf=%s", cf_names[i].c_str());
+
+ // NO_LINT_DEBUG
+ sql_print_information(" write_buffer_size=%ld", opts.write_buffer_size);
+
+ // NO_LINT_DEBUG
+ sql_print_information(" target_file_size_base=%" PRIu64,
+ opts.target_file_size_base);
+
+ /*
+ Temporarily disable compactions to prevent a race condition where
+ compaction starts before compaction filter is ready.
+ */
+ if (!opts.disable_auto_compactions) {
+ compaction_enabled_cf_indices.push_back(i);
+ opts.disable_auto_compactions = true;
+ }
+ cf_descr.push_back(rocksdb::ColumnFamilyDescriptor(cf_names[i], opts));
+ }
+
+ rocksdb::Options main_opts(*rocksdb_db_options,
+ cf_options_map->get_defaults());
+
+ rocksdb::TransactionDBOptions tx_db_options;
+ tx_db_options.transaction_lock_timeout = 2000; // 2 seconds
+ tx_db_options.custom_mutex_factory = std::make_shared<Rdb_mutex_factory>();
+ tx_db_options.write_policy =
+ static_cast<rocksdb::TxnDBWritePolicy>(rocksdb_write_policy);
+
+ status =
+ check_rocksdb_options_compatibility(rocksdb_datadir, main_opts, cf_descr);
+
+ // We won't start if we'll determine that there's a chance of data corruption
+ // because of incompatible options.
+ if (!status.ok()) {
+ rdb_log_status_error(
+ status, "Compatibility check against existing database options failed");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ status = rocksdb::TransactionDB::Open(
+ main_opts, tx_db_options, rocksdb_datadir, cf_descr, &cf_handles, &rdb);
+
+ if (!status.ok()) {
+ rdb_log_status_error(status, "Error opening instance");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ cf_manager.init(std::move(cf_options_map), &cf_handles);
+
+ if (dict_manager.init(rdb, &cf_manager)) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Failed to initialize data dictionary.");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (binlog_manager.init(&dict_manager)) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Failed to initialize binlog manager.");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (ddl_manager.init(&dict_manager, &cf_manager, rocksdb_validate_tables)) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Failed to initialize DDL manager.");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ Rdb_sst_info::init(rdb);
+
+ /*
+ Enable auto compaction, things needed for compaction filter are finished
+ initializing
+ */
+ std::vector<rocksdb::ColumnFamilyHandle *> compaction_enabled_cf_handles;
+ compaction_enabled_cf_handles.reserve(compaction_enabled_cf_indices.size());
+ for (const auto &index : compaction_enabled_cf_indices) {
+ compaction_enabled_cf_handles.push_back(cf_handles[index]);
+ }
+
+ status = rdb->EnableAutoCompaction(compaction_enabled_cf_handles);
+
+ if (!status.ok()) {
+ rdb_log_status_error(status, "Error enabling compaction");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+#ifndef HAVE_PSI_INTERFACE
+ auto err = rdb_bg_thread.create_thread(BG_THREAD_NAME);
+#else
+ auto err = rdb_bg_thread.create_thread(BG_THREAD_NAME,
+ rdb_background_psi_thread_key);
+#endif
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Couldn't start the background thread: (errno=%d)",
+ err);
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+#ifndef HAVE_PSI_INTERFACE
+ err = rdb_drop_idx_thread.create_thread(INDEX_THREAD_NAME);
+#else
+ err = rdb_drop_idx_thread.create_thread(INDEX_THREAD_NAME,
+ rdb_drop_idx_psi_thread_key);
+#endif
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Couldn't start the drop index thread: (errno=%d)",
+ err);
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ err = rdb_mc_thread.create_thread(MANUAL_COMPACTION_THREAD_NAME
+#ifdef HAVE_PSI_INTERFACE
+ ,
+ rdb_mc_psi_thread_key
+#endif
+ );
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Couldn't start the manual compaction thread: (errno=%d)",
+ err);
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ rdb_set_collation_exception_list(rocksdb_strict_collation_exceptions);
+
+ if (rocksdb_pause_background_work) {
+ rdb->PauseBackgroundWork();
+ }
+
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: global statistics using %s indexer",
+ STRINGIFY_ARG(RDB_INDEXER));
+#if defined(HAVE_SCHED_GETCPU)
+ if (sched_getcpu() == -1) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: sched_getcpu() failed - "
+ "global statistics will use thread_id_indexer_t instead");
+ }
+#endif
+
+ err = my_error_register(rdb_get_error_messages, HA_ERR_ROCKSDB_FIRST,
+ HA_ERR_ROCKSDB_LAST);
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Couldn't initialize error messages");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+
+
+ // Creating an instance of HistogramImpl should only happen after RocksDB
+ // has been successfully initialized.
+ commit_latency_stats = new rocksdb::HistogramImpl();
+
+ // Construct a list of directories which will be monitored by I/O watchdog
+ // to make sure that we won't lose write access to them.
+ std::vector<std::string> directories;
+
+ // 1. Data directory.
+ directories.push_back(mysql_real_data_home);
+
+ // 2. Transaction logs.
+ if (myrocks::rocksdb_wal_dir && *myrocks::rocksdb_wal_dir) {
+ directories.push_back(myrocks::rocksdb_wal_dir);
+ }
+
+#if !defined(_WIN32) && !defined(__APPLE__)
+ io_watchdog = new Rdb_io_watchdog(std::move(directories));
+ io_watchdog->reset_timeout(rocksdb_io_write_timeout_secs);
+#endif
+
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "MyRocks storage engine plugin has been successfully "
+ "initialized.");
+
+ // Skip cleaning up rdb_open_tables as we've succeeded
+ rdb_open_tables_cleanup.skip();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Storage Engine deinitialization function, invoked when plugin is unloaded.
+*/
+
+static int rocksdb_done_func(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ int error = 0;
+
+ // signal the drop index thread to stop
+ rdb_drop_idx_thread.signal(true);
+
+ // Flush all memtables for not losing data, even if WAL is disabled.
+ rocksdb_flush_all_memtables();
+
+ // Stop all rocksdb background work
+ CancelAllBackgroundWork(rdb->GetBaseDB(), true);
+
+ // Signal the background thread to stop and to persist all stats collected
+ // from background flushes and compactions. This will add more keys to a new
+ // memtable, but since the memtables were just flushed, it should not trigger
+ // a flush that can stall due to background threads being stopped. As long
+ // as these keys are stored in a WAL file, they can be retrieved on restart.
+ rdb_bg_thread.signal(true);
+
+ // Wait for the background thread to finish.
+ auto err = rdb_bg_thread.join();
+ if (err != 0) {
+ // We'll log the message and continue because we're shutting down and
+ // continuation is the optimal strategy.
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Couldn't stop the background thread: (errno=%d)",
+ err);
+ }
+
+ // Wait for the drop index thread to finish.
+ err = rdb_drop_idx_thread.join();
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Couldn't stop the index thread: (errno=%d)", err);
+ }
+
+ // signal the manual compaction thread to stop
+ rdb_mc_thread.signal(true);
+ // Wait for the manual compaction thread to finish.
+ err = rdb_mc_thread.join();
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Couldn't stop the manual compaction thread: (errno=%d)", err);
+ }
+
+ if (rdb_open_tables.count()) {
+ // Looks like we are getting unloaded and yet we have some open tables
+ // left behind.
+ error = 1;
+ }
+
+ rdb_open_tables.free();
+ /*
+ destructors for static objects can be called at _exit(),
+ but we want to free the memory at dlclose()
+ */
+ // MARIADB_MERGE_2019: rdb_open_tables.m_hash.~Rdb_table_set();
+ mysql_mutex_destroy(&rdb_sysvars_mutex);
+ mysql_mutex_destroy(&rdb_block_cache_resize_mutex);
+
+
+ delete rdb_collation_exceptions;
+
+ mysql_mutex_destroy(&rdb_collation_data_mutex);
+ mysql_mutex_destroy(&rdb_mem_cmp_space_mutex);
+
+ Rdb_transaction::term_mutex();
+
+ for (auto &it : rdb_collation_data) {
+ delete it;
+ it = nullptr;
+ }
+
+ ddl_manager.cleanup();
+ binlog_manager.cleanup();
+ dict_manager.cleanup();
+ cf_manager.cleanup();
+
+ delete rdb;
+ rdb = nullptr;
+
+ delete commit_latency_stats;
+ commit_latency_stats = nullptr;
+
+#if !defined(_WIN32) && !defined(__APPLE__)
+ delete io_watchdog;
+ io_watchdog = nullptr;
+#endif
+
+// Disown the cache data since we're shutting down.
+// This results in memory leaks but it improved the shutdown time.
+// Don't disown when running under valgrind
+#ifndef HAVE_valgrind
+ if (rocksdb_tbl_options->block_cache) {
+ rocksdb_tbl_options->block_cache->DisownData();
+ }
+#endif /* HAVE_valgrind */
+
+ /*
+ MariaDB: don't clear rocksdb_db_options and rocksdb_tbl_options.
+ MyRocks' plugin variables refer to them.
+
+ The plugin cannot be loaded again (see prevent_myrocks_loading) but plugin
+ variables are processed before myrocks::rocksdb_init_func is invoked, so
+ they must point to valid memory.
+ */
+ //rocksdb_db_options = nullptr;
+ rocksdb_db_options->statistics = nullptr;
+ //rocksdb_tbl_options = nullptr;
+ rocksdb_stats = nullptr;
+
+ my_free(rocksdb_update_cf_options);
+ rocksdb_update_cf_options = nullptr;
+
+ my_error_unregister(HA_ERR_ROCKSDB_FIRST, HA_ERR_ROCKSDB_LAST);
+
+ /*
+ Prevent loading the plugin after it has been loaded and then unloaded. This
+ doesn't work currently.
+ */
+ prevent_myrocks_loading= true;
+
+ DBUG_RETURN(error);
+}
+
+static inline void rocksdb_smart_seek(bool seek_backward,
+ rocksdb::Iterator *const iter,
+ const rocksdb::Slice &key_slice) {
+ if (seek_backward) {
+ iter->SeekForPrev(key_slice);
+ } else {
+ iter->Seek(key_slice);
+ }
+}
+
+static inline void rocksdb_smart_next(bool seek_backward,
+ rocksdb::Iterator *const iter) {
+ if (seek_backward) {
+ iter->Prev();
+ } else {
+ iter->Next();
+ }
+}
+
+#ifndef DBUG_OFF
+// simulate that RocksDB has reported corrupted data
+static void dbug_change_status_to_corrupted(rocksdb::Status *status) {
+ *status = rocksdb::Status::Corruption();
+}
+#endif
+
+// If the iterator is not valid it might be because of EOF but might be due
+// to IOError or corruption. The good practice is always check it.
+// https://github.com/facebook/rocksdb/wiki/Iterator#error-handling
+static inline bool is_valid(rocksdb::Iterator *scan_it) {
+ if (scan_it->Valid()) {
+ return true;
+ } else {
+ rocksdb::Status s = scan_it->status();
+ DBUG_EXECUTE_IF("rocksdb_return_status_corrupted",
+ dbug_change_status_to_corrupted(&s););
+ if (s.IsIOError() || s.IsCorruption()) {
+ if (s.IsCorruption()) {
+ rdb_persist_corruption_marker();
+ }
+ rdb_handle_io_error(s, RDB_IO_ERROR_GENERAL);
+ }
+ return false;
+ }
+}
+
+/**
+ @brief
+ Example of simple lock controls. The "table_handler" it creates is a
+ structure we will pass to each ha_rocksdb handler. Do you have to have
+ one of these? Well, you have pieces that are used for locking, and
+ they are needed to function.
+*/
+
+Rdb_table_handler *Rdb_open_tables_map::get_table_handler(
+ const char *const table_name) {
+ DBUG_ASSERT(table_name != nullptr);
+
+ Rdb_table_handler *table_handler;
+
+ std::string table_name_str(table_name);
+
+ // First, look up the table in the hash map.
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ const auto it = m_table_map.find(table_name_str);
+ if (it != m_table_map.end()) {
+ // Found it
+ table_handler = it->second;
+ } else {
+ char *tmp_name;
+
+ // Since we did not find it in the hash map, attempt to create and add it
+ // to the hash map.
+ if (!(table_handler = reinterpret_cast<Rdb_table_handler *>(my_multi_malloc(
+ PSI_INSTRUMENT_ME,
+ MYF(MY_WME | MY_ZEROFILL), &table_handler, sizeof(*table_handler),
+ &tmp_name, table_name_str.length() + 1, NullS)))) {
+ // Allocating a new Rdb_table_handler and a new table name failed.
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return nullptr;
+ }
+
+ table_handler->m_ref_count = 0;
+ table_handler->m_table_name_length = table_name_str.length();
+ table_handler->m_table_name = tmp_name;
+ strmov(table_handler->m_table_name, table_name);
+
+ m_table_map.emplace(table_name_str, table_handler);
+
+ thr_lock_init(&table_handler->m_thr_lock);
+#ifdef MARIAROCKS_NOT_YET
+ table_handler->m_io_perf_read.init();
+ table_handler->m_io_perf_write.init();
+#endif
+ }
+ DBUG_ASSERT(table_handler->m_ref_count >= 0);
+ table_handler->m_ref_count++;
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return table_handler;
+}
+
+std::vector<std::string> rdb_get_open_table_names(void) {
+ return rdb_open_tables.get_table_names();
+}
+
+std::vector<std::string> Rdb_open_tables_map::get_table_names(void) const {
+ const Rdb_table_handler *table_handler;
+ std::vector<std::string> names;
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ for (const auto &kv : m_table_map) {
+ table_handler = kv.second;
+ DBUG_ASSERT(table_handler != nullptr);
+ names.push_back(table_handler->m_table_name);
+ }
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return names;
+}
+
+/*
+ Inspired by innobase_get_int_col_max_value from InnoDB. This returns the
+ maximum value a type can take on.
+*/
+static ulonglong rdb_get_int_col_max_value(const Field *field) {
+ ulonglong max_value = 0;
+ switch (field->key_type()) {
+ case HA_KEYTYPE_BINARY:
+ max_value = 0xFFULL;
+ break;
+ case HA_KEYTYPE_INT8:
+ max_value = 0x7FULL;
+ break;
+ case HA_KEYTYPE_USHORT_INT:
+ max_value = 0xFFFFULL;
+ break;
+ case HA_KEYTYPE_SHORT_INT:
+ max_value = 0x7FFFULL;
+ break;
+ case HA_KEYTYPE_UINT24:
+ max_value = 0xFFFFFFULL;
+ break;
+ case HA_KEYTYPE_INT24:
+ max_value = 0x7FFFFFULL;
+ break;
+ case HA_KEYTYPE_ULONG_INT:
+ max_value = 0xFFFFFFFFULL;
+ break;
+ case HA_KEYTYPE_LONG_INT:
+ max_value = 0x7FFFFFFFULL;
+ break;
+ case HA_KEYTYPE_ULONGLONG:
+ max_value = 0xFFFFFFFFFFFFFFFFULL;
+ break;
+ case HA_KEYTYPE_LONGLONG:
+ max_value = 0x7FFFFFFFFFFFFFFFULL;
+ break;
+ case HA_KEYTYPE_FLOAT:
+ max_value = 0x1000000ULL;
+ break;
+ case HA_KEYTYPE_DOUBLE:
+ max_value = 0x20000000000000ULL;
+ break;
+ default:
+ abort();
+ }
+
+ return max_value;
+}
+
+void ha_rocksdb::load_auto_incr_value() {
+ ulonglong auto_incr = 0;
+ bool validate_last = false, use_datadic = true;
+#ifndef DBUG_OFF
+ DBUG_EXECUTE_IF("myrocks_autoinc_upgrade", use_datadic = false;);
+ validate_last = true;
+#endif
+
+ if (use_datadic && dict_manager.get_auto_incr_val(
+ m_tbl_def->get_autoincr_gl_index_id(), &auto_incr)) {
+ update_auto_incr_val(auto_incr);
+ }
+
+ // If we find nothing in the data dictionary, or if we are in debug mode,
+ // then call index_last to get the last value.
+ //
+ // This is needed when upgrading from a server that did not support
+ // persistent auto_increment, of if the table is empty.
+ //
+ // For debug mode, we are just verifying that the data dictionary value is
+ // greater than or equal to the maximum value in the table.
+ if (auto_incr == 0 || validate_last) {
+ auto_incr = load_auto_incr_value_from_index();
+ update_auto_incr_val(auto_incr);
+ }
+
+ // If we failed to find anything from the data dictionary and index, then
+ // initialize auto_increment to 1.
+ if (m_tbl_def->m_auto_incr_val == 0) {
+ update_auto_incr_val(1);
+ }
+}
+
+ulonglong ha_rocksdb::load_auto_incr_value_from_index() {
+ const int save_active_index = active_index;
+ active_index = table->s->next_number_index;
+ const uint8 save_table_status = table->status;
+ ulonglong last_val = 0;
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ const bool is_new_snapshot = !tx->has_snapshot();
+ if (is_new_snapshot) {
+ tx->acquire_snapshot(true);
+ }
+
+ // Do a lookup. We only need index column, so it should be index-only.
+ // (another reason to make it index-only is that table->read_set is not set
+ // appropriately and non-index-only lookup will not read the value)
+ const bool save_keyread_only = m_keyread_only;
+ m_keyread_only = true;
+ m_converter->set_is_key_requested(true);
+
+ if (!index_last(table->record[0])) {
+ Field *field =
+ table->key_info[table->s->next_number_index].key_part[0].field;
+ ulonglong max_val = rdb_get_int_col_max_value(field);
+ MY_BITMAP *const old_map = dbug_tmp_use_all_columns(table, &table->read_set);
+ last_val = field->val_int();
+ if (last_val != max_val) {
+ last_val++;
+ }
+#ifndef DBUG_OFF
+ ulonglong dd_val;
+ if (last_val <= max_val) {
+ const auto &gl_index_id = m_tbl_def->get_autoincr_gl_index_id();
+ if (dict_manager.get_auto_incr_val(gl_index_id, &dd_val) &&
+ tx->get_auto_incr(gl_index_id) == 0) {
+ DBUG_ASSERT(dd_val >= last_val);
+ }
+ }
+#endif
+ dbug_tmp_restore_column_map(&table->read_set, old_map);
+ }
+
+ m_keyread_only = save_keyread_only;
+ if (is_new_snapshot) {
+ tx->release_snapshot();
+ }
+
+ table->status = save_table_status;
+ active_index = save_active_index;
+
+ /*
+ Do what ha_rocksdb::index_end() does.
+ (Why don't we use index_init/index_end? class handler defines index_init
+ as private, for some reason).
+ */
+ release_scan_iterator();
+
+ return last_val;
+}
+
+void ha_rocksdb::update_auto_incr_val(ulonglong val) {
+ ulonglong auto_incr_val = m_tbl_def->m_auto_incr_val;
+ while (
+ auto_incr_val < val &&
+ !m_tbl_def->m_auto_incr_val.compare_exchange_weak(auto_incr_val, val)) {
+ // Do nothing - just loop until auto_incr_val is >= val or we successfully
+ // set it
+ }
+}
+
+void ha_rocksdb::update_auto_incr_val_from_field() {
+ Field *field;
+ ulonglong new_val, max_val;
+ field = table->key_info[table->s->next_number_index].key_part[0].field;
+ max_val = rdb_get_int_col_max_value(field);
+
+ MY_BITMAP *const old_map =
+ dbug_tmp_use_all_columns(table, &table->read_set);
+ new_val = field->val_int();
+ // don't increment if we would wrap around
+ if (new_val != max_val) {
+ new_val++;
+ }
+
+ dbug_tmp_restore_column_map(&table->read_set, old_map);
+
+ // Only update if positive value was set for auto_incr column.
+ if (new_val <= max_val) {
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ tx->set_auto_incr(m_tbl_def->get_autoincr_gl_index_id(), new_val);
+
+ // Update the in memory auto_incr value in m_tbl_def.
+ update_auto_incr_val(new_val);
+ }
+}
+
+int ha_rocksdb::load_hidden_pk_value() {
+ const int save_active_index = active_index;
+ active_index = m_tbl_def->m_key_count - 1;
+ const uint8 save_table_status = table->status;
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ const bool is_new_snapshot = !tx->has_snapshot();
+
+ longlong hidden_pk_id = 1;
+ // Do a lookup.
+ if (!index_last(table->record[0])) {
+ /*
+ Decode PK field from the key
+ */
+ auto err = read_hidden_pk_id_from_rowkey(&hidden_pk_id);
+ if (err) {
+ if (is_new_snapshot) {
+ tx->release_snapshot();
+ }
+ return err;
+ }
+
+ hidden_pk_id++;
+ }
+
+ longlong old = m_tbl_def->m_hidden_pk_val;
+ while (old < hidden_pk_id &&
+ !m_tbl_def->m_hidden_pk_val.compare_exchange_weak(old, hidden_pk_id)) {
+ }
+
+ if (is_new_snapshot) {
+ tx->release_snapshot();
+ }
+
+ table->status = save_table_status;
+ active_index = save_active_index;
+
+ release_scan_iterator();
+
+ return HA_EXIT_SUCCESS;
+}
+
+/* Get PK value from m_tbl_def->m_hidden_pk_info. */
+longlong ha_rocksdb::update_hidden_pk_val() {
+ DBUG_ASSERT(has_hidden_pk(table));
+ const longlong new_val = m_tbl_def->m_hidden_pk_val++;
+ return new_val;
+}
+
+/* Get the id of the hidden pk id from m_last_rowkey */
+int ha_rocksdb::read_hidden_pk_id_from_rowkey(longlong *const hidden_pk_id) {
+ DBUG_ASSERT(table != nullptr);
+ DBUG_ASSERT(has_hidden_pk(table));
+
+ rocksdb::Slice rowkey_slice(m_last_rowkey.ptr(), m_last_rowkey.length());
+
+ // Get hidden primary key from old key slice
+ Rdb_string_reader reader(&rowkey_slice);
+ if ((!reader.read(Rdb_key_def::INDEX_NUMBER_SIZE))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ const int length= 8; /* was Field_longlong::PACK_LENGTH in FB MySQL tree */
+ const uchar *from = reinterpret_cast<const uchar *>(reader.read(length));
+ if (from == nullptr) {
+ /* Mem-comparable image doesn't have enough bytes */
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ *hidden_pk_id = rdb_netbuf_read_uint64(&from);
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ @brief
+ Free lock controls. We call this whenever we close a table. If the table had
+ the last reference to the table_handler, then we free the memory associated
+ with it.
+*/
+
+void Rdb_open_tables_map::release_table_handler(
+ Rdb_table_handler *const table_handler) {
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+
+ DBUG_ASSERT(table_handler != nullptr);
+ DBUG_ASSERT(table_handler->m_ref_count > 0);
+ if (!--table_handler->m_ref_count) {
+ // Last reference was released. Tear down the hash entry.
+ const auto ret MY_ATTRIBUTE((__unused__)) =
+ m_table_map.erase(std::string(table_handler->m_table_name));
+ DBUG_ASSERT(ret == 1); // the hash entry must actually be found and deleted
+ my_core::thr_lock_delete(&table_handler->m_thr_lock);
+ my_free(table_handler);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+}
+
+static handler *rocksdb_create_handler(my_core::handlerton *const hton,
+ my_core::TABLE_SHARE *const table_arg,
+ my_core::MEM_ROOT *const mem_root) {
+ return new (mem_root) ha_rocksdb(hton, table_arg);
+}
+
+ha_rocksdb::ha_rocksdb(my_core::handlerton *const hton,
+ my_core::TABLE_SHARE *const table_arg)
+ : handler(hton, table_arg),
+ m_table_handler(nullptr),
+ m_scan_it(nullptr),
+ m_scan_it_skips_bloom(false),
+ m_scan_it_snapshot(nullptr),
+ m_scan_it_lower_bound(nullptr),
+ m_scan_it_upper_bound(nullptr),
+ m_tbl_def(nullptr),
+ m_pk_descr(nullptr),
+ m_key_descr_arr(nullptr),
+ m_pk_can_be_decoded(false),
+ m_pk_tuple(nullptr),
+ m_pk_packed_tuple(nullptr),
+ m_sk_packed_tuple(nullptr),
+ m_end_key_packed_tuple(nullptr),
+ m_sk_match_prefix(nullptr),
+ m_sk_match_prefix_buf(nullptr),
+ m_sk_packed_tuple_old(nullptr),
+ m_dup_sk_packed_tuple(nullptr),
+ m_dup_sk_packed_tuple_old(nullptr),
+ m_pack_buffer(nullptr),
+ m_record_buffer(nullptr),
+ m_lock_rows(RDB_LOCK_NONE),
+ m_keyread_only(false),
+ m_insert_with_update(false),
+ m_dup_pk_found(false),
+ m_in_rpl_delete_rows(false),
+ m_in_rpl_update_rows(false),
+ m_force_skip_unique_check(false) {}
+
+
+const std::string &ha_rocksdb::get_table_basename() const {
+ return m_tbl_def->base_tablename();
+}
+
+/**
+ @return
+ false OK
+ other Error inpacking the data
+*/
+bool ha_rocksdb::init_with_fields() {
+ DBUG_ENTER_FUNC();
+
+ const uint pk = table_share->primary_key;
+ if (pk != MAX_KEY) {
+ const uint key_parts = table_share->key_info[pk].user_defined_key_parts;
+ check_keyread_allowed(pk /*PK*/, key_parts - 1, true);
+ } else {
+ m_pk_can_be_decoded = false;
+ }
+ cached_table_flags = table_flags();
+
+ DBUG_RETURN(false); /* Ok */
+}
+
+/*
+ If the key is a TTL key, we may need to filter it out.
+
+ The purpose of read filtering for tables with TTL is to ensure that
+ during a transaction a key which has expired already but not removed by
+ compaction yet is not returned to the user.
+
+ Without this the user might be hit with problems such as disappearing
+ rows within a transaction, etc, because the compaction filter ignores
+ snapshots when filtering keys.
+*/
+bool ha_rocksdb::should_hide_ttl_rec(const Rdb_key_def &kd,
+ const rocksdb::Slice &ttl_rec_val,
+ const int64_t curr_ts) {
+ DBUG_ASSERT(kd.has_ttl());
+ DBUG_ASSERT(kd.m_ttl_rec_offset != UINT_MAX);
+
+ /*
+ Curr_ts can only be 0 if there are no snapshots open.
+ should_hide_ttl_rec can only be called when there is >=1 snapshots, unless
+ we are filtering on the write path (single INSERT/UPDATE) in which case
+ we are passed in the current time as curr_ts.
+
+ In the event curr_ts is 0, we always decide not to filter the record. We
+ also log a warning and increment a diagnostic counter.
+ */
+ if (curr_ts == 0) {
+ update_row_stats(ROWS_HIDDEN_NO_SNAPSHOT);
+ return false;
+ }
+
+ if (!rdb_is_ttl_read_filtering_enabled() || !rdb_is_ttl_enabled()) {
+ return false;
+ }
+
+ Rdb_string_reader reader(&ttl_rec_val);
+
+ /*
+ Find where the 8-byte ttl is for each record in this index.
+ */
+ uint64 ts;
+ if (!reader.read(kd.m_ttl_rec_offset) || reader.read_uint64(&ts)) {
+ /*
+ This condition should never be reached since all TTL records have an
+ 8 byte ttl field in front. Don't filter the record out, and log an error.
+ */
+ std::string buf;
+ buf = rdb_hexdump(ttl_rec_val.data(), ttl_rec_val.size(),
+ RDB_MAX_HEXDUMP_LEN);
+ const GL_INDEX_ID gl_index_id = kd.get_gl_index_id();
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "Decoding ttl from PK value failed, "
+ "for index (%u,%u), val: %s",
+ gl_index_id.cf_id, gl_index_id.index_id, buf.c_str());
+ DBUG_ASSERT(0);
+ return false;
+ }
+
+ /* Hide record if it has expired before the current snapshot time. */
+ uint64 read_filter_ts = 0;
+#ifndef DBUG_OFF
+ read_filter_ts += rdb_dbug_set_ttl_read_filter_ts();
+#endif
+ bool is_hide_ttl =
+ ts + kd.m_ttl_duration + read_filter_ts <= static_cast<uint64>(curr_ts);
+ if (is_hide_ttl) {
+ update_row_stats(ROWS_FILTERED);
+
+ /* increment examined row count when rows are skipped */
+ THD *thd = ha_thd();
+ thd->inc_examined_row_count(1);
+ DEBUG_SYNC(thd, "rocksdb.ttl_rows_examined");
+ }
+ return is_hide_ttl;
+}
+
+int ha_rocksdb::rocksdb_skip_expired_records(const Rdb_key_def &kd,
+ rocksdb::Iterator *const iter,
+ bool seek_backward) {
+ if (kd.has_ttl()) {
+ THD *thd = ha_thd();
+ while (iter->Valid() &&
+ should_hide_ttl_rec(
+ kd, iter->value(),
+ get_or_create_tx(table->in_use)->m_snapshot_timestamp)) {
+ DEBUG_SYNC(thd, "rocksdb.check_flags_ser");
+ if (thd && thd->killed) {
+ return HA_ERR_QUERY_INTERRUPTED;
+ }
+ rocksdb_smart_next(seek_backward, iter);
+ }
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+#ifndef DBUG_OFF
+void dbug_append_garbage_at_end(rocksdb::PinnableSlice *on_disk_rec) {
+ std::string str(on_disk_rec->data(), on_disk_rec->size());
+ on_disk_rec->Reset();
+ str.append("abc");
+ on_disk_rec->PinSelf(rocksdb::Slice(str));
+}
+
+void dbug_truncate_record(rocksdb::PinnableSlice *on_disk_rec) {
+ on_disk_rec->remove_suffix(on_disk_rec->size());
+}
+
+void dbug_modify_rec_varchar12(rocksdb::PinnableSlice *on_disk_rec) {
+ std::string res;
+ // The record is NULL-byte followed by VARCHAR(10).
+ // Put the NULL-byte
+ res.append("\0", 1);
+ // Then, add a valid VARCHAR(12) value.
+ res.append("\xC", 1);
+ res.append("123456789ab", 12);
+
+ on_disk_rec->Reset();
+ on_disk_rec->PinSelf(rocksdb::Slice(res));
+}
+
+void dbug_create_err_inplace_alter() {
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "Intentional failure in inplace alter occurred.", MYF(0));
+}
+#endif
+
+int ha_rocksdb::convert_record_from_storage_format(
+ const rocksdb::Slice *const key, uchar *const buf) {
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_row_read1",
+ dbug_append_garbage_at_end(&m_retrieved_record););
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_row_read2",
+ dbug_truncate_record(&m_retrieved_record););
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_row_read3",
+ dbug_modify_rec_varchar12(&m_retrieved_record););
+
+ return convert_record_from_storage_format(key, &m_retrieved_record, buf);
+}
+
+/*
+ @brief
+ Unpack the record in this->m_retrieved_record and this->m_last_rowkey from
+ storage format into buf (which can be table->record[0] or table->record[1]).
+
+ @param key Table record's key in mem-comparable form.
+ @param buf Store record in table->record[0] format here
+
+ @detail
+ If the table has blobs, the unpacked data in buf may keep pointers to the
+ data in this->m_retrieved_record.
+
+ The key is only needed to check its checksum value (the checksum is in
+ m_retrieved_record).
+
+ @seealso
+ rdb_converter::setup_read_decoders() Sets up data structures which tell
+ which columns to decode.
+
+ @return
+ 0 OK
+ other Error inpacking the data
+*/
+
+int ha_rocksdb::convert_record_from_storage_format(
+ const rocksdb::Slice *const key, const rocksdb::Slice *const value,
+ uchar *const buf) {
+ return m_converter->decode(m_pk_descr, buf, key, value);
+}
+
+int ha_rocksdb::alloc_key_buffers(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ bool alloc_alter_buffers) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(m_pk_tuple == nullptr);
+
+ std::shared_ptr<Rdb_key_def> *const kd_arr = tbl_def_arg->m_key_descr_arr;
+
+ uint key_len = 0;
+ uint max_packed_sk_len = 0;
+ uint pack_key_len = 0;
+ uint record_len = table->s->reclength + table->s->null_bytes;
+
+ m_pk_descr = kd_arr[pk_index(table_arg, tbl_def_arg)];
+ if (has_hidden_pk(table_arg)) {
+ m_pk_key_parts = 1;
+ } else {
+ m_pk_key_parts =
+ table->key_info[table->s->primary_key].user_defined_key_parts;
+ key_len = table->key_info[table->s->primary_key].key_length;
+ }
+
+ // move this into get_table_handler() ??
+ m_pk_descr->setup(table_arg, tbl_def_arg);
+
+ m_pk_tuple = reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, key_len, MYF(0)));
+
+ pack_key_len = m_pk_descr->max_storage_fmt_length();
+ m_pk_packed_tuple =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, pack_key_len, MYF(0)));
+
+ /* Sometimes, we may use m_sk_packed_tuple for storing packed PK */
+ max_packed_sk_len = pack_key_len;
+ for (uint i = 0; i < table_arg->s->keys; i++) {
+ /* Primary key was processed above */
+ if (i == table_arg->s->primary_key) continue;
+
+ // TODO: move this into get_table_handler() ??
+ kd_arr[i]->setup(table_arg, tbl_def_arg);
+
+ const uint packed_len = kd_arr[i]->max_storage_fmt_length();
+ if (packed_len > max_packed_sk_len) {
+ max_packed_sk_len = packed_len;
+ }
+ }
+
+ m_sk_packed_tuple =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_sk_match_prefix_buf =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_sk_packed_tuple_old =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_end_key_packed_tuple =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_pack_buffer =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_record_buffer =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, record_len, MYF(0)));
+
+ m_scan_it_lower_bound =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_scan_it_upper_bound =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+
+ /*
+ If inplace alter is happening, allocate special buffers for unique
+ secondary index duplicate checking.
+ */
+ if (alloc_alter_buffers) {
+ m_dup_sk_packed_tuple =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ m_dup_sk_packed_tuple_old =
+ reinterpret_cast<uchar *>(my_malloc(PSI_INSTRUMENT_ME, max_packed_sk_len, MYF(0)));
+ }
+
+ if (m_pk_tuple == nullptr || m_pk_packed_tuple == nullptr ||
+ m_sk_packed_tuple == nullptr || m_sk_packed_tuple_old == nullptr ||
+ m_end_key_packed_tuple == nullptr || m_pack_buffer == nullptr ||
+ m_scan_it_upper_bound == nullptr || m_scan_it_lower_bound == nullptr ||
+ m_record_buffer == nullptr ||
+ (alloc_alter_buffers && (m_dup_sk_packed_tuple == nullptr ||
+ m_dup_sk_packed_tuple_old == nullptr))) {
+ // One or more of the above allocations failed. Clean up and exit
+ free_key_buffers();
+
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+void ha_rocksdb::free_key_buffers() {
+ my_free(m_pk_tuple);
+ m_pk_tuple = nullptr;
+
+ my_free(m_pk_packed_tuple);
+ m_pk_packed_tuple = nullptr;
+
+ my_free(m_sk_packed_tuple);
+ m_sk_packed_tuple = nullptr;
+
+ my_free(m_sk_match_prefix_buf);
+ m_sk_match_prefix_buf = nullptr;
+
+ my_free(m_sk_packed_tuple_old);
+ m_sk_packed_tuple_old = nullptr;
+
+ my_free(m_end_key_packed_tuple);
+ m_end_key_packed_tuple = nullptr;
+
+ my_free(m_pack_buffer);
+ m_pack_buffer = nullptr;
+
+ my_free(m_record_buffer);
+ m_record_buffer = nullptr;
+
+ my_free(m_dup_sk_packed_tuple);
+ m_dup_sk_packed_tuple = nullptr;
+
+ my_free(m_dup_sk_packed_tuple_old);
+ m_dup_sk_packed_tuple_old = nullptr;
+
+ my_free(m_scan_it_lower_bound);
+ m_scan_it_lower_bound = nullptr;
+
+ my_free(m_scan_it_upper_bound);
+ m_scan_it_upper_bound = nullptr;
+}
+
+#ifdef MARIAROCKS_NOT_YET
+#endif
+void ha_rocksdb::set_skip_unique_check_tables(const char *const whitelist) {
+ const char *const wl =
+ whitelist ? whitelist : DEFAULT_SKIP_UNIQUE_CHECK_TABLES;
+
+#if defined(HAVE_PSI_INTERFACE)
+ Regex_list_handler regex_handler(key_rwlock_skip_unique_check_tables);
+#else
+ Regex_list_handler regex_handler;
+#endif
+
+ if (!regex_handler.set_patterns(wl)) {
+ warn_about_bad_patterns(&regex_handler, "skip_unique_check_tables");
+ }
+
+ m_skip_unique_check = regex_handler.matches(m_tbl_def->base_tablename());
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::open(const char *const name, int mode, uint test_if_locked) {
+ DBUG_ENTER_FUNC();
+
+ int err = close();
+ if (err) {
+ DBUG_RETURN(err);
+ }
+
+ m_table_handler = rdb_open_tables.get_table_handler(name);
+
+ if (m_table_handler == nullptr) {
+ DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+ }
+
+ my_core::thr_lock_data_init(&m_table_handler->m_thr_lock, &m_db_lock,
+ nullptr);
+ m_io_perf.init(&m_table_handler->m_table_perf_context,
+ &m_table_handler->m_io_perf_read,
+ &m_table_handler->m_io_perf_write, &stats);
+ Rdb_perf_context_guard guard(&m_io_perf,
+ rocksdb_perf_context_level(ha_thd()));
+
+ std::string fullname;
+ err = rdb_normalize_tablename(name, &fullname);
+ if (err != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(err);
+ }
+
+ m_tbl_def = ddl_manager.find(fullname);
+ if (m_tbl_def == nullptr) {
+ my_error(ER_INTERNAL_ERROR, MYF(0),
+ "Attempt to open a table that is not present in RocksDB-SE data "
+ "dictionary");
+ DBUG_RETURN(HA_ERR_ROCKSDB_INVALID_TABLE);
+ }
+
+ m_lock_rows = RDB_LOCK_NONE;
+ m_key_descr_arr = m_tbl_def->m_key_descr_arr;
+
+ /*
+ Full table scan actually uses primary key
+ (UPDATE needs to know this, otherwise it will go into infinite loop on
+ queries like "UPDATE tbl SET pk=pk+100")
+ */
+ key_used_on_scan = table->s->primary_key;
+
+ // close() above has already called free_key_buffers(). No need to do it here.
+ err = alloc_key_buffers(table, m_tbl_def);
+
+ if (err) {
+ DBUG_RETURN(err);
+ }
+
+ /*
+ init_with_fields() is used to initialize table flags based on the field
+ definitions in table->field[].
+ It is called by open_binary_frm(), but that function calls the method for
+ a temporary ha_rocksdb object which is later destroyed.
+
+ If we are here in ::open(), then init_with_fields() has not been called
+ for this object. Call it ourselves, we want all member variables to be
+ properly initialized.
+ */
+ init_with_fields();
+
+ /* Initialize decoder */
+ m_converter = std::make_shared<Rdb_converter>(ha_thd(), m_tbl_def, table);
+
+ /*
+ Update m_ttl_bytes address to same as Rdb_converter's m_ttl_bytes.
+ Remove this code after moving convert_record_to_storage_format() into
+ Rdb_converter class.
+ */
+ m_ttl_bytes = m_converter->get_ttl_bytes_buffer();
+
+ /*
+ MariaDB: adjust field->part_of_key for PK columns. We can only do it here
+ because SE API is just relying on the HA_PRIMARY_KEY_IN_READ_INDEX which
+ does not allow to distinguish between unpack'able and non-unpack'able
+ columns.
+ Upstream uses handler->init_with_fields() but we don't have that call.
+ */
+ {
+ if (!has_hidden_pk(table)) {
+ KEY *const pk_info = &table->key_info[table->s->primary_key];
+ for (uint kp = 0; kp < pk_info->user_defined_key_parts; kp++) {
+ if (!m_pk_descr->can_unpack(kp)) {
+ //
+ uint field_index= pk_info->key_part[kp].field->field_index;
+ table->field[field_index]->part_of_key.clear_all();
+ table->field[field_index]->part_of_key.set_bit(table->s->primary_key);
+ }
+ }
+ }
+
+ for (uint key= 0; key < table->s->keys; key++) {
+ KEY *const key_info = &table->key_info[key];
+ if (key == table->s->primary_key)
+ continue;
+ for (uint kp = 0; kp < key_info->usable_key_parts; kp++) {
+ uint field_index= key_info->key_part[kp].field->field_index;
+ if (m_key_descr_arr[key]->can_unpack(kp)) {
+ table->field[field_index]->part_of_key.set_bit(key);
+ } else {
+ table->field[field_index]->part_of_key.clear_bit(key);
+ }
+ }
+ }
+ }
+
+ info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
+
+ /*
+ The following load_XXX code calls row decode functions, and they do
+ that without having done ::external_lock() or index_init()/rnd_init().
+ (Note: this also means we're doing a read when there was no
+ rdb_converter::setup_field_encoders() call)
+
+ Initialize the necessary variables for them:
+ */
+
+ /* Load auto_increment value only once on first use. */
+ if (table->found_next_number_field && m_tbl_def->m_auto_incr_val == 0) {
+ load_auto_incr_value();
+ }
+
+ /* Load hidden pk only once on first use. */
+ if (has_hidden_pk(table) && m_tbl_def->m_hidden_pk_val == 0 &&
+ (err = load_hidden_pk_value()) != HA_EXIT_SUCCESS) {
+ free_key_buffers();
+ DBUG_RETURN(err);
+ }
+
+ /* Index block size in MyRocks: used by MySQL in query optimization */
+ stats.block_size = rocksdb_tbl_options->block_size;
+
+#ifdef MARIAROCKS_NOT_YET // MDEV-10976
+#endif
+ /* Determine at open whether we should skip unique checks for this table */
+ set_skip_unique_check_tables(THDVAR(ha_thd(), skip_unique_check_tables));
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+int ha_rocksdb::close(void) {
+ DBUG_ENTER_FUNC();
+
+ m_pk_descr = nullptr;
+ m_key_descr_arr = nullptr;
+ m_converter = nullptr;
+ free_key_buffers();
+
+ if (m_table_handler != nullptr) {
+ rdb_open_tables.release_table_handler(m_table_handler);
+ m_table_handler = nullptr;
+ }
+
+ // These are needed to suppress valgrind errors in rocksdb.partition
+ m_last_rowkey.free();
+ m_sk_tails.free();
+ m_sk_tails_old.free();
+ m_pk_unpack_info.free();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+static const char *rdb_error_messages[] = {
+ "Table must have a PRIMARY KEY.",
+ "Specifying DATA DIRECTORY for an individual table is not supported.",
+ "Specifying INDEX DIRECTORY for an individual table is not supported.",
+ "RocksDB commit failed.",
+ "Failure during bulk load operation.",
+ "Found data corruption.",
+ "CRC checksum mismatch.",
+ "Invalid table.",
+ "Could not access RocksDB properties.",
+ "File I/O error during merge/sort operation.",
+ "RocksDB status: not found.",
+ "RocksDB status: corruption.",
+ "RocksDB status: invalid argument.",
+ "RocksDB status: io error.",
+ "RocksDB status: no space.",
+ "RocksDB status: merge in progress.",
+ "RocksDB status: incomplete.",
+ "RocksDB status: shutdown in progress.",
+ "RocksDB status: timed out.",
+ "RocksDB status: aborted.",
+ "RocksDB status: lock limit reached.",
+ "RocksDB status: busy.",
+ "RocksDB status: deadlock.",
+ "RocksDB status: expired.",
+ "RocksDB status: try again.",
+};
+
+static_assert((sizeof(rdb_error_messages) / sizeof(rdb_error_messages[0])) ==
+ ((HA_ERR_ROCKSDB_LAST - HA_ERR_ROCKSDB_FIRST) + 1),
+ "Number of error messages doesn't match number of error codes");
+
+//psergey-merge: do we need this in MariaDB: we have get_error_messages
+//below...
+#if 0
+static const char *rdb_get_error_message(int nr) {
+ return rdb_error_messages[nr - HA_ERR_ROCKSDB_FIRST];
+}
+#endif
+
+static const char **rdb_get_error_messages(int nr) { return rdb_error_messages; }
+
+bool ha_rocksdb::get_error_message(const int error, String *const buf) {
+ DBUG_ENTER_FUNC();
+
+ static_assert(HA_ERR_ROCKSDB_LAST > HA_ERR_FIRST,
+ "HA_ERR_ROCKSDB_LAST > HA_ERR_FIRST");
+ static_assert(HA_ERR_ROCKSDB_LAST > HA_ERR_LAST,
+ "HA_ERR_ROCKSDB_LAST > HA_ERR_LAST");
+
+ if (error == HA_ERR_LOCK_WAIT_TIMEOUT || error == HA_ERR_LOCK_DEADLOCK ||
+ error == HA_ERR_ROCKSDB_STATUS_BUSY) {
+ Rdb_transaction *const tx = get_tx_from_thd(ha_thd());
+ DBUG_ASSERT(tx != nullptr);
+ buf->append(tx->m_detailed_error);
+ DBUG_RETURN(true);
+ }
+
+ if (error >= HA_ERR_ROCKSDB_FIRST && error <= HA_ERR_ROCKSDB_LAST) {
+ buf->append(rdb_error_messages[error - HA_ERR_ROCKSDB_FIRST]);
+ }
+
+ // We can be called with the values which are < HA_ERR_FIRST because most
+ // MySQL internal functions will just return HA_EXIT_FAILURE in case of
+ // an error.
+
+ DBUG_RETURN(false);
+}
+
+/*
+ Generalized way to convert RocksDB status errors into MySQL error code, and
+ print error message.
+
+ Each error code below maps to a RocksDB status code found in:
+ rocksdb/include/rocksdb/status.h
+*/
+int ha_rocksdb::rdb_error_to_mysql(const rocksdb::Status &s,
+ const char *opt_msg) {
+ DBUG_ASSERT(!s.ok());
+
+ int err;
+ switch (s.code()) {
+ case rocksdb::Status::Code::kOk:
+ err = HA_EXIT_SUCCESS;
+ break;
+ case rocksdb::Status::Code::kNotFound:
+ err = HA_ERR_ROCKSDB_STATUS_NOT_FOUND;
+ break;
+ case rocksdb::Status::Code::kCorruption:
+ err = HA_ERR_ROCKSDB_STATUS_CORRUPTION;
+ break;
+ case rocksdb::Status::Code::kNotSupported:
+ err = HA_ERR_ROCKSDB_STATUS_NOT_SUPPORTED;
+ break;
+ case rocksdb::Status::Code::kInvalidArgument:
+ err = HA_ERR_ROCKSDB_STATUS_INVALID_ARGUMENT;
+ break;
+ case rocksdb::Status::Code::kIOError:
+ err = (s.IsNoSpace()) ? HA_ERR_ROCKSDB_STATUS_NO_SPACE
+ : HA_ERR_ROCKSDB_STATUS_IO_ERROR;
+ break;
+ case rocksdb::Status::Code::kMergeInProgress:
+ err = HA_ERR_ROCKSDB_STATUS_MERGE_IN_PROGRESS;
+ break;
+ case rocksdb::Status::Code::kIncomplete:
+ err = HA_ERR_ROCKSDB_STATUS_INCOMPLETE;
+ break;
+ case rocksdb::Status::Code::kShutdownInProgress:
+ err = HA_ERR_ROCKSDB_STATUS_SHUTDOWN_IN_PROGRESS;
+ break;
+ case rocksdb::Status::Code::kTimedOut:
+ err = HA_ERR_ROCKSDB_STATUS_TIMED_OUT;
+ break;
+ case rocksdb::Status::Code::kAborted:
+ err = (s.IsLockLimit()) ? HA_ERR_ROCKSDB_STATUS_LOCK_LIMIT
+ : HA_ERR_ROCKSDB_STATUS_ABORTED;
+ break;
+ case rocksdb::Status::Code::kBusy:
+ err = (s.IsDeadlock()) ? HA_ERR_ROCKSDB_STATUS_DEADLOCK
+ : HA_ERR_ROCKSDB_STATUS_BUSY;
+ break;
+ case rocksdb::Status::Code::kExpired:
+ err = HA_ERR_ROCKSDB_STATUS_EXPIRED;
+ break;
+ case rocksdb::Status::Code::kTryAgain:
+ err = HA_ERR_ROCKSDB_STATUS_TRY_AGAIN;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ return -1;
+ }
+
+ std::string errMsg;
+ if (s.IsLockLimit()) {
+ errMsg =
+ "Operation aborted: Failed to acquire lock due to "
+ "rocksdb_max_row_locks limit";
+ } else {
+ errMsg = s.ToString();
+ }
+
+ if (opt_msg) {
+ std::string concatenated_error = errMsg + " (" + std::string(opt_msg) + ")";
+ my_error(ER_GET_ERRMSG, MYF(0), s.code(), concatenated_error.c_str(),
+ rocksdb_hton_name);
+ } else {
+ my_error(ER_GET_ERRMSG, MYF(0), s.code(), errMsg.c_str(),
+ rocksdb_hton_name);
+ }
+
+ return err;
+}
+
+/* MyRocks supports only the following collations for indexed columns */
+static const std::set<uint> RDB_INDEX_COLLATIONS = {
+ COLLATION_BINARY, COLLATION_UTF8_BIN, COLLATION_LATIN1_BIN};
+
+static bool rdb_is_index_collation_supported(
+ const my_core::Field *const field) {
+ const my_core::enum_field_types type = field->real_type();
+ /* Handle [VAR](CHAR|BINARY) or TEXT|BLOB */
+ if (type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_STRING ||
+ type == MYSQL_TYPE_BLOB) {
+
+ return (RDB_INDEX_COLLATIONS.find(field->charset()->number) !=
+ RDB_INDEX_COLLATIONS.end()) ||
+ rdb_is_collation_supported(field->charset());
+ }
+ return true;
+}
+
+
+static bool
+rdb_field_uses_nopad_collation(const my_core::Field *const field) {
+ const my_core::enum_field_types type = field->real_type();
+ /* Handle [VAR](CHAR|BINARY) or TEXT|BLOB */
+ if (type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_STRING ||
+ type == MYSQL_TYPE_BLOB) {
+
+ /*
+ This is technically a NOPAD collation but it's a binary collation
+ that we can handle.
+ */
+ if (RDB_INDEX_COLLATIONS.find(field->charset()->number) !=
+ RDB_INDEX_COLLATIONS.end())
+ return false;
+
+ return (field->charset()->state & MY_CS_NOPAD);
+ }
+ return false;
+}
+
+
+/*
+ Create structures needed for storing data in rocksdb. This is called when the
+ table is created. The structures will be shared by all TABLE* objects.
+
+ @param
+ table_arg Table with definition
+ db_table "dbname.tablename"
+ len strlen of the above
+ tbl_def_arg tbl_def whose key_descr is being created/populated
+ old_tbl_def_arg tbl_def from which keys are being copied over from
+ (for use during inplace alter)
+
+ @return
+ 0 - Ok
+ other - error, either given table ddl is not supported by rocksdb or OOM.
+*/
+int ha_rocksdb::create_key_defs(
+ const TABLE *const table_arg, Rdb_tbl_def *const tbl_def_arg,
+ const TABLE *const old_table_arg /* = nullptr */,
+ const Rdb_tbl_def *const old_tbl_def_arg
+ /* = nullptr */) const {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ /*
+ These need to be one greater than MAX_INDEXES since the user can create
+ MAX_INDEXES secondary keys and no primary key which would cause us
+ to generate a hidden one.
+ */
+ std::array<key_def_cf_info, MAX_INDEXES + 1> cfs;
+
+ /*
+ NOTE: All new column families must be created before new index numbers are
+ allocated to each key definition. See below for more details.
+ http://github.com/MySQLOnRocksDB/mysql-5.6/issues/86#issuecomment-138515501
+ */
+ if (create_cfs(table_arg, tbl_def_arg, &cfs)) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ uint64 ttl_duration = 0;
+ std::string ttl_column;
+ uint ttl_field_offset;
+
+ uint err;
+ if ((err = Rdb_key_def::extract_ttl_duration(table_arg, tbl_def_arg,
+ &ttl_duration))) {
+ DBUG_RETURN(err);
+ }
+
+ if ((err = Rdb_key_def::extract_ttl_col(table_arg, tbl_def_arg, &ttl_column,
+ &ttl_field_offset))) {
+ DBUG_RETURN(err);
+ }
+
+ /* We don't currently support TTL on tables with hidden primary keys. */
+ if (ttl_duration > 0 && has_hidden_pk(table_arg)) {
+ my_error(ER_RDB_TTL_UNSUPPORTED, MYF(0));
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /*
+ If TTL duration is not specified but TTL column was specified, throw an
+ error because TTL column requires duration.
+ */
+ if (ttl_duration == 0 && !ttl_column.empty()) {
+ my_error(ER_RDB_TTL_COL_FORMAT, MYF(0), ttl_column.c_str());
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (!old_tbl_def_arg) {
+ /*
+ old_tbl_def doesn't exist. this means we are in the process of creating
+ a new table.
+
+ Get the index numbers (this will update the next_index_number)
+ and create Rdb_key_def structures.
+ */
+ for (uint i = 0; i < tbl_def_arg->m_key_count; i++) {
+ if (create_key_def(table_arg, i, tbl_def_arg, &m_key_descr_arr[i], cfs[i],
+ ttl_duration, ttl_column)) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+ } else {
+ /*
+ old_tbl_def exists. This means we are creating a new tbl_def as part of
+ in-place alter table. Copy over existing keys from the old_tbl_def and
+ generate the necessary new key definitions if any.
+ */
+ if (create_inplace_key_defs(table_arg, tbl_def_arg, old_table_arg,
+ old_tbl_def_arg, cfs, ttl_duration,
+ ttl_column)) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Checks index parameters and creates column families needed for storing data
+ in rocksdb if necessary.
+
+ @param in
+ table_arg Table with definition
+ db_table Table name
+ tbl_def_arg Table def structure being populated
+
+ @param out
+ cfs CF info for each key definition in 'key_info' order
+
+ @return
+ 0 - Ok
+ other - error
+*/
+int ha_rocksdb::create_cfs(
+ const TABLE *const table_arg, Rdb_tbl_def *const tbl_def_arg,
+ std::array<struct key_def_cf_info, MAX_INDEXES + 1> *const cfs) const {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ char tablename_sys[NAME_LEN + 1];
+ bool tsys_set= false;
+
+ /*
+ The first loop checks the index parameters and creates
+ column families if necessary.
+ */
+ for (uint i = 0; i < tbl_def_arg->m_key_count; i++) {
+ rocksdb::ColumnFamilyHandle *cf_handle;
+
+ if (!is_hidden_pk(i, table_arg, tbl_def_arg) &&
+ tbl_def_arg->base_tablename().find(tmp_file_prefix) != 0) {
+ if (!tsys_set)
+ {
+ tsys_set= true;
+ my_core::filename_to_tablename(tbl_def_arg->base_tablename().c_str(),
+ tablename_sys, sizeof(tablename_sys));
+ }
+
+ for (uint part = 0; part < table_arg->key_info[i].ext_key_parts;
+ part++)
+ {
+ /* MariaDB: disallow NOPAD collations */
+ if (rdb_field_uses_nopad_collation(
+ table_arg->key_info[i].key_part[part].field))
+ {
+ my_error(ER_MYROCKS_CANT_NOPAD_COLLATION, MYF(0));
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (rocksdb_strict_collation_check &&
+ !rdb_is_index_collation_supported(
+ table_arg->key_info[i].key_part[part].field) &&
+ !rdb_collation_exceptions->matches(tablename_sys)) {
+
+ char buf[1024];
+ my_snprintf(buf, sizeof(buf),
+ "Indexed column %s.%s uses a collation that does not "
+ "allow index-only access in secondary key and has "
+ "reduced disk space efficiency in primary key.",
+ tbl_def_arg->full_tablename().c_str(),
+ table_arg->key_info[i].key_part[part].field->field_name.str);
+
+ my_error(ER_INTERNAL_ERROR, MYF(ME_WARNING), buf);
+ }
+ }
+ }
+
+ // Internal consistency check to make sure that data in TABLE and
+ // Rdb_tbl_def structures matches. Either both are missing or both are
+ // specified. Yes, this is critical enough to make it into SHIP_ASSERT.
+ SHIP_ASSERT(IF_PARTITIONING(!table_arg->part_info,true) == tbl_def_arg->base_partition().empty());
+
+ // Generate the name for the column family to use.
+ bool per_part_match_found = false;
+ std::string cf_name =
+ generate_cf_name(i, table_arg, tbl_def_arg, &per_part_match_found);
+
+ // Prevent create from using the system column family.
+ if (cf_name == DEFAULT_SYSTEM_CF_NAME) {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0),
+ "column family not valid for storing index data.");
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ // Here's how `get_or_create_cf` will use the input parameters:
+ //
+ // `cf_name` - will be used as a CF name.
+ cf_handle = cf_manager.get_or_create_cf(rdb, cf_name);
+
+ if (!cf_handle) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ auto &cf = (*cfs)[i];
+
+ cf.cf_handle = cf_handle;
+ cf.is_reverse_cf = Rdb_cf_manager::is_cf_name_reverse(cf_name.c_str());
+ cf.is_per_partition_cf = per_part_match_found;
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Create key definition needed for storing data in rocksdb during ADD index
+ inplace operations.
+
+ @param in
+ table_arg Table with definition
+ tbl_def_arg New table def structure being populated
+ old_tbl_def_arg Old(current) table def structure
+ cfs Struct array which contains column family information
+
+ @return
+ 0 - Ok
+ other - error, either given table ddl is not supported by rocksdb or OOM.
+*/
+int ha_rocksdb::create_inplace_key_defs(
+ const TABLE *const table_arg, Rdb_tbl_def *const tbl_def_arg,
+ const TABLE *const old_table_arg, const Rdb_tbl_def *const old_tbl_def_arg,
+ const std::array<key_def_cf_info, MAX_INDEXES + 1> &cfs,
+ uint64 ttl_duration, const std::string &ttl_column) const {
+ DBUG_ENTER_FUNC();
+
+ std::shared_ptr<Rdb_key_def> *const old_key_descr =
+ old_tbl_def_arg->m_key_descr_arr;
+ std::shared_ptr<Rdb_key_def> *const new_key_descr =
+ tbl_def_arg->m_key_descr_arr;
+ const std::unordered_map<std::string, uint> old_key_pos =
+ get_old_key_positions(table_arg, tbl_def_arg, old_table_arg,
+ old_tbl_def_arg);
+
+ uint i;
+ for (i = 0; i < tbl_def_arg->m_key_count; i++) {
+ const auto &it = old_key_pos.find(get_key_name(i, table_arg, tbl_def_arg));
+
+ if (it != old_key_pos.end()) {
+ /*
+ Found matching index in old table definition, so copy it over to the
+ new one created.
+ */
+ const Rdb_key_def &okd = *old_key_descr[it->second];
+
+ const GL_INDEX_ID gl_index_id = okd.get_gl_index_id();
+ struct Rdb_index_info index_info;
+ if (!dict_manager.get_index_info(gl_index_id, &index_info)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Could not get index information "
+ "for Index Number (%u,%u), table %s",
+ gl_index_id.cf_id, gl_index_id.index_id,
+ old_tbl_def_arg->full_tablename().c_str());
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ uint32 ttl_rec_offset =
+ Rdb_key_def::has_index_flag(index_info.m_index_flags,
+ Rdb_key_def::TTL_FLAG)
+ ? Rdb_key_def::calculate_index_flag_offset(
+ index_info.m_index_flags, Rdb_key_def::TTL_FLAG)
+ : UINT_MAX;
+
+ /*
+ We can't use the copy constructor because we need to update the
+ keynr within the pack_info for each field and the keyno of the keydef
+ itself.
+ */
+ new_key_descr[i] = std::make_shared<Rdb_key_def>(
+ okd.get_index_number(), i, okd.get_cf(),
+ index_info.m_index_dict_version, index_info.m_index_type,
+ index_info.m_kv_version, okd.m_is_reverse_cf,
+ okd.m_is_per_partition_cf, okd.m_name.c_str(),
+ dict_manager.get_stats(gl_index_id), index_info.m_index_flags,
+ ttl_rec_offset, index_info.m_ttl_duration);
+ } else if (create_key_def(table_arg, i, tbl_def_arg, &new_key_descr[i],
+ cfs[i], ttl_duration, ttl_column)) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ DBUG_ASSERT(new_key_descr[i] != nullptr);
+ new_key_descr[i]->setup(table_arg, tbl_def_arg);
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+std::unordered_map<std::string, uint> ha_rocksdb::get_old_key_positions(
+ const TABLE *const table_arg, const Rdb_tbl_def *const tbl_def_arg,
+ const TABLE *const old_table_arg,
+ const Rdb_tbl_def *const old_tbl_def_arg) const {
+ DBUG_ENTER_FUNC();
+
+ std::shared_ptr<Rdb_key_def> *const old_key_descr =
+ old_tbl_def_arg->m_key_descr_arr;
+ std::unordered_map<std::string, uint> old_key_pos;
+ std::unordered_map<std::string, uint> new_key_pos;
+ uint i;
+
+ for (i = 0; i < tbl_def_arg->m_key_count; i++) {
+ new_key_pos[get_key_name(i, table_arg, tbl_def_arg)] = i;
+ }
+
+ for (i = 0; i < old_tbl_def_arg->m_key_count; i++) {
+ if (is_hidden_pk(i, old_table_arg, old_tbl_def_arg)) {
+ old_key_pos[old_key_descr[i]->m_name] = i;
+ continue;
+ }
+
+ /*
+ In case of matching key name, need to check key parts of keys as well,
+ in case a simultaneous drop + add is performed, where the key name is the
+ same but the key parts are different.
+
+ Example:
+ CREATE TABLE t1 (a INT, b INT, KEY ka(a)) ENGINE=RocksDB;
+ ALTER TABLE t1 DROP INDEX ka, ADD INDEX ka(b), ALGORITHM=INPLACE;
+ */
+ const KEY *const old_key = &old_table_arg->key_info[i];
+ const auto &it = new_key_pos.find(old_key->name.str);
+ if (it == new_key_pos.end()) {
+ continue;
+ }
+
+ KEY *const new_key = &table_arg->key_info[it->second];
+
+ /*
+ Check that the key is identical between old and new tables.
+ If not, we still need to create a new index.
+
+ The exception is if there is an index changed from unique to non-unique,
+ in these cases we don't need to rebuild as they are stored the same way in
+ RocksDB.
+ */
+ bool unique_to_non_unique =
+ ((old_key->flags ^ new_key->flags) == HA_NOSAME) &&
+ (old_key->flags & HA_NOSAME);
+
+ if (compare_keys(old_key, new_key) && !unique_to_non_unique) {
+ continue;
+ }
+
+ /* Check to make sure key parts match. */
+ if (compare_key_parts(old_key, new_key)) {
+ continue;
+ }
+
+ old_key_pos[old_key->name.str] = i;
+ }
+
+ DBUG_RETURN(old_key_pos);
+}
+
+/* Check to see if two keys are identical. */
+int ha_rocksdb::compare_keys(const KEY *const old_key,
+ const KEY *const new_key) const {
+ DBUG_ENTER_FUNC();
+
+ /* Check index name. */
+ if (strcmp(old_key->name.str, new_key->name.str) != 0) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /* If index algorithms are different then keys are different. */
+ if (old_key->algorithm != new_key->algorithm) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /* Check that the key is identical between old and new tables. */
+ if ((old_key->flags ^ new_key->flags) & HA_KEYFLAG_MASK) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /* Check index comment. (for column family changes) */
+ std::string old_comment(old_key->comment.str, old_key->comment.length);
+ std::string new_comment(new_key->comment.str, new_key->comment.length);
+ if (old_comment.compare(new_comment) != 0) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/* Check two keys to ensure that key parts within keys match */
+int ha_rocksdb::compare_key_parts(const KEY *const old_key,
+ const KEY *const new_key) const {
+ DBUG_ENTER_FUNC();
+
+ /* Skip if key parts do not match, as it is a different key */
+ if (new_key->user_defined_key_parts != old_key->user_defined_key_parts) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /* Check to see that key parts themselves match */
+ for (uint i = 0; i < old_key->user_defined_key_parts; i++) {
+ if (strcmp(old_key->key_part[i].field->field_name.str,
+ new_key->key_part[i].field->field_name.str) != 0) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ /* Check if prefix index key part length has changed */
+ if (old_key->key_part[i].length != new_key->key_part[i].length) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Create key definition needed for storing data in rocksdb.
+ This can be called either during CREATE table or doing ADD index operations.
+
+ @param in
+ table_arg Table with definition
+ i Position of index being created inside table_arg->key_info
+ tbl_def_arg Table def structure being populated
+ cf_info Struct which contains column family information
+
+ @param out
+ new_key_def Newly created index definition.
+
+ @return
+ 0 - Ok
+ other - error, either given table ddl is not supported by rocksdb or OOM.
+*/
+int ha_rocksdb::create_key_def(const TABLE *const table_arg, const uint i,
+ const Rdb_tbl_def *const tbl_def_arg,
+ std::shared_ptr<Rdb_key_def> *const new_key_def,
+ const struct key_def_cf_info &cf_info,
+ uint64 ttl_duration,
+ const std::string &ttl_column) const {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(*new_key_def == nullptr);
+
+ const uint index_id = ddl_manager.get_and_update_next_number(&dict_manager);
+ const uint16_t index_dict_version = Rdb_key_def::INDEX_INFO_VERSION_LATEST;
+ uchar index_type;
+ uint16_t kv_version;
+
+ if (is_hidden_pk(i, table_arg, tbl_def_arg)) {
+ index_type = Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY;
+ kv_version = Rdb_key_def::PRIMARY_FORMAT_VERSION_LATEST;
+ } else if (i == table_arg->s->primary_key) {
+ index_type = Rdb_key_def::INDEX_TYPE_PRIMARY;
+ uint16 pk_latest_version = Rdb_key_def::PRIMARY_FORMAT_VERSION_LATEST;
+ kv_version = pk_latest_version;
+ } else {
+ index_type = Rdb_key_def::INDEX_TYPE_SECONDARY;
+ uint16 sk_latest_version = Rdb_key_def::SECONDARY_FORMAT_VERSION_LATEST;
+ kv_version = sk_latest_version;
+ }
+
+ // Use PRIMARY_FORMAT_VERSION_UPDATE1 here since it is the same value as
+ // SECONDARY_FORMAT_VERSION_UPDATE1 so it doesn't matter if this is a
+ // primary key or secondary key.
+ DBUG_EXECUTE_IF("MYROCKS_LEGACY_VARBINARY_FORMAT", {
+ kv_version = Rdb_key_def::PRIMARY_FORMAT_VERSION_UPDATE1;
+ });
+
+ DBUG_EXECUTE_IF("MYROCKS_NO_COVERED_BITMAP_FORMAT", {
+ if (index_type == Rdb_key_def::INDEX_TYPE_SECONDARY) {
+ kv_version = Rdb_key_def::SECONDARY_FORMAT_VERSION_UPDATE2;
+ }
+ });
+
+ uint32 index_flags = (ttl_duration > 0 ? Rdb_key_def::TTL_FLAG : 0);
+
+ uint32 ttl_rec_offset =
+ Rdb_key_def::has_index_flag(index_flags, Rdb_key_def::TTL_FLAG)
+ ? Rdb_key_def::calculate_index_flag_offset(index_flags,
+ Rdb_key_def::TTL_FLAG)
+ : UINT_MAX;
+
+ const char *const key_name = get_key_name(i, table_arg, m_tbl_def);
+ *new_key_def = std::make_shared<Rdb_key_def>(
+ index_id, i, cf_info.cf_handle, index_dict_version, index_type,
+ kv_version, cf_info.is_reverse_cf, cf_info.is_per_partition_cf, key_name,
+ Rdb_index_stats(), index_flags, ttl_rec_offset, ttl_duration);
+
+ if (!ttl_column.empty()) {
+ (*new_key_def)->m_ttl_column = ttl_column;
+ }
+ // initialize key_def
+ (*new_key_def)->setup(table_arg, tbl_def_arg);
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+int rdb_normalize_tablename(const std::string &tablename,
+ std::string *const strbuf) {
+ if (tablename.size() < 2 || tablename[0] != '.' ||
+ (tablename[1] != FN_LIBCHAR && tablename[1] != FN_LIBCHAR2)) {
+ DBUG_ASSERT(0); // We were not passed table name?
+ return HA_ERR_ROCKSDB_INVALID_TABLE;
+ }
+
+ size_t pos = tablename.find_first_of(FN_LIBCHAR, 2);
+ if (pos == std::string::npos) {
+ pos = tablename.find_first_of(FN_LIBCHAR2, 2);
+ }
+
+ if (pos == std::string::npos) {
+ DBUG_ASSERT(0); // We were not passed table name?
+ return HA_ERR_ROCKSDB_INVALID_TABLE;
+ }
+
+ *strbuf = tablename.substr(2, pos - 2) + "." + tablename.substr(pos + 1);
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Check to see if the user's original statement includes foreign key
+ references
+*/
+bool ha_rocksdb::contains_foreign_key(THD *const thd) {
+ bool success;
+ const char *str = thd_query_string(thd)->str;
+
+ DBUG_ASSERT(str != nullptr);
+
+ while (*str != '\0') {
+ // Scan from our current pos looking for 'FOREIGN'
+ str = rdb_find_in_string(str, "FOREIGN", &success);
+ if (!success) {
+ return false;
+ }
+
+ // Skip past the found "FOREIGN'
+ str = rdb_check_next_token(&my_charset_bin, str, "FOREIGN", &success);
+ DBUG_ASSERT(success);
+
+ if (!my_isspace(&my_charset_bin, *str)) {
+ return false;
+ }
+
+ // See if the next token is 'KEY'
+ str = rdb_check_next_token(&my_charset_bin, str, "KEY", &success);
+ if (!success) {
+ continue;
+ }
+
+ // See if the next token is '('
+ str = rdb_check_next_token(&my_charset_bin, str, "(", &success);
+ if (!success) {
+ // There is an optional index id after 'FOREIGN KEY', skip it
+ str = rdb_skip_id(&my_charset_bin, str);
+
+ // Now check for '(' again
+ str = rdb_check_next_token(&my_charset_bin, str, "(", &success);
+ }
+
+ // If we have found 'FOREIGN KEY [<word>] (' we can be confident we have
+ // a foreign key clause.
+ return success;
+ }
+
+ // We never found a valid foreign key clause
+ return false;
+}
+
+/**
+ @brief
+ splits the normalized table name of <dbname>.<tablename>#P#<part_no> into
+ the <dbname>, <tablename> and <part_no> components.
+
+ @param dbbuf returns database name/table_schema
+ @param tablebuf returns tablename
+ @param partitionbuf returns partition suffix if there is one
+ @return HA_EXIT_SUCCESS on success, non-zero on failure to split
+*/
+int rdb_split_normalized_tablename(const std::string &fullname,
+ std::string *const db,
+ std::string *const table,
+ std::string *const partition) {
+ DBUG_ASSERT(!fullname.empty());
+
+#define RDB_PARTITION_STR "#P#"
+
+ /* Normalize returns dbname.tablename. */
+ size_t dotpos = fullname.find('.');
+
+ /* Invalid table name? */
+ if (dotpos == std::string::npos) {
+ return HA_ERR_ROCKSDB_INVALID_TABLE;
+ }
+
+ // Table must have a database name associated with it.
+ DBUG_ASSERT(dotpos > 0);
+
+ if (db != nullptr) {
+ *db = fullname.substr(0, dotpos);
+ }
+
+ dotpos++;
+
+ const size_t partpos =
+ fullname.find(RDB_PARTITION_STR, dotpos, strlen(RDB_PARTITION_STR));
+
+ if (partpos != std::string::npos) {
+ DBUG_ASSERT(partpos >= dotpos);
+
+ if (table != nullptr) {
+ *table = fullname.substr(dotpos, partpos - dotpos);
+ }
+
+ if (partition != nullptr) {
+ *partition = fullname.substr(partpos + strlen(RDB_PARTITION_STR));
+ }
+ } else if (table != nullptr) {
+ *table = fullname.substr(dotpos);
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Create a table's Rdb_tbl_def and its Rdb_key_defs and store table information
+ into MyRocks Data Dictionary
+ The method is called during create table/partition, truncate table/partition
+
+ @param table_name IN table's name formated as
+ 'dbname.tablename'
+ @param table_arg IN sql table
+ @param auto_increment_value IN specified table's auto increment value
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::create_table(const std::string &table_name,
+ const TABLE *table_arg,
+ ulonglong auto_increment_value) {
+ DBUG_ENTER_FUNC();
+
+
+
+ int err;
+
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ /* Create table/key descriptions and put them into the data dictionary */
+ m_tbl_def = new Rdb_tbl_def(table_name);
+
+ uint n_keys = table_arg->s->keys;
+
+ /*
+ If no primary key found, create a hidden PK and place it inside table
+ definition
+ */
+ if (has_hidden_pk(table_arg)) {
+ n_keys += 1;
+ // reset hidden pk id
+ // the starting valid value for hidden pk is 1
+ m_tbl_def->m_hidden_pk_val = 1;
+ }
+
+ m_key_descr_arr = new std::shared_ptr<Rdb_key_def>[n_keys];
+ m_tbl_def->m_key_count = n_keys;
+ m_tbl_def->m_key_descr_arr = m_key_descr_arr;
+
+ err = create_key_defs(table_arg, m_tbl_def);
+ if (err != HA_EXIT_SUCCESS) {
+ goto error;
+ }
+
+ m_pk_descr = m_key_descr_arr[pk_index(table_arg, m_tbl_def)];
+
+ if (auto_increment_value) {
+ bool autoinc_upgrade_test = false;
+ m_tbl_def->m_auto_incr_val = auto_increment_value;
+ DBUG_EXECUTE_IF("myrocks_autoinc_upgrade", autoinc_upgrade_test = true;);
+ if (!autoinc_upgrade_test) {
+ auto s = dict_manager.put_auto_incr_val(
+ batch, m_tbl_def->get_autoincr_gl_index_id(),
+ m_tbl_def->m_auto_incr_val);
+ if (!s.ok()) {
+ goto error;
+ }
+ }
+ }
+
+ dict_manager.lock();
+ err = ddl_manager.put_and_write(m_tbl_def, batch);
+ if (err != HA_EXIT_SUCCESS) {
+ dict_manager.unlock();
+ goto error;
+ }
+
+ err = dict_manager.commit(batch);
+ if (err != HA_EXIT_SUCCESS) {
+ dict_manager.unlock();
+ goto error;
+ }
+
+ dict_manager.unlock();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+
+error:
+ /* Delete what we have allocated so far */
+ delete m_tbl_def;
+ m_tbl_def = nullptr;
+ m_key_descr_arr = nullptr;
+
+ DBUG_RETURN(err);
+}
+
+/**
+ @brief
+ create() is called to create a table. The variable name will have the name
+ of the table.
+
+ @details
+ When create() is called you do not need to worry about
+ opening the table. Also, the .frm file will have already been
+ created so adjusting create_info is not necessary. You can overwrite
+ the .frm file at this point if you wish to change the table
+ definition, but there are no methods currently provided for doing
+ so.
+
+ Called from handle.cc by ha_create_table().
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+
+ @see
+ ha_create_table() in handle.cc
+*/
+
+int ha_rocksdb::create(const char *const name, TABLE *const table_arg,
+ HA_CREATE_INFO *const create_info) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(table_arg != nullptr);
+ DBUG_ASSERT(create_info != nullptr);
+
+ if (create_info->data_file_name) {
+ // DATA DIRECTORY is used to create tables under a specific location
+ // outside the MySQL data directory. We don't support this for MyRocks.
+ // The `rocksdb_datadir` setting should be used to configure RocksDB data
+ // directory.
+ DBUG_RETURN(HA_ERR_ROCKSDB_TABLE_DATA_DIRECTORY_NOT_SUPPORTED);
+ }
+
+ if (create_info->index_file_name) {
+ // Similar check for INDEX DIRECTORY as well.
+ DBUG_RETURN(HA_ERR_ROCKSDB_TABLE_INDEX_DIRECTORY_NOT_SUPPORTED);
+ }
+
+ int err;
+ /*
+ Construct dbname.tablename ourselves, because parititioning
+ passes strings like "./test/t14#P#p0" for individual partitions,
+ while table_arg->s->table_name has none of that.
+ */
+ std::string str;
+ err = rdb_normalize_tablename(name, &str);
+ if (err != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(err);
+ }
+
+ // FOREIGN KEY isn't supported yet
+ THD *const thd = my_core::thd_get_current_thd();
+ if (contains_foreign_key(thd)) {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "FOREIGN KEY for the RocksDB storage engine");
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+
+ // Check whether Data Dictionary contain information
+ Rdb_tbl_def *tbl = ddl_manager.find(str);
+ if (tbl != nullptr) {
+ if (thd->lex->sql_command == SQLCOM_TRUNCATE) {
+ err = delete_table(tbl);
+ if (err != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(err);
+ }
+ } else {
+ my_error(ER_METADATA_INCONSISTENCY, MYF(0), str.c_str(), name);
+ DBUG_RETURN(HA_ERR_ROCKSDB_CORRUPT_DATA);
+ }
+ }
+
+ // The below adds/clears hooks in RocksDB sync points. There's no reason for
+ // this code to be in ::create() but it needs to be somewhere where it is
+ // away from any tight loops and where one can invoke it from mtr:
+ DBUG_EXECUTE_IF("rocksdb_enable_delay_commits",
+ {
+ auto syncpoint= rocksdb::SyncPoint::GetInstance();
+ syncpoint->SetCallBack("DBImpl::WriteImpl:BeforeLeaderEnters",
+ [&](void* /*arg*/) {my_sleep(500);} );
+ syncpoint->EnableProcessing();
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_ARGUMENTS,
+ "enable_delay_commits_mode ON");
+
+ });
+ DBUG_EXECUTE_IF("rocksdb_disable_delay_commits",
+ {
+ auto syncpoint= rocksdb::SyncPoint::GetInstance();
+ syncpoint->ClearCallBack("DBImpl::WriteImpl:BeforeLeaderEnters");
+ syncpoint->DisableProcessing();
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_ARGUMENTS,
+ "enable_delay_commits_mode OFF");
+ });
+
+ DBUG_RETURN(create_table(str, table_arg, create_info->auto_increment_value));
+}
+
+/**
+ @note
+ This function is used only when the table has not yet been opened, and
+ keyread_allowed bitmap doesn't have the correct values yet.
+
+ See comment in ha_rocksdb::index_flags() for details.
+*/
+
+bool ha_rocksdb::check_keyread_allowed(uint inx, uint part,
+ bool all_parts) const {
+ bool res = true;
+ KEY *const key_info = &table_share->key_info[inx];
+
+ Rdb_field_packing dummy1;
+ res = dummy1.setup(nullptr, key_info->key_part[part].field, inx, part,
+ key_info->key_part[part].length);
+
+ if (res && all_parts) {
+ for (uint i = 0; i < part; i++) {
+ Field *field;
+ if ((field = key_info->key_part[i].field)) {
+ Rdb_field_packing dummy;
+ if (!dummy.setup(nullptr, field, inx, i,
+ key_info->key_part[i].length)) {
+ /* Cannot do index-only reads for this column */
+ res = false;
+ break;
+ }
+ }
+ }
+ }
+
+ const uint pk = table_share->primary_key;
+ if (inx == pk && all_parts &&
+ part + 1 == table_share->key_info[pk].user_defined_key_parts) {
+ m_pk_can_be_decoded = res;
+ }
+
+ return res;
+}
+
+int ha_rocksdb::read_key_exact(const Rdb_key_def &kd,
+ rocksdb::Iterator *const iter,
+ const bool /* unused */,
+ const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts) {
+ THD *thd = ha_thd();
+ /*
+ We are looking for the first record such that
+ index_tuple= lookup_tuple.
+ lookup_tuple may be a prefix of the index.
+ */
+ rocksdb_smart_seek(kd.m_is_reverse_cf, iter, key_slice);
+
+ while (iter->Valid() && kd.value_matches_prefix(iter->key(), key_slice)) {
+ if (thd && thd->killed) {
+ return HA_ERR_QUERY_INTERRUPTED;
+ }
+ /*
+ If TTL is enabled we need to check if the given key has already expired
+ from the POV of the current transaction. If it has, try going to the next
+ key.
+ */
+ if (kd.has_ttl() && should_hide_ttl_rec(kd, iter->value(), ttl_filter_ts)) {
+ rocksdb_smart_next(kd.m_is_reverse_cf, iter);
+ continue;
+ }
+
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ Got a record that is not equal to the lookup value, or even a record
+ from another table.index.
+ */
+ return HA_ERR_KEY_NOT_FOUND;
+}
+
+int ha_rocksdb::read_before_key(const Rdb_key_def &kd,
+ const bool full_key_match,
+ const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts) {
+ THD *thd = ha_thd();
+ /*
+ We are looking for record with the biggest t.key such that
+ t.key < lookup_tuple.
+ */
+ rocksdb_smart_seek(!kd.m_is_reverse_cf, m_scan_it, key_slice);
+
+ while (is_valid(m_scan_it)) {
+ if (thd && thd->killed) {
+ return HA_ERR_QUERY_INTERRUPTED;
+ }
+ /*
+ We are using full key and we've hit an exact match, or...
+
+ If TTL is enabled we need to check if the given key has already expired
+ from the POV of the current transaction. If it has, try going to the next
+ key.
+ */
+ if ((full_key_match &&
+ kd.value_matches_prefix(m_scan_it->key(), key_slice)) ||
+ (kd.has_ttl() &&
+ should_hide_ttl_rec(kd, m_scan_it->value(), ttl_filter_ts))) {
+ rocksdb_smart_next(!kd.m_is_reverse_cf, m_scan_it);
+ continue;
+ }
+
+ return HA_EXIT_SUCCESS;
+ }
+
+ return HA_ERR_KEY_NOT_FOUND;
+}
+
+int ha_rocksdb::read_after_key(const Rdb_key_def &kd,
+ const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts) {
+ THD *thd = ha_thd();
+ /*
+ We are looking for the first record such that
+
+ index_tuple $GT lookup_tuple
+
+ with HA_READ_AFTER_KEY, $GT = '>',
+ with HA_READ_KEY_OR_NEXT, $GT = '>='
+ */
+ rocksdb_smart_seek(kd.m_is_reverse_cf, m_scan_it, key_slice);
+
+ /*
+ If TTL is enabled we need to check if the given key has already expired
+ from the POV of the current transaction. If it has, try going to the next
+ key.
+ */
+ while (is_valid(m_scan_it) && kd.has_ttl() &&
+ should_hide_ttl_rec(kd, m_scan_it->value(), ttl_filter_ts)) {
+ if (thd && thd->killed) {
+ return HA_ERR_QUERY_INTERRUPTED;
+ }
+ rocksdb_smart_next(kd.m_is_reverse_cf, m_scan_it);
+ }
+
+ return is_valid(m_scan_it) ? HA_EXIT_SUCCESS : HA_ERR_KEY_NOT_FOUND;
+}
+
+int ha_rocksdb::position_to_correct_key(
+ const Rdb_key_def &kd, const enum ha_rkey_function &find_flag,
+ const bool full_key_match, const uchar *const key,
+ const key_part_map &keypart_map, const rocksdb::Slice &key_slice,
+ bool *const move_forward, const int64_t ttl_filter_ts) {
+ int rc = 0;
+
+ *move_forward = true;
+
+ switch (find_flag) {
+ case HA_READ_KEY_EXACT:
+ rc = read_key_exact(kd, m_scan_it, full_key_match, key_slice,
+ ttl_filter_ts);
+ break;
+ case HA_READ_BEFORE_KEY:
+ *move_forward = false;
+ rc = read_before_key(kd, full_key_match, key_slice, ttl_filter_ts);
+ if (rc == 0 && !kd.covers_key(m_scan_it->key())) {
+ /* The record we've got is not from this index */
+ rc = HA_ERR_KEY_NOT_FOUND;
+ }
+ break;
+ case HA_READ_AFTER_KEY:
+ case HA_READ_KEY_OR_NEXT:
+ rc = read_after_key(kd, key_slice, ttl_filter_ts);
+ if (rc == 0 && !kd.covers_key(m_scan_it->key())) {
+ /* The record we've got is not from this index */
+ rc = HA_ERR_KEY_NOT_FOUND;
+ }
+ break;
+ case HA_READ_KEY_OR_PREV:
+ case HA_READ_PREFIX:
+ /* This flag is not used by the SQL layer, so we don't support it yet. */
+ rc = HA_ERR_UNSUPPORTED;
+ break;
+ case HA_READ_PREFIX_LAST:
+ case HA_READ_PREFIX_LAST_OR_PREV:
+ *move_forward = false;
+ /*
+ Find the last record with the specified index prefix lookup.
+ - HA_READ_PREFIX_LAST requires that the record has the
+ prefix=lookup (if there are no such records,
+ HA_ERR_KEY_NOT_FOUND should be returned).
+ - HA_READ_PREFIX_LAST_OR_PREV has no such requirement. If there are no
+ records with prefix=lookup, we should return the last record
+ before that.
+ */
+ rc = read_before_key(kd, full_key_match, key_slice, ttl_filter_ts);
+ if (rc == 0) {
+ const rocksdb::Slice &rkey = m_scan_it->key();
+ if (!kd.covers_key(rkey)) {
+ /* The record we've got is not from this index */
+ rc = HA_ERR_KEY_NOT_FOUND;
+ } else if (find_flag == HA_READ_PREFIX_LAST) {
+ uint size = kd.pack_index_tuple(table, m_pack_buffer,
+ m_sk_packed_tuple, m_record_buffer,
+ key, keypart_map);
+ rocksdb::Slice lookup_tuple(
+ reinterpret_cast<char *>(m_sk_packed_tuple), size);
+
+ // We need to compare the key we've got with the original search
+ // prefix.
+ if (!kd.value_matches_prefix(rkey, lookup_tuple)) {
+ rc = HA_ERR_KEY_NOT_FOUND;
+ }
+ }
+ }
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+
+ return rc;
+}
+
+int ha_rocksdb::calc_eq_cond_len(const Rdb_key_def &kd,
+ const enum ha_rkey_function &find_flag,
+ const rocksdb::Slice &slice,
+ const int bytes_changed_by_succ,
+ const key_range *const end_key,
+ uint *const end_key_packed_size) {
+ if (find_flag == HA_READ_KEY_EXACT) return slice.size();
+
+ if (find_flag == HA_READ_PREFIX_LAST) {
+ /*
+ We have made the kd.successor(m_sk_packed_tuple) call above.
+
+ The slice is at least Rdb_key_def::INDEX_NUMBER_SIZE bytes long.
+ */
+ return slice.size() - bytes_changed_by_succ;
+ }
+
+ if (end_key) {
+ *end_key_packed_size =
+ kd.pack_index_tuple(table, m_pack_buffer, m_end_key_packed_tuple,
+ m_record_buffer, end_key->key, end_key->keypart_map);
+
+ /*
+ Calculating length of the equal conditions here. 4 byte index id is
+ included.
+ Example1: id1 BIGINT, id2 INT, id3 BIGINT, PRIMARY KEY (id1, id2, id3)
+ WHERE id1=1 AND id2=1 AND id3>=2 => eq_cond_len= 4+8+4= 16
+ WHERE id1=1 AND id2>=1 AND id3>=2 => eq_cond_len= 4+8= 12
+ Example2: id1 VARCHAR(30), id2 INT, PRIMARY KEY (id1, id2)
+ WHERE id1 = 'AAA' and id2 < 3; => eq_cond_len=13 (varchar used 9 bytes)
+ */
+ rocksdb::Slice end_slice(reinterpret_cast<char *>(m_end_key_packed_tuple),
+ *end_key_packed_size);
+ return slice.difference_offset(end_slice);
+ }
+
+ /*
+ On range scan without any end key condition, there is no
+ eq cond, and eq cond length is the same as index_id size (4 bytes).
+ Example1: id1 BIGINT, id2 INT, id3 BIGINT, PRIMARY KEY (id1, id2, id3)
+ WHERE id1>=1 AND id2 >= 2 and id2 <= 5 => eq_cond_len= 4
+ */
+ return Rdb_key_def::INDEX_NUMBER_SIZE;
+}
+
+int ha_rocksdb::read_row_from_primary_key(uchar *const buf) {
+ int rc;
+ const rocksdb::Slice &rkey = m_scan_it->key();
+ const uint pk_size = rkey.size();
+ const char *pk_data = rkey.data();
+
+ memcpy(m_pk_packed_tuple, pk_data, pk_size);
+ m_last_rowkey.copy(pk_data, pk_size, &my_charset_bin);
+
+ if (m_lock_rows != RDB_LOCK_NONE) {
+ /* We need to put a lock and re-read */
+ rc = get_row_by_rowid(buf, m_pk_packed_tuple, pk_size);
+ } else {
+ /* Unpack from the row we've read */
+ const rocksdb::Slice &value = m_scan_it->value();
+ rc = convert_record_from_storage_format(&rkey, &value, buf);
+ }
+
+ return rc;
+}
+
+int ha_rocksdb::read_row_from_secondary_key(uchar *const buf,
+ const Rdb_key_def &kd,
+ bool move_forward) {
+ int rc = 0;
+ uint pk_size;
+
+ /* Get the key columns and primary key value */
+ const rocksdb::Slice &rkey = m_scan_it->key();
+ const rocksdb::Slice &value = m_scan_it->value();
+
+#ifndef DBUG_OFF
+ bool save_keyread_only = m_keyread_only;
+#endif
+ DBUG_EXECUTE_IF("dbug.rocksdb.HA_EXTRA_KEYREAD", { m_keyread_only = true; });
+
+ bool covered_lookup = (m_keyread_only && kd.can_cover_lookup()) ||
+ kd.covers_lookup(&value, &m_lookup_bitmap);
+
+#ifndef DBUG_OFF
+ m_keyread_only = save_keyread_only;
+#endif
+
+ if (covered_lookup && m_lock_rows == RDB_LOCK_NONE) {
+ pk_size =
+ kd.get_primary_key_tuple(table, *m_pk_descr, &rkey, m_pk_packed_tuple);
+ if (pk_size == RDB_INVALID_KEY_LEN) {
+ rc = HA_ERR_ROCKSDB_CORRUPT_DATA;
+ } else {
+ rc = kd.unpack_record(table, buf, &rkey, &value,
+ m_converter->get_verify_row_debug_checksums());
+ global_stats.covered_secondary_key_lookups.inc();
+ }
+ } else {
+ if (kd.m_is_reverse_cf) move_forward = !move_forward;
+
+ rc = find_icp_matching_index_rec(move_forward, buf);
+ if (!rc) {
+ const rocksdb::Slice &rkey = m_scan_it->key();
+ pk_size = kd.get_primary_key_tuple(table, *m_pk_descr, &rkey,
+ m_pk_packed_tuple);
+ if (pk_size == RDB_INVALID_KEY_LEN) {
+ rc = HA_ERR_ROCKSDB_CORRUPT_DATA;
+ } else {
+ rc = get_row_by_rowid(buf, m_pk_packed_tuple, pk_size);
+ }
+ }
+ }
+
+ if (!rc) {
+ m_last_rowkey.copy((const char *)m_pk_packed_tuple, pk_size,
+ &my_charset_bin);
+ }
+
+ return rc;
+}
+
+/**
+ @note
+ The problem with this function is that SQL layer calls it, when
+ - the table has not been yet opened (no ::open() call done)
+ - this->table_share already exists, but it is in the process of being
+ filled, so some of fields are still NULL.
+ - In particular, table_share->key_info[inx].key_part[] is filled only up
+ to part #part. Subsequent key parts are not yet filled.
+
+ To complicate things further, SQL layer will call index_flags() with
+ all_parts=TRUE. Essentially, we're asked to provide flags for reading
+ keyparts whose datatype is not yet known.
+
+ We walk around this problem by using check_keyread_allowed(), which uses
+ table_share object and is careful not to step on unitialized data.
+
+ When we get a call with all_parts=TRUE, we try to analyze all parts but
+ ignore those that have key_part->field==nullptr (these are not initialized
+ yet).
+*/
+
+ulong ha_rocksdb::index_flags(uint inx, uint part, bool all_parts) const {
+ DBUG_ENTER_FUNC();
+
+ ulong base_flags = HA_READ_NEXT | // doesn't seem to be used
+ HA_READ_ORDER | HA_READ_RANGE | HA_READ_PREV;
+
+ if (check_keyread_allowed(inx, part, all_parts)) {
+ base_flags |= HA_KEYREAD_ONLY;
+ }
+
+ if (inx == table_share->primary_key) {
+ /*
+ Index-only reads on primary key are the same as table scan for us. Still,
+ we need to explicitly "allow" them, otherwise SQL layer will miss some
+ plans.
+ */
+ base_flags |= HA_KEYREAD_ONLY | HA_CLUSTERED_INDEX;
+ } else {
+ /*
+ We can Index Condition Pushdown any key except the primary. With primary
+ key, we get (pk, record) pair immediately, there is no place to put the
+ ICP check.
+ */
+ base_flags |= HA_DO_INDEX_COND_PUSHDOWN;
+ }
+
+ DBUG_RETURN(base_flags);
+}
+
+/**
+ @brief
+ Read next index tuple through the secondary index.
+
+ @details
+ m_scan_it points at the index key-value pair that we should read the (pk,row)
+ pair for.
+*/
+int ha_rocksdb::secondary_index_read(const int keyno, uchar *const buf) {
+ DBUG_ASSERT(table != nullptr);
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_requested++;
+#endif
+ /* Use STATUS_NOT_FOUND when record not found or some error occurred */
+ table->status = STATUS_NOT_FOUND;
+
+ if (is_valid(m_scan_it)) {
+ rocksdb::Slice key = m_scan_it->key();
+
+ /* Check if we've ran out of records of this index */
+ if (m_key_descr_arr[keyno]->covers_key(key)) {
+ int rc = 0;
+
+ // TODO: We could here check if we have ran out of range we're scanning
+ const uint size = m_key_descr_arr[keyno]->get_primary_key_tuple(
+ table, *m_pk_descr, &key, m_pk_packed_tuple);
+ if (size == RDB_INVALID_KEY_LEN) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ m_last_rowkey.copy((const char *)m_pk_packed_tuple, size,
+ &my_charset_bin);
+
+ rocksdb::Slice value = m_scan_it->value();
+ bool covered_lookup =
+ (m_keyread_only && m_key_descr_arr[keyno]->can_cover_lookup()) ||
+ m_key_descr_arr[keyno]->covers_lookup(&value, &m_lookup_bitmap);
+ if (covered_lookup && m_lock_rows == RDB_LOCK_NONE) {
+ rc = m_key_descr_arr[keyno]->unpack_record(
+ table, buf, &key, &value,
+ m_converter->get_verify_row_debug_checksums());
+ global_stats.covered_secondary_key_lookups.inc();
+ } else {
+ DEBUG_SYNC(ha_thd(), "rocksdb_concurrent_delete_sk");
+ rc = get_row_by_rowid(buf, m_pk_packed_tuple, size);
+ }
+
+ if (!rc) {
+ table->status = 0;
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_read++;
+ stats.rows_index_next++;
+#endif
+ update_row_stats(ROWS_READ);
+ }
+ return rc;
+ }
+ }
+ return HA_ERR_END_OF_FILE;
+}
+
+bool ha_rocksdb::is_using_full_key(key_part_map keypart_map,
+ uint actual_key_parts)
+{
+ return (keypart_map == HA_WHOLE_KEY) ||
+ (keypart_map == ((key_part_map(1) << actual_key_parts)
+ - 1));
+}
+
+/*
+ ha_rocksdb::read_range_first overrides handler::read_range_first.
+ The only difference from handler::read_range_first is that
+ ha_rocksdb::read_range_first passes end_key to
+ ha_rocksdb::index_read_map_impl function.
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::read_range_first(const key_range *const start_key,
+ const key_range *const end_key,
+ bool eq_range_arg, bool sorted) {
+ DBUG_ENTER_FUNC();
+
+ int result;
+
+ eq_range = eq_range_arg;
+
+ /* MariaDB: Pass RANGE_SCAN_ASC when we support ICP on backwards scans */
+ set_end_range(end_key);
+
+ range_key_part = table->key_info[active_index].key_part;
+
+ if (!start_key) {
+ // Read first record
+ result = ha_index_first(table->record[0]);
+ } else {
+#ifdef MARIAROCKS_NOT_YET
+ if (is_using_prohibited_gap_locks(
+ is_using_full_unique_key(active_index, start_key->keypart_map,
+ start_key->flag))) {
+ DBUG_RETURN(HA_ERR_LOCK_DEADLOCK);
+ }
+#endif
+ increment_statistics(&SSV::ha_read_key_count);
+
+ result =
+ index_read_map_impl(table->record[0], start_key->key,
+ start_key->keypart_map, start_key->flag, end_key);
+ }
+ if (result) {
+ DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND) ? HA_ERR_END_OF_FILE : result);
+ }
+
+ if (compare_key(end_range) <= 0) {
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+ } else {
+ /*
+ The last read row does not fall in the range. So request
+ storage engine to release row lock if possible.
+ */
+ unlock_row();
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+}
+
+int ha_rocksdb::prepare_index_scan()
+{
+ range_key_part= table->key_info[active_index].key_part;
+ set_end_range(NULL);
+ return 0;
+}
+
+
+int ha_rocksdb::prepare_range_scan(const key_range *start_key,
+ const key_range *end_key)
+{
+ range_key_part= table->key_info[active_index].key_part;
+
+ if (start_key)
+ {
+ m_save_start_range= *start_key;
+ m_start_range= &m_save_start_range;
+ }
+ else
+ m_start_range= NULL;
+
+ set_end_range(end_key);
+ return 0;
+}
+
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+
+int ha_rocksdb::index_read_map(uchar *const buf, const uchar *const key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(index_read_map_impl(buf, key, keypart_map, find_flag, nullptr));
+}
+
+/*
+ See storage/rocksdb/rocksdb-range-access.txt for description of how MySQL
+ index navigation commands are converted into RocksDB lookup commands.
+
+ This function takes end_key as an argument, and it is set on range scan.
+ MyRocks needs to decide whether prefix bloom filter can be used or not.
+ To decide to use prefix bloom filter or not, calculating equal condition
+ length
+ is needed. On equal lookups (find_flag == HA_READ_KEY_EXACT), equal
+ condition length is the same as rocksdb::Slice.size() of the start key.
+ On range scan, equal condition length is MIN(start_key, end_key) of the
+ rocksdb::Slice expression.
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_read_map_impl(uchar *const buf, const uchar *const key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag,
+ const key_range *end_key) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_EXECUTE_IF("myrocks_busy_loop_on_row_read", int debug_i = 0;
+ while (1) { debug_i++; });
+
+ int rc = 0;
+
+ THD *thd = ha_thd();
+ DEBUG_SYNC(thd, "rocksdb.check_flags_rmi");
+ if (thd && thd->killed) {
+ rc = HA_ERR_QUERY_INTERRUPTED;
+ DBUG_RETURN(rc);
+ }
+
+ const Rdb_key_def &kd = *m_key_descr_arr[active_index];
+ const uint actual_key_parts = kd.get_key_parts();
+ bool using_full_key = is_using_full_key(keypart_map, actual_key_parts);
+
+ if (!end_key) end_key = end_range;
+
+ /* By default, we don't need the retrieved records to match the prefix */
+ m_sk_match_prefix = nullptr;
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_requested++;
+#endif
+ if (active_index == table->s->primary_key && find_flag == HA_READ_KEY_EXACT &&
+ using_full_key) {
+ /*
+ Equality lookup over primary key, using full tuple.
+ This is a special case, use DB::Get.
+ */
+ const uint size = kd.pack_index_tuple(table, m_pack_buffer,
+ m_pk_packed_tuple, m_record_buffer,
+ key, keypart_map);
+ bool skip_lookup = is_blind_delete_enabled();
+
+ rc = get_row_by_rowid(buf, m_pk_packed_tuple, size, skip_lookup, false);
+
+ if (!rc && !skip_lookup) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_read++;
+ stats.rows_index_first++;
+#endif
+ update_row_stats(ROWS_READ);
+ }
+ DBUG_RETURN(rc);
+ }
+
+ /*
+ Unique secondary index performs lookups without the extended key fields
+ */
+ uint packed_size;
+ if (active_index != table->s->primary_key &&
+ table->key_info[active_index].flags & HA_NOSAME &&
+ find_flag == HA_READ_KEY_EXACT && using_full_key) {
+ key_part_map tmp_map = (key_part_map(1) << table->key_info[active_index]
+ .user_defined_key_parts) -
+ 1;
+ packed_size = kd.pack_index_tuple(table, m_pack_buffer, m_sk_packed_tuple,
+ m_record_buffer, key, tmp_map);
+ if (table->key_info[active_index].user_defined_key_parts !=
+ kd.get_key_parts()) {
+ using_full_key = false;
+ }
+ } else {
+ packed_size = kd.pack_index_tuple(table, m_pack_buffer, m_sk_packed_tuple,
+ m_record_buffer, key, keypart_map);
+ }
+
+ if ((pushed_idx_cond && pushed_idx_cond_keyno == active_index) &&
+ (find_flag == HA_READ_KEY_EXACT || find_flag == HA_READ_PREFIX_LAST)) {
+ /*
+ We are doing a point index lookup, and ICP is enabled. It is possible
+ that this call will be followed by ha_rocksdb->index_next_same() call.
+
+ Do what InnoDB does: save the lookup tuple now. We will need it in
+ index_next_same/find_icp_matching_index_rec in order to stop scanning
+ as soon as index record doesn't match the lookup tuple.
+
+ When not using ICP, handler::index_next_same() will make sure that rows
+ that don't match the lookup prefix are not returned.
+ row matches the lookup prefix.
+ */
+ m_sk_match_prefix = m_sk_match_prefix_buf;
+ m_sk_match_length = packed_size;
+ memcpy(m_sk_match_prefix, m_sk_packed_tuple, packed_size);
+ }
+
+ int bytes_changed_by_succ = 0;
+ if (find_flag == HA_READ_PREFIX_LAST_OR_PREV ||
+ find_flag == HA_READ_PREFIX_LAST || find_flag == HA_READ_AFTER_KEY) {
+ /* See below */
+ bytes_changed_by_succ = kd.successor(m_sk_packed_tuple, packed_size);
+ }
+
+ rocksdb::Slice slice(reinterpret_cast<const char *>(m_sk_packed_tuple),
+ packed_size);
+
+ uint end_key_packed_size = 0;
+ /*
+ In MariaDB, the end_key is always the bigger end of the range.
+ If we are doing a reverse-ordered scan (that is, walking from the bigger
+ key values to smaller), we should use the smaller end of range as end_key.
+ */
+ const key_range *cur_end_key= end_key;
+ if (find_flag == HA_READ_PREFIX_LAST_OR_PREV ||
+ find_flag == HA_READ_BEFORE_KEY)
+ {
+ cur_end_key= m_start_range;
+ }
+
+ const uint eq_cond_len =
+ calc_eq_cond_len(kd, find_flag, slice, bytes_changed_by_succ, cur_end_key,
+ &end_key_packed_size);
+
+ bool use_all_keys = false;
+ if (find_flag == HA_READ_KEY_EXACT &&
+ my_count_bits(keypart_map) == kd.get_key_parts()) {
+ use_all_keys = true;
+ }
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ const bool is_new_snapshot = !tx->has_snapshot();
+ // Loop as long as we get a deadlock error AND we end up creating the
+ // snapshot here (i.e. it did not exist prior to this)
+ for (;;) {
+ DEBUG_SYNC(thd, "rocksdb.check_flags_rmi_scan");
+ if (thd && thd->killed) {
+ rc = HA_ERR_QUERY_INTERRUPTED;
+ break;
+ }
+ /*
+ This will open the iterator and position it at a record that's equal or
+ greater than the lookup tuple.
+ */
+ setup_scan_iterator(kd, &slice, use_all_keys, eq_cond_len);
+
+ /*
+ Once we are positioned on from above, move to the position we really
+ want: See storage/rocksdb/rocksdb-range-access.txt
+ */
+ bool move_forward;
+ rc =
+ position_to_correct_key(kd, find_flag, using_full_key, key, keypart_map,
+ slice, &move_forward, tx->m_snapshot_timestamp);
+
+ if (rc) {
+ break;
+ }
+
+ m_skip_scan_it_next_call = false;
+
+ /*
+ Now get the data for the row into 'buf'. If we were using a primary key
+ then we have all the rows we need. For a secondary key we now need to
+ lookup the primary key.
+ */
+ if (active_index == table->s->primary_key) {
+ rc = read_row_from_primary_key(buf);
+ } else {
+ rc = read_row_from_secondary_key(buf, kd, move_forward);
+ }
+
+ if (!should_recreate_snapshot(rc, is_new_snapshot)) {
+ break; /* Exit the loop */
+ }
+
+ // release the snapshot and iterator so they will be regenerated
+ tx->release_snapshot();
+ release_scan_iterator();
+ }
+
+ if (rc) {
+ /*
+ This status is returned on any error
+ the only possible error condition is record-not-found
+ */
+ table->status = STATUS_NOT_FOUND;
+ } else {
+ table->status = 0;
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_read++;
+ stats.rows_index_first++;
+#endif
+ update_row_stats(ROWS_READ);
+ }
+
+ DBUG_RETURN(rc);
+}
+
+/*
+ @brief
+ Scan the secondary index until we find an index record that satisfies ICP
+
+ @param move_forward TRUE <=> move m_scan_it forward
+ FALSE <=> move m_scan_it backward
+ @param buf Record buffer (must be the same buffer that
+ pushed index condition points to, in practice
+ it is table->record[0])
+
+ @detail
+ Move the current iterator m_scan_it until we get an index tuple that
+ satisfies the pushed Index Condition.
+ (if there is no pushed index condition, return right away)
+
+ @return
+ 0 - Index tuple satisfies ICP, can do index read.
+ other - error code
+*/
+
+int ha_rocksdb::find_icp_matching_index_rec(const bool move_forward,
+ uchar *const buf) {
+ if (pushed_idx_cond && pushed_idx_cond_keyno == active_index) {
+ const Rdb_key_def &kd = *m_key_descr_arr[active_index];
+ THD *thd = ha_thd();
+
+ while (1) {
+ int rc = rocksdb_skip_expired_records(kd, m_scan_it, !move_forward);
+ if (rc != HA_EXIT_SUCCESS) {
+ return rc;
+ }
+
+ if (thd && thd->killed) {
+ return HA_ERR_QUERY_INTERRUPTED;
+ }
+
+ if (!is_valid(m_scan_it)) {
+ table->status = STATUS_NOT_FOUND;
+ return HA_ERR_END_OF_FILE;
+ }
+ const rocksdb::Slice rkey = m_scan_it->key();
+
+ if (!kd.covers_key(rkey)) {
+ table->status = STATUS_NOT_FOUND;
+ return HA_ERR_END_OF_FILE;
+ }
+
+ if (m_sk_match_prefix) {
+ const rocksdb::Slice prefix((const char *)m_sk_match_prefix,
+ m_sk_match_length);
+ if (!kd.value_matches_prefix(rkey, prefix)) {
+ table->status = STATUS_NOT_FOUND;
+ return HA_ERR_END_OF_FILE;
+ }
+ }
+
+ const rocksdb::Slice value = m_scan_it->value();
+ int err = kd.unpack_record(table, buf, &rkey, &value,
+ m_converter->get_verify_row_debug_checksums());
+ if (err != HA_EXIT_SUCCESS) {
+ return err;
+ }
+
+ const check_result_t icp_status= handler_index_cond_check(this);
+ if (icp_status == CHECK_NEG) {
+ rocksdb_smart_next(!move_forward, m_scan_it);
+ continue; /* Get the next (or prev) index tuple */
+ }
+ else if (icp_status == CHECK_OUT_OF_RANGE ||
+ icp_status == CHECK_ABORTED_BY_USER) {
+ /* We have walked out of range we are scanning */
+ table->status = STATUS_NOT_FOUND;
+ return HA_ERR_END_OF_FILE;
+ }
+ else /* icp_status == CHECK_POS */
+ {
+ /* Index Condition is satisfied. We have rc==0, proceed to fetch the
+ * row. */
+ break;
+ }
+ }
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+
+/**
+ @return
+ HA_ADMIN_OK OK
+ other HA_ADMIN error code
+*/
+int ha_rocksdb::check(THD *const thd, HA_CHECK_OPT *const check_opt) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(check_opt != nullptr);
+
+ const uint pk = pk_index(table, m_tbl_def);
+ String rowkey_copy;
+ String sec_key_copy;
+ const char *const table_name = table->s->table_name.str;
+
+ // Only when debugging: don't use snapshot when reading
+ // Rdb_transaction *tx= get_or_create_tx(table->in_use);
+ // tx->snapshot= nullptr;
+
+ bool save_verify_row_debug_checksums =
+ m_converter->get_verify_row_debug_checksums();
+ m_converter->set_verify_row_debug_checksums(true);
+ /* For each secondary index, check that we can get a PK value from it */
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("CHECKTABLE %s: Checking table %s", table_name,
+ table_name);
+ ha_rows UNINIT_VAR(row_checksums_at_start); // set/used iff first_index==true
+ ha_rows row_checksums = ha_rows(-1);
+ bool first_index = true;
+
+ for (uint keyno = 0; keyno < table->s->keys; keyno++) {
+ if (keyno != pk) {
+ extra(HA_EXTRA_KEYREAD);
+ ha_index_init(keyno, true);
+ ha_rows rows = 0;
+ ha_rows checksums = 0;
+ if (first_index) {
+ row_checksums_at_start = m_converter->get_row_checksums_checked();
+ }
+ int res;
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("CHECKTABLE %s: Checking index %s", table_name,
+ table->key_info[keyno].name.str);
+ while (1) {
+ if (!rows) {
+ res = index_first(table->record[0]);
+ } else {
+ res = index_next(table->record[0]);
+ }
+
+ if (res == HA_ERR_END_OF_FILE) break;
+ if (res) {
+ // error
+ // NO_LINT_DEBUG
+ sql_print_error("CHECKTABLE %s: .. row %lld: index scan error %d",
+ table_name, rows, res);
+ goto error;
+ }
+ rocksdb::Slice key = m_scan_it->key();
+ sec_key_copy.copy(key.data(), key.size(), &my_charset_bin);
+ rowkey_copy.copy(m_last_rowkey.ptr(), m_last_rowkey.length(),
+ &my_charset_bin);
+
+ if (m_key_descr_arr[keyno]->unpack_info_has_checksum(
+ m_scan_it->value())) {
+ checksums++;
+ }
+
+ if ((res = get_row_by_rowid(table->record[0], rowkey_copy.ptr(),
+ rowkey_copy.length()))) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "CHECKTABLE %s: .. row %lld: "
+ "failed to fetch row by rowid",
+ table_name, rows);
+ goto error;
+ }
+
+ longlong hidden_pk_id = 0;
+ if (has_hidden_pk(table) &&
+ read_hidden_pk_id_from_rowkey(&hidden_pk_id)) {
+ goto error;
+ }
+
+ /* Check if we get the same PK value */
+ uint packed_size = m_pk_descr->pack_record(
+ table, m_pack_buffer, table->record[0], m_pk_packed_tuple, nullptr,
+ false, hidden_pk_id);
+ if (packed_size != rowkey_copy.length() ||
+ memcmp(m_pk_packed_tuple, rowkey_copy.ptr(), packed_size)) {
+ // NO_LINT_DEBUG
+ sql_print_error("CHECKTABLE %s: .. row %lld: PK value mismatch",
+ table_name, rows);
+ goto print_and_error;
+ }
+
+ /* Check if we get the same secondary key value */
+ packed_size = m_key_descr_arr[keyno]->pack_record(
+ table, m_pack_buffer, table->record[0], m_sk_packed_tuple,
+ &m_sk_tails, false, hidden_pk_id);
+ if (packed_size != sec_key_copy.length() ||
+ memcmp(m_sk_packed_tuple, sec_key_copy.ptr(), packed_size)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "CHECKTABLE %s: .. row %lld: "
+ "secondary index value mismatch",
+ table_name, rows);
+ goto print_and_error;
+ }
+ rows++;
+ continue;
+
+ print_and_error : {
+ std::string buf;
+ buf = rdb_hexdump(rowkey_copy.ptr(), rowkey_copy.length(),
+ RDB_MAX_HEXDUMP_LEN);
+ // NO_LINT_DEBUG
+ sql_print_error("CHECKTABLE %s: rowkey: %s", table_name, buf.c_str());
+
+ buf = rdb_hexdump(m_retrieved_record.data(), m_retrieved_record.size(),
+ RDB_MAX_HEXDUMP_LEN);
+ // NO_LINT_DEBUG
+ sql_print_error("CHECKTABLE %s: record: %s", table_name, buf.c_str());
+
+ buf = rdb_hexdump(sec_key_copy.ptr(), sec_key_copy.length(),
+ RDB_MAX_HEXDUMP_LEN);
+ // NO_LINT_DEBUG
+ sql_print_error("CHECKTABLE %s: index: %s", table_name, buf.c_str());
+
+ goto error;
+ }
+ }
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("CHECKTABLE %s: ... %lld index entries checked "
+ "(%lld had checksums)",
+ table_name, rows, checksums);
+
+ if (first_index) {
+ row_checksums =
+ m_converter->get_row_checksums_checked() - row_checksums_at_start;
+ first_index = false;
+ }
+ ha_index_end();
+ }
+ }
+ if (row_checksums != ha_rows(-1)) {
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("CHECKTABLE %s: %lld table records had checksums",
+ table_name, row_checksums);
+ }
+ extra(HA_EXTRA_NO_KEYREAD);
+
+ m_converter->set_verify_row_debug_checksums(save_verify_row_debug_checksums);
+ /*
+ TODO(sergiy): we should check also for PK records that are missing in
+ the secondary indexes.
+ For that, need to walk through the PK and check that every PK record has a
+ proper counterpart in each secondary index.
+ */
+ DBUG_RETURN(HA_ADMIN_OK);
+error:
+ m_converter->set_verify_row_debug_checksums(save_verify_row_debug_checksums);
+ ha_index_or_rnd_end();
+ extra(HA_EXTRA_NO_KEYREAD);
+
+ DBUG_RETURN(HA_ADMIN_CORRUPT);
+}
+
+static void dbug_dump_str(FILE *const out, const char *const str, int len) {
+ fprintf(out, "\"");
+ for (int i = 0; i < len; i++) {
+ if (str[i] > 32) {
+ fprintf(out, "%c", str[i]);
+ } else {
+ fprintf(out, "\\%d", str[i]);
+ }
+ }
+ fprintf(out, "\"");
+}
+
+/*
+ Debugging help: dump the whole database into a human-readable file.
+ Usage:
+ dbug_dump_database(rdb);
+*/
+
+void dbug_dump_database(rocksdb::DB *const db) {
+ FILE *const out = fopen("/tmp/rocksdb.dump", "wt");
+ if (!out) return;
+
+ rocksdb::Iterator *it = db->NewIterator(rocksdb::ReadOptions());
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ rocksdb::Slice key = it->key();
+ rocksdb::Slice val = it->value();
+ dbug_dump_str(out, key.data(), key.size());
+ fprintf(out, " -> ");
+ dbug_dump_str(out, val.data(), val.size());
+ fprintf(out, "\n");
+ }
+
+ delete it;
+ fclose(out);
+}
+
+rocksdb::Status ha_rocksdb::get_for_update(
+ Rdb_transaction *const tx, rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, rocksdb::PinnableSlice *const value) const {
+ DBUG_ASSERT(m_lock_rows != RDB_LOCK_NONE);
+ const bool exclusive = m_lock_rows != RDB_LOCK_READ;
+
+ const bool do_validate =
+ my_core::thd_tx_isolation(ha_thd()) > ISO_READ_COMMITTED;
+ rocksdb::Status s =
+ tx->get_for_update(column_family, key, value, exclusive, do_validate);
+
+#ifndef DBUG_OFF
+ ++rocksdb_num_get_for_update_calls;
+#endif
+ return s;
+}
+
+bool ha_rocksdb::is_blind_delete_enabled() {
+ THD *thd = ha_thd();
+ /*
+ Note: in MariaDB, thd->lex->table_count is only set for multi-table DELETE,
+ not for single-table DELETE. So we check thd->lex->query_tables instead.
+ */
+ return (THDVAR(thd, blind_delete_primary_key) &&
+ thd->lex->sql_command == SQLCOM_DELETE &&
+ thd->lex->query_tables && !thd->lex->query_tables->next_global &&
+ table->s->keys == 1 &&
+ !has_hidden_pk(table) && !thd->rgi_slave);
+}
+
+/*
+ Given a rowid (i.e. packed PK) as a parameter, get the record.
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+
+int ha_rocksdb::get_row_by_rowid(uchar *const buf, const char *const rowid,
+ const uint rowid_size, const bool skip_lookup,
+ const bool skip_ttl_check) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(table != nullptr);
+
+ int rc;
+
+ rocksdb::Slice key_slice(rowid, rowid_size);
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ DBUG_ASSERT(tx != nullptr);
+
+#ifdef ENABLED_DEBUG_SYNC
+ DEBUG_SYNC(ha_thd(), "rocksdb.get_row_by_rowid");
+ DBUG_EXECUTE_IF("dbug.rocksdb.get_row_by_rowid", {
+ THD *thd = ha_thd();
+ const char act[] =
+ "now signal Reached "
+ "wait_for signal.rocksdb.get_row_by_rowid_let_running";
+ DBUG_ASSERT(opt_debug_sync_timeout > 0);
+ DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
+ };);
+#endif /* ENABLED_DEBUG_SYNC */
+
+ bool found;
+ rocksdb::Status s;
+
+ /* Pretend row found without looking up */
+ if (skip_lookup) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_deleted_blind++;
+#endif
+ update_row_stats(ROWS_DELETED_BLIND);
+ m_last_rowkey.copy((const char *)rowid, rowid_size, &my_charset_bin);
+ table->status = 0;
+ DBUG_RETURN(0);
+ }
+
+ if (m_lock_rows == RDB_LOCK_NONE) {
+ tx->acquire_snapshot(true);
+ s = tx->get(m_pk_descr->get_cf(), key_slice, &m_retrieved_record);
+ } else if (m_insert_with_update && m_dup_pk_found) {
+ DBUG_ASSERT(m_pk_descr->get_keyno() == m_dupp_errkey);
+ DBUG_ASSERT(m_dup_pk_retrieved_record.length() ==
+ m_retrieved_record.size());
+ DBUG_ASSERT(memcmp(m_dup_pk_retrieved_record.ptr(),
+ m_retrieved_record.data(),
+ m_retrieved_record.size()) == 0);
+
+ // do nothing - we already have the result in m_retrieved_record and
+ // already taken the lock
+ s = rocksdb::Status::OK();
+ } else {
+ s = get_for_update(tx, m_pk_descr->get_cf(), key_slice,
+ &m_retrieved_record);
+ }
+
+ DBUG_EXECUTE_IF("rocksdb_return_status_corrupted",
+ dbug_change_status_to_corrupted(&s););
+
+ if (!s.IsNotFound() && !s.ok()) {
+ DBUG_RETURN(tx->set_status_error(table->in_use, s, *m_pk_descr, m_tbl_def,
+ m_table_handler));
+ }
+ found = !s.IsNotFound();
+
+ table->status = STATUS_NOT_FOUND;
+ if (found) {
+ /* If we found the record, but it's expired, pretend we didn't find it. */
+ if (!skip_ttl_check && m_pk_descr->has_ttl() &&
+ should_hide_ttl_rec(*m_pk_descr, m_retrieved_record,
+ tx->m_snapshot_timestamp)) {
+ DBUG_RETURN(HA_ERR_KEY_NOT_FOUND);
+ }
+
+ m_last_rowkey.copy((const char *)rowid, rowid_size, &my_charset_bin);
+ rc = convert_record_from_storage_format(&key_slice, buf);
+
+ if (!rc) {
+ table->status = 0;
+ }
+ } else {
+ /*
+ Note: we don't need to unlock the row. It is intentional that we keep
+ locks on rows that don't exist.
+ */
+ rc = HA_ERR_KEY_NOT_FOUND;
+ }
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_next(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ bool moves_forward = true;
+ if (m_key_descr_arr[active_index]->m_is_reverse_cf) {
+ moves_forward = false;
+ }
+
+ int rc = index_next_with_direction(buf, moves_forward);
+ if (rc == HA_ERR_KEY_NOT_FOUND) rc = HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_prev(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ bool moves_forward = false;
+ if (m_key_descr_arr[active_index]->m_is_reverse_cf) {
+ moves_forward = true;
+ }
+
+ int rc = index_next_with_direction(buf, moves_forward);
+ if (rc == HA_ERR_KEY_NOT_FOUND) rc = HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(rc);
+}
+
+int ha_rocksdb::index_next_with_direction(uchar *const buf, bool move_forward) {
+ DBUG_ENTER_FUNC();
+
+ int rc;
+
+ if (active_index == pk_index(table, m_tbl_def)) {
+ rc = rnd_next_with_direction(buf, move_forward);
+ } else {
+ THD *thd = ha_thd();
+ for (;;) {
+ DEBUG_SYNC(thd, "rocksdb.check_flags_inwd");
+ if (thd && thd->killed) {
+ rc = HA_ERR_QUERY_INTERRUPTED;
+ break;
+ }
+ if (m_skip_scan_it_next_call) {
+ m_skip_scan_it_next_call = false;
+ } else {
+ if (move_forward) {
+ m_scan_it->Next(); /* this call cannot fail */
+ } else {
+ m_scan_it->Prev();
+ }
+ }
+ rc = rocksdb_skip_expired_records(*m_key_descr_arr[active_index],
+ m_scan_it, !move_forward);
+ if (rc != HA_EXIT_SUCCESS) {
+ break;
+ }
+ rc = find_icp_matching_index_rec(move_forward, buf);
+ if (!rc) rc = secondary_index_read(active_index, buf);
+ if (!should_skip_invalidated_record(rc)) {
+ break;
+ }
+ }
+ }
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_first(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ m_sk_match_prefix = nullptr;
+ int rc = m_key_descr_arr[active_index]->m_is_reverse_cf
+ ? index_last_intern(buf)
+ : index_first_intern(buf);
+ if (rc == HA_ERR_KEY_NOT_FOUND) rc = HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_last(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ m_sk_match_prefix = nullptr;
+ int rc = m_key_descr_arr[active_index]->m_is_reverse_cf
+ ? index_first_intern(buf)
+ : index_last_intern(buf);
+ if (rc == HA_ERR_KEY_NOT_FOUND) rc = HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(rc);
+}
+
+/*
+ Start scanning from the "first" value.
+
+ The 'first' here means "the first from start of the key space".
+ For reverse-ordered key spaces, the first value will be the biggest, 'zzzz'.
+
+ An picture of a forward-ordered keyspace (remember, the keys have form
+ 'indexnr-keyval'. Suppose the index we are at has number n)
+
+ (n-1) - ...
+ ( n ) <--- 1. (n) doesn't exist in the db but it would be here.
+ ( n ) - aaa <--- 2. Seek("n") will put us here on the first index
+ ( n ) - bbb record.
+ ( n ) - cc
+
+ So, need to do: Seek(n);
+
+ A backward-ordered keyspace:
+
+ (n+1) - bbb
+ (n+1) - aaa
+ (n+1) <--- (n+1) doesn't exist in the db but would be here.
+ ( n ) - ccc <--- 1. We need to be here.
+ ( n ) - bbb
+ ( n ) - aaa
+ ( n )
+
+ So, need to: Seek(n+1);
+
+*/
+
+int ha_rocksdb::index_first_intern(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ uchar *key;
+ uint key_size;
+ int rc;
+
+ if (is_pk(active_index, table, m_tbl_def)) {
+ key = m_pk_packed_tuple;
+ } else {
+ key = m_sk_packed_tuple;
+ }
+
+ DBUG_ASSERT(key != nullptr);
+
+ const Rdb_key_def &kd = *m_key_descr_arr[active_index];
+ int key_start_matching_bytes = kd.get_first_key(key, &key_size);
+
+ rocksdb::Slice index_key((const char *)key, key_size);
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ DBUG_ASSERT(tx != nullptr);
+
+ const bool is_new_snapshot = !tx->has_snapshot();
+ // Loop as long as we get a deadlock error AND we end up creating the
+ // snapshot here (i.e. it did not exist prior to this)
+ for (;;) {
+ setup_scan_iterator(kd, &index_key, false, key_start_matching_bytes);
+ m_scan_it->Seek(index_key);
+ m_skip_scan_it_next_call = true;
+
+ rc = index_next_with_direction(buf, true);
+ if (!should_recreate_snapshot(rc, is_new_snapshot)) {
+ break; /* exit the loop */
+ }
+
+ // release the snapshot and iterator so they will be regenerated
+ tx->release_snapshot();
+ release_scan_iterator();
+ }
+
+ if (!rc) {
+ /*
+ index_next is always incremented on success, so decrement if it is
+ index_first instead
+ */
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_index_first++;
+ stats.rows_index_next--;
+#endif
+ }
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ @details
+ Start scanning from the "last" value
+
+ The 'last' here means "the last from start of the key space".
+ For reverse-ordered key spaces, we will actually read the smallest value.
+
+ An picture of a forward-ordered keyspace (remember, the keys have form
+ 'indexnr-keyval'. Suppose the we are at a key that has number n)
+
+ (n-1)-something
+ ( n )-aaa
+ ( n )-bbb
+ ( n )-ccc <----------- Need to seek to here.
+ (n+1) <---- Doesn't exist, but would be here.
+ (n+1)-smth, or no value at all
+
+ RocksDB's Iterator::SeekForPrev($val) seeks to "at $val or last value that's
+ smaller". We can't seek to "(n)-ccc" directly, because we don't know what
+ is the value of 'ccc' (the biggest record with prefix (n)). Instead, we seek
+ to "(n+1)", which is the least possible value that's greater than any value
+ in index #n.
+
+ So, need to: it->SeekForPrev(n+1)
+
+ A backward-ordered keyspace:
+
+ (n+1)-something
+ ( n ) - ccc
+ ( n ) - bbb
+ ( n ) - aaa <---------------- (*) Need to seek here.
+ ( n ) <--- Doesn't exist, but would be here.
+ (n-1)-smth, or no value at all
+
+ So, need to: it->SeekForPrev(n)
+*/
+
+int ha_rocksdb::index_last_intern(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ uchar *key;
+ uint key_size;
+ int rc;
+
+ if (is_pk(active_index, table, m_tbl_def)) {
+ key = m_pk_packed_tuple;
+ } else {
+ key = m_sk_packed_tuple;
+ }
+
+ DBUG_ASSERT(key != nullptr);
+
+ const Rdb_key_def &kd = *m_key_descr_arr[active_index];
+ int key_end_matching_bytes = kd.get_last_key(key, &key_size);
+
+ rocksdb::Slice index_key((const char *)key, key_size);
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ DBUG_ASSERT(tx != nullptr);
+
+ bool is_new_snapshot = !tx->has_snapshot();
+ // Loop as long as we get a deadlock error AND we end up creating the
+ // snapshot here (i.e. it did not exist prior to this)
+ for (;;) {
+ setup_scan_iterator(kd, &index_key, false, key_end_matching_bytes);
+ m_scan_it->SeekForPrev(index_key);
+ m_skip_scan_it_next_call = false;
+
+ if (is_pk(active_index, table, m_tbl_def)) {
+ m_skip_scan_it_next_call = true;
+ rc = rnd_next_with_direction(buf, false);
+ } else {
+ rc = find_icp_matching_index_rec(false /*move_forward*/, buf);
+ if (!rc) rc = secondary_index_read(active_index, buf);
+ }
+
+ if (!should_recreate_snapshot(rc, is_new_snapshot)) {
+ break; /* exit the loop */
+ }
+
+ // release the snapshot and iterator so they will be regenerated
+ tx->release_snapshot();
+ release_scan_iterator();
+ }
+
+ if (!rc) {
+ /*
+ index_next is always incremented on success, so decrement if it is
+ index_first instead
+ */
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_index_first++;
+ stats.rows_index_next--;
+#endif
+ }
+
+ DBUG_RETURN(rc);
+}
+
+void ha_rocksdb::unlock_row() {
+ DBUG_ENTER_FUNC();
+
+ if (m_lock_rows != RDB_LOCK_NONE) {
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ tx->release_lock(m_pk_descr->get_cf(),
+ std::string(m_last_rowkey.ptr(), m_last_rowkey.length()));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ Returning true if SingleDelete can be used.
+ - Secondary Indexes can always use SingleDelete.
+ - If the index is PRIMARY KEY, and if all of the columns of the table
+ are covered by the PRIMARY KEY, SingleDelete can be used.
+*/
+bool ha_rocksdb::can_use_single_delete(const uint index) const {
+ return (index != pk_index(table, m_tbl_def) ||
+ (!has_hidden_pk(table) &&
+ table->key_info[index].ext_key_parts == table->s->fields));
+}
+
+bool ha_rocksdb::skip_unique_check() const {
+ /*
+ We want to skip unique checks if:
+ 1) bulk_load is on
+ 2) this table is in the whitelist of tables to skip and the replication
+ lag has reached a large enough value (see unique_check_lag_threshold
+ and unique_check_lage_reset_threshold)
+ 3) the user set unique_checks option to 0, and the table does not have
+ any indexes. If the table has secondary keys, then those might becomes
+ inconsisted/corrupted
+ 4) We're using read-free replication
+ */
+ return THDVAR(table->in_use, bulk_load) ||
+ (m_force_skip_unique_check && m_skip_unique_check) ||
+ (my_core::thd_test_options(table->in_use,
+ OPTION_RELAXED_UNIQUE_CHECKS) &&
+ m_tbl_def->m_key_count == 1) ||
+#ifdef MARIAROCKS_NOT_YET
+ use_read_free_rpl();
+#else
+ FALSE;
+#endif
+}
+
+#ifdef MARIAROCKS_NOT_YET // MDEV-10975
+void ha_rocksdb::set_force_skip_unique_check(bool skip) {
+ DBUG_ENTER_FUNC();
+
+ m_force_skip_unique_check = skip;
+
+ DBUG_VOID_RETURN;
+}
+#endif
+
+bool ha_rocksdb::commit_in_the_middle() {
+ return THDVAR(table->in_use, bulk_load) ||
+ THDVAR(table->in_use, commit_in_the_middle);
+}
+
+/*
+ Executing bulk commit if it should.
+ @retval true if bulk commit failed
+ @retval false if bulk commit was skipped or succeeded
+*/
+bool ha_rocksdb::do_bulk_commit(Rdb_transaction *const tx) {
+ return commit_in_the_middle() &&
+ tx->get_write_count() >= THDVAR(table->in_use, bulk_load_size) &&
+ tx->flush_batch();
+}
+
+/*
+ If table was created without primary key, SQL layer represents the primary
+ key number as MAX_INDEXES. Hence, this function returns true if the table
+ does not contain a primary key. (In which case we generate a hidden
+ 'auto-incremented' pk.)
+*/
+bool ha_rocksdb::has_hidden_pk(const TABLE *const table) const {
+ return Rdb_key_def::table_has_hidden_pk(table);
+}
+
+/*
+ Returns true if given index number is a hidden_pk.
+ - This is used when a table is created with no primary key.
+*/
+bool ha_rocksdb::is_hidden_pk(const uint index, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg) {
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ return (table_arg->s->primary_key == MAX_INDEXES &&
+ index == tbl_def_arg->m_key_count - 1);
+}
+
+/* Returns index of primary key */
+uint ha_rocksdb::pk_index(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg) {
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ return table_arg->s->primary_key == MAX_INDEXES ? tbl_def_arg->m_key_count - 1
+ : table_arg->s->primary_key;
+}
+
+/* Returns true if given index number is a primary key */
+bool ha_rocksdb::is_pk(const uint index, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg) {
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ return index == table_arg->s->primary_key ||
+ is_hidden_pk(index, table_arg, tbl_def_arg);
+}
+
+uint ha_rocksdb::max_supported_key_part_length() const {
+ DBUG_ENTER_FUNC();
+ DBUG_RETURN(rocksdb_large_prefix ? MAX_INDEX_COL_LEN_LARGE
+ : MAX_INDEX_COL_LEN_SMALL);
+}
+
+const char *ha_rocksdb::get_key_name(const uint index,
+ const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg) {
+ if (is_hidden_pk(index, table_arg, tbl_def_arg)) {
+ return HIDDEN_PK_NAME;
+ }
+
+ DBUG_ASSERT(table_arg->key_info != nullptr);
+ DBUG_ASSERT(table_arg->key_info[index].name.str != nullptr);
+
+ return table_arg->key_info[index].name.str;
+}
+
+const char *ha_rocksdb::get_key_comment(const uint index,
+ const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg) {
+ if (is_hidden_pk(index, table_arg, tbl_def_arg)) {
+ return nullptr;
+ }
+
+ DBUG_ASSERT(table_arg->key_info != nullptr);
+
+ return table_arg->key_info[index].comment.str;
+}
+
+const std::string ha_rocksdb::generate_cf_name(
+ const uint index, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg, bool *per_part_match_found) {
+ DBUG_ASSERT(table_arg != nullptr);
+ DBUG_ASSERT(tbl_def_arg != nullptr);
+ DBUG_ASSERT(per_part_match_found != nullptr);
+
+ // When creating CF-s the caller needs to know if there was a custom CF name
+ // specified for a given paritition.
+ *per_part_match_found = false;
+
+ // Index comment is used to define the column family name specification(s).
+ // If there was no comment, we get an emptry string, and it means "use the
+ // default column family".
+ const char *const comment = get_key_comment(index, table_arg, tbl_def_arg);
+
+ // `get_key_comment` can return `nullptr`, that's why this.
+ std::string key_comment = comment ? comment : "";
+
+ std::string cf_name = Rdb_key_def::parse_comment_for_qualifier(
+ key_comment, table_arg, tbl_def_arg, per_part_match_found,
+ RDB_CF_NAME_QUALIFIER);
+
+ if (IF_PARTITIONING(table_arg->part_info,nullptr) != nullptr && !*per_part_match_found) {
+ // At this point we tried to search for a custom CF name for a partition,
+ // but none was specified. Therefore default one will be used.
+ return "";
+ }
+
+ // If we didn't find any partitioned/non-partitioned qualifiers, return the
+ // comment itself. NOTE: this currently handles returning the cf name
+ // specified in the index comment in the case of no partitions, which doesn't
+ // use any qualifiers at the moment. (aka its a special case)
+ if (cf_name.empty() && !key_comment.empty()) {
+ return key_comment;
+ }
+
+ return cf_name;
+}
+
+const std::string ha_rocksdb::get_table_comment(const TABLE *const table_arg) {
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ return table_arg->s->comment.str;
+}
+
+/**
+ Write a new row
+
+ @param[in] buf new row data to write
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::write_row(const uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(buf != nullptr);
+ DBUG_ASSERT(buf == table->record[0]);
+ DBUG_ASSERT(m_lock_rows == RDB_LOCK_WRITE);
+
+ /*
+ Note: "buf == table->record[0]" is copied from innodb. I am not aware of
+ any use cases where this condition is not true.
+ */
+ if (table->next_number_field && buf == table->record[0]) {
+ int err;
+ if ((err = update_auto_increment())) {
+ DBUG_RETURN(err);
+ }
+ }
+
+ // clear cache at beginning of write for INSERT ON DUPLICATE
+ // we may get multiple write->fail->read->update if there are multiple
+ // values from INSERT
+ m_dup_pk_found = false;
+
+ const int rv = update_write_row(nullptr, buf, skip_unique_check());
+
+ if (rv == 0) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_inserted++;
+#endif
+ update_row_stats(ROWS_INSERTED);
+ }
+
+ DBUG_RETURN(rv);
+}
+
+/**
+ Constructing m_last_rowkey (MyRocks key expression) from
+ before_update|delete image (MySQL row expression).
+ m_last_rowkey is normally set during lookup phase, such as
+ rnd_next_with_direction() and rnd_pos(). With Read Free Replication,
+ these read functions are skipped and update_rows(), delete_rows() are
+ called without setting m_last_rowkey. This function sets m_last_rowkey
+ for Read Free Replication.
+*/
+void ha_rocksdb::set_last_rowkey(const uchar *const old_data) {
+#ifdef MARIAROCKS_NOT_YET
+ if (old_data && use_read_free_rpl()) {
+ const int old_pk_size = m_pk_descr->pack_record(
+ table, m_pack_buffer, old_data, m_pk_packed_tuple, nullptr, false);
+ m_last_rowkey.copy((const char *)m_pk_packed_tuple, old_pk_size,
+ &my_charset_bin);
+ }
+#endif
+}
+
+/**
+ Collect update data for primary key
+
+ @param[in, out] row_info hold all data for update row, such as
+ new row data/old row data
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::get_pk_for_update(struct update_row_info *const row_info) {
+ int size;
+
+ /*
+ Get new row key for any insert, and any update where the pk is not hidden.
+ Row key for updates with hidden pk is handled below.
+ */
+ if (!has_hidden_pk(table)) {
+ row_info->hidden_pk_id = 0;
+
+ row_info->new_pk_unpack_info = &m_pk_unpack_info;
+
+ size = m_pk_descr->pack_record(
+ table, m_pack_buffer, row_info->new_data, m_pk_packed_tuple,
+ row_info->new_pk_unpack_info, false, 0, 0, nullptr);
+ } else if (row_info->old_data == nullptr) {
+ row_info->hidden_pk_id = update_hidden_pk_val();
+ size =
+ m_pk_descr->pack_hidden_pk(row_info->hidden_pk_id, m_pk_packed_tuple);
+ } else {
+ /*
+ If hidden primary key, rowkey for new record will always be the same as
+ before
+ */
+ size = row_info->old_pk_slice.size();
+ memcpy(m_pk_packed_tuple, row_info->old_pk_slice.data(), size);
+ int err = read_hidden_pk_id_from_rowkey(&row_info->hidden_pk_id);
+ if (err) {
+ return err;
+ }
+ }
+
+ row_info->new_pk_slice =
+ rocksdb::Slice((const char *)m_pk_packed_tuple, size);
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Check the specified primary key value is unique and also lock the row
+
+ @param[in] key_id key index
+ @param[in] row_info hold all data for update row, such as old row
+ data and new row data
+ @param[out] found whether the primary key exists before.
+ @param[out] pk_changed whether primary key is changed
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::check_and_lock_unique_pk(const uint key_id,
+ const struct update_row_info &row_info,
+ bool *const found) {
+ DBUG_ASSERT(found != nullptr);
+
+ DBUG_ASSERT(row_info.old_pk_slice.size() == 0 ||
+ row_info.new_pk_slice.compare(row_info.old_pk_slice) != 0);
+
+ /* Ignore PK violations if this is a optimized 'replace into' */
+#ifdef MARIAROCKS_NOT_YET
+ const bool ignore_pk_unique_check = ha_thd()->lex->blind_replace_into;
+#else
+ const bool ignore_pk_unique_check= false;
+#endif
+
+ /*
+ Perform a read to determine if a duplicate entry exists. For primary
+ keys, a point lookup will be sufficient.
+
+ note: we intentionally don't set options.snapshot here. We want to read
+ the latest committed data.
+ */
+
+ /*
+ To prevent race conditions like below, it is necessary to
+ take a lock for a target row. get_for_update() holds a gap lock if
+ target key does not exist, so below conditions should never
+ happen.
+
+ 1) T1 Get(empty) -> T2 Get(empty) -> T1 Put(insert) -> T1 commit
+ -> T2 Put(overwrite) -> T2 commit
+ 2) T1 Get(empty) -> T1 Put(insert, not committed yet) -> T2 Get(empty)
+ -> T2 Put(insert, blocked) -> T1 commit -> T2 commit(overwrite)
+ */
+ const rocksdb::Status s =
+ get_for_update(row_info.tx, m_pk_descr->get_cf(), row_info.new_pk_slice,
+ ignore_pk_unique_check ? nullptr : &m_retrieved_record);
+ if (!s.ok() && !s.IsNotFound()) {
+ return row_info.tx->set_status_error(
+ table->in_use, s, *m_key_descr_arr[key_id], m_tbl_def, m_table_handler);
+ }
+
+ bool key_found = ignore_pk_unique_check ? false : !s.IsNotFound();
+
+ /*
+ If the pk key has ttl, we may need to pretend the row wasn't
+ found if it is already expired.
+ */
+ if (key_found && m_pk_descr->has_ttl() &&
+ should_hide_ttl_rec(*m_pk_descr, m_retrieved_record,
+ (row_info.tx->m_snapshot_timestamp
+ ? row_info.tx->m_snapshot_timestamp
+ : static_cast<int64_t>(std::time(nullptr))))) {
+ key_found = false;
+ }
+
+ if (key_found && row_info.old_data == nullptr && m_insert_with_update) {
+ // In INSERT ON DUPLICATE KEY UPDATE ... case, if the insert failed
+ // due to a duplicate key, remember the last key and skip the check
+ // next time
+ m_dup_pk_found = true;
+
+#ifndef DBUG_OFF
+ // save it for sanity checking later
+ m_dup_pk_retrieved_record.copy(m_retrieved_record.data(),
+ m_retrieved_record.size(), &my_charset_bin);
+#endif
+ }
+
+ *found = key_found;
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Check the specified secondary key value is unique and also lock the row
+
+ @param[in] key_id key index
+ @param[in] row_info hold all data for update row, such as old row
+ data and new row data
+ @param[out] found whether specified key value exists before.
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::check_and_lock_sk(const uint key_id,
+ const struct update_row_info &row_info,
+ bool *const found) {
+ DBUG_ASSERT(found != nullptr);
+ *found = false;
+
+ /*
+ Can skip checking this key if none of the key fields have changed.
+ */
+ if (row_info.old_data != nullptr && !m_update_scope.is_set(key_id)) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ KEY *key_info = nullptr;
+ uint n_null_fields = 0;
+ uint user_defined_key_parts = 1;
+
+ key_info = &table->key_info[key_id];
+ user_defined_key_parts = key_info->user_defined_key_parts;
+ /*
+ If there are no uniqueness requirements, there's no need to obtain a
+ lock for this key.
+ */
+ if (!(key_info->flags & HA_NOSAME)) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ const Rdb_key_def &kd = *m_key_descr_arr[key_id];
+
+ /*
+ Calculate the new key for obtaining the lock
+
+ For unique secondary indexes, the key used for locking does not
+ include the extended fields.
+ */
+ int size =
+ kd.pack_record(table, m_pack_buffer, row_info.new_data, m_sk_packed_tuple,
+ nullptr, false, 0, user_defined_key_parts, &n_null_fields);
+ if (n_null_fields > 0) {
+ /*
+ If any fields are marked as NULL this will never match another row as
+ to NULL never matches anything else including another NULL.
+ */
+ return HA_EXIT_SUCCESS;
+ }
+
+ const rocksdb::Slice new_slice =
+ rocksdb::Slice((const char *)m_sk_packed_tuple, size);
+
+ /*
+ Acquire lock on the old key in case of UPDATE
+ */
+ if (row_info.old_data != nullptr) {
+ size = kd.pack_record(table, m_pack_buffer, row_info.old_data,
+ m_sk_packed_tuple_old, nullptr, false, 0,
+ user_defined_key_parts);
+ const rocksdb::Slice old_slice =
+ rocksdb::Slice((const char *)m_sk_packed_tuple_old, size);
+
+ const rocksdb::Status s =
+ get_for_update(row_info.tx, kd.get_cf(), old_slice, nullptr);
+ if (!s.ok()) {
+ return row_info.tx->set_status_error(table->in_use, s, kd, m_tbl_def,
+ m_table_handler);
+ }
+
+ /*
+ If the old and new keys are the same we're done since we've already taken
+ the lock on the old key
+ */
+ if (!new_slice.compare(old_slice)) {
+ return HA_EXIT_SUCCESS;
+ }
+ }
+
+ /*
+ Perform a read to determine if a duplicate entry exists - since this is
+ a secondary indexes a range scan is needed.
+
+ note: we intentionally don't set options.snapshot here. We want to read
+ the latest committed data.
+ */
+
+ const bool all_parts_used = (user_defined_key_parts == kd.get_key_parts());
+
+ /*
+ This iterator seems expensive since we need to allocate and free
+ memory for each unique index.
+
+ If this needs to be optimized, for keys without NULL fields, the
+ extended primary key fields can be migrated to the value portion of the
+ key. This enables using Get() instead of Seek() as in the primary key
+ case.
+
+ The bloom filter may need to be disabled for this lookup.
+ */
+ uchar lower_bound_buf[Rdb_key_def::INDEX_NUMBER_SIZE];
+ uchar upper_bound_buf[Rdb_key_def::INDEX_NUMBER_SIZE];
+ rocksdb::Slice lower_bound_slice;
+ rocksdb::Slice upper_bound_slice;
+
+ const bool total_order_seek = !check_bloom_and_set_bounds(
+ ha_thd(), kd, new_slice, all_parts_used, Rdb_key_def::INDEX_NUMBER_SIZE,
+ lower_bound_buf, upper_bound_buf, &lower_bound_slice, &upper_bound_slice);
+ const bool fill_cache = !THDVAR(ha_thd(), skip_fill_cache);
+
+ const rocksdb::Status s =
+ get_for_update(row_info.tx, kd.get_cf(), new_slice, nullptr);
+ if (!s.ok() && !s.IsNotFound()) {
+ return row_info.tx->set_status_error(table->in_use, s, kd, m_tbl_def,
+ m_table_handler);
+ }
+
+ rocksdb::Iterator *const iter = row_info.tx->get_iterator(
+ kd.get_cf(), total_order_seek, fill_cache, lower_bound_slice,
+ upper_bound_slice, true /* read current data */,
+ false /* acquire snapshot */);
+ /*
+ Need to scan the transaction to see if there is a duplicate key.
+ Also need to scan RocksDB and verify the key has not been deleted
+ in the transaction.
+ */
+ iter->Seek(new_slice);
+ *found = !read_key_exact(kd, iter, all_parts_used, new_slice,
+ row_info.tx->m_snapshot_timestamp);
+ delete iter;
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Enumerate all keys to check their uniquess and also lock it
+
+ @param[in] row_info hold all data for update row, such as old row
+ data and new row data
+ @param[out] pk_changed whether primary key is changed
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::check_uniqueness_and_lock(
+ const struct update_row_info &row_info, bool pk_changed) {
+ /*
+ Go through each index and determine if the index has uniqueness
+ requirements. If it does, then try to obtain a row lock on the new values.
+ Once all locks have been obtained, then perform the changes needed to
+ update/insert the row.
+ */
+ for (uint key_id = 0; key_id < m_tbl_def->m_key_count; key_id++) {
+ bool found;
+ int rc;
+
+ if (is_pk(key_id, table, m_tbl_def)) {
+ if (row_info.old_pk_slice.size() > 0 && !pk_changed) {
+ found = false;
+ rc = HA_EXIT_SUCCESS;
+ } else {
+ rc = check_and_lock_unique_pk(key_id, row_info, &found);
+ }
+ } else {
+ rc = check_and_lock_sk(key_id, row_info, &found);
+ }
+
+ if (rc != HA_EXIT_SUCCESS) {
+ return rc;
+ }
+
+ if (found) {
+ /* There is a row with this key already, so error out. */
+ errkey = key_id;
+ m_dupp_errkey = errkey;
+
+ return HA_ERR_FOUND_DUPP_KEY;
+ }
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Check whether secondary key value is duplicate or not
+
+ @param[in] table_arg the table currently working on
+ @param[in key_def the key_def is being checked
+ @param[in] key secondary key storage data
+ @param[out] sk_info hold secondary key memcmp datas(new/old)
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+
+int ha_rocksdb::check_duplicate_sk(const TABLE *table_arg,
+ const Rdb_key_def &key_def,
+ const rocksdb::Slice *key,
+ struct unique_sk_buf_info *sk_info) {
+ uint n_null_fields = 0;
+ const rocksdb::Comparator *index_comp = key_def.get_cf()->GetComparator();
+
+ /* Get proper SK buffer. */
+ uchar *sk_buf = sk_info->swap_and_get_sk_buf();
+
+ /* Get memcmp form of sk without extended pk tail */
+ uint sk_memcmp_size =
+ key_def.get_memcmp_sk_parts(table_arg, *key, sk_buf, &n_null_fields);
+
+ sk_info->sk_memcmp_key =
+ rocksdb::Slice(reinterpret_cast<char *>(sk_buf), sk_memcmp_size);
+
+ if (sk_info->sk_memcmp_key_old.size() > 0 && n_null_fields == 0 &&
+ index_comp->Compare(sk_info->sk_memcmp_key, sk_info->sk_memcmp_key_old) ==
+ 0) {
+ return 1;
+ }
+
+ sk_info->sk_memcmp_key_old = sk_info->sk_memcmp_key;
+ return 0;
+}
+
+int ha_rocksdb::bulk_load_key(Rdb_transaction *const tx, const Rdb_key_def &kd,
+ const rocksdb::Slice &key,
+ const rocksdb::Slice &value, bool sort) {
+ DBUG_ENTER_FUNC();
+ int res;
+ THD *thd = ha_thd();
+ if (thd && thd->killed) {
+ DBUG_RETURN(HA_ERR_QUERY_INTERRUPTED);
+ }
+
+ rocksdb::ColumnFamilyHandle *cf = kd.get_cf();
+
+ // In the case of unsorted inserts, m_sst_info allocated here is not
+ // used to store the keys. It is still used to indicate when tables
+ // are switched.
+ if (m_sst_info == nullptr || m_sst_info->is_done()) {
+ m_sst_info.reset(new Rdb_sst_info(rdb, m_table_handler->m_table_name,
+ kd.get_name(), cf, *rocksdb_db_options,
+ THDVAR(ha_thd(), trace_sst_api)));
+ res = tx->start_bulk_load(this, m_sst_info);
+ if (res != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(res);
+ }
+ }
+ DBUG_ASSERT(m_sst_info);
+
+ if (sort) {
+ Rdb_index_merge *key_merge;
+ DBUG_ASSERT(cf != nullptr);
+
+ res = tx->get_key_merge(kd.get_gl_index_id(), cf, &key_merge);
+ if (res == HA_EXIT_SUCCESS) {
+ res = key_merge->add(key, value);
+ }
+ } else {
+ res = m_sst_info->put(key, value);
+ }
+
+ DBUG_RETURN(res);
+}
+
+int ha_rocksdb::finalize_bulk_load(bool print_client_error) {
+ DBUG_ENTER_FUNC();
+
+ int res = HA_EXIT_SUCCESS;
+
+ /* Skip if there are no possible ongoing bulk loads */
+ if (m_sst_info) {
+ if (m_sst_info->is_done()) {
+ m_sst_info.reset();
+ DBUG_RETURN(res);
+ }
+
+ Rdb_sst_info::Rdb_sst_commit_info commit_info;
+
+ // Wrap up the current work in m_sst_info and get ready to commit
+ // This transfer the responsibility of commit over to commit_info
+ res = m_sst_info->finish(&commit_info, print_client_error);
+ if (res == 0) {
+ // Make sure we have work to do - under race condition we could lose
+ // to another thread and end up with no work
+ if (commit_info.has_work()) {
+ rocksdb::IngestExternalFileOptions opts;
+ opts.move_files = true;
+ opts.snapshot_consistency = false;
+ opts.allow_global_seqno = false;
+ opts.allow_blocking_flush = false;
+
+ const rocksdb::Status s = rdb->IngestExternalFile(
+ commit_info.get_cf(), commit_info.get_committed_files(), opts);
+ if (!s.ok()) {
+ if (print_client_error) {
+ Rdb_sst_info::report_error_msg(s, nullptr);
+ }
+ res = HA_ERR_ROCKSDB_BULK_LOAD;
+ } else {
+ // Mark the list of SST files as committed, otherwise they'll get
+ // cleaned up when commit_info destructs
+ commit_info.commit();
+ }
+ }
+ }
+ m_sst_info.reset();
+ }
+ DBUG_RETURN(res);
+}
+
+/**
+ Update an existing primary key record or write a new primary key record
+
+ @param[in] kd the primary key is being update/write
+ @param[in] update_row_info hold all row data, such as old row data and
+ new row data
+ @param[in] pk_changed whether primary key is changed
+ @return
+ HA_EXIT_SUCCESS OK
+ Other HA_ERR error code (can be SE-specific)
+ */
+int ha_rocksdb::update_write_pk(const Rdb_key_def &kd,
+ const struct update_row_info &row_info,
+ bool pk_changed) {
+ uint key_id = kd.get_keyno();
+ bool hidden_pk = is_hidden_pk(key_id, table, m_tbl_def);
+ ulonglong bytes_written = 0;
+
+ /*
+ If the PK has changed, or if this PK uses single deletes and this is an
+ update, the old key needs to be deleted. In the single delete case, it
+ might be possible to have this sequence of keys: PUT(X), PUT(X), SD(X),
+ resulting in the first PUT(X) showing up.
+ */
+ if (!hidden_pk && (pk_changed || ((row_info.old_pk_slice.size() > 0) &&
+ can_use_single_delete(key_id)))) {
+ const rocksdb::Status s = delete_or_singledelete(
+ key_id, row_info.tx, kd.get_cf(), row_info.old_pk_slice);
+ if (!s.ok()) {
+ return row_info.tx->set_status_error(table->in_use, s, kd, m_tbl_def,
+ m_table_handler);
+ } else {
+ bytes_written = row_info.old_pk_slice.size();
+ }
+ }
+
+ if (table->found_next_number_field) {
+ update_auto_incr_val_from_field();
+ }
+
+ int rc = HA_EXIT_SUCCESS;
+ rocksdb::Slice value_slice;
+ /* Prepare the new record to be written into RocksDB */
+ if ((rc = m_converter->encode_value_slice(
+ m_pk_descr, row_info.new_pk_slice, row_info.new_pk_unpack_info,
+ !row_info.old_pk_slice.empty(), should_store_row_debug_checksums(),
+ m_ttl_bytes, &m_ttl_bytes_updated, &value_slice))) {
+ return rc;
+ }
+
+ const auto cf = m_pk_descr->get_cf();
+ if (rocksdb_enable_bulk_load_api && THDVAR(table->in_use, bulk_load) &&
+ !hidden_pk) {
+ /*
+ Write the primary key directly to an SST file using an SstFileWriter
+ */
+ rc = bulk_load_key(row_info.tx, kd, row_info.new_pk_slice, value_slice,
+ THDVAR(table->in_use, bulk_load_allow_unsorted));
+ } else if (row_info.skip_unique_check || row_info.tx->m_ddl_transaction) {
+ /*
+ It is responsibility of the user to make sure that the data being
+ inserted doesn't violate any unique keys.
+ */
+ row_info.tx->get_indexed_write_batch()->Put(cf, row_info.new_pk_slice,
+ value_slice);
+ } else {
+ const bool assume_tracked = can_assume_tracked(ha_thd());
+ const auto s = row_info.tx->put(cf, row_info.new_pk_slice, value_slice,
+ assume_tracked);
+ if (!s.ok()) {
+ if (s.IsBusy()) {
+ errkey = table->s->primary_key;
+ m_dupp_errkey = errkey;
+ rc = HA_ERR_FOUND_DUPP_KEY;
+ } else {
+ rc = row_info.tx->set_status_error(table->in_use, s, *m_pk_descr,
+ m_tbl_def, m_table_handler);
+ }
+ }
+ }
+
+ if (rc == HA_EXIT_SUCCESS) {
+ row_info.tx->update_bytes_written(
+ bytes_written + row_info.new_pk_slice.size() + value_slice.size());
+ }
+ return rc;
+}
+
+/**
+ update an existing secondary key record or write a new secondary key record
+
+ @param[in] table_arg Table we're working on
+ @param[in] kd The secondary key being update/write
+ @param[in] row_info data structure contains old row data and new row data
+ @param[in] bulk_load_sk whether support bulk load. Currently it is only
+ support for write
+ @return
+ HA_EXIT_SUCCESS OK
+ Other HA_ERR error code (can be SE-specific)
+ */
+int ha_rocksdb::update_write_sk(const TABLE *const table_arg,
+ const Rdb_key_def &kd,
+ const struct update_row_info &row_info,
+ const bool bulk_load_sk) {
+ int new_packed_size;
+ int old_packed_size;
+ int rc = HA_EXIT_SUCCESS;
+
+ rocksdb::Slice new_key_slice;
+ rocksdb::Slice new_value_slice;
+ rocksdb::Slice old_key_slice;
+
+ const uint key_id = kd.get_keyno();
+
+ ulonglong bytes_written = 0;
+
+ /*
+ Can skip updating this key if none of the key fields have changed and, if
+ this table has TTL, the TTL timestamp has not changed.
+ */
+ if (row_info.old_data != nullptr && !m_update_scope.is_set(key_id) &&
+ (!kd.has_ttl() || !m_ttl_bytes_updated)) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ bool store_row_debug_checksums = should_store_row_debug_checksums();
+ new_packed_size =
+ kd.pack_record(table_arg, m_pack_buffer, row_info.new_data,
+ m_sk_packed_tuple, &m_sk_tails, store_row_debug_checksums,
+ row_info.hidden_pk_id, 0, nullptr, m_ttl_bytes);
+
+ if (row_info.old_data != nullptr) {
+ // The old value
+ old_packed_size = kd.pack_record(
+ table_arg, m_pack_buffer, row_info.old_data, m_sk_packed_tuple_old,
+ &m_sk_tails_old, store_row_debug_checksums, row_info.hidden_pk_id, 0,
+ nullptr, m_ttl_bytes);
+
+ /*
+ Check if we are going to write the same value. This can happen when
+ one does
+ UPDATE tbl SET col='foo'
+ and we are looking at the row that already has col='foo'.
+
+ We also need to compare the unpack info. Suppose, the collation is
+ case-insensitive, and unpack info contains information about whether
+ the letters were uppercase and lowercase. Then, both 'foo' and 'FOO'
+ will have the same key value, but different data in unpack_info.
+
+ (note: anyone changing bytewise_compare should take this code into
+ account)
+ */
+ if (old_packed_size == new_packed_size &&
+ m_sk_tails_old.get_current_pos() == m_sk_tails.get_current_pos() &&
+ !(kd.has_ttl() && m_ttl_bytes_updated) &&
+ memcmp(m_sk_packed_tuple_old, m_sk_packed_tuple, old_packed_size) ==
+ 0 &&
+ memcmp(m_sk_tails_old.ptr(), m_sk_tails.ptr(),
+ m_sk_tails.get_current_pos()) == 0) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ Deleting entries from secondary index should skip locking, but
+ be visible to the transaction.
+ (also note that DDL statements do not delete rows, so this is not a DDL
+ statement)
+ */
+ old_key_slice = rocksdb::Slice(
+ reinterpret_cast<const char *>(m_sk_packed_tuple_old), old_packed_size);
+
+ row_info.tx->get_indexed_write_batch()->SingleDelete(kd.get_cf(),
+ old_key_slice);
+
+ bytes_written = old_key_slice.size();
+ }
+
+ new_key_slice = rocksdb::Slice(
+ reinterpret_cast<const char *>(m_sk_packed_tuple), new_packed_size);
+ new_value_slice =
+ rocksdb::Slice(reinterpret_cast<const char *>(m_sk_tails.ptr()),
+ m_sk_tails.get_current_pos());
+
+ if (bulk_load_sk && row_info.old_data == nullptr) {
+ rc = bulk_load_key(row_info.tx, kd, new_key_slice, new_value_slice, true);
+ } else {
+ row_info.tx->get_indexed_write_batch()->Put(kd.get_cf(), new_key_slice,
+ new_value_slice);
+ }
+
+ row_info.tx->update_bytes_written(bytes_written + new_key_slice.size() +
+ new_value_slice.size());
+
+ return rc;
+}
+
+/**
+ Update existing indexes(PK/SKs) or write new indexes(PK/SKs)
+
+ @param[in] row_info hold all row data, such as old key/new key
+ @param[in] pk_changed whether primary key is changed
+ @return
+ HA_EXIT_SUCCESS OK
+ Other HA_ERR error code (can be SE-specific)
+ */
+int ha_rocksdb::update_write_indexes(const struct update_row_info &row_info,
+ const bool pk_changed) {
+ int rc;
+ bool bulk_load_sk;
+
+ // The PK must be updated first to pull out the TTL value.
+ rc = update_write_pk(*m_pk_descr, row_info, pk_changed);
+ if (rc != HA_EXIT_SUCCESS) {
+ return rc;
+ }
+
+ // Update the remaining indexes. Allow bulk loading only if
+ // allow_sk is enabled
+ bulk_load_sk = rocksdb_enable_bulk_load_api &&
+ THDVAR(table->in_use, bulk_load) &&
+ THDVAR(table->in_use, bulk_load_allow_sk);
+ for (uint key_id = 0; key_id < m_tbl_def->m_key_count; key_id++) {
+ if (is_pk(key_id, table, m_tbl_def)) {
+ continue;
+ }
+
+ rc = update_write_sk(table, *m_key_descr_arr[key_id], row_info,
+ bulk_load_sk);
+ if (rc != HA_EXIT_SUCCESS) {
+ return rc;
+ }
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Update an existing row or write a new row
+
+ @param[in] old_data nullptr for write, non-null for update
+ @param[in] new_data non-null for write/update
+ @param[in] skip_unique_check whether to check uniqueness
+ @return
+ HA_EXIT_SUCCESS OK
+ Other HA_ERR error code (can be SE-specific)
+ */
+int ha_rocksdb::update_write_row(const uchar *const old_data,
+ const uchar *const new_data,
+ const bool skip_unique_check) {
+ DBUG_ENTER_FUNC();
+
+ THD *thd = ha_thd();
+ if (thd && thd->killed) {
+ DBUG_RETURN(HA_ERR_QUERY_INTERRUPTED);
+ }
+
+ bool pk_changed = false;
+ struct update_row_info row_info;
+
+ row_info.old_data = old_data;
+ row_info.new_data = new_data;
+ row_info.skip_unique_check = skip_unique_check;
+ row_info.new_pk_unpack_info = nullptr;
+ set_last_rowkey(old_data);
+
+ row_info.tx = get_or_create_tx(table->in_use);
+
+ if (old_data != nullptr) {
+ row_info.old_pk_slice =
+ rocksdb::Slice(m_last_rowkey.ptr(), m_last_rowkey.length());
+
+ /* Determine which indexes need updating. */
+ calc_updated_indexes();
+ }
+
+ /*
+ Get the new row key into row_info.new_pk_slice
+ */
+ int rc = get_pk_for_update(&row_info);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ /*
+ For UPDATEs, if the key has changed, we need to obtain a lock. INSERTs
+ always require locking.
+ */
+ if (row_info.old_pk_slice.size() > 0) {
+ pk_changed = row_info.new_pk_slice.compare(row_info.old_pk_slice) != 0;
+ }
+
+ if (!skip_unique_check) {
+ /*
+ Check to see if we are going to have failures because of unique
+ keys. Also lock the appropriate key values.
+ */
+ rc = check_uniqueness_and_lock(row_info, pk_changed);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+ }
+
+ DEBUG_SYNC(ha_thd(), "rocksdb.update_write_row_after_unique_check");
+
+ /*
+ At this point, all locks have been obtained, and all checks for duplicate
+ keys have been performed. No further errors can be allowed to occur from
+ here because updates to the transaction will be made and those updates
+ cannot be easily removed without rolling back the entire transaction.
+ */
+ rc = update_write_indexes(row_info, pk_changed);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ if (old_data != nullptr) {
+ row_info.tx->incr_update_count();
+ } else {
+ row_info.tx->incr_insert_count();
+ }
+
+ row_info.tx->log_table_write_op(m_tbl_def);
+
+ if (do_bulk_commit(row_info.tx)) {
+ DBUG_RETURN(HA_ERR_ROCKSDB_BULK_LOAD);
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Setting iterator upper/lower bounds for Seek/SeekForPrev.
+ This makes RocksDB to avoid scanning tombstones outside of
+ the given key ranges, when prefix_same_as_start=true was not passed
+ (when prefix bloom filter can not be used).
+ Inversing upper/lower bound is necessary on reverse order CF.
+ This covers HA_READ_PREFIX_LAST* case as well. For example,
+ if given query eq condition was 12 bytes and condition was
+ 0x0000b3eb003f65c5e78858b8, and if doing HA_READ_PREFIX_LAST,
+ eq_cond_len was 11 (see calc_eq_cond_len() for details).
+ If the index was reverse order, upper bound would be
+ 0x0000b3eb003f65c5e78857, and lower bound would be
+ 0x0000b3eb003f65c5e78859. These cover given eq condition range.
+
+ @param lower_bound_buf IN Buffer for lower bound
+ @param upper_bound_buf IN Buffer for upper bound
+
+ @param outer_u
+*/
+void ha_rocksdb::setup_iterator_bounds(
+ const Rdb_key_def &kd, const rocksdb::Slice &eq_cond, size_t bound_len,
+ uchar *const lower_bound, uchar *const upper_bound,
+ rocksdb::Slice *lower_bound_slice, rocksdb::Slice *upper_bound_slice) {
+ // If eq_cond is shorter than Rdb_key_def::INDEX_NUMBER_SIZE, we should be
+ // able to get better bounds just by using index id directly.
+ if (eq_cond.size() <= Rdb_key_def::INDEX_NUMBER_SIZE) {
+ DBUG_ASSERT(bound_len == Rdb_key_def::INDEX_NUMBER_SIZE);
+ uint size;
+ kd.get_infimum_key(lower_bound, &size);
+ DBUG_ASSERT(size == Rdb_key_def::INDEX_NUMBER_SIZE);
+ kd.get_supremum_key(upper_bound, &size);
+ DBUG_ASSERT(size == Rdb_key_def::INDEX_NUMBER_SIZE);
+ } else {
+ DBUG_ASSERT(bound_len <= eq_cond.size());
+ memcpy(upper_bound, eq_cond.data(), bound_len);
+ kd.successor(upper_bound, bound_len);
+ memcpy(lower_bound, eq_cond.data(), bound_len);
+ kd.predecessor(lower_bound, bound_len);
+ }
+
+ if (kd.m_is_reverse_cf) {
+ *upper_bound_slice = rocksdb::Slice((const char *)lower_bound, bound_len);
+ *lower_bound_slice = rocksdb::Slice((const char *)upper_bound, bound_len);
+ } else {
+ *upper_bound_slice = rocksdb::Slice((const char *)upper_bound, bound_len);
+ *lower_bound_slice = rocksdb::Slice((const char *)lower_bound, bound_len);
+ }
+}
+
+/*
+ Open a cursor
+*/
+
+void ha_rocksdb::setup_scan_iterator(const Rdb_key_def &kd,
+ rocksdb::Slice *const slice,
+ const bool use_all_keys,
+ const uint eq_cond_len) {
+ DBUG_ASSERT(slice->size() >= eq_cond_len);
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+
+ bool skip_bloom = true;
+
+ const rocksdb::Slice eq_cond(slice->data(), eq_cond_len);
+ // The size of m_scan_it_lower_bound (and upper) is technically
+ // max_packed_sk_len as calculated in ha_rocksdb::alloc_key_buffers. Rather
+ // than recalculating that number, we pass in the max of eq_cond_len and
+ // Rdb_key_def::INDEX_NUMBER_SIZE which is guaranteed to be smaller than
+ // max_packed_sk_len, hence ensuring no buffer overrun.
+ //
+ // See ha_rocksdb::setup_iterator_bounds on how the bound_len parameter is
+ // used.
+ if (check_bloom_and_set_bounds(
+ ha_thd(), kd, eq_cond, use_all_keys,
+ std::max(eq_cond_len, (uint)Rdb_key_def::INDEX_NUMBER_SIZE),
+ m_scan_it_lower_bound, m_scan_it_upper_bound,
+ &m_scan_it_lower_bound_slice, &m_scan_it_upper_bound_slice)) {
+ skip_bloom = false;
+ }
+
+ /*
+ In some cases, setup_scan_iterator() is called multiple times from
+ the same query but bloom filter can not always be used.
+ Suppose the following query example. id2 is VARCHAR(30) and PRIMARY KEY
+ (id1, id2).
+ select count(*) from t2 WHERE id1=100 and id2 IN ('00000000000000000000',
+ '100');
+ In this case, setup_scan_iterator() is called twice, the first time is for
+ (id1, id2)=(100, '00000000000000000000') and the second time is for (100,
+ '100').
+ If prefix bloom filter length is 24 bytes, prefix bloom filter can be used
+ for the
+ first condition but not for the second condition.
+ If bloom filter condition is changed, currently it is necessary to destroy
+ and
+ re-create Iterator.
+ */
+ if (m_scan_it_skips_bloom != skip_bloom) {
+ release_scan_iterator();
+ }
+
+ /*
+ SQL layer can call rnd_init() multiple times in a row.
+ In that case, re-use the iterator, but re-position it at the table start.
+ */
+ if (!m_scan_it) {
+ const bool fill_cache = !THDVAR(ha_thd(), skip_fill_cache);
+ if (commit_in_the_middle()) {
+ DBUG_ASSERT(m_scan_it_snapshot == nullptr);
+ m_scan_it_snapshot = rdb->GetSnapshot();
+
+ auto read_opts = rocksdb::ReadOptions();
+ // TODO(mung): set based on WHERE conditions
+ read_opts.total_order_seek = true;
+ read_opts.snapshot = m_scan_it_snapshot;
+ m_scan_it = rdb->NewIterator(read_opts, kd.get_cf());
+ } else {
+ m_scan_it = tx->get_iterator(kd.get_cf(), skip_bloom, fill_cache,
+ m_scan_it_lower_bound_slice,
+ m_scan_it_upper_bound_slice);
+ }
+ m_scan_it_skips_bloom = skip_bloom;
+ }
+}
+
+void ha_rocksdb::release_scan_iterator() {
+ delete m_scan_it;
+ m_scan_it = nullptr;
+
+ if (m_scan_it_snapshot) {
+ rdb->ReleaseSnapshot(m_scan_it_snapshot);
+ m_scan_it_snapshot = nullptr;
+ }
+}
+
+void ha_rocksdb::setup_iterator_for_rnd_scan() {
+ uint key_size;
+
+ int key_start_matching_bytes =
+ m_pk_descr->get_first_key(m_pk_packed_tuple, &key_size);
+
+ rocksdb::Slice table_key((const char *)m_pk_packed_tuple, key_size);
+
+ setup_scan_iterator(*m_pk_descr, &table_key, false, key_start_matching_bytes);
+ m_scan_it->Seek(table_key);
+ m_skip_scan_it_next_call = true;
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::rnd_init(bool scan) {
+ DBUG_ENTER_FUNC();
+
+ THD *thd = ha_thd();
+ if (thd && thd->killed) {
+ DBUG_RETURN(HA_ERR_QUERY_INTERRUPTED);
+ }
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+
+ // when this table is being updated, decode all fields
+ m_converter->setup_field_decoders(table->read_set,
+ m_lock_rows == RDB_LOCK_WRITE);
+
+ if (scan) {
+ m_rnd_scan_is_new_snapshot = !tx->has_snapshot();
+ setup_iterator_for_rnd_scan();
+ } else {
+ /* We don't need any preparations for rnd_pos() calls. */
+ }
+
+ // If m_lock_rows is on then we will be doing a get_for_update when accessing
+ // the index, so don't acquire the snapshot right away. Otherwise acquire
+ // the snapshot immediately.
+ tx->acquire_snapshot(m_lock_rows == RDB_LOCK_NONE);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::rnd_next(uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ int rc;
+ for (;;) {
+ rc = rnd_next_with_direction(buf, true);
+ if (!should_recreate_snapshot(rc, m_rnd_scan_is_new_snapshot)) {
+ break; /* exit the loop */
+ }
+ // release the snapshot and iterator and then regenerate them
+ Rdb_transaction *tx = get_or_create_tx(table->in_use);
+ tx->release_snapshot();
+ release_scan_iterator();
+ setup_iterator_for_rnd_scan();
+ }
+
+ m_rnd_scan_is_new_snapshot = false;
+
+ if (rc == HA_ERR_KEY_NOT_FOUND) rc = HA_ERR_END_OF_FILE;
+
+ DBUG_RETURN(rc);
+}
+
+/*
+ See also secondary_index_read().
+*/
+int ha_rocksdb::rnd_next_with_direction(uchar *const buf, bool move_forward) {
+ DBUG_ENTER_FUNC();
+
+ int rc;
+ THD *thd = ha_thd();
+
+ table->status = STATUS_NOT_FOUND;
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_requested++;
+#endif
+ if (!m_scan_it || !is_valid(m_scan_it)) {
+ /*
+ We can get here when SQL layer has called
+
+ h->index_init(PRIMARY);
+ h->index_read_map(full index tuple, HA_READ_KEY_EXACT);
+
+ In this case, we should return EOF.
+ */
+ DBUG_RETURN(HA_ERR_END_OF_FILE);
+ }
+
+ for (;;) {
+ DEBUG_SYNC(thd, "rocksdb.check_flags_rnwd");
+ if (thd && thd->killed) {
+ rc = HA_ERR_QUERY_INTERRUPTED;
+ break;
+ }
+
+ if (m_skip_scan_it_next_call) {
+ m_skip_scan_it_next_call = false;
+ } else {
+ if (move_forward) {
+ m_scan_it->Next(); /* this call cannot fail */
+ } else {
+ m_scan_it->Prev(); /* this call cannot fail */
+ }
+ }
+
+ if (!is_valid(m_scan_it)) {
+ rc = HA_ERR_END_OF_FILE;
+ break;
+ }
+
+ /* check if we're out of this table */
+ const rocksdb::Slice key = m_scan_it->key();
+ if (!m_pk_descr->covers_key(key)) {
+ rc = HA_ERR_END_OF_FILE;
+ break;
+ }
+
+ if (m_lock_rows != RDB_LOCK_NONE) {
+ /*
+ Lock the row we've just read.
+
+ Now we call get_for_update which will 1) Take a lock and 2) Will fail
+ if the row was deleted since the snapshot was taken.
+ */
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ DEBUG_SYNC(ha_thd(), "rocksdb_concurrent_delete");
+
+ if (m_pk_descr->has_ttl() &&
+ should_hide_ttl_rec(*m_pk_descr, m_scan_it->value(),
+ tx->m_snapshot_timestamp)) {
+ continue;
+ }
+
+ const rocksdb::Status s =
+ get_for_update(tx, m_pk_descr->get_cf(), key, &m_retrieved_record);
+ if (s.IsNotFound() &&
+ should_skip_invalidated_record(HA_ERR_KEY_NOT_FOUND)) {
+ continue;
+ }
+
+ if (!s.ok()) {
+ DBUG_RETURN(tx->set_status_error(table->in_use, s, *m_pk_descr,
+ m_tbl_def, m_table_handler));
+ }
+
+ // If we called get_for_update() use the value from that call not from
+ // the iterator as it may be stale since we don't have a snapshot
+ // when m_lock_rows is not RDB_LOCK_NONE.
+ m_last_rowkey.copy(key.data(), key.size(), &my_charset_bin);
+ rc = convert_record_from_storage_format(&key, buf);
+ } else {
+ // Use the value from the iterator
+ rocksdb::Slice value = m_scan_it->value();
+
+ if (m_pk_descr->has_ttl() &&
+ should_hide_ttl_rec(
+ *m_pk_descr, value,
+ get_or_create_tx(table->in_use)->m_snapshot_timestamp)) {
+ continue;
+ }
+
+ m_last_rowkey.copy(key.data(), key.size(), &my_charset_bin);
+ rc = convert_record_from_storage_format(&key, &value, buf);
+ }
+
+ table->status = 0;
+ break;
+ }
+
+ if (!rc) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_read++;
+ stats.rows_index_next++;
+#endif
+ update_row_stats(ROWS_READ);
+ }
+
+ DBUG_RETURN(rc);
+}
+
+int ha_rocksdb::rnd_end() {
+ DBUG_ENTER_FUNC();
+
+ release_scan_iterator();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::index_init(uint idx, bool sorted) {
+ DBUG_ENTER_FUNC();
+
+ THD *thd = ha_thd();
+ if (thd && thd->killed) {
+ DBUG_RETURN(HA_ERR_QUERY_INTERRUPTED);
+ }
+
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ DBUG_ASSERT(tx != nullptr);
+
+ m_start_range= NULL;
+ // when this table is being updated, decode all fields
+ m_converter->setup_field_decoders(table->read_set,
+ m_lock_rows == RDB_LOCK_WRITE);
+
+ if (!m_keyread_only) {
+ m_key_descr_arr[idx]->get_lookup_bitmap(table, &m_lookup_bitmap);
+ }
+
+ // If m_lock_rows is not RDB_LOCK_NONE then we will be doing a get_for_update
+ // when accessing the index, so don't acquire the snapshot right away.
+ // Otherwise acquire the snapshot immediately.
+ tx->acquire_snapshot(m_lock_rows == RDB_LOCK_NONE);
+
+ active_index = idx;
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+*/
+int ha_rocksdb::index_end() {
+ DBUG_ENTER_FUNC();
+
+ release_scan_iterator();
+
+ bitmap_free(&m_lookup_bitmap);
+
+ active_index = MAX_KEY;
+ in_range_check_pushed_down = FALSE;
+ m_start_range= NULL;
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::truncate() {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(m_tbl_def != nullptr);
+
+ // Save table name to use later
+ std::string table_name = m_tbl_def->full_tablename();
+
+ // Delete current table
+ int err = delete_table(m_tbl_def);
+ if (err != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(err);
+ }
+
+ // Reset auto_increment_value to 1 if auto-increment feature is enabled
+ // By default, the starting valid value for auto_increment_value is 1
+ DBUG_RETURN(create_table(
+ table_name, table,
+ table->found_next_number_field ? 1 : 0 /* auto_increment_value */));
+}
+
+/*
+ Delete the row we've last read. The row is also passed as parameter.
+
+ @detail
+ The caller guarantees table buf points to the row that was just read.
+ The row is either table->record[0] or table->record[1].
+ (Check out InnoDB: row_update_for_mysql() has "UT_NOT_USED(mysql_rec)"
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::delete_row(const uchar *const buf) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(buf != nullptr);
+
+ set_last_rowkey(buf);
+
+ rocksdb::Slice key_slice(m_last_rowkey.ptr(), m_last_rowkey.length());
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+ ulonglong bytes_written = 0;
+
+ const uint index = pk_index(table, m_tbl_def);
+ rocksdb::Status s =
+ delete_or_singledelete(index, tx, m_pk_descr->get_cf(), key_slice);
+ if (!s.ok()) {
+ DBUG_RETURN(tx->set_status_error(table->in_use, s, *m_pk_descr, m_tbl_def,
+ m_table_handler));
+ } else {
+ bytes_written = key_slice.size();
+ }
+
+ longlong hidden_pk_id = 0;
+ if (m_tbl_def->m_key_count > 1 && has_hidden_pk(table)) {
+ int err = read_hidden_pk_id_from_rowkey(&hidden_pk_id);
+ if (err) {
+ DBUG_RETURN(err);
+ }
+ }
+
+ // Delete the record for every secondary index
+ for (uint i = 0; i < m_tbl_def->m_key_count; i++) {
+ if (!is_pk(i, table, m_tbl_def)) {
+ int packed_size;
+ const Rdb_key_def &kd = *m_key_descr_arr[i];
+ packed_size = kd.pack_record(table, m_pack_buffer, buf, m_sk_packed_tuple,
+ nullptr, false, hidden_pk_id);
+ rocksdb::Slice secondary_key_slice(
+ reinterpret_cast<const char *>(m_sk_packed_tuple), packed_size);
+ /* Deleting on secondary key doesn't need any locks: */
+ tx->get_indexed_write_batch()->SingleDelete(kd.get_cf(),
+ secondary_key_slice);
+ bytes_written += secondary_key_slice.size();
+ }
+ }
+
+ tx->incr_delete_count();
+ tx->log_table_write_op(m_tbl_def);
+
+ if (do_bulk_commit(tx)) {
+ DBUG_RETURN(HA_ERR_ROCKSDB_BULK_LOAD);
+ }
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_deleted++;
+#endif
+ update_row_stats(ROWS_DELETED);
+ tx->update_bytes_written(bytes_written);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+rocksdb::Status ha_rocksdb::delete_or_singledelete(
+ uint index, Rdb_transaction *const tx,
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key) {
+ const bool assume_tracked = can_assume_tracked(ha_thd());
+ if (can_use_single_delete(index)) {
+ return tx->single_delete(column_family, key, assume_tracked);
+ }
+ return tx->delete_key(column_family, key, assume_tracked);
+}
+
+void ha_rocksdb::update_stats(void) {
+ DBUG_ENTER_FUNC();
+
+ stats.records = 0;
+ stats.index_file_length = 0ul;
+ stats.data_file_length = 0ul;
+ stats.mean_rec_length = 0;
+
+ for (uint i = 0; i < m_tbl_def->m_key_count; i++) {
+ if (is_pk(i, table, m_tbl_def)) {
+ stats.data_file_length = m_pk_descr->m_stats.m_actual_disk_size;
+ stats.records = m_pk_descr->m_stats.m_rows;
+ } else {
+ stats.index_file_length += m_key_descr_arr[i]->m_stats.m_actual_disk_size;
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ HA_EXIT_FAILURE Error
+*/
+int ha_rocksdb::info(uint flag) {
+ DBUG_ENTER_FUNC();
+
+ if (!table) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ if (flag & HA_STATUS_VARIABLE) {
+ /*
+ Test only to simulate corrupted stats
+ */
+ DBUG_EXECUTE_IF("myrocks_simulate_negative_stats",
+ m_pk_descr->m_stats.m_actual_disk_size =
+ -m_pk_descr->m_stats.m_actual_disk_size;);
+
+ update_stats();
+
+ /*
+ If any stats are negative due to bad cached stats, re-run analyze table
+ and re-retrieve the stats.
+ */
+ if (static_cast<longlong>(stats.data_file_length) < 0 ||
+ static_cast<longlong>(stats.index_file_length) < 0 ||
+ static_cast<longlong>(stats.records) < 0) {
+ if (calculate_stats_for_table()) {
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ update_stats();
+ }
+
+ // if number of records is hardcoded, we do not want to force computation
+ // of memtable cardinalities
+ if (stats.records == 0 || (rocksdb_force_compute_memtable_stats &&
+ rocksdb_debug_optimizer_n_rows == 0)) {
+ // First, compute SST files stats
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2];
+ auto r = get_range(pk_index(table, m_tbl_def), buf);
+ uint64_t sz = 0;
+ uint8_t include_flags = rocksdb::DB::INCLUDE_FILES;
+ // recompute SST files stats only if records count is 0
+ if (stats.records == 0) {
+ rdb->GetApproximateSizes(m_pk_descr->get_cf(), &r, 1, &sz,
+ include_flags);
+ stats.records += sz / ROCKSDB_ASSUMED_KEY_VALUE_DISK_SIZE;
+ stats.data_file_length += sz;
+ }
+ // Second, compute memtable stats. This call is expensive, so cache
+ // values computed for some time.
+ uint64_t cachetime = rocksdb_force_compute_memtable_stats_cachetime;
+ uint64_t time = (cachetime == 0) ? 0 : my_interval_timer() / 1000;
+ if (cachetime == 0 ||
+ time > m_table_handler->m_mtcache_last_update + cachetime) {
+ uint64_t memtableCount;
+ uint64_t memtableSize;
+
+ // the stats below are calculated from skiplist wich is a probablistic
+ // data structure, so the results vary between test runs
+ // it also can return 0 for quite a large tables which means that
+ // cardinality for memtable only indxes will be reported as 0
+ rdb->GetApproximateMemTableStats(m_pk_descr->get_cf(), r,
+ &memtableCount, &memtableSize);
+
+ // Atomically update all of these fields at the same time
+ if (cachetime > 0) {
+ if (m_table_handler->m_mtcache_lock.fetch_add(
+ 1, std::memory_order_acquire) == 0) {
+ m_table_handler->m_mtcache_count = memtableCount;
+ m_table_handler->m_mtcache_size = memtableSize;
+ m_table_handler->m_mtcache_last_update = time;
+ }
+ m_table_handler->m_mtcache_lock.fetch_sub(1,
+ std::memory_order_release);
+ }
+
+ stats.records += memtableCount;
+ stats.data_file_length += memtableSize;
+ } else {
+ // Cached data is still valid, so use it instead
+ stats.records += m_table_handler->m_mtcache_count;
+ stats.data_file_length += m_table_handler->m_mtcache_size;
+ }
+
+ // Do like InnoDB does. stats.records=0 confuses the optimizer
+ if (stats.records == 0 && !(flag & (HA_STATUS_TIME | HA_STATUS_OPEN))) {
+ stats.records++;
+ }
+ }
+
+ if (rocksdb_debug_optimizer_n_rows > 0)
+ stats.records = rocksdb_debug_optimizer_n_rows;
+
+ if (stats.records != 0) {
+ stats.mean_rec_length = stats.data_file_length / stats.records;
+ }
+ }
+
+ if (flag & HA_STATUS_CONST) {
+ ref_length = m_pk_descr->max_storage_fmt_length();
+
+ for (uint i = 0; i < m_tbl_def->m_key_count; i++) {
+ if (is_hidden_pk(i, table, m_tbl_def)) {
+ continue;
+ }
+ KEY *const k = &table->key_info[i];
+ for (uint j = 0; j < k->ext_key_parts; j++) {
+ const Rdb_index_stats &k_stats = m_key_descr_arr[i]->m_stats;
+ uint x;
+
+ if (k_stats.m_distinct_keys_per_prefix.size() > j &&
+ k_stats.m_distinct_keys_per_prefix[j] > 0) {
+ x = k_stats.m_rows / k_stats.m_distinct_keys_per_prefix[j];
+ /*
+ If the number of rows is less than the number of prefixes (due to
+ sampling), the average number of rows with the same prefix is 1.
+ */
+ if (x == 0) {
+ x = 1;
+ }
+ } else {
+ x = 0;
+ }
+ if (x > stats.records) x = stats.records;
+ if ((x == 0 && rocksdb_debug_optimizer_no_zero_cardinality) ||
+ rocksdb_debug_optimizer_n_rows > 0) {
+ // Fake cardinality implementation. For example, (idx1, idx2, idx3)
+ // index
+ /*
+ Make MariaRocks behave the same way as MyRocks does:
+ 1. SQL layer thinks that unique secondary indexes are not extended
+ with PK columns (both in MySQL and MariaDB)
+ 2. MariaDB also thinks that indexes with partially-covered columns
+ are not extended with PK columns. Use the same number of
+ keyparts that MyRocks would use.
+ */
+ uint ext_key_parts2;
+ if (k->flags & HA_NOSAME)
+ ext_key_parts2= k->ext_key_parts; // This is #1
+ else
+ ext_key_parts2= m_key_descr_arr[i]->get_key_parts(); // This is #2.
+
+ // will have rec_per_key for (idx1)=4, (idx1,2)=2, and (idx1,2,3)=1.
+ // rec_per_key for the whole index is 1, and multiplied by 2^n if
+ // n suffix columns of the index are not used.
+ x = 1 << (ext_key_parts2 - j - 1);
+ }
+ k->rec_per_key[j] = x;
+ }
+ }
+
+ stats.create_time = m_tbl_def->get_create_time();
+ }
+
+ if (flag & HA_STATUS_TIME) {
+ stats.update_time = m_tbl_def->m_update_time;
+ }
+
+ if (flag & HA_STATUS_ERRKEY) {
+ /*
+ Currently we support only primary keys so we know which key had a
+ uniqueness violation.
+ */
+ errkey = m_dupp_errkey;
+ dup_ref = m_pk_tuple; // TODO(?): this should store packed PK.
+ }
+
+ if (flag & HA_STATUS_AUTO) {
+ stats.auto_increment_value = m_tbl_def->m_auto_incr_val;
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+void ha_rocksdb::position(const uchar *const record) {
+ DBUG_ENTER_FUNC();
+
+ longlong hidden_pk_id = 0;
+ if (has_hidden_pk(table) && read_hidden_pk_id_from_rowkey(&hidden_pk_id)) {
+ DBUG_ASSERT(false); // should never reach here
+ }
+
+ /*
+ Get packed primary key value from the record.
+
+ (Note: m_last_rowkey has the packed PK of last-read-row, which allows to
+ handle most cases, but there is an exception to this: when slave applies
+ RBR events, it fills the record and then calls position(); rnd_pos())
+
+ Also note that we *can* rely on PK column values being available. This is
+ because table_flags() includes HA_PRIMARY_KEY_REQUIRED_FOR_POSITION bit.
+ When that is true, table->prepare_for_position() adds PK columns into the
+ read set (this may potentially disable index-only access when PK column
+ cannot be restored from its mem-comparable form in the secondary indexes).
+ */
+ const uint packed_size = m_pk_descr->pack_record(
+ table, m_pack_buffer, record, ref, nullptr, false, hidden_pk_id);
+
+ /*
+ It could be that mem-comparable form of PK occupies less than ref_length
+ bytes. Fill the remainder with zeros.
+ */
+ if (ref_length > packed_size) {
+ memset(ref + packed_size, 0, ref_length - packed_size);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::rnd_pos(uchar *const buf, uchar *const pos) {
+ DBUG_ENTER_FUNC();
+
+ int rc;
+ size_t len;
+
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_requested++;
+#endif
+ len = m_pk_descr->key_length(table,
+ rocksdb::Slice((const char *)pos, ref_length));
+ if (len == size_t(-1)) {
+ DBUG_RETURN(HA_ERR_ROCKSDB_CORRUPT_DATA); /* Data corruption? */
+ }
+
+ rc = get_row_by_rowid(buf, pos, len);
+
+ if (!rc) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_read++;
+#endif
+ update_row_stats(ROWS_READ);
+ }
+
+ DBUG_RETURN(rc);
+}
+
+/*
+ @brief
+ Calculate (if needed) the bitmap of indexes that are modified by the
+ current query
+
+ @detail
+ The calculation done by checking index definitions against the
+ table->write_set
+ column bitmap.
+*/
+
+void ha_rocksdb::calc_updated_indexes() {
+ if (!m_update_scope_is_valid) {
+ m_update_scope_is_valid = true;
+ m_update_scope.clear_all();
+
+ for (uint keynr = 0; keynr < table->s->keys; keynr++) {
+ const Rdb_key_def &kd = *m_key_descr_arr[keynr];
+ /* Walk over all key parts, including the "extended key" suffix */
+ const uint key_parts = kd.get_key_parts();
+ for (uint kp = 0; kp < key_parts; kp++) {
+ if (has_hidden_pk(table) && kp + 1 == key_parts) break;
+
+ Field *const field = kd.get_table_field_for_part_no(table, kp);
+ if (bitmap_is_set(table->write_set, field->field_index)) {
+ m_update_scope.set_bit(keynr);
+ break;
+ }
+ }
+ }
+ }
+}
+
+/**
+ Update an existing row
+ @param[in] old_data nullptr for write, non-null for update
+ @param[in] new_data non-null for write/update
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::update_row(const uchar *const old_data,
+ const uchar *const new_data) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(old_data != nullptr);
+ DBUG_ASSERT(new_data != nullptr);
+ DBUG_ASSERT(m_lock_rows == RDB_LOCK_WRITE);
+ /*
+ old_data points to record we're updating. It is the same as the record
+ we've just read (for multi-table UPDATE, too, because SQL layer will make
+ an rnd_pos() call to re-read the record before calling update_row())
+ */
+ DBUG_ASSERT(new_data == table->record[0]);
+
+ const int rv = update_write_row(old_data, new_data, skip_unique_check());
+
+ if (rv == 0) {
+#ifdef MARIAROCKS_NOT_YET
+ stats.rows_updated++;
+#endif
+ update_row_stats(ROWS_UPDATED);
+ }
+
+ DBUG_RETURN(rv);
+}
+
+/*
+ MariaDB's temporary: MyRocks has this function in sql/handler.cc:
+*/
+
+bool can_hold_read_locks_on_select(THD *thd, thr_lock_type lock_type)
+{
+ return (lock_type == TL_READ_WITH_SHARED_LOCKS
+ || lock_type == TL_READ_NO_INSERT
+ || (lock_type != TL_IGNORE
+ && thd->lex->sql_command != SQLCOM_SELECT));
+}
+
+
+/* The following function was copied from ha_blackhole::store_lock: */
+THR_LOCK_DATA **ha_rocksdb::store_lock(THD *const thd, THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(to != nullptr);
+
+ bool in_lock_tables = my_core::thd_in_lock_tables(thd);
+
+ /* First, make a decision about MyRocks's internal locking */
+ if (lock_type >= TL_WRITE_ALLOW_WRITE) {
+ m_lock_rows = RDB_LOCK_WRITE;
+ } else if (lock_type == TL_READ_WITH_SHARED_LOCKS) {
+ m_lock_rows = RDB_LOCK_READ;
+ } else if (lock_type != TL_IGNORE) {
+ m_lock_rows = RDB_LOCK_NONE;
+ if (THDVAR(thd, lock_scanned_rows)) {
+ /*
+ The following logic was copied directly from
+ ha_innobase::store_lock_with_x_type() in
+ storage/innobase/handler/ha_innodb.cc and causes MyRocks to leave
+ locks in place on rows that are in a table that is not being updated.
+ */
+ const uint sql_command = my_core::thd_sql_command(thd);
+ if ((lock_type == TL_READ && in_lock_tables) ||
+ (lock_type == TL_READ_HIGH_PRIORITY && in_lock_tables) ||
+ can_hold_read_locks_on_select(thd, lock_type)) {
+ ulong tx_isolation = my_core::thd_tx_isolation(thd);
+ if (sql_command != SQLCOM_CHECKSUM &&
+ ((my_core::thd_test_options(thd, OPTION_BIN_LOG) &&
+ tx_isolation > ISO_READ_COMMITTED) ||
+ tx_isolation == ISO_SERIALIZABLE ||
+ (lock_type != TL_READ && lock_type != TL_READ_NO_INSERT) ||
+ (sql_command != SQLCOM_INSERT_SELECT &&
+ sql_command != SQLCOM_REPLACE_SELECT &&
+ sql_command != SQLCOM_UPDATE && sql_command != SQLCOM_DELETE &&
+ sql_command != SQLCOM_CREATE_TABLE))) {
+ m_lock_rows = RDB_LOCK_READ;
+ }
+ }
+ }
+ }
+
+ /* Then, tell the SQL layer what kind of locking it should use: */
+ if (lock_type != TL_IGNORE && m_db_lock.type == TL_UNLOCK) {
+ /*
+ Here is where we get into the guts of a row level lock.
+ If TL_UNLOCK is set
+ If we are not doing a LOCK TABLE or DISCARD/IMPORT
+ TABLESPACE, then allow multiple writers
+ */
+
+ if ((lock_type >= TL_WRITE_CONCURRENT_INSERT && lock_type <= TL_WRITE) &&
+ !in_lock_tables && !my_core::thd_tablespace_op(thd)) {
+ lock_type = TL_WRITE_ALLOW_WRITE;
+ }
+
+ /*
+ In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
+ MySQL would use the lock TL_READ_NO_INSERT on t2, and that
+ would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
+ to t2. Convert the lock to a normal read lock to allow
+ concurrent inserts to t2.
+ */
+
+ if (lock_type == TL_READ_NO_INSERT && !in_lock_tables) {
+ lock_type = TL_READ;
+ }
+
+ m_db_lock.type = lock_type;
+ }
+
+ *to++ = &m_db_lock;
+
+ DBUG_RETURN(to);
+}
+
+void ha_rocksdb::read_thd_vars(THD *const thd) {
+ m_store_row_debug_checksums = THDVAR(thd, store_row_debug_checksums);
+ m_converter->set_verify_row_debug_checksums(
+ THDVAR(thd, verify_row_debug_checksums));
+ m_checksums_pct = THDVAR(thd, checksums_pct);
+}
+
+ulonglong ha_rocksdb::table_flags() const
+{
+ DBUG_ENTER_FUNC();
+
+ /*
+ HA_BINLOG_STMT_CAPABLE
+ Upstream: MyRocks advertises itself as it supports SBR, but has additional
+ checks in ha_rocksdb::external_lock()/ start_stmt() which will return an
+ error if one tries to run the statement.
+ Exceptions: @@rocksdb_unsafe_for_binlog or we are an SQL slave thread.
+
+ MariaDB: Inform the upper layer we don't support SBR, so it switches to RBR
+ if possible. The exceptions are the same as with the upstream.
+
+ HA_REC_NOT_IN_SEQ
+ If we don't set it, filesort crashes, because it assumes rowids are
+ 1..8 byte numbers
+ HA_PRIMARY_KEY_IN_READ_INDEX
+ This flag is always set, even for tables that:
+ - have no PK
+ - have some (or all) of PK that can't be decoded from the secondary
+ index.
+ */
+ THD *thd= ha_thd();
+ DBUG_RETURN(HA_BINLOG_ROW_CAPABLE |
+ ((thd && (THDVAR(thd, unsafe_for_binlog) ||thd->rgi_slave))?
+ HA_BINLOG_STMT_CAPABLE : 0) |
+ HA_REC_NOT_IN_SEQ | HA_CAN_INDEX_BLOBS |
+ HA_PRIMARY_KEY_IN_READ_INDEX |
+ HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | HA_NULL_IN_KEY |
+ HA_PARTIAL_COLUMN_READ |
+ HA_TABLE_SCAN_ON_INDEX);
+}
+
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (cannot be SE-specific)
+*/
+int ha_rocksdb::external_lock(THD *const thd, int lock_type) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+
+ int res = HA_EXIT_SUCCESS;
+#if 0
+ // MariaDB uses a different way to implement this, see ha_rocksdb::table_flags
+
+ int binlog_format = my_core::thd_binlog_format(thd);
+ bool unsafe_for_binlog = THDVAR(ha_thd(), unsafe_for_binlog);
+
+ /*
+ A note about (*) below: In fb/mysql, LOCK TABLE myrocks_table WRITE
+ - produces an error when @@binlog_format='ROW'.
+ - does not produce an error with @@binlog_format='STATEMENT'
+
+ We cannot easily provide this behavior in MariaDB, because LOCK ROWS
+ changes @@binlog_format for the duration of the statement.
+
+ Instead we will make the check in ha_rocksdb::start_stmt and will produce
+ an error in the first DML statement done under LOCK TABLE
+
+ (**) - The same happens for other "DDL-type" commands, so allow them here
+ as well.
+ */
+ if (lock_type == F_WRLCK && !thd->rgi_slave && !unsafe_for_binlog &&
+ binlog_format != BINLOG_FORMAT_ROW &&
+ binlog_format != BINLOG_FORMAT_UNSPEC &&
+ thd->lex->sql_command != SQLCOM_LOCK_TABLES && // (*)
+ thd->lex->sql_command != SQLCOM_ANALYZE && // (**)
+ thd->lex->sql_command != SQLCOM_OPTIMIZE && // (**)
+ thd->lex->sql_command != SQLCOM_FLUSH && // (**)
+ my_core::thd_binlog_filter_ok(thd)) {
+ my_error(ER_REQUIRE_ROW_BINLOG_FORMAT, MYF(0));
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+#endif
+
+ if (lock_type == F_UNLCK) {
+ Rdb_transaction *const tx = get_tx_from_thd(thd);
+
+ if (tx) {
+ tx->io_perf_end_and_record(&m_io_perf);
+ tx->m_n_mysql_tables_in_use--;
+ if (tx->m_n_mysql_tables_in_use == 0 &&
+ !my_core::thd_test_options(thd,
+ OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
+ /*
+ Do like InnoDB: when we get here, it's time to commit a
+ single-statement transaction.
+
+ If the statement involved multiple tables, this code will be executed
+ for each of them, but that's ok because non-first tx->commit() calls
+ will be no-ops.
+ */
+ if (tx->commit_or_rollback()) {
+ res = HA_ERR_INTERNAL_ERROR;
+ }
+ }
+ }
+ } else {
+ if (my_core::thd_tx_isolation(thd) < ISO_READ_COMMITTED ||
+ my_core::thd_tx_isolation(thd) > ISO_REPEATABLE_READ) {
+ my_error(ER_ISOLATION_MODE_NOT_SUPPORTED, MYF(0),
+ tx_isolation_names[my_core::thd_tx_isolation(thd)]);
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+ /*
+ It's nice to do the following on start of every statement. The problem
+ is, handler->start_stmt() is not called for INSERTs.
+ So, we put this code here.
+ */
+ Rdb_transaction *const tx = get_or_create_tx(thd);
+ read_thd_vars(thd);
+
+ m_update_scope_is_valid = false;
+
+ if (skip_unique_check()) {
+ if ((thd->lex->sql_command == SQLCOM_INSERT ||
+ thd->lex->sql_command == SQLCOM_LOAD ||
+ thd->lex->sql_command == SQLCOM_REPLACE) &&
+ (thd->lex->duplicates == DUP_REPLACE ||
+ thd->lex->duplicates == DUP_UPDATE)) {
+ my_error(ER_ON_DUPLICATE_DISABLED, MYF(0), thd->query());
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+ }
+
+ if (lock_type == F_WRLCK) {
+ if (tx->is_tx_read_only()) {
+ my_error(ER_UPDATES_WITH_CONSISTENT_SNAPSHOT, MYF(0));
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+
+#ifdef MARIADB_NOT_YET
+ if (thd->get_explicit_snapshot()) {
+ my_error(ER_UPDATES_WITH_EXPLICIT_SNAPSHOT, MYF(0));
+ DBUG_RETURN(HA_ERR_UNSUPPORTED);
+ }
+#endif
+
+ /*
+ SQL layer signals us to take a write lock. It does so when starting DML
+ statement. We should put locks on the rows we're reading.
+
+ Note: sometimes, external_lock() can be called without a prior
+ ::store_lock call. That's why we need to set lock_* members here, too.
+ */
+ m_lock_rows = RDB_LOCK_WRITE;
+
+ if (thd->lex->sql_command == SQLCOM_CREATE_INDEX ||
+ thd->lex->sql_command == SQLCOM_DROP_INDEX ||
+ thd->lex->sql_command == SQLCOM_ALTER_TABLE) {
+ tx->m_ddl_transaction = true;
+ }
+ }
+ tx->m_n_mysql_tables_in_use++;
+ rocksdb_register_tx(rocksdb_hton, thd, tx);
+ tx->io_perf_start(&m_io_perf);
+ }
+
+ DBUG_RETURN(res);
+}
+
+/**
+ @note
+ A quote from ha_innobase::start_stmt():
+ <quote>
+ MySQL calls this function at the start of each SQL statement inside LOCK
+ TABLES. Inside LOCK TABLES the ::external_lock method does not work to
+ mark SQL statement borders.
+ </quote>
+
+ @return
+ HA_EXIT_SUCCESS OK
+*/
+
+int ha_rocksdb::start_stmt(THD *const thd, thr_lock_type lock_type) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+
+ Rdb_transaction *const tx = get_or_create_tx(thd);
+ read_thd_vars(thd);
+ rocksdb_register_tx(ht, thd, tx);
+ tx->io_perf_start(&m_io_perf);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+rocksdb::Range get_range(uint32_t i,
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2],
+ int offset1, int offset2) {
+ uchar *buf_begin = buf;
+ uchar *buf_end = buf + Rdb_key_def::INDEX_NUMBER_SIZE;
+ rdb_netbuf_store_index(buf_begin, i + offset1);
+ rdb_netbuf_store_index(buf_end, i + offset2);
+
+ return rocksdb::Range(
+ rocksdb::Slice((const char *)buf_begin, Rdb_key_def::INDEX_NUMBER_SIZE),
+ rocksdb::Slice((const char *)buf_end, Rdb_key_def::INDEX_NUMBER_SIZE));
+}
+
+static rocksdb::Range get_range(const Rdb_key_def &kd,
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2],
+ int offset1, int offset2) {
+ return get_range(kd.get_index_number(), buf, offset1, offset2);
+}
+
+rocksdb::Range get_range(const Rdb_key_def &kd,
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2]) {
+ if (kd.m_is_reverse_cf) {
+ return myrocks::get_range(kd, buf, 1, 0);
+ } else {
+ return myrocks::get_range(kd, buf, 0, 1);
+ }
+}
+
+rocksdb::Range ha_rocksdb::get_range(
+ const int i, uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2]) const {
+ return myrocks::get_range(*m_key_descr_arr[i], buf);
+}
+
+/*
+ This function is called with total_order_seek=true, but
+ upper/lower bound setting is not necessary.
+ Boundary set is useful when there is no matching key,
+ but in drop_index_thread's case, it means index is marked as removed,
+ so no further seek will happen for the index id.
+*/
+static bool is_myrocks_index_empty(rocksdb::ColumnFamilyHandle *cfh,
+ const bool is_reverse_cf,
+ const rocksdb::ReadOptions &read_opts,
+ const uint index_id) {
+ bool index_removed = false;
+ uchar key_buf[Rdb_key_def::INDEX_NUMBER_SIZE] = {0};
+ rdb_netbuf_store_uint32(key_buf, index_id);
+ const rocksdb::Slice key =
+ rocksdb::Slice(reinterpret_cast<char *>(key_buf), sizeof(key_buf));
+ std::unique_ptr<rocksdb::Iterator> it(rdb->NewIterator(read_opts, cfh));
+ rocksdb_smart_seek(is_reverse_cf, it.get(), key);
+ if (!it->Valid()) {
+ index_removed = true;
+ } else {
+ if (memcmp(it->key().data(), key_buf, Rdb_key_def::INDEX_NUMBER_SIZE)) {
+ // Key does not have same prefix
+ index_removed = true;
+ }
+ }
+ return index_removed;
+}
+
+/*
+ Drop index thread's main logic
+*/
+
+void Rdb_drop_index_thread::run() {
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+
+ for (;;) {
+ // The stop flag might be set by shutdown command
+ // after drop_index_thread releases signal_mutex
+ // (i.e. while executing expensive Seek()). To prevent drop_index_thread
+ // from entering long cond_timedwait, checking if stop flag
+ // is true or not is needed, with drop_index_interrupt_mutex held.
+ if (m_stop) {
+ break;
+ }
+
+ timespec ts;
+ int sec= dict_manager.is_drop_index_empty()
+ ? 24 * 60 * 60 // no filtering
+ : 60; // filtering
+ set_timespec(ts,sec);
+
+ const auto ret MY_ATTRIBUTE((__unused__)) =
+ mysql_cond_timedwait(&m_signal_cond, &m_signal_mutex, &ts);
+ if (m_stop) {
+ break;
+ }
+ // make sure, no program error is returned
+ DBUG_ASSERT(ret == 0 || ret == ETIMEDOUT);
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+
+ std::unordered_set<GL_INDEX_ID> indices;
+ dict_manager.get_ongoing_drop_indexes(&indices);
+ if (!indices.empty()) {
+ std::unordered_set<GL_INDEX_ID> finished;
+ rocksdb::ReadOptions read_opts;
+ read_opts.total_order_seek = true; // disable bloom filter
+
+ for (const auto d : indices) {
+ uint32 cf_flags = 0;
+ if (!dict_manager.get_cf_flags(d.cf_id, &cf_flags)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Failed to get column family flags "
+ "from cf id %u. MyRocks data dictionary may "
+ "get corrupted.",
+ d.cf_id);
+ abort();
+ }
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(d.cf_id);
+ DBUG_ASSERT(cfh);
+ const bool is_reverse_cf = cf_flags & Rdb_key_def::REVERSE_CF_FLAG;
+
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2];
+ rocksdb::Range range = get_range(d.index_id, buf, is_reverse_cf ? 1 : 0,
+ is_reverse_cf ? 0 : 1);
+ rocksdb::Status status = DeleteFilesInRange(rdb->GetBaseDB(), cfh,
+ &range.start, &range.limit);
+ if (!status.ok()) {
+ if (status.IsShutdownInProgress()) {
+ break;
+ }
+ rdb_handle_io_error(status, RDB_IO_ERROR_BG_THREAD);
+ }
+ status = rdb->CompactRange(getCompactRangeOptions(), cfh, &range.start,
+ &range.limit);
+ if (!status.ok()) {
+ if (status.IsShutdownInProgress()) {
+ break;
+ }
+ rdb_handle_io_error(status, RDB_IO_ERROR_BG_THREAD);
+ }
+ if (is_myrocks_index_empty(cfh, is_reverse_cf, read_opts, d.index_id)) {
+ finished.insert(d);
+ }
+ }
+
+ if (!finished.empty()) {
+ dict_manager.finish_drop_indexes(finished);
+ }
+ }
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+}
+
+Rdb_tbl_def *ha_rocksdb::get_table_if_exists(const char *const tablename) {
+ std::string str;
+ if (rdb_normalize_tablename(tablename, &str) != HA_EXIT_SUCCESS) {
+ // We were not passed table name?
+ DBUG_ASSERT(0);
+ return nullptr;
+ }
+
+ return ddl_manager.find(str);
+}
+
+/*
+ Overload func for delete table ---it deletes table meta data in data
+ dictionary immediately and delete real data in background thread(async)
+
+ @param tbl IN MyRocks table definition
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int ha_rocksdb::delete_table(Rdb_tbl_def *const tbl) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(tbl != nullptr);
+ DBUG_ASSERT(m_tbl_def == nullptr || m_tbl_def == tbl);
+
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ dict_manager.add_drop_table(tbl->m_key_descr_arr, tbl->m_key_count, batch);
+
+ /*
+ Remove the table entry in data dictionary (this will also remove it from
+ the persistent data dictionary).
+ */
+ ddl_manager.remove(tbl, batch, true);
+
+ int err = dict_manager.commit(batch);
+ if (err) {
+ DBUG_RETURN(err);
+ }
+
+ rdb_drop_idx_thread.signal();
+ // avoid dangling pointer
+ m_tbl_def = nullptr;
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Note: the following function is called when the table is not open. That is,
+ this->table==nullptr, pk_key_descr==nullptr, etc.
+
+ tablename points to line in form "./dbname/tablename".
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (can be SE-specific)
+*/
+
+int ha_rocksdb::delete_table(const char *const tablename) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(tablename != nullptr);
+
+ /* Find the table in the hash */
+ Rdb_tbl_def *const tbl = get_table_if_exists(tablename);
+ if (!tbl) {
+ DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
+ }
+
+ DBUG_RETURN(delete_table(tbl));
+}
+
+int ha_rocksdb::remove_rows(Rdb_tbl_def *const tbl) {
+ const rocksdb::WriteOptions wo =
+ rdb_get_rocksdb_write_options(handler::ha_thd());
+
+ rocksdb::ReadOptions opts;
+ opts.total_order_seek = true;
+ Rdb_transaction *const tx = get_or_create_tx(table->in_use);
+
+ char key_buf[MAX_KEY_LENGTH];
+ uint key_len;
+ ulonglong bytes_written = 0;
+
+ uchar lower_bound_buf[Rdb_key_def::INDEX_NUMBER_SIZE];
+ uchar upper_bound_buf[Rdb_key_def::INDEX_NUMBER_SIZE];
+ rocksdb::Slice lower_bound_slice;
+ rocksdb::Slice upper_bound_slice;
+
+ /*
+ Remove all records in each index.
+ (This is is not crash-safe, but it doesn't matter, because bulk row
+ deletion will be handled on rocksdb side)
+ */
+ for (uint i = 0; i < tbl->m_key_count; i++) {
+ const Rdb_key_def &kd = *tbl->m_key_descr_arr[i];
+ kd.get_infimum_key(reinterpret_cast<uchar *>(key_buf), &key_len);
+ rocksdb::ColumnFamilyHandle *cf = kd.get_cf();
+ const rocksdb::Slice table_key(key_buf, key_len);
+ setup_iterator_bounds(kd, table_key, Rdb_key_def::INDEX_NUMBER_SIZE,
+ lower_bound_buf, upper_bound_buf, &lower_bound_slice,
+ &upper_bound_slice);
+ DBUG_ASSERT(key_len == Rdb_key_def::INDEX_NUMBER_SIZE);
+ opts.iterate_lower_bound = &lower_bound_slice;
+ opts.iterate_upper_bound = &upper_bound_slice;
+ std::unique_ptr<rocksdb::Iterator> it(rdb->NewIterator(opts, cf));
+
+ it->Seek(table_key);
+ while (it->Valid()) {
+ const rocksdb::Slice key = it->key();
+ if (!kd.covers_key(key)) {
+ break;
+ }
+
+ rocksdb::Status s;
+ if (can_use_single_delete(i)) {
+ s = rdb->SingleDelete(wo, cf, key);
+ } else {
+ s = rdb->Delete(wo, cf, key);
+ }
+
+ if (!s.ok()) {
+ return tx->set_status_error(table->in_use, s, *m_pk_descr, m_tbl_def,
+ m_table_handler);
+ }
+ bytes_written += key.size();
+ it->Next();
+ }
+ }
+
+ tx->update_bytes_written(bytes_written);
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code (cannot be SE-specific)
+*/
+int ha_rocksdb::rename_table(const char *const from, const char *const to) {
+ DBUG_ENTER_FUNC();
+
+ std::string from_str;
+ std::string to_str;
+ std::string from_db;
+ std::string to_db;
+
+ int rc = rdb_normalize_tablename(from, &from_str);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ rc = rdb_split_normalized_tablename(from_str, &from_db);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ rc = rdb_normalize_tablename(to, &to_str);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ rc = rdb_split_normalized_tablename(to_str, &to_db);
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ // If the user changed the database part of the name then validate that the
+ // 'to' database exists.
+ if (from_db != to_db && !rdb_database_exists(to_db)) {
+ // If we return a RocksDB specific error code here we get
+ // "error: 206 - Unknown error 206". InnoDB gets
+ // "error -1 - Unknown error -1" so let's match them.
+ DBUG_RETURN(-1);
+ }
+
+ DBUG_EXECUTE_IF("gen_sql_table_name", to_str = to_str + "#sql-test";);
+
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+ dict_manager.lock();
+
+ if (ddl_manager.rename(from_str, to_str, batch)) {
+ rc = HA_ERR_NO_SUCH_TABLE;
+ } else {
+ rc = dict_manager.commit(batch);
+ }
+ dict_manager.unlock();
+
+ DBUG_RETURN(rc);
+}
+
+/**
+ check_if_incompatible_data() called if ALTER TABLE can't detect otherwise
+ if new and old definition are compatible
+
+ @details If there are no other explicit signs like changed number of
+ fields this function will be called by compare_tables()
+ (sql/sql_tables.cc) to decide should we rewrite whole table or only .frm
+ file.
+
+*/
+
+bool ha_rocksdb::check_if_incompatible_data(HA_CREATE_INFO *const info,
+ uint table_changes) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(info != nullptr);
+
+ // this function is needed only for online alter-table
+ DBUG_RETURN(COMPATIBLE_DATA_NO);
+}
+
+/**
+ @return
+ HA_EXIT_SUCCESS OK
+*/
+int ha_rocksdb::extra(enum ha_extra_function operation) {
+ DBUG_ENTER_FUNC();
+
+ switch (operation) {
+ case HA_EXTRA_KEYREAD:
+ m_keyread_only = true;
+ break;
+ case HA_EXTRA_NO_KEYREAD:
+ m_keyread_only = false;
+ break;
+ case HA_EXTRA_FLUSH:
+ /*
+ If the table has blobs, then they are part of m_retrieved_record.
+ This call invalidates them.
+ */
+ m_retrieved_record.Reset();
+ break;
+ case HA_EXTRA_INSERT_WITH_UPDATE:
+ // INSERT ON DUPLICATE KEY UPDATE
+ if (rocksdb_enable_insert_with_update_caching) {
+ m_insert_with_update = true;
+ }
+ break;
+ case HA_EXTRA_NO_IGNORE_DUP_KEY:
+ // PAIRED with HA_EXTRA_INSERT_WITH_UPDATE or HA_EXTRA_WRITE_CAN_REPLACE
+ // that indicates the end of REPLACE / INSERT ON DUPLICATE KEY
+ m_insert_with_update = false;
+ break;
+
+ default:
+ break;
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/*
+ Given a starting key and an ending key, estimate the number of rows that
+ will exist between the two keys.
+*/
+ha_rows ha_rocksdb::records_in_range(uint inx, const key_range *const min_key,
+ const key_range *const max_key,
+ page_range *pages) {
+ DBUG_ENTER_FUNC();
+
+ ha_rows ret = THDVAR(ha_thd(), records_in_range);
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ if (table->force_index) {
+ const ha_rows force_rows = THDVAR(ha_thd(), force_index_records_in_range);
+ if (force_rows) {
+ DBUG_RETURN(force_rows);
+ }
+ }
+
+ const Rdb_key_def &kd = *m_key_descr_arr[inx];
+
+ uint size1 = 0;
+ if (min_key) {
+ size1 = kd.pack_index_tuple(table, m_pack_buffer, m_sk_packed_tuple,
+ m_record_buffer,
+ min_key->key, min_key->keypart_map);
+ if (min_key->flag == HA_READ_PREFIX_LAST_OR_PREV ||
+ min_key->flag == HA_READ_PREFIX_LAST ||
+ min_key->flag == HA_READ_AFTER_KEY) {
+ kd.successor(m_sk_packed_tuple, size1);
+ }
+ } else {
+ kd.get_infimum_key(m_sk_packed_tuple, &size1);
+ }
+
+ uint size2 = 0;
+ if (max_key) {
+ size2 = kd.pack_index_tuple(table, m_pack_buffer, m_sk_packed_tuple_old,
+ m_record_buffer,
+ max_key->key, max_key->keypart_map);
+ if (max_key->flag == HA_READ_PREFIX_LAST_OR_PREV ||
+ max_key->flag == HA_READ_PREFIX_LAST ||
+ max_key->flag == HA_READ_AFTER_KEY) {
+ kd.successor(m_sk_packed_tuple_old, size2);
+ }
+ // pad the upper key with FFFFs to make sure it is more than the lower
+ if (size1 > size2) {
+ memset(m_sk_packed_tuple_old + size2, 0xff, size1 - size2);
+ size2 = size1;
+ }
+ } else {
+ kd.get_supremum_key(m_sk_packed_tuple_old, &size2);
+ }
+
+ const rocksdb::Slice slice1((const char *)m_sk_packed_tuple, size1);
+ const rocksdb::Slice slice2((const char *)m_sk_packed_tuple_old, size2);
+
+ // slice1 >= slice2 means no row will match
+ if (slice1.compare(slice2) >= 0) {
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+ }
+
+ rocksdb::Range r(kd.m_is_reverse_cf ? slice2 : slice1,
+ kd.m_is_reverse_cf ? slice1 : slice2);
+
+ uint64_t sz = 0;
+ auto disk_size = kd.m_stats.m_actual_disk_size;
+ if (disk_size == 0) disk_size = kd.m_stats.m_data_size;
+ auto rows = kd.m_stats.m_rows;
+ if (rows == 0 || disk_size == 0) {
+ rows = 1;
+ disk_size = ROCKSDB_ASSUMED_KEY_VALUE_DISK_SIZE;
+ }
+
+ // Getting statistics, including from Memtables
+ uint8_t include_flags = rocksdb::DB::INCLUDE_FILES;
+ rdb->GetApproximateSizes(kd.get_cf(), &r, 1, &sz, include_flags);
+ ret = rows * sz / disk_size;
+ uint64_t memTableCount;
+ rdb->GetApproximateMemTableStats(kd.get_cf(), r, &memTableCount, &sz);
+ ret += memTableCount;
+
+ /*
+ GetApproximateSizes() gives estimates so ret might exceed stats.records.
+ MySQL then decides to use full index scan rather than range scan, which
+ is not efficient for most cases.
+ To prevent this, changing estimated records slightly smaller than
+ stats.records.
+ */
+ if (ret >= stats.records) {
+ ret = stats.records * 0.99;
+ }
+
+ if (rocksdb_debug_optimizer_n_rows > 0) {
+ ret = rocksdb_debug_optimizer_n_rows;
+ } else if (ret == 0) {
+ ret = 1;
+ }
+
+ DBUG_RETURN(ret);
+}
+
+void ha_rocksdb::update_create_info(HA_CREATE_INFO *const create_info) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(create_info != nullptr);
+
+ if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) {
+ create_info->auto_increment_value = m_tbl_def->m_auto_incr_val;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ @brief
+ Doing manual compaction on OPTIMIZE TABLE in RocksDB.
+ Compaction itself is executed by background thread in RocksDB, but
+ CompactRange() waits until compaction completes so this function
+ may take a long time.
+ Since RocksDB dataset is allocated per index id, OPTIMIZE TABLE
+ triggers manual compaction for all indexes of the table.
+ @details
+ Compaction range is from the beginning of the index id to
+ the first row of the next index id. When using reverse order
+ column family, the first row of the next index id should be
+ the last row of the previous index id.
+
+ @return
+ HA_ADMIN_OK OK
+ other HA_ADMIN error code
+*/
+int ha_rocksdb::optimize(THD *const thd, HA_CHECK_OPT *const check_opt) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(check_opt != nullptr);
+
+ for (uint i = 0; i < table->s->keys; i++) {
+ uchar buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2];
+ auto range = get_range(i, buf);
+ const rocksdb::Status s = rdb->CompactRange(getCompactRangeOptions(),
+ m_key_descr_arr[i]->get_cf(),
+ &range.start, &range.limit);
+ if (!s.ok()) {
+ DBUG_RETURN(rdb_error_to_mysql(s));
+ }
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+static int calculate_stats(
+ const std::unordered_map<GL_INDEX_ID, std::shared_ptr<const Rdb_key_def>>
+ &to_recalc,
+ bool include_memtables) {
+ DBUG_ENTER_FUNC();
+
+ // find per column family key ranges which need to be queried
+ std::unordered_map<rocksdb::ColumnFamilyHandle *, std::vector<rocksdb::Range>>
+ ranges;
+ std::unordered_map<GL_INDEX_ID, Rdb_index_stats> stats;
+ std::vector<uchar> buf(to_recalc.size() * 2 * Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ uchar *bufp = buf.data();
+ for (const auto &it : to_recalc) {
+ const GL_INDEX_ID index_id = it.first;
+ auto &kd = it.second;
+ ranges[kd->get_cf()].push_back(myrocks::get_range(*kd, bufp));
+ bufp += 2 * Rdb_key_def::INDEX_NUMBER_SIZE;
+
+ stats[index_id] = Rdb_index_stats(index_id);
+ DBUG_ASSERT(kd->get_key_parts() > 0);
+ stats[index_id].m_distinct_keys_per_prefix.resize(kd->get_key_parts());
+ }
+
+ // get RocksDB table properties for these ranges
+ rocksdb::TablePropertiesCollection props;
+ for (const auto &it : ranges) {
+ const auto old_size MY_ATTRIBUTE((__unused__)) = props.size();
+ const auto status = rdb->GetPropertiesOfTablesInRange(
+ it.first, &it.second[0], it.second.size(), &props);
+ DBUG_ASSERT(props.size() >= old_size);
+ if (!status.ok()) {
+ DBUG_RETURN(ha_rocksdb::rdb_error_to_mysql(
+ status, "Could not access RocksDB properties"));
+ }
+ }
+
+ int num_sst = 0;
+ for (const auto &it : props) {
+ std::vector<Rdb_index_stats> sst_stats;
+ Rdb_tbl_prop_coll::read_stats_from_tbl_props(it.second, &sst_stats);
+ /*
+ sst_stats is a list of index statistics for indexes that have entries
+ in the current SST file.
+ */
+ for (const auto &it1 : sst_stats) {
+ /*
+ Only update statistics for indexes that belong to this SQL table.
+
+ The reason is: We are walking through all SST files that have
+ entries from this table (and so can compute good statistics). For
+ other SQL tables, it can be that we're only seeing a small fraction
+ of table's entries (and so we can't update statistics based on that).
+ */
+ if (stats.find(it1.m_gl_index_id) == stats.end()) {
+ continue;
+ }
+
+ auto it_index = to_recalc.find(it1.m_gl_index_id);
+ DBUG_ASSERT(it_index != to_recalc.end());
+ if (it_index == to_recalc.end()) {
+ continue;
+ }
+ stats[it1.m_gl_index_id].merge(
+ it1, true, it_index->second->max_storage_fmt_length());
+ }
+ num_sst++;
+ }
+
+ if (include_memtables) {
+ // calculate memtable cardinality
+ Rdb_tbl_card_coll cardinality_collector(rocksdb_table_stats_sampling_pct);
+ auto read_opts = rocksdb::ReadOptions();
+ read_opts.read_tier = rocksdb::ReadTier::kMemtableTier;
+ for (const auto &it_kd : to_recalc) {
+ const std::shared_ptr<const Rdb_key_def> &kd = it_kd.second;
+ Rdb_index_stats &stat = stats[kd->get_gl_index_id()];
+
+ uchar r_buf[Rdb_key_def::INDEX_NUMBER_SIZE * 2];
+ auto r = myrocks::get_range(*kd, r_buf);
+ uint64_t memtableCount;
+ uint64_t memtableSize;
+ rdb->GetApproximateMemTableStats(kd->get_cf(), r, &memtableCount,
+ &memtableSize);
+ if (memtableCount < (uint64_t)stat.m_rows / 10) {
+ // skip tables that already have enough stats from SST files to reduce
+ // overhead and avoid degradation of big tables stats by sampling from
+ // relatively tiny (less than 10% of full data set) memtable dataset
+ continue;
+ }
+
+ std::unique_ptr<rocksdb::Iterator> it =
+ std::unique_ptr<rocksdb::Iterator>(
+ rdb->NewIterator(read_opts, kd->get_cf()));
+
+ rocksdb::Slice first_index_key((const char *)r_buf,
+ Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ cardinality_collector.Reset();
+ for (it->Seek(first_index_key); is_valid(it.get()); it->Next()) {
+ const rocksdb::Slice key = it->key();
+ if (!kd->covers_key(key)) {
+ break; // end of this index
+ }
+ stat.m_rows++;
+
+ cardinality_collector.ProcessKey(key, kd.get(), &stat);
+ }
+ cardinality_collector.AdjustStats(&stat);
+ }
+ }
+
+ // set and persist new stats
+ ddl_manager.set_stats(stats);
+ ddl_manager.persist_stats(true);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+int ha_rocksdb::calculate_stats_for_table() {
+ DBUG_ENTER_FUNC();
+
+ std::unordered_map<GL_INDEX_ID, std::shared_ptr<const Rdb_key_def>>
+ ids_to_check;
+ for (uint i = 0; i < table->s->keys; i++) {
+ ids_to_check.insert(std::make_pair(m_key_descr_arr[i]->get_gl_index_id(),
+ m_key_descr_arr[i]));
+ }
+
+ DBUG_RETURN(calculate_stats(ids_to_check, true));
+}
+
+/*
+ @return
+ HA_ADMIN_OK OK
+ other HA_ADMIN error code
+*/
+int ha_rocksdb::analyze(THD *const thd, HA_CHECK_OPT *const check_opt) {
+ DBUG_ENTER_FUNC();
+
+ if (table) {
+ if (calculate_stats_for_table() != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(HA_ADMIN_FAILED);
+ }
+ }
+
+ // A call to ::info is needed to repopulate some SQL level structs. This is
+ // necessary for online analyze because we cannot rely on another ::open
+ // call to call info for us.
+ if (info(HA_STATUS_CONST | HA_STATUS_VARIABLE) != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(HA_ADMIN_FAILED);
+ }
+
+ DBUG_RETURN(HA_ADMIN_OK);
+}
+
+void ha_rocksdb::get_auto_increment(ulonglong off, ulonglong inc,
+ ulonglong nb_desired_values,
+ ulonglong *const first_value,
+ ulonglong *const nb_reserved_values) {
+ /*
+ MySQL has a somewhat complicated way of handling the auto-increment value.
+ The first time get_auto_increment is called for a statement,
+ nb_desired_values is the estimate for how many values will be needed. The
+ engine can then reserve some values, and those will be automatically used
+ by MySQL, until a hard-coded value shows up in the insert statement, after
+ which MySQL again calls this function to reset its starting value.
+ *
+ For simplicity we will just ignore nb_desired_values - we aren't going to
+ reserve any extra values for a multi-insert statement. Each row will
+ simply acquire the next value as needed and we will always tell MySQL that
+ we only reserved 1 value. Since we are using an atomic value for
+ m_auto_incr_val this should be safe - if we had to grab a mutex, doing
+ an actual reserve of some values might be a better solution.
+ */
+ DEBUG_SYNC(ha_thd(), "rocksdb.autoinc_vars");
+ DEBUG_SYNC(ha_thd(), "rocksdb.autoinc_vars2");
+
+ if (off > inc) {
+ off = 1;
+ }
+
+ Field *field;
+ ulonglong new_val, max_val;
+ field = table->key_info[table->s->next_number_index].key_part[0].field;
+ max_val = rdb_get_int_col_max_value(field);
+
+ // Local variable reference to simplify code below
+ auto &auto_incr = m_tbl_def->m_auto_incr_val;
+
+ if (inc == 1) {
+ DBUG_ASSERT(off == 1);
+ // Optimization for the standard case where we are always simply
+ // incrementing from the last position
+
+ // Use CAS operation in a loop to make sure automically get the next auto
+ // increment value while ensuring that we don't wrap around to a negative
+ // number.
+ //
+ // We set auto_incr to the min of max_val and new_val + 1. This means that
+ // if we're at the maximum, we should be returning the same value for
+ // multiple rows, resulting in duplicate key errors (as expected).
+ //
+ // If we return values greater than the max, the SQL layer will "truncate"
+ // the value anyway, but it means that we store invalid values into
+ // auto_incr that will be visible in SHOW CREATE TABLE.
+ new_val = auto_incr;
+ while (new_val != std::numeric_limits<ulonglong>::max()) {
+ if (auto_incr.compare_exchange_weak(new_val,
+ std::min(new_val + 1, max_val))) {
+ break;
+ }
+ }
+ } else {
+ // The next value can be more complicated if either 'inc' or 'off' is not 1
+ ulonglong last_val = auto_incr;
+
+ if (last_val > max_val) {
+ new_val = std::numeric_limits<ulonglong>::max();
+ } else {
+ // Loop until we can correctly update the atomic value
+ do {
+ DBUG_ASSERT(last_val > 0);
+ // Calculate the next value in the auto increment series: offset
+ // + N * increment where N is 0, 1, 2, ...
+ //
+ // For further information please visit:
+ // http://dev.mysql.com/doc/refman/5.7/en/replication-options-master.html
+ //
+ // The following is confusing so here is an explanation:
+ // To get the next number in the sequence above you subtract out the
+ // offset, calculate the next sequence (N * increment) and then add the
+ // offset back in.
+ //
+ // The additions are rearranged to avoid overflow. The following is
+ // equivalent to (last_val - 1 + inc - off) / inc. This uses the fact
+ // that (a+b)/c = a/c + b/c + (a%c + b%c)/c. To show why:
+ //
+ // (a+b)/c
+ // = (a - a%c + a%c + b - b%c + b%c) / c
+ // = (a - a%c) / c + (b - b%c) / c + (a%c + b%c) / c
+ // = a/c + b/c + (a%c + b%c) / c
+ //
+ // Now, substitute a = last_val - 1, b = inc - off, c = inc to get the
+ // following statement.
+ ulonglong n =
+ (last_val - 1) / inc + ((last_val - 1) % inc + inc - off) / inc;
+
+ // Check if n * inc + off will overflow. This can only happen if we have
+ // an UNSIGNED BIGINT field.
+ if (n > (std::numeric_limits<ulonglong>::max() - off) / inc) {
+ DBUG_ASSERT(max_val == std::numeric_limits<ulonglong>::max());
+ // The 'last_val' value is already equal to or larger than the largest
+ // value in the sequence. Continuing would wrap around (technically
+ // the behavior would be undefined). What should we do?
+ // We could:
+ // 1) set the new value to the last possible number in our sequence
+ // as described above. The problem with this is that this
+ // number could be smaller than a value in an existing row.
+ // 2) set the new value to the largest possible number. This number
+ // may not be in our sequence, but it is guaranteed to be equal
+ // to or larger than any other value already inserted.
+ //
+ // For now I'm going to take option 2.
+ //
+ // Returning ULLONG_MAX from get_auto_increment will cause the SQL
+ // layer to fail with ER_AUTOINC_READ_FAILED. This means that due to
+ // the SE API for get_auto_increment, inserts will fail with
+ // ER_AUTOINC_READ_FAILED if the column is UNSIGNED BIGINT, but
+ // inserts will fail with ER_DUP_ENTRY for other types (or no failure
+ // if the column is in a non-unique SK).
+ new_val = std::numeric_limits<ulonglong>::max();
+ auto_incr = new_val; // Store the largest value into auto_incr
+ break;
+ }
+
+ new_val = n * inc + off;
+
+ // Attempt to store the new value (plus 1 since m_auto_incr_val contains
+ // the next available value) into the atomic value. If the current
+ // value no longer matches what we have in 'last_val' this will fail and
+ // we will repeat the loop (`last_val` will automatically get updated
+ // with the current value).
+ //
+ // See above explanation for inc == 1 for why we use std::min.
+ } while (!auto_incr.compare_exchange_weak(
+ last_val, std::min(new_val + 1, max_val)));
+ }
+ }
+
+ *first_value = new_val;
+ *nb_reserved_values = 1;
+}
+
+#ifndef DBUG_OFF
+
+/* Debugger help function */
+static char dbug_item_print_buf[512];
+
+const char *dbug_print_item(Item *const item) {
+ char *const buf = dbug_item_print_buf;
+ String str(buf, sizeof(dbug_item_print_buf), &my_charset_bin);
+ str.length(0);
+ if (!item) return "(Item*)nullptr";
+ item->print(&str, QT_ORDINARY);
+ if (str.c_ptr() == buf) {
+ return buf;
+ } else {
+ return "Couldn't fit into buffer";
+ }
+}
+
+#endif /*DBUG_OFF*/
+
+/**
+ SQL layer calls this function to push an index condition.
+
+ @details
+ The condition is for index keyno (only one condition can be pushed at a
+ time).
+ The caller guarantees that condition refers only to index fields; besides
+ that, fields must have
+
+ $field->part_of_key.set_bit(keyno)
+
+ which means that
+
+ (handler->index_flags(keyno, $keypart, 0) & HA_KEYREAD_ONLY) == 1
+
+ which means that field value can be restored from the index tuple.
+
+ @return
+ Part of condition we couldn't check (always nullptr).
+*/
+
+class Item *ha_rocksdb::idx_cond_push(uint keyno, class Item *const idx_cond) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(keyno != MAX_KEY);
+ DBUG_ASSERT(idx_cond != nullptr);
+
+ pushed_idx_cond = idx_cond;
+ pushed_idx_cond_keyno = keyno;
+ in_range_check_pushed_down = TRUE;
+
+ /* We will check the whole condition */
+ DBUG_RETURN(nullptr);
+}
+
+/*
+ Checks if inplace alter is supported for a given operation.
+*/
+
+my_core::enum_alter_inplace_result ha_rocksdb::check_if_supported_inplace_alter(
+ TABLE *altered_table, my_core::Alter_inplace_info *const ha_alter_info) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(ha_alter_info != nullptr);
+
+ if (ha_alter_info->handler_flags &
+ ~(ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_DROP_UNIQUE_INDEX |
+ ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_PARTITIONED |
+ ALTER_ADD_UNIQUE_INDEX |
+ ALTER_CHANGE_CREATE_OPTION)) {
+ DBUG_RETURN(my_core::HA_ALTER_INPLACE_NOT_SUPPORTED);
+ }
+
+ /* We don't support unique keys on table w/ no primary keys */
+ if ((ha_alter_info->handler_flags &
+ ALTER_ADD_UNIQUE_INDEX) &&
+ has_hidden_pk(altered_table)) {
+ DBUG_RETURN(my_core::HA_ALTER_INPLACE_NOT_SUPPORTED);
+ }
+
+ /* We only support changing auto_increment for table options. */
+ if ((ha_alter_info->handler_flags & ALTER_CHANGE_CREATE_OPTION) &&
+ !(ha_alter_info->create_info->used_fields & HA_CREATE_USED_AUTO)) {
+ DBUG_RETURN(my_core::HA_ALTER_INPLACE_NOT_SUPPORTED);
+ }
+
+ /* FIXME: MDEV-16099 Use alter algorithm=nocopy or algorithm=instant
+ for non-InnoDB engine */
+ DBUG_RETURN(my_core::HA_ALTER_INPLACE_COPY_LOCK);
+}
+
+/**
+ Allows the storage engine to update internal structures with concurrent
+ writes blocked. If check_if_supported_inplace_alter() returns
+ HA_ALTER_INPLACE_COPY_NO_LOCK or HA_ALTER_INPLACE_COPY_LOCK.
+ this function is called with exclusive lock otherwise the same level of
+ locking as for inplace_alter_table() will be used.
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function reports error, commit_inplace_alter_table()
+ will be called with commit= false.
+
+ @note For partitioning, failing to prepare one partition, means that
+ commit_inplace_alter_table() will be called to roll back changes for
+ all partitions. This means that commit_inplace_alter_table() might be
+ called without prepare_inplace_alter_table() having been called first
+ for a given partition.
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+
+ @retval true Error
+ @retval false Success
+*/
+bool ha_rocksdb::prepare_inplace_alter_table(
+ TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(altered_table != nullptr);
+ DBUG_ASSERT(ha_alter_info != nullptr);
+
+ Rdb_tbl_def *new_tdef = nullptr;
+ std::shared_ptr<Rdb_key_def> *old_key_descr = nullptr;
+ std::shared_ptr<Rdb_key_def> *new_key_descr = nullptr;
+ uint old_n_keys = m_tbl_def->m_key_count;
+ uint new_n_keys = altered_table->s->keys;
+ std::unordered_set<std::shared_ptr<Rdb_key_def>> added_indexes;
+ std::unordered_set<GL_INDEX_ID> dropped_index_ids;
+ uint n_dropped_keys = 0;
+ uint n_added_keys = 0;
+ ulonglong max_auto_incr = 0;
+
+ if (ha_alter_info->handler_flags &
+ (ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_DROP_UNIQUE_INDEX |
+ ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_ADD_UNIQUE_INDEX)) {
+ if (has_hidden_pk(altered_table)) {
+ new_n_keys += 1;
+ }
+
+ const TABLE *const old_table = table;
+ old_key_descr = m_tbl_def->m_key_descr_arr;
+ new_key_descr = new std::shared_ptr<Rdb_key_def>[new_n_keys];
+
+ new_tdef = new Rdb_tbl_def(m_tbl_def->full_tablename());
+ new_tdef->m_key_descr_arr = new_key_descr;
+ new_tdef->m_key_count = new_n_keys;
+ new_tdef->m_auto_incr_val =
+ m_tbl_def->m_auto_incr_val.load(std::memory_order_relaxed);
+ new_tdef->m_hidden_pk_val =
+ m_tbl_def->m_hidden_pk_val.load(std::memory_order_relaxed);
+
+ if (create_key_defs(altered_table, new_tdef, table, m_tbl_def)) {
+ /* Delete the new key descriptors */
+ delete[] new_key_descr;
+
+ /*
+ Explicitly mark as nullptr so we don't accidentally remove entries
+ from data dictionary on cleanup (or cause double delete[]).
+ */
+ new_tdef->m_key_descr_arr = nullptr;
+ delete new_tdef;
+
+ my_error(ER_KEY_CREATE_DURING_ALTER, MYF(0));
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+
+ uint i;
+ uint j;
+
+ /* Determine which(if any) key definition(s) need to be dropped */
+ for (i = 0; i < ha_alter_info->index_drop_count; i++) {
+ const KEY *const dropped_key = ha_alter_info->index_drop_buffer[i];
+ for (j = 0; j < old_n_keys; j++) {
+ const KEY *const old_key =
+ &old_table->key_info[old_key_descr[j]->get_keyno()];
+
+ if (!compare_keys(old_key, dropped_key)) {
+ dropped_index_ids.insert(old_key_descr[j]->get_gl_index_id());
+ break;
+ }
+ }
+ }
+
+ /* Determine which(if any) key definitions(s) need to be added */
+ int identical_indexes_found = 0;
+ for (i = 0; i < ha_alter_info->index_add_count; i++) {
+ const KEY *const added_key =
+ &ha_alter_info->key_info_buffer[ha_alter_info->index_add_buffer[i]];
+ for (j = 0; j < new_n_keys; j++) {
+ const KEY *const new_key =
+ &altered_table->key_info[new_key_descr[j]->get_keyno()];
+ if (!compare_keys(new_key, added_key)) {
+ /*
+ Check for cases where an 'identical' index is being dropped and
+ re-added in a single ALTER statement. Turn this into a no-op as the
+ index has not changed.
+
+ E.G. Unique index -> non-unique index requires no change
+
+ Note that cases where the index name remains the same but the
+ key-parts are changed is already handled in create_inplace_key_defs.
+ In these cases the index needs to be rebuilt.
+ */
+ if (dropped_index_ids.count(new_key_descr[j]->get_gl_index_id())) {
+ dropped_index_ids.erase(new_key_descr[j]->get_gl_index_id());
+ identical_indexes_found++;
+ } else {
+ added_indexes.insert(new_key_descr[j]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ n_dropped_keys = ha_alter_info->index_drop_count - identical_indexes_found;
+ n_added_keys = ha_alter_info->index_add_count - identical_indexes_found;
+ DBUG_ASSERT(dropped_index_ids.size() == n_dropped_keys);
+ DBUG_ASSERT(added_indexes.size() == n_added_keys);
+ DBUG_ASSERT(new_n_keys == (old_n_keys - n_dropped_keys + n_added_keys));
+ }
+ if (ha_alter_info->handler_flags & ALTER_CHANGE_CREATE_OPTION) {
+ if (!new_tdef) {
+ new_tdef = m_tbl_def;
+ }
+ if (table->found_next_number_field) {
+ max_auto_incr = load_auto_incr_value_from_index();
+ }
+ }
+
+ ha_alter_info->handler_ctx = new Rdb_inplace_alter_ctx(
+ new_tdef, old_key_descr, new_key_descr, old_n_keys, new_n_keys,
+ added_indexes, dropped_index_ids, n_added_keys, n_dropped_keys,
+ max_auto_incr);
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ Alter the table structure in-place with operations specified using
+ HA_ALTER_FLAGS and Alter_inplace_info. The level of concurrency allowed
+ during this operation depends on the return value from
+ check_if_supported_inplace_alter().
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function reports error, commit_inplace_alter_table()
+ will be called with commit= false.
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+
+ @retval true Error
+ @retval false Success
+*/
+bool ha_rocksdb::inplace_alter_table(
+ TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(altered_table != nullptr);
+ DBUG_ASSERT(ha_alter_info != nullptr);
+ DBUG_ASSERT(ha_alter_info->handler_ctx != nullptr);
+
+ Rdb_inplace_alter_ctx *const ctx =
+ static_cast<Rdb_inplace_alter_ctx *>(ha_alter_info->handler_ctx);
+
+ if (ha_alter_info->handler_flags &
+ (ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_ADD_UNIQUE_INDEX)) {
+ /*
+ Buffers need to be set up again to account for new, possibly longer
+ secondary keys.
+ */
+ free_key_buffers();
+
+ DBUG_ASSERT(ctx != nullptr);
+
+ /*
+ If adding unique index, allocate special buffers for duplicate checking.
+ */
+ int err;
+ if ((err = alloc_key_buffers(
+ altered_table, ctx->m_new_tdef,
+ ha_alter_info->handler_flags &
+ ALTER_ADD_UNIQUE_INDEX))) {
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ DBUG_RETURN(err);
+ }
+
+ /* Populate all new secondary keys by scanning the primary key. */
+ if ((err = inplace_populate_sk(altered_table, ctx->m_added_indexes))) {
+ my_error(ER_SK_POPULATE_DURING_ALTER, MYF(0));
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ }
+ }
+
+ DBUG_EXECUTE_IF("myrocks_simulate_index_create_rollback", {
+ dbug_create_err_inplace_alter();
+ DBUG_RETURN(HA_EXIT_FAILURE);
+ };);
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+/**
+ Scan the Primary Key index entries and populate the new secondary keys.
+*/
+int ha_rocksdb::inplace_populate_sk(
+ TABLE *const new_table_arg,
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes) {
+ DBUG_ENTER_FUNC();
+ int res = HA_EXIT_SUCCESS;
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ /* Update the data dictionary */
+ std::unordered_set<GL_INDEX_ID> create_index_ids;
+ for (const auto &index : indexes) {
+ create_index_ids.insert(index->get_gl_index_id());
+ }
+ dict_manager.add_create_index(create_index_ids, batch);
+ res = dict_manager.commit(batch);
+ if (res != HA_EXIT_SUCCESS) {
+ return res;
+ }
+
+ /*
+ Add uncommitted key definitons to ddl_manager. We need to do this
+ so that the property collector can find this keydef when it needs to
+ update stats. The property collector looks for the keydef in the
+ data dictionary, but it won't be there yet since this key definition
+ is still in the creation process.
+ */
+ ddl_manager.add_uncommitted_keydefs(indexes);
+
+ const bool hidden_pk_exists = has_hidden_pk(table);
+
+ Rdb_transaction *tx = get_or_create_tx(table->in_use);
+
+ /*
+ There is one specific scenario where m_sst_info may not be nullptr. This
+ happens if the handler we're using happens to be the handler where the PK
+ bulk load was done on. The sequence of events that lead to this is as
+ follows (T1 is PK bulk load, T2 is SK alter table):
+
+ T1: Execute last INSERT statement
+ T1: Return TABLE and handler object back to Table_cache_manager
+ T1: Close connection
+ T2: Execute ALTER statement
+ T2: Take same TABLE/handler from Table_cache_manager
+ T2: Call closefrm which will call finalize_bulk_load on every other open
+ table/handler *except* the one it's on.
+ T2: Acquire stale snapshot of PK
+ T1: Call finalize_bulk_load
+
+ This is rare because usually, closefrm will call the destructor (and thus
+ finalize_bulk_load) on the handler where PK bulk load is done. However, if
+ the thread ids of the bulk load thread and the alter thread differ by a
+ multiple of table_cache_instances (8 by default), then they hash to the
+ same bucket in Table_cache_manager and the alter thread will not not call
+ the destructor on the handler it is holding. Thus, its m_sst_info will not
+ be nullptr.
+
+ At this point, it is safe to refresh the snapshot because we know all other
+ open handlers have been closed at this point, and the one we're on is the
+ only one left.
+ */
+ if (m_sst_info) {
+ if ((res = finalize_bulk_load())) {
+ DBUG_RETURN(res);
+ }
+ tx->commit();
+ }
+
+ const ulonglong rdb_merge_buf_size = THDVAR(ha_thd(), merge_buf_size);
+ const ulonglong rdb_merge_combine_read_size =
+ THDVAR(ha_thd(), merge_combine_read_size);
+ const ulonglong rdb_merge_tmp_file_removal_delay =
+ THDVAR(ha_thd(), merge_tmp_file_removal_delay_ms);
+
+ for (const auto &index : indexes) {
+ bool is_unique_index =
+ new_table_arg->key_info[index->get_keyno()].flags & HA_NOSAME;
+
+ Rdb_index_merge rdb_merge(tx->get_rocksdb_tmpdir(), rdb_merge_buf_size,
+ rdb_merge_combine_read_size,
+ rdb_merge_tmp_file_removal_delay,
+ index->get_cf());
+
+ if ((res = rdb_merge.init())) {
+ DBUG_RETURN(res);
+ }
+
+ /*
+ Note: We pass in the currently existing table + tbl_def object here,
+ as the pk index position may have changed in the case of hidden primary
+ keys.
+ */
+ const uint pk = pk_index(table, m_tbl_def);
+ ha_index_init(pk, true);
+
+ /* Scan each record in the primary key in order */
+ for (res = index_first(table->record[0]); res == 0;
+ res = index_next(table->record[0])) {
+ longlong hidden_pk_id = 0;
+ if (hidden_pk_exists &&
+ (res = read_hidden_pk_id_from_rowkey(&hidden_pk_id))) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error retrieving hidden pk id.");
+ ha_index_end();
+ DBUG_RETURN(res);
+ }
+
+ /* Create new secondary index entry */
+ const int new_packed_size = index->pack_record(
+ new_table_arg, m_pack_buffer, table->record[0], m_sk_packed_tuple,
+ &m_sk_tails, should_store_row_debug_checksums(), hidden_pk_id, 0,
+ nullptr, m_ttl_bytes);
+
+ const rocksdb::Slice key = rocksdb::Slice(
+ reinterpret_cast<const char *>(m_sk_packed_tuple), new_packed_size);
+ const rocksdb::Slice val =
+ rocksdb::Slice(reinterpret_cast<const char *>(m_sk_tails.ptr()),
+ m_sk_tails.get_current_pos());
+
+ /*
+ Add record to offset tree in preparation for writing out to
+ disk in sorted chunks.
+ */
+ if ((res = rdb_merge.add(key, val))) {
+ ha_index_end();
+ DBUG_RETURN(res);
+ }
+ }
+
+ if (res != HA_ERR_END_OF_FILE) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error retrieving index entry from primary key.");
+ ha_index_end();
+ DBUG_RETURN(res);
+ }
+
+ ha_index_end();
+
+ /*
+ Perform an n-way merge of n sorted buffers on disk, then writes all
+ results to RocksDB via SSTFileWriter API.
+ */
+ rocksdb::Slice merge_key;
+ rocksdb::Slice merge_val;
+
+ struct unique_sk_buf_info sk_info;
+ sk_info.dup_sk_buf = m_dup_sk_packed_tuple;
+ sk_info.dup_sk_buf_old = m_dup_sk_packed_tuple_old;
+
+ while ((res = rdb_merge.next(&merge_key, &merge_val)) == 0) {
+ /* Perform uniqueness check if needed */
+ if (is_unique_index) {
+ if (check_duplicate_sk(new_table_arg, *index, &merge_key, &sk_info)) {
+ /*
+ Duplicate entry found when trying to create unique secondary key.
+ We need to unpack the record into new_table_arg->record[0] as it
+ is used inside print_keydup_error so that the error message shows
+ the duplicate record.
+ */
+ if (index->unpack_record(
+ new_table_arg, new_table_arg->record[0], &merge_key,
+ &merge_val, m_converter->get_verify_row_debug_checksums())) {
+ /* Should never reach here */
+ DBUG_ASSERT(0);
+ }
+
+ print_keydup_error(new_table_arg,
+ &new_table_arg->key_info[index->get_keyno()],
+ MYF(0));
+ DBUG_RETURN(ER_DUP_ENTRY);
+ }
+ }
+
+ /*
+ Insert key and slice to SST via SSTFileWriter API.
+ */
+ if ((res = bulk_load_key(tx, *index, merge_key, merge_val, false))) {
+ break;
+ }
+ }
+
+ /*
+ Here, res == -1 means that we are finished, while > 0 means an error
+ occurred.
+ */
+ if (res > 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error while bulk loading keys in external merge sort.");
+ DBUG_RETURN(res);
+ }
+
+ bool is_critical_error;
+ res = tx->finish_bulk_load(&is_critical_error);
+ if (res && is_critical_error) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error finishing bulk load.");
+ DBUG_RETURN(res);
+ }
+ }
+
+ /*
+ Explicitly tell jemalloc to clean up any unused dirty pages at this point.
+ See https://reviews.facebook.net/D63723 for more details.
+ */
+ purge_all_jemalloc_arenas();
+
+ DBUG_EXECUTE_IF("crash_during_online_index_creation", DBUG_SUICIDE(););
+ DBUG_RETURN(res);
+}
+
+/**
+ Commit or rollback the changes made during prepare_inplace_alter_table()
+ and inplace_alter_table() inside the storage engine.
+ Note that in case of rollback the allowed level of concurrency during
+ this operation will be the same as for inplace_alter_table() and thus
+ might be higher than during prepare_inplace_alter_table(). (For example,
+ concurrent writes were blocked during prepare, but might not be during
+ rollback).
+
+ @note Storage engines are responsible for reporting any errors by
+ calling my_error()/print_error()
+
+ @note If this function with commit= true reports error, it will be called
+ again with commit= false.
+
+ @note In case of partitioning, this function might be called for rollback
+ without prepare_inplace_alter_table() having been called first.
+ Also partitioned tables sets ha_alter_info->group_commit_ctx to a NULL
+ terminated array of the partitions handlers and if all of them are
+ committed as one, then group_commit_ctx should be set to NULL to indicate
+ to the partitioning handler that all partitions handlers are committed.
+ @see prepare_inplace_alter_table().
+
+ @param altered_table TABLE object for new version of table.
+ @param ha_alter_info Structure describing changes to be done
+ by ALTER TABLE and holding data used
+ during in-place alter.
+ @param commit True => Commit, False => Rollback.
+
+ @retval true Error
+ @retval false Success
+*/
+bool ha_rocksdb::commit_inplace_alter_table(
+ my_core::TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info, bool commit) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(altered_table != nullptr);
+ DBUG_ASSERT(ha_alter_info != nullptr);
+
+ Rdb_inplace_alter_ctx *const ctx0 =
+ static_cast<Rdb_inplace_alter_ctx *>(ha_alter_info->handler_ctx);
+
+ DEBUG_SYNC(ha_thd(), "rocksdb.commit_in_place_alter_table");
+
+ /*
+ IMPORTANT: When rollback is requested, mysql will abort with
+ an assertion failure. That means every failed commit during inplace alter
+ table will result in a fatal error on the server. Indexes ongoing creation
+ will be detected when the server restarts, and dropped.
+
+ For partitioned tables, a rollback call to this function (commit == false)
+ is done for each partition. A successful commit call only executes once
+ for all partitions.
+ */
+ if (!commit) {
+ /* If ctx has not been created yet, nothing to do here */
+ if (!ctx0) {
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+ }
+
+ /*
+ Cannot call destructor for Rdb_tbl_def directly because we don't want to
+ erase the mappings inside the ddl_manager, as the old_key_descr is still
+ using them.
+ */
+ if (ctx0->m_new_key_descr) {
+ /* Delete the new key descriptors */
+ for (uint i = 0; i < ctx0->m_new_tdef->m_key_count; i++) {
+ ctx0->m_new_key_descr[i] = nullptr;
+ }
+
+ delete[] ctx0->m_new_key_descr;
+ ctx0->m_new_key_descr = nullptr;
+ ctx0->m_new_tdef->m_key_descr_arr = nullptr;
+
+ delete ctx0->m_new_tdef;
+ }
+
+ /* Remove uncommitted key definitons from ddl_manager */
+ ddl_manager.remove_uncommitted_keydefs(ctx0->m_added_indexes);
+
+ /* Rollback any partially created indexes */
+ dict_manager.rollback_ongoing_index_creation();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+ }
+
+ DBUG_ASSERT(ctx0);
+
+ /*
+ For partitioned tables, we need to commit all changes to all tables at
+ once, unlike in the other inplace alter API methods.
+ */
+ inplace_alter_handler_ctx **ctx_array;
+ inplace_alter_handler_ctx *ctx_single[2];
+
+ if (ha_alter_info->group_commit_ctx) {
+ DBUG_EXECUTE_IF("crash_during_index_creation_partition", DBUG_SUICIDE(););
+ ctx_array = ha_alter_info->group_commit_ctx;
+ } else {
+ ctx_single[0] = ctx0;
+ ctx_single[1] = nullptr;
+ ctx_array = ctx_single;
+ }
+
+ DBUG_ASSERT(ctx0 == ctx_array[0]);
+ ha_alter_info->group_commit_ctx = nullptr;
+
+ if (ha_alter_info->handler_flags &
+ (ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_DROP_UNIQUE_INDEX |
+ ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX |
+ ALTER_ADD_UNIQUE_INDEX)) {
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+ std::unordered_set<GL_INDEX_ID> create_index_ids;
+
+ m_tbl_def = ctx0->m_new_tdef;
+ m_key_descr_arr = m_tbl_def->m_key_descr_arr;
+ m_pk_descr = m_key_descr_arr[pk_index(altered_table, m_tbl_def)];
+
+ dict_manager.lock();
+ for (inplace_alter_handler_ctx **pctx = ctx_array; *pctx; pctx++) {
+ Rdb_inplace_alter_ctx *const ctx =
+ static_cast<Rdb_inplace_alter_ctx *>(*pctx);
+
+ /* Mark indexes to be dropped */
+ dict_manager.add_drop_index(ctx->m_dropped_index_ids, batch);
+
+ for (const auto &index : ctx->m_added_indexes) {
+ create_index_ids.insert(index->get_gl_index_id());
+ }
+
+ if (ddl_manager.put_and_write(ctx->m_new_tdef, batch)) {
+ /*
+ Failed to write new entry into data dictionary, this should never
+ happen.
+ */
+ DBUG_ASSERT(0);
+ }
+
+ /*
+ Remove uncommitted key definitons from ddl_manager, as they are now
+ committed into the data dictionary.
+ */
+ ddl_manager.remove_uncommitted_keydefs(ctx->m_added_indexes);
+ }
+
+ if (dict_manager.commit(batch)) {
+ /*
+ Should never reach here. We assume MyRocks will abort if commit fails.
+ */
+ DBUG_ASSERT(0);
+ }
+
+ dict_manager.unlock();
+
+ /* Mark ongoing create indexes as finished/remove from data dictionary */
+ dict_manager.finish_indexes_operation(
+ create_index_ids, Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ rdb_drop_idx_thread.signal();
+ }
+
+ if (ha_alter_info->handler_flags & ALTER_CHANGE_CREATE_OPTION) {
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict_manager.begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+ std::unordered_set<GL_INDEX_ID> create_index_ids;
+
+ ulonglong auto_incr_val = ha_alter_info->create_info->auto_increment_value;
+
+ for (inplace_alter_handler_ctx **pctx = ctx_array; *pctx; pctx++) {
+ Rdb_inplace_alter_ctx *const ctx =
+ static_cast<Rdb_inplace_alter_ctx *>(*pctx);
+ auto_incr_val = std::max(auto_incr_val, ctx->m_max_auto_incr);
+ dict_manager.put_auto_incr_val(
+ batch, ctx->m_new_tdef->get_autoincr_gl_index_id(), auto_incr_val,
+ true /* overwrite */);
+ ctx->m_new_tdef->m_auto_incr_val = auto_incr_val;
+ }
+
+ if (dict_manager.commit(batch)) {
+ DBUG_ASSERT(0);
+ }
+ }
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+}
+
+#define SHOW_FNAME(name) rocksdb_show_##name
+
+#define DEF_SHOW_FUNC(name, key) \
+ static int SHOW_FNAME(name)(MYSQL_THD thd, SHOW_VAR * var, char *buff) { \
+ rocksdb_status_counters.name = \
+ rocksdb_stats->getTickerCount(rocksdb::key); \
+ var->type = SHOW_LONGLONG; \
+ var->value = reinterpret_cast<char *>(&rocksdb_status_counters.name); \
+ return HA_EXIT_SUCCESS; \
+ }
+
+#define DEF_STATUS_VAR(name) \
+ { "rocksdb_" #name, (char *)&SHOW_FNAME(name), SHOW_FUNC }
+
+#define DEF_STATUS_VAR_PTR(name, ptr, option) \
+ { "rocksdb_" name, (char *)ptr, option }
+
+#define DEF_STATUS_VAR_FUNC(name, ptr, option) \
+ { name, reinterpret_cast<char *>(ptr), option }
+
+struct rocksdb_status_counters_t {
+ uint64_t block_cache_miss;
+ uint64_t block_cache_hit;
+ uint64_t block_cache_add;
+ uint64_t block_cache_add_failures;
+ uint64_t block_cache_index_miss;
+ uint64_t block_cache_index_hit;
+ uint64_t block_cache_index_add;
+ uint64_t block_cache_index_bytes_insert;
+ uint64_t block_cache_index_bytes_evict;
+ uint64_t block_cache_filter_miss;
+ uint64_t block_cache_filter_hit;
+ uint64_t block_cache_filter_add;
+ uint64_t block_cache_filter_bytes_insert;
+ uint64_t block_cache_filter_bytes_evict;
+ uint64_t block_cache_bytes_read;
+ uint64_t block_cache_bytes_write;
+ uint64_t block_cache_data_bytes_insert;
+ uint64_t block_cache_data_miss;
+ uint64_t block_cache_data_hit;
+ uint64_t block_cache_data_add;
+ uint64_t bloom_filter_useful;
+ uint64_t bloom_filter_full_positive;
+ uint64_t bloom_filter_full_true_positive;
+ uint64_t memtable_hit;
+ uint64_t memtable_miss;
+ uint64_t get_hit_l0;
+ uint64_t get_hit_l1;
+ uint64_t get_hit_l2_and_up;
+ uint64_t compaction_key_drop_new;
+ uint64_t compaction_key_drop_obsolete;
+ uint64_t compaction_key_drop_user;
+ uint64_t number_keys_written;
+ uint64_t number_keys_read;
+ uint64_t number_keys_updated;
+ uint64_t bytes_written;
+ uint64_t bytes_read;
+ uint64_t number_db_seek;
+ uint64_t number_db_seek_found;
+ uint64_t number_db_next;
+ uint64_t number_db_next_found;
+ uint64_t number_db_prev;
+ uint64_t number_db_prev_found;
+ uint64_t iter_bytes_read;
+ uint64_t no_file_closes;
+ uint64_t no_file_opens;
+ uint64_t no_file_errors;
+ uint64_t stall_micros;
+ uint64_t num_iterators;
+ uint64_t number_multiget_get;
+ uint64_t number_multiget_keys_read;
+ uint64_t number_multiget_bytes_read;
+ uint64_t number_deletes_filtered;
+ uint64_t number_merge_failures;
+ uint64_t bloom_filter_prefix_checked;
+ uint64_t bloom_filter_prefix_useful;
+ uint64_t number_reseeks_iteration;
+ uint64_t getupdatessince_calls;
+ uint64_t block_cachecompressed_miss;
+ uint64_t block_cachecompressed_hit;
+ uint64_t wal_synced;
+ uint64_t wal_bytes;
+ uint64_t write_self;
+ uint64_t write_other;
+ uint64_t write_timedout;
+ uint64_t write_wal;
+ uint64_t flush_write_bytes;
+ uint64_t compact_read_bytes;
+ uint64_t compact_write_bytes;
+ uint64_t number_superversion_acquires;
+ uint64_t number_superversion_releases;
+ uint64_t number_superversion_cleanups;
+ uint64_t number_block_not_compressed;
+};
+
+static rocksdb_status_counters_t rocksdb_status_counters;
+
+DEF_SHOW_FUNC(block_cache_miss, BLOCK_CACHE_MISS)
+DEF_SHOW_FUNC(block_cache_hit, BLOCK_CACHE_HIT)
+DEF_SHOW_FUNC(block_cache_add, BLOCK_CACHE_ADD)
+DEF_SHOW_FUNC(block_cache_add_failures, BLOCK_CACHE_ADD_FAILURES)
+DEF_SHOW_FUNC(block_cache_index_miss, BLOCK_CACHE_INDEX_MISS)
+DEF_SHOW_FUNC(block_cache_index_hit, BLOCK_CACHE_INDEX_HIT)
+DEF_SHOW_FUNC(block_cache_index_add, BLOCK_CACHE_INDEX_ADD)
+DEF_SHOW_FUNC(block_cache_index_bytes_insert, BLOCK_CACHE_INDEX_BYTES_INSERT)
+DEF_SHOW_FUNC(block_cache_index_bytes_evict, BLOCK_CACHE_INDEX_BYTES_EVICT)
+DEF_SHOW_FUNC(block_cache_filter_miss, BLOCK_CACHE_FILTER_MISS)
+DEF_SHOW_FUNC(block_cache_filter_hit, BLOCK_CACHE_FILTER_HIT)
+DEF_SHOW_FUNC(block_cache_filter_add, BLOCK_CACHE_FILTER_ADD)
+DEF_SHOW_FUNC(block_cache_filter_bytes_insert, BLOCK_CACHE_FILTER_BYTES_INSERT)
+DEF_SHOW_FUNC(block_cache_filter_bytes_evict, BLOCK_CACHE_FILTER_BYTES_EVICT)
+DEF_SHOW_FUNC(block_cache_bytes_read, BLOCK_CACHE_BYTES_READ)
+DEF_SHOW_FUNC(block_cache_bytes_write, BLOCK_CACHE_BYTES_WRITE)
+DEF_SHOW_FUNC(block_cache_data_bytes_insert, BLOCK_CACHE_DATA_BYTES_INSERT)
+DEF_SHOW_FUNC(block_cache_data_miss, BLOCK_CACHE_DATA_MISS)
+DEF_SHOW_FUNC(block_cache_data_hit, BLOCK_CACHE_DATA_HIT)
+DEF_SHOW_FUNC(block_cache_data_add, BLOCK_CACHE_DATA_ADD)
+DEF_SHOW_FUNC(bloom_filter_useful, BLOOM_FILTER_USEFUL)
+DEF_SHOW_FUNC(bloom_filter_full_positive, BLOOM_FILTER_FULL_POSITIVE)
+DEF_SHOW_FUNC(bloom_filter_full_true_positive, BLOOM_FILTER_FULL_TRUE_POSITIVE)
+DEF_SHOW_FUNC(memtable_hit, MEMTABLE_HIT)
+DEF_SHOW_FUNC(memtable_miss, MEMTABLE_MISS)
+DEF_SHOW_FUNC(get_hit_l0, GET_HIT_L0)
+DEF_SHOW_FUNC(get_hit_l1, GET_HIT_L1)
+DEF_SHOW_FUNC(get_hit_l2_and_up, GET_HIT_L2_AND_UP)
+DEF_SHOW_FUNC(compaction_key_drop_new, COMPACTION_KEY_DROP_NEWER_ENTRY)
+DEF_SHOW_FUNC(compaction_key_drop_obsolete, COMPACTION_KEY_DROP_OBSOLETE)
+DEF_SHOW_FUNC(compaction_key_drop_user, COMPACTION_KEY_DROP_USER)
+DEF_SHOW_FUNC(number_keys_written, NUMBER_KEYS_WRITTEN)
+DEF_SHOW_FUNC(number_keys_read, NUMBER_KEYS_READ)
+DEF_SHOW_FUNC(number_keys_updated, NUMBER_KEYS_UPDATED)
+DEF_SHOW_FUNC(bytes_written, BYTES_WRITTEN)
+DEF_SHOW_FUNC(bytes_read, BYTES_READ)
+DEF_SHOW_FUNC(number_db_seek, NUMBER_DB_SEEK)
+DEF_SHOW_FUNC(number_db_seek_found, NUMBER_DB_SEEK_FOUND)
+DEF_SHOW_FUNC(number_db_next, NUMBER_DB_NEXT)
+DEF_SHOW_FUNC(number_db_next_found, NUMBER_DB_NEXT_FOUND)
+DEF_SHOW_FUNC(number_db_prev, NUMBER_DB_PREV)
+DEF_SHOW_FUNC(number_db_prev_found, NUMBER_DB_PREV_FOUND)
+DEF_SHOW_FUNC(iter_bytes_read, ITER_BYTES_READ)
+DEF_SHOW_FUNC(no_file_closes, NO_FILE_CLOSES)
+DEF_SHOW_FUNC(no_file_opens, NO_FILE_OPENS)
+DEF_SHOW_FUNC(no_file_errors, NO_FILE_ERRORS)
+DEF_SHOW_FUNC(stall_micros, STALL_MICROS)
+DEF_SHOW_FUNC(num_iterators, NO_ITERATORS)
+DEF_SHOW_FUNC(number_multiget_get, NUMBER_MULTIGET_CALLS)
+DEF_SHOW_FUNC(number_multiget_keys_read, NUMBER_MULTIGET_KEYS_READ)
+DEF_SHOW_FUNC(number_multiget_bytes_read, NUMBER_MULTIGET_BYTES_READ)
+DEF_SHOW_FUNC(number_deletes_filtered, NUMBER_FILTERED_DELETES)
+DEF_SHOW_FUNC(number_merge_failures, NUMBER_MERGE_FAILURES)
+DEF_SHOW_FUNC(bloom_filter_prefix_checked, BLOOM_FILTER_PREFIX_CHECKED)
+DEF_SHOW_FUNC(bloom_filter_prefix_useful, BLOOM_FILTER_PREFIX_USEFUL)
+DEF_SHOW_FUNC(number_reseeks_iteration, NUMBER_OF_RESEEKS_IN_ITERATION)
+DEF_SHOW_FUNC(getupdatessince_calls, GET_UPDATES_SINCE_CALLS)
+DEF_SHOW_FUNC(block_cachecompressed_miss, BLOCK_CACHE_COMPRESSED_MISS)
+DEF_SHOW_FUNC(block_cachecompressed_hit, BLOCK_CACHE_COMPRESSED_HIT)
+DEF_SHOW_FUNC(wal_synced, WAL_FILE_SYNCED)
+DEF_SHOW_FUNC(wal_bytes, WAL_FILE_BYTES)
+DEF_SHOW_FUNC(write_self, WRITE_DONE_BY_SELF)
+DEF_SHOW_FUNC(write_other, WRITE_DONE_BY_OTHER)
+DEF_SHOW_FUNC(write_timedout, WRITE_TIMEDOUT)
+DEF_SHOW_FUNC(write_wal, WRITE_WITH_WAL)
+DEF_SHOW_FUNC(flush_write_bytes, FLUSH_WRITE_BYTES)
+DEF_SHOW_FUNC(compact_read_bytes, COMPACT_READ_BYTES)
+DEF_SHOW_FUNC(compact_write_bytes, COMPACT_WRITE_BYTES)
+DEF_SHOW_FUNC(number_superversion_acquires, NUMBER_SUPERVERSION_ACQUIRES)
+DEF_SHOW_FUNC(number_superversion_releases, NUMBER_SUPERVERSION_RELEASES)
+DEF_SHOW_FUNC(number_superversion_cleanups, NUMBER_SUPERVERSION_CLEANUPS)
+DEF_SHOW_FUNC(number_block_not_compressed, NUMBER_BLOCK_NOT_COMPRESSED)
+
+static void myrocks_update_status() {
+ export_stats.rows_deleted = global_stats.rows[ROWS_DELETED];
+ export_stats.rows_inserted = global_stats.rows[ROWS_INSERTED];
+ export_stats.rows_read = global_stats.rows[ROWS_READ];
+ export_stats.rows_updated = global_stats.rows[ROWS_UPDATED];
+ export_stats.rows_deleted_blind = global_stats.rows[ROWS_DELETED_BLIND];
+ export_stats.rows_expired = global_stats.rows[ROWS_EXPIRED];
+ export_stats.rows_filtered = global_stats.rows[ROWS_FILTERED];
+
+ export_stats.system_rows_deleted = global_stats.system_rows[ROWS_DELETED];
+ export_stats.system_rows_inserted = global_stats.system_rows[ROWS_INSERTED];
+ export_stats.system_rows_read = global_stats.system_rows[ROWS_READ];
+ export_stats.system_rows_updated = global_stats.system_rows[ROWS_UPDATED];
+
+ export_stats.queries_point = global_stats.queries[QUERIES_POINT];
+ export_stats.queries_range = global_stats.queries[QUERIES_RANGE];
+
+ export_stats.covered_secondary_key_lookups =
+ global_stats.covered_secondary_key_lookups;
+}
+
+static void myrocks_update_memory_status() {
+ std::vector<rocksdb::DB *> dbs;
+ std::unordered_set<const rocksdb::Cache *> cache_set;
+ dbs.push_back(rdb);
+ std::map<rocksdb::MemoryUtil::UsageType, uint64_t> temp_usage_by_type;
+ rocksdb::MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set,
+ &temp_usage_by_type);
+ memory_stats.memtable_total =
+ temp_usage_by_type[rocksdb::MemoryUtil::kMemTableTotal];
+ memory_stats.memtable_unflushed =
+ temp_usage_by_type[rocksdb::MemoryUtil::kMemTableUnFlushed];
+}
+
+static SHOW_VAR myrocks_status_variables[] = {
+ DEF_STATUS_VAR_FUNC("rows_deleted", &export_stats.rows_deleted,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_inserted", &export_stats.rows_inserted,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_read", &export_stats.rows_read, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_updated", &export_stats.rows_updated,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_deleted_blind", &export_stats.rows_deleted_blind,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_expired", &export_stats.rows_expired,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("rows_filtered", &export_stats.rows_filtered,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("system_rows_deleted",
+ &export_stats.system_rows_deleted, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("system_rows_inserted",
+ &export_stats.system_rows_inserted, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("system_rows_read", &export_stats.system_rows_read,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("system_rows_updated",
+ &export_stats.system_rows_updated, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("memtable_total", &memory_stats.memtable_total,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("memtable_unflushed", &memory_stats.memtable_unflushed,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("queries_point", &export_stats.queries_point,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("queries_range", &export_stats.queries_range,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("covered_secondary_key_lookups",
+ &export_stats.covered_secondary_key_lookups,
+ SHOW_LONGLONG),
+
+ {NullS, NullS, SHOW_LONG}};
+
+static void show_myrocks_vars(THD *thd, SHOW_VAR *var, char *buff) {
+ myrocks_update_status();
+ myrocks_update_memory_status();
+ var->type = SHOW_ARRAY;
+ var->value = reinterpret_cast<char *>(&myrocks_status_variables);
+}
+
+static ulonglong io_stall_prop_value(
+ const std::map<std::string, std::string> &props, const std::string &key) {
+ std::map<std::string, std::string>::const_iterator iter =
+ props.find("io_stalls." + key);
+ if (iter != props.end()) {
+ return std::stoull(iter->second);
+ } else {
+ DBUG_PRINT("warning",
+ ("RocksDB GetMapPropery hasn't returned key=%s", key.c_str()));
+ DBUG_ASSERT(0);
+ return 0;
+ }
+}
+
+static void update_rocksdb_stall_status() {
+ st_io_stall_stats local_io_stall_stats;
+ for (const auto &cf_name : cf_manager.get_cf_names()) {
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(cf_name);
+ if (cfh == nullptr) {
+ continue;
+ }
+
+ std::map<std::string, std::string> props;
+ if (!rdb->GetMapProperty(cfh, "rocksdb.cfstats", &props)) {
+ continue;
+ }
+
+ local_io_stall_stats.level0_slowdown +=
+ io_stall_prop_value(props, "level0_slowdown");
+ local_io_stall_stats.level0_slowdown_with_compaction +=
+ io_stall_prop_value(props, "level0_slowdown_with_compaction");
+ local_io_stall_stats.level0_numfiles +=
+ io_stall_prop_value(props, "level0_numfiles");
+ local_io_stall_stats.level0_numfiles_with_compaction +=
+ io_stall_prop_value(props, "level0_numfiles_with_compaction");
+ local_io_stall_stats.stop_for_pending_compaction_bytes +=
+ io_stall_prop_value(props, "stop_for_pending_compaction_bytes");
+ local_io_stall_stats.slowdown_for_pending_compaction_bytes +=
+ io_stall_prop_value(props, "slowdown_for_pending_compaction_bytes");
+ local_io_stall_stats.memtable_compaction +=
+ io_stall_prop_value(props, "memtable_compaction");
+ local_io_stall_stats.memtable_slowdown +=
+ io_stall_prop_value(props, "memtable_slowdown");
+ local_io_stall_stats.total_stop += io_stall_prop_value(props, "total_stop");
+ local_io_stall_stats.total_slowdown +=
+ io_stall_prop_value(props, "total_slowdown");
+ }
+ io_stall_stats = local_io_stall_stats;
+}
+
+static SHOW_VAR rocksdb_stall_status_variables[] = {
+ DEF_STATUS_VAR_FUNC("l0_file_count_limit_slowdowns",
+ &io_stall_stats.level0_slowdown, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("locked_l0_file_count_limit_slowdowns",
+ &io_stall_stats.level0_slowdown_with_compaction,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("l0_file_count_limit_stops",
+ &io_stall_stats.level0_numfiles, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("locked_l0_file_count_limit_stops",
+ &io_stall_stats.level0_numfiles_with_compaction,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("pending_compaction_limit_stops",
+ &io_stall_stats.stop_for_pending_compaction_bytes,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("pending_compaction_limit_slowdowns",
+ &io_stall_stats.slowdown_for_pending_compaction_bytes,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("memtable_limit_stops",
+ &io_stall_stats.memtable_compaction, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("memtable_limit_slowdowns",
+ &io_stall_stats.memtable_slowdown, SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("total_stops", &io_stall_stats.total_stop,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_FUNC("total_slowdowns", &io_stall_stats.total_slowdown,
+ SHOW_LONGLONG),
+ // end of the array marker
+ {NullS, NullS, SHOW_LONG}};
+
+static void show_rocksdb_stall_vars(THD *thd, SHOW_VAR *var, char *buff) {
+ update_rocksdb_stall_status();
+ var->type = SHOW_ARRAY;
+ var->value = reinterpret_cast<char *>(&rocksdb_stall_status_variables);
+}
+
+static SHOW_VAR rocksdb_status_vars[] = {
+ DEF_STATUS_VAR(block_cache_miss),
+ DEF_STATUS_VAR(block_cache_hit),
+ DEF_STATUS_VAR(block_cache_add),
+ DEF_STATUS_VAR(block_cache_add_failures),
+ DEF_STATUS_VAR(block_cache_index_miss),
+ DEF_STATUS_VAR(block_cache_index_hit),
+ DEF_STATUS_VAR(block_cache_index_add),
+ DEF_STATUS_VAR(block_cache_index_bytes_insert),
+ DEF_STATUS_VAR(block_cache_index_bytes_evict),
+ DEF_STATUS_VAR(block_cache_filter_miss),
+ DEF_STATUS_VAR(block_cache_filter_hit),
+ DEF_STATUS_VAR(block_cache_filter_add),
+ DEF_STATUS_VAR(block_cache_filter_bytes_insert),
+ DEF_STATUS_VAR(block_cache_filter_bytes_evict),
+ DEF_STATUS_VAR(block_cache_bytes_read),
+ DEF_STATUS_VAR(block_cache_bytes_write),
+ DEF_STATUS_VAR(block_cache_data_bytes_insert),
+ DEF_STATUS_VAR(block_cache_data_miss),
+ DEF_STATUS_VAR(block_cache_data_hit),
+ DEF_STATUS_VAR(block_cache_data_add),
+ DEF_STATUS_VAR(bloom_filter_useful),
+ DEF_STATUS_VAR(bloom_filter_full_positive),
+ DEF_STATUS_VAR(bloom_filter_full_true_positive),
+ DEF_STATUS_VAR(memtable_hit),
+ DEF_STATUS_VAR(memtable_miss),
+ DEF_STATUS_VAR(get_hit_l0),
+ DEF_STATUS_VAR(get_hit_l1),
+ DEF_STATUS_VAR(get_hit_l2_and_up),
+ DEF_STATUS_VAR(compaction_key_drop_new),
+ DEF_STATUS_VAR(compaction_key_drop_obsolete),
+ DEF_STATUS_VAR(compaction_key_drop_user),
+ DEF_STATUS_VAR(number_keys_written),
+ DEF_STATUS_VAR(number_keys_read),
+ DEF_STATUS_VAR(number_keys_updated),
+ DEF_STATUS_VAR(bytes_written),
+ DEF_STATUS_VAR(bytes_read),
+ DEF_STATUS_VAR(number_db_seek),
+ DEF_STATUS_VAR(number_db_seek_found),
+ DEF_STATUS_VAR(number_db_next),
+ DEF_STATUS_VAR(number_db_next_found),
+ DEF_STATUS_VAR(number_db_prev),
+ DEF_STATUS_VAR(number_db_prev_found),
+ DEF_STATUS_VAR(iter_bytes_read),
+ DEF_STATUS_VAR(no_file_closes),
+ DEF_STATUS_VAR(no_file_opens),
+ DEF_STATUS_VAR(no_file_errors),
+ DEF_STATUS_VAR(stall_micros),
+ DEF_STATUS_VAR(num_iterators),
+ DEF_STATUS_VAR(number_multiget_get),
+ DEF_STATUS_VAR(number_multiget_keys_read),
+ DEF_STATUS_VAR(number_multiget_bytes_read),
+ DEF_STATUS_VAR(number_deletes_filtered),
+ DEF_STATUS_VAR(number_merge_failures),
+ DEF_STATUS_VAR(bloom_filter_prefix_checked),
+ DEF_STATUS_VAR(bloom_filter_prefix_useful),
+ DEF_STATUS_VAR(number_reseeks_iteration),
+ DEF_STATUS_VAR(getupdatessince_calls),
+ DEF_STATUS_VAR(block_cachecompressed_miss),
+ DEF_STATUS_VAR(block_cachecompressed_hit),
+ DEF_STATUS_VAR(wal_synced),
+ DEF_STATUS_VAR(wal_bytes),
+ DEF_STATUS_VAR(write_self),
+ DEF_STATUS_VAR(write_other),
+ DEF_STATUS_VAR(write_timedout),
+ DEF_STATUS_VAR(write_wal),
+ DEF_STATUS_VAR(flush_write_bytes),
+ DEF_STATUS_VAR(compact_read_bytes),
+ DEF_STATUS_VAR(compact_write_bytes),
+ DEF_STATUS_VAR(number_superversion_acquires),
+ DEF_STATUS_VAR(number_superversion_releases),
+ DEF_STATUS_VAR(number_superversion_cleanups),
+ DEF_STATUS_VAR(number_block_not_compressed),
+ DEF_STATUS_VAR_PTR("row_lock_deadlocks", &rocksdb_row_lock_deadlocks,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("row_lock_wait_timeouts",
+ &rocksdb_row_lock_wait_timeouts, SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("snapshot_conflict_errors",
+ &rocksdb_snapshot_conflict_errors, SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("wal_group_syncs", &rocksdb_wal_group_syncs,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("manual_compactions_processed",
+ &rocksdb_manual_compactions_processed, SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("manual_compactions_running",
+ &rocksdb_manual_compactions_running, SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("number_sst_entry_put", &rocksdb_num_sst_entry_put,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("number_sst_entry_delete", &rocksdb_num_sst_entry_delete,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("number_sst_entry_singledelete",
+ &rocksdb_num_sst_entry_singledelete, SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("number_sst_entry_merge", &rocksdb_num_sst_entry_merge,
+ SHOW_LONGLONG),
+ DEF_STATUS_VAR_PTR("number_sst_entry_other", &rocksdb_num_sst_entry_other,
+ SHOW_LONGLONG),
+#ifndef DBUG_OFF
+ DEF_STATUS_VAR_PTR("num_get_for_update_calls",
+ &rocksdb_num_get_for_update_calls, SHOW_LONGLONG),
+#endif
+ // the variables generated by SHOW_FUNC are sorted only by prefix (first
+ // arg in the tuple below), so make sure it is unique to make sorting
+ // deterministic as quick sort is not stable
+ {"rocksdb", reinterpret_cast<char *>(&show_myrocks_vars), SHOW_FUNC},
+ {"rocksdb_stall", reinterpret_cast<char *>(&show_rocksdb_stall_vars),
+ SHOW_FUNC},
+ {NullS, NullS, SHOW_LONG}};
+
+/*
+ Background thread's main logic
+*/
+
+void Rdb_background_thread::run() {
+ // How many seconds to wait till flushing the WAL next time.
+ const int WAKE_UP_INTERVAL = 1;
+
+ timespec ts_next_sync;
+ set_timespec(ts_next_sync, WAKE_UP_INTERVAL);
+
+ for (;;) {
+ // Wait until the next timeout or until we receive a signal to stop the
+ // thread. Request to stop the thread should only be triggered when the
+ // storage engine is being unloaded.
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+ const auto ret MY_ATTRIBUTE((__unused__)) =
+ mysql_cond_timedwait(&m_signal_cond, &m_signal_mutex, &ts_next_sync);
+
+ // Check that we receive only the expected error codes.
+ DBUG_ASSERT(ret == 0 || ret == ETIMEDOUT);
+ const bool local_stop = m_stop;
+ const bool local_save_stats = m_save_stats;
+ reset();
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+
+ if (local_stop) {
+ // If we're here then that's because condition variable was signaled by
+ // another thread and we're shutting down. Break out the loop to make
+ // sure that shutdown thread can proceed.
+ break;
+ }
+
+ // This path should be taken only when the timer expired.
+ DBUG_ASSERT(ret == ETIMEDOUT);
+
+ if (local_save_stats) {
+ ddl_manager.persist_stats();
+ }
+
+ // Set the next timestamp for mysql_cond_timedwait() (which ends up calling
+ // pthread_cond_timedwait()) to wait on.
+ set_timespec(ts_next_sync, WAKE_UP_INTERVAL);
+
+ // Flush the WAL. Sync it for both background and never modes to copy
+ // InnoDB's behavior. For mode never, the wal file isn't even written,
+ // whereas background writes to the wal file, but issues the syncs in a
+ // background thread.
+ if (rdb && (rocksdb_flush_log_at_trx_commit != FLUSH_LOG_SYNC) &&
+ !rocksdb_db_options->allow_mmap_writes) {
+ const rocksdb::Status s = rdb->FlushWAL(true);
+ if (!s.ok()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_BG_THREAD);
+ }
+ }
+ // Recalculate statistics for indexes.
+ if (rocksdb_stats_recalc_rate) {
+ std::unordered_map<GL_INDEX_ID, std::shared_ptr<const Rdb_key_def>>
+ to_recalc;
+
+ if (rdb_indexes_to_recalc.empty()) {
+ struct Rdb_index_collector : public Rdb_tables_scanner {
+ int add_table(Rdb_tbl_def *tdef) override {
+ for (uint i = 0; i < tdef->m_key_count; i++) {
+ rdb_indexes_to_recalc.push_back(
+ tdef->m_key_descr_arr[i]->get_gl_index_id());
+ }
+ return HA_EXIT_SUCCESS;
+ }
+ } collector;
+ ddl_manager.scan_for_tables(&collector);
+ }
+
+ while (to_recalc.size() < rocksdb_stats_recalc_rate &&
+ !rdb_indexes_to_recalc.empty()) {
+ const auto index_id = rdb_indexes_to_recalc.back();
+ rdb_indexes_to_recalc.pop_back();
+
+ std::shared_ptr<const Rdb_key_def> keydef =
+ ddl_manager.safe_find(index_id);
+
+ if (keydef) {
+ to_recalc.insert(std::make_pair(keydef->get_gl_index_id(), keydef));
+ }
+ }
+
+ if (!to_recalc.empty()) {
+ calculate_stats(to_recalc, false);
+ }
+ }
+
+ }
+
+ // save remaining stats which might've left unsaved
+ ddl_manager.persist_stats();
+}
+
+/*
+ A background thread to handle manual compactions,
+ except for dropping indexes/tables. Every second, it checks
+ pending manual compactions, and it calls CompactRange if there is.
+*/
+void Rdb_manual_compaction_thread::run() {
+ mysql_mutex_init(0, &m_mc_mutex, MY_MUTEX_INIT_FAST);
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+ for (;;) {
+ if (m_stop) {
+ break;
+ }
+ timespec ts;
+ set_timespec(ts, 1);
+
+ const auto ret MY_ATTRIBUTE((__unused__)) =
+ mysql_cond_timedwait(&m_signal_cond, &m_signal_mutex, &ts);
+ if (m_stop) {
+ break;
+ }
+ // make sure, no program error is returned
+ DBUG_ASSERT(ret == 0 || ret == ETIMEDOUT);
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+
+ RDB_MUTEX_LOCK_CHECK(m_mc_mutex);
+ // Grab the first item and proceed, if not empty.
+ if (m_requests.empty()) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+ continue;
+ }
+ Manual_compaction_request &mcr = m_requests.begin()->second;
+ DBUG_ASSERT(mcr.cf != nullptr);
+ DBUG_ASSERT(mcr.state == Manual_compaction_request::INITED);
+ mcr.state = Manual_compaction_request::RUNNING;
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+
+ DBUG_ASSERT(mcr.state == Manual_compaction_request::RUNNING);
+ // NO_LINT_DEBUG
+ sql_print_information("Manual Compaction id %d cf %s started.", mcr.mc_id,
+ mcr.cf->GetName().c_str());
+ rocksdb_manual_compactions_running++;
+ if (rocksdb_debug_manual_compaction_delay > 0) {
+ my_sleep(rocksdb_debug_manual_compaction_delay * 1000000);
+ }
+ // CompactRange may take a very long time. On clean shutdown,
+ // it is cancelled by CancelAllBackgroundWork, then status is
+ // set to shutdownInProgress.
+ const rocksdb::Status s = rdb->CompactRange(
+ getCompactRangeOptions(mcr.concurrency), mcr.cf, mcr.start, mcr.limit);
+ rocksdb_manual_compactions_running--;
+ if (s.ok()) {
+ // NO_LINT_DEBUG
+ sql_print_information("Manual Compaction id %d cf %s ended.", mcr.mc_id,
+ mcr.cf->GetName().c_str());
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_information("Manual Compaction id %d cf %s aborted. %s",
+ mcr.mc_id, mcr.cf->GetName().c_str(), s.getState());
+ if (!s.IsShutdownInProgress()) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_BG_THREAD);
+ } else {
+ DBUG_ASSERT(m_requests.size() == 1);
+ }
+ }
+ rocksdb_manual_compactions_processed++;
+ clear_manual_compaction_request(mcr.mc_id, false);
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+ }
+ clear_all_manual_compaction_requests();
+ DBUG_ASSERT(m_requests.empty());
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+ mysql_mutex_destroy(&m_mc_mutex);
+}
+
+void Rdb_manual_compaction_thread::clear_all_manual_compaction_requests() {
+ RDB_MUTEX_LOCK_CHECK(m_mc_mutex);
+ m_requests.clear();
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+}
+
+void Rdb_manual_compaction_thread::clear_manual_compaction_request(
+ int mc_id, bool init_only) {
+ bool erase = true;
+ RDB_MUTEX_LOCK_CHECK(m_mc_mutex);
+ auto it = m_requests.find(mc_id);
+ if (it != m_requests.end()) {
+ if (init_only) {
+ Manual_compaction_request mcr = it->second;
+ if (mcr.state != Manual_compaction_request::INITED) {
+ erase = false;
+ }
+ }
+ if (erase) {
+ m_requests.erase(it);
+ }
+ } else {
+ // Current code path guarantees that erasing by the same mc_id happens
+ // at most once. INITED state may be erased by a thread that requested
+ // the compaction. RUNNING state is erased by mc thread only.
+ DBUG_ASSERT(0);
+ }
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+}
+
+int Rdb_manual_compaction_thread::request_manual_compaction(
+ rocksdb::ColumnFamilyHandle *cf, rocksdb::Slice *start,
+ rocksdb::Slice *limit, int concurrency) {
+ int mc_id = -1;
+ RDB_MUTEX_LOCK_CHECK(m_mc_mutex);
+ if (m_requests.size() >= rocksdb_max_manual_compactions) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+ return mc_id;
+ }
+ Manual_compaction_request mcr;
+ mc_id = mcr.mc_id = ++m_latest_mc_id;
+ mcr.state = Manual_compaction_request::INITED;
+ mcr.cf = cf;
+ mcr.start = start;
+ mcr.limit = limit;
+ mcr.concurrency = concurrency;
+ m_requests.insert(std::make_pair(mcr.mc_id, mcr));
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+ return mc_id;
+}
+
+bool Rdb_manual_compaction_thread::is_manual_compaction_finished(int mc_id) {
+ bool finished = false;
+ RDB_MUTEX_LOCK_CHECK(m_mc_mutex);
+ if (m_requests.count(mc_id) == 0) {
+ finished = true;
+ }
+ RDB_MUTEX_UNLOCK_CHECK(m_mc_mutex);
+ return finished;
+}
+
+/**
+ * Locking read + Not Found + Read Committed occurs if we accessed
+ * a row by Seek, tried to lock it, failed, released and reacquired the
+ * snapshot (because of READ COMMITTED mode) and the row was deleted by
+ * someone else in the meantime.
+ * If so, we either just skipping the row, or re-creating a snapshot
+ * and seek again. In both cases, Read Committed constraint is not broken.
+ */
+bool ha_rocksdb::should_skip_invalidated_record(const int rc) {
+ if ((m_lock_rows != RDB_LOCK_NONE && rc == HA_ERR_KEY_NOT_FOUND &&
+ my_core::thd_tx_isolation(ha_thd()) == ISO_READ_COMMITTED)) {
+ return true;
+ }
+ return false;
+}
+/**
+ * Indicating snapshot needs to be re-created and retrying seek again,
+ * instead of returning errors or empty set. This is normally applicable
+ * when hitting kBusy when locking the first row of the transaction,
+ * with Repeatable Read isolation level.
+ */
+bool ha_rocksdb::should_recreate_snapshot(const int rc,
+ const bool is_new_snapshot) {
+ if (should_skip_invalidated_record(rc) ||
+ (rc == HA_ERR_ROCKSDB_STATUS_BUSY && is_new_snapshot)) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * If calling put/delete/singledelete without locking the row,
+ * it is necessary to pass assume_tracked=false to RocksDB TX API.
+ * Read Free Replication and Blind Deletes are the cases when
+ * using TX API and skipping row locking.
+ */
+bool ha_rocksdb::can_assume_tracked(THD *thd) {
+ if (/* MARIAROCKS_NOT_YET use_read_free_rpl() ||*/ (THDVAR(thd, blind_delete_primary_key))) {
+ return false;
+ }
+ return true;
+}
+
+bool ha_rocksdb::check_bloom_and_set_bounds(
+ THD *thd, const Rdb_key_def &kd, const rocksdb::Slice &eq_cond,
+ const bool use_all_keys, size_t bound_len, uchar *const lower_bound,
+ uchar *const upper_bound, rocksdb::Slice *lower_bound_slice,
+ rocksdb::Slice *upper_bound_slice) {
+ bool can_use_bloom = can_use_bloom_filter(thd, kd, eq_cond, use_all_keys);
+ if (!can_use_bloom) {
+ setup_iterator_bounds(kd, eq_cond, bound_len, lower_bound, upper_bound,
+ lower_bound_slice, upper_bound_slice);
+ }
+ return can_use_bloom;
+}
+
+/**
+ Deciding if it is possible to use bloom filter or not.
+
+ @detail
+ Even if bloom filter exists, it is not always possible
+ to use bloom filter. If using bloom filter when you shouldn't,
+ false negative may happen -- fewer rows than expected may be returned.
+ It is users' responsibility to use bloom filter correctly.
+
+ If bloom filter does not exist, return value does not matter because
+ RocksDB does not use bloom filter internally.
+
+ @param kd
+ @param eq_cond Equal condition part of the key. This always includes
+ system index id (4 bytes).
+ @param use_all_keys True if all key parts are set with equal conditions.
+ This is aware of extended keys.
+*/
+bool ha_rocksdb::can_use_bloom_filter(THD *thd, const Rdb_key_def &kd,
+ const rocksdb::Slice &eq_cond,
+ const bool use_all_keys) {
+ bool can_use = false;
+
+ if (THDVAR(thd, skip_bloom_filter_on_read)) {
+ return can_use;
+ }
+
+ const rocksdb::SliceTransform *prefix_extractor = kd.get_extractor();
+ if (prefix_extractor) {
+ /*
+ This is an optimized use case for CappedPrefixTransform.
+ If eq_cond length >= prefix extractor length and if
+ all keys are used for equal lookup, it is
+ always possible to use bloom filter.
+
+ Prefix bloom filter can't be used on descending scan with
+ prefix lookup (i.e. WHERE id1=1 ORDER BY id2 DESC), because of
+ RocksDB's limitation. On ascending (or not sorting) scan,
+ keys longer than the capped prefix length will be truncated down
+ to the capped length and the resulting key is added to the bloom filter.
+
+ Keys shorter than the capped prefix length will be added to
+ the bloom filter. When keys are looked up, key conditionals
+ longer than the capped length can be used; key conditionals
+ shorter require all parts of the key to be available
+ for the short key match.
+ */
+ if ((use_all_keys && prefix_extractor->InRange(eq_cond)) ||
+ prefix_extractor->SameResultWhenAppended(eq_cond)) {
+ can_use = true;
+ } else {
+ can_use = false;
+ }
+ } else {
+ /*
+ if prefix extractor is not defined, all key parts have to be
+ used by eq_cond.
+ */
+ if (use_all_keys) {
+ can_use = true;
+ } else {
+ can_use = false;
+ }
+ }
+
+ return can_use;
+}
+
+/* For modules that need access to the global data structures */
+rocksdb::TransactionDB *rdb_get_rocksdb_db() { return rdb; }
+
+Rdb_cf_manager &rdb_get_cf_manager() { return cf_manager; }
+
+const rocksdb::BlockBasedTableOptions &rdb_get_table_options() {
+ return *rocksdb_tbl_options;
+}
+
+bool rdb_is_ttl_enabled() { return rocksdb_enable_ttl; }
+bool rdb_is_ttl_read_filtering_enabled() {
+ return rocksdb_enable_ttl_read_filtering;
+}
+#ifndef DBUG_OFF
+int rdb_dbug_set_ttl_rec_ts() { return rocksdb_debug_ttl_rec_ts; }
+int rdb_dbug_set_ttl_snapshot_ts() { return rocksdb_debug_ttl_snapshot_ts; }
+int rdb_dbug_set_ttl_read_filter_ts() {
+ return rocksdb_debug_ttl_read_filter_ts;
+}
+bool rdb_dbug_set_ttl_ignore_pk() { return rocksdb_debug_ttl_ignore_pk; }
+#endif
+
+void rdb_update_global_stats(const operation_type &type, uint count,
+ bool is_system_table) {
+ DBUG_ASSERT(type < ROWS_MAX);
+
+ if (count == 0) {
+ return;
+ }
+
+ if (is_system_table) {
+ global_stats.system_rows[type].add(count);
+ } else {
+ global_stats.rows[type].add(count);
+ }
+}
+
+int rdb_get_table_perf_counters(const char *const tablename,
+ Rdb_perf_counters *const counters) {
+ DBUG_ASSERT(tablename != nullptr);
+
+ Rdb_table_handler *table_handler;
+ table_handler = rdb_open_tables.get_table_handler(tablename);
+ if (table_handler == nullptr) {
+ return HA_ERR_ROCKSDB_INVALID_TABLE;
+ }
+
+ counters->load(table_handler->m_table_perf_context);
+
+ rdb_open_tables.release_table_handler(table_handler);
+ return HA_EXIT_SUCCESS;
+}
+
+const char *get_rdb_io_error_string(const RDB_IO_ERROR_TYPE err_type) {
+ // If this assertion fails then this means that a member has been either added
+ // to or removed from RDB_IO_ERROR_TYPE enum and this function needs to be
+ // changed to return the appropriate value.
+ static_assert(RDB_IO_ERROR_LAST == 4, "Please handle all the error types.");
+
+ switch (err_type) {
+ case RDB_IO_ERROR_TYPE::RDB_IO_ERROR_TX_COMMIT:
+ return "RDB_IO_ERROR_TX_COMMIT";
+ case RDB_IO_ERROR_TYPE::RDB_IO_ERROR_DICT_COMMIT:
+ return "RDB_IO_ERROR_DICT_COMMIT";
+ case RDB_IO_ERROR_TYPE::RDB_IO_ERROR_BG_THREAD:
+ return "RDB_IO_ERROR_BG_THREAD";
+ case RDB_IO_ERROR_TYPE::RDB_IO_ERROR_GENERAL:
+ return "RDB_IO_ERROR_GENERAL";
+ default:
+ DBUG_ASSERT(false);
+ return "(unknown)";
+ }
+}
+
+// In case of core dump generation we want this function NOT to be optimized
+// so that we can capture as much data as possible to debug the root cause
+// more efficiently.
+#ifdef __GNUC__
+#endif
+void rdb_handle_io_error(const rocksdb::Status status,
+ const RDB_IO_ERROR_TYPE err_type) {
+ if (status.IsIOError()) {
+ /* skip dumping core if write failed and we are allowed to do so */
+#ifdef MARIAROCKS_NOT_YET
+ if (skip_core_dump_on_error) {
+ opt_core_file = false;
+ }
+#endif
+ switch (err_type) {
+ case RDB_IO_ERROR_TX_COMMIT:
+ case RDB_IO_ERROR_DICT_COMMIT: {
+ rdb_log_status_error(status, "failed to write to WAL");
+ /* NO_LINT_DEBUG */
+ sql_print_error("MyRocks: aborting on WAL write error.");
+ abort();
+ break;
+ }
+ case RDB_IO_ERROR_BG_THREAD: {
+ rdb_log_status_error(status, "BG thread failed to write to RocksDB");
+ /* NO_LINT_DEBUG */
+ sql_print_error("MyRocks: aborting on BG write error.");
+ abort();
+ break;
+ }
+ case RDB_IO_ERROR_GENERAL: {
+ rdb_log_status_error(status, "failed on I/O");
+ /* NO_LINT_DEBUG */
+ sql_print_error("MyRocks: aborting on I/O error.");
+ abort();
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ } else if (status.IsCorruption()) {
+ rdb_log_status_error(status, "data corruption detected!");
+ rdb_persist_corruption_marker();
+ /* NO_LINT_DEBUG */
+ sql_print_error("MyRocks: aborting because of data corruption.");
+ abort();
+ } else if (!status.ok()) {
+ switch (err_type) {
+ case RDB_IO_ERROR_DICT_COMMIT: {
+ rdb_log_status_error(status, "Failed to write to WAL (dictionary)");
+ /* NO_LINT_DEBUG */
+ sql_print_error("MyRocks: aborting on WAL write error.");
+ abort();
+ break;
+ }
+ default:
+ rdb_log_status_error(status, "Failed to read/write in RocksDB");
+ break;
+ }
+ }
+}
+#ifdef __GNUC__
+#endif
+Rdb_dict_manager *rdb_get_dict_manager(void) { return &dict_manager; }
+
+Rdb_ddl_manager *rdb_get_ddl_manager(void) { return &ddl_manager; }
+
+Rdb_binlog_manager *rdb_get_binlog_manager(void) { return &binlog_manager; }
+
+void rocksdb_set_compaction_options(
+ my_core::THD *const thd MY_ATTRIBUTE((__unused__)),
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr, const void *const save) {
+ if (var_ptr && save) {
+ *(uint64_t *)var_ptr = *(const uint64_t *)save;
+ }
+ const Rdb_compact_params params = {
+ (uint64_t)rocksdb_compaction_sequential_deletes,
+ (uint64_t)rocksdb_compaction_sequential_deletes_window,
+ (uint64_t)rocksdb_compaction_sequential_deletes_file_size};
+ if (properties_collector_factory) {
+ properties_collector_factory->SetCompactionParams(params);
+ }
+}
+
+void rocksdb_set_table_stats_sampling_pct(
+ my_core::THD *const thd MY_ATTRIBUTE((__unused__)),
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const uint32_t new_val = *static_cast<const uint32_t *>(save);
+
+ if (new_val != rocksdb_table_stats_sampling_pct) {
+ rocksdb_table_stats_sampling_pct = new_val;
+
+ if (properties_collector_factory) {
+ properties_collector_factory->SetTableStatsSamplingPct(
+ rocksdb_table_stats_sampling_pct);
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+/*
+ This function allows setting the rate limiter's bytes per second value
+ but only if the rate limiter is turned on which has to be done at startup.
+ If the rate is already 0 (turned off) or we are changing it to 0 (trying
+ to turn it off) this function will push a warning to the client and do
+ nothing.
+ This is similar to the code in innodb_doublewrite_update (found in
+ storage/innobase/handler/ha_innodb.cc).
+*/
+void rocksdb_set_rate_limiter_bytes_per_sec(
+ my_core::THD *const thd,
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ const uint64_t new_val = *static_cast<const uint64_t *>(save);
+ if (new_val == 0 || rocksdb_rate_limiter_bytes_per_sec == 0) {
+ /*
+ If a rate_limiter was not enabled at startup we can't change it nor
+ can we disable it if one was created at startup
+ */
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_ARGUMENTS,
+ "RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot "
+ "be dynamically changed to or from 0. Do a clean "
+ "shutdown if you want to change it from or to 0.");
+ } else if (new_val != rocksdb_rate_limiter_bytes_per_sec) {
+ /* Apply the new value to the rate limiter and store it locally */
+ DBUG_ASSERT(rocksdb_rate_limiter != nullptr);
+ rocksdb_rate_limiter_bytes_per_sec = new_val;
+ rocksdb_rate_limiter->SetBytesPerSecond(new_val);
+ }
+}
+
+void rocksdb_set_sst_mgr_rate_bytes_per_sec(
+ my_core::THD *const thd,
+ my_core::st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const uint64_t new_val = *static_cast<const uint64_t *>(save);
+
+ if (new_val != rocksdb_sst_mgr_rate_bytes_per_sec) {
+ rocksdb_sst_mgr_rate_bytes_per_sec = new_val;
+
+ rocksdb_db_options->sst_file_manager->SetDeleteRateBytesPerSecond(
+ rocksdb_sst_mgr_rate_bytes_per_sec);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+void rocksdb_set_delayed_write_rate(THD *thd, struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save) {
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+ const uint64_t new_val = *static_cast<const uint64_t *>(save);
+ if (rocksdb_delayed_write_rate != new_val) {
+ rocksdb_delayed_write_rate = new_val;
+ rocksdb::Status s =
+ rdb->SetDBOptions({{"delayed_write_rate", std::to_string(new_val)}});
+
+ if (!s.ok()) {
+ /* NO_LINT_DEBUG */
+ sql_print_warning(
+ "MyRocks: failed to update delayed_write_rate. "
+ "status code = %d, status = %s",
+ s.code(), s.ToString().c_str());
+ }
+ }
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+void rocksdb_set_max_latest_deadlocks(THD *thd, struct st_mysql_sys_var *var,
+ void *var_ptr, const void *save) {
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+ const uint32_t new_val = *static_cast<const uint32_t *>(save);
+ if (rocksdb_max_latest_deadlocks != new_val) {
+ rocksdb_max_latest_deadlocks = new_val;
+ rdb->SetDeadlockInfoBufferSize(rocksdb_max_latest_deadlocks);
+ }
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+void rdb_set_collation_exception_list(const char *const exception_list) {
+ DBUG_ASSERT(rdb_collation_exceptions != nullptr);
+
+ if (!rdb_collation_exceptions->set_patterns(exception_list)) {
+ my_core::warn_about_bad_patterns(rdb_collation_exceptions,
+ "strict_collation_exceptions");
+ }
+}
+
+void rocksdb_set_collation_exception_list(THD *const thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save) {
+ const char *const val = *static_cast<const char *const *>(save);
+
+ rdb_set_collation_exception_list(val == nullptr ? "" : val);
+
+ //psergey-todo: what is the purpose of the below??
+ const char *val_copy= val? my_strdup(PSI_INSTRUMENT_ME, val, MYF(0)): nullptr;
+ my_free(*static_cast<char**>(var_ptr));
+ *static_cast<const char**>(var_ptr) = val_copy;
+}
+
+int mysql_value_to_bool(struct st_mysql_value *value, my_bool *return_value) {
+ int new_value_type = value->value_type(value);
+ if (new_value_type == MYSQL_VALUE_TYPE_STRING) {
+ char buf[16];
+ int len = sizeof(buf);
+ const char *str = value->val_str(value, buf, &len);
+ if (str && (my_strcasecmp(system_charset_info, "true", str) == 0 ||
+ my_strcasecmp(system_charset_info, "on", str) == 0)) {
+ *return_value = TRUE;
+ } else if (str && (my_strcasecmp(system_charset_info, "false", str) == 0 ||
+ my_strcasecmp(system_charset_info, "off", str) == 0)) {
+ *return_value = FALSE;
+ } else {
+ return 1;
+ }
+ } else if (new_value_type == MYSQL_VALUE_TYPE_INT) {
+ long long intbuf;
+ value->val_int(value, &intbuf);
+ if (intbuf > 1) return 1;
+ *return_value = intbuf > 0 ? TRUE : FALSE;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+int rocksdb_check_bulk_load(
+ THD *const thd, struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)),
+ void *save, struct st_mysql_value *value) {
+ my_bool new_value;
+ if (mysql_value_to_bool(value, &new_value) != 0) {
+ return 1;
+ }
+
+ Rdb_transaction *tx = get_tx_from_thd(thd);
+ if (tx != nullptr) {
+ bool is_critical_error;
+ const int rc = tx->finish_bulk_load(&is_critical_error);
+ if (rc != 0 && is_critical_error) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Error %d finalizing last SST file while "
+ "setting bulk loading variable",
+ rc);
+ THDVAR(thd, bulk_load) = 0;
+ return 1;
+ }
+ }
+
+ *static_cast<bool *>(save) = new_value;
+ return 0;
+}
+
+int rocksdb_check_bulk_load_allow_unsorted(
+ THD *const thd, struct st_mysql_sys_var *var MY_ATTRIBUTE((__unused__)),
+ void *save, struct st_mysql_value *value) {
+ my_bool new_value;
+ if (mysql_value_to_bool(value, &new_value) != 0) {
+ return 1;
+ }
+
+ if (THDVAR(thd, bulk_load)) {
+ my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SET",
+ "Cannot change this setting while bulk load is enabled");
+
+ return 1;
+ }
+
+ *static_cast<bool *>(save) = new_value;
+ return 0;
+}
+
+static void rocksdb_set_max_background_jobs(THD *thd,
+ struct st_mysql_sys_var *const var,
+ void *const var_ptr,
+ const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+ DBUG_ASSERT(rocksdb_db_options != nullptr);
+ DBUG_ASSERT(rocksdb_db_options->env != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const int new_val = *static_cast<const int *>(save);
+
+ if (rocksdb_db_options->max_background_jobs != new_val) {
+ rocksdb_db_options->max_background_jobs = new_val;
+ rocksdb::Status s =
+ rdb->SetDBOptions({{"max_background_jobs", std::to_string(new_val)}});
+
+ if (!s.ok()) {
+ /* NO_LINT_DEBUG */
+ sql_print_warning(
+ "MyRocks: failed to update max_background_jobs. "
+ "Status code = %d, status = %s.",
+ s.code(), s.ToString().c_str());
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_bytes_per_sync(
+ THD *thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+ DBUG_ASSERT(rocksdb_db_options != nullptr);
+ DBUG_ASSERT(rocksdb_db_options->env != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const ulonglong new_val = *static_cast<const ulonglong *>(save);
+
+ if (rocksdb_db_options->bytes_per_sync != new_val) {
+ rocksdb_db_options->bytes_per_sync = new_val;
+ rocksdb::Status s =
+ rdb->SetDBOptions({{"bytes_per_sync", std::to_string(new_val)}});
+
+ if (!s.ok()) {
+ /* NO_LINT_DEBUG */
+ sql_print_warning(
+ "MyRocks: failed to update max_background_jobs. "
+ "Status code = %d, status = %s.",
+ s.code(), s.ToString().c_str());
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+static void rocksdb_set_wal_bytes_per_sync(
+ THD *thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *const var_ptr MY_ATTRIBUTE((__unused__)), const void *const save) {
+ DBUG_ASSERT(save != nullptr);
+ DBUG_ASSERT(rocksdb_db_options != nullptr);
+ DBUG_ASSERT(rocksdb_db_options->env != nullptr);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ const ulonglong new_val = *static_cast<const ulonglong *>(save);
+
+ if (rocksdb_db_options->wal_bytes_per_sync != new_val) {
+ rocksdb_db_options->wal_bytes_per_sync = new_val;
+ rocksdb::Status s =
+ rdb->SetDBOptions({{"wal_bytes_per_sync", std::to_string(new_val)}});
+
+ if (!s.ok()) {
+ /* NO_LINT_DEBUG */
+ sql_print_warning(
+ "MyRocks: failed to update max_background_jobs. "
+ "Status code = %d, status = %s.",
+ s.code(), s.ToString().c_str());
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+/*
+ Validating and updating block cache size via sys_var::check path.
+ SetCapacity may take seconds when reducing block cache, and
+ sys_var::update holds LOCK_global_system_variables mutex, so
+ updating block cache size is done at check path instead.
+*/
+static int rocksdb_validate_set_block_cache_size(
+ THD *thd MY_ATTRIBUTE((__unused__)),
+ struct st_mysql_sys_var *const var MY_ATTRIBUTE((__unused__)),
+ void *var_ptr, struct st_mysql_value *value) {
+ DBUG_ASSERT(value != nullptr);
+
+ long long new_value;
+
+ /* value is NULL */
+ if (value->val_int(value, &new_value)) {
+ return HA_EXIT_FAILURE;
+ }
+
+ if (new_value < RDB_MIN_BLOCK_CACHE_SIZE ||
+ (uint64_t)new_value > (uint64_t)LLONG_MAX) {
+ return HA_EXIT_FAILURE;
+ }
+
+ RDB_MUTEX_LOCK_CHECK(rdb_block_cache_resize_mutex);
+ const rocksdb::BlockBasedTableOptions &table_options =
+ rdb_get_table_options();
+
+ if (rocksdb_block_cache_size != new_value && table_options.block_cache) {
+ table_options.block_cache->SetCapacity(new_value);
+ }
+ *static_cast<int64_t *>(var_ptr) = static_cast<int64_t>(new_value);
+ RDB_MUTEX_UNLOCK_CHECK(rdb_block_cache_resize_mutex);
+ return HA_EXIT_SUCCESS;
+}
+
+static int rocksdb_validate_update_cf_options(
+ THD * /* unused */, struct st_mysql_sys_var * /*unused*/, void *save,
+ struct st_mysql_value *value) {
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ int length;
+ length = sizeof(buff);
+ str = value->val_str(value, buff, &length);
+ // In some cases, str can point to buff in the stack.
+ // This can cause invalid memory access after validation is finished.
+ // To avoid this kind case, let's alway duplicate the str if str is not
+ // nullptr
+ *(const char **)save = (str == nullptr) ? nullptr : my_strdup(PSI_INSTRUMENT_ME, str, MYF(0));
+
+ if (str == nullptr) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ Rdb_cf_options::Name_to_config_t option_map;
+
+ // Basic sanity checking and parsing the options into a map. If this fails
+ // then there's no point to proceed.
+ if (!Rdb_cf_options::parse_cf_options(str, &option_map)) {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "rocksdb_update_cf_options", str);
+ // Free what we've copied with my_strdup above.
+ my_free((void*)(*(const char **)save));
+ return HA_EXIT_FAILURE;
+ }
+ // Loop through option_map and create missing column families
+ for (Rdb_cf_options::Name_to_config_t::iterator it = option_map.begin();
+ it != option_map.end(); ++it) {
+ cf_manager.get_or_create_cf(rdb, it->first);
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+static void rocksdb_set_update_cf_options(
+ THD *const /* unused */, struct st_mysql_sys_var *const /* unused */,
+ void *const var_ptr, const void *const save) {
+ const char *const val = *static_cast<const char *const *>(save);
+
+ RDB_MUTEX_LOCK_CHECK(rdb_sysvars_mutex);
+
+ my_free(*reinterpret_cast<char **>(var_ptr));
+
+ if (!val) {
+ *reinterpret_cast<char **>(var_ptr) = nullptr;
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+ return;
+ }
+
+ DBUG_ASSERT(val != nullptr);
+
+ // Reset the pointers regardless of how much success we had with updating
+ // the CF options. This will results in consistent behavior and avoids
+ // dealing with cases when only a subset of CF-s was successfully updated.
+ *reinterpret_cast<const char **>(var_ptr) = val;
+
+ // Do the real work of applying the changes.
+ Rdb_cf_options::Name_to_config_t option_map;
+
+ // This should never fail, because of rocksdb_validate_update_cf_options
+ if (!Rdb_cf_options::parse_cf_options(val, &option_map)) {
+ my_free(*reinterpret_cast<char**>(var_ptr));
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+ return;
+ }
+
+ // For each CF we have, see if we need to update any settings.
+ for (const auto &cf_name : cf_manager.get_cf_names()) {
+ DBUG_ASSERT(!cf_name.empty());
+
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(cf_name);
+ DBUG_ASSERT(cfh != nullptr);
+
+ const auto it = option_map.find(cf_name);
+ std::string per_cf_options = (it != option_map.end()) ? it->second : "";
+
+ if (!per_cf_options.empty()) {
+ Rdb_cf_options::Name_to_config_t opt_map;
+ rocksdb::Status s = rocksdb::StringToMap(per_cf_options, &opt_map);
+
+ if (s != rocksdb::Status::OK()) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "MyRocks: failed to convert the options for column "
+ "family '%s' to a map. %s",
+ cf_name.c_str(), s.ToString().c_str());
+ } else {
+ DBUG_ASSERT(rdb != nullptr);
+
+ // Finally we can apply the options.
+ s = rdb->SetOptions(cfh, opt_map);
+
+ if (s != rocksdb::Status::OK()) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "MyRocks: failed to apply the options for column "
+ "family '%s'. %s",
+ cf_name.c_str(), s.ToString().c_str());
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "MyRocks: options for column family '%s' "
+ "have been successfully updated.",
+ cf_name.c_str());
+
+ // Make sure that data is internally consistent as well and update
+ // the CF options. This is necessary also to make sure that the CF
+ // options will be correctly reflected in the relevant table:
+ // ROCKSDB_CF_OPTIONS in INFORMATION_SCHEMA.
+ rocksdb::ColumnFamilyOptions cf_options = rdb->GetOptions(cfh);
+ std::string updated_options;
+
+ s = rocksdb::GetStringFromColumnFamilyOptions(&updated_options,
+ cf_options);
+
+ DBUG_ASSERT(s == rocksdb::Status::OK());
+ DBUG_ASSERT(!updated_options.empty());
+
+ cf_manager.update_options_map(cf_name, updated_options);
+ }
+ }
+ }
+ }
+
+ // Our caller (`plugin_var_memalloc_global_update`) will call `my_free` to
+ // free up resources used before.
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_sysvars_mutex);
+}
+
+void rdb_queue_save_stats_request() { rdb_bg_thread.request_save_stats(); }
+
+#ifdef MARIAROCKS_NOT_YET // MDEV-10976
+
+void ha_rocksdb::rpl_before_delete_rows() {
+ DBUG_ENTER_FUNC();
+
+ m_in_rpl_delete_rows = true;
+
+ DBUG_VOID_RETURN;
+}
+
+void ha_rocksdb::rpl_after_delete_rows() {
+ DBUG_ENTER_FUNC();
+
+ m_in_rpl_delete_rows = false;
+
+ DBUG_VOID_RETURN;
+}
+
+void ha_rocksdb::rpl_before_update_rows() {
+ DBUG_ENTER_FUNC();
+
+ m_in_rpl_update_rows = true;
+
+ DBUG_VOID_RETURN;
+}
+
+void ha_rocksdb::rpl_after_update_rows() {
+ DBUG_ENTER_FUNC();
+
+ m_in_rpl_update_rows = false;
+
+ DBUG_VOID_RETURN;
+}
+
+#if 0
+bool ha_rocksdb::is_read_free_rpl_table() const {
+ return table->s && m_tbl_def->m_is_read_free_rpl_table;
+}
+#endif
+
+/**
+ @brief
+ Read Free Replication can be used or not. Returning true means
+ Read Free Replication can be used.
+*/
+bool ha_rocksdb::use_read_free_rpl() const {
+ DBUG_ENTER_FUNC();
+
+ if (!ha_thd()->rli_slave || table->triggers || /* !is_read_free_rpl_table()*/ ) {
+ DBUG_RETURN(false);
+ }
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+ switch (rocksdb_read_free_rpl) {
+ case read_free_rpl_type::OFF:
+ DBUG_RETURN(false);
+ case read_free_rpl_type::PK_ONLY:
+ DBUG_RETURN(!has_hidden_pk(table) && table->s->keys == 1);
+ case read_free_rpl_type::PK_SK:
+ DBUG_RETURN(!has_hidden_pk(table));
+ }
+#else
+ DBUG_RETURN(false);
+#endif
+
+ DBUG_ASSERT(false);
+ DBUG_RETURN(false);
+}
+#endif // MARIAROCKS_NOT_YET
+
+double ha_rocksdb::read_time(uint index, uint ranges, ha_rows rows) {
+ DBUG_ENTER_FUNC();
+
+ if (index != table->s->primary_key) {
+ /* Non covering index range scan */
+ DBUG_RETURN(handler::read_time(index, ranges, rows));
+ }
+
+ DBUG_RETURN((rows / 20.0) + 1);
+}
+
+void ha_rocksdb::print_error(int error, myf errflag) {
+ if (error == HA_ERR_ROCKSDB_STATUS_BUSY) {
+ error = HA_ERR_LOCK_DEADLOCK;
+ }
+ handler::print_error(error, errflag);
+}
+
+std::string rdb_corruption_marker_file_name() {
+ std::string ret(rocksdb_datadir);
+ ret.append("/ROCKSDB_CORRUPTED");
+ return ret;
+}
+
+void sql_print_verbose_info(const char *format, ...)
+{
+ va_list args;
+
+ if (global_system_variables.log_warnings > 2) {
+ va_start(args, format);
+ sql_print_information_v(format, args);
+ va_end(args);
+ }
+}
+
+} // namespace myrocks
+
+
+/**
+ Construct and emit duplicate key error message using information
+ from table's record buffer.
+
+ @sa print_keydup_error(table, key, msg, errflag, thd, org_table_name).
+*/
+
+void print_keydup_error(TABLE *table, KEY *key, myf errflag,
+ const THD *thd, const char *org_table_name)
+{
+ print_keydup_error(table, key, ER(ER_DUP_ENTRY_WITH_KEY_NAME), errflag);
+}
+
+/*
+ Register the storage engine plugin outside of myrocks namespace
+ so that mysql_declare_plugin does not get confused when it does
+ its name generation.
+*/
+
+
+struct st_mysql_storage_engine rocksdb_storage_engine = {
+ MYSQL_HANDLERTON_INTERFACE_VERSION};
+
+maria_declare_plugin(rocksdb_se){
+ MYSQL_STORAGE_ENGINE_PLUGIN, /* Plugin Type */
+ &rocksdb_storage_engine, /* Plugin Descriptor */
+ "ROCKSDB", /* Plugin Name */
+ "Monty Program Ab", /* Plugin Author */
+ "RocksDB storage engine", /* Plugin Description */
+ PLUGIN_LICENSE_GPL, /* Plugin Licence */
+ myrocks::rocksdb_init_func, /* Plugin Entry Point */
+ myrocks::rocksdb_done_func, /* Plugin Deinitializer */
+ 0x0001, /* version number (0.1) */
+ myrocks::rocksdb_status_vars, /* status variables */
+ myrocks::rocksdb_system_variables, /* system variables */
+ "1.0", /* string version */
+ myrocks::MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+},
+ myrocks::rdb_i_s_cfstats, myrocks::rdb_i_s_dbstats,
+ myrocks::rdb_i_s_perf_context, myrocks::rdb_i_s_perf_context_global,
+ myrocks::rdb_i_s_cfoptions, myrocks::rdb_i_s_compact_stats,
+ myrocks::rdb_i_s_global_info, myrocks::rdb_i_s_ddl,
+ myrocks::rdb_i_s_sst_props, myrocks::rdb_i_s_index_file_map,
+ myrocks::rdb_i_s_lock_info, myrocks::rdb_i_s_trx_info,
+ myrocks::rdb_i_s_deadlock_info
+maria_declare_plugin_end;
diff --git a/storage/rocksdb/ha_rocksdb.h b/storage/rocksdb/ha_rocksdb.h
new file mode 100644
index 00000000..05b5341b
--- /dev/null
+++ b/storage/rocksdb/ha_rocksdb.h
@@ -0,0 +1,1069 @@
+/*
+ Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+/* C++ standard header files */
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+/* MySQL header files */
+#include "./handler.h" /* handler */
+#include "./my_global.h" /* ulonglong */
+#include "./sql_string.h"
+
+#ifdef _WIN32
+#undef pthread_key_create
+#undef pthread_key_delete
+#undef pthread_setspecific
+#undef pthread_getspecific
+#endif
+
+
+/* RocksDB header files */
+#include "rocksdb/cache.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/sst_file_manager.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/utilities/options_util.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+
+/* MyRocks header files */
+#include "./rdb_buff.h"
+#include "./rdb_comparator.h"
+#include "./rdb_global.h"
+#include "./rdb_index_merge.h"
+#include "./rdb_io_watchdog.h"
+#include "./rdb_perf_context.h"
+#include "./rdb_sst_info.h"
+#include "./rdb_utils.h"
+#include "./ut0counter.h"
+
+#include "rdb_mariadb_server_port.h"
+
+class Field_blob;
+class Field_varstring;
+/**
+ @note MyRocks Coding Conventions:
+ MyRocks code follows the baseline MySQL coding conventions, available at
+ http://dev.mysql.com/doc/internals/en/coding-guidelines.html, with several
+ refinements (@see /storage/rocksdb/README file).
+*/
+
+namespace myrocks {
+
+class Rdb_converter;
+class Rdb_key_def;
+class Rdb_tbl_def;
+class Rdb_transaction;
+class Rdb_transaction_impl;
+class Rdb_writebatch_impl;
+class Rdb_field_encoder;
+/* collations, used in MariaRocks */
+enum collations_used {
+ COLLATION_UTF8MB4_BIN = 46,
+ COLLATION_LATIN1_BIN = 47,
+ COLLATION_UTF16LE_BIN = 55,
+ COLLATION_UTF32_BIN = 61,
+ COLLATION_UTF16_BIN = 62,
+ COLLATION_BINARY = 63,
+ COLLATION_UTF8_BIN = 83
+};
+
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+extern char *rocksdb_read_free_rpl_tables;
+#if defined(HAVE_PSI_INTERFACE)
+extern PSI_rwlock_key key_rwlock_read_free_rpl_tables;
+#endif
+extern Regex_list_handler rdb_read_free_regex_handler;
+#endif
+
+/**
+ @brief
+ Rdb_table_handler is a reference-counted structure storing information for
+ each open table. All the objects are stored in a global hash map.
+
+ //TODO: join this with Rdb_tbl_def ?
+*/
+struct Rdb_table_handler {
+ char *m_table_name;
+ uint m_table_name_length;
+ int m_ref_count;
+ atomic_stat<int> m_lock_wait_timeout_counter;
+ atomic_stat<int> m_deadlock_counter;
+
+ my_core::THR_LOCK m_thr_lock; ///< MySQL latch needed by m_db_lock
+
+ /* Stores cumulative table statistics */
+ my_io_perf_atomic_t m_io_perf_read;
+ my_io_perf_atomic_t m_io_perf_write;
+ Rdb_atomic_perf_counters m_table_perf_context;
+
+ /* Stores cached memtable estimate statistics */
+ std::atomic_uint m_mtcache_lock;
+ uint64_t m_mtcache_count;
+ uint64_t m_mtcache_size;
+ uint64_t m_mtcache_last_update;
+};
+
+} // namespace myrocks
+
+/* Provide hash function for GL_INDEX_ID so we can include it in sets */
+namespace std {
+template <>
+struct hash<myrocks::GL_INDEX_ID> {
+ std::size_t operator()(const myrocks::GL_INDEX_ID &gl_index_id) const {
+ const uint64_t val =
+ ((uint64_t)gl_index_id.cf_id << 32 | (uint64_t)gl_index_id.index_id);
+ return std::hash<uint64_t>()(val);
+ }
+};
+} // namespace std
+
+namespace myrocks {
+
+/**
+ @brief
+ Class definition for ROCKSDB storage engine plugin handler
+*/
+
+class ha_rocksdb : public my_core::handler {
+ my_core::THR_LOCK_DATA m_db_lock; ///< MySQL database lock
+
+ Rdb_table_handler *m_table_handler; ///< Open table handler
+
+ /* Iterator used for range scans and for full table/index scans */
+ rocksdb::Iterator *m_scan_it;
+
+ /* Same as handler::end_key but for start. Reverse-ordered scans need it */
+ key_range m_save_start_range;
+ const key_range *m_start_range;
+
+ /* Whether m_scan_it was created with skip_bloom=true */
+ bool m_scan_it_skips_bloom;
+
+ const rocksdb::Snapshot *m_scan_it_snapshot;
+
+ /* Buffers used for upper/lower bounds for m_scan_it. */
+ uchar *m_scan_it_lower_bound;
+ uchar *m_scan_it_upper_bound;
+ rocksdb::Slice m_scan_it_lower_bound_slice;
+ rocksdb::Slice m_scan_it_upper_bound_slice;
+
+ Rdb_tbl_def *m_tbl_def;
+
+ /* Primary Key encoder from KeyTupleFormat to StorageFormat */
+ std::shared_ptr<Rdb_key_def> m_pk_descr;
+
+ /* Array of index descriptors */
+ std::shared_ptr<Rdb_key_def> *m_key_descr_arr;
+
+ bool check_keyread_allowed(uint inx, uint part, bool all_parts) const;
+
+ /*
+ Number of key parts in PK. This is the same as
+ table->key_info[table->s->primary_key].keyparts
+ */
+ uint m_pk_key_parts;
+
+ /*
+ TRUE <=> Primary Key columns can be decoded from the index
+ */
+ mutable bool m_pk_can_be_decoded;
+
+ uchar *m_pk_tuple; /* Buffer for storing PK in KeyTupleFormat */
+ uchar *m_pk_packed_tuple; /* Buffer for storing PK in StorageFormat */
+ // ^^ todo: change it to 'char*'? TODO: ^ can we join this with last_rowkey?
+
+ /*
+ Temporary buffers for storing the key part of the Key/Value pair
+ for secondary indexes.
+ */
+ uchar *m_sk_packed_tuple;
+
+ /*
+ Temporary buffers for storing end key part of the Key/Value pair.
+ This is used for range scan only.
+ */
+ uchar *m_end_key_packed_tuple;
+
+ Rdb_string_writer m_sk_tails;
+ Rdb_string_writer m_pk_unpack_info;
+
+ /*
+ ha_rockdb->index_read_map(.. HA_READ_KEY_EXACT or similar) will save here
+ mem-comparable form of the index lookup tuple.
+ */
+ uchar *m_sk_match_prefix;
+ uint m_sk_match_length;
+
+ /* Buffer space for the above */
+ uchar *m_sk_match_prefix_buf;
+
+ /* Second buffers, used by UPDATE. */
+ uchar *m_sk_packed_tuple_old;
+ Rdb_string_writer m_sk_tails_old;
+
+ /* Buffers used for duplicate checking during unique_index_creation */
+ uchar *m_dup_sk_packed_tuple;
+ uchar *m_dup_sk_packed_tuple_old;
+
+ /*
+ Temporary space for packing VARCHARs (we provide it to
+ pack_record()/pack_index_tuple() calls).
+ */
+ uchar *m_pack_buffer;
+
+ /*
+ A buffer long enough to store table record
+ */
+ uchar *m_record_buffer;
+
+ /* class to convert between Mysql format and RocksDB format*/
+ std::shared_ptr<Rdb_converter> m_converter;
+
+ /*
+ Pointer to the original TTL timestamp value (8 bytes) during UPDATE.
+ */
+ char *m_ttl_bytes;
+ /*
+ The TTL timestamp value can change if the explicit TTL column is
+ updated. If we detect this when updating the PK, we indicate it here so
+ we know we must always update any SK's.
+ */
+ bool m_ttl_bytes_updated;
+
+ /* rowkey of the last record we've read, in StorageFormat. */
+ String m_last_rowkey;
+
+ /*
+ Last retrieved record, in table->record[0] data format.
+
+ This is used only when we get the record with rocksdb's Get() call (The
+ other option is when we get a rocksdb::Slice from an iterator)
+ */
+ rocksdb::PinnableSlice m_retrieved_record;
+
+ /* Type of locking to apply to rows */
+ enum { RDB_LOCK_NONE, RDB_LOCK_READ, RDB_LOCK_WRITE } m_lock_rows;
+
+ /* TRUE means we're doing an index-only read. FALSE means otherwise. */
+ bool m_keyread_only;
+
+ bool m_skip_scan_it_next_call;
+
+ /* TRUE means we are accessing the first row after a snapshot was created */
+ bool m_rnd_scan_is_new_snapshot;
+
+ /*
+ TRUE means we should skip unique key checks for this table if the
+ replication lag gets too large
+ */
+ bool m_skip_unique_check;
+
+ /*
+ TRUE means INSERT ON DUPLICATE KEY UPDATE. In such case we can optimize by
+ remember the failed attempt (if there is one that violates uniqueness check)
+ in write_row and in the following index_read to skip the lock check and read
+ entirely
+ */
+ bool m_insert_with_update;
+
+ /* TRUE if last time the insertion failed due to duplicated PK */
+ bool m_dup_pk_found;
+
+#ifndef DBUG_OFF
+ /* Last retreived record for sanity checking */
+ String m_dup_pk_retrieved_record;
+#endif
+
+ /**
+ @brief
+ This is a bitmap of indexes (i.e. a set) whose keys (in future, values) may
+ be changed by this statement. Indexes that are not in the bitmap do not need
+ to be updated.
+ @note Valid inside UPDATE statements, IIF(m_update_scope_is_valid == true).
+ */
+ my_core::key_map m_update_scope;
+ bool m_update_scope_is_valid;
+
+ /* SST information used for bulk loading the primary key */
+ std::shared_ptr<Rdb_sst_info> m_sst_info;
+
+ /*
+ MySQL index number for duplicate key error
+ */
+ uint m_dupp_errkey;
+
+ int create_key_defs(const TABLE *const table_arg,
+ Rdb_tbl_def *const tbl_def_arg,
+ const TABLE *const old_table_arg = nullptr,
+ const Rdb_tbl_def *const old_tbl_def_arg = nullptr) const
+ MY_ATTRIBUTE((__nonnull__(2, 3), __warn_unused_result__));
+ int secondary_index_read(const int keyno, uchar *const buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ void setup_iterator_for_rnd_scan();
+ bool is_ascending(const Rdb_key_def &keydef,
+ enum ha_rkey_function find_flag) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ void setup_iterator_bounds(const Rdb_key_def &kd,
+ const rocksdb::Slice &eq_cond, size_t bound_len,
+ uchar *const lower_bound, uchar *const upper_bound,
+ rocksdb::Slice *lower_bound_slice,
+ rocksdb::Slice *upper_bound_slice);
+ bool can_use_bloom_filter(THD *thd, const Rdb_key_def &kd,
+ const rocksdb::Slice &eq_cond,
+ const bool use_all_keys);
+ bool check_bloom_and_set_bounds(THD *thd, const Rdb_key_def &kd,
+ const rocksdb::Slice &eq_cond,
+ const bool use_all_keys, size_t bound_len,
+ uchar *const lower_bound,
+ uchar *const upper_bound,
+ rocksdb::Slice *lower_bound_slice,
+ rocksdb::Slice *upper_bound_slice);
+ void setup_scan_iterator(const Rdb_key_def &kd, rocksdb::Slice *slice,
+ const bool use_all_keys, const uint eq_cond_len)
+ MY_ATTRIBUTE((__nonnull__));
+ void release_scan_iterator(void);
+
+ rocksdb::Status get_for_update(
+ Rdb_transaction *const tx,
+ rocksdb::ColumnFamilyHandle *const column_family,
+ const rocksdb::Slice &key, rocksdb::PinnableSlice *value) const;
+
+ int get_row_by_rowid(uchar *const buf, const char *const rowid,
+ const uint rowid_size, const bool skip_lookup = false,
+ const bool skip_ttl_check = true)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int get_row_by_rowid(uchar *const buf, const uchar *const rowid,
+ const uint rowid_size, const bool skip_lookup = false,
+ const bool skip_ttl_check = true)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__)) {
+ return get_row_by_rowid(buf, reinterpret_cast<const char *>(rowid),
+ rowid_size, skip_lookup, skip_ttl_check);
+ }
+
+ void load_auto_incr_value();
+ ulonglong load_auto_incr_value_from_index();
+ void update_auto_incr_val(ulonglong val);
+ void update_auto_incr_val_from_field();
+ rocksdb::Status get_datadic_auto_incr(Rdb_transaction *const tx,
+ const GL_INDEX_ID &gl_index_id,
+ ulonglong *new_val) const;
+ longlong update_hidden_pk_val();
+ int load_hidden_pk_value() MY_ATTRIBUTE((__warn_unused_result__));
+ int read_hidden_pk_id_from_rowkey(longlong *const hidden_pk_id)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ bool can_use_single_delete(const uint index) const
+ MY_ATTRIBUTE((__warn_unused_result__));
+ bool is_blind_delete_enabled();
+ bool skip_unique_check() const MY_ATTRIBUTE((__warn_unused_result__));
+#ifdef MARIAROCKS_NOT_YET // MDEV-10975
+ void set_force_skip_unique_check(bool skip) override;
+#endif
+ bool commit_in_the_middle() MY_ATTRIBUTE((__warn_unused_result__));
+ bool do_bulk_commit(Rdb_transaction *const tx)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ bool has_hidden_pk(const TABLE *const table) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ void update_row_stats(const operation_type &type);
+
+ void set_last_rowkey(const uchar *const old_data);
+
+ /*
+ For the active index, indicates which columns must be covered for the
+ current lookup to be covered. If the bitmap field is null, that means this
+ index does not cover the current lookup for any record.
+ */
+ MY_BITMAP m_lookup_bitmap = {nullptr, nullptr, nullptr, 0, 0};
+
+ int alloc_key_buffers(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ bool alloc_alter_buffers = false)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ void free_key_buffers();
+
+ // the buffer size should be at least 2*Rdb_key_def::INDEX_NUMBER_SIZE
+ rocksdb::Range get_range(const int i, uchar buf[]) const;
+
+ /*
+ Perf timers for data reads
+ */
+ Rdb_io_perf m_io_perf;
+
+ /*
+ Update stats
+ */
+ void update_stats(void);
+
+ public:
+ /*
+ The following two are currently only used for getting the range bounds
+ from QUICK_SELECT_DESC.
+ We don't need to implement prepare_index_key_scan[_map] because it is
+ only used with HA_READ_KEY_EXACT and HA_READ_PREFIX_LAST where one
+ can infer the bounds of the range being scanned, anyway.
+ */
+ int prepare_index_scan() override;
+ int prepare_range_scan(const key_range *start_key,
+ const key_range *end_key) override;
+
+ /*
+ Controls whether writes include checksums. This is updated from the session
+ variable
+ at the start of each query.
+ */
+ bool m_store_row_debug_checksums;
+
+ int m_checksums_pct;
+
+ ha_rocksdb(my_core::handlerton *const hton,
+ my_core::TABLE_SHARE *const table_arg);
+ virtual ~ha_rocksdb() override {
+ int err MY_ATTRIBUTE((__unused__));
+ err = finalize_bulk_load(false);
+ if (err != 0) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Error %d finalizing bulk load while closing "
+ "handler.",
+ err);
+ }
+ }
+
+ /** @brief
+ The name that will be used for display purposes.
+ */
+ const char *table_type() const /*override*/ {
+ DBUG_ENTER_FUNC();
+ // MariaDB: this function is not virtual, however ha_innodb
+ // declares it (and then never uses!) psergey-merge-todo:.
+ DBUG_RETURN(rocksdb_hton_name);
+ }
+
+ /* The following is only used by SHOW KEYS: */
+ const char *index_type(uint inx) override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN("LSMTREE");
+ }
+
+ /*
+ Not present in MariaDB:
+ const char **bas_ext() const override;
+ */
+
+ /*
+ Returns the name of the table's base name
+ */
+ const std::string &get_table_basename() const;
+
+ /** @brief
+ This is a list of flags that indicate what functionality the storage engine
+ implements. The current table flags are documented in handler.h
+ */
+ ulonglong table_flags() const override ;
+private:
+ bool init_with_fields(); /* no 'override' in MariaDB */
+public:
+ /** @brief
+ This is a bitmap of flags that indicates how the storage engine
+ implements indexes. The current index flags are documented in
+ handler.h. If you do not implement indexes, just return zero here.
+
+ @details
+ part is the key part to check. First key part is 0.
+ If all_parts is set, MySQL wants to know the flags for the combined
+ index, up to and including 'part'.
+ */
+ ulong index_flags(uint inx, uint part, bool all_parts) const override;
+
+ const key_map *keys_to_use_for_scanning() override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(&key_map_full);
+ }
+
+ bool should_store_row_debug_checksums() const {
+ return m_store_row_debug_checksums && (rand() % 100 < m_checksums_pct);
+ }
+
+ int rename_table(const char *const from, const char *const to) override
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int convert_record_from_storage_format(const rocksdb::Slice *const key,
+ const rocksdb::Slice *const value,
+ uchar *const buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int convert_record_from_storage_format(const rocksdb::Slice *const key,
+ uchar *const buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static const std::vector<std::string> parse_into_tokens(const std::string &s,
+ const char delim);
+
+ static const std::string generate_cf_name(
+ const uint index, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg, bool *per_part_match_found);
+
+ static const char *get_key_name(const uint index,
+ const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static const char *get_key_comment(const uint index,
+ const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static const std::string get_table_comment(const TABLE *const table_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static bool is_hidden_pk(const uint index, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static uint pk_index(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ static bool is_pk(const uint index, const TABLE *table_arg,
+ const Rdb_tbl_def *tbl_def_arg)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ /** @brief
+ unireg.cc will call max_supported_record_length(), max_supported_keys(),
+ max_supported_key_parts(), uint max_supported_key_length()
+ to make sure that the storage engine can handle the data it is about to
+ send. Return *real* limits of your storage engine here; MySQL will do
+ min(your_limits, MySQL_limits) automatically.
+ */
+ uint max_supported_record_length() const override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(HA_MAX_REC_LENGTH);
+ }
+
+ uint max_supported_keys() const override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(MAX_INDEXES);
+ }
+
+ uint max_supported_key_parts() const override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(MAX_REF_PARTS);
+ }
+
+ uint max_supported_key_part_length() const override;
+
+ /** @brief
+ unireg.cc will call this to make sure that the storage engine can handle
+ the data it is about to send. Return *real* limits of your storage engine
+ here; MySQL will do min(your_limits, MySQL_limits) automatically.
+
+ @details
+ There is no need to implement ..._key_... methods if your engine doesn't
+ support indexes.
+ */
+ uint max_supported_key_length() const override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(16 * 1024); /* just to return something*/
+ }
+
+ /**
+ TODO: return actual upper bound of number of records in the table.
+ (e.g. save number of records seen on full table scan and/or use file size
+ as upper bound)
+ */
+ ha_rows estimate_rows_upper_bound() override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(HA_POS_ERROR);
+ }
+
+ /* At the moment, we're ok with default handler::index_init() implementation.
+ */
+ int index_read_map(uchar *const buf, const uchar *const key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int index_read_map_impl(uchar *const buf, const uchar *const key,
+ key_part_map keypart_map,
+ enum ha_rkey_function find_flag,
+ const key_range *end_key)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ bool is_using_full_key(key_part_map keypart_map, uint actual_key_parts);
+ int read_range_first(const key_range *const start_key,
+ const key_range *const end_key, bool eq_range,
+ bool sorted) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ virtual double scan_time() override {
+ DBUG_ENTER_FUNC();
+
+ DBUG_RETURN(
+ static_cast<double>((stats.records + stats.deleted) / 20.0 + 10));
+ }
+
+ virtual double read_time(uint, uint, ha_rows rows) override;
+ virtual void print_error(int error, myf errflag) override;
+
+ int open(const char *const name, int mode, uint test_if_locked) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int close(void) override MY_ATTRIBUTE((__warn_unused_result__));
+
+ int write_row(const uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int update_row(const uchar *const old_data, const uchar *const new_data) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int delete_row(const uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ rocksdb::Status delete_or_singledelete(uint index, Rdb_transaction *const tx,
+ rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::Slice &key)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int index_next(uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int index_next_with_direction(uchar *const buf, bool move_forward)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int index_prev(uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int index_first(uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int index_last(uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ class Item *idx_cond_push(uint keyno, class Item *const idx_cond) override;
+ /*
+ Default implementation from cancel_pushed_idx_cond() suits us
+ */
+ private:
+ struct key_def_cf_info {
+ rocksdb::ColumnFamilyHandle *cf_handle;
+ bool is_reverse_cf;
+ bool is_per_partition_cf;
+ };
+
+ struct update_row_info {
+ Rdb_transaction *tx;
+ const uchar *new_data;
+ const uchar *old_data;
+ rocksdb::Slice new_pk_slice;
+ rocksdb::Slice old_pk_slice;
+ rocksdb::Slice old_pk_rec;
+
+ // "unpack_info" data for the new PK value
+ Rdb_string_writer *new_pk_unpack_info;
+
+ longlong hidden_pk_id;
+ bool skip_unique_check;
+ };
+
+ /*
+ Used to check for duplicate entries during fast unique secondary index
+ creation.
+ */
+ struct unique_sk_buf_info {
+ bool sk_buf_switch = false;
+ rocksdb::Slice sk_memcmp_key;
+ rocksdb::Slice sk_memcmp_key_old;
+ uchar *dup_sk_buf;
+ uchar *dup_sk_buf_old;
+
+ /*
+ This method is meant to be called back to back during inplace creation
+ of unique indexes. It will switch between two buffers, which
+ will each store the memcmp form of secondary keys, which are then
+ converted to slices in sk_memcmp_key or sk_memcmp_key_old.
+
+ Switching buffers on each iteration allows us to retain the
+ sk_memcmp_key_old value for duplicate comparison.
+ */
+ inline uchar *swap_and_get_sk_buf() {
+ sk_buf_switch = !sk_buf_switch;
+ return sk_buf_switch ? dup_sk_buf : dup_sk_buf_old;
+ }
+ };
+
+ int create_cfs(const TABLE *const table_arg, Rdb_tbl_def *const tbl_def_arg,
+ std::array<struct key_def_cf_info, MAX_INDEXES + 1> *const cfs)
+ const MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int create_key_def(const TABLE *const table_arg, const uint i,
+ const Rdb_tbl_def *const tbl_def_arg,
+ std::shared_ptr<Rdb_key_def> *const new_key_def,
+ const struct key_def_cf_info &cf_info, uint64 ttl_duration,
+ const std::string &ttl_column) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int create_inplace_key_defs(
+ const TABLE *const table_arg, Rdb_tbl_def *vtbl_def_arg,
+ const TABLE *const old_table_arg,
+ const Rdb_tbl_def *const old_tbl_def_arg,
+ const std::array<key_def_cf_info, MAX_INDEXES + 1> &cf,
+ uint64 ttl_duration, const std::string &ttl_column) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ std::unordered_map<std::string, uint> get_old_key_positions(
+ const TABLE *table_arg, const Rdb_tbl_def *tbl_def_arg,
+ const TABLE *old_table_arg, const Rdb_tbl_def *old_tbl_def_arg) const
+ MY_ATTRIBUTE((__nonnull__));
+
+ using handler::compare_key_parts;
+ int compare_key_parts(const KEY *const old_key,
+ const KEY *const new_key) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int compare_keys(const KEY *const old_key, const KEY *const new_key) const
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ bool should_hide_ttl_rec(const Rdb_key_def &kd,
+ const rocksdb::Slice &ttl_rec_val,
+ const int64_t curr_ts)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int rocksdb_skip_expired_records(const Rdb_key_def &kd,
+ rocksdb::Iterator *const iter,
+ bool seek_backward);
+
+ int index_first_intern(uchar *buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int index_last_intern(uchar *buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int find_icp_matching_index_rec(const bool move_forward, uchar *const buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ void calc_updated_indexes();
+ int update_write_row(const uchar *const old_data, const uchar *const new_data,
+ const bool skip_unique_check)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int get_pk_for_update(struct update_row_info *const row_info);
+ int check_and_lock_unique_pk(const uint key_id,
+ const struct update_row_info &row_info,
+ bool *const found)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int check_and_lock_sk(const uint key_id,
+ const struct update_row_info &row_info,
+ bool *const found)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int check_uniqueness_and_lock(const struct update_row_info &row_info,
+ bool pk_changed)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ bool over_bulk_load_threshold(int *err)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int check_duplicate_sk(const TABLE *table_arg, const Rdb_key_def &key_def,
+ const rocksdb::Slice *key,
+ struct unique_sk_buf_info *sk_info)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int bulk_load_key(Rdb_transaction *const tx, const Rdb_key_def &kd,
+ const rocksdb::Slice &key, const rocksdb::Slice &value,
+ bool sort)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ void update_bytes_written(ulonglong bytes_written);
+ int update_write_pk(const Rdb_key_def &kd,
+ const struct update_row_info &row_info,
+ const bool pk_changed)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int update_write_sk(const TABLE *const table_arg, const Rdb_key_def &kd,
+ const struct update_row_info &row_info,
+ const bool bulk_load_sk)
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int update_write_indexes(const struct update_row_info &row_info,
+ const bool pk_changed)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int read_key_exact(const Rdb_key_def &kd, rocksdb::Iterator *const iter,
+ const bool using_full_key, const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int read_before_key(const Rdb_key_def &kd, const bool using_full_key,
+ const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int read_after_key(const Rdb_key_def &kd, const rocksdb::Slice &key_slice,
+ const int64_t ttl_filter_ts)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int position_to_correct_key(const Rdb_key_def &kd,
+ const enum ha_rkey_function &find_flag,
+ const bool full_key_match, const uchar *const key,
+ const key_part_map &keypart_map,
+ const rocksdb::Slice &key_slice,
+ bool *const move_forward,
+ const int64_t ttl_filter_ts)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int read_row_from_primary_key(uchar *const buf)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ int read_row_from_secondary_key(uchar *const buf, const Rdb_key_def &kd,
+ bool move_forward)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int calc_eq_cond_len(const Rdb_key_def &kd,
+ const enum ha_rkey_function &find_flag,
+ const rocksdb::Slice &slice,
+ const int bytes_changed_by_succ,
+ const key_range *const end_key,
+ uint *const end_key_packed_size)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ Rdb_tbl_def *get_table_if_exists(const char *const tablename)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+ void read_thd_vars(THD *const thd) MY_ATTRIBUTE((__nonnull__));
+
+ bool contains_foreign_key(THD *const thd)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int inplace_populate_sk(
+ TABLE *const table_arg,
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int finalize_bulk_load(bool print_client_error = true)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int calculate_stats_for_table() MY_ATTRIBUTE((__warn_unused_result__));
+
+ bool should_skip_invalidated_record(const int rc);
+ bool should_recreate_snapshot(const int rc, const bool is_new_snapshot);
+ bool can_assume_tracked(THD *thd);
+
+ public:
+ int index_init(uint idx, bool sorted) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int index_end() override MY_ATTRIBUTE((__warn_unused_result__));
+
+ void unlock_row() override;
+
+ /** @brief
+ Unlike index_init(), rnd_init() can be called two consecutive times
+ without rnd_end() in between (it only makes sense if scan=1). In this
+ case, the second call should prepare for the new table scan (e.g if
+ rnd_init() allocates the cursor, the second call should position the
+ cursor to the start of the table; no need to deallocate and allocate
+ it again. This is a required method.
+ */
+ int rnd_init(bool scan) override MY_ATTRIBUTE((__warn_unused_result__));
+ int rnd_end() override MY_ATTRIBUTE((__warn_unused_result__));
+
+ int rnd_next(uchar *const buf) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int rnd_next_with_direction(uchar *const buf, bool move_forward)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int rnd_pos(uchar *const buf, uchar *const pos) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ void position(const uchar *const record) override;
+ int info(uint) override;
+
+ /* This function will always return success, therefore no annotation related
+ * to checking the return value. Can't change the signature because it's
+ * required by the interface. */
+ int extra(enum ha_extra_function operation) override;
+
+ int start_stmt(THD *const thd, thr_lock_type lock_type) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int external_lock(THD *const thd, int lock_type) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int truncate() override MY_ATTRIBUTE((__warn_unused_result__));
+
+ int reset() override {
+ DBUG_ENTER_FUNC();
+
+ /* Free blob data */
+ m_retrieved_record.Reset();
+
+ DBUG_RETURN(HA_EXIT_SUCCESS);
+ }
+
+ int check(THD *const thd, HA_CHECK_OPT *const check_opt) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int remove_rows(Rdb_tbl_def *const tbl);
+ ha_rows records_in_range(uint inx,
+ const key_range *const min_key,
+ const key_range *const max_key,
+ page_range *pages) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ int delete_table(Rdb_tbl_def *const tbl);
+ int delete_table(const char *const from) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int create(const char *const name, TABLE *const form,
+ HA_CREATE_INFO *const create_info) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int create_table(const std::string &table_name, const TABLE *table_arg,
+ ulonglong auto_increment_value);
+ bool check_if_incompatible_data(HA_CREATE_INFO *const info,
+ uint table_changes) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ THR_LOCK_DATA **store_lock(THD *const thd, THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ my_bool register_query_cache_table(THD *const thd, const char *table_key,
+ uint key_length,
+ qc_engine_callback *const engine_callback,
+ ulonglong *const engine_data) override {
+ DBUG_ENTER_FUNC();
+
+ /* Currently, we don't support query cache */
+ DBUG_RETURN(FALSE);
+ }
+
+ bool get_error_message(const int error, String *const buf) override
+ MY_ATTRIBUTE((__nonnull__));
+
+ static int rdb_error_to_mysql(const rocksdb::Status &s,
+ const char *msg = nullptr)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ void get_auto_increment(ulonglong offset, ulonglong increment,
+ ulonglong nb_desired_values,
+ ulonglong *const first_value,
+ ulonglong *const nb_reserved_values) override;
+ void update_create_info(HA_CREATE_INFO *const create_info) override;
+ int optimize(THD *const thd, HA_CHECK_OPT *const check_opt) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+ int analyze(THD *const thd, HA_CHECK_OPT *const check_opt) override
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+ enum_alter_inplace_result check_if_supported_inplace_alter(
+ TABLE *altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info) override;
+
+ bool prepare_inplace_alter_table(
+ TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info) override;
+
+ bool inplace_alter_table(
+ TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info) override;
+
+ bool commit_inplace_alter_table(
+ TABLE *const altered_table,
+ my_core::Alter_inplace_info *const ha_alter_info, bool commit) override;
+
+ void set_skip_unique_check_tables(const char *const whitelist);
+#ifdef MARIAROCKS_NOT_YET // MDEV-10976
+ bool is_read_free_rpl_table() const;
+#endif
+
+#ifdef MARIAROCKS_NOT_YET // MDEV-10976
+ public:
+ virtual void rpl_before_delete_rows() override;
+ virtual void rpl_after_delete_rows() override;
+ virtual void rpl_before_update_rows() override;
+ virtual void rpl_after_update_rows() override;
+ virtual bool use_read_free_rpl() const override;
+#endif // MARIAROCKS_NOT_YET
+
+ private:
+ /* Flags tracking if we are inside different replication operation */
+ bool m_in_rpl_delete_rows;
+ bool m_in_rpl_update_rows;
+
+ bool m_force_skip_unique_check;
+};
+
+/*
+ Helper class for in-place alter, for storing handler context between inplace
+ alter calls
+*/
+struct Rdb_inplace_alter_ctx : public my_core::inplace_alter_handler_ctx {
+ /* The new table definition */
+ Rdb_tbl_def *const m_new_tdef;
+
+ /* Stores the original key definitions */
+ std::shared_ptr<Rdb_key_def> *const m_old_key_descr;
+
+ /* Stores the new key definitions */
+ std::shared_ptr<Rdb_key_def> *m_new_key_descr;
+
+ /* Stores the old number of key definitions */
+ const uint m_old_n_keys;
+
+ /* Stores the new number of key definitions */
+ const uint m_new_n_keys;
+
+ /* Stores the added key glids */
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> m_added_indexes;
+
+ /* Stores the dropped key glids */
+ const std::unordered_set<GL_INDEX_ID> m_dropped_index_ids;
+
+ /* Stores number of keys to add */
+ const uint m_n_added_keys;
+
+ /* Stores number of keys to drop */
+ const uint m_n_dropped_keys;
+
+ /* Stores the largest current auto increment value in the index */
+ const ulonglong m_max_auto_incr;
+
+ Rdb_inplace_alter_ctx(
+ Rdb_tbl_def *new_tdef, std::shared_ptr<Rdb_key_def> *old_key_descr,
+ std::shared_ptr<Rdb_key_def> *new_key_descr, uint old_n_keys,
+ uint new_n_keys,
+ std::unordered_set<std::shared_ptr<Rdb_key_def>> added_indexes,
+ std::unordered_set<GL_INDEX_ID> dropped_index_ids, uint n_added_keys,
+ uint n_dropped_keys, ulonglong max_auto_incr)
+ : my_core::inplace_alter_handler_ctx(),
+ m_new_tdef(new_tdef),
+ m_old_key_descr(old_key_descr),
+ m_new_key_descr(new_key_descr),
+ m_old_n_keys(old_n_keys),
+ m_new_n_keys(new_n_keys),
+ m_added_indexes(added_indexes),
+ m_dropped_index_ids(dropped_index_ids),
+ m_n_added_keys(n_added_keys),
+ m_n_dropped_keys(n_dropped_keys),
+ m_max_auto_incr(max_auto_incr) {}
+
+ ~Rdb_inplace_alter_ctx() {}
+
+ private:
+ /* Disable Copying */
+ Rdb_inplace_alter_ctx(const Rdb_inplace_alter_ctx &);
+ Rdb_inplace_alter_ctx &operator=(const Rdb_inplace_alter_ctx &);
+};
+
+// file name indicating RocksDB data corruption
+std::string rdb_corruption_marker_file_name();
+
+const int MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL= MariaDB_PLUGIN_MATURITY_STABLE;
+
+extern bool prevent_myrocks_loading;
+
+void sql_print_verbose_info(const char *format, ...);
+
+} // namespace myrocks
+
diff --git a/storage/rocksdb/ha_rocksdb_proto.h b/storage/rocksdb/ha_rocksdb_proto.h
new file mode 100644
index 00000000..03d24957
--- /dev/null
+++ b/storage/rocksdb/ha_rocksdb_proto.h
@@ -0,0 +1,103 @@
+/*
+ Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ standard header files */
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./sql_string.h"
+
+/* RocksDB includes */
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/transaction_db.h"
+
+namespace myrocks {
+
+enum RDB_IO_ERROR_TYPE {
+ RDB_IO_ERROR_TX_COMMIT,
+ RDB_IO_ERROR_DICT_COMMIT,
+ RDB_IO_ERROR_BG_THREAD,
+ RDB_IO_ERROR_GENERAL,
+ RDB_IO_ERROR_LAST
+};
+
+const char *get_rdb_io_error_string(const RDB_IO_ERROR_TYPE err_type);
+
+void rdb_handle_io_error(const rocksdb::Status status,
+ const RDB_IO_ERROR_TYPE err_type)
+#if defined(__clang__)
+ MY_ATTRIBUTE((optnone));
+#else
+ MY_ATTRIBUTE((noinline,noclone));
+#endif
+
+int rdb_normalize_tablename(const std::string &tablename, std::string *str)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+int rdb_split_normalized_tablename(const std::string &fullname, std::string *db,
+ std::string *table = nullptr,
+ std::string *partition = nullptr)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+std::vector<std::string> rdb_get_open_table_names(void);
+
+class Rdb_perf_counters;
+int rdb_get_table_perf_counters(const char *tablename,
+ Rdb_perf_counters *counters)
+ MY_ATTRIBUTE((__nonnull__(2)));
+
+void rdb_get_global_perf_counters(Rdb_perf_counters *counters)
+ MY_ATTRIBUTE((__nonnull__(1)));
+
+void rdb_queue_save_stats_request();
+
+/*
+ Access to singleton objects.
+*/
+
+rocksdb::TransactionDB *rdb_get_rocksdb_db();
+
+class Rdb_cf_manager;
+Rdb_cf_manager &rdb_get_cf_manager();
+
+const rocksdb::BlockBasedTableOptions &rdb_get_table_options();
+bool rdb_is_ttl_enabled();
+bool rdb_is_ttl_read_filtering_enabled();
+#ifndef DBUG_OFF
+int rdb_dbug_set_ttl_rec_ts();
+int rdb_dbug_set_ttl_snapshot_ts();
+int rdb_dbug_set_ttl_read_filter_ts();
+bool rdb_dbug_set_ttl_ignore_pk();
+#endif
+
+enum operation_type : int;
+void rdb_update_global_stats(const operation_type &type, uint count,
+ bool is_system_table = false);
+
+class Rdb_dict_manager;
+Rdb_dict_manager *rdb_get_dict_manager(void)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+class Rdb_ddl_manager;
+Rdb_ddl_manager *rdb_get_ddl_manager(void)
+ MY_ATTRIBUTE((__warn_unused_result__));
+
+class Rdb_binlog_manager;
+Rdb_binlog_manager *rdb_get_binlog_manager(void)
+ MY_ATTRIBUTE((__warn_unused_result__));
+} // namespace myrocks
diff --git a/storage/rocksdb/logger.h b/storage/rocksdb/logger.h
new file mode 100644
index 00000000..8902bc18
--- /dev/null
+++ b/storage/rocksdb/logger.h
@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#include <log.h>
+#include <sstream>
+#include <string>
+
+namespace myrocks {
+
+class Rdb_logger : public rocksdb::Logger {
+ public:
+ explicit Rdb_logger(const rocksdb::InfoLogLevel log_level =
+ rocksdb::InfoLogLevel::ERROR_LEVEL)
+ : m_mysql_log_level(log_level) {}
+
+ void Logv(const rocksdb::InfoLogLevel log_level, const char *format,
+ va_list ap) override {
+ DBUG_ASSERT(format != nullptr);
+
+ enum loglevel mysql_log_level;
+
+ if (m_logger) {
+ m_logger->Logv(log_level, format, ap);
+ }
+
+ if (log_level < m_mysql_log_level) {
+ return;
+ }
+
+ if (log_level >= rocksdb::InfoLogLevel::ERROR_LEVEL) {
+ mysql_log_level = ERROR_LEVEL;
+ } else if (log_level >= rocksdb::InfoLogLevel::WARN_LEVEL) {
+ mysql_log_level = WARNING_LEVEL;
+ } else {
+ mysql_log_level = INFORMATION_LEVEL;
+ }
+
+ // log to MySQL
+ std::string f("LibRocksDB:");
+ f.append(format);
+ error_log_print(mysql_log_level, f.c_str(), ap);
+ }
+
+ void Logv(const char *format, va_list ap) override {
+ DBUG_ASSERT(format != nullptr);
+ // If no level is specified, it is by default at information level
+ Logv(rocksdb::InfoLogLevel::INFO_LEVEL, format, ap);
+ }
+
+ void SetRocksDBLogger(const std::shared_ptr<rocksdb::Logger> logger) {
+ m_logger = logger;
+ }
+
+ void SetInfoLogLevel(const rocksdb::InfoLogLevel log_level) override {
+ // The InfoLogLevel for the logger is used by rocksdb to filter
+ // messages, so it needs to be the lower of the two loggers
+ rocksdb::InfoLogLevel base_level = log_level;
+
+ if (m_logger && m_logger->GetInfoLogLevel() < base_level) {
+ base_level = m_logger->GetInfoLogLevel();
+ }
+ rocksdb::Logger::SetInfoLogLevel(base_level);
+ m_mysql_log_level = log_level;
+ }
+
+ private:
+ std::shared_ptr<rocksdb::Logger> m_logger;
+ rocksdb::InfoLogLevel m_mysql_log_level;
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/myrocks_hotbackup.py b/storage/rocksdb/myrocks_hotbackup.py
new file mode 100755
index 00000000..42c25c95
--- /dev/null
+++ b/storage/rocksdb/myrocks_hotbackup.py
@@ -0,0 +1,697 @@
+#!@PYTHON_SHEBANG@
+
+from __future__ import division
+from optparse import OptionParser
+import collections
+import signal
+import os
+import stat
+import sys
+import re
+import subprocess
+import logging
+import logging.handlers
+import time
+import datetime
+import shutil
+import traceback
+import tempfile
+
+import MySQLdb
+import MySQLdb.connections
+from MySQLdb import OperationalError, ProgrammingError
+
+logger = None
+opts = None
+rocksdb_files = ['MANIFEST', 'CURRENT', 'OPTIONS']
+rocksdb_data_suffix = '.sst'
+rocksdb_wal_suffix = '.log'
+exclude_files = ['master.info', 'relay-log.info', 'worker-relay-log.info',
+ 'auto.cnf', 'gaplock.log', 'ibdata', 'ib_logfile', '.trash']
+wdt_bin = 'wdt'
+
+def is_manifest(fname):
+ for m in rocksdb_files:
+ if fname.startswith(m):
+ return True
+ return False
+
+class Writer(object):
+ a = None
+ def __init__(self):
+ a = None
+
+class StreamWriter(Writer):
+ stream_cmd= ''
+
+ def __init__(self, stream_option, direct = 0):
+ super(StreamWriter, self).__init__()
+ if stream_option == 'tar':
+ self.stream_cmd= 'tar chf -'
+ elif stream_option == 'xbstream':
+ self.stream_cmd= 'xbstream -c'
+ if direct:
+ self.stream_cmd = self.stream_cmd + ' -d'
+ else:
+ raise Exception("Only tar or xbstream is supported as streaming option.")
+
+ def write(self, file_name):
+ rc= os.system(self.stream_cmd + " " + file_name)
+ if (rc != 0):
+ raise Exception("Got error on stream write: " + str(rc) + " " + file_name)
+
+
+class MiscFilesProcessor():
+ datadir = None
+ wildcard = r'.*\.[frm|MYD|MYI|MAD|MAI|MRG|TRG|TRN|ARM|ARZ|CSM|CSV|opt|par]'
+ regex = None
+ start_backup_time = None
+ skip_check_frm_timestamp = None
+
+ def __init__(self, datadir, skip_check_frm_timestamp, start_backup_time):
+ self.datadir = datadir
+ self.regex = re.compile(self.wildcard)
+ self.skip_check_frm_timestamp = skip_check_frm_timestamp
+ self.start_backup_time = start_backup_time
+
+ def process_db(self, db):
+ # do nothing
+ pass
+
+ def process_file(self, path):
+ # do nothing
+ pass
+
+ def check_frm_timestamp(self, fname, path):
+ if not self.skip_check_frm_timestamp and fname.endswith('.frm'):
+ if os.path.getmtime(path) > self.start_backup_time:
+ logger.error('FRM file %s was updated after starting backups. '
+ 'Schema could have changed and the resulting copy may '
+ 'not be valid. Aborting. '
+ '(backup time: %s, file modifled time: %s)',
+ path, datetime.datetime.fromtimestamp(self.start_backup_time).strftime('%Y-%m-%d %H:%M:%S'),
+ datetime.datetime.fromtimestamp(os.path.getmtime(path)).strftime('%Y-%m-%d %H:%M:%S'))
+ raise Exception("Inconsistent frm file timestamp");
+
+ def process(self):
+ os.chdir(self.datadir)
+ for db in self.get_databases():
+ logger.info("Starting MySQL misc file traversal from database %s..", db)
+ self.process_db(db)
+ for f in self.get_files(db):
+ if self.match(f):
+ rel_path = os.path.join(db, f)
+ self.check_frm_timestamp(f, rel_path)
+ self.process_file(rel_path)
+ logger.info("Traversing misc files from data directory..")
+ for f in self.get_files(""):
+ should_skip = False
+ for e in exclude_files:
+ if f.startswith(e) or f.endswith(e):
+ logger.info("Skipping %s", f)
+ should_skip = True
+ break
+ if not should_skip:
+ self.process_file(f)
+
+ def match(self, filename):
+ if self.regex.match(filename):
+ return True
+ else:
+ return False
+
+ def get_databases(self):
+ dbs = []
+ dirs = [ d for d in os.listdir(self.datadir) \
+ if not os.path.isfile(os.path.join(self.datadir,d))]
+ for db in dirs:
+ if not db.startswith('.') and not self._is_socket(db) and not db == "#rocksdb":
+ dbs.append(db)
+ return dbs
+
+ def get_files(self, db):
+ dbdir = self.datadir + "/" + db
+ return [ f for f in os.listdir(dbdir) \
+ if os.path.isfile(os.path.join(dbdir,f))]
+
+ def _is_socket(self, item):
+ mode = os.stat(os.path.join(self.datadir, item)).st_mode
+ if stat.S_ISSOCK(mode):
+ return True
+ return False
+
+
+class MySQLBackup(MiscFilesProcessor):
+ writer = None
+
+ def __init__(self, datadir, writer, skip_check_frm_timestamp, start_backup_time):
+ MiscFilesProcessor.__init__(self, datadir, skip_check_frm_timestamp, start_backup_time)
+ self.writer = writer
+
+ def process_file(self, fname): # overriding base class
+ self.writer.write(fname)
+
+
+class MiscFilesLinkCreator(MiscFilesProcessor):
+ snapshot_dir = None
+
+ def __init__(self, datadir, snapshot_dir, skip_check_frm_timestamp, start_backup_time):
+ MiscFilesProcessor.__init__(self, datadir, skip_check_frm_timestamp, start_backup_time)
+ self.snapshot_dir = snapshot_dir
+
+ def process_db(self, db):
+ snapshot_sub_dir = os.path.join(self.snapshot_dir, db)
+ os.makedirs(snapshot_sub_dir)
+
+ def process_file(self, path):
+ dst_path = os.path.join(self.snapshot_dir, path)
+ os.link(path, dst_path)
+
+
+# RocksDB backup
+class RocksDBBackup():
+ source_dir = None
+ writer = None
+ # sst files sent in this backup round
+ sent_sst = {}
+ # target sst files in this backup round
+ target_sst = {}
+ # sst files sent in all backup rounds
+ total_sent_sst= {}
+ # sum of sst file size sent in this backup round
+ sent_sst_size = 0
+ # sum of target sst file size in this backup round
+ # if sent_sst_size becomes equal to target_sst_size,
+ # it means the backup round finished backing up all sst files
+ target_sst_size = 0
+ # sum of all sst file size sent all backup rounds
+ total_sent_sst_size= 0
+ # sum of all target sst file size from all backup rounds
+ total_target_sst_size = 0
+ show_progress_size_interval= 1073741824 # 1GB
+ wal_files= []
+ manifest_files= []
+ finished= False
+
+ def __init__(self, source_dir, writer, prev):
+ self.source_dir = source_dir
+ self.writer = writer
+ os.chdir(self.source_dir)
+ self.init_target_files(prev)
+
+ def init_target_files(self, prev):
+ sst = {}
+ self.sent_sst = {}
+ self.target_sst= {}
+ self.total_sent_sst = {}
+ self.sent_sst_size = 0
+ self.target_sst_size = 0
+ self.total_sent_sst_size= 0
+ self.total_target_sst_size= 0
+ self.wal_files= []
+ self.manifest_files= []
+
+ for f in os.listdir(self.source_dir):
+ if f.endswith(rocksdb_data_suffix):
+ # exactly the same file (same size) was sent in previous backup rounds
+ if prev is not None and f in prev.total_sent_sst and int(os.stat(f).st_size) == prev.total_sent_sst[f]:
+ continue
+ sst[f]= int(os.stat(f).st_size)
+ self.target_sst_size = self.target_sst_size + os.stat(f).st_size
+ elif is_manifest(f):
+ self.manifest_files.append(f)
+ elif f.endswith(rocksdb_wal_suffix):
+ self.wal_files.append(f)
+ self.target_sst= collections.OrderedDict(sorted(sst.items()))
+
+ if prev is not None:
+ self.total_sent_sst = prev.total_sent_sst
+ self.total_sent_sst_size = prev.total_sent_sst_size
+ self.total_target_sst_size = self.target_sst_size + prev.total_sent_sst_size
+ else:
+ self.total_target_sst_size = self.target_sst_size
+
+ def do_backup_single(self, fname):
+ self.writer.write(fname)
+ os.remove(fname)
+
+ def do_backup_sst(self, fname, size):
+ self.do_backup_single(fname)
+ self.sent_sst[fname]= size
+ self.total_sent_sst[fname]= size
+ self.sent_sst_size = self.sent_sst_size + size
+ self.total_sent_sst_size = self.total_sent_sst_size + size
+
+ def do_backup_manifest(self):
+ for f in self.manifest_files:
+ self.do_backup_single(f)
+
+ def do_backup_wal(self):
+ for f in self.wal_files:
+ self.do_backup_single(f)
+
+ # this is the last snapshot round. backing up all the rest files
+ def do_backup_final(self):
+ logger.info("Backup WAL..")
+ self.do_backup_wal()
+ logger.info("Backup Manifest..")
+ self.do_backup_manifest()
+ self.do_cleanup()
+ self.finished= True
+
+ def do_cleanup(self):
+ shutil.rmtree(self.source_dir)
+ logger.info("Cleaned up checkpoint from %s", self.source_dir)
+
+ def do_backup_until(self, time_limit):
+ logger.info("Starting backup from snapshot: target files %d", len(self.target_sst))
+ start_time= time.time()
+ last_progress_time= start_time
+ progress_size= 0
+ for fname, size in self.target_sst.iteritems():
+ self.do_backup_sst(fname, size)
+ progress_size= progress_size + size
+ elapsed_seconds = time.time() - start_time
+ progress_seconds = time.time() - last_progress_time
+
+ if self.should_show_progress(size):
+ self.show_progress(progress_size, progress_seconds)
+ progress_size=0
+ last_progress_time= time.time()
+
+ if elapsed_seconds > time_limit and self.has_sent_all_sst() is False:
+ logger.info("Snapshot round finished. Elapsed Time: %5.2f. Remaining sst files: %d",
+ elapsed_seconds, len(self.target_sst) - len(self.sent_sst))
+ self.do_cleanup()
+ break;
+ if self.has_sent_all_sst():
+ self.do_backup_final()
+
+ return self
+
+ def should_show_progress(self, size):
+ if int(self.total_sent_sst_size/self.show_progress_size_interval) > int((self.total_sent_sst_size-size)/self.show_progress_size_interval):
+ return True
+ else:
+ return False
+
+ def show_progress(self, size, seconds):
+ logger.info("Backup Progress: %5.2f%% Sent %6.2f GB of %6.2f GB data, Transfer Speed: %6.2f MB/s",
+ self.total_sent_sst_size*100/self.total_target_sst_size,
+ self.total_sent_sst_size/1024/1024/1024,
+ self.total_target_sst_size/1024/1024/1024,
+ size/seconds/1024/1024)
+
+ def print_backup_report(self):
+ logger.info("Sent %6.2f GB of sst files, %d files in total.",
+ self.total_sent_sst_size/1024/1024/1024,
+ len(self.total_sent_sst))
+
+ def has_sent_all_sst(self):
+ if self.sent_sst_size == self.target_sst_size:
+ return True
+ return False
+
+
+class MySQLUtil:
+ @staticmethod
+ def connect(user, password, port, socket=None):
+ if socket:
+ dbh = MySQLdb.Connect(user=user,
+ passwd=password,
+ unix_socket=socket)
+ else:
+ dbh = MySQLdb.Connect(user=user,
+ passwd=password,
+ port=port,
+ host="127.0.0.1")
+ return dbh
+
+ @staticmethod
+ def create_checkpoint(dbh, checkpoint_dir):
+ sql = ("SET GLOBAL rocksdb_create_checkpoint='{0}'"
+ .format(checkpoint_dir))
+ cur= dbh.cursor()
+ cur.execute(sql)
+ cur.close()
+
+ @staticmethod
+ def get_datadir(dbh):
+ sql = "SELECT @@datadir"
+ cur = dbh.cursor()
+ cur.execute(sql)
+ row = cur.fetchone()
+ return row[0]
+
+ @staticmethod
+ def is_directio_enabled(dbh):
+ sql = "SELECT @@global.rocksdb_use_direct_reads"
+ cur = dbh.cursor()
+ cur.execute(sql)
+ row = cur.fetchone()
+ return row[0]
+
+class BackupRunner:
+ datadir = None
+ start_backup_time = None
+
+ def __init__(self, datadir):
+ self.datadir = datadir
+ self.start_backup_time = time.time()
+
+ def start_backup_round(self, backup_round, prev_backup):
+ def signal_handler(*args):
+ logger.info("Got signal. Exit")
+ if b is not None:
+ logger.info("Cleaning up snapshot directory..")
+ b.do_cleanup()
+ sys.exit(1)
+
+ b = None
+ try:
+ signal.signal(signal.SIGINT, signal_handler)
+ w = None
+ if not opts.output_stream:
+ raise Exception("Currently only streaming backup is supported.")
+
+ snapshot_dir = opts.checkpoint_directory + '/' + str(backup_round)
+ dbh = MySQLUtil.connect(opts.mysql_user,
+ opts.mysql_password,
+ opts.mysql_port,
+ opts.mysql_socket)
+ direct = MySQLUtil.is_directio_enabled(dbh)
+ logger.info("Direct I/O: %d", direct)
+
+ w = StreamWriter(opts.output_stream, direct)
+
+ if not self.datadir:
+ self.datadir = MySQLUtil.get_datadir(dbh)
+ logger.info("Set datadir: %s", self.datadir)
+ logger.info("Creating checkpoint at %s", snapshot_dir)
+ MySQLUtil.create_checkpoint(dbh, snapshot_dir)
+ logger.info("Created checkpoint at %s", snapshot_dir)
+ b = RocksDBBackup(snapshot_dir, w, prev_backup)
+ return b.do_backup_until(opts.checkpoint_interval)
+ except Exception as e:
+ logger.error(e)
+ logger.error(traceback.format_exc())
+ if b is not None:
+ logger.info("Cleaning up snapshot directory.")
+ b.do_cleanup()
+ sys.exit(1)
+
+ def backup_mysql(self):
+ try:
+ w = None
+ if opts.output_stream:
+ w = StreamWriter(opts.output_stream)
+ else:
+ raise Exception("Currently only streaming backup is supported.")
+ b = MySQLBackup(self.datadir, w, opts.skip_check_frm_timestamp,
+ self.start_backup_time)
+ logger.info("Taking MySQL misc backups..")
+ b.process()
+ logger.info("MySQL misc backups done.")
+ except Exception as e:
+ logger.error(e)
+ logger.error(traceback.format_exc())
+ sys.exit(1)
+
+
+class WDTBackup:
+ datadir = None
+ start_backup_time = None
+
+ def __init__(self, datadir):
+ self.datadir = datadir
+ self.start_backup_time = time.time()
+
+ def cleanup(self, snapshot_dir, server_log):
+ if server_log:
+ server_log.seek(0)
+ logger.info("WDT server log:")
+ logger.info(server_log.read())
+ server_log.close()
+ if snapshot_dir:
+ logger.info("Cleaning up snapshot dir %s", snapshot_dir)
+ shutil.rmtree(snapshot_dir)
+
+ def backup_with_timeout(self, backup_round):
+ def signal_handler(*args):
+ logger.info("Got signal. Exit")
+ self.cleanup(snapshot_dir, server_log)
+ sys.exit(1)
+
+ logger.info("Starting backup round %d", backup_round)
+ snapshot_dir = None
+ server_log = None
+ try:
+ signal.signal(signal.SIGINT, signal_handler)
+ # create rocksdb snapshot
+ snapshot_dir = os.path.join(opts.checkpoint_directory, str(backup_round))
+ dbh = MySQLUtil.connect(opts.mysql_user,
+ opts.mysql_password,
+ opts.mysql_port,
+ opts.mysql_socket)
+ logger.info("Creating checkpoint at %s", snapshot_dir)
+ MySQLUtil.create_checkpoint(dbh, snapshot_dir)
+ logger.info("Created checkpoint at %s", snapshot_dir)
+
+ # get datadir if not provided
+ if not self.datadir:
+ self.datadir = MySQLUtil.get_datadir(dbh)
+ logger.info("Set datadir: %s", self.datadir)
+
+ # create links for misc files
+ link_creator = MiscFilesLinkCreator(self.datadir, snapshot_dir,
+ opts.skip_check_frm_timestamp,
+ self.start_backup_time)
+ link_creator.process()
+
+ current_path = os.path.join(opts.backupdir, "CURRENT")
+
+ # construct receiver cmd, using the data directory as recovery-id.
+ # we delete the current file because it is not append-only, therefore not
+ # resumable.
+ remote_cmd = (
+ "ssh {0} rm -f {1}; "
+ "{2} -directory {3} -enable_download_resumption "
+ "-recovery_id {4} -start_port 0 -abort_after_seconds {5} {6}"
+ ).format(opts.destination,
+ current_path,
+ wdt_bin,
+ opts.backupdir,
+ self.datadir,
+ opts.checkpoint_interval,
+ opts.extra_wdt_receiver_options)
+ logger.info("WDT remote cmd %s", remote_cmd)
+ server_log = tempfile.TemporaryFile()
+ remote_process = subprocess.Popen(remote_cmd.split(),
+ stdout=subprocess.PIPE,
+ stderr=server_log)
+ wdt_url = remote_process.stdout.readline().strip()
+ if not wdt_url:
+ raise Exception("Unable to get connection url from wdt receiver")
+ sender_cmd = (
+ "{0} -connection_url \'{1}\' -directory {2} -app_name=myrocks "
+ "-avg_mbytes_per_sec {3} "
+ "-enable_download_resumption -abort_after_seconds {4} {5}"
+ ).format(wdt_bin,
+ wdt_url,
+ snapshot_dir,
+ opts.avg_mbytes_per_sec,
+ opts.checkpoint_interval,
+ opts.extra_wdt_sender_options)
+ sender_status = os.system(sender_cmd) >> 8
+ remote_status = remote_process.wait()
+ self.cleanup(snapshot_dir, server_log)
+ # TODO: handle retryable and non-retyable errors differently
+ return (sender_status == 0 and remote_status == 0)
+
+ except Exception as e:
+ logger.error(e)
+ logger.error(traceback.format_exc())
+ self.cleanup(snapshot_dir, server_log)
+ sys.exit(1)
+
+
+def backup_using_wdt():
+ if not opts.destination:
+ logger.error("Must provide remote destination when using WDT")
+ sys.exit(1)
+
+ # TODO: detect whether WDT is installed
+ logger.info("Backing up myrocks to %s using WDT", opts.destination)
+ wdt_backup = WDTBackup(opts.datadir)
+ finished = False
+ backup_round = 1
+ while not finished:
+ start_time = time.time()
+ finished = wdt_backup.backup_with_timeout(backup_round)
+ end_time = time.time()
+ duration_seconds = end_time - start_time
+ if (not finished) and (duration_seconds < opts.checkpoint_interval):
+ # round finished before timeout
+ sleep_duration = (opts.checkpoint_interval - duration_seconds)
+ logger.info("Sleeping for %f seconds", sleep_duration)
+ time.sleep(sleep_duration)
+
+ backup_round = backup_round + 1
+ logger.info("Finished myrocks backup using WDT")
+
+
+def init_logger():
+ global logger
+ logger = logging.getLogger('myrocks_hotbackup')
+ logger.setLevel(logging.INFO)
+ h1= logging.StreamHandler(sys.stderr)
+ f = logging.Formatter("%(asctime)s.%(msecs)03d %(levelname)s %(message)s",
+ "%Y-%m-%d %H:%M:%S")
+ h1.setFormatter(f)
+ logger.addHandler(h1)
+
+backup_wdt_usage = ("Backup using WDT: myrocks_hotbackup "
+ "--user=root --password=pw --stream=wdt "
+ "--checkpoint_dir=<directory where temporary backup hard links "
+ "are created> --destination=<remote host name> --backup_dir="
+ "<remote directory name>. This has to be executed at the src "
+ "host.")
+backup_usage= "Backup: set -o pipefail; myrocks_hotbackup --user=root --password=pw --port=3306 --checkpoint_dir=<directory where temporary backup hard links are created> | ssh -o NoneEnabled=yes remote_server 'tar -xi -C <directory on remote server where backups will be sent>' . You need to execute backup command on a server where you take backups."
+move_back_usage= "Move-Back: myrocks_hotbackup --move_back --datadir=<dest mysql datadir> --rocksdb_datadir=<dest rocksdb datadir> --rocksdb_waldir=<dest rocksdb wal dir> --backup_dir=<where backup files are stored> . You need to execute move-back command on a server where backup files are sent."
+
+
+def parse_options():
+ global opts
+ parser = OptionParser(usage = "\n\n" + backup_usage + "\n\n" + \
+ backup_wdt_usage + "\n\n" + move_back_usage)
+ parser.add_option('-i', '--interval', type='int', dest='checkpoint_interval',
+ default=300,
+ help='Number of seconds to renew checkpoint')
+ parser.add_option('-c', '--checkpoint_dir', type='string', dest='checkpoint_directory',
+ default='/data/mysql/backup/snapshot',
+ help='Local directory name where checkpoints will be created.')
+ parser.add_option('-d', '--datadir', type='string', dest='datadir',
+ default=None,
+ help='backup mode: src MySQL datadir. move_back mode: dest MySQL datadir')
+ parser.add_option('-s', '--stream', type='string', dest='output_stream',
+ default='tar',
+ help='Setting streaming backup options. Currently tar, WDT '
+ 'and xbstream are supported. Default is tar')
+ parser.add_option('--destination', type='string', dest='destination',
+ default='',
+ help='Remote server name. Only used for WDT mode so far.')
+ parser.add_option('--avg_mbytes_per_sec', type='int',
+ dest='avg_mbytes_per_sec',
+ default=500,
+ help='Average backup rate in MBytes/sec. WDT only.')
+ parser.add_option('--extra_wdt_sender_options', type='string',
+ dest='extra_wdt_sender_options',
+ default='',
+ help='Extra options for WDT sender')
+ parser.add_option('--extra_wdt_receiver_options', type='string',
+ dest='extra_wdt_receiver_options',
+ default='',
+ help='Extra options for WDT receiver')
+ parser.add_option('-u', '--user', type='string', dest='mysql_user',
+ default='root',
+ help='MySQL user name')
+ parser.add_option('-p', '--password', type='string', dest='mysql_password',
+ default='',
+ help='MySQL password name')
+ parser.add_option('-P', '--port', type='int', dest='mysql_port',
+ default=3306,
+ help='MySQL port number')
+ parser.add_option('-S', '--socket', type='string', dest='mysql_socket',
+ default=None,
+ help='MySQL socket path. Takes precedence over --port.')
+ parser.add_option('-m', '--move_back', action='store_true', dest='move_back',
+ default=False,
+ help='Moving MyRocks backup files to proper locations.')
+ parser.add_option('-r', '--rocksdb_datadir', type='string', dest='rocksdb_datadir',
+ default=None,
+ help='RocksDB target data directory where backup data files will be moved. Must be empty.')
+ parser.add_option('-w', '--rocksdb_waldir', type='string', dest='rocksdb_waldir',
+ default=None,
+ help='RocksDB target data directory where backup wal files will be moved. Must be empty.')
+ parser.add_option('-b', '--backup_dir', type='string', dest='backupdir',
+ default=None,
+ help='backup mode for WDT: Remote directory to store '
+ 'backup. move_back mode: Locations where backup '
+ 'files are stored.')
+ parser.add_option('-f', '--skip_check_frm_timestamp',
+ dest='skip_check_frm_timestamp',
+ action='store_true', default=False,
+ help='skipping to check if frm files are updated after starting backup.')
+ parser.add_option('-D', '--debug_signal_file', type='string', dest='debug_signal_file',
+ default=None,
+ help='debugging purpose: waiting until the specified file is created')
+
+ opts, args = parser.parse_args()
+
+
+def create_moveback_dir(directory):
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ else:
+ for f in os.listdir(directory):
+ logger.error("Directory %s has file or directory %s!", directory, f)
+ raise
+
+def print_move_back_usage():
+ logger.warning(move_back_usage)
+
+def move_back():
+ if opts.rocksdb_datadir is None or opts.rocksdb_waldir is None or opts.backupdir is None or opts.datadir is None:
+ print_move_back_usage()
+ sys.exit()
+ create_moveback_dir(opts.datadir)
+ create_moveback_dir(opts.rocksdb_datadir)
+ create_moveback_dir(opts.rocksdb_waldir)
+
+ os.chdir(opts.backupdir)
+ for f in os.listdir(opts.backupdir):
+ if os.path.isfile(os.path.join(opts.backupdir,f)):
+ if f.endswith(rocksdb_wal_suffix):
+ shutil.move(f, opts.rocksdb_waldir)
+ elif f.endswith(rocksdb_data_suffix) or is_manifest(f):
+ shutil.move(f, opts.rocksdb_datadir)
+ else:
+ shutil.move(f, opts.datadir)
+ else: #directory
+ if f.endswith('.rocksdb'):
+ continue
+ shutil.move(f, opts.datadir)
+
+def start_backup():
+ logger.info("Starting backup.")
+ runner = BackupRunner(opts.datadir)
+ b = None
+ backup_round= 1
+ while True:
+ b = runner.start_backup_round(backup_round, b)
+ backup_round = backup_round + 1
+ if b.finished is True:
+ b.print_backup_report()
+ logger.info("RocksDB Backup Done.")
+ break
+ if opts.debug_signal_file:
+ while not os.path.exists(opts.debug_signal_file):
+ logger.info("Waiting until %s is created..", opts.debug_signal_file)
+ time.sleep(1)
+ runner.backup_mysql()
+ logger.info("All Backups Done.")
+
+
+def main():
+ parse_options()
+ init_logger()
+
+ if opts.move_back is True:
+ move_back()
+ elif opts.output_stream == 'wdt':
+ backup_using_wdt()
+ else:
+ start_backup()
+
+if __name__ == "__main__":
+ main()
diff --git a/storage/rocksdb/mysql-test/rocksdb/combinations b/storage/rocksdb/mysql-test/rocksdb/combinations
new file mode 100644
index 00000000..be8080d4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/combinations
@@ -0,0 +1,5 @@
+[write_committed]
+loose-rocksdb_write_policy=write_committed
+
+[write_prepared]
+loose-rocksdb_write_policy=write_prepared
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/autoinc_crash_safe.inc b/storage/rocksdb/mysql-test/rocksdb/include/autoinc_crash_safe.inc
new file mode 100644
index 00000000..ba2e7ace
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/autoinc_crash_safe.inc
@@ -0,0 +1,150 @@
+--echo #
+--echo # Testing concurrent transactions.
+--echo #
+
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+connect (con3,localhost,root,,);
+
+connection con1;
+begin;
+insert into t values (); # 1
+
+connection con2;
+begin;
+insert into t values (); # 2
+
+connection con3;
+begin;
+insert into t values (); # 3
+
+connection con1;
+insert into t values (); # 4
+
+connection con2;
+insert into t values (); # 5
+
+connection con3;
+insert into t values (); # 6
+
+connection con2;
+commit;
+
+connection con3;
+rollback;
+
+connection con1;
+commit;
+
+delete from t;
+
+--echo # Master value before restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Slave value before restart
+sync_slave_with_master;
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+connection slave;
+--source include/stop_slave.inc
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+
+connection default;
+--echo # Master value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--let $rpl_server_number = 2
+--source include/rpl_restart_server.inc
+
+connection slave;
+--source include/start_slave.inc
+--echo # Slave value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+disconnect con1;
+disconnect con2;
+disconnect con3;
+--source include/wait_until_count_sessions.inc
+
+--echo #
+--echo # Testing interaction of merge markers with various DDL statements.
+--echo #
+connection slave;
+--source include/stop_slave.inc
+
+connection default;
+
+--echo # Drop and add primary key.
+alter table t modify i int;
+alter table t drop primary key;
+alter table t add primary key (i);
+alter table t modify i int auto_increment;
+
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Remove auto_increment property.
+alter table t modify i int;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Add auto_increment property.
+insert into t values (123);
+alter table t modify i int auto_increment;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Add column j.
+alter table t add column j int;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Rename tables.
+rename table t to t2;
+rename table t2 to t;
+
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Change auto_increment property
+alter table t auto_increment = 1000;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+alter table t auto_increment = 1;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+alter table t drop primary key, add key (i), auto_increment = 1;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+alter table t add key (j), auto_increment = 1;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+alter table t modify i int;
+alter table t add column (k int auto_increment), add key(k), auto_increment=15;
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+
+--echo # Drop table.
+drop table t;
+
+--let $rpl_server_number = 1
+--source include/rpl_restart_server.inc
+
+connection slave;
+--source include/start_slave.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/bulk_load.inc b/storage/rocksdb/mysql-test/rocksdb/include/bulk_load.inc
new file mode 100644
index 00000000..3bb766d5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/bulk_load.inc
@@ -0,0 +1,165 @@
+--source include/count_sessions.inc
+
+if ($data_order_desc)
+{
+ --echo Data will be ordered in descending order
+}
+
+if (!$data_order_desc)
+{
+ --echo Data will be ordered in ascending order
+}
+
+# Create a table with a primary key and one secondary key as well as one
+# more column
+eval CREATE TABLE t1(
+ pk CHAR(5),
+ a CHAR(30),
+ b CHAR(30),
+ PRIMARY KEY(pk) COMMENT "$pk_cf",
+ KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+
+# Create a second identical table to validate that bulk loading different
+# tables in the same session works
+eval CREATE TABLE t2(
+ pk CHAR(5),
+ a CHAR(30),
+ b CHAR(30),
+ PRIMARY KEY(pk) COMMENT "$pk_cf",
+ KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+
+# Create a third table using partitions to validate that bulk loading works
+# across a partitioned table
+eval CREATE TABLE t3(
+ pk CHAR(5),
+ a CHAR(30),
+ b CHAR(30),
+ PRIMARY KEY(pk) COMMENT "$pk_cf",
+ KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin' PARTITION BY KEY() PARTITIONS 4;
+
+--let $file = `SELECT CONCAT(@@datadir, "test_loadfile.txt")`
+
+--let MTR_DATA_ORDER_DESC = $data_order_desc;
+
+# Create a text file with data to import into the table.
+# The primary key is in sorted order and the secondary keys are randomly generated
+--let ROCKSDB_INFILE = $file
+perl;
+my $fn = $ENV{'ROCKSDB_INFILE'};
+open(my $fh, '>', $fn) || die "perl open($fn): $!";
+my $max = 2500000;
+my $desc = $ENV{'MTR_DATA_ORDER_DESC'};
+my @chars = ("A".."Z", "a".."z", "0".."9");
+my @powers_of_26 = (26 * 26 * 26 * 26, 26 * 26 * 26, 26 * 26, 26, 1);
+for (my $ii = 0; $ii < $max; $ii++)
+{
+ my $pk;
+ my $tmp = $ii;
+ foreach (@powers_of_26)
+ {
+ if ($desc == 1)
+ {
+ $pk .= chr(ord('z') - int($tmp / $_));
+ }
+ else
+ {
+ $pk .= chr(ord('a') + int($tmp / $_));
+ }
+
+ $tmp = $tmp % $_;
+ }
+
+ my $num = int(rand(25)) + 6;
+ my $a;
+ $a .= $chars[rand(@chars)] for 1..$num;
+
+ $num = int(rand(25)) + 6;
+ my $b;
+ $b .= $chars[rand(@chars)] for 1..$num;
+ print $fh "$pk\t$a\t$b\n";
+}
+close($fh);
+EOF
+
+--file_exists $file
+
+# Make sure a snapshot held by another user doesn't block the bulk load
+connect (other,localhost,root,,);
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+
+# Assert that there is a pending snapshot
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+
+connection default;
+
+# Update CF to smaller value to create multiple SST in ingestion
+eval SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=
+ '$pk_cf_name={write_buffer_size=8m;target_file_size_base=1m};';
+
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+--disable_query_log
+--echo LOAD DATA INFILE <input_file> INTO TABLE t1;
+eval LOAD DATA INFILE '$file' INTO TABLE t1;
+# There should be no SST being ingested
+select * from t1;
+--echo LOAD DATA INFILE <input_file> INTO TABLE t2;
+eval LOAD DATA INFILE '$file' INTO TABLE t2;
+# There should be no SST being ingested
+select * from t2;
+--echo LOAD DATA INFILE <input_file> INTO TABLE t3;
+eval LOAD DATA INFILE '$file' INTO TABLE t3;
+# There should be no SST being ingested
+select * from t3;
+--enable_query_log
+set rocksdb_bulk_load=0;
+
+--remove_file $file
+
+# Make sure row count index stats are correct
+--replace_column 6 # 7 # 8 # 9 # 12 # 13 #
+SHOW TABLE STATUS WHERE name LIKE 't%';
+
+ANALYZE TABLE t1, t2, t3;
+
+--replace_column 6 # 7 # 8 # 9 # 12 # 13 #
+SHOW TABLE STATUS WHERE name LIKE 't%';
+
+# Make sure all the data is there.
+select count(pk),count(a) from t1;
+select count(b) from t1;
+select count(pk),count(a) from t2;
+select count(b) from t2;
+select count(pk),count(a) from t3;
+select count(b) from t3;
+
+# Create a dummy file with a bulk load extesion. It should be removed when
+# the server starts
+--let $tmpext = .bulk_load.tmp
+--let $MYSQLD_DATADIR= `SELECT @@datadir`
+--let $datadir = $MYSQLD_DATADIR/#rocksdb
+--write_file $datadir/test$tmpext
+dummy data
+EOF
+--write_file $datadir/longfilenamethatvalidatesthatthiswillgetdeleted$tmpext
+dummy data
+EOF
+
+# Show the files exists
+--list_files $datadir *$tmpext
+
+# Now restart the server and make sure it automatically removes this test file
+--source include/restart_mysqld.inc
+
+# Show the files do not exist
+--list_files $datadir *$tmpext
+
+# Cleanup
+disconnect other;
+DROP TABLE t1, t2, t3;
+
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/bulk_load_unsorted.inc b/storage/rocksdb/mysql-test/rocksdb/include/bulk_load_unsorted.inc
new file mode 100644
index 00000000..d6bee980
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/bulk_load_unsorted.inc
@@ -0,0 +1,143 @@
+--source include/have_partition.inc
+--source include/count_sessions.inc
+
+SET rocksdb_bulk_load_size=3;
+SET rocksdb_bulk_load_allow_unsorted=1;
+
+### Test individual INSERTs ###
+
+# A table with only a PK won't have rows until the bulk load is finished
+eval CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+--disable_query_log
+let $sign = 1;
+let $max = 5;
+let $i = 1;
+while ($i <= $max) {
+ let $a = 1 + $sign * $i;
+ let $b = 1 - $sign * $i;
+ let $sign = -$sign;
+ let $insert = INSERT INTO t1 VALUES ($a, $b);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+DROP TABLE t1;
+
+# A table with a PK and a SK shows rows immediately
+eval CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf", KEY(b))
+ ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+--disable_query_log
+let $sign = 1;
+let $max = 5;
+let $i = 1;
+while ($i <= $max) {
+ let $a = 1 + $sign * $i;
+ let $b = 1 - $sign * $i;
+ let $sign = -$sign;
+ let $insert = INSERT INTO t1 VALUES ($a, $b);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+DROP TABLE t1;
+
+# Inserting into another table finishes bulk load to the previous table
+eval CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB;
+eval CREATE TABLE t2(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB;
+
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t2 VALUES (1,1);
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+INSERT INTO t1 VALUES (2,2);
+SELECT * FROM t2 FORCE INDEX (PRIMARY);
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+DROP TABLE t1, t2;
+
+### Test bulk load from a file ###
+eval CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB;
+eval CREATE TABLE t2(a INT, b INT, PRIMARY KEY(b) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB;
+eval CREATE TABLE t3(a INT, b INT, PRIMARY KEY(a) COMMENT "$pk_cf")
+ ENGINE=ROCKSDB PARTITION BY KEY() PARTITIONS 4;
+
+--let $file = `SELECT CONCAT(@@datadir, "test_loadfile.txt")`
+# Create a text file with data to import into the table.
+# PK and SK are not in any order
+--let ROCKSDB_INFILE = $file
+perl;
+my $fn = $ENV{'ROCKSDB_INFILE'};
+open(my $fh, '>', $fn) || die "perl open($fn): $!";
+binmode $fh;
+my $max = 2500000;
+my $sign = 1;
+for (my $ii = 0; $ii < $max; $ii++)
+{
+ my $a = 1 + $sign * $ii;
+ my $b = 1 - $sign * $ii;
+ $sign = -$sign;
+ print $fh "$a\t$b\n";
+}
+close($fh);
+EOF
+--file_exists $file
+
+# Make sure a snapshot held by another user doesn't block the bulk load
+connect (other,localhost,root,,);
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+
+# Assert that there is a pending snapshot
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+
+connection default;
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+--disable_query_log
+--echo LOAD DATA INFILE <input_file> INTO TABLE t1;
+eval LOAD DATA INFILE '$file' INTO TABLE t1;
+--echo LOAD DATA INFILE <input_file> INTO TABLE t2;
+eval LOAD DATA INFILE '$file' INTO TABLE t2;
+--echo LOAD DATA INFILE <input_file> INTO TABLE t3;
+eval LOAD DATA INFILE '$file' INTO TABLE t3;
+--enable_query_log
+set rocksdb_bulk_load=0;
+
+--remove_file $file
+
+# Make sure row count index stats are correct
+--replace_column 6 # 7 # 8 # 9 # 12 # 13 #
+SHOW TABLE STATUS WHERE name LIKE 't%';
+
+ANALYZE TABLE t1, t2, t3;
+
+--replace_column 6 # 7 # 8 # 9 # 12 # 13 #
+SHOW TABLE STATUS WHERE name LIKE 't%';
+
+# Make sure all the data is there.
+select count(a),count(b) from t1;
+select count(a),count(b) from t2;
+select count(a),count(b) from t3;
+
+SELECT * FROM t1 FORCE INDEX (PRIMARY) LIMIT 3;
+SELECT * FROM t2 FORCE INDEX (PRIMARY) LIMIT 3;
+
+disconnect other;
+DROP TABLE t1, t2, t3;
+
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/bypass_create_table.inc b/storage/rocksdb/mysql-test/rocksdb/include/bypass_create_table.inc
new file mode 100644
index 00000000..233635b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/bypass_create_table.inc
@@ -0,0 +1,298 @@
+CREATE TABLE `link_table` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+ `time` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ PRIMARY KEY (`link_type` , `id1` , `id2`) COMMENT 'cf_link' ,
+ KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+ `version` , `data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+
+CREATE TABLE `link_table2` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ `visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+ `time` int(10) unsigned NOT NULL DEFAULT '0' ,
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+ PRIMARY KEY (`link_type` , `id1` , `id2`)
+ COMMENT 'cf_link' ,
+ KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+ `version` , `data`) COMMENT 'cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=9;
+
+insert into link_table values (1, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (1, 1, 2, 2, 3, 3, 'a10', 10, 125);
+insert into link_table values (1, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 5, 2, 3, 3, 'a12', 12, 125);
+insert into link_table values (1, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (1, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (1, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 4, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 4, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 4, 4, 'a15', 15, 125);
+insert into link_table values (3, 1, 10, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 9, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 8, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 7, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 4, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 3, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (3, 1, 2, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (3, 1, 1, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (9, 1, 9, 2, 5, 6, '0 ', 10, 125);
+insert into link_table values (9, 1, 8, 2, 5, 6, '01 ', 11, 125);
+insert into link_table values (9, 1, 7, 2, 5, 6, '012 ', 11, 125);
+insert into link_table values (9, 1, 6, 2, 5, 6, '0123 ', 12, 125);
+insert into link_table values (9, 1, 5, 2, 5, 6, '01234 ', 12, 125);
+insert into link_table values (9, 1, 4, 2, 5, 6, '012345 ', 12, 125);
+insert into link_table values (9, 1, 3, 2, 5, 6, '0123456 ', 13, 125);
+insert into link_table values (9, 1, 2, 2, 5, 6, '01234567 ', 14, 125);
+insert into link_table values (9, 1, 1, 2, 5, 6, '012345678 ', 15, 125);
+insert into link_table values (9, 1, 0, 2, 5, 6, '0123456789 ', 15, 125);
+
+insert into link_table2 select * from link_table;
+
+CREATE TABLE `id_table` (
+ `id` bigint(20) NOT NULL DEFAULT '0',
+ `type` int(11) NOT NULL DEFAULT '0',
+ `row_created_time` int(11) NOT NULL DEFAULT '0',
+ `hash_key` varchar(255) NOT NULL DEFAULT '',
+ `is_deleted` tinyint(4) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `type_id` (`type`,`id`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPRESSED
+KEY_BLOCK_SIZE=8;
+
+insert into id_table values (1, 1, 10, '111', 0);
+insert into id_table values (2, 1, 10, '111', 1);
+insert into id_table values (3, 1, 10, '111', 0);
+insert into id_table values (4, 1, 10, '111', 1);
+insert into id_table values (5, 1, 10, '111', 0);
+insert into id_table values (6, 1, 10, '111', 1);
+insert into id_table values (7, 1, 10, '111', 0);
+insert into id_table values (8, 1, 10, '111', 1);
+insert into id_table values (9, 1, 10, '111', 0);
+insert into id_table values (10, 1, 10, '111', 1);
+
+CREATE TABLE `node_table` (
+ `id` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `type` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `update_time` int(10) unsigned NOT NULL DEFAULT '0',
+ `data` mediumtext COLLATE latin1_bin NOT NULL,
+ PRIMARY KEY (`type`,`id`) COMMENT 'cf_node_type_id',
+ KEY `id` (`id`) COMMENT 'cf_node'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+
+insert into node_table values (1, 1, 1, 10, 'data');
+
+insert into node_table values (2, 1, 1, 10, 'data');
+
+insert into node_table values (3, 1, 1, 10, 'data');
+
+insert into node_table values (4, 1, 1, 10, 'data');
+
+insert into node_table values (5, 1, 1, 10, 'data');
+
+insert into node_table values (6, 1, 1, 10, 'data');
+
+insert into node_table values (7, 1, 1, 10, 'data');
+
+insert into node_table values (8, 1, 1, 10, 'data');
+
+insert into node_table values (9, 1, 1, 10, 'data');
+
+insert into node_table values (10, 1, 1, 10, 'data');
+
+CREATE TABLE `count_table` (
+ `id` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `count` int(10) unsigned NOT NULL DEFAULT '0',
+ `time` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`,`link_type`) COMMENT 'cf_count_table'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+
+
+insert into count_table values (2, 1, 1, 1, 10, 20);
+
+insert into count_table values (3, 1, 1, 1, 10, 20);
+
+insert into count_table values (4, 1, 1, 1, 10, 20);
+
+insert into count_table values (5, 1, 1, 1, 10, 20);
+
+insert into count_table values (6, 1, 1, 1, 10, 20);
+
+insert into count_table values (7, 1, 1, 1, 10, 20);
+
+insert into count_table values (8, 1, 1, 1, 10, 20);
+
+insert into count_table values (9, 1, 1, 1, 10, 20);
+
+insert into count_table values (10, 1, 1, 1, 10, 20);
+
+CREATE TABLE `link_table5` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+ `time` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+
+insert into link_table5 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table5 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table5 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table5 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table5 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table5 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table5 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+
+
+CREATE TABLE `link_table3` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(4) NOT NULL DEFAULT '0',
+ `data` text COLLATE latin1_bin NOT NULL,
+ `time` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`)
+ COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+
+insert into link_table3 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table3 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table3 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table3 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table3 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table3 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table3 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+
+CREATE TABLE `link_table6` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(4) NOT NULL DEFAULT '0',
+ `data` text COLLATE latin1_bin NOT NULL,
+ `time` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,
+ `data`(255)) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+
+insert into link_table6 values (1, 1, 2, 2, 1, 1,
+ 'data12_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 3, 2, 1, 2,
+ 'data13_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 4, 2, 1, 2,
+ 'data14_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 5, 2, 1, 1,
+ 'data15_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 1, 2, 1, 1,
+ 'data21_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 2, 2, 1, 1,
+ 'data22_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 3, 2, 1, 1,
+ 'data32_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+
+CREATE TABLE `link_table4` (
+ `id1` binary(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
+ `raw_key` text COLLATE latin1_bin,
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+ `time` int(10) unsigned NOT NULL DEFAULT '0',
+ `version` bigint(20) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,`data`)
+ COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+
+insert into link_table4 values ('a1', "rk1", 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table4 values ('a1', "rk2", 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table4 values ('a1', "rk3", 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table4 values ('a1', "rk4", 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table4 values ('b1', "rk5", 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table4 values ('b1', "rk6", 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table4 values ('b1', "rk7", 3, 2, 1, 1, 'data32', 1, 1);
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority.inc b/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority.inc
new file mode 100644
index 00000000..7adca5d7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority.inc
@@ -0,0 +1,174 @@
+###############################################################################
+# Common test file for high priority DDL
+###############################################################################
+
+
+create user test_user1@localhost;
+grant all on test to test_user1@localhost;
+create user test_user2@localhost;
+grant all on test to test_user2@localhost;
+
+# Default values
+--let $con_block = con1
+--let $con_kill = default
+--let $should_kill = 1
+--let $recreate_table = 1
+--let $throw_error = 1
+
+##
+## killing conflicting shared locks by alter table
+##
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = alter table t1 modify i bigint;
+--let $high_priority_cmd = alter high_priority table t1 modify i bigint;
+
+--source include/ddl_high_priority_module.inc
+
+##
+## killing conflicting shared lock in a transaction
+## transaction will rollback
+##
+
+--let $blocking_sql = begin; insert into t1 values (4); select i from t1;
+--let $cmd = alter table t1 rename t1_new;
+--let $high_priority_cmd = alter high_priority table t1 rename t1_new;
+
+--source include/ddl_high_priority_module.inc
+
+select * from t1_new;
+drop table t1_new;
+
+##
+## simulate conflicting DDL which will not be killed
+##
+
+# Simulate conflicting DDL
+# This will hold MDL_SHARED_NO_READ_WRITE, which may be upgraded to exclusive
+# locks to run DDLs like ALTER TABLE
+# the upgradable/exclusive lock should not be killed
+
+--let $should_kill = 0
+
+--let $blocking_sql = lock tables t1 write;
+--let $cmd = drop table t1;
+--let $high_priority_cmd = drop high_priority table t1;
+
+--source include/ddl_high_priority_module.inc
+
+# restore $should_kill
+--let $should_kill = 1
+
+##
+## killing conflicting transaction by drop table DDL
+##
+
+--let $blocking_sql = lock tables t1 read; begin; insert into t1 values (4);
+--let $cmd = drop table t1;
+--let $high_priority_cmd = drop high_priority table t1;
+
+--source include/ddl_high_priority_module.inc
+
+##
+## no effect for regular users
+##
+
+connect (con2,localhost,test_user2,,test,,);
+# $con_kill is regular user
+--let $con_kill = con2
+--let $should_kill = 0
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = alter table t1 modify i bigint;
+--let $high_priority_cmd = alter high_priority table t1 modify i bigint;
+
+--source include/ddl_high_priority_module.inc
+
+disconnect con2;
+
+# restore $con_kill
+--let $con_kill = default
+# restore $should_kill
+--let $should_kill = 1
+
+##
+## create/drop index
+##
+
+# create index
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = create index idx1 on t1 (i);
+--let $high_priority_cmd = create high_priority index idx1 on t1 (i);
+
+--source include/ddl_high_priority_module.inc
+
+# drop index (use the previously created table)
+--let $recreate_table = 0
+
+--let $cmd = drop index idx1 on t1;
+--let $high_priority_cmd = drop high_priority index idx1 on t1;
+
+--source include/ddl_high_priority_module.inc
+
+# restore $recreate_table
+--let $recreate_table = 1
+
+##
+## high_priority truncate table
+##
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = truncate t1;
+--let $high_priority_cmd = truncate high_priority t1;
+
+--source include/ddl_high_priority_module.inc
+
+##
+## high_priority create/drop trigger
+##
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+--let $high_priority_cmd = create high_priority trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+
+--source include/ddl_high_priority_module.inc
+
+# drop trigger (use the previously created table)
+--let $recreate_table = 0
+
+--let $cmd = drop trigger ins_sum;
+--let $high_priority_cmd = drop high_priority trigger ins_sum;
+
+--source include/ddl_high_priority_module.inc
+
+# restore $recreate_table
+--let $recreate_table = 1
+
+##
+## high_priority optimize table
+##
+## "optimize table" doesn't throw errors. It catches all errors, and
+## returns a result set in a table
+##
+
+--let $throw_error = 0
+
+--let $blocking_sql = lock tables t1 read;
+--let $cmd = optimize table t1;
+--let $high_priority_cmd = optimize high_priority table t1;
+
+--source include/ddl_high_priority_module.inc
+
+# restore throw_error
+--let $throw_error = 1
+
+##
+## clean up
+##
+
+drop user test_user1@localhost;
+drop user test_user2@localhost;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority_module.inc b/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority_module.inc
new file mode 100644
index 00000000..ffbdc306
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/ddl_high_priority_module.inc
@@ -0,0 +1,141 @@
+###############################################################################
+# This file plays as a function/module for ddl_high_priority test
+#
+# Usage: set the following variables before including
+#
+# $use_sys_var: whether using sys_var or syntax to trigger high_priority
+# value: 0/1
+#
+# $con_block: a blocking connection
+# value: con1/con2/default
+#
+# $con_kill: a connection that will attempt to kill $con_blocking
+# value: con1/con2/default
+#
+# $cmd: a regular command to evaluate (to use with sys var)
+# value: sql command
+#
+# $high_priority_cmd: a high_priority command to evaluate
+# value: sql command
+#
+# $should_kill: Expect the con_block to be killed or not
+# value: 0/1
+#
+# $recreate_table: Should recreate the test table or not
+# value: 0/1
+#
+# $throw_error: whether a command will throw lock_wait_timeout error.
+# Note, optimize table catches all errors.
+# value: 0/1
+###############################################################################
+
+##
+## Print out the parameters of the test set
+## (useful for debugging)
+##
+--echo
+--echo ## Test parameters:
+--echo ## use_sys_var = $use_sys_var
+--echo ## con_block = $con_block
+--echo ## con_kill = $con_kill
+--echo ## cmd = $cmd
+--echo ## high_priority_cmd = $high_priority_cmd
+--echo ## should_kill = $should_kill
+--echo ## recreate_table = $recreate_table
+--echo ## throw_error = $throw_error
+--echo
+
+
+##
+## Setup
+##
+
+connection default;
+
+# create con1
+connect (con1,localhost,test_user1,,test,,);
+
+if ($recreate_table) {
+ # create t1
+ --disable_warnings
+ drop table if exists t1;
+ --enable_warnings
+ create table t1 (i int);
+ show create table t1;
+ insert into t1 values (1), (2), (3);
+}
+
+##
+## Testing
+##
+
+--echo connection: $con_block
+--connection $con_block
+--eval $blocking_sql
+
+--echo connection: $con_kill
+--connection $con_kill
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+
+describe t1;
+
+--echo connection: default (for show processlist)
+connection default;
+--echo # both $con_block and $con_kill exist
+--replace_column 1 <Id> 3 <Host> 5 <Command> 6 <Time> 7 <State> 8 <Info> 9 <RExam> 10 <RSent> 11 <TID>
+show processlist;
+
+--echo connection: $con_kill
+--connection $con_kill
+
+# command will fail without high_priority
+if ($throw_error) {
+ --error ER_LOCK_WAIT_TIMEOUT
+ --eval $cmd
+}
+
+if (!$throw_error) {
+ --eval $cmd
+}
+
+if ($use_sys_var) {
+ set high_priority_ddl = 1;
+ select @@high_priority_ddl;
+
+ # non-supported command will timeout
+ --error ER_LOCK_WAIT_TIMEOUT
+ lock tables t1 write;
+
+ if (!$should_kill) {
+ # regular user ddl will fail regardless of high_priority_ddl being on
+ --error ER_LOCK_WAIT_TIMEOUT
+ --eval $cmd
+ }
+
+ if ($should_kill) {
+ --eval $cmd
+ }
+
+ # reset high_priority_ddl
+ set high_priority_ddl = 0;
+}
+
+if (!$use_sys_var) {
+ if (!$should_kill) {
+ # regular user ddl will fail regardless of high_priority being on
+ --error ER_LOCK_WAIT_TIMEOUT
+ --eval $high_priority_cmd
+ }
+
+ if ($should_kill) {
+ --eval $high_priority_cmd
+ }
+}
+
+--echo connection: default (for show processlist)
+connection default;
+--replace_column 1 <Id> 3 <Host> 5 <Command> 6 <Time> 7 <State> 8 <Info> 9 <RExam> 10 <RSent> 11 <TID>
+show processlist;
+
+disconnect con1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/deadlock_stats.inc b/storage/rocksdb/mysql-test/rocksdb/include/deadlock_stats.inc
new file mode 100644
index 00000000..48ef6f81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/deadlock_stats.inc
@@ -0,0 +1,52 @@
+let $prior_set_lwt = `select concat('set @prior_lock_wait_timeout = @@',
+ '$engine', '_lock_wait_timeout;')`;
+let $prior_set_dld = `select concat('set @prior_deadlock_detect = @@',
+ '$engine', '_deadlock_detect;')`;
+let $global_dld = `select concat('set global ', '$engine',
+ '_deadlock_detect = on;')`;
+let $global_lwt = `select concat('set global ', '$engine',
+ '_lock_wait_timeout = 100000;')`;
+eval $prior_set_lwt $prior_set_dld $global_dld $global_lwt;
+
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+let $con1= `SELECT CONNECTION_ID()`;
+
+connect (con2,localhost,root,,);
+let $con2= `SELECT CONNECTION_ID()`;
+
+connection default;
+eval create table t (i int primary key) engine=$engine;
+insert into t values (1), (2), (3);
+
+--echo #
+--echo # The following is disabled due:
+--echo # MDEV-13404: MyRocks upstream uses I_S.table_statistics.row_lock_deadlocks, should we import?
+--echo #
+--disable_parsing
+--source include/simple_deadlock.inc
+connection default;
+select row_lock_deadlocks from information_schema.table_statistics where
+table_name = "t";
+
+select row_lock_deadlocks from information_schema.table_statistics where
+table_name = "t";
+--source include/simple_deadlock.inc
+connection default;
+select row_lock_deadlocks from information_schema.table_statistics where
+table_name = "t";
+
+select row_lock_deadlocks from information_schema.table_statistics where
+table_name = "t";
+--enable_parsing
+
+disconnect con1;
+disconnect con2;
+
+let $restore_lwt = `select concat('set global ', '$engine',
+ '_lock_wait_timeout = @prior_lock_wait_timeout;')`;
+let $restore_dld = `select concat('set global ', '$engine',
+ '_deadlock_detect = @prior_deadlock_detect;')`;
+eval $restore_lwt $restore_dld;
+drop table t;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/dup_key_update.inc b/storage/rocksdb/mysql-test/rocksdb/include/dup_key_update.inc
new file mode 100644
index 00000000..82ceda19
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/dup_key_update.inc
@@ -0,0 +1,69 @@
+let $max_table = 2;
+
+--disable_query_log
+let $table = 1;
+while ($table <= $max_table) {
+ let $max = 9;
+ let $i = 2;
+ while ($i <= $max) {
+ let $insert = INSERT INTO t$table VALUES ($i, $i, $i);
+ eval $insert;
+ inc $i;
+ }
+ inc $table;
+}
+--enable_query_log
+
+let $table = 1;
+while ($table <= $max_table) {
+ let $i = 1;
+ let $j = 9;
+ while ($i <= $max) {
+
+ let $insert = INSERT INTO t$table VALUES ($i, $i, $i) ON DUPLICATE KEY UPDATE id2 = $j;
+ eval $insert;
+
+ let $select = SELECT * FROM t$table WHERE id1 = $i;
+ eval $select;
+
+ let $select = SELECT * FROM t$table FORCE INDEX (id3) WHERE id3 = $i;
+ eval $select;
+
+ inc $j;
+
+ let $insert = INSERT INTO t$table VALUES ($i, $i, $i) ON DUPLICATE KEY UPDATE id2 = $j;
+ eval $insert;
+
+ let $select = SELECT * FROM t$table WHERE id1 = $i;
+ eval $select;
+
+ let $select = SELECT * FROM t$table FORCE INDEX (id3) WHERE id3 = $i;
+ eval $select;
+
+ inc $j;
+
+ let $insert = INSERT INTO t$table VALUES ($i, $i, $i) ON DUPLICATE KEY UPDATE id2 = $j;
+ eval $insert;
+
+ let $select = SELECT * FROM t$table WHERE id1 = $i;
+ eval $select;
+
+ let $select = SELECT * FROM t$table FORCE INDEX (id3) WHERE id3 = $i;
+ eval $select;
+
+ inc $j;
+
+ inc $i;
+ inc $i;
+ inc $i;
+ inc $i;
+ }
+
+ let $select = SELECT * FROM t$table;
+ eval $select;
+
+ let $select = SELECT * FROM t$table FORCE INDEX (id3);
+ eval $select;
+
+ inc $table;
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/group_min_max.inc b/storage/rocksdb/mysql-test/rocksdb/include/group_min_max.inc
new file mode 100644
index 00000000..40fabce0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/group_min_max.inc
@@ -0,0 +1,1438 @@
+#
+# Test file for WL#1724 (Min/Max Optimization for Queries with Group By Clause).
+# The queries in this file test query execution via QUICK_GROUP_MIN_MAX_SELECT.
+#
+
+#
+# TODO:
+# Add queries with:
+# - C != const
+# - C IS NOT NULL
+# - HAVING clause
+
+--source include/default_optimizer_switch.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+eval create table t1 (
+ a1 char(64), a2 char(64), b char(16), c char(16) not null, d char(16), dummy char(248) default ' '
+) engine=$engine;
+
+insert into t1 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'),
+('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'),
+('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'),
+('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'),
+('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'),
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'),
+('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'),
+('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'),
+('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'),
+('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4');
+
+create index idx_t1_0 on t1 (a1);
+create index idx_t1_1 on t1 (a1,a2,b,c);
+create index idx_t1_2 on t1 (a1,a2,b);
+analyze table t1;
+
+# t2 is the same as t1, but with some NULLs in the MIN/MAX column, and
+# one more nullable attribute
+
+--disable_warnings
+drop table if exists t2;
+--enable_warnings
+
+eval create table t2 (
+ a1 char(64), a2 char(64) not null, b char(16), c char(16), d char(16), dummy char(248) default ' '
+) engine=$engine;
+insert into t2 select * from t1;
+# add few rows with NULL's in the MIN/MAX column
+insert into t2 (a1, a2, b, c, d) values
+('a','a',NULL,'a777','xyz'),('a','a',NULL,'a888','xyz'),('a','a',NULL,'a999','xyz'),
+('a','a','a',NULL,'xyz'),
+('a','a','b',NULL,'xyz'),
+('a','b','a',NULL,'xyz'),
+('c','a',NULL,'c777','xyz'),('c','a',NULL,'c888','xyz'),('c','a',NULL,'c999','xyz'),
+('d','b','b',NULL,'xyz'),
+('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),
+('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),
+('a','a',NULL,'a777','xyz'),('a','a',NULL,'a888','xyz'),('a','a',NULL,'a999','xyz'),
+('a','a','a',NULL,'xyz'),
+('a','a','b',NULL,'xyz'),
+('a','b','a',NULL,'xyz'),
+('c','a',NULL,'c777','xyz'),('c','a',NULL,'c888','xyz'),('c','a',NULL,'c999','xyz'),
+('d','b','b',NULL,'xyz'),
+('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),
+('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz');
+
+create index idx_t2_0 on t2 (a1);
+create index idx_t2_1 on t2 (a1,a2,b,c);
+create index idx_t2_2 on t2 (a1,a2,b);
+analyze table t2;
+
+# Table t3 is the same as t1, but with smaller column lenghts.
+# This allows to test different branches of the cost computation procedure
+# when the number of keys per block are less than the number of keys in the
+# sub-groups formed by predicates over non-group attributes.
+
+--disable_warnings
+drop table if exists t3;
+--enable_warnings
+
+eval create table t3 (
+ a1 char(1), a2 char(1), b char(1), c char(4) not null, d char(3), dummy char(1) default ' '
+) engine=$engine;
+
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+
+create index idx_t3_0 on t3 (a1);
+create index idx_t3_1 on t3 (a1,a2,b,c);
+create index idx_t3_2 on t3 (a1,a2,b);
+analyze table t3;
+
+
+#
+# Queries without a WHERE clause. These queries do not use ranges.
+#
+
+# plans
+explain select a1, min(a2) from t1 group by a1;
+explain select a1, max(a2) from t1 group by a1;
+explain select a1, min(a2), max(a2) from t1 group by a1;
+explain select a1, a2, b, min(c), max(c) from t1 group by a1,a2,b;
+explain select a1,a2,b,max(c),min(c) from t1 group by a1,a2,b;
+--replace_column 7 # 9 #
+explain select a1,a2,b,max(c),min(c) from t2 group by a1,a2,b;
+# Select fields in different order
+explain select min(a2), a1, max(a2), min(a2), a1 from t1 group by a1;
+explain select a1, b, min(c), a1, max(c), b, a2, max(c), max(c) from t1 group by a1, a2, b;
+explain select min(a2) from t1 group by a1;
+explain select a2, min(c), max(c) from t1 group by a1,a2,b;
+
+# queries
+select a1, min(a2) from t1 group by a1;
+select a1, max(a2) from t1 group by a1;
+select a1, min(a2), max(a2) from t1 group by a1;
+select a1, a2, b, min(c), max(c) from t1 group by a1,a2,b;
+select a1,a2,b,max(c),min(c) from t1 group by a1,a2,b;
+select a1,a2,b,max(c),min(c) from t2 group by a1,a2,b;
+# Select fields in different order
+select min(a2), a1, max(a2), min(a2), a1 from t1 group by a1;
+select a1, b, min(c), a1, max(c), b, a2, max(c), max(c) from t1 group by a1, a2, b;
+select min(a2) from t1 group by a1;
+select a2, min(c), max(c) from t1 group by a1,a2,b;
+
+#
+# Queries with a where clause
+#
+
+# A) Preds only over the group 'A' attributes
+# plans
+explain select a1,a2,b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+explain select a1, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+explain select a1,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+explain select a1, max(c) from t1 where a1 in ('a','b','d') group by a1,a2,b;
+
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where a1 < 'd' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where a1 < 'd' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+--replace_column 9 #
+explain select a1, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+--replace_column 9 #
+explain select a1, max(c) from t2 where a1 in ('a','b','d') group by a1,a2,b;
+
+# queries
+select a1,a2,b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+select a1, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+select a1,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+select a1, max(c) from t1 where a1 in ('a','b','d') group by a1,a2,b;
+
+select a1,a2,b, max(c) from t2 where a1 < 'd' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where a1 < 'd' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+select a1, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+select a1,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+select a1, max(c) from t2 where a1 in ('a','b','d') group by a1,a2,b;
+
+# B) Equalities only over the non-group 'B' attributes
+# plans
+--echo #
+--echo # MariaDB: we dont have the following patch:
+--echo #
+--echo # commit 60a92a79a3b7fde3c6efe91799e344b977c8e5c3
+--echo # Author: Manuel Ung <mung@fb.com>
+--echo # Date: Thu Apr 19 23:06:27 2018 -0700
+--echo #
+--echo # Enhance group-by loose index scan
+--echo #
+--echo # So the following results are not very meaningful, but are still kept here
+
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b' or b = 'a') group by a1;
+
+explain select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+explain select a1,a2,b, max(c) from t1 where (b = 'b') group by a1,a2;
+explain select a1,a2,b, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+explain select a1,a2,b,min(c),max(c) from t1 where (b = 'b') group by a1,a2;
+explain select a1,a2,b,min(c),max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+explain select a1,a2, max(c) from t1 where (b = 'b') group by a1,a2;
+explain select a1,a2, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+explain select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+explain select a1,a2,b, max(c) from t2 where (b = 'b') group by a1,a2;
+explain select a1,a2,b, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+explain select a1,a2,b,min(c),max(c) from t2 where (b = 'b') group by a1,a2;
+explain select a1,a2,b,min(c),max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+explain select a1,a2, max(c) from t2 where (b = 'b') group by a1,a2;
+explain select a1,a2, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+# these queries test case 2) in TRP_GROUP_MIN_MAX::update_cost()
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+explain select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+explain select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+# queries
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b' or b = 'a') group by a1;
+
+select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+select a1,a2,b, max(c) from t1 where (b = 'b') group by a1,a2;
+select a1,a2,b, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+select a1,a2,b,min(c),max(c) from t1 where (b = 'b') group by a1,a2;
+select a1,a2,b,min(c),max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+select a1,a2, max(c) from t1 where (b = 'b') group by a1,a2;
+select a1,a2, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+select a1,a2,b, max(c) from t2 where (b = 'b') group by a1,a2;
+select a1,a2,b, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+select a1,a2,b,min(c),max(c) from t2 where (b = 'b') group by a1,a2;
+select a1,a2,b,min(c),max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+select a1,a2, max(c) from t2 where (b = 'b') group by a1,a2;
+select a1,a2, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+
+# these queries test case 2) in TRP_GROUP_MIN_MAX::update_cost()
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+
+
+# IS NULL (makes sense for t2 only)
+# plans
+explain select a1,a2,b,min(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+explain select a1,a2,b,min(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+
+explain select a1,a2,b,max(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+explain select a1,a2,b,max(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+
+explain select a1,a2,b,min(c) from t2 where b is NULL group by a1,a2;
+explain select a1,a2,b,max(c) from t2 where b is NULL group by a1,a2;
+explain select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+
+# queries
+select a1,a2,b,min(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+select a1,a2,b,min(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+select a1,a2,b,max(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+select a1,a2,b,max(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+select a1,a2,b,min(c) from t2 where b is NULL group by a1,a2;
+select a1,a2,b,max(c) from t2 where b is NULL group by a1,a2;
+select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+
+# C) Range predicates for the MIN/MAX attribute
+# plans
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t1 where (c > 'b1') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (c > 'f123') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'f123') group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (c < 'a0') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (c < 'k321') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'k321') group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+explain select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (c between 'b111' and 'g112') or (c between 'd000' and 'i110') group by a1,a2,b;
+
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c > 'b1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c > 'f123') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'f123') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c < 'a0') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c < 'k321') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'k321') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+
+# queries
+select a1,a2,b, max(c) from t1 where (c > 'b1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (c > 'f123') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c > 'f123') group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (c < 'a0') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (c < 'k321') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c < 'k321') group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (c between 'b111' and 'g112') or (c between 'd000' and 'i110') group by a1,a2,b;
+
+select a1,a2,b, max(c) from t2 where (c > 'b1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (c > 'f123') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c > 'f123') group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (c < 'a0') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (c < 'k321') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c < 'k321') group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+
+# analyze the sub-select
+explain select a1,a2,b,min(c),max(c) from t1
+where exists ( select * from t2 where t2.c = t1.c )
+group by a1,a2,b;
+
+# the sub-select is unrelated to MIN/MAX
+explain select a1,a2,b,min(c),max(c) from t1
+where exists ( select * from t2 where t2.c > 'b1' )
+group by a1,a2,b;
+
+
+# A,B,C) Predicates referencing mixed classes of attributes
+# plans
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+explain select a1,a2,b,min(c),max(c) from t1 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+explain select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+explain select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+explain select a1,a2,b,min(c) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+explain select a1,a2,b,min(c) from t1 where (ord(a1) > 97) and (ord(a2) + ord(a1) > 194) and (b = 'c') group by a1,a2,b;
+
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c),max(c) from t2 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,min(c) from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+# queries
+select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t1 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+select a1,a2,b,min(c) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+select a1,a2,b,min(c) from t1 where (ord(a1) > 97) and (ord(a2) + ord(a1) > 194) and (b = 'c') group by a1,a2,b;
+
+select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+select a1,a2,b,min(c),max(c) from t2 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+select a1,a2,b,min(c) from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+
+#
+# GROUP BY queries without MIN/MAX
+#
+
+# plans
+explain select a1,a2,b from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+explain select a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+explain select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+explain select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+explain select a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+--replace_column 9 #
+explain select a1,a2,b from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+--replace_column 9 #
+explain select a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+# queries
+select a1,a2,b from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+select a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+select a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+select a1,a2,b from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+select a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+select a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+
+#
+# DISTINCT queries
+#
+
+# plans
+explain select distinct a1,a2,b from t1;
+explain select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a');
+explain extended select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+explain select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+explain select distinct b from t1 where (a2 >= 'b') and (b = 'a');
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b';
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e';
+
+--replace_column 9 #
+explain select distinct a1,a2,b from t2;
+--replace_column 9 #
+explain select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a');
+explain extended select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+--replace_column 9 #
+explain select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+explain select distinct b from t2 where (a2 >= 'b') and (b = 'a');
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b';
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e';
+
+# queries
+select distinct a1,a2,b from t1;
+select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a');
+select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+select distinct b from t1 where (a2 >= 'b') and (b = 'a');
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b';
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e';
+
+select distinct a1,a2,b from t2;
+select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a');
+select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+select distinct b from t2 where (a2 >= 'b') and (b = 'a');
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b';
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e';
+
+# BUG #6303
+select distinct t_00.a1
+from t1 t_00
+where exists ( select * from t2 where a1 = t_00.a1 );
+
+# BUG #8532 - SELECT DISTINCT a, a causes server to crash
+select distinct a1,a1 from t1;
+select distinct a2,a1,a2,a1 from t1;
+select distinct t1.a1,t2.a1 from t1,t2;
+
+
+#
+# DISTINCT queries with GROUP-BY
+#
+
+# plans
+explain select distinct a1,a2,b from t1;
+explain select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+explain select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+explain select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+explain select distinct b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+
+--replace_column 9 #
+explain select distinct a1,a2,b from t2;
+--replace_column 9 #
+explain select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+--replace_column 9 #
+explain select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+--replace_column 9 #
+explain select distinct b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+--replace_column 9 #
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+--replace_column 9 #
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+
+# queries
+select distinct a1,a2,b from t1;
+select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+select distinct b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+
+select distinct a1,a2,b from t2;
+select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+select distinct b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+
+
+#
+# COUNT (DISTINCT cols) queries
+#
+
+explain select count(distinct a1,a2,b) from t1 where (a2 >= 'b') and (b = 'a');
+explain select count(distinct a1,a2,b,c) from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+explain extended select count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+explain select count(distinct b) from t1 where (a2 >= 'b') and (b = 'a');
+explain extended select 98 + count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a');
+
+select count(distinct a1,a2,b) from t1 where (a2 >= 'b') and (b = 'a');
+select count(distinct a1,a2,b,c) from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+select count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+select count(distinct b) from t1 where (a2 >= 'b') and (b = 'a');
+select 98 + count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a');
+
+#
+# Queries with expressions in the select clause
+#
+
+explain select a1,a2,b, concat(min(c), max(c)) from t1 where a1 < 'd' group by a1,a2,b;
+explain select concat(a1,min(c)),b from t1 where a1 < 'd' group by a1,a2,b;
+explain select concat(a1,min(c)),b,max(c) from t1 where a1 < 'd' group by a1,a2,b;
+explain select concat(a1,a2),b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+explain select concat(ord(min(b)),ord(max(b))),min(b),max(b) from t1 group by a1,a2;
+
+select a1,a2,b, concat(min(c), max(c)) from t1 where a1 < 'd' group by a1,a2,b;
+select concat(a1,min(c)),b from t1 where a1 < 'd' group by a1,a2,b;
+select concat(a1,min(c)),b,max(c) from t1 where a1 < 'd' group by a1,a2,b;
+select concat(a1,a2),b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+select concat(ord(min(b)),ord(max(b))),min(b),max(b) from t1 group by a1,a2;
+
+
+#
+# Negative examples: queries that should NOT be treated as optimizable by
+# QUICK_GROUP_MIN_MAX_SELECT
+#
+
+# select a non-indexed attribute
+explain select a1,a2,b,d,min(c),max(c) from t1 group by a1,a2,b;
+
+explain select a1,a2,b,d from t1 group by a1,a2,b;
+
+# predicate that references an attribute that is after the MIN/MAX argument
+# in the index
+explain extended select a1,a2,min(b),max(b) from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (c > 'a111') group by a1,a2;
+
+# predicate that references a non-indexed attribute
+explain extended select a1,a2,b,min(c),max(c) from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (d > 'xy2') group by a1,a2,b;
+
+explain extended select a1,a2,b,c from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (d > 'xy2') group by a1,a2,b,c;
+
+# non-equality predicate for a non-group select attribute
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') or (b < 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b < 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b <= 'b') group by a1;
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b <= 'b' and b >= 'a') group by a1;
+explain extended select a1,a2,b from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (c > 'a111') group by a1,a2,b;
+
+# non-group field with an equality predicate that references a keypart after the
+# MIN/MAX argument
+explain select a1,a2,min(b),c from t2 where (a2 = 'a') and (c = 'a111') group by a1;
+select a1,a2,min(b),c from t2 where (a2 = 'a') and (c = 'a111') group by a1;
+
+# disjunction for a non-group select attribute
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') or (b = 'a') group by a1;
+
+# non-range predicate for the MIN/MAX attribute
+explain select a1,a2,b,min(c),max(c) from t2
+where (c > 'a000') and (c <= 'd999') and (c like '_8__') group by a1,a2,b;
+
+# not all attributes are indexed by one index
+explain select a1, a2, b, c, min(d), max(d) from t1 group by a1,a2,b,c;
+
+# other aggregate functions than MIN/MAX
+explain select a1,a2,count(a2) from t1 group by a1,a2,b;
+explain extended select a1,a2,count(a2) from t1 where (a1 > 'a') group by a1,a2,b;
+explain extended select sum(ord(a1)) from t1 where (a1 > 'a') group by a1,a2,b;
+
+# test multi_range_groupby flag
+#MariaDB: no support: set optimizer_switch = 'multi_range_groupby=off';
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'a' or b = 'b') group by a1;
+#set optimizer_switch = 'default';
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'a' or b = 'b') group by a1;
+
+
+#
+# Bug #16710: select distinct doesn't return all it should
+#
+
+explain select distinct(a1) from t1 where ord(a2) = 98;
+select distinct(a1) from t1 where ord(a2) = 98;
+
+#
+# BUG#11044: DISTINCT or GROUP BY queries with equality predicates instead of MIN/MAX.
+#
+
+explain select a1 from t1 where a2 = 'b' group by a1;
+select a1 from t1 where a2 = 'b' group by a1;
+
+explain select distinct a1 from t1 where a2 = 'b';
+select distinct a1 from t1 where a2 = 'b';
+
+#
+# Bug #12672: primary key implcitly included in every innodb index
+#
+# Test case moved to group_min_max_innodb
+
+
+#
+# Bug #6142: a problem with the empty innodb table
+#
+# Test case moved to group_min_max_innodb
+
+
+#
+# Bug #9798: group by with rollup
+#
+# Test case moved to group_min_max_innodb
+
+
+#
+# Bug #13293 Wrongly used index results in endless loop.
+#
+# Test case moved to group_min_max_innodb
+
+
+drop table t1,t2,t3;
+
+#
+# Bug #14920 Ordering aggregated result sets with composite primary keys
+# corrupts resultset
+#
+eval create table t1 (c1 int not null,c2 int not null, primary key(c1,c2)) engine=$engine;
+insert into t1 (c1,c2) values
+(10,1),(10,2),(10,3),(20,4),(20,5),(20,6),(30,7),(30,8),(30,9);
+select distinct c1, c2 from t1 order by c2;
+select c1,min(c2) as c2 from t1 group by c1 order by c2;
+select c1,c2 from t1 group by c1,c2 order by c2;
+drop table t1;
+
+#
+# Bug #16203: Analysis for possible min/max optimization erroneously
+# returns impossible range
+#
+
+eval CREATE TABLE t1 (a varchar(5), b int(11), PRIMARY KEY (a,b)) engine=$engine;
+INSERT INTO t1 VALUES ('AA',1), ('AA',2), ('AA',3), ('BB',1), ('AA',4);
+OPTIMIZE TABLE t1;
+
+SELECT a FROM t1 WHERE a='AA' GROUP BY a;
+SELECT a FROM t1 WHERE a='BB' GROUP BY a;
+
+EXPLAIN SELECT a FROM t1 WHERE a='AA' GROUP BY a;
+EXPLAIN SELECT a FROM t1 WHERE a='BB' GROUP BY a;
+
+SELECT DISTINCT a FROM t1 WHERE a='BB';
+SELECT DISTINCT a FROM t1 WHERE a LIKE 'B%';
+SELECT a FROM t1 WHERE a LIKE 'B%' GROUP BY a;
+
+DROP TABLE t1;
+
+
+#
+# Bug #15102: select distinct returns empty result, select count
+# distinct > 0 (correct)
+#
+
+CREATE TABLE t1 (
+ a int(11) NOT NULL DEFAULT '0',
+ b varchar(16) COLLATE latin1_general_ci NOT NULL DEFAULT '',
+ PRIMARY KEY (a,b)
+ ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
+
+delimiter |;
+
+CREATE PROCEDURE a(x INT)
+BEGIN
+ DECLARE rnd INT;
+ DECLARE cnt INT;
+
+ WHILE x > 0 DO
+ SET rnd= x % 100;
+ SET cnt = (SELECT COUNT(*) FROM t1 WHERE a = rnd);
+ INSERT INTO t1(a,b) VALUES (rnd, CAST(cnt AS CHAR));
+ SET x= x - 1;
+ END WHILE;
+END|
+
+DELIMITER ;|
+
+CALL a(1000);
+
+SELECT a FROM t1 WHERE a=0;
+SELECT DISTINCT a FROM t1 WHERE a=0;
+SELECT COUNT(DISTINCT a) FROM t1 WHERE a=0;
+
+DROP TABLE t1;
+DROP PROCEDURE a;
+
+#
+# Bug #18068: SELECT DISTINCT
+#
+
+eval CREATE TABLE t1 (a varchar(64) NOT NULL default '', PRIMARY KEY(a)) engine=$engine;
+
+INSERT INTO t1 (a) VALUES
+ (''), ('CENTRAL'), ('EASTERN'), ('GREATER LONDON'),
+ ('NORTH CENTRAL'), ('NORTH EAST'), ('NORTH WEST'), ('SCOTLAND'),
+ ('SOUTH EAST'), ('SOUTH WEST'), ('WESTERN');
+
+EXPLAIN SELECT DISTINCT a,a FROM t1 ORDER BY a;
+SELECT DISTINCT a,a FROM t1 ORDER BY a;
+
+DROP TABLE t1;
+
+#
+# Bug #21007: NATURAL JOIN (any JOIN (2 x NATURAL JOIN)) crashes the server
+#
+
+eval CREATE TABLE t1 (id1 INT, id2 INT) engine=$engine;
+eval CREATE TABLE t2 (id2 INT, id3 INT, id5 INT) engine=$engine;
+eval CREATE TABLE t3 (id3 INT, id4 INT) engine=$engine;
+eval CREATE TABLE t4 (id4 INT) engine=$engine;
+eval CREATE TABLE t5 (id5 INT, id6 INT) engine=$engine;
+eval CREATE TABLE t6 (id6 INT) engine=$engine;
+
+INSERT INTO t1 VALUES(1,1);
+INSERT INTO t2 VALUES(1,1,1);
+INSERT INTO t3 VALUES(1,1);
+INSERT INTO t4 VALUES(1);
+INSERT INTO t5 VALUES(1,1);
+INSERT INTO t6 VALUES(1);
+
+# original bug query
+SELECT * FROM
+t1
+ NATURAL JOIN
+(t2 JOIN (t3 NATURAL JOIN t4, t5 NATURAL JOIN t6)
+ ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5));
+
+# inner join swapped
+SELECT * FROM
+t1
+ NATURAL JOIN
+(((t3 NATURAL JOIN t4) join (t5 NATURAL JOIN t6) on t3.id4 = t5.id5) JOIN t2
+ ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5));
+
+# one join less, no ON cond
+SELECT * FROM t1 NATURAL JOIN ((t3 join (t5 NATURAL JOIN t6)) JOIN t2);
+
+# wrong error message: 'id2' - ambiguous column
+SELECT * FROM
+(t2 JOIN (t3 NATURAL JOIN t4, t5 NATURAL JOIN t6)
+ ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5))
+ NATURAL JOIN
+t1;
+SELECT * FROM
+(t2 JOIN ((t3 NATURAL JOIN t4) join (t5 NATURAL JOIN t6)))
+ NATURAL JOIN
+t1;
+
+DROP TABLE t1,t2,t3,t4,t5,t6;
+
+#
+# Bug#22342: No results returned for query using max and group by
+#
+eval CREATE TABLE t1 (a int, b int, PRIMARY KEY (a,b), KEY b (b)) engine=$engine;
+INSERT INTO t1 VALUES (1,1),(1,2),(1,0),(1,3);
+ANALYZE TABLE t1;
+
+explain SELECT MAX(b), a FROM t1 WHERE b < 2 AND a = 1 GROUP BY a;
+SELECT MAX(b), a FROM t1 WHERE b < 2 AND a = 1 GROUP BY a;
+SELECT MIN(b), a FROM t1 WHERE b > 1 AND a = 1 GROUP BY a;
+eval CREATE TABLE t2 (a int, b int, c int, PRIMARY KEY (a,b,c)) engine=$engine;
+INSERT INTO t2 SELECT a,b,b FROM t1;
+ANALYZE TABLE t2;
+explain SELECT MIN(c) FROM t2 WHERE b = 2 and a = 1 and c > 1 GROUP BY a;
+SELECT MIN(c) FROM t2 WHERE b = 2 and a = 1 and c > 1 GROUP BY a;
+
+DROP TABLE t1,t2;
+
+#
+# Bug#24156: Loose index scan not used with CREATE TABLE ...SELECT and similar statements
+#
+
+eval CREATE TABLE t1 (a INT, b INT, INDEX (a,b)) engine=$engine;
+INSERT INTO t1 (a, b) VALUES (1,1), (1,2), (1,3), (1,4), (1,5),
+ (2,2), (2,3), (2,1), (3,1), (4,1), (4,2), (4,3), (4,4), (4,5), (4,6);
+ANALYZE TABLE t1;
+EXPLAIN SELECT max(b), a FROM t1 GROUP BY a;
+FLUSH STATUS;
+SELECT max(b), a FROM t1 GROUP BY a;
+SHOW STATUS LIKE 'handler_read__e%';
+EXPLAIN SELECT max(b), a FROM t1 GROUP BY a;
+FLUSH STATUS;
+eval CREATE TABLE t2 engine=$engine SELECT max(b), a FROM t1 GROUP BY a;
+SHOW STATUS LIKE 'handler_read__e%';
+FLUSH STATUS;
+SELECT * FROM (SELECT max(b), a FROM t1 GROUP BY a) b;
+SHOW STATUS LIKE 'handler_read__e%';
+FLUSH STATUS;
+(SELECT max(b), a FROM t1 GROUP BY a) UNION
+ (SELECT max(b), a FROM t1 GROUP BY a);
+SHOW STATUS LIKE 'handler_read__e%';
+EXPLAIN (SELECT max(b), a FROM t1 GROUP BY a) UNION
+ (SELECT max(b), a FROM t1 GROUP BY a);
+
+EXPLAIN SELECT (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) x
+ FROM t1 AS t1_outer;
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE EXISTS
+ (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE
+ (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) > 12;
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE
+ a IN (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+EXPLAIN SELECT 1 FROM t1 AS t1_outer GROUP BY a HAVING
+ a > (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+EXPLAIN SELECT 1 FROM t1 AS t1_outer1 JOIN t1 AS t1_outer2
+ ON t1_outer1.a = (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2)
+ AND t1_outer1.b = t1_outer2.b;
+EXPLAIN SELECT (SELECT (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) x
+ FROM t1 AS t1_outer) x2 FROM t1 AS t1_outer2;
+
+CREATE TABLE t3 LIKE t1;
+FLUSH STATUS;
+INSERT INTO t3 SELECT a,MAX(b) FROM t1 GROUP BY a;
+SHOW STATUS LIKE 'handler_read__e%';
+DELETE FROM t3;
+FLUSH STATUS;
+INSERT INTO t3 SELECT 1, (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2)
+ FROM t1 LIMIT 1;
+SHOW STATUS LIKE 'handler_read__e%';
+FLUSH STATUS;
+DELETE FROM t3 WHERE (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2) > 10000;
+SHOW STATUS LIKE 'handler_read__e%';
+FLUSH STATUS;
+--error ER_SUBQUERY_NO_1_ROW
+DELETE FROM t3 WHERE (SELECT (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2) x
+ FROM t1) > 10000;
+SHOW STATUS LIKE 'handler_read__e%';
+
+DROP TABLE t1,t2,t3;
+
+#
+# Bug#25602: queries with DISTINCT and SQL_BIG_RESULT hint
+# for which loose scan optimization is applied
+#
+
+eval CREATE TABLE t1 (a int, INDEX idx(a)) engine=$engine;
+INSERT INTO t1 VALUES
+ (4), (2), (1), (2), (4), (2), (1), (4),
+ (4), (2), (1), (2), (2), (4), (1), (4);
+ANALYZE TABLE t1;
+
+EXPLAIN SELECT DISTINCT(a) FROM t1;
+SELECT DISTINCT(a) FROM t1;
+EXPLAIN SELECT SQL_BIG_RESULT DISTINCT(a) FROM t1;
+SELECT SQL_BIG_RESULT DISTINCT(a) FROM t1;
+
+DROP TABLE t1;
+
+#
+# Bug #32268: Indexed queries give bogus MIN and MAX results
+#
+
+eval CREATE TABLE t1 (a INT, b INT) engine=$engine;
+INSERT INTO t1 (a, b) VALUES (1,1), (1,2), (1,3);
+INSERT INTO t1 SELECT a + 1, b FROM t1;
+INSERT INTO t1 SELECT a + 2, b FROM t1;
+ANALYZE TABLE t1;
+
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+
+CREATE INDEX break_it ON t1 (a, b);
+
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a;
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a;
+
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+
+EXPLAIN
+SELECT a, MIN(b), MAX(b), AVG(b) FROM t1 GROUP BY a ORDER BY a DESC;
+SELECT a, MIN(b), MAX(b), AVG(b) FROM t1 GROUP BY a ORDER BY a DESC;
+
+DROP TABLE t1;
+
+#
+# Bug#38195: Incorrect handling of aggregate functions when loose index scan is
+# used causes server crash.
+#
+create table t1 (a int, b int, primary key (a,b), key `index` (a,b)) engine=MyISAM;
+insert into t1 (a,b) values
+(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),
+ (0,7),(0,8),(0,9),(0,10),(0,11),(0,12),(0,13),
+(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),
+ (1,7),(1,8),(1,9),(1,10),(1,11),(1,12),(1,13),
+(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),
+ (2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),
+(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),
+ (3,7),(3,8),(3,9),(3,10),(3,11),(3,12),(3,13);
+insert into t1 (a,b) select a, max(b)+1 from t1 where a = 0 group by a;
+ANALYZE TABLE t1;
+select * from t1;
+explain extended select sql_buffer_result a, max(b)+1 from t1 where a = 0 group by a;
+drop table t1;
+
+
+#
+# Bug #41610: key_infix_len can be overwritten causing some group by queries
+# to return no rows
+#
+
+eval CREATE TABLE t1 (a int, b int, c int, d int,
+ KEY foo (c,d,a,b), KEY bar (c,a,b,d)) engine=$engine;
+
+INSERT INTO t1 VALUES (1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3), (1, 1, 1, 4);
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT a,b,c+1,d FROM t1;
+ANALYZE TABLE t1;
+
+#Should be non-empty
+EXPLAIN SELECT DISTINCT c FROM t1 WHERE d=4;
+SELECT DISTINCT c FROM t1 WHERE d=4;
+
+DROP TABLE t1;
+
+--echo #
+--echo # Bug #45386: Wrong query result with MIN function in field list,
+--echo # WHERE and GROUP BY clause
+--echo #
+
+eval CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=$engine;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+INSERT INTO t SELECT * FROM t;
+ANALYZE TABLE t;
+
+--echo # test MIN
+--echo #should use range with index for group by
+EXPLAIN
+SELECT a, MIN(b) FROM t WHERE b <> 0 GROUP BY a;
+--echo #should return 1 row
+SELECT a, MIN(b) FROM t WHERE b <> 0 GROUP BY a;
+
+--echo # test MAX
+--echo #should use range with index for group by
+EXPLAIN
+SELECT a, MAX(b) FROM t WHERE b <> 1 GROUP BY a;
+--echo #should return 1 row
+SELECT a, MAX(b) FROM t WHERE b <> 1 GROUP BY a;
+
+--echo # test 3 ranges and use the middle one
+INSERT INTO t SELECT a, 2 FROM t;
+
+--echo #should use range with index for group by
+EXPLAIN
+SELECT a, MAX(b) FROM t WHERE b > 0 AND b < 2 GROUP BY a;
+--echo #should return 1 row
+SELECT a, MAX(b) FROM t WHERE b > 0 AND b < 2 GROUP BY a;
+
+DROP TABLE t;
+
+--echo #
+--echo # Bug #48472: Loose index scan inappropriately chosen for some WHERE
+--echo # conditions
+--echo #
+
+eval CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=$engine;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+ANALYZE TABLE t;
+
+SELECT a, MAX(b) FROM t WHERE 0=b+0 GROUP BY a;
+
+DROP TABLE t;
+
+--echo End of 5.0 tests
+
+--echo #
+--echo # Bug #46607: Assertion failed: (cond_type == Item::FUNC_ITEM) results in
+--echo # server crash
+--echo #
+
+eval CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=$engine;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+
+SELECT a, MAX(b) FROM t WHERE b GROUP BY a;
+
+DROP TABLE t;
+
+#
+# BUG#49902 - SELECT returns incorrect results
+#
+eval CREATE TABLE t1(a INT NOT NULL, b INT NOT NULL, KEY (b)) engine=$engine;
+INSERT INTO t1 VALUES(1,1),(2,1);
+ANALYZE TABLE t1;
+SELECT 1 AS c, b FROM t1 WHERE b IN (1,2) GROUP BY c, b;
+SELECT a FROM t1 WHERE b=1;
+DROP TABLE t1;
+
+--echo #
+--echo # Bug#47762: Incorrect result from MIN() when WHERE tests NOT NULL column
+--echo # for NULL
+--echo #
+
+--echo ## Test for NULLs allowed
+eval CREATE TABLE t1 ( a INT, KEY (a) ) engine=$engine;
+INSERT INTO t1 VALUES (1), (2), (3);
+ANALYZE TABLE t1;
+--source include/min_null_cond.inc
+INSERT INTO t1 VALUES (NULL), (NULL);
+ANALYZE TABLE t1;
+--source include/min_null_cond.inc
+DROP TABLE t1;
+
+--echo ## Test for NOT NULLs
+eval CREATE TABLE t1 ( a INT NOT NULL PRIMARY KEY) engine=$engine;
+INSERT INTO t1 VALUES (1), (2), (3);
+ANALYZE TABLE t1;
+--echo #
+--echo # NULL-safe operator test disabled for non-NULL indexed columns.
+--echo #
+--echo # See bugs
+--echo #
+--echo # - Bug#52173: Reading NULL value from non-NULL index gives
+--echo # wrong result in embedded server
+--echo #
+--echo # - Bug#52174: Sometimes wrong plan when reading a MAX value from
+--echo # non-NULL index
+--echo #
+--let $skip_null_safe_test= 1
+--source include/min_null_cond.inc
+DROP TABLE t1;
+
+--echo #
+--echo # Bug#53859: Valgrind: opt_sum_query(TABLE_LIST*, List<Item>&, Item*) at
+--echo # opt_sum.cc:305
+--echo #
+eval CREATE TABLE t1 ( a INT, KEY (a) ) engine=$engine;
+INSERT INTO t1 VALUES (1), (2), (3);
+
+SELECT MIN( a ) AS min_a
+FROM t1
+WHERE a > 1 AND a IS NULL
+ORDER BY min_a;
+
+DROP TABLE t1;
+
+
+--echo End of 5.1 tests
+
+
+--echo #
+--echo # WL#3220 (Loose index scan for COUNT DISTINCT)
+--echo #
+
+eval CREATE TABLE t1 (a INT, b INT, c INT, KEY (a,b)) engine=$engine;
+INSERT INTO t1 VALUES (1,1,1), (1,2,1), (1,3,1), (1,4,1);
+INSERT INTO t1 SELECT a, b + 4, 1 FROM t1;
+INSERT INTO t1 SELECT a + 1, b, 1 FROM t1;
+ANALYZE TABLE t1;
+eval CREATE TABLE t2 (a INT, b INT, c INT, d INT, e INT, f INT, KEY (a,b,c)) engine=$engine;
+INSERT INTO t2 VALUES (1,1,1,1,1,1), (1,2,1,1,1,1), (1,3,1,1,1,1),
+ (1,4,1,1,1,1);
+INSERT INTO t2 SELECT a, b + 4, c,d,e,f FROM t2;
+INSERT INTO t2 SELECT a + 1, b, c,d,e,f FROM t2;
+ANALYZE TABLE t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1;
+SELECT COUNT(DISTINCT a) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT a,b) FROM t1;
+SELECT COUNT(DISTINCT a,b) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT b,a) FROM t1;
+SELECT COUNT(DISTINCT b,a) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT b) FROM t1;
+SELECT COUNT(DISTINCT b) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 GROUP BY a;
+SELECT COUNT(DISTINCT a) FROM t1 GROUP BY a;
+
+EXPLAIN SELECT COUNT(DISTINCT b) FROM t1 GROUP BY a;
+SELECT COUNT(DISTINCT b) FROM t1 GROUP BY a;
+
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 GROUP BY b;
+SELECT COUNT(DISTINCT a) FROM t1 GROUP BY b;
+
+EXPLAIN SELECT DISTINCT COUNT(DISTINCT a) FROM t1;
+SELECT DISTINCT COUNT(DISTINCT a) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b + 0) FROM t1;
+SELECT COUNT(DISTINCT a, b + 0) FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT b) < 10;
+SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT b) < 10;
+
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT c) < 10;
+SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT c) < 10;
+
+EXPLAIN SELECT 1 FROM t1 HAVING COUNT(DISTINCT a) < 10;
+SELECT 1 FROM t1 HAVING COUNT(DISTINCT a) < 10;
+
+EXPLAIN SELECT 1 FROM t1 GROUP BY a HAVING COUNT(DISTINCT b) > 1;
+SELECT 1 FROM t1 GROUP BY a HAVING COUNT(DISTINCT b) > 1;
+
+EXPLAIN SELECT COUNT(DISTINCT t1_1.a) FROM t1 t1_1, t1 t1_2 GROUP BY t1_1.a;
+SELECT COUNT(DISTINCT t1_1.a) FROM t1 t1_1, t1 t1_2 GROUP BY t1_1.a;
+
+EXPLAIN SELECT COUNT(DISTINCT a), 12 FROM t1;
+SELECT COUNT(DISTINCT a), 12 FROM t1;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b, c) FROM t2;
+SELECT COUNT(DISTINCT a, b, c) FROM t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT a) FROM t2;
+SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT a) FROM t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT f) FROM t2;
+SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT f) FROM t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, a) FROM t2;
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, a) FROM t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, f) FROM t2;
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, f) FROM t2;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, d) FROM t2;
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, d) FROM t2;
+
+EXPLAIN SELECT a, c, COUNT(DISTINCT c, a, b) FROM t2 GROUP BY a, b, c;
+SELECT a, c, COUNT(DISTINCT c, a, b) FROM t2 GROUP BY a, b, c;
+
+EXPLAIN SELECT COUNT(DISTINCT c, a, b) FROM t2
+ WHERE a > 5 AND b BETWEEN 10 AND 20 GROUP BY a, b, c;
+SELECT COUNT(DISTINCT c, a, b) FROM t2
+ WHERE a > 5 AND b BETWEEN 10 AND 20 GROUP BY a, b, c;
+
+EXPLAIN SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 WHERE a = 5
+ GROUP BY b;
+SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 WHERE a = 5
+ GROUP BY b;
+
+EXPLAIN SELECT a, COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+SELECT a, COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+
+EXPLAIN SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+
+EXPLAIN SELECT COUNT(DISTINCT a, b) FROM t2 WHERE c = 13 AND d = 42;
+SELECT COUNT(DISTINCT a, b) FROM t2 WHERE c = 13 AND d = 42;
+
+EXPLAIN SELECT a, COUNT(DISTINCT a), SUM(DISTINCT a) FROM t2
+ WHERE b = 13 AND c = 42 GROUP BY a;
+SELECT a, COUNT(DISTINCT a), SUM(DISTINCT a) FROM t2
+ WHERE b = 13 AND c = 42 GROUP BY a;
+
+--echo # This query could have been resolved using loose index scan since
+--echo # the second part of count(..) is defined by a constant predicate
+EXPLAIN SELECT COUNT(DISTINCT a, b), SUM(DISTINCT a) FROM t2 WHERE b = 42;
+SELECT COUNT(DISTINCT a, b), SUM(DISTINCT a) FROM t2 WHERE b = 42;
+
+EXPLAIN SELECT SUM(DISTINCT a), MAX(b) FROM t2 GROUP BY a;
+SELECT SUM(DISTINCT a), MAX(b) FROM t2 GROUP BY a;
+
+EXPLAIN SELECT 42 * (a + c + COUNT(DISTINCT c, a, b)) FROM t2 GROUP BY a, b, c;
+SELECT 42 * (a + c + COUNT(DISTINCT c, a, b)) FROM t2 GROUP BY a, b, c;
+
+EXPLAIN SELECT (SUM(DISTINCT a) + MAX(b)) FROM t2 GROUP BY a;
+SELECT (SUM(DISTINCT a) + MAX(b)) FROM t2 GROUP BY a;
+
+DROP TABLE t1,t2;
+
+--echo # end of WL#3220 tests
+
+--echo #
+--echo # Bug#50539: Wrong result when loose index scan is used for an aggregate
+--echo # function with distinct
+--echo #
+eval CREATE TABLE t1 (
+ f1 int(11) NOT NULL DEFAULT '0',
+ f2 char(1) NOT NULL DEFAULT '',
+ PRIMARY KEY (f1,f2)
+) engine=$engine;
+insert into t1 values(1,'A'),(1 , 'B'), (1, 'C'), (2, 'A'),
+(3, 'A'), (3, 'B'), (3, 'C'), (3, 'D');
+ANALYZE TABLE t1;
+
+SELECT f1, COUNT(DISTINCT f2) FROM t1 GROUP BY f1;
+explain SELECT f1, COUNT(DISTINCT f2) FROM t1 GROUP BY f1;
+
+drop table t1;
+--echo # End of test#50539.
+
+--echo #
+--echo # Bug#17217128 - BAD INTERACTION BETWEEN MIN/MAX AND
+--echo # "HAVING SUM(DISTINCT)": WRONG RESULTS.
+--echo #
+
+eval CREATE TABLE t (a INT, b INT, KEY(a,b)) engine=$engine;
+INSERT INTO t VALUES (1,1), (2,2), (3,3), (4,4), (1,0), (3,2), (4,5);
+ANALYZE TABLE t;
+# MariaDB: 10.2 doesn't have trace, yet: let $DEFAULT_TRACE_MEM_SIZE=1048576; # 1MB
+# eval set optimizer_trace_max_mem_size=$DEFAULT_TRACE_MEM_SIZE;
+# set @@session.optimizer_trace='enabled=on';
+# set end_markers_in_json=on;
+
+ANALYZE TABLE t;
+
+SELECT a, SUM(DISTINCT a), MIN(b) FROM t GROUP BY a;
+EXPLAIN SELECT a, SUM(DISTINCT a), MIN(b) FROM t GROUP BY a;
+#SELECT TRACE RLIKE 'have_both_agg_distinct_and_min_max' AS OK
+# FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+
+SELECT a, SUM(DISTINCT a), MAX(b) FROM t GROUP BY a;
+EXPLAIN SELECT a, SUM(DISTINCT a), MAX(b) FROM t GROUP BY a;
+#SELECT TRACE RLIKE 'have_both_agg_distinct_and_min_max' AS OK
+# FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+
+SELECT a, MAX(b) FROM t GROUP BY a HAVING SUM(DISTINCT a);
+EXPLAIN SELECT a, MAX(b) FROM t GROUP BY a HAVING SUM(DISTINCT a);
+#SELECT TRACE RLIKE 'have_both_agg_distinct_and_min_max' AS OK
+# FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+
+SELECT SUM(DISTINCT a), MIN(b), MAX(b) FROM t;
+EXPLAIN SELECT SUM(DISTINCT a), MIN(b), MAX(b) FROM t;
+#SELECT TRACE RLIKE 'have_both_agg_distinct_and_min_max' AS OK
+# FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+
+SELECT a, SUM(DISTINCT a), MIN(b), MAX(b) FROM t GROUP BY a;
+EXPLAIN SELECT a, SUM(DISTINCT a), MIN(b), MAX(b) FROM t GROUP BY a;
+#SELECT TRACE RLIKE 'have_both_agg_distinct_and_min_max' AS OK
+# FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+
+#SET optimizer_trace_max_mem_size=DEFAULT;
+#SET optimizer_trace=DEFAULT;
+#SET end_markers_in_json=DEFAULT;
+
+DROP TABLE t;
+
+--echo #
+--echo # Bug#18109609: LOOSE INDEX SCAN IS NOT USED WHEN IT SHOULD
+--echo #
+
+eval CREATE TABLE t1 (
+id INT AUTO_INCREMENT PRIMARY KEY,
+c1 INT,
+c2 INT,
+KEY(c1,c2)) engine=$engine;
+
+INSERT INTO t1(c1,c2) VALUES
+(1, 1), (1,2), (2,1), (2,2), (3,1), (3,2), (3,3), (4,1), (4,2), (4,3),
+(4,4), (4,5), (4,6), (4,7), (4,8), (4,9), (4,10), (4,11), (4,12), (4,13),
+(4,14), (4,15), (4,16), (4,17), (4,18), (4,19), (4,20),(5,5);
+ANALYZE TABLE t1;
+
+EXPLAIN SELECT MAX(c2), c1 FROM t1 WHERE c1 = 4 GROUP BY c1;
+FLUSH STATUS;
+SELECT MAX(c2), c1 FROM t1 WHERE c1 = 4 GROUP BY c1;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+DROP TABLE t1;
+
+--echo # End of test for Bug#18109609
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_direct_io.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_direct_io.inc
new file mode 100644
index 00000000..d9b4b46b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_direct_io.inc
@@ -0,0 +1,23 @@
+# Common test pattern for options that control direct i/o
+#
+# Required input:
+# $io_option - name and assignment to enable on server command line
+
+--perl
+use Cwd 'abs_path';
+
+open(FILE, ">", "$ENV{MYSQL_TMP_DIR}/data_in_shm.inc") or die;
+my $real_path= abs_path($ENV{'MYSQLTEST_VARDIR'});
+my $in_shm= (index($real_path, "/dev/shm") != -1) ||
+ (index($real_path, "/run/shm") != -1);
+print FILE "let \$DATA_IN_SHM= $in_shm;\n";
+close FILE;
+EOF
+
+--source $MYSQL_TMP_DIR/data_in_shm.inc
+--remove_file $MYSQL_TMP_DIR/data_in_shm.inc
+
+if ($DATA_IN_SHM)
+{
+ --skip DATADIR is in /{dev|run}/shm, possibly due to --mem
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.inc
new file mode 100644
index 00000000..1f762d38
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.inc
@@ -0,0 +1,10 @@
+if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'rocksdb' AND support IN ('YES', 'DEFAULT', 'ENABLED')`)
+{
+ --skip Test requires engine RocksDB.
+}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.opt b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.opt
new file mode 100644
index 00000000..36d7dda1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb.opt
@@ -0,0 +1,12 @@
+--loose-enable-rocksdb
+--loose-enable-rocksdb_global_info
+--loose-enable-rocksdb_ddl
+--loose-enable-rocksdb_cf_options
+--loose-enable_rocksdb_perf_context
+--loose-enable_rocksdb_perf_context_global
+--loose-enable-rocksdb_index_file_map
+--loose-enable-rocksdb_dbstats
+--loose-enable-rocksdb_cfstats
+--loose-enable-rocksdb_lock_info
+--loose-enable-rocksdb_trx
+--loose-enable-rocksdb_locks
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_default.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_default.inc
new file mode 100644
index 00000000..2c50afd5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_default.inc
@@ -0,0 +1,10 @@
+if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'rocksdb'` AND support in ('DEFAULT')`)
+{
+ --skip Test requires engine RocksDB as default.
+}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_replication.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_replication.inc
new file mode 100644
index 00000000..92261211
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_rocksdb_replication.inc
@@ -0,0 +1,11 @@
+# MARIAROCKS_NOT_YET: replication doesn't work yet:
+#if (`select count(*) = 0 from information_schema.tables where engine='rocksdb' and table_name='slave_gtid_info'`)
+#{
+# --skip Test requires default engine RocksDB
+#}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_write_committed.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_write_committed.inc
new file mode 100644
index 00000000..681b966f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_write_committed.inc
@@ -0,0 +1,3 @@
+if (`select count(*) = 0 from information_schema.session_variables where variable_name = 'rocksdb_write_policy' and variable_value = 'write_committed';`) {
+ --skip Test requires write_committed policy
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/have_write_prepared.inc b/storage/rocksdb/mysql-test/rocksdb/include/have_write_prepared.inc
new file mode 100644
index 00000000..df088ea0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/have_write_prepared.inc
@@ -0,0 +1,3 @@
+if (`select count(*) = 0 from information_schema.session_variables where variable_name = 'rocksdb_write_policy' and variable_value = 'write_prepared';`) {
+ --skip Test requires write_prepared policy
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/index_merge1.inc b/storage/rocksdb/mysql-test/rocksdb/include/index_merge1.inc
new file mode 100644
index 00000000..c1462e78
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/index_merge1.inc
@@ -0,0 +1,910 @@
+# include/index_merge1.inc
+#
+# Index merge tests
+#
+# The variables
+# $engine_type -- storage engine to be tested
+# $merge_table_support -- 1 storage engine supports merge tables
+# -- 0 storage engine does not support merge tables
+# have to be set before sourcing this script.
+#
+# Note: The comments/expectations refer to MyISAM.
+# They might be not valid for other storage engines.
+#
+# Last update:
+# 2006-08-02 ML test refactored
+# old name was t/index_merge.test
+# main code went into include/index_merge1.inc
+#
+
+--echo #---------------- Index merge test 1 -------------------------------------------
+
+eval SET SESSION DEFAULT_STORAGE_ENGINE = $engine_type;
+
+--disable_warnings
+drop table if exists t0, t1, t2, t3, t4;
+--enable_warnings
+
+# Create and fill a table with simple keys
+create table t0
+(
+ key1 int not null,
+ key2 int not null,
+ key3 int not null,
+ key4 int not null,
+ key5 int not null,
+ key6 int not null,
+ key7 int not null,
+ key8 int not null,
+ INDEX i1(key1),
+ INDEX i2(key2),
+ INDEX i3(key3),
+ INDEX i4(key4),
+ INDEX i5(key5),
+ INDEX i6(key6),
+ INDEX i7(key7),
+ INDEX i8(key8)
+);
+
+--disable_query_log
+insert into t0 values (1,1,1,1,1,1,1,1023),(2,2,2,2,2,2,2,1022);
+
+let $1=9;
+set @d=2;
+while ($1)
+{
+ eval insert into t0 select key1+@d, key2+@d, key3+@d, key4+@d, key5+@d,
+ key6+@d, key7+@d, key8-@d from t0;
+ eval set @d=@d*2;
+ dec $1;
+}
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+--enable_query_log
+
+analyze table t0;
+
+# 1. One index
+explain select * from t0 where key1 < 3 or key1 > 1020;
+
+# 2. Simple cases
+explain
+select * from t0 where key1 < 3 or key2 > 1020;
+select * from t0 where key1 < 3 or key2 > 1020;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key1 < 2 or key2 <3;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain
+select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40);
+# Bug#21277: InnoDB, wrong result set, index_merge strategy, second index not evaluated
+select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40);
+
+# 3. Check that index_merge doesn't break "ignore/force/use index"
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 ignore index (i2) where key1 < 3 or key2 <4;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where (key1 < 3 or key2 <4) and key3 = 50;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 use index (i1,i2) where (key1 < 2 or key2 <3) and key3 = 50;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where (key1 > 1 or key2 > 2);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 force index (i1,i2) where (key1 > 1 or key2 > 2);
+
+
+# 4. Check if conjuncts are grouped by keyuse
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain
+ select * from t0 where key1<2 or key2<3 or (key1>5 and key1<7) or
+ (key1>10 and key1<12) or (key2>100 and key2<102);
+
+# 5. Check index_merge with conjuncts that are always true/false
+# verify fallback to "range" if there is only one non-confluent condition
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key2 = 45 or key1 <=> null;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key2 = 45 or key1 is not null;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key2 = 45 or key1 is null;
+
+# the last conj. is always false and will be discarded
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key2=10 or key3=3 or key4 <=> null;
+
+# the last conj. is always true and will cause 'all' scan
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where key2=10 or key3=3 or key4 is null;
+
+# some more complicated cases
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select key1 from t0 where (key1 <=> null) or (key2 < 2) or
+ (key3=10) or (key4 <=> null);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select key1 from t0 where (key1 <=> null) or (key1 < 5) or
+ (key3=10) or (key4 <=> null);
+
+# 6.Several ways to do index_merge, (ignored) index_merge vs. range
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ (key1 < 2 or key2 < 2) and (key3 < 3 or key4 < 3) and (key5 < 5 or key6 < 5);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain
+select * from t0 where (key1 < 2 or key2 < 4) and (key1 < 5 or key3 < 3);
+
+select * from t0 where (key1 < 2 or key2 < 4) and (key1 < 5 or key3 < 3);
+
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ (key1 < 3 or key2 < 2) and (key3 < 3 or key4 < 3) and (key5 < 2 or key6 < 2);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ (key1 < 3 or key2 < 3) and (key3 < 70);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ (key1 < 3 or key2 < 3) and (key3 < 1000);
+
+
+# 7. Complex cases
+# tree_or(List<SEL_IMERGE>, range SEL_TREE).
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key1 < 3 or key2 < 3) and (key2 <4 or key3 < 3))
+ or
+ key2 > 4;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key1 < 4 or key2 < 4) and (key2 <4 or key3 < 3))
+ or
+ key1 < 5;
+
+select * from t0 where
+ ((key1 < 4 or key2 < 4) and (key2 <4 or key3 < 3))
+ or
+ key1 < 5;
+
+# tree_or(List<SEL_IMERGE>, List<SEL_IMERGE>).
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key1 < 2 or key2 < 2) and (key3 <4 or key5 < 3))
+ or
+ ((key5 < 3 or key6 < 3) and (key7 <3 or key8 < 3));
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 3))
+ or
+ ((key7 <5 or key8 < 3) and (key5 < 4 or key6 < 4));
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 4))
+ or
+ ((key3 <4 or key5 < 2) and (key5 < 5 or key6 < 3));
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key3 <4 or key5 < 3) and (key1 < 3 or key2 < 3))
+ or
+ (((key3 <5 and key7 < 5) or key5 < 2) and (key5 < 4 or key6 < 4));
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 where
+ ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4))
+ or
+ ((key3 >5 or key5 < 2) and (key5 < 5 or key6 < 6));
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where
+ ((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 3))
+ or
+ ((key3 >4 or key5 < 2) and (key5 < 5 or key6 < 4));
+
+# Can't merge any indexes here (predicate on key3 is always true)
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where
+ ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4))
+ or
+ ((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6));
+
+# 8. Verify that "order by" after index merge uses filesort
+select * from t0 where key1 < 3 or key8 < 2 order by key1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain
+select * from t0 where key1 < 3 or key8 < 2 order by key1;
+
+# 9. Check that index_merge cost is compared to 'index' where possible
+create table t2 like t0;
+insert into t2 select * from t0;
+
+alter table t2 add index i1_3(key1, key3);
+alter table t2 add index i2_3(key2, key3);
+alter table t2 drop index i1;
+alter table t2 drop index i2;
+alter table t2 add index i321(key3, key2, key1);
+
+-- disable_query_log
+-- disable_result_log
+analyze table t2;
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+-- enable_result_log
+-- enable_query_log
+
+# index_merge vs 'index', index_merge is better.
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select key3 from t2 where key1 = 100 or key2 = 100;
+
+# index_merge vs 'index', 'index' is better.
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select key3 from t2 where key1 <100 or key2 < 100;
+
+# index_merge vs 'all', index_merge is better.
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select key7 from t2 where key1 <100 or key2 < 100;
+
+# 10. Multipart keys.
+create table t4 (
+ key1a int not null,
+ key1b int not null,
+ key2 int not null,
+ key2_1 int not null,
+ key2_2 int not null,
+ key3 int not null,
+ index i1a (key1a, key1b),
+ index i1b (key1b, key1a),
+ index i2_1(key2, key2_1),
+ index i2_2(key2, key2_1)
+);
+
+insert into t4 select key1,key1,key1 div 10, key1 % 10, key1 % 10, key1 from t0;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t4;
+-- enable_result_log
+-- enable_query_log
+
+# the following will be handled by index_merge:
+select * from t4 where key1a = 3 or key1b = 4;
+explain select * from t4 where key1a = 3 or key1b = 4;
+
+# and the following will not
+explain select * from t4 where key2 = 1 and (key2_1 = 1 or key3 = 5);
+
+explain select * from t4 where key2 = 1 and (key2_1 = 1 or key2_2 = 5);
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t4 where key2_1 = 1 or key2_2 = 5;
+
+
+# 11. Multitable selects
+create table t1 like t0;
+insert into t1 select * from t0;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# index_merge on first table in join
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0 left join t1 on (t0.key1=t1.key1)
+ where t0.key1=3 or t0.key2=4;
+
+select * from t0 left join t1 on (t0.key1=t1.key1)
+ where t0.key1=3 or t0.key2=4;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain
+select * from t0,t1 where (t0.key1=t1.key1) and ( t0.key1=3 or t0.key2=4);
+
+# index_merge vs. ref
+if (!$index_merge_random_rows_in_EXPLAIN)
+{
+#this plan varies too much for InnoDB
+explain
+select * from t0,t1 where (t0.key1=t1.key1) and
+ (t0.key1=3 or t0.key2=4) and t1.key1<200;
+}
+
+# index_merge vs. ref
+explain
+select * from t0,t1 where (t0.key1=t1.key1) and
+ (t0.key1=3 or t0.key2<4) and t1.key1=2;
+
+# index_merge on second table in join
+explain select * from t0,t1 where t0.key1 = 5 and
+ (t1.key1 = t0.key1 or t1.key8 = t0.key1);
+
+# Fix for bug#1974
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t0,t1 where t0.key1 < 3 and
+ (t1.key1 = t0.key1 or t1.key8 = t0.key1);
+
+# index_merge inside union
+explain select * from t1 where key1=3 or key2=4
+ union select * from t1 where key1<4 or key3=5;
+
+# index merge in subselect
+explain select * from (select * from t1 where key1 = 3 or key2 =3) as Z where key8 >5;
+
+# 12. check for long index_merges.
+create table t3 like t0;
+insert into t3 select * from t0;
+alter table t3 add key9 int not null, add index i9(key9);
+alter table t3 add keyA int not null, add index iA(keyA);
+alter table t3 add keyB int not null, add index iB(keyB);
+alter table t3 add keyC int not null, add index iC(keyC);
+update t3 set key9=key1,keyA=key1,keyB=key1,keyC=key1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t3;
+-- enable_result_log
+-- enable_query_log
+
+explain select * from t3 where
+ key1=1 or key2=2 or key3=3 or key4=4 or
+ key5=5 or key6=6 or key7=7 or key8=8 or
+ key9=9 or keyA=10 or keyB=11 or keyC=12;
+
+select * from t3 where
+ key1=1 or key2=2 or key3=3 or key4=4 or
+ key5=5 or key6=6 or key7=7 or key8=8 or
+ key9=9 or keyA=10 or keyB=11 or keyC=12;
+
+# Test for Bug#3183
+explain select * from t0 where key1 < 3 or key2 < 4;
+# Bug#21277: InnoDB, wrong result set, index_merge strategy, second index not evaluated
+select * from t0 where key1 < 3 or key2 < 4;
+
+update t0 set key8=123 where key1 < 3 or key2 < 4;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+-- enable_result_log
+-- enable_query_log
+
+# Bug#21277: InnoDB, wrong result set, index_merge strategy, second index not evaluated
+select * from t0 where key1 < 3 or key2 < 4;
+
+delete from t0 where key1 < 3 or key2 < 4;
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+-- enable_result_log
+-- enable_query_log
+
+select * from t0 where key1 < 3 or key2 < 4;
+select count(*) from t0;
+
+# Test for BUG#4177
+drop table t4;
+create table t4 (a int);
+insert into t4 values (1),(4),(3);
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t4;
+-- enable_result_log
+-- enable_query_log
+
+set @save_join_buffer_size=@@join_buffer_size;
+set join_buffer_size= 4096;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+ where (A.key1 < 500000 or A.key2 < 3)
+ and (B.key1 < 500000 or B.key2 < 3);
+
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+ where (A.key1 < 500000 or A.key2 < 3)
+ and (B.key1 < 500000 or B.key2 < 3);
+
+update t0 set key1=1;
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+-- enable_result_log
+-- enable_query_log
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+ where (A.key1 = 1 or A.key2 = 1)
+ and (B.key1 = 1 or B.key2 = 1);
+
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+ where (A.key1 = 1 or A.key2 = 1)
+ and (B.key1 = 1 or B.key2 = 1);
+
+alter table t0 add filler1 char(200), add filler2 char(200), add filler3 char(200);
+update t0 set key2=1, key3=1, key4=1, key5=1,key6=1,key7=1 where key7 < 500;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+-- enable_result_log
+-- enable_query_log
+
+# The next query will not use index i7 in intersection if the OS doesn't
+# support file sizes > 2GB. (ha_myisam::ref_length depends on this and index
+# scan cost estimates depend on ha_myisam::ref_length)
+if (!$index_merge_random_rows_in_EXPLAIN)
+{
+ # Too unstable for innodb
+ --replace_column 9 #
+ --replace_result "4,4,4,4,4,4,4" X "4,4,4,4,4,4" X "i6,i7" "i6,i7?" "i6" "i6,i7?"
+ explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A, t0 as B
+ where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1)
+ and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1);
+}
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+ from t0 as A, t0 as B
+ where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1)
+ and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1);
+
+set join_buffer_size= @save_join_buffer_size;
+# Test for BUG#4177 ends
+
+drop table t0, t1, t2, t3, t4;
+
+# BUG#16166
+CREATE TABLE t1 (
+ cola char(3) not null, colb char(3) not null, filler char(200),
+ key(cola), key(colb)
+);
+INSERT INTO t1 VALUES ('foo','bar', 'ZZ'),('fuz','baz', 'ZZ');
+
+--disable_query_log
+let $1=9;
+while ($1)
+{
+ eval INSERT INTO t1 SELECT * from t1 WHERE cola = 'foo';
+ dec $1;
+}
+
+let $1=13;
+while ($1)
+{
+ eval INSERT INTO t1 SELECT * from t1 WHERE cola <> 'foo';
+ dec $1;
+}
+
+--enable_query_log
+
+OPTIMIZE TABLE t1;
+select count(*) from t1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 WHERE cola = 'foo' AND colb = 'bar';
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 force index(cola,colb) WHERE cola = 'foo' AND colb = 'bar';
+drop table t1;
+
+if ($merge_table_support)
+{
+#
+# BUG#17314: Index_merge/intersection not chosen by the optimizer for MERGE tables
+#
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (
+ a int, b int,
+ filler1 char(200), filler2 char(200),
+ key(a),key(b)
+);
+insert into t1 select @v:= A.a, @v, 't1', 'filler2' from t0 A, t0 B, t0 C;
+create table t2 like t1;
+
+create table t3 (
+ a int, b int,
+ filler1 char(200), filler2 char(200),
+ key(a),key(b)
+) engine=merge union=(t1,t2);
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+analyze table t1;
+analyze table t2;
+analyze table t3;
+-- enable_result_log
+-- enable_query_log
+
+--replace_column 9 #
+explain select * from t1 where a=1 and b=1;
+--replace_column 9 #
+explain select * from t3 where a=1 and b=1;
+
+drop table t3;
+drop table t0, t1, t2;
+}
+
+#
+# BUG#20256 - LOCK WRITE - MyISAM
+#
+CREATE TABLE t1(a INT);
+INSERT INTO t1 VALUES(1);
+CREATE TABLE t2(a INT, b INT, dummy CHAR(16) DEFAULT '', KEY(a), KEY(b));
+INSERT INTO t2(a,b) VALUES
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(1,2);
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t2(a,b) VALUES(1,2);
+SELECT t2.a FROM t1,t2 WHERE t2.b=2 AND t2.a=1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+
+#
+# BUG#29740: HA_KEY_SCAN_NOT_ROR wasn't set for HEAP engine
+#
+CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `filler` char(200) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `a` (`a`),
+ KEY `b` (`b`)
+) ENGINE=MEMORY DEFAULT CHARSET=latin1;
+
+insert into t1 values
+(0, 'filler', 0), (1, 'filler', 1), (2, 'filler', 2), (3, 'filler', 3),
+(4, 'filler', 4), (5, 'filler', 5), (6, 'filler', 6), (7, 'filler', 7),
+(8, 'filler', 8), (9, 'filler', 9), (0, 'filler', 0), (1, 'filler', 1),
+(2, 'filler', 2), (3, 'filler', 3), (4, 'filler', 4), (5, 'filler', 5),
+(6, 'filler', 6), (7, 'filler', 7), (8, 'filler', 8), (9, 'filler', 9),
+(10, 'filler', 10), (11, 'filler', 11), (12, 'filler', 12), (13, 'filler', 13),
+(14, 'filler', 14), (15, 'filler', 15), (16, 'filler', 16), (17, 'filler', 17),
+(18, 'filler', 18), (19, 'filler', 19), (4, '5 ', 0), (5, '4 ', 0),
+(4, '4 ', 0), (4, 'qq ', 5), (5, 'qq ', 4), (4, 'zz ', 4);
+
+create table t2(
+ `a` int(11) DEFAULT NULL,
+ `filler` char(200) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY USING BTREE (`a`),
+ KEY USING BTREE (`b`)
+) ENGINE=MEMORY DEFAULT CHARSET=latin1;
+insert into t2 select * from t1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+analyze table t2;
+-- enable_result_log
+-- enable_query_log
+
+--echo must use sort-union rather than union:
+--replace_column 9 #
+explain select * from t1 where a=4 or b=4;
+--sorted_result
+select * from t1 where a=4 or b=4;
+--sorted_result
+select * from t1 ignore index(a,b) where a=4 or b=4;
+
+--echo must use union, not sort-union:
+--replace_column 9 #
+explain select * from t2 where a=4 or b=4;
+--sorted_result
+select * from t2 where a=4 or b=4;
+
+drop table t1, t2;
+
+#
+# Bug #37943: Reproducible mysqld crash/sigsegv in sel_trees_can_be_ored
+#
+
+CREATE TABLE t1 (a varchar(8), b set('a','b','c','d','e','f','g','h'),
+ KEY b(b), KEY a(a));
+INSERT INTO t1 VALUES ('y',''), ('z','');
+
+#should not crash
+SELECT b,a from t1 WHERE (b!='c' AND b!='f' && b!='h') OR
+ (a='pure-S') OR (a='DE80337a') OR (a='DE80799');
+
+DROP TABLE t1;
+
+--echo #
+--echo # BUG#40974: Incorrect query results when using clause evaluated using range check
+--echo #
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (a int);
+insert into t1 values (1),(2);
+create table t2(a int, b int);
+insert into t2 values (1,1), (2, 1000);
+create table t3 (a int, b int, filler char(100), key(a), key(b));
+
+insert into t3 select 1000, 1000,'filler' from t0 A, t0 B, t0 C;
+insert into t3 values (1,1,'data');
+insert into t3 values (1,1,'data');
+-- echo The plan should be ALL/ALL/ALL(Range checked for each record (index map: 0x3)
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+analyze table t1;
+analyze table t2;
+analyze table t3;
+-- enable_result_log
+-- enable_query_log
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1
+where exists (select 1 from t2, t3
+ where t2.a=t1.a and (t3.a=t2.b or t3.b=t2.b or t3.b=t2.b+1));
+
+select * from t1
+where exists (select 1 from t2, t3
+ where t2.a=t1.a and (t3.a=t2.b or t3.b=t2.b or t3.b=t2.b+1));
+
+drop table t0, t1, t2, t3;
+
+--echo #
+--echo # BUG#44810: index merge and order by with low sort_buffer_size
+--echo # crashes server!
+--echo #
+CREATE TABLE t1(a VARCHAR(128),b VARCHAR(128),KEY(A),KEY(B));
+INSERT INTO t1 VALUES (REPEAT('a',128),REPEAT('b',128));
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# Causes "out of sort memory" error in MariaDB:
+#SET SESSION sort_buffer_size=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+EXPLAIN
+SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
+ ORDER BY a,b;
+
+# we don't actually care about the result : we're checking if it crashes
+--disable_result_log
+SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
+ ORDER BY a,b;
+--enable_result_log
+
+SET SESSION sort_buffer_size=DEFAULT;
+DROP TABLE t1;
+
+
+--echo End of 5.0 tests
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/index_merge2.inc b/storage/rocksdb/mysql-test/rocksdb/include/index_merge2.inc
new file mode 100644
index 00000000..7e5cec40
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/index_merge2.inc
@@ -0,0 +1,520 @@
+# include/index_merge2.inc
+#
+# Index merge tests
+#
+# The variable
+# $engine_type -- storage engine to be tested
+# has to be set before sourcing this script.
+#
+# Note: The comments/expectations refer to InnoDB.
+# They might be not valid for other storage engines.
+#
+# Last update:
+# 2006-08-02 ML test refactored
+# old name was t/index_merge_innodb.test
+# main code went into include/index_merge2.inc
+#
+
+--echo #---------------- Index merge test 2 -------------------------------------------
+
+eval SET SESSION DEFAULT_STORAGE_ENGINE = $engine_type;
+
+--disable_warnings
+drop table if exists t1,t2;
+--enable_warnings
+
+create table t1
+(
+ key1 int not null,
+ key2 int not null,
+
+ INDEX i1(key1),
+ INDEX i2(key2)
+);
+
+--disable_query_log
+let $1=200;
+while ($1)
+{
+ eval insert into t1 values (200-$1, $1);
+ dec $1;
+}
+--enable_query_log
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# No primary key
+explain select * from t1 where key1 < 5 or key2 > 197;
+
+select * from t1 where key1 < 5 or key2 > 197;
+
+explain select * from t1 where key1 < 3 or key2 > 195;
+select * from t1 where key1 < 3 or key2 > 195;
+
+# Primary key as case-sensitive string with \0s.
+# also make primary key be longer then max. index length of MyISAM.
+alter table t1 add str1 char (255) not null,
+ add zeroval int not null default 0,
+ add str2 char (255) not null,
+ add str3 char (255) not null;
+
+update t1 set str1='aaa', str2='bbb', str3=concat(key2, '-', key1 div 2, '_' ,if(key1 mod 2 = 0, 'a', 'A'));
+
+alter table t1 add primary key (str1, zeroval, str2, str3);
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+explain select * from t1 where key1 < 5 or key2 > 197;
+
+select * from t1 where key1 < 5 or key2 > 197;
+
+explain select * from t1 where key1 < 3 or key2 > 195;
+select * from t1 where key1 < 3 or key2 > 195;
+
+# Test for BUG#5401
+drop table t1;
+create table t1 (
+ pk integer not null auto_increment primary key,
+ key1 integer,
+ key2 integer not null,
+ filler char (200),
+ index (key1),
+ index (key2)
+);
+show warnings;
+--disable_query_log
+let $1=30;
+while ($1)
+{
+ eval insert into t1 (key1, key2, filler) values ($1/4, $1/8, 'filler-data');
+ dec $1;
+}
+--enable_query_log
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+explain select pk from t1 where key1 = 1 and key2 = 1;
+select pk from t1 where key2 = 1 and key1 = 1;
+select pk from t1 ignore index(key1,key2) where key2 = 1 and key1 = 1;
+
+# More tests for BUG#5401.
+drop table t1;
+create table t1 (
+ pk int primary key auto_increment,
+ key1a int,
+ key2a int,
+ key1b int,
+ key2b int,
+ dummy1 int,
+ dummy2 int,
+ dummy3 int,
+ dummy4 int,
+ key3a int,
+ key3b int,
+ filler1 char (200),
+ index i1(key1a, key1b),
+ index i2(key2a, key2b),
+ index i3(key3a, key3b)
+);
+
+create table t2 (a int);
+insert into t2 values (0),(1),(2),(3),(4),(NULL);
+
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+ select A.a, B.a, C.a, D.a, C.a, D.a from t2 A,t2 B,t2 C, t2 D;
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+ select key1a, key1b, key2a, key2b, key3a, key3b from t1;
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+ select key1a, key1b, key2a, key2b, key3a, key3b from t1;
+analyze table t1;
+select count(*) from t1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t2;
+-- enable_result_log
+-- enable_query_log
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select count(*) from t1 where
+ key1a = 2 and key1b is null and key2a = 2 and key2b is null;
+}
+
+select count(*) from t1 where
+ key1a = 2 and key1b is null and key2a = 2 and key2b is null;
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select count(*) from t1 where
+ key1a = 2 and key1b is null and key3a = 2 and key3b is null;
+}
+
+select count(*) from t1 where
+ key1a = 2 and key1b is null and key3a = 2 and key3b is null;
+
+drop table t1,t2;
+
+# Test for BUG#8441
+create table t1 (
+ id1 int,
+ id2 date ,
+ index idx2 (id1,id2),
+ index idx1 (id2)
+);
+insert into t1 values(1,'20040101'), (2,'20040102');
+select * from t1 where id1 = 1 and id2= '20040101';
+drop table t1;
+
+# Test for BUG#12720
+--disable_warnings
+drop view if exists v1;
+--enable_warnings
+CREATE TABLE t1 (
+ `oid` int(11) unsigned NOT NULL auto_increment,
+ `fk_bbk_niederlassung` int(11) unsigned NOT NULL,
+ `fk_wochentag` int(11) unsigned NOT NULL,
+ `uhrzeit_von` time NOT NULL COMMENT 'HH:MM',
+ `uhrzeit_bis` time NOT NULL COMMENT 'HH:MM',
+ `geloescht` tinyint(4) NOT NULL,
+ `version` int(5) NOT NULL,
+ PRIMARY KEY (`oid`),
+ KEY `fk_bbk_niederlassung` (`fk_bbk_niederlassung`),
+ KEY `fk_wochentag` (`fk_wochentag`),
+ KEY `ix_version` (`version`)
+) DEFAULT CHARSET=latin1;
+
+insert into t1 values
+(1, 38, 1, '08:00:00', '13:00:00', 0, 1),
+(2, 38, 2, '08:00:00', '13:00:00', 0, 1),
+(3, 38, 3, '08:00:00', '13:00:00', 0, 1),
+(4, 38, 4, '08:00:00', '13:00:00', 0, 1),
+(5, 38, 5, '08:00:00', '13:00:00', 0, 1),
+(6, 38, 5, '08:00:00', '13:00:00', 1, 2),
+(7, 38, 3, '08:00:00', '13:00:00', 1, 2),
+(8, 38, 1, '08:00:00', '13:00:00', 1, 2),
+(9, 38, 2, '08:00:00', '13:00:00', 1, 2),
+(10, 38, 4, '08:00:00', '13:00:00', 1, 2),
+(11, 38, 1, '08:00:00', '13:00:00', 0, 3),
+(12, 38, 2, '08:00:00', '13:00:00', 0, 3),
+(13, 38, 3, '08:00:00', '13:00:00', 0, 3),
+(14, 38, 4, '08:00:00', '13:00:00', 0, 3),
+(15, 38, 5, '08:00:00', '13:00:00', 0, 3),
+(16, 38, 4, '08:00:00', '13:00:00', 0, 4),
+(17, 38, 5, '08:00:00', '13:00:00', 0, 4),
+(18, 38, 1, '08:00:00', '13:00:00', 0, 4),
+(19, 38, 2, '08:00:00', '13:00:00', 0, 4),
+(20, 38, 3, '08:00:00', '13:00:00', 0, 4),
+(21, 7, 1, '08:00:00', '13:00:00', 0, 1),
+(22, 7, 2, '08:00:00', '13:00:00', 0, 1),
+(23, 7, 3, '08:00:00', '13:00:00', 0, 1),
+(24, 7, 4, '08:00:00', '13:00:00', 0, 1),
+(25, 7, 5, '08:00:00', '13:00:00', 0, 1);
+
+create view v1 as
+select
+ zeit1.oid AS oid,
+ zeit1.fk_bbk_niederlassung AS fk_bbk_niederlassung,
+ zeit1.fk_wochentag AS fk_wochentag,
+ zeit1.uhrzeit_von AS uhrzeit_von,
+ zeit1.uhrzeit_bis AS uhrzeit_bis,
+ zeit1.geloescht AS geloescht,
+ zeit1.version AS version
+from
+ t1 zeit1
+where
+(zeit1.version =
+ (select max(zeit2.version) AS `max(version)`
+ from t1 zeit2
+ where
+ ((zeit1.fk_bbk_niederlassung = zeit2.fk_bbk_niederlassung) and
+ (zeit1.fk_wochentag = zeit2.fk_wochentag) and
+ (zeit1.uhrzeit_von = zeit2.uhrzeit_von) and
+ (zeit1.uhrzeit_bis = zeit2.uhrzeit_bis)
+ )
+ )
+)
+and (zeit1.geloescht = 0);
+
+select * from v1 where oid = 21;
+drop view v1;
+drop table t1;
+##
+CREATE TABLE t1(
+ t_cpac varchar(2) NOT NULL,
+ t_vers varchar(4) NOT NULL,
+ t_rele varchar(2) NOT NULL,
+ t_cust varchar(4) NOT NULL,
+ filler1 char(250) default NULL,
+ filler2 char(250) default NULL,
+ PRIMARY KEY (t_cpac,t_vers,t_rele,t_cust),
+ UNIQUE KEY IX_4 (t_cust,t_cpac,t_vers,t_rele),
+ KEY IX_5 (t_vers,t_rele,t_cust)
+);
+
+insert into t1 values
+('tm','2.5 ','a ',' ','',''), ('tm','2.5U','a ','stnd','',''),
+('da','3.3 ','b ',' ','',''), ('da','3.3U','b ','stnd','',''),
+('tl','7.6 ','a ',' ','',''), ('tt','7.6 ','a ',' ','',''),
+('bc','B61 ','a ',' ','',''), ('bp','B61 ','a ',' ','',''),
+('ca','B61 ','a ',' ','',''), ('ci','B61 ','a ',' ','',''),
+('cp','B61 ','a ',' ','',''), ('dm','B61 ','a ',' ','',''),
+('ec','B61 ','a ',' ','',''), ('ed','B61 ','a ',' ','',''),
+('fm','B61 ','a ',' ','',''), ('nt','B61 ','a ',' ','',''),
+('qm','B61 ','a ',' ','',''), ('tc','B61 ','a ',' ','',''),
+('td','B61 ','a ',' ','',''), ('tf','B61 ','a ',' ','',''),
+('tg','B61 ','a ',' ','',''), ('ti','B61 ','a ',' ','',''),
+('tp','B61 ','a ',' ','',''), ('ts','B61 ','a ',' ','',''),
+('wh','B61 ','a ',' ','',''), ('bc','B61U','a ','stnd','',''),
+('bp','B61U','a ','stnd','',''), ('ca','B61U','a ','stnd','',''),
+('ci','B61U','a ','stnd','',''), ('cp','B61U','a ','stnd','',''),
+('dm','B61U','a ','stnd','',''), ('ec','B61U','a ','stnd','',''),
+('fm','B61U','a ','stnd','',''), ('nt','B61U','a ','stnd','',''),
+('qm','B61U','a ','stnd','',''), ('tc','B61U','a ','stnd','',''),
+('td','B61U','a ','stnd','',''), ('tf','B61U','a ','stnd','',''),
+('tg','B61U','a ','stnd','',''), ('ti','B61U','a ','stnd','',''),
+('tp','B61U','a ','stnd','',''), ('ts','B61U','a ','stnd','',''),
+('wh','B61U','a ','stnd','','');
+show create table t1;
+
+select t_vers,t_rele,t_cust,filler1 from t1 where t_vers = '7.6';
+select t_vers,t_rele,t_cust,filler1 from t1 where t_vers = '7.6'
+ and t_rele='a' and t_cust = ' ';
+
+drop table t1;
+
+# BUG#19021: Crash in index_merge/ROR-intersection optimizer under
+# specific circumstances.
+create table t1 (
+ pk int(11) not null auto_increment,
+ a int(11) not null default '0',
+ b int(11) not null default '0',
+ c int(11) not null default '0',
+
+ filler1 datetime, filler2 varchar(15),
+ filler3 longtext,
+
+ kp1 varchar(4), kp2 varchar(7),
+ kp3 varchar(2), kp4 varchar(4),
+ kp5 varchar(7),
+ filler4 char(1),
+
+ primary key (pk),
+ key idx1(a,b,c),
+ key idx2(c),
+ key idx3(kp1,kp2,kp3,kp4,kp5)
+) default charset=latin1;
+--disable_query_log
+set @fill= uncompress(unhex(concat(
+'F91D0000789CDD993D6FDB301086F7FE0A6D4E0105B8E3F1335D5BA028DA0EEDE28E1D320408',
+'52A0713BF4D7571FB62C51A475924839080307B603E77DEE787C8FA41F9E9EEF7F1F8A87A7C3',
+'AFE280C5DF9F8F7FEE9F8B1B2CB114D6902E918455245DB91300FA16E42D5201FA4EE29DA05D',
+'B9FB3718A33718A3FA8C30AEFAFDE1F317D016AA67BA7A60FDE45BF5F8BA7B5BDE8812AA9F1A',
+'069DB03C9804346644F3A3A6A1338DB572756A3C4D1BCC804CABF912C654AE9BB855A2B85962',
+'3A479259CAE6A86C0411D01AE5483581EDCBD9A39C45252D532E533979EB9F82E971D979BDB4',
+'8531105670740AFBFD1E34AAB0029E4AD0A1D46A6D0946A21A16038A5CD965CD2D524673F712',
+'20C304477315CE18405EAF9BD0AFFEAC74FDA14F1FBF5BD34C769D73FBBEDF4750ADD4E5A99C',
+'5C8DC04934AFA275D483D536D174C11B12AF27F8F888B41B6FC9DBA569E1FD7BD72D698130B7',
+'91B23A98803512B3D31881E8DCDA2AC1754E3644C4BB3A8466750B911681274A39E35E8624B7',
+'444A42AC1213F354758E3CF1A4CDD5A688C767CF1B11ABC5867CB15D8A18E0B91E9EC275BB94',
+'58F33C2936F64690D55BC29E4A293D95A798D84217736CEAAA538CE1354269EE2162053FBC66',
+'496D90CB53323CB279D3A6AF651B4B22B9E430743D83BE48E995A09D4FC9871C22D8D189B945',
+'706911BCB8C3C774B9C08D2FC6ED853ADACA37A14A4CB2E027630E5B80ECACD939431B1CDF62',
+'7D71487536EA2C678F59685E91F4B6C144BCCB94C1EBA9FA6F5552DDCA4E4539BE326A2720CB',
+'45ED028EB3616AC93C46E775FEA9FA6DA7CFCEC6DEBA5FCD1F915EED4D983BDDB881528AD9AB',
+'43C1576F29AAB35BDFBC21D422F52B307D350589D45225A887AC46C8EDD72D99EC3ED2E1BCEF',
+'7AF26FC4C74097B6768A5EDAFA660CC64278F7E63F99AC954B')));
+prepare x from @fill;
+execute x;
+deallocate prepare x;
+--enable_query_log
+set @fill=NULL;
+SELECT COUNT(*) FROM t1 WHERE b = 0 AND a = 0 AND c = 13286427 AND
+ kp1='279' AND kp2='ELM0678' AND kp3='6' AND kp4='10' AND kp5 = 'R ';
+
+drop table t1;
+
+# BUG#21277: Index Merge/sort_union: wrong query results
+create table t1
+(
+ key1 int not null,
+ key2 int not null default 0,
+ key3 int not null default 0
+);
+
+insert into t1(key1) values (1),(2),(3),(4),(5),(6),(7),(8);
+
+let $1=7;
+set @d=8;
+while ($1)
+{
+ eval insert into t1 (key1) select key1+@d from t1;
+ eval set @d=@d*2;
+ dec $1;
+}
+
+alter table t1 add index i2(key2);
+alter table t1 add index i3(key3);
+update t1 set key2=key1,key3=key1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+# to test the bug, the following must use "sort_union":
+explain select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40);
+}
+select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40);
+drop table t1;
+
+--echo #
+--echo # Bug#56423: Different count with SELECT and CREATE SELECT queries
+--echo #
+
+CREATE TABLE t1 (
+ a INT,
+ b INT,
+ c INT,
+ d INT,
+ PRIMARY KEY (a),
+ KEY (c),
+ KEY bd (b,d)
+);
+
+INSERT INTO t1 VALUES
+(1, 0, 1, 0),
+(2, 1, 1, 1),
+(3, 1, 1, 1),
+(4, 0, 1, 1);
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+EXPLAIN
+SELECT a
+FROM t1
+WHERE c = 1 AND b = 1 AND d = 1;
+
+CREATE TABLE t2 ( a INT )
+SELECT a
+FROM t1
+WHERE c = 1 AND b = 1 AND d = 1;
+
+SELECT * FROM t2;
+
+DROP TABLE t1, t2;
+
+CREATE TABLE t1( a INT, b INT, KEY(a), KEY(b) );
+INSERT INTO t1 VALUES (1, 2), (1, 2), (1, 2), (1, 2);
+SELECT * FROM t1 FORCE INDEX(a, b) WHERE a = 1 AND b = 2;
+
+DROP TABLE t1;
+
+--echo # Code coverage of fix.
+CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b INT);
+INSERT INTO t1 (b) VALUES (1);
+UPDATE t1 SET b = 2 WHERE a = 1;
+SELECT * FROM t1;
+
+CREATE TABLE t2 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b VARCHAR(1) );
+INSERT INTO t2 (b) VALUES ('a');
+UPDATE t2 SET b = 'b' WHERE a = 1;
+SELECT * FROM t2;
+
+DROP TABLE t1, t2;
+
+--echo #
+--echo # BUG#13970015: ASSERT `MIN_ENDP || MAX_ENDP' FAILED IN
+--echo # HANDLER::MULTI_RANGE_READ_INFO_CONST
+--echo #
+
+CREATE TABLE t1 (
+ pk INT NOT NULL,
+ col_int_key INT NOT NULL,
+ col_varchar_key VARCHAR(1) NOT NULL,
+ PRIMARY KEY (pk),
+ KEY col_int_key (col_int_key),
+ KEY col_varchar_key (col_varchar_key,col_int_key)
+);
+
+INSERT INTO t1 VALUES (1,1,'a'), (2,2,'b');
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+EXPLAIN
+SELECT col_int_key
+FROM t1
+WHERE col_varchar_key >= 'l' OR
+ (((pk BETWEEN 141 AND 141) OR col_varchar_key <> 'l')
+ AND ((pk BETWEEN 141 AND 141) OR (col_int_key > 141)));
+
+SELECT col_int_key
+FROM t1
+WHERE col_varchar_key >= 'l' OR
+ (((pk BETWEEN 141 AND 141) OR col_varchar_key <> 'l')
+ AND ((pk BETWEEN 141 AND 141) OR (col_int_key > 141)));
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/index_merge_2sweeps.inc b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_2sweeps.inc
new file mode 100644
index 00000000..4f195051
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_2sweeps.inc
@@ -0,0 +1,80 @@
+# include/index_merge_2sweeps.inc
+#
+# 2-sweeps read Index_merge test
+#
+# The variable
+# $engine_type -- storage engine to be tested
+# has to be set before sourcing this script.
+#
+# Last update:
+# 2006-08-02 ML test refactored
+# old name was index_merge_innodb2.test
+# main code went into include/index_merge_2sweeps.inc
+#
+
+--echo #---------------- 2-sweeps read Index merge test 2 -------------------------------
+
+eval SET SESSION DEFAULT_STORAGE_ENGINE = $engine_type;
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+create table t1 (
+ pk int primary key,
+ key1 int,
+ key2 int,
+ filler char(200),
+ filler2 char(200),
+ index(key1),
+ index(key2)
+);
+
+
+--disable_query_log
+let $1=1000;
+while ($1)
+{
+ eval insert into t1 values($1, $1, $1, 'filler-data','filler-data-2');
+ dec $1;
+}
+--enable_query_log
+
+if ($sorted_result) {
+ --sorted_result
+}
+select * from t1 where (key1 >= 2 and key1 <= 10) or (pk >= 4 and pk <=8 );
+
+set @maxv=1000;
+
+if ($sorted_result) {
+ --sorted_result
+}
+select * from t1 where
+ (pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+ or key1=18 or key1=60;
+
+if ($sorted_result) {
+ --sorted_result
+}
+select * from t1 where
+ (pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+ or key1 < 3 or key1 > @maxv-11;
+
+if ($sorted_result) {
+ --sorted_result
+}
+select * from t1 where
+ (pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+ or
+ (key1 < 5) or (key1 > 10 and key1 < 15) or (key1 >= 50 and key1 < 55 ) or (key1 > @maxv-10);
+
+if ($sorted_result) {
+ --sorted_result
+}
+select * from t1 where
+ (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 )
+ or
+ (key1 < 5) or (key1 > @maxv-10);
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror.inc b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror.inc
new file mode 100644
index 00000000..21219d1a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror.inc
@@ -0,0 +1,463 @@
+# include/index_merge_ror.inc
+#
+# ROR-index_merge tests.
+#
+# The variable
+# $engine_type -- storage engine to be tested
+# has to be set before sourcing this script.
+#
+# Note: The comments/expectations refer to MyISAM.
+# They might be not valid for other storage engines.
+#
+# Last update:
+# 2006-08-02 ML test refactored
+# old name was t/index_merge_ror.test
+# main code went into include/index_merge_ror.inc
+#
+
+--echo #---------------- ROR-index_merge tests -----------------------
+
+eval SET SESSION DEFAULT_STORAGE_ENGINE = $engine_type;
+
+--disable_warnings
+drop table if exists t0,t1,t2;
+--enable_warnings
+create table t1
+(
+ /* Field names reflect value(rowid) distribution, st=STairs, swt= SaWTooth */
+ st_a int not null default 0,
+ swt1a int not null default 0,
+ swt2a int not null default 0,
+
+ st_b int not null default 0,
+ swt1b int not null default 0,
+ swt2b int not null default 0,
+
+ /* fields/keys for row retrieval tests */
+ key1 int,
+ key2 int,
+ key3 int,
+ key4 int,
+
+ /* make rows much bigger then keys */
+ filler1 char (200),
+ filler2 char (200),
+ filler3 char (200),
+ filler4 char (200),
+ filler5 char (200),
+ filler6 char (200),
+
+ /* order of keys is important */
+ key sta_swt12a(st_a,swt1a,swt2a),
+ key sta_swt1a(st_a,swt1a),
+ key sta_swt2a(st_a,swt2a),
+ key sta_swt21a(st_a,swt2a,swt1a),
+
+ key st_a(st_a),
+ key stb_swt1a_2b(st_b,swt1b,swt2a),
+ key stb_swt1b(st_b,swt1b),
+ key st_b(st_b),
+
+ key(key1),
+ key(key2),
+ key(key3),
+ key(key4)
+) ;
+
+# Fill table
+create table t0 as select * from t1;
+--disable_query_log
+--echo # Printing of many insert into t0 values (....) disabled.
+let $cnt=1000;
+while ($cnt)
+{
+ eval insert into t0 values (1, 2, 3, 1, 2, 3, 0, 0, 0, 0, 'data1', 'data2', 'data3', 'data4', 'data5', 'data6');
+ dec $cnt;
+}
+--enable_query_log
+
+alter table t1 disable keys;
+--disable_query_log
+--echo # Printing of many insert into t1 select .... from t0 disabled.
+let $1=4;
+while ($1)
+{
+ let $2=4;
+ while ($2)
+ {
+ let $3=4;
+ while ($3)
+ {
+ eval insert into t1 select $1, $2, $3, $1 ,$2, $3, key1, key2, key3, key4, filler1, filler2, filler3, filler4, filler5, filler6 from t0;
+ dec $3;
+ }
+ dec $2;
+ }
+ dec $1;
+}
+
+--echo # Printing of many insert into t1 (...) values (....) disabled.
+# Row retrieval tests
+# -1 is used for values 'out of any range we are using'
+# insert enough rows for index intersection to be used for (key1,key2)
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 100, 100,'key1-key2-key3-key4');
+let $cnt=400;
+while ($cnt)
+{
+ eval insert into t1 (key1, key2, key3, key4, filler1) values (100, -1, 100, -1,'key1-key3');
+ dec $cnt;
+}
+let $cnt=400;
+while ($cnt)
+{
+ eval insert into t1 (key1, key2, key3, key4, filler1) values (-1, 100, -1, 100,'key2-key4');
+ dec $cnt;
+}
+--enable_query_log
+alter table t1 enable keys;
+select count(*) from t1;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t0;
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# One row results tests for cases where a single row matches all conditions
+--replace_column 9 #
+explain select key1,key2 from t1 where key1=100 and key2=100;
+select key1,key2 from t1 where key1=100 and key2=100;
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+ explain format=json select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+}
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+
+# Several-rows results
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, -1, -1, 'key1-key2');
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 100, 100, 'key4-key3');
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# ROR-intersection, not covering
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,filler1 from t1 where key1=100 and key2=100;
+}
+select key1,key2,filler1 from t1 where key1=100 and key2=100;
+
+# ROR-intersection, covering
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2 from t1 where key1=100 and key2=100;
+}
+select key1,key2 from t1 where key1=100 and key2=100;
+
+# ROR-union of ROR-intersections
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+}
+select key1,key2,key3,key4 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+}
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+
+# 3-way ROR-intersection
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3 from t1 where key1=100 and key2=100 and key3=100;
+}
+select key1,key2,key3 from t1 where key1=100 and key2=100 and key3=100;
+
+# ROR-union(ROR-intersection, ROR-range)
+insert into t1 (key1,key2,key3,key4,filler1) values (101,101,101,101, 'key1234-101');
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=101;
+}
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=101;
+
+# Run some ROR updates/deletes
+select key1,key2, filler1 from t1 where key1=100 and key2=100;
+update t1 set filler1='to be deleted' where key1=100 and key2=100;
+update t1 set key1=200,key2=200 where key1=100 and key2=100;
+delete from t1 where key1=200 and key2=200;
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+select key1,key2,filler1 from t1 where key2=100 and key2=200;
+
+# ROR-union(ROR-intersection) with one of ROR-intersection giving empty
+# results
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+}
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+
+delete from t1 where key3=100 and key4=100;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# ROR-union with all ROR-intersections giving empty results
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+}
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+
+# ROR-intersection with empty result
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2 from t1 where key1=100 and key2=100;
+}
+select key1,key2 from t1 where key1=100 and key2=100;
+
+# ROR-union tests with various cases.
+# All scans returning duplicate rows:
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-1');
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-2');
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-3');
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+}
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, -1, 200,'key4');
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+}
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 200, -1,'key3');
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+ explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+}
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+
+##
+## Optimizer tests
+##
+
+# Check that the shortest key is used for ROR-intersection, covering and non-covering.
+if (!$index_merge_random_rows_in_EXPLAIN)
+{
+ # Too unstable on InnoDB
+ explain select * from t1 where st_a=1 and st_b=1;
+ explain select st_a,st_b from t1 where st_a=1 and st_b=1;
+ explain select st_a from t1 ignore index (st_a) where st_a=1 and st_b=1;
+}
+
+# Do many tests
+# Check that keys that don't improve selectivity are skipped.
+#
+if (!$skip_ror_EXPLAIN_for_MyRocks)
+{
+# Different value on 32 and 64 bit
+if ($random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+--replace_result sta_swt12a sta_swt21a sta_swt12a, sta_swt12a,
+explain select * from t1 where st_a=1 and swt1a=1 and swt2a=1;
+
+if ($random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 where st_b=1 and swt1b=1 and swt2b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 ignore index (sta_swt21a, stb_swt1a_2b)
+ where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 ignore index (sta_swt21a, sta_swt12a, stb_swt1a_2b)
+ where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1 ignore index (sta_swt21a, sta_swt12a, stb_swt1a_2b, stb_swt1b)
+ where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1
+ where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select * from t1
+ where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select st_a from t1
+ where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
+
+if ($index_merge_random_rows_in_EXPLAIN)
+{
+ --replace_column 9 #
+}
+explain select st_a from t1
+ where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
+}
+
+drop table t0,t1;
+
+# 'Partially' covered fields test
+
+create table t2 (
+ a char(10),
+ b char(10),
+ filler1 char(255),
+ filler2 char(255),
+ key(a(5)),
+ key(b(5))
+);
+
+--disable_query_log
+let $1=8;
+while ($1)
+{
+ eval insert into t2 values (repeat(char($1+64), 8),repeat(char($1+64), 8),'filler1', 'filler2');
+ dec $1;
+}
+insert into t2 select * from t2;
+insert into t2 select * from t2;
+--enable_query_log
+
+# The table row buffer is reused. Fill it with rows that don't match.
+select count(a) from t2 where a='BBBBBBBB';
+select count(a) from t2 where b='BBBBBBBB';
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t2;
+-- enable_result_log
+-- enable_query_log
+
+# BUG#1:
+--replace_result a a_or_b b a_or_b
+explain select count(a) from t2 where a='AAAAAAAA' and b='AAAAAAAA';
+select count(a) from t2 where a='AAAAAAAA' and b='AAAAAAAA';
+select count(a) from t2 ignore index(a,b) where a='AAAAAAAA' and b='AAAAAAAA';
+
+insert into t2 values ('ab', 'ab', 'uh', 'oh');
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t2;
+-- enable_result_log
+-- enable_query_log
+explain select a from t2 where a='ab';
+drop table t2;
+
+#
+# BUG#25048 - ERROR 126 : Incorrect key file for table '.XXXX.MYI'; try to
+# repair it
+#
+CREATE TABLE t1(c1 INT, c2 INT DEFAULT 0, c3 CHAR(255) DEFAULT '',
+KEY(c1), KEY(c2), KEY(c3));
+INSERT INTO t1(c1) VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
+(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+INSERT INTO t1 VALUES(0,0,0);
+CREATE TABLE t2(c1 int);
+INSERT INTO t2 VALUES(1);
+DELETE t1 FROM t1,t2 WHERE t1.c1=0 AND t1.c2=0;
+SELECT * FROM t1;
+DROP TABLE t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror_cpk.inc b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror_cpk.inc
new file mode 100644
index 00000000..f0d18a50
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/index_merge_ror_cpk.inc
@@ -0,0 +1,178 @@
+# include/index_merge_ror_cpk.inc
+#
+# Clustered PK ROR-index_merge tests
+#
+# The variable
+# $engine_type -- storage engine to be tested
+# has to be set before sourcing this script.
+#
+# Note: The comments/expectations refer to InnoDB.
+# They might be not valid for other storage engines.
+#
+# Last update:
+# 2006-08-02 ML test refactored
+# old name was t/index_merge_ror_cpk.test
+# main code went into include/index_merge_ror_cpk.inc
+#
+
+--echo #---------------- Clustered PK ROR-index_merge tests -----------------------------
+
+eval SET SESSION DEFAULT_STORAGE_ENGINE = $engine_type;
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+create table t1
+(
+ pk1 int not null,
+ pk2 int not null,
+
+ key1 int not null,
+ key2 int not null,
+
+ pktail1ok int not null,
+ pktail2ok int not null,
+ pktail3bad int not null,
+ pktail4bad int not null,
+ pktail5bad int not null,
+
+ pk2copy int not null,
+ badkey int not null,
+
+ filler1 char (200),
+ filler2 char (200),
+ key (key1),
+ key (key2),
+
+ /* keys with tails from CPK members */
+ key (pktail1ok, pk1),
+ key (pktail2ok, pk1, pk2),
+ key (pktail3bad, pk2, pk1),
+ key (pktail4bad, pk1, pk2copy),
+ key (pktail5bad, pk1, pk2, pk2copy),
+
+ primary key (pk1, pk2)
+);
+
+--disable_query_log
+set autocommit=0;
+let $1=10000;
+while ($1)
+{
+ eval insert into t1 values ($1 div 10,$1 mod 100, $1/100,$1/100, $1/100,$1/100,$1/100,$1/100,$1/100, $1 mod 100, $1/1000,'filler-data-$1','filler2');
+ dec $1;
+}
+set autocommit=1;
+--enable_query_log
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+# Verify that range scan on CPK is ROR
+# (use index_intersection because it is impossible to check that for index union)
+# Column 9, rows, can change depending on innodb-page-size.
+--replace_column 9 ROWS
+explain select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
+# CPK scan + 1 ROR range scan is a special case
+--sorted_result
+select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
+
+# Verify that CPK fields are considered to be covered by index scans
+explain select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
+select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
+
+# Verify that CPK is always used for index intersection scans
+# (this is because it is used as a filter, not for retrieval)
+# The expected number of rows can vary depending on page size
+--replace_column 9 ROWS
+explain select * from t1 where badkey=1 and key1=10;
+# The expected number of rows can vary depending on page size
+--replace_column 9 ROWS
+explain select * from t1 where pk1 < 7500 and key1 = 10;
+
+# Verify that keys with 'tails' of PK members are ok.
+explain select * from t1 where pktail1ok=1 and key1=10;
+explain select * from t1 where pktail2ok=1 and key1=10;
+
+# Note: The following is actually a deficiency, it uses sort_union currently.
+# This comment refers to InnoDB and is probably not valid for other engines.
+# The expected number of rows can vary depending on page size
+--replace_column 9 ROWS
+explain select * from t1 where (pktail2ok=1 and pk1< 50000) or key1=10;
+
+# The expected column used for KEY vary depending on page size
+# The expected number of rows can vary depending on page size and platform
+--replace_column 6 EITHER_KEY 9 ROWS
+explain select * from t1 where pktail3bad=1 and key1=10;
+# The expected column used for KEY vary depending on page size
+--replace_column 9 ROWS
+explain select * from t1 where pktail4bad=1 and key1=10;
+# The expected column used for KEY vary depending on page size
+--replace_column 9 ROWS
+explain select * from t1 where pktail5bad=1 and key1=10;
+
+# Test for problem with innodb key values prefetch buffer:
+explain select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
+select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
+
+drop table t1;
+# Testcase for BUG#4984
+create table t1
+(
+ RUNID varchar(22),
+ SUBMITNR varchar(5),
+ ORDERNR char(1),
+ PROGRAMM varchar(8),
+ TESTID varchar(4),
+ UCCHECK char(1),
+ ETEXT varchar(80),
+ ETEXT_TYPE char(1),
+ INFO char(1),
+ SEVERITY tinyint(3),
+ TADIRFLAG char(1),
+ PRIMARY KEY (RUNID,SUBMITNR,ORDERNR,PROGRAMM,TESTID,UCCHECK),
+ KEY `TVERM~KEY` (PROGRAMM,TESTID,UCCHECK)
+) DEFAULT CHARSET=latin1;
+
+update t1 set `ETEXT` = '', `ETEXT_TYPE`='', `INFO`='', `SEVERITY`='', `TADIRFLAG`=''
+WHERE
+ `RUNID`= '' AND `SUBMITNR`= '' AND `ORDERNR`='' AND `PROGRAMM`='' AND
+ `TESTID`='' AND `UCCHECK`='';
+
+drop table t1;
+
+--echo #
+--echo # Bug#50402 Optimizer producing wrong results when using Index Merge on InnoDB
+--echo #
+CREATE TABLE t1 (f1 INT, PRIMARY KEY (f1));
+INSERT INTO t1 VALUES (2);
+CREATE TABLE t2 (f1 INT, f2 INT, f3 char(1),
+ PRIMARY KEY (f1), KEY (f2), KEY (f3) );
+INSERT INTO t2 VALUES (1, 1, 'h'), (2, 3, 'h'), (3, 2, ''), (4, 2, '');
+
+SELECT t1.f1 FROM t1
+WHERE (SELECT COUNT(*) FROM t2 WHERE t2.f3 = 'h' AND t2.f2 = t1.f1) = 0 AND t1.f1 = 2;
+
+-- disable_query_log
+-- disable_result_log
+if ($engine_type == RocksDB)
+{
+ set global rocksdb_force_flush_memtable_now=1;
+}
+analyze table t1;
+analyze table t2;
+-- enable_result_log
+-- enable_query_log
+
+EXPLAIN SELECT t1.f1 FROM t1
+WHERE (SELECT COUNT(*) FROM t2 WHERE t2.f3 = 'h' AND t2.f2 = t1.f1) = 0 AND t1.f1 = 2;
+
+DROP TABLE t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_1.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_1.inc
new file mode 100644
index 00000000..6dc5a78e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_1.inc
@@ -0,0 +1,51 @@
+#
+# Check concurrent locking issues:
+# Locking rows that do not exist when using all primary key columns in a
+# WHERE clause
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case1_1.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 1.1:
+--echo - Locking rows that do not exist when using all primary key columns in
+--echo - a WHERE clause
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+
+connection con2;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t0 VALUES (1,5,0);
+
+--error ER_LOCK_WAIT_TIMEOUT
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+
+connection con1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_2.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_2.inc
new file mode 100644
index 00000000..13083bf8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case1_2.inc
@@ -0,0 +1,48 @@
+#
+# Check concurrent locking issues:
+# Locking rows that do not exist without using all primary key columns in a
+# WHERE clause
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case1_2.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 1.2:
+--echo - Locking rows that do not exist without using all primary key
+--echo - columns in a WHERE clause
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 FOR UPDATE;
+
+connection con2;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=4 FOR UPDATE;
+INSERT INTO t0 VALUES (1,5,0);
+
+connection con1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case2.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case2.inc
new file mode 100644
index 00000000..61c604dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case2.inc
@@ -0,0 +1,97 @@
+#
+# Check concurrent locking issues:
+# Rows that are scanned but do not match the WHERE clause are not locked.
+#
+# To call this, set $isolation_level and call this file
+# If you want to enable rocksdb_lock_scanned_rows set $lock_scanned_rows=1
+#
+# let $isolation_level = REPEATABLE READ;
+# let $lock_scanned_rows = 1 (optional)
+# --source suite/rocksdb/include/locking_issues_case2.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 2:
+--echo - Rows that are scanned but do not match the WHERE are not locked
+--echo - using $isolation_level transaction isolation level unless
+--echo - rocksdb_lock_scanned_rows is on
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+SELECT @@global.rocksdb_lock_scanned_rows;
+
+if ($lock_scanned_rows)
+{
+ let $original_val=query_get_value(
+ select @@global.rocksdb_lock_scanned_rows as val, val, 1);
+ SET GLOBAL rocksdb_lock_scanned_rows=ON;
+}
+
+CREATE TABLE t0(id INT PRIMARY KEY, value INT);
+INSERT INTO t0 VALUES (1,0), (2,1), (3,0), (4,0), (5,1);
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+
+connection con2;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+
+if ($lock_scanned_rows == 1)
+{
+ connection con1;
+ # This is expected to leave locks on all the rows in t0
+ SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+ connection con2;
+ --error ER_LOCK_WAIT_TIMEOUT
+ UPDATE t0 SET VALUE=10 WHERE id=1;
+}
+
+if ($lock_scanned_rows == 0)
+{
+ connection con1;
+ # This is expected to release locks on rows with value=0
+ SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+ connection con2;
+ # This should succeed as con1 should have released the lock on row (1,0)
+ UPDATE t0 SET VALUE=10 WHERE id=1;
+
+ # This should fail because lock on row (5,1) is still held.
+ --error ER_LOCK_WAIT_TIMEOUT
+ UPDATE t0 SET VALUE=10 WHERE id=5;
+
+ connection con1;
+ # Do another operation
+ UPDATE t0 SET value=100 WHERE id in (4,5) and value>0;
+
+ connection con2;
+ # Check that row (4,0) is still not locked
+ SELECT * FROM t0 WHERE id=4 FOR UPDATE;
+
+ COMMIT;
+ SELECT * FROM t0;
+}
+
+connection con1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
+
+if ($lock_scanned_rows == 1)
+{
+ eval SET GLOBAL rocksdb_lock_scanned_rows=$original_val;
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case3.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case3.inc
new file mode 100644
index 00000000..34947cb0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case3.inc
@@ -0,0 +1,71 @@
+#
+# Check concurrent locking issues:
+# After creating a snapshot, other clients updating rows
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case3.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 3:
+--echo - After creating a snapshot, other clients updating rows
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+
+# Insert 200,000 rows, breaking it up into inserts of 1000 rows at a time
+--echo Inserting 200,000 rows
+--disable_query_log
+SET @save_rocksdb_bulk_load=@@rocksdb_bulk_load;
+SET rocksdb_bulk_load=1;
+SET @save_rocksdb_write_disable_wal=@@rocksdb_write_disable_wal;
+SET GLOBAL rocksdb_write_disable_wal=1;
+let $i = 1;
+while ($i <= 200) {
+ eval BEGIN;
+ let $j = 1;
+ while ($j <= 100) {
+ eval INSERT INTO t0(value) VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+ inc $j;
+ }
+ eval COMMIT;
+ inc $i;
+}
+SET rocksdb_bulk_load=@save_rocksdb_bulk_load;
+SET GLOBAL rocksdb_write_disable_wal=@save_rocksdb_write_disable_wal;
+--enable_query_log
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+let $ID = `SELECT connection_id()`;
+send SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+connection con2;
+let $wait_condition =
+ SELECT 1 FROM information_schema.processlist
+ WHERE (id = $ID/* OR srv_id = $ID*/) AND state = "Sending data";
+--source include/wait_condition.inc
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+UPDATE t0 SET VALUE=VALUE+1 WHERE id=190000;
+
+connection con1;
+--error 0,ER_LOCK_DEADLOCK
+reap;
+--echo ERROR: $mysql_errno
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case4.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case4.inc
new file mode 100644
index 00000000..8140b81a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case4.inc
@@ -0,0 +1,69 @@
+#
+# Check concurrent locking issues:
+# Phantom rows
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case4.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 4:
+--echo - Phantom rows
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+
+# Insert 200,000 rows, breaking it up into inserts of 1000 rows at a time
+--echo Inserting 200,000 rows
+--disable_query_log
+SET @save_rocksdb_bulk_load=@@rocksdb_bulk_load;
+SET rocksdb_bulk_load=1;
+SET @save_rocksdb_write_disable_wal=@@rocksdb_write_disable_wal;
+SET GLOBAL rocksdb_write_disable_wal=1;
+let $i = 1;
+while ($i <= 200) {
+ eval BEGIN;
+ let $j = 1;
+ while ($j <= 100) {
+ eval INSERT INTO t0(value) VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+ inc $j;
+ }
+ eval COMMIT;
+ inc $i;
+}
+SET rocksdb_bulk_load=@save_rocksdb_bulk_load;
+SET GLOBAL rocksdb_write_disable_wal=@save_rocksdb_write_disable_wal;
+--enable_query_log
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+let $ID = `SELECT connection_id()`;
+send SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+connection con2;
+let $wait_condition =
+ SELECT 1 FROM information_schema.processlist
+ WHERE (id = $ID/* OR srv_id = $ID*/) AND state = "Sending data";
+--source include/wait_condition.inc
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+INSERT INTO t0 VALUES(200001,1), (-1,1);
+
+connection con1;
+reap;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case5.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case5.inc
new file mode 100644
index 00000000..e28f1c90
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case5.inc
@@ -0,0 +1,77 @@
+#
+# Check concurrent locking issues:
+# Deleting primary key
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case5.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 5:
+--echo - Deleting primary key
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+
+# Insert 200,000 rows, breaking it up into inserts of 1000 rows at a time
+--echo Inserting 200,000 rows
+--disable_query_log
+SET @save_rocksdb_bulk_load=@@rocksdb_bulk_load;
+SET rocksdb_bulk_load=1;
+SET @save_rocksdb_write_disable_wal=@@rocksdb_write_disable_wal;
+SET GLOBAL rocksdb_write_disable_wal=1;
+let $i = 1;
+while ($i <= 200) {
+ eval BEGIN;
+ let $j = 1;
+ while ($j <= 100) {
+ eval INSERT INTO t0(value) VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+ inc $j;
+ }
+ eval COMMIT;
+ inc $i;
+}
+SET rocksdb_bulk_load=@save_rocksdb_bulk_load;
+SET GLOBAL rocksdb_write_disable_wal=@save_rocksdb_write_disable_wal;
+--enable_query_log
+
+UPDATE t0 SET value=100 WHERE id=190000;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+let $ID = `SELECT connection_id()`;
+send SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+connection con2;
+let $wait_condition =
+ SELECT 1 FROM information_schema.processlist
+ WHERE (id = $ID /* OR srv_id = $ID*/) AND state = "Sending data";
+--source include/wait_condition.inc
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+DELETE FROM t0 WHERE id=190000;
+COMMIT;
+
+connection con1;
+--error 0,ER_LOCK_DEADLOCK
+reap;
+--echo ERROR: $mysql_errno
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case6.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case6.inc
new file mode 100644
index 00000000..13ceca07
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case6.inc
@@ -0,0 +1,77 @@
+#
+# Check concurrent locking issues:
+# Changing primary key
+#
+# To call this, set $isolation_level and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# --source suite/rocksdb/include/locking_issues_case6.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 6:
+--echo - Changing primary key
+--echo - using $isolation_level transaction isolation level
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t0;
+--enable_warnings
+
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+
+# Insert 200,000 rows, breaking it up into inserts of 1000 rows at a time
+--echo Inserting 200,000 rows
+--disable_query_log
+SET @save_rocksdb_bulk_load=@@rocksdb_bulk_load;
+SET rocksdb_bulk_load=1;
+SET @save_rocksdb_write_disable_wal=@@rocksdb_write_disable_wal;
+SET GLOBAL rocksdb_write_disable_wal=1;
+let $i = 1;
+while ($i <= 200) {
+ eval BEGIN;
+ let $j = 1;
+ while ($j <= 100) {
+ eval INSERT INTO t0(value) VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+ inc $j;
+ }
+ eval COMMIT;
+ inc $i;
+}
+SET rocksdb_bulk_load=@save_rocksdb_bulk_load;
+SET GLOBAL rocksdb_write_disable_wal=@save_rocksdb_write_disable_wal;
+--enable_query_log
+
+UPDATE t0 SET value=100 WHERE id=190000;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+let $ID = `SELECT connection_id()`;
+send SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+
+connection con2;
+let $wait_condition =
+ SELECT 1 FROM information_schema.processlist
+ WHERE (id = $ID/* OR srv_id = $ID*/) AND state = "Sending data";
+--source include/wait_condition.inc
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+UPDATE t0 SET id=200001 WHERE id=190000;
+COMMIT;
+
+connection con1;
+--error 0,ER_LOCK_DEADLOCK
+reap;
+--echo ERROR: $mysql_errno
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case7.inc b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case7.inc
new file mode 100644
index 00000000..d71d3989
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/locking_issues_case7.inc
@@ -0,0 +1,89 @@
+#
+# Check concurrent locking issues:
+# Rows scanned but are not in the updated table should be locked when
+# rocksdb_lock_scanned_rows is on but not locked otherwise.
+#
+# To call this, set $isolation_level and $lock_scanned_rows and call this file
+#
+# let $isolation_level = REPEATABLE READ;
+# let $lock_scanned_rows = 0 (or 1)
+# --source suite/rocksdb/include/locking_issues_case7.inc
+#
+
+--echo
+--echo -----------------------------------------------------------------------
+--echo - Locking issues case 7:
+--echo - Rows that are scanned as part of a query but not in the table being
+--echo - updated should not be locked unless rocksdb_lock_scanned_rows is on
+--echo -----------------------------------------------------------------------
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+SELECT @@global.rocksdb_lock_scanned_rows;
+
+if ($lock_scanned_rows)
+{
+ let $original_val=query_get_value(
+ select @@global.rocksdb_lock_scanned_rows as val, val, 1);
+ SET GLOBAL rocksdb_lock_scanned_rows=ON;
+}
+
+CREATE TABLE t1(id INT PRIMARY KEY, value INT);
+CREATE TABLE t2(id INT PRIMARY KEY, value INT);
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3);
+INSERT INTO t2 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+
+connection con2;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+BEGIN;
+
+--echo lock_scanned_rows is $lock_scanned_rows
+if ($lock_scanned_rows == 1)
+{
+ connection con1;
+ # This is expected to leave a lock id=3 in t2;
+ UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+
+ connection con2;
+ --error ER_LOCK_WAIT_TIMEOUT
+ UPDATE t2 SET value=value+100 WHERE id=3;
+
+ # No other row in t2 should be locked;
+ UPDATE t2 SET value=value+100 WHERE id IN (1,2,4,5);
+ SELECT * FROM t2;
+}
+
+if ($lock_scanned_rows == 0)
+{
+ connection con1;
+ # This should leave no locks on any row in t2;
+ UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+
+ connection con2;
+ UPDATE t2 SET value=value+100;
+ SELECT * FROM t2;
+}
+
+connection con1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t1;
+DROP TABLE t2;
+
+if ($lock_scanned_rows == 1)
+{
+ eval SET GLOBAL rocksdb_lock_scanned_rows=$original_val;
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/prefix_index_only_query_check.inc b/storage/rocksdb/mysql-test/rocksdb/include/prefix_index_only_query_check.inc
new file mode 100644
index 00000000..e96eb573
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/prefix_index_only_query_check.inc
@@ -0,0 +1,21 @@
+#
+# A helper include file for prefix index index-only query tests
+#
+# Parameters:
+# $prefix_index_check_title - title of the test
+# $prefix_index_check_query - test query
+# $prefix_index_check_read_avoided_delta - expected change of
+# 'rocksdb_covered_secondary_key_lookups' status variable
+# value after running the query
+
+--let $show_count_statement = show status like 'rocksdb_covered_secondary_key_lookups'
+
+--echo # $prefix_index_check_title
+--let $base_count = query_get_value($show_count_statement, Value, 1)
+
+--eval $prefix_index_check_query
+
+--let $count = query_get_value($show_count_statement, Value, 1)
+--let $assert_text= $prefix_index_check_title: $prefix_index_check_read_avoided_delta rocksdb_covered_secondary_key_lookups
+--let $assert_cond= $count - $base_count = $prefix_index_check_read_avoided_delta
+--source include/assert.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_invalid_option.inc b/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_invalid_option.inc
new file mode 100644
index 00000000..8eef7ed2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_invalid_option.inc
@@ -0,0 +1,8 @@
+--source include/shutdown_mysqld.inc
+
+# Expect the server to fail to come up with these options
+--error 1
+--exec $MYSQLD_CMD --plugin_load=$HA_ROCKSDB_SO $_mysqld_option
+
+# Restart the server with the default options
+--source include/start_mysqld.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_option.inc b/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_option.inc
new file mode 100644
index 00000000..81cd2200
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/restart_mysqld_with_option.inc
@@ -0,0 +1,31 @@
+
+if ($rpl_inited)
+{
+ if (!$allow_rpl_inited)
+ {
+ --die ERROR IN TEST: This script does not support replication
+ }
+}
+
+# Write file to make mysql-test-run.pl expect the "crash", but don't start
+# it until it's told to
+--let $_server_id= `SELECT @@server_id`
+--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
+--exec echo "wait" > $_expect_file_name
+
+# Send shutdown to the connected server and give
+# it 10 seconds to die before zapping it
+shutdown_server;
+
+# Write file to make mysql-test-run.pl start up the server again
+--exec echo "restart:$_mysqld_option" > $_expect_file_name
+
+# Turn on reconnect
+--enable_reconnect
+
+# Call script that will poll the server waiting for it to be back online again
+--source include/wait_until_connected_again.inc
+
+# Turn off reconnect again
+--disable_reconnect
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/rocksdb_icp.inc b/storage/rocksdb/mysql-test/rocksdb/include/rocksdb_icp.inc
new file mode 100644
index 00000000..c76b52d4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/rocksdb_icp.inc
@@ -0,0 +1,199 @@
+#
+# Testing Index Condition Pushdown for MyRocks
+# Test file parameter: $cf_name specifies the CF to store test data in
+# It can be forward or reverse-ordered CF
+#
+select * from information_schema.engines where engine = 'rocksdb';
+
+--disable_warnings
+drop table if exists t0,t1,t2,t3;
+--enable_warnings
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+
+eval
+create table t2 (
+ pk int primary key,
+ kp1 int,
+ kp2 int,
+ col1 int,
+ key (kp1,kp2) comment '$cf_name'
+) engine=rocksdb;
+
+insert into t2 select a,a,a,a from t1;
+
+--echo # Try a basic case:
+--replace_column 9 #
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+
+--echo # Check that ICP doesnt work for columns where column value
+--echo # cant be restored from mem-comparable form:
+
+eval
+create table t3 (
+ pk int primary key,
+ kp1 int,
+ kp2 varchar(10) collate utf8_general_ci,
+ col1 int,
+ key (kp1,kp2) comment '$cf_name'
+) engine=rocksdb;
+
+insert into t3 select a,a/10,a,a from t1;
+--echo # This must not use ICP:
+--replace_column 9 #
+explain
+select * from t3 where kp1=3 and kp2 like '%foo%';
+
+--replace_column 9 #
+explain format=json
+select * from t3 where kp1 between 2 and 4 and mod(kp1,3)=0 and kp2 like '%foo%';
+
+--echo # Check that we handle the case where out-of-range is encountered sooner
+--echo # than matched index condition
+--replace_column 9 #
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+select * from t2 where kp1< 3 and kp2+1>50000;
+
+--replace_column 9 #
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+select * from t2 where kp1< 3 and kp2+1>50000;
+
+--echo # Try doing backwards scans
+--echo # MariaDB: ICP is not supported for reverse scans.
+
+--replace_column 9 #
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+
+--replace_column 9 #
+explain
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+
+--replace_column 9 #
+explain
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+
+drop table t0,t1,t2,t3;
+
+--echo #
+--echo # Check how ICP affects counters
+--echo #
+--echo # First, some preparations
+--echo #
+--echo # in facebook/mysql-5.6, it was:
+--echo # select ROWS_READ, ROWS_REQUESTED, ROWS_INDEX_FIRST, ROWS_INDEX_NEXT
+--echo #
+--echo # In MariaDB, we do:
+delimiter |;
+create procedure save_read_stats()
+begin
+ set @rr=(select ROWS_READ
+ from information_schema.table_statistics
+ where table_name='t4' and table_schema=database());
+
+ set @rif= (select VARIABLE_VALUE
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_read_first');
+
+ set @rin=(select VARIABLE_VALUE
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_read_next');
+
+ set @icp_attempts=(select VARIABLE_VALUE
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_icp_attempts');
+
+ set @icp_matches=(select VARIABLE_VALUE
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_icp_match');
+end|
+
+create procedure get_read_stats()
+begin
+ select
+ (select ROWS_READ
+ from information_schema.table_statistics
+ where table_name='t4' and table_schema=database()
+ ) - @rr as ROWS_READ_DIFF,
+
+ (select VARIABLE_VALUE - @rif
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_read_first') as ROWS_INDEX_FIRST,
+
+ (select VARIABLE_VALUE - @rin
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_read_next') as ROWS_INDEX_NEXT,
+
+ (select VARIABLE_VALUE - @icp_attempts
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_icp_attempts') as ICP_ATTEMPTS,
+
+ (select VARIABLE_VALUE - @icp_matches
+ from information_schema.session_status
+ where VARIABLE_NAME='Handler_icp_match') as ICP_MATCHES;
+end|
+
+delimiter ;|
+
+eval
+create table t4 (
+ id int,
+ id1 int,
+ id2 int,
+ value int,
+ value2 varchar(100),
+ primary key (id),
+ key id1_id2 (id1, id2) comment '$cf_name'
+) engine=rocksdb charset=latin1 collate latin1_bin;
+
+insert into t4 values
+(1,1,1,1,1), (2,1,2,2,2), (3,1,3,3,3),(4,1,4,4,4),(5,1,5,5,5),
+(6,1,6,6,6), (7,1,7,7,7), (8,1,8,8,8),(9,1,9,9,9),(10,1,10,10,10);
+
+--echo #
+--echo # Now, the test itself
+--echo #
+call save_read_stats();
+call get_read_stats();
+
+
+--echo # ============== index-only query ==============
+--replace_column 9 #
+explain
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+call save_read_stats();
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+query_vertical call get_read_stats();
+
+--echo # ============== Query without ICP ==============
+set optimizer_switch='index_condition_pushdown=off';
+--replace_column 9 #
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+query_vertical call get_read_stats();
+
+--echo # ============== Query with ICP ==============
+set optimizer_switch='index_condition_pushdown=on';
+--replace_column 9 #
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+query_vertical call get_read_stats();
+
+drop table t4;
+drop procedure save_read_stats;
+drop procedure get_read_stats;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/simple_deadlock.inc b/storage/rocksdb/mysql-test/rocksdb/include/simple_deadlock.inc
new file mode 100644
index 00000000..0afdfea7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/simple_deadlock.inc
@@ -0,0 +1,29 @@
+connection con1;
+begin;
+select * from t where i=1 for update;
+
+connection con2;
+begin;
+select * from t where i=2 for update;
+
+connection con1;
+--send select * from t where i=2 for update
+
+connection con2;
+if ($engine == "rocksdb"){
+ let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+ where thread_id = $con1 and waiting_key != "";
+}
+if ($engine == "innodb"){
+ let $wait_condition = select count(*) = 1 from information_schema.innodb_trx
+ where trx_mysql_thread_id = $con1 and trx_state="LOCK WAIT";
+}
+--source include/wait_condition.inc
+
+--error ER_LOCK_DEADLOCK
+select * from t where i=1 for update;
+rollback;
+
+connection con1;
+--reap
+rollback;
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/start_mysqld_with_option.inc b/storage/rocksdb/mysql-test/rocksdb/include/start_mysqld_with_option.inc
new file mode 100644
index 00000000..73e30b3e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/start_mysqld_with_option.inc
@@ -0,0 +1,14 @@
+# Include this script only after using shutdown_mysqld.inc
+# where $_expect_file_name was initialized.
+# Write file to make mysql-test-run.pl start up the server again
+--exec echo "restart:$_mysqld_option" > $_expect_file_name
+
+# Turn on reconnect
+--enable_reconnect
+
+# Call script that will poll the server waiting for it to be back online again
+--source include/wait_until_connected_again.inc
+
+# Turn off reconnect again
+--disable_reconnect
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/include/use_direct_io_option.inc b/storage/rocksdb/mysql-test/rocksdb/include/use_direct_io_option.inc
new file mode 100644
index 00000000..6e427f26
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/include/use_direct_io_option.inc
@@ -0,0 +1,24 @@
+# Common test pattern for options that control direct i/o
+#
+# Required input:
+# $io_option - name and assignment to enable on server command line
+
+--source include/have_direct_io.inc
+
+--echo Checking direct reads
+--let $restart_noprint=2
+--let $_mysqld_option=$io_option
+--source include/restart_mysqld_with_option.inc
+
+CREATE TABLE t1 (pk INT PRIMARY KEY DEFAULT '0', a INT(11), b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 VALUES (1, 1,'a');
+INSERT INTO t1 (a,b) VALUES (2,'b');
+set global rocksdb_force_flush_memtable_now=1;
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+# cleanup
+--let _$mysqld_option=
+--source include/restart_mysqld.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/my.cnf b/storage/rocksdb/mysql-test/rocksdb/my.cnf
new file mode 100644
index 00000000..ab89713c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/my.cnf
@@ -0,0 +1,19 @@
+!include include/default_my.cnf
+
+[server]
+skip-innodb
+default-storage-engine=rocksdb
+
+sql-mode=NO_ENGINE_SUBSTITUTION
+explicit-defaults-for-timestamp=1
+loose-rocksdb_lock_wait_timeout=1
+loose-rocksdb_strict_collation_check=0
+
+# MariaDB: speed up the tests:
+loose-rocksdb-flush-log-at-trx-commit=0
+
+loose-rocksdb_force_compute_memtable_stats_cachetime=0
+
+# The following is to get rid of the harmless
+# "Deadlock found when trying to get lock" errors, see MDEV-12285.
+log-warnings=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/1st.result b/storage/rocksdb/mysql-test/rocksdb/r/1st.result
new file mode 100644
index 00000000..323b614e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/1st.result
@@ -0,0 +1,22 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY DEFAULT '0', a INT(11), b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL DEFAULT 0,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI 0
+a int(11) YES NULL
+b char(8) YES NULL
+INSERT INTO t1 VALUES (1, 1,'a');
+INSERT INTO t1 (a,b) VALUES (2,'b');
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/2pc_group_commit.result b/storage/rocksdb/mysql-test/rocksdb/r/2pc_group_commit.result
new file mode 100644
index 00000000..a6bb9eb6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/2pc_group_commit.result
@@ -0,0 +1,101 @@
+# Disable for valgrind because this takes too long
+DROP DATABASE IF EXISTS mysqlslap;
+CREATE DATABASE mysqlslap;
+USE mysqlslap;
+CREATE TABLE t1(id BIGINT AUTO_INCREMENT, value BIGINT, PRIMARY KEY(id)) ENGINE=rocksdb;
+SET @save_rocksdb_enable_2pc= @@rocksdb_enable_2pc;
+SET @save_rocksdb_flush_log_at_trx_commit= @@rocksdb_flush_log_at_trx_commit;
+# 2PC enabled, MyRocks durability enabled
+SET GLOBAL rocksdb_enable_2pc=1;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=1;
+##
+## 2PC + durability + single thread
+##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+select IF(variable_value - @b1 = 1000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+Binlog_commits
+OK
+select IF(variable_value - @b2 = 1000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+Binlog_group_commits
+OK
+# Prepare operations sync, commits don't. We expect slightly more than 1K syncs:
+select IF(variable_value - @b3 between 1000 and 1500, 'OK', variable_value - @b3) as Rocksdb_wal_synced
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+Rocksdb_wal_synced
+OK
+set debug_dbug='+d,rocksdb_enable_delay_commits';
+create table dummy10(a int) engine=rocksdb;
+Warnings:
+Warning 1210 enable_delay_commits_mode ON
+drop table dummy10;
+set debug_dbug='-d,rocksdb_enable_delay_commits';
+##
+## 2PC + durability + group commit
+##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+select IF(variable_value - @b1 = 10000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+Binlog_commits
+OK
+select IF(variable_value - @b2 between 100 and 5000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+Binlog_group_commits
+OK
+select IF(variable_value - @b3 between 1 and 9000, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+IF(variable_value - @b3 between 1 and 9000, 'OK', variable_value - @b3)
+OK
+set debug_dbug='+d,rocksdb_disable_delay_commits';
+create table dummy10(a int) engine=rocksdb;
+Warnings:
+Warning 1210 enable_delay_commits_mode OFF
+drop table dummy10;
+set debug_dbug='-d,rocksdb_disable_delay_commits';
+##
+# 2PC enabled, MyRocks durability disabled, single thread
+##
+SET GLOBAL rocksdb_enable_2pc=1;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=0;
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+select IF(variable_value - @b1 = 1000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+Binlog_commits
+OK
+select IF(variable_value - @b2 = 1000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+Binlog_group_commits
+OK
+select IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+OK
+##
+# 2PC enabled, MyRocks durability disabled, concurrent workload
+##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+select IF(variable_value - @b1 = 10000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+Binlog_commits
+OK
+select IF(variable_value - @b2 < 8000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+Binlog_group_commits
+OK
+select IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+OK
+SET GLOBAL rocksdb_enable_2pc= @save_rocksdb_enable_2pc;
+SET GLOBAL rocksdb_flush_log_at_trx_commit= @save_rocksdb_flush_log_at_trx_commit;
+DROP TABLE t1;
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace.result b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace.result
new file mode 100644
index 00000000..20f84dd5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace.result
@@ -0,0 +1,505 @@
+drop table if exists t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `ka` (`a`),
+ KEY `kab` (`a`,`b`),
+ KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+a b
+2 6
+3 7
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+a b
+3 7
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), DROP INDEX ka, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `kab` (`a`,`b`),
+ KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+a b
+2 6
+3 7
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+a b
+3 7
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 DROP INDEX ka, DROP INDEX kab, ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kb(b), ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `kb` (`b`),
+ KEY `kab` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+a b
+2 6
+3 7
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+a b
+3 7
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), DROP INDEX ka, ADD INDEX kba(b,a), DROP INDEX kab, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `kb` (`b`),
+ KEY `kba` (`b`,`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+a b
+2 6
+3 7
+SELECT * FROM t1 FORCE INDEX(kba) WHERE a > 2;
+a b
+3 7
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+ALTER TABLE t1 DROP INDEX ka, ADD INDEX ka(b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `kab` (`a`,`b`),
+ KEY `ka` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(ka) WHERE b > 5;
+a b
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+a b
+DROP TABLE t1;
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk char(8) NO PRI NULL
+a varchar(11) YES NULL
+b int(10) unsigned YES NULL
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`),
+ KEY `kab` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > '2' AND b < 3;
+pk a b
+bbb 2222 2
+DROP TABLE t1;
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk char(8) NO PRI NULL
+a varchar(11) YES NULL
+b int(10) unsigned YES NULL
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX ka(a), DROP INDEX kab, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`),
+ KEY `ka` (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(ka) WHERE a > '2' AND b < 3;
+pk a b
+bbb 2222 2
+DROP TABLE t1;
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk char(8) NO PRI NULL
+a varchar(11) YES NULL
+b int(10) unsigned YES NULL
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ADD INDEX ka(a), ADD INDEX kb(b), ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX ka, DROP INDEX kb, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(8) COLLATE utf8_bin NOT NULL,
+ `a` varchar(11) COLLATE utf8_bin DEFAULT NULL,
+ `b` int(10) unsigned DEFAULT NULL,
+ PRIMARY KEY (`pk`),
+ KEY `kab` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > '2' AND b < 3;
+pk a b
+bbb 2222 2
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+CREATE INDEX kb on t1 (b);
+CREATE INDEX kba on t1 (b,a);
+DROP INDEX ka on t1;
+DROP INDEX kab on t1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `kb` (`b`),
+ KEY `kba` (`b`,`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+a b
+2 6
+3 7
+SELECT * FROM t1 FORCE INDEX(kba) WHERE a > 2;
+a b
+3 7
+DROP TABLE t1;
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+DROP INDEX kij ON t1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ `k` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `j` (`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY KEY (`i`)
+PARTITIONS 4
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+i j k
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+100
+DROP TABLE t1;
+set @tmp_rocksdb_strict_collation_check= @@rocksdb_strict_collation_check;
+set global rocksdb_strict_collation_check=1;
+CREATE TABLE t1 (a INT, b TEXT);
+# MariaDB no longer gives ER_UNSUPPORTED_COLLATION
+ALTER TABLE t1 ADD KEY kb(b(10));
+ALTER TABLE t1 ADD PRIMARY KEY(a);
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b TEXT collate utf8_general_ci);
+# MariaDB no longer gives ER_UNSUPPORTED_COLLATION
+ALTER TABLE t1 ADD KEY kb(b(10));
+Warnings:
+Warning 1815 Internal error: Indexed column test.t1.b uses a collation that does not allow index-only access in secondary key and has reduced disk space efficiency in primary key.
+ALTER TABLE t1 ADD PRIMARY KEY(a);
+DROP TABLE t1;
+set global rocksdb_strict_collation_check= @tmp_rocksdb_strict_collation_check;
+set global rocksdb_bulk_load=1;
+# Establish connection con1 (user=root)
+connect con1,localhost,root,,;
+# Switch to connection con1
+connection con1;
+show global variables like 'rocksdb_bulk_load%';
+Variable_name Value
+rocksdb_bulk_load ON
+rocksdb_bulk_load_allow_sk OFF
+rocksdb_bulk_load_allow_unsorted OFF
+rocksdb_bulk_load_size 1000
+show session variables like 'rocksdb_bulk_load%';
+Variable_name Value
+rocksdb_bulk_load ON
+rocksdb_bulk_load_allow_sk OFF
+rocksdb_bulk_load_allow_unsorted OFF
+rocksdb_bulk_load_size 1000
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1,1);
+# Disconnecting on con1
+disconnect con1;
+# Establish connection con2 (user=root)
+connect con2,localhost,root,,;
+# Switch to connection con2
+connection con2;
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+COUNT(*)
+1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kj);
+COUNT(*)
+1
+DROP TABLE t1;
+disconnect con2;
+# Establish connection con1 (user=root)
+connect con1,localhost,root,,;
+# Establish connection con2 (user=root)
+connect con2,localhost,root,,;
+# Switch to connection con1
+connection con1;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+set rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1,1);
+# Switch to connection con2
+connection con2;
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+COUNT(*)
+0
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+COUNT(*)
+1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kj);
+COUNT(*)
+1
+set global rocksdb_bulk_load=0;
+DROP TABLE t1;
+connection default;
+SET @prior_rocksdb_merge_combine_read_size= @@rocksdb_merge_combine_read_size;
+SET @prior_rocksdb_strict_collation_check= @@rocksdb_strict_collation_check;
+SET @prior_rocksdb_merge_buf_size = @@rocksdb_merge_buf_size;
+SET global rocksdb_strict_collation_check = off;
+SET session rocksdb_merge_combine_read_size = 566;
+SET session rocksdb_merge_buf_size = 340;
+show variables like 'rocksdb_bulk_load%';
+Variable_name Value
+rocksdb_bulk_load OFF
+rocksdb_bulk_load_allow_sk OFF
+rocksdb_bulk_load_allow_unsorted OFF
+rocksdb_bulk_load_size 1000
+CREATE TABLE t1 (a VARCHAR(80)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+ALTER TABLE t1 ADD INDEX ka(a), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` varchar(80) DEFAULT NULL,
+ KEY `ka` (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+SELECT * FROM t1 FORCE INDEX(ka) WHERE a > "";
+a
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+DROP TABLE t1;
+SET session rocksdb_merge_buf_size = @prior_rocksdb_merge_buf_size;
+SET session rocksdb_merge_combine_read_size = @prior_rocksdb_merge_combine_read_size;
+SET global rocksdb_strict_collation_check = @prior_rocksdb_strict_collation_check;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+set global rocksdb_force_flush_memtable_now=1;
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+larger
+1
+# restart
+larger
+1
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+larger
+1
+# restart
+larger
+1
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+select 1300 < 1300 * 1.5 as "same";
+same
+1
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+c INT,
+KEY kbc(b,c)) ENGINE = ROCKSDB;
+INSERT INTO t1 (a,b,c) VALUES (1,1,1);
+INSERT INTO t1 (a,b,c) VALUES (2,2,2);
+INSERT INTO t1 (a,b,c) VALUES (3,3,3);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kbc` (`b`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 DROP INDEX kbc, ADD INDEX kbc(b,c), ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX kbc;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b varchar(10),
+index kb(b(5))
+) ENGINE = ROCKSDB charset utf8 collate utf8_bin;
+INSERT INTO t1 (a,b) VALUES (1,'1111122222');
+INSERT INTO t1 (a,b) VALUES (2,'2222233333');
+INSERT INTO t1 (a,b) VALUES (3,'3333344444');
+ALTER TABLE t1 DROP INDEX kb, ADD INDEX kb(b(8)), ALGORITHM=INPLACE;
+SELECT * FROM t1 FORCE INDEX(kb);
+a b
+1 1111122222
+2 2222233333
+3 3333344444
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` varchar(10) COLLATE utf8_bin DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kb` (`b`(8))
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
+DROP TABLE t1;
+SET @prior_rocksdb_table_stats_sampling_pct = @@rocksdb_table_stats_sampling_pct;
+set global rocksdb_table_stats_sampling_pct = 100;
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+Warnings:
+Warning 1280 Name 'ka' ignored for PRIMARY key.
+INSERT INTO t1 (a, b) VALUES (1, 10);
+INSERT INTO t1 (a, b) VALUES (2, 10);
+INSERT INTO t1 (a, b) VALUES (3, 20);
+INSERT INTO t1 (a, b) VALUES (4, 20);
+set global rocksdb_force_flush_memtable_now=1;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+SHOW INDEX in t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 4 NULL NULL LSMTREE
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+SHOW INDEX in t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 4 NULL NULL LSMTREE
+t1 1 kb 1 b A 2 NULL NULL YES LSMTREE
+DROP TABLE t1;
+SET global rocksdb_table_stats_sampling_pct = @prior_rocksdb_table_stats_sampling_pct;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_cardinality.result b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_cardinality.result
new file mode 100644
index 00000000..61105fa1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_cardinality.result
@@ -0,0 +1,24 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (i INT PRIMARY KEY, j INT) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1,2), (2,4), (3,6), (4,8), (5,10);
+SET debug_sync= 'rocksdb.commit_in_place_alter_table WAIT_FOR flushed';
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+connect con1,localhost,root,,;
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+SET debug_sync= 'now SIGNAL flushed';
+connection default;
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+(SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+WHERE TABLE_NAME = 't1' AND INDEX_NAME = "PRIMARY");
+COLUMN_FAMILY INDEX_NUMBER SST_NAME NUM_ROWS DATA_SIZE ENTRY_DELETES ENTRY_SINGLEDELETES ENTRY_MERGES ENTRY_OTHERS DISTINCT_KEYS_PREFIX
+# # SSTNAME 5 # # # # # 5
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+(SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+WHERE TABLE_NAME = 't1' AND INDEX_NAME = "kj");
+COLUMN_FAMILY INDEX_NUMBER SST_NAME NUM_ROWS DATA_SIZE ENTRY_DELETES ENTRY_SINGLEDELETES ENTRY_MERGES ENTRY_OTHERS DISTINCT_KEYS_PREFIX
+# # SSTNAME 5 # # # # # 5,5
+disconnect con1;
+SET debug_sync='RESET';
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_crash.result b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_crash.result
new file mode 100644
index 00000000..d3801258
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_crash.result
@@ -0,0 +1,93 @@
+drop table if exists t1;
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+# crash_during_online_index_creation
+flush logs;
+SET SESSION debug_dbug="+d,crash_during_online_index_creation";
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+ERROR HY000: Lost connection to MySQL server during query
+SET SESSION debug_dbug="-d,crash_during_online_index_creation";
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ KEY `ka` (`a`),
+ KEY `kab` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+DROP TABLE t1;
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+# crash_during_index_creation_partition
+flush logs;
+SET SESSION debug_dbug="+d,crash_during_index_creation_partition";
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+ERROR HY000: Lost connection to MySQL server during query
+SET SESSION debug_dbug="-d,crash_during_index_creation_partition";
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ `k` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `j` (`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY KEY (`i`)
+PARTITIONS 4
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+i j k
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+100
+DROP TABLE t1;
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+# crash_during_index_creation_partition
+flush logs;
+SET SESSION debug_dbug="+d,myrocks_simulate_index_create_rollback";
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+ERROR HY000: Intentional failure in inplace alter occurred.
+SET SESSION debug_dbug="-d,myrocks_simulate_index_create_rollback";
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ `k` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `j` (`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY KEY (`i`)
+PARTITIONS 4
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ `k` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `j` (`j`),
+ KEY `kij` (`i`,`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY KEY (`i`)
+PARTITIONS 4
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+100
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_sstfilewriter.result b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_sstfilewriter.result
new file mode 100644
index 00000000..e7883f7e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/add_index_inplace_sstfilewriter.result
@@ -0,0 +1,79 @@
+drop table if exists t1;
+CREATE TABLE t1(pk CHAR(5) PRIMARY KEY, a char(30), b char(30)) COLLATE 'latin1_bin';
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=10000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+set rocksdb_bulk_load=0;
+select count(pk) from t1;
+count(pk)
+300000
+select count(a) from t1;
+count(a)
+300000
+select count(b) from t1;
+count(b)
+300000
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+set @tmp= @@rocksdb_max_row_locks;
+set session rocksdb_max_row_locks=1000;
+ALTER TABLE t1 ADD INDEX kb_copy(b), ALGORITHM=COPY;
+ERROR HY000: Got error 10 'Operation aborted: Failed to acquire lock due to rocksdb_max_row_locks limit' from ROCKSDB
+set session rocksdb_bulk_load=1;
+ALTER TABLE t1 ADD INDEX kb_copy(b), ALGORITHM=COPY;
+set session rocksdb_bulk_load=0;
+set session rocksdb_max_row_locks=@tmp;
+SELECT COUNT(*) as c FROM
+(SELECT COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `b`, CONCAT(ISNULL(`b`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `t1` FORCE INDEX(`kb`)
+UNION DISTINCT
+SELECT COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#',
+`b`, CONCAT(ISNULL(`b`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `t1` FORCE
+INDEX(`kb_copy`)) as temp;
+c
+1
+select count(*) from t1 FORCE INDEX(kb);
+count(*)
+300000
+select count(*) from t1 FORCE INDEX(kb_copy);
+count(*)
+300000
+select count(*) from t1 FORCE INDEX(PRIMARY);
+count(*)
+300000
+ALTER TABLE t1 DROP INDEX kb, ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX kb_copy, ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kb(b), ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kab);
+COUNT(*)
+300000
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+COUNT(*)
+300000
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` char(5) COLLATE latin1_bin NOT NULL,
+ `a` char(30) COLLATE latin1_bin DEFAULT NULL,
+ `b` char(30) COLLATE latin1_bin DEFAULT NULL,
+ PRIMARY KEY (`pk`),
+ KEY `kb` (`b`),
+ KEY `kab` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT, KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 DROP INDEX kab, ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kb(b) comment 'rev:cf1', ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kb` (`b`) COMMENT 'rev:cf1'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+COUNT(*)
+3
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/add_unique_index_inplace.result b/storage/rocksdb/mysql-test/rocksdb/r/add_unique_index_inplace.result
new file mode 100644
index 00000000..e998cfb4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/add_unique_index_inplace.result
@@ -0,0 +1,111 @@
+drop table if exists t1;
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+Warnings:
+Warning 1280 Name 'ka' ignored for PRIMARY key.
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+INSERT INTO t1 (a,b) VALUES (4,5);
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+ERROR 23000: Duplicate entry '5' for key 'kb'
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+Warnings:
+Warning 1280 Name 'ka' ignored for PRIMARY key.
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+INSERT INTO t1 (a,b) VALUES (4,5);
+ERROR 23000: Duplicate entry '5' for key 'kb'
+INSERT INTO t1 (a,b) VALUES (5,8);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ UNIQUE KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT * FROM t1 FORCE INDEX(kb);
+a b
+1 5
+2 6
+3 7
+5 8
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+Warnings:
+Warning 1280 Name 'ka' ignored for PRIMARY key.
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, NULL);
+INSERT INTO t1 (a, b) VALUES (3, NULL);
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+INSERT INTO t1 (a, b) VALUES (4, NULL);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ UNIQUE KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+COUNT(*)
+4
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+Warnings:
+Warning 1280 Name 'ka' ignored for PRIMARY key.
+INSERT INTO t1 (a,b,c) VALUES (1,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (2,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (3,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (4,1,5);
+ALTER TABLE t1 ADD UNIQUE INDEX kbc(b,c), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ UNIQUE KEY `kbc` (`b`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kbc);
+COUNT(*)
+4
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b);
+INSERT INTO t1 (a, b) VALUES (4, 8);
+INSERT INTO t1 (a, b) VALUES (5, 5);
+ERROR 23000: Duplicate entry '5' for key 'kb'
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ UNIQUE KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+c INT,
+KEY kbc(b,c)) ENGINE = ROCKSDB;
+INSERT INTO t1 (a,b,c) VALUES (1,1,1);
+INSERT INTO t1 (a,b,c) VALUES (2,2,2);
+INSERT INTO t1 (a,b,c) VALUES (3,2,2);
+ALTER TABLE t1 DROP INDEX kbc, ADD UNIQUE INDEX kbc(b,c), ALGORITHM=INPLACE;
+ERROR 23000: Duplicate entry '2-2' for key 'kbc'
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/allow_no_pk_concurrent_insert.result b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_pk_concurrent_insert.result
new file mode 100644
index 00000000..4fef9bce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_pk_concurrent_insert.result
@@ -0,0 +1,7 @@
+drop table if exists t1;
+# Binary must be compiled with debug for this test
+CREATE TABLE t1 (a INT) ENGINE=rocksdb;
+SELECT COUNT(*) from t1;
+COUNT(*)
+400
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key.result b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key.result
new file mode 100644
index 00000000..96d25b2e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key.result
@@ -0,0 +1,298 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, c CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,'a'),(5,'z');
+ALTER TABLE t1 ADD COLUMN b INT;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT * FROM t1;
+a c b
+1 a NULL
+5 z NULL
+ALTER TABLE t1 DROP COLUMN b;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `c` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT * FROM t1;
+a c
+1 a
+5 z
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+ALTER TABLE t1 DROP COLUMN pk;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT, b CHAR(8)) ENGINE=rocksdb;
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+CHECK TABLE t1, t2 FOR UPGRADE;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+test.t2 check status OK
+INSERT INTO t2 (a,b) VALUES (5,'e');
+CHECK TABLE t2 QUICK;
+Table Op Msg_type Msg_text
+test.t2 check status OK
+INSERT INTO t1 (a,b) VALUES (6,'f');
+CHECK TABLE t1 FAST;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (7,'g');
+INSERT INTO t2 (a,b) VALUES (8,'h');
+CHECK TABLE t2, t1 MEDIUM;
+Table Op Msg_type Msg_text
+test.t2 check status OK
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (9,'i');
+INSERT INTO t2 (a,b) VALUES (10,'j');
+CHECK TABLE t1, t2 EXTENDED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+test.t2 check status OK
+INSERT INTO t1 (a,b) VALUES (11,'k');
+CHECK TABLE t1 CHANGED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+INSERT INTO t1 (a,b) VALUES (1,'c');
+ERROR 23000: Duplicate entry '1' for key 'a'
+SELECT * FROM t1;
+a b
+1 a
+2 b
+SELECT * FROM t1 WHERE a = 2;
+a b
+2 b
+EXPLAIN SELECT * FROM t1 WHERE a = 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 const a a 5 const 1
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+# restart
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (36,'foo');
+DELETE FROM t1 WHERE a = 35 AND b = 'foo';
+SELECT * FROM t1;
+a b
+36 foo
+DROP TABLE t1;
+#
+# Issue #834/MDEV-15304 ALTER TABLE table_with_hidden_pk causes Can't
+# write; duplicate key in table error and/or crash
+#
+CREATE TABLE t1 (a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1),(1+1);
+create table t2 (a int);
+insert into t2 values (10),(20),(30);
+BEGIN;
+select * from t2;
+a
+10
+20
+30
+connect con1,localhost,root,,;
+connection con1;
+alter table t1 force;
+connection default;
+select * from t1;
+a
+connection con1;
+insert into t1 values (100);
+select * from t1;
+a
+1
+2
+100
+disconnect con1;
+connection default;
+rollback;
+drop table t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key_with_sk.result b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key_with_sk.result
new file mode 100644
index 00000000..27722b23
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/allow_no_primary_key_with_sk.result
@@ -0,0 +1,806 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `a` (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+ALTER TABLE t1 ADD INDEX (b);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `b` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES MUL NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+ALTER TABLE t1 DROP INDEX b;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+ALTER TABLE t1 DROP COLUMN pk;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+#
+# MDEV-4313: RocksDB: Server crashes in Rdb_key_def::setup on dropping the primary key column
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT NOT NULL, KEY(i)) ENGINE=RocksDB;
+ALTER TABLE t1 DROP COLUMN `pk`;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a), KEY(b)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `a` (`a`),
+ KEY `b` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b char(8) YES MUL NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a, b)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `a` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b char(8) YES NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a), KEY(b)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `a` (`a`),
+ KEY `b` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b char(8) YES MUL NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a) VALUES (1),(2),(5);
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (6),(8),(12);
+CHECK TABLE t1 FOR UPGRADE;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (13),(15),(16);
+CHECK TABLE t1 QUICK;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (17),(120),(132);
+CHECK TABLE t1 FAST;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (801),(900),(7714);
+CHECK TABLE t1 MEDIUM;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (8760),(10023),(12000);
+CHECK TABLE t1 EXTENDED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (13345),(24456),(78302),(143028);
+CHECK TABLE t1 CHANGED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, c INT, d INT, KEY kab(a, b), KEY kbc(b, c), KEY kabc(a,b,c)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ `d` int(11) DEFAULT NULL,
+ KEY `kab` (`a`,`b`),
+ KEY `kbc` (`b`,`c`),
+ KEY `kabc` (`a`,`b`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b int(11) YES MUL NULL
+c int(11) YES NULL
+d int(11) YES NULL
+INSERT INTO t1 (a,b,c,d) VALUES (1,2,3,4);
+INSERT INTO t1 (a,b,c,d) VALUES (5,6,7,8);
+INSERT INTO t1 (a,b,c,d) VALUES (10,11,12,13);
+INSERT INTO t1 (a,b,c,d) VALUES (14,15,16,17);
+SELECT * FROM t1;
+a b c d
+1 2 3 4
+10 11 12 13
+14 15 16 17
+5 6 7 8
+SELECT * FROM t1 WHERE a = 1 OR a = 10;
+a b c d
+1 2 3 4
+10 11 12 13
+SELECT * FROM t1 WHERE c = 3 OR d = 17;
+a b c d
+1 2 3 4
+14 15 16 17
+SELECT * FROM t1 WHERE a > 5 OR d > 5;
+a b c d
+10 11 12 13
+14 15 16 17
+5 6 7 8
+SELECT a, b, c FROM t1 FORCE INDEX (kabc) WHERE a=1 OR b=11;
+a b c
+1 2 3
+10 11 12
+SELECT d FROM t1 FORCE INDEX (kbc) WHERE b > 6 AND c > 12;
+d
+17
+UPDATE t1 SET a=a+100;
+UPDATE t1 SET a=a-100, b=99 WHERE a>100;
+SELECT * FROM t1;
+a b c d
+1 99 3 4
+10 99 12 13
+14 99 16 17
+5 99 7 8
+DELETE FROM t1 WHERE a>5;
+DELETE FROM t1 WHERE b=99 AND d>4;
+SELECT * FROM t1;
+a b c d
+1 99 3 4
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), KEY ka(a) comment 'rev:cf1', KEY kb(b)
+comment 'rev:cf1', KEY kab(a,b) comment 'rev:cf2') ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ KEY `ka` (`a`) COMMENT 'rev:cf1',
+ KEY `kb` (`b`) COMMENT 'rev:cf1',
+ KEY `kab` (`a`,`b`) COMMENT 'rev:cf2'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) YES MUL NULL
+b char(8) YES MUL NULL
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SELECT * FROM t1 WHERE a = 35;
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+a b
+35 foo
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+a b
+76 bar
+77 baz
+SELECT * FROM t1 WHERE a > 35;
+a b
+76 bar
+77 baz
+SELECT * FROM t1;
+a b
+35 foo
+76 bar
+77 baz
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+135 foo
+176 bar
+177 baz
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+SELECT * FROM t1;
+a b
+35 bbb
+76 bbb
+77 bbb
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+SELECT * FROM t1;
+a b
+300 ccc
+300 ccc
+35 bbb
+UPDATE t1 SET a=123 WHERE a=35;
+SELECT * FROM t1;
+a b
+123 bbb
+300 ccc
+300 ccc
+UPDATE t1 SET a=321 WHERE b='ccc';
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+# restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+SELECT * FROM t1;
+a b
+123 bbb
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE a=123;
+SELECT * FROM t1;
+a b
+321 ccc
+321 ccc
+45 bob
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+SELECT * FROM t1;
+a b
+45 bob
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+CREATE TABLE t1 (col1 int, col2 int, KEY kcol1(col1)) ENGINE=ROCKSDB;
+INSERT INTO t1 (col1, col2) values (2,2);
+ALTER TABLE t1 ADD COLUMN extra INT;
+UPDATE t1 SET col2 = 1;
+select * from t1;
+col1 col2 extra
+2 1 NULL
+DELETE FROM t1 WHERE col1 = 2;
+set global rocksdb_force_flush_memtable_now = true;
+select * from t1;
+col1 col2 extra
+DROP TABLE t1;
+create table t1 (i int auto_increment, key(i)) engine=rocksdb;
+insert into t1 values();
+insert into t1 values();
+insert into t1 values();
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL AUTO_INCREMENT,
+ KEY `i` (`i`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+# restart
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL AUTO_INCREMENT,
+ KEY `i` (`i`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/allow_to_start_after_corruption.result b/storage/rocksdb/mysql-test/rocksdb/r/allow_to_start_after_corruption.result
new file mode 100644
index 00000000..9b5a335b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/allow_to_start_after_corruption.result
@@ -0,0 +1,38 @@
+#
+# Test how MyRocks behaves when RocksDB reports corrupted data.
+#
+#
+# Test server crashes on corrupted data and restarts
+#
+create table t1 (
+pk int not null primary key,
+col1 varchar(10)
+) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3);
+select * from t1 where pk=1;
+pk col1
+1 1
+set session debug_dbug= "+d,rocksdb_return_status_corrupted";
+select * from t1 where pk=1;
+ERROR HY000: Lost connection to MySQL server during query
+FOUND 1 /data corruption detected/ in allow_to_start_after_corruption_debug.err
+#
+# The same for scan queries
+#
+select * from t1;
+pk col1
+1 1
+2 2
+3 3
+set session debug_dbug= "+d,rocksdb_return_status_corrupted";
+select * from t1;
+ERROR HY000: Lost connection to MySQL server during query
+FOUND 1 /data corruption detected/ in allow_to_start_after_corruption_debug.err
+#
+# Test restart failure. The server is shutdown at this point.
+#
+FOUND 1 /The server will exit normally and stop restart attempts/ in allow_to_start_after_corruption_debug.err
+#
+# Remove corruption file and restart cleanly
+#
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/alter_table.result b/storage/rocksdb/mysql-test/rocksdb/r/alter_table.result
new file mode 100644
index 00000000..a4e00626
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/alter_table.result
@@ -0,0 +1,183 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, c CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,'a'),(2,5,'z');
+ALTER TABLE t1 ADD COLUMN b INT;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 ALTER COLUMN a SET DEFAULT '0';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT 0,
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 ALTER a DROP DEFAULT;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 CHANGE COLUMN b b1 CHAR(8) FIRST;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `b1` char(8) DEFAULT NULL,
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 CHANGE b1 b INT AFTER c;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 CHANGE b b CHAR(8);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 MODIFY COLUMN b INT;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 MODIFY COLUMN b CHAR(8) FIRST;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `b` char(8) DEFAULT NULL,
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 MODIFY COLUMN b INT AFTER a;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `b` int(11) DEFAULT NULL,
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 DROP COLUMN b;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 RENAME TO t2;
+SHOW CREATE TABLE t1;
+ERROR 42S02: Table 'test.t1' doesn't exist
+SHOW CREATE TABLE t2;
+Table Create Table
+t2 CREATE TABLE `t2` (
+ `pk` int(11) NOT NULL,
+ `a` int(11),
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t2;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,5),(2,2,2),(3,4,3);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 ORDER BY b ASC, a DESC, pk DESC;
+Warnings:
+Warning 1105 ORDER BY ignored as there is a user-defined clustered index in the table 't1'
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT * FROM t1;
+pk a b
+1 1 5
+2 2 2
+3 4 3
+DROP TABLE t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b CHAR(8), c CHAR(8)) ENGINE=rocksdb CHARACTER SET latin1 COLLATE latin1_general_cs;
+INSERT INTO t1 VALUES (1,5,'z','t');
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) COLLATE latin1_general_cs DEFAULT NULL,
+ `c` char(8) COLLATE latin1_general_cs DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_cs
+ALTER TABLE t1 CONVERT TO CHARACTER SET utf8;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ `c` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8
+ALTER TABLE t1 DEFAULT CHARACTER SET = latin1 COLLATE latin1_general_ci;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) CHARACTER SET utf8 DEFAULT NULL,
+ `c` char(8) CHARACTER SET utf8 DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
+ALTER TABLE t1 FORCE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) CHARACTER SET utf8 DEFAULT NULL,
+ `c` char(8) CHARACTER SET utf8 DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/analyze_table.result b/storage/rocksdb/mysql-test/rocksdb/r/analyze_table.result
new file mode 100644
index 00000000..3459b6f1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/analyze_table.result
@@ -0,0 +1,61 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT(11), b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,'a'),(2,2,'b');
+CREATE TABLE t2 (pk INT PRIMARY KEY, a INT(11), b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (3,3,'c');
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+INSERT INTO t2 VALUES (1,4,'d');
+ANALYZE NO_WRITE_TO_BINLOG TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+INSERT INTO t1 VALUES (4,5,'e');
+INSERT INTO t2 VALUES (2,6,'f');
+ANALYZE LOCAL TABLE t1, t2;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT(11), KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1),(2,2),(3,4),(4,7);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+INSERT INTO t1 VALUES (5,8),(6,10),(7,11),(8,12);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+DROP TABLE t1;
+#
+# MDEV-12465: Server crashes in my_scan_weight_utf8_bin upon
+# collecting stats for RocksDB table
+#
+CREATE TABLE t1 (
+pk INT,
+f1 CHAR(255),
+f2 TEXT,
+f3 VARCHAR(255),
+f4 TEXT,
+PRIMARY KEY (pk),
+KEY (f4(255))
+) ENGINE=RocksDB
+CHARSET utf8
+COLLATE utf8_bin
+PARTITION BY KEY (pk) PARTITIONS 2;
+INSERT INTO t1 VALUES
+(1,'foo','bar','foo','bar'), (2,'bar','foo','bar','foo');
+ANALYZE TABLE t1 PERSISTENT FOR ALL;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze Warning Engine-independent statistics are not collected for column 'f2'
+test.t1 analyze Warning Engine-independent statistics are not collected for column 'f4'
+test.t1 analyze status OK
+drop table t1;
+# End of 10.2 tests
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/apply_changes_iter.result b/storage/rocksdb/mysql-test/rocksdb/r/apply_changes_iter.result
new file mode 100644
index 00000000..a5d81031
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/apply_changes_iter.result
@@ -0,0 +1,64 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t1 (
+pk INT NOT NULL PRIMARY KEY,
+key1 INT NOT NULL,
+KEY (key1)
+) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES (12,12);
+INSERT INTO t1 VALUES (6,6);
+BEGIN;
+INSERT INTO t1 VALUES (8,8), (10,10);
+SELECT * FROM t1 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 DESC;
+pk key1
+10 10
+8 8
+6 6
+SELECT * FROM t1 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 ASC;
+pk key1
+6 6
+8 8
+10 10
+SELECT * FROM t1 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 DESC;
+pk key1
+10 10
+8 8
+6 6
+SELECT * FROM t1 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 ASC;
+pk key1
+6 6
+8 8
+10 10
+ROLLBACK;
+CREATE TABLE t2 (
+pk INT NOT NULL PRIMARY KEY,
+key1 INT NOT NULL,
+KEY (key1) COMMENT 'rev:cf'
+) ENGINE=ROCKSDB;
+INSERT INTO t2 VALUES (12,12);
+INSERT INTO t2 VALUES (6,6);
+BEGIN;
+INSERT INTO t2 VALUES (8,8), (10,10);
+SELECT * FROM t2 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 DESC;
+pk key1
+10 10
+8 8
+6 6
+SELECT * FROM t2 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 ASC;
+pk key1
+6 6
+8 8
+10 10
+SELECT * FROM t2 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 DESC;
+pk key1
+10 10
+8 8
+6 6
+SELECT * FROM t2 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 ASC;
+pk key1
+6 6
+8 8
+10 10
+ROLLBACK;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result
new file mode 100644
index 00000000..60395ece
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result
@@ -0,0 +1,132 @@
+include/master-slave.inc
+[connection master]
+create table t (i int primary key auto_increment) engine=rocksdb;
+#
+# Testing concurrent transactions.
+#
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connect con3,localhost,root,,;
+connection con1;
+begin;
+insert into t values ();
+connection con2;
+begin;
+insert into t values ();
+connection con3;
+begin;
+insert into t values ();
+connection con1;
+insert into t values ();
+connection con2;
+insert into t values ();
+connection con3;
+insert into t values ();
+connection con2;
+commit;
+connection con3;
+rollback;
+connection con1;
+commit;
+delete from t;
+# Master value before restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 7
+# Slave value before restart
+connection slave;
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+connection slave;
+include/stop_slave.inc
+include/rpl_restart_server.inc [server_number=1]
+connection default;
+# Master value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+include/rpl_restart_server.inc [server_number=2]
+connection slave;
+include/start_slave.inc
+# Slave value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+disconnect con1;
+disconnect con2;
+disconnect con3;
+#
+# Testing interaction of merge markers with various DDL statements.
+#
+connection slave;
+include/stop_slave.inc
+connection default;
+# Drop and add primary key.
+alter table t modify i int;
+alter table t drop primary key;
+alter table t add primary key (i);
+alter table t modify i int auto_increment;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+# Remove auto_increment property.
+alter table t modify i int;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t NULL
+# Add auto_increment property.
+insert into t values (123);
+alter table t modify i int auto_increment;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Add column j.
+alter table t add column j int;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Rename tables.
+rename table t to t2;
+rename table t2 to t;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Change auto_increment property
+alter table t auto_increment = 1000;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 1000
+alter table t auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t drop primary key, add key (i), auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t add key (j), auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t modify i int;
+alter table t add column (k int auto_increment), add key(k), auto_increment=15;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 16
+# Drop table.
+drop table t;
+include/rpl_restart_server.inc [server_number=1]
+connection slave;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result
new file mode 100644
index 00000000..c837fb7c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result
@@ -0,0 +1,132 @@
+include/master-slave.inc
+[connection master]
+create table t (i int primary key auto_increment) engine=rocksdb partition by key (i) partitions 3;
+#
+# Testing concurrent transactions.
+#
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connect con3,localhost,root,,;
+connection con1;
+begin;
+insert into t values ();
+connection con2;
+begin;
+insert into t values ();
+connection con3;
+begin;
+insert into t values ();
+connection con1;
+insert into t values ();
+connection con2;
+insert into t values ();
+connection con3;
+insert into t values ();
+connection con2;
+commit;
+connection con3;
+rollback;
+connection con1;
+commit;
+delete from t;
+# Master value before restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 7
+# Slave value before restart
+connection slave;
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+connection slave;
+include/stop_slave.inc
+include/rpl_restart_server.inc [server_number=1]
+connection default;
+# Master value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+include/rpl_restart_server.inc [server_number=2]
+connection slave;
+include/start_slave.inc
+# Slave value after restart
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 6
+disconnect con1;
+disconnect con2;
+disconnect con3;
+#
+# Testing interaction of merge markers with various DDL statements.
+#
+connection slave;
+include/stop_slave.inc
+connection default;
+# Drop and add primary key.
+alter table t modify i int;
+alter table t drop primary key;
+alter table t add primary key (i);
+alter table t modify i int auto_increment;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 1
+# Remove auto_increment property.
+alter table t modify i int;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t NULL
+# Add auto_increment property.
+insert into t values (123);
+alter table t modify i int auto_increment;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Add column j.
+alter table t add column j int;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Rename tables.
+rename table t to t2;
+rename table t2 to t;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+# Change auto_increment property
+alter table t auto_increment = 1000;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 1000
+alter table t auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t drop primary key, add key (i), auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t add key (j), auto_increment = 1;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 124
+alter table t modify i int;
+alter table t add column (k int auto_increment), add key(k), auto_increment=15;
+include/rpl_restart_server.inc [server_number=1]
+select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment
+test t 16
+# Drop table.
+drop table t;
+include/rpl_restart_server.inc [server_number=1]
+connection slave;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_debug.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_debug.result
new file mode 100644
index 00000000..604e5572
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_debug.result
@@ -0,0 +1,99 @@
+#
+# Testing upgrading from server without merges for auto_increment
+# to new server with such support.
+#
+set debug_dbug='+d,myrocks_autoinc_upgrade';
+create table t (i int primary key auto_increment);
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+i
+1
+2
+3
+delete from t where i > 1;
+select * from t;
+i
+1
+select table_name, index_name, auto_increment
+from information_schema.rocksdb_ddl where table_name = 't';
+table_name index_name auto_increment
+t PRIMARY NULL
+set debug_dbug='-d,myrocks_autoinc_upgrade';
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+i
+1
+2
+3
+4
+select table_name, index_name, auto_increment
+from information_schema.rocksdb_ddl where table_name = 't';
+table_name index_name auto_increment
+t PRIMARY 5
+delete from t where i > 1;
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+i
+1
+5
+6
+7
+drop table t;
+#
+# Testing crash safety of transactions.
+#
+create table t (i int primary key auto_increment);
+insert into t values ();
+insert into t values ();
+insert into t values ();
+# Before anything
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_before";
+commit;
+ERROR HY000: Lost connection to MySQL server during query
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment > @row_max
+test t 1
+# After engine prepare
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after_prepare";
+commit;
+ERROR HY000: Lost connection to MySQL server during query
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment > @row_max
+test t 1
+# After binlog
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after_log";
+commit;
+ERROR HY000: Lost connection to MySQL server during query
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment > @row_max
+test t 1
+# After everything
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after";
+commit;
+ERROR HY000: Lost connection to MySQL server during query
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+table_schema table_name auto_increment > @row_max
+test t 1
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_secondary.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_secondary.result
new file mode 100644
index 00000000..c6dcb023
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_secondary.result
@@ -0,0 +1,17 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT AUTO_INCREMENT, KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (pk) VALUES (3), (2), (1);
+SELECT * FROM t1;
+pk a
+3 1
+2 2
+1 3
+# restart
+INSERT INTO t1 (pk) VALUES (4);
+SELECT * FROM t1;
+pk a
+3 1
+2 2
+1 3
+4 4
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result
new file mode 100644
index 00000000..cc47ceff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result
@@ -0,0 +1,199 @@
+DROP TABLE IF EXISTS t1;
+#---------------------------
+# auto_increment_offset
+#---------------------------
+SET auto_increment_offset = 200;
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (NULL,'a'),(NULL,'b'),(NULL,'c');
+SELECT LAST_INSERT_ID();
+LAST_INSERT_ID()
+1
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 a
+2 b
+3 c
+#---------------------------
+# auto_increment_increment
+#---------------------------
+SET auto_increment_increment = 300;
+INSERT INTO t1 (a,b) VALUES (NULL,'d'),(NULL,'e'),(NULL,'f');
+SELECT LAST_INSERT_ID();
+LAST_INSERT_ID()
+200
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 a
+2 b
+3 c
+200 d
+500 e
+800 f
+SET auto_increment_increment = 50;
+INSERT INTO t1 (a,b) VALUES (NULL,'g'),(NULL,'h'),(NULL,'i');
+SELECT LAST_INSERT_ID();
+LAST_INSERT_ID()
+850
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 a
+2 b
+3 c
+200 d
+500 e
+800 f
+850 g
+900 h
+950 i
+DROP TABLE t1;
+#---------------------------
+# offset is greater than the max value
+#---------------------------
+SET auto_increment_increment = 500;
+SET auto_increment_offset = 300;
+CREATE TABLE t1 (a TINYINT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+# In MariaDB, this is an error:
+INSERT INTO t1 (a) VALUES (NULL);
+ERROR 22003: Out of range value for column 'a' at row 1
+SELECT LAST_INSERT_ID();
+LAST_INSERT_ID()
+850
+SELECT a FROM t1 ORDER BY a;
+a
+DROP TABLE t1;
+#---------------------------
+# test large autoincrement values
+#---------------------------
+SET auto_increment_increment = 1;
+SET auto_increment_offset = 1;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551614 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'b');
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551615 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'c');
+ERROR HY000: Failed to read auto-increment value from storage engine
+SELECT * FROM t1;
+a b
+18446744073709551613 a
+18446744073709551614 b
+DROP TABLE t1;
+SET auto_increment_increment = 300;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551614 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'b');
+ERROR HY000: Failed to read auto-increment value from storage engine
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551615 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'c');
+ERROR HY000: Failed to read auto-increment value from storage engine
+SELECT * FROM t1;
+a b
+18446744073709551613 a
+DROP TABLE t1;
+SET auto_increment_offset = 200;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551614 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'b');
+ERROR HY000: Failed to read auto-increment value from storage engine
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551615 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL, 'c');
+ERROR HY000: Failed to read auto-increment value from storage engine
+SELECT * FROM t1;
+a b
+18446744073709551613 a
+DROP TABLE t1;
+#----------------------------------
+# Issue #792 Crash in autoincrement
+#----------------------------------
+CREATE TABLE t1(C1 DOUBLE AUTO_INCREMENT KEY,C2 CHAR) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(2177,0);
+DROP TABLE t1;
+CREATE TABLE t0(c0 BLOB) ENGINE=ROCKSDB;
+INSERT INTO t0 VALUES(0);
+ALTER TABLE t0 AUTO_INCREMENT=0;
+DROP TABLE t0;
+#---------------------------------------------------------------
+# MDEV-16703 Assertion failed in load_auto_incr_value_from_index
+#---------------------------------------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (1);
+UPDATE t1 SET pk = 3;
+ALTER TABLE t1 AUTO_INCREMENT 2;
+DROP TABLE t1;
+#----------------------------------
+# Issue #792 Crash in autoincrement
+#----------------------------------
+CREATE TABLE t1(C1 DOUBLE AUTO_INCREMENT KEY,C2 CHAR) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(2177,0);
+DROP TABLE t1;
+CREATE TABLE t0(c0 BLOB) ENGINE=ROCKSDB;
+INSERT INTO t0 VALUES(0);
+ALTER TABLE t0 AUTO_INCREMENT=0;
+DROP TABLE t0;
+#----------------------------------
+# Issue #869 Crash in autoincrement
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (1);
+UPDATE t1 SET pk = 3;
+ALTER TABLE t1 AUTO_INCREMENT 2;
+DROP TABLE t1;
+#----------------------------------
+# Issue #902 Debug assert in autoincrement with small field type
+#----------------------------------
+SET auto_increment_increment=100, auto_increment_offset=10;
+CREATE TABLE t1(i INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551615;
+INSERT INTO t1 VALUES (NULL);
+ERROR HY000: Failed to read auto-increment value from storage engine
+SELECT * FROM t1;
+i
+ALTER TABLE t1 AUTO_INCREMENT=1;
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+i
+10
+ALTER TABLE t1 AUTO_INCREMENT=18446744073709551615;
+INSERT INTO t1 VALUES (NULL);
+ERROR HY000: Failed to read auto-increment value from storage engine
+SELECT * FROM t1;
+i
+10
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread.result
new file mode 100644
index 00000000..9331b4e2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread.result
@@ -0,0 +1,39 @@
+#---------------------------
+# two threads inserting simultaneously with increment > 1
+# Issue #390
+#---------------------------
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+connect con1, localhost, root,,;
+SET auto_increment_increment = 2;
+SET auto_increment_offset = 1;
+INSERT INTO t1 VALUES(NULL);
+connect con2, localhost, root,,;
+SET auto_increment_increment = 2;
+SET auto_increment_offset = 1;
+connect con3, localhost, root,,;
+connection con1;
+SET debug_sync='rocksdb.autoinc_vars2 SIGNAL go2';
+SET debug_sync='rocksdb.autoinc_vars SIGNAL parked1 WAIT_FOR go1';
+INSERT INTO t1 VALUES(NULL);
+connection default;
+SET debug_sync='now WAIT_FOR parked1';
+connection con2;
+SET debug_sync='rocksdb.autoinc_vars SIGNAL parked2 WAIT_FOR go2';
+INSERT INTO t1 VALUES(NULL);
+connection default;
+SET debug_sync='now WAIT_FOR parked2';
+SET debug_sync='now SIGNAL go1';
+connection con3;
+connection default;
+connection con1;
+connection con2;
+connection default;
+SET debug_sync='RESET';
+disconnect con1;
+disconnect con2;
+SELECT * FROM t1;
+a
+1
+3
+5
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread_2.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread_2.result
new file mode 100644
index 00000000..6bd6cea9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars_thread_2.result
@@ -0,0 +1,98 @@
+#---------------------------
+# ten threads inserting simultaneously with increment > 1
+# Issue #390
+#---------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, thr INT) ENGINE=rocksdb;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 9 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 8 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 7 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 6 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 5 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 4 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 3 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 2 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 1 + 1;
+connect con$i, localhost, root,,;
+SET auto_increment_increment = 100;
+SET auto_increment_offset = 0 + 1;
+connection default;
+connection con9;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con8;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con7;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con6;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con5;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con4;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con3;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con2;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con1;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection con0;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+connection default;
+connection con9;
+connection con8;
+connection con7;
+connection con6;
+connection con5;
+connection con4;
+connection con3;
+connection con2;
+connection con1;
+connection con0;
+connection default;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1000000
+SELECT thr, COUNT(pk) FROM t1 GROUP BY thr;
+thr COUNT(pk)
+0 100000
+1 100000
+2 100000
+3 100000
+4 100000
+5 100000
+6 100000
+7 100000
+8 100000
+9 100000
+disconnect con9;
+disconnect con8;
+disconnect con7;
+disconnect con6;
+disconnect con5;
+disconnect con4;
+disconnect con3;
+disconnect con2;
+disconnect con1;
+disconnect con0;
+SELECT * FROM t1 ORDER BY pk INTO OUTFILE <output_file>;
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+All pk values matched their expected values
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/binlog_rotate_crash.result b/storage/rocksdb/mysql-test/rocksdb/r/binlog_rotate_crash.result
new file mode 100644
index 00000000..1b221418
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/binlog_rotate_crash.result
@@ -0,0 +1,19 @@
+#
+# MDEV-25305: MyRocks: Killing server during RESET MASTER can lose last transactions
+#
+set global rocksdb_flush_log_at_trx_commit=1;
+create table t1 (a int, b int, key(a)) engine=rocksdb;
+insert into t1 values (1,1),(2,2);
+select * from t1;
+a b
+1 1
+2 2
+flush tables;
+set @@debug_dbug="+d,crash_after_reset_master";
+RESET MASTER;
+# Must show the inserted rows:
+select * from t1;
+a b
+1 1
+2 2
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rc.result
new file mode 100644
index 00000000..973d1876
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rc.result
@@ -0,0 +1,87 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set @save_rocksdb_blind_delete_primary_key=@@session.rocksdb_blind_delete_primary_key;
+set @save_rocksdb_master_skip_tx_api=@@session.rocksdb_master_skip_tx_api;
+DROP TABLE IF EXISTS t1,t2;
+create table t1 (id int primary key, value int, value2 varchar(200)) engine=rocksdb;
+create table t2 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+SET session rocksdb_blind_delete_primary_key=1;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+1000
+SELECT count(*) FROM t1;
+count(*)
+9000
+include/sync_slave_sql_with_master.inc
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SELECT count(*) FROM t1;
+count(*)
+9000
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+0
+SELECT count(*) FROM t2;
+count(*)
+9000
+SET session rocksdb_master_skip_tx_api=1;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+1000
+SELECT count(*) FROM t1;
+count(*)
+8000
+SELECT count(*) FROM t2;
+count(*)
+8000
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+8000
+SELECT count(*) FROM t2;
+count(*)
+8000
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+DELETE FROM t1 WHERE id BETWEEN 3001 AND 4000;
+DELETE FROM t2 WHERE id BETWEEN 3001 AND 4000;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+0
+SELECT count(*) FROM t1;
+count(*)
+7000
+SELECT count(*) FROM t2;
+count(*)
+7000
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+7000
+SELECT count(*) FROM t2;
+count(*)
+7000
+DELETE FROM t1 WHERE id = 10;
+SELECT count(*) FROM t1;
+count(*)
+7000
+call mtr.add_suppression("Slave SQL.*Could not execute Delete_rows event on table test.t1.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 't1'.*");
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @save_rocksdb_read_free_rpl=@@global.rocksdb_read_free_rpl;
+set global rocksdb_read_free_rpl=PK_SK;
+START SLAVE;
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+7000
+set global rocksdb_read_free_rpl=@save_rocksdb_read_free_rpl;
+SET session rocksdb_blind_delete_primary_key=@save_rocksdb_blind_delete_primary_key;
+SET session rocksdb_master_skip_tx_api=@save_rocksdb_master_skip_tx_api;
+DROP TABLE t1, t2;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rr.result
new file mode 100644
index 00000000..683b672e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/blind_delete_rr.result
@@ -0,0 +1,87 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set @save_rocksdb_blind_delete_primary_key=@@session.rocksdb_blind_delete_primary_key;
+set @save_rocksdb_master_skip_tx_api=@@session.rocksdb_master_skip_tx_api;
+DROP TABLE IF EXISTS t1,t2;
+create table t1 (id int primary key, value int, value2 varchar(200)) engine=rocksdb;
+create table t2 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+SET session rocksdb_blind_delete_primary_key=1;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+1000
+SELECT count(*) FROM t1;
+count(*)
+9000
+include/sync_slave_sql_with_master.inc
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SELECT count(*) FROM t1;
+count(*)
+9000
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+0
+SELECT count(*) FROM t2;
+count(*)
+9000
+SET session rocksdb_master_skip_tx_api=1;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+1000
+SELECT count(*) FROM t1;
+count(*)
+8000
+SELECT count(*) FROM t2;
+count(*)
+8000
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+8000
+SELECT count(*) FROM t2;
+count(*)
+8000
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+DELETE FROM t1 WHERE id BETWEEN 3001 AND 4000;
+DELETE FROM t2 WHERE id BETWEEN 3001 AND 4000;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+variable_value-@c
+0
+SELECT count(*) FROM t1;
+count(*)
+7000
+SELECT count(*) FROM t2;
+count(*)
+7000
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+7000
+SELECT count(*) FROM t2;
+count(*)
+7000
+DELETE FROM t1 WHERE id = 10;
+SELECT count(*) FROM t1;
+count(*)
+7000
+call mtr.add_suppression("Slave SQL.*Could not execute Delete_rows event on table test.t1.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 't1'.*");
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @save_rocksdb_read_free_rpl=@@global.rocksdb_read_free_rpl;
+set global rocksdb_read_free_rpl=PK_SK;
+START SLAVE;
+include/sync_slave_sql_with_master.inc
+SELECT count(*) FROM t1;
+count(*)
+7000
+set global rocksdb_read_free_rpl=@save_rocksdb_read_free_rpl;
+SET session rocksdb_blind_delete_primary_key=@save_rocksdb_blind_delete_primary_key;
+SET session rocksdb_master_skip_tx_api=@save_rocksdb_master_skip_tx_api;
+DROP TABLE t1, t2;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter.result
new file mode 100644
index 00000000..bc5d685f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter.result
@@ -0,0 +1,2042 @@
+CREATE PROCEDURE bloom_start()
+BEGIN
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+END//
+CREATE PROCEDURE bloom_end()
+BEGIN
+select case when variable_value-@c > 0 then 'true' else 'false' end as checked from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+END//
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) ,
+index id2 (id2) ,
+index id2_id1 (id2, id1) ,
+index id2_id3 (id2, id3) ,
+index id2_id4 (id2, id4) ,
+index id2_id3_id1_id4 (id2, id3, id1, id4) ,
+index id3_id2 (id3, id2)
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) ,
+index id2 (id2) ,
+index id2_id3 (id2, id3) ,
+index id2_id4 (id2, id4) ,
+index id2_id4_id5 (id2, id4, id5) ,
+index id3_id4 (id3, id4) ,
+index id3_id5 (id3, id5)
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'cf_short_prefix',
+index id2 (id2) COMMENT 'cf_short_prefix',
+index id2_id1 (id2, id1) COMMENT 'cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_short_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'cf_short_prefix',
+index id3_id2 (id3, id2) COMMENT 'cf_short_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'cf_short_prefix',
+index id2 (id2) COMMENT 'cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_short_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'cf_short_prefix',
+index id3_id4 (id3, id4) COMMENT 'cf_short_prefix',
+index id3_id5 (id3, id5) COMMENT 'cf_short_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+true
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'rev:cf_short_prefix',
+index id2 (id2) COMMENT 'rev:cf_short_prefix',
+index id2_id1 (id2, id1) COMMENT 'rev:cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_short_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'rev:cf_short_prefix',
+index id3_id2 (id3, id2) COMMENT 'rev:cf_short_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'rev:cf_short_prefix',
+index id2 (id2) COMMENT 'rev:cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_short_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'rev:cf_short_prefix',
+index id3_id4 (id3, id4) COMMENT 'rev:cf_short_prefix',
+index id3_id5 (id3, id5) COMMENT 'rev:cf_short_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+true
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'cf_long_prefix',
+index id2 (id2) COMMENT 'cf_long_prefix',
+index id2_id1 (id2, id1) COMMENT 'cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_long_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'cf_long_prefix',
+index id3_id2 (id3, id2) COMMENT 'cf_long_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'cf_long_prefix',
+index id2 (id2) COMMENT 'cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_long_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'cf_long_prefix',
+index id3_id4 (id3, id4) COMMENT 'cf_long_prefix',
+index id3_id5 (id3, id5) COMMENT 'cf_long_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'rev:cf_long_prefix',
+index id2 (id2) COMMENT 'rev:cf_long_prefix',
+index id2_id1 (id2, id1) COMMENT 'rev:cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_long_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'rev:cf_long_prefix',
+index id3_id2 (id3, id2) COMMENT 'rev:cf_long_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'rev:cf_long_prefix',
+index id2 (id2) COMMENT 'rev:cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_long_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'rev:cf_long_prefix',
+index id3_id4 (id3, id4) COMMENT 'rev:cf_long_prefix',
+index id3_id5 (id3, id5) COMMENT 'rev:cf_long_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+true
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create table r1 (id1 bigint, id2 bigint, id3 bigint, v1 int, v2 text, primary key (id1, id2, id3)) engine=rocksdb DEFAULT CHARSET=latin1 collate latin1_bin;
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 asc;
+id1 id2 id3 v1 v2
+1 1 1 1 1
+call bloom_end();
+checked
+true
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 desc;
+id1 id2 id3 v1 v2
+1 1 1 1 1
+call bloom_end();
+checked
+false
+DROP PROCEDURE bloom_start;
+DROP PROCEDURE bloom_end;
+truncate table t1;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+truncate table t2;
+optimize table t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status OK
+drop table if exists t1;
+drop table if exists t2;
+drop table if exists r1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter2.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter2.result
new file mode 100644
index 00000000..d5369e2d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter2.result
@@ -0,0 +1,71 @@
+CREATE TABLE t0 (id1 VARCHAR(30), id2 INT, value INT, PRIMARY KEY (id1, id2)) ENGINE=rocksdb collate latin1_bin;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t0 WHERE id1='X' AND id2>=1;
+COUNT(*)
+10000
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u = 0 then 'true' else 'false' end
+true
+DROP TABLE t0;
+CREATE TABLE t1 (id1 BIGINT, id2 INT, id3 BIGINT, value INT, PRIMARY KEY (id1, id2, id3)) ENGINE=rocksdb;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3>=2;
+COUNT(*)
+9999
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u = 0 then 'true' else 'false' end
+true
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2>=1 AND id3>=2;
+COUNT(*)
+9999
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u = 0 then 'true' else 'false' end
+true
+DROP TABLE t1;
+CREATE TABLE t2 (id1 INT, id2 VARCHAR(100), id3 BIGINT, value INT, PRIMARY KEY (id1, id2, id3)) ENGINE=rocksdb collate latin1_bin;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=100 and id2 IN ('00000000000000000000', '100');
+count(*)
+1
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u > 0 then 'true' else 'false' end
+true
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=200 and id2 IN ('00000000000000000000', '200');
+count(*)
+1
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u > 0 then 'true' else 'false' end
+true
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=200 and id2 IN ('3', '200');
+count(*)
+1
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u = 0 then 'true' else 'false' end
+true
+DROP TABLE t2;
+CREATE TABLE t3 (id1 BIGINT, id2 BIGINT, id3 BIGINT, id4 BIGINT, PRIMARY KEY (id1, id2, id3, id4)) ENGINE=rocksdb collate latin1_bin;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=5000 AND id3=1 AND id4=1;
+COUNT(*)
+0
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_useful';
+case when variable_value-@u > 0 then 'true' else 'false' end
+true
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=1 AND id3=1;
+COUNT(*)
+1
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u > 0 then 'true' else 'false' end
+true
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=1 AND id3=1 AND id4 <= 500;
+COUNT(*)
+1
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+case when variable_value-@u > 0 then 'true' else 'false' end
+true
+DROP TABLE t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter3.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter3.result
new file mode 100644
index 00000000..d447b480
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter3.result
@@ -0,0 +1,143 @@
+# restart
+CREATE TABLE `linktable` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) NOT NULL DEFAULT '',
+`time` bigint(20) unsigned NOT NULL DEFAULT '0',
+`version` int(11) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (link_type, `id1`,`id2`) COMMENT 'cf_link_pk',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'rev:cf_link_id1_type',
+KEY `id1_type2` (`id1`,`link_type`,`time`,`version`,`data`,`visibility`) COMMENT 'rev:cf_link_id1_type2',
+KEY `id1_type3` (`id1`,`visibility`,`time`,`version`,`data`,`link_type`) COMMENT 'rev:cf_link_id1_type3'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 and visibility = 1 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c > 0 then 'true' else 'false' end
+true
+# MariaDB: we don't have optimizer_force_index_for_range, but we can use EITS
+# to get the query plan we want.
+set @tmp_use_stat_tables= @@use_stat_tables;
+set use_stat_tables='preferably';
+analyze table linktable persistent for all;
+Table Op Msg_type Msg_text
+test.linktable analyze status Engine-independent statistics collected
+test.linktable analyze status OK
+flush tables;
+explain select * from linktable;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE linktable ALL NULL NULL NULL NULL 10000
+# This must use range(id1_type2), key_len=24
+explain
+select id1, id2, link_type, visibility, data, time, version from linktable
+FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 order by time desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE linktable range id1_type2 id1_type2 24 NULL 1000 Using where; Using index
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c > 0 then 'true' else 'false' end
+true
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type3`) where id1 = 100 and time >= 0 and time <= 9223372036854775807 and visibility = 1 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c = 0 then 'true' else 'false' end
+true
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and visibility = 1 and time >= 0 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c > 0 then 'true' else 'false' end
+true
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c = 0 then 'true' else 'false' end
+true
+## HA_READ_PREFIX_LAST
+# BF len 20
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and visibility = 1 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c > 0 then 'true' else 'false' end
+true
+# BF len 19
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c = 0 then 'true' else 'false' end
+true
+# BF len 12
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type3`) where id1 = 100 and visibility = 1 order by time desc;
+id1 id2 link_type visibility data time version
+100 100 1 1 100 100 100
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c = 0 then 'true' else 'false' end
+true
+DROP TABLE linktable;
+# restart
+#
+# bloom filter prefix is 20 byte
+# Create a key which is longer than that, so that we see that
+# eq_cond_len= slice.size() - 1;
+# doesnt work.
+#
+# indexnr 4
+# kp0 + 4 = 8
+# kp1 + 8 = 16
+# kp2 + 8 = 24 24>20 byte length prefix
+# kp3 + 8 = 28
+create table t1 (
+pk int primary key,
+kp0 int not null,
+kp1 bigint not null,
+kp2 bigint not null,
+kp3 bigint not null,
+key kp12(kp0, kp1, kp2, kp3) comment 'rev:x1'
+) engine=rocksdb;
+insert into t1 values (1, 1,1, 1,1);
+insert into t1 values (10,1,1,0x12FFFFFFFFFF,1);
+insert into t1 values (11,1,1,0x12FFFFFFFFFF,1);
+insert into t1 values (20,2,2,0x12FFFFFFFFFF,1);
+insert into t1 values (21,2,2,0x12FFFFFFFFFF,1);
+# restart
+explain
+select * from t1 where kp0=1 and kp1=1 and kp2=0x12FFFFFFFFFF order by kp3 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref kp12 kp12 20 const,const,const # Using where; Using index
+show status like '%rocksdb_bloom_filter_prefix%';
+Variable_name Value
+Rocksdb_bloom_filter_prefix_checked 0
+Rocksdb_bloom_filter_prefix_useful 0
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select * from t1 where kp0=1 and kp1=1 and kp2=0x12FFFFFFFFFF order by kp3 desc;
+pk kp0 kp1 kp2 kp3
+11 1 1 20890720927743 1
+10 1 1 20890720927743 1
+show status like '%rocksdb_bloom_filter_prefix%';
+Variable_name Value
+Rocksdb_bloom_filter_prefix_checked 0
+Rocksdb_bloom_filter_prefix_useful 0
+# The following MUST show TRUE:
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+case when variable_value-@c = 0 then 'true' else 'false' end
+true
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter4.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter4.result
new file mode 100644
index 00000000..c4a1c5f4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter4.result
@@ -0,0 +1,32 @@
+CREATE TABLE t1 (
+`id1` int unsigned NOT NULL DEFAULT '0',
+`id2` int unsigned NOT NULL DEFAULT '0',
+`link_type` int unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint NOT NULL DEFAULT '0',
+`data` varchar(255) NOT NULL DEFAULT '',
+`time` int unsigned NOT NULL DEFAULT '0',
+`version` int unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (id1, link_type, visibility, id2) COMMENT 'rev:cf_link_pk'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+CREATE PROCEDURE select_test()
+BEGIN
+DECLARE id1_cond INT;
+SET id1_cond = 1;
+WHILE id1_cond <= 20000 DO
+SELECT count(*) AS cnt FROM (SELECT id1 FROM t1 FORCE INDEX (PRIMARY) WHERE id1 = id1_cond AND link_type = 1 AND visibility = 1 ORDER BY id2 DESC) AS t INTO @cnt;
+IF @cnt < 1 THEN
+SELECT id1_cond, @cnt;
+END IF;
+SET id1_cond = id1_cond + 1;
+END WHILE;
+END//
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+"Skipping bloom filter"
+SET session rocksdb_skip_bloom_filter_on_read=1;
+CALL select_test();
+"Using bloom filter"
+SET session rocksdb_skip_bloom_filter_on_read=0;
+CALL select_test();
+DROP PROCEDURE select_test;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter5.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter5.result
new file mode 100644
index 00000000..daf4f5e3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter5.result
@@ -0,0 +1,85 @@
+#
+# Issue #809: Wrong query result with bloom filters
+#
+create table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'rev:bf5_1'
+) engine=ROCKSDB;
+create table t2(a int);
+insert into t2 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t3(seq int);
+insert into t3
+select
+1+ A.a + B.a* 10 + C.a * 100 + D.a * 1000
+from t2 A, t2 B, t2 C, t2 D;
+insert t1
+select
+(seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+from t3;
+set global rocksdb_force_flush_memtable_now=1;
+# Full table scan
+explain
+select * from t1 limit 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 10000
+select * from t1 limit 10;
+id1 id2 id3 id4 id5 value value2
+1000 2000 2000 10000 10000 1000 aaabbbccc
+1000 2000 2000 9999 9999 1000 aaabbbccc
+1000 2000 2000 9998 9998 1000 aaabbbccc
+1000 2000 2000 9997 9997 1000 aaabbbccc
+1000 2000 2000 9996 9996 1000 aaabbbccc
+1000 1999 1999 9995 9995 1000 aaabbbccc
+1000 1999 1999 9994 9994 1000 aaabbbccc
+1000 1999 1999 9993 9993 1000 aaabbbccc
+1000 1999 1999 9992 9992 1000 aaabbbccc
+1000 1999 1999 9991 9991 1000 aaabbbccc
+# An index scan starting from the end of the table:
+explain
+select * from t1 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 122 NULL 1
+select * from t1 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+id1 id2 id3 id4 id5 value value2
+1000 2000 2000 10000 10000 1000 aaabbbccc
+create table t4 (
+pk int unsigned not null primary key,
+kp1 int unsigned not null,
+kp2 int unsigned not null,
+col1 int unsigned,
+key(kp1, kp2) comment 'rev:bf5_2'
+) engine=rocksdb;
+insert into t4 values (1, 0xFFFF, 0xFFF, 12345);
+# This must not fail an assert:
+select * from t4 force index(kp1) where kp1=0xFFFFFFFF and kp2<=0xFFFFFFFF order by kp2 desc;
+pk kp1 kp2 col1
+#
+# Issue #881: Issue #809 still occurs for reverse scans on forward cfs
+#
+create table t5 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'bf5_1'
+) engine=ROCKSDB;
+insert into t5 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+# An index scan starting from the end of the table:
+explain
+select * from t5 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t5 index NULL PRIMARY 122 NULL 1
+select * from t5 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+id1 id2 id3 id4 id5 value value2
+1000 2000 2000 10000 10000 1000 aaabbbccc
+drop table t1,t2,t3,t4,t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_bulk_load.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_bulk_load.result
new file mode 100644
index 00000000..4b02d110
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_bulk_load.result
@@ -0,0 +1,15 @@
+create table r1 (id bigint primary key, value bigint) engine=rocksdb;
+create table r2 (id bigint, value bigint, primary key (id) comment 'cf2') engine=rocksdb;
+set session rocksdb_bulk_load=1;
+set session rocksdb_bulk_load=0;
+select variable_value into @h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+insert into r1 values (100, 100);
+select variable_value-@h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+variable_value-@h
+1
+select variable_value into @h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+insert into r2 values (100, 100);
+select variable_value-@h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+variable_value-@h
+0
+DROP TABLE r1, r2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_skip.result b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_skip.result
new file mode 100644
index 00000000..28475630
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bloomfilter_skip.result
@@ -0,0 +1,2042 @@
+CREATE PROCEDURE bloom_start()
+BEGIN
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+END//
+CREATE PROCEDURE bloom_end()
+BEGIN
+select case when variable_value-@c > 0 then 'true' else 'false' end as checked from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+END//
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) ,
+index id2 (id2) ,
+index id2_id1 (id2, id1) ,
+index id2_id3 (id2, id3) ,
+index id2_id4 (id2, id4) ,
+index id2_id3_id1_id4 (id2, id3, id1, id4) ,
+index id3_id2 (id3, id2)
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) ,
+index id2 (id2) ,
+index id2_id3 (id2, id3) ,
+index id2_id4 (id2, id4) ,
+index id2_id4_id5 (id2, id4, id5) ,
+index id3_id4 (id3, id4) ,
+index id3_id5 (id3, id5)
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'cf_short_prefix',
+index id2 (id2) COMMENT 'cf_short_prefix',
+index id2_id1 (id2, id1) COMMENT 'cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_short_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'cf_short_prefix',
+index id3_id2 (id3, id2) COMMENT 'cf_short_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'cf_short_prefix',
+index id2 (id2) COMMENT 'cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_short_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'cf_short_prefix',
+index id3_id4 (id3, id4) COMMENT 'cf_short_prefix',
+index id3_id5 (id3, id5) COMMENT 'cf_short_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'rev:cf_short_prefix',
+index id2 (id2) COMMENT 'rev:cf_short_prefix',
+index id2_id1 (id2, id1) COMMENT 'rev:cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_short_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'rev:cf_short_prefix',
+index id3_id2 (id3, id2) COMMENT 'rev:cf_short_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'rev:cf_short_prefix',
+index id2 (id2) COMMENT 'rev:cf_short_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_short_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_short_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'rev:cf_short_prefix',
+index id3_id4 (id3, id4) COMMENT 'rev:cf_short_prefix',
+index id3_id5 (id3, id5) COMMENT 'rev:cf_short_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'cf_long_prefix',
+index id2 (id2) COMMENT 'cf_long_prefix',
+index id2_id1 (id2, id1) COMMENT 'cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_long_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'cf_long_prefix',
+index id3_id2 (id3, id2) COMMENT 'cf_long_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'cf_long_prefix',
+index id2 (id2) COMMENT 'cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'cf_long_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'cf_long_prefix',
+index id3_id4 (id3, id4) COMMENT 'cf_long_prefix',
+index id3_id5 (id3, id5) COMMENT 'cf_long_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create or replace table t1 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id1, id2, id3, id4) COMMENT 'rev:cf_long_prefix',
+index id2 (id2) COMMENT 'rev:cf_long_prefix',
+index id2_id1 (id2, id1) COMMENT 'rev:cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_long_prefix',
+index id2_id3_id1_id4 (id2, id3, id1, id4) COMMENT 'rev:cf_long_prefix',
+index id3_id2 (id3, id2) COMMENT 'rev:cf_long_prefix'
+) engine=ROCKSDB;
+create or replace table t2 (
+id1 bigint not null,
+id2 bigint not null,
+id3 varchar(100) not null,
+id4 int not null,
+id5 int not null,
+value bigint,
+value2 varchar(100),
+primary key (id4) COMMENT 'rev:cf_long_prefix',
+index id2 (id2) COMMENT 'rev:cf_long_prefix',
+index id2_id3 (id2, id3) COMMENT 'rev:cf_long_prefix',
+index id2_id4 (id2, id4) COMMENT 'rev:cf_long_prefix',
+index id2_id4_id5 (id2, id4, id5) COMMENT 'rev:cf_long_prefix',
+index id3_id4 (id3, id4) COMMENT 'rev:cf_long_prefix',
+index id3_id5 (id3, id5) COMMENT 'rev:cf_long_prefix'
+) engine=ROCKSDB;
+insert t1
+select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+call bloom_start();
+select count(*) from t1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+count(*)
+10000
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+count(*)
+10
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+count(*)
+5
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+count(*)
+1
+call bloom_end();
+checked
+false
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+count(*)
+1
+call bloom_end();
+checked
+false
+create table r1 (id1 bigint, id2 bigint, id3 bigint, v1 int, v2 text, primary key (id1, id2, id3)) engine=rocksdb DEFAULT CHARSET=latin1 collate latin1_bin;
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 asc;
+id1 id2 id3 v1 v2
+1 1 1 1 1
+call bloom_end();
+checked
+false
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 desc;
+id1 id2 id3 v1 v2
+1 1 1 1 1
+call bloom_end();
+checked
+false
+DROP PROCEDURE bloom_start;
+DROP PROCEDURE bloom_end;
+truncate table t1;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+truncate table t2;
+optimize table t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status OK
+drop table if exists t1;
+drop table if exists t2;
+drop table if exists r1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load.result
new file mode 100644
index 00000000..b8a98efa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load.result
@@ -0,0 +1,81 @@
+Data will be ordered in ascending order
+CREATE TABLE t1(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t2(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t3(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin' PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=
+'cf1={write_buffer_size=8m;target_file_size_base=1m};';
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+pk a b
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+select count(pk),count(a) from t1;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t1;
+count(b)
+2500000
+select count(pk),count(a) from t2;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t2;
+count(b)
+2500000
+select count(pk),count(a) from t3;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t3;
+count(b)
+2500000
+longfilenamethatvalidatesthatthiswillgetdeleted.bulk_load.tmp
+test.bulk_load.tmp
+# restart
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_drop_table.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_drop_table.result
new file mode 100644
index 00000000..4e79d828
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_drop_table.result
@@ -0,0 +1,11 @@
+CREATE TABLE t1 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1);
+connect con1,localhost,root,,;
+DROP TABLE t1;
+connection default;
+disconnect con1;
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+ERROR 42S02: Table 'test.t1' doesn't exist
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_errors.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_errors.result
new file mode 100644
index 00000000..995da9e8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_errors.result
@@ -0,0 +1,100 @@
+CREATE TABLE t1(pk INT, PRIMARY KEY(pk)) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(10);
+INSERT INTO t1 VALUES(11);
+INSERT INTO t1 VALUES(9);
+ERROR HY000: Rows must be inserted in primary key order during bulk load operation
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+pk
+10
+11
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(20);
+INSERT INTO t1 VALUES(21);
+SET rocksdb_bulk_load=0;
+ERROR HY000: Rows inserted during bulk load must not overlap existing rows
+SHOW VARIABLES LIKE 'rocksdb_bulk_load';
+Variable_name Value
+rocksdb_bulk_load OFF
+call mtr.add_suppression('finalizing last SST file while setting bulk loading variable');
+SELECT * FROM t1;
+pk
+10
+11
+FOUND 1 /RocksDB: Error [0-9]+ finalizing last SST file while setting bulk loading variable/ in rocksdb.bulk_load_errors.1.err
+connect con1,localhost,root,,;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(20);
+INSERT INTO t1 VALUES(21);
+connection default;
+disconnect con1;
+SELECT * FROM t1;
+pk
+10
+11
+FOUND 1 /RocksDB: Error [0-9]+ finalizing last SST file while disconnecting/ in rocksdb.bulk_load_errors.2.err
+TRUNCATE TABLE t1;
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(100);
+INSERT INTO t1 VALUES(101);
+INSERT INTO t1 VALUES(99);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+pk
+99
+100
+101
+TRUNCATE TABLE t1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(201);
+INSERT INTO t1 VALUES(200);
+INSERT INTO t1 VALUES(202);
+INSERT INTO t1 VALUES(201);
+ERROR 23000: Failed to insert the record: the key already exists
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+pk
+200
+201
+202
+SET rocksdb_bulk_load_allow_unsorted=DEFAULT;
+DROP TABLE t1;
+CREATE TABLE t1(c1 INT KEY) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (),(),();
+ERROR HY000: Rows must be inserted in primary key order during bulk load operation
+SET rocksdb_bulk_load=0;
+DROP TABLE t1;
+SET @orig_table_open_cache=@@global.table_open_cache;
+CREATE TABLE t1(a INT AUTO_INCREMENT, b INT, PRIMARY KEY (a)) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(13, 0);
+INSERT INTO t1 VALUES(2, 'test 2');
+Warnings:
+Warning 1366 Incorrect integer value: 'test 2' for column `test`.`t1`.`b` at row 1
+INSERT INTO t1 VALUES(@id, @arg04);
+SET @@global.table_open_cache=FALSE;
+Warnings:
+Warning 1292 Truncated incorrect table_open_cache value: '0'
+INSERT INTO t1 VALUES(51479+0.333333333,1);
+DROP TABLE t1;
+SET @@global.table_open_cache=@orig_table_open_cache;
+FOUND 1 /RocksDB: Error [0-9]+ finalizing bulk load while closing handler/ in rocksdb.bulk_load_errors.3.err
+# restart
+CREATE TABLE t1 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+CREATE TABLE t2 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1), (2);
+INSERT INTO t2 VALUES (1), (2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (3);
+ERROR HY000: Rows inserted during bulk load must not overlap existing rows
+SET rocksdb_bulk_load=0;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf.result
new file mode 100644
index 00000000..56b1a832
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf.result
@@ -0,0 +1,81 @@
+Data will be ordered in ascending order
+CREATE TABLE t1(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t2(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t3(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin' PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=
+'cf1={write_buffer_size=8m;target_file_size_base=1m};';
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+pk a b
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+select count(pk),count(a) from t1;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t1;
+count(b)
+2500000
+select count(pk),count(a) from t2;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t2;
+count(b)
+2500000
+select count(pk),count(a) from t3;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t3;
+count(b)
+2500000
+longfilenamethatvalidatesthatthiswillgetdeleted.bulk_load.tmp
+test.bulk_load.tmp
+# restart
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf_and_data.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf_and_data.result
new file mode 100644
index 00000000..1a260c69
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_cf_and_data.result
@@ -0,0 +1,81 @@
+Data will be ordered in descending order
+CREATE TABLE t1(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t2(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t3(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "rev:cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin' PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=
+'cf1={write_buffer_size=8m;target_file_size_base=1m};';
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+pk a b
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+select count(pk),count(a) from t1;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t1;
+count(b)
+2500000
+select count(pk),count(a) from t2;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t2;
+count(b)
+2500000
+select count(pk),count(a) from t3;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t3;
+count(b)
+2500000
+longfilenamethatvalidatesthatthiswillgetdeleted.bulk_load.tmp
+test.bulk_load.tmp
+# restart
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_data.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_data.result
new file mode 100644
index 00000000..e216fb1c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_rev_data.result
@@ -0,0 +1,81 @@
+Data will be ordered in descending order
+CREATE TABLE t1(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t2(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin';
+CREATE TABLE t3(
+pk CHAR(5),
+a CHAR(30),
+b CHAR(30),
+PRIMARY KEY(pk) COMMENT "cf1",
+KEY(a)
+) ENGINE=ROCKSDB COLLATE 'latin1_bin' PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=
+'cf1={write_buffer_size=8m;target_file_size_base=1m};';
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+pk a b
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+pk a b
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_bin NULL partitioned 0 N
+select count(pk),count(a) from t1;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t1;
+count(b)
+2500000
+select count(pk),count(a) from t2;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t2;
+count(b)
+2500000
+select count(pk),count(a) from t3;
+count(pk) count(a)
+2500000 2500000
+select count(b) from t3;
+count(b)
+2500000
+longfilenamethatvalidatesthatthiswillgetdeleted.bulk_load.tmp
+test.bulk_load.tmp
+# restart
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_sk.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_sk.result
new file mode 100644
index 00000000..42f820a2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_sk.result
@@ -0,0 +1,229 @@
+SET rocksdb_bulk_load_size=15;
+CREATE TABLE t4 (a INT, b INT, c INT,
+PRIMARY KEY (a),
+KEY (b),
+KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t3 (a INT, b INT, c INT,
+PRIMARY KEY (a),
+KEY (b),
+KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t2 (a INT, b INT, c INT,
+PRIMARY KEY (a),
+KEY (b),
+KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t1 (a INT, b INT, c INT,
+PRIMARY KEY (a),
+KEY (b),
+KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 SELECT * FROM t3 FORCE INDEX (PRIMARY) ORDER BY a;
+SELECT count(*) FROM t1 FORCE INDEX (PRIMARY);
+count(*)
+0
+SELECT count(*) FROM t1 FORCE INDEX (b);
+count(*)
+10
+SELECT count(*) FROM t1 FORCE INDEX (c);
+count(*)
+10
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b c
+-9 11 11
+-7 9 9
+-5 7 7
+-3 5 5
+-1 3 3
+2 0 0
+4 -2 -2
+6 -4 -4
+8 -6 -6
+10 -8 -8
+SELECT b FROM t1 FORCE INDEX (b);
+b
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+SELECT c FROM t1 FORCE INDEX (c);
+c
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+Checksums should match
+CHECKSUM TABLE t3;
+Table Checksum
+test.t3 3862424802
+CHECKSUM TABLE t1;
+Table Checksum
+test.t1 3862424802
+SET rocksdb_bulk_load_allow_sk=1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t4 SELECT * FROM t3 FORCE INDEX (PRIMARY) ORDER BY a;
+SELECT count(*) FROM t4 FORCE INDEX (PRIMARY);
+count(*)
+0
+SELECT count(*) FROM t4 FORCE INDEX (b);
+count(*)
+0
+SELECT count(*) FROM t4 FORCE INDEX (c);
+count(*)
+0
+SET rocksdb_bulk_load=0;
+SELECT * FROM t4 FORCE INDEX (PRIMARY);
+a b c
+-9 11 11
+-7 9 9
+-5 7 7
+-3 5 5
+-1 3 3
+2 0 0
+4 -2 -2
+6 -4 -4
+8 -6 -6
+10 -8 -8
+SELECT b FROM t4 FORCE INDEX (b);
+b
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+SELECT c FROM t4 FORCE INDEX (c);
+c
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+Checksums should match
+CHECKSUM TABLE t3;
+Table Checksum
+test.t3 3862424802
+CHECKSUM TABLE t4;
+Table Checksum
+test.t4 3862424802
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load_allow_sk=1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t2 SELECT * FROM t3 WHERE b >= 0 ORDER BY b;
+INSERT INTO t2 SELECT * FROM t3 WHERE b < 0 ORDER BY b;
+SELECT count(*) FROM t2 FORCE INDEX (PRIMARY);
+count(*)
+0
+SELECT count(*) FROM t2 FORCE INDEX (b);
+count(*)
+0
+SELECT count(*) FROM t2 FORCE INDEX (c);
+count(*)
+0
+SELECT count(*) FROM t2 FORCE INDEX (PRIMARY);
+count(*)
+0
+SELECT count(*) FROM t2 FORCE INDEX (b);
+count(*)
+0
+SELECT count(*) FROM t2 FORCE INDEX (c);
+count(*)
+0
+SET rocksdb_bulk_load=0;
+SELECT * FROM t2 FORCE INDEX (PRIMARY);
+a b c
+-19 21 21
+-17 19 19
+-15 17 17
+-13 15 15
+-11 13 13
+-9 11 11
+-7 9 9
+-5 7 7
+-3 5 5
+-1 3 3
+2 0 0
+4 -2 -2
+6 -4 -4
+8 -6 -6
+10 -8 -8
+12 -10 -10
+14 -12 -12
+16 -14 -14
+18 -16 -16
+20 -18 -18
+SELECT b FROM t2 FORCE INDEX (b);
+b
+-18
+-16
+-14
+-12
+-10
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+13
+15
+17
+19
+21
+SELECT c FROM t2 FORCE INDEX (c);
+c
+-18
+-16
+-14
+-12
+-10
+-8
+-6
+-4
+-2
+0
+3
+5
+7
+9
+11
+13
+15
+17
+19
+21
+Checksums should match
+CHECKSUM TABLE t3;
+Table Checksum
+test.t3 1495594118
+CHECKSUM TABLE t2;
+Table Checksum
+test.t2 1495594118
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted.result
new file mode 100644
index 00000000..63d07a25
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted.result
@@ -0,0 +1,111 @@
+SET rocksdb_bulk_load_size=3;
+SET rocksdb_bulk_load_allow_unsorted=1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1")
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+-3 5
+-1 3
+2 0
+4 -2
+6 -4
+DROP TABLE t1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1", KEY(b))
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+-3 5
+-1 3
+2 0
+4 -2
+6 -4
+DROP TABLE t1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t2(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1")
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t2 VALUES (1,1);
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+INSERT INTO t1 VALUES (2,2);
+SELECT * FROM t2 FORCE INDEX (PRIMARY);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+2 2
+DROP TABLE t1, t2;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t2(a INT, b INT, PRIMARY KEY(b) COMMENT "cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t3(a INT, b INT, PRIMARY KEY(a) COMMENT "cf1")
+ENGINE=ROCKSDB PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL partitioned 0 N
+select count(a),count(b) from t1;
+count(a) count(b)
+2500000 2500000
+select count(a),count(b) from t2;
+count(a) count(b)
+2500000 2500000
+select count(a),count(b) from t3;
+count(a) count(b)
+2500000 2500000
+SELECT * FROM t1 FORCE INDEX (PRIMARY) LIMIT 3;
+a b
+-2499998 2500000
+-2499996 2499998
+-2499994 2499996
+SELECT * FROM t2 FORCE INDEX (PRIMARY) LIMIT 3;
+a b
+2499999 -2499997
+2499997 -2499995
+2499995 -2499993
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_errors.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_errors.result
new file mode 100644
index 00000000..d8e5b92e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_errors.result
@@ -0,0 +1,4 @@
+SET rocksdb_bulk_load=1;
+SET rocksdb_bulk_load_allow_unsorted=1;
+ERROR HY000: Error when executing command SET: Cannot change this setting while bulk load is enabled
+SET rocksdb_bulk_load=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_rev.result b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_rev.result
new file mode 100644
index 00000000..a6e0793f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bulk_load_unsorted_rev.result
@@ -0,0 +1,111 @@
+SET rocksdb_bulk_load_size=3;
+SET rocksdb_bulk_load_allow_unsorted=1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1")
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+6 -4
+4 -2
+2 0
+-1 3
+-3 5
+DROP TABLE t1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1", KEY(b))
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+6 -4
+4 -2
+2 0
+-1 3
+-3 5
+DROP TABLE t1;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t2(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1")
+ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t2 VALUES (1,1);
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+INSERT INTO t1 VALUES (2,2);
+SELECT * FROM t2 FORCE INDEX (PRIMARY);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+2 2
+1 1
+DROP TABLE t1, t2;
+CREATE TABLE t1(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t2(a INT, b INT, PRIMARY KEY(b) COMMENT "rev:cf1")
+ENGINE=ROCKSDB;
+CREATE TABLE t3(a INT, b INT, PRIMARY KEY(a) COMMENT "rev:cf1")
+ENGINE=ROCKSDB PARTITION BY KEY() PARTITIONS 4;
+connect other,localhost,root,,;
+set session transaction isolation level repeatable read;
+start transaction with consistent snapshot;
+select VALUE > 0 as 'Has opened snapshots' from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+Has opened snapshots
+1
+connection default;
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=100000;
+LOAD DATA INFILE <input_file> INTO TABLE t1;
+LOAD DATA INFILE <input_file> INTO TABLE t2;
+LOAD DATA INFILE <input_file> INTO TABLE t3;
+set rocksdb_bulk_load=0;
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL partitioned 0 N
+ANALYZE TABLE t1, t2, t3;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+SHOW TABLE STATUS WHERE name LIKE 't%';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t2 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t3 ROCKSDB 10 Fixed 2500000 # # # # 0 NULL # # NULL latin1_swedish_ci NULL partitioned 0 N
+select count(a),count(b) from t1;
+count(a) count(b)
+2500000 2500000
+select count(a),count(b) from t2;
+count(a) count(b)
+2500000 2500000
+select count(a),count(b) from t3;
+count(a) count(b)
+2500000 2500000
+SELECT * FROM t1 FORCE INDEX (PRIMARY) LIMIT 3;
+a b
+2499999 -2499997
+2499997 -2499995
+2499995 -2499993
+SELECT * FROM t2 FORCE INDEX (PRIMARY) LIMIT 3;
+a b
+-2499998 2500000
+-2499996 2499998
+-2499994 2499996
+disconnect other;
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic.result b/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic.result
new file mode 100644
index 00000000..1f687dfe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic.result
@@ -0,0 +1,693 @@
+CREATE TABLE `link_table` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+`time` int(10) unsigned NOT NULL DEFAULT '0' ,
+`version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+PRIMARY KEY (`link_type` , `id1` , `id2`) COMMENT 'cf_link' ,
+KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+`version` , `data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+CREATE TABLE `link_table2` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+`time` int(10) unsigned NOT NULL DEFAULT '0' ,
+`version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+PRIMARY KEY (`link_type` , `id1` , `id2`)
+COMMENT 'cf_link' ,
+KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+`version` , `data`) COMMENT 'cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=9;
+insert into link_table values (1, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (1, 1, 2, 2, 3, 3, 'a10', 10, 125);
+insert into link_table values (1, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 5, 2, 3, 3, 'a12', 12, 125);
+insert into link_table values (1, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (1, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (1, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 4, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 4, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 4, 4, 'a15', 15, 125);
+insert into link_table values (3, 1, 10, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 9, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 8, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 7, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 4, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 3, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (3, 1, 2, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (3, 1, 1, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (9, 1, 9, 2, 5, 6, '0 ', 10, 125);
+insert into link_table values (9, 1, 8, 2, 5, 6, '01 ', 11, 125);
+insert into link_table values (9, 1, 7, 2, 5, 6, '012 ', 11, 125);
+insert into link_table values (9, 1, 6, 2, 5, 6, '0123 ', 12, 125);
+insert into link_table values (9, 1, 5, 2, 5, 6, '01234 ', 12, 125);
+insert into link_table values (9, 1, 4, 2, 5, 6, '012345 ', 12, 125);
+insert into link_table values (9, 1, 3, 2, 5, 6, '0123456 ', 13, 125);
+insert into link_table values (9, 1, 2, 2, 5, 6, '01234567 ', 14, 125);
+insert into link_table values (9, 1, 1, 2, 5, 6, '012345678 ', 15, 125);
+insert into link_table values (9, 1, 0, 2, 5, 6, '0123456789 ', 15, 125);
+insert into link_table2 select * from link_table;
+CREATE TABLE `id_table` (
+`id` bigint(20) NOT NULL DEFAULT '0',
+`type` int(11) NOT NULL DEFAULT '0',
+`row_created_time` int(11) NOT NULL DEFAULT '0',
+`hash_key` varchar(255) NOT NULL DEFAULT '',
+`is_deleted` tinyint(4) DEFAULT '0',
+PRIMARY KEY (`id`),
+KEY `type_id` (`type`,`id`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPRESSED
+KEY_BLOCK_SIZE=8;
+insert into id_table values (1, 1, 10, '111', 0);
+insert into id_table values (2, 1, 10, '111', 1);
+insert into id_table values (3, 1, 10, '111', 0);
+insert into id_table values (4, 1, 10, '111', 1);
+insert into id_table values (5, 1, 10, '111', 0);
+insert into id_table values (6, 1, 10, '111', 1);
+insert into id_table values (7, 1, 10, '111', 0);
+insert into id_table values (8, 1, 10, '111', 1);
+insert into id_table values (9, 1, 10, '111', 0);
+insert into id_table values (10, 1, 10, '111', 1);
+CREATE TABLE `node_table` (
+`id` bigint(20) unsigned NOT NULL DEFAULT '0',
+`type` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+`update_time` int(10) unsigned NOT NULL DEFAULT '0',
+`data` mediumtext COLLATE latin1_bin NOT NULL,
+PRIMARY KEY (`type`,`id`) COMMENT 'cf_node_type_id',
+KEY `id` (`id`) COMMENT 'cf_node'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into node_table values (1, 1, 1, 10, 'data');
+insert into node_table values (2, 1, 1, 10, 'data');
+insert into node_table values (3, 1, 1, 10, 'data');
+insert into node_table values (4, 1, 1, 10, 'data');
+insert into node_table values (5, 1, 1, 10, 'data');
+insert into node_table values (6, 1, 1, 10, 'data');
+insert into node_table values (7, 1, 1, 10, 'data');
+insert into node_table values (8, 1, 1, 10, 'data');
+insert into node_table values (9, 1, 1, 10, 'data');
+insert into node_table values (10, 1, 1, 10, 'data');
+CREATE TABLE `count_table` (
+`id` bigint(20) unsigned NOT NULL DEFAULT '0',
+`type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`count` int(10) unsigned NOT NULL DEFAULT '0',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`id`,`link_type`) COMMENT 'cf_count_table'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into count_table values (2, 1, 1, 1, 10, 20);
+insert into count_table values (3, 1, 1, 1, 10, 20);
+insert into count_table values (4, 1, 1, 1, 10, 20);
+insert into count_table values (5, 1, 1, 1, 10, 20);
+insert into count_table values (6, 1, 1, 1, 10, 20);
+insert into count_table values (7, 1, 1, 1, 10, 20);
+insert into count_table values (8, 1, 1, 1, 10, 20);
+insert into count_table values (9, 1, 1, 1, 10, 20);
+insert into count_table values (10, 1, 1, 1, 10, 20);
+CREATE TABLE `link_table5` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into link_table5 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table5 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table5 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table5 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table5 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table5 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table5 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+CREATE TABLE `link_table3` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(4) NOT NULL DEFAULT '0',
+`data` text COLLATE latin1_bin NOT NULL,
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`)
+COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+insert into link_table3 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table3 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table3 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table3 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table3 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table3 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table3 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+CREATE TABLE `link_table6` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(4) NOT NULL DEFAULT '0',
+`data` text COLLATE latin1_bin NOT NULL,
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,
+`data`(255)) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+insert into link_table6 values (1, 1, 2, 2, 1, 1,
+'data12_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 3, 2, 1, 2,
+'data13_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 4, 2, 1, 2,
+'data14_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 5, 2, 1, 1,
+'data15_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 1, 2, 1, 1,
+'data21_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 2, 2, 1, 1,
+'data22_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 3, 2, 1, 1,
+'data32_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+CREATE TABLE `link_table4` (
+`id1` binary(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
+`raw_key` text COLLATE latin1_bin,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,`data`)
+COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into link_table4 values ('a1', "rk1", 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table4 values ('a1', "rk2", 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table4 values ('a1', "rk3", 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table4 values ('a1', "rk4", 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table4 values ('b1', "rk5", 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table4 values ('b1', "rk6", 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table4 values ('b1', "rk7", 3, 2, 1, 1, 'data32', 1, 1);
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ no_bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /* +bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*aaaaaaaaabbbbbbbbb*/ id1,id2,id1_type,id2_type,data,version
+from link_table WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+byp*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypw*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*-b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /**/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+# Point query
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2, 3, 4) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+1 3 1 2 a11 125
+1 4 1 2 a11 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1) and id2 IN (2) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1, 2) and id2 IN (2, 3, 4) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+1 3 1 2 a11 125
+1 4 1 2 a11 125
+2 2 1 2 a10 125
+2 3 1 2 a11 125
+2 4 1 2 a11 125
+# Prefix range query
+# Prefix range query with SK
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME DESC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 2 3 3 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME ASC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 2 3 3 a10 10 125
+# Prefix range query with SK with limits
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,10;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,5;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,1;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,10;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,5;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,1;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,10;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,5;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,1;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,10;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,5;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,1;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,0;
+id1 id2 link_type visibility data time version
+# Prefix range query with PK
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+id1 id2 link_type
+1 10 3
+1 9 3
+1 8 3
+1 7 3
+1 6 3
+1 5 3
+1 4 3
+1 3 3
+1 2 3
+1 1 3
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+id1 id2 link_type
+1 1 3
+1 2 3
+1 3 3
+1 4 3
+1 5 3
+1 6 3
+1 7 3
+1 8 3
+1 9 3
+1 10 3
+# Prefix range query with PK + value
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 5 3 3 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 2 3 3 a10 10 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+id1 id2 link_type visibility data time version
+1 1 3 4 a10 10 125
+1 2 3 3 a10 10 125
+1 3 3 4 a11 11 125
+1 4 3 4 a11 11 125
+1 5 3 3 a12 12 125
+1 6 3 4 a12 12 125
+1 7 3 4 a12 12 125
+1 8 3 4 a13 13 125
+1 9 3 4 a14 14 125
+1 10 3 4 a15 15 125
+# Transaction
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+UPDATE link_table set data="bcd" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+COMMIT;
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+UPDATE link_table set data="cde" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 cde 125
+ROLLBACK;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+# Data types
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3";
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+2
+2
+2
+2
+2
+2
+2
+2
+2
+2
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1="1";
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=True;
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=b'1';
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=x'01';
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=NULL;
+id1
+DROP TABLE count_table;
+DROP TABLE link_table;
+DROP TABLE link_table3;
+DROP TABLE link_table2;
+DROP TABLE id_table;
+DROP TABLE node_table;
+DROP TABLE link_table5;
+DROP TABLE link_table6;
+DROP TABLE link_table4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic_bloom.result b/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic_bloom.result
new file mode 100644
index 00000000..1f687dfe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bypass_select_basic_bloom.result
@@ -0,0 +1,693 @@
+CREATE TABLE `link_table` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+`time` int(10) unsigned NOT NULL DEFAULT '0' ,
+`version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+PRIMARY KEY (`link_type` , `id1` , `id2`) COMMENT 'cf_link' ,
+KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+`version` , `data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+CREATE TABLE `link_table2` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0' ,
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+`visibility` tinyint(3) NOT NULL DEFAULT '0' ,
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '' ,
+`time` int(10) unsigned NOT NULL DEFAULT '0' ,
+`version` bigint(20) unsigned NOT NULL DEFAULT '0' ,
+PRIMARY KEY (`link_type` , `id1` , `id2`)
+COMMENT 'cf_link' ,
+KEY `id1_type` (`id1` , `link_type` , `visibility` , `time` , `id2` ,
+`version` , `data`) COMMENT 'cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=9;
+insert into link_table values (1, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (1, 1, 2, 2, 3, 3, 'a10', 10, 125);
+insert into link_table values (1, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (1, 1, 5, 2, 3, 3, 'a12', 12, 125);
+insert into link_table values (1, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (1, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (1, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (1, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (2, 1, 1, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 2, 2, 4, 4, 'a10', 10, 125);
+insert into link_table values (2, 1, 3, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 4, 2, 4, 4, 'a11', 11, 125);
+insert into link_table values (2, 1, 5, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 6, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 7, 2, 4, 4, 'a12', 12, 125);
+insert into link_table values (2, 1, 8, 2, 4, 4, 'a13', 13, 125);
+insert into link_table values (2, 1, 9, 2, 4, 4, 'a14', 14, 125);
+insert into link_table values (2, 1, 10, 2, 4, 4, 'a15', 15, 125);
+insert into link_table values (3, 1, 10, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 9, 2, 3, 4, 'a10', 10, 125);
+insert into link_table values (3, 1, 8, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 7, 2, 3, 4, 'a11', 11, 125);
+insert into link_table values (3, 1, 6, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 5, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 4, 2, 3, 4, 'a12', 12, 125);
+insert into link_table values (3, 1, 3, 2, 3, 4, 'a13', 13, 125);
+insert into link_table values (3, 1, 2, 2, 3, 4, 'a14', 14, 125);
+insert into link_table values (3, 1, 1, 2, 3, 4, 'a15', 15, 125);
+insert into link_table values (9, 1, 9, 2, 5, 6, '0 ', 10, 125);
+insert into link_table values (9, 1, 8, 2, 5, 6, '01 ', 11, 125);
+insert into link_table values (9, 1, 7, 2, 5, 6, '012 ', 11, 125);
+insert into link_table values (9, 1, 6, 2, 5, 6, '0123 ', 12, 125);
+insert into link_table values (9, 1, 5, 2, 5, 6, '01234 ', 12, 125);
+insert into link_table values (9, 1, 4, 2, 5, 6, '012345 ', 12, 125);
+insert into link_table values (9, 1, 3, 2, 5, 6, '0123456 ', 13, 125);
+insert into link_table values (9, 1, 2, 2, 5, 6, '01234567 ', 14, 125);
+insert into link_table values (9, 1, 1, 2, 5, 6, '012345678 ', 15, 125);
+insert into link_table values (9, 1, 0, 2, 5, 6, '0123456789 ', 15, 125);
+insert into link_table2 select * from link_table;
+CREATE TABLE `id_table` (
+`id` bigint(20) NOT NULL DEFAULT '0',
+`type` int(11) NOT NULL DEFAULT '0',
+`row_created_time` int(11) NOT NULL DEFAULT '0',
+`hash_key` varchar(255) NOT NULL DEFAULT '',
+`is_deleted` tinyint(4) DEFAULT '0',
+PRIMARY KEY (`id`),
+KEY `type_id` (`type`,`id`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPRESSED
+KEY_BLOCK_SIZE=8;
+insert into id_table values (1, 1, 10, '111', 0);
+insert into id_table values (2, 1, 10, '111', 1);
+insert into id_table values (3, 1, 10, '111', 0);
+insert into id_table values (4, 1, 10, '111', 1);
+insert into id_table values (5, 1, 10, '111', 0);
+insert into id_table values (6, 1, 10, '111', 1);
+insert into id_table values (7, 1, 10, '111', 0);
+insert into id_table values (8, 1, 10, '111', 1);
+insert into id_table values (9, 1, 10, '111', 0);
+insert into id_table values (10, 1, 10, '111', 1);
+CREATE TABLE `node_table` (
+`id` bigint(20) unsigned NOT NULL DEFAULT '0',
+`type` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+`update_time` int(10) unsigned NOT NULL DEFAULT '0',
+`data` mediumtext COLLATE latin1_bin NOT NULL,
+PRIMARY KEY (`type`,`id`) COMMENT 'cf_node_type_id',
+KEY `id` (`id`) COMMENT 'cf_node'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into node_table values (1, 1, 1, 10, 'data');
+insert into node_table values (2, 1, 1, 10, 'data');
+insert into node_table values (3, 1, 1, 10, 'data');
+insert into node_table values (4, 1, 1, 10, 'data');
+insert into node_table values (5, 1, 1, 10, 'data');
+insert into node_table values (6, 1, 1, 10, 'data');
+insert into node_table values (7, 1, 1, 10, 'data');
+insert into node_table values (8, 1, 1, 10, 'data');
+insert into node_table values (9, 1, 1, 10, 'data');
+insert into node_table values (10, 1, 1, 10, 'data');
+CREATE TABLE `count_table` (
+`id` bigint(20) unsigned NOT NULL DEFAULT '0',
+`type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`count` int(10) unsigned NOT NULL DEFAULT '0',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`id`,`link_type`) COMMENT 'cf_count_table'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into count_table values (2, 1, 1, 1, 10, 20);
+insert into count_table values (3, 1, 1, 1, 10, 20);
+insert into count_table values (4, 1, 1, 1, 10, 20);
+insert into count_table values (5, 1, 1, 1, 10, 20);
+insert into count_table values (6, 1, 1, 1, 10, 20);
+insert into count_table values (7, 1, 1, 1, 10, 20);
+insert into count_table values (8, 1, 1, 1, 10, 20);
+insert into count_table values (9, 1, 1, 1, 10, 20);
+insert into count_table values (10, 1, 1, 1, 10, 20);
+CREATE TABLE `link_table5` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into link_table5 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table5 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table5 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table5 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table5 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table5 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table5 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+CREATE TABLE `link_table3` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(4) NOT NULL DEFAULT '0',
+`data` text COLLATE latin1_bin NOT NULL,
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`)
+COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+insert into link_table3 values (1, 1, 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table3 values (1, 1, 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table3 values (1, 1, 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table3 values (1, 1, 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table3 values (2, 1, 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table3 values (2, 1, 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table3 values (2, 1, 3, 2, 1, 1, 'data32', 1, 1);
+CREATE TABLE `link_table6` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(4) NOT NULL DEFAULT '0',
+`data` text COLLATE latin1_bin NOT NULL,
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,
+`data`(255)) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
+insert into link_table6 values (1, 1, 2, 2, 1, 1,
+'data12_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 3, 2, 1, 2,
+'data13_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 4, 2, 1, 2,
+'data14_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (1, 1, 5, 2, 1, 1,
+'data15_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 1, 2, 1, 1,
+'data21_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 2, 2, 1, 1,
+'data22_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+insert into link_table6 values (2, 1, 3, 2, 1, 1,
+'data32_12345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890123456789012345678901234567890123456789'
+ '0123456789012345678901234567890', 1, 1);
+CREATE TABLE `link_table4` (
+`id1` binary(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
+`raw_key` text COLLATE latin1_bin,
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` int(10) unsigned NOT NULL DEFAULT '0',
+`version` bigint(20) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,`data`)
+COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin
+ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
+insert into link_table4 values ('a1', "rk1", 2, 2, 1, 1, 'data12', 1, 1);
+insert into link_table4 values ('a1', "rk2", 3, 2, 1, 2, 'data13', 1, 1);
+insert into link_table4 values ('a1', "rk3", 4, 2, 1, 2, 'data14', 1, 1);
+insert into link_table4 values ('a1', "rk4", 5, 2, 1, 1, 'data15', 1, 1);
+insert into link_table4 values ('b1', "rk5", 1, 2, 1, 1, 'data21', 1, 1);
+insert into link_table4 values ('b1', "rk6", 2, 2, 1, 1, 'data22', 1, 1);
+insert into link_table4 values ('b1', "rk7", 3, 2, 1, 1, 'data32', 1, 1);
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ no_bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /* +bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*aaaaaaaaabbbbbbbbb*/ id1,id2,id1_type,id2_type,data,version
+from link_table WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+byp*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+bypw*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*-b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /**/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+# Point query
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2, 3, 4) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+1 3 1 2 a11 125
+1 4 1 2 a11 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1) and id2 IN (2) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1, 2) and id2 IN (2, 3, 4) and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+1 3 1 2 a11 125
+1 4 1 2 a11 125
+2 2 1 2 a10 125
+2 3 1 2 a11 125
+2 4 1 2 a11 125
+# Prefix range query
+# Prefix range query with SK
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME DESC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 2 3 3 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME ASC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 2 3 3 a10 10 125
+# Prefix range query with SK with limits
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,10;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,5;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,1;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,10;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,5;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 4 3 4 a11 11 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,1;
+id1 id2 link_type visibility data time version
+1 9 3 4 a14 14 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,10;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,5;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,1;
+id1 id2 link_type visibility data time version
+1 4 3 4 a11 11 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,0;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,10;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,5;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,1;
+id1 id2 link_type visibility data time version
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,0;
+id1 id2 link_type visibility data time version
+# Prefix range query with PK
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+id1 id2 link_type
+1 10 3
+1 9 3
+1 8 3
+1 7 3
+1 6 3
+1 5 3
+1 4 3
+1 3 3
+1 2 3
+1 1 3
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+id1 id2 link_type
+1 1 3
+1 2 3
+1 3 3
+1 4 3
+1 5 3
+1 6 3
+1 7 3
+1 8 3
+1 9 3
+1 10 3
+# Prefix range query with PK + value
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+id1 id2 link_type visibility data time version
+1 10 3 4 a15 15 125
+1 9 3 4 a14 14 125
+1 8 3 4 a13 13 125
+1 7 3 4 a12 12 125
+1 6 3 4 a12 12 125
+1 5 3 3 a12 12 125
+1 4 3 4 a11 11 125
+1 3 3 4 a11 11 125
+1 2 3 3 a10 10 125
+1 1 3 4 a10 10 125
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+id1 id2 link_type visibility data time version
+1 1 3 4 a10 10 125
+1 2 3 3 a10 10 125
+1 3 3 4 a11 11 125
+1 4 3 4 a11 11 125
+1 5 3 3 a12 12 125
+1 6 3 4 a12 12 125
+1 7 3 4 a12 12 125
+1 8 3 4 a13 13 125
+1 9 3 4 a14 14 125
+1 10 3 4 a15 15 125
+# Transaction
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 a10 125
+UPDATE link_table set data="bcd" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+COMMIT;
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+UPDATE link_table set data="cde" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 cde 125
+ROLLBACK;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+id1 id2 id1_type id2_type data version
+1 2 1 2 bcd 125
+# Data types
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3";
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+2
+2
+2
+2
+2
+2
+2
+2
+2
+2
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1="1";
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=True;
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=b'1';
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=x'01';
+id1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=NULL;
+id1
+DROP TABLE count_table;
+DROP TABLE link_table;
+DROP TABLE link_table3;
+DROP TABLE link_table2;
+DROP TABLE id_table;
+DROP TABLE node_table;
+DROP TABLE link_table5;
+DROP TABLE link_table6;
+DROP TABLE link_table4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/bytes_written.result b/storage/rocksdb/mysql-test/rocksdb/r/bytes_written.result
new file mode 100644
index 00000000..d9d29e6a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/bytes_written.result
@@ -0,0 +1,10 @@
+DROP TABLE IF EXISTS stats_test_table;
+CREATE TABLE stats_test_table (a INT, b INT, PRIMARY KEY (a)) ENGINE=ROCKSDB;
+SET GLOBAL rocksdb_perf_context_level=3;
+INSERT INTO stats_test_table VALUES (7,1);
+INSERT INTO stats_test_table VALUES (2,2);
+SELECT io_write_bytes > 0 FROM INFORMATION_SCHEMA.TABLE_STATISTICS WHERE TABLE_NAME = "stats_test_table";
+io_write_bytes > 0
+1
+DROP TABLE stats_test_table;
+SET GLOBAL rocksdb_perf_context_level=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/cardinality.result b/storage/rocksdb/mysql-test/rocksdb/r/cardinality.result
new file mode 100644
index 00000000..dcaca8b7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/cardinality.result
@@ -0,0 +1,104 @@
+# restart
+set use_stat_tables= 'COMPLEMENTARY';
+CREATE TABLE t0 (id int PRIMARY KEY, a int, INDEX ix_a (a)) engine=rocksdb;
+insert into t0 values (0, 0),(1, 1),(2, 2),(3, 3),(4, 4),
+(5, 4),(6, 4),(7, 4),(8, 4),(9, 4);
+SELECT cardinality FROM information_schema.statistics where table_name="t0" and
+column_name="id";
+cardinality
+NULL
+SELECT cardinality FROM information_schema.statistics where table_name="t0" and
+column_name="a";
+cardinality
+NULL
+ANALYZE TABLE t0;
+SELECT table_rows into @N FROM information_schema.tables
+WHERE table_name = "t0";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="id";
+FLOOR(@N/cardinality)
+1
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="a";
+FLOOR(@N/cardinality)
+2
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+ANALYZE TABLE t0;
+SELECT table_rows into @N FROM information_schema.tables
+WHERE table_name = "t0";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="id";
+FLOOR(@N/cardinality)
+1
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="a";
+FLOOR(@N/cardinality)
+2
+drop table t0;
+DROP TABLE IF EXISTS t1,t10,t11;
+create table t1(
+id bigint not null primary key,
+i1 bigint, #unique
+i2 bigint, #repeating
+c1 varchar(20), #unique
+c2 varchar(20), #repeating
+index t1_1(id, i1),
+index t1_2(i1, i2),
+index t1_3(i2, i1),
+index t1_4(c1, c2),
+index t1_5(c2, c1)
+) engine=rocksdb;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+show index in t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 id A 100000 NULL NULL LSMTREE
+t1 1 t1_1 1 id A 100000 NULL NULL LSMTREE
+t1 1 t1_1 2 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_2 1 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_2 2 i2 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_3 1 i2 A 11111 NULL NULL YES LSMTREE
+t1 1 t1_3 2 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_4 1 c1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_4 2 c2 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_5 1 c2 A 11111 NULL NULL YES LSMTREE
+t1 1 t1_5 2 c1 A 100000 NULL NULL YES LSMTREE
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name table_rows
+t1 100000
+restarting...
+# restart
+show index in t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 id A 100000 NULL NULL LSMTREE
+t1 1 t1_1 1 id A 100000 NULL NULL LSMTREE
+t1 1 t1_1 2 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_2 1 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_2 2 i2 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_3 1 i2 A 11111 NULL NULL YES LSMTREE
+t1 1 t1_3 2 i1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_4 1 c1 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_4 2 c2 A 100000 NULL NULL YES LSMTREE
+t1 1 t1_5 1 c2 A 11111 NULL NULL YES LSMTREE
+t1 1 t1_5 2 c1 A 100000 NULL NULL YES LSMTREE
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name table_rows
+t1 100000
+CREATE TABLE t2 (a INT, b INT, c INT, d INT, e INT, f INT, g INT,
+PRIMARY KEY (a), KEY (c, b, a, d, e, f, g))
+ENGINE=ROCKSDB;
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+ANALYZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+cardinality of the columns after 'a' must be equal to the cardinality of column 'a'
+SELECT CARDINALITY INTO @c FROM information_schema.statistics WHERE TABLE_NAME='t2' AND INDEX_NAME='c' AND COLUMN_NAME='a';
+SELECT COLUMN_NAME, CARDINALITY = @c FROM information_schema.statistics WHERE TABLE_NAME='t2' AND INDEX_NAME='c' AND SEQ_IN_INDEX > 3;
+COLUMN_NAME CARDINALITY = @c
+d 1
+e 1
+f 1
+g 1
+drop table t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/check_flags.result b/storage/rocksdb/mysql-test/rocksdb/r/check_flags.result
new file mode 100644
index 00000000..12c5bc4f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/check_flags.result
@@ -0,0 +1,66 @@
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = -10;
+connect conn1, localhost, root,,;
+connection default;
+CREATE TABLE t1 (id INT, value INT, KEY (id), KEY (value)) ENGINE=ROCKSDB;
+CREATE TABLE t2 (id INT, value INT) ENGINE=ROCKSDB;
+CREATE TABLE t3 (id INT, kp1 INT, PRIMARY KEY (id), KEY(kp1)) ENGINE=ROCKSDB COMMENT='ttl_duration=1';
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+INSERT INTO t2 SELECT * FROM t1;
+INSERT INTO t3 SELECT * FROM t1;
+connection conn1;
+set debug_sync='rocksdb.check_flags_rmi SIGNAL parked WAIT_FOR go';
+SELECT value FROM t1 WHERE value = 3;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+KILL QUERY $conn1_id;
+set debug_sync='now SIGNAL go';
+connection conn1;
+ERROR 70100: Query execution was interrupted
+set debug_sync='RESET';
+connection conn1;
+set debug_sync='rocksdb.check_flags_rmi_scan SIGNAL parked WAIT_FOR go';
+SELECT DISTINCT(id) FROM t1 WHERE value = 5 AND id IN (1, 3, 5);
+connection default;
+set debug_sync='now WAIT_FOR parked';
+KILL QUERY $conn1_id;
+set debug_sync='now SIGNAL go';
+connection conn1;
+ERROR 70100: Query execution was interrupted
+set debug_sync='RESET';
+connection conn1;
+set debug_sync='rocksdb.check_flags_inwd SIGNAL parked WAIT_FOR go';
+SELECT value FROM t1 WHERE value > 3;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+KILL QUERY $conn1_id;
+set debug_sync='now SIGNAL go';
+connection conn1;
+ERROR 70100: Query execution was interrupted
+set debug_sync='RESET';
+connection conn1;
+set debug_sync='rocksdb.check_flags_rnwd SIGNAL parked WAIT_FOR go';
+SELECT id FROM t2;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+KILL QUERY $conn1_id;
+set debug_sync='now SIGNAL go';
+connection conn1;
+ERROR 70100: Query execution was interrupted
+set debug_sync='RESET';
+connection conn1;
+set debug_sync='rocksdb.check_flags_ser SIGNAL parked WAIT_FOR go';
+SELECT kp1 FROM t3 ORDER BY kp1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+KILL QUERY $conn1_id;
+set debug_sync='now SIGNAL go';
+connection conn1;
+ERROR 70100: Query execution was interrupted
+connection default;
+disconnect conn1;
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = DEFAULT;
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/check_ignore_unknown_options.result b/storage/rocksdb/mysql-test/rocksdb/r/check_ignore_unknown_options.result
new file mode 100644
index 00000000..6ff49908
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/check_ignore_unknown_options.result
@@ -0,0 +1,7 @@
+select variable_name, variable_value from information_schema.global_variables where variable_name="rocksdb_ignore_unknown_options";
+variable_name variable_value
+ROCKSDB_IGNORE_UNKNOWN_OPTIONS ON
+FOUND 1 /RocksDB: Compatibility check against existing database options failed/ in my_restart.err
+select variable_name, variable_value from information_schema.global_variables where variable_name="rocksdb_ignore_unknown_options";
+variable_name variable_value
+ROCKSDB_IGNORE_UNKNOWN_OPTIONS ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/check_table.result b/storage/rocksdb/mysql-test/rocksdb/r/check_table.result
new file mode 100644
index 00000000..116c168c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/check_table.result
@@ -0,0 +1,68 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+CHECK TABLE t1, t2 FOR UPGRADE;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+test.t2 check status OK
+INSERT INTO t2 (a,b) VALUES (5,'e');
+CHECK TABLE t2 QUICK;
+Table Op Msg_type Msg_text
+test.t2 check status OK
+INSERT INTO t1 (a,b) VALUES (6,'f');
+CHECK TABLE t1 FAST;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (7,'g');
+INSERT INTO t2 (a,b) VALUES (8,'h');
+CHECK TABLE t2, t1 MEDIUM;
+Table Op Msg_type Msg_text
+test.t2 check status OK
+test.t1 check status OK
+INSERT INTO t1 (a,b) VALUES (9,'i');
+INSERT INTO t2 (a,b) VALUES (10,'j');
+CHECK TABLE t1, t2 EXTENDED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+test.t2 check status OK
+INSERT INTO t1 (a,b) VALUES (11,'k');
+CHECK TABLE t1 CHANGED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a) VALUES (1),(2),(5);
+CHECK TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (6),(8),(12);
+CHECK TABLE t1 FOR UPGRADE;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (13),(15),(16);
+CHECK TABLE t1 QUICK;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (17),(120),(132);
+CHECK TABLE t1 FAST;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (801),(900),(7714);
+CHECK TABLE t1 MEDIUM;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (8760),(10023),(12000);
+CHECK TABLE t1 EXTENDED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+INSERT INTO t1 (a) VALUES (13345),(24456),(78302),(143028);
+CHECK TABLE t1 CHANGED;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/checkpoint.result b/storage/rocksdb/mysql-test/rocksdb/r/checkpoint.result
new file mode 100644
index 00000000..fd1ac636
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/checkpoint.result
@@ -0,0 +1,59 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+CREATE TABLE t1 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t2 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t3 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t4 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+DELETE FROM t4;
+CREATE TABLE t5 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t5;
+SET GLOBAL ROCKSDB_CREATE_CHECKPOINT = '[CHECKPOINT]';
+CURRENT
+SET GLOBAL ROCKSDB_CREATE_CHECKPOINT = '[CHECKPOINT]';
+CURRENT
+truncate table t1;
+optimize table t1;
+truncate table t2;
+optimize table t2;
+truncate table t3;
+optimize table t3;
+truncate table t4;
+optimize table t4;
+truncate table t5;
+optimize table t5;
+drop table if exists t1;
+drop table if exists t2;
+drop table if exists t3;
+drop table if exists t4;
+drop table if exists t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/checksum_table.result b/storage/rocksdb/mysql-test/rocksdb/r/checksum_table.result
new file mode 100644
index 00000000..bb209856
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/checksum_table.result
@@ -0,0 +1,92 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=0;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=0;
+CHECKSUM TABLE t1;
+Table Checksum
+test.t1 4259194219
+CHECKSUM TABLE t2, t1;
+Table Checksum
+test.t2 0
+test.t1 4259194219
+CHECKSUM TABLE t1, t2 QUICK;
+Table Checksum
+test.t1 NULL
+test.t2 NULL
+CHECKSUM TABLE t1, t2 EXTENDED;
+Table Checksum
+test.t1 4259194219
+test.t2 0
+DROP TABLE t1, t2;
+#
+# Issue #110: SQL command checksum returns inconsistent result
+#
+create table t1 (pk int primary key, col1 varchar(10)) engine=rocksdb;
+insert into t1 values (2,'fooo');
+insert into t1 values (1,NULL);
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+select * from t1 where pk=2;
+pk col1
+2 fooo
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+flush tables;
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+drop table t1;
+#
+# The following test is about making sure MyRocks CHECKSUM TABLE
+# values are the same as with InnoDB.
+# If you see checksum values changed, make sure their counterparts
+# in suite/innodb/r/checksum-matches-myrocks.result match.
+#
+create table t1 (pk int primary key, col1 varchar(10)) engine=rocksdb;
+insert into t1 values (2,'fooo');
+insert into t1 values (1,NULL);
+checksum table t1;
+Table Checksum
+test.t1 1303411884
+drop table t1;
+create table t1 (
+pk bigint unsigned primary key,
+col1 varchar(10),
+col2 tinyint,
+col3 double
+) engine=rocksdb;
+# MariaDB has changed the checksumming algorithm
+# Enable the old algorithm:
+set @tmp_old=@@old;
+set old=1;
+checksum table t1;
+Table Checksum
+test.t1 0
+insert into t1 values (1, NULL, NULL, NULL);
+insert into t1 values (2, 'foo', NULL, NULL);
+checksum table t1;
+Table Checksum
+test.t1 3633741545
+insert into t1 values (3, NULL, 123, NULL);
+insert into t1 values (4, NULL, NULL, 2.78);
+checksum table t1;
+Table Checksum
+test.t1 390004011
+insert into t1 values (5, 'xxxYYYzzzT', NULL, 2.78);
+insert into t1 values (6, '', NULL, 2.78);
+checksum table t1;
+Table Checksum
+test.t1 3183101003
+set old=@tmp_old;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/checksum_table_live.result b/storage/rocksdb/mysql-test/rocksdb/r/checksum_table_live.result
new file mode 100644
index 00000000..fb86c0af
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/checksum_table_live.result
@@ -0,0 +1,20 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+CHECKSUM TABLE t1;
+Table Checksum
+test.t1 4259194219
+CHECKSUM TABLE t2, t1;
+Table Checksum
+test.t2 0
+test.t1 4259194219
+CHECKSUM TABLE t1, t2 QUICK;
+Table Checksum
+test.t1 NULL
+test.t2 NULL
+CHECKSUM TABLE t1, t2 EXTENDED;
+Table Checksum
+test.t1 4259194219
+test.t2 0
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/col_opt_default.result b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_default.result
new file mode 100644
index 00000000..af53f061
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_default.result
@@ -0,0 +1,20 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY DEFAULT '0') ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) NO PRI 0
+INSERT INTO t1 (a) VALUES (1);
+SELECT a FROM t1;
+a
+1
+ALTER TABLE t1 ADD COLUMN b CHAR(8) DEFAULT '';
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a int(11) NO PRI 0
+b char(8) YES
+INSERT INTO t1 (b) VALUES ('a');
+SELECT a,b FROM t1 ORDER BY a,b;
+a b
+0 a
+1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/col_opt_not_null.result b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_not_null.result
new file mode 100644
index 00000000..e5a8d2dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_not_null.result
@@ -0,0 +1,2613 @@
+########################
+# BINARY columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+b BINARY NOT NULL,
+b0 BINARY(0) NOT NULL,
+b1 BINARY(1) NOT NULL,
+b20 BINARY(20) NOT NULL,
+b255 BINARY(255) NOT NULL,
+pk BINARY PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) NO NULL
+b0 binary(0) NO NULL
+b1 binary(1) NO NULL
+b20 binary(20) NO NULL
+b255 binary(255) NO NULL
+pk binary(1) NO PRI NULL
+INSERT INTO t1 VALUES ('','','','','','');
+INSERT INTO t1 VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.','a');
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+INSERT INTO t1 VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256),'b');
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b20' at row 1
+Warning 1265 Data truncated for column 'b255' at row 1
+INSERT INTO t1 SELECT b255, b255, b255, b255, CONCAT('a',b255,b255), 'c' FROM t1;
+ERROR 23000: Duplicate entry 'c' for key 'PRIMARY'
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 61 6161616161616161616161616161616161616161 787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878 62
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+ALTER TABLE t1 ADD COLUMN b257 BINARY(257) NOT NULL;
+ERROR 42000: Column length too big for column 'b257' (max = 255); use BLOB or TEXT instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) NO NULL
+b0 binary(0) NO NULL
+b1 binary(1) NO NULL
+b20 binary(20) NO NULL
+b255 binary(255) NO NULL
+pk binary(1) NO PRI NULL
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# BINARY NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c BINARY NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c binary(1) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+SELECT HEX(c) FROM t1;
+HEX(c)
+30
+DROP TABLE t1;
+#----------------------------------
+# BINARY NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c BINARY NOT NULL DEFAULT 0
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c binary(1) NO 0
+ALTER TABLE t1 ADD COLUMN err BINARY NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 30
+2 30
+DROP TABLE t1;
+########################
+# VARBINARY columns
+########################
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARBINARY(0) NOT NULL,
+v1 VARBINARY(1) NOT NULL,
+v64 VARBINARY(64) NOT NULL,
+v65000 VARBINARY(65000) NOT NULL,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) NO NULL
+v1 varbinary(1) NO NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) NO NULL
+CREATE TABLE t2 (v VARBINARY(65532) NOT NULL, PRIMARY KEY(v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varbinary(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT HEX(v0), HEX(v1), HEX(v64), HEX(v65000) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) HEX(v65000)
+
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E2057652776652070726F7669646564206C696E6B7320746F20416D617A6F6E2E636F6D206865726520666F7220636F6E76656E69656E63652C2062757420746865792063616E20626520666F756E64206174206D616E79206F7468657220626F6F6B73746F7265732C20626F7468206F6E6C696E6520616E64206F66662E0A0A2020496620796F752077616E7420746F206861766520796F7572206661766F72697465204D7953514C202F204D61726961444220626F6F6B206C697374656420686572652C20706C65617365206C65617665206120636F6D6D656E742E0A2020466F7220646576656C6F706572732077686F2077616E7420746F20636F6465206F6E204D617269614442206F72204D7953514C0A0A2020202020202A20556E6465727374616E64696E67204D7953514C20496E7465726E616C73206279205361736861205061636865762C20666F726D6572204D7953514C20646576656C6F706572206174204D7953514C2041422E0A2020202020202020202020206F205468697320697320746865206F6E6C7920626F6F6B207765206B6E6F772061626F75742074686174206465736372696265732074686520696E7465726E616C73206F66204D617269614442202F204D7953514C2E2041206D757374206861766520666F7220616E796F6E652077686F2077616E747320746F20756E6465727374616E6420616E6420646576656C6F70206F6E204D617269614442210A2020202020202020202020206F204E6F7420616C6C20746F706963732061726520636F766572656420616E6420736F6D652070617274732061726520736C696768746C79206F757464617465642C20627574207374696C6C20746865206265737420626F6F6B206F6E207468697320746F7069632E200A2020202020202A204D7953514C20352E3120506C7567696E20446576656C6F706D656E742062792053657267656920476F6C75626368696B20616E6420416E64726577204875746368696E67730A2020202020202020202020206F2041206D757374207265616420666F7220616E796F6E652077616E74696E6720746F207772697465206120706C7567696E20666F72204D6172696144422C207772697474656E20627920746865205365726765692077686F2064657369676E65642074686520706C7567696E20696E7465726661636520666F72204D7953514C20616E64204D61726961444221200A0A2020466F72204D617269614442202F204D7953514C20656E642075736572730A0A2020202020202A204D61726961444220437261736820436F757273652062792042656E20466F7274610A2020202020202020202020206F204669727374204D61726961444220626F6F6B210A2020202020202020202020206F20466F722070656F706C652077686F2077616E7420746F206C6561726E2053514C20616E642074686520626173696373206F66204D6172696144422E0A2020202020202020202020206F204E6F77207368697070696E672E20507572636861736520617420416D617A6F6E2E636F6D206F7220796F7572206661766F7269746520626F6F6B73656C6C65722E200A0A2020202020202A2053514C2D393920436F6D706C6574652C205265616C6C792062792050657465722047756C75747A616E20262054727564792050656C7A65722E0A2020202020202020202020206F2045766572797468696E6720796F752077616E74656420746F206B6E6F772061626F7574207468652053514C203939207374616E646172642E20457863656C6C656E74207265666572656E636520626F6F6B210A2020202020202020202020206F204672656520746F207265616420696E20746865204B6E6F776C656467656261736521200A0A2020202020202A204D7953514C20283474682045646974696F6E29206279205061756C204475426F69730A2020202020202020202020206F20546865202764656661756C742720626F6F6B20746F207265616420696620796F7520776F6E7420746F206C6561726E20746F20757365204D7953514C202F204D6172696144422E200A0A2020202020202A204D7953514C20436F6F6B626F6F6B206279205061756C204475426F69730A2020202020202020202020206F2041206C6F74206F66206578616D706C6573206F6620686F7720746F20757365204D7953514C2E204173207769746820616C6C206F66205061756C277320626F6F6B732C206974277320776F727468206974732077656967687420696E20676F6C6420616E64206576656E20656E6A6F7961626C652072656164696E6720666F7220737563682061202764727927207375626A6563742E200A0A2020202020202A204869676820506572666F726D616E6365204D7953514C2C205365636F6E642045646974696F6E2C204279204261726F6E20536368776172747A2C205065746572205A6169747365762C20566164696D20546B616368656E6B6F2C204A6572656D7920442E205A61776F646E792C2041726A656E204C656E747A2C20446572656B204A2E2042616C6C696E672C20657420616C2E0A2020202020202020202020206F20224869676820506572666F726D616E6365204D7953514C2069732074686520646566696E697469766520677569646520746F206275696C64696E6720666173742C2072656C6961626C652073797374656D732077697468204D7953514C2E205772697474656E206279206E6F74656420657870657274732077697468207965617273206F66207265616C2D776F726C6420657870657269656E6365206275696C64696E672076657279206C617267652073797374656D732C207468697320626F6F6B20636F7665727320657665727920617370656374206F66204D7953514C20706572666F726D616E636520696E2064657461696C2C20616E6420666F6375736573206F6E20726F627573746E6573732C2073656375726974792C20616E64206461746120696E746567726974792E204C6561726E20616476616E63656420746563686E697175657320696E20646570746820736F20796F752063616E206272696E67206F7574204D7953514C27732066756C6C20706F7765722E22202846726F6D2074686520626F6F6B206465736372697074696F6E206174204F275265696C6C7929200A0A2020202020202A204D7953514C2041646D696E20436F6F6B626F6F6B0A2020202020202020202020206F204120717569636B20737465702D62792D7374657020677569646520666F72204D7953514C20757365727320616E642064617461626173652061646D696E6973747261746F727320746F207461636B6C65207265616C2D776F726C64206368616C6C656E6765732077697468204D7953514C20636F6E66696775726174696F6E20616E642061646D696E697374726174696F6E200A0A2020202020202A204D7953514C20352E302043657274696669636174696F6E2053747564792047756964652C204279205061756C204475426F69732C2053746566616E2048696E7A2C204361727374656E20506564657273656E0A2020202020202020202020206F205468697320697320746865206F6666696369616C20677569646520746F20636F766572207468652070617373696E67206F66207468652074776F204D7953514C2043657274696669636174696F6E206578616D696E6174696F6E732E2049742069732076616C69642074696C6C2076657273696F6E20352E30206F6620746865207365727665722C20736F207768696C65206974206D697373657320616C6C2074686520666561747572657320617661696C61626C6520696E204D7953514C20352E3120616E6420677265617465722028696E636C7564696E67204D61726961444220352E3120616E642067726561746572292C2069742070726F7669646573206120676F6F6420626173696320756E6465727374616E64696E67206F66204D7953514C20666F722074686520656E642D757365722E20
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+Warning 1265 Data truncated for column 'v65000' at row 6
+SELECT HEX(v0), HEX(v1), HEX(v64), LENGTH(HEX(v65000)) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) LENGTH(HEX(v65000))
+ 0
+ 61 0
+ 48 61486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E205765277665 5932
+ 61 61616263646566676869206162636465666768692061626364656667686920616263646566676869206162636465666768692061626364656667686920616263 130000
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 5930
+ 79 63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363 130000
+ALTER TABLE t1 ADD COLUMN v65536 VARBINARY(65536) NOT NULL;
+Warnings:
+Note 1246 Converting column 'v65536' from VARBINARY to BLOB
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) NO NULL
+v1 varbinary(1) NO NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) NO NULL
+v65536 mediumblob NO NULL
+DROP TABLE t1, t2;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# VARBINARY(64) NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c VARBINARY(64) NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c varbinary(64) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test');
+SELECT HEX(c) FROM t1;
+HEX(c)
+74657374
+DROP TABLE t1;
+#----------------------------------
+# VARBINARY(64) NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c VARBINARY(64) NOT NULL DEFAULT 'test'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c varbinary(64) NO test
+ALTER TABLE t1 ADD COLUMN err VARBINARY(64) NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 74657374
+2 74657374
+DROP TABLE t1;
+########################
+# BIT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a BIT NOT NULL,
+b BIT(20) NOT NULL,
+c BIT(64) NOT NULL,
+d BIT(1) NOT NULL,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) NO NULL
+b bit(20) NO NULL
+c bit(64) NO PRI NULL
+d bit(1) NO NULL
+ALTER TABLE t1 DROP COLUMN d;
+ALTER TABLE t1 ADD COLUMN d BIT(0) NOT NULL;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) NO NULL
+b bit(20) NO NULL
+c bit(64) NO PRI NULL
+d bit(1) NO NULL
+INSERT INTO t1 (a,b,c,d) VALUES (0,POW(2,20)-1,b'1111111111111111111111111111111111111111111111111111111111111111',1);
+SELECT BIN(a), HEX(b), c+0 FROM t1 WHERE d>0;
+BIN(a) HEX(b) c+0
+0 FFFFF 18446744073709551615
+INSERT INTO t1 (a,b,c,d) VALUES (1,0,-2,0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d<100;
+a+0 b+0 c+0
+0 1048575 18446744073709551615
+1 0 18446744073709551614
+INSERT INTO t1 (a,b,c,d) VALUES (b'1', 'f', 0xFF, 0x0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d IN (0, 2);
+a+0 b+0 c+0
+1 0 18446744073709551614
+1 102 255
+DELETE FROM t1;
+INSERT INTO t1 (a,b,c,d) VALUES (0x10,0,0,1);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+INSERT INTO t1 (a,b,c,d) VALUES (0x01,0,0x10000000000000000,0);
+Warnings:
+Warning 1264 Out of range value for column 'c' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+1 0 18446744073709551615 0
+DROP TABLE t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a BIT(65) NOT NULL) ENGINE=rocksdb;
+ERROR 42000: Display width out of range for 'a' (max = 64)
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# BIT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c BIT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c bit(1) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1);
+SELECT HEX(c) FROM t1;
+HEX(c)
+1
+DROP TABLE t1;
+#----------------------------------
+# BIT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c BIT NOT NULL DEFAULT 1
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c bit(1) NO b'1'
+ALTER TABLE t1 ADD COLUMN err BIT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 1
+2 1
+DROP TABLE t1;
+########################
+# BLOB columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b BLOB NOT NULL,
+b0 BLOB(0) NOT NULL,
+b1 BLOB(1) NOT NULL,
+b300 BLOB(300) NOT NULL,
+bm BLOB(65535) NOT NULL,
+b70k BLOB(70000) NOT NULL,
+b17m BLOB(17000000) NOT NULL,
+t TINYBLOB NOT NULL,
+m MEDIUMBLOB NOT NULL,
+l LONGBLOB NOT NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b blob NO NULL
+b0 blob NO NULL
+b1 tinyblob NO NULL
+b300 blob NO NULL
+bm blob NO NULL
+b70k mediumblob NO NULL
+b17m longblob NO NULL
+t tinyblob NO NULL
+m mediumblob NO NULL
+l longblob NO NULL
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), HEX(REPEAT('g',1048576)), REPEAT('h',255), REPEAT('i',1048576), HEX(REPEAT('j',1048576)) );
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b300' at row 1
+Warning 1265 Data truncated for column 'bm' at row 1
+Warning 1265 Data truncated for column 't' at row 1
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+ALTER TABLE t1 ADD COLUMN bbb BLOB(4294967296);
+ERROR 42000: Display width out of range for 'bbb' (max = 4294967295)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# BLOB NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c BLOB NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c blob NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# BLOB NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c BLOB NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c blob NO ''
+ALTER TABLE t1 ADD COLUMN err BLOB NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TINYBLOB NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TINYBLOB NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyblob NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# TINYBLOB NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TINYBLOB NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyblob NO ''
+ALTER TABLE t1 ADD COLUMN err TINYBLOB NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# MEDIUMBLOB NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c MEDIUMBLOB NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumblob NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# MEDIUMBLOB NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c MEDIUMBLOB NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumblob NO ''
+ALTER TABLE t1 ADD COLUMN err MEDIUMBLOB NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# LONGBLOB NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c LONGBLOB NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c longblob NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# LONGBLOB NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c LONGBLOB NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c longblob NO ''
+ALTER TABLE t1 ADD COLUMN err LONGBLOB NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+########################
+# BOOL columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b1 BOOL NOT NULL,
+b2 BOOLEAN NOT NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b1 tinyint(1) NO NULL
+b2 tinyint(1) NO NULL
+INSERT INTO t1 (b1,b2) VALUES (1,TRUE);
+SELECT b1,b2 FROM t1;
+b1 b2
+1 1
+INSERT INTO t1 (b1,b2) VALUES (FALSE,0);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+INSERT INTO t1 (b1,b2) VALUES (2,3);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (-1,-2);
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+1 1
+2 3
+SELECT IF(b1,'true','false') AS a, IF(b2,'true','false') AS b FROM t1;
+a b
+false false
+true true
+true true
+true true
+SELECT b1,b2 FROM t1 WHERE b1 = TRUE;
+b1 b2
+1 1
+SELECT b1,b2 FROM t1 WHERE b2 = FALSE;
+b1 b2
+0 0
+INSERT INTO t1 (b1,b2) VALUES ('a','b');
+Warnings:
+Warning 1366 Incorrect integer value: 'a' for column `test`.`t1`.`b1` at row 1
+Warning 1366 Incorrect integer value: 'b' for column `test`.`t1`.`b2` at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (128,-129);
+Warnings:
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b2' at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+127 -128
+2 3
+ALTER TABLE t1 ADD COLUMN b3 BOOLEAN UNSIGNED NOT NULL;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UNSIGNED NOT NULL' at line 1
+ALTER TABLE ADD COLUMN b3 BOOL ZEROFILL NOT NULL;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'ADD COLUMN b3 BOOL ZEROFILL NOT NULL' at line 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# BOOL NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c BOOL NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyint(1) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('0');
+SELECT HEX(c) FROM t1;
+HEX(c)
+0
+DROP TABLE t1;
+#----------------------------------
+# BOOL NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c BOOL NOT NULL DEFAULT '0'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyint(1) NO 0
+ALTER TABLE t1 ADD COLUMN err BOOL NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('0');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 0
+2 0
+DROP TABLE t1;
+########################
+# CHAR columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c CHAR NOT NULL,
+c0 CHAR(0) NOT NULL,
+c1 CHAR(1) NOT NULL,
+c20 CHAR(20) NOT NULL,
+c255 CHAR(255) NOT NULL,
+PRIMARY KEY (c255)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c char(1) NO NULL
+c0 char(0) NO NULL
+c1 char(1) NO NULL
+c20 char(20) NO NULL
+c255 char(255) NO PRI NULL
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('','','','','');
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.');
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256));
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+Warning 1265 Data truncated for column 'c0' at row 1
+Warning 1265 Data truncated for column 'c1' at row 1
+Warning 1265 Data truncated for column 'c20' at row 1
+Warning 1265 Data truncated for column 'c255' at row 1
+INSERT INTO t1 (c,c0,c1,c20,c255) SELECT c255, c255, c255, c255, CONCAT('a',c255,c1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 5
+Warning 1265 Data truncated for column 'c0' at row 5
+Warning 1265 Data truncated for column 'c1' at row 5
+Warning 1265 Data truncated for column 'c20' at row 5
+Warning 1265 Data truncated for column 'c' at row 6
+Warning 1265 Data truncated for column 'c0' at row 6
+Warning 1265 Data truncated for column 'c1' at row 6
+Warning 1265 Data truncated for column 'c20' at row 6
+Warning 1265 Data truncated for column 'c255' at row 6
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+ a
+C C Creating an article aCreating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.b
+a a aaaaaaaaaaaaaaaaaaaa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+x x xxxxxxxxxxxxxxxxxxxx axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+SELECT DISTINCT c20, REPEAT('a',LENGTH(c20)), COUNT(*) FROM t1 GROUP BY c1, c20;
+c20 REPEAT('a',LENGTH(c20)) COUNT(*)
+ 2
+Creating an article aaaaaaaaaaaaaaaaaaa 1
+aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa 1
+abcdefghi klmnopqrst aaaaaaaaaaaaaaaaaaaa 1
+xxxxxxxxxxxxxxxxxxxx aaaaaaaaaaaaaaaaaaaa 1
+ALTER TABLE t1 ADD COLUMN c257 CHAR(257) NOT NULL;
+ERROR 42000: Column length too big for column 'c257' (max = 255); use BLOB or TEXT instead
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# CHAR NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c CHAR NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c char(1) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('_');
+SELECT HEX(c) FROM t1;
+HEX(c)
+5F
+DROP TABLE t1;
+#----------------------------------
+# CHAR NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c CHAR NOT NULL DEFAULT '_'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c char(1) NO _
+ALTER TABLE t1 ADD COLUMN err CHAR NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('_');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 5F
+2 5F
+DROP TABLE t1;
+########################
+# VARCHAR columns
+########################
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARCHAR(0) NOT NULL,
+v1 VARCHAR(1) NOT NULL,
+v64 VARCHAR(64) NOT NULL,
+v65000 VARCHAR(65000) NOT NULL,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) NO NULL
+v1 varchar(1) NO NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) NO NULL
+CREATE TABLE t2 (v VARCHAR(65532), PRIMARY KEY (v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varchar(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT v0,v1,v64,v65000 FROM t1;
+v0 v1 v64 v65000
+
+
+
+
+
+
+
+
+
+
+
+ y Once there, double check that an article doesn't already exist Here is a list of recommended books on MariaDB and MySQL. We've provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+ o "High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL's full power." (From the book description at O'Reilly)
+ o A lot of examples of how to use MySQL. As with all of Paul's books, it's worth its weight in gold and even enjoyable reading for such a 'dry' subject.
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Free to read in the Knowledgebase!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+ o The 'default' book to read if you wont to learn to use MySQL / MariaDB.
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ * MariaDB Crash Course by Ben Forta
+ * MySQL (4th Edition) by Paul DuBois
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ * MySQL Admin Cookbook
+ * MySQL Cookbook by Paul DuBois
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ For MariaDB / MySQL end users
+ For developers who want to code on MariaDB or MySQL
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v65000' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+SELECT v0, v1, v64, LENGTH(v65000) FROM t1;
+v0 v1 v64 LENGTH(v65000)
+ 0
+ a 0
+ H aHere is a list of recommended books on MariaDB and MySQL. We've 2966
+ a aabcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abc 65000
+ y Once there, double check that an article doesn't already exist 2965
+ y cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc 65000
+ALTER TABLE t1 ADD COLUMN v65536 VARCHAR(65536) NOT NULL;
+Warnings:
+Note 1246 Converting column 'v65536' from VARCHAR to TEXT
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) NO NULL
+v1 varchar(1) NO NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) NO NULL
+v65536 mediumtext NO NULL
+DROP TABLE t1, t2;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# VARCHAR(64) NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c VARCHAR(64) NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c varchar(64) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test default');
+SELECT HEX(c) FROM t1;
+HEX(c)
+746573742064656661756C74
+DROP TABLE t1;
+#----------------------------------
+# VARCHAR(64) NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c VARCHAR(64) NOT NULL DEFAULT 'test default'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c varchar(64) NO test default
+ALTER TABLE t1 ADD COLUMN err VARCHAR(64) NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test default');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 746573742064656661756C74
+2 746573742064656661756C74
+DROP TABLE t1;
+########################
+# date and time columns
+########################
+set @col_opt_not_nullsave_time_zone=@@time_zone;
+set time_zone='UTC';
+DROP TABLE IF EXISTS t1;
+set @save_time_zone=@@time_zone;
+set time_zone='UTC';
+CREATE TABLE t1 (
+d DATE NOT NULL,
+dt DATETIME NOT NULL,
+ts TIMESTAMP NOT NULL,
+t TIME NOT NULL,
+y YEAR NOT NULL,
+y4 YEAR(4) NOT NULL,
+y2 YEAR(2) NOT NULL,
+pk DATETIME PRIMARY KEY
+) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d date NO NULL
+dt datetime NO NULL
+ts timestamp NO NULL
+t time NO NULL
+y year(4) NO NULL
+y4 year(4) NO NULL
+y2 year(2) NO NULL
+pk datetime NO PRI NULL
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('1000-01-01', '1000-01-01 00:00:00', FROM_UNIXTIME(1), '-838:59:59', '1901', '1901', '00','2012-12-12 12:12:12'),
+('9999-12-31', '9999-12-31 23:59:59', FROM_UNIXTIME(2147483647), '838:59:59', '2155', '2155', '99','2012-12-12 12:12:13'),
+('0000-00-00', '0000-00-00 00:00:00', '0000-00-00 00:00:00', '00:00:00', '0', '0', '0','2012-12-12 12:12:14'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),YEAR(@tm),YEAR(@tm),'2012-12-12 12:12:15');
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('999-13-32', '999-11-31 00:00:00', '0', '-839:00:00', '1900', '1900', '-1','2012-12-12 12:12:16');
+Warnings:
+Warning 1265 Data truncated for column 'd' at row 1
+Warning 1265 Data truncated for column 'dt' at row 1
+Warning 1265 Data truncated for column 'ts' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 'y' at row 1
+Warning 1264 Out of range value for column 'y4' at row 1
+Warning 1264 Out of range value for column 'y2' at row 1
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 -838:59:59 0000 0000 00
+set time_zone=@save_time_zone;
+DROP TABLE t1;
+SET TIMESTAMP=UNIX_TIMESTAMP('2013-12-12 12:12:12');
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# DATE NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c DATE NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c date NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012-12-21');
+SELECT HEX(c) FROM t1;
+HEX(c)
+323031322D31322D3231
+DROP TABLE t1;
+#----------------------------------
+# DATE NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c DATE NOT NULL DEFAULT '2012-12-21'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c date NO 2012-12-21
+ALTER TABLE t1 ADD COLUMN err DATE NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012-12-21');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 323031322D31322D3231
+2 323031322D31322D3231
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# DATETIME NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c DATETIME NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c datetime NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012-12-21 12:21:12');
+SELECT HEX(c) FROM t1;
+HEX(c)
+323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+#----------------------------------
+# DATETIME NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c DATETIME NOT NULL DEFAULT '2012-12-21 12:21:12'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c datetime NO 2012-12-21 12:21:12
+ALTER TABLE t1 ADD COLUMN err DATETIME NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012-12-21 12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 323031322D31322D32312031323A32313A3132
+2 323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TIMESTAMP NOT NULL column without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TIMESTAMP NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c timestamp NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+INSERT INTO t1 (c) VALUES ('2012-12-21 12:21:12');
+SELECT HEX(c) FROM t1;
+HEX(c)
+323031332D31322D31322031323A31323A3132
+323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+#----------------------------------
+# TIMESTAMP NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TIMESTAMP NOT NULL DEFAULT '2012-12-21 12:21:12'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c timestamp NO 2012-12-21 12:21:12
+ALTER TABLE t1 ADD COLUMN err TIMESTAMP NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+set @save_ts=@@timestamp;
+set timestamp=1478923914;
+INSERT INTO t1 (c) VALUES (NULL);
+set timestamp=@save_ts;
+INSERT INTO t1 (c) VALUES ('2012-12-21 12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 323031362D31312D31322030343A31313A3534
+2 323031322D31322D32312031323A32313A3132
+3 323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TIME NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TIME NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c time NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('12:21:12');
+SELECT HEX(c) FROM t1;
+HEX(c)
+31323A32313A3132
+DROP TABLE t1;
+#----------------------------------
+# TIME NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TIME NOT NULL DEFAULT '12:21:12'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c time NO 12:21:12
+ALTER TABLE t1 ADD COLUMN err TIME NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 31323A32313A3132
+2 31323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# YEAR NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c YEAR NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c year(4) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012');
+SELECT HEX(c) FROM t1;
+HEX(c)
+7DC
+DROP TABLE t1;
+#----------------------------------
+# YEAR NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c YEAR NOT NULL DEFAULT '2012'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c year(4) NO 2012
+ALTER TABLE t1 ADD COLUMN err YEAR NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('2012');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 7DC
+2 7DC
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# YEAR(2) NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c YEAR(2) NOT NULL) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c year(2) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('12');
+SELECT HEX(c) FROM t1;
+HEX(c)
+C
+DROP TABLE t1;
+#----------------------------------
+# YEAR(2) NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c YEAR(2) NOT NULL DEFAULT '12'
+) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c year(2) NO 12
+ALTER TABLE t1 ADD COLUMN err YEAR(2) NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 C
+2 C
+DROP TABLE t1;
+set time_zone= @col_opt_not_nullsave_time_zone;
+########################
+# ENUM columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a ENUM('') NOT NULL,
+b ENUM('test1','test2','test3','test4','test5') NOT NULL,
+c ENUM('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') NOT NULL,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') NO NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') NO NULL
+INSERT INTO t1 (a,b,c) VALUES ('','test2','4'),('',5,2);
+SELECT a,b,c FROM t1;
+a b c
+ test2 4
+ test5 2
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'a' at row 1
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+
+ test2 4
+ test5 2
+ALTER TABLE t1 ADD COLUMN e ENUM('a','A') NOT NULL;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in ENUM
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') NO NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') NO NULL
+e enum('a','A') NO NULL
+INSERT INTO t1 (a,b,c,e) VALUES ('','test3','75','A');
+SELECT a,b,c,e FROM t1;
+a b c e
+ a
+ test2 4 a
+ test3 75 a
+ test5 2 a
+SELECT a,b,c,e FROM t1 WHERE b='test2' OR a != '';
+a b c e
+ test2 4 a
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# ENUM('test1','test2','test3') NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c ENUM('test1','test2','test3') NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c enum('test1','test2','test3') NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test2');
+SELECT HEX(c) FROM t1;
+HEX(c)
+7465737432
+DROP TABLE t1;
+#----------------------------------
+# ENUM('test1','test2','test3') NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c ENUM('test1','test2','test3') NOT NULL DEFAULT 'test2'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c enum('test1','test2','test3') NO test2
+ALTER TABLE t1 ADD COLUMN err ENUM('test1','test2','test3') NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test2');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 7465737432
+2 7465737432
+DROP TABLE t1;
+########################
+# Fixed point columns (NUMERIC, DECIMAL)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DECIMAL NOT NULL,
+d0 DECIMAL(0) NOT NULL,
+d1_1 DECIMAL(1,1) NOT NULL,
+d10_2 DECIMAL(10,2) NOT NULL,
+d60_10 DECIMAL(60,10) NOT NULL,
+n NUMERIC NOT NULL,
+n0_0 NUMERIC(0,0) NOT NULL,
+n1 NUMERIC(1) NOT NULL,
+n20_4 NUMERIC(20,4) NOT NULL,
+n65_4 NUMERIC(65,4) NOT NULL,
+pk NUMERIC NOT NULL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d decimal(10,0) NO NULL
+d0 decimal(10,0) NO NULL
+d1_1 decimal(1,1) NO NULL
+d10_2 decimal(10,2) NO NULL
+d60_10 decimal(60,10) NO NULL
+n decimal(10,0) NO NULL
+n0_0 decimal(10,0) NO NULL
+n1 decimal(1,0) NO NULL
+n20_4 decimal(20,4) NO NULL
+n65_4 decimal(65,4) NO NULL
+pk decimal(10,0) NO PRI NULL
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+6
+);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+Warnings:
+Note 1265 Data truncated for column 'd' at row 1
+Note 1265 Data truncated for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Note 1265 Data truncated for column 'd10_2' at row 1
+Note 1265 Data truncated for column 'd60_10' at row 1
+Note 1265 Data truncated for column 'n' at row 1
+Note 1265 Data truncated for column 'n0_0' at row 1
+Note 1265 Data truncated for column 'n1' at row 1
+Note 1265 Data truncated for column 'n20_4' at row 1
+Note 1265 Data truncated for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.00 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.0000 9999999999999999999999999999999999999999999999999999999999999.1111
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) NOT NULL;
+ERROR 42000: Too big precision 66 specified for 'n66'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) NOT NULL;
+ERROR 42000: Too big precision 66 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) NOT NULL;
+ERROR 42000: Too big scale 66 specified for 'n66_66'. Maximum is 38
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# DECIMAL NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c DECIMAL NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c decimal(10,0) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1.1);
+Warnings:
+Note 1265 Data truncated for column 'c' at row 1
+SELECT HEX(c) FROM t1;
+HEX(c)
+1
+DROP TABLE t1;
+#----------------------------------
+# DECIMAL NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c DECIMAL NOT NULL DEFAULT 1.1
+) ENGINE=rocksdb;
+Warnings:
+Note 1265 Data truncated for column 'c' at row 1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c decimal(10,0) NO 1
+ALTER TABLE t1 ADD COLUMN err DECIMAL NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1.1);
+Warnings:
+Note 1265 Data truncated for column 'c' at row 1
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 1
+2 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# NUMERIC NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c NUMERIC NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c decimal(10,0) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+SELECT HEX(c) FROM t1;
+HEX(c)
+0
+DROP TABLE t1;
+#----------------------------------
+# NUMERIC NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c NUMERIC NOT NULL DEFAULT 0
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c decimal(10,0) NO 0
+ALTER TABLE t1 ADD COLUMN err NUMERIC NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 0
+2 0
+DROP TABLE t1;
+########################
+# Floating point columns (FLOAT, DOUBLE)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT NOT NULL,
+f0 FLOAT(0) NOT NULL,
+r1_1 REAL(1,1) NOT NULL,
+f23_0 FLOAT(23) NOT NULL,
+f20_3 FLOAT(20,3) NOT NULL,
+d DOUBLE NOT NULL,
+d1_0 DOUBLE(1,0) NOT NULL,
+d10_10 DOUBLE PRECISION (10,10) NOT NULL,
+d53 DOUBLE(53,0) NOT NULL,
+d53_10 DOUBLE(53,10) NOT NULL,
+pk DOUBLE NOT NULL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+f float NO NULL
+f0 float NO NULL
+r1_1 double(1,1) NO NULL
+f23_0 float NO NULL
+f20_3 float(20,3) NO NULL
+d double NO NULL
+d1_0 double(1,0) NO NULL
+d10_10 double(10,10) NO NULL
+d53 double(53,0) NO NULL
+d53_10 double(53,10) NO NULL
+pk double NO PRI NULL
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 11111111.111
+d10_10 0.0123456789
+d1_0 8
+d53 1234566789123456800
+d53_10 100000000000000000.0000000000
+f0 12345.1
+f20_3 56789.988
+f23_0 123457000
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+99999999999999999999999999999999999999,
+99999999999999999999999999999999999999.9999999999999999,
+0.9,
+99999999999999999999999999999999999999.9,
+99999999999999999.999,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+9,
+0.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+3
+);
+Warnings:
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 11111111.111
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 8
+d1_0 9
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 1e38
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+SELECT
+CONCAT('', MAX(f)),
+CONCAT('', MAX(f0)),
+CONCAT('', MAX(r1_1)),
+CONCAT('', MAX(f23_0)),
+CONCAT('', MAX(f20_3)),
+CONCAT('', MAX(d)),
+CONCAT('', MAX(d1_0)),
+CONCAT('', MAX(d10_10)),
+CONCAT('', MAX(d53)),
+CONCAT('', MAX(d53_10)) FROM t1;
+CONCAT('', MAX(f)) 1e38
+CONCAT('', MAX(d)) 1e81
+CONCAT('', MAX(d10_10)) 0.9999999999
+CONCAT('', MAX(d1_0)) 9
+CONCAT('', MAX(d53)) 100000000000000000000000000000000000000000000000000000
+CONCAT('', MAX(d53_10)) 10000000000000000000000000000000000000000000.0000000000
+CONCAT('', MAX(f0)) 1e38
+CONCAT('', MAX(f20_3)) 99999998430674940.000
+CONCAT('', MAX(f23_0)) 1e38
+CONCAT('', MAX(r1_1)) 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+5
+);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+999999999999999999999999999999999999999,
+999999999999999999999999999999999999999.9999999999999999,
+1.9,
+999999999999999999999999999999999999999.9,
+999999999999999999.999,
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+99,
+1.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+6
+);
+Warnings:
+Warning 1916 Got overflow when converting '' to DECIMAL. Value truncated
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e65
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) NOT NULL;
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) NOT NULL;
+ERROR 42000: Too big precision 256 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) NOT NULL;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# FLOAT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c FLOAT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c float NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1.1);
+SELECT HEX(c) FROM t1;
+HEX(c)
+1
+DROP TABLE t1;
+#----------------------------------
+# FLOAT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c FLOAT NOT NULL DEFAULT 1.1
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c float NO 1.1
+ALTER TABLE t1 ADD COLUMN err FLOAT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1.1);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 1
+2 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# DOUBLE NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c DOUBLE NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c double NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+SELECT HEX(c) FROM t1;
+HEX(c)
+0
+DROP TABLE t1;
+#----------------------------------
+# DOUBLE NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c DOUBLE NOT NULL DEFAULT 0
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c double NO 0
+ALTER TABLE t1 ADD COLUMN err DOUBLE NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 0
+2 0
+DROP TABLE t1;
+########################
+# INT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT NOT NULL,
+i0 INT(0) NOT NULL,
+i1 INT(1) NOT NULL,
+i20 INT(20) NOT NULL,
+t TINYINT NOT NULL,
+t0 TINYINT(0) NOT NULL,
+t1 TINYINT(1) NOT NULL,
+t20 TINYINT(20) NOT NULL,
+s SMALLINT NOT NULL,
+s0 SMALLINT(0) NOT NULL,
+s1 SMALLINT(1) NOT NULL,
+s20 SMALLINT(20) NOT NULL,
+m MEDIUMINT NOT NULL,
+m0 MEDIUMINT(0) NOT NULL,
+m1 MEDIUMINT(1) NOT NULL,
+m20 MEDIUMINT(20) NOT NULL,
+b BIGINT NOT NULL,
+b0 BIGINT(0) NOT NULL,
+b1 BIGINT(1) NOT NULL,
+b20 BIGINT(20) NOT NULL,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+i int(11) NO NULL
+i0 int(11) NO NULL
+i1 int(1) NO NULL
+i20 int(20) NO NULL
+t tinyint(4) NO NULL
+t0 tinyint(4) NO NULL
+t1 tinyint(1) NO NULL
+t20 tinyint(20) NO NULL
+s smallint(6) NO NULL
+s0 smallint(6) NO NULL
+s1 smallint(1) NO NULL
+s20 smallint(20) NO NULL
+m mediumint(9) NO NULL
+m0 mediumint(9) NO NULL
+m1 mediumint(1) NO NULL
+m20 mediumint(20) NO NULL
+b bigint(20) NO NULL
+b0 bigint(20) NO NULL
+b1 bigint(1) NO NULL
+b20 bigint(20) NO NULL
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 8
+Warning 1264 Out of range value for column 'i0' at row 8
+Warning 1264 Out of range value for column 'i1' at row 8
+Warning 1264 Out of range value for column 'i20' at row 8
+Warning 1264 Out of range value for column 't' at row 8
+Warning 1264 Out of range value for column 't0' at row 8
+Warning 1264 Out of range value for column 't1' at row 8
+Warning 1264 Out of range value for column 't20' at row 8
+Warning 1264 Out of range value for column 's' at row 8
+Warning 1264 Out of range value for column 's0' at row 8
+Warning 1264 Out of range value for column 's1' at row 8
+Warning 1264 Out of range value for column 's20' at row 8
+Warning 1264 Out of range value for column 'm' at row 8
+Warning 1264 Out of range value for column 'm0' at row 8
+Warning 1264 Out of range value for column 'm1' at row 8
+Warning 1264 Out of range value for column 'm20' at row 8
+Warning 1264 Out of range value for column 'i' at row 9
+Warning 1264 Out of range value for column 'i0' at row 9
+Warning 1264 Out of range value for column 'i1' at row 9
+Warning 1264 Out of range value for column 'i20' at row 9
+Warning 1264 Out of range value for column 't' at row 9
+Warning 1264 Out of range value for column 't0' at row 9
+Warning 1264 Out of range value for column 't1' at row 9
+Warning 1264 Out of range value for column 't20' at row 9
+Warning 1264 Out of range value for column 's' at row 9
+Warning 1264 Out of range value for column 's0' at row 9
+Warning 1264 Out of range value for column 's1' at row 9
+Warning 1264 Out of range value for column 's20' at row 9
+Warning 1264 Out of range value for column 'm' at row 9
+Warning 1264 Out of range value for column 'm0' at row 9
+Warning 1264 Out of range value for column 'm1' at row 9
+Warning 1264 Out of range value for column 'm20' at row 9
+Warning 1264 Out of range value for column 'i' at row 10
+Warning 1264 Out of range value for column 'i0' at row 10
+Warning 1264 Out of range value for column 'i1' at row 10
+Warning 1264 Out of range value for column 'i20' at row 10
+Warning 1264 Out of range value for column 't' at row 10
+Warning 1264 Out of range value for column 't0' at row 10
+Warning 1264 Out of range value for column 't1' at row 10
+Warning 1264 Out of range value for column 't20' at row 10
+Warning 1264 Out of range value for column 's' at row 10
+Warning 1264 Out of range value for column 's0' at row 10
+Warning 1264 Out of range value for column 's1' at row 10
+Warning 1264 Out of range value for column 's20' at row 10
+Warning 1264 Out of range value for column 'm' at row 10
+Warning 1264 Out of range value for column 'm0' at row 10
+Warning 1264 Out of range value for column 'm1' at row 10
+Warning 1264 Out of range value for column 'm20' at row 10
+Warning 1264 Out of range value for column 'i' at row 11
+Warning 1264 Out of range value for column 'i0' at row 11
+Warning 1264 Out of range value for column 'i1' at row 11
+Warning 1264 Out of range value for column 'i20' at row 11
+Warning 1264 Out of range value for column 't' at row 11
+Warning 1264 Out of range value for column 't0' at row 11
+Warning 1264 Out of range value for column 't1' at row 11
+Warning 1264 Out of range value for column 't20' at row 11
+Warning 1264 Out of range value for column 's' at row 11
+Warning 1264 Out of range value for column 's0' at row 11
+Warning 1264 Out of range value for column 's1' at row 11
+Warning 1264 Out of range value for column 's20' at row 11
+Warning 1264 Out of range value for column 'm' at row 11
+Warning 1264 Out of range value for column 'm0' at row 11
+Warning 1264 Out of range value for column 'm1' at row 11
+Warning 1264 Out of range value for column 'm20' at row 11
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+ALTER TABLE t1 ADD COLUMN i257 INT(257) NOT NULL;
+ERROR 42000: Display width out of range for 'i257' (max = 255)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# INT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c INT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c int(11) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (2147483647);
+SELECT HEX(c) FROM t1;
+HEX(c)
+7FFFFFFF
+DROP TABLE t1;
+#----------------------------------
+# INT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c INT NOT NULL DEFAULT 2147483647
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c int(11) NO 2147483647
+ALTER TABLE t1 ADD COLUMN err INT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (2147483647);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 7FFFFFFF
+2 7FFFFFFF
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TINYINT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TINYINT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyint(4) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (127);
+SELECT HEX(c) FROM t1;
+HEX(c)
+7F
+DROP TABLE t1;
+#----------------------------------
+# TINYINT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TINYINT NOT NULL DEFAULT 127
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinyint(4) NO 127
+ALTER TABLE t1 ADD COLUMN err TINYINT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (127);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 7F
+2 7F
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# SMALLINT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c SMALLINT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c smallint(6) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+SELECT HEX(c) FROM t1;
+HEX(c)
+0
+DROP TABLE t1;
+#----------------------------------
+# SMALLINT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c SMALLINT NOT NULL DEFAULT 0
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c smallint(6) NO 0
+ALTER TABLE t1 ADD COLUMN err SMALLINT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 0
+2 0
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# MEDIUMINT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c MEDIUMINT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumint(9) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1);
+SELECT HEX(c) FROM t1;
+HEX(c)
+1
+DROP TABLE t1;
+#----------------------------------
+# MEDIUMINT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c MEDIUMINT NOT NULL DEFAULT 1
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumint(9) NO 1
+ALTER TABLE t1 ADD COLUMN err MEDIUMINT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (1);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 1
+2 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# BIGINT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c BIGINT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c bigint(20) NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (9223372036854775807);
+SELECT HEX(c) FROM t1;
+HEX(c)
+7FFFFFFFFFFFFFFF
+DROP TABLE t1;
+#----------------------------------
+# BIGINT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c BIGINT NOT NULL DEFAULT 9223372036854775807
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c bigint(20) NO 9223372036854775807
+ALTER TABLE t1 ADD COLUMN err BIGINT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES (9223372036854775807);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 7FFFFFFFFFFFFFFF
+2 7FFFFFFFFFFFFFFF
+DROP TABLE t1;
+########################
+# SET columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a SET('') NOT NULL,
+b SET('test1','test2','test3','test4','test5') NOT NULL,
+c SET('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NOT NULL,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') NO NULL
+b set('test1','test2','test3','test4','test5') NO NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,,23'),
+('',5,2),
+(',','test4,test2','');
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ 01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50'51,52,53,54,55,56,57,58,59,60,61,62,63,64
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+ALTER TABLE t1 ADD COLUMN e SET('a','A') NOT NULL;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in SET
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') NO NULL
+b set('test1','test2','test3','test4','test5') NO NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+e set('a','A') NO NULL
+ALTER TABLE t1 ADD COLUMN f SET('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i') NOT NULL;
+ERROR HY000: Too many strings for column f and SET
+SELECT a,b,c,e FROM t1 WHERE FIND_IN_SET('test2',b)>0 OR a != '';
+a b c e
+ test2,test3 01,23,34,44
+ test2,test4
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# SET('test1','test2','test3') NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c SET('test1','test2','test3') NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c set('test1','test2','test3') NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test2,test3');
+SELECT HEX(c) FROM t1;
+HEX(c)
+74657374322C7465737433
+DROP TABLE t1;
+#----------------------------------
+# SET('test1','test2','test3') NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c SET('test1','test2','test3') NOT NULL DEFAULT 'test2,test3'
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c set('test1','test2','test3') NO test2,test3
+ALTER TABLE t1 ADD COLUMN err SET('test1','test2','test3') NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('test2,test3');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1 74657374322C7465737433
+2 74657374322C7465737433
+DROP TABLE t1;
+########################
+# TEXT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+t TEXT NOT NULL,
+t0 TEXT(0) NOT NULL,
+t1 TEXT(1) NOT NULL,
+t300 TEXT(300) NOT NULL,
+tm TEXT(65535) NOT NULL,
+t70k TEXT(70000) NOT NULL,
+t17m TEXT(17000000) NOT NULL,
+tt TINYTEXT NOT NULL,
+m MEDIUMTEXT NOT NULL,
+l LONGTEXT NOT NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+t text NO NULL
+t0 text NO NULL
+t1 tinytext NO NULL
+t300 text NO NULL
+tm text NO NULL
+t70k mediumtext NO NULL
+t17m longtext NO NULL
+tt tinytext NO NULL
+m mediumtext NO NULL
+l longtext NO NULL
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',255), REPEAT('i',1048576), REPEAT('j',1048576) );
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 't' at row 1
+Warning 1265 Data truncated for column 't0' at row 1
+Warning 1265 Data truncated for column 't1' at row 1
+Warning 1265 Data truncated for column 't300' at row 1
+Warning 1265 Data truncated for column 'tm' at row 1
+Warning 1265 Data truncated for column 'tt' at row 1
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+ALTER TABLE t1 ADD COLUMN ttt TEXT(4294967296) NOT NULL;
+ERROR 42000: Display width out of range for 'ttt' (max = 4294967295)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TEXT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TEXT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c text NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# TEXT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TEXT NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c text NO ''
+ALTER TABLE t1 ADD COLUMN err TEXT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# TINYTEXT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c TINYTEXT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinytext NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# TINYTEXT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c TINYTEXT NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c tinytext NO ''
+ALTER TABLE t1 ADD COLUMN err TINYTEXT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# MEDIUMTEXT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c MEDIUMTEXT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumtext NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# MEDIUMTEXT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c MEDIUMTEXT NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c mediumtext NO ''
+ALTER TABLE t1 ADD COLUMN err MEDIUMTEXT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+#----------------------------------
+# LONGTEXT NOT NULL columns without a default
+#----------------------------------
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c LONGTEXT NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c longtext NO NULL
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+SELECT HEX(c) FROM t1;
+HEX(c)
+
+DROP TABLE t1;
+#----------------------------------
+# LONGTEXT NOT NULL columns with a default
+#----------------------------------
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+c LONGTEXT NOT NULL DEFAULT ''
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+c longtext NO ''
+ALTER TABLE t1 ADD COLUMN err LONGTEXT NOT NULL DEFAULT NULL;
+ERROR 42000: Invalid default value for 'err'
+INSERT INTO t1 (c) VALUES (NULL);
+ERROR 23000: Column 'c' cannot be null
+INSERT INTO t1 (c) VALUES ('');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+pk HEX(c)
+1
+2
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/col_opt_null.result b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_null.result
new file mode 100644
index 00000000..ac87cb02
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_null.result
@@ -0,0 +1,2268 @@
+########################
+# BINARY columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+b BINARY NULL,
+b0 BINARY(0) NULL,
+b1 BINARY(1) NULL,
+b20 BINARY(20) NULL,
+b255 BINARY(255) NULL,
+pk BINARY PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) YES NULL
+b0 binary(0) YES NULL
+b1 binary(1) YES NULL
+b20 binary(20) YES NULL
+b255 binary(255) YES NULL
+pk binary(1) NO PRI NULL
+INSERT INTO t1 VALUES ('','','','','','');
+INSERT INTO t1 VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.','a');
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+INSERT INTO t1 VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256),'b');
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b20' at row 1
+Warning 1265 Data truncated for column 'b255' at row 1
+INSERT INTO t1 SELECT b255, b255, b255, b255, CONCAT('a',b255,b255), 'c' FROM t1;
+ERROR 23000: Duplicate entry 'c' for key 'PRIMARY'
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 61 6161616161616161616161616161616161616161 787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878 62
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+ALTER TABLE t1 ADD COLUMN b257 BINARY(257) NULL;
+ERROR 42000: Column length too big for column 'b257' (max = 255); use BLOB or TEXT instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) YES NULL
+b0 binary(0) YES NULL
+b1 binary(1) YES NULL
+b20 binary(20) YES NULL
+b255 binary(255) YES NULL
+pk binary(1) NO PRI NULL
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c BINARY NULL,
+c1 BINARY NULL DEFAULT NULL,
+c2 BINARY NULL DEFAULT 0,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c binary(1) YES NULL
+c1 binary(1) YES NULL
+c2 binary(1) YES 0
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (0,0,0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 30 30 30
+3 NULL NULL 30
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 30
+3 30
+DROP TABLE t1;
+########################
+# VARBINARY columns
+########################
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARBINARY(0) NULL,
+v1 VARBINARY(1) NULL,
+v64 VARBINARY(64) NULL,
+v65000 VARBINARY(65000) NULL,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) YES NULL
+v1 varbinary(1) YES NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) YES NULL
+CREATE TABLE t2 (v VARBINARY(65532) NULL, PRIMARY KEY(v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varbinary(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT HEX(v0), HEX(v1), HEX(v64), HEX(v65000) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) HEX(v65000)
+
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E2057652776652070726F7669646564206C696E6B7320746F20416D617A6F6E2E636F6D206865726520666F7220636F6E76656E69656E63652C2062757420746865792063616E20626520666F756E64206174206D616E79206F7468657220626F6F6B73746F7265732C20626F7468206F6E6C696E6520616E64206F66662E0A0A2020496620796F752077616E7420746F206861766520796F7572206661766F72697465204D7953514C202F204D61726961444220626F6F6B206C697374656420686572652C20706C65617365206C65617665206120636F6D6D656E742E0A2020466F7220646576656C6F706572732077686F2077616E7420746F20636F6465206F6E204D617269614442206F72204D7953514C0A0A2020202020202A20556E6465727374616E64696E67204D7953514C20496E7465726E616C73206279205361736861205061636865762C20666F726D6572204D7953514C20646576656C6F706572206174204D7953514C2041422E0A2020202020202020202020206F205468697320697320746865206F6E6C7920626F6F6B207765206B6E6F772061626F75742074686174206465736372696265732074686520696E7465726E616C73206F66204D617269614442202F204D7953514C2E2041206D757374206861766520666F7220616E796F6E652077686F2077616E747320746F20756E6465727374616E6420616E6420646576656C6F70206F6E204D617269614442210A2020202020202020202020206F204E6F7420616C6C20746F706963732061726520636F766572656420616E6420736F6D652070617274732061726520736C696768746C79206F757464617465642C20627574207374696C6C20746865206265737420626F6F6B206F6E207468697320746F7069632E200A2020202020202A204D7953514C20352E3120506C7567696E20446576656C6F706D656E742062792053657267656920476F6C75626368696B20616E6420416E64726577204875746368696E67730A2020202020202020202020206F2041206D757374207265616420666F7220616E796F6E652077616E74696E6720746F207772697465206120706C7567696E20666F72204D6172696144422C207772697474656E20627920746865205365726765692077686F2064657369676E65642074686520706C7567696E20696E7465726661636520666F72204D7953514C20616E64204D61726961444221200A0A2020466F72204D617269614442202F204D7953514C20656E642075736572730A0A2020202020202A204D61726961444220437261736820436F757273652062792042656E20466F7274610A2020202020202020202020206F204669727374204D61726961444220626F6F6B210A2020202020202020202020206F20466F722070656F706C652077686F2077616E7420746F206C6561726E2053514C20616E642074686520626173696373206F66204D6172696144422E0A2020202020202020202020206F204E6F77207368697070696E672E20507572636861736520617420416D617A6F6E2E636F6D206F7220796F7572206661766F7269746520626F6F6B73656C6C65722E200A0A2020202020202A2053514C2D393920436F6D706C6574652C205265616C6C792062792050657465722047756C75747A616E20262054727564792050656C7A65722E0A2020202020202020202020206F2045766572797468696E6720796F752077616E74656420746F206B6E6F772061626F7574207468652053514C203939207374616E646172642E20457863656C6C656E74207265666572656E636520626F6F6B210A2020202020202020202020206F204672656520746F207265616420696E20746865204B6E6F776C656467656261736521200A0A2020202020202A204D7953514C20283474682045646974696F6E29206279205061756C204475426F69730A2020202020202020202020206F20546865202764656661756C742720626F6F6B20746F207265616420696620796F7520776F6E7420746F206C6561726E20746F20757365204D7953514C202F204D6172696144422E200A0A2020202020202A204D7953514C20436F6F6B626F6F6B206279205061756C204475426F69730A2020202020202020202020206F2041206C6F74206F66206578616D706C6573206F6620686F7720746F20757365204D7953514C2E204173207769746820616C6C206F66205061756C277320626F6F6B732C206974277320776F727468206974732077656967687420696E20676F6C6420616E64206576656E20656E6A6F7961626C652072656164696E6720666F7220737563682061202764727927207375626A6563742E200A0A2020202020202A204869676820506572666F726D616E6365204D7953514C2C205365636F6E642045646974696F6E2C204279204261726F6E20536368776172747A2C205065746572205A6169747365762C20566164696D20546B616368656E6B6F2C204A6572656D7920442E205A61776F646E792C2041726A656E204C656E747A2C20446572656B204A2E2042616C6C696E672C20657420616C2E0A2020202020202020202020206F20224869676820506572666F726D616E6365204D7953514C2069732074686520646566696E697469766520677569646520746F206275696C64696E6720666173742C2072656C6961626C652073797374656D732077697468204D7953514C2E205772697474656E206279206E6F74656420657870657274732077697468207965617273206F66207265616C2D776F726C6420657870657269656E6365206275696C64696E672076657279206C617267652073797374656D732C207468697320626F6F6B20636F7665727320657665727920617370656374206F66204D7953514C20706572666F726D616E636520696E2064657461696C2C20616E6420666F6375736573206F6E20726F627573746E6573732C2073656375726974792C20616E64206461746120696E746567726974792E204C6561726E20616476616E63656420746563686E697175657320696E20646570746820736F20796F752063616E206272696E67206F7574204D7953514C27732066756C6C20706F7765722E22202846726F6D2074686520626F6F6B206465736372697074696F6E206174204F275265696C6C7929200A0A2020202020202A204D7953514C2041646D696E20436F6F6B626F6F6B0A2020202020202020202020206F204120717569636B20737465702D62792D7374657020677569646520666F72204D7953514C20757365727320616E642064617461626173652061646D696E6973747261746F727320746F207461636B6C65207265616C2D776F726C64206368616C6C656E6765732077697468204D7953514C20636F6E66696775726174696F6E20616E642061646D696E697374726174696F6E200A0A2020202020202A204D7953514C20352E302043657274696669636174696F6E2053747564792047756964652C204279205061756C204475426F69732C2053746566616E2048696E7A2C204361727374656E20506564657273656E0A2020202020202020202020206F205468697320697320746865206F6666696369616C20677569646520746F20636F766572207468652070617373696E67206F66207468652074776F204D7953514C2043657274696669636174696F6E206578616D696E6174696F6E732E2049742069732076616C69642074696C6C2076657273696F6E20352E30206F6620746865207365727665722C20736F207768696C65206974206D697373657320616C6C2074686520666561747572657320617661696C61626C6520696E204D7953514C20352E3120616E6420677265617465722028696E636C7564696E67204D61726961444220352E3120616E642067726561746572292C2069742070726F7669646573206120676F6F6420626173696320756E6465727374616E64696E67206F66204D7953514C20666F722074686520656E642D757365722E20
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+Warning 1265 Data truncated for column 'v65000' at row 6
+SELECT HEX(v0), HEX(v1), HEX(v64), LENGTH(HEX(v65000)) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) LENGTH(HEX(v65000))
+ 0
+ 61 0
+ 48 61486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E205765277665 5932
+ 61 61616263646566676869206162636465666768692061626364656667686920616263646566676869206162636465666768692061626364656667686920616263 130000
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 5930
+ 79 63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363 130000
+ALTER TABLE t1 ADD COLUMN v65536 VARBINARY(65536) NULL;
+Warnings:
+Note 1246 Converting column 'v65536' from VARBINARY to BLOB
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) YES NULL
+v1 varbinary(1) YES NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) YES NULL
+v65536 mediumblob YES NULL
+DROP TABLE t1, t2;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c VARBINARY(64) NULL,
+c1 VARBINARY(64) NULL DEFAULT NULL,
+c2 VARBINARY(64) NULL DEFAULT 'test',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c varbinary(64) YES NULL
+c1 varbinary(64) YES NULL
+c2 varbinary(64) YES test
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('test','test','test');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 74657374 74657374 74657374
+3 NULL NULL 74657374
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 74657374
+3 74657374
+DROP TABLE t1;
+########################
+# BIT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a BIT NULL,
+b BIT(20) NULL,
+c BIT(64) NULL,
+d BIT(1) NULL,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) YES NULL
+b bit(20) YES NULL
+c bit(64) NO PRI NULL
+d bit(1) YES NULL
+ALTER TABLE t1 DROP COLUMN d;
+ALTER TABLE t1 ADD COLUMN d BIT(0) NULL;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) YES NULL
+b bit(20) YES NULL
+c bit(64) NO PRI NULL
+d bit(1) YES NULL
+INSERT INTO t1 (a,b,c,d) VALUES (0,POW(2,20)-1,b'1111111111111111111111111111111111111111111111111111111111111111',1);
+SELECT BIN(a), HEX(b), c+0 FROM t1 WHERE d>0;
+BIN(a) HEX(b) c+0
+0 FFFFF 18446744073709551615
+INSERT INTO t1 (a,b,c,d) VALUES (1,0,-2,0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d<100;
+a+0 b+0 c+0
+0 1048575 18446744073709551615
+1 0 18446744073709551614
+INSERT INTO t1 (a,b,c,d) VALUES (b'1', 'f', 0xFF, 0x0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d IN (0, 2);
+a+0 b+0 c+0
+1 0 18446744073709551614
+1 102 255
+DELETE FROM t1;
+INSERT INTO t1 (a,b,c,d) VALUES (0x10,0,0,1);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+INSERT INTO t1 (a,b,c,d) VALUES (0x01,0,0x10000000000000000,0);
+Warnings:
+Warning 1264 Out of range value for column 'c' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+1 0 18446744073709551615 0
+DROP TABLE t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a BIT(65) NULL) ENGINE=rocksdb;
+ERROR 42000: Display width out of range for 'a' (max = 64)
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c BIT NULL,
+c1 BIT NULL DEFAULT NULL,
+c2 BIT NULL DEFAULT 1,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c bit(1) YES NULL
+c1 bit(1) YES NULL
+c2 bit(1) YES b'1'
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (1,1,1);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 1 1 1
+3 NULL NULL 1
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 1
+3 1
+DROP TABLE t1;
+########################
+# BLOB columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b BLOB NULL,
+b0 BLOB(0) NULL,
+b1 BLOB(1) NULL,
+b300 BLOB(300) NULL,
+bm BLOB(65535) NULL,
+b70k BLOB(70000) NULL,
+b17m BLOB(17000000) NULL,
+t TINYBLOB NULL,
+m MEDIUMBLOB NULL,
+l LONGBLOB NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b blob YES NULL
+b0 blob YES NULL
+b1 tinyblob YES NULL
+b300 blob YES NULL
+bm blob YES NULL
+b70k mediumblob YES NULL
+b17m longblob YES NULL
+t tinyblob YES NULL
+m mediumblob YES NULL
+l longblob YES NULL
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), HEX(REPEAT('g',1048576)), REPEAT('h',255), REPEAT('i',1048576), HEX(REPEAT('j',1048576)) );
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b300' at row 1
+Warning 1265 Data truncated for column 'bm' at row 1
+Warning 1265 Data truncated for column 't' at row 1
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+ALTER TABLE t1 ADD COLUMN bbb BLOB(4294967296);
+ERROR 42000: Display width out of range for 'bbb' (max = 4294967295)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c BLOB NULL,
+c1 BLOB NULL DEFAULT NULL,
+c2 BLOB NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c blob YES NULL
+c1 blob YES NULL
+c2 blob YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TINYBLOB NULL,
+c1 TINYBLOB NULL DEFAULT NULL,
+c2 TINYBLOB NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c tinyblob YES NULL
+c1 tinyblob YES NULL
+c2 tinyblob YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c MEDIUMBLOB NULL,
+c1 MEDIUMBLOB NULL DEFAULT NULL,
+c2 MEDIUMBLOB NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c mediumblob YES NULL
+c1 mediumblob YES NULL
+c2 mediumblob YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c LONGBLOB NULL,
+c1 LONGBLOB NULL DEFAULT NULL,
+c2 LONGBLOB NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c longblob YES NULL
+c1 longblob YES NULL
+c2 longblob YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+########################
+# BOOL columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b1 BOOL NULL,
+b2 BOOLEAN NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b1 tinyint(1) YES NULL
+b2 tinyint(1) YES NULL
+INSERT INTO t1 (b1,b2) VALUES (1,TRUE);
+SELECT b1,b2 FROM t1;
+b1 b2
+1 1
+INSERT INTO t1 (b1,b2) VALUES (FALSE,0);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+INSERT INTO t1 (b1,b2) VALUES (2,3);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (-1,-2);
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+1 1
+2 3
+SELECT IF(b1,'true','false') AS a, IF(b2,'true','false') AS b FROM t1;
+a b
+false false
+true true
+true true
+true true
+SELECT b1,b2 FROM t1 WHERE b1 = TRUE;
+b1 b2
+1 1
+SELECT b1,b2 FROM t1 WHERE b2 = FALSE;
+b1 b2
+0 0
+INSERT INTO t1 (b1,b2) VALUES ('a','b');
+Warnings:
+Warning 1366 Incorrect integer value: 'a' for column `test`.`t1`.`b1` at row 1
+Warning 1366 Incorrect integer value: 'b' for column `test`.`t1`.`b2` at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (128,-129);
+Warnings:
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b2' at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+127 -128
+2 3
+ALTER TABLE t1 ADD COLUMN b3 BOOLEAN UNSIGNED NULL;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UNSIGNED NULL' at line 1
+ALTER TABLE ADD COLUMN b3 BOOL ZEROFILL NULL;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'ADD COLUMN b3 BOOL ZEROFILL NULL' at line 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c BOOL NULL,
+c1 BOOL NULL DEFAULT NULL,
+c2 BOOL NULL DEFAULT '0',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c tinyint(1) YES NULL
+c1 tinyint(1) YES NULL
+c2 tinyint(1) YES 0
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('0','0','0');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 0 0 0
+3 NULL NULL 0
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 0
+3 0
+DROP TABLE t1;
+########################
+# CHAR columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c CHAR NULL,
+c0 CHAR(0) NULL,
+c1 CHAR(1) NULL,
+c20 CHAR(20) NULL,
+c255 CHAR(255) NULL,
+PRIMARY KEY (c255)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c char(1) YES NULL
+c0 char(0) YES NULL
+c1 char(1) YES NULL
+c20 char(20) YES NULL
+c255 char(255) NO PRI NULL
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('','','','','');
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.');
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256));
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+Warning 1265 Data truncated for column 'c0' at row 1
+Warning 1265 Data truncated for column 'c1' at row 1
+Warning 1265 Data truncated for column 'c20' at row 1
+Warning 1265 Data truncated for column 'c255' at row 1
+INSERT INTO t1 (c,c0,c1,c20,c255) SELECT c255, c255, c255, c255, CONCAT('a',c255,c1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 5
+Warning 1265 Data truncated for column 'c0' at row 5
+Warning 1265 Data truncated for column 'c1' at row 5
+Warning 1265 Data truncated for column 'c20' at row 5
+Warning 1265 Data truncated for column 'c' at row 6
+Warning 1265 Data truncated for column 'c0' at row 6
+Warning 1265 Data truncated for column 'c1' at row 6
+Warning 1265 Data truncated for column 'c20' at row 6
+Warning 1265 Data truncated for column 'c255' at row 6
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+ a
+C C Creating an article aCreating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.b
+a a aaaaaaaaaaaaaaaaaaaa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+x x xxxxxxxxxxxxxxxxxxxx axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+SELECT DISTINCT c20, REPEAT('a',LENGTH(c20)), COUNT(*) FROM t1 GROUP BY c1, c20;
+c20 REPEAT('a',LENGTH(c20)) COUNT(*)
+ 2
+Creating an article aaaaaaaaaaaaaaaaaaa 1
+aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa 1
+abcdefghi klmnopqrst aaaaaaaaaaaaaaaaaaaa 1
+xxxxxxxxxxxxxxxxxxxx aaaaaaaaaaaaaaaaaaaa 1
+ALTER TABLE t1 ADD COLUMN c257 CHAR(257) NULL;
+ERROR 42000: Column length too big for column 'c257' (max = 255); use BLOB or TEXT instead
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c CHAR NULL,
+c1 CHAR NULL DEFAULT NULL,
+c2 CHAR NULL DEFAULT '_',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c char(1) YES NULL
+c1 char(1) YES NULL
+c2 char(1) YES _
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('_','_','_');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 5F 5F 5F
+3 NULL NULL 5F
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 5F
+3 5F
+DROP TABLE t1;
+########################
+# VARCHAR columns
+########################
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARCHAR(0) NULL,
+v1 VARCHAR(1) NULL,
+v64 VARCHAR(64) NULL,
+v65000 VARCHAR(65000) NULL,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) YES NULL
+v1 varchar(1) YES NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) YES NULL
+CREATE TABLE t2 (v VARCHAR(65532), PRIMARY KEY (v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varchar(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT v0,v1,v64,v65000 FROM t1;
+v0 v1 v64 v65000
+
+
+
+
+
+
+
+
+
+
+
+ y Once there, double check that an article doesn't already exist Here is a list of recommended books on MariaDB and MySQL. We've provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+ o "High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL's full power." (From the book description at O'Reilly)
+ o A lot of examples of how to use MySQL. As with all of Paul's books, it's worth its weight in gold and even enjoyable reading for such a 'dry' subject.
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Free to read in the Knowledgebase!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+ o The 'default' book to read if you wont to learn to use MySQL / MariaDB.
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ * MariaDB Crash Course by Ben Forta
+ * MySQL (4th Edition) by Paul DuBois
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ * MySQL Admin Cookbook
+ * MySQL Cookbook by Paul DuBois
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ For MariaDB / MySQL end users
+ For developers who want to code on MariaDB or MySQL
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v65000' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+SELECT v0, v1, v64, LENGTH(v65000) FROM t1;
+v0 v1 v64 LENGTH(v65000)
+ 0
+ a 0
+ H aHere is a list of recommended books on MariaDB and MySQL. We've 2966
+ a aabcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abc 65000
+ y Once there, double check that an article doesn't already exist 2965
+ y cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc 65000
+ALTER TABLE t1 ADD COLUMN v65536 VARCHAR(65536) NULL;
+Warnings:
+Note 1246 Converting column 'v65536' from VARCHAR to TEXT
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) YES NULL
+v1 varchar(1) YES NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) YES NULL
+v65536 mediumtext YES NULL
+DROP TABLE t1, t2;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c VARCHAR(64) NULL,
+c1 VARCHAR(64) NULL DEFAULT NULL,
+c2 VARCHAR(64) NULL DEFAULT 'test default',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c varchar(64) YES NULL
+c1 varchar(64) YES NULL
+c2 varchar(64) YES test default
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('test default','test default','test default');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 746573742064656661756C74 746573742064656661756C74 746573742064656661756C74
+3 NULL NULL 746573742064656661756C74
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 746573742064656661756C74
+3 746573742064656661756C74
+DROP TABLE t1;
+########################
+# date and time columns
+########################
+set @col_opt_nullsave_time_zone=@@time_zone;
+set time_zone='UTC';
+DROP TABLE IF EXISTS t1;
+set @save_time_zone=@@time_zone;
+set time_zone='UTC';
+CREATE TABLE t1 (
+d DATE NULL,
+dt DATETIME NULL,
+ts TIMESTAMP NULL,
+t TIME NULL,
+y YEAR NULL,
+y4 YEAR(4) NULL,
+y2 YEAR(2) NULL,
+pk DATETIME PRIMARY KEY
+) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d date YES NULL
+dt datetime YES NULL
+ts timestamp YES NULL
+t time YES NULL
+y year(4) YES NULL
+y4 year(4) YES NULL
+y2 year(2) YES NULL
+pk datetime NO PRI NULL
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('1000-01-01', '1000-01-01 00:00:00', FROM_UNIXTIME(1), '-838:59:59', '1901', '1901', '00','2012-12-12 12:12:12'),
+('9999-12-31', '9999-12-31 23:59:59', FROM_UNIXTIME(2147483647), '838:59:59', '2155', '2155', '99','2012-12-12 12:12:13'),
+('0000-00-00', '0000-00-00 00:00:00', '0000-00-00 00:00:00', '00:00:00', '0', '0', '0','2012-12-12 12:12:14'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),YEAR(@tm),YEAR(@tm),'2012-12-12 12:12:15');
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('999-13-32', '999-11-31 00:00:00', '0', '-839:00:00', '1900', '1900', '-1','2012-12-12 12:12:16');
+Warnings:
+Warning 1265 Data truncated for column 'd' at row 1
+Warning 1265 Data truncated for column 'dt' at row 1
+Warning 1265 Data truncated for column 'ts' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 'y' at row 1
+Warning 1264 Out of range value for column 'y4' at row 1
+Warning 1264 Out of range value for column 'y2' at row 1
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 -838:59:59 0000 0000 00
+set time_zone=@save_time_zone;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c DATE NULL,
+c1 DATE NULL DEFAULT NULL,
+c2 DATE NULL DEFAULT '2012-12-21',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c date YES NULL
+c1 date YES NULL
+c2 date YES 2012-12-21
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('2012-12-21','2012-12-21','2012-12-21');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 323031322D31322D3231 323031322D31322D3231 323031322D31322D3231
+3 NULL NULL 323031322D31322D3231
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 323031322D31322D3231
+3 323031322D31322D3231
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c DATETIME NULL,
+c1 DATETIME NULL DEFAULT NULL,
+c2 DATETIME NULL DEFAULT '2012-12-21 12:21:12',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c datetime YES NULL
+c1 datetime YES NULL
+c2 datetime YES 2012-12-21 12:21:12
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('2012-12-21 12:21:12','2012-12-21 12:21:12','2012-12-21 12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 323031322D31322D32312031323A32313A3132 323031322D31322D32312031323A32313A3132 323031322D31322D32312031323A32313A3132
+3 NULL NULL 323031322D31322D32312031323A32313A3132
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 323031322D31322D32312031323A32313A3132
+3 323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TIMESTAMP NULL,
+c1 TIMESTAMP NULL DEFAULT NULL,
+c2 TIMESTAMP NULL DEFAULT '2012-12-21 12:21:12',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c timestamp YES NULL
+c1 timestamp YES NULL
+c2 timestamp YES 2012-12-21 12:21:12
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('2012-12-21 12:21:12','2012-12-21 12:21:12','2012-12-21 12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 323031322D31322D32312031323A32313A3132 323031322D31322D32312031323A32313A3132 323031322D31322D32312031323A32313A3132
+3 NULL NULL 323031322D31322D32312031323A32313A3132
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 323031322D31322D32312031323A32313A3132
+3 323031322D31322D32312031323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TIME NULL,
+c1 TIME NULL DEFAULT NULL,
+c2 TIME NULL DEFAULT '12:21:12',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c time YES NULL
+c1 time YES NULL
+c2 time YES 12:21:12
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('12:21:12','12:21:12','12:21:12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 31323A32313A3132 31323A32313A3132 31323A32313A3132
+3 NULL NULL 31323A32313A3132
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 31323A32313A3132
+3 31323A32313A3132
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c YEAR NULL,
+c1 YEAR NULL DEFAULT NULL,
+c2 YEAR NULL DEFAULT '2012',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c year(4) YES NULL
+c1 year(4) YES NULL
+c2 year(4) YES 2012
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('2012','2012','2012');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 7DC 7DC 7DC
+3 NULL NULL 7DC
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 7DC
+3 7DC
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c YEAR(2) NULL,
+c1 YEAR(2) NULL DEFAULT NULL,
+c2 YEAR(2) NULL DEFAULT '12',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c year(2) YES NULL
+c1 year(2) YES NULL
+c2 year(2) YES 12
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('12','12','12');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 C C C
+3 NULL NULL C
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 C
+3 C
+DROP TABLE t1;
+set time_zone=@col_opt_nullsave_time_zone;
+########################
+# ENUM columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a ENUM('') NULL,
+b ENUM('test1','test2','test3','test4','test5') NULL,
+c ENUM('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') NULL,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') YES NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') YES NULL
+INSERT INTO t1 (a,b,c) VALUES ('','test2','4'),('',5,2);
+SELECT a,b,c FROM t1;
+a b c
+ test2 4
+ test5 2
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'a' at row 1
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+
+ test2 4
+ test5 2
+ALTER TABLE t1 ADD COLUMN e ENUM('a','A') NULL;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in ENUM
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') YES NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') YES NULL
+e enum('a','A') YES NULL
+INSERT INTO t1 (a,b,c,e) VALUES ('','test3','75','A');
+SELECT a,b,c,e FROM t1;
+a b c e
+ NULL
+ test2 4 NULL
+ test3 75 a
+ test5 2 NULL
+SELECT a,b,c,e FROM t1 WHERE b='test2' OR a != '';
+a b c e
+ test2 4 NULL
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c ENUM('test1','test2','test3') NULL,
+c1 ENUM('test1','test2','test3') NULL DEFAULT NULL,
+c2 ENUM('test1','test2','test3') NULL DEFAULT 'test2',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c enum('test1','test2','test3') YES NULL
+c1 enum('test1','test2','test3') YES NULL
+c2 enum('test1','test2','test3') YES test2
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('test2','test2','test2');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 7465737432 7465737432 7465737432
+3 NULL NULL 7465737432
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 7465737432
+3 7465737432
+DROP TABLE t1;
+########################
+# Fixed point columns (NUMERIC, DECIMAL)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DECIMAL NULL,
+d0 DECIMAL(0) NULL,
+d1_1 DECIMAL(1,1) NULL,
+d10_2 DECIMAL(10,2) NULL,
+d60_10 DECIMAL(60,10) NULL,
+n NUMERIC NULL,
+n0_0 NUMERIC(0,0) NULL,
+n1 NUMERIC(1) NULL,
+n20_4 NUMERIC(20,4) NULL,
+n65_4 NUMERIC(65,4) NULL,
+pk NUMERIC NULL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d decimal(10,0) YES NULL
+d0 decimal(10,0) YES NULL
+d1_1 decimal(1,1) YES NULL
+d10_2 decimal(10,2) YES NULL
+d60_10 decimal(60,10) YES NULL
+n decimal(10,0) YES NULL
+n0_0 decimal(10,0) YES NULL
+n1 decimal(1,0) YES NULL
+n20_4 decimal(20,4) YES NULL
+n65_4 decimal(65,4) YES NULL
+pk decimal(10,0) NO PRI NULL
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+6
+);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+Warnings:
+Note 1265 Data truncated for column 'd' at row 1
+Note 1265 Data truncated for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Note 1265 Data truncated for column 'd10_2' at row 1
+Note 1265 Data truncated for column 'd60_10' at row 1
+Note 1265 Data truncated for column 'n' at row 1
+Note 1265 Data truncated for column 'n0_0' at row 1
+Note 1265 Data truncated for column 'n1' at row 1
+Note 1265 Data truncated for column 'n20_4' at row 1
+Note 1265 Data truncated for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.00 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.0000 9999999999999999999999999999999999999999999999999999999999999.1111
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) NULL;
+ERROR 42000: Too big precision 66 specified for 'n66'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) NULL;
+ERROR 42000: Too big precision 66 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) NULL;
+ERROR 42000: Too big scale 66 specified for 'n66_66'. Maximum is 38
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c DECIMAL NULL,
+c1 DECIMAL NULL DEFAULT NULL,
+c2 DECIMAL NULL DEFAULT 1.1,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+Warnings:
+Note 1265 Data truncated for column 'c2' at row 1
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c decimal(10,0) YES NULL
+c1 decimal(10,0) YES NULL
+c2 decimal(10,0) YES 1
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (1.1,1.1,1.1);
+Warnings:
+Note 1265 Data truncated for column 'c' at row 1
+Note 1265 Data truncated for column 'c1' at row 1
+Note 1265 Data truncated for column 'c2' at row 1
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 1 1 1
+3 NULL NULL 1
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 1
+3 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c NUMERIC NULL,
+c1 NUMERIC NULL DEFAULT NULL,
+c2 NUMERIC NULL DEFAULT 0 ,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c decimal(10,0) YES NULL
+c1 decimal(10,0) YES NULL
+c2 decimal(10,0) YES 0
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (0 ,0 ,0 );
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 0 0 0
+3 NULL NULL 0
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 0
+3 0
+DROP TABLE t1;
+########################
+# Floating point columns (FLOAT, DOUBLE)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT NULL,
+f0 FLOAT(0) NULL,
+r1_1 REAL(1,1) NULL,
+f23_0 FLOAT(23) NULL,
+f20_3 FLOAT(20,3) NULL,
+d DOUBLE NULL,
+d1_0 DOUBLE(1,0) NULL,
+d10_10 DOUBLE PRECISION (10,10) NULL,
+d53 DOUBLE(53,0) NULL,
+d53_10 DOUBLE(53,10) NULL,
+pk DOUBLE NULL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+f float YES NULL
+f0 float YES NULL
+r1_1 double(1,1) YES NULL
+f23_0 float YES NULL
+f20_3 float(20,3) YES NULL
+d double YES NULL
+d1_0 double(1,0) YES NULL
+d10_10 double(10,10) YES NULL
+d53 double(53,0) YES NULL
+d53_10 double(53,10) YES NULL
+pk double NO PRI NULL
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 11111111.111
+d10_10 0.0123456789
+d1_0 8
+d53 1234566789123456800
+d53_10 100000000000000000.0000000000
+f0 12345.1
+f20_3 56789.988
+f23_0 123457000
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+99999999999999999999999999999999999999,
+99999999999999999999999999999999999999.9999999999999999,
+0.9,
+99999999999999999999999999999999999999.9,
+99999999999999999.999,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+9,
+0.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+3
+);
+Warnings:
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 11111111.111
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 8
+d1_0 9
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 1e38
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+SELECT
+CONCAT('', MAX(f)),
+CONCAT('', MAX(f0)),
+CONCAT('', MAX(r1_1)),
+CONCAT('', MAX(f23_0)),
+CONCAT('', MAX(f20_3)),
+CONCAT('', MAX(d)),
+CONCAT('', MAX(d1_0)),
+CONCAT('', MAX(d10_10)),
+CONCAT('', MAX(d53)),
+CONCAT('', MAX(d53_10)) FROM t1;
+CONCAT('', MAX(f)) 1e38
+CONCAT('', MAX(d)) 1e81
+CONCAT('', MAX(d10_10)) 0.9999999999
+CONCAT('', MAX(d1_0)) 9
+CONCAT('', MAX(d53)) 100000000000000000000000000000000000000000000000000000
+CONCAT('', MAX(d53_10)) 10000000000000000000000000000000000000000000.0000000000
+CONCAT('', MAX(f0)) 1e38
+CONCAT('', MAX(f20_3)) 99999998430674940.000
+CONCAT('', MAX(f23_0)) 1e38
+CONCAT('', MAX(r1_1)) 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+5
+);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+999999999999999999999999999999999999999,
+999999999999999999999999999999999999999.9999999999999999,
+1.9,
+999999999999999999999999999999999999999.9,
+999999999999999999.999,
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+99,
+1.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+6
+);
+Warnings:
+Warning 1916 Got overflow when converting '' to DECIMAL. Value truncated
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e65
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) NULL;
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) NULL;
+ERROR 42000: Too big precision 256 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) NULL;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c FLOAT NULL,
+c1 FLOAT NULL DEFAULT NULL,
+c2 FLOAT NULL DEFAULT 1.1 ,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c float YES NULL
+c1 float YES NULL
+c2 float YES 1.1
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (1.1 ,1.1 ,1.1 );
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 1 1 1
+3 NULL NULL 1
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 1
+3 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c DOUBLE NULL,
+c1 DOUBLE NULL DEFAULT NULL,
+c2 DOUBLE NULL DEFAULT 0 ,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c double YES NULL
+c1 double YES NULL
+c2 double YES 0
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (0 ,0 ,0 );
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 0 0 0
+3 NULL NULL 0
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 0
+3 0
+DROP TABLE t1;
+########################
+# INT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT NULL,
+i0 INT(0) NULL,
+i1 INT(1) NULL,
+i20 INT(20) NULL,
+t TINYINT NULL,
+t0 TINYINT(0) NULL,
+t1 TINYINT(1) NULL,
+t20 TINYINT(20) NULL,
+s SMALLINT NULL,
+s0 SMALLINT(0) NULL,
+s1 SMALLINT(1) NULL,
+s20 SMALLINT(20) NULL,
+m MEDIUMINT NULL,
+m0 MEDIUMINT(0) NULL,
+m1 MEDIUMINT(1) NULL,
+m20 MEDIUMINT(20) NULL,
+b BIGINT NULL,
+b0 BIGINT(0) NULL,
+b1 BIGINT(1) NULL,
+b20 BIGINT(20) NULL,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+i0 int(11) YES NULL
+i1 int(1) YES NULL
+i20 int(20) YES NULL
+t tinyint(4) YES NULL
+t0 tinyint(4) YES NULL
+t1 tinyint(1) YES NULL
+t20 tinyint(20) YES NULL
+s smallint(6) YES NULL
+s0 smallint(6) YES NULL
+s1 smallint(1) YES NULL
+s20 smallint(20) YES NULL
+m mediumint(9) YES NULL
+m0 mediumint(9) YES NULL
+m1 mediumint(1) YES NULL
+m20 mediumint(20) YES NULL
+b bigint(20) YES NULL
+b0 bigint(20) YES NULL
+b1 bigint(1) YES NULL
+b20 bigint(20) YES NULL
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 8
+Warning 1264 Out of range value for column 'i0' at row 8
+Warning 1264 Out of range value for column 'i1' at row 8
+Warning 1264 Out of range value for column 'i20' at row 8
+Warning 1264 Out of range value for column 't' at row 8
+Warning 1264 Out of range value for column 't0' at row 8
+Warning 1264 Out of range value for column 't1' at row 8
+Warning 1264 Out of range value for column 't20' at row 8
+Warning 1264 Out of range value for column 's' at row 8
+Warning 1264 Out of range value for column 's0' at row 8
+Warning 1264 Out of range value for column 's1' at row 8
+Warning 1264 Out of range value for column 's20' at row 8
+Warning 1264 Out of range value for column 'm' at row 8
+Warning 1264 Out of range value for column 'm0' at row 8
+Warning 1264 Out of range value for column 'm1' at row 8
+Warning 1264 Out of range value for column 'm20' at row 8
+Warning 1264 Out of range value for column 'i' at row 9
+Warning 1264 Out of range value for column 'i0' at row 9
+Warning 1264 Out of range value for column 'i1' at row 9
+Warning 1264 Out of range value for column 'i20' at row 9
+Warning 1264 Out of range value for column 't' at row 9
+Warning 1264 Out of range value for column 't0' at row 9
+Warning 1264 Out of range value for column 't1' at row 9
+Warning 1264 Out of range value for column 't20' at row 9
+Warning 1264 Out of range value for column 's' at row 9
+Warning 1264 Out of range value for column 's0' at row 9
+Warning 1264 Out of range value for column 's1' at row 9
+Warning 1264 Out of range value for column 's20' at row 9
+Warning 1264 Out of range value for column 'm' at row 9
+Warning 1264 Out of range value for column 'm0' at row 9
+Warning 1264 Out of range value for column 'm1' at row 9
+Warning 1264 Out of range value for column 'm20' at row 9
+Warning 1264 Out of range value for column 'i' at row 10
+Warning 1264 Out of range value for column 'i0' at row 10
+Warning 1264 Out of range value for column 'i1' at row 10
+Warning 1264 Out of range value for column 'i20' at row 10
+Warning 1264 Out of range value for column 't' at row 10
+Warning 1264 Out of range value for column 't0' at row 10
+Warning 1264 Out of range value for column 't1' at row 10
+Warning 1264 Out of range value for column 't20' at row 10
+Warning 1264 Out of range value for column 's' at row 10
+Warning 1264 Out of range value for column 's0' at row 10
+Warning 1264 Out of range value for column 's1' at row 10
+Warning 1264 Out of range value for column 's20' at row 10
+Warning 1264 Out of range value for column 'm' at row 10
+Warning 1264 Out of range value for column 'm0' at row 10
+Warning 1264 Out of range value for column 'm1' at row 10
+Warning 1264 Out of range value for column 'm20' at row 10
+Warning 1264 Out of range value for column 'i' at row 11
+Warning 1264 Out of range value for column 'i0' at row 11
+Warning 1264 Out of range value for column 'i1' at row 11
+Warning 1264 Out of range value for column 'i20' at row 11
+Warning 1264 Out of range value for column 't' at row 11
+Warning 1264 Out of range value for column 't0' at row 11
+Warning 1264 Out of range value for column 't1' at row 11
+Warning 1264 Out of range value for column 't20' at row 11
+Warning 1264 Out of range value for column 's' at row 11
+Warning 1264 Out of range value for column 's0' at row 11
+Warning 1264 Out of range value for column 's1' at row 11
+Warning 1264 Out of range value for column 's20' at row 11
+Warning 1264 Out of range value for column 'm' at row 11
+Warning 1264 Out of range value for column 'm0' at row 11
+Warning 1264 Out of range value for column 'm1' at row 11
+Warning 1264 Out of range value for column 'm20' at row 11
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+ALTER TABLE t1 ADD COLUMN i257 INT(257) NULL;
+ERROR 42000: Display width out of range for 'i257' (max = 255)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c INT NULL,
+c1 INT NULL DEFAULT NULL,
+c2 INT NULL DEFAULT 2147483647,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c int(11) YES NULL
+c1 int(11) YES NULL
+c2 int(11) YES 2147483647
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (2147483647,2147483647,2147483647);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 7FFFFFFF 7FFFFFFF 7FFFFFFF
+3 NULL NULL 7FFFFFFF
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 7FFFFFFF
+3 7FFFFFFF
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TINYINT NULL,
+c1 TINYINT NULL DEFAULT NULL,
+c2 TINYINT NULL DEFAULT 127 ,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c tinyint(4) YES NULL
+c1 tinyint(4) YES NULL
+c2 tinyint(4) YES 127
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (127 ,127 ,127 );
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 7F 7F 7F
+3 NULL NULL 7F
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 7F
+3 7F
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c SMALLINT NULL,
+c1 SMALLINT NULL DEFAULT NULL,
+c2 SMALLINT NULL DEFAULT 0,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c smallint(6) YES NULL
+c1 smallint(6) YES NULL
+c2 smallint(6) YES 0
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (0,0,0);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 0 0 0
+3 NULL NULL 0
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 0
+3 0
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c MEDIUMINT NULL,
+c1 MEDIUMINT NULL DEFAULT NULL,
+c2 MEDIUMINT NULL DEFAULT 1,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c mediumint(9) YES NULL
+c1 mediumint(9) YES NULL
+c2 mediumint(9) YES 1
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (1,1,1);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 1 1 1
+3 NULL NULL 1
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 1
+3 1
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c BIGINT NULL,
+c1 BIGINT NULL DEFAULT NULL,
+c2 BIGINT NULL DEFAULT 9223372036854775807,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c bigint(20) YES NULL
+c1 bigint(20) YES NULL
+c2 bigint(20) YES 9223372036854775807
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES (9223372036854775807,9223372036854775807,9223372036854775807);
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 7FFFFFFFFFFFFFFF 7FFFFFFFFFFFFFFF 7FFFFFFFFFFFFFFF
+3 NULL NULL 7FFFFFFFFFFFFFFF
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 7FFFFFFFFFFFFFFF
+3 7FFFFFFFFFFFFFFF
+DROP TABLE t1;
+########################
+# SET columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a SET('') NULL,
+b SET('test1','test2','test3','test4','test5') NULL,
+c SET('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NULL,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') YES NULL
+b set('test1','test2','test3','test4','test5') YES NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,,23'),
+('',5,2),
+(',','test4,test2','');
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ 01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50'51,52,53,54,55,56,57,58,59,60,61,62,63,64
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+ALTER TABLE t1 ADD COLUMN e SET('a','A') NULL;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in SET
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') YES NULL
+b set('test1','test2','test3','test4','test5') YES NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+e set('a','A') YES NULL
+ALTER TABLE t1 ADD COLUMN f SET('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i') NULL;
+ERROR HY000: Too many strings for column f and SET
+SELECT a,b,c,e FROM t1 WHERE FIND_IN_SET('test2',b)>0 OR a != '';
+a b c e
+ test2,test3 01,23,34,44 NULL
+ test2,test4 NULL
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c SET('test1','test2','test3') NULL,
+c1 SET('test1','test2','test3') NULL DEFAULT NULL,
+c2 SET('test1','test2','test3') NULL DEFAULT 'test2,test3',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c set('test1','test2','test3') YES NULL
+c1 set('test1','test2','test3') YES NULL
+c2 set('test1','test2','test3') YES test2,test3
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('test2,test3','test2,test3','test2,test3');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2 74657374322C7465737433 74657374322C7465737433 74657374322C7465737433
+3 NULL NULL 74657374322C7465737433
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2 74657374322C7465737433
+3 74657374322C7465737433
+DROP TABLE t1;
+########################
+# TEXT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+t TEXT NULL,
+t0 TEXT(0) NULL,
+t1 TEXT(1) NULL,
+t300 TEXT(300) NULL,
+tm TEXT(65535) NULL,
+t70k TEXT(70000) NULL,
+t17m TEXT(17000000) NULL,
+tt TINYTEXT NULL,
+m MEDIUMTEXT NULL,
+l LONGTEXT NULL
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+t text YES NULL
+t0 text YES NULL
+t1 tinytext YES NULL
+t300 text YES NULL
+tm text YES NULL
+t70k mediumtext YES NULL
+t17m longtext YES NULL
+tt tinytext YES NULL
+m mediumtext YES NULL
+l longtext YES NULL
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',255), REPEAT('i',1048576), REPEAT('j',1048576) );
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 't' at row 1
+Warning 1265 Data truncated for column 't0' at row 1
+Warning 1265 Data truncated for column 't1' at row 1
+Warning 1265 Data truncated for column 't300' at row 1
+Warning 1265 Data truncated for column 'tm' at row 1
+Warning 1265 Data truncated for column 'tt' at row 1
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+ALTER TABLE t1 ADD COLUMN ttt TEXT(4294967296) NULL;
+ERROR 42000: Display width out of range for 'ttt' (max = 4294967295)
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TEXT NULL,
+c1 TEXT NULL DEFAULT NULL,
+c2 TEXT NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c text YES NULL
+c1 text YES NULL
+c2 text YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c TINYTEXT NULL,
+c1 TINYTEXT NULL DEFAULT NULL,
+c2 TINYTEXT NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c tinytext YES NULL
+c1 tinytext YES NULL
+c2 tinytext YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c MEDIUMTEXT NULL,
+c1 MEDIUMTEXT NULL DEFAULT NULL,
+c2 MEDIUMTEXT NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c mediumtext YES NULL
+c1 mediumtext YES NULL
+c2 mediumtext YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c LONGTEXT NULL,
+c1 LONGTEXT NULL DEFAULT NULL,
+c2 LONGTEXT NULL DEFAULT '',
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c longtext YES NULL
+c1 longtext YES NULL
+c2 longtext YES ''
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+INSERT INTO t1 (c,c1,c2) VALUES ('','','');
+INSERT INTO t1 () VALUES ();
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c) HEX(c1) HEX(c2)
+1 NULL NULL NULL
+2
+3 NULL NULL
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+pk HEX(c2)
+1 NULL
+2
+3
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/col_opt_unsigned.result b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_unsigned.result
new file mode 100644
index 00000000..439f9be9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_unsigned.result
@@ -0,0 +1,749 @@
+########################
+# Fixed point columns (NUMERIC, DECIMAL)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DECIMAL UNSIGNED,
+d0 DECIMAL(0) UNSIGNED,
+d1_1 DECIMAL(1,1) UNSIGNED,
+d10_2 DECIMAL(10,2) UNSIGNED,
+d60_10 DECIMAL(60,10) UNSIGNED,
+n NUMERIC UNSIGNED,
+n0_0 NUMERIC(0,0) UNSIGNED,
+n1 NUMERIC(1) UNSIGNED,
+n20_4 NUMERIC(20,4) UNSIGNED,
+n65_4 NUMERIC(65,4) UNSIGNED,
+pk NUMERIC UNSIGNED PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d decimal(10,0) unsigned YES NULL
+d0 decimal(10,0) unsigned YES NULL
+d1_1 decimal(1,1) unsigned YES NULL
+d10_2 decimal(10,2) unsigned YES NULL
+d60_10 decimal(60,10) unsigned YES NULL
+n decimal(10,0) unsigned YES NULL
+n0_0 decimal(10,0) unsigned YES NULL
+n1 decimal(1,0) unsigned YES NULL
+n20_4 decimal(20,4) unsigned YES NULL
+n65_4 decimal(65,4) unsigned YES NULL
+pk decimal(10,0) unsigned NO PRI NULL
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+6
+);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+Warnings:
+Note 1265 Data truncated for column 'd' at row 1
+Note 1265 Data truncated for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Note 1265 Data truncated for column 'd10_2' at row 1
+Note 1265 Data truncated for column 'd60_10' at row 1
+Note 1265 Data truncated for column 'n' at row 1
+Note 1265 Data truncated for column 'n0_0' at row 1
+Note 1265 Data truncated for column 'n1' at row 1
+Note 1265 Data truncated for column 'n20_4' at row 1
+Note 1265 Data truncated for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.00 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.0000 9999999999999999999999999999999999999999999999999999999999999.1111
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) UNSIGNED;
+ERROR 42000: Too big precision 66 specified for 'n66'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) UNSIGNED;
+ERROR 42000: Too big precision 66 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) UNSIGNED;
+ERROR 42000: Too big scale 66 specified for 'n66_66'. Maximum is 38
+DROP TABLE t1;
+CREATE TABLE t1 (
+a DECIMAL UNSIGNED,
+b NUMERIC UNSIGNED,
+PRIMARY KEY (a)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a decimal(10,0) unsigned NO PRI NULL
+b decimal(10,0) unsigned YES NULL
+INSERT INTO t1 (a,b) VALUES (1.0,-1.0);
+Warnings:
+Warning 1264 Out of range value for column 'b' at row 1
+INSERT INTO t1 (a,b) VALUES (-100,100);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+SELECT a,b FROM t1;
+a b
+0 100
+1 0
+DROP TABLE t1;
+########################
+# Floating point columns (FLOAT, DOUBLE)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT UNSIGNED,
+f0 FLOAT(0) UNSIGNED,
+r1_1 REAL(1,1) UNSIGNED,
+f23_0 FLOAT(23) UNSIGNED,
+f20_3 FLOAT(20,3) UNSIGNED,
+d DOUBLE UNSIGNED,
+d1_0 DOUBLE(1,0) UNSIGNED,
+d10_10 DOUBLE PRECISION (10,10) UNSIGNED,
+d53 DOUBLE(53,0) UNSIGNED,
+d53_10 DOUBLE(53,10) UNSIGNED,
+pk DOUBLE UNSIGNED PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+f float unsigned YES NULL
+f0 float unsigned YES NULL
+r1_1 double(1,1) unsigned YES NULL
+f23_0 float unsigned YES NULL
+f20_3 float(20,3) unsigned YES NULL
+d double unsigned YES NULL
+d1_0 double(1,0) unsigned YES NULL
+d10_10 double(10,10) unsigned YES NULL
+d53 double(53,0) unsigned YES NULL
+d53_10 double(53,10) unsigned YES NULL
+pk double unsigned NO PRI NULL
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 11111111.111
+d10_10 0.0123456789
+d1_0 8
+d53 1234566789123456800
+d53_10 100000000000000000.0000000000
+f0 12345.1
+f20_3 56789.988
+f23_0 123457000
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+99999999999999999999999999999999999999,
+99999999999999999999999999999999999999.9999999999999999,
+0.9,
+99999999999999999999999999999999999999.9,
+99999999999999999.999,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+9,
+0.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+3
+);
+Warnings:
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 11111111.111
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 8
+d1_0 9
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 1e38
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 0
+d 11111111.111
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d53 0
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 0
+f 1e38
+f0 0
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 0.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+SELECT
+CONCAT('', MAX(f)),
+CONCAT('', MAX(f0)),
+CONCAT('', MAX(r1_1)),
+CONCAT('', MAX(f23_0)),
+CONCAT('', MAX(f20_3)),
+CONCAT('', MAX(d)),
+CONCAT('', MAX(d1_0)),
+CONCAT('', MAX(d10_10)),
+CONCAT('', MAX(d53)),
+CONCAT('', MAX(d53_10)) FROM t1;
+CONCAT('', MAX(f)) 1e38
+CONCAT('', MAX(d)) 1e81
+CONCAT('', MAX(d10_10)) 0.9999999999
+CONCAT('', MAX(d1_0)) 9
+CONCAT('', MAX(d53)) 100000000000000000000000000000000000000000000000000000
+CONCAT('', MAX(d53_10)) 10000000000000000000000000000000000000000000.0000000000
+CONCAT('', MAX(f0)) 1e38
+CONCAT('', MAX(f20_3)) 99999998430674940.000
+CONCAT('', MAX(f23_0)) 1e38
+CONCAT('', MAX(r1_1)) 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+5
+);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 0
+d 11111111.111
+d 1e61
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d53 0
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 0
+f 1e38
+f 3.40282e38
+f0 0
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f20_3 0.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+999999999999999999999999999999999999999,
+999999999999999999999999999999999999999.9999999999999999,
+1.9,
+999999999999999999999999999999999999999.9,
+999999999999999999.999,
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+99,
+1.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+6
+);
+Warnings:
+Warning 1916 Got overflow when converting '' to DECIMAL. Value truncated
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 0
+d 11111111.111
+d 1e61
+d 1e65
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d1_0 9
+d53 0
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 0
+f 1e38
+f 3.40282e38
+f 3.40282e38
+f0 0
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f0 3.40282e38
+f20_3 0.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+f23_0 3.40282e38
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) UNSIGNED;
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) UNSIGNED;
+ERROR 42000: Too big precision 256 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) UNSIGNED;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a DOUBLE UNSIGNED,
+b FLOAT UNSIGNED,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a double unsigned YES NULL
+b float unsigned NO PRI NULL
+INSERT INTO t1 (a,b) VALUES (1.0,-1.0);
+Warnings:
+Warning 1264 Out of range value for column 'b' at row 1
+INSERT INTO t1 (a,b) VALUES (-100,100);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+SELECT a,b FROM t1;
+a b
+0 100
+1 0
+DROP TABLE t1;
+########################
+# INT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT UNSIGNED,
+i0 INT(0) UNSIGNED,
+i1 INT(1) UNSIGNED,
+i20 INT(20) UNSIGNED,
+t TINYINT UNSIGNED,
+t0 TINYINT(0) UNSIGNED,
+t1 TINYINT(1) UNSIGNED,
+t20 TINYINT(20) UNSIGNED,
+s SMALLINT UNSIGNED,
+s0 SMALLINT(0) UNSIGNED,
+s1 SMALLINT(1) UNSIGNED,
+s20 SMALLINT(20) UNSIGNED,
+m MEDIUMINT UNSIGNED,
+m0 MEDIUMINT(0) UNSIGNED,
+m1 MEDIUMINT(1) UNSIGNED,
+m20 MEDIUMINT(20) UNSIGNED,
+b BIGINT UNSIGNED,
+b0 BIGINT(0) UNSIGNED,
+b1 BIGINT(1) UNSIGNED,
+b20 BIGINT(20) UNSIGNED,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+i int(10) unsigned YES NULL
+i0 int(10) unsigned YES NULL
+i1 int(1) unsigned YES NULL
+i20 int(20) unsigned YES NULL
+t tinyint(3) unsigned YES NULL
+t0 tinyint(3) unsigned YES NULL
+t1 tinyint(1) unsigned YES NULL
+t20 tinyint(20) unsigned YES NULL
+s smallint(5) unsigned YES NULL
+s0 smallint(5) unsigned YES NULL
+s1 smallint(1) unsigned YES NULL
+s20 smallint(20) unsigned YES NULL
+m mediumint(8) unsigned YES NULL
+m0 mediumint(8) unsigned YES NULL
+m1 mediumint(1) unsigned YES NULL
+m20 mediumint(20) unsigned YES NULL
+b bigint(20) unsigned YES NULL
+b0 bigint(20) unsigned YES NULL
+b1 bigint(1) unsigned YES NULL
+b20 bigint(20) unsigned YES NULL
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 8
+Warning 1264 Out of range value for column 'i0' at row 8
+Warning 1264 Out of range value for column 'i1' at row 8
+Warning 1264 Out of range value for column 'i20' at row 8
+Warning 1264 Out of range value for column 't' at row 8
+Warning 1264 Out of range value for column 't0' at row 8
+Warning 1264 Out of range value for column 't1' at row 8
+Warning 1264 Out of range value for column 't20' at row 8
+Warning 1264 Out of range value for column 's' at row 8
+Warning 1264 Out of range value for column 's0' at row 8
+Warning 1264 Out of range value for column 's1' at row 8
+Warning 1264 Out of range value for column 's20' at row 8
+Warning 1264 Out of range value for column 'm' at row 8
+Warning 1264 Out of range value for column 'm0' at row 8
+Warning 1264 Out of range value for column 'm1' at row 8
+Warning 1264 Out of range value for column 'm20' at row 8
+Warning 1264 Out of range value for column 'i' at row 9
+Warning 1264 Out of range value for column 'i0' at row 9
+Warning 1264 Out of range value for column 'i1' at row 9
+Warning 1264 Out of range value for column 'i20' at row 9
+Warning 1264 Out of range value for column 't' at row 9
+Warning 1264 Out of range value for column 't0' at row 9
+Warning 1264 Out of range value for column 't1' at row 9
+Warning 1264 Out of range value for column 't20' at row 9
+Warning 1264 Out of range value for column 's' at row 9
+Warning 1264 Out of range value for column 's0' at row 9
+Warning 1264 Out of range value for column 's1' at row 9
+Warning 1264 Out of range value for column 's20' at row 9
+Warning 1264 Out of range value for column 'm' at row 9
+Warning 1264 Out of range value for column 'm0' at row 9
+Warning 1264 Out of range value for column 'm1' at row 9
+Warning 1264 Out of range value for column 'm20' at row 9
+Warning 1264 Out of range value for column 'i' at row 10
+Warning 1264 Out of range value for column 'i0' at row 10
+Warning 1264 Out of range value for column 'i1' at row 10
+Warning 1264 Out of range value for column 'i20' at row 10
+Warning 1264 Out of range value for column 't' at row 10
+Warning 1264 Out of range value for column 't0' at row 10
+Warning 1264 Out of range value for column 't1' at row 10
+Warning 1264 Out of range value for column 't20' at row 10
+Warning 1264 Out of range value for column 's' at row 10
+Warning 1264 Out of range value for column 's0' at row 10
+Warning 1264 Out of range value for column 's1' at row 10
+Warning 1264 Out of range value for column 's20' at row 10
+Warning 1264 Out of range value for column 'm' at row 10
+Warning 1264 Out of range value for column 'm0' at row 10
+Warning 1264 Out of range value for column 'm1' at row 10
+Warning 1264 Out of range value for column 'm20' at row 10
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 4294967295 255 255 255 255 65535 65535 65535 65535 16777215 16777215 16777215 16777215 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+ALTER TABLE t1 ADD COLUMN i257 INT(257) UNSIGNED;
+ERROR 42000: Display width out of range for 'i257' (max = 255)
+DROP TABLE t1;
+CREATE TABLE t1 (
+t TINYINT UNSIGNED,
+s SMALLINT UNSIGNED,
+m MEDIUMINT UNSIGNED,
+i INT UNSIGNED,
+b BIGINT UNSIGNED,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+t tinyint(3) unsigned YES NULL
+s smallint(5) unsigned YES NULL
+m mediumint(8) unsigned YES NULL
+i int(10) unsigned YES NULL
+b bigint(20) unsigned NO PRI NULL
+INSERT INTO t1 (t,s,m,i,b) VALUES (255,65535,16777215,4294967295,18446744073709551615);
+INSERT INTO t1 (t,s,m,i,b) VALUES (-1,-1,-1,-1,-1);
+Warnings:
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+SELECT t,s,m,i,b FROM t1;
+t s m i b
+0 0 0 0 0
+255 65535 16777215 4294967295 18446744073709551615
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/col_opt_zerofill.result b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_zerofill.result
new file mode 100644
index 00000000..f8fdfed8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/col_opt_zerofill.result
@@ -0,0 +1,731 @@
+########################
+# Fixed point columns (NUMERIC, DECIMAL)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DECIMAL ZEROFILL,
+d0 DECIMAL(0) ZEROFILL,
+d1_1 DECIMAL(1,1) ZEROFILL,
+d10_2 DECIMAL(10,2) ZEROFILL,
+d60_10 DECIMAL(60,10) ZEROFILL,
+n NUMERIC ZEROFILL,
+n0_0 NUMERIC(0,0) ZEROFILL,
+n1 NUMERIC(1) ZEROFILL,
+n20_4 NUMERIC(20,4) ZEROFILL,
+n65_4 NUMERIC(65,4) ZEROFILL,
+pk NUMERIC ZEROFILL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d decimal(10,0) unsigned zerofill YES NULL
+d0 decimal(10,0) unsigned zerofill YES NULL
+d1_1 decimal(1,1) unsigned zerofill YES NULL
+d10_2 decimal(10,2) unsigned zerofill YES NULL
+d60_10 decimal(60,10) unsigned zerofill YES NULL
+n decimal(10,0) unsigned zerofill YES NULL
+n0_0 decimal(10,0) unsigned zerofill YES NULL
+n1 decimal(1,0) unsigned zerofill YES NULL
+n20_4 decimal(20,4) unsigned zerofill YES NULL
+n65_4 decimal(65,4) unsigned zerofill YES NULL
+pk decimal(10,0) unsigned zerofill NO PRI NULL
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000100 0000123456 0.3 00040000.25 00000000000000000000000000000000123456789123456789.1000100000 0000001024 0000007000 8 0000000000999999.9000 0000000000000000000000000000000000000000009223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000100 0000123456 0.3 00040000.25 00000000000000000000000000000000123456789123456789.1000100000 0000001024 0000007000 8 0000000000999999.9000 0000000000000000000000000000000000000000009223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+6
+);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000100 0000123456 0.3 00040000.25 00000000000000000000000000000000123456789123456789.1000100000 0000001024 0000007000 8 0000000000999999.9000 0000000000000000000000000000000000000000009223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000100 0000123456 0.3 00040000.25 00000000000000000000000000000000123456789123456789.1000100000 0000001024 0000007000 8 0000000000999999.9000 0000000000000000000000000000000000000000009223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+Warnings:
+Note 1265 Data truncated for column 'd' at row 1
+Note 1265 Data truncated for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Note 1265 Data truncated for column 'd10_2' at row 1
+Note 1265 Data truncated for column 'd60_10' at row 1
+Note 1265 Data truncated for column 'n' at row 1
+Note 1265 Data truncated for column 'n0_0' at row 1
+Note 1265 Data truncated for column 'n1' at row 1
+Note 1265 Data truncated for column 'n20_4' at row 1
+Note 1265 Data truncated for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000000 0000000000 0.0 00000000.00 00000000000000000000000000000000000000000000000000.0000000000 0000000000 0000000000 0 0000000000000000.0000 0000000000000000000000000000000000000000000000000000000000000.0000
+0000000100 0000123456 0.3 00040000.25 00000000000000000000000000000000123456789123456789.1000100000 0000001024 0000007000 8 0000000000999999.9000 0000000000000000000000000000000000000000009223372036854775807.0000
+9999999999 9999999999 0.9 99999999.00 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.0000 9999999999999999999999999999999999999999999999999999999999999.1111
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) ZEROFILL;
+ERROR 42000: Too big precision 66 specified for 'n66'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) ZEROFILL;
+ERROR 42000: Too big precision 66 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) ZEROFILL;
+ERROR 42000: Too big scale 66 specified for 'n66_66'. Maximum is 38
+DROP TABLE t1;
+CREATE TABLE t1 (
+a DECIMAL ZEROFILL,
+b NUMERIC ZEROFILL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a decimal(10,0) unsigned zerofill NO PRI NULL
+b decimal(10,0) unsigned zerofill YES NULL
+INSERT INTO t1 (a,b) VALUES (1.1,1234);
+Warnings:
+Note 1265 Data truncated for column 'a' at row 1
+SELECT a,b FROM t1;
+a b
+0000000001 0000001234
+DROP TABLE t1;
+########################
+# Floating point columns (FLOAT, DOUBLE)
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT ZEROFILL,
+f0 FLOAT(0) ZEROFILL,
+r1_1 REAL(1,1) ZEROFILL,
+f23_0 FLOAT(23) ZEROFILL,
+f20_3 FLOAT(20,3) ZEROFILL,
+d DOUBLE ZEROFILL,
+d1_0 DOUBLE(1,0) ZEROFILL,
+d10_10 DOUBLE PRECISION (10,10) ZEROFILL,
+d53 DOUBLE(53,0) ZEROFILL,
+d53_10 DOUBLE(53,10) ZEROFILL,
+pk DOUBLE ZEROFILL PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+f float unsigned zerofill YES NULL
+f0 float unsigned zerofill YES NULL
+r1_1 double(1,1) unsigned zerofill YES NULL
+f23_0 float unsigned zerofill YES NULL
+f20_3 float(20,3) unsigned zerofill YES NULL
+d double unsigned zerofill YES NULL
+d1_0 double(1,0) unsigned zerofill YES NULL
+d10_10 double(10,10) unsigned zerofill YES NULL
+d53 double(53,0) unsigned zerofill YES NULL
+d53_10 double(53,10) unsigned zerofill YES NULL
+pk double unsigned zerofill NO PRI NULL
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 0000012345.1
+d 000000000011111111.111
+d10_10 0.0123456789
+d1_0 8
+d53 00000000000000000000000000000000001234566789123456800
+d53_10 000000000000000000000000100000000000000000.0000000000
+f0 0000012345.1
+f20_3 0000000000056789.988
+f23_0 000123457000
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+99999999999999999999999999999999999999,
+99999999999999999999999999999999999999.9999999999999999,
+0.9,
+99999999999999999999999999999999999999.9,
+99999999999999999.999,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+9,
+0.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+3
+);
+Warnings:
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 0000012345.1
+d 0000000000000000000000
+d 0000000000000000001e81
+d 000000000011111111.111
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 8
+d1_0 9
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000001234566789123456800
+d53 100000000000000000000000000000000000000000000000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 000000000000
+f 000000001e38
+f0 000000000000
+f0 000000001e38
+f0 0000012345.1
+f20_3 0000000000000000.000
+f20_3 0000000000056789.988
+f20_3 99999998430674940.000
+f23_0 000000000000
+f23_0 000000001e38
+f23_0 000123457000
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 0000012345.1
+d 0000000000000000000000
+d 0000000000000000000000
+d 0000000000000000001e81
+d 000000000011111111.111
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000001234566789123456800
+d53 100000000000000000000000000000000000000000000000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 000000000000
+f 000000000000
+f 000000001e38
+f0 000000000000
+f0 000000000000
+f0 000000001e38
+f0 0000012345.1
+f20_3 0000000000000000.000
+f20_3 0000000000000000.000
+f20_3 0000000000056789.988
+f20_3 99999998430674940.000
+f23_0 000000000000
+f23_0 000000000000
+f23_0 000000001e38
+f23_0 000123457000
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+SELECT
+CONCAT('', MAX(f)),
+CONCAT('', MAX(f0)),
+CONCAT('', MAX(r1_1)),
+CONCAT('', MAX(f23_0)),
+CONCAT('', MAX(f20_3)),
+CONCAT('', MAX(d)),
+CONCAT('', MAX(d1_0)),
+CONCAT('', MAX(d10_10)),
+CONCAT('', MAX(d53)),
+CONCAT('', MAX(d53_10)) FROM t1;
+CONCAT('', MAX(f)) 9.999999680285692e37
+CONCAT('', MAX(d)) 1e81
+CONCAT('', MAX(d10_10)) 0.9999999999
+CONCAT('', MAX(d1_0)) 9
+CONCAT('', MAX(d53)) 100000000000000000000000000000000000000000000000000000
+CONCAT('', MAX(d53_10)) 10000000000000000000000000000000000000000000.0000000000
+CONCAT('', MAX(f0)) 9.999999680285692e37
+CONCAT('', MAX(f20_3)) 99999998430674940.000
+CONCAT('', MAX(f23_0)) 9.999999680285692e37
+CONCAT('', MAX(r1_1)) 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+5
+);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 0000012345.1
+d 0000000000000000000000
+d 0000000000000000000000
+d 0000000000000000001e61
+d 0000000000000000001e81
+d 000000000011111111.111
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000001234566789123456800
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 000000000000
+f 000000000000
+f 000000001e38
+f 003.40282e38
+f0 000000000000
+f0 000000000000
+f0 000000001e38
+f0 0000012345.1
+f0 003.40282e38
+f20_3 0000000000000000.000
+f20_3 0000000000000000.000
+f20_3 0000000000056789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 000000000000
+f23_0 000000000000
+f23_0 000000001e38
+f23_0 000123457000
+f23_0 003.40282e38
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+999999999999999999999999999999999999999,
+999999999999999999999999999999999999999.9999999999999999,
+1.9,
+999999999999999999999999999999999999999.9,
+999999999999999999.999,
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+99,
+1.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+6
+);
+Warnings:
+Warning 1916 Got overflow when converting '' to DECIMAL. Value truncated
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 0000012345.1
+d 0000000000000000000000
+d 0000000000000000000000
+d 0000000000000000001e61
+d 0000000000000000001e65
+d 0000000000000000001e81
+d 000000000011111111.111
+d10_10 0.0000000000
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 0
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d1_0 9
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000000000000000000000000
+d53 00000000000000000000000000000000001234566789123456800
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000000000000000000000.0000000000
+d53_10 000000000000000000000000100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 000000000000
+f 000000000000
+f 000000001e38
+f 003.40282e38
+f 003.40282e38
+f0 000000000000
+f0 000000000000
+f0 000000001e38
+f0 0000012345.1
+f0 003.40282e38
+f0 003.40282e38
+f20_3 0000000000000000.000
+f20_3 0000000000000000.000
+f20_3 0000000000056789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 000000000000
+f23_0 000000000000
+f23_0 000000001e38
+f23_0 000123457000
+f23_0 003.40282e38
+f23_0 003.40282e38
+r1_1 0.0
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) ZEROFILL;
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) ZEROFILL;
+ERROR 42000: Too big precision 256 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) ZEROFILL;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a DOUBLE ZEROFILL,
+b FLOAT ZEROFILL,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a double unsigned zerofill YES NULL
+b float unsigned zerofill NO PRI NULL
+INSERT INTO t1 (a,b) VALUES (1,1234.5);
+SELECT a,b FROM t1;
+a b
+0000000000000000000001 0000001234.5
+DROP TABLE t1;
+########################
+# INT columns
+########################
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT ZEROFILL,
+i0 INT(0) ZEROFILL,
+i1 INT(1) ZEROFILL,
+i20 INT(20) ZEROFILL,
+t TINYINT ZEROFILL,
+t0 TINYINT(0) ZEROFILL,
+t1 TINYINT(1) ZEROFILL,
+t20 TINYINT(20) ZEROFILL,
+s SMALLINT ZEROFILL,
+s0 SMALLINT(0) ZEROFILL,
+s1 SMALLINT(1) ZEROFILL,
+s20 SMALLINT(20) ZEROFILL,
+m MEDIUMINT ZEROFILL,
+m0 MEDIUMINT(0) ZEROFILL,
+m1 MEDIUMINT(1) ZEROFILL,
+m20 MEDIUMINT(20) ZEROFILL,
+b BIGINT ZEROFILL,
+b0 BIGINT(0) ZEROFILL,
+b1 BIGINT(1) ZEROFILL,
+b20 BIGINT(20) ZEROFILL,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+i int(10) unsigned zerofill YES NULL
+i0 int(10) unsigned zerofill YES NULL
+i1 int(1) unsigned zerofill YES NULL
+i20 int(20) unsigned zerofill YES NULL
+t tinyint(3) unsigned zerofill YES NULL
+t0 tinyint(3) unsigned zerofill YES NULL
+t1 tinyint(1) unsigned zerofill YES NULL
+t20 tinyint(20) unsigned zerofill YES NULL
+s smallint(5) unsigned zerofill YES NULL
+s0 smallint(5) unsigned zerofill YES NULL
+s1 smallint(1) unsigned zerofill YES NULL
+s20 smallint(20) unsigned zerofill YES NULL
+m mediumint(8) unsigned zerofill YES NULL
+m0 mediumint(8) unsigned zerofill YES NULL
+m1 mediumint(1) unsigned zerofill YES NULL
+m20 mediumint(20) unsigned zerofill YES NULL
+b bigint(20) unsigned zerofill YES NULL
+b0 bigint(20) unsigned zerofill YES NULL
+b1 bigint(1) unsigned zerofill YES NULL
+b20 bigint(20) unsigned zerofill YES NULL
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000001 0000000002 3 00000000000000000004 005 006 7 00000000000000000008 00009 00010 11 00000000000000000012 00000013 00000014 15 00000000000000000016 00000000000000000017 00000000000000000018 19 00000000000000000020
+2147483647 2147483647 2147483647 00000000002147483647 127 127 127 00000000000000000127 32767 32767 32767 00000000000000032767 08388607 08388607 8388607 00000000000008388607 09223372036854775807 09223372036854775807 9223372036854775807 09223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000001 0000000002 3 00000000000000000004 005 006 7 00000000000000000008 00009 00010 11 00000000000000000012 00000013 00000014 15 00000000000000000016 00000000000000000017 00000000000000000018 19 00000000000000000020
+2147483647 2147483647 2147483647 00000000002147483647 127 127 127 00000000000000000127 32767 32767 32767 00000000000000032767 08388607 08388607 8388607 00000000000008388607 09223372036854775807 09223372036854775807 9223372036854775807 09223372036854775807
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 8
+Warning 1264 Out of range value for column 'i0' at row 8
+Warning 1264 Out of range value for column 'i1' at row 8
+Warning 1264 Out of range value for column 'i20' at row 8
+Warning 1264 Out of range value for column 't' at row 8
+Warning 1264 Out of range value for column 't0' at row 8
+Warning 1264 Out of range value for column 't1' at row 8
+Warning 1264 Out of range value for column 't20' at row 8
+Warning 1264 Out of range value for column 's' at row 8
+Warning 1264 Out of range value for column 's0' at row 8
+Warning 1264 Out of range value for column 's1' at row 8
+Warning 1264 Out of range value for column 's20' at row 8
+Warning 1264 Out of range value for column 'm' at row 8
+Warning 1264 Out of range value for column 'm0' at row 8
+Warning 1264 Out of range value for column 'm1' at row 8
+Warning 1264 Out of range value for column 'm20' at row 8
+Warning 1264 Out of range value for column 'i' at row 9
+Warning 1264 Out of range value for column 'i0' at row 9
+Warning 1264 Out of range value for column 'i1' at row 9
+Warning 1264 Out of range value for column 'i20' at row 9
+Warning 1264 Out of range value for column 't' at row 9
+Warning 1264 Out of range value for column 't0' at row 9
+Warning 1264 Out of range value for column 't1' at row 9
+Warning 1264 Out of range value for column 't20' at row 9
+Warning 1264 Out of range value for column 's' at row 9
+Warning 1264 Out of range value for column 's0' at row 9
+Warning 1264 Out of range value for column 's1' at row 9
+Warning 1264 Out of range value for column 's20' at row 9
+Warning 1264 Out of range value for column 'm' at row 9
+Warning 1264 Out of range value for column 'm0' at row 9
+Warning 1264 Out of range value for column 'm1' at row 9
+Warning 1264 Out of range value for column 'm20' at row 9
+Warning 1264 Out of range value for column 'i' at row 10
+Warning 1264 Out of range value for column 'i0' at row 10
+Warning 1264 Out of range value for column 'i1' at row 10
+Warning 1264 Out of range value for column 'i20' at row 10
+Warning 1264 Out of range value for column 't' at row 10
+Warning 1264 Out of range value for column 't0' at row 10
+Warning 1264 Out of range value for column 't1' at row 10
+Warning 1264 Out of range value for column 't20' at row 10
+Warning 1264 Out of range value for column 's' at row 10
+Warning 1264 Out of range value for column 's0' at row 10
+Warning 1264 Out of range value for column 's1' at row 10
+Warning 1264 Out of range value for column 's20' at row 10
+Warning 1264 Out of range value for column 'm' at row 10
+Warning 1264 Out of range value for column 'm0' at row 10
+Warning 1264 Out of range value for column 'm1' at row 10
+Warning 1264 Out of range value for column 'm20' at row 10
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000000 0000000000 0 00000000000000000000 000 000 0 00000000000000000000 00000 00000 0 00000000000000000000 00000000 00000000 0 00000000000000000000 00000000000000000000 00000000000000000000 0 00000000000000000000
+0000000001 0000000002 3 00000000000000000004 005 006 7 00000000000000000008 00009 00010 11 00000000000000000012 00000013 00000014 15 00000000000000000016 00000000000000000017 00000000000000000018 19 00000000000000000020
+2147483647 2147483647 2147483647 00000000002147483647 127 127 127 00000000000000000127 32767 32767 32767 00000000000000032767 08388607 08388607 8388607 00000000000008388607 09223372036854775807 09223372036854775807 9223372036854775807 09223372036854775807
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 09223372036854775807 09223372036854775807 9223372036854775807 09223372036854775807
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+4294967295 4294967295 4294967295 00000000004294967295 255 255 255 00000000000000000255 65535 65535 65535 00000000000000065535 16777215 16777215 16777215 00000000000016777215 18446744073709551615 18446744073709551615 18446744073709551615 18446744073709551615
+ALTER TABLE t1 ADD COLUMN i257 INT(257) ZEROFILL;
+ERROR 42000: Display width out of range for 'i257' (max = 255)
+DROP TABLE t1;
+CREATE TABLE t1 (
+t TINYINT ZEROFILL,
+s SMALLINT ZEROFILL,
+m MEDIUMINT ZEROFILL,
+i INT ZEROFILL,
+b BIGINT ZEROFILL,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+t tinyint(3) unsigned zerofill YES NULL
+s smallint(5) unsigned zerofill YES NULL
+m mediumint(8) unsigned zerofill YES NULL
+i int(10) unsigned zerofill YES NULL
+b bigint(20) unsigned zerofill NO PRI NULL
+INSERT INTO t1 (t,s,m,i,b) VALUES (1,10,100,1000,0);
+SELECT t,s,m,i,b FROM t1;
+t s m i b
+001 00010 00000100 0000001000 00000000000000000000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/collation.result b/storage/rocksdb/mysql-test/rocksdb/r/collation.result
new file mode 100644
index 00000000..10e0d9b0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/collation.result
@@ -0,0 +1,144 @@
+call mtr.add_suppression("Invalid pattern");
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text) engine=rocksdb charset utf8;
+ALTER TABLE t1 ADD INDEX (value);
+ERROR HY000: Unsupported collation on string indexed column test.t1.value Use binary collation (binary, latin1_bin, utf8_bin).
+DROP TABLE t1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.t1.value Use binary collation (latin1_bin, binary, utf8_bin).
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value3(50))) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.t1.value3 Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_check=0;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value3(50))) engine=rocksdb charset utf8;
+DROP TABLE t1;
+SET GLOBAL rocksdb_strict_collation_check=1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value2)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset latin1 collate latin1_bin;
+ALTER TABLE t1 collate=latin1_general_ci;
+DROP TABLE t1;
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset utf8 collate utf8_bin;
+DROP TABLE t1;
+CREATE TABLE t1 (id varchar(20) collate latin1_bin, value varchar(50) collate utf8_bin, value2 varchar(50) collate latin1_bin, value3 text, primary key (id), index(value, value2)) engine=rocksdb;
+DROP TABLE t1;
+SET GLOBAL rocksdb_strict_collation_exceptions=t1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE t2 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.t2.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="t.*";
+CREATE TABLE t123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t123;
+CREATE TABLE s123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.s123.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions=".t.*";
+CREATE TABLE xt123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE xt123;
+CREATE TABLE t123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.t123.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*|t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions=",s.*,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="|s.*|t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*||t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,t.*,";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*|t.*|";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="||||,,,,s.*,,|,,||,t.*,,|||,,,";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.u1.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions='t1';
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb;
+ALTER TABLE t1 AUTO_INCREMENT=1;
+DROP TABLE t1;
+CREATE TABLE t2 (id INT primary key, value varchar(50), index(value)) engine=rocksdb;
+ERROR HY000: Unsupported collation on string indexed column test.t2.value Use binary collation (latin1_bin, binary, utf8_bin).
+CREATE TABLE t2 (id INT primary key, value varchar(50)) engine=rocksdb;
+ALTER TABLE t2 ADD INDEX(value);
+ERROR HY000: Unsupported collation on string indexed column test.t2.value Use binary collation (latin1_bin, binary, utf8_bin).
+DROP TABLE t2;
+SET GLOBAL rocksdb_strict_collation_exceptions="[a-b";
+FOUND 1 /Invalid pattern in strict_collation_exceptions: \[a-b/ in mysqld.1.err
+CREATE TABLE a (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.a.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="[a-b]";
+CREATE TABLE a (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+CREATE TABLE b (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+CREATE TABLE c (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.c.value Use binary collation (latin1_bin, binary, utf8_bin).
+DROP TABLE a, b;
+call mtr.add_suppression("Invalid pattern in strict_collation_exceptions:");
+SET GLOBAL rocksdb_strict_collation_exceptions="abc\\";
+FOUND 1 /Invalid pattern in strict_collation_exceptions: abc/ in mysqld.1.err
+CREATE TABLE abc (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.abc.value Use binary collation (latin1_bin, binary, utf8_bin).
+SET GLOBAL rocksdb_strict_collation_exceptions="abc";
+CREATE TABLE abc (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+CREATE TABLE abcd (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+ERROR HY000: Unsupported collation on string indexed column test.abcd.value Use binary collation (latin1_bin, binary, utf8_bin).
+DROP TABLE abc;
+SET GLOBAL rocksdb_strict_collation_exceptions=null;
+SET GLOBAL rocksdb_strict_collation_check=1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value)) engine=rocksdb charset utf8;
+Warnings:
+Warning 1210 Unsupported collation on string indexed column test.t1.value Use binary collation (binary, latin1_bin, utf8_bin).
+DROP TABLE t1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text) engine=rocksdb charset utf8;
+ALTER TABLE t1 ADD INDEX (value);
+Warnings:
+Warning 1210 Unsupported collation on string indexed column test.t1.value Use binary collation (binary, latin1_bin, utf8_bin).
+DROP TABLE t1;
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset latin1 collate latin1_bin;
+ALTER TABLE t1 collate=latin1_general_ci;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/collation_exception.result b/storage/rocksdb/mysql-test/rocksdb/r/collation_exception.result
new file mode 100644
index 00000000..83d72d6c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/collation_exception.result
@@ -0,0 +1,25 @@
+CREATE TABLE `r1.lol` (
+`c1` int(10) NOT NULL DEFAULT '0',
+`c2` int(11) NOT NULL DEFAULT '0',
+`c3` int(1) NOT NULL DEFAULT '0',
+`c4` int(11) NOT NULL DEFAULT '0',
+`c5` int(11) NOT NULL DEFAULT '0',
+`c6` varchar(100) NOT NULL DEFAULT '',
+`c7` varchar(100) NOT NULL DEFAULT '',
+`c8` varchar(255) NOT NULL DEFAULT '',
+`c9` int(10) NOT NULL DEFAULT '125',
+`c10` int(10) NOT NULL DEFAULT '125',
+`c11` text NOT NULL,
+`c12` int(11) NOT NULL DEFAULT '0',
+`c13` int(10) NOT NULL DEFAULT '0',
+`c14` text NOT NULL,
+`c15` blob NOT NULL,
+`c16` int(11) NOT NULL DEFAULT '0',
+`c17` int(11) NOT NULL DEFAULT '0',
+`c18` int(11) NOT NULL DEFAULT '0',
+PRIMARY KEY (`c1`),
+KEY i1 (`c4`),
+KEY i2 (`c7`),
+KEY i3 (`c2`)) ENGINE=RocksDB DEFAULT CHARSET=latin1;
+DROP INDEX i1 ON `r1.lol`;
+DROP TABLE `r1.lol`;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/com_rpc_tx.result b/storage/rocksdb/mysql-test/rocksdb/r/com_rpc_tx.result
new file mode 100644
index 00000000..789ce12e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/com_rpc_tx.result
@@ -0,0 +1,21 @@
+CREATE DATABASE db_rpc;
+USE db_rpc;
+CREATE TABLE t1(pk INT PRIMARY KEY) ENGINE=rocksdb;
+SET GLOBAL rocksdb_enable_2pc=1;
+SET autocommit = 0;
+SET autocommit = 0;
+BEGIN;
+BEGIN;
+SELECT * from t1;
+pk
+SELECT * from t1;
+pk
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2);
+COMMIT;
+COMMIT;
+SELECT * from db_rpc.t1;
+pk
+1
+2
+DROP DATABASE db_rpc;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/commit_in_the_middle_ddl.result b/storage/rocksdb/mysql-test/rocksdb/r/commit_in_the_middle_ddl.result
new file mode 100644
index 00000000..4d64d128
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/commit_in_the_middle_ddl.result
@@ -0,0 +1,14 @@
+DROP TABLE IF EXISTS a;
+create table a (id int, value int, primary key (id) comment 'cf_a') engine=rocksdb;
+set rocksdb_bulk_load=1;
+set rocksdb_commit_in_the_middle=1;
+alter table a add index v (value) COMMENT 'cf_a';
+set rocksdb_bulk_load=0;
+set rocksdb_commit_in_the_middle=0;
+select count(*) from a force index(primary);
+count(*)
+100000
+select count(*) from a force index(v);
+count(*)
+100000
+DROP TABLE a;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/compact_deletes.result b/storage/rocksdb/mysql-test/rocksdb/r/compact_deletes.result
new file mode 100644
index 00000000..5b3cfaf7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/compact_deletes.result
@@ -0,0 +1,78 @@
+DROP TABLE IF EXISTS r1;
+create table r1 (
+id1 int,
+id2 int,
+type int,
+value varchar(100),
+value2 int,
+value3 int,
+primary key (type, id1, id2),
+index id1_type (id1, type, value2, value, id2)
+) engine=rocksdb collate latin1_bin;
+select 'loading data';
+loading data
+loading data
+set global rocksdb_force_flush_memtable_now=1;
+optimize table r1;
+Table Op Msg_type Msg_text
+test.r1 optimize status OK
+Test 1: Do a bunch of updates without setting the compaction sysvar
+Expect: no compaction
+set global rocksdb_compaction_sequential_deletes_window=0;
+set global rocksdb_compaction_sequential_deletes= 0;
+set global rocksdb_compaction_sequential_deletes_file_size=0;
+set global rocksdb_force_flush_memtable_now=1;
+wait_for_delete: 0
+There are deletes left
+SET GLOBAL rocksdb_compaction_sequential_deletes= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_window= 0;
+Test 2: Do a bunch of updates and set the compaction sysvar
+Expect: compaction
+set global rocksdb_compaction_sequential_deletes_window=1000;
+set global rocksdb_compaction_sequential_deletes= 990;
+set global rocksdb_compaction_sequential_deletes_file_size=0;
+set global rocksdb_force_flush_memtable_now=1;
+wait_for_delete: 1
+No more deletes left
+SET GLOBAL rocksdb_compaction_sequential_deletes= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_window= 0;
+Test 3: Do a bunch of updates and set the compaction sysvar and a file size to something large
+Expect: no compaction
+set global rocksdb_compaction_sequential_deletes_window=1000;
+set global rocksdb_compaction_sequential_deletes= 1000;
+set global rocksdb_compaction_sequential_deletes_file_size=1000000;
+set global rocksdb_force_flush_memtable_now=1;
+wait_for_delete: 0
+There are deletes left
+SET GLOBAL rocksdb_compaction_sequential_deletes= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_window= 0;
+Test 4: Do a bunch of secondary key updates and set the compaction sysvar
+Expect: compaction
+set global rocksdb_compaction_sequential_deletes_window=1000;
+set global rocksdb_compaction_sequential_deletes= 50;
+set global rocksdb_compaction_sequential_deletes_file_size=0;
+set global rocksdb_force_flush_memtable_now=1;
+wait_for_delete: 1
+No more deletes left
+SET GLOBAL rocksdb_compaction_sequential_deletes= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_window= 0;
+Test 5: Do a bunch of secondary key updates and set the compaction sysvar,
+and rocksdb_compaction_sequential_deletes_count_sd turned on
+Expect: compaction
+SET @save_rocksdb_compaction_sequential_deletes_count_sd = @@global.rocksdb_compaction_sequential_deletes_count_sd;
+SET GLOBAL rocksdb_compaction_sequential_deletes_count_sd= ON;
+set global rocksdb_compaction_sequential_deletes_window=1000;
+set global rocksdb_compaction_sequential_deletes= 50;
+set global rocksdb_compaction_sequential_deletes_file_size=0;
+set global rocksdb_force_flush_memtable_now=1;
+wait_for_delete: 1
+No more deletes left
+SET GLOBAL rocksdb_compaction_sequential_deletes= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_window= 0;
+SET GLOBAL rocksdb_compaction_sequential_deletes_count_sd= @save_rocksdb_compaction_sequential_deletes_count_sd;
+drop table r1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/compression_zstd.result b/storage/rocksdb/mysql-test/rocksdb/r/compression_zstd.result
new file mode 100644
index 00000000..2c1cab7f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/compression_zstd.result
@@ -0,0 +1,3 @@
+# restart: --rocksdb_default_cf_options=compression_per_level=kZSTDNotFinalCompression;compression_opts=-14:4:0;
+create table t (id int primary key) engine=rocksdb;
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/concurrent_alter.result b/storage/rocksdb/mysql-test/rocksdb/r/concurrent_alter.result
new file mode 100644
index 00000000..b8c73b24
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/concurrent_alter.result
@@ -0,0 +1,12 @@
+DROP DATABASE IF EXISTS mysqlslap;
+CREATE DATABASE mysqlslap;
+use mysqlslap;
+CREATE TABLE a1 (a int, b int) ENGINE=ROCKSDB;
+INSERT INTO a1 VALUES (1, 1);
+SHOW CREATE TABLE a1;
+Table Create Table
+a1 CREATE TABLE `a1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=DEFAULT_CHARSET
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_read_committed.result b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_read_committed.result
new file mode 100644
index 00000000..637354e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_read_committed.result
@@ -0,0 +1,151 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+STAT_TYPE VALUE
+DB_NUM_SNAPSHOTS 0
+connection con1;
+COMMIT;
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+STAT_TYPE VALUE
+DB_NUM_SNAPSHOTS 0
+connection con1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+connection con2;
+INSERT INTO t1 (a) VALUES (1);
+connection con1;
+# If consistent read works on this isolation level (READ COMMITTED), the following SELECT should not return the value we inserted (1)
+SELECT a FROM t1;
+a
+1
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE r1 (id int primary key, value int, value2 int) engine=ROCKSDB;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+insert into r1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4);
+BEGIN;
+connection con2;
+INSERT INTO r1 values (5,5,5);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+connection con2;
+INSERT INTO r1 values (6,6,6);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+COMMIT;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+connection con2;
+INSERT INTO r1 values (7,7,7);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+connection con2;
+INSERT INTO r1 values (8,8,8);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+COMMIT;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+connection con2;
+INSERT INTO r1 values (9,9,9);
+connection con1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+connection con2;
+INSERT INTO r1 values (10,10,10);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 4062
+INSERT INTO r1 values (11,11,11);
+ERROR: 0
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+11 11 11
+drop table r1;
+connection default;
+disconnect con1;
+disconnect con2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_repeatable_read.result b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_repeatable_read.result
new file mode 100644
index 00000000..d9be37ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_repeatable_read.result
@@ -0,0 +1,144 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+STAT_TYPE VALUE
+DB_NUM_SNAPSHOTS 1
+connection con1;
+COMMIT;
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+STAT_TYPE VALUE
+DB_NUM_SNAPSHOTS 0
+connection con1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+connection con2;
+INSERT INTO t1 (a) VALUES (1);
+connection con1;
+# If consistent read works on this isolation level (REPEATABLE READ), the following SELECT should not return the value we inserted (1)
+SELECT a FROM t1;
+a
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE r1 (id int primary key, value int, value2 int) engine=ROCKSDB;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+insert into r1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4);
+BEGIN;
+connection con2;
+INSERT INTO r1 values (5,5,5);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+connection con2;
+INSERT INTO r1 values (6,6,6);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+COMMIT;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+connection con2;
+INSERT INTO r1 values (7,7,7);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+connection con2;
+INSERT INTO r1 values (8,8,8);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+COMMIT;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+connection con2;
+INSERT INTO r1 values (9,9,9);
+connection con1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+connection con2;
+INSERT INTO r1 values (10,10,10);
+connection con1;
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR: 0
+INSERT INTO r1 values (11,11,11);
+ERROR: 4059
+SELECT * FROM r1;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+drop table r1;
+connection default;
+disconnect con1;
+disconnect con2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_serializable.result b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_serializable.result
new file mode 100644
index 00000000..9c55b0dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/cons_snapshot_serializable.result
@@ -0,0 +1,24 @@
+# -- WARNING ----------------------------------------------------------------
+# According to I_S.ENGINES, does not support transactions.
+# If it is true, the test will most likely fail; you can
+# either create an rdiff file, or add the test to disabled.def.
+# If transactions should be supported, check the data in Information Schema.
+# ---------------------------------------------------------------------------
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection con2;
+INSERT INTO t1 (a) VALUES (1);
+connection con1;
+# If consistent read works on this isolation level (SERIALIZABLE), the following SELECT should not return the value we inserted (1)
+SELECT a FROM t1;
+a
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/corrupted_data_reads_debug.result b/storage/rocksdb/mysql-test/rocksdb/r/corrupted_data_reads_debug.result
new file mode 100644
index 00000000..47f7bb92
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/corrupted_data_reads_debug.result
@@ -0,0 +1,74 @@
+#
+# Test how MyRocks handles reading corrupted data from disk.
+# Data corruption is simulated at source-code level.
+#
+#
+# A test for case when data in the table *record* is longer
+# than table DDL expects it to be
+#
+create table t1 (
+pk int not null primary key,
+col1 varchar(10)
+) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3);
+select * from t1;
+pk col1
+1 1
+2 2
+3 3
+set @tmp1=@@rocksdb_verify_row_debug_checksums;
+set rocksdb_verify_row_debug_checksums=1;
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read1";
+select * from t1 where pk=1;
+ERROR HY000: Got error 202 'Found data corruption.' from ROCKSDB
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read1";
+set rocksdb_verify_row_debug_checksums=@tmp1;
+select * from t1 where pk=1;
+pk col1
+1 1
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read2";
+select * from t1 where pk=1;
+ERROR HY000: Got error 202 'Found data corruption.' from ROCKSDB
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read2";
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read3";
+select * from t1 where pk=1;
+ERROR HY000: Got error 202 'Found data corruption.' from ROCKSDB
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read3";
+insert into t1 values(4,'0123456789');
+select * from t1;
+pk col1
+1 1
+2 2
+3 3
+4 0123456789
+drop table t1;
+#
+# A test for case when index data is longer than table DDL
+# expects it to be
+#
+create table t2 (
+pk varchar(4) not null primary key,
+col1 int not null
+) engine=rocksdb collate latin1_bin;
+insert into t2 values ('ABCD',1);
+select * from t2;
+pk col1
+ABCD 1
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_read1";
+select * from t2;
+ERROR HY000: Got error 202 'Found data corruption.' from ROCKSDB
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_read1";
+drop table t2;
+create table t2 (
+pk varchar(4) not null primary key,
+col1 int not null
+) engine=rocksdb;
+insert into t2 values ('ABCD',1);
+select * from t2;
+pk col1
+ABCD 1
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_read1";
+select * from t2;
+ERROR HY000: Got error 202 'Found data corruption.' from ROCKSDB
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_read1";
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/covered_unpack_info_format.result b/storage/rocksdb/mysql-test/rocksdb/r/covered_unpack_info_format.result
new file mode 100644
index 00000000..19521533
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/covered_unpack_info_format.result
@@ -0,0 +1,73 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+id INT,
+fake_id INT,
+bigfield VARCHAR(4096),
+PRIMARY KEY (id),
+KEY bf (bigfield(32)),
+KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+(8, 1008, REPEAT('b', 8)),
+(24, 1024, REPEAT('c', 24)),
+(31, 1031, REPEAT('d', 31)),
+(32, 1032, REPEAT('x', 32)),
+(33, 1033, REPEAT('y', 33)),
+(128, 1128, REPEAT('z', 128));
+SELECT * FROM t1;
+id fake_id bigfield
+1 1001 a
+8 1008 bbbbbbbb
+24 1024 cccccccccccccccccccccccc
+31 1031 ddddddddddddddddddddddddddddddd
+32 1032 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+33 1033 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+128 1128 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+# Eligible for optimization, access via fake_id only
+SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1031;
+id bigfield
+31 ddddddddddddddddddddddddddddddd
+include/assert.inc [Eligible for optimization, access via fake_id only: 2 rocksdb_covered_secondary_key_lookups]
+# Not eligible for optimization, access via fake_id of big row.
+SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1033;
+id bigfield
+33 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+include/assert.inc [Not eligible for optimization, access via fake_id of big row.: 0 rocksdb_covered_secondary_key_lookups]
+DROP TABLE t1;
+set session debug= '+d,MYROCKS_NO_COVERED_BITMAP_FORMAT';
+CREATE TABLE t1 (
+id INT,
+fake_id INT,
+bigfield VARCHAR(4096),
+PRIMARY KEY (id),
+KEY bf (bigfield(32)),
+KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+set session debug= '-d,MYROCKS_NO_COVERED_BITMAP_FORMAT';
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+(8, 1008, REPEAT('b', 8)),
+(24, 1024, REPEAT('c', 24)),
+(31, 1031, REPEAT('d', 31)),
+(32, 1032, REPEAT('x', 32)),
+(33, 1033, REPEAT('y', 33)),
+(128, 1128, REPEAT('z', 128));
+SELECT * FROM t1;
+id fake_id bigfield
+1 1001 a
+8 1008 bbbbbbbb
+24 1024 cccccccccccccccccccccccc
+31 1031 ddddddddddddddddddddddddddddddd
+32 1032 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+33 1033 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+128 1128 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+# No longer eligible for optimization since no covered bitmap was stored.
+SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1031;
+id bigfield
+31 ddddddddddddddddddddddddddddddd
+include/assert.inc [No longer eligible for optimization since no covered bitmap was stored.: 0 rocksdb_covered_secondary_key_lookups]
+# Not eligible for optimization.
+SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1033;
+id bigfield
+33 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+include/assert.inc [Not eligible for optimization.: 0 rocksdb_covered_secondary_key_lookups]
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/create_no_primary_key_table.result b/storage/rocksdb/mysql-test/rocksdb/r/create_no_primary_key_table.result
new file mode 100644
index 00000000..1c45cfd0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/create_no_primary_key_table.result
@@ -0,0 +1,52 @@
+USE mysql;
+CREATE TABLE mysql_table (a INT) ENGINE=ROCKSDB;
+CREATE TABLE test.mysql_table (a INT) ENGINE=ROCKSDB;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+USE test;
+CREATE TABLE mysql_table (a INT) ENGINE=ROCKSDB;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+CREATE TABLE IF NOT EXISTS mysql_table_2 (a INT) ENGINE=ROCKSDB;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+CREATE TABLE mysql_table_no_cols ENGINE=ROCKSDB;
+ERROR 42000: A table must have at least 1 column
+CREATE TABLE mysql.mysql_table_2 (a INT) ENGINE=ROCKSDB;
+CREATE TABLE mysql_primkey (a INT PRIMARY KEY, b INT, c INT, d INT, INDEX (c)) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey DROP b, DROP a, ADD (f INT PRIMARY KEY);
+ALTER TABLE mysql_primkey DROP PRIMARY KEY;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+CREATE TABLE mysql_primkey2 (a INT PRIMARY KEY, b INT, c INT) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey2 DROP b;
+ALTER TABLE mysql_primkey2 ADD (b INT);
+ALTER TABLE mysql_primkey2 DROP c, DROP A;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+CREATE TABLE mysql_primkey3 (a INT PRIMARY KEY, b INT, c INT, INDEX indexonb (b), INDEX indexonc (c)) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey3 DROP INDEX indexonb;
+ALTER TABLE mysql_primkey3 DROP c;
+ALTER TABLE mysql_primkey3 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+CREATE TABLE mysql_primkey4(a INT, b INT, PRIMARY KEY(a), INDEX si (a, b)) ENGINE=ROCKSDB;
+DROP INDEX si ON mysql_primkey4;
+DROP INDEX `PRIMARY` ON mysql_primkey4;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+ALTER TABLE mysql.mysql_table ADD PRIMARY KEY (a);
+ALTER TABLE mysql.mysql_table DROP PRIMARY KEY;
+SET default_storage_engine=ROCKSDB;
+CREATE TABLE mysql_noeng(a INT, b INT);
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+SET sql_mode="";
+CREATE TABLE mysql_noeng_sub(a INT, b INT) ENGINE=BOGUS_ENGINE;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+CREATE TABLE mysql_primkey5 LIKE mysql_primkey;
+SET @@global.block_create_no_primary_key = false;
+CREATE TABLE mysql_no_primkey (a INT) ENGINE=ROCKSDB;
+SET @@global.block_create_no_primary_key = true;
+CREATE TABLE mysql_block_no_primkey LIKE mysql_no_primkey;
+ERROR HY000: Table without primary key cannot be created outside mysql schema.
+DROP TABLE mysql_primkey;
+DROP TABLE mysql_primkey2;
+DROP TABLE mysql_primkey3;
+DROP TABLE mysql_primkey4;
+DROP TABLE mysql_primkey5;
+DROP TABLE mysql_no_primkey;
+USE mysql;
+DROP TABLE mysql_table;
+DROP TABLE mysql_table_2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/create_table.result b/storage/rocksdb/mysql-test/rocksdb/r/create_table.result
new file mode 100644
index 00000000..8c879d82
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/create_table.result
@@ -0,0 +1,165 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CREATE TABLE IF NOT EXISTS t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+Warnings:
+Note 1050 Table 't1' already exists
+CREATE TABLE t2 LIKE t1;
+SHOW CREATE TABLE t2;
+Table Create Table
+t2 CREATE TABLE `t2` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+CREATE TEMPORARY TABLE t2 (a INT PRIMARY KEY) ENGINE=rocksdb;
+ERROR HY000: Table storage engine 'ROCKSDB' does not support the create option 'TEMPORARY'
+DROP TABLE t2;
+DROP TABLE IF EXISTS t1;
+SET default_storage_engine = rocksdb;
+CREATE TABLE t1 (a INT PRIMARY KEY);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY) AS SELECT 1 AS a UNION SELECT 2 AS a;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT * FROM t1;
+a
+1
+2
+FLUSH LOGS;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1(c1 INT,c2 INT,c3 INT,c4 INT,c5 INT,c6 INT,c7 INT,c8 INT,c9 INT,
+c10 INT,c11 INT,c12 INT,c13 INT,c14 INT,c15 INT,c16 INT,c17 INT,
+c18 INT,c19 INT,c20 INT,c21 INT,c22 INT,c23 INT,c24 INT,c25 INT,
+c26 INT,c27 INT,c28 INT,c29 INT,c30 INT,c31 INT,c32 INT,c33 INT,
+c34 INT,c35 INT,c36 INT,c37 INT,c38 INT,c39 INT,c40 INT,c41 INT,
+c42 INT,c43 INT,c44 INT,c45 INT,c46 INT,c47 INT,c48 INT,c49 INT,
+c50 INT,c51 INT,c52 INT,c53 INT,c54 INT,c55 INT,c56 INT,c57 INT,
+c58 INT,c59 INT,c60 INT,c61 INT,c62 INT,c63 INT,c64 INT,c65 INT,
+c66 INT,c67 INT,c68 INT,c69 INT,c70 INT,c71 INT,c72 INT,c73 INT,
+c74 INT,c75 INT,c76 INT,c77 INT,c78 INT,c79 INT,c80 INT,c81 INT,
+c82 INT,c83 INT,c84 INT,c85 INT,c86 INT,c87 INT,c88 INT,c89 INT,
+c90 INT,c91 INT,c92 INT,c93 INT,c94 INT,c95 INT,c96 INT,c97 INT,
+c98 INT,c99 INT,c100 INT,c101 INT,c102 INT,c103 INT,c104 INT,
+c105 INT,c106 INT,c107 INT,c108 INT,c109 INT,c110 INT,c111 INT,
+c112 INT,c113 INT,c114 INT,c115 INT,c116 INT,c117 INT,c118 INT,
+c119 INT,c120 INT,c121 INT,c122 INT,c123 INT,c124 INT,c125 INT,
+c126 INT,c127 INT,c128 INT,c129 INT,c130 INT,c131 INT,c132 INT,
+c133 INT,c134 INT,c135 INT,c136 INT,c137 INT,c138 INT,c139 INT,
+c140 INT,c141 INT,c142 INT,c143 INT,c144 INT,c145 INT,c146 INT,
+c147 INT,c148 INT,c149 INT,c150 INT,c151 INT,c152 INT,c153 INT,
+c154 INT,c155 INT,c156 INT,c157 INT,c158 INT,c159 INT,c160 INT,
+c161 INT,c162 INT,c163 INT,c164 INT,c165 INT,c166 INT,c167 INT,
+c168 INT,c169 INT,c170 INT,c171 INT,c172 INT,c173 INT,c174 INT,
+c175 INT,c176 INT,c177 INT,c178 INT,c179 INT,c180 INT,c181 INT,
+c182 INT,c183 INT,c184 INT,c185 INT,c186 INT,c187 INT,c188 INT,
+c189 INT,c190 INT,c191 INT,c192 INT,c193 INT,c194 INT,c195 INT,
+c196 INT,c197 INT,c198 INT,c199 INT,c200 INT,c201 INT,c202 INT,
+c203 INT,c204 INT,c205 INT,c206 INT,c207 INT,c208 INT,c209 INT,
+c210 INT,c211 INT,c212 INT,c213 INT,c214 INT,c215 INT,c216 INT,
+c217 INT,c218 INT,c219 INT,c220 INT,c221 INT,c222 INT,c223 INT,
+c224 INT,c225 INT,c226 INT,c227 INT,c228 INT,c229 INT,c230 INT,
+c231 INT,c232 INT,c233 INT,c234 INT,c235 INT,c236 INT,c237 INT,
+c238 INT,c239 INT,c240 INT,c241 INT,c242 INT,c243 INT,c244 INT,
+c245 INT,c246 INT,c247 INT,c248 INT,c249 INT,c250 INT,c251 INT,
+c252 INT,c253 INT,c254 INT,c255 INT,c256 INT,c257 INT,c258 INT,
+c259 INT,c260 INT,c261 INT,c262 INT,c263 INT,c264 INT,c265 INT,
+c266 INT,c267 INT,c268 INT,c269 INT,c270 INT,c271 INT,c272 INT,
+c273 INT,c274 INT,c275 INT,c276 INT,c277 INT,c278 INT,c279 INT,
+c280 INT,c281 INT,c282 INT,c283 INT,c284 INT,c285 INT,c286 INT,
+c287 INT,c288 INT,c289 INT,c290 INT,c291 INT,c292 INT,c293 INT,
+c294 INT,c295 INT,c296 INT,c297 INT,c298 INT,c299 INT,c300 INT,
+c301 INT,c302 INT,c303 INT,c304 INT,c305 INT,c306 INT,c307 INT,
+c308 INT,c309 INT,c310 INT,c311 INT,c312 INT,c313 INT,c314 INT,
+c315 INT,c316 INT,c317 INT,c318 INT,c319 INT,c320 INT,c321 INT,
+c322 INT,c323 INT,c324 INT,c325 INT,c326 INT,c327 INT,c328 INT,
+c329 INT,c330 INT,c331 INT,c332 INT,c333 INT,c334 INT,c335 INT,
+c336 INT,c337 INT,c338 INT,c339 INT,c340 INT,c341 INT,c342 INT,
+c343 INT,c344 INT,c345 INT,c346 INT,c347 INT,c348 INT,c349 INT,
+c350 INT,c351 INT,c352 INT,c353 INT,c354 INT,c355 INT,c356 INT,
+c357 INT,c358 INT,c359 INT,c360 INT,c361 INT,c362 INT,c363 INT,
+c364 INT,c365 INT,c366 INT,c367 INT,c368 INT,c369 INT,c370 INT,
+c371 INT,c372 INT,c373 INT,c374 INT,c375 INT,c376 INT,c377 INT,
+c378 INT,c379 INT,c380 INT,c381 INT,c382 INT,c383 INT,c384 INT,
+c385 INT,c386 INT,c387 INT,c388 INT,c389 INT,c390 INT,c391 INT,
+c392 INT,c393 INT,c394 INT,c395 INT,c396 INT,c397 INT,c398 INT,
+c399 INT,c400 INT,c401 INT,c402 INT,c403 INT,c404 INT,c405 INT,
+c406 INT,c407 INT,c408 INT,c409 INT,c410 INT,c411 INT,c412 INT,
+c413 INT,c414 INT,c415 INT,c416 INT,c417 INT,c418 INT,c419 INT,
+c420 INT,c421 INT,c422 INT,c423 INT,c424 INT,c425 INT,c426 INT,
+c427 INT,c428 INT,c429 INT,c430 INT,c431 INT,c432 INT,c433 INT,
+c434 INT,c435 INT,c436 INT,c437 INT,c438 INT,c439 INT,c440 INT,
+c441 INT,c442 INT,c443 INT,c444 INT,c445 INT,c446 INT,c447 INT,
+c448 INT,
+KEY (c1,c2,c3,c4,c5,c6,c7),KEY (c8,c9,c10,c11,c12,c13,c14),
+KEY (c15,c16,c17,c18,c19,c20,c21),KEY (c22,c23,c24,c25,c26,c27,c28),
+KEY (c29,c30,c31,c32,c33,c34,c35),KEY (c36,c37,c38,c39,c40,c41,c42),
+KEY (c43,c44,c45,c46,c47,c48,c49),KEY (c50,c51,c52,c53,c54,c55,c56),
+KEY (c57,c58,c59,c60,c61,c62,c63),KEY (c64,c65,c66,c67,c68,c69,c70),
+KEY (c71,c72,c73,c74,c75,c76,c77),KEY (c78,c79,c80,c81,c82,c83,c84),
+KEY (c85,c86,c87,c88,c89,c90,c91),KEY (c92,c93,c94,c95,c96,c97,c98),
+KEY (c99,c100,c101,c102,c103,c104,c105),
+KEY (c106,c107,c108,c109,c110,c111,c112),
+KEY (c113,c114,c115,c116,c117,c118,c119),
+KEY (c120,c121,c122,c123,c124,c125,c126),
+KEY (c127,c128,c129,c130,c131,c132,c133),
+KEY (c134,c135,c136,c137,c138,c139,c140),
+KEY (c141,c142,c143,c144,c145,c146,c147),
+KEY (c148,c149,c150,c151,c152,c153,c154),
+KEY (c155,c156,c157,c158,c159,c160,c161),
+KEY (c162,c163,c164,c165,c166,c167,c168),
+KEY (c169,c170,c171,c172,c173,c174,c175),
+KEY (c176,c177,c178,c179,c180,c181,c182),
+KEY (c183,c184,c185,c186,c187,c188,c189),
+KEY (c190,c191,c192,c193,c194,c195,c196),
+KEY (c197,c198,c199,c200,c201,c202,c203),
+KEY (c204,c205,c206,c207,c208,c209,c210),
+KEY (c211,c212,c213,c214,c215,c216,c217),
+KEY (c218,c219,c220,c221,c222,c223,c224),
+KEY (c225,c226,c227,c228,c229,c230,c231),
+KEY (c232,c233,c234,c235,c236,c237,c238),
+KEY (c239,c240,c241,c242,c243,c244,c245),
+KEY (c246,c247,c248,c249,c250,c251,c252),
+KEY (c253,c254,c255,c256,c257,c258,c259),
+KEY (c260,c261,c262,c263,c264,c265,c266),
+KEY (c267,c268,c269,c270,c271,c272,c273),
+KEY (c274,c275,c276,c277,c278,c279,c280),
+KEY (c281,c282,c283,c284,c285,c286,c287),
+KEY (c288,c289,c290,c291,c292,c293,c294),
+KEY (c295,c296,c297,c298,c299,c300,c301),
+KEY (c302,c303,c304,c305,c306,c307,c308),
+KEY (c309,c310,c311,c312,c313,c314,c315),
+KEY (c316,c317,c318,c319,c320,c321,c322),
+KEY (c323,c324,c325,c326,c327,c328,c329),
+KEY (c330,c331,c332,c333,c334,c335,c336),
+KEY (c337,c338,c339,c340,c341,c342,c343),
+KEY (c344,c345,c346,c347,c348,c349,c350),
+KEY (c351,c352,c353,c354,c355,c356,c357),
+KEY (c358,c359,c360,c361,c362,c363,c364),
+KEY (c365,c366,c367,c368,c369,c370,c371),
+KEY (c372,c373,c374,c375,c376,c377,c378),
+KEY (c379,c380,c381,c382,c383,c384,c385),
+KEY (c386,c387,c388,c389,c390,c391,c392),
+KEY (c393,c394,c395,c396,c397,c398,c399),
+KEY (c400,c401,c402,c403,c404,c405,c406),
+KEY (c407,c408,c409,c410,c411,c412,c413),
+KEY (c414,c415,c416,c417,c418,c419,c420),
+KEY (c421,c422,c423,c424,c425,c426,c427),
+KEY (c428,c429,c430,c431,c432,c433,c434),
+KEY (c435,c436,c437,c438,c439,c440,c441),
+KEY (c442,c443,c444,c445,c446,c447,c448));
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ddl_high_priority.result b/storage/rocksdb/mysql-test/rocksdb/r/ddl_high_priority.result
new file mode 100644
index 00000000..1e2636c8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ddl_high_priority.result
@@ -0,0 +1,1058 @@
+##
+## Using the system variable high_priority_ddl"
+##
+create user test_user1@localhost;
+grant all on test to test_user1@localhost;
+create user test_user2@localhost;
+grant all on test to test_user2@localhost;
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = alter table t1 modify i bigint;
+## high_priority_cmd = alter high_priority table t1 modify i bigint;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+alter table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter table t1 modify i bigint;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = alter table t1 rename t1_new;
+## high_priority_cmd = alter high_priority table t1 rename t1_new;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+begin; insert into t1 values (4); select i from t1;;
+i
+1
+2
+3
+4
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+alter table t1 rename t1_new;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter table t1 rename t1_new;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+select * from t1_new;
+i
+1
+2
+3
+drop table t1_new;
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = drop table t1;
+## high_priority_cmd = drop high_priority table t1;
+## should_kill = 0
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 write;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = drop table t1;
+## high_priority_cmd = drop high_priority table t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read; begin; insert into t1 values (4);;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop table t1;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = con2
+## cmd = alter table t1 modify i bigint;
+## high_priority_cmd = alter high_priority table t1 modify i bigint;
+## should_kill = 0
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: con2
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and con2 exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user2 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: con2
+alter table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user2 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = create index idx1 on t1 (i);
+## high_priority_cmd = create high_priority index idx1 on t1 (i);
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+create index idx1 on t1 (i);;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+create index idx1 on t1 (i);;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = drop index idx1 on t1;
+## high_priority_cmd = drop high_priority index idx1 on t1;
+## should_kill = 1
+## recreate_table = 0
+## throw_error = 1
+
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES MUL NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop index idx1 on t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop index idx1 on t1;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = truncate t1;
+## high_priority_cmd = truncate high_priority t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+truncate t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+truncate t1;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+## high_priority_cmd = create high_priority trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = drop trigger ins_sum;
+## high_priority_cmd = drop high_priority trigger ins_sum;
+## should_kill = 1
+## recreate_table = 0
+## throw_error = 1
+
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop trigger ins_sum;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop trigger ins_sum;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = optimize table t1;
+## high_priority_cmd = optimize high_priority table t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 0
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+optimize table t1;;
+Table Op Msg_type Msg_text
+test.t1 optimize Error Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+test.t1 optimize status Operation failed
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+optimize table t1;;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 1;
+## con_block = con1
+## con_kill = default
+## cmd = lock tables t1 write;
+## high_priority_cmd = optimize high_priority table t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+lock tables t1 write;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+set high_priority_ddl = 1;
+select @@high_priority_ddl;
+@@high_priority_ddl
+1
+rename table t1 to t2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+lock tables t1 write;;
+set high_priority_ddl = 0;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+unlock tables;
+drop user test_user1@localhost;
+drop user test_user2@localhost;
+drop table if exists t1;
+##
+## Using HIGH_PRIORITY syntax
+##
+create user test_user1@localhost;
+grant all on test to test_user1@localhost;
+create user test_user2@localhost;
+grant all on test to test_user2@localhost;
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = alter table t1 modify i bigint;
+## high_priority_cmd = alter high_priority table t1 modify i bigint;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+alter table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter high_priority table t1 modify i bigint;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = alter table t1 rename t1_new;
+## high_priority_cmd = alter high_priority table t1 rename t1_new;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+begin; insert into t1 values (4); select i from t1;;
+i
+1
+2
+3
+4
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+alter table t1 rename t1_new;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter high_priority table t1 rename t1_new;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+select * from t1_new;
+i
+1
+2
+3
+drop table t1_new;
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = drop table t1;
+## high_priority_cmd = drop high_priority table t1;
+## should_kill = 0
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 write;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop high_priority table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = drop table t1;
+## high_priority_cmd = drop high_priority table t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read; begin; insert into t1 values (4);;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop table t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop high_priority table t1;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = con2
+## cmd = alter table t1 modify i bigint;
+## high_priority_cmd = alter high_priority table t1 modify i bigint;
+## should_kill = 0
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: con2
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and con2 exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user2 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: con2
+alter table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+alter high_priority table t1 modify i bigint;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user2 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = create index idx1 on t1 (i);
+## high_priority_cmd = create high_priority index idx1 on t1 (i);
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+create index idx1 on t1 (i);;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+create high_priority index idx1 on t1 (i);;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = drop index idx1 on t1;
+## high_priority_cmd = drop high_priority index idx1 on t1;
+## should_kill = 1
+## recreate_table = 0
+## throw_error = 1
+
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES MUL NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop index idx1 on t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop high_priority index idx1 on t1;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = truncate t1;
+## high_priority_cmd = truncate high_priority t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+truncate t1;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+truncate high_priority t1;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+## high_priority_cmd = create high_priority trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 1
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+create trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+create high_priority trigger ins_sum before insert on t1 for each row set @sum = @sum + new.i;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = drop trigger ins_sum;
+## high_priority_cmd = drop high_priority trigger ins_sum;
+## should_kill = 1
+## recreate_table = 0
+## throw_error = 1
+
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+drop trigger ins_sum;;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+drop high_priority trigger ins_sum;;
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+
+## Test parameters:
+## use_sys_var = 0;
+## con_block = con1
+## con_kill = default
+## cmd = optimize table t1;
+## high_priority_cmd = optimize high_priority table t1;
+## should_kill = 1
+## recreate_table = 1
+## throw_error = 0
+
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection: con1
+lock tables t1 read;;
+connection: default
+set lock_wait_timeout = 0.02;
+set high_priority_lock_wait_timeout = 0.02;
+describe t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+connection: default (for show processlist)
+# both con1 and default exist
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+<Id> test_user1 <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+connection: default
+optimize table t1;;
+Table Op Msg_type Msg_text
+test.t1 optimize Error Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: test.t1
+test.t1 optimize status Operation failed
+optimize high_priority table t1;;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+connection: default (for show processlist)
+show processlist;
+Id User Host db Command Time State Info Rows examined Rows sent Tid Srv_Id
+<Id> root <Host> test <Command> <Time> <State> <Info> <RExam> <RSent> <TID> <SrvID>
+drop user test_user1@localhost;
+drop user test_user2@localhost;
+drop table if exists t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/deadlock.result b/storage/rocksdb/mysql-test/rocksdb/r/deadlock.result
new file mode 100644
index 00000000..3e2f5709
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/deadlock.result
@@ -0,0 +1,37 @@
+#
+# Validate that deadlock errors don't occur with a high level of concurrency
+#
+# Disable for valgrind because this takes too long
+DROP DATABASE IF EXISTS mysqlslap;
+CREATE DATABASE mysqlslap;
+USE mysqlslap;
+CREATE TABLE t1(id1 BIGINT, id2 BIGINT, count INT, PRIMARY KEY(id1, id2), KEY(id2)) ENGINE=rocksdb;
+CREATE TABLE t1rev(id1 BIGINT, id2 BIGINT, count INT, PRIMARY KEY(id1, id2) COMMENT "rev:cf2", KEY(id2) COMMENT "rev:cf2") ENGINE=rocksdb;
+SET @save = @@global.rocksdb_lock_wait_timeout;
+SET GLOBAL rocksdb_lock_wait_timeout = 60;
+SELECT count from t1;
+count
+50000
+SELECT count from t1;
+count
+100000
+SELECT count from t1;
+count
+150000
+SELECT count from t1;
+count
+200000
+SELECT count from t1rev;
+count
+50000
+SELECT count from t1rev;
+count
+100000
+SELECT count from t1rev;
+count
+150000
+SELECT count from t1rev;
+count
+200000
+SET GLOBAL rocksdb_lock_wait_timeout = @save;
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/deadlock_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/deadlock_stats.result
new file mode 100644
index 00000000..79cb6bb0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/deadlock_stats.result
@@ -0,0 +1,14 @@
+set @prior_lock_wait_timeout = @@rocksdb_lock_wait_timeout; set @prior_deadlock_detect = @@rocksdb_deadlock_detect; set global rocksdb_deadlock_detect = on; set global rocksdb_lock_wait_timeout = 100000;;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection default;
+create table t (i int primary key) engine=rocksdb;
+insert into t values (1), (2), (3);
+#
+# The following is disabled due:
+# MDEV-13404: MyRocks upstream uses I_S.table_statistics.row_lock_deadlocks, should we import?
+#
+disconnect con1;
+disconnect con2;
+set global rocksdb_lock_wait_timeout = @prior_lock_wait_timeout; set global rocksdb_deadlock_detect = @prior_deadlock_detect;;
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/deadlock_tracking.result b/storage/rocksdb/mysql-test/rocksdb/r/deadlock_tracking.result
new file mode 100644
index 00000000..fffae916
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/deadlock_tracking.result
@@ -0,0 +1,488 @@
+set @prior_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_deadlock_detect = @@rocksdb_deadlock_detect;
+set @prior_max_latest_deadlocks = @@rocksdb_max_latest_deadlocks;
+set global rocksdb_deadlock_detect = on;
+set global rocksdb_lock_wait_timeout = 10000;
+# Clears deadlock buffer of any prior deadlocks.
+set global rocksdb_max_latest_deadlocks = 0;
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+create table t (i int primary key) engine=rocksdb;
+insert into t values (1), (2), (3);
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+Deadlock #1
+begin;
+select * from t where i=1 for update;
+i
+1
+begin;
+select * from t where i=2 for update;
+i
+2
+select * from t where i=2 for update;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+i
+2
+rollback;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+Deadlock #2
+begin;
+select * from t where i=1 for update;
+i
+1
+begin;
+select * from t where i=2 for update;
+i
+2
+select * from t where i=2 for update;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+i
+2
+rollback;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+set global rocksdb_max_latest_deadlocks = 10;
+Deadlock #3
+begin;
+select * from t where i=1 for update;
+i
+1
+begin;
+select * from t where i=2 for update;
+i
+2
+select * from t where i=2 for update;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+i
+2
+rollback;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+set global rocksdb_max_latest_deadlocks = 1;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+set rocksdb_deadlock_detect_depth = 2;
+Deadlock #4
+begin;
+select * from t where i=1 for update;
+i
+1
+begin;
+select * from t where i=2 for update;
+i
+2
+begin;
+select * from t where i=3 for update;
+i
+3
+select * from t where i=2 for update;
+select * from t where i=3 for update;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_deadlocks';
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+select case when variable_value-@a = 1 then 'true' else 'false' end as deadlocks from information_schema.global_status where variable_name='rocksdb_row_lock_deadlocks';
+deadlocks
+true
+rollback;
+i
+3
+rollback;
+i
+2
+rollback;
+set global rocksdb_max_latest_deadlocks = 5;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+-------DEADLOCK EXCEEDED MAX DEPTH-------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+Deadlock #5
+begin;
+select * from t where i=1 for update;
+i
+1
+begin;
+select * from t where i=2 for update;
+i
+2
+begin;
+select * from t where i=3 lock in share mode;
+i
+3
+select * from t where i=100 for update;
+i
+select * from t where i=101 for update;
+i
+select * from t where i=2 for update;
+select * from t where i=3 lock in share mode;
+i
+3
+select * from t where i=200 for update;
+i
+select * from t where i=201 for update;
+i
+select * from t where i=1 lock in share mode;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+i
+2
+rollback;
+rollback;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: SHARED
+INDEX NAME: PRIMARY
+TABLE NAME: test.t
+
+--------TXN_ID GOT DEADLOCK---------
+
+-------DEADLOCK EXCEEDED MAX DEPTH-------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+Deadlock #6
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5);
+begin;
+update t1 set value=value+100 where id=1;
+update t1 set value=value+100 where id=2;
+begin;
+update t1 set value=value+200 where id=3;
+update t1 set value=value+100 where id=3;
+update t1 set value=value+200 where id=1;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+select * from t1;
+id value
+1 101
+2 102
+3 103
+4 4
+5 5
+drop table t1;
+set global rocksdb_lock_wait_timeout = @prior_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_deadlock_detect;
+drop table t;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: NOT FOUND; IDX_ID
+TABLE NAME: NOT FOUND; IDX_ID
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: NOT FOUND; IDX_ID
+TABLE NAME: NOT FOUND; IDX_ID
+
+--------TXN_ID GOT DEADLOCK---------
+
+*** DEADLOCK PATH
+=========================================
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: EXCLUSIVE
+INDEX NAME: NOT FOUND; IDX_ID
+TABLE NAME: NOT FOUND; IDX_ID
+---------------WAITING FOR---------------
+TSTAMP
+TXN_ID
+COLUMN FAMILY NAME: default
+KEY
+LOCK TYPE: SHARED
+INDEX NAME: NOT FOUND; IDX_ID
+TABLE NAME: NOT FOUND; IDX_ID
+
+--------TXN_ID GOT DEADLOCK---------
+
+-------DEADLOCK EXCEEDED MAX DEPTH-------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+set global rocksdb_max_latest_deadlocks = 0;
+# Clears deadlock buffer of any existent deadlocks.
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+show engine rocksdb transaction status;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/delete.result b/storage/rocksdb/mysql-test/rocksdb/r/delete.result
new file mode 100644
index 00000000..8ec3c50f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/delete.result
@@ -0,0 +1,166 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (10000,'foobar'),(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+DELETE FROM t1 WHERE b IN ('c');
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+DELETE FROM t1 WHERE a < 0 OR b = 'a';
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+5 e
+5 e
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+SELECT c,d FROM t2;
+c d
+b 2
+b 2
+d 4
+e 5
+e 5
+foobar 10000
+foobar 10000
+DELETE t2.* FROM t1, t2 WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+5 e
+5 e
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+DELETE FROM t2, t1.* USING t2, t1 WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+2 b
+2 b
+4 d
+5 e
+5 e
+SELECT c,d FROM t2;
+c d
+DELETE FROM t1;
+SELECT a,b FROM t1;
+a b
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'f'),(7,'g'),(8,'h'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+BEGIN;
+DELETE FROM t1 WHERE b IN ('c');
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+6 f
+6 f
+7 g
+7 g
+8 h
+8 h
+DELETE FROM t1 WHERE a < 0 OR b = 'a';
+COMMIT;
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+6 f
+6 f
+7 g
+7 g
+8 h
+8 h
+BEGIN;
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+SAVEPOINT spt1;
+DELETE FROM t1;
+RELEASE SAVEPOINT spt1;
+ROLLBACK;
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+6 f
+6 f
+7 g
+7 g
+8 h
+8 h
+BEGIN;
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+SAVEPOINT spt1;
+DELETE FROM t1;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+ROLLBACK TO SAVEPOINT spt1;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+COMMIT;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+SELECT a,b FROM t1;
+a b
+10000 foobar
+10000 foobar
+2 b
+2 b
+4 d
+4 d
+5 e
+5 e
+6 f
+6 f
+7 g
+7 g
+8 h
+8 h
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/delete_ignore.result b/storage/rocksdb/mysql-test/rocksdb/r/delete_ignore.result
new file mode 100644
index 00000000..f8ac42c4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/delete_ignore.result
@@ -0,0 +1,59 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (10000,'foobar'),(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (pk INT AUTO_INCREMENT PRIMARY KEY, c CHAR(8), d INT) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+10000 foobar
+10000 foobar
+2 b
+2 b
+3 c
+3 c
+4 d
+4 d
+5 e
+5 e
+SELECT c,d FROM t2;
+c d
+a 1
+a 1
+b 2
+b 2
+c 3
+c 3
+d 4
+d 4
+e 5
+e 5
+foobar 10000
+foobar 10000
+DELETE IGNORE FROM t1 WHERE b IS NOT NULL ORDER BY a LIMIT 1;
+SELECT a,b FROM t1;
+a b
+1 a
+10000 foobar
+10000 foobar
+2 b
+2 b
+3 c
+3 c
+4 d
+4 d
+5 e
+5 e
+DELETE IGNORE t1.*, t2.* FROM t1, t2 WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+Warnings:
+Warning 1242 Subquery returns more than 1 row
+SELECT a,b FROM t1;
+a b
+1 a
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/delete_quick.result b/storage/rocksdb/mysql-test/rocksdb/r/delete_quick.result
new file mode 100644
index 00000000..4173d875
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/delete_quick.result
@@ -0,0 +1,24 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+DELETE QUICK FROM t1 WHERE a = 1 OR b > 'foo';
+SELECT a,b FROM t1;
+a b
+2 b
+3 c
+4 d
+5 e
+CREATE TABLE t2 (c CHAR(8), d INT, PRIMARY KEY (c)) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+SELECT c,d FROM t2;
+c d
+b 2
+c 3
+d 4
+e 5
+DELETE QUICK FROM t2, t1.* USING t2, t1 WHERE c IS NULL OR a = d;
+SELECT a,b FROM t1;
+a b
+SELECT c,d FROM t2;
+c d
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/delete_with_keys.result b/storage/rocksdb/mysql-test/rocksdb/r/delete_with_keys.result
new file mode 100644
index 00000000..c94708b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/delete_with_keys.result
@@ -0,0 +1,38 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+DELETE FROM t1 WHERE b > 'y';
+DELETE FROM t1 WHERE a=2;
+SELECT a,b FROM t1;
+a b
+1 a
+3 c
+4 d
+5 e
+6 x
+7 y
+DELETE FROM t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+DELETE FROM t1 WHERE b > 'y';
+DELETE FROM t1 WHERE a=2;
+SELECT a,b FROM t1;
+a b
+1 a
+3 c
+4 d
+5 e
+6 x
+7 y
+DELETE FROM t1;
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, c INT, pk INT AUTO_INCREMENT PRIMARY KEY, KEY(a), KEY (b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b,c) VALUES (1,2,3),(4,5,6),(7,8,9);
+DELETE FROM t1 WHERE a = 10 OR b = 20 ORDER BY c LIMIT 1;
+SELECT a,b,c FROM t1;
+a b c
+1 2 3
+4 5 6
+7 8 9
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/describe.result b/storage/rocksdb/mysql-test/rocksdb/r/describe.result
new file mode 100644
index 00000000..6d43f89c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/describe.result
@@ -0,0 +1,19 @@
+DROP TABLE IF EXISTS t1, t2, t3;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'foo'),(2, 'b');
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY (b)) ENGINE=rocksdb CHARACTER SET utf8;
+INSERT INTO t2 (a,b) VALUES (1, 'bar');
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb CHARACTER SET utf8;
+DESCRIBE t1;
+Field Type Null Key Default Extra
+a int(11) NO PRI NULL
+b char(8) YES NULL
+DESC t2 a;
+Field Type Null Key Default Extra
+a int(11) YES NULL
+DESCRIBE t3 '%';
+Field Type Null Key Default Extra
+a int(11) YES NULL
+b char(8) YES NULL
+pk int(11) NO PRI NULL auto_increment
+DROP TABLE t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/drop_database.result b/storage/rocksdb/mysql-test/rocksdb/r/drop_database.result
new file mode 100644
index 00000000..f220efe9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/drop_database.result
@@ -0,0 +1,6 @@
+DROP DATABASE IF EXISTS test_drop_database;
+CREATE DATABASE test_drop_database;
+CREATE TABLE t1 (a int, b int, c int, primary key (a), unique key (b)) ENGINE=ROCKSDB;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (a);
+DROP TABLE t1;
+DROP DATABASE test_drop_database;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/drop_index_inplace.result b/storage/rocksdb/mysql-test/rocksdb/r/drop_index_inplace.result
new file mode 100644
index 00000000..668f7e8f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/drop_index_inplace.result
@@ -0,0 +1,158 @@
+drop table if exists t1;
+CREATE TABLE t1 (a INT, b INT AUTO_INCREMENT, KEY ka(a), KEY kb(a,b), PRIMARY KEY(b)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`b`),
+ KEY `ka` (`a`),
+ KEY `kb` (`a`,`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+INSERT INTO t1 (a) VALUES (1);
+INSERT INTO t1 (a) VALUES (3);
+INSERT INTO t1 (a) VALUES (5);
+ALTER TABLE t1 DROP INDEX ka, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`b`),
+ KEY `kb` (`a`,`b`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+SELECT * FROM t1 FORCE INDEX(ka) where a > 1;
+ERROR 42000: Key 'ka' doesn't exist in table 't1'
+SELECT * FROM t1 FORCE INDEX(kb) where a > 1;
+a b
+3 2
+5 3
+SELECT * FROM t1 where b > 1;
+a b
+3 2
+5 3
+DROP TABLE t1;
+CREATE TABLE t1 (a INT AUTO_INCREMENT, b INT, c INT, KEY kb(b), KEY kbc(b,c), KEY kc(c), PRIMARY KEY(a)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kb` (`b`),
+ KEY `kbc` (`b`,`c`),
+ KEY `kc` (`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kc` (`c`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+# restart
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kc` (`c`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+SELECT * FROM t1 FORCE INDEX(kc) where c > 3;
+a b c
+2 3 4
+3 5 6
+5 3 4
+6 5 6
+SELECT * FROM t1 where b > 3;
+a b c
+3 5 6
+6 5 6
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT, c INT, KEY kb(b), KEY kbc(b,c), KEY kc(c), PRIMARY KEY(a)) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 0 NULL NULL LSMTREE
+t1 1 kb 1 b A 0 NULL NULL YES LSMTREE
+t1 1 kbc 1 b A 0 NULL NULL YES LSMTREE
+t1 1 kbc 2 c A 0 NULL NULL YES LSMTREE
+t1 1 kc 1 c A 0 NULL NULL YES LSMTREE
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc, ALGORITHM=INPLACE;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 0 NULL NULL LSMTREE
+t1 1 kc 1 c A 0 NULL NULL YES LSMTREE
+ALTER TABLE t1 DROP PRIMARY KEY;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 1 kc 1 c A 0 NULL NULL YES LSMTREE
+ALTER TABLE t1 DROP INDEX kc, ALGORITHM=INPLACE;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+DROP TABLE t1;
+CREATE TABLE t1 (a INT AUTO_INCREMENT, b INT, c INT, PRIMARY KEY(a)) ENGINE=rocksdb;
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b);
+ALTER TABLE t1 ADD UNIQUE INDEX kbc(b,c);
+ALTER TABLE t1 ADD UNIQUE INDEX kc(c);
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 0 NULL NULL LSMTREE
+t1 0 kb 1 b A 0 NULL NULL YES LSMTREE
+t1 0 kbc 1 b A 0 NULL NULL YES LSMTREE
+t1 0 kbc 2 c A 0 NULL NULL YES LSMTREE
+t1 0 kc 1 c A 0 NULL NULL YES LSMTREE
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A 0 NULL NULL LSMTREE
+t1 0 kc 1 c A 0 NULL NULL YES LSMTREE
+# restart
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+SELECT * FROM t1 FORCE INDEX(kc) where c > 3;
+a b c
+2 3 4
+3 5 6
+ALTER TABLE t1 DROP INDEX kc, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ `b` int(11) DEFAULT NULL,
+ `c` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE IF NOT EXISTS t1 (col1 INT, col2 INT, col3 INT);
+INSERT INTO t1 (col1,col2,col3) VALUES (1,2,3);
+ALTER TABLE t1 ADD KEY idx ( col1, col2 );
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+ALTER TABLE t1 DROP COLUMN col2;
+ALTER TABLE t1 DROP COLUMN col3;
+DROP TABLE t1;
+CREATE TABLE IF NOT EXISTS t1 (col1 INT, col2 INT, col3 INT);
+INSERT INTO t1 (col1,col2,col3) VALUES (1,2,3);
+ALTER TABLE t1 ADD KEY idx ( col1, col2 );
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+ALTER TABLE t1 DROP COLUMN col2;
+ALTER TABLE t1 DROP COLUMN col3;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/drop_table.result b/storage/rocksdb/mysql-test/rocksdb/r/drop_table.result
new file mode 100644
index 00000000..4d20242f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/drop_table.result
@@ -0,0 +1,79 @@
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+call mtr.add_suppression("LibRocksDB");
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+CREATE TABLE t1 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t2 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t3 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t4 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+DELETE FROM t4;
+drop table t2;
+DELETE FROM t1;
+DELETE FROM t4;
+drop table t3;
+DELETE FROM t1;
+DELETE FROM t4;
+SET GLOBAL rocksdb_max_manual_compactions = 2;
+SET GLOBAL rocksdb_debug_manual_compaction_delay = 3600;
+connect con1, localhost, root,,;
+connect con2, localhost, root,,;
+connect con3, localhost, root,,;
+connection con1;
+SET GLOBAL rocksdb_compact_cf='cf1';
+connection con2;
+SET GLOBAL rocksdb_compact_cf='rev:cf2';
+connection default;
+select * from information_schema.global_status where variable_name='rocksdb_manual_compactions_running';
+VARIABLE_NAME VARIABLE_VALUE
+ROCKSDB_MANUAL_COMPACTIONS_RUNNING 1
+connection con3;
+SET GLOBAL rocksdb_compact_cf='cf1';
+ERROR HY000: Internal error: Can't schedule more manual compactions. Increase rocksdb_max_manual_compactions or stop issuing more manual compactions.
+SET GLOBAL rocksdb_compact_cf='rev:cf2';
+ERROR HY000: Internal error: Can't schedule more manual compactions. Increase rocksdb_max_manual_compactions or stop issuing more manual compactions.
+connection default;
+drop table t4;
+CREATE TABLE t5 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t5;
+drop table t5;
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/drop_table2.result b/storage/rocksdb/mysql-test/rocksdb/r/drop_table2.result
new file mode 100644
index 00000000..aec4138c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/drop_table2.result
@@ -0,0 +1,66 @@
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+# restart
+CREATE TABLE t1 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t2 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t3 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+CREATE TABLE t4 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t1;
+DELETE FROM t2;
+DELETE FROM t3;
+DELETE FROM t4;
+# restart
+DELETE FROM t1;
+DELETE FROM t4;
+DELETE FROM t1;
+DELETE FROM t4;
+# restart
+CREATE TABLE t5 (
+a int not null,
+b int not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t5;
+set @@global.rocksdb_compact_cf = 'cf1';
+set @@global.rocksdb_compact_cf = 'rev:cf2';
+set @@global.rocksdb_compact_cf = 'default';
+drop table t1;
+drop table t2;
+drop table t3;
+drop table t4;
+drop table t5;
+set @@global.rocksdb_compact_cf = 'cf1';
+set @@global.rocksdb_compact_cf = 'rev:cf2';
+set @@global.rocksdb_compact_cf = 'default';
+Compacted
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/drop_table3.result b/storage/rocksdb/mysql-test/rocksdb/r/drop_table3.result
new file mode 100644
index 00000000..954e6079
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/drop_table3.result
@@ -0,0 +1,25 @@
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+DROP TABLE IF EXISTS t1;
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+# restart
+CREATE TABLE t1 (
+a int not null,
+b int not null,
+c varchar(500) not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+drop table t1;
+select case when variable_value-@a < 500000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+case when variable_value-@a < 500000 then 'true' else 'false' end
+true
+DROP TABLE IF EXISTS t1;
+Warnings:
+Note 1051 Unknown table 'test.t1'
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/dup_key_update.result b/storage/rocksdb/mysql-test/rocksdb/r/dup_key_update.result
new file mode 100644
index 00000000..b4cebb08
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/dup_key_update.result
@@ -0,0 +1,366 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t1 (id1 INT, id2 INT, id3 INT,
+PRIMARY KEY (id1, id2, id3),
+UNIQUE KEY (id3, id1)) ENGINE=ROCKSDB;
+CREATE TABLE t2 (id1 INT, id2 INT, id3 INT,
+PRIMARY KEY (id1, id2, id3),
+UNIQUE KEY (id3, id1) COMMENT 'rev:cf') ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 9;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 1 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 1 1
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 10;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 10 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 10 1
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 11;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 11 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 11 1
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 12;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 12 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 12 5
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 13;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 13 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 13 5
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 14;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 14 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 14 5
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 15;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 15 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 15 9
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 16;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 16 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 16 9
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 17;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t1;
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+SELECT * FROM t1 FORCE INDEX (id3);
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 9;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 1 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 1 1
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 10;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 10 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 10 1
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 11;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 11 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 11 1
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 12;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 12 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 12 5
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 13;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 13 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 13 5
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 14;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 14 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 14 5
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 15;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 15 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 15 9
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 16;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 16 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 16 9
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 17;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t2;
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+SELECT * FROM t2 FORCE INDEX (id3);
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+DROP TABLE t1;
+DROP TABLE t2;
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (id1 varchar(128) CHARACTER SET latin1 COLLATE latin1_bin,
+id2 varchar(256) CHARACTER SET utf8 COLLATE utf8_bin,
+id3 varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+PRIMARY KEY (id1, id2, id3),
+UNIQUE KEY (id3, id1)) ENGINE=ROCKSDB;
+set global rocksdb_large_prefix=DEFAULT;
+set global rocksdb_large_prefix=1;
+CREATE TABLE t2 (id1 varchar(128) CHARACTER SET latin1 COLLATE latin1_bin,
+id2 varchar(256) CHARACTER SET utf8 COLLATE utf8_bin,
+id3 varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+PRIMARY KEY (id1, id2, id3),
+UNIQUE KEY (id3, id1) COMMENT 'rev:cf') ENGINE=ROCKSDB;
+set global rocksdb_large_prefix=DEFAULT;
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 9;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 1 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 1 1
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 10;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 10 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 10 1
+INSERT INTO t1 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 11;
+SELECT * FROM t1 WHERE id1 = 1;
+id1 id2 id3
+1 11 1
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 11 1
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 12;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 12 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 12 5
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 13;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 13 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 13 5
+INSERT INTO t1 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 14;
+SELECT * FROM t1 WHERE id1 = 5;
+id1 id2 id3
+5 14 5
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 14 5
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 15;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 15 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 15 9
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 16;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 16 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 16 9
+INSERT INTO t1 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 17;
+SELECT * FROM t1 WHERE id1 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t1 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t1;
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+SELECT * FROM t1 FORCE INDEX (id3);
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 9;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 1 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 1 1
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 10;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 10 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 10 1
+INSERT INTO t2 VALUES (1, 1, 1) ON DUPLICATE KEY UPDATE id2 = 11;
+SELECT * FROM t2 WHERE id1 = 1;
+id1 id2 id3
+1 11 1
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 1;
+id1 id2 id3
+1 11 1
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 12;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 12 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 12 5
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 13;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 13 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 13 5
+INSERT INTO t2 VALUES (5, 5, 5) ON DUPLICATE KEY UPDATE id2 = 14;
+SELECT * FROM t2 WHERE id1 = 5;
+id1 id2 id3
+5 14 5
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 5;
+id1 id2 id3
+5 14 5
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 15;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 15 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 15 9
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 16;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 16 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 16 9
+INSERT INTO t2 VALUES (9, 9, 9) ON DUPLICATE KEY UPDATE id2 = 17;
+SELECT * FROM t2 WHERE id1 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t2 FORCE INDEX (id3) WHERE id3 = 9;
+id1 id2 id3
+9 17 9
+SELECT * FROM t2;
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+SELECT * FROM t2 FORCE INDEX (id3);
+id1 id2 id3
+1 11 1
+2 2 2
+3 3 3
+4 4 4
+5 14 5
+6 6 6
+7 7 7
+8 8 8
+9 17 9
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/duplicate_table.result b/storage/rocksdb/mysql-test/rocksdb/r/duplicate_table.result
new file mode 100644
index 00000000..ba16aaa6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/duplicate_table.result
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS t;
+CREATE TABLE t(id int primary key) engine=rocksdb;
+INSERT INTO t values (1), (2), (3);
+CREATE TABLE t(id int primary key) engine=rocksdb;
+ERROR 42S01: Table 't' already exists
+FLUSH TABLES;
+CREATE TABLE t(id int primary key) engine=rocksdb;
+ERROR HY000: Table 'test.t' does not exist, but metadata information exists inside MyRocks. This is a sign of data inconsistency. Please check if './test/t.frm' exists, and try to restore it if it does not exist.
+FLUSH TABLES;
+SELECT * FROM t;
+id
+1
+2
+3
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/explicit_snapshot.result b/storage/rocksdb/mysql-test/rocksdb/r/explicit_snapshot.result
new file mode 100644
index 00000000..14f5ef65
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/explicit_snapshot.result
@@ -0,0 +1,265 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE T1 (a INT PRIMARY KEY AUTO_INCREMENT) ENGINE=ROCKSDB;
+INSERT INTO T1 VALUES();
+"con1: Creating explict snapshot"
+SELECT * FROM T1;
+a
+1
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+"con2: Attaching snapshot id 1"
+ATTACH EXPLICIT ROCKSDB SNAPSHOT 1;
+"con2: New row should not be visible"
+SELECT * FROM T1;
+a
+1
+"con2: Releasing snapshot"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con2: New row should be visible"
+SELECT * FROM T1;
+a
+1
+2
+"con1: New row should not be visible"
+SELECT * FROM T1;
+a
+1
+"con1: Releasing snapshot"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: New row should be visible"
+SELECT * FROM T1;
+a
+1
+2
+"con1: Starting shared snapshot"
+SELECT * FROM T1;
+a
+1
+2
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+3
+"con2: Starting existing snapshot"
+START TRANSACTION WITH EXISTING ROCKSDB SNAPSHOT 2;
+"con2: New row should not be visible"
+SELECT * FROM T1;
+a
+1
+2
+COMMIT;
+"con2: New row should be visible"
+SELECT * FROM T1;
+a
+1
+2
+3
+COMMIT;
+"con1: New row should be visible"
+SELECT * FROM T1;
+a
+1
+2
+3
+"con1: Creating explict snapshot"
+"con2: Trying to insert row"
+INSERT INTO T1 VALUES();
+ERROR HY000: Can't execute updates when an explicit snapshot is associated with the connection using CREATE|ATTACH EXPLICIT [ENGINE] SNAPSHOT
+"con2: Attaching existing snapshot"
+ATTACH EXPLICIT ROCKSDB SNAPSHOT 3;
+"con2: Trying to insert row"
+INSERT INTO T1 VALUES();
+ERROR HY000: Can't execute updates when an explicit snapshot is associated with the connection using CREATE|ATTACH EXPLICIT [ENGINE] SNAPSHOT
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: Starting shared snapshot"
+"con1: Trying to insert row"
+INSERT INTO T1 VALUES();
+ERROR HY000: Can't execute updates when you started a transaction with START TRANSACTION WITH CONSISTENT|SHARED|EXISTING [ROCKSDB] SNAPSHOT.
+"con2: Starting existing snapshot"
+START TRANSACTION WITH EXISTING ROCKSDB SNAPSHOT 4;
+"con2: Trying to insert row"
+INSERT INTO T1 VALUES();
+ERROR HY000: Can't execute updates when you started a transaction with START TRANSACTION WITH CONSISTENT|SHARED|EXISTING [ROCKSDB] SNAPSHOT.
+COMMIT;
+COMMIT;
+"con1: Creating explicit snapshot"
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+"con1: New row should not be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+"con1: Creating another explicit snapshot"
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: Now the new row should be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+"con1: Starting transaction with consistent snapshot"
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+"con1: The new row should not be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+"con1: Creating another explicit snapshot"
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: The new row should still not be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+"con1: Committing trx"
+COMMIT;
+"con1: The new row should now be seen because of the new explicit snapshot created above"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+"con1: Releasing explicit snapshot"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: Starting transaction with shared snapshot"
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+"con1: The new row should not be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+"con1: Starting another transaction with shared snapshot"
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+"con1: The new row should now be seen"
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+COMMIT;
+"con1: Creating explicit snapshot"
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+"con1: Releasing explicit snapshot"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: Releasing explicit snapshot again"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+ERROR HY000: Cannot process explicit snapshot
+"con1: Starting transaction with shared snapshot"
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+"con2: Inserting a row"
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+7
+"con1: Creating explicit snapshot"
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+SELECT * FROM T1;
+a
+1
+2
+3
+4
+5
+6
+"con1: Releasing explicit snapshot"
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+"con1: The new row should not be seen"
+SELECT* FROM T1;
+a
+1
+2
+3
+4
+5
+6
+COMMIT;
+DROP TABLE T1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/fail_system_cf.result b/storage/rocksdb/mysql-test/rocksdb/r/fail_system_cf.result
new file mode 100644
index 00000000..df90f2b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/fail_system_cf.result
@@ -0,0 +1,4 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT '__system__') ENGINE = ROCKSDB;
+ERROR HY000: Incorrect arguments to column family not valid for storing index data.
+DROP TABLE IF EXISTS t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/fast_prefix_index_fetch.result b/storage/rocksdb/mysql-test/rocksdb/r/fast_prefix_index_fetch.result
new file mode 100644
index 00000000..963f9706
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/fast_prefix_index_fetch.result
@@ -0,0 +1,80 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+id INT,
+fake_id INT,
+bigfield VARCHAR(4096),
+PRIMARY KEY (id),
+KEY bf (bigfield(32)),
+KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+(8, 1008, REPEAT('b', 8)),
+(24, 1024, REPEAT('c', 24)),
+(31, 1031, REPEAT('d', 31)),
+(32, 1032, REPEAT('x', 32)),
+(33, 1033, REPEAT('y', 33)),
+(128, 1128, REPEAT('z', 128));
+SELECT * FROM t1;
+id fake_id bigfield
+1 1001 a
+8 1008 bbbbbbbb
+24 1024 cccccccccccccccccccccccc
+31 1031 ddddddddddddddddddddddddddddddd
+32 1032 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+33 1033 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+128 1128 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+# Baseline sanity check
+no-op query
+no-op query
+include/assert.inc [Baseline sanity check: 0 rocksdb_covered_secondary_key_lookups]
+# Eligible for optimization.
+id bigfield
+31 ddddddddddddddddddddddddddddddd
+include/assert.inc [Eligible for optimization.: 2 rocksdb_covered_secondary_key_lookups]
+# Eligible for optimization, access via fake_id only
+id bigfield
+31 ddddddddddddddddddddddddddddddd
+include/assert.inc [Eligible for optimization, access via fake_id only: 2 rocksdb_covered_secondary_key_lookups]
+# Not eligible for optimization, access via fake_id of big row.
+id bigfield
+33 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+include/assert.inc [Not eligible for optimization, access via fake_id of big row.: 0 rocksdb_covered_secondary_key_lookups]
+# Eligible for optimization.
+id bigfield
+32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+include/assert.inc [Eligible for optimization.: 1 rocksdb_covered_secondary_key_lookups]
+# Not eligible for optimization.
+id bigfield
+33 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+include/assert.inc [Not eligible for optimization.: 0 rocksdb_covered_secondary_key_lookups]
+# Eligible for optimization.
+id bigfield
+8 bbbbbbbb
+include/assert.inc [Eligible for optimization.: 2 rocksdb_covered_secondary_key_lookups]
+# Eligible for optimization.
+id bigfield
+24 cccccccccccccccccccccccc
+include/assert.inc [Eligible for optimization.: 2 rocksdb_covered_secondary_key_lookups]
+# Not eligible for optimization.
+id bigfield
+128 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+include/assert.inc [Not eligible for optimization.: 0 rocksdb_covered_secondary_key_lookups]
+#
+# Test that multi-byte charsets are handled correctly
+#
+# Charset record obviously shorter than the prefix
+a b
+1 a
+include/assert.inc [Charset record obviously shorter than the prefix: 2 rocksdb_covered_secondary_key_lookups]
+# Charset record shorter than prefix
+a b
+2 cc
+include/assert.inc [Charset record shorter than prefix: 2 rocksdb_covered_secondary_key_lookups]
+# Charset record with glyphs shorter than prefix
+a b
+3 ŽŽ
+include/assert.inc [Charset record with glyphs shorter than prefix: 1 rocksdb_covered_secondary_key_lookups]
+# Charset record longer than prefix
+a b
+4 žžžž
+include/assert.inc [Charset record longer than prefix: 0 rocksdb_covered_secondary_key_lookups]
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/force_shutdown.result b/storage/rocksdb/mysql-test/rocksdb/r/force_shutdown.result
new file mode 100644
index 00000000..4386ad59
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/force_shutdown.result
@@ -0,0 +1,38 @@
+create table t1 (
+pk int not null primary key,
+col1 varchar(10)
+) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3);
+set session debug= "+d,myrocks_busy_loop_on_row_read";
+select * from t1 where pk=1;
+# testing unclean shutdown on stuck instance
+# Run shutdown sql command with forcing kill (exit code 127)
+shutdown 1;
+Got one of the listed errors
+# verifying exit code is printed
+# restart the server
+shutdown 230;
+Got one of the listed errors
+# restart the server
+# verifying SHUTDOWN is refused if exit code > 255
+SHUTDOWN 256;
+ERROR HY000: exit code must be 0..255
+SHUTDOWN 10000;
+ERROR HY000: exit code must be 0..255
+# verifying SHUTDOWN is refused if instances are not read only
+SHUTDOWN 0 read_only;
+ERROR HY000: Only read_only instance can be killed.
+SHUTDOWN 127 read_only;
+ERROR HY000: Only read_only instance can be killed.
+SHUTDOWN 127;
+Got one of the listed errors
+# restart the server
+set session debug= "+d,myrocks_busy_loop_on_row_read";
+select * from t1 where pk=1;
+SET GLOBAL read_only=1;
+# verifying SHUTDOWN read_only works with read_only instance
+# Run shutdown sql command with forcing kill (exit code 127)
+shutdown 255 read_only;
+Got one of the listed errors
+# restart the server
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/foreign_key.result b/storage/rocksdb/mysql-test/rocksdb/r/foreign_key.result
new file mode 100644
index 00000000..fa3809e9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/foreign_key.result
@@ -0,0 +1,25 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (b INT PRIMARY KEY);
+CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, FOREIGN KEY (b) REFERENCES t1(b));
+ERROR 42000: This version of MariaDB doesn't yet support 'FOREIGN KEY for the RocksDB storage engine'
+CREATE TABLE t2 (a INT NOT NULL, bforeign INT NOT NULL);
+DROP TABLE t2;
+CREATE TABLE t2 (a INT NOT NULL, foreignkey INT NOT NULL);
+DROP TABLE t2;
+CREATE TABLE t2 (a INT NOT NULL, bforeign INT not null, FOREIGN KEY (bforeign) REFERENCES t1(b));
+ERROR 42000: This version of MariaDB doesn't yet support 'FOREIGN KEY for the RocksDB storage engine'
+CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL);
+ALTER TABLE t2 ADD FOREIGN KEY (b) REFERENCES t1(b);
+ERROR 42000: This version of MariaDB doesn't yet support 'FOREIGN KEY for the RocksDB storage engine'
+DROP TABLE t2;
+CREATE TABLE t2 (a INT NOT NULL);
+ALTER TABLE t2 ADD bforeign INT NOT NULL;
+DROP TABLE t2;
+CREATE TABLE t2 (a INT NOT NULL);
+ALTER TABLE t2 ADD foreignkey INT NOT NULL;
+DROP TABLE t2;
+CREATE TABLE t2 (a INT NOT NULL);
+ALTER TABLE t2 ADD bforeign INT NOT NULL, ADD FOREIGN KEY (bforeign) REFERENCES t1(b);
+ERROR 42000: This version of MariaDB doesn't yet support 'FOREIGN KEY for the RocksDB storage engine'
+DROP TABLE t2;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_issue254.result b/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_issue254.result
new file mode 100644
index 00000000..d4204118
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_issue254.result
@@ -0,0 +1,9 @@
+create table t (id int primary key, value int);
+begin;
+update t set value=100 where id in (1, 2);
+commit;
+begin;
+select * from t for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from t for update
+commit;
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_raise_error.result b/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_raise_error.result
new file mode 100644
index 00000000..c1cf1e77
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/gap_lock_raise_error.result
@@ -0,0 +1,504 @@
+drop table if exists gap1,gap2,gap3;
+CREATE DATABASE mysqlslap;
+CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
+PRIMARY KEY (id1, id2, id3),
+INDEX i (c1)) ENGINE=rocksdb;
+CREATE TABLE gap2 like gap1;
+CREATE TABLE gap3 (id INT, value INT,
+PRIMARY KEY (id),
+UNIQUE KEY ui(value)) ENGINE=rocksdb;
+insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
+create table gap4 (
+pk int primary key,
+a int,
+b int,
+key(a)
+) ENGINE=rocksdb;
+insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+create table gap5 like gap4;
+insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+set session gap_lock_raise_error=1;
+set session gap_lock_write_log=1;
+set @save_gap_lock_write_log = @@gap_lock_write_log;
+set @save_gap_lock_raise_error = @@gap_lock_raise_error;
+set gap_lock_write_log = 1;
+set gap_lock_raise_error = 0;
+begin;
+update gap4 set a= (select 1+max(a) from gap5 where gap5.pk between 1 and 3 and gap5.b=gap4.b);
+1
+update gap4 set a= (select 2+max(a) from gap5 where gap5.pk between 1 and 3 and gap5.b=gap4.b);
+update gap4 set a= (select 3+max(a) from gap5 where gap5.pk between 1 and 3 and gap5.b=gap4.b);
+1
+1
+0
+flush logs;
+0
+rollback;
+set gap_lock_write_log = @save_gap_lock_write_log;
+set gap_lock_raise_error = @save_gap_lock_raise_error;
+set global gap_lock_write_log = 1;
+set global gap_lock_write_log = 0;
+1000
+set session autocommit=0;
+select * from gap1 limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 limit 1 for update
+select * from gap1 where value != 100 limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where value != 100 limit 1 for update
+select * from gap1 where id1=1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 for update
+select * from gap1 where id1=1 and id2= 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 for update
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 and id3 != 1 for update
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 for update
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 for update
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 for update
+select * from gap1 order by id1 asc limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 asc limit 1 for update
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 for update
+select * from gap1 order by id1 desc limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 desc limit 1 for update
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 for update
+select * from gap1 force index(i) where c1=1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 force index(i) where c1=1 for update
+select * from gap3 force index(ui) where value=1 for update;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 for update;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 for update;
+id value
+1 1
+set session autocommit=1;
+select * from gap1 limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where value != 100 limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where id1=1 for update;
+id1 id2 id3 c1 value
+1 0 2 2 2
+1 0 3 3 3
+select * from gap1 where id1=1 and id2= 1 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 for update;
+id1 id2 id3 c1 value
+select * from gap1 order by id1 asc limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 desc limit 1 for update;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 for update;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 force index(i) where c1=1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap3 force index(ui) where value=1 for update;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) for update;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 for update;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 for update;
+id value
+1 1
+set session autocommit=0;
+select * from gap1 limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 limit 1 lock in share mode
+select * from gap1 where value != 100 limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where value != 100 limit 1 lock in share mode
+select * from gap1 where id1=1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 lock in share mode
+select * from gap1 where id1=1 and id2= 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 lock in share mode
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 and id3 != 1 lock in share mode
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 lock in share mode
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 lock in share mode
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 lock in share mode
+select * from gap1 order by id1 asc limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 asc limit 1 lock in share mode
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 lock in share mode
+select * from gap1 order by id1 desc limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 desc limit 1 lock in share mode
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 lock in share mode
+select * from gap1 force index(i) where c1=1 lock in share mode;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 force index(i) where c1=1 lock in share mode
+select * from gap3 force index(ui) where value=1 lock in share mode;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 lock in share mode;
+id value
+1 1
+set session autocommit=1;
+select * from gap1 limit 1 lock in share mode;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where value != 100 limit 1 lock in share mode;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where id1=1 lock in share mode;
+id1 id2 id3 c1 value
+1 0 2 2 2
+1 0 3 3 3
+select * from gap1 where id1=1 and id2= 1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 order by id1 asc limit 1 lock in share mode;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 lock in share mode;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 desc limit 1 lock in share mode;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 lock in share mode;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 force index(i) where c1=1 lock in share mode;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap3 force index(ui) where value=1 lock in share mode;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) lock in share mode;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 lock in share mode;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 lock in share mode;
+id value
+1 1
+set session autocommit=0;
+select * from gap1 limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where value != 100 limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where id1=1 ;
+id1 id2 id3 c1 value
+1 0 2 2 2
+1 0 3 3 3
+select * from gap1 where id1=1 and id2= 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 ;
+id1 id2 id3 c1 value
+select * from gap1 order by id1 asc limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 desc limit 1 ;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 ;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 force index(i) where c1=1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap3 force index(ui) where value=1 ;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 ;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 ;
+id value
+1 1
+set session autocommit=1;
+select * from gap1 limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where value != 100 limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where id1=1 ;
+id1 id2 id3 c1 value
+1 0 2 2 2
+1 0 3 3 3
+select * from gap1 where id1=1 and id2= 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3 != 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 and id3
+between 1 and 3 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 asc
+limit 1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2= 1 order by id3 desc
+limit 1 ;
+id1 id2 id3 c1 value
+select * from gap1 order by id1 asc limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 asc, id2 asc, id3 asc limit 1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 order by id1 desc limit 1 ;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 order by id1 desc, id2 desc, id3 desc
+limit 1 ;
+id1 id2 id3 c1 value
+500 100 1000 1000 1000
+select * from gap1 force index(i) where c1=1 ;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap3 force index(ui) where value=1 ;
+id value
+1 1
+select * from gap1 where id1=1 and id2=1 and id3=1 ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3 in (1, 2, 3) ;
+id1 id2 id3 c1 value
+select * from gap1 where id1=1 and id2=1 and id3=1 and value=1
+order by c1 ;
+id1 id2 id3 c1 value
+select * from gap3 where id=1 ;
+id value
+1 1
+set session autocommit=0;
+insert into gap1 (id1, id2, id3) values (-1,-1,-1);
+insert into gap1 (id1, id2, id3) values (-1,-1,-1)
+on duplicate key update value=100;
+update gap1 set value=100 where id1=1;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: update gap1 set value=100 where id1=1
+update gap1 set value=100 where id1=1 and id2=1 and id3=1;
+delete from gap1 where id1=2;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: delete from gap1 where id1=2
+delete from gap1 where id1=-1 and id2=-1 and id3=-1;
+commit;
+set session autocommit=1;
+insert into gap1 (id1, id2, id3) values (-1,-1,-1);
+insert into gap1 (id1, id2, id3) values (-1,-1,-1)
+on duplicate key update value=100;
+update gap1 set value=100 where id1=1;
+update gap1 set value=100 where id1=1 and id2=1 and id3=1;
+delete from gap1 where id1=2;
+delete from gap1 where id1=-1 and id2=-1 and id3=-1;
+commit;
+set session autocommit=1;
+insert into gap2 select * from gap1;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: insert into gap2 select * from gap1
+insert into gap2 select * from gap1 where id1=1;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: insert into gap2 select * from gap1 where id1=1
+insert into gap2 select * from gap1 where id1=1 and id2=1 and id3=1;
+create table t4 select * from gap1 where id1=1 and id2=1 and id3=1;
+drop table t4;
+create table t4 select * from gap1;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: create table t4 select * from gap1
+create table t4 select * from gap1 where id1=1;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: create table t4 select * from gap1 where id1=1
+update gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 set gap1.value=100 where gap2.id1=3
+and gap2.id2=3 and gap2.id3=3;
+update gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 set gap1.value=100 where gap2.id1=3;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: update gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 set gap1.value=100 where gap2.id1=3
+update gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 join gap3 on gap1.id1=gap3.id
+set gap1.value=100 where gap2.id1=3;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: update gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 join gap3 on gap1.id1=gap3.id
+set gap1.value=100 where gap2.id1=3
+update gap1 set gap1.value= (select count(*) from gap2);
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: update gap1 set gap1.value= (select count(*) from gap2)
+delete gap1 from gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 where gap2.id1=3
+and gap2.id2=3 and gap2.id3=3;
+delete gap1 from gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 where gap2.id1=3;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: delete gap1 from gap1 join gap2 on gap1.id1 and gap1.id2=gap2.id2 where gap2.id1=3
+select * from gap1, gap2 limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1, gap2 limit 1 for update
+select * from gap1 a, gap1 b limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 a, gap1 b limit 1 for update
+create table u1(
+c1 int,
+c2 int,
+c3 int,
+c4 int,
+primary key (c1, c2, c3),
+unique key (c3, c1)
+);
+set session gap_lock_raise_error=1;
+begin;
+insert into u1 values (1,1,1,1);
+commit;
+begin;
+insert into u1 values (1,2,1,1) on duplicate key update c4=10;
+commit;
+begin;
+select * from u1 where c3=1 and c1 = 1 for update;
+c1 c2 c3 c4
+1 1 1 10
+select * from u1 where c3=1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from u1 where c3=1 for update
+commit;
+drop table u1;
+set global gap_lock_write_log= 0;
+set global gap_lock_raise_error= 0;
+drop table if exists gap1, gap2, gap3, gap4, gap5;
+DROP DATABASE mysqlslap;
+0
+SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
+SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
+flush general logs;
+SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;
+SET GLOBAL gap_lock_exceptions="t.*";
+drop table if exists gap1,gap2,gap3;
+CREATE DATABASE mysqlslap;
+CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
+PRIMARY KEY (id1, id2, id3),
+INDEX i (c1)) ENGINE=rocksdb;
+CREATE TABLE gap2 like gap1;
+CREATE TABLE gap3 (id INT, value INT,
+PRIMARY KEY (id),
+UNIQUE KEY ui(value)) ENGINE=rocksdb;
+insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
+create table gap4 (
+pk int primary key,
+a int,
+b int,
+key(a)
+) ENGINE=rocksdb;
+insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+create table gap5 like gap4;
+insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+set session gap_lock_raise_error=1;
+set session gap_lock_write_log=1;
+set session autocommit=0;
+select * from gap1 limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 limit 1 for update
+select * from gap1 where value != 100 limit 1 for update;
+ERROR HY000: Using Gap Lock without full unique key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all unique key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where value != 100 limit 1 for update
+set global gap_lock_write_log= 0;
+set global gap_lock_raise_error= 0;
+drop table if exists gap1, gap2, gap3, gap4, gap5;
+DROP DATABASE mysqlslap;
+0
+SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
+SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
+flush general logs;
+SET GLOBAL gap_lock_exceptions="gap.*";
+drop table if exists gap1,gap2,gap3;
+CREATE DATABASE mysqlslap;
+CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
+PRIMARY KEY (id1, id2, id3),
+INDEX i (c1)) ENGINE=rocksdb;
+CREATE TABLE gap2 like gap1;
+CREATE TABLE gap3 (id INT, value INT,
+PRIMARY KEY (id),
+UNIQUE KEY ui(value)) ENGINE=rocksdb;
+insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
+create table gap4 (
+pk int primary key,
+a int,
+b int,
+key(a)
+) ENGINE=rocksdb;
+insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+create table gap5 like gap4;
+insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
+set session gap_lock_raise_error=1;
+set session gap_lock_write_log=1;
+set session autocommit=0;
+select * from gap1 limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+select * from gap1 where value != 100 limit 1 for update;
+id1 id2 id3 c1 value
+0 0 1 1 1
+set global gap_lock_write_log= 0;
+set global gap_lock_raise_error= 0;
+drop table if exists gap1, gap2, gap3, gap4, gap5;
+DROP DATABASE mysqlslap;
+0
+SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
+SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
+flush general logs;
+SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/get_error_message.result b/storage/rocksdb/mysql-test/rocksdb/r/get_error_message.result
new file mode 100644
index 00000000..04dcac1f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/get_error_message.result
@@ -0,0 +1,8 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1(a INT,b INT,KEY (a)) PARTITION BY HASH (a) PARTITIONS 3;
+SHOW TABLES;
+Tables_in_test
+t1
+ALTER TABLE t1 ADD PARTITION(PARTITION p3 DATA DIRECTORY='G:/mysqltest/p3Data' INDEX DIRECTORY='H:/mysqltest/p3Index');
+ERROR 42000: Incorrect table name 'H:/mysqltest/p3Index'
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/group_min_max.result b/storage/rocksdb/mysql-test/rocksdb/r/group_min_max.result
new file mode 100644
index 00000000..b8cb4157
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/group_min_max.result
@@ -0,0 +1,3526 @@
+set @debug_tmp= @@debug_dbug;
+set global debug_dbug="+d,force_group_by";
+drop table if exists t1;
+create table t1 (
+a1 char(64), a2 char(64), b char(16), c char(16) not null, d char(16), dummy char(248) default ' '
+) engine=RocksDB;
+insert into t1 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'),
+('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'),
+('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'),
+('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'),
+('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'),
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'),
+('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'),
+('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'),
+('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'),
+('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4');
+create index idx_t1_0 on t1 (a1);
+create index idx_t1_1 on t1 (a1,a2,b,c);
+create index idx_t1_2 on t1 (a1,a2,b);
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+drop table if exists t2;
+create table t2 (
+a1 char(64), a2 char(64) not null, b char(16), c char(16), d char(16), dummy char(248) default ' '
+) engine=RocksDB;
+insert into t2 select * from t1;
+insert into t2 (a1, a2, b, c, d) values
+('a','a',NULL,'a777','xyz'),('a','a',NULL,'a888','xyz'),('a','a',NULL,'a999','xyz'),
+('a','a','a',NULL,'xyz'),
+('a','a','b',NULL,'xyz'),
+('a','b','a',NULL,'xyz'),
+('c','a',NULL,'c777','xyz'),('c','a',NULL,'c888','xyz'),('c','a',NULL,'c999','xyz'),
+('d','b','b',NULL,'xyz'),
+('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),
+('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),
+('a','a',NULL,'a777','xyz'),('a','a',NULL,'a888','xyz'),('a','a',NULL,'a999','xyz'),
+('a','a','a',NULL,'xyz'),
+('a','a','b',NULL,'xyz'),
+('a','b','a',NULL,'xyz'),
+('c','a',NULL,'c777','xyz'),('c','a',NULL,'c888','xyz'),('c','a',NULL,'c999','xyz'),
+('d','b','b',NULL,'xyz'),
+('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),('e','a','a',NULL,'xyz'),
+('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz'),('e','a','b',NULL,'xyz');
+create index idx_t2_0 on t2 (a1);
+create index idx_t2_1 on t2 (a1,a2,b,c);
+create index idx_t2_2 on t2 (a1,a2,b);
+analyze table t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+drop table if exists t3;
+create table t3 (
+a1 char(1), a2 char(1), b char(1), c char(4) not null, d char(3), dummy char(1) default ' '
+) engine=RocksDB;
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+insert into t3 (a1, a2, b, c, d) values
+('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'),
+('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'),
+('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'),
+('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'),
+('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'),
+('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'),
+('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'),
+('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'),
+('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'),
+('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'),
+('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'),
+('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4');
+create index idx_t3_0 on t3 (a1);
+create index idx_t3_1 on t3 (a1,a2,b,c);
+create index idx_t3_2 on t3 (a1,a2,b);
+analyze table t3;
+Table Op Msg_type Msg_text
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+explain select a1, min(a2) from t1 group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 130 NULL 5 Using index for group-by
+explain select a1, max(a2) from t1 group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 65 NULL 5 Using index for group-by
+explain select a1, min(a2), max(a2) from t1 group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 130 NULL 5 Using index for group-by
+explain select a1, a2, b, min(c), max(c) from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using index for group-by
+explain select a1,a2,b,max(c),min(c) from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using index for group-by
+explain select a1,a2,b,max(c),min(c) from t2 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 # NULL # Using index for group-by
+explain select min(a2), a1, max(a2), min(a2), a1 from t1 group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 130 NULL 5 Using index for group-by
+explain select a1, b, min(c), a1, max(c), b, a2, max(c), max(c) from t1 group by a1, a2, b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using index for group-by
+explain select min(a2) from t1 group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 130 NULL 5 Using index for group-by
+explain select a2, min(c), max(c) from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using index for group-by
+select a1, min(a2) from t1 group by a1;
+a1 min(a2)
+a a
+b a
+c a
+d a
+select a1, max(a2) from t1 group by a1;
+a1 max(a2)
+a b
+b b
+c b
+d b
+select a1, min(a2), max(a2) from t1 group by a1;
+a1 min(a2) max(a2)
+a a b
+b a b
+c a b
+d a b
+select a1, a2, b, min(c), max(c) from t1 group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b,max(c),min(c) from t1 group by a1,a2,b;
+a1 a2 b max(c) min(c)
+a a a d111 a111
+a a b h112 e112
+a b a l121 i121
+a b b p122 m122
+b a a d211 a211
+b a b h212 e212
+b b a l221 i221
+b b b p222 m222
+c a a d311 a311
+c a b h312 e312
+c b a l321 i321
+c b b p322 m322
+d a a d411 a411
+d a b h412 e412
+d b a l421 i421
+d b b p422 m422
+select a1,a2,b,max(c),min(c) from t2 group by a1,a2,b;
+a1 a2 b max(c) min(c)
+a a NULL a999 a777
+a a a d111 a111
+a a b h112 e112
+a b a l121 i121
+a b b p122 m122
+b a a d211 a211
+b a b h212 e212
+b b a l221 i221
+b b b p222 m222
+c a NULL c999 c777
+c a a d311 a311
+c a b h312 e312
+c b a l321 i321
+c b b p322 m322
+d a a d411 a411
+d a b h412 e412
+d b a l421 i421
+d b b p422 m422
+e a a NULL NULL
+e a b NULL NULL
+select min(a2), a1, max(a2), min(a2), a1 from t1 group by a1;
+min(a2) a1 max(a2) min(a2) a1
+a a b a a
+a b b a b
+a c b a c
+a d b a d
+select a1, b, min(c), a1, max(c), b, a2, max(c), max(c) from t1 group by a1, a2, b;
+a1 b min(c) a1 max(c) b a2 max(c) max(c)
+a a a111 a d111 a a d111 d111
+a b e112 a h112 b a h112 h112
+a a i121 a l121 a b l121 l121
+a b m122 a p122 b b p122 p122
+b a a211 b d211 a a d211 d211
+b b e212 b h212 b a h212 h212
+b a i221 b l221 a b l221 l221
+b b m222 b p222 b b p222 p222
+c a a311 c d311 a a d311 d311
+c b e312 c h312 b a h312 h312
+c a i321 c l321 a b l321 l321
+c b m322 c p322 b b p322 p322
+d a a411 d d411 a a d411 d411
+d b e412 d h412 b a h412 h412
+d a i421 d l421 a b l421 l421
+d b m422 d p422 b b p422 p422
+select min(a2) from t1 group by a1;
+min(a2)
+a
+a
+a
+a
+select a2, min(c), max(c) from t1 group by a1,a2,b;
+a2 min(c) max(c)
+a a111 d111
+a e112 h112
+b i121 l121
+b m122 p122
+a a211 d211
+a e212 h212
+b i221 l221
+b m222 p222
+a a311 d311
+a e312 h312
+b i321 l321
+b m322 p322
+a a411 d411
+a e412 h412
+b i421 l421
+b m422 p422
+explain select a1,a2,b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1, max(c) from t1 where a1 in ('a','b','d') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1, max(c) from t2 where a1 in ('a','b','d') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 146 NULL # Using where; Using index for group-by
+select a1,a2,b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+select a1,a2,b,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1, max(c) from t1 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+a1 max(c)
+a d111
+a h112
+a l121
+a p122
+c d311
+c h312
+c l321
+c p322
+d d411
+d h412
+d l421
+d p422
+select a1,a2,b,min(c),max(c) from t1 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 d111
+a a b e112 h112
+b a a a211 d211
+b a b e212 h212
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+a1 a2 b max(c)
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+a1 a2 b max(c)
+a b a l121
+a b b p122
+b b a l221
+b b b p222
+c b a l321
+c b b p322
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a b a i121 l121
+a b b m122 p122
+b b a i221 l221
+b b b m222 p222
+c b a i321 l321
+c b b m322 p322
+d b a i421 l421
+d b b m422 p422
+select a1,min(c),max(c) from t1 where a1 >= 'b' group by a1,a2,b;
+a1 min(c) max(c)
+b a211 d211
+b e212 h212
+b i221 l221
+b m222 p222
+c a311 d311
+c e312 h312
+c i321 l321
+c m322 p322
+d a411 d411
+d e412 h412
+d i421 l421
+d m422 p422
+select a1, max(c) from t1 where a1 in ('a','b','d') group by a1,a2,b;
+a1 max(c)
+a d111
+a h112
+a l121
+a p122
+b d211
+b h212
+b l221
+b p222
+d d411
+d h412
+d l421
+d p422
+select a1,a2,b, max(c) from t2 where a1 < 'd' group by a1,a2,b;
+a1 a2 b max(c)
+a a NULL a999
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a NULL c999
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+select a1,a2,b,min(c),max(c) from t2 where a1 < 'd' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+a a a a111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a NULL c777 c999
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+select a1,a2,b,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a NULL c777 c999
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+e a a NULL NULL
+e a b NULL NULL
+select a1,a2,b, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+a1 a2 b max(c)
+a a NULL a999
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+c a NULL c999
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+e a a NULL
+e a b NULL
+select a1, max(c) from t2 where a1 >= 'c' or a1 < 'b' group by a1,a2,b;
+a1 max(c)
+a a999
+a d111
+a h112
+a l121
+a p122
+c c999
+c d311
+c h312
+c l321
+c p322
+d d411
+d h412
+d l421
+d p422
+e NULL
+e NULL
+select a1,a2,b,min(c),max(c) from t2 where a1 >= 'c' or a2 < 'b' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+a a a a111 d111
+a a b e112 h112
+b a a a211 d211
+b a b e212 h212
+c a NULL c777 c999
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+e a a NULL NULL
+e a b NULL NULL
+select a1,a2,b, max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+a1 a2 b max(c)
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where a1 = 'z' or a1 = 'b' or a1 = 'd' group by a1,a2,b;
+a1 a2 b min(c) max(c)
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+a1 a2 b max(c)
+a b a l121
+a b b p122
+b b a l221
+b b b p222
+c b a l321
+c b b p322
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a b a i121 l121
+a b b m122 p122
+b b a i221 l221
+b b b m222 p222
+c b a i321 l321
+c b b m322 p322
+d b a i421 l421
+d b b m422 p422
+select a1,min(c),max(c) from t2 where a1 >= 'b' group by a1,a2,b;
+a1 min(c) max(c)
+b a211 d211
+b e212 h212
+b i221 l221
+b m222 p222
+c c777 c999
+c a311 d311
+c e312 h312
+c i321 l321
+c m322 p322
+d a411 d411
+d e412 h412
+d i421 l421
+d m422 p422
+e NULL NULL
+e NULL NULL
+select a1, max(c) from t2 where a1 in ('a','b','d') group by a1,a2,b;
+a1 max(c)
+a a999
+a d111
+a h112
+a l121
+a p122
+b d211
+b h212
+b l221
+b p222
+d d411
+d h412
+d l421
+d p422
+#
+# MariaDB: we dont have the following patch:
+#
+# commit 60a92a79a3b7fde3c6efe91799e344b977c8e5c3
+# Author: Manuel Ung <mung@fb.com>
+# Date: Thu Apr 19 23:06:27 2018 -0700
+#
+# Enhance group-by loose index scan
+#
+# So the following results are not very meaningful, but are still kept here
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 5 Using where; Using index for group-by
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 5 Using where; Using index for group-by
+explain select a1,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2,b, max(c) from t1 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 9 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2,b,min(c),max(c) from t1 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 9 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2, max(c) from t1 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 9 Using where; Using index for group-by
+explain select a1,a2, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 6 Using where; Using index for group-by
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 6 Using where; Using index for group-by
+explain select a1,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b, max(c) from t2 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL 10 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,min(c),max(c) from t2 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 10 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2, max(c) from t2 where (b = 'b') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL 10 Using where; Using index for group-by
+explain select a1,a2, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 range NULL idx_t3_1 6 NULL 4 Using where; Using index for group-by
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 index NULL idx_t3_1 10 NULL 192 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 index NULL idx_t3_1 10 NULL 192 Using where; Using index
+explain select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 range NULL idx_t3_1 6 NULL 4 Using where; Using index for group-by
+explain select a1,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 index NULL idx_t3_1 10 NULL 192 Using where; Using index
+explain select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 index NULL idx_t3_1 10 NULL 192 Using where; Using index
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b h112 e112
+b a b h212 e212
+c a b h312 e312
+d a b h412 e412
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b p122 e112
+b a b p222 e212
+c a b p322 e312
+d a b p422 e412
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 a2 b max(c) min(c)
+a a a h112 a111
+b a a h212 a211
+c a a h312 a311
+d a a h412 a411
+select a1,a2,b,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b' or b = 'a') group by a1;
+a1 a2 b max(c) min(c)
+a a a p122 a111
+b a a p222 a211
+c a a p322 a311
+d a a p422 a411
+select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a h112 e112
+b h212 e212
+c h312 e312
+d h412 e412
+select a1,max(c),min(c) from t1 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a p122 e112
+b p222 e212
+c p322 e312
+d p422 e412
+select a1,max(c),min(c) from t1 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 max(c) min(c)
+a h112 a111
+b h212 a211
+c h312 a311
+d h412 a411
+select a1,a2,b, max(c) from t1 where (b = 'b') group by a1,a2;
+a1 a2 b max(c)
+a a b h112
+a b b p122
+b a b h212
+b b b p222
+c a b h312
+c b b p322
+d a b h412
+d b b p422
+select a1,a2,b, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 b max(c)
+a a a h112
+a b a p122
+b a a h212
+b b a p222
+c a a h312
+c b a p322
+d a a h412
+d b a p422
+select a1,a2,b,min(c),max(c) from t1 where (b = 'b') group by a1,a2;
+a1 a2 b min(c) max(c)
+a a b e112 h112
+a b b m122 p122
+b a b e212 h212
+b b b m222 p222
+c a b e312 h312
+c b b m322 p322
+d a b e412 h412
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 b min(c) max(c)
+a a a a111 h112
+a b a i121 p122
+b a a a211 h212
+b b a i221 p222
+c a a a311 h312
+c b a i321 p322
+d a a a411 h412
+d b a i421 p422
+select a1,a2, max(c) from t1 where (b = 'b') group by a1,a2;
+a1 a2 max(c)
+a a h112
+a b p122
+b a h212
+b b p222
+c a h312
+c b p322
+d a h412
+d b p422
+select a1,a2, max(c) from t1 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 max(c)
+a a h112
+a b p122
+b a h212
+b b p222
+c a h312
+c b p322
+d a h412
+d b p422
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b h112 e112
+b a b h212 e212
+c a b h312 e312
+d a b h412 e412
+e a b NULL NULL
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b p122 e112
+b a b p222 e212
+c a b p322 e312
+d a b p422 e412
+e a b NULL NULL
+select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 a2 b max(c) min(c)
+a a a h112 a111
+b a a h212 a211
+c a a h312 a311
+d a a h412 a411
+e a a NULL NULL
+select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a h112 e112
+b h212 e212
+c h312 e312
+d h412 e412
+e NULL NULL
+select a1,max(c),min(c) from t2 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a p122 e112
+b p222 e212
+c p322 e312
+d p422 e412
+e NULL NULL
+select a1,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 max(c) min(c)
+a h112 a111
+b h212 a211
+c h312 a311
+d h412 a411
+e NULL NULL
+select a1,a2,b, max(c) from t2 where (b = 'b') group by a1,a2;
+a1 a2 b max(c)
+a a b h112
+a b b p122
+b a b h212
+b b b p222
+c a b h312
+c b b p322
+d a b h412
+d b b p422
+e a b NULL
+select a1,a2,b, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 b max(c)
+a a a h112
+a b a p122
+b a a h212
+b b a p222
+c a a h312
+c b a p322
+d a a h412
+d b a p422
+e a a NULL
+select a1,a2,b,min(c),max(c) from t2 where (b = 'b') group by a1,a2;
+a1 a2 b min(c) max(c)
+a a b e112 h112
+a b b m122 p122
+b a b e212 h212
+b b b m222 p222
+c a b e312 h312
+c b b m322 p322
+d a b e412 h412
+d b b m422 p422
+e a b NULL NULL
+select a1,a2,b,min(c),max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 b min(c) max(c)
+a a a a111 h112
+a b a i121 p122
+b a a a211 h212
+b b a i221 p222
+c a a a311 h312
+c b a i321 p322
+d a a a411 h412
+d b a i421 p422
+e a a NULL NULL
+select a1,a2, max(c) from t2 where (b = 'b') group by a1,a2;
+a1 a2 max(c)
+a a h112
+a b p122
+b a h212
+b b p222
+c a h312
+c b p322
+d a h412
+d b p422
+e a NULL
+select a1,a2, max(c) from t2 where (b = 'b' or b = 'a') group by a1,a2;
+a1 a2 max(c)
+a a h112
+a b p122
+b a h212
+b b p222
+c a h312
+c b p322
+d a h412
+d b p422
+e a NULL
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b h112 e112
+b a b h212 e212
+c a b h312 e312
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 a2 b max(c) min(c)
+a a b p122 e112
+b a b p222 e212
+c a b p322 e312
+select a1,a2,b,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 a2 b max(c) min(c)
+a a a h112 a111
+b a a h212 a211
+c a a h312 a311
+select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a h112 e112
+b h212 e212
+c h312 e312
+select a1,max(c),min(c) from t3 where (a2 = 'a' or a2 = 'b') and (b = 'b') group by a1;
+a1 max(c) min(c)
+a p122 e112
+b p222 e212
+c p322 e312
+select a1,max(c),min(c) from t3 where (a2 = 'a') and (b = 'b' or b = 'a') group by a1;
+a1 max(c) min(c)
+a h112 a111
+b h212 a211
+c h312 a311
+explain select a1,a2,b,min(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 6 Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL 6 Using where; Using index for group-by
+explain select a1,a2,b,max(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,min(c) from t2 where b is NULL group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 10 Using where; Using index for group-by
+explain select a1,a2,b,max(c) from t2 where b is NULL group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL 10 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 10 Using where; Using index for group-by
+select a1,a2,b,min(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+a1 a2 b min(c)
+a a NULL a777
+c a NULL c777
+select a1,a2,b,min(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+a1 a2 b min(c)
+a a NULL a777
+c a NULL c777
+select a1,a2,b,max(c) from t2 where (a2 = 'a') and b is NULL group by a1;
+a1 a2 b max(c)
+a a NULL a999
+c a NULL c999
+select a1,a2,b,max(c) from t2 where (a2 = 'a' or a2 = 'b') and b is NULL group by a1;
+a1 a2 b max(c)
+a a NULL a999
+c a NULL c999
+select a1,a2,b,min(c) from t2 where b is NULL group by a1,a2;
+a1 a2 b min(c)
+a a NULL a777
+c a NULL c777
+select a1,a2,b,max(c) from t2 where b is NULL group by a1,a2;
+a1 a2 b max(c)
+a a NULL a999
+c a NULL c999
+select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+c a NULL c777 c999
+select a1,a2,b,min(c),max(c) from t2 where b is NULL group by a1,a2;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+c a NULL c777 c999
+explain select a1,a2,b, max(c) from t1 where (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (c > 'f123') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'f123') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (c < 'a0') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (c < 'k321') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'k321') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (c between 'b111' and 'g112') or (c between 'd000' and 'i110') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c > 'f123') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'f123') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c < 'a0') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c < 'k321') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'k321') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+select a1,a2,b, max(c) from t1 where (c > 'b1') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a b111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a b211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where (c > 'f123') group by a1,a2,b;
+a1 a2 b max(c)
+a a b h112
+a b a l121
+a b b p122
+b a b h212
+b b a l221
+b b b p222
+c a b h312
+c b a l321
+c b b p322
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where (c > 'f123') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a b g112 h112
+a b a i121 l121
+a b b m122 p122
+b a b f212 h212
+b b a i221 l221
+b b b m222 p222
+c a b f312 h312
+c b a i321 l321
+c b b m322 p322
+d a b f412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where (c < 'a0') group by a1,a2,b;
+a1 a2 b max(c)
+select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+select a1,a2,b, max(c) from t1 where (c < 'k321') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a k121
+b a a d211
+b a b h212
+b b a k221
+c a a d311
+c a b h312
+c b a j321
+d a a d411
+d a b h412
+d b a j421
+select a1,a2,b,min(c),max(c) from t1 where (c < 'k321') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 d111
+a a b e112 h112
+a b a i121 k121
+b a a a211 d211
+b a b e212 h212
+b b a i221 k221
+c a a a311 d311
+c a b e312 h312
+c b a i321 j321
+d a a a411 d411
+d a b e412 h412
+d b a i421 j421
+select a1,a2,b, max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a b111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a b211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 g112
+b a a b211 d211
+b a b e212 f212
+c a a b311 d311
+c a b e312 f312
+d a a b411 d411
+d a b e412 f412
+select a1,a2,b,min(c),max(c) from t1 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a a111 c111
+b a a a211 c211
+c a a a311 c311
+d a a a411 c411
+d a b g412 g412
+d b a k421 k421
+select a1,a2,b,min(c),max(c) from t1 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 h112
+b a a b211 d211
+b a b e212 h212
+c a a b311 d311
+c a b e312 h312
+d a a b411 d411
+d a b e412 h412
+select a1,a2,b,min(c),max(c) from t1 where (c between 'b111' and 'g112') or (c between 'd000' and 'i110') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a b111 d111
+a a b e112 h112
+b a a b211 d211
+b a b e212 h212
+c a a b311 d311
+c a b e312 h312
+d a a b411 d411
+d a b e412 h412
+select a1,a2,b, max(c) from t2 where (c > 'b1') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a NULL c999
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a b111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a b211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a NULL c777 c999
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t2 where (c > 'f123') group by a1,a2,b;
+a1 a2 b max(c)
+a a b h112
+a b a l121
+a b b p122
+b a b h212
+b b a l221
+b b b p222
+c a b h312
+c b a l321
+c b b p322
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where (c > 'f123') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a b g112 h112
+a b a i121 l121
+a b b m122 p122
+b a b f212 h212
+b b a i221 l221
+b b b m222 p222
+c a b f312 h312
+c b a i321 l321
+c b b m322 p322
+d a b f412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t2 where (c < 'a0') group by a1,a2,b;
+a1 a2 b max(c)
+select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+select a1,a2,b, max(c) from t2 where (c < 'k321') group by a1,a2,b;
+a1 a2 b max(c)
+a a NULL a999
+a a a d111
+a a b h112
+a b a k121
+b a a d211
+b a b h212
+b b a k221
+c a NULL c999
+c a a d311
+c a b h312
+c b a j321
+d a a d411
+d a b h412
+d b a j421
+select a1,a2,b,min(c),max(c) from t2 where (c < 'k321') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+a a a a111 d111
+a a b e112 h112
+a b a i121 k121
+b a a a211 d211
+b a b e212 h212
+b b a i221 k221
+c a NULL c777 c999
+c a a a311 d311
+c a b e312 h312
+c b a i321 j321
+d a a a411 d411
+d a b e412 h412
+d b a i421 j421
+select a1,a2,b, max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+a1 a2 b max(c)
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a NULL c999
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where (c < 'a0') or (c > 'b1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a b111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a b211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a NULL c777 c999
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+a1 a2 b max(c)
+a a NULL a999
+a a a d111
+a a b h112
+a b a l121
+a b b p122
+b a a d211
+b a b h212
+b b a l221
+b b b p222
+c a NULL c999
+c a a d311
+c a b h312
+c b a l321
+c b b p322
+d a a d411
+d a b h412
+d b a l421
+d b b p422
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+a a a a111 d111
+a a b e112 h112
+a b a i121 l121
+a b b m122 p122
+b a a a211 d211
+b a b e212 h212
+b b a i221 l221
+b b b m222 p222
+c a NULL c777 c999
+c a a a311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a a411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t2 where (c > 'b111') and (c <= 'g112') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 g112
+b a a b211 d211
+b a b e212 f212
+c a NULL c777 c999
+c a a b311 d311
+c a b e312 f312
+d a a b411 d411
+d a b e412 f412
+select a1,a2,b,min(c),max(c) from t2 where (c < 'c5') or (c = 'g412') or (c = 'k421') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a NULL a777 a999
+a a a a111 c111
+b a a a211 c211
+c a a a311 c311
+d a a a411 c411
+d a b g412 g412
+d b a k421 k421
+select a1,a2,b,min(c),max(c) from t2 where ((c > 'b111') and (c <= 'g112')) or ((c > 'd000') and (c <= 'i110')) group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 h112
+b a a b211 d211
+b a b e212 h212
+c a NULL c777 c999
+c a a b311 d311
+c a b e312 h312
+d a a b411 d411
+d a b e412 h412
+explain select a1,a2,b,min(c),max(c) from t1
+where exists ( select * from t2 where t2.c = t1.c )
+group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 index NULL idx_t1_1 163 NULL 128 Using index
+1 PRIMARY <subquery2> eq_ref distinct_key distinct_key 16 func 1
+2 MATERIALIZED t2 index NULL idx_t2_1 163 NULL 164 Using index
+explain select a1,a2,b,min(c),max(c) from t1
+where exists ( select * from t2 where t2.c > 'b1' )
+group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 index NULL idx_t1_1 163 NULL 128 Using index
+2 SUBQUERY t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t1 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t1 where (ord(a1) > 97) and (ord(a2) + ord(a1) > 194) and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c),max(c) from t2 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,min(c) from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_1 163 NULL # Using where; Using index for group-by
+select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a b e112 h112
+b a b e212 h212
+c a b e312 h312
+c b b m322 p322
+d a b e412 h412
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t1 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 h112
+b a a b211 d211
+b a b e212 h212
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t1 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a b a i121 l121
+b b a i221 l221
+c b a i321 l321
+d b a i421 l421
+select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+a1 a2 b min(c)
+b b a k221
+c b a k321
+d b a k421
+select a1,a2,b,min(c) from t1 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+a1 a2 b min(c)
+b b a k221
+c b a k321
+d b a k421
+select a1,a2,b,min(c) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b min(c)
+select a1,a2,b,min(c) from t1 where (ord(a1) > 97) and (ord(a2) + ord(a1) > 194) and (b = 'c') group by a1,a2,b;
+a1 a2 b min(c)
+select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a b e112 h112
+b a b e212 h212
+c a b e312 h312
+c b b m322 p322
+d a b e412 h412
+d b b m422 p422
+e a b NULL NULL
+select a1,a2,b,min(c),max(c) from t2 where (a1 >= 'c' or a2 < 'b') and (c > 'b111') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a a a c111 d111
+a a b e112 h112
+b a a b211 d211
+b a b e212 h212
+c a NULL c777 c999
+c a a b311 d311
+c a b e312 h312
+c b a i321 l321
+c b b m322 p322
+d a a b411 d411
+d a b e412 h412
+d b a i421 l421
+d b b m422 p422
+select a1,a2,b,min(c),max(c) from t2 where (a2 >= 'b') and (b = 'a') and (c > 'b111') group by a1,a2,b;
+a1 a2 b min(c) max(c)
+a b a i121 l121
+b b a i221 l221
+c b a i321 l321
+d b a i421 l421
+select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c < 'h112') or (c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122')) group by a1,a2,b;
+a1 a2 b min(c)
+b b a k221
+c b a k321
+d b a k421
+select a1,a2,b,min(c) from t2 where ((a1 > 'a') or (a1 < '9')) and ((a2 >= 'b') and (a2 < 'z')) and (b = 'a') and ((c = 'j121') or (c > 'k121' and c < 'm122') or (c > 'o122') or (c < 'h112') or (c = 'c111')) group by a1,a2,b;
+a1 a2 b min(c)
+b b a k221
+c b a k321
+d b a k421
+select a1,a2,b,min(c) from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b min(c)
+explain select a1,a2,b from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select a1,a2,b from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using where; Using index for group-by
+explain select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 146 NULL # Using where; Using index for group-by
+select a1,a2,b from t1 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+a1 a2 b
+a a b
+b a b
+c a b
+c b b
+d a b
+d b b
+select a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b
+select a1,a2,b from t2 where (a1 >= 'c' or a2 < 'b') and (b > 'a') group by a1,a2,b;
+a1 a2 b
+a a b
+b a b
+c a b
+c b b
+d a b
+d b b
+e a b
+select a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121' or c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b
+explain select distinct a1,a2,b from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using index for group-by
+explain select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain extended select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 65 100.00 Using where; Using index for group-by
+Warnings:
+Note 1003 select distinct `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,`test`.`t1`.`b` AS `b`,`test`.`t1`.`c` AS `c` from `test`.`t1` where `test`.`t1`.`b` = 'a' and `test`.`t1`.`c` = 'i121' and `test`.`t1`.`a2` >= 'b'
+explain select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select distinct b from t1 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128 Using where; Using index
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 130 NULL 5 Using where; Using index for group-by
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 130 NULL 5 Using where; Using index for group-by
+explain select distinct a1,a2,b from t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using index for group-by
+explain select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using where; Using index for group-by
+explain extended select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL 83 100.00 Using where; Using index for group-by
+Warnings:
+Note 1003 select distinct `test`.`t2`.`a1` AS `a1`,`test`.`t2`.`a2` AS `a2`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where `test`.`t2`.`b` = 'a' and `test`.`t2`.`c` = 'i121' and `test`.`t2`.`a2` >= 'b'
+explain select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 146 NULL # Using where; Using index for group-by
+explain select distinct b from t2 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_2 146 NULL 164 Using where; Using index
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 129 NULL 6 Using where; Using index for group-by
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 129 NULL 6 Using where; Using index for group-by
+select distinct a1,a2,b from t1;
+a1 a2 b
+a a a
+a a b
+a b a
+a b b
+b a a
+b a b
+b b a
+b b b
+c a a
+c a b
+c b a
+c b b
+d a a
+d a b
+d b a
+d b b
+select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a');
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+a1 a2 b c
+a b a i121
+select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+a1 a2 b
+select distinct b from t1 where (a2 >= 'b') and (b = 'a');
+b
+a
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b';
+a1
+a
+d
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e';
+a1
+select distinct a1,a2,b from t2;
+a1 a2 b
+a a NULL
+a a a
+a a b
+a b a
+a b b
+b a a
+b a b
+b b a
+b b b
+c a NULL
+c a a
+c a b
+c b a
+c b b
+d a a
+d a b
+d b a
+d b b
+e a a
+e a b
+select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a');
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+a1 a2 b c
+a b a i121
+select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+a1 a2 b
+select distinct b from t2 where (a2 >= 'b') and (b = 'a');
+b
+a
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b';
+a1
+a
+d
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e';
+a1
+select distinct t_00.a1
+from t1 t_00
+where exists ( select * from t2 where a1 = t_00.a1 );
+a1
+a
+b
+c
+d
+select distinct a1,a1 from t1;
+a1 a1
+a a
+b b
+c c
+d d
+select distinct a2,a1,a2,a1 from t1;
+a2 a1 a2 a1
+a a a a
+b a b a
+a b a b
+b b b b
+a c a c
+b c b c
+a d a d
+b d b d
+select distinct t1.a1,t2.a1 from t1,t2;
+a1 a1
+a a
+b a
+c a
+d a
+a b
+b b
+c b
+d b
+a c
+b c
+c c
+d c
+a d
+b d
+c d
+d d
+a e
+b e
+c e
+d e
+explain select distinct a1,a2,b from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using index for group-by
+explain select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by
+explain select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select distinct b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using where; Using index for group-by; Using temporary; Using filesort
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 130 NULL 5 Using where; Using index for group-by
+explain select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 130 NULL 5 Using where; Using index for group-by
+explain select distinct a1,a2,b from t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using index for group-by
+explain select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using where; Using index for group-by
+explain select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by
+explain select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 146 NULL # Using where; Using index for group-by
+explain select distinct b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL idx_t2_2 146 NULL # Using where; Using index for group-by; Using temporary; Using filesort
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 129 NULL # Using where; Using index for group-by
+explain select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range idx_t2_0,idx_t2_1,idx_t2_2 idx_t2_2 129 NULL # Using where; Using index for group-by
+select distinct a1,a2,b from t1;
+a1 a2 b
+a a a
+a a b
+a b a
+a b b
+b a a
+b a b
+b b a
+b b b
+c a a
+c a b
+c b a
+c b b
+d a a
+d a b
+d b a
+d b b
+select distinct a1,a2,b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select distinct a1,a2,b,c from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select distinct a1,a2,b from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b
+select distinct b from t1 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+b
+a
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+a1
+a
+d
+select distinct a1 from t1 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+a1
+select distinct a1,a2,b from t2;
+a1 a2 b
+a a NULL
+a a a
+a a b
+a b a
+a b b
+b a a
+b a b
+b b a
+b b b
+c a NULL
+c a a
+c a b
+c b a
+c b b
+d a a
+d a b
+d b a
+d b b
+e a a
+e a b
+select distinct a1,a2,b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+a1 a2 b
+a b a
+b b a
+c b a
+d b a
+select distinct a1,a2,b,c from t2 where (a2 >= 'b') and (b = 'a') and (c = 'i121') group by a1,a2,b;
+a1 a2 b c
+a b a i121
+select distinct a1,a2,b from t2 where (a1 > 'a') and (a2 > 'a') and (b = 'c') group by a1,a2,b;
+a1 a2 b
+select distinct b from t2 where (a2 >= 'b') and (b = 'a') group by a1,a2,b;
+b
+a
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'b' group by a1;
+a1
+a
+d
+select distinct a1 from t2 where a1 in ('a', 'd') and a2 = 'e' group by a1;
+a1
+explain select count(distinct a1,a2,b) from t1 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 17 Using where; Using index for group-by
+explain select count(distinct a1,a2,b,c) from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_1 163 NULL 65 Using where; Using index for group-by
+explain extended select count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 100.00 Using where; Using index for group-by
+Warnings:
+Note 1003 select count(distinct `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`) AS `count(distinct a1,a2,b)` from `test`.`t1` where `test`.`t1`.`b` = 'c' and `test`.`t1`.`a1` > 'a' and `test`.`t1`.`a2` > 'a'
+explain select count(distinct b) from t1 where (a2 >= 'b') and (b = 'a');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128 Using where; Using index
+explain extended select 98 + count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a');
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 147 NULL 17 100.00 Using where; Using index for group-by
+Warnings:
+Note 1003 select 98 + count(distinct `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`) AS `98 + count(distinct a1,a2,b)` from `test`.`t1` where `test`.`t1`.`a1` > 'a' and `test`.`t1`.`a2` > 'a'
+select count(distinct a1,a2,b) from t1 where (a2 >= 'b') and (b = 'a');
+count(distinct a1,a2,b)
+4
+select count(distinct a1,a2,b,c) from t1 where (a2 >= 'b') and (b = 'a') and (c = 'i121');
+count(distinct a1,a2,b,c)
+1
+select count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a') and (b = 'c');
+count(distinct a1,a2,b)
+0
+select count(distinct b) from t1 where (a2 >= 'b') and (b = 'a');
+count(distinct b)
+1
+select 98 + count(distinct a1,a2,b) from t1 where (a1 > 'a') and (a2 > 'a');
+98 + count(distinct a1,a2,b)
+104
+explain select a1,a2,b, concat(min(c), max(c)) from t1 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select concat(a1,min(c)),b from t1 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select concat(a1,min(c)),b,max(c) from t1 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select concat(a1,a2),b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 147 NULL 17 Using where; Using index for group-by
+explain select concat(ord(min(b)),ord(max(b))),min(b),max(b) from t1 group by a1,a2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 147 NULL 9 Using index for group-by
+select a1,a2,b, concat(min(c), max(c)) from t1 where a1 < 'd' group by a1,a2,b;
+a1 a2 b concat(min(c), max(c))
+a a a a111d111
+a a b e112h112
+a b a i121l121
+a b b m122p122
+b a a a211d211
+b a b e212h212
+b b a i221l221
+b b b m222p222
+c a a a311d311
+c a b e312h312
+c b a i321l321
+c b b m322p322
+select concat(a1,min(c)),b from t1 where a1 < 'd' group by a1,a2,b;
+concat(a1,min(c)) b
+aa111 a
+ae112 b
+ai121 a
+am122 b
+ba211 a
+be212 b
+bi221 a
+bm222 b
+ca311 a
+ce312 b
+ci321 a
+cm322 b
+select concat(a1,min(c)),b,max(c) from t1 where a1 < 'd' group by a1,a2,b;
+concat(a1,min(c)) b max(c)
+aa111 a d111
+ae112 b h112
+ai121 a l121
+am122 b p122
+ba211 a d211
+be212 b h212
+bi221 a l221
+bm222 b p222
+ca311 a d311
+ce312 b h312
+ci321 a l321
+cm322 b p322
+select concat(a1,a2),b,min(c),max(c) from t1 where a1 < 'd' group by a1,a2,b;
+concat(a1,a2) b min(c) max(c)
+aa a a111 d111
+aa b e112 h112
+ab a i121 l121
+ab b m122 p122
+ba a a211 d211
+ba b e212 h212
+bb a i221 l221
+bb b m222 p222
+ca a a311 d311
+ca b e312 h312
+cb a i321 l321
+cb b m322 p322
+select concat(ord(min(b)),ord(max(b))),min(b),max(b) from t1 group by a1,a2;
+concat(ord(min(b)),ord(max(b))) min(b) max(b)
+9798 a b
+9798 a b
+9798 a b
+9798 a b
+9798 a b
+9798 a b
+9798 a b
+9798 a b
+explain select a1,a2,b,d,min(c),max(c) from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128
+explain select a1,a2,b,d from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128
+explain extended select a1,a2,min(b),max(b) from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (c > 'a111') group by a1,a2;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 130 NULL 128 99.22 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,min(`test`.`t1`.`b`) AS `min(b)`,max(`test`.`t1`.`b`) AS `max(b)` from `test`.`t1` where (`test`.`t1`.`a1` = 'b' or `test`.`t1`.`a1` = 'd' or `test`.`t1`.`a1` = 'a' or `test`.`t1`.`a1` = 'c') and `test`.`t1`.`a2` > 'a' and `test`.`t1`.`c` > 'a111' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`
+explain extended select a1,a2,b,min(c),max(c) from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (d > 'xy2') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 130 NULL 128 75.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,`test`.`t1`.`b` AS `b`,min(`test`.`t1`.`c`) AS `min(c)`,max(`test`.`t1`.`c`) AS `max(c)` from `test`.`t1` where (`test`.`t1`.`a1` = 'b' or `test`.`t1`.`a1` = 'd' or `test`.`t1`.`a1` = 'a' or `test`.`t1`.`a1` = 'c') and `test`.`t1`.`a2` > 'a' and `test`.`t1`.`d` > 'xy2' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`
+explain extended select a1,a2,b,c from t1
+where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (d > 'xy2') group by a1,a2,b,c;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 130 NULL 128 75.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,`test`.`t1`.`b` AS `b`,`test`.`t1`.`c` AS `c` from `test`.`t1` where (`test`.`t1`.`a1` = 'b' or `test`.`t1`.`a1` = 'd' or `test`.`t1`.`a1` = 'a' or `test`.`t1`.`a1` = 'c') and `test`.`t1`.`a2` > 'a' and `test`.`t1`.`d` > 'xy2' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`,`test`.`t1`.`c`
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') or (b < 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b < 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b <= 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b <= 'b' and b >= 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain extended select a1,a2,b from t1 where (a1 = 'b' or a1 = 'd' or a1 = 'a' or a1 = 'c') and (a2 > 'a') and (c > 'a111') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_1 130 NULL 128 99.22 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`a1` = 'b' or `test`.`t1`.`a1` = 'd' or `test`.`t1`.`a1` = 'a' or `test`.`t1`.`a1` = 'c') and `test`.`t1`.`a2` > 'a' and `test`.`t1`.`c` > 'a111' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`
+explain select a1,a2,min(b),c from t2 where (a2 = 'a') and (c = 'a111') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+select a1,a2,min(b),c from t2 where (a2 = 'a') and (c = 'a111') group by a1;
+a1 a2 min(b) c
+a a a a111
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'b') or (b = 'a') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,min(c),max(c) from t2
+where (c > 'a000') and (c <= 'd999') and (c like '_8__') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1, a2, b, c, min(d), max(d) from t1 group by a1,a2,b,c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_1 163 NULL 128
+explain select a1,a2,count(a2) from t1 group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128 Using index
+explain extended select a1,a2,count(a2) from t1 where (a1 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 65 NULL 128 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2`,count(`test`.`t1`.`a2`) AS `count(a2)` from `test`.`t1` where `test`.`t1`.`a1` > 'a' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`
+explain extended select sum(ord(a1)) from t1 where (a1 > 'a') group by a1,a2,b;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range idx_t1_0,idx_t1_1,idx_t1_2 idx_t1_2 65 NULL 128 100.00 Using where; Using index
+Warnings:
+Note 1003 select sum(ord(`test`.`t1`.`a1`)) AS `sum(ord(a1))` from `test`.`t1` where `test`.`t1`.`a1` > 'a' group by `test`.`t1`.`a1`,`test`.`t1`.`a2`,`test`.`t1`.`b`
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'a' or b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select a1,a2,b,max(c),min(c) from t2 where (a2 = 'a') and (b = 'a' or b = 'b') group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL idx_t2_1 163 NULL 164 Using where; Using index
+explain select distinct(a1) from t1 where ord(a2) = 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL idx_t1_2 147 NULL 128 Using where; Using index
+select distinct(a1) from t1 where ord(a2) = 98;
+a1
+a
+b
+c
+d
+explain select a1 from t1 where a2 = 'b' group by a1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 130 NULL 5 Using where; Using index for group-by
+select a1 from t1 where a2 = 'b' group by a1;
+a1
+a
+b
+c
+d
+explain select distinct a1 from t1 where a2 = 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx_t1_2 130 NULL 5 Using where; Using index for group-by
+select distinct a1 from t1 where a2 = 'b';
+a1
+a
+b
+c
+d
+drop table t1,t2,t3;
+create table t1 (c1 int not null,c2 int not null, primary key(c1,c2)) engine=RocksDB;
+insert into t1 (c1,c2) values
+(10,1),(10,2),(10,3),(20,4),(20,5),(20,6),(30,7),(30,8),(30,9);
+select distinct c1, c2 from t1 order by c2;
+c1 c2
+10 1
+10 2
+10 3
+20 4
+20 5
+20 6
+30 7
+30 8
+30 9
+select c1,min(c2) as c2 from t1 group by c1 order by c2;
+c1 c2
+10 1
+20 4
+30 7
+select c1,c2 from t1 group by c1,c2 order by c2;
+c1 c2
+10 1
+10 2
+10 3
+20 4
+20 5
+20 6
+30 7
+30 8
+30 9
+drop table t1;
+CREATE TABLE t1 (a varchar(5), b int(11), PRIMARY KEY (a,b)) engine=RocksDB;
+INSERT INTO t1 VALUES ('AA',1), ('AA',2), ('AA',3), ('BB',1), ('AA',4);
+OPTIMIZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+SELECT a FROM t1 WHERE a='AA' GROUP BY a;
+a
+AA
+SELECT a FROM t1 WHERE a='BB' GROUP BY a;
+a
+BB
+EXPLAIN SELECT a FROM t1 WHERE a='AA' GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref PRIMARY PRIMARY 7 const 1000 Using where; Using index
+EXPLAIN SELECT a FROM t1 WHERE a='BB' GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref PRIMARY PRIMARY 7 const 1000 Using where; Using index
+SELECT DISTINCT a FROM t1 WHERE a='BB';
+a
+BB
+SELECT DISTINCT a FROM t1 WHERE a LIKE 'B%';
+a
+BB
+SELECT a FROM t1 WHERE a LIKE 'B%' GROUP BY a;
+a
+BB
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int(11) NOT NULL DEFAULT '0',
+b varchar(16) COLLATE latin1_general_ci NOT NULL DEFAULT '',
+PRIMARY KEY (a,b)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
+CREATE PROCEDURE a(x INT)
+BEGIN
+DECLARE rnd INT;
+DECLARE cnt INT;
+WHILE x > 0 DO
+SET rnd= x % 100;
+SET cnt = (SELECT COUNT(*) FROM t1 WHERE a = rnd);
+INSERT INTO t1(a,b) VALUES (rnd, CAST(cnt AS CHAR));
+SET x= x - 1;
+END WHILE;
+END|
+CALL a(1000);
+SELECT a FROM t1 WHERE a=0;
+a
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+SELECT DISTINCT a FROM t1 WHERE a=0;
+a
+0
+SELECT COUNT(DISTINCT a) FROM t1 WHERE a=0;
+COUNT(DISTINCT a)
+1
+DROP TABLE t1;
+DROP PROCEDURE a;
+CREATE TABLE t1 (a varchar(64) NOT NULL default '', PRIMARY KEY(a)) engine=RocksDB;
+INSERT INTO t1 (a) VALUES
+(''), ('CENTRAL'), ('EASTERN'), ('GREATER LONDON'),
+('NORTH CENTRAL'), ('NORTH EAST'), ('NORTH WEST'), ('SCOTLAND'),
+('SOUTH EAST'), ('SOUTH WEST'), ('WESTERN');
+EXPLAIN SELECT DISTINCT a,a FROM t1 ORDER BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 66 NULL 1000 Using index
+SELECT DISTINCT a,a FROM t1 ORDER BY a;
+a a
+
+CENTRAL CENTRAL
+EASTERN EASTERN
+GREATER LONDON GREATER LONDON
+NORTH CENTRAL NORTH CENTRAL
+NORTH EAST NORTH EAST
+NORTH WEST NORTH WEST
+SCOTLAND SCOTLAND
+SOUTH EAST SOUTH EAST
+SOUTH WEST SOUTH WEST
+WESTERN WESTERN
+DROP TABLE t1;
+CREATE TABLE t1 (id1 INT, id2 INT) engine=RocksDB;
+CREATE TABLE t2 (id2 INT, id3 INT, id5 INT) engine=RocksDB;
+CREATE TABLE t3 (id3 INT, id4 INT) engine=RocksDB;
+CREATE TABLE t4 (id4 INT) engine=RocksDB;
+CREATE TABLE t5 (id5 INT, id6 INT) engine=RocksDB;
+CREATE TABLE t6 (id6 INT) engine=RocksDB;
+INSERT INTO t1 VALUES(1,1);
+INSERT INTO t2 VALUES(1,1,1);
+INSERT INTO t3 VALUES(1,1);
+INSERT INTO t4 VALUES(1);
+INSERT INTO t5 VALUES(1,1);
+INSERT INTO t6 VALUES(1);
+SELECT * FROM
+t1
+NATURAL JOIN
+(t2 JOIN (t3 NATURAL JOIN t4, t5 NATURAL JOIN t6)
+ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5));
+id2 id1 id3 id5 id4 id3 id6 id5
+1 1 1 1 1 1 1 1
+SELECT * FROM
+t1
+NATURAL JOIN
+(((t3 NATURAL JOIN t4) join (t5 NATURAL JOIN t6) on t3.id4 = t5.id5) JOIN t2
+ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5));
+id2 id1 id4 id3 id6 id5 id3 id5
+1 1 1 1 1 1 1 1
+SELECT * FROM t1 NATURAL JOIN ((t3 join (t5 NATURAL JOIN t6)) JOIN t2);
+id2 id1 id3 id4 id6 id5 id3 id5
+1 1 1 1 1 1 1 1
+SELECT * FROM
+(t2 JOIN (t3 NATURAL JOIN t4, t5 NATURAL JOIN t6)
+ON (t3.id3 = t2.id3 AND t5.id5 = t2.id5))
+NATURAL JOIN
+t1;
+id2 id3 id5 id4 id3 id6 id5 id1
+1 1 1 1 1 1 1 1
+SELECT * FROM
+(t2 JOIN ((t3 NATURAL JOIN t4) join (t5 NATURAL JOIN t6)))
+NATURAL JOIN
+t1;
+id2 id3 id5 id4 id3 id6 id5 id1
+1 1 1 1 1 1 1 1
+DROP TABLE t1,t2,t3,t4,t5,t6;
+CREATE TABLE t1 (a int, b int, PRIMARY KEY (a,b), KEY b (b)) engine=RocksDB;
+INSERT INTO t1 VALUES (1,1),(1,2),(1,0),(1,3);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+explain SELECT MAX(b), a FROM t1 WHERE b < 2 AND a = 1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range PRIMARY,b PRIMARY 8 NULL 2 Using where; Using index for group-by
+SELECT MAX(b), a FROM t1 WHERE b < 2 AND a = 1 GROUP BY a;
+MAX(b) a
+1 1
+SELECT MIN(b), a FROM t1 WHERE b > 1 AND a = 1 GROUP BY a;
+MIN(b) a
+2 1
+CREATE TABLE t2 (a int, b int, c int, PRIMARY KEY (a,b,c)) engine=RocksDB;
+INSERT INTO t2 SELECT a,b,b FROM t1;
+ANALYZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+explain SELECT MIN(c) FROM t2 WHERE b = 2 and a = 1 and c > 1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range PRIMARY PRIMARY 12 NULL 2 Using where; Using index for group-by
+SELECT MIN(c) FROM t2 WHERE b = 2 and a = 1 and c > 1 GROUP BY a;
+MIN(c)
+2
+DROP TABLE t1,t2;
+CREATE TABLE t1 (a INT, b INT, INDEX (a,b)) engine=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1,1), (1,2), (1,3), (1,4), (1,5),
+(2,2), (2,3), (2,1), (3,1), (4,1), (4,2), (4,3), (4,4), (4,5), (4,6);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN SELECT max(b), a FROM t1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 6 Using index for group-by
+FLUSH STATUS;
+SELECT max(b), a FROM t1 GROUP BY a;
+max(b) a
+5 1
+3 2
+1 3
+6 4
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 0
+Handler_read_retry 0
+EXPLAIN SELECT max(b), a FROM t1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 6 Using index for group-by
+FLUSH STATUS;
+CREATE TABLE t2 engine=RocksDB SELECT max(b), a FROM t1 GROUP BY a;
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 0
+Handler_read_retry 0
+FLUSH STATUS;
+SELECT * FROM (SELECT max(b), a FROM t1 GROUP BY a) b;
+max(b) a
+5 1
+3 2
+1 3
+6 4
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 0
+Handler_read_retry 0
+FLUSH STATUS;
+(SELECT max(b), a FROM t1 GROUP BY a) UNION
+(SELECT max(b), a FROM t1 GROUP BY a);
+max(b) a
+5 1
+3 2
+1 3
+6 4
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 16
+Handler_read_next 0
+Handler_read_retry 0
+EXPLAIN (SELECT max(b), a FROM t1 GROUP BY a) UNION
+(SELECT max(b), a FROM t1 GROUP BY a);
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 range NULL a 5 NULL 6 Using index for group-by
+2 UNION t1 range NULL a 5 NULL 6 Using index for group-by
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+EXPLAIN SELECT (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) x
+FROM t1 AS t1_outer;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1_outer index NULL a 10 NULL 15 Using index
+2 SUBQUERY t1 range a a 5 NULL 6 Using where; Using index for group-by
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE EXISTS
+(SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1_outer index NULL a 10 NULL 15 Using index
+2 SUBQUERY t1 index NULL a 10 NULL 15 Using index
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE
+(SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) > 12;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE
+2 SUBQUERY t1 range a a 5 NULL 6 Using where; Using index for group-by
+EXPLAIN SELECT 1 FROM t1 AS t1_outer WHERE
+a IN (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY <subquery2> ALL distinct_key NULL NULL NULL 6
+1 PRIMARY t1_outer ref a a 5 <subquery2>.max(b) 3 Using index
+2 MATERIALIZED t1 range a a 5 NULL 6 Using where; Using index for group-by
+EXPLAIN SELECT 1 FROM t1 AS t1_outer GROUP BY a HAVING
+a > (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1_outer range NULL a 5 NULL 6 Using index for group-by
+2 SUBQUERY t1 range a a 5 NULL 6 Using where; Using index for group-by
+EXPLAIN SELECT 1 FROM t1 AS t1_outer1 JOIN t1 AS t1_outer2
+ON t1_outer1.a = (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2)
+AND t1_outer1.b = t1_outer2.b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1_outer2 index NULL a 10 NULL 15 Using where; Using index
+1 PRIMARY t1_outer1 ref a a 10 const,test.t1_outer2.b 1 Using where; Using index
+2 SUBQUERY t1 range a a 5 NULL 6 Using where; Using index for group-by
+EXPLAIN SELECT (SELECT (SELECT max(b) FROM t1 GROUP BY a HAVING a < 2) x
+FROM t1 AS t1_outer) x2 FROM t1 AS t1_outer2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1_outer2 index NULL a 10 NULL 15 Using index
+2 SUBQUERY t1_outer index NULL a 10 NULL 15 Using index
+3 SUBQUERY t1 range a a 5 NULL 6 Using where; Using index for group-by
+CREATE TABLE t3 LIKE t1;
+FLUSH STATUS;
+INSERT INTO t3 SELECT a,MAX(b) FROM t1 GROUP BY a;
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 13
+Handler_read_next 0
+Handler_read_retry 0
+DELETE FROM t3;
+FLUSH STATUS;
+INSERT INTO t3 SELECT 1, (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2)
+FROM t1 LIMIT 1;
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 0
+Handler_read_retry 0
+FLUSH STATUS;
+DELETE FROM t3 WHERE (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2) > 10000;
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 0
+Handler_read_retry 0
+FLUSH STATUS;
+DELETE FROM t3 WHERE (SELECT (SELECT MAX(b) FROM t1 GROUP BY a HAVING a < 2) x
+FROM t1) > 10000;
+ERROR 21000: Subquery returns more than 1 row
+SHOW STATUS LIKE 'handler_read__e%';
+Variable_name Value
+Handler_read_key 8
+Handler_read_next 1
+Handler_read_retry 0
+DROP TABLE t1,t2,t3;
+CREATE TABLE t1 (a int, INDEX idx(a)) engine=RocksDB;
+INSERT INTO t1 VALUES
+(4), (2), (1), (2), (4), (2), (1), (4),
+(4), (2), (1), (2), (2), (4), (1), (4);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN SELECT DISTINCT(a) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx 5 NULL 4 Using index for group-by
+SELECT DISTINCT(a) FROM t1;
+a
+1
+2
+4
+EXPLAIN SELECT SQL_BIG_RESULT DISTINCT(a) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL idx 5 NULL 4 Using index for group-by
+SELECT SQL_BIG_RESULT DISTINCT(a) FROM t1;
+a
+1
+2
+4
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b INT) engine=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1,1), (1,2), (1,3);
+INSERT INTO t1 SELECT a + 1, b FROM t1;
+INSERT INTO t1 SELECT a + 2, b FROM t1;
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 12 Using temporary; Using filesort
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+a MIN(b) MAX(b)
+4 1 3
+3 1 3
+2 1 3
+1 1 3
+CREATE INDEX break_it ON t1 (a, b);
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL break_it 10 NULL 4 Using index for group-by
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a;
+a MIN(b) MAX(b)
+1 1 3
+2 1 3
+3 1 3
+4 1 3
+EXPLAIN
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL break_it 10 NULL 4 Using index for group-by; Using temporary; Using filesort
+SELECT a, MIN(b), MAX(b) FROM t1 GROUP BY a ORDER BY a DESC;
+a MIN(b) MAX(b)
+4 1 3
+3 1 3
+2 1 3
+1 1 3
+EXPLAIN
+SELECT a, MIN(b), MAX(b), AVG(b) FROM t1 GROUP BY a ORDER BY a DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL break_it 10 NULL 12 Using index
+SELECT a, MIN(b), MAX(b), AVG(b) FROM t1 GROUP BY a ORDER BY a DESC;
+a MIN(b) MAX(b) AVG(b)
+4 1 3 2.0000
+3 1 3 2.0000
+2 1 3 2.0000
+1 1 3 2.0000
+DROP TABLE t1;
+create table t1 (a int, b int, primary key (a,b), key `index` (a,b)) engine=MyISAM;
+insert into t1 (a,b) values
+(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),
+(0,7),(0,8),(0,9),(0,10),(0,11),(0,12),(0,13),
+(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),
+(1,7),(1,8),(1,9),(1,10),(1,11),(1,12),(1,13),
+(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),
+(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),
+(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),
+(3,7),(3,8),(3,9),(3,10),(3,11),(3,12),(3,13);
+insert into t1 (a,b) select a, max(b)+1 from t1 where a = 0 group by a;
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+select * from t1;
+a b
+0 0
+0 1
+0 2
+0 3
+0 4
+0 5
+0 6
+0 7
+0 8
+0 9
+0 10
+0 11
+0 12
+0 13
+0 14
+1 0
+1 1
+1 2
+1 3
+1 4
+1 5
+1 6
+1 7
+1 8
+1 9
+1 10
+1 11
+1 12
+1 13
+2 0
+2 1
+2 2
+2 3
+2 4
+2 5
+2 6
+2 7
+2 8
+2 9
+2 10
+2 11
+2 12
+2 13
+3 0
+3 1
+3 2
+3 3
+3 4
+3 5
+3 6
+3 7
+3 8
+3 9
+3 10
+3 11
+3 12
+3 13
+explain extended select sql_buffer_result a, max(b)+1 from t1 where a = 0 group by a;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ref PRIMARY,index PRIMARY 4 const 15 6.67 Using index; Using temporary
+Warnings:
+Note 1003 select sql_buffer_result `test`.`t1`.`a` AS `a`,max(`test`.`t1`.`b`) + 1 AS `max(b)+1` from `test`.`t1` where `test`.`t1`.`a` = 0 group by `test`.`t1`.`a`
+drop table t1;
+CREATE TABLE t1 (a int, b int, c int, d int,
+KEY foo (c,d,a,b), KEY bar (c,a,b,d)) engine=RocksDB;
+INSERT INTO t1 VALUES (1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3), (1, 1, 1, 4);
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT a,b,c+1,d FROM t1;
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN SELECT DISTINCT c FROM t1 WHERE d=4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL foo 10 NULL 3 Using where; Using index for group-by
+SELECT DISTINCT c FROM t1 WHERE d=4;
+c
+1
+2
+DROP TABLE t1;
+#
+# Bug #45386: Wrong query result with MIN function in field list,
+# WHERE and GROUP BY clause
+#
+CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=RocksDB;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+INSERT INTO t SELECT * FROM t;
+ANALYZE TABLE t;
+Table Op Msg_type Msg_text
+test.t analyze status Engine-independent statistics collected
+test.t analyze status OK
+# test MIN
+#should use range with index for group by
+EXPLAIN
+SELECT a, MIN(b) FROM t WHERE b <> 0 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range NULL a 10 NULL 2 Using where; Using index for group-by
+#should return 1 row
+SELECT a, MIN(b) FROM t WHERE b <> 0 GROUP BY a;
+a MIN(b)
+2 1
+# test MAX
+#should use range with index for group by
+EXPLAIN
+SELECT a, MAX(b) FROM t WHERE b <> 1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range NULL a 10 NULL 2 Using where; Using index for group-by
+#should return 1 row
+SELECT a, MAX(b) FROM t WHERE b <> 1 GROUP BY a;
+a MAX(b)
+2 0
+# test 3 ranges and use the middle one
+INSERT INTO t SELECT a, 2 FROM t;
+#should use range with index for group by
+EXPLAIN
+SELECT a, MAX(b) FROM t WHERE b > 0 AND b < 2 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range NULL a 10 NULL 2 Using where; Using index for group-by
+#should return 1 row
+SELECT a, MAX(b) FROM t WHERE b > 0 AND b < 2 GROUP BY a;
+a MAX(b)
+2 1
+DROP TABLE t;
+#
+# Bug #48472: Loose index scan inappropriately chosen for some WHERE
+# conditions
+#
+CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=RocksDB;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+ANALYZE TABLE t;
+Table Op Msg_type Msg_text
+test.t analyze status Engine-independent statistics collected
+test.t analyze status OK
+SELECT a, MAX(b) FROM t WHERE 0=b+0 GROUP BY a;
+a MAX(b)
+2 0
+DROP TABLE t;
+End of 5.0 tests
+#
+# Bug #46607: Assertion failed: (cond_type == Item::FUNC_ITEM) results in
+# server crash
+#
+CREATE TABLE t (a INT, b INT, INDEX (a,b)) engine=RocksDB;
+INSERT INTO t VALUES (2,0), (2,0), (2,1), (2,1);
+INSERT INTO t SELECT * FROM t;
+SELECT a, MAX(b) FROM t WHERE b GROUP BY a;
+a MAX(b)
+2 1
+DROP TABLE t;
+CREATE TABLE t1(a INT NOT NULL, b INT NOT NULL, KEY (b)) engine=RocksDB;
+INSERT INTO t1 VALUES(1,1),(2,1);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+SELECT 1 AS c, b FROM t1 WHERE b IN (1,2) GROUP BY c, b;
+c b
+1 1
+SELECT a FROM t1 WHERE b=1;
+a
+1
+2
+DROP TABLE t1;
+#
+# Bug#47762: Incorrect result from MIN() when WHERE tests NOT NULL column
+# for NULL
+#
+## Test for NULLs allowed
+CREATE TABLE t1 ( a INT, KEY (a) ) engine=RocksDB;
+INSERT INTO t1 VALUES (1), (2), (3);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a <=> NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x No matching min/max row
+SELECT MIN( a ) FROM t1 WHERE a <=> NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+x x x x x x x x x Using where; Using index
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x No matching min/max row
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+MIN( a )
+NULL
+INSERT INTO t1 VALUES (NULL), (NULL);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a <=> NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Select tables optimized away
+SELECT MIN( a ) FROM t1 WHERE a <=> NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+x x x x x x x x x Using where; Using index
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Select tables optimized away
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+MIN( a )
+NULL
+DROP TABLE t1;
+## Test for NOT NULLs
+CREATE TABLE t1 ( a INT NOT NULL PRIMARY KEY) engine=RocksDB;
+INSERT INTO t1 VALUES (1), (2), (3);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+#
+# NULL-safe operator test disabled for non-NULL indexed columns.
+#
+# See bugs
+#
+# - Bug#52173: Reading NULL value from non-NULL index gives
+# wrong result in embedded server
+#
+# - Bug#52174: Sometimes wrong plan when reading a MAX value from
+# non-NULL index
+#
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a = NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a <> NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a > NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a < NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND 10;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN NULL AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+SELECT MIN( a ) FROM t1 WHERE a BETWEEN 10 AND NULL;
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE noticed after reading const tables
+x x x x x x x x x Using where; Using index
+SELECT MIN( a ) FROM t1 WHERE a = (SELECT a FROM t1 WHERE a < 0);
+MIN( a )
+NULL
+EXPLAIN
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+id select_type table type possible_keys key key_len ref rows Extra
+x x x x x x x x x Impossible WHERE
+SELECT MIN( a ) FROM t1 WHERE a IS NULL;
+MIN( a )
+NULL
+DROP TABLE t1;
+#
+# Bug#53859: Valgrind: opt_sum_query(TABLE_LIST*, List<Item>&, Item*) at
+# opt_sum.cc:305
+#
+CREATE TABLE t1 ( a INT, KEY (a) ) engine=RocksDB;
+INSERT INTO t1 VALUES (1), (2), (3);
+SELECT MIN( a ) AS min_a
+FROM t1
+WHERE a > 1 AND a IS NULL
+ORDER BY min_a;
+min_a
+NULL
+DROP TABLE t1;
+End of 5.1 tests
+#
+# WL#3220 (Loose index scan for COUNT DISTINCT)
+#
+CREATE TABLE t1 (a INT, b INT, c INT, KEY (a,b)) engine=RocksDB;
+INSERT INTO t1 VALUES (1,1,1), (1,2,1), (1,3,1), (1,4,1);
+INSERT INTO t1 SELECT a, b + 4, 1 FROM t1;
+INSERT INTO t1 SELECT a + 1, b, 1 FROM t1;
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+CREATE TABLE t2 (a INT, b INT, c INT, d INT, e INT, f INT, KEY (a,b,c)) engine=RocksDB;
+INSERT INTO t2 VALUES (1,1,1,1,1,1), (1,2,1,1,1,1), (1,3,1,1,1,1),
+(1,4,1,1,1,1);
+INSERT INTO t2 SELECT a, b + 4, c,d,e,f FROM t2;
+INSERT INTO t2 SELECT a + 1, b, c,d,e,f FROM t2;
+ANALYZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 3 Using index for group-by
+SELECT COUNT(DISTINCT a) FROM t1;
+COUNT(DISTINCT a)
+2
+EXPLAIN SELECT COUNT(DISTINCT a,b) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 10 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT a,b) FROM t1;
+COUNT(DISTINCT a,b)
+16
+EXPLAIN SELECT COUNT(DISTINCT b,a) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 10 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT b,a) FROM t1;
+COUNT(DISTINCT b,a)
+16
+EXPLAIN SELECT COUNT(DISTINCT b) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 10 NULL 16 Using index
+SELECT COUNT(DISTINCT b) FROM t1;
+COUNT(DISTINCT b)
+8
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 3 Using index for group-by
+SELECT COUNT(DISTINCT a) FROM t1 GROUP BY a;
+COUNT(DISTINCT a)
+1
+1
+EXPLAIN SELECT COUNT(DISTINCT b) FROM t1 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 10 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT b) FROM t1 GROUP BY a;
+COUNT(DISTINCT b)
+8
+8
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 GROUP BY b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 10 NULL 16 Using index; Using filesort
+SELECT COUNT(DISTINCT a) FROM t1 GROUP BY b;
+COUNT(DISTINCT a)
+2
+2
+2
+2
+2
+2
+2
+2
+EXPLAIN SELECT DISTINCT COUNT(DISTINCT a) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 10 NULL 16 Using index
+SELECT DISTINCT COUNT(DISTINCT a) FROM t1;
+COUNT(DISTINCT a)
+2
+EXPLAIN SELECT COUNT(DISTINCT a, b + 0) FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 10 NULL 16 Using index
+SELECT COUNT(DISTINCT a, b + 0) FROM t1;
+COUNT(DISTINCT a, b + 0)
+16
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT b) < 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 10 NULL 16 Using index
+SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT b) < 10;
+COUNT(DISTINCT a)
+2
+EXPLAIN SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT c) < 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL 16
+SELECT COUNT(DISTINCT a) FROM t1 HAVING COUNT(DISTINCT c) < 10;
+COUNT(DISTINCT a)
+2
+EXPLAIN SELECT 1 FROM t1 HAVING COUNT(DISTINCT a) < 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 3 Using index for group-by
+SELECT 1 FROM t1 HAVING COUNT(DISTINCT a) < 10;
+1
+1
+EXPLAIN SELECT 1 FROM t1 GROUP BY a HAVING COUNT(DISTINCT b) > 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 10 NULL 17 Using index for group-by
+SELECT 1 FROM t1 GROUP BY a HAVING COUNT(DISTINCT b) > 1;
+1
+1
+1
+EXPLAIN SELECT COUNT(DISTINCT t1_1.a) FROM t1 t1_1, t1 t1_2 GROUP BY t1_1.a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1_1 index NULL a 10 NULL 16 Using index; Using temporary; Using filesort
+1 SIMPLE t1_2 index NULL a 10 NULL 16 Using index; Using join buffer (flat, BNL join)
+SELECT COUNT(DISTINCT t1_1.a) FROM t1 t1_1, t1 t1_2 GROUP BY t1_1.a;
+COUNT(DISTINCT t1_1.a)
+1
+1
+EXPLAIN SELECT COUNT(DISTINCT a), 12 FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL a 5 NULL 3 Using index for group-by
+SELECT COUNT(DISTINCT a), 12 FROM t1;
+COUNT(DISTINCT a) 12
+2 12
+EXPLAIN SELECT COUNT(DISTINCT a, b, c) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 15 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT a, b, c) FROM t2;
+COUNT(DISTINCT a, b, c)
+16
+EXPLAIN SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT a) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 5 NULL 3 Using index for group-by
+SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT a) FROM t2;
+COUNT(DISTINCT a) SUM(DISTINCT a) AVG(DISTINCT a)
+2 3 1.5000
+EXPLAIN SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT f) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ALL NULL NULL NULL NULL 16
+SELECT COUNT(DISTINCT a), SUM(DISTINCT a), AVG(DISTINCT f) FROM t2;
+COUNT(DISTINCT a) SUM(DISTINCT a) AVG(DISTINCT f)
+2 3 1.0000
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, a) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 10 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, a) FROM t2;
+COUNT(DISTINCT a, b) COUNT(DISTINCT b, a)
+16 16
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, f) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ALL NULL NULL NULL NULL 16
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, f) FROM t2;
+COUNT(DISTINCT a, b) COUNT(DISTINCT b, f)
+16 8
+EXPLAIN SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, d) FROM t2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ALL NULL NULL NULL NULL 16
+SELECT COUNT(DISTINCT a, b), COUNT(DISTINCT b, d) FROM t2;
+COUNT(DISTINCT a, b) COUNT(DISTINCT b, d)
+16 8
+EXPLAIN SELECT a, c, COUNT(DISTINCT c, a, b) FROM t2 GROUP BY a, b, c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 15 NULL 17 Using index for group-by
+SELECT a, c, COUNT(DISTINCT c, a, b) FROM t2 GROUP BY a, b, c;
+a c COUNT(DISTINCT c, a, b)
+1 1 1
+1 1 1
+1 1 1
+1 1 1
+1 1 1
+1 1 1
+1 1 1
+1 1 1
+2 1 1
+2 1 1
+2 1 1
+2 1 1
+2 1 1
+2 1 1
+2 1 1
+2 1 1
+EXPLAIN SELECT COUNT(DISTINCT c, a, b) FROM t2
+WHERE a > 5 AND b BETWEEN 10 AND 20 GROUP BY a, b, c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 15 NULL 17 Using where; Using index for group-by
+SELECT COUNT(DISTINCT c, a, b) FROM t2
+WHERE a > 5 AND b BETWEEN 10 AND 20 GROUP BY a, b, c;
+COUNT(DISTINCT c, a, b)
+EXPLAIN SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 WHERE a = 5
+GROUP BY b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref a a 5 const 16 Using where; Using index
+SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 WHERE a = 5
+GROUP BY b;
+COUNT(DISTINCT b) SUM(DISTINCT b)
+EXPLAIN SELECT a, COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 10 NULL 17 Using index for group-by
+SELECT a, COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+a COUNT(DISTINCT b) SUM(DISTINCT b)
+1 8 36
+2 8 36
+EXPLAIN SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 10 NULL 17 Using index for group-by
+SELECT COUNT(DISTINCT b), SUM(DISTINCT b) FROM t2 GROUP BY a;
+COUNT(DISTINCT b) SUM(DISTINCT b)
+8 36
+8 36
+EXPLAIN SELECT COUNT(DISTINCT a, b) FROM t2 WHERE c = 13 AND d = 42;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ALL NULL NULL NULL NULL 16 Using where
+SELECT COUNT(DISTINCT a, b) FROM t2 WHERE c = 13 AND d = 42;
+COUNT(DISTINCT a, b)
+0
+EXPLAIN SELECT a, COUNT(DISTINCT a), SUM(DISTINCT a) FROM t2
+WHERE b = 13 AND c = 42 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 15 NULL 3 Using where; Using index for group-by
+SELECT a, COUNT(DISTINCT a), SUM(DISTINCT a) FROM t2
+WHERE b = 13 AND c = 42 GROUP BY a;
+a COUNT(DISTINCT a) SUM(DISTINCT a)
+# This query could have been resolved using loose index scan since
+# the second part of count(..) is defined by a constant predicate
+EXPLAIN SELECT COUNT(DISTINCT a, b), SUM(DISTINCT a) FROM t2 WHERE b = 42;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL a 15 NULL 16 Using where; Using index
+SELECT COUNT(DISTINCT a, b), SUM(DISTINCT a) FROM t2 WHERE b = 42;
+COUNT(DISTINCT a, b) SUM(DISTINCT a)
+0 NULL
+EXPLAIN SELECT SUM(DISTINCT a), MAX(b) FROM t2 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL a 15 NULL 16 Using index
+SELECT SUM(DISTINCT a), MAX(b) FROM t2 GROUP BY a;
+SUM(DISTINCT a) MAX(b)
+1 8
+2 8
+EXPLAIN SELECT 42 * (a + c + COUNT(DISTINCT c, a, b)) FROM t2 GROUP BY a, b, c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range NULL a 15 NULL 17 Using index for group-by
+SELECT 42 * (a + c + COUNT(DISTINCT c, a, b)) FROM t2 GROUP BY a, b, c;
+42 * (a + c + COUNT(DISTINCT c, a, b))
+126
+126
+126
+126
+126
+126
+126
+126
+168
+168
+168
+168
+168
+168
+168
+168
+EXPLAIN SELECT (SUM(DISTINCT a) + MAX(b)) FROM t2 GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index NULL a 15 NULL 16 Using index
+SELECT (SUM(DISTINCT a) + MAX(b)) FROM t2 GROUP BY a;
+(SUM(DISTINCT a) + MAX(b))
+9
+10
+DROP TABLE t1,t2;
+# end of WL#3220 tests
+#
+# Bug#50539: Wrong result when loose index scan is used for an aggregate
+# function with distinct
+#
+CREATE TABLE t1 (
+f1 int(11) NOT NULL DEFAULT '0',
+f2 char(1) NOT NULL DEFAULT '',
+PRIMARY KEY (f1,f2)
+) engine=RocksDB;
+insert into t1 values(1,'A'),(1 , 'B'), (1, 'C'), (2, 'A'),
+(3, 'A'), (3, 'B'), (3, 'C'), (3, 'D');
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+SELECT f1, COUNT(DISTINCT f2) FROM t1 GROUP BY f1;
+f1 COUNT(DISTINCT f2)
+1 3
+2 1
+3 4
+explain SELECT f1, COUNT(DISTINCT f2) FROM t1 GROUP BY f1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 5 NULL 8 Using index
+drop table t1;
+# End of test#50539.
+#
+# Bug#17217128 - BAD INTERACTION BETWEEN MIN/MAX AND
+# "HAVING SUM(DISTINCT)": WRONG RESULTS.
+#
+CREATE TABLE t (a INT, b INT, KEY(a,b)) engine=RocksDB;
+INSERT INTO t VALUES (1,1), (2,2), (3,3), (4,4), (1,0), (3,2), (4,5);
+ANALYZE TABLE t;
+Table Op Msg_type Msg_text
+test.t analyze status Engine-independent statistics collected
+test.t analyze status OK
+ANALYZE TABLE t;
+Table Op Msg_type Msg_text
+test.t analyze status Engine-independent statistics collected
+test.t analyze status OK
+SELECT a, SUM(DISTINCT a), MIN(b) FROM t GROUP BY a;
+a SUM(DISTINCT a) MIN(b)
+1 1 0
+2 2 2
+3 3 2
+4 4 4
+EXPLAIN SELECT a, SUM(DISTINCT a), MIN(b) FROM t GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL a 10 NULL 7 Using index
+SELECT a, SUM(DISTINCT a), MAX(b) FROM t GROUP BY a;
+a SUM(DISTINCT a) MAX(b)
+1 1 1
+2 2 2
+3 3 3
+4 4 5
+EXPLAIN SELECT a, SUM(DISTINCT a), MAX(b) FROM t GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL a 10 NULL 7 Using index
+SELECT a, MAX(b) FROM t GROUP BY a HAVING SUM(DISTINCT a);
+a MAX(b)
+1 1
+2 2
+3 3
+4 5
+EXPLAIN SELECT a, MAX(b) FROM t GROUP BY a HAVING SUM(DISTINCT a);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL a 10 NULL 7 Using index
+SELECT SUM(DISTINCT a), MIN(b), MAX(b) FROM t;
+SUM(DISTINCT a) MIN(b) MAX(b)
+10 0 5
+EXPLAIN SELECT SUM(DISTINCT a), MIN(b), MAX(b) FROM t;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL a 10 NULL 7 Using index
+SELECT a, SUM(DISTINCT a), MIN(b), MAX(b) FROM t GROUP BY a;
+a SUM(DISTINCT a) MIN(b) MAX(b)
+1 1 0 1
+2 2 2 2
+3 3 2 3
+4 4 4 5
+EXPLAIN SELECT a, SUM(DISTINCT a), MIN(b), MAX(b) FROM t GROUP BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL a 10 NULL 7 Using index
+DROP TABLE t;
+#
+# Bug#18109609: LOOSE INDEX SCAN IS NOT USED WHEN IT SHOULD
+#
+CREATE TABLE t1 (
+id INT AUTO_INCREMENT PRIMARY KEY,
+c1 INT,
+c2 INT,
+KEY(c1,c2)) engine=RocksDB;
+INSERT INTO t1(c1,c2) VALUES
+(1, 1), (1,2), (2,1), (2,2), (3,1), (3,2), (3,3), (4,1), (4,2), (4,3),
+(4,4), (4,5), (4,6), (4,7), (4,8), (4,9), (4,10), (4,11), (4,12), (4,13),
+(4,14), (4,15), (4,16), (4,17), (4,18), (4,19), (4,20),(5,5);
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+EXPLAIN SELECT MAX(c2), c1 FROM t1 WHERE c1 = 4 GROUP BY c1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref c1 c1 5 const 28 Using index
+FLUSH STATUS;
+SELECT MAX(c2), c1 FROM t1 WHERE c1 = 4 GROUP BY c1;
+MAX(c2) c1
+20 4
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 1
+Handler_read_last 0
+Handler_read_next 20
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+DROP TABLE t1;
+# End of test for Bug#18109609
+set global debug_dbug=@debug_tmp;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ha_extra_keyread.result b/storage/rocksdb/mysql-test/rocksdb/r/ha_extra_keyread.result
new file mode 100644
index 00000000..93c8a464
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ha_extra_keyread.result
@@ -0,0 +1,10 @@
+CREATE TABLE t1 (a INT, b CHAR(8), KEY ab(a, b)) ENGINE=rocksdb DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_bin;
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+SET debug_dbug="+d,dbug.rocksdb.HA_EXTRA_KEYREAD";
+SELECT b FROM t1 FORCE INDEX(ab) WHERE a=35;
+b
+foo
+SET debug_dbug="-d,dbug.rocksdb.HA_EXTRA_KEYREAD";
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/handler_basic.result b/storage/rocksdb/mysql-test/rocksdb/r/handler_basic.result
new file mode 100644
index 00000000..4f285134
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/handler_basic.result
@@ -0,0 +1,128 @@
+DROP TABLE IF EXISTS t1;
+FLUSH STATUS;
+CREATE TABLE t1 (id INT PRIMARY KEY, a VARCHAR(100), b INT,
+INDEX b(b)) ENGINE=rocksdb;
+INSERT INTO t1 (id,a,b) VALUES (1,'foobar',100),(2,'z',0),(3,'bar',50);
+SHOW SESSION STATUS LIKE 'Handler_write%';
+Variable_name Value
+Handler_write 3
+UPDATE t1 SET b=1000 WHERE id=1;
+SHOW SESSION STATUS LIKE 'Handler_update%';
+Variable_name Value
+Handler_update 1
+DELETE FROM t1 WHERE id=2;
+SHOW SESSION STATUS LIKE 'Handler_delete%';
+Variable_name Value
+Handler_delete 1
+INSERT INTO t1 (id,b) VALUES(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+SHOW SESSION STATUS LIKE 'Handler_write%';
+Variable_name Value
+Handler_write 10
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id=8;
+id a b
+8 NULL 8
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 1
+Handler_read_last 0
+Handler_read_next 0
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+FLUSH STATUS;
+SET GLOBAL rocksdb_force_flush_memtable_and_lzero_now=1;
+SELECT * FROM t1 WHERE b=6;
+id a b
+6 NULL 6
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 1
+Handler_read_last 0
+Handler_read_next 1
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+FLUSH STATUS;
+SELECT * FROM t1;
+id a b
+1 foobar 1000
+10 NULL 10
+3 bar 50
+4 NULL 4
+5 NULL 5
+6 NULL 6
+7 NULL 7
+8 NULL 8
+9 NULL 9
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 0
+Handler_read_last 0
+Handler_read_next 0
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 10
+FLUSH STATUS;
+SELECT * FROM t1 FORCE INDEX(b) WHERE b <=5 ORDER BY b;
+id a b
+4 NULL 4
+5 NULL 5
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 1
+Handler_read_last 0
+Handler_read_next 2
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id >=8 ORDER BY id;
+id a b
+8 NULL 8
+9 NULL 9
+10 NULL 10
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 0
+Handler_read_key 1
+Handler_read_last 0
+Handler_read_next 3
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id < 8 ORDER BY id;
+id a b
+1 foobar 1000
+3 bar 50
+4 NULL 4
+5 NULL 5
+6 NULL 6
+7 NULL 7
+SHOW SESSION STATUS LIKE 'Handler_read%';
+Variable_name Value
+Handler_read_first 1
+Handler_read_key 0
+Handler_read_last 0
+Handler_read_next 6
+Handler_read_prev 0
+Handler_read_retry 0
+Handler_read_rnd 0
+Handler_read_rnd_deleted 0
+Handler_read_rnd_next 0
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/hermitage.result b/storage/rocksdb/mysql-test/rocksdb/r/hermitage.result
new file mode 100644
index 00000000..8bf2416a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/hermitage.result
@@ -0,0 +1,648 @@
+DROP TABLE IF EXISTS test;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connect con3,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connection con1;
+create table test (id int primary key, value int) engine=rocksdb;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test;
+id value
+1 10
+2 20
+update test set value = 101 where id = 1;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+connection con1;
+rollback;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 101 where id = 1;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+connection con1;
+update test set value = 11 where id = 1;
+commit;
+connection con2;
+select * from test;
+id value
+1 11
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 22 where id = 2;
+connection con1;
+select * from test where id = 2;
+id value
+2 20
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+connection con1;
+commit;
+connection con2;
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 11 where id = 1;
+update test set value = 19 where id = 2;
+connection con2;
+update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+connection con3;
+select * from test;
+id value
+1 11
+2 19
+connection con2;
+update test set value = 18 where id = 2;
+connection con3;
+select * from test;
+id value
+1 11
+2 19
+connection con2;
+commit;
+connection con3;
+select * from test;
+id value
+1 12
+2 18
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value = 30;
+id value
+connection con2;
+insert into test (id, value) values(3, 30);
+commit;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+3 30
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = value + 10;
+connection con2;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_snapshot_conflict_errors';
+select * from test;
+id value
+1 10
+2 20
+delete from test where value = 20;
+connection con1;
+commit;
+connection con2;
+select * from test;
+id value
+2 30
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+select * from test;
+id value
+1 12
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+select * from test where id = 2;
+id value
+2 20
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+select * from test where id = 2;
+id value
+2 18
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value % 5 = 0;
+id value
+1 10
+2 20
+connection con2;
+update test set value = 12 where value = 10;
+commit;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+1 12
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+delete from test where value = 20;
+select * from test where id = 2;
+id value
+2 18
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id in (1,2);
+id value
+1 10
+2 20
+connection con2;
+select * from test where id in (1,2);
+id value
+1 10
+2 20
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 21 where id = 2;
+connection con1;
+commit;
+connection con2;
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+connection con2;
+select * from test where value % 3 = 0;
+id value
+connection con1;
+insert into test (id, value) values(3, 30);
+connection con2;
+insert into test (id, value) values(4, 42);
+connection con1;
+commit;
+connection con2;
+commit;
+select * from test where value % 3 = 0;
+id value
+3 30
+4 42
+connection con1;
+select * from test where value % 3 = 0;
+id value
+3 30
+4 42
+connection default;
+drop table test;
+disconnect con1;
+disconnect con2;
+disconnect con3;
+DROP TABLE IF EXISTS test;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connect con3,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connection con1;
+create table test (id int primary key, value int) engine=rocksdb;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test;
+id value
+1 10
+2 20
+update test set value = 101 where id = 1;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+connection con1;
+rollback;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 101 where id = 1;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+connection con1;
+update test set value = 11 where id = 1;
+commit;
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 22 where id = 2;
+connection con1;
+select * from test where id = 2;
+id value
+2 20
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+connection con1;
+commit;
+connection con2;
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = 11 where id = 1;
+update test set value = 19 where id = 2;
+connection con2;
+update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+connection con3;
+select * from test;
+id value
+1 11
+2 19
+connection con2;
+update test set value = 18 where id = 2;
+connection con3;
+select * from test;
+id value
+1 11
+2 19
+connection con2;
+commit;
+connection con3;
+select * from test;
+id value
+1 11
+2 19
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value = 30;
+id value
+connection con2;
+insert into test (id, value) values(3, 30);
+commit;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+update test set value = value + 10;
+connection con2;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_snapshot_conflict_errors';
+select * from test;
+id value
+1 10
+2 20
+delete from test where value = 20;
+connection con1;
+commit;
+connection con2;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_snapshot_conflict_errors';
+variable_value-@a
+1
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test where id = 1;
+id value
+1 10
+select * from test where id = 2;
+id value
+2 20
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+select * from test where id = 2;
+id value
+2 20
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value % 5 = 0;
+id value
+1 10
+2 20
+connection con2;
+update test set value = 12 where value = 10;
+commit;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id = 1;
+id value
+1 10
+connection con2;
+select * from test;
+id value
+1 10
+2 20
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+delete from test where value = 20;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where id in (1,2);
+id value
+1 10
+2 20
+connection con2;
+select * from test where id in (1,2);
+id value
+1 10
+2 20
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 21 where id = 2;
+connection con1;
+commit;
+connection con2;
+commit;
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
+connection con1;
+select * from test where value % 3 = 0;
+id value
+connection con2;
+select * from test where value % 3 = 0;
+id value
+connection con1;
+insert into test (id, value) values(3, 30);
+connection con2;
+insert into test (id, value) values(4, 42);
+connection con1;
+commit;
+connection con2;
+commit;
+select * from test where value % 3 = 0;
+id value
+3 30
+4 42
+connection con1;
+select * from test where value % 3 = 0;
+id value
+3 30
+4 42
+connection default;
+drop table test;
+disconnect con1;
+disconnect con2;
+disconnect con3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/i_s.result b/storage/rocksdb/mysql-test/rocksdb/r/i_s.result
new file mode 100644
index 00000000..84671b76
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/i_s.result
@@ -0,0 +1,159 @@
+SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
+WHERE TABLE_SCHEMA='INFORMATION_SCHEMA'
+ AND TABLE_NAME LIKE 'ROCKSDB%'
+ ORDER BY TABLE_NAME;
+TABLE_NAME
+ROCKSDB_CFSTATS
+ROCKSDB_CF_OPTIONS
+ROCKSDB_COMPACTION_STATS
+ROCKSDB_DBSTATS
+ROCKSDB_DDL
+ROCKSDB_DEADLOCK
+ROCKSDB_GLOBAL_INFO
+ROCKSDB_INDEX_FILE_MAP
+ROCKSDB_LOCKS
+ROCKSDB_PERF_CONTEXT
+ROCKSDB_PERF_CONTEXT_GLOBAL
+ROCKSDB_SST_PROPS
+ROCKSDB_TRX
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_CF_OPTIONS;
+Table Create Table
+ROCKSDB_CF_OPTIONS CREATE TEMPORARY TABLE `ROCKSDB_CF_OPTIONS` (
+ `CF_NAME` varchar(193) NOT NULL DEFAULT '',
+ `OPTION_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `VALUE` varchar(193) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_CFSTATS;
+Table Create Table
+ROCKSDB_CFSTATS CREATE TEMPORARY TABLE `ROCKSDB_CFSTATS` (
+ `CF_NAME` varchar(193) NOT NULL DEFAULT '',
+ `STAT_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `VALUE` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS;
+Table Create Table
+ROCKSDB_COMPACTION_STATS CREATE TEMPORARY TABLE `ROCKSDB_COMPACTION_STATS` (
+ `CF_NAME` varchar(193) NOT NULL DEFAULT '',
+ `LEVEL` varchar(513) NOT NULL DEFAULT '',
+ `TYPE` varchar(513) NOT NULL DEFAULT '',
+ `VALUE` double NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DBSTATS;
+Table Create Table
+ROCKSDB_DBSTATS CREATE TEMPORARY TABLE `ROCKSDB_DBSTATS` (
+ `STAT_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `VALUE` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DDL;
+Table Create Table
+ROCKSDB_DDL CREATE TEMPORARY TABLE `ROCKSDB_DDL` (
+ `TABLE_SCHEMA` varchar(193) NOT NULL DEFAULT '',
+ `TABLE_NAME` varchar(193) NOT NULL DEFAULT '',
+ `PARTITION_NAME` varchar(193) DEFAULT NULL,
+ `INDEX_NAME` varchar(193) NOT NULL DEFAULT '',
+ `COLUMN_FAMILY` int(11) NOT NULL DEFAULT 0,
+ `INDEX_NUMBER` int(11) NOT NULL DEFAULT 0,
+ `INDEX_TYPE` smallint(6) NOT NULL DEFAULT 0,
+ `KV_FORMAT_VERSION` smallint(6) NOT NULL DEFAULT 0,
+ `TTL_DURATION` bigint(21) NOT NULL DEFAULT 0,
+ `INDEX_FLAGS` bigint(21) NOT NULL DEFAULT 0,
+ `CF` varchar(193) NOT NULL DEFAULT '',
+ `AUTO_INCREMENT` bigint(21) unsigned DEFAULT NULL
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DEADLOCK;
+Table Create Table
+ROCKSDB_DEADLOCK CREATE TEMPORARY TABLE `ROCKSDB_DEADLOCK` (
+ `DEADLOCK_ID` bigint(21) NOT NULL DEFAULT 0,
+ `TIMESTAMP` bigint(21) NOT NULL DEFAULT 0,
+ `TRANSACTION_ID` bigint(21) NOT NULL DEFAULT 0,
+ `CF_NAME` varchar(193) NOT NULL DEFAULT '',
+ `WAITING_KEY` varchar(513) NOT NULL DEFAULT '',
+ `LOCK_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `INDEX_NAME` varchar(193) NOT NULL DEFAULT '',
+ `TABLE_NAME` varchar(193) NOT NULL DEFAULT '',
+ `ROLLED_BACK` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+Table Create Table
+ROCKSDB_GLOBAL_INFO CREATE TEMPORARY TABLE `ROCKSDB_GLOBAL_INFO` (
+ `TYPE` varchar(513) NOT NULL DEFAULT '',
+ `NAME` varchar(513) NOT NULL DEFAULT '',
+ `VALUE` varchar(513) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP;
+Table Create Table
+ROCKSDB_INDEX_FILE_MAP CREATE TEMPORARY TABLE `ROCKSDB_INDEX_FILE_MAP` (
+ `COLUMN_FAMILY` int(11) NOT NULL DEFAULT 0,
+ `INDEX_NUMBER` int(11) NOT NULL DEFAULT 0,
+ `SST_NAME` varchar(193) NOT NULL DEFAULT '',
+ `NUM_ROWS` bigint(21) NOT NULL DEFAULT 0,
+ `DATA_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `ENTRY_DELETES` bigint(21) NOT NULL DEFAULT 0,
+ `ENTRY_SINGLEDELETES` bigint(21) NOT NULL DEFAULT 0,
+ `ENTRY_MERGES` bigint(21) NOT NULL DEFAULT 0,
+ `ENTRY_OTHERS` bigint(21) NOT NULL DEFAULT 0,
+ `DISTINCT_KEYS_PREFIX` varchar(800) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_LOCKS;
+Table Create Table
+ROCKSDB_LOCKS CREATE TEMPORARY TABLE `ROCKSDB_LOCKS` (
+ `COLUMN_FAMILY_ID` int(11) NOT NULL DEFAULT 0,
+ `TRANSACTION_ID` int(11) NOT NULL DEFAULT 0,
+ `KEY` varchar(513) NOT NULL DEFAULT '',
+ `MODE` varchar(32) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT;
+Table Create Table
+ROCKSDB_PERF_CONTEXT CREATE TEMPORARY TABLE `ROCKSDB_PERF_CONTEXT` (
+ `TABLE_SCHEMA` varchar(193) NOT NULL DEFAULT '',
+ `TABLE_NAME` varchar(193) NOT NULL DEFAULT '',
+ `PARTITION_NAME` varchar(193) DEFAULT NULL,
+ `STAT_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `VALUE` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL;
+Table Create Table
+ROCKSDB_PERF_CONTEXT_GLOBAL CREATE TEMPORARY TABLE `ROCKSDB_PERF_CONTEXT_GLOBAL` (
+ `STAT_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `VALUE` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_SST_PROPS;
+Table Create Table
+ROCKSDB_SST_PROPS CREATE TEMPORARY TABLE `ROCKSDB_SST_PROPS` (
+ `SST_NAME` varchar(193) NOT NULL DEFAULT '',
+ `COLUMN_FAMILY` int(11) NOT NULL DEFAULT 0,
+ `DATA_BLOCKS` bigint(21) NOT NULL DEFAULT 0,
+ `ENTRIES` bigint(21) NOT NULL DEFAULT 0,
+ `RAW_KEY_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `RAW_VALUE_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `DATA_BLOCK_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `INDEX_BLOCK_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `INDEX_PARTITIONS` int(11) NOT NULL DEFAULT 0,
+ `TOP_LEVEL_INDEX_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `FILTER_BLOCK_SIZE` bigint(21) NOT NULL DEFAULT 0,
+ `COMPRESSION_ALGO` varchar(193) NOT NULL DEFAULT '',
+ `CREATION_TIME` bigint(21) NOT NULL DEFAULT 0,
+ `FILE_CREATION_TIME` bigint(21) NOT NULL DEFAULT 0,
+ `OLDEST_KEY_TIME` bigint(21) NOT NULL DEFAULT 0,
+ `FILTER_POLICY` varchar(193) NOT NULL DEFAULT '',
+ `COMPRESSION_OPTIONS` varchar(193) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_TRX;
+Table Create Table
+ROCKSDB_TRX CREATE TEMPORARY TABLE `ROCKSDB_TRX` (
+ `TRANSACTION_ID` bigint(21) NOT NULL DEFAULT 0,
+ `STATE` varchar(193) NOT NULL DEFAULT '',
+ `NAME` varchar(193) NOT NULL DEFAULT '',
+ `WRITE_COUNT` bigint(21) NOT NULL DEFAULT 0,
+ `LOCK_COUNT` bigint(21) NOT NULL DEFAULT 0,
+ `TIMEOUT_SEC` int(11) NOT NULL DEFAULT 0,
+ `WAITING_KEY` varchar(513) NOT NULL DEFAULT '',
+ `WAITING_COLUMN_FAMILY_ID` int(11) NOT NULL DEFAULT 0,
+ `IS_REPLICATION` int(11) NOT NULL DEFAULT 0,
+ `SKIP_TRX_API` int(11) NOT NULL DEFAULT 0,
+ `READ_ONLY` int(11) NOT NULL DEFAULT 0,
+ `HAS_DEADLOCK_DETECTION` int(11) NOT NULL DEFAULT 0,
+ `NUM_ONGOING_BULKLOAD` int(11) NOT NULL DEFAULT 0,
+ `THREAD_ID` int(11) NOT NULL DEFAULT 0,
+ `QUERY` varchar(193) NOT NULL DEFAULT ''
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/i_s_ddl.result b/storage/rocksdb/mysql-test/rocksdb/r/i_s_ddl.result
new file mode 100644
index 00000000..6bca2cba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/i_s_ddl.result
@@ -0,0 +1,22 @@
+DROP TABLE IF EXISTS is_ddl_t1;
+DROP TABLE IF EXISTS is_ddl_t2;
+DROP TABLE IF EXISTS is_ddl_t3;
+CREATE TABLE is_ddl_t1 (i INT, j INT, k INT, l INT,
+PRIMARY KEY (i), KEY (j), KEY (k, l) COMMENT 'kl_cf')
+ENGINE = ROCKSDB;
+CREATE TABLE is_ddl_t2 (x INT, y INT, z INT,
+PRIMARY KEY (z, y) COMMENT 'zy_cf',
+KEY (x)) ENGINE = ROCKSDB;
+CREATE TABLE is_ddl_t3 (a INT, b INT, c INT, PRIMARY KEY (a)) ENGINE = ROCKSDB
+COMMENT "ttl_duration=3600;";
+SELECT TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,INDEX_NAME,INDEX_TYPE,KV_FORMAT_VERSION,CF,TTL_DURATION,INDEX_FLAGS FROM INFORMATION_SCHEMA.ROCKSDB_DDL WHERE TABLE_NAME like 'is_ddl_t%';
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME INDEX_NAME INDEX_TYPE KV_FORMAT_VERSION CF TTL_DURATION INDEX_FLAGS
+test is_ddl_t1 NULL PRIMARY 1 13 default 0 0
+test is_ddl_t1 NULL j 2 13 default 0 0
+test is_ddl_t1 NULL k 2 13 kl_cf 0 0
+test is_ddl_t2 NULL PRIMARY 1 13 zy_cf 0 0
+test is_ddl_t2 NULL x 2 13 default 0 0
+test is_ddl_t3 NULL PRIMARY 1 13 default 3600 1
+DROP TABLE is_ddl_t1;
+DROP TABLE is_ddl_t2;
+DROP TABLE is_ddl_t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/i_s_deadlock.result b/storage/rocksdb/mysql-test/rocksdb/r/i_s_deadlock.result
new file mode 100644
index 00000000..0419c0f3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/i_s_deadlock.result
@@ -0,0 +1,216 @@
+set @prior_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_deadlock_detect = @@rocksdb_deadlock_detect;
+set @prior_max_latest_deadlocks = @@rocksdb_max_latest_deadlocks;
+set global rocksdb_deadlock_detect = on;
+set global rocksdb_lock_wait_timeout = 10000;
+# Clears deadlock buffer of any prior deadlocks.
+set global rocksdb_max_latest_deadlocks = 0;
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connect con3,localhost,root,,;
+connection default;
+show create table information_schema.rocksdb_deadlock;
+Table Create Table
+ROCKSDB_DEADLOCK CREATE TEMPORARY TABLE `ROCKSDB_DEADLOCK` (
+ `DEADLOCK_ID` bigint(21) NOT NULL DEFAULT 0,
+ `TIMESTAMP` bigint(21) NOT NULL DEFAULT 0,
+ `TRANSACTION_ID` bigint(21) NOT NULL DEFAULT 0,
+ `CF_NAME` varchar(193) NOT NULL DEFAULT '',
+ `WAITING_KEY` varchar(513) NOT NULL DEFAULT '',
+ `LOCK_TYPE` varchar(193) NOT NULL DEFAULT '',
+ `INDEX_NAME` varchar(193) NOT NULL DEFAULT '',
+ `TABLE_NAME` varchar(193) NOT NULL DEFAULT '',
+ `ROLLED_BACK` bigint(21) NOT NULL DEFAULT 0
+) ENGINE=MEMORY DEFAULT CHARSET=utf8
+create table t (i int primary key) engine=rocksdb;
+insert into t values (1), (2), (3);
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+Deadlock #1
+connection con1;
+begin;
+select * from t where i=1 for update;
+i
+1
+connection con2;
+begin;
+select * from t where i=2 for update;
+i
+2
+connection con1;
+select * from t where i=2 for update;
+connection con2;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con1;
+i
+2
+rollback;
+connection default;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+Deadlock #2
+connection con1;
+begin;
+select * from t where i=1 for update;
+i
+1
+connection con2;
+begin;
+select * from t where i=2 for update;
+i
+2
+connection con1;
+select * from t where i=2 for update;
+connection con2;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con1;
+i
+2
+rollback;
+connection default;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+set global rocksdb_max_latest_deadlocks = 10;
+Deadlock #3
+connection con1;
+begin;
+select * from t where i=1 for update;
+i
+1
+connection con2;
+begin;
+select * from t where i=2 for update;
+i
+2
+connection con1;
+select * from t where i=2 for update;
+connection con2;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con1;
+i
+2
+rollback;
+connection default;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+set global rocksdb_max_latest_deadlocks = 1;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 1
+connection con3;
+set rocksdb_deadlock_detect_depth = 2;
+Deadlock #4
+connection con1;
+begin;
+select * from t where i=1 for update;
+i
+1
+connection con2;
+begin;
+select * from t where i=2 for update;
+i
+2
+connection con3;
+begin;
+select * from t where i=3 for update;
+i
+3
+connection con1;
+select * from t where i=2 for update;
+connection con2;
+select * from t where i=3 for update;
+connection con3;
+select * from t where i=1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con2;
+i
+3
+rollback;
+connection con1;
+i
+2
+rollback;
+connection default;
+set global rocksdb_max_latest_deadlocks = 5;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+Deadlock #5
+connection con1;
+begin;
+select * from t where i=1 for update;
+i
+1
+connection con2;
+begin;
+select * from t where i=2 for update;
+i
+2
+connection con3;
+begin;
+select * from t where i=3 lock in share mode;
+i
+3
+connection con1;
+select * from t where i=100 for update;
+i
+select * from t where i=101 for update;
+i
+select * from t where i=2 for update;
+connection con2;
+select * from t where i=3 lock in share mode;
+i
+3
+select * from t where i=200 for update;
+i
+select * from t where i=201 for update;
+i
+select * from t where i=1 lock in share mode;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con1;
+i
+2
+rollback;
+connection con3;
+rollback;
+connection default;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE PRIMARY test.t 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY SHARED PRIMARY test.t 1
+disconnect con1;
+disconnect con2;
+disconnect con3;
+set global rocksdb_lock_wait_timeout = @prior_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_deadlock_detect;
+drop table t;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY EXCLUSIVE INDEX_NAME TABLE_NAME 0
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID default WAITING_KEY SHARED INDEX_NAME TABLE_NAME 1
+set global rocksdb_max_latest_deadlocks = 0;
+# Clears deadlock buffer of any existent deadlocks.
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+select * from information_schema.rocksdb_deadlock;
+DEADLOCK_ID TIMESTAMP TRANSACTION_ID CF_NAME WAITING_KEY LOCK_TYPE INDEX_NAME TABLE_NAME ROLLED_BACK
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index.result b/storage/rocksdb/mysql-test/rocksdb/r/index.result
new file mode 100644
index 00000000..0920d0e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index.result
@@ -0,0 +1,89 @@
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY (a)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY a_b (a,b) COMMENT 'a_b index'
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a_b 1 a A # NULL NULL YES LSMTREE a_b index
+t1 1 a_b 2 b A # NULL NULL YES LSMTREE a_b index
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY (a),
+KEY (b)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+t1 1 b 1 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'z');
+ALTER TABLE t1 ADD KEY (a) COMMENT 'simple index on a';
+SHOW INDEX FROM t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE simple index on a
+ALTER TABLE t1 DROP KEY a;
+DROP TABLE t1;
+set global rocksdb_large_prefix=0;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(768))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 767 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3073))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 3072 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=DEFAULT;
+#
+# Issue #376: MyRocks: ORDER BY optimizer is unable to use the index extension
+#
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int);
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int not null,
+a int not null,
+b int not null,
+primary key(pk),
+key(a)
+) engine=rocksdb;
+insert into t2 select A.a, FLOOR(A.a/10), A.a from t1 A;
+# This must have type=range, index=a, and must not have 'Using filesort':
+explain select * from t2 force index (a) where a=0 and pk>=3 order by pk;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 8 NULL # Using index condition
+drop table t0,t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_file_map.result b/storage/rocksdb/mysql-test/rocksdb/r/index_file_map.result
new file mode 100644
index 00000000..ad007d71
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_file_map.result
@@ -0,0 +1,31 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t1 (i INT PRIMARY KEY, j INT, INDEX(j)) ENGINE = ROCKSDB;
+CREATE TABLE t2 (k INT PRIMARY KEY, l INT REFERENCES t1.i) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1,2), (2,4), (3,6), (4,8), (5,10);
+INSERT INTO t2 VALUES (100,1), (200,2), (300,3), (400,4);
+COMMIT;
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+(SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+WHERE TABLE_NAME = 't1' AND INDEX_NAME = "PRIMARY");
+COLUMN_FAMILY INDEX_NUMBER SST_NAME NUM_ROWS DATA_SIZE ENTRY_DELETES ENTRY_SINGLEDELETES ENTRY_MERGES ENTRY_OTHERS DISTINCT_KEYS_PREFIX
+# # SSTNAME 5 # # # # # 5
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+(SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+WHERE TABLE_NAME = 't1' AND INDEX_NAME = "j");
+COLUMN_FAMILY INDEX_NUMBER SST_NAME NUM_ROWS DATA_SIZE ENTRY_DELETES ENTRY_SINGLEDELETES ENTRY_MERGES ENTRY_OTHERS DISTINCT_KEYS_PREFIX
+# # SSTNAME 5 # # # # # 5,5
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+(SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+WHERE TABLE_NAME = 't2' AND INDEX_NAME = "PRIMARY");
+COLUMN_FAMILY INDEX_NUMBER SST_NAME NUM_ROWS DATA_SIZE ENTRY_DELETES ENTRY_SINGLEDELETES ENTRY_MERGES ENTRY_OTHERS DISTINCT_KEYS_PREFIX
+# # SSTNAME 4 # # # # # 4
+select count(*) > 0 from information_schema.rocksdb_sst_props;
+count(*) > 0
+1
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_key_block_size.result b/storage/rocksdb/mysql-test/rocksdb/r/index_key_block_size.result
new file mode 100644
index 00000000..5b804828
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_key_block_size.result
@@ -0,0 +1,53 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY (a) KEY_BLOCK_SIZE=8
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY ind1(b ASC) KEY_BLOCK_SIZE=0
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 ind1 1 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+PRIMARY KEY ind2(b(1) DESC) KEY_BLOCK_SIZE=32768 COMMENT 'big key_block_size value'
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'ind2' ignored for PRIMARY key.
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A # 1 NULL LSMTREE big key_block_size value
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT AUTO_INCREMENT PRIMARY KEY,
+KEY a_b(a,b) KEY_BLOCK_SIZE=8192
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a_b 1 a A # NULL NULL YES LSMTREE
+t1 1 a_b 2 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'z');
+ALTER TABLE t1 ADD KEY(a) KEY_BLOCK_SIZE 8192;
+SHOW INDEX FROM t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb.result b/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb.result
new file mode 100644
index 00000000..7c4f57b6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb.result
@@ -0,0 +1,49 @@
+CREATE TABLE t1
+(
+/* fields/keys for row retrieval tests */
+key1 INT,
+key2 INT,
+key3 INT,
+key4 INT,
+/* make rows much bigger then keys */
+filler1 CHAR(200),
+KEY(key1),
+KEY(key2)
+) ENGINE=ROCKSDB;
+CREATE TABLE t0 AS SELECT * FROM t1;
+# Printing of many insert into t0 values (....) disabled.
+# Printing of many insert into t1 select .... from t0 disabled.
+# Printing of many insert into t1 (...) values (....) disabled.
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+7201
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+EXPLAIN UPDATE t1 SET filler1='to be deleted' WHERE key1=100 AND key2=100;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key1,key2 5,5 NULL # Using intersect(key1,key2); Using where
+UPDATE t1 SET filler1='to be deleted' WHERE key1=100 and key2=100;
+DROP TABLE t0, t1;
+create table t1 (key1 int, key2 int, key3 int, key (key1), key (key2), key(key3)) engine=rocksdb;
+insert into t1 values (1, 100, 100), (1, 200, 200), (1, 300, 300);
+set global rocksdb_force_flush_memtable_now=1;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+explain select * from t1 where key1 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 5 const #
+explain select key1,key2 from t1 where key1 = 1 or key2 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key1,key2 5,5 NULL # Using union(key1,key2); Using where
+select * from t1 where key1 = 1;
+key1 key2 key3
+1 100 100
+1 200 200
+1 300 300
+select key1,key2 from t1 where key1 = 1 or key2 = 1;
+key1 key2
+1 100
+1 200
+1 300
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb2.result b/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb2.result
new file mode 100644
index 00000000..d96c4012
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_merge_rocksdb2.result
@@ -0,0 +1,1418 @@
+set global rocksdb_force_flush_memtable_now=1;
+#---------------- Index merge test 1 -------------------------------------------
+SET SESSION DEFAULT_STORAGE_ENGINE = RocksDB;
+drop table if exists t0, t1, t2, t3, t4;
+create table t0
+(
+key1 int not null,
+key2 int not null,
+key3 int not null,
+key4 int not null,
+key5 int not null,
+key6 int not null,
+key7 int not null,
+key8 int not null,
+INDEX i1(key1),
+INDEX i2(key2),
+INDEX i3(key3),
+INDEX i4(key4),
+INDEX i5(key5),
+INDEX i6(key6),
+INDEX i7(key7),
+INDEX i8(key8)
+);
+analyze table t0;
+Table Op Msg_type Msg_text
+test.t0 analyze status Engine-independent statistics collected
+test.t0 analyze status OK
+explain select * from t0 where key1 < 3 or key1 > 1020;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 range i1 i1 4 NULL 4 Using index condition
+explain
+select * from t0 where key1 < 3 or key2 > 1020;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t0 where key1 < 3 or key2 > 1020;
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 1023
+2 2 2 2 2 2 2 1022
+1021 1021 1021 1021 1021 1021 1021 3
+1022 1022 1022 1022 1022 1022 1022 2
+1023 1023 1023 1023 1023 1023 1023 1
+1024 1024 1024 1024 1024 1024 1024 0
+explain select * from t0 where key1 < 2 or key2 <3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain
+select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40);
+key1 key2 key3 key4 key5 key6 key7 key8
+31 31 31 31 31 31 31 993
+32 32 32 32 32 32 32 992
+33 33 33 33 33 33 33 991
+34 34 34 34 34 34 34 990
+35 35 35 35 35 35 35 989
+36 36 36 36 36 36 36 988
+37 37 37 37 37 37 37 987
+38 38 38 38 38 38 38 986
+39 39 39 39 39 39 39 985
+explain select * from t0 ignore index (i2) where key1 < 3 or key2 <4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 ALL i1 NULL NULL NULL # Using where
+explain select * from t0 where (key1 < 3 or key2 <4) and key3 = 50;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 use index (i1,i2) where (key1 < 2 or key2 <3) and key3 = 50;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where (key1 > 1 or key2 > 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 force index (i1,i2) where (key1 > 1 or key2 > 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain
+select * from t0 where key1<2 or key2<3 or (key1>5 and key1<7) or
+(key1>10 and key1<12) or (key2>100 and key2<102);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where key2 = 45 or key1 <=> null;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 range i1,i2 i2 4 NULL # Using index condition
+explain select * from t0 where key2 = 45 or key1 is not null;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 ALL i1,i2 NULL NULL NULL # Using where
+explain select * from t0 where key2 = 45 or key1 is null;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 ref i2 i2 4 const #
+explain select * from t0 where key2=10 or key3=3 or key4 <=> null;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i2,i3,i4 i2,i3 4,4 NULL # Using union(i2,i3); Using where
+explain select * from t0 where key2=10 or key3=3 or key4 is null;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i2,i3 i2,i3 4,4 NULL # Using union(i2,i3); Using where
+explain select key1 from t0 where (key1 <=> null) or (key2 < 2) or
+(key3=10) or (key4 <=> null);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i4 i2,i3 4,4 NULL # Using sort_union(i2,i3); Using where
+explain select key1 from t0 where (key1 <=> null) or (key1 < 5) or
+(key3=10) or (key4 <=> null);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i3,i4 i1,i3 4,4 NULL # Using sort_union(i1,i3); Using where
+explain select * from t0 where
+(key1 < 2 or key2 < 2) and (key3 < 3 or key4 < 3) and (key5 < 5 or key6 < 5);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i4,i5,i6 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain
+select * from t0 where (key1 < 2 or key2 < 4) and (key1 < 5 or key3 < 3);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+select * from t0 where (key1 < 2 or key2 < 4) and (key1 < 5 or key3 < 3);
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 1023
+2 2 2 2 2 2 2 1022
+3 3 3 3 3 3 3 1021
+explain select * from t0 where
+(key1 < 3 or key2 < 2) and (key3 < 3 or key4 < 3) and (key5 < 2 or key6 < 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i4,i5,i6 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where
+(key1 < 3 or key2 < 3) and (key3 < 70);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where
+(key1 < 3 or key2 < 3) and (key3 < 1000);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where
+((key1 < 3 or key2 < 3) and (key2 <4 or key3 < 3))
+or
+key2 > 4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+explain select * from t0 where
+((key1 < 4 or key2 < 4) and (key2 <4 or key3 < 3))
+or
+key1 < 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+select * from t0 where
+((key1 < 4 or key2 < 4) and (key2 <4 or key3 < 3))
+or
+key1 < 5;
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 1023
+2 2 2 2 2 2 2 1022
+3 3 3 3 3 3 3 1021
+4 4 4 4 4 4 4 1020
+explain select * from t0 where
+((key1 < 2 or key2 < 2) and (key3 <4 or key5 < 3))
+or
+((key5 < 3 or key6 < 3) and (key7 <3 or key8 < 3));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7,i8 i1,i2,i5,i6 4,4,4,4 NULL # Using sort_union(i1,i2,i5,i6); Using where
+explain select * from t0 where
+((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 3))
+or
+((key7 <5 or key8 < 3) and (key5 < 4 or key6 < 4));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7,i8 i3,i5,i7,i8 4,4,4,4 NULL # Using sort_union(i3,i5,i7,i8); Using where
+explain select * from t0 where
+((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 4))
+or
+((key3 <4 or key5 < 2) and (key5 < 5 or key6 < 3));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 4,4 NULL # Using sort_union(i3,i5); Using where
+explain select * from t0 where
+((key3 <4 or key5 < 3) and (key1 < 3 or key2 < 3))
+or
+(((key3 <5 and key7 < 5) or key5 < 2) and (key5 < 4 or key6 < 4));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7 i3,i5 4,4 NULL # Using sort_union(i3,i5); Using where
+explain select * from t0 where
+((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4))
+or
+((key3 >5 or key5 < 2) and (key5 < 5 or key6 < 6));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 4,4 NULL # Using sort_union(i3,i5); Using where
+explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where
+((key3 <3 or key5 < 4) and (key1 < 3 or key2 < 3))
+or
+((key3 >4 or key5 < 2) and (key5 < 5 or key6 < 4));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 4,4 NULL # Using sort_union(i3,i5); Using where
+explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where
+((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4))
+or
+((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 0,4 NULL # Using sort_union(i3,i5); Using where
+select * from t0 where key1 < 3 or key8 < 2 order by key1;
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 1023
+2 2 2 2 2 2 2 1022
+1023 1023 1023 1023 1023 1023 1023 1
+1024 1024 1024 1024 1024 1024 1024 0
+explain
+select * from t0 where key1 < 3 or key8 < 2 order by key1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i8 i1,i8 4,4 NULL # Using sort_union(i1,i8); Using where; Using filesort
+create table t2 like t0;
+insert into t2 select * from t0;
+alter table t2 add index i1_3(key1, key3);
+alter table t2 add index i2_3(key2, key3);
+alter table t2 drop index i1;
+alter table t2 drop index i2;
+alter table t2 add index i321(key3, key2, key1);
+explain select key3 from t2 where key1 = 100 or key2 = 100;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index_merge i1_3,i2_3 i1_3,i2_3 4,4 NULL # Using sort_union(i1_3,i2_3); Using where
+explain select key3 from t2 where key1 <100 or key2 < 100;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index_merge i1_3,i2_3 i1_3,i2_3 4,4 NULL # Using sort_union(i1_3,i2_3); Using where
+explain select key7 from t2 where key1 <100 or key2 < 100;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index_merge i1_3,i2_3 i1_3,i2_3 4,4 NULL # Using sort_union(i1_3,i2_3); Using where
+create table t4 (
+key1a int not null,
+key1b int not null,
+key2 int not null,
+key2_1 int not null,
+key2_2 int not null,
+key3 int not null,
+index i1a (key1a, key1b),
+index i1b (key1b, key1a),
+index i2_1(key2, key2_1),
+index i2_2(key2, key2_1)
+);
+Warnings:
+Note 1831 Duplicate index `i2_2`. This is deprecated and will be disallowed in a future release
+insert into t4 select key1,key1,key1 div 10, key1 % 10, key1 % 10, key1 from t0;
+select * from t4 where key1a = 3 or key1b = 4;
+key1a key1b key2 key2_1 key2_2 key3
+3 3 0 3 3 3
+4 4 0 4 4 4
+explain select * from t4 where key1a = 3 or key1b = 4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 index_merge i1a,i1b i1a,i1b 4,4 NULL 4 Using sort_union(i1a,i1b); Using where
+explain select * from t4 where key2 = 1 and (key2_1 = 1 or key3 = 5);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref i2_1,i2_2 i2_1 4 const 2 Using where
+explain select * from t4 where key2 = 1 and (key2_1 = 1 or key2_2 = 5);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref i2_1,i2_2 i2_1 4 const 2 Using where
+explain select * from t4 where key2_1 = 1 or key2_2 = 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ALL NULL NULL NULL NULL # Using where
+create table t1 like t0;
+insert into t1 select * from t0;
+explain select * from t0 left join t1 on (t0.key1=t1.key1)
+where t0.key1=3 or t0.key2=4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using union(i1,i2); Using where
+1 SIMPLE t1 ref i1 i1 4 test.t0.key1 #
+select * from t0 left join t1 on (t0.key1=t1.key1)
+where t0.key1=3 or t0.key2=4;
+key1 key2 key3 key4 key5 key6 key7 key8 key1 key2 key3 key4 key5 key6 key7 key8
+3 3 3 3 3 3 3 1021 3 3 3 3 3 3 3 1021
+4 4 4 4 4 4 4 1020 4 4 4 4 4 4 4 1020
+explain
+select * from t0,t1 where (t0.key1=t1.key1) and ( t0.key1=3 or t0.key2=4);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL # Using union(i1,i2); Using where
+1 SIMPLE t1 ref i1 i1 4 test.t0.key1 #
+explain
+select * from t0,t1 where (t0.key1=t1.key1) and
+(t0.key1=3 or t0.key2<4) and t1.key1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 ref i1,i2 i1 4 const 2 Using where
+1 SIMPLE t1 ref i1 i1 4 const 2
+explain select * from t0,t1 where t0.key1 = 5 and
+(t1.key1 = t0.key1 or t1.key8 = t0.key1);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 ref i1 i1 4 const 2
+1 SIMPLE t1 index_merge i1,i8 i1,i8 4,4 NULL 4 Using union(i1,i8); Using where; Using join buffer (flat, BNL join)
+explain select * from t0,t1 where t0.key1 < 3 and
+(t1.key1 = t0.key1 or t1.key8 = t0.key1);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 range i1 i1 4 NULL # Using index condition
+1 SIMPLE t1 ALL i1,i8 NULL NULL NULL # Range checked for each record (index map: 0x81)
+explain select * from t1 where key1=3 or key2=4
+union select * from t1 where key1<4 or key3=5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 index_merge i1,i2 i1,i2 4,4 NULL 4 Using union(i1,i2); Using where
+2 UNION t1 index_merge i1,i3 i1,i3 4,4 NULL 4 Using sort_union(i1,i3); Using where
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+explain select * from (select * from t1 where key1 = 3 or key2 =3) as Z where key8 >5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge i1,i2,i8 i1,i2 4,4 NULL 4 Using union(i1,i2); Using where
+create table t3 like t0;
+insert into t3 select * from t0;
+alter table t3 add key9 int not null, add index i9(key9);
+alter table t3 add keyA int not null, add index iA(keyA);
+alter table t3 add keyB int not null, add index iB(keyB);
+alter table t3 add keyC int not null, add index iC(keyC);
+update t3 set key9=key1,keyA=key1,keyB=key1,keyC=key1;
+explain select * from t3 where
+key1=1 or key2=2 or key3=3 or key4=4 or
+key5=5 or key6=6 or key7=7 or key8=8 or
+key9=9 or keyA=10 or keyB=11 or keyC=12;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 index_merge i1,i2,i3,i4,i5,i6,i7,i8,i9,iA,iB,iC i1,i2,i3,i4,i5,i6,i7,i8,i9,iA,iB,iC 4,4,4,4,4,4,4,4,4,4,4,4 NULL 24 Using union(i1,i2,i3,i4,i5,i6,i7,i8,i9,iA,iB,iC); Using where
+select * from t3 where
+key1=1 or key2=2 or key3=3 or key4=4 or
+key5=5 or key6=6 or key7=7 or key8=8 or
+key9=9 or keyA=10 or keyB=11 or keyC=12;
+key1 key2 key3 key4 key5 key6 key7 key8 key9 keyA keyB keyC
+1 1 1 1 1 1 1 1023 1 1 1 1
+2 2 2 2 2 2 2 1022 2 2 2 2
+3 3 3 3 3 3 3 1021 3 3 3 3
+4 4 4 4 4 4 4 1020 4 4 4 4
+5 5 5 5 5 5 5 1019 5 5 5 5
+6 6 6 6 6 6 6 1018 6 6 6 6
+7 7 7 7 7 7 7 1017 7 7 7 7
+9 9 9 9 9 9 9 1015 9 9 9 9
+10 10 10 10 10 10 10 1014 10 10 10 10
+11 11 11 11 11 11 11 1013 11 11 11 11
+12 12 12 12 12 12 12 1012 12 12 12 12
+1016 1016 1016 1016 1016 1016 1016 8 1016 1016 1016 1016
+explain select * from t0 where key1 < 3 or key2 < 4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t0 where key1 < 3 or key2 < 4;
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 1023
+2 2 2 2 2 2 2 1022
+3 3 3 3 3 3 3 1021
+update t0 set key8=123 where key1 < 3 or key2 < 4;
+select * from t0 where key1 < 3 or key2 < 4;
+key1 key2 key3 key4 key5 key6 key7 key8
+1 1 1 1 1 1 1 123
+2 2 2 2 2 2 2 123
+3 3 3 3 3 3 3 123
+delete from t0 where key1 < 3 or key2 < 4;
+select * from t0 where key1 < 3 or key2 < 4;
+key1 key2 key3 key4 key5 key6 key7 key8
+select count(*) from t0;
+count(*)
+1021
+drop table t4;
+create table t4 (a int);
+insert into t4 values (1),(4),(3);
+set @save_join_buffer_size=@@join_buffer_size;
+set join_buffer_size= 4096;
+explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+where (A.key1 < 500000 or A.key2 < 3)
+and (B.key1 < 500000 or B.key2 < 3);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE A index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where
+1 SIMPLE B index_merge i1,i2 i1,i2 4,4 NULL # Using sort_union(i1,i2); Using where; Using join buffer (flat, BNL join)
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+where (A.key1 < 500000 or A.key2 < 3)
+and (B.key1 < 500000 or B.key2 < 3);
+max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+10240
+update t0 set key1=1;
+explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+where (A.key1 = 1 or A.key2 = 1)
+and (B.key1 = 1 or B.key2 = 1);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE A index_merge i1,i2 i1,i2 4,4 NULL # Using union(i1,i2); Using where
+1 SIMPLE B index_merge i1,i2 i1,i2 4,4 NULL # Using union(i1,i2); Using where; Using join buffer (flat, BNL join)
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+from t0 as A force index(i1,i2), t0 as B force index (i1,i2)
+where (A.key1 = 1 or A.key2 = 1)
+and (B.key1 = 1 or B.key2 = 1);
+max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+8194
+alter table t0 add filler1 char(200), add filler2 char(200), add filler3 char(200);
+update t0 set key2=1, key3=1, key4=1, key5=1,key6=1,key7=1 where key7 < 500;
+select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+from t0 as A, t0 as B
+where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1)
+and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1);
+max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5)
+8186
+set join_buffer_size= @save_join_buffer_size;
+drop table t0, t1, t2, t3, t4;
+CREATE TABLE t1 (
+cola char(3) not null, colb char(3) not null, filler char(200),
+key(cola), key(colb)
+);
+INSERT INTO t1 VALUES ('foo','bar', 'ZZ'),('fuz','baz', 'ZZ');
+OPTIMIZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+select count(*) from t1;
+count(*)
+8704
+explain select * from t1 WHERE cola = 'foo' AND colb = 'bar';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref cola,colb cola 3 const # Using index condition; Using where
+explain select * from t1 force index(cola,colb) WHERE cola = 'foo' AND colb = 'bar';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref cola,colb cola 3 const # Using index condition; Using where
+drop table t1;
+CREATE TABLE t1(a INT);
+INSERT INTO t1 VALUES(1);
+CREATE TABLE t2(a INT, b INT, dummy CHAR(16) DEFAULT '', KEY(a), KEY(b));
+INSERT INTO t2(a,b) VALUES
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),
+(1,2);
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t2(a,b) VALUES(1,2);
+SELECT t2.a FROM t1,t2 WHERE t2.b=2 AND t2.a=1;
+a
+1
+1
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE `t1` (
+`a` int(11) DEFAULT NULL,
+`filler` char(200) DEFAULT NULL,
+`b` int(11) DEFAULT NULL,
+KEY `a` (`a`),
+KEY `b` (`b`)
+) ENGINE=MEMORY DEFAULT CHARSET=latin1;
+insert into t1 values
+(0, 'filler', 0), (1, 'filler', 1), (2, 'filler', 2), (3, 'filler', 3),
+(4, 'filler', 4), (5, 'filler', 5), (6, 'filler', 6), (7, 'filler', 7),
+(8, 'filler', 8), (9, 'filler', 9), (0, 'filler', 0), (1, 'filler', 1),
+(2, 'filler', 2), (3, 'filler', 3), (4, 'filler', 4), (5, 'filler', 5),
+(6, 'filler', 6), (7, 'filler', 7), (8, 'filler', 8), (9, 'filler', 9),
+(10, 'filler', 10), (11, 'filler', 11), (12, 'filler', 12), (13, 'filler', 13),
+(14, 'filler', 14), (15, 'filler', 15), (16, 'filler', 16), (17, 'filler', 17),
+(18, 'filler', 18), (19, 'filler', 19), (4, '5 ', 0), (5, '4 ', 0),
+(4, '4 ', 0), (4, 'qq ', 5), (5, 'qq ', 4), (4, 'zz ', 4);
+create table t2(
+`a` int(11) DEFAULT NULL,
+`filler` char(200) DEFAULT NULL,
+`b` int(11) DEFAULT NULL,
+KEY USING BTREE (`a`),
+KEY USING BTREE (`b`)
+) ENGINE=MEMORY DEFAULT CHARSET=latin1;
+insert into t2 select * from t1;
+must use sort-union rather than union:
+explain select * from t1 where a=4 or b=4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge a,b a,b 5,5 NULL # Using sort_union(a,b); Using where
+select * from t1 where a=4 or b=4;
+a filler b
+4 4 0
+4 5 0
+4 filler 4
+4 filler 4
+4 qq 5
+4 zz 4
+5 qq 4
+select * from t1 ignore index(a,b) where a=4 or b=4;
+a filler b
+4 4 0
+4 5 0
+4 filler 4
+4 filler 4
+4 qq 5
+4 zz 4
+5 qq 4
+must use union, not sort-union:
+explain select * from t2 where a=4 or b=4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 index_merge a,b a,b 5,5 NULL # Using union(a,b); Using where
+select * from t2 where a=4 or b=4;
+a filler b
+4 4 0
+4 5 0
+4 filler 4
+4 filler 4
+4 qq 5
+4 zz 4
+5 qq 4
+drop table t1, t2;
+CREATE TABLE t1 (a varchar(8), b set('a','b','c','d','e','f','g','h'),
+KEY b(b), KEY a(a));
+INSERT INTO t1 VALUES ('y',''), ('z','');
+SELECT b,a from t1 WHERE (b!='c' AND b!='f' && b!='h') OR
+(a='pure-S') OR (a='DE80337a') OR (a='DE80799');
+b a
+ y
+ z
+DROP TABLE t1;
+#
+# BUG#40974: Incorrect query results when using clause evaluated using range check
+#
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (a int);
+insert into t1 values (1),(2);
+create table t2(a int, b int);
+insert into t2 values (1,1), (2, 1000);
+create table t3 (a int, b int, filler char(100), key(a), key(b));
+insert into t3 select 1000, 1000,'filler' from t0 A, t0 B, t0 C;
+insert into t3 values (1,1,'data');
+insert into t3 values (1,1,'data');
+The plan should be ALL/ALL/ALL(Range checked for each record (index map: 0x3)
+explain select * from t1
+where exists (select 1 from t2, t3
+where t2.a=t1.a and (t3.a=t2.b or t3.b=t2.b or t3.b=t2.b+1));
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 ALL NULL NULL NULL NULL #
+1 PRIMARY <subquery2> eq_ref distinct_key distinct_key 4 func #
+2 MATERIALIZED t2 ALL NULL NULL NULL NULL #
+2 MATERIALIZED t3 ALL a,b NULL NULL NULL # Range checked for each record (index map: 0x3)
+select * from t1
+where exists (select 1 from t2, t3
+where t2.a=t1.a and (t3.a=t2.b or t3.b=t2.b or t3.b=t2.b+1));
+a
+1
+2
+drop table t0, t1, t2, t3;
+#
+# BUG#44810: index merge and order by with low sort_buffer_size
+# crashes server!
+#
+CREATE TABLE t1(a VARCHAR(128),b VARCHAR(128),KEY(A),KEY(B));
+INSERT INTO t1 VALUES (REPEAT('a',128),REPEAT('b',128));
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+INSERT INTO t1 SELECT * FROM t1;
+EXPLAIN
+SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
+ORDER BY a,b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge a,b a,b 131,131 NULL # Using sort_union(a,b); Using where; Using filesort
+SELECT * FROM t1 FORCE INDEX(a,b) WHERE a LIKE 'a%' OR b LIKE 'b%'
+ORDER BY a,b;
+SET SESSION sort_buffer_size=DEFAULT;
+DROP TABLE t1;
+End of 5.0 tests
+set global rocksdb_force_flush_memtable_now=1;
+#---------------- ROR-index_merge tests -----------------------
+SET SESSION DEFAULT_STORAGE_ENGINE = RocksDB;
+drop table if exists t0,t1,t2;
+create table t1
+(
+/* Field names reflect value(rowid) distribution, st=STairs, swt= SaWTooth */
+st_a int not null default 0,
+swt1a int not null default 0,
+swt2a int not null default 0,
+st_b int not null default 0,
+swt1b int not null default 0,
+swt2b int not null default 0,
+/* fields/keys for row retrieval tests */
+key1 int,
+key2 int,
+key3 int,
+key4 int,
+/* make rows much bigger then keys */
+filler1 char (200),
+filler2 char (200),
+filler3 char (200),
+filler4 char (200),
+filler5 char (200),
+filler6 char (200),
+/* order of keys is important */
+key sta_swt12a(st_a,swt1a,swt2a),
+key sta_swt1a(st_a,swt1a),
+key sta_swt2a(st_a,swt2a),
+key sta_swt21a(st_a,swt2a,swt1a),
+key st_a(st_a),
+key stb_swt1a_2b(st_b,swt1b,swt2a),
+key stb_swt1b(st_b,swt1b),
+key st_b(st_b),
+key(key1),
+key(key2),
+key(key3),
+key(key4)
+) ;
+create table t0 as select * from t1;
+# Printing of many insert into t0 values (....) disabled.
+alter table t1 disable keys;
+Warnings:
+Note 1031 Storage engine ROCKSDB of the table `test`.`t1` doesn't have this option
+# Printing of many insert into t1 select .... from t0 disabled.
+# Printing of many insert into t1 (...) values (....) disabled.
+alter table t1 enable keys;
+Warnings:
+Note 1031 Storage engine ROCKSDB of the table `test`.`t1` doesn't have this option
+select count(*) from t1;
+count(*)
+64801
+explain select key1,key2 from t1 where key1=100 and key2=100;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key1,key2 5,5 NULL # Using intersect(key1,key2); Using where; Using index
+select key1,key2 from t1 where key1=100 and key2=100;
+key1 key2
+100 100
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+key1 key2 key3 key4 filler1
+100 100 100 100 key1-key2-key3-key4
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, -1, -1, 'key1-key2');
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 100, 100, 'key4-key3');
+select key1,key2,filler1 from t1 where key1=100 and key2=100;
+key1 key2 filler1
+100 100 key1-key2-key3-key4
+100 100 key1-key2
+select key1,key2 from t1 where key1=100 and key2=100;
+key1 key2
+100 100
+100 100
+select key1,key2,key3,key4 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+key1 key2 key3 key4
+100 100 100 100
+100 100 -1 -1
+-1 -1 100 100
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+key1 key2 key3 key4 filler1
+100 100 100 100 key1-key2-key3-key4
+100 100 -1 -1 key1-key2
+-1 -1 100 100 key4-key3
+select key1,key2,key3 from t1 where key1=100 and key2=100 and key3=100;
+key1 key2 key3
+100 100 100
+insert into t1 (key1,key2,key3,key4,filler1) values (101,101,101,101, 'key1234-101');
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=101;
+key1 key2 key3 key4 filler1
+100 100 100 100 key1-key2-key3-key4
+100 100 -1 -1 key1-key2
+101 101 101 101 key1234-101
+select key1,key2, filler1 from t1 where key1=100 and key2=100;
+key1 key2 filler1
+100 100 key1-key2-key3-key4
+100 100 key1-key2
+update t1 set filler1='to be deleted' where key1=100 and key2=100;
+update t1 set key1=200,key2=200 where key1=100 and key2=100;
+delete from t1 where key1=200 and key2=200;
+select key1,key2,filler1 from t1 where key2=100 and key2=200;
+key1 key2 filler1
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+key1 key2 key3 key4 filler1
+-1 -1 100 100 key4-key3
+delete from t1 where key3=100 and key4=100;
+select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
+key1 key2 key3 key4 filler1
+select key1,key2 from t1 where key1=100 and key2=100;
+key1 key2
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-1');
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-2');
+insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-3');
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+key1 key2 key3 key4 filler1
+100 100 200 200 key1-key2-key3-key4-1
+100 100 200 200 key1-key2-key3-key4-2
+100 100 200 200 key1-key2-key3-key4-3
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, -1, 200,'key4');
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+key1 key2 key3 key4 filler1
+100 100 200 200 key1-key2-key3-key4-1
+100 100 200 200 key1-key2-key3-key4-2
+100 100 200 200 key1-key2-key3-key4-3
+-1 -1 -1 200 key4
+insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 200, -1,'key3');
+select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
+key1 key2 key3 key4 filler1
+100 100 200 200 key1-key2-key3-key4-1
+100 100 200 200 key1-key2-key3-key4-2
+100 100 200 200 key1-key2-key3-key4-3
+-1 -1 -1 200 key4
+-1 -1 200 -1 key3
+drop table t0,t1;
+create table t2 (
+a char(10),
+b char(10),
+filler1 char(255),
+filler2 char(255),
+key(a(5)),
+key(b(5))
+);
+select count(a) from t2 where a='BBBBBBBB';
+count(a)
+4
+select count(a) from t2 where b='BBBBBBBB';
+count(a)
+4
+expla_or_bin select count(a_or_b) from t2 where a_or_b='AAAAAAAA' a_or_bnd a_or_b='AAAAAAAA';
+id select_type ta_or_ba_or_ble type possia_or_ble_keys key key_len ref rows Extra_or_b
+1 SIMPLE t2 ref a_or_b,a_or_b a_or_b 6 const 2 Using where
+select count(a) from t2 where a='AAAAAAAA' and b='AAAAAAAA';
+count(a)
+4
+select count(a) from t2 ignore index(a,b) where a='AAAAAAAA' and b='AAAAAAAA';
+count(a)
+4
+insert into t2 values ('ab', 'ab', 'uh', 'oh');
+explain select a from t2 where a='ab';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref a a 6 const 2 Using where
+drop table t2;
+CREATE TABLE t1(c1 INT, c2 INT DEFAULT 0, c3 CHAR(255) DEFAULT '',
+KEY(c1), KEY(c2), KEY(c3));
+INSERT INTO t1(c1) VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
+(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0);
+INSERT INTO t1 VALUES(0,0,0);
+CREATE TABLE t2(c1 int);
+INSERT INTO t2 VALUES(1);
+DELETE t1 FROM t1,t2 WHERE t1.c1=0 AND t1.c2=0;
+SELECT * FROM t1;
+c1 c2 c3
+DROP TABLE t1,t2;
+set global rocksdb_force_flush_memtable_now=1;
+#---------------- Index merge test 2 -------------------------------------------
+SET SESSION DEFAULT_STORAGE_ENGINE = RocksDB;
+drop table if exists t1,t2;
+create table t1
+(
+key1 int not null,
+key2 int not null,
+INDEX i1(key1),
+INDEX i2(key2)
+);
+explain select * from t1 where key1 < 5 or key2 > 197;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t1 where key1 < 5 or key2 > 197;
+key1 key2
+0 200
+1 199
+2 198
+3 197
+4 196
+explain select * from t1 where key1 < 3 or key2 > 195;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t1 where key1 < 3 or key2 > 195;
+key1 key2
+0 200
+1 199
+2 198
+3 197
+4 196
+alter table t1 add str1 char (255) not null,
+add zeroval int not null default 0,
+add str2 char (255) not null,
+add str3 char (255) not null;
+update t1 set str1='aaa', str2='bbb', str3=concat(key2, '-', key1 div 2, '_' ,if(key1 mod 2 = 0, 'a', 'A'));
+alter table t1 add primary key (str1, zeroval, str2, str3);
+explain select * from t1 where key1 < 5 or key2 > 197;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t1 where key1 < 5 or key2 > 197;
+key1 key2 str1 zeroval str2 str3
+4 196 aaa 0 bbb 196-2_a
+3 197 aaa 0 bbb 197-1_A
+2 198 aaa 0 bbb 198-1_a
+1 199 aaa 0 bbb 199-0_A
+0 200 aaa 0 bbb 200-0_a
+explain select * from t1 where key1 < 3 or key2 > 195;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 4 Using sort_union(i1,i2); Using where
+select * from t1 where key1 < 3 or key2 > 195;
+key1 key2 str1 zeroval str2 str3
+4 196 aaa 0 bbb 196-2_a
+3 197 aaa 0 bbb 197-1_A
+2 198 aaa 0 bbb 198-1_a
+1 199 aaa 0 bbb 199-0_A
+0 200 aaa 0 bbb 200-0_a
+drop table t1;
+create table t1 (
+pk integer not null auto_increment primary key,
+key1 integer,
+key2 integer not null,
+filler char (200),
+index (key1),
+index (key2)
+);
+show warnings;
+Level Code Message
+explain select pk from t1 where key1 = 1 and key2 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key2,key1 4,5 NULL 1 Using intersect(key2,key1); Using where; Using index
+select pk from t1 where key2 = 1 and key1 = 1;
+pk
+26
+27
+select pk from t1 ignore index(key1,key2) where key2 = 1 and key1 = 1;
+pk
+26
+27
+drop table t1;
+create table t1 (
+pk int primary key auto_increment,
+key1a int,
+key2a int,
+key1b int,
+key2b int,
+dummy1 int,
+dummy2 int,
+dummy3 int,
+dummy4 int,
+key3a int,
+key3b int,
+filler1 char (200),
+index i1(key1a, key1b),
+index i2(key2a, key2b),
+index i3(key3a, key3b)
+);
+create table t2 (a int);
+insert into t2 values (0),(1),(2),(3),(4),(NULL);
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+select A.a, B.a, C.a, D.a, C.a, D.a from t2 A,t2 B,t2 C, t2 D;
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+select key1a, key1b, key2a, key2b, key3a, key3b from t1;
+insert into t1 (key1a, key1b, key2a, key2b, key3a, key3b)
+select key1a, key1b, key2a, key2b, key3a, key3b from t1;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+select count(*) from t1;
+count(*)
+5184
+select count(*) from t1 where
+key1a = 2 and key1b is null and key2a = 2 and key2b is null;
+count(*)
+4
+select count(*) from t1 where
+key1a = 2 and key1b is null and key3a = 2 and key3b is null;
+count(*)
+4
+drop table t1,t2;
+create table t1 (
+id1 int,
+id2 date ,
+index idx2 (id1,id2),
+index idx1 (id2)
+);
+insert into t1 values(1,'20040101'), (2,'20040102');
+select * from t1 where id1 = 1 and id2= '20040101';
+id1 id2
+1 2004-01-01
+drop table t1;
+drop view if exists v1;
+CREATE TABLE t1 (
+`oid` int(11) unsigned NOT NULL auto_increment,
+`fk_bbk_niederlassung` int(11) unsigned NOT NULL,
+`fk_wochentag` int(11) unsigned NOT NULL,
+`uhrzeit_von` time NOT NULL COMMENT 'HH:MM',
+`uhrzeit_bis` time NOT NULL COMMENT 'HH:MM',
+`geloescht` tinyint(4) NOT NULL,
+`version` int(5) NOT NULL,
+PRIMARY KEY (`oid`),
+KEY `fk_bbk_niederlassung` (`fk_bbk_niederlassung`),
+KEY `fk_wochentag` (`fk_wochentag`),
+KEY `ix_version` (`version`)
+) DEFAULT CHARSET=latin1;
+insert into t1 values
+(1, 38, 1, '08:00:00', '13:00:00', 0, 1),
+(2, 38, 2, '08:00:00', '13:00:00', 0, 1),
+(3, 38, 3, '08:00:00', '13:00:00', 0, 1),
+(4, 38, 4, '08:00:00', '13:00:00', 0, 1),
+(5, 38, 5, '08:00:00', '13:00:00', 0, 1),
+(6, 38, 5, '08:00:00', '13:00:00', 1, 2),
+(7, 38, 3, '08:00:00', '13:00:00', 1, 2),
+(8, 38, 1, '08:00:00', '13:00:00', 1, 2),
+(9, 38, 2, '08:00:00', '13:00:00', 1, 2),
+(10, 38, 4, '08:00:00', '13:00:00', 1, 2),
+(11, 38, 1, '08:00:00', '13:00:00', 0, 3),
+(12, 38, 2, '08:00:00', '13:00:00', 0, 3),
+(13, 38, 3, '08:00:00', '13:00:00', 0, 3),
+(14, 38, 4, '08:00:00', '13:00:00', 0, 3),
+(15, 38, 5, '08:00:00', '13:00:00', 0, 3),
+(16, 38, 4, '08:00:00', '13:00:00', 0, 4),
+(17, 38, 5, '08:00:00', '13:00:00', 0, 4),
+(18, 38, 1, '08:00:00', '13:00:00', 0, 4),
+(19, 38, 2, '08:00:00', '13:00:00', 0, 4),
+(20, 38, 3, '08:00:00', '13:00:00', 0, 4),
+(21, 7, 1, '08:00:00', '13:00:00', 0, 1),
+(22, 7, 2, '08:00:00', '13:00:00', 0, 1),
+(23, 7, 3, '08:00:00', '13:00:00', 0, 1),
+(24, 7, 4, '08:00:00', '13:00:00', 0, 1),
+(25, 7, 5, '08:00:00', '13:00:00', 0, 1);
+create view v1 as
+select
+zeit1.oid AS oid,
+zeit1.fk_bbk_niederlassung AS fk_bbk_niederlassung,
+zeit1.fk_wochentag AS fk_wochentag,
+zeit1.uhrzeit_von AS uhrzeit_von,
+zeit1.uhrzeit_bis AS uhrzeit_bis,
+zeit1.geloescht AS geloescht,
+zeit1.version AS version
+from
+t1 zeit1
+where
+(zeit1.version =
+(select max(zeit2.version) AS `max(version)`
+ from t1 zeit2
+where
+((zeit1.fk_bbk_niederlassung = zeit2.fk_bbk_niederlassung) and
+(zeit1.fk_wochentag = zeit2.fk_wochentag) and
+(zeit1.uhrzeit_von = zeit2.uhrzeit_von) and
+(zeit1.uhrzeit_bis = zeit2.uhrzeit_bis)
+)
+)
+)
+and (zeit1.geloescht = 0);
+select * from v1 where oid = 21;
+oid fk_bbk_niederlassung fk_wochentag uhrzeit_von uhrzeit_bis geloescht version
+21 7 1 08:00:00 13:00:00 0 1
+drop view v1;
+drop table t1;
+CREATE TABLE t1(
+t_cpac varchar(2) NOT NULL,
+t_vers varchar(4) NOT NULL,
+t_rele varchar(2) NOT NULL,
+t_cust varchar(4) NOT NULL,
+filler1 char(250) default NULL,
+filler2 char(250) default NULL,
+PRIMARY KEY (t_cpac,t_vers,t_rele,t_cust),
+UNIQUE KEY IX_4 (t_cust,t_cpac,t_vers,t_rele),
+KEY IX_5 (t_vers,t_rele,t_cust)
+);
+insert into t1 values
+('tm','2.5 ','a ',' ','',''), ('tm','2.5U','a ','stnd','',''),
+('da','3.3 ','b ',' ','',''), ('da','3.3U','b ','stnd','',''),
+('tl','7.6 ','a ',' ','',''), ('tt','7.6 ','a ',' ','',''),
+('bc','B61 ','a ',' ','',''), ('bp','B61 ','a ',' ','',''),
+('ca','B61 ','a ',' ','',''), ('ci','B61 ','a ',' ','',''),
+('cp','B61 ','a ',' ','',''), ('dm','B61 ','a ',' ','',''),
+('ec','B61 ','a ',' ','',''), ('ed','B61 ','a ',' ','',''),
+('fm','B61 ','a ',' ','',''), ('nt','B61 ','a ',' ','',''),
+('qm','B61 ','a ',' ','',''), ('tc','B61 ','a ',' ','',''),
+('td','B61 ','a ',' ','',''), ('tf','B61 ','a ',' ','',''),
+('tg','B61 ','a ',' ','',''), ('ti','B61 ','a ',' ','',''),
+('tp','B61 ','a ',' ','',''), ('ts','B61 ','a ',' ','',''),
+('wh','B61 ','a ',' ','',''), ('bc','B61U','a ','stnd','',''),
+('bp','B61U','a ','stnd','',''), ('ca','B61U','a ','stnd','',''),
+('ci','B61U','a ','stnd','',''), ('cp','B61U','a ','stnd','',''),
+('dm','B61U','a ','stnd','',''), ('ec','B61U','a ','stnd','',''),
+('fm','B61U','a ','stnd','',''), ('nt','B61U','a ','stnd','',''),
+('qm','B61U','a ','stnd','',''), ('tc','B61U','a ','stnd','',''),
+('td','B61U','a ','stnd','',''), ('tf','B61U','a ','stnd','',''),
+('tg','B61U','a ','stnd','',''), ('ti','B61U','a ','stnd','',''),
+('tp','B61U','a ','stnd','',''), ('ts','B61U','a ','stnd','',''),
+('wh','B61U','a ','stnd','','');
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `t_cpac` varchar(2) NOT NULL,
+ `t_vers` varchar(4) NOT NULL,
+ `t_rele` varchar(2) NOT NULL,
+ `t_cust` varchar(4) NOT NULL,
+ `filler1` char(250) DEFAULT NULL,
+ `filler2` char(250) DEFAULT NULL,
+ PRIMARY KEY (`t_cpac`,`t_vers`,`t_rele`,`t_cust`),
+ UNIQUE KEY `IX_4` (`t_cust`,`t_cpac`,`t_vers`,`t_rele`),
+ KEY `IX_5` (`t_vers`,`t_rele`,`t_cust`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+select t_vers,t_rele,t_cust,filler1 from t1 where t_vers = '7.6';
+t_vers t_rele t_cust filler1
+7.6 a
+7.6 a
+select t_vers,t_rele,t_cust,filler1 from t1 where t_vers = '7.6'
+ and t_rele='a' and t_cust = ' ';
+t_vers t_rele t_cust filler1
+7.6 a
+7.6 a
+drop table t1;
+create table t1 (
+pk int(11) not null auto_increment,
+a int(11) not null default '0',
+b int(11) not null default '0',
+c int(11) not null default '0',
+filler1 datetime, filler2 varchar(15),
+filler3 longtext,
+kp1 varchar(4), kp2 varchar(7),
+kp3 varchar(2), kp4 varchar(4),
+kp5 varchar(7),
+filler4 char(1),
+primary key (pk),
+key idx1(a,b,c),
+key idx2(c),
+key idx3(kp1,kp2,kp3,kp4,kp5)
+) default charset=latin1;
+set @fill=NULL;
+SELECT COUNT(*) FROM t1 WHERE b = 0 AND a = 0 AND c = 13286427 AND
+kp1='279' AND kp2='ELM0678' AND kp3='6' AND kp4='10' AND kp5 = 'R ';
+COUNT(*)
+1
+drop table t1;
+create table t1
+(
+key1 int not null,
+key2 int not null default 0,
+key3 int not null default 0
+);
+insert into t1(key1) values (1),(2),(3),(4),(5),(6),(7),(8);
+set @d=8;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+insert into t1 (key1) select key1+@d from t1;
+set @d=@d*2;
+alter table t1 add index i2(key2);
+alter table t1 add index i3(key3);
+update t1 set key2=key1,key3=key1;
+select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40);
+key1 key2 key3
+31 31 31
+32 32 32
+33 33 33
+34 34 34
+35 35 35
+36 36 36
+37 37 37
+38 38 38
+39 39 39
+drop table t1;
+#
+# Bug#56423: Different count with SELECT and CREATE SELECT queries
+#
+CREATE TABLE t1 (
+a INT,
+b INT,
+c INT,
+d INT,
+PRIMARY KEY (a),
+KEY (c),
+KEY bd (b,d)
+);
+INSERT INTO t1 VALUES
+(1, 0, 1, 0),
+(2, 1, 1, 1),
+(3, 1, 1, 1),
+(4, 0, 1, 1);
+EXPLAIN
+SELECT a
+FROM t1
+WHERE c = 1 AND b = 1 AND d = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge c,bd c,bd 5,10 NULL 1 Using intersect(c,bd); Using where; Using index
+CREATE TABLE t2 ( a INT )
+SELECT a
+FROM t1
+WHERE c = 1 AND b = 1 AND d = 1;
+SELECT * FROM t2;
+a
+2
+3
+DROP TABLE t1, t2;
+CREATE TABLE t1( a INT, b INT, KEY(a), KEY(b) );
+INSERT INTO t1 VALUES (1, 2), (1, 2), (1, 2), (1, 2);
+SELECT * FROM t1 FORCE INDEX(a, b) WHERE a = 1 AND b = 2;
+a b
+1 2
+1 2
+1 2
+1 2
+DROP TABLE t1;
+# Code coverage of fix.
+CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b INT);
+INSERT INTO t1 (b) VALUES (1);
+UPDATE t1 SET b = 2 WHERE a = 1;
+SELECT * FROM t1;
+a b
+1 2
+CREATE TABLE t2 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b VARCHAR(1) );
+INSERT INTO t2 (b) VALUES ('a');
+UPDATE t2 SET b = 'b' WHERE a = 1;
+SELECT * FROM t2;
+a b
+1 b
+DROP TABLE t1, t2;
+#
+# BUG#13970015: ASSERT `MIN_ENDP || MAX_ENDP' FAILED IN
+# HANDLER::MULTI_RANGE_READ_INFO_CONST
+#
+CREATE TABLE t1 (
+pk INT NOT NULL,
+col_int_key INT NOT NULL,
+col_varchar_key VARCHAR(1) NOT NULL,
+PRIMARY KEY (pk),
+KEY col_int_key (col_int_key),
+KEY col_varchar_key (col_varchar_key,col_int_key)
+);
+INSERT INTO t1 VALUES (1,1,'a'), (2,2,'b');
+EXPLAIN
+SELECT col_int_key
+FROM t1
+WHERE col_varchar_key >= 'l' OR
+(((pk BETWEEN 141 AND 141) OR col_varchar_key <> 'l')
+AND ((pk BETWEEN 141 AND 141) OR (col_int_key > 141)));
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index PRIMARY,col_int_key,col_varchar_key col_varchar_key 7 NULL 2 Using where; Using index
+SELECT col_int_key
+FROM t1
+WHERE col_varchar_key >= 'l' OR
+(((pk BETWEEN 141 AND 141) OR col_varchar_key <> 'l')
+AND ((pk BETWEEN 141 AND 141) OR (col_int_key > 141)));
+col_int_key
+DROP TABLE t1;
+set global rocksdb_force_flush_memtable_now=1;
+#---------------- 2-sweeps read Index merge test 2 -------------------------------
+SET SESSION DEFAULT_STORAGE_ENGINE = RocksDB;
+drop table if exists t1;
+create table t1 (
+pk int primary key,
+key1 int,
+key2 int,
+filler char(200),
+filler2 char(200),
+index(key1),
+index(key2)
+);
+select * from t1 where (key1 >= 2 and key1 <= 10) or (pk >= 4 and pk <=8 );
+pk key1 key2 filler filler2
+10 10 10 filler-data filler-data-2
+2 2 2 filler-data filler-data-2
+3 3 3 filler-data filler-data-2
+4 4 4 filler-data filler-data-2
+5 5 5 filler-data filler-data-2
+6 6 6 filler-data filler-data-2
+7 7 7 filler-data filler-data-2
+8 8 8 filler-data filler-data-2
+9 9 9 filler-data filler-data-2
+set @maxv=1000;
+select * from t1 where
+(pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+or key1=18 or key1=60;
+pk key1 key2 filler filler2
+1 1 1 filler-data filler-data-2
+1000 1000 1000 filler-data filler-data-2
+11 11 11 filler-data filler-data-2
+12 12 12 filler-data filler-data-2
+13 13 13 filler-data filler-data-2
+14 14 14 filler-data filler-data-2
+18 18 18 filler-data filler-data-2
+2 2 2 filler-data filler-data-2
+3 3 3 filler-data filler-data-2
+4 4 4 filler-data filler-data-2
+50 50 50 filler-data filler-data-2
+51 51 51 filler-data filler-data-2
+52 52 52 filler-data filler-data-2
+53 53 53 filler-data filler-data-2
+54 54 54 filler-data filler-data-2
+60 60 60 filler-data filler-data-2
+991 991 991 filler-data filler-data-2
+992 992 992 filler-data filler-data-2
+993 993 993 filler-data filler-data-2
+994 994 994 filler-data filler-data-2
+995 995 995 filler-data filler-data-2
+996 996 996 filler-data filler-data-2
+997 997 997 filler-data filler-data-2
+998 998 998 filler-data filler-data-2
+999 999 999 filler-data filler-data-2
+select * from t1 where
+(pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+or key1 < 3 or key1 > @maxv-11;
+pk key1 key2 filler filler2
+1 1 1 filler-data filler-data-2
+1000 1000 1000 filler-data filler-data-2
+11 11 11 filler-data filler-data-2
+12 12 12 filler-data filler-data-2
+13 13 13 filler-data filler-data-2
+14 14 14 filler-data filler-data-2
+2 2 2 filler-data filler-data-2
+3 3 3 filler-data filler-data-2
+4 4 4 filler-data filler-data-2
+50 50 50 filler-data filler-data-2
+51 51 51 filler-data filler-data-2
+52 52 52 filler-data filler-data-2
+53 53 53 filler-data filler-data-2
+54 54 54 filler-data filler-data-2
+990 990 990 filler-data filler-data-2
+991 991 991 filler-data filler-data-2
+992 992 992 filler-data filler-data-2
+993 993 993 filler-data filler-data-2
+994 994 994 filler-data filler-data-2
+995 995 995 filler-data filler-data-2
+996 996 996 filler-data filler-data-2
+997 997 997 filler-data filler-data-2
+998 998 998 filler-data filler-data-2
+999 999 999 filler-data filler-data-2
+select * from t1 where
+(pk < 5) or (pk > 10 and pk < 15) or (pk >= 50 and pk < 55 ) or (pk > @maxv-10)
+or
+(key1 < 5) or (key1 > 10 and key1 < 15) or (key1 >= 50 and key1 < 55 ) or (key1 > @maxv-10);
+pk key1 key2 filler filler2
+1 1 1 filler-data filler-data-2
+1000 1000 1000 filler-data filler-data-2
+11 11 11 filler-data filler-data-2
+12 12 12 filler-data filler-data-2
+13 13 13 filler-data filler-data-2
+14 14 14 filler-data filler-data-2
+2 2 2 filler-data filler-data-2
+3 3 3 filler-data filler-data-2
+4 4 4 filler-data filler-data-2
+50 50 50 filler-data filler-data-2
+51 51 51 filler-data filler-data-2
+52 52 52 filler-data filler-data-2
+53 53 53 filler-data filler-data-2
+54 54 54 filler-data filler-data-2
+991 991 991 filler-data filler-data-2
+992 992 992 filler-data filler-data-2
+993 993 993 filler-data filler-data-2
+994 994 994 filler-data filler-data-2
+995 995 995 filler-data filler-data-2
+996 996 996 filler-data filler-data-2
+997 997 997 filler-data filler-data-2
+998 998 998 filler-data filler-data-2
+999 999 999 filler-data filler-data-2
+select * from t1 where
+(pk > 10 and pk < 15) or (pk >= 50 and pk < 55 )
+or
+(key1 < 5) or (key1 > @maxv-10);
+pk key1 key2 filler filler2
+1 1 1 filler-data filler-data-2
+1000 1000 1000 filler-data filler-data-2
+11 11 11 filler-data filler-data-2
+12 12 12 filler-data filler-data-2
+13 13 13 filler-data filler-data-2
+14 14 14 filler-data filler-data-2
+2 2 2 filler-data filler-data-2
+3 3 3 filler-data filler-data-2
+4 4 4 filler-data filler-data-2
+50 50 50 filler-data filler-data-2
+51 51 51 filler-data filler-data-2
+52 52 52 filler-data filler-data-2
+53 53 53 filler-data filler-data-2
+54 54 54 filler-data filler-data-2
+991 991 991 filler-data filler-data-2
+992 992 992 filler-data filler-data-2
+993 993 993 filler-data filler-data-2
+994 994 994 filler-data filler-data-2
+995 995 995 filler-data filler-data-2
+996 996 996 filler-data filler-data-2
+997 997 997 filler-data filler-data-2
+998 998 998 filler-data filler-data-2
+999 999 999 filler-data filler-data-2
+drop table t1;
+set global rocksdb_force_flush_memtable_now=1;
+#---------------- Clustered PK ROR-index_merge tests -----------------------------
+SET SESSION DEFAULT_STORAGE_ENGINE = RocksDB;
+drop table if exists t1;
+create table t1
+(
+pk1 int not null,
+pk2 int not null,
+key1 int not null,
+key2 int not null,
+pktail1ok int not null,
+pktail2ok int not null,
+pktail3bad int not null,
+pktail4bad int not null,
+pktail5bad int not null,
+pk2copy int not null,
+badkey int not null,
+filler1 char (200),
+filler2 char (200),
+key (key1),
+key (key2),
+/* keys with tails from CPK members */
+key (pktail1ok, pk1),
+key (pktail2ok, pk1, pk2),
+key (pktail3bad, pk2, pk1),
+key (pktail4bad, pk1, pk2copy),
+key (pktail5bad, pk1, pk2, pk2copy),
+primary key (pk1, pk2)
+);
+explain select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range PRIMARY,key1 PRIMARY 8 NULL ROWS Using where
+select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
+pk1 pk2 key1 key2 pktail1ok pktail2ok pktail3bad pktail4bad pktail5bad pk2copy badkey filler1 filler2
+1 10 0 0 0 0 0 0 0 10 0 filler-data-10 filler2
+1 11 0 0 0 0 0 0 0 11 0 filler-data-11 filler2
+1 12 0 0 0 0 0 0 0 12 0 filler-data-12 filler2
+1 13 0 0 0 0 0 0 0 13 0 filler-data-13 filler2
+1 14 0 0 0 0 0 0 0 14 0 filler-data-14 filler2
+1 15 0 0 0 0 0 0 0 15 0 filler-data-15 filler2
+1 16 0 0 0 0 0 0 0 16 0 filler-data-16 filler2
+1 17 0 0 0 0 0 0 0 17 0 filler-data-17 filler2
+1 18 0 0 0 0 0 0 0 18 0 filler-data-18 filler2
+1 19 0 0 0 0 0 0 0 19 0 filler-data-19 filler2
+explain select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key1,key2 4,4 NULL 1 Using intersect(key1,key2); Using where; Using index
+select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
+pk1 pk2
+95 50
+95 51
+95 52
+95 53
+95 54
+95 55
+95 56
+95 57
+95 58
+95 59
+explain select * from t1 where badkey=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 4 const ROWS Using where
+explain select * from t1 where pk1 < 7500 and key1 = 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range PRIMARY,key1 PRIMARY 4 NULL ROWS Using where
+explain select * from t1 where pktail1ok=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1,pktail1ok key1 4 const 2 Using where
+explain select * from t1 where pktail2ok=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1,pktail2ok key1 4 const 2 Using where
+explain select * from t1 where (pktail2ok=1 and pk1< 50000) or key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge PRIMARY,key1,pktail2ok pktail2ok,key1 8,4 NULL ROWS Using sort_union(pktail2ok,key1); Using where
+explain select * from t1 where pktail3bad=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1,pktail3bad EITHER_KEY 4 const ROWS Using where
+explain select * from t1 where pktail4bad=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1,pktail4bad key1 4 const ROWS Using where
+explain select * from t1 where pktail5bad=1 and key1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1,pktail5bad key1 4 const ROWS Using where
+explain select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index_merge key1,key2 key1,key2 4,4 NULL 1 Using intersect(key1,key2); Using where; Using index
+select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
+pk1 pk2 key1 key2
+95 50 10 10
+95 51 10 10
+95 52 10 10
+95 53 10 10
+95 54 10 10
+95 55 10 10
+95 56 10 10
+95 57 10 10
+95 58 10 10
+95 59 10 10
+drop table t1;
+create table t1
+(
+RUNID varchar(22),
+SUBMITNR varchar(5),
+ORDERNR char(1),
+PROGRAMM varchar(8),
+TESTID varchar(4),
+UCCHECK char(1),
+ETEXT varchar(80),
+ETEXT_TYPE char(1),
+INFO char(1),
+SEVERITY tinyint(3),
+TADIRFLAG char(1),
+PRIMARY KEY (RUNID,SUBMITNR,ORDERNR,PROGRAMM,TESTID,UCCHECK),
+KEY `TVERM~KEY` (PROGRAMM,TESTID,UCCHECK)
+) DEFAULT CHARSET=latin1;
+update t1 set `ETEXT` = '', `ETEXT_TYPE`='', `INFO`='', `SEVERITY`='', `TADIRFLAG`=''
+WHERE
+`RUNID`= '' AND `SUBMITNR`= '' AND `ORDERNR`='' AND `PROGRAMM`='' AND
+`TESTID`='' AND `UCCHECK`='';
+drop table t1;
+#
+# Bug#50402 Optimizer producing wrong results when using Index Merge on InnoDB
+#
+CREATE TABLE t1 (f1 INT, PRIMARY KEY (f1));
+INSERT INTO t1 VALUES (2);
+CREATE TABLE t2 (f1 INT, f2 INT, f3 char(1),
+PRIMARY KEY (f1), KEY (f2), KEY (f3) );
+INSERT INTO t2 VALUES (1, 1, 'h'), (2, 3, 'h'), (3, 2, ''), (4, 2, '');
+SELECT t1.f1 FROM t1
+WHERE (SELECT COUNT(*) FROM t2 WHERE t2.f3 = 'h' AND t2.f2 = t1.f1) = 0 AND t1.f1 = 2;
+f1
+2
+EXPLAIN SELECT t1.f1 FROM t1
+WHERE (SELECT COUNT(*) FROM t2 WHERE t2.f3 = 'h' AND t2.f2 = t1.f1) = 0 AND t1.f1 = 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index
+2 SUBQUERY t2 index_merge f2,f3 f3,f2 2,5 NULL 1 Using intersect(f3,f2); Using where; Using index
+DROP TABLE t1,t2;
+set global rocksdb_force_flush_memtable_now=1;
+#
+# Bug#11747423 32254: INDEX MERGE USED UNNECESSARILY
+#
+CREATE TABLE t1 (
+id INT NOT NULL PRIMARY KEY,
+id2 INT NOT NULL,
+id3 INT NOT NULL,
+KEY (id2),
+KEY (id3),
+KEY covering_index (id2,id3)
+) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7);
+INSERT INTO t1 SELECT id + 8, id2 + 8, id3 +8 FROM t1;
+INSERT INTO t1 SELECT id + 16, 7, 0 FROM t1;
+EXPLAIN SELECT SQL_NO_CACHE count(*) FROM t1 WHERE id2=7 AND id3=0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref id2,id3,covering_index covering_index 8 const,const 2 Using index
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_primary.result b/storage/rocksdb/mysql-test/rocksdb/r/index_primary.result
new file mode 100644
index 00000000..1f4c88e5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_primary.result
@@ -0,0 +1,71 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY,
+b CHAR(8)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A # NULL NULL LSMTREE
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+INSERT INTO t1 (a,b) VALUES (1,'c');
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY,
+b CHAR(8) PRIMARY KEY
+) ENGINE=rocksdb;
+ERROR 42000: Multiple primary key defined
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+PRIMARY KEY (a,b)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A # NULL NULL LSMTREE
+t1 0 PRIMARY 2 b A # NULL NULL LSMTREE
+INSERT INTO t1 (a,b) VALUES (1,'a'),(1,'b'),(2,'a'),(2,'b');
+INSERT INTO t1 (a,b) VALUES (1,'b');
+ERROR 23000: Duplicate entry '1-b' for key 'PRIMARY'
+DROP TABLE t1;
+CREATE TABLE t1 (a INT KEY,
+b CHAR(8),
+KEY (b)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 a A # NULL NULL LSMTREE
+t1 1 b 1 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8) PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A # NULL NULL LSMTREE
+ALTER TABLE t1 ADD CONSTRAINT PRIMARY KEY pk (a);
+ERROR 42000: Multiple primary key defined
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A # NULL NULL LSMTREE
+DROP TABLE t1;
+set global rocksdb_large_prefix=0;
+CREATE TABLE t1 (
+a BLOB(1024),
+PRIMARY KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(1024),
+PRIMARY KEY (a(768))
+) ENGINE=rocksdb;
+ERROR 42000: Specified key was too long; max key length is 767 bytes
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (
+a BLOB(4096),
+PRIMARY KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(4096),
+PRIMARY KEY (a(3073))
+) ENGINE=rocksdb;
+ERROR 42000: Specified key was too long; max key length is 3072 bytes
+set global rocksdb_large_prefix=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_type_btree.result b/storage/rocksdb/mysql-test/rocksdb/r/index_type_btree.result
new file mode 100644
index 00000000..9eaab9f5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_type_btree.result
@@ -0,0 +1,69 @@
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY USING BTREE (a)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY a_b USING BTREE (a,b) COMMENT 'a_b index'
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a_b 1 a A # NULL NULL YES LSMTREE a_b index
+t1 1 a_b 2 b A # NULL NULL YES LSMTREE a_b index
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY USING BTREE (a),
+KEY USING BTREE (b)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+t1 1 b 1 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'z');
+ALTER TABLE t1 ADD KEY (a) USING BTREE COMMENT 'simple index on a';
+SHOW INDEX FROM t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE simple index on a
+ALTER TABLE t1 DROP KEY a;
+DROP TABLE t1;
+set global rocksdb_large_prefix=0;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(768))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 767 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3073))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 3072 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/index_type_hash.result b/storage/rocksdb/mysql-test/rocksdb/r/index_type_hash.result
new file mode 100644
index 00000000..bc1a96fa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/index_type_hash.result
@@ -0,0 +1,69 @@
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY USING HASH (a)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY a_b USING HASH (a,b) COMMENT 'a_b index'
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a_b 1 a A # NULL NULL YES LSMTREE a_b index
+t1 1 a_b 2 b A # NULL NULL YES LSMTREE a_b index
+DROP TABLE t1;
+CREATE TABLE t1 (a INT,
+b CHAR(8),
+pk INT PRIMARY KEY,
+KEY USING HASH (a),
+KEY USING HASH (b)
+) ENGINE=rocksdb;
+SHOW KEYS IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE
+t1 1 b 1 b A # NULL NULL YES LSMTREE
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'z');
+ALTER TABLE t1 ADD KEY (a) USING HASH COMMENT 'simple index on a';
+SHOW INDEX FROM t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A # NULL NULL LSMTREE
+t1 1 a 1 a A # NULL NULL YES LSMTREE simple index on a
+ALTER TABLE t1 DROP KEY a;
+DROP TABLE t1;
+set global rocksdb_large_prefix=0;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(1024),
+KEY (a(768))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 767 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a BLOB(4096),
+KEY (a(3073))
+) ENGINE=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 3072 bytes
+DROP TABLE t1;
+set global rocksdb_large_prefix=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/information_schema.result b/storage/rocksdb/mysql-test/rocksdb/r/information_schema.result
new file mode 100644
index 00000000..e22a85fd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/information_schema.result
@@ -0,0 +1,83 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+create table t1 (a int) engine=rocksdb;
+drop table t1;
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type<>'DDL_DROP_INDEX_ONGOING';
+TYPE NAME VALUE
+MAX_INDEX_ID MAX_INDEX_ID max_index_id
+CF_FLAGS 0 default [0]
+CF_FLAGS 1 __system__ [0]
+select count(*) from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type<>'DDL_DROP_INDEX_ONGOING';
+count(*)
+3
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+select VALUE into @keysIn from INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS where CF_NAME = 'default' and LEVEL = 'Sum' and TYPE = 'KeyIn';
+CREATE TABLE t1 (i1 INT, i2 INT, PRIMARY KEY (i1)) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+set global rocksdb_force_flush_memtable_now = true;
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+TYPE NAME VALUE
+MAX_INDEX_ID MAX_INDEX_ID max_index_id
+CF_FLAGS 0 default [0]
+CF_FLAGS 1 __system__ [0]
+select count(*) from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+count(*)
+3
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf='default';
+select case when VALUE-@keysIn >= 3 then 'true' else 'false' end from INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS where CF_NAME = 'default' and LEVEL = 'Sum' and TYPE = 'KeyIn';
+case when VALUE-@keysIn >= 3 then 'true' else 'false' end
+true
+CREATE INDEX tindex1 on t1 (i1);
+CREATE INDEX tindex2 on t1 (i2);
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where TYPE = 'CF_FLAGS';
+TYPE NAME VALUE
+CF_FLAGS 0 default [0]
+CF_FLAGS 1 __system__ [0]
+CREATE TABLE t2 (
+a int,
+b int,
+c int,
+d int,
+PRIMARY KEY (a) COMMENT "cf_a",
+KEY (b) COMMENT "cf_b",
+KEY (c) COMMENT "cf_c",
+KEY (d) COMMENT "rev:cf_d") ENGINE=ROCKSDB;
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where TYPE = 'CF_FLAGS';
+TYPE NAME VALUE
+CF_FLAGS 0 default [0]
+CF_FLAGS 1 __system__ [0]
+CF_FLAGS 2 cf_a [0]
+CF_FLAGS 3 cf_b [0]
+CF_FLAGS 4 cf_c [0]
+CF_FLAGS 5 rev:cf_d [1]
+CREATE TABLE t3 (a INT, PRIMARY KEY (a)) ENGINE=ROCKSDB;
+insert into t3 (a) values (1), (2), (3);
+SET @ORIG_ROCKSDB_PAUSE_BACKGROUND_WORK = @@GLOBAL.ROCKSDB_PAUSE_BACKGROUND_WORK;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+Variable_name Value
+rocksdb_pause_background_work OFF
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+Variable_name Value
+rocksdb_pause_background_work ON
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+Variable_name Value
+rocksdb_pause_background_work ON
+DROP TABLE t3;
+cf_id:0,index_id:264
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+Variable_name Value
+rocksdb_pause_background_work OFF
+next line shouldn't cause assertion to fail
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+Variable_name Value
+rocksdb_pause_background_work OFF
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_ROCKSDB_PAUSE_BACKGROUND_WORK;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result
new file mode 100644
index 00000000..ac6e2bcc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result
@@ -0,0 +1,326 @@
+SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
+trx_id trx_state trx_started trx_requested_lock_id trx_wait_started trx_weight trx_mysql_thread_id trx_query trx_operation_state trx_tables_in_use trx_tables_locked trx_lock_structs trx_lock_memory_bytes trx_rows_locked trx_rows_modified trx_concurrency_tickets trx_isolation_level trx_unique_checks trx_foreign_key_checks trx_last_foreign_key_error trx_is_read_only trx_autocommit_non_locking
+SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
+lock_id lock_trx_id lock_mode lock_type lock_table lock_index lock_space lock_page lock_rec lock_data
+SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
+requesting_trx_id requested_lock_id blocking_trx_id blocking_lock_id
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP;
+page_size compress_ops compress_ops_ok compress_time uncompress_ops uncompress_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_RESET;
+page_size compress_ops compress_ops_ok compress_time uncompress_ops uncompress_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX;
+database_name table_name index_name compress_ops compress_ops_ok compress_time uncompress_ops uncompress_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX_RESET;
+database_name table_name index_name compress_ops compress_ops_ok compress_time uncompress_ops uncompress_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMPMEM;
+page_size buffer_pool_instance pages_used pages_free relocation_ops relocation_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMPMEM_RESET;
+page_size buffer_pool_instance pages_used pages_free relocation_ops relocation_time
+SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS;
+NAME SUBSYSTEM COUNT MAX_COUNT MIN_COUNT AVG_COUNT COUNT_RESET MAX_COUNT_RESET MIN_COUNT_RESET AVG_COUNT_RESET TIME_ENABLED TIME_DISABLED TIME_ELAPSED TIME_RESET ENABLED TYPE COMMENT
+metadata_table_handles_opened metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles opened
+metadata_table_handles_closed metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles closed
+metadata_table_reference_count metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Table reference counter
+lock_deadlocks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of deadlocks
+lock_timeouts lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of lock timeouts
+lock_rec_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into record lock wait queue
+lock_table_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into table lock wait queue
+lock_rec_lock_requests lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of record locks requested
+lock_rec_lock_created lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of record locks created
+lock_rec_lock_removed lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of record locks removed from the lock queue
+lock_rec_locks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Current number of record locks on tables
+lock_table_lock_created lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table locks created
+lock_table_lock_removed lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table locks removed from the lock queue
+lock_table_locks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Current number of table locks on tables
+lock_row_lock_current_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of row locks currently being waited for (innodb_row_lock_current_waits)
+lock_row_lock_time lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Time spent in acquiring row locks, in milliseconds (innodb_row_lock_time)
+lock_row_lock_time_max lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value The maximum time to acquire a row lock, in milliseconds (innodb_row_lock_time_max)
+lock_row_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of times a row lock had to be waited for (innodb_row_lock_waits)
+lock_row_lock_time_avg lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value The average time to acquire a row lock, in milliseconds (innodb_row_lock_time_avg)
+buffer_pool_size server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Server buffer pool size (all buffer pools) in bytes
+buffer_pool_reads buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of reads directly from disk (innodb_buffer_pool_reads)
+buffer_pool_read_requests buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of logical read requests (innodb_buffer_pool_read_requests)
+buffer_pool_write_requests buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of write requests (innodb_buffer_pool_write_requests)
+buffer_pool_wait_free buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of times waited for free buffer (innodb_buffer_pool_wait_free)
+buffer_pool_read_ahead buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pages read as read ahead (innodb_buffer_pool_read_ahead)
+buffer_pool_read_ahead_evicted buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Read-ahead pages evicted without being accessed (innodb_buffer_pool_read_ahead_evicted)
+buffer_pool_pages_total buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Total buffer pool size in pages (innodb_buffer_pool_pages_total)
+buffer_pool_pages_misc buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer pages for misc use such as row locks or the adaptive hash index (innodb_buffer_pool_pages_misc)
+buffer_pool_pages_data buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer pages containing data (innodb_buffer_pool_pages_data)
+buffer_pool_bytes_data buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer bytes containing data (innodb_buffer_pool_bytes_data)
+buffer_pool_pages_dirty buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer pages currently dirty (innodb_buffer_pool_pages_dirty)
+buffer_pool_bytes_dirty buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer bytes currently dirty (innodb_buffer_pool_bytes_dirty)
+buffer_pool_pages_free buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Buffer pages currently free (innodb_buffer_pool_pages_free)
+buffer_pages_created buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pages created (innodb_pages_created)
+buffer_pages_written buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pages written (innodb_pages_written)
+buffer_index_pages_written buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of index pages written (innodb_index_pages_written)
+buffer_non_index_pages_written buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of non index pages written (innodb_non_index_pages_written)
+buffer_pages_read buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pages read (innodb_pages_read)
+buffer_index_sec_rec_cluster_reads buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of secondary record reads triggered cluster read
+buffer_index_sec_rec_cluster_reads_avoided buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of secondary record reads avoided triggering cluster read
+buffer_data_reads buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Amount of data read in bytes (innodb_data_reads)
+buffer_data_written buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Amount of data written in bytes (innodb_data_written)
+buffer_flush_batch_scanned buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages scanned as part of flush batch
+buffer_flush_batch_num_scan buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of times buffer flush list flush is called
+buffer_flush_batch_scanned_per_call buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages scanned per flush batch scan
+buffer_flush_batch_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages flushed as part of flush batch
+buffer_flush_batches buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of flush batches
+buffer_flush_batch_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as a flush batch
+buffer_flush_neighbor_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total neighbors flushed as part of neighbor flush
+buffer_flush_neighbor buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of times neighbors flushing is invoked
+buffer_flush_neighbor_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as a neighbor batch
+buffer_flush_n_to_flush_requested buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages requested for flushing.
+buffer_flush_n_to_flush_by_age buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages target by LSN Age for flushing.
+buffer_flush_adaptive_avg_time buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Avg time (ms) spent for adaptive flushing recently.
+buffer_flush_adaptive_avg_pass buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of adaptive flushes passed during the recent Avg period.
+buffer_LRU_get_free_loops buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Total loops in LRU get free.
+buffer_LRU_get_free_waits buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Total sleep waits in LRU get free.
+buffer_flush_avg_page_rate buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Average number of pages at which flushing is happening
+buffer_flush_lsn_avg_rate buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Average redo generation rate
+buffer_flush_pct_for_dirty buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Percent of IO capacity used to avoid max dirty page limit
+buffer_flush_pct_for_lsn buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Percent of IO capacity used to avoid reusable redo space limit
+buffer_flush_sync_waits buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times a wait happens due to sync flushing
+buffer_flush_adaptive_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages flushed as part of adaptive flushing
+buffer_flush_adaptive buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of adaptive batches
+buffer_flush_adaptive_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as an adaptive batch
+buffer_flush_sync_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages flushed as part of sync batches
+buffer_flush_sync buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of sync batches
+buffer_flush_sync_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as a sync batch
+buffer_flush_background_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages flushed as part of background batches
+buffer_flush_background buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of background batches
+buffer_flush_background_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as a background batch
+buffer_LRU_batch_scanned buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages scanned as part of LRU batch
+buffer_LRU_batch_num_scan buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of times LRU batch is called
+buffer_LRU_batch_scanned_per_call buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages scanned per LRU batch call
+buffer_LRU_batch_flush_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages flushed as part of LRU batches
+buffer_LRU_batches_flush buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of LRU batches
+buffer_LRU_batch_flush_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as an LRU batch
+buffer_LRU_batch_evict_total_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages evicted as part of LRU batches
+buffer_LRU_batches_evict buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of LRU batches
+buffer_LRU_batch_evict_pages buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Pages queued as an LRU batch
+buffer_LRU_single_flush_failure_count Buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times attempt to flush a single page from LRU failed
+buffer_LRU_get_free_search Buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of searches performed for a clean page
+buffer_LRU_search_scanned buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages scanned as part of LRU search
+buffer_LRU_search_num_scan buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of times LRU search is performed
+buffer_LRU_search_scanned_per_call buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Page scanned per single LRU search
+buffer_LRU_unzip_search_scanned buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_owner Total pages scanned as part of LRU unzip search
+buffer_LRU_unzip_search_num_scan buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Number of times LRU unzip search is performed
+buffer_LRU_unzip_search_scanned_per_call buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 set_member Page scanned per single LRU unzip search
+buffer_page_read_index_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Leaf Pages read
+buffer_page_read_index_non_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Non-leaf Pages read
+buffer_page_read_index_ibuf_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Index Leaf Pages read
+buffer_page_read_index_ibuf_non_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Index Non-Leaf Pages read
+buffer_page_read_undo_log buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Undo Log Pages read
+buffer_page_read_index_inode buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Inode Pages read
+buffer_page_read_ibuf_free_list buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Free List Pages read
+buffer_page_read_ibuf_bitmap buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Bitmap Pages read
+buffer_page_read_system_page buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of System Pages read
+buffer_page_read_trx_system buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Transaction System Pages read
+buffer_page_read_fsp_hdr buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of File Space Header Pages read
+buffer_page_read_xdes buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Extent Descriptor Pages read
+buffer_page_read_blob buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Uncompressed BLOB Pages read
+buffer_page_read_zblob buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of First Compressed BLOB Pages read
+buffer_page_read_zblob2 buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Subsequent Compressed BLOB Pages read
+buffer_page_read_other buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of other/unknown (old version of InnoDB) Pages read
+buffer_page_written_index_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Leaf Pages written
+buffer_page_written_index_non_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Non-leaf Pages written
+buffer_page_written_index_ibuf_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Index Leaf Pages written
+buffer_page_written_index_ibuf_non_leaf buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Index Non-Leaf Pages written
+buffer_page_written_undo_log buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Undo Log Pages written
+buffer_page_written_index_inode buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Index Inode Pages written
+buffer_page_written_ibuf_free_list buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Free List Pages written
+buffer_page_written_ibuf_bitmap buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Insert Buffer Bitmap Pages written
+buffer_page_written_system_page buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of System Pages written
+buffer_page_written_trx_system buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Transaction System Pages written
+buffer_page_written_fsp_hdr buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of File Space Header Pages written
+buffer_page_written_xdes buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Extent Descriptor Pages written
+buffer_page_written_blob buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Uncompressed BLOB Pages written
+buffer_page_written_zblob buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of First Compressed BLOB Pages written
+buffer_page_written_zblob2 buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Subsequent Compressed BLOB Pages written
+buffer_page_written_other buffer_page_io 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of other/unknown (old version InnoDB) Pages written
+os_data_reads os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of reads initiated (innodb_data_reads)
+os_data_writes os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of writes initiated (innodb_data_writes)
+os_data_fsyncs os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of fsync() calls (innodb_data_fsyncs)
+os_pending_reads os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of reads pending
+os_pending_writes os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of writes pending
+os_log_bytes_written os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Bytes of log written (innodb_os_log_written)
+os_log_fsyncs os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of fsync log writes (innodb_os_log_fsyncs)
+os_log_pending_fsyncs os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pending fsync write (innodb_os_log_pending_fsyncs)
+os_log_pending_writes os 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pending log file writes (innodb_os_log_pending_writes)
+trx_rw_commits transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of read-write transactions committed
+trx_ro_commits transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of read-only transactions committed
+trx_nl_ro_commits transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of non-locking auto-commit read-only transactions committed
+trx_commits_insert_update transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of transactions committed with inserts and updates
+trx_rollbacks transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of transactions rolled back
+trx_rollbacks_savepoint transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of transactions rolled back to savepoint
+trx_active_transactions transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of active transactions
+trx_rseg_history_len transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Length of the TRX_RSEG_HISTORY list
+trx_undo_slots_used transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of undo slots used
+trx_undo_slots_cached transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of undo slots cached
+trx_rseg_current_size transaction 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Current rollback segment size in pages
+purge_del_mark_records purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of delete-marked rows purged
+purge_upd_exist_or_extern_records purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of purges on updates of existing records and updates on delete marked record with externally stored field
+purge_invoked purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times purge was invoked
+purge_undo_log_pages purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of undo log pages handled by the purge
+purge_dml_delay_usec purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Microseconds DML to be delayed due to purge lagging
+purge_stop_count purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of times purge was stopped
+purge_resume_count purge 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of times purge was resumed
+log_checkpoints recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of checkpoints
+log_lsn_last_flush recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value LSN of Last flush
+log_lsn_last_checkpoint recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value LSN at last checkpoint
+log_lsn_current recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Current LSN value
+log_lsn_checkpoint_age recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Current LSN value minus LSN at last checkpoint
+log_lsn_buf_pool_oldest recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value The oldest modified block LSN in the buffer pool
+log_max_modified_age_async recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Maximum LSN difference; when exceeded, start asynchronous preflush
+log_pending_log_flushes recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Pending log flushes
+log_pending_checkpoint_writes recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Pending checkpoints
+log_num_log_io recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of log I/Os
+log_waits recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of log waits due to small log buffer (innodb_log_waits)
+log_write_requests recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of log write requests (innodb_log_write_requests)
+log_writes recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of log writes (innodb_log_writes)
+log_padded recovery 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Bytes of log padded for log write ahead
+compress_pages_compressed compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages compressed
+compress_pages_decompressed compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages decompressed
+compression_pad_increments compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times padding is incremented to avoid compression failures
+compression_pad_decrements compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times padding is decremented due to good compressibility
+compress_saved compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of bytes saved by page compression
+compress_pages_page_compressed compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages compressed by page compression
+compress_page_compressed_trim_op compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of TRIM operation performed by page compression
+compress_pages_page_decompressed compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages decompressed by page compression
+compress_pages_page_compression_error compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of page compression errors
+compress_pages_encrypted compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages encrypted
+compress_pages_decrypted compression 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of pages decrypted
+index_page_splits index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index page splits
+index_page_merge_attempts index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index page merge attempts
+index_page_merge_successful index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of successful index page merges
+index_page_reorg_attempts index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index page reorganization attempts
+index_page_reorg_successful index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of successful index page reorganizations
+index_page_discards index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages discarded
+adaptive_hash_searches adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of successful searches using Adaptive Hash Index
+adaptive_hash_searches_btree adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of searches using B-tree on an index search
+adaptive_hash_pages_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages on which the Adaptive Hash Index is built
+adaptive_hash_pages_removed adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages whose corresponding Adaptive Hash Index entries were removed
+adaptive_hash_rows_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows added
+adaptive_hash_rows_removed adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows removed
+adaptive_hash_rows_deleted_no_hash_entry adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of rows deleted that did not have corresponding Adaptive Hash Index entries
+adaptive_hash_rows_updated adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows updated
+file_num_open_files file_system 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of files currently open (innodb_num_open_files)
+ibuf_merges_insert change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of inserted records merged by change buffering
+ibuf_merges_delete_mark change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of deleted records merged by change buffering
+ibuf_merges_delete change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of purge records merged by change buffering
+ibuf_merges_discard_insert change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of insert merged operations discarded
+ibuf_merges_discard_delete_mark change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of deleted merged operations discarded
+ibuf_merges_discard_delete change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of purge merged operations discarded
+ibuf_merges change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of change buffer merges
+ibuf_size change_buffer 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Change buffer size in pages
+innodb_master_thread_sleeps server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times (seconds) master thread sleeps
+innodb_activity_count server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Current server activity count
+innodb_master_active_loops server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times master thread performs its tasks when server is active
+innodb_master_idle_loops server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times master thread performs its tasks when server is idle
+innodb_background_drop_table_usec server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Time (in microseconds) spent to process drop table list
+innodb_log_flush_usec server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Time (in microseconds) spent to flush log records
+innodb_dict_lru_usec server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Time (in microseconds) spent to process DICT LRU list
+innodb_dict_lru_count_active server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of tables evicted from DICT LRU list in the active loop
+innodb_dict_lru_count_idle server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of tables evicted from DICT LRU list in the idle loop
+innodb_dblwr_writes server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of doublewrite operations that have been performed (innodb_dblwr_writes)
+innodb_dblwr_pages_written server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of pages that have been written for doublewrite operations (innodb_dblwr_pages_written)
+innodb_page_size server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value InnoDB page size in bytes (innodb_page_size)
+innodb_rwlock_s_spin_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin waits due to shared latch request
+innodb_rwlock_x_spin_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin waits due to exclusive latch request
+innodb_rwlock_sx_spin_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin waits due to sx latch request
+innodb_rwlock_s_spin_rounds server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin loop rounds due to shared latch request
+innodb_rwlock_x_spin_rounds server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin loop rounds due to exclusive latch request
+innodb_rwlock_sx_spin_rounds server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rwlock spin loop rounds due to sx latch request
+innodb_rwlock_s_os_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of OS waits due to shared latch request
+innodb_rwlock_x_os_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of OS waits due to exclusive latch request
+innodb_rwlock_sx_os_waits server 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of OS waits due to sx latch request
+dml_reads dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rows read
+dml_inserts dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rows inserted
+dml_deletes dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rows deleted
+dml_updates dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of rows updated
+dml_system_reads dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of system rows read
+dml_system_inserts dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of system rows inserted
+dml_system_deletes dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of system rows deleted
+dml_system_updates dml 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of system rows updated
+ddl_background_drop_indexes ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of indexes waiting to be dropped after failed index creation
+ddl_background_drop_tables ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of tables in background drop table list
+ddl_online_create_index ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of indexes being created online
+ddl_pending_alter_table ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of ALTER TABLE, CREATE INDEX, DROP INDEX in progress
+ddl_sort_file_alter_table ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of sort files created during alter table
+ddl_log_file_alter_table ddl 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of log files created during alter table
+icp_attempts icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of attempts for index push-down condition checks
+icp_no_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition does not match
+icp_out_of_range icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition out of range
+icp_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition matches
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
+value
+a
+about
+an
+are
+as
+at
+be
+by
+com
+de
+en
+for
+from
+how
+i
+in
+is
+it
+la
+of
+on
+or
+that
+the
+this
+to
+was
+what
+when
+where
+who
+will
+with
+und
+the
+www
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
+DOC_ID
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
+DOC_ID
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
+WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
+WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
+KEY VALUE
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS;
+POOL_ID POOL_SIZE FREE_BUFFERS DATABASE_PAGES OLD_DATABASE_PAGES MODIFIED_DATABASE_PAGES PENDING_DECOMPRESS PENDING_READS PENDING_FLUSH_LRU PENDING_FLUSH_LIST PAGES_MADE_YOUNG PAGES_NOT_MADE_YOUNG PAGES_MADE_YOUNG_RATE PAGES_MADE_NOT_YOUNG_RATE NUMBER_PAGES_READ NUMBER_PAGES_CREATED NUMBER_PAGES_WRITTEN PAGES_READ_RATE PAGES_CREATE_RATE PAGES_WRITTEN_RATE NUMBER_PAGES_GET HIT_RATE YOUNG_MAKE_PER_THOUSAND_GETS NOT_YOUNG_MAKE_PER_THOUSAND_GETS NUMBER_PAGES_READ_AHEAD NUMBER_READ_AHEAD_EVICTED READ_AHEAD_RATE READ_AHEAD_EVICTED_RATE LRU_IO_TOTAL LRU_IO_CURRENT UNCOMPRESS_TOTAL UNCOMPRESS_CURRENT
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE;
+POOL_ID BLOCK_ID SPACE PAGE_NUMBER PAGE_TYPE FLUSH_TYPE FIX_COUNT IS_HASHED NEWEST_MODIFICATION OLDEST_MODIFICATION ACCESS_TIME TABLE_NAME INDEX_NAME NUMBER_RECORDS DATA_SIZE COMPRESSED_SIZE PAGE_STATE IO_FIX IS_OLD FREE_PAGE_CLOCK
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU;
+POOL_ID LRU_POSITION SPACE PAGE_NUMBER PAGE_TYPE FLUSH_TYPE FIX_COUNT IS_HASHED NEWEST_MODIFICATION OLDEST_MODIFICATION ACCESS_TIME TABLE_NAME INDEX_NAME NUMBER_RECORDS DATA_SIZE COMPRESSED_SIZE COMPRESSED IO_FIX IS_OLD FREE_PAGE_CLOCK
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES;
+TABLE_ID NAME FLAG N_COLS SPACE ROW_FORMAT ZIP_PAGE_SIZE SPACE_TYPE
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESTATS;
+TABLE_ID NAME STATS_INITIALIZED NUM_ROWS CLUST_INDEX_SIZE OTHER_INDEX_SIZE MODIFIED_COUNTER AUTOINC REF_COUNT
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES;
+INDEX_ID NAME TABLE_ID TYPE N_FIELDS PAGE_NO SPACE MERGE_THRESHOLD
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_COLUMNS;
+TABLE_ID NAME POS MTYPE PRTYPE LEN
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FIELDS;
+INDEX_ID NAME POS
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FOREIGN;
+ID FOR_NAME REF_NAME N_COLS TYPE
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FOREIGN_COLS;
+ID FOR_COL_NAME REF_COL_NAME POS
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES;
+SPACE NAME FLAG ROW_FORMAT PAGE_SIZE ZIP_PAGE_SIZE FS_BLOCK_SIZE FILE_SIZE ALLOCATED_SIZE
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_DATAFILES;
+SPACE PATH
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/insert.result b/storage/rocksdb/mysql-test/rocksdb/r/insert.result
new file mode 100644
index 00000000..a1fb3ae9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/insert.result
@@ -0,0 +1,202 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (100,'foobar',100),(1,'a',101),(2,'b',103),(3,'c',104),(4,'d',105),(5,'e',106);
+SELECT a,b FROM t1;
+a b
+1 a
+100 foobar
+2 b
+3 c
+4 d
+5 e
+INSERT t1 VALUE (10,'foo',107),(11,'abc',108);
+SELECT a,b FROM t1;
+a b
+1 a
+10 foo
+100 foobar
+11 abc
+2 b
+3 c
+4 d
+5 e
+INSERT INTO t1 (b,a) VALUES ('test',0);
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 foo
+100 foobar
+11 abc
+2 b
+3 c
+4 d
+5 e
+INSERT INTO t1 VALUES (DEFAULT,DEFAULT,NULL);
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 foo
+100 foobar
+11 abc
+2 b
+3 c
+4 d
+5 e
+NULL NULL
+INSERT t1 (a) VALUE (10),(20);
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 NULL
+10 foo
+100 foobar
+11 abc
+2 b
+20 NULL
+3 c
+4 d
+5 e
+NULL NULL
+INSERT INTO t1 SET a = 11, b = 'f';
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 NULL
+10 foo
+100 foobar
+11 abc
+11 f
+2 b
+20 NULL
+3 c
+4 d
+5 e
+NULL NULL
+INSERT t1 SET b = DEFAULT;
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 NULL
+10 foo
+100 foobar
+11 abc
+11 f
+2 b
+20 NULL
+3 c
+4 d
+5 e
+NULL NULL
+NULL NULL
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 SELECT a,b,pk FROM t1;
+INSERT INTO t1 (a) SELECT a FROM t2 WHERE b = 'foo';
+SELECT a,b FROM t1;
+a b
+0 test
+1 a
+10 NULL
+10 NULL
+10 foo
+100 foobar
+11 abc
+11 f
+2 b
+20 NULL
+3 c
+4 d
+5 e
+NULL NULL
+NULL NULL
+INSERT t1 (a,b) SELECT a,b FROM t1;
+SELECT a,b FROM t1;
+a b
+0 test
+0 test
+1 a
+1 a
+10 NULL
+10 NULL
+10 NULL
+10 NULL
+10 foo
+10 foo
+100 foobar
+100 foobar
+11 abc
+11 abc
+11 f
+11 f
+2 b
+2 b
+20 NULL
+20 NULL
+3 c
+3 c
+4 d
+4 d
+5 e
+5 e
+NULL NULL
+NULL NULL
+NULL NULL
+NULL NULL
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+BEGIN;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(100,'foo');
+INSERT t1 (a,b) VALUE (10,'foo'),(11,'abc');
+COMMIT;
+SELECT a,b FROM t1;
+a b
+1 a
+10 foo
+100 foo
+11 abc
+2 b
+3 c
+4 d
+5 e
+BEGIN;
+INSERT INTO t1 (b,a) VALUES ('test',0);
+SAVEPOINT spt1;
+INSERT INTO t1 (a,b) VALUES (DEFAULT,DEFAULT);
+RELEASE SAVEPOINT spt1;
+INSERT INTO t1 (a,b) VALUES (DEFAULT,DEFAULT);
+ROLLBACK;
+SELECT a,b FROM t1;
+a b
+1 a
+10 foo
+100 foo
+11 abc
+2 b
+3 c
+4 d
+5 e
+BEGIN;
+INSERT t1 (a) VALUE (10),(20);
+SAVEPOINT spt1;
+INSERT INTO t1 SET a = 11, b = 'f';
+INSERT t1 SET b = DEFAULT;
+ROLLBACK TO SAVEPOINT spt1;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+INSERT INTO t1 (b,a) VALUES ('test1',10);
+COMMIT;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+SELECT a,b FROM t1;
+a b
+1 a
+10 foo
+100 foo
+11 abc
+2 b
+3 c
+4 d
+5 e
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/insert_optimized_config.result b/storage/rocksdb/mysql-test/rocksdb/r/insert_optimized_config.result
new file mode 100644
index 00000000..ded48057
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/insert_optimized_config.result
@@ -0,0 +1,16 @@
+DROP TABLE IF EXISTS t1;
+create table t1(
+id bigint not null primary key,
+i1 bigint, #unique
+i2 bigint, #repeating
+c1 varchar(20), #unique
+c2 varchar(20), #repeating
+index t1_2(i1)
+) engine=rocksdb;
+select count(*), sum(id), sum(i1), sum(i2) from t1;
+count(*) sum(id) sum(i1) sum(i2)
+50000 1250025000 1250025000 124980000
+select count(*), sum(id), sum(i1), sum(i2) from t1;
+count(*) sum(id) sum(i1) sum(i2)
+50000 1250025000 1250025000 124980000
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/insert_with_keys.result b/storage/rocksdb/mysql-test/rocksdb/r/insert_with_keys.result
new file mode 100644
index 00000000..6d4139ca
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/insert_with_keys.result
@@ -0,0 +1,262 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+SELECT a,b FROM t1;
+a b
+0
+1 a
+1 a
+100 a
+12345 z
+2 b
+29 n
+3 a
+3 c
+30 m
+4 d
+5 e
+6 f
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+ERROR 23000: Duplicate entry '1' for key 'a'
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+ERROR 23000: Duplicate entry '3' for key 'a'
+INSERT INTO t1 (a,b) VALUES (0,'');
+SELECT a,b FROM t1;
+a b
+0
+1 a
+100 a
+2 b
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+Warnings:
+Warning 1062 Duplicate entry '1' for key 'a'
+INSERT INTO t1 (a,b) VALUES (3,'a'),(4,'d') ON DUPLICATE KEY UPDATE a = a+10;
+SELECT a,b FROM t1;
+a b
+0
+1 a
+100 a
+12345 z
+13 c
+14 d
+2 b
+29 n
+30 m
+5 e
+6 f
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (100,'b'), (2,'c');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+ERROR 23000: Duplicate entry '1-a' for key 'a'
+SELECT a,b FROM t1;
+a b
+1 a
+100 a
+100 b
+2 b
+2 c
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+Warnings:
+Warning 1062 Duplicate entry '1-a' for key 'a'
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z') ON DUPLICATE KEY UPDATE a = a+VALUES(a);
+SELECT a,b FROM t1;
+a b
+100 a
+100 b
+2 a
+2 b
+2 c
+24690 z
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+INSERT INTO t1 (a,b) VALUES (101,'x'),(101,'x');
+ERROR 23000: Duplicate entry '101-x' for key 'a'
+SELECT a,b FROM t1;
+a b
+100 a
+100 b
+2 a
+2 b
+2 c
+24690 z
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+ERROR 23000: Duplicate entry '3' for key 'PRIMARY'
+INSERT INTO t1 (a,b) VALUES (0,'');
+SELECT a,b FROM t1;
+a b
+0
+1 a
+100 a
+2 b
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+Warnings:
+Warning 1062 Duplicate entry '1' for key 'PRIMARY'
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z') ON DUPLICATE KEY UPDATE b = CONCAT(b,b);
+SELECT a,b FROM t1;
+a b
+0
+1 aa
+100 a
+12345 zz
+2 b
+29 n
+3 c
+30 m
+4 d
+5 e
+6 f
+DROP TABLE t1;
+
+INSERT on DUPLICATE KEY UPDATE with multiple keys
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(255)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+SELECT a,b FROM t1;
+a b
+1 aaaaaaaaaaaaaaaaaa
+10 aa
+2 aaaaaaa
+3 aa
+4 aa
+5 aa
+6 aa
+7 aa
+8 aa
+9 aa
+DROP TABLE t1;
+
+INSERT on DUPLICATE KEY UPDATE with secondary key
+
+CREATE TABLE t1 (a INT, b CHAR(8), c INT DEFAULT 0, pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c') ON DUPLICATE KEY UPDATE c = c + 1;
+SELECT a,b,c,pk FROM t1;
+a b c pk
+1 a 22 1
+10 j 1 11
+2 b 6 3
+2 c 4 14
+3 c 1 4
+4 d 1 5
+5 e 1 6
+6 f 1 7
+7 g 1 8
+8 h 1 9
+9 i 1 10
+DROP TABLE t1;
+
+Disable caching and see if it still functions properly
+
+SELECT @@rocksdb_enable_insert_with_update_caching;
+@@rocksdb_enable_insert_with_update_caching
+1
+SET GLOBAL rocksdb_enable_insert_with_update_caching=0;
+SELECT @@rocksdb_enable_insert_with_update_caching;
+@@rocksdb_enable_insert_with_update_caching
+0
+
+INSERT on DUPLICATE KEY UPDATE with multiple keys
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(255)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+SELECT a,b FROM t1;
+a b
+1 aaaaaaaaaaaaaaaaaa
+10 aa
+2 aaaaaaa
+3 aa
+4 aa
+5 aa
+6 aa
+7 aa
+8 aa
+9 aa
+DROP TABLE t1;
+
+INSERT on DUPLICATE KEY UPDATE with secondary key
+
+CREATE TABLE t1 (a INT, b CHAR(8), c INT DEFAULT 0, pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c') ON DUPLICATE KEY UPDATE c = c + 1;
+SELECT a,b,c,pk FROM t1;
+a b c pk
+1 a 22 1
+10 j 1 11
+2 b 6 3
+2 c 4 14
+3 c 1 4
+4 d 1 5
+5 e 1 6
+6 f 1 7
+7 g 1 8
+8 h 1 9
+9 i 1 10
+DROP TABLE t1;
+
+Cleanup
+
+SET GLOBAL rocksdb_enable_insert_with_update_caching=1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue100.result b/storage/rocksdb/mysql-test/rocksdb/r/issue100.result
new file mode 100644
index 00000000..ee73ac3e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue100.result
@@ -0,0 +1,23 @@
+create table t1 (
+id int,
+value int,
+primary key (id)
+) engine=rocksdb;
+insert into t1 values(1,1),(2,2);
+set autocommit=0;
+begin;
+insert into t1 values (50,50);
+select * from t1;
+id value
+1 1
+2 2
+50 50
+update t1 set id=id+100;
+select * from t1;
+id value
+101 1
+102 2
+150 50
+rollback;
+set autocommit=1;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue100_delete.result b/storage/rocksdb/mysql-test/rocksdb/r/issue100_delete.result
new file mode 100644
index 00000000..9e55ebd0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue100_delete.result
@@ -0,0 +1,17 @@
+create table ten(a int primary key);
+insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table one_k(a int primary key);
+insert into one_k select A.a + B.a* 10 + C.a * 100 from ten A, ten B, ten C;
+create table t100(pk int primary key, a int, b int, key(a));
+insert into t100 select a,a,a from test.one_k;
+set global rocksdb_force_flush_memtable_now=1;
+select num_rows, entry_deletes, entry_singledeletes from information_schema.rocksdb_index_file_map where index_number = (select max(index_number) from information_schema.rocksdb_index_file_map) order by entry_deletes, entry_singledeletes;
+num_rows entry_deletes entry_singledeletes
+1000 0 0
+update t100 set a=a+1;
+set global rocksdb_force_flush_memtable_now=1;
+select num_rows, entry_deletes, entry_singledeletes from information_schema.rocksdb_index_file_map where index_number = (select max(index_number) from information_schema.rocksdb_index_file_map) order by entry_deletes, entry_singledeletes;
+num_rows entry_deletes entry_singledeletes
+1000 0 0
+1000 0 1000
+drop table ten, t100, one_k;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue111.result b/storage/rocksdb/mysql-test/rocksdb/r/issue111.result
new file mode 100644
index 00000000..796a637e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue111.result
@@ -0,0 +1,37 @@
+connect con2,localhost,root,,;
+connection default;
+create table t1 (
+pk int not null primary key,
+col1 int not null,
+col2 int not null,
+key(col1)
+) engine=rocksdb;
+create table ten(a int primary key);
+insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table one_k(a int primary key);
+insert into one_k select A.a + B.a* 10 + C.a * 100 from ten A, ten B, ten C;
+insert into t1 select a,a,a from one_k;
+# Start the transaction, get the snapshot
+begin;
+select * from t1 where col1<10;
+pk col1 col2
+0 0 0
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+# Connect with another connection and make a conflicting change
+connection con2;
+begin;
+update t1 set col2=123456 where pk=0;
+commit;
+connection default;
+update t1 set col2=col2+1 where col1 < 10 limit 5;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+disconnect con2;
+drop table t1, ten, one_k;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue243_transactionStatus.result b/storage/rocksdb/mysql-test/rocksdb/r/issue243_transactionStatus.result
new file mode 100644
index 00000000..d4d211b9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue243_transactionStatus.result
@@ -0,0 +1,161 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+id INT,
+val1 INT,
+val2 INT,
+PRIMARY KEY (id)
+) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1,1,1),(2,1,2);
+SELECT * FROM t1;
+id val1 val2
+1 1 1
+2 1 2
+UPDATE t1 SET val1=2 WHERE id=2;
+SELECT * FROM t1;
+id val1 val2
+1 1 1
+2 2 2
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+SET AUTOCOMMIT=0;
+START TRANSACTION;
+INSERT INTO t1 VALUES(20,1,1),(30,30,30);
+SELECT * FROM t1;
+id val1 val2
+1 1 1
+2 2 2
+20 1 1
+30 30 30
+UPDATE t1 SET val1=20, val2=20 WHERE id=20;
+SELECT * FROM t1;
+id val1 val2
+1 1 1
+2 2 2
+20 20 20
+30 30 30
+DELETE FROM t1 WHERE id=30;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+---SNAPSHOT, ACTIVE NUM sec
+MySQL thread id TID, OS thread handle PTR, query id QID localhost root ACTION
+SHOW ENGINE rocksdb TRANSACTION STATUS
+lock count 8, write count 4
+insert count 2, update count 1, delete count 1
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+ROLLBACK;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+START TRANSACTION;
+INSERT INTO t1 VALUES(40,40,40);
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+---SNAPSHOT, ACTIVE NUM sec
+MySQL thread id TID, OS thread handle PTR, query id QID localhost root ACTION
+SHOW ENGINE rocksdb TRANSACTION STATUS
+lock count 2, write count 1
+insert count 1, update count 0, delete count 0
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+COMMIT;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+SET AUTOCOMMIT=1;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t2 (
+id1 INT,
+id2 INT,
+value INT,
+PRIMARY KEY (id1),
+KEY (id2)
+) ENGINE=rocksdb;
+SET AUTOCOMMIT=0;
+START TRANSACTION;
+INSERT INTO t2 VALUES(1,2,0),(10,20,30);
+UPDATE t2 SET value=3 WHERE id2=2;
+DELETE FROM t2 WHERE id1=10;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+---SNAPSHOT, ACTIVE NUM sec
+MySQL thread id TID, OS thread handle PTR, query id QID localhost root ACTION
+SHOW ENGINE rocksdb TRANSACTION STATUS
+lock count 9, write count 7
+insert count 2, update count 1, delete count 1
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+ROLLBACK;
+SET AUTOCOMMIT=1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue255.result b/storage/rocksdb/mysql-test/rocksdb/r/issue255.result
new file mode 100644
index 00000000..840699dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue255.result
@@ -0,0 +1,68 @@
+CREATE TABLE t1 (pk BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT);
+INSERT INTO t1 VALUES (5);
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 6 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES ('538647864786478647864');
+Warnings:
+Warning 1264 Out of range value for column 'pk' at row 1
+SELECT * FROM t1;
+pk
+5
+9223372036854775807
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 2 22 44 0 0 0 9223372036854775807 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES ();
+ERROR 23000: Duplicate entry '9223372036854775807' for key 'PRIMARY'
+SELECT * FROM t1;
+pk
+5
+9223372036854775807
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 9223372036854775807 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES ();
+ERROR 23000: Duplicate entry '9223372036854775807' for key 'PRIMARY'
+SELECT * FROM t1;
+pk
+5
+9223372036854775807
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 9223372036854775807 # # NULL latin1_swedish_ci NULL 0 N
+DROP TABLE t1;
+CREATE TABLE t1 (pk TINYINT NOT NULL PRIMARY KEY AUTO_INCREMENT);
+INSERT INTO t1 VALUES (5);
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 6 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES (1000);
+Warnings:
+Warning 1264 Out of range value for column 'pk' at row 1
+SELECT * FROM t1;
+pk
+5
+127
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 127 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES ();
+ERROR 23000: Duplicate entry '127' for key 'PRIMARY'
+SELECT * FROM t1;
+pk
+5
+127
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 127 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 VALUES ();
+ERROR 23000: Duplicate entry '127' for key 'PRIMARY'
+SELECT * FROM t1;
+pk
+5
+127
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB # Fixed # # # # # # 127 # # NULL latin1_swedish_ci NULL 0 N
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue290.result b/storage/rocksdb/mysql-test/rocksdb/r/issue290.result
new file mode 100644
index 00000000..1a83a93b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue290.result
@@ -0,0 +1,28 @@
+CREATE TABLE `linktable` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) NOT NULL DEFAULT '',
+`time` bigint(20) unsigned NOT NULL DEFAULT '0',
+`version` int(11) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (link_type, `id1`,`id2`) COMMENT 'cf_link_pk',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,`data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+set global rocksdb_force_flush_memtable_now=1;
+insert into linktable (id1, link_type, id2) values (2, 1, 1);
+insert into linktable (id1, link_type, id2) values (2, 1, 2);
+insert into linktable (id1, link_type, id2) values (2, 1, 3);
+insert into linktable (id1, link_type, id2) values (2, 1, 4);
+insert into linktable (id1, link_type, id2) values (2, 1, 5);
+insert into linktable (id1, link_type, id2) values (2, 1, 6);
+insert into linktable (id1, link_type, id2) values (2, 1, 7);
+insert into linktable (id1, link_type, id2) values (2, 1, 8);
+insert into linktable (id1, link_type, id2) values (2, 1, 9);
+insert into linktable (id1, link_type, id2) values (2, 1, 10);
+explain select id1, id2, link_type, data from linktable force index(primary) where id1=2 and link_type=1 and (id2=1 or id2=2 or id2=3 or id2=4 or id2=5);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE linktable range PRIMARY PRIMARY 24 NULL # Using where
+drop table linktable;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue314.result b/storage/rocksdb/mysql-test/rocksdb/r/issue314.result
new file mode 100644
index 00000000..eee90800
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue314.result
@@ -0,0 +1,12 @@
+drop table if exists t1;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+CREATE TABLE t1(a int);
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+INSERT INTO t1 VALUES(1);
+select * from t1;
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level SERIALIZABLE
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+select * from t1;
+a
+1
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue495.result b/storage/rocksdb/mysql-test/rocksdb/r/issue495.result
new file mode 100644
index 00000000..c7ac34c6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue495.result
@@ -0,0 +1,30 @@
+drop table if exists t;
+create table t (
+a int,
+b int,
+c varchar(12249) collate latin1_bin,
+d datetime,
+e int,
+f int,
+g blob,
+h int,
+i int,
+key (b,e),
+key (h,b)
+) engine=rocksdb
+partition by linear hash (i) partitions 8 ;
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+select i from t group by h;
+i
+1
+select i from t group by h;
+i
+1
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue884.result b/storage/rocksdb/mysql-test/rocksdb/r/issue884.result
new file mode 100644
index 00000000..60c96745
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue884.result
@@ -0,0 +1,80 @@
+create table test (
+a bigint(20) not null,
+b bigint(20) not null,
+c varchar(500) not null,
+d bigint(20) not null,
+e bigint(20) not null,
+f varchar(500) not null,
+g varchar(500) not null,
+h varchar(500) not null,
+i varchar(1000) not null,
+j varchar(16384) not null,
+k varchar(200) not null,
+l varchar(500) not null,
+m varchar(100) not null,
+n bigint(20) not null,
+primary key (a, b, m, c(100), l(100), d, e, f(100), g(100), h(100), n),
+key n (n),
+key d (d, a)
+) engine = rocksdb default charset = latin1;
+Table Op Msg_type Msg_text
+test.test analyze status Engine-independent statistics collected
+test.test analyze status OK
+explain
+select * from test where d = 10 and a = 10 and b = 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE test index_merge PRIMARY,d d,PRIMARY 24,16 NULL # Using intersect(d,PRIMARY); Using where
+select * from test where d = 10 and a = 10 and b = 2;
+a b c d e f g h i j k l m n
+10 2 i 10 950 f g h i j k l m 950
+10 2 i 10 951 f g h i j k l m 951
+10 2 i 10 952 f g h i j k l m 952
+10 2 i 10 953 f g h i j k l m 953
+10 2 i 10 954 f g h i j k l m 954
+10 2 i 10 955 f g h i j k l m 955
+10 2 i 10 956 f g h i j k l m 956
+10 2 i 10 957 f g h i j k l m 957
+10 2 i 10 958 f g h i j k l m 958
+10 2 i 10 959 f g h i j k l m 959
+10 2 i 10 960 f g h i j k l m 960
+10 2 i 10 961 f g h i j k l m 961
+10 2 i 10 962 f g h i j k l m 962
+10 2 i 10 963 f g h i j k l m 963
+10 2 i 10 964 f g h i j k l m 964
+10 2 i 10 965 f g h i j k l m 965
+10 2 i 10 966 f g h i j k l m 966
+10 2 i 10 967 f g h i j k l m 967
+10 2 i 10 968 f g h i j k l m 968
+10 2 i 10 969 f g h i j k l m 969
+10 2 i 10 970 f g h i j k l m 970
+10 2 i 10 971 f g h i j k l m 971
+10 2 i 10 972 f g h i j k l m 972
+10 2 i 10 973 f g h i j k l m 973
+10 2 i 10 974 f g h i j k l m 974
+10 2 i 10 975 f g h i j k l m 975
+10 2 i 10 976 f g h i j k l m 976
+10 2 i 10 977 f g h i j k l m 977
+10 2 i 10 978 f g h i j k l m 978
+10 2 i 10 979 f g h i j k l m 979
+10 2 i 10 980 f g h i j k l m 980
+10 2 i 10 981 f g h i j k l m 981
+10 2 i 10 982 f g h i j k l m 982
+10 2 i 10 983 f g h i j k l m 983
+10 2 i 10 984 f g h i j k l m 984
+10 2 i 10 985 f g h i j k l m 985
+10 2 i 10 986 f g h i j k l m 986
+10 2 i 10 987 f g h i j k l m 987
+10 2 i 10 988 f g h i j k l m 988
+10 2 i 10 989 f g h i j k l m 989
+10 2 i 10 990 f g h i j k l m 990
+10 2 i 10 991 f g h i j k l m 991
+10 2 i 10 992 f g h i j k l m 992
+10 2 i 10 993 f g h i j k l m 993
+10 2 i 10 994 f g h i j k l m 994
+10 2 i 10 995 f g h i j k l m 995
+10 2 i 10 996 f g h i j k l m 996
+10 2 i 10 997 f g h i j k l m 997
+10 2 i 10 998 f g h i j k l m 998
+10 2 i 10 999 f g h i j k l m 999
+10 2 i 10 1000 f g h i j k l m 1000
+drop table test;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue896.result b/storage/rocksdb/mysql-test/rocksdb/r/issue896.result
new file mode 100644
index 00000000..6b742eba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue896.result
@@ -0,0 +1,17 @@
+CREATE TABLE `t1` (
+`a` bigint(20) NOT NULL,
+`b` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+`u` bigint(20) unsigned NOT NULL,
+`d` bigint(20) DEFAULT NULL,
+PRIMARY KEY (`a`,`b`),
+KEY `d` (`d`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='ttl_duration=1000;ttl_col=u';
+INSERT INTO t1 VALUES (100, 'aaabbb', UNIX_TIMESTAMP(), 200);
+EXPLAIN SELECT COUNT(*) FROM t1 FORCE INDEX(d);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL d 9 NULL # Using index
+# segfault here without the fix
+SELECT COUNT(*) FROM t1 FORCE INDEX(d);
+COUNT(*)
+1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/issue900.result b/storage/rocksdb/mysql-test/rocksdb/r/issue900.result
new file mode 100644
index 00000000..062d0da0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/issue900.result
@@ -0,0 +1,11 @@
+CREATE TABLE t1(c1 VARCHAR(1) CHARACTER SET 'utf8' COLLATE 'utf8_bin', c2 YEAR, c3 REAL(1,0) UNSIGNED, PRIMARY KEY(c1)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES(0,'0','0');
+INSERT INTO t1 VALUES('{0}','0','0');
+Warnings:
+Warning 1265 Data truncated for column 'c1' at row 1
+INSERT INTO t1 VALUES('1','0','1');
+ALTER TABLE t1 ADD INDEX(c3), ADD UNIQUE (c3);
+ERROR 23000: Duplicate entry '0' for key 'c3_2'
+SELECT c3 FROM t1 FORCE INDEX(c3) ORDER BY c3;
+ERROR 42000: Key 'c3' doesn't exist in table 't1'
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/iterator_bounds.result b/storage/rocksdb/mysql-test/rocksdb/r/iterator_bounds.result
new file mode 100644
index 00000000..600f19e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/iterator_bounds.result
@@ -0,0 +1,15 @@
+create table t (i int primary key) engine=rocksdb;
+drop table t;
+create table t (i int primary key, j int, key(j) comment 'rev:bf5_2') engine=rocksdb;
+select RIGHT(HEX(index_number), 2) from information_schema.rocksdb_ddl where table_name = 't';
+RIGHT(HEX(index_number), 2)
+FE
+FF
+insert into t values (1, 1);
+select j from t order by j asc;
+j
+1
+select j from t order by j desc;
+j
+1
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/kill.result b/storage/rocksdb/mysql-test/rocksdb/r/kill.result
new file mode 100644
index 00000000..19614e1f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/kill.result
@@ -0,0 +1,6 @@
+CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+LOCK TABLE t1 WRITE;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+FLUSH TABLES;
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level SERIALIZABLE
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/level_read_committed.result b/storage/rocksdb/mysql-test/rocksdb/r/level_read_committed.result
new file mode 100644
index 00000000..b68b37cf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/level_read_committed.result
@@ -0,0 +1,111 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connection con1;
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+START TRANSACTION;
+SELECT a FROM t1;
+a
+connection con2;
+BEGIN;
+INSERT INTO t1 (a) VALUES(1);
+connection con1;
+SELECT a FROM t1;
+a
+connection con2;
+INSERT INTO t1 (a) VALUES (2);
+connection con1;
+SELECT a FROM t1;
+a
+INSERT INTO t1 (a) SELECT a+100 FROM t1;
+SELECT a FROM t1;
+a
+connection con2;
+SELECT a FROM t1;
+a
+1
+2
+COMMIT;
+SELECT a FROM t1;
+a
+1
+2
+connection con1;
+SELECT a FROM t1;
+a
+1
+2
+INSERT INTO t1 (a) SELECT a+200 FROM t1;
+SELECT a FROM t1;
+a
+1
+2
+201
+202
+COMMIT;
+SELECT a FROM t1;
+a
+1
+2
+201
+202
+connection con2;
+SELECT a FROM t1;
+a
+1
+2
+201
+202
+connection default;
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (a) VALUES (1);
+COMMIT;
+connection con1;
+BEGIN;
+SELECT a from t2;
+a
+1
+INSERT INTO t2 (a) VALUES (1), (3);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+connection con2;
+INSERT INTO t2 (a) VALUES (2);
+COMMIT;
+connection con1;
+SELECT a from t2;
+a
+1
+2
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
+CREATE TABLE t3 (
+pk int unsigned PRIMARY KEY,
+count int unsigned DEFAULT '0'
+) ENGINE=ROCKSDB;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+connection con1;
+BEGIN;
+SELECT * FROM t3;
+pk count
+connection con2;
+BEGIN;
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+connection con1;
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+SELECT count FROM t3;
+count
+1
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/level_read_uncommitted.result b/storage/rocksdb/mysql-test/rocksdb/r/level_read_uncommitted.result
new file mode 100644
index 00000000..68fbe563
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/level_read_uncommitted.result
@@ -0,0 +1,116 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+connection con1;
+CREATE TABLE t1 (a <INT_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+START TRANSACTION;
+SELECT a FROM t1;
+a
+connection con2;
+BEGIN;
+INSERT INTO t1 (a) VALUES(1);
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+connection con1;
+SELECT a FROM t1;
+a
+1
+connection con2;
+INSERT INTO t1 (a) VALUES (2);
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+connection con1;
+SELECT a FROM t1;
+a
+1
+2
+INSERT INTO t1 (a) SELECT a+100 FROM t1;
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+connection con2;
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+COMMIT;
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+connection con1;
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+INSERT INTO t1 (a) SELECT a+200 FROM t1;
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+201
+202
+301
+302
+COMMIT;
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+201
+202
+301
+302
+connection con2;
+SELECT a FROM t1;
+a
+1
+101
+102
+2
+201
+202
+301
+302
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (a <INT_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection con2;
+INSERT INTO t1 (a) VALUES (1);
+connection con1;
+# If consistent read works on this isolation level (READ UNCOMMITTED), the following SELECT should not return the value we inserted (1)
+SELECT a FROM t1;
+a
+1
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/level_repeatable_read.result b/storage/rocksdb/mysql-test/rocksdb/r/level_repeatable_read.result
new file mode 100644
index 00000000..13da8a0f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/level_repeatable_read.result
@@ -0,0 +1,100 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connection con1;
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+START TRANSACTION;
+SELECT a FROM t1;
+a
+connection con2;
+BEGIN;
+INSERT INTO t1 (a) VALUES(1);
+connection con1;
+SELECT a FROM t1;
+a
+connection con2;
+INSERT INTO t1 (a) VALUES (2);
+connection con1;
+SELECT a FROM t1;
+a
+INSERT INTO t1 (a) SELECT a+100 FROM t1;
+SELECT a FROM t1;
+a
+connection con2;
+SELECT a FROM t1;
+a
+1
+2
+COMMIT;
+SELECT a FROM t1;
+a
+1
+2
+connection con1;
+SELECT a FROM t1;
+a
+INSERT INTO t1 (a) SELECT a+200 FROM t1;
+SELECT a FROM t1;
+a
+COMMIT;
+SELECT a FROM t1;
+a
+1
+2
+connection con2;
+SELECT a FROM t1;
+a
+1
+2
+connection default;
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (a) VALUES (1);
+COMMIT;
+connection con1;
+BEGIN;
+SELECT a from t2;
+a
+1
+INSERT INTO t2 (a) VALUES (1), (3);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+connection con2;
+INSERT INTO t2 (a) VALUES (2);
+COMMIT;
+connection con1;
+SELECT a from t2;
+a
+1
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
+CREATE TABLE t3 (
+pk int unsigned PRIMARY KEY,
+count int unsigned DEFAULT '0'
+) ENGINE=ROCKSDB;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+connection con1;
+BEGIN;
+SELECT * FROM t3;
+pk count
+connection con2;
+BEGIN;
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+connection con1;
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+SELECT count FROM t3;
+count
+0
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/level_serializable.result b/storage/rocksdb/mysql-test/rocksdb/r/level_serializable.result
new file mode 100644
index 00000000..3f57395f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/level_serializable.result
@@ -0,0 +1,56 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+connect con2,localhost,root,,;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+connection con1;
+CREATE TABLE t1 (a <INT_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+START TRANSACTION;
+SELECT a FROM t1;
+a
+connection con2;
+BEGIN;
+INSERT INTO t1 (a) VALUES(1);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# WARNING: Statement ended with errno 1205, errname 'ER_LOCK_WAIT_TIMEOUT'.
+# If it differs from the result file, it might indicate a problem.
+connection con1;
+SELECT a FROM t1;
+a
+connection con2;
+INSERT INTO t1 (a) VALUES (2);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# WARNING: Statement ended with errno 1205, errname 'ER_LOCK_WAIT_TIMEOUT'.
+# If it differs from the result file, it might indicate a problem.
+connection con1;
+SELECT a FROM t1;
+a
+INSERT INTO t1 (a) SELECT a+100 FROM t1;
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+SELECT a FROM t1;
+a
+connection con2;
+SELECT a FROM t1;
+a
+COMMIT;
+SELECT a FROM t1;
+a
+connection con1;
+SELECT a FROM t1;
+a
+INSERT INTO t1 (a) SELECT a+200 FROM t1;
+# WARNING: Statement ended with errno 0, errname ''.
+# If it differs from the result file, it might indicate a problem.
+SELECT a FROM t1;
+a
+COMMIT;
+SELECT a FROM t1;
+a
+connection con2;
+SELECT a FROM t1;
+a
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/loaddata.result b/storage/rocksdb/mysql-test/rocksdb/r/loaddata.result
new file mode 100644
index 00000000..a9f9c0b4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/loaddata.result
@@ -0,0 +1,239 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ',' (a,b);
+SELECT a,b FROM t1;
+a b
+1 foo
+2 bar
+3
+4 abc
+LOAD DATA LOCAL INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+CHARACTER SET utf8 COLUMNS TERMINATED BY ','
+ ESCAPED BY '/' (a,b);
+SELECT a,b FROM t1;
+a b
+1 foo
+1 foo
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+LOAD DATA LOCAL INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ';'
+ (a) SET b='loaded';
+Warnings:
+Warning 1262 Row 1 was truncated; it contained more data than there were input columns
+Warning 1262 Row 2 was truncated; it contained more data than there were input columns
+Warning 1262 Row 3 was truncated; it contained more data than there were input columns
+SELECT a,b FROM t1;
+a b
+0 loaded
+1 foo
+1 foo
+102 loaded
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+5 loaded
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ';'
+ OPTIONALLY ENCLOSED BY ''''
+ LINES STARTING BY 'prefix:'
+IGNORE 2 LINES (a,b);
+Warnings:
+Warning 1262 Row 2 was truncated; it contained more data than there were input columns
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+5 loaded
+7 test
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1;
+Warnings:
+Warning 1261 Row 1 doesn't contain data for all columns
+Warning 1261 Row 2 doesn't contain data for all columns
+Warning 1261 Row 3 doesn't contain data for all columns
+Warning 1261 Row 4 doesn't contain data for all columns
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 foo
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+2 bar
+3
+3
+3
+4 abc
+4 abc
+4 abc
+5 loaded
+7 test
+LOAD DATA INFILE '<DATADIR>/se_replacedata.dat' REPLACE INTO TABLE t1;
+Warnings:
+Warning 1261 Row 1 doesn't contain data for all columns
+Warning 1261 Row 2 doesn't contain data for all columns
+Warning 1261 Row 3 doesn't contain data for all columns
+Warning 1261 Row 4 doesn't contain data for all columns
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 aaa
+1 foo
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+2 bar
+2 bbb
+3
+3
+3
+3 ccc
+4 abc
+4 abc
+4 abc
+4 ddd
+5 loaded
+7 test
+DROP TABLE t1;
+set session unique_checks=0;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ',' (a,b);
+SELECT a,b FROM t1;
+a b
+1 foo
+2 bar
+3
+4 abc
+LOAD DATA LOCAL INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+CHARACTER SET utf8 COLUMNS TERMINATED BY ','
+ ESCAPED BY '/' (a,b);
+SELECT a,b FROM t1;
+a b
+1 foo
+1 foo
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+LOAD DATA LOCAL INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ';'
+ (a) SET b='loaded';
+Warnings:
+Warning 1262 Row 1 was truncated; it contained more data than there were input columns
+Warning 1262 Row 2 was truncated; it contained more data than there were input columns
+Warning 1262 Row 3 was truncated; it contained more data than there were input columns
+SELECT a,b FROM t1;
+a b
+0 loaded
+1 foo
+1 foo
+102 loaded
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+5 loaded
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1
+FIELDS TERMINATED BY ';'
+ OPTIONALLY ENCLOSED BY ''''
+ LINES STARTING BY 'prefix:'
+IGNORE 2 LINES (a,b);
+Warnings:
+Warning 1262 Row 2 was truncated; it contained more data than there were input columns
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+3
+3
+4 abc
+4 abc
+5 loaded
+7 test
+LOAD DATA INFILE '<DATADIR>/se_loaddata.dat' INTO TABLE t1;
+Warnings:
+Warning 1261 Row 1 doesn't contain data for all columns
+Warning 1261 Row 2 doesn't contain data for all columns
+Warning 1261 Row 3 doesn't contain data for all columns
+Warning 1261 Row 4 doesn't contain data for all columns
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 foo
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+2 bar
+3
+3
+3
+4 abc
+4 abc
+4 abc
+5 loaded
+7 test
+LOAD DATA INFILE '<DATADIR>/se_replacedata.dat' REPLACE INTO TABLE t1;
+ERROR HY000: When unique checking is disabled in MyRocks, INSERT,UPDATE,LOAD statements with clauses that update or replace the key (i.e. INSERT ON DUPLICATE KEY UPDATE, REPLACE) are not allowed. Query: LOAD DATA INFILE '<DATADIR>/se_replacedata.dat' REPLACE INTO TABLE t1
+SELECT a,b FROM t1;
+a b
+0
+0 loaded
+1 foo
+1 foo
+1 foo
+100 foo
+102 loaded
+2 bar
+2 bar
+2 bar
+3
+3
+3
+4 abc
+4 abc
+4 abc
+5 loaded
+7 test
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/lock.result b/storage/rocksdb/mysql-test/rocksdb/r/lock.result
new file mode 100644
index 00000000..7c4f5477
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/lock.result
@@ -0,0 +1,121 @@
+connect con1,localhost,root,,;
+SET lock_wait_timeout=1;
+connection default;
+DROP TABLE IF EXISTS t1, t2, t3;
+CREATE TABLE t1 (id INT, id2 INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (id,id2) VALUES (1,1),(1,2),(1,3);
+LOCK TABLE t1 LOW_PRIORITY WRITE;
+SELECT id2,COUNT(DISTINCT id) FROM t1 GROUP BY id2;
+id2 COUNT(DISTINCT id)
+1 1
+2 1
+3 1
+UPDATE t1 SET id=-1 WHERE id=1;
+connection con1;
+SELECT id,id2 FROM t1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+LOCK TABLE t1 READ;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection default;
+LOCK TABLE t1 READ;
+UPDATE t1 SET id=1 WHERE id=1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+connection con1;
+SELECT COUNT(DISTINCT id) FROM t1;
+COUNT(DISTINCT id)
+1
+UPDATE t1 SET id=2 WHERE id=2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+LOCK TABLE t1 WRITE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+LOCK TABLE t1 READ;
+UNLOCK TABLES;
+connection default;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+UNLOCK TABLES;
+CREATE TABLE t2 (id INT, id2 INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLE t1 WRITE, t2 WRITE;
+INSERT INTO t2 (id,id2) SELECT id,id2 FROM t1;
+UPDATE t1 SET id=1 WHERE id=-1;
+DROP TABLE t1,t2;
+CREATE TABLE t1 (i1 INT, nr INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+CREATE TABLE t2 (nr INT, nm INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (nr,nm) VALUES (1,3);
+INSERT INTO t2 (nr,nm) VALUES (2,4);
+LOCK TABLES t1 WRITE, t2 READ;
+INSERT INTO t1 (i1,nr) SELECT 1, nr FROM t2 WHERE nm=3;
+INSERT INTO t1 (i1,nr) SELECT 2, nr FROM t2 WHERE nm=4;
+UNLOCK TABLES;
+LOCK TABLES t1 WRITE;
+INSERT INTO t1 (i1,nr) SELECT i1, nr FROM t1;
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
+UNLOCK TABLES;
+LOCK TABLES t1 WRITE, t1 AS t1_alias READ;
+INSERT INTO t1 (i1,nr) SELECT i1, nr FROM t1 AS t1_alias;
+DROP TABLE t1,t2;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+UNLOCK TABLES;
+DROP TABLE t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+DROP TABLE t2, t3, t1;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t1 AS t4 READ;
+ALTER TABLE t2 ADD COLUMN c2 INT;
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+LOCK TABLE t1 READ, t2 READ;
+FLUSH TABLE t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+FLUSH TABLES;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+FLUSH TABLES t1, t2 WITH READ LOCK;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+UNLOCK TABLES;
+FLUSH TABLES t1, t2 WITH READ LOCK;
+connection con1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection default;
+UNLOCK TABLES;
+FLUSH TABLES WITH READ LOCK;
+connection con1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection default;
+UNLOCK TABLES;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+FLUSH TABLES WITH READ LOCK;
+DROP TABLE t1, t2;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+disconnect con1;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+LOCK TABLE t1 WRITE, t2 WRITE;
+SELECT a,b FROM t1;
+a b
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (i INT) ENGINE=MyISAM;
+HANDLER t1 OPEN h;
+CREATE TABLE t2 (i INT) ENGINE=RocksDB;
+LOCK TABLES t2 WRITE;
+connect con1,localhost,root,,test;
+connection con1;
+FLUSH TABLES WITH READ LOCK;
+connection default;
+INSERT INTO t2 VALUES (1);
+UNLOCK TABLES;
+HANDLER h CLOSE;
+connection con1;
+disconnect con1;
+connection default;
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/lock_info.result b/storage/rocksdb/mysql-test/rocksdb/r/lock_info.result
new file mode 100644
index 00000000..d0f1221e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/lock_info.result
@@ -0,0 +1,31 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+create table t1 (a int, primary key(a) comment 'lock_into_cf1') engine=rocksdb;
+insert into t1 values (1);
+insert into t1 values (2);
+create table t2 (a int, primary key(a) comment 'lock_info_cf2') engine=rocksdb;
+insert into t2 values (1);
+insert into t2 values (2);
+set autocommit=0;
+select * from t1 for update;
+a
+1
+2
+select * from t2 for update;
+a
+1
+2
+use information_schema;
+select rocksdb_ddl.cf, rocksdb_locks.transaction_id, rocksdb_locks.key
+from rocksdb_locks
+left join rocksdb_ddl
+on rocksdb_locks.column_family_id=rocksdb_ddl.column_family
+order by rocksdb_ddl.cf;
+cf transaction_id key
+lock_info_cf2 _txn_id_ _key_
+lock_info_cf2 _txn_id_ _key_
+lock_into_cf1 _txn_id_ _key_
+lock_into_cf1 _txn_id_ _key_
+use test;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/lock_rows_not_exist.result b/storage/rocksdb/mysql-test/rocksdb/r/lock_rows_not_exist.result
new file mode 100644
index 00000000..7898489d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/lock_rows_not_exist.result
@@ -0,0 +1,40 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t (id1 int, id2 int, id3 int, value int, PRIMARY KEY (id1, id2, id3)) ENGINE=RocksDB;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+id1 id2 id3 value
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=2 FOR UPDATE;
+id1 id2 id3 value
+connection con1;
+ROLLBACK;
+BEGIN;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+connection con2;
+ROLLBACK;
+BEGIN;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=0 AND id3=1;
+connection con1;
+ROLLBACK;
+BEGIN;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+connection con2;
+ROLLBACK;
+BEGIN;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=0;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/lock_wait_timeout_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/lock_wait_timeout_stats.result
new file mode 100644
index 00000000..96efca6e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/lock_wait_timeout_stats.result
@@ -0,0 +1,35 @@
+create table t (a int primary key) engine=rocksdb;
+begin;
+insert into t values (0);
+set @@rocksdb_lock_wait_timeout=1;
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+ROW_LOCK_WAIT_TIMEOUTS
+0
+begin;
+set @@rocksdb_lock_wait_timeout=1;
+begin;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+insert into t values(0);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on index: test.t.PRIMARY
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+ROW_LOCK_WAIT_TIMEOUTS
+1
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+ROW_LOCK_WAIT_TIMEOUTS
+1
+select case when variable_value-@a = 1 then 'true' else 'false' end as waits from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+waits
+true
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+insert into t values(0);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on index: test.t.PRIMARY
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+ROW_LOCK_WAIT_TIMEOUTS
+2
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+ROW_LOCK_WAIT_TIMEOUTS
+2
+select case when variable_value-@a = 1 then 'true' else 'false' end as waits from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+waits
+true
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues.result
new file mode 100644
index 00000000..84c93481
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues.result
@@ -0,0 +1 @@
+tests moved to rocksdb.locking_issues_case*
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rc.result
new file mode 100644
index 00000000..a47aa3c7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rc.result
@@ -0,0 +1,30 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 1.1:
+- Locking rows that do not exist when using all primary key columns in
+- a WHERE clause
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+id1 id2 value
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+INSERT INTO t0 VALUES (1,5,0);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rr.result
new file mode 100644
index 00000000..c923c34c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_1_rr.result
@@ -0,0 +1,30 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 1.1:
+- Locking rows that do not exist when using all primary key columns in
+- a WHERE clause
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+id1 id2 value
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+INSERT INTO t0 VALUES (1,5,0);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+SELECT * FROM t0 WHERE id1=1 AND id2=5 FOR UPDATE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rc.result
new file mode 100644
index 00000000..01c4e7e3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rc.result
@@ -0,0 +1,30 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 1.2:
+- Locking rows that do not exist without using all primary key
+- columns in a WHERE clause
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 FOR UPDATE;
+id1 id2 value
+1 1 0
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=4 FOR UPDATE;
+id1 id2 value
+INSERT INTO t0 VALUES (1,5,0);
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rr.result
new file mode 100644
index 00000000..798fd15b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case1_2_rr.result
@@ -0,0 +1,30 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 1.2:
+- Locking rows that do not exist without using all primary key
+- columns in a WHERE clause
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id1 INT, id2 INT, value INT, PRIMARY KEY(id1, id2));
+INSERT INTO t0 VALUES (1,1,0), (3,3,0), (4,4,0), (6,6,0);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 FOR UPDATE;
+id1 id2 value
+1 1 0
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t0 WHERE id1=1 AND id2=4 FOR UPDATE;
+id1 id2 value
+INSERT INTO t0 VALUES (1,5,0);
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc.result
new file mode 100644
index 00000000..16480da8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc.result
@@ -0,0 +1,50 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 2:
+- Rows that are scanned but do not match the WHERE are not locked
+- using READ COMMITTED transaction isolation level unless
+- rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+CREATE TABLE t0(id INT PRIMARY KEY, value INT);
+INSERT INTO t0 VALUES (1,0), (2,1), (3,0), (4,0), (5,1);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con1;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+id value
+2 1
+5 1
+connection con2;
+UPDATE t0 SET VALUE=10 WHERE id=1;
+UPDATE t0 SET VALUE=10 WHERE id=5;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+UPDATE t0 SET value=100 WHERE id in (4,5) and value>0;
+connection con2;
+SELECT * FROM t0 WHERE id=4 FOR UPDATE;
+id value
+4 0
+COMMIT;
+SELECT * FROM t0;
+id value
+1 10
+2 1
+3 0
+4 0
+5 1
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc_lsr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc_lsr.result
new file mode 100644
index 00000000..330cd09d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rc_lsr.result
@@ -0,0 +1,37 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 2:
+- Rows that are scanned but do not match the WHERE are not locked
+- using READ COMMITTED transaction isolation level unless
+- rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+SET GLOBAL rocksdb_lock_scanned_rows=ON;
+CREATE TABLE t0(id INT PRIMARY KEY, value INT);
+INSERT INTO t0 VALUES (1,0), (2,1), (3,0), (4,0), (5,1);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con1;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+id value
+2 1
+5 1
+connection con2;
+UPDATE t0 SET VALUE=10 WHERE id=1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
+SET GLOBAL rocksdb_lock_scanned_rows=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr.result
new file mode 100644
index 00000000..3e6b63af
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr.result
@@ -0,0 +1,50 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 2:
+- Rows that are scanned but do not match the WHERE are not locked
+- using REPEATABLE READ transaction isolation level unless
+- rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+CREATE TABLE t0(id INT PRIMARY KEY, value INT);
+INSERT INTO t0 VALUES (1,0), (2,1), (3,0), (4,0), (5,1);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con1;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+id value
+2 1
+5 1
+connection con2;
+UPDATE t0 SET VALUE=10 WHERE id=1;
+UPDATE t0 SET VALUE=10 WHERE id=5;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+UPDATE t0 SET value=100 WHERE id in (4,5) and value>0;
+connection con2;
+SELECT * FROM t0 WHERE id=4 FOR UPDATE;
+id value
+4 0
+COMMIT;
+SELECT * FROM t0;
+id value
+1 10
+2 1
+3 0
+4 0
+5 1
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr_lsr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr_lsr.result
new file mode 100644
index 00000000..088e975e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case2_rr_lsr.result
@@ -0,0 +1,37 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 2:
+- Rows that are scanned but do not match the WHERE are not locked
+- using REPEATABLE READ transaction isolation level unless
+- rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+SET GLOBAL rocksdb_lock_scanned_rows=ON;
+CREATE TABLE t0(id INT PRIMARY KEY, value INT);
+INSERT INTO t0 VALUES (1,0), (2,1), (3,0), (4,0), (5,1);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con1;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+id value
+2 1
+5 1
+connection con2;
+UPDATE t0 SET VALUE=10 WHERE id=1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
+SET GLOBAL rocksdb_lock_scanned_rows=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rc.result
new file mode 100644
index 00000000..9a6f02cd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rc.result
@@ -0,0 +1,25 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 3:
+- After creating a snapshot, other clients updating rows
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+UPDATE t0 SET VALUE=VALUE+1 WHERE id=190000;
+connection con1;
+id value
+190000 1
+ERROR: 0
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rr.result
new file mode 100644
index 00000000..fdd4d8fc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case3_rr.result
@@ -0,0 +1,23 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 3:
+- After creating a snapshot, other clients updating rows
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+UPDATE t0 SET VALUE=VALUE+1 WHERE id=190000;
+connection con1;
+ERROR: 1213
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rc.result
new file mode 100644
index 00000000..1a35d276
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rc.result
@@ -0,0 +1,23 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 4:
+- Phantom rows
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+INSERT INTO t0 VALUES(200001,1), (-1,1);
+connection con1;
+id value
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rr.result
new file mode 100644
index 00000000..7ecb1ac5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case4_rr.result
@@ -0,0 +1,23 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 4:
+- Phantom rows
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+INSERT INTO t0 VALUES(200001,1), (-1,1);
+connection con1;
+id value
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rc.result
new file mode 100644
index 00000000..4d707e83
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rc.result
@@ -0,0 +1,29 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 5:
+- Deleting primary key
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+UPDATE t0 SET value=100 WHERE id=190000;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+DELETE FROM t0 WHERE id=190000;
+COMMIT;
+connection con1;
+id value
+ERROR: 0
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rr.result
new file mode 100644
index 00000000..0ebfe8e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case5_rr.result
@@ -0,0 +1,28 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 5:
+- Deleting primary key
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+UPDATE t0 SET value=100 WHERE id=190000;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+DELETE FROM t0 WHERE id=190000;
+COMMIT;
+connection con1;
+ERROR: 1213
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rc.result
new file mode 100644
index 00000000..f16ffbea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rc.result
@@ -0,0 +1,29 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 6:
+- Changing primary key
+- using READ COMMITTED transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+UPDATE t0 SET value=100 WHERE id=190000;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+UPDATE t0 SET id=200001 WHERE id=190000;
+COMMIT;
+connection con1;
+id value
+ERROR: 0
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rr.result
new file mode 100644
index 00000000..c2323937
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case6_rr.result
@@ -0,0 +1,28 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 6:
+- Changing primary key
+- using REPEATABLE READ transaction isolation level
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t0;
+CREATE TABLE t0(id INT AUTO_INCREMENT PRIMARY KEY, value INT);
+Inserting 200,000 rows
+UPDATE t0 SET value=100 WHERE id=190000;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t0 WHERE value > 0 FOR UPDATE;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+UPDATE t0 SET id=200001 WHERE id=190000;
+COMMIT;
+connection con1;
+ERROR: 1213
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc.result
new file mode 100644
index 00000000..f30dbab0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc.result
@@ -0,0 +1,41 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 7:
+- Rows that are scanned as part of a query but not in the table being
+- updated should not be locked unless rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t1, t2;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+CREATE TABLE t1(id INT PRIMARY KEY, value INT);
+CREATE TABLE t2(id INT PRIMARY KEY, value INT);
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3);
+INSERT INTO t2 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+lock_scanned_rows is 0
+connection con1;
+UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+connection con2;
+UPDATE t2 SET value=value+100;
+SELECT * FROM t2;
+id value
+1 101
+2 102
+3 103
+4 104
+5 105
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc_lsr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc_lsr.result
new file mode 100644
index 00000000..d43e3efb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rc_lsr.result
@@ -0,0 +1,45 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 7:
+- Rows that are scanned as part of a query but not in the table being
+- updated should not be locked unless rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t1, t2;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+SET GLOBAL rocksdb_lock_scanned_rows=ON;
+CREATE TABLE t1(id INT PRIMARY KEY, value INT);
+CREATE TABLE t2(id INT PRIMARY KEY, value INT);
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3);
+INSERT INTO t2 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+lock_scanned_rows is 1
+connection con1;
+UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+connection con2;
+UPDATE t2 SET value=value+100 WHERE id=3;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t2 SET value=value+100 WHERE id IN (1,2,4,5);
+SELECT * FROM t2;
+id value
+1 101
+2 102
+3 3
+4 104
+5 105
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
+SET GLOBAL rocksdb_lock_scanned_rows=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr.result
new file mode 100644
index 00000000..dbcb0a03
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr.result
@@ -0,0 +1,41 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 7:
+- Rows that are scanned as part of a query but not in the table being
+- updated should not be locked unless rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t1, t2;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+CREATE TABLE t1(id INT PRIMARY KEY, value INT);
+CREATE TABLE t2(id INT PRIMARY KEY, value INT);
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3);
+INSERT INTO t2 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+lock_scanned_rows is 0
+connection con1;
+UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+connection con2;
+UPDATE t2 SET value=value+100;
+SELECT * FROM t2;
+id value
+1 101
+2 102
+3 103
+4 104
+5 105
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr_lsr.result b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr_lsr.result
new file mode 100644
index 00000000..d9f7e333
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/locking_issues_case7_rr_lsr.result
@@ -0,0 +1,45 @@
+
+-----------------------------------------------------------------------
+- Locking issues case 7:
+- Rows that are scanned as part of a query but not in the table being
+- updated should not be locked unless rocksdb_lock_scanned_rows is on
+-----------------------------------------------------------------------
+DROP TABLE IF EXISTS t1, t2;
+SELECT @@global.rocksdb_lock_scanned_rows;
+@@global.rocksdb_lock_scanned_rows
+0
+SET GLOBAL rocksdb_lock_scanned_rows=ON;
+CREATE TABLE t1(id INT PRIMARY KEY, value INT);
+CREATE TABLE t2(id INT PRIMARY KEY, value INT);
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3);
+INSERT INTO t2 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+lock_scanned_rows is 1
+connection con1;
+UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.value=t1.value+100 WHERE t2.id=3;
+connection con2;
+UPDATE t2 SET value=value+100 WHERE id=3;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t2 SET value=value+100 WHERE id IN (1,2,4,5);
+SELECT * FROM t2;
+id value
+1 101
+2 102
+3 3
+4 104
+5 105
+connection con1;
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
+SET GLOBAL rocksdb_lock_scanned_rows=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mariadb_ignore_dirs.result b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_ignore_dirs.result
new file mode 100644
index 00000000..9b91cdb5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_ignore_dirs.result
@@ -0,0 +1,9 @@
+#
+# RocksDB plugin adds #rocksdb to ignore_db_dirs
+#
+select @@ignore_db_dirs;
+@@ignore_db_dirs
+#rocksdb
+select @@ignore_db_dirs;
+@@ignore_db_dirs
+aa,bbb,#rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mariadb_misc_binlog.result b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_misc_binlog.result
new file mode 100644
index 00000000..c37ab946
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_misc_binlog.result
@@ -0,0 +1,33 @@
+create table t1 (a int) engine=rocksdb;
+# Should have binlog ON
+select @@log_bin;
+@@log_bin
+1
+set binlog_format='row';
+# Should succeed
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+#
+# MDEV-13602: rocksdb.index_merge_rocksdb2 failed in buildbot
+#
+lock tables t1 write;
+insert into t1 values(1);
+unlock tables;
+set @tmp_bf= @@binlog_format;
+set binlog_format='STATEMENT';
+lock tables t1 write;
+insert into t1 values(1);
+ERROR HY000: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging.
+unlock tables;
+set @@binlog_format=@tmp_bf;
+drop table t1;
+#
+# MDEV-17045: MyRocks tables cannot be updated when binlog_format=MIXED.
+#
+set @tmp_bf= @@binlog_format;
+set binlog_format='MIXED';
+create table t1 (pk int primary key) engine=rocksdb;
+insert into t1 values (1);
+drop table t1;
+set @@binlog_format=@tmp_bf;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mariadb_plugin.result b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_plugin.result
new file mode 100644
index 00000000..6d6cb1db
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_plugin.result
@@ -0,0 +1,35 @@
+#
+# MDEV-14843: Assertion `s_tx_list.size() == 0' failed in myrocks::Rdb_transaction::term_mutex
+#
+INSTALL SONAME 'ha_rocksdb';
+connect con1,localhost,root,,test;
+CREATE TABLE t1 (i INT) ENGINE=RocksDB;
+insert into t1 values (1);
+DROP TABLE t1;
+connection default;
+UNINSTALL SONAME 'ha_rocksdb';
+Warnings:
+Warning 1620 Plugin is busy and will be uninstalled on shutdown
+SELECT ENGINE, SUPPORT FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='ROCKSDB';
+ENGINE SUPPORT
+ROCKSDB NO
+disconnect con1;
+#
+# MDEV-15686: Loading MyRocks plugin back after it has been unloaded causes a crash
+#
+call mtr.add_suppression("Plugin 'ROCKSDB.*' init function returned error.");
+call mtr.add_suppression("Plugin 'ROCKSDB.*' registration as a INFORMATION SCHEMA failed.");
+call mtr.add_suppression("Plugin 'ROCKSDB' registration as a STORAGE ENGINE failed");
+#
+# There are two possible scenarios:
+# ha_rocksdb.{dll,so} is still loaded into mysqld's address space. Its
+# global variables are in the state that doesn't allow it to be
+# initialized back (this is what MDEV-15686 is about). This is handled
+# by intentionally returning an error from rocksdb_init_func.
+#
+# The second case is when ha_rocksdb.{ddl,so} has been fully unloaded
+# and so it will be now loaded as if it happens for the first time.
+INSTALL SONAME 'ha_rocksdb';
+# Whatever happened on the previous step, restore things to the way they
+# were at testcase start.
+UNINSTALL SONAME 'ha_rocksdb';
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_fixes.result b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_fixes.result
new file mode 100644
index 00000000..730e12d0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_fixes.result
@@ -0,0 +1,119 @@
+#
+# MDEV-14433: RocksDB may show empty or incorrect output with rocksdb_strict_collation_check=off
+#
+set global rocksdb_strict_collation_check=off;
+set @tmp_rscc=@@rocksdb_strict_collation_check;
+CREATE TABLE t1(
+a varchar(10) NOT NULL,
+b char(1) DEFAULT 'X',
+c char(2) NOT NULL DEFAULT '??',
+d varchar(10) NOT NULL,
+e int(11) DEFAULT 0,
+PRIMARY KEY (a,d),
+KEY (e)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+insert into t1 select 1,1,1,1,0;
+insert into t1 select 2,1,1,1,0;
+insert into t1 select 3,1,1,1,0;
+explain
+select a from t1 force index(e) where e<10000;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range e e 5 NULL # Using index condition
+select a from t1;
+a
+1
+2
+3
+select * from t1;
+a b c d e
+1 1 1 1 0
+2 1 1 1 0
+3 1 1 1 0
+DROP TABLE t1;
+#
+# MDEV-14563: Wrong query plan for query with no PK
+#
+CREATE TABLE t1(
+pk int primary key,
+a varchar(10) NOT NULL,
+e int(11) DEFAULT 0,
+KEY (a)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+insert into t1 values (1,1,1),(2,2,2);
+explain select a from t1 where a <'zzz';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range a a 32 NULL # Using where
+CREATE TABLE t2(
+pk int,
+a varchar(10) NOT NULL,
+e int(11) DEFAULT 0,
+KEY (a)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+insert into t2 values (1,1,1),(2,2,2);
+explain select a from t2 where a <'zzz';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 32 NULL # Using where
+drop table t1,t2;
+set global rocksdb_strict_collation_check=@tmp_rscc;
+#
+# MDEV-14389: MyRocks and NOPAD collations
+#
+create table t1 (pk varchar(10) collate latin1_nopad_bin, primary key(pk)) engine=rocksdb;
+ERROR HY000: MyRocks doesn't currently support collations with "No pad" attribute.
+set global rocksdb_strict_collation_check=off;
+create table t1 (pk varchar(10) collate latin1_nopad_bin, primary key(pk)) engine=rocksdb;
+ERROR HY000: MyRocks doesn't currently support collations with "No pad" attribute.
+set global rocksdb_strict_collation_check=@tmp_rscc;
+#
+# MDEV-14679: RocksdB plugin fails to load with "Loading of unknown plugin ROCKSDB_CFSTATS
+#
+select plugin_name, plugin_maturity from information_schema.plugins where plugin_name like '%rocksdb%';
+plugin_name plugin_maturity
+ROCKSDB Stable
+ROCKSDB_CFSTATS Stable
+ROCKSDB_DBSTATS Stable
+ROCKSDB_PERF_CONTEXT Stable
+ROCKSDB_PERF_CONTEXT_GLOBAL Stable
+ROCKSDB_CF_OPTIONS Stable
+ROCKSDB_COMPACTION_STATS Stable
+ROCKSDB_GLOBAL_INFO Stable
+ROCKSDB_DDL Stable
+ROCKSDB_SST_PROPS Stable
+ROCKSDB_INDEX_FILE_MAP Stable
+ROCKSDB_LOCKS Stable
+ROCKSDB_TRX Stable
+ROCKSDB_DEADLOCK Stable
+#
+# MDEV-12466 : Assertion `thd->transaction.stmt.is_empty() || thd->in_sub_stmt || ...
+#
+CREATE TABLE t1 (i INT) ENGINE=RocksDB;
+FLUSH TABLE t1 FOR EXPORT;
+ERROR HY000: Storage engine ROCKSDB of the table `test`.`t1` doesn't have this option
+DROP TABLE t1;
+#
+# MDEV-16154 Server crashes in in myrocks::ha_rocksdb::load_auto_incr_value_from_inde
+#
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1);
+ALTER TABLE t1 AUTO_INCREMENT 10;
+DROP TABLE t1;
+#
+# MDEV-16155: UPDATE on RocksDB table with unique constraint does not work
+#
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a,b) VALUES (1,'foo'),(2,'bar');
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+101 foo
+102 bar
+DROP TABLE t1;
+#
+# MDEV-15319: [SQL Layer] Server crashes in Field::set_null / myrocks::ha_rocksdb::convert_record_from_storage_format
+# (just a testcase)
+#
+CREATE TABLE t1 (i INT);
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t2 ENGINE=RocksDB AS SELECT VALUE(i) AS a FROM t1;
+DELETE FROM t2;
+DROP TABLE t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_rpl.result b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_rpl.result
new file mode 100644
index 00000000..98408c78
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mariadb_port_rpl.result
@@ -0,0 +1,14 @@
+#
+# MDEV-15472: Assertion `!is_set() || (m_status == DA_OK_BULK && is_bulk_op())' failure ...
+#
+select @@log_bin;
+@@log_bin
+1
+select @@binlog_format;
+@@binlog_format
+ROW
+CREATE OR REPLACE TABLE t1 (i INT) ENGINE=RocksDB;
+LOCK TABLE t1 WRITE;
+FLUSH TABLES;
+UNLOCK TABLES;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/max_open_files.result b/storage/rocksdb/mysql-test/rocksdb/r/max_open_files.result
new file mode 100644
index 00000000..d3aac194
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/max_open_files.result
@@ -0,0 +1,22 @@
+CALL mtr.add_suppression("RocksDB: rocksdb_max_open_files should not be greater than the open_files_limit*");
+FOUND 1 /RocksDB: rocksdb_max_open_files should not be greater than the open_files_limit/ in rocksdb.max_open_files.err
+SELECT FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files;
+FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files
+1
+SELECT @@global.open_files_limit - 1 = @@global.rocksdb_max_open_files;
+@@global.open_files_limit - 1 = @@global.rocksdb_max_open_files
+1
+SELECT @@global.rocksdb_max_open_files;
+@@global.rocksdb_max_open_files
+0
+CREATE TABLE t1(a INT) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(0),(1),(2),(3),(4);
+SET GLOBAL rocksdb_force_flush_memtable_and_lzero_now=1;
+DROP TABLE t1;
+SELECT @@global.rocksdb_max_open_files;
+@@global.rocksdb_max_open_files
+-1
+SELECT FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files;
+FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files
+1
+# restart
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/misc.result b/storage/rocksdb/mysql-test/rocksdb/r/misc.result
new file mode 100644
index 00000000..b2e5d04f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/misc.result
@@ -0,0 +1,94 @@
+INSERT INTO mysql.event (
+db,
+name,
+body,
+definer,
+interval_value,
+interval_field,
+originator,
+character_set_client,
+collation_connection,
+db_collation,
+body_utf8)
+values (
+database(),
+"ev1",
+"select 1",
+user(),
+100,
+"SECOND_MICROSECOND",
+1,
+'utf8',
+'utf8_general_ci',
+'utf8_general_ci',
+'select 1');
+SHOW EVENTS;
+ERROR 42000: This version of MariaDB doesn't yet support 'MICROSECOND'
+DROP EVENT ev1;
+SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
+FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE ORDER BY TABLE_NAME;
+TABLE_NAME COLUMN_NAME REFERENCED_TABLE_NAME REFERENCED_COLUMN_NAME
+Warning 1286 Unknown storage engine 'InnoDB'
+Warning 1286 Unknown storage engine 'InnoDB'
+Warning 1286 Unknown storage engine 'InnoDB'
+Warnings:
+column_stats column_name NULL NULL
+column_stats db_name NULL NULL
+column_stats table_name NULL NULL
+columns_priv Column_name NULL NULL
+columns_priv Db NULL NULL
+columns_priv Host NULL NULL
+columns_priv Table_name NULL NULL
+columns_priv User NULL NULL
+db Db NULL NULL
+db Host NULL NULL
+db User NULL NULL
+event db NULL NULL
+event name NULL NULL
+func name NULL NULL
+global_priv Host NULL NULL
+global_priv User NULL NULL
+gtid_slave_pos domain_id NULL NULL
+gtid_slave_pos sub_id NULL NULL
+help_category help_category_id NULL NULL
+help_category name NULL NULL
+help_keyword help_keyword_id NULL NULL
+help_keyword name NULL NULL
+help_relation help_keyword_id NULL NULL
+help_relation help_topic_id NULL NULL
+help_topic help_topic_id NULL NULL
+help_topic name NULL NULL
+index_stats db_name NULL NULL
+index_stats index_name NULL NULL
+index_stats prefix_arity NULL NULL
+index_stats table_name NULL NULL
+plugin name NULL NULL
+proc db NULL NULL
+proc name NULL NULL
+proc type NULL NULL
+procs_priv Db NULL NULL
+procs_priv Host NULL NULL
+procs_priv Routine_name NULL NULL
+procs_priv Routine_type NULL NULL
+procs_priv User NULL NULL
+proxies_priv Host NULL NULL
+proxies_priv Proxied_host NULL NULL
+proxies_priv Proxied_user NULL NULL
+proxies_priv User NULL NULL
+roles_mapping Host NULL NULL
+roles_mapping Role NULL NULL
+roles_mapping User NULL NULL
+servers Server_name NULL NULL
+table_stats db_name NULL NULL
+table_stats table_name NULL NULL
+tables_priv Db NULL NULL
+tables_priv Host NULL NULL
+tables_priv Table_name NULL NULL
+tables_priv User NULL NULL
+time_zone Time_zone_id NULL NULL
+time_zone_leap_second Transition_time NULL NULL
+time_zone_name Name NULL NULL
+time_zone_transition Time_zone_id NULL NULL
+time_zone_transition Transition_time NULL NULL
+time_zone_transition_type Time_zone_id NULL NULL
+time_zone_transition_type Transition_type_id NULL NULL
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/multi_varchar_sk_lookup.result b/storage/rocksdb/mysql-test/rocksdb/r/multi_varchar_sk_lookup.result
new file mode 100644
index 00000000..86ba6d92
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/multi_varchar_sk_lookup.result
@@ -0,0 +1,37 @@
+DROP TABLE IF EXISTS T1;
+CREATE TABLE T1 (
+P1 VARCHAR(64), P2 VARCHAR(64), P3 VARCHAR(64), P4 VARCHAR(64),
+S1 VARCHAR(64), S2 VARCHAR(64), S3 VARCHAR(64), S4 VARCHAR(64),
+S5 VARCHAR(64), S6 VARCHAR(64), S7 VARCHAR(64), S8 VARCHAR(64),
+S9 VARCHAR(64), S10 VARCHAR(64), S11 VARCHAR(64), S12 VARCHAR(64),
+S13 VARCHAR(64), S14 VARCHAR(64), S15 VARCHAR(64), S16 VARCHAR(64),
+PRIMARY KEY (P1(8), P2(8), P3(8), P4(8)),
+KEY SK (S1(8), S2(8), S3(8), S4(8),
+S5(8), S6(8), S7(8), S8(8),
+S9(8), S10(8), S11(8), S12(8),
+S13(8), S14(8), S15(8), S16(8))
+) ENGINE=rocksdb;
+INSERT INTO T1 VALUES ('1', '2', '3', '4',
+'5', '6', '7', '8',
+'9', '10', '11', '12',
+'13', '14', '15', '16',
+'17', '18', '19', '20');
+SELECT * FROM T1;
+P1 P2 P3 P4 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15 S16
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+# Not eligible for optimization, shorter than prefix length.
+SELECT P4, S2 FROM T1 FORCE INDEX(SK) WHERE S1='5';
+P4 S2
+4 6
+include/assert.inc [Not eligible for optimization, shorter than prefix length.: 0 rocksdb_covered_secondary_key_lookups]
+DELETE FROM T1;
+INSERT INTO T1 VALUES ('100000000', '200000000', '300000000', '400000000',
+'500000000', '600000000', '700000000', '800000000',
+'900000000', '100000000', '110000000', '120000000',
+'130000000', '140000000', '150000000', '160000000',
+'170000000', '180000000', '190000000', '200000000');
+# Not eligible for optimization, longer than prefix length.
+SELECT P4, S2 FROM T1 FORCE INDEX(SK) WHERE S1='5';
+P4 S2
+include/assert.inc [Not eligible for optimization, longer than prefix length.: 0 rocksdb_covered_secondary_key_lookups]
+DROP TABLE T1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_blind_replace.result b/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_blind_replace.result
new file mode 100644
index 00000000..ff462569
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_blind_replace.result
@@ -0,0 +1,128 @@
+reset master;
+set GLOBAL binlog_format= 'ROW';
+SET GLOBAL enable_blind_replace=ON;
+set binlog_format=row;
+create table t5 (c1 int primary key, c2 int);
+insert into t5 values (1, 1);
+insert into t5 values (2, 2);
+insert into t5 values (3, 3);
+select * from t5;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t5 values (1, 11);
+replace into t5 values (2, 22);
+replace into t5 values (3, 33);
+select case when variable_value-@c = 3 then 'true' else 'false' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t5;
+c1 c2
+1 11
+2 22
+3 33
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Query # # use `test`; create table t5 (c1 int primary key, c2 int)
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+flush logs;
+drop table t5;
+reset master;
+Replaying binlog events containing blind replace statements should work
+select * from t5;
+c1 c2
+1 11
+2 22
+3 33
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Query # # use `test`; create table t5 (c1 int primary key, c2 int)
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+drop table t5;
+reset master;
+Replaying the same binlog events with blind replace disabled should work
+The server should internally convert such events into updates
+SET GLOBAL enable_blind_replace=OFF;
+select * from t5;
+c1 c2
+1 11
+2 22
+3 33
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Query # # use `test`; create table t5 (c1 int primary key, c2 int)
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t5)
+master-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+set GLOBAL binlog_format=DEFAULT;
+SET GLOBAL enable_blind_replace=DEFAULT;
+drop table t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_gtid_skip_empty_trans_rocksdb.result b/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_gtid_skip_empty_trans_rocksdb.result
new file mode 100644
index 00000000..835361ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mysqlbinlog_gtid_skip_empty_trans_rocksdb.result
@@ -0,0 +1,143 @@
+reset master;
+set timestamp=1000000000;
+set SESSION binlog_format = 'ROW';
+create database test2;
+create database test3;
+use test;
+create table t1 (a int primary key, b char(8)) ENGINE=rocksdb;
+insert into t1 values(1, 'a');
+insert into t1 values(2, 'b');
+create table t2 (a int primary key, b char(8)) ENGINE=rocksdb;
+start transaction;
+insert into t2 values(1, 'a');
+insert into t2 values(2, 'b');
+insert into t2 values(3, 'c');
+insert into t2 values(4, 'd');
+commit;
+use test2;
+create table t1 (a int primary key, b char(8)) ENGINE=rocksdb;
+insert into t1 values(1, 'a');
+insert into t1 values(2, 'b');
+create table t2 (a int primary key, b char(8)) ENGINE=rocksdb;
+start transaction;
+insert into t2 values(1, 'a');
+insert into t2 values(2, 'b');
+insert into t2 values(3, 'c');
+insert into t2 values(4, 'd');
+commit;
+use test3;
+create table t1 (a int primary key, b char(8)) ENGINE=rocksdb;
+insert into t1 values(1, 'a');
+insert into t1 values(2, 'b');
+create table t2 (a int primary key, b char(8)) ENGINE=rocksdb;
+start transaction;
+insert into t2 values(1, 'a');
+insert into t2 values(2, 'b');
+insert into t2 values(3, 'c');
+insert into t2 values(4, 'd');
+commit;
+FLUSH LOGS;
+==== Output of mysqlbinlog with --short-form --skip-empty-trans, --database and --skip-gtids options ====
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
+/*!40019 SET @@session.max_insert_delayed_threads=0*/;
+/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
+DELIMITER /*!*/;
+ROLLBACK/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+SET @@session.pseudo_thread_id=999999999/*!*/;
+SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
+SET @@session.sql_mode=1073741824/*!*/;
+SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
+/*!\C latin1 *//*!*/;
+SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/;
+SET @@session.lc_time_names=0/*!*/;
+SET @@session.collation_database=DEFAULT/*!*/;
+create database test2
+/*!*/;
+use `test2`/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+create table t1 (a int primary key, b char(8)) ENGINE=rocksdb
+/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+BEGIN
+/*!*/;
+COMMIT/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+BEGIN
+/*!*/;
+COMMIT/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+create table t2 (a int primary key, b char(8)) ENGINE=rocksdb
+/*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+BEGIN
+/*!*/;
+COMMIT/*!*/;
+DELIMITER ;
+# End of log file
+ROLLBACK /* added by mysqlbinlog */;
+/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
+use test2;
+start transaction;
+insert into t2 values(5, 'e');
+insert into t2 values(6, 'f');
+use test;
+insert into t2 values(7, 'g');
+insert into t2 values(8, 'h');
+commit;
+FLUSH LOGS;
+==== Output of mysqlbinlog with --short-form --skip-empty-trans, --database and --skip-gtids options ====
+==== DB changed in the middle of the transaction, which belongs to the selected database
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
+/*!40019 SET @@session.max_insert_delayed_threads=0*/;
+/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
+DELIMITER /*!*/;
+SET TIMESTAMP=1000000000/*!*/;
+SET @@session.pseudo_thread_id=999999999/*!*/;
+SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
+SET @@session.sql_mode=1073741824/*!*/;
+SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
+/*!\C latin1 *//*!*/;
+SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/;
+SET @@session.lc_time_names=0/*!*/;
+SET @@session.collation_database=DEFAULT/*!*/;
+BEGIN
+/*!*/;
+DELIMITER ;
+# End of log file
+ROLLBACK /* added by mysqlbinlog */;
+/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
+use test;
+start transaction;
+insert into t2 values(9, 'i');
+insert into t2 values(10, 'j');
+use test2;
+insert into t2 values(11, 'k');
+insert into t2 values(12, 'l');
+commit;
+FLUSH LOGS;
+==== Output of mysqlbinlog with --short-form --skip-empty-trans, --database and --skip-gtids options ====
+==== DB changed in the middle of the transaction, which belongs to the non-selected database
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
+/*!40019 SET @@session.max_insert_delayed_threads=0*/;
+/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
+DELIMITER /*!*/;
+DELIMITER ;
+# End of log file
+ROLLBACK /* added by mysqlbinlog */;
+/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
+/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
+use test;
+drop table t1;
+drop table if exists t2;
+use test2;
+drop table t1;
+drop table if exists t2;
+use test3;
+drop table t1;
+drop table if exists t2;
+drop database test2;
+drop database test3;
+FLUSH LOGS;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mysqldump.result b/storage/rocksdb/mysql-test/rocksdb/r/mysqldump.result
new file mode 100644
index 00000000..ac6615be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mysqldump.result
@@ -0,0 +1,246 @@
+drop table if exists r1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+create table r1 (id1 int, id2 int, id3 varchar(100), id4 int, value1 int, value2 int, value3 int, value4 int, primary key (id1, id2, id3, id4), KEY (value1, value2, value3)) engine=rocksdb;
+insert into r1 values (1,1,1,1,1,1,1,1);
+insert into r1 values (1,1,1,2,2,2,2,2);
+insert into r1 values (1,1,2,1,3,3,3,3);
+insert into r1 values (1,1,2,2,4,4,4,4);
+insert into r1 values (1,2,1,1,5,5,5,5);
+insert into r1 values (1,2,1,2,6,6,6,6);
+insert into r1 values (1,2,2,1,7,7,7,7);
+insert into r1 values (1,2,2,2,8,8,8,8);
+insert into r1 values (2,1,1,1,9,9,9,9);
+insert into r1 values (2,1,1,2,10,10,10,10);
+insert into r1 values (2,1,2,1,11,11,11,11);
+insert into r1 values (2,1,2,2,12,12,12,12);
+insert into r1 values (2,2,1,1,13,13,13,13);
+insert into r1 values (2,2,1,2,14,14,14,14);
+insert into r1 values (2,2,2,1,15,15,15,15);
+insert into r1 values (2,2,2,2,16,16,16,16);
+connection con2;
+BEGIN;
+insert into r1 values (5,5,5,5,5,5,5,5);
+update r1 set value1=value1+100 where id1=1 and id2=1 and id3='1';
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+/*!50601 SELECT count(*) INTO @is_mysql8 FROM information_schema.TABLES WHERE table_schema='performance_schema' AND table_name='session_variables' */;
+/*!50601 SET @check_rocksdb = CONCAT( 'SELECT count(*) INTO @is_rocksdb_supported FROM ', IF (@is_mysql8, 'performance', 'information'), '_schema.session_variables WHERE variable_name=\'rocksdb_bulk_load\'') */;
+/*!50601 PREPARE s FROM @check_rocksdb */;
+/*!50601 EXECUTE s */;
+/*!50601 SET @enable_bulk_load = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load=1', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @enable_bulk_load */;
+/*!50601 EXECUTE s */;
+-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
+-- SET GLOBAL gtid_slave_pos='0-1-18';
+DROP TABLE IF EXISTS `r1`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r1` (
+ `id1` int(11) NOT NULL,
+ `id2` int(11) NOT NULL,
+ `id3` varchar(100) NOT NULL,
+ `id4` int(11) NOT NULL,
+ `value1` int(11) DEFAULT NULL,
+ `value2` int(11) DEFAULT NULL,
+ `value3` int(11) DEFAULT NULL,
+ `value4` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id1`,`id2`,`id3`,`id4`),
+ KEY `value1` (`value1`,`value2`,`value3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/* ORDERING KEY (DESC) : PRIMARY */;
+
+LOCK TABLES `r1` WRITE;
+/*!40000 ALTER TABLE `r1` DISABLE KEYS */;
+INSERT INTO `r1` VALUES (2,2,'2',2,16,16,16,16),(2,2,'2',1,15,15,15,15),(2,2,'1',2,14,14,14,14),(2,2,'1',1,13,13,13,13),(2,1,'2',2,12,12,12,12),(2,1,'2',1,11,11,11,11),(2,1,'1',2,10,10,10,10),(2,1,'1',1,9,9,9,9),(1,2,'2',2,8,8,8,8),(1,2,'2',1,7,7,7,7),(1,2,'1',2,6,6,6,6),(1,2,'1',1,5,5,5,5),(1,1,'2',2,4,4,4,4),(1,1,'2',1,3,3,3,3),(1,1,'1',2,2,2,2,2),(1,1,'1',1,1,1,1,1);
+/*!40000 ALTER TABLE `r1` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!50601 SET @disable_bulk_load = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load=0', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @disable_bulk_load */;
+/*!50601 EXECUTE s */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+/*!50601 SELECT count(*) INTO @is_mysql8 FROM information_schema.TABLES WHERE table_schema='performance_schema' AND table_name='session_variables' */;
+/*!50601 SET @check_rocksdb = CONCAT( 'SELECT count(*) INTO @is_rocksdb_supported FROM ', IF (@is_mysql8, 'performance', 'information'), '_schema.session_variables WHERE variable_name=\'rocksdb_bulk_load\'') */;
+/*!50601 PREPARE s FROM @check_rocksdb */;
+/*!50601 EXECUTE s */;
+/*!50601 SET @bulk_load_allow_sk = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load_allow_sk=1', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @bulk_load_allow_sk */;
+/*!50601 EXECUTE s */;
+/*!50601 SET @enable_bulk_load = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load=1', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @enable_bulk_load */;
+/*!50601 EXECUTE s */;
+-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
+DROP TABLE IF EXISTS `r1`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r1` (
+ `id1` int(11) NOT NULL DEFAULT '0',
+ `id2` int(11) NOT NULL DEFAULT '0',
+ `id3` varchar(100) NOT NULL DEFAULT '',
+ `id4` int(11) NOT NULL DEFAULT '0',
+ `value1` int(11) DEFAULT NULL,
+ `value2` int(11) DEFAULT NULL,
+ `value3` int(11) DEFAULT NULL,
+ `value4` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id1`,`id2`,`id3`,`id4`),
+ KEY `value1` (`value1`,`value2`,`value3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/* ORDERING KEY (DESC) : PRIMARY */;
+
+LOCK TABLES `r1` WRITE;
+/*!40000 ALTER TABLE `r1` DISABLE KEYS */;
+INSERT INTO `r1` VALUES (2,2,'2',2,16,16,16,16),(2,2,'2',1,15,15,15,15),(2,2,'1',2,14,14,14,14),(2,2,'1',1,13,13,13,13),(2,1,'2',2,12,12,12,12),(2,1,'2',1,11,11,11,11),(2,1,'1',2,10,10,10,10),(2,1,'1',1,9,9,9,9),(1,2,'2',2,8,8,8,8),(1,2,'2',1,7,7,7,7),(1,2,'1',2,6,6,6,6),(1,2,'1',1,5,5,5,5),(1,1,'2',2,4,4,4,4),(1,1,'2',1,3,3,3,3),(1,1,'1',2,2,2,2,2),(1,1,'1',1,1,1,1,1);
+/*!40000 ALTER TABLE `r1` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!50601 SET @disable_bulk_load = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load=0', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @disable_bulk_load */;
+/*!50601 EXECUTE s */;
+/*!50601 SET @disable_bulk_load_allow_sk = IF (@is_rocksdb_supported, 'SET SESSION rocksdb_bulk_load_allow_sk=0', 'SET @dummy = 0') */;
+/*!50601 PREPARE s FROM @disable_bulk_load_allow_sk */;
+/*!50601 EXECUTE s */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+rollback;
+connection con1;
+2
+set @save_default_storage_engine=@@global.default_storage_engine;
+SET GLOBAL default_storage_engine=rocksdb;
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
+-- SET GLOBAL gtid_slave_pos='0-1-18';
+DROP TABLE IF EXISTS `r1`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r1` (
+ `id1` int(11) NOT NULL,
+ `id2` int(11) NOT NULL,
+ `id3` varchar(100) NOT NULL,
+ `id4` int(11) NOT NULL,
+ `value1` int(11) DEFAULT NULL,
+ `value2` int(11) DEFAULT NULL,
+ `value3` int(11) DEFAULT NULL,
+ `value4` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id1`,`id2`,`id3`,`id4`),
+ KEY `value1` (`value1`,`value2`,`value3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/* ORDERING KEY : (null) */;
+
+LOCK TABLES `r1` WRITE;
+/*!40000 ALTER TABLE `r1` DISABLE KEYS */;
+INSERT INTO `r1` VALUES (1,1,'1',1,1,1,1,1),(1,1,'1',2,2,2,2,2),(1,1,'2',1,3,3,3,3),(1,1,'2',2,4,4,4,4),(1,2,'1',1,5,5,5,5),(1,2,'1',2,6,6,6,6),(1,2,'2',1,7,7,7,7),(1,2,'2',2,8,8,8,8),(2,1,'1',1,9,9,9,9),(2,1,'1',2,10,10,10,10),(2,1,'2',1,11,11,11,11),(2,1,'2',2,12,12,12,12),(2,2,'1',1,13,13,13,13),(2,2,'1',2,14,14,14,14),(2,2,'2',1,15,15,15,15),(2,2,'2',2,16,16,16,16);
+/*!40000 ALTER TABLE `r1` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+3
+==== mysqldump with --innodb-stats-on-metadata ====
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
+-- SET GLOBAL gtid_slave_pos='0-1-18';
+DROP TABLE IF EXISTS `r1`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `r1` (
+ `id1` int(11) NOT NULL,
+ `id2` int(11) NOT NULL,
+ `id3` varchar(100) NOT NULL,
+ `id4` int(11) NOT NULL,
+ `value1` int(11) DEFAULT NULL,
+ `value2` int(11) DEFAULT NULL,
+ `value3` int(11) DEFAULT NULL,
+ `value4` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id1`,`id2`,`id3`,`id4`),
+ KEY `value1` (`value1`,`value2`,`value3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/* ORDERING KEY : (null) */;
+
+LOCK TABLES `r1` WRITE;
+/*!40000 ALTER TABLE `r1` DISABLE KEYS */;
+INSERT INTO `r1` VALUES (1,1,'1',1,1,1,1,1),(1,1,'1',2,2,2,2,2),(1,1,'2',1,3,3,3,3),(1,1,'2',2,4,4,4,4),(1,2,'1',1,5,5,5,5),(1,2,'1',2,6,6,6,6),(1,2,'2',1,7,7,7,7),(1,2,'2',2,8,8,8,8),(2,1,'1',1,9,9,9,9),(2,1,'1',2,10,10,10,10),(2,1,'2',1,11,11,11,11),(2,1,'2',2,12,12,12,12),(2,2,'1',1,13,13,13,13),(2,2,'1',2,14,14,14,14),(2,2,'2',1,15,15,15,15),(2,2,'2',2,16,16,16,16);
+/*!40000 ALTER TABLE `r1` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+SET GLOBAL binlog_format=statement;
+SET GLOBAL binlog_format=row;
+drop table r1;
+reset master;
+set @@global.default_storage_engine=@save_default_storage_engine;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/mysqldump2.result b/storage/rocksdb/mysql-test/rocksdb/r/mysqldump2.result
new file mode 100644
index 00000000..11c1f370
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/mysqldump2.result
@@ -0,0 +1,16 @@
+DROP TABLE IF EXISTS t1;
+create table t1 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+select case when variable_value - @a > 20 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+case when variable_value - @a > 20 then 'true' else 'false' end
+false
+select count(*) from t1;
+count(*)
+50000
+select case when variable_value - @a > 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+case when variable_value - @a > 100 then 'true' else 'false' end
+true
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/native_procedure.result b/storage/rocksdb/mysql-test/rocksdb/r/native_procedure.result
new file mode 100644
index 00000000..725b74e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/native_procedure.result
@@ -0,0 +1,397 @@
+create database linkdb;
+use linkdb;
+create table linktable (
+id1 bigint(20) unsigned NOT NULL DEFAULT '0',
+id2 bigint(20) unsigned NOT NULL DEFAULT '0',
+link_type bigint(20) unsigned NOT NULL DEFAULT '0',
+visibility tinyint(3) NOT NULL DEFAULT '0',
+data varchar(255) NOT NULL DEFAULT '',
+time bigint(20) unsigned NOT NULL DEFAULT '0',
+version int(11) unsigned NOT NULL DEFAULT '0',
+primary key (id1,id2,link_type) COMMENT 'cf_link_pk',
+KEY id1_type (id1,link_type,visibility,time,version,data)
+COMMENT 'rev:cf_link_id1_type') ENGINE=rocksdb DEFAULT COLLATE=latin1_bin;
+create table counttable (
+id bigint(20) unsigned NOT NULL DEFAULT '0',
+link_type bigint(20) unsigned NOT NULL DEFAULT '0',
+count int(10) unsigned NOT NULL DEFAULT '0',
+time bigint(20) unsigned NOT NULL DEFAULT '0',
+version bigint(20) unsigned NOT NULL DEFAULT '0',
+primary key (id,link_type) COMMENT 'cf_count_pk')
+ENGINE=rocksdb DEFAULT COLLATE=latin1_bin;
+create table nodetable (
+id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+type int(10) unsigned NOT NULL,
+version bigint(20) unsigned NOT NULL,
+time int(10) unsigned NOT NULL,
+data mediumtext NOT NULL,
+primary key(id) COMMENT 'cf_node_pk')
+ENGINE=rocksdb DEFAULT COLLATE=latin1_bin;
+#
+# Test nodeGet function
+#
+create native procedure nodeGet soname "NP_EXAMPLE_LIB";
+%nodeGet 1;
+id type version time data
+1 1 1 1000 data
+%nodeGet 50 anything can go here;
+id type version time data
+50 1 1 50000 data
+%nodeGet 39;
+id type version time data
+39 1 1 39000 data
+%nodeGet 98;
+id type version time data
+98 1 1 98000 data
+%nodeGet 1000;
+id type version time data
+%nodeGet -1;
+ERROR HY000: Native procedure failed. (code: 7, msg: 'Invalid arguments: Conversion failed for field id.', query 'nodeGet -1')
+%nodeGet asdf;
+ERROR HY000: Native procedure failed. (code: 7, msg: 'Invalid arguments: Conversion failed for field id.', query 'nodeGet asdf')
+#
+# Test linkGetRange/linkGetId2s function
+#
+create native procedure linkGetRange soname "NP_EXAMPLE_LIB";
+create native procedure linkGetId2s soname "NP_EXAMPLE_LIB";
+%linkGetRange 1 1 1000 2000 0 1000;
+id1 id2 link_type visibility data time version
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%linkGetRange 1 2 1000 2000 0 1000;
+id1 id2 link_type visibility data time version
+%linkGetRange 1 1 5000 2000 0 1000;
+id1 id2 link_type visibility data time version
+%linkGetRange 1 2 1000 6000 0 5;
+id1 id2 link_type visibility data time version
+1 5 2 1 data 2005 1
+1 4 2 1 data 2004 1
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+%linkGetRange 1 2 1000 6000 0 2;
+id1 id2 link_type visibility data time version
+1 5 2 1 data 2005 1
+1 4 2 1 data 2004 1
+%linkGetRange 1 2 1000 6000 2 2;
+id1 id2 link_type visibility data time version
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+%linkGetId2s 1 3 3 1 2 3;
+id1 id2 link_type visibility data time version
+1 1 3 1 data 3001 1
+1 2 3 1 data 3002 1
+1 3 3 1 data 3003 1
+%linkGetId2s 1 3 3 3 2 1;
+id1 id2 link_type visibility data time version
+1 3 3 1 data 3003 1
+1 2 3 1 data 3002 1
+1 1 3 1 data 3001 1
+%linkGetId2s 1 3 3 3 2 10;
+id1 id2 link_type visibility data time version
+1 3 3 1 data 3003 1
+1 2 3 1 data 3002 1
+%linkGetId2s 1 3 3 3 2 1 asdf;
+id1 id2 link_type visibility data time version
+1 3 3 1 data 3003 1
+1 2 3 1 data 3002 1
+1 1 3 1 data 3001 1
+%linkGetId2s 1 3 0;
+id1 id2 link_type visibility data time version
+%linkGetId2s 1 3 4 2;
+ERROR HY000: Incorrect arguments to native procedure. (query 'linkGetId2s 1 3 4 2')
+#
+# Test rangeQuery function
+#
+create native procedure rangeQuery soname "NP_EXAMPLE_LIB";
+%rangeQuery 1 0 0 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 1 1 1 data 1001 1
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+1 5 1 1 data 1005 1
+%rangeQuery 1 0 1 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 1 1 1 data 1001 1
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+%rangeQuery 1 1 0 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+1 5 1 1 data 1005 1
+%rangeQuery 1 1 1 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+%rangeQuery 0 0 0 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 0 1 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 1 0 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+%rangeQuery 0 1 1 4 id1 1 link_type 1 visibility 1 time 1001 4 id1 1 link_type 1 visibility 1 time 1005;
+id1 id2 link_type visibility data time version
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+%rangeQuery 1 0 0 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 1 1 1 data 1001 1
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+1 5 1 1 data 1005 1
+1 1 2 1 data 2001 1
+1 2 2 1 data 2002 1
+1 3 2 1 data 2003 1
+1 4 2 1 data 2004 1
+1 5 2 1 data 2005 1
+%rangeQuery 1 0 1 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 1 1 1 data 1001 1
+1 2 1 1 data 1002 1
+1 3 1 1 data 1003 1
+1 4 1 1 data 1004 1
+1 5 1 1 data 1005 1
+%rangeQuery 1 1 0 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 1 2 1 data 2001 1
+1 2 2 1 data 2002 1
+1 3 2 1 data 2003 1
+1 4 2 1 data 2004 1
+1 5 2 1 data 2005 1
+%rangeQuery 1 1 1 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+%rangeQuery 0 0 0 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 5 2 1 data 2005 1
+1 4 2 1 data 2004 1
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 0 1 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 1 0 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+1 5 2 1 data 2005 1
+1 4 2 1 data 2004 1
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+%rangeQuery 0 1 1 2 id1 1 link_type 1 2 id1 1 link_type 2;
+id1 id2 link_type visibility data time version
+%rangeQuery 0 0 0 2 id1 1 link_type 1 4 id1 1 link_type 2 visibility 1 time 2004;
+id1 id2 link_type visibility data time version
+1 4 2 1 data 2004 1
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 0 1 2 id1 1 link_type 1 4 id1 1 link_type 2 visibility 1 time 2004;
+id1 id2 link_type visibility data time version
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+%rangeQuery 0 1 0 2 id1 1 link_type 1 4 id1 1 link_type 2 visibility 1 time 2004;
+id1 id2 link_type visibility data time version
+1 4 2 1 data 2004 1
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+%rangeQuery 0 1 1 2 id1 1 link_type 1 4 id1 1 link_type 2 visibility 1 time 2004;
+id1 id2 link_type visibility data time version
+1 3 2 1 data 2003 1
+1 2 2 1 data 2002 1
+1 1 2 1 data 2001 1
+#
+# Test countGet function
+#
+create native procedure countGet soname "NP_EXAMPLE_LIB";
+%countGet 1 1;
+count
+2
+%countGet 10 1;
+count
+20
+%countGet 111 1;
+count
+%countGet 1 111;
+count
+%countGet -1 1 1;
+ERROR HY000: Native procedure failed. (code: 7, msg: 'Invalid arguments: Conversion failed for field id.', query 'countGet -1 1 1')
+%countGet -1 1 2;
+ERROR HY000: Native procedure failed. (code: 7, msg: 'Invalid arguments: Conversion failed for field id.', query 'countGet -1 1 2')
+%countGet;
+ERROR HY000: Incorrect arguments to native procedure. (query 'countGet')
+#
+# Check that DDL locks are respected.
+#
+create native procedure sleepRange soname "NP_EXAMPLE_LIB";
+%sleepRange 1;
+set @start_lock_wait_timeout = @@session.lock_wait_timeout;
+set lock_wait_timeout = 1;
+drop table counttable;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction: Timeout on table metadata: linkdb.counttable
+set lock_wait_timeout = @start_lock_wait_timeout;
+count
+2
+4
+6
+8
+10
+12
+14
+16
+18
+20
+#
+# Check that queries can be killed.
+#
+%sleepRange 0;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+#
+# Check that native procedures work properly with transactions.
+#
+use linkdb;
+set session transaction isolation level repeatable read;
+%countGet 1 1;
+count
+2
+begin;
+select count from counttable where id = 1 and link_type = 1;
+count
+2
+%countGet 1 1;
+count
+2
+# Before update
+%countGet 1 1;
+count
+2
+update counttable set count = count + 1 where id = 1 and link_type = 1;
+# After update
+%countGet 1 1;
+count
+3
+# Unchanged due to consistent reads
+%countGet 1 1;
+count
+2
+#
+# Check index reads on prefixed data.
+#
+alter table linktable drop index id1_type;
+alter table linktable
+add index id1_type (id1,link_type,visibility,time,version,data(1))
+COMMENT 'rev:cf_link_id1_type';
+%linkGetRange 1 1 1000 2000 0 1000;
+id1 id2 link_type visibility data time version
+1 5 1 1 data 1005 1
+1 4 1 1 data 1004 1
+1 3 1 1 data 1003 1
+1 2 1 1 data 1002 1
+1 1 1 1 data 1001 1
+#
+# Check correct error handling for various scenarios.
+#
+create native procedure invalidKey1 soname "NP_EXAMPLE_LIB";
+%invalidKey1;
+ERROR HY000: Native procedure failed. (code: 6, msg: 'Not found: ', query 'invalidKey1')
+create native procedure invalidOpen1 soname "NP_EXAMPLE_LIB";
+%invalidOpen1;
+ERROR HY000: Native procedure failed. (code: 5, msg: 'Cannot reinitialize: ', query 'invalidOpen1')
+create native procedure invalidOpen2 soname "NP_EXAMPLE_LIB";
+%invalidOpen2;
+ERROR HY000: Native procedure failed. (code: 5, msg: 'Cannot reinitialize: ', query 'invalidOpen2')
+create native procedure invalidOpen3 soname "NP_EXAMPLE_LIB";
+%invalidOpen3;
+ERROR HY000: Native procedure failed. (code: 5, msg: 'Cannot reinitialize: ', query 'invalidOpen3')
+create native procedure invalidOpen4 soname "NP_EXAMPLE_LIB";
+%invalidOpen4;
+ERROR HY000: Native procedure failed. (code: 5, msg: 'Cannot reinitialize: ', query 'invalidOpen4')
+%invalidProcedure;
+ERROR HY000: Unknown native procedure. 'invalidProcedure'
+create native procedure invalidProcedure soname "invalid.so";
+ERROR HY000: Can't open shared library
+create native procedure invalidProcedure soname "NP_EXAMPLE_LIB";
+ERROR HY000: Can't find symbol 'invalidProcedure' in library
+#
+# Check that our functions are reloaded after restart.
+#
+select * from mysql.native_proc order by name;
+name type dl lua
+countGet native np_example.so
+invalidKey1 native np_example.so
+invalidOpen1 native np_example.so
+invalidOpen2 native np_example.so
+invalidOpen3 native np_example.so
+invalidOpen4 native np_example.so
+linkGetId2s native np_example.so
+linkGetRange native np_example.so
+nodeGet native np_example.so
+rangeQuery native np_example.so
+sleepRange native np_example.so
+drop native procedure nodeGet;
+create native procedure nodeGet soname "NP_EXAMPLE_LIB";
+ERROR HY000: Native procedure 'nodeGet' exists.
+drop native procedure linkGetRange;
+drop native procedure linkGetId2s;
+drop native procedure countGet;
+drop native procedure sleepRange;
+drop native procedure rangeQuery;
+drop native procedure invalidKey1;
+drop native procedure invalidOpen1;
+drop native procedure invalidOpen2;
+drop native procedure invalidOpen3;
+drop native procedure invalidOpen4;
+%nodeGet 1;
+ERROR HY000: Unknown native procedure. 'nodeGet'
+#
+# Check that our functions are unloaded after restart.
+#
+select * from mysql.native_proc order by name;
+name type dl lua
+%nodeGet 1;
+ERROR HY000: Unknown native procedure. 'nodeGet'
+drop database linkdb;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/negative_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/negative_stats.result
new file mode 100644
index 00000000..61c1d7e9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/negative_stats.result
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (i1 INT, PRIMARY KEY (i1)) ENGINE = ROCKSDB;
+SET GLOBAL ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW=1;
+set session debug_dbug= "+d,myrocks_simulate_negative_stats";
+SELECT CASE WHEN DATA_LENGTH < 1024 * 1024 THEN 'true' ELSE 'false' END FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't1';
+CASE WHEN DATA_LENGTH < 1024 * 1024 THEN 'true' ELSE 'false' END
+true
+set session debug_dbug= "-d,myrocks_simulate_negative_stats";
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/no_merge_sort.result b/storage/rocksdb/mysql-test/rocksdb/r/no_merge_sort.result
new file mode 100644
index 00000000..6ea13872
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/no_merge_sort.result
@@ -0,0 +1,123 @@
+Warnings:
+Note 1051 Unknown table 'test.ti_nk'
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+skip_merge_sort
+true
+DROP TABLE ti_nk;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_base.result b/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_base.result
new file mode 100644
index 00000000..12223ebf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_base.result
@@ -0,0 +1,98 @@
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+SET GLOBAL enable_blind_replace=ON;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+drop table t1;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+create trigger trg before insert on t1 for each row set @a:=1;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+drop table t1;
+create table t1(c1 int,c2 int) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+drop table t1;
+create table t1(c1 int,c2 int unique) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+drop table t1;
+create table t1(c1 int primary key,c2 int unique) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+drop table t1;
+create table t1(c1 int primary key,c2 int, key idx1(c2)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+drop table t1;
+SET GLOBAL enable_blind_replace=OFF;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+drop table t1;
+SET GLOBAL enable_blind_replace=DEFAULT;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_lock.result b/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_lock.result
new file mode 100644
index 00000000..65ee9768
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/optimize_myrocks_replace_into_lock.result
@@ -0,0 +1,46 @@
+SET GLOBAL enable_blind_replace=ON;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+SELECT @@global.enable_blind_replace;
+@@global.enable_blind_replace
+1
+begin;
+replace into t1 values(1,11);
+SELECT @@global.enable_blind_replace;
+@@global.enable_blind_replace
+1
+begin;
+update t1 set c2=22 where c1=1;
+commit;
+# Reap update.
+commit;
+select * from t1;
+c1 c2
+1 22
+2 2
+3 3
+SELECT @@global.enable_blind_replace;
+@@global.enable_blind_replace
+1
+begin;
+update t1 set c2=55 where c1=1;
+SELECT @@global.enable_blind_replace;
+@@global.enable_blind_replace
+1
+begin;
+replace into t1 values(1,66);
+commit;
+# Reap replace into.
+commit;
+select * from t1;
+c1 c2
+1 66
+2 2
+3 3
+drop table t1;
+SET GLOBAL enable_blind_replace=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/optimize_table.result b/storage/rocksdb/mysql-test/rocksdb/r/optimize_table.result
new file mode 100644
index 00000000..8273fdba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/optimize_table.result
@@ -0,0 +1,77 @@
+DROP TABLE IF EXISTS t1,t2,t3,t4,t5,t6;
+create table t1 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t2 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t3 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t4 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+create table t5 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+create table t6 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+select count(*) from t1;
+count(*)
+10000
+select count(*) from t2;
+count(*)
+10000
+select count(*) from t3;
+count(*)
+10000
+select count(*) from t4;
+count(*)
+10000
+select count(*) from t5;
+count(*)
+10000
+select count(*) from t6;
+count(*)
+10000
+delete from t1 where id <= 9900;
+delete from t2 where id <= 9900;
+delete from t3 where id <= 9900;
+delete from t4 where id <= 9900;
+delete from t5 where id <= 9900;
+delete from t6 where id <= 9900;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+sst file reduction ok
+optimize table t3;
+Table Op Msg_type Msg_text
+test.t3 optimize status OK
+sst file reduction ok
+optimize table t4;
+Table Op Msg_type Msg_text
+test.t4 optimize status OK
+sst file reduction ok
+optimize table t6;
+Table Op Msg_type Msg_text
+test.t6 optimize status OK
+sst file reduction ok
+select count(*) from t1;
+count(*)
+100
+select count(*) from t2;
+count(*)
+100
+select count(*) from t3;
+count(*)
+100
+select count(*) from t4;
+count(*)
+100
+select count(*) from t5;
+count(*)
+100
+select count(*) from t6;
+count(*)
+100
+optimize table t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status OK
+optimize table t5;
+Table Op Msg_type Msg_text
+test.t5 optimize status OK
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
+DROP TABLE t5;
+DROP TABLE t6;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/optimizer_loose_index_scans.result b/storage/rocksdb/mysql-test/rocksdb/r/optimizer_loose_index_scans.result
new file mode 100644
index 00000000..1fe61fe9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/optimizer_loose_index_scans.result
@@ -0,0 +1,281 @@
+set optimizer_switch='index_merge_sort_union=off';
+create table t (a int, b int, c int, d int, e int, primary key(a, b, c, d), key(b, d)) engine=rocksdb;
+analyze table t;
+Table Op Msg_type Msg_text
+test.t analyze status OK
+show indexes from t;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t 0 PRIMARY 1 a A 100 NULL NULL LSMTREE
+t 0 PRIMARY 2 b A 500 NULL NULL LSMTREE
+t 0 PRIMARY 3 c A 2500 NULL NULL LSMTREE
+t 0 PRIMARY 4 d A 2500 NULL NULL LSMTREE
+t 1 b 1 b A 50 NULL NULL LSMTREE
+t 1 b 2 d A 500 NULL NULL LSMTREE
+set optimizer_switch = 'skip_scan=off';
+explain select b, d from t where d < 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL b 8 NULL # Using where; Using index
+rows_read
+2500
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select b, d from t where d < 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b b 8 NULL # Using where; Using index for skip scan
+rows_read
+260
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select b, d from t where d > 4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL b 8 NULL # Using where; Using index
+rows_read
+2500
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select b, d from t where d > 4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b b 8 NULL # Using where; Using index for skip scan
+rows_read
+1505
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a = 5 and d <= 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where; Using index
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a = 5 and d <= 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+126
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select e from t where a = 5 and d <= 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select e from t where a = 5 and d <= 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 4 const # Using where
+rows_read
+250
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a = 5 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where; Using index
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a = 5 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+26
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select e from t where a = 5 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select e from t where a = 5 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 4 const # Using where
+rows_read
+250
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a in (1, 5) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY PRIMARY 4 NULL # Using where; Using index
+rows_read
+500
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a in (1, 5) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+52
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a in (1, 3, 5) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY PRIMARY 4 NULL # Using where; Using index
+rows_read
+750
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a in (1, 3, 5) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+78
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a in (1, 5) and b in (1, 2) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 8 NULL # Using where; Using index
+rows_read
+200
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a in (1, 5) and b in (1, 2) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+24
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a in (1, 2, 3, 4, 5) and b in (1, 2, 3) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 8 NULL # Using where; Using index
+rows_read
+750
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a in (1, 2, 3, 4, 5) and b in (1, 2, 3) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+90
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a = 5 and b = 2 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 8 const,const # Using where; Using index
+rows_read
+50
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a = 5 and b = 2 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+6
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a+1, b, c, d from t where a = 5 and d < 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where; Using index
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a+1, b, c, d from t where a = 5 and d < 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+101
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select b, c, d from t where a = 5 and d < 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where; Using index
+rows_read
+250
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select b, c, d from t where a = 5 and d < 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 16 NULL # Using where; Using index for skip scan
+rows_read
+101
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=off';
+explain select a, b, c, d from t where a = b and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL b 8 NULL # Using where; Using index
+rows_read
+2500
+set optimizer_switch = 'skip_scan=on,skip_scan_cost_based=off';
+explain select a, b, c, d from t where a = b and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b b 8 NULL # Using where; Using index for skip scan
+rows_read
+5
+include/diff_tables.inc [temp_orig, temp_skip]
+set optimizer_switch = 'skip_scan=off,skip_scan_cost_based=on';
+set optimizer_switch = 'skip_scan=on';
+set optimizer_trace = 'enabled=on';
+explain select a, b, c, d from t where a = 5 and d < 3 order by b, c, d;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 4 const # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%order_attribute_not_prefix_in_index%';
+count(*)
+1
+explain select a, b, c, d from t where a = 2 and d >= 98 and e = 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY PRIMARY 4 const # Using where
+select count(*) from information_schema.optimizer_trace where trace like '%query_references_nonkey_column%';
+count(*)
+1
+explain select a, b, c, d from t where a = 5 or b = 2 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index PRIMARY,b b 8 NULL # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%no_range_tree%';
+count(*)
+1
+explain select a, b, c, d from t where a = 5 or b = 2 or d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index PRIMARY,b b 8 NULL # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%no_range_tree%';
+count(*)
+1
+explain select a, b, c, d from t where a = 5 or d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index PRIMARY,b b 8 NULL # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%no_range_tree%';
+count(*)
+1
+explain select a, b, c, d from t where ((a = 5 and b = 2) or a = 2) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b b 8 NULL # Using where; Using index for skip scan
+select count(*) from information_schema.optimizer_trace where trace like '%keypart_in_disjunctive_query%';
+count(*)
+1
+explain select a, b, c, d from t where a > 2 and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 4 NULL # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%prefix_not_const_equality%';
+count(*)
+1
+explain select a, b, c, d from t where a = 2 and (d >= 98 or d < 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 4 const # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%range_predicate_too_complex%';
+count(*)
+1
+explain select a, b, c, d from t where a = 2 and b = 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 8 const,const # Using index
+select count(*) from information_schema.optimizer_trace where trace like '%no_range_predicate%';
+count(*)
+1
+explain select a, b, c, d from t where a = 2 and c > 2 and d < 2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ref PRIMARY,b PRIMARY 4 const # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%too_many_range_predicates%';
+count(*)
+1
+explain select a, b, c, d from t where (a < 1 or a = 4 or a = 5) and b in (1, 2, 3) and d >= 98;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t range PRIMARY,b PRIMARY 8 NULL # Using where; Using index
+select count(*) from information_schema.optimizer_trace where trace like '%prefix_not_const_equality%';
+count(*)
+1
+set optimizer_trace = 'enabled=off';
+set optimizer_switch= 'skip_scan=off';
+drop table t;
+set optimizer_switch='index_merge_sort_union=on';
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/partition.result b/storage/rocksdb/mysql-test/rocksdb/r/partition.result
new file mode 100644
index 00000000..a7f2a611
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/partition.result
@@ -0,0 +1,689 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS VAR_POP;
+DROP TABLE IF EXISTS TEMP0;
+DROP TABLE IF EXISTS VAR_SAMP;
+DROP TABLE IF EXISTS ti;
+DROP TABLE IF EXISTS members;
+DROP TABLE IF EXISTS members_2;
+DROP TABLE IF EXISTS employees;
+DROP TABLE IF EXISTS employees_2;
+DROP TABLE IF EXISTS employees_3;
+DROP TABLE IF EXISTS quarterly_report_status;
+DROP TABLE IF EXISTS employees_4;
+DROP TABLE IF EXISTS h2;
+DROP TABLE IF EXISTS rcx;
+DROP TABLE IF EXISTS r1;
+DROP TABLE IF EXISTS rc1;
+DROP TABLE IF EXISTS rx;
+DROP TABLE IF EXISTS rc2;
+DROP TABLE IF EXISTS rc3;
+DROP TABLE IF EXISTS rc4;
+DROP TABLE IF EXISTS employees_by_lname;
+DROP TABLE IF EXISTS customers_1;
+DROP TABLE IF EXISTS customers_2;
+DROP TABLE IF EXISTS customers_3;
+DROP TABLE IF EXISTS employees_hash;
+DROP TABLE IF EXISTS employees_hash_1;
+DROP TABLE IF EXISTS t1_hash;
+DROP TABLE IF EXISTS employees_linear_hash;
+DROP TABLE IF EXISTS t1_linear_hash;
+DROP TABLE IF EXISTS k1;
+DROP TABLE IF EXISTS k2;
+DROP TABLE IF EXISTS tm1;
+DROP TABLE IF EXISTS tk;
+DROP TABLE IF EXISTS ts;
+DROP TABLE IF EXISTS ts_1;
+DROP TABLE IF EXISTS ts_3;
+DROP TABLE IF EXISTS ts_4;
+DROP TABLE IF EXISTS ts_5;
+DROP TABLE IF EXISTS trb3;
+DROP TABLE IF EXISTS tr;
+DROP TABLE IF EXISTS members_3;
+DROP TABLE IF EXISTS clients;
+DROP TABLE IF EXISTS clients_lk;
+DROP TABLE IF EXISTS trb1;
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+Table Op Msg_type Msg_text
+test.t1 repair status OK
+Table Op Msg_type Msg_text
+test.t1 check status OK
+select lower(table_name) as tname
+from information_schema.tables
+where table_schema=database()
+order by tname;
+tname
+t1
+temp0
+var_pop
+var_samp
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+i j k
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1000
+CREATE TABLE ti(
+id INT,
+amount DECIMAL(7,2),
+tr_date DATE
+) ENGINE=ROCKSDB
+PARTITION BY HASH(MONTH(tr_date))
+PARTITIONS 6;
+CREATE TABLE members (
+firstname VARCHAR(25) NOT NULL,
+lastname VARCHAR(25) NOT NULL,
+username VARCHAR(16) NOT NULL,
+email VARCHAR(35),
+joined DATE NOT NULL
+) ENGINE=ROCKSDB
+PARTITION BY KEY(joined)
+PARTITIONS 6;
+CREATE TABLE members_2 (
+firstname VARCHAR(25) NOT NULL,
+lastname VARCHAR(25) NOT NULL,
+username VARCHAR(16) NOT NULL,
+email VARCHAR(35),
+joined DATE NOT NULL
+) ENGINE=ROCKSDB
+PARTITION BY RANGE(YEAR(joined)) (
+PARTITION p0 VALUES LESS THAN (1960),
+PARTITION p1 VALUES LESS THAN (1970),
+PARTITION p2 VALUES LESS THAN (1980),
+PARTITION p3 VALUES LESS THAN (1990),
+PARTITION p4 VALUES LESS THAN MAXVALUE
+);
+CREATE TABLE t2 (val INT)
+ENGINE=ROCKSDB
+PARTITION BY LIST(val)(
+PARTITION mypart VALUES IN (1,3,5),
+PARTITION MyPart VALUES IN (2,4,6)
+);
+ERROR HY000: Duplicate partition name MyPart
+CREATE TABLE employees (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT NOT NULL,
+store_id INT NOT NULL
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (store_id) (
+PARTITION p0 VALUES LESS THAN (6),
+PARTITION p1 VALUES LESS THAN (11),
+PARTITION p2 VALUES LESS THAN (16),
+PARTITION p3 VALUES LESS THAN MAXVALUE
+);
+CREATE TABLE employees_2 (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT NOT NULL,
+store_id INT NOT NULL
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (job_code) (
+PARTITION p0 VALUES LESS THAN (100),
+PARTITION p1 VALUES LESS THAN (1000),
+PARTITION p2 VALUES LESS THAN (10000)
+);
+CREATE TABLE employees_3 (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT,
+store_id INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (YEAR(separated)) (
+PARTITION p0 VALUES LESS THAN (1991),
+PARTITION p1 VALUES LESS THAN (1996),
+PARTITION p2 VALUES LESS THAN (2001),
+PARTITION p3 VALUES LESS THAN MAXVALUE
+);
+CREATE TABLE quarterly_report_status (
+report_id INT NOT NULL,
+report_status VARCHAR(20) NOT NULL,
+report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (UNIX_TIMESTAMP(report_updated)) (
+PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
+PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
+PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
+PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
+PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
+PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
+PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
+PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
+PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
+PARTITION p9 VALUES LESS THAN (MAXVALUE)
+);
+CREATE TABLE employees_4 (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT,
+store_id INT
+) ENGINE=ROCKSDB
+PARTITION BY LIST(store_id) (
+PARTITION pNorth VALUES IN (3,5,6,9,17),
+PARTITION pEast VALUES IN (1,2,10,11,19,20),
+PARTITION pWest VALUES IN (4,12,13,14,18),
+PARTITION pCentral VALUES IN (7,8,15,16)
+);
+CREATE TABLE h2 (
+c1 INT,
+c2 INT
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION p0 VALUES IN (1, 4, 7),
+PARTITION p1 VALUES IN (2, 5, 8)
+);
+INSERT INTO h2 VALUES (3, 5);
+ERROR HY000: Table has no partition for value 3
+CREATE TABLE rcx (
+a INT,
+b INT,
+c CHAR(3),
+d INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a,d,c) (
+PARTITION p0 VALUES LESS THAN (5,10,'ggg'),
+PARTITION p1 VALUES LESS THAN (10,20,'mmm'),
+PARTITION p2 VALUES LESS THAN (15,30,'sss'),
+PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+);
+CREATE TABLE r1 (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (a) (
+PARTITION p0 VALUES LESS THAN (5),
+PARTITION p1 VALUES LESS THAN (MAXVALUE)
+);
+INSERT INTO r1 VALUES (5,10), (5,11), (5,12);
+CREATE TABLE rc1 (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a, b) (
+PARTITION p0 VALUES LESS THAN (5, 12),
+PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE)
+);
+INSERT INTO rc1 VALUES (5,10), (5,11), (5,12);
+SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12);
+(5,10) < (5,12) (5,11) < (5,12) (5,12) < (5,12)
+1 1 0
+CREATE TABLE rx (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS (a) (
+PARTITION p0 VALUES LESS THAN (5),
+PARTITION p1 VALUES LESS THAN (MAXVALUE)
+);
+INSERT INTO rx VALUES (5,10), (5,11), (5,12);
+CREATE TABLE rc2 (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a,b) (
+PARTITION p0 VALUES LESS THAN (0,10),
+PARTITION p1 VALUES LESS THAN (10,20),
+PARTITION p2 VALUES LESS THAN (10,30),
+PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
+);
+CREATE TABLE rc3 (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a,b) (
+PARTITION p0 VALUES LESS THAN (0,10),
+PARTITION p1 VALUES LESS THAN (10,20),
+PARTITION p2 VALUES LESS THAN (10,30),
+PARTITION p3 VALUES LESS THAN (10,35),
+PARTITION p4 VALUES LESS THAN (20,40),
+PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)
+);
+CREATE TABLE rc4 (
+a INT,
+b INT,
+c INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a,b,c) (
+PARTITION p0 VALUES LESS THAN (0,25,50),
+PARTITION p1 VALUES LESS THAN (10,20,100),
+PARTITION p2 VALUES LESS THAN (10,30,50),
+PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+);
+SELECT (0,25,50) < (10,20,100), (10,20,100) < (10,30,50);
+(0,25,50) < (10,20,100) (10,20,100) < (10,30,50)
+1 1
+CREATE TABLE rcf (
+a INT,
+b INT,
+c INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(a,b,c) (
+PARTITION p0 VALUES LESS THAN (0,25,50),
+PARTITION p1 VALUES LESS THAN (20,20,100),
+PARTITION p2 VALUES LESS THAN (10,30,50),
+PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+);
+ERROR HY000: VALUES LESS THAN value must be strictly increasing for each partition
+CREATE TABLE employees_by_lname (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT NOT NULL,
+store_id INT NOT NULL
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS (lname) (
+PARTITION p0 VALUES LESS THAN ('g'),
+PARTITION p1 VALUES LESS THAN ('m'),
+PARTITION p2 VALUES LESS THAN ('t'),
+PARTITION p3 VALUES LESS THAN (MAXVALUE)
+);
+ALTER TABLE employees_by_lname PARTITION BY RANGE COLUMNS (lname) (
+PARTITION p0 VALUES LESS THAN ('g'),
+PARTITION p1 VALUES LESS THAN ('m'),
+PARTITION p2 VALUES LESS THAN ('t'),
+PARTITION p3 VALUES LESS THAN (MAXVALUE)
+);
+ALTER TABLE employees_by_lname PARTITION BY RANGE COLUMNS (hired) (
+PARTITION p0 VALUES LESS THAN ('1970-01-01'),
+PARTITION p1 VALUES LESS THAN ('1980-01-01'),
+PARTITION p2 VALUES LESS THAN ('1990-01-01'),
+PARTITION p3 VALUES LESS THAN ('2000-01-01'),
+PARTITION p4 VALUES LESS THAN ('2010-01-01'),
+PARTITION p5 VALUES LESS THAN (MAXVALUE)
+);
+CREATE TABLE customers_1 (
+first_name VARCHAR(25),
+last_name VARCHAR(25),
+street_1 VARCHAR(30),
+street_2 VARCHAR(30),
+city VARCHAR(15),
+renewal DATE
+) ENGINE=ROCKSDB
+PARTITION BY LIST COLUMNS(city) (
+PARTITION pRegion_1 VALUES IN('Oskarshamn', 'Högsby', 'Mönsterås'),
+PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'Västervik'),
+PARTITION pRegion_3 VALUES IN('Nässjö', 'Eksjö', 'Vetlanda'),
+PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'Växjo')
+);
+CREATE TABLE customers_2 (
+first_name VARCHAR(25),
+last_name VARCHAR(25),
+street_1 VARCHAR(30),
+street_2 VARCHAR(30),
+city VARCHAR(15),
+renewal DATE
+) ENGINE=ROCKSDB
+PARTITION BY LIST COLUMNS(renewal) (
+PARTITION pWeek_1 VALUES IN('2010-02-01', '2010-02-02', '2010-02-03',
+'2010-02-04', '2010-02-05', '2010-02-06', '2010-02-07'),
+PARTITION pWeek_2 VALUES IN('2010-02-08', '2010-02-09', '2010-02-10',
+'2010-02-11', '2010-02-12', '2010-02-13', '2010-02-14'),
+PARTITION pWeek_3 VALUES IN('2010-02-15', '2010-02-16', '2010-02-17',
+'2010-02-18', '2010-02-19', '2010-02-20', '2010-02-21'),
+PARTITION pWeek_4 VALUES IN('2010-02-22', '2010-02-23', '2010-02-24',
+'2010-02-25', '2010-02-26', '2010-02-27', '2010-02-28')
+);
+CREATE TABLE customers_3 (
+first_name VARCHAR(25),
+last_name VARCHAR(25),
+street_1 VARCHAR(30),
+street_2 VARCHAR(30),
+city VARCHAR(15),
+renewal DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE COLUMNS(renewal) (
+PARTITION pWeek_1 VALUES LESS THAN('2010-02-09'),
+PARTITION pWeek_2 VALUES LESS THAN('2010-02-15'),
+PARTITION pWeek_3 VALUES LESS THAN('2010-02-22'),
+PARTITION pWeek_4 VALUES LESS THAN('2010-03-01')
+);
+CREATE TABLE employees_hash (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT,
+store_id INT
+) ENGINE=ROCKSDB
+PARTITION BY HASH(store_id)
+PARTITIONS 4;
+CREATE TABLE employees_hash_1 (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT,
+store_id INT
+) ENGINE=ROCKSDB
+PARTITION BY HASH( YEAR(hired) )
+PARTITIONS 4;
+CREATE TABLE t1_hash (
+col1 INT,
+col2 CHAR(5),
+col3 DATE
+) ENGINE=ROCKSDB
+PARTITION BY HASH( YEAR(col3) )
+PARTITIONS 4;
+CREATE TABLE employees_linear_hash (
+id INT NOT NULL,
+fname VARCHAR(30),
+lname VARCHAR(30),
+hired DATE NOT NULL DEFAULT '1970-01-01',
+separated DATE NOT NULL DEFAULT '9999-12-31',
+job_code INT,
+store_id INT
+) ENGINE=ROCKSDB
+PARTITION BY LINEAR HASH( YEAR(hired) )
+PARTITIONS 4;
+CREATE TABLE t1_linear_hash (
+col1 INT,
+col2 CHAR(5),
+col3 DATE
+) ENGINE=ROCKSDB
+PARTITION BY LINEAR HASH( YEAR(col3) )
+PARTITIONS 6;
+CREATE TABLE k1 (
+id INT NOT NULL PRIMARY KEY,
+name VARCHAR(20)
+) ENGINE=ROCKSDB
+PARTITION BY KEY()
+PARTITIONS 2;
+CREATE TABLE k2 (
+id INT NOT NULL,
+name VARCHAR(20),
+UNIQUE KEY (id)
+) ENGINE=ROCKSDB
+PARTITION BY KEY()
+PARTITIONS 2;
+CREATE TABLE tm1 (
+s1 CHAR(32) PRIMARY KEY
+) ENGINE=ROCKSDB
+PARTITION BY KEY(s1)
+PARTITIONS 10;
+CREATE TABLE tk (
+col1 INT NOT NULL,
+col2 CHAR(5),
+col3 DATE
+) ENGINE=ROCKSDB
+PARTITION BY LINEAR KEY (col1)
+PARTITIONS 3;
+CREATE TABLE ts (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) )
+SUBPARTITION BY HASH( TO_DAYS(purchased) )
+SUBPARTITIONS 2 (
+PARTITION p0 VALUES LESS THAN (1990),
+PARTITION p1 VALUES LESS THAN (2000),
+PARTITION p2 VALUES LESS THAN MAXVALUE
+);
+CREATE TABLE ts_1 (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) )
+SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990) (
+SUBPARTITION s0,
+SUBPARTITION s1
+),
+PARTITION p1 VALUES LESS THAN (2000) (
+SUBPARTITION s2,
+SUBPARTITION s3
+),
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s4,
+SUBPARTITION s5
+)
+);
+CREATE TABLE ts_2 (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) )
+SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990) (
+SUBPARTITION s0,
+SUBPARTITION s1
+),
+PARTITION p1 VALUES LESS THAN (2000),
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s2,
+SUBPARTITION s3
+)
+);
+ERROR 42000: Wrong number of subpartitions defined, mismatch with previous setting near '
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s2,
+SUBPARTITION s3
+)
+)' at line 11
+CREATE TABLE ts_3 (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) )
+SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990) (
+SUBPARTITION s0,
+SUBPARTITION s1
+),
+PARTITION p1 VALUES LESS THAN (2000) (
+SUBPARTITION s2,
+SUBPARTITION s3
+),
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s4,
+SUBPARTITION s5
+)
+);
+CREATE TABLE ts_4 (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) )
+SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990) (
+SUBPARTITION s0,
+SUBPARTITION s1
+),
+PARTITION p1 VALUES LESS THAN (2000) (
+SUBPARTITION s2,
+SUBPARTITION s3
+),
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s4,
+SUBPARTITION s5
+)
+);
+CREATE TABLE ts_5 (
+id INT,
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE(YEAR(purchased))
+SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990) (
+SUBPARTITION s0a,
+SUBPARTITION s0b
+),
+PARTITION p1 VALUES LESS THAN (2000) (
+SUBPARTITION s1a,
+SUBPARTITION s1b
+),
+PARTITION p2 VALUES LESS THAN MAXVALUE (
+SUBPARTITION s2a,
+SUBPARTITION s2b
+)
+);
+CREATE TABLE trb3 (
+id INT,
+name VARCHAR(50),
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990),
+PARTITION p1 VALUES LESS THAN (1995),
+PARTITION p2 VALUES LESS THAN (2000),
+PARTITION p3 VALUES LESS THAN (2005)
+);
+ALTER TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2;
+CREATE TABLE tr (
+id INT,
+name VARCHAR(50),
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(purchased) ) (
+PARTITION p0 VALUES LESS THAN (1990),
+PARTITION p1 VALUES LESS THAN (1995),
+PARTITION p2 VALUES LESS THAN (2000),
+PARTITION p3 VALUES LESS THAN (2005)
+);
+INSERT INTO tr VALUES
+(1, 'desk organiser', '2003-10-15'),
+(2, 'CD player', '1993-11-05'),
+(3, 'TV set', '1996-03-10'),
+(4, 'bookcase', '1982-01-10'),
+(5, 'exercise bike', '2004-05-09'),
+(6, 'sofa', '1987-06-05'),
+(7, 'popcorn maker', '2001-11-22'),
+(8, 'aquarium', '1992-08-04'),
+(9, 'study desk', '1984-09-16'),
+(10, 'lava lamp', '1998-12-25');
+SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+id name purchased
+3 TV set 1996-03-10
+10 lava lamp 1998-12-25
+ALTER TABLE tr DROP PARTITION p2;
+SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+id name purchased
+CREATE TABLE members_3 (
+id INT,
+fname VARCHAR(25),
+lname VARCHAR(25),
+dob DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE( YEAR(dob) ) (
+PARTITION p0 VALUES LESS THAN (1970),
+PARTITION p1 VALUES LESS THAN (1980),
+PARTITION p2 VALUES LESS THAN (1990)
+);
+ALTER TABLE members_3 ADD PARTITION (PARTITION p3 VALUES LESS THAN (2000));
+ALTER TABLE members_3 ADD PARTITION (PARTITION n VALUES LESS THAN (1960));
+ERROR HY000: VALUES LESS THAN value must be strictly increasing for each partition
+CREATE TABLE clients (
+id INT,
+fname VARCHAR(30),
+lname VARCHAR(30),
+signed DATE
+) ENGINE=ROCKSDB
+PARTITION BY HASH( MONTH(signed) )
+PARTITIONS 12;
+ALTER TABLE clients COALESCE PARTITION 4;
+CREATE TABLE clients_lk (
+id INT,
+fname VARCHAR(30),
+lname VARCHAR(30),
+signed DATE
+) ENGINE=ROCKSDB
+PARTITION BY LINEAR KEY(signed)
+PARTITIONS 12;
+ALTER TABLE clients COALESCE PARTITION 18;
+ERROR HY000: Cannot remove all partitions, use DROP TABLE instead
+ALTER TABLE clients ADD PARTITION PARTITIONS 6;
+CREATE TABLE trb1 (
+id INT,
+name VARCHAR(50),
+purchased DATE
+) ENGINE=ROCKSDB
+PARTITION BY RANGE(id) (
+PARTITION p0 VALUES LESS THAN (3),
+PARTITION p1 VALUES LESS THAN (7),
+PARTITION p2 VALUES LESS THAN (9),
+PARTITION p3 VALUES LESS THAN (11)
+);
+INSERT INTO trb1 VALUES
+(1, 'desk organiser', '2003-10-15'),
+(2, 'CD player', '1993-11-05'),
+(3, 'TV set', '1996-03-10'),
+(4, 'bookcase', '1982-01-10'),
+(5, 'exercise bike', '2004-05-09'),
+(6, 'sofa', '1987-06-05'),
+(7, 'popcorn maker', '2001-11-22'),
+(8, 'aquarium', '1992-08-04'),
+(9, 'study desk', '1984-09-16'),
+(10, 'lava lamp', '1998-12-25');
+ALTER TABLE trb1 ADD PRIMARY KEY (id);
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS VAR_POP;
+DROP TABLE IF EXISTS TEMP0;
+DROP TABLE IF EXISTS VAR_SAMP;
+DROP TABLE IF EXISTS ti;
+DROP TABLE IF EXISTS members;
+DROP TABLE IF EXISTS members_2;
+DROP TABLE IF EXISTS employees;
+DROP TABLE IF EXISTS employees_2;
+DROP TABLE IF EXISTS employees_3;
+DROP TABLE IF EXISTS quarterly_report_status;
+DROP TABLE IF EXISTS employees_4;
+DROP TABLE IF EXISTS h2;
+DROP TABLE IF EXISTS rcx;
+DROP TABLE IF EXISTS r1;
+DROP TABLE IF EXISTS rc1;
+DROP TABLE IF EXISTS rx;
+DROP TABLE IF EXISTS rc2;
+DROP TABLE IF EXISTS rc3;
+DROP TABLE IF EXISTS rc4;
+DROP TABLE IF EXISTS employees_by_lname;
+DROP TABLE IF EXISTS customers_1;
+DROP TABLE IF EXISTS customers_2;
+DROP TABLE IF EXISTS customers_3;
+DROP TABLE IF EXISTS employees_hash;
+DROP TABLE IF EXISTS employees_hash_1;
+DROP TABLE IF EXISTS t1_hash;
+DROP TABLE IF EXISTS employees_linear_hash;
+DROP TABLE IF EXISTS t1_linear_hash;
+DROP TABLE IF EXISTS k1;
+DROP TABLE IF EXISTS k2;
+DROP TABLE IF EXISTS tm1;
+DROP TABLE IF EXISTS tk;
+DROP TABLE IF EXISTS ts;
+DROP TABLE IF EXISTS ts_1;
+DROP TABLE IF EXISTS ts_3;
+DROP TABLE IF EXISTS ts_4;
+DROP TABLE IF EXISTS ts_5;
+DROP TABLE IF EXISTS trb3;
+DROP TABLE IF EXISTS tr;
+DROP TABLE IF EXISTS members_3;
+DROP TABLE IF EXISTS clients;
+DROP TABLE IF EXISTS clients_lk;
+DROP TABLE IF EXISTS trb1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/percona_nonflushing_analyze_debug.result b/storage/rocksdb/mysql-test/rocksdb/r/percona_nonflushing_analyze_debug.result
new file mode 100644
index 00000000..84176da3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/percona_nonflushing_analyze_debug.result
@@ -0,0 +1,19 @@
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1), (2), (3);
+SET DEBUG_SYNC="handler_ha_index_next_end SIGNAL idx_scan_in_progress WAIT_FOR finish_scan";
+SELECT * FROM t1;
+SET DEBUG_SYNC="now WAIT_FOR idx_scan_in_progress";
+ANALYZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status OK
+SELECT * FROM t1;
+a
+1
+2
+3
+SET DEBUG_SYNC="now SIGNAL finish_scan";
+a
+1
+2
+3
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/perf_context.result b/storage/rocksdb/mysql-test/rocksdb/r/perf_context.result
new file mode 100644
index 00000000..28f96584
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/perf_context.result
@@ -0,0 +1,191 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+CREATE TABLE t2 (k INT, PRIMARY KEY (k)) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT WHERE TABLE_NAME = 't1';
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME STAT_TYPE VALUE
+test t1 NULL USER_KEY_COMPARISON_COUNT #
+test t1 NULL BLOCK_CACHE_HIT_COUNT #
+test t1 NULL BLOCK_READ_COUNT #
+test t1 NULL BLOCK_READ_BYTE #
+test t1 NULL BLOCK_READ_TIME #
+test t1 NULL BLOCK_CHECKSUM_TIME #
+test t1 NULL BLOCK_DECOMPRESS_TIME #
+test t1 NULL GET_READ_BYTES #
+test t1 NULL MULTIGET_READ_BYTES #
+test t1 NULL ITER_READ_BYTES #
+test t1 NULL INTERNAL_KEY_SKIPPED_COUNT #
+test t1 NULL INTERNAL_DELETE_SKIPPED_COUNT #
+test t1 NULL INTERNAL_RECENT_SKIPPED_COUNT #
+test t1 NULL INTERNAL_MERGE_COUNT #
+test t1 NULL GET_SNAPSHOT_TIME #
+test t1 NULL GET_FROM_MEMTABLE_TIME #
+test t1 NULL GET_FROM_MEMTABLE_COUNT #
+test t1 NULL GET_POST_PROCESS_TIME #
+test t1 NULL GET_FROM_OUTPUT_FILES_TIME #
+test t1 NULL SEEK_ON_MEMTABLE_TIME #
+test t1 NULL SEEK_ON_MEMTABLE_COUNT #
+test t1 NULL NEXT_ON_MEMTABLE_COUNT #
+test t1 NULL PREV_ON_MEMTABLE_COUNT #
+test t1 NULL SEEK_CHILD_SEEK_TIME #
+test t1 NULL SEEK_CHILD_SEEK_COUNT #
+test t1 NULL SEEK_MIN_HEAP_TIME #
+test t1 NULL SEEK_MAX_HEAP_TIME #
+test t1 NULL SEEK_INTERNAL_SEEK_TIME #
+test t1 NULL FIND_NEXT_USER_ENTRY_TIME #
+test t1 NULL WRITE_WAL_TIME #
+test t1 NULL WRITE_MEMTABLE_TIME #
+test t1 NULL WRITE_DELAY_TIME #
+test t1 NULL WRITE_PRE_AND_POST_PROCESS_TIME #
+test t1 NULL DB_MUTEX_LOCK_NANOS #
+test t1 NULL DB_CONDITION_WAIT_NANOS #
+test t1 NULL MERGE_OPERATOR_TIME_NANOS #
+test t1 NULL READ_INDEX_BLOCK_NANOS #
+test t1 NULL READ_FILTER_BLOCK_NANOS #
+test t1 NULL NEW_TABLE_BLOCK_ITER_NANOS #
+test t1 NULL NEW_TABLE_ITERATOR_NANOS #
+test t1 NULL BLOCK_SEEK_NANOS #
+test t1 NULL FIND_TABLE_NANOS #
+test t1 NULL BLOOM_MEMTABLE_HIT_COUNT #
+test t1 NULL BLOOM_MEMTABLE_MISS_COUNT #
+test t1 NULL BLOOM_SST_HIT_COUNT #
+test t1 NULL BLOOM_SST_MISS_COUNT #
+test t1 NULL KEY_LOCK_WAIT_TIME #
+test t1 NULL KEY_LOCK_WAIT_COUNT #
+test t1 NULL IO_THREAD_POOL_ID #
+test t1 NULL IO_BYTES_WRITTEN #
+test t1 NULL IO_BYTES_READ #
+test t1 NULL IO_OPEN_NANOS #
+test t1 NULL IO_ALLOCATE_NANOS #
+test t1 NULL IO_WRITE_NANOS #
+test t1 NULL IO_READ_NANOS #
+test t1 NULL IO_RANGE_SYNC_NANOS #
+test t1 NULL IO_LOGGER_NANOS #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL;
+STAT_TYPE VALUE
+USER_KEY_COMPARISON_COUNT #
+BLOCK_CACHE_HIT_COUNT #
+BLOCK_READ_COUNT #
+BLOCK_READ_BYTE #
+BLOCK_READ_TIME #
+BLOCK_CHECKSUM_TIME #
+BLOCK_DECOMPRESS_TIME #
+GET_READ_BYTES #
+MULTIGET_READ_BYTES #
+ITER_READ_BYTES #
+INTERNAL_KEY_SKIPPED_COUNT #
+INTERNAL_DELETE_SKIPPED_COUNT #
+INTERNAL_RECENT_SKIPPED_COUNT #
+INTERNAL_MERGE_COUNT #
+GET_SNAPSHOT_TIME #
+GET_FROM_MEMTABLE_TIME #
+GET_FROM_MEMTABLE_COUNT #
+GET_POST_PROCESS_TIME #
+GET_FROM_OUTPUT_FILES_TIME #
+SEEK_ON_MEMTABLE_TIME #
+SEEK_ON_MEMTABLE_COUNT #
+NEXT_ON_MEMTABLE_COUNT #
+PREV_ON_MEMTABLE_COUNT #
+SEEK_CHILD_SEEK_TIME #
+SEEK_CHILD_SEEK_COUNT #
+SEEK_MIN_HEAP_TIME #
+SEEK_MAX_HEAP_TIME #
+SEEK_INTERNAL_SEEK_TIME #
+FIND_NEXT_USER_ENTRY_TIME #
+WRITE_WAL_TIME #
+WRITE_MEMTABLE_TIME #
+WRITE_DELAY_TIME #
+WRITE_PRE_AND_POST_PROCESS_TIME #
+DB_MUTEX_LOCK_NANOS #
+DB_CONDITION_WAIT_NANOS #
+MERGE_OPERATOR_TIME_NANOS #
+READ_INDEX_BLOCK_NANOS #
+READ_FILTER_BLOCK_NANOS #
+NEW_TABLE_BLOCK_ITER_NANOS #
+NEW_TABLE_ITERATOR_NANOS #
+BLOCK_SEEK_NANOS #
+FIND_TABLE_NANOS #
+BLOOM_MEMTABLE_HIT_COUNT #
+BLOOM_MEMTABLE_MISS_COUNT #
+BLOOM_SST_HIT_COUNT #
+BLOOM_SST_MISS_COUNT #
+KEY_LOCK_WAIT_TIME #
+KEY_LOCK_WAIT_COUNT #
+IO_THREAD_POOL_ID #
+IO_BYTES_WRITTEN #
+IO_BYTES_READ #
+IO_OPEN_NANOS #
+IO_ALLOCATE_NANOS #
+IO_WRITE_NANOS #
+IO_READ_NANOS #
+IO_RANGE_SYNC_NANOS #
+IO_LOGGER_NANOS #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME STAT_TYPE VALUE
+test t1 NULL INTERNAL_KEY_SKIPPED_COUNT 0
+test t1 NULL INTERNAL_DELETE_SKIPPED_COUNT 0
+SELECT * FROM t1;
+i j
+1 1
+2 2
+3 3
+4 4
+5 5
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME STAT_TYPE VALUE
+test t1 NULL INTERNAL_KEY_SKIPPED_COUNT 5
+test t1 NULL INTERNAL_DELETE_SKIPPED_COUNT 0
+SELECT * FROM t1 WHERE j BETWEEN 1 AND 5;
+i j
+1 1
+2 2
+3 3
+4 4
+5 5
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME STAT_TYPE VALUE
+test t1 NULL INTERNAL_KEY_SKIPPED_COUNT 10
+test t1 NULL INTERNAL_DELETE_SKIPPED_COUNT 0
+set @tmp_flush_log= @@rocksdb_flush_log_at_trx_commit;
+set global rocksdb_flush_log_at_trx_commit=1;
+BEGIN;
+INSERT INTO t2 VALUES (1), (2);
+INSERT INTO t2 VALUES (3), (4);
+COMMIT;
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't2'
+AND STAT_TYPE = 'IO_WRITE_NANOS'
+AND VALUE > 0;
+COUNT(*)
+0
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS' AND VALUE > 0;
+COUNT(*)
+1
+SELECT VALUE INTO @a from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS';
+INSERT INTO t2 VALUES (5), (6), (7), (8);
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't2'
+AND STAT_TYPE = 'IO_WRITE_NANOS'
+AND VALUE > 0;
+COUNT(*)
+1
+SELECT VALUE INTO @b from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS';
+SELECT CASE WHEN @b - @a > 0 THEN 'true' ELSE 'false' END;
+CASE WHEN @b - @a > 0 THEN 'true' ELSE 'false' END
+true
+DROP TABLE t1;
+DROP TABLE t2;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
+set global rocksdb_flush_log_at_trx_commit= @tmp_flush_log;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/persistent_cache.result b/storage/rocksdb/mysql-test/rocksdb/r/persistent_cache.result
new file mode 100644
index 00000000..bc5739c2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/persistent_cache.result
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a int primary key) ENGINE=ROCKSDB;
+insert into t1 values (1);
+set global rocksdb_force_flush_memtable_now=1;
+select * from t1 where a = 1;
+a
+1
+select * from t1 where a = 1;
+a
+1
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/prefix_extractor_override.result b/storage/rocksdb/mysql-test/rocksdb/r/prefix_extractor_override.result
new file mode 100644
index 00000000..070169fd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/prefix_extractor_override.result
@@ -0,0 +1,82 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (id1 BIGINT, id2 BIGINT, id3 BIGINT, id4 BIGINT, PRIMARY KEY (id1, id2, id3, id4) comment 'cf1') ENGINE=rocksdb collate latin1_bin;
+set global rocksdb_force_flush_memtable_now = 1;
+
+Original Prefix Extractor:
+
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+CF_NAME OPTION_TYPE VALUE
+__system__ PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+cf1 PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+default PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+COUNT(*)
+1
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+variable_value-@u
+1
+
+Prefix Extractor (after override_cf_options set, should not be changed):
+
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+CF_NAME OPTION_TYPE VALUE
+__system__ PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+cf1 PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+default PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+SET @@global.rocksdb_update_cf_options = 'cf1={prefix_extractor=capped:26};';
+
+Changed Prefix Extractor (after update_cf_options set, without restart):
+
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+CF_NAME OPTION_TYPE VALUE
+__system__ PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+cf1 PREFIX_EXTRACTOR rocksdb.CappedPrefix.26
+default PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+SET @@global.rocksdb_update_cf_options = 'cf2={prefix_extractor=capped:28};';
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+CF_NAME OPTION_TYPE VALUE
+__system__ PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+cf1 PREFIX_EXTRACTOR rocksdb.CappedPrefix.26
+cf2 PREFIX_EXTRACTOR rocksdb.CappedPrefix.28
+default PREFIX_EXTRACTOR rocksdb.CappedPrefix.24
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+COUNT(*)
+1
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+variable_value-@u
+0
+set global rocksdb_force_flush_memtable_now = 1;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+COUNT(*)
+1
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+variable_value-@u
+1
+SELECT COUNT(*) FROM information_schema.rocksdb_index_file_map WHERE COLUMN_FAMILY != 1;
+COUNT(*)
+2
+UPDATE t1 SET id1=1,id2 = 30,id3 = 30 WHERE id4 >= 0 AND id4 <=10;
+set global rocksdb_force_flush_memtable_now = 1;
+SELECT COUNT(*) FROM information_schema.rocksdb_index_file_map WHERE COLUMN_FAMILY != 1;
+COUNT(*)
+3
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+COUNT(*)
+0
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+variable_value-@u
+2
+SET @@global.rocksdb_update_cf_options = '';
+set global rocksdb_compact_cf='cf1';
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=30 AND id3=30;
+COUNT(*)
+11
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+variable_value-@u
+1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/read_only_tx.result b/storage/rocksdb/mysql-test/rocksdb/r/read_only_tx.result
new file mode 100644
index 00000000..db21c3c0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/read_only_tx.result
@@ -0,0 +1,46 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1);
+select variable_value into @p from information_schema.global_status where variable_name='rocksdb_number_sst_entry_put';
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection con2;
+connection con1;
+select case when variable_value-@p < 1000 then 'true' else variable_value-@p end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_put';
+case when variable_value-@p < 1000 then 'true' else variable_value-@p end
+true
+select case when variable_value-@s < 100 then 'true' else variable_value-@s end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s < 100 then 'true' else variable_value-@s end
+true
+SELECT * FROM t1;
+id value
+1 1
+INSERT INTO t1 values (2, 2);
+ERROR HY000: Can't execute updates when you started a transaction with START TRANSACTION WITH CONSISTENT [ROCKSDB] SNAPSHOT.
+ROLLBACK;
+SELECT * FROM t1;
+id value
+1 10001
+INSERT INTO t1 values (2, 2);
+SELECT * FROM t1 ORDER BY id;
+id value
+1 10001
+2 2
+BEGIN;
+connection con2;
+connection con1;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+9998
+COMMIT;
+connection default;
+disconnect con1;
+disconnect con2;
+OPTIMIZE TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+DROP TABLE t1;
+reset master;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/records_in_range.result b/storage/rocksdb/mysql-test/rocksdb/r/records_in_range.result
new file mode 100644
index 00000000..feeabd7e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/records_in_range.result
@@ -0,0 +1,211 @@
+# restart
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT,
+a INT,
+b INT,
+PRIMARY KEY (i),
+KEY ka(a),
+KEY kb(b) comment 'rev:cf1'
+) ENGINE = rocksdb;
+explain extended select * from t1 where a> 500 and a< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` > 500 and `test`.`t1`.`a` < 750
+explain extended select * from t1 where a< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750
+explain extended select * from t1 where a> 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` > 500
+explain extended select * from t1 where a>=0 and a<=1000;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` >= 0 and `test`.`t1`.`a` <= 1000
+explain extended select * from t1 where b> 500 and b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` > 500 and `test`.`t1`.`b` < 750
+explain extended select * from t1 where b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` < 750
+explain extended select * from t1 where b> 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` > 500
+explain extended select * from t1 where b>=0 and b<=1000;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` >= 0 and `test`.`t1`.`b` <= 1000
+set @save_rocksdb_records_in_range = @@session.rocksdb_records_in_range;
+set rocksdb_records_in_range = 15000;
+explain extended select a from t1 where a < 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 15000 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 750
+explain extended select a, b from t1 where a < 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ALL ka NULL NULL NULL 20000 75.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750
+explain extended select a from t1 where a = 700;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ref ka ka 5 const 15000 100.00 Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 700
+explain extended select a,b from t1 where a = 700;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ALL ka NULL NULL NULL 20000 75.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` = 700
+explain extended select a from t1 where a in (700, 800);
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 index ka ka 5 NULL 20000 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` in (700,800)
+explain extended select a,b from t1 where a in (700, 800);
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ALL ka NULL NULL NULL 20000 100.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` in (700,800)
+set rocksdb_records_in_range=8000;
+explain extended select a from t1 where a in (700, 800);
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 16000 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` in (700,800)
+explain extended select a,b from t1 where a in (700, 800);
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 ALL ka NULL NULL NULL 20000 80.00 Using where
+Warnings:
+Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` in (700,800)
+set rocksdb_records_in_range = @save_rocksdb_records_in_range;
+set global rocksdb_force_flush_memtable_now = true;
+explain extended select * from t1 where a> 500 and a< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` > 500 and `test`.`t1`.`a` < 750
+explain extended select * from t1 where a< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750
+explain extended select * from t1 where a> 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` > 500
+explain extended select * from t1 where a>=0 and a<=1000;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` >= 0 and `test`.`t1`.`a` <= 1000
+explain extended select * from t1 where b> 500 and b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` > 500 and `test`.`t1`.`b` < 750
+explain extended select * from t1 where b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` < 750
+explain extended select * from t1 where b> 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` > 500
+explain extended select * from t1 where b>=0 and b<=1000;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` >= 0 and `test`.`t1`.`b` <= 1000
+explain extended select * from t1 where a>= 500 and a<= 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka ka 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` >= 500 and `test`.`t1`.`a` <= 500
+explain extended select * from t1 where b>= 500 and b<= 500;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kb kb 5 NULL 1000 100.00 Using index condition
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`b` >= 500 and `test`.`t1`.`b` <= 500
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range ka,kb ka 5 NULL 1000 5.00 Using index condition; Using where
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750 and `test`.`t1`.`b` > 500 and `test`.`t1`.`b` < 750
+drop index ka on t1;
+drop index kb on t1;
+create index kab on t1(a,b);
+set global rocksdb_force_flush_memtable_now = true;
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kab kab 5 NULL 1000 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750 and `test`.`t1`.`b` > 500 and `test`.`t1`.`b` < 750
+set rocksdb_records_in_range=444;
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+id select_type table type possible_keys key key_len ref rows filtered Extra
+1 SIMPLE t1 range kab kab 5 NULL 444 100.00 Using where; Using index
+Warnings:
+Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where `test`.`t1`.`a` < 750 and `test`.`t1`.`b` > 500 and `test`.`t1`.`b` < 750
+set rocksdb_records_in_range=0;
+CREATE TABLE `linktable` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` bigint(20) unsigned NOT NULL DEFAULT '0',
+`version` int(11) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link_pk',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+insert into linktable values (1,1,1,1,1,1,1,1,1);
+insert into linktable values (1,1,2,1,1,1,1,1,1);
+insert into linktable values (1,1,3,1,1,1,1,1,1);
+insert into linktable values (1,1,4,1,1,1,1,1,1);
+set global rocksdb_force_flush_memtable_now = true;
+explain select id1, id2, link_type, visibility, data, time, version from linktable where id1 = 1 and link_type = 1 and id2 in (1, 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE linktable range PRIMARY,id1_type PRIMARY 24 NULL 2 Using where
+drop table linktable;
+CREATE TABLE `linktable` (
+`id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+`id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+`id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+`link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+`visibility` tinyint(3) NOT NULL DEFAULT '0',
+`data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+`time` bigint(20) unsigned NOT NULL DEFAULT '0',
+`version` int(11) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link_pk',
+KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+insert into linktable values (1,1,1,1,1,1,1,1,1);
+insert into linktable values (1,1,2,1,1,1,1,1,1);
+insert into linktable values (1,1,3,1,1,1,1,1,1);
+insert into linktable values (1,1,4,1,1,1,1,1,1);
+set global rocksdb_force_flush_memtable_now = true;
+explain select id1, id2, link_type, visibility, data, time, version from linktable where id1 = 1 and link_type = 1 and id2 in (1, 2);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE linktable range PRIMARY,id1_type PRIMARY 24 NULL 2 Using where
+drop table linktable;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/repair_table.result b/storage/rocksdb/mysql-test/rocksdb/r/repair_table.result
new file mode 100644
index 00000000..f227d200
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/repair_table.result
@@ -0,0 +1,37 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb;
+REPAIR TABLE t1;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+REPAIR NO_WRITE_TO_BINLOG TABLE t1, t2;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+test.t2 repair note The storage engine for the table doesn't support repair
+INSERT INTO t2 (a,b) VALUES (5,'e'),(6,'f');
+REPAIR LOCAL TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 repair note The storage engine for the table doesn't support repair
+INSERT INTO t1 (a,b) VALUES (7,'g'),(8,'h');
+INSERT INTO t2 (a,b) VALUES (9,'i');
+REPAIR LOCAL TABLE t2, t1 EXTENDED;
+Table Op Msg_type Msg_text
+test.t2 repair note The storage engine for the table doesn't support repair
+test.t1 repair note The storage engine for the table doesn't support repair
+INSERT INTO t1 (a,b) VALUES (10,'j');
+INSERT INTO t2 (a,b) VALUES (11,'k');
+REPAIR TABLE t1, t2 QUICK USE_FRM;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+test.t2 repair note The storage engine for the table doesn't support repair
+INSERT INTO t1 (a,b) VALUES (12,'l');
+INSERT INTO t2 (a,b) VALUES (13,'m');
+REPAIR NO_WRITE_TO_BINLOG TABLE t1, t2 QUICK EXTENDED USE_FRM;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+test.t2 repair note The storage engine for the table doesn't support repair
+FLUSH TABLE t1;
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/replace.result b/storage/rocksdb/mysql-test/rocksdb/r/replace.result
new file mode 100644
index 00000000..f8f61a3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/replace.result
@@ -0,0 +1,32 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+REPLACE INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+3 c
+4 d
+5 e
+REPLACE t1 (a,b) VALUE (10,'foo'),(10,'foo');
+SELECT a,b FROM t1;
+a b
+1 a
+10 foo
+10 foo
+2 b
+3 c
+4 d
+5 e
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (b)) ENGINE=rocksdb;
+REPLACE INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+INSERT INTO t1 (a,b) VALUES (4,'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+REPLACE INTO t1 (a,b) VALUES (4,'b');
+SELECT a,b FROM t1;
+a b
+1 a
+3 c
+4 b
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb.result
new file mode 100644
index 00000000..95cbc3e7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb.result
@@ -0,0 +1,2643 @@
+select ENGINE,COMMENT,TRANSACTIONS,XA,SAVEPOINTS from information_schema.engines where engine = 'rocksdb';
+ENGINE COMMENT TRANSACTIONS XA SAVEPOINTS
+ROCKSDB RocksDB storage engine YES YES YES
+drop table if exists t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10;
+drop table if exists t11,t12,t13,t14,t15,t16,t17,t18,t19,t20;
+drop table if exists t21,t22,t23,t24,t25,t26,t27,t28,t29;
+drop table if exists t30,t31,t32,t33,t34,t35,t36,t37,t38,t39;
+drop table if exists t40,t41,t42,t43,t44,t45,t46,t47,t48,t49;
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+#
+# Issue #1: Don't update indexes if index values have not changed
+#
+create table t1 (
+pk int primary key,
+a int,
+b int,
+key(a)
+) engine=rocksdb;
+insert into t1 values
+(1,1,1), (2,2,2), (3,3,3), (4,4,4);
+set @var1=(select variable_value
+from information_schema.global_status
+where variable_name='rocksdb_number_keys_written');
+# Do an update that doesn't change the key 'a'.
+update t1 set b=3334341 where a=2;
+set @var2=(select variable_value
+from information_schema.global_status
+where variable_name='rocksdb_number_keys_written');
+# The following should produce 1
+select @var2 - @var1;
+@var2 - @var1
+1
+# Do an update that sets the key to the same value
+update t1 set a=pk where a=3;
+set @var3=(select variable_value
+from information_schema.global_status
+where variable_name='rocksdb_number_keys_written');
+# We have 'updated' column to the same value, so the following must return 0:
+select @var3 - @var2;
+@var3 - @var2
+0
+drop table t1;
+create table t0 (a int primary key) engine=rocksdb;
+show create table t0;
+Table Create Table
+t0 CREATE TABLE `t0` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+drop table t0;
+create table t1 (a int primary key, b int) engine=rocksdb;
+insert into t1 values (1,1);
+insert into t1 values (2,2);
+select * from t1;
+a b
+1 1
+2 2
+# Check that we can create another table and insert there
+create table t2 (a varchar(10) primary key, b varchar(10)) engine=rocksdb;
+insert into t2 value ('abc','def');
+insert into t2 value ('hijkl','mnopq');
+select * from t2;
+a b
+abc def
+hijkl mnopq
+# Select again from t1 to see that records from different tables dont mix
+select * from t1;
+a b
+1 1
+2 2
+explain select * from t2 where a='no-such-key';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables
+explain select * from t2 where a='abc';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 const PRIMARY PRIMARY 12 const #
+select * from t2 where a='abc';
+a b
+abc def
+# Try a composite PK
+create table t3 (
+pk1 int,
+pk2 varchar(10),
+col1 varchar(10),
+primary key(pk1, pk2)
+) engine=rocksdb;
+insert into t3 values (2,'two', 'row#2');
+insert into t3 values (3,'three', 'row#3');
+insert into t3 values (1,'one', 'row#1');
+select * from t3;
+pk1 pk2 col1
+1 one row#1
+2 two row#2
+3 three row#3
+select * from t3 where pk1=3 and pk2='three';
+pk1 pk2 col1
+3 three row#3
+drop table t1, t2, t3;
+#
+# Test blob values
+#
+create table t4 (a int primary key, b blob) engine=rocksdb;
+insert into t4 values (1, repeat('quux-quux', 60));
+insert into t4 values (10, repeat('foo-bar', 43));
+insert into t4 values (5, repeat('foo-bar', 200));
+insert into t4 values (2, NULL);
+select
+a,
+(case a
+when 1 then b=repeat('quux-quux', 60)
+when 10 then b=repeat('foo-bar', 43)
+when 5 then b=repeat('foo-bar', 200)
+when 2 then b is null
+else 'IMPOSSIBLE!' end) as CMP
+from t4;
+a CMP
+1 1
+2 1
+5 1
+10 1
+drop table t4;
+#
+# Test blobs of various sizes
+#
+# TINYBLOB
+create table t5 (a int primary key, b tinyblob) engine=rocksdb;
+insert into t5 values (1, repeat('quux-quux', 6));
+insert into t5 values (10, repeat('foo-bar', 4));
+insert into t5 values (5, repeat('foo-bar', 2));
+select
+a,
+(case a
+when 1 then b=repeat('quux-quux', 6)
+when 10 then b=repeat('foo-bar', 4)
+when 5 then b=repeat('foo-bar', 2)
+else 'IMPOSSIBLE!' end) as CMP
+from t5;
+a CMP
+1 1
+5 1
+10 1
+drop table t5;
+# MEDIUMBLOB
+create table t6 (a int primary key, b mediumblob) engine=rocksdb;
+insert into t6 values (1, repeat('AB', 65000));
+insert into t6 values (10, repeat('bbb', 40000));
+insert into t6 values (5, repeat('foo-bar', 2));
+select
+a,
+(case a
+when 1 then b=repeat('AB', 65000)
+when 10 then b=repeat('bbb', 40000)
+when 5 then b=repeat('foo-bar', 2)
+else 'IMPOSSIBLE!' end) as CMP
+from t6;
+a CMP
+1 1
+5 1
+10 1
+drop table t6;
+# LONGBLOB
+create table t7 (a int primary key, b longblob) engine=rocksdb;
+insert into t7 values (1, repeat('AB', 65000));
+insert into t7 values (10, repeat('bbb', 40000));
+insert into t7 values (5, repeat('foo-bar', 2));
+select
+a,
+(case a
+when 1 then b=repeat('AB', 65000)
+when 10 then b=repeat('bbb', 40000)
+when 5 then b=repeat('foo-bar', 2)
+else 'IMPOSSIBLE!' end) as CMP
+from t7;
+a CMP
+1 1
+5 1
+10 1
+drop table t7;
+#
+# Check if DELETEs work
+#
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+insert into t8 values
+('one', 'eins'),
+('two', 'zwei'),
+('three', 'drei'),
+('four', 'vier'),
+('five', 'funf');
+# Delete by PK
+explain delete from t8 where a='three';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t8 range PRIMARY PRIMARY 12 NULL # Using where
+delete from t8 where a='three';
+select * from t8;
+a col1
+five funf
+four vier
+one eins
+two zwei
+# Delete while doing a full table scan
+delete from t8 where col1='eins' or col1='vier';
+select * from t8;
+a col1
+five funf
+two zwei
+# delete w/o WHERE:
+delete from t8;
+select * from t8;
+a col1
+#
+# Test UPDATEs
+#
+insert into t8 values
+('one', 'eins'),
+('two', 'zwei'),
+('three', 'drei'),
+('four', 'vier'),
+('five', 'funf');
+update t8 set col1='dva' where a='two';
+update t8 set a='fourAAA' where col1='vier';
+select * from t8;
+a col1
+five funf
+fourAAA vier
+one eins
+three drei
+two dva
+delete from t8;
+#
+# Basic transactions tests
+#
+begin;
+insert into t8 values ('trx1-val1', 'data');
+insert into t8 values ('trx1-val2', 'data');
+rollback;
+select * from t8;
+a col1
+begin;
+insert into t8 values ('trx1-val1', 'data');
+insert into t8 values ('trx1-val2', 'data');
+commit;
+select * from t8;
+a col1
+trx1-val1 data
+trx1-val2 data
+drop table t8;
+#
+# Check if DROP TABLE works
+#
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+select * from t8;
+a col1
+insert into t8 values ('foo','foo');
+drop table t8;
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+select * from t8;
+a col1
+drop table t8;
+#
+# MDEV-3961: Assertion ... on creating a TEMPORARY RocksDB table
+#
+CREATE TEMPORARY TABLE t10 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+ERROR HY000: Table storage engine 'ROCKSDB' does not support the create option 'TEMPORARY'
+#
+# MDEV-3963: JOIN or WHERE conditions involving keys on RocksDB tables don't work
+#
+CREATE TABLE t10 (i INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t10 VALUES (1),(3);
+CREATE TABLE t11 (j INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t11 VALUES (1),(4);
+select * from t10;
+i
+1
+3
+select * from t11;
+j
+1
+4
+EXPLAIN
+SELECT * FROM t10, t11 WHERE i=j;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t10 index PRIMARY PRIMARY 4 NULL # Using index
+1 SIMPLE t11 eq_ref PRIMARY PRIMARY 4 test.t10.i # Using index
+SELECT * FROM t10, t11 WHERE i=j;
+i j
+1 1
+DROP TABLE t10,t11;
+#
+# MDEV-3962: SELECT with ORDER BY causes "ERROR 1030 (HY000): Got error 122
+#
+CREATE TABLE t12 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t12 VALUES (2),(1);
+SELECT * FROM t12 ORDER BY pk;
+pk
+1
+2
+DROP TABLE t12;
+#
+# MDEV-3964: Assertion `!pk_descr' fails in ha_rocksdb::open on adding partitions ...
+#
+create table t14 (pk int primary key) engine=RocksDB partition by hash(pk) partitions 2;
+drop table t14;
+#
+# MDEV-3960: Server crashes on running DISCARD TABLESPACE on a RocksDB table
+#
+create table t9 (i int primary key) engine=rocksdb;
+alter table t9 discard tablespace;
+ERROR HY000: Storage engine ROCKSDB of the table `test`.`t9` doesn't have this option
+drop table t9;
+#
+# MDEV-3959: Assertion `slice->size() == table->s->reclength' fails ...
+# on accessing a table after ALTER
+#
+CREATE TABLE t15 (a INT, rocksdb_pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t15 VALUES (1,1),(5,2);
+ALTER TABLE t15 DROP COLUMN a;
+DROP TABLE t15;
+#
+# MDEV-3968: UPDATE produces a wrong result while modifying a PK on a RocksDB table
+#
+create table t16 (pk int primary key, a char(8)) engine=RocksDB;
+insert into t16 values (1,'a'),(2,'b'),(3,'c'),(4,'d');
+update t16 set pk=100, a = 'updated' where a in ('b','c');
+ERROR 23000: Duplicate entry '100' for key 'PRIMARY'
+select * from t16;
+pk a
+1 a
+2 b
+3 c
+4 d
+drop table t16;
+#
+# MDEV-3970: A set of assorted crashes on inserting a row into a RocksDB table
+#
+drop table if exists t_very_long_table_name;
+CREATE TABLE `t_very_long_table_name` (
+`c` char(1) NOT NULL,
+`c0` char(0) NOT NULL,
+`c1` char(1) NOT NULL,
+`c20` char(20) NOT NULL,
+`c255` char(255) NOT NULL,
+PRIMARY KEY (`c255`)
+) ENGINE=RocksDB DEFAULT CHARSET=latin1;
+INSERT INTO t_very_long_table_name VALUES ('a', '', 'c', REPEAT('a',20), REPEAT('x',255));
+drop table t_very_long_table_name;
+#
+# Test table locking and read-before-write checks.
+#
+create table t17 (pk varchar(12) primary key, col1 varchar(12)) engine=rocksdb;
+insert into t17 values ('row1', 'val1');
+insert into t17 values ('row1', 'val1-try2');
+ERROR 23000: Duplicate entry 'row1' for key 'PRIMARY'
+insert into t17 values ('ROW1', 'val1-try2');
+ERROR 23000: Duplicate entry 'ROW1' for key 'PRIMARY'
+insert into t17 values ('row2', 'val2');
+insert into t17 values ('row3', 'val3');
+# This is ok
+update t17 set pk='row4' where pk='row1';
+# This will try to overwrite another row:
+update t17 set pk='row3' where pk='row2';
+ERROR 23000: Duplicate entry 'row3' for key 'PRIMARY'
+select * from t17;
+pk col1
+row2 val2
+row3 val3
+row4 val1
+#
+# Locking tests
+#
+connect con1,localhost,root,,;
+# First, make sure there's no locking when transactions update different rows
+connection con1;
+set autocommit=0;
+update t17 set col1='UPD1' where pk='row2';
+connection default;
+update t17 set col1='UPD2' where pk='row3';
+connection con1;
+commit;
+connection default;
+select * from t17;
+pk col1
+row2 UPD1
+row3 UPD2
+row4 val1
+# Check the variable
+show variables like 'rocksdb_lock_wait_timeout';
+Variable_name Value
+rocksdb_lock_wait_timeout 1
+set rocksdb_lock_wait_timeout=2;
+show variables like 'rocksdb_lock_wait_timeout';
+Variable_name Value
+rocksdb_lock_wait_timeout 2
+# Try updating the same row from two transactions
+connection con1;
+begin;
+update t17 set col1='UPD2-AA' where pk='row2';
+connection default;
+update t17 set col1='UPD2-BB' where pk='row2';
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+set rocksdb_lock_wait_timeout=1000;
+update t17 set col1='UPD2-CC' where pk='row2';
+connection con1;
+rollback;
+connection default;
+select * from t17 where pk='row2';
+pk col1
+row2 UPD2-CC
+drop table t17;
+disconnect con1;
+#
+# MDEV-4035: RocksDB: SELECT produces different results inside a transaction (read is not repeatable)
+#
+create table t18 (pk int primary key, i int) engine=RocksDB;
+begin;
+select * from t18;
+pk i
+select * from t18 where pk = 1;
+pk i
+connect con1,localhost,root,,;
+insert into t18 values (1,100);
+connection default;
+select * from t18;
+pk i
+select * from t18 where pk = 1;
+pk i
+commit;
+drop table t18;
+#
+# MDEV-4036: RocksDB: INSERT .. ON DUPLICATE KEY UPDATE does not work, produces ER_DUP_KEY
+#
+create table t19 (pk int primary key, i int) engine=RocksDB;
+insert into t19 values (1,1);
+insert into t19 values (1,100) on duplicate key update i = 102;
+select * from t19;
+pk i
+1 102
+drop table t19;
+# MDEV-4037: RocksDB: REPLACE doesn't work, produces ER_DUP_KEY
+create table t20 (pk int primary key, i int) engine=RocksDB;
+insert into t20 values (1,1);
+replace into t20 values (1,100);
+select * from t20;
+pk i
+1 100
+drop table t20;
+#
+# MDEV-4041: Server crashes in Primary_key_comparator::get_hashnr on INSERT
+#
+create table t21 (v varbinary(16) primary key, i int) engine=RocksDB;
+insert into t21 values ('a',1);
+select * from t21;
+v i
+a 1
+drop table t21;
+#
+# MDEV-4047: RocksDB: Assertion `0' fails in Protocol::end_statement() on multi-table INSERT IGNORE
+#
+CREATE TABLE t22 (a int primary key) ENGINE=RocksDB;
+INSERT INTO t22 VALUES (1),(2);
+CREATE TABLE t23 (b int primary key) ENGINE=RocksDB;
+INSERT INTO t23 SELECT * FROM t22;
+DELETE IGNORE t22.*, t23.* FROM t22, t23 WHERE b < a;
+DROP TABLE t22,t23;
+#
+# MDEV-4046: RocksDB: Multi-table DELETE locks itself and ends with ER_LOCK_WAIT_TIMEOUT
+#
+CREATE TABLE t24 (pk int primary key) ENGINE=RocksDB;
+INSERT INTO t24 VALUES (1),(2);
+CREATE TABLE t25 LIKE t24;
+INSERT INTO t25 SELECT * FROM t24;
+DELETE t25.* FROM t24, t25;
+DROP TABLE t24,t25;
+#
+# MDEV-4044: RocksDB: UPDATE or DELETE with ORDER BY locks itself
+#
+create table t26 (pk int primary key, c char(1)) engine=RocksDB;
+insert into t26 values (1,'a'),(2,'b');
+update t26 set c = 'x' order by pk limit 1;
+delete from t26 order by pk limit 1;
+select * from t26;
+pk c
+2 b
+drop table t26;
+#
+# Test whether SELECT ... FOR UPDATE puts locks
+#
+create table t27(pk varchar(10) primary key, col1 varchar(20)) engine=RocksDB;
+insert into t27 values
+('row1', 'row1data'),
+('row2', 'row2data'),
+('row3', 'row3data');
+connection con1;
+begin;
+select * from t27 where pk='row3' for update;
+pk col1
+row3 row3data
+connection default;
+set rocksdb_lock_wait_timeout=1;
+update t27 set col1='row2-modified' where pk='row3';
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+rollback;
+connection default;
+disconnect con1;
+drop table t27;
+#
+# MDEV-4060: RocksDB: Assertion `! trx->batch' fails in
+#
+create table t28 (pk int primary key, a int) engine=RocksDB;
+insert into t28 values (1,10),(2,20);
+begin;
+update t28 set a = 100 where pk = 3;
+rollback;
+select * from t28;
+pk a
+1 10
+2 20
+drop table t28;
+#
+# Secondary indexes
+#
+create table t30 (
+pk varchar(16) not null primary key,
+key1 varchar(16) not null,
+col1 varchar(16) not null,
+key(key1)
+) engine=rocksdb;
+insert into t30 values ('row1', 'row1-key', 'row1-data');
+insert into t30 values ('row2', 'row2-key', 'row2-data');
+insert into t30 values ('row3', 'row3-key', 'row3-data');
+explain
+select * from t30 where key1='row2-key';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 ref key1 key1 18 const # Using index condition
+select * from t30 where key1='row2-key';
+pk key1 col1
+row2 row2-key row2-data
+explain
+select * from t30 where key1='row1';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 ref key1 key1 18 const # Using index condition
+# This will produce nothing:
+select * from t30 where key1='row1';
+pk key1 col1
+explain
+select key1 from t30;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 index NULL key1 18 NULL # Using index
+select key1 from t30;
+key1
+row1-key
+row2-key
+row3-key
+# Create a duplicate record
+insert into t30 values ('row2a', 'row2-key', 'row2a-data');
+# Can we see it?
+select * from t30 where key1='row2-key';
+pk key1 col1
+row2 row2-key row2-data
+row2a row2-key row2a-data
+delete from t30 where pk='row2';
+select * from t30 where key1='row2-key';
+pk key1 col1
+row2a row2-key row2a-data
+#
+# Range scans on secondary index
+#
+delete from t30;
+insert into t30 values
+('row1', 'row1-key', 'row1-data'),
+('row2', 'row2-key', 'row2-data'),
+('row3', 'row3-key', 'row3-data'),
+('row4', 'row4-key', 'row4-data'),
+('row5', 'row5-key', 'row5-data');
+explain
+select * from t30 where key1 <='row3-key';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range key1 key1 18 NULL # Using index condition
+select * from t30 where key1 <='row3-key';
+pk key1 col1
+row1 row1-key row1-data
+row2 row2-key row2-data
+row3 row3-key row3-data
+explain
+select * from t30 where key1 between 'row2-key' and 'row4-key';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range key1 key1 18 NULL # Using index condition
+select * from t30 where key1 between 'row2-key' and 'row4-key';
+pk key1 col1
+row2 row2-key row2-data
+row3 row3-key row3-data
+row4 row4-key row4-data
+explain
+select * from t30 where key1 in ('row2-key','row4-key');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range key1 key1 18 NULL # Using index condition
+select * from t30 where key1 in ('row2-key','row4-key');
+pk key1 col1
+row2 row2-key row2-data
+row4 row4-key row4-data
+explain
+select key1 from t30 where key1 in ('row2-key','row4-key');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range key1 key1 18 NULL # Using where; Using index
+select key1 from t30 where key1 in ('row2-key','row4-key');
+key1
+row2-key
+row4-key
+explain
+select * from t30 where key1 > 'row1-key' and key1 < 'row4-key';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range key1 key1 18 NULL # Using index condition
+select * from t30 where key1 > 'row1-key' and key1 < 'row4-key';
+pk key1 col1
+row2 row2-key row2-data
+row3 row3-key row3-data
+explain
+select * from t30 order by key1 limit 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 index NULL key1 18 NULL #
+select * from t30 order by key1 limit 3;
+pk key1 col1
+row1 row1-key row1-data
+row2 row2-key row2-data
+row3 row3-key row3-data
+explain
+select * from t30 order by key1 desc limit 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 index NULL key1 18 NULL #
+select * from t30 order by key1 desc limit 3;
+pk key1 col1
+row5 row5-key row5-data
+row4 row4-key row4-data
+row3 row3-key row3-data
+#
+# Range scans on primary key
+#
+explain
+select * from t30 where pk <='row3';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range PRIMARY PRIMARY 18 NULL # Using where
+select * from t30 where pk <='row3';
+pk key1 col1
+row1 row1-key row1-data
+row2 row2-key row2-data
+row3 row3-key row3-data
+explain
+select * from t30 where pk between 'row2' and 'row4';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range PRIMARY PRIMARY 18 NULL # Using where
+select * from t30 where pk between 'row2' and 'row4';
+pk key1 col1
+row2 row2-key row2-data
+row3 row3-key row3-data
+row4 row4-key row4-data
+explain
+select * from t30 where pk in ('row2','row4');
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 range PRIMARY PRIMARY 18 NULL # Using where
+select * from t30 where pk in ('row2','row4');
+pk key1 col1
+row2 row2-key row2-data
+row4 row4-key row4-data
+explain
+select * from t30 order by pk limit 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t30 index NULL PRIMARY 18 NULL #
+select * from t30 order by pk limit 3;
+pk key1 col1
+row1 row1-key row1-data
+row2 row2-key row2-data
+row3 row3-key row3-data
+drop table t30;
+#
+# MDEV-3841: RocksDB: Reading by PK prefix does not work
+#
+create table t31 (i int, j int, k int, primary key(i,j,k)) engine=RocksDB;
+insert into t31 values (1,10,100),(2,20,200);
+select * from t31 where i = 1;
+i j k
+1 10 100
+select * from t31 where j = 10;
+i j k
+1 10 100
+select * from t31 where k = 100;
+i j k
+1 10 100
+select * from t31 where i = 1 and j = 10;
+i j k
+1 10 100
+select * from t31 where i = 1 and k = 100;
+i j k
+1 10 100
+select * from t31 where j = 10 and k = 100;
+i j k
+1 10 100
+select * from t31 where i = 1 and j = 10 and k = 100;
+i j k
+1 10 100
+drop table t31;
+#
+# MDEV-4055: RocksDB: UPDATE/DELETE by a multi-part PK does not work
+#
+create table t32 (i int, j int, k int, primary key(i,j,k), a varchar(8)) engine=RocksDB;
+insert into t32 values
+(1,10,100,''),
+(2,20,200,'');
+select * from t32 where i = 1 and j = 10 and k = 100;
+i j k a
+1 10 100
+update t32 set a = 'updated' where i = 1 and j = 10 and k = 100;
+select * from t32;
+i j k a
+1 10 100 updated
+2 20 200
+drop table t32;
+#
+# MDEV-3841: RocksDB: Assertion `0' fails in ha_rocksdb::index_read_map on range select with ORDER BY .. DESC
+#
+CREATE TABLE t33 (pk INT PRIMARY KEY, a CHAR(1)) ENGINE=RocksDB;
+INSERT INTO t33 VALUES (1,'a'),(2,'b');
+SELECT * FROM t33 WHERE pk <= 10 ORDER BY pk DESC;
+pk a
+2 b
+1 a
+DROP TABLE t33;
+#
+# MDEV-4081: RocksDB throws error 122 on an attempt to create a table with unique index
+#
+# Unique indexes can be created, but uniqueness won't be enforced
+create table t33 (pk int primary key, u int, unique index(u)) engine=RocksDB;
+drop table t33;
+#
+# MDEV-4077: RocksDB: Wrong result (duplicate row) on select with range
+#
+CREATE TABLE t34 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t34 VALUES (10),(11);
+SELECT pk FROM t34 WHERE pk > 5 AND pk < 15;
+pk
+10
+11
+SELECT pk FROM t34 WHERE pk BETWEEN 5 AND 15;
+pk
+10
+11
+SELECT pk FROM t34 WHERE pk > 5;
+pk
+10
+11
+SELECT pk FROM t34 WHERE pk < 15;
+pk
+10
+11
+drop table t34;
+#
+# MDEV-4086: RocksDB does not allow a query with multi-part pk and index and ORDER BY .. DEC
+#
+create table t35 (a int, b int, c int, d int, e int, primary key (a,b,c), key (a,c,d,e)) engine=RocksDB;
+insert into t35 values (1,1,1,1,1),(2,2,2,2,2);
+select * from t35 where a = 1 and c = 1 and d = 1 order by e desc;
+a b c d e
+1 1 1 1 1
+drop table t35;
+#
+# MDEV-4084: RocksDB: Wrong result on IN subquery with index
+#
+CREATE TABLE t36 (pk INT PRIMARY KEY, a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t36 VALUES (1,10),(2,20);
+SELECT 3 IN ( SELECT a FROM t36 );
+3 IN ( SELECT a FROM t36 )
+0
+drop table t36;
+#
+# MDEV-4084: RocksDB: Wrong result on IN subquery with index
+#
+CREATE TABLE t37 (pk INT PRIMARY KEY, a INT, b CHAR(1), KEY(a), KEY(a,b))
+ENGINE=RocksDB;
+INSERT INTO t37 VALUES (1,10,'x'), (2,20,'y');
+SELECT MAX(a) FROM t37 WHERE a < 100;
+MAX(a)
+20
+DROP TABLE t37;
+#
+# MDEV-4090: RocksDB: Wrong result (duplicate rows) on range access with secondary key and ORDER BY DESC
+#
+CREATE TABLE t38 (pk INT PRIMARY KEY, i INT, KEY(i)) ENGINE=RocksDB;
+INSERT INTO t38 VALUES (1,10), (2,20);
+SELECT i FROM t38 WHERE i NOT IN (8) ORDER BY i DESC;
+i
+20
+10
+drop table t38;
+#
+# MDEV-4092: RocksDB: Assertion `in_table(pa, a_len)' fails in Rdb_key_def::cmp_full_keys
+# with a multi-part key and ORDER BY .. DESC
+#
+CREATE TABLE t40 (pk1 INT PRIMARY KEY, a INT, b VARCHAR(1), KEY(b,a)) ENGINE=RocksDB;
+INSERT INTO t40 VALUES (1, 7,'x'),(2,8,'y');
+CREATE TABLE t41 (pk2 INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t41 VALUES (1),(2);
+SELECT * FROM t40, t41 WHERE pk1 = pk2 AND b = 'o' ORDER BY a DESC;
+pk1 a b pk2
+DROP TABLE t40,t41;
+#
+# MDEV-4093: RocksDB: IN subquery by secondary key with NULL among values returns true instead of NULL
+#
+CREATE TABLE t42 (pk INT PRIMARY KEY, a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t42 VALUES (1, NULL),(2, 8);
+SELECT ( 3 ) NOT IN ( SELECT a FROM t42 );
+( 3 ) NOT IN ( SELECT a FROM t42 )
+NULL
+DROP TABLE t42;
+#
+# MDEV-4094: RocksDB: Wrong result on SELECT and ER_KEY_NOT_FOUND on
+# DELETE with search by NULL-able secondary key ...
+#
+CREATE TABLE t43 (pk INT PRIMARY KEY, a INT, b CHAR(1), KEY(a)) ENGINE=RocksDB;
+INSERT INTO t43 VALUES (1,8,'g'),(2,9,'x');
+UPDATE t43 SET pk = 10 WHERE a = 8;
+REPLACE INTO t43 ( a ) VALUES ( 8 );
+Warnings:
+Warning 1364 Field 'pk' doesn't have a default value
+REPLACE INTO t43 ( b ) VALUES ( 'y' );
+Warnings:
+Warning 1364 Field 'pk' doesn't have a default value
+SELECT * FROM t43 WHERE a = 8;
+pk a b
+10 8 g
+DELETE FROM t43 WHERE a = 8;
+DROP TABLE t43;
+#
+# Basic AUTO_INCREMENT tests
+#
+create table t44(pk int primary key auto_increment, col1 varchar(12)) engine=rocksdb;
+insert into t44 (col1) values ('row1');
+insert into t44 (col1) values ('row2');
+insert into t44 (col1) values ('row3');
+select * from t44;
+pk col1
+1 row1
+2 row2
+3 row3
+drop table t44;
+#
+# ALTER TABLE tests
+#
+create table t45 (pk int primary key, col1 varchar(12)) engine=rocksdb;
+insert into t45 values (1, 'row1');
+insert into t45 values (2, 'row2');
+alter table t45 rename t46;
+select * from t46;
+pk col1
+1 row1
+2 row2
+drop table t46;
+drop table t45;
+ERROR 42S02: Unknown table 'test.t45'
+#
+# Check Bulk loading
+# Bulk loading used to overwrite existing data
+# Now it fails if there is data overlap with what
+# already exists
+#
+show variables
+where
+variable_name like 'rocksdb%' and
+variable_name not like 'rocksdb_max_open_files' and
+variable_name not like 'rocksdb_supported_compression_types';
+Variable_name Value
+rocksdb_access_hint_on_compaction_start 1
+rocksdb_advise_random_on_open ON
+rocksdb_allow_concurrent_memtable_write OFF
+rocksdb_allow_mmap_reads OFF
+rocksdb_allow_mmap_writes OFF
+rocksdb_allow_to_start_after_corruption OFF
+rocksdb_blind_delete_primary_key OFF
+rocksdb_block_cache_size 536870912
+rocksdb_block_restart_interval 16
+rocksdb_block_size 4096
+rocksdb_block_size_deviation 10
+rocksdb_bulk_load OFF
+rocksdb_bulk_load_allow_sk OFF
+rocksdb_bulk_load_allow_unsorted OFF
+rocksdb_bulk_load_size 1000
+rocksdb_bytes_per_sync 0
+rocksdb_cache_dump ON
+rocksdb_cache_high_pri_pool_ratio 0.000000
+rocksdb_cache_index_and_filter_blocks ON
+rocksdb_cache_index_and_filter_with_high_priority ON
+rocksdb_checksums_pct 100
+rocksdb_collect_sst_properties ON
+rocksdb_commit_in_the_middle OFF
+rocksdb_commit_time_batch_for_recovery ON
+rocksdb_compact_cf
+rocksdb_compaction_readahead_size 0
+rocksdb_compaction_sequential_deletes 0
+rocksdb_compaction_sequential_deletes_count_sd OFF
+rocksdb_compaction_sequential_deletes_file_size 0
+rocksdb_compaction_sequential_deletes_window 0
+rocksdb_create_checkpoint
+rocksdb_create_if_missing ON
+rocksdb_create_missing_column_families OFF
+rocksdb_datadir ./#rocksdb
+rocksdb_db_write_buffer_size 0
+rocksdb_deadlock_detect OFF
+rocksdb_deadlock_detect_depth 50
+rocksdb_debug_manual_compaction_delay 0
+rocksdb_debug_optimizer_no_zero_cardinality ON
+rocksdb_debug_ttl_ignore_pk OFF
+rocksdb_debug_ttl_read_filter_ts 0
+rocksdb_debug_ttl_rec_ts 0
+rocksdb_debug_ttl_snapshot_ts 0
+rocksdb_default_cf_options
+rocksdb_delayed_write_rate 0
+rocksdb_delete_cf
+rocksdb_delete_obsolete_files_period_micros 21600000000
+rocksdb_enable_2pc ON
+rocksdb_enable_bulk_load_api ON
+rocksdb_enable_insert_with_update_caching ON
+rocksdb_enable_thread_tracking ON
+rocksdb_enable_ttl ON
+rocksdb_enable_ttl_read_filtering ON
+rocksdb_enable_write_thread_adaptive_yield OFF
+rocksdb_error_if_exists OFF
+rocksdb_error_on_suboptimal_collation ON
+rocksdb_flush_log_at_trx_commit 0
+rocksdb_force_compute_memtable_stats ON
+rocksdb_force_compute_memtable_stats_cachetime 0
+rocksdb_force_flush_memtable_and_lzero_now OFF
+rocksdb_force_flush_memtable_now OFF
+rocksdb_force_index_records_in_range 0
+rocksdb_git_hash #
+rocksdb_hash_index_allow_collision ON
+rocksdb_ignore_unknown_options ON
+rocksdb_index_type kBinarySearch
+rocksdb_info_log_level error_level
+rocksdb_io_write_timeout 0
+rocksdb_is_fd_close_on_exec ON
+rocksdb_keep_log_file_num 1000
+rocksdb_large_prefix OFF
+rocksdb_lock_scanned_rows OFF
+rocksdb_lock_wait_timeout 1
+rocksdb_log_file_time_to_roll 0
+rocksdb_manifest_preallocation_size 4194304
+rocksdb_manual_compaction_threads 0
+rocksdb_manual_wal_flush ON
+rocksdb_master_skip_tx_api OFF
+rocksdb_max_background_jobs 2
+rocksdb_max_latest_deadlocks 5
+rocksdb_max_log_file_size 0
+rocksdb_max_manifest_file_size 1073741824
+rocksdb_max_manual_compactions 10
+rocksdb_max_row_locks 1048576
+rocksdb_max_subcompactions 1
+rocksdb_max_total_wal_size 0
+rocksdb_merge_buf_size 67108864
+rocksdb_merge_combine_read_size 1073741824
+rocksdb_merge_tmp_file_removal_delay_ms 0
+rocksdb_new_table_reader_for_compaction_inputs OFF
+rocksdb_no_block_cache OFF
+rocksdb_override_cf_options
+rocksdb_paranoid_checks ON
+rocksdb_pause_background_work ON
+rocksdb_perf_context_level 0
+rocksdb_persistent_cache_path
+rocksdb_persistent_cache_size_mb 0
+rocksdb_pin_l0_filter_and_index_blocks_in_cache ON
+rocksdb_print_snapshot_conflict_queries OFF
+rocksdb_rate_limiter_bytes_per_sec 0
+rocksdb_records_in_range 50
+rocksdb_remove_mariabackup_checkpoint OFF
+rocksdb_reset_stats OFF
+rocksdb_rollback_on_timeout OFF
+rocksdb_seconds_between_stat_computes 3600
+rocksdb_signal_drop_index_thread OFF
+rocksdb_sim_cache_size 0
+rocksdb_skip_bloom_filter_on_read OFF
+rocksdb_skip_fill_cache OFF
+rocksdb_skip_unique_check_tables .*
+rocksdb_sst_mgr_rate_bytes_per_sec 0
+rocksdb_stats_dump_period_sec 600
+rocksdb_stats_level 0
+rocksdb_stats_recalc_rate 0
+rocksdb_store_row_debug_checksums OFF
+rocksdb_strict_collation_check OFF
+rocksdb_strict_collation_exceptions
+rocksdb_table_cache_numshardbits 6
+rocksdb_table_stats_sampling_pct 10
+rocksdb_tmpdir
+rocksdb_trace_sst_api OFF
+rocksdb_two_write_queues ON
+rocksdb_unsafe_for_binlog OFF
+rocksdb_update_cf_options
+rocksdb_use_adaptive_mutex OFF
+rocksdb_use_clock_cache OFF
+rocksdb_use_direct_io_for_flush_and_compaction OFF
+rocksdb_use_direct_reads OFF
+rocksdb_use_fsync OFF
+rocksdb_validate_tables 1
+rocksdb_verify_row_debug_checksums OFF
+rocksdb_wal_bytes_per_sync 0
+rocksdb_wal_dir
+rocksdb_wal_recovery_mode 1
+rocksdb_wal_size_limit_mb 0
+rocksdb_wal_ttl_seconds 0
+rocksdb_whole_key_filtering ON
+rocksdb_write_batch_max_bytes 0
+rocksdb_write_disable_wal OFF
+rocksdb_write_ignore_missing_column_families OFF
+rocksdb_write_policy write_committed
+create table t47 (pk int primary key, col1 varchar(12)) engine=rocksdb;
+insert into t47 values (1, 'row1');
+insert into t47 values (2, 'row2');
+set rocksdb_bulk_load=1;
+insert into t47 values (3, 'row3'),(4, 'row4');
+set rocksdb_bulk_load=0;
+connect con1,localhost,root,,;
+set rocksdb_bulk_load=1;
+insert into t47 values (10, 'row10'),(11, 'row11');
+connection default;
+set rocksdb_bulk_load=1;
+insert into t47 values (100, 'row100'),(101, 'row101');
+disconnect con1;
+connection default;
+set rocksdb_bulk_load=0;
+select * from t47;
+pk col1
+1 row1
+2 row2
+3 row3
+4 row4
+10 row10
+11 row11
+100 row100
+101 row101
+drop table t47;
+#
+# Fix TRUNCATE over empty table (transaction is committed when it wasn't
+# started)
+#
+create table t48(pk int primary key auto_increment, col1 varchar(12)) engine=rocksdb;
+set autocommit=0;
+truncate table t48;
+set autocommit=1;
+drop table t48;
+#
+# MDEV-4059: RocksDB: query waiting for a lock cannot be killed until query timeout exceeded
+#
+create table t49 (pk int primary key, a int) engine=RocksDB;
+insert into t49 values (1,10),(2,20);
+begin;
+update t49 set a = 100 where pk = 1;
+connect con1,localhost,root,,;
+set rocksdb_lock_wait_timeout=60;
+set @var1= to_seconds(now());
+update t49 set a = 1000 where pk = 1;
+connect con2,localhost,root,,;
+kill query $con1_id;
+connection con1;
+ERROR 70100: Query execution was interrupted
+set @var2= to_seconds(now());
+select if ((@var2 - @var1) < 60, "passed", (@var2 - @var1)) as 'result';
+result
+passed
+connection default;
+disconnect con1;
+commit;
+drop table t49;
+#
+# Index-only tests for INT-based columns
+#
+create table t1 (pk int primary key, key1 int, col1 int, key(key1)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,2,2);
+insert into t1 values (-5,-5,-5);
+# INT column uses index-only:
+explain
+select key1 from t1 where key1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 5 const # Using index
+select key1 from t1 where key1=2;
+key1
+2
+select key1 from t1 where key1=-5;
+key1
+-5
+drop table t1;
+create table t2 (pk int primary key, key1 int unsigned, col1 int, key(key1)) engine=rocksdb;
+insert into t2 values (1,1,1), (2,2,2);
+# INT UNSIGNED column uses index-only:
+explain
+select key1 from t2 where key1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref key1 key1 5 const # Using index
+select key1 from t2 where key1=2;
+key1
+2
+drop table t2;
+create table t3 (pk bigint primary key, key1 bigint, col1 int, key(key1)) engine=rocksdb;
+insert into t3 values (1,1,1), (2,2,2);
+# BIGINT uses index-only:
+explain
+select key1 from t3 where key1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 ref key1 key1 9 const # Using index
+select key1 from t3 where key1=2;
+key1
+2
+drop table t3;
+#
+# Index-only reads for string columns
+#
+create table t1 (
+pk int primary key,
+key1 char(10) character set binary,
+col1 int,
+key (key1)
+) engine=rocksdb;
+insert into t1 values(1, 'one',11), (2,'two',22);
+explain
+select key1 from t1 where key1='one';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 11 const # Using where; Using index
+# The following will produce no rows. This looks like a bug,
+# but it is actually correct behavior. Binary strings are end-padded
+# with \0 character (and not space). Comparison does not ignore
+# the tail of \0.
+select key1 from t1 where key1='one';
+key1
+explain
+select hex(key1) from t1 where key1='one\0\0\0\0\0\0\0';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 11 const # Using where; Using index
+select hex(key1) from t1 where key1='one\0\0\0\0\0\0\0';
+hex(key1)
+6F6E6500000000000000
+drop table t1;
+create table t2 (
+pk int primary key,
+key1 char(10) collate latin1_bin,
+col1 int,
+key (key1)
+) engine=rocksdb;
+insert into t2 values(1, 'one',11), (2,'two',22);
+explain
+select key1 from t2 where key1='one';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref key1 key1 11 const # Using where; Using index
+select key1 from t2 where key1='one';
+key1
+one
+drop table t2;
+create table t3 (
+pk int primary key,
+key1 char(10) collate utf8_bin,
+col1 int,
+key (key1)
+) engine=rocksdb;
+insert into t3 values(1, 'one',11), (2,'two',22);
+explain
+select key1 from t3 where key1='one';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 ref key1 key1 31 const # Using where; Using index
+select key1 from t3 where key1='one';
+key1
+one
+drop table t3;
+# a VARCHAR column
+create table t4 (
+pk int primary key,
+key1 varchar(10) collate latin1_bin,
+key(key1)
+) engine=rocksdb;
+insert into t4 values(1, 'one'), (2,'two'),(3,'threee'),(55,'fifty-five');
+explain
+select key1 from t4 where key1='two';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref key1 key1 13 const # Using where; Using index
+select key1 from t4 where key1='two';
+key1
+two
+select key1 from t4 where key1='fifty-five';
+key1
+fifty-five
+explain
+select key1 from t4 where key1 between 's' and 'u';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 range key1 key1 13 NULL # Using where; Using index
+select key1 from t4 where key1 between 's' and 'u';
+key1
+threee
+two
+drop table t4;
+#
+# MDEV-4305: RocksDB: Assertion `((keypart_map + 1) & keypart_map) == 0' fails in calculate_key_len
+#
+CREATE TABLE t1 (pk1 INT, pk2 CHAR(32), i INT, PRIMARY KEY(pk1,pk2), KEY(i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'test1',6),(2,'test2',8);
+SELECT * FROM t1 WHERE i != 3 OR pk1 > 9;
+pk1 pk2 i
+1 test1 6
+2 test2 8
+DROP TABLE t1;
+#
+# MDEV-4298: RocksDB: Assertion `thd->is_error() || kill_errno' fails in ha_rows filesort
+#
+call mtr.add_suppression("Sort aborted");
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT, KEY(i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1),(2,2);
+BEGIN;
+UPDATE t1 SET i = 100;
+connect con1,localhost,root,,test;
+DELETE IGNORE FROM t1 ORDER BY i;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+disconnect con1;
+connection default;
+COMMIT;
+DROP TABLE t1;
+#
+# MDEV-4324: RocksDB: Valgrind "Use of uninitialised value" warnings on inserting value into varchar field
+# (testcase only)
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, c VARCHAR(4)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'foo'), (2,'bar');
+DROP TABLE t1;
+#
+# MDEV-4304: RocksDB: Index-only scan by a field with utf8_bin collation returns garbage symbols
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, c1 CHAR(1), c2 CHAR(1), KEY(c1)) ENGINE=RocksDB CHARSET utf8 COLLATE utf8_bin;
+INSERT INTO t1 VALUES (1,'h','h');
+SELECT * FROM t1;
+pk c1 c2
+1 h h
+SELECT c1 FROM t1;
+c1
+h
+DROP TABLE t1;
+#
+# MDEV-4300: RocksDB: Server crashes in inline_mysql_mutex_lock on SELECT .. FOR UPDATE
+#
+CREATE TABLE t2 (pk INT PRIMARY KEY, i INT, KEY (i)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,4),(2,5);
+SELECT 1 FROM t2 WHERE i < 0 FOR UPDATE;
+1
+DROP TABLE t2;
+#
+# MDEV-4301: RocksDB: Assertion `pack_info != __null' fails in Rdb_key_def::unpack_record
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT, c CHAR(1), KEY(c,i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,4,'d'),(2,8,'e');
+SELECT MAX( pk ) FROM t1 WHERE i = 105 AND c = 'h';
+MAX( pk )
+NULL
+DROP TABLE t1;
+#
+# MDEV-4337: RocksDB: Inconsistent results comparing a char field with an int field
+#
+create table t1 (c char(1), i int, primary key(c), key(i)) engine=RocksDB;
+insert into t1 values ('2',2),('6',6);
+select * from t1 where c = i;
+c i
+2 2
+6 6
+select * from t1 ignore index (i) where c = i;
+c i
+2 2
+6 6
+drop table t1;
+#
+# Test statement rollback inside a transaction
+#
+create table t1 (pk varchar(12) primary key) engine=rocksdb;
+insert into t1 values ('old-val1'),('old-val2');
+create table t2 (pk varchar(12) primary key) engine=rocksdb;
+insert into t2 values ('new-val2'),('old-val1');
+begin;
+insert into t1 values ('new-val1');
+insert into t1 select * from t2;
+ERROR 23000: Duplicate entry 'old-val1' for key 'PRIMARY'
+commit;
+select * from t1;
+pk
+new-val1
+old-val1
+old-val2
+drop table t1, t2;
+#
+# MDEV-4383: RocksDB: Wrong result of DELETE .. ORDER BY .. LIMIT:
+# rows that should be deleted remain in the table
+#
+CREATE TABLE t2 (pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=RocksDB;
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t1 (pk) VALUES (NULL),(NULL);
+BEGIN;
+INSERT INTO t2 (pk) VALUES (NULL),(NULL);
+INSERT INTO t1 (pk) VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL);
+SELECT * FROM t1 ORDER BY pk LIMIT 9;
+pk
+1
+2
+3
+4
+5
+6
+7
+8
+affected rows: 8
+DELETE FROM t1 ORDER BY pk LIMIT 9;
+affected rows: 8
+SELECT * FROM t1 ORDER BY pk LIMIT 9;
+pk
+affected rows: 0
+DROP TABLE t1,t2;
+#
+# MDEV-4374: RocksDB: Valgrind warnings 'Use of uninitialised value' on
+# inserting into a varchar column
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, a VARCHAR(32)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'foo'),(2,'bar');
+DROP TABLE t1;
+#
+# MDEV-4061: RocksDB: Changes from an interrupted query are still applied
+#
+create table t1 (pk int primary key, a int) engine=RocksDB;
+insert into t1 values (1,10),(2,20);
+set autocommit = 1;
+update t1 set a = sleep(100) where pk = 1;
+connect con1,localhost,root,,;
+kill query $con_id;
+connection default;
+ERROR 70100: Query execution was interrupted
+select * from t1;
+pk a
+1 10
+2 20
+disconnect con1;
+drop table t1;
+#
+# MDEV-4099: RocksDB: Wrong results with index and range access after INSERT IGNORE or REPLACE
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, a SMALLINT, b INT, KEY (a)) ENGINE=RocksDB;
+INSERT IGNORE INTO t1 VALUES (1, 157, 0), (2, 1898, -504403), (1, -14659, 0);
+Warnings:
+Warning 1062 Duplicate entry '1' for key 'PRIMARY'
+SELECT * FROM t1;
+pk a b
+1 157 0
+2 1898 -504403
+SELECT pk FROM t1;
+pk
+1
+2
+SELECT * FROM t1 WHERE a != 97;
+pk a b
+1 157 0
+2 1898 -504403
+DROP TABLE t1;
+#
+# Test @@rocksdb_max_row_locks
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, a int) ENGINE=RocksDB;
+set @a=-1;
+insert into t1 select (@a:=@a+1), 1234 from information_schema.session_variables limit 100;
+set @tmp1= @@rocksdb_max_row_locks;
+set rocksdb_max_row_locks= 20;
+update t1 set a=a+10;
+ERROR HY000: Got error 10 'Operation aborted: Failed to acquire lock due to rocksdb_max_row_locks limit' from ROCKSDB
+DROP TABLE t1;
+#
+# Test AUTO_INCREMENT behavior problem,
+# "explicit insert into an auto-inc column is not noticed by RocksDB"
+#
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+insert into t1 values (null);
+insert into t1 values (null);
+select * from t1;
+i
+1
+2
+drop table t1;
+create table t2 (i int primary key auto_increment) engine=RocksDB;
+insert into t2 values (1);
+select * from t2;
+i
+1
+# this fails (ie. used to fail), RocksDB engine did not notice use of '1' above
+insert into t2 values (null);
+select * from t2;
+i
+1
+2
+# but then this succeeds, so previous statement must have incremented next number counter
+insert into t2 values (null);
+select * from t2;
+i
+1
+2
+3
+drop table t2;
+#
+# Fix Issue#2: AUTO_INCREMENT value doesn't survive server shutdown
+#
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+insert into t1 values (null);
+insert into t1 values (null);
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
+# restart
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+insert into t1 values (null);
+select * from t1;
+i
+1
+2
+3
+drop table t1;
+#
+# Fix Issue #3: SHOW TABLE STATUS shows Auto_increment=0
+#
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+insert into t1 values (null),(null);
+show table status like 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 1000 0 # 0 0 0 3 # # NULL latin1_swedish_ci NULL 0 N
+drop table t1;
+#
+# Fix Issue #4: Crash when using pseudo-unique keys
+#
+CREATE TABLE t1 (
+i INT,
+t TINYINT,
+s SMALLINT,
+m MEDIUMINT,
+b BIGINT,
+pk MEDIUMINT AUTO_INCREMENT PRIMARY KEY,
+UNIQUE KEY b_t (b,t)
+) ENGINE=rocksdb;
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+SELECT b+t FROM t1 WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+b+t
+9
+11
+25
+27
+29
+207
+10107
+100000000000000100
+1000000000000000100
+DROP TABLE t1;
+#
+# Fix issue #5: Transaction rollback doesn't undo all changes.
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (id int auto_increment primary key, value int) engine=rocksdb;
+set autocommit=0;
+begin;
+set @a:=0;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+rollback;
+select count(*) from t1;
+count(*)
+0
+set autocommit=1;
+drop table t0, t1;
+#
+# Check status variables
+# NOTE: We exclude rocksdb_num_get_for_update_calls because it's a debug only status var
+#
+show status where variable_name like 'rocksdb%' and variable_name not like '%num_get_for_update%';
+Variable_name Value
+Rocksdb_rows_deleted #
+Rocksdb_rows_inserted #
+Rocksdb_rows_read #
+Rocksdb_rows_updated #
+Rocksdb_rows_deleted_blind #
+Rocksdb_rows_expired #
+Rocksdb_rows_filtered #
+Rocksdb_system_rows_deleted #
+Rocksdb_system_rows_inserted #
+Rocksdb_system_rows_read #
+Rocksdb_system_rows_updated #
+Rocksdb_memtable_total #
+Rocksdb_memtable_unflushed #
+Rocksdb_queries_point #
+Rocksdb_queries_range #
+Rocksdb_covered_secondary_key_lookups #
+Rocksdb_block_cache_add #
+Rocksdb_block_cache_add_failures #
+Rocksdb_block_cache_bytes_read #
+Rocksdb_block_cache_bytes_write #
+Rocksdb_block_cache_data_add #
+Rocksdb_block_cache_data_bytes_insert #
+Rocksdb_block_cache_data_hit #
+Rocksdb_block_cache_data_miss #
+Rocksdb_block_cache_filter_add #
+Rocksdb_block_cache_filter_bytes_evict #
+Rocksdb_block_cache_filter_bytes_insert #
+Rocksdb_block_cache_filter_hit #
+Rocksdb_block_cache_filter_miss #
+Rocksdb_block_cache_hit #
+Rocksdb_block_cache_index_add #
+Rocksdb_block_cache_index_bytes_evict #
+Rocksdb_block_cache_index_bytes_insert #
+Rocksdb_block_cache_index_hit #
+Rocksdb_block_cache_index_miss #
+Rocksdb_block_cache_miss #
+Rocksdb_block_cachecompressed_hit #
+Rocksdb_block_cachecompressed_miss #
+Rocksdb_bloom_filter_full_positive #
+Rocksdb_bloom_filter_full_true_positive #
+Rocksdb_bloom_filter_prefix_checked #
+Rocksdb_bloom_filter_prefix_useful #
+Rocksdb_bloom_filter_useful #
+Rocksdb_bytes_read #
+Rocksdb_bytes_written #
+Rocksdb_compact_read_bytes #
+Rocksdb_compact_write_bytes #
+Rocksdb_compaction_key_drop_new #
+Rocksdb_compaction_key_drop_obsolete #
+Rocksdb_compaction_key_drop_user #
+Rocksdb_flush_write_bytes #
+Rocksdb_get_hit_l0 #
+Rocksdb_get_hit_l1 #
+Rocksdb_get_hit_l2_and_up #
+Rocksdb_getupdatessince_calls #
+Rocksdb_iter_bytes_read #
+Rocksdb_manual_compactions_processed #
+Rocksdb_manual_compactions_running #
+Rocksdb_memtable_hit #
+Rocksdb_memtable_miss #
+Rocksdb_no_file_closes #
+Rocksdb_no_file_errors #
+Rocksdb_no_file_opens #
+Rocksdb_num_iterators #
+Rocksdb_number_block_not_compressed #
+Rocksdb_number_db_next #
+Rocksdb_number_db_next_found #
+Rocksdb_number_db_prev #
+Rocksdb_number_db_prev_found #
+Rocksdb_number_db_seek #
+Rocksdb_number_db_seek_found #
+Rocksdb_number_deletes_filtered #
+Rocksdb_number_keys_read #
+Rocksdb_number_keys_updated #
+Rocksdb_number_keys_written #
+Rocksdb_number_merge_failures #
+Rocksdb_number_multiget_bytes_read #
+Rocksdb_number_multiget_get #
+Rocksdb_number_multiget_keys_read #
+Rocksdb_number_reseeks_iteration #
+Rocksdb_number_sst_entry_delete #
+Rocksdb_number_sst_entry_merge #
+Rocksdb_number_sst_entry_other #
+Rocksdb_number_sst_entry_put #
+Rocksdb_number_sst_entry_singledelete #
+Rocksdb_number_superversion_acquires #
+Rocksdb_number_superversion_cleanups #
+Rocksdb_number_superversion_releases #
+Rocksdb_row_lock_deadlocks #
+Rocksdb_row_lock_wait_timeouts #
+Rocksdb_snapshot_conflict_errors #
+Rocksdb_stall_l0_file_count_limit_slowdowns #
+Rocksdb_stall_locked_l0_file_count_limit_slowdowns #
+Rocksdb_stall_l0_file_count_limit_stops #
+Rocksdb_stall_locked_l0_file_count_limit_stops #
+Rocksdb_stall_pending_compaction_limit_stops #
+Rocksdb_stall_pending_compaction_limit_slowdowns #
+Rocksdb_stall_memtable_limit_stops #
+Rocksdb_stall_memtable_limit_slowdowns #
+Rocksdb_stall_total_stops #
+Rocksdb_stall_total_slowdowns #
+Rocksdb_stall_micros #
+Rocksdb_wal_bytes #
+Rocksdb_wal_group_syncs #
+Rocksdb_wal_synced #
+Rocksdb_write_other #
+Rocksdb_write_self #
+Rocksdb_write_timedout #
+Rocksdb_write_wal #
+select VARIABLE_NAME from INFORMATION_SCHEMA.global_status where VARIABLE_NAME LIKE 'rocksdb%' and VARIABLE_NAME NOT LIKE '%num_get_for_update%';
+VARIABLE_NAME
+ROCKSDB_ROWS_DELETED
+ROCKSDB_ROWS_INSERTED
+ROCKSDB_ROWS_READ
+ROCKSDB_ROWS_UPDATED
+ROCKSDB_ROWS_DELETED_BLIND
+ROCKSDB_ROWS_EXPIRED
+ROCKSDB_ROWS_FILTERED
+ROCKSDB_SYSTEM_ROWS_DELETED
+ROCKSDB_SYSTEM_ROWS_INSERTED
+ROCKSDB_SYSTEM_ROWS_READ
+ROCKSDB_SYSTEM_ROWS_UPDATED
+ROCKSDB_MEMTABLE_TOTAL
+ROCKSDB_MEMTABLE_UNFLUSHED
+ROCKSDB_QUERIES_POINT
+ROCKSDB_QUERIES_RANGE
+ROCKSDB_COVERED_SECONDARY_KEY_LOOKUPS
+ROCKSDB_BLOCK_CACHE_ADD
+ROCKSDB_BLOCK_CACHE_ADD_FAILURES
+ROCKSDB_BLOCK_CACHE_BYTES_READ
+ROCKSDB_BLOCK_CACHE_BYTES_WRITE
+ROCKSDB_BLOCK_CACHE_DATA_ADD
+ROCKSDB_BLOCK_CACHE_DATA_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_DATA_HIT
+ROCKSDB_BLOCK_CACHE_DATA_MISS
+ROCKSDB_BLOCK_CACHE_FILTER_ADD
+ROCKSDB_BLOCK_CACHE_FILTER_BYTES_EVICT
+ROCKSDB_BLOCK_CACHE_FILTER_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_FILTER_HIT
+ROCKSDB_BLOCK_CACHE_FILTER_MISS
+ROCKSDB_BLOCK_CACHE_HIT
+ROCKSDB_BLOCK_CACHE_INDEX_ADD
+ROCKSDB_BLOCK_CACHE_INDEX_BYTES_EVICT
+ROCKSDB_BLOCK_CACHE_INDEX_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_INDEX_HIT
+ROCKSDB_BLOCK_CACHE_INDEX_MISS
+ROCKSDB_BLOCK_CACHE_MISS
+ROCKSDB_BLOCK_CACHECOMPRESSED_HIT
+ROCKSDB_BLOCK_CACHECOMPRESSED_MISS
+ROCKSDB_BLOOM_FILTER_FULL_POSITIVE
+ROCKSDB_BLOOM_FILTER_FULL_TRUE_POSITIVE
+ROCKSDB_BLOOM_FILTER_PREFIX_CHECKED
+ROCKSDB_BLOOM_FILTER_PREFIX_USEFUL
+ROCKSDB_BLOOM_FILTER_USEFUL
+ROCKSDB_BYTES_READ
+ROCKSDB_BYTES_WRITTEN
+ROCKSDB_COMPACT_READ_BYTES
+ROCKSDB_COMPACT_WRITE_BYTES
+ROCKSDB_COMPACTION_KEY_DROP_NEW
+ROCKSDB_COMPACTION_KEY_DROP_OBSOLETE
+ROCKSDB_COMPACTION_KEY_DROP_USER
+ROCKSDB_FLUSH_WRITE_BYTES
+ROCKSDB_GET_HIT_L0
+ROCKSDB_GET_HIT_L1
+ROCKSDB_GET_HIT_L2_AND_UP
+ROCKSDB_GETUPDATESSINCE_CALLS
+ROCKSDB_ITER_BYTES_READ
+ROCKSDB_MANUAL_COMPACTIONS_PROCESSED
+ROCKSDB_MANUAL_COMPACTIONS_RUNNING
+ROCKSDB_MEMTABLE_HIT
+ROCKSDB_MEMTABLE_MISS
+ROCKSDB_NO_FILE_CLOSES
+ROCKSDB_NO_FILE_ERRORS
+ROCKSDB_NO_FILE_OPENS
+ROCKSDB_NUM_ITERATORS
+ROCKSDB_NUMBER_BLOCK_NOT_COMPRESSED
+ROCKSDB_NUMBER_DB_NEXT
+ROCKSDB_NUMBER_DB_NEXT_FOUND
+ROCKSDB_NUMBER_DB_PREV
+ROCKSDB_NUMBER_DB_PREV_FOUND
+ROCKSDB_NUMBER_DB_SEEK
+ROCKSDB_NUMBER_DB_SEEK_FOUND
+ROCKSDB_NUMBER_DELETES_FILTERED
+ROCKSDB_NUMBER_KEYS_READ
+ROCKSDB_NUMBER_KEYS_UPDATED
+ROCKSDB_NUMBER_KEYS_WRITTEN
+ROCKSDB_NUMBER_MERGE_FAILURES
+ROCKSDB_NUMBER_MULTIGET_BYTES_READ
+ROCKSDB_NUMBER_MULTIGET_GET
+ROCKSDB_NUMBER_MULTIGET_KEYS_READ
+ROCKSDB_NUMBER_RESEEKS_ITERATION
+ROCKSDB_NUMBER_SST_ENTRY_DELETE
+ROCKSDB_NUMBER_SST_ENTRY_MERGE
+ROCKSDB_NUMBER_SST_ENTRY_OTHER
+ROCKSDB_NUMBER_SST_ENTRY_PUT
+ROCKSDB_NUMBER_SST_ENTRY_SINGLEDELETE
+ROCKSDB_NUMBER_SUPERVERSION_ACQUIRES
+ROCKSDB_NUMBER_SUPERVERSION_CLEANUPS
+ROCKSDB_NUMBER_SUPERVERSION_RELEASES
+ROCKSDB_ROW_LOCK_DEADLOCKS
+ROCKSDB_ROW_LOCK_WAIT_TIMEOUTS
+ROCKSDB_SNAPSHOT_CONFLICT_ERRORS
+ROCKSDB_STALL_L0_FILE_COUNT_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_L0_FILE_COUNT_LIMIT_STOPS
+ROCKSDB_STALL_LOCKED_L0_FILE_COUNT_LIMIT_STOPS
+ROCKSDB_STALL_PENDING_COMPACTION_LIMIT_STOPS
+ROCKSDB_STALL_PENDING_COMPACTION_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_MEMTABLE_LIMIT_STOPS
+ROCKSDB_STALL_MEMTABLE_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_TOTAL_STOPS
+ROCKSDB_STALL_TOTAL_SLOWDOWNS
+ROCKSDB_STALL_MICROS
+ROCKSDB_WAL_BYTES
+ROCKSDB_WAL_GROUP_SYNCS
+ROCKSDB_WAL_SYNCED
+ROCKSDB_WRITE_OTHER
+ROCKSDB_WRITE_SELF
+ROCKSDB_WRITE_TIMEDOUT
+ROCKSDB_WRITE_WAL
+# RocksDB-SE's status variables are global internally
+# but they are shown as both session and global, like InnoDB's status vars.
+select VARIABLE_NAME from INFORMATION_SCHEMA.session_status where VARIABLE_NAME LIKE 'rocksdb%' and VARIABLE_NAME NOT LIKE '%num_get_for_update%';
+VARIABLE_NAME
+ROCKSDB_ROWS_DELETED
+ROCKSDB_ROWS_INSERTED
+ROCKSDB_ROWS_READ
+ROCKSDB_ROWS_UPDATED
+ROCKSDB_ROWS_DELETED_BLIND
+ROCKSDB_ROWS_EXPIRED
+ROCKSDB_ROWS_FILTERED
+ROCKSDB_SYSTEM_ROWS_DELETED
+ROCKSDB_SYSTEM_ROWS_INSERTED
+ROCKSDB_SYSTEM_ROWS_READ
+ROCKSDB_SYSTEM_ROWS_UPDATED
+ROCKSDB_MEMTABLE_TOTAL
+ROCKSDB_MEMTABLE_UNFLUSHED
+ROCKSDB_QUERIES_POINT
+ROCKSDB_QUERIES_RANGE
+ROCKSDB_COVERED_SECONDARY_KEY_LOOKUPS
+ROCKSDB_BLOCK_CACHE_ADD
+ROCKSDB_BLOCK_CACHE_ADD_FAILURES
+ROCKSDB_BLOCK_CACHE_BYTES_READ
+ROCKSDB_BLOCK_CACHE_BYTES_WRITE
+ROCKSDB_BLOCK_CACHE_DATA_ADD
+ROCKSDB_BLOCK_CACHE_DATA_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_DATA_HIT
+ROCKSDB_BLOCK_CACHE_DATA_MISS
+ROCKSDB_BLOCK_CACHE_FILTER_ADD
+ROCKSDB_BLOCK_CACHE_FILTER_BYTES_EVICT
+ROCKSDB_BLOCK_CACHE_FILTER_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_FILTER_HIT
+ROCKSDB_BLOCK_CACHE_FILTER_MISS
+ROCKSDB_BLOCK_CACHE_HIT
+ROCKSDB_BLOCK_CACHE_INDEX_ADD
+ROCKSDB_BLOCK_CACHE_INDEX_BYTES_EVICT
+ROCKSDB_BLOCK_CACHE_INDEX_BYTES_INSERT
+ROCKSDB_BLOCK_CACHE_INDEX_HIT
+ROCKSDB_BLOCK_CACHE_INDEX_MISS
+ROCKSDB_BLOCK_CACHE_MISS
+ROCKSDB_BLOCK_CACHECOMPRESSED_HIT
+ROCKSDB_BLOCK_CACHECOMPRESSED_MISS
+ROCKSDB_BLOOM_FILTER_FULL_POSITIVE
+ROCKSDB_BLOOM_FILTER_FULL_TRUE_POSITIVE
+ROCKSDB_BLOOM_FILTER_PREFIX_CHECKED
+ROCKSDB_BLOOM_FILTER_PREFIX_USEFUL
+ROCKSDB_BLOOM_FILTER_USEFUL
+ROCKSDB_BYTES_READ
+ROCKSDB_BYTES_WRITTEN
+ROCKSDB_COMPACT_READ_BYTES
+ROCKSDB_COMPACT_WRITE_BYTES
+ROCKSDB_COMPACTION_KEY_DROP_NEW
+ROCKSDB_COMPACTION_KEY_DROP_OBSOLETE
+ROCKSDB_COMPACTION_KEY_DROP_USER
+ROCKSDB_FLUSH_WRITE_BYTES
+ROCKSDB_GET_HIT_L0
+ROCKSDB_GET_HIT_L1
+ROCKSDB_GET_HIT_L2_AND_UP
+ROCKSDB_GETUPDATESSINCE_CALLS
+ROCKSDB_ITER_BYTES_READ
+ROCKSDB_MANUAL_COMPACTIONS_PROCESSED
+ROCKSDB_MANUAL_COMPACTIONS_RUNNING
+ROCKSDB_MEMTABLE_HIT
+ROCKSDB_MEMTABLE_MISS
+ROCKSDB_NO_FILE_CLOSES
+ROCKSDB_NO_FILE_ERRORS
+ROCKSDB_NO_FILE_OPENS
+ROCKSDB_NUM_ITERATORS
+ROCKSDB_NUMBER_BLOCK_NOT_COMPRESSED
+ROCKSDB_NUMBER_DB_NEXT
+ROCKSDB_NUMBER_DB_NEXT_FOUND
+ROCKSDB_NUMBER_DB_PREV
+ROCKSDB_NUMBER_DB_PREV_FOUND
+ROCKSDB_NUMBER_DB_SEEK
+ROCKSDB_NUMBER_DB_SEEK_FOUND
+ROCKSDB_NUMBER_DELETES_FILTERED
+ROCKSDB_NUMBER_KEYS_READ
+ROCKSDB_NUMBER_KEYS_UPDATED
+ROCKSDB_NUMBER_KEYS_WRITTEN
+ROCKSDB_NUMBER_MERGE_FAILURES
+ROCKSDB_NUMBER_MULTIGET_BYTES_READ
+ROCKSDB_NUMBER_MULTIGET_GET
+ROCKSDB_NUMBER_MULTIGET_KEYS_READ
+ROCKSDB_NUMBER_RESEEKS_ITERATION
+ROCKSDB_NUMBER_SST_ENTRY_DELETE
+ROCKSDB_NUMBER_SST_ENTRY_MERGE
+ROCKSDB_NUMBER_SST_ENTRY_OTHER
+ROCKSDB_NUMBER_SST_ENTRY_PUT
+ROCKSDB_NUMBER_SST_ENTRY_SINGLEDELETE
+ROCKSDB_NUMBER_SUPERVERSION_ACQUIRES
+ROCKSDB_NUMBER_SUPERVERSION_CLEANUPS
+ROCKSDB_NUMBER_SUPERVERSION_RELEASES
+ROCKSDB_ROW_LOCK_DEADLOCKS
+ROCKSDB_ROW_LOCK_WAIT_TIMEOUTS
+ROCKSDB_SNAPSHOT_CONFLICT_ERRORS
+ROCKSDB_STALL_L0_FILE_COUNT_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_L0_FILE_COUNT_LIMIT_STOPS
+ROCKSDB_STALL_LOCKED_L0_FILE_COUNT_LIMIT_STOPS
+ROCKSDB_STALL_PENDING_COMPACTION_LIMIT_STOPS
+ROCKSDB_STALL_PENDING_COMPACTION_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_MEMTABLE_LIMIT_STOPS
+ROCKSDB_STALL_MEMTABLE_LIMIT_SLOWDOWNS
+ROCKSDB_STALL_TOTAL_STOPS
+ROCKSDB_STALL_TOTAL_SLOWDOWNS
+ROCKSDB_STALL_MICROS
+ROCKSDB_WAL_BYTES
+ROCKSDB_WAL_GROUP_SYNCS
+ROCKSDB_WAL_SYNCED
+ROCKSDB_WRITE_OTHER
+ROCKSDB_WRITE_SELF
+ROCKSDB_WRITE_TIMEDOUT
+ROCKSDB_WRITE_WAL
+#
+# Fix issue #9: HA_ERR_INTERNAL_ERROR when running linkbench
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (
+pk int primary key,
+col1 varchar(255),
+key(col1)
+) engine=rocksdb;
+insert into t1 select a, repeat('123456789ABCDEF-', 15) from t0;
+select * from t1 where pk=3;
+pk col1
+3 123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-123456789ABCDEF-
+drop table t0, t1;
+#
+# Fix issue #10: Segfault in Rdb_key_def::get_primary_key_tuple
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+CREATE TABLE t1 (
+id1 bigint(20) unsigned NOT NULL DEFAULT '0',
+id2 bigint(20) unsigned NOT NULL DEFAULT '0',
+link_type bigint(20) unsigned NOT NULL DEFAULT '0',
+visibility tinyint(3) NOT NULL DEFAULT '0',
+data varchar(255) NOT NULL DEFAULT '',
+time bigint(20) unsigned NOT NULL DEFAULT '0',
+version int(11) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (link_type,id1,id2)
+) engine=rocksdb;
+insert into t1 select a,a,a,1,a,a,a from t0;
+alter table t1 add index id1_type (id1,link_type,visibility,time,version,data);
+select * from t1 where id1 = 3;
+id1 id2 link_type visibility data time version
+3 3 3 1 3 3 3
+drop table t0,t1;
+#
+# Test column families
+#
+create table t1 (
+pk int primary key,
+col1 int,
+col2 int,
+key(col1) comment 'cf3',
+key(col2) comment 'cf4'
+) engine=rocksdb;
+insert into t1 values (1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
+explain
+select * from t1 where col1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref col1 col1 5 const #
+select * from t1 where col1=2;
+pk col1 col2
+2 2 2
+explain
+select * from t1 where col2=3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref col2 col2 5 const #
+select * from t1 where col2=3;
+pk col1 col2
+3 3 3
+select * from t1 where pk=4;
+pk col1 col2
+4 4 4
+drop table t1;
+#
+# Try primary key in a non-default CF:
+#
+create table t1 (
+pk int,
+col1 int,
+col2 int,
+key(col1) comment 'cf3',
+key(col2) comment 'cf4',
+primary key (pk) comment 'cf5'
+) engine=rocksdb;
+insert into t1 values (1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
+explain
+select * from t1 where col1=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref col1 col1 5 const #
+select * from t1 where col1=2;
+pk col1 col2
+2 2 2
+select * from t1 where pk=4;
+pk col1 col2
+4 4 4
+drop table t1;
+#
+# Issue #15: SIGSEGV from reading in blob data
+#
+CREATE TABLE t1 (
+id int not null,
+blob_col text,
+PRIMARY KEY (id)
+) ENGINE=ROCKSDB CHARSET=latin1;
+INSERT INTO t1 SET id=123, blob_col=repeat('z',64000) ON DUPLICATE KEY UPDATE blob_col=VALUES(blob_col);
+INSERT INTO t1 SET id=123, blob_col='' ON DUPLICATE KEY UPDATE blob_col=VALUES(blob_col);
+DROP TABLE t1;
+#
+# Issue #17: Automatic per-index column families
+# (Now deprecated)
+#
+create table t1 (
+id int not null,
+key1 int,
+PRIMARY KEY (id),
+index (key1) comment '$per_index_cf'
+) engine=rocksdb;
+ERROR HY000: The per-index column family option has been deprecated
+#
+# Issue #22: SELECT ... FOR UPDATE takes a long time
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (
+id1 int,
+id2 int,
+value1 int,
+value2 int,
+primary key(id1, id2) COMMENT 'new_column_family',
+key(id2)
+) engine=rocksdb default charset=latin1 collate=latin1_bin;
+insert into t1 select A.a, B.a, 31, 1234 from t0 A, t0 B;
+explain
+select * from t1 where id1=30 and value1=30 for update;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref PRIMARY PRIMARY 4 const # Using where
+set @var1=(select variable_value
+from information_schema.global_status
+where variable_name='rocksdb_number_keys_read');
+select * from t1 where id1=3 and value1=3 for update;
+id1 id2 value1 value2
+set @var2=(select variable_value
+from information_schema.global_status
+where variable_name='rocksdb_number_keys_read');
+# The following must return true (before the fix, the difference was 70):
+select if((@var2 - @var1) < 30, 1, @var2-@var1);
+if((@var2 - @var1) < 30, 1, @var2-@var1)
+1
+drop table t0,t1;
+#
+# Issue #33: SELECT ... FROM rocksdb_table ORDER BY primary_key uses sorting
+#
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3);
+# The following must not use 'Using filesort':
+explain select * from t1 ORDER BY id;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 4 NULL #
+drop table t1;
+#
+# Issue #26: Index-only scans for DATETIME and TIMESTAMP
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+# Try a DATETIME column:
+create table t1 (
+pk int auto_increment primary key,
+kp1 datetime,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 12:34:56', interval a day), a from t0;
+select * from t1;
+pk kp1 kp2 col1
+1 2015-01-01 12:34:56 0 NULL
+2 2015-01-02 12:34:56 1 NULL
+3 2015-01-03 12:34:56 2 NULL
+4 2015-01-04 12:34:56 3 NULL
+5 2015-01-05 12:34:56 4 NULL
+6 2015-01-06 12:34:56 5 NULL
+7 2015-01-07 12:34:56 6 NULL
+8 2015-01-08 12:34:56 7 NULL
+9 2015-01-09 12:34:56 8 NULL
+10 2015-01-10 12:34:56 9 NULL
+# This must show 'Using index'
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range kp1 kp1 6 NULL # Using where; Using index
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+kp1 kp2
+2015-01-01 12:34:56 0
+2015-01-02 12:34:56 1
+2015-01-03 12:34:56 2
+2015-01-04 12:34:56 3
+2015-01-05 12:34:56 4
+# Now, the same with NOT NULL column
+create table t2 (
+pk int auto_increment primary key,
+kp1 datetime not null,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+# This must show 'Using index'
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where; Using index
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+kp1 kp2
+2015-01-01 12:34:56 0
+2015-01-02 12:34:56 1
+2015-01-03 12:34:56 2
+2015-01-04 12:34:56 3
+2015-01-05 12:34:56 4
+drop table t1,t2;
+# Try a DATE column:
+create table t1 (
+pk int auto_increment primary key,
+kp1 date,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01', interval a day), a from t0;
+select * from t1;
+pk kp1 kp2 col1
+1 2015-01-01 0 NULL
+2 2015-01-02 1 NULL
+3 2015-01-03 2 NULL
+4 2015-01-04 3 NULL
+5 2015-01-05 4 NULL
+6 2015-01-06 5 NULL
+7 2015-01-07 6 NULL
+8 2015-01-08 7 NULL
+9 2015-01-09 8 NULL
+10 2015-01-10 9 NULL
+# This must show 'Using index'
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01' and '2015-01-05';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range kp1 kp1 4 NULL # Using where; Using index
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01' and '2015-01-05';
+kp1 kp2
+2015-01-01 0
+2015-01-02 1
+2015-01-03 2
+2015-01-04 3
+2015-01-05 4
+# Now, the same with NOT NULL column
+create table t2 (
+pk int auto_increment primary key,
+kp1 date not null,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+# This must show 'Using index'
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 3 NULL # Using where; Using index
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+kp1 kp2
+2015-01-01 0
+2015-01-02 1
+2015-01-03 2
+2015-01-04 3
+2015-01-05 4
+drop table t1,t2;
+#
+# Try a TIMESTAMP column:
+#
+create table t1 (
+pk int auto_increment primary key,
+kp1 timestamp,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 12:34:56', interval a day), a from t0;
+select * from t1;
+pk kp1 kp2 col1
+1 2015-01-01 12:34:56 0 NULL
+2 2015-01-02 12:34:56 1 NULL
+3 2015-01-03 12:34:56 2 NULL
+4 2015-01-04 12:34:56 3 NULL
+5 2015-01-05 12:34:56 4 NULL
+6 2015-01-06 12:34:56 5 NULL
+7 2015-01-07 12:34:56 6 NULL
+8 2015-01-08 12:34:56 7 NULL
+9 2015-01-09 12:34:56 8 NULL
+10 2015-01-10 12:34:56 9 NULL
+# This must show 'Using index'
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range kp1 kp1 5 NULL # Using where; Using index
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+kp1 kp2
+2015-01-01 12:34:56 0
+2015-01-02 12:34:56 1
+2015-01-03 12:34:56 2
+2015-01-04 12:34:56 3
+2015-01-05 12:34:56 4
+# Now, the same with NOT NULL column
+create table t2 (
+pk int auto_increment primary key,
+kp1 timestamp not null,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+# This must show 'Using index'
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 4 NULL # Using where; Using index
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+kp1 kp2
+2015-01-01 12:34:56 0
+2015-01-02 12:34:56 1
+2015-01-03 12:34:56 2
+2015-01-04 12:34:56 3
+2015-01-05 12:34:56 4
+drop table t1,t2;
+#
+# Try a TIME column:
+#
+create table t1 (
+pk int auto_increment primary key,
+kp1 time,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 09:00:00', interval a minute), a from t0;
+select * from t1;
+pk kp1 kp2 col1
+1 09:00:00 0 NULL
+2 09:01:00 1 NULL
+3 09:02:00 2 NULL
+4 09:03:00 3 NULL
+5 09:04:00 4 NULL
+6 09:05:00 5 NULL
+7 09:06:00 6 NULL
+8 09:07:00 7 NULL
+9 09:08:00 8 NULL
+10 09:09:00 9 NULL
+# This must show 'Using index'
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range kp1 kp1 4 NULL # Using where; Using index
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+kp1 kp2
+09:01:00 1
+09:02:00 2
+09:03:00 3
+09:04:00 4
+09:05:00 5
+# Now, the same with NOT NULL column
+create table t2 (
+pk int auto_increment primary key,
+kp1 time not null,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+# This must show 'Using index'
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 3 NULL # Using where; Using index
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+kp1 kp2
+09:01:00 1
+09:02:00 2
+09:03:00 3
+09:04:00 4
+09:05:00 5
+drop table t1,t2;
+#
+# Try a YEAR column:
+#
+create table t1 (
+pk int auto_increment primary key,
+kp1 year,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2) select 2015+a, a from t0;
+select * from t1;
+pk kp1 kp2 col1
+1 2015 0 NULL
+2 2016 1 NULL
+3 2017 2 NULL
+4 2018 3 NULL
+5 2019 4 NULL
+6 2020 5 NULL
+7 2021 6 NULL
+8 2022 7 NULL
+9 2023 8 NULL
+10 2024 9 NULL
+# This must show 'Using index'
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2016' and '2020';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range kp1 kp1 2 NULL # Using where; Using index
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2016' and '2020';
+kp1 kp2
+2016 1
+2017 2
+2018 3
+2019 4
+2020 5
+# Now, the same with NOT NULL column
+create table t2 (
+pk int auto_increment primary key,
+kp1 year not null,
+kp2 int,
+col1 int,
+key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+# This must show 'Using index'
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2016' and '2020';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 1 NULL # Using where; Using index
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2016' and '2020';
+kp1 kp2
+2016 1
+2017 2
+2018 3
+2019 4
+2020 5
+drop table t1,t2;
+#
+# Issue #57: Release row locks on statement errors
+#
+create table t1 (id int primary key) engine=rocksdb;
+insert into t1 values (1), (2), (3);
+begin;
+insert into t1 values (4), (5), (6);
+insert into t1 values (7), (8), (2), (9);
+ERROR 23000: Duplicate entry '2' for key 'PRIMARY'
+select * from t1;
+id
+1
+2
+3
+4
+5
+6
+begin;
+select * from t1 where id=4 for update;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+select * from t1 where id=7 for update;
+id
+select * from t1 where id=9 for update;
+id
+drop table t1;
+#Index on blob column
+SET @old_mode = @@sql_mode;
+SET sql_mode = 'strict_all_tables';
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(c, b(255))) engine=rocksdb;
+drop table t1;
+set global rocksdb_large_prefix=1;
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(b(1255))) engine=rocksdb;
+set global rocksdb_large_prefix=0;
+insert into t1 values (1, '1abcde', '1abcde'), (2, '2abcde', '2abcde'), (3, '3abcde', '3abcde');
+select * from t1;
+a b c
+1 1abcde 1abcde
+2 2abcde 2abcde
+3 3abcde 3abcde
+explain select * from t1 where b like '1%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range b b 1258 NULL # Using where
+explain select b, a from t1 where b like '1%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range b b 1258 NULL # Using where
+update t1 set b= '12345' where b = '2abcde';
+select * from t1;
+a b c
+1 1abcde 1abcde
+2 12345 2abcde
+3 3abcde 3abcde
+drop table t1;
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(b(2255))) engine=rocksdb;
+Warnings:
+Note 1071 Specified key was too long; max key length is 767 bytes
+drop table t1;
+SET sql_mode = @old_mode;
+drop table t0;
+#
+# Fix assertion failure (attempt to overrun the key buffer) for prefix indexes
+#
+create table t1 (
+pk int primary key,
+col1 varchar(100),
+key (col1(10))
+) engine=rocksdb;
+insert into t1 values (1, repeat('0123456789', 9));
+drop table t1;
+#
+# Issue #76: Assertion `buf == table->record[0]' fails in virtual int ha_rocksdb::delete_row(const uchar*)
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+CREATE TABLE t2 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+CREATE TRIGGER tr AFTER DELETE ON t1 FOR EACH ROW DELETE FROM t2 WHERE pk = old.pk;
+INSERT INTO t1 VALUES (1,1);
+REPLACE INTO t1 VALUES (1,2);
+SELECT * FROM t1;
+pk f1
+1 2
+DROP TABLE t1, t2;
+#
+# Issue #99: UPDATE for table with VARCHAR pk gives "Can't find record" error
+#
+create table t1(a int primary key);
+insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t2 (
+a varchar(32) primary key,
+col1 int
+) engine=rocksdb;
+insert into t2
+select concat('v-', 100 + A.a*100 + B.a), 12345 from t1 A, t1 B;
+update t2 set a=concat('x-', a) where a between 'v-1002' and 'v-1004';
+drop table t1,t2;
+#
+# Issue #131: Assertion `v->cfd_->internal_comparator().Compare(start, end) <= 0' failed
+#
+CREATE TABLE t2(c1 INTEGER UNSIGNED NOT NULL, c2 INTEGER NULL, c3 TINYINT, c4 SMALLINT , c5 MEDIUMINT, c6 INT, c7 BIGINT, PRIMARY KEY(c1,c6)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,1,1,1,1,1,1);
+SELECT * FROM t2 WHERE c1 > 4294967295 ORDER BY c1,c6;
+c1 c2 c3 c4 c5 c6 c7
+EXPLAIN SELECT * FROM t2 WHERE c1 > 4294967295 ORDER BY c1,c6;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 50 Using where
+drop table t2;
+#
+# Issue #135: register transaction was not being called for statement
+#
+DROP DATABASE IF EXISTS test_db;
+CREATE DATABASE test_db;
+CREATE TABLE test_db.t1(c1 INT PRIMARY KEY);
+LOCK TABLES test_db.t1 READ;
+SET AUTOCOMMIT=0;
+SELECT c1 FROM test_db.t1;
+c1
+START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY;
+DROP DATABASE test_db;
+#
+# Issue #143: Split rocksdb_bulk_load option into two
+#
+CREATE TABLE t1 (id int primary key, value int) engine=RocksDB;
+SET unique_checks=0;
+INSERT INTO t1 VALUES(1, 1);
+INSERT INTO t1 VALUES(1, 2);
+INSERT INTO t1 VALUES(1, 3);
+SELECT * FROM t1;
+id value
+1 3
+REPLACE INTO t1 VALUES(4, 4);
+ERROR HY000: When unique checking is disabled in MyRocks, INSERT,UPDATE,LOAD statements with clauses that update or replace the key (i.e. INSERT ON DUPLICATE KEY UPDATE, REPLACE) are not allowed. Query: REPLACE INTO t1 VALUES(4, 4)
+INSERT INTO t1 VALUES(5, 5) ON DUPLICATE KEY UPDATE value=value+1;
+ERROR HY000: When unique checking is disabled in MyRocks, INSERT,UPDATE,LOAD statements with clauses that update or replace the key (i.e. INSERT ON DUPLICATE KEY UPDATE, REPLACE) are not allowed. Query: INSERT INTO t1 VALUES(5, 5) ON DUPLICATE KEY UPDATE value=value+1
+TRUNCATE TABLE t1;
+SET @save_rocksdb_bulk_load_size= @@rocksdb_bulk_load_size;
+SET unique_checks=1;
+SET rocksdb_commit_in_the_middle=1;
+SET rocksdb_bulk_load_size=10;
+BEGIN;
+INSERT INTO t1 (id) VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
+(11),(12),(13),(14),(15),(16),(17),(18),(19);
+ROLLBACK;
+SELECT * FROM t1;
+id value
+1 NULL
+2 NULL
+3 NULL
+4 NULL
+5 NULL
+6 NULL
+7 NULL
+8 NULL
+9 NULL
+10 NULL
+INSERT INTO t1 (id) VALUES (11),(12),(13),(14),(15);
+BEGIN;
+UPDATE t1 SET value=100;
+ROLLBACK;
+SELECT * FROM t1;
+id value
+1 100
+2 100
+3 100
+4 100
+5 100
+6 100
+7 100
+8 100
+9 100
+10 100
+11 NULL
+12 NULL
+13 NULL
+14 NULL
+15 NULL
+BEGIN;
+DELETE FROM t1;
+ROLLBACK;
+SELECT * FROM t1;
+id value
+11 NULL
+12 NULL
+13 NULL
+14 NULL
+15 NULL
+SET rocksdb_commit_in_the_middle=0;
+SET rocksdb_bulk_load_size= @save_rocksdb_bulk_load_size;
+DROP TABLE t1;
+#
+# Issue #185 Assertion `BaseValid()' failed in void rocksdb::BaseDeltaIterator::Advance()
+#
+CREATE TABLE t2(id INT NOT NULL PRIMARY KEY, data INT) Engine=MEMORY;
+INSERT INTO t2 VALUES (100,NULL),(150,"long varchar"),(200,"varchar"),(250,"long long long varchar");
+Warnings:
+Warning 1366 Incorrect integer value: 'long varchar' for column `test`.`t2`.`data` at row 2
+Warning 1366 Incorrect integer value: 'varchar' for column `test`.`t2`.`data` at row 3
+Warning 1366 Incorrect integer value: 'long long long varchar' for column `test`.`t2`.`data` at row 4
+create TABLE t1 (a int not null, b int not null, primary key(a,b));
+INSERT INTO t1 VALUES (1,1);
+SELECT a FROM t1, t2 WHERE a=b AND (b NOT IN (SELECT a FROM t1 WHERE a > 4));
+a
+1
+1
+1
+1
+DROP TABLE t1, t2;
+#
+# Issue #189 ha_rocksdb::load_auto_incr_value() creates implicit snapshot and doesn't release
+#
+create table r1 (id int auto_increment primary key, value int);
+insert into r1 (id) values (null), (null), (null), (null), (null);
+create table r2 like r1;
+show create table r2;
+Table Create Table
+r2 CREATE TABLE `r2` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `value` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+begin;
+insert into r1 values (10, 1);
+commit;
+begin;
+select * from r1;
+id value
+1 NULL
+2 NULL
+3 NULL
+4 NULL
+5 NULL
+10 1
+commit;
+drop table r1, r2;
+create table r1 (id int auto_increment, value int, index i(id));
+insert into r1 (id) values (null), (null), (null), (null), (null);
+create table r2 like r1;
+show create table r2;
+Table Create Table
+r2 CREATE TABLE `r2` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `value` int(11) DEFAULT NULL,
+ KEY `i` (`id`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+begin;
+insert into r1 values (10, 1);
+commit;
+begin;
+select * from r1;
+id value
+1 NULL
+2 NULL
+3 NULL
+4 NULL
+5 NULL
+10 1
+commit;
+drop table r1, r2;
+#
+# Issue#211 Crash on LOCK TABLES + START TRANSACTION WITH CONSISTENT SNAPSHOT
+#
+CREATE TABLE t1(c1 INT);
+lock TABLE t1 read local;
+SELECT 1 FROM t1 GROUP BY TRIM(LEADING RAND()FROM'');
+1
+set AUTOCOMMIT=0;
+start transaction with consistent snapshot;
+SELECT * FROM t1;
+c1
+COMMIT;
+UNLOCK TABLES;
+DROP TABLE t1;
+#
+# Issue#213 Crash on LOCK TABLES + partitions
+#
+CREATE TABLE t1(a INT,b INT,KEY (b)) engine=rocksdb PARTITION BY HASH(a) PARTITIONS 2;
+INSERT INTO t1(a)VALUES (20010101101010.999949);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+lock tables t1 write,t1 as t0 write,t1 as t2 write;
+SELECT a FROM t1 ORDER BY a;
+a
+2147483647
+truncate t1;
+INSERT INTO t1 VALUES(X'042000200020',X'042000200020'),(X'200400200020',X'200400200020');
+Warnings:
+Warning 1366 Incorrect integer value: '\x04 \x00 \x00 ' for column `test`.`t1`.`a` at row 1
+Warning 1366 Incorrect integer value: '\x04 \x00 \x00 ' for column `test`.`t1`.`b` at row 1
+Warning 1366 Incorrect integer value: ' \x04\x00 \x00 ' for column `test`.`t1`.`a` at row 2
+Warning 1366 Incorrect integer value: ' \x04\x00 \x00 ' for column `test`.`t1`.`b` at row 2
+UNLOCK TABLES;
+DROP TABLE t1;
+#
+# Issue#250: MyRocks/Innodb different output from query with order by on table with index and decimal type
+# (the test was changed to use VARCHAR, because DECIMAL now supports index-only, and this issue
+# needs a datype that doesn't support index-inly)
+#
+CREATE TABLE t1(
+c1 varchar(10) character set utf8 collate utf8_general_ci NOT NULL,
+c2 varchar(10) character set utf8 collate utf8_general_ci,
+c3 INT,
+INDEX idx(c1,c2)
+);
+INSERT INTO t1 VALUES ('c1-val1','c2-val1',5);
+INSERT INTO t1 VALUES ('c1-val2','c2-val3',6);
+INSERT INTO t1 VALUES ('c1-val3','c2-val3',7);
+SELECT * FROM t1 force index(idx) WHERE c1 <> 'c1-val2' ORDER BY c1 DESC;
+c1 c2 c3
+c1-val3 c2-val3 7
+c1-val1 c2-val1 5
+explain SELECT * FROM t1 force index(idx) WHERE c1 <> '1' ORDER BY c1 DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range idx idx 32 NULL # Using where
+drop table t1;
+#
+# Issue#267: MyRocks issue with no matching min/max row and count(*)
+#
+CREATE TABLE t1(c1 INT UNSIGNED, c2 INT SIGNED, INDEX idx2(c2));
+INSERT INTO t1 VALUES(1,null);
+INSERT INTO t1 VALUES(2,null);
+SELECT count(*) as total_rows, min(c2) as min_value FROM t1;
+total_rows min_value
+2 NULL
+DROP TABLE t1;
+#
+# Issue#263: MyRocks auto_increment skips values if you insert a negative value
+#
+CREATE TABLE t1(a INT AUTO_INCREMENT KEY);
+INSERT INTO t1 VALUES(0),(-1),(0);
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 1000 0 0 0 0 0 3 # # NULL latin1_swedish_ci NULL 0 N
+SELECT * FROM t1;
+a
+-1
+1
+2
+DROP TABLE t1;
+CREATE TABLE t1(a INT AUTO_INCREMENT KEY);
+INSERT INTO t1 VALUES(0),(10),(0);
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 1000 0 0 0 0 0 12 # # NULL latin1_swedish_ci NULL 0 N
+SELECT * FROM t1;
+a
+1
+10
+11
+DROP TABLE t1;
+#
+# Issue #411: Setting rocksdb_commit_in_the_middle commits transaction
+# without releasing iterator
+#
+CREATE TABLE t1 (id1 bigint(20),
+id2 bigint(20),
+id3 bigint(20),
+PRIMARY KEY (id1, id2, id3))
+DEFAULT CHARSET=latin1;
+CREATE TABLE t2 (id1 bigint(20),
+id2 bigint(20),
+PRIMARY KEY (id1, id2))
+DEFAULT CHARSET=latin1;
+set rocksdb_commit_in_the_middle=1;
+SET @save_rocksdb_bulk_load_size= @@rocksdb_bulk_load_size;
+set rocksdb_bulk_load_size = 100;
+DELETE t2, t1 FROM t2 LEFT JOIN t1 ON t2.id2 = t1.id2 AND t2.id1 = t1.id1 WHERE t2.id1 = 0;
+SET rocksdb_bulk_load_size= @save_rocksdb_bulk_load_size;
+SET rocksdb_commit_in_the_middle=0;
+DROP TABLE t1, t2;
+#
+# MDEV-21831: Assertion `length == pack_length()' failed in Field_inet6::sort_string upon
+# INSERT into RocksDB table
+#
+CREATE TABLE t1 (a INET6 NOT NULL, KEY (a)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES ('41::1'),('61::1');
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_options.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_options.result
new file mode 100644
index 00000000..80b6301a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_options.result
@@ -0,0 +1,64 @@
+create table t1 (a int,
+primary key (a) comment 'cf1') engine=rocksdb;
+create table t2 (a int,
+primary key (a) comment 'cf2') engine=rocksdb;
+create table t3 (a int,
+primary key (a) comment 'z') engine=rocksdb;
+insert into t1 values (1);
+insert into t2 values (2);
+insert into t3 values (2);
+# restart
+
+Default options for all column families:
+
+select cf_name, option_type, value
+from information_schema.rocksdb_cf_options
+where option_type in ('WRITE_BUFFER_SIZE',
+'TARGET_FILE_SIZE_BASE',
+'MAX_BYTES_FOR_LEVEL_MULTIPLIER')
+order by cf_name, option_type;
+cf_name option_type value
+cf1 MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+cf1 TARGET_FILE_SIZE_BASE 1048576
+cf1 WRITE_BUFFER_SIZE 12582912
+cf2 MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+cf2 TARGET_FILE_SIZE_BASE 1048576
+cf2 WRITE_BUFFER_SIZE 12582912
+default MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+default TARGET_FILE_SIZE_BASE 1048576
+default WRITE_BUFFER_SIZE 12582912
+z MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+z TARGET_FILE_SIZE_BASE 1048576
+z WRITE_BUFFER_SIZE 12582912
+__system__ MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+__system__ TARGET_FILE_SIZE_BASE 1048576
+__system__ WRITE_BUFFER_SIZE 12582912
+# restart: --rocksdb_override_cf_options=cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};z={target_file_size_base=4m};
+
+Individualized options for column families:
+
+select cf_name, option_type, value
+from information_schema.rocksdb_cf_options
+where option_type in ('WRITE_BUFFER_SIZE',
+'TARGET_FILE_SIZE_BASE',
+'MAX_BYTES_FOR_LEVEL_MULTIPLIER')
+order by cf_name, option_type;
+cf_name option_type value
+cf1 MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+cf1 TARGET_FILE_SIZE_BASE 2097152
+cf1 WRITE_BUFFER_SIZE 8388608
+cf2 MAX_BYTES_FOR_LEVEL_MULTIPLIER 8.000000
+cf2 TARGET_FILE_SIZE_BASE 1048576
+cf2 WRITE_BUFFER_SIZE 16777216
+default MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+default TARGET_FILE_SIZE_BASE 1048576
+default WRITE_BUFFER_SIZE 12582912
+z MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+z TARGET_FILE_SIZE_BASE 4194304
+z WRITE_BUFFER_SIZE 12582912
+__system__ MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+__system__ TARGET_FILE_SIZE_BASE 1048576
+__system__ WRITE_BUFFER_SIZE 12582912
+# restart: --rocksdb_override_cf_options=cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};z={target_file_size_base=4m};
+
+drop table t1,t2,t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_per_partition.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_per_partition.result
new file mode 100644
index 00000000..32bb70a7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_per_partition.result
@@ -0,0 +1,425 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'testcomment'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='testcomment';
+cf_name
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'rev:testrevcomment'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='rev:testrevcomment';
+cf_name
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'baz';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='foo';
+cf_name
+foo
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_custom_cf';
+cf_name
+my_custom_cf
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='baz';
+cf_name
+baz
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=t1-p0;custom_p1_cfname=rev:bar;custom_p2_cfname=t1-p2'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set @@global.rocksdb_compact_cf = 't1-p0';
+set @@global.rocksdb_compact_cf = 'rev:bar';
+set @@global.rocksdb_compact_cf = 't1-p2';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='t1-p0';
+cf_name
+t1-p0
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='rev:bar';
+cf_name
+rev:bar
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='t1-p2';
+cf_name
+t1-p2
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=cf-zero;custom_p1_cfname=cf-one;custom_p2_cfname=cf-zero'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9),
+PARTITION custom_p3 VALUES IN (10, 20, 30)
+);
+set @@global.rocksdb_compact_cf = 'cf-zero';
+set @@global.rocksdb_compact_cf = 'cf-one';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='cf-zero';
+cf_name
+cf-zero
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='cf-one';
+cf_name
+cf-one
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+SELECT * FROM t1;
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+5 5 five NULL
+3 3 three NULL
+9 9 nine NULL
+ALTER TABLE t1 DROP PRIMARY KEY;
+SELECT * FROM t1;
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+5 5 five NULL
+3 3 three NULL
+9 9 nine NULL
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'bar';
+set @@global.rocksdb_compact_cf = 'baz';
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+ALTER TABLE t1 DROP PRIMARY KEY;
+ALTER TABLE t1 ADD PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=p0_cf;custom_p1_cfname=p1_cf';
+set @@global.rocksdb_compact_cf = 'p0_cf';
+set @@global.rocksdb_compact_cf = 'p1_cf';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p0_cf';
+cf_name
+p0_cf
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p1_cf';
+cf_name
+p1_cf
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+ALTER TABLE t1 PARTITION BY LIST(c1) (
+PARTITION custom_p3 VALUES IN (1, 4, 7),
+PARTITION custom_p4 VALUES IN (2, 5, 8, 3, 6, 9)
+);
+ALTER TABLE t1 DROP PRIMARY KEY;
+ALTER TABLE t1 ADD PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p3_cfname=p3_cf;custom_p4_cfname=p4_cf';
+set @@global.rocksdb_compact_cf = 'p3_cf';
+set @@global.rocksdb_compact_cf = 'p4_cf';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p3_cf';
+cf_name
+p3_cf
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p4_cf';
+cf_name
+p4_cf
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=;'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+DROP TABLE t1;
+CREATE TABLE `t2` (
+`col1` bigint(20) NOT NULL,
+`col2` varbinary(64) NOT NULL,
+`col3` varbinary(256) NOT NULL,
+`col4` bigint(20) NOT NULL,
+`col5` mediumblob NOT NULL,
+PRIMARY KEY (`col1`,`col2`,`col3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+PARTITION BY LIST COLUMNS (`col2`) (
+PARTITION custom_p0 VALUES IN (0x12345),
+PARTITION custom_p1 VALUES IN (0x23456),
+PARTITION custom_p2 VALUES IN (0x34567),
+PARTITION custom_p3 VALUES IN (0x45678),
+PARTITION custom_p4 VALUES IN (0x56789),
+PARTITION custom_p5 VALUES IN (0x6789A),
+PARTITION custom_p6 VALUES IN (0x789AB),
+PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+DROP TABLE t2;
+CREATE TABLE `t2` (
+`col1` bigint(20) NOT NULL,
+`col2` varbinary(64) NOT NULL,
+`col3` varbinary(256) NOT NULL,
+`col4` bigint(20) NOT NULL,
+`col5` mediumblob NOT NULL,
+PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=my_cf0;custom_p1_cfname=my_cf1'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+PARTITION BY LIST COLUMNS (`col2`) (
+PARTITION custom_p0 VALUES IN (0x12345),
+PARTITION custom_p1 VALUES IN (0x23456),
+PARTITION custom_p2 VALUES IN (0x34567),
+PARTITION custom_p3 VALUES IN (0x45678),
+PARTITION custom_p4 VALUES IN (0x56789),
+PARTITION custom_p5 VALUES IN (0x6789A),
+PARTITION custom_p6 VALUES IN (0x789AB),
+PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+set @@global.rocksdb_compact_cf = 'my_cf0';
+set @@global.rocksdb_compact_cf = 'my_cf1';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_cf0';
+cf_name
+my_cf0
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_cf1';
+cf_name
+my_cf1
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+SELECT col1, HEX(col2), HEX(col3), col4, HEX(col5) FROM t2;
+col1 HEX(col2) HEX(col3) col4 HEX(col5)
+100 012345 01 1 02
+200 012345 01 1 02
+300 012345 01 1 02
+100 023456 02 1 03
+100 034567 04 1 05
+400 089ABC 04 1 05
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x12345;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p0 index NULL PRIMARY 332 NULL 3 Using where; Using index
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x23456;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p1 index NULL PRIMARY 332 NULL 2 Using where; Using index
+ALTER TABLE t2 DROP PRIMARY KEY;
+ALTER TABLE t2 ADD PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=new_cf0;custom_p1_cfname=new_cf1';
+set @@global.rocksdb_compact_cf = 'new_cf0';
+set @@global.rocksdb_compact_cf = 'new_cf1';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='new_cf0';
+cf_name
+new_cf0
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='new_cf1';
+cf_name
+new_cf1
+INSERT INTO t2 VALUES (500, 0x12345, 0x5, 1, 0x2);
+INSERT INTO t2 VALUES (700, 0x23456, 0x7, 1, 0x3);
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x12345;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p0 index NULL PRIMARY 332 NULL 4 Using where; Using index
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x23456;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p1 index NULL PRIMARY 332 NULL 2 Using where; Using index
+SELECT col1, HEX(col2), HEX(col3), col4, HEX(col5) FROM t2;
+col1 HEX(col2) HEX(col3) col4 HEX(col5)
+100 012345 01 1 02
+200 012345 01 1 02
+300 012345 01 1 02
+500 012345 05 1 02
+100 023456 02 1 03
+700 023456 07 1 03
+100 034567 04 1 05
+400 089ABC 04 1 05
+DROP TABLE t2;
+CREATE TABLE `t2` (
+`col1` bigint(20) NOT NULL,
+`col2` varbinary(64) NOT NULL,
+`col3` varbinary(256) NOT NULL,
+`col4` bigint(20) NOT NULL,
+`col5` mediumblob NOT NULL,
+PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=test_cf0;custom_p1_cfname=test_cf1',
+KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=test_cf5'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+PARTITION BY LIST COLUMNS (`col2`) (
+PARTITION custom_p0 VALUES IN (0x12345),
+PARTITION custom_p1 VALUES IN (0x23456),
+PARTITION custom_p2 VALUES IN (0x34567),
+PARTITION custom_p3 VALUES IN (0x45678),
+PARTITION custom_p4 VALUES IN (0x56789),
+PARTITION custom_p5 VALUES IN (0x6789A),
+PARTITION custom_p6 VALUES IN (0x789AB),
+PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf0';
+cf_name
+test_cf0
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf1';
+cf_name
+test_cf1
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf5';
+cf_name
+test_cf5
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (500, 0x6789A, 0x5, 1, 0x7);
+EXPLAIN PARTITIONS SELECT * FROM t2 WHERE col2 = 0x6789A AND col4 = 1;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p5 ref col2 col2 74 const,const 1 Using where
+ALTER TABLE t2 DROP KEY `col2`;
+ALTER TABLE t2 ADD KEY (`col3`, `col4`) COMMENT 'custom_p5_cfname=another_cf_for_p5';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='another_cf_for_p5';
+cf_name
+another_cf_for_p5
+ANALYZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze Warning Engine-independent statistics are not collected for column 'col5'
+test.t2 analyze status OK
+EXPLAIN PARTITIONS SELECT * FROM t2 WHERE col3 = 0x4 AND col2 = 0x34567;
+id select_type table partitions type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 custom_p2 ref col3 col3 258 const # Using where
+DROP TABLE t2;
+CREATE TABLE `t2` (
+`col1` bigint(20) NOT NULL,
+`col2` varbinary(64) NOT NULL,
+`col3` varbinary(256) NOT NULL,
+`col4` bigint(20) NOT NULL,
+`col5` mediumblob NOT NULL,
+PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=test_cf0;custom_p1_cfname=test_cf1',
+UNIQUE KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=unique_test_cf5'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+PARTITION BY LIST COLUMNS (`col2`) (
+PARTITION custom_p0 VALUES IN (0x12345),
+PARTITION custom_p1 VALUES IN (0x23456),
+PARTITION custom_p2 VALUES IN (0x34567),
+PARTITION custom_p3 VALUES IN (0x45678),
+PARTITION custom_p4 VALUES IN (0x56789),
+PARTITION custom_p5 VALUES IN (0x6789A),
+PARTITION custom_p6 VALUES IN (0x789AB),
+PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='unique_test_cf5';
+cf_name
+unique_test_cf5
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+ERROR 23000: Duplicate entry '\x01#E-1' for key 'col2'
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+ERROR 23000: Duplicate entry '\x01#E-1' for key 'col2'
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (500, 0x6789A, 0x5, 1, 0x7);
+DROP TABLE t2;
+CREATE TABLE t1 (
+`a` int,
+PRIMARY KEY (a) COMMENT "sharedcf"
+) ENGINE=ROCKSDB;
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='sharedcf';
+cf_name
+sharedcf
+CREATE TABLE t2 (
+`a` INT,
+`b` DATE,
+`c` VARCHAR(42),
+PRIMARY KEY (`a`) COMMENT "custom_p0_cfname=sharedcf;custom_p2_cfname=notsharedcf"
+) ENGINE=ROCKSDB
+PARTITION BY LIST(`a`) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='notsharedcf';
+cf_name
+notsharedcf
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+CREATE TABLE t1 (
+a INT NOT NULL,
+PRIMARY KEY (a) COMMENT 'p1_cfname=foo;'
+) ENGINE=ROCKSDB
+PARTITION BY LIST COLUMNS(a)
+(PARTITION p1 VALUES IN (1) ENGINE = ROCKSDB);
+INSERT INTO t1 values (1);
+TRUNCATE TABLE t1;
+SELECT * FROM t1;
+a
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_reverse.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_reverse.result
new file mode 100644
index 00000000..1c85343c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_cf_reverse.result
@@ -0,0 +1,120 @@
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (
+pk int primary key,
+a int not null,
+b int not null,
+key(a) comment 'rev:foo',
+key(b) comment 'bar'
+) engine=rocksdb;
+insert into t1 select a,a,a from t0;
+insert into t1 select a+10,a+10,a+10 from t0;
+# Primary key is not in a reverse-ordered CF, so full table scan
+# returns rows in ascending order:
+select * from t1;
+pk a b
+0 0 0
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+11 11 11
+12 12 12
+13 13 13
+14 14 14
+15 15 15
+16 16 16
+17 17 17
+18 18 18
+19 19 19
+explain
+select a from t1 order by a limit 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 4 NULL # Using index
+select a from t1 order by a limit 5;
+a
+0
+1
+2
+3
+4
+explain
+select b from t1 order by b limit 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL b 4 NULL # Using index
+select a from t1 order by a limit 5;
+a
+0
+1
+2
+3
+4
+explain
+select a from t1 order by a desc limit 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL a 4 NULL # Using index
+select a from t1 order by a desc limit 5;
+a
+19
+18
+17
+16
+15
+explain
+select b from t1 order by b desc limit 5;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL b 4 NULL # Using index
+select b from t1 order by b desc limit 5;
+b
+19
+18
+17
+16
+15
+drop table t1;
+#
+# Try a primary key in a reverse-ordered CF.
+#
+create table t2 (
+pk int,
+a int not null,
+primary key(pk) comment 'rev:cf1'
+) engine=rocksdb;
+insert into t2 select a,a from t0;
+# Primary key is in a reverse-ordered CF, so full table scan
+# returns rows in descending order:
+select * from t2;
+pk a
+9 9
+8 8
+7 7
+6 6
+5 5
+4 4
+3 3
+2 2
+1 1
+0 0
+set autocommit=0;
+begin;
+delete from t2 where a=3 or a=7;
+select * from t2;
+pk a
+9 9
+8 8
+6 6
+5 5
+4 4
+2 2
+1 1
+0 0
+rollback;
+set autocommit=1;
+drop table t2;
+drop table t0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_checksums.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_checksums.result
new file mode 100644
index 00000000..e4b757ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_checksums.result
@@ -0,0 +1,129 @@
+set @save_rocksdb_store_row_debug_checksums=@@global.rocksdb_store_row_debug_checksums;
+set @save_rocksdb_verify_row_debug_checksums=@@global.rocksdb_verify_row_debug_checksums;
+set @save_rocksdb_checksums_pct=@@global.rocksdb_checksums_pct;
+show variables like 'rocksdb_%checksum%';
+Variable_name Value
+rocksdb_checksums_pct 100
+rocksdb_store_row_debug_checksums OFF
+rocksdb_verify_row_debug_checksums OFF
+create table t1 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3);
+check table t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+CHECKTABLE t1: Checking table t1
+CHECKTABLE t1: Checking index a
+CHECKTABLE t1: ... 3 index entries checked (0 had checksums)
+CHECKTABLE t1: Checking index b
+CHECKTABLE t1: ... 3 index entries checked (0 had checksums)
+CHECKTABLE t1: 0 table records had checksums
+drop table t1;
+set session rocksdb_store_row_debug_checksums=on;
+create table t2 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t2 values (1,1,1),(2,2,2),(3,3,3);
+check table t2;
+Table Op Msg_type Msg_text
+test.t2 check status OK
+CHECKTABLE t2: Checking table t2
+CHECKTABLE t2: Checking index a
+CHECKTABLE t2: ... 3 index entries checked (3 had checksums)
+CHECKTABLE t2: Checking index b
+CHECKTABLE t2: ... 3 index entries checked (3 had checksums)
+CHECKTABLE t2: 3 table records had checksums
+# Now, make a table that has both rows with checksums and without
+create table t3 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t3 values (1,1,1),(2,2,2),(3,3,3);
+set session rocksdb_store_row_debug_checksums=off;
+update t3 set b=3 where a=2;
+set session rocksdb_store_row_debug_checksums=on;
+check table t3;
+Table Op Msg_type Msg_text
+test.t3 check status OK
+CHECKTABLE t3: Checking table t3
+CHECKTABLE t3: Checking index a
+CHECKTABLE t3: ... 3 index entries checked (3 had checksums)
+CHECKTABLE t3: Checking index b
+CHECKTABLE t3: ... 3 index entries checked (2 had checksums)
+CHECKTABLE t3: 2 table records had checksums
+set session rocksdb_store_row_debug_checksums=on;
+set session rocksdb_checksums_pct=5;
+create table t4 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+check table t4;
+Table Op Msg_type Msg_text
+test.t4 check status OK
+4000 index entries had around 200 checksums
+4000 index entries had around 200 checksums
+Around 200 table records had checksums
+set session rocksdb_checksums_pct=100;
+#
+# Ok, table t2 has all rows with checksums. Simulate a few checksum mismatches.
+#
+insert into mtr.test_suppressions values
+('Checksum mismatch in key of key-value pair for index'),
+('Checksum mismatch in value of key-value pair for index'),
+('Data with incorrect checksum');
+# 1. Start with mismatch in key checksum of the PK.
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_checksum1";
+set session rocksdb_verify_row_debug_checksums=off;
+select * from t3;
+pk a b
+1 1 1
+2 2 3
+3 3 3
+set session rocksdb_verify_row_debug_checksums=on;
+select * from t3;
+ERROR HY000: Internal error: Record checksum mismatch
+select * from t4;
+ERROR HY000: Internal error: Record checksum mismatch
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_checksum1";
+# 2. Continue with mismatch in pk value checksum.
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_checksum2";
+set session rocksdb_verify_row_debug_checksums=off;
+select * from t3;
+pk a b
+1 1 1
+2 2 3
+3 3 3
+set session rocksdb_verify_row_debug_checksums=on;
+select * from t3;
+ERROR HY000: Internal error: Record checksum mismatch
+select * from t4;
+ERROR HY000: Internal error: Record checksum mismatch
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_checksum2";
+# 3. Check if we catch checksum mismatches for secondary indexes
+explain
+select * from t3 force index(a) where a<4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 range a a 5 NULL # Using index condition
+select * from t3 force index(a) where a<4;
+pk a b
+1 1 1
+2 2 3
+3 3 3
+set session debug_dbug= "+d,myrocks_simulate_bad_key_checksum1";
+select * from t3 force index(a) where a<4;
+ERROR HY000: Internal error: Record checksum mismatch
+select * from t4 force index(a) where a<1000000;
+ERROR HY000: Internal error: Record checksum mismatch
+set session debug_dbug= "-d,myrocks_simulate_bad_key_checksum1";
+# 4. The same for index-only reads?
+explain
+select a from t3 force index(a) where a<4;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 range a a 5 NULL # Using where; Using index
+select a from t3 force index(a) where a<4;
+a
+1
+2
+3
+set session debug_dbug= "+d,myrocks_simulate_bad_key_checksum1";
+select a from t3 force index(a) where a<4;
+ERROR HY000: Internal error: Record checksum mismatch
+select a from t4 force index(a) where a<1000000;
+ERROR HY000: Internal error: Record checksum mismatch
+set session debug_dbug= "-d,myrocks_simulate_bad_key_checksum1";
+set @@global.rocksdb_store_row_debug_checksums=@save_rocksdb_store_row_debug_checksums;
+set @@global.rocksdb_verify_row_debug_checksums=@save_rocksdb_verify_row_debug_checksums;
+set @@global.rocksdb_checksums_pct=@save_rocksdb_checksums_pct;
+# restart
+drop table t2,t3,t4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_concurrent_delete.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_concurrent_delete.result
new file mode 100644
index 00000000..9106e79f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_concurrent_delete.result
@@ -0,0 +1,671 @@
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+3 3
+4 4
+5 5
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, index a(a));
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--SK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+a
+2
+3
+4
+5
+--SK middle row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+--SK end row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+3 3
+2 2
+1 1
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "rev:cf2", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+3 3
+4 4
+5 5
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "rev:cf2", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+3 3
+2 2
+1 1
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+set debug_sync='RESET';
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+create table t1 (id1 int, id2 int, value int, primary key (id1, id2), index sk (id1, value)) engine=rocksdb;
+insert into t1 values (1, 1, 1),(1, 2, 1),(1, 3, 1),(1, 4, 1),(1, 5, 1),(2, 2, 2);
+--First row delete with PRIMARY
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=100 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=1;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 3 100
+1 4 100
+1 5 100
+--Middle row delete with PRIMARY
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=200 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=3;
+set debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 4 100
+1 5 100
+--End row delete with PRIMARY
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=300 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=5;
+set debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 4 100
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+set debug_sync='RESET';
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+create table t1 (id1 int, id2 int, value int, primary key (id1, id2), index sk (id1, value)) engine=rocksdb;
+insert into t1 values (1, 1, 1),(1, 2, 1),(1, 3, 1),(1, 4, 1),(1, 5, 1),(2, 2, 2);
+--First row delete with sk
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=100 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=1;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 3 100
+1 4 100
+1 5 100
+--Middle row delete with sk
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=200 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=3;
+set debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 4 100
+1 5 100
+--End row delete with sk
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=300 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=5;
+set debug_sync='now SIGNAL go';
+connection con;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 4 100
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+3 3
+4 4
+5 5
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+4 4
+5 5
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+4 4
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, index a(a));
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--SK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+a
+2
+3
+4
+5
+--SK middle row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+a
+2
+4
+5
+--SK end row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+a
+2
+4
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+3 3
+2 2
+1 1
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+2 2
+1 1
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+2 2
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "rev:cf2", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+3 3
+4 4
+5 5
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+4 4
+5 5
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk ASC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+2 2
+4 4
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='RESET';
+CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT "rev:cf2", a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+--PK first row delete
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+3 3
+2 2
+1 1
+--PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+2 2
+1 1
+--PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+SELECT * FROM t1 order by t1.pk DESC FOR UPDATE;
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+DELETE FROM t1 WHERE pk = 1;
+SET debug_sync='now SIGNAL go';
+connection con;
+pk a
+4 4
+2 2
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+set debug_sync='RESET';
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+create table t1 (id1 int, id2 int, value int, primary key (id1, id2), index sk (id1, value)) engine=rocksdb;
+insert into t1 values (1, 1, 1),(1, 2, 1),(1, 3, 1),(1, 4, 1),(1, 5, 1),(2, 2, 2);
+--First row delete with PRIMARY
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=100 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=1;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 3 100
+1 4 100
+1 5 100
+--Middle row delete with PRIMARY
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=200 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=3;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 200
+1 4 200
+1 5 200
+--End row delete with PRIMARY
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (PRIMARY) set value=300 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=5;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 300
+1 4 300
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+connect con, localhost, root,,;
+connection default;
+set debug_sync='RESET';
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+create table t1 (id1 int, id2 int, value int, primary key (id1, id2), index sk (id1, value)) engine=rocksdb;
+insert into t1 values (1, 1, 1),(1, 2, 1),(1, 3, 1),(1, 4, 1),(1, 5, 1),(2, 2, 2);
+--First row delete with sk
+connection con;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=100 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=1;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 100
+1 3 100
+1 4 100
+1 5 100
+--Middle row delete with sk
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=200 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=3;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 200
+1 4 200
+1 5 200
+--End row delete with sk
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+update t1 force index (sk) set value=300 where id1=1;
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=5;
+set debug_sync='now SIGNAL go';
+connection con;
+select * from t1 where id1=1;
+id1 id2 value
+1 2 300
+1 4 300
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_datadir.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_datadir.result
new file mode 100644
index 00000000..40c53f6f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_datadir.result
@@ -0,0 +1,2 @@
+Check for MANIFEST files
+MANIFEST-000006
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rc.result
new file mode 100644
index 00000000..ea250694
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rc.result
@@ -0,0 +1,89 @@
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+create table t (i int primary key);
+create table r1 (id int primary key, value int);
+insert into r1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+create table r2 like r1;
+insert into r2 select * from r1;
+connect con1,localhost,root,,;
+begin;
+update r2 set value=100 where id=9;
+connect con2,localhost,root,,;
+begin;
+update r1 set value=100 where id=8;
+select * from r2 for update;;
+connection con1;
+select * from r1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con2;
+id value
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+10 10
+rollback;
+connection con1;
+begin;
+insert into t values (1);
+connection con2;
+begin;
+insert into t values (2);
+connect con3,localhost,root,,;
+begin;
+insert into t values (3);
+connection con1;
+select * from t where i = 2 for update;
+connection con2;
+select * from t where i = 3 for update;
+connection con3;
+select * from t;
+i
+3
+insert into t values (4), (1);
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Transaction should be rolled back
+select * from t;
+i
+rollback;
+connection con2;
+i
+rollback;
+connection con1;
+i
+rollback;
+connection default;
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5),(6,6,6),(7,7,7),(8,8,8),(9,9,9),(10,10,10);
+connection con1;
+begin;
+update t1 force index (value) set value2=value2+1 where value=3;
+connection con2;
+begin;
+update t1 force index (value) set value2=value2+1 where value=2;
+update t1 force index (value) set value2=value2+1 where value=4;
+connection con1;
+update t1 force index (value) set value2=value2+1 where value=4;
+connection con2;
+update t1 force index (value) set value2=value2+1 where value=3;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+connection con1;
+rollback;
+connection con2;
+rollback;
+drop table t1;
+connection default;
+disconnect con1;
+disconnect con2;
+disconnect con3;
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t,r1,r2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rr.result
new file mode 100644
index 00000000..ea250694
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_detect_rr.result
@@ -0,0 +1,89 @@
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+create table t (i int primary key);
+create table r1 (id int primary key, value int);
+insert into r1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+create table r2 like r1;
+insert into r2 select * from r1;
+connect con1,localhost,root,,;
+begin;
+update r2 set value=100 where id=9;
+connect con2,localhost,root,,;
+begin;
+update r1 set value=100 where id=8;
+select * from r2 for update;;
+connection con1;
+select * from r1 for update;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+rollback;
+connection con2;
+id value
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+10 10
+rollback;
+connection con1;
+begin;
+insert into t values (1);
+connection con2;
+begin;
+insert into t values (2);
+connect con3,localhost,root,,;
+begin;
+insert into t values (3);
+connection con1;
+select * from t where i = 2 for update;
+connection con2;
+select * from t where i = 3 for update;
+connection con3;
+select * from t;
+i
+3
+insert into t values (4), (1);
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Transaction should be rolled back
+select * from t;
+i
+rollback;
+connection con2;
+i
+rollback;
+connection con1;
+i
+rollback;
+connection default;
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5),(6,6,6),(7,7,7),(8,8,8),(9,9,9),(10,10,10);
+connection con1;
+begin;
+update t1 force index (value) set value2=value2+1 where value=3;
+connection con2;
+begin;
+update t1 force index (value) set value2=value2+1 where value=2;
+update t1 force index (value) set value2=value2+1 where value=4;
+connection con1;
+update t1 force index (value) set value2=value2+1 where value=4;
+connection con2;
+update t1 force index (value) set value2=value2+1 where value=3;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+connection con1;
+rollback;
+connection con2;
+rollback;
+drop table t1;
+connection default;
+disconnect con1;
+disconnect con2;
+disconnect con3;
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t,r1,r2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rc.result
new file mode 100644
index 00000000..f97da009
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rc.result
@@ -0,0 +1,8 @@
+create table t1 (a int primary key, b int) engine=rocksdb;
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rr.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rr.result
new file mode 100644
index 00000000..f97da009
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_deadlock_stress_rr.result
@@ -0,0 +1,8 @@
+create table t1 (a int primary key, b int) engine=rocksdb;
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_debug.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_debug.result
new file mode 100644
index 00000000..a245fa85
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_debug.result
@@ -0,0 +1,11 @@
+#
+# Issue #728: Assertion `covers_key(b)' failed in int
+# myrocks::Rdb_key_def::cmp_full_keys(const rocks db::Slice&,
+# const rocksdb::Slice&)
+#
+CREATE TABLE t2(c1 TINYINT SIGNED KEY,c2 TINYINT UNSIGNED,c3 INT);
+INSERT INTO t2(c1)VALUES(0);
+SELECT * FROM t2 WHERE c1<=127 ORDER BY c1 DESC;
+c1 c2 c3
+0 NULL NULL
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp.result
new file mode 100644
index 00000000..101e159e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp.result
@@ -0,0 +1,257 @@
+select * from information_schema.engines where engine = 'rocksdb';
+ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS
+ROCKSDB DEFAULT RocksDB storage engine YES YES YES
+drop table if exists t0,t1,t2,t3;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int primary key,
+kp1 int,
+kp2 int,
+col1 int,
+key (kp1,kp2) comment 'cf1'
+) engine=rocksdb;
+insert into t2 select a,a,a,a from t1;
+# Try a basic case:
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+pk kp1 kp2 col1
+2 2 2 2
+4 4 4 4
+6 6 6 6
+8 8 8 8
+10 10 10 10
+# Check that ICP doesnt work for columns where column value
+# cant be restored from mem-comparable form:
+create table t3 (
+pk int primary key,
+kp1 int,
+kp2 varchar(10) collate utf8_general_ci,
+col1 int,
+key (kp1,kp2) comment 'cf1'
+) engine=rocksdb;
+insert into t3 select a,a/10,a,a from t1;
+# This must not use ICP:
+explain
+select * from t3 where kp1=3 and kp2 like '%foo%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 ref kp1 kp1 5 const # Using where
+explain format=json
+select * from t3 where kp1 between 2 and 4 and mod(kp1,3)=0 and kp2 like '%foo%';
+EXPLAIN
+{
+ "query_block": {
+ "select_id": 1,
+ "table": {
+ "table_name": "t3",
+ "access_type": "range",
+ "possible_keys": ["kp1"],
+ "key": "kp1",
+ "key_length": "5",
+ "used_key_parts": ["kp1"],
+ "rows": 1000,
+ "filtered": 100,
+ "index_condition": "t3.kp1 between 2 and 4 and t3.kp1 MOD 3 = 0",
+ "attached_condition": "t3.kp2 like '%foo%'"
+ }
+ }
+}
+# Check that we handle the case where out-of-range is encountered sooner
+# than matched index condition
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1< 3 and kp2+1>50000;
+pk kp1 kp2 col1
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1< 3 and kp2+1>50000;
+pk kp1 kp2 col1
+# Try doing backwards scans
+# MariaDB: ICP is not supported for reverse scans.
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+pk kp1 kp2 col1
+10 10 10 10
+8 8 8 8
+6 6 6 6
+4 4 4 4
+2 2 2 2
+explain
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+pk kp1 kp2 col1
+998 998 998 998
+996 996 996 996
+994 994 994 994
+992 992 992 992
+explain
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+pk kp1 kp2 col1
+drop table t0,t1,t2,t3;
+#
+# Check how ICP affects counters
+#
+# First, some preparations
+#
+# in facebook/mysql-5.6, it was:
+# select ROWS_READ, ROWS_REQUESTED, ROWS_INDEX_FIRST, ROWS_INDEX_NEXT
+#
+# In MariaDB, we do:
+create procedure save_read_stats()
+begin
+set @rr=(select ROWS_READ
+from information_schema.table_statistics
+where table_name='t4' and table_schema=database());
+set @rif= (select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_first');
+set @rin=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_next');
+set @icp_attempts=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_attempts');
+set @icp_matches=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_match');
+end|
+create procedure get_read_stats()
+begin
+select
+(select ROWS_READ
+from information_schema.table_statistics
+where table_name='t4' and table_schema=database()
+) - @rr as ROWS_READ_DIFF,
+(select VARIABLE_VALUE - @rif
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_first') as ROWS_INDEX_FIRST,
+(select VARIABLE_VALUE - @rin
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_next') as ROWS_INDEX_NEXT,
+(select VARIABLE_VALUE - @icp_attempts
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_attempts') as ICP_ATTEMPTS,
+(select VARIABLE_VALUE - @icp_matches
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_match') as ICP_MATCHES;
+end|
+create table t4 (
+id int,
+id1 int,
+id2 int,
+value int,
+value2 varchar(100),
+primary key (id),
+key id1_id2 (id1, id2) comment 'cf1'
+) engine=rocksdb charset=latin1 collate latin1_bin;
+insert into t4 values
+(1,1,1,1,1), (2,1,2,2,2), (3,1,3,3,3),(4,1,4,4,4),(5,1,5,5,5),
+(6,1,6,6,6), (7,1,7,7,7), (8,1,8,8,8),(9,1,9,9,9),(10,1,10,10,10);
+#
+# Now, the test itself
+#
+call save_read_stats();
+call get_read_stats();
+ROWS_READ_DIFF ROWS_INDEX_FIRST ROWS_INDEX_NEXT ICP_ATTEMPTS ICP_MATCHES
+0 0 0 0 0
+# ============== index-only query ==============
+explain
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using where; Using index
+call save_read_stats();
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id1 id2
+1 1
+call get_read_stats();
+ROWS_READ_DIFF 10
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 10
+ICP_ATTEMPTS 0
+ICP_MATCHES 0
+# ============== Query without ICP ==============
+set optimizer_switch='index_condition_pushdown=off';
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using where
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id id1 id2 value value2
+1 1 1 1 1
+call get_read_stats();
+ROWS_READ_DIFF 10
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 10
+ICP_ATTEMPTS 0
+ICP_MATCHES 0
+# ============== Query with ICP ==============
+set optimizer_switch='index_condition_pushdown=on';
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using index condition
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id id1 id2 value value2
+1 1 1 1 1
+call get_read_stats();
+ROWS_READ_DIFF 1
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 1
+ICP_ATTEMPTS 10
+ICP_MATCHES 1
+drop table t4;
+drop procedure save_read_stats;
+drop procedure get_read_stats;
+#
+# Issue #67: Inefficient index condition pushdown
+#
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (
+pk int not null primary key,
+key1 bigint(20) unsigned,
+col1 int,
+key (key1)
+) engine=rocksdb;
+insert into t1
+select
+A.a+10*B.a+100*C.a,
+A.a+10*B.a+100*C.a,
+1234
+from t0 A, t0 B, t0 C;
+set @count=0;
+explain
+select * from t1 force index(key1) where key1=1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref key1 key1 9 const #
+set @count_diff =(select (value - @count) from information_schema.rocksdb_perf_context
+where table_schema=database() and table_name='t1' and stat_type='INTERNAL_KEY_SKIPPED_COUNT');
+select * from t1 force index(key1) where key1=1;
+pk key1 col1
+1 1 1234
+set @count_diff =(select (value - @count) from information_schema.rocksdb_perf_context
+where table_schema=database() and table_name='t1' and stat_type='INTERNAL_KEY_SKIPPED_COUNT');
+# The following must be =1, or in any case not 999:
+select @count_diff as "INTERNAL_KEY_SKIPPED_COUNT increment";
+INTERNAL_KEY_SKIPPED_COUNT increment
+1
+drop table t0,t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp_rev.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp_rev.result
new file mode 100644
index 00000000..b00e0e14
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_icp_rev.result
@@ -0,0 +1,223 @@
+select * from information_schema.engines where engine = 'rocksdb';
+ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS
+ROCKSDB DEFAULT RocksDB storage engine YES YES YES
+drop table if exists t0,t1,t2,t3;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int primary key,
+kp1 int,
+kp2 int,
+col1 int,
+key (kp1,kp2) comment 'rev:cf1'
+) engine=rocksdb;
+insert into t2 select a,a,a,a from t1;
+# Try a basic case:
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0;
+pk kp1 kp2 col1
+2 2 2 2
+4 4 4 4
+6 6 6 6
+8 8 8 8
+10 10 10 10
+# Check that ICP doesnt work for columns where column value
+# cant be restored from mem-comparable form:
+create table t3 (
+pk int primary key,
+kp1 int,
+kp2 varchar(10) collate utf8_general_ci,
+col1 int,
+key (kp1,kp2) comment 'rev:cf1'
+) engine=rocksdb;
+insert into t3 select a,a/10,a,a from t1;
+# This must not use ICP:
+explain
+select * from t3 where kp1=3 and kp2 like '%foo%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t3 ref kp1 kp1 5 const # Using where
+explain format=json
+select * from t3 where kp1 between 2 and 4 and mod(kp1,3)=0 and kp2 like '%foo%';
+EXPLAIN
+{
+ "query_block": {
+ "select_id": 1,
+ "table": {
+ "table_name": "t3",
+ "access_type": "range",
+ "possible_keys": ["kp1"],
+ "key": "kp1",
+ "key_length": "5",
+ "used_key_parts": ["kp1"],
+ "rows": 1000,
+ "filtered": 100,
+ "index_condition": "t3.kp1 between 2 and 4 and t3.kp1 MOD 3 = 0",
+ "attached_condition": "t3.kp2 like '%foo%'"
+ }
+ }
+}
+# Check that we handle the case where out-of-range is encountered sooner
+# than matched index condition
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1< 3 and kp2+1>50000;
+pk kp1 kp2 col1
+explain
+select * from t2 where kp1< 3 and kp2+1>50000;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using index condition
+select * from t2 where kp1< 3 and kp2+1>50000;
+pk kp1 kp2 col1
+# Try doing backwards scans
+# MariaDB: ICP is not supported for reverse scans.
+explain
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1 between 1 and 10 and mod(kp2,2)=0 order by kp1 desc;
+pk kp1 kp2 col1
+10 10 10 10
+8 8 8 8
+6 6 6 6
+4 4 4 4
+2 2 2 2
+explain
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1 >990 and mod(kp2,2)=0 order by kp1 desc;
+pk kp1 kp2 col1
+998 998 998 998
+996 996 996 996
+994 994 994 994
+992 992 992 992
+explain
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range kp1 kp1 5 NULL # Using where
+select * from t2 where kp1< 3 and kp2+1>50000 order by kp1 desc;
+pk kp1 kp2 col1
+drop table t0,t1,t2,t3;
+#
+# Check how ICP affects counters
+#
+# First, some preparations
+#
+# in facebook/mysql-5.6, it was:
+# select ROWS_READ, ROWS_REQUESTED, ROWS_INDEX_FIRST, ROWS_INDEX_NEXT
+#
+# In MariaDB, we do:
+create procedure save_read_stats()
+begin
+set @rr=(select ROWS_READ
+from information_schema.table_statistics
+where table_name='t4' and table_schema=database());
+set @rif= (select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_first');
+set @rin=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_next');
+set @icp_attempts=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_attempts');
+set @icp_matches=(select VARIABLE_VALUE
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_match');
+end|
+create procedure get_read_stats()
+begin
+select
+(select ROWS_READ
+from information_schema.table_statistics
+where table_name='t4' and table_schema=database()
+) - @rr as ROWS_READ_DIFF,
+(select VARIABLE_VALUE - @rif
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_first') as ROWS_INDEX_FIRST,
+(select VARIABLE_VALUE - @rin
+from information_schema.session_status
+where VARIABLE_NAME='Handler_read_next') as ROWS_INDEX_NEXT,
+(select VARIABLE_VALUE - @icp_attempts
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_attempts') as ICP_ATTEMPTS,
+(select VARIABLE_VALUE - @icp_matches
+from information_schema.session_status
+where VARIABLE_NAME='Handler_icp_match') as ICP_MATCHES;
+end|
+create table t4 (
+id int,
+id1 int,
+id2 int,
+value int,
+value2 varchar(100),
+primary key (id),
+key id1_id2 (id1, id2) comment 'rev:cf1'
+) engine=rocksdb charset=latin1 collate latin1_bin;
+insert into t4 values
+(1,1,1,1,1), (2,1,2,2,2), (3,1,3,3,3),(4,1,4,4,4),(5,1,5,5,5),
+(6,1,6,6,6), (7,1,7,7,7), (8,1,8,8,8),(9,1,9,9,9),(10,1,10,10,10);
+#
+# Now, the test itself
+#
+call save_read_stats();
+call get_read_stats();
+ROWS_READ_DIFF ROWS_INDEX_FIRST ROWS_INDEX_NEXT ICP_ATTEMPTS ICP_MATCHES
+0 0 0 0 0
+# ============== index-only query ==============
+explain
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using where; Using index
+call save_read_stats();
+select id1,id2 from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id1 id2
+1 1
+call get_read_stats();
+ROWS_READ_DIFF 10
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 10
+ICP_ATTEMPTS 0
+ICP_MATCHES 0
+# ============== Query without ICP ==============
+set optimizer_switch='index_condition_pushdown=off';
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using where
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id id1 id2 value value2
+1 1 1 1 1
+call get_read_stats();
+ROWS_READ_DIFF 10
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 10
+ICP_ATTEMPTS 0
+ICP_MATCHES 0
+# ============== Query with ICP ==============
+set optimizer_switch='index_condition_pushdown=on';
+explain
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref id1_id2 id1_id2 5 const # Using index condition
+call save_read_stats();
+select * from t4 force index (id1_id2) where id1=1 and id2 % 10 = 1;
+id id1 id2 value value2
+1 1 1 1 1
+call get_read_stats();
+ROWS_READ_DIFF 1
+ROWS_INDEX_FIRST 0
+ROWS_INDEX_NEXT 1
+ICP_ATTEMPTS 10
+ICP_MATCHES 1
+drop table t4;
+drop procedure save_read_stats;
+drop procedure get_read_stats;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_locks.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_locks.result
new file mode 100644
index 00000000..c0995973
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_locks.result
@@ -0,0 +1,64 @@
+create table t1 (pk int not null primary key) engine=rocksdb;
+insert into t1 values (1),(2),(3);
+set autocommit=0;
+begin;
+select * from t1 where pk=1 for update;
+pk
+1
+connect con1,localhost,root,,;
+connection con1;
+call mtr.add_suppression("Got snapshot conflict errors");
+### Connection con1
+set @@rocksdb_lock_wait_timeout=500;
+set autocommit=0;
+begin;
+select * from t1 where pk=1 for update;;
+connection default;
+### Connection default
+rollback;
+connection con1;
+pk
+1
+rollback;
+connection default;
+begin;
+select * from t1 where pk=1 for update;
+pk
+1
+connection con1;
+### Connection con1
+set @@rocksdb_lock_wait_timeout=2;
+set autocommit=0;
+begin;
+select * from t1 where pk=1 for update;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection default;
+rollback;
+set autocommit=1;
+connection con1;
+drop table t1;
+connection default;
+#
+# Now, test what happens if another transaction modified the record and committed
+#
+CREATE TABLE t1 (
+id int primary key,
+value int
+) engine=rocksdb collate latin1_bin;
+insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+connection con1;
+BEGIN;
+SELECT * FROM t1 WHERE id=3;
+id value
+3 3
+connection default;
+BEGIN;
+UPDATE t1 SET value=30 WHERE id=3;
+COMMIT;
+connection con1;
+SELECT * FROM t1 WHERE id=3 FOR UPDATE;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict)
+ROLLBACK;
+disconnect con1;
+connection default;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_parts.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_parts.result
new file mode 100644
index 00000000..70bef39e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_parts.result
@@ -0,0 +1,152 @@
+drop table if exists t1,t2;
+# Tests for MyRocks + partitioning
+#
+# MyRocks Issue #70: Server crashes in Rdb_key_def::get_primary_key_tuple
+#
+CREATE TABLE t1 (pk INT PRIMARY KEY, f1 INT, f2 INT, KEY(f2)) ENGINE=RocksDB
+PARTITION BY HASH(pk) PARTITIONS 2;
+INSERT INTO t1 VALUES (1, 6, NULL), (2, NULL, 1);
+CREATE TABLE t2 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1, 1), (2, 1);
+SELECT f1 FROM t1 WHERE f2 = ( SELECT f1 FROM t2 WHERE pk = 2 );
+f1
+NULL
+drop table t1,t2;
+#
+# Issue#105: key_info[secondary_key].actual_key_parts does not include primary key on partitioned tables
+#
+CREATE TABLE t1 (
+id INT PRIMARY KEY,
+a set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8,
+b set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8 default null,
+c set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8 not null,
+INDEX (a),
+INDEX (b),
+INDEX (c)
+) ENGINE=RocksDB PARTITION BY key (id) partitions 2;
+INSERT INTO t1 (id, b) VALUES (28, 3);
+Warnings:
+Warning 1364 Field 'c' doesn't have a default value
+UPDATE t1 SET id=8 WHERE c < 8 LIMIT 1;
+check table t1;
+Table Op Msg_type Msg_text
+test.t1 check status OK
+drop table t1;
+#
+# Issue #105, another testcase
+#
+create table t1 (
+pk int primary key,
+col1 int,
+col2 int,
+key (col1) comment 'rev:cf_issue105'
+) engine=rocksdb partition by hash(pk) partitions 2;
+insert into t1 values (1,10,10);
+insert into t1 values (2,10,10);
+insert into t1 values (11,20,20);
+insert into t1 values (12,20,20);
+set @tmp_rfirr= @@rocksdb_force_index_records_in_range;
+set rocksdb_force_index_records_in_range= 12;
+explain select * from t1 force index(col1) where col1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref col1 col1 5 const #
+select * from t1 force index(col1) where col1=10;
+pk col1 col2
+1 10 10
+2 10 10
+select * from t1 use index () where col1=10;
+pk col1 col2
+2 10 10
+1 10 10
+set rocksdb_force_index_records_in_range= @tmp_rfirr;
+drop table t1;
+#
+# Issue #108: Index-only scans do not work for partitioned tables and extended keys
+#
+create table t1 (
+pk int primary key,
+col1 int,
+col2 int,
+key (col1)
+) engine=rocksdb partition by hash(pk) partitions 2;
+insert into t1 values (1,10,10);
+insert into t1 values (2,10,10);
+insert into t1 values (11,20,20);
+insert into t1 values (12,20,20);
+# The following must use "Using index"
+explain select pk from t1 force index(col1) where col1=10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref col1 col1 5 const 2000 Using index
+drop table t1;
+#
+# Issue #214: subqueries cause crash
+#
+create TABLE t1(a int,b int,c int,primary key(a,b))
+partition by list (b*a) (partition x1 values in (1) tablespace ts1,
+partition x2 values in (3,11,5,7) tablespace ts2,
+partition x3 values in (16,8,5+19,70-43) tablespace ts3);
+create table t2(b binary(2));
+set session optimizer_switch='materialization=off';
+insert into t1(a,b) values(1,7);
+select a from t1 where a in (select a from t1 where a in (select b from t2));
+a
+drop table t1, t2;
+#
+# Issue #260: altering name to invalid value leaves table unaccessible
+#
+CREATE TABLE t1 (c1 INT NOT NULL, c2 CHAR(5)) PARTITION BY HASH(c1) PARTITIONS 4;
+INSERT INTO t1 VALUES(1,'a');
+RENAME TABLE t1 TO db3.t3;
+ERROR HY000: Error on rename of './test/t1' to './db3/t3' (errno: -1 "Internal error < 0 (Not system error)")
+SELECT * FROM t1;
+c1 c2
+1 a
+SHOW TABLES;
+Tables_in_test
+t1
+RENAME TABLE t1 TO test.t3;
+SELECT * FROM t3;
+c1 c2
+1 a
+SHOW TABLES;
+Tables_in_test
+t3
+CREATE DATABASE db3;
+USE test;
+RENAME TABLE t3 to db3.t2;
+USE db3;
+SELECT * FROM t2;
+c1 c2
+1 a
+SHOW TABLES;
+Tables_in_db3
+t2
+DROP TABLE t2;
+use test;
+DROP DATABASE db3;
+#
+# MDEV-13153 Assertion `global_status_var.global_memory_used == 0 '
+# failed upon server restart with partitioned RocksDB table
+#
+CREATE TABLE t1 (a INT) ENGINE=RocksDB PARTITION BY HASH(a) PARTITIONS 2;
+INSERT INTO t1 (a) VALUES (1),(2);
+ALTER TABLE t1 ADD PARTITION PARTITIONS 2;
+# restart
+SELECT 1;
+1
+1
+DROP TABLE t1;
+#
+# MDEV-15245: Assertion `false' failed in myrocks::ha_rocksdb::position
+#
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1),(2);
+CREATE TABLE t2 (b INT) ENGINE=RocksDB
+PARTITION BY RANGE(b)
+(PARTITION p0 VALUES LESS THAN (1),
+PARTITION p1 VALUES LESS THAN MAXVALUE);
+INSERT INTO t2 VALUES (NULL),(0);
+CREATE ALGORITHM = MERGE VIEW v1 AS SELECT t2.* FROM t1, t2;
+UPDATE v1 SET b = 5;
+drop view v1;
+drop table t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_qcache.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_qcache.result
new file mode 100644
index 00000000..28479217
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_qcache.result
@@ -0,0 +1,45 @@
+set @save_query_cache_type=@@global.query_cache_type;
+set @save_query_cache_size=@@global.query_cache_size;
+set @@global.query_cache_type=1;
+set @@global.query_cache_size=1024*1024;
+create table t1 (pk int primary key, c char(8)) engine=RocksDB;
+insert into t1 values (1,'new'),(2,'new');
+select * from t1;
+pk c
+1 new
+2 new
+connect con1,localhost,root,,;
+update t1 set c = 'updated';
+connection default;
+flush status;
+show status like 'Qcache_hits';
+Variable_name Value
+Qcache_hits 0
+show global status like 'Qcache_hits';
+Variable_name Value
+Qcache_hits 0
+select * from t1;
+pk c
+1 updated
+2 updated
+select sql_no_cache * from t1;
+pk c
+1 updated
+2 updated
+select * from t1 where pk = 1;
+pk c
+1 updated
+show status like 'Qcache_hits';
+Variable_name Value
+Qcache_hits 0
+# MariaDB: Qcache_not_cached is not incremented for select sql_no_cache queries
+# so the following query produces 2, not 3:
+show status like 'Qcache_not_cached';
+Variable_name Value
+Qcache_not_cached 2
+show global status like 'Qcache_hits';
+Variable_name Value
+Qcache_hits 0
+drop table t1;
+set @@global.query_cache_type=@save_query_cache_type;
+set @@global.query_cache_size=@save_query_cache_size;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range.result
new file mode 100644
index 00000000..918859ea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range.result
@@ -0,0 +1,293 @@
+select * from information_schema.engines where engine = 'rocksdb';
+ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS
+ROCKSDB DEFAULT RocksDB storage engine YES YES YES
+drop table if exists t0,t1,t2,t3,t4,t5;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int not null,
+a int not null,
+b int not null,
+primary key(pk),
+key(a) comment 'rev:cf1'
+) engine=rocksdb;
+insert into t2 select A.a, FLOOR(A.a/10), A.a from t1 A;
+#
+# HA_READ_KEY_EXACT tests
+#
+# Original failure was here:
+explain
+select * from t2 force index (a) where a=0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref a a 4 const #
+select * from t2 force index (a) where a=0;
+pk a b
+0 0 0
+1 0 1
+2 0 2
+3 0 3
+4 0 4
+5 0 5
+6 0 6
+7 0 7
+8 0 8
+9 0 9
+# The rest are for code coverage:
+explain
+select * from t2 force index (a) where a=2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 ref a a 4 const #
+select * from t2 force index (a) where a=2;
+pk a b
+20 2 20
+21 2 21
+22 2 22
+23 2 23
+24 2 24
+25 2 25
+26 2 26
+27 2 27
+28 2 28
+29 2 29
+explain
+select * from t2 force index (a) where a=3 and pk=33;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 const a a 8 const,const #
+select * from t2 force index (a) where a=3 and pk=33;
+pk a b
+33 3 33
+select * from t2 force index (a) where a=99 and pk=99;
+pk a b
+select * from t2 force index (a) where a=0 and pk=0;
+pk a b
+0 0 0
+select * from t2 force index (a) where a=-1;
+pk a b
+select * from t2 force index (a) where a=-1 and pk in (101,102);
+pk a b
+select * from t2 force index (a) where a=100 and pk in (101,102);
+pk a b
+#
+# #36: Range in form tbl.key >= const doesn't work in reverse column family
+#
+explain
+select count(*) from t2 force index (a) where a>=0 and a <=1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 4 NULL # Using where; Using index
+select count(*) from t2 force index (a) where a>=0 and a <=1;
+count(*)
+20
+explain
+select count(*) from t2 force index (a) where a>=-1 and a <=1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 4 NULL # Using where; Using index
+select count(*) from t2 force index (a) where a>=-1 and a <=1;
+count(*)
+20
+explain
+select * from t2 force index (a) where a=0 and pk>=3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 8 NULL # Using index condition
+select * from t2 force index (a) where a=0 and pk>=3;
+pk a b
+3 0 3
+4 0 4
+5 0 5
+6 0 6
+7 0 7
+8 0 8
+9 0 9
+# Try edge cases where we fall over the end of the table
+create table t3 like t2;
+insert into t3 select * from t2;
+select * from t3 where pk>=1000000;
+pk a b
+select * from t2 where pk>=1000000;
+pk a b
+#
+# #42: Range in form tbl.key > const doesn't work in reverse column family
+#
+explain
+select count(*) from t2 force index (a) where a>0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 4 NULL # Using where; Using index
+select count(*) from t2 force index (a) where a>0;
+count(*)
+990
+explain
+select count(*) from t2 force index (a) where a>99;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 4 NULL # Using where; Using index
+select count(*) from t2 force index (a) where a>99;
+count(*)
+0
+select * from t2 where pk>1000000;
+pk a b
+select * from t3 where pk>1000000;
+pk a b
+explain
+select count(*) from t2 force index (a) where a=2 and pk>25;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 8 NULL # Using where; Using index
+select count(*) from t2 force index (a) where a=2 and pk>25;
+count(*)
+4
+select * from t2 force index (a) where a>-10 and a < 1;
+pk a b
+0 0 0
+1 0 1
+2 0 2
+3 0 3
+4 0 4
+5 0 5
+6 0 6
+7 0 7
+8 0 8
+9 0 9
+select * from t3 force index (a) where a>-10 and a < 1;
+pk a b
+0 0 0
+1 0 1
+2 0 2
+3 0 3
+4 0 4
+5 0 5
+6 0 6
+7 0 7
+8 0 8
+9 0 9
+#
+# #46: index_read_map(HA_READ_BEFORE_KEY) does not work in reverse column family
+#
+select max(a) from t2 where a < 2;
+max(a)
+1
+select max(a) from t2 where a < -1;
+max(a)
+NULL
+select max(pk) from t2 where a=3 and pk < 6;
+max(pk)
+NULL
+select max(pk) from t2 where pk < 200000;
+max(pk)
+999
+select max(pk) from t2 where pk < 20;
+max(pk)
+19
+select max(a) from t3 where a < 2;
+max(a)
+1
+select max(a) from t3 where a < -1;
+max(a)
+NULL
+select max(pk) from t3 where pk < 200000;
+max(pk)
+999
+select max(pk) from t3 where pk < 20;
+max(pk)
+19
+select max(pk) from t2 where a=3 and pk < 33;
+max(pk)
+32
+select max(pk) from t3 where a=3 and pk < 33;
+max(pk)
+32
+#
+# #48: index_read_map(HA_READ_PREFIX_LAST) does not work in reverse CF
+#
+# Tests for search_flag=HA_READ_PREFIX_LAST_OR_PREV
+# Note: the next explain has "Using index condition" in fb/mysql-5.6
+# but "Using where" in MariaDB because the latter does not
+# support ICP over reverse scans.
+explain
+select * from t2 where a between 99 and 2000 order by a desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t2 range a a 4 NULL # Using where
+select * from t2 where a between 99 and 2000 order by a desc;
+pk a b
+999 99 999
+998 99 998
+997 99 997
+996 99 996
+995 99 995
+994 99 994
+993 99 993
+992 99 992
+991 99 991
+990 99 990
+select max(a) from t2 where a <=10;
+max(a)
+10
+select max(a) from t2 where a <=-4;
+max(a)
+NULL
+select max(pk) from t2 where a=5 and pk <=55;
+max(pk)
+55
+select max(pk) from t2 where a=5 and pk <=55555;
+max(pk)
+59
+select max(pk) from t2 where a=5 and pk <=0;
+max(pk)
+NULL
+select max(pk) from t2 where pk <=-1;
+max(pk)
+NULL
+select max(pk) from t2 where pk <=999999;
+max(pk)
+999
+select max(pk) from t3 where pk <=-1;
+max(pk)
+NULL
+select max(pk) from t3 where pk <=999999;
+max(pk)
+999
+#
+# Tests for search_flag=HA_READ_PREFIX_LAST
+#
+create table t4 (
+pk int primary key,
+a int,
+b int,
+c int,
+key(a,b,c)
+) engine=rocksdb;
+insert into t4 select pk,pk,pk,pk from t2 where pk < 100;
+explain
+select * from t4 where a=1 and b in (1) order by c desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref a a 10 const,const # Using where; Using index
+select * from t4 where a=1 and b in (1) order by c desc;
+pk a b c
+1 1 1 1
+explain
+select * from t4 where a=5 and b in (4) order by c desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t4 ref a a 10 const,const # Using where; Using index
+select * from t4 where a=5 and b in (4) order by c desc;
+pk a b c
+# HA_READ_PREFIX_LAST for reverse-ordered CF
+create table t5 (
+pk int primary key,
+a int,
+b int,
+c int,
+key(a,b,c) comment 'rev:cf2'
+) engine=rocksdb;
+insert into t5 select pk,pk,pk,pk from t2 where pk < 100;
+explain
+select * from t5 where a=1 and b in (1) order by c desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t5 ref a a 10 const,const # Using where; Using index
+select * from t5 where a=1 and b in (1) order by c desc;
+pk a b c
+1 1 1 1
+explain
+select * from t5 where a=5 and b in (4) order by c desc;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t5 ref a a 10 const,const # Using where; Using index
+select * from t5 where a=5 and b in (4) order by c desc;
+pk a b c
+drop table t0,t1,t2,t3,t4,t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range2.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range2.result
new file mode 100644
index 00000000..bcda9341
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_range2.result
@@ -0,0 +1,29 @@
+create table t1 (id1 bigint, id2 bigint, c1 bigint, c2 bigint, c3 bigint, c4 bigint, c5 bigint, c6 bigint, c7 bigint, primary key (id1, id2), index i(c1, c2));
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+select count(*) from t1;
+count(*)
+10000
+explain select c1 from t1 where c1 > 5 limit 10;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index i i 18 NULL # Using where; Using index
+drop table t1;
+#
+# MDEV-17414: MyROCKS order desc limit 1 fails
+#
+create table t1 (date date);
+insert into t1 values ('2018-10-04'), ('2018-10-05');
+select * from t1 where date < '2018-10-09' order by date desc limit 1;
+date
+2018-10-05
+alter table t1 add index date_index (date);
+select * from t1 where date < '2018-10-05' order by date desc limit 1;
+date
+2018-10-04
+# this should not produce an empty set:
+select * from t1 where date < '2018-10-09' order by date desc limit 1;
+date
+2018-10-05
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl.result
new file mode 100644
index 00000000..fabf077e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl.result
@@ -0,0 +1,335 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+drop table if exists t1;
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3), (4,4);
+include/sync_slave_sql_with_master.inc
+
+# regular update/delete. With rocks_read_free_rpl=PK_SK, rocksdb_rows_read does not increase on slaves
+
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set value=value+1 where id=1;
+delete from t1 where id=4;
+select * from t1;
+id value
+1 2
+2 2
+3 3
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+id value
+1 2
+2 2
+3 3
+
+# "rocks_read_free_rpl=PK_SK" makes "row not found error" not happen anymore
+
+include/stop_slave.inc
+delete from t1 where id in (2, 3);
+include/start_slave.inc
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set value=value+1 where id=3;
+delete from t1 where id=2;
+select * from t1;
+id value
+1 2
+3 4
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+id value
+1 2
+3 4
+
+## tables without primary key -- read free replication should be disabled
+
+
+#no index
+
+drop table t1;
+create table t1 (c1 int, c2 int);
+insert into t1 values (1,1), (2,2),(3,3),(4,4),(5,5);
+include/sync_slave_sql_with_master.inc
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set c2=100 where c1=3;
+delete from t1 where c1 <= 2;
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+select * from t1;
+c1 c2
+3 100
+4 4
+5 5
+
+#secondary index only
+
+drop table t1;
+create table t1 (c1 int, c2 int, index i(c1));
+insert into t1 values (1,1), (2,2),(3,3),(4,4),(5,5);
+include/sync_slave_sql_with_master.inc
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set c2=100 where c1=3;
+delete from t1 where c1 <= 2;
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+select * from t1;
+c1 c2
+3 100
+4 4
+5 5
+
+## large row operations -- primary key modification, secondary key modification
+
+drop table t1;
+create table t1 (id1 bigint, id2 bigint, c1 bigint, c2 bigint, c3 bigint, c4 bigint, c5 bigint, c6 bigint, c7 bigint, primary key (id1, id2), index i(c1, c2));
+include/sync_slave_sql_with_master.inc
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+#updating all secondary keys by 1
+
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+include/diff_tables.inc [master:t1, slave:t1]
+
+#updating all primary keys by 2
+
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+include/diff_tables.inc [master:t1, slave:t1]
+
+#updating secondary keys after truncating t1 on slave
+
+truncate table t1;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set c2=c2+10;
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+include/diff_tables.inc [master:t1, slave:t1]
+
+#updating primary keys after truncating t1 on slave
+
+truncate table t1;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+update t1 set id2=id2+10;
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+include/diff_tables.inc [master:t1, slave:t1]
+
+#deleting half rows
+
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+delete from t1 where id1 <= 5000;
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+include/diff_tables.inc [master:t1, slave:t1]
+
+# rocksdb_read_free_rpl = PK_ONLY i.e. it only works on tables with only PK
+
+[on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl = PK_ONLY;
+start slave;
+[on master]
+create table t2 (id int primary key, i1 int, i2 int, value int);
+create table u2 (id int primary key, i1 int, i2 int, value int, index(i1), index(i2));
+insert into t2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+insert into u2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+include/sync_slave_sql_with_master.inc
+[on slave]
+delete from t2 where id <= 2;
+delete from u2 where id <= 2;
+[on master]
+update t2 set i2=100, value=100 where id=1;
+update u2 set i2=100, value=100 where id=1;
+[on slave]
+call mtr.add_suppression("Slave SQL.*Could not execute Update_rows event on table test.u2.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 'u2'.*");
+include/wait_for_slave_sql_error.inc [errno=1032]
+select count(*) from t2 force index(primary);
+count(*)
+2
+select * from t2 where id=1;
+id i1 i2 value
+1 1 100 100
+select i1 from t2 where i1=1;
+i1
+1
+select i2 from t2 where i2=100;
+i2
+100
+select count(*) from u2 force index(primary);
+count(*)
+1
+select count(*) from u2 force index(i1);
+count(*)
+1
+select count(*) from u2 force index(i2);
+count(*)
+1
+select * from u2 where id=1;
+id i1 i2 value
+select i1 from u2 where i1=1;
+i1
+select i2 from u2 where i2=100;
+i2
+include/wait_for_slave_sql_to_start.inc
+[on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl = PK_SK;
+start slave;
+
+# some tables with read-free replication on and some with it off
+
+[on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl_tables = "t.*";
+start slave;
+[on master]
+drop table if exists t2;
+drop table if exists u2;
+create table t2 (id int primary key, i1 int, i2 int, value int);
+create table u2 (id int primary key, i1 int, i2 int, value int);
+insert into t2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+insert into u2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+include/sync_slave_sql_with_master.inc
+[on slave]
+delete from t2 where id <= 2;
+delete from u2 where id <= 2;
+[on master]
+update t2 set i2=100, value=100 where id=1;
+update u2 set i2=100, value=100 where id=1;
+[on slave]
+call mtr.add_suppression("Slave SQL.*Could not execute Update_rows event on table test.u2.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 'u2'.*");
+include/wait_for_slave_sql_error.inc [errno=1032]
+select count(*) from t2 force index(primary);
+count(*)
+2
+select * from t2 where id=1;
+id i1 i2 value
+1 1 100 100
+select i1 from t2 where i1=1;
+i1
+1
+select i2 from t2 where i2=100;
+i2
+100
+select count(*) from u2 force index(primary);
+count(*)
+1
+select * from u2 where id=1;
+id i1 i2 value
+select i1 from u2 where i1=1;
+i1
+select i2 from u2 where i2=100;
+i2
+include/wait_for_slave_sql_to_start.inc
+[on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl_tables = ".*";
+start slave;
+
+# secondary keys lose rows
+
+[on master]
+create table t3 (id int primary key, i1 int, i2 int, value int, index(i1),
+index(i2));
+insert into t3 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+include/sync_slave_sql_with_master.inc
+[on slave]
+delete from t3 where id <= 2;
+[on master]
+update t3 set i2=100, value=100 where id=1;
+include/sync_slave_sql_with_master.inc
+select count(*) from t3 force index(primary);
+count(*)
+2
+select count(*) from t3 force index(i1);
+count(*)
+1
+select count(*) from t3 force index(i2);
+count(*)
+2
+select * from t3 where id=1;
+id i1 i2 value
+1 1 100 100
+select i1 from t3 where i1=1;
+i1
+select i2 from t3 where i2=100;
+i2
+100
+
+# secondary keys have extra rows
+
+[on master]
+create table t4 (id int primary key, i1 int, i2 int, value int, index(i1), index(i2));
+insert into t4 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+include/sync_slave_sql_with_master.inc
+[on slave]
+update t4 set i1=100 where id=1;
+[on master]
+delete from t4 where id=1;
+include/sync_slave_sql_with_master.inc
+[on slave]
+select count(*) from t4 force index(primary);
+count(*)
+2
+select count(*) from t4 force index(i1);
+count(*)
+3
+select count(*) from t4 force index(i2);
+count(*)
+2
+select i1 from t4 where i1=100;
+i1
+100
+
+# inserts are also read-free
+
+[on master]
+drop table if exists t2;
+drop table if exists t3;
+create table t2 (id int primary key, i1 int, i2 int);
+create table t3 (id int primary key, i1 int, i2 int, key(i1));
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+insert into t2 values(1, 1, 1);
+insert into t2 values(2, 2, 2);
+insert into t3 values(1, 1, 1);
+insert into t3 values(2, 2, 2);
+include/sync_slave_sql_with_master.inc
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t2;
+id i1 i2
+1 1 1
+2 2 2
+select * from t3;
+id i1 i2
+1 1 1
+2 2 2
+drop table t1, t2, t3, t4, u2;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl_stress.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl_stress.result
new file mode 100644
index 00000000..9e3c7a05
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_read_free_rpl_stress.result
@@ -0,0 +1,35 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+include/stop_slave.inc
+set @@global.rocksdb_read_free_rpl = PK_SK;
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+include/diff_tables.inc [master:t1, slave:t1]
+include/diff_tables.inc [master:t2, slave:t2]
+include/diff_tables.inc [master:t3, slave:t3]
+include/diff_tables.inc [master:t4, slave:t4]
+include/diff_tables.inc [master:t5, slave:t5]
+include/diff_tables.inc [master:t6, slave:t6]
+include/diff_tables.inc [master:t7, slave:t7]
+include/diff_tables.inc [master:t8, slave:t8]
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+set @@global.rocksdb_read_free_rpl = PK_ONLY;
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+include/diff_tables.inc [master:t1, slave:t1]
+include/diff_tables.inc [master:t2, slave:t2]
+include/diff_tables.inc [master:t3, slave:t3]
+include/diff_tables.inc [master:t4, slave:t4]
+include/diff_tables.inc [master:t5, slave:t5]
+include/diff_tables.inc [master:t6, slave:t6]
+include/diff_tables.inc [master:t7, slave:t7]
+include/diff_tables.inc [master:t8, slave:t8]
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+set @@global.rocksdb_read_free_rpl = default;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_row_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_row_stats.result
new file mode 100644
index 00000000..8c02de98
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_row_stats.result
@@ -0,0 +1,66 @@
+create table t1 (a int primary key) engine=rocksdb;
+Verify rocksdb_rows_inserted
+select variable_value into @old_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_rows_inserted';
+insert into t1 values(1);
+select variable_value into @new_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_rows_inserted';
+select @new_rows_inserted - @old_rows_inserted;
+@new_rows_inserted - @old_rows_inserted
+1
+Verify rocksdb_rows_updated
+select variable_value into @old_rows_updated from information_schema.global_status where variable_name = 'rocksdb_rows_updated';
+update t1 set a=2 where a=1;
+select variable_value into @new_rows_updated from information_schema.global_status where variable_name = 'rocksdb_rows_updated';
+select @new_rows_updated - @old_rows_updated;
+@new_rows_updated - @old_rows_updated
+1
+Verify rocksdb_rows_read
+select variable_value into @old_rows_read from information_schema.global_status where variable_name = 'rocksdb_rows_read';
+select * from t1;
+a
+2
+select variable_value into @new_rows_read from information_schema.global_status where variable_name = 'rocksdb_rows_read';
+select @new_rows_read - @old_rows_read;
+@new_rows_read - @old_rows_read
+1
+Verify rocksdb_rows_deleted
+select variable_value into @old_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_rows_deleted';
+delete from t1;
+select variable_value into @new_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_rows_deleted';
+select @new_rows_deleted - @old_rows_deleted;
+@new_rows_deleted - @old_rows_deleted
+1
+use mysql;
+create table t1(a int primary key) engine=rocksdb;
+Verify rocksdb_system_rows_inserted
+select variable_value into @old_system_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_system_rows_inserted';
+insert into t1 values(1);
+select variable_value into @new_system_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_system_rows_inserted';
+select @new_system_rows_inserted - @old_system_rows_inserted;
+@new_system_rows_inserted - @old_system_rows_inserted
+1
+Verify rocksdb_system_rows_updated
+select variable_value into @old_system_rows_updated from information_schema.global_status where variable_name = 'rocksdb_system_rows_updated';
+update t1 set a=2 where a=1;
+select variable_value into @new_system_rows_updated from information_schema.global_status where variable_name = 'rocksdb_system_rows_updated';
+select @new_system_rows_updated - @old_system_rows_updated;
+@new_system_rows_updated - @old_system_rows_updated
+1
+Verify rocksdb_system_rows_read
+select variable_value into @old_system_rows_read from information_schema.global_status where variable_name = 'rocksdb_system_rows_read';
+select * from t1;
+a
+2
+select variable_value into @new_system_rows_read from information_schema.global_status where variable_name = 'rocksdb_system_rows_read';
+select @new_system_rows_read - @old_system_rows_read;
+@new_system_rows_read - @old_system_rows_read
+1
+Verify rocksdb_system_rows_deleted
+select variable_value into @old_system_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_system_rows_deleted';
+delete from t1;
+select variable_value into @new_system_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_system_rows_deleted';
+select @new_system_rows_deleted - @old_system_rows_deleted;
+@new_system_rows_deleted - @old_system_rows_deleted
+1
+drop table t1;
+use test;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_table_stats_sampling_pct_change.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_table_stats_sampling_pct_change.result
new file mode 100644
index 00000000..1e8aa578
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_table_stats_sampling_pct_change.result
@@ -0,0 +1,23 @@
+drop table if exists t1;
+SET @ORIG_PCT = @@ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 100;
+create table t1 (pk int primary key) engine=rocksdb;
+set global rocksdb_force_flush_memtable_now = true;
+select table_rows from information_schema.tables
+where table_schema = database() and table_name = 't1';
+table_rows
+10000
+drop table t1;
+drop table if exists t2;
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 10;
+create table t2 (pk int primary key) engine=rocksdb;
+set global rocksdb_force_flush_memtable_now = true;
+select table_rows from information_schema.tables
+where table_schema = database() and table_name = 't2';
+table_rows
+10000
+select table_name from information_schema.tables where table_schema = database() and table_name = 't2';
+table_name
+t2
+drop table t2;
+SET GLOBAL ROCKSDB_TABLE_STATS_SAMPLING_PCT = @ORIG_PCT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_timeout_rollback.result b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_timeout_rollback.result
new file mode 100644
index 00000000..adf05d06
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rocksdb_timeout_rollback.result
@@ -0,0 +1,84 @@
+drop table if exists t1;
+SET @@global.rocksdb_rollback_on_timeout = 1;
+show variables like 'rocksdb_rollback_on_timeout';
+Variable_name Value
+rocksdb_rollback_on_timeout ON
+create table t1 (a int unsigned not null primary key) engine = rocksdb;
+insert into t1 values (1);
+commit;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con2;
+begin work;
+insert into t1 values (5);
+insert into t1 values (6);
+update t1 set a = a + 1 where a = 1;
+connection con1;
+begin work;
+insert into t1 values (7);
+insert into t1 values (8);
+update t1 set a = a + 1 where a = 1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+select * from t1;
+a
+1
+commit;
+connection con2;
+select * from t1;
+a
+2
+5
+6
+commit;
+connection default;
+select * from t1;
+a
+2
+5
+6
+SET @@global.rocksdb_rollback_on_timeout = 0;
+show variables like 'rocksdb_rollback_on_timeout';
+Variable_name Value
+rocksdb_rollback_on_timeout OFF
+connection con2;
+begin work;
+insert into t1 values (9);
+insert into t1 values (10);
+update t1 set a = a + 1 where a = 2;
+connection con1;
+begin work;
+insert into t1 values (11);
+insert into t1 values (12);
+update t1 set a = a + 1 where a = 2;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+select * from t1;
+a
+2
+5
+6
+11
+12
+commit;
+connection con2;
+select * from t1;
+a
+3
+5
+6
+9
+10
+commit;
+connection default;
+select * from t1;
+a
+3
+5
+6
+9
+10
+11
+12
+SET @@global.rocksdb_rollback_on_timeout = DEFAULT;
+drop table t1;
+disconnect con1;
+disconnect con2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rollback_savepoint.result b/storage/rocksdb/mysql-test/rocksdb/r/rollback_savepoint.result
new file mode 100644
index 00000000..18b76818
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rollback_savepoint.result
@@ -0,0 +1,29 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+CREATE TABLE t2 LIKE t1;
+INSERT INTO t2 SELECT * FROM t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+SAVEPOINT a;
+SELECT * FROM t1 ORDER BY pk;
+a b pk
+1 a 1
+2 b 2
+3 a 3
+ROLLBACK TO SAVEPOINT a;
+SAVEPOINT a;
+SELECT * FROM t2 ORDER BY pk;
+a b pk
+1 a 1
+2 b 2
+3 a 3
+ROLLBACK TO SAVEPOINT a;
+connection con2;
+ALTER TABLE t1 RENAME TO t3;
+connection default;
+DROP TABLE t2, t3;
+disconnect con1;
+disconnect con2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found.result
new file mode 100644
index 00000000..8cdfa910
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found.result
@@ -0,0 +1,56 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+drop table if exists t1;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int primary key,
+kp1 int,
+kp2 int,
+col1 int,
+key (kp1,kp2)
+) engine=rocksdb;
+insert into t2 select a,a,a,a from t1;
+create table t3 like t2;
+insert into t3 select * from t2;
+include/sync_slave_sql_with_master.inc
+set global debug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+update t2 set col1=100 where kp1 between 1 and 3 and mod(kp2,2)=0;
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug = '';
+set sql_log_bin=0;
+delete from t2 where pk=2;
+delete from t2 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+include/sync_slave_sql_with_master.inc
+select * from t2 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 4
+set global debug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+update t3 set col1=100 where kp1 between 1 and 4 and mod(kp2,2)=0;
+call mtr.add_suppression("Deadlock found when trying to get lock");
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug = '';
+set sql_log_bin=0;
+delete from t3 where pk=2;
+delete from t3 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+include/sync_slave_sql_with_master.inc
+select * from t3 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 100
+drop table t0, t1, t2, t3;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found_rc.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found_rc.result
new file mode 100644
index 00000000..8cdfa910
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_not_found_rc.result
@@ -0,0 +1,56 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+drop table if exists t1;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int primary key,
+kp1 int,
+kp2 int,
+col1 int,
+key (kp1,kp2)
+) engine=rocksdb;
+insert into t2 select a,a,a,a from t1;
+create table t3 like t2;
+insert into t3 select * from t2;
+include/sync_slave_sql_with_master.inc
+set global debug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+update t2 set col1=100 where kp1 between 1 and 3 and mod(kp2,2)=0;
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug = '';
+set sql_log_bin=0;
+delete from t2 where pk=2;
+delete from t2 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+include/sync_slave_sql_with_master.inc
+select * from t2 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 4
+set global debug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+update t3 set col1=100 where kp1 between 1 and 4 and mod(kp2,2)=0;
+call mtr.add_suppression("Deadlock found when trying to get lock");
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug = '';
+set sql_log_bin=0;
+delete from t3 where pk=2;
+delete from t3 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+include/sync_slave_sql_with_master.inc
+select * from t3 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 100
+drop table t0, t1, t2, t3;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_rocksdb.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_rocksdb.result
new file mode 100644
index 00000000..de47f3b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_rocksdb.result
@@ -0,0 +1,45 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists t1;
+connection master;
+select @@binlog_format;
+@@binlog_format
+ROW
+create table t1 (pk int primary key) engine=rocksdb;
+insert into t1 values (1),(2),(3);
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+pk
+1
+2
+3
+connection master;
+drop table t1;
+#
+# Issue #18: slave crash on update with row based binary logging
+#
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,1,1);
+insert into t1 values (3,1,1);
+insert into t1 values (4,1,1);
+insert into t1 values (5,1,1);
+update t1 set value2=100 where id=1;
+update t1 set value2=200 where id=2;
+update t1 set value2=300 where id=3;
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1 where id=1;
+id value value2
+1 1 100
+select * from t1 where id=2;
+id value value2
+2 1 200
+select * from t1 where id=3;
+id value value2
+3 1 300
+connection master;
+drop table t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_stats.result
new file mode 100644
index 00000000..a14d2693
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_stats.result
@@ -0,0 +1,98 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists t1;
+connection master;
+create procedure save_read_stats()
+begin
+/*select rows_requested into @rq from information_schema.table_statistics
+where table_schema=database() and table_name='t1';*/
+select rows_read into @rr_is from information_schema.table_statistics
+where table_schema=database() and table_name='t1';
+select variable_value into @rr from information_schema.global_status
+where variable_name='rocksdb_rows_read';
+select variable_value into @ru from information_schema.global_status
+where variable_name='rocksdb_rows_updated';
+select variable_value into @rd from information_schema.global_status
+where variable_name='rocksdb_rows_deleted';
+end//
+create procedure get_read_stats()
+begin
+/*select rows_requested - @rq as rows_requested from
+information_schema.table_statistics
+where table_schema=database() and table_name='t1';*/
+select rows_read - @rr_is as rows_read_userstat from
+information_schema.table_statistics
+where table_schema=database() and table_name='t1';
+select variable_value - @rr as rows_read from
+information_schema.global_status
+where variable_name='rocksdb_rows_read';
+select variable_value - @ru as rows_updated from
+information_schema.global_status
+where variable_name='rocksdb_rows_updated';
+select variable_value - @rd as rows_deleted from
+information_schema.global_status
+where variable_name='rocksdb_rows_deleted';
+end//
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3), (4,4), (5,5);
+include/sync_slave_sql_with_master.inc
+connection slave;
+call save_read_stats();
+connection master;
+update t1 set value=value+1 where id=1;
+update t1 set value=value+1 where id=3;
+select * from t1;
+id value
+1 2
+2 2
+3 4
+4 4
+5 5
+include/sync_slave_sql_with_master.inc
+connection slave;
+call get_read_stats();
+rows_read_userstat
+2
+rows_read
+2
+rows_updated
+2
+rows_deleted
+0
+select * from t1;
+id value
+1 2
+2 2
+3 4
+4 4
+5 5
+call save_read_stats();
+connection master;
+delete from t1 where id in (4,5);
+select * from t1;
+id value
+1 2
+2 2
+3 4
+include/sync_slave_sql_with_master.inc
+connection slave;
+call get_read_stats();
+rows_read_userstat
+2
+rows_read
+2
+rows_updated
+0
+rows_deleted
+2
+select * from t1;
+id value
+1 2
+2 2
+3 4
+connection master;
+drop table t1;
+drop procedure save_read_stats;
+drop procedure get_read_stats;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_triggers.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_triggers.result
new file mode 100644
index 00000000..69acc4a9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_row_triggers.result
@@ -0,0 +1,286 @@
+include/master-slave.inc
+[connection master]
+# Test of row replication with triggers on the slave side
+connection master;
+CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1));
+SELECT * FROM t1;
+C1 C2
+connection slave;
+connection slave;
+SET @old_slave_exec_mode= @@global.slave_exec_mode;
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET @@global.slave_exec_mode= IDEMPOTENT;
+SET @@global.slave_run_triggers_for_rbr= YES;
+SELECT * FROM t1;
+C1 C2
+create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
+insert into t2 values
+('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
+('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
+('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
+create trigger t1_cnt_b before update on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
+create trigger t1_cnt_db before delete on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd0';
+create trigger t1_cnt_ib before insert on t1 for each row
+update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
+create trigger t1_cnt_a after update on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
+create trigger t1_cnt_da after delete on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
+create trigger t1_cnt_ia after insert on t1 for each row
+update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 0
+i1 0
+u0 0
+u1 0
+connection master;
+# INSERT triggers test
+insert into t1 values ('a','b');
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 1 a
+i1 1 a
+u0 0
+u1 0
+connection master;
+# UPDATE triggers test
+update t1 set C1= 'd';
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 1 a
+i1 1 a
+u0 1 a d
+u1 1 a d
+connection master;
+# DELETE triggers test
+delete from t1 where C1='d';
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 1 d
+d1 1 d
+i0 1 a
+i1 1 a
+u0 1 a d
+u1 1 a d
+# INSERT triggers which cause also UPDATE test (insert duplicate row)
+insert into t1 values ('0','1');
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 1 d
+d1 1 d
+i0 2 0
+i1 2 0
+u0 1 a d
+u1 1 a d
+connection master;
+insert into t1 values ('0','1');
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 1 d
+d1 1 d
+i0 3 0
+i1 3 0
+u0 2 0 0
+u1 2 0 0
+# INSERT triggers which cause also DELETE test
+# (insert duplicate row in table referenced by foreign key)
+insert into t1 values ('1','1');
+connection master;
+drop table if exists t1;
+connection slave;
+connection slave;
+SET @@global.slave_exec_mode= @old_slave_exec_mode;
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+drop table t2;
+connection master;
+CREATE TABLE t1 (i INT);
+CREATE TABLE t2 (i INT);
+connection slave;
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET GLOBAL slave_run_triggers_for_rbr=YES;
+CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
+INSERT INTO t2 VALUES (new.i);
+connection master;
+BEGIN;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t1 VALUES (2);
+COMMIT;
+connection slave;
+select * from t2;
+i
+1
+2
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+connection master;
+drop tables t2,t1;
+connection slave;
+# Triggers on slave do not work if master has some
+connection master;
+CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1));
+SELECT * FROM t1;
+C1 C2
+create trigger t1_dummy before delete on t1 for each row
+set @dummy= 1;
+connection slave;
+connection slave;
+SET @old_slave_exec_mode= @@global.slave_exec_mode;
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET @@global.slave_exec_mode= IDEMPOTENT;
+SET @@global.slave_run_triggers_for_rbr= YES;
+SELECT * FROM t1;
+C1 C2
+create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
+insert into t2 values
+('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
+('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
+('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
+create trigger t1_cnt_b before update on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
+create trigger t1_cnt_ib before insert on t1 for each row
+update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
+create trigger t1_cnt_a after update on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
+create trigger t1_cnt_da after delete on t1 for each row
+update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
+create trigger t1_cnt_ia after insert on t1 for each row
+update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 0
+i1 0
+u0 0
+u1 0
+connection master;
+# INSERT triggers test
+insert into t1 values ('a','b');
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 0
+i1 0
+u0 0
+u1 0
+connection master;
+# UPDATE triggers test
+update t1 set C1= 'd';
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 0
+i1 0
+u0 0
+u1 0
+connection master;
+# DELETE triggers test
+delete from t1 where C1='d';
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 0
+i1 0
+u0 0
+u1 0
+# INSERT triggers which cause also UPDATE test (insert duplicate row)
+insert into t1 values ('0','1');
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 1 0
+i1 1 0
+u0 0
+u1 0
+connection master;
+insert into t1 values ('0','1');
+connection slave;
+connection slave;
+SELECT * FROM t2 order by id;
+id cnt o n
+d0 0
+d1 0
+i0 1 0
+i1 1 0
+u0 0
+u1 0
+# INSERT triggers which cause also DELETE test
+# (insert duplicate row in table referenced by foreign key)
+insert into t1 values ('1','1');
+connection master;
+drop table if exists t1;
+connection slave;
+connection slave;
+SET @@global.slave_exec_mode= @old_slave_exec_mode;
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+drop table t2;
+#
+# MDEV-5513: Trigger is applied to the rows after first one
+#
+connection master;
+create table t1 (a int, b int);
+create table tlog (a int auto_increment primary key);
+set sql_log_bin=0;
+create trigger tr1 after insert on t1 for each row insert into tlog values (null);
+set sql_log_bin=1;
+connection slave;
+connection slave;
+set @slave_run_triggers_for_rbr.saved = @@slave_run_triggers_for_rbr;
+set global slave_run_triggers_for_rbr=1;
+create trigger tr2 before insert on t1 for each row set new.b = new.a;
+connection master;
+insert into t1 values (1,10),(2,20),(3,30);
+connection slave;
+select * from t1;
+a b
+1 10
+2 20
+3 30
+#
+# Verify slave skips running triggers if master ran and logged the row events for triggers
+#
+create table t4(a int, b int);
+delete from tlog;
+create trigger tr4 before insert on t4 for each row insert into tlog values (null);
+insert into t4 values (1, 10),(2, 20);
+select * from tlog;
+a
+4
+5
+select * from t4;
+a b
+1 10
+2 20
+select * from tlog;
+a
+4
+5
+set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved;
+drop table t1, tlog, t4;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_savepoint.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_savepoint.result
new file mode 100644
index 00000000..5746119e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_savepoint.result
@@ -0,0 +1,110 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists t1;
+connection master;
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3);
+begin;
+insert into t1 values (11, 1);
+savepoint a;
+insert into t1 values (12, 1);
+rollback to savepoint a;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+commit;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+commit;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+connection master;
+begin;
+insert into t1 values (21, 1);
+savepoint a;
+insert into t1 values (22, 1);
+rollback to savepoint a;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+insert into t1 values (23, 1);
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+commit;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+commit;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+connection master;
+begin;
+insert into t1 values (31, 1);
+savepoint a;
+insert into t1 values (32, 1);
+savepoint b;
+insert into t1 values (33, 1);
+rollback to savepoint a;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+insert into t1 values (34, 1);
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+rollback;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+connection master;
+SET autocommit=off;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+SAVEPOINT A;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+SAVEPOINT A;
+insert into t1 values (35, 35);
+ROLLBACK TO SAVEPOINT A;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+START TRANSACTION;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+select * from t1;
+id value
+1 1
+2 2
+3 3
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+id value
+1 1
+2 2
+3 3
+connection master;
+drop table t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement.result
new file mode 100644
index 00000000..df1a6051
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement.result
@@ -0,0 +1,57 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists t1;
+connection master;
+select @@binlog_format;
+@@binlog_format
+STATEMENT
+create table t1 (pk int primary key) engine=rocksdb;
+insert into t1 values (1),(2),(3);
+ERROR HY000: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging.
+set session rocksdb_unsafe_for_binlog=on;
+insert into t1 values (1),(2),(3);
+select * from t1;
+pk
+1
+2
+3
+delete from t1;
+set session rocksdb_unsafe_for_binlog=off;
+insert into t1 values (1),(2),(3);
+ERROR HY000: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging.
+set binlog_format=row;
+insert into t1 values (1),(2),(3);
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+pk
+1
+2
+3
+connection master;
+drop table t1;
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,1,1);
+insert into t1 values (3,1,1);
+insert into t1 values (4,1,1);
+insert into t1 values (5,1,1);
+update t1 set value2=100 where id=1;
+update t1 set value2=200 where id=2;
+update t1 set value2=300 where id=3;
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1 where id=1;
+id value value2
+1 1 100
+select * from t1 where id=2;
+id value value2
+2 1 200
+select * from t1 where id=3;
+id value value2
+3 1 300
+connection master;
+drop table t1;
+set binlog_format=row;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement_not_found.result b/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement_not_found.result
new file mode 100644
index 00000000..9e71ffa7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rpl_statement_not_found.result
@@ -0,0 +1,70 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists t1;
+connection master;
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+pk int primary key,
+kp1 int,
+kp2 int,
+col1 int,
+key (kp1,kp2)
+) engine=rocksdb;
+set @tmp_binlog_format=@@binlog_format;
+set @@binlog_format=ROW;
+insert into t2 select a,a,a,a from t1;
+create table t3 like t2;
+insert into t3 select * from t2;
+set binlog_format=@tmp_binlog_format;
+include/sync_slave_sql_with_master.inc
+connection slave;
+set global debug_dbug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+connection master;
+update t2 set col1=100 where kp1 between 1 and 3 and mod(kp2,2)=0;
+connection slave;
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug_dbug = '';
+set sql_log_bin=0;
+delete from t2 where pk=2;
+delete from t2 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+connection master;
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t2 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 4
+connection slave;
+set global debug_dbug= 'd,dbug.rocksdb.get_row_by_rowid';
+include/stop_slave.inc
+include/start_slave.inc
+connection master;
+update t3 set col1=100 where kp1 between 1 and 4 and mod(kp2,2)=0;
+connection slave;
+call mtr.add_suppression("Deadlock found when trying to get lock");
+set debug_sync= 'now WAIT_FOR Reached';
+set global debug_dbug = '';
+set sql_log_bin=0;
+delete from t3 where pk=2;
+delete from t3 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+connection master;
+include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t3 where pk < 5;
+pk kp1 kp2 col1
+0 0 0 0
+1 1 1 1
+4 4 4 100
+set debug_sync='RESET';
+connection master;
+drop table t0, t1, t2, t3;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rqg_examples.result b/storage/rocksdb/mysql-test/rocksdb/r/rqg_examples.result
new file mode 100644
index 00000000..76679593
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rqg_examples.result
@@ -0,0 +1,3 @@
+CREATE DATABASE IF NOT EXISTS rqg_examples;
+Running test with grammar file example.yy
+DROP DATABASE rqg_examples;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rqg_runtime.result b/storage/rocksdb/mysql-test/rocksdb/r/rqg_runtime.result
new file mode 100644
index 00000000..1b872f82
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rqg_runtime.result
@@ -0,0 +1,30 @@
+call mtr.add_suppression("Did not write failed ");
+call mtr.add_suppression("Can't open and lock privilege tables");
+call mtr.add_suppression("Attempt to delete the trigger file");
+SET @ORIG_EVENT_SCHEDULER = @@EVENT_SCHEDULER;
+CREATE TABLE mysql.user_temp LIKE mysql.user;
+INSERT mysql.user_temp SELECT * FROM mysql.user;
+CREATE TABLE mysql.tables_priv_temp LIKE mysql.tables_priv;
+INSERT mysql.tables_priv_temp SELECT * FROM mysql.tables_priv_temp;
+CREATE DATABASE IF NOT EXISTS rqg_runtime;
+Running test with grammar file alter_online.yy
+DROP DATABASE rqg_runtime;
+CREATE DATABASE IF NOT EXISTS rqg_runtime;
+Running test with grammar file concurrency_1.yy
+DROP DATABASE rqg_runtime;
+CREATE DATABASE IF NOT EXISTS rqg_runtime;
+Running test with grammar file connect_kill_sql.yy
+DROP DATABASE rqg_runtime;
+CREATE DATABASE IF NOT EXISTS rqg_runtime;
+Running test with grammar file metadata_stability.yy
+DROP DATABASE rqg_runtime;
+DELETE FROM mysql.tables_priv;
+DELETE FROM mysql.user;
+INSERT mysql.user SELECT * FROM mysql.user_temp;
+INSERT mysql.tables_priv SELECT * FROM mysql.tables_priv_temp;
+DROP TABLE mysql.user_temp;
+DROP TABLE mysql.tables_priv_temp;
+DROP TABLE IF EXISTS test.executors;
+DROP DATABASE IF EXISTS testdb_N;
+DROP DATABASE IF EXISTS testdb_S;
+SET GLOBAL EVENT_SCHEDULER = @ORIG_EVENT_SCHEDULER;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/rqg_transactions.result b/storage/rocksdb/mysql-test/rocksdb/r/rqg_transactions.result
new file mode 100644
index 00000000..23705d49
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/rqg_transactions.result
@@ -0,0 +1,11 @@
+call mtr.add_suppression("Deadlock found when trying to get lock");
+CREATE DATABASE IF NOT EXISTS rqg_transactions;
+Running test with grammar file transactions.yy
+Running test with grammar file repeatable_read.yy
+Running test with grammar file transaction_durability.yy
+Running test with grammar file transactions-flat.yy
+Running test with grammar file combinations.yy
+Running test with grammar file repeatable_read.yy
+Running test with grammar file transaction_durability.yy
+Running test with grammar file transactions-flat.yy
+DROP DATABASE rqg_transactions;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/secondary_key_update_lock.result b/storage/rocksdb/mysql-test/rocksdb/r/secondary_key_update_lock.result
new file mode 100644
index 00000000..14217824
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/secondary_key_update_lock.result
@@ -0,0 +1,18 @@
+connect con, localhost, root,,;
+connection default;
+create table t1 (a int primary key, b int unique key) engine = rocksdb;
+insert into t1 values(1, 1);
+connection con;
+begin;
+update t1 set b = 2 where b = 1;
+connection default;
+insert into t1 values(2, 1);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con;
+rollback;
+select * from t1;
+a b
+1 1
+connection default;
+drop table t1;
+disconnect con;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/select.result b/storage/rocksdb/mysql-test/rocksdb/r/select.result
new file mode 100644
index 00000000..7ea43adc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/select.result
@@ -0,0 +1,380 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'foobar'),(1,'z'),(200,'bar');
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (a,b) SELECT a, b FROM t1;
+INSERT INTO t1 (a,b) SELECT a, b FROM t2;
+SELECT * FROM t1;
+a b pk
+1 z 2
+1 z 5
+100 foobar 1
+100 foobar 4
+200 bar 3
+200 bar 6
+SELECT DISTINCT a FROM t1;
+a
+1
+100
+200
+SELECT ALL b, a FROM t1;
+b a
+bar 200
+bar 200
+foobar 100
+foobar 100
+z 1
+z 1
+SELECT STRAIGHT_JOIN SQL_CACHE t1.* FROM t2, t1 WHERE t1.a <> t2.a;
+a b pk
+1 z 2
+1 z 2
+1 z 5
+1 z 5
+100 foobar 1
+100 foobar 1
+100 foobar 4
+100 foobar 4
+200 bar 3
+200 bar 3
+200 bar 6
+200 bar 6
+SELECT SQL_SMALL_RESULT SQL_NO_CACHE t1.a FROM t1, t2;
+a
+1
+1
+1
+1
+1
+1
+100
+100
+100
+100
+100
+100
+200
+200
+200
+200
+200
+200
+SELECT SQL_BIG_RESULT SQL_CALC_FOUND_ROWS DISTINCT(t2.a)
+FROM t1 t1_1, t2, t1 t1_2;
+a
+1
+100
+200
+SELECT FOUND_ROWS();
+FOUND_ROWS()
+3
+SET @save_query_cache=@@global.query_cache_size;
+SET GLOBAL query_cache_size = 1024*1024;
+SELECT SQL_CACHE * FROM t1, t2;
+a b pk a b pk
+1 z 2 1 z 2
+1 z 2 100 foobar 1
+1 z 2 200 bar 3
+1 z 5 1 z 2
+1 z 5 100 foobar 1
+1 z 5 200 bar 3
+100 foobar 1 1 z 2
+100 foobar 1 100 foobar 1
+100 foobar 1 200 bar 3
+100 foobar 4 1 z 2
+100 foobar 4 100 foobar 1
+100 foobar 4 200 bar 3
+200 bar 3 1 z 2
+200 bar 3 100 foobar 1
+200 bar 3 200 bar 3
+200 bar 6 1 z 2
+200 bar 6 100 foobar 1
+200 bar 6 200 bar 3
+SET @@global.query_cache_size=@save_query_cache;
+SELECT a+10 AS field1, CONCAT(b,':',b) AS field2 FROM t1
+WHERE b > 'b' AND a IS NOT NULL
+GROUP BY 2 DESC, field1 ASC
+HAVING field1 < 1000
+ORDER BY field2, 1 DESC, field1*2
+LIMIT 5 OFFSET 1;
+field1 field2
+11 z:z
+110 foobar:foobar
+SELECT SUM(a), MAX(a), b FROM t1 GROUP BY b WITH ROLLUP;
+SUM(a) MAX(a) b
+2 1 z
+200 100 foobar
+400 200 bar
+602 200 NULL
+SELECT * FROM t2 WHERE a>0 PROCEDURE ANALYSE();
+Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype
+test.t2.a 1 200 1 3 0 0 100.3333 81.2418 ENUM('1','100','200') NOT NULL
+test.t2.b bar z 1 6 0 0 3.3333 NULL ENUM('bar','foobar','z') NOT NULL
+test.t2.pk 1 3 1 1 0 0 2.0000 0.8165 ENUM('1','2','3') NOT NULL
+SELECT t1.a, t2.b FROM t2, t1 WHERE t1.a = t2.a ORDER BY t2.b, t1.a
+INTO OUTFILE '<DATADIR>/select.out'
+CHARACTER SET utf8
+FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '''';
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+200,'bar'
+200,'bar'
+100,'foobar'
+100,'foobar'
+1,'z'
+1,'z'
+SELECT t1.a, t2.b FROM t2, t1 WHERE t1.a = t2.a ORDER BY t2.b, t1.a
+INTO DUMPFILE '<DATADIR>/select.dump';
+ERROR 42000: Result consisted of more than one row
+SELECT t1.*, t2.* FROM t1, t2 ORDER BY t2.b, t1.a, t2.a, t1.b, t1.pk, t2.pk LIMIT 1
+INTO DUMPFILE '<DATADIR>/select.dump';
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+1z2200bar3
+SELECT MIN(a), MAX(a) FROM t1 INTO @min, @max;
+Warnings:
+Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead
+SELECT @min, @max;
+@min @max
+1 200
+SELECT t1_1.*, t2.* FROM t2, t1 AS t1_1, t1 AS t1_2
+WHERE t1_1.a = t1_2.a AND t2.a = t1_1.a;
+a b pk a b pk
+1 z 2 1 z 2
+1 z 2 1 z 2
+1 z 5 1 z 2
+1 z 5 1 z 2
+100 foobar 1 100 foobar 1
+100 foobar 1 100 foobar 1
+100 foobar 4 100 foobar 1
+100 foobar 4 100 foobar 1
+200 bar 3 200 bar 3
+200 bar 3 200 bar 3
+200 bar 6 200 bar 3
+200 bar 6 200 bar 3
+SELECT alias1.* FROM ( SELECT a,b FROM t1 ) alias1, t2 WHERE t2.a IN (100,200);
+a b
+1 z
+1 z
+1 z
+1 z
+100 foobar
+100 foobar
+100 foobar
+100 foobar
+200 bar
+200 bar
+200 bar
+200 bar
+SELECT t1.a FROM { OJ t1 LEFT OUTER JOIN t2 ON t1.a = t2.a+10 };
+a
+1
+1
+100
+100
+200
+200
+SELECT t1.* FROM t2 INNER JOIN t1;
+a b pk
+1 z 2
+1 z 2
+1 z 2
+1 z 5
+1 z 5
+1 z 5
+100 foobar 1
+100 foobar 1
+100 foobar 1
+100 foobar 4
+100 foobar 4
+100 foobar 4
+200 bar 3
+200 bar 3
+200 bar 3
+200 bar 6
+200 bar 6
+200 bar 6
+SELECT t1_2.* FROM t1 t1_1 CROSS JOIN t1 t1_2 ON t1_1.b = t1_2.b;
+a b pk
+1 z 2
+1 z 2
+1 z 5
+1 z 5
+100 foobar 1
+100 foobar 1
+100 foobar 4
+100 foobar 4
+200 bar 3
+200 bar 3
+200 bar 6
+200 bar 6
+SELECT t1.a, t2.b FROM t2 STRAIGHT_JOIN t1 WHERE t1.b > t2.b;
+a b
+1 bar
+1 bar
+1 foobar
+1 foobar
+100 bar
+100 bar
+SELECT t1.a, t2.b FROM t2 STRAIGHT_JOIN t1 ON t1.b > t2.b ORDER BY t1.a, t2.b;
+a b
+1 bar
+1 bar
+1 foobar
+1 foobar
+100 bar
+100 bar
+SELECT t2.* FROM t1 LEFT JOIN t2 USING (a) ORDER BY t2.a, t2.b LIMIT 1;
+a b pk
+1 z 2
+SELECT t2.* FROM t2 LEFT OUTER JOIN t1 ON t1.a = t2.a WHERE t1.a IS NOT NULL;
+a b pk
+1 z 2
+1 z 2
+100 foobar 1
+100 foobar 1
+200 bar 3
+200 bar 3
+SELECT SUM(t2.a) FROM t1 RIGHT JOIN t2 ON t2.b = t1.b;
+SUM(t2.a)
+602
+SELECT MIN(t2.a) FROM t1 RIGHT OUTER JOIN t2 USING (b,a);
+MIN(t2.a)
+1
+SELECT alias.b FROM t1 NATURAL JOIN ( SELECT a,b FROM t1 ) alias WHERE b > '';
+b
+bar
+bar
+bar
+bar
+foobar
+foobar
+foobar
+foobar
+z
+z
+z
+z
+SELECT t2.b FROM ( SELECT a,b FROM t1 ) alias NATURAL LEFT JOIN t2 WHERE b IS NOT NULL;
+b
+bar
+bar
+foobar
+foobar
+z
+z
+SELECT t1.*, t2.* FROM t1 NATURAL LEFT OUTER JOIN t2;
+a b pk a b pk
+1 z 2 1 z 2
+1 z 5 NULL NULL NULL
+100 foobar 1 100 foobar 1
+100 foobar 4 NULL NULL NULL
+200 bar 3 200 bar 3
+200 bar 6 NULL NULL NULL
+SELECT t2_2.* FROM t2 t2_1 NATURAL RIGHT JOIN t2 t2_2 WHERE t2_1.a IN ( SELECT a FROM t1 );
+a b pk
+1 z 2
+100 foobar 1
+200 bar 3
+SELECT t1_2.b FROM t1 t1_1 NATURAL RIGHT OUTER JOIN t1 t1_2 INNER JOIN t2;
+b
+bar
+bar
+bar
+bar
+bar
+bar
+foobar
+foobar
+foobar
+foobar
+foobar
+foobar
+z
+z
+z
+z
+z
+z
+SELECT ( SELECT MIN(a) FROM ( SELECT a,b FROM t1 ) alias1 ) AS min_a FROM t2;
+min_a
+1
+1
+1
+SELECT a,b FROM t2 WHERE a = ( SELECT MIN(a) FROM t1 );
+a b
+1 z
+SELECT a,b FROM t2 WHERE b LIKE ( SELECT b FROM t1 ORDER BY b LIMIT 1 );
+a b
+200 bar
+SELECT t2.* FROM t1 t1_outer, t2 WHERE ( t1_outer.a, t2.b ) IN ( SELECT a, b FROM t2 WHERE a = t1_outer.a );
+a b pk
+1 z 2
+1 z 2
+100 foobar 1
+100 foobar 1
+200 bar 3
+200 bar 3
+SELECT a,b FROM t2 WHERE b = ANY ( SELECT b FROM t1 WHERE a > 1 );
+a b
+100 foobar
+200 bar
+SELECT a,b FROM t2 WHERE b > ALL ( SELECT b FROM t1 WHERE b < 'foo' );
+a b
+1 z
+100 foobar
+SELECT a,b FROM t1 WHERE ROW(a, b) = ( SELECT a, b FROM t2 ORDER BY a, b LIMIT 1 );
+a b
+1 z
+1 z
+SELECT a,b FROM t1 WHERE EXISTS ( SELECT a,b FROM t2 WHERE t2.b > t1.b );
+a b
+100 foobar
+100 foobar
+200 bar
+200 bar
+SELECT t1.* FROM t1, t2 ORDER BY ( SELECT b FROM t1 WHERE a IS NULL ORDER BY b LIMIT 1 ) DESC;
+a b pk
+1 z 2
+1 z 2
+1 z 2
+1 z 5
+1 z 5
+1 z 5
+100 foobar 1
+100 foobar 1
+100 foobar 1
+100 foobar 4
+100 foobar 4
+100 foobar 4
+200 bar 3
+200 bar 3
+200 bar 3
+200 bar 6
+200 bar 6
+200 bar 6
+SELECT a, b FROM t1 HAVING a IN ( SELECT a FROM t2 WHERE b = t1.b );
+a b
+1 z
+1 z
+100 foobar
+100 foobar
+200 bar
+200 bar
+SELECT a,b FROM t1 UNION SELECT a,b FROM t2 UNION DISTINCT SELECT a,b FROM t1;
+a b
+1 z
+100 foobar
+200 bar
+SELECT a,b FROM t1 UNION SELECT a,b FROM t2 UNION ALL SELECT a,b FROM t1;
+a b
+1 z
+1 z
+1 z
+100 foobar
+100 foobar
+100 foobar
+200 bar
+200 bar
+200 bar
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/select_for_update.result b/storage/rocksdb/mysql-test/rocksdb/r/select_for_update.result
new file mode 100644
index 00000000..2890941a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/select_for_update.result
@@ -0,0 +1,35 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+connect con1,localhost,root,,;
+BEGIN;
+SELECT a,b FROM t1 WHERE b='a' FOR UPDATE;
+a b
+1 a
+3 a
+connection default;
+SET lock_wait_timeout = 1;
+SELECT a,b FROM t1 WHERE b='a';
+a b
+1 a
+3 a
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t1 SET b='c' WHERE b='a';
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+3 a
+disconnect con1;
+connection default;
+UPDATE t1 SET b='c' WHERE b='a';
+SELECT a,b FROM t1;
+a b
+1 c
+2 b
+3 c
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/select_for_update_skip_locked_nowait.result b/storage/rocksdb/mysql-test/rocksdb/r/select_for_update_skip_locked_nowait.result
new file mode 100644
index 00000000..044aa4d6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/select_for_update_skip_locked_nowait.result
@@ -0,0 +1,28 @@
+drop table if exists t1;
+create table t1 (a int primary key) engine=rocksdb;
+insert into t1 values (1), (2), (3);
+Should succeed since no table gets involved
+select 1 for update skip locked;
+1
+1
+select * from nonexistence for update skip locked;
+ERROR 42S02: Table 'test.nonexistence' doesn't exist
+select * from t1 for update skip locked;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+select * from t1 where a > 1 and a < 3 for update skip locked;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+insert into t1 select * from t1 for update skip locked;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+Should succeed since no table gets involved
+select 1 for update nowait;
+1
+1
+select * from nonexistence for update nowait;
+ERROR 42S02: Table 'test.nonexistence' doesn't exist
+select * from t1 for update nowait;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+select * from t1 where a > 1 and a < 3 for update nowait;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+insert into t1 select * from t1 for update nowait;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/select_lock_in_share_mode.result b/storage/rocksdb/mysql-test/rocksdb/r/select_lock_in_share_mode.result
new file mode 100644
index 00000000..b073b887
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/select_lock_in_share_mode.result
@@ -0,0 +1,37 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+connect con1,localhost,root,,;
+BEGIN;
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+a b
+1 a
+3 a
+connection default;
+SET lock_wait_timeout = 1;
+SELECT a,b FROM t1 WHERE b='a';
+a b
+1 a
+3 a
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+a b
+1 a
+3 a
+UPDATE t1 SET b='c' WHERE b='a';
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+COMMIT;
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+3 a
+disconnect con1;
+connection default;
+UPDATE t1 SET b='c' WHERE b='a';
+SELECT a,b FROM t1;
+a b
+1 c
+2 b
+3 c
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/show_engine.result b/storage/rocksdb/mysql-test/rocksdb/r/show_engine.result
new file mode 100644
index 00000000..eac329a2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/show_engine.result
@@ -0,0 +1,491 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+CREATE TABLE t2 (j INT, PRIMARY KEY (j) COMMENT 'rev:cf_t2') ENGINE = ROCKSDB;
+CREATE TABLE t3 (k INT, PRIMARY KEY (k) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+CREATE TABLE t4 (l INT, PRIMARY KEY (l) COMMENT 'cf_t4') ENGINE = ROCKSDB
+PARTITION BY KEY(l) PARTITIONS 4;
+SET @save.rocksdb_max_background_jobs= @@global.rocksdb_max_background_jobs;
+SET GLOBAL rocksdb_max_background_jobs= 1;
+INSERT INTO t1 VALUES (1), (2), (3);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+3
+INSERT INTO t2 VALUES (1), (2), (3), (4);
+SELECT COUNT(*) FROM t2;
+COUNT(*)
+4
+INSERT INTO t4 VALUES (1), (2), (3), (4), (5);
+SELECT COUNT(*) FROM t4;
+COUNT(*)
+5
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+SET GLOBAL rocksdb_compact_cf="cf_t1";
+SHOW ENGINE rocksdb STATUS;
+Type Name Status
+STATISTICS # #
+DBSTATS # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+MEMORY_STATS # #
+BG_THREADS # #
+BG_THREADS # #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_CFSTATS;
+CF_NAME STAT_TYPE VALUE
+__system__ NUM_IMMUTABLE_MEM_TABLE #
+__system__ MEM_TABLE_FLUSH_PENDING #
+__system__ COMPACTION_PENDING #
+__system__ CUR_SIZE_ACTIVE_MEM_TABLE #
+__system__ CUR_SIZE_ALL_MEM_TABLES #
+__system__ NUM_ENTRIES_ACTIVE_MEM_TABLE #
+__system__ NUM_ENTRIES_IMM_MEM_TABLES #
+__system__ NON_BLOCK_CACHE_SST_MEM_USAGE #
+__system__ NUM_LIVE_VERSIONS #
+cf_t1 NUM_IMMUTABLE_MEM_TABLE #
+cf_t1 MEM_TABLE_FLUSH_PENDING #
+cf_t1 COMPACTION_PENDING #
+cf_t1 CUR_SIZE_ACTIVE_MEM_TABLE #
+cf_t1 CUR_SIZE_ALL_MEM_TABLES #
+cf_t1 NUM_ENTRIES_ACTIVE_MEM_TABLE #
+cf_t1 NUM_ENTRIES_IMM_MEM_TABLES #
+cf_t1 NON_BLOCK_CACHE_SST_MEM_USAGE #
+cf_t1 NUM_LIVE_VERSIONS #
+default NUM_IMMUTABLE_MEM_TABLE #
+default MEM_TABLE_FLUSH_PENDING #
+default COMPACTION_PENDING #
+default CUR_SIZE_ACTIVE_MEM_TABLE #
+default CUR_SIZE_ALL_MEM_TABLES #
+default NUM_ENTRIES_ACTIVE_MEM_TABLE #
+default NUM_ENTRIES_IMM_MEM_TABLES #
+default NON_BLOCK_CACHE_SST_MEM_USAGE #
+default NUM_LIVE_VERSIONS #
+rev:cf_t2 NUM_IMMUTABLE_MEM_TABLE #
+rev:cf_t2 MEM_TABLE_FLUSH_PENDING #
+rev:cf_t2 COMPACTION_PENDING #
+rev:cf_t2 CUR_SIZE_ACTIVE_MEM_TABLE #
+rev:cf_t2 CUR_SIZE_ALL_MEM_TABLES #
+rev:cf_t2 NUM_ENTRIES_ACTIVE_MEM_TABLE #
+rev:cf_t2 NUM_ENTRIES_IMM_MEM_TABLES #
+rev:cf_t2 NON_BLOCK_CACHE_SST_MEM_USAGE #
+rev:cf_t2 NUM_LIVE_VERSIONS #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_DBSTATS;
+STAT_TYPE VALUE
+DB_BACKGROUND_ERRORS #
+DB_NUM_SNAPSHOTS #
+DB_OLDEST_SNAPSHOT_TIME #
+DB_BLOCK_CACHE_USAGE #
+SELECT TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME, COUNT(STAT_TYPE)
+FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_SCHEMA = 'test'
+GROUP BY TABLE_NAME, PARTITION_NAME;
+TABLE_SCHEMA TABLE_NAME PARTITION_NAME COUNT(STAT_TYPE)
+test t1 NULL 57
+test t2 NULL 57
+test t4 p0 57
+test t4 p1 57
+test t4 p2 57
+test t4 p3 57
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_CF_OPTIONS;
+CF_NAME OPTION_TYPE VALUE
+__system__ COMPARATOR #
+__system__ MERGE_OPERATOR #
+__system__ COMPACTION_FILTER #
+__system__ COMPACTION_FILTER_FACTORY #
+__system__ WRITE_BUFFER_SIZE #
+__system__ MAX_WRITE_BUFFER_NUMBER #
+__system__ MIN_WRITE_BUFFER_NUMBER_TO_MERGE #
+__system__ NUM_LEVELS #
+__system__ LEVEL0_FILE_NUM_COMPACTION_TRIGGER #
+__system__ LEVEL0_SLOWDOWN_WRITES_TRIGGER #
+__system__ LEVEL0_STOP_WRITES_TRIGGER #
+__system__ MAX_MEM_COMPACTION_LEVEL #
+__system__ TARGET_FILE_SIZE_BASE #
+__system__ TARGET_FILE_SIZE_MULTIPLIER #
+__system__ MAX_BYTES_FOR_LEVEL_BASE #
+__system__ LEVEL_COMPACTION_DYNAMIC_LEVEL_BYTES #
+__system__ MAX_BYTES_FOR_LEVEL_MULTIPLIER #
+__system__ SOFT_RATE_LIMIT #
+__system__ HARD_RATE_LIMIT #
+__system__ RATE_LIMIT_DELAY_MAX_MILLISECONDS #
+__system__ ARENA_BLOCK_SIZE #
+__system__ DISABLE_AUTO_COMPACTIONS #
+__system__ PURGE_REDUNDANT_KVS_WHILE_FLUSH #
+__system__ MAX_SEQUENTIAL_SKIP_IN_ITERATIONS #
+__system__ MEMTABLE_FACTORY #
+__system__ INPLACE_UPDATE_SUPPORT #
+__system__ INPLACE_UPDATE_NUM_LOCKS #
+__system__ MEMTABLE_PREFIX_BLOOM_BITS_RATIO #
+__system__ MEMTABLE_PREFIX_BLOOM_HUGE_PAGE_TLB_SIZE #
+__system__ BLOOM_LOCALITY #
+__system__ MAX_SUCCESSIVE_MERGES #
+__system__ OPTIMIZE_FILTERS_FOR_HITS #
+__system__ MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL #
+__system__ COMPRESSION_TYPE #
+__system__ COMPRESSION_PER_LEVEL #
+__system__ COMPRESSION_OPTS #
+__system__ BOTTOMMOST_COMPRESSION #
+__system__ PREFIX_EXTRACTOR #
+__system__ COMPACTION_STYLE #
+__system__ COMPACTION_OPTIONS_UNIVERSAL #
+__system__ COMPACTION_OPTION_FIFO::MAX_TABLE_FILES_SIZE #
+__system__ TABLE_FACTORY::FLUSH_BLOCK_POLICY_FACTORY #
+__system__ TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS #
+__system__ TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS_WITH_HIGH_PRIORITY #
+__system__ TABLE_FACTORY::PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE #
+__system__ TABLE_FACTORY::PIN_TOP_LEVEL_INDEX_AND_FILTER #
+__system__ TABLE_FACTORY::INDEX_TYPE #
+__system__ TABLE_FACTORY::DATA_BLOCK_INDEX_TYPE #
+__system__ TABLE_FACTORY::INDEX_SHORTENING #
+__system__ TABLE_FACTORY::DATA_BLOCK_HASH_TABLE_UTIL_RATIO #
+__system__ TABLE_FACTORY::HASH_INDEX_ALLOW_COLLISION #
+__system__ TABLE_FACTORY::CHECKSUM #
+__system__ TABLE_FACTORY::NO_BLOCK_CACHE #
+__system__ TABLE_FACTORY::BLOCK_CACHE #
+__system__ TABLE_FACTORY::BLOCK_CACHE_NAME #
+__system__ TABLE_FACTORY::BLOCK_CACHE_OPTIONS #
+__system__ TABLE_FACTORY::CAPACITY #
+__system__ TABLE_FACTORY::NUM_SHARD_BITS #
+__system__ TABLE_FACTORY::STRICT_CAPACITY_LIMIT #
+__system__ TABLE_FACTORY::MEMORY_ALLOCATOR #
+__system__ TABLE_FACTORY::HIGH_PRI_POOL_RATIO #
+__system__ TABLE_FACTORY::BLOCK_CACHE_COMPRESSED #
+__system__ TABLE_FACTORY::PERSISTENT_CACHE #
+__system__ TABLE_FACTORY::BLOCK_SIZE #
+__system__ TABLE_FACTORY::BLOCK_SIZE_DEVIATION #
+__system__ TABLE_FACTORY::BLOCK_RESTART_INTERVAL #
+__system__ TABLE_FACTORY::INDEX_BLOCK_RESTART_INTERVAL #
+__system__ TABLE_FACTORY::METADATA_BLOCK_SIZE #
+__system__ TABLE_FACTORY::PARTITION_FILTERS #
+__system__ TABLE_FACTORY::USE_DELTA_ENCODING #
+__system__ TABLE_FACTORY::FILTER_POLICY #
+__system__ TABLE_FACTORY::WHOLE_KEY_FILTERING #
+__system__ TABLE_FACTORY::VERIFY_COMPRESSION #
+__system__ TABLE_FACTORY::READ_AMP_BYTES_PER_BIT #
+__system__ TABLE_FACTORY::FORMAT_VERSION #
+__system__ TABLE_FACTORY::ENABLE_INDEX_COMPRESSION #
+__system__ TABLE_FACTORY::BLOCK_ALIGN #
+cf_t1 COMPARATOR #
+cf_t1 MERGE_OPERATOR #
+cf_t1 COMPACTION_FILTER #
+cf_t1 COMPACTION_FILTER_FACTORY #
+cf_t1 WRITE_BUFFER_SIZE #
+cf_t1 MAX_WRITE_BUFFER_NUMBER #
+cf_t1 MIN_WRITE_BUFFER_NUMBER_TO_MERGE #
+cf_t1 NUM_LEVELS #
+cf_t1 LEVEL0_FILE_NUM_COMPACTION_TRIGGER #
+cf_t1 LEVEL0_SLOWDOWN_WRITES_TRIGGER #
+cf_t1 LEVEL0_STOP_WRITES_TRIGGER #
+cf_t1 MAX_MEM_COMPACTION_LEVEL #
+cf_t1 TARGET_FILE_SIZE_BASE #
+cf_t1 TARGET_FILE_SIZE_MULTIPLIER #
+cf_t1 MAX_BYTES_FOR_LEVEL_BASE #
+cf_t1 LEVEL_COMPACTION_DYNAMIC_LEVEL_BYTES #
+cf_t1 MAX_BYTES_FOR_LEVEL_MULTIPLIER #
+cf_t1 SOFT_RATE_LIMIT #
+cf_t1 HARD_RATE_LIMIT #
+cf_t1 RATE_LIMIT_DELAY_MAX_MILLISECONDS #
+cf_t1 ARENA_BLOCK_SIZE #
+cf_t1 DISABLE_AUTO_COMPACTIONS #
+cf_t1 PURGE_REDUNDANT_KVS_WHILE_FLUSH #
+cf_t1 MAX_SEQUENTIAL_SKIP_IN_ITERATIONS #
+cf_t1 MEMTABLE_FACTORY #
+cf_t1 INPLACE_UPDATE_SUPPORT #
+cf_t1 INPLACE_UPDATE_NUM_LOCKS #
+cf_t1 MEMTABLE_PREFIX_BLOOM_BITS_RATIO #
+cf_t1 MEMTABLE_PREFIX_BLOOM_HUGE_PAGE_TLB_SIZE #
+cf_t1 BLOOM_LOCALITY #
+cf_t1 MAX_SUCCESSIVE_MERGES #
+cf_t1 OPTIMIZE_FILTERS_FOR_HITS #
+cf_t1 MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL #
+cf_t1 COMPRESSION_TYPE #
+cf_t1 COMPRESSION_PER_LEVEL #
+cf_t1 COMPRESSION_OPTS #
+cf_t1 BOTTOMMOST_COMPRESSION #
+cf_t1 PREFIX_EXTRACTOR #
+cf_t1 COMPACTION_STYLE #
+cf_t1 COMPACTION_OPTIONS_UNIVERSAL #
+cf_t1 COMPACTION_OPTION_FIFO::MAX_TABLE_FILES_SIZE #
+cf_t1 TABLE_FACTORY::FLUSH_BLOCK_POLICY_FACTORY #
+cf_t1 TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS #
+cf_t1 TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS_WITH_HIGH_PRIORITY #
+cf_t1 TABLE_FACTORY::PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE #
+cf_t1 TABLE_FACTORY::PIN_TOP_LEVEL_INDEX_AND_FILTER #
+cf_t1 TABLE_FACTORY::INDEX_TYPE #
+cf_t1 TABLE_FACTORY::DATA_BLOCK_INDEX_TYPE #
+cf_t1 TABLE_FACTORY::INDEX_SHORTENING #
+cf_t1 TABLE_FACTORY::DATA_BLOCK_HASH_TABLE_UTIL_RATIO #
+cf_t1 TABLE_FACTORY::HASH_INDEX_ALLOW_COLLISION #
+cf_t1 TABLE_FACTORY::CHECKSUM #
+cf_t1 TABLE_FACTORY::NO_BLOCK_CACHE #
+cf_t1 TABLE_FACTORY::BLOCK_CACHE #
+cf_t1 TABLE_FACTORY::BLOCK_CACHE_NAME #
+cf_t1 TABLE_FACTORY::BLOCK_CACHE_OPTIONS #
+cf_t1 TABLE_FACTORY::CAPACITY #
+cf_t1 TABLE_FACTORY::NUM_SHARD_BITS #
+cf_t1 TABLE_FACTORY::STRICT_CAPACITY_LIMIT #
+cf_t1 TABLE_FACTORY::MEMORY_ALLOCATOR #
+cf_t1 TABLE_FACTORY::HIGH_PRI_POOL_RATIO #
+cf_t1 TABLE_FACTORY::BLOCK_CACHE_COMPRESSED #
+cf_t1 TABLE_FACTORY::PERSISTENT_CACHE #
+cf_t1 TABLE_FACTORY::BLOCK_SIZE #
+cf_t1 TABLE_FACTORY::BLOCK_SIZE_DEVIATION #
+cf_t1 TABLE_FACTORY::BLOCK_RESTART_INTERVAL #
+cf_t1 TABLE_FACTORY::INDEX_BLOCK_RESTART_INTERVAL #
+cf_t1 TABLE_FACTORY::METADATA_BLOCK_SIZE #
+cf_t1 TABLE_FACTORY::PARTITION_FILTERS #
+cf_t1 TABLE_FACTORY::USE_DELTA_ENCODING #
+cf_t1 TABLE_FACTORY::FILTER_POLICY #
+cf_t1 TABLE_FACTORY::WHOLE_KEY_FILTERING #
+cf_t1 TABLE_FACTORY::VERIFY_COMPRESSION #
+cf_t1 TABLE_FACTORY::READ_AMP_BYTES_PER_BIT #
+cf_t1 TABLE_FACTORY::FORMAT_VERSION #
+cf_t1 TABLE_FACTORY::ENABLE_INDEX_COMPRESSION #
+cf_t1 TABLE_FACTORY::BLOCK_ALIGN #
+default COMPARATOR #
+default MERGE_OPERATOR #
+default COMPACTION_FILTER #
+default COMPACTION_FILTER_FACTORY #
+default WRITE_BUFFER_SIZE #
+default MAX_WRITE_BUFFER_NUMBER #
+default MIN_WRITE_BUFFER_NUMBER_TO_MERGE #
+default NUM_LEVELS #
+default LEVEL0_FILE_NUM_COMPACTION_TRIGGER #
+default LEVEL0_SLOWDOWN_WRITES_TRIGGER #
+default LEVEL0_STOP_WRITES_TRIGGER #
+default MAX_MEM_COMPACTION_LEVEL #
+default TARGET_FILE_SIZE_BASE #
+default TARGET_FILE_SIZE_MULTIPLIER #
+default MAX_BYTES_FOR_LEVEL_BASE #
+default LEVEL_COMPACTION_DYNAMIC_LEVEL_BYTES #
+default MAX_BYTES_FOR_LEVEL_MULTIPLIER #
+default SOFT_RATE_LIMIT #
+default HARD_RATE_LIMIT #
+default RATE_LIMIT_DELAY_MAX_MILLISECONDS #
+default ARENA_BLOCK_SIZE #
+default DISABLE_AUTO_COMPACTIONS #
+default PURGE_REDUNDANT_KVS_WHILE_FLUSH #
+default MAX_SEQUENTIAL_SKIP_IN_ITERATIONS #
+default MEMTABLE_FACTORY #
+default INPLACE_UPDATE_SUPPORT #
+default INPLACE_UPDATE_NUM_LOCKS #
+default MEMTABLE_PREFIX_BLOOM_BITS_RATIO #
+default MEMTABLE_PREFIX_BLOOM_HUGE_PAGE_TLB_SIZE #
+default BLOOM_LOCALITY #
+default MAX_SUCCESSIVE_MERGES #
+default OPTIMIZE_FILTERS_FOR_HITS #
+default MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL #
+default COMPRESSION_TYPE #
+default COMPRESSION_PER_LEVEL #
+default COMPRESSION_OPTS #
+default BOTTOMMOST_COMPRESSION #
+default PREFIX_EXTRACTOR #
+default COMPACTION_STYLE #
+default COMPACTION_OPTIONS_UNIVERSAL #
+default COMPACTION_OPTION_FIFO::MAX_TABLE_FILES_SIZE #
+default TABLE_FACTORY::FLUSH_BLOCK_POLICY_FACTORY #
+default TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS #
+default TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS_WITH_HIGH_PRIORITY #
+default TABLE_FACTORY::PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE #
+default TABLE_FACTORY::PIN_TOP_LEVEL_INDEX_AND_FILTER #
+default TABLE_FACTORY::INDEX_TYPE #
+default TABLE_FACTORY::DATA_BLOCK_INDEX_TYPE #
+default TABLE_FACTORY::INDEX_SHORTENING #
+default TABLE_FACTORY::DATA_BLOCK_HASH_TABLE_UTIL_RATIO #
+default TABLE_FACTORY::HASH_INDEX_ALLOW_COLLISION #
+default TABLE_FACTORY::CHECKSUM #
+default TABLE_FACTORY::NO_BLOCK_CACHE #
+default TABLE_FACTORY::BLOCK_CACHE #
+default TABLE_FACTORY::BLOCK_CACHE_NAME #
+default TABLE_FACTORY::BLOCK_CACHE_OPTIONS #
+default TABLE_FACTORY::CAPACITY #
+default TABLE_FACTORY::NUM_SHARD_BITS #
+default TABLE_FACTORY::STRICT_CAPACITY_LIMIT #
+default TABLE_FACTORY::MEMORY_ALLOCATOR #
+default TABLE_FACTORY::HIGH_PRI_POOL_RATIO #
+default TABLE_FACTORY::BLOCK_CACHE_COMPRESSED #
+default TABLE_FACTORY::PERSISTENT_CACHE #
+default TABLE_FACTORY::BLOCK_SIZE #
+default TABLE_FACTORY::BLOCK_SIZE_DEVIATION #
+default TABLE_FACTORY::BLOCK_RESTART_INTERVAL #
+default TABLE_FACTORY::INDEX_BLOCK_RESTART_INTERVAL #
+default TABLE_FACTORY::METADATA_BLOCK_SIZE #
+default TABLE_FACTORY::PARTITION_FILTERS #
+default TABLE_FACTORY::USE_DELTA_ENCODING #
+default TABLE_FACTORY::FILTER_POLICY #
+default TABLE_FACTORY::WHOLE_KEY_FILTERING #
+default TABLE_FACTORY::VERIFY_COMPRESSION #
+default TABLE_FACTORY::READ_AMP_BYTES_PER_BIT #
+default TABLE_FACTORY::FORMAT_VERSION #
+default TABLE_FACTORY::ENABLE_INDEX_COMPRESSION #
+default TABLE_FACTORY::BLOCK_ALIGN #
+rev:cf_t2 COMPARATOR #
+rev:cf_t2 MERGE_OPERATOR #
+rev:cf_t2 COMPACTION_FILTER #
+rev:cf_t2 COMPACTION_FILTER_FACTORY #
+rev:cf_t2 WRITE_BUFFER_SIZE #
+rev:cf_t2 MAX_WRITE_BUFFER_NUMBER #
+rev:cf_t2 MIN_WRITE_BUFFER_NUMBER_TO_MERGE #
+rev:cf_t2 NUM_LEVELS #
+rev:cf_t2 LEVEL0_FILE_NUM_COMPACTION_TRIGGER #
+rev:cf_t2 LEVEL0_SLOWDOWN_WRITES_TRIGGER #
+rev:cf_t2 LEVEL0_STOP_WRITES_TRIGGER #
+rev:cf_t2 MAX_MEM_COMPACTION_LEVEL #
+rev:cf_t2 TARGET_FILE_SIZE_BASE #
+rev:cf_t2 TARGET_FILE_SIZE_MULTIPLIER #
+rev:cf_t2 MAX_BYTES_FOR_LEVEL_BASE #
+rev:cf_t2 LEVEL_COMPACTION_DYNAMIC_LEVEL_BYTES #
+rev:cf_t2 MAX_BYTES_FOR_LEVEL_MULTIPLIER #
+rev:cf_t2 SOFT_RATE_LIMIT #
+rev:cf_t2 HARD_RATE_LIMIT #
+rev:cf_t2 RATE_LIMIT_DELAY_MAX_MILLISECONDS #
+rev:cf_t2 ARENA_BLOCK_SIZE #
+rev:cf_t2 DISABLE_AUTO_COMPACTIONS #
+rev:cf_t2 PURGE_REDUNDANT_KVS_WHILE_FLUSH #
+rev:cf_t2 MAX_SEQUENTIAL_SKIP_IN_ITERATIONS #
+rev:cf_t2 MEMTABLE_FACTORY #
+rev:cf_t2 INPLACE_UPDATE_SUPPORT #
+rev:cf_t2 INPLACE_UPDATE_NUM_LOCKS #
+rev:cf_t2 MEMTABLE_PREFIX_BLOOM_BITS_RATIO #
+rev:cf_t2 MEMTABLE_PREFIX_BLOOM_HUGE_PAGE_TLB_SIZE #
+rev:cf_t2 BLOOM_LOCALITY #
+rev:cf_t2 MAX_SUCCESSIVE_MERGES #
+rev:cf_t2 OPTIMIZE_FILTERS_FOR_HITS #
+rev:cf_t2 MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL #
+rev:cf_t2 COMPRESSION_TYPE #
+rev:cf_t2 COMPRESSION_PER_LEVEL #
+rev:cf_t2 COMPRESSION_OPTS #
+rev:cf_t2 BOTTOMMOST_COMPRESSION #
+rev:cf_t2 PREFIX_EXTRACTOR #
+rev:cf_t2 COMPACTION_STYLE #
+rev:cf_t2 COMPACTION_OPTIONS_UNIVERSAL #
+rev:cf_t2 COMPACTION_OPTION_FIFO::MAX_TABLE_FILES_SIZE #
+rev:cf_t2 TABLE_FACTORY::FLUSH_BLOCK_POLICY_FACTORY #
+rev:cf_t2 TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS #
+rev:cf_t2 TABLE_FACTORY::CACHE_INDEX_AND_FILTER_BLOCKS_WITH_HIGH_PRIORITY #
+rev:cf_t2 TABLE_FACTORY::PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE #
+rev:cf_t2 TABLE_FACTORY::PIN_TOP_LEVEL_INDEX_AND_FILTER #
+rev:cf_t2 TABLE_FACTORY::INDEX_TYPE #
+rev:cf_t2 TABLE_FACTORY::DATA_BLOCK_INDEX_TYPE #
+rev:cf_t2 TABLE_FACTORY::INDEX_SHORTENING #
+rev:cf_t2 TABLE_FACTORY::DATA_BLOCK_HASH_TABLE_UTIL_RATIO #
+rev:cf_t2 TABLE_FACTORY::HASH_INDEX_ALLOW_COLLISION #
+rev:cf_t2 TABLE_FACTORY::CHECKSUM #
+rev:cf_t2 TABLE_FACTORY::NO_BLOCK_CACHE #
+rev:cf_t2 TABLE_FACTORY::BLOCK_CACHE #
+rev:cf_t2 TABLE_FACTORY::BLOCK_CACHE_NAME #
+rev:cf_t2 TABLE_FACTORY::BLOCK_CACHE_OPTIONS #
+rev:cf_t2 TABLE_FACTORY::CAPACITY #
+rev:cf_t2 TABLE_FACTORY::NUM_SHARD_BITS #
+rev:cf_t2 TABLE_FACTORY::STRICT_CAPACITY_LIMIT #
+rev:cf_t2 TABLE_FACTORY::MEMORY_ALLOCATOR #
+rev:cf_t2 TABLE_FACTORY::HIGH_PRI_POOL_RATIO #
+rev:cf_t2 TABLE_FACTORY::BLOCK_CACHE_COMPRESSED #
+rev:cf_t2 TABLE_FACTORY::PERSISTENT_CACHE #
+rev:cf_t2 TABLE_FACTORY::BLOCK_SIZE #
+rev:cf_t2 TABLE_FACTORY::BLOCK_SIZE_DEVIATION #
+rev:cf_t2 TABLE_FACTORY::BLOCK_RESTART_INTERVAL #
+rev:cf_t2 TABLE_FACTORY::INDEX_BLOCK_RESTART_INTERVAL #
+rev:cf_t2 TABLE_FACTORY::METADATA_BLOCK_SIZE #
+rev:cf_t2 TABLE_FACTORY::PARTITION_FILTERS #
+rev:cf_t2 TABLE_FACTORY::USE_DELTA_ENCODING #
+rev:cf_t2 TABLE_FACTORY::FILTER_POLICY #
+rev:cf_t2 TABLE_FACTORY::WHOLE_KEY_FILTERING #
+rev:cf_t2 TABLE_FACTORY::VERIFY_COMPRESSION #
+rev:cf_t2 TABLE_FACTORY::READ_AMP_BYTES_PER_BIT #
+rev:cf_t2 TABLE_FACTORY::FORMAT_VERSION #
+rev:cf_t2 TABLE_FACTORY::ENABLE_INDEX_COMPRESSION #
+rev:cf_t2 TABLE_FACTORY::BLOCK_ALIGN #
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
+SHOW ENGINE rocksdb MUTEX;
+Type Name Status
+SHOW ENGINE ALL MUTEX;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+Type Name Status
+rocksdb
+============================================================
+TIMESTAMP ROCKSDB TRANSACTION MONITOR OUTPUT
+============================================================
+---------
+SNAPSHOTS
+---------
+LIST OF SNAPSHOTS FOR EACH SESSION:
+---SNAPSHOT, ACTIVE NUM sec
+MySQL thread id TID, OS thread handle PTR, query id QID localhost root ACTION
+SHOW ENGINE rocksdb TRANSACTION STATUS
+lock count 0, write count 0
+insert count 0, update count 0, delete count 0
+----------LATEST DETECTED DEADLOCKS----------
+-----------------------------------------
+END OF ROCKSDB TRANSACTION MONITOR OUTPUT
+=========================================
+
+ROLLBACK;
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+File Position Gtid_executed Snapshot_ID
+ 0 1
+SHOW ENGINE rocksdb STATUS;
+Type Name Status
+STATISTICS # #
+DBSTATS # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+MEMORY_STATS # #
+BG_THREADS # #
+BG_THREADS # #
+EXPLICIT_SNAPSHOTS # #
+ROLLBACK;
+CREATE EXPLICIT rocksdb SNAPSHOT;
+File Position Gtid_executed Snapshot_ID
+ 0 2
+SHOW ENGINE rocksdb STATUS;
+Type Name Status
+STATISTICS # #
+DBSTATS # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+MEMORY_STATS # #
+BG_THREADS # #
+BG_THREADS # #
+EXPLICIT_SNAPSHOTS # #
+RELEASE EXPLICIT rocksdb SNAPSHOT;
+File Position Gtid_executed Snapshot_ID
+ 0 2
+SHOW ENGINE rocksdb STATUS;
+Type Name Status
+STATISTICS # #
+DBSTATS # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+CF_COMPACTION # #
+MEMORY_STATS # #
+BG_THREADS # #
+BG_THREADS # #
+SET GLOBAL rocksdb_max_background_jobs= @save.rocksdb_max_background_jobs;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/show_table_status.result b/storage/rocksdb/mysql-test/rocksdb/r/show_table_status.result
new file mode 100644
index 00000000..139d323b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/show_table_status.result
@@ -0,0 +1,136 @@
+DROP TABLE IF EXISTS t1, t2, t3;
+CREATE TABLE t1 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'a'),(2,'foo');
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t2 (a,b) VALUES (1,'bar');
+set global rocksdb_force_flush_memtable_now = true;
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb CHARACTER SET utf8;
+SHOW TABLE STATUS WHERE name IN ( 't1', 't2', 't3' );
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed 1000 # # 0 0 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t2 ROCKSDB 10 Fixed 1000 # # 0 0 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+t3 ROCKSDB 10 Fixed 1000 # # 0 0 0 NULL # # NULL utf8_general_ci NULL 0 N
+SHOW TABLE STATUS WHERE name LIKE 't2';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t2 ROCKSDB 10 Fixed 1000 # # 0 0 0 NULL # # NULL latin1_swedish_ci NULL 0 N
+DROP TABLE t1, t2, t3;
+CREATE DATABASE `db_new..............................................end`;
+USE `db_new..............................................end`;
+CREATE TABLE `t1_new..............................................end`(a int) engine=rocksdb;
+INSERT INTO `t1_new..............................................end` VALUES (1);
+SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.table_statistics WHERE TABLE_NAME = 't1_new..............................................end';
+TABLE_SCHEMA db_new..............................................end
+TABLE_NAME t1_new..............................................end
+DROP DATABASE `db_new..............................................end`;
+#
+# MDEV-17171: Bug: RocksDB Tables do not have "Creation Date"
+#
+use test;
+create table t1 (a int) engine=rocksdb;
+select create_time is not null, update_time, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+create_time is not null update_time check_time
+1 NULL NULL
+insert into t1 values (1);
+select create_time is not null, update_time is not null, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+create_time is not null update_time is not null check_time
+1 1 NULL
+flush tables;
+select create_time is not null, update_time is not null, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+create_time is not null update_time is not null check_time
+1 1 NULL
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+select sleep(3);
+sleep(3)
+0
+insert into t1 values (2);
+select
+create_time=@create_tm /* should not change */ ,
+timestampdiff(second, @update_tm, update_time) > 2,
+check_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time=@create_tm 1
+timestampdiff(second, @update_tm, update_time) > 2 1
+check_time NULL
+#
+# Check how create_time survives ALTER TABLE.
+# First, an ALTER TABLE that re-creates the table:
+alter table t1 add b int;
+select
+create_time<>@create_tm /* should change */,
+create_time IS NOT NULL,
+update_time IS NULL
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time<>@create_tm 1
+create_time IS NOT NULL 1
+update_time IS NULL 1
+insert into t1 values (5,5);
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+# Then, an in-place ALTER TABLE:
+select sleep(2);
+sleep(2) 0
+alter table t1 add key (a);
+# create_time will change as .frm file is rewritten:
+select
+create_time=@create_tm,
+update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time=@create_tm 0
+update_time NULL
+# Check TRUNCATE TABLE
+insert into t1 values (10,10);
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+select sleep(2);
+sleep(2) 0
+truncate table t1;
+select
+create_time=@create_tm /* should not change */,
+update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time=@create_tm 1
+update_time NULL
+#
+# Check what is left after server restart
+#
+drop table t1;
+create table t1 (a int);
+insert into t1 values (1);
+# Save t1's creation time
+create table t2 as
+select create_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+select sleep(2);
+sleep(2) 0
+# restart
+select
+create_time=(select create_time from t2) /* should not change */,
+update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time=(select create_time from t2) 1
+update_time NULL
+drop table t1, t2;
+#
+# Check how it works for partitioned tables
+#
+create table t1 (pk int primary key) partition by hash(pk) partitions 2;
+insert into t1 values (1);
+select create_time IS NOT NULL , update_time IS NOT NULL
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+create_time IS NOT NULL 1
+update_time IS NOT NULL 1
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/shutdown.result b/storage/rocksdb/mysql-test/rocksdb/r/shutdown.result
new file mode 100644
index 00000000..f40aceff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/shutdown.result
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS t1;
+SHOW GLOBAL VARIABLES LIKE "log_bin";
+Variable_name Value
+log_bin ON
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/singledelete.result b/storage/rocksdb/mysql-test/rocksdb/r/singledelete.result
new file mode 100644
index 00000000..da29f325
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/singledelete.result
@@ -0,0 +1,86 @@
+CREATE TABLE t1 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end
+true
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+case when variable_value-@d < 10 then 'true' else 'false' end
+true
+CREATE TABLE t2 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+optimize table t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status OK
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end
+true
+select case when variable_value-@d > 9000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+case when variable_value-@d > 9000 then 'true' else 'false' end
+true
+CREATE TABLE t3 (id INT, value int, PRIMARY KEY (id)) ENGINE=RocksDB;
+INSERT INTO t3 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+optimize table t3;
+Table Op Msg_type Msg_text
+test.t3 optimize status OK
+select case when variable_value-@s = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s = 0 then 'true' else 'false' end
+true
+select case when variable_value-@d > 9000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+case when variable_value-@d > 9000 then 'true' else 'false' end
+true
+CREATE TABLE t4 (id INT, PRIMARY KEY (id)) ENGINE=RocksDB;
+INSERT INTO t4 VALUES (1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+optimize table t4;
+Table Op Msg_type Msg_text
+test.t4 optimize status OK
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end
+true
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+case when variable_value-@d < 10 then 'true' else 'false' end
+true
+CREATE TABLE t5 (id1 INT, id2 INT, PRIMARY KEY (id1, id2), INDEX(id2)) ENGINE=RocksDB;
+INSERT INTO t5 VALUES (1, 1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+optimize table t5;
+Table Op Msg_type Msg_text
+test.t5 optimize status OK
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end
+true
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+case when variable_value-@d < 10 then 'true' else 'false' end
+true
+CREATE TABLE t6 (
+pk VARCHAR(64) COLLATE latin1_swedish_ci PRIMARY KEY
+) ENGINE=RocksDB;
+INSERT INTO t6 VALUES ('a');
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t6;
+pk
+a
+UPDATE t6 SET pk='A' WHERE pk='a';
+SELECT * FROM t6;
+pk
+A
+DELETE FROM t6 where pk='A';
+SELECT should return nothing;
+SELECT * FROM t6;
+pk
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+SELECT should return nothing;
+SELECT * FROM t6;
+pk
+DROP TABLE t1, t2, t3, t4, t5, t6;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/skip_core_dump_on_error.result b/storage/rocksdb/mysql-test/rocksdb/r/skip_core_dump_on_error.result
new file mode 100644
index 00000000..60d9f69a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/skip_core_dump_on_error.result
@@ -0,0 +1,31 @@
+create table mz(c int);
+affected rows: 0
+insert into mz values(1);
+affected rows: 1
+commit;
+affected rows: 0
+SET debug= '+d,abort_with_io_write_error';
+affected rows: 0
+set global binlog_error_action=1;
+affected rows: 0
+show session variables like 'debug';
+Variable_name Value
+debug d,abort_with_io_write_error
+affected rows: 1
+show global variables like 'binlog_error_action';
+Variable_name Value
+binlog_error_action ABORT_SERVER
+affected rows: 1
+show global variables like 'skip_core_dump_on_error';
+Variable_name Value
+skip_core_dump_on_error ON
+affected rows: 1
+# crash_during_update
+update mz set c=13;
+ERROR HY000: Binary logging not possible. Message: An error occurred during sync stage of the commit. 'binlog_error_action' is set to 'ABORT_SERVER'. Hence aborting the server.
+# server aborted
+Pattern "mysqld got signal 6" found
+# but no core written
+Pattern "Writing a core file" not found
+drop table mz;
+affected rows: 0
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/skip_validate_tmp_table.result b/storage/rocksdb/mysql-test/rocksdb/r/skip_validate_tmp_table.result
new file mode 100644
index 00000000..d67c4cbb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/skip_validate_tmp_table.result
@@ -0,0 +1,22 @@
+create table t1 (pk int primary key) engine=rocksdb;
+# restart
+show tables;
+Tables_in_test
+#mysql50#t1#sql-test
+t1
+call mtr.add_suppression('Invalid .old.. table or database name .t1#sql-test.');
+set session debug_dbug="+d,gen_sql_table_name";
+rename table t1 to t2;
+set session debug_dbug= "-d,gen_sql_table_name";
+show tables;
+Tables_in_test
+#mysql50#t1#sql-test
+t2
+# restart
+show tables;
+Tables_in_test
+create table t2 (pk int primary key) engine=rocksdb;
+show tables;
+Tables_in_test
+t2
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/slow_query_log.result b/storage/rocksdb/mysql-test/rocksdb/r/slow_query_log.result
new file mode 100644
index 00000000..e8a11363
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/slow_query_log.result
@@ -0,0 +1,10 @@
+SET @cur_long_query_time = @@long_query_time;
+SET @@long_query_time = 600;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (id INT PRIMARY KEY, value INT) ENGINE=ROCKSDB;
+SET @@long_query_time = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+7500
+SET @@long_query_time = @cur_long_query_time;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/statistics.result b/storage/rocksdb/mysql-test/rocksdb/r/statistics.result
new file mode 100644
index 00000000..1d099352
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/statistics.result
@@ -0,0 +1,73 @@
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+create table t1(
+id bigint not null primary key auto_increment,
+a varchar(255) not null,
+b bigint,
+index t1_1(b)
+) engine=rocksdb;
+create table t2(
+id bigint not null primary key auto_increment,
+a varchar(255) not null,
+b bigint,
+index t2_1(b) comment 'cf_t3'
+) engine=rocksdb;
+create table t3(
+id bigint not null primary key auto_increment,
+a varchar(255) not null,
+b bigint,
+index t3_1(b) comment 'rev:cf_t4'
+) engine=rocksdb;
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE() and table_name <> 't1';
+table_name table_rows
+t2 1000
+t3 1000
+SELECT CASE WHEN table_rows < 100000 then 'true' else 'false' end from information_schema.tables where table_name = 't1';
+CASE WHEN table_rows < 100000 then 'true' else 'false' end
+true
+set global rocksdb_force_flush_memtable_now = true;
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name table_rows
+t1 1000
+t2 1000
+t3 1000
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name data_length>0 index_length>0
+t1 1 1
+t2 1 1
+t3 1 1
+# restart
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name table_rows
+t1 1000
+t2 1000
+t3 1000
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name data_length>0 index_length>0
+t1 1 1
+t2 1 1
+t3 1 1
+analyze table t1,t2,t3,t4,t5;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+test.t2 analyze status Engine-independent statistics collected
+test.t2 analyze status OK
+test.t3 analyze status Engine-independent statistics collected
+test.t3 analyze status OK
+test.t4 analyze Error Table 'test.t4' doesn't exist
+test.t4 analyze status Operation failed
+test.t5 analyze Error Table 'test.t5' doesn't exist
+test.t5 analyze status Operation failed
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name table_rows
+t1 1000
+t2 1000
+t3 1000
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+table_name data_length>0 index_length>0
+t1 1 1
+t2 1 1
+t3 1 1
+drop table t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/table_stats.result b/storage/rocksdb/mysql-test/rocksdb/r/table_stats.result
new file mode 100644
index 00000000..31cb1b64
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/table_stats.result
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1000
+SELECT * FROM INFORMATION_SCHEMA.TABLE_STATISTICS WHERE TABLE_NAME = "t1";
+TABLE_SCHEMA test
+TABLE_NAME t1
+ROWS_READ 1000
+ROWS_CHANGED 1000
+ROWS_CHANGED_X_INDEXES 1000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result
new file mode 100644
index 00000000..7cc0cc7c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result
@@ -0,0 +1,38 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb AUTO_INCREMENT=10;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+a
+10
+ALTER TABLE t1 AUTO_INCREMENT=100;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=100 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1 ORDER BY a;
+a
+10
+100
+ALTER TABLE t1 AUTO_INCREMENT=50;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB AUTO_INCREMENT=101 DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1 ORDER BY a;
+a
+10
+100
+101
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_avg_row_length.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_avg_row_length.result
new file mode 100644
index 00000000..f904c04e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_avg_row_length.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb AVG_ROW_LENGTH=300;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 AVG_ROW_LENGTH=300
+ALTER TABLE t1 AVG_ROW_LENGTH=30000000;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 AVG_ROW_LENGTH=30000000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_checksum.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_checksum.result
new file mode 100644
index 00000000..d9cc69ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_checksum.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 CHECKSUM=1
+ALTER TABLE t1 CHECKSUM=0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_connection.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_connection.result
new file mode 100644
index 00000000..0beddd9f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_connection.result
@@ -0,0 +1,26 @@
+DROP TABLE IF EXISTS t1;
+CREATE DATABASE test_remote;
+CREATE SERVER test_connection FOREIGN DATA WRAPPER mysql
+OPTIONS (USER 'root', HOST 'localhost', DATABASE 'test_remote');
+CREATE SERVER test_connection2 FOREIGN DATA WRAPPER mysql
+OPTIONS (USER 'root', HOST 'localhost', DATABASE 'test_remote');
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CONNECTION='test_connection';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 CONNECTION='test_connection'
+ALTER TABLE t1 CONNECTION='test_connection2';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 CONNECTION='test_connection2'
+DROP TABLE t1;
+DROP SERVER test_connection;
+DROP SERVER test_connection2;
+DROP DATABASE test_remote;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_data_index_dir.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_data_index_dir.result
new file mode 100644
index 00000000..95dae68b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_data_index_dir.result
@@ -0,0 +1,41 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb DATA DIRECTORY = '/foo/bar/data';
+ERROR HY000: Can't create table `test`.`t1` (errno: 198 "Unknown error 198")
+show warnings;
+Level Code Message
+Error 1005 Can't create table `test`.`t1` (errno: 198 "Unknown error 198")
+Warning 1296 Got error 198 'Specifying DATA DIRECTORY for an individual table is not supported.' from ROCKSDB
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb INDEX DIRECTORY = '/foo/bar/index';
+ERROR HY000: Can't create table `test`.`t1` (errno: 199 "Unknown error 199")
+show warnings;
+Level Code Message
+Error 1005 Can't create table `test`.`t1` (errno: 199 "Unknown error 199")
+Warning 1296 Got error 199 'Specifying INDEX DIRECTORY for an individual table is not supported.' from ROCKSDB
+CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY) ENGINE=rocksdb PARTITION BY RANGE (id)
+(
+PARTITION P0 VALUES LESS THAN (1000)
+DATA DIRECTORY = '/foo/bar/data/',
+PARTITION P1 VALUES LESS THAN (2000)
+DATA DIRECTORY = '/foo/bar/data/',
+PARTITION P2 VALUES LESS THAN (MAXVALUE)
+);
+ERROR HY000: Can't create table `test`.`t1` (errno: 198 "Unknown error 198")
+show warnings;
+Level Code Message
+Error 1005 Can't create table `test`.`t1` (errno: 198 "Unknown error 198")
+Warning 1296 Got error 198 'Specifying DATA DIRECTORY for an individual table is not supported.' from ROCKSDB
+Error 6 Error on delete of './test/t1.par' (Errcode: 2 "No such file or directory")
+CREATE TABLE t1 (id int not null primary key) ENGINE=rocksdb PARTITION BY RANGE (id)
+(
+PARTITION P0 VALUES LESS THAN (1000)
+INDEX DIRECTORY = '/foo/bar/data/',
+PARTITION P1 VALUES LESS THAN (2000)
+INDEX DIRECTORY = '/foo/bar/data/',
+PARTITION P2 VALUES LESS THAN (MAXVALUE)
+);
+ERROR HY000: Can't create table `test`.`t1` (errno: 199 "Unknown error 199")
+show warnings;
+Level Code Message
+Error 1005 Can't create table `test`.`t1` (errno: 199 "Unknown error 199")
+Warning 1296 Got error 199 'Specifying INDEX DIRECTORY for an individual table is not supported.' from ROCKSDB
+Error 6 Error on delete of './test/t1.par' (Errcode: 2 "No such file or directory")
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_delay_key_write.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_delay_key_write.result
new file mode 100644
index 00000000..c5d1ad8a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_delay_key_write.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb DELAY_KEY_WRITE=1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 DELAY_KEY_WRITE=1
+ALTER TABLE t1 DELAY_KEY_WRITE=0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_insert_method.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_insert_method.result
new file mode 100644
index 00000000..bd5e65f5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_insert_method.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb INSERT_METHOD=FIRST;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 INSERT_METHOD=NO;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_key_block_size.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_key_block_size.result
new file mode 100644
index 00000000..6c34d08b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_key_block_size.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb KEY_BLOCK_SIZE=8;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 KEY_BLOCK_SIZE=8
+ALTER TABLE t1 KEY_BLOCK_SIZE=1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`) KEY_BLOCK_SIZE=8
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 KEY_BLOCK_SIZE=1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_max_rows.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_max_rows.result
new file mode 100644
index 00000000..679e00e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_max_rows.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb MAX_ROWS=10000000;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 MAX_ROWS=10000000
+ALTER TABLE t1 MAX_ROWS=30000000;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 MAX_ROWS=30000000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_min_rows.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_min_rows.result
new file mode 100644
index 00000000..bc650434
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_min_rows.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb MIN_ROWS=1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 MIN_ROWS=1
+ALTER TABLE t1 MIN_ROWS=10000;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 MIN_ROWS=10000
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_pack_keys.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_pack_keys.result
new file mode 100644
index 00000000..b42d3f4d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_pack_keys.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb PACK_KEYS=1;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 PACK_KEYS=1
+ALTER TABLE t1 PACK_KEYS=0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 PACK_KEYS=0
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_password.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_password.result
new file mode 100644
index 00000000..80ec7949
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_password.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb PASSWORD='password';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 PASSWORD='new_password';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_row_format.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_row_format.result
new file mode 100644
index 00000000..a0c480ea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_row_format.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb ROW_FORMAT=FIXED;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) NOT NULL,
+ PRIMARY KEY (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 ROW_FORMAT=FIXED
+ALTER TABLE t1 ROW_FORMAT=DYNAMIC;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) NOT NULL,
+ PRIMARY KEY (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_union.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_union.result
new file mode 100644
index 00000000..fb021dbd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_union.result
@@ -0,0 +1,16 @@
+DROP TABLE IF EXISTS t1, child1, child2;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb UNION(child1);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ALTER TABLE t1 UNION = (child1,child2);
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1, child1, child2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_standard_opts.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_standard_opts.result
new file mode 100644
index 00000000..ecfc7293
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_standard_opts.result
@@ -0,0 +1,46 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb
+DEFAULT CHARACTER SET = utf8
+COLLATE = utf8_general_ci
+COMMENT = 'standard table options'
+;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COMMENT='standard table options'
+ALTER TABLE t1 COMMENT = 'table altered';
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COMMENT='table altered'
+ALTER TABLE t1 ENGINE=MEMORY;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=MEMORY DEFAULT CHARSET=utf8 COMMENT='table altered'
+ALTER TABLE t1 ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COMMENT='table altered'
+ALTER TABLE t1 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` char(8) CHARACTER SET utf8 DEFAULT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COMMENT='table altered'
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tmpdir.result b/storage/rocksdb/mysql-test/rocksdb/r/tmpdir.result
new file mode 100644
index 00000000..4c1e0221
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/tmpdir.result
@@ -0,0 +1,32 @@
+# If rocksdb_tmpdir is NULL or "", temporary file will be created in
+# server configuration variable location(--tmpdir)
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+show session variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir
+# Connection con1
+connect con1,localhost,root;
+show session variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+connection default;
+disconnect con1;
+DROP TABLE t1;
+# rocksdb_tmpdir with valid location.
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+set @tmpdir = @@global.tmpdir;
+set global rocksdb_tmpdir = @tmpdir;
+show session variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir
+# Connection con3
+connect con2,localhost,root;
+show session variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir MYSQL_TMP_DIR/mysqld.1
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+disconnect con2;
+connection default;
+set global rocksdb_tmpdir=NULL;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/transaction.result b/storage/rocksdb/mysql-test/rocksdb/r/transaction.result
new file mode 100644
index 00000000..8a5825b9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/transaction.result
@@ -0,0 +1,977 @@
+create table t1 (id int primary key, value int, value2 varchar(100), index(value)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5),(6,6,6),(8,8,8),(9,9,9),(10,10,10);
+begin;
+insert into t1 values (11,11,11);
+select * from t1 where id=1;
+id value value2
+1 1 1
+select * from t1 where value=1;
+id value value2
+1 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+1 1 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+11 11 11
+select value from t1 order by id;
+value
+1
+2
+3
+4
+5
+6
+8
+9
+10
+11
+rollback;
+begin;
+insert into t1 values (7,7,7);
+select * from t1 where id=1;
+id value value2
+1 1 1
+select * from t1 where value=1;
+id value value2
+1 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+1 1 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+7 7 7
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+rollback;
+begin;
+update t1 set value2=100 where id=1;
+select * from t1 where id=1;
+id value value2
+1 1 100
+select * from t1 where value=1;
+id value value2
+1 1 100
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 100
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+1
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set value=100 where id=1;
+select * from t1 where id=1;
+id value value2
+1 100 1
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+1 100 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 100 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+100
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set id=100 where id=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+100 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+100 1 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select value from t1 where value < 3 order by id;
+value
+2
+1
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+100 1 1
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+1
+rollback;
+begin;
+update t1 set value2=100 where value=1;
+select * from t1 where id=1;
+id value value2
+1 1 100
+select * from t1 where value=1;
+id value value2
+1 1 100
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 100
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+1
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set value=100 where value=1;
+select * from t1 where id=1;
+id value value2
+1 100 1
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+1 100 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 100 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+100
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set id=100 where value=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+100 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+100 1 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select value from t1 where value < 3 order by id;
+value
+2
+1
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+100 1 1
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+1
+rollback;
+begin;
+update t1 set value2=100 where value2=1;
+select * from t1 where id=1;
+id value value2
+1 1 100
+select * from t1 where value=1;
+id value value2
+1 1 100
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 100
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 100
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+1
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set value=100 where value2=1;
+select * from t1 where id=1;
+id value value2
+1 100 1
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+1 100 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 100 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 100 1
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+100
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+update t1 set id=100 where value2=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+100 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+100 1 1
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select value from t1 where value < 3 order by id;
+value
+2
+1
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+100 1 1
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+100 1 1
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+1
+rollback;
+begin;
+delete from t1 where id=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+delete from t1 where value=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+delete from t1 where value2=1;
+select * from t1 where id=1;
+id value value2
+select * from t1 where value=1;
+id value value2
+select value from t1 where value=1;
+value
+select * from t1 where value2=1;
+id value value2
+select * from t1 where id=5;
+id value value2
+5 5 5
+select * from t1 where value=5;
+id value value2
+5 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+5 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+2 2 2
+select * from t1 order by id;
+id value value2
+2 2 2
+3 3 3
+4 4 4
+5 5 5
+6 6 6
+8 8 8
+9 9 9
+10 10 10
+select value from t1 order by id;
+value
+2
+3
+4
+5
+6
+8
+9
+10
+rollback;
+begin;
+insert into t1 values (11,11,11);
+insert into t1 values (12,12,12);
+insert into t1 values (13,13,13);
+delete from t1 where id=9;
+delete from t1 where value=8;
+update t1 set id=100 where value2=5;
+update t1 set value=103 where value=4;
+update t1 set id=115 where id=3;
+select * from t1 where id=1;
+id value value2
+1 1 1
+select * from t1 where value=1;
+id value value2
+1 1 1
+select value from t1 where value=1;
+value
+1
+select * from t1 where value2=1;
+id value value2
+1 1 1
+select * from t1 where id=5;
+id value value2
+select * from t1 where value=5;
+id value value2
+100 5 5
+select value from t1 where value=5;
+value
+5
+select * from t1 where value2=5;
+id value value2
+100 5 5
+select * from t1 where id < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 where value < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select value from t1 where value < 3 order by id;
+value
+1
+2
+select * from t1 where value2 < 3 order by id;
+id value value2
+1 1 1
+2 2 2
+select * from t1 order by id;
+id value value2
+1 1 1
+2 2 2
+4 103 4
+6 6 6
+10 10 10
+11 11 11
+12 12 12
+13 13 13
+100 5 5
+115 3 3
+select value from t1 order by id;
+value
+1
+2
+103
+6
+10
+11
+12
+13
+5
+3
+rollback;
+drop table t1;
+#
+# #802: MyRocks: Statement rollback doesnt work correctly for nested statements
+#
+create table t1 (a varchar(100)) engine=rocksdb;
+create table t2(a int) engine=rocksdb;
+insert into t2 values (1), (2);
+create table t3(a varchar(100)) engine=rocksdb;
+create function func() returns varchar(100) deterministic
+begin
+insert into t3 values ('func-called');
+set @a= (select a from t2);
+return 'func-returned';
+end;//
+begin;
+insert into t1 values (func());
+ERROR 21000: Subquery returns more than 1 row
+select * from t1;
+a
+# The following must not produce 'func-called':
+select * from t3;
+a
+rollback;
+drop function func;
+drop table t1,t2,t3;
+#
+# MDEV-16710: Slave SQL: Could not execute Update_rows_v1 event with RocksDB and triggers
+# Issue#857: MyRocks: Incorrect behavior when multiple statements fail inside a transaction
+#
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t2 (b INT PRIMARY KEY) ENGINE=RocksDB;
+CREATE TRIGGER tr AFTER INSERT ON t2 FOR EACH ROW INSERT INTO non_existing_table VALUES (NULL);
+BEGIN;
+DELETE FROM t1;
+INSERT INTO t2 VALUES (1);
+INSERT INTO t2 VALUES (2);
+# Must return empty result:
+SELECT * FROM t1;
+a
+COMMIT;
+drop table t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/truncate_partition.result b/storage/rocksdb/mysql-test/rocksdb/r/truncate_partition.result
new file mode 100644
index 00000000..67f655b6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/truncate_partition.result
@@ -0,0 +1,620 @@
+#
+# table(hidden key)
+#
+CREATE TABLE t1 (
+a INT,
+b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (b) (
+PARTITION p0 VALUES LESS THAN (3),
+PARTITION p1 VALUES LESS THAN (6),
+PARTITION p2 VALUES LESS THAN MAXVALUE
+);
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+3 8
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+SELECT a FROM t1 WHERE b > 2;
+a
+2
+SELECT b from t1 where a != 3;
+b
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+a b
+1 1
+SELECT a FROM t1 WHERE b > 2;
+a
+SELECT b from t1 where a != 3;
+b
+1
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+SELECT a FROM t1 WHERE b < 5;
+a
+4
+5
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+INSERT INTO t1(a,b) VALUES(7, 1);
+SELECT b from t1 WHERE a > 2;
+b
+1
+1
+4
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+7 1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+INSERT INTO t1(a,b) VALUES(8, 4);
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+8 4
+SELECT b from t1 WHERE a < 9;
+b
+1
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1;
+a b
+8 4
+INSERT INTO t1(a,b) VALUES(9, 8);
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+SELECT b FROM t1 WHERE a < 5;
+b
+1
+2
+3
+4
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+b
+DROP TABLE t1;
+#
+# table(secondary key)
+#
+CREATE TABLE t1(
+a INT,
+b INT,
+KEY (b)
+) ENGINE=ROCKSDB
+PARTITION BY HASH(a) PARTITIONS 3;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+3 8
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+3 8
+SELECT a FROM t1 WHERE b > 2;
+a
+3
+SELECT b from t1 where a != 3;
+b
+1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+a b
+3 8
+SELECT a FROM t1 WHERE b > 2;
+a
+3
+SELECT b from t1 where a != 3;
+b
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+SELECT a FROM t1 WHERE b < 5;
+a
+4
+5
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1;
+a b
+4 1
+6 8
+INSERT INTO t1(a,b) VALUES(7, 1);
+SELECT b from t1 WHERE a > 2;
+b
+1
+1
+8
+SELECT a,b FROM t1;
+a b
+4 1
+6 8
+7 1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1;
+a b
+6 8
+INSERT INTO t1(a,b) VALUES(8, 4);
+SELECT a,b FROM t1;
+a b
+6 8
+8 4
+SELECT b from t1 WHERE a < 9;
+b
+4
+8
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1;
+a b
+8 4
+INSERT INTO t1(a,b) VALUES(9, 8);
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+SELECT b FROM t1 WHERE a < 5;
+b
+1
+2
+3
+4
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+b
+DROP TABLE t1;
+#
+# table(primary key, auto increment)
+#
+CREATE TABLE t1(
+a INT NOT NULL AUTO_INCREMENT,
+b INT,
+PRIMARY KEY(a)
+) ENGINE=ROCKSDB
+PARTITION BY KEY() PARTITIONS 3;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+3 8
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+SELECT a FROM t1 WHERE b > 2;
+a
+SELECT b from t1 where a != 3;
+b
+1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+a b
+SELECT a FROM t1 WHERE b > 2;
+a
+SELECT b from t1 where a != 3;
+b
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+SELECT a FROM t1 WHERE b < 5;
+a
+4
+5
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+INSERT INTO t1(a,b) VALUES(7, 1);
+SELECT b from t1 WHERE a > 2;
+b
+1
+1
+4
+8
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+7 1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+INSERT INTO t1(a,b) VALUES(8, 4);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+8 4
+SELECT b from t1 WHERE a < 9;
+b
+1
+4
+4
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1;
+a b
+8 4
+INSERT INTO t1(a,b) VALUES(9, 8);
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+SELECT b FROM t1 WHERE a < 5;
+b
+1
+2
+3
+4
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+b
+DROP TABLE t1;
+#
+# table(cf)
+#
+CREATE TABLE t1 (
+a INT,
+b INT,
+PRIMARY KEY (`a`, `b`) COMMENT 'testcomment'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(a) (
+PARTITION p0 VALUES IN (1, 4, 7),
+PARTITION p1 VALUES IN (2, 5, 8),
+PARTITION p2 VALUES IN (3, 6, 9)
+);
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+3 8
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+SELECT a FROM t1 WHERE b > 2;
+a
+2
+SELECT b from t1 where a != 3;
+b
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+a b
+1 1
+SELECT a FROM t1 WHERE b > 2;
+a
+SELECT b from t1 where a != 3;
+b
+1
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+SELECT a FROM t1 WHERE b < 5;
+a
+4
+5
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+INSERT INTO t1(a,b) VALUES(7, 1);
+SELECT b from t1 WHERE a > 2;
+b
+1
+1
+4
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+7 1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+INSERT INTO t1(a,b) VALUES(8, 4);
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+8 4
+SELECT b from t1 WHERE a < 9;
+b
+1
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1;
+a b
+8 4
+INSERT INTO t1(a,b) VALUES(9, 8);
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+SELECT b FROM t1 WHERE a < 5;
+b
+1
+2
+3
+4
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+b
+DROP TABLE t1;
+#
+# table(reverse cf)
+#
+CREATE TABLE t1 (
+a INT,
+b INT,
+PRIMARY KEY (`a`, `b`) COMMENT 'p0_cfname=rev:foo;p1_cfname=bar;p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(a) (
+PARTITION p0 VALUES IN (1, 4, 7),
+PARTITION p1 VALUES IN (2, 5, 8),
+PARTITION p2 VALUES IN (3, 6, 9)
+);
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+3 8
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+1 1
+2 4
+SELECT a FROM t1 WHERE b > 2;
+a
+2
+SELECT b from t1 where a != 3;
+b
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+a b
+1 1
+SELECT a FROM t1 WHERE b > 2;
+a
+SELECT b from t1 where a != 3;
+b
+1
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+6 8
+SELECT a FROM t1 WHERE b < 5;
+a
+4
+5
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+INSERT INTO t1(a,b) VALUES(7, 1);
+SELECT b from t1 WHERE a > 2;
+b
+1
+1
+4
+SELECT a,b FROM t1;
+a b
+4 1
+5 4
+7 1
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+INSERT INTO t1(a,b) VALUES(8, 4);
+SELECT a,b FROM t1;
+a b
+4 1
+7 1
+8 4
+SELECT b from t1 WHERE a < 9;
+b
+1
+1
+4
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1;
+a b
+8 4
+INSERT INTO t1(a,b) VALUES(9, 8);
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+SELECT b FROM t1 WHERE a < 5;
+b
+1
+2
+3
+4
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+b
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/truncate_table.result b/storage/rocksdb/mysql-test/rocksdb/r/truncate_table.result
new file mode 100644
index 00000000..47048677
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/truncate_table.result
@@ -0,0 +1,33 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+TRUNCATE TABLE t1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c');
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+a b
+DROP TABLE t1;
+CREATE TABLE t1 (a INT KEY AUTO_INCREMENT, c CHAR(8)) ENGINE=rocksdb;
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed # # # 0 0 0 1 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 (c) VALUES ('a'),('b'),('c');
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed # # # 0 0 0 4 # # NULL latin1_swedish_ci NULL 0 N
+TRUNCATE TABLE t1;
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed # # # 0 0 0 1 # # NULL latin1_swedish_ci NULL 0 N
+INSERT INTO t1 (c) VALUES ('d');
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment Max_index_length Temporary
+t1 ROCKSDB 10 Fixed # # # 0 0 0 2 # # NULL latin1_swedish_ci NULL 0 N
+SELECT a,c FROM t1;
+a c
+1 d
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+HANDLER t1 OPEN AS h1;
+ERROR HY000: Storage engine ROCKSDB of the table `test`.`t1` doesn't have this option
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/truncate_table3.result b/storage/rocksdb/mysql-test/rocksdb/r/truncate_table3.result
new file mode 100644
index 00000000..7c5631a2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/truncate_table3.result
@@ -0,0 +1,23 @@
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+DROP TABLE IF EXISTS t1;
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+# restart
+CREATE TABLE t1 (
+a int not null,
+b int not null,
+c varchar(500) not null,
+primary key (a,b) comment 'cf1',
+key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+DELETE FROM t1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+truncate table t1;
+select case when variable_value-@a < 500000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+case when variable_value-@a < 500000 then 'true' else 'false' end
+true
+DROP TABLE IF EXISTS t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/trx_info.result b/storage/rocksdb/mysql-test/rocksdb/r/trx_info.result
new file mode 100644
index 00000000..ada2e127
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/trx_info.result
@@ -0,0 +1,13 @@
+DROP TABLE IF EXISTS t1;
+create table t1 (a int) engine=rocksdb;
+insert into t1 values (1);
+insert into t1 values (2);
+set autocommit=0;
+select * from t1 for update;
+a
+1
+2
+select * from information_schema.rocksdb_trx;
+TRANSACTION_ID STATE NAME WRITE_COUNT LOCK_COUNT TIMEOUT_SEC WAITING_KEY WAITING_COLUMN_FAMILY_ID IS_REPLICATION SKIP_TRX_API READ_ONLY HAS_DEADLOCK_DETECTION NUM_ONGOING_BULKLOAD THREAD_ID QUERY
+_TRX_ID_ STARTED _NAME_ 0 2 1 _KEY_ 0 0 0 0 0 0 _THREAD_ID_ select * from information_schema.rocksdb_trx
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/trx_info_rpl.result b/storage/rocksdb/mysql-test/rocksdb/r/trx_info_rpl.result
new file mode 100644
index 00000000..b096e42a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/trx_info_rpl.result
@@ -0,0 +1,16 @@
+include/master-slave.inc
+[connection master]
+DROP TABLE IF EXISTS t1;
+connection slave;
+include/stop_slave.inc
+create table t1 (a int, b int, primary key (a), unique key (b)) engine=rocksdb;
+connection master;
+connection slave;
+show variables like 'rpl_skip_tx_api';
+Variable_name Value
+rpl_skip_tx_api ON
+include/start_slave.inc
+found
+connection master;
+DROP TABLE t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary.result
new file mode 100644
index 00000000..2903e9aa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary.result
@@ -0,0 +1,489 @@
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int,
+c int,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, NULL, NULL, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, NULL, NULL, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'efghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5);
+INSERT INTO t1 values (2, 4, 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a, ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=ts;';
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_debug_ttl_snapshot_ts = -10;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_debug_ttl_snapshot_ts = 10;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a, ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`, `ts`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('de', 'fghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT NOT NULL,
+b varbinary(64) NOT NULL,
+c varbinary(256) NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+value mediumblob NOT NULL,
+PRIMARY KEY (b,a,c)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=10;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values (2, 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (3, 'i', 'j', UNIX_TIMESTAMP(), 'k');
+INSERT INTO t1 values (4, 'm', 'n', UNIX_TIMESTAMP(), 'o');
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT a FROM t1;
+a
+1
+2
+3
+4
+set global rocksdb_compact_cf='default';
+SELECT a FROM t1;
+a
+3
+4
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT a FROM t1;
+a
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts bigint(20),
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+ERROR HY000: TTL column (ts) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts int,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+ERROR HY000: TTL column (ts) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=abc;';
+ERROR HY000: TTL duration (abc) in MyRocks must be an unsigned non-null 64-bit integer.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=abc;';
+ERROR HY000: TTL column (abc) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_col=abc;';
+ERROR HY000: TTL column (abc) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=500;';
+INSERT INTO t1 values (1);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+INSERT INTO t1 values (1);
+SELECT * FROM t1;
+a
+1
+set global rocksdb_debug_ttl_rec_ts = -300;
+ALTER TABLE t1 COMMENT = 'ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COMMENT='ttl_duration=1'
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+ALTER TABLE t1 DROP PRIMARY KEY;
+ERROR HY000: TTL support is currently disabled when table has a hidden PK.
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t1 VALUES (2,2);
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int,
+PRIMARY KEY (a,b)
+) ENGINE=rocksdb
+COMMENT='asdadfasdfsadfadf ;ttl_duration=1; asfasdfasdfadfa';
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 1);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+ALTER TABLE t1 COMMENT = 'adsf;;ttl_duration=5;asfasdfa;ttl_col=a;asdfasdf;';
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+1
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (7);
+INSERT INTO t1 values (9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+UPDATE t1 SET a=a+1;
+SELECT * FROM t1;
+a
+10
+2
+4
+6
+8
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT * FROM t1;
+a
+10
+8
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT,
+b bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=UNIX_TIMESTAMP() WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT a FROM t1;
+a
+1
+3
+5
+7
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT a FROM t1;
+a
+1
+3
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_enable_ttl=0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_enable_ttl=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+3
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+0
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_read_filtering.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_read_filtering.result
new file mode 100644
index 00000000..558924e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_read_filtering.result
@@ -0,0 +1,283 @@
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t1;
+a
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+2
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b BIGINT UNSIGNED NOT NULL
+) ENGINE=rocksdb
+COMMENT='ttl_duration=10;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+SELECT a FROM t1;
+a
+2
+3
+set global rocksdb_compact_cf='default';
+SELECT a FROM t1;
+a
+2
+3
+set global rocksdb_debug_ttl_read_filter_ts = -310;
+SELECT a FROM t1;
+a
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+4
+set global rocksdb_enable_ttl_read_filtering=0;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+1
+3
+5
+7
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+0
+set global rocksdb_enable_ttl_read_filtering=1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+4
+DROP TABLE t1;
+set global ROCKSDB_COMPACT_CF= 'default';
+CREATE TABLE t1 (
+a int,
+b int,
+c int,
+PRIMARY KEY (a,b,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (0,0,0);
+INSERT INTO t1 values (0,0,1);
+INSERT INTO t1 values (0,1,0);
+INSERT INTO t1 values (0,1,1);
+INSERT INTO t1 values (1,1,2);
+INSERT INTO t1 values (1,2,1);
+INSERT INTO t1 values (1,2,2);
+INSERT INTO t1 values (1,2,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t1 WHERE a=1 AND b=2 AND c=2;
+a b c
+SELECT * FROM t1 WHERE a = 1;
+a b c
+SELECT max(a) from t1 where a < 3;
+max(a)
+NULL
+SELECT max(a) from t1 where a < 2 AND b = 1 AND c < 3;
+max(a)
+NULL
+SELECT min(a) from t1 where a >= 1;
+min(a)
+NULL
+SELECT min(a) from t1 where a > 1;
+min(a)
+NULL
+select * from t1 where a=1 and b in (1) order by c desc;
+a b c
+select max(a) from t1 where a <=10;
+max(a)
+NULL
+select a from t1 where a > 0 and a <= 2;
+a
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+0
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+8
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+set global rocksdb_debug_ttl_rec_ts = -110;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1;
+a
+INSERT INTO t1 values (1);
+SELECT * FROM t1;
+a
+1
+DROP TABLE t1;
+set global rocksdb_force_flush_memtable_now=1;
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1;
+a
+UPDATE t1 set a = 1;
+DROP TABLE t1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b int
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+set global rocksdb_debug_ttl_rec_ts = -110;
+INSERT INTO t1 values (1,1);
+INSERT INTO t1 values (3,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (5,5);
+UPDATE t1 set a = 1;
+SELECT * FROM t1;
+a b
+1 5
+set global rocksdb_enable_ttl_read_filtering=0;
+SELECT * FROM t1;
+a b
+1 5
+3 3
+set global rocksdb_enable_ttl_read_filtering=1;
+UPDATE t1 set a = 999 where a = 1;
+SELECT * FROM t1;
+a b
+999 5
+UPDATE t1 set a = a - 1;
+SELECT * FROM t1;
+a b
+998 5
+DROP TABLE t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+INSERT INTO t1 values (1);
+connection con1;
+# Creating Snapshot (start transaction)
+BEGIN;
+SELECT * FROM t1;
+a
+1
+SELECT * FROM t1;
+a
+1
+# Switching to connection 2
+connection con2;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+1
+# Switching to connection 1
+connection con1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+1
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+0
+UPDATE t1 set a = a + 1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+2
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+0
+COMMIT;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+SELECT * FROM t1;
+a
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+variable_value-@a
+1
+DROP TABLE t1;
+disconnect con1;
+disconnect con2;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+# On Connection 1
+connection con1;
+# Creating Snapshot (start transaction)
+BEGIN;
+SELECT * FROM t1;
+a
+# On Connection 2
+connection con2;
+set global rocksdb_debug_ttl_rec_ts = -2;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+# On Connection 1
+connection con1;
+SELECT * FROM t1;
+a
+# On Connection 2
+connection con2;
+SELECT * FROM t1;
+a
+set global rocksdb_enable_ttl_read_filtering=0;
+SELECT * FROM t1;
+a
+1
+3
+5
+7
+set global rocksdb_enable_ttl_read_filtering=1;
+disconnect con2;
+disconnect con1;
+connection default;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_with_partitions.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_with_partitions.result
new file mode 100644
index 00000000..d6d9e290
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_primary_with_partitions.result
@@ -0,0 +1,256 @@
+CREATE TABLE t1 (
+c1 INT,
+PRIMARY KEY (`c1`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (4);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (6);
+INSERT INTO t1 values (7);
+INSERT INTO t1 values (8);
+INSERT INTO t1 values (9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1;
+c1
+1
+2
+3
+4
+5
+6
+7
+8
+9
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT * FROM t1;
+c1
+2
+3
+5
+6
+8
+9
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;custom_p1_ttl_duration=7;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (4,4,'aaaa');
+INSERT INTO t1 values (7,7,'aaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (2,2,'aa');
+INSERT INTO t1 values (3,3,'aaa');
+INSERT INTO t1 values (5,5,'aaaaa');
+INSERT INTO t1 values (6,6,'aaaaaa');
+INSERT INTO t1 values (8,8,'aaaaaaaa');
+INSERT INTO t1 values (9,9,'aaaaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1;
+c1 c2 name
+1 1 a
+2 2 aa
+3 3 aaa
+4 4 aaaa
+5 5 aaaaa
+6 6 aaaaaa
+7 7 aaaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+SELECT * FROM t1;
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'foo';
+SELECT * FROM t1;
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1;
+c1 c2 name
+3 3 aaa
+6 6 aaaaaa
+9 9 aaaaaaaaa
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 2, 3),
+PARTITION custom_p1 VALUES IN (4, 5, 6),
+PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (4, 4, "four", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (6, 6, "six", null);
+INSERT INTO t1 VALUES (7, 7, "seven", null);
+INSERT INTO t1 VALUES (8, 8, "eight", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+SELECT * FROM t1;
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+set global rocksdb_debug_ttl_rec_ts = 600;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `c1` int(11) NOT NULL,
+ `c2` int(11) NOT NULL,
+ `name` varchar(25) NOT NULL,
+ `event` date DEFAULT NULL,
+ PRIMARY KEY (`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COMMENT='custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;'
+ PARTITION BY LIST (`c1`)
+(PARTITION `custom_p0` VALUES IN (1,2,3) ENGINE = ROCKSDB,
+ PARTITION `custom_p1` VALUES IN (4,5,6) ENGINE = ROCKSDB,
+ PARTITION `custom_p2` VALUES IN (7,8,9) ENGINE = ROCKSDB)
+set global rocksdb_debug_ttl_snapshot_ts = 100;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'baz';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1;
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+set global rocksdb_debug_ttl_snapshot_ts = 1200;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1;
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 BIGINT,
+c2 BIGINT UNSIGNED NOT NULL,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;'
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=1;custom_p1_ttl_duration=100;custom_p1_ttl_col=c2;custom_p2_ttl_duration=5000;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 2, 3),
+PARTITION custom_p1 VALUES IN (4, 5, 6),
+PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 VALUES (1, UNIX_TIMESTAMP(), "one", null);
+INSERT INTO t1 VALUES (2, UNIX_TIMESTAMP(), "two", null);
+INSERT INTO t1 VALUES (3, UNIX_TIMESTAMP(), "three", null);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 VALUES (4, UNIX_TIMESTAMP(), "four", null);
+INSERT INTO t1 VALUES (5, UNIX_TIMESTAMP(), "five", null);
+INSERT INTO t1 VALUES (6, UNIX_TIMESTAMP(), "six", null);
+INSERT INTO t1 VALUES (7, UNIX_TIMESTAMP(), "seven", null);
+INSERT INTO t1 VALUES (8, UNIX_TIMESTAMP(), "eight", null);
+INSERT INTO t1 VALUES (9, UNIX_TIMESTAMP(), "nine", null);
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'bar';
+SELECT c1 FROM t1;
+c1
+4
+5
+6
+7
+8
+9
+set global rocksdb_debug_ttl_snapshot_ts = 600;
+set @@global.rocksdb_compact_cf = 'bar';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT c1 FROM t1;
+c1
+7
+8
+9
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 BIGINT,
+c2 BIGINT UNSIGNED NOT NULL,
+PRIMARY KEY (`c1`, `c2`)
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=100;ttl_col=c2;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1),
+PARTITION custom_p1 VALUES IN (2),
+PARTITION custom_p2 VALUES IN (3)
+);
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT c1 FROM t1;
+c1
+1
+2
+3
+set global rocksdb_debug_ttl_snapshot_ts = 300;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT c1 FROM t1;
+c1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_rows_examined.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_rows_examined.result
new file mode 100644
index 00000000..97a2eff5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_rows_examined.result
@@ -0,0 +1,45 @@
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = -10;
+connect conn1, localhost, root,,test;
+connect conn2, localhost, root,,test;
+connection conn1;
+CREATE TABLE t_re (
+a INT, b INT, PRIMARY KEY (a)
+) ENGINE=ROCKSDB
+COMMENT 'ttl_duration=1';
+affected rows: 0
+set global rocksdb_debug_ttl_rec_ts = -13;
+affected rows: 0
+insert into t_re values (1,1);
+affected rows: 1
+insert into t_re values (2,2);
+affected rows: 1
+set global rocksdb_debug_ttl_rec_ts = 0;
+affected rows: 0
+commit;
+affected rows: 0
+set debug_sync='rocksdb.ttl_rows_examined SIGNAL parked WAIT_FOR go';
+affected rows: 0
+SELECT * FROM t_re;
+connection conn2;
+set debug_sync='now WAIT_FOR parked';
+affected rows: 0
+SHOW PROCESSLIST;
+Id User Host db Command Time State Info Progress
+### ### ### ### Query ### debug sync point: rocksdb.ttl_rows_examined SELECT * FROM t_re 0.000
+### ### ### ### Query ### starting SHOW PROCESSLIST 0.000
+### ### ### ### Sleep ### NULL 0.000
+affected rows: 3
+set debug_sync='now SIGNAL go';
+affected rows: 0
+connection conn1;
+a b
+affected rows: 0
+set debug_sync='RESET';
+affected rows: 0
+set global rocksdb_debug_ttl_read_filter_ts = DEFAULT;
+affected rows: 0
+drop table t_re;
+affected rows: 0
+disconnect conn1;
+disconnect conn2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary.result
new file mode 100644
index 00000000..1f748a38
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary.result
@@ -0,0 +1,709 @@
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`),
+KEY kb (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a,c),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int,
+c int,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a),
+KEY kbc (b, c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, NULL, NULL, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, NULL, NULL, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`),
+KEY kbc (`b`, `c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'efghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5);
+INSERT INTO t1 values (2, 4, 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a, ts),
+KEY kt (ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=ts;';
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_snapshot_ts = -10;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 10;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a, ts),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`, `ts`),
+KEY kb (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('de', 'fghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT NOT NULL,
+b varbinary(64) NOT NULL,
+c varbinary(256) NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+value mediumblob NOT NULL,
+PRIMARY KEY (b,a,c),
+KEY kb (b)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=10;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values (2, 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (3, 'i', 'j', UNIX_TIMESTAMP(), 'k');
+INSERT INTO t1 values (4, 'm', 'n', UNIX_TIMESTAMP(), 'o');
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+1
+2
+3
+4
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+3
+4
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts bigint(20),
+PRIMARY KEY (a,c),
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+ERROR HY000: TTL column (ts) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+ts int,
+PRIMARY KEY (a,c),
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+ERROR HY000: TTL column (ts) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c),
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=abc;';
+ERROR HY000: TTL duration (abc) in MyRocks must be an unsigned non-null 64-bit integer.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c),
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=abc;';
+ERROR HY000: TTL column (abc) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a,c),
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_col=abc;';
+ERROR HY000: TTL column (abc) in MyRocks must be an unsigned non-null 64-bit integer, exist inside the table, and have an accompanying ttl duration.
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=500;';
+INSERT INTO t1 values (1, 1);
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+1
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+INSERT INTO t1 values (1, 1);
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+set global rocksdb_debug_ttl_rec_ts = -300;
+ALTER TABLE t1 COMMENT = 'ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) NOT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kb` (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COMMENT='ttl_duration=1'
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+ALTER TABLE t1 DROP PRIMARY KEY;
+ERROR HY000: TTL support is currently disabled when table has a hidden PK.
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t1 VALUES (2,2);
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+2
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) UNSIGNED NOT NULL,
+b int,
+PRIMARY KEY (a,b),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='asdadfasdfsadfadf ;ttl_duration=1; asfasdfasdfadfa';
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 1);
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+ALTER TABLE t1 COMMENT = 'adsf;;ttl_duration=5;asfasdfa;ttl_col=a;asdfasdf;';
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 0);
+INSERT INTO t1 values (3, 0);
+INSERT INTO t1 values (5, 0);
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (7, 0);
+INSERT INTO t1 values (9, 0);
+set global rocksdb_debug_ttl_rec_ts = 0;
+UPDATE t1 SET a=a+1;
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+10 0
+2 0
+4 0
+6 0
+8 0
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT * FROM t1;
+a b
+10 0
+8 0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT,
+b bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+1
+3
+5
+7
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+1
+3
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 1);
+INSERT INTO t1 values (3, 1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_enable_ttl=0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_enable_ttl=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+6
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a bigint(20) NOT NULL,
+b int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT,
+b bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a, b),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+1
+3
+5
+7
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+1
+3
+DROP TABLE t1;
+CREATE TABLE t1 (
+a INT,
+b bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a, b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT a FROM t1;
+a
+1
+3
+5
+7
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT a FROM t1;
+a
+1
+3
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+CREATE INDEX kb on t1 (b);
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', 'd');
+INSERT INTO t1 values ('d', 'e', 'f', 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+CREATE INDEX kb on t1 (b);
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`, `ts`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*);
+COUNT(*)
+1
+CREATE INDEX kb on t1 (b);
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+COUNT(*)
+0
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering.result
new file mode 100644
index 00000000..395c84ed
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering.result
@@ -0,0 +1,511 @@
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b int NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+2
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b BIGINT UNSIGNED NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=10;';
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+# 1 should be hidden
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+a
+2
+3
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+2
+3
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+# none should be hidden yet, compaction runs but records aren't expired
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+a
+2
+3
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+2
+3
+# all should be hidden now, even though compaction hasn't run again
+set global rocksdb_debug_ttl_read_filter_ts = -310;
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+a
+SELECT a FROM t1 FORCE INDEX (kb);
+a
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b int NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+# should return nothing.
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+set global rocksdb_enable_ttl_read_filtering=0;
+# should return everything
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+3 3
+5 5
+7 7
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+3 3
+5 5
+7 7
+set global rocksdb_enable_ttl_read_filtering=1;
+# should return nothing.
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+DROP TABLE t1;
+set global rocksdb_compact_cf= 'default';
+# Read filtering index scan tests (None of these queries should return any results)
+CREATE TABLE t1 (
+a int,
+b int,
+c int,
+PRIMARY KEY (a,b,c),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (0,0,0);
+INSERT INTO t1 values (0,0,1);
+INSERT INTO t1 values (0,1,0);
+INSERT INTO t1 values (0,1,1);
+INSERT INTO t1 values (1,1,2);
+INSERT INTO t1 values (1,2,1);
+INSERT INTO t1 values (1,2,2);
+INSERT INTO t1 values (1,2,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a=1 AND b=2 AND c=2;
+a b c
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a=1 AND b=2 AND c=2;
+a b c
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a = 1;
+a b c
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a = 1;
+a b c
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a < 3;
+max(a)
+NULL
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a < 3;
+max(a)
+NULL
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a < 2 AND b = 1 AND c < 3;
+max(a)
+NULL
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a < 2 AND b = 1 AND c < 3;
+max(a)
+NULL
+SELECT min(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a >= 1;
+min(a)
+NULL
+SELECT min(a) FROM t1 FORCE INDEX (kb) WHERE a >= 1;
+min(a)
+NULL
+SELECT min(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a > 1;
+min(a)
+NULL
+SELECT min(a) FROM t1 FORCE INDEX (kb) WHERE a > 1;
+min(a)
+NULL
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a=1 and b in (1) order by c desc;
+a b c
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a=1 and b in (1) order by c desc;
+a b c
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a <=10;
+max(a)
+NULL
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a <=10;
+max(a)
+NULL
+SELECT a FROM t1 FORCE INDEX (PRIMARY) WHERE a > 0 and a <= 2;
+a
+SELECT a FROM t1 FORCE INDEX (kb) WHERE a > 0 and a <= 2;
+a
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+0
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+variable_value-@c
+8
+DROP TABLE t1;
+# Attempt to update expired value, should filter out
+set global rocksdb_force_flush_memtable_now=1;
+CREATE TABLE t1 (
+a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a
+SELECT * FROM t1;
+a
+# No error is thrown here, under the hood index_next_with_direction is
+# filtering out the record from being seen in the first place.
+UPDATE t1 set a = 1;
+DROP TABLE t1;
+# Ensure no rows can disappear in the middle of long-running transactions
+# Also ensure repeatable-read works as expected
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b int NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+INSERT INTO t1 values (1, 1);
+connection con1;
+# Creating Snapshot (start transaction)
+BEGIN;
+# Nothing filtered out here
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+# Switching to connection 2
+connection con2;
+# compaction doesn't do anything since con1 snapshot is still open
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+# read filtered out, because on a different connection, on
+# this connection the records have 'expired' already so they are filtered out
+# even though they have not yet been removed by compaction
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+# Switching to connection 1
+connection con1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+UPDATE t1 set a = a + 1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+2 1
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+2 1
+COMMIT;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+DROP TABLE t1;
+disconnect con1;
+disconnect con2;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+CREATE TABLE t1 (
+a int PRIMARY KEY,
+b int NOT NULL,
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+# On Connection 1
+connection con1;
+# Creating Snapshot (start transaction)
+BEGIN;
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+# On Connection 2
+connection con2;
+set global rocksdb_debug_ttl_rec_ts = -2;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+# On Connection 1
+connection con1;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+# On Connection 2
+connection con2;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+set global rocksdb_enable_ttl_read_filtering=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b
+1 1
+3 3
+5 5
+7 7
+SELECT * FROM t1 FORCE INDEX (kb);
+a b
+1 1
+3 3
+5 5
+7 7
+set global rocksdb_enable_ttl_read_filtering=1;
+disconnect con2;
+disconnect con1;
+connection default;
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int,
+b int,
+ts bigint(20) UNSIGNED NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 VALUES (1, 1, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (2, 2, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (3, 3, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (4, 4, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (5, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (6, 6, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (7, 7, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (8, 8, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (9, 9, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (10, 10, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+# None are expired
+SELECT a, b FROM t1 FORCE INDEX (kb);
+a b
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+10 10
+set global rocksdb_debug_ttl_rec_ts = -100;
+UPDATE t1 SET ts=(UNIX_TIMESTAMP()+1) WHERE a IN (4, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+# 4 and 7 should be gone
+SELECT a, b FROM t1 FORCE INDEX (kb);
+a b
+1 1
+2 2
+3 3
+5 5
+6 6
+8 8
+9 9
+10 10
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+PRIMARY KEY (c1, c2),
+KEY kc2 (c2)
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (2,2,'b');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (3,3,'c');
+INSERT INTO t1 values (4,4,'d');
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (5,5,'e');
+INSERT INTO t1 values (6,6,'f');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (7,7,'g');
+INSERT INTO t1 values (8,8,'h');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name
+3 3 c
+4 4 d
+7 7 g
+8 8 h
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name
+3 3 c
+4 4 d
+7 7 g
+8 8 h
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE c1 > 5;
+c1 c2 name
+7 7 g
+8 8 h
+SELECT * FROM t1 FORCE INDEX (kc2) WHERE c2 > 5;
+c1 c2 name
+7 7 g
+8 8 h
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE 3 < c1 AND c1 < 6;
+c1 c2 name
+4 4 d
+SELECT * FROM t1 FORCE INDEX (kc2) WHERE 3 < c2 AND c2 < 6;
+c1 c2 name
+4 4 d
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int,
+b int,
+PRIMARY KEY (a),
+KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1800;';
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (1,1);
+INSERT INTO t1 values (2,2);
+INSERT INTO t1 values (7,7);
+INSERT INTO t1 values (10,10);
+INSERT INTO t1 values (11,11);
+INSERT INTO t1 values (12,12);
+set global rocksdb_debug_ttl_rec_ts = 450;
+INSERT INTO t1 values (3,3);
+INSERT INTO t1 values (4,4);
+INSERT INTO t1 values (8,8);
+INSERT INTO t1 values (16,16);
+INSERT INTO t1 values (17,17);
+INSERT INTO t1 values (18,18);
+set global rocksdb_debug_ttl_rec_ts = 900;
+INSERT INTO t1 values (5,5);
+INSERT INTO t1 values (6,6);
+INSERT INTO t1 values (9,9);
+INSERT INTO t1 values (13,13);
+INSERT INTO t1 values (14,14);
+INSERT INTO t1 values (15,15);
+set global rocksdb_debug_ttl_rec_ts = 0;
+# Should see everything
+SELECT * FROM t1;
+a b
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+10 10
+11 11
+12 12
+13 13
+14 14
+15 15
+16 16
+17 17
+18 18
+# Should have no records from the first group
+set global rocksdb_debug_ttl_read_filter_ts = -1800;
+SELECT * FROM t1;
+a b
+3 3
+4 4
+5 5
+6 6
+8 8
+9 9
+13 13
+14 14
+15 15
+16 16
+17 17
+18 18
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a > 5 AND a < 15;
+a b
+6 6
+8 8
+9 9
+13 13
+14 14
+# Should only have records from the last group
+set global rocksdb_debug_ttl_read_filter_ts = -1800 - 450;
+SELECT * FROM t1;
+a b
+5 5
+6 6
+9 9
+13 13
+14 14
+15 15
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a < 10;
+a b
+5 5
+6 6
+9 9
+# Should be empty
+set global rocksdb_debug_ttl_read_filter_ts = -1800 - 900;
+SELECT * FROM t1;
+a b
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering_multiple_index.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering_multiple_index.result
new file mode 100644
index 00000000..e4c36157
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_read_filtering_multiple_index.result
@@ -0,0 +1,82 @@
+CREATE TABLE t1 (
+a int NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b) COMMENT 'kb',
+KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='kb';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kb);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kc);
+a b c
+3 3 3
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b) COMMENT 'kb',
+KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kb);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kc);
+a b c
+3 3 3
+DROP TABLE t1;
+CREATE TABLE t1 (
+a int NOT NULL,
+b int NOT NULL,
+c int NOT NULL,
+PRIMARY KEY (a),
+KEY kb (b) COMMENT 'kb',
+KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_compact_cf='kb';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kb);
+a b c
+3 3 3
+SELECT * FROM t1 FORCE INDEX (kc);
+a b c
+3 3 3
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_with_partitions.result b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_with_partitions.result
new file mode 100644
index 00000000..713c7e92
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/ttl_secondary_with_partitions.result
@@ -0,0 +1,389 @@
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+PRIMARY KEY (`c1`),
+KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (4, 4);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (6, 6);
+INSERT INTO t1 values (7, 7);
+INSERT INTO t1 values (8, 8);
+INSERT INTO t1 values (9, 9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2
+2 2
+3 3
+5 5
+6 6
+8 8
+9 9
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2
+2 2
+3 3
+5 5
+6 6
+8 8
+9 9
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz',
+KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;custom_p1_ttl_duration=7;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (4,4,'aaaa');
+INSERT INTO t1 values (7,7,'aaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (2,2,'aa');
+INSERT INTO t1 values (3,3,'aaa');
+INSERT INTO t1 values (5,5,'aaaaa');
+INSERT INTO t1 values (6,6,'aaaaaa');
+INSERT INTO t1 values (8,8,'aaaaaaaa');
+INSERT INTO t1 values (9,9,'aaaaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name
+1 1 a
+2 2 aa
+3 3 aaa
+4 4 aaaa
+5 5 aaaaa
+6 6 aaaaaa
+7 7 aaaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name
+1 1 a
+2 2 aa
+3 3 aaa
+4 4 aaaa
+5 5 aaaaa
+6 6 aaaaaa
+7 7 aaaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'default';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'foo';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name
+2 2 aa
+3 3 aaa
+5 5 aaaaa
+6 6 aaaaaa
+8 8 aaaaaaaa
+9 9 aaaaaaaaa
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name
+3 3 aaa
+6 6 aaaaaa
+9 9 aaaaaaaaa
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name
+3 3 aaa
+6 6 aaaaaa
+9 9 aaaaaaaaa
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;',
+KEY kc2 (c2)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 2, 3),
+PARTITION custom_p1 VALUES IN (4, 5, 6),
+PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (4, 4, "four", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (6, 6, "six", null);
+INSERT INTO t1 VALUES (7, 7, "seven", null);
+INSERT INTO t1 VALUES (8, 8, "eight", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+set global rocksdb_debug_ttl_rec_ts = 600;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `c1` int(11) NOT NULL,
+ `c2` int(11) NOT NULL,
+ `name` varchar(25) NOT NULL,
+ `event` date DEFAULT NULL,
+ PRIMARY KEY (`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;',
+ KEY `kc2` (`c2`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COMMENT='custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;'
+ PARTITION BY LIST (`c1`)
+(PARTITION `custom_p0` VALUES IN (1,2,3) ENGINE = ROCKSDB,
+ PARTITION `custom_p1` VALUES IN (4,5,6) ENGINE = ROCKSDB,
+ PARTITION `custom_p2` VALUES IN (7,8,9) ENGINE = ROCKSDB)
+set global rocksdb_debug_ttl_snapshot_ts = 100;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+7 7 seven NULL
+8 8 eight NULL
+9 9 nine NULL
+set global rocksdb_debug_ttl_snapshot_ts = 1200;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2 name event
+1 1 one NULL
+2 2 two NULL
+3 3 three NULL
+4 4 four NULL
+5 5 five NULL
+6 6 six NULL
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 BIGINT,
+c2 BIGINT UNSIGNED NOT NULL,
+name VARCHAR(25) NOT NULL,
+event DATE,
+PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;',
+KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=1;custom_p1_ttl_duration=100;custom_p1_ttl_col=c2;custom_p2_ttl_duration=5000;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 2, 3),
+PARTITION custom_p1 VALUES IN (4, 5, 6),
+PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 VALUES (1, UNIX_TIMESTAMP(), "one", null);
+INSERT INTO t1 VALUES (2, UNIX_TIMESTAMP(), "two", null);
+INSERT INTO t1 VALUES (3, UNIX_TIMESTAMP(), "three", null);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 VALUES (4, UNIX_TIMESTAMP(), "four", null);
+INSERT INTO t1 VALUES (5, UNIX_TIMESTAMP(), "five", null);
+INSERT INTO t1 VALUES (6, UNIX_TIMESTAMP(), "six", null);
+INSERT INTO t1 VALUES (7, UNIX_TIMESTAMP(), "seven", null);
+INSERT INTO t1 VALUES (8, UNIX_TIMESTAMP(), "eight", null);
+INSERT INTO t1 VALUES (9, UNIX_TIMESTAMP(), "nine", null);
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'bar';
+set @@global.rocksdb_compact_cf = 'default';
+SELECT c1 FROM t1 FORCE INDEX (PRIMARY);
+c1
+4
+5
+6
+7
+8
+9
+SELECT c1 FROM t1 FORCE INDEX (kc2);
+c1
+4
+5
+6
+7
+8
+9
+set global rocksdb_debug_ttl_snapshot_ts = 600;
+set @@global.rocksdb_compact_cf = 'bar';
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT c1 FROM t1 FORCE INDEX (PRIMARY);
+c1
+7
+8
+9
+SELECT c1 FROM t1 FORCE INDEX (kc2);
+c1
+7
+8
+9
+DROP TABLE t1;
+CREATE TABLE t1 (
+c1 INT,
+c2 INT,
+PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+PARTITION custom_p0 VALUES IN (1, 4, 7),
+PARTITION custom_p1 VALUES IN (2, 5, 8),
+PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (4, 4);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (6, 6);
+INSERT INTO t1 values (8, 8);
+INSERT INTO t1 values (9, 9);
+SELECT * FROM t1;
+c1 c2
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+CREATE INDEX kc2 on t1 (c2);
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='foo';
+set global rocksdb_compact_cf='default';
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+c1 c2
+2 2
+3 3
+5 5
+6 6
+8 8
+9 9
+SELECT * FROM t1 FORCE INDEX (kc2);
+c1 c2
+2 2
+3 3
+5 5
+6 6
+8 8
+9 9
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_binary.result b/storage/rocksdb/mysql-test/rocksdb/r/type_binary.result
new file mode 100644
index 00000000..bbdd6d21
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_binary.result
@@ -0,0 +1,48 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+b BINARY ,
+b0 BINARY(0) ,
+b1 BINARY(1) ,
+b20 BINARY(20) ,
+b255 BINARY(255) ,
+pk BINARY PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) YES NULL
+b0 binary(0) YES NULL
+b1 binary(1) YES NULL
+b20 binary(20) YES NULL
+b255 binary(255) YES NULL
+pk binary(1) NO PRI NULL
+INSERT INTO t1 VALUES ('','','','','','');
+INSERT INTO t1 VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.','a');
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+INSERT INTO t1 VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256),'b');
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b20' at row 1
+Warning 1265 Data truncated for column 'b255' at row 1
+INSERT INTO t1 SELECT b255, b255, b255, b255, CONCAT('a',b255,b255), 'c' FROM t1;
+ERROR 23000: Duplicate entry 'c' for key 'PRIMARY'
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+HEX(b) HEX(b0) HEX(b1) HEX(b20) HEX(b255) HEX(pk)
+00 00 0000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00
+61 61 6161616161616161616161616161616161616161 787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878 62
+61 62 616263646566676869206B6C6D6E6F7071727374 4372656174696E6720616E2061727469636C6520666F7220746865204B6E6F776C65646765626173652069732073696D696C617220746F2061736B696E67207175657374696F6E732E2046697273742C206E6176696761746520746F207468652063617465676F727920776865726520796F75206665656C207468652061727469636C652073686F756C642062652E204F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C726561647920657869737420776869636820776F756C6420776F726B2E00000000000000000000000000000000000000000000000000000000000000 61
+ALTER TABLE t1 ADD COLUMN b257 BINARY(257) ;
+ERROR 42000: Column length too big for column 'b257' (max = 255); use BLOB or TEXT instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+b binary(1) YES NULL
+b0 binary(0) YES NULL
+b1 binary(1) YES NULL
+b20 binary(20) YES NULL
+b255 binary(255) YES NULL
+pk binary(1) NO PRI NULL
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_binary_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_binary_indexes.result
new file mode 100644
index 00000000..c5cffdc1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_binary_indexes.result
@@ -0,0 +1,80 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (b BINARY,
+b20 BINARY(20) PRIMARY KEY,
+v16 VARBINARY(16),
+v128 VARBINARY(128)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b20 A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (b,b20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+EXPLAIN SELECT HEX(b20) FROM t1 ORDER BY b20;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 20 NULL # Using index
+SELECT HEX(b20) FROM t1 ORDER BY b20;
+HEX(b20)
+6368617231000000000000000000000000000000
+6368617232000000000000000000000000000000
+6368617233000000000000000000000000000000
+6368617234000000000000000000000000000000
+EXPLAIN SELECT HEX(b20) FROM t1 IGNORE INDEX (PRIMARY) ORDER BY b20 DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using filesort
+SELECT HEX(b20) FROM t1 ORDER BY b20 DESC;
+HEX(b20)
+6368617234000000000000000000000000000000
+6368617233000000000000000000000000000000
+6368617232000000000000000000000000000000
+6368617231000000000000000000000000000000
+DROP TABLE t1;
+CREATE TABLE t1 (b BINARY,
+b20 BINARY(20),
+v16 VARBINARY(16),
+v128 VARBINARY(128),
+pk VARBINARY(10) PRIMARY KEY,
+INDEX (v16(10))
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 v16 1 v16 A 500 10 NULL YES LSMTREE
+INSERT INTO t1 (b,b20,v16,v128,pk) VALUES ('a','char1','varchar1a','varchar1b',1),('a','char2','varchar2a','varchar2b',2),('b','char3','varchar1a','varchar1b',3),('c','char4','varchar3a','varchar3b',4),('d','char5','varchar4a','varchar3b',5),('e','char6','varchar2a','varchar3b',6);
+INSERT INTO t1 (b,b20,v16,v128,pk) SELECT b,b20,v16,v128,pk+100 FROM t1;
+EXPLAIN SELECT HEX(SUBSTRING(v16,0,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range v16 v16 13 NULL # Using where
+SELECT HEX(SUBSTRING(v16,7,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+HEX(SUBSTRING(v16,7,3))
+723161
+723161
+723161
+723161
+723261
+723261
+723261
+723261
+723361
+723361
+723461
+723461
+EXPLAIN SELECT HEX(SUBSTRING(v16,0,3)) FROM t1 FORCE INDEX (v16) WHERE v16 LIKE 'varchar%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range v16 v16 13 NULL # Using where
+SELECT HEX(SUBSTRING(v16,7,3)) FROM t1 FORCE INDEX (v16) WHERE v16 LIKE 'varchar%';
+HEX(SUBSTRING(v16,7,3))
+723161
+723161
+723161
+723161
+723261
+723261
+723261
+723261
+723361
+723361
+723461
+723461
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_bit.result b/storage/rocksdb/mysql-test/rocksdb/r/type_bit.result
new file mode 100644
index 00000000..fa84cbde
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_bit.result
@@ -0,0 +1,53 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a BIT ,
+b BIT(20) ,
+c BIT(64) ,
+d BIT(1) ,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) YES NULL
+b bit(20) YES NULL
+c bit(64) NO PRI NULL
+d bit(1) YES NULL
+ALTER TABLE t1 DROP COLUMN d;
+ALTER TABLE t1 ADD COLUMN d BIT(0) ;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a bit(1) YES NULL
+b bit(20) YES NULL
+c bit(64) NO PRI NULL
+d bit(1) YES NULL
+INSERT INTO t1 (a,b,c,d) VALUES (0,POW(2,20)-1,b'1111111111111111111111111111111111111111111111111111111111111111',1);
+SELECT BIN(a), HEX(b), c+0 FROM t1 WHERE d>0;
+BIN(a) HEX(b) c+0
+0 FFFFF 18446744073709551615
+INSERT INTO t1 (a,b,c,d) VALUES (1,0,-2,0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d<100;
+a+0 b+0 c+0
+0 1048575 18446744073709551615
+1 0 18446744073709551614
+INSERT INTO t1 (a,b,c,d) VALUES (b'1', 'f', 0xFF, 0x0);
+SELECT a+0, b+0, c+0 FROM t1 WHERE d IN (0, 2);
+a+0 b+0 c+0
+1 0 18446744073709551614
+1 102 255
+DELETE FROM t1;
+INSERT INTO t1 (a,b,c,d) VALUES (0x10,0,0,1);
+Warnings:
+Warning 1264 Out of range value for column 'a' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+INSERT INTO t1 (a,b,c,d) VALUES (0x01,0,0x10000000000000000,0);
+Warnings:
+Warning 1264 Out of range value for column 'c' at row 1
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+a+0 b+0 c+0 d+0
+1 0 0 1
+1 0 18446744073709551615 0
+DROP TABLE t1;
+CREATE TABLE t1 (pk INT PRIMARY KEY, a BIT(65) ) ENGINE=rocksdb;
+ERROR 42000: Display width out of range for 'a' (max = 64)
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_bit_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_bit_indexes.result
new file mode 100644
index 00000000..8da878eb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_bit_indexes.result
@@ -0,0 +1,58 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a BIT,
+b BIT(20) PRIMARY KEY,
+c BIT(32),
+d BIT(64)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (a,b,c,d) VALUES
+(0,0xFFFFF,0,1),(0,256,0xAAA,0x12345),(1,16,0,0xFFFFFFF),(0,11,12,13),
+(1,100,101,102),(0,12,13,14),(1,13,14,15),(0,101,201,202),(1,1000,1001,1002),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF);
+EXPLAIN SELECT b+0 FROM t1 ORDER BY b;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 3 NULL # Using index
+SELECT b+0 FROM t1 ORDER BY b;
+b+0
+11
+12
+13
+16
+100
+101
+256
+1000
+65535
+1048575
+DROP TABLE t1;
+# TODO: Unique indexes are not enforced
+CREATE TABLE t1 (
+a BIT,
+b BIT(20),
+c BIT(32),
+d BIT(64),
+pk BIT(10) PRIMARY KEY,
+INDEX(a)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 a 1 a A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (a,b,c,d,pk) VALUES
+(0,0xFFFFF,0,1,1),(0,256,0xAAA,0x12345,2),(1,16,0,0xFFFFFFF,3),(0,11,12,13,4),
+(1,100,101,102,5),(0,12,13,14,6),(1,13,14,15,7),(0,101,201,202,8),(1,1000,1001,1002,9),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF,10);
+EXPLAIN SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using temporary; Using filesort
+SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+a+0
+0
+1
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_blob.result b/storage/rocksdb/mysql-test/rocksdb/r/type_blob.result
new file mode 100644
index 00000000..3b2bee74
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_blob.result
@@ -0,0 +1,57 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b BLOB ,
+b0 BLOB(0) ,
+b1 BLOB(1) ,
+b300 BLOB(300) ,
+bm BLOB(65535) ,
+b70k BLOB(70000) ,
+b17m BLOB(17000000) ,
+t TINYBLOB ,
+m MEDIUMBLOB ,
+l LONGBLOB
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b blob YES NULL
+b0 blob YES NULL
+b1 tinyblob YES NULL
+b300 blob YES NULL
+bm blob YES NULL
+b70k mediumblob YES NULL
+b17m longblob YES NULL
+t tinyblob YES NULL
+m mediumblob YES NULL
+l longblob YES NULL
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), HEX(REPEAT('g',1048576)), REPEAT('h',255), REPEAT('i',1048576), HEX(REPEAT('j',1048576)) );
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'b0' at row 1
+Warning 1265 Data truncated for column 'b1' at row 1
+Warning 1265 Data truncated for column 'b300' at row 1
+Warning 1265 Data truncated for column 'bm' at row 1
+Warning 1265 Data truncated for column 't' at row 1
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(b) LENGTH(b0) LENGTH(b1) LENGTH(b300) LENGTH(bm) LENGTH(b70k) LENGTH(b17m) LENGTH(t) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 2097152 255 1048576 2097152
+ALTER TABLE t1 ADD COLUMN bbb BLOB(4294967296);
+ERROR 42000: Display width out of range for 'bbb' (max = 4294967295)
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_blob_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_blob_indexes.result
new file mode 100644
index 00000000..1e614a2b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_blob_indexes.result
@@ -0,0 +1,198 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+b BLOB,
+t TINYBLOB,
+m MEDIUMBLOB,
+l LONGBLOB,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 b A 1000 32 NULL LSMTREE
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+EXPLAIN SELECT SUBSTRING(b,16) AS f FROM t1 WHERE b IN ('test1','test2') ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+# # # # # PRIMARY # # # #
+SELECT SUBSTRING(b,16) AS f FROM t1 WHERE b IN ('test1','test2') ORDER BY f;
+f
+
+
+EXPLAIN SELECT SUBSTRING(b,16) AS f FROM t1 USE INDEX () WHERE b IN ('test1','test2') ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+# # # # # NULL # # # #
+SELECT SUBSTRING(b,16) AS f FROM t1 USE INDEX () WHERE b IN ('test1','test2') ORDER BY f;
+f
+
+
+DROP TABLE t1;
+CREATE TABLE t1 (
+b BLOB,
+t TINYBLOB,
+m MEDIUMBLOB,
+l LONGBLOB,
+pk INT AUTO_INCREMENT PRIMARY KEY,
+UNIQUE INDEX l_t (l(256),t(64))
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk # # NULL NULL # #
+t1 0 l_t 1 l # # 256 NULL # #
+t1 0 l_t 2 t # # 64 NULL # #
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+EXPLAIN SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range l_t l_t 259 NULL # Using where; Using filesort
+SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+SUBSTRING(t,64) SUBSTRING(l,256)
+
+
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+
+
+
+EXPLAIN SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 FORCE INDEX (l_t) WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range l_t l_t 259 NULL # Using where; Using filesort
+SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 FORCE INDEX (l_t) WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+SUBSTRING(t,64) SUBSTRING(l,256)
+
+
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+
+
+
+DROP TABLE t1;
+CREATE TABLE t1 (
+b BLOB,
+t TINYBLOB,
+m MEDIUMBLOB,
+l LONGBLOB,
+pk INT AUTO_INCREMENT PRIMARY KEY,
+INDEX (m(128))
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 m 1 m A 500 128 NULL YES LSMTREE
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref m m 131 const # Using where; Using filesort
+SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+f
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 IGNORE INDEX FOR ORDER BY (m) WHERE m = 'test1' ORDER BY f DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref m m 131 const # Using where; Using filesort
+SELECT SUBSTRING(m,128) AS f FROM t1 IGNORE INDEX FOR ORDER BY (m) WHERE m = 'test1' ORDER BY f DESC;
+f
+DROP TABLE t1;
+CREATE TABLE t1 (
+b BLOB,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b TINYBLOB,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b MEDIUMBLOB,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b LONGBLOB,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_bool.result b/storage/rocksdb/mysql-test/rocksdb/r/type_bool.result
new file mode 100644
index 00000000..bfe2c18a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_bool.result
@@ -0,0 +1,73 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+b1 BOOL ,
+b2 BOOLEAN
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+b1 tinyint(1) YES NULL
+b2 tinyint(1) YES NULL
+INSERT INTO t1 (b1,b2) VALUES (1,TRUE);
+SELECT b1,b2 FROM t1;
+b1 b2
+1 1
+INSERT INTO t1 (b1,b2) VALUES (FALSE,0);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+INSERT INTO t1 (b1,b2) VALUES (2,3);
+SELECT b1,b2 FROM t1;
+b1 b2
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (-1,-2);
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+1 1
+2 3
+SELECT IF(b1,'true','false') AS a, IF(b2,'true','false') AS b FROM t1;
+a b
+false false
+true true
+true true
+true true
+SELECT b1,b2 FROM t1 WHERE b1 = TRUE;
+b1 b2
+1 1
+SELECT b1,b2 FROM t1 WHERE b2 = FALSE;
+b1 b2
+0 0
+INSERT INTO t1 (b1,b2) VALUES ('a','b');
+Warnings:
+Warning 1366 Incorrect integer value: 'a' for column `test`.`t1`.`b1` at row 1
+Warning 1366 Incorrect integer value: 'b' for column `test`.`t1`.`b2` at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+2 3
+INSERT INTO t1 (b1,b2) VALUES (128,-129);
+Warnings:
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b2' at row 1
+SELECT b1,b2 FROM t1;
+b1 b2
+-1 -2
+0 0
+0 0
+1 1
+127 -128
+2 3
+ALTER TABLE t1 ADD COLUMN b3 BOOLEAN UNSIGNED ;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UNSIGNED' at line 1
+ALTER TABLE ADD COLUMN b3 BOOL ZEROFILL ;
+ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'ADD COLUMN b3 BOOL ZEROFILL' at line 1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_char.result b/storage/rocksdb/mysql-test/rocksdb/r/type_char.result
new file mode 100644
index 00000000..1786dfae
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_char.result
@@ -0,0 +1,76 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c CHAR ,
+c0 CHAR(0) ,
+c1 CHAR(1) ,
+c20 CHAR(20) ,
+c255 CHAR(255) ,
+PRIMARY KEY (c255)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+c char(1) YES NULL
+c0 char(0) YES NULL
+c1 char(1) YES NULL
+c20 char(20) YES NULL
+c255 char(255) NO PRI NULL
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('','','','','');
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.');
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256));
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+Warning 1265 Data truncated for column 'c0' at row 1
+Warning 1265 Data truncated for column 'c1' at row 1
+Warning 1265 Data truncated for column 'c20' at row 1
+Warning 1265 Data truncated for column 'c255' at row 1
+INSERT INTO t1 (c,c0,c1,c20,c255) SELECT c255, c255, c255, c255, CONCAT('a',c255,c1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 5
+Warning 1265 Data truncated for column 'c0' at row 5
+Warning 1265 Data truncated for column 'c1' at row 5
+Warning 1265 Data truncated for column 'c20' at row 5
+Warning 1265 Data truncated for column 'c' at row 6
+Warning 1265 Data truncated for column 'c0' at row 6
+Warning 1265 Data truncated for column 'c1' at row 6
+Warning 1265 Data truncated for column 'c20' at row 6
+Warning 1265 Data truncated for column 'c255' at row 6
+SELECT c,c0,c1,c20,c255 FROM t1;
+c c0 c1 c20 c255
+
+ a
+C C Creating an article aCreating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.b
+a a aaaaaaaaaaaaaaaaaaaa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+a b abcdefghi klmnopqrst Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn't already exist which would work.
+x x xxxxxxxxxxxxxxxxxxxx axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+SELECT DISTINCT c20, REPEAT('a',LENGTH(c20)), COUNT(*) FROM t1 GROUP BY c1, c20;
+c20 REPEAT('a',LENGTH(c20)) COUNT(*)
+ 2
+Creating an article aaaaaaaaaaaaaaaaaaa 1
+aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa 1
+abcdefghi klmnopqrst aaaaaaaaaaaaaaaaaaaa 1
+xxxxxxxxxxxxxxxxxxxx aaaaaaaaaaaaaaaaaaaa 1
+ALTER TABLE t1 ADD COLUMN c257 CHAR(257) ;
+ERROR 42000: Column length too big for column 'c257' (max = 255); use BLOB or TEXT instead
+DROP TABLE t1;
+CREATE TABLE t1(c1 CHAR(0) NOT NULL);
+INSERT INTO t1 VALUES('a');
+Warnings:
+Warning 1265 Data truncated for column 'c1' at row 1
+SELECT * FROM t1;
+c1
+
+DROP TABLE t1;
+CREATE TABLE t1(a char(10) character set utf8 collate utf8_bin primary key);
+INSERT INTO t1 VALUES ('one'),('two'),('three'),('four'),('five');
+(SELECT * FROM t1 LIMIT 1) UNION (SELECT * FROM t1);
+a
+five
+four
+one
+three
+two
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes.result
new file mode 100644
index 00000000..3a99e6a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes.result
@@ -0,0 +1,73 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c CHAR,
+c20 CHAR(20) PRIMARY KEY,
+v16 VARCHAR(16),
+v128 VARCHAR(128)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 c20 A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (c,c20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+EXPLAIN SELECT c20 FROM t1 ORDER BY c20;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 20 NULL # Using index
+SELECT c20 FROM t1 ORDER BY c20;
+c20
+char1
+char2
+char3
+char4
+EXPLAIN SELECT c20 FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY c20;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 20 NULL # Using index
+SELECT c20 FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY c20;
+c20
+char1
+char2
+char3
+char4
+DROP TABLE t1;
+CREATE TABLE t1 (
+c CHAR,
+c20 CHAR(20),
+v16 VARCHAR(16),
+v128 VARCHAR(128),
+pk VARCHAR(64) PRIMARY KEY,
+INDEX (v16)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 v16 1 v16 A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (c,c20,v16,v128,pk) VALUES ('a','char1','varchar1a','varchar1b','1'),('a','char2','varchar2a','varchar2b','2'),('b','char3','varchar1a','varchar1b','3'),('c','char4','varchar3a','varchar3b','4');
+EXPLAIN SELECT SUBSTRING(v16,0,3) FROM t1 WHERE v16 LIKE 'varchar%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range v16 v16 19 NULL # Using where; Using index
+SELECT SUBSTRING(v16,7,3) FROM t1 WHERE v16 LIKE 'varchar%';
+SUBSTRING(v16,7,3)
+r1a
+r1a
+r2a
+r3a
+EXPLAIN SELECT SUBSTRING(v16,0,3) FROM t1 IGNORE INDEX (v16) WHERE v16 LIKE 'varchar%';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using where
+SELECT SUBSTRING(v16,7,3) FROM t1 IGNORE INDEX (v16) WHERE v16 LIKE 'varchar%';
+SUBSTRING(v16,7,3)
+r1a
+r1a
+r2a
+r3a
+EXPLAIN SELECT c,c20,v16,v128 FROM t1 WHERE v16 = 'varchar1a' OR v16 = 'varchar3a' ORDER BY v16;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL v16 NULL NULL NULL # Using where; Using filesort
+SELECT c,c20,v16,v128 FROM t1 WHERE v16 = 'varchar1a' OR v16 = 'varchar3a' ORDER BY v16;
+c c20 v16 v128
+a char1 varchar1a varchar1b
+b char3 varchar1a varchar1b
+c char4 varchar3a varchar3b
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes_collation.result b/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes_collation.result
new file mode 100644
index 00000000..cb560895
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_char_indexes_collation.result
@@ -0,0 +1,91 @@
+set session debug_dbug= "+d,myrocks_enable_unknown_collation_index_only_scans";
+create table t (id int not null auto_increment primary key,
+c varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci,
+key sk (c));
+insert into t (c) values ('☀'), ('ß');
+explain select c from t;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL sk 27 NULL # Using index
+select c from t;
+c
+ß
+☀
+drop table t;
+set session debug_dbug= "-d,myrocks_enable_unknown_collation_index_only_scans";
+create table t (id int not null auto_increment,
+c1 varchar(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+c2 char(1) CHARACTER SET latin1 COLLATE latin1_general_ci,
+primary key (id),
+key sk1 (c1),
+key sk2 (c2));
+explain select hex(c1) from t order by c1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL sk1 4 NULL # Using index
+explain select hex(c1) from t IGNORE INDEX (sk1) order by c1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ALL NULL NULL NULL NULL # Using filesort
+explain select hex(c2) from t order by c2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL sk2 2 NULL # Using index
+explain select hex(c2) from t IGNORE INDEX (sk1) order by c2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t index NULL sk2 2 NULL # Using index
+truncate t;
+insert into t (c1, c2) values ('Asdf ', 'Asdf ');
+Warnings:
+Warning 1265 Data truncated for column 'c1' at row 1
+Warning 1265 Data truncated for column 'c2' at row 1
+select char_length(c1), char_length(c2), c1, c2 from t;
+char_length(c1) char_length(c2) c1 c2
+1 1 A A
+drop table t;
+create table t (id int not null auto_increment,
+c2 char(255) CHARACTER SET latin1 COLLATE latin1_general_ci,
+primary key (id),
+unique key sk2 (c2));
+insert into t (c2) values ('Asdf');
+insert into t (c2) values ('asdf ');
+ERROR 23000: Duplicate entry 'asdf' for key 'sk2'
+drop table t;
+create table t (id int not null auto_increment,
+c1 varchar(256) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+primary key (id),
+unique key sk1 (c1));
+insert into t (c1) values ('Asdf');
+insert into t (c1) values ('asdf ');
+ERROR 23000: Duplicate entry 'asdf ' for key 'sk1'
+insert into t (c1) values ('asdf');
+ERROR 23000: Duplicate entry 'asdf' for key 'sk1'
+drop table t;
+create table t (id int not null auto_increment,
+c1 varchar(256) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+primary key (id),
+unique key sk1 (c1(1)));
+insert into t (c1) values ('Asdf');
+insert into t (c1) values ('bbbb ');
+insert into t (c1) values ('a ');
+ERROR 23000: Duplicate entry 'a' for key 'sk1'
+explain select c1 from t;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ALL NULL NULL NULL NULL #
+select c1 from t;
+c1
+Asdf
+bbbb
+drop table t;
+set session rocksdb_verify_row_debug_checksums = on;
+create table t (id int primary key, email varchar(100), KEY email_i (email(30))) engine=rocksdb default charset=latin1;
+insert into t values (1, ' a');
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ALL NULL NULL NULL NULL #
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+index_name count
+email_i 1
+drop table t;
+create table t (id int primary key, email varchar(767), KEY email_i (email)) engine=rocksdb default charset=latin1;
+insert into t values (1, REPEAT('a', 700));
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+index_name count
+email_i 1
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_date_time.result b/storage/rocksdb/mysql-test/rocksdb/r/type_date_time.result
new file mode 100644
index 00000000..91e7ac25
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_date_time.result
@@ -0,0 +1,56 @@
+DROP TABLE IF EXISTS t1;
+set @save_time_zone=@@time_zone;
+set time_zone='UTC';
+CREATE TABLE t1 (
+d DATE ,
+dt DATETIME ,
+ts TIMESTAMP ,
+t TIME ,
+y YEAR ,
+y4 YEAR(4) ,
+y2 YEAR(2) ,
+pk DATETIME PRIMARY KEY
+) ENGINE=rocksdb;
+Warnings:
+Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d date YES NULL
+dt datetime YES NULL
+ts timestamp YES NULL
+t time YES NULL
+y year(4) YES NULL
+y4 year(4) YES NULL
+y2 year(2) YES NULL
+pk datetime NO PRI NULL
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('1000-01-01', '1000-01-01 00:00:00', FROM_UNIXTIME(1), '-838:59:59', '1901', '1901', '00','2012-12-12 12:12:12'),
+('9999-12-31', '9999-12-31 23:59:59', FROM_UNIXTIME(2147483647), '838:59:59', '2155', '2155', '99','2012-12-12 12:12:13'),
+('0000-00-00', '0000-00-00 00:00:00', '0000-00-00 00:00:00', '00:00:00', '0', '0', '0','2012-12-12 12:12:14'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),YEAR(@tm),YEAR(@tm),'2012-12-12 12:12:15');
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('999-13-32', '999-11-31 00:00:00', '0', '-839:00:00', '1900', '1900', '-1','2012-12-12 12:12:16');
+Warnings:
+Warning 1265 Data truncated for column 'd' at row 1
+Warning 1265 Data truncated for column 'dt' at row 1
+Warning 1265 Data truncated for column 'ts' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 'y' at row 1
+Warning 1264 Out of range value for column 'y4' at row 1
+Warning 1264 Out of range value for column 'y2' at row 1
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+d dt ts t y y4 y2
+1000-01-01 1000-01-01 00:00:00 1970-01-01 00:00:01 -838:59:59 1901 1901 00
+9999-12-31 9999-12-31 23:59:59 2038-01-19 03:14:07 838:59:59 2155 2155 99
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 00:00:00 2000 2000 00
+2012-04-09 2012-04-09 05:27:00 2012-04-09 05:27:00 05:27:00 2012 2012 12
+0000-00-00 0000-00-00 00:00:00 0000-00-00 00:00:00 -838:59:59 0000 0000 00
+set time_zone=@save_time_zone;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_date_time_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_date_time_indexes.result
new file mode 100644
index 00000000..77d07035
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_date_time_indexes.result
@@ -0,0 +1,119 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DATE,
+dt DATETIME PRIMARY KEY,
+ts TIMESTAMP,
+t TIME,
+y YEAR
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 dt A 1000 NULL NULL LSMTREE
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm));
+EXPLAIN SELECT dt FROM t1 ORDER BY dt LIMIT 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 5 NULL # Using index
+SELECT dt FROM t1 ORDER BY dt LIMIT 3;
+dt
+2010-11-22 11:43:14
+2010-11-22 12:33:54
+2011-08-27 21:33:56
+EXPLAIN SELECT dt FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY dt LIMIT 3;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 5 NULL # Using index
+SELECT dt FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY dt LIMIT 3;
+dt
+2010-11-22 11:43:14
+2010-11-22 12:33:54
+2011-08-27 21:33:56
+INSERT INTO t1 (d,dt,ts,t,y) VALUES
+('2012-01-11', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000');
+ERROR 23000: Duplicate entry '2010-11-22 12:33:54' for key 'PRIMARY'
+DROP TABLE t1;
+CREATE TABLE t1 (
+d DATE,
+dt DATETIME,
+ts TIMESTAMP,
+t TIME,
+y YEAR,
+pk TIME PRIMARY KEY,
+INDEX (ts)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 ts 1 ts A 500 NULL NULL YES LSMTREE
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y,pk) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000','12:00:00'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001','12:01:00'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999','12:02:00'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998','12:03:00'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994','12:04:00'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),'12:05:00');
+EXPLAIN SELECT ts FROM t1 WHERE ts > NOW();
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range ts ts 5 NULL # Using where; Using index
+SELECT ts FROM t1 WHERE ts > NOW();
+ts
+EXPLAIN SELECT ts FROM t1 USE INDEX () WHERE ts > NOW();
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using where
+SELECT ts FROM t1 USE INDEX () WHERE ts > NOW();
+ts
+DROP TABLE t1;
+CREATE TABLE t1 (
+d DATE,
+dt DATETIME,
+ts TIMESTAMP,
+t TIME,
+y YEAR,
+pk TIME PRIMARY KEY,
+INDEX (y,t)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 y 1 y A 250 NULL NULL YES LSMTREE
+t1 1 y 2 t A 500 NULL NULL YES LSMTREE
+SET @tm = '2012-04-09 05:27:00';
+INSERT INTO t1 (d,dt,ts,t,y,pk) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000','18:18:18'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001','19:18:18'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999','20:18:18'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998','21:18:18'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994','22:18:18'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),'23:18:18');
+EXPLAIN SELECT y, COUNT(*) FROM t1 GROUP BY y;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL y 6 NULL # Using index
+SELECT y, COUNT(*) FROM t1 GROUP BY y;
+y COUNT(*)
+1994 1
+1998 1
+1999 1
+2000 1
+2001 1
+2012 1
+EXPLAIN SELECT y, COUNT(*) FROM t1 USE INDEX FOR GROUP BY () GROUP BY y;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL y 6 NULL # Using index; Using temporary; Using filesort
+SELECT y, COUNT(*) FROM t1 USE INDEX FOR GROUP BY () GROUP BY y;
+y COUNT(*)
+1994 1
+1998 1
+1999 1
+2000 1
+2001 1
+2012 1
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_decimal.result b/storage/rocksdb/mysql-test/rocksdb/r/type_decimal.result
new file mode 100644
index 00000000..668a9276
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_decimal.result
@@ -0,0 +1,105 @@
+drop table if exists t1, t2;
+#
+# Check that DECIMAL datatype supports 'index-only' scans and is decoded correctly.
+# (Decoding happens from the mem-comparable image in the index, regardless
+# of whether the value part has original value or not)
+#
+create table t1 (
+pk int not null primary key,
+col1 decimal (2,1) signed,
+col2 decimal (2,1) unsigned,
+filler varchar(100),
+key key1(col1, col2)
+)engine=rocksdb;
+insert into t1 values
+(1,-9.1, 0.7, 'filler'),
+(2,-8.2, 1.6, 'filler'),
+(3, 0.3, 2.5, 'filler'),
+(4, 1.4, 3.4, 'filler'),
+(5, 2.5, 4.3, 'filler'),
+(6, 3.3, 5.3, 'filler');
+insert into t1 select pk+100, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+200, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+1000, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+10000, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+100000, 9.0, 9.0, 'extra-data' from t1;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# The following can't use index-only:
+explain select * from t1 where col1 between -8 and 8;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range key1 key1 3 NULL # Using index condition
+# This will use index-only:
+explain
+select col1, col2 from t1 where col1 between -8 and 8;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range key1 key1 3 NULL # Using where; Using index
+select col1, col2 from t1 where col1 between -8 and 8;
+col1 col2
+0.3 2.5
+1.4 3.4
+2.5 4.3
+3.3 5.3
+insert into t1 values (11, NULL, 0.9, 'row1-with-null');
+insert into t1 values (10, -8.4, NULL, 'row2-with-null');
+explain
+select col1, col2 from t1 force index(key1) where col1 is null or col1 < -7;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range key1 key1 3 NULL # Using where; Using index
+select col1, col2 from t1 force index(key1) where col1 is null or col1 < -7;
+col1 col2
+NULL 0.9
+-9.1 0.7
+-8.4 NULL
+-8.2 1.6
+# Try an UPDATE
+select * from t1 where pk in (3,4);
+pk col1 col2 filler
+3 0.3 2.5 filler
+4 1.4 3.4 filler
+update t1 set col2= col2+0.2 where pk in (3,4);
+select * from t1 where pk in (3,4);
+pk col1 col2 filler
+3 0.3 2.7 filler
+4 1.4 3.6 filler
+drop table t1;
+#
+# Try another DECIMAL-based type that takes more space
+#
+create table t1 (
+pk int not null primary key,
+col1 decimal (12,6) signed,
+col2 decimal (12,6) unsigned,
+filler varchar(100),
+key key1(col1, col2)
+)engine=rocksdb;
+insert into t1 values
+(1,-900.001, 000.007, 'filler'),
+(2,-700.002, 100.006, 'filler'),
+(3, 000.003, 200.005, 'filler'),
+(4, 100.004, 300.004, 'filler'),
+(5, 200.005, 400.003, 'filler'),
+(6, 300.003, 500.003, 'filler');
+insert into t1 select pk+100, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+200, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+1000, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+10000, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+100000, col1+20000, col2+20000, 'extra-data' from t1;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+explain
+select col1, col2 from t1 force index(key1) where col1 between -800 and 800;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range key1 key1 7 NULL # Using where; Using index
+select col1, col2 from t1 force index(key1) where col1 between -800 and 800;
+col1 col2
+-700.002000 100.006000
+0.003000 200.005000
+100.004000 300.004000
+200.005000 400.003000
+300.003000 500.003000
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_enum.result b/storage/rocksdb/mysql-test/rocksdb/r/type_enum.result
new file mode 100644
index 00000000..ed66303c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_enum.result
@@ -0,0 +1,47 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a ENUM('') ,
+b ENUM('test1','test2','test3','test4','test5') ,
+c ENUM('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') ,
+PRIMARY KEY (b)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') YES NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') YES NULL
+INSERT INTO t1 (a,b,c) VALUES ('','test2','4'),('',5,2);
+SELECT a,b,c FROM t1;
+a b c
+ test2 4
+ test5 2
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'a' at row 1
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+
+ test2 4
+ test5 2
+ALTER TABLE t1 ADD COLUMN e ENUM('a','A') ;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in ENUM
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a enum('') YES NULL
+b enum('test1','test2','test3','test4','test5') NO PRI NULL
+c enum('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') YES NULL
+e enum('a','A') YES NULL
+INSERT INTO t1 (a,b,c,e) VALUES ('','test3','75','A');
+SELECT a,b,c,e FROM t1;
+a b c e
+ NULL
+ test2 4 NULL
+ test3 75 a
+ test5 2 NULL
+SELECT a,b,c,e FROM t1 WHERE b='test2' OR a != '';
+a b c e
+ test2 4 NULL
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_enum_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_enum_indexes.result
new file mode 100644
index 00000000..70bbc840
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_enum_indexes.result
@@ -0,0 +1,69 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a ENUM('N.America','S.America','Africa','Europe','Australia','Asia','Antarctica'),
+b ENUM('test1','test2','test3','test4','test5'),
+c ENUM('1a','1b','1d','1j','4a','4z','5a','5b','6v','6z') PRIMARY KEY
+) ENGINE=rocksdb;
+INSERT INTO t1 (a,b,c) VALUES
+('N.America','test1','5a'),('Europe','test1','5b'),('Europe','test2','6v'),
+('Africa','test3','4z'),('Africa','test4','1j'),('Antarctica','test4','1d');
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 c A 1000 NULL NULL LSMTREE
+EXPLAIN SELECT c FROM t1 WHERE c BETWEEN '1d' AND '6u';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index PRIMARY PRIMARY 1 NULL # Using where; Using index
+SELECT c FROM t1 WHERE c BETWEEN '1d' AND '6u';
+c
+1d
+1j
+4z
+5a
+5b
+EXPLAIN SELECT c FROM t1 USE INDEX () WHERE c BETWEEN '1d' AND '6u';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using where
+SELECT c FROM t1 USE INDEX () WHERE c BETWEEN '1d' AND '6u';
+c
+1d
+1j
+4z
+5a
+5b
+DROP TABLE t1;
+CREATE TABLE t1 (
+a ENUM('N.America','S.America','Africa','Europe','Australia','Asia','Antarctica'),
+b ENUM('test1','test2','test3','test4','test5'),
+c ENUM('1a','1b','1d','1j','4a','4z','5a','5b','6v','6z'),
+pk ENUM('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+INDEX(b)
+) ENGINE=rocksdb;
+INSERT INTO t1 (a,b,c,pk) VALUES
+('N.America','test1','5a',1),('Europe','test1','5b',2),('Europe','test2','6v',3),
+('Africa','test3','4z',4),('Africa','test4','1j',5),('Antarctica','test4','1d',6);
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 b 1 b A 500 NULL NULL YES LSMTREE
+EXPLAIN SELECT DISTINCT b FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL b 2 NULL #
+SELECT DISTINCT b FROM t1;
+b
+test1
+test2
+test3
+test4
+EXPLAIN SELECT DISTINCT b FROM t1 IGNORE INDEX (b);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using temporary
+SELECT DISTINCT b FROM t1 IGNORE INDEX (b);
+b
+test1
+test2
+test3
+test4
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_fixed.result b/storage/rocksdb/mysql-test/rocksdb/r/type_fixed.result
new file mode 100644
index 00000000..055952ea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_fixed.result
@@ -0,0 +1,131 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d DECIMAL ,
+d0 DECIMAL(0) ,
+d1_1 DECIMAL(1,1) ,
+d10_2 DECIMAL(10,2) ,
+d60_10 DECIMAL(60,10) ,
+n NUMERIC ,
+n0_0 NUMERIC(0,0) ,
+n1 NUMERIC(1) ,
+n20_4 NUMERIC(20,4) ,
+n65_4 NUMERIC(65,4) ,
+pk NUMERIC PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+d decimal(10,0) YES NULL
+d0 decimal(10,0) YES NULL
+d1_1 decimal(1,1) YES NULL
+d10_2 decimal(10,2) YES NULL
+d60_10 decimal(60,10) YES NULL
+n decimal(10,0) YES NULL
+n0_0 decimal(10,0) YES NULL
+n1 decimal(1,0) YES NULL
+n20_4 decimal(20,4) YES NULL
+n65_4 decimal(65,4) YES NULL
+pk decimal(10,0) NO PRI NULL
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+6
+);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+Warnings:
+Warning 1264 Out of range value for column 'd' at row 1
+Warning 1264 Out of range value for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Warning 1264 Out of range value for column 'd10_2' at row 1
+Warning 1264 Out of range value for column 'd60_10' at row 1
+Warning 1264 Out of range value for column 'n' at row 1
+Warning 1264 Out of range value for column 'n0_0' at row 1
+Warning 1264 Out of range value for column 'n1' at row 1
+Warning 1264 Out of range value for column 'n20_4' at row 1
+Warning 1264 Out of range value for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+Warnings:
+Note 1265 Data truncated for column 'd' at row 1
+Note 1265 Data truncated for column 'd0' at row 1
+Warning 1264 Out of range value for column 'd1_1' at row 1
+Note 1265 Data truncated for column 'd10_2' at row 1
+Note 1265 Data truncated for column 'd60_10' at row 1
+Note 1265 Data truncated for column 'n' at row 1
+Note 1265 Data truncated for column 'n0_0' at row 1
+Note 1265 Data truncated for column 'n1' at row 1
+Note 1265 Data truncated for column 'n20_4' at row 1
+Note 1265 Data truncated for column 'n65_4' at row 1
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+d d0 d1_1 d10_2 d60_10 n n0_0 n1 n20_4 n65_4
+-100 -123456 -0.3 -40000.25 -123456789123456789.1000100000 -1024 -7000 -8 -999999.9000 -9223372036854775807.0000
+-9999999999 -9999999999 -0.9 -99999999.99 -99999999999999999999999999999999999999999999999999.9999999999 -9999999999 -9999999999 -9 -9999999999999999.9999 -9999999999999999999999999999999999999999999999999999999999999.9999
+0 0 0.0 0.00 0.0000000000 0 0 0 0.0000 0.0000
+100 123456 0.3 40000.25 123456789123456789.1000100000 1024 7000 8 999999.9000 9223372036854775807.0000
+9999999999 9999999999 0.9 99999999.00 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.0000 9999999999999999999999999999999999999999999999999999999999999.1111
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+9999999999 9999999999 0.9 99999999.99 99999999999999999999999999999999999999999999999999.9999999999 9999999999 9999999999 9 9999999999999999.9999 9999999999999999999999999999999999999999999999999999999999999.9999
+ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) ;
+ERROR 42000: Too big precision 66 specified for 'n66'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) ;
+ERROR 42000: Too big precision 66 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) ;
+ERROR 42000: Too big scale 66 specified for 'n66_66'. Maximum is 38
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_fixed_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_fixed_indexes.result
new file mode 100644
index 00000000..3f6a0f5d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_fixed_indexes.result
@@ -0,0 +1,129 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+d1 DECIMAL(10,2) PRIMARY KEY,
+d2 DECIMAL(60,10),
+n1 NUMERIC,
+n2 NUMERIC(65,4)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 d1 A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (d1,d2,n1,n2) VALUES
+(10.22,60.12345,123456,14.3456),
+(10.0,60.12345,123456,14),
+(11.14,15,123456,13),
+(100,100,1,2),
+(0,0,0,0),
+(4540424564.23,3343303441.0,12,13),
+(15,17,23,100000);
+Warnings:
+Warning 1264 Out of range value for column 'd1' at row 6
+EXPLAIN SELECT d1 FROM t1 ORDER BY d1 DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 5 NULL # Using index
+SELECT d1 FROM t1 ORDER BY d1 DESC;
+d1
+99999999.99
+100.00
+15.00
+11.14
+10.22
+10.00
+0.00
+EXPLAIN SELECT d1 FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY d1 DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 5 NULL # Using index; Using filesort
+SELECT d1 FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY d1 DESC;
+d1
+99999999.99
+100.00
+15.00
+11.14
+10.22
+10.00
+0.00
+DROP TABLE t1;
+CREATE TABLE t1 (
+d1 DECIMAL(10,2),
+d2 DECIMAL(60,10),
+n1 NUMERIC,
+n2 NUMERIC(65,4),
+pk NUMERIC PRIMARY KEY,
+UNIQUE INDEX n1_n2 (n1,n2)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 0 n1_n2 1 n1 A 500 NULL NULL YES LSMTREE
+t1 0 n1_n2 2 n2 A 1000 NULL NULL YES LSMTREE
+INSERT INTO t1 (d1,d2,n1,n2,pk) VALUES
+(10.22,60.12345,123456,14.3456,1),
+(10.0,60.12345,123456,14,2),
+(11.14,15,123456,13,3),
+(100,100,1,2,4),
+(0,0,0,0,5),
+(4540424564.23,3343303441.0,12,13,6),
+(15,17,23,100000,7);
+Warnings:
+Warning 1264 Out of range value for column 'd1' at row 6
+EXPLAIN SELECT DISTINCT n1+n2 FROM t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL n1_n2 37 NULL # Using index; Using temporary
+SELECT DISTINCT n1+n2 FROM t1;
+n1+n2
+0.0000
+100023.0000
+123469.0000
+123470.0000
+123470.3456
+25.0000
+3.0000
+DROP TABLE t1;
+CREATE TABLE t1 (
+d1 DECIMAL(10,2),
+d2 DECIMAL(60,10),
+n1 NUMERIC,
+n2 NUMERIC(65,4),
+pk DECIMAL(20,10) PRIMARY KEY,
+INDEX (d2)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 d2 1 d2 A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (d1,d2,n1,n2,pk) VALUES
+(10.22,60.12345,123456,14.3456,1),
+(10.0,60.12345,123456,14,2),
+(11.14,15,123456,13,3),
+(100,100,1,2,4),
+(0,0,0,0,5),
+(4540424564.23,3343303441.0,12,13,6),
+(15,17,23,100000,7);
+Warnings:
+Warning 1264 Out of range value for column 'd1' at row 6
+EXPLAIN SELECT d2, COUNT(*) FROM t1 GROUP BY d2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL d2 29 NULL # Using index
+SELECT d2, COUNT(*) FROM t1 GROUP BY d2;
+d2 COUNT(*)
+0.0000000000 1
+100.0000000000 1
+15.0000000000 1
+17.0000000000 1
+3343303441.0000000000 1
+60.1234500000 2
+EXPLAIN SELECT d2, COUNT(*) FROM t1 IGNORE INDEX FOR GROUP BY (d2) GROUP BY d2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL d2 29 NULL # Using index; Using temporary; Using filesort
+SELECT d2, COUNT(*) FROM t1 IGNORE INDEX FOR GROUP BY (d2) GROUP BY d2;
+d2 COUNT(*)
+0.0000000000 1
+100.0000000000 1
+15.0000000000 1
+17.0000000000 1
+3343303441.0000000000 1
+60.1234500000 2
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_float.result b/storage/rocksdb/mysql-test/rocksdb/r/type_float.result
new file mode 100644
index 00000000..371b550d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_float.result
@@ -0,0 +1,314 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT ,
+f0 FLOAT(0) ,
+r1_1 REAL(1,1) ,
+f23_0 FLOAT(23) ,
+f20_3 FLOAT(20,3) ,
+d DOUBLE ,
+d1_0 DOUBLE(1,0) ,
+d10_10 DOUBLE PRECISION (10,10) ,
+d53 DOUBLE(53,0) ,
+d53_10 DOUBLE(53,10) ,
+pk DOUBLE PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+f float YES NULL
+f0 float YES NULL
+r1_1 double(1,1) YES NULL
+f23_0 float YES NULL
+f20_3 float(20,3) YES NULL
+d double YES NULL
+d1_0 double(1,0) YES NULL
+d10_10 double(10,10) YES NULL
+d53 double(53,0) YES NULL
+d53_10 double(53,10) YES NULL
+pk double NO PRI NULL
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 11111111.111
+d10_10 0.0123456789
+d1_0 8
+d53 1234566789123456800
+d53_10 100000000000000000.0000000000
+f0 12345.1
+f20_3 56789.988
+f23_0 123457000
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+99999999999999999999999999999999999999,
+99999999999999999999999999999999999999.9999999999999999,
+0.9,
+99999999999999999999999999999999999999.9,
+99999999999999999.999,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+9,
+0.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+3
+);
+Warnings:
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d 0
+d 11111111.111
+d 1e81
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 0
+d1_0 8
+d1_0 9
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f 0
+f 1e38
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+SELECT
+CONCAT('', MAX(f)),
+CONCAT('', MAX(f0)),
+CONCAT('', MAX(r1_1)),
+CONCAT('', MAX(f23_0)),
+CONCAT('', MAX(f20_3)),
+CONCAT('', MAX(d)),
+CONCAT('', MAX(d1_0)),
+CONCAT('', MAX(d10_10)),
+CONCAT('', MAX(d53)),
+CONCAT('', MAX(d53_10)) FROM t1;
+CONCAT('', MAX(f)) 1e38
+CONCAT('', MAX(d)) 1e81
+CONCAT('', MAX(d10_10)) 0.9999999999
+CONCAT('', MAX(d1_0)) 9
+CONCAT('', MAX(d53)) 100000000000000000000000000000000000000000000000000000
+CONCAT('', MAX(d53_10)) 10000000000000000000000000000000000000000000.0000000000
+CONCAT('', MAX(f0)) 1e38
+CONCAT('', MAX(f20_3)) 99999998430674940.000
+CONCAT('', MAX(f23_0)) 1e38
+CONCAT('', MAX(r1_1)) 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+9999999999999999999999999999999999999999999999999999999999999.9999,
+5
+);
+Warnings:
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+999999999999999999999999999999999999999,
+999999999999999999999999999999999999999.9999999999999999,
+1.9,
+999999999999999999999999999999999999999.9,
+999999999999999999.999,
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+99,
+1.9999999999,
+1999999999999999999999999999999999999999999999999999999,
+19999999999999999999999999999999999999999999.9999999999,
+6
+);
+Warnings:
+Warning 1916 Got overflow when converting '' to DECIMAL. Value truncated
+Warning 1264 Out of range value for column 'f' at row 1
+Warning 1264 Out of range value for column 'f0' at row 1
+Warning 1264 Out of range value for column 'r1_1' at row 1
+Warning 1264 Out of range value for column 'f23_0' at row 1
+Warning 1264 Out of range value for column 'f20_3' at row 1
+Warning 1264 Out of range value for column 'd1_0' at row 1
+Warning 1264 Out of range value for column 'd10_10' at row 1
+Warning 1264 Out of range value for column 'd53' at row 1
+Warning 1264 Out of range value for column 'd53_10' at row 1
+SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1;
+f 12345.1
+d -1e60
+d 0
+d 11111111.111
+d 1e61
+d 1e65
+d 1e81
+d10_10 -0.9999999999
+d10_10 0.0000000000
+d10_10 0.0123456789
+d10_10 0.9999999999
+d10_10 0.9999999999
+d10_10 0.9999999999
+d1_0 -9
+d1_0 0
+d1_0 8
+d1_0 9
+d1_0 9
+d1_0 9
+d53 -1000000000000000000000000000000
+d53 0
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 100000000000000000000000000000000000000000000000000000
+d53 1234566789123456800
+d53_10 -10000000000000000000000000000000000000000000.0000000000
+d53_10 0.0000000000
+d53_10 100000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+d53_10 10000000000000000000000000000000000000000000.0000000000
+f -1e24
+f 0
+f 1e38
+f 3.40282e38
+f 3.40282e38
+f0 -100000000000
+f0 0
+f0 12345.1
+f0 1e38
+f0 3.40282e38
+f0 3.40282e38
+f20_3 -99999998430674940.000
+f20_3 0.000
+f20_3 56789.988
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f20_3 99999998430674940.000
+f23_0 -1000
+f23_0 0
+f23_0 123457000
+f23_0 1e38
+f23_0 3.40282e38
+f23_0 3.40282e38
+r1_1 -0.9
+r1_1 0.0
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+r1_1 0.9
+ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) ;
+ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) ;
+ERROR 42000: Too big precision 256 specified for 'n66_6'. Maximum is 65
+ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) ;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_float_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_float_indexes.result
new file mode 100644
index 00000000..ca6ed6d5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_float_indexes.result
@@ -0,0 +1,189 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+f FLOAT PRIMARY KEY,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 f A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (f,r,d,dp) VALUES
+(1.2345,1422.22,1.2345,1234567.89),
+(0,0,0,0),
+(-1,-1,-1,-1),
+(17.5843,4953453454.44,29229114.0,1111111.23),
+(4644,1422.22,466664.999,0.5);
+EXPLAIN SELECT f FROM t1 ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 4 NULL # Using index
+SELECT f FROM t1 ORDER BY f;
+f
+-1
+0
+1.2345
+17.5843
+4644
+EXPLAIN SELECT f FROM t1 IGNORE INDEX (PRIMARY) ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using filesort
+SELECT f FROM t1 IGNORE INDEX (PRIMARY) ORDER BY f;
+f
+-1
+0
+1.2345
+17.5843
+4644
+DROP TABLE t1;
+CREATE TABLE t1 (
+f FLOAT,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10),
+pk DOUBLE PRIMARY KEY,
+UNIQUE KEY r_dp (r,dp)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 0 r_dp 1 r A 500 NULL NULL YES LSMTREE
+t1 0 r_dp 2 dp A 1000 NULL NULL YES LSMTREE
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+EXPLAIN SELECT r, dp FROM t1 WHERE r > 0 or dp > 0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index r_dp r_dp 18 NULL # Using where; Using index
+SELECT r, dp FROM t1 WHERE r > 0 or dp > 0;
+r dp
+1422.220 0.5000000000
+1422.220 1234567.8900000000
+4953453454.440 1111111.2300000000
+DROP TABLE t1;
+CREATE TABLE t1 (
+f FLOAT,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10),
+pk FLOAT PRIMARY KEY,
+UNIQUE KEY(d)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 0 d 1 d A 1000 NULL NULL YES LSMTREE
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+EXPLAIN SELECT DISTINCT d FROM t1 ORDER BY d;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL d 9 NULL # Using index
+SELECT DISTINCT d FROM t1 ORDER BY d;
+d
+-1
+0
+1.2345
+466664.999
+29229114
+DROP TABLE t1;
+CREATE TABLE t1 (
+f FLOAT,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10),
+pk FLOAT PRIMARY KEY,
+KEY(d)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 d 1 d A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+EXPLAIN SELECT DISTINCT d FROM t1 ORDER BY d;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL d 9 NULL # Using index for group-by
+SELECT DISTINCT d FROM t1 ORDER BY d;
+d
+-1
+0
+1.2345
+466664.999
+29229114
+DROP TABLE t1;
+CREATE TABLE t1 (
+f FLOAT,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10),
+pk FLOAT PRIMARY KEY,
+UNIQUE KEY(f)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 0 f 1 f A 1000 NULL NULL YES LSMTREE
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,0,0,0,6);
+ERROR 23000: Duplicate entry '1.2345' for key 'f'
+EXPLAIN SELECT DISTINCT f FROM t1 ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL f 5 NULL # Using index
+SELECT DISTINCT f FROM t1 ORDER BY f;
+f
+-1
+0
+1.2345
+17.5843
+4644
+DROP TABLE t1;
+CREATE TABLE t1 (
+f FLOAT,
+r REAL(20,3),
+d DOUBLE,
+dp DOUBLE PRECISION (64,10),
+pk FLOAT PRIMARY KEY,
+KEY(f)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 f 1 f A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,0,0,0,6);
+EXPLAIN SELECT DISTINCT f FROM t1 ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range NULL f 5 NULL # Using index for group-by
+SELECT DISTINCT f FROM t1 ORDER BY f;
+f
+-1
+0
+1.2345
+17.5843
+4644
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_int.result b/storage/rocksdb/mysql-test/rocksdb/r/type_int.result
new file mode 100644
index 00000000..30604291
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_int.result
@@ -0,0 +1,212 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT ,
+i0 INT(0) ,
+i1 INT(1) ,
+i20 INT(20) ,
+t TINYINT ,
+t0 TINYINT(0) ,
+t1 TINYINT(1) ,
+t20 TINYINT(20) ,
+s SMALLINT ,
+s0 SMALLINT(0) ,
+s1 SMALLINT(1) ,
+s20 SMALLINT(20) ,
+m MEDIUMINT ,
+m0 MEDIUMINT(0) ,
+m1 MEDIUMINT(1) ,
+m20 MEDIUMINT(20) ,
+b BIGINT ,
+b0 BIGINT(0) ,
+b1 BIGINT(1) ,
+b20 BIGINT(20) ,
+pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+i int(11) YES NULL
+i0 int(11) YES NULL
+i1 int(1) YES NULL
+i20 int(20) YES NULL
+t tinyint(4) YES NULL
+t0 tinyint(4) YES NULL
+t1 tinyint(1) YES NULL
+t20 tinyint(20) YES NULL
+s smallint(6) YES NULL
+s0 smallint(6) YES NULL
+s1 smallint(1) YES NULL
+s20 smallint(20) YES NULL
+m mediumint(9) YES NULL
+m0 mediumint(9) YES NULL
+m1 mediumint(1) YES NULL
+m20 mediumint(20) YES NULL
+b bigint(20) YES NULL
+b0 bigint(20) YES NULL
+b1 bigint(1) YES NULL
+b20 bigint(20) YES NULL
+pk int(11) NO PRI NULL auto_increment
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 1
+Warning 1264 Out of range value for column 'i0' at row 1
+Warning 1264 Out of range value for column 'i1' at row 1
+Warning 1264 Out of range value for column 'i20' at row 1
+Warning 1264 Out of range value for column 't' at row 1
+Warning 1264 Out of range value for column 't0' at row 1
+Warning 1264 Out of range value for column 't1' at row 1
+Warning 1264 Out of range value for column 't20' at row 1
+Warning 1264 Out of range value for column 's' at row 1
+Warning 1264 Out of range value for column 's0' at row 1
+Warning 1264 Out of range value for column 's1' at row 1
+Warning 1264 Out of range value for column 's20' at row 1
+Warning 1264 Out of range value for column 'm' at row 1
+Warning 1264 Out of range value for column 'm0' at row 1
+Warning 1264 Out of range value for column 'm1' at row 1
+Warning 1264 Out of range value for column 'm20' at row 1
+Warning 1264 Out of range value for column 'b' at row 1
+Warning 1264 Out of range value for column 'b0' at row 1
+Warning 1264 Out of range value for column 'b1' at row 1
+Warning 1264 Out of range value for column 'b20' at row 1
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+Warnings:
+Warning 1264 Out of range value for column 'i' at row 8
+Warning 1264 Out of range value for column 'i0' at row 8
+Warning 1264 Out of range value for column 'i1' at row 8
+Warning 1264 Out of range value for column 'i20' at row 8
+Warning 1264 Out of range value for column 't' at row 8
+Warning 1264 Out of range value for column 't0' at row 8
+Warning 1264 Out of range value for column 't1' at row 8
+Warning 1264 Out of range value for column 't20' at row 8
+Warning 1264 Out of range value for column 's' at row 8
+Warning 1264 Out of range value for column 's0' at row 8
+Warning 1264 Out of range value for column 's1' at row 8
+Warning 1264 Out of range value for column 's20' at row 8
+Warning 1264 Out of range value for column 'm' at row 8
+Warning 1264 Out of range value for column 'm0' at row 8
+Warning 1264 Out of range value for column 'm1' at row 8
+Warning 1264 Out of range value for column 'm20' at row 8
+Warning 1264 Out of range value for column 'i' at row 9
+Warning 1264 Out of range value for column 'i0' at row 9
+Warning 1264 Out of range value for column 'i1' at row 9
+Warning 1264 Out of range value for column 'i20' at row 9
+Warning 1264 Out of range value for column 't' at row 9
+Warning 1264 Out of range value for column 't0' at row 9
+Warning 1264 Out of range value for column 't1' at row 9
+Warning 1264 Out of range value for column 't20' at row 9
+Warning 1264 Out of range value for column 's' at row 9
+Warning 1264 Out of range value for column 's0' at row 9
+Warning 1264 Out of range value for column 's1' at row 9
+Warning 1264 Out of range value for column 's20' at row 9
+Warning 1264 Out of range value for column 'm' at row 9
+Warning 1264 Out of range value for column 'm0' at row 9
+Warning 1264 Out of range value for column 'm1' at row 9
+Warning 1264 Out of range value for column 'm20' at row 9
+Warning 1264 Out of range value for column 'i' at row 10
+Warning 1264 Out of range value for column 'i0' at row 10
+Warning 1264 Out of range value for column 'i1' at row 10
+Warning 1264 Out of range value for column 'i20' at row 10
+Warning 1264 Out of range value for column 't' at row 10
+Warning 1264 Out of range value for column 't0' at row 10
+Warning 1264 Out of range value for column 't1' at row 10
+Warning 1264 Out of range value for column 't20' at row 10
+Warning 1264 Out of range value for column 's' at row 10
+Warning 1264 Out of range value for column 's0' at row 10
+Warning 1264 Out of range value for column 's1' at row 10
+Warning 1264 Out of range value for column 's20' at row 10
+Warning 1264 Out of range value for column 'm' at row 10
+Warning 1264 Out of range value for column 'm0' at row 10
+Warning 1264 Out of range value for column 'm1' at row 10
+Warning 1264 Out of range value for column 'm20' at row 10
+Warning 1264 Out of range value for column 'i' at row 11
+Warning 1264 Out of range value for column 'i0' at row 11
+Warning 1264 Out of range value for column 'i1' at row 11
+Warning 1264 Out of range value for column 'i20' at row 11
+Warning 1264 Out of range value for column 't' at row 11
+Warning 1264 Out of range value for column 't0' at row 11
+Warning 1264 Out of range value for column 't1' at row 11
+Warning 1264 Out of range value for column 't20' at row 11
+Warning 1264 Out of range value for column 's' at row 11
+Warning 1264 Out of range value for column 's0' at row 11
+Warning 1264 Out of range value for column 's1' at row 11
+Warning 1264 Out of range value for column 's20' at row 11
+Warning 1264 Out of range value for column 'm' at row 11
+Warning 1264 Out of range value for column 'm0' at row 11
+Warning 1264 Out of range value for column 'm1' at row 11
+Warning 1264 Out of range value for column 'm20' at row 11
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+i i0 i1 i20 t t0 t1 t20 s s0 s1 s20 m m0 m1 m20 b b0 b1 b20
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+-2147483648 -2147483648 -2147483648 -2147483648 -128 -128 -128 -128 -32768 -32768 -32768 -32768 -8388608 -8388608 -8388608 -8388608 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+2147483647 2147483647 2147483647 2147483647 127 127 127 127 32767 32767 32767 32767 8388607 8388607 8388607 8388607 9223372036854775807 9223372036854775807 9223372036854775807 9223372036854775807
+ALTER TABLE t1 ADD COLUMN i257 INT(257) ;
+ERROR 42000: Display width out of range for 'i257' (max = 255)
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_int_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_int_indexes.result
new file mode 100644
index 00000000..e4c47f04
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_int_indexes.result
@@ -0,0 +1,99 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+i INT PRIMARY KEY,
+t TINYINT,
+s SMALLINT,
+m MEDIUMINT,
+b BIGINT
+) ENGINE=rocksdb;
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+EXPLAIN SELECT i FROM t1 ORDER BY i;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 4 NULL # Using index
+SELECT i FROM t1 ORDER BY i;
+i
+1
+2
+3
+5
+10
+11
+12
+101
+1000
+10001
+DROP TABLE t1;
+CREATE TABLE t1 (
+i INT,
+t TINYINT,
+s SMALLINT,
+m MEDIUMINT,
+b BIGINT,
+pk SMALLINT AUTO_INCREMENT PRIMARY KEY,
+INDEX s_m (s,m)
+) ENGINE=rocksdb;
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+EXPLAIN SELECT s, m FROM t1 WHERE s != 10 AND m != 1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index s_m s_m 7 NULL # Using where; Using index
+SELECT s, m FROM t1 WHERE s != 10 AND m != 1;
+s m
+10000 1000000
+10000 1000000
+10002 10003
+103 104
+12 13
+13 14
+14 15
+3 4
+4 5
+5 6
+DROP TABLE t1;
+# RocksDB: unique indexes allowed
+CREATE TABLE t1 (
+i INT,
+t TINYINT,
+s SMALLINT,
+m MEDIUMINT,
+b BIGINT,
+pk MEDIUMINT AUTO_INCREMENT PRIMARY KEY,
+UNIQUE KEY b_t (b,t)
+) ENGINE=rocksdb;
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+SELECT b+t FROM t1 WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+b+t
+9
+11
+25
+27
+29
+207
+10107
+100000000000000100
+1000000000000000100
+SELECT b+t FROM t1 FORCE INDEX (b_t) WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+b+t
+9
+11
+25
+27
+29
+207
+10107
+100000000000000100
+1000000000000000100
+SELECT b+t FROM t1 IGNORE INDEX (b_t) WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+b+t
+9
+11
+25
+27
+29
+207
+10107
+100000000000000100
+1000000000000000100
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_set.result b/storage/rocksdb/mysql-test/rocksdb/r/type_set.result
new file mode 100644
index 00000000..5f875d14
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_set.result
@@ -0,0 +1,49 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a SET('') ,
+b SET('test1','test2','test3','test4','test5') ,
+c SET('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') ,
+PRIMARY KEY (c)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') YES NULL
+b set('test1','test2','test3','test4','test5') YES NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,,23'),
+('',5,2),
+(',','test4,test2','');
+Warnings:
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 1
+Warning 1265 Data truncated for column 'c' at row 1
+SELECT a,b,c FROM t1;
+a b c
+ 01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50'51,52,53,54,55,56,57,58,59,60,61,62,63,64
+ test1,test3 02
+ test2,test3 01,23,34,44
+ test2,test4
+ALTER TABLE t1 ADD COLUMN e SET('a','A') ;
+Warnings:
+Note 1291 Column 'e' has duplicated value 'a' in SET
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+a set('') YES NULL
+b set('test1','test2','test3','test4','test5') YES NULL
+c set('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') NO PRI NULL
+e set('a','A') YES NULL
+ALTER TABLE t1 ADD COLUMN f SET('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i') ;
+ERROR HY000: Too many strings for column f and SET
+SELECT a,b,c,e FROM t1 WHERE FIND_IN_SET('test2',b)>0 OR a != '';
+a b c e
+ test2,test3 01,23,34,44 NULL
+ test2,test4 NULL
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_set_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_set_indexes.result
new file mode 100644
index 00000000..391649e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_set_indexes.result
@@ -0,0 +1,115 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+b SET('test1','test2','test3','test4','test5'),
+c SET('01','22','23','33','34','39','40','44','50','63','64') PRIMARY KEY
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 c A 1000 NULL NULL LSMTREE
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,23'),
+('',5,2),
+('N.America,Asia','test4,test2',''),
+('Africa,Europe,Asia','test2,test3','01'),
+('Antarctica','test3','34,44'),
+('Asia','test5','50'),
+('Europe,S.America','test1,','39');
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 7
+EXPLAIN SELECT c FROM t1 ORDER BY c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 2 NULL # Using index
+SELECT c FROM t1 ORDER BY c;
+c
+
+01
+22
+39
+34,44
+01,23,34,44
+50
+EXPLAIN SELECT c FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY c;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL PRIMARY 2 NULL # Using index; Using filesort
+SELECT c FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY c;
+c
+
+01
+22
+39
+34,44
+01,23,34,44
+50
+INSERT INTO t1 (a,b,c) VALUES ('Antarctica','test3','02');
+ERROR 23000: Duplicate entry '22' for key 'PRIMARY'
+INSERT INTO t1 (a,b,c) VALUES ('','test1','34,44');
+ERROR 23000: Duplicate entry '34,44' for key 'PRIMARY'
+DROP TABLE t1;
+CREATE TABLE t1 (
+a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+b SET('test1','test2','test3','test4','test5'),
+c SET('01','22','23','33','34','39','40','44','50','63','64'),
+pk SET('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+INDEX(a)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 1 a 1 a A 500 NULL NULL YES LSMTREE
+INSERT INTO t1 (a,b,c,pk) VALUES
+('','test2,test3','01,34,44,23',1),
+('',5,2,2),
+('N.America,Asia','test4,test2','',3),
+('Africa,Europe,Asia','test2,test3','01',4),
+('Antarctica','test3','34,44',5),
+('Asia','test5','50',6),
+('Europe,S.America','test1,','39',7);
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 7
+EXPLAIN SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using where
+SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+a
+Africa,Europe,Asia
+S.America,Europe
+DROP TABLE t1;
+CREATE TABLE t1 (
+a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+b SET('test1','test2','test3','test4','test5'),
+c SET('01','22','23','33','34','39','40','44','50','63','64'),
+pk SET('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+UNIQUE INDEX b_a (b,a)
+) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 NULL NULL LSMTREE
+t1 0 b_a 1 b A 500 NULL NULL YES LSMTREE
+t1 0 b_a 2 a A 1000 NULL NULL YES LSMTREE
+INSERT INTO t1 (a,b,c,pk) VALUES
+('','test2,test3','01,34,44,23',1),
+('',5,2,2),
+('N.America,Asia','test4,test2','',3),
+('Africa,Europe,Asia','test2,test3','01',4),
+('Antarctica','test3','34,44',5),
+('Asia','test5','50',6),
+('Europe,S.America','test1,','39',7);
+Warnings:
+Warning 1265 Data truncated for column 'b' at row 7
+EXPLAIN SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using temporary; Using filesort
+SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+a b
+ test1,test3
+ test2,test3
+Africa,Europe,Asia test2,test3
+Antarctica test3
+Asia test5
+N.America,Asia test2,test4
+S.America,Europe test1
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_text.result b/storage/rocksdb/mysql-test/rocksdb/r/type_text.result
new file mode 100644
index 00000000..4e118bb9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_text.result
@@ -0,0 +1,57 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+pk INT AUTO_INCREMENT PRIMARY KEY,
+t TEXT ,
+t0 TEXT(0) ,
+t1 TEXT(1) ,
+t300 TEXT(300) ,
+tm TEXT(65535) ,
+t70k TEXT(70000) ,
+t17m TEXT(17000000) ,
+tt TINYTEXT ,
+m MEDIUMTEXT ,
+l LONGTEXT
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+pk int(11) NO PRI NULL auto_increment
+t text YES NULL
+t0 text YES NULL
+t1 tinytext YES NULL
+t300 text YES NULL
+tm text YES NULL
+t70k mediumtext YES NULL
+t17m longtext YES NULL
+tt tinytext YES NULL
+m mediumtext YES NULL
+l longtext YES NULL
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',255), REPEAT('i',1048576), REPEAT('j',1048576) );
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+Warnings:
+Warning 1265 Data truncated for column 't' at row 1
+Warning 1265 Data truncated for column 't0' at row 1
+Warning 1265 Data truncated for column 't1' at row 1
+Warning 1265 Data truncated for column 't300' at row 1
+Warning 1265 Data truncated for column 'tm' at row 1
+Warning 1265 Data truncated for column 'tt' at row 1
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+LENGTH(t) LENGTH(t0) LENGTH(t1) LENGTH(t300) LENGTH(tm) LENGTH(t70k) LENGTH(t17m) LENGTH(tt) LENGTH(m) LENGTH(l)
+0 0 0 0 0 0 0 0 0 0
+1 1 1 1 1 1 1 1 1 1
+5 5 5 5 5 5 5 5 5 6
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+65535 65535 255 65535 65535 1048576 1048576 255 1048576 1048576
+ALTER TABLE t1 ADD COLUMN ttt TEXT(4294967296) ;
+ERROR 42000: Display width out of range for 'ttt' (max = 4294967295)
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_text_indexes.result b/storage/rocksdb/mysql-test/rocksdb/r/type_text_indexes.result
new file mode 100644
index 00000000..7db5c23c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_text_indexes.result
@@ -0,0 +1,181 @@
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+t TEXT,
+tt TINYTEXT,
+m MEDIUMTEXT,
+l LONGTEXT,
+PRIMARY KEY t (t(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 't' ignored for PRIMARY key.
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 t A 1000 32 NULL LSMTREE
+INSERT INTO t1 (t,tt,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+EXPLAIN SELECT SUBSTRING(t,16) AS f FROM t1 WHERE t IN ('test1','test2') ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range PRIMARY PRIMARY 34 NULL # Using where; Using filesort
+SELECT SUBSTRING(t,16) AS f FROM t1 WHERE t IN ('test1','test2') ORDER BY f;
+f
+
+
+EXPLAIN SELECT SUBSTRING(t,16) AS f FROM t1 IGNORE INDEX (PRIMARY) WHERE t IN ('test1','test2') ORDER BY f;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL # Using where; Using filesort
+SELECT SUBSTRING(t,16) AS f FROM t1 IGNORE INDEX (PRIMARY) WHERE t IN ('test1','test2') ORDER BY f;
+f
+
+
+DROP TABLE t1;
+CREATE TABLE t1 (
+t TEXT,
+tt TINYTEXT,
+m MEDIUMTEXT,
+l LONGTEXT,
+pk TINYTEXT PRIMARY KEY,
+UNIQUE INDEX l_tt (l(256),tt(64))
+) ENGINE=rocksdb;
+ERROR 42000: BLOB/TEXT column 'pk' used in key specification without a key length
+CREATE TABLE t1 (
+t TEXT,
+tt TINYTEXT,
+m MEDIUMTEXT,
+l LONGTEXT,
+pk MEDIUMTEXT,
+PRIMARY KEY mt (pk(1)),
+INDEX (m(128))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'mt' ignored for PRIMARY key.
+SHOW INDEX IN t1;
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 0 PRIMARY 1 pk A 1000 1 NULL LSMTREE
+t1 1 m 1 m A 500 128 NULL YES LSMTREE
+INSERT INTO t1 (t,tt,m,l,pk) VALUES
+('','','','','0'),
+('a','b','c','d','1'),
+('b','d','c','b','2'),
+('test1','test2','test3','test4','3'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128),'4'),
+('abc','def','ghi','jkl','5'),
+('test2','test3','test4','test5','6'),
+('test3','test4','test5','test6','7'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128),'8'),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128),'9');
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ref m m 131 const # Using where; Using filesort
+SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+f
+DROP TABLE t1;
+CREATE TABLE t1 (
+b TEXT,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b TINYTEXT,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b MEDIUMTEXT,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b LONGTEXT,
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+SELECT b FROM t1;
+b
+00000000000000000000000000000000
+00000000000000000000000000000001
+00000000000000000000000000000002
+DROP TABLE t1;
+CREATE TABLE t1 (
+b LONGTEXT CHARACTER SET "binary" COLLATE "binary",
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0), (' ');
+SELECT hex(b) FROM t1;
+hex(b)
+
+00
+20
+3030303030303030303030303030303030303030303030303030303030303030
+3030303030303030303030303030303030303030303030303030303030303031
+3030303030303030303030303030303030303030303030303030303030303032
+DROP TABLE t1;
+CREATE TABLE t1 (
+b LONGTEXT CHARACTER SET "latin1" COLLATE "latin1_bin",
+PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+Warnings:
+Warning 1280 Name 'b' ignored for PRIMARY key.
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0), (' ');
+ERROR 23000: Duplicate entry ' ' for key 'PRIMARY'
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0);
+SELECT hex(b) FROM t1;
+hex(b)
+00
+
+DROP TABLE t1;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_varbinary.result b/storage/rocksdb/mysql-test/rocksdb/r/type_varbinary.result
new file mode 100644
index 00000000..6de8c033
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_varbinary.result
@@ -0,0 +1,93 @@
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARBINARY(0) ,
+v1 VARBINARY(1) ,
+v64 VARBINARY(64) ,
+v65000 VARBINARY(65000) ,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) YES NULL
+v1 varbinary(1) YES NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) YES NULL
+CREATE TABLE t2 (v VARBINARY(65532) , PRIMARY KEY(v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varbinary(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT HEX(v0), HEX(v1), HEX(v64), HEX(v65000) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) HEX(v65000)
+
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E2057652776652070726F7669646564206C696E6B7320746F20416D617A6F6E2E636F6D206865726520666F7220636F6E76656E69656E63652C2062757420746865792063616E20626520666F756E64206174206D616E79206F7468657220626F6F6B73746F7265732C20626F7468206F6E6C696E6520616E64206F66662E0A0A2020496620796F752077616E7420746F206861766520796F7572206661766F72697465204D7953514C202F204D61726961444220626F6F6B206C697374656420686572652C20706C65617365206C65617665206120636F6D6D656E742E0A2020466F7220646576656C6F706572732077686F2077616E7420746F20636F6465206F6E204D617269614442206F72204D7953514C0A0A2020202020202A20556E6465727374616E64696E67204D7953514C20496E7465726E616C73206279205361736861205061636865762C20666F726D6572204D7953514C20646576656C6F706572206174204D7953514C2041422E0A2020202020202020202020206F205468697320697320746865206F6E6C7920626F6F6B207765206B6E6F772061626F75742074686174206465736372696265732074686520696E7465726E616C73206F66204D617269614442202F204D7953514C2E2041206D757374206861766520666F7220616E796F6E652077686F2077616E747320746F20756E6465727374616E6420616E6420646576656C6F70206F6E204D617269614442210A2020202020202020202020206F204E6F7420616C6C20746F706963732061726520636F766572656420616E6420736F6D652070617274732061726520736C696768746C79206F757464617465642C20627574207374696C6C20746865206265737420626F6F6B206F6E207468697320746F7069632E200A2020202020202A204D7953514C20352E3120506C7567696E20446576656C6F706D656E742062792053657267656920476F6C75626368696B20616E6420416E64726577204875746368696E67730A2020202020202020202020206F2041206D757374207265616420666F7220616E796F6E652077616E74696E6720746F207772697465206120706C7567696E20666F72204D6172696144422C207772697474656E20627920746865205365726765692077686F2064657369676E65642074686520706C7567696E20696E7465726661636520666F72204D7953514C20616E64204D61726961444221200A0A2020466F72204D617269614442202F204D7953514C20656E642075736572730A0A2020202020202A204D61726961444220437261736820436F757273652062792042656E20466F7274610A2020202020202020202020206F204669727374204D61726961444220626F6F6B210A2020202020202020202020206F20466F722070656F706C652077686F2077616E7420746F206C6561726E2053514C20616E642074686520626173696373206F66204D6172696144422E0A2020202020202020202020206F204E6F77207368697070696E672E20507572636861736520617420416D617A6F6E2E636F6D206F7220796F7572206661766F7269746520626F6F6B73656C6C65722E200A0A2020202020202A2053514C2D393920436F6D706C6574652C205265616C6C792062792050657465722047756C75747A616E20262054727564792050656C7A65722E0A2020202020202020202020206F2045766572797468696E6720796F752077616E74656420746F206B6E6F772061626F7574207468652053514C203939207374616E646172642E20457863656C6C656E74207265666572656E636520626F6F6B210A2020202020202020202020206F204672656520746F207265616420696E20746865204B6E6F776C656467656261736521200A0A2020202020202A204D7953514C20283474682045646974696F6E29206279205061756C204475426F69730A2020202020202020202020206F20546865202764656661756C742720626F6F6B20746F207265616420696620796F7520776F6E7420746F206C6561726E20746F20757365204D7953514C202F204D6172696144422E200A0A2020202020202A204D7953514C20436F6F6B626F6F6B206279205061756C204475426F69730A2020202020202020202020206F2041206C6F74206F66206578616D706C6573206F6620686F7720746F20757365204D7953514C2E204173207769746820616C6C206F66205061756C277320626F6F6B732C206974277320776F727468206974732077656967687420696E20676F6C6420616E64206576656E20656E6A6F7961626C652072656164696E6720666F7220737563682061202764727927207375626A6563742E200A0A2020202020202A204869676820506572666F726D616E6365204D7953514C2C205365636F6E642045646974696F6E2C204279204261726F6E20536368776172747A2C205065746572205A6169747365762C20566164696D20546B616368656E6B6F2C204A6572656D7920442E205A61776F646E792C2041726A656E204C656E747A2C20446572656B204A2E2042616C6C696E672C20657420616C2E0A2020202020202020202020206F20224869676820506572666F726D616E6365204D7953514C2069732074686520646566696E697469766520677569646520746F206275696C64696E6720666173742C2072656C6961626C652073797374656D732077697468204D7953514C2E205772697474656E206279206E6F74656420657870657274732077697468207965617273206F66207265616C2D776F726C6420657870657269656E6365206275696C64696E672076657279206C617267652073797374656D732C207468697320626F6F6B20636F7665727320657665727920617370656374206F66204D7953514C20706572666F726D616E636520696E2064657461696C2C20616E6420666F6375736573206F6E20726F627573746E6573732C2073656375726974792C20616E64206461746120696E746567726974792E204C6561726E20616476616E63656420746563686E697175657320696E20646570746820736F20796F752063616E206272696E67206F7574204D7953514C27732066756C6C20706F7765722E22202846726F6D2074686520626F6F6B206465736372697074696F6E206174204F275265696C6C7929200A0A2020202020202A204D7953514C2041646D696E20436F6F6B626F6F6B0A2020202020202020202020206F204120717569636B20737465702D62792D7374657020677569646520666F72204D7953514C20757365727320616E642064617461626173652061646D696E6973747261746F727320746F207461636B6C65207265616C2D776F726C64206368616C6C656E6765732077697468204D7953514C20636F6E66696775726174696F6E20616E642061646D696E697374726174696F6E200A0A2020202020202A204D7953514C20352E302043657274696669636174696F6E2053747564792047756964652C204279205061756C204475426F69732C2053746566616E2048696E7A2C204361727374656E20506564657273656E0A2020202020202020202020206F205468697320697320746865206F6666696369616C20677569646520746F20636F766572207468652070617373696E67206F66207468652074776F204D7953514C2043657274696669636174696F6E206578616D696E6174696F6E732E2049742069732076616C69642074696C6C2076657273696F6E20352E30206F6620746865207365727665722C20736F207768696C65206974206D697373657320616C6C2074686520666561747572657320617661696C61626C6520696E204D7953514C20352E3120616E6420677265617465722028696E636C7564696E67204D61726961444220352E3120616E642067726561746572292C2069742070726F7669646573206120676F6F6420626173696320756E6465727374616E64696E67206F66204D7953514C20666F722074686520656E642D757365722E20
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+Warning 1265 Data truncated for column 'v65000' at row 6
+SELECT HEX(v0), HEX(v1), HEX(v64), LENGTH(HEX(v65000)) FROM t1;
+HEX(v0) HEX(v1) HEX(v64) LENGTH(HEX(v65000))
+ 0
+ 61 0
+ 48 61486572652069732061206C697374206F66207265636F6D6D656E64656420626F6F6B73206F6E204D61726961444220616E64204D7953514C2E205765277665 5932
+ 61 61616263646566676869206162636465666768692061626364656667686920616263646566676869206162636465666768692061626364656667686920616263 130000
+ 79 4F6E63652074686572652C20646F75626C6520636865636B207468617420616E2061727469636C6520646F65736E277420616C7265616479206578697374 5930
+ 79 63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363 130000
+ALTER TABLE t1 ADD COLUMN v65536 VARBINARY(65536) ;
+Warnings:
+Note 1246 Converting column 'v65536' from VARBINARY to BLOB
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varbinary(0) YES NULL
+v1 varbinary(1) YES NULL
+v64 varbinary(64) NO PRI NULL
+v65000 varbinary(65000) YES NULL
+v65536 mediumblob YES NULL
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/type_varchar.result b/storage/rocksdb/mysql-test/rocksdb/r/type_varchar.result
new file mode 100644
index 00000000..4c5ef475
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/type_varchar.result
@@ -0,0 +1,775 @@
+#
+# A basic test whether endspace-aware variable length encoding
+# works when in PK
+#
+create table t1 ( pk varchar(10) primary key, col1 int not null) ;
+insert into t1 values ('a', 1);
+select * from t1;
+pk col1
+a 1
+drop table t1;
+DROP TABLE IF EXISTS t1, t2;
+CREATE TABLE t1 (
+v0 VARCHAR(0) ,
+v1 VARCHAR(1) ,
+v64 VARCHAR(64) ,
+v65000 VARCHAR(65000) ,
+PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) YES NULL
+v1 varchar(1) YES NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) YES NULL
+CREATE TABLE t2 (v VARCHAR(65532), PRIMARY KEY (v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+Field Type Null Key Default Extra
+v varchar(65532) NO PRI NULL
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+SELECT v0,v1,v64,v65000 FROM t1;
+v0 v1 v64 v65000
+
+
+
+
+
+
+
+
+
+
+
+ y Once there, double check that an article doesn't already exist Here is a list of recommended books on MariaDB and MySQL. We've provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+ o "High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL's full power." (From the book description at O'Reilly)
+ o A lot of examples of how to use MySQL. As with all of Paul's books, it's worth its weight in gold and even enjoyable reading for such a 'dry' subject.
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Free to read in the Knowledgebase!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+ o The 'default' book to read if you wont to learn to use MySQL / MariaDB.
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ * MariaDB Crash Course by Ben Forta
+ * MySQL (4th Edition) by Paul DuBois
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ * MySQL Admin Cookbook
+ * MySQL Cookbook by Paul DuBois
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ For MariaDB / MySQL end users
+ For developers who want to code on MariaDB or MySQL
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 1
+Warning 1265 Data truncated for column 'v1' at row 1
+Warning 1265 Data truncated for column 'v64' at row 1
+Warning 1265 Data truncated for column 'v65000' at row 1
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+Warnings:
+Warning 1265 Data truncated for column 'v0' at row 5
+Warning 1265 Data truncated for column 'v1' at row 5
+Warning 1265 Data truncated for column 'v64' at row 5
+Warning 1265 Data truncated for column 'v65000' at row 5
+Warning 1265 Data truncated for column 'v0' at row 6
+Warning 1265 Data truncated for column 'v1' at row 6
+Warning 1265 Data truncated for column 'v64' at row 6
+SELECT v0, v1, v64, LENGTH(v65000) FROM t1;
+v0 v1 v64 LENGTH(v65000)
+ 0
+ a 0
+ H aHere is a list of recommended books on MariaDB and MySQL. We've 2966
+ a aabcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abc 65000
+ y Once there, double check that an article doesn't already exist 2965
+ y cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc 65000
+ALTER TABLE t1 ADD COLUMN v65536 VARCHAR(65536) ;
+Warnings:
+Note 1246 Converting column 'v65536' from VARCHAR to TEXT
+SHOW COLUMNS IN t1;
+Field Type Null Key Default Extra
+v0 varchar(0) YES NULL
+v1 varchar(1) YES NULL
+v64 varchar(64) NO PRI NULL
+v65000 varchar(65000) YES NULL
+v65536 mediumtext YES NULL
+DROP TABLE t1, t2;
+#
+# Endspace-comparison tests:
+#
+#
+# Issue 257: Sort order for varchars is different between
+# MyISAM/InnoDB vs MyRocks
+#
+create table t1 (
+pk varchar(64) CHARACTER SET latin1 COLLATE latin1_bin,
+col1 varchar(64),
+primary key (pk)
+);
+insert into t1 values ('a','a');
+insert into t1 values ('a ', 'a-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values('b ', 'b-2x-space');
+insert into t1 values ('b', 'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+select pk, hex(pk), col1 from t1;
+pk hex(pk) col1
+a 61 a
+b 622020 b-2x-space
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+pk hex(pk) col1
+a 6109 a-tab
+a 612009 a-space-tab
+a 61 a
+b 622020 b-2x-space
+# Try longer values
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+pk col1
+a a-tab
+a a-space-tab
+a a
+b b-2x-space
+c c-10-x-space
+drop table t1;
+# Secondary index
+create table t1 (
+pk int not null primary key,
+col1 varchar(64) CHARACTER SET latin1 COLLATE latin1_bin,
+col2 varchar(64),
+key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL col1 67 NULL # Using index
+select col1, hex(col1) from t1;
+col1 hex(col1)
+a 61202009
+a 6120
+a 61
+ab 6162
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # col1 col1 67 NULL # Using where; Using index
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+col1 hex(col1)
+a 61202009
+a 6120
+a 61
+ab 6162
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # NULL col1 67 NULL # Using index
+select pk, col1, hex(col1), length(col1) from t1;
+pk col1 hex(col1) length(col1)
+10 0
+11 2020202020202020 8
+12 20202020202020202020202020202020 16
+13 202020202020202020202020202020202020202020202020 24
+21 202020202020202020 9
+22 2020202020202020202020202020202020 17
+23 202020202020202020202020202020202020 18
+14 a 2020202020202020202020202020202061 17
+drop table t1;
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+select pk,length(a) from t1 force index(a) where a < 'zz';
+pk length(a)
+1 301
+2 301
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+drop table t1;
+#
+# Issue 257: Sort order for varchars is different between
+# MyISAM/InnoDB vs MyRocks
+#
+create table t1 (
+pk varchar(64) CHARACTER SET utf8 COLLATE utf8_bin,
+col1 varchar(64),
+primary key (pk)
+);
+insert into t1 values ('a','a');
+insert into t1 values ('a ', 'a-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values('b ', 'b-2x-space');
+insert into t1 values ('b', 'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+select pk, hex(pk), col1 from t1;
+pk hex(pk) col1
+a 61 a
+b 622020 b-2x-space
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+pk hex(pk) col1
+a 6109 a-tab
+a 612009 a-space-tab
+a 61 a
+b 622020 b-2x-space
+# Try longer values
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+pk col1
+a a-tab
+a a-space-tab
+a a
+b b-2x-space
+c c-10-x-space
+drop table t1;
+# Secondary index
+create table t1 (
+pk int not null primary key,
+col1 varchar(64) CHARACTER SET utf8 COLLATE utf8_bin,
+col2 varchar(64),
+key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index NULL col1 195 NULL # Using index
+select col1, hex(col1) from t1;
+col1 hex(col1)
+a 61202009
+a 6120
+a 61
+ab 6162
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # col1 col1 195 NULL # Using where; Using index
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+col1 hex(col1)
+a 61202009
+a 6120
+a 61
+ab 6162
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # NULL col1 195 NULL # Using index
+select pk, col1, hex(col1), length(col1) from t1;
+pk col1 hex(col1) length(col1)
+10 0
+11 2020202020202020 8
+12 20202020202020202020202020202020 16
+13 202020202020202020202020202020202020202020202020 24
+21 202020202020202020 9
+22 2020202020202020202020202020202020 17
+23 202020202020202020202020202020202020 18
+14 a 2020202020202020202020202020202061 17
+drop table t1;
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+select pk,length(a) from t1 force index(a) where a < 'zz';
+pk length(a)
+1 301
+2 301
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+drop table t1;
+#
+# Issue 257: Sort order for varchars is different between
+# MyISAM/InnoDB vs MyRocks
+#
+create table t1 (
+pk varchar(64) CHARACTER SET ucs2 COLLATE ucs2_bin,
+col1 varchar(64),
+primary key (pk)
+);
+insert into t1 values ('a','a');
+insert into t1 values ('a ', 'a-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values('b ', 'b-2x-space');
+insert into t1 values ('b', 'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+select pk, hex(pk), col1 from t1;
+pk hex(pk) col1
+a 0061 a
+b 006200200020 b-2x-space
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+pk hex(pk) col1
+a 00610009 a-tab
+a 006100200009 a-space-tab
+a 0061 a
+b 006200200020 b-2x-space
+# Try longer values
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+pk col1
+a a-tab
+a a-space-tab
+a a
+b b-2x-space
+c c-10-x-space
+drop table t1;
+# Secondary index
+create table t1 (
+pk int not null primary key,
+col1 varchar(64) CHARACTER SET ucs2 COLLATE ucs2_bin,
+col2 varchar(64),
+key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL #
+select col1, hex(col1) from t1;
+col1 hex(col1)
+ab 00610062
+a 00610020
+a 0061
+a 0061002000200009
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # col1 col1 131 NULL # Using where
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+col1 hex(col1)
+a 0061002000200009
+a 00610020
+a 0061
+ab 00610062
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # NULL NULL NULL NULL #
+select pk, col1, hex(col1), length(col1) from t1;
+pk col1 hex(col1) length(col1)
+10 0
+11 00200020002000200020002000200020 16
+12 0020002000200020002000200020002000200020002000200020002000200020 32
+13 002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020 48
+14 a 00200020002000200020002000200020002000200020002000200020002000200061 34
+21 002000200020002000200020002000200020 18
+22 00200020002000200020002000200020002000200020002000200020002000200020 34
+23 002000200020002000200020002000200020002000200020002000200020002000200020 36
+drop table t1;
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+select pk,length(a) from t1 force index(a) where a < 'zz';
+pk length(a)
+1 301
+2 301
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+drop table t1;
+#
+# Issue 257: Sort order for varchars is different between
+# MyISAM/InnoDB vs MyRocks
+#
+create table t1 (
+pk varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
+col1 varchar(64),
+primary key (pk)
+);
+insert into t1 values ('a','a');
+insert into t1 values ('a ', 'a-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values('b ', 'b-2x-space');
+insert into t1 values ('b', 'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+select pk, hex(pk), col1 from t1;
+pk hex(pk) col1
+a 61 a
+b 622020 b-2x-space
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+pk hex(pk) col1
+a 6109 a-tab
+a 612009 a-space-tab
+a 61 a
+b 622020 b-2x-space
+# Try longer values
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+pk col1
+a a-tab
+a a-space-tab
+a a
+b b-2x-space
+c c-10-x-space
+drop table t1;
+# Secondary index
+create table t1 (
+pk int not null primary key,
+col1 varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
+col2 varchar(64),
+key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL #
+select col1, hex(col1) from t1;
+col1 hex(col1)
+ab 6162
+a 6120
+a 61
+a 61202009
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # col1 col1 259 NULL # Using where
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+col1 hex(col1)
+a 61202009
+a 6120
+a 61
+ab 6162
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # NULL NULL NULL NULL #
+select pk, col1, hex(col1), length(col1) from t1;
+pk col1 hex(col1) length(col1)
+10 0
+11 2020202020202020 8
+12 20202020202020202020202020202020 16
+13 202020202020202020202020202020202020202020202020 24
+14 a 2020202020202020202020202020202061 17
+21 202020202020202020 9
+22 2020202020202020202020202020202020 17
+23 202020202020202020202020202020202020 18
+drop table t1;
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+select pk,length(a) from t1 force index(a) where a < 'zz';
+pk length(a)
+1 301
+2 301
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+drop table t1;
+#
+# Issue 257: Sort order for varchars is different between
+# MyISAM/InnoDB vs MyRocks
+#
+create table t1 (
+pk varchar(64) CHARACTER SET utf16 COLLATE utf16_bin,
+col1 varchar(64),
+primary key (pk)
+);
+insert into t1 values ('a','a');
+insert into t1 values ('a ', 'a-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values('b ', 'b-2x-space');
+insert into t1 values ('b', 'b');
+ERROR 23000: Duplicate entry 'b' for key 'PRIMARY'
+select pk, hex(pk), col1 from t1;
+pk hex(pk) col1
+a 0061 a
+b 006200200020 b-2x-space
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+pk hex(pk) col1
+a 00610009 a-tab
+a 006100200009 a-space-tab
+a 0061 a
+b 006200200020 b-2x-space
+# Try longer values
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+ERROR 23000: Duplicate entry 'a ' for key 'PRIMARY'
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+pk col1
+a a-tab
+a a-space-tab
+a a
+b b-2x-space
+c c-10-x-space
+drop table t1;
+# Secondary index
+create table t1 (
+pk int not null primary key,
+col1 varchar(64) CHARACTER SET utf16 COLLATE utf16_bin,
+col2 varchar(64),
+key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Engine-independent statistics collected
+test.t1 analyze status OK
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 ALL NULL NULL NULL NULL #
+select col1, hex(col1) from t1;
+col1 hex(col1)
+ab 00610062
+a 00610020
+a 0061
+a 0061002000200009
+# Must show 'using index' for latin1_bin and utf8_bin:
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # col1 col1 259 NULL # Using where
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+col1 hex(col1)
+a 0061002000200009
+a 00610020
+a 0061
+ab 00610062
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 # NULL NULL NULL NULL #
+select pk, col1, hex(col1), length(col1) from t1;
+pk col1 hex(col1) length(col1)
+10 0
+11 00200020002000200020002000200020 16
+12 0020002000200020002000200020002000200020002000200020002000200020 32
+13 002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020 48
+14 a 00200020002000200020002000200020002000200020002000200020002000200061 34
+21 002000200020002000200020002000200020 18
+22 00200020002000200020002000200020002000200020002000200020002000200020 34
+23 002000200020002000200020002000200020002000200020002000200020002000200020 36
+drop table t1;
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+select pk,length(a) from t1 force index(a) where a < 'zz';
+pk length(a)
+1 301
+2 301
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+pk length(a) rtrim(a)
+1 301 a
+2 301 b
+drop table t1;
+create table t1 (
+pk int primary key,
+col1 varchar(10) collate utf8mb4_bin not null,
+col2 varchar(20),
+key(col1)
+) engine=rocksdb;
+insert into t1 values (1, 'ab','ab');
+insert into t1 values (2, 'ab\0','ab0');
+select pk, hex(col1), col2 from t1 force index(col1) order by col1;
+pk hex(col1) col2
+2 616200 ab0
+1 6162 ab
+select pk, hex(col1), col2 from t1 ignore index(col1) order by col1;
+pk hex(col1) col2
+2 616200 ab0
+1 6162 ab
+drop table t1;
+create table t (id int primary key, email varchar(100), KEY email_i (email(30)));
+insert into t values (1, 'abcabcabcabcabcabcabcabcabcabcabc ');
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ALL NULL NULL NULL NULL #
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+index_name count
+email_i 1
+drop table t;
+set @save_rocksdb_checksums_pct = @@global.rocksdb_checksums_pct;
+set @save_rocksdb_verify_row_debug_checksums = @@session.rocksdb_verify_row_debug_checksums;
+set global rocksdb_checksums_pct = 100;
+set session rocksdb_verify_row_debug_checksums = on;
+create table t (id int primary key, email varchar(100), KEY email_i (email(30)));
+insert into t values (1, 'a');
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t ALL NULL NULL NULL NULL #
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+index_name count
+email_i 1
+drop table t;
+set global rocksdb_checksums_pct = @save_rocksdb_checksums_pct;
+set session rocksdb_verify_row_debug_checksums = @save_rocksdb_verify_row_debug_checksums;
+drop table if exists t;
+Warnings:
+Note 1051 Unknown table 'test.t'
+create table t (h varchar(31) character set utf8 collate utf8_bin not null, i varchar(19) collate latin1_bin not null, primary key(i), key(h)) engine=rocksdb;
+insert into t(i,h) values('a','b');
+check table t;
+Table Op Msg_type Msg_text
+test.t check status OK
+alter table t modify h varchar(31) character set cp1257 collate cp1257_bin not null;
+check table t;
+Table Op Msg_type Msg_text
+test.t check status OK
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/unique_check.result b/storage/rocksdb/mysql-test/rocksdb/r/unique_check.result
new file mode 100644
index 00000000..db9b1190
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/unique_check.result
@@ -0,0 +1,117 @@
+connect con1, localhost, root,,;
+connect con2, localhost, root,,;
+connect con3, localhost, root,,;
+connection default;
+set debug_sync='RESET';
+drop table if exists t1;
+create table t1 (id int, value int, primary key (id)) engine=rocksdb;
+create table t2 (id int, id2 int, value int, primary key (id), unique key (id2)) engine=rocksdb;
+connection con1;
+begin;
+insert into t1 values (1,1);
+connection con2;
+set session rocksdb_lock_wait_timeout=50;
+begin;
+insert into t1 values (1,2);
+connection con1;
+commit;
+connection con2;
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+commit;
+select * from t1;
+id value
+1 1
+truncate table t1;
+connection con1;
+begin;
+insert into t2 values (1,1,1);
+connection con2;
+begin;
+insert into t2 values (2,1,2);
+connection con1;
+commit;
+connection con2;
+ERROR 23000: Duplicate entry '1' for key 'id2'
+commit;
+select * from t2;
+id id2 value
+1 1 1
+truncate table t2;
+connection con1;
+begin;
+insert into t1 values (1,1);
+connection con2;
+begin;
+insert into t1 values (1,2);
+connection con1;
+rollback;
+connection con2;
+commit;
+select * from t1;
+id value
+1 2
+truncate table t1;
+connection con1;
+begin;
+insert into t2 values (1,1,1);
+connection con2;
+begin;
+insert into t2 values (2,1,2);
+connection con1;
+rollback;
+connection con2;
+commit;
+select * from t2;
+id id2 value
+2 1 2
+truncate table t2;
+connection con1;
+set debug_sync='rocksdb.update_write_row_after_unique_check SIGNAL parked1 WAIT_FOR go';
+insert into t1 values (1,1);
+connection default;
+set debug_sync='now WAIT_FOR parked1';
+connection con2;
+set debug_sync='rocksdb.update_write_row_after_unique_check SIGNAL parked2 WAIT_FOR go';
+insert into t2 values (1,1,1);
+connection default;
+set debug_sync='now WAIT_FOR parked2';
+connection con3;
+set session rocksdb_lock_wait_timeout=1;
+insert into t1 values (1,2);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+insert into t2 values (2,1,2);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection default;
+set debug_sync='now SIGNAL go';
+connection con1;
+connection con2;
+connection default;
+insert into t1 values (1,2);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+insert into t2 values (2,1,2);
+ERROR 23000: Duplicate entry '1' for key 'id2'
+select * from t1;
+id value
+1 1
+select * from t2;
+id id2 value
+1 1 1
+connection default;
+set debug_sync='RESET';
+disconnect con1;
+disconnect con2;
+disconnect con3;
+drop table t1, t2;
+connection default;
+drop table if exists t1,t2,t3;
+create table t1 (id int, value int, primary key (id)) engine=rocksdb;
+create table t2 (id int, id2 int, value int, primary key (id), unique key (id2)) engine=rocksdb;
+create table t3 (id int, value int) engine=rocksdb;
+SET @old_val = @@session.unique_checks;
+set @@session.unique_checks = FALSE;
+insert into t1 values (1, 1), (1, 2);
+insert into t2 values (1, 1, 1), (1, 2, 1);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+insert into t3 values (1, 1), (1, 1);
+set @@session.unique_checks = @old_val;
+drop table t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/unique_sec.result b/storage/rocksdb/mysql-test/rocksdb/r/unique_sec.result
new file mode 100644
index 00000000..a37e7f1c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/unique_sec.result
@@ -0,0 +1,221 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (id1 INT NOT NULL, id2 INT NOT NULL, id3 VARCHAR(32),
+id4 INT, id5 VARCHAR(32),
+value1 INT, value2 INT, value3 VARCHAR(32),
+PRIMARY KEY (id1, id2) ,
+UNIQUE INDEX (id2, id1) ,
+UNIQUE INDEX (id2, id3, id4) ,
+INDEX (id1) ,
+INDEX (id3, id1) ,
+UNIQUE INDEX(id5) ,
+INDEX (id2, id5)) ENGINE=ROCKSDB;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test inserting a key that returns duplicate error
+INSERT INTO t1 VALUES (1, 1, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '1-1' for key 'PRIMARY'
+INSERT INTO t1 VALUES (5, 5, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '5-5' for key 'PRIMARY'
+INSERT INTO t1 VALUES (10, 10, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '10-10' for key 'PRIMARY'
+INSERT INTO t1 VALUES (11, 1, 1, 1, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 5, 5, 5, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '5-5-5' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 10, 10, 10, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '10-10-10' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 1, 11, 11, 11);
+ERROR 23000: Duplicate entry '1' for key 'id5'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 5, 11, 11, 11);
+ERROR 23000: Duplicate entry '5' for key 'id5'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 10, 11, 11, 11);
+ERROR 23000: Duplicate entry '10' for key 'id5'
+# Test updating a key that returns duplicate error
+UPDATE t1 SET id2=1, id3=1, id4=1 WHERE id1=2;
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+UPDATE t1 SET id2=1, id3=1, id4=1;
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test updating a key to itself
+UPDATE t1 set id2=id4;
+UPDATE t1 set id5=id3, value1=value2;
+UPDATE t1 set value3=value1;
+# Test modifying values should not cause duplicates
+UPDATE t1 SET value1=value3+1;
+UPDATE t1 SET value3=value3 div 2;
+UPDATE t1 SET value2=value3;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test NULL values are considered unique
+INSERT INTO t1 VALUES (20, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (21, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (22, 20, 20, NULL, NULL, 20, 20, 20);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+# Adding multiple rows where one of the rows fail the duplicate
+# check should fail the whole statement
+INSERT INTO t1 VALUES (23, 23, 23, 23, 23, 23, 23, 23),
+(24, 24, 24, 24, 24, 24, 24, 24),
+(25, 10, 10, 10, 25, 25, 25, 25),
+(26, 26, 26, 26, 26, 26, 26, 26);
+ERROR 23000: Duplicate entry '10-10-10' for key 'id2_2'
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (30, 31, 32, 33, 34, 30, 30, 30);
+connection con2;
+BEGIN;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+# Primary key should prevent duplicate on insert
+INSERT INTO t1 VALUES (30, 31, 30, 30, 30, 30, 30, 30);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Primary key should prevent duplicate on update
+UPDATE t1 SET id1=30, id2=31 WHERE id2=10;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Unique secondary key should prevent duplicate on insert
+INSERT INTO t1 VALUES (31, 31, 32, 33, 30, 30, 30, 30);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (32, 32, 32, 32, 34, 32, 32, 32);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Unique secondary key should prevent duplicate on update
+UPDATE t1 SET id2=31, id3=32, id4=33 WHERE id2=8;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t1 SET id5=34 WHERE id2=8;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Adding multiple rows where one of the rows fail the duplicate
+# check should fail the whole statement
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+(36, 36, 36, 36, 36, 36, 36, 36),
+(37, 31, 32, 33, 37, 37, 37, 37),
+(38, 38, 38, 38, 38, 38, 38, 38);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+(36, 36, 36, 36, 36, 36, 36, 36),
+(37, 37, 37, 37, 34, 37, 37, 37),
+(38, 38, 38, 38, 38, 38, 38, 38);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# NULL values are unique and duplicates in value fields are ignored
+INSERT INTO t1 VALUES (37, 31, 32, NULL, 37, 37, 37, 37),
+(38, 31, 32, NULL, 38, 37, 37, 37),
+(39, 31, 32, NULL, 39, 37, 37, 37);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+16
+# Fail on duplicate key update for row added in our transaction
+UPDATE t1 SET id5=37 WHERE id1=38;
+ERROR 23000: Duplicate entry '37' for key 'id5'
+# Fail on lock timeout for row modified in another transaction
+UPDATE t1 SET id5=34 WHERE id1=38;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# NULL values are unique
+UPDATE t1 SET id5=NULL WHERE value1 > 37;
+connection con1;
+COMMIT;
+connection con2;
+COMMIT;
+connection con2;
+BEGIN;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+17
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+connection con2;
+# When transaction is pending, fail on lock acquisition
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+17
+connection con1;
+COMMIT;
+connection con2;
+# When transaction is committed, fail on duplicate key
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+Got one of the listed errors
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+ERROR 23000: Duplicate entry '40-40-40' for key 'id2_2'
+ROLLBACK;
+SELECT * FROM t1;
+id1 id2 id3 id4 id5 value1 value2 value3
+1 1 1 1 1 2 0 0
+2 2 2 2 2 3 1 1
+3 3 3 3 3 4 1 1
+4 4 4 4 4 5 2 2
+5 5 5 5 5 6 2 2
+6 6 6 6 6 7 3 3
+7 7 7 7 7 8 3 3
+8 8 8 8 8 9 4 4
+9 9 9 9 9 10 4 4
+10 10 10 10 10 11 5 5
+20 20 20 NULL NULL 20 20 20
+21 20 20 NULL NULL 20 20 20
+22 20 20 NULL NULL 20 20 20
+30 31 32 33 34 30 30 30
+37 31 32 NULL 37 37 37 37
+38 31 32 NULL 38 37 37 37
+39 31 32 NULL 39 37 37 37
+40 40 40 40 40 40 40 40
+disconnect con1;
+disconnect con2;
+connection default;
+DROP TABLE t1;
+#
+# Issue #88: Creating unique index over column with duplicate values succeeds
+#
+create table t1 (pk int primary key, a int) engine=rocksdb;
+insert into t1 values
+(1, 1),
+(2, 2),
+(3, 3),
+(4, 1),
+(5, 5);
+alter table t1 add unique(a);
+ERROR 23000: Duplicate entry '1' for key 'a'
+drop table t1;
+#
+# Issue #111
+#
+CREATE TABLE t2 (pk int, a int, PRIMARY KEY (pk, a), UNIQUE KEY (a)) ENGINE=ROCKSDB PARTITION BY KEY (a) PARTITIONS 16;
+INSERT INTO t2 VALUES (1,1);
+INSERT INTO t2 VALUES (1,1);
+ERROR 23000: Duplicate entry '1-1' for key 'PRIMARY'
+INSERT INTO t2 VALUES (2,1);
+ERROR 23000: Duplicate entry '1' for key 'a'
+DROP TABLE t2;
+#
+# Issue #491 (https://github.com/facebook/mysql-5.6/issues/491)
+#
+CREATE TABLE t (a BLOB, PRIMARY KEY(a(2)), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+Table Op Msg_type Msg_text
+test.t check status OK
+DROP TABLE t;
+CREATE TABLE t (a VARCHAR(255), PRIMARY KEY(a), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+Table Op Msg_type Msg_text
+test.t check status OK
+DROP TABLE t;
+CREATE TABLE t (a VARCHAR(255), PRIMARY KEY(a(2)), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+Table Op Msg_type Msg_text
+test.t check status OK
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/unique_sec_rev_cf.result b/storage/rocksdb/mysql-test/rocksdb/r/unique_sec_rev_cf.result
new file mode 100644
index 00000000..210c7409
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/unique_sec_rev_cf.result
@@ -0,0 +1,177 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+CREATE TABLE t1 (id1 INT NOT NULL, id2 INT NOT NULL, id3 VARCHAR(32),
+id4 INT, id5 VARCHAR(32),
+value1 INT, value2 INT, value3 VARCHAR(32),
+PRIMARY KEY (id1, id2) COMMENT 'rev:cf',
+UNIQUE INDEX (id2, id1) COMMENT 'rev:cf',
+UNIQUE INDEX (id2, id3, id4) COMMENT 'rev:cf',
+INDEX (id1) COMMENT 'rev:cf',
+INDEX (id3, id1) COMMENT 'rev:cf',
+UNIQUE INDEX(id5) COMMENT 'rev:cf',
+INDEX (id2, id5)) ENGINE=ROCKSDB;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test inserting a key that returns duplicate error
+INSERT INTO t1 VALUES (1, 1, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '1-1' for key 'PRIMARY'
+INSERT INTO t1 VALUES (5, 5, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '5-5' for key 'PRIMARY'
+INSERT INTO t1 VALUES (10, 10, 11, 11, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '10-10' for key 'PRIMARY'
+INSERT INTO t1 VALUES (11, 1, 1, 1, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 5, 5, 5, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '5-5-5' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 10, 10, 10, 11, 11, 11, 11);
+ERROR 23000: Duplicate entry '10-10-10' for key 'id2_2'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 1, 11, 11, 11);
+ERROR 23000: Duplicate entry '1' for key 'id5'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 5, 11, 11, 11);
+ERROR 23000: Duplicate entry '5' for key 'id5'
+INSERT INTO t1 VALUES (11, 11, 11, 11, 10, 11, 11, 11);
+ERROR 23000: Duplicate entry '10' for key 'id5'
+# Test updating a key that returns duplicate error
+UPDATE t1 SET id2=1, id3=1, id4=1 WHERE id1=2;
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+UPDATE t1 SET id2=1, id3=1, id4=1;
+ERROR 23000: Duplicate entry '1-1-1' for key 'id2_2'
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test updating a key to itself
+UPDATE t1 set id2=id4;
+UPDATE t1 set id5=id3, value1=value2;
+UPDATE t1 set value3=value1;
+# Test modifying values should not cause duplicates
+UPDATE t1 SET value1=value3+1;
+UPDATE t1 SET value3=value3 div 2;
+UPDATE t1 SET value2=value3;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+10
+# Test NULL values are considered unique
+INSERT INTO t1 VALUES (20, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (21, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (22, 20, 20, NULL, NULL, 20, 20, 20);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+# Adding multiple rows where one of the rows fail the duplicate
+# check should fail the whole statement
+INSERT INTO t1 VALUES (23, 23, 23, 23, 23, 23, 23, 23),
+(24, 24, 24, 24, 24, 24, 24, 24),
+(25, 10, 10, 10, 25, 25, 25, 25),
+(26, 26, 26, 26, 26, 26, 26, 26);
+ERROR 23000: Duplicate entry '10-10-10' for key 'id2_2'
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (30, 31, 32, 33, 34, 30, 30, 30);
+connection con2;
+BEGIN;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+13
+# Primary key should prevent duplicate on insert
+INSERT INTO t1 VALUES (30, 31, 30, 30, 30, 30, 30, 30);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Primary key should prevent duplicate on update
+UPDATE t1 SET id1=30, id2=31 WHERE id2=10;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Unique secondary key should prevent duplicate on insert
+INSERT INTO t1 VALUES (31, 31, 32, 33, 30, 30, 30, 30);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (32, 32, 32, 32, 34, 32, 32, 32);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Unique secondary key should prevent duplicate on update
+UPDATE t1 SET id2=31, id3=32, id4=33 WHERE id2=8;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+UPDATE t1 SET id5=34 WHERE id2=8;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Adding multiple rows where one of the rows fail the duplicate
+# check should fail the whole statement
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+(36, 36, 36, 36, 36, 36, 36, 36),
+(37, 31, 32, 33, 37, 37, 37, 37),
+(38, 38, 38, 38, 38, 38, 38, 38);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+(36, 36, 36, 36, 36, 36, 36, 36),
+(37, 37, 37, 37, 34, 37, 37, 37),
+(38, 38, 38, 38, 38, 38, 38, 38);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# NULL values are unique and duplicates in value fields are ignored
+INSERT INTO t1 VALUES (37, 31, 32, NULL, 37, 37, 37, 37),
+(38, 31, 32, NULL, 38, 37, 37, 37),
+(39, 31, 32, NULL, 39, 37, 37, 37);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+16
+# Fail on duplicate key update for row added in our transaction
+UPDATE t1 SET id5=37 WHERE id1=38;
+ERROR 23000: Duplicate entry '37' for key 'id5'
+# Fail on lock timeout for row modified in another transaction
+UPDATE t1 SET id5=34 WHERE id1=38;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# NULL values are unique
+UPDATE t1 SET id5=NULL WHERE value1 > 37;
+connection con1;
+COMMIT;
+connection con2;
+COMMIT;
+connection con2;
+BEGIN;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+17
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+connection con2;
+# When transaction is pending, fail on lock acquisition
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+17
+connection con1;
+COMMIT;
+connection con2;
+# When transaction is committed, fail on duplicate key
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+Got one of the listed errors
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+ERROR 23000: Duplicate entry '40-40-40' for key 'id2_2'
+ROLLBACK;
+SELECT * FROM t1;
+id1 id2 id3 id4 id5 value1 value2 value3
+40 40 40 40 40 40 40 40
+39 31 32 NULL 39 37 37 37
+38 31 32 NULL 38 37 37 37
+37 31 32 NULL 37 37 37 37
+30 31 32 33 34 30 30 30
+22 20 20 NULL NULL 20 20 20
+21 20 20 NULL NULL 20 20 20
+20 20 20 NULL NULL 20 20 20
+10 10 10 10 10 11 5 5
+9 9 9 9 9 10 4 4
+8 8 8 8 8 9 4 4
+7 7 7 7 7 8 3 3
+6 6 6 6 6 7 3 3
+5 5 5 5 5 6 2 2
+4 4 4 4 4 5 2 2
+3 3 3 3 3 4 1 1
+2 2 2 2 2 3 1 1
+1 1 1 1 1 2 0 0
+disconnect con1;
+disconnect con2;
+connection default;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/unsupported_tx_isolations.result b/storage/rocksdb/mysql-test/rocksdb/r/unsupported_tx_isolations.result
new file mode 100644
index 00000000..32776e19
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/unsupported_tx_isolations.result
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS t1;
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1);
+SET session transaction isolation level read uncommitted;
+begin;
+insert into t1 values (2,1);
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level READ-UNCOMMITTED
+select * from t1 where id=1;
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level READ-UNCOMMITTED
+rollback;
+SET session transaction isolation level serializable;
+begin;
+insert into t1 values (2,1);
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level SERIALIZABLE
+select * from t1 where id=1;
+ERROR HY000: MyRocks supports only READ COMMITTED and REPEATABLE READ isolation levels. Please change from current isolation level SERIALIZABLE
+rollback;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/update.result b/storage/rocksdb/mysql-test/rocksdb/r/update.result
new file mode 100644
index 00000000..791fddd2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/update.result
@@ -0,0 +1,121 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+UPDATE t1 SET a=a+100;
+SELECT a,b FROM t1;
+a b
+101 a
+101 a
+10100 foobar
+10100 foobar
+102 b
+102 b
+103 c
+103 c
+104 d
+104 d
+105 e
+105 e
+UPDATE t1 SET a=a-100, b=DEFAULT WHERE a>100;
+SELECT a,b FROM t1;
+a b
+1 NULL
+1 NULL
+10000 NULL
+10000 NULL
+2 NULL
+2 NULL
+3 NULL
+3 NULL
+4 NULL
+4 NULL
+5 NULL
+5 NULL
+UPDATE t1 SET b = 'update' WHERE a <= 4 ORDER BY b DESC, a ASC LIMIT 1;
+SELECT a,b FROM t1;
+a b
+1 NULL
+1 update
+10000 NULL
+10000 NULL
+2 NULL
+2 NULL
+3 NULL
+3 NULL
+4 NULL
+4 NULL
+5 NULL
+5 NULL
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+BEGIN;
+UPDATE t1 SET a=a+100;
+UPDATE t1 SET a=a-50, b=DEFAULT WHERE a>100;
+COMMIT;
+SELECT * FROM t1 ORDER BY pk;
+a b pk
+10050 NULL 12
+10050 NULL 6
+51 NULL 1
+51 NULL 7
+52 NULL 2
+52 NULL 8
+53 NULL 3
+53 NULL 9
+54 NULL 10
+54 NULL 4
+55 NULL 11
+55 NULL 5
+BEGIN;
+UPDATE t1 SET b = 'update' WHERE a <= 4 ORDER BY a DESC, b ASC LIMIT 3;
+UPDATE t1 SET b = '';
+ROLLBACK;
+SELECT * FROM t1 ORDER BY pk;
+a b pk
+51 NULL 1
+52 NULL 2
+53 NULL 3
+54 NULL 4
+55 NULL 5
+10050 NULL 6
+51 NULL 7
+52 NULL 8
+53 NULL 9
+54 NULL 10
+55 NULL 11
+10050 NULL 12
+BEGIN;
+UPDATE t1 SET b = 'update2' WHERE a <= 100;
+SAVEPOINT spt1;
+UPDATE t1 SET b = '';
+ROLLBACK TO SAVEPOINT spt1;
+ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+UPDATE t1 SET b = 'upd' WHERE a = 10050;
+COMMIT;
+ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+SELECT * FROM t1 ORDER BY pk;
+a b pk
+51 NULL 1
+52 NULL 2
+53 NULL 3
+54 NULL 4
+55 NULL 5
+10050 NULL 6
+51 NULL 7
+52 NULL 8
+53 NULL 9
+54 NULL 10
+55 NULL 11
+10050 NULL 12
+DROP TABLE t1;
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a,b) VALUES (1,'foo'),(2,'bar');
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+a b
+101 foo
+102 bar
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/update_ignore.result b/storage/rocksdb/mysql-test/rocksdb/r/update_ignore.result
new file mode 100644
index 00000000..d36371be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/update_ignore.result
@@ -0,0 +1,57 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE IGNORE t1 SET b = 'upd1' WHERE b IS NOT NULL ORDER BY a LIMIT 1;
+SELECT a,b FROM t1 ORDER BY pk;
+a b
+1 upd1
+2 b
+3 c
+4 d
+5 e
+10000 foobar
+1 a
+2 b
+3 c
+4 d
+5 e
+10000 foobar
+UPDATE t1, t2 SET b = 'upd2a', c = 'upd2b'
+WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+ERROR 21000: Subquery returns more than 1 row
+UPDATE IGNORE t1, t2 SET b = 'upd2a', c = 'upd2b'
+WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+Warnings:
+Warning 1242 Subquery returns more than 1 row
+SELECT a,b FROM t1 ORDER BY pk;
+a b
+1 upd2a
+2 upd2a
+3 upd2a
+4 upd2a
+5 upd2a
+10000 upd2a
+1 a
+2 upd2a
+3 upd2a
+4 upd2a
+5 upd2a
+10000 upd2a
+SELECT c,d FROM t2 ORDER BY pk;
+c d
+upd2b 1
+upd2b 2
+upd2b 3
+upd2b 4
+upd2b 5
+upd2b 10000
+upd2b 1
+upd2b 2
+upd2b 3
+upd2b 4
+upd2b 5
+upd2b 10000
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/update_multi.result b/storage/rocksdb/mysql-test/rocksdb/r/update_multi.result
new file mode 100644
index 00000000..294c07b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/update_multi.result
@@ -0,0 +1,691 @@
+DROP TABLE IF EXISTS t1,t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar 10000
+foobar 10000
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+SELECT a,b FROM t1;
+a b
+1 a
+1 a
+101 b
+101 b
+101 c
+101 c
+101 d
+101 d
+101 e
+101 e
+101 foobar
+101 foobar
+SELECT c,d FROM t2;
+c d
+foobar NULL
+foobar NULL
+multi 1
+multi 1
+multi 2
+multi 2
+multi 3
+multi 3
+multi 4
+multi 4
+multi 5
+multi 5
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/update_with_keys.result b/storage/rocksdb/mysql-test/rocksdb/r/update_with_keys.result
new file mode 100644
index 00000000..576fe6f6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/update_with_keys.result
@@ -0,0 +1,38 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, INDEX(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+UPDATE t1 SET a=100, b='f' WHERE b IN ('b','c');
+UPDATE t1 SET b='m' WHERE b = 'f';
+UPDATE t1 SET b='z' WHERE a < 2;
+UPDATE t1 SET b='';
+SELECT a,b FROM t1;
+a b
+1
+100
+100
+4
+5
+6
+7
+8
+DROP TABLE t1;
+# RocksDB: skip the test for secondary UNIQUE keys.
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(0,'f'),(100,'a');
+UPDATE t1 SET a=a+200;
+UPDATE t1 SET a=0 WHERE a > 250;
+UPDATE t1 SET a=205 WHERE a=200;
+ERROR 23000: Duplicate entry '205' for key 'PRIMARY'
+UPDATE t1 SET a=12345 ORDER BY a DESC, b LIMIT 1;
+SELECT a,b FROM t1;
+a b
+0 a
+12345 e
+200 f
+201 a
+202 b
+203 c
+204 d
+UPDATE t1 SET a=80 WHERE a IN (202,203);
+ERROR 23000: Duplicate entry '80' for key 'PRIMARY'
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/use_direct_io_for_flush_and_compaction.result b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_io_for_flush_and_compaction.result
new file mode 100644
index 00000000..8a4ee14c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_io_for_flush_and_compaction.result
@@ -0,0 +1,18 @@
+Checking direct reads
+CREATE TABLE t1 (pk INT PRIMARY KEY DEFAULT '0', a INT(11), b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL DEFAULT 0,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (1, 1,'a');
+INSERT INTO t1 (a,b) VALUES (2,'b');
+set global rocksdb_force_flush_memtable_now=1;
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads.result b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads.result
new file mode 100644
index 00000000..8a4ee14c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads.result
@@ -0,0 +1,18 @@
+Checking direct reads
+CREATE TABLE t1 (pk INT PRIMARY KEY DEFAULT '0', a INT(11), b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `pk` int(11) NOT NULL DEFAULT 0,
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL,
+ PRIMARY KEY (`pk`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+INSERT INTO t1 VALUES (1, 1,'a');
+INSERT INTO t1 (a,b) VALUES (2,'b');
+set global rocksdb_force_flush_memtable_now=1;
+SELECT a,b FROM t1;
+a b
+1 a
+2 b
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads_writes.result b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads_writes.result
new file mode 100644
index 00000000..eda49c58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/use_direct_reads_writes.result
@@ -0,0 +1,22 @@
+call mtr.add_suppression("rocksdb");
+call mtr.add_suppression("Aborting");
+# This shows that RocksDB plugin is loaded:
+select plugin_name, plugin_type from information_schema.plugins where plugin_name='RocksDB';
+plugin_name plugin_type
+ROCKSDB STORAGE ENGINE
+Checking direct reads
+# restart
+FOUND 1 /enable both use_direct_reads/ in use_direct_reads_writes.err
+Checking direct writes
+# restart
+FOUND 1 /enable both use_direct_io_for_flush_and_compaction/ in use_direct_reads_writes.err
+Checking rocksdb_flush_log_at_trx_commit
+# restart
+FOUND 1 /rocksdb_flush_log_at_trx_commit needs to be/ in use_direct_reads_writes.err
+Validate flush_log settings when direct writes is enabled
+set global rocksdb_flush_log_at_trx_commit=0;
+set global rocksdb_flush_log_at_trx_commit=1;
+ERROR 42000: Variable 'rocksdb_flush_log_at_trx_commit' can't be set to the value of '1'
+set global rocksdb_flush_log_at_trx_commit=2;
+ERROR 42000: Variable 'rocksdb_flush_log_at_trx_commit' can't be set to the value of '2'
+# restart
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/validate_datadic.result b/storage/rocksdb/mysql-test/rocksdb/r/validate_datadic.result
new file mode 100644
index 00000000..5d9a6342
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/validate_datadic.result
@@ -0,0 +1,9 @@
+call mtr.add_suppression('RocksDB: Schema mismatch');
+CREATE TABLE t1 (pk int primary key) ENGINE=ROCKSDB;
+CREATE TABLE t2 (pk int primary key) ENGINE=ROCKSDB PARTITION BY KEY(pk) PARTITIONS 4;
+"Expect errors that we are missing two .frm files"
+FOUND 1 /RocksDB: Schema mismatch - Table test.t1 is registered in RocksDB but does not have a .frm file/ in validate_datadic.err
+FOUND 1 /RocksDB: Schema mismatch - Table test.t2 is registered in RocksDB but does not have a .frm file/ in validate_datadic.err
+"Expect an error that we have an extra .frm file"
+FOUND 1 /Schema mismatch - A .frm file exists for table test.t1_dummy, but that table is not registered in RocksDB/ in validate_datadic.err
+DROP TABLE t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/varbinary_format.result b/storage/rocksdb/mysql-test/rocksdb/r/varbinary_format.result
new file mode 100644
index 00000000..8b4634bb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/varbinary_format.result
@@ -0,0 +1,260 @@
+CREATE TABLE t1(
+vb VARBINARY(64) primary key
+) ENGINE=rocksdb;
+INSERT INTO t1 values(0x00);
+INSERT INTO t1 values(0x0000);
+INSERT INTO t1 values(0x0000000000000000);
+INSERT INTO t1 values(0x000000);
+INSERT INTO t1 values(0x000000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x0000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000);
+INSERT INTO t1 values(0x0000000000);
+INSERT INTO t1 values(0x00000000000000000000);
+INSERT INTO t1 values(0x000000000000);
+INSERT INTO t1 values(0x00000000000000);
+INSERT INTO t1 values(0x000000000000000000);
+SELECT hex(vb) FROM t1;
+hex(vb)
+00
+0000
+000000
+00000000
+0000000000
+000000000000
+00000000000000
+0000000000000000
+000000000000000000
+00000000000000000000
+00000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000
+BEGIN;
+SELECT hex(vb) FROM t1 FOR UPDATE;
+hex(vb)
+00
+0000
+000000
+00000000
+0000000000
+000000000000
+00000000000000
+0000000000000000
+000000000000000000
+00000000000000000000
+00000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+SUBSTRING(a.key,9)
+000000000000000001
+000000000000000002
+000000000000000003
+000000000000000004
+000000000000000005
+000000000000000006
+000000000000000007
+000000000000000008
+000000000000000009000000000000000001
+000000000000000009000000000000000002
+000000000000000009000000000000000009000000000000000009000000000000000007
+000000000000000009000000000000000009000000000000000009000000000000000008
+000000000000000009000000000000000009000000000000000009000000000000000009000000000000000001
+ROLLBACK;
+DROP TABLE t1;
+set session debug_dbug= '+d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+CREATE TABLE t1(
+vb VARBINARY(64) primary key
+) ENGINE=rocksdb;
+set session debug_dbug= '-d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+INSERT INTO t1 values(0x00);
+INSERT INTO t1 values(0x0000);
+INSERT INTO t1 values(0x0000000000000000);
+INSERT INTO t1 values(0x000000);
+INSERT INTO t1 values(0x000000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x0000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000);
+INSERT INTO t1 values(0x0000000000);
+INSERT INTO t1 values(0x00000000000000000000);
+INSERT INTO t1 values(0x000000000000);
+INSERT INTO t1 values(0x00000000000000);
+INSERT INTO t1 values(0x000000000000000000);
+SELECT hex(vb) FROM t1;
+hex(vb)
+00
+0000
+000000
+00000000
+0000000000
+000000000000
+00000000000000
+0000000000000000
+000000000000000000
+00000000000000000000
+00000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000
+BEGIN;
+SELECT hex(vb) FROM t1 FOR UPDATE;
+hex(vb)
+00
+0000
+000000
+00000000
+0000000000
+000000000000
+00000000000000
+0000000000000000
+000000000000000000
+00000000000000000000
+00000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+SUBSTRING(a.key,9)
+0000000000000000f8
+0000000000000000f9
+0000000000000000fa
+0000000000000000fb
+0000000000000000fc
+0000000000000000fd
+0000000000000000fe
+0000000000000000ff0000000000000000f7
+0000000000000000ff0000000000000000f8
+0000000000000000ff0000000000000000f9
+0000000000000000ff0000000000000000ff0000000000000000ff0000000000000000fe
+0000000000000000ff0000000000000000ff0000000000000000ff0000000000000000ff0000000000000000f7
+0000000000000000ff0000000000000000ff0000000000000000ff0000000000000000ff0000000000000000f8
+ROLLBACK;
+DROP TABLE t1;
+CREATE TABLE t1(
+vc VARCHAR(64) collate 'binary' primary key
+) ENGINE=rocksdb;
+INSERT INTO t1 values('a');
+INSERT INTO t1 values('aa');
+INSERT INTO t1 values('aaaaaaaa');
+INSERT INTO t1 values('aaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaa');
+INSERT INTO t1 values('aaaaa');
+INSERT INTO t1 values('aaaaaaaaaa');
+INSERT INTO t1 values('aaaaaa');
+INSERT INTO t1 values('aaaaaaa');
+INSERT INTO t1 values('aaaaaaaaa');
+SELECT * FROM t1;
+vc
+a
+aa
+aaa
+aaaa
+aaaaa
+aaaaaa
+aaaaaaa
+aaaaaaaa
+aaaaaaaaa
+aaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+BEGIN;
+SELECT * FROM t1 FOR UPDATE;
+vc
+a
+aa
+aaa
+aaaa
+aaaaa
+aaaaaa
+aaaaaaa
+aaaaaaaa
+aaaaaaaaa
+aaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+SUBSTRING(a.key,9)
+610000000000000001
+616100000000000002
+616161000000000003
+616161610000000004
+616161616100000005
+616161616161000006
+616161616161610007
+616161616161616108
+616161616161616109610000000000000001
+616161616161616109616100000000000002
+616161616161616109616161616161616109616161616161616109616161616161610007
+616161616161616109616161616161616109616161616161616109616161616161616108
+616161616161616109616161616161616109616161616161616109616161616161616109610000000000000001
+ROLLBACK;
+DROP TABLE t1;
+set session debug_dbug= '+d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+CREATE TABLE t1(
+vc VARCHAR(64) collate 'binary' primary key
+) ENGINE=rocksdb;
+set session debug_dbug= '-d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+INSERT INTO t1 values('a');
+INSERT INTO t1 values('aa');
+INSERT INTO t1 values('aaaaaaaa');
+INSERT INTO t1 values('aaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaa');
+INSERT INTO t1 values('aaaaa');
+INSERT INTO t1 values('aaaaaaaaaa');
+INSERT INTO t1 values('aaaaaa');
+INSERT INTO t1 values('aaaaaaa');
+INSERT INTO t1 values('aaaaaaaaa');
+SELECT * FROM t1;
+vc
+a
+aa
+aaa
+aaaa
+aaaaa
+aaaaaa
+aaaaaaa
+aaaaaaaa
+aaaaaaaaa
+aaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+BEGIN;
+SELECT * FROM t1 FOR UPDATE;
+vc
+a
+aa
+aaa
+aaaa
+aaaaa
+aaaaaa
+aaaaaaa
+aaaaaaaa
+aaaaaaaaa
+aaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+SUBSTRING(a.key,9)
+6100000000000000f8
+6161000000000000f9
+6161610000000000fa
+6161616100000000fb
+6161616161000000fc
+6161616161610000fd
+6161616161616100fe
+6161616161616161ff0000000000000000f7
+6161616161616161ff6100000000000000f8
+6161616161616161ff6161000000000000f9
+6161616161616161ff6161616161616161ff6161616161616161ff6161616161616100fe
+6161616161616161ff6161616161616161ff6161616161616161ff6161616161616161ff0000000000000000f7
+6161616161616161ff6161616161616161ff6161616161616161ff6161616161616161ff6100000000000000f8
+ROLLBACK;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/write_sync.result b/storage/rocksdb/mysql-test/rocksdb/r/write_sync.result
new file mode 100644
index 00000000..d0a9b034
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/write_sync.result
@@ -0,0 +1,30 @@
+SET GLOBAL rocksdb_write_disable_wal=false;
+SET GLOBAL rocksdb_write_ignore_missing_column_families=true;
+create table aaa (id int primary key, i int) engine rocksdb;
+set @save_rocksdb_flush_log_at_trx_commit=@@global.rocksdb_flush_log_at_trx_commit;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=1;
+insert aaa(id, i) values(0,1);
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+insert aaa(id, i) values(1,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+variable_value-@a
+1
+insert aaa(id, i) values(2,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+variable_value-@a
+2
+insert aaa(id, i) values(3,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+variable_value-@a
+3
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+SET GLOBAL rocksdb_flush_log_at_trx_commit=0;
+insert aaa(id, i) values(4,1);
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+SET GLOBAL rocksdb_flush_log_at_trx_commit=2;
+insert aaa(id, i) values(5,1);
+truncate table aaa;
+drop table aaa;
+set @@global.rocksdb_flush_log_at_trx_commit=@save_rocksdb_flush_log_at_trx_commit;
+SET GLOBAL rocksdb_write_disable_wal=false;
+SET GLOBAL rocksdb_write_ignore_missing_column_families=false;
diff --git a/storage/rocksdb/mysql-test/rocksdb/r/xa.result b/storage/rocksdb/mysql-test/rocksdb/r/xa.result
new file mode 100644
index 00000000..8cb6f39b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/r/xa.result
@@ -0,0 +1,70 @@
+#
+# MDEV-742 fixes
+# MDEV-13155: XA recovery not supported for RocksDB
+# as well.
+call mtr.add_suppression("Found .* prepared XA transactions");
+connect con1,localhost,root,,test;
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+XA START 'xa1';
+INSERT INTO t1 (a) VALUES (1),(2);
+XA END 'xa1';
+XA PREPARE 'xa1';
+connect con2,localhost,root,,test;
+XA START 'xa2';
+INSERT INTO t1 (a) VALUES (3);
+INSERT INTO t1 (a) VALUES (4);
+XA END 'xa2';
+XA PREPARE 'xa2';
+connect con3,localhost,root,,test;
+XA START 'xa3';
+INSERT INTO t1 (a) VALUES (5);
+INSERT INTO t1 (a) VALUES (6);
+XA END 'xa3';
+XA PREPARE 'xa3';
+disconnect con3;
+connection default;
+SELECT * FROM t1;
+a
+Must be all three XA:s in
+XA RECOVER;
+formatID gtrid_length bqual_length data
+1 3 0 xa3
+1 3 0 xa1
+1 3 0 xa2
+# restart
+connect con3,localhost,root,,test;
+XA RECOVER;
+formatID gtrid_length bqual_length data
+1 3 0 xa3
+1 3 0 xa1
+1 3 0 xa2
+XA ROLLBACK 'xa1';
+XA COMMIT 'xa2';
+XA ROLLBACK 'xa3';
+SELECT a FROM t1;
+a
+3
+4
+connect con4,localhost,root,,test;
+XA START 'xa4';
+INSERT INTO t1 (a) VALUES (7);
+INSERT INTO t1 (a) VALUES (8);
+XA END 'xa4';
+XA PREPARE 'xa4';
+connection default;
+# Now restart through graceful shutdown
+# restart
+connect con5,localhost,root,,test;
+Must have 'xa4'
+XA RECOVER;
+formatID gtrid_length bqual_length data
+1 3 0 xa4
+XA COMMIT 'xa4';
+SELECT a FROM t1;
+a
+3
+4
+7
+8
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/slow_query_log.awk b/storage/rocksdb/mysql-test/rocksdb/slow_query_log.awk
new file mode 100644
index 00000000..a921f472
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/slow_query_log.awk
@@ -0,0 +1,25 @@
+/Query_time:/ {
+ results["Rows_examined:"] = "uninit";
+ results["RocksDB_key_skipped:"] = "uninit";
+ results["RocksDB_del_skipped:"] = "uninit";
+
+ for (i = 2; i <= NF; i = i+2) {
+ results[$i] = $(i+1);
+ }
+
+ # If the output format has changed and we don't find these keys,
+ # error out.
+ if (results["Rows_examined:"] == "uninit" ||
+ results["RocksDB_key_skipped:"] == "uninit" ||
+ results["RocksDB_del_skipped:"] == "uninit") {
+ exit(-2);
+ }
+
+ if (results["Rows_examined:"] == 0) {
+ next
+ }
+ if (results["RocksDB_key_skipped:"] == 0 ||
+ results["RocksDB_del_skipped:"] == 0) {
+ exit(-1);
+ }
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/suite.opt b/storage/rocksdb/mysql-test/rocksdb/suite.opt
new file mode 100644
index 00000000..8ca2405f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/suite.opt
@@ -0,0 +1 @@
+--plugin-load=$HA_ROCKSDB_SO --default-storage-engine=rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb/suite.pm b/storage/rocksdb/mysql-test/rocksdb/suite.pm
new file mode 100644
index 00000000..633e883f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/suite.pm
@@ -0,0 +1,28 @@
+package My::Suite::Rocksdb;
+
+use My::SysInfo;
+
+#
+# Note: ../rocksdb_sys_vars/suite.pm file has a similar
+# function. If you modify this file, consider modifying that one, too.
+#
+@ISA = qw(My::Suite);
+use My::Find;
+use File::Basename;
+use strict;
+
+#sub is_default { not $::opt_embedded_server }
+
+my $sst_dump=
+::mtr_exe_maybe_exists(
+ "$::bindir/storage/rocksdb$::opt_vs_config/sst_dump",
+ "$::path_client_bindir/sst_dump");
+return "RocksDB is not compiled, no sst_dump" unless $sst_dump;
+$ENV{MARIAROCKS_SST_DUMP}="$sst_dump";
+
+## Temporarily disable testing under valgrind, due to MDEV-12439
+#return "RocksDB tests disabled under valgrind" if ($::opt_valgrind);
+
+
+bless { };
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/1st.test b/storage/rocksdb/mysql-test/rocksdb/t/1st.test
new file mode 100644
index 00000000..cecef8b7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/1st.test
@@ -0,0 +1,36 @@
+--source include/have_rocksdb.inc
+
+#
+# This test checks some very basic capabilities
+# which will be used in almost every other test,
+# and will not be checked through support* variables.
+# If this test does not pass, there is no point
+# at executing other ones.
+#
+# Minimal requirements:
+# - supported column types: INT, CHAR (default CHAR(8), INT(11));
+# - column attributes as declared in define_engine.inc ($default_col_opts)
+# (by default empty, which means no additional attributes apart from the type);
+# - table attributes as declared in define_engine.inc ($default_tbl_opts)
+# (by default empty, which means no additional attributes apart from ENGINE);
+# - CREATE TABLE .. (column1 <column options>, column2 <column options>) ENGINE=<storage_engine>;
+# - INSERT INTO TABLE .. VALUES (val1,val2);
+# - DROP TABLE ..
+# - SELECT a,b FROM ..
+# - SHOW CREATE TABLE ..
+# - SHOW COLUMNS IN ...
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (pk INT PRIMARY KEY DEFAULT '0', a INT(11), b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+INSERT INTO t1 VALUES (1, 1,'a');
+INSERT INTO t1 (a,b) VALUES (2,'b');
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit-master.opt
new file mode 100644
index 00000000..83ed8522
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit-master.opt
@@ -0,0 +1 @@
+--binlog-format=row
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit.test b/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit.test
new file mode 100644
index 00000000..aeadf538
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/2pc_group_commit.test
@@ -0,0 +1,168 @@
+--source include/have_rocksdb.inc
+--source include/have_log_bin.inc
+--source include/not_windows.inc
+--echo # Disable for valgrind because this takes too long
+--source include/not_valgrind.inc
+
+# MariaDB: tooling to slowdown commits (also when running on ramdisk, we need
+# write_prepared for some reason, this is set in the .opt file)
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+--source include/have_write_prepared.inc
+
+
+--disable_warnings
+DROP DATABASE IF EXISTS mysqlslap;
+--enable_warnings
+
+CREATE DATABASE mysqlslap;
+USE mysqlslap;
+CREATE TABLE t1(id BIGINT AUTO_INCREMENT, value BIGINT, PRIMARY KEY(id)) ENGINE=rocksdb;
+
+SET @save_rocksdb_enable_2pc= @@rocksdb_enable_2pc;
+SET @save_rocksdb_flush_log_at_trx_commit= @@rocksdb_flush_log_at_trx_commit;
+
+
+#
+# In MariaDB, regular group commit operation does not cause increment of
+# rocksdb_wal_group_syncs.
+#
+
+--echo # 2PC enabled, MyRocks durability enabled
+SET GLOBAL rocksdb_enable_2pc=1;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=1;
+
+--echo ##
+--echo ## 2PC + durability + single thread
+--echo ##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+--exec $MYSQL_SLAP --silent --concurrency=1 --number-of-queries=1000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+select IF(variable_value - @b1 = 1000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select IF(variable_value - @b2 = 1000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+--echo # Prepare operations sync, commits don't. We expect slightly more than 1K syncs:
+select IF(variable_value - @b3 between 1000 and 1500, 'OK', variable_value - @b3) as Rocksdb_wal_synced
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+# SQL layer solution is sufficient for Binlog counts but not RocksDB.
+#set @tmp_bcwc= @@binlog_commit_wait_count;
+#set @tmp_bcwu= @@binlog_commit_wait_usec;
+#set global binlog_commit_wait_count=30;
+#set global binlog_commit_wait_usec=500*1000;
+
+# RocksDB-side solution:
+
+set debug_dbug='+d,rocksdb_enable_delay_commits';
+create table dummy10(a int) engine=rocksdb;
+drop table dummy10;
+set debug_dbug='-d,rocksdb_enable_delay_commits';
+
+--echo ##
+--echo ## 2PC + durability + group commit
+--echo ##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=10000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+
+select IF(variable_value - @b1 = 10000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select IF(variable_value - @b2 between 100 and 5000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+select IF(variable_value - @b3 between 1 and 9000, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+#set global binlog_commit_wait_count= @tmp_bcwc;
+#set global binlog_commit_wait_usec= @tmp_bcwu;
+
+set debug_dbug='+d,rocksdb_disable_delay_commits';
+create table dummy10(a int) engine=rocksdb;
+drop table dummy10;
+set debug_dbug='-d,rocksdb_disable_delay_commits';
+
+--echo ##
+--echo # 2PC enabled, MyRocks durability disabled, single thread
+--echo ##
+SET GLOBAL rocksdb_enable_2pc=1;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=0;
+
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+--exec $MYSQL_SLAP --silent --concurrency=1 --number-of-queries=1000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+
+select IF(variable_value - @b1 = 1000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select IF(variable_value - @b2 = 1000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+select IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+--echo ##
+--echo # 2PC enabled, MyRocks durability disabled, concurrent workload
+--echo ##
+
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=10000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+
+select IF(variable_value - @b1 = 10000, 'OK', variable_value - @b1) as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select IF(variable_value - @b2 < 8000, 'OK', variable_value - @b2) as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+select IF(variable_value - @b3 < 10, 'OK', variable_value - @b3)
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+##
+## The next two are disabled because they don't add any test coverage in
+## MariaDB. @@rocksdb_enable_2pc=0 is a MyRocks-internal setting, binlog
+## [group] commit still happens, and syncing RocksDB WAL too.
+##
+
+--disable_parsing
+--echo ##
+--echo # 2PC disabled, MyRocks durability enabled, one thread
+--echo ##
+SET GLOBAL rocksdb_enable_2pc=0;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=1;
+
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+--exec $MYSQL_SLAP --silent --concurrency=1 --number-of-queries=1000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+
+select variable_value - @b1 as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value - @b2 as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value - @b3 as Rocksdb_wal_synced
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+
+--echo ##
+--echo # 2PC disabled, MyRocks durability enabled, concurrent workload
+--echo ##
+select variable_value into @b1 from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value into @b2 from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value into @b3 from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=10000 --query="INSERT INTO t1 (id, value) VALUES(NULL, 1)"
+
+select variable_value - @b1 as Binlog_commits
+from information_schema.global_status where variable_name='Binlog_commits';
+select variable_value - @b2 as Binlog_group_commits
+from information_schema.global_status where variable_name='Binlog_group_commits';
+select variable_value - @b3 as Rocksdb_wal_synced
+from information_schema.global_status where variable_name='Rocksdb_wal_synced';
+--enable_parsing
+
+SET GLOBAL rocksdb_enable_2pc= @save_rocksdb_enable_2pc;
+SET GLOBAL rocksdb_flush_log_at_trx_commit= @save_rocksdb_flush_log_at_trx_commit;
+
+DROP TABLE t1;
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace.test b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace.test
new file mode 100644
index 00000000..df7790ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace.test
@@ -0,0 +1,417 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+##
+## test adding index inplace
+##
+
+# test basic add
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+DROP TABLE t1;
+
+# test add + drop (simultaneous)
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), DROP INDEX ka, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+DROP TABLE t1;
+
+## test multi-drop + multi-add
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 DROP INDEX ka, DROP INDEX kab, ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kb(b), ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+DROP TABLE t1;
+
+# test multi add + drop (simultaneous)
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD INDEX kb(b), DROP INDEX ka, ADD INDEX kba(b,a), DROP INDEX kab, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kba) WHERE a > 2;
+DROP TABLE t1;
+
+# test dropping and adding a key simultaneously w/ same name but different col
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+ALTER TABLE t1 DROP INDEX ka, ADD INDEX ka(b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(ka) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > 2;
+DROP TABLE t1;
+
+##
+## test adding index inplace w/ various column types
+##
+
+# test basic add
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > '2' AND b < 3;
+DROP TABLE t1;
+
+## test add + drop (simultaneous)
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX ka(a), DROP INDEX kab, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(ka) WHERE a > '2' AND b < 3;
+DROP TABLE t1;
+
+### test multi-drop + multi-add
+CREATE TABLE t1 (pk CHAR(8) PRIMARY KEY, a VARCHAR(11), b INT UNSIGNED) ENGINE=rocksdb charset utf8 collate utf8_bin;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+INSERT INTO t1 VALUES ('aaa', '1111', 1);
+INSERT INTO t1 VALUES ('bbb', '2222', 2);
+INSERT INTO t1 VALUES ('ccc', '3333', 3);
+ALTER TABLE t1 ADD INDEX kab(a,b), ADD INDEX ka(a), ADD INDEX kb(b), ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX ka, DROP INDEX kb, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kab) WHERE a > '2' AND b < 3;
+DROP TABLE t1;
+
+##
+## test adding via CREATE/DROP index syntax
+##
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+CREATE INDEX kb on t1 (b);
+CREATE INDEX kba on t1 (b,a);
+DROP INDEX ka on t1;
+DROP INDEX kab on t1;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) WHERE b > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kba) WHERE a > 2;
+DROP TABLE t1;
+
+#
+# Create tables with partitions and try to update/select from them.
+#
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+DROP INDEX kij ON t1;
+SHOW CREATE TABLE t1;
+
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+SELECT COUNT(*) FROM t1;
+
+DROP TABLE t1;
+
+# test failure in prepare phase (due to collation)
+set @tmp_rocksdb_strict_collation_check= @@rocksdb_strict_collation_check;
+set global rocksdb_strict_collation_check=1;
+CREATE TABLE t1 (a INT, b TEXT);
+
+--echo # MariaDB no longer gives ER_UNSUPPORTED_COLLATION
+ALTER TABLE t1 ADD KEY kb(b(10));
+ALTER TABLE t1 ADD PRIMARY KEY(a);
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT, b TEXT collate utf8_general_ci);
+--echo # MariaDB no longer gives ER_UNSUPPORTED_COLLATION
+ALTER TABLE t1 ADD KEY kb(b(10));
+ALTER TABLE t1 ADD PRIMARY KEY(a);
+DROP TABLE t1;
+
+set global rocksdb_strict_collation_check= @tmp_rocksdb_strict_collation_check;
+
+# make sure race condition between connection close and alter on another
+# connection is handled
+
+set global rocksdb_bulk_load=1;
+
+--echo # Establish connection con1 (user=root)
+connect (con1,localhost,root,,);
+
+--echo # Switch to connection con1
+connection con1;
+
+show global variables like 'rocksdb_bulk_load%';
+show session variables like 'rocksdb_bulk_load%';
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+INSERT INTO t1 VALUES (1,1);
+
+# Disconnect connection 1, this starts the code path that will call
+# rocksdb_close_connection, ending the bulk load.
+--echo # Disconnecting on con1
+disconnect con1;
+
+--echo # Establish connection con2 (user=root)
+connect (con2,localhost,root,,);
+--echo # Switch to connection con2
+connection con2;
+
+# when alter table happens, it tries to close all other TABLE instances
+# when acquiring the exclusive lock for alter table (this happens in SQL layer)
+# make sure bulk_load now handles this possible race condition properly
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+SELECT COUNT(*) FROM t1 FORCE INDEX(kj);
+
+DROP TABLE t1;
+disconnect con2;
+
+# make sure implicilty closing the alter from another session works
+
+--echo # Establish connection con1 (user=root)
+connect (con1,localhost,root,,);
+--echo # Establish connection con2 (user=root)
+connect (con2,localhost,root,,);
+
+--echo # Switch to connection con1
+connection con1;
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+set rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1,1);
+
+--echo # Switch to connection con2
+connection con2;
+
+# here, the bulk load hasn't been completed yet, and we are in conn2
+# therefore select count returns 0
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+
+# implicilty close the table from connection 2
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+
+SELECT COUNT(*) FROM t1 FORCE INDEX(PRIMARY);
+SELECT COUNT(*) FROM t1 FORCE INDEX(kj);
+
+set global rocksdb_bulk_load=0;
+
+DROP TABLE t1;
+
+connection default;
+
+
+SET @prior_rocksdb_merge_combine_read_size= @@rocksdb_merge_combine_read_size;
+SET @prior_rocksdb_strict_collation_check= @@rocksdb_strict_collation_check;
+SET @prior_rocksdb_merge_buf_size = @@rocksdb_merge_buf_size;
+
+SET global rocksdb_strict_collation_check = off;
+SET session rocksdb_merge_combine_read_size = 566;
+SET session rocksdb_merge_buf_size = 340;
+
+show variables like 'rocksdb_bulk_load%';
+CREATE TABLE t1 (a VARCHAR(80)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+INSERT INTO t1 (a) VALUES (REPEAT("a", 80));
+ALTER TABLE t1 ADD INDEX ka(a), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(ka) WHERE a > "";
+DROP TABLE t1;
+
+SET session rocksdb_merge_buf_size = @prior_rocksdb_merge_buf_size;
+SET session rocksdb_merge_combine_read_size = @prior_rocksdb_merge_combine_read_size;
+SET global rocksdb_strict_collation_check = @prior_rocksdb_strict_collation_check;
+
+# Test to make sure index statistics are updating properly
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+set global rocksdb_force_flush_memtable_now=1;
+
+--let $data_length_old = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+
+## uncomment to see the actual values
+#--replace_column 8 #
+#SHOW TABLE STATUS WHERE name LIKE 't1';
+
+# Now do an alter and see what happens
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+
+--let $data_length_new = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+--disable_query_log
+--eval select $data_length_old < $data_length_new as "larger"
+
+--source include/restart_mysqld.inc
+--source include/wait_until_connected_again.inc
+--let $data_length_new = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+--disable_query_log
+--eval select $data_length_old < $data_length_new as "larger"
+
+analyze table t1;
+--let $data_length_new = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+--disable_query_log
+--eval select $data_length_old < $data_length_new as "larger"
+
+--source include/restart_mysqld.inc
+--source include/wait_until_connected_again.inc
+--let $data_length_new = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+--disable_query_log
+--eval select $data_length_old < $data_length_new as "larger"
+
+# verifying multiple analyze table won't change stats
+--disable_query_log
+let $max = 10;
+let $i = 1;
+while ($i <= $max) {
+ let $analyze = ANALYZE TABLE t1;
+ inc $i;
+ eval $analyze;
+}
+--enable_query_log
+
+--let $data_length_new2 = query_get_value("select INDEX_LENGTH from information_schema.tables where table_schema=database() and table_name='t1'", INDEX_LENGTH, 1)
+--eval select $data_length_new2 < $data_length_new * 1.5 as "same"
+
+
+--enable_query_log
+
+## uncomment to see the actual values
+#--replace_column 8 #
+#SHOW TABLE STATUS WHERE name LIKE 't1';
+
+DROP TABLE t1;
+
+# https://github.com/facebook/mysql-5.6/issues/602
+# Add then drop same index should be optimized out.
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+c INT,
+KEY kbc(b,c)) ENGINE = ROCKSDB;
+INSERT INTO t1 (a,b,c) VALUES (1,1,1);
+INSERT INTO t1 (a,b,c) VALUES (2,2,2);
+INSERT INTO t1 (a,b,c) VALUES (3,3,3);
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 DROP INDEX kbc, ADD INDEX kbc(b,c), ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX kbc;
+DROP TABLE t1;
+
+# Make sure changing key part prefix length causes index rebuild as well.
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b varchar(10),
+index kb(b(5))
+) ENGINE = ROCKSDB charset utf8 collate utf8_bin;
+
+INSERT INTO t1 (a,b) VALUES (1,'1111122222');
+INSERT INTO t1 (a,b) VALUES (2,'2222233333');
+INSERT INTO t1 (a,b) VALUES (3,'3333344444');
+
+--let $start_max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+
+ALTER TABLE t1 DROP INDEX kb, ADD INDEX kb(b(8)), ALGORITHM=INPLACE;
+SELECT * FROM t1 FORCE INDEX(kb);
+
+--let $end_max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+
+if ($end_max_index_id <= $start_max_index_id) {
+ echo Max index ID did not increase;
+}
+
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+# Cardinality checks for indexes statistics
+SET @prior_rocksdb_table_stats_sampling_pct = @@rocksdb_table_stats_sampling_pct;
+set global rocksdb_table_stats_sampling_pct = 100;
+
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+
+INSERT INTO t1 (a, b) VALUES (1, 10);
+INSERT INTO t1 (a, b) VALUES (2, 10);
+INSERT INTO t1 (a, b) VALUES (3, 20);
+INSERT INTO t1 (a, b) VALUES (4, 20);
+
+set global rocksdb_force_flush_memtable_now=1;
+analyze table t1;
+
+SHOW INDEX in t1;
+
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+SHOW INDEX in t1;
+
+DROP TABLE t1;
+SET global rocksdb_table_stats_sampling_pct = @prior_rocksdb_table_stats_sampling_pct;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality-master.opt
new file mode 100644
index 00000000..436edf2b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality-master.opt
@@ -0,0 +1 @@
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality.test b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality.test
new file mode 100644
index 00000000..148edf7a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_cardinality.test
@@ -0,0 +1,44 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Test that fast secondary index creation updates cardinality properly
+CREATE TABLE t1 (i INT PRIMARY KEY, j INT) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1,2), (2,4), (3,6), (4,8), (5,10);
+
+SET debug_sync= 'rocksdb.commit_in_place_alter_table WAIT_FOR flushed';
+send ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+
+connect (con1,localhost,root,,);
+
+# Flush memtable out to SST
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+SET debug_sync= 'now SIGNAL flushed';
+
+connection default;
+reap;
+
+# Return the data for the primary key of t1
+--replace_column 1 # 2 # 3 SSTNAME 5 # 6 # 7 # 8 # 9 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+ (SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+ WHERE TABLE_NAME = 't1' AND INDEX_NAME = "PRIMARY");
+
+# Return the data for the secondary index of t1
+--replace_column 1 # 2 # 3 SSTNAME 5 # 6 # 7 # 8 # 9 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+ (SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+ WHERE TABLE_NAME = 't1' AND INDEX_NAME = "kj");
+
+disconnect con1;
+SET debug_sync='RESET';
+
+# cleanup
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_crash.test b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_crash.test
new file mode 100644
index 00000000..4f34cbe8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_crash.test
@@ -0,0 +1,118 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+--source include/have_partition.inc
+--source include/not_valgrind.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+#
+# test crash recovery
+#
+
+CREATE TABLE t1 (a INT, b INT, KEY ka(a), KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+
+--echo # crash_during_online_index_creation
+flush logs;
+
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="+d,crash_during_online_index_creation";
+--error 2013
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+SET SESSION debug_dbug="-d,crash_during_online_index_creation";
+
+SHOW CREATE TABLE t1;
+CHECK TABLE t1;
+
+DROP TABLE t1;
+
+#
+# Test crash recovery with partitioned tables
+#
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+--echo # crash_during_index_creation_partition
+flush logs;
+
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="+d,crash_during_index_creation_partition";
+--error 2013
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+SET SESSION debug_dbug="-d,crash_during_index_creation_partition";
+
+SHOW CREATE TABLE t1;
+
+# here, the index numbers should be higher because previously 4 index numbers
+# were allocated for the partitioned table
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+SELECT COUNT(*) FROM t1;
+
+DROP TABLE t1;
+
+#
+# Test rollback on partitioned tables for inplace alter
+#
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i), KEY(j)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+--echo # crash_during_index_creation_partition
+flush logs;
+
+SET SESSION debug_dbug="+d,myrocks_simulate_index_create_rollback";
+
+--error 1105
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SET SESSION debug_dbug="-d,myrocks_simulate_index_create_rollback";
+SHOW CREATE TABLE t1;
+
+# here, the index numbers should be higher because previously 4 index numbers
+# were allocated for the partitioned table
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+
+SHOW CREATE TABLE t1;
+SELECT COUNT(*) FROM t1;
+
+DROP TABLE t1;
+
+# Cleanup temporary #sql files. In the future server will remove these
+# automatically but for now we need to do the delete explicit
+
+--disable_query_log
+--disable_result_log
+let $datadir=`select @@datadir`;
+--remove_files_wildcard $datadir/test #sql*
+--enable_result_log
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_sstfilewriter.test b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_sstfilewriter.test
new file mode 100644
index 00000000..61a10a60
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_index_inplace_sstfilewriter.test
@@ -0,0 +1,113 @@
+--source include/have_rocksdb.inc
+
+# This test requires ~1.3G of disk space
+--source include/big_test.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+# Create a table with a primary key and one secondary key as well as one
+# more column
+CREATE TABLE t1(pk CHAR(5) PRIMARY KEY, a char(30), b char(30)) COLLATE 'latin1_bin';
+
+--let $file = `SELECT CONCAT(@@datadir, "test_loadfile.txt")`
+
+# Create a text file with data to import into the table.
+# The primary key is in sorted order and the secondary keys are randomly generated
+--let ROCKSDB_INFILE = $file
+perl;
+my $fn = $ENV{'ROCKSDB_INFILE'};
+open(my $fh, '>>', $fn) || die "perl open($fn): $!";
+my $max = 300000;
+my @chars = ("A".."Z", "a".."z", "0".."9");
+my @lowerchars = ("a".."z");
+my @powers_of_26 = (26 * 26 * 26 * 26, 26 * 26 * 26, 26 * 26, 26, 1);
+for (my $ii = 0; $ii < $max; $ii++)
+{
+ my $pk;
+ my $tmp = $ii;
+ foreach (@powers_of_26)
+ {
+ $pk .= $lowerchars[$tmp / $_];
+ $tmp = $tmp % $_;
+ }
+
+ my $num = int(rand(25)) + 6;
+ my $a;
+ $a .= $chars[rand(@chars)] for 1..$num;
+
+ $num = int(rand(25)) + 6;
+ my $b;
+ $b .= $chars[rand(@chars)] for 1..$num;
+ print $fh "$pk\t$a\t$b\n";
+}
+close($fh);
+EOF
+
+--file_exists $file
+
+set rocksdb_bulk_load=1;
+set rocksdb_bulk_load_size=10000;
+--disable_query_log
+--echo LOAD DATA INFILE <input_file> INTO TABLE t1;
+eval LOAD DATA INFILE '$file' INTO TABLE t1;
+--enable_query_log
+set rocksdb_bulk_load=0;
+
+# Make sure all the data is there.
+select count(pk) from t1;
+select count(a) from t1;
+select count(b) from t1;
+
+# now do fast secondary index creation
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+# disable duplicate index warning
+--disable_warnings
+# now do same index using copy algorithm
+# hitting max row locks (1M)
+set @tmp= @@rocksdb_max_row_locks;
+set session rocksdb_max_row_locks=1000;
+--error ER_GET_ERRMSG
+ALTER TABLE t1 ADD INDEX kb_copy(b), ALGORITHM=COPY;
+set session rocksdb_bulk_load=1;
+ALTER TABLE t1 ADD INDEX kb_copy(b), ALGORITHM=COPY;
+set session rocksdb_bulk_load=0;
+--enable_warnings
+set session rocksdb_max_row_locks=@tmp;
+
+# checksum testing
+SELECT COUNT(*) as c FROM
+(SELECT COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `b`, CONCAT(ISNULL(`b`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `t1` FORCE INDEX(`kb`)
+UNION DISTINCT
+SELECT COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#',
+`b`, CONCAT(ISNULL(`b`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `t1` FORCE
+INDEX(`kb_copy`)) as temp;
+
+select count(*) from t1 FORCE INDEX(kb);
+select count(*) from t1 FORCE INDEX(kb_copy);
+select count(*) from t1 FORCE INDEX(PRIMARY);
+
+# drop the index
+ALTER TABLE t1 DROP INDEX kb, ALGORITHM=INPLACE;
+ALTER TABLE t1 DROP INDEX kb_copy, ALGORITHM=INPLACE;
+
+# add two indexes simultaneously
+ALTER TABLE t1 ADD INDEX kb(b), ADD INDEX kab(a,b), ALGORITHM=INPLACE;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kab);
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
+# Reverse CF testing, needs to be added to SSTFileWriter in reverse order
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT, KEY kab(a,b)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 DROP INDEX kab, ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kb(b) comment 'rev:cf1', ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+DROP TABLE t1;
+--remove_file $file
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/add_unique_index_inplace.test b/storage/rocksdb/mysql-test/rocksdb/t/add_unique_index_inplace.test
new file mode 100644
index 00000000..cf0546d1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/add_unique_index_inplace.test
@@ -0,0 +1,101 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+# test adding duplicate value before unique index
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+
+INSERT INTO t1 (a,b) VALUES (4,5);
+
+# should cause error here, duplicate value on b
+--error 1062
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+# test dup value AFTER unique index
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+
+# should error here, duplicate value on b
+--error 1062
+INSERT INTO t1 (a,b) VALUES (4,5);
+
+# should succeed
+INSERT INTO t1 (a,b) VALUES (5,8);
+
+SHOW CREATE TABLE t1;
+SELECT * FROM t1 FORCE INDEX(kb);
+DROP TABLE t1;
+
+# test what happens when duplicate nulls exist
+CREATE TABLE t1 (a INT, b INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, NULL);
+INSERT INTO t1 (a, b) VALUES (3, NULL);
+
+# should pass, because in MySQL we allow multiple NULLS in unique key
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b), ALGORITHM=INPLACE;
+INSERT INTO t1 (a, b) VALUES (4, NULL);
+
+SHOW CREATE TABLE t1;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+DROP TABLE t1;
+
+## test case with multi-part key with nulls
+CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY ka(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a,b,c) VALUES (1,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (2,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (3,1,NULL);
+INSERT INTO t1 (a,b,c) VALUES (4,1,5);
+
+# should pass
+ALTER TABLE t1 ADD UNIQUE INDEX kbc(b,c), ALGORITHM=INPLACE;
+
+SHOW CREATE TABLE t1;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kbc);
+DROP TABLE t1;
+
+## test case with table w/ no primary key, and we try to add unique key
+CREATE TABLE t1 (a INT, b INT) ENGINE=RocksDB;
+INSERT INTO t1 (a, b) VALUES (1, 5);
+INSERT INTO t1 (a, b) VALUES (2, 6);
+INSERT INTO t1 (a, b) VALUES (3, 7);
+
+# should succeed, we can add unique index on table w/ no pk
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b);
+INSERT INTO t1 (a, b) VALUES (4, 8);
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a, b) VALUES (5, 5);
+
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+
+# Test changing key with same name and parts from not unique to unique, should
+# fail.
+CREATE TABLE t1 (
+a INT PRIMARY KEY,
+b INT,
+c INT,
+KEY kbc(b,c)) ENGINE = ROCKSDB;
+INSERT INTO t1 (a,b,c) VALUES (1,1,1);
+INSERT INTO t1 (a,b,c) VALUES (2,2,2);
+INSERT INTO t1 (a,b,c) VALUES (3,2,2);
+
+# should cause error here, duplicate value 2-2
+--error 1062
+ALTER TABLE t1 DROP INDEX kbc, ADD UNIQUE INDEX kbc(b,c), ALGORITHM=INPLACE;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/allow_no_pk_concurrent_insert.test b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_pk_concurrent_insert.test
new file mode 100644
index 00000000..8dda4372
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_pk_concurrent_insert.test
@@ -0,0 +1,22 @@
+
+#
+# Tests concurrent inserts for tables with no primary key.
+#
+
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+--echo # Binary must be compiled with debug for this test
+--source include/have_debug.inc
+
+# create the actual table
+CREATE TABLE t1 (a INT) ENGINE=rocksdb;
+
+let $exec = python ../storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_insert.py root 127.0.0.1 $MASTER_MYPORT test t1 100 4;
+exec $exec;
+
+SELECT COUNT(*) from t1;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key.test b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key.test
new file mode 100644
index 00000000..5f2a37f2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key.test
@@ -0,0 +1,126 @@
+--source include/have_rocksdb.inc
+
+#
+# This test checks some very basic capabilities
+# for tables without primary keys. A hidden pk will be generated under the hood
+# in myrocks. Everything should work here as normal.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# test CREATE
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+## test ALTER
+CREATE TABLE t1 (a INT, c CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,'a'),(5,'z');
+ALTER TABLE t1 ADD COLUMN b INT;
+SHOW CREATE TABLE t1;
+
+--sorted_result
+SELECT * FROM t1;
+ALTER TABLE t1 DROP COLUMN b;
+SHOW CREATE TABLE t1;
+--sorted_result
+SELECT * FROM t1;
+DROP TABLE t1;
+
+## test creating a table with primary and then dropping that key
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+ALTER TABLE t1 DROP COLUMN pk;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+# test CHECK TABLE
+# CHECK TABLE statements
+#
+# Note: the output is likely to be different for the engine under test,
+# in which case rdiff will be needed. Or, the output might say that
+# the storage engine does not support CHECK.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+
+CREATE TABLE t2 (a INT, b CHAR(8)) ENGINE=rocksdb;
+
+CHECK TABLE t1;
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+CHECK TABLE t1, t2 FOR UPGRADE;
+INSERT INTO t2 (a,b) VALUES (5,'e');
+CHECK TABLE t2 QUICK;
+INSERT INTO t1 (a,b) VALUES (6,'f');
+CHECK TABLE t1 FAST;
+INSERT INTO t1 (a,b) VALUES (7,'g');
+INSERT INTO t2 (a,b) VALUES (8,'h');
+CHECK TABLE t2, t1 MEDIUM;
+INSERT INTO t1 (a,b) VALUES (9,'i');
+INSERT INTO t2 (a,b) VALUES (10,'j');
+CHECK TABLE t1, t2 EXTENDED;
+INSERT INTO t1 (a,b) VALUES (11,'k');
+CHECK TABLE t1 CHANGED;
+
+DROP TABLE t1, t2;
+
+# test unique keys with no primary key
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'c');
+SELECT * FROM t1;
+SELECT * FROM t1 WHERE a = 2;
+EXPLAIN SELECT * FROM t1 WHERE a = 2;
+
+DROP TABLE t1;
+
+## test restarting a table that has no data
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+--source include/restart_mysqld.inc
+
+## single delete statement should remove MULTIPLE rows (aka duplicate rows)
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (36,'foo');
+DELETE FROM t1 WHERE a = 35 AND b = 'foo';
+--sorted_result
+SELECT * FROM t1;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue #834/MDEV-15304 ALTER TABLE table_with_hidden_pk causes Can't
+--echo # write; duplicate key in table error and/or crash
+--echo #
+CREATE TABLE t1 (a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1),(1+1);
+create table t2 (a int);
+insert into t2 values (10),(20),(30);
+
+BEGIN;
+select * from t2;
+
+connect (con1,localhost,root,,);
+connection con1;
+alter table t1 force;
+
+connection default;
+select * from t1;
+
+connection con1;
+insert into t1 values (100);
+select * from t1;
+
+disconnect con1;
+connection default;
+rollback;
+drop table t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key_with_sk.test b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key_with_sk.test
new file mode 100644
index 00000000..18ccf2e3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/allow_no_primary_key_with_sk.test
@@ -0,0 +1,149 @@
+--source include/have_rocksdb.inc
+
+#
+# This test checks some very basic capabilities
+# for tables without primary keys. A hidden pk will be generated under the hood
+# in myrocks. Everything should work here as normal.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+## test CREATE with SK
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a)) ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+## test adding/dropping sk w/no pk
+CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+ALTER TABLE t1 ADD INDEX (b);
+--source no_primary_key_basic_ops.inc
+
+ALTER TABLE t1 DROP INDEX b;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+# test dropping pk w/ sk
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+ALTER TABLE t1 DROP COLUMN pk;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4313: RocksDB: Server crashes in Rdb_key_def::setup on dropping the primary key column
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT NOT NULL, KEY(i)) ENGINE=RocksDB;
+ALTER TABLE t1 DROP COLUMN `pk`;
+DROP TABLE t1;
+
+# create table with multiple sk, make sure it still works
+# test CREATE with SK
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a), KEY(b)) ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+# test CREATE table with multi-part sk
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a, b)) ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+# test CREATE table with more than one sk
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a), KEY(b)) ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+# test check table with sk
+CREATE TABLE t1 (a INT, b CHAR(8), KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a) VALUES (1),(2),(5);
+CHECK TABLE t1;
+INSERT INTO t1 (a) VALUES (6),(8),(12);
+CHECK TABLE t1 FOR UPGRADE;
+INSERT INTO t1 (a) VALUES (13),(15),(16);
+CHECK TABLE t1 QUICK;
+INSERT INTO t1 (a) VALUES (17),(120),(132);
+CHECK TABLE t1 FAST;
+INSERT INTO t1 (a) VALUES (801),(900),(7714);
+CHECK TABLE t1 MEDIUM;
+INSERT INTO t1 (a) VALUES (8760),(10023),(12000);
+CHECK TABLE t1 EXTENDED;
+INSERT INTO t1 (a) VALUES (13345),(24456),(78302),(143028);
+CHECK TABLE t1 CHANGED;
+DROP TABLE t1;
+
+## tables with multi-part secondary indexes + columns that dont belong to any
+## secondary indexes
+CREATE TABLE t1 (a INT, b INT, c INT, d INT, KEY kab(a, b), KEY kbc(b, c), KEY kabc(a,b,c)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b,c,d) VALUES (1,2,3,4);
+INSERT INTO t1 (a,b,c,d) VALUES (5,6,7,8);
+INSERT INTO t1 (a,b,c,d) VALUES (10,11,12,13);
+INSERT INTO t1 (a,b,c,d) VALUES (14,15,16,17);
+
+--sorted_result
+SELECT * FROM t1;
+--sorted_result
+SELECT * FROM t1 WHERE a = 1 OR a = 10;
+--sorted_result
+SELECT * FROM t1 WHERE c = 3 OR d = 17;
+--sorted_result
+SELECT * FROM t1 WHERE a > 5 OR d > 5;
+
+# force some of these selects to use different indexes and/or have the columns
+# being selected also not contain column d
+--sorted_result
+SELECT a, b, c FROM t1 FORCE INDEX (kabc) WHERE a=1 OR b=11;
+--sorted_result
+SELECT d FROM t1 FORCE INDEX (kbc) WHERE b > 6 AND c > 12;
+
+UPDATE t1 SET a=a+100;
+UPDATE t1 SET a=a-100, b=99 WHERE a>100;
+--sorted_result
+SELECT * FROM t1;
+
+DELETE FROM t1 WHERE a>5;
+DELETE FROM t1 WHERE b=99 AND d>4;
+--sorted_result
+SELECT * FROM t1;
+
+TRUNCATE TABLE t1;
+DROP TABLE t1;
+
+## secondary indexes live in reverse column families
+CREATE TABLE t1 (a INT, b CHAR(8), KEY ka(a) comment 'rev:cf1', KEY kb(b)
+comment 'rev:cf1', KEY kab(a,b) comment 'rev:cf2') ENGINE=rocksdb;
+--source no_primary_key_basic_ops.inc
+DROP TABLE t1;
+
+## https://github.com/facebook/mysql-5.6/issues/209
+## Accidental single delete caused data inconsistency
+CREATE TABLE t1 (col1 int, col2 int, KEY kcol1(col1)) ENGINE=ROCKSDB;
+INSERT INTO t1 (col1, col2) values (2,2);
+ALTER TABLE t1 ADD COLUMN extra INT;
+UPDATE t1 SET col2 = 1;
+select * from t1;
+DELETE FROM t1 WHERE col1 = 2;
+
+# flush memtable to cause compaction to occur.
+# During compaction, if a SingleDelete occurs then the delete marker and the
+# key it is deleting are both removed. This will cause data inconsistency if
+# SingleDelete is called on PK, since we do multiple Put() operations to update
+# primary keys.
+set global rocksdb_force_flush_memtable_now = true;
+
+select * from t1;
+DROP TABLE t1;
+
+## https://github.com/facebook/mysql-5.6/issues/736
+create table t1 (i int auto_increment, key(i)) engine=rocksdb;
+insert into t1 values();
+insert into t1 values();
+insert into t1 values();
+
+show create table t1;
+--source include/restart_mysqld.inc
+show create table t1;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption-master.opt
new file mode 100644
index 00000000..70c12060
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption-master.opt
@@ -0,0 +1 @@
+--rocksdb_flush_log_at_trx_commit=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption.test b/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption.test
new file mode 100644
index 00000000..e084b57f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/allow_to_start_after_corruption.test
@@ -0,0 +1,77 @@
+--source include/have_rocksdb.inc
+--source include/not_valgrind.inc
+
+--let $restart_noprint=2
+
+--echo #
+--echo # Test how MyRocks behaves when RocksDB reports corrupted data.
+--echo #
+
+--source include/have_debug.inc
+
+# use custom error log to assert on error message in search_pattern_in_file.inc
+--let LOG=$MYSQLTEST_VARDIR/tmp/allow_to_start_after_corruption_debug.err
+--let SEARCH_FILE=$LOG
+
+# restart server to change error log and ignore corruptopn on startup
+--let $_mysqld_option=--log-error=$LOG --rocksdb_allow_to_start_after_corruption=1
+--source include/restart_mysqld_with_option.inc
+
+--echo #
+--echo # Test server crashes on corrupted data and restarts
+--echo #
+create table t1 (
+ pk int not null primary key,
+ col1 varchar(10)
+) engine=rocksdb;
+
+insert into t1 values (1,1),(2,2),(3,3);
+
+select * from t1 where pk=1;
+set session debug_dbug= "+d,rocksdb_return_status_corrupted";
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+select * from t1 where pk=1;
+--source include/wait_until_disconnected.inc
+--let SEARCH_PATTERN=data corruption detected
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+--echo #
+--echo # The same for scan queries
+--echo #
+
+--source include/start_mysqld_with_option.inc
+select * from t1;
+set session debug_dbug= "+d,rocksdb_return_status_corrupted";
+--exec echo "wait" > $_expect_file_name
+--error 2013
+select * from t1;
+--source include/wait_until_disconnected.inc
+--let SEARCH_PATTERN=data corruption detected
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+--echo #
+--echo # Test restart failure. The server is shutdown at this point.
+--echo #
+
+# remove flag to ignore corruption
+--let $_mysqld_option=--log-error=$LOG
+--error 0
+--exec $MYSQLD_CMD --plugin_load=$HA_ROCKSDB_SO $_mysqld_option
+--let SEARCH_PATTERN=The server will exit normally and stop restart attempts
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+--echo #
+--echo # Remove corruption file and restart cleanly
+--echo #
+
+--exec rm $MYSQLTEST_VARDIR/mysqld.$_server_id/data/#rocksdb/ROCKSDB_CORRUPTED
+--source include/start_mysqld_with_option.inc
+
+drop table t1;
+
+# Restart mysqld with default options
+--source include/restart_mysqld.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/alter_table.test b/storage/rocksdb/mysql-test/rocksdb/t/alter_table.test
new file mode 100644
index 00000000..2603311d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/alter_table.test
@@ -0,0 +1,94 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic ALTER TABLE statements.
+#
+# USAGE of table options in ALTER statements
+# is covered in tbl_standard_opts and tbl_opt*.tests.
+#
+# Index operations are covered in index* tests.
+#
+# ALTER OFFLINE is not covered as it is not supported, as of 5.5.23
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, c CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,'a'),(2,5,'z');
+
+# Column operations
+
+ALTER TABLE t1 ADD COLUMN b INT;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 ALTER COLUMN a SET DEFAULT '0';
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 ALTER a DROP DEFAULT;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 CHANGE COLUMN b b1 CHAR(8) FIRST;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 CHANGE b1 b INT AFTER c;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 CHANGE b b CHAR(8);
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 MODIFY COLUMN b INT;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 MODIFY COLUMN b CHAR(8) FIRST;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 MODIFY COLUMN b INT AFTER a;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 DROP COLUMN b;
+SHOW CREATE TABLE t1;
+
+
+# Rename table
+
+ALTER TABLE t1 RENAME TO t2;
+--error ER_NO_SUCH_TABLE
+SHOW CREATE TABLE t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+
+
+# ORDER BY
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,5),(2,2,2),(3,4,3);
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 ORDER BY b ASC, a DESC, pk DESC;
+SHOW CREATE TABLE t1;
+SELECT * FROM t1;
+DROP TABLE t1;
+
+
+# Character set, collate
+
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b CHAR(8), c CHAR(8)) ENGINE=rocksdb CHARACTER SET latin1 COLLATE latin1_general_cs;
+INSERT INTO t1 VALUES (1,5,'z','t');
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 CONVERT TO CHARACTER SET utf8;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 DEFAULT CHARACTER SET = latin1 COLLATE latin1_general_ci;
+SHOW CREATE TABLE t1;
+
+
+# A 'null' ALTER operation
+
+ALTER TABLE t1 FORCE;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/analyze_table.test b/storage/rocksdb/mysql-test/rocksdb/t/analyze_table.test
new file mode 100644
index 00000000..b24398b1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/analyze_table.test
@@ -0,0 +1,57 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# ANALYZE TABLE statements
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT(11), b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1,'a'),(2,2,'b');
+CREATE TABLE t2 (pk INT PRIMARY KEY, a INT(11), b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (3,3,'c');
+ANALYZE TABLE t1;
+INSERT INTO t2 VALUES (1,4,'d');
+ANALYZE NO_WRITE_TO_BINLOG TABLE t2;
+INSERT INTO t1 VALUES (4,5,'e');
+INSERT INTO t2 VALUES (2,6,'f');
+ANALYZE LOCAL TABLE t1, t2;
+
+DROP TABLE t1, t2;
+
+ --let $create_definition = a $int_indexed_col, $default_index(a)
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT(11), KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1,1),(2,2),(3,4),(4,7);
+ANALYZE TABLE t1;
+INSERT INTO t1 VALUES (5,8),(6,10),(7,11),(8,12);
+ANALYZE TABLE t1;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-12465: Server crashes in my_scan_weight_utf8_bin upon
+--echo # collecting stats for RocksDB table
+--echo #
+
+CREATE TABLE t1 (
+ pk INT,
+ f1 CHAR(255),
+ f2 TEXT,
+ f3 VARCHAR(255),
+ f4 TEXT,
+ PRIMARY KEY (pk),
+ KEY (f4(255))
+) ENGINE=RocksDB
+ CHARSET utf8
+ COLLATE utf8_bin
+ PARTITION BY KEY (pk) PARTITIONS 2;
+INSERT INTO t1 VALUES
+(1,'foo','bar','foo','bar'), (2,'bar','foo','bar','foo');
+
+ANALYZE TABLE t1 PERSISTENT FOR ALL;
+
+drop table t1;
+
+--echo # End of 10.2 tests
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/apply_changes_iter.test b/storage/rocksdb/mysql-test/rocksdb/t/apply_changes_iter.test
new file mode 100644
index 00000000..4f759a8e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/apply_changes_iter.test
@@ -0,0 +1,44 @@
+--source include/have_rocksdb.inc
+
+# Tests the Apply_changes_iter class for walking forward and backwards
+# with data in both the transaction class and in the rocksdb storage layer
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+CREATE TABLE t1 (
+ pk INT NOT NULL PRIMARY KEY,
+ key1 INT NOT NULL,
+ KEY (key1)
+) ENGINE=ROCKSDB;
+
+INSERT INTO t1 VALUES (12,12);
+INSERT INTO t1 VALUES (6,6);
+BEGIN;
+INSERT INTO t1 VALUES (8,8), (10,10);
+SELECT * FROM t1 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 DESC;
+SELECT * FROM t1 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 ASC;
+SELECT * FROM t1 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 DESC;
+SELECT * FROM t1 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 ASC;
+ROLLBACK;
+
+CREATE TABLE t2 (
+ pk INT NOT NULL PRIMARY KEY,
+ key1 INT NOT NULL,
+ KEY (key1) COMMENT 'rev:cf'
+) ENGINE=ROCKSDB;
+
+INSERT INTO t2 VALUES (12,12);
+INSERT INTO t2 VALUES (6,6);
+BEGIN;
+INSERT INTO t2 VALUES (8,8), (10,10);
+SELECT * FROM t2 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 DESC;
+SELECT * FROM t2 WHERE key1 BETWEEN 4 and 11 ORDER BY KEY1 ASC;
+SELECT * FROM t2 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 DESC;
+SELECT * FROM t2 IGNORE INDEX(key1) WHERE key1 BETWEEN 4 and 11 ORDER BY key1 ASC;
+ROLLBACK;
+
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.cnf b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.cnf
new file mode 100644
index 00000000..a43c4617
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.cnf
@@ -0,0 +1,8 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+[mysqld.2]
+binlog_format=row
+slave_parallel_workers=1
+#rpl_skip_tx_api=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.test
new file mode 100644
index 00000000..e61ba720
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe.test
@@ -0,0 +1,9 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+--source include/master-slave.inc
+
+create table t (i int primary key auto_increment) engine=rocksdb;
+
+--source include/autoinc_crash_safe.inc
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.cnf b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.cnf
new file mode 100644
index 00000000..0c0b6140
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.cnf
@@ -0,0 +1,8 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+[mysqld.2]
+binlog_format=row
+#slave_parallel_workers=1
+#rpl_skip_tx_api=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.test
new file mode 100644
index 00000000..56cf93db
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_crash_safe_partition.test
@@ -0,0 +1,10 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+--source include/master-slave.inc
+--source include/have_partition.inc
+
+create table t (i int primary key auto_increment) engine=rocksdb partition by key (i) partitions 3;
+
+--source include/autoinc_crash_safe.inc
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug-master.opt
new file mode 100644
index 00000000..06917181
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug-master.opt
@@ -0,0 +1 @@
+--binlog-format=row --rocksdb-flush-log-at-trx-commit=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug.test
new file mode 100644
index 00000000..99e28f3b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_debug.test
@@ -0,0 +1,121 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+--source include/have_log_bin.inc
+--source include/not_valgrind.inc
+
+--let $restart_noprint=2
+
+--echo #
+--echo # Testing upgrading from server without merges for auto_increment
+--echo # to new server with such support.
+--echo #
+
+set debug_dbug='+d,myrocks_autoinc_upgrade';
+create table t (i int primary key auto_increment);
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+
+delete from t where i > 1;
+select * from t;
+
+select table_name, index_name, auto_increment
+ from information_schema.rocksdb_ddl where table_name = 't';
+
+set debug_dbug='-d,myrocks_autoinc_upgrade';
+
+--source include/restart_mysqld.inc
+
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+
+select table_name, index_name, auto_increment
+ from information_schema.rocksdb_ddl where table_name = 't';
+
+delete from t where i > 1;
+
+--source include/restart_mysqld.inc
+
+insert into t values ();
+insert into t values ();
+insert into t values ();
+select * from t;
+
+drop table t;
+
+--echo #
+--echo # Testing crash safety of transactions.
+--echo #
+create table t (i int primary key auto_increment);
+insert into t values ();
+insert into t values ();
+insert into t values ();
+
+--echo # Before anything
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_before";
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+commit;
+--source include/wait_until_disconnected.inc
+--enable_reconnect
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+
+--echo # After engine prepare
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after_prepare";
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+commit;
+--source include/wait_until_disconnected.inc
+--enable_reconnect
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+
+--echo # After binlog
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after_log";
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+commit;
+--source include/wait_until_disconnected.inc
+--enable_reconnect
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+
+--echo # After everything
+begin;
+insert into t values ();
+insert into t values ();
+set debug_dbug="+d,crash_commit_after";
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+commit;
+--source include/wait_until_disconnected.inc
+--enable_reconnect
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+select max(i) into @row_max from t;
+select table_schema, table_name, auto_increment > @row_max from information_schema.tables where table_name = 't';
+
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_secondary.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_secondary.test
new file mode 100644
index 00000000..68ad21be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_secondary.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (pk INT PRIMARY KEY, a INT AUTO_INCREMENT, KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (pk) VALUES (3), (2), (1);
+SELECT * FROM t1;
+
+--source include/restart_mysqld.inc
+
+INSERT INTO t1 (pk) VALUES (4);
+SELECT * FROM t1;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars.test
new file mode 100644
index 00000000..08532448
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars.test
@@ -0,0 +1,171 @@
+--source include/have_rocksdb.inc
+
+#
+# auto-increment-offset and auto-increment-increment
+#
+
+############################################
+# TODO:
+# This test currently produces wrong result
+# on the line 36 of the result file and further
+# due to bug MySQL:47118.
+# When/if the bug is fixed,
+# the result will need to be updated
+############################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--echo #---------------------------
+--echo # auto_increment_offset
+--echo #---------------------------
+SET auto_increment_offset = 200;
+
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+
+# If auto_increment_offset is greater than auto_increment_increment,
+# the offset is ignored
+
+INSERT INTO t1 (a,b) VALUES (NULL,'a'),(NULL,'b'),(NULL,'c');
+SELECT LAST_INSERT_ID();
+SELECT a,b FROM t1 ORDER BY a;
+
+--echo #---------------------------
+--echo # auto_increment_increment
+--echo #---------------------------
+
+SET auto_increment_increment = 300;
+# offset should not be ignored anymore
+
+INSERT INTO t1 (a,b) VALUES (NULL,'d'),(NULL,'e'),(NULL,'f');
+SELECT LAST_INSERT_ID();
+SELECT a,b FROM t1 ORDER BY a;
+
+SET auto_increment_increment = 50;
+INSERT INTO t1 (a,b) VALUES (NULL,'g'),(NULL,'h'),(NULL,'i');
+SELECT LAST_INSERT_ID();
+SELECT a,b FROM t1 ORDER BY a;
+DROP TABLE t1;
+
+
+--echo #---------------------------
+--echo # offset is greater than the max value
+--echo #---------------------------
+
+SET auto_increment_increment = 500;
+SET auto_increment_offset = 300;
+
+CREATE TABLE t1 (a TINYINT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+--echo # In MariaDB, this is an error:
+--error HA_ERR_AUTOINC_ERANGE
+INSERT INTO t1 (a) VALUES (NULL);
+SELECT LAST_INSERT_ID();
+SELECT a FROM t1 ORDER BY a;
+DROP TABLE t1;
+
+--echo #---------------------------
+--echo # test large autoincrement values
+--echo #---------------------------
+
+SET auto_increment_increment = 1;
+SET auto_increment_offset = 1;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+INSERT INTO t1 VALUES (NULL, 'b');
+SHOW CREATE TABLE t1;
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL, 'c');
+SELECT * FROM t1;
+DROP TABLE t1;
+
+SET auto_increment_increment = 300;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL, 'b');
+SHOW CREATE TABLE t1;
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL, 'c');
+SELECT * FROM t1;
+DROP TABLE t1;
+
+SET auto_increment_offset = 200;
+CREATE TABLE t1 (a BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (18446744073709551613, 'a');
+SHOW CREATE TABLE t1;
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL, 'b');
+SHOW CREATE TABLE t1;
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL, 'c');
+SELECT * FROM t1;
+DROP TABLE t1;
+
+--echo #----------------------------------
+--echo # Issue #792 Crash in autoincrement
+--echo #----------------------------------
+
+CREATE TABLE t1(C1 DOUBLE AUTO_INCREMENT KEY,C2 CHAR) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(2177,0);
+DROP TABLE t1;
+
+CREATE TABLE t0(c0 BLOB) ENGINE=ROCKSDB;
+INSERT INTO t0 VALUES(0);
+ALTER TABLE t0 AUTO_INCREMENT=0;
+DROP TABLE t0;
+
+--echo #---------------------------------------------------------------
+--echo # MDEV-16703 Assertion failed in load_auto_incr_value_from_index
+--echo #---------------------------------------------------------------
+
+CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (1);
+UPDATE t1 SET pk = 3;
+ALTER TABLE t1 AUTO_INCREMENT 2;
+DROP TABLE t1;
+
+--echo #----------------------------------
+--echo # Issue #792 Crash in autoincrement
+--echo #----------------------------------
+
+CREATE TABLE t1(C1 DOUBLE AUTO_INCREMENT KEY,C2 CHAR) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(2177,0);
+DROP TABLE t1;
+
+CREATE TABLE t0(c0 BLOB) ENGINE=ROCKSDB;
+INSERT INTO t0 VALUES(0);
+ALTER TABLE t0 AUTO_INCREMENT=0;
+DROP TABLE t0;
+
+--echo #----------------------------------
+--echo # Issue #869 Crash in autoincrement
+--echo #----------------------------------
+
+CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB;
+INSERT INTO t1 (a) VALUES (1);
+UPDATE t1 SET pk = 3;
+ALTER TABLE t1 AUTO_INCREMENT 2;
+DROP TABLE t1;
+
+--echo #----------------------------------
+--echo # Issue #902 Debug assert in autoincrement with small field type
+--echo #----------------------------------
+
+SET auto_increment_increment=100, auto_increment_offset=10;
+CREATE TABLE t1(i INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB AUTO_INCREMENT=18446744073709551615;
+# ha_rocksdb::get_auto_increment would assert here
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+ALTER TABLE t1 AUTO_INCREMENT=1;
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+ALTER TABLE t1 AUTO_INCREMENT=18446744073709551615;
+# ha_rocksdb::get_auto_increment would assert here
+--error ER_AUTOINC_READ_FAILED
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread.test
new file mode 100644
index 00000000..5a40d28e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread.test
@@ -0,0 +1,65 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+--echo #---------------------------
+--echo # two threads inserting simultaneously with increment > 1
+--echo # Issue #390
+--echo #---------------------------
+
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+# Set up connections
+connect (con1, localhost, root,,);
+SET auto_increment_increment = 2;
+SET auto_increment_offset = 1;
+# Insert one row to set up the conditions that caused the original failure
+INSERT INTO t1 VALUES(NULL);
+
+connect (con2, localhost, root,,);
+SET auto_increment_increment = 2;
+SET auto_increment_offset = 1;
+
+connect (con3, localhost, root,,);
+
+# Start each thread on an insert that will block waiting for a signal
+connection con1;
+SET debug_sync='rocksdb.autoinc_vars2 SIGNAL go2';
+SET debug_sync='rocksdb.autoinc_vars SIGNAL parked1 WAIT_FOR go1';
+send INSERT INTO t1 VALUES(NULL);
+
+connection default;
+SET debug_sync='now WAIT_FOR parked1';
+
+connection con2;
+SET debug_sync='rocksdb.autoinc_vars SIGNAL parked2 WAIT_FOR go2';
+send INSERT INTO t1 VALUES(NULL);
+
+# Wait for both threads to be at debug_sync point
+connection default;
+SET debug_sync='now WAIT_FOR parked2';
+
+
+# Signal both threads to continue
+# (In MariaDB, we signal one of them which continues and signals the other)
+send SET debug_sync='now SIGNAL go1';
+connection con3;
+# MariaDB: do nothing here
+
+connection default;
+reap;
+
+connection con1;
+reap;
+
+connection con2;
+reap;
+
+connection default;
+SET debug_sync='RESET';
+
+disconnect con1;
+disconnect con2;
+
+SELECT * FROM t1;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread_2.test b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread_2.test
new file mode 100644
index 00000000..b64af164
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/autoinc_vars_thread_2.test
@@ -0,0 +1,142 @@
+--source include/have_rocksdb.inc
+
+--echo #---------------------------
+--echo # ten threads inserting simultaneously with increment > 1
+--echo # Issue #390
+--echo #---------------------------
+
+# Run 10 simulatenous threads each inserting 10,000 rows
+let $num_threads = 10;
+let $num_rows_per_thread = 100000;
+
+# Create the table with an AUTO_INCREMENT primary key and a separate colum
+# to store which thread created the row
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, thr INT) ENGINE=rocksdb;
+
+# For each thread...
+# 1) set up a connection
+# 2) create a file that can be used for LOAD DATA INFILE ...
+let $i = `SELECT $num_threads`;
+while ($i > 0)
+{
+ dec $i;
+
+ # Set up connection
+ connect (con$i, localhost, root,,);
+
+ # Set up the auto_increment_* variables for each thread
+ eval SET auto_increment_increment = 100;
+ eval SET auto_increment_offset = $i + 1;
+ let $file = `SELECT CONCAT(@@datadir, "test_insert_", $i, ".txt")`;
+
+ # Pass variables into perl
+ let ROCKSDB_INFILE = $file;
+ let ROCKSDB_THREAD = `SELECT $i`;
+ let ROCKSDB_ROWS_PER_THREAD = `SELECT $num_rows_per_thread`;
+
+ # Create a file to load
+ perl;
+ my $fn = $ENV{'ROCKSDB_INFILE'};
+ my $thr = $ENV{'ROCKSDB_THREAD'};
+ my $num = $ENV{'ROCKSDB_ROWS_PER_THREAD'};
+ open(my $fh, '>>', $fn) || die "perl open($fn): $!";
+ binmode $fh;
+ for (my $ii = 0; $ii < $num; $ii++)
+ {
+ print $fh "\\N\t$thr\n"
+ }
+ close($fh);
+ EOF
+}
+
+# For each connection start the LOAD DATA INFILE in the background
+connection default;
+let $i = `SELECT $num_threads`;
+while ($i > 0)
+{
+ dec $i;
+
+ connection con$i;
+ let $file = `SELECT CONCAT(@@datadir, "test_insert_", $i, ".txt")`;
+ --disable_query_log
+ --echo LOAD DATA INFILE <input_file> INTO TABLE t1;
+ send_eval LOAD DATA INFILE '$file' INTO TABLE t1;
+ --enable_query_log
+}
+
+# Reap each connection's background result
+connection default;
+let $i = `SELECT $num_threads`;
+while ($i > 0)
+{
+ dec $i;
+
+ connection con$i;
+ reap;
+}
+
+# Make sure we have the required number of rows
+connection default;
+SELECT COUNT(*) FROM t1;
+SELECT thr, COUNT(pk) FROM t1 GROUP BY thr;
+
+# Cleanup the connection and file used for LOAD DATA INFILE
+let $i = `SELECT $num_threads`;
+while ($i > 0)
+{
+ dec $i;
+
+ disconnect con$i;
+ let $file = `SELECT CONCAT(@@datadir, "test_insert_", "$i", ".txt")`;
+ remove_file $file;
+}
+
+# Validate each row. For each row, the created 'thr' column shows which
+# thread created the row. The pk that was automatically generated should
+# therefore match a certain pattern. For thread 0, the pk should be in
+# the sequence [1, 101, 201, 301, ...]; for thread 1, it should be in the
+# sequence [2, 102, 202, 302, ...], etc. The pk for each row should be
+# smallest value in the sequence for thread 'thr' that is greater than
+# the pk in the previous row.
+let $file = `SELECT CONCAT(@@datadir, "test_export.txt")`;
+--disable_query_log
+--echo SELECT * FROM t1 ORDER BY pk INTO OUTFILE <output_file>;
+eval SELECT * FROM t1 ORDER BY pk INTO OUTFILE "$file";
+--enable_query_log
+
+let ROCKSDB_OUTFILE = $file;
+
+perl;
+my $fn = $ENV{'ROCKSDB_OUTFILE'};
+my $last_pk = 0;
+open(my $fh, '<', $fn) || die "perl open($fn): $!";
+while (<$fh>)
+{
+ if ($_ =~ m/^(.*)\t(.*)$/)
+ {
+ my $pk = $1;
+ my $thr = $2;
+
+ my $expected_pk = int($last_pk / 100) * 100 + ($thr + 1);
+ $expected_pk += 100 if $expected_pk <= $last_pk;
+
+ if ($expected_pk != $pk)
+ {
+ die "Incorrect next pk ($pk); expected $expected_pk (previous: $last_pk)"
+ }
+
+ $last_pk = $pk;
+ }
+ else
+ {
+ die "output file has incorrect format: $_";
+ }
+}
+print stdout "All pk values matched their expected values\n";
+EOF
+
+remove_file $file;
+
+# Drop the table to finally clean up
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/binlog_rotate_crash.test b/storage/rocksdb/mysql-test/rocksdb/t/binlog_rotate_crash.test
new file mode 100644
index 00000000..dd42001b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/binlog_rotate_crash.test
@@ -0,0 +1,31 @@
+--source include/have_rocksdb.inc
+--source include/have_log_bin.inc
+--source include/have_debug_sync.inc
+
+--echo #
+--echo # MDEV-25305: MyRocks: Killing server during RESET MASTER can lose last transactions
+--echo #
+
+set global rocksdb_flush_log_at_trx_commit=1;
+
+create table t1 (a int, b int, key(a)) engine=rocksdb;
+insert into t1 values (1,1),(2,2);
+select * from t1;
+flush tables;
+
+--write_file $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+restart
+EOF
+
+set @@debug_dbug="+d,crash_after_reset_master";
+
+--disable_reconnect
+--error 0,2013
+RESET MASTER;
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--echo # Must show the inserted rows:
+select * from t1;
+
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.cnf b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.cnf
new file mode 100644
index 00000000..a76f1244
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.cnf
@@ -0,0 +1,11 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+sync_binlog=0
+binlog_format=row
+slave-exec-mode=strict
+
+[mysqld.2]
+sync_binlog=0
+binlog_format=row
+slave-exec-mode=strict
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.test
new file mode 100644
index 00000000..9b5c4571
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rc.test
@@ -0,0 +1,3 @@
+let $trx_isolation = READ COMMITTED;
+--source blind_delete_without_tx_api.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.cnf b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.cnf
new file mode 100644
index 00000000..a76f1244
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.cnf
@@ -0,0 +1,11 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+sync_binlog=0
+binlog_format=row
+slave-exec-mode=strict
+
+[mysqld.2]
+sync_binlog=0
+binlog_format=row
+slave-exec-mode=strict
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.test
new file mode 100644
index 00000000..4369f6ba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_rr.test
@@ -0,0 +1,3 @@
+let $trx_isolation = REPEATABLE READ;
+--source blind_delete_without_tx_api.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_without_tx_api.inc b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_without_tx_api.inc
new file mode 100644
index 00000000..4f03695b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/blind_delete_without_tx_api.inc
@@ -0,0 +1,132 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+
+source include/master-slave.inc;
+
+connection master;
+
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+set @save_rocksdb_blind_delete_primary_key=@@session.rocksdb_blind_delete_primary_key;
+set @save_rocksdb_master_skip_tx_api=@@session.rocksdb_master_skip_tx_api;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+create table t1 (id int primary key, value int, value2 varchar(200)) engine=rocksdb;
+create table t2 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+
+--disable_query_log
+let $t = 1;
+while ($t <= 2) {
+ let $i = 1;
+ while ($i <= 10000) {
+ let $insert = INSERT INTO t$t VALUES($i, $i, REPEAT('x', 150));
+ inc $i;
+ eval $insert;
+ }
+ inc $t;
+}
+--enable_query_log
+
+SET session rocksdb_blind_delete_primary_key=1;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+# Deleting 1000 rows from t1
+--disable_query_log
+let $i = 1;
+while ($i <= 1000) {
+ let $insert = DELETE FROM t1 WHERE id=$i;
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+SELECT count(*) FROM t1;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+SELECT count(*) FROM t1;
+connection master;
+
+# Deleting 1000 rows from t2 (blind delete disabled because of secondary key)
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+--disable_query_log
+let $i = 1;
+while ($i <= 1000) {
+ let $insert = DELETE FROM t2 WHERE id=$i;
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+SELECT count(*) FROM t2;
+
+SET session rocksdb_master_skip_tx_api=1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+--disable_query_log
+let $t = 1;
+while ($t <= 2) {
+ let $i = 1001;
+ while ($i <= 2000) {
+ let $insert = DELETE FROM t$t WHERE id=$i;
+ inc $i;
+ eval $insert;
+ }
+ inc $t;
+}
+--enable_query_log
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+SELECT count(*) FROM t1;
+SELECT count(*) FROM t2;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+SELECT count(*) FROM t1;
+SELECT count(*) FROM t2;
+connection master;
+
+
+# Range Deletes (blind delete disabled)
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+DELETE FROM t1 WHERE id BETWEEN 3001 AND 4000;
+DELETE FROM t2 WHERE id BETWEEN 3001 AND 4000;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_deleted_blind';
+SELECT count(*) FROM t1;
+SELECT count(*) FROM t2;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+SELECT count(*) FROM t1;
+SELECT count(*) FROM t2;
+connection master;
+
+
+# Deleting same keys (slaves stop)
+DELETE FROM t1 WHERE id = 10;
+SELECT count(*) FROM t1;
+connection slave;
+call mtr.add_suppression("Slave SQL.*Could not execute Delete_rows event on table test.t1.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 't1'.*");
+# wait until we have the expected error
+--let $slave_sql_errno= convert_error(ER_KEY_NOT_FOUND)
+--source include/wait_for_slave_sql_error.inc
+
+connection slave;
+set @save_rocksdb_read_free_rpl=@@global.rocksdb_read_free_rpl;
+set global rocksdb_read_free_rpl=PK_SK;
+START SLAVE;
+connection master;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+SELECT count(*) FROM t1;
+connection master;
+
+
+# cleanup
+connection slave;
+set global rocksdb_read_free_rpl=@save_rocksdb_read_free_rpl;
+connection master;
+SET session rocksdb_blind_delete_primary_key=@save_rocksdb_blind_delete_primary_key;
+SET session rocksdb_master_skip_tx_api=@save_rocksdb_master_skip_tx_api;
+
+DROP TABLE t1, t2;
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter-master.opt
new file mode 100644
index 00000000..bf9d0624
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_default_cf_options=write_buffer_size=256k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:20
+--rocksdb_override_cf_options=cf_short_prefix={prefix_extractor=capped:4};rev:cf_short_prefix={prefix_extractor=capped:4};cf_long_prefix={prefix_extractor=capped:240};rev:cf_long_prefix={prefix_extractor=capped:240}
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.inc b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.inc
new file mode 100644
index 00000000..1fb9ef8a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.inc
@@ -0,0 +1,78 @@
+--source include/have_rocksdb.inc
+
+DELIMITER //;
+CREATE PROCEDURE bloom_start()
+BEGIN
+ select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+ select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+END//
+CREATE PROCEDURE bloom_end()
+BEGIN
+select case when variable_value-@c > 0 then 'true' else 'false' end as checked from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+END//
+DELIMITER ;//
+
+#BF is sometimes invoked and useful
+--let $CF=
+--source bloomfilter_table_def.inc
+--source bloomfilter_load_select.inc
+
+#BF is always invoked but not useful at all
+--let $CF=COMMENT 'cf_short_prefix'
+--source bloomfilter_table_def.inc
+--source bloomfilter_load_select.inc
+
+--let $CF=COMMENT 'rev:cf_short_prefix'
+--source bloomfilter_table_def.inc
+--source bloomfilter_load_select.inc
+
+#BF is most of the time invoked and useful
+--let $CF=COMMENT 'cf_long_prefix'
+--source bloomfilter_table_def.inc
+--source bloomfilter_load_select.inc
+
+--let $CF=COMMENT 'rev:cf_long_prefix'
+--source bloomfilter_table_def.inc
+--source bloomfilter_load_select.inc
+
+
+# BUG: Prev() with prefix lookup should not use prefix bloom filter
+create table r1 (id1 bigint, id2 bigint, id3 bigint, v1 int, v2 text, primary key (id1, id2, id3)) engine=rocksdb DEFAULT CHARSET=latin1 collate latin1_bin;
+--disable_query_log
+let $max = 100;
+let $i = 1;
+
+create table t10(a int primary key);
+insert into t10 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t11(a int primary key);
+insert into t11 select A.a + B.a* 10 + C.a * 100 from t10 A, t10 B, t10 C;
+
+set @a=0;
+set rocksdb_bulk_load=1;
+while ($i <= $max) {
+ inc $i;
+ eval insert into r1 select @a:=@a+1, @a, @a, @a, @a from t11;
+}
+set rocksdb_bulk_load=0;
+
+drop table t10, t11;
+
+--enable_query_log
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 asc;
+call bloom_end();
+call bloom_start();
+select * from r1 where id1=1 and id2 in (1) order by id3 desc;
+call bloom_end();
+
+# cleanup
+DROP PROCEDURE bloom_start;
+DROP PROCEDURE bloom_end;
+truncate table t1;
+optimize table t1;
+truncate table t2;
+optimize table t2;
+drop table if exists t1;
+drop table if exists t2;
+drop table if exists r1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.test
new file mode 100644
index 00000000..efcf9ee1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter.test
@@ -0,0 +1 @@
+--source bloomfilter.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2-master.opt
new file mode 100644
index 00000000..f3824106
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=64k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:24
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2.test
new file mode 100644
index 00000000..c4f1570e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter2.test
@@ -0,0 +1,103 @@
+--source include/have_rocksdb.inc
+
+## Test 0: Eq cond len includs VARCHAR, and real cond len < prefix bloom len < VARCHAR definition len
+CREATE TABLE t0 (id1 VARCHAR(30), id2 INT, value INT, PRIMARY KEY (id1, id2)) ENGINE=rocksdb collate latin1_bin;
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $insert = INSERT INTO t0 VALUES('X', $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+# BF not used
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t0 WHERE id1='X' AND id2>=1;
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+DROP TABLE t0;
+
+
+## Test 1: Eq cond len is shorter than prefix bloom len
+CREATE TABLE t1 (id1 BIGINT, id2 INT, id3 BIGINT, value INT, PRIMARY KEY (id1, id2, id3)) ENGINE=rocksdb;
+
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $insert = INSERT INTO t1 VALUES(1, 1, $i, $i);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+# BF not used (4+8+4=16)
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3>=2;
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+# BF not used (4+8=12)
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2>=1 AND id3>=2;
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+DROP TABLE t1;
+
+
+## Test 2: Long IN and short IN (varchar) -- can_use_bloom_filter changes within the same query
+CREATE TABLE t2 (id1 INT, id2 VARCHAR(100), id3 BIGINT, value INT, PRIMARY KEY (id1, id2, id3)) ENGINE=rocksdb collate latin1_bin;
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $insert = INSERT INTO t2 VALUES($i, $i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+# BF used for large cond, not used for short cond
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=100 and id2 IN ('00000000000000000000', '100');
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=200 and id2 IN ('00000000000000000000', '200');
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+# BF not used because cond length is too small in all cases
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+select count(*) from t2 WHERE id1=200 and id2 IN ('3', '200');
+select case when variable_value-@u = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+DROP TABLE t2;
+
+
+## Test 3: Eq cond len is longer than prefix bloom len
+CREATE TABLE t3 (id1 BIGINT, id2 BIGINT, id3 BIGINT, id4 BIGINT, PRIMARY KEY (id1, id2, id3, id4)) ENGINE=rocksdb collate latin1_bin;
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ if ($i != 5000) {
+ let $insert = INSERT INTO t3 VALUES(1, $i, $i, $i);
+ eval $insert;
+ }
+ inc $i;
+}
+--enable_query_log
+
+# Full BF works with Get(), Block based does not.
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=5000 AND id3=1 AND id4=1;
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_useful';
+
+# BF used (4+8+8+8)
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=1 AND id3=1;
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+SELECT COUNT(*) FROM t3 WHERE id1=1 AND id2=1 AND id3=1 AND id4 <= 500;
+select case when variable_value-@u > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_useful';
+
+DROP TABLE t3;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3-master.opt
new file mode 100644
index 00000000..a21608c7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3-master.opt
@@ -0,0 +1,4 @@
+--rocksdb_default_cf_options=write_buffer_size=64k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:20
+--rocksdb_debug_optimizer_n_rows=1000
+--rocksdb_table_stats_sampling_pct=100
+--rocksdb_info_log_level=debug_level
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3.test
new file mode 100644
index 00000000..dc2a0da5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter3.test
@@ -0,0 +1,136 @@
+--source include/have_rocksdb.inc
+
+--source include/restart_mysqld.inc
+CREATE TABLE `linktable` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) NOT NULL DEFAULT '',
+ `time` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `version` int(11) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (link_type, `id1`,`id2`) COMMENT 'cf_link_pk',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'rev:cf_link_id1_type',
+ KEY `id1_type2` (`id1`,`link_type`,`time`,`version`,`data`,`visibility`) COMMENT 'rev:cf_link_id1_type2',
+ KEY `id1_type3` (`id1`,`visibility`,`time`,`version`,`data`,`link_type`) COMMENT 'rev:cf_link_id1_type3'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+
+--disable_query_log
+call mtr.add_suppression("LibRocksDB");
+let $i = 1;
+while ($i <= 10000) {
+ let $insert = INSERT INTO linktable VALUES($i, $i, $i, $i, 1, 1, $i, $i, $i);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+## HA_READ_PREFIX_LAST_OR_PREV
+# BF len 21
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 and visibility = 1 order by time desc;
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+# BF len 20
+
+--echo # MariaDB: we don't have optimizer_force_index_for_range, but we can use EITS
+--echo # to get the query plan we want.
+set @tmp_use_stat_tables= @@use_stat_tables;
+set use_stat_tables='preferably';
+analyze table linktable persistent for all;
+flush tables;
+explain select * from linktable;
+--echo # This must use range(id1_type2), key_len=24
+explain
+select id1, id2, link_type, visibility, data, time, version from linktable
+FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 order by time desc;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+# MariaDB: no support for optimizer_force_index_for_range:
+#set @tmp_force_index_for_range=@@optimizer_force_index_for_range;
+#set optimizer_force_index_for_range=on;
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 and time <= 9223372036854775807 order by time desc;
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+#set global optimizer_force_index_for_range=@tmp_force_index_for_range;
+
+# BF len 13
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type3`) where id1 = 100 and time >= 0 and time <= 9223372036854775807 and visibility = 1 order by time desc;
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+## HA_READ_PREFIX_LAST_OR_PREV (no end range)
+# BF len 20
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and visibility = 1 and time >= 0 order by time desc;
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+# BF len 19
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 and time >= 0 order by time desc;
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+--echo ## HA_READ_PREFIX_LAST
+--echo # BF len 20
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type`) where id1 = 100 and link_type = 1 and visibility = 1 order by time desc;
+select case when variable_value-@c > 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+--echo # BF len 19
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type2`) where id1 = 100 and link_type = 1 order by time desc;
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+--echo # BF len 12
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select id1, id2, link_type, visibility, data, time, version from linktable FORCE INDEX(`id1_type3`) where id1 = 100 and visibility = 1 order by time desc;
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+
+DROP TABLE linktable;
+--source include/restart_mysqld.inc
+
+--echo #
+--echo # bloom filter prefix is 20 byte
+--echo # Create a key which is longer than that, so that we see that
+--echo # eq_cond_len= slice.size() - 1;
+--echo # doesnt work.
+--echo #
+--echo # indexnr 4
+--echo # kp0 + 4 = 8
+--echo # kp1 + 8 = 16
+--echo # kp2 + 8 = 24 24>20 byte length prefix
+--echo # kp3 + 8 = 28
+
+create table t1 (
+ pk int primary key,
+ kp0 int not null,
+ kp1 bigint not null,
+ kp2 bigint not null,
+ kp3 bigint not null,
+ key kp12(kp0, kp1, kp2, kp3) comment 'rev:x1'
+) engine=rocksdb;
+
+insert into t1 values (1, 1,1, 1,1);
+insert into t1 values (10,1,1,0x12FFFFFFFFFF,1);
+insert into t1 values (11,1,1,0x12FFFFFFFFFF,1);
+insert into t1 values (20,2,2,0x12FFFFFFFFFF,1);
+insert into t1 values (21,2,2,0x12FFFFFFFFFF,1);
+
+--source include/restart_mysqld.inc
+
+--replace_column 9 #
+explain
+select * from t1 where kp0=1 and kp1=1 and kp2=0x12FFFFFFFFFF order by kp3 desc;
+show status like '%rocksdb_bloom_filter_prefix%';
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+select * from t1 where kp0=1 and kp1=1 and kp2=0x12FFFFFFFFFF order by kp3 desc;
+show status like '%rocksdb_bloom_filter_prefix%';
+--echo # The following MUST show TRUE:
+select case when variable_value-@c = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+drop table t1;
+# Key length is 4 + 8 + 8 = 20
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4-master.opt
new file mode 100644
index 00000000..0a325757
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=16k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:12
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4.test
new file mode 100644
index 00000000..76ec6ca1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter4.test
@@ -0,0 +1,52 @@
+--source include/have_rocksdb.inc
+
+# Fixing issue#230 -- Prefix bloom filter + reverse column family misses some rows
+# This test inserts 20,000 rows into t1, then selecting one by one from stored procedure.
+# If the select does not return any row, it is wrong.
+
+CREATE TABLE t1 (
+ `id1` int unsigned NOT NULL DEFAULT '0',
+ `id2` int unsigned NOT NULL DEFAULT '0',
+ `link_type` int unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint NOT NULL DEFAULT '0',
+ `data` varchar(255) NOT NULL DEFAULT '',
+ `time` int unsigned NOT NULL DEFAULT '0',
+ `version` int unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (id1, link_type, visibility, id2) COMMENT 'rev:cf_link_pk'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+
+DELIMITER //;
+CREATE PROCEDURE select_test()
+BEGIN
+ DECLARE id1_cond INT;
+ SET id1_cond = 1;
+ WHILE id1_cond <= 20000 DO
+ SELECT count(*) AS cnt FROM (SELECT id1 FROM t1 FORCE INDEX (PRIMARY) WHERE id1 = id1_cond AND link_type = 1 AND visibility = 1 ORDER BY id2 DESC) AS t INTO @cnt;
+ IF @cnt < 1 THEN
+ SELECT id1_cond, @cnt;
+ END IF;
+ SET id1_cond = id1_cond + 1;
+ END WHILE;
+END//
+DELIMITER ;//
+
+--disable_query_log
+let $i = 1;
+while ($i <= 20000) {
+ let $insert = INSERT INTO t1 VALUES($i, $i, 1, 1, $i, $i, $i);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+--echo "Skipping bloom filter"
+SET session rocksdb_skip_bloom_filter_on_read=1;
+CALL select_test();
+
+--echo "Using bloom filter"
+SET session rocksdb_skip_bloom_filter_on_read=0;
+CALL select_test();
+
+DROP PROCEDURE select_test;
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5-master.opt
new file mode 100644
index 00000000..4576d20f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_default_cf_options=write_buffer_size=256k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;}
+--rocksdb_override_cf_options=rev:bf5_1={prefix_extractor=capped:4};bf5_1={prefix_extractor=capped:4}
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5.test
new file mode 100644
index 00000000..11890dcf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter5.test
@@ -0,0 +1,86 @@
+
+--echo #
+--echo # Issue #809: Wrong query result with bloom filters
+--echo #
+
+create table t1 (
+ id1 bigint not null,
+ id2 bigint not null,
+ id3 varchar(100) not null,
+ id4 int not null,
+ id5 int not null,
+ value bigint,
+ value2 varchar(100),
+ primary key (id1, id2, id3, id4) COMMENT 'rev:bf5_1'
+) engine=ROCKSDB;
+
+
+create table t2(a int);
+insert into t2 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t3(seq int);
+insert into t3
+select
+ 1+ A.a + B.a* 10 + C.a * 100 + D.a * 1000
+from t2 A, t2 B, t2 C, t2 D;
+
+insert t1
+select
+ (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+from t3;
+
+set global rocksdb_force_flush_memtable_now=1;
+
+--echo # Full table scan
+explain
+select * from t1 limit 10;
+select * from t1 limit 10;
+
+--echo # An index scan starting from the end of the table:
+explain
+select * from t1 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+select * from t1 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+
+# A testcase for an assertion that the fix is removing
+# The only requirement for the used column family is that it is reverse-ordered
+create table t4 (
+ pk int unsigned not null primary key,
+ kp1 int unsigned not null,
+ kp2 int unsigned not null,
+ col1 int unsigned,
+ key(kp1, kp2) comment 'rev:bf5_2'
+) engine=rocksdb;
+
+insert into t4 values (1, 0xFFFF, 0xFFF, 12345);
+
+--echo # This must not fail an assert:
+select * from t4 force index(kp1) where kp1=0xFFFFFFFF and kp2<=0xFFFFFFFF order by kp2 desc;
+
+
+--echo #
+--echo # Issue #881: Issue #809 still occurs for reverse scans on forward cfs
+--echo #
+
+# The same as t1 above but uses forward-ordered column family:
+
+create table t5 (
+ id1 bigint not null,
+ id2 bigint not null,
+ id3 varchar(100) not null,
+ id4 int not null,
+ id5 int not null,
+ value bigint,
+ value2 varchar(100),
+ primary key (id1, id2, id3, id4) COMMENT 'bf5_1'
+) engine=ROCKSDB;
+
+insert into t5 select * from t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+
+--echo # An index scan starting from the end of the table:
+explain
+select * from t5 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+select * from t5 order by id1 desc,id2 desc, id3 desc, id4 desc limit 1;
+
+drop table t1,t2,t3,t4,t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load-master.opt
new file mode 100644
index 00000000..c28681ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_default_cf_options=write_buffer_size=16k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:12
+--rocksdb_override_cf_options=cf2={optimize_filters_for_hits=true}
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load.test
new file mode 100644
index 00000000..09d9d734
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_bulk_load.test
@@ -0,0 +1,35 @@
+--source include/have_rocksdb.inc
+
+create table r1 (id bigint primary key, value bigint) engine=rocksdb;
+create table r2 (id bigint, value bigint, primary key (id) comment 'cf2') engine=rocksdb;
+set session rocksdb_bulk_load=1;
+--disable_query_log
+let $t = 1;
+let $i = 1;
+while ($t <= 2) {
+ while ($i <= 1000) {
+ let $insert = INSERT INTO r$t VALUES($i, $i);
+ #skipping a row
+ if ($i != 100) {
+ eval $insert;
+ }
+ inc $i;
+ }
+ inc $t;
+}
+--enable_query_log
+set session rocksdb_bulk_load=0;
+
+# bloom filter should be useful on insert (calling GetForUpdate)
+select variable_value into @h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+insert into r1 values (100, 100);
+select variable_value-@h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+
+# cf2 has no bloo filter in the bottommost level
+select variable_value into @h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+insert into r2 values (100, 100);
+select variable_value-@h from information_schema.global_status where variable_name='rocksdb_block_cache_filter_hit';
+
+DROP TABLE r1, r2;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_load_select.inc b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_load_select.inc
new file mode 100644
index 00000000..cf8b2684
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_load_select.inc
@@ -0,0 +1,190 @@
+source include/have_sequence.inc;
+
+# loading some data (larger than write buf size) to cause compaction
+insert t1
+ select (seq+9) div 10, (seq+4) div 5, (seq+4) div 5, seq, seq, 1000, "aaabbbccc"
+ from seq_1_to_10000;
+insert t2 select * from t1;
+set global rocksdb_force_flush_memtable_now=1;
+
+# BF conditions (prefix short(4B)|medium(20B)|long(240B))
+#0 no eq condition (o, x, x)
+## cond length 4, key length > 4
+call bloom_start();
+select count(*) from t1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(PRIMARY) where id1 >= 1;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2 >= 1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index(id3_id4) where id3 >= '1';
+call bloom_end();
+
+#1 cond length == prefix length (o, o, x)
+## cond length 4+8+8=20, key length > 20
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=2 and id1=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=24 and id1=12;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=88 and id1=44;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=100 and id1=50;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index(id2_id1) where id2=428 and id1=214;
+call bloom_end();
+## (cond_length == extended_key_length(4+8+4+4=20) == prefix_length)
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=1 and id4=1 and id5=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=23 and id4=115 and id5=115;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=500 and id4=2500 and id5=2500;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4_id5) where id2=601 and id4=3005 and id5=3005;
+call bloom_end();
+
+#2 cond length < actual key length and cond_length < prefix length (o, x, x)
+## for long prefix key, most cases falling into this category, unless all key colums are used.
+## cond length 4+8=12, key length > 12
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=23;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=345;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=456;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=23;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=345;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id4) where id2=456;
+call bloom_end();
+
+#3 both actual key length and cond length >= prefix length (o, o, o/x)
+## cond length 4+8+9+8+4=33
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=1 and id3='1' and id1=1 order by id4;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36 and id3='36' and id1=18 order by id4;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124' and id1=62 order by id4;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=888 and id3='888' and id1=444 order by id4;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=124 and id3='124';
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=1 and id3='1' and id4=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2_id3) where id2=12 and id3='12' and id4=60;
+call bloom_end();
+## 4+8+9=25
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=1 and id3='1';
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3) where id2=23 and id3='23';
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=1 and id3='1';
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id3_id2) where id2=23 and id3='23';
+call bloom_end();
+
+#4 actual key length > prefix length and cond length < prefix length (o, x, x)
+## cond length 4+8=12
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=12;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=23;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=100;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (PRIMARY) where id1=234;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=36;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2_id3_id1_id4) where id2=234;
+call bloom_end();
+
+#5 cond length == extended key length < prefix length (o, o, o)
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=1 and id4=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=23 and id4=115;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=500 and id4=2500;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id2) where id2=601 and id4=3005;
+call bloom_end();
+## 4+9+4=17
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='1' and id4=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id3_id4) where id3='12' and id4=60;
+call bloom_end();
+
+#6 cond length == non-extended key length < prefix length, actual key length > prefix length (o, x, x)
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=1;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=23;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=345;
+call bloom_end();
+call bloom_start();
+select count(*) from t1 force index (id2) where id2=456;
+call bloom_end();
+## 4+9+4=17
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='100' and id5=500;
+call bloom_end();
+call bloom_start();
+select count(*) from t2 force index (id3_id5) where id3='240' and id5=1200;
+call bloom_end();
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip-master.opt
new file mode 100644
index 00000000..5c62c7cf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_default_cf_options=write_buffer_size=256k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:20
+--rocksdb_override_cf_options=cf_short_prefix={prefix_extractor=capped:4};cf_long_prefix={prefix_extractor=capped:240}
+--rocksdb_skip_bloom_filter_on_read=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip.test b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip.test
new file mode 100644
index 00000000..efcf9ee1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_skip.test
@@ -0,0 +1 @@
+--source bloomfilter.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_table_def.inc b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_table_def.inc
new file mode 100644
index 00000000..2bc9bb64
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bloomfilter_table_def.inc
@@ -0,0 +1,33 @@
+eval create or replace table t1 (
+ id1 bigint not null,
+ id2 bigint not null,
+ id3 varchar(100) not null,
+ id4 int not null,
+ id5 int not null,
+ value bigint,
+ value2 varchar(100),
+ primary key (id1, id2, id3, id4) $CF,
+ index id2 (id2) $CF,
+ index id2_id1 (id2, id1) $CF,
+ index id2_id3 (id2, id3) $CF,
+ index id2_id4 (id2, id4) $CF,
+ index id2_id3_id1_id4 (id2, id3, id1, id4) $CF,
+ index id3_id2 (id3, id2) $CF
+) engine=ROCKSDB;
+
+eval create or replace table t2 (
+ id1 bigint not null,
+ id2 bigint not null,
+ id3 varchar(100) not null,
+ id4 int not null,
+ id5 int not null,
+ value bigint,
+ value2 varchar(100),
+ primary key (id4) $CF,
+ index id2 (id2) $CF,
+ index id2_id3 (id2, id3) $CF,
+ index id2_id4 (id2, id4) $CF,
+ index id2_id4_id5 (id2, id4, id5) $CF,
+ index id3_id4 (id3, id4) $CF,
+ index id3_id5 (id3, id5) $CF
+) engine=ROCKSDB;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load.test
new file mode 100644
index 00000000..b1afc5b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load.test
@@ -0,0 +1,11 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+# This test requires ~2.3G of disk space
+--source include/big_test.inc
+
+--let pk_cf=cf1
+--let pk_cf_name=cf1
+--let data_order_desc=0
+
+--source ../include/bulk_load.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_drop_table.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_drop_table.test
new file mode 100644
index 00000000..18e40fbf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_drop_table.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load=1;
+
+INSERT INTO t1 VALUES (1);
+
+--connect (con1,localhost,root,,)
+DROP TABLE t1;
+
+--connection default
+--disconnect con1
+
+# This would have crashed the server prior to the fix
+SET rocksdb_bulk_load=0;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_errors.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_errors.test
new file mode 100644
index 00000000..04097848
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_errors.test
@@ -0,0 +1,168 @@
+--source include/have_rocksdb.inc
+--source include/count_sessions.inc
+
+--let LOG1=$MYSQLTEST_VARDIR/tmp/rocksdb.bulk_load_errors.1.err
+--let $_mysqld_option=--log-error=$LOG1
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+
+### Bulk load ###
+CREATE TABLE t1(pk INT, PRIMARY KEY(pk)) ENGINE=ROCKSDB;
+
+# Make sure we get an error with out of order keys during bulk load
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(10);
+INSERT INTO t1 VALUES(11);
+--error ER_KEYS_OUT_OF_ORDER
+INSERT INTO t1 VALUES(9);
+SET rocksdb_bulk_load=0;
+
+# Make sure only 10 and 11 got into the table
+SELECT * FROM t1;
+
+# Make sure we get an error with overlapping data
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(20);
+INSERT INTO t1 VALUES(21);
+
+--error ER_OVERLAPPING_KEYS
+SET rocksdb_bulk_load=0;
+
+SHOW VARIABLES LIKE 'rocksdb_bulk_load';
+call mtr.add_suppression('finalizing last SST file while setting bulk loading variable');
+
+SELECT * FROM t1;
+
+--let SEARCH_FILE=$LOG1
+--let SEARCH_PATTERN=RocksDB: Error [0-9]+ finalizing last SST file while setting bulk loading variable
+--source include/search_pattern_in_file.inc
+
+--let LOG2=$MYSQLTEST_VARDIR/tmp/rocksdb.bulk_load_errors.2.err
+--let $_mysqld_option=--log-error=$LOG2
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+--remove_file $LOG1
+
+
+# Make sure we get an error in log when we disconnect and do not assert the server
+--connect (con1,localhost,root,,)
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(20);
+INSERT INTO t1 VALUES(21);
+let $ID = `SELECT connection_id()`;
+--connection default
+--disconnect con1
+
+SELECT * FROM t1;
+
+--disable_parsing
+# MariaDB: no support for $RPC_PROTOCOL
+if (`SELECT $RPC_PROTOCOL > 0`) {
+ # for --rpc_protocol mode wait for the background detached session to
+ # go away
+ let $wait_condition =
+ SELECT COUNT(*) = 0
+ FROM information_schema.srv_sessions
+ WHERE id = $ID;
+ --source include/wait_condition.inc
+}
+
+if (`SELECT $RPC_PROTOCOL = 0`) {
+ # for non --rpc_protocol mode simply wait until the number of sessions
+ # returns to earlier levels
+ --source include/wait_until_count_sessions.inc
+}
+--enable_parsing
+# MariaDB:
+--source include/wait_until_count_sessions.inc
+
+# Note: in MariaDB, session count will be decremented *before*
+# myrocks::rocksdb_close_connection is called. This causes a race condition:
+# we may grep the error log before bulk load is finalized.
+# To prevent that, do a soft restart of the server (I wasnt able to find
+# any other reliable way)
+--source include/restart_mysqld_with_option.inc
+
+--let SEARCH_FILE=$LOG2
+--let SEARCH_PATTERN=RocksDB: Error [0-9]+ finalizing last SST file while disconnecting
+--source include/search_pattern_in_file.inc
+
+--let LOG3=$MYSQLTEST_VARDIR/tmp/rocksdb.bulk_load_errors.3.err
+--let $_mysqld_option=--log-error=$LOG3
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+--remove_file $LOG2
+
+TRUNCATE TABLE t1;
+
+### Bulk load with unsorted PKs ###
+SET rocksdb_bulk_load_allow_unsorted=1;
+
+# We should not get an error with out of order PKs
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(100);
+INSERT INTO t1 VALUES(101);
+INSERT INTO t1 VALUES(99);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+TRUNCATE TABLE t1;
+
+# We should get an error with duplicate PKs in the same bulk load
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(201);
+INSERT INTO t1 VALUES(200);
+INSERT INTO t1 VALUES(202);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES(201);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1;
+
+SET rocksdb_bulk_load_allow_unsorted=DEFAULT;
+DROP TABLE t1;
+
+# This would trigger a debug assertion that is just an error in release builds
+CREATE TABLE t1(c1 INT KEY) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+--error ER_KEYS_OUT_OF_ORDER
+INSERT INTO t1 VALUES (),(),();
+SET rocksdb_bulk_load=0;
+DROP TABLE t1;
+
+# Crash when table open cache closes handler with bulk load operation not finalized
+SET @orig_table_open_cache=@@global.table_open_cache;
+CREATE TABLE t1(a INT AUTO_INCREMENT, b INT, PRIMARY KEY (a)) ENGINE=ROCKSDB DEFAULT CHARSET=latin1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES(13, 0);
+INSERT INTO t1 VALUES(2, 'test 2');
+INSERT INTO t1 VALUES(@id, @arg04);
+SET @@global.table_open_cache=FALSE;
+INSERT INTO t1 VALUES(51479+0.333333333,1);
+DROP TABLE t1;
+SET @@global.table_open_cache=@orig_table_open_cache;
+
+--let SEARCH_FILE=$LOG3
+--let SEARCH_PATTERN=RocksDB: Error [0-9]+ finalizing bulk load while closing handler
+--source include/search_pattern_in_file.inc
+
+--source include/restart_mysqld.inc
+
+--remove_file $LOG3
+
+# Switch between tables, but also introduce duplicate key errors
+CREATE TABLE t1 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+CREATE TABLE t2 (pk INT, PRIMARY KEY (pk)) ENGINE=ROCKSDB;
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 VALUES (1), (2);
+INSERT INTO t2 VALUES (1), (2);
+INSERT INTO t1 VALUES (1);
+--error ER_OVERLAPPING_KEYS
+INSERT INTO t2 VALUES (3);
+SET rocksdb_bulk_load=0;
+DROP TABLE t1;
+DROP TABLE t2;
+
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf.test
new file mode 100644
index 00000000..f011964d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf.test
@@ -0,0 +1,10 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--source include/big_test.inc
+
+--let pk_cf=rev:cf1
+--let pk_cf_name=cf1
+--let data_order_desc=0
+
+--source ../include/bulk_load.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf_and_data.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf_and_data.test
new file mode 100644
index 00000000..37f19a39
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_cf_and_data.test
@@ -0,0 +1,10 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--source include/big_test.inc
+
+--let pk_cf=rev:cf1
+--let pk_cf_name=cf1
+--let data_order_desc=1
+
+--source ../include/bulk_load.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_data.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_data.test
new file mode 100644
index 00000000..4f3ffd23
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_rev_data.test
@@ -0,0 +1,10 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--source include/big_test.inc
+
+--let pk_cf=cf1
+--let pk_cf_name=cf1
+--let data_order_desc=1
+
+--source ../include/bulk_load.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_sk.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_sk.test
new file mode 100644
index 00000000..0fb47f74
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_sk.test
@@ -0,0 +1,119 @@
+--source include/have_rocksdb.inc
+
+SET rocksdb_bulk_load_size=15;
+
+CREATE TABLE t4 (a INT, b INT, c INT,
+ PRIMARY KEY (a),
+ KEY (b),
+ KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t3 (a INT, b INT, c INT,
+ PRIMARY KEY (a),
+ KEY (b),
+ KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t2 (a INT, b INT, c INT,
+ PRIMARY KEY (a),
+ KEY (b),
+ KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+CREATE TABLE t1 (a INT, b INT, c INT,
+ PRIMARY KEY (a),
+ KEY (b),
+ KEY (c) COMMENT "rev:cf") ENGINE=ROCKSDB;
+
+### Setup the control table ###
+--disable_query_log
+let $sign = 1;
+let $max = 10;
+let $i = 1;
+while ($i <= $max) {
+ let $a = 1 + $sign * $i;
+ let $b = 1 - $sign * $i;
+ let $sign = -$sign;
+ let $insert = INSERT INTO t3 VALUES ($a, $b, $b);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+### Bulk load PK only ###
+SET rocksdb_bulk_load=1;
+INSERT INTO t1 SELECT * FROM t3 FORCE INDEX (PRIMARY) ORDER BY a;
+SELECT count(*) FROM t1 FORCE INDEX (PRIMARY);
+SELECT count(*) FROM t1 FORCE INDEX (b);
+SELECT count(*) FROM t1 FORCE INDEX (c);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SELECT b FROM t1 FORCE INDEX (b);
+SELECT c FROM t1 FORCE INDEX (c);
+--echo Checksums should match
+CHECKSUM TABLE t3;
+CHECKSUM TABLE t1;
+
+### Bulk load PK and SK but require PK order ###
+SET rocksdb_bulk_load_allow_sk=1;
+SET rocksdb_bulk_load=1;
+INSERT INTO t4 SELECT * FROM t3 FORCE INDEX (PRIMARY) ORDER BY a;
+SELECT count(*) FROM t4 FORCE INDEX (PRIMARY);
+SELECT count(*) FROM t4 FORCE INDEX (b);
+SELECT count(*) FROM t4 FORCE INDEX (c);
+SET rocksdb_bulk_load=0;
+SELECT * FROM t4 FORCE INDEX (PRIMARY);
+SELECT b FROM t4 FORCE INDEX (b);
+SELECT c FROM t4 FORCE INDEX (c);
+--echo Checksums should match
+CHECKSUM TABLE t3;
+CHECKSUM TABLE t4;
+
+### Bulk load both PK and SK in random order for all ###
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load_allow_sk=1;
+SET rocksdb_bulk_load=1;
+
+INSERT INTO t2 SELECT * FROM t3 WHERE b >= 0 ORDER BY b;
+INSERT INTO t2 SELECT * FROM t3 WHERE b < 0 ORDER BY b;
+SELECT count(*) FROM t2 FORCE INDEX (PRIMARY);
+SELECT count(*) FROM t2 FORCE INDEX (b);
+SELECT count(*) FROM t2 FORCE INDEX (c);
+
+--disable_query_log
+let $sign = 1;
+let $max = 20;
+let $i = 11;
+while ($i <= $max) {
+ let $a = 1 + $sign * $i;
+ let $b = 1 - $sign * $i;
+ let $sign = -$sign;
+ let $insert = INSERT INTO t2 VALUES ($a, $b, $b);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+SELECT count(*) FROM t2 FORCE INDEX (PRIMARY);
+SELECT count(*) FROM t2 FORCE INDEX (b);
+SELECT count(*) FROM t2 FORCE INDEX (c);
+SET rocksdb_bulk_load=0;
+
+--disable_query_log
+let $sign = 1;
+let $max = 20;
+let $i = 11;
+while ($i <= $max) {
+ let $a = 1 + $sign * $i;
+ let $b = 1 - $sign * $i;
+ let $sign = -$sign;
+ let $insert = INSERT INTO t3 VALUES ($a, $b, $b);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+SELECT * FROM t2 FORCE INDEX (PRIMARY);
+SELECT b FROM t2 FORCE INDEX (b);
+SELECT c FROM t2 FORCE INDEX (c);
+--echo Checksums should match
+CHECKSUM TABLE t3;
+CHECKSUM TABLE t2;
+
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted.test
new file mode 100644
index 00000000..2abeae34
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--let pk_cf=cf1
+
+--source ../include/bulk_load_unsorted.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_errors.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_errors.test
new file mode 100644
index 00000000..eee4f713
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_errors.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+--source include/not_debug.inc
+
+# Cannot change unsorted input preference during bulk load
+SET rocksdb_bulk_load=1;
+--error ER_ERROR_WHEN_EXECUTING_COMMAND
+SET rocksdb_bulk_load_allow_unsorted=1;
+SET rocksdb_bulk_load=0;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_rev.test b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_rev.test
new file mode 100644
index 00000000..de9a5c26
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bulk_load_unsorted_rev.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+--let pk_cf=rev:cf1
+
+--source ../include/bulk_load_unsorted.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.inc b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.inc
new file mode 100644
index 00000000..1f5c9fbb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.inc
@@ -0,0 +1,213 @@
+--source include/have_rocksdb.inc
+
+--source ../include/bypass_create_table.inc
+
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+ no_bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+
+SELECT /*+bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /* +bypassabc*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*aaaaaaaaabbbbbbbbb*/ id1,id2,id1_type,id2_type,data,version
+from link_table WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+byp*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+bypw*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*-b*/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /**/ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+
+--echo # Point query
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2, 3, 4) and link_type=3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2 IN (2) and link_type=3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1) and id2 IN (2) and link_type=3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1 IN (1, 2) and id2 IN (2, 3, 4) and link_type=3;
+
+--echo # Prefix range query
+
+--echo # Prefix range query with SK
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME DESC LIMIT 10;
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 3 AND time = 10
+ORDER BY TIME ASC LIMIT 10;
+
+--echo # Prefix range query with SK with limits
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,10;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,5;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 0,1;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,10;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,5;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,1;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 1,0;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,10;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,5;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,1;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 5,0;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,10;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,5;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,1;
+
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (id1_type)
+WHERE link_type = 3 AND id1 = 1 AND visibility = 4 AND time >= 10
+ORDER BY TIME DESC LIMIT 10,0;
+
+--echo # Prefix range query with PK
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+SELECT /*+ bypass */ id1, id2, link_type FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+
+--echo # Prefix range query with PK + value
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 DESC;
+SELECT /*+ bypass */ id1, id2, link_type, visibility, data, time, version
+FROM link_table FORCE INDEX (PRIMARY)
+WHERE link_type=3 and id1=1 ORDER BY id2 ASC;
+
+--echo # Transaction
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+UPDATE link_table set data="bcd" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+COMMIT;
+
+BEGIN;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+UPDATE link_table set data="cde" WHERE id1=1 and id2=2 and link_type = 3;
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+ROLLBACK;
+
+SELECT /*+ bypass */ id1,id2,id1_type,id2_type,data,version from link_table
+WHERE id1=1 and id2=2 and link_type=3;
+
+--echo # Data types
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3";
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1="1";
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=True;
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=b'1';
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=x'01';
+SELECT /*+ bypass */ id1 FROM link_table where link_type="3" AND id1=NULL;
+
+DROP TABLE count_table;
+DROP TABLE link_table;
+DROP TABLE link_table3;
+DROP TABLE link_table2;
+DROP TABLE id_table;
+DROP TABLE node_table;
+DROP TABLE link_table5;
+DROP TABLE link_table6;
+DROP TABLE link_table4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.test b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.test
new file mode 100644
index 00000000..51064356
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic.test
@@ -0,0 +1,3 @@
+--source include/have_rocksdb.inc
+
+--source bypass_select_basic.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom-master.opt
new file mode 100644
index 00000000..81bc90b0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_default_cf_options=write_buffer_size=128m;target_file_size_base=32m;max_bytes_for_level_base=512m;level0_file_num_compaction_trigger=4;level0_slowdown_writes_trigger=20;level0_stop_writes_trigger=30;max_write_buffer_number=4;compression_per_level=kLZ4Compression;bottommost_compression=kZSTD;compression_opts=-14:6:0;block_based_table_factory={cache_index_and_filter_blocks=1;filter_policy=bloomfilter:10:false;whole_key_filtering=0};prefix_extractor=capped:12;level_compaction_dynamic_level_bytes=true;optimize_filters_for_hits=true;memtable_prefix_bloom_size_ratio=0.039;max_compaction_bytes=402653184;report_bg_io_stats=true;compaction_pri=kMinOverlappingRatio;soft_pending_compaction_bytes_limit=20480000000
+--rocksdb_override_cf_options=cf_assoc={prefix_extractor=capped:28};cf_assoc_count={prefix_extractor=capped:20};rev:cf_assoc_id1_type={prefix_extractor=capped:20};cf_fbobj_type_id={prefix_extractor=capped:16};cf_assoc_disagg={prefix_extractor=capped:20};__system__={write_buffer_size=16m};
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom.test b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom.test
new file mode 100644
index 00000000..51064356
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bypass_select_basic_bloom.test
@@ -0,0 +1,3 @@
+--source include/have_rocksdb.inc
+
+--source bypass_select_basic.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/bytes_written.test b/storage/rocksdb/mysql-test/rocksdb/t/bytes_written.test
new file mode 100644
index 00000000..f0361707
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/bytes_written.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS stats_test_table;
+--enable_warnings
+
+# Create the table
+CREATE TABLE stats_test_table (a INT, b INT, PRIMARY KEY (a)) ENGINE=ROCKSDB;
+
+# Ensure appropriate perf_context_level is set
+SET GLOBAL rocksdb_perf_context_level=3;
+
+# Insert some values
+INSERT INTO stats_test_table VALUES (7,1);
+INSERT INTO stats_test_table VALUES (2,2);
+
+# Verify the bytes written are updated in the table stats
+SELECT io_write_bytes > 0 FROM INFORMATION_SCHEMA.TABLE_STATISTICS WHERE TABLE_NAME = "stats_test_table";
+
+# Cleanup
+DROP TABLE stats_test_table;
+SET GLOBAL rocksdb_perf_context_level=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cardinality-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/cardinality-master.opt
new file mode 100644
index 00000000..2cd3c805
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cardinality-master.opt
@@ -0,0 +1,3 @@
+--skip-rocksdb_debug_optimizer_no_zero_cardinality
+--rocksdb_compaction_sequential_deletes=0
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cardinality.test b/storage/rocksdb/mysql-test/rocksdb/t/cardinality.test
new file mode 100644
index 00000000..1dcb176e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cardinality.test
@@ -0,0 +1,119 @@
+--source include/have_rocksdb.inc
+
+--source include/restart_mysqld.inc
+
+set use_stat_tables= 'COMPLEMENTARY';
+
+# Test memtable cardinality statistics
+CREATE TABLE t0 (id int PRIMARY KEY, a int, INDEX ix_a (a)) engine=rocksdb;
+
+# populate the table with 10 reconds where cardinality of id is N and a is N/2.
+insert into t0 values (0, 0),(1, 1),(2, 2),(3, 3),(4, 4),
+(5, 4),(6, 4),(7, 4),(8, 4),(9, 4);
+
+# Assert no cardinality data exists before ANALYZE TABLE is done
+SELECT cardinality FROM information_schema.statistics where table_name="t0" and
+column_name="id";
+SELECT cardinality FROM information_schema.statistics where table_name="t0" and
+column_name="a";
+
+--disable_result_log
+ANALYZE TABLE t0;
+--enable_result_log
+
+SELECT table_rows into @N FROM information_schema.tables
+WHERE table_name = "t0";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="id";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="a";
+
+# Flush the table and re-run the test as statistics is calculated a bit
+# differently for memtable and SST files
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+--disable_result_log
+ANALYZE TABLE t0;
+--enable_result_log
+
+SELECT table_rows into @N FROM information_schema.tables
+WHERE table_name = "t0";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="id";
+SELECT FLOOR(@N/cardinality) FROM
+information_schema.statistics where table_name="t0" and column_name="a";
+
+drop table t0;
+
+# Test big table on SST
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t10,t11;
+--enable_warnings
+
+create table t1(
+ id bigint not null primary key,
+ i1 bigint, #unique
+ i2 bigint, #repeating
+ c1 varchar(20), #unique
+ c2 varchar(20), #repeating
+ index t1_1(id, i1),
+ index t1_2(i1, i2),
+ index t1_3(i2, i1),
+ index t1_4(c1, c2),
+ index t1_5(c2, c1)
+) engine=rocksdb;
+
+--disable_query_log
+create table t10(a int primary key);
+insert into t10 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t11(a int primary key);
+insert into t11 select A.a + B.a* 10 + C.a * 100 from t10 A, t10 B, t10 C;
+
+set @a=0;
+let $i=0;
+set rocksdb_bulk_load=1;
+while ($i<100)
+{
+ inc $i;
+ eval insert into t1 select (@a:=@a+1), @a, @a div 10, @a, @a div 10 from t11;
+}
+set rocksdb_bulk_load=0;
+
+drop table t10;
+drop table t11;
+--enable_query_log
+
+# Flush memtable out to SST and display index cardinalities
+optimize table t1;
+show index in t1;
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+
+--echo restarting...
+--source include/restart_mysqld.inc
+
+# display index cardinalities after the restart
+show index in t1;
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+
+CREATE TABLE t2 (a INT, b INT, c INT, d INT, e INT, f INT, g INT,
+ PRIMARY KEY (a), KEY (c, b, a, d, e, f, g))
+ ENGINE=ROCKSDB;
+--disable_query_log
+let $i=0;
+while ($i<100)
+{
+ inc $i;
+ eval insert t2 values($i, $i div 10, 1, 1, 1, 1, 1);
+}
+--enable_query_log
+
+# Cardinality of key c should be 1 for c, 10 for b, 100 for a and the other fields.
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+ANALYZE TABLE t2;
+--echo cardinality of the columns after 'a' must be equal to the cardinality of column 'a'
+SELECT CARDINALITY INTO @c FROM information_schema.statistics WHERE TABLE_NAME='t2' AND INDEX_NAME='c' AND COLUMN_NAME='a';
+SELECT COLUMN_NAME, CARDINALITY = @c FROM information_schema.statistics WHERE TABLE_NAME='t2' AND INDEX_NAME='c' AND SEQ_IN_INDEX > 3;
+
+drop table t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/check_flags.test b/storage/rocksdb/mysql-test/rocksdb/t/check_flags.test
new file mode 100644
index 00000000..9afe562f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/check_flags.test
@@ -0,0 +1,117 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = -10;
+
+connect (conn1, localhost, root,,);
+--let $conn1_id = `SELECT CONNECTION_ID()`
+connection default;
+
+CREATE TABLE t1 (id INT, value INT, KEY (id), KEY (value)) ENGINE=ROCKSDB;
+CREATE TABLE t2 (id INT, value INT) ENGINE=ROCKSDB;
+CREATE TABLE t3 (id INT, kp1 INT, PRIMARY KEY (id), KEY(kp1)) ENGINE=ROCKSDB COMMENT='ttl_duration=1';
+
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+INSERT INTO t2 SELECT * FROM t1;
+INSERT INTO t3 SELECT * FROM t1;
+
+connection conn1;
+set debug_sync='rocksdb.check_flags_rmi SIGNAL parked WAIT_FOR go';
+send SELECT value FROM t1 WHERE value = 3;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+--echo KILL QUERY \$conn1_id;
+--disable_query_log
+eval KILL QUERY $conn1_id;
+--enable_query_log
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+set debug_sync='RESET';
+
+connection conn1;
+set debug_sync='rocksdb.check_flags_rmi_scan SIGNAL parked WAIT_FOR go';
+send SELECT DISTINCT(id) FROM t1 WHERE value = 5 AND id IN (1, 3, 5);
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+--echo KILL QUERY \$conn1_id;
+--disable_query_log
+eval KILL QUERY $conn1_id;
+--enable_query_log
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+set debug_sync='RESET';
+
+connection conn1;
+set debug_sync='rocksdb.check_flags_inwd SIGNAL parked WAIT_FOR go';
+send SELECT value FROM t1 WHERE value > 3;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+--echo KILL QUERY \$conn1_id;
+--disable_query_log
+eval KILL QUERY $conn1_id;
+--enable_query_log
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+set debug_sync='RESET';
+
+connection conn1;
+set debug_sync='rocksdb.check_flags_rnwd SIGNAL parked WAIT_FOR go';
+send SELECT id FROM t2;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+--echo KILL QUERY \$conn1_id;
+--disable_query_log
+eval KILL QUERY $conn1_id;
+--enable_query_log
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+set debug_sync='RESET';
+
+
+connection conn1;
+set debug_sync='rocksdb.check_flags_ser SIGNAL parked WAIT_FOR go';
+send SELECT kp1 FROM t3 ORDER BY kp1;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+--echo KILL QUERY \$conn1_id;
+--disable_query_log
+eval KILL QUERY $conn1_id;
+--enable_query_log
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+connection default;
+--disconnect conn1
+
+set debug_sync='RESET';
+
+set global rocksdb_debug_ttl_read_filter_ts = DEFAULT;
+
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/check_ignore_unknown_options.test b/storage/rocksdb/mysql-test/rocksdb/t/check_ignore_unknown_options.test
new file mode 100644
index 00000000..15a7d319
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/check_ignore_unknown_options.test
@@ -0,0 +1,56 @@
+--disable_warnings
+let $MYSQLD_DATADIR= `select @@datadir`;
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+let $error_log= $MYSQLTEST_VARDIR/log/my_restart.err;
+select variable_name, variable_value from information_schema.global_variables where variable_name="rocksdb_ignore_unknown_options";
+
+#
+# MariaDB: The following shell commands are not portable so we are
+# using perl instead:
+#--exec find $MYSQLD_DATADIR/#rocksdb/OPTIONS* | sort -t- -k 2 -n | tail -1 | xargs -0 -I {} -t sh -c "sed -i 's/rocksdb_version=.*/rocksdb_version=99.9.9/' {}"
+#--exec find $MYSQLD_DATADIR/#rocksdb/OPTIONS* | sort -t- -k 2 -n | tail -1 | xargs -0 -I {} -t sh -c "echo hello=world>>{}"
+
+perl;
+ my $path=$ENV{MYSQLTEST_VARDIR} . "/mysqld.1/data/\#rocksdb";
+ opendir(my $dh, $path) || die "Can't opendir $some_dir: $!";
+ my @files = grep { /^OPTIONS/ } readdir($dh);
+ closedir($dh);
+
+ sub compare_second_as_number {
+ local $aa= shift;
+ local $bb= shift;
+ $aa =~ s/OPTIONS-//;
+ $bb =~ s/OPTIONS-//;
+ return $aa <=> $bb;
+ }
+
+ @sorted_files = sort { compare_second_as_number($a, $b); } @files;
+ my $last_file= $sorted_files[-1];
+
+ my $contents="";
+ open(my $fh, "<", "$path/$last_file") || die ("Couldn't open $path/$last_file");
+ while (<$fh>) {
+ $_ =~ s/rocksdb_version=.*/rocksdb_version=99.9.9/;
+ $contents .= $_;
+ }
+ close($fh);
+ $contents .= "hello=world\n";
+ open(my $fh, ">", "$path/$last_file") || die("Can't open $path/$file for writing");
+ print $fh $contents;
+ close($fh);
+EOF
+
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--shutdown_server
+
+--error 1
+--exec $MYSQLD_CMD --plugin_load=$HA_ROCKSDB_SO --rocksdb_ignore_unknown_options=0 --log-error=$error_log
+
+let SEARCH_FILE= $error_log;
+let SEARCH_PATTERN= RocksDB: Compatibility check against existing database options failed;
+--source include/search_pattern_in_file.inc
+--remove_file $error_log
+--enable_reconnect
+--exec echo "restart" > $restart_file
+--source include/wait_until_connected_again.inc
+select variable_name, variable_value from information_schema.global_variables where variable_name="rocksdb_ignore_unknown_options";
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/check_log_for_xa.py b/storage/rocksdb/mysql-test/rocksdb/t/check_log_for_xa.py
new file mode 100644
index 00000000..a3d50f30
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/check_log_for_xa.py
@@ -0,0 +1,31 @@
+import sys
+import re
+
+"""
+Example usage:
+ python check_log_for_xa.py path/to/log/mysqld.2.err rollback,commit,prepare
+"""
+
+log_path = sys.argv[1]
+desired_filters = sys.argv[2]
+
+all_filters = [
+ ('rollback', re.compile('(\[Note\] rollback xid .+)')),
+ ('commit', re.compile('(\[Note\] commit xid .+)')),
+ ('prepare',
+ re.compile('(\[Note\] Found \d+ prepared transaction\(s\) in \w+)')),
+]
+
+active_filters = filter(lambda f: f[0] in desired_filters, all_filters)
+
+results = set()
+with open(log_path) as log:
+ for line in log:
+ line = line.strip()
+ for f in active_filters:
+ match = f[1].search(line)
+ if match:
+ results.add("**found '%s' log entry**" % f[0])
+
+for res in results:
+ print res
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/check_table.inc b/storage/rocksdb/mysql-test/rocksdb/t/check_table.inc
new file mode 100644
index 00000000..c108a973
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/check_table.inc
@@ -0,0 +1,54 @@
+#
+# CHECK TABLE statements
+#
+# Note: the output is likely to be different for the engine under test,
+# in which case rdiff will be needed. Or, the output might say that
+# the storage engine does not support CHECK.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+CHECK TABLE t1;
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+CHECK TABLE t1, t2 FOR UPGRADE;
+INSERT INTO t2 (a,b) VALUES (5,'e');
+CHECK TABLE t2 QUICK;
+INSERT INTO t1 (a,b) VALUES (6,'f');
+CHECK TABLE t1 FAST;
+INSERT INTO t1 (a,b) VALUES (7,'g');
+INSERT INTO t2 (a,b) VALUES (8,'h');
+CHECK TABLE t2, t1 MEDIUM;
+INSERT INTO t1 (a,b) VALUES (9,'i');
+INSERT INTO t2 (a,b) VALUES (10,'j');
+CHECK TABLE t1, t2 EXTENDED;
+INSERT INTO t1 (a,b) VALUES (11,'k');
+CHECK TABLE t1 CHANGED;
+
+DROP TABLE t1, t2;
+
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a) VALUES (1),(2),(5);
+CHECK TABLE t1;
+INSERT INTO t1 (a) VALUES (6),(8),(12);
+CHECK TABLE t1 FOR UPGRADE;
+INSERT INTO t1 (a) VALUES (13),(15),(16);
+CHECK TABLE t1 QUICK;
+INSERT INTO t1 (a) VALUES (17),(120),(132);
+CHECK TABLE t1 FAST;
+INSERT INTO t1 (a) VALUES (801),(900),(7714);
+CHECK TABLE t1 MEDIUM;
+INSERT INTO t1 (a) VALUES (8760),(10023),(12000);
+CHECK TABLE t1 EXTENDED;
+INSERT INTO t1 (a) VALUES (13345),(24456),(78302),(143028);
+CHECK TABLE t1 CHANGED;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/check_table.test b/storage/rocksdb/mysql-test/rocksdb/t/check_table.test
new file mode 100644
index 00000000..4d349f7a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/check_table.test
@@ -0,0 +1,12 @@
+--source include/have_rocksdb.inc
+
+#
+# CHECK TABLE statements
+#
+# Note: the output is likely to be different for the engine under test,
+# in which case rdiff will be needed. Or, the output might say that
+# the storage engine does not support CHECK.
+#
+
+--source check_table.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/checkpoint.test b/storage/rocksdb/mysql-test/rocksdb/t/checkpoint.test
new file mode 100644
index 00000000..68fe02bb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/checkpoint.test
@@ -0,0 +1,107 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+--enable_warnings
+
+# Start from clean slate
+#--source include/restart_mysqld.inc
+
+CREATE TABLE t1 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t2 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t3 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t4 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+# Populate tables
+let $max = 1000;
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t2;
+--source drop_table_repopulate_table.inc
+let $table = t3;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+# Make sure new table gets unique indices
+CREATE TABLE t5 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+let $max = 1000;
+let $table = t5;
+--source drop_table_repopulate_table.inc
+
+# Create checkpoint without trailing '/'
+let $checkpoint = $MYSQL_TMP_DIR/checkpoint;
+let $succeeds = 1;
+--source set_checkpoint.inc
+
+# Create checkpoint with a trailing '/'
+let $checkpoint = $MYSQL_TMP_DIR/checkpoint/;
+let $succeeds = 1;
+--source set_checkpoint.inc
+
+# Set checkpoint dir as empty string, which fails
+let $checkpoint = ;
+let $succeeds = 0;
+--source set_checkpoint.inc
+
+# Set checkpoint as a directory that does not exist, which fails
+let $checkpoint = /does/not/exist;
+let $succeeds = 0;
+--source set_checkpoint.inc
+
+# Set checkpoint as a directory that already exists, which fails
+let $checkpoint = $MYSQL_TMP_DIR/already-existing-directory;
+--mkdir $checkpoint
+let $succeeds = 0;
+--source set_checkpoint.inc
+rmdir $checkpoint;
+
+--disable_result_log
+truncate table t1;
+optimize table t1;
+truncate table t2;
+optimize table t2;
+truncate table t3;
+optimize table t3;
+truncate table t4;
+optimize table t4;
+truncate table t5;
+optimize table t5;
+drop table if exists t1;
+drop table if exists t2;
+drop table if exists t3;
+drop table if exists t4;
+drop table if exists t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/checksum_table.test b/storage/rocksdb/mysql-test/rocksdb/t/checksum_table.test
new file mode 100644
index 00000000..51c639a8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/checksum_table.test
@@ -0,0 +1,84 @@
+--source include/have_rocksdb.inc
+
+#
+# CHECKSUM TABLE statements for standard CHECKSUM properties.
+# Live checksums are covered in checksum_table_live.test
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=0;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=0;
+
+CHECKSUM TABLE t1;
+CHECKSUM TABLE t2, t1;
+CHECKSUM TABLE t1, t2 QUICK;
+CHECKSUM TABLE t1, t2 EXTENDED;
+
+DROP TABLE t1, t2;
+
+--echo #
+--echo # Issue #110: SQL command checksum returns inconsistent result
+--echo #
+create table t1 (pk int primary key, col1 varchar(10)) engine=rocksdb;
+insert into t1 values (2,'fooo');
+insert into t1 values (1,NULL);
+checksum table t1;
+checksum table t1;
+select * from t1 where pk=2;
+checksum table t1;
+checksum table t1;
+flush tables;
+checksum table t1;
+checksum table t1;
+
+drop table t1;
+
+--echo #
+--echo # The following test is about making sure MyRocks CHECKSUM TABLE
+--echo # values are the same as with InnoDB.
+--echo # If you see checksum values changed, make sure their counterparts
+--echo # in suite/innodb/r/checksum-matches-myrocks.result match.
+--echo #
+
+create table t1 (pk int primary key, col1 varchar(10)) engine=rocksdb;
+insert into t1 values (2,'fooo');
+insert into t1 values (1,NULL);
+checksum table t1;
+drop table t1;
+
+create table t1 (
+ pk bigint unsigned primary key,
+ col1 varchar(10),
+ col2 tinyint,
+ col3 double
+) engine=rocksdb;
+
+--echo # MariaDB has changed the checksumming algorithm
+--echo # Enable the old algorithm:
+set @tmp_old=@@old;
+set old=1;
+
+
+checksum table t1;
+
+insert into t1 values (1, NULL, NULL, NULL);
+insert into t1 values (2, 'foo', NULL, NULL);
+checksum table t1;
+
+insert into t1 values (3, NULL, 123, NULL);
+insert into t1 values (4, NULL, NULL, 2.78);
+checksum table t1;
+
+insert into t1 values (5, 'xxxYYYzzzT', NULL, 2.78);
+insert into t1 values (6, '', NULL, 2.78);
+checksum table t1;
+
+set old=@tmp_old;
+
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/checksum_table_live.test b/storage/rocksdb/mysql-test/rocksdb/t/checksum_table_live.test
new file mode 100644
index 00000000..da278ed7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/checksum_table_live.test
@@ -0,0 +1,24 @@
+--source include/have_rocksdb.inc
+
+#
+# CHECKSUM TABLE statements for live CHECKSUM.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+# For most engines CHECKSUM=1 option will be ignored,
+# and the results will be different
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+
+CHECKSUM TABLE t1;
+CHECKSUM TABLE t2, t1;
+CHECKSUM TABLE t1, t2 QUICK;
+CHECKSUM TABLE t1, t2 EXTENDED;
+
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_not_null.inc b/storage/rocksdb/mysql-test/rocksdb/t/col_not_null.inc
new file mode 100644
index 00000000..2d3c9292
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_not_null.inc
@@ -0,0 +1,55 @@
+#
+# NOT NULL attribute in columns
+#
+# Usage:
+# let $col_type = <column type>;
+# let $col_default = <default value for a column>;
+# --source col_not_null.inc
+#
+# We will add NOT NULL to the column options;
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--echo #----------------------------------
+--echo # $col_type NOT NULL columns without a default
+--echo #----------------------------------
+
+eval CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c $col_type NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+
+--error ER_BAD_NULL_ERROR
+INSERT INTO t1 (c) VALUES (NULL);
+eval INSERT INTO t1 (c) VALUES ($col_default);
+SELECT HEX(c) FROM t1;
+
+DROP TABLE t1;
+
+--echo #----------------------------------
+--echo # $col_type NOT NULL columns with a default
+--echo #----------------------------------
+
+eval CREATE TABLE t1 (
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ c $col_type NOT NULL DEFAULT $col_default
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+--error ER_INVALID_DEFAULT
+eval ALTER TABLE t1 ADD COLUMN err $col_type NOT NULL DEFAULT NULL;
+
+--error ER_BAD_NULL_ERROR
+INSERT INTO t1 (c) VALUES (NULL);
+
+eval INSERT INTO t1 (c) VALUES ($col_default);
+eval INSERT INTO t1 () VALUES ();
+
+# HEX should be universal for all column types
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_not_null_timestamp.inc b/storage/rocksdb/mysql-test/rocksdb/t/col_not_null_timestamp.inc
new file mode 100644
index 00000000..812ada6f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_not_null_timestamp.inc
@@ -0,0 +1,70 @@
+#
+# NOT NULL attribute in TIMESTAMP columns
+#
+# This is a copy of col_not_null.inc, except that
+# instead of getting an error on inserting NULL into a non-NULL column,
+# we are getting the current timestamp (see MySQL:68472).
+# If the bug is ever fixed, this include file won't be needed anymore.
+
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--echo #----------------------------------
+--echo # $col_type NOT NULL column without a default
+--echo #----------------------------------
+
+eval CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, c $col_type NOT NULL) ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+
+# Here where the non-standard behavior strikes:
+# instead of an error we are getting the current timestamp
+
+# As of mysql-5.6.11, this no longer works, and we get an error:
+# (MariaDB doesn't have this patch, so it doesnt produce an error):
+# --error ER_BAD_NULL_ERROR
+INSERT INTO t1 (c) VALUES (NULL);
+eval INSERT INTO t1 (c) VALUES ($col_default);
+SELECT HEX(c) FROM t1;
+
+DROP TABLE t1;
+
+--echo #----------------------------------
+--echo # $col_type NOT NULL columns with a default
+--echo #----------------------------------
+
+eval CREATE TABLE t1 (
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ c $col_type NOT NULL DEFAULT $col_default
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+--error ER_INVALID_DEFAULT
+eval ALTER TABLE t1 ADD COLUMN err $col_type NOT NULL DEFAULT NULL;
+
+# Here where the non-standard behavior strikes:
+# instead of an error we are getting the current timestamp
+
+# As of mysql-5.6.11, this no longer works, and we get an error:
+# (MariaDB doesn't have this patch, so it doesnt produce an error):
+# --error ER_BAD_NULL_ERROR
+
+# Since we don't produce an error, the row will get inserted. Make it
+# deterministic:
+set @save_ts=@@timestamp;
+set timestamp=1478923914;
+
+INSERT INTO t1 (c) VALUES (NULL);
+set timestamp=@save_ts;
+
+eval INSERT INTO t1 (c) VALUES ($col_default);
+eval INSERT INTO t1 () VALUES ();
+
+# HEX should be universal for all column types
+SELECT pk, HEX(c) FROM t1 ORDER BY pk;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_null.inc b/storage/rocksdb/mysql-test/rocksdb/t/col_null.inc
new file mode 100644
index 00000000..7ebfee0b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_null.inc
@@ -0,0 +1,34 @@
+#
+# NULL attribute and DEFAULT NULL in columns
+#
+# Usage:
+# let $col_type = <column type>;
+# let $col_default = <default non-null value for a column>;
+# --source col_null.inc
+#
+# We will add NULL attribute to the column options.
+#
+
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ c $col_type NULL,
+ c1 $col_type NULL DEFAULT NULL,
+ c2 $col_type NULL DEFAULT $col_default,
+ pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (c,c1,c2) VALUES (NULL,NULL,NULL);
+eval INSERT INTO t1 (c,c1,c2) VALUES ($col_default,$col_default,$col_default);
+INSERT INTO t1 () VALUES ();
+
+SELECT pk, HEX(c), HEX(c1), HEX(c2) FROM t1 ORDER BY pk;
+SELECT pk, HEX(c2) FROM t1 ORDER BY pk;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_opt_default.test b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_default.test
new file mode 100644
index 00000000..6f91ee7c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_default.test
@@ -0,0 +1,27 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether DEFAULT column attribute
+# is supported in CREATE and ALTER TABLE.
+# If the attribute is supported at all, it will be covered
+# in more details in col_option_null and col_option_not_null tests.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY DEFAULT '0') ENGINE=rocksdb;
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a) VALUES (1);
+SELECT a FROM t1;
+
+ALTER TABLE t1 ADD COLUMN b CHAR(8) DEFAULT '';
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (b) VALUES ('a');
+SELECT a,b FROM t1 ORDER BY a,b;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_opt_not_null.test b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_not_null.test
new file mode 100644
index 00000000..1de4ccee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_not_null.test
@@ -0,0 +1,229 @@
+--source include/have_rocksdb.inc
+
+#
+# NOT NULL column attribute
+#
+
+#########################################
+# TODO:
+# Currently the test produces incorrect (?) result
+# due to bug MySQL:68472. If the bug is ever fixed,
+# the test and result files will need to be updated.
+#########################################
+
+
+let $extra_col_opts = NOT NULL;
+
+--echo ########################
+--echo # BINARY columns
+--echo ########################
+
+--source type_binary.inc
+--let $col_type = BINARY
+--let $col_default = 0
+--source col_not_null.inc
+
+--echo ########################
+--echo # VARBINARY columns
+--echo ########################
+
+--source type_varbinary.inc
+--let $col_type = VARBINARY(64)
+--let $col_default = 'test'
+--source col_not_null.inc
+
+--echo ########################
+--echo # BIT columns
+--echo ########################
+
+--source type_bit.inc
+--let $col_type = BIT
+--let $col_default = 1
+--source col_not_null.inc
+
+--echo ########################
+--echo # BLOB columns
+--echo ########################
+
+--source type_blob.inc
+
+--let $col_default = ''
+
+--let $col_type = BLOB
+--source col_not_null.inc
+
+--let $col_type = TINYBLOB
+--source col_not_null.inc
+
+--let $col_type = MEDIUMBLOB
+--source col_not_null.inc
+
+--let $col_type = LONGBLOB
+--source col_not_null.inc
+
+--echo ########################
+--echo # BOOL columns
+--echo ########################
+
+--source type_bool.inc
+--let $col_type = BOOL
+--let $col_default = '0'
+--source col_not_null.inc
+
+--echo ########################
+--echo # CHAR columns
+--echo ########################
+
+--source type_char.inc
+--let $col_type = CHAR
+--let $col_default = '_'
+--source col_not_null.inc
+
+--echo ########################
+--echo # VARCHAR columns
+--echo ########################
+
+--source type_varchar.inc
+--let $col_type = VARCHAR(64)
+--let $col_default = 'test default'
+--source col_not_null.inc
+
+--echo ########################
+--echo # date and time columns
+--echo ########################
+
+set @col_opt_not_nullsave_time_zone=@@time_zone;
+set time_zone='UTC';
+
+--source type_date_time.inc
+
+SET TIMESTAMP=UNIX_TIMESTAMP('2013-12-12 12:12:12');
+
+--let $col_type = DATE
+--let $col_default = '2012-12-21'
+--source col_not_null.inc
+
+--let $col_type = DATETIME
+--let $col_default = '2012-12-21 12:21:12'
+--source col_not_null.inc
+
+# Even with explicit-defaults-for-timestamps, we still can't use
+# the standard include file, due to bug MySQL:68472
+
+--let $col_type = TIMESTAMP
+--let $col_default = '2012-12-21 12:21:12'
+--source col_not_null_timestamp.inc
+
+--let $col_type = TIME
+--let $col_default = '12:21:12'
+--source col_not_null.inc
+
+--let $col_type = YEAR
+--let $col_default = '2012'
+--source col_not_null.inc
+
+--let $col_type = YEAR(2)
+--let $col_default = '12'
+--source col_not_null.inc
+
+set time_zone= @col_opt_not_nullsave_time_zone;
+
+--echo ########################
+--echo # ENUM columns
+--echo ########################
+
+--source type_enum.inc
+
+--let $col_type = ENUM('test1','test2','test3')
+--let $col_default = 'test2'
+--source col_not_null.inc
+
+--echo ########################
+--echo # Fixed point columns (NUMERIC, DECIMAL)
+--echo ########################
+
+--source type_fixed.inc
+
+--let $col_type = DECIMAL
+--let $col_default = 1.1
+--source col_not_null.inc
+
+--let $col_type = NUMERIC
+--let $col_default = 0
+--source col_not_null.inc
+
+--echo ########################
+--echo # Floating point columns (FLOAT, DOUBLE)
+--echo ########################
+
+--source type_float.inc
+
+--let $col_type = FLOAT
+--let $col_default = 1.1
+--source col_not_null.inc
+
+--let $col_type = DOUBLE
+--let $col_default = 0
+--source col_not_null.inc
+
+--echo ########################
+--echo # INT columns
+--echo ########################
+
+--source type_int.inc
+
+--let $col_type = INT
+--let $col_default = 2147483647
+--source col_not_null.inc
+
+--let $col_type = TINYINT
+--let $col_default = 127
+--source col_not_null.inc
+
+--let $col_type = SMALLINT
+--let $col_default = 0
+--source col_not_null.inc
+
+--let $col_type = MEDIUMINT
+--let $col_default = 1
+--source col_not_null.inc
+
+--let $col_type = BIGINT
+--let $col_default = 9223372036854775807
+--source col_not_null.inc
+
+--echo ########################
+--echo # SET columns
+--echo ########################
+
+--source type_set.inc
+--let $col_type = SET('test1','test2','test3')
+--let $col_default = 'test2,test3'
+--source col_not_null.inc
+
+--echo ########################
+--echo # TEXT columns
+--echo ########################
+
+--source type_text.inc
+
+--let $col_default = ''
+
+--let $col_type = TEXT
+--source col_not_null.inc
+
+--let $col_type = TINYTEXT
+--source col_not_null.inc
+
+--let $col_type = MEDIUMTEXT
+--source col_not_null.inc
+
+--let $col_type = LONGTEXT
+--source col_not_null.inc
+
+
+--let $col_type =
+--let $col_default =
+--let $extra_col_opts =
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_opt_null.test b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_null.test
new file mode 100644
index 00000000..c41abb78
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_null.test
@@ -0,0 +1,220 @@
+--source include/have_rocksdb.inc
+
+#
+# NULL column attribute
+#
+
+let $extra_col_opts = NULL;
+
+
+--echo ########################
+--echo # BINARY columns
+--echo ########################
+
+--source type_binary.inc
+--let $col_type = BINARY
+--let $col_default = 0
+--source col_null.inc
+
+--echo ########################
+--echo # VARBINARY columns
+--echo ########################
+
+--source type_varbinary.inc
+--let $col_type = VARBINARY(64)
+--let $col_default = 'test'
+--source col_null.inc
+
+--echo ########################
+--echo # BIT columns
+--echo ########################
+
+--source type_bit.inc
+--let $col_type = BIT
+--let $col_default = 1
+--source col_null.inc
+
+--echo ########################
+--echo # BLOB columns
+--echo ########################
+
+--source type_blob.inc
+
+--let $col_default = ''
+
+--let $col_type = BLOB
+--source col_null.inc
+
+--let $col_type = TINYBLOB
+--source col_null.inc
+
+--let $col_type = MEDIUMBLOB
+--source col_null.inc
+
+--let $col_type = LONGBLOB
+--source col_null.inc
+
+--echo ########################
+--echo # BOOL columns
+--echo ########################
+
+--source type_bool.inc
+--let $col_type = BOOL
+--let $col_default = '0'
+--source col_null.inc
+
+
+--echo ########################
+--echo # CHAR columns
+--echo ########################
+
+--source type_char.inc
+--let $col_type = CHAR
+--let $col_default = '_'
+--source col_null.inc
+
+--echo ########################
+--echo # VARCHAR columns
+--echo ########################
+
+
+--source type_varchar.inc
+--let $col_type = VARCHAR(64)
+--let $col_default = 'test default'
+--source col_null.inc
+
+
+--echo ########################
+--echo # date and time columns
+--echo ########################
+
+set @col_opt_nullsave_time_zone=@@time_zone;
+set time_zone='UTC';
+
+--source type_date_time.inc
+
+--let $col_type = DATE
+--let $col_default = '2012-12-21'
+--source col_null.inc
+
+--let $col_type = DATETIME
+--let $col_default = '2012-12-21 12:21:12'
+--source col_null.inc
+
+--let $col_type = TIMESTAMP
+--let $col_default = '2012-12-21 12:21:12'
+--source col_null.inc
+
+--let $col_type = TIME
+--let $col_default = '12:21:12'
+--source col_null.inc
+
+--let $col_type = YEAR
+--let $col_default = '2012'
+--source col_null.inc
+
+--let $col_type = YEAR(2)
+--let $col_default = '12'
+--source col_null.inc
+
+set time_zone=@col_opt_nullsave_time_zone;
+
+--echo ########################
+--echo # ENUM columns
+--echo ########################
+
+--source type_enum.inc
+--let $col_type = ENUM('test1','test2','test3')
+--let $col_default = 'test2'
+--source col_null.inc
+
+--echo ########################
+--echo # Fixed point columns (NUMERIC, DECIMAL)
+--echo ########################
+
+--source type_fixed.inc
+
+--let $col_type = DECIMAL
+--let $col_default = 1.1
+--source col_null.inc
+
+--let $col_type = NUMERIC
+--let $col_default = 0
+--source col_null.inc
+
+--echo ########################
+--echo # Floating point columns (FLOAT, DOUBLE)
+--echo ########################
+
+--source type_float.inc
+
+--let $col_type = FLOAT
+--let $col_default = 1.1
+--source col_null.inc
+
+--let $col_type = DOUBLE
+--let $col_default = 0
+--source col_null.inc
+
+--echo ########################
+--echo # INT columns
+--echo ########################
+
+--source type_int.inc
+
+--let $col_type = INT
+--let $col_default = 2147483647
+--source col_null.inc
+
+--let $col_type = TINYINT
+--let $col_default = 127
+--source col_null.inc
+
+--let $col_type = SMALLINT
+--let $col_default = 0
+--source col_null.inc
+
+--let $col_type = MEDIUMINT
+--let $col_default = 1
+--source col_null.inc
+
+--let $col_type = BIGINT
+--let $col_default = 9223372036854775807
+--source col_null.inc
+
+--echo ########################
+--echo # SET columns
+--echo ########################
+
+--source type_set.inc
+--let $col_type = SET('test1','test2','test3')
+--let $col_default = 'test2,test3'
+--source col_null.inc
+
+
+--echo ########################
+--echo # TEXT columns
+--echo ########################
+
+--source type_text.inc
+
+--let $col_default = ''
+
+--let $col_type = TEXT
+--source col_null.inc
+
+--let $col_type = TINYTEXT
+--source col_null.inc
+
+--let $col_type = MEDIUMTEXT
+--source col_null.inc
+
+--let $col_type = LONGTEXT
+--source col_null.inc
+
+
+--let $col_type =
+--let $col_default =
+--let $extra_col_opts =
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_opt_unsigned.test b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_unsigned.test
new file mode 100644
index 00000000..25cda84c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_unsigned.test
@@ -0,0 +1,74 @@
+--source include/have_rocksdb.inc
+
+#
+# UNSIGNED column attribute
+#
+
+--let $extra_col_opts = UNSIGNED
+
+--echo ########################
+--echo # Fixed point columns (NUMERIC, DECIMAL)
+--echo ########################
+
+--source type_fixed.inc
+
+CREATE TABLE t1 (
+ a DECIMAL UNSIGNED,
+ b NUMERIC UNSIGNED,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b) VALUES (1.0,-1.0);
+INSERT INTO t1 (a,b) VALUES (-100,100);
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+--echo ########################
+--echo # Floating point columns (FLOAT, DOUBLE)
+--echo ########################
+
+--source type_float.inc
+
+CREATE TABLE t1 (
+ a DOUBLE UNSIGNED,
+ b FLOAT UNSIGNED,
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b) VALUES (1.0,-1.0);
+INSERT INTO t1 (a,b) VALUES (-100,100);
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+--echo ########################
+--echo # INT columns
+--echo ########################
+
+--source type_int.inc
+
+CREATE TABLE t1 (
+ t TINYINT UNSIGNED,
+ s SMALLINT UNSIGNED,
+ m MEDIUMINT UNSIGNED,
+ i INT UNSIGNED,
+ b BIGINT UNSIGNED,
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (t,s,m,i,b) VALUES (255,65535,16777215,4294967295,18446744073709551615);
+INSERT INTO t1 (t,s,m,i,b) VALUES (-1,-1,-1,-1,-1);
+--sorted_result
+SELECT t,s,m,i,b FROM t1;
+
+DROP TABLE t1;
+
+--let $extra_col_opts =
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/col_opt_zerofill.test b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_zerofill.test
new file mode 100644
index 00000000..37982ae0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/col_opt_zerofill.test
@@ -0,0 +1,67 @@
+--source include/have_rocksdb.inc
+
+#
+# ZEROFILL column attribute
+#
+
+let $extra_col_opts = ZEROFILL;
+
+--echo ########################
+--echo # Fixed point columns (NUMERIC, DECIMAL)
+--echo ########################
+
+--source type_fixed.inc
+
+CREATE TABLE t1 (
+ a DECIMAL ZEROFILL,
+ b NUMERIC ZEROFILL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b) VALUES (1.1,1234);
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+--echo ########################
+--echo # Floating point columns (FLOAT, DOUBLE)
+--echo ########################
+
+--source type_float.inc
+
+CREATE TABLE t1 (
+ a DOUBLE ZEROFILL,
+ b FLOAT ZEROFILL,
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b) VALUES (1,1234.5);
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+--echo ########################
+--echo # INT columns
+--echo ########################
+
+--source type_int.inc
+
+CREATE TABLE t1 (
+ t TINYINT ZEROFILL,
+ s SMALLINT ZEROFILL,
+ m MEDIUMINT ZEROFILL,
+ i INT ZEROFILL,
+ b BIGINT ZEROFILL,
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (t,s,m,i,b) VALUES (1,10,100,1000,0);
+SELECT t,s,m,i,b FROM t1;
+DROP TABLE t1;
+
+--let $extra_col_opts =
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/collation-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/collation-master.opt
new file mode 100644
index 00000000..79e59163
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/collation-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/collation.test b/storage/rocksdb/mysql-test/rocksdb/t/collation.test
new file mode 100644
index 00000000..3b808bc3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/collation.test
@@ -0,0 +1,211 @@
+--source include/have_rocksdb.inc
+# MariaDB doesn't have server variables to check for GCC version, so the
+# following check is commented out:
+# --source include/have_fullregex.inc
+
+call mtr.add_suppression("Invalid pattern");
+
+# ci non-indexed column is allowed
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text) engine=rocksdb charset utf8;
+# ci indexed column is not allowed
+--error ER_UNSUPPORTED_COLLATION
+ALTER TABLE t1 ADD INDEX (value);
+DROP TABLE t1;
+
+# ci indexed column is not allowed
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value)) engine=rocksdb charset utf8;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value3(50))) engine=rocksdb charset utf8;
+# ci indexed column with rocksdb_strict_collation_check=OFF is allowed.
+SET GLOBAL rocksdb_strict_collation_check=0;
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value3(50))) engine=rocksdb charset utf8;
+DROP TABLE t1;
+SET GLOBAL rocksdb_strict_collation_check=1;
+
+# cs indexed column is allowed
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value2)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+
+# cs latin1_bin is allowed
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset latin1 collate latin1_bin;
+# THIS SHOULD FAIL BUT IT DOES NOT
+ALTER TABLE t1 collate=latin1_general_ci;
+DROP TABLE t1;
+
+# cs utf8_bin is allowed
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset utf8 collate utf8_bin;
+DROP TABLE t1;
+
+# cs mixed latin1_bin and utf8_bin is allowed
+CREATE TABLE t1 (id varchar(20) collate latin1_bin, value varchar(50) collate utf8_bin, value2 varchar(50) collate latin1_bin, value3 text, primary key (id), index(value, value2)) engine=rocksdb;
+DROP TABLE t1;
+
+# ci indexed column is not allowed unless table name is in exception list
+SET GLOBAL rocksdb_strict_collation_exceptions=t1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE t2 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test regex for exception list
+SET GLOBAL rocksdb_strict_collation_exceptions="t.*";
+CREATE TABLE t123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t123;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE s123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+SET GLOBAL rocksdb_strict_collation_exceptions=".t.*";
+CREATE TABLE xt123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE xt123;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE t123 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list with commas
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list with vertical bar
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*|t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra comma at the front
+SET GLOBAL rocksdb_strict_collation_exceptions=",s.*,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra vertical bar at the front
+SET GLOBAL rocksdb_strict_collation_exceptions="|s.*|t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra comma in the middle
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,,t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra vertical bar in the middle
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*||t.*";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra comma at the end
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*,t.*,";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and extra vertical bar at the end
+SET GLOBAL rocksdb_strict_collation_exceptions="s.*|t.*|";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test multiple entries in the list and tons of commas and vertical bars just for the fun of it
+SET GLOBAL rocksdb_strict_collation_exceptions="||||,,,,s.*,,|,,||,t.*,,|||,,,";
+CREATE TABLE s1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE s1;
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE u1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb charset utf8;
+
+# test allowing alters to create temporary tables
+SET GLOBAL rocksdb_strict_collation_exceptions='t1';
+CREATE TABLE t1 (id INT primary key, value varchar(50), index(value)) engine=rocksdb;
+ALTER TABLE t1 AUTO_INCREMENT=1;
+DROP TABLE t1;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE t2 (id INT primary key, value varchar(50), index(value)) engine=rocksdb;
+CREATE TABLE t2 (id INT primary key, value varchar(50)) engine=rocksdb;
+--error ER_UNSUPPORTED_COLLATION
+ALTER TABLE t2 ADD INDEX(value);
+DROP TABLE t2;
+
+
+# test invalid regex (missing end bracket)
+SET GLOBAL rocksdb_strict_collation_exceptions="[a-b";
+let SEARCH_FILE=$MYSQLTEST_VARDIR/log/mysqld.1.err;
+let SEARCH_PATTERN=Invalid pattern in strict_collation_exceptions: \[a-b;
+source include/search_pattern_in_file.inc;
+CREATE TABLE a (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+SET GLOBAL rocksdb_strict_collation_exceptions="[a-b]";
+CREATE TABLE a (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+CREATE TABLE b (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE c (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE a, b;
+
+call mtr.add_suppression("Invalid pattern in strict_collation_exceptions:");
+# test invalid regex (trailing escape)
+SET GLOBAL rocksdb_strict_collation_exceptions="abc\\";
+let SEARCH_PATTERN=Invalid pattern in strict_collation_exceptions: abc;
+source include/search_pattern_in_file.inc;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE abc (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+SET GLOBAL rocksdb_strict_collation_exceptions="abc";
+CREATE TABLE abc (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+--error ER_UNSUPPORTED_COLLATION
+CREATE TABLE abcd (id INT PRIMARY KEY, value varchar(50), index(value)) engine=rocksdb charset utf8;
+DROP TABLE abc;
+
+# test bad regex (null caused a crash) - Issue 493
+SET GLOBAL rocksdb_strict_collation_exceptions=null;
+
+# test for warnings instead of errors
+--let $_mysqld_option=--rocksdb_error_on_suboptimal_collation=0
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+
+SET GLOBAL rocksdb_strict_collation_check=1;
+
+# ci indexed column is not optimal, should emit a warning
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text, index(value)) engine=rocksdb charset utf8;
+DROP TABLE t1;
+
+# ci non-indexed column is allowed
+CREATE TABLE t1 (id INT primary key, value varchar(50), value2 varbinary(50), value3 text) engine=rocksdb charset utf8;
+# ci indexed column is not allowed, should emit a warning
+ALTER TABLE t1 ADD INDEX (value);
+DROP TABLE t1;
+
+# cs latin1_bin is allowed
+CREATE TABLE t1 (id varchar(20), value varchar(50), value2 varchar(50), value3 text, primary key (id), index(value, value2)) engine=rocksdb charset latin1 collate latin1_bin;
+# THIS SHOULD WARN BUT IT DOES NOT
+ALTER TABLE t1 collate=latin1_general_ci;
+DROP TABLE t1;
+
+# cleanup
+--source include/restart_mysqld.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/collation_exception-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/collation_exception-master.opt
new file mode 100644
index 00000000..13563edb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/collation_exception-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_strict_collation_check=ON
+--rocksdb_strict_collation_exceptions='r1.lol'
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/collation_exception.test b/storage/rocksdb/mysql-test/rocksdb/t/collation_exception.test
new file mode 100644
index 00000000..334b1bb2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/collation_exception.test
@@ -0,0 +1,29 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE `r1.lol` (
+ `c1` int(10) NOT NULL DEFAULT '0',
+ `c2` int(11) NOT NULL DEFAULT '0',
+ `c3` int(1) NOT NULL DEFAULT '0',
+ `c4` int(11) NOT NULL DEFAULT '0',
+ `c5` int(11) NOT NULL DEFAULT '0',
+ `c6` varchar(100) NOT NULL DEFAULT '',
+ `c7` varchar(100) NOT NULL DEFAULT '',
+ `c8` varchar(255) NOT NULL DEFAULT '',
+ `c9` int(10) NOT NULL DEFAULT '125',
+ `c10` int(10) NOT NULL DEFAULT '125',
+ `c11` text NOT NULL,
+ `c12` int(11) NOT NULL DEFAULT '0',
+ `c13` int(10) NOT NULL DEFAULT '0',
+ `c14` text NOT NULL,
+ `c15` blob NOT NULL,
+ `c16` int(11) NOT NULL DEFAULT '0',
+ `c17` int(11) NOT NULL DEFAULT '0',
+ `c18` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`c1`),
+ KEY i1 (`c4`),
+ KEY i2 (`c7`),
+ KEY i3 (`c2`)) ENGINE=RocksDB DEFAULT CHARSET=latin1;
+
+DROP INDEX i1 ON `r1.lol`;
+DROP TABLE `r1.lol`;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.cnf b/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.cnf
new file mode 100644
index 00000000..49ebd28c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.cnf
@@ -0,0 +1,4 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.test b/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.test
new file mode 100644
index 00000000..963f6c24
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/com_rpc_tx.test
@@ -0,0 +1,90 @@
+--source "include/have_rocksdb.inc"
+--source "include/have_log_bin.inc"
+# Don't run this with --rpc_protocol because it is doing its own work with
+# the RPC protocol
+--source "include/not_rpc_protocol.inc"
+
+#
+# This test was created because 2pc transactions were failing in MyRocks
+# when using detached sessions. The test generates two separate transactions
+# in two detached sessions and then attempts to commit them as simultaneously
+# as possible. This consistently showed the problem but succeeds after the
+# fix was put in place.
+
+CREATE DATABASE db_rpc;
+USE db_rpc;
+CREATE TABLE t1(pk INT PRIMARY KEY) ENGINE=rocksdb;
+
+SET GLOBAL rocksdb_enable_2pc=1;
+
+connect(con2,localhost,root,,);
+connection default;
+
+query_attrs_add rpc_role root;
+query_attrs_add rpc_db db_rpc;
+SET autocommit = 0;
+let $rpc_id1 = get_rpc_id();
+if ($rpc_id1 == "") {
+ echo "Fail: rpc_id not returned as expected";
+}
+
+SET autocommit = 0;
+let $rpc_id2 = get_rpc_id();
+if ($rpc_id2 == "") {
+ echo "Fail: rpc_id not returned as expected";
+}
+
+query_attrs_delete rpc_role;
+query_attrs_delete rpc_db;
+
+query_attrs_add rpc_id $rpc_id1;
+BEGIN;
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id2;
+BEGIN;
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id1;
+SELECT * from t1;
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id2;
+SELECT * from t1;
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id1;
+INSERT INTO t1 VALUES(1);
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id2;
+INSERT INTO t1 VALUES(2);
+query_attrs_delete rpc_id;
+
+query_attrs_add rpc_id $rpc_id1;
+send COMMIT;
+
+connection con2;
+query_attrs_add rpc_id $rpc_id2;
+send COMMIT;
+
+connection default;
+reap;
+query_attrs_delete rpc_id;
+
+connection con2;
+reap;
+query_attrs_delete rpc_id;
+
+connection default;
+disconnect con2;
+
+SELECT * from db_rpc.t1;
+
+disable_query_log;
+eval KILL $rpc_id1;
+eval KILL $rpc_id2;
+enable_query_log;
+
+DROP DATABASE db_rpc;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/commit_in_the_middle_ddl.test b/storage/rocksdb/mysql-test/rocksdb/t/commit_in_the_middle_ddl.test
new file mode 100644
index 00000000..02f45779
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/commit_in_the_middle_ddl.test
@@ -0,0 +1,27 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS a;
+--enable_warnings
+
+create table a (id int, value int, primary key (id) comment 'cf_a') engine=rocksdb;
+
+--disable_query_log
+let $i = 1;
+while ($i <= 100000) {
+ let $insert = INSERT INTO a VALUES($i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+set rocksdb_bulk_load=1;
+set rocksdb_commit_in_the_middle=1;
+alter table a add index v (value) COMMENT 'cf_a';
+set rocksdb_bulk_load=0;
+set rocksdb_commit_in_the_middle=0;
+select count(*) from a force index(primary);
+select count(*) from a force index(v);
+
+DROP TABLE a;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes-master.opt
new file mode 100644
index 00000000..3b4871f8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_debug_optimizer_n_rows=1000
+--rocksdb_records_in_range=50
+--rocksdb_compaction_sequential_deletes_count_sd=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes.test b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes.test
new file mode 100644
index 00000000..b61da676
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes.test
@@ -0,0 +1,88 @@
+--source include/not_windows.inc
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS r1;
+--enable_warnings
+
+create table r1 (
+ id1 int,
+ id2 int,
+ type int,
+ value varchar(100),
+ value2 int,
+ value3 int,
+ primary key (type, id1, id2),
+ index id1_type (id1, type, value2, value, id2)
+) engine=rocksdb collate latin1_bin;
+
+select 'loading data';
+
+--disable_query_log
+let $i=0;
+while ($i<1000)
+{
+ inc $i;
+ eval insert r1(id1, id2, type, value, value2, value3)
+ values($i,$i,$i, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',$i,$i);
+}
+--enable_query_log
+
+set global rocksdb_force_flush_memtable_now=1;
+optimize table r1;
+
+--echo Test 1: Do a bunch of updates without setting the compaction sysvar
+--echo Expect: no compaction
+let $window = 0;
+let $deletes = 0;
+let $file_size = 0;
+let $secondary_only = 0;
+let $primary = 1;
+let $no_more_deletes = 0;
+--source compact_deletes_test.inc
+
+--echo Test 2: Do a bunch of updates and set the compaction sysvar
+--echo Expect: compaction
+let $window = 1000;
+let $deletes = 990;
+let $file_size = 0;
+let $secondary_only = 0;
+let $primary = 1;
+let $no_more_deletes = 1;
+--source compact_deletes_test.inc
+
+--echo Test 3: Do a bunch of updates and set the compaction sysvar and a file size to something large
+--echo Expect: no compaction
+let $window = 1000;
+let $deletes = 1000;
+let $file_size = 1000000;
+let $secondary_only = 0;
+let $primary = 1;
+let $no_more_deletes = 0;
+--source compact_deletes_test.inc
+
+--echo Test 4: Do a bunch of secondary key updates and set the compaction sysvar
+--echo Expect: compaction
+let $window = 1000;
+let $deletes = 50;
+let $file_size = 0;
+let $secondary_only = 1;
+let $primary = 0;
+let $no_more_deletes = 1;
+--source compact_deletes_test.inc
+
+--echo Test 5: Do a bunch of secondary key updates and set the compaction sysvar,
+--echo and rocksdb_compaction_sequential_deletes_count_sd turned on
+--echo Expect: compaction
+let $window = 1000;
+let $deletes = 50;
+let $file_size = 0;
+let $secondary_only = 1;
+let $primary = 0;
+let $no_more_deletes = 1;
+SET @save_rocksdb_compaction_sequential_deletes_count_sd = @@global.rocksdb_compaction_sequential_deletes_count_sd;
+SET GLOBAL rocksdb_compaction_sequential_deletes_count_sd= ON;
+--source compact_deletes_test.inc
+SET GLOBAL rocksdb_compaction_sequential_deletes_count_sd= @save_rocksdb_compaction_sequential_deletes_count_sd;
+
+drop table r1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes_test.inc b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes_test.inc
new file mode 100644
index 00000000..0a0d6705
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/compact_deletes_test.inc
@@ -0,0 +1,72 @@
+# Usage:
+# let $window = <window size>;
+# let $deletes = <number of deletes per window>;
+# let $file_size = <min size of the file>;
+# --source compact_deletes_test.inc
+#
+
+let $save_rocksdb_compaction_sequential_deletes_window = `SELECT @@rocksdb_compaction_sequential_deletes_window`;
+eval set global rocksdb_compaction_sequential_deletes_window=$window;
+let $save_rocksdb_compaction_sequential_deletes = `SELECT @@rocksdb_compaction_sequential_deletes`;
+eval set global rocksdb_compaction_sequential_deletes= $deletes;
+let $save_rocksdb_compaction_sequential_deletes_file_size = `SELECT @@rocksdb_compaction_sequential_deletes_file_size`;
+eval set global rocksdb_compaction_sequential_deletes_file_size=$file_size;
+--disable_query_log
+let $i=0;
+while ($i<1000)
+{
+ inc $i;
+ if ($secondary_only)
+ {
+ eval update r1 set value2=value2+1 where id1=$i;
+ }
+ if ($primary)
+ {
+ eval update r1 set id2=id2+10000 where id1=500;
+ }
+}
+--enable_query_log
+set global rocksdb_force_flush_memtable_now=1;
+--sleep 1
+
+--disable_query_log
+let $wait_timeout= 300; # Override default 30 seconds with 300.
+let $wait_condition = select count(*) = 0
+ as c from information_schema.rocksdb_global_info
+ where TYPE = 'DDL_DROP_INDEX_ONGOING';
+--source include/wait_condition.inc
+--enable_query_log
+
+let NO_MORE_DELETES=$no_more_deletes;
+perl;
+ $num_retries=240;
+ $retry=0;
+ print "wait_for_delete: $ENV{no_more_deletes}\n";
+ while ($retry++ < $num_retries) {
+ $total_d=$total_e=0;
+ for $f (<$ENV{MYSQLTEST_VARDIR}/mysqld.1/data/#rocksdb/*.sst>) {
+ # excluding system cf
+ $filename= "$ENV{MARIAROCKS_SST_DUMP} --command=scan --output_hex --file=$f";
+ open(D, '-|', $filename) || die("Can't open file $filename: $!");
+ while (<D>) {
+ next unless /'(\d{8})/ and $1 >= 8;
+ $total_d++ if /: [07]/;
+ $total_e++ if /: 1/;
+ }
+ close D;
+ }
+ last if $total_e and not ($total_d and $ENV{no_more_deletes});
+ sleep 1;
+ }
+
+ unless ($total_e) {
+ print "No records in the database\n";
+ exit;
+ }
+
+ print $total_d ? "There are deletes left\n" : "No more deletes left\n";
+EOF
+
+eval SET GLOBAL rocksdb_compaction_sequential_deletes= $save_rocksdb_compaction_sequential_deletes;
+eval SET GLOBAL rocksdb_compaction_sequential_deletes_file_size= $save_rocksdb_compaction_sequential_deletes_file_size;
+eval SET GLOBAL rocksdb_compaction_sequential_deletes_window= $save_rocksdb_compaction_sequential_deletes_window;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/compression_zstd.test b/storage/rocksdb/mysql-test/rocksdb/t/compression_zstd.test
new file mode 100644
index 00000000..c2216f76
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/compression_zstd.test
@@ -0,0 +1,14 @@
+--source include/have_rocksdb.inc
+
+let $no_zstd=`select @@rocksdb_supported_compression_types NOT LIKE '%ZSTD%'`;
+
+if ($no_zstd)
+{
+ -- Skip Requires RocksDB to be built with ZStandard Compression support
+}
+
+--let $restart_parameters=--rocksdb_default_cf_options=compression_per_level=kZSTDNotFinalCompression;compression_opts=-14:4:0;
+--source include/restart_mysqld.inc
+
+create table t (id int primary key) engine=rocksdb;
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/concurrent_alter.test b/storage/rocksdb/mysql-test/rocksdb/t/concurrent_alter.test
new file mode 100644
index 00000000..aee65383
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/concurrent_alter.test
@@ -0,0 +1,39 @@
+--source include/have_rocksdb.inc
+
+# Bash
+--source include/not_windows.inc
+
+#
+# Generate concurrent requests to alter a table using mysqlslap
+#
+
+--disable_warnings
+DROP DATABASE IF EXISTS mysqlslap;
+--enable_warnings
+
+CREATE DATABASE mysqlslap;
+
+use mysqlslap;
+
+CREATE TABLE a1 (a int, b int) ENGINE=ROCKSDB;
+INSERT INTO a1 VALUES (1, 1);
+
+--write_file $MYSQL_TMP_DIR/concurrent_alter.sh
+$MYSQL_SLAP --silent --delimiter=";" --query="alter table a1 add index bx(b); alter table a1 drop index bx" --concurrency=1 --iterations=25 &
+$MYSQL_SLAP --silent --delimiter=";" --query="alter table a1 add index ax(a); alter table a1 drop index ax" --concurrency=1 --iterations=25 &
+sleep 2
+$MYSQL_SLAP --silent --delimiter=";" --query="select * from a1 where a=1" --concurrency=16 --iterations=1000 &
+$MYSQL_SLAP --silent --delimiter=";" --query="select * from a1 where b=1" --concurrency=16 --iterations=1000
+sleep 2
+$MYSQL_SLAP --silent --delimiter=";" --query="select * from a1 where a=1" --concurrency=16 --iterations=1000 &
+$MYSQL_SLAP --silent --delimiter=";" --query="select * from a1 where b=1" --concurrency=16 --iterations=1000
+wait
+EOF
+
+--exec sh $MYSQL_TMP_DIR/concurrent_alter.sh
+
+let $server_charset=`select @@character_set_server`;
+--replace_result $server_charset DEFAULT_CHARSET
+SHOW CREATE TABLE a1;
+
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.opt b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.opt
new file mode 100644
index 00000000..418e4c3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true \ No newline at end of file
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.test b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.test
new file mode 100644
index 00000000..4dfa5abb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_read_committed.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = READ COMMITTED;
+
+--source consistent_snapshot.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.opt b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.opt
new file mode 100644
index 00000000..418e4c3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true \ No newline at end of file
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.test b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.test
new file mode 100644
index 00000000..c9f28dbc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_repeatable_read.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = REPEATABLE READ;
+
+--source consistent_snapshot.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.opt b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.opt
new file mode 100644
index 00000000..418e4c3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true \ No newline at end of file
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.test b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.test
new file mode 100644
index 00000000..57b45050
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/cons_snapshot_serializable.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = SERIALIZABLE;
+
+--source consistent_snapshot.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/consistent_snapshot.inc b/storage/rocksdb/mysql-test/rocksdb/t/consistent_snapshot.inc
new file mode 100644
index 00000000..ca7510b1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/consistent_snapshot.inc
@@ -0,0 +1,136 @@
+#
+# TRANSACTION WITH CONSISTENT SNAPSHOT
+#
+
+--enable_connect_log
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=ROCKSDB;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+
+# While a consistent snapshot transaction is executed,
+# no external inserts should be visible to the transaction.
+# But it should only work this way for REPEATABLE-READ and SERIALIZABLE
+
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+
+connection con1;
+COMMIT;
+
+# verifying snapshot is released after finishing transaction
+connection con2;
+select * from information_schema.rocksdb_dbstats where stat_type='DB_NUM_SNAPSHOTS';
+
+connection con1;
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+
+connection con2;
+INSERT INTO t1 (a) VALUES (1);
+
+connection con1;
+--echo # If consistent read works on this isolation level ($trx_isolation), the following SELECT should not return the value we inserted (1)
+SELECT a FROM t1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+CREATE TABLE r1 (id int primary key, value int, value2 int) engine=ROCKSDB;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+insert into r1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4);
+
+BEGIN;
+
+connection con2;
+INSERT INTO r1 values (5,5,5);
+
+connection con1;
+SELECT * FROM r1; # 5
+
+connection con2;
+INSERT INTO r1 values (6,6,6);
+
+connection con1;
+SELECT * FROM r1; # 5
+COMMIT;
+SELECT * FROM r1; # 6
+
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+
+connection con2;
+INSERT INTO r1 values (7,7,7);
+
+connection con1;
+SELECT * FROM r1; # 6
+
+connection con2;
+INSERT INTO r1 values (8,8,8);
+
+connection con1;
+SELECT * FROM r1; # 6
+COMMIT;
+SELECT * FROM r1; # 8
+
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+
+connection con2;
+INSERT INTO r1 values (9,9,9);
+
+connection con1;
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+
+connection con2;
+INSERT INTO r1 values (10,10,10);
+
+connection con1;
+SELECT * FROM r1; # 9
+
+--error 0,ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--echo ERROR: $mysql_errno
+# Succeeds with Read Committed, Fails with Repeatable Read
+--error 0,ER_UPDATES_WITH_CONSISTENT_SNAPSHOT
+INSERT INTO r1 values (11,11,11);
+--echo ERROR: $mysql_errno
+SELECT * FROM r1; # self changes should be visible
+
+
+drop table r1;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
+
+--source include/wait_until_count_sessions.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/corrupted_data_reads_debug.test b/storage/rocksdb/mysql-test/rocksdb/t/corrupted_data_reads_debug.test
new file mode 100644
index 00000000..eca0830c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/corrupted_data_reads_debug.test
@@ -0,0 +1,80 @@
+--source include/have_rocksdb.inc
+
+--echo #
+--echo # Test how MyRocks handles reading corrupted data from disk.
+--echo # Data corruption is simulated at source-code level.
+--echo #
+
+--source include/have_debug.inc
+
+
+--echo #
+--echo # A test for case when data in the table *record* is longer
+--echo # than table DDL expects it to be
+--echo #
+create table t1 (
+ pk int not null primary key,
+ col1 varchar(10)
+) engine=rocksdb;
+
+insert into t1 values (1,1),(2,2),(3,3);
+
+select * from t1;
+
+set @tmp1=@@rocksdb_verify_row_debug_checksums;
+set rocksdb_verify_row_debug_checksums=1;
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read1";
+--error ER_GET_ERRMSG
+select * from t1 where pk=1;
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read1";
+set rocksdb_verify_row_debug_checksums=@tmp1;
+
+select * from t1 where pk=1;
+
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read2";
+--error ER_GET_ERRMSG
+select * from t1 where pk=1;
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read2";
+
+set session debug_dbug= "+d,myrocks_simulate_bad_row_read3";
+--error ER_GET_ERRMSG
+select * from t1 where pk=1;
+set session debug_dbug= "-d,myrocks_simulate_bad_row_read3";
+
+insert into t1 values(4,'0123456789');
+select * from t1;
+drop table t1;
+
+--echo #
+--echo # A test for case when index data is longer than table DDL
+--echo # expects it to be
+--echo #
+
+create table t2 (
+ pk varchar(4) not null primary key,
+ col1 int not null
+) engine=rocksdb collate latin1_bin;
+
+insert into t2 values ('ABCD',1);
+select * from t2;
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_read1";
+--error ER_GET_ERRMSG
+select * from t2;
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_read1";
+
+drop table t2;
+
+create table t2 (
+ pk varchar(4) not null primary key,
+ col1 int not null
+) engine=rocksdb;
+
+insert into t2 values ('ABCD',1);
+
+select * from t2;
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_read1";
+--error ER_GET_ERRMSG
+select * from t2;
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_read1";
+
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/covered_unpack_info_format.test b/storage/rocksdb/mysql-test/rocksdb/t/covered_unpack_info_format.test
new file mode 100644
index 00000000..ccd91a61
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/covered_unpack_info_format.test
@@ -0,0 +1,79 @@
+# Disable until covered unpack_info format enabled in prod
+--source include/have_debug.inc
+--source include/not_debug.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+#
+# Normal case
+#
+
+CREATE TABLE t1 (
+ id INT,
+ fake_id INT,
+ bigfield VARCHAR(4096),
+ PRIMARY KEY (id),
+ KEY bf (bigfield(32)),
+ KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+ (8, 1008, REPEAT('b', 8)),
+ (24, 1024, REPEAT('c', 24)),
+ (31, 1031, REPEAT('d', 31)),
+ (32, 1032, REPEAT('x', 32)),
+ (33, 1033, REPEAT('y', 33)),
+ (128, 1128, REPEAT('z', 128));
+
+SELECT * FROM t1;
+
+--let $prefix_index_check_title= Eligible for optimization, access via fake_id only
+--let $prefix_index_check_read_avoided_delta= 2
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1031
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Not eligible for optimization, access via fake_id of big row.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1033
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DROP TABLE t1;
+
+#
+# Create same table with older format to test compatibility
+#
+
+set session debug= '+d,MYROCKS_NO_COVERED_BITMAP_FORMAT';
+CREATE TABLE t1 (
+ id INT,
+ fake_id INT,
+ bigfield VARCHAR(4096),
+ PRIMARY KEY (id),
+ KEY bf (bigfield(32)),
+ KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+set session debug= '-d,MYROCKS_NO_COVERED_BITMAP_FORMAT';
+
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+ (8, 1008, REPEAT('b', 8)),
+ (24, 1024, REPEAT('c', 24)),
+ (31, 1031, REPEAT('d', 31)),
+ (32, 1032, REPEAT('x', 32)),
+ (33, 1033, REPEAT('y', 33)),
+ (128, 1128, REPEAT('z', 128));
+
+SELECT * FROM t1;
+
+--let $prefix_index_check_title= No longer eligible for optimization since no covered bitmap was stored.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1031
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Not eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1033
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table-master.opt
new file mode 100644
index 00000000..ce274af3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table-master.opt
@@ -0,0 +1 @@
+--block_create_no_primary_key=TRUE
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table.test b/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table.test
new file mode 100644
index 00000000..3ef35cb2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/create_no_primary_key_table.test
@@ -0,0 +1,63 @@
+USE mysql;
+CREATE TABLE mysql_table (a INT) ENGINE=ROCKSDB;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE test.mysql_table (a INT) ENGINE=ROCKSDB;
+USE test;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE mysql_table (a INT) ENGINE=ROCKSDB;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE IF NOT EXISTS mysql_table_2 (a INT) ENGINE=ROCKSDB;
+-- error ER_TABLE_MUST_HAVE_COLUMNS
+CREATE TABLE mysql_table_no_cols ENGINE=ROCKSDB;
+CREATE TABLE mysql.mysql_table_2 (a INT) ENGINE=ROCKSDB;
+
+CREATE TABLE mysql_primkey (a INT PRIMARY KEY, b INT, c INT, d INT, INDEX (c)) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey DROP b, DROP a, ADD (f INT PRIMARY KEY);
+-- error ER_BLOCK_NO_PRIMARY_KEY
+ALTER TABLE mysql_primkey DROP PRIMARY KEY;
+
+CREATE TABLE mysql_primkey2 (a INT PRIMARY KEY, b INT, c INT) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey2 DROP b;
+ALTER TABLE mysql_primkey2 ADD (b INT);
+-- error ER_BLOCK_NO_PRIMARY_KEY
+ALTER TABLE mysql_primkey2 DROP c, DROP A;
+
+CREATE TABLE mysql_primkey3 (a INT PRIMARY KEY, b INT, c INT, INDEX indexonb (b), INDEX indexonc (c)) ENGINE=ROCKSDB;
+ALTER TABLE mysql_primkey3 DROP INDEX indexonb;
+ALTER TABLE mysql_primkey3 DROP c;
+ALTER TABLE mysql_primkey3 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+
+CREATE TABLE mysql_primkey4(a INT, b INT, PRIMARY KEY(a), INDEX si (a, b)) ENGINE=ROCKSDB;
+DROP INDEX si ON mysql_primkey4;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+DROP INDEX `PRIMARY` ON mysql_primkey4;
+
+ALTER TABLE mysql.mysql_table ADD PRIMARY KEY (a);
+ALTER TABLE mysql.mysql_table DROP PRIMARY KEY;
+
+SET default_storage_engine=ROCKSDB;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE mysql_noeng(a INT, b INT);
+
+# Disable no_engine_substitution
+SET sql_mode="";
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE mysql_noeng_sub(a INT, b INT) ENGINE=BOGUS_ENGINE;
+
+CREATE TABLE mysql_primkey5 LIKE mysql_primkey;
+
+SET @@global.block_create_no_primary_key = false;
+CREATE TABLE mysql_no_primkey (a INT) ENGINE=ROCKSDB;
+SET @@global.block_create_no_primary_key = true;
+-- error ER_BLOCK_NO_PRIMARY_KEY
+CREATE TABLE mysql_block_no_primkey LIKE mysql_no_primkey;
+
+DROP TABLE mysql_primkey;
+DROP TABLE mysql_primkey2;
+DROP TABLE mysql_primkey3;
+DROP TABLE mysql_primkey4;
+DROP TABLE mysql_primkey5;
+DROP TABLE mysql_no_primkey;
+USE mysql;
+DROP TABLE mysql_table;
+DROP TABLE mysql_table_2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/create_table.test b/storage/rocksdb/mysql-test/rocksdb/t/create_table.test
new file mode 100644
index 00000000..4fffe749
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/create_table.test
@@ -0,0 +1,192 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic CREATE TABLE statements
+#
+
+#############################################
+# TODO:
+# A part of the test is currently disabled
+# because temporary tables are not supported
+#############################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+# Simple create table with minimal table options
+# which are defined in have_engine.inc
+# (default empty) plus ENGINE=
+
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+
+# IF NOT EXISTS
+CREATE TABLE IF NOT EXISTS t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+
+# CREATE .. LIKE
+
+CREATE TABLE t2 LIKE t1;
+SHOW CREATE TABLE t2;
+
+--error ER_ILLEGAL_HA_CREATE_OPTION
+CREATE TEMPORARY TABLE t2 (a INT PRIMARY KEY) ENGINE=rocksdb;
+
+--disable_parsing
+
+DROP TABLE t2;
+
+CREATE TEMPORARY TABLE t2 LIKE t1;
+
+SHOW CREATE TABLE t2;
+DROP TEMPORARY TABLE t2;
+
+--enable_parsing
+
+DROP TABLE t2;
+
+DROP TABLE IF EXISTS t1;
+
+# CREATE .. AS SELECT
+
+# Use the engine as default
+
+SET default_storage_engine = rocksdb;
+
+CREATE TABLE t1 (a INT PRIMARY KEY);
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT PRIMARY KEY) AS SELECT 1 AS a UNION SELECT 2 AS a;
+SHOW CREATE TABLE t1;
+--sorted_result
+SELECT * FROM t1;
+
+# Just to add FLUSH LOGS into the mix while we are in the most common test
+FLUSH LOGS;
+
+DROP TABLE IF EXISTS t1;
+
+# CREATE TABLE with MAX_INDEXES (64) keys and no primary key
+# MyRocks adds a hidden primary key, so make sure we don't break anything
+CREATE TABLE t1(c1 INT,c2 INT,c3 INT,c4 INT,c5 INT,c6 INT,c7 INT,c8 INT,c9 INT,
+ c10 INT,c11 INT,c12 INT,c13 INT,c14 INT,c15 INT,c16 INT,c17 INT,
+ c18 INT,c19 INT,c20 INT,c21 INT,c22 INT,c23 INT,c24 INT,c25 INT,
+ c26 INT,c27 INT,c28 INT,c29 INT,c30 INT,c31 INT,c32 INT,c33 INT,
+ c34 INT,c35 INT,c36 INT,c37 INT,c38 INT,c39 INT,c40 INT,c41 INT,
+ c42 INT,c43 INT,c44 INT,c45 INT,c46 INT,c47 INT,c48 INT,c49 INT,
+ c50 INT,c51 INT,c52 INT,c53 INT,c54 INT,c55 INT,c56 INT,c57 INT,
+ c58 INT,c59 INT,c60 INT,c61 INT,c62 INT,c63 INT,c64 INT,c65 INT,
+ c66 INT,c67 INT,c68 INT,c69 INT,c70 INT,c71 INT,c72 INT,c73 INT,
+ c74 INT,c75 INT,c76 INT,c77 INT,c78 INT,c79 INT,c80 INT,c81 INT,
+ c82 INT,c83 INT,c84 INT,c85 INT,c86 INT,c87 INT,c88 INT,c89 INT,
+ c90 INT,c91 INT,c92 INT,c93 INT,c94 INT,c95 INT,c96 INT,c97 INT,
+ c98 INT,c99 INT,c100 INT,c101 INT,c102 INT,c103 INT,c104 INT,
+ c105 INT,c106 INT,c107 INT,c108 INT,c109 INT,c110 INT,c111 INT,
+ c112 INT,c113 INT,c114 INT,c115 INT,c116 INT,c117 INT,c118 INT,
+ c119 INT,c120 INT,c121 INT,c122 INT,c123 INT,c124 INT,c125 INT,
+ c126 INT,c127 INT,c128 INT,c129 INT,c130 INT,c131 INT,c132 INT,
+ c133 INT,c134 INT,c135 INT,c136 INT,c137 INT,c138 INT,c139 INT,
+ c140 INT,c141 INT,c142 INT,c143 INT,c144 INT,c145 INT,c146 INT,
+ c147 INT,c148 INT,c149 INT,c150 INT,c151 INT,c152 INT,c153 INT,
+ c154 INT,c155 INT,c156 INT,c157 INT,c158 INT,c159 INT,c160 INT,
+ c161 INT,c162 INT,c163 INT,c164 INT,c165 INT,c166 INT,c167 INT,
+ c168 INT,c169 INT,c170 INT,c171 INT,c172 INT,c173 INT,c174 INT,
+ c175 INT,c176 INT,c177 INT,c178 INT,c179 INT,c180 INT,c181 INT,
+ c182 INT,c183 INT,c184 INT,c185 INT,c186 INT,c187 INT,c188 INT,
+ c189 INT,c190 INT,c191 INT,c192 INT,c193 INT,c194 INT,c195 INT,
+ c196 INT,c197 INT,c198 INT,c199 INT,c200 INT,c201 INT,c202 INT,
+ c203 INT,c204 INT,c205 INT,c206 INT,c207 INT,c208 INT,c209 INT,
+ c210 INT,c211 INT,c212 INT,c213 INT,c214 INT,c215 INT,c216 INT,
+ c217 INT,c218 INT,c219 INT,c220 INT,c221 INT,c222 INT,c223 INT,
+ c224 INT,c225 INT,c226 INT,c227 INT,c228 INT,c229 INT,c230 INT,
+ c231 INT,c232 INT,c233 INT,c234 INT,c235 INT,c236 INT,c237 INT,
+ c238 INT,c239 INT,c240 INT,c241 INT,c242 INT,c243 INT,c244 INT,
+ c245 INT,c246 INT,c247 INT,c248 INT,c249 INT,c250 INT,c251 INT,
+ c252 INT,c253 INT,c254 INT,c255 INT,c256 INT,c257 INT,c258 INT,
+ c259 INT,c260 INT,c261 INT,c262 INT,c263 INT,c264 INT,c265 INT,
+ c266 INT,c267 INT,c268 INT,c269 INT,c270 INT,c271 INT,c272 INT,
+ c273 INT,c274 INT,c275 INT,c276 INT,c277 INT,c278 INT,c279 INT,
+ c280 INT,c281 INT,c282 INT,c283 INT,c284 INT,c285 INT,c286 INT,
+ c287 INT,c288 INT,c289 INT,c290 INT,c291 INT,c292 INT,c293 INT,
+ c294 INT,c295 INT,c296 INT,c297 INT,c298 INT,c299 INT,c300 INT,
+ c301 INT,c302 INT,c303 INT,c304 INT,c305 INT,c306 INT,c307 INT,
+ c308 INT,c309 INT,c310 INT,c311 INT,c312 INT,c313 INT,c314 INT,
+ c315 INT,c316 INT,c317 INT,c318 INT,c319 INT,c320 INT,c321 INT,
+ c322 INT,c323 INT,c324 INT,c325 INT,c326 INT,c327 INT,c328 INT,
+ c329 INT,c330 INT,c331 INT,c332 INT,c333 INT,c334 INT,c335 INT,
+ c336 INT,c337 INT,c338 INT,c339 INT,c340 INT,c341 INT,c342 INT,
+ c343 INT,c344 INT,c345 INT,c346 INT,c347 INT,c348 INT,c349 INT,
+ c350 INT,c351 INT,c352 INT,c353 INT,c354 INT,c355 INT,c356 INT,
+ c357 INT,c358 INT,c359 INT,c360 INT,c361 INT,c362 INT,c363 INT,
+ c364 INT,c365 INT,c366 INT,c367 INT,c368 INT,c369 INT,c370 INT,
+ c371 INT,c372 INT,c373 INT,c374 INT,c375 INT,c376 INT,c377 INT,
+ c378 INT,c379 INT,c380 INT,c381 INT,c382 INT,c383 INT,c384 INT,
+ c385 INT,c386 INT,c387 INT,c388 INT,c389 INT,c390 INT,c391 INT,
+ c392 INT,c393 INT,c394 INT,c395 INT,c396 INT,c397 INT,c398 INT,
+ c399 INT,c400 INT,c401 INT,c402 INT,c403 INT,c404 INT,c405 INT,
+ c406 INT,c407 INT,c408 INT,c409 INT,c410 INT,c411 INT,c412 INT,
+ c413 INT,c414 INT,c415 INT,c416 INT,c417 INT,c418 INT,c419 INT,
+ c420 INT,c421 INT,c422 INT,c423 INT,c424 INT,c425 INT,c426 INT,
+ c427 INT,c428 INT,c429 INT,c430 INT,c431 INT,c432 INT,c433 INT,
+ c434 INT,c435 INT,c436 INT,c437 INT,c438 INT,c439 INT,c440 INT,
+ c441 INT,c442 INT,c443 INT,c444 INT,c445 INT,c446 INT,c447 INT,
+ c448 INT,
+ KEY (c1,c2,c3,c4,c5,c6,c7),KEY (c8,c9,c10,c11,c12,c13,c14),
+ KEY (c15,c16,c17,c18,c19,c20,c21),KEY (c22,c23,c24,c25,c26,c27,c28),
+ KEY (c29,c30,c31,c32,c33,c34,c35),KEY (c36,c37,c38,c39,c40,c41,c42),
+ KEY (c43,c44,c45,c46,c47,c48,c49),KEY (c50,c51,c52,c53,c54,c55,c56),
+ KEY (c57,c58,c59,c60,c61,c62,c63),KEY (c64,c65,c66,c67,c68,c69,c70),
+ KEY (c71,c72,c73,c74,c75,c76,c77),KEY (c78,c79,c80,c81,c82,c83,c84),
+ KEY (c85,c86,c87,c88,c89,c90,c91),KEY (c92,c93,c94,c95,c96,c97,c98),
+ KEY (c99,c100,c101,c102,c103,c104,c105),
+ KEY (c106,c107,c108,c109,c110,c111,c112),
+ KEY (c113,c114,c115,c116,c117,c118,c119),
+ KEY (c120,c121,c122,c123,c124,c125,c126),
+ KEY (c127,c128,c129,c130,c131,c132,c133),
+ KEY (c134,c135,c136,c137,c138,c139,c140),
+ KEY (c141,c142,c143,c144,c145,c146,c147),
+ KEY (c148,c149,c150,c151,c152,c153,c154),
+ KEY (c155,c156,c157,c158,c159,c160,c161),
+ KEY (c162,c163,c164,c165,c166,c167,c168),
+ KEY (c169,c170,c171,c172,c173,c174,c175),
+ KEY (c176,c177,c178,c179,c180,c181,c182),
+ KEY (c183,c184,c185,c186,c187,c188,c189),
+ KEY (c190,c191,c192,c193,c194,c195,c196),
+ KEY (c197,c198,c199,c200,c201,c202,c203),
+ KEY (c204,c205,c206,c207,c208,c209,c210),
+ KEY (c211,c212,c213,c214,c215,c216,c217),
+ KEY (c218,c219,c220,c221,c222,c223,c224),
+ KEY (c225,c226,c227,c228,c229,c230,c231),
+ KEY (c232,c233,c234,c235,c236,c237,c238),
+ KEY (c239,c240,c241,c242,c243,c244,c245),
+ KEY (c246,c247,c248,c249,c250,c251,c252),
+ KEY (c253,c254,c255,c256,c257,c258,c259),
+ KEY (c260,c261,c262,c263,c264,c265,c266),
+ KEY (c267,c268,c269,c270,c271,c272,c273),
+ KEY (c274,c275,c276,c277,c278,c279,c280),
+ KEY (c281,c282,c283,c284,c285,c286,c287),
+ KEY (c288,c289,c290,c291,c292,c293,c294),
+ KEY (c295,c296,c297,c298,c299,c300,c301),
+ KEY (c302,c303,c304,c305,c306,c307,c308),
+ KEY (c309,c310,c311,c312,c313,c314,c315),
+ KEY (c316,c317,c318,c319,c320,c321,c322),
+ KEY (c323,c324,c325,c326,c327,c328,c329),
+ KEY (c330,c331,c332,c333,c334,c335,c336),
+ KEY (c337,c338,c339,c340,c341,c342,c343),
+ KEY (c344,c345,c346,c347,c348,c349,c350),
+ KEY (c351,c352,c353,c354,c355,c356,c357),
+ KEY (c358,c359,c360,c361,c362,c363,c364),
+ KEY (c365,c366,c367,c368,c369,c370,c371),
+ KEY (c372,c373,c374,c375,c376,c377,c378),
+ KEY (c379,c380,c381,c382,c383,c384,c385),
+ KEY (c386,c387,c388,c389,c390,c391,c392),
+ KEY (c393,c394,c395,c396,c397,c398,c399),
+ KEY (c400,c401,c402,c403,c404,c405,c406),
+ KEY (c407,c408,c409,c410,c411,c412,c413),
+ KEY (c414,c415,c416,c417,c418,c419,c420),
+ KEY (c421,c422,c423,c424,c425,c426,c427),
+ KEY (c428,c429,c430,c431,c432,c433,c434),
+ KEY (c435,c436,c437,c438,c439,c440,c441),
+ KEY (c442,c443,c444,c445,c446,c447,c448));
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ddl_high_priority.test b/storage/rocksdb/mysql-test/rocksdb/t/ddl_high_priority.test
new file mode 100644
index 00000000..01bb5cfa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ddl_high_priority.test
@@ -0,0 +1,18 @@
+# Test high priority DDL
+# There are two modes of high priority DDL:
+# 1. Through the system variable high_priority_ddl
+# 2. Through syntax: CREATE/ALTER/DROP/OPTIMIZE HIGH_PRIORITY ...
+
+--source include/have_rocksdb.inc
+
+--echo ##
+--echo ## Using the system variable high_priority_ddl"
+--echo ##
+--let $use_sys_var = 1;
+--source include/ddl_high_priority.inc
+
+--echo ##
+--echo ## Using HIGH_PRIORITY syntax
+--echo ##
+--let $use_sys_var = 0;
+--source include/ddl_high_priority.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/deadlock.test b/storage/rocksdb/mysql-test/rocksdb/t/deadlock.test
new file mode 100644
index 00000000..656be9eb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/deadlock.test
@@ -0,0 +1,44 @@
+--echo #
+--echo # Validate that deadlock errors don't occur with a high level of concurrency
+--echo #
+
+--source include/have_rocksdb.inc
+--source include/big_test.inc
+
+--echo # Disable for valgrind because this takes too long
+--source include/not_valgrind.inc
+
+--disable_warnings
+DROP DATABASE IF EXISTS mysqlslap;
+--enable_warnings
+
+CREATE DATABASE mysqlslap;
+USE mysqlslap;
+CREATE TABLE t1(id1 BIGINT, id2 BIGINT, count INT, PRIMARY KEY(id1, id2), KEY(id2)) ENGINE=rocksdb;
+CREATE TABLE t1rev(id1 BIGINT, id2 BIGINT, count INT, PRIMARY KEY(id1, id2) COMMENT "rev:cf2", KEY(id2) COMMENT "rev:cf2") ENGINE=rocksdb;
+
+SET @save = @@global.rocksdb_lock_wait_timeout;
+SET GLOBAL rocksdb_lock_wait_timeout = 60;
+
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="INSERT INTO t1 VALUES(1, 1, 1) ON DUPLICATE KEY UPDATE count=count+1"
+SELECT count from t1;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1 SET count=count+1 WHERE id1=1 AND id2=1"
+SELECT count from t1;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1 SET count=count+1 WHERE id2=1"
+SELECT count from t1;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1 SET count=count+1"
+SELECT count from t1;
+
+# Same tests on a table with reverse orderings
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="INSERT INTO t1rev VALUES(1, 1, 1) ON DUPLICATE KEY UPDATE count=count+1"
+SELECT count from t1rev;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1rev SET count=count+1 WHERE id1=1 AND id2=1"
+SELECT count from t1rev;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1rev SET count=count+1 WHERE id2=1"
+SELECT count from t1rev;
+--exec $MYSQL_SLAP --silent --concurrency=50 --number-of-queries=50000 --query="UPDATE t1rev SET count=count+1"
+SELECT count from t1rev;
+
+SET GLOBAL rocksdb_lock_wait_timeout = @save;
+
+DROP DATABASE mysqlslap;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/deadlock_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/deadlock_stats.test
new file mode 100644
index 00000000..a9b30a42
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/deadlock_stats.test
@@ -0,0 +1,3 @@
+let $engine=rocksdb;
+
+--source include/deadlock_stats.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/deadlock_tracking.test b/storage/rocksdb/mysql-test/rocksdb/t/deadlock_tracking.test
new file mode 100644
index 00000000..8ceebef8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/deadlock_tracking.test
@@ -0,0 +1,185 @@
+set @prior_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_deadlock_detect = @@rocksdb_deadlock_detect;
+set @prior_max_latest_deadlocks = @@rocksdb_max_latest_deadlocks;
+set global rocksdb_deadlock_detect = on;
+set global rocksdb_lock_wait_timeout = 10000;
+--echo # Clears deadlock buffer of any prior deadlocks.
+set global rocksdb_max_latest_deadlocks = 0;
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+let $engine = rocksdb;
+
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+let $con1= `SELECT CONNECTION_ID()`;
+
+connect (con2,localhost,root,,);
+let $con2= `SELECT CONNECTION_ID()`;
+
+connect (con3,localhost,root,,);
+let $con3= `SELECT CONNECTION_ID()`;
+
+connection default;
+eval create table t (i int primary key) engine=$engine;
+insert into t values (1), (2), (3);
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+
+echo Deadlock #1;
+--source include/simple_deadlock.inc
+connection default;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+
+echo Deadlock #2;
+--source include/simple_deadlock.inc
+connection default;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+set global rocksdb_max_latest_deadlocks = 10;
+
+echo Deadlock #3;
+--source include/simple_deadlock.inc
+connection default;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+set global rocksdb_max_latest_deadlocks = 1;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+
+connection con3;
+set rocksdb_deadlock_detect_depth = 2;
+
+echo Deadlock #4;
+connection con1;
+begin;
+select * from t where i=1 for update;
+
+connection con2;
+begin;
+select * from t where i=2 for update;
+
+connection con3;
+begin;
+select * from t where i=3 for update;
+
+connection con1;
+send select * from t where i=2 for update;
+
+connection con2;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con1 and waiting_key != "";
+--source include/wait_condition.inc
+
+send select * from t where i=3 for update;
+
+connection con3;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con2 and waiting_key != "";
+--source include/wait_condition.inc
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_deadlocks';
+--error ER_LOCK_DEADLOCK
+select * from t where i=1 for update;
+select case when variable_value-@a = 1 then 'true' else 'false' end as deadlocks from information_schema.global_status where variable_name='rocksdb_row_lock_deadlocks';
+rollback;
+
+connection con2;
+reap;
+rollback;
+
+connection con1;
+reap;
+rollback;
+
+connection default;
+set global rocksdb_max_latest_deadlocks = 5;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+
+echo Deadlock #5;
+connection con1;
+begin;
+select * from t where i=1 for update;
+
+connection con2;
+begin;
+select * from t where i=2 for update;
+
+connection con3;
+begin;
+select * from t where i=3 lock in share mode;
+
+connection con1;
+select * from t where i=100 for update;
+select * from t where i=101 for update;
+send select * from t where i=2 for update;
+
+connection con2;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con1 and waiting_key != "";
+--source include/wait_condition.inc
+
+select * from t where i=3 lock in share mode;
+select * from t where i=200 for update;
+select * from t where i=201 for update;
+
+--error ER_LOCK_DEADLOCK
+select * from t where i=1 lock in share mode;
+rollback;
+
+connection con1;
+reap;
+rollback;
+
+connection con3;
+rollback;
+
+connection default;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+
+echo Deadlock #6;
+connection con1;
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5);
+begin;
+update t1 set value=value+100 where id=1;
+update t1 set value=value+100 where id=2;
+
+connection con2;
+begin;
+update t1 set value=value+200 where id=3;
+
+connection con1;
+send update t1 set value=value+100 where id=3;
+
+connection con2;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con1 and waiting_key != "";
+--source include/wait_condition.inc
+--error ER_LOCK_DEADLOCK
+update t1 set value=value+200 where id=1;
+
+# con2 tx is automatically rolled back
+connection con1;
+reap;
+select * from t1;
+drop table t1;
+
+connection default;
+
+disconnect con1;
+disconnect con2;
+disconnect con3;
+
+set global rocksdb_lock_wait_timeout = @prior_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_deadlock_detect;
+drop table t;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /INDEX_ID: [0-9a-f]*/IDX_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+set global rocksdb_max_latest_deadlocks = 0;
+--echo # Clears deadlock buffer of any existent deadlocks.
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /WAITING KEY: [0-9a-f]{16}/KEY/ /TRANSACTION ID: [0-9]*/TXN_ID/ /INDEX_ID: [0-9a-f]*/IDX_ID/ /TIMESTAMP: [0-9]*/TSTAMP/
+show engine rocksdb transaction status;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/delete.test b/storage/rocksdb/mysql-test/rocksdb/t/delete.test
new file mode 100644
index 00000000..c829dd9d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/delete.test
@@ -0,0 +1,101 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic DELETE statements.
+# DELETE LOW_PRIORITY is covered in delete_low_prio test
+# DELETE QUICK is covered in delete_quick test (syntax only)
+# DELETE IGNORE is covered in delete_ignore test
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (10000,'foobar'),(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+# Single-table DELETE
+
+DELETE FROM t1 WHERE b IN ('c');
+--sorted_result
+SELECT a,b FROM t1;
+
+DELETE FROM t1 WHERE a < 0 OR b = 'a';
+--sorted_result
+SELECT a,b FROM t1;
+
+# ORDER BY and LIMIT
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+--sorted_result
+SELECT a,b FROM t1;
+
+# Multi-table DELETE
+
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DELETE t2.* FROM t1, t2 WHERE c < b AND a + d != 1;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DELETE FROM t2, t1.* USING t2, t1 WHERE c = 'foobar' and b = c;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DELETE FROM t1;
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1, t2;
+
+
+#
+# Transactional DELETE
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'f'),(7,'g'),(8,'h'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+BEGIN;
+DELETE FROM t1 WHERE b IN ('c');
+--sorted_result
+SELECT a,b FROM t1;
+DELETE FROM t1 WHERE a < 0 OR b = 'a';
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+# Savepoints
+
+BEGIN;
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+SAVEPOINT spt1;
+
+DELETE FROM t1;
+RELEASE SAVEPOINT spt1;
+ROLLBACK;
+--sorted_result
+SELECT a,b FROM t1;
+
+BEGIN;
+DELETE FROM t1 WHERE a <= 4 ORDER BY b DESC LIMIT 1;
+SAVEPOINT spt1;
+DELETE FROM t1;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+--error ER_ROLLBACK_TO_SAVEPOINT
+ROLLBACK TO SAVEPOINT spt1;
+--error ER_ROLLBACK_ONLY
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/delete_ignore.test b/storage/rocksdb/mysql-test/rocksdb/t/delete_ignore.test
new file mode 100644
index 00000000..d087d80f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/delete_ignore.test
@@ -0,0 +1,37 @@
+--source include/have_rocksdb.inc
+
+#
+# DELETE IGNORE
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b) VALUES (10000,'foobar'),(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+CREATE TABLE t2 (pk INT AUTO_INCREMENT PRIMARY KEY, c CHAR(8), d INT) ENGINE=rocksdb;
+
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DELETE IGNORE FROM t1 WHERE b IS NOT NULL ORDER BY a LIMIT 1;
+--sorted_result
+SELECT a,b FROM t1;
+
+DELETE IGNORE t1.*, t2.* FROM t1, t2 WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+# Cleanup
+DROP TABLE t1, t2;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/delete_quick.test b/storage/rocksdb/mysql-test/rocksdb/t/delete_quick.test
new file mode 100644
index 00000000..127ef47d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/delete_quick.test
@@ -0,0 +1,32 @@
+--source include/have_rocksdb.inc
+
+#
+# DELETE QUICK syntax.
+# For now we only check that the keyword is accepted,
+# without actually checking whether the feature works.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+
+DELETE QUICK FROM t1 WHERE a = 1 OR b > 'foo';
+--sorted_result
+SELECT a,b FROM t1;
+
+CREATE TABLE t2 (c CHAR(8), d INT, PRIMARY KEY (c)) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DELETE QUICK FROM t2, t1.* USING t2, t1 WHERE c IS NULL OR a = d;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/delete_with_keys.test b/storage/rocksdb/mysql-test/rocksdb/t/delete_with_keys.test
new file mode 100644
index 00000000..f4d890eb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/delete_with_keys.test
@@ -0,0 +1,39 @@
+--source include/have_rocksdb.inc
+
+#
+# DELETE statements for tables with keys
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+DELETE FROM t1 WHERE b > 'y';
+DELETE FROM t1 WHERE a=2;
+
+--sorted_result
+SELECT a,b FROM t1;
+DELETE FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+DELETE FROM t1 WHERE b > 'y';
+DELETE FROM t1 WHERE a=2;
+
+--sorted_result
+SELECT a,b FROM t1;
+DELETE FROM t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT, b INT, c INT, pk INT AUTO_INCREMENT PRIMARY KEY, KEY(a), KEY (b)) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b,c) VALUES (1,2,3),(4,5,6),(7,8,9);
+DELETE FROM t1 WHERE a = 10 OR b = 20 ORDER BY c LIMIT 1;
+--sorted_result
+SELECT a,b,c FROM t1;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/describe.test b/storage/rocksdb/mysql-test/rocksdb/t/describe.test
new file mode 100644
index 00000000..9bc5d299
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/describe.test
@@ -0,0 +1,24 @@
+--source include/have_rocksdb.inc
+
+#
+# DESCRIBE statement
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2, t3;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'foo'),(2, 'b');
+
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY (b)) ENGINE=rocksdb CHARACTER SET utf8;
+INSERT INTO t2 (a,b) VALUES (1, 'bar');
+
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb CHARACTER SET utf8;
+
+DESCRIBE t1;
+DESC t2 a;
+DESCRIBE t3 '%';
+
+DROP TABLE t1, t2, t3;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/disabled.def b/storage/rocksdb/mysql-test/rocksdb/t/disabled.def
new file mode 100644
index 00000000..627d7da4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/disabled.def
@@ -0,0 +1,98 @@
+##
+## See also: storage/rocksdb/mysql-test/rocksdb/suite.pm - tests on
+## platforms and under valgrind are disabled from there.
+##
+
+##
+## Tests that are disabled in the upstream
+##
+cons_snapshot_serializable : Consistent read does not work on serializable
+level_read_uncommitted : Not supported
+level_serializable: Not supported
+
+
+##
+## Tests that require FB/MySQL specific features for which there are
+## no plans of ever porting them into MariaDB
+##
+optimizer_loose_index_scans: MariaDB doesnt support Skip Scan
+mysqldump : MariaDB's mysqldump doesn't support --print-ordering-key
+mysqldump2 : MariaDB's mysqldump doesn't support --print-ordering-key
+native_procedure : Not supported in MariaDB
+slow_query_log: MDEV-11480
+select_for_update_skip_locked_nowait: MDEV-11481
+create_no_primary_key_table: MariaDB doesn't have --block_create_no_primary_key
+explicit_snapshot: MariaDB doesn't support Shared/Explicit snapshots
+percona_nonflushing_analyze_debug : Requires Percona Server's Non-flushing ANALYZE feature
+com_rpc_tx : Requires connection attributes and detached sessions
+mysqlbinlog_blind_replace: requires @@enable_blind_replace support
+optimize_myrocks_replace_into_base: requires @@enable_blind_replace support
+optimize_myrocks_replace_into_lock: requires @@enable_blind_replace support
+rocksdb.skip_core_dump_on_error: requires @@binlog_error_action support
+bypass_select_basic_bloom : Query bypass is not supported
+bypass_select_basic : Query bypass is not supported
+
+rocksdb_read_free_rpl : Read-Free replication is not supported
+rocksdb_read_free_rpl_stress : Read-Free replication is not supported
+
+blind_delete_rr : Read-Free replication is not supported
+blind_delete_rc : Read-Free replication is not supported
+
+force_shutdown: requires support for SHUTDOWN statement which calls exit().
+
+##
+## Tests that do not fit MariaDB's test environment. Upstream seems to test
+## on boxes with much more RAM and CPU, some tests are more of a stress tests
+## than functional tests or bug testcases.
+##
+rqg_examples : Test that use RQG are disabled
+rqg_runtime : Test that use RQG are disabled
+rqg_transactions : Test that use RQG are disabled
+allow_no_pk_concurrent_insert: stress test
+rocksdb_deadlock_stress_rc: stress test
+rocksdb_deadlock_stress_rr: stress test
+use_direct_reads: Direct IO is not supported on all filesystems
+
+##
+## Tests which hit a problem elsewhere (Upstream, SQL layer, etc)
+##
+persistent_cache: Upstream RocksDB bug https://github.com/facebook/mysql-5.6/issues/579
+collation: Fails on gcc 4.8 and before, MDEV-12433
+col_opt_zerofill: MDEV-14729 (also MDEV-14165 which was fixed): problem in the client
+
+
+##
+## Tests that require FB/MySQL specific features which MariaDB
+## currently doesnt have but eventually might get:
+##
+gap_lock_issue254: MDEV-11735: MyRocks: Gap Lock detector support
+gap_lock_raise_error: MDEV-11735: MyRocks: Gap Lock detector support
+show_engine : MariaRocks: MariaDB doesnt support SHOW ENGINE rocksdb TRANSACTION STATUS
+issue243_transactionStatus: MariaDB doesnt support SHOW ENGINE rocksdb TRANSACTION STATUS
+rpl_row_not_found : MariaDB doesnt support slave_exec_mode='SEMI_STRICT'
+rpl_row_not_found_rc : MariaDB doesnt support slave_exec_mode='SEMI_STRICT'
+
+ddl_high_priority: Needs fractional @@lock_wait_timeout
+deadlock_tracking : Needs SHOW ENGINE ROCKSDB TRANSACTION STATUS
+bytes_written: Needs I_S.TABLE_STATISTICS.IO_WRITE_BYTES
+trx_info_rpl : MariaRocks: @@rpl_skip_tx_api doesn't work, yet.
+lock_wait_timeout_stats: MDEV-13404
+rpl_row_triggers : Requires read-free slave.
+
+##
+## Test failures (in buildbot or else where)
+##
+
+compact_deletes: MDEV-12663 : rocksdb.compact_deletes times out and causes other tests to fail
+blind_delete_without_tx_api: MDEV-12286: rocksdb.blind_delete_without_tx_api test fails
+information_schema: MDEV-14372: unstable testcase
+
+##
+## Tests that fail for some other reason
+##
+
+mysqlbinlog_gtid_skip_empty_trans_rocksdb : MariaRocks: requires GTIDs
+drop_table: Hangs on shutdown
+add_index_inplace: not stable result
+rocksdb_range2 : result difference, update after MDEV-16746 is fixed
+add_index_inplace: FORCE INDEX gives wrong count
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_database.test b/storage/rocksdb/mysql-test/rocksdb/t/drop_database.test
new file mode 100644
index 00000000..174a4bbf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_database.test
@@ -0,0 +1,11 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP DATABASE IF EXISTS test_drop_database;
+--enable_warnings
+
+CREATE DATABASE test_drop_database;
+CREATE TABLE t1 (a int, b int, c int, primary key (a), unique key (b)) ENGINE=ROCKSDB;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY (a);
+DROP TABLE t1;
+DROP DATABASE test_drop_database;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_index_inplace.test b/storage/rocksdb/mysql-test/rocksdb/t/drop_index_inplace.test
new file mode 100644
index 00000000..57d7cdf5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_index_inplace.test
@@ -0,0 +1,116 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+##
+## test dropping index inplace
+##
+
+CREATE TABLE t1 (a INT, b INT AUTO_INCREMENT, KEY ka(a), KEY kb(a,b), PRIMARY KEY(b)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 (a) VALUES (1);
+INSERT INTO t1 (a) VALUES (3);
+INSERT INTO t1 (a) VALUES (5);
+
+ALTER TABLE t1 DROP INDEX ka, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+# Key ka does not exist in table t1
+--error 1176
+SELECT * FROM t1 FORCE INDEX(ka) where a > 1;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kb) where a > 1;
+--sorted_result
+SELECT * FROM t1 where b > 1;
+
+DROP TABLE t1;
+
+##
+## test dropping multiple indexes at once and multi-part indexes
+##
+
+CREATE TABLE t1 (a INT AUTO_INCREMENT, b INT, c INT, KEY kb(b), KEY kbc(b,c), KEY kc(c), PRIMARY KEY(a)) ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+
+# test restarting to make sure everything is still ok and persisted properly
+--source include/restart_mysqld.inc
+
+SHOW CREATE TABLE t1;
+
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX(kc) where c > 3;
+--sorted_result
+SELECT * FROM t1 where b > 3;
+
+DROP TABLE t1;
+
+# test dropping pk to see if thats still ok
+CREATE TABLE t1 (a INT, b INT, c INT, KEY kb(b), KEY kbc(b,c), KEY kc(c), PRIMARY KEY(a)) ENGINE=rocksdb;
+SHOW INDEX IN t1;
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc, ALGORITHM=INPLACE;
+SHOW INDEX IN t1;
+
+ALTER TABLE t1 DROP PRIMARY KEY;
+SHOW INDEX IN t1;
+# test dropping index on tables with no pk
+ALTER TABLE t1 DROP INDEX kc, ALGORITHM=INPLACE;
+SHOW INDEX IN t1;
+
+DROP TABLE t1;
+
+# test dropping unique keys
+CREATE TABLE t1 (a INT AUTO_INCREMENT, b INT, c INT, PRIMARY KEY(a)) ENGINE=rocksdb;
+ALTER TABLE t1 ADD UNIQUE INDEX kb(b);
+ALTER TABLE t1 ADD UNIQUE INDEX kbc(b,c);
+ALTER TABLE t1 ADD UNIQUE INDEX kc(c);
+SHOW INDEX IN t1;
+
+ALTER TABLE t1 DROP INDEX kb, DROP INDEX kbc;
+SHOW INDEX IN t1;
+
+# test restarting to make sure everything is still ok and persisted properly
+--source include/restart_mysqld.inc
+
+--sorted_result
+INSERT INTO t1 (b,c) VALUES (1,2);
+INSERT INTO t1 (b,c) VALUES (3,4);
+INSERT INTO t1 (b,c) VALUES (5,6);
+SELECT * FROM t1 FORCE INDEX(kc) where c > 3;
+
+# test dropping index on tables with no pk
+ALTER TABLE t1 DROP INDEX kc, ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
+# case where dropping column, where column is the key, we dont want to use
+# inplace in this scenario
+CREATE TABLE IF NOT EXISTS t1 (col1 INT, col2 INT, col3 INT);
+INSERT INTO t1 (col1,col2,col3) VALUES (1,2,3);
+ALTER TABLE t1 ADD KEY idx ( col1, col2 );
+ANALYZE TABLE t1;
+ALTER TABLE t1 DROP COLUMN col2;
+ALTER TABLE t1 DROP COLUMN col3;
+DROP TABLE t1;
+
+# case drop and add at same time, should not use inplace algorithm yet
+CREATE TABLE IF NOT EXISTS t1 (col1 INT, col2 INT, col3 INT);
+INSERT INTO t1 (col1,col2,col3) VALUES (1,2,3);
+ALTER TABLE t1 ADD KEY idx ( col1, col2 );
+ANALYZE TABLE t1;
+ALTER TABLE t1 DROP COLUMN col2;
+ALTER TABLE t1 DROP COLUMN col3;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_stats_procedure.inc b/storage/rocksdb/mysql-test/rocksdb/t/drop_stats_procedure.inc
new file mode 100644
index 00000000..b4000440
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_stats_procedure.inc
@@ -0,0 +1,3 @@
+drop procedure save_read_stats;
+drop procedure get_read_stats;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/drop_table-master.opt
new file mode 100644
index 00000000..f40e9db5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_max_subcompactions=1
+--rocksdb_default_cf_options=write_buffer_size=512k;target_file_size_base=512k;level0_file_num_compaction_trigger=2;level0_slowdown_writes_trigger=-1;level0_stop_writes_trigger=1000;max_bytes_for_level_base=1m
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table.test b/storage/rocksdb/mysql-test/rocksdb/t/drop_table.test
new file mode 100644
index 00000000..9667c7ba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table.test
@@ -0,0 +1,145 @@
+--source include/have_rocksdb.inc
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+--enable_warnings
+
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+call mtr.add_suppression("LibRocksDB");
+
+# Start from clean slate
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+--source include/restart_mysqld.inc
+
+CREATE TABLE t1 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t2 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t3 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t4 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+# Populate tables
+let $max = 1000;
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t2;
+--source drop_table_repopulate_table.inc
+let $table = t3;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+drop table t2;
+
+# Restart the server before t2's indices are deleted
+--source include/restart_mysqld.inc
+
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+drop table t3;
+
+# Insert enough data to trigger compactions that eliminate t2 and t3
+let $max = 50000;
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+# Run manual compaction, then restarting mysqld
+# and confirming it is not blocked.
+SET GLOBAL rocksdb_max_manual_compactions = 2;
+SET GLOBAL rocksdb_debug_manual_compaction_delay = 3600;
+connect (con1, localhost, root,,);
+connect (con2, localhost, root,,);
+connect (con3, localhost, root,,);
+connection con1;
+send SET GLOBAL rocksdb_compact_cf='cf1';
+connection con2;
+send SET GLOBAL rocksdb_compact_cf='rev:cf2';
+connection default;
+let $wait_condition = select count(*) = 2 from information_schema.processlist where info like 'SET GLOBAL rocksdb_compact_cf%';
+--source include/wait_condition.inc
+# longer enough than manual compaction thread to start compaction
+--sleep 2
+select * from information_schema.global_status where variable_name='rocksdb_manual_compactions_running';
+
+connection con3;
+--error ER_INTERNAL_ERROR
+SET GLOBAL rocksdb_compact_cf='cf1';
+--error ER_INTERNAL_ERROR
+SET GLOBAL rocksdb_compact_cf='rev:cf2';
+
+connection default;
+--source include/restart_mysqld.inc
+
+drop table t4;
+
+# Restart the server before t4's indices are deleted
+--source include/restart_mysqld.inc
+
+# Make sure new table gets unique indices
+CREATE TABLE t5 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+let $max = 1000;
+let $table = t5;
+--source drop_table_repopulate_table.inc
+
+drop table t5;
+
+# Manually compact column families, cleaning up all lingering data
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+
+# Signal thread to check for dropped indices
+set global rocksdb_signal_drop_index_thread = 1;
+
+let $show_rpl_debug_info= 1; # to force post-failure printout
+let $wait_timeout= 300; # Override default 30 seconds with 300.
+let $wait_condition = select count(*) = 0
+ as c from information_schema.rocksdb_global_info
+ where TYPE = 'DDL_DROP_INDEX_ONGOING';
+--source include/wait_condition.inc
+
+## Upstream has removed the following:
+--disable_parsing
+--enable_parsing
+# Cleanup
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table2.test b/storage/rocksdb/mysql-test/rocksdb/t/drop_table2.test
new file mode 100644
index 00000000..a9012aea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table2.test
@@ -0,0 +1,131 @@
+--source include/have_rocksdb.inc
+
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+DROP TABLE IF EXISTS t5;
+--enable_warnings
+
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+
+# Start from clean slate
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+--source include/restart_mysqld.inc
+
+CREATE TABLE t1 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t2 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t3 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+CREATE TABLE t4 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+# Populate tables
+let $max = 1000;
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t2;
+--source drop_table_repopulate_table.inc
+let $table = t3;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+
+# Restart the server before t2's indices are deleted
+--source include/restart_mysqld.inc
+
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+
+# Insert enough data to trigger compactions that eliminate t2 and t3
+let $max = 50000;
+let $table = t1;
+--source drop_table_repopulate_table.inc
+let $table = t4;
+--source drop_table_repopulate_table.inc
+
+
+# Restart the server before t4's indices are deleted
+--source include/restart_mysqld.inc
+
+# Make sure new table gets unique indices
+CREATE TABLE t5 (
+ a int not null,
+ b int not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+let $max = 1000;
+let $table = t5;
+--source drop_table_repopulate_table.inc
+
+set @@global.rocksdb_compact_cf = 'cf1';
+set @@global.rocksdb_compact_cf = 'rev:cf2';
+set @@global.rocksdb_compact_cf = 'default';
+
+perl;
+$size+=-s $_ for (<$ENV{MYSQLTEST_VARDIR}/mysqld.1/data/\#rocksdb/*.sst>);
+$filename= "$ENV{MYSQLTEST_VARDIR}/tmp/size_output";
+open(F, '>', $filename) || die("Can't open file $filename: $!");
+print F $size;
+EOF
+drop table t1;
+drop table t2;
+drop table t3;
+drop table t4;
+drop table t5;
+
+set @@global.rocksdb_compact_cf = 'cf1';
+set @@global.rocksdb_compact_cf = 'rev:cf2';
+set @@global.rocksdb_compact_cf = 'default';
+
+let $show_rpl_debug_info= 1; # to force post-failure printout
+let $wait_timeout= 300; # Override default 30 seconds with 300.
+let $wait_condition = select count(*) = 0
+ as c from information_schema.rocksdb_global_info
+ where TYPE = 'DDL_DROP_INDEX_ONGOING';
+--source include/wait_condition.inc
+
+# Check that space is reclaimed
+perl;
+$size+=-s $_ for (<$ENV{MYSQLTEST_VARDIR}/mysqld.1/data/\#rocksdb/*.sst>);
+$filename= "$ENV{MYSQLTEST_VARDIR}/tmp/size_output";
+open(F, '<', $filename) || die("Can't open file $filename: $!");
+$old=<F>;
+print "Compacted\n" if $old > $size * 2;
+EOF
+
+# Cleanup
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table3-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3-master.opt
new file mode 100644
index 00000000..a9ebc4ec
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_max_subcompactions=1
+--rocksdb_default_cf_options=write_buffer_size=16k;target_file_size_base=16k;level0_slowdown_writes_trigger=-1;level0_stop_writes_trigger=1000;compression_per_level=kNoCompression;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.inc b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.inc
new file mode 100644
index 00000000..1a044384
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.inc
@@ -0,0 +1,52 @@
+--source include/have_rocksdb.inc
+
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+call mtr.add_suppression("Column family 'cf1' not found");
+call mtr.add_suppression("Column family 'rev:cf2' not found");
+
+# Start from clean slate
+set global rocksdb_compact_cf = 'cf1';
+set global rocksdb_compact_cf = 'rev:cf2';
+set global rocksdb_signal_drop_index_thread = 1;
+--source include/restart_mysqld.inc
+
+CREATE TABLE t1 (
+ a int not null,
+ b int not null,
+ c varchar(500) not null,
+ primary key (a,b) comment 'cf1',
+ key (b) comment 'rev:cf2'
+) ENGINE=RocksDB;
+
+# Populate tables
+let $max = 50000;
+let $table = t1;
+--source drop_table3_repopulate_table.inc
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+if ($truncate_table)
+{
+ truncate table t1;
+}
+if ($drop_table)
+{
+ drop table t1;
+}
+
+let $show_rpl_debug_info= 1; # to force post-failure printout
+let $wait_timeout= 300; # Override default 30 seconds with 300.
+let $wait_condition = select count(*) = 0
+ as c from information_schema.rocksdb_global_info
+ where TYPE = 'DDL_DROP_INDEX_ONGOING';
+--source include/wait_condition.inc
+
+select case when variable_value-@a < 500000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_compact_read_bytes';
+
+# Cleanup
+DROP TABLE IF EXISTS t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.test b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.test
new file mode 100644
index 00000000..b3a6bf99
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+-- let $truncate_table = 0
+-- let $drop_table = 1
+-- source drop_table3.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table3_repopulate_table.inc b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3_repopulate_table.inc
new file mode 100644
index 00000000..c34af072
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table3_repopulate_table.inc
@@ -0,0 +1,15 @@
+# Usage:
+# let $max = <count>;
+# let $table = <table name>;
+# --source drop_table_repopulate_table.inc
+#
+eval DELETE FROM $table;
+
+--disable_query_log
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO $table VALUES ($i, $i, rpad('a', 499, 'b'));
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table_repopulate_table.inc b/storage/rocksdb/mysql-test/rocksdb/t/drop_table_repopulate_table.inc
new file mode 100644
index 00000000..6faf41ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table_repopulate_table.inc
@@ -0,0 +1,15 @@
+# Usage:
+# let $max = <count>;
+# let $table = <table name>;
+# --source drop_table_repopulate_table.inc
+#
+eval DELETE FROM $table;
+
+--disable_query_log
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO $table VALUES ($i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/drop_table_sync.inc b/storage/rocksdb/mysql-test/rocksdb/t/drop_table_sync.inc
new file mode 100644
index 00000000..c6a3ccde
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/drop_table_sync.inc
@@ -0,0 +1,6 @@
+let $show_rpl_debug_info= 1; # to force post-failure printout
+let $wait_timeout= 300; # Override default 30 seconds with 300.
+let $wait_condition = select count(*) = 0
+ as c from information_schema.rocksdb_global_info
+ where TYPE = 'DDL_DROP_INDEX_ONGOING';
+--source include/wait_condition.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/dup_key_update.test b/storage/rocksdb/mysql-test/rocksdb/t/dup_key_update.test
new file mode 100644
index 00000000..b4a0c9e5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/dup_key_update.test
@@ -0,0 +1,45 @@
+--source include/have_rocksdb.inc
+
+# Test insert ... on duplicate key update statements
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+CREATE TABLE t1 (id1 INT, id2 INT, id3 INT,
+ PRIMARY KEY (id1, id2, id3),
+ UNIQUE KEY (id3, id1)) ENGINE=ROCKSDB;
+
+CREATE TABLE t2 (id1 INT, id2 INT, id3 INT,
+ PRIMARY KEY (id1, id2, id3),
+ UNIQUE KEY (id3, id1) COMMENT 'rev:cf') ENGINE=ROCKSDB;
+
+
+--source include/dup_key_update.inc
+
+# Cleanup
+DROP TABLE t1;
+DROP TABLE t2;
+
+set global rocksdb_large_prefix=1;
+CREATE TABLE t1 (id1 varchar(128) CHARACTER SET latin1 COLLATE latin1_bin,
+ id2 varchar(256) CHARACTER SET utf8 COLLATE utf8_bin,
+ id3 varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+ PRIMARY KEY (id1, id2, id3),
+ UNIQUE KEY (id3, id1)) ENGINE=ROCKSDB;
+set global rocksdb_large_prefix=DEFAULT;
+
+set global rocksdb_large_prefix=1;
+CREATE TABLE t2 (id1 varchar(128) CHARACTER SET latin1 COLLATE latin1_bin,
+ id2 varchar(256) CHARACTER SET utf8 COLLATE utf8_bin,
+ id3 varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+ PRIMARY KEY (id1, id2, id3),
+ UNIQUE KEY (id3, id1) COMMENT 'rev:cf') ENGINE=ROCKSDB;
+set global rocksdb_large_prefix=DEFAULT;
+
+--source include/dup_key_update.inc
+
+# Cleanup
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/duplicate_table.test b/storage/rocksdb/mysql-test/rocksdb/t/duplicate_table.test
new file mode 100644
index 00000000..875f8514
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/duplicate_table.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+--disable_warnings
+DROP TABLE IF EXISTS t;
+--enable_warnings
+CREATE TABLE t(id int primary key) engine=rocksdb;
+INSERT INTO t values (1), (2), (3);
+--error ER_TABLE_EXISTS_ERROR
+CREATE TABLE t(id int primary key) engine=rocksdb;
+FLUSH TABLES;
+move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t.frm $MYSQLTEST_VARDIR/mysqld.1/data/test/t.frm.tmp;
+--error ER_METADATA_INCONSISTENCY
+CREATE TABLE t(id int primary key) engine=rocksdb;
+move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t.frm.tmp $MYSQLTEST_VARDIR/mysqld.1/data/test/t.frm;
+FLUSH TABLES;
+SELECT * FROM t;
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot-master.opt
new file mode 100644
index 00000000..d7dd66b4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot-master.opt
@@ -0,0 +1 @@
+--log-bin --binlog_format=row --gtid_mode=ON --enforce_gtid_consistency --log-slave-updates
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot.test b/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot.test
new file mode 100644
index 00000000..efe1980b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/explicit_snapshot.test
@@ -0,0 +1,263 @@
+source include/have_log_bin.inc;
+source include/have_rocksdb.inc;
+
+disable_warnings;
+DROP TABLE IF EXISTS t1;
+enable_warnings;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+CREATE TABLE T1 (a INT PRIMARY KEY AUTO_INCREMENT) ENGINE=ROCKSDB;
+INSERT INTO T1 VALUES();
+
+#
+
+echo "con1: Creating explict snapshot";
+let $snapshot=
+ query_get_value(CREATE EXPLICIT ROCKSDB SNAPSHOT, Snapshot_ID, 1);
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+echo "con2: Attaching snapshot id $snapshot";
+disable_result_log;
+eval ATTACH EXPLICIT ROCKSDB SNAPSHOT $snapshot;
+enable_result_log;
+echo "con2: New row should not be visible";
+SELECT * FROM T1;
+echo "con2: Releasing snapshot";
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+echo "con2: New row should be visible";
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: New row should not be visible";
+SELECT * FROM T1;
+echo "con1: Releasing snapshot";
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+echo "con1: New row should be visible";
+SELECT * FROM T1;
+
+#
+
+echo "con1: Starting shared snapshot";
+let $snapshot=
+query_get_value(START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT, Snapshot_ID, 1);
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+echo "con2: Starting existing snapshot";
+disable_result_log;
+eval START TRANSACTION WITH EXISTING ROCKSDB SNAPSHOT $snapshot;
+enable_result_log;
+echo "con2: New row should not be visible";
+SELECT * FROM T1;
+COMMIT;
+echo "con2: New row should be visible";
+SELECT * FROM T1;
+
+connection con1;
+COMMIT;
+echo "con1: New row should be visible";
+SELECT * FROM T1;
+
+## Negative test cases
+
+connection con1;
+echo "con1: Creating explict snapshot";
+let $snapshot=
+ query_get_value(CREATE EXPLICIT ROCKSDB SNAPSHOT, Snapshot_ID, 1);
+echo "con2: Trying to insert row";
+error ER_UPDATES_WITH_EXPLICIT_SNAPSHOT;
+INSERT INTO T1 VALUES();
+
+connection con2;
+echo "con2: Attaching existing snapshot";
+disable_result_log;
+eval ATTACH EXPLICIT ROCKSDB SNAPSHOT $snapshot;
+enable_result_log;
+echo "con2: Trying to insert row";
+error ER_UPDATES_WITH_EXPLICIT_SNAPSHOT;
+INSERT INTO T1 VALUES();
+
+connection con1;
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+connection con2;
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+
+connection con1;
+echo "con1: Starting shared snapshot";
+let $snapshot=
+query_get_value(START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT, Snapshot_ID, 1);
+echo "con1: Trying to insert row";
+error ER_UPDATES_WITH_CONSISTENT_SNAPSHOT;
+INSERT INTO T1 VALUES();
+
+connection con2;
+echo "con2: Starting existing snapshot";
+disable_result_log;
+eval START TRANSACTION WITH EXISTING ROCKSDB SNAPSHOT $snapshot;
+enable_result_log;
+echo "con2: Trying to insert row";
+error ER_UPDATES_WITH_CONSISTENT_SNAPSHOT;
+INSERT INTO T1 VALUES();
+
+connection con1;
+COMMIT;
+connection con2;
+COMMIT;
+
+## Test how overlapping explicit snapshot statements work
+
+connection con1;
+echo "con1: Creating explicit snapshot";
+disable_result_log;
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: New row should not be seen";
+SELECT * FROM T1;
+echo "con1: Creating another explicit snapshot";
+disable_result_log;
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+echo "con1: Now the new row should be seen";
+SELECT * FROM T1;
+
+#
+
+echo "con1: Starting transaction with consistent snapshot";
+disable_result_log;
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: The new row should not be seen";
+SELECT * FROM T1;
+
+echo "con1: Creating another explicit snapshot";
+disable_result_log;
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+echo "con1: The new row should still not be seen";
+SELECT * FROM T1;
+
+echo "con1: Committing trx";
+COMMIT;
+echo "con1: The new row should now be seen because of the new explicit snapshot created above";
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: Releasing explicit snapshot";
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+
+#
+
+echo "con1: Starting transaction with shared snapshot";
+disable_result_log;
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: The new row should not be seen";
+SELECT * FROM T1;
+
+echo "con1: Starting another transaction with shared snapshot";
+disable_result_log;
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+enable_result_log;
+echo "con1: The new row should now be seen";
+SELECT * FROM T1;
+COMMIT;
+
+#
+
+echo "con1: Creating explicit snapshot";
+disable_result_log;
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+echo "con1: Releasing explicit snapshot";
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+
+echo "con1: Releasing explicit snapshot again";
+error ER_UNKNOWN_ERROR;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+
+#
+
+echo "con1: Starting transaction with shared snapshot";
+disable_result_log;
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+connection con2;
+echo "con2: Inserting a row";
+INSERT INTO T1 VALUES();
+SELECT * FROM T1;
+
+connection con1;
+echo "con1: Creating explicit snapshot";
+disable_result_log;
+CREATE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+SELECT * FROM T1;
+
+echo "con1: Releasing explicit snapshot";
+disable_result_log;
+RELEASE EXPLICIT ROCKSDB SNAPSHOT;
+enable_result_log;
+
+echo "con1: The new row should not be seen";
+SELECT* FROM T1;
+COMMIT;
+
+#
+
+## Cleanup
+DROP TABLE T1;
+
+connection default;
+disconnect con1;
+disconnect con2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/fail_system_cf.test b/storage/rocksdb/mysql-test/rocksdb/t/fail_system_cf.test
new file mode 100644
index 00000000..25581970
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/fail_system_cf.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+#
+# Any create table using the system column family should fail
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--error ER_WRONG_ARGUMENTS
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT '__system__') ENGINE = ROCKSDB;
+
+#cleanup
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/fast_prefix_index_fetch.test b/storage/rocksdb/mysql-test/rocksdb/t/fast_prefix_index_fetch.test
new file mode 100644
index 00000000..0b6cf42a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/fast_prefix_index_fetch.test
@@ -0,0 +1,120 @@
+# Disable until covered unpack_info format enabled in prod
+--source include/have_debug.inc
+--source include/not_debug.inc
+
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ id INT,
+ fake_id INT,
+ bigfield VARCHAR(4096),
+ PRIMARY KEY (id),
+ KEY bf (bigfield(32)),
+ KEY fid (fake_id, bigfield(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 VALUES (1, 1001, REPEAT('a', 1)),
+ (8, 1008, REPEAT('b', 8)),
+ (24, 1024, REPEAT('c', 24)),
+ (31, 1031, REPEAT('d', 31)),
+ (32, 1032, REPEAT('x', 32)),
+ (33, 1033, REPEAT('y', 33)),
+ (128, 1128, REPEAT('z', 128));
+
+SELECT * FROM t1;
+
+--disable_query_log
+
+--let $prefix_index_check_title= Baseline sanity check
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT "no-op query"
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 2
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('d', 31)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Eligible for optimization, access via fake_id only
+--let $prefix_index_check_read_avoided_delta= 2
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1031
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Not eligible for optimization, access via fake_id of big row.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(fid) WHERE fake_id = 1033
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+# The secondary_index_read call isn't covered because the next record in the
+# index has a bigfield value of length 33, so only one of two lookups are
+# covered here.
+--let $prefix_index_check_title= Eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 1
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('x', 32)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Not eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('y', 33)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 2
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('b', 8)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 2
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('c', 24)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Not eligible for optimization.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT id, bigfield FROM t1 FORCE INDEX(bf) WHERE bigfield = REPEAT('z', 128)
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DROP TABLE t1;
+
+--echo #
+--echo # Test that multi-byte charsets are handled correctly
+--echo #
+
+SET NAMES utf8;
+
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b VARCHAR(30) CHARACTER SET utf8 collate utf8_bin,
+ KEY k (b(2))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 VALUES
+ (1, 'a'),
+ (2, 'cc'),
+ (3, 'ŽŽ'),
+ (4, 'žžžž');
+
+--let $prefix_index_check_title= Charset record obviously shorter than the prefix
+--let $prefix_index_check_query= SELECT * FROM t1 FORCE INDEX(k) WHERE b = "a"
+--let $prefix_index_check_read_avoided_delta= 2
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Charset record shorter than prefix
+--let $prefix_index_check_query= SELECT * FROM t1 FORCE INDEX(k) WHERE b = "cc"
+--let $prefix_index_check_read_avoided_delta= 2
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Charset record with glyphs shorter than prefix
+--let $prefix_index_check_query= SELECT * FROM t1 FORCE INDEX(k) WHERE b = "ŽŽ"
+--let $prefix_index_check_read_avoided_delta= 1
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+--let $prefix_index_check_title= Charset record longer than prefix
+--let $prefix_index_check_query= SELECT * FROM t1 FORCE INDEX(k) WHERE b = "žžžž"
+--let $prefix_index_check_read_avoided_delta= 0
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/force_shutdown.test b/storage/rocksdb/mysql-test/rocksdb/t/force_shutdown.test
new file mode 100644
index 00000000..1817bc06
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/force_shutdown.test
@@ -0,0 +1,97 @@
+--source include/have_rocksdb.inc
+
+--source include/have_debug.inc
+--source include/not_valgrind.inc
+
+connect (conn1, localhost, root,,test);
+
+create table t1 (
+ pk int not null primary key,
+ col1 varchar(10)
+) engine=rocksdb;
+
+insert into t1 values (1,1),(2,2),(3,3);
+
+connection conn1;
+set session debug= "+d,myrocks_busy_loop_on_row_read";
+send select * from t1 where pk=1;
+
+--echo # testing unclean shutdown on stuck instance
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where info = 'select * from t1 where pk=1';
+--source include/wait_condition.inc
+--echo # Run shutdown sql command with forcing kill (exit code 127)
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2006,2013
+shutdown 1;
+--source include/wait_until_disconnected.inc
+
+--echo # verifying exit code is printed
+let $error_log=$MYSQLTEST_VARDIR/log/testlog.err;
+let SEARCH_FILE=$error_log;
+--echo # restart the server
+--exec echo "restart:--log-error=$error_log" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+--error 2006,2013
+shutdown 230;
+--source include/wait_until_disconnected.inc
+let SEARCH_PATTERN=COM_SHUTDOWN received from host/user = localhost/root, exit code 230;
+--source include/search_pattern_in_file.inc
+
+--echo # restart the server
+--exec echo "restart:" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+--echo # verifying SHUTDOWN is refused if exit code > 255
+--error ER_UNKNOWN_ERROR
+SHUTDOWN 256;
+--error ER_UNKNOWN_ERROR
+SHUTDOWN 10000;
+
+--echo # verifying SHUTDOWN is refused if instances are not read only
+--error ER_UNKNOWN_ERROR
+SHUTDOWN 0 read_only;
+--error ER_UNKNOWN_ERROR
+SHUTDOWN 127 read_only;
+--error 2006,2013
+SHUTDOWN 127;
+--source include/wait_until_disconnected.inc
+
+--echo # restart the server
+--exec echo "restart:" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+connect (conn2, localhost, root,,test);
+
+connection conn2;
+set session debug= "+d,myrocks_busy_loop_on_row_read";
+send select * from t1 where pk=1;
+
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where info = 'select * from t1 where pk=1';
+--source include/wait_condition.inc
+
+SET GLOBAL read_only=1;
+--echo # verifying SHUTDOWN read_only works with read_only instance
+--echo # Run shutdown sql command with forcing kill (exit code 127)
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2006,2013
+shutdown 255 read_only;
+--source include/wait_until_disconnected.inc
+
+--echo # restart the server
+--exec echo "restart:" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+
+disconnect conn1;
+disconnect conn2;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/foreign_key.test b/storage/rocksdb/mysql-test/rocksdb/t/foreign_key.test
new file mode 100644
index 00000000..675a337c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/foreign_key.test
@@ -0,0 +1,47 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (b INT PRIMARY KEY);
+
+# Try simple foreign key - should fail
+--error ER_NOT_SUPPORTED_YET
+CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL, FOREIGN KEY (b) REFERENCES t1(b));
+
+# Try simple valid syntax with 'foreign' as part - should succeed
+CREATE TABLE t2 (a INT NOT NULL, bforeign INT NOT NULL);
+DROP TABLE t2;
+
+# Try simple valid syntax with 'foreign' and 'key' as part (with no space) - should succeed
+CREATE TABLE t2 (a INT NOT NULL, foreignkey INT NOT NULL);
+DROP TABLE t2;
+
+# Try with valid id containing 'foreign' and then a foreign key - should fail
+--error ER_NOT_SUPPORTED_YET
+CREATE TABLE t2 (a INT NOT NULL, bforeign INT not null, FOREIGN KEY (bforeign) REFERENCES t1(b));
+
+CREATE TABLE t2 (a INT NOT NULL, b INT NOT NULL);
+# Alter with foreign key - should fail
+--error ER_NOT_SUPPORTED_YET
+ALTER TABLE t2 ADD FOREIGN KEY (b) REFERENCES t1(b);
+DROP TABLE t2;
+
+# Alter with valid syntax that contains 'foreign' - should succeed
+CREATE TABLE t2 (a INT NOT NULL);
+ALTER TABLE t2 ADD bforeign INT NOT NULL;
+DROP TABLE t2;
+
+# Alter with valid syntax that contains 'foreign' and 'key' (no space) - should succeed
+CREATE TABLE t2 (a INT NOT NULL);
+ALTER TABLE t2 ADD foreignkey INT NOT NULL;
+DROP TABLE t2;
+
+# Alter with valid syntax that contains 'foreign' and then foreign key - should fail
+CREATE TABLE t2 (a INT NOT NULL);
+--error ER_NOT_SUPPORTED_YET
+ALTER TABLE t2 ADD bforeign INT NOT NULL, ADD FOREIGN KEY (bforeign) REFERENCES t1(b);
+DROP TABLE t2;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254-master.opt
new file mode 100644
index 00000000..f0b7f4b5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254-master.opt
@@ -0,0 +1 @@
+--binlog-format=row --binlog-row-image=full --gap-lock-raise-error=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254.test b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254.test
new file mode 100644
index 00000000..af7c9b1a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_issue254.test
@@ -0,0 +1,14 @@
+-- source include/have_binlog_format_row.inc
+-- source include/have_rocksdb.inc
+
+# For issue#254
+create table t (id int primary key, value int);
+begin;
+update t set value=100 where id in (1, 2);
+commit;
+begin;
+--error ER_UNKNOWN_ERROR
+select * from t for update;
+commit;
+drop table t;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_raise_error.test b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_raise_error.test
new file mode 100644
index 00000000..59fe7e6f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/gap_lock_raise_error.test
@@ -0,0 +1,37 @@
+--source include/have_rocksdb.inc
+
+let $engine=rocksdb;
+--source include/gap_lock_raise_error_all.inc
+
+SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;
+
+SET GLOBAL gap_lock_exceptions="t.*";
+--source include/gap_lock_raise_error_init.inc
+
+set session autocommit=0;
+--error ER_UNKNOWN_ERROR
+select * from gap1 limit 1 for update;
+--error ER_UNKNOWN_ERROR
+select * from gap1 where value != 100 limit 1 for update;
+
+--source include/gap_lock_raise_error_cleanup.inc
+
+SET GLOBAL gap_lock_exceptions="gap.*";
+--source include/gap_lock_raise_error_init.inc
+
+set session autocommit=0;
+select * from gap1 limit 1 for update;
+select * from gap1 where value != 100 limit 1 for update;
+
+--source include/gap_lock_raise_error_cleanup.inc
+
+# This test has been temporarily removed because it fails when the server
+# is compiled using GCC 4.8 as full regular expression handling was added
+# in GCC 4.9. We need to add the ability to detect if full regex is
+# available before re-enabling this test.
+## Make sure we handle invalid regex expressions and generate a warning
+#--exec echo "" >$MYSQLTEST_VARDIR/log/mysqld.1.err
+#SET GLOBAL gap_lock_exceptions="[a-b,abc\\";
+#--exec grep -A 2 "Invalid pattern" $MYSQLTEST_VARDIR/log/mysqld.1.err | cut -d] -f2
+
+SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/get_error_message.test b/storage/rocksdb/mysql-test/rocksdb/t/get_error_message.test
new file mode 100644
index 00000000..b4c9e2da
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/get_error_message.test
@@ -0,0 +1,27 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+--source include/not_windows.inc # Data directory option not availablr
+
+#
+# Create tables with partitions and try to generate an error while creating
+# partitions.
+#
+--disable_warnings
+
+DROP TABLE IF EXISTS t1;
+
+--enable_warnings
+
+CREATE TABLE t1(a INT,b INT,KEY (a)) PARTITION BY HASH (a) PARTITIONS 3;
+
+SHOW TABLES;
+
+--disable_query_log
+call mtr.add_suppression("Failed to execute action for entry.*");
+--enable_query_log
+
+--error ER_WRONG_TABLE_NAME
+ALTER TABLE t1 ADD PARTITION(PARTITION p3 DATA DIRECTORY='G:/mysqltest/p3Data' INDEX DIRECTORY='H:/mysqltest/p3Index');
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/group_min_max-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/group_min_max-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/group_min_max-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/group_min_max.test b/storage/rocksdb/mysql-test/rocksdb/t/group_min_max.test
new file mode 100644
index 00000000..eb66bd0e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/group_min_max.test
@@ -0,0 +1,9 @@
+--source include/have_debug.inc
+set @debug_tmp= @@debug_dbug;
+set global debug_dbug="+d,force_group_by";
+
+let $engine=RocksDB;
+--source include/group_min_max.inc
+
+set global debug_dbug=@debug_tmp;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ha_extra_keyread.test b/storage/rocksdb/mysql-test/rocksdb/t/ha_extra_keyread.test
new file mode 100644
index 00000000..0d0fad2e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ha_extra_keyread.test
@@ -0,0 +1,15 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (a INT, b CHAR(8), KEY ab(a, b)) ENGINE=rocksdb DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_bin;
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+
+SET debug_dbug="+d,dbug.rocksdb.HA_EXTRA_KEYREAD";
+
+SELECT b FROM t1 FORCE INDEX(ab) WHERE a=35;
+
+
+SET debug_dbug="-d,dbug.rocksdb.HA_EXTRA_KEYREAD";
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/handler_basic.test b/storage/rocksdb/mysql-test/rocksdb/t/handler_basic.test
new file mode 100644
index 00000000..22b5d697
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/handler_basic.test
@@ -0,0 +1,53 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic HANDLER counts
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+FLUSH STATUS;
+CREATE TABLE t1 (id INT PRIMARY KEY, a VARCHAR(100), b INT,
+INDEX b(b)) ENGINE=rocksdb;
+INSERT INTO t1 (id,a,b) VALUES (1,'foobar',100),(2,'z',0),(3,'bar',50);
+SHOW SESSION STATUS LIKE 'Handler_write%';
+
+UPDATE t1 SET b=1000 WHERE id=1;
+SHOW SESSION STATUS LIKE 'Handler_update%';
+
+DELETE FROM t1 WHERE id=2;
+SHOW SESSION STATUS LIKE 'Handler_delete%';
+
+INSERT INTO t1 (id,b) VALUES(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+SHOW SESSION STATUS LIKE 'Handler_write%';
+
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id=8;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+FLUSH STATUS;
+SET GLOBAL rocksdb_force_flush_memtable_and_lzero_now=1;
+SELECT * FROM t1 WHERE b=6;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+FLUSH STATUS;
+--sorted_result
+SELECT * FROM t1;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+FLUSH STATUS;
+SELECT * FROM t1 FORCE INDEX(b) WHERE b <=5 ORDER BY b;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id >=8 ORDER BY id;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+FLUSH STATUS;
+SELECT * FROM t1 WHERE id < 8 ORDER BY id;
+SHOW SESSION STATUS LIKE 'Handler_read%';
+
+# Cleanup
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/hermitage.inc b/storage/rocksdb/mysql-test/rocksdb/t/hermitage.inc
new file mode 100644
index 00000000..17baf5b6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/hermitage.inc
@@ -0,0 +1,257 @@
+--enable_connect_log
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+### See full test cases here:
+### https://github.com/ept/hermitage/blob/master/mysql.md
+
+--disable_warnings
+DROP TABLE IF EXISTS test;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+connect (con2,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+connect (con3,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+
+connection con1;
+
+create table test (id int primary key, value int) engine=rocksdb;
+
+### Prevents Aborted Reads (G1a)
+--source hermitage_init.inc
+
+connection con1;
+select * from test;
+update test set value = 101 where id = 1;
+connection con2;
+select * from test; # Still shows 1 => 10
+connection con1;
+rollback;
+connection con2; # Still shows 1 => 10
+select * from test;
+commit;
+
+
+### Prevents Intermediate Reads (G1b)
+--source hermitage_init.inc
+
+connection con1;
+update test set value = 101 where id = 1;
+connection con2;
+select * from test; # Still shows 1 => 10
+connection con1;
+update test set value = 11 where id = 1;
+commit;
+connection con2;
+select * from test; # Now shows 1 => 11
+commit;
+
+
+### Prevents Circular Information Flow (G1c)
+--source hermitage_init.inc
+
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 22 where id = 2;
+connection con1;
+select * from test where id = 2; # Still shows 2 => 20
+connection con2;
+select * from test where id = 1; # Still shows 1 => 10
+connection con1;
+commit;
+connection con2;
+commit;
+
+
+### prevents Observed Transaction Vanishes (OTV)
+--source hermitage_init.inc
+
+connection con1;
+update test set value = 11 where id = 1;
+update test set value = 19 where id = 2;
+connection con2;
+send update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+reap;
+connection con3;
+select * from test; # Shows 1 => 11, 2 => 19
+connection con2;
+update test set value = 18 where id = 2;
+connection con3;
+select * from test; # Shows 1 => 11, 2 => 19
+connection con2;
+commit;
+connection con3;
+select * from test; # Shows 1 => 12, 2 => 18
+commit;
+
+
+### Predicate-Many-Preceders (PMP) -- RC does not prevent, RR prevents
+--source hermitage_init.inc
+
+connection con1;
+select * from test where value = 30;
+connection con2;
+insert into test (id, value) values(3, 30);
+commit;
+connection con1;
+# RC: Returns the newly inserted row
+# RR: Still returns nothing
+select * from test where value % 3 = 0;
+commit;
+
+--source hermitage_init.inc
+connection con1;
+update test set value = value + 10;
+connection con2;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_snapshot_conflict_errors';
+select * from test;
+send delete from test where value = 20;
+connection con1;
+commit;
+connection con2;
+if ($trx_isolation == "READ COMMITTED")
+{
+ reap;
+ # RC: Returns 2 => 30
+ select * from test;
+}
+if ($trx_isolation == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+ select variable_value-@a from information_schema.global_status where variable_name='rocksdb_snapshot_conflict_errors';
+
+}
+commit;
+
+
+### Lost Update (P4) -- RC does not prevent, RR prevents
+--source hermitage_init.inc
+
+connection con1;
+select * from test where id = 1;
+connection con2;
+select * from test where id = 1;
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+send update test set value = 12 where id = 1;
+connection con1;
+commit;
+connection con2;
+if ($trx_isolation == "READ COMMITTED")
+{
+ reap;
+ # RC: Returns 1 => 12
+ select * from test;
+}
+if ($trx_isolation == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+commit;
+
+
+### Read Skew (G-single) -- RC does not prevent, RR prevents
+--source hermitage_init.inc
+
+connection con1;
+select * from test where id = 1;
+connection con2;
+select * from test where id = 1;
+select * from test where id = 2;
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+select * from test where id = 2; # RC shows 18, RR shows 20
+commit;
+
+# test using predicate dependencies
+--source hermitage_init.inc
+
+connection con1;
+select * from test where value % 5 = 0;
+connection con2;
+update test set value = 12 where value = 10;
+commit;
+connection con1;
+# RC: returns 1 => 12, RR: returns nothing
+select * from test where value % 3 = 0;
+commit;
+
+# on a write predicate
+--source hermitage_init.inc
+
+connection con1;
+select * from test where id = 1;
+connection con2;
+select * from test;
+update test set value = 12 where id = 1;
+update test set value = 18 where id = 2;
+commit;
+connection con1;
+if ($trx_isolation == "READ COMMITTED")
+{
+ delete from test where value = 20; # doesn't delete anything
+ select * from test where id = 2; # shows 2 => 18
+}
+if ($trx_isolation == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ delete from test where value = 20;
+}
+commit;
+
+
+### Write Skew (G2-item) -- Neither RC/RR prevents
+--source hermitage_init.inc
+
+connection con1;
+select * from test where id in (1,2);
+connection con2;
+select * from test where id in (1,2);
+connection con1;
+update test set value = 11 where id = 1;
+connection con2;
+update test set value = 21 where id = 2;
+connection con1;
+commit;
+connection con2;
+commit;
+
+### Anti-Dependency Cycles (G2) -- Neither RC/RR prevents
+--source hermitage_init.inc
+
+connection con1;
+select * from test where value % 3 = 0;
+connection con2;
+select * from test where value % 3 = 0;
+connection con1;
+insert into test (id, value) values(3, 30);
+connection con2;
+insert into test (id, value) values(4, 42);
+connection con1;
+commit;
+connection con2;
+commit;
+select * from test where value % 3 = 0; # Either. Returns 3 => 30, 4 => 42
+connection con1;
+select * from test where value % 3 = 0;
+
+
+connection default;
+drop table test;
+
+disconnect con1;
+disconnect con2;
+disconnect con3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/hermitage.test b/storage/rocksdb/mysql-test/rocksdb/t/hermitage.test
new file mode 100644
index 00000000..e4138e8d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/hermitage.test
@@ -0,0 +1,10 @@
+--source include/have_rocksdb.inc
+
+# Hermitage is an attempt to test transaction isolation levels.
+# https://github.com/ept/hermitage
+
+let $trx_isolation = READ COMMITTED;
+--source hermitage.inc
+
+let $trx_isolation = REPEATABLE READ;
+--source hermitage.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/hermitage_init.inc b/storage/rocksdb/mysql-test/rocksdb/t/hermitage_init.inc
new file mode 100644
index 00000000..4f3f03ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/hermitage_init.inc
@@ -0,0 +1,8 @@
+connection con1;
+truncate table test;
+insert into test (id, value) values (1, 10), (2, 20);
+begin;
+connection con2;
+begin;
+connection con3;
+begin;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/i_s.test b/storage/rocksdb/mysql-test/rocksdb/t/i_s.test
new file mode 100644
index 00000000..c9dc98c9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/i_s.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA='INFORMATION_SCHEMA'
+ AND TABLE_NAME LIKE 'ROCKSDB%'
+ ORDER BY TABLE_NAME;
+
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_CF_OPTIONS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_CFSTATS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DBSTATS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DDL;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_DEADLOCK;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_LOCKS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_SST_PROPS;
+SHOW CREATE TABLE INFORMATION_SCHEMA.ROCKSDB_TRX;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/i_s_ddl.test b/storage/rocksdb/mysql-test/rocksdb/t/i_s_ddl.test
new file mode 100644
index 00000000..716f3720
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/i_s_ddl.test
@@ -0,0 +1,29 @@
+--source include/have_rocksdb.inc
+
+#
+# Information Schema DDL
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS is_ddl_t1;
+DROP TABLE IF EXISTS is_ddl_t2;
+DROP TABLE IF EXISTS is_ddl_t3;
+--enable_warnings
+
+CREATE TABLE is_ddl_t1 (i INT, j INT, k INT, l INT,
+ PRIMARY KEY (i), KEY (j), KEY (k, l) COMMENT 'kl_cf')
+ ENGINE = ROCKSDB;
+
+CREATE TABLE is_ddl_t2 (x INT, y INT, z INT,
+ PRIMARY KEY (z, y) COMMENT 'zy_cf',
+ KEY (x)) ENGINE = ROCKSDB;
+CREATE TABLE is_ddl_t3 (a INT, b INT, c INT, PRIMARY KEY (a)) ENGINE = ROCKSDB
+ COMMENT "ttl_duration=3600;";
+
+--sorted_result
+SELECT TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,INDEX_NAME,INDEX_TYPE,KV_FORMAT_VERSION,CF,TTL_DURATION,INDEX_FLAGS FROM INFORMATION_SCHEMA.ROCKSDB_DDL WHERE TABLE_NAME like 'is_ddl_t%';
+
+# cleanup
+DROP TABLE is_ddl_t1;
+DROP TABLE is_ddl_t2;
+DROP TABLE is_ddl_t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/i_s_deadlock.test b/storage/rocksdb/mysql-test/rocksdb/t/i_s_deadlock.test
new file mode 100644
index 00000000..e0479d6a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/i_s_deadlock.test
@@ -0,0 +1,158 @@
+--source include/have_rocksdb.inc
+
+set @prior_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_deadlock_detect = @@rocksdb_deadlock_detect;
+set @prior_max_latest_deadlocks = @@rocksdb_max_latest_deadlocks;
+set global rocksdb_deadlock_detect = on;
+set global rocksdb_lock_wait_timeout = 10000;
+--echo # Clears deadlock buffer of any prior deadlocks.
+set global rocksdb_max_latest_deadlocks = 0;
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+
+# needed by simple_deadlock.inc
+let $engine = rocksdb;
+
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+let $con1= `SELECT CONNECTION_ID()`;
+
+connect (con2,localhost,root,,);
+let $con2= `SELECT CONNECTION_ID()`;
+
+connect (con3,localhost,root,,);
+let $con3= `SELECT CONNECTION_ID()`;
+
+connection default;
+show create table information_schema.rocksdb_deadlock;
+
+create table t (i int primary key) engine=rocksdb;
+insert into t values (1), (2), (3);
+select * from information_schema.rocksdb_deadlock;
+
+echo Deadlock #1;
+--source include/simple_deadlock.inc
+connection default;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+
+echo Deadlock #2;
+--source include/simple_deadlock.inc
+connection default;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+set global rocksdb_max_latest_deadlocks = 10;
+
+echo Deadlock #3;
+--source include/simple_deadlock.inc
+connection default;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+set global rocksdb_max_latest_deadlocks = 1;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+
+connection con3;
+set rocksdb_deadlock_detect_depth = 2;
+
+echo Deadlock #4;
+connection con1;
+begin;
+select * from t where i=1 for update;
+
+connection con2;
+begin;
+select * from t where i=2 for update;
+
+connection con3;
+begin;
+select * from t where i=3 for update;
+
+connection con1;
+send select * from t where i=2 for update;
+
+connection con2;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con1 and waiting_key != "";
+--source include/wait_condition.inc
+
+send select * from t where i=3 for update;
+
+connection con3;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con2 and waiting_key != "";
+--source include/wait_condition.inc
+
+--error ER_LOCK_DEADLOCK
+select * from t where i=1 for update;
+rollback;
+
+connection con2;
+reap;
+rollback;
+
+connection con1;
+reap;
+rollback;
+
+connection default;
+set global rocksdb_max_latest_deadlocks = 5;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+
+echo Deadlock #5;
+connection con1;
+begin;
+select * from t where i=1 for update;
+
+connection con2;
+begin;
+select * from t where i=2 for update;
+
+connection con3;
+begin;
+select * from t where i=3 lock in share mode;
+
+connection con1;
+select * from t where i=100 for update;
+select * from t where i=101 for update;
+send select * from t where i=2 for update;
+
+connection con2;
+let $wait_condition = select count(*) = 1 from information_schema.rocksdb_trx
+where thread_id = $con1 and waiting_key != "";
+--source include/wait_condition.inc
+
+select * from t where i=3 lock in share mode;
+select * from t where i=200 for update;
+select * from t where i=201 for update;
+
+--error ER_LOCK_DEADLOCK
+select * from t where i=1 lock in share mode;
+rollback;
+
+connection con1;
+reap;
+rollback;
+
+connection con3;
+rollback;
+
+connection default;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+
+disconnect con1;
+disconnect con2;
+disconnect con3;
+
+set global rocksdb_lock_wait_timeout = @prior_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_deadlock_detect;
+drop table t;
+--replace_column 1 DEADLOCK_ID 2 TIMESTAMP 3 TRANSACTION_ID 5 WAITING_KEY 7 INDEX_NAME 8 TABLE_NAME
+select * from information_schema.rocksdb_deadlock;
+set global rocksdb_max_latest_deadlocks = 0;
+--echo # Clears deadlock buffer of any existent deadlocks.
+set global rocksdb_max_latest_deadlocks = @prior_max_latest_deadlocks;
+--replace_column 1 DEADLOCK_ID 2 TRANSACTION_ID 4 WAITING_KEY
+select * from information_schema.rocksdb_deadlock;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index.inc b/storage/rocksdb/mysql-test/rocksdb/t/index.inc
new file mode 100644
index 00000000..8b000a25
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index.inc
@@ -0,0 +1,155 @@
+#
+# Basic syntax related to indexes:
+# unique and non-unique keys,
+# single- and multi-column keys,
+# index option COMMENT.
+#
+# See other index* tests for operations
+# which are less likely to be supported
+#
+# PRIMARY KEY syntax is covered in index_primary test.
+# Index types BTREE|HASH -- in index_type_btree|hash tests.
+# SPATIAL -- in type_spatial_indexes test.
+# FULLTEXT -- in fulltext_search test.
+# KEY_BLOCK_SIZE -- in index_key_block_size test.
+#
+# Usage to call the test from another test:
+#
+# A calling test may define $index_type, in which case
+# USING clause will be added to the syntax.
+#
+
+################################################
+# TODO:
+# A part of the test is disabled because unique indexes
+# are not currently supported
+################################################
+
+
+let $using_index_type = ;
+if ($index_type)
+{
+ let $using_index_type = USING $index_type;
+}
+
+
+eval CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ KEY $using_index_type (a)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+DROP TABLE t1;
+
+eval CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ KEY a_b $using_index_type (a,b) COMMENT 'a_b index'
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+DROP TABLE t1;
+
+eval CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ KEY $using_index_type (a),
+ KEY $using_index_type (b)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+DROP TABLE t1;
+
+--disable_parsing
+
+eval CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ UNIQUE INDEX $using_index_type (a)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+--error ER_DUP_ENTRY,ER_DUP_KEY
+INSERT INTO t1 (a,b) VALUES (1,'c');
+
+DROP TABLE t1;
+
+--source drop_table_sync.inc
+
+--enable_parsing
+
+#
+# ALTER TABLE
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'z');
+
+eval ALTER TABLE t1 ADD KEY (a) $using_index_type COMMENT 'simple index on a';
+--replace_column 7 #
+SHOW INDEX FROM t1;
+ALTER TABLE t1 DROP KEY a;
+DROP TABLE t1;
+
+--disable_parsing
+
+eval CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ UNIQUE INDEX $using_index_type (a)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+--error ER_DUP_ENTRY,ER_DUP_KEY
+INSERT INTO t1 (a,b) VALUES (1,'c');
+
+ALTER TABLE t1 DROP INDEX a;
+INSERT INTO t1 (a,b) VALUES (1,'c');
+--error ER_DUP_ENTRY
+eval ALTER TABLE t1 ADD UNIQUE INDEX a(a) $using_index_type;
+DROP TABLE t1;
+
+--enable_parsing
+
+#
+# Test index prefix length limits.
+#
+set global rocksdb_large_prefix=0;
+
+CREATE TABLE t1 (
+ a BLOB(1024),
+ KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+# Should display warning
+CREATE TABLE t1 (
+ a BLOB(1024),
+ KEY (a(768))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+set global rocksdb_large_prefix=1;
+
+CREATE TABLE t1 (
+ a BLOB(4096),
+ KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+# Should display warning
+CREATE TABLE t1 (
+ a BLOB(4096),
+ KEY (a(3073))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+set global rocksdb_large_prefix=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index.test b/storage/rocksdb/mysql-test/rocksdb/t/index.test
new file mode 100644
index 00000000..e0c6175b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index.test
@@ -0,0 +1,44 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic syntax related to indexes:
+# unique and non-unique keys,
+# single- and multi-column keys,
+# index option COMMENT.
+#
+# See other index* tests for operations
+# which are less likely to be supported
+#
+# PRIMARY KEY syntax is covered in index_primary test.
+# Index types BTREE|HASH -- in index_type_btree|hash tests.
+# SPATIAL -- in type_spatial_indexes test.
+# FULLTEXT -- in fulltext_search test.
+# KEY_BLOCK_SIZE -- in index_key_block_size test.
+#
+
+# (Default index type)
+
+--source index.inc
+
+--echo #
+--echo # Issue #376: MyRocks: ORDER BY optimizer is unable to use the index extension
+--echo #
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int);
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+ pk int not null,
+ a int not null,
+ b int not null,
+ primary key(pk),
+ key(a)
+) engine=rocksdb;
+insert into t2 select A.a, FLOOR(A.a/10), A.a from t1 A;
+
+--echo # This must have type=range, index=a, and must not have 'Using filesort':
+--replace_column 9 #
+explain select * from t2 force index (a) where a=0 and pk>=3 order by pk;
+
+drop table t0,t1,t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_file_map-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/index_file_map-master.opt
new file mode 100644
index 00000000..436edf2b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_file_map-master.opt
@@ -0,0 +1 @@
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_file_map.test b/storage/rocksdb/mysql-test/rocksdb/t/index_file_map.test
new file mode 100644
index 00000000..8b0e2339
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_file_map.test
@@ -0,0 +1,54 @@
+--source include/have_rocksdb.inc
+
+#
+# Information Schema index file map
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+
+CREATE TABLE t1 (i INT PRIMARY KEY, j INT, INDEX(j)) ENGINE = ROCKSDB;
+CREATE TABLE t2 (k INT PRIMARY KEY, l INT REFERENCES t1.i) ENGINE = ROCKSDB;
+
+INSERT INTO t1 VALUES (1,2), (2,4), (3,6), (4,8), (5,10);
+INSERT INTO t2 VALUES (100,1), (200,2), (300,3), (400,4);
+
+COMMIT;
+
+# Flush memtable out to SST
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+
+###############################################################################
+# Test that expected index_file_map data exists
+###############################################################################
+
+# Return the data for the primary key of t1
+--replace_column 1 # 2 # 3 SSTNAME 5 # 6 # 7 # 8 # 9 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+ (SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+ WHERE TABLE_NAME = 't1' AND INDEX_NAME = "PRIMARY");
+
+# Return the data for the secondary index of t1
+--replace_column 1 # 2 # 3 SSTNAME 5 # 6 # 7 # 8 # 9 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+ (SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+ WHERE TABLE_NAME = 't1' AND INDEX_NAME = "j");
+
+# Return the data for the primary index of t2
+--replace_column 1 # 2 # 3 SSTNAME 5 # 6 # 7 # 8 # 9 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP
+WHERE INDEX_NUMBER =
+ (SELECT INDEX_NUMBER FROM INFORMATION_SCHEMA.ROCKSDB_DDL
+ WHERE TABLE_NAME = 't2' AND INDEX_NAME = "PRIMARY");
+
+# The number of sst files should be 1 or more
+select count(*) > 0 from information_schema.rocksdb_sst_props;
+
+# cleanup
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_key_block_size.test b/storage/rocksdb/mysql-test/rocksdb/t/index_key_block_size.test
new file mode 100644
index 00000000..f156aec0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_key_block_size.test
@@ -0,0 +1,70 @@
+--source include/have_rocksdb.inc
+
+#
+# KEY_BLOCK_SIZE index option.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ KEY (a) KEY_BLOCK_SIZE=8
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT PRIMARY KEY,
+ KEY ind1(b ASC) KEY_BLOCK_SIZE=0
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ PRIMARY KEY ind2(b(1) DESC) KEY_BLOCK_SIZE=32768 COMMENT 'big key_block_size value'
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ KEY a_b(a,b) KEY_BLOCK_SIZE=8192
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+
+DROP TABLE t1;
+
+--source drop_table_sync.inc
+
+#
+# ALTER TABLE
+#
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b) VALUES (100,'z');
+
+ALTER TABLE t1 ADD KEY(a) KEY_BLOCK_SIZE 8192;
+--replace_column 7 #
+SHOW INDEX FROM t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb-master.opt
new file mode 100644
index 00000000..c07b063f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=off --binlog_format=row --log-bin
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb.test b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb.test
new file mode 100644
index 00000000..887b4dd6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb.test
@@ -0,0 +1,110 @@
+--source include/have_rocksdb.inc
+
+#
+# index_merge_rocksdb test copied over from index_merge_ror.inc
+#
+# Triggers issue # https://github.com/facebook/mysql-5.6/issues/604
+
+CREATE TABLE t1
+(
+ /* fields/keys for row retrieval tests */
+ key1 INT,
+ key2 INT,
+ key3 INT,
+ key4 INT,
+
+ /* make rows much bigger then keys */
+ filler1 CHAR(200),
+
+ KEY(key1),
+ KEY(key2)
+) ENGINE=ROCKSDB;
+
+# fill table
+CREATE TABLE t0 AS SELECT * FROM t1;
+--disable_query_log
+--echo # Printing of many insert into t0 values (....) disabled.
+let $cnt=100;
+while ($cnt)
+{
+ eval INSERT INTO t0 VALUES (0, 0, 0, 0, 'data1');
+ dec $cnt;
+}
+
+--echo # Printing of many insert into t1 select .... from t0 disabled.
+let $1=4;
+while ($1)
+{
+ let $2=4;
+ while ($2)
+ {
+ let $3=4;
+ while ($3)
+ {
+ eval INSERT INTO t1 SELECT key1, key2, key3, key4, filler1 FROM t0;
+ dec $3;
+ }
+ dec $2;
+ }
+ dec $1;
+}
+
+--echo # Printing of many insert into t1 (...) values (....) disabled.
+# Row retrieval tests
+# -1 is used for values 'out of any range we are using'
+# insert enough rows for index intersection to be used for (key1,key2)
+INSERT INTO t1 (key1, key2, key3, key4, filler1) VALUES (100, 100, 100, 100,'key1-key2-key3-key4');
+let $cnt=400;
+while ($cnt)
+{
+ eval INSERT INTO t1 (key1, key2, key3, key4, filler1) VALUES (100, -1, 100, -1,'key1-key3');
+ dec $cnt;
+}
+let $cnt=400;
+while ($cnt)
+{
+ eval INSERT INTO t1 (key1, key2, key3, key4, filler1) VALUES (-1, 100, -1, 100,'key2-key4');
+ dec $cnt;
+}
+--enable_query_log
+
+SELECT COUNT(*) FROM t1;
+
+# flush the table first as statistics is calculated a bit differently for memtable and SST files
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+
+-- disable_query_log
+-- disable_result_log
+ANALYZE TABLE t1;
+-- enable_result_log
+-- enable_query_log
+
+--replace_column 9 #
+EXPLAIN UPDATE t1 SET filler1='to be deleted' WHERE key1=100 AND key2=100;
+UPDATE t1 SET filler1='to be deleted' WHERE key1=100 and key2=100;
+
+DROP TABLE t0, t1;
+
+# Issue624 - MyRocks executes index_merge query plan incorrectly
+create table t1 (key1 int, key2 int, key3 int, key (key1), key (key2), key(key3)) engine=rocksdb;
+insert into t1 values (1, 100, 100), (1, 200, 200), (1, 300, 300);
+--disable_query_log
+let $i = 1;
+while ($i <= 1000) {
+ let $insert = INSERT INTO t1 VALUES(1000,1000,1000);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+set global rocksdb_force_flush_memtable_now=1;
+analyze table t1;
+
+--replace_column 9 #
+explain select * from t1 where key1 = 1;
+--replace_column 9 #
+explain select key1,key2 from t1 where key1 = 1 or key2 = 1;
+select * from t1 where key1 = 1;
+select key1,key2 from t1 where key1 = 1 or key2 = 1;
+
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2-master.opt
new file mode 100644
index 00000000..7681c42e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=off --binlog_format=row --log-bin --rocksdb_records_in_range=2
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2.test b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2.test
new file mode 100644
index 00000000..2306558f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_merge_rocksdb2.test
@@ -0,0 +1,72 @@
+# Skiping this test from Valgrind execution as per Bug-14627884
+--source include/not_valgrind.inc
+# Adding big test option for this test.
+--source include/big_test.inc
+
+# t/index_merge_innodb.test
+#
+# Index merge tests (the test is called 'index_merge_rocksdb2' because
+# 'index_merge_rocksdb' has already existed before copying 'index_merge_innodb')
+#
+# Last update:
+# 2006-08-07 ML test refactored (MySQL 5.1)
+# Main code of several index_merge tests
+# -> include/index_merge*.inc
+# wrapper t/index_merge_innodb.test sources now several
+# include/index_merge*.inc files
+#
+
+--source include/have_rocksdb.inc
+let $engine_type= RocksDB;
+# skipping because too unstable in MyRocks
+let $skip_ror_EXPLAIN_for_MyRocks = 1;
+let $random_rows_in_EXPLAIN = 1;
+let $sorted_result = 1;
+# According to Oracle: "InnoDB's estimate for the index cardinality
+# depends on a pseudo random number generator (it picks up random
+# pages to sample). After an optimization that was made in r2625 two
+# EXPLAINs started returning a different number of rows (3 instead of
+# 4)", so:
+let $index_merge_random_rows_in_EXPLAIN = 1;
+# RocksDB does not support Merge tables (affects include/index_merge1.inc)
+let $merge_table_support= 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+--source include/index_merge1.inc
+set global rocksdb_force_flush_memtable_now=1;
+--source include/index_merge_ror.inc
+set global rocksdb_force_flush_memtable_now=1;
+--source include/index_merge2.inc
+
+set global rocksdb_force_flush_memtable_now=1;
+--source include/index_merge_2sweeps.inc
+set global rocksdb_force_flush_memtable_now=1;
+--source include/index_merge_ror_cpk.inc
+
+set global rocksdb_force_flush_memtable_now=1;
+--echo #
+--echo # Bug#11747423 32254: INDEX MERGE USED UNNECESSARILY
+--echo #
+CREATE TABLE t1 (
+ id INT NOT NULL PRIMARY KEY,
+ id2 INT NOT NULL,
+ id3 INT NOT NULL,
+ KEY (id2),
+ KEY (id3),
+ KEY covering_index (id2,id3)
+) ENGINE=RocksDB;
+
+INSERT INTO t1 VALUES (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7);
+INSERT INTO t1 SELECT id + 8, id2 + 8, id3 +8 FROM t1;
+INSERT INTO t1 SELECT id + 16, 7, 0 FROM t1;
+
+-- disable_query_log
+-- disable_result_log
+set global rocksdb_force_flush_memtable_now=1;
+analyze table t1;
+-- enable_result_log
+-- enable_query_log
+
+EXPLAIN SELECT SQL_NO_CACHE count(*) FROM t1 WHERE id2=7 AND id3=0;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_primary.test b/storage/rocksdb/mysql-test/rocksdb/t/index_primary.test
new file mode 100644
index 00000000..5dcfbaa8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_primary.test
@@ -0,0 +1,96 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic syntax related to primary keys
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY,
+ b CHAR(8)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW KEYS IN t1;
+
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'c');
+
+DROP TABLE t1;
+
+--error ER_MULTIPLE_PRI_KEY
+CREATE TABLE t1 (a INT PRIMARY KEY,
+ b CHAR(8) PRIMARY KEY
+) ENGINE=rocksdb;
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8),
+ PRIMARY KEY (a,b)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(1,'b'),(2,'a'),(2,'b');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'b');
+ DROP TABLE t1;
+
+# KEY in a column definition means PK!
+
+CREATE TABLE t1 (a INT KEY,
+ b CHAR(8),
+ KEY (b)
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT,
+ b CHAR(8) PRIMARY KEY
+) ENGINE=rocksdb;
+
+--replace_column 7 #
+SHOW INDEX IN t1;
+
+--error ER_MULTIPLE_PRI_KEY
+ALTER TABLE t1 ADD CONSTRAINT PRIMARY KEY pk (a);
+--replace_column 7 #
+SHOW KEYS IN t1;
+DROP TABLE t1;
+
+#
+# Test index prefix length limits.
+#
+set global rocksdb_large_prefix=0;
+
+CREATE TABLE t1 (
+ a BLOB(1024),
+ PRIMARY KEY (a(767))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+--error ER_TOO_LONG_KEY
+CREATE TABLE t1 (
+ a BLOB(1024),
+ PRIMARY KEY (a(768))
+) ENGINE=rocksdb;
+
+set global rocksdb_large_prefix=1;
+
+CREATE TABLE t1 (
+ a BLOB(4096),
+ PRIMARY KEY (a(3072))
+) ENGINE=rocksdb;
+DROP TABLE t1;
+
+--error ER_TOO_LONG_KEY
+CREATE TABLE t1 (
+ a BLOB(4096),
+ PRIMARY KEY (a(3073))
+) ENGINE=rocksdb;
+
+set global rocksdb_large_prefix=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_type_btree.test b/storage/rocksdb/mysql-test/rocksdb/t/index_type_btree.test
new file mode 100644
index 00000000..4adc5b55
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_type_btree.test
@@ -0,0 +1,12 @@
+--source include/have_rocksdb.inc
+
+#
+# Index type BTREE
+#
+
+let $index_type = BTREE;
+
+--source index.inc
+
+let $index_type =;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/index_type_hash.test b/storage/rocksdb/mysql-test/rocksdb/t/index_type_hash.test
new file mode 100644
index 00000000..f3dc9cf5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/index_type_hash.test
@@ -0,0 +1,12 @@
+--source include/have_rocksdb.inc
+
+#
+# Index type HASH
+#
+
+let $index_type = HASH;
+
+--source index.inc
+
+let $index_type =;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/information_schema-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/information_schema-master.opt
new file mode 100644
index 00000000..86379847
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/information_schema-master.opt
@@ -0,0 +1 @@
+--binlog_format=row --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/information_schema.test b/storage/rocksdb/mysql-test/rocksdb/t/information_schema.test
new file mode 100644
index 00000000..09998b9a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/information_schema.test
@@ -0,0 +1,89 @@
+--source include/have_rocksdb.inc
+--source include/have_log_bin.inc
+
+--source include/restart_mysqld.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+--enable_warnings
+
+# MariaDB: the following is for handling the case where the tests
+# is started on a totally empty datadir, where no MyRocks table has
+# ever been created). In that case, there is no MAX_INDEX_ID.
+# Create/drop a table so that we do have MAX_INDEX_ID.
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+create table t1 (a int) engine=rocksdb;
+drop table t1;
+
+--let $max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+--replace_result $max_index_id max_index_id
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type<>'DDL_DROP_INDEX_ONGOING';
+select count(*) from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type<>'DDL_DROP_INDEX_ONGOING';
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+
+select VALUE into @keysIn from INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS where CF_NAME = 'default' and LEVEL = 'Sum' and TYPE = 'KeyIn';
+
+CREATE TABLE t1 (i1 INT, i2 INT, PRIMARY KEY (i1)) ENGINE = ROCKSDB;
+INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+
+set global rocksdb_force_flush_memtable_now = true;
+# No binlog coordinates in MariaDB: --let $MASTER_UUID = query_get_value(SELECT @@SERVER_UUID, @@SERVER_UUID, 1)
+--let $max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+# No binlog coordinates in MariaDB: --replace_result $MASTER_UUID uuid $max_index_id max_index_id
+--replace_result $max_index_id max_index_id
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+select count(*) from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO;
+
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf='default';
+select case when VALUE-@keysIn >= 3 then 'true' else 'false' end from INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS where CF_NAME = 'default' and LEVEL = 'Sum' and TYPE = 'KeyIn';
+
+CREATE INDEX tindex1 on t1 (i1);
+--let $start_max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+
+CREATE INDEX tindex2 on t1 (i2);
+--let $end_max_index_id = query_get_value(SELECT * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where type = 'MAX_INDEX_ID', VALUE, 1)
+
+if ($end_max_index_id <= $start_max_index_id) {
+ echo Max index ID did not increase;
+}
+
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where TYPE = 'CF_FLAGS';
+
+CREATE TABLE t2 (
+ a int,
+ b int,
+ c int,
+ d int,
+ PRIMARY KEY (a) COMMENT "cf_a",
+ KEY (b) COMMENT "cf_b",
+ KEY (c) COMMENT "cf_c",
+ KEY (d) COMMENT "rev:cf_d") ENGINE=ROCKSDB;
+
+select * from INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO where TYPE = 'CF_FLAGS';
+
+CREATE TABLE t3 (a INT, PRIMARY KEY (a)) ENGINE=ROCKSDB;
+insert into t3 (a) values (1), (2), (3);
+SET @ORIG_ROCKSDB_PAUSE_BACKGROUND_WORK = @@GLOBAL.ROCKSDB_PAUSE_BACKGROUND_WORK;
+--let $t3_index_id = query_get_value(SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_DDL WHERE TABLE_NAME = 't3', INDEX_NUMBER, 1)
+--let $t3_cf_id = query_get_value(SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_DDL WHERE TABLE_NAME = 't3', COLUMN_FAMILY, 1)
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=1;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+DROP TABLE t3;
+--let $result = query_get_value("SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO WHERE TYPE = 'DDL_DROP_INDEX_ONGOING' AND NAME LIKE 'cf_id:$t3_cf_id,index_id:$t3_index_id'", NAME, 1)
+--echo $result
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+--echo next line shouldn't cause assertion to fail
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK=0;
+SHOW GLOBAL VARIABLES LIKE 'ROCKSDB_PAUSE_BACKGROUND_WORK';
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_ROCKSDB_PAUSE_BACKGROUND_WORK;
+
+DROP TABLE t1;
+DROP TABLE t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/init_stats_procedure.inc b/storage/rocksdb/mysql-test/rocksdb/t/init_stats_procedure.inc
new file mode 100644
index 00000000..dda253bc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/init_stats_procedure.inc
@@ -0,0 +1,40 @@
+# This inc script creates two procedures -- save_read_stats() and
+# get_read_stats(). get_read_stats() prints differential rocksdb_rows_read,
+# rocksdb_rows_updated, and rocksdb_rows_deleted values since calling
+# save_read_stats().
+
+delimiter //;
+create procedure save_read_stats()
+begin
+ /*select rows_requested into @rq from information_schema.table_statistics
+ where table_schema=database() and table_name='t1';*/
+ select rows_read into @rr_is from information_schema.table_statistics
+ where table_schema=database() and table_name='t1';
+ select variable_value into @rr from information_schema.global_status
+ where variable_name='rocksdb_rows_read';
+ select variable_value into @ru from information_schema.global_status
+ where variable_name='rocksdb_rows_updated';
+ select variable_value into @rd from information_schema.global_status
+ where variable_name='rocksdb_rows_deleted';
+end//
+
+create procedure get_read_stats()
+begin
+ /*select rows_requested - @rq as rows_requested from
+ information_schema.table_statistics
+ where table_schema=database() and table_name='t1';*/
+ select rows_read - @rr_is as rows_read_userstat from
+ information_schema.table_statistics
+ where table_schema=database() and table_name='t1';
+ select variable_value - @rr as rows_read from
+ information_schema.global_status
+ where variable_name='rocksdb_rows_read';
+ select variable_value - @ru as rows_updated from
+ information_schema.global_status
+ where variable_name='rocksdb_rows_updated';
+ select variable_value - @rd as rows_deleted from
+ information_schema.global_status
+ where variable_name='rocksdb_rows_deleted';
+end//
+delimiter ;//
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled-master.opt
new file mode 100644
index 00000000..b3565b5f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled-master.opt
@@ -0,0 +1,30 @@
+--loose-enable-innodb_trx
+--loose-enable-innodb_file_status
+--loose-enable-innodb_locks
+--loose-enable-innodb_lock_waits
+--loose-enable-innodb_cmp
+--loose-enable-innodb_cmp_reset
+--loose-enable-innodb_cmp_per_index
+--loose-enable-innodb_cmp_per_index_reset
+--loose-enable-innodb_cmpmem
+--loose-enable-innodb_cmpmem_reset
+--loose-enable-innodb_metrics
+--loose-enable-innodb_ft_default_stopword
+--loose-enable-innodb_ft_deleted
+--loose-enable-innodb_ft_being_deleted
+--loose-enable-innodb_ft_index_cache
+--loose-enable-innodb_ft_index_table
+--loose-enable-innodb_ft_config
+--loose-enable-innodb_buffer_pool_stats
+--loose-enable-innodb_buffer_page
+--loose-enable-innodb_buffer_page_lru
+--loose-enable-innodb_sys_tables
+--loose-enable-innodb_sys_tablestats
+--loose-enable-innodb_sys_indexes
+--loose-enable-innodb_sys_columns
+--loose-enable-innodb_sys_fields
+--loose-enable-innodb_sys_foreign
+--loose-enable-innodb_sys_foreign_cols
+--loose-enable-innodb_sys_tablespaces
+--loose-enable-innodb_sys_datafiles
+--loose-enable-innodb_sys_docstore_fields
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled.test b/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled.test
new file mode 100644
index 00000000..4ff48e13
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/innodb_i_s_tables_disabled.test
@@ -0,0 +1,43 @@
+--source include/have_rocksdb.inc
+
+# Make sure that the InnoDb information schema tables are disabled when InnoDB
+# is turned off and attempting to access them doesn't crash.
+
+# Disable warnings, as the table names in warnings appear in lower or uppercase
+# depending on platform
+
+--disable_warnings
+
+SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
+#Not in MariaDB: SELECT * FROM INFORMATION_SCHEMA.INNODB_FILE_STATUS;
+
+SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_RESET;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMP_PER_INDEX_RESET;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMPMEM;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_CMPMEM_RESET;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESTATS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_COLUMNS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FIELDS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FOREIGN;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_FOREIGN_COLS;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES;
+SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_DATAFILES;
+#Not in MariaDB: SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_DOCSTORE_FIELDS;
+
+--enable_warnings
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/insert.test b/storage/rocksdb/mysql-test/rocksdb/t/insert.test
new file mode 100644
index 00000000..aebd3f76
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/insert.test
@@ -0,0 +1,99 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic INSERT statements
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+# INSERT [INTO] .. VALUES|VALUE ..
+
+INSERT INTO t1 VALUES (100,'foobar',100),(1,'a',101),(2,'b',103),(3,'c',104),(4,'d',105),(5,'e',106);
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT t1 VALUE (10,'foo',107),(11,'abc',108);
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT INTO t1 (b,a) VALUES ('test',0);
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT INTO t1 VALUES (DEFAULT,DEFAULT,NULL);
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT t1 (a) VALUE (10),(20);
+--sorted_result
+SELECT a,b FROM t1;
+
+# INSERT [INTO] .. SET
+
+INSERT INTO t1 SET a = 11, b = 'f';
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT t1 SET b = DEFAULT;
+--sorted_result
+SELECT a,b FROM t1;
+
+
+# INSERT .. SELECT
+
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+INSERT INTO t2 SELECT a,b,pk FROM t1;
+INSERT INTO t1 (a) SELECT a FROM t2 WHERE b = 'foo';
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT t1 (a,b) SELECT a,b FROM t1;
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1, t2;
+
+#
+# Transactional INSERT
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+BEGIN;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(100,'foo');
+INSERT t1 (a,b) VALUE (10,'foo'),(11,'abc');
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+BEGIN;
+INSERT INTO t1 (b,a) VALUES ('test',0);
+SAVEPOINT spt1;
+INSERT INTO t1 (a,b) VALUES (DEFAULT,DEFAULT);
+RELEASE SAVEPOINT spt1;
+INSERT INTO t1 (a,b) VALUES (DEFAULT,DEFAULT);
+ROLLBACK;
+--sorted_result
+SELECT a,b FROM t1;
+
+BEGIN;
+INSERT t1 (a) VALUE (10),(20);
+SAVEPOINT spt1;
+INSERT INTO t1 SET a = 11, b = 'f';
+INSERT t1 SET b = DEFAULT;
+--error ER_ROLLBACK_TO_SAVEPOINT
+ROLLBACK TO SAVEPOINT spt1;
+INSERT INTO t1 (b,a) VALUES ('test1',10);
+--error ER_ROLLBACK_ONLY
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/insert_optimized_config.test b/storage/rocksdb/mysql-test/rocksdb/t/insert_optimized_config.test
new file mode 100644
index 00000000..a24851d9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/insert_optimized_config.test
@@ -0,0 +1,51 @@
+--source include/have_rocksdb.inc
+--source include/have_write_committed.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# reload with load optimized config
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+--exec echo "wait" > $restart_file
+--shutdown_server
+--source include/wait_until_disconnected.inc
+-- exec echo "restart:--rocksdb_write_disable_wal=1 --rocksdb_flush_log_at_trx_commit=0 --rocksdb_default_cf_options=write_buffer_size=16k;target_file_size_base=16k;level0_file_num_compaction_trigger=4;level0_slowdown_writes_trigger=256;level0_stop_writes_trigger=256;max_write_buffer_number=16;compression_per_level=kNoCompression;memtable=vector:1024 --rocksdb_override_cf_options=__system__={memtable=skip_list:16} --rocksdb_compaction_sequential_deletes=0 --rocksdb_compaction_sequential_deletes_window=0 --rocksdb_allow_concurrent_memtable_write=0" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+create table t1(
+ id bigint not null primary key,
+ i1 bigint, #unique
+ i2 bigint, #repeating
+ c1 varchar(20), #unique
+ c2 varchar(20), #repeating
+ index t1_2(i1)
+) engine=rocksdb;
+
+--disable_query_log
+set rocksdb_bulk_load=1;
+let $i=0;
+while ($i<50000)
+{
+ inc $i;
+ eval insert t1(id, i1, i2, c1, c2) values($i, $i, $i div 10, $i, $i div 10);
+}
+set rocksdb_bulk_load=0;
+--enable_query_log
+
+select count(*), sum(id), sum(i1), sum(i2) from t1;
+
+# reload without load optimized config
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+--exec echo "wait" > $restart_file
+--shutdown_server
+--source include/wait_until_disconnected.inc
+-- exec echo "restart:--rocksdb_write_disable_wal=0 --rocksdb_default_cf_options=write_buffer_size=64k;target_file_size_base=64k;max_bytes_for_level_base=1m;compression_per_level=kNoCompression;" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+select count(*), sum(id), sum(i1), sum(i2) from t1;
+
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/insert_with_keys.test b/storage/rocksdb/mysql-test/rocksdb/t/insert_with_keys.test
new file mode 100644
index 00000000..1332fe14
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/insert_with_keys.test
@@ -0,0 +1,169 @@
+--source include/have_rocksdb.inc
+
+#
+# INSERT statements for tables with keys
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, KEY(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+INSERT INTO t1 (a,b) VALUES (0,'');
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+INSERT INTO t1 (a,b) VALUES (3,'a'),(4,'d') ON DUPLICATE KEY UPDATE a = a+10;
+
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+INSERT INTO t1 (a,b) VALUES (100,'b'), (2,'c');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z') ON DUPLICATE KEY UPDATE a = a+VALUES(a);
+--sorted_result
+SELECT a,b FROM t1;
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (101,'x'),(101,'x');
+
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+INSERT INTO t1 (a,b) VALUES (100,'a'), (6,'f');
+INSERT INTO t1 (a,b) VALUES (30,'m'),(29,'n');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (3,'a'),(0,'');
+INSERT INTO t1 (a,b) VALUES (0,'');
+--sorted_result
+SELECT a,b FROM t1;
+
+INSERT IGNORE INTO t1 (a,b) VALUES (1,'a'),(12345,'z');
+INSERT INTO t1 (a,b) VALUES (1,'a'),(12345,'z') ON DUPLICATE KEY UPDATE b = CONCAT(b,b);
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+#
+# INSERT on DUPLICATE KEY UPDATE with multiple keys
+#
+--echo
+--echo INSERT on DUPLICATE KEY UPDATE with multiple keys
+--echo
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(255)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+#
+# INSERT on DUPLICATE KEY UPDATE with secondary key
+#
+--echo
+--echo INSERT on DUPLICATE KEY UPDATE with secondary key
+--echo
+CREATE TABLE t1 (a INT, b CHAR(8), c INT DEFAULT 0, pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c') ON DUPLICATE KEY UPDATE c = c + 1;
+
+--sorted_result
+SELECT a,b,c,pk FROM t1;
+
+DROP TABLE t1;
+
+--echo
+--echo Disable caching and see if it still functions properly
+--echo
+SELECT @@rocksdb_enable_insert_with_update_caching;
+SET GLOBAL rocksdb_enable_insert_with_update_caching=0;
+SELECT @@rocksdb_enable_insert_with_update_caching;
+
+#
+# INSERT on DUPLICATE KEY UPDATE with multiple keys
+#
+--echo
+--echo INSERT on DUPLICATE KEY UPDATE with multiple keys
+--echo
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(255)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (3,'a'), (4,'a'), (5,'a'), (6,'a'), (7,'a'), (8,'a'), (9,'a'), (10,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a'), (1,'a'), (2,'a') ON DUPLICATE KEY UPDATE b = CONCAT(b, 'a');
+
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+#
+# INSERT on DUPLICATE KEY UPDATE with secondary key
+#
+--echo
+--echo INSERT on DUPLICATE KEY UPDATE with secondary key
+--echo
+CREATE TABLE t1 (a INT, b CHAR(8), c INT DEFAULT 0, pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a');
+INSERT INTO t1 (a,b) VALUES (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a'), (1,'a') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'), (5,'e'), (6,'f'), (7,'g'), (8,'h'), (9,'i'), (10,'j') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b'), (1,'a'), (2,'b') ON DUPLICATE KEY UPDATE c = c + 1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c'), (1,'a'), (2,'c') ON DUPLICATE KEY UPDATE c = c + 1;
+
+--sorted_result
+SELECT a,b,c,pk FROM t1;
+
+DROP TABLE t1;
+
+--echo
+--echo Cleanup
+--echo
+SET GLOBAL rocksdb_enable_insert_with_update_caching=1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue100.test b/storage/rocksdb/mysql-test/rocksdb/t/issue100.test
new file mode 100644
index 00000000..b0b3eb7a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue100.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+create table t1 (
+ id int,
+ value int,
+ primary key (id)
+) engine=rocksdb;
+
+insert into t1 values(1,1),(2,2);
+set autocommit=0;
+
+begin;
+insert into t1 values (50,50);
+select * from t1;
+
+update t1 set id=id+100;
+
+select * from t1;
+
+rollback;
+set autocommit=1;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete-master.opt
new file mode 100644
index 00000000..436edf2b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete-master.opt
@@ -0,0 +1 @@
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete.test b/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete.test
new file mode 100644
index 00000000..743bf7dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue100_delete.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+create table ten(a int primary key);
+insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table one_k(a int primary key);
+insert into one_k select A.a + B.a* 10 + C.a * 100 from ten A, ten B, ten C;
+
+create table t100(pk int primary key, a int, b int, key(a));
+insert into t100 select a,a,a from test.one_k;
+
+set global rocksdb_force_flush_memtable_now=1;
+select num_rows, entry_deletes, entry_singledeletes from information_schema.rocksdb_index_file_map where index_number = (select max(index_number) from information_schema.rocksdb_index_file_map) order by entry_deletes, entry_singledeletes;
+
+update t100 set a=a+1;
+set global rocksdb_force_flush_memtable_now=1;
+select num_rows, entry_deletes, entry_singledeletes from information_schema.rocksdb_index_file_map where index_number = (select max(index_number) from information_schema.rocksdb_index_file_map) order by entry_deletes, entry_singledeletes;
+
+drop table ten, t100, one_k;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue111.test b/storage/rocksdb/mysql-test/rocksdb/t/issue111.test
new file mode 100644
index 00000000..671ea470
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue111.test
@@ -0,0 +1,38 @@
+--source include/have_rocksdb.inc
+
+connect (con2,localhost,root,,);
+connection default;
+
+create table t1 (
+ pk int not null primary key,
+ col1 int not null,
+ col2 int not null,
+ key(col1)
+) engine=rocksdb;
+
+create table ten(a int primary key);
+insert into ten values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table one_k(a int primary key);
+insert into one_k select A.a + B.a* 10 + C.a * 100 from ten A, ten B, ten C;
+
+insert into t1 select a,a,a from one_k;
+
+--echo # Start the transaction, get the snapshot
+begin;
+select * from t1 where col1<10;
+
+--echo # Connect with another connection and make a conflicting change
+connection con2;
+
+begin;
+update t1 set col2=123456 where pk=0;
+commit;
+
+connection default;
+
+--error ER_LOCK_DEADLOCK
+update t1 set col2=col2+1 where col1 < 10 limit 5;
+
+disconnect con2;
+drop table t1, ten, one_k;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue243_transactionStatus.test b/storage/rocksdb/mysql-test/rocksdb/t/issue243_transactionStatus.test
new file mode 100644
index 00000000..0997bde3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue243_transactionStatus.test
@@ -0,0 +1,80 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ id INT,
+ val1 INT,
+ val2 INT,
+ PRIMARY KEY (id)
+) ENGINE=rocksdb;
+
+#
+# DB operations without Transaction , every count should be 0
+# so there's no count related output
+#
+INSERT INTO t1 VALUES(1,1,1),(2,1,2);
+SELECT * FROM t1;
+UPDATE t1 SET val1=2 WHERE id=2;
+SELECT * FROM t1;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+#
+# DB operations with Tansaction, insert_count, update_count, delete_count
+# and total write_count should be printed
+# Cases: rollback, commit transaction
+#
+SET AUTOCOMMIT=0;
+START TRANSACTION;
+INSERT INTO t1 VALUES(20,1,1),(30,30,30);
+SELECT * FROM t1;
+UPDATE t1 SET val1=20, val2=20 WHERE id=20;
+SELECT * FROM t1;
+DELETE FROM t1 WHERE id=30;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+ROLLBACK;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+START TRANSACTION;
+INSERT INTO t1 VALUES(40,40,40);
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+COMMIT;
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+SET AUTOCOMMIT=1;
+DROP TABLE t1;
+
+#
+# Secondary Key Tests
+#
+--disable_warnings
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+CREATE TABLE t2 (
+ id1 INT,
+ id2 INT,
+ value INT,
+ PRIMARY KEY (id1),
+ KEY (id2)
+) ENGINE=rocksdb;
+
+SET AUTOCOMMIT=0;
+START TRANSACTION;
+INSERT INTO t2 VALUES(1,2,0),(10,20,30);
+UPDATE t2 SET value=3 WHERE id2=2;
+DELETE FROM t2 WHERE id1=10;
+
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+ROLLBACK;
+SET AUTOCOMMIT=1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue255.test b/storage/rocksdb/mysql-test/rocksdb/t/issue255.test
new file mode 100644
index 00000000..686f45b4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue255.test
@@ -0,0 +1,52 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (pk BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT);
+
+INSERT INTO t1 VALUES (5);
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+INSERT INTO t1 VALUES ('538647864786478647864');
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SELECT * FROM t1;
+--replace_column 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES ();
+SELECT * FROM t1;
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES ();
+SELECT * FROM t1;
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (pk TINYINT NOT NULL PRIMARY KEY AUTO_INCREMENT);
+
+INSERT INTO t1 VALUES (5);
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+INSERT INTO t1 VALUES (1000);
+SELECT * FROM t1;
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES ();
+SELECT * FROM t1;
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES ();
+SELECT * FROM t1;
+--replace_column 3 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue290.test b/storage/rocksdb/mysql-test/rocksdb/t/issue290.test
new file mode 100644
index 00000000..5ea8799c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue290.test
@@ -0,0 +1,40 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE `linktable` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) NOT NULL DEFAULT '',
+ `time` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `version` int(11) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (link_type, `id1`,`id2`) COMMENT 'cf_link_pk',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`id2`,`version`,`data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=RocksDB DEFAULT COLLATE=latin1_bin;
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $insert = INSERT INTO linktable (id1, link_type, id2) values (1, 1, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+set global rocksdb_force_flush_memtable_now=1;
+
+insert into linktable (id1, link_type, id2) values (2, 1, 1);
+insert into linktable (id1, link_type, id2) values (2, 1, 2);
+insert into linktable (id1, link_type, id2) values (2, 1, 3);
+insert into linktable (id1, link_type, id2) values (2, 1, 4);
+insert into linktable (id1, link_type, id2) values (2, 1, 5);
+insert into linktable (id1, link_type, id2) values (2, 1, 6);
+insert into linktable (id1, link_type, id2) values (2, 1, 7);
+insert into linktable (id1, link_type, id2) values (2, 1, 8);
+insert into linktable (id1, link_type, id2) values (2, 1, 9);
+insert into linktable (id1, link_type, id2) values (2, 1, 10);
+
+--replace_column 9 #
+explain select id1, id2, link_type, data from linktable force index(primary) where id1=2 and link_type=1 and (id2=1 or id2=2 or id2=3 or id2=4 or id2=5);
+
+drop table linktable;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue314.test b/storage/rocksdb/mysql-test/rocksdb/t/issue314.test
new file mode 100644
index 00000000..822969a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue314.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+CREATE TABLE t1(a int);
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+INSERT INTO t1 VALUES(1);
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+select * from t1;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+select * from t1;
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue495.test b/storage/rocksdb/mysql-test/rocksdb/t/issue495.test
new file mode 100644
index 00000000..5dcc7c19
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue495.test
@@ -0,0 +1,34 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+--disable_warnings
+drop table if exists t;
+--enable_warnings
+
+create table t (
+ a int,
+ b int,
+ c varchar(12249) collate latin1_bin,
+ d datetime,
+ e int,
+ f int,
+ g blob,
+ h int,
+ i int,
+ key (b,e),
+ key (h,b)
+) engine=rocksdb
+partition by linear hash (i) partitions 8 ;
+
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+insert into t values (1,1,'a',now(),1,1,'a',1,1);
+select i from t group by h;
+select i from t group by h;
+
+drop table t;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue884.test b/storage/rocksdb/mysql-test/rocksdb/t/issue884.test
new file mode 100644
index 00000000..6bf3e517
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue884.test
@@ -0,0 +1,43 @@
+--source include/have_rocksdb.inc
+
+create table test (
+ a bigint(20) not null,
+ b bigint(20) not null,
+ c varchar(500) not null,
+ d bigint(20) not null,
+ e bigint(20) not null,
+ f varchar(500) not null,
+ g varchar(500) not null,
+ h varchar(500) not null,
+ i varchar(1000) not null,
+ j varchar(16384) not null,
+ k varchar(200) not null,
+ l varchar(500) not null,
+ m varchar(100) not null,
+ n bigint(20) not null,
+ primary key (a, b, m, c(100), l(100), d, e, f(100), g(100), h(100), n),
+ key n (n),
+ key d (d, a)
+) engine = rocksdb default charset = latin1;
+
+--disable_query_log
+let $i = 1000;
+while ($i) {
+ --eval insert into test values (10, 1, "i", $i / 100, $i, "f", "g", "h", "i", "j", "k", "l", "m", $i);
+ --eval insert into test values (10, 2, "i", $i / 100, $i, "f", "g", "h", "i", "j", "k", "l", "m", $i);
+ --eval insert into test values (10, 3, "i", $i / 100, $i, "f", "g", "h", "i", "j", "k", "l", "m", $i);
+ --eval insert into test values (10, 4, "i", $i / 100, $i, "f", "g", "h", "i", "j", "k", "l", "m", $i);
+ --eval insert into test values (10, 5, "i", $i / 100, $i, "f", "g", "h", "i", "j", "k", "l", "m", $i);
+ dec $i;
+}
+set global rocksdb_force_flush_memtable_now = true;
+analyze table test;
+--enable_query_log
+
+--replace_column 9 #
+explain
+select * from test where d = 10 and a = 10 and b = 2;
+select * from test where d = 10 and a = 10 and b = 2;
+
+
+drop table test;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue896.test b/storage/rocksdb/mysql-test/rocksdb/t/issue896.test
new file mode 100644
index 00000000..ba57fb99
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue896.test
@@ -0,0 +1,17 @@
+# issue 896 : Segmentation fault in myrocks::Rdb_string_reader::read
+--source include/have_rocksdb.inc
+
+CREATE TABLE `t1` (
+`a` bigint(20) NOT NULL,
+`b` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+`u` bigint(20) unsigned NOT NULL,
+`d` bigint(20) DEFAULT NULL,
+PRIMARY KEY (`a`,`b`),
+KEY `d` (`d`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='ttl_duration=1000;ttl_col=u';
+INSERT INTO t1 VALUES (100, 'aaabbb', UNIX_TIMESTAMP(), 200);
+--replace_column 9 #
+EXPLAIN SELECT COUNT(*) FROM t1 FORCE INDEX(d);
+--echo # segfault here without the fix
+SELECT COUNT(*) FROM t1 FORCE INDEX(d);
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/issue900.test b/storage/rocksdb/mysql-test/rocksdb/t/issue900.test
new file mode 100644
index 00000000..ce52e0ad
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/issue900.test
@@ -0,0 +1,13 @@
+--source include/have_rocksdb.inc
+
+# Issue 900 : Segmentation fault in myrocks::Rdb_string_reader::read
+CREATE TABLE t1(c1 VARCHAR(1) CHARACTER SET 'utf8' COLLATE 'utf8_bin', c2 YEAR, c3 REAL(1,0) UNSIGNED, PRIMARY KEY(c1)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES(0,'0','0');
+INSERT INTO t1 VALUES('{0}','0','0');
+INSERT INTO t1 VALUES('1','0','1');
+# Would segfault here
+--error ER_DUP_ENTRY
+ALTER TABLE t1 ADD INDEX(c3), ADD UNIQUE (c3);
+--error ER_KEY_DOES_NOT_EXISTS
+SELECT c3 FROM t1 FORCE INDEX(c3) ORDER BY c3;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds-master.opt
new file mode 100644
index 00000000..d7743993
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_default_cf_options=write_buffer_size=256k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;}
+--rocksdb_override_cf_options=rev:bf5_1={prefix_extractor=capped:12};
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds.test b/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds.test
new file mode 100644
index 00000000..2cced2a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/iterator_bounds.test
@@ -0,0 +1,29 @@
+#
+# Issue #878: Descending scans from reverse column families return no results
+# due to iterator bounds
+#
+
+create table t (i int primary key) engine=rocksdb;
+
+let $cond=1;
+while ($cond)
+{
+ --disable_query_log
+ truncate table t;
+ --enable_query_log
+ let $cond=`select RIGHT(HEX(index_number), 2) != "FD" from information_schema.rocksdb_ddl where table_name = 't'`;
+}
+
+# Index id is now at FD. Create a table with primary and secondary key, so
+# that the secondary key index id ends in 0xFF.
+
+drop table t;
+create table t (i int primary key, j int, key(j) comment 'rev:bf5_2') engine=rocksdb;
+select RIGHT(HEX(index_number), 2) from information_schema.rocksdb_ddl where table_name = 't';
+
+insert into t values (1, 1);
+
+select j from t order by j asc;
+select j from t order by j desc;
+
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/kill.test b/storage/rocksdb/mysql-test/rocksdb/t/kill.test
new file mode 100644
index 00000000..a2809d96
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/kill.test
@@ -0,0 +1,9 @@
+--source include/have_binlog_format_row.inc
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+LOCK TABLE t1 WRITE;
+SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+FLUSH TABLES;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/level_read_committed.test b/storage/rocksdb/mysql-test/rocksdb/t/level_read_committed.test
new file mode 100644
index 00000000..bedf87a8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/level_read_committed.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = READ COMMITTED;
+
+--source transaction_isolation.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.opt b/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.opt
new file mode 100644
index 00000000..418e4c3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true \ No newline at end of file
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.test b/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.test
new file mode 100644
index 00000000..8ee3af60
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/level_read_uncommitted.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = READ UNCOMMITTED;
+--source transaction_isolation.inc
+--source consistent_snapshot.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/level_repeatable_read.test b/storage/rocksdb/mysql-test/rocksdb/t/level_repeatable_read.test
new file mode 100644
index 00000000..cf29073f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/level_repeatable_read.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = REPEATABLE READ;
+--source transaction_isolation.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/level_serializable.test b/storage/rocksdb/mysql-test/rocksdb/t/level_serializable.test
new file mode 100644
index 00000000..9b5db0e8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/level_serializable.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $trx_isolation = SERIALIZABLE;
+--source transaction_isolation.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/loaddata.inc b/storage/rocksdb/mysql-test/rocksdb/t/loaddata.inc
new file mode 100644
index 00000000..1d83598a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/loaddata.inc
@@ -0,0 +1,117 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic LOAD DATA statements
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+let $datadir = `SELECT @@datadir`;
+
+--write_file $datadir/se_loaddata.dat
+1,foo,
+2,bar,
+3,,
+4,abc,
+EOF
+
+--replace_result $datadir <DATADIR>
+eval
+LOAD DATA INFILE '$datadir/se_loaddata.dat' INTO TABLE t1
+ FIELDS TERMINATED BY ',' (a,b);
+--sorted_result
+SELECT a,b FROM t1;
+
+--replace_result $datadir <DATADIR>
+eval
+LOAD DATA LOCAL INFILE '$datadir/se_loaddata.dat' INTO TABLE t1
+ CHARACTER SET utf8 COLUMNS TERMINATED BY ','
+ ESCAPED BY '/' (a,b);
+--sorted_result
+SELECT a,b FROM t1;
+
+--remove_file $datadir/se_loaddata.dat
+--write_file $datadir/se_loaddata.dat
+5;YYY;
+102;'zzz';
+0;'test';
+EOF
+
+--replace_result $datadir <DATADIR>
+eval
+LOAD DATA LOCAL INFILE '$datadir/se_loaddata.dat' INTO TABLE t1
+ FIELDS TERMINATED BY ';'
+ (a) SET b='loaded';
+
+--sorted_result
+SELECT a,b FROM t1;
+
+--remove_file $datadir/se_loaddata.dat
+--write_file $datadir/se_loaddata.dat
+prefix:5;'foo';
+prefix:6;'';
+prefix:100;foo;
+prefix:7;'test';suffix
+101;abc;
+102;'z';
+prefix:0;;
+EOF
+
+--replace_result $datadir <DATADIR>
+eval
+LOAD DATA INFILE '$datadir/se_loaddata.dat' INTO TABLE t1
+ FIELDS TERMINATED BY ';'
+ OPTIONALLY ENCLOSED BY ''''
+ LINES STARTING BY 'prefix:'
+ IGNORE 2 LINES (a,b);
+
+--sorted_result
+SELECT a,b FROM t1;
+
+--remove_file $datadir/se_loaddata.dat
+--write_file $datadir/se_loaddata.dat
+1 foo
+2 bar
+3
+4 abc
+EOF
+
+--replace_result $datadir <DATADIR>
+eval
+LOAD DATA INFILE '$datadir/se_loaddata.dat' INTO TABLE t1;
+--sorted_result
+SELECT a,b FROM t1;
+--remove_file $datadir/se_loaddata.dat
+
+
+--write_file $datadir/se_replacedata.dat
+1 aaa
+2 bbb
+3 ccc
+4 ddd
+EOF
+
+--replace_result $datadir <DATADIR>
+if ($skip_unique_check == 1)
+{
+ --error ER_ON_DUPLICATE_DISABLED
+ eval
+ LOAD DATA INFILE '$datadir/se_replacedata.dat' REPLACE INTO TABLE t1;
+}
+if ($skip_unique_check == 0)
+{
+ eval
+ LOAD DATA INFILE '$datadir/se_replacedata.dat' REPLACE INTO TABLE t1;
+}
+--sorted_result
+SELECT a,b FROM t1;
+--remove_file $datadir/se_replacedata.dat
+
+
+# Cleanup
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/loaddata.test b/storage/rocksdb/mysql-test/rocksdb/t/loaddata.test
new file mode 100644
index 00000000..1f59d5ce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/loaddata.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+let $skip_unique_check = 0;
+--source loaddata.inc
+let $skip_unique_check = 1;
+set session unique_checks=0;
+--source loaddata.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/lock.test b/storage/rocksdb/mysql-test/rocksdb/t/lock.test
new file mode 100644
index 00000000..9ce5e566
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/lock.test
@@ -0,0 +1,224 @@
+--source include/have_rocksdb.inc
+
+# Standard table locking:
+# LOCK TABLE .. READ
+# LOCK TABLE .. [LOW_PRIORITY] WRITE
+# UNLOCK TABLES
+#
+# and global locking:
+# FLUSH TABLES [..] WITH READ LOCK
+#
+############################################
+# TODO:
+# A part of the test is currently disabled
+# because temporary tables are not supported
+############################################
+
+--enable_connect_log
+
+# Save the initial number of concurrent sessions.
+--source include/count_sessions.inc
+
+connect (con1,localhost,root,,);
+SET lock_wait_timeout=1;
+
+connection default;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2, t3;
+--enable_warnings
+
+CREATE TABLE t1 (id INT, id2 INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (id,id2) VALUES (1,1),(1,2),(1,3);
+
+# LOW_PRIORITY has no effect, but is still syntactically correct
+LOCK TABLE t1 LOW_PRIORITY WRITE;
+SELECT id2,COUNT(DISTINCT id) FROM t1 GROUP BY id2;
+
+UPDATE t1 SET id=-1 WHERE id=1;
+
+connection con1;
+# With WRITE lock held by connection 'default',
+# nobody else can access the table
+--error ER_LOCK_WAIT_TIMEOUT
+SELECT id,id2 FROM t1;
+--error ER_LOCK_WAIT_TIMEOUT
+LOCK TABLE t1 READ;
+
+connection default;
+LOCK TABLE t1 READ;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+UPDATE t1 SET id=1 WHERE id=1;
+
+connection con1;
+# With READ lock held by connection 'default',
+# it should be possible to read from the table
+# or acquire another READ lock,
+# but not update it or acquire WRITE lock
+SELECT COUNT(DISTINCT id) FROM t1;
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET id=2 WHERE id=2;
+--error ER_LOCK_WAIT_TIMEOUT
+LOCK TABLE t1 WRITE;
+LOCK TABLE t1 READ;
+UNLOCK TABLES;
+
+
+--connection default
+
+--error ER_TABLE_NOT_LOCKED
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+
+--disable_parsing
+
+CREATE TEMPORARY TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+DROP TABLE IF EXISTS t2;
+
+--enable_parsing
+
+UNLOCK TABLES;
+
+CREATE TABLE t2 (id INT, id2 INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLE t1 WRITE, t2 WRITE;
+INSERT INTO t2 (id,id2) SELECT id,id2 FROM t1;
+UPDATE t1 SET id=1 WHERE id=-1;
+DROP TABLE t1,t2;
+
+#
+# INSERT ... SELECT with lock tables
+#
+
+CREATE TABLE t1 (i1 INT, nr INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+CREATE TABLE t2 (nr INT, nm INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (nr,nm) VALUES (1,3);
+INSERT INTO t2 (nr,nm) VALUES (2,4);
+
+LOCK TABLES t1 WRITE, t2 READ;
+INSERT INTO t1 (i1,nr) SELECT 1, nr FROM t2 WHERE nm=3;
+INSERT INTO t1 (i1,nr) SELECT 2, nr FROM t2 WHERE nm=4;
+UNLOCK TABLES;
+
+LOCK TABLES t1 WRITE;
+--error ER_TABLE_NOT_LOCKED
+INSERT INTO t1 (i1,nr) SELECT i1, nr FROM t1;
+UNLOCK TABLES;
+LOCK TABLES t1 WRITE, t1 AS t1_alias READ;
+INSERT INTO t1 (i1,nr) SELECT i1, nr FROM t1 AS t1_alias;
+--error ER_TABLE_NOT_LOCKED
+DROP TABLE t1,t2;
+UNLOCK TABLES;
+DROP TABLE t1,t2;
+
+#
+# Check that a dropped table is removed from a lock
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+# This removes one table after the other from the lock.
+DROP TABLE t2, t3, t1;
+#
+# Check that a lock merge works
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t1 AS t4 READ;
+
+ALTER TABLE t2 ADD COLUMN c2 INT;
+
+DROP TABLE t1, t2, t3;
+
+# FLUSH TABLES is not permitted when there is an active LOCK TABLES .. READ,
+# FLUSH TABLES .. WITH READ LOCK should be used instead
+# (and for other connections the table is locked)
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+
+LOCK TABLE t1 READ, t2 READ;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+FLUSH TABLE t1;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+FLUSH TABLES;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+FLUSH TABLES t1, t2 WITH READ LOCK;
+UNLOCK TABLES;
+
+FLUSH TABLES t1, t2 WITH READ LOCK;
+
+--connection con1
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+
+--connection default
+UNLOCK TABLES;
+
+# Global lock
+
+FLUSH TABLES WITH READ LOCK;
+
+--connection con1
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+
+--connection default
+UNLOCK TABLES;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+FLUSH TABLES WITH READ LOCK;
+--error ER_CANT_UPDATE_WITH_READLOCK
+DROP TABLE t1, t2;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+
+--disconnect con1
+
+#
+# Ensure that FLUSH TABLES doesn't substitute a base locked table
+# with a temporary one.
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+CREATE TABLE t2 (a INT, b CHAR(8), PRIMARY KEY(b)) ENGINE=rocksdb;
+
+LOCK TABLE t1 WRITE, t2 WRITE;
+
+--disable_parsing
+
+CREATE TEMPORARY TABLE t1 (a INT, b CHAR(8), PRIMARY KEY(a)) ENGINE=rocksdb;
+FLUSH TABLE t1;
+DROP TEMPORARY TABLE t1;
+
+--enable_parsing
+
+SELECT a,b FROM t1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+
+CREATE TABLE t1 (i INT) ENGINE=MyISAM;
+HANDLER t1 OPEN h;
+CREATE TABLE t2 (i INT) ENGINE=RocksDB;
+LOCK TABLES t2 WRITE;
+
+--connect (con1,localhost,root,,test)
+--connection con1
+--send
+FLUSH TABLES WITH READ LOCK;
+
+--connection default
+INSERT INTO t2 VALUES (1);
+UNLOCK TABLES;
+HANDLER h CLOSE;
+
+--connection con1
+--reap
+--disconnect con1
+
+--connection default
+DROP TABLE t1, t2;
+
+# Check that all connections opened by test cases in this file are really
+# gone so execution of other tests won't be affected by their presence.
+--source include/wait_until_count_sessions.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/lock_info.test b/storage/rocksdb/mysql-test/rocksdb/t/lock_info.test
new file mode 100644
index 00000000..1b624cf3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/lock_info.test
@@ -0,0 +1,31 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+create table t1 (a int, primary key(a) comment 'lock_into_cf1') engine=rocksdb;
+insert into t1 values (1);
+insert into t1 values (2);
+
+create table t2 (a int, primary key(a) comment 'lock_info_cf2') engine=rocksdb;
+insert into t2 values (1);
+insert into t2 values (2);
+
+set autocommit=0;
+select * from t1 for update;
+select * from t2 for update;
+
+use information_schema;
+--replace_column 2 _txn_id_ 3 _key_
+select rocksdb_ddl.cf, rocksdb_locks.transaction_id, rocksdb_locks.key
+from rocksdb_locks
+left join rocksdb_ddl
+on rocksdb_locks.column_family_id=rocksdb_ddl.column_family
+order by rocksdb_ddl.cf;
+
+use test;
+
+DROP TABLE t1;
+DROP TABLE t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/lock_rows_not_exist.test b/storage/rocksdb/mysql-test/rocksdb/t/lock_rows_not_exist.test
new file mode 100644
index 00000000..a51a516a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/lock_rows_not_exist.test
@@ -0,0 +1,110 @@
+--source include/have_rocksdb.inc
+
+--enable_connect_log
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+#1. Using all primary key columns, with equal conditions
+connection con1;
+CREATE TABLE t (id1 int, id2 int, id3 int, value int, PRIMARY KEY (id1, id2, id3)) ENGINE=RocksDB;
+
+#1.1 SELECT FOR UPDATE
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+
+connection con2;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+BEGIN;
+--error ER_LOCK_WAIT_TIMEOUT
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=2 FOR UPDATE;
+
+#1.2 UPDATE
+connection con1;
+ROLLBACK;
+BEGIN;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+
+connection con2;
+ROLLBACK;
+BEGIN;
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=0 AND id3=1;
+
+#1.3 DELETE
+connection con1;
+ROLLBACK;
+BEGIN;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+
+connection con2;
+ROLLBACK;
+BEGIN;
+--error ER_LOCK_WAIT_TIMEOUT
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=0;
+
+--disable_parsing
+#
+# The following is commented out because RocksDB's Transaction API doesn't
+# "support" READ COMMITTED, in particular, it doesn't release row locks
+# after each statement. (MyRocks is able to request a new snapshot for
+# every statement, but this won't free the locks. TODO: Is the behavior
+# that is tested below really needed?)
+#
+connection con1;
+ROLLBACK;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+
+connection con2;
+ROLLBACK;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=1 FOR UPDATE;
+SELECT * FROM t WHERE id1=1 AND id2=1 AND id3=2 FOR UPDATE;
+
+connection con1;
+ROLLBACK;
+BEGIN;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+
+connection con2;
+ROLLBACK;
+BEGIN;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=1 AND id3=1;
+UPDATE t SET value=value+100 WHERE id1=1 AND id2=0 AND id3=1;
+
+connection con1;
+ROLLBACK;
+BEGIN;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+
+connection con2;
+ROLLBACK;
+BEGIN;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=1;
+DELETE FROM t WHERE id1=1 AND id2=1 AND id3=0;
+
+connection con1;
+ROLLBACK;
+connection con2;
+ROLLBACK;
+
+--enable_parsing
+connection default;
+disconnect con1;
+disconnect con2;
+
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/lock_wait_timeout_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/lock_wait_timeout_stats.test
new file mode 100644
index 00000000..5288680c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/lock_wait_timeout_stats.test
@@ -0,0 +1,38 @@
+create table t (a int primary key) engine=rocksdb;
+
+begin;
+insert into t values (0);
+
+--source include/count_sessions.inc
+--connect (con1,localhost,root,,)
+--connection con1
+set @@rocksdb_lock_wait_timeout=1;
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+begin;
+
+--connect (con2,localhost,root,,)
+--connection con2
+set @@rocksdb_lock_wait_timeout=1;
+begin;
+
+--connection con1
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+--error ER_LOCK_WAIT_TIMEOUT
+insert into t values(0);
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+select case when variable_value-@a = 1 then 'true' else 'false' end as waits from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+
+--connection con2
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+--error ER_LOCK_WAIT_TIMEOUT
+insert into t values(0);
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+select ROW_LOCK_WAIT_TIMEOUTS from information_schema.table_statistics where table_name="t";
+select case when variable_value-@a = 1 then 'true' else 'false' end as waits from information_schema.global_status where variable_name='rocksdb_row_lock_wait_timeouts';
+
+--disconnect con1
+--connection default
+--disconnect con2
+drop table t;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues.test
new file mode 100644
index 00000000..41e2f695
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues.test
@@ -0,0 +1,3 @@
+--source include/have_rocksdb.inc
+
+--echo tests moved to rocksdb.locking_issues_case*
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rc.test
new file mode 100644
index 00000000..3fd183bf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case1_1.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rr.test
new file mode 100644
index 00000000..cd4a0bd3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_1_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case1_1.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rc.test
new file mode 100644
index 00000000..3fe052a4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case1_2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rr.test
new file mode 100644
index 00000000..02263273
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case1_2_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case1_2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc.test
new file mode 100644
index 00000000..d780b524
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=0;
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc_lsr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc_lsr.test
new file mode 100644
index 00000000..bd46f93a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rc_lsr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=1;
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr.test
new file mode 100644
index 00000000..b820fddb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=0;
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr_lsr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr_lsr.test
new file mode 100644
index 00000000..33d3b752
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case2_rr_lsr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=1;
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case2.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rc.test
new file mode 100644
index 00000000..7dc7f878
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case3.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rr.test
new file mode 100644
index 00000000..7c81dacc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case3_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case3.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rc.test
new file mode 100644
index 00000000..edc1111b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case4.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rr.test
new file mode 100644
index 00000000..8c26c2d1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case4_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case4.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rc.test
new file mode 100644
index 00000000..10bedcf2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case5.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rr.test
new file mode 100644
index 00000000..6de3847c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case5_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case5.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rc.test
new file mode 100644
index 00000000..9409bde0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rc.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case6.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rr.test
new file mode 100644
index 00000000..0c7a8a11
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case6_rr.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case6.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc.test
new file mode 100644
index 00000000..f8e874a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=0;
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case7.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc_lsr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc_lsr.test
new file mode 100644
index 00000000..0f97ca2f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rc_lsr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=1;
+let $isolation_level = READ COMMITTED;
+--source include/locking_issues_case7.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr.test
new file mode 100644
index 00000000..c8cec956
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=0;
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case7.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr_lsr.test b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr_lsr.test
new file mode 100644
index 00000000..875e81a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/locking_issues_case7_rr_lsr.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+let $lock_scanned_rows=1;
+let $isolation_level = REPEATABLE READ;
+--source include/locking_issues_case7.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_ignore_dirs.test b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_ignore_dirs.test
new file mode 100644
index 00000000..49591dd6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_ignore_dirs.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+
+--echo #
+--echo # RocksDB plugin adds #rocksdb to ignore_db_dirs
+--echo #
+
+select @@ignore_db_dirs;
+
+--let $_mysqld_option=--ignore-db-dirs=aa --ignore-db-dirs=bbb
+--source include/restart_mysqld_with_option.inc
+
+select @@ignore_db_dirs;
+
+--let $_mysqld_option=--ignore-db-dirs=#rocksdb
+--source include/restart_mysqld_with_option.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog-master.opt
new file mode 100644
index 00000000..beae84b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog-master.opt
@@ -0,0 +1 @@
+--log-bin
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog.test b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog.test
new file mode 100644
index 00000000..e32679e8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_misc_binlog.test
@@ -0,0 +1,40 @@
+--source include/have_rocksdb.inc
+
+create table t1 (a int) engine=rocksdb;
+
+--echo # Should have binlog ON
+select @@log_bin;
+
+set binlog_format='row';
+
+--echo # Should succeed
+optimize table t1;
+
+--echo #
+--echo # MDEV-13602: rocksdb.index_merge_rocksdb2 failed in buildbot
+--echo #
+
+lock tables t1 write;
+insert into t1 values(1);
+unlock tables;
+
+set @tmp_bf= @@binlog_format;
+set binlog_format='STATEMENT';
+lock tables t1 write;
+--error ER_BINLOG_STMT_MODE_AND_ROW_ENGINE
+insert into t1 values(1);
+unlock tables;
+set @@binlog_format=@tmp_bf;
+
+drop table t1;
+
+--echo #
+--echo # MDEV-17045: MyRocks tables cannot be updated when binlog_format=MIXED.
+--echo #
+set @tmp_bf= @@binlog_format;
+set binlog_format='MIXED';
+create table t1 (pk int primary key) engine=rocksdb;
+insert into t1 values (1);
+drop table t1;
+set @@binlog_format=@tmp_bf;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin-master.opt
new file mode 100644
index 00000000..0f0a3ef3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin-master.opt
@@ -0,0 +1 @@
+--default-storage-engine=myisam --plugin-load='' --ignore-db-dirs=#rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin.test b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin.test
new file mode 100644
index 00000000..0cf56c0c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_plugin.test
@@ -0,0 +1,59 @@
+--source include/have_log_bin.inc
+--source include/have_binlog_format_row.inc
+--source include/not_windows.inc
+
+--echo #
+--echo # MDEV-14843: Assertion `s_tx_list.size() == 0' failed in myrocks::Rdb_transaction::term_mutex
+--echo #
+
+#
+# On Linux, wsrep plugin is always loaded so XA is enabled at this point.
+# On Windows, there is no wsrep, so we get this warning:
+# Warning 1105 Cannot enable tc-log at run-time. XA features of ROCKSDB are disabled
+#
+--disable_warnings
+INSTALL SONAME 'ha_rocksdb';
+--enable_warnings
+
+connect (con1,localhost,root,,test);
+CREATE TABLE t1 (i INT) ENGINE=RocksDB;
+insert into t1 values (1);
+DROP TABLE t1;
+
+connection default;
+# Cleanup
+UNINSTALL SONAME 'ha_rocksdb';
+SELECT ENGINE, SUPPORT FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='ROCKSDB';
+disconnect con1;
+# Unfortunately this is the only more or less reliable way to wait until
+# connection done ha_close_connections(). It doesn't work on Windows due
+# to different thread handling.
+let $wait_condition= SELECT VARIABLE_VALUE=1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME='Threads_cached';
+--source include/wait_condition.inc
+
+--echo #
+--echo # MDEV-15686: Loading MyRocks plugin back after it has been unloaded causes a crash
+--echo #
+call mtr.add_suppression("Plugin 'ROCKSDB.*' init function returned error.");
+call mtr.add_suppression("Plugin 'ROCKSDB.*' registration as a INFORMATION SCHEMA failed.");
+call mtr.add_suppression("Plugin 'ROCKSDB' registration as a STORAGE ENGINE failed");
+
+--echo #
+--echo # There are two possible scenarios:
+
+--echo # ha_rocksdb.{dll,so} is still loaded into mysqld's address space. Its
+--echo # global variables are in the state that doesn't allow it to be
+--echo # initialized back (this is what MDEV-15686 is about). This is handled
+--echo # by intentionally returning an error from rocksdb_init_func.
+--echo #
+--echo # The second case is when ha_rocksdb.{ddl,so} has been fully unloaded
+--echo # and so it will be now loaded as if it happens for the first time.
+
+--error 0,ER_INTERNAL_ERROR
+INSTALL SONAME 'ha_rocksdb';
+
+--echo # Whatever happened on the previous step, restore things to the way they
+--echo # were at testcase start.
+--error 0,ER_SP_DOES_NOT_EXIST
+UNINSTALL SONAME 'ha_rocksdb';
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_fixes.test b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_fixes.test
new file mode 100644
index 00000000..99d4e2d1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_fixes.test
@@ -0,0 +1,114 @@
+--source include/have_rocksdb.inc
+
+--echo #
+--echo # MDEV-14433: RocksDB may show empty or incorrect output with rocksdb_strict_collation_check=off
+--echo #
+set global rocksdb_strict_collation_check=off;
+set @tmp_rscc=@@rocksdb_strict_collation_check;
+
+CREATE TABLE t1(
+ a varchar(10) NOT NULL,
+ b char(1) DEFAULT 'X',
+ c char(2) NOT NULL DEFAULT '??',
+ d varchar(10) NOT NULL,
+ e int(11) DEFAULT 0,
+ PRIMARY KEY (a,d),
+ KEY (e)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+
+insert into t1 select 1,1,1,1,0;
+insert into t1 select 2,1,1,1,0;
+insert into t1 select 3,1,1,1,0;
+
+--replace_column 9 #
+explain
+select a from t1 force index(e) where e<10000;
+select a from t1;
+select * from t1;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-14563: Wrong query plan for query with no PK
+--echo #
+
+CREATE TABLE t1(
+ pk int primary key,
+ a varchar(10) NOT NULL,
+ e int(11) DEFAULT 0,
+ KEY (a)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+insert into t1 values (1,1,1),(2,2,2);
+--replace_column 9 #
+explain select a from t1 where a <'zzz';
+
+CREATE TABLE t2(
+ pk int,
+ a varchar(10) NOT NULL,
+ e int(11) DEFAULT 0,
+ KEY (a)
+) ENGINE=ROCKSDB DEFAULT CHARSET=utf8;
+insert into t2 values (1,1,1),(2,2,2);
+--replace_column 9 #
+explain select a from t2 where a <'zzz';
+
+drop table t1,t2;
+
+set global rocksdb_strict_collation_check=@tmp_rscc;
+
+--echo #
+--echo # MDEV-14389: MyRocks and NOPAD collations
+--echo #
+
+--error ER_MYROCKS_CANT_NOPAD_COLLATION
+create table t1 (pk varchar(10) collate latin1_nopad_bin, primary key(pk)) engine=rocksdb;
+
+set global rocksdb_strict_collation_check=off;
+--error ER_MYROCKS_CANT_NOPAD_COLLATION
+create table t1 (pk varchar(10) collate latin1_nopad_bin, primary key(pk)) engine=rocksdb;
+
+set global rocksdb_strict_collation_check=@tmp_rscc;
+
+--echo #
+--echo # MDEV-14679: RocksdB plugin fails to load with "Loading of unknown plugin ROCKSDB_CFSTATS
+--echo #
+select plugin_name, plugin_maturity from information_schema.plugins where plugin_name like '%rocksdb%';
+
+
+--echo #
+--echo # MDEV-12466 : Assertion `thd->transaction.stmt.is_empty() || thd->in_sub_stmt || ...
+--echo #
+
+CREATE TABLE t1 (i INT) ENGINE=RocksDB;
+--error ER_ILLEGAL_HA
+FLUSH TABLE t1 FOR EXPORT;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-16154 Server crashes in in myrocks::ha_rocksdb::load_auto_incr_value_from_inde
+--echo #
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1);
+ALTER TABLE t1 AUTO_INCREMENT 10;
+
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-16155: UPDATE on RocksDB table with unique constraint does not work
+--echo #
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a,b) VALUES (1,'foo'),(2,'bar');
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+DROP TABLE t1;
+
+
+--echo #
+--echo # MDEV-15319: [SQL Layer] Server crashes in Field::set_null / myrocks::ha_rocksdb::convert_record_from_storage_format
+--echo # (just a testcase)
+--echo #
+
+CREATE TABLE t1 (i INT);
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t2 ENGINE=RocksDB AS SELECT VALUE(i) AS a FROM t1;
+DELETE FROM t2;
+DROP TABLE t1,t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_rpl.test b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_rpl.test
new file mode 100644
index 00000000..ed9eb029
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mariadb_port_rpl.test
@@ -0,0 +1,14 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+
+--echo #
+--echo # MDEV-15472: Assertion `!is_set() || (m_status == DA_OK_BULK && is_bulk_op())' failure ...
+--echo #
+select @@log_bin;
+select @@binlog_format;
+CREATE OR REPLACE TABLE t1 (i INT) ENGINE=RocksDB;
+LOCK TABLE t1 WRITE;
+FLUSH TABLES;
+UNLOCK TABLES;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/max_open_files.test b/storage/rocksdb/mysql-test/rocksdb/t/max_open_files.test
new file mode 100644
index 00000000..c7c5e7b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/max_open_files.test
@@ -0,0 +1,53 @@
+--source include/have_rocksdb.inc
+
+# Basic Sysbench run fails with basic MyROCKS install due to lack of open files
+
+# test for over limit
+CALL mtr.add_suppression("RocksDB: rocksdb_max_open_files should not be greater than the open_files_limit*");
+
+--let $over_rocksdb_max_open_files=`SELECT @@global.open_files_limit + 100`
+--let $under_rocksdb_max_open_files=`SELECT @@global.open_files_limit -1`
+--let SEARCH_FILE=$MYSQLTEST_VARDIR/tmp/rocksdb.max_open_files.err
+--let SEARCH_PATTERN=RocksDB: rocksdb_max_open_files should not be greater than the open_files_limit
+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR $over_rocksdb_max_open_files over_rocksdb_max_open_files
+--let $_mysqld_option=--log-error=$SEARCH_FILE --rocksdb_max_open_files=$over_rocksdb_max_open_files
+--source include/restart_mysqld_with_option.inc
+--source include/search_pattern_in_file.inc
+
+SELECT FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files;
+
+# test for within limit
+--let $_mysqld_option=--rocksdb_max_open_files=$under_rocksdb_max_open_files
+--source include/restart_mysqld_with_option.inc
+
+SELECT @@global.open_files_limit - 1 = @@global.rocksdb_max_open_files;
+
+# test for minimal value
+--let $_mysqld_option=--rocksdb_max_open_files=0
+--source include/restart_mysqld_with_option.inc
+
+SELECT @@global.rocksdb_max_open_files;
+
+# verify that we can still do work with no descriptor cache
+CREATE TABLE t1(a INT) ENGINE=ROCKSDB;
+INSERT INTO t1 VALUES(0),(1),(2),(3),(4);
+SET GLOBAL rocksdb_force_flush_memtable_and_lzero_now=1;
+DROP TABLE t1;
+
+# test for unlimited
+--let $_mysqld_option=--rocksdb_max_open_files=-1
+--source include/restart_mysqld_with_option.inc
+
+SELECT @@global.rocksdb_max_open_files;
+
+# test for auto-tune
+--let $_mysqld_option=--rocksdb_max_open_files=-2
+--source include/restart_mysqld_with_option.inc
+
+SELECT FLOOR(@@global.open_files_limit / 2) = @@global.rocksdb_max_open_files;
+
+# cleanup
+--let _$mysqld_option=
+--source include/restart_mysqld.inc
+--remove_file $SEARCH_FILE
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/misc.test b/storage/rocksdb/mysql-test/rocksdb/t/misc.test
new file mode 100644
index 00000000..b8efd038
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/misc.test
@@ -0,0 +1,45 @@
+--source include/have_rocksdb.inc
+
+#
+# Different statements not related to an engine,
+# but added to provide function coverage for handler.cc and handler.h.
+# The test can be disabled or removed later.
+#
+
+# hits get_error_message(int, String*)
+
+INSERT INTO mysql.event (
+ db,
+ name,
+ body,
+ definer,
+ interval_value,
+ interval_field,
+ originator,
+ character_set_client,
+ collation_connection,
+ db_collation,
+ body_utf8)
+values (
+ database(),
+ "ev1",
+ "select 1",
+ user(),
+ 100,
+ "SECOND_MICROSECOND",
+ 1,
+ 'utf8',
+ 'utf8_general_ci',
+ 'utf8_general_ci',
+ 'select 1');
+
+--error ER_NOT_SUPPORTED_YET
+SHOW EVENTS;
+DROP EVENT ev1;
+
+# hits get_foreign_key_list(THD*, List<st_foreign_key_info>*)
+--sorted_result
+SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
+FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE ORDER BY TABLE_NAME;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/multi_varchar_sk_lookup.test b/storage/rocksdb/mysql-test/rocksdb/t/multi_varchar_sk_lookup.test
new file mode 100644
index 00000000..cb65cf91
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/multi_varchar_sk_lookup.test
@@ -0,0 +1,49 @@
+# Disable until covered unpack_info format enabled in prod
+--source include/have_debug.inc
+--source include/not_debug.inc
+
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS T1;
+--enable_warnings
+
+CREATE TABLE T1 (
+ P1 VARCHAR(64), P2 VARCHAR(64), P3 VARCHAR(64), P4 VARCHAR(64),
+ S1 VARCHAR(64), S2 VARCHAR(64), S3 VARCHAR(64), S4 VARCHAR(64),
+ S5 VARCHAR(64), S6 VARCHAR(64), S7 VARCHAR(64), S8 VARCHAR(64),
+ S9 VARCHAR(64), S10 VARCHAR(64), S11 VARCHAR(64), S12 VARCHAR(64),
+ S13 VARCHAR(64), S14 VARCHAR(64), S15 VARCHAR(64), S16 VARCHAR(64),
+ PRIMARY KEY (P1(8), P2(8), P3(8), P4(8)),
+ KEY SK (S1(8), S2(8), S3(8), S4(8),
+ S5(8), S6(8), S7(8), S8(8),
+ S9(8), S10(8), S11(8), S12(8),
+ S13(8), S14(8), S15(8), S16(8))
+) ENGINE=rocksdb;
+
+INSERT INTO T1 VALUES ('1', '2', '3', '4',
+ '5', '6', '7', '8',
+ '9', '10', '11', '12',
+ '13', '14', '15', '16',
+ '17', '18', '19', '20');
+
+SELECT * FROM T1;
+
+--let $prefix_index_check_title= Not eligible for optimization, shorter than prefix length.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT P4, S2 FROM T1 FORCE INDEX(SK) WHERE S1='5'
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DELETE FROM T1;
+INSERT INTO T1 VALUES ('100000000', '200000000', '300000000', '400000000',
+ '500000000', '600000000', '700000000', '800000000',
+ '900000000', '100000000', '110000000', '120000000',
+ '130000000', '140000000', '150000000', '160000000',
+ '170000000', '180000000', '190000000', '200000000');
+
+--let $prefix_index_check_title= Not eligible for optimization, longer than prefix length.
+--let $prefix_index_check_read_avoided_delta= 0
+--let $prefix_index_check_query= SELECT P4, S2 FROM T1 FORCE INDEX(SK) WHERE S1='5'
+--source suite/rocksdb/include/prefix_index_only_query_check.inc
+
+DROP TABLE T1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_blind_replace.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_blind_replace.test
new file mode 100644
index 00000000..2b033023
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_blind_replace.test
@@ -0,0 +1,62 @@
+#
+# This test is intended to check that when blind replace is enabled,
+# mysqlbinlog is able to pass this information in the captured binlog
+# events and we are able to reapply such events
+#
+
+--source include/have_log_bin.inc
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+reset master;
+set GLOBAL binlog_format= 'ROW';
+SET GLOBAL enable_blind_replace=ON;
+set binlog_format=row;
+
+create table t5 (c1 int primary key, c2 int);
+insert into t5 values (1, 1);
+insert into t5 values (2, 2);
+insert into t5 values (3, 3);
+select * from t5;
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t5 values (1, 11);
+replace into t5 values (2, 22);
+replace into t5 values (3, 33);
+
+# Ensure that this was a blind replace
+select case when variable_value-@c = 3 then 'true' else 'false' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t5;
+--source include/show_binlog_events.inc
+
+flush logs;
+
+# Capture binlog events using mysqlbinlog
+let $MYSQLD_DATADIR= `select @@datadir`;
+--let $log_file_name = query_get_value("SHOW BINARY LOGS", Log_name, 1)
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/$log_file_name > $MYSQLTEST_VARDIR/tmp/mysqlbinlog-output
+
+# Drop the table. This will be recreated when we reapply binlog events
+drop table t5;
+reset master;
+
+# Now replay the binlog events
+--echo Replaying binlog events containing blind replace statements should work
+--exec $MYSQL < $MYSQLTEST_VARDIR/tmp/mysqlbinlog-output
+select * from t5;
+--source include/show_binlog_events.inc
+
+drop table t5;
+reset master;
+
+# Replay the same binlog events again, but with blind_replace turned off
+--echo Replaying the same binlog events with blind replace disabled should work
+--echo The server should internally convert such events into updates
+SET GLOBAL enable_blind_replace=OFF;
+--exec $MYSQL < $MYSQLTEST_VARDIR/tmp/mysqlbinlog-output
+select * from t5;
+
+--source include/show_binlog_events.inc
+
+set GLOBAL binlog_format=DEFAULT;
+SET GLOBAL enable_blind_replace=DEFAULT;
+drop table t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb-master.opt
new file mode 100644
index 00000000..53303252
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb-master.opt
@@ -0,0 +1 @@
+--binlog_format=row --gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb.test
new file mode 100644
index 00000000..0c04b604
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqlbinlog_gtid_skip_empty_trans_rocksdb.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+################################################################################
+# Test the --skip-empty-trans option for mysqlbinlog with rocksdb
+################################################################################
+--disable_warnings
+--source include/have_gtid.inc
+--enable_warnings
+
+reset master;
+set timestamp=1000000000;
+let $storage_engine=rocksdb;
+
+# Test row based replication
+set SESSION binlog_format = 'ROW';
+--source include/mysqlbinlog_gtid_skip_empty_trans_input.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump-master.opt
new file mode 100644
index 00000000..2672d4ff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump-master.opt
@@ -0,0 +1 @@
+--binlog_format=row
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test
new file mode 100644
index 00000000..473bebdd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump.test
@@ -0,0 +1,67 @@
+--source include/have_rocksdb.inc
+--source include/have_log_bin.inc
+--source include/restart_mysqld.inc
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+create table r1 (id1 int, id2 int, id3 varchar(100), id4 int, value1 int, value2 int, value3 int, value4 int, primary key (id1, id2, id3, id4), KEY (value1, value2, value3)) engine=rocksdb;
+insert into r1 values (1,1,1,1,1,1,1,1);
+insert into r1 values (1,1,1,2,2,2,2,2);
+insert into r1 values (1,1,2,1,3,3,3,3);
+insert into r1 values (1,1,2,2,4,4,4,4);
+insert into r1 values (1,2,1,1,5,5,5,5);
+insert into r1 values (1,2,1,2,6,6,6,6);
+insert into r1 values (1,2,2,1,7,7,7,7);
+insert into r1 values (1,2,2,2,8,8,8,8);
+insert into r1 values (2,1,1,1,9,9,9,9);
+insert into r1 values (2,1,1,2,10,10,10,10);
+insert into r1 values (2,1,2,1,11,11,11,11);
+insert into r1 values (2,1,2,2,12,12,12,12);
+insert into r1 values (2,2,1,1,13,13,13,13);
+insert into r1 values (2,2,1,2,14,14,14,14);
+insert into r1 values (2,2,2,1,15,15,15,15);
+insert into r1 values (2,2,2,2,16,16,16,16);
+
+connection con2;
+BEGIN;
+insert into r1 values (5,5,5,5,5,5,5,5);
+update r1 set value1=value1+100 where id1=1 and id2=1 and id3='1';
+
+--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load test
+
+--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb --order-by-primary-desc --rocksdb_bulk_load --rocksdb_bulk_load_allow_sk test
+
+rollback;
+
+connection con1;
+
+let SEARCH_FILE=$MYSQLTEST_VARDIR/mysqld.1/mysqld.log;
+let SEARCH_PATTERN=START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+source include/search_pattern_in_file.inc;
+
+set @save_default_storage_engine=@@global.default_storage_engine;
+SET GLOBAL default_storage_engine=rocksdb;
+--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test
+source include/search_pattern_in_file.inc;
+
+# Sanity test mysqldump when the --innodb-stats-on-metadata is specified (no effect)
+--echo ==== mysqldump with --innodb-stats-on-metadata ====
+--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --innodb-stats-on-metadata test
+
+# testing mysqldump work with statement based binary logging
+SET GLOBAL binlog_format=statement;
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key test > /dev/null
+SET GLOBAL binlog_format=row;
+
+drop table r1;
+reset master;
+set @@global.default_storage_engine=@save_default_storage_engine;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2-master.opt
new file mode 100644
index 00000000..2672d4ff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2-master.opt
@@ -0,0 +1 @@
+--binlog_format=row
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test
new file mode 100644
index 00000000..4f4f5ed0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/mysqldump2.test
@@ -0,0 +1,43 @@
+--source include/have_rocksdb.inc
+
+--source include/have_log_bin.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+create table t1 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+
+--disable_query_log
+let $i = 1;
+while ($i <= 50000) {
+ let $insert = INSERT INTO t1 VALUES($i, $i, REPEAT('x', 150));
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+optimize table t1;
+
+#wiping block cache
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+--exec echo "wait" > $restart_file
+--shutdown_server
+--source include/wait_until_disconnected.inc
+-- exec echo "restart:--rocksdb_default_cf_options=write_buffer_size=64k;target_file_size_base=64k;max_bytes_for_level_base=1m;compression_per_level=kNoCompression;disable_auto_compactions=true;level0_stop_writes_trigger=1000 " > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+
+--exec ASAN_OPTIONS="detect_leaks=0" $MYSQL_DUMP --skip-comments --single-transaction --master-data=2 --print-ordering-key --rocksdb test > /dev/null
+
+# verifying block cache was not filled
+select case when variable_value - @a > 20 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+
+select count(*) from t1;
+
+# verifying block cache was filled
+select case when variable_value - @a > 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_block_cache_add';
+
+#cleanup
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/native_procedure-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/native_procedure-master.opt
new file mode 100644
index 00000000..6c4cea3d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/native_procedure-master.opt
@@ -0,0 +1 @@
+$NP_EXAMPLE_LIB_OPT
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/native_procedure.test b/storage/rocksdb/mysql-test/rocksdb/t/native_procedure.test
new file mode 100644
index 00000000..d4f38a60
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/native_procedure.test
@@ -0,0 +1,2 @@
+let $engine=rocksdb;
+--source include/native_procedure.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/negative_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/negative_stats.test
new file mode 100644
index 00000000..9de41d17
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/negative_stats.test
@@ -0,0 +1,26 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (i1 INT, PRIMARY KEY (i1)) ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 10000;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+SET GLOBAL ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW=1;
+
+set session debug_dbug= "+d,myrocks_simulate_negative_stats";
+SELECT CASE WHEN DATA_LENGTH < 1024 * 1024 THEN 'true' ELSE 'false' END FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't1';
+set session debug_dbug= "-d,myrocks_simulate_negative_stats";
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/no_merge_sort.test b/storage/rocksdb/mysql-test/rocksdb/t/no_merge_sort.test
new file mode 100644
index 00000000..ccef7182
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/no_merge_sort.test
@@ -0,0 +1,32 @@
+--source include/have_rocksdb.inc
+
+# For Issue#117 MyRocks does merge sort with small data sets
+
+let $datadir = `SELECT @@datadir`;
+
+--disable_query_log
+let $i= 0;
+while ($i < 30) {
+ DROP TABLE IF EXISTS ti_nk;
+
+ CREATE TABLE `ti_nk` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL,
+ `c` varchar(36) COLLATE latin1_bin NOT NULL,
+ PRIMARY KEY (`c`)
+ );
+ let $j = 0;
+ while ($j < 200) {
+ eval insert into ti_nk values ($j, $j, md5($j));
+ inc $j;
+ }
+
+ select variable_value into @s from information_schema.global_status where variable_name='Sort_merge_passes';
+ eval SELECT a, b, c FROM ti_nk ORDER BY a,b,c INTO OUTFILE '$datadir/select.out';
+ --remove_file $datadir/select.out
+ select case when variable_value-@s = 0 then 'true' else 'false' end as skip_merge_sort from information_schema.global_status where variable_name='Sort_merge_passes';
+ inc $i;
+}
+--enable_query_log
+
+DROP TABLE ti_nk;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/no_primary_key_basic_ops.inc b/storage/rocksdb/mysql-test/rocksdb/t/no_primary_key_basic_ops.inc
new file mode 100644
index 00000000..0dcd4521
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/no_primary_key_basic_ops.inc
@@ -0,0 +1,65 @@
+#
+# This include file checks some very basic capabilities for restart insert
+# update and delete for tables with no pk
+# NOTE: requires table with structure similar to
+# CREATE TABLE t1 (a INT, b CHAR(8)) ENGINE=rocksdb;
+#
+
+SHOW CREATE TABLE t1;
+SHOW COLUMNS IN t1;
+
+### test INSERT
+INSERT INTO t1 (a,b) VALUES (76,'bar');
+INSERT INTO t1 (a,b) VALUES (35,'foo');
+INSERT INTO t1 (a,b) VALUES (77,'baz');
+
+## test SELECT w/ index scans
+--sorted_result
+SELECT * FROM t1 WHERE a = 35;
+--sorted_result
+SELECT * FROM t1 WHERE a = 35 AND b = 'foo';
+--sorted_result
+SELECT * FROM t1 WHERE a = 77 OR b = 'bar';
+--sorted_result
+SELECT * FROM t1 WHERE a > 35;
+--sorted_result
+SELECT * FROM t1;
+
+# test UPDATE
+UPDATE t1 SET a=a+100;
+--sorted_result
+SELECT * FROM t1;
+
+UPDATE t1 SET a=a-100, b='bbb' WHERE a>100;
+--sorted_result
+SELECT * FROM t1;
+UPDATE t1 SET a=300, b='ccc' WHERE a>70;
+--sorted_result
+SELECT * FROM t1;
+UPDATE t1 SET a=123 WHERE a=35;
+--sorted_result
+SELECT * FROM t1;
+UPDATE t1 SET a=321 WHERE b='ccc';
+--sorted_result
+SELECT * FROM t1;
+
+
+## test RESTART/OPEN
+--source include/restart_mysqld.inc
+## test insert after restart
+INSERT INTO t1 (a,b) VALUES (45,'bob');
+--sorted_result
+SELECT * FROM t1;
+
+# test DELETE
+DELETE FROM t1 WHERE a=123;
+--sorted_result
+SELECT * FROM t1;
+
+DELETE FROM t1 WHERE b > 'bbb' AND a > 100;
+--sorted_result
+SELECT * FROM t1;
+
+# test TRUNCATE
+TRUNCATE TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_base.test b/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_base.test
new file mode 100644
index 00000000..b37f532a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_base.test
@@ -0,0 +1,96 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+SET GLOBAL enable_blind_replace=ON;
+
+#
+# case 1: table only with primary key, support replace blind write
+#
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+#
+# case 2: table only with primary key but with trigger, not support replace blind write
+#
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+create trigger trg before insert on t1 for each row set @a:=1;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+
+#
+# case 3: table without primary key, not support replace blind write
+#
+
+create table t1(c1 int,c2 int) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+
+create table t1(c1 int,c2 int unique) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+
+
+#
+# case 4: table with primary key and secondary key, not support replace blind write
+#
+create table t1(c1 int primary key,c2 int unique) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+
+create table t1(c1 int primary key,c2 int, key idx1(c2)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+
+
+#
+# case 5: Disabling blind replace through enable_blind_replace should work
+SET GLOBAL enable_blind_replace=OFF;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+select case when variable_value-@c > 1 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+drop table t1;
+
+SET GLOBAL enable_blind_replace=DEFAULT;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_lock.test b/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_lock.test
new file mode 100644
index 00000000..6cce429a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimize_myrocks_replace_into_lock.test
@@ -0,0 +1,88 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+# Enable blind replace
+SET GLOBAL enable_blind_replace=ON;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+#
+# case 1: update is blocked by replace into
+#
+connection con1;
+SELECT @@global.enable_blind_replace;
+begin;
+replace into t1 values(1,11);
+
+
+connection con2;
+SELECT @@global.enable_blind_replace;
+begin;
+send update t1 set c2=22 where c1=1;
+
+
+connection default;
+# Check that the above update is blocked
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = 'Waiting for row lock' and
+ info = 'update t1 set c2=22 where c1=1';
+--source include/wait_condition.inc
+
+
+connection con1;
+commit;
+
+connection con2;
+--echo # Reap update.
+--reap
+commit;
+select * from t1;
+
+
+#
+# cast 2: replace into is blocked by update
+#
+
+connection con1;
+SELECT @@global.enable_blind_replace;
+begin;
+update t1 set c2=55 where c1=1;
+
+connection con2;
+SELECT @@global.enable_blind_replace;
+begin;
+send replace into t1 values(1,66);
+
+
+connection default;
+# Check that the above replace into is blocked
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = 'Waiting for row lock' and
+ info = 'replace into t1 values(1,66)';
+--source include/wait_condition.inc
+
+
+connection con1;
+commit;
+
+connection con2;
+--echo # Reap replace into.
+--reap
+commit;
+select * from t1;
+
+connection default;
+drop table t1;
+
+disconnect con1;
+disconnect con2;
+
+# Disable blind replace
+SET GLOBAL enable_blind_replace=DEFAULT;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimize_table-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table-master.opt
new file mode 100644
index 00000000..71f74ee5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=64k;target_file_size_base=64k;max_bytes_for_level_base=1m;compression_per_level=kNoCompression
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.inc b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.inc
new file mode 100644
index 00000000..5f66937c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.inc
@@ -0,0 +1,20 @@
+# run a check script to verify sst files reduced enough during each optimize table
+perl;
+
+$size += -s $_ for (<$ENV{datadir}/#rocksdb/*.sst>);
+$file= "$ENV{MYSQL_TMP_DIR}/sst_size.dat";
+
+if (-f $file) {
+ open(F, '<', $file) || die("Can't open file $file: $!");
+ $old = <F>;
+ close F;
+ if ($old - $size < 1e6) {
+ print "sst file reduction was not enough $old -> $size (minimum 1000kb)\n";
+ } else {
+ print "sst file reduction ok\n";
+ }
+}
+open(F, '>', $file) || die("Can't open file $file: $!");
+print F $size;
+close F;
+EOF
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.test b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.test
new file mode 100644
index 00000000..50df3f9c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimize_table.test
@@ -0,0 +1,81 @@
+--source include/have_rocksdb.inc
+
+#
+# OPTIMIZE TABLE statements
+#
+
+let datadir = `SELECT @@datadir`;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2,t3,t4,t5,t6;
+--enable_warnings
+create table t1 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t2 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t3 (id int primary key, value int, value2 varchar(200), index(value)) engine=rocksdb;
+create table t4 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+create table t5 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+create table t6 (id int, value int, value2 varchar(200), primary key (id) comment 'rev:cf_i', index(value) comment 'rev:cf_i') engine=rocksdb;
+
+--disable_query_log
+let $t = 1;
+while ($t <= 6) {
+ let $i = 1;
+ while ($i <= 10000) {
+ let $insert = INSERT INTO t$t VALUES($i, $i, REPEAT('x', 150));
+ inc $i;
+ eval $insert;
+ }
+ inc $t;
+}
+--enable_query_log
+
+# Disable auto compaction so that effects of optimize table are stable
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+--exec echo "wait" > $restart_file
+--shutdown_server
+--source include/wait_until_disconnected.inc
+-- exec echo "restart:--rocksdb_default_cf_options=write_buffer_size=64k;target_file_size_base=64k;max_bytes_for_level_base=1m;compression_per_level=kNoCompression;disable_auto_compactions=true;level0_stop_writes_trigger=1000 " > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+select count(*) from t1;
+select count(*) from t2;
+select count(*) from t3;
+select count(*) from t4;
+select count(*) from t5;
+select count(*) from t6;
+delete from t1 where id <= 9900;
+delete from t2 where id <= 9900;
+delete from t3 where id <= 9900;
+delete from t4 where id <= 9900;
+delete from t5 where id <= 9900;
+delete from t6 where id <= 9900;
+
+source optimize_table.inc;
+optimize table t1;
+source optimize_table.inc;
+optimize table t3;
+source optimize_table.inc;
+optimize table t4;
+source optimize_table.inc;
+optimize table t6;
+source optimize_table.inc;
+
+select count(*) from t1;
+select count(*) from t2;
+select count(*) from t3;
+select count(*) from t4;
+select count(*) from t5;
+select count(*) from t6;
+
+#cleanup
+optimize table t2;
+optimize table t5;
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
+DROP TABLE t5;
+DROP TABLE t6;
+--remove_file $MYSQL_TMP_DIR/sst_size.dat
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/optimizer_loose_index_scans.test b/storage/rocksdb/mysql-test/rocksdb/t/optimizer_loose_index_scans.test
new file mode 100644
index 00000000..db66da3b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/optimizer_loose_index_scans.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $engine=rocksdb;
+--source include/loose_index_scans.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/partition.test b/storage/rocksdb/mysql-test/rocksdb/t/partition.test
new file mode 100644
index 00000000..5954c0d9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/partition.test
@@ -0,0 +1,762 @@
+
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# Create tables with partitions and try to update/select from them.
+#
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS VAR_POP;
+DROP TABLE IF EXISTS TEMP0;
+DROP TABLE IF EXISTS VAR_SAMP;
+DROP TABLE IF EXISTS ti;
+DROP TABLE IF EXISTS members;
+DROP TABLE IF EXISTS members_2;
+DROP TABLE IF EXISTS employees;
+DROP TABLE IF EXISTS employees_2;
+DROP TABLE IF EXISTS employees_3;
+DROP TABLE IF EXISTS quarterly_report_status;
+DROP TABLE IF EXISTS employees_4;
+DROP TABLE IF EXISTS h2;
+DROP TABLE IF EXISTS rcx;
+DROP TABLE IF EXISTS r1;
+DROP TABLE IF EXISTS rc1;
+DROP TABLE IF EXISTS rx;
+DROP TABLE IF EXISTS rc2;
+DROP TABLE IF EXISTS rc3;
+DROP TABLE IF EXISTS rc4;
+DROP TABLE IF EXISTS employees_by_lname;
+DROP TABLE IF EXISTS customers_1;
+DROP TABLE IF EXISTS customers_2;
+DROP TABLE IF EXISTS customers_3;
+DROP TABLE IF EXISTS employees_hash;
+DROP TABLE IF EXISTS employees_hash_1;
+DROP TABLE IF EXISTS t1_hash;
+DROP TABLE IF EXISTS employees_linear_hash;
+DROP TABLE IF EXISTS t1_linear_hash;
+DROP TABLE IF EXISTS k1;
+DROP TABLE IF EXISTS k2;
+DROP TABLE IF EXISTS tm1;
+DROP TABLE IF EXISTS tk;
+DROP TABLE IF EXISTS ts;
+DROP TABLE IF EXISTS ts_1;
+DROP TABLE IF EXISTS ts_3;
+DROP TABLE IF EXISTS ts_4;
+DROP TABLE IF EXISTS ts_5;
+DROP TABLE IF EXISTS trb3;
+DROP TABLE IF EXISTS tr;
+DROP TABLE IF EXISTS members_3;
+DROP TABLE IF EXISTS clients;
+DROP TABLE IF EXISTS clients_lk;
+DROP TABLE IF EXISTS trb1;
+
+--enable_warnings
+
+CREATE TABLE t1 (i INT, j INT, k INT, PRIMARY KEY (i)) ENGINE = ROCKSDB PARTITION BY KEY(i) PARTITIONS 4;
+
+--disable_query_log
+let $max = 1000;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+
+ALTER TABLE t1 REBUILD PARTITION p0, p1;
+ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;
+ALTER TABLE t1 ANALYZE PARTITION p3;
+ALTER TABLE t1 REPAIR PARTITION p0,p1;
+ALTER TABLE t1 CHECK PARTITION p1;
+
+# Parition string is "#P#". To verify that parsing is done correctly then we'll
+# verify if tables containing "P" somwhere can be created correctly.
+CREATE TABLE VAR_POP (a int) ENGINE = ROCKSDB;
+CREATE TABLE TEMP0 (a int) ENGINE = ROCKSDB PARTITION BY HASH (a) PARTITIONS 3;
+CREATE TABLE VAR_SAMP (a int) ENGINE = ROCKSDB PARTITION BY HASH (a) PARTITIONS 10;
+
+--enable_query_log
+
+select lower(table_name) as tname
+from information_schema.tables
+where table_schema=database()
+order by tname;
+
+SELECT * FROM t1 ORDER BY i LIMIT 10;
+SELECT COUNT(*) FROM t1;
+
+#
+# Test-cases above are copied from
+# https://dev.mysql.com/doc/refman/5.6/en/partitioning.html to validate that the
+# partitioning related examples work with MyRocks.
+#
+
+# Create a table that is partitioned by hash into 6 partitions.
+CREATE TABLE ti(
+ id INT,
+ amount DECIMAL(7,2),
+ tr_date DATE
+) ENGINE=ROCKSDB
+ PARTITION BY HASH(MONTH(tr_date))
+ PARTITIONS 6;
+
+CREATE TABLE members (
+ firstname VARCHAR(25) NOT NULL,
+ lastname VARCHAR(25) NOT NULL,
+ username VARCHAR(16) NOT NULL,
+ email VARCHAR(35),
+ joined DATE NOT NULL
+) ENGINE=ROCKSDB
+ PARTITION BY KEY(joined)
+ PARTITIONS 6;
+
+CREATE TABLE members_2 (
+ firstname VARCHAR(25) NOT NULL,
+ lastname VARCHAR(25) NOT NULL,
+ username VARCHAR(16) NOT NULL,
+ email VARCHAR(35),
+ joined DATE NOT NULL
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE(YEAR(joined)) (
+ PARTITION p0 VALUES LESS THAN (1960),
+ PARTITION p1 VALUES LESS THAN (1970),
+ PARTITION p2 VALUES LESS THAN (1980),
+ PARTITION p3 VALUES LESS THAN (1990),
+ PARTITION p4 VALUES LESS THAN MAXVALUE
+ );
+
+# Partition names are not case-sensitive.
+--error 1517
+CREATE TABLE t2 (val INT)
+ ENGINE=ROCKSDB
+ PARTITION BY LIST(val)(
+ PARTITION mypart VALUES IN (1,3,5),
+ PARTITION MyPart VALUES IN (2,4,6)
+ );
+
+CREATE TABLE employees (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT NOT NULL,
+ store_id INT NOT NULL
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE (store_id) (
+ PARTITION p0 VALUES LESS THAN (6),
+ PARTITION p1 VALUES LESS THAN (11),
+ PARTITION p2 VALUES LESS THAN (16),
+ PARTITION p3 VALUES LESS THAN MAXVALUE
+ );
+
+CREATE TABLE employees_2 (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT NOT NULL,
+ store_id INT NOT NULL
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE (job_code) (
+ PARTITION p0 VALUES LESS THAN (100),
+ PARTITION p1 VALUES LESS THAN (1000),
+ PARTITION p2 VALUES LESS THAN (10000)
+ );
+
+CREATE TABLE employees_3 (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT,
+ store_id INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE (YEAR(separated)) (
+ PARTITION p0 VALUES LESS THAN (1991),
+ PARTITION p1 VALUES LESS THAN (1996),
+ PARTITION p2 VALUES LESS THAN (2001),
+ PARTITION p3 VALUES LESS THAN MAXVALUE
+ );
+
+CREATE TABLE quarterly_report_status (
+ report_id INT NOT NULL,
+ report_status VARCHAR(20) NOT NULL,
+ report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE (UNIX_TIMESTAMP(report_updated)) (
+ PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
+ PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
+ PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
+ PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
+ PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
+ PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
+ PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
+ PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
+ PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
+ PARTITION p9 VALUES LESS THAN (MAXVALUE)
+ );
+
+CREATE TABLE employees_4 (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT,
+ store_id INT
+) ENGINE=ROCKSDB
+ PARTITION BY LIST(store_id) (
+ PARTITION pNorth VALUES IN (3,5,6,9,17),
+ PARTITION pEast VALUES IN (1,2,10,11,19,20),
+ PARTITION pWest VALUES IN (4,12,13,14,18),
+ PARTITION pCentral VALUES IN (7,8,15,16)
+ );
+
+CREATE TABLE h2 (
+ c1 INT,
+ c2 INT
+) ENGINE=ROCKSDB
+ PARTITION BY LIST(c1) (
+ PARTITION p0 VALUES IN (1, 4, 7),
+ PARTITION p1 VALUES IN (2, 5, 8)
+ );
+
+# ERROR 1526 (HY000): Table has no partition for value 3
+--error 1526
+INSERT INTO h2 VALUES (3, 5);
+
+CREATE TABLE rcx (
+ a INT,
+ b INT,
+ c CHAR(3),
+ d INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a,d,c) (
+ PARTITION p0 VALUES LESS THAN (5,10,'ggg'),
+ PARTITION p1 VALUES LESS THAN (10,20,'mmm'),
+ PARTITION p2 VALUES LESS THAN (15,30,'sss'),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+ );
+
+CREATE TABLE r1 (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE (a) (
+ PARTITION p0 VALUES LESS THAN (5),
+ PARTITION p1 VALUES LESS THAN (MAXVALUE)
+ );
+
+INSERT INTO r1 VALUES (5,10), (5,11), (5,12);
+
+CREATE TABLE rc1 (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a, b) (
+ PARTITION p0 VALUES LESS THAN (5, 12),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE)
+ );
+
+INSERT INTO rc1 VALUES (5,10), (5,11), (5,12);
+SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12);
+
+CREATE TABLE rx (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS (a) (
+ PARTITION p0 VALUES LESS THAN (5),
+ PARTITION p1 VALUES LESS THAN (MAXVALUE)
+ );
+
+INSERT INTO rx VALUES (5,10), (5,11), (5,12);
+
+CREATE TABLE rc2 (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a,b) (
+ PARTITION p0 VALUES LESS THAN (0,10),
+ PARTITION p1 VALUES LESS THAN (10,20),
+ PARTITION p2 VALUES LESS THAN (10,30),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE)
+ );
+
+CREATE TABLE rc3 (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a,b) (
+ PARTITION p0 VALUES LESS THAN (0,10),
+ PARTITION p1 VALUES LESS THAN (10,20),
+ PARTITION p2 VALUES LESS THAN (10,30),
+ PARTITION p3 VALUES LESS THAN (10,35),
+ PARTITION p4 VALUES LESS THAN (20,40),
+ PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)
+);
+
+CREATE TABLE rc4 (
+ a INT,
+ b INT,
+ c INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a,b,c) (
+ PARTITION p0 VALUES LESS THAN (0,25,50),
+ PARTITION p1 VALUES LESS THAN (10,20,100),
+ PARTITION p2 VALUES LESS THAN (10,30,50),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+ );
+
+SELECT (0,25,50) < (10,20,100), (10,20,100) < (10,30,50);
+
+-- ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition
+
+--error 1493
+CREATE TABLE rcf (
+ a INT,
+ b INT,
+ c INT
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(a,b,c) (
+ PARTITION p0 VALUES LESS THAN (0,25,50),
+ PARTITION p1 VALUES LESS THAN (20,20,100),
+ PARTITION p2 VALUES LESS THAN (10,30,50),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
+ );
+
+CREATE TABLE employees_by_lname (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT NOT NULL,
+ store_id INT NOT NULL
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS (lname) (
+ PARTITION p0 VALUES LESS THAN ('g'),
+ PARTITION p1 VALUES LESS THAN ('m'),
+ PARTITION p2 VALUES LESS THAN ('t'),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE)
+ );
+
+ALTER TABLE employees_by_lname PARTITION BY RANGE COLUMNS (lname) (
+ PARTITION p0 VALUES LESS THAN ('g'),
+ PARTITION p1 VALUES LESS THAN ('m'),
+ PARTITION p2 VALUES LESS THAN ('t'),
+ PARTITION p3 VALUES LESS THAN (MAXVALUE)
+);
+
+ALTER TABLE employees_by_lname PARTITION BY RANGE COLUMNS (hired) (
+ PARTITION p0 VALUES LESS THAN ('1970-01-01'),
+ PARTITION p1 VALUES LESS THAN ('1980-01-01'),
+ PARTITION p2 VALUES LESS THAN ('1990-01-01'),
+ PARTITION p3 VALUES LESS THAN ('2000-01-01'),
+ PARTITION p4 VALUES LESS THAN ('2010-01-01'),
+ PARTITION p5 VALUES LESS THAN (MAXVALUE)
+);
+
+CREATE TABLE customers_1 (
+ first_name VARCHAR(25),
+ last_name VARCHAR(25),
+ street_1 VARCHAR(30),
+ street_2 VARCHAR(30),
+ city VARCHAR(15),
+ renewal DATE
+) ENGINE=ROCKSDB
+ PARTITION BY LIST COLUMNS(city) (
+ PARTITION pRegion_1 VALUES IN('Oskarshamn', 'Högsby', 'Mönsterås'),
+ PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'Västervik'),
+ PARTITION pRegion_3 VALUES IN('Nässjö', 'Eksjö', 'Vetlanda'),
+ PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'Växjo')
+ );
+
+CREATE TABLE customers_2 (
+ first_name VARCHAR(25),
+ last_name VARCHAR(25),
+ street_1 VARCHAR(30),
+ street_2 VARCHAR(30),
+ city VARCHAR(15),
+ renewal DATE
+) ENGINE=ROCKSDB
+ PARTITION BY LIST COLUMNS(renewal) (
+ PARTITION pWeek_1 VALUES IN('2010-02-01', '2010-02-02', '2010-02-03',
+ '2010-02-04', '2010-02-05', '2010-02-06', '2010-02-07'),
+ PARTITION pWeek_2 VALUES IN('2010-02-08', '2010-02-09', '2010-02-10',
+ '2010-02-11', '2010-02-12', '2010-02-13', '2010-02-14'),
+ PARTITION pWeek_3 VALUES IN('2010-02-15', '2010-02-16', '2010-02-17',
+ '2010-02-18', '2010-02-19', '2010-02-20', '2010-02-21'),
+ PARTITION pWeek_4 VALUES IN('2010-02-22', '2010-02-23', '2010-02-24',
+ '2010-02-25', '2010-02-26', '2010-02-27', '2010-02-28')
+ );
+
+CREATE TABLE customers_3 (
+ first_name VARCHAR(25),
+ last_name VARCHAR(25),
+ street_1 VARCHAR(30),
+ street_2 VARCHAR(30),
+ city VARCHAR(15),
+ renewal DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE COLUMNS(renewal) (
+ PARTITION pWeek_1 VALUES LESS THAN('2010-02-09'),
+ PARTITION pWeek_2 VALUES LESS THAN('2010-02-15'),
+ PARTITION pWeek_3 VALUES LESS THAN('2010-02-22'),
+ PARTITION pWeek_4 VALUES LESS THAN('2010-03-01')
+ );
+
+CREATE TABLE employees_hash (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT,
+ store_id INT
+) ENGINE=ROCKSDB
+ PARTITION BY HASH(store_id)
+ PARTITIONS 4;
+
+CREATE TABLE employees_hash_1 (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT,
+ store_id INT
+) ENGINE=ROCKSDB
+ PARTITION BY HASH( YEAR(hired) )
+ PARTITIONS 4;
+
+CREATE TABLE t1_hash (
+ col1 INT,
+ col2 CHAR(5),
+ col3 DATE
+) ENGINE=ROCKSDB
+ PARTITION BY HASH( YEAR(col3) )
+ PARTITIONS 4;
+
+CREATE TABLE employees_linear_hash (
+ id INT NOT NULL,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ hired DATE NOT NULL DEFAULT '1970-01-01',
+ separated DATE NOT NULL DEFAULT '9999-12-31',
+ job_code INT,
+ store_id INT
+) ENGINE=ROCKSDB
+ PARTITION BY LINEAR HASH( YEAR(hired) )
+ PARTITIONS 4;
+
+CREATE TABLE t1_linear_hash (
+ col1 INT,
+ col2 CHAR(5),
+ col3 DATE
+) ENGINE=ROCKSDB
+ PARTITION BY LINEAR HASH( YEAR(col3) )
+ PARTITIONS 6;
+
+CREATE TABLE k1 (
+ id INT NOT NULL PRIMARY KEY,
+ name VARCHAR(20)
+) ENGINE=ROCKSDB
+ PARTITION BY KEY()
+ PARTITIONS 2;
+
+CREATE TABLE k2 (
+ id INT NOT NULL,
+ name VARCHAR(20),
+ UNIQUE KEY (id)
+) ENGINE=ROCKSDB
+ PARTITION BY KEY()
+ PARTITIONS 2;
+
+CREATE TABLE tm1 (
+ s1 CHAR(32) PRIMARY KEY
+) ENGINE=ROCKSDB
+ PARTITION BY KEY(s1)
+ PARTITIONS 10;
+
+CREATE TABLE tk (
+ col1 INT NOT NULL,
+ col2 CHAR(5),
+ col3 DATE
+) ENGINE=ROCKSDB
+ PARTITION BY LINEAR KEY (col1)
+ PARTITIONS 3;
+
+CREATE TABLE ts (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) )
+ SUBPARTITION BY HASH( TO_DAYS(purchased) )
+ SUBPARTITIONS 2 (
+ PARTITION p0 VALUES LESS THAN (1990),
+ PARTITION p1 VALUES LESS THAN (2000),
+ PARTITION p2 VALUES LESS THAN MAXVALUE
+ );
+
+CREATE TABLE ts_1 (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) )
+ SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990) (
+ SUBPARTITION s0,
+ SUBPARTITION s1
+ ),
+ PARTITION p1 VALUES LESS THAN (2000) (
+ SUBPARTITION s2,
+ SUBPARTITION s3
+ ),
+ PARTITION p2 VALUES LESS THAN MAXVALUE (
+ SUBPARTITION s4,
+ SUBPARTITION s5
+ )
+ );
+
+--error 1064
+CREATE TABLE ts_2 (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) )
+ SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990) (
+ SUBPARTITION s0,
+ SUBPARTITION s1
+ ),
+ PARTITION p1 VALUES LESS THAN (2000),
+ PARTITION p2 VALUES LESS THAN MAXVALUE (
+ SUBPARTITION s2,
+ SUBPARTITION s3
+ )
+ );
+
+CREATE TABLE ts_3 (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) )
+ SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990) (
+ SUBPARTITION s0,
+ SUBPARTITION s1
+ ),
+ PARTITION p1 VALUES LESS THAN (2000) (
+ SUBPARTITION s2,
+ SUBPARTITION s3
+ ),
+ PARTITION p2 VALUES LESS THAN MAXVALUE (
+ SUBPARTITION s4,
+ SUBPARTITION s5
+ )
+ );
+
+CREATE TABLE ts_4 (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) )
+ SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990) (
+ SUBPARTITION s0,
+ SUBPARTITION s1
+ ),
+ PARTITION p1 VALUES LESS THAN (2000) (
+ SUBPARTITION s2,
+ SUBPARTITION s3
+ ),
+ PARTITION p2 VALUES LESS THAN MAXVALUE (
+ SUBPARTITION s4,
+ SUBPARTITION s5
+ )
+ );
+
+CREATE TABLE ts_5 (
+ id INT,
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE(YEAR(purchased))
+ SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990) (
+ SUBPARTITION s0a,
+ SUBPARTITION s0b
+ ),
+ PARTITION p1 VALUES LESS THAN (2000) (
+ SUBPARTITION s1a,
+ SUBPARTITION s1b
+ ),
+ PARTITION p2 VALUES LESS THAN MAXVALUE (
+ SUBPARTITION s2a,
+ SUBPARTITION s2b
+ )
+ );
+
+CREATE TABLE trb3 (
+ id INT,
+ name VARCHAR(50),
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990),
+ PARTITION p1 VALUES LESS THAN (1995),
+ PARTITION p2 VALUES LESS THAN (2000),
+ PARTITION p3 VALUES LESS THAN (2005)
+ );
+
+ALTER TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2;
+
+CREATE TABLE tr (
+ id INT,
+ name VARCHAR(50),
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(purchased) ) (
+ PARTITION p0 VALUES LESS THAN (1990),
+ PARTITION p1 VALUES LESS THAN (1995),
+ PARTITION p2 VALUES LESS THAN (2000),
+ PARTITION p3 VALUES LESS THAN (2005)
+ );
+
+INSERT INTO tr VALUES
+ (1, 'desk organiser', '2003-10-15'),
+ (2, 'CD player', '1993-11-05'),
+ (3, 'TV set', '1996-03-10'),
+ (4, 'bookcase', '1982-01-10'),
+ (5, 'exercise bike', '2004-05-09'),
+ (6, 'sofa', '1987-06-05'),
+ (7, 'popcorn maker', '2001-11-22'),
+ (8, 'aquarium', '1992-08-04'),
+ (9, 'study desk', '1984-09-16'),
+ (10, 'lava lamp', '1998-12-25');
+
+SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+
+ALTER TABLE tr DROP PARTITION p2;
+
+SELECT * FROM tr WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+
+CREATE TABLE members_3 (
+ id INT,
+ fname VARCHAR(25),
+ lname VARCHAR(25),
+ dob DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE( YEAR(dob) ) (
+ PARTITION p0 VALUES LESS THAN (1970),
+ PARTITION p1 VALUES LESS THAN (1980),
+ PARTITION p2 VALUES LESS THAN (1990)
+ );
+
+ALTER TABLE members_3 ADD PARTITION (PARTITION p3 VALUES LESS THAN (2000));
+
+# ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition
+--error 1493
+ALTER TABLE members_3 ADD PARTITION (PARTITION n VALUES LESS THAN (1960));
+
+CREATE TABLE clients (
+ id INT,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ signed DATE
+) ENGINE=ROCKSDB
+ PARTITION BY HASH( MONTH(signed) )
+ PARTITIONS 12;
+
+ALTER TABLE clients COALESCE PARTITION 4;
+
+CREATE TABLE clients_lk (
+ id INT,
+ fname VARCHAR(30),
+ lname VARCHAR(30),
+ signed DATE
+) ENGINE=ROCKSDB
+ PARTITION BY LINEAR KEY(signed)
+ PARTITIONS 12;
+
+# ERROR 1508 (HY000): Cannot remove all partitions, use DROP TABLE instead
+--error 1508
+ALTER TABLE clients COALESCE PARTITION 18;
+
+ALTER TABLE clients ADD PARTITION PARTITIONS 6;
+
+CREATE TABLE trb1 (
+ id INT,
+ name VARCHAR(50),
+ purchased DATE
+) ENGINE=ROCKSDB
+ PARTITION BY RANGE(id) (
+ PARTITION p0 VALUES LESS THAN (3),
+ PARTITION p1 VALUES LESS THAN (7),
+ PARTITION p2 VALUES LESS THAN (9),
+ PARTITION p3 VALUES LESS THAN (11)
+ );
+
+INSERT INTO trb1 VALUES
+ (1, 'desk organiser', '2003-10-15'),
+ (2, 'CD player', '1993-11-05'),
+ (3, 'TV set', '1996-03-10'),
+ (4, 'bookcase', '1982-01-10'),
+ (5, 'exercise bike', '2004-05-09'),
+ (6, 'sofa', '1987-06-05'),
+ (7, 'popcorn maker', '2001-11-22'),
+ (8, 'aquarium', '1992-08-04'),
+ (9, 'study desk', '1984-09-16'),
+ (10, 'lava lamp', '1998-12-25');
+
+ALTER TABLE trb1 ADD PRIMARY KEY (id);
+
+# Clean up.
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS VAR_POP;
+DROP TABLE IF EXISTS TEMP0;
+DROP TABLE IF EXISTS VAR_SAMP;
+DROP TABLE IF EXISTS ti;
+DROP TABLE IF EXISTS members;
+DROP TABLE IF EXISTS members_2;
+DROP TABLE IF EXISTS employees;
+DROP TABLE IF EXISTS employees_2;
+DROP TABLE IF EXISTS employees_3;
+DROP TABLE IF EXISTS quarterly_report_status;
+DROP TABLE IF EXISTS employees_4;
+DROP TABLE IF EXISTS h2;
+DROP TABLE IF EXISTS rcx;
+DROP TABLE IF EXISTS r1;
+DROP TABLE IF EXISTS rc1;
+DROP TABLE IF EXISTS rx;
+DROP TABLE IF EXISTS rc2;
+DROP TABLE IF EXISTS rc3;
+DROP TABLE IF EXISTS rc4;
+DROP TABLE IF EXISTS employees_by_lname;
+DROP TABLE IF EXISTS customers_1;
+DROP TABLE IF EXISTS customers_2;
+DROP TABLE IF EXISTS customers_3;
+DROP TABLE IF EXISTS employees_hash;
+DROP TABLE IF EXISTS employees_hash_1;
+DROP TABLE IF EXISTS t1_hash;
+DROP TABLE IF EXISTS employees_linear_hash;
+DROP TABLE IF EXISTS t1_linear_hash;
+DROP TABLE IF EXISTS k1;
+DROP TABLE IF EXISTS k2;
+DROP TABLE IF EXISTS tm1;
+DROP TABLE IF EXISTS tk;
+DROP TABLE IF EXISTS ts;
+DROP TABLE IF EXISTS ts_1;
+DROP TABLE IF EXISTS ts_3;
+DROP TABLE IF EXISTS ts_4;
+DROP TABLE IF EXISTS ts_5;
+DROP TABLE IF EXISTS trb3;
+DROP TABLE IF EXISTS tr;
+DROP TABLE IF EXISTS members_3;
+DROP TABLE IF EXISTS clients;
+DROP TABLE IF EXISTS clients_lk;
+DROP TABLE IF EXISTS trb1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/percona_nonflushing_analyze_debug.test b/storage/rocksdb/mysql-test/rocksdb/t/percona_nonflushing_analyze_debug.test
new file mode 100644
index 00000000..c0c73e68
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/percona_nonflushing_analyze_debug.test
@@ -0,0 +1,11 @@
+--source include/have_debug_sync.inc
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1), (2), (3);
+
+--let $percona_nonflushing_analyze_table= t1
+--source include/percona_nonflushing_analyze_debug.inc
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/perf_context.test b/storage/rocksdb/mysql-test/rocksdb/t/perf_context.test
new file mode 100644
index 00000000..ee41324a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/perf_context.test
@@ -0,0 +1,96 @@
+--source include/have_rocksdb.inc
+
+#
+# Information Schema perf context
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+CREATE TABLE t2 (k INT, PRIMARY KEY (k)) ENGINE = ROCKSDB;
+
+INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
+
+###############################################################################
+# Test that expected perf context stats exists
+###############################################################################
+
+# Check per-table perf context
+--replace_column 5 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT WHERE TABLE_NAME = 't1';
+
+# Check global perf context
+--replace_column 2 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL;
+
+###############################################################################
+# Test iteration skip counters
+###############################################################################
+
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+
+SELECT * FROM t1;
+
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+
+SELECT * FROM t1 WHERE j BETWEEN 1 AND 5;
+
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't1'
+AND STAT_TYPE in ('INTERNAL_KEY_SKIPPED_COUNT', 'INTERNAL_DELETE_SKIPPED_COUNT');
+
+###############################################################################
+# Test write I/O stats
+###############################################################################
+
+# Statistics for multi-statement transactions cannot be attributed to
+# individual tables but should show up in global perf context stats
+set @tmp_flush_log= @@rocksdb_flush_log_at_trx_commit;
+set global rocksdb_flush_log_at_trx_commit=1;
+
+BEGIN;
+INSERT INTO t2 VALUES (1), (2);
+INSERT INTO t2 VALUES (3), (4);
+COMMIT;
+
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't2'
+AND STAT_TYPE = 'IO_WRITE_NANOS'
+AND VALUE > 0;
+
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS' AND VALUE > 0;
+
+SELECT VALUE INTO @a from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS';
+
+# Single statement writes do show up in per-table stats
+INSERT INTO t2 VALUES (5), (6), (7), (8);
+
+SELECT COUNT(*) from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_NAME = 't2'
+AND STAT_TYPE = 'IO_WRITE_NANOS'
+AND VALUE > 0;
+
+SELECT VALUE INTO @b from INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL
+WHERE STAT_TYPE = 'IO_WRITE_NANOS';
+
+SELECT CASE WHEN @b - @a > 0 THEN 'true' ELSE 'false' END;
+
+# cleanup
+DROP TABLE t1;
+DROP TABLE t2;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
+set global rocksdb_flush_log_at_trx_commit= @tmp_flush_log;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/persistent_cache.test b/storage/rocksdb/mysql-test/rocksdb/t/persistent_cache.test
new file mode 100644
index 00000000..49e5e5c1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/persistent_cache.test
@@ -0,0 +1,41 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--let $_server_id= `SELECT @@server_id`
+--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
+--let $_cache_file_name= $MYSQLTEST_VARDIR/tmp/persistent_cache
+--exec echo "wait" >$_expect_file_name
+
+# restart server with correct parameters
+shutdown_server;
+--exec echo "restart:--rocksdb_persistent_cache_path=$_cache_file_name --rocksdb_persistent_cache_size_mb=100" >$_expect_file_name
+--sleep 5
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+
+# insert values and flush out of memtable
+CREATE TABLE t1 (a int primary key) ENGINE=ROCKSDB;
+insert into t1 values (1);
+set global rocksdb_force_flush_memtable_now=1;
+
+# pull data through cache
+select * from t1 where a = 1;
+
+# restart server to re-read cache
+--exec echo "wait" >$_expect_file_name
+shutdown_server;
+--exec echo "restart:--rocksdb_persistent_cache_path=$_cache_file_name --rocksdb_persistent_cache_size_mb=100" >$_expect_file_name
+--sleep 5
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+# pull values from cache again
+select * from t1 where a = 1;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override-master.opt
new file mode 100644
index 00000000..ca7e3636
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=64k;block_based_table_factory={filter_policy=bloomfilter:10:false;whole_key_filtering=0;};prefix_extractor=capped:24;disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override.test b/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override.test
new file mode 100644
index 00000000..8fa43e15
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/prefix_extractor_override.test
@@ -0,0 +1,96 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (id1 BIGINT, id2 BIGINT, id3 BIGINT, id4 BIGINT, PRIMARY KEY (id1, id2, id3, id4) comment 'cf1') ENGINE=rocksdb collate latin1_bin;
+--disable_query_log
+let $i = 1;
+while ($i <= 100) {
+ let $insert = INSERT INTO t1 VALUES(1, $i, $i, $i);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+set global rocksdb_force_flush_memtable_now = 1;
+
+--echo
+--echo Original Prefix Extractor:
+--echo
+--sorted_result
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+
+# BF used (4+8+8+8)
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+--let $_mysqld_option=--rocksdb_override_cf_options=cf1={prefix_extractor=capped:26};
+
+--echo
+--echo Prefix Extractor (after override_cf_options set, should not be changed):
+--echo
+--sorted_result
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+
+SET @@global.rocksdb_update_cf_options = 'cf1={prefix_extractor=capped:26};';
+--echo
+--echo Changed Prefix Extractor (after update_cf_options set, without restart):
+--echo
+--sorted_result
+# Restart no longer needed
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+
+# set cf_options for non-existent cf2, cf2 should be created automatically
+SET @@global.rocksdb_update_cf_options = 'cf2={prefix_extractor=capped:28};';
+SELECT * FROM information_schema.rocksdb_cf_options WHERE option_type like '%prefix_extractor%';
+
+# Satisfies can_use_bloom_filter (4+8+8+8), but can't use because the old SST
+# files have old prefix extractor
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+# Insert more data into t1, verify it uses new bloom filter
+--disable_query_log
+let $i = 101;
+while ($i <= 200) {
+ let $insert = INSERT INTO t1 VALUES(1, $i, $i, $i);
+ eval $insert;
+ inc $i;
+}
+--enable_query_log
+
+set global rocksdb_force_flush_memtable_now = 1;
+
+# BF used w/ new prefix extractor (4+8+8+8) (still increments once because it
+# needs to check the new SST file, but doesnt increment for SST file with old
+# extractor)
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+# should have 2 sst files, one with old prefix extractor and one with new
+SELECT COUNT(*) FROM information_schema.rocksdb_index_file_map WHERE COLUMN_FAMILY != 1;
+
+# update some old data, force compaction, verify that new SST files use
+# new bloom filter
+UPDATE t1 SET id1=1,id2 = 30,id3 = 30 WHERE id4 >= 0 AND id4 <=10;
+set global rocksdb_force_flush_memtable_now = 1;
+
+# should have 3 sst files, one with old prefix extractor and two with new
+SELECT COUNT(*) FROM information_schema.rocksdb_index_file_map WHERE COLUMN_FAMILY != 1;
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=1 AND id3=1;
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+SET @@global.rocksdb_update_cf_options = '';
+set global rocksdb_compact_cf='cf1';
+
+# Select the updated, make sure bloom filter is checked now
+select variable_value into @u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+SELECT COUNT(*) FROM t1 WHERE id1=1 AND id2=30 AND id3=30;
+select variable_value-@u from information_schema.global_status where variable_name='rocksdb_bloom_filter_prefix_checked';
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx-master.opt
new file mode 100644
index 00000000..221b35c6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=16k --log-bin --binlog_format=row --log-slave-updates
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx.test b/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx.test
new file mode 100644
index 00000000..3a1025a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/read_only_tx.test
@@ -0,0 +1,70 @@
+--source include/have_log_bin.inc
+--source include/have_rocksdb.inc
+--source include/count_sessions.inc
+--disable_warnings
+#--source include/have_gtid.inc
+--enable_warnings
+#-- let $uuid = `select @@server_uuid;`
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+CREATE TABLE t1 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1);
+
+# Read-only, long-running transaction. SingleDelete/Put shouldn't increase much.
+select variable_value into @p from information_schema.global_status where variable_name='rocksdb_number_sst_entry_put';
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+#-- replace_result $uuid uuid
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+
+connection con2;
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t1 SET value=value+1 WHERE id=1;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+
+connection con1;
+select case when variable_value-@p < 1000 then 'true' else variable_value-@p end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_put';
+select case when variable_value-@s < 100 then 'true' else variable_value-@s end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+SELECT * FROM t1;
+--error ER_UPDATES_WITH_CONSISTENT_SNAPSHOT
+INSERT INTO t1 values (2, 2);
+ROLLBACK;
+SELECT * FROM t1;
+INSERT INTO t1 values (2, 2);
+SELECT * FROM t1 ORDER BY id;
+
+# Regular long-running transaction.
+# No "Transaction could not check for conflicts for opearation" error should happen.
+BEGIN;
+
+connection con2;
+--disable_query_log
+let $i = 5;
+while ($i <= 10000) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+connection con1;
+SELECT COUNT(*) FROM t1;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+OPTIMIZE TABLE t1;
+DROP TABLE t1;
+reset master;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/records_in_range-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/records_in_range-master.opt
new file mode 100644
index 00000000..75a17cc1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/records_in_range-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_debug_optimizer_n_rows=20000
+--rocksdb_records_in_range=1000
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/records_in_range.test b/storage/rocksdb/mysql-test/rocksdb/t/records_in_range.test
new file mode 100644
index 00000000..68fb1a09
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/records_in_range.test
@@ -0,0 +1,146 @@
+--source include/have_rocksdb.inc
+--source include/restart_mysqld.inc
+--source include/default_optimizer_switch.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Create the table and insert some keys
+CREATE TABLE t1 (
+ i INT,
+ a INT,
+ b INT,
+ PRIMARY KEY (i),
+ KEY ka(a),
+ KEY kb(b) comment 'rev:cf1'
+) ENGINE = rocksdb;
+
+--disable_query_log
+let $max = 20000;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+# get results for records_in_range prior to memtable flush
+# normal CF
+explain extended select * from t1 where a> 500 and a< 750;
+explain extended select * from t1 where a< 750;
+explain extended select * from t1 where a> 500;
+explain extended select * from t1 where a>=0 and a<=1000;
+
+#reverse CF
+explain extended select * from t1 where b> 500 and b< 750;
+explain extended select * from t1 where b< 750;
+explain extended select * from t1 where b> 500;
+explain extended select * from t1 where b>=0 and b<=1000;
+
+## cost calculation differences between covering vs non-covering (#298)
+set @save_rocksdb_records_in_range = @@session.rocksdb_records_in_range;
+set rocksdb_records_in_range = 15000;
+# covering, range
+explain extended select a from t1 where a < 750;
+# non-covering, full
+explain extended select a, b from t1 where a < 750;
+# covering, ref
+explain extended select a from t1 where a = 700;
+# non-covering, ref
+explain extended select a,b from t1 where a = 700;
+# covering, full index
+explain extended select a from t1 where a in (700, 800);
+# non-covering, full
+explain extended select a,b from t1 where a in (700, 800);
+set rocksdb_records_in_range=8000;
+# covering, range
+explain extended select a from t1 where a in (700, 800);
+# non-covering, full
+explain extended select a,b from t1 where a in (700, 800);
+set rocksdb_records_in_range = @save_rocksdb_records_in_range;
+
+# flush memtable and repeat
+set global rocksdb_force_flush_memtable_now = true;
+# normal CF
+explain extended select * from t1 where a> 500 and a< 750;
+explain extended select * from t1 where a< 750;
+explain extended select * from t1 where a> 500;
+explain extended select * from t1 where a>=0 and a<=1000;
+
+#reverse CF
+explain extended select * from t1 where b> 500 and b< 750;
+explain extended select * from t1 where b< 750;
+explain extended select * from t1 where b> 500;
+explain extended select * from t1 where b>=0 and b<=1000;
+
+# a set of 1
+explain extended select * from t1 where a>= 500 and a<= 500;
+explain extended select * from t1 where b>= 500 and b<= 500;
+
+# two indexes
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+
+# composite index
+drop index ka on t1;
+drop index kb on t1;
+create index kab on t1(a,b);
+set global rocksdb_force_flush_memtable_now = true;
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+
+# override records in range
+set rocksdb_records_in_range=444;
+explain extended select * from t1 where a< 750 and b> 500 and b< 750;
+set rocksdb_records_in_range=0;
+
+# issue 82
+## forward cf
+CREATE TABLE `linktable` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+ `time` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `version` int(11) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link_pk',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+
+insert into linktable values (1,1,1,1,1,1,1,1,1);
+insert into linktable values (1,1,2,1,1,1,1,1,1);
+insert into linktable values (1,1,3,1,1,1,1,1,1);
+insert into linktable values (1,1,4,1,1,1,1,1,1);
+set global rocksdb_force_flush_memtable_now = true;
+explain select id1, id2, link_type, visibility, data, time, version from linktable where id1 = 1 and link_type = 1 and id2 in (1, 2);
+drop table linktable;
+
+## rev cf
+CREATE TABLE `linktable` (
+ `id1` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id1_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `id2` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `id2_type` int(10) unsigned NOT NULL DEFAULT '0',
+ `link_type` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `visibility` tinyint(3) NOT NULL DEFAULT '0',
+ `data` varchar(255) COLLATE latin1_bin NOT NULL DEFAULT '',
+ `time` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `version` int(11) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`link_type`,`id1`,`id2`) COMMENT 'cf_link_pk',
+ KEY `id1_type` (`id1`,`link_type`,`visibility`,`time`,`version`,`data`) COMMENT 'rev:cf_link_id1_type'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+
+insert into linktable values (1,1,1,1,1,1,1,1,1);
+insert into linktable values (1,1,2,1,1,1,1,1,1);
+insert into linktable values (1,1,3,1,1,1,1,1,1);
+insert into linktable values (1,1,4,1,1,1,1,1,1);
+set global rocksdb_force_flush_memtable_now = true;
+explain select id1, id2, link_type, visibility, data, time, version from linktable where id1 = 1 and link_type = 1 and id2 in (1, 2);
+drop table linktable;
+
+#cleanup
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/repair_table.inc b/storage/rocksdb/mysql-test/rocksdb/t/repair_table.inc
new file mode 100644
index 00000000..47d6a299
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/repair_table.inc
@@ -0,0 +1,38 @@
+#
+# REPAIR TABLE statements
+#
+# Note: the output is likely to be different for the engine under test,
+# in which case rdiff will be needed. Or, the output might say that
+# the storage engine does not support REPAIR.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+CREATE TABLE t2 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb;
+
+REPAIR TABLE t1;
+
+INSERT INTO t1 (a,b) VALUES (3,'c');
+INSERT INTO t2 (a,b) VALUES (4,'d');
+REPAIR NO_WRITE_TO_BINLOG TABLE t1, t2;
+INSERT INTO t2 (a,b) VALUES (5,'e'),(6,'f');
+REPAIR LOCAL TABLE t2;
+INSERT INTO t1 (a,b) VALUES (7,'g'),(8,'h');
+INSERT INTO t2 (a,b) VALUES (9,'i');
+REPAIR LOCAL TABLE t2, t1 EXTENDED;
+INSERT INTO t1 (a,b) VALUES (10,'j');
+INSERT INTO t2 (a,b) VALUES (11,'k');
+REPAIR TABLE t1, t2 QUICK USE_FRM;
+INSERT INTO t1 (a,b) VALUES (12,'l');
+INSERT INTO t2 (a,b) VALUES (13,'m');
+REPAIR NO_WRITE_TO_BINLOG TABLE t1, t2 QUICK EXTENDED USE_FRM;
+FLUSH TABLE t1;
+
+DROP TABLE t1, t2;
+
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/repair_table.test b/storage/rocksdb/mysql-test/rocksdb/t/repair_table.test
new file mode 100644
index 00000000..5c4807ce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/repair_table.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# REPAIR TABLE statements
+#
+
+--source repair_table.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/replace.test b/storage/rocksdb/mysql-test/rocksdb/t/replace.test
new file mode 100644
index 00000000..3ac37330
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/replace.test
@@ -0,0 +1,54 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic REPLACE statements
+#
+
+###########################################
+# TODO:
+# A part of the test is currently disabled
+# because unique indexes are not supported
+###########################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+# When there is no duplicate key, REPLACE should work as INSERT
+
+REPLACE INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
+--sorted_result
+SELECT a,b FROM t1;
+
+REPLACE t1 (a,b) VALUE (10,'foo'),(10,'foo');
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+--disable_parsing
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX (a)) ENGINE=rocksdb;
+REPLACE INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (2,'d');
+REPLACE INTO t1 (a,b) VALUES (2,'d');
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (a INT, b CHAR(8), PRIMARY KEY (b)) ENGINE=rocksdb;
+REPLACE INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b) VALUES (4,'b');
+REPLACE INTO t1 (a,b) VALUES (4,'b');
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb-master.opt
new file mode 100644
index 00000000..6ad42e58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000 --rocksdb_records_in_range=50
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb.test
new file mode 100644
index 00000000..f7de167b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb.test
@@ -0,0 +1,1974 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+--source include/have_write_committed.inc
+
+#
+# RocksDB Storage Engine tests
+#
+select ENGINE,COMMENT,TRANSACTIONS,XA,SAVEPOINTS from information_schema.engines where engine = 'rocksdb';
+
+--disable_warnings
+drop table if exists t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10;
+drop table if exists t11,t12,t13,t14,t15,t16,t17,t18,t19,t20;
+drop table if exists t21,t22,t23,t24,t25,t26,t27,t28,t29;
+drop table if exists t30,t31,t32,t33,t34,t35,t36,t37,t38,t39;
+drop table if exists t40,t41,t42,t43,t44,t45,t46,t47,t48,t49;
+--enable_warnings
+
+# Disable background compaction to prevent stats from affect explain output
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--echo #
+--echo # Issue #1: Don't update indexes if index values have not changed
+--echo #
+# [Jay Edgar] I moved this test first because it uses the
+# rocksdb_number_keys_written value, but this value is affected out of band
+# by drop tables. There is a background thread that periodically processes
+# through the list of dropped keys and if any are gone from the database it
+# deletes information related to the key - and this delete causes this count
+# to be incorrect. I moved this test first and made the whole test require
+# a fresh server to hopefully avoid tihs.
+create table t1 (
+ pk int primary key,
+ a int,
+ b int,
+ key(a)
+) engine=rocksdb;
+
+insert into t1 values
+(1,1,1), (2,2,2), (3,3,3), (4,4,4);
+
+set @var1=(select variable_value
+ from information_schema.global_status
+ where variable_name='rocksdb_number_keys_written');
+
+--echo # Do an update that doesn't change the key 'a'.
+update t1 set b=3334341 where a=2;
+
+set @var2=(select variable_value
+ from information_schema.global_status
+ where variable_name='rocksdb_number_keys_written');
+--echo # The following should produce 1
+select @var2 - @var1;
+
+--echo # Do an update that sets the key to the same value
+update t1 set a=pk where a=3;
+set @var3=(select variable_value
+ from information_schema.global_status
+ where variable_name='rocksdb_number_keys_written');
+--echo # We have 'updated' column to the same value, so the following must return 0:
+select @var3 - @var2;
+drop table t1;
+
+create table t0 (a int primary key) engine=rocksdb;
+show create table t0;
+drop table t0;
+
+create table t1 (a int primary key, b int) engine=rocksdb;
+insert into t1 values (1,1);
+insert into t1 values (2,2);
+
+select * from t1;
+
+--echo # Check that we can create another table and insert there
+create table t2 (a varchar(10) primary key, b varchar(10)) engine=rocksdb;
+insert into t2 value ('abc','def');
+insert into t2 value ('hijkl','mnopq');
+select * from t2;
+
+--echo # Select again from t1 to see that records from different tables dont mix
+select * from t1;
+
+explain select * from t2 where a='no-such-key';
+--replace_column 9 #
+explain select * from t2 where a='abc';
+select * from t2 where a='abc';
+
+--echo # Try a composite PK
+create table t3 (
+ pk1 int,
+ pk2 varchar(10),
+ col1 varchar(10),
+ primary key(pk1, pk2)
+) engine=rocksdb;
+
+insert into t3 values (2,'two', 'row#2');
+insert into t3 values (3,'three', 'row#3');
+insert into t3 values (1,'one', 'row#1');
+
+select * from t3;
+select * from t3 where pk1=3 and pk2='three';
+
+drop table t1, t2, t3;
+
+--echo #
+--echo # Test blob values
+--echo #
+
+create table t4 (a int primary key, b blob) engine=rocksdb;
+insert into t4 values (1, repeat('quux-quux', 60));
+insert into t4 values (10, repeat('foo-bar', 43));
+insert into t4 values (5, repeat('foo-bar', 200));
+
+insert into t4 values (2, NULL);
+
+
+select
+ a,
+ (case a
+ when 1 then b=repeat('quux-quux', 60)
+ when 10 then b=repeat('foo-bar', 43)
+ when 5 then b=repeat('foo-bar', 200)
+ when 2 then b is null
+ else 'IMPOSSIBLE!' end) as CMP
+from t4;
+
+drop table t4;
+
+--echo #
+--echo # Test blobs of various sizes
+--echo #
+
+--echo # TINYBLOB
+create table t5 (a int primary key, b tinyblob) engine=rocksdb;
+insert into t5 values (1, repeat('quux-quux', 6));
+insert into t5 values (10, repeat('foo-bar', 4));
+insert into t5 values (5, repeat('foo-bar', 2));
+select
+ a,
+ (case a
+ when 1 then b=repeat('quux-quux', 6)
+ when 10 then b=repeat('foo-bar', 4)
+ when 5 then b=repeat('foo-bar', 2)
+ else 'IMPOSSIBLE!' end) as CMP
+from t5;
+drop table t5;
+
+--echo # MEDIUMBLOB
+create table t6 (a int primary key, b mediumblob) engine=rocksdb;
+insert into t6 values (1, repeat('AB', 65000));
+insert into t6 values (10, repeat('bbb', 40000));
+insert into t6 values (5, repeat('foo-bar', 2));
+select
+ a,
+ (case a
+ when 1 then b=repeat('AB', 65000)
+ when 10 then b=repeat('bbb', 40000)
+ when 5 then b=repeat('foo-bar', 2)
+ else 'IMPOSSIBLE!' end) as CMP
+from t6;
+drop table t6;
+
+--echo # LONGBLOB
+create table t7 (a int primary key, b longblob) engine=rocksdb;
+insert into t7 values (1, repeat('AB', 65000));
+insert into t7 values (10, repeat('bbb', 40000));
+insert into t7 values (5, repeat('foo-bar', 2));
+select
+ a,
+ (case a
+ when 1 then b=repeat('AB', 65000)
+ when 10 then b=repeat('bbb', 40000)
+ when 5 then b=repeat('foo-bar', 2)
+ else 'IMPOSSIBLE!' end) as CMP
+from t7;
+drop table t7;
+
+
+--echo #
+--echo # Check if DELETEs work
+--echo #
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+
+insert into t8 values
+ ('one', 'eins'),
+ ('two', 'zwei'),
+ ('three', 'drei'),
+ ('four', 'vier'),
+ ('five', 'funf');
+
+--echo # Delete by PK
+--replace_column 9 #
+explain delete from t8 where a='three';
+delete from t8 where a='three';
+
+select * from t8;
+
+--echo # Delete while doing a full table scan
+delete from t8 where col1='eins' or col1='vier';
+select * from t8;
+
+--echo # delete w/o WHERE:
+delete from t8;
+select * from t8;
+
+--echo #
+--echo # Test UPDATEs
+--echo #
+insert into t8 values
+ ('one', 'eins'),
+ ('two', 'zwei'),
+ ('three', 'drei'),
+ ('four', 'vier'),
+ ('five', 'funf');
+
+update t8 set col1='dva' where a='two';
+
+update t8 set a='fourAAA' where col1='vier';
+
+select * from t8;
+delete from t8;
+
+--echo #
+--echo # Basic transactions tests
+--echo #
+begin;
+insert into t8 values ('trx1-val1', 'data');
+insert into t8 values ('trx1-val2', 'data');
+rollback;
+select * from t8;
+
+begin;
+insert into t8 values ('trx1-val1', 'data');
+insert into t8 values ('trx1-val2', 'data');
+commit;
+select * from t8;
+
+drop table t8;
+
+--echo #
+--echo # Check if DROP TABLE works
+--echo #
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+select * from t8;
+insert into t8 values ('foo','foo');
+drop table t8;
+create table t8 (a varchar(10) primary key, col1 varchar(12)) engine=rocksdb;
+select * from t8;
+drop table t8;
+
+--echo #
+--echo # MDEV-3961: Assertion ... on creating a TEMPORARY RocksDB table
+--echo #
+--error ER_ILLEGAL_HA_CREATE_OPTION
+CREATE TEMPORARY TABLE t10 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+
+--echo #
+--echo # MDEV-3963: JOIN or WHERE conditions involving keys on RocksDB tables don't work
+--echo #
+CREATE TABLE t10 (i INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t10 VALUES (1),(3);
+CREATE TABLE t11 (j INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t11 VALUES (1),(4);
+
+select * from t10;
+select * from t11;
+--replace_column 9 #
+EXPLAIN
+SELECT * FROM t10, t11 WHERE i=j;
+SELECT * FROM t10, t11 WHERE i=j;
+
+DROP TABLE t10,t11;
+
+--echo #
+--echo # MDEV-3962: SELECT with ORDER BY causes "ERROR 1030 (HY000): Got error 122
+--echo #
+CREATE TABLE t12 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t12 VALUES (2),(1);
+SELECT * FROM t12 ORDER BY pk;
+DROP TABLE t12;
+
+--echo #
+--echo # MDEV-3964: Assertion `!pk_descr' fails in ha_rocksdb::open on adding partitions ...
+--echo #
+create table t14 (pk int primary key) engine=RocksDB partition by hash(pk) partitions 2;
+#--error ER_GET_ERRNO
+#alter table t14 add partition partitions 2;
+# ^^ works, but causes weird warnings in error log.
+drop table t14;
+
+--echo #
+--echo # MDEV-3960: Server crashes on running DISCARD TABLESPACE on a RocksDB table
+--echo #
+create table t9 (i int primary key) engine=rocksdb;
+--error ER_ILLEGAL_HA
+alter table t9 discard tablespace;
+drop table t9;
+
+--echo #
+--echo # MDEV-3959: Assertion `slice->size() == table->s->reclength' fails ...
+--echo # on accessing a table after ALTER
+--echo #
+CREATE TABLE t15 (a INT, rocksdb_pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t15 VALUES (1,1),(5,2);
+#--error ER_ILLEGAL_HA
+ALTER TABLE t15 DROP COLUMN a;
+DROP TABLE t15;
+
+--echo #
+--echo # MDEV-3968: UPDATE produces a wrong result while modifying a PK on a RocksDB table
+--echo #
+create table t16 (pk int primary key, a char(8)) engine=RocksDB;
+insert into t16 values (1,'a'),(2,'b'),(3,'c'),(4,'d');
+
+#
+# Not anymore: The following query will still eat a record because of CANT-SEE-OWN-CHANGES
+# property.
+#
+--error ER_DUP_ENTRY
+update t16 set pk=100, a = 'updated' where a in ('b','c');
+select * from t16;
+drop table t16;
+
+--echo #
+--echo # MDEV-3970: A set of assorted crashes on inserting a row into a RocksDB table
+--echo #
+--disable_warnings
+drop table if exists t_very_long_table_name;
+--enable_warnings
+
+CREATE TABLE `t_very_long_table_name` (
+ `c` char(1) NOT NULL,
+ `c0` char(0) NOT NULL,
+ `c1` char(1) NOT NULL,
+ `c20` char(20) NOT NULL,
+ `c255` char(255) NOT NULL,
+ PRIMARY KEY (`c255`)
+ ) ENGINE=RocksDB DEFAULT CHARSET=latin1;
+INSERT INTO t_very_long_table_name VALUES ('a', '', 'c', REPEAT('a',20), REPEAT('x',255));
+drop table t_very_long_table_name;
+
+
+--echo #
+--echo # Test table locking and read-before-write checks.
+--echo #
+create table t17 (pk varchar(12) primary key, col1 varchar(12)) engine=rocksdb;
+insert into t17 values ('row1', 'val1');
+
+--error ER_DUP_ENTRY
+insert into t17 values ('row1', 'val1-try2');
+--error ER_DUP_ENTRY
+insert into t17 values ('ROW1', 'val1-try2');
+
+insert into t17 values ('row2', 'val2');
+insert into t17 values ('row3', 'val3');
+
+--echo # This is ok
+update t17 set pk='row4' where pk='row1';
+
+--echo # This will try to overwrite another row:
+--error ER_DUP_ENTRY
+update t17 set pk='row3' where pk='row2';
+
+select * from t17;
+
+--echo #
+--echo # Locking tests
+--echo #
+
+connect (con1,localhost,root,,);
+
+--echo # First, make sure there's no locking when transactions update different rows
+connection con1;
+set autocommit=0;
+update t17 set col1='UPD1' where pk='row2';
+
+connection default;
+update t17 set col1='UPD2' where pk='row3';
+
+connection con1;
+commit;
+
+connection default;
+select * from t17;
+
+--echo # Check the variable
+show variables like 'rocksdb_lock_wait_timeout';
+set rocksdb_lock_wait_timeout=2; # seconds
+show variables like 'rocksdb_lock_wait_timeout';
+
+--echo # Try updating the same row from two transactions
+connection con1;
+begin;
+update t17 set col1='UPD2-AA' where pk='row2';
+
+connection default;
+--error ER_LOCK_WAIT_TIMEOUT
+update t17 set col1='UPD2-BB' where pk='row2';
+
+set rocksdb_lock_wait_timeout=1000; # seconds
+--send
+ update t17 set col1='UPD2-CC' where pk='row2';
+
+connection con1;
+rollback;
+
+connection default;
+reap;
+select * from t17 where pk='row2';
+
+drop table t17;
+
+disconnect con1;
+--echo #
+--echo # MDEV-4035: RocksDB: SELECT produces different results inside a transaction (read is not repeatable)
+--echo #
+--enable_connect_log
+
+create table t18 (pk int primary key, i int) engine=RocksDB;
+begin;
+select * from t18;
+select * from t18 where pk = 1;
+
+--connect (con1,localhost,root,,)
+insert into t18 values (1,100);
+
+--connection default
+select * from t18;
+select * from t18 where pk = 1;
+commit;
+
+drop table t18;
+
+--echo #
+--echo # MDEV-4036: RocksDB: INSERT .. ON DUPLICATE KEY UPDATE does not work, produces ER_DUP_KEY
+--echo #
+create table t19 (pk int primary key, i int) engine=RocksDB;
+insert into t19 values (1,1);
+insert into t19 values (1,100) on duplicate key update i = 102;
+select * from t19;
+drop table t19;
+
+--echo # MDEV-4037: RocksDB: REPLACE doesn't work, produces ER_DUP_KEY
+create table t20 (pk int primary key, i int) engine=RocksDB;
+insert into t20 values (1,1);
+replace into t20 values (1,100);
+select * from t20;
+drop table t20;
+
+--echo #
+--echo # MDEV-4041: Server crashes in Primary_key_comparator::get_hashnr on INSERT
+--echo #
+create table t21 (v varbinary(16) primary key, i int) engine=RocksDB;
+insert into t21 values ('a',1);
+select * from t21;
+drop table t21;
+
+--echo #
+--echo # MDEV-4047: RocksDB: Assertion `0' fails in Protocol::end_statement() on multi-table INSERT IGNORE
+--echo #
+
+CREATE TABLE t22 (a int primary key) ENGINE=RocksDB;
+INSERT INTO t22 VALUES (1),(2);
+CREATE TABLE t23 (b int primary key) ENGINE=RocksDB;
+INSERT INTO t23 SELECT * FROM t22;
+DELETE IGNORE t22.*, t23.* FROM t22, t23 WHERE b < a;
+DROP TABLE t22,t23;
+
+--echo #
+--echo # MDEV-4046: RocksDB: Multi-table DELETE locks itself and ends with ER_LOCK_WAIT_TIMEOUT
+--echo #
+CREATE TABLE t24 (pk int primary key) ENGINE=RocksDB;
+INSERT INTO t24 VALUES (1),(2);
+
+CREATE TABLE t25 LIKE t24;
+INSERT INTO t25 SELECT * FROM t24;
+
+DELETE t25.* FROM t24, t25;
+DROP TABLE t24,t25;
+
+--echo #
+--echo # MDEV-4044: RocksDB: UPDATE or DELETE with ORDER BY locks itself
+--echo #
+create table t26 (pk int primary key, c char(1)) engine=RocksDB;
+insert into t26 values (1,'a'),(2,'b');
+update t26 set c = 'x' order by pk limit 1;
+delete from t26 order by pk limit 1;
+select * from t26;
+drop table t26;
+
+
+--echo #
+--echo # Test whether SELECT ... FOR UPDATE puts locks
+--echo #
+create table t27(pk varchar(10) primary key, col1 varchar(20)) engine=RocksDB;
+insert into t27 values
+ ('row1', 'row1data'),
+ ('row2', 'row2data'),
+ ('row3', 'row3data');
+
+connection con1;
+begin;
+select * from t27 where pk='row3' for update;
+
+connection default;
+set rocksdb_lock_wait_timeout=1;
+--error ER_LOCK_WAIT_TIMEOUT
+update t27 set col1='row2-modified' where pk='row3';
+
+connection con1;
+rollback;
+connection default;
+disconnect con1;
+
+drop table t27;
+
+--echo #
+--echo # MDEV-4060: RocksDB: Assertion `! trx->batch' fails in
+--echo #
+create table t28 (pk int primary key, a int) engine=RocksDB;
+insert into t28 values (1,10),(2,20);
+begin;
+update t28 set a = 100 where pk = 3;
+rollback;
+select * from t28;
+drop table t28;
+
+
+--echo #
+--echo # Secondary indexes
+--echo #
+create table t30 (
+ pk varchar(16) not null primary key,
+ key1 varchar(16) not null,
+ col1 varchar(16) not null,
+ key(key1)
+) engine=rocksdb;
+
+insert into t30 values ('row1', 'row1-key', 'row1-data');
+insert into t30 values ('row2', 'row2-key', 'row2-data');
+insert into t30 values ('row3', 'row3-key', 'row3-data');
+
+--replace_column 9 #
+explain
+select * from t30 where key1='row2-key';
+select * from t30 where key1='row2-key';
+
+--replace_column 9 #
+explain
+select * from t30 where key1='row1';
+--echo # This will produce nothing:
+select * from t30 where key1='row1';
+
+--replace_column 9 #
+explain
+select key1 from t30;
+select key1 from t30;
+
+--echo # Create a duplicate record
+insert into t30 values ('row2a', 'row2-key', 'row2a-data');
+
+--echo # Can we see it?
+select * from t30 where key1='row2-key';
+
+delete from t30 where pk='row2';
+select * from t30 where key1='row2-key';
+
+--echo #
+--echo # Range scans on secondary index
+--echo #
+delete from t30;
+insert into t30 values
+ ('row1', 'row1-key', 'row1-data'),
+ ('row2', 'row2-key', 'row2-data'),
+ ('row3', 'row3-key', 'row3-data'),
+ ('row4', 'row4-key', 'row4-data'),
+ ('row5', 'row5-key', 'row5-data');
+
+--replace_column 9 #
+explain
+select * from t30 where key1 <='row3-key';
+select * from t30 where key1 <='row3-key';
+
+--replace_column 9 #
+explain
+select * from t30 where key1 between 'row2-key' and 'row4-key';
+select * from t30 where key1 between 'row2-key' and 'row4-key';
+
+--replace_column 9 #
+explain
+select * from t30 where key1 in ('row2-key','row4-key');
+select * from t30 where key1 in ('row2-key','row4-key');
+
+--replace_column 9 #
+explain
+select key1 from t30 where key1 in ('row2-key','row4-key');
+select key1 from t30 where key1 in ('row2-key','row4-key');
+
+--replace_column 9 #
+explain
+select * from t30 where key1 > 'row1-key' and key1 < 'row4-key';
+select * from t30 where key1 > 'row1-key' and key1 < 'row4-key';
+
+--replace_column 9 #
+explain
+select * from t30 order by key1 limit 3;
+select * from t30 order by key1 limit 3;
+
+--replace_column 9 #
+explain
+select * from t30 order by key1 desc limit 3;
+select * from t30 order by key1 desc limit 3;
+
+--echo #
+--echo # Range scans on primary key
+--echo #
+--replace_column 9 #
+explain
+select * from t30 where pk <='row3';
+select * from t30 where pk <='row3';
+
+--replace_column 9 #
+explain
+select * from t30 where pk between 'row2' and 'row4';
+select * from t30 where pk between 'row2' and 'row4';
+
+--replace_column 9 #
+explain
+select * from t30 where pk in ('row2','row4');
+select * from t30 where pk in ('row2','row4');
+
+--replace_column 9 #
+explain
+select * from t30 order by pk limit 3;
+select * from t30 order by pk limit 3;
+
+drop table t30;
+
+
+--echo #
+--echo # MDEV-3841: RocksDB: Reading by PK prefix does not work
+--echo #
+create table t31 (i int, j int, k int, primary key(i,j,k)) engine=RocksDB;
+insert into t31 values (1,10,100),(2,20,200);
+select * from t31 where i = 1;
+select * from t31 where j = 10;
+select * from t31 where k = 100;
+select * from t31 where i = 1 and j = 10;
+select * from t31 where i = 1 and k = 100;
+select * from t31 where j = 10 and k = 100;
+select * from t31 where i = 1 and j = 10 and k = 100;
+drop table t31;
+
+--echo #
+--echo # MDEV-4055: RocksDB: UPDATE/DELETE by a multi-part PK does not work
+--echo #
+create table t32 (i int, j int, k int, primary key(i,j,k), a varchar(8)) engine=RocksDB;
+insert into t32 values
+ (1,10,100,''),
+ (2,20,200,'');
+select * from t32 where i = 1 and j = 10 and k = 100;
+update t32 set a = 'updated' where i = 1 and j = 10 and k = 100;
+select * from t32;
+drop table t32;
+
+--echo #
+--echo # MDEV-3841: RocksDB: Assertion `0' fails in ha_rocksdb::index_read_map on range select with ORDER BY .. DESC
+--echo #
+CREATE TABLE t33 (pk INT PRIMARY KEY, a CHAR(1)) ENGINE=RocksDB;
+INSERT INTO t33 VALUES (1,'a'),(2,'b');
+SELECT * FROM t33 WHERE pk <= 10 ORDER BY pk DESC;
+DROP TABLE t33;
+
+--echo #
+--echo # MDEV-4081: RocksDB throws error 122 on an attempt to create a table with unique index
+--echo #
+#--error ER_GET_ERRMSG
+--echo # Unique indexes can be created, but uniqueness won't be enforced
+create table t33 (pk int primary key, u int, unique index(u)) engine=RocksDB;
+drop table t33;
+
+--echo #
+--echo # MDEV-4077: RocksDB: Wrong result (duplicate row) on select with range
+--echo #
+CREATE TABLE t34 (pk INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t34 VALUES (10),(11);
+SELECT pk FROM t34 WHERE pk > 5 AND pk < 15;
+SELECT pk FROM t34 WHERE pk BETWEEN 5 AND 15;
+SELECT pk FROM t34 WHERE pk > 5;
+SELECT pk FROM t34 WHERE pk < 15;
+drop table t34;
+
+--echo #
+--echo # MDEV-4086: RocksDB does not allow a query with multi-part pk and index and ORDER BY .. DEC
+--echo #
+create table t35 (a int, b int, c int, d int, e int, primary key (a,b,c), key (a,c,d,e)) engine=RocksDB;
+insert into t35 values (1,1,1,1,1),(2,2,2,2,2);
+select * from t35 where a = 1 and c = 1 and d = 1 order by e desc;
+drop table t35;
+
+--echo #
+--echo # MDEV-4084: RocksDB: Wrong result on IN subquery with index
+--echo #
+CREATE TABLE t36 (pk INT PRIMARY KEY, a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t36 VALUES (1,10),(2,20);
+SELECT 3 IN ( SELECT a FROM t36 );
+drop table t36;
+
+--echo #
+--echo # MDEV-4084: RocksDB: Wrong result on IN subquery with index
+--echo #
+CREATE TABLE t37 (pk INT PRIMARY KEY, a INT, b CHAR(1), KEY(a), KEY(a,b))
+ ENGINE=RocksDB;
+INSERT INTO t37 VALUES (1,10,'x'), (2,20,'y');
+SELECT MAX(a) FROM t37 WHERE a < 100;
+DROP TABLE t37;
+
+--echo #
+--echo # MDEV-4090: RocksDB: Wrong result (duplicate rows) on range access with secondary key and ORDER BY DESC
+--echo #
+CREATE TABLE t38 (pk INT PRIMARY KEY, i INT, KEY(i)) ENGINE=RocksDB;
+INSERT INTO t38 VALUES (1,10), (2,20);
+SELECT i FROM t38 WHERE i NOT IN (8) ORDER BY i DESC;
+drop table t38;
+
+--echo #
+--echo # MDEV-4092: RocksDB: Assertion `in_table(pa, a_len)' fails in Rdb_key_def::cmp_full_keys
+--echo # with a multi-part key and ORDER BY .. DESC
+--echo #
+CREATE TABLE t40 (pk1 INT PRIMARY KEY, a INT, b VARCHAR(1), KEY(b,a)) ENGINE=RocksDB;
+INSERT INTO t40 VALUES (1, 7,'x'),(2,8,'y');
+
+CREATE TABLE t41 (pk2 INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t41 VALUES (1),(2);
+
+SELECT * FROM t40, t41 WHERE pk1 = pk2 AND b = 'o' ORDER BY a DESC;
+DROP TABLE t40,t41;
+
+--echo #
+--echo # MDEV-4093: RocksDB: IN subquery by secondary key with NULL among values returns true instead of NULL
+--echo #
+CREATE TABLE t42 (pk INT PRIMARY KEY, a INT, KEY(a)) ENGINE=RocksDB;
+INSERT INTO t42 VALUES (1, NULL),(2, 8);
+SELECT ( 3 ) NOT IN ( SELECT a FROM t42 );
+DROP TABLE t42;
+
+--echo #
+--echo # MDEV-4094: RocksDB: Wrong result on SELECT and ER_KEY_NOT_FOUND on
+--echo # DELETE with search by NULL-able secondary key ...
+--echo #
+CREATE TABLE t43 (pk INT PRIMARY KEY, a INT, b CHAR(1), KEY(a)) ENGINE=RocksDB;
+INSERT INTO t43 VALUES (1,8,'g'),(2,9,'x');
+UPDATE t43 SET pk = 10 WHERE a = 8;
+REPLACE INTO t43 ( a ) VALUES ( 8 );
+REPLACE INTO t43 ( b ) VALUES ( 'y' );
+SELECT * FROM t43 WHERE a = 8;
+DELETE FROM t43 WHERE a = 8;
+DROP TABLE t43;
+
+--echo #
+--echo # Basic AUTO_INCREMENT tests
+--echo #
+create table t44(pk int primary key auto_increment, col1 varchar(12)) engine=rocksdb;
+insert into t44 (col1) values ('row1');
+insert into t44 (col1) values ('row2');
+insert into t44 (col1) values ('row3');
+select * from t44;
+drop table t44;
+
+--echo #
+--echo # ALTER TABLE tests
+--echo #
+create table t45 (pk int primary key, col1 varchar(12)) engine=rocksdb;
+insert into t45 values (1, 'row1');
+insert into t45 values (2, 'row2');
+alter table t45 rename t46;
+select * from t46;
+drop table t46;
+--error ER_BAD_TABLE_ERROR
+drop table t45;
+
+
+--echo #
+--echo # Check Bulk loading
+--echo # Bulk loading used to overwrite existing data
+--echo # Now it fails if there is data overlap with what
+--echo # already exists
+--echo #
+# We exclude rocksdb_max_open_files here because it value is dependent on
+# the value of the servers open_file_limit and is expected to be different
+# across distros and installs
+
+--replace_regex /[a-f0-9]{40}/#/
+show variables
+where
+ variable_name like 'rocksdb%' and
+ variable_name not like 'rocksdb_max_open_files' and
+ variable_name not like 'rocksdb_supported_compression_types';
+
+create table t47 (pk int primary key, col1 varchar(12)) engine=rocksdb;
+insert into t47 values (1, 'row1');
+insert into t47 values (2, 'row2');
+set rocksdb_bulk_load=1;
+insert into t47 values (3, 'row3'),(4, 'row4');
+set rocksdb_bulk_load=0;
+# Check concurrent bulk loading
+--connect (con1,localhost,root,,)
+set rocksdb_bulk_load=1;
+insert into t47 values (10, 'row10'),(11, 'row11');
+--connection default
+set rocksdb_bulk_load=1;
+insert into t47 values (100, 'row100'),(101, 'row101');
+--disconnect con1
+--connection default
+set rocksdb_bulk_load=0;
+--disable_query_log
+let $wait_condition = select count(*) = 8 as c from t47;
+--source include/wait_condition.inc
+--enable_query_log
+select * from t47;
+drop table t47;
+
+--echo #
+--echo # Fix TRUNCATE over empty table (transaction is committed when it wasn't
+--echo # started)
+--echo #
+create table t48(pk int primary key auto_increment, col1 varchar(12)) engine=rocksdb;
+set autocommit=0;
+#--error ER_ILLEGAL_HA
+truncate table t48;
+set autocommit=1;
+drop table t48;
+
+--echo #
+--echo # MDEV-4059: RocksDB: query waiting for a lock cannot be killed until query timeout exceeded
+--echo #
+--enable_connect_log
+
+create table t49 (pk int primary key, a int) engine=RocksDB;
+insert into t49 values (1,10),(2,20);
+begin;
+update t49 set a = 100 where pk = 1;
+
+--connect (con1,localhost,root,,)
+--let $con1_id = `SELECT CONNECTION_ID()`
+set rocksdb_lock_wait_timeout=60;
+set @var1= to_seconds(now());
+send update t49 set a = 1000 where pk = 1;
+
+--connect (con2,localhost,root,,)
+--echo kill query \$con1_id;
+--disable_query_log
+# If we immeditely kill the query - internally the condition broadcast can
+# occur before the lock is waiting on the condition, thus the broadcast call
+# is lost. Sleep 1 second to avoid this condition.
+--sleep 1
+eval kill query $con1_id;
+--enable_query_log
+--connection con1
+--error ER_QUERY_INTERRUPTED
+--reap
+set @var2= to_seconds(now());
+
+# We expect the time to kill query in con1 should be below
+# rocksdb_lock_wait_timeout (60).
+select if ((@var2 - @var1) < 60, "passed", (@var2 - @var1)) as 'result';
+
+--connection default
+--disconnect con1
+
+commit;
+drop table t49;
+
+--echo #
+--echo # Index-only tests for INT-based columns
+--echo #
+create table t1 (pk int primary key, key1 int, col1 int, key(key1)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,2,2);
+insert into t1 values (-5,-5,-5);
+--echo # INT column uses index-only:
+--replace_column 9 #
+explain
+select key1 from t1 where key1=2;
+select key1 from t1 where key1=2;
+select key1 from t1 where key1=-5;
+drop table t1;
+
+
+create table t2 (pk int primary key, key1 int unsigned, col1 int, key(key1)) engine=rocksdb;
+insert into t2 values (1,1,1), (2,2,2);
+--echo # INT UNSIGNED column uses index-only:
+--replace_column 9 #
+explain
+select key1 from t2 where key1=2;
+select key1 from t2 where key1=2;
+drop table t2;
+
+
+create table t3 (pk bigint primary key, key1 bigint, col1 int, key(key1)) engine=rocksdb;
+insert into t3 values (1,1,1), (2,2,2);
+--echo # BIGINT uses index-only:
+--replace_column 9 #
+explain
+select key1 from t3 where key1=2;
+select key1 from t3 where key1=2;
+drop table t3;
+
+--echo #
+--echo # Index-only reads for string columns
+--echo #
+create table t1 (
+ pk int primary key,
+ key1 char(10) character set binary,
+ col1 int,
+ key (key1)
+) engine=rocksdb;
+insert into t1 values(1, 'one',11), (2,'two',22);
+--replace_column 9 #
+explain
+select key1 from t1 where key1='one';
+--echo # The following will produce no rows. This looks like a bug,
+--echo # but it is actually correct behavior. Binary strings are end-padded
+--echo # with \0 character (and not space). Comparison does not ignore
+--echo # the tail of \0.
+select key1 from t1 where key1='one';
+--replace_column 9 #
+explain
+select hex(key1) from t1 where key1='one\0\0\0\0\0\0\0';
+select hex(key1) from t1 where key1='one\0\0\0\0\0\0\0';
+drop table t1;
+
+
+create table t2 (
+ pk int primary key,
+ key1 char(10) collate latin1_bin,
+ col1 int,
+ key (key1)
+) engine=rocksdb;
+insert into t2 values(1, 'one',11), (2,'two',22);
+--replace_column 9 #
+explain
+select key1 from t2 where key1='one';
+select key1 from t2 where key1='one';
+drop table t2;
+
+
+create table t3 (
+ pk int primary key,
+ key1 char(10) collate utf8_bin,
+ col1 int,
+ key (key1)
+) engine=rocksdb;
+insert into t3 values(1, 'one',11), (2,'two',22);
+--replace_column 9 #
+explain
+select key1 from t3 where key1='one';
+select key1 from t3 where key1='one';
+drop table t3;
+
+
+--echo # a VARCHAR column
+create table t4 (
+ pk int primary key,
+ key1 varchar(10) collate latin1_bin,
+ key(key1)
+) engine=rocksdb;
+insert into t4 values(1, 'one'), (2,'two'),(3,'threee'),(55,'fifty-five');
+
+--replace_column 9 #
+explain
+select key1 from t4 where key1='two';
+select key1 from t4 where key1='two';
+
+select key1 from t4 where key1='fifty-five';
+
+--replace_column 9 #
+explain
+select key1 from t4 where key1 between 's' and 'u';
+select key1 from t4 where key1 between 's' and 'u';
+
+drop table t4;
+
+--echo #
+--echo # MDEV-4305: RocksDB: Assertion `((keypart_map + 1) & keypart_map) == 0' fails in calculate_key_len
+--echo #
+CREATE TABLE t1 (pk1 INT, pk2 CHAR(32), i INT, PRIMARY KEY(pk1,pk2), KEY(i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'test1',6),(2,'test2',8);
+SELECT * FROM t1 WHERE i != 3 OR pk1 > 9;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4298: RocksDB: Assertion `thd->is_error() || kill_errno' fails in ha_rows filesort
+--echo #
+call mtr.add_suppression("Sort aborted");
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT, KEY(i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1),(2,2);
+BEGIN;
+UPDATE t1 SET i = 100;
+
+--connect (con1,localhost,root,,test)
+--error ER_LOCK_WAIT_TIMEOUT
+DELETE IGNORE FROM t1 ORDER BY i;
+--disconnect con1
+
+--connection default
+COMMIT;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4324: RocksDB: Valgrind "Use of uninitialised value" warnings on inserting value into varchar field
+--echo # (testcase only)
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, c VARCHAR(4)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'foo'), (2,'bar');
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4304: RocksDB: Index-only scan by a field with utf8_bin collation returns garbage symbols
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, c1 CHAR(1), c2 CHAR(1), KEY(c1)) ENGINE=RocksDB CHARSET utf8 COLLATE utf8_bin;
+INSERT INTO t1 VALUES (1,'h','h');
+SELECT * FROM t1;
+SELECT c1 FROM t1;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4300: RocksDB: Server crashes in inline_mysql_mutex_lock on SELECT .. FOR UPDATE
+--echo #
+CREATE TABLE t2 (pk INT PRIMARY KEY, i INT, KEY (i)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,4),(2,5);
+SELECT 1 FROM t2 WHERE i < 0 FOR UPDATE;
+DROP TABLE t2;
+
+--echo #
+--echo # MDEV-4301: RocksDB: Assertion `pack_info != __null' fails in Rdb_key_def::unpack_record
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, i INT, c CHAR(1), KEY(c,i)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,4,'d'),(2,8,'e');
+SELECT MAX( pk ) FROM t1 WHERE i = 105 AND c = 'h';
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-4337: RocksDB: Inconsistent results comparing a char field with an int field
+--echo #
+create table t1 (c char(1), i int, primary key(c), key(i)) engine=RocksDB;
+insert into t1 values ('2',2),('6',6);
+select * from t1 where c = i;
+select * from t1 ignore index (i) where c = i;
+drop table t1;
+
+
+--echo #
+--echo # Test statement rollback inside a transaction
+--echo #
+create table t1 (pk varchar(12) primary key) engine=rocksdb;
+insert into t1 values ('old-val1'),('old-val2');
+
+create table t2 (pk varchar(12) primary key) engine=rocksdb;
+insert into t2 values ('new-val2'),('old-val1');
+
+begin;
+insert into t1 values ('new-val1');
+--error ER_DUP_ENTRY
+insert into t1 select * from t2;
+commit;
+
+select * from t1;
+drop table t1, t2;
+
+--echo #
+--echo # MDEV-4383: RocksDB: Wrong result of DELETE .. ORDER BY .. LIMIT:
+--echo # rows that should be deleted remain in the table
+--echo #
+CREATE TABLE t2 (pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=RocksDB;
+CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=RocksDB;
+
+INSERT INTO t1 (pk) VALUES (NULL),(NULL);
+BEGIN;
+INSERT INTO t2 (pk) VALUES (NULL),(NULL);
+INSERT INTO t1 (pk) VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL);
+
+--enable_info
+SELECT * FROM t1 ORDER BY pk LIMIT 9;
+DELETE FROM t1 ORDER BY pk LIMIT 9;
+SELECT * FROM t1 ORDER BY pk LIMIT 9;
+--disable_info
+
+DROP TABLE t1,t2;
+
+--echo #
+--echo # MDEV-4374: RocksDB: Valgrind warnings 'Use of uninitialised value' on
+--echo # inserting into a varchar column
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, a VARCHAR(32)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,'foo'),(2,'bar');
+DROP TABLE t1;
+
+
+--echo #
+--echo # MDEV-4061: RocksDB: Changes from an interrupted query are still applied
+--echo #
+
+--enable_connect_log
+
+create table t1 (pk int primary key, a int) engine=RocksDB;
+insert into t1 values (1,10),(2,20);
+
+--let $con_id = `select connection_id()`
+
+set autocommit = 1;
+--send
+update t1 set a = sleep(100) where pk = 1;
+
+--connect (con1,localhost,root,,)
+
+let $wait_condition= select State='User sleep' from information_schema.processlist where id=$con_id/* or srv_id=$con_id*/;
+--source include/wait_condition.inc
+
+--echo kill query \$con_id;
+--disable_query_log
+eval kill query $con_id;
+--enable_query_log
+
+--connection default
+--error ER_QUERY_INTERRUPTED
+--reap
+
+select * from t1;
+--disconnect con1
+--disable_connect_log
+drop table t1;
+
+
+--echo #
+--echo # MDEV-4099: RocksDB: Wrong results with index and range access after INSERT IGNORE or REPLACE
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, a SMALLINT, b INT, KEY (a)) ENGINE=RocksDB;
+INSERT IGNORE INTO t1 VALUES (1, 157, 0), (2, 1898, -504403), (1, -14659, 0);
+SELECT * FROM t1;
+SELECT pk FROM t1;
+SELECT * FROM t1 WHERE a != 97;
+DROP TABLE t1;
+
+
+--echo #
+--echo # Test @@rocksdb_max_row_locks
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, a int) ENGINE=RocksDB;
+set @a=-1;
+insert into t1 select (@a:=@a+1), 1234 from information_schema.session_variables limit 100;
+set @tmp1= @@rocksdb_max_row_locks;
+set rocksdb_max_row_locks= 20;
+--error ER_GET_ERRMSG
+update t1 set a=a+10;
+DROP TABLE t1;
+
+
+--echo #
+--echo # Test AUTO_INCREMENT behavior problem,
+--echo # "explicit insert into an auto-inc column is not noticed by RocksDB"
+--echo #
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+
+insert into t1 values (null);
+insert into t1 values (null);
+select * from t1;
+drop table t1;
+
+create table t2 (i int primary key auto_increment) engine=RocksDB;
+
+insert into t2 values (1);
+select * from t2;
+
+--echo # this fails (ie. used to fail), RocksDB engine did not notice use of '1' above
+insert into t2 values (null);
+select * from t2;
+
+--echo # but then this succeeds, so previous statement must have incremented next number counter
+insert into t2 values (null);
+select * from t2;
+drop table t2;
+
+--echo #
+--echo # Fix Issue#2: AUTO_INCREMENT value doesn't survive server shutdown
+--echo #
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+
+insert into t1 values (null);
+insert into t1 values (null);
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
+
+--source include/restart_mysqld.inc
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+insert into t1 values (null);
+select * from t1;
+
+drop table t1;
+
+--echo #
+--echo # Fix Issue #3: SHOW TABLE STATUS shows Auto_increment=0
+--echo #
+create table t1 (i int primary key auto_increment) engine=RocksDB;
+
+insert into t1 values (null),(null);
+--replace_column 7 # 12 # 13 #
+show table status like 't1';
+drop table t1;
+
+--echo #
+--echo # Fix Issue #4: Crash when using pseudo-unique keys
+--echo #
+CREATE TABLE t1 (
+ i INT,
+ t TINYINT,
+ s SMALLINT,
+ m MEDIUMINT,
+ b BIGINT,
+ pk MEDIUMINT AUTO_INCREMENT PRIMARY KEY,
+ UNIQUE KEY b_t (b,t)
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+
+SELECT b+t FROM t1 WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+DROP TABLE t1;
+
+--echo #
+--echo # Fix issue #5: Transaction rollback doesn't undo all changes.
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (id int auto_increment primary key, value int) engine=rocksdb;
+
+set autocommit=0;
+begin;
+set @a:=0;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+insert into t1 select @a:=@a+1, @a from t0 A, t0 B, t0 C, t0 D where D.a<4;
+rollback;
+select count(*) from t1;
+
+set autocommit=1;
+drop table t0, t1;
+
+--echo #
+--echo # Check status variables
+--echo # NOTE: We exclude rocksdb_num_get_for_update_calls because it's a debug only status var
+--echo #
+--replace_column 2 #
+show status where variable_name like 'rocksdb%' and variable_name not like '%num_get_for_update%';
+
+select VARIABLE_NAME from INFORMATION_SCHEMA.global_status where VARIABLE_NAME LIKE 'rocksdb%' and VARIABLE_NAME NOT LIKE '%num_get_for_update%';
+--echo # RocksDB-SE's status variables are global internally
+--echo # but they are shown as both session and global, like InnoDB's status vars.
+select VARIABLE_NAME from INFORMATION_SCHEMA.session_status where VARIABLE_NAME LIKE 'rocksdb%' and VARIABLE_NAME NOT LIKE '%num_get_for_update%';
+
+
+--echo #
+--echo # Fix issue #9: HA_ERR_INTERNAL_ERROR when running linkbench
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (
+ pk int primary key,
+ col1 varchar(255),
+ key(col1)
+) engine=rocksdb;
+insert into t1 select a, repeat('123456789ABCDEF-', 15) from t0;
+select * from t1 where pk=3;
+drop table t0, t1;
+
+--echo #
+--echo # Fix issue #10: Segfault in Rdb_key_def::get_primary_key_tuple
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+CREATE TABLE t1 (
+ id1 bigint(20) unsigned NOT NULL DEFAULT '0',
+ id2 bigint(20) unsigned NOT NULL DEFAULT '0',
+ link_type bigint(20) unsigned NOT NULL DEFAULT '0',
+ visibility tinyint(3) NOT NULL DEFAULT '0',
+ data varchar(255) NOT NULL DEFAULT '',
+ time bigint(20) unsigned NOT NULL DEFAULT '0',
+ version int(11) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (link_type,id1,id2)
+) engine=rocksdb;
+
+insert into t1 select a,a,a,1,a,a,a from t0;
+
+alter table t1 add index id1_type (id1,link_type,visibility,time,version,data);
+select * from t1 where id1 = 3;
+
+drop table t0,t1;
+
+--echo #
+--echo # Test column families
+--echo #
+
+create table t1 (
+ pk int primary key,
+ col1 int,
+ col2 int,
+ key(col1) comment 'cf3',
+ key(col2) comment 'cf4'
+) engine=rocksdb;
+
+insert into t1 values (1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
+
+--replace_column 9 #
+explain
+select * from t1 where col1=2;
+select * from t1 where col1=2;
+
+--replace_column 9 #
+explain
+select * from t1 where col2=3;
+select * from t1 where col2=3;
+
+select * from t1 where pk=4;
+
+drop table t1;
+
+--echo #
+--echo # Try primary key in a non-default CF:
+--echo #
+create table t1 (
+ pk int,
+ col1 int,
+ col2 int,
+ key(col1) comment 'cf3',
+ key(col2) comment 'cf4',
+ primary key (pk) comment 'cf5'
+) engine=rocksdb;
+insert into t1 values (1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
+
+--replace_column 9 #
+explain
+select * from t1 where col1=2;
+select * from t1 where col1=2;
+
+select * from t1 where pk=4;
+
+drop table t1;
+
+--echo #
+--echo # Issue #15: SIGSEGV from reading in blob data
+--echo #
+CREATE TABLE t1 (
+ id int not null,
+ blob_col text,
+ PRIMARY KEY (id)
+) ENGINE=ROCKSDB CHARSET=latin1;
+
+INSERT INTO t1 SET id=123, blob_col=repeat('z',64000) ON DUPLICATE KEY UPDATE blob_col=VALUES(blob_col);
+INSERT INTO t1 SET id=123, blob_col='' ON DUPLICATE KEY UPDATE blob_col=VALUES(blob_col);
+DROP TABLE t1;
+
+
+--echo #
+--echo # Issue #17: Automatic per-index column families
+--echo # (Now deprecated)
+--echo #
+--error ER_PER_INDEX_CF_DEPRECATED
+create table t1 (
+ id int not null,
+ key1 int,
+ PRIMARY KEY (id),
+ index (key1) comment '$per_index_cf'
+) engine=rocksdb;
+
+
+--echo #
+--echo # Issue #22: SELECT ... FOR UPDATE takes a long time
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (
+ id1 int,
+ id2 int,
+ value1 int,
+ value2 int,
+ primary key(id1, id2) COMMENT 'new_column_family',
+ key(id2)
+) engine=rocksdb default charset=latin1 collate=latin1_bin;
+
+insert into t1 select A.a, B.a, 31, 1234 from t0 A, t0 B;
+
+--replace_column 9 #
+explain
+select * from t1 where id1=30 and value1=30 for update;
+
+set @var1=(select variable_value
+ from information_schema.global_status
+ where variable_name='rocksdb_number_keys_read');
+
+select * from t1 where id1=3 and value1=3 for update;
+
+set @var2=(select variable_value
+ from information_schema.global_status
+ where variable_name='rocksdb_number_keys_read');
+--echo # The following must return true (before the fix, the difference was 70):
+select if((@var2 - @var1) < 30, 1, @var2-@var1);
+
+drop table t0,t1;
+
+--echo #
+--echo # Issue #33: SELECT ... FROM rocksdb_table ORDER BY primary_key uses sorting
+--echo #
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1),(2,2),(3,3);
+--echo # The following must not use 'Using filesort':
+--replace_column 9 #
+explain select * from t1 ORDER BY id;
+drop table t1;
+
+--echo #
+--echo # Issue #26: Index-only scans for DATETIME and TIMESTAMP
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+--echo # Try a DATETIME column:
+create table t1 (
+ pk int auto_increment primary key,
+ kp1 datetime,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 12:34:56', interval a day), a from t0;
+
+select * from t1;
+
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+--echo # Now, the same with NOT NULL column
+create table t2 (
+ pk int auto_increment primary key,
+ kp1 datetime not null,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+drop table t1,t2;
+
+--echo # Try a DATE column:
+create table t1 (
+ pk int auto_increment primary key,
+ kp1 date,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01', interval a day), a from t0;
+
+select * from t1;
+
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01' and '2015-01-05';
+
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01' and '2015-01-05';
+
+--echo # Now, the same with NOT NULL column
+create table t2 (
+ pk int auto_increment primary key,
+ kp1 date not null,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+drop table t1,t2;
+
+--echo #
+--echo # Try a TIMESTAMP column:
+--echo #
+create table t1 (
+ pk int auto_increment primary key,
+ kp1 timestamp,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 12:34:56', interval a day), a from t0;
+
+select * from t1;
+
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+--echo # Now, the same with NOT NULL column
+create table t2 (
+ pk int auto_increment primary key,
+ kp1 timestamp not null,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2015-01-01 00:00:00' and '2015-01-05 23:59:59';
+drop table t1,t2;
+
+--echo #
+--echo # Try a TIME column:
+--echo #
+create table t1 (
+ pk int auto_increment primary key,
+ kp1 time,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+--disable_warnings
+insert into t1 (kp1,kp2)
+select date_add('2015-01-01 09:00:00', interval a minute), a from t0;
+--enable_warnings
+
+select * from t1;
+
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+
+--echo # Now, the same with NOT NULL column
+create table t2 (
+ pk int auto_increment primary key,
+ kp1 time not null,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '09:01:00' and '09:05:00';
+drop table t1,t2;
+
+--echo #
+--echo # Try a YEAR column:
+--echo #
+create table t1 (
+ pk int auto_increment primary key,
+ kp1 year,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+--disable_warnings
+insert into t1 (kp1,kp2) select 2015+a, a from t0;
+--enable_warnings
+
+select * from t1;
+
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2016' and '2020';
+
+select kp1,kp2 from t1 force index (kp1)
+where kp1 between '2016' and '2020';
+
+--echo # Now, the same with NOT NULL column
+create table t2 (
+ pk int auto_increment primary key,
+ kp1 year not null,
+ kp2 int,
+ col1 int,
+ key(kp1, kp2)
+) engine=rocksdb;
+insert into t2 select * from t1;
+--echo # This must show 'Using index'
+--replace_column 9 #
+explain
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2016' and '2020';
+
+select kp1,kp2 from t2 force index (kp1)
+where kp1 between '2016' and '2020';
+
+drop table t1,t2;
+
+--echo #
+--echo # Issue #57: Release row locks on statement errors
+--echo #
+create table t1 (id int primary key) engine=rocksdb;
+insert into t1 values (1), (2), (3);
+begin;
+insert into t1 values (4), (5), (6);
+--error ER_DUP_ENTRY
+insert into t1 values (7), (8), (2), (9);
+select * from t1;
+
+-- connect(con1,localhost,root,,)
+--connection con1
+begin;
+--error ER_LOCK_WAIT_TIMEOUT
+select * from t1 where id=4 for update;
+
+select * from t1 where id=7 for update;
+
+select * from t1 where id=9 for update;
+
+--connection default
+-- disconnect con1
+drop table t1;
+
+--echo #Index on blob column
+SET @old_mode = @@sql_mode;
+SET sql_mode = 'strict_all_tables';
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(c, b(255))) engine=rocksdb;
+drop table t1;
+set global rocksdb_large_prefix=1;
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(b(1255))) engine=rocksdb;
+set global rocksdb_large_prefix=0;
+insert into t1 values (1, '1abcde', '1abcde'), (2, '2abcde', '2abcde'), (3, '3abcde', '3abcde');
+select * from t1;
+--replace_column 9 #
+explain select * from t1 where b like '1%';
+--replace_column 9 #
+explain select b, a from t1 where b like '1%';
+update t1 set b= '12345' where b = '2abcde';
+select * from t1;
+drop table t1;
+# In MariaDB, the error becomes a warning:
+# --error ER_TOO_LONG_KEY
+create table t1 (a int, b text, c varchar(400), Primary Key(a), Key(b(2255))) engine=rocksdb;
+drop table t1;
+SET sql_mode = @old_mode;
+
+drop table t0;
+
+--echo #
+--echo # Fix assertion failure (attempt to overrun the key buffer) for prefix indexes
+--echo #
+
+create table t1 (
+ pk int primary key,
+ col1 varchar(100),
+ key (col1(10))
+) engine=rocksdb;
+
+insert into t1 values (1, repeat('0123456789', 9));
+
+drop table t1;
+
+--echo #
+--echo # Issue #76: Assertion `buf == table->record[0]' fails in virtual int ha_rocksdb::delete_row(const uchar*)
+--echo #
+
+CREATE TABLE t1 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+CREATE TABLE t2 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+
+CREATE TRIGGER tr AFTER DELETE ON t1 FOR EACH ROW DELETE FROM t2 WHERE pk = old.pk;
+
+INSERT INTO t1 VALUES (1,1);
+REPLACE INTO t1 VALUES (1,2);
+
+SELECT * FROM t1;
+DROP TABLE t1, t2;
+
+--echo #
+--echo # Issue #99: UPDATE for table with VARCHAR pk gives "Can't find record" error
+--echo #
+create table t1(a int primary key);
+insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t2 (
+ a varchar(32) primary key,
+ col1 int
+) engine=rocksdb;
+
+insert into t2
+select concat('v-', 100 + A.a*100 + B.a), 12345 from t1 A, t1 B;
+update t2 set a=concat('x-', a) where a between 'v-1002' and 'v-1004';
+
+drop table t1,t2;
+
+--echo #
+--echo # Issue #131: Assertion `v->cfd_->internal_comparator().Compare(start, end) <= 0' failed
+--echo #
+CREATE TABLE t2(c1 INTEGER UNSIGNED NOT NULL, c2 INTEGER NULL, c3 TINYINT, c4 SMALLINT , c5 MEDIUMINT, c6 INT, c7 BIGINT, PRIMARY KEY(c1,c6)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,1,1,1,1,1,1);
+SELECT * FROM t2 WHERE c1 > 4294967295 ORDER BY c1,c6;
+EXPLAIN SELECT * FROM t2 WHERE c1 > 4294967295 ORDER BY c1,c6;
+drop table t2;
+
+--echo #
+--echo # Issue #135: register transaction was not being called for statement
+--echo #
+--disable_warnings
+DROP DATABASE IF EXISTS test_db;
+--enable_warnings
+CREATE DATABASE test_db;
+CREATE TABLE test_db.t1(c1 INT PRIMARY KEY);
+LOCK TABLES test_db.t1 READ;
+SET AUTOCOMMIT=0;
+SELECT c1 FROM test_db.t1;
+START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY;
+DROP DATABASE test_db;
+
+--echo #
+--echo # Issue #143: Split rocksdb_bulk_load option into two
+--echo #
+CREATE TABLE t1 (id int primary key, value int) engine=RocksDB;
+SET unique_checks=0;
+INSERT INTO t1 VALUES(1, 1);
+INSERT INTO t1 VALUES(1, 2);
+INSERT INTO t1 VALUES(1, 3);
+SELECT * FROM t1;
+--error ER_ON_DUPLICATE_DISABLED
+REPLACE INTO t1 VALUES(4, 4);
+--error ER_ON_DUPLICATE_DISABLED
+INSERT INTO t1 VALUES(5, 5) ON DUPLICATE KEY UPDATE value=value+1;
+TRUNCATE TABLE t1;
+SET @save_rocksdb_bulk_load_size= @@rocksdb_bulk_load_size;
+SET unique_checks=1;
+SET rocksdb_commit_in_the_middle=1;
+SET rocksdb_bulk_load_size=10;
+BEGIN;
+INSERT INTO t1 (id) VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
+ (11),(12),(13),(14),(15),(16),(17),(18),(19);
+ROLLBACK;
+SELECT * FROM t1;
+INSERT INTO t1 (id) VALUES (11),(12),(13),(14),(15);
+BEGIN;
+UPDATE t1 SET value=100;
+ROLLBACK;
+SELECT * FROM t1;
+BEGIN;
+DELETE FROM t1;
+ROLLBACK;
+SELECT * FROM t1;
+SET rocksdb_commit_in_the_middle=0;
+SET rocksdb_bulk_load_size= @save_rocksdb_bulk_load_size;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue #185 Assertion `BaseValid()' failed in void rocksdb::BaseDeltaIterator::Advance()
+--echo #
+CREATE TABLE t2(id INT NOT NULL PRIMARY KEY, data INT) Engine=MEMORY;
+INSERT INTO t2 VALUES (100,NULL),(150,"long varchar"),(200,"varchar"),(250,"long long long varchar");
+create TABLE t1 (a int not null, b int not null, primary key(a,b));
+INSERT INTO t1 VALUES (1,1);
+SELECT a FROM t1, t2 WHERE a=b AND (b NOT IN (SELECT a FROM t1 WHERE a > 4));
+DROP TABLE t1, t2;
+
+--echo #
+--echo # Issue #189 ha_rocksdb::load_auto_incr_value() creates implicit snapshot and doesn't release
+--echo #
+--connect (con1,localhost,root,,)
+create table r1 (id int auto_increment primary key, value int);
+insert into r1 (id) values (null), (null), (null), (null), (null);
+connection con1;
+create table r2 like r1;
+show create table r2;
+connection default;
+begin;
+insert into r1 values (10, 1);
+commit;
+connection con1;
+begin;
+select * from r1;
+commit;
+connection default;
+drop table r1, r2;
+
+# hidden primary key
+create table r1 (id int auto_increment, value int, index i(id));
+insert into r1 (id) values (null), (null), (null), (null), (null);
+connection con1;
+create table r2 like r1;
+show create table r2;
+connection default;
+begin;
+insert into r1 values (10, 1);
+commit;
+connection con1;
+begin;
+select * from r1;
+commit;
+connection default;
+drop table r1, r2;
+
+disconnect con1;
+
+--echo #
+--echo # Issue#211 Crash on LOCK TABLES + START TRANSACTION WITH CONSISTENT SNAPSHOT
+--echo #
+CREATE TABLE t1(c1 INT);
+lock TABLE t1 read local;
+SELECT 1 FROM t1 GROUP BY TRIM(LEADING RAND()FROM'');
+set AUTOCOMMIT=0;
+start transaction with consistent snapshot;
+SELECT * FROM t1;
+COMMIT;
+UNLOCK TABLES;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue#213 Crash on LOCK TABLES + partitions
+--echo #
+CREATE TABLE t1(a INT,b INT,KEY (b)) engine=rocksdb PARTITION BY HASH(a) PARTITIONS 2;
+INSERT INTO t1(a)VALUES (20010101101010.999949);
+lock tables t1 write,t1 as t0 write,t1 as t2 write;
+SELECT a FROM t1 ORDER BY a;
+truncate t1;
+INSERT INTO t1 VALUES(X'042000200020',X'042000200020'),(X'200400200020',X'200400200020');
+UNLOCK TABLES;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue#250: MyRocks/Innodb different output from query with order by on table with index and decimal type
+--echo # (the test was changed to use VARCHAR, because DECIMAL now supports index-only, and this issue
+--echo # needs a datype that doesn't support index-inly)
+--echo #
+
+CREATE TABLE t1(
+ c1 varchar(10) character set utf8 collate utf8_general_ci NOT NULL,
+ c2 varchar(10) character set utf8 collate utf8_general_ci,
+ c3 INT,
+ INDEX idx(c1,c2)
+);
+INSERT INTO t1 VALUES ('c1-val1','c2-val1',5);
+INSERT INTO t1 VALUES ('c1-val2','c2-val3',6);
+INSERT INTO t1 VALUES ('c1-val3','c2-val3',7);
+SELECT * FROM t1 force index(idx) WHERE c1 <> 'c1-val2' ORDER BY c1 DESC;
+--replace_column 9 #
+explain SELECT * FROM t1 force index(idx) WHERE c1 <> '1' ORDER BY c1 DESC;
+drop table t1;
+
+--echo #
+--echo # Issue#267: MyRocks issue with no matching min/max row and count(*)
+--echo #
+CREATE TABLE t1(c1 INT UNSIGNED, c2 INT SIGNED, INDEX idx2(c2));
+INSERT INTO t1 VALUES(1,null);
+INSERT INTO t1 VALUES(2,null);
+SELECT count(*) as total_rows, min(c2) as min_value FROM t1;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue#263: MyRocks auto_increment skips values if you insert a negative value
+--echo #
+# We have slightly different behavior regarding auto-increment values than
+# InnoDB, so the results of the SHOW TABLE STATUS command will be slightly
+# different. InnoDB will reserve 3 values but only use 2 of them (because
+# the user hard-coded a -1 as the second value). MyRocks will only reserve
+# the values as needed, so only 2 values will be used. This means that the
+# SHOW TABLE STATUS in InnoDB will indicate that the next auto-increment
+# value is 4 while MyRocks will show it as 3.
+CREATE TABLE t1(a INT AUTO_INCREMENT KEY);
+INSERT INTO t1 VALUES(0),(-1),(0);
+--replace_column 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+SELECT * FROM t1;
+DROP TABLE t1;
+CREATE TABLE t1(a INT AUTO_INCREMENT KEY);
+INSERT INTO t1 VALUES(0),(10),(0);
+--replace_column 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+SELECT * FROM t1;
+DROP TABLE t1;
+
+--echo #
+--echo # Issue #411: Setting rocksdb_commit_in_the_middle commits transaction
+--echo # without releasing iterator
+--echo #
+
+CREATE TABLE t1 (id1 bigint(20),
+ id2 bigint(20),
+ id3 bigint(20),
+ PRIMARY KEY (id1, id2, id3))
+ DEFAULT CHARSET=latin1;
+
+CREATE TABLE t2 (id1 bigint(20),
+ id2 bigint(20),
+ PRIMARY KEY (id1, id2))
+ DEFAULT CHARSET=latin1;
+
+
+set rocksdb_commit_in_the_middle=1;
+SET @save_rocksdb_bulk_load_size= @@rocksdb_bulk_load_size;
+set rocksdb_bulk_load_size = 100;
+
+--disable_query_log
+let $j = 10000;
+while ($j)
+{
+ --eval insert into t1 (id1, id2, id3) values (0, $j, 0);
+ --eval insert into t2 (id1, id2) values (0, $j);
+ dec $j;
+}
+--enable_query_log
+
+DELETE t2, t1 FROM t2 LEFT JOIN t1 ON t2.id2 = t1.id2 AND t2.id1 = t1.id1 WHERE t2.id1 = 0;
+
+SET rocksdb_bulk_load_size= @save_rocksdb_bulk_load_size;
+SET rocksdb_commit_in_the_middle=0;
+DROP TABLE t1, t2;
+
+--echo #
+--echo # MDEV-21831: Assertion `length == pack_length()' failed in Field_inet6::sort_string upon
+--echo # INSERT into RocksDB table
+--echo #
+
+CREATE TABLE t1 (a INET6 NOT NULL, KEY (a)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES ('41::1'),('61::1');
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.opt
new file mode 100644
index 00000000..95d819ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options="write_buffer_size=12m;target_file_size_base=1m;max_bytes_for_level_multiplier=10"
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.sh b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.sh
new file mode 100755
index 00000000..9381de1f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options-master.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+cat > $MYSQL_TMP_DIR/cf_configs.cnf <<EOL
+
+EOL
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options.test
new file mode 100644
index 00000000..9482c217
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_options.test
@@ -0,0 +1,75 @@
+--source include/have_rocksdb.inc
+
+let MYSQLD_DATADIR= `SELECT @@datadir`;
+let file= $MYSQL_TMP_DIR/cf_configs.cnf;
+
+# create new column families with no configs in cf_configs.cnf file
+
+create table t1 (a int,
+ primary key (a) comment 'cf1') engine=rocksdb;
+create table t2 (a int,
+ primary key (a) comment 'cf2') engine=rocksdb;
+create table t3 (a int,
+ primary key (a) comment 'z') engine=rocksdb;
+
+insert into t1 values (1);
+insert into t2 values (2);
+insert into t3 values (2);
+
+# restart with new column families in DB
+
+--source include/restart_mysqld.inc
+
+# check column family options in log -- should be all default settings
+
+--echo
+--echo Default options for all column families:
+--echo
+select cf_name, option_type, value
+ from information_schema.rocksdb_cf_options
+ where option_type in ('WRITE_BUFFER_SIZE',
+ 'TARGET_FILE_SIZE_BASE',
+ 'MAX_BYTES_FOR_LEVEL_MULTIPLIER')
+ order by cf_name, option_type;
+# restart with cf configs for cf1 and cf2
+
+--let $restart_parameters=--rocksdb_override_cf_options=cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};z={target_file_size_base=4m};
+--source include/restart_mysqld.inc
+
+# check column family options in log -- should reflect individual settings
+
+--echo
+--echo Individualized options for column families:
+--echo
+select cf_name, option_type, value
+ from information_schema.rocksdb_cf_options
+ where option_type in ('WRITE_BUFFER_SIZE',
+ 'TARGET_FILE_SIZE_BASE',
+ 'MAX_BYTES_FOR_LEVEL_MULTIPLIER')
+ order by cf_name, option_type;
+
+# syntax error in options (no equal sign)
+
+--exec echo "restart:--rocksdb_override_cf_options=cf1" > $_expect_file_name
+--error 1
+--source include/wait_until_connected_again.inc
+
+# invalid cf option config (no curly braces)
+
+--exec echo "restart:--rocksdb_override_cf_options=cf1=write_buffer_size=8m" > $_expect_file_name
+--error 1
+--source include/wait_until_connected_again.inc
+
+# invalid cf option config (cf listed twice)
+
+--exec echo "restart:--rocksdb_override_cf_options=cf1={write_buffer_size=8m};cf1={target_file_size_base=2m}" > $_expect_file_name
+--error 1
+--source include/wait_until_connected_again.inc
+
+# clean up
+
+--source include/restart_mysqld.inc
+
+--echo
+drop table t1,t2,t3;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_per_partition.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_per_partition.test
new file mode 100644
index 00000000..fcbd8527
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_per_partition.test
@@ -0,0 +1,513 @@
+--source include/have_rocksdb.inc
+
+--source include/have_partition.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+--enable_warnings
+
+#
+# Create a table with multiple partitions, but in the comment don't specify
+# that per-partition based column families (CF) should be created. Expect that
+# default CF will be used and new one won't be created.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'testcomment'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+# Expecting no results here.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='testcomment';
+
+DROP TABLE t1;
+
+#
+# Same test case as above, only with the reverse CF. Should result in the same
+# behavior. No new CF-s created, only default one will be used.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'rev:testrevcomment'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+# Expecting no results here.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='rev:testrevcomment';
+
+DROP TABLE t1;
+
+#
+# Create a table with multiple partitions and request for separate CF to be
+# created per every partition. As a result we expect three different CF-s to be
+# created.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'baz';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='foo';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_custom_cf';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='baz';
+
+DROP TABLE t1;
+
+#
+# Same test case as above, only one of the partitions has "rev:" prefix. The
+# intent here is to make sure that qualifier can specify reverse CF as well.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=t1-p0;custom_p1_cfname=rev:bar;custom_p2_cfname=t1-p2'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set @@global.rocksdb_compact_cf = 't1-p0';
+set @@global.rocksdb_compact_cf = 'rev:bar';
+set @@global.rocksdb_compact_cf = 't1-p2';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='t1-p0';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='rev:bar';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='t1-p2';
+
+DROP TABLE t1;
+
+
+#
+# Create a table with multiple partitions and assign two partitions to the same
+# CF, third one gets a separate partition, and fourth one will belong to a
+# default one. As a result we expect two new CF-s to be created.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=cf-zero;custom_p1_cfname=cf-one;custom_p2_cfname=cf-zero'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9),
+ PARTITION custom_p3 VALUES IN (10, 20, 30)
+);
+
+set @@global.rocksdb_compact_cf = 'cf-zero';
+set @@global.rocksdb_compact_cf = 'cf-one';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='cf-zero';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='cf-one';
+
+DROP TABLE t1;
+
+#
+# Create a table with CF-s per partition and verify that ALTER TABLE + DROP
+# INDEX work for that scenario and data is persisted.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+
+SELECT * FROM t1;
+ALTER TABLE t1 DROP PRIMARY KEY;
+SELECT * FROM t1;
+
+#
+# Verify that we can compact custom CF-s.
+#
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'bar';
+set @@global.rocksdb_compact_cf = 'baz';
+
+DROP TABLE t1;
+
+#
+# Create a table with CF-s per partition and verify that ALTER TABLE + DROP
+# INDEX + ADD INDEX work for that scenario and data is persisted and new cf_name_str
+# are created.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+
+ALTER TABLE t1 DROP PRIMARY KEY;
+ALTER TABLE t1 ADD PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=p0_cf;custom_p1_cfname=p1_cf';
+
+set @@global.rocksdb_compact_cf = 'p0_cf';
+set @@global.rocksdb_compact_cf = 'p1_cf';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p0_cf';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p1_cf';
+
+DROP TABLE t1;
+
+#
+# Create a table CF-s per partition, use ALTER TABLE to change the way it's
+# partitioned and verify that new CF-s will be created.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+
+ALTER TABLE t1 PARTITION BY LIST(c1) (
+ PARTITION custom_p3 VALUES IN (1, 4, 7),
+ PARTITION custom_p4 VALUES IN (2, 5, 8, 3, 6, 9)
+);
+
+ALTER TABLE t1 DROP PRIMARY KEY;
+ALTER TABLE t1 ADD PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p3_cfname=p3_cf;custom_p4_cfname=p4_cf';
+
+set @@global.rocksdb_compact_cf = 'p3_cf';
+set @@global.rocksdb_compact_cf = 'p4_cf';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p3_cf';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='p4_cf';
+
+DROP TABLE t1;
+
+#
+# Create a table CF-s per partition, use empty qualifier name. Verify that no
+# new CF-s are created. This will also make sure that nothing gets added for
+# `custom_p2`.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=;'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+DROP TABLE t1;
+
+#
+# Verify some basic partition related operations when using PARTITION BY LIST
+# COLUMNS on a VARBINARY column on a table with more complicated schema.
+#
+
+#
+# Verify that creating the table without COMMENT actually works.
+#
+CREATE TABLE `t2` (
+ `col1` bigint(20) NOT NULL,
+ `col2` varbinary(64) NOT NULL,
+ `col3` varbinary(256) NOT NULL,
+ `col4` bigint(20) NOT NULL,
+ `col5` mediumblob NOT NULL,
+ PRIMARY KEY (`col1`,`col2`,`col3`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY LIST COLUMNS (`col2`) (
+ PARTITION custom_p0 VALUES IN (0x12345),
+ PARTITION custom_p1 VALUES IN (0x23456),
+ PARTITION custom_p2 VALUES IN (0x34567),
+ PARTITION custom_p3 VALUES IN (0x45678),
+ PARTITION custom_p4 VALUES IN (0x56789),
+ PARTITION custom_p5 VALUES IN (0x6789A),
+ PARTITION custom_p6 VALUES IN (0x789AB),
+ PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+
+DROP TABLE t2;
+
+#
+# Create the same table with two custom CF-s per partition as specified in the
+# COMMENT.
+#
+CREATE TABLE `t2` (
+ `col1` bigint(20) NOT NULL,
+ `col2` varbinary(64) NOT NULL,
+ `col3` varbinary(256) NOT NULL,
+ `col4` bigint(20) NOT NULL,
+ `col5` mediumblob NOT NULL,
+ PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=my_cf0;custom_p1_cfname=my_cf1'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY LIST COLUMNS (`col2`) (
+ PARTITION custom_p0 VALUES IN (0x12345),
+ PARTITION custom_p1 VALUES IN (0x23456),
+ PARTITION custom_p2 VALUES IN (0x34567),
+ PARTITION custom_p3 VALUES IN (0x45678),
+ PARTITION custom_p4 VALUES IN (0x56789),
+ PARTITION custom_p5 VALUES IN (0x6789A),
+ PARTITION custom_p6 VALUES IN (0x789AB),
+ PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+
+# Verify that CF-s were created earlier.
+set @@global.rocksdb_compact_cf = 'my_cf0';
+set @@global.rocksdb_compact_cf = 'my_cf1';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_cf0';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='my_cf1';
+
+# Insert some random data.
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+
+# Verify it's there.
+SELECT col1, HEX(col2), HEX(col3), col4, HEX(col5) FROM t2;
+
+# Verify it's being fetched from the right partition. This tests partitioning
+# functionality, but we want to make sure that by adding CF-s per partition we
+# don't regress anything.
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x12345;
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x23456;
+
+# Delete the current PK and create a new one referencing different CF-s. We
+# need to verity that new CF-s will be created and no data will be lost in
+# process.
+ALTER TABLE t2 DROP PRIMARY KEY;
+ALTER TABLE t2 ADD PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=new_cf0;custom_p1_cfname=new_cf1';
+
+# Verify that new CF-s are created as well.
+set @@global.rocksdb_compact_cf = 'new_cf0';
+set @@global.rocksdb_compact_cf = 'new_cf1';
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='new_cf0';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='new_cf1';
+
+# Insert some more random data.
+INSERT INTO t2 VALUES (500, 0x12345, 0x5, 1, 0x2);
+INSERT INTO t2 VALUES (700, 0x23456, 0x7, 1, 0x3);
+
+# Verify that partition mappings are still intact.
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x12345;
+EXPLAIN PARTITIONS SELECT HEX(col2) FROM t2 where col2 = 0x23456;
+
+# Verify that no data is lost.
+SELECT col1, HEX(col2), HEX(col3), col4, HEX(col5) FROM t2;
+
+DROP TABLE t2;
+
+#
+# Create the same table with two custom CF-s per partition as specified in the
+# COMMENT. Use both the PK and SK when creating the table.
+#
+CREATE TABLE `t2` (
+ `col1` bigint(20) NOT NULL,
+ `col2` varbinary(64) NOT NULL,
+ `col3` varbinary(256) NOT NULL,
+ `col4` bigint(20) NOT NULL,
+ `col5` mediumblob NOT NULL,
+ PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=test_cf0;custom_p1_cfname=test_cf1',
+ KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=test_cf5'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY LIST COLUMNS (`col2`) (
+ PARTITION custom_p0 VALUES IN (0x12345),
+ PARTITION custom_p1 VALUES IN (0x23456),
+ PARTITION custom_p2 VALUES IN (0x34567),
+ PARTITION custom_p3 VALUES IN (0x45678),
+ PARTITION custom_p4 VALUES IN (0x56789),
+ PARTITION custom_p5 VALUES IN (0x6789A),
+ PARTITION custom_p6 VALUES IN (0x789AB),
+ PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+
+# Verify that CF-s were created for PK.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf0';
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf1';
+
+# Verify that CF-s were created for SK.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='test_cf5';
+
+# Insert some random data.
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (500, 0x6789A, 0x5, 1, 0x7);
+
+# Basic verification that correct partition and key are used when searching.
+EXPLAIN PARTITIONS SELECT * FROM t2 WHERE col2 = 0x6789A AND col4 = 1;
+
+# Remove the key.
+ALTER TABLE t2 DROP KEY `col2`;
+
+# Add a new key and expect new CF to be created as well.
+ALTER TABLE t2 ADD KEY (`col3`, `col4`) COMMENT 'custom_p5_cfname=another_cf_for_p5';
+
+# Verify that CF-s were created for SK.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='another_cf_for_p5';
+
+# Verify that correct partition and key are used when searching.
+ANALYZE TABLE t2;
+--replace_column 10 #
+EXPLAIN PARTITIONS SELECT * FROM t2 WHERE col3 = 0x4 AND col2 = 0x34567;
+
+DROP TABLE t2;
+
+#
+# Verify the same scenario as before, but with a UNIQUE KEY in addition to PK.
+#
+CREATE TABLE `t2` (
+ `col1` bigint(20) NOT NULL,
+ `col2` varbinary(64) NOT NULL,
+ `col3` varbinary(256) NOT NULL,
+ `col4` bigint(20) NOT NULL,
+ `col5` mediumblob NOT NULL,
+ PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=test_cf0;custom_p1_cfname=test_cf1',
+ UNIQUE KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=unique_test_cf5'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY LIST COLUMNS (`col2`) (
+ PARTITION custom_p0 VALUES IN (0x12345),
+ PARTITION custom_p1 VALUES IN (0x23456),
+ PARTITION custom_p2 VALUES IN (0x34567),
+ PARTITION custom_p3 VALUES IN (0x45678),
+ PARTITION custom_p4 VALUES IN (0x56789),
+ PARTITION custom_p5 VALUES IN (0x6789A),
+ PARTITION custom_p6 VALUES IN (0x789AB),
+ PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+
+# Verify that CF-s were created for SK.
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='unique_test_cf5';
+
+INSERT INTO t2 VALUES (100, 0x12345, 0x1, 1, 0x2);
+
+--error ER_DUP_ENTRY
+INSERT INTO t2 VALUES (200, 0x12345, 0x1, 1, 0x2);
+
+--error ER_DUP_ENTRY
+INSERT INTO t2 VALUES (300, 0x12345, 0x1, 1, 0x2);
+
+INSERT INTO t2 VALUES (100, 0x23456, 0x2, 1, 0x3);
+INSERT INTO t2 VALUES (100, 0x34567, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (400, 0x89ABC, 0x4, 1, 0x5);
+INSERT INTO t2 VALUES (500, 0x6789A, 0x5, 1, 0x7);
+
+DROP TABLE t2;
+
+#
+# Verify that both partitioned and non-partitioned table can share a CF.
+#
+CREATE TABLE t1 (
+ `a` int,
+ PRIMARY KEY (a) COMMENT "sharedcf"
+) ENGINE=ROCKSDB;
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='sharedcf';
+
+#
+# We expect this to succeed.
+#
+CREATE TABLE t2 (
+ `a` INT,
+ `b` DATE,
+ `c` VARCHAR(42),
+ PRIMARY KEY (`a`) COMMENT "custom_p0_cfname=sharedcf;custom_p2_cfname=notsharedcf"
+) ENGINE=ROCKSDB
+ PARTITION BY LIST(`a`) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+SELECT DISTINCT(cf_name) FROM information_schema.rocksdb_cfstats WHERE cf_name='notsharedcf';
+
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+
+
+# Test that truncating table actually removes rows.
+CREATE TABLE t1 (
+ a INT NOT NULL,
+ PRIMARY KEY (a) COMMENT 'p1_cfname=foo;'
+) ENGINE=ROCKSDB
+PARTITION BY LIST COLUMNS(a)
+(PARTITION p1 VALUES IN (1) ENGINE = ROCKSDB);
+
+INSERT INTO t1 values (1);
+TRUNCATE TABLE t1;
+SELECT * FROM t1;
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse.test
new file mode 100644
index 00000000..8e30332b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_cf_reverse.test
@@ -0,0 +1,71 @@
+--source include/have_rocksdb.inc
+
+#
+# RocksDB-SE tests for reverse-ordered Column Families
+#
+
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (
+ pk int primary key,
+ a int not null,
+ b int not null,
+ key(a) comment 'rev:foo',
+ key(b) comment 'bar'
+) engine=rocksdb;
+
+insert into t1 select a,a,a from t0;
+insert into t1 select a+10,a+10,a+10 from t0;
+
+--echo # Primary key is not in a reverse-ordered CF, so full table scan
+--echo # returns rows in ascending order:
+select * from t1;
+
+--replace_column 9 #
+explain
+select a from t1 order by a limit 5;
+select a from t1 order by a limit 5;
+
+--replace_column 9 #
+explain
+select b from t1 order by b limit 5;
+select a from t1 order by a limit 5;
+
+--replace_column 9 #
+explain
+select a from t1 order by a desc limit 5;
+select a from t1 order by a desc limit 5;
+
+--replace_column 9 #
+explain
+select b from t1 order by b desc limit 5;
+select b from t1 order by b desc limit 5;
+
+drop table t1;
+
+--echo #
+--echo # Try a primary key in a reverse-ordered CF.
+--echo #
+
+create table t2 (
+ pk int,
+ a int not null,
+ primary key(pk) comment 'rev:cf1'
+) engine=rocksdb;
+
+insert into t2 select a,a from t0;
+--echo # Primary key is in a reverse-ordered CF, so full table scan
+--echo # returns rows in descending order:
+select * from t2;
+
+set autocommit=0;
+begin;
+delete from t2 where a=3 or a=7;
+select * from t2;
+rollback;
+set autocommit=1;
+
+drop table t2;
+drop table t0;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums-master.opt
new file mode 100644
index 00000000..320c48e5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_debug_optimizer_n_rows=1000
+--rocksdb_records_in_range=50
+--log_warnings=3
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums.test
new file mode 100644
index 00000000..42a4c83f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_checksums.test
@@ -0,0 +1,152 @@
+--source include/have_rocksdb.inc
+
+#
+# Tests for row checksums feature
+#
+--source include/have_debug.inc
+
+--let LOG=$MYSQLTEST_VARDIR/tmp/rocksdb_checksum.err
+--let $_mysqld_option=--log-error=$LOG
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+
+set @save_rocksdb_store_row_debug_checksums=@@global.rocksdb_store_row_debug_checksums;
+set @save_rocksdb_verify_row_debug_checksums=@@global.rocksdb_verify_row_debug_checksums;
+set @save_rocksdb_checksums_pct=@@global.rocksdb_checksums_pct;
+
+show variables like 'rocksdb_%checksum%';
+
+create table t1 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3);
+check table t1;
+--let SEARCH_FILE=$LOG
+--let SEARCH_PATTERN=0 table records had checksums
+--let SEARCH_PATTERN=CHECKTABLE t1[^\n]*
+--let SEARCH_OUTPUT=matches
+--source include/search_pattern_in_file.inc
+
+drop table t1;
+
+set session rocksdb_store_row_debug_checksums=on;
+create table t2 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t2 values (1,1,1),(2,2,2),(3,3,3);
+check table t2;
+--let SEARCH_PATTERN=CHECKTABLE t2[^\n]*
+--source include/search_pattern_in_file.inc
+
+--echo # Now, make a table that has both rows with checksums and without
+create table t3 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+insert into t3 values (1,1,1),(2,2,2),(3,3,3);
+set session rocksdb_store_row_debug_checksums=off;
+update t3 set b=3 where a=2;
+set session rocksdb_store_row_debug_checksums=on;
+check table t3;
+--let SEARCH_PATTERN=CHECKTABLE t3[^\n]*
+--source include/search_pattern_in_file.inc
+
+set session rocksdb_store_row_debug_checksums=on;
+set session rocksdb_checksums_pct=5;
+create table t4 (pk int primary key, a int, b int, key(a), key(b)) engine=rocksdb;
+--disable_query_log
+let $i=0;
+let $x= 100000;
+while ($i<4000)
+{
+ inc $i;
+ eval insert t4(pk,a,b) values($i, $i, $i div 10);
+ eval update t4 set a= a+$x where a=$i;
+ eval update t4 set pk=pk+$x where pk=$i;
+}
+--enable_query_log
+check table t4;
+perl;
+$total=4000;
+$pct=5;
+@out=();
+
+$filename= "$ENV{LOG}";
+print $filename "\n";
+open(F, '<', $filename) || die("Can't open file $filename: $!");
+while(<F>) {
+ @out=() if /^CURRENT_TEST:/;
+ if (/(\d+) index entries checked \((\d+) had checksums/) {
+ if ($1 == $total and $2 >= $total*($pct-2)/100 and $2 <= $total*($pct+2)/100) {
+ push @out, sprintf "%d index entries had around %d checksums\n", $total, $total*$pct/100;
+ }
+ } elsif (/(\d+) table records had checksums/) {
+ if ($1 >= $total*($pct-2)/100 and $1 <= $total*($pct+2)/100) {
+ push @out, sprintf "Around %d table records had checksums\n", $total*$pct/100;
+ }
+ }
+}
+print @out;
+EOF
+set session rocksdb_checksums_pct=100;
+
+--echo #
+--echo # Ok, table t2 has all rows with checksums. Simulate a few checksum mismatches.
+--echo #
+insert into mtr.test_suppressions values
+ ('Checksum mismatch in key of key-value pair for index'),
+ ('Checksum mismatch in value of key-value pair for index'),
+ ('Data with incorrect checksum');
+
+--echo # 1. Start with mismatch in key checksum of the PK.
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_checksum1";
+set session rocksdb_verify_row_debug_checksums=off;
+select * from t3;
+set session rocksdb_verify_row_debug_checksums=on;
+--error ER_INTERNAL_ERROR
+select * from t3;
+--error ER_INTERNAL_ERROR
+select * from t4;
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_checksum1";
+
+--echo # 2. Continue with mismatch in pk value checksum.
+set session debug_dbug= "+d,myrocks_simulate_bad_pk_checksum2";
+set session rocksdb_verify_row_debug_checksums=off;
+select * from t3;
+set session rocksdb_verify_row_debug_checksums=on;
+--error ER_INTERNAL_ERROR
+select * from t3;
+--error ER_INTERNAL_ERROR
+select * from t4;
+set session debug_dbug= "-d,myrocks_simulate_bad_pk_checksum2";
+
+--echo # 3. Check if we catch checksum mismatches for secondary indexes
+--replace_column 9 #
+explain
+select * from t3 force index(a) where a<4;
+select * from t3 force index(a) where a<4;
+
+set session debug_dbug= "+d,myrocks_simulate_bad_key_checksum1";
+--error ER_INTERNAL_ERROR
+select * from t3 force index(a) where a<4;
+--error ER_INTERNAL_ERROR
+select * from t4 force index(a) where a<1000000;
+set session debug_dbug= "-d,myrocks_simulate_bad_key_checksum1";
+
+--echo # 4. The same for index-only reads?
+--disable_query_log
+set global rocksdb_force_flush_memtable_now=1;
+--enable_query_log
+--replace_column 9 #
+explain
+select a from t3 force index(a) where a<4;
+select a from t3 force index(a) where a<4;
+
+set session debug_dbug= "+d,myrocks_simulate_bad_key_checksum1";
+--error ER_INTERNAL_ERROR
+select a from t3 force index(a) where a<4;
+--error ER_INTERNAL_ERROR
+select a from t4 force index(a) where a<1000000;
+set session debug_dbug= "-d,myrocks_simulate_bad_key_checksum1";
+
+set @@global.rocksdb_store_row_debug_checksums=@save_rocksdb_store_row_debug_checksums;
+set @@global.rocksdb_verify_row_debug_checksums=@save_rocksdb_verify_row_debug_checksums;
+set @@global.rocksdb_checksums_pct=@save_rocksdb_checksums_pct;
+
+--source include/restart_mysqld.inc
+#--remove_file $LOG
+
+drop table t2,t3,t4;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.inc
new file mode 100644
index 00000000..5336c77e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.inc
@@ -0,0 +1,109 @@
+# MariaDB: including the below too many times causes really long argv list
+# in win_main()'s argument which blows up some limit on Windows.
+# Comment it out:
+#--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+--source include/count_sessions.inc
+
+# Usage:
+#
+# let $order = ASC; # or DESC
+# let $comment = "rev:cf2"; # or ""
+# --source suite/rocksdb/t/rocksdb_concurrent_delete.inc
+
+let $first_row = -1; # Error this should never happen
+if ($order == 'ASC')
+{
+ let $first_row = 1;
+ let $middle_row = 3;
+ let $end_row = 5;
+}
+if ($order == 'DESC')
+{
+ let $first_row = 5;
+ let $middle_row = 3;
+ let $end_row = 1;
+}
+
+connect (con, localhost, root,,);
+connection default;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+
+SET debug_sync='RESET';
+
+eval CREATE TABLE t1 (pk INT PRIMARY KEY COMMENT $comment, a INT);
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+
+# This will cause the SELECT to block after finding the first row, but
+# before locking and reading it.
+--echo --PK first row delete
+connection con;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+send_eval SELECT * FROM t1 order by t1.pk $order FOR UPDATE;
+
+# While that connection is waiting, delete the first row (the one con
+# is about to lock and read
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = $first_row;
+
+# Signal the waiting select to continue
+SET debug_sync='now SIGNAL go';
+
+# Now get the results from the select. The first entry (1,1) (or (3,3) when
+# using reverse ordering) should be missing. Prior to the fix the SELECT
+# would have returned: "1815: Internal error: NotFound:"
+connection con;
+reap;
+
+# Deleting a middle row
+--echo --PK middle row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+send_eval SELECT * FROM t1 order by t1.pk $order FOR UPDATE;
+
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = $middle_row;
+SET debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+
+# Deleting the end row
+--echo --PK end row delete
+SET debug_sync='rocksdb_concurrent_delete SIGNAL parked WAIT_FOR go';
+send_eval SELECT * FROM t1 order by t1.pk $order FOR UPDATE;
+
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = $end_row;
+SET debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+
+
+# Cleanup
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.test
new file mode 100644
index 00000000..47818bfd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete.test
@@ -0,0 +1,38 @@
+# rocksdb_concurrent_delete test case covers snapshot conflicts, and
+# verifying locking reads not stopping scanning when hitting row not found.
+# The following code coverages are covered.
+#
+# 1. PK full scan (key=NULL)
+# first row, and other rows
+# ha_rnd_next -> rnd_next -> rnd_next_with_direction
+#
+# 2. PK range scan (key=PRIMARY)
+# first row
+# read_range_first -> index_read_map_impl -> read_row_from_primary_key
+# next row
+# index_next -> index_next_with_direction -> rnd_next_with_direction
+#
+# 3. SK full scan
+# first row
+# index_first -> index_first_intern -> index_next_with_direction -> secondary_index_read
+# next row
+# index_next -> index_next_with_direction -> secondary_index_read
+#
+# 4. SK range scan
+# first row
+# read_range_first -> index_read_map_impl -> read_row_from_secondary_key
+# next row
+# index_next -> index_next_with_direction -> secondary_index_read
+#
+# In all cases, RR gets snapshot conflict errors if non-first rows get
+# deleted by another transaction after scanning.
+
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+let $isolation_level = REPEATABLE READ;
+--source rocksdb_concurrent_delete_main.inc
+
+let $isolation_level = READ COMMITTED;
+--source rocksdb_concurrent_delete_main.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_main.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_main.inc
new file mode 100644
index 00000000..bcd86af9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_main.inc
@@ -0,0 +1,30 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+# This validates the fix for Issue #144. The problem was that with more
+# than one client accessing/deleting the same row there was a possibility
+# of client A finding a row (through Next() or Prev()) but the row being
+# deleted before the GetForUpdate() call could occur. When this happened
+# a nearly useless error was being returned.
+
+let $order=ASC;
+let $comment="";
+--source rocksdb_concurrent_delete.inc
+--source rocksdb_concurrent_delete_sk.inc
+
+let $order=DESC;
+let $comment="";
+--source rocksdb_concurrent_delete.inc
+
+let $order=ASC;
+let $comment="rev:cf2";
+--source rocksdb_concurrent_delete.inc
+
+let $order=DESC;
+let $comment="rev:cf2";
+--source rocksdb_concurrent_delete.inc
+
+let $index=PRIMARY;
+--source rocksdb_concurrent_delete_range.inc
+let $index=sk;
+--source rocksdb_concurrent_delete_range.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_range.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_range.inc
new file mode 100644
index 00000000..a8552714
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_range.inc
@@ -0,0 +1,85 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+--source include/count_sessions.inc
+
+# This is a test case to reproduce https://github.com/facebook/mysql-5.6/issues/162
+# Expected output of the last select for update was (1,2,100) and (1,3,100), but
+# currently it returns (1,2,1) and (1,3,1), which must be fixed.
+
+connect (con, localhost, root,,);
+connection default;
+
+set debug_sync='RESET';
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+create table t1 (id1 int, id2 int, value int, primary key (id1, id2), index sk (id1, value)) engine=rocksdb;
+insert into t1 values (1, 1, 1),(1, 2, 1),(1, 3, 1),(1, 4, 1),(1, 5, 1),(2, 2, 2);
+
+# deleting a first row
+--echo --First row delete with $index
+connection con;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+send_eval update t1 force index ($index) set value=100 where id1=1;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=1;
+set debug_sync='now SIGNAL go';
+
+connection con;
+reap;
+select * from t1 where id1=1;
+
+# deleting a middle row
+--echo --Middle row delete with $index
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+send_eval update t1 force index ($index) set value=200 where id1=1;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=3;
+set debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+select * from t1 where id1=1;
+
+# deleting the end row
+--echo --End row delete with $index
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+set debug_sync='rocksdb.get_row_by_rowid SIGNAL parked WAIT_FOR go';
+send_eval update t1 force index ($index) set value=300 where id1=1;
+
+connection default;
+set debug_sync='now WAIT_FOR parked';
+delete from t1 where id1=1 and id2=5;
+set debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+select * from t1 where id1=1;
+
+# Cleanup
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_sk.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_sk.inc
new file mode 100644
index 00000000..ac0b5d76
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_delete_sk.inc
@@ -0,0 +1,82 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+--source include/count_sessions.inc
+
+connect (con, localhost, root,,);
+connection default;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+
+SET debug_sync='RESET';
+
+eval CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, index a(a));
+INSERT INTO t1 VALUES(1,1), (2,2), (3,3), (4,4), (5,5);
+
+# This will cause the SELECT to block after finding the first row, but
+# before locking and reading it.
+--echo --SK first row delete
+connection con;
+eval SET SESSION TRANSACTION ISOLATION LEVEL $isolation_level;
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+send_eval SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+
+# While that connection is waiting, delete the first row (the one con
+# is about to lock and read
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = 1;
+
+# Signal the waiting select to continue
+SET debug_sync='now SIGNAL go';
+
+connection con;
+reap;
+
+# Deleting a middle row
+--echo --SK middle row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+send_eval SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = 3;
+SET debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+
+# Deleting the end row
+--echo --SK end row delete
+SET debug_sync='rocksdb_concurrent_delete_sk SIGNAL parked WAIT_FOR go';
+send_eval SELECT a FROM t1 FORCE INDEX(a) FOR UPDATE;
+
+connection default;
+SET debug_sync='now WAIT_FOR parked';
+eval DELETE FROM t1 WHERE pk = 5;
+SET debug_sync='now SIGNAL go';
+
+connection con;
+if ($isolation_level == "REPEATABLE READ")
+{
+ --error ER_LOCK_DEADLOCK
+ reap;
+}
+if ($isolation_level == "READ COMMITTED")
+{
+ reap;
+}
+
+# Cleanup
+connection default;
+disconnect con;
+set debug_sync='RESET';
+drop table t1;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_insert.py b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_insert.py
new file mode 100644
index 00000000..37b118d5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_concurrent_insert.py
@@ -0,0 +1,95 @@
+"""
+This script tests concurrent inserts on a given table.
+Example Usage (in Mysql Test Framework):
+
+ CREATE TABLE t1 (a INT) ENGINE=rocksdb;
+
+ let $exec = python suite/rocksdb/t/rocksdb_concurrent_insert.py \
+ root 127.0.0.1 $MASTER_MYPORT test t1 100 4;
+ exec $exec;
+
+"""
+import cStringIO
+import hashlib
+import MySQLdb
+import os
+import random
+import signal
+import sys
+import threading
+import time
+import string
+
+def get_insert(table_name, idx):
+ return """INSERT INTO %s (a) VALUES (%d)""" % (table_name, idx)
+
+class Inserter(threading.Thread):
+ Instance = None
+ def __init__(self, con, table_name, num_inserts):
+ threading.Thread.__init__(self)
+ self.finished = False
+ self.num_inserts = num_inserts
+ con.autocommit(False)
+ self.con = con
+ self.rand = random.Random()
+ self.exception = None
+ self.table_name = table_name
+ Inserter.Instance = self
+ self.start()
+ def run(self):
+ try:
+ self.runme()
+ except Exception, e:
+ self.exception = traceback.format_exc()
+ print "caught (%s)" % e
+ finally:
+ self.finish()
+ def runme(self):
+ cur = self.con.cursor()
+ for i in xrange(self.num_inserts):
+ try:
+ cur.execute(get_insert(self.table_name, i))
+ r = self.rand.randint(1,10)
+ if r < 4:
+ self.con.commit()
+ except:
+ cur = self.con.cursor()
+ try:
+ self.con.commit()
+ except Exception, e:
+ self.exception = traceback.format_exc()
+ print "caught (%s)" % e
+ pass
+ def finish(self):
+ self.finished = True
+
+if __name__ == '__main__':
+ if len(sys.argv) != 8:
+ print "Usage: rocksdb_concurrent_insert.py user host port db_name " \
+ "table_name num_inserts num_threads"
+ sys.exit(1)
+
+ user = sys.argv[1]
+ host = sys.argv[2]
+ port = int(sys.argv[3])
+ db = sys.argv[4]
+ table_name = sys.argv[5]
+ num_inserts = int(sys.argv[6])
+ num_workers = int(sys.argv[7])
+
+ worker_failed = False
+ workers = []
+ for i in xrange(num_workers):
+ inserter = Inserter(
+ MySQLdb.connect(user=user, host=host, port=port, db=db), table_name,
+ num_inserts)
+ workers.append(inserter)
+
+ for w in workers:
+ w.join()
+ if w.exception:
+ print "Worker hit an exception:\n%s\n" % w.exception
+ worker_failed = True
+
+ if worker_failed:
+ sys.exit(1)
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_datadir.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_datadir.test
new file mode 100644
index 00000000..4399dd1a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_datadir.test
@@ -0,0 +1,33 @@
+--source include/have_rocksdb.inc
+let $ddir = $MYSQL_TMP_DIR/.rocksdb_datadir.test.install.db;
+let $rdb_ddir = $MYSQL_TMP_DIR/.rocksdb_datadir.test;
+let $sql_file = $MYSQL_TMP_DIR/rocksdb_datadir.sql;
+
+--write_file $sql_file
+CREATE DATABASE mysqltest;
+USE mysqltest;
+CREATE TABLE t1 (a INT PRIMARY KEY);
+INSERT INTO t1 VALUES(42);
+SET GLOBAL rocksdb_force_flush_memtable_now = 1;
+SELECT sleep(1);
+DROP TABLE t1;
+DROP DATABASE mysqltest;
+EOF
+
+# Must ensure this directory exists before launching mysqld
+mkdir $ddir;
+
+let $plugin_dir=`select @@plugin_dir`;
+# Launch mysqld with non-standard rocksdb_datadir
+exec $MYSQLD_BOOTSTRAP_CMD --plugin-dir=$plugin_dir --plugin-load=$HA_ROCKSDB_SO --datadir=$ddir --rocksdb_datadir=$rdb_ddir --default-storage-engine=rocksdb --skip-innodb --default-tmp-storage-engine=MyISAM --rocksdb < $sql_file;
+
+--echo Check for MANIFEST files
+--list_files $rdb_ddir MANIFEST-0000*
+
+# Clean up
+remove_files_wildcard $ddir *;
+remove_files_wildcard $ddir *;
+remove_files_wildcard $rdb_ddir *;
+rmdir $ddir;
+rmdir $rdb_ddir;
+remove_file $sql_file;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect.inc
new file mode 100644
index 00000000..082c6130
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect.inc
@@ -0,0 +1,123 @@
+#
+# Some basic sanity tests for deadlock detection.
+#
+--source include/have_rocksdb.inc
+
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+
+create table t (i int primary key);
+create table r1 (id int primary key, value int);
+insert into r1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+create table r2 like r1;
+insert into r2 select * from r1;
+
+# deadlock on scanned locking reads
+connect (con1,localhost,root,,);
+let $con1= `SELECT CONNECTION_ID()`;
+begin;
+update r2 set value=100 where id=9;
+
+connect (con2,localhost,root,,);
+let $con2= `SELECT CONNECTION_ID()`;
+begin;
+update r1 set value=100 where id=8;
+--send select * from r2 for update;
+
+connection con1;
+let $wait_condition =
+`SELECT CONCAT('select count(*) = 1 from information_schema.rocksdb_trx where THREAD_ID = ', '$con2', ' and WAITING_KEY != ""')`;
+--source include/wait_condition.inc
+--error ER_LOCK_DEADLOCK
+select * from r1 for update;
+rollback;
+
+connection con2;
+--reap;
+rollback;
+
+connection con1;
+begin;
+insert into t values (1);
+
+connection con2;
+begin;
+insert into t values (2);
+
+connect (con3,localhost,root,,);
+begin;
+insert into t values (3);
+
+connection con1;
+--send select * from t where i = 2 for update
+
+connection con2;
+let $wait_condition =
+`SELECT CONCAT('select count(*) = 1 from information_schema.rocksdb_trx where THREAD_ID = ', '$con1', ' and WAITING_KEY != ""')`;
+--source include/wait_condition.inc
+
+--send select * from t where i = 3 for update
+
+connection con3;
+let $wait_condition =
+`SELECT CONCAT('select count(*) = 1 from information_schema.rocksdb_trx where THREAD_ID = ', '$con2', ' and WAITING_KEY != ""')`;
+--source include/wait_condition.inc
+
+select * from t;
+--error ER_LOCK_DEADLOCK
+insert into t values (4), (1);
+--echo # Transaction should be rolled back
+select * from t;
+rollback;
+
+connection con2;
+--reap
+rollback;
+
+connection con1;
+--reap
+rollback;
+
+
+connection default;
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5),(6,6,6),(7,7,7),(8,8,8),(9,9,9),(10,10,10);
+
+connection con1;
+begin;
+update t1 force index (value) set value2=value2+1 where value=3;
+
+connection con2;
+begin;
+update t1 force index (value) set value2=value2+1 where value=2;
+update t1 force index (value) set value2=value2+1 where value=4;
+
+connection con1;
+send update t1 force index (value) set value2=value2+1 where value=4;
+
+connection con2;
+let $wait_condition =
+`SELECT CONCAT('select count(*) = 1 from information_schema.rocksdb_trx where THREAD_ID = ', '$con1', ' and WAITING_KEY != ""')`;
+--source include/wait_condition.inc
+--error ER_LOCK_DEADLOCK
+update t1 force index (value) set value2=value2+1 where value=3;
+
+connection con1;
+--reap
+rollback;
+
+connection con2;
+rollback;
+drop table t1;
+
+
+connection default;
+disconnect con1;
+disconnect con2;
+disconnect con3;
+
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t,r1,r2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc-master.opt
new file mode 100644
index 00000000..25b80282
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc-master.opt
@@ -0,0 +1 @@
+--transaction-isolation=read-committed
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc.test
new file mode 100644
index 00000000..9757285f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rc.test
@@ -0,0 +1 @@
+--source t/rocksdb_deadlock_detect.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rr.test
new file mode 100644
index 00000000..9757285f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_detect_rr.test
@@ -0,0 +1 @@
+--source t/rocksdb_deadlock_detect.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.inc
new file mode 100644
index 00000000..c88c7ebd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.inc
@@ -0,0 +1,18 @@
+#
+# Stress tests deadlock detection
+#
+
+--source include/have_rocksdb.inc
+
+create table t1 (a int primary key, b int) engine=rocksdb;
+
+set @prior_rocksdb_lock_wait_timeout = @@rocksdb_lock_wait_timeout;
+set @prior_rocksdb_deadlock_detect = @@rocksdb_deadlock_detect;
+set global rocksdb_lock_wait_timeout = 100000;
+set global rocksdb_deadlock_detect = ON;
+
+exec python ../storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.py root 127.0.0.1 $MASTER_MYPORT test t1 10000 10;
+
+set global rocksdb_lock_wait_timeout = @prior_rocksdb_lock_wait_timeout;
+set global rocksdb_deadlock_detect = @prior_rocksdb_deadlock_detect;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.py b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.py
new file mode 100644
index 00000000..3bc8a3be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress.py
@@ -0,0 +1,94 @@
+"""
+This script stress tests deadlock detection.
+
+Usage: rocksdb_deadlock_stress.py user host port db_name table_name
+ num_iters num_threads
+"""
+import cStringIO
+import hashlib
+import MySQLdb
+from MySQLdb.constants import ER
+import os
+import random
+import signal
+import sys
+import threading
+import time
+import string
+import traceback
+
+def is_deadlock_error(exc):
+ error_code = exc.args[0]
+ return (error_code == MySQLdb.constants.ER.LOCK_DEADLOCK)
+
+def get_query(table_name, idx):
+ # Let's assume that even indexes will always be acquireable, to make
+ # deadlock detection more interesting.
+ if idx % 2 == 0:
+ return """SELECT * from %s WHERE a = %d LOCK IN SHARE MODE""" % (table_name, idx)
+ else:
+ r = random.randint(1, 3);
+ if r == 1:
+ return """SELECT * from %s WHERE a = %d FOR UPDATE""" % (table_name, idx)
+ elif r == 2:
+ return """INSERT INTO %s VALUES (%d, 1)
+ ON DUPLICATE KEY UPDATE b=b+1""" % (table_name, idx)
+ else:
+ return """DELETE from %s WHERE a = %d""" % (table_name, idx)
+
+class Worker(threading.Thread):
+ def __init__(self, con, table_name, num_iters):
+ threading.Thread.__init__(self)
+ self.con = con
+ self.table_name = table_name
+ self.num_iters = num_iters
+ self.exception = None
+ self.start()
+ def run(self):
+ try:
+ self.runme()
+ except Exception, e:
+ self.exception = traceback.format_exc()
+ def runme(self):
+ cur = self.con.cursor()
+ for x in xrange(self.num_iters):
+ try:
+ for i in random.sample(xrange(100), 10):
+ cur.execute(get_query(self.table_name, i))
+ self.con.commit()
+ except MySQLdb.OperationalError, e:
+ self.con.rollback()
+ cur = self.con.cursor()
+ if not is_deadlock_error(e):
+ raise e
+
+if __name__ == '__main__':
+ if len(sys.argv) != 8:
+ print "Usage: rocksdb_deadlock_stress.py user host port db_name " \
+ "table_name num_iters num_threads"
+ sys.exit(1)
+
+ user = sys.argv[1]
+ host = sys.argv[2]
+ port = int(sys.argv[3])
+ db = sys.argv[4]
+ table_name = sys.argv[5]
+ num_iters = int(sys.argv[6])
+ num_workers = int(sys.argv[7])
+
+ worker_failed = False
+ workers = []
+ for i in xrange(num_workers):
+ w = Worker(
+ MySQLdb.connect(user=user, host=host, port=port, db=db), table_name,
+ num_iters)
+ workers.append(w)
+
+ for w in workers:
+ w.join()
+ if w.exception:
+ print "Worker hit an exception:\n%s\n" % w.exception
+ worker_failed = True
+
+ if worker_failed:
+ sys.exit(1)
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc-master.opt
new file mode 100644
index 00000000..25b80282
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc-master.opt
@@ -0,0 +1 @@
+--transaction-isolation=read-committed
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc.test
new file mode 100644
index 00000000..67e306b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rc.test
@@ -0,0 +1 @@
+--source t/rocksdb_deadlock_stress.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rr.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rr.test
new file mode 100644
index 00000000..67e306b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_deadlock_stress_rr.test
@@ -0,0 +1 @@
+--source t/rocksdb_deadlock_stress.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_debug.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_debug.test
new file mode 100644
index 00000000..7cd4e09e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_debug.test
@@ -0,0 +1,14 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+--echo #
+--echo # Issue #728: Assertion `covers_key(b)' failed in int
+--echo # myrocks::Rdb_key_def::cmp_full_keys(const rocks db::Slice&,
+--echo # const rocksdb::Slice&)
+--echo #
+
+CREATE TABLE t2(c1 TINYINT SIGNED KEY,c2 TINYINT UNSIGNED,c3 INT);
+INSERT INTO t2(c1)VALUES(0);
+SELECT * FROM t2 WHERE c1<=127 ORDER BY c1 DESC;
+DROP TABLE t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp-master.opt
new file mode 100644
index 00000000..acc0bdaa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=20000 --rocksdb_records_in_range=1000 --rocksdb_perf_context_level=2 --userstat=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp.test
new file mode 100644
index 00000000..8d0ec89e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp.test
@@ -0,0 +1,44 @@
+--source include/have_rocksdb.inc
+
+
+let $cf_name=cf1;
+
+--source include/rocksdb_icp.inc
+
+--echo #
+--echo # Issue #67: Inefficient index condition pushdown
+--echo #
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1 (
+ pk int not null primary key,
+ key1 bigint(20) unsigned,
+ col1 int,
+ key (key1)
+) engine=rocksdb;
+
+insert into t1
+select
+ A.a+10*B.a+100*C.a,
+ A.a+10*B.a+100*C.a,
+ 1234
+from t0 A, t0 B, t0 C;
+
+set @count=0;
+let $save_query=
+set @count_diff =(select (value - @count) from information_schema.rocksdb_perf_context
+ where table_schema=database() and table_name='t1' and stat_type='INTERNAL_KEY_SKIPPED_COUNT');
+
+--replace_column 9 #
+explain
+select * from t1 force index(key1) where key1=1;
+
+eval $save_query;
+select * from t1 force index(key1) where key1=1;
+eval $save_query;
+--echo # The following must be =1, or in any case not 999:
+select @count_diff as "INTERNAL_KEY_SKIPPED_COUNT increment";
+
+drop table t0,t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev-master.opt
new file mode 100644
index 00000000..fe129d79
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=20000 --rocksdb_records_in_range=1000 --userstat=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev.test
new file mode 100644
index 00000000..33914a4e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_icp_rev.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+
+let $cf_name=rev:cf1;
+
+--source include/rocksdb_icp.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks-master.opt
new file mode 100644
index 00000000..c9d9edb8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks-master.opt
@@ -0,0 +1 @@
+--rocksdb_print_snapshot_conflict_queries=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks.test
new file mode 100644
index 00000000..ff092773
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_locks.test
@@ -0,0 +1,94 @@
+--source include/have_rocksdb.inc
+
+#
+# MyRocks-specific tests for locking
+#
+--source include/have_debug.inc
+
+--enable_connect_log
+create table t1 (pk int not null primary key) engine=rocksdb;
+
+insert into t1 values (1),(2),(3);
+
+set autocommit=0;
+begin;
+select * from t1 where pk=1 for update;
+
+--connect (con1,localhost,root,,)
+--connection con1
+call mtr.add_suppression("Got snapshot conflict errors");
+--echo ### Connection con1
+let $ID= `select connection_id()`;
+set @@rocksdb_lock_wait_timeout=500;
+set autocommit=0;
+begin;
+--send select * from t1 where pk=1 for update;
+
+--connection default
+--echo ### Connection default
+
+let $wait_condition=
+ select 1 from INFORMATION_SCHEMA.PROCESSLIST
+ where (ID = $ID /* or SRV_ID = $ID */) and STATE = "Waiting for row lock";
+--source include/wait_condition.inc
+## Waiting for row lock
+## select connection_id();
+## select state='Waiting for row lock' from information_schema.processlist where id=2;
+
+rollback;
+
+connection con1;
+reap;
+rollback;
+connection default;
+
+##
+## Now, repeat the same test but let the wait time out.
+##
+begin;
+select * from t1 where pk=1 for update;
+
+--connection con1
+--echo ### Connection con1
+set @@rocksdb_lock_wait_timeout=2;
+set autocommit=0;
+begin;
+--error ER_LOCK_WAIT_TIMEOUT
+select * from t1 where pk=1 for update;
+
+--connection default
+
+rollback;
+set autocommit=1;
+
+--connection con1
+drop table t1;
+--connection default
+
+--echo #
+--echo # Now, test what happens if another transaction modified the record and committed
+--echo #
+
+CREATE TABLE t1 (
+ id int primary key,
+ value int
+) engine=rocksdb collate latin1_bin;
+insert into t1 values (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
+
+--connection con1
+BEGIN;
+SELECT * FROM t1 WHERE id=3;
+
+--connection default
+BEGIN;
+UPDATE t1 SET value=30 WHERE id=3;
+COMMIT;
+
+--connection con1
+--error ER_LOCK_DEADLOCK
+SELECT * FROM t1 WHERE id=3 FOR UPDATE;
+
+ROLLBACK;
+--disconnect con1
+--connection default
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts.test
new file mode 100644
index 00000000..b8d41ae9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_parts.test
@@ -0,0 +1,160 @@
+--source include/have_rocksdb.inc
+
+--source include/have_partition.inc
+
+--disable_warnings
+drop table if exists t1,t2;
+--enable_warnings
+
+--echo # Tests for MyRocks + partitioning
+
+--echo #
+--echo # MyRocks Issue #70: Server crashes in Rdb_key_def::get_primary_key_tuple
+--echo #
+CREATE TABLE t1 (pk INT PRIMARY KEY, f1 INT, f2 INT, KEY(f2)) ENGINE=RocksDB
+PARTITION BY HASH(pk) PARTITIONS 2;
+INSERT INTO t1 VALUES (1, 6, NULL), (2, NULL, 1);
+
+CREATE TABLE t2 (pk INT PRIMARY KEY, f1 INT) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1, 1), (2, 1);
+
+SELECT f1 FROM t1 WHERE f2 = ( SELECT f1 FROM t2 WHERE pk = 2 );
+
+drop table t1,t2;
+
+--echo #
+--echo # Issue#105: key_info[secondary_key].actual_key_parts does not include primary key on partitioned tables
+--echo #
+CREATE TABLE t1 (
+ id INT PRIMARY KEY,
+ a set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8,
+ b set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8 default null,
+ c set ('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') CHARACTER SET utf8 not null,
+ INDEX (a),
+ INDEX (b),
+ INDEX (c)
+) ENGINE=RocksDB PARTITION BY key (id) partitions 2;
+
+INSERT INTO t1 (id, b) VALUES (28, 3);
+UPDATE t1 SET id=8 WHERE c < 8 LIMIT 1;
+check table t1;
+drop table t1;
+
+--echo #
+--echo # Issue #105, another testcase
+--echo #
+create table t1 (
+ pk int primary key,
+ col1 int,
+ col2 int,
+ key (col1) comment 'rev:cf_issue105'
+) engine=rocksdb partition by hash(pk) partitions 2;
+
+insert into t1 values (1,10,10);
+insert into t1 values (2,10,10);
+
+insert into t1 values (11,20,20);
+insert into t1 values (12,20,20);
+
+set @tmp_rfirr= @@rocksdb_force_index_records_in_range;
+set rocksdb_force_index_records_in_range= 12;
+
+--replace_column 9 #
+explain select * from t1 force index(col1) where col1=10;
+select * from t1 force index(col1) where col1=10;
+select * from t1 use index () where col1=10;
+
+set rocksdb_force_index_records_in_range= @tmp_rfirr;
+
+drop table t1;
+
+--echo #
+--echo # Issue #108: Index-only scans do not work for partitioned tables and extended keys
+--echo #
+create table t1 (
+ pk int primary key,
+ col1 int,
+ col2 int,
+ key (col1)
+) engine=rocksdb partition by hash(pk) partitions 2;
+
+insert into t1 values (1,10,10);
+insert into t1 values (2,10,10);
+
+insert into t1 values (11,20,20);
+insert into t1 values (12,20,20);
+--echo # The following must use "Using index"
+explain select pk from t1 force index(col1) where col1=10;
+
+drop table t1;
+
+--echo #
+--echo # Issue #214: subqueries cause crash
+--echo #
+create TABLE t1(a int,b int,c int,primary key(a,b))
+ partition by list (b*a) (partition x1 values in (1) tablespace ts1,
+ partition x2 values in (3,11,5,7) tablespace ts2,
+ partition x3 values in (16,8,5+19,70-43) tablespace ts3);
+create table t2(b binary(2));
+set session optimizer_switch='materialization=off';
+insert into t1(a,b) values(1,7);
+select a from t1 where a in (select a from t1 where a in (select b from t2));
+
+drop table t1, t2;
+
+--echo #
+--echo # Issue #260: altering name to invalid value leaves table unaccessible
+--echo #
+CREATE TABLE t1 (c1 INT NOT NULL, c2 CHAR(5)) PARTITION BY HASH(c1) PARTITIONS 4;
+INSERT INTO t1 VALUES(1,'a');
+--replace_result \\ /
+--error ER_ERROR_ON_RENAME
+RENAME TABLE t1 TO db3.t3;
+SELECT * FROM t1;
+SHOW TABLES;
+# try it again to the same database
+RENAME TABLE t1 TO test.t3;
+SELECT * FROM t3;
+SHOW TABLES;
+# now try it again but with another existing database
+CREATE DATABASE db3;
+USE test;
+RENAME TABLE t3 to db3.t2;
+USE db3;
+SELECT * FROM t2;
+SHOW TABLES;
+# cleanup
+DROP TABLE t2;
+use test;
+DROP DATABASE db3;
+
+--echo #
+--echo # MDEV-13153 Assertion `global_status_var.global_memory_used == 0 '
+--echo # failed upon server restart with partitioned RocksDB table
+--echo #
+CREATE TABLE t1 (a INT) ENGINE=RocksDB PARTITION BY HASH(a) PARTITIONS 2;
+INSERT INTO t1 (a) VALUES (1),(2);
+ALTER TABLE t1 ADD PARTITION PARTITIONS 2;
+--source include/restart_mysqld.inc
+SELECT 1;
+DROP TABLE t1;
+
+--echo #
+--echo # MDEV-15245: Assertion `false' failed in myrocks::ha_rocksdb::position
+--echo #
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1),(2);
+# partitioning
+CREATE TABLE t2 (b INT) ENGINE=RocksDB
+PARTITION BY RANGE(b)
+(PARTITION p0 VALUES LESS THAN (1),
+ PARTITION p1 VALUES LESS THAN MAXVALUE);
+
+
+INSERT INTO t2 VALUES (NULL),(0);
+CREATE ALGORITHM = MERGE VIEW v1 AS SELECT t2.* FROM t1, t2;
+UPDATE v1 SET b = 5;
+
+drop view v1;
+drop table t1,t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache-master.opt
new file mode 100644
index 00000000..a00258bc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache-master.opt
@@ -0,0 +1 @@
+--query_cache_type=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache.test
new file mode 100644
index 00000000..0369e758
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_qcache.test
@@ -0,0 +1,43 @@
+--source include/have_rocksdb.inc
+
+# Important:
+# The test needs to be run with --mysqld=--query-cache-type=1
+
+--source include/have_query_cache.inc
+set @save_query_cache_type=@@global.query_cache_type;
+set @save_query_cache_size=@@global.query_cache_size;
+set @@global.query_cache_type=1;
+set @@global.query_cache_size=1024*1024;
+
+--enable_connect_log
+
+create table t1 (pk int primary key, c char(8)) engine=RocksDB;
+insert into t1 values (1,'new'),(2,'new');
+
+select * from t1;
+
+--connect (con1,localhost,root,,)
+
+update t1 set c = 'updated';
+#select * from t1;
+
+--connection default
+flush status;
+show status like 'Qcache_hits';
+show global status like 'Qcache_hits';
+select * from t1;
+select sql_no_cache * from t1;
+select * from t1 where pk = 1;
+show status like 'Qcache_hits';
+--echo # MariaDB: Qcache_not_cached is not incremented for select sql_no_cache queries
+--echo # so the following query produces 2, not 3:
+show status like 'Qcache_not_cached';
+show global status like 'Qcache_hits';
+
+drop table t1;
+
+#
+# Cleanup
+#
+set @@global.query_cache_type=@save_query_cache_type;
+set @@global.query_cache_size=@save_query_cache_size;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range-master.opt
new file mode 100644
index 00000000..6ad42e58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000 --rocksdb_records_in_range=50
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range.test
new file mode 100644
index 00000000..f4b6096c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range.test
@@ -0,0 +1,196 @@
+--source include/have_rocksdb.inc
+
+#
+# Range access test for RocksDB storage engine
+#
+select * from information_schema.engines where engine = 'rocksdb';
+
+--disable_warnings
+drop table if exists t0,t1,t2,t3,t4,t5;
+--enable_warnings
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+
+create table t2 (
+ pk int not null,
+ a int not null,
+ b int not null,
+ primary key(pk),
+ key(a) comment 'rev:cf1'
+) engine=rocksdb;
+
+# 10 pk values for each value of a...
+insert into t2 select A.a, FLOOR(A.a/10), A.a from t1 A;
+
+--echo #
+--echo # HA_READ_KEY_EXACT tests
+--echo #
+
+--echo # Original failure was here:
+--replace_column 9 #
+explain
+select * from t2 force index (a) where a=0;
+select * from t2 force index (a) where a=0;
+
+--echo # The rest are for code coverage:
+--replace_column 9 #
+explain
+select * from t2 force index (a) where a=2;
+select * from t2 force index (a) where a=2;
+
+--replace_column 9 #
+explain
+select * from t2 force index (a) where a=3 and pk=33;
+select * from t2 force index (a) where a=3 and pk=33;
+
+select * from t2 force index (a) where a=99 and pk=99;
+select * from t2 force index (a) where a=0 and pk=0;
+select * from t2 force index (a) where a=-1;
+select * from t2 force index (a) where a=-1 and pk in (101,102);
+select * from t2 force index (a) where a=100 and pk in (101,102);
+
+
+--echo #
+--echo # #36: Range in form tbl.key >= const doesn't work in reverse column family
+--echo #
+--replace_column 9 #
+explain
+select count(*) from t2 force index (a) where a>=0 and a <=1;
+select count(*) from t2 force index (a) where a>=0 and a <=1;
+
+--replace_column 9 #
+explain
+select count(*) from t2 force index (a) where a>=-1 and a <=1;
+select count(*) from t2 force index (a) where a>=-1 and a <=1;
+
+--replace_column 9 #
+explain
+select * from t2 force index (a) where a=0 and pk>=3;
+select * from t2 force index (a) where a=0 and pk>=3;
+
+--echo # Try edge cases where we fall over the end of the table
+create table t3 like t2;
+insert into t3 select * from t2;
+
+select * from t3 where pk>=1000000;
+select * from t2 where pk>=1000000;
+
+--echo #
+--echo # #42: Range in form tbl.key > const doesn't work in reverse column family
+--echo #
+--replace_column 9 #
+explain
+select count(*) from t2 force index (a) where a>0;
+select count(*) from t2 force index (a) where a>0;
+
+--replace_column 9 #
+explain
+select count(*) from t2 force index (a) where a>99;
+select count(*) from t2 force index (a) where a>99;
+
+select * from t2 where pk>1000000;
+select * from t3 where pk>1000000;
+
+--replace_column 9 #
+explain
+select count(*) from t2 force index (a) where a=2 and pk>25;
+select count(*) from t2 force index (a) where a=2 and pk>25;
+
+
+select * from t2 force index (a) where a>-10 and a < 1;
+select * from t3 force index (a) where a>-10 and a < 1;
+
+
+--echo #
+--echo # #46: index_read_map(HA_READ_BEFORE_KEY) does not work in reverse column family
+--echo #
+select max(a) from t2 where a < 2;
+select max(a) from t2 where a < -1;
+
+select max(pk) from t2 where a=3 and pk < 6;
+
+select max(pk) from t2 where pk < 200000;
+select max(pk) from t2 where pk < 20;
+
+select max(a) from t3 where a < 2;
+select max(a) from t3 where a < -1;
+select max(pk) from t3 where pk < 200000;
+select max(pk) from t3 where pk < 20;
+
+select max(pk) from t2 where a=3 and pk < 33;
+select max(pk) from t3 where a=3 and pk < 33;
+
+--echo #
+--echo # #48: index_read_map(HA_READ_PREFIX_LAST) does not work in reverse CF
+--echo #
+
+--echo # Tests for search_flag=HA_READ_PREFIX_LAST_OR_PREV
+--echo # Note: the next explain has "Using index condition" in fb/mysql-5.6
+--echo # but "Using where" in MariaDB because the latter does not
+--echo # support ICP over reverse scans.
+--replace_column 9 #
+explain
+select * from t2 where a between 99 and 2000 order by a desc;
+select * from t2 where a between 99 and 2000 order by a desc;
+
+select max(a) from t2 where a <=10;
+select max(a) from t2 where a <=-4;
+
+select max(pk) from t2 where a=5 and pk <=55;
+select max(pk) from t2 where a=5 and pk <=55555;
+select max(pk) from t2 where a=5 and pk <=0;
+
+select max(pk) from t2 where pk <=-1;
+select max(pk) from t2 where pk <=999999;
+select max(pk) from t3 where pk <=-1;
+select max(pk) from t3 where pk <=999999;
+
+--echo #
+--echo # Tests for search_flag=HA_READ_PREFIX_LAST
+--echo #
+
+create table t4 (
+ pk int primary key,
+ a int,
+ b int,
+ c int,
+ key(a,b,c)
+) engine=rocksdb;
+
+insert into t4 select pk,pk,pk,pk from t2 where pk < 100;
+
+--replace_column 9 #
+explain
+select * from t4 where a=1 and b in (1) order by c desc;
+select * from t4 where a=1 and b in (1) order by c desc;
+
+--replace_column 9 #
+explain
+select * from t4 where a=5 and b in (4) order by c desc;
+select * from t4 where a=5 and b in (4) order by c desc;
+
+--echo # HA_READ_PREFIX_LAST for reverse-ordered CF
+create table t5 (
+ pk int primary key,
+ a int,
+ b int,
+ c int,
+ key(a,b,c) comment 'rev:cf2'
+) engine=rocksdb;
+
+insert into t5 select pk,pk,pk,pk from t2 where pk < 100;
+
+--replace_column 9 #
+explain
+select * from t5 where a=1 and b in (1) order by c desc;
+select * from t5 where a=1 and b in (1) order by c desc;
+
+--replace_column 9 #
+explain
+select * from t5 where a=5 and b in (4) order by c desc;
+select * from t5 where a=5 and b in (4) order by c desc;
+
+drop table t0,t1,t2,t3,t4,t5;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range2.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range2.test
new file mode 100644
index 00000000..28010d13
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_range2.test
@@ -0,0 +1,33 @@
+--source include/have_rocksdb.inc
+
+# Issue#212 MyRocks chooses full index scan even if range scan is more efficient
+# rocksdb_debug_optimizer_n_rows must not be set.
+
+create table t1 (id1 bigint, id2 bigint, c1 bigint, c2 bigint, c3 bigint, c4 bigint, c5 bigint, c6 bigint, c7 bigint, primary key (id1, id2), index i(c1, c2));
+--disable_query_log
+let $i=0;
+while ($i<10000)
+{
+ inc $i;
+ eval insert t1(id1, id2, c1, c2, c3, c4, c5, c6, c7)
+ values($i, 0, $i, 0, 0, 0, 0, 0, 0);
+}
+--enable_query_log
+analyze table t1;
+select count(*) from t1;
+--replace_column 9 #
+explain select c1 from t1 where c1 > 5 limit 10;
+drop table t1;
+
+--echo #
+--echo # MDEV-17414: MyROCKS order desc limit 1 fails
+--echo #
+create table t1 (date date);
+insert into t1 values ('2018-10-04'), ('2018-10-05');
+select * from t1 where date < '2018-10-09' order by date desc limit 1; # Works as expected
+alter table t1 add index date_index (date);
+select * from t1 where date < '2018-10-05' order by date desc limit 1; # Works as expected
+--echo # this should not produce an empty set:
+select * from t1 where date < '2018-10-09' order by date desc limit 1;
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.cnf
new file mode 100644
index 00000000..9ceb0cc0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.cnf
@@ -0,0 +1,16 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+sync_binlog=0
+binlog_format=row
+rocksdb_read_free_rpl=PK_SK
+slave-exec-mode=strict
+rocksdb_perf_context_level=3
+
+[mysqld.2]
+sync_binlog=0
+binlog_format=row
+rocksdb_read_free_rpl=PK_SK
+slave-exec-mode=strict
+rocksdb_default_cf_options=write_buffer_size=16k;target_file_size_base=16k
+rocksdb_perf_context_level=3
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.test
new file mode 100644
index 00000000..e1fb9db0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl.test
@@ -0,0 +1,414 @@
+source include/have_rocksdb.inc;
+source include/master-slave.inc;
+source include/have_debug.inc;
+
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+# initialization/insert
+connection master;
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3), (4,4);
+--source include/sync_slave_sql_with_master.inc
+
+--let $diff_tables= master:t1, slave:t1
+
+--echo
+--echo # regular update/delete. With rocks_read_free_rpl=PK_SK, rocksdb_rows_read does not increase on slaves
+--echo
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+update t1 set value=value+1 where id=1;
+delete from t1 where id=4;
+select * from t1;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+
+--echo
+--echo # "rocks_read_free_rpl=PK_SK" makes "row not found error" not happen anymore
+--echo
+connection slave;
+--source include/stop_slave.inc
+delete from t1 where id in (2, 3);
+--source include/start_slave.inc
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+connection master;
+update t1 set value=value+1 where id=3;
+delete from t1 where id=2;
+select * from t1;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+
+--echo
+--echo ## tables without primary key -- read free replication should be disabled
+--echo
+--echo
+--echo #no index
+--echo
+connection master;
+drop table t1;
+create table t1 (c1 int, c2 int);
+insert into t1 values (1,1), (2,2),(3,3),(4,4),(5,5);
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+update t1 set c2=100 where c1=3;
+delete from t1 where c1 <= 2;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+--echo
+--echo #secondary index only
+--echo
+connection master;
+drop table t1;
+create table t1 (c1 int, c2 int, index i(c1));
+insert into t1 values (1,1), (2,2),(3,3),(4,4),(5,5);
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+update t1 set c2=100 where c1=3;
+delete from t1 where c1 <= 2;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+
+
+--echo
+--echo ## large row operations -- primary key modification, secondary key modification
+--echo
+connection master;
+drop table t1;
+create table t1 (id1 bigint, id2 bigint, c1 bigint, c2 bigint, c3 bigint, c4 bigint, c5 bigint, c6 bigint, c7 bigint, primary key (id1, id2), index i(c1, c2));
+
+--disable_query_log
+let $i=1;
+while ($i<=10000)
+{
+ eval insert t1(id1,id2,c1,c2,c3,c4,c5,c6,c7)
+ values($i,0,$i,0,0,0,0,0,0);
+ inc $i;
+}
+--enable_query_log
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+
+--echo
+--echo #updating all secondary keys by 1
+--echo
+--disable_query_log
+let $i=1;
+while ($i<=10000)
+{
+ eval update t1 set c2=c2+1 where id1=$i and id2=0;
+ inc $i;
+}
+--enable_query_log
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--source include/diff_tables.inc
+
+--echo
+--echo #updating all primary keys by 2
+--echo
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--disable_query_log
+let $i=1;
+while ($i<=10000)
+{
+ eval update t1 set id2=id2+2 where id1=$i and id2=0;
+ inc $i;
+}
+--enable_query_log
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--source include/diff_tables.inc
+
+--echo
+--echo #updating secondary keys after truncating t1 on slave
+--echo
+connection slave;
+truncate table t1;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+update t1 set c2=c2+10;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--source include/diff_tables.inc
+
+--echo
+--echo #updating primary keys after truncating t1 on slave
+--echo
+connection slave;
+truncate table t1;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+update t1 set id2=id2+10;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--source include/diff_tables.inc
+
+--echo
+--echo #deleting half rows
+--echo
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+delete from t1 where id1 <= 5000;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+--source include/diff_tables.inc
+
+--echo
+--echo # rocksdb_read_free_rpl = PK_ONLY i.e. it only works on tables with only PK
+--echo
+connection slave;
+--echo [on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl = PK_ONLY;
+start slave;
+connection master;
+--echo [on master]
+create table t2 (id int primary key, i1 int, i2 int, value int);
+create table u2 (id int primary key, i1 int, i2 int, value int, index(i1), index(i2));
+insert into t2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+insert into u2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+--source include/sync_slave_sql_with_master.inc
+
+# make a mismatch between the slave and the master
+connection slave;
+--echo [on slave]
+delete from t2 where id <= 2;
+delete from u2 where id <= 2;
+
+# make changes on the master
+connection master;
+--echo [on master]
+update t2 set i2=100, value=100 where id=1;
+update u2 set i2=100, value=100 where id=1;
+
+connection slave;
+--echo [on slave]
+call mtr.add_suppression("Slave SQL.*Could not execute Update_rows event on table test.u2.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 'u2'.*");
+# wait until we have the expected error
+--let $slave_sql_errno= convert_error(ER_KEY_NOT_FOUND)
+--source include/wait_for_slave_sql_error.inc
+
+# query the t2 table on the slave
+connection slave;
+select count(*) from t2 force index(primary);
+select * from t2 where id=1;
+select i1 from t2 where i1=1;
+select i2 from t2 where i2=100;
+
+# query the u2 table on the slave
+select count(*) from u2 force index(primary);
+select count(*) from u2 force index(i1);
+select count(*) from u2 force index(i2);
+select * from u2 where id=1;
+select i1 from u2 where i1=1;
+select i2 from u2 where i2=100;
+
+# the slave replication thread stopped because of the errors;
+# cleanup the problem and restart it
+--disable_query_log
+insert into u2 values(1,1,1,1), (2,2,2,2);
+start slave sql_thread;
+--source include/wait_for_slave_sql_to_start.inc
+--enable_query_log
+
+connection slave;
+--echo [on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl = PK_SK;
+start slave;
+
+--echo
+--echo # some tables with read-free replication on and some with it off
+--echo
+# We'll set the table filter to all tables starting with 't'
+connection slave;
+--echo [on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl_tables = "t.*";
+start slave;
+connection master;
+--echo [on master]
+drop table if exists t2;
+drop table if exists u2;
+create table t2 (id int primary key, i1 int, i2 int, value int);
+create table u2 (id int primary key, i1 int, i2 int, value int);
+insert into t2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+insert into u2 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+--source include/sync_slave_sql_with_master.inc
+
+# make a mismatch between the slave and the master
+connection slave;
+--echo [on slave]
+delete from t2 where id <= 2;
+delete from u2 where id <= 2;
+
+# make changes on the master
+connection master;
+--echo [on master]
+update t2 set i2=100, value=100 where id=1;
+update u2 set i2=100, value=100 where id=1;
+
+connection slave;
+--echo [on slave]
+call mtr.add_suppression("Slave SQL.*Could not execute Update_rows event on table test.u2.*Error_code.*");
+call mtr.add_suppression("Slave: Can't find record in 'u2'.*");
+# wait until we have the expected error
+--let $slave_sql_errno= convert_error(ER_KEY_NOT_FOUND)
+--source include/wait_for_slave_sql_error.inc
+
+# query the t2 table on the slave
+connection slave;
+select count(*) from t2 force index(primary);
+select * from t2 where id=1;
+select i1 from t2 where i1=1;
+select i2 from t2 where i2=100;
+
+# query the u2 table on the slave
+select count(*) from u2 force index(primary);
+select * from u2 where id=1;
+select i1 from u2 where i1=1;
+select i2 from u2 where i2=100;
+
+# the slave replication thread stopped because of the errors;
+# cleanup the problem and restart it
+--disable_query_log
+insert into u2 values(1,1,1,1), (2,2,2,2);
+start slave sql_thread;
+--source include/wait_for_slave_sql_to_start.inc
+--enable_query_log
+
+connection slave;
+--echo [on slave]
+stop slave;
+set @@global.rocksdb_read_free_rpl_tables = ".*";
+start slave;
+
+--echo
+--echo # secondary keys lose rows
+--echo
+connection master;
+--echo [on master]
+create table t3 (id int primary key, i1 int, i2 int, value int, index(i1),
+index(i2));
+insert into t3 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+--source include/sync_slave_sql_with_master.inc
+
+# make a mismatch between the slave and the master
+connection slave;
+--echo [on slave]
+delete from t3 where id <= 2;
+
+# make changes on the master
+connection master;
+--echo [on master]
+update t3 set i2=100, value=100 where id=1;
+
+# make sure the slave is caught up
+--source include/sync_slave_sql_with_master.inc
+
+# query the t3 table on the slave
+connection slave;
+select count(*) from t3 force index(primary);
+select count(*) from t3 force index(i1);
+select count(*) from t3 force index(i2);
+select * from t3 where id=1;
+select i1 from t3 where i1=1;
+select i2 from t3 where i2=100;
+
+--echo
+--echo # secondary keys have extra rows
+--echo
+connection master;
+--echo [on master]
+create table t4 (id int primary key, i1 int, i2 int, value int, index(i1), index(i2));
+insert into t4 values (1,1,1,1),(2,2,2,2),(3,3,3,3);
+--source include/sync_slave_sql_with_master.inc
+
+# make a mismatch between the slave and the master
+connection slave;
+--echo [on slave]
+update t4 set i1=100 where id=1;
+
+# make changes on the master
+connection master;
+--echo [on master]
+delete from t4 where id=1;
+
+# make sure the slave is caught up
+--source include/sync_slave_sql_with_master.inc
+
+# query the t4 table on the slave
+connection slave;
+--echo [on slave]
+select count(*) from t4 force index(primary);
+select count(*) from t4 force index(i1);
+select count(*) from t4 force index(i2);
+select i1 from t4 where i1=100;
+
+--echo
+--echo # inserts are also read-free
+--echo
+connection master;
+--echo [on master]
+drop table if exists t2;
+drop table if exists t3;
+create table t2 (id int primary key, i1 int, i2 int);
+create table t3 (id int primary key, i1 int, i2 int, key(i1));
+connection slave;
+select variable_value into @up from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+connection master;
+insert into t2 values(1, 1, 1);
+insert into t2 values(2, 2, 2);
+insert into t3 values(1, 1, 1);
+insert into t3 values(2, 2, 2);
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select case when variable_value-@up > 0 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t2;
+select * from t3;
+
+# cleanup
+connection master;
+drop table t1, t2, t3, t4, u2;
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.cnf
new file mode 100644
index 00000000..f225d5dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.cnf
@@ -0,0 +1,17 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+sync_binlog=0
+binlog_format=row
+rocksdb_perf_context_level=3
+
+[mysqld.2]
+sync_binlog=0
+binlog_format=row
+slave-exec-mode=strict
+rocksdb_perf_context_level=3
+slave_use_idempotent_for_recovery=YES
+slave_parallel_workers=8
+mts_dependency_replication=STMT
+mts_dependency_order_commits=0
+slave_tx_isolation=READ-COMMITTED
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.inc b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.inc
new file mode 100644
index 00000000..e69bcce7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.inc
@@ -0,0 +1,69 @@
+
+disable_query_log;
+
+# Create a schema with different kinds of tables (with different kinds of keys)
+connection master;
+create table t1(a int, b int, c int, d int); #no keys
+create table t2(a int primary key, b int, c int, d int); #only pk
+create table t3(a int, b int, c int, d int, key(b)); #only sk
+create table t4(a int, b int unique, c int, d int); #only unique sk
+create table t5(a int primary key, b int, c int, d int, key(b)); #pk + sk
+create table t6(a int primary key, b int unique, c int, d int); #pk + unique sk
+create table t7(a int, b int unique, c int, d int, key(c)); #sk + unique sk
+create table t8(a int primary key, b int unique, c int, d int, key(c)); #pk + sk + unique sk
+
+# Insert a bunch of rows
+let $iter = 0;
+while ($iter < 1000) {
+ let $t = 1;
+ while ($t <= 8) {
+ eval insert into t$t values($iter, $iter, $iter, $iter);
+ inc $t;
+ }
+ inc $iter;
+}
+
+let $iter = 0;
+while ($iter < 10) {
+ let $t = 1;
+ while ($t <= 8) {
+ eval update t$t set a = a + 10000 where a > 900; # update pk (if any)
+ eval update t$t set b = b + 10000 where b > 900; # update sk or unique (if any)
+ eval update t$t set c = c + 10000 where c > 900; # update sk or unique(if any)
+ eval update t$t set d = d + 10000 where d > 900; # update non key col
+
+ eval delete from t$t where a < 25;
+ eval delete from t$t where b < 50;
+ eval delete from t$t where c < 75;
+ eval delete from t$t where d < 100;
+
+ # Re-insert the deleted rows
+ let $i = 0;
+ while ($i < 100) {
+ eval insert into t$t values($i, $i, $i, $i);
+ inc $i;
+ }
+ inc $t;
+ }
+ inc $iter;
+}
+source include/sync_slave_sql_with_master.inc;
+
+connection master;
+let $t = 1;
+while ($t <= 8) {
+ let $diff_tables = master:t$t, slave:t$t;
+ source include/diff_tables.inc;
+ inc $t;
+}
+
+# Cleanup
+connection master;
+let $t = 1;
+while ($t <= 8) {
+ eval drop table t$t;
+ inc $t;
+}
+source include/sync_slave_sql_with_master.inc;
+
+enable_query_log;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.test
new file mode 100644
index 00000000..31e65db8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_read_free_rpl_stress.test
@@ -0,0 +1,22 @@
+source include/have_rocksdb.inc;
+source include/master-slave.inc;
+source include/not_valgrind.inc;
+
+connection slave;
+source include/stop_slave.inc;
+set @@global.rocksdb_read_free_rpl = PK_SK;
+source include/start_slave.inc;
+source rocksdb_read_free_rpl_stress.inc;
+
+connection slave;
+source include/stop_slave.inc;
+set @@global.rocksdb_read_free_rpl = PK_ONLY;
+source include/start_slave.inc;
+source rocksdb_read_free_rpl_stress.inc;
+
+connection slave;
+source include/stop_slave.inc;
+set @@global.rocksdb_read_free_rpl = default;
+source include/start_slave.inc;
+
+source include/rpl_end.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_row_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_row_stats.test
new file mode 100644
index 00000000..ebcc741f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_row_stats.test
@@ -0,0 +1,57 @@
+source include/have_rocksdb.inc;
+create table t1 (a int primary key) engine=rocksdb;
+
+-- echo Verify rocksdb_rows_inserted
+select variable_value into @old_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_rows_inserted';
+insert into t1 values(1);
+select variable_value into @new_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_rows_inserted';
+select @new_rows_inserted - @old_rows_inserted;
+
+-- echo Verify rocksdb_rows_updated
+select variable_value into @old_rows_updated from information_schema.global_status where variable_name = 'rocksdb_rows_updated';
+update t1 set a=2 where a=1;
+select variable_value into @new_rows_updated from information_schema.global_status where variable_name = 'rocksdb_rows_updated';
+select @new_rows_updated - @old_rows_updated;
+
+-- echo Verify rocksdb_rows_read
+select variable_value into @old_rows_read from information_schema.global_status where variable_name = 'rocksdb_rows_read';
+select * from t1;
+select variable_value into @new_rows_read from information_schema.global_status where variable_name = 'rocksdb_rows_read';
+select @new_rows_read - @old_rows_read;
+
+-- echo Verify rocksdb_rows_deleted
+select variable_value into @old_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_rows_deleted';
+delete from t1;
+select variable_value into @new_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_rows_deleted';
+select @new_rows_deleted - @old_rows_deleted;
+
+use mysql;
+create table t1(a int primary key) engine=rocksdb;
+
+-- echo Verify rocksdb_system_rows_inserted
+select variable_value into @old_system_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_system_rows_inserted';
+insert into t1 values(1);
+select variable_value into @new_system_rows_inserted from information_schema.global_status where variable_name = 'rocksdb_system_rows_inserted';
+select @new_system_rows_inserted - @old_system_rows_inserted;
+
+-- echo Verify rocksdb_system_rows_updated
+select variable_value into @old_system_rows_updated from information_schema.global_status where variable_name = 'rocksdb_system_rows_updated';
+update t1 set a=2 where a=1;
+select variable_value into @new_system_rows_updated from information_schema.global_status where variable_name = 'rocksdb_system_rows_updated';
+select @new_system_rows_updated - @old_system_rows_updated;
+
+-- echo Verify rocksdb_system_rows_read
+select variable_value into @old_system_rows_read from information_schema.global_status where variable_name = 'rocksdb_system_rows_read';
+select * from t1;
+select variable_value into @new_system_rows_read from information_schema.global_status where variable_name = 'rocksdb_system_rows_read';
+select @new_system_rows_read - @old_system_rows_read;
+
+-- echo Verify rocksdb_system_rows_deleted
+select variable_value into @old_system_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_system_rows_deleted';
+delete from t1;
+select variable_value into @new_system_rows_deleted from information_schema.global_status where variable_name = 'rocksdb_system_rows_deleted';
+select @new_system_rows_deleted - @old_system_rows_deleted;
+
+drop table t1;
+use test;
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_table_stats_sampling_pct_change.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_table_stats_sampling_pct_change.test
new file mode 100644
index 00000000..5eaeff5c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_table_stats_sampling_pct_change.test
@@ -0,0 +1,80 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+#
+# First set sampling rate to 100% and make sure that the baseline is
+# correct and we get the correct number of rows as a result.
+#
+SET @ORIG_PCT = @@ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 100;
+
+create table t1 (pk int primary key) engine=rocksdb;
+
+--disable_query_log
+let $i = 0;
+let $n = 10000;
+
+while ($i < $n)
+{
+ inc $i;
+ eval insert t1(pk) values($i);
+}
+--enable_query_log
+
+set global rocksdb_force_flush_memtable_now = true;
+
+# This should return 10K rows.
+select table_rows from information_schema.tables
+where table_schema = database() and table_name = 't1';
+
+let $t1_len = `select data_length from information_schema.tables where table_schema = database() and table_name = 't1'`;
+
+drop table t1;
+
+--disable_warnings
+drop table if exists t2;
+--enable_warnings
+
+#
+# Now, set the sampling rate to 10% and expect to see the same amount of
+# rows.
+#
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 10;
+
+create table t2 (pk int primary key) engine=rocksdb;
+
+--disable_query_log
+let $i = 0;
+let $n = 10000;
+
+while ($i < $n)
+{
+ inc $i;
+ eval insert t2(pk) values($i);
+}
+--enable_query_log
+
+set global rocksdb_force_flush_memtable_now = true;
+
+# This should return 10K rows as well.
+select table_rows from information_schema.tables
+where table_schema = database() and table_name = 't2';
+
+let $t2_len = `select data_length from information_schema.tables where table_schema = database() and table_name = 't2'`;
+let $diff = `select abs($t1_len - $t2_len)`;
+
+#
+# Table sizes are approximations and for this particular case we allow about
+# 10% deviation.
+#
+if ($diff < 6000) {
+ select table_name from information_schema.tables where table_schema = database() and table_name = 't2';
+}
+
+drop table t2;
+
+SET GLOBAL ROCKSDB_TABLE_STATS_SAMPLING_PCT = @ORIG_PCT;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback-master.opt
new file mode 100644
index 00000000..8d8ae3d6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback-master.opt
@@ -0,0 +1 @@
+--rocksdb_lock_wait_timeout=2
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback.test b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback.test
new file mode 100644
index 00000000..d47af90d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rocksdb_timeout_rollback.test
@@ -0,0 +1,78 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+SET @@global.rocksdb_rollback_on_timeout = 1;
+show variables like 'rocksdb_rollback_on_timeout';
+
+create table t1 (a int unsigned not null primary key) engine = rocksdb;
+insert into t1 values (1);
+commit;
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con2;
+begin work;
+insert into t1 values (5);
+insert into t1 values (6);
+
+update t1 set a = a + 1 where a = 1;
+
+connection con1;
+begin work;
+insert into t1 values (7);
+insert into t1 values (8);
+
+# This statement will time out. The whole transaction will be
+# rolled back. So values 7 and 8 are not inserted.
+--error ER_LOCK_WAIT_TIMEOUT
+update t1 set a = a + 1 where a = 1;
+
+select * from t1;
+commit;
+
+connection con2;
+select * from t1;
+commit;
+
+connection default;
+select * from t1;
+
+SET @@global.rocksdb_rollback_on_timeout = 0;
+show variables like 'rocksdb_rollback_on_timeout';
+
+connection con2;
+begin work;
+insert into t1 values (9);
+insert into t1 values (10);
+
+update t1 set a = a + 1 where a = 2;
+
+connection con1;
+begin work;
+insert into t1 values (11);
+insert into t1 values (12);
+
+# This statement will time out. Only this statement will be
+# rolled back. So values 11 and 12 are inserted.
+--error ER_LOCK_WAIT_TIMEOUT
+update t1 set a = a + 1 where a = 2;
+
+select * from t1;
+commit;
+
+connection con2;
+select * from t1;
+commit;
+
+connection default;
+select * from t1;
+
+SET @@global.rocksdb_rollback_on_timeout = DEFAULT;
+
+drop table t1;
+disconnect con1;
+disconnect con2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rollback_savepoint.test b/storage/rocksdb/mysql-test/rocksdb/t/rollback_savepoint.test
new file mode 100644
index 00000000..c46d8b07
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rollback_savepoint.test
@@ -0,0 +1,33 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+CREATE TABLE t2 LIKE t1;
+INSERT INTO t2 SELECT * FROM t1;
+
+--connect (con1,localhost,root,,)
+--connect (con2,localhost,root,,)
+
+--connection con1
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+SAVEPOINT a;
+SELECT * FROM t1 ORDER BY pk;
+ROLLBACK TO SAVEPOINT a;
+SAVEPOINT a;
+SELECT * FROM t2 ORDER BY pk;
+ROLLBACK TO SAVEPOINT a;
+
+# should not be blocked
+--connection con2
+ALTER TABLE t1 RENAME TO t3;
+
+--connection default
+DROP TABLE t2, t3;
+
+--disconnect con1
+--disconnect con2
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.cnf
new file mode 100644
index 00000000..44100e59
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.cnf
@@ -0,0 +1,9 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+[mysqld.2]
+binlog_format=row
+slave_parallel_workers=4
+slave_exec_mode=SEMI_STRICT
+rocksdb_lock_wait_timeout=5
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.inc b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.inc
new file mode 100644
index 00000000..9575abb7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.inc
@@ -0,0 +1,98 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+connection master;
+
+create table t0 (a int) engine=myisam;
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1(a int) engine=myisam;
+insert into t1 select A.a + B.a* 10 + C.a * 100 from t0 A, t0 B, t0 C;
+create table t2 (
+ pk int primary key,
+ kp1 int,
+ kp2 int,
+ col1 int,
+ key (kp1,kp2)
+) engine=rocksdb;
+# Use RBR for next few statements to avoid the
+# 'Unsafe statement written to the binary log' warnings.
+set @tmp_binlog_format=@@binlog_format;
+set @@binlog_format=ROW;
+insert into t2 select a,a,a,a from t1;
+create table t3 like t2;
+insert into t3 select * from t2;
+set binlog_format=@tmp_binlog_format;
+
+
+# For GitHub issue#166
+# Slave is suspended at ha_rocksdb::read_range_first() -> index_read_map_impl()
+# -> ha_rocksdb::get_row_by_rowid() -- which is after creating an iterator,
+# Seek(), Next() (getting pk=1)
+# and before GetForUpdate() and before creating a snapshot.
+# Deletes remove pk=2 and pk=3, then resumes update on slave.
+# The update resumes with GetForUpdate(pk=1),
+# index_next() -> secondary_index_read() -> get_row_by_rowid(pk=2)
+# then doesn't find a row.
+# The slave should not stop with error (Can't find a record).
+
+--source include/sync_slave_sql_with_master.inc
+
+connection slave;
+let $old_debug = `select @@global.debug`;
+set global debug_dbug= 'd,dbug.rocksdb.get_row_by_rowid';
+--source include/stop_slave.inc
+--source include/start_slave.inc
+
+connection master;
+update t2 set col1=100 where kp1 between 1 and 3 and mod(kp2,2)=0;
+
+connection slave;
+set debug_sync= 'now WAIT_FOR Reached';
+eval set global debug_dbug = '$old_debug';
+set sql_log_bin=0;
+delete from t2 where pk=2;
+delete from t2 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+
+connection master;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t2 where pk < 5;
+
+# For GitHub issue#162 (result file must be updated after fixing #162)
+connection slave;
+set global debug_dbug= 'd,dbug.rocksdb.get_row_by_rowid';
+--source include/stop_slave.inc
+--source include/start_slave.inc
+
+connection master;
+update t3 set col1=100 where kp1 between 1 and 4 and mod(kp2,2)=0;
+
+connection slave;
+call mtr.add_suppression("Deadlock found when trying to get lock");
+set debug_sync= 'now WAIT_FOR Reached';
+eval set global debug_dbug = '$old_debug';
+set sql_log_bin=0;
+delete from t3 where pk=2;
+delete from t3 where pk=3;
+set debug_sync= 'now SIGNAL signal.rocksdb.get_row_by_rowid_let_running';
+
+connection master;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+# col1 for pk=4 should be 100
+select * from t3 where pk < 5;
+
+set debug_sync='RESET';
+# Cleanup
+connection master;
+drop table t0, t1, t2, t3;
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.test
new file mode 100644
index 00000000..36188427
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found.test
@@ -0,0 +1,4 @@
+--source include/have_binlog_format_row.inc
+
+--source rpl_row_not_found.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.cnf
new file mode 100644
index 00000000..110d18ab
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.cnf
@@ -0,0 +1,11 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+transaction_isolation=read-committed
+[mysqld.2]
+binlog_format=row
+slave_parallel_workers=4
+slave_exec_mode=SEMI_STRICT
+rocksdb_lock_wait_timeout=5
+transaction_isolation=read-committed
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.test
new file mode 100644
index 00000000..36188427
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_not_found_rc.test
@@ -0,0 +1,4 @@
+--source include/have_binlog_format_row.inc
+
+--source rpl_row_not_found.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.cnf
new file mode 100644
index 00000000..09a1c853
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.cnf
@@ -0,0 +1 @@
+!include suite/rpl/my.cnf
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.test
new file mode 100644
index 00000000..b103dfc3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_rocksdb.test
@@ -0,0 +1,48 @@
+--source include/have_rocksdb.inc
+
+source include/master-slave.inc;
+source include/have_binlog_format_row.inc;
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+connection master;
+
+select @@binlog_format;
+create table t1 (pk int primary key) engine=rocksdb;
+insert into t1 values (1),(2),(3);
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+
+select * from t1;
+
+connection master;
+drop table t1;
+
+--echo #
+--echo # Issue #18: slave crash on update with row based binary logging
+--echo #
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,1,1);
+insert into t1 values (3,1,1);
+insert into t1 values (4,1,1);
+insert into t1 values (5,1,1);
+update t1 set value2=100 where id=1;
+update t1 set value2=200 where id=2;
+update t1 set value2=300 where id=3;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1 where id=1;
+select * from t1 where id=2;
+select * from t1 where id=3;
+
+connection master;
+drop table t1;
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats-slave.opt b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats-slave.opt
new file mode 100644
index 00000000..039295e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats-slave.opt
@@ -0,0 +1 @@
+--userstat=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.cnf
new file mode 100644
index 00000000..09a1c853
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.cnf
@@ -0,0 +1 @@
+!include suite/rpl/my.cnf
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.test
new file mode 100644
index 00000000..db4d1ca6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_stats.test
@@ -0,0 +1,47 @@
+--source include/have_rocksdb.inc
+
+source include/have_binlog_format_row.inc;
+source include/master-slave.inc;
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+# initialization/insert
+connection master;
+# creating save_read_stats() and get_read_stats() procedures
+--source init_stats_procedure.inc
+
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3), (4,4), (5,5);
+--source include/sync_slave_sql_with_master.inc
+
+connection slave;
+call save_read_stats();
+connection master;
+update t1 set value=value+1 where id=1;
+update t1 set value=value+1 where id=3;
+select * from t1;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+call get_read_stats();
+select * from t1;
+call save_read_stats();
+
+connection master;
+delete from t1 where id in (4,5);
+select * from t1;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+call get_read_stats();
+select * from t1;
+
+
+# cleanup
+connection master;
+drop table t1;
+--source drop_stats_procedure.inc
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.cnf
new file mode 100644
index 00000000..b0a37fd3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.cnf
@@ -0,0 +1,19 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+gtid_mode=ON
+enforce_gtid_consistency
+log_slave_updates
+binlog_row_image=FULL
+rocksdb_read_free_rpl=PK_SK
+rocksdb_strict_collation_check=0
+[mysqld.2]
+binlog_format=row
+gtid_mode=ON
+enforce_gtid_consistency
+log_slave_updates
+binlog_row_image=FULL
+rocksdb_read_free_rpl=PK_SK
+rocksdb_strict_collation_check=0
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.test
new file mode 100644
index 00000000..4490353b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_row_triggers.test
@@ -0,0 +1,262 @@
+-- source include/have_binlog_format_row.inc
+-- source include/have_rbr_triggers.inc
+-- source include/have_rocksdb.inc
+-- source include/master-slave.inc
+
+-- echo # Test of row replication with triggers on the slave side
+connection master;
+CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1));
+SELECT * FROM t1;
+
+sync_slave_with_master;
+
+connection slave;
+SET @old_slave_exec_mode= @@global.slave_exec_mode;
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET @@global.slave_exec_mode= IDEMPOTENT;
+SET @@global.slave_run_triggers_for_rbr= YES;
+SELECT * FROM t1;
+create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
+insert into t2 values
+ ('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
+ ('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
+ ('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
+create trigger t1_cnt_b before update on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
+create trigger t1_cnt_db before delete on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd0';
+create trigger t1_cnt_ib before insert on t1 for each row
+ update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
+create trigger t1_cnt_a after update on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
+create trigger t1_cnt_da after delete on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
+create trigger t1_cnt_ia after insert on t1 for each row
+ update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
+SELECT * FROM t2 order by id;
+
+connection master;
+--echo # INSERT triggers test
+insert into t1 values ('a','b');
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+
+connection master;
+--echo # UPDATE triggers test
+update t1 set C1= 'd';
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+
+connection master;
+--echo # DELETE triggers test
+delete from t1 where C1='d';
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+--echo # INSERT triggers which cause also UPDATE test (insert duplicate row)
+insert into t1 values ('0','1');
+SELECT * FROM t2 order by id;
+
+connection master;
+insert into t1 values ('0','1');
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+--echo # INSERT triggers which cause also DELETE test
+--echo # (insert duplicate row in table referenced by foreign key)
+insert into t1 values ('1','1');
+
+connection master;
+# Foreign key is not supported in MyRocks
+#CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) );
+#insert into t1 values ('1','1');
+
+#sync_slave_with_master;
+
+#connection slave;
+#SELECT * FROM t2 order by id;
+
+#connection master;
+#drop table t3,t1;
+drop table if exists t1;
+
+sync_slave_with_master;
+
+connection slave;
+SET @@global.slave_exec_mode= @old_slave_exec_mode;
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+drop table t2;
+
+--connection master
+CREATE TABLE t1 (i INT);
+CREATE TABLE t2 (i INT);
+
+--sync_slave_with_master
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET GLOBAL slave_run_triggers_for_rbr=YES;
+CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
+ INSERT INTO t2 VALUES (new.i);
+
+--connection master
+BEGIN;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t1 VALUES (2);
+COMMIT;
+--sync_slave_with_master
+select * from t2;
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+--connection master
+drop tables t2,t1;
+
+--sync_slave_with_master
+
+-- echo # Triggers on slave do not work if master has some
+
+connection master;
+CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1));
+SELECT * FROM t1;
+create trigger t1_dummy before delete on t1 for each row
+ set @dummy= 1;
+
+sync_slave_with_master;
+
+connection slave;
+SET @old_slave_exec_mode= @@global.slave_exec_mode;
+SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
+SET @@global.slave_exec_mode= IDEMPOTENT;
+SET @@global.slave_run_triggers_for_rbr= YES;
+SELECT * FROM t1;
+create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
+insert into t2 values
+ ('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
+ ('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
+ ('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
+create trigger t1_cnt_b before update on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
+create trigger t1_cnt_ib before insert on t1 for each row
+ update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
+create trigger t1_cnt_a after update on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
+create trigger t1_cnt_da after delete on t1 for each row
+ update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
+create trigger t1_cnt_ia after insert on t1 for each row
+ update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
+SELECT * FROM t2 order by id;
+
+connection master;
+--echo # INSERT triggers test
+insert into t1 values ('a','b');
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+connection master;
+--echo # UPDATE triggers test
+update t1 set C1= 'd';
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+
+connection master;
+--echo # DELETE triggers test
+delete from t1 where C1='d';
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+--echo # INSERT triggers which cause also UPDATE test (insert duplicate row)
+insert into t1 values ('0','1');
+SELECT * FROM t2 order by id;
+
+
+connection master;
+insert into t1 values ('0','1');
+
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t2 order by id;
+--echo # INSERT triggers which cause also DELETE test
+--echo # (insert duplicate row in table referenced by foreign key)
+insert into t1 values ('1','1');
+
+connection master;
+
+# Foreign Key is not supported in MyRocks
+#CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) );
+#insert into t1 values ('1','1');
+
+#sync_slave_with_master;
+
+#connection slave;
+#SELECT * FROM t2 order by id;
+
+#connection master;
+#drop table t3,t1;
+drop table if exists t1;
+
+sync_slave_with_master;
+
+connection slave;
+SET @@global.slave_exec_mode= @old_slave_exec_mode;
+SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
+drop table t2;
+
+--echo #
+--echo # MDEV-5513: Trigger is applied to the rows after first one
+--echo #
+
+--connection master
+create table t1 (a int, b int);
+create table tlog (a int auto_increment primary key);
+set sql_log_bin=0;
+create trigger tr1 after insert on t1 for each row insert into tlog values (null);
+set sql_log_bin=1;
+
+sync_slave_with_master;
+--connection slave
+
+set @slave_run_triggers_for_rbr.saved = @@slave_run_triggers_for_rbr;
+set global slave_run_triggers_for_rbr=1;
+create trigger tr2 before insert on t1 for each row set new.b = new.a;
+
+--connection master
+insert into t1 values (1,10),(2,20),(3,30);
+
+--sync_slave_with_master
+select * from t1;
+
+--echo #
+--echo # Verify slave skips running triggers if master ran and logged the row events for triggers
+--echo #
+--connection master
+create table t4(a int, b int);
+delete from tlog;
+create trigger tr4 before insert on t4 for each row insert into tlog values (null);
+insert into t4 values (1, 10),(2, 20);
+select * from tlog;
+
+--sync_slave_with_master
+select * from t4;
+select * from tlog;
+
+# Cleanup
+set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved;
+--connection master
+drop table t1, tlog, t4;
+sync_slave_with_master;
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.cnf
new file mode 100644
index 00000000..09a1c853
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.cnf
@@ -0,0 +1 @@
+!include suite/rpl/my.cnf
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.test
new file mode 100644
index 00000000..56d14f92
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_savepoint.test
@@ -0,0 +1,91 @@
+--source include/have_rocksdb.inc
+
+source include/have_binlog_format_row.inc;
+source include/master-slave.inc;
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+connection master;
+
+create table t1 (id int primary key, value int);
+insert into t1 values (1,1), (2,2), (3,3);
+
+begin;
+insert into t1 values (11, 1);
+savepoint a;
+insert into t1 values (12, 1);
+--error ER_ROLLBACK_TO_SAVEPOINT
+rollback to savepoint a;
+--error ER_ROLLBACK_ONLY
+commit;
+commit;
+select * from t1;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+
+select * from t1;
+
+connection master;
+begin;
+insert into t1 values (21, 1);
+savepoint a;
+insert into t1 values (22, 1);
+--error ER_ROLLBACK_TO_SAVEPOINT
+rollback to savepoint a;
+--error ER_ROLLBACK_ONLY
+insert into t1 values (23, 1);
+--error ER_ROLLBACK_ONLY
+commit;
+commit;
+select * from t1;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+
+
+connection master;
+begin;
+insert into t1 values (31, 1);
+savepoint a;
+insert into t1 values (32, 1);
+savepoint b;
+insert into t1 values (33, 1);
+--error ER_ROLLBACK_TO_SAVEPOINT
+rollback to savepoint a;
+--error ER_ROLLBACK_ONLY
+insert into t1 values (34, 1);
+rollback;
+select * from t1;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+
+### GitHub Issue#195
+connection master;
+SET autocommit=off;
+select * from t1;
+SAVEPOINT A;
+select * from t1;
+SAVEPOINT A;
+insert into t1 values (35, 35);
+--error ER_ROLLBACK_TO_SAVEPOINT
+ROLLBACK TO SAVEPOINT A;
+--error ER_ROLLBACK_ONLY
+START TRANSACTION;
+select * from t1;
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1;
+
+
+connection master;
+drop table t1;
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.cnf
new file mode 100644
index 00000000..6e5130c1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.cnf
@@ -0,0 +1,7 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=statement
+[mysqld.2]
+binlog_format=mixed
+rocksdb_lock_wait_timeout=5
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.test
new file mode 100644
index 00000000..cb5f5e04
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement.test
@@ -0,0 +1,59 @@
+--source include/have_rocksdb.inc
+source include/master-slave.inc;
+
+source include/have_binlog_format_statement.inc;
+
+connection master;
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+connection master;
+
+select @@binlog_format;
+create table t1 (pk int primary key) engine=rocksdb;
+--error ER_BINLOG_STMT_MODE_AND_ROW_ENGINE
+insert into t1 values (1),(2),(3);
+
+set session rocksdb_unsafe_for_binlog=on;
+insert into t1 values (1),(2),(3);
+select * from t1;
+delete from t1;
+set session rocksdb_unsafe_for_binlog=off;
+
+--error ER_BINLOG_STMT_MODE_AND_ROW_ENGINE
+insert into t1 values (1),(2),(3);
+
+set binlog_format=row;
+insert into t1 values (1),(2),(3);
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+
+select * from t1;
+
+connection master;
+drop table t1;
+
+create table t1 (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into t1 values (1,1,1);
+insert into t1 values (2,1,1);
+insert into t1 values (3,1,1);
+insert into t1 values (4,1,1);
+insert into t1 values (5,1,1);
+update t1 set value2=100 where id=1;
+update t1 set value2=200 where id=2;
+update t1 set value2=300 where id=3;
+
+--source include/sync_slave_sql_with_master.inc
+connection slave;
+select * from t1 where id=1;
+select * from t1 where id=2;
+select * from t1 where id=3;
+
+connection master;
+drop table t1;
+set binlog_format=row;
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.cnf b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.cnf
new file mode 100644
index 00000000..470b073d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.cnf
@@ -0,0 +1,9 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=statement
+rocksdb_unsafe_for_binlog=1
+[mysqld.2]
+binlog_format=row
+slave_parallel_workers=4
+rocksdb_lock_wait_timeout=5
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.test b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.test
new file mode 100644
index 00000000..019e83ac
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rpl_statement_not_found.test
@@ -0,0 +1,3 @@
+--source include/have_binlog_format_statement.inc
+--source rpl_row_not_found.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg.inc b/storage/rocksdb/mysql-test/rocksdb/t/rqg.inc
new file mode 100644
index 00000000..0f3246de
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg.inc
@@ -0,0 +1,44 @@
+#
+# Random Query Generator tests
+#
+# Arguments needed to be set by the test when including this one:
+# $TESTDIR : name of sub-directory in conf containing the data/grammar files
+# $GRAMMAR_FILES: space separated list of grammar files
+# $DATA_FILE: name of the data file
+#
+
+let $MYSQL_BASEDIR = `SELECT @@BASEDIR`;
+let RQG_BASE = $MYSQL_BASEDIR/rqg/rqg/common/mariadb-patches;
+let MYSQL_SOCKET = `SELECT @@SOCKET`;
+let GRAMMAR_FILES = $GRAMMAR_FILES;
+let DATA_FILE = $DATA_FILE;
+let TESTDIR = $TESTDIR;
+let $TESTDB = rqg_$TESTDIR;
+let TESTDB = $TESTDB;
+
+--eval CREATE DATABASE IF NOT EXISTS $TESTDB
+
+--perl
+
+$ENV{'RQG_HOME'}=$ENV{'RQG_BASE'};
+foreach $grammar_file (split(/ /, $ENV{'GRAMMAR_FILES'})) {
+
+ # Errors from the gentest.pl file will be captured in the results file
+ my $cmd = "perl $ENV{'RQG_BASE'}/gentest.pl " .
+ "--dsn=DBI:MariaDB:host=:port=:user=root:database=$ENV{'TESTDB'}" .
+ ":mariadb_socket=$ENV{'MYSQL_SOCKET'} " .
+ "--gendata=$ENV{'RQG_BASE'}/conf/$ENV{'TESTDIR'}/$ENV{'DATA_FILE'} " .
+ "--grammar=$ENV{'RQG_BASE'}/conf/$ENV{'TESTDIR'}/$grammar_file " .
+ "--threads=5 --queries=10000 --duration=60 --sqltrace 2>&1 >> " .
+ "$ENV{'MYSQLTEST_VARDIR'}/tmp/$ENV{'TESTDB'}.log";
+
+ print "Running test with grammar file $grammar_file\n";
+ system($cmd);
+ if ($? != 0) {
+ print ("Failure running test! Command executed: $cmd\n");
+ }
+}
+
+EOF
+
+--eval DROP DATABASE $TESTDB
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples-master.opt
new file mode 100644
index 00000000..5b714857
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=0
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples.test b/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples.test
new file mode 100644
index 00000000..80bae004
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_examples.test
@@ -0,0 +1,12 @@
+--source include/have_rocksdb.inc
+# Don't run the RQG tests with --rpc_protocol because the connections and
+# queries will be coming from Perl where we don't have any ability to
+# specify the query attributes needed for the RPC protocol.
+--source include/not_rpc_protocol.inc
+
+# RQG's examples test
+let $TESTDIR = examples;
+let $GRAMMAR_FILES = example.yy;
+let $DATA_FILE = example.zz;
+
+--source rqg.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime-master.opt
new file mode 100644
index 00000000..f4942738
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=0 --secure-file-priv=/tmp
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime.test b/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime.test
new file mode 100644
index 00000000..2e560c86
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_runtime.test
@@ -0,0 +1,58 @@
+--source include/have_rocksdb.inc
+# Don't run the RQG tests with --rpc_protocol because the connections and
+# queries will be coming from Perl where we don't have any ability to
+# specify the query attributes needed for the RPC protocol.
+--source include/not_rpc_protocol.inc
+
+call mtr.add_suppression("Did not write failed ");
+call mtr.add_suppression("Can't open and lock privilege tables");
+call mtr.add_suppression("Attempt to delete the trigger file");
+
+SET @ORIG_EVENT_SCHEDULER = @@EVENT_SCHEDULER;
+
+# mysql.user and mysql.tables_priv are modified by the
+# tests, so they need to be restored to the original
+# state.
+--disable_warnings
+CREATE TABLE mysql.user_temp LIKE mysql.user;
+INSERT mysql.user_temp SELECT * FROM mysql.user;
+CREATE TABLE mysql.tables_priv_temp LIKE mysql.tables_priv;
+INSERT mysql.tables_priv_temp SELECT * FROM mysql.tables_priv_temp;
+--enable_warnings
+
+# RQG's runtime test
+let $TESTDIR = runtime;
+
+let $GRAMMAR_FILES = alter_online.yy;
+let $DATA_FILE = alter_online.zz;
+
+--source rqg.inc
+
+let $GRAMMAR_FILES = concurrency_1.yy;
+let $DATA_FILE = concurrency_1.zz;
+
+--source rqg.inc
+
+let $GRAMMAR_FILES = connect_kill_sql.yy;
+let $DATA_FILE = connect_kill_data.zz;
+
+--source rqg.inc
+
+let $GRAMMAR_FILES = metadata_stability.yy;
+let $DATA_FILE = metadata_stability.zz;
+
+--source rqg.inc
+
+--disable_warnings
+DELETE FROM mysql.tables_priv;
+DELETE FROM mysql.user;
+INSERT mysql.user SELECT * FROM mysql.user_temp;
+INSERT mysql.tables_priv SELECT * FROM mysql.tables_priv_temp;
+DROP TABLE mysql.user_temp;
+DROP TABLE mysql.tables_priv_temp;
+DROP TABLE IF EXISTS test.executors;
+DROP DATABASE IF EXISTS testdb_N;
+DROP DATABASE IF EXISTS testdb_S;
+--enable_warnings
+
+SET GLOBAL EVENT_SCHEDULER = @ORIG_EVENT_SCHEDULER;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions-master.opt
new file mode 100644
index 00000000..5b714857
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=0
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions.test b/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions.test
new file mode 100644
index 00000000..383b9aed
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/rqg_transactions.test
@@ -0,0 +1,14 @@
+--source include/have_rocksdb.inc
+# Don't run the RQG tests with --rpc_protocol because the connections and
+# queries will be coming from Perl where we don't have any ability to
+# specify the query attributes needed for the RPC protocol.
+--source include/not_rpc_protocol.inc
+
+call mtr.add_suppression("Deadlock found when trying to get lock");
+
+# RQG's transactions test
+let $TESTDIR = transactions;
+let $GRAMMAR_FILES = transactions.yy repeatable_read.yy transaction_durability.yy transactions-flat.yy combinations.yy repeatable_read.yy transaction_durability.yy transactions-flat.yy;
+let $DATA_FILE = transactions.zz;
+
+--source rqg.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/se-innodb.out b/storage/rocksdb/mysql-test/rocksdb/t/se-innodb.out
new file mode 100644
index 00000000..406e5066
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/se-innodb.out
@@ -0,0 +1 @@
+Can't open perl script "./mtr": No such file or directory
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/secondary_key_update_lock.test b/storage/rocksdb/mysql-test/rocksdb/t/secondary_key_update_lock.test
new file mode 100644
index 00000000..b43a32b6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/secondary_key_update_lock.test
@@ -0,0 +1,26 @@
+--source include/have_rocksdb.inc
+
+# Tests if locks are held for the secondary keys of old rows in updates
+
+connect (con, localhost, root,,);
+
+connection default;
+create table t1 (a int primary key, b int unique key) engine = rocksdb;
+insert into t1 values(1, 1);
+
+connection con;
+begin;
+update t1 set b = 2 where b = 1; # this should lock the row where b = 1
+
+connection default;
+error ER_LOCK_WAIT_TIMEOUT;
+insert into t1 values(2, 1); # should error out with lock_wait_timeout
+
+connection con;
+rollback;
+select * from t1;
+
+# Cleanup
+connection default;
+drop table t1;
+disconnect con;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/select.test b/storage/rocksdb/mysql-test/rocksdb/t/select.test
new file mode 100644
index 00000000..3d9bdc7b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/select.test
@@ -0,0 +1,202 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic SELECT statements
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'foobar'),(1,'z'),(200,'bar');
+
+CREATE TABLE t2 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (a,b) SELECT a, b FROM t1;
+INSERT INTO t1 (a,b) SELECT a, b FROM t2;
+
+--sorted_result
+SELECT * FROM t1;
+
+# Modifiers
+
+--sorted_result
+SELECT DISTINCT a FROM t1;
+
+--sorted_result
+SELECT ALL b, a FROM t1;
+
+# Optimizer and cache directives should not have any visible effect here,
+# but we will add them for completness
+
+--sorted_result
+SELECT STRAIGHT_JOIN SQL_CACHE t1.* FROM t2, t1 WHERE t1.a <> t2.a;
+
+--sorted_result
+SELECT SQL_SMALL_RESULT SQL_NO_CACHE t1.a FROM t1, t2;
+
+--sorted_result
+SELECT SQL_BIG_RESULT SQL_CALC_FOUND_ROWS DISTINCT(t2.a)
+ FROM t1 t1_1, t2, t1 t1_2;
+SELECT FOUND_ROWS();
+
+SET @save_query_cache=@@global.query_cache_size;
+SET GLOBAL query_cache_size = 1024*1024;
+--sorted_result
+SELECT SQL_CACHE * FROM t1, t2;
+SET @@global.query_cache_size=@save_query_cache;
+
+# Combination of main clauses
+
+--sorted_result
+SELECT a+10 AS field1, CONCAT(b,':',b) AS field2 FROM t1
+WHERE b > 'b' AND a IS NOT NULL
+GROUP BY 2 DESC, field1 ASC
+HAVING field1 < 1000
+ORDER BY field2, 1 DESC, field1*2
+LIMIT 5 OFFSET 1;
+
+# ROLLUP
+--sorted_result
+SELECT SUM(a), MAX(a), b FROM t1 GROUP BY b WITH ROLLUP;
+
+# Procedure
+
+--sorted_result
+SELECT * FROM t2 WHERE a>0 PROCEDURE ANALYSE();
+
+# SELECT INTO
+let $datadir = `SELECT @@datadir`;
+
+--replace_result $datadir <DATADIR>
+eval
+SELECT t1.a, t2.b FROM t2, t1 WHERE t1.a = t2.a ORDER BY t2.b, t1.a
+ INTO OUTFILE '$datadir/select.out'
+ CHARACTER SET utf8
+ FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '''';
+--cat_file $datadir/select.out
+--remove_file $datadir/select.out
+
+--replace_result $datadir <DATADIR>
+--error ER_TOO_MANY_ROWS
+eval
+SELECT t1.a, t2.b FROM t2, t1 WHERE t1.a = t2.a ORDER BY t2.b, t1.a
+ INTO DUMPFILE '$datadir/select.dump';
+--remove_file $datadir/select.dump
+--replace_result $datadir <DATADIR>
+eval
+SELECT t1.*, t2.* FROM t1, t2 ORDER BY t2.b, t1.a, t2.a, t1.b, t1.pk, t2.pk LIMIT 1
+ INTO DUMPFILE '$datadir/select.dump';
+
+--cat_file $datadir/select.dump
+--echo
+--remove_file $datadir/select.dump
+
+SELECT MIN(a), MAX(a) FROM t1 INTO @min, @max;
+SELECT @min, @max;
+
+# Joins
+
+--sorted_result
+SELECT t1_1.*, t2.* FROM t2, t1 AS t1_1, t1 AS t1_2
+ WHERE t1_1.a = t1_2.a AND t2.a = t1_1.a;
+
+--sorted_result
+SELECT alias1.* FROM ( SELECT a,b FROM t1 ) alias1, t2 WHERE t2.a IN (100,200);
+
+--sorted_result
+SELECT t1.a FROM { OJ t1 LEFT OUTER JOIN t2 ON t1.a = t2.a+10 };
+
+--sorted_result
+SELECT t1.* FROM t2 INNER JOIN t1;
+
+--sorted_result
+SELECT t1_2.* FROM t1 t1_1 CROSS JOIN t1 t1_2 ON t1_1.b = t1_2.b;
+
+--sorted_result
+SELECT t1.a, t2.b FROM t2 STRAIGHT_JOIN t1 WHERE t1.b > t2.b;
+
+SELECT t1.a, t2.b FROM t2 STRAIGHT_JOIN t1 ON t1.b > t2.b ORDER BY t1.a, t2.b;
+
+SELECT t2.* FROM t1 LEFT JOIN t2 USING (a) ORDER BY t2.a, t2.b LIMIT 1;
+
+--sorted_result
+SELECT t2.* FROM t2 LEFT OUTER JOIN t1 ON t1.a = t2.a WHERE t1.a IS NOT NULL;
+
+SELECT SUM(t2.a) FROM t1 RIGHT JOIN t2 ON t2.b = t1.b;
+
+SELECT MIN(t2.a) FROM t1 RIGHT OUTER JOIN t2 USING (b,a);
+
+--sorted_result
+SELECT alias.b FROM t1 NATURAL JOIN ( SELECT a,b FROM t1 ) alias WHERE b > '';
+
+--sorted_result
+SELECT t2.b FROM ( SELECT a,b FROM t1 ) alias NATURAL LEFT JOIN t2 WHERE b IS NOT NULL;
+
+--sorted_result
+SELECT t1.*, t2.* FROM t1 NATURAL LEFT OUTER JOIN t2;
+
+--sorted_result
+SELECT t2_2.* FROM t2 t2_1 NATURAL RIGHT JOIN t2 t2_2 WHERE t2_1.a IN ( SELECT a FROM t1 );
+
+--sorted_result
+SELECT t1_2.b FROM t1 t1_1 NATURAL RIGHT OUTER JOIN t1 t1_2 INNER JOIN t2;
+
+# Subquery as scalar operand, subquery in the FROM clause
+
+--sorted_result
+SELECT ( SELECT MIN(a) FROM ( SELECT a,b FROM t1 ) alias1 ) AS min_a FROM t2;
+
+# Comparison using subqueries
+
+--sorted_result
+SELECT a,b FROM t2 WHERE a = ( SELECT MIN(a) FROM t1 );
+
+--sorted_result
+SELECT a,b FROM t2 WHERE b LIKE ( SELECT b FROM t1 ORDER BY b LIMIT 1 );
+
+# Subquery with IN, correlated subquery
+
+--sorted_result
+SELECT t2.* FROM t1 t1_outer, t2 WHERE ( t1_outer.a, t2.b ) IN ( SELECT a, b FROM t2 WHERE a = t1_outer.a );
+
+# Subquery with ANY, ALL
+
+--sorted_result
+SELECT a,b FROM t2 WHERE b = ANY ( SELECT b FROM t1 WHERE a > 1 );
+
+--sorted_result
+SELECT a,b FROM t2 WHERE b > ALL ( SELECT b FROM t1 WHERE b < 'foo' );
+
+# Row subqueries
+
+--sorted_result
+SELECT a,b FROM t1 WHERE ROW(a, b) = ( SELECT a, b FROM t2 ORDER BY a, b LIMIT 1 );
+
+# Subquery with EXISTS
+
+--sorted_result
+SELECT a,b FROM t1 WHERE EXISTS ( SELECT a,b FROM t2 WHERE t2.b > t1.b );
+
+# Subquery in ORDER BY
+
+--sorted_result
+SELECT t1.* FROM t1, t2 ORDER BY ( SELECT b FROM t1 WHERE a IS NULL ORDER BY b LIMIT 1 ) DESC;
+
+# Subquery in HAVING
+
+--sorted_result
+SELECT a, b FROM t1 HAVING a IN ( SELECT a FROM t2 WHERE b = t1.b );
+
+# Union
+
+--sorted_result
+SELECT a,b FROM t1 UNION SELECT a,b FROM t2 UNION DISTINCT SELECT a,b FROM t1;
+
+--sorted_result
+SELECT a,b FROM t1 UNION SELECT a,b FROM t2 UNION ALL SELECT a,b FROM t1;
+
+
+# Cleanup
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/select_for_update.test b/storage/rocksdb/mysql-test/rocksdb/t/select_for_update.test
new file mode 100644
index 00000000..14fdfb78
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/select_for_update.test
@@ -0,0 +1,55 @@
+--source include/have_rocksdb.inc
+
+#
+# SELECT .. FOR UPDATE
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--enable_connect_log
+
+--source include/count_sessions.inc
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+
+--connect (con1,localhost,root,,)
+BEGIN;
+--sorted_result
+SELECT a,b FROM t1 WHERE b='a' FOR UPDATE;
+
+--connection default
+SET lock_wait_timeout = 1;
+
+# Should still be able to select
+
+--sorted_result
+SELECT a,b FROM t1 WHERE b='a';
+
+# ... but not with LOCK IN SHARE MODE
+
+--sorted_result
+--error ER_LOCK_WAIT_TIMEOUT
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET b='c' WHERE b='a';
+
+--connection con1
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+--disconnect con1
+--connection default
+# Now it can be updated all right
+UPDATE t1 SET b='c' WHERE b='a';
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+--source include/wait_until_count_sessions.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/select_for_update_skip_locked_nowait.test b/storage/rocksdb/mysql-test/rocksdb/t/select_for_update_skip_locked_nowait.test
new file mode 100644
index 00000000..c6ebbfa3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/select_for_update_skip_locked_nowait.test
@@ -0,0 +1,48 @@
+--source include/have_rocksdb.inc
+
+##############################################################################
+## SKIP LOCKED | NOWAIT are *not* supported for SELECT...FOR UPDATE in RocksDB
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+create table t1 (a int primary key) engine=rocksdb;
+
+insert into t1 values (1), (2), (3);
+
+### SKIP LOCKED
+
+--echo Should succeed since no table gets involved
+select 1 for update skip locked;
+
+--error ER_NO_SUCH_TABLE
+select * from nonexistence for update skip locked;
+
+--error ER_ILLEGAL_HA
+select * from t1 for update skip locked;
+
+--error ER_ILLEGAL_HA
+select * from t1 where a > 1 and a < 3 for update skip locked;
+
+--error ER_ILLEGAL_HA
+insert into t1 select * from t1 for update skip locked;
+
+### NOWAIT
+
+--echo Should succeed since no table gets involved
+select 1 for update nowait;
+
+--error ER_NO_SUCH_TABLE
+select * from nonexistence for update nowait;
+
+--error ER_ILLEGAL_HA
+select * from t1 for update nowait;
+
+--error ER_ILLEGAL_HA
+select * from t1 where a > 1 and a < 3 for update nowait;
+
+--error ER_ILLEGAL_HA
+insert into t1 select * from t1 for update nowait;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/select_lock_in_share_mode.test b/storage/rocksdb/mysql-test/rocksdb/t/select_lock_in_share_mode.test
new file mode 100644
index 00000000..23ce6d45
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/select_lock_in_share_mode.test
@@ -0,0 +1,54 @@
+--source include/have_rocksdb.inc
+
+#
+# SELECT .. LOCK IN SHARE MODE
+#
+# If the engine has its own lock timeouts,
+# it makes sense to set them to minimum to decrease
+# the duration of the test.
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--enable_connect_log
+
+--source include/count_sessions.inc
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'a');
+
+--connect (con1,localhost,root,,)
+BEGIN;
+--sorted_result
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+
+--connection default
+SET lock_wait_timeout = 1;
+
+# Should still be able to select
+
+--sorted_result
+SELECT a,b FROM t1 WHERE b='a';
+--sorted_result
+SELECT a,b FROM t1 WHERE b='a' LOCK IN SHARE MODE;
+
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET b='c' WHERE b='a';
+
+--connection con1
+COMMIT;
+--sorted_result
+SELECT a,b FROM t1;
+
+--disconnect con1
+--connection default
+# Now it can be updated all right
+UPDATE t1 SET b='c' WHERE b='a';
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+--source include/wait_until_count_sessions.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/set_checkpoint.inc b/storage/rocksdb/mysql-test/rocksdb/t/set_checkpoint.inc
new file mode 100644
index 00000000..aae9db6c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/set_checkpoint.inc
@@ -0,0 +1,30 @@
+# Usage:
+# let $checkpoint = <value to set the checkpoint>;
+# let $succeeds = <1 if checkpoint creation should succeed, 0 otherwise>;
+# --source set_checkpoint.inc
+
+
+if ($succeeds)
+{
+ # Create checkpoint
+ --replace_result '$checkpoint' [CHECKPOINT]
+ eval SET GLOBAL ROCKSDB_CREATE_CHECKPOINT = '$checkpoint';
+
+ # Check checkpoint
+ --list_files $checkpoint CURRENT
+
+ # Cleanup
+ --remove_files_wildcard $checkpoint *
+ --rmdir $checkpoint
+ --disable_abort_on_error
+ --enable_abort_on_error
+}
+if (!$succeeds)
+{
+ --disable_result_log
+ --disable_query_log
+ --error ER_GET_ERRMSG
+ eval SET GLOBAL ROCKSDB_CREATE_CHECKPOINT = '$checkpoint';
+ --enable_query_log
+ --enable_result_log
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/show_engine.test b/storage/rocksdb/mysql-test/rocksdb/t/show_engine.test
new file mode 100644
index 00000000..ccca197d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/show_engine.test
@@ -0,0 +1,103 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--source include/restart_mysqld.inc
+
+#
+# SHOW ENGINE STATUS command
+# Checking that the command doesn't produce an error.
+# If it starts producing an actual result, the result file
+# will need to be updated, and possibly masked.
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+DROP TABLE IF EXISTS t4;
+--enable_warnings
+
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+CREATE TABLE t2 (j INT, PRIMARY KEY (j) COMMENT 'rev:cf_t2') ENGINE = ROCKSDB;
+CREATE TABLE t3 (k INT, PRIMARY KEY (k) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+
+# With partition based column family creation we now expect all the partitions
+# to belong to a default column family because mapping wasn't specified in
+# this case.
+CREATE TABLE t4 (l INT, PRIMARY KEY (l) COMMENT 'cf_t4') ENGINE = ROCKSDB
+ PARTITION BY KEY(l) PARTITIONS 4;
+
+SET @save.rocksdb_max_background_jobs= @@global.rocksdb_max_background_jobs;
+SET GLOBAL rocksdb_max_background_jobs= 1;
+
+INSERT INTO t1 VALUES (1), (2), (3);
+SELECT COUNT(*) FROM t1;
+
+INSERT INTO t2 VALUES (1), (2), (3), (4);
+SELECT COUNT(*) FROM t2;
+
+INSERT INTO t4 VALUES (1), (2), (3), (4), (5);
+SELECT COUNT(*) FROM t4;
+
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+SET GLOBAL rocksdb_compact_cf="cf_t1";
+--replace_column 2 # 3 #
+SHOW ENGINE rocksdb STATUS;
+
+# Fetch data from information schema as well
+--replace_column 3 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_CFSTATS;
+
+--replace_column 2 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_DBSTATS;
+
+SELECT TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME, COUNT(STAT_TYPE)
+FROM INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT
+WHERE TABLE_SCHEMA = 'test'
+GROUP BY TABLE_NAME, PARTITION_NAME;
+
+--replace_column 3 #
+SELECT * FROM INFORMATION_SCHEMA.ROCKSDB_CF_OPTIONS;
+
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+DROP TABLE t4;
+
+SHOW ENGINE rocksdb MUTEX;
+# For SHOW ALL MUTEX even the number of lines is volatile, so the result logging is disabled
+--disable_result_log
+SHOW ENGINE ALL MUTEX;
+--enable_result_log
+
+# The output from SHOW ENGINE ROCKSDB TRANSACTION STATUS has some
+# non-deterministic results. Replace the timestamp with 'TIMESTAMP', the
+# number of seconds active with 'NUM', the thread id with 'TID' and the thread
+# pointer with 'PTR'. This test may fail in the future if it is being run in
+# parallel with other tests as the number of snapshots would then be greater
+# than expected. We may need to turn off the result log if that is the case.
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+
+#select sleep(10);
+--replace_regex /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/TIMESTAMP/ /(ACTIVE) [0-9]+ /\1 NUM / /(thread id) [0-9]+/\1 TID/ /0x[0-9a-f]+/PTR/ /(query id) [0-9]+/\1 QID/ /(root) [a-z ]+/\1 ACTION/
+SHOW ENGINE rocksdb TRANSACTION STATUS;
+
+ROLLBACK;
+
+# Check if explicit snapshots are correctly populated
+START TRANSACTION WITH SHARED ROCKSDB SNAPSHOT;
+--replace_column 2 # 3 #
+SHOW ENGINE rocksdb STATUS;
+ROLLBACK;
+CREATE EXPLICIT rocksdb SNAPSHOT;
+--replace_column 2 # 3 #
+SHOW ENGINE rocksdb STATUS;
+RELEASE EXPLICIT rocksdb SNAPSHOT;
+--replace_column 2 # 3 #
+SHOW ENGINE rocksdb STATUS;
+
+
+# Restore old values
+SET GLOBAL rocksdb_max_background_jobs= @save.rocksdb_max_background_jobs;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/show_table_status-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/show_table_status-master.opt
new file mode 100644
index 00000000..843f7012
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/show_table_status-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_debug_optimizer_n_rows=1000
+--rocksdb_table_stats_sampling_pct=100
+--userstat=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/show_table_status.test b/storage/rocksdb/mysql-test/rocksdb/t/show_table_status.test
new file mode 100644
index 00000000..55854710
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/show_table_status.test
@@ -0,0 +1,175 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+--source include/not_windows.inc # decorated database names is too long, exceeded OS limits
+
+#
+# SHOW TABLE STATUS statement
+#
+
+###################################
+# TODO:
+# The result file is likely to change
+# if MDEV-4197 is fixed
+###################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2, t3;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (100,'a'),(2,'foo');
+
+CREATE TABLE t2 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t2 (a,b) VALUES (1,'bar');
+
+set global rocksdb_force_flush_memtable_now = true;
+
+CREATE TABLE t3 (a INT, b CHAR(8), pk INT PRIMARY KEY) ENGINE=rocksdb CHARACTER SET utf8;
+
+--replace_column 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS WHERE name IN ( 't1', 't2', 't3' );
+
+# Some statistics don't get updated as quickly. The Data_length and
+# Avg_row_length are trailing statistics, meaning they don't get updated
+# for the current SST until the next SST is written. Insert a bunch of data,
+# then flush, then insert a bit more and do another flush to get them to show
+# up.
+
+--disable_query_log
+let $count = 2;
+let $max = 10000;
+while ($count < $max) {
+ eval INSERT INTO t2 (a) VALUES ($count);
+ inc $count;
+}
+
+set global rocksdb_force_flush_memtable_now = true;
+eval INSERT INTO t2 (a) VALUES ($max);
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
+
+# We expect the number of rows to be 10000. Data_len and Avg_row_len
+# may vary, depending on built-in compression library.
+--replace_column 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS WHERE name LIKE 't2';
+DROP TABLE t1, t2, t3;
+
+#
+# Confirm that long db and table names work.
+#
+
+CREATE DATABASE `db_new..............................................end`;
+USE `db_new..............................................end`;
+CREATE TABLE `t1_new..............................................end`(a int) engine=rocksdb;
+INSERT INTO `t1_new..............................................end` VALUES (1);
+--query_vertical SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.table_statistics WHERE TABLE_NAME = 't1_new..............................................end'
+DROP DATABASE `db_new..............................................end`;
+--echo #
+--echo # MDEV-17171: Bug: RocksDB Tables do not have "Creation Date"
+--echo #
+use test;
+create table t1 (a int) engine=rocksdb;
+
+select create_time is not null, update_time, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+
+insert into t1 values (1);
+select create_time is not null, update_time is not null, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+
+flush tables;
+select create_time is not null, update_time is not null, check_time
+from information_schema.tables where table_schema=database() and table_name='t1';
+
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+select sleep(3);
+insert into t1 values (2);
+
+--vertical_results
+select
+ create_time=@create_tm /* should not change */ ,
+ timestampdiff(second, @update_tm, update_time) > 2,
+ check_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+--echo #
+--echo # Check how create_time survives ALTER TABLE.
+--echo # First, an ALTER TABLE that re-creates the table:
+alter table t1 add b int;
+select
+ create_time<>@create_tm /* should change */,
+ create_time IS NOT NULL,
+ update_time IS NULL
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+insert into t1 values (5,5);
+
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+--echo # Then, an in-place ALTER TABLE:
+select sleep(2);
+alter table t1 add key (a);
+
+--echo # create_time will change as .frm file is rewritten:
+select
+ create_time=@create_tm,
+ update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+--echo # Check TRUNCATE TABLE
+insert into t1 values (10,10);
+select create_time, update_time into @create_tm, @update_tm
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+select sleep(2);
+truncate table t1;
+
+select
+ create_time=@create_tm /* should not change */,
+ update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+
+--echo #
+--echo # Check what is left after server restart
+--echo #
+drop table t1;
+create table t1 (a int);
+insert into t1 values (1);
+--echo # Save t1's creation time
+create table t2 as
+select create_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+select sleep(2);
+--source include/restart_mysqld.inc
+
+select
+ create_time=(select create_time from t2) /* should not change */,
+ update_time
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+
+drop table t1, t2;
+
+--echo #
+--echo # Check how it works for partitioned tables
+--echo #
+create table t1 (pk int primary key) partition by hash(pk) partitions 2;
+insert into t1 values (1);
+
+select create_time IS NOT NULL , update_time IS NOT NULL
+from information_schema.tables
+where table_schema=database() and table_name='t1';
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/shutdown-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/shutdown-master.opt
new file mode 100644
index 00000000..d6c7939e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/shutdown-master.opt
@@ -0,0 +1 @@
+--log-bin --binlog_format=row --rocksdb_default_cf_options=write_buffer_size=64k
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/shutdown.test b/storage/rocksdb/mysql-test/rocksdb/t/shutdown.test
new file mode 100644
index 00000000..f76bc9f6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/shutdown.test
@@ -0,0 +1,36 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Ensure bin log is enabled.
+SHOW GLOBAL VARIABLES LIKE "log_bin";
+
+# Create the table and insert some keys
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 1000;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+# Restart the server
+let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect;
+--exec echo "wait" > $restart_file
+--shutdown_server
+--source include/wait_until_disconnected.inc
+-- exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+-- enable_reconnect
+-- source include/wait_until_connected_again.inc
+
+# Verify table has correct rows
+SELECT COUNT(*) FROM t1;
+
+#cleanup
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/singledelete-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/singledelete-master.opt
new file mode 100644
index 00000000..a3d2d07e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/singledelete-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=write_buffer_size=16k --rocksdb_strict_collation_check=0
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/singledelete.test b/storage/rocksdb/mysql-test/rocksdb/t/singledelete.test
new file mode 100644
index 00000000..5a9d17e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/singledelete.test
@@ -0,0 +1,105 @@
+--source include/have_rocksdb.inc
+
+# only SingleDelete increases
+CREATE TABLE t1 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t1 SET value=value+1 WHERE value=$i;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+optimize table t1;
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+
+
+# both SingleDelete and Delete increases
+CREATE TABLE t2 (id INT, value int, PRIMARY KEY (id), INDEX (value)) ENGINE=RocksDB;
+INSERT INTO t2 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t2 SET id=id+1 WHERE id=$i;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+optimize table t2;
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select case when variable_value-@d > 9000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+
+# only Delete increases
+CREATE TABLE t3 (id INT, value int, PRIMARY KEY (id)) ENGINE=RocksDB;
+INSERT INTO t3 VALUES (1,1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t3 SET id=id+1 WHERE id=$i;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+optimize table t3;
+select case when variable_value-@s = 0 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select case when variable_value-@d > 9000 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+
+# only SingleDelete increases
+CREATE TABLE t4 (id INT, PRIMARY KEY (id)) ENGINE=RocksDB;
+INSERT INTO t4 VALUES (1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t4 SET id=id+1 WHERE id=$i;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+optimize table t4;
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+
+# only SingleDelete increases
+CREATE TABLE t5 (id1 INT, id2 INT, PRIMARY KEY (id1, id2), INDEX(id2)) ENGINE=RocksDB;
+INSERT INTO t5 VALUES (1, 1);
+select variable_value into @s from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+--disable_query_log
+let $i = 1;
+while ($i <= 10000) {
+ let $update = UPDATE t5 SET id1=id1+1 WHERE id1=$i;
+ inc $i;
+ eval $update;
+}
+--enable_query_log
+optimize table t5;
+select case when variable_value-@s > 5 and variable_value-@s < 100 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_singledelete';
+select case when variable_value-@d < 10 then 'true' else 'false' end from information_schema.global_status where variable_name='rocksdb_number_sst_entry_delete';
+
+# SingleDelete used for PK. Verify old PK is always deleted.
+CREATE TABLE t6 (
+ pk VARCHAR(64) COLLATE latin1_swedish_ci PRIMARY KEY
+) ENGINE=RocksDB;
+INSERT INTO t6 VALUES ('a');
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+SELECT * FROM t6;
+UPDATE t6 SET pk='A' WHERE pk='a';
+SELECT * FROM t6;
+DELETE FROM t6 where pk='A';
+--echo SELECT should return nothing;
+SELECT * FROM t6;
+SET GLOBAL rocksdb_force_flush_memtable_now=1;
+--echo SELECT should return nothing;
+SELECT * FROM t6;
+
+DROP TABLE t1, t2, t3, t4, t5, t6;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error-master.opt
new file mode 100644
index 00000000..c07b063f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error-master.opt
@@ -0,0 +1 @@
+--rocksdb_strict_collation_check=off --binlog_format=row --log-bin
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error.test b/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error.test
new file mode 100644
index 00000000..451eed05
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/skip_core_dump_on_error.test
@@ -0,0 +1,53 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+--source include/not_valgrind.inc
+
+--enable_connect_log
+--enable_info
+
+# setup search pattern and file (new log error file)
+--let SEARCH_FILE=$MYSQLTEST_VARDIR/tmp/skip_core_dump_on_error.err
+
+# restart the server with the custom error log file
+--let $_mysqld_option=--log-error=$SEARCH_FILE --default-storage-engine=rocksdb
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_option.inc
+
+# setup
+create table mz(c int);
+insert into mz values(1);
+commit;
+
+# simulate a write error
+SET debug= '+d,abort_with_io_write_error';
+
+# we want to abort server if we fail to write (ABORT_SERVER)
+set global binlog_error_action=1;
+
+# diplay the values of the key parameters
+show session variables like 'debug';
+show global variables like 'binlog_error_action';
+show global variables like 'skip_core_dump_on_error';
+
+--echo # crash_during_update
+# tell client that crash is expected
+--error 1598
+# run an update to trigger a write error
+update mz set c=13;
+
+# should find server abort (prints: Pattern "..." found)
+--echo # server aborted
+--let SEARCH_PATTERN=mysqld got signal 6
+--source include/search_pattern.inc
+
+# should not find a core dump (prints: Pattern "..." not found)
+--echo # but no core written
+--let SEARCH_PATTERN=Writing a core file
+--source include/search_pattern.inc
+
+--let _$mysqld_option=
+--source include/start_mysqld.inc
+--remove_file $SEARCH_FILE
+
+# tidy up
+drop table mz;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/skip_validate_tmp_table.test b/storage/rocksdb/mysql-test/rocksdb/t/skip_validate_tmp_table.test
new file mode 100644
index 00000000..c4321462
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/skip_validate_tmp_table.test
@@ -0,0 +1,39 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+--let $_server_id= `SELECT @@server_id`
+
+create table t1 (pk int primary key) engine=rocksdb;
+
+# Create a .frm file without a matching table
+--exec cp $MYSQLTEST_VARDIR/mysqld.$_server_id/data/test/t1.frm $MYSQLTEST_VARDIR/mysqld.$_server_id/data/test/t1#sql-test.frm
+
+--source include/restart_mysqld.inc
+
+show tables;
+
+# MariaDB produces a warning:
+call mtr.add_suppression('Invalid .old.. table or database name .t1#sql-test.');
+
+# This will append '#sql-test' to the end of new name
+set session debug_dbug="+d,gen_sql_table_name";
+rename table t1 to t2;
+set session debug_dbug= "-d,gen_sql_table_name";
+
+show tables;
+
+# Remove the corresponding .frm files
+--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.$_server_id/data/test *t1*.frm
+--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.$_server_id/data/test *t2*.frm
+
+# Restart the server with a table registered in RocksDB but does not have a .frm file
+--source include/restart_mysqld.inc
+
+show tables;
+
+# try to recreate a table with the same name
+create table t2 (pk int primary key) engine=rocksdb;
+
+show tables;
+
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log-master.opt
new file mode 100644
index 00000000..fc5c3ed4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log-master.opt
@@ -0,0 +1 @@
+--log-slow-extra --rocksdb-perf-context-level=2
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log.test b/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log.test
new file mode 100644
index 00000000..9f1694ab
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/slow_query_log.test
@@ -0,0 +1,37 @@
+--source include/have_rocksdb.inc
+#Unixism (exec awk)
+-- source include/not_windows.inc
+
+SET @cur_long_query_time = @@long_query_time;
+# Set the long query time to something big so that nothing unexpected gets into it
+SET @@long_query_time = 600;
+# Test the slow query log feature
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (id INT PRIMARY KEY, value INT) ENGINE=ROCKSDB;
+
+--disable_query_log
+let $max = 10000;
+let $i = 1;
+while ($i < $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i);
+ inc $i;
+ eval $insert;
+}
+
+DELETE FROM t1 WHERE id < 2500;
+--enable_query_log
+
+SET @@long_query_time = 0;
+# we expect this query to be reflected in the slow query log
+SELECT COUNT(*) FROM t1;
+
+SET @@long_query_time = @cur_long_query_time;
+
+# Verify the output of the slow query log contains counts for the skipped keys
+--exec awk -f suite/rocksdb/slow_query_log.awk $MYSQLTEST_VARDIR/mysqld.1/mysqld-slow.log
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/statistics-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/statistics-master.opt
new file mode 100644
index 00000000..8a56deb0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/statistics-master.opt
@@ -0,0 +1,3 @@
+--rocksdb_default_cf_options=max_write_buffer_number_to_maintain=10
+--rocksdb_debug_optimizer_n_rows=1000
+--rocksdb_table_stats_sampling_pct=100
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/statistics.test b/storage/rocksdb/mysql-test/rocksdb/t/statistics.test
new file mode 100644
index 00000000..25a1224c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/statistics.test
@@ -0,0 +1,82 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+DROP TABLE IF EXISTS t2;
+DROP TABLE IF EXISTS t3;
+--enable_warnings
+
+# table with index in default CF
+create table t1(
+ id bigint not null primary key auto_increment,
+ a varchar(255) not null,
+ b bigint,
+ index t1_1(b)
+) engine=rocksdb;
+
+# a table with index in a different CF
+create table t2(
+ id bigint not null primary key auto_increment,
+ a varchar(255) not null,
+ b bigint,
+ index t2_1(b) comment 'cf_t3'
+) engine=rocksdb;
+
+# a table wint index in a reverse CF
+create table t3(
+ id bigint not null primary key auto_increment,
+ a varchar(255) not null,
+ b bigint,
+ index t3_1(b) comment 'rev:cf_t4'
+) engine=rocksdb;
+
+--disable_query_log
+let $i=0;
+while ($i<100000)
+{
+ inc $i;
+ eval insert t1(a,b) values(concat('a',$i,'b',$i,'c',$i), $i);
+ if ($i<5000)
+ {
+ eval insert t2(a,b) values(concat('a',$i,'b',$i,'c',$i), $i);
+ eval insert t3(a,b) values(concat('a',$i,'b',$i,'c',$i), $i);
+ }
+}
+--enable_query_log
+
+# should have some statistics before the memtable flush
+--sorted_result
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE() and table_name <> 't1';
+
+# due to inconsistencies in when the memtable is flushed, just verify t1 has fewer
+# than the expected number of rows.
+--sorted_result
+SELECT CASE WHEN table_rows < 100000 then 'true' else 'false' end from information_schema.tables where table_name = 't1';
+
+# flush and get even better statistics
+set global rocksdb_force_flush_memtable_now = true;
+--sorted_result
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+--sorted_result
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+
+# restart the server, check the stats
+--source include/restart_mysqld.inc
+
+# give the server a chance to load in statistics
+--sleep 5
+
+--sorted_result
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+--sorted_result
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+
+analyze table t1,t2,t3,t4,t5;
+
+# make sure that stats do not change after calling analyze table
+--sorted_result
+SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();
+--sorted_result
+SELECT table_name, data_length>0, index_length>0 FROM information_schema.tables WHERE table_schema = DATABASE();
+
+drop table t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/table_stats-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/table_stats-master.opt
new file mode 100644
index 00000000..be8a06ea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/table_stats-master.opt
@@ -0,0 +1 @@
+--userstat
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/table_stats.test b/storage/rocksdb/mysql-test/rocksdb/t/table_stats.test
new file mode 100644
index 00000000..3eb58098
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/table_stats.test
@@ -0,0 +1,29 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Create the table and insert some keys
+CREATE TABLE t1 (i INT, PRIMARY KEY (i) COMMENT 'cf_t1') ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 1000;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+# Verify table has correct rows
+SELECT COUNT(*) FROM t1;
+
+# Verify the table stats are returned
+--vertical_results
+SELECT * FROM INFORMATION_SCHEMA.TABLE_STATISTICS WHERE TABLE_NAME = "t1";
+--horizontal_results
+
+#cleanup
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_ai.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_ai.test
new file mode 100644
index 00000000..8fb4539b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_ai.test
@@ -0,0 +1,29 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether AUTO_INCREMENT option
+# is supported in CREATE and ALTER TABLE
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb AUTO_INCREMENT=10;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1;
+
+ALTER TABLE t1 AUTO_INCREMENT=100;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1 ORDER BY a;
+
+ALTER TABLE t1 AUTO_INCREMENT=50;
+SHOW CREATE TABLE t1;
+INSERT INTO t1 VALUES (NULL);
+SELECT * FROM t1 ORDER BY a;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_avg_row_length.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_avg_row_length.test
new file mode 100644
index 00000000..3e6797a8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_avg_row_length.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether AVG_ROW_LENGTH option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb AVG_ROW_LENGTH=300;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 AVG_ROW_LENGTH=30000000;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_checksum.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_checksum.test
new file mode 100644
index 00000000..3b49b967
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_checksum.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether CHECKSUM option is supported
+# in CREATE and ALTER TABLE.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CHECKSUM=1;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 CHECKSUM=0;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_connection.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_connection.test
new file mode 100644
index 00000000..b97b3dd9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_connection.test
@@ -0,0 +1,32 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether CONNECTION option is supported
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE DATABASE test_remote;
+CREATE SERVER test_connection FOREIGN DATA WRAPPER mysql
+OPTIONS (USER 'root', HOST 'localhost', DATABASE 'test_remote');
+CREATE SERVER test_connection2 FOREIGN DATA WRAPPER mysql
+OPTIONS (USER 'root', HOST 'localhost', DATABASE 'test_remote');
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb CONNECTION='test_connection';
+SHOW CREATE TABLE t1;
+ALTER TABLE t1 CONNECTION='test_connection2';
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
+DROP SERVER test_connection;
+DROP SERVER test_connection2;
+DROP DATABASE test_remote;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_data_index_dir.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_data_index_dir.test
new file mode 100644
index 00000000..6fcfd491
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_data_index_dir.test
@@ -0,0 +1,60 @@
+--source include/have_rocksdb.inc
+
+--source include/have_partition.inc
+--source include/not_windows.inc
+
+#
+# Check that when either DATA DIRECTORY or INDEX DIRECTORY are specified
+# then MyRocks returns an appropriate error. We don't support this
+# functionality and therefore shouldn't just silently accept the values.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# On a Mac, strerror() prints "Unknown error: nn", as
+# opposed to "Unknown error nn" on Linux/etc.
+# Replacing 'error:' with 'error' below to make the output uniform.
+
+--replace_result error: error
+--error ER_CANT_CREATE_TABLE
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb DATA DIRECTORY = '/foo/bar/data';
+--replace_result error: error
+show warnings;
+
+--replace_result error: error
+--error ER_CANT_CREATE_TABLE
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb INDEX DIRECTORY = '/foo/bar/index';
+--replace_result error: error
+show warnings;
+
+#
+# Verify that we'll get the same error codes when using the partitions.
+#
+
+--replace_result error: error
+--error ER_CANT_CREATE_TABLE
+CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY) ENGINE=rocksdb PARTITION BY RANGE (id)
+(
+ PARTITION P0 VALUES LESS THAN (1000)
+ DATA DIRECTORY = '/foo/bar/data/',
+ PARTITION P1 VALUES LESS THAN (2000)
+ DATA DIRECTORY = '/foo/bar/data/',
+ PARTITION P2 VALUES LESS THAN (MAXVALUE)
+);
+--replace_result error: error
+show warnings;
+
+--replace_result error: error
+--error ER_CANT_CREATE_TABLE
+CREATE TABLE t1 (id int not null primary key) ENGINE=rocksdb PARTITION BY RANGE (id)
+(
+ PARTITION P0 VALUES LESS THAN (1000)
+ INDEX DIRECTORY = '/foo/bar/data/',
+ PARTITION P1 VALUES LESS THAN (2000)
+ INDEX DIRECTORY = '/foo/bar/data/',
+ PARTITION P2 VALUES LESS THAN (MAXVALUE)
+);
+--replace_result error: error
+show warnings;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_delay_key_write.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_delay_key_write.test
new file mode 100644
index 00000000..85cd45e9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_delay_key_write.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether DELAY_KEY_WRITE option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb DELAY_KEY_WRITE=1;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 DELAY_KEY_WRITE=0;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_insert_method.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_insert_method.test
new file mode 100644
index 00000000..e289827a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_insert_method.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether INSERT_METHOD option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted (and apparently ignored)
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb INSERT_METHOD=FIRST;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 INSERT_METHOD=NO;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_key_block_size.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_key_block_size.test
new file mode 100644
index 00000000..d927c785
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_key_block_size.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether KEY_BLOCK_SIZE option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb KEY_BLOCK_SIZE=8;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 KEY_BLOCK_SIZE=1;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_max_rows.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_max_rows.test
new file mode 100644
index 00000000..35aa0f4d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_max_rows.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether MAX_ROWS option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb MAX_ROWS=10000000;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 MAX_ROWS=30000000;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_min_rows.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_min_rows.test
new file mode 100644
index 00000000..d62a8771
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_min_rows.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether MIN_ROWS option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb MIN_ROWS=1;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 MIN_ROWS=10000;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_pack_keys.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_pack_keys.test
new file mode 100644
index 00000000..acdb612b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_pack_keys.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether PACK KEYS option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb PACK_KEYS=1;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 PACK_KEYS=0;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_password.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_password.test
new file mode 100644
index 00000000..e897992e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_password.test
@@ -0,0 +1,27 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether PASSWORD option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+# This option is not supported by any known engines,
+# that's why the result file does not contain it;
+# but it's syntactically acceptable.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb PASSWORD='password';
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 PASSWORD='new_password';
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_row_format.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_row_format.test
new file mode 100644
index 00000000..de834d23
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_row_format.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether ROW_FORMAT option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8) PRIMARY KEY) ENGINE=rocksdb ROW_FORMAT=FIXED;
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 ROW_FORMAT=DYNAMIC;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_union.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_union.test
new file mode 100644
index 00000000..d3c371b1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_opt_union.test
@@ -0,0 +1,28 @@
+--source include/have_rocksdb.inc
+
+#
+# Check whether UNION option
+# is supported in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the option
+# has any real effect on the table, only
+# that it's accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, child1, child2;
+--enable_warnings
+
+--disable_query_log
+CREATE TABLE child1 (a INT PRIMARY KEY) ENGINE=MyISAM;
+CREATE TABLE child2 (a INT PRIMARY KEY) ENGINE=MyISAM;
+--enable_query_log
+
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb UNION(child1);
+SHOW CREATE TABLE t1;
+
+ALTER TABLE t1 UNION = (child1,child2);
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1, child1, child2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tbl_standard_opts.test b/storage/rocksdb/mysql-test/rocksdb/t/tbl_standard_opts.test
new file mode 100644
index 00000000..5d60c02a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tbl_standard_opts.test
@@ -0,0 +1,42 @@
+--source include/have_rocksdb.inc
+
+#
+# Standard options in CREATE and ALTER TABLE
+#
+# Note: the test does not check whether the options
+# have any real effect on the table, only
+# that they are accepted
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Create table with standard options
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb
+ DEFAULT CHARACTER SET = utf8
+ COLLATE = utf8_general_ci
+ COMMENT = 'standard table options'
+;
+SHOW CREATE TABLE t1;
+
+# Alter comment
+
+ALTER TABLE t1 COMMENT = 'table altered';
+SHOW CREATE TABLE t1;
+
+# Alter ENGINE value
+
+ALTER TABLE t1 ENGINE=MEMORY;
+SHOW CREATE TABLE t1;
+ALTER TABLE t1 ENGINE=rocksdb;
+SHOW CREATE TABLE t1;
+
+# Alter character set and collation
+
+ALTER TABLE t1 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/tmpdir.test b/storage/rocksdb/mysql-test/rocksdb/t/tmpdir.test
new file mode 100644
index 00000000..28615264
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/tmpdir.test
@@ -0,0 +1,35 @@
+--source include/have_rocksdb.inc
+
+--echo # If rocksdb_tmpdir is NULL or "", temporary file will be created in
+--echo # server configuration variable location(--tmpdir)
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+show session variables like 'rocksdb_tmpdir';
+--echo # Connection con1
+connect (con1,localhost,root);
+show session variables like 'rocksdb_tmpdir';
+
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+connection default;
+disconnect con1;
+DROP TABLE t1;
+
+--echo # rocksdb_tmpdir with valid location.
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+set @tmpdir = @@global.tmpdir;
+set global rocksdb_tmpdir = @tmpdir;
+show session variables like 'rocksdb_tmpdir';
+
+--echo # Connection con3
+connect (con2,localhost,root);
+--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR
+show session variables like 'rocksdb_tmpdir';
+
+ALTER TABLE t1 ADD INDEX kb(b), ALGORITHM=INPLACE;
+disconnect con2;
+connection default;
+set global rocksdb_tmpdir=NULL;
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/transaction.test b/storage/rocksdb/mysql-test/rocksdb/t/transaction.test
new file mode 100644
index 00000000..129484ba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/transaction.test
@@ -0,0 +1,158 @@
+--source include/have_rocksdb.inc
+
+create table t1 (id int primary key, value int, value2 varchar(100), index(value)) engine=rocksdb;
+
+insert into t1 values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5),(6,6,6),(8,8,8),(9,9,9),(10,10,10);
+
+# insert
+begin;
+insert into t1 values (11,11,11);
+--source transaction_select.inc
+rollback;
+
+# insert in the middle
+begin;
+insert into t1 values (7,7,7);
+--source transaction_select.inc
+rollback;
+
+# update non-index column by primary key
+begin;
+update t1 set value2=100 where id=1;
+--source transaction_select.inc
+rollback;
+
+# update secondary key by primary key
+begin;
+update t1 set value=100 where id=1;
+--source transaction_select.inc
+rollback;
+
+# update primary key by primary key
+begin;
+update t1 set id=100 where id=1;
+--source transaction_select.inc
+rollback;
+
+# update non-index column key by secondary key
+begin;
+update t1 set value2=100 where value=1;
+--source transaction_select.inc
+rollback;
+
+# update secondary key by secondary key
+begin;
+update t1 set value=100 where value=1;
+--source transaction_select.inc
+rollback;
+
+# update primary key by secondary key
+begin;
+update t1 set id=100 where value=1;
+--source transaction_select.inc
+rollback;
+
+# update non-index column by non-index column
+begin;
+update t1 set value2=100 where value2=1;
+--source transaction_select.inc
+rollback;
+
+# update secondary key by non-index column
+begin;
+update t1 set value=100 where value2=1;
+--source transaction_select.inc
+rollback;
+
+# update primary key column by non-index column
+begin;
+update t1 set id=100 where value2=1;
+--source transaction_select.inc
+rollback;
+
+
+# delete by primary key
+begin;
+delete from t1 where id=1;
+--source transaction_select.inc
+rollback;
+
+# delete by secondary key
+begin;
+delete from t1 where value=1;
+--source transaction_select.inc
+rollback;
+
+# delete by non-index column
+begin;
+delete from t1 where value2=1;
+--source transaction_select.inc
+rollback;
+
+# mixed
+begin;
+insert into t1 values (11,11,11);
+insert into t1 values (12,12,12);
+insert into t1 values (13,13,13);
+delete from t1 where id=9;
+delete from t1 where value=8;
+update t1 set id=100 where value2=5;
+update t1 set value=103 where value=4;
+update t1 set id=115 where id=3;
+--source transaction_select.inc
+rollback;
+
+drop table t1;
+
+--echo #
+--echo # #802: MyRocks: Statement rollback doesnt work correctly for nested statements
+--echo #
+create table t1 (a varchar(100)) engine=rocksdb;
+create table t2(a int) engine=rocksdb;
+insert into t2 values (1), (2);
+
+create table t3(a varchar(100)) engine=rocksdb;
+
+delimiter //;
+create function func() returns varchar(100) deterministic
+begin
+ insert into t3 values ('func-called');
+ set @a= (select a from t2);
+ return 'func-returned';
+end;//
+delimiter ;//
+
+begin;
+--error ER_SUBQUERY_NO_1_ROW
+insert into t1 values (func());
+select * from t1;
+--echo # The following must not produce 'func-called':
+select * from t3;
+
+rollback;
+drop function func;
+drop table t1,t2,t3;
+
+--echo #
+--echo # MDEV-16710: Slave SQL: Could not execute Update_rows_v1 event with RocksDB and triggers
+--echo # Issue#857: MyRocks: Incorrect behavior when multiple statements fail inside a transaction
+--echo #
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=RocksDB;
+INSERT INTO t1 VALUES (1);
+
+CREATE TABLE t2 (b INT PRIMARY KEY) ENGINE=RocksDB;
+
+CREATE TRIGGER tr AFTER INSERT ON t2 FOR EACH ROW INSERT INTO non_existing_table VALUES (NULL);
+
+BEGIN;
+DELETE FROM t1;
+--error 0,ER_NO_SUCH_TABLE
+INSERT INTO t2 VALUES (1);
+--error 0,ER_NO_SUCH_TABLE
+INSERT INTO t2 VALUES (2);
+--echo # Must return empty result:
+SELECT * FROM t1;
+COMMIT;
+
+drop table t1,t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/transaction_isolation.inc b/storage/rocksdb/mysql-test/rocksdb/t/transaction_isolation.inc
new file mode 100644
index 00000000..dbd1d906
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/transaction_isolation.inc
@@ -0,0 +1,150 @@
+#
+# Basic check for transaction isolation.
+# The results should be different depending on the isolation level.
+# For some isolation levels, some statements will end with a timeout.
+# If the engine has its own timeout parameters, reduce them to minimum,
+# otherwise the test will take very long.
+# If the timeout value is greater than the testcase-timeout the test is run with,
+# it might fail due to the testcase timeout.
+#
+
+--enable_connect_log
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+connect (con2,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+
+connection con1;
+
+CREATE TABLE t1 (a INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+START TRANSACTION;
+--sorted_result
+SELECT a FROM t1; # First snapshot
+
+connection con2;
+
+BEGIN;
+--error 0,ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a) VALUES(1);
+
+connection con1;
+--sorted_result
+SELECT a FROM t1; # Second snapshot
+
+connection con2;
+--error 0,ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a) VALUES (2);
+
+connection con1;
+--sorted_result
+SELECT a FROM t1; # Third snapshot
+
+--error 0,ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a) SELECT a+100 FROM t1;
+
+--sorted_result
+SELECT a FROM t1;
+
+connection con2;
+--sorted_result
+SELECT a FROM t1; # Inside the transaction
+COMMIT;
+--sorted_result
+SELECT a FROM t1; # Outside the transaction
+
+connection con1;
+--sorted_result
+SELECT a FROM t1; # Inside the transaction
+
+# Note: INSERT .. SELECT might be tricky, for example for InnoDB
+# even with REPEATABLE-READ it works as if it is executed with READ COMMITTED.
+# The test will have a 'logical' result for repeatable read, even although
+# we currently don't have an engine which works this way.
+
+--error 0,ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 (a) SELECT a+200 FROM t1;
+
+--sorted_result
+SELECT a FROM t1;
+COMMIT;
+--sorted_result
+SELECT a FROM t1; # Outside the transaction
+
+connection con2;
+--sorted_result
+SELECT a FROM t1; # After both transactions have committed
+
+# Now test with an error in one statement to make sure the snapshots are
+# Held/released when expected
+connection default;
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (a) VALUES (1);
+COMMIT;
+
+connection con1;
+BEGIN;
+--sorted_result
+SELECT a from t2;
+--error ER_DUP_ENTRY
+INSERT INTO t2 (a) VALUES (1), (3); # failure
+
+connection con2;
+--error 0,ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t2 (a) VALUES (2);
+COMMIT;
+
+connection con1;
+--sorted_result
+SELECT a from t2;
+COMMIT;
+
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t1;
+DROP TABLE t2;
+
+CREATE TABLE t3 (
+ pk int unsigned PRIMARY KEY,
+ count int unsigned DEFAULT '0'
+) ENGINE=ROCKSDB;
+
+connect (con1,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+connect (con2,localhost,root,,);
+eval SET SESSION TRANSACTION ISOLATION LEVEL $trx_isolation;
+
+connection con1;
+BEGIN;
+SELECT * FROM t3;
+
+connection con2;
+BEGIN;
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+
+connection con1;
+--error 0,ER_LOCK_DEADLOCK
+INSERT INTO t3 (pk) VALUES(1) ON DUPLICATE KEY UPDATE count=count+1;
+COMMIT;
+
+# count will be 0 for repeatable read (because the last insert failed)
+# and 1 for read committed
+SELECT count FROM t3;
+
+connection default;
+disconnect con1;
+disconnect con2;
+DROP TABLE t3;
+
+--source include/wait_until_count_sessions.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/transaction_select.inc b/storage/rocksdb/mysql-test/rocksdb/t/transaction_select.inc
new file mode 100644
index 00000000..932a450e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/transaction_select.inc
@@ -0,0 +1,14 @@
+select * from t1 where id=1;
+select * from t1 where value=1;
+select value from t1 where value=1;
+select * from t1 where value2=1;
+select * from t1 where id=5;
+select * from t1 where value=5;
+select value from t1 where value=5;
+select * from t1 where value2=5;
+select * from t1 where id < 3 order by id;
+select * from t1 where value < 3 order by id;
+select value from t1 where value < 3 order by id;
+select * from t1 where value2 < 3 order by id;
+select * from t1 order by id;
+select value from t1 order by id;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.inc b/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.inc
new file mode 100644
index 00000000..2193aa9f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.inc
@@ -0,0 +1,102 @@
+
+# Truncate table multiple times
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+TRUNCATE TABLE t1;
+
+# Truncate partition multiple times
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+
+# TRUNCATE multiple times
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+
+# TRUNCATE multiple times
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+
+# TRUNCATE different partition
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+TRUNCATE TABLE t1;
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+
+# Insert value once and truncate multiple times
+INSERT INTO t1 (a,b) VALUES (1, 1), (2, 4), (3, 8);
+SELECT a,b FROM t1 ORDER BY a;
+
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+SELECT a,b FROM t1 ORDER BY a;
+SELECT a FROM t1 WHERE b > 2;
+SELECT b from t1 where a != 3;
+
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+SELECT a,b FROM t1 ORDER BY b;
+SELECT a FROM t1 WHERE b > 2;
+SELECT b from t1 where a != 3;
+
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+SELECT a,b FROM t1 ORDER BY a;
+
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+
+# Insert value multiple times and truncate multiple times
+INSERT INTO t1 (a,b) VALUES (4, 1), (5, 4), (6, 8);
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT a FROM t1 WHERE b < 5;
+
+ALTER TABLE t1 TRUNCATE PARTITION p2;
+--sorted_result
+SELECT a,b FROM t1;
+INSERT INTO t1(a,b) VALUES(7, 1);
+--sorted_result
+SELECT b from t1 WHERE a > 2;
+--sorted_result
+SELECT a,b FROM t1;
+
+ALTER TABLE t1 TRUNCATE PARTITION p1;
+--sorted_result
+SELECT a,b FROM t1;
+INSERT INTO t1(a,b) VALUES(8, 4);
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT b from t1 WHERE a < 9;
+
+ALTER TABLE t1 TRUNCATE PARTITION p0;
+--sorted_result
+SELECT a,b FROM t1;
+INSERT INTO t1(a,b) VALUES(9, 8);
+
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+
+# manual commpact
+set global rocksdb_force_flush_memtable_now = true;
+set global rocksdb_compact_cf = 'default';
+
+--disable_query_log
+let $i = 0;
+while($i < 9)
+{
+ inc $i;
+ eval insert t1 values($i, $i);
+}
+--enable_query_log
+--sorted_result
+SELECT b FROM t1 WHERE a < 5;
+
+TRUNCATE TABLE t1;
+SELECT b FROM t1 WHERE a < 5;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.test b/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.test
new file mode 100644
index 00000000..f9a89517
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/truncate_partition.test
@@ -0,0 +1,83 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# TRUNCATE PARTITION
+#
+
+
+# Hidden Key table
+--echo #
+--echo # table(hidden key)
+--echo #
+CREATE TABLE t1 (
+ a INT,
+ b INT
+) ENGINE=ROCKSDB
+PARTITION BY RANGE (b) (
+ PARTITION p0 VALUES LESS THAN (3),
+ PARTITION p1 VALUES LESS THAN (6),
+ PARTITION p2 VALUES LESS THAN MAXVALUE
+);
+
+--source truncate_partition.inc
+
+
+--echo #
+--echo # table(secondary key)
+--echo #
+CREATE TABLE t1(
+ a INT,
+ b INT,
+ KEY (b)
+) ENGINE=ROCKSDB
+PARTITION BY HASH(a) PARTITIONS 3;
+
+--source truncate_partition.inc
+
+
+--echo #
+--echo # table(primary key, auto increment)
+--echo #
+CREATE TABLE t1(
+ a INT NOT NULL AUTO_INCREMENT,
+ b INT,
+ PRIMARY KEY(a)
+) ENGINE=ROCKSDB
+PARTITION BY KEY() PARTITIONS 3;
+
+--source truncate_partition.inc
+
+
+--echo #
+--echo # table(cf)
+--echo #
+CREATE TABLE t1 (
+ a INT,
+ b INT,
+ PRIMARY KEY (`a`, `b`) COMMENT 'testcomment'
+) ENGINE=ROCKSDB
+ PARTITION BY LIST(a) (
+ PARTITION p0 VALUES IN (1, 4, 7),
+ PARTITION p1 VALUES IN (2, 5, 8),
+ PARTITION p2 VALUES IN (3, 6, 9)
+);
+
+--source truncate_partition.inc
+
+
+--echo #
+--echo # table(reverse cf)
+--echo #
+CREATE TABLE t1 (
+ a INT,
+ b INT,
+ PRIMARY KEY (`a`, `b`) COMMENT 'p0_cfname=rev:foo;p1_cfname=bar;p2_cfname=baz'
+) ENGINE=ROCKSDB
+PARTITION BY LIST(a) (
+ PARTITION p0 VALUES IN (1, 4, 7),
+ PARTITION p1 VALUES IN (2, 5, 8),
+ PARTITION p2 VALUES IN (3, 6, 9)
+);
+
+--source truncate_partition.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/truncate_table.test b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table.test
new file mode 100644
index 00000000..1001eeb6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table.test
@@ -0,0 +1,74 @@
+--source include/have_rocksdb.inc
+
+#
+# TRUNCATE TABLE
+#
+
+########################################
+# TODO:
+# A part of the test is disabled because
+# HANDLER is not supported. If it ever
+# changes, the test will complain about
+# NOT producing ER_ILLEGAL_HA
+########################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+TRUNCATE TABLE t1;
+INSERT INTO t1 (a,b) VALUES (1,'a'), (2,'b'), (3,'c');
+TRUNCATE TABLE t1;
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+
+# Truncate resets auto-increment value on the table
+
+CREATE TABLE t1 (a INT KEY AUTO_INCREMENT, c CHAR(8)) ENGINE=rocksdb;
+
+#--replace_column 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 # 14 # 15 # 16 # 17 # 18 #
+--replace_column 5 # 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+INSERT INTO t1 (c) VALUES ('a'),('b'),('c');
+#--replace_column 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 # 14 # 15 # 16 # 17 # 18 #
+--replace_column 5 # 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+TRUNCATE TABLE t1;
+#--replace_column 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 # 14 # 15 # 16 # 17 # 18 #
+--replace_column 5 # 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+INSERT INTO t1 (c) VALUES ('d');
+#--replace_column 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 # 10 # 12 # 13 # 14 # 15 # 16 # 17 # 18 #
+--replace_column 5 # 6 # 7 # 12 # 13 #
+SHOW TABLE STATUS LIKE 't1';
+
+--sorted_result
+SELECT a,c FROM t1;
+DROP TABLE t1;
+
+# Truncate closes handlers
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+
+--error ER_ILLEGAL_HA
+HANDLER t1 OPEN AS h1;
+
+--disable_parsing
+
+HANDLER h1 READ FIRST;
+TRUNCATE TABLE t1;
+--error ER_UNKNOWN_TABLE
+HANDLER h1 READ NEXT;
+HANDLER t1 OPEN AS h2;
+HANDLER h2 READ FIRST;
+
+--enable_parsing
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3-master.opt
new file mode 100644
index 00000000..a9ebc4ec
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_max_subcompactions=1
+--rocksdb_default_cf_options=write_buffer_size=16k;target_file_size_base=16k;level0_slowdown_writes_trigger=-1;level0_stop_writes_trigger=1000;compression_per_level=kNoCompression;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3.test b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3.test
new file mode 100644
index 00000000..b3f95f81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/truncate_table3.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+-- let $truncate_table = 1
+-- let $drop_table = 0
+-- source drop_table3.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/trx_info.test b/storage/rocksdb/mysql-test/rocksdb/t/trx_info.test
new file mode 100644
index 00000000..975bed61
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/trx_info.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+create table t1 (a int) engine=rocksdb;
+insert into t1 values (1);
+insert into t1 values (2);
+
+set autocommit=0;
+select * from t1 for update;
+
+--replace_column 1 _TRX_ID_ 3 _NAME_ 7 _KEY_ 14 _THREAD_ID_
+select * from information_schema.rocksdb_trx;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.cnf b/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.cnf
new file mode 100644
index 00000000..46771b5a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.cnf
@@ -0,0 +1,11 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+loose-enable-rocksdb_trx
+
+[mysqld.2]
+binlog_format=row
+slave_parallel_workers=1
+rpl_skip_tx_api=ON
+loose-enable-rocksdb_trx
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.test b/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.test
new file mode 100644
index 00000000..91ab266c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/trx_info_rpl.test
@@ -0,0 +1,44 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+connection slave;
+--source include/stop_slave.inc
+--enable_warnings
+
+connection master;
+create table t1 (a int, b int, primary key (a), unique key (b)) engine=rocksdb;
+--disable_query_log
+--let $aa= 0
+while ($aa < 1000) {
+ eval insert into t1 values ($aa, $aa);
+ --inc $aa
+}
+--enable_query_log
+
+connection slave;
+show variables like 'rpl_skip_tx_api';
+--source include/start_slave.inc
+
+--let $it=0
+--let $stop=0
+while ($stop != 1) {
+let $count= query_get_value(select count(*) as Value from information_schema.rocksdb_trx, Value, 1);
+ if ($count) {
+ --echo found
+ --let $stop=1
+ }
+
+ if ($it > 1000) {
+ --echo not found
+ --let $stop=1
+ }
+
+ --inc $it
+}
+
+connection master;
+DROP TABLE t1;
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary-master.opt
new file mode 100644
index 00000000..b991f718
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_enable_ttl_read_filtering=0
+--rocksdb_default_cf_options=disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary.test
new file mode 100644
index 00000000..38bfb2ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary.test
@@ -0,0 +1,545 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# Basic TTL test
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# column before TTL in value
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# multi-part PK w/ TTL
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# multi-part PK w/ TTL
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# nullable column(s) before TTL
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int,
+ c int,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, NULL, NULL, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, NULL, NULL, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# variable len columns + null column(s) before TTL
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'efghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL implicitly generated (no ttl column)
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5);
+INSERT INTO t1 values (2, 4, 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL field as the PK
+CREATE TABLE t1 (
+ a int,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a, ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=ts;';
+
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = -10;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+# should all still be there..
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 10;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+
+# TTL field inside multi-part pk
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a, ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL field inside key with variable length things..
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`, `ts`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('de', 'fghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL test where you compact (values still exist), real_sleep, then compact again,
+# values should now be gone.
+CREATE TABLE t1 (
+a INT NOT NULL,
+b varbinary(64) NOT NULL,
+c varbinary(256) NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+value mediumblob NOT NULL,
+PRIMARY KEY (b,a,c)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=10;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values (2, 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (3, 'i', 'j', UNIX_TIMESTAMP(), 'k');
+INSERT INTO t1 values (4, 'm', 'n', UNIX_TIMESTAMP(), 'o');
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+# Nothing should get removed here.
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT a FROM t1;
+
+# 1 and 2 should get removed here.
+set global rocksdb_compact_cf='default';
+--sorted_result
+SELECT a FROM t1;
+
+# 3 and 4 should get removed here.
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT a FROM t1;
+
+DROP TABLE t1;
+
+# TTL field with nullable ttl column (should fail)
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts bigint(20),
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+# TTL field with non 8-bit integer column (should fail)
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts int,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+# TTL duration as some random garbage value
+--error ER_RDB_TTL_DURATION_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=abc;';
+
+# TTL col is some column outside of the table
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=abc;';
+
+# TTL col must have accompanying duration
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c)
+) ENGINE=rocksdb
+COMMENT='ttl_col=abc;';
+
+# Make sure it doesn't filter out things early
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=500;';
+
+INSERT INTO t1 values (1);
+SELECT COUNT(*) FROM t1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# Testing altering table comment with updated TTL duration
+# This should trigger a rebuild of the table
+CREATE TABLE t1 (
+ a INT PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+INSERT INTO t1 values (1);
+SELECT * FROM t1;
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+ALTER TABLE t1 COMMENT = 'ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# Tables with hidden PK and SK disabled
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b INT
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+--error ER_RDB_TTL_UNSUPPORTED
+ALTER TABLE t1 DROP PRIMARY KEY;
+
+DROP TABLE t1;
+
+# Test replacing PK, ttl should still work after
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b INT
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t1 VALUES (2,2);
+
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+
+--sorted_result
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+
+--sorted_result
+SELECT COUNT(*) FROM t1;
+
+DROP TABLE t1;
+
+# Make sure table comment filled with other text before/after will work
+# (basically, it needs semicolon before and after)
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int,
+ PRIMARY KEY (a,b)
+) ENGINE=rocksdb
+COMMENT='asdadfasdfsadfadf ;ttl_duration=1; asfasdfasdfadfa';
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 1);
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+
+SELECT COUNT(*) FROM t1;
+
+ALTER TABLE t1 COMMENT = 'adsf;;ttl_duration=5;asfasdfa;ttl_col=a;asdfasdf;';
+
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+
+# nothing removed here
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1;
+
+# all removed here
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+DROP TABLE t1;
+
+# Test to make sure that TTL retains original timestamp during update
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (7);
+INSERT INTO t1 values (9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+UPDATE t1 SET a=a+1;
+--sorted_result
+SELECT * FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# 1,3,5 should be dropped
+--sorted_result
+SELECT * FROM t1;
+DROP TABLE t1;
+
+# test behaviour on update with TTL column, TTL time can be updated here.
+CREATE TABLE t1 (
+ a INT,
+ b bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=UNIX_TIMESTAMP() WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT a FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# 5 and 7 should be gone here
+--sorted_result
+SELECT a FROM t1;
+DROP TABLE t1;
+
+# Test rows expired stat variable and disable ttl variable
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_enable_ttl=0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_enable_ttl=1;
+set global rocksdb_compact_cf='default';
+
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+
+# Table with TTL won't increment rows expired when no records have been
+# compacted
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ PRIMARY KEY (a)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering-master.opt
new file mode 100644
index 00000000..aefc2f5d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering.test
new file mode 100644
index 00000000..7a7609f4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_read_filtering.test
@@ -0,0 +1,388 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# The purpose of read filtering for tables with TTL is to ensure that during a
+# transaction a key which has expired already but not removed by compaction
+# yet, is not returned to the user.
+#
+# Without this the user might be hit with problems such as disappearing rows
+# within a transaction, etc, because the compaction filter ignores snapshots
+# when filtering keys.
+
+# Basic read filtering test
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+
+--sorted_result
+SELECT * FROM t1;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+DROP TABLE t1;
+
+# Test that some rows are hidden but others aren't...
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b BIGINT UNSIGNED NOT NULL
+) ENGINE=rocksdb
+COMMENT='ttl_duration=10;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+
+# 1 should be hidden even though compaction hasn't run.
+--sorted_result
+SELECT a FROM t1;
+
+set global rocksdb_compact_cf='default';
+
+# none should be hidden yet, compaction runs but records aren't expired
+--sorted_result
+SELECT a FROM t1;
+
+# all should be hidden now, even though compaction hasn't run again
+set global rocksdb_debug_ttl_read_filter_ts = -310;
+--sorted_result
+SELECT a FROM t1;
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+
+DROP TABLE t1;
+
+# Test the filtering code explicitly.
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+# should return nothing.
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1;
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+# disable filtering
+set global rocksdb_enable_ttl_read_filtering=0;
+
+# should return everything
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1;
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+# enable filtering
+set global rocksdb_enable_ttl_read_filtering=1;
+
+# should return nothing.
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1;
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+DROP TABLE t1;
+# Compact away the dropped data
+set global ROCKSDB_COMPACT_CF= 'default';
+
+# Read filtering index scan tests (None of these queries should return any results)
+CREATE TABLE t1 (
+ a int,
+ b int,
+ c int,
+ PRIMARY KEY (a,b,c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (0,0,0);
+INSERT INTO t1 values (0,0,1);
+INSERT INTO t1 values (0,1,0);
+INSERT INTO t1 values (0,1,1);
+INSERT INTO t1 values (1,1,2);
+INSERT INTO t1 values (1,2,1);
+INSERT INTO t1 values (1,2,2);
+INSERT INTO t1 values (1,2,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+set global rocksdb_force_flush_memtable_now=1;
+
+# HA_READ_KEY_EXACT, using full key
+SELECT * FROM t1 WHERE a=1 AND b=2 AND c=2;
+
+# HA_READ_KEY_EXACT, not using full key
+SELECT * FROM t1 WHERE a = 1;
+
+# HA_READ_BEFORE_KEY, not using full key
+SELECT max(a) from t1 where a < 3;
+
+#HA_READ_BEFORE_KEY, using full key
+SELECT max(a) from t1 where a < 2 AND b = 1 AND c < 3;
+
+# HA_READ_KEY_OR_NEXT
+SELECT min(a) from t1 where a >= 1;
+
+# HA_READ_AFTER_KEY, /* Find next rec. after key-record */
+SELECT min(a) from t1 where a > 1;
+
+# HA_READ_PREFIX_LAST, /* Last key with the same prefix */
+select * from t1 where a=1 and b in (1) order by c desc;
+
+# HA_READ_PREFIX_LAST_OR_PREV, /* Last or prev key with the same prefix */
+select max(a) from t1 where a <=10;
+
+# need to test read_range_first()
+# calls into read_range_next() and uses compare_keys() to see if its out of
+# range
+select a from t1 where a > 0 and a <= 2;
+
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+DROP TABLE t1;
+
+# duplicate PK value attempt to be inserted when old one is expired...
+# in this case, we pretend the expired key was not found and insert into PK
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+set global rocksdb_debug_ttl_rec_ts = -110;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+SELECT * FROM t1;
+
+# this should work, even if old value is not filtered out yet.
+INSERT INTO t1 values (1);
+
+# should show (1) result
+SELECT * FROM t1;
+
+DROP TABLE t1;
+
+# Attempt to update expired value, should filter out
+set global rocksdb_force_flush_memtable_now=1;
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1;
+
+# No error is thrown here, under the hood rnd_next_with_direction is
+# filtering out the record from being seen in the first place.
+UPDATE t1 set a = 1;
+DROP TABLE t1;
+
+##
+## More tests on update behaviour with expired keys.
+##
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b int
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+set global rocksdb_debug_ttl_rec_ts = -110;
+INSERT INTO t1 values (1,1);
+INSERT INTO t1 values (3,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (5,5);
+
+# expired key (1) is still around under the hood, but
+# this time rnd_next_with_direction finds non-expired key (5). So the
+# execution flow in the SQL layer moves onto update_write_row, where it then
+# finds the duplicate key (1). But the duplicate key is expired, so it allows
+# the overwrite.
+UPDATE t1 set a = 1;
+
+--sorted_result
+SELECT * FROM t1;
+
+set global rocksdb_enable_ttl_read_filtering=0;
+# 1,1 should be gone, even with read filtering disabled as it has been
+# overwritten
+--sorted_result
+SELECT * FROM t1;
+set global rocksdb_enable_ttl_read_filtering=1;
+
+# get_row_by_rowid tested here via index_read_map_impl
+UPDATE t1 set a = 999 where a = 1;
+--sorted_result
+SELECT * FROM t1;
+
+UPDATE t1 set a = a - 1;
+--sorted_result
+SELECT * FROM t1;
+
+DROP TABLE t1;
+
+# Ensure no rows can disappear in the middle of long-running transactions
+# Also ensure repeatable-read works as expected
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+INSERT INTO t1 values (1);
+
+connection con1;
+--echo # Creating Snapshot (start transaction)
+BEGIN;
+
+# We need the below snippet in case establishing con1 took an arbitrary
+# amount of time. See https://github.com/facebook/mysql-5.6/pull/617#discussion_r120525391.
+--disable_query_log
+--let $snapshot_size= `SELECT COUNT(*) FROM t1`
+--let $i= 0
+while ($snapshot_size != 1)
+{
+ if ($i == 1000)
+ {
+ --die Your testing host is too slow for reasonable TTL testing
+ }
+
+ $i++;
+ ROLLBACK;
+ INSERT INTO t1 values (1);
+ BEGIN;
+ --let $snapshot_size= `SELECT COUNT(*) FROM t1`
+}
+--enable_query_log
+
+# Nothing filtered out here
+--sorted_result
+SELECT * FROM t1;
+
+--sleep 5
+
+--sorted_result
+SELECT * FROM t1; # <= shouldn't be filtered out here
+
+--echo # Switching to connection 2
+connection con2;
+# compaction doesn't do anything since con1 snapshot is still open
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+# read filtered out, because on a different connection, on
+# this connection the records have 'expired' already so they are filtered out
+# even though they have not yet been removed by compaction
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1;
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+--echo # Switching to connection 1
+connection con1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1; # <= shouldn't be filtered out here
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+UPDATE t1 set a = a + 1;
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result
+SELECT * FROM t1; # <= shouldn't be filtered out here
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+COMMIT;
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+--sorted_result # <= filtered out here because time has passed.
+SELECT * FROM t1;
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_rows_filtered';
+
+DROP TABLE t1;
+disconnect con1;
+disconnect con2;
+
+#transaction 1, create a snapshot and select * => returns nothing.
+#transaction 2, insert into table, flush
+#transaction 1, select * => returns nothing, but the snapshot should prevent the compaction code from removing the rows, no matter what the ttl duration is.
+#transaction 2, select * -> sees nothing, disable filter, select * -> sees everything, enable filter, select * -> sees nothing.
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+--echo # On Connection 1
+connection con1;
+--echo # Creating Snapshot (start transaction)
+BEGIN;
+--sorted_result
+SELECT * FROM t1;
+# Sleep 5 secs after creating snapshot, this ensures any records created after
+# this can't be removed by compaction until this snapshot is released.
+--sleep 5
+
+--echo # On Connection 2
+connection con2;
+set global rocksdb_debug_ttl_rec_ts = -2;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+--echo # On Connection 1
+connection con1;
+--sorted_result
+SELECT * FROM t1;
+
+--echo # On Connection 2
+connection con2;
+--sorted_result
+SELECT * FROM t1;
+set global rocksdb_enable_ttl_read_filtering=0;
+--sorted_result
+SELECT * FROM t1;
+set global rocksdb_enable_ttl_read_filtering=1;
+
+disconnect con2;
+disconnect con1;
+connection default;
+
+DROP TABLE t1;
+# Wait till we reached the initial number of concurrent sessions
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions-master.opt
new file mode 100644
index 00000000..b991f718
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_enable_ttl_read_filtering=0
+--rocksdb_default_cf_options=disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions.test
new file mode 100644
index 00000000..aba2b594
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_primary_with_partitions.test
@@ -0,0 +1,254 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# Create a table with multiple partitions, but in the comment don't specify
+# that per-partition based column families (CF) should be created. Expect that
+# default CF will be used and new one won't be created.
+#
+# In addition, specify TTL on one of the partitions. Insert a few things
+# inside all the partitions, verify after compaction that the rows inside the
+# partition with TTL has disappeared.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ PRIMARY KEY (`c1`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1);
+INSERT INTO t1 values (2);
+INSERT INTO t1 values (3);
+INSERT INTO t1 values (4);
+INSERT INTO t1 values (5);
+INSERT INTO t1 values (6);
+INSERT INTO t1 values (7);
+INSERT INTO t1 values (8);
+INSERT INTO t1 values (9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# 1,4, and 7 should be gone
+--sorted_result
+SELECT * FROM t1;
+DROP TABLE t1;
+
+#
+# Create a table with multiple partitions and request for separate CF to be
+# created per every partition. As a result we expect three different CF-s to be
+# created.
+#
+# In addition, specify TTL on some of the partitions. Insert a few things
+# inside all the partitions, verify after compaction that the rows inside the
+# partition with TTL has disappeared.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;custom_p1_ttl_duration=7;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (4,4,'aaaa');
+INSERT INTO t1 values (7,7,'aaaaaaa');
+
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (2,2,'aa');
+INSERT INTO t1 values (3,3,'aaa');
+INSERT INTO t1 values (5,5,'aaaaa');
+INSERT INTO t1 values (6,6,'aaaaaa');
+INSERT INTO t1 values (8,8,'aaaaaaaa');
+INSERT INTO t1 values (9,9,'aaaaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+--sorted_result
+SELECT * FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'foo';
+--sorted_result
+SELECT * FROM t1;
+
+# Now 2,5,8 should be removed (this verifies that TTL is only operating on the
+# particular CF.
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1;
+
+DROP TABLE t1;
+
+#
+# Create a table with CF-s/TTL per partition and verify that ALTER TABLE + DROP
+# PRIMARY, ADD PRIMARY work for that scenario and data is persisted/filtered as
+# expected.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 2, 3),
+ PARTITION custom_p1 VALUES IN (4, 5, 6),
+ PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+
+INSERT INTO t1 VALUES (4, 4, "four", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (6, 6, "six", null);
+
+INSERT INTO t1 VALUES (7, 7, "seven", null);
+INSERT INTO t1 VALUES (8, 8, "eight", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+
+--sorted_result
+SELECT * FROM t1;
+
+# TTL should be reset after alter table
+set global rocksdb_debug_ttl_rec_ts = 600;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+
+# ...so nothing should be gone here
+set global rocksdb_debug_ttl_snapshot_ts = 100;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'baz';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 1200;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1;
+
+DROP TABLE t1;
+
+#
+# Create a table with non-partitioned TTL duration, with partitioned TTL
+# columns
+#
+# In this case the same TTL duration will be applied across different TTL
+# columns in different partitions, except for in p2 where we override the ttl
+# duration.
+#
+CREATE TABLE t1 (
+ c1 BIGINT,
+ c2 BIGINT UNSIGNED NOT NULL,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;'
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=1;custom_p1_ttl_duration=100;custom_p1_ttl_col=c2;custom_p2_ttl_duration=5000;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 2, 3),
+ PARTITION custom_p1 VALUES IN (4, 5, 6),
+ PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 VALUES (1, UNIX_TIMESTAMP(), "one", null);
+INSERT INTO t1 VALUES (2, UNIX_TIMESTAMP(), "two", null);
+INSERT INTO t1 VALUES (3, UNIX_TIMESTAMP(), "three", null);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+INSERT INTO t1 VALUES (4, UNIX_TIMESTAMP(), "four", null);
+INSERT INTO t1 VALUES (5, UNIX_TIMESTAMP(), "five", null);
+INSERT INTO t1 VALUES (6, UNIX_TIMESTAMP(), "six", null);
+
+INSERT INTO t1 VALUES (7, UNIX_TIMESTAMP(), "seven", null);
+INSERT INTO t1 VALUES (8, UNIX_TIMESTAMP(), "eight", null);
+INSERT INTO t1 VALUES (9, UNIX_TIMESTAMP(), "nine", null);
+
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'bar';
+
+# here we expect only 1,2,3 to be gone, ttl implicit.
+--sorted_result
+SELECT c1 FROM t1;
+
+# here we expect only 4,5,6 to be gone, ttl based on column c2.
+set global rocksdb_debug_ttl_snapshot_ts = 600;
+set @@global.rocksdb_compact_cf = 'bar';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT c1 FROM t1;
+
+# at this point only 7,8,9 should be left..
+DROP TABLE t1;
+
+#
+# Make sure non-partitioned TTL duration/col still works on table with
+# partitions.
+#
+# Simultaneously tests when TTL col is part of the key in partitioned table
+#
+CREATE TABLE t1 (
+ c1 BIGINT,
+ c2 BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (`c1`, `c2`)
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=100;ttl_col=c2;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1),
+ PARTITION custom_p1 VALUES IN (2),
+ PARTITION custom_p2 VALUES IN (3)
+);
+
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# everything should still be here
+--sorted_result
+SELECT c1 FROM t1;
+
+set global rocksdb_debug_ttl_snapshot_ts = 300;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+# everything should now be gone
+--sorted_result
+SELECT c1 FROM t1;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_rows_examined.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_rows_examined.test
new file mode 100644
index 00000000..714c2c60
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_rows_examined.test
@@ -0,0 +1,57 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+####
+# Bump rows_examined count whenever MyRocks filters out a row due to expired TTL
+####
+
+# clean start
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = -10;
+
+--enable_connect_log
+--enable_info
+
+connect (conn1, localhost, root,,test);
+connect (conn2, localhost, root,,test);
+
+connection conn1;
+
+# create table with TTL policy (1s)
+CREATE TABLE t_re (
+ a INT, b INT, PRIMARY KEY (a)
+) ENGINE=ROCKSDB
+COMMENT 'ttl_duration=1';
+
+# start with 2 rows, expired at the insertion time
+set global rocksdb_debug_ttl_rec_ts = -13;
+insert into t_re values (1,1);
+insert into t_re values (2,2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+commit;
+
+# setup signal to stop in code where we skip expired records
+set debug_sync='rocksdb.ttl_rows_examined SIGNAL parked WAIT_FOR go';
+send SELECT * FROM t_re;
+
+connection conn2;
+set debug_sync='now WAIT_FOR parked';
+
+# display "Rows Examined" before returning from call
+--replace_result Execute Query
+--replace_column 1 ### 2 ### 3 ### 4 ### 6 ### 10 ### 11 ### 12 ###
+--sorted_result
+SHOW PROCESSLIST;
+
+set debug_sync='now SIGNAL go';
+
+connection conn1;
+reap;
+
+# tidy up
+set debug_sync='RESET';
+set global rocksdb_debug_ttl_read_filter_ts = DEFAULT;
+drop table t_re;
+
+disconnect conn1;
+disconnect conn2;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary-master.opt
new file mode 100644
index 00000000..b991f718
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary-master.opt
@@ -0,0 +1,2 @@
+--rocksdb_enable_ttl_read_filtering=0
+--rocksdb_default_cf_options=disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary.test
new file mode 100644
index 00000000..fb439e10
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary.test
@@ -0,0 +1,780 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# Basic TTL test, pk ignored, no sk
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# no rows should be filtered
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# Basic TTL test
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`),
+KEY kb (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# column before TTL in value
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# multi-part PK w/ TTL
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a,c),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, 4, 6, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# nullable column(s) before TTL
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int,
+ c int,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a),
+ KEY kbc (b, c)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, NULL, NULL, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, NULL, NULL, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# variable len columns + null column(s) before TTL
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`),
+KEY kbc (`b`, `c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'efghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# TTL implicitly generated (no ttl column)
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, 5);
+INSERT INTO t1 values (2, 4, 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# TTL field as the PK
+CREATE TABLE t1 (
+ a int,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a, ts),
+ KEY kt (ts)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=ts;';
+
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+
+set global rocksdb_debug_ttl_snapshot_ts = -10;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+# should all still be there..
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 10;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX(kt);
+DROP TABLE t1;
+
+# TTL field inside multi-part pk
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ ts bigint(20) UNSIGNED NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a, ts),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 3, UNIX_TIMESTAMP(), 5);
+INSERT INTO t1 values (2, 4, UNIX_TIMESTAMP(), 6);
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL field inside key with variable length things..
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64),
+`c` varbinary(256),
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`a`, `ts`),
+KEY kb (`b`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', NULL, 'bc', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('de', 'fghijk', NULL, UNIX_TIMESTAMP(), 'l');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*) FROM t1;
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1;
+DROP TABLE t1;
+
+# TTL test where you compact (values still exist), real_sleep, then compact again,
+# values should now be gone.
+CREATE TABLE t1 (
+a INT NOT NULL,
+b varbinary(64) NOT NULL,
+c varbinary(256) NOT NULL,
+ts bigint(20) UNSIGNED NOT NULL,
+value mediumblob NOT NULL,
+PRIMARY KEY (b,a,c),
+KEY kb (b)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=10;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values (2, 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (3, 'i', 'j', UNIX_TIMESTAMP(), 'k');
+INSERT INTO t1 values (4, 'm', 'n', UNIX_TIMESTAMP(), 'o');
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+# Nothing should get removed here.
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+# 1 and 2 should get removed here.
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+# 3 and 4 should get removed here.
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+
+# TTL field with nullable ttl column (should fail)
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts bigint(20),
+ PRIMARY KEY (a,c),
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+# TTL field with non 8-bit integer column (should fail)
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ ts int,
+ PRIMARY KEY (a,c),
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+# TTL duration as some random garbage value
+--error ER_RDB_TTL_DURATION_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c),
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=abc;';
+
+# TTL col is some column outside of the table
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c),
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=abc;';
+
+# TTL col must have accompanying duration
+--error ER_RDB_TTL_COL_FORMAT
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a,c),
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_col=abc;';
+
+# Make sure it doesn't filter out things early
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=500;';
+
+INSERT INTO t1 values (1, 1);
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Testing altering table comment with updated TTL duration
+# This should trigger a rebuild of the table
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b INT NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+INSERT INTO t1 values (1, 1);
+SELECT * FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+ALTER TABLE t1 COMMENT = 'ttl_duration=1';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Tables with hidden PK disabled
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b INT,
+ KEY (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+--error ER_RDB_TTL_UNSUPPORTED
+ALTER TABLE t1 DROP PRIMARY KEY;
+
+DROP TABLE t1;
+
+# Test replacing PK, ttl should still work after
+CREATE TABLE t1 (
+ a INT PRIMARY KEY,
+ b INT,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+INSERT INTO t1 VALUES (1,1);
+INSERT INTO t1 VALUES (2,2);
+
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(b);
+set global rocksdb_debug_ttl_snapshot_ts = -3600;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+
+--sorted_result
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+--sorted_result
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+
+# Make sure table comment filled with other text before/after will work
+# (basically, it needs semicolon before and after)
+CREATE TABLE t1 (
+ a bigint(20) UNSIGNED NOT NULL,
+ b int,
+ PRIMARY KEY (a,b),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='asdadfasdfsadfadf ;ttl_duration=1; asfasdfasdfadfa';
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 1);
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+ALTER TABLE t1 COMMENT = 'adsf;;ttl_duration=5;asfasdfa;ttl_col=a;asdfasdf;';
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (UNIX_TIMESTAMP(), 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+
+# nothing removed here
+set global rocksdb_compact_cf='default';
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+# all removed here
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+set global rocksdb_debug_ttl_ignore_pk=0;
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+
+# Test to make sure that TTL retains original timestamp during update
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, 0);
+INSERT INTO t1 values (3, 0);
+INSERT INTO t1 values (5, 0);
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (7, 0);
+INSERT INTO t1 values (9, 0);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+UPDATE t1 SET a=a+1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# 1,3,5 should be dropped
+--sorted_result
+SELECT * FROM t1;
+DROP TABLE t1;
+
+# test behaviour on update with TTL column, TTL time can be updated here.
+CREATE TABLE t1 (
+ a INT,
+ b bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# 5 and 7 should be gone here
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Test rows expired stat variable and disable ttl variable
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 1);
+INSERT INTO t1 values (3, 1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_enable_ttl=0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_enable_ttl=1;
+set global rocksdb_compact_cf='default';
+
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Table with TTL won't increment rows expired when no records have been
+# compacted
+CREATE TABLE t1 (
+ a bigint(20) NOT NULL,
+ b int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=100;';
+
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+DROP TABLE t1;
+
+# Test update on TTL column in pk
+CREATE TABLE t1 (
+ a INT,
+ b bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a, b),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk=1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk=0;
+
+# 5 and 7 should be gone here
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# test behaviour on update with TTL column, TTL time can be updated here.
+CREATE TABLE t1 (
+ a INT,
+ b bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a, b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;ttl_col=b;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+INSERT INTO t1 values (5, UNIX_TIMESTAMP());
+INSERT INTO t1 values (7, UNIX_TIMESTAMP());
+
+set global rocksdb_debug_ttl_rec_ts = 300;
+UPDATE t1 SET b=(UNIX_TIMESTAMP()+1) WHERE a < 4;
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT a FROM t1;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# 7 should be gone here
+--sorted_result
+SELECT a FROM t1;
+DROP TABLE t1;
+
+# Add index inplace
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# nothing filtered out
+SELECT COUNT(*);
+
+CREATE INDEX kb on t1 (b);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Add index inplace, implicit TTL
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', 'd');
+INSERT INTO t1 values ('d', 'e', 'f', 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# nothing filtered out
+SELECT COUNT(*);
+
+CREATE INDEX kb on t1 (b);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
+
+# Add index inplace, TTL column in PK
+CREATE TABLE t1 (
+`a` binary(8) NOT NULL,
+`b` varbinary(64) NOT NULL,
+`c` varbinary(256) NOT NULL,
+`ts` bigint(20) UNSIGNED NOT NULL,
+`value` mediumblob NOT NULL,
+PRIMARY KEY (`b`,`a`,`c`, `ts`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values ('a', 'b', 'c', UNIX_TIMESTAMP(), 'd');
+INSERT INTO t1 values ('d', 'e', 'f', UNIX_TIMESTAMP(), 'g');
+set global rocksdb_debug_ttl_rec_ts = 0;
+SELECT COUNT(*);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# nothing filtered out
+SELECT COUNT(*);
+
+CREATE INDEX kb on t1 (b);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+# should have filtered the rows out since ttl is passed in compaction filter
+SELECT COUNT(*) FROM t1 FORCE INDEX (kb);
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering-master.opt
new file mode 100644
index 00000000..aefc2f5d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering-master.opt
@@ -0,0 +1 @@
+--rocksdb_default_cf_options=disable_auto_compactions=true
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering.test
new file mode 100644
index 00000000..f6042cc5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering.test
@@ -0,0 +1,503 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# The purpose of read filtering for tables with TTL is to ensure that during a
+# transaction a key which has expired already but not removed by compaction
+# yet, is not returned to the user.
+#
+# Without this the user might be hit with problems such as disappearing rows
+# within a transaction, etc, because the compaction filter ignores snapshots
+# when filtering keys.
+
+# Basic read filtering test
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b int NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+DROP TABLE t1;
+
+# Test that some rows are hidden but others aren't...
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b BIGINT UNSIGNED NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=10;';
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 values (1, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 300;
+INSERT INTO t1 values (2, UNIX_TIMESTAMP());
+INSERT INTO t1 values (3, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+
+--echo # 1 should be hidden
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+
+--echo # none should be hidden yet, compaction runs but records aren't expired
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+
+--echo # all should be hidden now, even though compaction hasn't run again
+set global rocksdb_debug_ttl_read_filter_ts = -310;
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT a FROM t1 FORCE INDEX (kb);
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+
+DROP TABLE t1;
+
+# Test the filtering code explicitly.
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b int NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--echo # should return nothing.
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+# disable filtering
+set global rocksdb_enable_ttl_read_filtering=0;
+
+--echo # should return everything
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+# enable filtering
+set global rocksdb_enable_ttl_read_filtering=1;
+
+--echo # should return nothing.
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+
+# Compact away the dropped data
+set global rocksdb_compact_cf= 'default';
+
+--echo # Read filtering index scan tests (None of these queries should return any results)
+CREATE TABLE t1 (
+ a int,
+ b int,
+ c int,
+ PRIMARY KEY (a,b,c),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (0,0,0);
+INSERT INTO t1 values (0,0,1);
+INSERT INTO t1 values (0,1,0);
+INSERT INTO t1 values (0,1,1);
+INSERT INTO t1 values (1,1,2);
+INSERT INTO t1 values (1,2,1);
+INSERT INTO t1 values (1,2,2);
+INSERT INTO t1 values (1,2,3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+select variable_value into @c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+
+set global rocksdb_force_flush_memtable_now=1;
+
+# HA_READ_KEY_EXACT, using full key
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a=1 AND b=2 AND c=2;
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a=1 AND b=2 AND c=2;
+
+# HA_READ_KEY_EXACT, not using full key
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a = 1;
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a = 1;
+
+# HA_READ_BEFORE_KEY, not using full key
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a < 3;
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a < 3;
+
+# HA_READ_BEFORE_KEY, using full key
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a < 2 AND b = 1 AND c < 3;
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a < 2 AND b = 1 AND c < 3;
+
+# HA_READ_KEY_OR_NEXT
+SELECT min(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a >= 1;
+SELECT min(a) FROM t1 FORCE INDEX (kb) WHERE a >= 1;
+
+# HA_READ_AFTER_KEY, /* Find next rec. after key-record */
+SELECT min(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a > 1;
+SELECT min(a) FROM t1 FORCE INDEX (kb) WHERE a > 1;
+
+# HA_READ_PREFIX_LAST, /* Last key with the same prefix */
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE a=1 and b in (1) order by c desc;
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a=1 and b in (1) order by c desc;
+
+# HA_READ_PREFIX_LAST_OR_PREV, /* Last or prev key with the same prefix */
+SELECT max(a) FROM t1 FORCE INDEX (PRIMARY) WHERE a <=10;
+SELECT max(a) FROM t1 FORCE INDEX (kb) WHERE a <=10;
+
+# need to test read_range_first()
+# calls into read_range_next() and uses compare_keys() to see if its out of
+# range
+SELECT a FROM t1 FORCE INDEX (PRIMARY) WHERE a > 0 and a <= 2;
+SELECT a FROM t1 FORCE INDEX (kb) WHERE a > 0 and a <= 2;
+
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+select variable_value-@c from information_schema.global_status where variable_name='rocksdb_rows_expired';
+DROP TABLE t1;
+
+--echo # Attempt to update expired value, should filter out
+set global rocksdb_force_flush_memtable_now=1;
+CREATE TABLE t1 (
+ a int PRIMARY KEY
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1;
+
+--echo # No error is thrown here, under the hood index_next_with_direction is
+--echo # filtering out the record from being seen in the first place.
+UPDATE t1 set a = 1;
+DROP TABLE t1;
+
+--echo # Ensure no rows can disappear in the middle of long-running transactions
+--echo # Also ensure repeatable-read works as expected
+--source include/count_sessions.inc
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b int NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=5;';
+
+INSERT INTO t1 values (1, 1);
+
+connection con1;
+--echo # Creating Snapshot (start transaction)
+BEGIN;
+
+# We need the below snippet in case establishing con1 took an arbitrary
+# amount of time. See https://github.com/facebook/mysql-5.6/pull/617#discussion_r120525391.
+--disable_query_log
+--let $snapshot_size= `SELECT COUNT(*) FROM t1`
+--let $i= 0
+while ($snapshot_size != 1)
+{
+ if ($i == 1000)
+ {
+ --die Your testing host is too slow for reasonable TTL testing
+ }
+
+ $i++;
+ ROLLBACK;
+ INSERT INTO t1 values (1,1);
+ BEGIN;
+ --let $snapshot_size= `SELECT COUNT(*) FROM t1`
+}
+--enable_query_log
+
+--echo # Nothing filtered out here
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+--sleep 5
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb); # <= shouldn't be filtered out here
+
+--echo # Switching to connection 2
+connection con2;
+--echo # compaction doesn't do anything since con1 snapshot is still open
+set global rocksdb_debug_ttl_ignore_pk = 1;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_debug_ttl_ignore_pk = 0;
+--echo # read filtered out, because on a different connection, on
+--echo # this connection the records have 'expired' already so they are filtered out
+--echo # even though they have not yet been removed by compaction
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+--echo # Switching to connection 1
+connection con1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb); # <= shouldn't be filtered out here
+
+UPDATE t1 set a = a + 1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb); # <= shouldn't be filtered out here
+
+COMMIT;
+
+--sorted_result # <= filtered out here because time has passed.
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+disconnect con1;
+disconnect con2;
+
+#transaction 1, create a snapshot and select * => returns nothing.
+#transaction 2, insert into table, flush
+#transaction 1, select * => returns nothing, but the snapshot should prevent the compaction code from removing the rows, no matter what the ttl duration is.
+#transaction 2, select * -> sees nothing, disable filter, select * -> sees everything, enable filter, select * -> sees nothing.
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+CREATE TABLE t1 (
+ a int PRIMARY KEY,
+ b int NOT NULL,
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;';
+
+--echo # On Connection 1
+connection con1;
+--echo # Creating Snapshot (start transaction)
+BEGIN;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+# Sleep 5 secs after creating snapshot, this ensures any records created after
+# this can't be removed by compaction until this snapshot is released.
+--sleep 5
+
+--echo # On Connection 2
+connection con2;
+set global rocksdb_debug_ttl_rec_ts = -2;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+--echo # On Connection 1
+connection con1;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+
+--echo # On Connection 2
+connection con2;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+set global rocksdb_enable_ttl_read_filtering=0;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kb);
+set global rocksdb_enable_ttl_read_filtering=1;
+
+disconnect con2;
+disconnect con1;
+connection default;
+
+DROP TABLE t1;
+# Wait till we reached the initial number of concurrent sessions
+--source include/wait_until_count_sessions.inc
+
+# Test that index_next_with_direction skips records properly
+CREATE TABLE t1 (
+ a int,
+ b int,
+ ts bigint(20) UNSIGNED NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1;ttl_col=ts;';
+
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 VALUES (1, 1, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (2, 2, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (3, 3, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (4, 4, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (5, 5, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (6, 6, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (7, 7, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (8, 8, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (9, 9, UNIX_TIMESTAMP());
+INSERT INTO t1 VALUES (10, 10, UNIX_TIMESTAMP());
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+--echo # None are expired
+SELECT a, b FROM t1 FORCE INDEX (kb);
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+UPDATE t1 SET ts=(UNIX_TIMESTAMP()+1) WHERE a IN (4, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+--echo # 4 and 7 should be gone
+SELECT a, b FROM t1 FORCE INDEX (kb);
+
+DROP TABLE t1;
+
+# Test range scans with various conditionals
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ PRIMARY KEY (c1, c2),
+ KEY kc2 (c2)
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1;';
+
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (2,2,'b');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (3,3,'c');
+INSERT INTO t1 values (4,4,'d');
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (5,5,'e');
+INSERT INTO t1 values (6,6,'f');
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (7,7,'g');
+INSERT INTO t1 values (8,8,'h');
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE c1 > 5;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2) WHERE c2 > 5;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY) WHERE 3 < c1 AND c1 < 6;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2) WHERE 3 < c2 AND c2 < 6;
+
+DROP TABLE t1;
+
+# Test range scans with varying expirations
+CREATE TABLE t1 (
+ a int,
+ b int,
+ PRIMARY KEY (a),
+ KEY kb (b)
+) ENGINE=rocksdb
+COMMENT='ttl_duration=1800;';
+
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (1,1);
+INSERT INTO t1 values (2,2);
+INSERT INTO t1 values (7,7);
+INSERT INTO t1 values (10,10);
+INSERT INTO t1 values (11,11);
+INSERT INTO t1 values (12,12);
+set global rocksdb_debug_ttl_rec_ts = 450;
+INSERT INTO t1 values (3,3);
+INSERT INTO t1 values (4,4);
+INSERT INTO t1 values (8,8);
+INSERT INTO t1 values (16,16);
+INSERT INTO t1 values (17,17);
+INSERT INTO t1 values (18,18);
+set global rocksdb_debug_ttl_rec_ts = 900;
+INSERT INTO t1 values (5,5);
+INSERT INTO t1 values (6,6);
+INSERT INTO t1 values (9,9);
+INSERT INTO t1 values (13,13);
+INSERT INTO t1 values (14,14);
+INSERT INTO t1 values (15,15);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--echo # Should see everything
+SELECT * FROM t1;
+
+--echo # Should have no records from the first group
+set global rocksdb_debug_ttl_read_filter_ts = -1800;
+SELECT * FROM t1;
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a > 5 AND a < 15;
+
+--echo # Should only have records from the last group
+set global rocksdb_debug_ttl_read_filter_ts = -1800 - 450;
+SELECT * FROM t1;
+SELECT * FROM t1 FORCE INDEX (kb) WHERE a < 10;
+
+--echo # Should be empty
+set global rocksdb_debug_ttl_read_filter_ts = -1800 - 900;
+SELECT * FROM t1;
+
+set global rocksdb_debug_ttl_read_filter_ts = 0;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering_multiple_index.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering_multiple_index.test
new file mode 100644
index 00000000..4f9788ce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_read_filtering_multiple_index.test
@@ -0,0 +1,87 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# Multiple indexes, trigger compaction on sk and check consistency
+CREATE TABLE t1 (
+ a int NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b) COMMENT 'kb',
+ KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='kb';
+
+# Results should be consistent
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SELECT * FROM t1 FORCE INDEX (kb);
+SELECT * FROM t1 FORCE INDEX (kc);
+
+DROP TABLE t1;
+
+# Trigger compaction on pk and check consistency
+CREATE TABLE t1 (
+ a int NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b) COMMENT 'kb',
+ KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# Results should be consistent
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SELECT * FROM t1 FORCE INDEX (kb);
+SELECT * FROM t1 FORCE INDEX (kc);
+
+DROP TABLE t1;
+
+# Trigger compaction on pk and sk and check consistency
+CREATE TABLE t1 (
+ a int NOT NULL,
+ b int NOT NULL,
+ c int NOT NULL,
+ PRIMARY KEY (a),
+ KEY kb (b) COMMENT 'kb',
+ KEY kc (c) COMMENT 'kc'
+) ENGINE=ROCKSDB
+COMMENT='ttl_duration=1';
+
+set global rocksdb_debug_ttl_rec_ts = -100;
+INSERT INTO t1 values (1, 1, 1);
+INSERT INTO t1 values (2, 2, 2);
+set global rocksdb_debug_ttl_rec_ts = 100;
+INSERT INTO t1 values (3, 3, 3);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+set global rocksdb_compact_cf='kb';
+
+# Results should be consistent
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+SELECT * FROM t1 FORCE INDEX (kb);
+SELECT * FROM t1 FORCE INDEX (kc);
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions-master.opt
new file mode 100644
index 00000000..10a88c30
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions-master.opt
@@ -0,0 +1 @@
+--rocksdb_enable_ttl_read_filtering=0
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions.test b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions.test
new file mode 100644
index 00000000..c10c83f9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/ttl_secondary_with_partitions.test
@@ -0,0 +1,300 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# Create a table with multiple partitions, but in the comment don't specify
+# that per-partition based column families (CF) should be created. Expect that
+# default CF will be used and new one won't be created.
+#
+# In addition, specify TTL on one of the partitions. Insert a few things
+# inside all the partitions, verify after compaction that the rows inside the
+# partition with TTL has disappeared.
+#
+# Add secondary keys to all tables to test compatibility.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ PRIMARY KEY (`c1`),
+ KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (4, 4);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (6, 6);
+INSERT INTO t1 values (7, 7);
+INSERT INTO t1 values (8, 8);
+INSERT INTO t1 values (9, 9);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='default';
+
+# 1,4, and 7 should be gone
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+DROP TABLE t1;
+
+#
+# Create a table with multiple partitions and request for separate CF to be
+# created per every partition. As a result we expect three different CF-s to be
+# created.
+#
+# In addition, specify TTL on some of the partitions. Insert a few things
+# inside all the partitions, verify after compaction that the rows inside the
+# partition with TTL has disappeared.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=my_custom_cf;custom_p2_cfname=baz',
+ KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;custom_p1_ttl_duration=7;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -1200;
+INSERT INTO t1 values (1,1,'a');
+INSERT INTO t1 values (4,4,'aaaa');
+INSERT INTO t1 values (7,7,'aaaaaaa');
+
+set global rocksdb_debug_ttl_rec_ts = 1200;
+INSERT INTO t1 values (2,2,'aa');
+INSERT INTO t1 values (3,3,'aaa');
+INSERT INTO t1 values (5,5,'aaaaa');
+INSERT INTO t1 values (6,6,'aaaaaa');
+INSERT INTO t1 values (8,8,'aaaaaaaa');
+INSERT INTO t1 values (9,9,'aaaaaaaaa');
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'default';
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+set global rocksdb_debug_ttl_snapshot_ts = 3600;
+set @@global.rocksdb_compact_cf = 'foo';
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+# Now 2,5,8 should be removed (this verifies that TTL is only operating on the
+# particular CF.
+set @@global.rocksdb_compact_cf = 'my_custom_cf';
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+DROP TABLE t1;
+
+#
+# Create a table with CF-s/TTL per partition and verify that ALTER TABLE + DROP
+# PRIMARY, ADD PRIMARY work for that scenario and data is persisted/filtered as
+# expected.
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`, `c2`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;',
+ KEY kc2 (c2)
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=9999;custom_p2_ttl_duration=5;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 2, 3),
+ PARTITION custom_p1 VALUES IN (4, 5, 6),
+ PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+
+INSERT INTO t1 VALUES (1, 1, "one", null);
+INSERT INTO t1 VALUES (2, 2, "two", null);
+INSERT INTO t1 VALUES (3, 3, "three", null);
+
+INSERT INTO t1 VALUES (4, 4, "four", null);
+INSERT INTO t1 VALUES (5, 5, "five", null);
+INSERT INTO t1 VALUES (6, 6, "six", null);
+
+INSERT INTO t1 VALUES (7, 7, "seven", null);
+INSERT INTO t1 VALUES (8, 8, "eight", null);
+INSERT INTO t1 VALUES (9, 9, "nine", null);
+
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+# TTL should be reset after alter table
+set global rocksdb_debug_ttl_rec_ts = 600;
+ALTER TABLE t1 DROP PRIMARY KEY, ADD PRIMARY KEY(`c2`,`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;';
+set global rocksdb_debug_ttl_rec_ts = 0;
+SHOW CREATE TABLE t1;
+
+# ...so nothing should be gone here
+set global rocksdb_debug_ttl_snapshot_ts = 100;
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'baz';
+# Filter out expired secondary keys too
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+set global rocksdb_debug_ttl_snapshot_ts = 1200;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+# Filter out expired secondary keys too
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+
+DROP TABLE t1;
+
+#
+# Create a table with non-partitioned TTL duration, with partitioned TTL
+# columns
+#
+# In this case the same TTL duration will be applied across different TTL
+# columns in different partitions, except for in p2 where we override the ttl
+# duration.
+#
+CREATE TABLE t1 (
+ c1 BIGINT,
+ c2 BIGINT UNSIGNED NOT NULL,
+ name VARCHAR(25) NOT NULL,
+ event DATE,
+ PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;custom_p1_cfname=bar;custom_p2_cfname=baz;',
+ KEY kc2 (`c2`)
+) ENGINE=ROCKSDB
+COMMENT="ttl_duration=1;custom_p1_ttl_duration=100;custom_p1_ttl_col=c2;custom_p2_ttl_duration=5000;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 2, 3),
+ PARTITION custom_p1 VALUES IN (4, 5, 6),
+ PARTITION custom_p2 VALUES IN (7, 8, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -300;
+INSERT INTO t1 VALUES (1, UNIX_TIMESTAMP(), "one", null);
+INSERT INTO t1 VALUES (2, UNIX_TIMESTAMP(), "two", null);
+INSERT INTO t1 VALUES (3, UNIX_TIMESTAMP(), "three", null);
+set global rocksdb_debug_ttl_rec_ts = 0;
+
+INSERT INTO t1 VALUES (4, UNIX_TIMESTAMP(), "four", null);
+INSERT INTO t1 VALUES (5, UNIX_TIMESTAMP(), "five", null);
+INSERT INTO t1 VALUES (6, UNIX_TIMESTAMP(), "six", null);
+
+INSERT INTO t1 VALUES (7, UNIX_TIMESTAMP(), "seven", null);
+INSERT INTO t1 VALUES (8, UNIX_TIMESTAMP(), "eight", null);
+INSERT INTO t1 VALUES (9, UNIX_TIMESTAMP(), "nine", null);
+
+set global rocksdb_force_flush_memtable_now=1;
+set @@global.rocksdb_compact_cf = 'foo';
+set @@global.rocksdb_compact_cf = 'baz';
+set @@global.rocksdb_compact_cf = 'bar';
+# Filter out expired secondary keys too
+set @@global.rocksdb_compact_cf = 'default';
+
+# here we expect only 1,2,3 to be gone, ttl implicit.
+--sorted_result
+SELECT c1 FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT c1 FROM t1 FORCE INDEX (kc2);
+
+# here we expect only 4,5,6 to be gone, ttl based on column c2.
+set global rocksdb_debug_ttl_snapshot_ts = 600;
+set @@global.rocksdb_compact_cf = 'bar';
+# Filter out expired secondary keys too
+set @@global.rocksdb_compact_cf = 'default';
+set global rocksdb_debug_ttl_snapshot_ts = 0;
+--sorted_result
+SELECT c1 FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT c1 FROM t1 FORCE INDEX (kc2);
+
+# at this point only 7,8,9 should be left..
+DROP TABLE t1;
+
+#
+# Add index inplace
+#
+CREATE TABLE t1 (
+ c1 INT,
+ c2 INT,
+ PRIMARY KEY (`c1`) COMMENT 'custom_p0_cfname=foo;'
+) ENGINE=ROCKSDB
+COMMENT="custom_p0_ttl_duration=1;"
+PARTITION BY LIST(c1) (
+ PARTITION custom_p0 VALUES IN (1, 4, 7),
+ PARTITION custom_p1 VALUES IN (2, 5, 8),
+ PARTITION custom_p2 VALUES IN (3, 6, 9)
+);
+
+set global rocksdb_debug_ttl_rec_ts = -3600;
+INSERT INTO t1 values (1, 1);
+INSERT INTO t1 values (4, 4);
+INSERT INTO t1 values (7, 7);
+set global rocksdb_debug_ttl_rec_ts = 0;
+INSERT INTO t1 values (2, 2);
+INSERT INTO t1 values (3, 3);
+INSERT INTO t1 values (5, 5);
+INSERT INTO t1 values (6, 6);
+INSERT INTO t1 values (8, 8);
+INSERT INTO t1 values (9, 9);
+
+# Nothing filtered out
+--sorted_result
+SELECT * FROM t1;
+
+CREATE INDEX kc2 on t1 (c2);
+
+set global rocksdb_force_flush_memtable_now=1;
+set global rocksdb_compact_cf='foo';
+set global rocksdb_compact_cf='default';
+
+# 1,4, and 7 should be gone
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (PRIMARY);
+--sorted_result
+SELECT * FROM t1 FORCE INDEX (kc2);
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_binary.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_binary.inc
new file mode 100644
index 00000000..8e2b0e85
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_binary.inc
@@ -0,0 +1,45 @@
+#
+# BINARY column types
+#
+
+# $extra_col_options is set by a calling test
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ b BINARY $extra_col_opts,
+ b0 BINARY(0) $extra_col_opts,
+ b1 BINARY(1) $extra_col_opts,
+ b20 BINARY(20) $extra_col_opts,
+ b255 BINARY(255) $extra_col_opts,
+ pk BINARY PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Valid values
+
+INSERT INTO t1 VALUES ('','','','','','');
+INSERT INTO t1 VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.','a');
+
+--sorted_result
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+
+# Invalid values
+
+INSERT INTO t1 VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256),'b');
+--error ER_DUP_ENTRY
+INSERT INTO t1 SELECT b255, b255, b255, b255, CONCAT('a',b255,b255), 'c' FROM t1;
+
+--sorted_result
+SELECT HEX(b), HEX(b0), HEX(b1), HEX(b20), HEX(b255), HEX(pk) FROM t1 ORDER BY pk;
+
+--error ER_TOO_BIG_FIELDLENGTH
+eval ALTER TABLE t1 ADD COLUMN b257 BINARY(257) $extra_col_opts;
+
+SHOW COLUMNS IN t1;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_binary.test b/storage/rocksdb/mysql-test/rocksdb/t/type_binary.test
new file mode 100644
index 00000000..91749e36
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_binary.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# BINARY column types
+#
+
+--source type_binary.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes-master.opt
new file mode 100644
index 00000000..6ad42e58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000 --rocksdb_records_in_range=50
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes.test
new file mode 100644
index 00000000..f4360ed6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_binary_indexes.test
@@ -0,0 +1,99 @@
+--source include/have_rocksdb.inc
+
+#
+# BINARY and VARBINARY columns with indexes
+#
+
+#######################################
+# TODO:
+# A part of the test is disabled
+# because unique keys are not supported
+#######################################
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (b BINARY,
+ b20 BINARY(20) PRIMARY KEY,
+ v16 VARBINARY(16),
+ v128 VARBINARY(128)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,b20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(b20) FROM t1 ORDER BY b20;
+SELECT HEX(b20) FROM t1 ORDER BY b20;
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(b20) FROM t1 IGNORE INDEX (PRIMARY) ORDER BY b20 DESC;
+SELECT HEX(b20) FROM t1 ORDER BY b20 DESC;
+
+DROP TABLE t1;
+
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (b BINARY,
+ b20 BINARY(20),
+ v16 VARBINARY(16),
+ v128 VARBINARY(128),
+ UNIQUE INDEX b_v (b,v128),
+ pk VARBINARY(10) PRIMARY KEY
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,b20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(b), HEX(v128) FROM t1 WHERE b != 'a' AND v128 > 'varchar';
+--sorted_result
+SELECT HEX(b), HEX(v128) FROM t1 WHERE b != 'a' AND v128 > 'varchar';
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(b), HEX(v128) FROM t1 USE INDEX (b_v) WHERE b != 'a' AND v128 > 'varchar';
+--sorted_result
+SELECT HEX(b), HEX(v128) FROM t1 USE INDEX (b_v) WHERE b != 'a' AND v128 > 'varchar';
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(v128), COUNT(*) FROM t1 GROUP BY HEX(v128);
+--sorted_result
+SELECT HEX(v128), COUNT(*) FROM t1 GROUP BY HEX(v128);
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (b BINARY,
+ b20 BINARY(20),
+ v16 VARBINARY(16),
+ v128 VARBINARY(128),
+ pk VARBINARY(10) PRIMARY KEY,
+ INDEX (v16(10))
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,b20,v16,v128,pk) VALUES ('a','char1','varchar1a','varchar1b',1),('a','char2','varchar2a','varchar2b',2),('b','char3','varchar1a','varchar1b',3),('c','char4','varchar3a','varchar3b',4),('d','char5','varchar4a','varchar3b',5),('e','char6','varchar2a','varchar3b',6);
+INSERT INTO t1 (b,b20,v16,v128,pk) SELECT b,b20,v16,v128,pk+100 FROM t1;
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(SUBSTRING(v16,0,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+--sorted_result
+SELECT HEX(SUBSTRING(v16,7,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(SUBSTRING(v16,0,3)) FROM t1 FORCE INDEX (v16) WHERE v16 LIKE 'varchar%';
+--sorted_result
+SELECT HEX(SUBSTRING(v16,7,3)) FROM t1 FORCE INDEX (v16) WHERE v16 LIKE 'varchar%';
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bit.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_bit.inc
new file mode 100644
index 00000000..ba0c6537
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bit.inc
@@ -0,0 +1,53 @@
+#
+# BIT column type
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Valid values
+
+eval CREATE TABLE t1 (
+ a BIT $extra_col_opts,
+ b BIT(20) $extra_col_opts,
+ c BIT(64) $extra_col_opts,
+ d BIT(1) $extra_col_opts,
+ PRIMARY KEY (c)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+ALTER TABLE t1 DROP COLUMN d;
+eval ALTER TABLE t1 ADD COLUMN d BIT(0) $extra_col_opts;
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b,c,d) VALUES (0,POW(2,20)-1,b'1111111111111111111111111111111111111111111111111111111111111111',1);
+SELECT BIN(a), HEX(b), c+0 FROM t1 WHERE d>0;
+
+INSERT INTO t1 (a,b,c,d) VALUES (1,0,-2,0);
+--sorted_result
+SELECT a+0, b+0, c+0 FROM t1 WHERE d<100;
+
+INSERT INTO t1 (a,b,c,d) VALUES (b'1', 'f', 0xFF, 0x0);
+--sorted_result
+SELECT a+0, b+0, c+0 FROM t1 WHERE d IN (0, 2);
+
+DELETE FROM t1;
+
+# Out of range values
+# (should produce warnings)
+
+INSERT INTO t1 (a,b,c,d) VALUES (0x10,0,0,1);
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+
+INSERT INTO t1 (a,b,c,d) VALUES (0x01,0,0x10000000000000000,0);
+--sorted_result
+SELECT a+0,b+0,c+0,d+0 FROM t1;
+
+DROP TABLE t1;
+
+--error ER_TOO_BIG_DISPLAYWIDTH
+eval CREATE TABLE t1 (pk INT PRIMARY KEY, a BIT(65) $extra_col_opts) ENGINE=rocksdb;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bit.test b/storage/rocksdb/mysql-test/rocksdb/t/type_bit.test
new file mode 100644
index 00000000..8d57cabf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bit.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# BIT column type
+#
+
+--source type_bit.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes.test
new file mode 100644
index 00000000..e4f4bb81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bit_indexes.test
@@ -0,0 +1,113 @@
+--source include/have_rocksdb.inc
+
+#
+# BIT columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ a BIT,
+ b BIT(20) PRIMARY KEY,
+ c BIT(32),
+ d BIT(64)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,d) VALUES
+(0,0xFFFFF,0,1),(0,256,0xAAA,0x12345),(1,16,0,0xFFFFFFF),(0,11,12,13),
+(1,100,101,102),(0,12,13,14),(1,13,14,15),(0,101,201,202),(1,1000,1001,1002),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF);
+
+--replace_column 9 #
+EXPLAIN SELECT b+0 FROM t1 ORDER BY b;
+SELECT b+0 FROM t1 ORDER BY b;
+
+DROP TABLE t1;
+
+--echo # TODO: Unique indexes are not enforced
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ a BIT,
+ b BIT(20),
+ c BIT(32),
+ d BIT(64),
+ pk BIT(10) PRIMARY KEY,
+UNIQUE INDEX b_c (b,c)
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,d,pk) VALUES
+(0,0xFFFFF,0,1,1),(0,256,0xAAA,0x12345,2),(1,16,0,0xFFFFFFF,3),(0,11,12,13,4),
+(1,100,101,102,5),(0,12,13,14,6),(1,13,14,15,7),(0,101,201,202,8),(1,1000,1001,1002,9),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF,10);
+
+--replace_column 9 #
+EXPLAIN SELECT HEX(b+c) FROM t1 WHERE c > 1 OR HEX(b) < 0xFFFFFF;
+--sorted_result
+SELECT HEX(b+c) FROM t1 WHERE c > 1 OR HEX(b) < 0xFFFFFF;
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (
+ a BIT,
+ b BIT(20),
+ c BIT(32),
+ d BIT(64),
+ pk BIT(10) PRIMARY KEY,
+ INDEX(a)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,d,pk) VALUES
+(0,0xFFFFF,0,1,1),(0,256,0xAAA,0x12345,2),(1,16,0,0xFFFFFFF,3),(0,11,12,13,4),
+(1,100,101,102,5),(0,12,13,14,6),(1,13,14,15,7),(0,101,201,202,8),(1,1000,1001,1002,9),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF,10);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+
+DROP TABLE t1;
+
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ a BIT,
+ b BIT(20),
+ c BIT(32),
+ d BIT(64),
+ pk BIT(10) PRIMARY KEY,
+ UNIQUE INDEX (d)
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,d,pk) VALUES
+(0,0xFFFFF,0,1,1),(0,256,0xAAA,0x12345,2),(1,16,0,0xFFFFFFF,3),(0,11,12,13,4),
+(1,100,101,102,5),(0,12,13,14,6),(1,13,14,15,7),(0,101,201,202,8),(1,1000,1001,1002,9),
+(1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF,10);
+
+--replace_column 9 #
+EXPLAIN SELECT d FROM t1 WHERE d BETWEEN 1 AND 10000;
+--sorted_result
+SELECT d+0 FROM t1 WHERE d BETWEEN 1 AND 10000;
+
+DROP TABLE t1;
+
+--enable_parsing
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_blob.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_blob.inc
new file mode 100644
index 00000000..723b3ee5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_blob.inc
@@ -0,0 +1,49 @@
+#
+# BLOB column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ b BLOB $extra_col_opts,
+ b0 BLOB(0) $extra_col_opts,
+ b1 BLOB(1) $extra_col_opts,
+ b300 BLOB(300) $extra_col_opts,
+ bm BLOB(65535) $extra_col_opts,
+ b70k BLOB(70000) $extra_col_opts,
+ b17m BLOB(17000000) $extra_col_opts,
+ t TINYBLOB $extra_col_opts,
+ m MEDIUMBLOB $extra_col_opts,
+ l LONGBLOB $extra_col_opts
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Valid values
+# (cannot get MAX for all columns due to max_allowed_packet limitations)
+
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), HEX(REPEAT('g',1048576)), REPEAT('h',255), REPEAT('i',1048576), HEX(REPEAT('j',1048576)) );
+
+--sorted_result
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+
+# Invalid values (produce warnings, except for mediumblob and longblob columns for which the values are within limits)
+
+INSERT INTO t1 (b,b0,b1,b300,bm,b70k,b17m,t,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+
+--sorted_result
+SELECT LENGTH(b), LENGTH(b0), LENGTH(b1), LENGTH(b300), LENGTH(bm), LENGTH(b70k), LENGTH(b17m), LENGTH(t), LENGTH(m), LENGTH(l) FROM t1;
+
+--error ER_TOO_BIG_DISPLAYWIDTH
+ALTER TABLE t1 ADD COLUMN bbb BLOB(4294967296);
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_blob.test b/storage/rocksdb/mysql-test/rocksdb/t/type_blob.test
new file mode 100644
index 00000000..54859707
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_blob.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# BLOB column types
+#
+
+--source type_blob.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes-master.opt
new file mode 100644
index 00000000..6ad42e58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000 --rocksdb_records_in_range=50
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes.test
new file mode 100644
index 00000000..24c70e8e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_blob_indexes.test
@@ -0,0 +1,176 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+#
+# BLOB columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ b BLOB,
+ t TINYBLOB,
+ m MEDIUMBLOB,
+ l LONGBLOB,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+
+--replace_column 1 # 2 # 3 # 4 # 5 # 7 # 8 # 9 # 10 #
+EXPLAIN SELECT SUBSTRING(b,16) AS f FROM t1 WHERE b IN ('test1','test2') ORDER BY f;
+SELECT SUBSTRING(b,16) AS f FROM t1 WHERE b IN ('test1','test2') ORDER BY f;
+
+--replace_column 1 # 2 # 3 # 4 # 5 # 7 # 8 # 9 # 10 #
+EXPLAIN SELECT SUBSTRING(b,16) AS f FROM t1 USE INDEX () WHERE b IN ('test1','test2') ORDER BY f;
+SELECT SUBSTRING(b,16) AS f FROM t1 USE INDEX () WHERE b IN ('test1','test2') ORDER BY f;
+
+DROP TABLE t1;
+
+
+CREATE TABLE t1 (
+ b BLOB,
+ t TINYBLOB,
+ m MEDIUMBLOB,
+ l LONGBLOB,
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ UNIQUE INDEX l_t (l(256),t(64))
+) ENGINE=rocksdb;
+
+--replace_column 6 # 7 # 10 # 11 #
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+
+# Here we are getting possible key l_t, but not the final key
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 FORCE INDEX (l_t) WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+SELECT SUBSTRING(t,64), SUBSTRING(l,256) FROM t1 FORCE INDEX (l_t) WHERE t!=l AND l NOT IN ('test1') ORDER BY t, l DESC;
+
+DROP TABLE t1;
+
+
+CREATE TABLE t1 (
+ b BLOB,
+ t TINYBLOB,
+ m MEDIUMBLOB,
+ l LONGBLOB,
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ INDEX (m(128))
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (b,t,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+(HEX('abcd'),HEX('def'),HEX('a'),HEX('abc')),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 IGNORE INDEX FOR ORDER BY (m) WHERE m = 'test1' ORDER BY f DESC;
+SELECT SUBSTRING(m,128) AS f FROM t1 IGNORE INDEX FOR ORDER BY (m) WHERE m = 'test1' ORDER BY f DESC;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b BLOB,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b TINYBLOB,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b MEDIUMBLOB,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b LONGBLOB,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bool.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_bool.inc
new file mode 100644
index 00000000..cddc0822
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bool.inc
@@ -0,0 +1,64 @@
+#
+# BOOLEAN column type
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ b1 BOOL $extra_col_opts,
+ b2 BOOLEAN $extra_col_opts
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Valid values
+
+INSERT INTO t1 (b1,b2) VALUES (1,TRUE);
+SELECT b1,b2 FROM t1;
+
+INSERT INTO t1 (b1,b2) VALUES (FALSE,0);
+--sorted_result
+SELECT b1,b2 FROM t1;
+
+INSERT INTO t1 (b1,b2) VALUES (2,3);
+--sorted_result
+SELECT b1,b2 FROM t1;
+
+INSERT INTO t1 (b1,b2) VALUES (-1,-2);
+--sorted_result
+SELECT b1,b2 FROM t1;
+
+--sorted_result
+SELECT IF(b1,'true','false') AS a, IF(b2,'true','false') AS b FROM t1;
+
+--sorted_result
+SELECT b1,b2 FROM t1 WHERE b1 = TRUE;
+
+--sorted_result
+SELECT b1,b2 FROM t1 WHERE b2 = FALSE;
+
+# Invalid values
+
+INSERT INTO t1 (b1,b2) VALUES ('a','b');
+--sorted_result
+SELECT b1,b2 FROM t1;
+
+INSERT INTO t1 (b1,b2) VALUES (128,-129);
+--sorted_result
+SELECT b1,b2 FROM t1;
+
+# This is why we don't have zerofill and unsigned tests
+# for boolean columns:
+--error ER_PARSE_ERROR
+eval ALTER TABLE t1 ADD COLUMN b3 BOOLEAN UNSIGNED $extra_col_opts;
+
+--error ER_PARSE_ERROR
+eval ALTER TABLE ADD COLUMN b3 BOOL ZEROFILL $extra_col_opts;
+
+DROP TABLE t1;
+
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_bool.test b/storage/rocksdb/mysql-test/rocksdb/t/type_bool.test
new file mode 100644
index 00000000..d5a3b9be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_bool.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# BOOLEAN column type
+#
+
+--source type_bool.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_char.inc
new file mode 100644
index 00000000..d770dc60
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char.inc
@@ -0,0 +1,45 @@
+#
+# CHAR column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ c CHAR $extra_col_opts,
+ c0 CHAR(0) $extra_col_opts,
+ c1 CHAR(1) $extra_col_opts,
+ c20 CHAR(20) $extra_col_opts,
+ c255 CHAR(255) $extra_col_opts,
+ PRIMARY KEY (c255)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Valid values
+
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('','','','','');
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('a','','b','abcdefghi klmnopqrst', 'Creating an article for the Knowledgebase is similar to asking questions. First, navigate to the category where you feel the article should be. Once there, double check that an article doesn\'t already exist which would work.');
+
+--sorted_result
+SELECT c,c0,c1,c20,c255 FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (c,c0,c1,c20,c255) VALUES ('abc', 'a', 'abc', REPEAT('a',21), REPEAT('x',256));
+INSERT INTO t1 (c,c0,c1,c20,c255) SELECT c255, c255, c255, c255, CONCAT('a',c255,c1) FROM t1;
+
+--sorted_result
+SELECT c,c0,c1,c20,c255 FROM t1;
+
+--sorted_result
+SELECT DISTINCT c20, REPEAT('a',LENGTH(c20)), COUNT(*) FROM t1 GROUP BY c1, c20;
+
+--error ER_TOO_BIG_FIELDLENGTH
+eval ALTER TABLE t1 ADD COLUMN c257 CHAR(257) $extra_col_opts;
+
+DROP TABLE t1;
+
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char.test b/storage/rocksdb/mysql-test/rocksdb/t/type_char.test
new file mode 100644
index 00000000..347f83ca
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+#
+# CHAR column types
+#
+
+--source type_char.inc
+
+# Issue #226
+CREATE TABLE t1(c1 CHAR(0) NOT NULL);
+INSERT INTO t1 VALUES('a');
+SELECT * FROM t1;
+DROP TABLE t1;
+
+# Issue #259
+CREATE TABLE t1(a char(10) character set utf8 collate utf8_bin primary key);
+INSERT INTO t1 VALUES ('one'),('two'),('three'),('four'),('five');
+(SELECT * FROM t1 LIMIT 1) UNION (SELECT * FROM t1);
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes.test
new file mode 100644
index 00000000..6ee2f03e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes.test
@@ -0,0 +1,107 @@
+--source include/have_rocksdb.inc
+
+#
+# CHAR and VARCHAR columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ c CHAR,
+ c20 CHAR(20) PRIMARY KEY,
+ v16 VARCHAR(16),
+ v128 VARCHAR(128)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (c,c20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+
+--replace_column 9 #
+EXPLAIN SELECT c20 FROM t1 ORDER BY c20;
+SELECT c20 FROM t1 ORDER BY c20;
+
+--replace_column 9 #
+EXPLAIN SELECT c20 FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY c20;
+SELECT c20 FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY c20;
+
+DROP TABLE t1;
+
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ c CHAR,
+ c20 CHAR(20),
+ v16 VARCHAR(16),
+ v128 VARCHAR(128),
+ pk CHAR(64) PRIMARY KEY,
+ UNIQUE INDEX c_v (c,v128)
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (c,c20,v16,v128) VALUES ('a','char1','varchar1a','varchar1b'),('a','char2','varchar2a','varchar2b'),('b','char3','varchar1a','varchar1b'),('c','char4','varchar3a','varchar3b');
+
+--replace_column 9 #
+EXPLAIN SELECT c, v128 FROM t1 WHERE c != 'a' AND v128 > 'varchar';
+--sorted_result
+SELECT c, v128 FROM t1 WHERE c != 'a' AND v128 > 'varchar';
+
+--replace_column 9 #
+EXPLAIN SELECT v128, COUNT(*) FROM t1 GROUP BY v128;
+--sorted_result
+SELECT v128, COUNT(*) FROM t1 GROUP BY v128;
+
+--replace_column 9 #
+EXPLAIN SELECT v128, COUNT(*) FROM t1 USE INDEX FOR GROUP BY (c_v) GROUP BY v128;
+--sorted_result
+SELECT v128, COUNT(*) FROM t1 USE INDEX FOR GROUP BY (c_v) GROUP BY v128;
+
+SET SESSION optimizer_switch = 'engine_condition_pushdown=on';
+--replace_column 9 #
+EXPLAIN SELECT c,c20,v16,v128 FROM t1 WHERE c > 'a';
+--sorted_result
+SELECT c,c20,v16,v128 FROM t1 WHERE c > 'a';
+SET SESSION optimizer_switch = @@global.optimizer_switch;
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (
+ c CHAR,
+ c20 CHAR(20),
+ v16 VARCHAR(16),
+ v128 VARCHAR(128),
+ pk VARCHAR(64) PRIMARY KEY,
+ INDEX (v16)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (c,c20,v16,v128,pk) VALUES ('a','char1','varchar1a','varchar1b','1'),('a','char2','varchar2a','varchar2b','2'),('b','char3','varchar1a','varchar1b','3'),('c','char4','varchar3a','varchar3b','4');
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(v16,0,3) FROM t1 WHERE v16 LIKE 'varchar%';
+--sorted_result
+SELECT SUBSTRING(v16,7,3) FROM t1 WHERE v16 LIKE 'varchar%';
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(v16,0,3) FROM t1 IGNORE INDEX (v16) WHERE v16 LIKE 'varchar%';
+--sorted_result
+SELECT SUBSTRING(v16,7,3) FROM t1 IGNORE INDEX (v16) WHERE v16 LIKE 'varchar%';
+
+--replace_column 9 #
+EXPLAIN SELECT c,c20,v16,v128 FROM t1 WHERE v16 = 'varchar1a' OR v16 = 'varchar3a' ORDER BY v16;
+--sorted_result
+SELECT c,c20,v16,v128 FROM t1 WHERE v16 = 'varchar1a' OR v16 = 'varchar3a' ORDER BY v16;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation.test b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation.test
new file mode 100644
index 00000000..d231236b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_char_indexes_collation.test
@@ -0,0 +1,126 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+# Test if unknown collation works.
+set session debug_dbug= "+d,myrocks_enable_unknown_collation_index_only_scans";
+create table t (id int not null auto_increment primary key,
+ c varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci,
+ key sk (c));
+insert into t (c) values ('☀'), ('ß');
+--replace_column 9 #
+explain select c from t;
+select c from t;
+drop table t;
+set session debug_dbug= "-d,myrocks_enable_unknown_collation_index_only_scans";
+
+# Testing if all characters in latin1 charset get restored correctly. This is
+# done by comparing results from a PK scan.
+create table t (id int not null auto_increment,
+ c1 varchar(1) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+ c2 char(1) CHARACTER SET latin1 COLLATE latin1_general_ci,
+ primary key (id),
+ key sk1 (c1),
+ key sk2 (c2));
+
+let $i = 0;
+
+--disable_query_log
+while ($i < 256)
+{
+ --eval insert into t (c1, c2) values (CHAR('$i'), CHAR('$i'))
+ inc $i;
+}
+--enable_query_log
+
+--replace_column 9 #
+explain select hex(c1) from t order by c1;
+--replace_column 9 #
+explain select hex(c1) from t IGNORE INDEX (sk1) order by c1;
+
+--replace_column 9 #
+explain select hex(c2) from t order by c2;
+--replace_column 9 #
+explain select hex(c2) from t IGNORE INDEX (sk1) order by c2;
+
+--let $file1=$MYSQLTEST_VARDIR/tmp/filesort_order
+--let $file2=$MYSQLTEST_VARDIR/tmp/sk_order
+--disable_query_log
+--eval select hex(weight_string(c1)) INTO OUTFILE '$file1' from t order by c1
+--eval select hex(weight_string(c1)) INTO OUTFILE '$file2' from t IGNORE INDEX (sk1) order by c1
+--enable_query_log
+
+--diff_files $file1 $file2
+--remove_file $file1
+--remove_file $file2
+
+--disable_query_log
+--eval select hex(weight_string(c2)) INTO OUTFILE '$file1' from t order by c2
+--eval select hex(weight_string(c2)) INTO OUTFILE '$file2' from t IGNORE INDEX (sk1) order by c2
+--enable_query_log
+
+--diff_files $file1 $file2
+--remove_file $file1
+--remove_file $file2
+
+truncate t;
+
+# Test handling of spaces at the end of fields.
+insert into t (c1, c2) values ('Asdf ', 'Asdf ');
+select char_length(c1), char_length(c2), c1, c2 from t;
+
+drop table t;
+
+create table t (id int not null auto_increment,
+ c2 char(255) CHARACTER SET latin1 COLLATE latin1_general_ci,
+ primary key (id),
+ unique key sk2 (c2));
+
+insert into t (c2) values ('Asdf');
+--error ER_DUP_ENTRY
+insert into t (c2) values ('asdf ');
+
+drop table t;
+
+create table t (id int not null auto_increment,
+ c1 varchar(256) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+ primary key (id),
+ unique key sk1 (c1));
+
+insert into t (c1) values ('Asdf');
+--error ER_DUP_ENTRY
+insert into t (c1) values ('asdf ');
+--error ER_DUP_ENTRY
+insert into t (c1) values ('asdf');
+
+drop table t;
+
+create table t (id int not null auto_increment,
+ c1 varchar(256) CHARACTER SET latin1 COLLATE latin1_swedish_ci,
+ primary key (id),
+ unique key sk1 (c1(1)));
+
+insert into t (c1) values ('Asdf');
+insert into t (c1) values ('bbbb ');
+--error ER_DUP_ENTRY
+insert into t (c1) values ('a ');
+
+--replace_column 9 #
+explain select c1 from t;
+select c1 from t;
+
+drop table t;
+
+# Test varchar keyparts with key prefix
+set session rocksdb_verify_row_debug_checksums = on;
+create table t (id int primary key, email varchar(100), KEY email_i (email(30))) engine=rocksdb default charset=latin1;
+insert into t values (1, ' a');
+--replace_column 9 #
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+drop table t;
+
+# Test varchar with length greater than 255
+create table t (id int primary key, email varchar(767), KEY email_i (email)) engine=rocksdb default charset=latin1;
+insert into t values (1, REPEAT('a', 700));
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.inc
new file mode 100644
index 00000000..18ed7436
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.inc
@@ -0,0 +1,47 @@
+#
+# Date and time column types
+# (DATE, DATETIME, TIMESTAMP, TIME, YEAR)
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+set @save_time_zone=@@time_zone;
+set time_zone='UTC';
+eval CREATE TABLE t1 (
+ d DATE $extra_col_opts,
+ dt DATETIME $extra_col_opts,
+ ts TIMESTAMP $extra_col_opts,
+ t TIME $extra_col_opts,
+ y YEAR $extra_col_opts,
+ y4 YEAR(4) $extra_col_opts,
+ y2 YEAR(2) $extra_col_opts,
+ pk DATETIME PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+SET @tm = '2012-04-09 05:27:00';
+
+# Valid values
+# '1970-01-01 00:00:01'
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('1000-01-01', '1000-01-01 00:00:00', FROM_UNIXTIME(1), '-838:59:59', '1901', '1901', '00','2012-12-12 12:12:12'),
+('9999-12-31', '9999-12-31 23:59:59', FROM_UNIXTIME(2147483647), '838:59:59', '2155', '2155', '99','2012-12-12 12:12:13'),
+('0000-00-00', '0000-00-00 00:00:00', '0000-00-00 00:00:00', '00:00:00', '0', '0', '0','2012-12-12 12:12:14'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),YEAR(@tm),YEAR(@tm),'2012-12-12 12:12:15');
+
+--sorted_result
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (d,dt,ts,t,y,y4,y2,pk) VALUES
+('999-13-32', '999-11-31 00:00:00', '0', '-839:00:00', '1900', '1900', '-1','2012-12-12 12:12:16');
+
+SELECT d,dt,ts,t,y,y4,y2 FROM t1;
+set time_zone=@save_time_zone;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.test b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.test
new file mode 100644
index 00000000..af4e006c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time.test
@@ -0,0 +1,9 @@
+--source include/have_rocksdb.inc
+
+#
+# Date and time column types
+# (DATE, DATETIME, TIMESTAMP, TIME, YEAR)
+#
+
+--source type_date_time.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes.test
new file mode 100644
index 00000000..06cf86b7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_date_time_indexes.test
@@ -0,0 +1,157 @@
+--source include/have_rocksdb.inc
+
+#
+# Date and time columns with indexes
+# (DATE, DATETIME, TIMESTAMP, TIME, YEAR)
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+
+CREATE TABLE t1 (
+ d DATE,
+ dt DATETIME PRIMARY KEY,
+ ts TIMESTAMP,
+ t TIME,
+ y YEAR
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+SET @tm = '2012-04-09 05:27:00';
+
+INSERT INTO t1 (d,dt,ts,t,y) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm));
+
+--replace_column 9 #
+EXPLAIN SELECT dt FROM t1 ORDER BY dt LIMIT 3;
+SELECT dt FROM t1 ORDER BY dt LIMIT 3;
+
+--replace_column 9 #
+EXPLAIN SELECT dt FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY dt LIMIT 3;
+SELECT dt FROM t1 FORCE INDEX FOR ORDER BY (PRIMARY) ORDER BY dt LIMIT 3;
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (d,dt,ts,t,y) VALUES
+('2012-01-11', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000');
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ d DATE,
+ dt DATETIME,
+ ts TIMESTAMP,
+ t TIME,
+ y YEAR,
+ pk TIME PRIMARY KEY,
+ INDEX (ts)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+SET @tm = '2012-04-09 05:27:00';
+
+INSERT INTO t1 (d,dt,ts,t,y,pk) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000','12:00:00'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001','12:01:00'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999','12:02:00'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998','12:03:00'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994','12:04:00'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),'12:05:00');
+
+--replace_column 9 #
+EXPLAIN SELECT ts FROM t1 WHERE ts > NOW();
+--sorted_result
+SELECT ts FROM t1 WHERE ts > NOW();
+
+--replace_column 9 #
+EXPLAIN SELECT ts FROM t1 USE INDEX () WHERE ts > NOW();
+--sorted_result
+SELECT ts FROM t1 USE INDEX () WHERE ts > NOW();
+
+DROP TABLE t1;
+
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ d DATE,
+ dt DATETIME,
+ ts TIMESTAMP,
+ t TIME,
+ y YEAR,
+ pk YEAR PRIMARY KEY,
+ UNIQUE INDEX d_t (d,t)
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+SET @tm = '2012-04-09 05:27:00';
+
+INSERT INTO t1 (d,dt,ts,t,y,pk) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000','1990'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001','1991'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999','1992'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998','1993'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994','1994'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),'1995');
+
+--replace_column 9 #
+EXPLAIN SELECT d, t FROM t1 WHERE CONCAT(d,' ',t) != CURRENT_DATE();
+--sorted_result
+SELECT d, t FROM t1 WHERE CONCAT(d,' ',t) != CURRENT_DATE();
+
+--replace_column 9 #
+EXPLAIN SELECT d, t FROM t1 IGNORE INDEX (d_t) WHERE CONCAT(d,' ',t) != CURRENT_DATE();
+--sorted_result
+SELECT d, t FROM t1 IGNORE INDEX (d_t) WHERE CONCAT(d,' ',t) != CURRENT_DATE();
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (d,dt,ts,t,y) VALUES
+('2012-01-12', '2010-11-22 12:33:53', '2011-11-14 21:45:55', '00:12:33', '2000');
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (
+ d DATE,
+ dt DATETIME,
+ ts TIMESTAMP,
+ t TIME,
+ y YEAR,
+ pk TIME PRIMARY KEY,
+ INDEX (y,t)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+SET @tm = '2012-04-09 05:27:00';
+
+INSERT INTO t1 (d,dt,ts,t,y,pk) VALUES
+('2012-01-12', '2010-11-22 12:33:54', '2011-11-14 21:45:55', '00:12:33', '2000','18:18:18'),
+('2012-01-12', '2010-11-22 11:43:14', '2011-11-14 21:45:55', '00:12:32', '2001','19:18:18'),
+('2012-03-31', '2011-08-28 21:33:56', '1999-04-30 19:11:08', '12:00:00', '1999','20:18:18'),
+('2012-03-13', '2011-08-27 21:33:56', '1999-03-30 19:11:08', '12:10:00', '1998','21:18:18'),
+('2011-03-31', '2011-08-28 20:33:56', '1997-01-31 11:54:01', '22:04:10', '1994','22:18:18'),
+(DATE(@tm),@tm,TIMESTAMP(@tm),TIME(@tm),YEAR(@tm),'23:18:18');
+
+--replace_column 9 #
+EXPLAIN SELECT y, COUNT(*) FROM t1 GROUP BY y;
+--sorted_result
+SELECT y, COUNT(*) FROM t1 GROUP BY y;
+
+--replace_column 9 #
+EXPLAIN SELECT y, COUNT(*) FROM t1 USE INDEX FOR GROUP BY () GROUP BY y;
+--sorted_result
+SELECT y, COUNT(*) FROM t1 USE INDEX FOR GROUP BY () GROUP BY y;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_decimal-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_decimal-master.opt
new file mode 100644
index 00000000..33e72265
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_decimal-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=10
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_decimal.test b/storage/rocksdb/mysql-test/rocksdb/t/type_decimal.test
new file mode 100644
index 00000000..ee325b34
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_decimal.test
@@ -0,0 +1,88 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+
+--echo #
+--echo # Check that DECIMAL datatype supports 'index-only' scans and is decoded correctly.
+--echo # (Decoding happens from the mem-comparable image in the index, regardless
+--echo # of whether the value part has original value or not)
+--echo #
+
+create table t1 (
+ pk int not null primary key,
+ col1 decimal (2,1) signed,
+ col2 decimal (2,1) unsigned,
+ filler varchar(100),
+ key key1(col1, col2)
+)engine=rocksdb;
+insert into t1 values
+(1,-9.1, 0.7, 'filler'),
+(2,-8.2, 1.6, 'filler'),
+(3, 0.3, 2.5, 'filler'),
+(4, 1.4, 3.4, 'filler'),
+(5, 2.5, 4.3, 'filler'),
+(6, 3.3, 5.3, 'filler');
+insert into t1 select pk+100, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+200, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+1000, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+10000, 9.0, 9.0, 'extra-data' from t1;
+insert into t1 select pk+100000, 9.0, 9.0, 'extra-data' from t1;
+analyze table t1;
+
+--echo # The following can't use index-only:
+--replace_column 9 #
+explain select * from t1 where col1 between -8 and 8;
+
+--echo # This will use index-only:
+--replace_column 9 #
+explain
+select col1, col2 from t1 where col1 between -8 and 8;
+select col1, col2 from t1 where col1 between -8 and 8;
+
+insert into t1 values (11, NULL, 0.9, 'row1-with-null');
+insert into t1 values (10, -8.4, NULL, 'row2-with-null');
+--replace_column 9 #
+explain
+select col1, col2 from t1 force index(key1) where col1 is null or col1 < -7;
+select col1, col2 from t1 force index(key1) where col1 is null or col1 < -7;
+
+--echo # Try an UPDATE
+select * from t1 where pk in (3,4);
+update t1 set col2= col2+0.2 where pk in (3,4);
+select * from t1 where pk in (3,4);
+
+drop table t1;
+
+--echo #
+--echo # Try another DECIMAL-based type that takes more space
+--echo #
+create table t1 (
+ pk int not null primary key,
+ col1 decimal (12,6) signed,
+ col2 decimal (12,6) unsigned,
+ filler varchar(100),
+ key key1(col1, col2)
+)engine=rocksdb;
+insert into t1 values
+(1,-900.001, 000.007, 'filler'),
+(2,-700.002, 100.006, 'filler'),
+(3, 000.003, 200.005, 'filler'),
+(4, 100.004, 300.004, 'filler'),
+(5, 200.005, 400.003, 'filler'),
+(6, 300.003, 500.003, 'filler');
+insert into t1 select pk+100, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+200, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+1000, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+10000, col1+20000, col2+20000, 'extra-data' from t1;
+insert into t1 select pk+100000, col1+20000, col2+20000, 'extra-data' from t1;
+analyze table t1;
+
+--replace_column 9 #
+explain
+select col1, col2 from t1 force index(key1) where col1 between -800 and 800;
+select col1, col2 from t1 force index(key1) where col1 between -800 and 800;
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_enum.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_enum.inc
new file mode 100644
index 00000000..8184f626
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_enum.inc
@@ -0,0 +1,50 @@
+#
+# ENUM column type
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Valid values.
+# We cannot test the maximum of 65,536 here,
+# because mysqltest has the standard limit of MAX_QUERY=256K;
+# but we can at least try 257
+
+eval CREATE TABLE t1 (
+ a ENUM('') $extra_col_opts,
+ b ENUM('test1','test2','test3','test4','test5') $extra_col_opts,
+ c ENUM('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i','3j','3k','3l','3m','3n','3o','3p','3q','3r','3s','3t','3u','3v','3w','3x','3y','3z','40','41','42','43','44','45','46','47','48','49','4a','4b','4c','4d','4e','4f','4g','4h','4i','4j','4k','4l','4m','4n','4o','4p','4q','4r','4s','4t','4u','4v','4w','4x','4y','4z','50','51','52','53','54','55','56','57','58','59','5a','5b','5c','5d','5e','5f','5g','5h','5i','5j','5k','5l','5m','5n','5o','5p','5q','5r','5s','5t','5u','5v','5w','5x','5y','5z','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f','6g','6h','6i','6j','6k','6l','6m','6n','6o','6p','6q','6r','6s','6t','6u','6v','6w','6x','6y','6z','70','71','72','73','74','75') $extra_col_opts,
+ PRIMARY KEY (b)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b,c) VALUES ('','test2','4'),('',5,2);
+SELECT a,b,c FROM t1;
+
+# Out of range values
+# (should produce warnings)
+
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+--sorted_result
+SELECT a,b,c FROM t1;
+
+# Non-unique values in enum
+# (should produce a warning)
+eval ALTER TABLE t1 ADD COLUMN e ENUM('a','A') $extra_col_opts;
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b,c,e) VALUES ('','test3','75','A');
+--sorted_result
+SELECT a,b,c,e FROM t1;
+
+# Simple comparison
+
+--sorted_result
+SELECT a,b,c,e FROM t1 WHERE b='test2' OR a != '';
+
+DROP TABLE t1;
+
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_enum.test b/storage/rocksdb/mysql-test/rocksdb/t/type_enum.test
new file mode 100644
index 00000000..d79469b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_enum.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# ENUM column type
+#
+
+--source type_enum.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes.test
new file mode 100644
index 00000000..d7086a45
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_enum_indexes.test
@@ -0,0 +1,93 @@
+--source include/have_rocksdb.inc
+
+#
+# ENUM columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ a ENUM('N.America','S.America','Africa','Europe','Australia','Asia','Antarctica'),
+ b ENUM('test1','test2','test3','test4','test5'),
+ c ENUM('1a','1b','1d','1j','4a','4z','5a','5b','6v','6z'),
+ pk ENUM('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+ UNIQUE KEY a_b (a,b)
+) ENGINE=rocksdb;
+
+
+INSERT INTO t1 (a,b,c,pk) VALUES
+('N.America','test1','5a',1),('Europe','test1','5b',2),('Europe','test2','6v',3),
+('Africa','test3','4z',4),('Africa','test4','1j',5),('Antarctica','test4','1d',6);
+
+SHOW INDEX IN t1;
+
+--replace_column 9 #
+EXPLAIN SELECT a FROM t1 WHERE b > 'test2' ORDER BY a;
+SELECT a FROM t1 WHERE b > 'test2' ORDER BY a;
+
+--replace_column 9 #
+EXPLAIN SELECT a FROM t1 FORCE INDEX (a_b) WHERE b > 'test2' ORDER BY a;
+SELECT a FROM t1 FORCE INDEX (a_b) WHERE b > 'test2' ORDER BY a;
+
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (
+ a ENUM('N.America','S.America','Africa','Europe','Australia','Asia','Antarctica'),
+ b ENUM('test1','test2','test3','test4','test5'),
+ c ENUM('1a','1b','1d','1j','4a','4z','5a','5b','6v','6z') PRIMARY KEY
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b,c) VALUES
+('N.America','test1','5a'),('Europe','test1','5b'),('Europe','test2','6v'),
+('Africa','test3','4z'),('Africa','test4','1j'),('Antarctica','test4','1d');
+
+SHOW INDEX IN t1;
+
+--replace_column 9 #
+EXPLAIN SELECT c FROM t1 WHERE c BETWEEN '1d' AND '6u';
+--sorted_result
+SELECT c FROM t1 WHERE c BETWEEN '1d' AND '6u';
+
+--replace_column 9 #
+EXPLAIN SELECT c FROM t1 USE INDEX () WHERE c BETWEEN '1d' AND '6u';
+--sorted_result
+SELECT c FROM t1 USE INDEX () WHERE c BETWEEN '1d' AND '6u';
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ a ENUM('N.America','S.America','Africa','Europe','Australia','Asia','Antarctica'),
+ b ENUM('test1','test2','test3','test4','test5'),
+ c ENUM('1a','1b','1d','1j','4a','4z','5a','5b','6v','6z'),
+ pk ENUM('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+ INDEX(b)
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b,c,pk) VALUES
+('N.America','test1','5a',1),('Europe','test1','5b',2),('Europe','test2','6v',3),
+('Africa','test3','4z',4),('Africa','test4','1j',5),('Antarctica','test4','1d',6);
+
+SHOW INDEX IN t1;
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT b FROM t1;
+--sorted_result
+SELECT DISTINCT b FROM t1;
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT b FROM t1 IGNORE INDEX (b);
+--sorted_result
+SELECT DISTINCT b FROM t1 IGNORE INDEX (b);
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.inc
new file mode 100644
index 00000000..424f7c4f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.inc
@@ -0,0 +1,85 @@
+#
+# Fixed point types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ d DECIMAL $extra_col_opts,
+ d0 DECIMAL(0) $extra_col_opts,
+ d1_1 DECIMAL(1,1) $extra_col_opts,
+ d10_2 DECIMAL(10,2) $extra_col_opts,
+ d60_10 DECIMAL(60,10) $extra_col_opts,
+ n NUMERIC $extra_col_opts,
+ n0_0 NUMERIC(0,0) $extra_col_opts,
+ n1 NUMERIC(1) $extra_col_opts,
+ n20_4 NUMERIC(20,4) $extra_col_opts,
+ n65_4 NUMERIC(65,4) $extra_col_opts,
+ pk NUMERIC $extra_col_opts PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Always valid values
+
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (100,123456,0.3,40000.25,123456789123456789.10001,1024,7000.0,8.0,999999.9,9223372036854775807,1);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.0,9999999999.0,0.9,99999999.99,99999999999999999999999999999999999999999999999999.9999999999,9999999999.0,9999999999.0,9.0,9999999999999999.9999,9999999999999999999999999999999999999999999999999999999999999.9999,3);
+
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+
+# Values which can be valid or not,
+# depending on whether columns are SIGNED or UNSIGNED
+# (if not valid should produce warnings)
+
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-100,-123456,-0.3,-40000.25,-123456789123456789.10001,-1024,-7000.0,-8.0,-999999.9,-9223372036854775807,4);
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (-9999999999.0,-9999999999.0,-0.9,-99999999.99,-99999999999999999999999999999999999999999999999999.9999999999,-9999999999.0,-9999999999.0,-9.0,-9999999999999999.9999,-9999999999999999999999999999999999999999999999999999999999999.9999,5);
+
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1 WHERE n20_4 = 9999999999999999.9999 OR d < 100;
+
+# Invalid values
+
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 6
+);
+
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (10000000000.0,10000000000.0,1.1,100000000.99,100000000000000000000000000000000000000000000000000.0,10000000000.0,10000000000.0,10.0,10000000000000000.9999,10000000000000000000000000000000000000000000000000000000000000.9999,7);
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+
+INSERT INTO t1 (d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4,pk) VALUES (9999999999.1,9999999999.1,1.9,99999999.001,99999999999999999999999999999999999999999999999999.99999999991,9999999999.1,9999999999.1,9.1,9999999999999999.00001,9999999999999999999999999999999999999999999999999999999999999.11111,8);
+--sorted_result
+SELECT d,d0,d1_1,d10_2,d60_10,n,n0_0,n1,n20_4,n65_4 FROM t1;
+
+--error ER_TOO_BIG_PRECISION
+eval ALTER TABLE t1 ADD COLUMN n66 NUMERIC(66) $extra_col_opts;
+
+--error ER_TOO_BIG_PRECISION
+eval ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(66,6) $extra_col_opts;
+
+--error ER_TOO_BIG_SCALE
+eval ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(66,66) $extra_col_opts;
+
+DROP TABLE t1;
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.test b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.test
new file mode 100644
index 00000000..4f2c09d1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# Fixed point types
+#
+
+--source type_fixed.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes.test
new file mode 100644
index 00000000..e9e6df58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_fixed_indexes.test
@@ -0,0 +1,107 @@
+--source include/have_rocksdb.inc
+
+#
+# Fixed point columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+
+CREATE TABLE t1 (
+ d1 DECIMAL(10,2) PRIMARY KEY,
+ d2 DECIMAL(60,10),
+ n1 NUMERIC,
+ n2 NUMERIC(65,4)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (d1,d2,n1,n2) VALUES
+(10.22,60.12345,123456,14.3456),
+(10.0,60.12345,123456,14),
+(11.14,15,123456,13),
+(100,100,1,2),
+(0,0,0,0),
+(4540424564.23,3343303441.0,12,13),
+(15,17,23,100000);
+
+--replace_column 9 #
+EXPLAIN SELECT d1 FROM t1 ORDER BY d1 DESC;
+SELECT d1 FROM t1 ORDER BY d1 DESC;
+
+--replace_column 9 #
+EXPLAIN SELECT d1 FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY d1 DESC;
+SELECT d1 FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY d1 DESC;
+
+DROP TABLE t1;
+
+# --error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ d1 DECIMAL(10,2),
+ d2 DECIMAL(60,10),
+ n1 NUMERIC,
+ n2 NUMERIC(65,4),
+ pk NUMERIC PRIMARY KEY,
+ UNIQUE INDEX n1_n2 (n1,n2)
+) ENGINE=rocksdb;
+
+# --disable_parsing
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (d1,d2,n1,n2,pk) VALUES
+(10.22,60.12345,123456,14.3456,1),
+(10.0,60.12345,123456,14,2),
+(11.14,15,123456,13,3),
+(100,100,1,2,4),
+(0,0,0,0,5),
+(4540424564.23,3343303441.0,12,13,6),
+(15,17,23,100000,7);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT n1+n2 FROM t1;
+--sorted_result
+SELECT DISTINCT n1+n2 FROM t1;
+
+DROP TABLE t1;
+
+#--enable_parsing
+
+CREATE TABLE t1 (
+ d1 DECIMAL(10,2),
+ d2 DECIMAL(60,10),
+ n1 NUMERIC,
+ n2 NUMERIC(65,4),
+ pk DECIMAL(20,10) PRIMARY KEY,
+ INDEX (d2)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (d1,d2,n1,n2,pk) VALUES
+(10.22,60.12345,123456,14.3456,1),
+(10.0,60.12345,123456,14,2),
+(11.14,15,123456,13,3),
+(100,100,1,2,4),
+(0,0,0,0,5),
+(4540424564.23,3343303441.0,12,13,6),
+(15,17,23,100000,7);
+
+--replace_column 9 #
+EXPLAIN SELECT d2, COUNT(*) FROM t1 GROUP BY d2;
+--sorted_result
+SELECT d2, COUNT(*) FROM t1 GROUP BY d2;
+
+--replace_column 9 #
+EXPLAIN SELECT d2, COUNT(*) FROM t1 IGNORE INDEX FOR GROUP BY (d2) GROUP BY d2;
+--sorted_result
+SELECT d2, COUNT(*) FROM t1 IGNORE INDEX FOR GROUP BY (d2) GROUP BY d2;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_float.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_float.inc
new file mode 100644
index 00000000..ff58b73a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_float.inc
@@ -0,0 +1,121 @@
+#
+# Float types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ f FLOAT $extra_col_opts,
+ f0 FLOAT(0) $extra_col_opts,
+ r1_1 REAL(1,1) $extra_col_opts,
+ f23_0 FLOAT(23) $extra_col_opts,
+ f20_3 FLOAT(20,3) $extra_col_opts,
+ d DOUBLE $extra_col_opts,
+ d1_0 DOUBLE(1,0) $extra_col_opts,
+ d10_10 DOUBLE PRECISION (10,10) $extra_col_opts,
+ d53 DOUBLE(53,0) $extra_col_opts,
+ d53_10 DOUBLE(53,10) $extra_col_opts,
+ pk DOUBLE $extra_col_opts PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Always valid values
+
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (12345.12345,12345.12345,0.9,123456789.123,56789.987,11111111.111,8.0,0.0123456789,1234566789123456789,99999999999999999.99999999,1);
+
+--sorted_result
+--query_vertical SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1
+
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (0,0,0,0,0,0,0,0,0,0,2);
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+ 99999999999999999999999999999999999999,
+ 99999999999999999999999999999999999999.9999999999999999,
+ 0.9,
+ 99999999999999999999999999999999999999.9,
+ 99999999999999999.999,
+ 999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+ 9,
+ 0.9999999999,
+ 1999999999999999999999999999999999999999999999999999999,
+ 19999999999999999999999999999999999999999999.9999999999,
+ 3
+);
+
+--sorted_result
+--query_vertical SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1
+
+# Values which can be valid or not,
+# depending on whether columns are SIGNED or UNSIGNED
+# (if not valid should produce warnings)
+
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (-999999999999999999999999,-99999999999.999999999999,-0.9,-999.99999999999999999999,-99999999999999999.999,-999999999999999999999999999999999999999999999999999999999999-0.999,-9,-.9999999999,-999999999999999999999999999999.99999999999999999999999,-9999999999999999999999999999999999999999999.9999999999,4);
+
+--sorted_result
+--query_vertical SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1
+
+--sorted_result
+query_vertical
+SELECT
+ CONCAT('', MAX(f)),
+ CONCAT('', MAX(f0)),
+ CONCAT('', MAX(r1_1)),
+ CONCAT('', MAX(f23_0)),
+ CONCAT('', MAX(f20_3)),
+ CONCAT('', MAX(d)),
+ CONCAT('', MAX(d1_0)),
+ CONCAT('', MAX(d10_10)),
+ CONCAT('', MAX(d53)),
+ CONCAT('', MAX(d53_10)) FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 9999999999999999999999999999999999999999999999999999999999999.9999,
+ 5
+);
+
+--sorted_result
+--query_vertical SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1
+
+INSERT INTO t1 (f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10,pk) VALUES (
+ 999999999999999999999999999999999999999,
+ 999999999999999999999999999999999999999.9999999999999999,
+ 1.9,
+ 999999999999999999999999999999999999999.9,
+ 999999999999999999.999,
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999,
+ 99,
+ 1.9999999999,
+ 1999999999999999999999999999999999999999999999999999999,
+ 19999999999999999999999999999999999999999999.9999999999,
+ 6
+);
+
+--sorted_result
+--query_vertical SELECT f,f0,r1_1,f23_0,f20_3,d,d1_0,d10_10,d53,d53_10 FROM t1
+
+# Error occurs on MySQL but not on MariaDB:
+# --error ER_TOO_BIG_DISPLAYWIDTH
+eval ALTER TABLE t1 ADD COLUMN d0_0 DOUBLE(0,0) $extra_col_opts;
+
+--error ER_TOO_BIG_PRECISION
+eval ALTER TABLE t1 ADD COLUMN n66_6 DECIMAL(256,1) $extra_col_opts;
+
+# Error occurs on MySQL but not on MariaDB:
+# --error ER_TOO_BIG_SCALE
+eval ALTER TABLE t1 ADD COLUMN n66_66 DECIMAL(40,35) $extra_col_opts;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_float.test b/storage/rocksdb/mysql-test/rocksdb/t/type_float.test
new file mode 100644
index 00000000..1f184999
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_float.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# Float types
+#
+
+--source type_float.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes.test
new file mode 100644
index 00000000..907bc614
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_float_indexes.test
@@ -0,0 +1,175 @@
+--source include/have_rocksdb.inc
+
+#
+# Float type columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ f FLOAT PRIMARY KEY,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp) VALUES
+(1.2345,1422.22,1.2345,1234567.89),
+(0,0,0,0),
+(-1,-1,-1,-1),
+(17.5843,4953453454.44,29229114.0,1111111.23),
+(4644,1422.22,466664.999,0.5);
+
+--replace_column 9 #
+EXPLAIN SELECT f FROM t1 ORDER BY f;
+SELECT f FROM t1 ORDER BY f;
+
+--replace_column 9 #
+EXPLAIN SELECT f FROM t1 IGNORE INDEX (PRIMARY) ORDER BY f;
+SELECT f FROM t1 IGNORE INDEX (PRIMARY) ORDER BY f;
+
+DROP TABLE t1;
+
+#--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ f FLOAT,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10),
+ pk DOUBLE PRIMARY KEY,
+ UNIQUE KEY r_dp (r,dp)
+) ENGINE=rocksdb;
+
+#--disable_parsing
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+
+--replace_column 9 #
+EXPLAIN SELECT r, dp FROM t1 WHERE r > 0 or dp > 0;
+--sorted_result
+SELECT r, dp FROM t1 WHERE r > 0 or dp > 0;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ f FLOAT,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10),
+ pk FLOAT PRIMARY KEY,
+ UNIQUE KEY(d)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT d FROM t1 ORDER BY d;
+SELECT DISTINCT d FROM t1 ORDER BY d;
+
+DROP TABLE t1;
+
+#--enable_parsing
+
+CREATE TABLE t1 (
+ f FLOAT,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10),
+ pk FLOAT PRIMARY KEY,
+ KEY(d)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT d FROM t1 ORDER BY d;
+SELECT DISTINCT d FROM t1 ORDER BY d;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ f FLOAT,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10),
+ pk FLOAT PRIMARY KEY,
+ UNIQUE KEY(f)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+
+# Should fail because of 'unique' constraint
+--error ER_DUP_ENTRY
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,0,0,0,6);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT f FROM t1 ORDER BY f;
+SELECT DISTINCT f FROM t1 ORDER BY f;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ f FLOAT,
+ r REAL(20,3),
+ d DOUBLE,
+ dp DOUBLE PRECISION (64,10),
+ pk FLOAT PRIMARY KEY,
+ KEY(f)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,1422.22,1.2345,1234567.89,1),
+(0,0,0,0,2),
+(-1,-1,-1,-1,3),
+(17.5843,4953453454.44,29229114.0,1111111.23,4),
+(4644,1422.22,466664.999,0.5,5);
+
+# Should succeed because of no 'unique' constraint
+INSERT INTO t1 (f,r,d,dp,pk) VALUES
+(1.2345,0,0,0,6);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT f FROM t1 ORDER BY f;
+SELECT DISTINCT f FROM t1 ORDER BY f;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_int.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_int.inc
new file mode 100644
index 00000000..dbcdfe4f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_int.inc
@@ -0,0 +1,68 @@
+#
+# INT column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ i INT $extra_col_opts,
+ i0 INT(0) $extra_col_opts,
+ i1 INT(1) $extra_col_opts,
+ i20 INT(20) $extra_col_opts,
+ t TINYINT $extra_col_opts,
+ t0 TINYINT(0) $extra_col_opts,
+ t1 TINYINT(1) $extra_col_opts,
+ t20 TINYINT(20) $extra_col_opts,
+ s SMALLINT $extra_col_opts,
+ s0 SMALLINT(0) $extra_col_opts,
+ s1 SMALLINT(1) $extra_col_opts,
+ s20 SMALLINT(20) $extra_col_opts,
+ m MEDIUMINT $extra_col_opts,
+ m0 MEDIUMINT(0) $extra_col_opts,
+ m1 MEDIUMINT(1) $extra_col_opts,
+ m20 MEDIUMINT(20) $extra_col_opts,
+ b BIGINT $extra_col_opts,
+ b0 BIGINT(0) $extra_col_opts,
+ b1 BIGINT(1) $extra_col_opts,
+ b20 BIGINT(20) $extra_col_opts,
+ pk INT AUTO_INCREMENT PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Always valid values
+
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (2147483647,2147483647,2147483647,2147483647,127,127,127,127,32767,32767,32767,32767,8388607,8388607,8388607,8388607,9223372036854775807,9223372036854775807,9223372036854775807,9223372036854775807);
+--sorted_result
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+
+# Values which can be valid or not,
+# depending on whether columns are SIGNED or UNSIGNED
+# (if not valid should produce warnings)
+
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483648,-2147483648,-2147483648,-2147483648,-128,-128,-128,-128,-32768,-32768,-32768,-32768,-8388608,-8388608,-8388608,-8388608,-9223372036854775808,-9223372036854775808,-9223372036854775808,-9223372036854775808);
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967295,4294967295,4294967295,4294967295,255,255,255,255,65535,65535,65535,65535,16777215,16777215,16777215,16777215,18446744073709551615,18446744073709551615,18446744073709551615,18446744073709551615);
+
+--sorted_result
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (-2147483649,-2147483649,-2147483649,-2147483649,-129,-129,-129,-129,-32769,-32769,-32769,-32769,-8388609,-8388609,-8388609,-8388609,-9223372036854775809,-9223372036854775809,-9223372036854775809,-9223372036854775809);
+
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) VALUES (4294967296,4294967296,4294967296,4294967296,256,256,256,256,65536,65536,65536,65536,16777216,16777216,16777216,16777216,18446744073709551616,18446744073709551616,18446744073709551616,18446744073709551616);
+
+INSERT INTO t1 (i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20) SELECT b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b FROM t1 WHERE b IN (-9223372036854775808,9223372036854775807,18446744073709551615);
+
+--sorted_result
+SELECT i,i0,i1,i20,t,t0,t1,t20,s,s0,s1,s20,m,m0,m1,m20,b,b0,b1,b20 FROM t1;
+
+--error ER_TOO_BIG_DISPLAYWIDTH
+eval ALTER TABLE t1 ADD COLUMN i257 INT(257) $extra_col_opts;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_int.test b/storage/rocksdb/mysql-test/rocksdb/t/type_int.test
new file mode 100644
index 00000000..e92f6692
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_int.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# INT column types
+#
+
+--source type_int.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes.test
new file mode 100644
index 00000000..c95c3d88
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_int_indexes.test
@@ -0,0 +1,75 @@
+--source include/have_rocksdb.inc
+
+#
+# INT column types with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ i INT PRIMARY KEY,
+ t TINYINT,
+ s SMALLINT,
+ m MEDIUMINT,
+ b BIGINT
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+
+--replace_column 9 #
+EXPLAIN SELECT i FROM t1 ORDER BY i;
+SELECT i FROM t1 ORDER BY i;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ i INT,
+ t TINYINT,
+ s SMALLINT,
+ m MEDIUMINT,
+ b BIGINT,
+ pk SMALLINT AUTO_INCREMENT PRIMARY KEY,
+ INDEX s_m (s,m)
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+
+--replace_column 9 #
+EXPLAIN SELECT s, m FROM t1 WHERE s != 10 AND m != 1;
+--sorted_result
+SELECT s, m FROM t1 WHERE s != 10 AND m != 1;
+
+DROP TABLE t1;
+
+--echo # RocksDB: unique indexes allowed
+#--error ER_GET_ERRMSG
+CREATE TABLE t1 (
+ i INT,
+ t TINYINT,
+ s SMALLINT,
+ m MEDIUMINT,
+ b BIGINT,
+ pk MEDIUMINT AUTO_INCREMENT PRIMARY KEY,
+ UNIQUE KEY b_t (b,t)
+) ENGINE=rocksdb;
+
+##--disable_parsing
+
+INSERT INTO t1 (i,t,s,m,b) VALUES (1,2,3,4,5),(1000,100,10000,1000000,1000000000000000000),(5,100,10000,1000000,100000000000000000),(2,3,4,5,6),(3,4,5,6,7),(101,102,103,104,105),(10001,103,10002,10003,10004),(10,11,12,13,14),(11,12,13,14,15),(12,13,14,15,16);
+
+# This query should use the index b_t, we just don't want to run EXPLAIN
+# (to avoid mismatches due to different subquery-related plans)
+SELECT b+t FROM t1 WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+SELECT b+t FROM t1 FORCE INDEX (b_t) WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+SELECT b+t FROM t1 IGNORE INDEX (b_t) WHERE (b,t) IN ( SELECT b, t FROM t1 WHERE i>1 ) ORDER BY b+t;
+
+DROP TABLE t1;
+
+##--enable_parsing
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_set.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_set.inc
new file mode 100644
index 00000000..25ae5829
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_set.inc
@@ -0,0 +1,49 @@
+#
+# SET column type
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Valid values.
+
+eval CREATE TABLE t1 (
+ a SET('') $extra_col_opts,
+ b SET('test1','test2','test3','test4','test5') $extra_col_opts,
+ c SET('01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50''51','52','53','54','55','56','57','58','59','60','61','62','63','64') $extra_col_opts,
+ PRIMARY KEY (c)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,,23'),
+('',5,2),
+(',','test4,test2','');
+--sorted_result
+SELECT a,b,c FROM t1;
+
+# Out of range values
+# (should produce warnings)
+
+INSERT INTO t1 (a,b,c) VALUES (0,'test6',-1);
+--sorted_result
+SELECT a,b,c FROM t1;
+
+# Non-unique values in set
+# (should produce a warning)
+eval ALTER TABLE t1 ADD COLUMN e SET('a','A') $extra_col_opts;
+
+SHOW COLUMNS IN t1;
+
+--error ER_TOO_BIG_SET
+eval ALTER TABLE t1 ADD COLUMN f SET('1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','11','12','13','14','15','16','17','18','19','1a','1b','1c','1d','1e','1f','1g','1h','1i','1j','1k','1l','1m','1n','1o','1p','1q','1r','1s','1t','1u','1v','1w','1x','1y','1z','20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','2g','2h','2i','2j','2k','2l','2m','2n','2o','2p','2q','2r','2s','2t','2u','2v','2w','2x','2y','2z','30','31','32','33','34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','3g','3h','3i') $extra_col_opts;
+
+# Simple comparison
+
+--sorted_result
+SELECT a,b,c,e FROM t1 WHERE FIND_IN_SET('test2',b)>0 OR a != '';
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_set.test b/storage/rocksdb/mysql-test/rocksdb/t/type_set.test
new file mode 100644
index 00000000..bd306668
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_set.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# SET column type
+#
+
+--source type_set.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes.test
new file mode 100644
index 00000000..983c7025
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_set_indexes.test
@@ -0,0 +1,100 @@
+--source include/have_rocksdb.inc
+
+#
+# SET columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (
+ a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+ b SET('test1','test2','test3','test4','test5'),
+ c SET('01','22','23','33','34','39','40','44','50','63','64') PRIMARY KEY
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c) VALUES
+('','test2,test3','01,34,44,23'),
+('',5,2),
+('N.America,Asia','test4,test2',''),
+('Africa,Europe,Asia','test2,test3','01'),
+('Antarctica','test3','34,44'),
+('Asia','test5','50'),
+('Europe,S.America','test1,','39');
+
+--replace_column 9 #
+EXPLAIN SELECT c FROM t1 ORDER BY c;
+SELECT c FROM t1 ORDER BY c;
+
+--replace_column 9 #
+EXPLAIN SELECT c FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY c;
+SELECT c FROM t1 IGNORE INDEX FOR ORDER BY (PRIMARY) ORDER BY c;
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b,c) VALUES ('Antarctica','test3','02');
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (a,b,c) VALUES ('','test1','34,44');
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+ b SET('test1','test2','test3','test4','test5'),
+ c SET('01','22','23','33','34','39','40','44','50','63','64'),
+ pk SET('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+ INDEX(a)
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,pk) VALUES
+('','test2,test3','01,34,44,23',1),
+('',5,2,2),
+('N.America,Asia','test4,test2','',3),
+('Africa,Europe,Asia','test2,test3','01',4),
+('Antarctica','test3','34,44',5),
+('Asia','test5','50',6),
+('Europe,S.America','test1,','39',7);
+
+--replace_column 9 #
+EXPLAIN SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+--sorted_result
+SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ a SET('N.America','S.America','Africa','Antarctica','Australia','Europe','Asia'),
+ b SET('test1','test2','test3','test4','test5'),
+ c SET('01','22','23','33','34','39','40','44','50','63','64'),
+ pk SET('1','2','3','4','5','6','7','8','9') PRIMARY KEY,
+ UNIQUE INDEX b_a (b,a)
+) ENGINE=rocksdb;
+
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (a,b,c,pk) VALUES
+('','test2,test3','01,34,44,23',1),
+('',5,2,2),
+('N.America,Asia','test4,test2','',3),
+('Africa,Europe,Asia','test2,test3','01',4),
+('Antarctica','test3','34,44',5),
+('Asia','test5','50',6),
+('Europe,S.America','test1,','39',7);
+
+--replace_column 9 #
+EXPLAIN SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+--sorted_result
+SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_text.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_text.inc
new file mode 100644
index 00000000..f5c9c3b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_text.inc
@@ -0,0 +1,49 @@
+#
+# TEXT column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ pk INT AUTO_INCREMENT PRIMARY KEY,
+ t TEXT $extra_col_opts,
+ t0 TEXT(0) $extra_col_opts,
+ t1 TEXT(1) $extra_col_opts,
+ t300 TEXT(300) $extra_col_opts,
+ tm TEXT(65535) $extra_col_opts,
+ t70k TEXT(70000) $extra_col_opts,
+ t17m TEXT(17000000) $extra_col_opts,
+ tt TINYTEXT $extra_col_opts,
+ m MEDIUMTEXT $extra_col_opts,
+ l LONGTEXT $extra_col_opts
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+# Valid values
+# (cannot get MAX for all columns due to max_allowed_packet limitations)
+
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+('','','','','','','','','',''),
+('a','b','c','d','e','f','g','h','i','j'),
+('test1','test2','test3','test4','test5','test6','test7','test8','test9','test10'),
+( REPEAT('a',65535), REPEAT('b',65535), REPEAT('c',255), REPEAT('d',65535), REPEAT('e',65535), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',255), REPEAT('i',1048576), REPEAT('j',1048576) );
+
+--sorted_result
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+
+# Invalid values (produce warnings, except for mediumtext and longtext columns for which the values are within limits)
+
+INSERT INTO t1 (t,t0,t1,t300,tm,t70k,t17m,tt,m,l) VALUES
+( REPEAT('a',65536), REPEAT('b',65536), REPEAT('c',256), REPEAT('d',65536), REPEAT('e',65536), REPEAT('f',1048576), REPEAT('g',1048576), REPEAT('h',256), REPEAT('i',1048576), REPEAT('j',1048576) );
+
+--sorted_result
+SELECT LENGTH(t), LENGTH(t0), LENGTH(t1), LENGTH(t300), LENGTH(tm), LENGTH(t70k), LENGTH(t17m), LENGTH(tt), LENGTH(m), LENGTH(l) FROM t1;
+
+--error ER_TOO_BIG_DISPLAYWIDTH
+eval ALTER TABLE t1 ADD COLUMN ttt TEXT(4294967296) $extra_col_opts;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_text.test b/storage/rocksdb/mysql-test/rocksdb/t/type_text.test
new file mode 100644
index 00000000..c6dd24ff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_text.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# TEXT column types
+#
+
+--source type_text.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes-master.opt
new file mode 100644
index 00000000..6ad42e58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000 --rocksdb_records_in_range=50
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes.test b/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes.test
new file mode 100644
index 00000000..5fdc4bff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_text_indexes.test
@@ -0,0 +1,171 @@
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+
+#
+# TEXT columns with indexes
+#
+
+SET @ORIG_PAUSE_BACKGROUND_WORK = @@ROCKSDB_PAUSE_BACKGROUND_WORK;
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+
+CREATE TABLE t1 (
+ t TEXT,
+ tt TINYTEXT,
+ m MEDIUMTEXT,
+ l LONGTEXT,
+ PRIMARY KEY t (t(32))
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (t,tt,m,l) VALUES
+('','','',''),
+('a','b','c','d'),
+('b','d','c','b'),
+('test1','test2','test3','test4'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128)),
+('abc','def','ghi','jkl'),
+('test2','test3','test4','test5'),
+('test3','test4','test5','test6'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128)),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128));
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(t,16) AS f FROM t1 WHERE t IN ('test1','test2') ORDER BY f;
+SELECT SUBSTRING(t,16) AS f FROM t1 WHERE t IN ('test1','test2') ORDER BY f;
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(t,16) AS f FROM t1 IGNORE INDEX (PRIMARY) WHERE t IN ('test1','test2') ORDER BY f;
+SELECT SUBSTRING(t,16) AS f FROM t1 IGNORE INDEX (PRIMARY) WHERE t IN ('test1','test2') ORDER BY f;
+DROP TABLE t1;
+
+--error ER_BLOB_KEY_WITHOUT_LENGTH
+CREATE TABLE t1 (
+ t TEXT,
+ tt TINYTEXT,
+ m MEDIUMTEXT,
+ l LONGTEXT,
+ pk TINYTEXT PRIMARY KEY,
+ UNIQUE INDEX l_tt (l(256),tt(64))
+) ENGINE=rocksdb;
+
+CREATE TABLE t1 (
+ t TEXT,
+ tt TINYTEXT,
+ m MEDIUMTEXT,
+ l LONGTEXT,
+ pk MEDIUMTEXT,
+ PRIMARY KEY mt (pk(1)),
+ INDEX (m(128))
+) ENGINE=rocksdb;
+
+SHOW INDEX IN t1;
+
+INSERT INTO t1 (t,tt,m,l,pk) VALUES
+('','','','','0'),
+('a','b','c','d','1'),
+('b','d','c','b','2'),
+('test1','test2','test3','test4','3'),
+(REPEAT('a',128),REPEAT('b',128),REPEAT('c',128),REPEAT('d',128),'4'),
+('abc','def','ghi','jkl','5'),
+('test2','test3','test4','test5','6'),
+('test3','test4','test5','test6','7'),
+(REPEAT('b',128),REPEAT('f',128),REPEAT('e',128),REPEAT('d',128),'8'),
+(REPEAT('c',128),REPEAT('b',128),REPEAT('c',128),REPEAT('e',128),'9');
+
+--replace_column 9 #
+EXPLAIN SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+SELECT SUBSTRING(m,128) AS f FROM t1 WHERE m = 'test1' ORDER BY f DESC;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b TEXT,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b TINYTEXT,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b MEDIUMTEXT,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b LONGTEXT,
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+
+SELECT b FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b LONGTEXT CHARACTER SET "binary" COLLATE "binary",
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+INSERT INTO t1 (b) VALUES
+('00000000000000000000000000000000'),
+('00000000000000000000000000000001'),
+('00000000000000000000000000000002');
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0), (' ');
+
+SELECT hex(b) FROM t1;
+
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+ b LONGTEXT CHARACTER SET "latin1" COLLATE "latin1_bin",
+ PRIMARY KEY b (b(32))
+) ENGINE=rocksdb;
+
+--error ER_DUP_ENTRY
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0), (' ');
+
+INSERT INTO t1 (b) VALUES (''), (_binary 0x0);
+SELECT hex(b) FROM t1;
+
+DROP TABLE t1;
+
+SET GLOBAL ROCKSDB_PAUSE_BACKGROUND_WORK = @ORIG_PAUSE_BACKGROUND_WORK;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.inc
new file mode 100644
index 00000000..a9f98831
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.inc
@@ -0,0 +1,75 @@
+#
+# VARBINARY column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ v0 VARBINARY(0) $extra_col_opts,
+ v1 VARBINARY(1) $extra_col_opts,
+ v64 VARBINARY(64) $extra_col_opts,
+ v65000 VARBINARY(65000) $extra_col_opts,
+ PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+eval CREATE TABLE t2 (v VARBINARY(65532) $extra_col_opts, PRIMARY KEY(v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+
+--sorted_result
+SELECT HEX(v0), HEX(v1), HEX(v64), HEX(v65000) FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+
+--sorted_result
+SELECT HEX(v0), HEX(v1), HEX(v64), LENGTH(HEX(v65000)) FROM t1;
+
+eval ALTER TABLE t1 ADD COLUMN v65536 VARBINARY(65536) $extra_col_opts;
+SHOW COLUMNS IN t1;
+
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.test b/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.test
new file mode 100644
index 00000000..2ef3da8a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varbinary.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+#
+# VARBINARY column types
+#
+
+--source type_varbinary.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varchar-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar-master.opt
new file mode 100644
index 00000000..33e72265
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=10
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.inc
new file mode 100644
index 00000000..ce5dbb44
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.inc
@@ -0,0 +1,77 @@
+#
+# VARCHAR column types
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1, t2;
+--enable_warnings
+
+eval CREATE TABLE t1 (
+ v0 VARCHAR(0) $extra_col_opts,
+ v1 VARCHAR(1) $extra_col_opts,
+ v64 VARCHAR(64) $extra_col_opts,
+ v65000 VARCHAR(65000) $extra_col_opts,
+ PRIMARY KEY (v64)
+) ENGINE=rocksdb;
+
+SHOW COLUMNS IN t1;
+
+CREATE TABLE t2 (v VARCHAR(65532), PRIMARY KEY (v(255))) ENGINE=rocksdb;
+SHOW COLUMNS IN t2;
+
+# Valid values
+
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','','','');
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('','y','Once there, double check that an article doesn\'t already exist','Here is a list of recommended books on MariaDB and MySQL. We\'ve provided links to Amazon.com here for convenience, but they can be found at many other bookstores, both online and off.
+
+ If you want to have your favorite MySQL / MariaDB book listed here, please leave a comment.
+ For developers who want to code on MariaDB or MySQL
+
+ * Understanding MySQL Internals by Sasha Pachev, former MySQL developer at MySQL AB.
+ o This is the only book we know about that describes the internals of MariaDB / MySQL. A must have for anyone who wants to understand and develop on MariaDB!
+ o Not all topics are covered and some parts are slightly outdated, but still the best book on this topic.
+ * MySQL 5.1 Plugin Development by Sergei Golubchik and Andrew Hutchings
+ o A must read for anyone wanting to write a plugin for MariaDB, written by the Sergei who designed the plugin interface for MySQL and MariaDB!
+
+ For MariaDB / MySQL end users
+
+ * MariaDB Crash Course by Ben Forta
+ o First MariaDB book!
+ o For people who want to learn SQL and the basics of MariaDB.
+ o Now shipping. Purchase at Amazon.com or your favorite bookseller.
+
+ * SQL-99 Complete, Really by Peter Gulutzan & Trudy Pelzer.
+ o Everything you wanted to know about the SQL 99 standard. Excellent reference book!
+ o Free to read in the Knowledgebase!
+
+ * MySQL (4th Edition) by Paul DuBois
+ o The \'default\' book to read if you wont to learn to use MySQL / MariaDB.
+
+ * MySQL Cookbook by Paul DuBois
+ o A lot of examples of how to use MySQL. As with all of Paul\'s books, it\'s worth its weight in gold and even enjoyable reading for such a \'dry\' subject.
+
+ * High Performance MySQL, Second Edition, By Baron Schwartz, Peter Zaitsev, Vadim Tkachenko, Jeremy D. Zawodny, Arjen Lentz, Derek J. Balling, et al.
+ o \"High Performance MySQL is the definitive guide to building fast, reliable systems with MySQL. Written by noted experts with years of real-world experience building very large systems, this book covers every aspect of MySQL performance in detail, and focuses on robustness, security, and data integrity. Learn advanced techniques in depth so you can bring out MySQL\'s full power.\" (From the book description at O\'Reilly)
+
+ * MySQL Admin Cookbook
+ o A quick step-by-step guide for MySQL users and database administrators to tackle real-world challenges with MySQL configuration and administration
+
+ * MySQL 5.0 Certification Study Guide, By Paul DuBois, Stefan Hinz, Carsten Pedersen
+ o This is the official guide to cover the passing of the two MySQL Certification examinations. It is valid till version 5.0 of the server, so while it misses all the features available in MySQL 5.1 and greater (including MariaDB 5.1 and greater), it provides a good basic understanding of MySQL for the end-user. ');
+
+--sorted_result
+SELECT v0,v1,v64,v65000 FROM t1;
+
+# Invalid values
+
+INSERT INTO t1 (v0,v1,v64,v65000) VALUES ('y', 'yy', REPEAT('c',65), REPEAT('abcdefghi ',6501));
+INSERT INTO t1 (v0,v1,v64,v65000) SELECT v65000, v65000, CONCAT('a',v65000), CONCAT(v65000,v1) FROM t1;
+
+--sorted_result
+SELECT v0, v1, v64, LENGTH(v65000) FROM t1;
+
+eval ALTER TABLE t1 ADD COLUMN v65536 VARCHAR(65536) $extra_col_opts;
+SHOW COLUMNS IN t1;
+
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.test b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.test
new file mode 100644
index 00000000..b631615c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar.test
@@ -0,0 +1,82 @@
+--source include/have_rocksdb.inc
+
+#
+# VARCHAR column types
+#
+
+--echo #
+--echo # A basic test whether endspace-aware variable length encoding
+--echo # works when in PK
+--echo #
+create table t1 ( pk varchar(10) primary key, col1 int not null) ;
+insert into t1 values ('a', 1);
+select * from t1;
+drop table t1;
+
+--source type_varchar.inc
+
+
+--echo #
+--echo # Endspace-comparison tests:
+--echo #
+
+--let $character_set_collate=CHARACTER SET latin1 COLLATE latin1_bin
+--source type_varchar_endspace.inc
+
+--let $character_set_collate=CHARACTER SET utf8 COLLATE utf8_bin
+--source type_varchar_endspace.inc
+
+--let $character_set_collate=CHARACTER SET ucs2 COLLATE ucs2_bin
+--source type_varchar_endspace.inc
+
+--let $character_set_collate=CHARACTER SET utf8mb4 COLLATE utf8mb4_bin
+--source type_varchar_endspace.inc
+
+--let $character_set_collate=CHARACTER SET utf16 COLLATE utf16_bin
+--source type_varchar_endspace.inc
+
+create table t1 (
+ pk int primary key,
+ col1 varchar(10) collate utf8mb4_bin not null,
+ col2 varchar(20),
+ key(col1)
+) engine=rocksdb;
+
+insert into t1 values (1, 'ab','ab');
+insert into t1 values (2, 'ab\0','ab0');
+
+select pk, hex(col1), col2 from t1 force index(col1) order by col1;
+select pk, hex(col1), col2 from t1 ignore index(col1) order by col1;
+drop table t1;
+
+# Issue #306 - Do not store trailing spaces for prefixed keys.
+create table t (id int primary key, email varchar(100), KEY email_i (email(30)));
+insert into t values (1, 'abcabcabcabcabcabcabcabcabcabcabc ');
+--replace_column 9 #
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+drop table t;
+
+set @save_rocksdb_checksums_pct = @@global.rocksdb_checksums_pct;
+set @save_rocksdb_verify_row_debug_checksums = @@session.rocksdb_verify_row_debug_checksums;
+set global rocksdb_checksums_pct = 100;
+set session rocksdb_verify_row_debug_checksums = on;
+create table t (id int primary key, email varchar(100), KEY email_i (email(30)));
+insert into t values (1, 'a');
+--replace_column 9 #
+explain select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+select 'email_i' as index_name, count(*) AS count from t force index(email_i);
+drop table t;
+set global rocksdb_checksums_pct = @save_rocksdb_checksums_pct;
+set session rocksdb_verify_row_debug_checksums = @save_rocksdb_verify_row_debug_checksums;
+
+# Issue #784 - Skip trailing space bytes for non-unpackable fields
+
+drop table if exists t;
+create table t (h varchar(31) character set utf8 collate utf8_bin not null, i varchar(19) collate latin1_bin not null, primary key(i), key(h)) engine=rocksdb;
+insert into t(i,h) values('a','b');
+check table t;
+alter table t modify h varchar(31) character set cp1257 collate cp1257_bin not null;
+check table t;
+drop table t;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/type_varchar_endspace.inc b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar_endspace.inc
new file mode 100644
index 00000000..494f0ea1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/type_varchar_endspace.inc
@@ -0,0 +1,85 @@
+--echo #
+--echo # Issue 257: Sort order for varchars is different between
+--echo # MyISAM/InnoDB vs MyRocks
+--echo #
+
+eval create table t1 (
+ pk varchar(64) $character_set_collate,
+ col1 varchar(64),
+ primary key (pk)
+);
+insert into t1 values ('a','a');
+--error ER_DUP_ENTRY
+insert into t1 values ('a ', 'a-space');
+
+insert into t1 values('b ', 'b-2x-space');
+--error ER_DUP_ENTRY
+insert into t1 values ('b', 'b');
+
+select pk, hex(pk), col1 from t1;
+
+insert into t1 values ('a\t', 'a-tab');
+insert into t1 values ('a \t', 'a-space-tab');
+select pk, hex(pk), col1 from t1 order by pk;
+
+--echo # Try longer values
+--error ER_DUP_ENTRY
+insert into t1 values (concat('a', repeat(' ',10)), 'a-10-x-space');
+
+insert into t1 values (concat('c', repeat(' ',10)), 'c-10-x-space');
+select * from t1;
+
+drop table t1;
+
+--echo # Secondary index
+eval create table t1 (
+ pk int not null primary key,
+ col1 varchar(64) $character_set_collate,
+ col2 varchar(64),
+ key (col1)
+);
+insert into t1 values (0, 'ab', 'a-b');
+insert into t1 values (1, 'a ', 'a-space');
+insert into t1 values (2, 'a', 'a');
+insert into t1 values (3, 'a \t', 'a-tab');
+
+analyze table t1;
+--echo # Must show 'using index' for latin1_bin and utf8_bin:
+--replace_column 9 #
+explain
+select col1, hex(col1) from t1;
+select col1, hex(col1) from t1;
+
+--echo # Must show 'using index' for latin1_bin and utf8_bin:
+--replace_column 4 # 9 #
+explain
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+select col1, hex(col1) from t1 force index(col1) where col1 < 'b';
+
+delete from t1;
+insert into t1 values(10, '', 'empty');
+insert into t1 values(11, repeat(' ', 8), '8x-space');
+insert into t1 values(12, repeat(' ', 16), '16x-space');
+insert into t1 values(13, repeat(' ', 24), '24x-space');
+insert into t1 values(14, concat(repeat(' ', 16),'a'), '16x-space-a');
+
+insert into t1 values(21, repeat(' ', 9), '9x-space');
+insert into t1 values(22, repeat(' ',17), '17x-space');
+insert into t1 values(23, repeat(' ',18), '18x-space');
+
+--replace_column 4 # 9 #
+explain
+select pk, col1, hex(col1), length(col1) from t1;
+select pk, col1, hex(col1), length(col1) from t1;
+drop table t1;
+
+create table t1 (pk int primary key, a varchar(512), key(a)) engine=rocksdb;
+insert into t1 values (1, concat('a', repeat(' ', 300)));
+insert into t1 values (2, concat('b', repeat(' ', 300)));
+
+select pk,length(a) from t1 force index(a) where a < 'zz';
+select pk,length(a),rtrim(a) from t1 force index(a) where a < 'zz';
+select pk,length(a),rtrim(a) from t1 ignore index(a) where a < 'zz';
+
+drop table t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/unique_check.test b/storage/rocksdb/mysql-test/rocksdb/t/unique_check.test
new file mode 100644
index 00000000..e2520388
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/unique_check.test
@@ -0,0 +1,173 @@
+--source include/have_rocksdb.inc
+--source include/have_debug_sync.inc
+
+# For GitHub issue#167 -- Unique key check doesn't work
+
+connect (con1, localhost, root,,);
+connect (con2, localhost, root,,);
+connect (con3, localhost, root,,);
+connection default;
+
+--disable_warnings
+set debug_sync='RESET';
+drop table if exists t1;
+--enable_warnings
+
+create table t1 (id int, value int, primary key (id)) engine=rocksdb;
+create table t2 (id int, id2 int, value int, primary key (id), unique key (id2)) engine=rocksdb;
+
+# 1) second insert should be blocked at GetForUpdate(), then returning duplicate key error
+# after con1's commit
+connection con1;
+begin;
+insert into t1 values (1,1);
+
+connection con2;
+let $ID= `select connection_id()`;
+set session rocksdb_lock_wait_timeout=50;
+begin;
+send insert into t1 values (1,2);
+
+connection con1;
+let $wait_condition= select 1 from INFORMATION_SCHEMA.PROCESSLIST
+ where (ID = $ID /* or SRV_ID = $ID*/)
+ and STATE = "Waiting for row lock";
+--source include/wait_condition.inc
+commit;
+
+connection con2;
+--error ER_DUP_ENTRY
+reap;
+commit;
+select * from t1;
+truncate table t1;
+
+# 2) same as 1) but using secondary unique key constraint
+connection con1;
+begin;
+insert into t2 values (1,1,1);
+
+connection con2;
+begin;
+send insert into t2 values (2,1,2);
+
+connection con1;
+--source include/wait_condition.inc
+commit;
+
+connection con2;
+--error ER_DUP_ENTRY
+reap;
+commit;
+select * from t2;
+truncate table t2;
+
+# 3) similar to 1),2) but rolled back
+connection con1;
+begin;
+insert into t1 values (1,1);
+
+connection con2;
+begin;
+send insert into t1 values (1,2);
+
+connection con1;
+--source include/wait_condition.inc
+rollback;
+
+connection con2;
+reap;
+commit;
+select * from t1;
+truncate table t1;
+
+connection con1;
+begin;
+insert into t2 values (1,1,1);
+
+connection con2;
+begin;
+send insert into t2 values (2,1,2);
+
+connection con1;
+--source include/wait_condition.inc
+rollback;
+
+connection con2;
+reap;
+commit;
+select * from t2;
+truncate table t2;
+
+
+# 4) simulating T1 GetForUpdate() -> T2 GetForUpdate(). T2 should fail with lock wait timeout.
+connection con1;
+set debug_sync='rocksdb.update_write_row_after_unique_check SIGNAL parked1 WAIT_FOR go';
+send insert into t1 values (1,1);
+
+connection default;
+set debug_sync='now WAIT_FOR parked1';
+
+connection con2;
+set debug_sync='rocksdb.update_write_row_after_unique_check SIGNAL parked2 WAIT_FOR go';
+send insert into t2 values (1,1,1);
+
+connection default;
+set debug_sync='now WAIT_FOR parked2';
+
+connection con3;
+set session rocksdb_lock_wait_timeout=1;
+--error ER_LOCK_WAIT_TIMEOUT
+insert into t1 values (1,2);
+--error ER_LOCK_WAIT_TIMEOUT
+insert into t2 values (2,1,2);
+
+connection default;
+set debug_sync='now SIGNAL go';
+
+connection con1;
+reap;
+
+connection con2;
+reap;
+
+connection default;
+--error ER_DUP_ENTRY
+insert into t1 values (1,2);
+--error ER_DUP_ENTRY
+insert into t2 values (2,1,2);
+select * from t1;
+select * from t2;
+# Cleanup
+connection default;
+set debug_sync='RESET';
+disconnect con1;
+disconnect con2;
+disconnect con3;
+drop table t1, t2;
+
+# skip_unique checks should skip checks only for tables that don't have
+# secondary indexes
+connection default;
+--disable_warnings
+drop table if exists t1,t2,t3;
+--enable_warnings
+
+# table with PK only
+create table t1 (id int, value int, primary key (id)) engine=rocksdb;
+# table with PK and SK
+create table t2 (id int, id2 int, value int, primary key (id), unique key (id2)) engine=rocksdb;
+# table with hidden PK
+create table t3 (id int, value int) engine=rocksdb;
+
+SET @old_val = @@session.unique_checks;
+set @@session.unique_checks = FALSE;
+
+insert into t1 values (1, 1), (1, 2);
+--error ER_DUP_ENTRY
+insert into t2 values (1, 1, 1), (1, 2, 1);
+insert into t3 values (1, 1), (1, 1);
+
+set @@session.unique_checks = @old_val;
+# cleanup
+drop table t1, t2, t3;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.inc b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.inc
new file mode 100644
index 00000000..ecfc0d8f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.inc
@@ -0,0 +1,198 @@
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+
+eval CREATE TABLE t1 (id1 INT NOT NULL, id2 INT NOT NULL, id3 VARCHAR(32),
+ id4 INT, id5 VARCHAR(32),
+ value1 INT, value2 INT, value3 VARCHAR(32),
+ PRIMARY KEY (id1, id2) $CF,
+ UNIQUE INDEX (id2, id1) $CF,
+ UNIQUE INDEX (id2, id3, id4) $CF,
+ INDEX (id1) $CF,
+ INDEX (id3, id1) $CF,
+ UNIQUE INDEX(id5) $CF,
+ INDEX (id2, id5)) ENGINE=ROCKSDB;
+
+--disable_query_log
+let $max = 10;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i, $i, $i, $i, $i, $i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Test inserting a key that returns duplicate error
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (1, 1, 11, 11, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (5, 5, 11, 11, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (10, 10, 11, 11, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 1, 1, 1, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 5, 5, 5, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 10, 10, 10, 11, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 11, 11, 11, 1, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 11, 11, 11, 5, 11, 11, 11);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (11, 11, 11, 11, 10, 11, 11, 11);
+
+--echo # Test updating a key that returns duplicate error
+--error ER_DUP_ENTRY
+UPDATE t1 SET id2=1, id3=1, id4=1 WHERE id1=2;
+--error ER_DUP_ENTRY
+UPDATE t1 SET id2=1, id3=1, id4=1;
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Test updating a key to itself
+UPDATE t1 set id2=id4;
+UPDATE t1 set id5=id3, value1=value2;
+UPDATE t1 set value3=value1;
+
+--echo # Test modifying values should not cause duplicates
+UPDATE t1 SET value1=value3+1;
+UPDATE t1 SET value3=value3 div 2;
+UPDATE t1 SET value2=value3;
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Test NULL values are considered unique
+INSERT INTO t1 VALUES (20, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (21, 20, 20, NULL, NULL, 20, 20, 20);
+INSERT INTO t1 VALUES (22, 20, 20, NULL, NULL, 20, 20, 20);
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Adding multiple rows where one of the rows fail the duplicate
+--echo # check should fail the whole statement
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (23, 23, 23, 23, 23, 23, 23, 23),
+ (24, 24, 24, 24, 24, 24, 24, 24),
+ (25, 10, 10, 10, 25, 25, 25, 25),
+ (26, 26, 26, 26, 26, 26, 26, 26);
+SELECT COUNT(*) FROM t1;
+
+# Test open transactions can prevent duplicate keys
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (30, 31, 32, 33, 34, 30, 30, 30);
+
+connection con2;
+BEGIN;
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Primary key should prevent duplicate on insert
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (30, 31, 30, 30, 30, 30, 30, 30);
+
+--echo # Primary key should prevent duplicate on update
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET id1=30, id2=31 WHERE id2=10;
+
+--echo # Unique secondary key should prevent duplicate on insert
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (31, 31, 32, 33, 30, 30, 30, 30);
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (32, 32, 32, 32, 34, 32, 32, 32);
+
+--echo # Unique secondary key should prevent duplicate on update
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET id2=31, id3=32, id4=33 WHERE id2=8;
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET id5=34 WHERE id2=8;
+
+--echo # Adding multiple rows where one of the rows fail the duplicate
+--echo # check should fail the whole statement
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+ (36, 36, 36, 36, 36, 36, 36, 36),
+ (37, 31, 32, 33, 37, 37, 37, 37),
+ (38, 38, 38, 38, 38, 38, 38, 38);
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (35, 35, 35, 35, 35, 35, 35, 35),
+ (36, 36, 36, 36, 36, 36, 36, 36),
+ (37, 37, 37, 37, 34, 37, 37, 37),
+ (38, 38, 38, 38, 38, 38, 38, 38);
+
+--echo # NULL values are unique and duplicates in value fields are ignored
+INSERT INTO t1 VALUES (37, 31, 32, NULL, 37, 37, 37, 37),
+ (38, 31, 32, NULL, 38, 37, 37, 37),
+ (39, 31, 32, NULL, 39, 37, 37, 37);
+
+SELECT COUNT(*) FROM t1;
+
+--echo # Fail on duplicate key update for row added in our transaction
+--error ER_DUP_ENTRY
+UPDATE t1 SET id5=37 WHERE id1=38;
+
+--echo # Fail on lock timeout for row modified in another transaction
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET id5=34 WHERE id1=38;
+
+--echo # NULL values are unique
+UPDATE t1 SET id5=NULL WHERE value1 > 37;
+
+connection con1;
+COMMIT;
+
+connection con2;
+COMMIT;
+
+# Test transaction is reading from latest data
+connection con2;
+BEGIN;
+SELECT COUNT(*) FROM t1;
+
+connection con1;
+BEGIN;
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+
+connection con2;
+--echo # When transaction is pending, fail on lock acquisition
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+--error ER_LOCK_WAIT_TIMEOUT
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+
+SELECT COUNT(*) FROM t1;
+
+connection con1;
+COMMIT;
+
+connection con2;
+--echo # When transaction is committed, fail on duplicate key
+--error ER_DUP_ENTRY,ER_LOCK_DEADLOCK
+INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40);
+
+ROLLBACK;
+
+SELECT * FROM t1;
+
+disconnect con1;
+disconnect con2;
+
+connection default;
+DROP TABLE t1;
+
+# Wait till we reached the initial number of concurrent sessions
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.test b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.test
new file mode 100644
index 00000000..1dedd75f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec.test
@@ -0,0 +1,51 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+--let $CF=
+--source unique_sec.inc
+
+--echo #
+--echo # Issue #88: Creating unique index over column with duplicate values succeeds
+--echo #
+create table t1 (pk int primary key, a int) engine=rocksdb;
+
+insert into t1 values
+(1, 1),
+(2, 2),
+(3, 3),
+(4, 1),
+(5, 5);
+
+--error ER_DUP_ENTRY
+alter table t1 add unique(a);
+drop table t1;
+
+--echo #
+--echo # Issue #111
+--echo #
+CREATE TABLE t2 (pk int, a int, PRIMARY KEY (pk, a), UNIQUE KEY (a)) ENGINE=ROCKSDB PARTITION BY KEY (a) PARTITIONS 16;
+
+INSERT INTO t2 VALUES (1,1);
+--error ER_DUP_ENTRY
+INSERT INTO t2 VALUES (1,1);
+--error ER_DUP_ENTRY
+INSERT INTO t2 VALUES (2,1);
+DROP TABLE t2;
+
+--echo #
+--echo # Issue #491 (https://github.com/facebook/mysql-5.6/issues/491)
+--echo #
+CREATE TABLE t (a BLOB, PRIMARY KEY(a(2)), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+DROP TABLE t;
+
+CREATE TABLE t (a VARCHAR(255), PRIMARY KEY(a), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+DROP TABLE t;
+
+CREATE TABLE t (a VARCHAR(255), PRIMARY KEY(a(2)), UNIQUE KEY (a(1))) engine=rocksdb;
+INSERT INTO t VALUES('a');
+CHECK TABLE t EXTENDED;
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/unique_sec_rev_cf.test b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec_rev_cf.test
new file mode 100644
index 00000000..dc0bbd8e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/unique_sec_rev_cf.test
@@ -0,0 +1,4 @@
+--source include/have_rocksdb.inc
+
+let $CF=COMMENT 'rev:cf';
+--source unique_sec.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/unsupported_tx_isolations.test b/storage/rocksdb/mysql-test/rocksdb/t/unsupported_tx_isolations.test
new file mode 100644
index 00000000..a15e5d2a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/unsupported_tx_isolations.test
@@ -0,0 +1,25 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+create table t1 (id int primary key, value int) engine=rocksdb;
+insert into t1 values (1,1);
+
+SET session transaction isolation level read uncommitted;
+begin;
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+insert into t1 values (2,1);
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+select * from t1 where id=1;
+rollback;
+
+SET session transaction isolation level serializable;
+begin;
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+insert into t1 values (2,1);
+--error ER_ISOLATION_MODE_NOT_SUPPORTED
+select * from t1 where id=1;
+rollback;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update.test b/storage/rocksdb/mysql-test/rocksdb/t/update.test
new file mode 100644
index 00000000..6220c4f5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update.test
@@ -0,0 +1,82 @@
+--source include/have_rocksdb.inc
+
+#
+# Basic UPDATE statements.
+# UPDATE LOW_PRIORITY is covered in update_low_prio test
+# UPDATE IGNORE is covered in update_ignore test
+# Multi-table update is covered in update_multi test
+#
+
+########################################
+# TODO:
+# The results of the transactional part
+# are unusual due to "can't-see-own-changes"
+########################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+UPDATE t1 SET a=a+100;
+--sorted_result
+SELECT a,b FROM t1;
+
+UPDATE t1 SET a=a-100, b=DEFAULT WHERE a>100;
+--sorted_result
+SELECT a,b FROM t1;
+
+# ORDER BY and LIMIT
+UPDATE t1 SET b = 'update' WHERE a <= 4 ORDER BY b DESC, a ASC LIMIT 1;
+--sorted_result
+SELECT a,b FROM t1;
+
+DROP TABLE t1;
+
+#
+# Transactional UPDATE
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+BEGIN;
+UPDATE t1 SET a=a+100;
+UPDATE t1 SET a=a-50, b=DEFAULT WHERE a>100;
+COMMIT;
+--sorted_result
+SELECT * FROM t1 ORDER BY pk;
+
+BEGIN;
+UPDATE t1 SET b = 'update' WHERE a <= 4 ORDER BY a DESC, b ASC LIMIT 3;
+UPDATE t1 SET b = '';
+ROLLBACK;
+SELECT * FROM t1 ORDER BY pk;
+
+BEGIN;
+UPDATE t1 SET b = 'update2' WHERE a <= 100;
+SAVEPOINT spt1;
+UPDATE t1 SET b = '';
+--error ER_ROLLBACK_TO_SAVEPOINT
+ROLLBACK TO SAVEPOINT spt1;
+UPDATE t1 SET b = 'upd' WHERE a = 10050;
+--error ER_ROLLBACK_ONLY
+COMMIT;
+SELECT * FROM t1 ORDER BY pk;
+
+DROP TABLE t1;
+
+
+#
+# Issue #830 UPDATE with unique constraint does not work
+#
+
+CREATE TABLE t1 (a INT, b CHAR(8), UNIQUE INDEX(a)) ENGINE=RocksDB;
+INSERT INTO t1 (a,b) VALUES (1,'foo'),(2,'bar');
+UPDATE t1 SET a=a+100;
+SELECT * FROM t1;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update_ignore-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/update_ignore-master.opt
new file mode 100644
index 00000000..ba9364e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update_ignore-master.opt
@@ -0,0 +1 @@
+--rocksdb_debug_optimizer_n_rows=1000
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update_ignore.test b/storage/rocksdb/mysql-test/rocksdb/t/update_ignore.test
new file mode 100644
index 00000000..0208e1b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update_ignore.test
@@ -0,0 +1,35 @@
+--source include/have_rocksdb.inc
+
+#
+# UPDATE IGNORE
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT PRIMARY KEY) ENGINE=rocksdb;
+
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+
+UPDATE IGNORE t1 SET b = 'upd1' WHERE b IS NOT NULL ORDER BY a LIMIT 1;
+SELECT a,b FROM t1 ORDER BY pk;
+
+--error ER_SUBQUERY_NO_1_ROW
+UPDATE t1, t2 SET b = 'upd2a', c = 'upd2b'
+ WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+
+UPDATE IGNORE t1, t2 SET b = 'upd2a', c = 'upd2b'
+ WHERE c < b OR a != ( SELECT 1 UNION SELECT 2 );
+
+SELECT a,b FROM t1 ORDER BY pk;
+SELECT c,d FROM t2 ORDER BY pk;
+
+# Cleanup
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update_multi.test b/storage/rocksdb/mysql-test/rocksdb/t/update_multi.test
new file mode 100644
index 00000000..1cb2f128
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update_multi.test
@@ -0,0 +1,15 @@
+--source include/have_rocksdb.inc
+
+#
+# Multi-table UPDATE statements.
+#
+
+--disable_warnings
+DROP TABLE IF EXISTS t1,t2;
+--enable_warnings
+
+let $i= 0;
+while ($i < 10) {
+ --source update_multi_exec.inc
+ inc $i;
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update_multi_exec.inc b/storage/rocksdb/mysql-test/rocksdb/t/update_multi_exec.inc
new file mode 100644
index 00000000..40d73669
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update_multi_exec.inc
@@ -0,0 +1,27 @@
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES
+(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(10000,'foobar');
+INSERT INTO t1 (a,b) SELECT a, b FROM t1;
+
+CREATE TABLE t2 (c CHAR(8), d INT, pk INT AUTO_INCREMENT
+PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t2 (c,d) SELECT b, a FROM t1;
+
+UPDATE t1 STRAIGHT_JOIN t2 SET t1.a = t2.d+100, t2.c = 'multi'
+WHERE c < b AND a + d != 1;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+UPDATE t2 STRAIGHT_JOIN t1 SET t2.d = DEFAULT
+WHERE c = 'foobar' and b = c;
+--sorted_result
+SELECT a,b FROM t1;
+--sorted_result
+SELECT c,d FROM t2;
+
+# Cleanup
+DROP TABLE t1, t2;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/update_with_keys.test b/storage/rocksdb/mysql-test/rocksdb/t/update_with_keys.test
new file mode 100644
index 00000000..c53eb9be
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/update_with_keys.test
@@ -0,0 +1,78 @@
+--source include/have_rocksdb.inc
+
+#
+# UPDATE statements for tables with keys
+#
+
+#############################################
+# TODO:
+# The test doesn't work quite as expected,
+# apparently due to "can't see own changes"
+#############################################
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, INDEX(b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(6,'x'),(7,'y'),(8,'z');
+UPDATE t1 SET a=100, b='f' WHERE b IN ('b','c');
+UPDATE t1 SET b='m' WHERE b = 'f';
+UPDATE t1 SET b='z' WHERE a < 2;
+UPDATE t1 SET b='';
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+--echo # RocksDB: skip the test for secondary UNIQUE keys.
+--disable_parsing
+--error ER_GET_ERRMSG
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a)) ENGINE=innodb;
+
+
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(0,'f'),(100,'a');
+UPDATE t1 SET a=a+200;
+UPDATE t1 SET a=0 WHERE a > 250;
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=205 WHERE a=200;
+UPDATE t1 SET a=12345 ORDER BY a, b LIMIT 1;
+--sorted_result
+SELECT a,b FROM t1;
+
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=80 WHERE a IN (202,203);
+--sorted_result
+SELECT a,b FROM t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (a INT, b CHAR(8), pk INT AUTO_INCREMENT PRIMARY KEY, UNIQUE INDEX(a,b)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(100,'a'),(6,'f');
+UPDATE t1 SET a=6 WHERE a=3;
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=100 WHERE a=1;
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=4, b='d' WHERE b='f';
+UPDATE t1 SET a=a+1;
+--sorted_result
+SELECT a,b FROM t1;
+--error ER_DUP_ENTRY
+UPDATE t1 SET b='z';
+DROP TABLE t1;
+
+--enable_parsing
+
+CREATE TABLE t1 (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'),(0,'f'),(100,'a');
+UPDATE t1 SET a=a+200;
+UPDATE t1 SET a=0 WHERE a > 250;
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=205 WHERE a=200;
+UPDATE t1 SET a=12345 ORDER BY a DESC, b LIMIT 1;
+
+--sorted_result
+SELECT a,b FROM t1;
+
+--error ER_DUP_ENTRY
+UPDATE t1 SET a=80 WHERE a IN (202,203);
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/use_direct_io_for_flush_and_compaction.test b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_io_for_flush_and_compaction.test
new file mode 100644
index 00000000..782e2a36
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_io_for_flush_and_compaction.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+--let $io_option=--rocksdb_use_direct_io_for_flush_and_compaction=1
+
+--source ../include/use_direct_io_option.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads.test b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads.test
new file mode 100644
index 00000000..a1b717e8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+
+--let $io_option=--rocksdb_use_direct_reads=1
+
+--source ../include/use_direct_io_option.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads_writes.test b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads_writes.test
new file mode 100644
index 00000000..53ba5161
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/use_direct_reads_writes.test
@@ -0,0 +1,62 @@
+--source include/have_rocksdb.inc
+--source include/have_direct_io.inc
+
+call mtr.add_suppression("rocksdb");
+call mtr.add_suppression("Aborting");
+
+--echo # This shows that RocksDB plugin is loaded:
+select plugin_name, plugin_type from information_schema.plugins where plugin_name='RocksDB';
+
+# Issue221
+# Turning on both --rocksdb-allow-mmap-reads and --rocksdb-use-direct-reads
+# caused an assertion in RocksDB. Now it should not be allowed and ROCKSDB
+# plugin will not load in such configuration.
+#
+--let LOG=$MYSQLTEST_VARDIR/tmp/use_direct_reads_writes.err
+--let SEARCH_FILE=$LOG
+
+--echo Checking direct reads
+--let $_mysqld_option=--log-error=$LOG --rocksdb_use_direct_reads=1 --rocksdb_allow_mmap_reads=1
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_invalid_option.inc
+
+--let SEARCH_PATTERN=enable both use_direct_reads
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+
+# Repeat with direct-writes
+--echo Checking direct writes
+--let $_mysqld_option=--log-error=$LOG --rocksdb_use_direct_io_for_flush_and_compaction=1 --rocksdb_allow_mmap_writes=1
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_invalid_option.inc
+
+--let SEARCH_PATTERN=enable both use_direct_io_for_flush_and_compaction
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+
+# Verify invalid direct-writes and --rocksdb_flush_log_at_trx_commit combination at startup fails
+--echo Checking rocksdb_flush_log_at_trx_commit
+--let $_mysqld_option=--log-error=$LOG --rocksdb_flush_log_at_trx_commit=1 --rocksdb_allow_mmap_writes=1
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--source include/restart_mysqld_with_invalid_option.inc
+
+--let SEARCH_PATTERN=rocksdb_flush_log_at_trx_commit needs to be
+--source include/search_pattern_in_file.inc
+--remove_file $LOG
+
+
+# Verify rocksdb_flush_log_at_trx_commit cannot be changed if direct writes are used
+--echo Validate flush_log settings when direct writes is enabled
+--let $_mysqld_option=--rocksdb_flush_log_at_trx_commit=0 --rocksdb_allow_mmap_writes=1
+--source include/restart_mysqld_with_option.inc
+
+set global rocksdb_flush_log_at_trx_commit=0;
+--error ER_WRONG_VALUE_FOR_VAR
+set global rocksdb_flush_log_at_trx_commit=1;
+--error ER_WRONG_VALUE_FOR_VAR
+set global rocksdb_flush_log_at_trx_commit=2;
+
+# Cleanup
+--source include/restart_mysqld.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/validate_datadic.test b/storage/rocksdb/mysql-test/rocksdb/t/validate_datadic.test
new file mode 100644
index 00000000..e7ab37d2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/validate_datadic.test
@@ -0,0 +1,108 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+#
+# Validate that the server starts when everything is okay, but detects errors
+# if a table exists in the data dictionary but not as an .frm or vice versa.
+# The default mode causes these failures to keep the server from starting, but
+# this is problematic for the test as a server start failure is not easily
+# trappable. Instead use the mode where it will detect the problem and report
+# it in the log bug still start: --rocksdb_validate_tables=2
+#
+
+call mtr.add_suppression('RocksDB: Schema mismatch');
+
+CREATE TABLE t1 (pk int primary key) ENGINE=ROCKSDB;
+CREATE TABLE t2 (pk int primary key) ENGINE=ROCKSDB PARTITION BY KEY(pk) PARTITIONS 4;
+
+# Write file to make mysql-test-run.pl expect the "crash", but don't restart the
+# server until it is told to
+--let $_server_id= `SELECT @@server_id`
+--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
+--let LOG=$MYSQLTEST_VARDIR/tmp/validate_datadic.err
+
+--exec echo "wait" >$_expect_file_name
+
+# Send shutdown to the connected server and give it 10 seconds to die before
+# zapping it
+shutdown_server;
+
+# Write file to make mysql-test-run.pl start up the server again
+--exec echo "restart" >$_expect_file_name
+--sleep 5
+
+# Turn on reconnect
+--enable_reconnect
+
+# Call script that will poll the server waiting for it to be back online again
+--source include/wait_until_connected_again.inc
+
+# Turn off reconnect again
+--disable_reconnect
+
+# Now shut down again and rename one of the .frm files
+--exec echo "wait" >$_expect_file_name
+shutdown_server;
+
+# Rename the file
+--move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t1.frm $MYSQLTEST_VARDIR/mysqld.1/data/test/t1.frm.tmp
+--move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t2.frm $MYSQLTEST_VARDIR/mysqld.1/data/test/t2.frm.tmp
+
+# Attempt to restart the server
+--exec echo "restart:--rocksdb_validate_tables=2 --log-error=$LOG" >$_expect_file_name
+
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+# We should now have an error message
+--echo "Expect errors that we are missing two .frm files"
+#--let SEARCH_FILE=$MYSQLTEST_VARDIR/log/mysqld.1.err
+#--let SEARCH_PATTERN=Schema mismatch
+#--source include/search_pattern_in_file.inc
+#--let SEARCH_PATTERN=RocksDB: Schema mismatch - Table test.t2 is registered in RocksDB but does not have a .frm file
+#--source include/search_pattern_in_file.inc
+--let SEARCH_FILE=$LOG
+--let SEARCH_PATTERN=RocksDB: Schema mismatch - Table test.t1 is registered in RocksDB but does not have a .frm file
+--source include/search_pattern_in_file.inc
+--let SEARCH_PATTERN=RocksDB: Schema mismatch - Table test.t2 is registered in RocksDB but does not have a .frm file
+--source include/search_pattern_in_file.inc
+
+# Now shut down again and rename one the .frm file back and make a copy of it
+--exec echo "wait" >$_expect_file_name
+shutdown_server;
+--remove_file $LOG
+# Rename the file
+--move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t1.frm.tmp $MYSQLTEST_VARDIR/mysqld.1/data/test/t1.frm
+--move_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t2.frm.tmp $MYSQLTEST_VARDIR/mysqld.1/data/test/t2.frm
+--copy_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t1.frm $MYSQLTEST_VARDIR/mysqld.1/data/test/t1_dummy.frm
+
+# Attempt to restart the server
+--exec echo "restart:--rocksdb_validate_tables=2 --log-error=$LOG" >$_expect_file_name
+
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+# We should now have an error message for the second case
+--echo "Expect an error that we have an extra .frm file"
+--let SEARCH_FILE=$LOG
+--let SEARCH_PATTERN=Schema mismatch - A .frm file exists for table test.t1_dummy, but that table is not registered in RocksDB
+--source include/search_pattern_in_file.inc
+
+# Shut down an clean up
+--exec echo "wait" >$_expect_file_name
+shutdown_server;
+--remove_file $MYSQLTEST_VARDIR/mysqld.1/data/test/t1_dummy.frm
+--exec echo "restart" >$_expect_file_name
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+# Remove log file
+--remove_file $LOG
+
+--disable_warnings
+DROP TABLE t1, t2;
+--enable_warnings
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/varbinary_format.test b/storage/rocksdb/mysql-test/rocksdb/t/varbinary_format.test
new file mode 100644
index 00000000..d10082bb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/varbinary_format.test
@@ -0,0 +1,131 @@
+--source include/have_debug.inc
+--source include/have_rocksdb.inc
+
+# Create a table with a varbinary key with the current format and validate
+# that it sorts correctly
+CREATE TABLE t1(
+ vb VARBINARY(64) primary key
+) ENGINE=rocksdb;
+
+INSERT INTO t1 values(0x00);
+INSERT INTO t1 values(0x0000);
+INSERT INTO t1 values(0x0000000000000000);
+INSERT INTO t1 values(0x000000);
+INSERT INTO t1 values(0x000000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x0000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000);
+INSERT INTO t1 values(0x0000000000);
+INSERT INTO t1 values(0x00000000000000000000);
+INSERT INTO t1 values(0x000000000000);
+INSERT INTO t1 values(0x00000000000000);
+INSERT INTO t1 values(0x000000000000000000);
+
+SELECT hex(vb) FROM t1;
+
+# Use the fact that the rocksdb_locks shows the keys as they are encoded to
+# validate that the keys were encoded as expected
+BEGIN;
+SELECT hex(vb) FROM t1 FOR UPDATE;
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+ROLLBACK;
+
+DROP TABLE t1;
+
+# Now create the same table in the old format to show that they can be read
+# and handled correctly
+set session debug_dbug= '+d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+CREATE TABLE t1(
+ vb VARBINARY(64) primary key
+) ENGINE=rocksdb;
+set session debug_dbug= '-d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+
+INSERT INTO t1 values(0x00);
+INSERT INTO t1 values(0x0000);
+INSERT INTO t1 values(0x0000000000000000);
+INSERT INTO t1 values(0x000000);
+INSERT INTO t1 values(0x000000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x0000000000000000000000000000000000000000000000000000000000000000);
+INSERT INTO t1 values(0x00000000);
+INSERT INTO t1 values(0x0000000000);
+INSERT INTO t1 values(0x00000000000000000000);
+INSERT INTO t1 values(0x000000000000);
+INSERT INTO t1 values(0x00000000000000);
+INSERT INTO t1 values(0x000000000000000000);
+
+SELECT hex(vb) FROM t1;
+
+# Use the fact that the rocksdb_locks shows the keys as they are encoded to
+# validate that the keys were encoded as expected
+BEGIN;
+SELECT hex(vb) FROM t1 FOR UPDATE;
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+ROLLBACK;
+
+DROP TABLE t1;
+
+# Now create a table with a varchar key using a binary collation with the
+# current format and validate that it sorts correctly
+CREATE TABLE t1(
+ vc VARCHAR(64) collate 'binary' primary key
+) ENGINE=rocksdb;
+
+INSERT INTO t1 values('a');
+INSERT INTO t1 values('aa');
+INSERT INTO t1 values('aaaaaaaa');
+INSERT INTO t1 values('aaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaa');
+INSERT INTO t1 values('aaaaa');
+INSERT INTO t1 values('aaaaaaaaaa');
+INSERT INTO t1 values('aaaaaa');
+INSERT INTO t1 values('aaaaaaa');
+INSERT INTO t1 values('aaaaaaaaa');
+
+SELECT * FROM t1;
+
+# Use the fact that the rocksdb_locks shows the keys as they are encoded to
+# validate that the keys were encoded as expected
+BEGIN;
+SELECT * FROM t1 FOR UPDATE;
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+ROLLBACK;
+
+DROP TABLE t1;
+
+# Now create the same table in the old format to show that they can be read
+# and handled correctly
+set session debug_dbug= '+d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+CREATE TABLE t1(
+ vc VARCHAR(64) collate 'binary' primary key
+) ENGINE=rocksdb;
+set session debug_dbug= '-d,MYROCKS_LEGACY_VARBINARY_FORMAT';
+
+INSERT INTO t1 values('a');
+INSERT INTO t1 values('aa');
+INSERT INTO t1 values('aaaaaaaa');
+INSERT INTO t1 values('aaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+INSERT INTO t1 values('aaaa');
+INSERT INTO t1 values('aaaaa');
+INSERT INTO t1 values('aaaaaaaaaa');
+INSERT INTO t1 values('aaaaaa');
+INSERT INTO t1 values('aaaaaaa');
+INSERT INTO t1 values('aaaaaaaaa');
+
+SELECT * FROM t1;
+
+# Use the fact that the rocksdb_locks shows the keys as they are encoded to
+# validate that the keys were encoded as expected
+BEGIN;
+SELECT * FROM t1 FOR UPDATE;
+SELECT SUBSTRING(a.key,9) FROM information_schema.rocksdb_locks AS a ORDER BY a.key;
+ROLLBACK;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/write_sync.test b/storage/rocksdb/mysql-test/rocksdb/t/write_sync.test
new file mode 100644
index 00000000..7c30d4fc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/write_sync.test
@@ -0,0 +1,41 @@
+--source include/have_rocksdb.inc
+
+SET GLOBAL rocksdb_write_disable_wal=false;
+SET GLOBAL rocksdb_write_ignore_missing_column_families=true;
+
+create table aaa (id int primary key, i int) engine rocksdb;
+set @save_rocksdb_flush_log_at_trx_commit=@@global.rocksdb_flush_log_at_trx_commit;
+SET GLOBAL rocksdb_flush_log_at_trx_commit=1;
+insert aaa(id, i) values(0,1);
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+insert aaa(id, i) values(1,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+insert aaa(id, i) values(2,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+insert aaa(id, i) values(3,1);
+select variable_value-@a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+SET GLOBAL rocksdb_flush_log_at_trx_commit=0;
+insert aaa(id, i) values(4,1);
+
+let $status_var=rocksdb_wal_synced;
+let $status_var_value=`select @a+1`;
+source include/wait_for_status_var.inc;
+
+select variable_value into @a from information_schema.global_status where variable_name='rocksdb_wal_synced';
+SET GLOBAL rocksdb_flush_log_at_trx_commit=2;
+insert aaa(id, i) values(5,1);
+
+let $status_var=rocksdb_wal_synced;
+let $status_var_value=`select @a+1`;
+source include/wait_for_status_var.inc;
+
+truncate table aaa;
+
+# Cleanup
+drop table aaa;
+set @@global.rocksdb_flush_log_at_trx_commit=@save_rocksdb_flush_log_at_trx_commit;
+SET GLOBAL rocksdb_write_disable_wal=false;
+SET GLOBAL rocksdb_write_ignore_missing_column_families=false;
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/xa-master.opt b/storage/rocksdb/mysql-test/rocksdb/t/xa-master.opt
new file mode 100644
index 00000000..70c12060
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/xa-master.opt
@@ -0,0 +1 @@
+--rocksdb_flush_log_at_trx_commit=1
diff --git a/storage/rocksdb/mysql-test/rocksdb/t/xa.test b/storage/rocksdb/mysql-test/rocksdb/t/xa.test
new file mode 100644
index 00000000..0c23e71d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb/t/xa.test
@@ -0,0 +1,73 @@
+--echo #
+--echo # MDEV-742 fixes
+--echo # MDEV-13155: XA recovery not supported for RocksDB
+--echo # as well.
+
+call mtr.add_suppression("Found .* prepared XA transactions");
+
+--connect (con1,localhost,root,,test)
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+CREATE TABLE t1 (a INT) ENGINE=RocksDB;
+
+XA START 'xa1';
+INSERT INTO t1 (a) VALUES (1),(2);
+XA END 'xa1';
+XA PREPARE 'xa1';
+
+--connect (con2,localhost,root,,test)
+XA START 'xa2';
+INSERT INTO t1 (a) VALUES (3);
+INSERT INTO t1 (a) VALUES (4);
+XA END 'xa2';
+XA PREPARE 'xa2';
+
+--connect (con3,localhost,root,,test)
+XA START 'xa3';
+INSERT INTO t1 (a) VALUES (5);
+INSERT INTO t1 (a) VALUES (6);
+XA END 'xa3';
+XA PREPARE 'xa3';
+--disconnect con3
+
+--connection default
+SELECT * FROM t1;
+
+--echo Must be all three XA:s in
+XA RECOVER;
+
+--let $shutdown_timeout= 0
+--source include/restart_mysqld.inc
+
+--connect (con3,localhost,root,,test)
+--disable_abort_on_error
+XA RECOVER; # like above
+XA ROLLBACK 'xa1';
+XA COMMIT 'xa2';
+XA ROLLBACK 'xa3';
+SELECT a FROM t1;
+
+--connect (con4,localhost,root,,test)
+XA START 'xa4';
+INSERT INTO t1 (a) VALUES (7);
+INSERT INTO t1 (a) VALUES (8);
+XA END 'xa4';
+XA PREPARE 'xa4';
+
+--connection default
+--echo # Now restart through graceful shutdown
+--source include/restart_mysqld.inc
+
+
+--connect (con5,localhost,root,,test)
+--disable_abort_on_error
+
+--echo Must have 'xa4'
+XA RECOVER;
+XA COMMIT 'xa4';
+
+SELECT a FROM t1;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/base.cnf b/storage/rocksdb/mysql-test/rocksdb_hotbackup/base.cnf
new file mode 100644
index 00000000..101dbce2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/base.cnf
@@ -0,0 +1,25 @@
+# Use default setting for mysqld processes
+!include include/default_mysqld.cnf
+!include include/default_client.cnf
+
+[mysqld.1]
+rocksdb
+default-storage-engine=rocksdb
+skip-innodb
+default-tmp-storage-engine=MyISAM
+binlog_format=row
+
+[mysqld.2]
+rocksdb
+default-storage-engine=rocksdb
+skip-innodb
+default-tmp-storage-engine=MyISAM
+binlog_format=row
+
+[ENV]
+MASTER_MYPORT= @mysqld.1.port
+MASTER_MYSOCK= @mysqld.1.socket
+
+SLAVE_MYPORT= @mysqld.2.port
+SLAVE_MYSOCK= @mysqld.2.socket
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/clean_tmpfiles.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/clean_tmpfiles.sh
new file mode 100755
index 00000000..98a1fecc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/clean_tmpfiles.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+COPY_LOG="${MYSQL_TMP_DIR}/myrocks_hotbackup_copy_log"
+SIGNAL_FILE=${MYSQL_TMP_DIR}/myrocks_hotbackup_signal
+MOVEBACK_LOG="${MYSQL_TMP_DIR}/myrocks_hotbackup_moveback_log"
+rm -f $COPY_LOG
+rm -f $SIGNAL_FILE
+rm -f $MOVEBACK_LOG
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/cleanup.inc b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/cleanup.inc
new file mode 100644
index 00000000..947bf027
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/cleanup.inc
@@ -0,0 +1,3 @@
+
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_slocket_socket.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_slocket_socket.sh
new file mode 100755
index 00000000..db470f52
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_slocket_socket.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+src_data_dir="${MYSQLTEST_VARDIR}/mysqld.1/data/"
+python -c "import socket as s; sock = s.socket(s.AF_UNIX); sock.bind('${src_data_dir}/slocket')"
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_table.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_table.sh
new file mode 100755
index 00000000..2004caca
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/create_table.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+
+COPY_LOG=$1
+SIGNAL_FILE=$2
+# Creating a table after myrocks_hotbackup reaches waiting loop
+
+done=0
+while : ; do
+ wait=`tail -1 $COPY_LOG | grep 'Waiting until' | wc -l`
+ if [ "$wait" -eq "1" ]; then
+ break
+ fi
+ sleep 1
+done
+$MYSQL --defaults-group-suffix=.1 db1 -e "create table r10 (id int primary key ) engine=rocksdb"
+touch $SIGNAL_FILE
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data.sh
new file mode 100755
index 00000000..80f1a5e2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+
+# Insert 100 batches of 100 records each to a table with following schema:
+# create table db1.t1 (
+# `id` int(10) not null auto_increment,
+# `k` int(10),
+# `data` varchar(2048),
+# primary key (`id`),
+# key (`k`)
+# ) engine=innodb;
+
+MAX_INSERTS=100
+MAX_ROWS_PER_INSERT=100
+
+insertData() {
+ for ((i=1; i<=$MAX_INSERTS; i++));
+ do
+ stmt='INSERT INTO db1.t1 values'
+ for ((j=1; j<=$MAX_ROWS_PER_INSERT; j++));
+ do
+ k=$RANDOM
+ data=$(head -c 2048 /dev/urandom|tr -cd 'a-zA-Z0-9')
+ stmt=$stmt' (NULL, '$k', "'$data'")'
+ if [ $j -lt $MAX_ROWS_PER_INSERT ]; then
+ stmt=$stmt','
+ fi
+ done
+ stmt=$stmt';'
+ $MYSQL --defaults-group-suffix=.1 -e "$stmt"
+ done
+}
+
+NUM_PARALLEL_INSERTS=25
+pids=()
+for ((k=1; k<=$NUM_PARALLEL_INSERTS; k++));
+do
+ insertData &
+ pids+=($!)
+done
+for ((k=1; k<=$NUM_PARALLEL_INSERTS; k++));
+do
+ wait ${pids[k]}
+done
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_and_run.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_and_run.sh
new file mode 100755
index 00000000..a8e6fc44
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_and_run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+# Initially loads a chunk of data.
+# Then start loading another chunk of data,
+# while simultaneously running a backup
+
+suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+suite/rocksdb_hotbackup/include/load_data.sh 2>&1 &
+suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_slocket.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_slocket.sh
new file mode 100755
index 00000000..036d6866
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/load_data_slocket.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+
+# Insert 10 batches of 10 records each to a table with following schema:
+# create table slocket.t1 (
+# `id` int(10) not null auto_increment,
+# `k` int(10),
+# `data` varchar(2048),
+# primary key (`id`),
+# key (`k`)
+# ) engine=innodb;
+
+MAX_INSERTS=10
+MAX_ROWS_PER_INSERT=10
+
+insertData() {
+ for ((i=1; i<=$MAX_INSERTS; i++));
+ do
+ stmt='INSERT INTO slocket.t1 values'
+ for ((j=1; j<=$MAX_ROWS_PER_INSERT; j++));
+ do
+ k=$RANDOM
+ data=$(head -c 2048 /dev/urandom|tr -cd 'a-zA-Z0-9')
+ stmt=$stmt' (NULL, '$k', "'$data'")'
+ if [ $j -lt $MAX_ROWS_PER_INSERT ]; then
+ stmt=$stmt','
+ fi
+ done
+ stmt=$stmt';'
+ $MYSQL --defaults-group-suffix=.1 -e "$stmt"
+ done
+}
+
+NUM_PARALLEL_INSERTS=25
+pids=()
+for ((k=1; k<=$NUM_PARALLEL_INSERTS; k++));
+do
+ insertData &
+ pids+=($!)
+done
+for ((k=1; k<=$NUM_PARALLEL_INSERTS; k++));
+do
+ wait ${pids[k]}
+done
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/remove_slocket_socket.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/remove_slocket_socket.sh
new file mode 100755
index 00000000..9114629b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/remove_slocket_socket.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+src_data_dir="${MYSQLTEST_VARDIR}/mysqld.1/data/"
+rm "${src_data_dir}/slocket"
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup.inc b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup.inc
new file mode 100644
index 00000000..26c3f2ce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup.inc
@@ -0,0 +1,16 @@
+--let $rpl_server_count= 2
+--let $rpl_topology= none
+--source include/rpl_init.inc
+--source include/rpl_default_connections.inc
+
+connection server_1;
+create database db1;
+
+create table db1.t1 (
+ `id` int(10) not null auto_increment,
+ `k` int(10),
+ `data` varchar(2048),
+ primary key (`id`),
+ key (`k`)
+) engine=rocksdb;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid.sh
new file mode 100755
index 00000000..3c95068a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -e
+
+binlog_line=($(grep -o "Last binlog file position [0-9]*, file name .*\.[0-9]*" ${MYSQLTEST_VARDIR}/log/mysqld.2.err | tail -1))
+binlog_pos=${binlog_line[4]%?}
+binlog_file=${binlog_line[7]}
+
+sql="show gtid_executed in '$binlog_file' from $binlog_pos"
+result=($($MYSQL --defaults-group-suffix=.1 -e "$sql"))
+gtid_executed=${result[1]}
+
+sql="reset master;"
+sql="$sql reset slave;"
+sql="$sql change master to master_host='127.0.0.1', master_port=${MASTER_MYPORT}, master_user='root', master_auto_position=1, master_connect_retry=1;"
+sql="$sql set global gtid_purged='$gtid_executed';"
+sql="$sql start slave;"
+sql="$sql stop slave;"
+sql="$sql change master to master_auto_position=0;"
+sql="$sql start slave;"
+$MYSQL --defaults-group-suffix=.2 -e "$sql"
+echo "$sql" > ${MYSQL_TMP_DIR}/gtid_stmt
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid_and_sync.inc b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid_and_sync.inc
new file mode 100644
index 00000000..75dc3196
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_replication_gtid_and_sync.inc
@@ -0,0 +1,4 @@
+--exec suite/rocksdb_hotbackup/include/setup_replication_gtid.sh
+
+let $slave_sync_timeout = 1800;
+source include/wait_for_slave_to_sync_with_master.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_slocket.inc b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_slocket.inc
new file mode 100644
index 00000000..ce889164
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/setup_slocket.inc
@@ -0,0 +1,10 @@
+connection server_1;
+create database slocket;
+
+create table slocket.t1 (
+ `id` int(10) not null auto_increment,
+ `k` int(10),
+ `data` varchar(2048),
+ primary key (`id`),
+ key (`k`)
+) engine=rocksdb;
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/stream_run.sh b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/stream_run.sh
new file mode 100755
index 00000000..6108cfbb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/include/stream_run.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+. suite/rocksdb_hotbackup/include/clean_tmpfiles.sh
+
+if [ "$STREAM_TYPE" == 'wdt' ]; then
+ which wdt >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ # change to tar if wdt is not installed
+ STREAM_TYPE='tar'
+ fi
+fi
+
+set -e
+set -o pipefail
+
+# Takes a full backup from server_1 to server_2
+# using myrocks_hotbackup streaming
+
+checkpoint_dir="${MYSQLTEST_VARDIR}/checkpoint"
+backup_dir="${MYSQLTEST_VARDIR}/backup"
+dest_data_dir="${MYSQLTEST_VARDIR}/mysqld.2/data/"
+
+mysql_dir=$(echo $MYSQL | awk '{print $1}' | xargs dirname)
+PATH=$mysql_dir:$PATH
+
+mkdir -p $checkpoint_dir
+rm -rf $checkpoint_dir/*
+
+mkdir -p $backup_dir
+rm -rf $backup_dir/*
+# delete and recreate the dest dir to make sure all hidden files
+# and directories (such as .rocksdb) are blown away
+rm -rf $dest_data_dir/
+mkdir $dest_data_dir
+
+
+SIGNAL_CONDITION=""
+
+if [ "$FRM" == '1' ]; then
+ suite/rocksdb_hotbackup/include/create_table.sh $COPY_LOG $SIGNAL_FILE 2>&1 &
+fi
+
+if [ "$DEBUG_SIGNAL" == '1' ]; then
+ SIGNAL_CONDITION="--debug_signal_file=$SIGNAL_FILE"
+fi
+
+if [ "$STREAM_TYPE" == 'tar' ]; then
+ BACKUP_CMD="$MYSQL_MYROCKS_HOTBACKUP --user='root' --port=${MASTER_MYPORT} \
+ --stream=tar --checkpoint_dir=$checkpoint_dir $SIGNAL_CONDITION 2> \
+ $COPY_LOG | tar -xi -C $backup_dir"
+elif [ "$STREAM_TYPE" == 'wdt' ]; then
+ BACKUP_CMD="$MYSQL_MYROCKS_HOTBACKUP --user='root' --stream=wdt \
+ --port=${MASTER_MYPORT} --destination=localhost --backup_dir=$backup_dir \
+ --avg_mbytes_per_sec=10 --interval=5 $SIGNAL_CONDITION \
+ --extra_wdt_sender_options='--block_size_mbytes=1' \
+ --checkpoint_dir=$checkpoint_dir 2> \
+ $COPY_LOG"
+elif [ "$STREAM_TYPE" == "xbstream_socket" ]; then
+ BACKUP_CMD="$MYSQL_MYROCKS_HOTBACKUP --user='root' --socket=${MASTER_MYSOCK} \
+ --stream=xbstream --checkpoint_dir=$checkpoint_dir $SIGNAL_CONDITION 2> \
+ $COPY_LOG | xbstream -x \
+ --directory=$backup_dir"
+else
+ BACKUP_CMD="$MYSQL_MYROCKS_HOTBACKUP --user='root' --port=${MASTER_MYPORT} \
+ --stream=xbstream --checkpoint_dir=$checkpoint_dir $SIGNAL_CONDITION 2> \
+ $COPY_LOG | xbstream -x \
+ --directory=$backup_dir"
+fi
+
+echo "myrocks_hotbackup copy phase"
+eval "$BACKUP_CMD"
+
+mkdir ${backup_dir}/test # TODO: Fix skipping empty directories
+
+
+echo "myrocks_hotbackup move-back phase"
+$MYSQL_MYROCKS_HOTBACKUP --move_back --datadir=$dest_data_dir \
+ --rocksdb_datadir=$dest_data_dir/\#rocksdb \
+ --rocksdb_waldir=$dest_data_dir/\#rocksdb \
+ --backup_dir=$backup_dir > $MOVEBACK_LOG 2>&1
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/my.cnf b/storage/rocksdb/mysql-test/rocksdb_hotbackup/my.cnf
new file mode 100644
index 00000000..bd9af04c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/my.cnf
@@ -0,0 +1,2 @@
+# Use settings from xb_base.cnf
+!include base.cnf
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/gtid.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/gtid.result
new file mode 100644
index 00000000..6cec6ca5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/gtid.result
@@ -0,0 +1,23 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+stop slave;
+start slave;
+select count(*) from db1.t1;
+count(*)
+500000
+drop database db1;
+stop slave;
+reset slave;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/slocket.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/slocket.result
new file mode 100644
index 00000000..9accd18b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/slocket.result
@@ -0,0 +1,41 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+create database slocket;
+create table slocket.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+select count(*) from slocket.t1;
+count(*)
+2500
+drop database slocket;
+drop database db1;
+drop database slocket;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/stream.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/stream.result
new file mode 100644
index 00000000..d3f2ebc4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/stream.result
@@ -0,0 +1,20 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/wdt.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/wdt.result
new file mode 100644
index 00000000..d3f2ebc4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/wdt.result
@@ -0,0 +1,20 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream.result
new file mode 100644
index 00000000..31ed2677
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream.result
@@ -0,0 +1,21 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_direct.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_direct.result
new file mode 100644
index 00000000..31ed2677
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_direct.result
@@ -0,0 +1,21 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_socket.result b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_socket.result
new file mode 100644
index 00000000..d3f2ebc4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/r/xbstream_socket.result
@@ -0,0 +1,20 @@
+include/rpl_init.inc [topology=none]
+include/rpl_default_connections.inc
+create database db1;
+create table db1.t1 (
+`id` int(10) not null auto_increment,
+`k` int(10),
+`data` varchar(2048),
+primary key (`id`),
+key (`k`)
+) engine=rocksdb;
+include/rpl_stop_server.inc [server_number=2]
+myrocks_hotbackup copy phase
+myrocks_hotbackup move-back phase
+include/rpl_start_server.inc [server_number=2]
+select count(*) from db1.t1;
+count(*)
+250000
+drop database db1;
+drop database db1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-master.opt b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-master.opt
new file mode 100644
index 00000000..9d7af67e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-master.opt
@@ -0,0 +1 @@
+--gtid_mode=on --log_slave_updates=on --enforce_gtid_consistency=on
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-slave.opt b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-slave.opt
new file mode 100644
index 00000000..9d7af67e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid-slave.opt
@@ -0,0 +1 @@
+--gtid_mode=on --log_slave_updates=on --enforce_gtid_consistency=on
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid.test
new file mode 100644
index 00000000..f9d58da0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/gtid.test
@@ -0,0 +1,47 @@
+
+source suite/rocksdb_hotbackup/include/setup.inc;
+
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec suite/rocksdb_hotbackup/include/load_data_and_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+let $num_rows = `select count(*) from db1.t1`;
+let $max_id = `select id from db1.t1 order by id desc limit 1`;
+
+if($num_rows != $max_id) {
+ echo Number of rows($num_rows) and last_id($max_id) does not match;
+}
+if($num_rows < 250000) {
+ echo Number of rows($num_rows) is less than 250000;
+}
+if($num_rows > 500000) {
+ echo Number of rows($num_rows) is more than 500000;
+}
+
+--source suite/rocksdb_hotbackup/include/setup_replication_gtid_and_sync.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+let $checksum1 = `checksum tables db1.t1`;
+connection server_2;
+let $checksum2 = `checksum tables db1.t1`;
+
+if($checksum1 != $checksum2) {
+ echo Checksums ($checksum1 and $checksum2) do not match;
+}
+
+connection server_1;
+drop database db1;
+sync_slave_with_master;
+connection server_2;
+stop slave;
+reset slave;
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/slocket.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/slocket.test
new file mode 100644
index 00000000..14ad8d23
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/slocket.test
@@ -0,0 +1,46 @@
+source suite/rocksdb_hotbackup/include/setup.inc;
+source suite/rocksdb_hotbackup/include/setup_slocket.inc;
+
+--exec suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+--exec suite/rocksdb_hotbackup/include/load_data_slocket.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+select count(*) from slocket.t1;
+
+connection server_1;
+drop database slocket;
+connection server_2;
+drop database db1;
+drop database slocket;
+
+--exec sleep 2
+--exec suite/rocksdb_hotbackup/include/create_slocket_socket.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+drop database db1;
+connection server_2;
+drop database db1;
+
+--exec suite/rocksdb_hotbackup/include/remove_slocket_socket.sh 2>&1
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/stream.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/stream.test
new file mode 100644
index 00000000..2b999f3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/stream.test
@@ -0,0 +1,22 @@
+
+source suite/rocksdb_hotbackup/include/setup.inc;
+
+--exec suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec STREAM_TYPE=tar suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+drop database db1;
+connection server_2;
+drop database db1;
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/wdt.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/wdt.test
new file mode 100644
index 00000000..2d2ed891
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/wdt.test
@@ -0,0 +1,22 @@
+
+source suite/rocksdb_hotbackup/include/setup.inc;
+
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+--exec STREAM_TYPE=wdt suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+drop database db1;
+connection server_2;
+drop database db1;
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.inc b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.inc
new file mode 100644
index 00000000..52456a68
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.inc
@@ -0,0 +1,25 @@
+
+source suite/rocksdb_hotbackup/include/setup.inc;
+
+--exec suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--error 1
+--exec STREAM_TYPE=xbstream FRM=1 DEBUG_SIGNAL=1 suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--exec STREAM_TYPE=xbstream suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+drop database db1;
+connection server_2;
+drop database db1;
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.test
new file mode 100644
index 00000000..18816c34
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+--source xbstream.inc
+let SEARCH_FILE= $MYSQL_TMP_DIR/myrocks_hotbackup_copy_log;
+let SEARCH_PATTERN= Direct I/O: 0;
+--source include/search_pattern_in_file.inc
+
+--exec suite/rocksdb_hotbackup/include/clean_tmpfiles.sh
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct-master.opt b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct-master.opt
new file mode 100644
index 00000000..4ab98aea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct-master.opt
@@ -0,0 +1 @@
+--rocksdb_use_direct_reads=ON --rocksdb_use_direct_io_for_flush_and_compaction=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct.test
new file mode 100644
index 00000000..41357d68
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_direct.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+--source xbstream.inc
+let SEARCH_FILE= $MYSQL_TMP_DIR/myrocks_hotbackup_copy_log;
+let SEARCH_PATTERN= Direct I/O: 1;
+--source include/search_pattern_in_file.inc
+
+--exec suite/rocksdb_hotbackup/include/clean_tmpfiles.sh
diff --git a/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_socket.test b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_socket.test
new file mode 100644
index 00000000..28edff07
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_hotbackup/t/xbstream_socket.test
@@ -0,0 +1,22 @@
+
+source suite/rocksdb_hotbackup/include/setup.inc;
+
+--exec suite/rocksdb_hotbackup/include/load_data.sh 2>&1
+--let $rpl_server_number= 2
+--source include/rpl_stop_server.inc
+
+--exec STREAM_TYPE=xbstream_socket suite/rocksdb_hotbackup/include/stream_run.sh 2>&1
+
+--let $rpl_server_number= 2
+--source include/rpl_start_server.inc
+
+connection server_2;
+select count(*) from db1.t1;
+
+connection server_1;
+drop database db1;
+connection server_2;
+drop database db1;
+
+source suite/rocksdb_hotbackup/include/cleanup.inc;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/combinations b/storage/rocksdb/mysql-test/rocksdb_rpl/combinations
new file mode 100644
index 00000000..eae74316
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/combinations
@@ -0,0 +1,7 @@
+[row-write-committed]
+binlog-format=row
+rocksdb_write_policy=write_committed
+
+[row-write-prepared]
+binlog-format=row
+rocksdb_write_policy=write_prepared
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.inc b/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.inc
new file mode 100644
index 00000000..1f762d38
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.inc
@@ -0,0 +1,10 @@
+if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'rocksdb' AND support IN ('YES', 'DEFAULT', 'ENABLED')`)
+{
+ --skip Test requires engine RocksDB.
+}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.opt
new file mode 100644
index 00000000..36d7dda1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/include/have_rocksdb.opt
@@ -0,0 +1,12 @@
+--loose-enable-rocksdb
+--loose-enable-rocksdb_global_info
+--loose-enable-rocksdb_ddl
+--loose-enable-rocksdb_cf_options
+--loose-enable_rocksdb_perf_context
+--loose-enable_rocksdb_perf_context_global
+--loose-enable-rocksdb_index_file_map
+--loose-enable-rocksdb_dbstats
+--loose-enable-rocksdb_cfstats
+--loose-enable-rocksdb_lock_info
+--loose-enable-rocksdb_trx
+--loose-enable-rocksdb_locks
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_gtid_crash_safe.inc b/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_gtid_crash_safe.inc
new file mode 100644
index 00000000..f0c0134e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_gtid_crash_safe.inc
@@ -0,0 +1,37 @@
+
+-- let $engine = ROCKSDB
+
+call mtr.add_suppression("Recovery from master pos");
+
+-- let $debug_option = crash_before_update_pos
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_after_update_pos_before_apply
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_before_writing_xid
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = half_binlogged_transaction
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_commit_before
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_commit_after_log
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_commit_after_prepare
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_reset.inc
+-- let $debug_option = crash_commit_after
+-- source extra/rpl_tests/rpl_gtid_crash_safe.inc
+
+-- source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_no_unique_check_on_lag.inc b/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_no_unique_check_on_lag.inc
new file mode 100644
index 00000000..d983bdf8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/include/rpl_no_unique_check_on_lag.inc
@@ -0,0 +1,72 @@
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+--source include/not_embedded.inc
+--source include/not_valgrind.inc
+
+call mtr.add_suppression("Slave SQL: Could not execute Write_rows event on table test.t1");
+call mtr.add_suppression(".*Worker.*failed executing transaction");
+call mtr.add_suppression(".*The slave coordinator and worker threads are stopped");
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+connection master;
+CREATE TABLE t1 (id int primary key, value int) engine=RocksDB;
+sync_slave_with_master;
+--let $rsbm = query_get_value(select @@global.reset_seconds_behind_master, @@global.reset_seconds_behind_master, 1)
+set global reset_seconds_behind_master=1;
+
+connection slave;
+INSERT INTO t1 VALUES(1, 0);
+INSERT INTO t1 VALUES(2, 0);
+INSERT INTO t1 VALUES(3, 0);
+
+connection master;
+sync_slave_with_master;
+connection master;
+INSERT INTO t1 VALUES(1, 1);
+
+connection slave;
+--let $slave_sql_errno= 1062
+--let $not_switch_connection= 0
+--let $slave_timeout= 120
+--source include/wait_for_slave_sql_error_and_skip.inc
+set global reset_seconds_behind_master=0;
+--source include/stop_slave_io.inc
+
+connection master;
+INSERT INTO t1 values (4,0);
+--sleep 11
+INSERT INTO t1 VALUES(2, 1);
+
+connection slave;
+--source include/start_slave_io.inc
+
+connection master;
+sync_slave_with_master;
+
+connection slave;
+set global reset_seconds_behind_master=1;
+
+connection master;
+insert into t1 values (5,0);
+--sleep 1
+sync_slave_with_master;
+
+connection master;
+INSERT INTO t1 VALUES(3, 1);
+
+connection slave;
+--let $slave_sql_errno= 1062
+--let $not_switch_connection= 0
+--source include/wait_for_slave_sql_error_and_skip.inc
+
+--echo #
+--echo # Cleanup
+--echo #
+
+connection master;
+DROP TABLE t1;
+eval set global reset_seconds_behind_master=$rsbm;
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/my.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/my.cnf
new file mode 100644
index 00000000..518b1653
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/my.cnf
@@ -0,0 +1,17 @@
+!include rpl_1slave_base.cnf
+!include include/default_my.cnf
+
+[server]
+skip-innodb
+default-storage-engine=rocksdb
+
+sql-mode=NO_ENGINE_SUBSTITUTION
+explicit-defaults-for-timestamp=1
+loose-rocksdb_lock_wait_timeout=1
+loose-rocksdb_strict_collation_check=0
+
+loose-rocksdb-flush-log-at-trx-commit=0
+
+# The following is to get rid of the harmless
+# "Deadlock found when trying to get lock" errors, see MDEV-12285.
+log-warnings=1
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/consistent_snapshot_mixed_engines.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/consistent_snapshot_mixed_engines.result
new file mode 100644
index 00000000..31777c45
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/consistent_snapshot_mixed_engines.result
@@ -0,0 +1,68 @@
+DROP TABLE IF EXISTS t1;
+connect con1,localhost,root,,;
+connect con2,localhost,root,,;
+connection con1;
+create table i1 (id int primary key , value int) engine=innodb;
+create table r1 (id int primary key , value int) engine=rocksdb;
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection con2;
+insert into i1 values (1,1);
+insert into r1 values (1,1);
+connection con1;
+select * from i1;
+id value
+select * from r1;
+id value
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 1115 uuid:1-5
+connection con2;
+insert into i1 values (2,2);
+insert into r1 values (2,2);
+connection con1;
+select * from i1;
+id value
+1 1
+2 2
+select * from r1;
+id value
+1 1
+connection con2;
+insert into i1 values (3,2);
+insert into r1 values (3,2);
+connection con1;
+select * from i1;
+id value
+1 1
+2 2
+select * from r1;
+id value
+1 1
+START TRANSACTION WITH CONSISTENT INNODB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 2015 uuid:1-9
+connection con2;
+insert into r1 values (4,4);
+connection con1;
+select * from r1;
+id value
+1 1
+2 2
+3 2
+4 4
+connection con2;
+insert into r1 values (5,5);
+connection con1;
+select * from r1;
+id value
+1 1
+2 2
+3 2
+4 4
+drop table i1;
+drop table r1;
+connection default;
+disconnect con1;
+disconnect con2;
+reset master;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/mdev12179.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/mdev12179.result
new file mode 100644
index 00000000..a1e501f7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/mdev12179.result
@@ -0,0 +1,283 @@
+include/master-slave.inc
+[connection master]
+connection server_2;
+include/stop_slave.inc
+SET GLOBAL gtid_cleanup_batch_size = 999999999;
+CHANGE MASTER TO master_use_gtid=slave_pos;
+SET sql_log_bin=0;
+CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB;
+CREATE TABLE mysql.gtid_slave_pos_rocksdb LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_rocksdb ENGINE=rocksdb;
+CREATE TABLE mysql.gtid_slave_pos_myisam_redundant LIKE mysql.gtid_slave_pos;
+CREATE TABLE mysql.gtid_slave_pos_innodb_redundant LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_innodb_redundant ENGINE=InnoDB;
+call mtr.add_suppression("Ignoring redundant table.*since.*has the same storage engine");
+include/start_slave.inc
+connection server_1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=MyISAM;
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB;
+CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (1);
+INSERT INTO t3 VALUES (1);
+SELECT * FROM t1 ORDER BY a;
+a
+1
+SELECT * FROM t2 ORDER BY a;
+a
+1
+SELECT * FROM t3 ORDER BY a;
+a
+1
+connection server_2;
+SELECT * FROM t1 ORDER BY a;
+a
+1
+SELECT * FROM t2 ORDER BY a;
+a
+1
+SELECT * FROM t3 ORDER BY a;
+a
+1
+SELECT * FROM mysql.gtid_slave_pos ORDER BY sub_id;
+domain_id sub_id server_id seq_no
+0 1 1 1
+0 2 1 2
+0 3 1 3
+0 4 1 4
+SELECT * FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb
+UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select
+ORDER BY sub_id;
+domain_id sub_id server_id seq_no
+0 5 1 5
+SELECT * FROM mysql.gtid_slave_pos_rocksdb ORDER BY sub_id;
+domain_id sub_id server_id seq_no
+0 6 1 6
+connection server_2;
+FLUSH NO_WRITE_TO_BINLOG STATUS;
+SET sql_log_bin=0;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 0
+INSERT INTO t1 VALUES (100);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 0
+INSERT INTO t2 VALUES (101);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 0
+INSERT INTO t3 VALUES (101);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 0
+BEGIN;
+INSERT INTO t3 VALUES (102);
+INSERT INTO t2 VALUES (103);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 1
+BEGIN;
+INSERT INTO t2 VALUES (104);
+INSERT INTO t3 VALUES (105);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 2
+UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 3
+SET sql_log_bin=1;
+INSERT INTO t1 VALUES (200);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 3
+INSERT INTO t2 VALUES (201);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 3
+INSERT INTO t3 VALUES (201);
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 3
+BEGIN;
+INSERT INTO t3 VALUES (202);
+INSERT INTO t2 VALUES (203);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 4
+BEGIN;
+INSERT INTO t2 VALUES (204);
+INSERT INTO t3 VALUES (205);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 5
+UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205;
+SHOW STATUS LIKE "Transactions_multi_engine";
+Variable_name Value
+Transactions_multi_engine 6
+DELETE FROM t1 WHERE a >= 100;
+DELETE FROM t2 WHERE a >= 100;
+DELETE FROM t3 WHERE a >= 100;
+connection server_1;
+include/save_master_gtid.inc
+connection server_2;
+include/sync_with_master_gtid.inc
+SELECT COUNT(*)>=10 FROM mysql.gtid_slave_pos;
+COUNT(*)>=10
+1
+SELECT COUNT(*)>=10 FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb
+UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select;
+COUNT(*)>=10
+1
+SELECT COUNT(*)>=10 FROM mysql.gtid_slave_pos_rocksdb;
+COUNT(*)>=10
+1
+SET GLOBAL gtid_cleanup_batch_size = 3;
+connection server_2;
+include/stop_slave.inc
+SET sql_log_bin=0;
+DROP TABLE mysql.gtid_slave_pos_rocksdb;
+DROP TABLE mysql.gtid_slave_pos_myisam_redundant;
+DROP TABLE mysql.gtid_slave_pos_innodb_redundant;
+SET sql_log_bin=1;
+FLUSH NO_WRITE_TO_BINLOG STATUS;
+include/start_slave.inc
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t1 VALUES (100);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t2 VALUES (101);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t3 VALUES (101);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 0
+connection server_1;
+BEGIN;
+INSERT INTO t3 VALUES (102);
+INSERT INTO t2 VALUES (103);
+COMMIT;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 1
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 1
+connection server_1;
+BEGIN;
+INSERT INTO t2 VALUES (104);
+INSERT INTO t3 VALUES (105);
+COMMIT;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 2
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 2
+connection server_1;
+UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 3
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 3
+connection server_2;
+connection server_2;
+SHOW VARIABLES LIKE 'log_bin';
+Variable_name Value
+log_bin OFF
+include/start_slave.inc
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t1 VALUES (200);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t2 VALUES (201);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 0
+Transactions_multi_engine 0
+connection server_1;
+INSERT INTO t3 VALUES (201);
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 0
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 0
+connection server_1;
+BEGIN;
+INSERT INTO t3 VALUES (202);
+INSERT INTO t2 VALUES (203);
+COMMIT;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 1
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 1
+connection server_1;
+BEGIN;
+INSERT INTO t2 VALUES (204);
+INSERT INTO t3 VALUES (205);
+COMMIT;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 2
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 2
+connection server_1;
+UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205;
+connection server_2;
+SHOW STATUS LIKE "%transactions%engine";
+Variable_name Value
+Rpl_transactions_multi_engine 3
+Transactions_gtid_foreign_engine 1
+Transactions_multi_engine 3
+connection server_2;
+SET sql_log_bin=0;
+DROP TABLE mysql.gtid_slave_pos_innodb;
+SET sql_log_bin=1;
+connection server_1;
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/multiclient_2pc.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/multiclient_2pc.result
new file mode 100644
index 00000000..493107ec
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/multiclient_2pc.result
@@ -0,0 +1,26 @@
+DROP TABLE IF EXISTS t1;
+SET GLOBAL MAX_BINLOG_SIZE = 4096;
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+create table t1 (a int primary key, b int, c varchar(255)) engine=rocksdb;
+'con1'
+SET SESSION debug="d,crash_commit_after_log";
+SET DEBUG_SYNC='rocksdb.prepared SIGNAL parked WAIT_FOR go';
+insert into t1 values (1, 1, "iamtheogthealphaandomega");;
+'con2'
+insert into t1 values (2, 1, "i_am_just_here_to_trigger_a_flush");
+SET GLOBAL ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 0;
+SET GLOBAL SYNC_BINLOG = 0;
+SET DEBUG_SYNC='now WAIT_FOR parked';
+SET GLOBAL ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 2;
+SET GLOBAL SYNC_BINLOG = 1;
+insert into t1 values (1000000, 1, "i_am_just_here_to_trigger_a_flush");
+SET DEBUG_SYNC='now SIGNAL go';
+**found 'prepare' log entry**
+**found 'commit' log entry**
+select * from t1 where a=1;
+a b c
+1 1 iamtheogthealphaandomega
+select count(*) from t1;
+count(*)
+4096
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/optimize_myrocks_replace_into.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/optimize_myrocks_replace_into.result
new file mode 100644
index 00000000..1b41405f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/optimize_myrocks_replace_into.result
@@ -0,0 +1,282 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+SET GLOBAL enable_blind_replace=ON;
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+c1 c2
+1 1
+2 2
+3 3
+create table t2(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t2 values(1,1),(2,2),(3,3);
+select * from t2;
+c1 c2
+1 1
+2 2
+3 3
+create table t3(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t3 values(1,1),(2,2),(3,3);
+select * from t3;
+c1 c2
+1 1
+2 2
+3 3
+SET GLOBAL enable_blind_replace=ON;
+create trigger trg before insert on t2 for each row set @a:=1;
+alter table t3 add constraint slave_unique_key unique (c2);
+connect slave
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+Case 1
+connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(1,11);
+replace into t1 values(2,22);
+replace into t1 values(3,33);
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+c1 c2
+1 11
+2 22
+3 33
+connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+c1 c2
+1 11
+2 22
+3 33
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+Case 2
+connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(2,44),(3,55);
+select case when variable_value-@d > 2 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+c1 c2
+1 11
+2 44
+3 55
+connect slave
+select case when variable_value-@d > 2 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t1;
+c1 c2
+1 11
+2 44
+3 55
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+Case 3
+connect master
+update t1 set c2=66 where c1=3;
+select * from t1;
+c1 c2
+1 11
+2 44
+3 66
+connect slave
+select * from t1;
+c1 c2
+1 11
+2 44
+3 66
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+Case 4
+connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t2 values(1,111);
+replace into t2 values(2,222);
+replace into t2 values(3,333);
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+select * from t2;
+c1 c2
+1 111
+2 222
+3 333
+connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+select * from t2;
+c1 c2
+1 111
+2 222
+3 333
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+Case 5
+connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t3 values(1,1111);
+replace into t3 values(2,2222);
+replace into t3 values(3,3333);
+select * from t3;
+c1 c2
+1 1111
+2 2222
+3 3333
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+true
+connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+read_free
+false
+select * from t3;
+c1 c2
+1 1111
+2 2222
+3 3333
+select * from t3 use index (slave_unique_key);
+c1 c2
+1 1111
+2 2222
+3 3333
+Case 6
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Query # # use `test`; create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # use `test`; create table t2(c1 int,c2 int, primary key (c1)) engine=rocksdb
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t2)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # use `test`; create table t3(c1 int,c2 int, primary key (c1)) engine=rocksdb
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t3)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t1)
+master-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t2)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t2)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t2)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t3)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t3)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+master-bin.000001 # Query # # BEGIN
+master-bin.000001 # Table_map # # table_id: # (test.t3)
+master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+master-bin.000001 # Xid # # COMMIT /* XID */
+include/show_binlog_events.inc
+Log_name Pos Event_type Server_id End_log_pos Info
+slave-bin.000001 # Query # # use `test`; create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # use `test`; create table t2(c1 int,c2 int, primary key (c1)) engine=rocksdb
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t2)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # use `test`; create table t3(c1 int,c2 int, primary key (c1)) engine=rocksdb
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t3)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` trigger trg before insert on t2 for each row set @a:=1
+slave-bin.000001 # Query # # use `test`; alter table t3 add constraint slave_unique_key unique (c2)
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t1)
+slave-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t2)
+slave-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t2)
+slave-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t2)
+slave-bin.000001 # Update_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t3)
+slave-bin.000001 # Delete_rows # # table_id: #
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t3)
+slave-bin.000001 # Delete_rows # # table_id: #
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+slave-bin.000001 # Query # # BEGIN
+slave-bin.000001 # Table_map # # table_id: # (test.t3)
+slave-bin.000001 # Delete_rows # # table_id: #
+slave-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
+slave-bin.000001 # Xid # # COMMIT /* XID */
+drop table t1;
+drop table t2;
+drop table t3;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
+SET GLOBAL enable_blind_replace=DEFAULT;
+SET GLOBAL enable_blind_replace=DEFAULT;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rocksdb_slave_check_before_image_consistency.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rocksdb_slave_check_before_image_consistency.result
new file mode 100644
index 00000000..a7708222
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rocksdb_slave_check_before_image_consistency.result
@@ -0,0 +1,165 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Error_code: 1032");
+create table t1 (a int primary key, b int, c int) engine = rocksdb;
+create table t2 (a int unique, b int, c int) engine = rocksdb;
+create table t3 (a int, b int, c int, key(a)) engine = rocksdb;
+create table t4 (a int, b int, c int) engine = rocksdb;
+insert into t1 values(1, 1, 1);
+insert into t2 values(1, 1, 1);
+insert into t3 values(1, 1, 1);
+insert into t4 values(1, 1, 1);
+include/sync_slave_sql_with_master.inc
+set @@sql_log_bin = 0;
+update t1 set c = 2;
+update t2 set c = 2;
+update t3 set c = 2;
+update t4 set c = 2;
+set @@sql_log_bin = 1;
+update t1 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t1 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t2 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t2 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t3 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t3 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t4 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t4 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+select * from t1;
+a b c
+1 2 1
+select * from t2;
+a b c
+1 2 1
+select * from t3;
+a b c
+1 2 1
+select * from t4;
+a b c
+1 2 1
+select * from t1;
+a b c
+1 2 1
+select * from t2;
+a b c
+1 2 1
+select * from t3;
+a b c
+1 2 1
+select * from t4;
+a b c
+1 2 1
+drop table t1;
+drop table t2;
+drop table t3;
+drop table t4;
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+set @@global.slave_rows_search_algorithms = 'INDEX_SCAN,TABLE_SCAN,HASH_SCAN';
+include/start_slave.inc
+create table t1 (a int primary key, b int, c int) engine = rocksdb;
+create table t2 (a int unique, b int, c int) engine = rocksdb;
+create table t3 (a int, b int, c int, key(a)) engine = rocksdb;
+create table t4 (a int, b int, c int) engine = rocksdb;
+insert into t1 values(1, 1, 1);
+insert into t2 values(1, 1, 1);
+insert into t3 values(1, 1, 1);
+insert into t4 values(1, 1, 1);
+include/sync_slave_sql_with_master.inc
+set @@sql_log_bin = 0;
+update t1 set c = 2;
+update t2 set c = 2;
+update t3 set c = 2;
+update t4 set c = 2;
+set @@sql_log_bin = 1;
+update t1 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t1 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t2 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t2 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t3 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t3 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+update t4 set b = 2;
+include/wait_for_slave_sql_error.inc [errno=1032]
+set @@sql_log_bin = 0;
+update t4 set c = 1;
+set @@sql_log_bin = 1;
+include/stop_slave.inc
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+select * from t1;
+a b c
+1 2 1
+select * from t2;
+a b c
+1 2 1
+select * from t3;
+a b c
+1 2 1
+select * from t4;
+a b c
+1 2 1
+select * from t1;
+a b c
+1 2 1
+select * from t2;
+a b c
+1 2 1
+select * from t3;
+a b c
+1 2 1
+select * from t4;
+a b c
+1 2 1
+drop table t1;
+drop table t2;
+drop table t3;
+drop table t4;
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+set @@global.slave_rows_search_algorithms = DEFAULT;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_binlog_xid_count.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_binlog_xid_count.result
new file mode 100644
index 00000000..9b46a5b5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_binlog_xid_count.result
@@ -0,0 +1,204 @@
+CREATE TABLE `t` (
+`a` text DEFAULT NULL
+) ENGINE=ROCKSDB;
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+INSERT INTO t SET a=repeat('a', 4096);
+INSERT INTO t SET a=repeat('a', 4096/2);
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_crash_safe_wal_corrupt.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_crash_safe_wal_corrupt.result
new file mode 100644
index 00000000..6d061e99
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_crash_safe_wal_corrupt.result
@@ -0,0 +1,135 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+drop table if exists x;
+select @@binlog_format;
+@@binlog_format
+ROW
+create table x (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into x values (1,1,1);
+insert into x values (2,1,1);
+insert into x values (3,1,1);
+insert into x values (4,1,1);
+insert into x values (5,1,1);
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+
+--- slave state before crash ---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+include/rpl_start_server.inc [server_number=2]
+
+--- slave state after crash recovery, slave stop, one transaction recovered---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+
+--- slave state after restart, slave start ---
+include/start_slave.inc
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+insert into x values (6,1,1);
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+insert into x values (7,1,1);
+insert into x values (8,1,1);
+insert into x values (9,1,1);
+insert into x values (10,1,1);
+insert into x values (11,1,1);
+insert into x values (12,1,1);
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+10 1 1
+11 1 1
+12 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+include/rpl_start_server.inc [server_number=2]
+
+--- slave state after crash recovery, slave stop, WAL was corrupted, point in time recovery with wal_recovery_mode=2 ---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+include/start_slave.inc
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+10 1 1
+11 1 1
+12 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+drop table x;
+include/rpl_end.inc
+Binlog Info Found
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_ddl_high_priority.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_ddl_high_priority.result
new file mode 100644
index 00000000..8a1fd1b9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_ddl_high_priority.result
@@ -0,0 +1,39 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+include/rpl_connect.inc [creating slave_block]
+drop table if exists t1;
+create table t1 (i int);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+insert into t1 values (1), (2), (3);
+connection slave
+select * from t1;
+i
+1
+2
+3
+connection slave_block
+lock tables t1 read;
+connection master;
+create high_priority index idx1 on t1 (i);
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL,
+ KEY `idx1` (`i`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+connection slave;
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) DEFAULT NULL,
+ KEY `idx1` (`i`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+drop table t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe.result
new file mode 100644
index 00000000..352ceff2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe.result
@@ -0,0 +1,361 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Recovery from master pos");
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr uuid:1
+2 test uuid:4
+SET GLOBAL debug = '+d,crash_before_update_pos';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr uuid:1
+2 test uuid:4
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr uuid:1
+2 test uuid:7
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_after_update_pos_before_apply';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_before_writing_xid';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,half_binlogged_transaction';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_before';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after_log';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after_prepare';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_optimized.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_optimized.result
new file mode 100644
index 00000000..a518de2b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_optimized.result
@@ -0,0 +1,361 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Recovery from master pos");
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr
+2 test uuid:4
+SET GLOBAL debug = '+d,crash_before_update_pos';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr
+2 test uuid:4
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 mtr
+2 test uuid:5
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_after_update_pos_before_apply';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_before_writing_xid';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,half_binlogged_transaction';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_before';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after_log';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after_prepare';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_reset.inc
+create table t1(a int, PRIMARY KEY(a)) ENGINE=ROCKSDB;
+insert into t1 values(1);
+insert into t1 values(2);
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+SET GLOBAL debug = '+d,crash_commit_after';
+insert into t1 values(3);
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:3
+use test;
+select * from t1;
+a
+1
+2
+change master to master_auto_position = 1;
+include/start_slave.inc
+rename table t1 to test1;
+use test;
+select * from test1;
+a
+1
+2
+3
+use test;
+select * from test1;
+a
+1
+2
+3
+drop table test1;
+include/stop_slave.inc
+change master to master_auto_position = 0;
+include/start_slave.inc
+use mysql;
+select * from slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:4
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_wal_corrupt.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_wal_corrupt.result
new file mode 100644
index 00000000..e765e338
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_crash_safe_wal_corrupt.result
@@ -0,0 +1,140 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+drop table if exists x;
+select @@binlog_format;
+@@binlog_format
+ROW
+create table x (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into x values (1,1,1);
+insert into x values (2,1,1);
+insert into x values (3,1,1);
+insert into x values (4,1,1);
+insert into x values (5,1,1);
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-7
+
+--- slave state before crash ---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-7
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:7
+include/rpl_start_server.inc [server_number=2]
+
+--- slave state after crash recovery, slave stop, one transaction recovered---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-6
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:6
+
+--- slave state after restart, slave start ---
+include/start_slave.inc
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-7
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:7
+insert into x values (6,1,1);
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-8
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:8
+insert into x values (7,1,1);
+insert into x values (8,1,1);
+insert into x values (9,1,1);
+insert into x values (10,1,1);
+insert into x values (11,1,1);
+insert into x values (12,1,1);
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+10 1 1
+11 1 1
+12 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-14
+include/rpl_start_server.inc [server_number=2]
+
+--- slave state after crash recovery, slave stop, WAL was corrupted, point in time recovery with wal_recovery_mode=2 ---
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+include/start_slave.inc
+select * from x;
+id value value2
+1 1 1
+2 1 1
+3 1 1
+4 1 1
+5 1 1
+6 1 1
+7 1 1
+8 1 1
+9 1 1
+10 1 1
+11 1 1
+12 1 1
+select @@global.gtid_executed;
+@@global.gtid_executed
+uuid:1-14
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test uuid:14
+drop table x;
+include/rpl_end.inc
+Binlog Info Found
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_rocksdb_sys_header.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_rocksdb_sys_header.result
new file mode 100644
index 00000000..b2703ee0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_gtid_rocksdb_sys_header.result
@@ -0,0 +1,16 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+create table t1 (a int primary key) engine=rocksdb;
+insert into t1 values(1);
+SET GLOBAL debug = '+d,crash_before_writing_xid';
+insert into t1 values(2);
+ERROR HY000: Lost connection to MySQL server during query
+include/rpl_reconnect.inc
+SET GLOBAL debug = ``;
+include/start_slave.inc
+RocksDB: Last MySQL Gtid master_uuid:2
+drop table t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_missing_columns_sk_update.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_missing_columns_sk_update.result
new file mode 100644
index 00000000..19b0a191
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_missing_columns_sk_update.result
@@ -0,0 +1,62 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+set @@sql_log_bin = 0;
+CREATE TABLE `t1` (
+`a` int(10) unsigned NOT NULL DEFAULT '0',
+`b` bigint(20) unsigned NOT NULL DEFAULT '0',
+`c` bigint(20) unsigned NOT NULL DEFAULT '0',
+`d` bigint(20) unsigned NOT NULL DEFAULT '0',
+`e` varbinary(64) DEFAULT NULL,
+`f` int(10) NOT NULL DEFAULT '0',
+`g` int(10) NOT NULL DEFAULT '0',
+`h` int(10) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`a`,`b`),
+KEY `key1` (`a`, `e`(1)),
+KEY `key2` (`a`,`h`)
+) ENGINE=RocksDB;
+set @@sql_log_bin = 1;
+set @@sql_log_bin = 0;
+CREATE TABLE `t1` (
+`a` int(10) unsigned NOT NULL DEFAULT '0',
+`b` bigint(20) unsigned NOT NULL DEFAULT '0',
+`c` bigint(20) unsigned NOT NULL DEFAULT '0',
+`d` bigint(20) unsigned NOT NULL DEFAULT '0',
+`e` varbinary(64) DEFAULT NULL,
+`f` int(10) NOT NULL DEFAULT '0',
+`g` int(10) NOT NULL DEFAULT '0',
+`x` TINYINT(3) UNSIGNED DEFAULT NULL,
+`y` INT(10) DEFAULT NULL,
+`h` int(10) unsigned NOT NULL DEFAULT '0',
+PRIMARY KEY (`a`,`b`),
+KEY `key1` (`a`, `e`(1)),
+KEY `key2` (`a`,`h`)
+) ENGINE=RocksDB;
+set @@sql_log_bin = 1;
+INSERT INTO t1 VALUES (1, 1, 1, 1, 'a', 1, 1, 1);
+SELECT * FROM t1;
+a b c d e f g h
+1 1 1 1 a 1 1 1
+SELECT * FROM t1;
+a b c d e f g x y h
+1 1 1 1 a 1 1 NULL NULL 1
+UPDATE t1 SET h = 10 WHERE h = 1;
+SELECT * FROM t1;
+a b c d e f g h
+1 1 1 1 a 1 1 10
+SELECT * FROM t1;
+a b c d e f g x y h
+1 1 1 1 a 1 1 NULL NULL 10
+SELECT COUNT(*) FROM t1 FORCE INDEX (key1) WHERE h = 10 AND a = 1;
+COUNT(*)
+1
+SELECT COUNT(*) FROM t1 FORCE INDEX (key2) WHERE h = 10 AND a = 1;
+COUNT(*)
+1
+SELECT COUNT(*) FROM t1 FORCE INDEX (PRIMARY) WHERE h = 10 AND a = 1;
+COUNT(*)
+1
+DROP TABLE t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_mts_dependency_unique_key_conflicts.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_mts_dependency_unique_key_conflicts.result
new file mode 100644
index 00000000..0eadc7de
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_mts_dependency_unique_key_conflicts.result
@@ -0,0 +1,44 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+include/stop_slave.inc
+set @save.slave_parallel_workers= @@global.slave_parallel_workers;
+set @save.slave_use_idempotent_for_recovery= @@global.slave_use_idempotent_for_recovery;
+set @save.mts_dependency_replication= @@global.mts_dependency_replication;
+set @save.mts_dependency_order_commits= @@global.mts_dependency_order_commits;
+set @save.debug= @@global.debug;
+set @@global.slave_parallel_workers= 2;
+set @@global.slave_use_idempotent_for_recovery= YES;
+set @@global.mts_dependency_replication= STMT;
+set @@global.mts_dependency_order_commits= false;
+set @@global.debug= '+d,dbug.dep_wait_before_update_execution';
+include/start_slave.inc
+create table t1 (a int primary key, b int unique key) engine = rocksdb;
+insert into t1 values(1, 1);
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+update t1 set b = 2 where a = 1;
+insert into t1 values(2, 1);
+include/start_slave.inc
+set debug_sync="now wait_for signal.reached";
+select * from t1;
+a b
+1 1
+set debug_sync="now signal signal.done";
+include/sync_slave_sql_with_master.inc
+select * from t1;
+a b
+2 1
+1 2
+drop table t1;
+include/sync_slave_sql_with_master.inc
+include/stop_slave.inc
+set @@global.slave_parallel_workers= @save.slave_parallel_workers;
+set @@global.mts_dependency_replication= @save.mts_dependency_replication;
+set @@global.slave_use_idempotent_for_recovery= @save.slave_use_idempotent_for_recovery;
+set @@global.mts_dependency_order_commits= @save.mts_dependency_order_commits;
+set @@global.debug= @save.debug;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag.result
new file mode 100644
index 00000000..905b56da
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag.result
@@ -0,0 +1,34 @@
+#
+# Ensure skip_unique_check is set when lag exceeds lag_threshold
+#
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Slave SQL: Could not execute Write_rows event on table test.t1");
+call mtr.add_suppression(".*Worker.*failed executing transaction");
+call mtr.add_suppression(".*The slave coordinator and worker threads are stopped");
+drop table if exists t1;
+CREATE TABLE t1 (id int primary key, value int) engine=RocksDB;
+set global reset_seconds_behind_master=1;
+INSERT INTO t1 VALUES(1, 0);
+INSERT INTO t1 VALUES(2, 0);
+INSERT INTO t1 VALUES(3, 0);
+INSERT INTO t1 VALUES(1, 1);
+include/wait_for_slave_sql_error_and_skip.inc [errno=1062]
+set global reset_seconds_behind_master=0;
+include/stop_slave_io.inc
+INSERT INTO t1 values (4,0);
+INSERT INTO t1 VALUES(2, 1);
+include/start_slave_io.inc
+set global reset_seconds_behind_master=1;
+insert into t1 values (5,0);
+INSERT INTO t1 VALUES(3, 1);
+include/wait_for_slave_sql_error_and_skip.inc [errno=1062]
+#
+# Cleanup
+#
+DROP TABLE t1;
+set global reset_seconds_behind_master=1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag_mts.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag_mts.result
new file mode 100644
index 00000000..6c58cb16
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_no_unique_check_on_lag_mts.result
@@ -0,0 +1,31 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Slave SQL: Could not execute Write_rows event on table test.t1");
+call mtr.add_suppression(".*Worker.*failed executing transaction");
+call mtr.add_suppression(".*The slave coordinator and worker threads are stopped");
+drop table if exists t1;
+CREATE TABLE t1 (id int primary key, value int) engine=RocksDB;
+set global reset_seconds_behind_master=1;
+INSERT INTO t1 VALUES(1, 0);
+INSERT INTO t1 VALUES(2, 0);
+INSERT INTO t1 VALUES(3, 0);
+INSERT INTO t1 VALUES(1, 1);
+include/wait_for_slave_sql_error_and_skip.inc [errno=1062]
+set global reset_seconds_behind_master=0;
+include/stop_slave_io.inc
+INSERT INTO t1 values (4,0);
+INSERT INTO t1 VALUES(2, 1);
+include/start_slave_io.inc
+set global reset_seconds_behind_master=1;
+insert into t1 values (5,0);
+INSERT INTO t1 VALUES(3, 1);
+include/wait_for_slave_sql_error_and_skip.inc [errno=1062]
+#
+# Cleanup
+#
+DROP TABLE t1;
+set global reset_seconds_behind_master=1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_2pc_crash_recover.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_2pc_crash_recover.result
new file mode 100644
index 00000000..cf3fe03a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_2pc_crash_recover.result
@@ -0,0 +1,44 @@
+DROP TABLE IF EXISTS t1;
+create table t1 (a int primary key, msg varchar(255)) engine=rocksdb;
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+SET SESSION debug_dbug="d,crash_commit_after_prepare";
+insert into t1 values (1, 'dogz');
+select * from t1;
+a msg
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+SET SESSION debug_dbug="d,crash_commit_after_log";
+insert into t1 values (2, 'catz'), (3, 'men');
+select * from t1;
+a msg
+2 catz
+3 men
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+SET SESSION debug_dbug="d,crash_commit_after";
+insert into t1 values (4, 'cars'), (5, 'foo');
+select * from t1;
+a msg
+2 catz
+3 men
+4 cars
+5 foo
+SET GLOBAL ROCKSDB_ENABLE_2PC = OFF;
+SET SESSION debug_dbug="d,crash_commit_after_log";
+insert into t1 values (6, 'shipz'), (7, 'tankz');
+select * from t1;
+a msg
+2 catz
+3 men
+4 cars
+5 foo
+SET GLOBAL ROCKSDB_ENABLE_2PC = OFF;
+SET SESSION debug_dbug="d,crash_commit_after";
+insert into t1 values (8, 'space'), (9, 'time');
+select * from t1;
+a msg
+2 catz
+3 men
+4 cars
+5 foo
+8 space
+9 time
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_slave_gtid_info_optimized.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_slave_gtid_info_optimized.result
new file mode 100644
index 00000000..1f6acf32
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_slave_gtid_info_optimized.result
@@ -0,0 +1,43 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+Make changes in master
+create table test1 (a int primary key, b int) engine=rocksdb;
+insert into test1 values (1, 1);
+Make sure slave is up-to-date and mysql.slave_gtid_info is good
+select * from test1;
+a b
+1 1
+select id, database_name, last_gtid from mysql.slave_gtid_info;
+id database_name last_gtid
+1 test UUID:2
+Make changes in master
+insert into test1 values (2, 2);
+Make sure slave is up-to-date and mysql.slave_gtid_info is good
+select @@slave_gtid_info;
+@@slave_gtid_info
+OPTIMIZED
+select * from test1;
+a b
+1 1
+2 2
+select * from mysql.slave_gtid_info;
+Id Database_name Last_gtid
+1 test UUID:3
+Make changes in master
+insert into test1 values (3, 3);
+insert into test1 values (4, 4);
+Make sure slave is up-to-date and mysql.slave_gtid_info is good
+select * from test1;
+a b
+1 1
+2 2
+3 3
+4 4
+select id, database_name, last_gtid from mysql.slave_gtid_info;
+id database_name last_gtid
+1 test UUID:5
+DROP TABLE IF EXISTS test1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot.result
new file mode 100644
index 00000000..f5e861fe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot.result
@@ -0,0 +1,222 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+DROP TABLE IF EXISTS t1;
+# Establish connection con1 (user=root)
+# Establish connection con2 (user=root)
+# Establish connection con3 (user=root)
+# Establish connection con4 (user=root)
+# reset replication to guarantee that master-bin.000001 is used
+include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+RESET MASTER;
+CHANGE MASTER TO master_host="127.0.0.1",master_port=MASTER_PORT,master_user="root";
+Warnings:
+Note 1759 Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note 1760 Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+include/start_slave.inc
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+ERROR HY000: Only REPEATABLE READ isolation level is supported for START TRANSACTION WITH CONSISTENT|SHARED|EXISTING SNAPSHOT in RocksDB Storage Engine.
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+ERROR HY000: Only REPEATABLE READ isolation level is supported for START TRANSACTION WITH CONSISTENT|SHARED|EXISTING SNAPSHOT in RocksDB Storage Engine.
+ROLLBACK;
+SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 531 UUID:1-2
+# Switch to connection con2
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+# Switch to connection con1
+SELECT * FROM t1;
+a
+1
+COMMIT;
+SELECT * FROM t1;
+a
+1
+2
+3
+DROP TABLE t1;
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 1510 UUID:1-7
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 1510 UUID:1-7
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 1510 UUID:1-7
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 1510 UUID:1-7
+# Switch to connection con2
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+# Switch to connection con1
+SELECT * FROM t1;
+a
+1
+SELECT * INTO OUTFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' FROM t1;
+COMMIT;
+# Switch to slave
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+DELETE FROM t1;
+LOAD DATA INFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' INTO TABLE t1;
+SELECT * FROM t1;
+a
+1
+CHANGE MASTER TO master_host="127.0.0.1",master_port=MASTER_PORT,master_user="root",master_log_file="master-bin.000001",master_log_pos=binlog_pos;
+Warnings:
+Note 1759 Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note 1760 Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+include/start_slave.inc
+SELECT * FROM t1;
+a
+1
+2
+3
+SELECT * FROM t1_backup;
+a
+1
+2
+3
+DROP TABLE t1_backup;
+DROP TABLE t1;
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+# async queries from con2
+INSERT INTO t1 VALUES(2);
+# async queries from con3
+INSERT INTO t1 VALUES(21);
+# Switch to connection con1
+# Switch to connection con4
+INSERT INTO t1 VALUES(9);
+# Switch to connection con1
+SELECT * INTO OUTFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' FROM t1;
+COMMIT;
+# reap async statements
+# Switch to slave
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+DELETE FROM t1;
+LOAD DATA INFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' INTO TABLE t1;
+CHANGE MASTER TO master_host="127.0.0.1",master_port=MASTER_PORT,master_user="root",master_log_file="master-bin.000001",master_log_pos=binlog_pos;
+Warnings:
+Note 1759 Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note 1760 Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+include/start_slave.inc
+# sync and then query slave
+ShouldBeZero
+0
+DROP TABLE t1_backup;
+DROP TABLE t1;
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 3688 UUID:1-18
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 3688 UUID:1-18
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 3688 UUID:1-18
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000001 3688 UUID:1-18
+# Switch to connection con2
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+# Switch to connection con1
+SELECT * FROM t1;
+a
+1
+SELECT * INTO OUTFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' FROM t1;
+COMMIT;
+# Switch to slave
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+SET @@global.gtid_purged='gtid_executed_from_snapshot';
+DELETE FROM t1;
+LOAD DATA INFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' INTO TABLE t1;
+SELECT * FROM t1;
+a
+1
+CHANGE MASTER TO master_host="127.0.0.1",master_port=MASTER_PORT,master_user="root", master_auto_position=1;
+Warnings:
+Note 1759 Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note 1760 Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+include/start_slave.inc
+SELECT * FROM t1;
+a
+1
+2
+3
+SELECT * FROM t1_backup;
+a
+1
+2
+3
+DROP TABLE t1_backup;
+DROP TABLE t1;
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+# async queries from con2
+INSERT INTO t1 VALUES(2);
+# async queries from con3
+INSERT INTO t1 VALUES(21);
+# Switch to connection con1
+# Switch to connection con4
+INSERT INTO t1 VALUES(9);
+# Switch to connection con1
+SELECT * INTO OUTFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' FROM t1;
+COMMIT;
+# reap async statements
+# Switch to slave
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+SET @@global.gtid_purged='gtid_executed_from_snapshot';
+DELETE FROM t1;
+LOAD DATA INFILE '<MYSQLTEST_VARDIR>/tmp/rpl_rocksdb_snapshot.out.file' INTO TABLE t1;
+CHANGE MASTER TO master_host="127.0.0.1",master_port=MASTER_PORT,master_user="root", master_auto_position=1;
+Warnings:
+Note 1759 Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note 1760 Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+include/start_slave.inc
+# sync and then query slave
+ShouldBeZero
+0
+DROP TABLE t1_backup;
+DROP TABLE t1;
+# Switch to connection default + close connections con1 and con2
+include/stop_slave.inc
+CHANGE MASTER to master_auto_position=0;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot_without_gtid.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot_without_gtid.result
new file mode 100644
index 00000000..57c1d082
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_snapshot_without_gtid.result
@@ -0,0 +1,15 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+create table t1(a int primary key);
+FLUSH LOGS;
+insert into t1 values(1);
+insert into t1 values(2);
+FLUSH LOGS;
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+File Position Gtid_executed
+master-bin.000003 120
+drop table t1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_stress_crash.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_stress_crash.result
new file mode 100644
index 00000000..d4920b14
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_rocksdb_stress_crash.result
@@ -0,0 +1,28 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression(".*");
+include/stop_slave.inc
+change master to master_auto_position=1;
+include/start_slave.inc
+call mtr.add_suppression('Slave: Error dropping database');
+stop slave sql_thread;
+insert into test0.benchmark set state='slave is processing load';
+start slave sql_thread;
+use test0;
+insert into benchmark set state='slave ends load';
+use test;
+select * from test1.benchmark into outfile 'benchmark.out';
+select ts from test0.benchmark where state like 'master started load' into @m_0;
+select ts from test0.benchmark where state like 'master ends load' into @m_1;
+select ts from test0.benchmark where state like 'slave takes on load' into @s_m0;
+select ts from test0.benchmark where state like 'slave is supposed to finish with load' into @s_m1;
+select ts from test0.benchmark where state like 'slave ends load' into @s_1;
+select ts from test0.benchmark where state like 'slave is processing load' into @s_0;
+select time_to_sec(@m_1) - time_to_sec(@m_0) as 'delta.out';
+include/stop_slave.inc
+change master to master_auto_position=0;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_skip_trx_api_binlog_format.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_skip_trx_api_binlog_format.result
new file mode 100644
index 00000000..5559bf61
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_skip_trx_api_binlog_format.result
@@ -0,0 +1,27 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Master's binlog format is not ROW but rpl_skip_tx_api is enabled on the slave");
+set global rpl_skip_tx_api=ON;
+set global rocksdb_unsafe_for_binlog=1;
+create table t1(a int);
+set session binlog_format=STATEMENT;
+insert into t1 values(1);
+include/wait_for_slave_sql_error.inc [errno=1756]
+Last_SQL_Error = 'Master's binlog format is not ROW but rpl_skip_tx_api is enabled on the slave, this should only be used when master's binlog format is ROW.'
+"Table after error"
+select * from t1;
+a
+set global rpl_skip_tx_api=OFF;
+include/start_slave.inc
+include/sync_slave_sql_with_master.inc
+"Table after error fixed"
+select * from t1;
+a
+1
+drop table t1;
+set global rocksdb_unsafe_for_binlog=0;
+set global rpl_skip_tx_api=0;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_xa.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_xa.result
new file mode 100644
index 00000000..86f73f2f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/rpl_xa.result
@@ -0,0 +1,61 @@
+include/master-slave.inc
+[connection master]
+connection master;
+create table ti (a int, b int) engine=innodb;
+create table t1 (a int, b int) engine=rocksdb;
+insert into ti values(0, 0);
+insert into t1 values(0, 0);
+xa start 't';
+insert into ti values(1, 2);
+insert into t1 values(1, 2);
+xa end 't';
+xa prepare 't';
+xa commit 't';
+connection slave;
+include/diff_tables.inc [master:t1, slave:t1]
+connection master;
+xa start 't';
+insert into ti values(3, 4);
+insert into t1 values(3, 4);
+xa end 't';
+xa prepare 't';
+xa rollback 't';
+connection slave;
+include/diff_tables.inc [master:t1, slave:t1]
+connection master;
+SET pseudo_slave_mode=1;
+create table t2 (a int) engine=rocksdb;
+xa start 't';
+insert into ti values (5, 6);
+insert into t1 values (5, 6);
+xa end 't';
+xa prepare 't';
+xa start 's';
+insert into ti values (7, 8);
+insert into t2 values (0);
+xa end 's';
+xa prepare 's';
+include/save_master_gtid.inc
+connection slave;
+include/sync_with_master_gtid.inc
+xa recover;
+formatID gtrid_length bqual_length data
+1 1 0 t
+1 1 0 s
+connection master;
+xa commit 't';
+xa commit 's';
+SET pseudo_slave_mode=0;
+Warnings:
+Warning 1231 Slave applier execution mode not active, statement ineffective.
+xa start 'r';
+insert into t1 values(7, 8);
+xa end 'r';
+xa prepare 'r';
+xa commit 'r';
+connection slave;
+include/diff_tables.inc [master:t1, slave:t1]
+include/diff_tables.inc [master:t2, slave:t2]
+connection master;
+drop table ti, t1, t2;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_recovery.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_recovery.result
new file mode 100644
index 00000000..89e93f6b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_recovery.result
@@ -0,0 +1,25 @@
+include/master-slave.inc
+Warnings:
+Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
+Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
+[connection master]
+call mtr.add_suppression("Recovery from master pos");
+drop table if exists r1;
+create table r1 (id1 int, id2 int, primary key (id1, id2), index i (id2)) engine=rocksdb;
+insert into r1 values (1, 1000);
+set global rocksdb_force_flush_memtable_now=1;
+include/rpl_start_server.inc [server_number=2]
+include/start_slave.inc
+insert into r1 values (2,2000);
+delete r1 from r1 force index (i) where id2=1000;
+select id1,id2 from r1 force index (primary) where id1=1 and id2=1000;
+id1 id2
+select id2 from r1 force index (i) where id1=1 and id2=1000;
+id2
+set global rocksdb_compact_cf='default';
+select id1,id2 from r1 force index (primary) where id1=1 and id2=1000;
+id1 id2
+select id2 from r1 force index (i) where id1=1 and id2=1000;
+id2
+drop table r1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_table.result b/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_table.result
new file mode 100644
index 00000000..979e2cbf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/r/singledelete_idempotent_table.result
@@ -0,0 +1,29 @@
+include/master-slave.inc
+[connection master]
+connection master;
+drop table if exists r1;
+create table r1 (id1 int, id2 int, primary key (id1, id2), index i (id2)) engine=rocksdb;
+insert into r1 values (1, 1000);
+set sql_log_bin=0;
+delete from r1 where id1=1 and id2=1000;
+set sql_log_bin=1;
+connection slave;
+connection slave;
+set global rocksdb_force_flush_memtable_now=1;
+connection master;
+insert into r1 values (1, 1000);
+connection slave;
+connection slave;
+delete r1 from r1 force index (i) where id2=1000;
+select id1,id2 from r1 force index (primary);
+id1 id2
+select id2 from r1 force index (i);
+id2
+set global rocksdb_compact_cf='default';
+select id1,id2 from r1 force index (primary);
+id1 id2
+select id2 from r1 force index (i);
+id2
+connection master;
+drop table r1;
+include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/rpl_1slave_base.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/rpl_1slave_base.cnf
new file mode 100644
index 00000000..ed8c77bc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/rpl_1slave_base.cnf
@@ -0,0 +1,51 @@
+# Use default setting for mysqld processes
+!include include/default_mysqld.cnf
+!include include/default_client.cnf
+
+[mysqld.1]
+
+# Run the master.sh script before starting this process
+#!run-master-sh
+
+log-bin= master-bin
+
+loose-innodb
+
+[mysqld.2]
+# Run the slave.sh script before starting this process
+#!run-slave-sh
+
+# Append <testname>-slave.opt file to the list of argument used when
+# starting the mysqld
+#!use-slave-opt
+innodb_use_native_aio = 0
+
+log-bin= slave-bin
+relay-log= slave-relay-bin
+
+log-slave-updates
+master-retry-count= 10
+
+# Values reported by slave when it connect to master
+# and shows up in SHOW SLAVE STATUS;
+report-host= 127.0.0.1
+report-port= @mysqld.2.port
+report-user= root
+
+skip-slave-start
+
+# Directory where slaves find the dumps generated by "load data"
+# on the server. The path need to have constant length otherwise
+# test results will vary, thus a relative path is used.
+slave-load-tmpdir= ../../tmp
+
+loose-innodb
+
+
+[ENV]
+MASTER_MYPORT= @mysqld.1.port
+MASTER_MYSOCK= @mysqld.1.socket
+
+SLAVE_MYPORT= @mysqld.2.port
+SLAVE_MYSOCK= @mysqld.2.socket
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/suite.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/suite.opt
new file mode 100644
index 00000000..761a2108
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/suite.opt
@@ -0,0 +1 @@
+--ignore-db-dirs=.rocksdb --plugin-load=$HA_ROCKSDB_SO --default-storage-engine=rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/suite.pm b/storage/rocksdb/mysql-test/rocksdb_rpl/suite.pm
new file mode 100644
index 00000000..1cc123b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/suite.pm
@@ -0,0 +1,25 @@
+package My::Suite::Rocksdb;
+
+#
+# Note: ../rocksdb_sys_vars/suite.pm file has a similar
+# function. If you modify this file, consider modifying that one, too.
+#
+@ISA = qw(My::Suite);
+use My::Find;
+use File::Basename;
+use strict;
+
+#sub is_default { not $::opt_embedded_server }
+
+my $sst_dump=
+::mtr_exe_maybe_exists(
+ "$::bindir/storage/rocksdb$::opt_vs_config/sst_dump",
+ "$::path_client_bindir/sst_dump");
+return "RocksDB is not compiled, no sst_dump" unless $sst_dump;
+$ENV{MARIAROCKS_SST_DUMP}="$sst_dump";
+
+# Temporarily disable testing under valgrind, due to MDEV-12439
+return "RocksDB tests disabled under valgrind" if ($::opt_valgrind);
+
+bless { };
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines-master.opt
new file mode 100644
index 00000000..c747adc9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines.test
new file mode 100644
index 00000000..acea1903
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/consistent_snapshot_mixed_engines.test
@@ -0,0 +1,81 @@
+--source include/have_log_bin.inc
+--source include/have_rocksdb.inc
+--source include/have_innodb.inc
+--enable_connect_log
+-- let $uuid = `select @@server_uuid;`
+
+# Save the initial number of concurrent sessions
+--source include/count_sessions.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+
+connection con1;
+create table i1 (id int primary key , value int) engine=innodb;
+create table r1 (id int primary key , value int) engine=rocksdb;
+
+
+SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+
+# Without setting engine, this takes both InnoDB and RocksDB snapshots
+-- replace_result $uuid uuid
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+
+connection con2;
+insert into i1 values (1,1);
+insert into r1 values (1,1);
+
+connection con1;
+select * from i1;
+select * from r1;
+
+# This takes RocksDB snapshot only but both InnoDB participates in transaction.
+-- replace_result $uuid uuid
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+
+connection con2;
+insert into i1 values (2,2);
+insert into r1 values (2,2);
+
+connection con1;
+# takes InnoDB snapshot here so changes after that not visible
+select * from i1;
+select * from r1;
+
+connection con2;
+insert into i1 values (3,2);
+insert into r1 values (3,2);
+
+connection con1;
+select * from i1;
+select * from r1;
+
+# RocksDB also partipates in transaction
+-- replace_result $uuid uuid
+START TRANSACTION WITH CONSISTENT INNODB SNAPSHOT;
+
+connection con2;
+insert into r1 values (4,4);
+
+connection con1;
+# takes RocksDB snapshot here so changes after that are not visible
+select * from r1;
+
+connection con2;
+insert into r1 values (5,5);
+
+connection con1;
+select * from r1;
+
+drop table i1;
+drop table r1;
+
+connection default;
+disconnect con1;
+disconnect con2;
+reset master;
+--source include/wait_until_count_sessions.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/disabled.def b/storage/rocksdb/mysql-test/rocksdb_rpl/t/disabled.def
new file mode 100644
index 00000000..2147e3e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/disabled.def
@@ -0,0 +1,34 @@
+##
+## Tests that require FB/MySQL specific features for which there are
+## no plans to port them to MariaDB
+##
+rpl_no_unique_check_on_lag : unique_check_lag_threshold is not available in MariaDB
+rpl_no_unique_check_on_lag_mts : unique_check_lag_threshold is not available in MariaDB
+consistent_snapshot_mixed_engines : Tests START TRANSACTION WITH CONSISTENT $ENGINE_NAME SNAPSHOT
+rpl_skip_trx_api_binlog_format : requires @@rpl_skip_tx_api
+rpl_ddl_high_priority : DDL commands with HIGH_PRIORITY syntax are not in MariaDB
+rpl_gtid_rocksdb_sys_header : MariaDB doesn't support printing "RocksDB: Last MySQL Gtid UUID" into server stderr on startup
+singledelete_idempotent_recovery: MariaDB doesn't support --slave-use-idempotent-for-recovery
+rpl_mts_dependency_unique_key_conflicts: MariaDB doesn't support --slave-use-idempotent-for-recovery
+rpl_missing_columns_sk_update : Uses log_column_names=ON feature which is only present in FB/MySQL
+optimize_myrocks_replace_into: requires @@enable_blind_replace support.
+rpl_gtid_crash_safe_optimized: requires slave_gtid_info=optimized
+
+##
+## Tests that do not fit MariaDB's test environment (Functional tests only,
+## can't have stress tests)
+##
+rpl_rocksdb_stress_crash : Stress test
+
+##
+## Tests that are disabled for other reasons
+##
+
+multiclient_2pc : Didn't try with MariaDB, yet
+rpl_crash_safe_wal_corrupt : Didn't try with MariaDB, yet
+rpl_gtid_crash_safe : Didn't try with MariaDB, yet
+rpl_gtid_crash_safe_wal_corrupt : Didn't try with MariaDB, yet
+rpl_rocksdb_snapshot : Didn't try with MariaDB, yet
+rpl_rocksdb_snapshot_without_gtid : Didn't try with MariaDB, yet
+rpl_rocksdb_slave_gtid_info_optimized: requires slave-gtid-info=optimized which is an FB/MySQL-only feature
+rocksdb_slave_check_before_image_consistency: requires slave_check_before_image_consistency feature
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/mdev12179.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/mdev12179.test
new file mode 100644
index 00000000..845f1558
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/mdev12179.test
@@ -0,0 +1,317 @@
+--source include/have_rocksdb.inc
+--source include/have_innodb.inc
+--source include/master-slave.inc
+
+--connection server_2
+--source include/stop_slave.inc
+
+# Set GTID cleanup limit high enough that cleanup will not run and we
+# can rely on consistent table output in .result.
+--let $old_gtid_cleanup_batch_size=`SELECT @@GLOBAL.gtid_cleanup_batch_size`
+SET GLOBAL gtid_cleanup_batch_size = 999999999;
+
+CHANGE MASTER TO master_use_gtid=slave_pos;
+SET sql_log_bin=0;
+CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB;
+CREATE TABLE mysql.gtid_slave_pos_rocksdb LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_rocksdb ENGINE=rocksdb;
+CREATE TABLE mysql.gtid_slave_pos_myisam_redundant LIKE mysql.gtid_slave_pos;
+CREATE TABLE mysql.gtid_slave_pos_innodb_redundant LIKE mysql.gtid_slave_pos;
+ALTER TABLE mysql.gtid_slave_pos_innodb_redundant ENGINE=InnoDB;
+call mtr.add_suppression("Ignoring redundant table.*since.*has the same storage engine");
+--source include/start_slave.inc
+
+--connection server_1
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=MyISAM;
+CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB;
+CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (1);
+INSERT INTO t3 VALUES (1);
+SELECT * FROM t1 ORDER BY a;
+SELECT * FROM t2 ORDER BY a;
+SELECT * FROM t3 ORDER BY a;
+--save_master_pos
+
+--connection server_2
+--sync_with_master
+SELECT * FROM t1 ORDER BY a;
+SELECT * FROM t2 ORDER BY a;
+SELECT * FROM t3 ORDER BY a;
+SELECT * FROM mysql.gtid_slave_pos ORDER BY sub_id;
+SELECT * FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb
+ UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select
+ ORDER BY sub_id;
+SELECT * FROM mysql.gtid_slave_pos_rocksdb ORDER BY sub_id;
+
+
+# Test status variable Transactions_multi_engine.
+--connection server_2
+FLUSH NO_WRITE_TO_BINLOG STATUS;
+SET sql_log_bin=0;
+SHOW STATUS LIKE "Transactions_multi_engine";
+INSERT INTO t1 VALUES (100);
+SHOW STATUS LIKE "Transactions_multi_engine";
+INSERT INTO t2 VALUES (101);
+SHOW STATUS LIKE "Transactions_multi_engine";
+INSERT INTO t3 VALUES (101);
+SHOW STATUS LIKE "Transactions_multi_engine";
+BEGIN;
+INSERT INTO t3 VALUES (102);
+INSERT INTO t2 VALUES (103);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+BEGIN;
+INSERT INTO t2 VALUES (104);
+INSERT INTO t3 VALUES (105);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105;
+SHOW STATUS LIKE "Transactions_multi_engine";
+# Try again with binlog enabled.
+SET sql_log_bin=1;
+INSERT INTO t1 VALUES (200);
+SHOW STATUS LIKE "Transactions_multi_engine";
+INSERT INTO t2 VALUES (201);
+SHOW STATUS LIKE "Transactions_multi_engine";
+INSERT INTO t3 VALUES (201);
+SHOW STATUS LIKE "Transactions_multi_engine";
+BEGIN;
+INSERT INTO t3 VALUES (202);
+INSERT INTO t2 VALUES (203);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+BEGIN;
+INSERT INTO t2 VALUES (204);
+INSERT INTO t3 VALUES (205);
+COMMIT;
+SHOW STATUS LIKE "Transactions_multi_engine";
+UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205;
+SHOW STATUS LIKE "Transactions_multi_engine";
+
+DELETE FROM t1 WHERE a >= 100;
+DELETE FROM t2 WHERE a >= 100;
+DELETE FROM t3 WHERE a >= 100;
+
+
+# Create a bunch more GTIDs in mysql.gtid_slave_pos* tables to test with.
+--connection server_1
+--disable_query_log
+let $i=10;
+while ($i) {
+ eval INSERT INTO t1 VALUES (300+$i);
+ eval INSERT INTO t2 VALUES (300+$i);
+ eval INSERT INTO t3 VALUES (300+$i);
+ dec $i;
+}
+--enable_query_log
+--source include/save_master_gtid.inc
+
+--connection server_2
+--source include/sync_with_master_gtid.inc
+
+# Check that we have many rows in mysql.gtid_slave_pos now (since
+# @@gtid_cleanup_batch_size was set to a huge value). No need to check
+# for an exact number, since that will require changing .result if
+# anything changes prior to this point, and we just need to know that
+# we have still have some data in the tables to make the following
+# test effective.
+SELECT COUNT(*)>=10 FROM mysql.gtid_slave_pos;
+SELECT COUNT(*)>=10 FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb
+ UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select;
+SELECT COUNT(*)>=10 FROM mysql.gtid_slave_pos_rocksdb;
+
+# Check that old GTID rows will be deleted when batch delete size is
+# set reasonably. Old row deletion is not 100% deterministic (by design), so
+# we must wait for it to occur, but it should occur eventually.
+SET GLOBAL gtid_cleanup_batch_size = 3;
+let $i=40;
+--disable_query_log
+--let $keep_include_silent=1
+while ($i) {
+ let N=`SELECT 1+($i MOD 3)`;
+ --connection server_1
+ eval UPDATE t$N SET a=a+1 WHERE a=(SELECT MAX(a) FROM t$N);
+ --source include/save_master_gtid.inc
+ --connection server_2
+ --source include/sync_with_master_gtid.inc
+ let $j=50;
+ while ($j) {
+ let $is_done=`SELECT SUM(a)=1 FROM (
+ SELECT COUNT(*) AS a FROM mysql.gtid_slave_pos
+ UNION ALL
+ SELECT COUNT(*) AS a FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb
+ UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select
+ UNION ALL
+ SELECT COUNT(*) AS a FROM mysql.gtid_slave_pos_rocksdb) outer_select`;
+ if ($is_done) {
+ let $j=0;
+ }
+ if (!$is_done) {
+ real_sleep 0.1;
+ dec $j;
+ }
+ }
+ dec $i;
+ if ($is_done) {
+ let $i=0;
+ }
+}
+--enable_query_log
+--let $keep_include_silent=0
+if (!$is_done) {
+ --echo Timed out waiting for mysql.gtid_slave_pos* tables to be cleaned up
+}
+
+--disable_query_log
+DELETE FROM t1 WHERE a >= 100;
+DELETE FROM t2 WHERE a >= 100;
+DELETE FROM t3 WHERE a >= 100;
+--enable_query_log
+
+
+# Test status variables Rpl_transactions_multi_engine and Transactions_gtid_foreign_engine.
+# Have mysql.gtid_slave_pos* for myisam and innodb but not rocksdb.
+--connection server_2
+--source include/stop_slave.inc
+SET sql_log_bin=0;
+DROP TABLE mysql.gtid_slave_pos_rocksdb;
+DROP TABLE mysql.gtid_slave_pos_myisam_redundant;
+DROP TABLE mysql.gtid_slave_pos_innodb_redundant;
+SET sql_log_bin=1;
+FLUSH NO_WRITE_TO_BINLOG STATUS;
+--source include/start_slave.inc
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t1 VALUES (100);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t2 VALUES (101);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t3 VALUES (101);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+BEGIN;
+INSERT INTO t3 VALUES (102);
+INSERT INTO t2 VALUES (103);
+COMMIT;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+BEGIN;
+INSERT INTO t2 VALUES (104);
+INSERT INTO t3 VALUES (105);
+COMMIT;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+# Now the same thing, but without binlogging on the slave.
+--connection server_2
+--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+wait
+EOF
+--shutdown_server
+--source include/wait_until_disconnected.inc
+
+# Restart without binary log.
+--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+restart: --skip-log-bin
+EOF
+
+--connection server_2
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+SHOW VARIABLES LIKE 'log_bin';
+--source include/start_slave.inc
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t1 VALUES (200);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t2 VALUES (201);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+INSERT INTO t3 VALUES (201);
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+BEGIN;
+INSERT INTO t3 VALUES (202);
+INSERT INTO t2 VALUES (203);
+COMMIT;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+BEGIN;
+INSERT INTO t2 VALUES (204);
+INSERT INTO t3 VALUES (205);
+COMMIT;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+--connection server_1
+UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205;
+--save_master_pos
+--connection server_2
+--sync_with_master
+SHOW STATUS LIKE "%transactions%engine";
+
+
+--connection server_2
+SET sql_log_bin=0;
+DROP TABLE mysql.gtid_slave_pos_innodb;
+SET sql_log_bin=1;
+--disable_query_log
+eval SET GLOBAL gtid_cleanup_batch_size = $old_gtid_cleanup_batch_size;
+--enable_query_log
+
+--connection server_1
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc-master.opt
new file mode 100644
index 00000000..c747adc9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc.test
new file mode 100644
index 00000000..ff484171
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/multiclient_2pc.test
@@ -0,0 +1,77 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+--source include/big_test.inc
+# The test involves a crash which does not seem to be handled well with
+# mysql-test/lib/My/SafeProcess/my_safe_process under valgrind as it hangs
+# forever. The test did not mean to verify the memory leaks so not much
+# coverage should be missed by not running it under valgrind.
+--source include/not_valgrind.inc
+
+--exec echo > $MYSQLTEST_VARDIR/log/mysqld.1.err
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# Set it to the minimum so that we can make the binlog rotate with a few inserts
+SET GLOBAL MAX_BINLOG_SIZE = 4096;
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+create table t1 (a int primary key, b int, c varchar(255)) engine=rocksdb;
+
+connect (con1, localhost, root,,);
+connect (con2, localhost, root,,);
+
+# On connection one we insert a row and pause after prepare marker is written to
+# WAL. Connection two then inserts many rows to rotate the binlog. After
+# connection two completes, connection one continues only to crash before commit
+# but after binlog write. On crash recovery we see that connection one's value
+# has been recovered and commited
+connection con1;
+--echo 'con1'
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug="d,crash_commit_after_log";
+SET DEBUG_SYNC='rocksdb.prepared SIGNAL parked WAIT_FOR go';
+--error 0,2013
+--send insert into t1 values (1, 1, "iamtheogthealphaandomega");
+
+connection con2;
+--echo 'con2'
+insert into t1 values (2, 1, "i_am_just_here_to_trigger_a_flush");
+
+# Disable 2PC and syncing for faster inserting of dummy rows
+# These rows only purpose is to rotate the binlog
+SET GLOBAL ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 0;
+SET GLOBAL SYNC_BINLOG = 0;
+
+SET DEBUG_SYNC='now WAIT_FOR parked';
+--disable_query_log
+--let $pk= 3
+# binlog size is 4096 bytes so with that many insertion it will definitely rotate
+while ($pk < 4096) {
+ eval insert into t1 values ($pk, 1, "foobardatagoesheresothatmorelogsrollwhichiswhatwewant");
+ --inc $pk
+}
+--enable_query_log
+
+# re-enable 2PC an syncing then write to trigger a flush
+# before we trigger the crash to simulate full-durability
+SET GLOBAL ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 2;
+SET GLOBAL SYNC_BINLOG = 1;
+
+insert into t1 values (1000000, 1, "i_am_just_here_to_trigger_a_flush");
+
+--error 0,2013
+SET DEBUG_SYNC='now SIGNAL go';
+--source include/wait_until_disconnected.inc
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+--exec python suite/rocksdb/t/check_log_for_xa.py $MYSQLTEST_VARDIR/log/mysqld.1.err commit,prepare,rollback
+
+select * from t1 where a=1;
+select count(*) from t1;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/optimize_myrocks_replace_into.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/optimize_myrocks_replace_into.test
new file mode 100644
index 00000000..82b231d4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/optimize_myrocks_replace_into.test
@@ -0,0 +1,149 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_debug.inc
+
+connection master;
+SET @prior_rocksdb_perf_context_level = @@rocksdb_perf_context_level;
+SET GLOBAL rocksdb_perf_context_level=3;
+SET GLOBAL enable_blind_replace=ON;
+
+# Create and insert some rows in a table
+create table t1(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t1 values(1,1),(2,2),(3,3);
+select * from t1;
+
+# Create table which has a trigger only in slave
+create table t2(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t2 values(1,1),(2,2),(3,3);
+select * from t2;
+
+# Create table which has a secondary key only in slave
+create table t3(c1 int,c2 int, primary key (c1)) engine=rocksdb;
+insert into t3 values(1,1),(2,2),(3,3);
+select * from t3;
+
+sync_slave_with_master;
+
+# Enable blind replace in both slave and master
+connection slave;
+SET GLOBAL enable_blind_replace=ON;
+create trigger trg before insert on t2 for each row set @a:=1;
+alter table t3 add constraint slave_unique_key unique (c2);
+
+connection master;
+
+sync_slave_with_master;
+--echo connect slave
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+# Case 1 - 'replace into' on a table with no triggers or secondary keys. Blind replace optimization should kick in both in master and slave
+--echo Case 1
+connection master;
+--echo connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+replace into t1 values(1,11);
+replace into t1 values(2,22);
+replace into t1 values(3,33);
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+select * from t1;
+
+sync_slave_with_master;
+--echo connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+# Case 2 - Multiple replaces in a single statement. blind replace optimization should kick in
+connection master;
+--echo Case 2
+--echo connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t1 values(2,44),(3,55);
+select case when variable_value-@d > 2 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+sync_slave_with_master;
+--echo connect slave
+select case when variable_value-@d > 2 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t1;
+
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+# Case 3 - A regular update. This is not a blind replace
+--echo Case 3
+connection master;
+--echo connect master
+update t1 set c2=66 where c1=3;
+select * from t1;
+
+sync_slave_with_master;
+--echo connect slave
+select * from t1;
+
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+# Case 4 - Slave has trigger on its table. No triggers on the table in master.
+# Blind replace optimization should kick in on master.
+# Slave should convert this statement into a regular update
+--echo Case 4
+connection master;
+--echo connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t2 values(1,111);
+replace into t2 values(2,222);
+replace into t2 values(3,333);
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t2;
+
+sync_slave_with_master;
+--echo connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t2;
+
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+# Case 5 - Slave has secondary keys on the table. No secondary keys on the table in master
+# Blind replace optimization should kick in on master.
+# Slave should convert this statement into a regular delete_insert
+--echo Case 5
+connection master;
+--echo connect master
+select variable_value into @d from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+replace into t3 values(1,1111);
+replace into t3 values(2,2222);
+replace into t3 values(3,3333);
+select * from t3;
+
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+
+sync_slave_with_master;
+--echo connect slave
+select case when variable_value-@d > 3 then 'false' else 'true' end as read_free from information_schema.global_status where variable_name='rocksdb_num_get_for_update_calls';
+select * from t3;
+select * from t3 use index (slave_unique_key);
+
+# Case 6 - Just to verify all binlog events.
+# blind replace will generate a write_rows event.
+# Or else, it will be a update_rows event or a delete_rows_write_rows event
+--echo Case 6
+connection master;
+--source include/show_binlog_events.inc
+
+connection slave;
+--source include/show_binlog_events.inc
+
+# Cleanup
+connection master;
+drop table t1;
+drop table t2;
+drop table t3;
+SET GLOBAL rocksdb_perf_context_level = @prior_rocksdb_perf_context_level;
+SET GLOBAL enable_blind_replace=DEFAULT;
+
+connection slave;
+SET GLOBAL enable_blind_replace=DEFAULT;
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency-slave.opt
new file mode 100644
index 00000000..78b517e9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency-slave.opt
@@ -0,0 +1 @@
+--slave_check_before_image_consistency=ON
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency.test
new file mode 100644
index 00000000..d7db127a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rocksdb_slave_check_before_image_consistency.test
@@ -0,0 +1,22 @@
+source include/master-slave.inc;
+source include/have_binlog_format_row.inc;
+
+call mtr.add_suppression("Error_code: 1032");
+
+let $engine= rocksdb;
+
+source extra/rpl_tests/rpl_slave_check_before_image_consistency.inc;
+
+# check detection with HASH_SCAN enabled
+connection slave;
+source include/stop_slave.inc;
+set @@global.slave_rows_search_algorithms = 'INDEX_SCAN,TABLE_SCAN,HASH_SCAN';
+source include/start_slave.inc;
+source extra/rpl_tests/rpl_slave_check_before_image_consistency.inc;
+
+# cleanup
+source include/stop_slave.inc;
+set @@global.slave_rows_search_algorithms = DEFAULT;
+source include/start_slave.inc;
+
+source include/rpl_end.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count-master.opt
new file mode 100644
index 00000000..ed50a8a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count-master.opt
@@ -0,0 +1,3 @@
+--innodb --max-binlog-size=4096
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count.test
new file mode 100644
index 00000000..7667f153
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_binlog_xid_count.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+--source include/have_binlog_format_row.inc
+
+CREATE TABLE `t` (
+ `a` text DEFAULT NULL
+) ENGINE=ROCKSDB;
+
+
+--let $size=`SELECT @@GLOBAL.max_binlog_size`
+--let $loop_cnt= 100
+while ($loop_cnt)
+{
+ --eval INSERT INTO t SET a=repeat('a', $size)
+ --eval INSERT INTO t SET a=repeat('a', $size/2)
+
+ --dec $loop_cnt
+}
+
+# Cleanup
+DROP TABLE t;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_check_for_binlog_info.pl b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_check_for_binlog_info.pl
new file mode 100644
index 00000000..a5e4d9d8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_check_for_binlog_info.pl
@@ -0,0 +1,19 @@
+my $pid_file = $ARGV[0];
+my $log_file = $ARGV[1];
+
+open(my $fh, '<', $pid_file) || die "Cannot open pid file $pid_file";
+my $slave_pid = <$fh>;
+close($fh);
+
+$slave_pid =~ s/\s//g;
+open(my $log_fh, '<', $log_file) || die "Cannot open log file $log_file";
+
+my $pid_found = 0;
+while (my $line = <$log_fh>) {
+ next unless ($pid_found || $line =~ /^[\d-]* [\d:]* $slave_pid /);
+ $pid_found = 1 unless ($pid_found);
+ if ($line =~ /^RocksDB: Last binlog file position.*slave-bin\..*\n/) {
+ print "Binlog Info Found\n";
+ }
+}
+close($log_fh);
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.cnf
new file mode 100644
index 00000000..bbffb0ec
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.cnf
@@ -0,0 +1,13 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+log_slave_updates
+rocksdb_enable_2pc=OFF
+rocksdb_wal_recovery_mode=2
+
+[mysqld.2]
+relay_log_recovery=1
+relay_log_info_repository=TABLE
+log_slave_updates
+rocksdb_enable_2pc=OFF
+rocksdb_wal_recovery_mode=2
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.test
new file mode 100644
index 00000000..0e40e542
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_crash_safe_wal_corrupt.test
@@ -0,0 +1,12 @@
+--source suite/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc
+
+connection slave;
+--let slave_pid_file= query_get_value(SELECT @@pid_file, @@pid_file, 1)
+
+# Verify the log file contains the Last binlog line, but only if the slave server's pid is found
+--exec perl suite/rocksdb_rpl/t/rpl_check_for_binlog_info.pl $slave_pid_file $MYSQLTEST_VARDIR/log/mysqld.2.err
+
+--disable_query_log
+connection slave;
+call mtr.add_suppression("Recovery from master pos");
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_ddl_high_priority.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_ddl_high_priority.test
new file mode 100644
index 00000000..7cf4a4d3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_ddl_high_priority.test
@@ -0,0 +1,2 @@
+--source include/have_rocksdb.inc
+--source include/rpl_ddl_high_priority.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-master.opt
new file mode 100644
index 00000000..397310d3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates --rocksdb_enable_2pc=OFF
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-slave.opt
new file mode 100644
index 00000000..3f959684
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe-slave.opt
@@ -0,0 +1,2 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates --rocksdb_enable_2pc=OFF
+--sync_binlog=1000 --relay_log_recovery=1
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe.test
new file mode 100644
index 00000000..5a3e665a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe.test
@@ -0,0 +1,11 @@
+-- source include/have_rocksdb.inc
+-- source include/have_gtid.inc
+-- source include/master-slave.inc
+-- source include/have_debug.inc
+-- source include/not_valgrind.inc
+
+if (`select count(*) = 1 from information_schema.global_variables where variable_name = 'slave_gtid_info' and variable_value = 'optimized';`) {
+ --skip Test does not support row_write_committed_slave_gtid_optimized policy due to subtle behavioral differences. rpl_gtid_crash_safe_optimized covers slave_gtid_info=optimized.
+}
+
+-- source ../include/rpl_gtid_crash_safe.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-master.opt
new file mode 100644
index 00000000..397310d3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates --rocksdb_enable_2pc=OFF
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-slave.opt
new file mode 100644
index 00000000..e41dcc5e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized-slave.opt
@@ -0,0 +1,2 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates --rocksdb_enable_2pc=OFF
+--sync_binlog=1000 --relay_log_recovery=1 --slave_gtid_info=optimized
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized.test
new file mode 100644
index 00000000..c2624032
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_optimized.test
@@ -0,0 +1,11 @@
+-- source include/have_rocksdb.inc
+-- source include/have_gtid.inc
+-- source include/master-slave.inc
+-- source include/have_debug.inc
+-- source include/not_valgrind.inc
+
+if (`select count(*) = 0 from information_schema.global_variables where variable_name = 'slave_gtid_info' and variable_value = 'optimized';`) {
+ --skip Test requires row_write_committed_slave_gtid_optimized policy where slave_gtid_info=optimized
+}
+
+-- source ../include/rpl_gtid_crash_safe.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.cnf
new file mode 100644
index 00000000..457665f9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.cnf
@@ -0,0 +1,18 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+log_slave_updates
+gtid_mode=ON
+enforce_gtid_consistency=ON
+rocksdb_enable_2pc=OFF
+rocksdb_wal_recovery_mode=2
+
+[mysqld.2]
+sync_relay_log_info=100
+relay_log_recovery=1
+relay_log_info_repository=FILE
+log_slave_updates
+gtid_mode=ON
+enforce_gtid_consistency=ON
+rocksdb_enable_2pc=OFF
+rocksdb_wal_recovery_mode=2
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc
new file mode 100644
index 00000000..16ad535f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc
@@ -0,0 +1,154 @@
+source include/have_rocksdb.inc;
+source include/master-slave.inc;
+-- let $uuid = `select @@server_uuid;`
+
+--exec echo > $MYSQLTEST_VARDIR/log/mysqld.1.err
+
+connection master;
+--disable_warnings
+drop table if exists x;
+--enable_warnings
+
+connection master;
+
+select @@binlog_format;
+
+create table x (id int primary key, value int, value2 int, index(value)) engine=rocksdb;
+insert into x values (1,1,1);
+insert into x values (2,1,1);
+insert into x values (3,1,1);
+insert into x values (4,1,1);
+insert into x values (5,1,1);
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+
+sync_slave_with_master;
+connection slave;
+--let slave_data_dir= query_get_value(SELECT @@DATADIR, @@DATADIR, 1)
+--let slave_pid_file= query_get_value(SELECT @@pid_file, @@pid_file, 1)
+--disable_query_log
+select "--- slave state before crash ---" as "";
+--enable_query_log
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+-- replace_result $uuid uuid
+select * from mysql.slave_gtid_info;
+
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+
+--write_file $MYSQL_TMP_DIR/truncate_tail_wal.sh
+#!/bin/bash
+
+F=`ls -t $slave_data_dir/\#rocksdb/*.log | head -n 1`
+SIZE=`stat -c %s $F`
+NEW_SIZE=`expr $SIZE - 30`
+truncate -s $NEW_SIZE $F
+rc=$?
+if [[ $rc != 0 ]]; then
+ exit 1
+fi
+
+kill -9 `head -1 $slave_pid_file`
+
+exit 0
+EOF
+--chmod 0755 $MYSQL_TMP_DIR/truncate_tail_wal.sh
+--exec $MYSQL_TMP_DIR/truncate_tail_wal.sh
+
+--let $rpl_skip_start_slave= 1
+--source include/rpl_start_server.inc
+--disable_query_log
+select "--- slave state after crash recovery, slave stop, one transaction recovered---" as "";
+--enable_query_log
+connection slave;
+--exec python suite/rocksdb/t/check_log_for_xa.py $MYSQLTEST_VARDIR/log/mysqld.2.err commit,prepare,rollback
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+-- replace_result $uuid uuid
+select * from mysql.slave_gtid_info;
+
+--disable_query_log
+select "--- slave state after restart, slave start ---" as "";
+--enable_query_log
+--source include/start_slave.inc
+connection master;
+sync_slave_with_master;
+connection slave;
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+-- replace_result $uuid uuid
+select * from mysql.slave_gtid_info;
+
+connection master;
+insert into x values (6,1,1);
+
+sync_slave_with_master;
+connection slave;
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+-- replace_result $uuid uuid
+select * from mysql.slave_gtid_info;
+
+connection master;
+insert into x values (7,1,1);
+insert into x values (8,1,1);
+insert into x values (9,1,1);
+insert into x values (10,1,1);
+insert into x values (11,1,1);
+insert into x values (12,1,1);
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+sync_slave_with_master;
+
+connection slave;
+
+# Corrupting WAL. MyRocks does point in time recovery with wal_recovery_mode=2.
+# It loses some data but can resync after restarting slave.
+
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+
+--write_file $MYSQL_TMP_DIR/corrupt_wal.sh
+#!/bin/bash
+
+# expected to be around 950 bytes
+F=`ls -t $slave_data_dir/\#rocksdb/*.log | head -n 1`
+SIZE=`stat -c %s $F`
+OFFSET=$(( $SIZE-500 ))
+dd bs=1 if=/dev/zero of=$F count=100 seek=$OFFSET conv=notrunc
+
+kill -9 `head -1 $slave_pid_file`
+
+exit 0
+EOF
+--chmod 0755 $MYSQL_TMP_DIR/corrupt_wal.sh
+--exec $MYSQL_TMP_DIR/corrupt_wal.sh
+
+--let $rpl_skip_start_slave= 1
+--source include/rpl_start_server.inc
+--disable_query_log
+select "--- slave state after crash recovery, slave stop, WAL was corrupted, point in time recovery with wal_recovery_mode=2 ---" as "";
+--enable_query_log
+select * from x;
+--source include/start_slave.inc
+connection master;
+sync_slave_with_master;
+connection slave;
+select * from x;
+-- replace_result $uuid uuid
+select @@global.gtid_executed;
+-- replace_result $uuid uuid
+select * from mysql.slave_gtid_info;
+
+connection master;
+drop table x;
+
+
+--remove_file $MYSQL_TMP_DIR/truncate_tail_wal.sh
+--remove_file $MYSQL_TMP_DIR/corrupt_wal.sh
+--source include/rpl_end.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.test
new file mode 100644
index 00000000..3b660b26
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.test
@@ -0,0 +1,12 @@
+-- source suite/rocksdb_rpl/t/rpl_gtid_crash_safe_wal_corrupt.inc
+
+connection slave;
+-- let _SLAVE_PID_FILE= query_get_value(SELECT @@pid_file, @@pid_file, 1)
+
+# Verify the log file contains the Last binlog line, but only if the slave server's pid is found
+--exec perl suite/rocksdb_rpl/t/rpl_check_for_binlog_info.pl $slave_pid_file $MYSQLTEST_VARDIR/log/mysqld.2.err
+
+--disable_query_log
+connection slave;
+call mtr.add_suppression("Recovery from master pos");
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-master.opt
new file mode 100644
index 00000000..d828b6c0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-slave.opt
new file mode 100644
index 00000000..d828b6c0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header-slave.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header.test
new file mode 100644
index 00000000..d1793c4a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_gtid_rocksdb_sys_header.test
@@ -0,0 +1,40 @@
+# based on rpl/rpl_gtid_innondb_sys_header.test
+source include/have_rocksdb.inc;
+source include/master-slave.inc;
+source include/have_gtid.inc;
+source include/have_debug.inc;
+source include/not_valgrind.inc;
+
+--let $old_debug = `select @@global.debug;`
+
+connection master;
+create table t1 (a int primary key) engine=rocksdb;
+insert into t1 values(1);
+--eval SET GLOBAL debug = '+d,crash_before_writing_xid'
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+--error 2013
+insert into t1 values(2);
+
+--source include/wait_until_disconnected.inc
+--let $rpl_server_number = 1
+--source include/rpl_reconnect.inc
+
+--eval SET GLOBAL debug = `$old_debug`
+
+connection slave;
+disable_warnings;
+source include/start_slave.inc;
+enable_warnings;
+connection master;
+sync_slave_with_master;
+
+connection master;
+--let $master_uuid= query_get_value(select @@server_uuid, @@server_uuid, 1)
+--replace_result $master_uuid master_uuid
+--exec grep 'RocksDB: Last MySQL Gtid $master_uuid' $MYSQLTEST_VARDIR/log/mysqld.1.err
+
+drop table t1;
+source include/rpl_end.inc;
+-- move_file $MYSQLTEST_VARDIR/log/mysqld.1.err $MYSQLTEST_VARDIR/log/mysqld.1.err.orig
+-- write_file $MYSQLTEST_VARDIR/log/mysqld.1.err
+EOF
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.cnf
new file mode 100644
index 00000000..92ed7198
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.cnf
@@ -0,0 +1,13 @@
+!include include/default_mysqld.cnf
+
+[mysqld.1]
+binlog_row_image = COMPLETE
+log_column_names= ON
+
+[mysqld.2]
+binlog_row_image = COMPLETE
+log_column_names= ON
+
+[ENV]
+SERVER_MYPORT_1= @mysqld.1.port
+SERVER_MYPORT_2= @mysqld.2.port
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.test
new file mode 100644
index 00000000..624f54ac
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_missing_columns_sk_update.test
@@ -0,0 +1,69 @@
+source include/master-slave.inc;
+source include/have_binlog_format_row.inc;
+
+# Create a table with SKs on master
+connection master;
+set @@sql_log_bin = 0;
+CREATE TABLE `t1` (
+ `a` int(10) unsigned NOT NULL DEFAULT '0',
+ `b` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `c` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `d` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `e` varbinary(64) DEFAULT NULL,
+ `f` int(10) NOT NULL DEFAULT '0',
+ `g` int(10) NOT NULL DEFAULT '0',
+ `h` int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`a`,`b`),
+ KEY `key1` (`a`, `e`(1)),
+ KEY `key2` (`a`,`h`)
+) ENGINE=RocksDB;
+set @@sql_log_bin = 1;
+
+# Create same table on slaves but with two extra columns in the middle (x, y)
+connection slave;
+set @@sql_log_bin = 0;
+CREATE TABLE `t1` (
+ `a` int(10) unsigned NOT NULL DEFAULT '0',
+ `b` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `c` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `d` bigint(20) unsigned NOT NULL DEFAULT '0',
+ `e` varbinary(64) DEFAULT NULL,
+ `f` int(10) NOT NULL DEFAULT '0',
+ `g` int(10) NOT NULL DEFAULT '0',
+ `x` TINYINT(3) UNSIGNED DEFAULT NULL,
+ `y` INT(10) DEFAULT NULL,
+ `h` int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`a`,`b`),
+ KEY `key1` (`a`, `e`(1)),
+ KEY `key2` (`a`,`h`)
+) ENGINE=RocksDB;
+set @@sql_log_bin = 1;
+
+# Insert something in the table
+connection master;
+INSERT INTO t1 VALUES (1, 1, 1, 1, 'a', 1, 1, 1);
+SELECT * FROM t1;
+sync_slave_with_master;
+
+connection slave;
+SELECT * FROM t1;
+
+# Update a column that belongs to an SK
+connection master;
+UPDATE t1 SET h = 10 WHERE h = 1;
+SELECT * FROM t1;
+sync_slave_with_master;
+
+# Check if all keys are updated on the slave
+connection slave;
+SELECT * FROM t1;
+SELECT COUNT(*) FROM t1 FORCE INDEX (key1) WHERE h = 10 AND a = 1;
+SELECT COUNT(*) FROM t1 FORCE INDEX (key2) WHERE h = 10 AND a = 1;
+SELECT COUNT(*) FROM t1 FORCE INDEX (PRIMARY) WHERE h = 10 AND a = 1;
+
+# Cleanup
+connection master;
+DROP TABLE t1;
+sync_slave_with_master;
+
+source include/rpl_end.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_mts_dependency_unique_key_conflicts.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_mts_dependency_unique_key_conflicts.test
new file mode 100644
index 00000000..5869b9a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_mts_dependency_unique_key_conflicts.test
@@ -0,0 +1,64 @@
+# RocksDB clone of rpl_mts.rpl_mts_dependency_unique_key_conflicts
+source include/have_rocksdb.inc;
+source include/have_debug_sync.inc;
+source include/master-slave.inc;
+
+connection slave;
+source include/stop_slave.inc;
+set @save.slave_parallel_workers= @@global.slave_parallel_workers;
+set @save.slave_use_idempotent_for_recovery= @@global.slave_use_idempotent_for_recovery;
+set @save.mts_dependency_replication= @@global.mts_dependency_replication;
+set @save.mts_dependency_order_commits= @@global.mts_dependency_order_commits;
+set @save.debug= @@global.debug;
+set @@global.slave_parallel_workers= 2;
+set @@global.slave_use_idempotent_for_recovery= YES;
+set @@global.mts_dependency_replication= STMT;
+set @@global.mts_dependency_order_commits= false;
+set @@global.debug= '+d,dbug.dep_wait_before_update_execution';
+source include/start_slave.inc;
+
+connection master;
+create table t1 (a int primary key, b int unique key) engine = rocksdb;
+insert into t1 values(1, 1);
+source include/sync_slave_sql_with_master.inc;
+source include/stop_slave.inc;
+
+connection master;
+update t1 set b = 2 where a = 1; # this will stall on slave due to dbug_sync
+insert into t1 values(2, 1); # this should wait for the update to finish
+
+connection slave;
+source include/start_slave.inc;
+# wait till one of the workers reach the point just before execution of update
+set debug_sync="now wait_for signal.reached";
+
+# wait till the other worker is waiting after executing the table map for the
+# insert
+let $wait_condition=
+ select count(*)= 1 from information_schema.processlist
+ where state = 'Waiting for dependencies to be satisfied';
+source include/wait_condition.inc;
+
+select * from t1;
+set debug_sync="now signal signal.done";
+
+connection master;
+source include/sync_slave_sql_with_master.inc;
+
+connection slave;
+select * from t1;
+
+# Cleanup
+connection master;
+drop table t1;
+source include/sync_slave_sql_with_master.inc;
+connection slave;
+source include/stop_slave.inc;
+set @@global.slave_parallel_workers= @save.slave_parallel_workers;
+set @@global.mts_dependency_replication= @save.mts_dependency_replication;
+set @@global.slave_use_idempotent_for_recovery= @save.slave_use_idempotent_for_recovery;
+set @@global.mts_dependency_order_commits= @save.mts_dependency_order_commits;
+set @@global.debug= @save.debug;
+source include/start_slave.inc;
+
+source include/rpl_end.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag-slave.opt
new file mode 100644
index 00000000..1c8dc1e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag-slave.opt
@@ -0,0 +1 @@
+--unique-check-lag-threshold=5
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag.test
new file mode 100644
index 00000000..cecacda4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag.test
@@ -0,0 +1,7 @@
+--echo #
+--echo # Ensure skip_unique_check is set when lag exceeds lag_threshold
+--echo #
+
+--source include/have_rocksdb.inc
+--source ../include/rpl_no_unique_check_on_lag.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts-slave.opt
new file mode 100644
index 00000000..1c8dc1e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts-slave.opt
@@ -0,0 +1 @@
+--unique-check-lag-threshold=5
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts.test
new file mode 100644
index 00000000..7e77ec87
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_no_unique_check_on_lag_mts.test
@@ -0,0 +1,3 @@
+--source include/have_rocksdb.inc
+--source ../include/rpl_no_unique_check_on_lag.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-master.opt
new file mode 100644
index 00000000..f8f297c5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-master.opt
@@ -0,0 +1 @@
+--log_bin --log_slave_updates --rocksdb_flush_log_at_trx_commit=1
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-slave.opt
new file mode 100644
index 00000000..7a3f630f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover-slave.opt
@@ -0,0 +1 @@
+--log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover.test
new file mode 100644
index 00000000..58b37209
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_2pc_crash_recover.test
@@ -0,0 +1,57 @@
+--source include/have_binlog_format_row.inc
+--source include/have_rocksdb.inc
+--source include/have_debug.inc
+--source include/not_valgrind.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+create table t1 (a int primary key, msg varchar(255)) engine=rocksdb;
+
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="d,crash_commit_after_prepare";
+--error 0,2013
+insert into t1 values (1, 'dogz');
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+select * from t1;
+
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="d,crash_commit_after_log";
+--error 0,2013
+insert into t1 values (2, 'catz'), (3, 'men');
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+select * from t1;
+
+SET GLOBAL ROCKSDB_ENABLE_2PC = ON;
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="d,crash_commit_after";
+--error 0,2013
+insert into t1 values (4, 'cars'), (5, 'foo');
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+select * from t1;
+
+SET GLOBAL ROCKSDB_ENABLE_2PC = OFF;
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="d,crash_commit_after_log";
+--error 0,2013
+insert into t1 values (6, 'shipz'), (7, 'tankz');
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+select * from t1;
+
+SET GLOBAL ROCKSDB_ENABLE_2PC = OFF;
+--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+SET SESSION debug_dbug="d,crash_commit_after";
+--error 0,2013
+insert into t1 values (8, 'space'), (9, 'time');
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+select * from t1;
+
+drop table t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-master.opt
new file mode 100644
index 00000000..c747adc9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-slave.opt
new file mode 100644
index 00000000..6cde3c55
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized-slave.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates --slave-gtid-info=optimized
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized.test
new file mode 100644
index 00000000..c8a0c8da
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_slave_gtid_info_optimized.test
@@ -0,0 +1,51 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+
+if (`select count(*) = 0 from information_schema.global_variables where variable_name = 'slave_gtid_info' and variable_value = 'optimized';`) {
+ --skip Test requires row_write_committed_slave_gtid_optimized policy where slave_gtid_info=optimized
+}
+
+--echo Make changes in master
+create table test1 (a int primary key, b int) engine=rocksdb;
+insert into test1 values (1, 1);
+
+--echo Make sure slave is up-to-date and mysql.slave_gtid_info is good
+sync_slave_with_master slave;
+connection slave;
+select * from test1;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+select id, database_name, last_gtid from mysql.slave_gtid_info;
+
+--echo Make changes in master
+connection master;
+insert into test1 values (2, 2);
+
+--echo Make sure slave is up-to-date and mysql.slave_gtid_info is good
+sync_slave_with_master slave;
+connection slave;
+select @@slave_gtid_info;
+select * from test1;
+
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+select * from mysql.slave_gtid_info;
+
+--echo Make changes in master
+connection master;
+insert into test1 values (3, 3);
+insert into test1 values (4, 4);
+
+--echo Make sure slave is up-to-date and mysql.slave_gtid_info is good
+sync_slave_with_master slave;
+connection slave;
+select * from test1;
+
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+select id, database_name, last_gtid from mysql.slave_gtid_info;
+
+connection master;
+DROP TABLE IF EXISTS test1;
+
+sync_slave_with_master slave;
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-master.opt
new file mode 100644
index 00000000..c747adc9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-master.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-slave.opt
new file mode 100644
index 00000000..c747adc9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot-slave.opt
@@ -0,0 +1 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_bin --log_slave_updates
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot.test
new file mode 100644
index 00000000..200f1cb3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot.test
@@ -0,0 +1,374 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+--echo # Establish connection con1 (user=root)
+connect (con1,localhost,root,,);
+--echo # Establish connection con2 (user=root)
+connect (con2,localhost,root,,);
+--echo # Establish connection con3 (user=root)
+connect (con3,localhost,root,,);
+--echo # Establish connection con4 (user=root)
+connect (con4,localhost,root,,);
+
+--echo # reset replication to guarantee that master-bin.000001 is used
+connection slave;
+--source include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+
+connection master;
+RESET MASTER;
+
+connection slave;
+--replace_result $MASTER_MYPORT MASTER_PORT
+eval CHANGE MASTER TO master_host="127.0.0.1",master_port=$MASTER_MYPORT,master_user="root";
+--source include/start_slave.inc
+
+### Test 1:
+### - While a consistent snapshot transaction is executed,
+### no external inserts should be visible to the transaction.
+
+--echo # Switch to connection con1
+connection con1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+--error ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+--error ER_ISOLATION_LEVEL_WITH_CONSISTENT_SNAPSHOT
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+ROLLBACK;
+SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+
+
+--disable_query_log
+--disable_result_log
+let $x=1000;
+while ($x) {
+ START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+ dec $x;
+}
+--enable_query_log
+--enable_result_log
+
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+
+--echo # Switch to connection con2
+connection con2;
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1; # should fetch one row
+COMMIT;
+
+SELECT * FROM t1; # should fetch three rows
+
+DROP TABLE t1;
+
+### Test 2:
+### - confirm result from snapshot select and replication replay matches original
+
+--echo # Switch to connection con1
+connection con1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+
+let $binlog_pos = query_get_value(START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT, Position, 1);
+
+--echo # Switch to connection con2
+connection con2;
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1;
+
+--let $outfile = $MYSQLTEST_VARDIR/tmp/rpl_rocksdb_snapshot.out.file
+
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval SELECT * INTO OUTFILE '$outfile' FROM t1;
+COMMIT;
+
+--echo # Switch to slave
+sync_slave_with_master slave;
+
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+--source include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+DELETE FROM t1;
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval LOAD DATA INFILE '$outfile' INTO TABLE t1;
+SELECT * FROM t1;
+
+--replace_result $MASTER_MYPORT MASTER_PORT $binlog_pos binlog_pos
+eval CHANGE MASTER TO master_host="127.0.0.1",master_port=$MASTER_MYPORT,master_user="root",master_log_file="master-bin.000001",master_log_pos=$binlog_pos;
+--source include/start_slave.inc
+
+connection master;
+sync_slave_with_master slave;
+
+SELECT * FROM t1;
+SELECT * FROM t1_backup;
+DROP TABLE t1_backup;
+
+connection master;
+DROP TABLE t1;
+--remove_file $outfile
+
+### Test 3:
+### - confirm result from snapshot select and replication replay matches original
+### - use non-deterministic concurrency
+
+--echo # Switch to connection con1
+connection con1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+
+--echo # async queries from con2
+connection con2;
+send INSERT INTO t1 VALUES(2);
+
+--echo # async queries from con3
+connection con3;
+send INSERT INTO t1 VALUES(21);
+
+--echo # Switch to connection con1
+connection con1;
+
+let $binlog_pos = query_get_value(START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT, Position, 1);
+
+--echo # Switch to connection con4
+connection con4;
+INSERT INTO t1 VALUES(9);
+
+--echo # Switch to connection con1
+connection con1;
+
+--let $outfile = $MYSQLTEST_VARDIR/tmp/rpl_rocksdb_snapshot.out.file
+
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval SELECT * INTO OUTFILE '$outfile' FROM t1;
+COMMIT;
+
+--echo # reap async statements
+connection con2;
+reap;
+
+connection con3;
+reap;
+
+--echo # Switch to slave
+sync_slave_with_master slave;
+
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+--source include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+DELETE FROM t1;
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval LOAD DATA INFILE '$outfile' INTO TABLE t1;
+
+--replace_result $MASTER_MYPORT MASTER_PORT $binlog_pos binlog_pos
+eval CHANGE MASTER TO master_host="127.0.0.1",master_port=$MASTER_MYPORT,master_user="root",master_log_file="master-bin.000001",master_log_pos=$binlog_pos;
+--source include/start_slave.inc
+
+--echo # sync and then query slave
+connection master;
+sync_slave_with_master slave;
+
+let $sum1 = `SELECT SUM(a) from t1`;
+let $sum2 = `SELECT SUM(a) from t1_backup`;
+--disable_query_log
+eval select $sum2 - $sum1 ShouldBeZero;
+--enable_query_log
+
+DROP TABLE t1_backup;
+
+connection master;
+DROP TABLE t1;
+--remove_file $outfile
+
+### Test 4:
+### - confirm result from snapshot select and replication relay using gtid protocol matches original
+
+--echo # Switch to connection con1
+connection con1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+-- replace_regex /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/UUID/
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+
+let $gtid_executed = query_get_value(START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT, Gtid_executed, 1);
+
+--echo # Switch to connection con2
+connection con2;
+INSERT INTO t1 VALUES(2);
+INSERT INTO t1 VALUES(3);
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1;
+
+--let $outfile = $MYSQLTEST_VARDIR/tmp/rpl_rocksdb_snapshot.out.file
+
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval SELECT * INTO OUTFILE '$outfile' FROM t1;
+COMMIT;
+
+--echo # Switch to slave
+sync_slave_with_master slave;
+
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+--source include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+--replace_result $gtid_executed gtid_executed_from_snapshot
+eval SET @@global.gtid_purged='$gtid_executed';
+DELETE FROM t1;
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval LOAD DATA INFILE '$outfile' INTO TABLE t1;
+SELECT * FROM t1;
+
+--replace_result $MASTER_MYPORT MASTER_PORT
+eval CHANGE MASTER TO master_host="127.0.0.1",master_port=$MASTER_MYPORT,master_user="root", master_auto_position=1;
+--source include/start_slave.inc
+
+connection master;
+sync_slave_with_master slave;
+
+SELECT * FROM t1;
+SELECT * FROM t1_backup;
+DROP TABLE t1_backup;
+
+connection master;
+DROP TABLE t1;
+--remove_file $outfile
+
+### Test 5:
+### - confirm result from snapshot select and replication replay using gtid_protocol matches original
+### - use non-deterministic concurrency
+
+--echo # Switch to connection con1
+connection con1;
+CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=rocksdb;
+INSERT INTO t1 VALUES(1);
+
+--echo # async queries from con2
+connection con2;
+send INSERT INTO t1 VALUES(2);
+
+--echo # async queries from con3
+connection con3;
+send INSERT INTO t1 VALUES(21);
+
+--echo # Switch to connection con1
+connection con1;
+
+let $gtid_executed = query_get_value(START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT, Gtid_executed, 1);
+
+--echo # Switch to connection con4
+connection con4;
+INSERT INTO t1 VALUES(9);
+
+--echo # Switch to connection con1
+connection con1;
+
+--let $outfile = $MYSQLTEST_VARDIR/tmp/rpl_rocksdb_snapshot.out.file
+
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval SELECT * INTO OUTFILE '$outfile' FROM t1;
+COMMIT;
+
+--echo # reap async statements
+connection con2;
+reap;
+
+connection con3;
+reap;
+
+--echo # Switch to slave
+sync_slave_with_master slave;
+
+CREATE TABLE t1_backup LIKE t1;
+INSERT INTO t1_backup SELECT * FROM t1;
+--source include/stop_slave.inc
+RESET SLAVE;
+RESET MASTER;
+--replace_result $gtid_executed gtid_executed_from_snapshot
+eval SET @@global.gtid_purged='$gtid_executed';
+DELETE FROM t1;
+
+--replace_result $MYSQLTEST_VARDIR <MYSQLTEST_VARDIR>
+eval LOAD DATA INFILE '$outfile' INTO TABLE t1;
+
+--replace_result $MASTER_MYPORT MASTER_PORT
+eval CHANGE MASTER TO master_host="127.0.0.1",master_port=$MASTER_MYPORT,master_user="root", master_auto_position=1;
+--source include/start_slave.inc
+
+--echo # sync and then query slave
+connection master;
+sync_slave_with_master slave;
+
+let $sum1 = `SELECT SUM(a) from t1`;
+let $sum2 = `SELECT SUM(a) from t1_backup`;
+--disable_query_log
+eval select $sum2 - $sum1 ShouldBeZero;
+--enable_query_log
+
+DROP TABLE t1_backup;
+
+connection master;
+DROP TABLE t1;
+--remove_file $outfile
+
+--echo # Switch to connection default + close connections con1 and con2
+connection con1;
+disconnect con1;
+--source include/wait_until_disconnected.inc
+connection con2;
+disconnect con2;
+--source include/wait_until_disconnected.inc
+connection con3;
+disconnect con3;
+--source include/wait_until_disconnected.inc
+connection con4;
+disconnect con4;
+--source include/wait_until_disconnected.inc
+
+connection default;
+sync_slave_with_master slave;
+--source include/stop_slave.inc
+CHANGE MASTER to master_auto_position=0;
+--source include/start_slave.inc
+
+--source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot_without_gtid.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot_without_gtid.test
new file mode 100644
index 00000000..79d71f20
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_snapshot_without_gtid.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+
+--connection master
+create table t1(a int primary key);
+
+FLUSH LOGS;
+
+insert into t1 values(1);
+insert into t1 values(2);
+
+FLUSH LOGS;
+
+START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT;
+
+drop table t1;
+-- source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-master.opt
new file mode 100644
index 00000000..5c5a73bf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-master.opt
@@ -0,0 +1,2 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates
+--binlog_rows_query_log_events=TRUE --rocksdb_unsafe_for_binlog=TRUE
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-slave.opt
new file mode 100644
index 00000000..b3d52445
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash-slave.opt
@@ -0,0 +1,3 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates --max_binlog_size=50000
+--slave_parallel_workers=30 --relay_log_recovery=1 --rocksdb_unsafe_for_binlog=TRUE
+--rocksdb_wal_recovery_mode=2
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash.test
new file mode 100644
index 00000000..17b86606
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_rocksdb_stress_crash.test
@@ -0,0 +1,26 @@
+-- source include/big_test.inc
+-- source include/master-slave.inc
+-- source include/not_valgrind.inc
+-- source include/have_gtid.inc
+-- source include/have_rocksdb.inc
+
+connection master;
+call mtr.add_suppression(".*");
+sync_slave_with_master;
+-- source include/stop_slave.inc
+change master to master_auto_position=1;
+-- source include/start_slave.inc
+
+-- let $iter=100
+-- let $databases=30
+-- let $num_crashes=100
+-- let $include_silent=1
+-- let $storage_engine='rocksdb'
+-- source extra/rpl_tests/rpl_parallel_load_innodb.test
+-- let $include_silent=0
+
+-- source include/stop_slave.inc
+change master to master_auto_position=0;
+-- source include/start_slave.inc
+
+-- source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-master.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-master.opt
new file mode 100644
index 00000000..39bb3238
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-master.opt
@@ -0,0 +1,2 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates
+--binlog_format=STATEMENT --default-storage-engine=rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-slave.opt b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-slave.opt
new file mode 100644
index 00000000..826f1ee9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format-slave.opt
@@ -0,0 +1,2 @@
+--gtid_mode=ON --enforce_gtid_consistency --log_slave_updates
+--sync_binlog=1000 --relay_log_recovery=1 --default-storage-engine=rocksdb
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format.test
new file mode 100644
index 00000000..1ea9add8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_skip_trx_api_binlog_format.test
@@ -0,0 +1,52 @@
+# Checks if the slave stops executing transactions when master's binlog format
+# is STATEMENT but rpl_skip_tx_api is enabled
+-- source include/have_rocksdb.inc
+-- source include/master-slave.inc
+
+call mtr.add_suppression("Master's binlog format is not ROW but rpl_skip_tx_api is enabled on the slave");
+
+connection slave;
+let $old_rpl_skip_tx_api= `SELECT @@global.rpl_skip_tx_api`;
+set global rpl_skip_tx_api=ON;
+
+connection master;
+let $old_rocksdb_unsafe_for_binlog= `SELECT @@global.rocksdb_unsafe_for_binlog`;
+set global rocksdb_unsafe_for_binlog=1;
+create table t1(a int);
+set session binlog_format=STATEMENT;
+insert into t1 values(1);
+
+# Wait till we hit the binlog format mismatch error
+connection slave;
+let $slave_sql_errno= convert_error(ER_MTS_INCONSISTENT_DATA); # 1756
+let $show_slave_sql_error= 1;
+source include/wait_for_slave_sql_error.inc;
+
+# Print table
+connection slave;
+echo "Table after error";
+select * from t1;
+
+connection slave;
+# Turn off rpl_skip_tx_api and start the slave again
+set global rpl_skip_tx_api=OFF;
+source include/start_slave.inc;
+
+connection slave;
+source include/sync_slave_sql_with_master.inc;
+
+connection slave;
+# Print table again
+echo "Table after error fixed";
+select * from t1;
+
+# Cleanup
+connection master;
+drop table t1;
+eval set global rocksdb_unsafe_for_binlog=$old_rocksdb_unsafe_for_binlog;
+sync_slave_with_master;
+
+connection slave;
+eval set global rpl_skip_tx_api=$old_rpl_skip_tx_api;
+
+-- source include/rpl_end.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.inc b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.inc
new file mode 100644
index 00000000..253d9f16
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.inc
@@ -0,0 +1,84 @@
+#
+# This "body" file checks general properties of XA transaction replication
+# as of MDEV-7974, including XA of mixed engine branches.
+# Parameters:
+# --let rpl_xa_check= SELECT ...
+#
+connection master;
+create table ti (a int, b int) engine=innodb;
+create table t1 (a int, b int) engine=rocksdb;
+insert into ti values(0, 0);
+insert into t1 values(0, 0);
+xa start 't';
+insert into ti values(1, 2);
+insert into t1 values(1, 2);
+xa end 't';
+xa prepare 't';
+xa commit 't';
+
+sync_slave_with_master;
+let $diff_tables= master:t1, slave:t1;
+source include/diff_tables.inc;
+
+connection master;
+
+xa start 't';
+insert into ti values(3, 4);
+insert into t1 values(3, 4);
+xa end 't';
+xa prepare 't';
+xa rollback 't';
+
+sync_slave_with_master;
+let $diff_tables= master:t1, slave:t1;
+source include/diff_tables.inc;
+
+connection master;
+SET pseudo_slave_mode=1;
+create table t2 (a int) engine=rocksdb;
+xa start 't';
+insert into ti values (5, 6);
+insert into t1 values (5, 6);
+xa end 't';
+xa prepare 't';
+xa start 's';
+insert into ti values (7, 8);
+insert into t2 values (0);
+xa end 's';
+xa prepare 's';
+--source include/save_master_gtid.inc
+
+connection slave;
+source include/sync_with_master_gtid.inc;
+if ($rpl_xa_check)
+{
+ --eval $rpl_xa_check
+ if ($rpl_xa_verbose)
+ {
+ --eval SELECT $rpl_xa_check_lhs
+ --eval SELECT $rpl_xa_check_rhs
+ }
+}
+xa recover;
+
+connection master;
+xa commit 't';
+xa commit 's';
+SET pseudo_slave_mode=0;
+
+# pure rocksdb xa
+xa start 'r';
+insert into t1 values(7, 8);
+xa end 'r';
+xa prepare 'r';
+xa commit 'r';
+
+
+sync_slave_with_master;
+let $diff_tables= master:t1, slave:t1;
+source include/diff_tables.inc;
+let $diff_tables= master:t2, slave:t2;
+source include/diff_tables.inc;
+
+connection master;
+drop table ti, t1, t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.test
new file mode 100644
index 00000000..34384c74
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/rpl_xa.test
@@ -0,0 +1,7 @@
+source include/have_rocksdb.inc;
+source include/have_innodb.inc;
+source include/master-slave.inc;
+source include/have_binlog_format_row.inc;
+
+source rpl_xa.inc;
+source include/rpl_end.inc;
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.cnf
new file mode 100644
index 00000000..71e124ad
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.cnf
@@ -0,0 +1,15 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+log_slave_updates
+gtid_mode=ON
+enforce_gtid_consistency=ON
+
+[mysqld.2]
+relay_log_recovery=1
+relay_log_info_repository=FILE
+log_slave_updates
+gtid_mode=ON
+enforce_gtid_consistency=ON
+slave_use_idempotent_for_recovery=Yes
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.test
new file mode 100644
index 00000000..6d953ead
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_recovery.test
@@ -0,0 +1,78 @@
+
+--source include/have_binlog_format_row.inc
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_gtid.inc
+--source include/not_valgrind.inc
+
+# This is a test case for issue#655 -- SingleDelete on Primary Key may
+# cause extra rows than Secondary Keys
+
+call mtr.add_suppression("Recovery from master pos");
+
+connection master;
+--disable_warnings
+drop table if exists r1;
+--enable_warnings
+create table r1 (id1 int, id2 int, primary key (id1, id2), index i (id2)) engine=rocksdb;
+insert into r1 values (1, 1000);
+
+sync_slave_with_master;
+connection slave;
+set global rocksdb_force_flush_memtable_now=1;
+--let slave_data_dir= query_get_value(SELECT @@DATADIR, @@DATADIR, 1)
+--let slave_binlog_file= query_get_value(SHOW MASTER STATUS, File, 1)
+--let slave_pid_file= query_get_value(SELECT @@pid_file, @@pid_file, 1)
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+
+--write_file $MYSQL_TMP_DIR/truncate_tail_binlog.sh
+#!/bin/bash
+
+F=$slave_data_dir/$slave_binlog_file
+SIZE=`stat -c %s $F`
+NEW_SIZE=`expr $SIZE - 100`
+truncate -s $NEW_SIZE $F
+rc=$?
+if [[ $rc != 0 ]]; then
+ exit 1
+fi
+
+kill -9 `head -1 $slave_pid_file`
+
+exit 0
+EOF
+--chmod 0755 $MYSQL_TMP_DIR/truncate_tail_binlog.sh
+--exec $MYSQL_TMP_DIR/truncate_tail_binlog.sh
+
+--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.2.expect
+
+# Crash recovery (losing some binlogs) with slave_use_idempotent_for_recovery may
+# replay same transactions with slave_exec_mode=idempotent implicitly enabled.
+# On slave, the last insert is converted to update with the same key.
+# It should be treated as SD and Put (same as singledelete_idempotent_table.test).
+
+--source include/rpl_start_server.inc
+--source include/start_slave.inc
+
+# Due to the binlogs being truncated, the slave may still think it's processed up to
+# the truncated binlog and select master_pos_wait() can return prematurely. Add
+# a new transaction to the master to force master_pos_wait() to wait.
+connection master;
+insert into r1 values (2,2000);
+sync_slave_with_master;
+
+connection slave;
+delete r1 from r1 force index (i) where id2=1000;
+select id1,id2 from r1 force index (primary) where id1=1 and id2=1000;
+select id2 from r1 force index (i) where id1=1 and id2=1000;
+set global rocksdb_compact_cf='default';
+select id1,id2 from r1 force index (primary) where id1=1 and id2=1000;
+select id2 from r1 force index (i) where id1=1 and id2=1000;
+
+connection master;
+drop table r1;
+
+--remove_file $MYSQL_TMP_DIR/truncate_tail_binlog.sh
+--source include/rpl_end.inc
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.cnf b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.cnf
new file mode 100644
index 00000000..5f1f87d7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.cnf
@@ -0,0 +1,11 @@
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+log_slave_updates
+
+[mysqld.2]
+relay_log_recovery=1
+#relay_log_info_repository=FILE
+log_slave_updates
+#rbr_idempotent_tables='r1'
+slave_exec_mode=IDEMPOTENT
diff --git a/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.test b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.test
new file mode 100644
index 00000000..00dce7c2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_rpl/t/singledelete_idempotent_table.test
@@ -0,0 +1,44 @@
+
+--source include/have_binlog_format_row.inc
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+#--source include/have_gtid.inc
+--source include/not_valgrind.inc
+
+# This is a test case for issue#655 -- SingleDelete on Primary Key may
+# cause extra rows than Secondary Keys
+
+connection master;
+--disable_warnings
+drop table if exists r1;
+--enable_warnings
+create table r1 (id1 int, id2 int, primary key (id1, id2), index i (id2)) engine=rocksdb;
+insert into r1 values (1, 1000);
+set sql_log_bin=0;
+delete from r1 where id1=1 and id2=1000;
+set sql_log_bin=1;
+
+sync_slave_with_master;
+connection slave;
+set global rocksdb_force_flush_memtable_now=1;
+
+connection master;
+# same key insert on slave. Since slave sets rbr_idempotent_tables, the insert
+# is converted to update with the same key. MyRocks should call SD and Put for the key
+insert into r1 values (1, 1000);
+sync_slave_with_master;
+
+connection slave;
+delete r1 from r1 force index (i) where id2=1000;
+select id1,id2 from r1 force index (primary);
+select id2 from r1 force index (i);
+set global rocksdb_compact_cf='default';
+select id1,id2 from r1 force index (primary);
+select id2 from r1 force index (i);
+
+connection master;
+drop table r1;
+
+--source include/rpl_end.inc
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/combinations b/storage/rocksdb/mysql-test/rocksdb_stress/combinations
new file mode 100644
index 00000000..b7316c71
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/combinations
@@ -0,0 +1,5 @@
+[write_committed]
+rocksdb_write_policy=write_committed
+
+[write_prepared]
+rocksdb_write_policy=write_prepared
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.inc b/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.inc
new file mode 100644
index 00000000..1f762d38
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.inc
@@ -0,0 +1,10 @@
+if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'rocksdb' AND support IN ('YES', 'DEFAULT', 'ENABLED')`)
+{
+ --skip Test requires engine RocksDB.
+}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.opt b/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.opt
new file mode 100644
index 00000000..36d7dda1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/include/have_rocksdb.opt
@@ -0,0 +1,12 @@
+--loose-enable-rocksdb
+--loose-enable-rocksdb_global_info
+--loose-enable-rocksdb_ddl
+--loose-enable-rocksdb_cf_options
+--loose-enable_rocksdb_perf_context
+--loose-enable_rocksdb_perf_context_global
+--loose-enable-rocksdb_index_file_map
+--loose-enable-rocksdb_dbstats
+--loose-enable-rocksdb_cfstats
+--loose-enable-rocksdb_lock_info
+--loose-enable-rocksdb_trx
+--loose-enable-rocksdb_locks
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/include/rocksdb_stress.inc b/storage/rocksdb/mysql-test/rocksdb_stress/include/rocksdb_stress.inc
new file mode 100644
index 00000000..3838987b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/include/rocksdb_stress.inc
@@ -0,0 +1,57 @@
+# Run the load generator to populate the table and generate concurrent
+# updates. After the load generator is complete, verify the tables on the
+# master and the slave are consistent
+
+--sync_slave_with_master
+
+--connection master
+--let $master_host = 127.0.0.1
+let $MYSQL_BASEDIR = `SELECT @@BASEDIR`;
+
+let $exec =
+ python
+ $MYSQL_BASEDIR/storage/rocksdb/mysql-test/rocksdb_stress/t/load_generator.py \
+ -L $MYSQL_TMP_DIR/load_generator.log -H $master_host -t $table \
+ -P $MASTER_MYPORT -n $num_records -m $max_records \
+ -l $num_loaders -c $num_checkers -r $num_requests \
+ -E $MYSQLTEST_VARDIR/tmp/mysqld.1.expect \
+ -D $reap_delay;
+
+exec $exec;
+
+enable_reconnect;
+source include/wait_until_connected_again.inc;
+
+--let $master_checksum = query_get_value(CHECKSUM TABLE $table, Checksum, 1)
+
+# if sync_slave_with_master had a configurable timeout this would not be needed
+let $slave_sync_timeout = 7200;
+--source include/wait_for_slave_to_sync_with_master.inc
+
+--connection slave
+--let $slave_checksum = query_get_value(CHECKSUM TABLE $table, Checksum, 1)
+
+let $not_same = `SELECT $master_checksum-$slave_checksum`;
+if ($not_same)
+{
+ --die "The checksums of table $table for the master and slave do not match!"
+}
+
+# Cleanup
+--connection master
+--let $cleanup = DROP TABLE $table
+eval $cleanup;
+
+# if sync_slave_with_master had a configurable timeout this would not be needed
+let $slave_sync_timeout = 7200;
+--source include/wait_for_slave_to_sync_with_master.inc
+
+--connection slave
+--source include/stop_slave.inc
+# For stress tests sometimes the replication thread can not connect to master
+# temporarily. This is either because the master crashed and it is recovering
+# or the master is too busy and could not service the slave's requests.
+# mtr's internal check requires that there be no errors in slave status.
+# restarting replication clears the errors.
+--source include/start_slave.inc
+--source include/stop_slave.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/my.cnf b/storage/rocksdb/mysql-test/rocksdb_stress/my.cnf
new file mode 100644
index 00000000..6597d18b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/my.cnf
@@ -0,0 +1,9 @@
+!include include/default_my.cnf
+!include ../storage/rocksdb/mysql-test/rocksdb/my.cnf
+!include suite/rpl/my.cnf
+
+[mysqld.1]
+binlog_format=row
+
+[mysqld.2]
+binlog_format=row
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress.result b/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress.result
new file mode 100644
index 00000000..9f161b18
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress.result
@@ -0,0 +1,23 @@
+include/master-slave.inc
+[connection master]
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1(id INT PRIMARY KEY,
+thread_id INT NOT NULL,
+request_id BIGINT UNSIGNED NOT NULL,
+update_count INT UNSIGNED NOT NULL DEFAULT 0,
+zero_sum INT DEFAULT 0,
+msg VARCHAR(1024),
+msg_length int,
+msg_checksum varchar(128),
+auto_inc BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+KEY(auto_inc),
+KEY msg_i(msg(255), zero_sum))
+ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+stop slave;
+start slave;
+DROP TABLE t1;
+stop slave;
+start slave;
+include/stop_slave.inc
+include/start_slave.inc
+include/stop_slave.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress_crash.result b/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress_crash.result
new file mode 100644
index 00000000..9f161b18
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/r/rocksdb_stress_crash.result
@@ -0,0 +1,23 @@
+include/master-slave.inc
+[connection master]
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1(id INT PRIMARY KEY,
+thread_id INT NOT NULL,
+request_id BIGINT UNSIGNED NOT NULL,
+update_count INT UNSIGNED NOT NULL DEFAULT 0,
+zero_sum INT DEFAULT 0,
+msg VARCHAR(1024),
+msg_length int,
+msg_checksum varchar(128),
+auto_inc BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+KEY(auto_inc),
+KEY msg_i(msg(255), zero_sum))
+ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+stop slave;
+start slave;
+DROP TABLE t1;
+stop slave;
+start slave;
+include/stop_slave.inc
+include/start_slave.inc
+include/stop_slave.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/suite.opt b/storage/rocksdb/mysql-test/rocksdb_stress/suite.opt
new file mode 100644
index 00000000..22c9d7a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/suite.opt
@@ -0,0 +1,2 @@
+--plugin-load=$HA_ROCKSDB_SO --default-storage-engine=rocksdb
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/suite.pm b/storage/rocksdb/mysql-test/rocksdb_stress/suite.pm
new file mode 100644
index 00000000..106a1d6a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/suite.pm
@@ -0,0 +1,28 @@
+package My::Suite::Rocksdb;
+
+use My::SysInfo;
+
+#
+# Note: ../rocksdb_sys_vars/suite.pm file has a similar
+# function. If you modify this file, consider modifying that one, too.
+#
+@ISA = qw(My::Suite);
+use My::Find;
+use File::Basename;
+use strict;
+
+#sub is_default { not $::opt_embedded_server }
+
+my $sst_dump=
+::mtr_exe_maybe_exists(
+ "$::bindir/storage/rocksdb$::opt_vs_config/sst_dump",
+ "$::path_client_bindir/sst_dump");
+return "RocksDB is not compiled, no sst_dump" unless $sst_dump;
+$ENV{MARIAROCKS_SST_DUMP}="$sst_dump";
+
+# Temporarily disable testing under valgrind, due to MDEV-12439
+return "RocksDB tests disabled under valgrind" if ($::opt_valgrind);
+
+
+bless { };
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/t/disabled.def b/storage/rocksdb/mysql-test/rocksdb_stress/t/disabled.def
new file mode 100644
index 00000000..42b2f661
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/t/disabled.def
@@ -0,0 +1,2 @@
+rocksdb_stress_crash: Client scipt seems to hang on terminal input in MariaDB
+rocksdb_stress: Client scipt seems to hang on terminal input in MariaDB
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/t/load_generator.py b/storage/rocksdb/mysql-test/rocksdb_stress/t/load_generator.py
new file mode 100644
index 00000000..c1d3e7fb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/t/load_generator.py
@@ -0,0 +1,1042 @@
+import cStringIO
+import array
+import hashlib
+import MySQLdb
+from MySQLdb.constants import CR
+from MySQLdb.constants import ER
+from collections import deque
+import os
+import random
+import signal
+import sys
+import threading
+import time
+import string
+import traceback
+import logging
+import argparse
+
+# This is a generic load_generator for mysqld which persists across server
+# restarts and attempts to verify both committed and uncommitted transactions
+# are persisted correctly.
+#
+# The table schema used should look something like:
+#
+# CREATE TABLE t1(id INT PRIMARY KEY,
+# thread_id INT NOT NULL,
+# request_id BIGINT UNSIGNED NOT NULL,
+# update_count INT UNSIGNED NOT NULL DEFAULT 0,
+# zero_sum INT DEFAULT 0,
+# msg VARCHAR(1024),
+# msg_length int,
+# msg_checksum varchar(128),
+# KEY msg_i(msg(255), zero_sum))
+# ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+#
+# zero_sum should always sum up to 0 regardless of when the transaction tries
+# to process the transaction. Each transaction always maintain this sum to 0.
+#
+# request_id should be unique across transactions. It is used during
+# transaction verification and is monotonically increasing..
+#
+# Several threads are spawned at the start of the test to populate the table.
+# Once the table is populated, both loader and checker threads are created.
+#
+# The row id space is split into two sections: exclusive and shared. Each
+# loader thread owns some part of the exclusive section which it maintains
+# complete information on insert/updates/deletes. Since this section is only
+# modified by one thread, the thread can maintain an accurate picture of all
+# changes. The shared section contains rows which multiple threads can
+# update/delete/insert. For checking purposes, the request_id is used to
+# determine if a row is consistent with a committed transaction.
+#
+# Each loader thread's transaction consists of selecting some number of rows
+# randomly. The thread can choose to delete the row, update the row or insert
+# the row if it doesn't exist. The state of rows that are owned by the loader
+# thread are tracked within the thread's id_map. This map contains the row id
+# and the request_id of the latest update. For indicating deleted rows, the
+# -request_id marker is used. Thus, at any point in time, the thread's id_map
+# should reflect the exact state of the rows that are owned.
+#
+# The loader thread also maintains the state of older transactions that were
+# successfully processed in addition to the current transaction, which may or
+# may not be committed. Each transaction state consists of the row id, and the
+# request_id. Again, -request_id is used to indicate a delete. For committed
+# transactions, the thread can verify the request_id of the row is larger than
+# what the thread has recorded. For uncommitted transactions, the thread would
+# verify the request_id of the row does not match that of the transaction. To
+# determine whether or not a transaction succeeded in case of a crash right at
+# commit, each thread always includes a particular row in the transaction which
+# it could use to check the request id against.
+#
+# Checker threads run continuously to verify the checksums on the rows and to
+# verify the zero_sum column sums up to zero at any point in time. The checker
+# threads run both point lookups and range scans for selecting the rows.
+
+class ValidateError(Exception):
+ """Raised when validation fails"""
+ pass
+
+class TestError(Exception):
+ """Raised when the test cannot make forward progress"""
+ pass
+
+CHARS = string.letters + string.digits
+OPTIONS = {}
+
+# max number of rows per transaction
+MAX_ROWS_PER_REQ = 10
+
+# global variable checked by threads to determine if the test is stopping
+TEST_STOP = False
+LOADERS_READY = 0
+
+# global monotonically increasing request id counter
+REQUEST_ID = 1
+REQUEST_ID_LOCK = threading.Lock()
+
+INSERT_ID_SET = set()
+
+def get_next_request_id():
+ global REQUEST_ID
+ with REQUEST_ID_LOCK:
+ REQUEST_ID += 1
+ return REQUEST_ID
+
+# given a percentage value, rolls a 100-sided die and return whether the
+# given value is above or equal to the die roll
+#
+# passing 0 should always return false and 100 should always return true
+def roll_d100(p):
+ assert p >= 0 and p <= 100
+ return p >= random.randint(1, 100)
+
+def sha1(x):
+ return hashlib.sha1(str(x)).hexdigest()
+
+def is_connection_error(exc):
+ error_code = exc.args[0]
+ return (error_code == MySQLdb.constants.CR.CONNECTION_ERROR or
+ error_code == MySQLdb.constants.CR.CONN_HOST_ERROR or
+ error_code == MySQLdb.constants.CR.SERVER_LOST or
+ error_code == MySQLdb.constants.CR.SERVER_GONE_ERROR or
+ error_code == MySQLdb.constants.ER.QUERY_INTERRUPTED or
+ error_code == MySQLdb.constants.ER.SERVER_SHUTDOWN)
+
+def is_deadlock_error(exc):
+ error_code = exc.args[0]
+ return (error_code == MySQLdb.constants.ER.LOCK_DEADLOCK or
+ error_code == MySQLdb.constants.ER.LOCK_WAIT_TIMEOUT)
+
+# should be deterministic given an idx
+def gen_msg(idx, thread_id, request_id):
+ random.seed(idx);
+ # field length is 1024 bytes, but 32 are reserved for the tid and req tag
+ blob_length = random.randint(1, 1024 - 32)
+
+ if roll_d100(50):
+ # blob that cannot be compressed (well, compresses to 85% of original size)
+ msg = ''.join([random.choice(CHARS) for x in xrange(blob_length)])
+ else:
+ # blob that can be compressed
+ msg = random.choice(CHARS) * blob_length
+
+ # append the thread_id and request_id to the end of the msg
+ return ''.join([msg, ' tid: %d req: %d' % (thread_id, request_id)])
+
+def execute(cur, stmt):
+ ROW_COUNT_ERROR = 18446744073709551615L
+ logging.debug("Executing %s" % stmt)
+ cur.execute(stmt)
+ if cur.rowcount < 0 or cur.rowcount == ROW_COUNT_ERROR:
+ raise MySQLdb.OperationalError(MySQLdb.constants.CR.CONNECTION_ERROR,
+ "Possible connection error, rowcount is %d"
+ % cur.rowcount)
+
+def wait_for_workers(workers, min_active = 0):
+ logging.info("Waiting for %d workers", len(workers))
+ # min_active needs to include the current waiting thread
+ min_active += 1
+
+ # polling here allows this thread to be responsive to keyboard interrupt
+ # exceptions, otherwise a user hitting ctrl-c would see the load_generator as
+ # hanging and unresponsive
+ try:
+ while threading.active_count() > min_active:
+ time.sleep(1)
+ except KeyboardInterrupt, e:
+ os._exit(1)
+
+ num_failures = 0
+ for w in workers:
+ w.join()
+ if w.exception:
+ logging.error(w.exception)
+ num_failures += 1
+
+ return num_failures
+
+# base class for worker threads and contains logic for handling reconnecting to
+# the mysqld server during connection failure
+class WorkerThread(threading.Thread):
+ def __init__(self, name):
+ threading.Thread.__init__(self)
+ self.name = name
+ self.exception = None
+ self.con = None
+ self.cur = None
+ self.isolation_level = None
+ self.start_time = time.time()
+ self.total_time = 0
+
+ def run(self):
+ global TEST_STOP
+
+ try:
+ logging.info("Started")
+ self.runme()
+ logging.info("Completed successfully")
+ except Exception, e:
+ self.exception = traceback.format_exc()
+ logging.error(self.exception)
+ TEST_STOP = True
+ finally:
+ self.total_time = time.time() - self.start_time
+ logging.info("Total run time: %.2f s" % self.total_time)
+ self.finish()
+
+ def reconnect(self, timeout=900):
+ global TEST_STOP
+
+ self.con = None
+ SECONDS_BETWEEN_RETRY = 10
+ attempts = 1
+ logging.info("Attempting to connect to MySQL Server")
+ while not self.con and timeout > 0 and not TEST_STOP:
+ try:
+ self.con = MySQLdb.connect(user=OPTIONS.user, host=OPTIONS.host,
+ port=OPTIONS.port, db=OPTIONS.db)
+ if self.con:
+ self.con.autocommit(False)
+ self.cur = self.con.cursor()
+ self.set_isolation_level(self.isolation_level)
+ logging.info("Connection successful after attempt %d" % attempts)
+ break
+ except MySQLdb.Error, e:
+ logging.debug(traceback.format_exc())
+ time.sleep(SECONDS_BETWEEN_RETRY)
+ timeout -= SECONDS_BETWEEN_RETRY
+ attempts += 1
+ return self.con is None
+
+ def get_isolation_level(self):
+ execute(self.cur, "SELECT @@SESSION.tx_isolation")
+ if self.cur.rowcount != 1:
+ raise TestError("Unable to retrieve tx_isolation")
+ return self.cur.fetchone()[0]
+
+ def set_isolation_level(self, isolation_level, persist = False):
+ if isolation_level is not None:
+ execute(self.cur, "SET @@SESSION.tx_isolation = '%s'" % isolation_level)
+ if self.cur.rowcount != 0:
+ raise TestError("Unable to set the isolation level to %s")
+
+ if isolation_level is None or persist:
+ self.isolation_level = isolation_level
+
+# periodically kills the server
+class ReaperWorker(WorkerThread):
+ def __init__(self):
+ WorkerThread.__init__(self, 'reaper')
+ self.start()
+ self.kills = 0
+
+ def finish(self):
+ logging.info('complete with %d kills' % self.kills)
+ if self.con:
+ self.con.close()
+
+ def get_server_pid(self):
+ execute(self.cur, "SELECT @@pid_file")
+ if self.cur.rowcount != 1:
+ raise TestError("Unable to retrieve pid_file")
+ return int(open(self.cur.fetchone()[0]).read())
+
+ def runme(self):
+ global TEST_STOP
+ time_remain = random.randint(10, 30)
+ while not TEST_STOP:
+ if time_remain > 0:
+ time_remain -= 1
+ time.sleep(1)
+ continue
+ if self.reconnect():
+ raise Exception("Unable to connect to MySQL server")
+ logging.info('killing server...')
+ with open(OPTIONS.expect_file, 'w+') as expect_file:
+ expect_file.write('restart')
+ os.kill(self.get_server_pid(), signal.SIGTERM)
+ self.kills += 1
+ time_remain = random.randint(0, 30) + OPTIONS.reap_delay;
+
+# runs initially to populate the table with the given number of rows
+class PopulateWorker(WorkerThread):
+ def __init__(self, thread_id, start_id, num_to_add):
+ WorkerThread.__init__(self, 'populate-%d' % thread_id)
+ self.thread_id = thread_id
+ self.start_id = start_id
+ self.num_to_add = num_to_add
+ self.table = OPTIONS.table
+ self.start()
+
+ def finish(self):
+ if self.con:
+ self.con.commit()
+ self.con.close()
+
+ def runme(self):
+ if self.reconnect():
+ raise Exception("Unable to connect to MySQL server")
+
+ stmt = None
+ for i in xrange(self.start_id, self.start_id + self.num_to_add):
+ stmt = gen_insert(self.table, i, 0, 0, 0)
+ execute(self.cur, stmt)
+ if i % 101 == 0:
+ self.con.commit()
+ check_id(self.con.insert_id())
+ self.con.commit()
+ check_id(self.con.insert_id())
+ logging.info("Inserted %d rows starting at id %d" %
+ (self.num_to_add, self.start_id))
+
+def check_id(id):
+ if id == 0:
+ return
+ if id in INSERT_ID_SET:
+ raise Exception("Duplicate auto_inc id %d" % id)
+ INSERT_ID_SET.add(id)
+
+def populate_table(num_records):
+
+ logging.info("Populate_table started for %d records" % num_records)
+ if num_records == 0:
+ return False
+
+ num_workers = min(10, num_records / 100)
+ workers = []
+
+ N = num_records / num_workers
+ start_id = 0
+ for i in xrange(num_workers):
+ workers.append(PopulateWorker(i, start_id, N))
+ start_id += N
+ if num_records > start_id:
+ workers.append(PopulateWorker(num_workers, start_id,
+ num_records - start_id))
+
+ # Wait for the populate threads to complete
+ return wait_for_workers(workers) > 0
+
+def gen_insert(table, idx, thread_id, request_id, zero_sum):
+ msg = gen_msg(idx, thread_id, request_id)
+ return ("INSERT INTO %s (id, thread_id, request_id, zero_sum, "
+ "msg, msg_length, msg_checksum) VALUES (%d,%d,%d,%d,'%s',%d,'%s')"
+ % (table, idx, thread_id, request_id,
+ zero_sum, msg, len(msg), sha1(msg)))
+
+def gen_update(table, idx, thread_id, request_id, zero_sum):
+ msg = gen_msg(idx, thread_id, request_id)
+ return ("UPDATE %s SET thread_id = %d, request_id = %d, "
+ "update_count = update_count + 1, zero_sum = zero_sum + (%d), "
+ "msg = '%s', msg_length = %d, msg_checksum = '%s' WHERE id = %d "
+ % (table, thread_id, request_id, zero_sum, msg, len(msg),
+ sha1(msg), idx))
+
+def gen_delete(table, idx):
+ return "DELETE FROM %s WHERE id = %d" % (table, idx)
+
+def gen_insert_on_dup(table, idx, thread_id, request_id, zero_sum):
+ msg = gen_msg(idx, thread_id, request_id)
+ msg_checksum = sha1(msg)
+ return ("INSERT INTO %s (id, thread_id, request_id, zero_sum, "
+ "msg, msg_length, msg_checksum) VALUES (%d,%d,%d,%d,'%s',%d,'%s') "
+ "ON DUPLICATE KEY UPDATE "
+ "thread_id=%d, request_id=%d, "
+ "update_count=update_count+1, "
+ "zero_sum=zero_sum + (%d), msg='%s', msg_length=%d, "
+ "msg_checksum='%s'" %
+ (table, idx, thread_id, request_id,
+ zero_sum, msg, len(msg), msg_checksum, thread_id, request_id,
+ zero_sum, msg, len(msg), msg_checksum))
+
+# Each loader thread owns a part of the id space which it maintains inventory
+# for. The loader thread generates inserts, updates and deletes for the table.
+# The latest successful transaction and the latest open transaction are kept to
+# verify after a disconnect that the rows were recovered properly.
+class LoadGenWorker(WorkerThread):
+ TXN_UNCOMMITTED = 0
+ TXN_COMMIT_STARTED = 1
+ TXN_COMMITTED = 2
+
+ def __init__(self, thread_id):
+ WorkerThread.__init__(self, 'loader-%02d' % thread_id)
+ self.thread_id = thread_id
+ self.rand = random.Random()
+ self.rand.seed(thread_id)
+ self.loop_num = 0
+
+ # id_map contains the array of id's owned by this worker thread. It needs
+ # to be offset by start_id for the actual id
+ self.id_map = array.array('l')
+ self.start_id = thread_id * OPTIONS.ids_per_loader
+ self.num_id = OPTIONS.ids_per_loader
+ self.start_share_id = OPTIONS.num_loaders * OPTIONS.ids_per_loader
+ self.max_id = OPTIONS.max_id
+ self.table = OPTIONS.table
+ self.num_requests = OPTIONS.num_requests
+
+ # stores information about the latest series of successful transactions
+ #
+ # each transaction is simply a map of id -> request_id
+ # deleted rows are indicated by -request_id
+ self.prev_txn = deque()
+ self.cur_txn = None
+ self.cur_txn_state = None
+
+ self.start()
+
+ def finish(self):
+ if self.total_time:
+ req_per_sec = self.loop_num / self.total_time
+ else:
+ req_per_sec = -1
+ logging.info("total txns: %d, txn/s: %.2f rps" %
+ (self.loop_num, req_per_sec))
+
+ # constructs the internal hash map of the ids owned by this thread and
+ # the request id of each id
+ def populate_id_map(self):
+ logging.info("Populating id map")
+
+ REQ_ID_COL = 0
+ stmt = "SELECT request_id FROM %s WHERE id = %d"
+
+ # the start_id is used for tracking active transactions, so the row needs
+ # to exist
+ idx = self.start_id
+ execute(self.cur, stmt % (self.table, idx))
+ if self.cur.rowcount > 0:
+ request_id = self.cur.fetchone()[REQ_ID_COL]
+ else:
+ request_id = get_next_request_id()
+ execute(self.cur, gen_insert(self.table, idx, self.thread_id,
+ request_id, 0))
+ self.con.commit()
+ check_id(self.con.insert_id())
+
+ self.id_map.append(request_id)
+
+ self.cur_txn = {idx:request_id}
+ self.cur_txn_state = self.TXN_COMMITTED
+ for i in xrange(OPTIONS.committed_txns):
+ self.prev_txn.append(self.cur_txn)
+
+ # fetch the rest of the row for the id space owned by this thread
+ for idx in xrange(self.start_id + 1, self.start_id + self.num_id):
+ execute(self.cur, stmt % (self.table, idx))
+ if self.cur.rowcount == 0:
+ # Negative number is used to indicated a missing row
+ self.id_map.append(-1)
+ else:
+ res = self.cur.fetchone()
+ self.id_map.append(res[REQ_ID_COL])
+
+ self.con.commit()
+
+ def apply_cur_txn_changes(self):
+ # apply the changes to the id_map
+ for idx in self.cur_txn:
+ if idx < self.start_id + self.num_id:
+ assert idx >= self.start_id
+ self.id_map[idx - self.start_id] = self.cur_txn[idx]
+ self.cur_txn_state = self.TXN_COMMITTED
+
+ self.prev_txn.append(self.cur_txn)
+ self.prev_txn.popleft()
+
+ def verify_txn(self, txn, committed):
+ request_id = txn[self.start_id]
+ if not committed:
+ # if the transaction was not committed, then there should be no rows
+ # in the table that have this request_id
+ cond = '='
+ # it is possible the start_id used to track this transaction is in
+ # the process of being deleted
+ if request_id < 0:
+ request_id = -request_id
+ else:
+ # if the transaction was committed, then no rows modified by this
+ # transaction should have a request_id less than this transaction's id
+ cond = '<'
+ stmt = ("SELECT COUNT(*) FROM %s WHERE id IN (%s) AND request_id %s %d" %
+ (self.table, ','.join(str(x) for x in txn), cond, request_id))
+ execute(self.cur, stmt)
+ if (self.cur.rowcount != 1):
+ raise TestError("Unable to retrieve results for query '%s'" % stmt)
+ count = self.cur.fetchone()[0]
+ if (count > 0):
+ raise TestError("Expected '%s' to return 0 rows, but %d returned "
+ "instead" % (stmt, count))
+ self.con.commit()
+
+ def verify_data(self):
+ # if the state of the current transaction is unknown (i.e. a commit was
+ # issued, but the connection failed before, check the start_id row to
+ # determine if it was committed
+ request_id = self.cur_txn[self.start_id]
+ if self.cur_txn_state == self.TXN_COMMIT_STARTED:
+ assert request_id >= 0
+ idx = self.start_id
+ stmt = "SELECT id, request_id FROM %s where id = %d" % (self.table, idx)
+ execute(self.cur, stmt)
+ if (self.cur.rowcount == 0):
+ raise TestError("Fetching start_id %d via '%s' returned no data! "
+ "This row should never be deleted!" % (idx, stmt))
+ REQUEST_ID_COL = 1
+ res = self.cur.fetchone()
+ if res[REQUEST_ID_COL] == self.cur_txn[idx]:
+ self.apply_cur_txn_changes()
+ else:
+ self.cur_txn_state = self.TXN_UNCOMMITTED
+ self.con.commit()
+
+ # if the transaction was not committed, verify there are no rows at this
+ # request id
+ #
+ # however, if the transaction was committed, then verify none of the rows
+ # have a request_id below the request_id recorded by the start_id row.
+ if self.cur_txn_state == self.TXN_UNCOMMITTED:
+ self.verify_txn(self.cur_txn, False)
+
+ # verify all committed transactions
+ for txn in self.prev_txn:
+ self.verify_txn(txn, True)
+
+ # verify the rows owned by this worker matches the request_id at which
+ # they were set.
+ idx = self.start_id
+ max_map_id = self.start_id + self.num_id
+ row_count = 0
+ ID_COL = 0
+ REQ_ID_COL = ID_COL + 1
+
+ while idx < max_map_id:
+ if (row_count == 0):
+ num_rows_to_check = random.randint(50, 100)
+ execute(self.cur,
+ "SELECT id, request_id FROM %s where id >= %d and id < %d "
+ "ORDER BY id LIMIT %d"
+ % (self.table, idx, max_map_id, num_rows_to_check))
+
+ # prevent future queries from being issued since we've hit the end of
+ # the rows that exist in the table
+ row_count = self.cur.rowcount if self.cur.rowcount != 0 else -1
+
+ # determine the id of the next available row in the table
+ if (row_count > 0):
+ res = self.cur.fetchone()
+ assert idx <= res[ID_COL]
+ next_id = res[ID_COL]
+ row_count -= 1
+ else:
+ next_id = max_map_id
+
+ # rows up to the next id don't exist within the table, verify our
+ # map has them as removed
+ while idx < next_id:
+ # see if the latest transaction may have modified this id. If so, use
+ # that value.
+ if self.id_map[idx - self.start_id] >= 0:
+ raise ValidateError("Row id %d was not found in table, but "
+ "id_map has it at request_id %d" %
+ (idx, self.id_map[idx - self.start_id]))
+ idx += 1
+
+ if idx == max_map_id:
+ break
+
+ if (self.id_map[idx - self.start_id] != res[REQ_ID_COL]):
+ raise ValidateError("Row id %d has req id %d, but %d is the "
+ "expected value!" %
+ (idx, res[REQ_ID_COL],
+ self.id_map[idx - self.start_id]))
+ idx += 1
+
+ self.con.commit()
+ logging.debug("Verified data successfully")
+
+ def execute_one(self):
+ # select a number of rows; perform an insert; update or delete operation on
+ # them
+ num_rows = random.randint(1, MAX_ROWS_PER_REQ)
+ ids = array.array('L')
+
+ # allocate at least one row in the id space owned by this worker
+ idx = random.randint(self.start_id, self.start_id + self.num_id - 1)
+ ids.append(idx)
+
+ for i in xrange(1, num_rows):
+ # The valid ranges for ids is from start_id to start_id + num_id and from
+ # start_share_id to max_id. The randint() uses the range from
+ # start_share_id to max_id + num_id - 1. start_share_id to max_id covers
+ # the shared range. The exclusive range is covered by max_id to max_id +
+ # num_id - 1. If any number lands in this >= max_id section, it is
+ # remapped to start_id and used for selecting a row in the exclusive
+ # section.
+ idx = random.randint(self.start_share_id, self.max_id + self.num_id - 1)
+ if idx >= self.max_id:
+ idx -= self.max_id - self.start_id
+ if ids.count(idx) == 0:
+ ids.append(idx)
+
+ # perform a read of these rows
+ ID_COL = 0
+ ZERO_SUM_COL = ID_COL + 1
+
+ # For repeatable-read isolation levels on MyRocks, during the lock
+ # acquisition part of this transaction, it is possible the selected rows
+ # conflict with another thread's transaction. This results in a deadlock
+ # error that requires the whole transaction to be rolled back because the
+ # transaction's current snapshot will always be reading an older version of
+ # the row. MyRocks will prevent any updates to this row until the
+ # snapshot is released and re-acquired.
+ NUM_RETRIES = 100
+ for i in xrange(NUM_RETRIES):
+ ids_found = {}
+ try:
+ for idx in ids:
+ stmt = ("SELECT id, zero_sum FROM %s WHERE id = %d "
+ "FOR UPDATE" % (self.table, idx))
+ execute(self.cur, stmt)
+ if self.cur.rowcount > 0:
+ res = self.cur.fetchone()
+ ids_found[res[ID_COL]] = res[ZERO_SUM_COL]
+ break
+ except MySQLdb.OperationalError, e:
+ if not is_deadlock_error(e):
+ raise e
+
+ # if a deadlock occurred, rollback the transaction and wait a short time
+ # before retrying.
+ logging.debug("%s generated deadlock, retry %d of %d" %
+ (stmt, i, NUM_RETRIES))
+ self.con.rollback()
+ time.sleep(0.2)
+
+ if i == NUM_RETRIES - 1:
+ raise TestError("Unable to acquire locks after a number of retries "
+ "for query '%s'" % stmt)
+
+ # ensure that the zero_sum column remains summed up to zero at the
+ # end of this operation
+ current_sum = 0
+
+ # all row locks acquired at this point, so allocate a request_id
+ request_id = get_next_request_id()
+ self.cur_txn = {self.start_id:request_id}
+ self.cur_txn_state = self.TXN_UNCOMMITTED
+
+ for idx in ids:
+ stmt = None
+ zero_sum = self.rand.randint(-1000, 1000)
+ action = self.rand.randint(0, 3)
+ is_delete = False
+
+ if idx in ids_found:
+ # for each row found, determine if it should be updated or deleted
+ if action == 0:
+ stmt = gen_delete(self.table, idx)
+ is_delete = True
+ current_sum -= ids_found[idx]
+ else:
+ stmt = gen_update(self.table, idx, self.thread_id, request_id,
+ zero_sum)
+ current_sum += zero_sum
+ else:
+ # if it does not exist, then determine if an insert should happen
+ if action <= 1:
+ stmt = gen_insert(self.table, idx, self.thread_id, request_id,
+ zero_sum)
+ current_sum += zero_sum
+
+ if stmt is not None:
+ # mark in self.cur_txn what these new changes will be
+ if is_delete:
+ self.cur_txn[idx] = -request_id
+ else:
+ self.cur_txn[idx] = request_id
+ execute(self.cur, stmt)
+ if self.cur.rowcount == 0:
+ raise TestError("Executing %s returned row count of 0!" % stmt)
+
+ # the start_id row is used to determine if this transaction has been
+ # committed if the connect fails and it is used to adjust the zero_sum
+ # correctly
+ idx = self.start_id
+ ids.append(idx)
+ self.cur_txn[idx] = request_id
+ stmt = gen_insert_on_dup(self.table, idx, self.thread_id, request_id,
+ -current_sum)
+ execute(self.cur, stmt)
+ if self.cur.rowcount == 0:
+ raise TestError("Executing '%s' returned row count of 0!" % stmt)
+
+ # 90% commit, 10% rollback
+ if roll_d100(90):
+ self.con.rollback()
+ logging.debug("request %s was rolled back" % request_id)
+ else:
+ self.cur_txn_state = self.TXN_COMMIT_STARTED
+ self.con.commit()
+ check_id(self.con.insert_id())
+ if not self.con.get_server_info():
+ raise MySQLdb.OperationalError(MySQLdb.constants.CR.CONNECTION_ERROR,
+ "Possible connection error on commit")
+ self.apply_cur_txn_changes()
+
+ self.loop_num += 1
+ if self.loop_num % 1000 == 0:
+ logging.info("Processed %d transactions so far" % self.loop_num)
+
+ def runme(self):
+ global TEST_STOP, LOADERS_READY
+
+ self.start_time = time.time()
+ if self.reconnect():
+ raise Exception("Unable to connect to MySQL server")
+
+ self.populate_id_map()
+ self.verify_data()
+
+ logging.info("Starting load generator")
+ reconnected = False
+ LOADERS_READY += 1
+
+ while self.loop_num < self.num_requests and not TEST_STOP:
+ try:
+ # verify our data on each reconnect and also on ocassion
+ if reconnected or random.randint(1, 500) == 1:
+ self.verify_data()
+ reconnected = False
+
+ self.execute_one()
+ self.loop_num += 1
+ except MySQLdb.OperationalError, e:
+ if not is_connection_error(e):
+ raise e
+ if self.reconnect():
+ raise Exception("Unable to connect to MySQL server")
+ reconnected = True
+ return
+
+# the checker thread is running read only transactions to verify the row
+# checksums match the message.
+class CheckerWorker(WorkerThread):
+ def __init__(self, thread_id):
+ WorkerThread.__init__(self, 'checker-%02d' % thread_id)
+ self.thread_id = thread_id
+ self.rand = random.Random()
+ self.rand.seed(thread_id)
+ self.max_id = OPTIONS.max_id
+ self.table = OPTIONS.table
+ self.loop_num = 0
+ self.start()
+
+ def finish(self):
+ logging.info("total loops: %d" % self.loop_num)
+
+ def check_zerosum(self):
+ # two methods for checking zero sum
+ # 1. request the server to do it (90% of the time for now)
+ # 2. read all rows and calculate directly
+ if roll_d100(90):
+ stmt = "SELECT SUM(zero_sum) FROM %s" % self.table
+ if roll_d100(50):
+ stmt += " FORCE INDEX(msg_i)"
+ execute(self.cur, stmt)
+
+ if self.cur.rowcount != 1:
+ raise ValidateError("Error with query '%s'" % stmt)
+ res = self.cur.fetchone()[0]
+ if res != 0:
+ raise ValidateError("Expected zero_sum to be 0, but %d returned "
+ "instead" % res)
+ else:
+ cur_isolation_level = self.get_isolation_level()
+ self.set_isolation_level('REPEATABLE-READ')
+ num_rows_to_check = random.randint(500, 1000)
+ idx = 0
+ sum = 0
+
+ stmt = "SELECT id, zero_sum FROM %s where id >= %d ORDER BY id LIMIT %d"
+ ID_COL = 0
+ ZERO_SUM_COL = 1
+
+ while idx < self.max_id:
+ execute(self.cur, stmt % (self.table, idx, num_rows_to_check))
+ if self.cur.rowcount == 0:
+ break
+
+ for i in xrange(self.cur.rowcount - 1):
+ sum += self.cur.fetchone()[ZERO_SUM_COL]
+
+ last_row = self.cur.fetchone()
+ idx = last_row[ID_COL] + 1
+ sum += last_row[ZERO_SUM_COL]
+
+ if sum != 0:
+ raise TestError("Zero sum column expected to total 0, but sum is %d "
+ "instead!" % sum)
+ self.set_isolation_level(cur_isolation_level)
+
+ def check_rows(self):
+ class id_range():
+ def __init__(self, min_id, min_inclusive, max_id, max_inclusive):
+ self.min_id = min_id if min_inclusive else min_id + 1
+ self.max_id = max_id if max_inclusive else max_id - 1
+ def count(self, idx):
+ return idx >= self.min_id and idx <= self.max_id
+
+ stmt = ("SELECT id, msg, msg_length, msg_checksum FROM %s WHERE " %
+ self.table)
+
+ # two methods for checking rows
+ # 1. pick a number of rows at random
+ # 2. range scan
+ if roll_d100(90):
+ ids = []
+ for i in xrange(random.randint(1, MAX_ROWS_PER_REQ)):
+ ids.append(random.randint(0, self.max_id - 1))
+ stmt += "id in (%s)" % ','.join(str(x) for x in ids)
+ else:
+ id1 = random.randint(0, self.max_id - 1)
+ id2 = random.randint(0, self.max_id - 1)
+ min_inclusive = random.randint(0, 1)
+ cond1 = '>=' if min_inclusive else '>'
+ max_inclusive = random.randint(0, 1)
+ cond2 = '<=' if max_inclusive else '<'
+ stmt += ("id %s %d AND id %s %d" %
+ (cond1, min(id1, id2), cond2, max(id1, id2)))
+ ids = id_range(min(id1, id2), min_inclusive, max(id1, id2), max_inclusive)
+
+ execute(self.cur, stmt)
+
+ ID_COL = 0
+ MSG_COL = ID_COL + 1
+ MSG_LENGTH_COL = MSG_COL + 1
+ MSG_CHECKSUM_COL = MSG_LENGTH_COL + 1
+
+ for row in self.cur.fetchall():
+ idx = row[ID_COL]
+ msg = row[MSG_COL]
+ msg_length = row[MSG_LENGTH_COL]
+ msg_checksum = row[MSG_CHECKSUM_COL]
+ if ids.count(idx) < 1:
+ raise ValidateError(
+ "id %d returned from database, but query was '%s'" % (idx, stmt))
+ if (len(msg) != msg_length):
+ raise ValidateError(
+ "id %d contains msg_length %d, but msg '%s' is only %d "
+ "characters long" % (idx, msg_length, msg, len(msg)))
+ if (sha1(msg) != msg_checksum):
+ raise ValidateError("id %d has checksum '%s', but expected checksum "
+ "is '%s'" % (idx, msg_checksum, sha1(msg)))
+
+ def runme(self):
+ global TEST_STOP
+
+ self.start_time = time.time()
+ if self.reconnect():
+ raise Exception("Unable to connect to MySQL server")
+ logging.info("Starting checker")
+
+ while not TEST_STOP:
+ try:
+ # choose one of three options:
+ # 1. compute zero_sum across all rows is 0
+ # 2. read a number of rows and verify checksums
+ if roll_d100(25):
+ self.check_zerosum()
+ else:
+ self.check_rows()
+
+ self.con.commit()
+ self.loop_num += 1
+ if self.loop_num % 10000 == 0:
+ logging.info("Processed %d transactions so far" % self.loop_num)
+ except MySQLdb.OperationalError, e:
+ if not is_connection_error(e):
+ raise e
+ if self.reconnect():
+ raise Exception("Unable to reconnect to MySQL server")
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Concurrent load generator.')
+
+ parser.add_argument('-C, --committed-txns', dest='committed_txns',
+ default=3, type=int,
+ help="number of committed txns to verify")
+
+ parser.add_argument('-c, --num-checkers', dest='num_checkers', type=int,
+ default=4,
+ help="number of reader/checker threads to test with")
+
+ parser.add_argument('-d, --db', dest='db', default='test',
+ help="mysqld server database to test with")
+
+ parser.add_argument('-H, --host', dest='host', default='127.0.0.1',
+ help="mysqld server host ip address")
+
+ parser.add_argument('-i, --ids-per-loader', dest='ids_per_loader',
+ type=int, default=100,
+ help="number of records which each loader owns "
+ "exclusively, up to max-id / 2 / num-loaders")
+
+ parser.add_argument('-L, --log-file', dest='log_file', default=None,
+ help="log file for output")
+
+ parser.add_argument('-l, --num-loaders', dest='num_loaders', type=int,
+ default=16,
+ help="number of loader threads to test with")
+
+ parser.add_argument('-m, --max-id', dest='max_id', type=int, default=1000,
+ help="maximum number of records which the table "
+ "extends to, must be larger than ids_per_loader * "
+ "num_loaders")
+
+ parser.add_argument('-n, --num-records', dest='num_records', type=int,
+ default=0,
+ help="number of records to populate the table with")
+
+ parser.add_argument('-P, --port', dest='port', default=3307, type=int,
+ help='mysqld server host port')
+
+ parser.add_argument('-r, --num-requests', dest='num_requests', type=int,
+ default=100000000,
+ help="number of requests issued per worker thread")
+
+ parser.add_argument('-T, --truncate', dest='truncate', action='store_true',
+ help="truncates or creates the table before the test")
+
+ parser.add_argument('-t, --table', dest='table', default='t1',
+ help="mysqld server table to test with")
+
+ parser.add_argument('-u, --user', dest='user', default='root',
+ help="user to log into the mysql server")
+
+ parser.add_argument('-v, --verbose', dest='verbose', action='store_true',
+ help="enable debug logging")
+
+ parser.add_argument('-E, --expect-file', dest='expect_file', default=None,
+ help="expect file for server restart")
+
+ parser.add_argument('-D, --reap-delay', dest='reap_delay', type=int,
+ default=0,
+ help="seconds to sleep after each server reap")
+
+ OPTIONS = parser.parse_args()
+
+ if OPTIONS.verbose:
+ log_level = logging.DEBUG
+ else:
+ log_level = logging.INFO
+
+ logging.basicConfig(level=log_level,
+ format='%(asctime)s %(threadName)s [%(levelname)s] '
+ '%(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S',
+ filename=OPTIONS.log_file)
+
+ logging.info("Command line given: %s" % ' '.join(sys.argv))
+
+ if (OPTIONS.max_id < 0 or OPTIONS.ids_per_loader <= 0 or
+ OPTIONS.max_id < OPTIONS.ids_per_loader * OPTIONS.num_loaders):
+ logging.error("ids-per-loader must be larger tha 0 and max-id must be "
+ "larger than ids_per_loader * num_loaders")
+ exit(1)
+
+ logging.info("Using table %s.%s for test" % (OPTIONS.db, OPTIONS.table))
+
+ if OPTIONS.truncate:
+ logging.info("Truncating table")
+ con = MySQLdb.connect(user=OPTIONS.user, host=OPTIONS.host,
+ port=OPTIONS.port, db=OPTIONS.db)
+ if not con:
+ raise TestError("Unable to connect to mysqld server to create/truncate "
+ "table")
+ cur = con.cursor()
+ cur.execute("SELECT COUNT(*) FROM INFORMATION_SCHEMA.tables WHERE "
+ "table_schema = '%s' AND table_name = '%s'" %
+ (OPTIONS.db, OPTIONS.table))
+ if cur.rowcount != 1:
+ logging.error("Unable to retrieve information about table %s "
+ "from information_schema!" % OPTIONS.table)
+ exit(1)
+
+ if cur.fetchone()[0] == 0:
+ logging.info("Table %s not found, creating a new one" % OPTIONS.table)
+ cur.execute("CREATE TABLE %s (id INT PRIMARY KEY, "
+ "thread_id INT NOT NULL, "
+ "request_id BIGINT UNSIGNED NOT NULL, "
+ "update_count INT UNSIGNED NOT NULL DEFAULT 0, "
+ "zero_sum INT DEFAULT 0, "
+ "msg VARCHAR(1024), "
+ "msg_length int, "
+ "msg_checksum varchar(128), "
+ "KEY msg_i(msg(255), zero_sum)) "
+ "ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin" %
+ OPTIONS.table)
+ else:
+ logging.info("Table %s found, truncating" % OPTIONS.table)
+ cur.execute("TRUNCATE TABLE %s" % OPTIONS.table)
+ con.commit()
+
+ if populate_table(OPTIONS.num_records):
+ logging.error("Populate table returned an error")
+ exit(1)
+
+ logging.info("Starting %d loaders" % OPTIONS.num_loaders)
+ loaders = []
+ for i in xrange(OPTIONS.num_loaders):
+ loaders.append(LoadGenWorker(i))
+
+ logging.info("Starting %d checkers" % OPTIONS.num_checkers)
+ checkers = []
+ for i in xrange(OPTIONS.num_checkers):
+ checkers.append(CheckerWorker(i))
+
+ while LOADERS_READY < OPTIONS.num_loaders:
+ time.sleep(0.5)
+
+ if OPTIONS.expect_file and OPTIONS.reap_delay > 0:
+ logging.info('Starting reaper')
+ checkers.append(ReaperWorker())
+
+ workers_failed = 0
+ workers_failed += wait_for_workers(loaders, len(checkers))
+
+ if TEST_STOP:
+ logging.error("Detected test failure, aborting")
+ os._exit(1)
+
+ TEST_STOP = True
+
+ workers_failed += wait_for_workers(checkers)
+
+ if workers_failed > 0:
+ logging.error("Test detected %d failures, aborting" % workers_failed)
+ sys.exit(1)
+
+ logging.info("Test completed successfully")
+ sys.exit(0)
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress.test b/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress.test
new file mode 100644
index 00000000..e48b0c47
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress.test
@@ -0,0 +1,33 @@
+# basic stress tests for myrocks, just runs the load generator without any crashes
+
+# Don't test this under valgrind, memory leaks will occur
+--disable_warnings
+--source include/not_valgrind.inc
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# create the actual table
+CREATE TABLE t1(id INT PRIMARY KEY,
+ thread_id INT NOT NULL,
+ request_id BIGINT UNSIGNED NOT NULL,
+ update_count INT UNSIGNED NOT NULL DEFAULT 0,
+ zero_sum INT DEFAULT 0,
+ msg VARCHAR(1024),
+ msg_length int,
+ msg_checksum varchar(128),
+ auto_inc BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ KEY(auto_inc),
+ KEY msg_i(msg(255), zero_sum))
+ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+
+--let $table = t1
+--let $num_loaders = 8
+--let $num_checkers = 2
+--let $num_records = 200
+--let $max_records = 100000
+--let $num_requests = 10000
+--let $reap_delay = 0
+
+--source include/rocksdb_stress.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress_crash.test b/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress_crash.test
new file mode 100644
index 00000000..3da3dc7b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_stress/t/rocksdb_stress_crash.test
@@ -0,0 +1,34 @@
+# basic stress tests for myrocks, runs the load generator with periodic crashes
+
+# Don't test this under valgrind, memory leaks will occur
+--disable_warnings
+--source include/not_valgrind.inc
+--source include/have_rocksdb.inc
+--source include/master-slave.inc
+--source include/have_binlog_format_row.inc
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+# create the actual table
+CREATE TABLE t1(id INT PRIMARY KEY,
+ thread_id INT NOT NULL,
+ request_id BIGINT UNSIGNED NOT NULL,
+ update_count INT UNSIGNED NOT NULL DEFAULT 0,
+ zero_sum INT DEFAULT 0,
+ msg VARCHAR(1024),
+ msg_length int,
+ msg_checksum varchar(128),
+ auto_inc BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ KEY(auto_inc),
+ KEY msg_i(msg(255), zero_sum))
+ENGINE=RocksDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+
+--let $table = t1
+--let $num_loaders = 8
+--let $num_checkers = 2
+--let $num_records = 200
+--let $max_records = 100000
+--let $num_requests = 10000
+--let $reap_delay = 180
+
+--source include/rocksdb_stress.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/correctboolvalue.inc b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/correctboolvalue.inc
new file mode 100644
index 00000000..f675aec1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/correctboolvalue.inc
@@ -0,0 +1,25 @@
+##
+# $input the value of a boolean type
+# $output the value of int type
+##
+--let $int_value=$value
+if ($value==on)
+{
+ --let $int_value=1
+}
+
+if ($value==off)
+{
+ --let $int_value=0
+}
+
+# MySQL allows 'true' and 'false' on bool values
+if ($value==true)
+{
+ --let $int_value=1
+}
+
+if ($value==false)
+{
+ --let $int_value=0
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.inc b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.inc
new file mode 100644
index 00000000..1f762d38
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.inc
@@ -0,0 +1,10 @@
+if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'rocksdb' AND support IN ('YES', 'DEFAULT', 'ENABLED')`)
+{
+ --skip Test requires engine RocksDB.
+}
+
+--disable_query_log
+# Table statistics can vary depending on when the memtables are flushed, so
+# flush them at the beginning of the test to ensure the test runs consistently.
+set global rocksdb_force_flush_memtable_now = true;
+--enable_query_log
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.opt b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.opt
new file mode 100644
index 00000000..36d7dda1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/have_rocksdb.opt
@@ -0,0 +1,12 @@
+--loose-enable-rocksdb
+--loose-enable-rocksdb_global_info
+--loose-enable-rocksdb_ddl
+--loose-enable-rocksdb_cf_options
+--loose-enable_rocksdb_perf_context
+--loose-enable_rocksdb_perf_context_global
+--loose-enable-rocksdb_index_file_map
+--loose-enable-rocksdb_dbstats
+--loose-enable-rocksdb_cfstats
+--loose-enable-rocksdb_lock_info
+--loose-enable-rocksdb_trx
+--loose-enable-rocksdb_locks
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/rocksdb_sys_var.inc b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/rocksdb_sys_var.inc
new file mode 100644
index 00000000..db0abc57
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/include/rocksdb_sys_var.inc
@@ -0,0 +1,124 @@
+##
+# $sys_var name of the variable
+# $read_only - true if read-only
+# $session - true if this is session, false if global-only
+# $suppress_default_value - if true, don't check the default value
+# valid_values table should contain valid values
+# invalid_values
+##
+
+--eval SET @start_global_value = @@global.$sys_var
+if (!$suppress_default_value)
+{
+ --replace_regex /[a-f0-9]{40}/#/
+ SELECT @start_global_value;
+ if ($session)
+ {
+ --eval SET @start_session_value = @@session.$sys_var
+ SELECT @start_session_value;
+ }
+}
+
+if (!$read_only)
+{
+ --echo '# Setting to valid values in global scope#'
+
+ --let $i=1
+ --let $value=query_get_value(select value from valid_values, value, $i)
+ while ($value != 'No such row')
+ {
+ --echo "Trying to set variable @@global.$sys_var to $value"
+ --eval SET @@global.$sys_var = $value
+ --eval SELECT @@global.$sys_var
+ --let $v=`SELECT @@global.$sys_var`
+ --source include/correctboolvalue.inc
+ if (!$sticky)
+ {
+ if ($v != $int_value)
+ {
+ --echo Set @@global.$sys_var to $value but it remained set to $v
+ --die Wrong variable value
+ }
+ }
+
+ --echo "Setting the global scope variable back to default"
+ --eval SET @@global.$sys_var = DEFAULT
+ --eval SELECT @@global.$sys_var
+
+ --inc $i
+ --let $value=query_get_value(select value from valid_values, value, $i)
+ }
+
+ if ($session)
+ {
+ --echo '# Setting to valid values in session scope#'
+
+ --let $i=1
+ --let $value=query_get_value(select value from valid_values, value, $i)
+ while ($value != 'No such row')
+ {
+ --echo "Trying to set variable @@session.$sys_var to $value"
+ --eval SET @@session.$sys_var = $value
+ --eval SELECT @@session.$sys_var
+ --let $v=`SELECT @@session.$sys_var`
+ --source include/correctboolvalue.inc
+ if (!$sticky)
+ {
+ if ($v != $int_value)
+ {
+ --echo Set @@session.$sys_var to $value but it remained set to $v
+ --die Wrong variable value
+ }
+ }
+ --echo "Setting the session scope variable back to default"
+ --eval SET @@session.$sys_var = DEFAULT
+ --eval SELECT @@session.$sys_var
+
+ --inc $i
+ --let $value=query_get_value(select value from valid_values, value, $i)
+ }
+ }
+ if (!$session)
+ {
+ --echo "Trying to set variable @@session.$sys_var to 444. It should fail because it is not session."
+ --Error ER_GLOBAL_VARIABLE
+ --eval SET @@session.$sys_var = 444
+ }
+
+ --echo '# Testing with invalid values in global scope #'
+ ####################################################################
+ # Change the value of query_prealloc_size to an invalid value #
+ ####################################################################
+ --let $i=1
+ --let $value=query_get_value(select value from invalid_values, value, $i)
+ while ($value != 'No such row')
+ {
+ --echo "Trying to set variable @@global.$sys_var to $value"
+ --Error ER_WRONG_VALUE_FOR_VAR, ER_WRONG_TYPE_FOR_VAR
+ --eval SET @@global.$sys_var = $value
+ --eval SELECT @@global.$sys_var
+ --inc $i
+ --let $value=query_get_value(select value from invalid_values, value, $i)
+ }
+}
+
+if ($read_only)
+{
+ --echo "Trying to set variable @@global.$sys_var to 444. It should fail because it is readonly."
+ --Error ER_INCORRECT_GLOBAL_LOCAL_VAR
+ --eval SET @@global.$sys_var = 444
+}
+
+####################################
+# Restore initial value #
+####################################
+if (!$read_only)
+{
+ --eval SET @@global.$sys_var = @start_global_value
+ --eval SELECT @@global.$sys_var
+ if ($session)
+ {
+ --eval SET @@session.$sys_var = @start_session_value
+ --eval SELECT @@session.$sys_var
+ }
+}
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/my.cnf b/storage/rocksdb/mysql-test/rocksdb_sys_vars/my.cnf
new file mode 100644
index 00000000..f37bb45b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/my.cnf
@@ -0,0 +1,10 @@
+!include include/default_my.cnf
+
+[server]
+skip-innodb
+default-storage-engine=rocksdb
+
+sql-mode=NO_ENGINE_SUBSTITUTION
+explicit-defaults-for-timestamp=1
+loose-rocksdb_lock_wait_timeout=1
+loose-rocksdb_force_compute_memtable_stats_cachetime=0
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/all_vars.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/all_vars.result
new file mode 100644
index 00000000..159d6a98
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/all_vars.result
@@ -0,0 +1,13 @@
+create table t1 (test_name text) engine=MyISAM;
+create table t2 (variable_name text) engine=MyISAM;
+load data infile "MYSQLTEST_VARDIR/tmp/rocksdb_sys_vars.all_vars.txt" into table t1;
+insert into t2 select variable_name from information_schema.global_variables where variable_name like "rocksdb_%";
+insert into t2 select variable_name from information_schema.session_variables where variable_name like "rocksdb_%";
+select variable_name as `There should be *no* long test name listed below:` from t2
+where length(variable_name) > 50;
+There should be *no* long test name listed below:
+select variable_name as `There should be *no* variables listed below:` from t2
+left join t1 on variable_name=test_name where test_name is null ORDER BY variable_name;
+There should be *no* variables listed below:
+drop table t1;
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_access_hint_on_compaction_start_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_access_hint_on_compaction_start_basic.result
new file mode 100644
index 00000000..4398563d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_access_hint_on_compaction_start_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ACCESS_HINT_ON_COMPACTION_START;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_ACCESS_HINT_ON_COMPACTION_START to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ACCESS_HINT_ON_COMPACTION_START = 444;
+ERROR HY000: Variable 'rocksdb_access_hint_on_compaction_start' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_advise_random_on_open_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_advise_random_on_open_basic.result
new file mode 100644
index 00000000..f7175fd9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_advise_random_on_open_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ADVISE_RANDOM_ON_OPEN;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_ADVISE_RANDOM_ON_OPEN to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ADVISE_RANDOM_ON_OPEN = 444;
+ERROR HY000: Variable 'rocksdb_advise_random_on_open' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_concurrent_memtable_write_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_concurrent_memtable_write_basic.result
new file mode 100644
index 00000000..3b174fbb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_concurrent_memtable_write_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ALLOW_CONCURRENT_MEMTABLE_WRITE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ALLOW_CONCURRENT_MEMTABLE_WRITE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ALLOW_CONCURRENT_MEMTABLE_WRITE = 444;
+ERROR HY000: Variable 'rocksdb_allow_concurrent_memtable_write' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_reads_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_reads_basic.result
new file mode 100644
index 00000000..f0f1b077
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_reads_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ALLOW_MMAP_READS;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ALLOW_MMAP_READS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ALLOW_MMAP_READS = 444;
+ERROR HY000: Variable 'rocksdb_allow_mmap_reads' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_writes_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_writes_basic.result
new file mode 100644
index 00000000..3fa1f14e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_mmap_writes_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ALLOW_MMAP_WRITES;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ALLOW_MMAP_WRITES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ALLOW_MMAP_WRITES = 444;
+ERROR HY000: Variable 'rocksdb_allow_mmap_writes' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_to_start_after_corruption_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_to_start_after_corruption_basic.result
new file mode 100644
index 00000000..086010dc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_allow_to_start_after_corruption_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ALLOW_TO_START_AFTER_CORRUPTION;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ALLOW_TO_START_AFTER_CORRUPTION to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ALLOW_TO_START_AFTER_CORRUPTION = 444;
+ERROR HY000: Variable 'rocksdb_allow_to_start_after_corruption' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_blind_delete_primary_key_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_blind_delete_primary_key_basic.result
new file mode 100644
index 00000000..805ed233
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_blind_delete_primary_key_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 1"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 1;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Trying to set variable @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 0"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 0;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Trying to set variable @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to on"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = on;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 1"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 1;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Trying to set variable @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 0"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 0;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Trying to set variable @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to on"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = on;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = DEFAULT;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 'aaa'"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+"Trying to set variable @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY to 'bbb'"
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+SET @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = @start_global_value;
+SELECT @@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@global.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+SET @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY = @start_session_value;
+SELECT @@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY;
+@@session.ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_cache_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_cache_size_basic.result
new file mode 100644
index 00000000..b319c39a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_cache_size_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(65536);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(1*1024*1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+SELECT @start_global_value;
+@start_global_value
+536870912
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to 65536"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = 65536;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+65536
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to 1024"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = 1024;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to 1048576"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = 1048576;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+1048576
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@session.ROCKSDB_BLOCK_CACHE_SIZE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_BLOCK_CACHE_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_block_cache_size' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to 'aaa'"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to 'bbb'"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to '-1'"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to '101'"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+"Trying to set variable @@global.ROCKSDB_BLOCK_CACHE_SIZE to '484436'"
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+SET @@global.ROCKSDB_BLOCK_CACHE_SIZE = @start_global_value;
+SELECT @@global.ROCKSDB_BLOCK_CACHE_SIZE;
+@@global.ROCKSDB_BLOCK_CACHE_SIZE
+536870912
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_restart_interval_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_restart_interval_basic.result
new file mode 100644
index 00000000..4d02e197
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_restart_interval_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_BLOCK_RESTART_INTERVAL;
+SELECT @start_global_value;
+@start_global_value
+16
+"Trying to set variable @@global.ROCKSDB_BLOCK_RESTART_INTERVAL to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_BLOCK_RESTART_INTERVAL = 444;
+ERROR HY000: Variable 'rocksdb_block_restart_interval' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_basic.result
new file mode 100644
index 00000000..0382184f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_BLOCK_SIZE;
+SELECT @start_global_value;
+@start_global_value
+4096
+"Trying to set variable @@global.ROCKSDB_BLOCK_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_BLOCK_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_block_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_deviation_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_deviation_basic.result
new file mode 100644
index 00000000..83513f81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_block_size_deviation_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_BLOCK_SIZE_DEVIATION;
+SELECT @start_global_value;
+@start_global_value
+10
+"Trying to set variable @@global.ROCKSDB_BLOCK_SIZE_DEVIATION to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_BLOCK_SIZE_DEVIATION = 444;
+ERROR HY000: Variable 'rocksdb_block_size_deviation' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_sk_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_sk_basic.result
new file mode 100644
index 00000000..c0d09d89
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_sk_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_SK to 1"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = 1;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_SK to 0"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = 0;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_SK to on"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = on;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_SK to 1"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = 1;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_SK to 0"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = 0;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_SK to on"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = on;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_SK to 'aaa'"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_SK to 'bbb'"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_SK = @start_global_value;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_SK = @start_session_value;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_SK;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_SK
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_unsorted_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_unsorted_basic.result
new file mode 100644
index 00000000..a59ba561
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_allow_unsorted_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 1"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 1;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 0"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 0;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to on"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = on;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 1"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 1;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 0"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 0;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to on"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = on;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 'aaa'"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED to 'bbb'"
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+SET @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = @start_global_value;
+SELECT @@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@global.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+SET @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED = @start_session_value;
+SELECT @@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED;
+@@session.ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_basic.result
new file mode 100644
index 00000000..96b78cf6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_BULK_LOAD;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_BULK_LOAD;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD to 1"
+SET @@global.ROCKSDB_BULK_LOAD = 1;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD to 0"
+SET @@global.ROCKSDB_BULK_LOAD = 0;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD to on"
+SET @@global.ROCKSDB_BULK_LOAD = on;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD to 1"
+SET @@session.ROCKSDB_BULK_LOAD = 1;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD to 0"
+SET @@session.ROCKSDB_BULK_LOAD = 0;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+0
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD to on"
+SET @@session.ROCKSDB_BULK_LOAD = on;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD to 'aaa'"
+SET @@global.ROCKSDB_BULK_LOAD = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD to 'bbb'"
+SET @@global.ROCKSDB_BULK_LOAD = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+SET @@global.ROCKSDB_BULK_LOAD = @start_global_value;
+SELECT @@global.ROCKSDB_BULK_LOAD;
+@@global.ROCKSDB_BULK_LOAD
+0
+SET @@session.ROCKSDB_BULK_LOAD = @start_session_value;
+SELECT @@session.ROCKSDB_BULK_LOAD;
+@@session.ROCKSDB_BULK_LOAD
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_size_basic.result
new file mode 100644
index 00000000..40404d2f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bulk_load_size_basic.result
@@ -0,0 +1,72 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_BULK_LOAD_SIZE;
+SELECT @start_global_value;
+@start_global_value
+1000
+SET @start_session_value = @@session.ROCKSDB_BULK_LOAD_SIZE;
+SELECT @start_session_value;
+@start_session_value
+1000
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_SIZE to 1"
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = 1;
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1000
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_SIZE to 1024"
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = 1024;
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1000
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_SIZE to 1"
+SET @@session.ROCKSDB_BULK_LOAD_SIZE = 1;
+SELECT @@session.ROCKSDB_BULK_LOAD_SIZE;
+@@session.ROCKSDB_BULK_LOAD_SIZE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_SIZE = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_SIZE;
+@@session.ROCKSDB_BULK_LOAD_SIZE
+1000
+"Trying to set variable @@session.ROCKSDB_BULK_LOAD_SIZE to 1024"
+SET @@session.ROCKSDB_BULK_LOAD_SIZE = 1024;
+SELECT @@session.ROCKSDB_BULK_LOAD_SIZE;
+@@session.ROCKSDB_BULK_LOAD_SIZE
+1024
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_BULK_LOAD_SIZE = DEFAULT;
+SELECT @@session.ROCKSDB_BULK_LOAD_SIZE;
+@@session.ROCKSDB_BULK_LOAD_SIZE
+1000
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BULK_LOAD_SIZE to 'aaa'"
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1000
+SET @@global.ROCKSDB_BULK_LOAD_SIZE = @start_global_value;
+SELECT @@global.ROCKSDB_BULK_LOAD_SIZE;
+@@global.ROCKSDB_BULK_LOAD_SIZE
+1000
+SET @@session.ROCKSDB_BULK_LOAD_SIZE = @start_session_value;
+SELECT @@session.ROCKSDB_BULK_LOAD_SIZE;
+@@session.ROCKSDB_BULK_LOAD_SIZE
+1000
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bytes_per_sync_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bytes_per_sync_basic.result
new file mode 100644
index 00000000..9af4f730
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_bytes_per_sync_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_BYTES_PER_SYNC;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to 100"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = 100;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to 1"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = 1;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to 0"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = 0;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@session.ROCKSDB_BYTES_PER_SYNC to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_BYTES_PER_SYNC = 444;
+ERROR HY000: Variable 'rocksdb_bytes_per_sync' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to 'aaa'"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to 'bbb'"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to '-1'"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to '101'"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_BYTES_PER_SYNC to '484436'"
+SET @@global.ROCKSDB_BYTES_PER_SYNC = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+SET @@global.ROCKSDB_BYTES_PER_SYNC = @start_global_value;
+SELECT @@global.ROCKSDB_BYTES_PER_SYNC;
+@@global.ROCKSDB_BYTES_PER_SYNC
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_dump_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_dump_basic.result
new file mode 100644
index 00000000..90fc99ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_dump_basic.result
@@ -0,0 +1,19 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_CACHE_DUMP;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_CACHE_DUMP to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CACHE_DUMP = 444;
+ERROR HY000: Variable 'rocksdb_cache_dump' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_high_pri_pool_ratio_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_high_pri_pool_ratio_basic.result
new file mode 100644
index 00000000..9d098385
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_high_pri_pool_ratio_basic.result
@@ -0,0 +1,22 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1.0);
+INSERT INTO valid_values VALUES(0.0);
+INSERT INTO valid_values VALUES(0.5);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES(2.0);
+INSERT INTO invalid_values VALUES(-0.5);
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+INSERT INTO invalid_values VALUES('\'0.5\'');
+SET @start_global_value = @@global.ROCKSDB_CACHE_HIGH_PRI_POOL_RATIO;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_CACHE_HIGH_PRI_POOL_RATIO to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CACHE_HIGH_PRI_POOL_RATIO = 444;
+ERROR HY000: Variable 'rocksdb_cache_high_pri_pool_ratio' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_blocks_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_blocks_basic.result
new file mode 100644
index 00000000..12c25ad6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_blocks_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_BLOCKS;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_BLOCKS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_BLOCKS = 444;
+ERROR HY000: Variable 'rocksdb_cache_index_and_filter_blocks' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_with_high_priority_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_with_high_priority_basic.result
new file mode 100644
index 00000000..819425c8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_cache_index_and_filter_with_high_priority_basic.result
@@ -0,0 +1,19 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_WITH_HIGH_PRIORITY;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_WITH_HIGH_PRIORITY to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CACHE_INDEX_AND_FILTER_WITH_HIGH_PRIORITY = 444;
+ERROR HY000: Variable 'rocksdb_cache_index_and_filter_with_high_priority' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_checksums_pct_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_checksums_pct_basic.result
new file mode 100644
index 00000000..694c9a4f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_checksums_pct_basic.result
@@ -0,0 +1,93 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(99);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_CHECKSUMS_PCT;
+SELECT @start_global_value;
+@start_global_value
+100
+SET @start_session_value = @@session.ROCKSDB_CHECKSUMS_PCT;
+SELECT @start_session_value;
+@start_session_value
+100
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_CHECKSUMS_PCT to 0"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = 0;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+100
+"Trying to set variable @@global.ROCKSDB_CHECKSUMS_PCT to 1"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = 1;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+100
+"Trying to set variable @@global.ROCKSDB_CHECKSUMS_PCT to 99"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = 99;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+99
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+100
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_CHECKSUMS_PCT to 0"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = 0;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+100
+"Trying to set variable @@session.ROCKSDB_CHECKSUMS_PCT to 1"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = 1;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+100
+"Trying to set variable @@session.ROCKSDB_CHECKSUMS_PCT to 99"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = 99;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+99
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_CHECKSUMS_PCT = DEFAULT;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+100
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_CHECKSUMS_PCT to 'aaa'"
+SET @@global.ROCKSDB_CHECKSUMS_PCT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+100
+SET @@global.ROCKSDB_CHECKSUMS_PCT = @start_global_value;
+SELECT @@global.ROCKSDB_CHECKSUMS_PCT;
+@@global.ROCKSDB_CHECKSUMS_PCT
+100
+SET @@session.ROCKSDB_CHECKSUMS_PCT = @start_session_value;
+SELECT @@session.ROCKSDB_CHECKSUMS_PCT;
+@@session.ROCKSDB_CHECKSUMS_PCT
+100
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_collect_sst_properties_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_collect_sst_properties_basic.result
new file mode 100644
index 00000000..2f101987
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_collect_sst_properties_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_COLLECT_SST_PROPERTIES;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_COLLECT_SST_PROPERTIES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_COLLECT_SST_PROPERTIES = 444;
+ERROR HY000: Variable 'rocksdb_collect_sst_properties' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_in_the_middle_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_in_the_middle_basic.result
new file mode 100644
index 00000000..4664ccb2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_in_the_middle_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE to 1"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = 1;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Trying to set variable @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE to 0"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = 0;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Trying to set variable @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE to on"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = on;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE to 1"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = 1;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Trying to set variable @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE to 0"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = 0;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Trying to set variable @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE to on"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = on;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE to 'aaa'"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+"Trying to set variable @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE to 'bbb'"
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+SET @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE = @start_global_value;
+SELECT @@global.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@global.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+SET @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE = @start_session_value;
+SELECT @@session.ROCKSDB_COMMIT_IN_THE_MIDDLE;
+@@session.ROCKSDB_COMMIT_IN_THE_MIDDLE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_time_batch_for_recovery_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_time_batch_for_recovery_basic.result
new file mode 100644
index 00000000..8d3f4a6e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_commit_time_batch_for_recovery_basic.result
@@ -0,0 +1,121 @@
+CREATE TABLE valid_values (value varchar(255));
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255));
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+SELECT @start_global_value;
+@start_global_value
+1
+SET @start_session_value = @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+SELECT @start_session_value;
+@start_session_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 1"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 1;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 0"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 0;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to on"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = on;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to off"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = off;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 1"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 1;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 0"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 0;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to on"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = on;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to off"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = off;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = DEFAULT;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 'aaa'"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+"Trying to set variable @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY to 'bbb'"
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+SET @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = @start_global_value;
+SELECT @@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@global.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+SET @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY = @start_session_value;
+SELECT @@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY;
+@@session.ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compact_cf_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compact_cf_basic.result
new file mode 100644
index 00000000..b65ef65c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compact_cf_basic.result
@@ -0,0 +1,40 @@
+call mtr.add_suppression(" Column family '[a-z]*' not found.");
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('abc');
+INSERT INTO valid_values VALUES('def');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+SET @start_global_value = @@global.ROCKSDB_COMPACT_CF;
+SELECT @start_global_value;
+@start_global_value
+
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACT_CF to abc"
+SET @@global.ROCKSDB_COMPACT_CF = abc;
+SELECT @@global.ROCKSDB_COMPACT_CF;
+@@global.ROCKSDB_COMPACT_CF
+
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACT_CF = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACT_CF;
+@@global.ROCKSDB_COMPACT_CF
+
+"Trying to set variable @@global.ROCKSDB_COMPACT_CF to def"
+SET @@global.ROCKSDB_COMPACT_CF = def;
+SELECT @@global.ROCKSDB_COMPACT_CF;
+@@global.ROCKSDB_COMPACT_CF
+
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACT_CF = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACT_CF;
+@@global.ROCKSDB_COMPACT_CF
+
+"Trying to set variable @@session.ROCKSDB_COMPACT_CF to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACT_CF = 444;
+ERROR HY000: Variable 'rocksdb_compact_cf' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+SET @@global.ROCKSDB_COMPACT_CF = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACT_CF;
+@@global.ROCKSDB_COMPACT_CF
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_readahead_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_readahead_size_basic.result
new file mode 100644
index 00000000..206cfa81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_readahead_size_basic.result
@@ -0,0 +1,70 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @@global.rocksdb_compaction_readahead_size = -1;
+Warnings:
+Warning 1292 Truncated incorrect rocksdb_compaction_readahead_... value: '-1'
+SELECT @@global.rocksdb_compaction_readahead_size;
+@@global.rocksdb_compaction_readahead_size
+0
+SET @start_global_value = @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE to 1"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = 1;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE to 0"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = 0;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE to 222333"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = 222333;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+222333
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+"Trying to set variable @@session.ROCKSDB_COMPACTION_READAHEAD_SIZE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACTION_READAHEAD_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_compaction_readahead_size' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE to 'aaa'"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE to 'bbb'"
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+SET @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACTION_READAHEAD_SIZE;
+@@global.ROCKSDB_COMPACTION_READAHEAD_SIZE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_basic.result
new file mode 100644
index 00000000..311184a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(2000000);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'2000001\'');
+SET @start_global_value = @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to 1"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = 1;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to 1024"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = 1024;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to 2000000"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = 2000000;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+2000000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+"Trying to set variable @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = 444;
+ERROR HY000: Variable 'rocksdb_compaction_sequential_deletes' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to 'aaa'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES to '2000001'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = '2000001';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_count_sd_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_count_sd_basic.result
new file mode 100644
index 00000000..d4e7e28b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_count_sd_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to 1"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = 1;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to 0"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = 0;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to on"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = on;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+"Trying to set variable @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = 444;
+ERROR HY000: Variable 'rocksdb_compaction_sequential_deletes_count_sd' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to 'aaa'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD to 'bbb'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_file_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_file_size_basic.result
new file mode 100644
index 00000000..703e235e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_file_size_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE to 1"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = 1;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE to 1024"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = 1024;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+0
+"Trying to set variable @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_compaction_sequential_deletes_file_size' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE to 'aaa'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+0
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_window_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_window_basic.result
new file mode 100644
index 00000000..84436b65
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_compaction_sequential_deletes_window_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(2000000);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'2000001\'');
+SET @start_global_value = @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to 1"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = 1;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to 1024"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = 1024;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to 2000000"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = 2000000;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+2000000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = DEFAULT;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+"Trying to set variable @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = 444;
+ERROR HY000: Variable 'rocksdb_compaction_sequential_deletes_window' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to 'aaa'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+"Trying to set variable @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW to '2000001'"
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = '2000001';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+SET @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW = @start_global_value;
+SELECT @@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW;
+@@global.ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_checkpoint_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_checkpoint_basic.result
new file mode 100644
index 00000000..630fa78e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_checkpoint_basic.result
@@ -0,0 +1,15 @@
+SET @start_value = @@global.ROCKSDB_CREATE_CHECKPOINT;
+SET @@global.ROCKSDB_CREATE_CHECKPOINT = 'TMP/abc';
+SELECT @@global.ROCKSDB_CREATE_CHECKPOINT;
+@@global.ROCKSDB_CREATE_CHECKPOINT
+
+SET @@global.ROCKSDB_CREATE_CHECKPOINT = DEFAULT;
+SET @@global.ROCKSDB_CREATE_CHECKPOINT = 'TMP/def';
+SELECT @@global.ROCKSDB_CREATE_CHECKPOINT;
+@@global.ROCKSDB_CREATE_CHECKPOINT
+
+SET @@global.ROCKSDB_CREATE_CHECKPOINT = DEFAULT;
+SET @@session.ROCKSDB_CREATE_CHECKPOINT = 444;
+ERROR HY000: Variable 'rocksdb_create_checkpoint' is a GLOBAL variable and should be set with SET GLOBAL
+SET @@global.ROCKSDB_CREATE_CHECKPOINT = @start_value;
+ERROR HY000: Got error 4 'Invalid argument: invalid checkpoint directory name' from ROCKSDB
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_if_missing_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_if_missing_basic.result
new file mode 100644
index 00000000..26dd14fb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_if_missing_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_CREATE_IF_MISSING;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_CREATE_IF_MISSING to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CREATE_IF_MISSING = 444;
+ERROR HY000: Variable 'rocksdb_create_if_missing' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_missing_column_families_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_missing_column_families_basic.result
new file mode 100644
index 00000000..7debadc2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_create_missing_column_families_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_CREATE_MISSING_COLUMN_FAMILIES;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_CREATE_MISSING_COLUMN_FAMILIES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_CREATE_MISSING_COLUMN_FAMILIES = 444;
+ERROR HY000: Variable 'rocksdb_create_missing_column_families' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_datadir_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_datadir_basic.result
new file mode 100644
index 00000000..3215624b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_datadir_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_DATADIR;
+SELECT @start_global_value;
+@start_global_value
+./#rocksdb
+"Trying to set variable @@global.ROCKSDB_DATADIR to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_DATADIR = 444;
+ERROR HY000: Variable 'rocksdb_datadir' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_db_write_buffer_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_db_write_buffer_size_basic.result
new file mode 100644
index 00000000..6c588b7e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_db_write_buffer_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_DB_WRITE_BUFFER_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_DB_WRITE_BUFFER_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_DB_WRITE_BUFFER_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_db_write_buffer_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_basic.result
new file mode 100644
index 00000000..f200105b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_basic.result
@@ -0,0 +1,121 @@
+CREATE TABLE valid_values (value varchar(255));
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255));
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_DEADLOCK_DETECT;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_DEADLOCK_DETECT;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to 1"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = 1;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to 0"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = 0;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to on"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = on;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to off"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = off;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT to 1"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = 1;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT to 0"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = 0;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT to on"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = on;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT to off"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = off;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to 'aaa'"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT to 'bbb'"
+SET @@global.ROCKSDB_DEADLOCK_DETECT = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+SET @@global.ROCKSDB_DEADLOCK_DETECT = @start_global_value;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT;
+@@global.ROCKSDB_DEADLOCK_DETECT
+0
+SET @@session.ROCKSDB_DEADLOCK_DETECT = @start_session_value;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT;
+@@session.ROCKSDB_DEADLOCK_DETECT
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_depth_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_depth_basic.result
new file mode 100644
index 00000000..6f052687
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_deadlock_detect_depth_basic.result
@@ -0,0 +1,79 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(2);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+SET @start_global_value = @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+SELECT @start_global_value;
+@start_global_value
+50
+SET @start_session_value = @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+SELECT @start_session_value;
+@start_session_value
+50
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH to 100"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = 100;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH to 2"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = 2;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+2
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = DEFAULT;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH to 100"
+SET @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH = 100;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@session.ROCKSDB_DEADLOCK_DETECT_DEPTH
+100
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@session.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+"Trying to set variable @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH to 2"
+SET @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH = 2;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@session.ROCKSDB_DEADLOCK_DETECT_DEPTH
+2
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH = DEFAULT;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@session.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH to 'aaa'"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+"Trying to set variable @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH to '123'"
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = '123';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+SET @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH = @start_global_value;
+SELECT @@global.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@global.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+SET @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH = @start_session_value;
+SELECT @@session.ROCKSDB_DEADLOCK_DETECT_DEPTH;
+@@session.ROCKSDB_DEADLOCK_DETECT_DEPTH
+50
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_manual_compaction_delay_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_manual_compaction_delay_basic.result
new file mode 100644
index 00000000..6c5db015
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_manual_compaction_delay_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(100000);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY to 2400"
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = 2400;
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY to 100000"
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = 100000;
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+100000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+0
+"Trying to set variable @@session.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = 444;
+ERROR HY000: Variable 'rocksdb_debug_manual_compaction_delay' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+0
+SET @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY;
+@@global.ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_optimizer_no_zero_cardinality_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_optimizer_no_zero_cardinality_basic.result
new file mode 100644
index 00000000..5e64ccc6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_optimizer_no_zero_cardinality_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to 1"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = 1;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Trying to set variable @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to 0"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = 0;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Trying to set variable @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to on"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = on;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Trying to set variable @@session.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = 444;
+ERROR HY000: Variable 'rocksdb_debug_optimizer_no_zero_cardinality' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+"Trying to set variable @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY to 'bbb'"
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+SET @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY;
+@@global.ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_ignore_pk_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_ignore_pk_basic.result
new file mode 100644
index 00000000..1d8eb721
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_ignore_pk_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK to 1"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = 1;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK to 0"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = 0;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK to on"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = on;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+"Trying to set variable @@session.ROCKSDB_DEBUG_TTL_IGNORE_PK to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_TTL_IGNORE_PK = 444;
+ERROR HY000: Variable 'rocksdb_debug_ttl_ignore_pk' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK to 'bbb'"
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+SET @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_TTL_IGNORE_PK;
+@@global.ROCKSDB_DEBUG_TTL_IGNORE_PK
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_read_filter_ts_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_read_filter_ts_basic.result
new file mode 100644
index 00000000..bbc46001
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_read_filter_ts_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS to 2400"
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = 2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS to -2400"
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = -2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+-2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+0
+"Trying to set variable @@session.ROCKSDB_DEBUG_TTL_READ_FILTER_TS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = 444;
+ERROR HY000: Variable 'rocksdb_debug_ttl_read_filter_ts' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+0
+SET @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS;
+@@global.ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_rec_ts_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_rec_ts_basic.result
new file mode 100644
index 00000000..347ba9a0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_rec_ts_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_REC_TS to 2400"
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = 2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_REC_TS to -2400"
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = -2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+-2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+0
+"Trying to set variable @@session.ROCKSDB_DEBUG_TTL_REC_TS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_TTL_REC_TS = 444;
+ERROR HY000: Variable 'rocksdb_debug_ttl_rec_ts' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_REC_TS to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+0
+SET @@global.ROCKSDB_DEBUG_TTL_REC_TS = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_TTL_REC_TS;
+@@global.ROCKSDB_DEBUG_TTL_REC_TS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_snapshot_ts_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_snapshot_ts_basic.result
new file mode 100644
index 00000000..03a937ef
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_debug_ttl_snapshot_ts_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS to 2400"
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = 2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+0
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS to -2400"
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = -2400;
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+-2400
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = DEFAULT;
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+0
+"Trying to set variable @@session.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = 444;
+ERROR HY000: Variable 'rocksdb_debug_ttl_snapshot_ts' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS to 'aaa'"
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+0
+SET @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS = @start_global_value;
+SELECT @@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS;
+@@global.ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_default_cf_options_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_default_cf_options_basic.result
new file mode 100644
index 00000000..b2b1c0e4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_default_cf_options_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_DEFAULT_CF_OPTIONS;
+SELECT @start_global_value;
+@start_global_value
+
+"Trying to set variable @@global.ROCKSDB_DEFAULT_CF_OPTIONS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_DEFAULT_CF_OPTIONS = 444;
+ERROR HY000: Variable 'rocksdb_default_cf_options' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delayed_write_rate_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delayed_write_rate_basic.result
new file mode 100644
index 00000000..13749e1c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delayed_write_rate_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_DELAYED_WRITE_RATE;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to 100"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = 100;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = DEFAULT;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to 1"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = 1;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = DEFAULT;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to 0"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = 0;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = DEFAULT;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@session.ROCKSDB_DELAYED_WRITE_RATE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_DELAYED_WRITE_RATE = 444;
+ERROR HY000: Variable 'rocksdb_delayed_write_rate' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to 'aaa'"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to 'bbb'"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to '-1'"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to '101'"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+"Trying to set variable @@global.ROCKSDB_DELAYED_WRITE_RATE to '484436'"
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+SET @@global.ROCKSDB_DELAYED_WRITE_RATE = @start_global_value;
+SELECT @@global.ROCKSDB_DELAYED_WRITE_RATE;
+@@global.ROCKSDB_DELAYED_WRITE_RATE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_cf_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_cf_basic.result
new file mode 100644
index 00000000..b840baf2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_cf_basic.result
@@ -0,0 +1,6 @@
+SET @@global.ROCKSDB_DELETE_CF = 'nonexistent_cf';
+ERROR 42000: Variable 'rocksdb_delete_cf' can't be set to the value of 'nonexistent_cf'
+SET @@global.ROCKSDB_DELETE_CF = '__system__';
+ERROR 42000: Variable 'rocksdb_delete_cf' can't be set to the value of '__system__'
+SET @@global.ROCKSDB_DELETE_CF = 'default';
+ERROR 42000: Variable 'rocksdb_delete_cf' can't be set to the value of 'default'
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_obsolete_files_period_micros_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_obsolete_files_period_micros_basic.result
new file mode 100644
index 00000000..2dc220fb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_delete_obsolete_files_period_micros_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_DELETE_OBSOLETE_FILES_PERIOD_MICROS;
+SELECT @start_global_value;
+@start_global_value
+21600000000
+"Trying to set variable @@global.ROCKSDB_DELETE_OBSOLETE_FILES_PERIOD_MICROS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_DELETE_OBSOLETE_FILES_PERIOD_MICROS = 444;
+ERROR HY000: Variable 'rocksdb_delete_obsolete_files_period_micros' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_2pc_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_2pc_basic.result
new file mode 100644
index 00000000..686f8bcd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_2pc_basic.result
@@ -0,0 +1,75 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_ENABLE_2PC;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to 1"
+SET @@global.ROCKSDB_ENABLE_2PC = 1;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_2PC = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to 0"
+SET @@global.ROCKSDB_ENABLE_2PC = 0;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_2PC = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to on"
+SET @@global.ROCKSDB_ENABLE_2PC = on;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_2PC = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to off"
+SET @@global.ROCKSDB_ENABLE_2PC = off;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_2PC = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Trying to set variable @@session.ROCKSDB_ENABLE_2PC to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_ENABLE_2PC = 444;
+ERROR HY000: Variable 'rocksdb_enable_2pc' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to 'aaa'"
+SET @@global.ROCKSDB_ENABLE_2PC = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_2PC to 'bbb'"
+SET @@global.ROCKSDB_ENABLE_2PC = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+SET @@global.ROCKSDB_ENABLE_2PC = @start_global_value;
+SELECT @@global.ROCKSDB_ENABLE_2PC;
+@@global.ROCKSDB_ENABLE_2PC
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_bulk_load_api_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_bulk_load_api_basic.result
new file mode 100644
index 00000000..2c0ff289
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_bulk_load_api_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_ENABLE_BULK_LOAD_API;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_BULK_LOAD_API to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ENABLE_BULK_LOAD_API = 444;
+ERROR HY000: Variable 'rocksdb_enable_bulk_load_api' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_insert_with_update_caching_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_insert_with_update_caching_basic.result
new file mode 100644
index 00000000..ede1690f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_insert_with_update_caching_basic.result
@@ -0,0 +1,75 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to 1"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = 1;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to 0"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = 0;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to on"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = on;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to off"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = off;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Trying to set variable @@session.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = 444;
+ERROR HY000: Variable 'rocksdb_enable_insert_with_update_caching' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to 'aaa'"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING to 'bbb'"
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+SET @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING = @start_global_value;
+SELECT @@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING;
+@@global.ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_thread_tracking_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_thread_tracking_basic.result
new file mode 100644
index 00000000..a63383a4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_thread_tracking_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ENABLE_THREAD_TRACKING;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_THREAD_TRACKING to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ENABLE_THREAD_TRACKING = 444;
+ERROR HY000: Variable 'rocksdb_enable_thread_tracking' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_basic.result
new file mode 100644
index 00000000..1f569235
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_ENABLE_TTL;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL to 1"
+SET @@global.ROCKSDB_ENABLE_TTL = 1;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL to 0"
+SET @@global.ROCKSDB_ENABLE_TTL = 0;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL to on"
+SET @@global.ROCKSDB_ENABLE_TTL = on;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Trying to set variable @@session.ROCKSDB_ENABLE_TTL to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_ENABLE_TTL = 444;
+ERROR HY000: Variable 'rocksdb_enable_ttl' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL to 'aaa'"
+SET @@global.ROCKSDB_ENABLE_TTL = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL to 'bbb'"
+SET @@global.ROCKSDB_ENABLE_TTL = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+SET @@global.ROCKSDB_ENABLE_TTL = @start_global_value;
+SELECT @@global.ROCKSDB_ENABLE_TTL;
+@@global.ROCKSDB_ENABLE_TTL
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_read_filtering_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_read_filtering_basic.result
new file mode 100644
index 00000000..005c15e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_ttl_read_filtering_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING to 1"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = 1;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING to 0"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = 0;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING to on"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = on;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = DEFAULT;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Trying to set variable @@session.ROCKSDB_ENABLE_TTL_READ_FILTERING to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_ENABLE_TTL_READ_FILTERING = 444;
+ERROR HY000: Variable 'rocksdb_enable_ttl_read_filtering' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING to 'aaa'"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+"Trying to set variable @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING to 'bbb'"
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+SET @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING = @start_global_value;
+SELECT @@global.ROCKSDB_ENABLE_TTL_READ_FILTERING;
+@@global.ROCKSDB_ENABLE_TTL_READ_FILTERING
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_write_thread_adaptive_yield_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_write_thread_adaptive_yield_basic.result
new file mode 100644
index 00000000..37107be4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_enable_write_thread_adaptive_yield_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ENABLE_WRITE_THREAD_ADAPTIVE_YIELD;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ENABLE_WRITE_THREAD_ADAPTIVE_YIELD to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ENABLE_WRITE_THREAD_ADAPTIVE_YIELD = 444;
+ERROR HY000: Variable 'rocksdb_enable_write_thread_adaptive_yield' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_if_exists_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_if_exists_basic.result
new file mode 100644
index 00000000..650e2956
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_if_exists_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_ERROR_IF_EXISTS;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_ERROR_IF_EXISTS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ERROR_IF_EXISTS = 444;
+ERROR HY000: Variable 'rocksdb_error_if_exists' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_on_suboptimal_collation_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_on_suboptimal_collation_basic.result
new file mode 100644
index 00000000..e10583bf
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_error_on_suboptimal_collation_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_ERROR_ON_SUBOPTIMAL_COLLATION;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_ERROR_ON_SUBOPTIMAL_COLLATION to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_ERROR_ON_SUBOPTIMAL_COLLATION = 444;
+ERROR HY000: Variable 'rocksdb_error_on_suboptimal_collation' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_flush_log_at_trx_commit_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_flush_log_at_trx_commit_basic.result
new file mode 100644
index 00000000..b8fe837d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_flush_log_at_trx_commit_basic.result
@@ -0,0 +1,57 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT to 2"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 2;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+2
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = DEFAULT;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+"Trying to set variable @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT to 1"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 1;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = DEFAULT;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+"Trying to set variable @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT to 0"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 0;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = DEFAULT;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+"Trying to set variable @@session.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 444;
+ERROR HY000: Variable 'rocksdb_flush_log_at_trx_commit' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT to 'aaa'"
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+SET @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT = @start_global_value;
+SELECT @@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT;
+@@global.ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_basic.result
new file mode 100644
index 00000000..90fd829e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_basic.result
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS t;
+CREATE TABLE t (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t (a,b) VALUES (1,'bar'),(2,'foo');
+SET @ORIG_FORCE_COMPUTE_MEMTABLE_STATS = @@rocksdb_force_compute_memtable_stats;
+set global rocksdb_force_flush_memtable_now = true;
+INSERT INTO t (a,b) VALUES (3,'dead'),(4,'beef'),(5,'a'),(6,'bbb'),(7,'c'),(8,'d');
+set global rocksdb_force_compute_memtable_stats=0;
+SELECT TABLE_ROWS INTO @ROWS_EXCLUDE_MEMTABLE FROM information_schema.TABLES WHERE table_name = 't';
+set global rocksdb_force_compute_memtable_stats=1;
+SELECT TABLE_ROWS INTO @ROWS_INCLUDE_MEMTABLE FROM information_schema.TABLES WHERE table_name = 't';
+select case when @ROWS_INCLUDE_MEMTABLE-@ROWS_EXCLUDE_MEMTABLE > 0 then 'true' else 'false' end;
+case when @ROWS_INCLUDE_MEMTABLE-@ROWS_EXCLUDE_MEMTABLE > 0 then 'true' else 'false' end
+true
+DROP TABLE t;
+set global rocksdb_force_compute_memtable_stats = @ORIG_FORCE_COMPUTE_MEMTABLE_STATS;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_cachetime_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_cachetime_basic.result
new file mode 100644
index 00000000..50e06b5b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_compute_memtable_stats_cachetime_basic.result
@@ -0,0 +1,68 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(1024 * 1024 * 1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 0"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 0;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+60000000
+"Trying to set variable @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 1"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 1;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+60000000
+"Trying to set variable @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 1024"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 1024;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+60000000
+"Trying to set variable @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 1073741824"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 1073741824;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+1073741824
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+60000000
+"Trying to set variable @@session.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 444;
+ERROR HY000: Variable 'rocksdb_force_compute_memtable_stats_cachetime' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME to 'aaa'"
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+60000000
+SET @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME = @start_global_value;
+SELECT @@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME;
+@@global.ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_and_lzero_now_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_and_lzero_now_basic.result
new file mode 100644
index 00000000..68cfeb07
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_and_lzero_now_basic.result
@@ -0,0 +1,50 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+SET @start_global_value = @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW to 1"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = 1;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW to 0"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = 0;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW to on"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = on;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+"Trying to set variable @@session.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = 444;
+ERROR HY000: Variable 'rocksdb_force_flush_memtable_and_lzero_now' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW = @start_global_value;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_now_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_now_basic.result
new file mode 100644
index 00000000..30444e26
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_flush_memtable_now_basic.result
@@ -0,0 +1,50 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+SET @start_global_value = @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW to 1"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = 1;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW to 0"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = 0;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW to on"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = on;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+"Trying to set variable @@session.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = 444;
+ERROR HY000: Variable 'rocksdb_force_flush_memtable_now' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+SET @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW = @start_global_value;
+SELECT @@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW;
+@@global.ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_index_records_in_range_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_index_records_in_range_basic.result
new file mode 100644
index 00000000..d4768cfd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_force_index_records_in_range_basic.result
@@ -0,0 +1,106 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @@session.rocksdb_force_index_records_in_range = -1;
+Warnings:
+Warning 1292 Truncated incorrect rocksdb_force_index_records_i... value: '-1'
+SELECT @@session.rocksdb_force_index_records_in_range;
+@@session.rocksdb_force_index_records_in_range
+0
+SET @start_global_value = @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 1"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 1;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 0"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 0;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 222333"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 222333;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+222333
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 1"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 1;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Trying to set variable @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 0"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 0;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Trying to set variable @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 222333"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 222333;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+222333
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 'aaa'"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE to 'bbb'"
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+SET @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = @start_global_value;
+SELECT @@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@global.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+SET @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE = @start_session_value;
+SELECT @@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE;
+@@session.ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_git_hash_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_git_hash_basic.result
new file mode 100644
index 00000000..bbcfa141
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_git_hash_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_GIT_HASH;
+SELECT @start_global_value;
+@start_global_value
+#
+"Trying to set variable @@global.ROCKSDB_GIT_HASH to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_GIT_HASH = 444;
+ERROR HY000: Variable 'rocksdb_git_hash' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_hash_index_allow_collision_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_hash_index_allow_collision_basic.result
new file mode 100644
index 00000000..34deca6c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_hash_index_allow_collision_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_HASH_INDEX_ALLOW_COLLISION;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_HASH_INDEX_ALLOW_COLLISION to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_HASH_INDEX_ALLOW_COLLISION = 444;
+ERROR HY000: Variable 'rocksdb_hash_index_allow_collision' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_ignore_unknown_options_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_ignore_unknown_options_basic.result
new file mode 100644
index 00000000..621213cd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_ignore_unknown_options_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_IGNORE_UNKNOWN_OPTIONS;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_IGNORE_UNKNOWN_OPTIONS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_IGNORE_UNKNOWN_OPTIONS = 444;
+ERROR HY000: Variable 'rocksdb_ignore_unknown_options' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_index_type_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_index_type_basic.result
new file mode 100644
index 00000000..97c6ed84
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_index_type_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_INDEX_TYPE;
+SELECT @start_global_value;
+@start_global_value
+kBinarySearch
+"Trying to set variable @@global.ROCKSDB_INDEX_TYPE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_INDEX_TYPE = 444;
+ERROR HY000: Variable 'rocksdb_index_type' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_info_log_level_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_info_log_level_basic.result
new file mode 100644
index 00000000..1509f9ae
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_info_log_level_basic.result
@@ -0,0 +1,93 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('fatal_level');
+INSERT INTO valid_values VALUES('error_level');
+INSERT INTO valid_values VALUES('warn_level');
+INSERT INTO valid_values VALUES('info_level');
+INSERT INTO valid_values VALUES('debug_level');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES(5);
+INSERT INTO invalid_values VALUES(6);
+INSERT INTO invalid_values VALUES('foo');
+SET @start_global_value = @@global.ROCKSDB_INFO_LOG_LEVEL;
+SELECT @start_global_value;
+@start_global_value
+error_level
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to fatal_level"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = fatal_level;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+fatal_level
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to error_level"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = error_level;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to warn_level"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = warn_level;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+warn_level
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to info_level"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = info_level;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+info_level
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to debug_level"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = debug_level;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+debug_level
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@session.ROCKSDB_INFO_LOG_LEVEL to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_INFO_LOG_LEVEL = 444;
+ERROR HY000: Variable 'rocksdb_info_log_level' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to 5"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = 5;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to 6"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = 6;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+"Trying to set variable @@global.ROCKSDB_INFO_LOG_LEVEL to foo"
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = foo;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+SET @@global.ROCKSDB_INFO_LOG_LEVEL = @start_global_value;
+SELECT @@global.ROCKSDB_INFO_LOG_LEVEL;
+@@global.ROCKSDB_INFO_LOG_LEVEL
+error_level
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_io_write_timeout_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_io_write_timeout_basic.result
new file mode 100644
index 00000000..0917a397
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_io_write_timeout_basic.result
@@ -0,0 +1,86 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(10);
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(42);
+INSERT INTO valid_values VALUES(142);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 10"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 10;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+10
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 100"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 100;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 0"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 0;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 42"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 42;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+42
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 142"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 142;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+142
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@session.ROCKSDB_IO_WRITE_TIMEOUT to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_IO_WRITE_TIMEOUT = 444;
+ERROR HY000: Variable 'rocksdb_io_write_timeout' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 'aaa'"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_IO_WRITE_TIMEOUT to 'bbb'"
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+SET @@global.ROCKSDB_IO_WRITE_TIMEOUT = @start_global_value;
+SELECT @@global.ROCKSDB_IO_WRITE_TIMEOUT;
+@@global.ROCKSDB_IO_WRITE_TIMEOUT
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_is_fd_close_on_exec_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_is_fd_close_on_exec_basic.result
new file mode 100644
index 00000000..87dd0e90
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_is_fd_close_on_exec_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_IS_FD_CLOSE_ON_EXEC;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_IS_FD_CLOSE_ON_EXEC to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_IS_FD_CLOSE_ON_EXEC = 444;
+ERROR HY000: Variable 'rocksdb_is_fd_close_on_exec' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_keep_log_file_num_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_keep_log_file_num_basic.result
new file mode 100644
index 00000000..3a0c5060
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_keep_log_file_num_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_KEEP_LOG_FILE_NUM;
+SELECT @start_global_value;
+@start_global_value
+1000
+"Trying to set variable @@global.ROCKSDB_KEEP_LOG_FILE_NUM to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_KEEP_LOG_FILE_NUM = 444;
+ERROR HY000: Variable 'rocksdb_keep_log_file_num' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_large_prefix_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_large_prefix_basic.result
new file mode 100644
index 00000000..89697683
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_large_prefix_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_LARGE_PREFIX;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_LARGE_PREFIX to 1"
+SET @@global.ROCKSDB_LARGE_PREFIX = 1;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LARGE_PREFIX = DEFAULT;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+"Trying to set variable @@global.ROCKSDB_LARGE_PREFIX to 0"
+SET @@global.ROCKSDB_LARGE_PREFIX = 0;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LARGE_PREFIX = DEFAULT;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+"Trying to set variable @@global.ROCKSDB_LARGE_PREFIX to on"
+SET @@global.ROCKSDB_LARGE_PREFIX = on;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LARGE_PREFIX = DEFAULT;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+"Trying to set variable @@session.ROCKSDB_LARGE_PREFIX to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_LARGE_PREFIX = 444;
+ERROR HY000: Variable 'rocksdb_large_prefix' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_LARGE_PREFIX to 'aaa'"
+SET @@global.ROCKSDB_LARGE_PREFIX = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+"Trying to set variable @@global.ROCKSDB_LARGE_PREFIX to 'bbb'"
+SET @@global.ROCKSDB_LARGE_PREFIX = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+SET @@global.ROCKSDB_LARGE_PREFIX = @start_global_value;
+SELECT @@global.ROCKSDB_LARGE_PREFIX;
+@@global.ROCKSDB_LARGE_PREFIX
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_scanned_rows_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_scanned_rows_basic.result
new file mode 100644
index 00000000..eff9e619
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_scanned_rows_basic.result
@@ -0,0 +1,170 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES(2);
+INSERT INTO invalid_values VALUES(1000);
+SET @start_global_value = @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to 1"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = 1;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to 0"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = 0;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to on"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = on;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to off"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = off;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to true"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = true;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to false"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = false;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to 1"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = 1;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to 0"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = 0;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to on"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = on;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to off"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = off;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to true"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = true;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@session.ROCKSDB_LOCK_SCANNED_ROWS to false"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = false;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to 'aaa'"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to 2"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = 2;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+"Trying to set variable @@global.ROCKSDB_LOCK_SCANNED_ROWS to 1000"
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = 1000;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+SET @@global.ROCKSDB_LOCK_SCANNED_ROWS = @start_global_value;
+SELECT @@global.ROCKSDB_LOCK_SCANNED_ROWS;
+@@global.ROCKSDB_LOCK_SCANNED_ROWS
+0
+SET @@session.ROCKSDB_LOCK_SCANNED_ROWS = @start_session_value;
+SELECT @@session.ROCKSDB_LOCK_SCANNED_ROWS;
+@@session.ROCKSDB_LOCK_SCANNED_ROWS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_wait_timeout_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_wait_timeout_basic.result
new file mode 100644
index 00000000..38df5820
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_lock_wait_timeout_basic.result
@@ -0,0 +1,72 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+SELECT @start_global_value;
+@start_global_value
+1
+SET @start_session_value = @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+SELECT @start_session_value;
+@start_session_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_LOCK_WAIT_TIMEOUT to 1"
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = 1;
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+"Trying to set variable @@global.ROCKSDB_LOCK_WAIT_TIMEOUT to 1024"
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = 1024;
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_LOCK_WAIT_TIMEOUT to 1"
+SET @@session.ROCKSDB_LOCK_WAIT_TIMEOUT = 1;
+SELECT @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@session.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_WAIT_TIMEOUT = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@session.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+"Trying to set variable @@session.ROCKSDB_LOCK_WAIT_TIMEOUT to 1024"
+SET @@session.ROCKSDB_LOCK_WAIT_TIMEOUT = 1024;
+SELECT @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@session.ROCKSDB_LOCK_WAIT_TIMEOUT
+1024
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_LOCK_WAIT_TIMEOUT = DEFAULT;
+SELECT @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@session.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_LOCK_WAIT_TIMEOUT to 'aaa'"
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+SET @@global.ROCKSDB_LOCK_WAIT_TIMEOUT = @start_global_value;
+SELECT @@global.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@global.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+SET @@session.ROCKSDB_LOCK_WAIT_TIMEOUT = @start_session_value;
+SELECT @@session.ROCKSDB_LOCK_WAIT_TIMEOUT;
+@@session.ROCKSDB_LOCK_WAIT_TIMEOUT
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_log_file_time_to_roll_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_log_file_time_to_roll_basic.result
new file mode 100644
index 00000000..24cff584
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_log_file_time_to_roll_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_LOG_FILE_TIME_TO_ROLL;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_LOG_FILE_TIME_TO_ROLL to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_LOG_FILE_TIME_TO_ROLL = 444;
+ERROR HY000: Variable 'rocksdb_log_file_time_to_roll' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manifest_preallocation_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manifest_preallocation_size_basic.result
new file mode 100644
index 00000000..dbb331d2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manifest_preallocation_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_MANIFEST_PREALLOCATION_SIZE;
+SELECT @start_global_value;
+@start_global_value
+4194304
+"Trying to set variable @@global.ROCKSDB_MANIFEST_PREALLOCATION_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MANIFEST_PREALLOCATION_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_manifest_preallocation_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_compaction_threads_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_compaction_threads_basic.result
new file mode 100644
index 00000000..3d599e17
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_compaction_threads_basic.result
@@ -0,0 +1,93 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(99);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MANUAL_COMPACTION_THREADS to 0"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = 0;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Trying to set variable @@global.ROCKSDB_MANUAL_COMPACTION_THREADS to 1"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = 1;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Trying to set variable @@global.ROCKSDB_MANUAL_COMPACTION_THREADS to 99"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = 99;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+99
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_MANUAL_COMPACTION_THREADS to 0"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = 0;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Trying to set variable @@session.ROCKSDB_MANUAL_COMPACTION_THREADS to 1"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = 1;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+"Trying to set variable @@session.ROCKSDB_MANUAL_COMPACTION_THREADS to 99"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = 99;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+99
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = DEFAULT;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MANUAL_COMPACTION_THREADS to 'aaa'"
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+SET @@global.ROCKSDB_MANUAL_COMPACTION_THREADS = @start_global_value;
+SELECT @@global.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@global.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+SET @@session.ROCKSDB_MANUAL_COMPACTION_THREADS = @start_session_value;
+SELECT @@session.ROCKSDB_MANUAL_COMPACTION_THREADS;
+@@session.ROCKSDB_MANUAL_COMPACTION_THREADS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_wal_flush_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_wal_flush_basic.result
new file mode 100644
index 00000000..9b176263
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_manual_wal_flush_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_MANUAL_WAL_FLUSH;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_MANUAL_WAL_FLUSH to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MANUAL_WAL_FLUSH = 444;
+ERROR HY000: Variable 'rocksdb_manual_wal_flush' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_master_skip_tx_api_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_master_skip_tx_api_basic.result
new file mode 100644
index 00000000..3f50772d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_master_skip_tx_api_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_MASTER_SKIP_TX_API;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_MASTER_SKIP_TX_API;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MASTER_SKIP_TX_API to 1"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = 1;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Trying to set variable @@global.ROCKSDB_MASTER_SKIP_TX_API to 0"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = 0;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Trying to set variable @@global.ROCKSDB_MASTER_SKIP_TX_API to on"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = on;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_MASTER_SKIP_TX_API to 1"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = 1;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Trying to set variable @@session.ROCKSDB_MASTER_SKIP_TX_API to 0"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = 0;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Trying to set variable @@session.ROCKSDB_MASTER_SKIP_TX_API to on"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = on;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = DEFAULT;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MASTER_SKIP_TX_API to 'aaa'"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+"Trying to set variable @@global.ROCKSDB_MASTER_SKIP_TX_API to 'bbb'"
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+SET @@global.ROCKSDB_MASTER_SKIP_TX_API = @start_global_value;
+SELECT @@global.ROCKSDB_MASTER_SKIP_TX_API;
+@@global.ROCKSDB_MASTER_SKIP_TX_API
+0
+SET @@session.ROCKSDB_MASTER_SKIP_TX_API = @start_session_value;
+SELECT @@session.ROCKSDB_MASTER_SKIP_TX_API;
+@@session.ROCKSDB_MASTER_SKIP_TX_API
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_background_jobs_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_background_jobs_basic.result
new file mode 100644
index 00000000..88e6d21c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_background_jobs_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(64);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'abc\'');
+SET @start_global_value = @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+SELECT @start_global_value;
+@start_global_value
+2
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MAX_BACKGROUND_JOBS to 1"
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = 1;
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+2
+"Trying to set variable @@global.ROCKSDB_MAX_BACKGROUND_JOBS to 64"
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = 64;
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+64
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+2
+"Trying to set variable @@session.ROCKSDB_MAX_BACKGROUND_JOBS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_MAX_BACKGROUND_JOBS = 444;
+ERROR HY000: Variable 'rocksdb_max_background_jobs' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MAX_BACKGROUND_JOBS to 'abc'"
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = 'abc';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+2
+SET @@global.ROCKSDB_MAX_BACKGROUND_JOBS = @start_global_value;
+SELECT @@global.ROCKSDB_MAX_BACKGROUND_JOBS;
+@@global.ROCKSDB_MAX_BACKGROUND_JOBS
+2
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_latest_deadlocks_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_latest_deadlocks_basic.result
new file mode 100644
index 00000000..74dbdb42
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_latest_deadlocks_basic.result
@@ -0,0 +1,53 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+SET @start_global_value = @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+SELECT @start_global_value;
+@start_global_value
+5
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MAX_LATEST_DEADLOCKS to 100"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = 100;
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+5
+"Trying to set variable @@global.ROCKSDB_MAX_LATEST_DEADLOCKS to 1"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = 1;
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+5
+"Trying to set variable @@session.ROCKSDB_MAX_LATEST_DEADLOCKS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_MAX_LATEST_DEADLOCKS = 444;
+ERROR HY000: Variable 'rocksdb_max_latest_deadlocks' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MAX_LATEST_DEADLOCKS to 'aaa'"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+5
+"Trying to set variable @@global.ROCKSDB_MAX_LATEST_DEADLOCKS to '123'"
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = '123';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+5
+SET @@global.ROCKSDB_MAX_LATEST_DEADLOCKS = @start_global_value;
+SELECT @@global.ROCKSDB_MAX_LATEST_DEADLOCKS;
+@@global.ROCKSDB_MAX_LATEST_DEADLOCKS
+5
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_log_file_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_log_file_size_basic.result
new file mode 100644
index 00000000..4359ee72
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_log_file_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_MAX_LOG_FILE_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_MAX_LOG_FILE_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MAX_LOG_FILE_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_max_log_file_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manifest_file_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manifest_file_size_basic.result
new file mode 100644
index 00000000..45330b82
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manifest_file_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_MAX_MANIFEST_FILE_SIZE;
+SELECT @start_global_value;
+@start_global_value
+1073741824
+"Trying to set variable @@global.ROCKSDB_MAX_MANIFEST_FILE_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MAX_MANIFEST_FILE_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_max_manifest_file_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manual_compactions_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manual_compactions_basic.result
new file mode 100644
index 00000000..815506cc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_manual_compactions_basic.result
@@ -0,0 +1,57 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(512*1024*1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+SELECT @start_global_value;
+@start_global_value
+10
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS to 1"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = 1;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+10
+"Trying to set variable @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS to 1024"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = 1024;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+10
+"Trying to set variable @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS to 536870912"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = 536870912;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+536870912
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+10
+"Trying to set variable @@session.ROCKSDB_MAX_MANUAL_COMPACTIONS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_MAX_MANUAL_COMPACTIONS = 444;
+ERROR HY000: Variable 'rocksdb_max_manual_compactions' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS to 'aaa'"
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+10
+SET @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS = @start_global_value;
+SELECT @@global.ROCKSDB_MAX_MANUAL_COMPACTIONS;
+@@global.ROCKSDB_MAX_MANUAL_COMPACTIONS
+10
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_open_files_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_open_files_basic.result
new file mode 100644
index 00000000..60f50531
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_open_files_basic.result
@@ -0,0 +1,3 @@
+show variables like 'rocksdb_max_open_files';
+Variable_name Value
+rocksdb_max_open_files #
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_row_locks_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_row_locks_basic.result
new file mode 100644
index 00000000..b195df09
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_row_locks_basic.result
@@ -0,0 +1,93 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(512*1024*1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_MAX_ROW_LOCKS;
+SELECT @start_global_value;
+@start_global_value
+1048576
+SET @start_session_value = @@session.ROCKSDB_MAX_ROW_LOCKS;
+SELECT @start_session_value;
+@start_session_value
+1048576
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MAX_ROW_LOCKS to 1"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = 1;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1048576
+"Trying to set variable @@global.ROCKSDB_MAX_ROW_LOCKS to 1024"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = 1024;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1048576
+"Trying to set variable @@global.ROCKSDB_MAX_ROW_LOCKS to 536870912"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = 536870912;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+536870912
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1048576
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_MAX_ROW_LOCKS to 1"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = 1;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1048576
+"Trying to set variable @@session.ROCKSDB_MAX_ROW_LOCKS to 1024"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = 1024;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1024
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1048576
+"Trying to set variable @@session.ROCKSDB_MAX_ROW_LOCKS to 536870912"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = 536870912;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+536870912
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = DEFAULT;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1048576
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MAX_ROW_LOCKS to 'aaa'"
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1048576
+SET @@global.ROCKSDB_MAX_ROW_LOCKS = @start_global_value;
+SELECT @@global.ROCKSDB_MAX_ROW_LOCKS;
+@@global.ROCKSDB_MAX_ROW_LOCKS
+1048576
+SET @@session.ROCKSDB_MAX_ROW_LOCKS = @start_session_value;
+SELECT @@session.ROCKSDB_MAX_ROW_LOCKS;
+@@session.ROCKSDB_MAX_ROW_LOCKS
+1048576
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_subcompactions_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_subcompactions_basic.result
new file mode 100644
index 00000000..58452f58
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_subcompactions_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_MAX_SUBCOMPACTIONS;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_MAX_SUBCOMPACTIONS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MAX_SUBCOMPACTIONS = 444;
+ERROR HY000: Variable 'rocksdb_max_subcompactions' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_total_wal_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_total_wal_size_basic.result
new file mode 100644
index 00000000..22c17c24
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_max_total_wal_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_MAX_TOTAL_WAL_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_MAX_TOTAL_WAL_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_MAX_TOTAL_WAL_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_max_total_wal_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_buf_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_buf_size_basic.result
new file mode 100644
index 00000000..5715b198
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_buf_size_basic.result
@@ -0,0 +1,43 @@
+drop table if exists t1;
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `kj` (`j`),
+ KEY `kij` (`i`,`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP INDEX kj on t1;
+DROP INDEX kij ON t1;
+ALTER TABLE t1 ADD INDEX kj(j), ADD INDEX kij(i,j), ADD INDEX kji(j,i), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `kj` (`j`),
+ KEY `kij` (`i`,`j`),
+ KEY `kji` (`j`,`i`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+ALTER TABLE t1 ADD INDEX kb(b) comment 'rev:cf1', ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) NOT NULL,
+ `b` int(11) DEFAULT NULL,
+ PRIMARY KEY (`a`),
+ KEY `kb` (`b`) COMMENT 'rev:cf1'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+COUNT(*)
+100
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_combine_read_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_combine_read_size_basic.result
new file mode 100644
index 00000000..5b73305c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_combine_read_size_basic.result
@@ -0,0 +1,29 @@
+drop table if exists t1;
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `kj` (`j`),
+ KEY `kij` (`i`,`j`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP INDEX kj on t1;
+DROP INDEX kij ON t1;
+ALTER TABLE t1 ADD INDEX kj(j), ADD INDEX kij(i,j), ADD INDEX kji(j,i), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) NOT NULL,
+ `j` int(11) DEFAULT NULL,
+ PRIMARY KEY (`i`),
+ KEY `kj` (`j`),
+ KEY `kij` (`i`,`j`),
+ KEY `kji` (`j`,`i`)
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_tmp_file_removal_delay_ms_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_tmp_file_removal_delay_ms_basic.result
new file mode 100644
index 00000000..277de716
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_merge_tmp_file_removal_delay_ms_basic.result
@@ -0,0 +1,93 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('on');
+SET @start_global_value = @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 1"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 1;
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = DEFAULT;
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Trying to set variable @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 0"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 0;
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = DEFAULT;
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 1"
+SET @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 1;
+SELECT @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = DEFAULT;
+SELECT @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Trying to set variable @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 0"
+SET @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 0;
+SELECT @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = DEFAULT;
+SELECT @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 'aaa'"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Trying to set variable @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to 'bbb'"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+"Trying to set variable @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS to on"
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = on;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+SET @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = @start_global_value;
+SELECT @@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@global.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+SET @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS = @start_session_value;
+SELECT @@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS;
+@@session.ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+set session rocksdb_merge_tmp_file_removal_delay_ms=1000;
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+include/assert.inc [Alter should have taken at least 10 seconds]
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_new_table_reader_for_compaction_inputs_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_new_table_reader_for_compaction_inputs_basic.result
new file mode 100644
index 00000000..c2daec32
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_new_table_reader_for_compaction_inputs_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_NEW_TABLE_READER_FOR_COMPACTION_INPUTS;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_NEW_TABLE_READER_FOR_COMPACTION_INPUTS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_NEW_TABLE_READER_FOR_COMPACTION_INPUTS = 444;
+ERROR HY000: Variable 'rocksdb_new_table_reader_for_compaction_inputs' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_no_block_cache_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_no_block_cache_basic.result
new file mode 100644
index 00000000..7bd32950
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_no_block_cache_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_NO_BLOCK_CACHE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_NO_BLOCK_CACHE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_NO_BLOCK_CACHE = 444;
+ERROR HY000: Variable 'rocksdb_no_block_cache' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_override_cf_options_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_override_cf_options_basic.result
new file mode 100644
index 00000000..59042124
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_override_cf_options_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_OVERRIDE_CF_OPTIONS;
+SELECT @start_global_value;
+@start_global_value
+
+"Trying to set variable @@global.ROCKSDB_OVERRIDE_CF_OPTIONS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_OVERRIDE_CF_OPTIONS = 444;
+ERROR HY000: Variable 'rocksdb_override_cf_options' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_paranoid_checks_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_paranoid_checks_basic.result
new file mode 100644
index 00000000..102d4926
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_paranoid_checks_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_PARANOID_CHECKS;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_PARANOID_CHECKS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_PARANOID_CHECKS = 444;
+ERROR HY000: Variable 'rocksdb_paranoid_checks' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pause_background_work_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pause_background_work_basic.result
new file mode 100644
index 00000000..5849fe09
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pause_background_work_basic.result
@@ -0,0 +1,75 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to 1"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = 1;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = DEFAULT;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to 0"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = 0;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = DEFAULT;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to on"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = on;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = DEFAULT;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to off"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = off;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = DEFAULT;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Trying to set variable @@session.ROCKSDB_PAUSE_BACKGROUND_WORK to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_PAUSE_BACKGROUND_WORK = 444;
+ERROR HY000: Variable 'rocksdb_pause_background_work' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to 'aaa'"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+"Trying to set variable @@global.ROCKSDB_PAUSE_BACKGROUND_WORK to 'bbb'"
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+SET @@global.ROCKSDB_PAUSE_BACKGROUND_WORK = @start_global_value;
+SELECT @@global.ROCKSDB_PAUSE_BACKGROUND_WORK;
+@@global.ROCKSDB_PAUSE_BACKGROUND_WORK
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_perf_context_level_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_perf_context_level_basic.result
new file mode 100644
index 00000000..292ba58a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_perf_context_level_basic.result
@@ -0,0 +1,114 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(2);
+INSERT INTO valid_values VALUES(3);
+INSERT INTO valid_values VALUES(4);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_PERF_CONTEXT_LEVEL to 1"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = 1;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_PERF_CONTEXT_LEVEL to 2"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = 2;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+2
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_PERF_CONTEXT_LEVEL to 3"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = 3;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+3
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_PERF_CONTEXT_LEVEL to 4"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = 4;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+4
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_PERF_CONTEXT_LEVEL to 1"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = 1;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@session.ROCKSDB_PERF_CONTEXT_LEVEL to 2"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = 2;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+2
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@session.ROCKSDB_PERF_CONTEXT_LEVEL to 3"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = 3;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+3
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+"Trying to set variable @@session.ROCKSDB_PERF_CONTEXT_LEVEL to 4"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = 4;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+4
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = DEFAULT;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_PERF_CONTEXT_LEVEL to 'aaa'"
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+SET @@global.ROCKSDB_PERF_CONTEXT_LEVEL = @start_global_value;
+SELECT @@global.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@global.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+SET @@session.ROCKSDB_PERF_CONTEXT_LEVEL = @start_session_value;
+SELECT @@session.ROCKSDB_PERF_CONTEXT_LEVEL;
+@@session.ROCKSDB_PERF_CONTEXT_LEVEL
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_path_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_path_basic.result
new file mode 100644
index 00000000..10b187d4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_path_basic.result
@@ -0,0 +1,13 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('abc');
+INSERT INTO valid_values VALUES('def');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+SET @start_global_value = @@global.ROCKSDB_PERSISTENT_CACHE_PATH;
+SELECT @start_global_value;
+@start_global_value
+
+"Trying to set variable @@global.ROCKSDB_PERSISTENT_CACHE_PATH to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_PERSISTENT_CACHE_PATH = 444;
+ERROR HY000: Variable 'rocksdb_persistent_cache_path' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_size_mb_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_size_mb_basic.result
new file mode 100644
index 00000000..d0971925
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_persistent_cache_size_mb_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_PERSISTENT_CACHE_SIZE_MB;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_PERSISTENT_CACHE_SIZE_MB to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_PERSISTENT_CACHE_SIZE_MB = 444;
+ERROR HY000: Variable 'rocksdb_persistent_cache_size_mb' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.result
new file mode 100644
index 00000000..c152ecf1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE = 444;
+ERROR HY000: Variable 'rocksdb_pin_l0_filter_and_index_blocks_in_cache' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_print_snapshot_conflict_queries_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_print_snapshot_conflict_queries_basic.result
new file mode 100644
index 00000000..02a4b404
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_print_snapshot_conflict_queries_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to 1"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = 1;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = DEFAULT;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+"Trying to set variable @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to 0"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = 0;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = DEFAULT;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+"Trying to set variable @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to on"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = on;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = DEFAULT;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+"Trying to set variable @@session.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = 444;
+ERROR HY000: Variable 'rocksdb_print_snapshot_conflict_queries' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to 'aaa'"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+"Trying to set variable @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES to 'bbb'"
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+SET @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES = @start_global_value;
+SELECT @@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES;
+@@global.ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rate_limiter_bytes_per_sec_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rate_limiter_bytes_per_sec_basic.result
new file mode 100644
index 00000000..9d194ad7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rate_limiter_bytes_per_sec_basic.result
@@ -0,0 +1,101 @@
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = 10000;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1), (1000), (1000000), (1000000000), (1000000000000);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\''), (3.14);
+SET @start_global_value = @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+SELECT @start_global_value;
+@start_global_value
+10000
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 1"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 1;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = DEFAULT;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 1000"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 1000;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = DEFAULT;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 1000000"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 1000000;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = DEFAULT;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 1000000000"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 1000000000;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = DEFAULT;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 1000000000000"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 1000000000000;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000000
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = DEFAULT;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000000
+"Trying to set variable @@session.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 444;
+ERROR HY000: Variable 'rocksdb_rate_limiter_bytes_per_sec' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 'aaa'"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000000
+"Trying to set variable @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC to 3.14"
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = 3.14;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+1000000000000
+SET @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC = @start_global_value;
+SELECT @@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC;
+@@global.ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+10000
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = 0;
+Warnings:
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = -1;
+Warnings:
+Warning 1292 Truncated incorrect rocksdb_rate_limiter_bytes_pe... value: '-1'
+Warning 1210 RocksDB: rocksdb_rate_limiter_bytes_per_sec cannot be dynamically changed to or from 0. Do a clean shutdown if you want to change it from or to 0.
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_basic.result
new file mode 100644
index 00000000..78837992
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_basic.result
@@ -0,0 +1,58 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('PK_SK');
+INSERT INTO valid_values VALUES('OFF');
+INSERT INTO valid_values VALUES('PK_ONLY');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('a');
+SET @start_global_value = @@global.ROCKSDB_READ_FREE_RPL;
+SELECT @start_global_value;
+@start_global_value
+OFF
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL to PK_SK"
+SET @@global.ROCKSDB_READ_FREE_RPL = PK_SK;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+PK_SK
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_READ_FREE_RPL = DEFAULT;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL to OFF"
+SET @@global.ROCKSDB_READ_FREE_RPL = OFF;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_READ_FREE_RPL = DEFAULT;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL to PK_ONLY"
+SET @@global.ROCKSDB_READ_FREE_RPL = PK_ONLY;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+PK_ONLY
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_READ_FREE_RPL = DEFAULT;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+"Trying to set variable @@session.ROCKSDB_READ_FREE_RPL to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_READ_FREE_RPL = 444;
+ERROR HY000: Variable 'rocksdb_read_free_rpl' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL to a"
+SET @@global.ROCKSDB_READ_FREE_RPL = a;
+Got one of the listed errors
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+SET @@global.ROCKSDB_READ_FREE_RPL = @start_global_value;
+SELECT @@global.ROCKSDB_READ_FREE_RPL;
+@@global.ROCKSDB_READ_FREE_RPL
+OFF
+SET GLOBAL ROCKSDB_READ_FREE_RPL=DEFAULT;
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_tables_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_tables_basic.result
new file mode 100644
index 00000000..2643eb08
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_read_free_rpl_tables_basic.result
@@ -0,0 +1,49 @@
+call mtr.add_suppression(".*Invalid pattern in rocksdb_read_free_rpl_tables.*");
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('a');
+INSERT INTO valid_values VALUES('b');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'*\'');
+SET @start_global_value = @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+SELECT @start_global_value;
+@start_global_value
+.*
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL_TABLES to a"
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = a;
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+a
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = DEFAULT;
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+.*
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL_TABLES to b"
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = b;
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+b
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = DEFAULT;
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+.*
+"Trying to set variable @@session.ROCKSDB_READ_FREE_RPL_TABLES to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_READ_FREE_RPL_TABLES = 444;
+ERROR HY000: Variable 'rocksdb_read_free_rpl_tables' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_READ_FREE_RPL_TABLES to '*'"
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = '*';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+.*
+SET @@global.ROCKSDB_READ_FREE_RPL_TABLES = @start_global_value;
+SELECT @@global.ROCKSDB_READ_FREE_RPL_TABLES;
+@@global.ROCKSDB_READ_FREE_RPL_TABLES
+.*
+SET GLOBAL ROCKSDB_READ_FREE_RPL_TABLES=NULL;
+SET GLOBAL ROCKSDB_READ_FREE_RPL_TABLES=DEFAULT;
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_records_in_range_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_records_in_range_basic.result
new file mode 100644
index 00000000..e866787e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_records_in_range_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_RECORDS_IN_RANGE;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_RECORDS_IN_RANGE;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE to 1"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = 1;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE to 0"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = 0;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE to 222333"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = 222333;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+222333
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE to 1"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = 1;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+0
+"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE to 0"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = 0;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+0
+"Trying to set variable @@session.ROCKSDB_RECORDS_IN_RANGE to 222333"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = 222333;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+222333
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = DEFAULT;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE to 'aaa'"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+"Trying to set variable @@global.ROCKSDB_RECORDS_IN_RANGE to 'bbb'"
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+SET @@global.ROCKSDB_RECORDS_IN_RANGE = @start_global_value;
+SELECT @@global.ROCKSDB_RECORDS_IN_RANGE;
+@@global.ROCKSDB_RECORDS_IN_RANGE
+0
+SET @@session.ROCKSDB_RECORDS_IN_RANGE = @start_session_value;
+SELECT @@session.ROCKSDB_RECORDS_IN_RANGE;
+@@session.ROCKSDB_RECORDS_IN_RANGE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_remove_mariabackup_checkpoint_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_remove_mariabackup_checkpoint_basic.result
new file mode 100644
index 00000000..01145cd2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_remove_mariabackup_checkpoint_basic.result
@@ -0,0 +1,4 @@
+SET GLOBAL rocksdb_create_checkpoint=CONCAT(@@rocksdb_datadir,'/mariabackup-checkpoint');
+SET GLOBAL rocksdb_remove_mariabackup_checkpoint=ON;
+SET GLOBAL rocksdb_create_checkpoint=CONCAT(@@rocksdb_datadir,'/mariabackup-checkpoint');
+SET GLOBAL rocksdb_remove_mariabackup_checkpoint=ON;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_reset_stats_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_reset_stats_basic.result
new file mode 100644
index 00000000..d585e734
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_reset_stats_basic.result
@@ -0,0 +1,97 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_RESET_STATS;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to 1"
+SET @@global.ROCKSDB_RESET_STATS = 1;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to 0"
+SET @@global.ROCKSDB_RESET_STATS = 0;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to on"
+SET @@global.ROCKSDB_RESET_STATS = on;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to off"
+SET @@global.ROCKSDB_RESET_STATS = off;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to true"
+SET @@global.ROCKSDB_RESET_STATS = true;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to false"
+SET @@global.ROCKSDB_RESET_STATS = false;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_RESET_STATS = DEFAULT;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@session.ROCKSDB_RESET_STATS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_RESET_STATS = 444;
+ERROR HY000: Variable 'rocksdb_reset_stats' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to 'aaa'"
+SET @@global.ROCKSDB_RESET_STATS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+"Trying to set variable @@global.ROCKSDB_RESET_STATS to 'bbb'"
+SET @@global.ROCKSDB_RESET_STATS = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+SET @@global.ROCKSDB_RESET_STATS = @start_global_value;
+SELECT @@global.ROCKSDB_RESET_STATS;
+@@global.ROCKSDB_RESET_STATS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rollback_on_timeout_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rollback_on_timeout_basic.result
new file mode 100644
index 00000000..e51df4f6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_rollback_on_timeout_basic.result
@@ -0,0 +1,97 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to 1"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = 1;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to 0"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = 0;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to on"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = on;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to off"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = off;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to true"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = true;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to false"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = false;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = DEFAULT;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@session.ROCKSDB_ROLLBACK_ON_TIMEOUT to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_ROLLBACK_ON_TIMEOUT = 444;
+ERROR HY000: Variable 'rocksdb_rollback_on_timeout' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to 'aaa'"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+"Trying to set variable @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT to 'bbb'"
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+SET @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT = @start_global_value;
+SELECT @@global.ROCKSDB_ROLLBACK_ON_TIMEOUT;
+@@global.ROCKSDB_ROLLBACK_ON_TIMEOUT
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_seconds_between_stat_computes_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_seconds_between_stat_computes_basic.result
new file mode 100644
index 00000000..ea80d88f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_seconds_between_stat_computes_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+SELECT @start_global_value;
+@start_global_value
+3600
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 1"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 1;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = DEFAULT;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+"Trying to set variable @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 0"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 0;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = DEFAULT;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+"Trying to set variable @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 1024"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 1024;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+1024
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = DEFAULT;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+"Trying to set variable @@session.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 444;
+ERROR HY000: Variable 'rocksdb_seconds_between_stat_computes' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 'aaa'"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+"Trying to set variable @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES to 'bbb'"
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+SET @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES = @start_global_value;
+SELECT @@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES;
+@@global.ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+3600
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_signal_drop_index_thread_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_signal_drop_index_thread_basic.result
new file mode 100644
index 00000000..94a15275
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_signal_drop_index_thread_basic.result
@@ -0,0 +1,64 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to 1"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = 1;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = DEFAULT;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Trying to set variable @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to 0"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = 0;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = DEFAULT;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Trying to set variable @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to on"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = on;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = DEFAULT;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Trying to set variable @@session.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = 444;
+ERROR HY000: Variable 'rocksdb_signal_drop_index_thread' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to 'aaa'"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+"Trying to set variable @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD to 'bbb'"
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+SET @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD = @start_global_value;
+SELECT @@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD;
+@@global.ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sim_cache_size_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sim_cache_size_basic.result
new file mode 100644
index 00000000..ef4c6194
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sim_cache_size_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_SIM_CACHE_SIZE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_SIM_CACHE_SIZE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_SIM_CACHE_SIZE = 444;
+ERROR HY000: Variable 'rocksdb_sim_cache_size' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_bloom_filter_on_read_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_bloom_filter_on_read_basic.result
new file mode 100644
index 00000000..201bc500
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_bloom_filter_on_read_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 1"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 1;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 0"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 0;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to on"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = on;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 1"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 1;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Trying to set variable @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 0"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 0;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Trying to set variable @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to on"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = on;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 'aaa'"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ to 'bbb'"
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+SET @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = @start_global_value;
+SELECT @@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@global.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+SET @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ = @start_session_value;
+SELECT @@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ;
+@@session.ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_fill_cache_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_fill_cache_basic.result
new file mode 100644
index 00000000..a843851c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_fill_cache_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_SKIP_FILL_CACHE;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_SKIP_FILL_CACHE;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SKIP_FILL_CACHE to 1"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = 1;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_FILL_CACHE to 0"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = 0;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_FILL_CACHE to on"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = on;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_SKIP_FILL_CACHE to 1"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = 1;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+0
+"Trying to set variable @@session.ROCKSDB_SKIP_FILL_CACHE to 0"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = 0;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+0
+"Trying to set variable @@session.ROCKSDB_SKIP_FILL_CACHE to on"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = on;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_SKIP_FILL_CACHE to 'aaa'"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+"Trying to set variable @@global.ROCKSDB_SKIP_FILL_CACHE to 'bbb'"
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+SET @@global.ROCKSDB_SKIP_FILL_CACHE = @start_global_value;
+SELECT @@global.ROCKSDB_SKIP_FILL_CACHE;
+@@global.ROCKSDB_SKIP_FILL_CACHE
+0
+SET @@session.ROCKSDB_SKIP_FILL_CACHE = @start_session_value;
+SELECT @@session.ROCKSDB_SKIP_FILL_CACHE;
+@@session.ROCKSDB_SKIP_FILL_CACHE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_unique_check_tables_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_unique_check_tables_basic.result
new file mode 100644
index 00000000..a3749b75
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_skip_unique_check_tables_basic.result
@@ -0,0 +1,67 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES("aaa");
+INSERT INTO valid_values VALUES("bbb");
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+SET @start_global_value = @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+SELECT @start_global_value;
+@start_global_value
+.*
+SET @start_session_value = @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+SELECT @start_session_value;
+@start_session_value
+.*
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES to aaa"
+SET @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = aaa;
+SELECT @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+aaa
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+"Trying to set variable @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES to bbb"
+SET @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = bbb;
+SELECT @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+bbb
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = DEFAULT;
+SELECT @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES to aaa"
+SET @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = aaa;
+SELECT @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+aaa
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+"Trying to set variable @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES to bbb"
+SET @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = bbb;
+SELECT @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+bbb
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = DEFAULT;
+SELECT @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+'# Testing with invalid values in global scope #'
+SET @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = @start_global_value;
+SELECT @@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@global.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+SET @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES = @start_session_value;
+SELECT @@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES;
+@@session.ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+.*
+SET GLOBAL ROCKSDB_SKIP_UNIQUE_CHECK_TABLES=NULL;
+SET GLOBAL ROCKSDB_SKIP_UNIQUE_CHECK_TABLES=DEFAULT;
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sst_mgr_rate_bytes_per_sec_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sst_mgr_rate_bytes_per_sec_basic.result
new file mode 100644
index 00000000..a714f1c2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_sst_mgr_rate_bytes_per_sec_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 100"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 100;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = DEFAULT;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 1"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 1;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = DEFAULT;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 0"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 0;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = DEFAULT;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@session.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 444;
+ERROR HY000: Variable 'rocksdb_sst_mgr_rate_bytes_per_sec' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 'aaa'"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to 'bbb'"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to '-1'"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to '101'"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+"Trying to set variable @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC to '484436'"
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+SET @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC = @start_global_value;
+SELECT @@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC;
+@@global.ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_dump_period_sec_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_dump_period_sec_basic.result
new file mode 100644
index 00000000..2dbf5a55
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_dump_period_sec_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_STATS_DUMP_PERIOD_SEC;
+SELECT @start_global_value;
+@start_global_value
+600
+"Trying to set variable @@global.ROCKSDB_STATS_DUMP_PERIOD_SEC to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_STATS_DUMP_PERIOD_SEC = 444;
+ERROR HY000: Variable 'rocksdb_stats_dump_period_sec' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_level_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_level_basic.result
new file mode 100644
index 00000000..d8d218fe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_level_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(4);
+INSERT INTO valid_values VALUES(2);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_STATS_LEVEL;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to 0"
+SET @@global.ROCKSDB_STATS_LEVEL = 0;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STATS_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to 4"
+SET @@global.ROCKSDB_STATS_LEVEL = 4;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+4
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STATS_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to 2"
+SET @@global.ROCKSDB_STATS_LEVEL = 2;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+2
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STATS_LEVEL = DEFAULT;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@session.ROCKSDB_STATS_LEVEL to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_STATS_LEVEL = 444;
+ERROR HY000: Variable 'rocksdb_stats_level' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to 'aaa'"
+SET @@global.ROCKSDB_STATS_LEVEL = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to 'bbb'"
+SET @@global.ROCKSDB_STATS_LEVEL = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to '-1'"
+SET @@global.ROCKSDB_STATS_LEVEL = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to '101'"
+SET @@global.ROCKSDB_STATS_LEVEL = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+"Trying to set variable @@global.ROCKSDB_STATS_LEVEL to '484436'"
+SET @@global.ROCKSDB_STATS_LEVEL = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+SET @@global.ROCKSDB_STATS_LEVEL = @start_global_value;
+SELECT @@global.ROCKSDB_STATS_LEVEL;
+@@global.ROCKSDB_STATS_LEVEL
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_recalc_rate_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_recalc_rate_basic.result
new file mode 100644
index 00000000..4f337b84
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_stats_recalc_rate_basic.result
@@ -0,0 +1,53 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+SET @start_global_value = @@global.ROCKSDB_STATS_RECALC_RATE;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_STATS_RECALC_RATE to 100"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = 100;
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = DEFAULT;
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+0
+"Trying to set variable @@global.ROCKSDB_STATS_RECALC_RATE to 1"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = 1;
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = DEFAULT;
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+0
+"Trying to set variable @@session.ROCKSDB_STATS_RECALC_RATE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_STATS_RECALC_RATE = 444;
+ERROR HY000: Variable 'rocksdb_stats_recalc_rate' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_STATS_RECALC_RATE to 'aaa'"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+0
+"Trying to set variable @@global.ROCKSDB_STATS_RECALC_RATE to '123'"
+SET @@global.ROCKSDB_STATS_RECALC_RATE = '123';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+0
+SET @@global.ROCKSDB_STATS_RECALC_RATE = @start_global_value;
+SELECT @@global.ROCKSDB_STATS_RECALC_RATE;
+@@global.ROCKSDB_STATS_RECALC_RATE
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_store_row_debug_checksums_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_store_row_debug_checksums_basic.result
new file mode 100644
index 00000000..a838d660
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_store_row_debug_checksums_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 1"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 1;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 0"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 0;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to on"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = on;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 1"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 1;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 0"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 0;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to on"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = on;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 'aaa'"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS to 'bbb'"
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+SET @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = @start_global_value;
+SELECT @@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+SET @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS = @start_session_value;
+SELECT @@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_check_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_check_basic.result
new file mode 100644
index 00000000..46d238d1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_check_basic.result
@@ -0,0 +1,75 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to 1"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = 1;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to 0"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = 0;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to on"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = on;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to off"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = off;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Trying to set variable @@session.ROCKSDB_STRICT_COLLATION_CHECK to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_STRICT_COLLATION_CHECK = 444;
+ERROR HY000: Variable 'rocksdb_strict_collation_check' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to 'aaa'"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+"Trying to set variable @@global.ROCKSDB_STRICT_COLLATION_CHECK to 'bbb'"
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+SET @@global.ROCKSDB_STRICT_COLLATION_CHECK = @start_global_value;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_CHECK;
+@@global.ROCKSDB_STRICT_COLLATION_CHECK
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_exceptions_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_exceptions_basic.result
new file mode 100644
index 00000000..5f748621
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_strict_collation_exceptions_basic.result
@@ -0,0 +1,36 @@
+SET @start_global_value = @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+SELECT @start_global_value;
+@start_global_value
+
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to simple table name."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = mytable;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+mytable
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to regex table name(s)."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "t.*";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+t.*
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to multiple regex table names."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "s.*,t.*";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+s.*,t.*
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to empty."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to default."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+
+"Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = 444;
+ERROR HY000: Variable 'rocksdb_strict_collation_exceptions' is a GLOBAL variable and should be set with SET GLOBAL
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = @start_global_value;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+@@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_supported_compression_types_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_supported_compression_types_basic.result
new file mode 100644
index 00000000..aa77d739
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_supported_compression_types_basic.result
@@ -0,0 +1,4 @@
+SET @start_global_value = @@global.ROCKSDB_SUPPORTED_COMPRESSION_TYPES;
+"Trying to set variable @@global.ROCKSDB_SUPPORTED_COMPRESSION_TYPES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_SUPPORTED_COMPRESSION_TYPES = 444;
+ERROR HY000: Variable 'rocksdb_supported_compression_types' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_cache_numshardbits_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_cache_numshardbits_basic.result
new file mode 100644
index 00000000..0161a339
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_cache_numshardbits_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_TABLE_CACHE_NUMSHARDBITS;
+SELECT @start_global_value;
+@start_global_value
+6
+"Trying to set variable @@global.ROCKSDB_TABLE_CACHE_NUMSHARDBITS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_TABLE_CACHE_NUMSHARDBITS = 444;
+ERROR HY000: Variable 'rocksdb_table_cache_numshardbits' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_stats_sampling_pct_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_stats_sampling_pct_basic.result
new file mode 100644
index 00000000..6ff47ab9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_table_stats_sampling_pct_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+SELECT @start_global_value;
+@start_global_value
+10
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 100"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 100;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 1"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 1;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 0"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 0;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = DEFAULT;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@session.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 444;
+ERROR HY000: Variable 'rocksdb_table_stats_sampling_pct' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 'aaa'"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to 'bbb'"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to '-1'"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to '101'"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+"Trying to set variable @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT to '484436'"
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+SET @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT = @start_global_value;
+SELECT @@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT;
+@@global.ROCKSDB_TABLE_STATS_SAMPLING_PCT
+10
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_tmpdir_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_tmpdir_basic.result
new file mode 100644
index 00000000..25b19ee5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_tmpdir_basic.result
@@ -0,0 +1,29 @@
+SET @start_global_value = @@global.rocksdb_tmpdir;
+SELECT @start_global_value;
+@start_global_value
+
+select @@session.rocksdb_tmpdir;
+@@session.rocksdb_tmpdir
+
+show global variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir
+show session variables like 'rocksdb_tmpdir';
+Variable_name Value
+rocksdb_tmpdir
+select * from information_schema.global_variables where variable_name='rocksdb_tmpdir';
+VARIABLE_NAME VARIABLE_VALUE
+ROCKSDB_TMPDIR
+select * from information_schema.session_variables where variable_name='rocksdb_tmpdir';
+VARIABLE_NAME VARIABLE_VALUE
+ROCKSDB_TMPDIR
+set global rocksdb_tmpdir='value';
+set session rocksdb_tmpdir='value';
+set global rocksdb_tmpdir=1.1;
+ERROR 42000: Incorrect argument type to variable 'rocksdb_tmpdir'
+set global rocksdb_tmpdir=1e1;
+ERROR 42000: Incorrect argument type to variable 'rocksdb_tmpdir'
+SET @@global.rocksdb_tmpdir = @start_global_value;
+SELECT @@global.rocksdb_tmpdir;
+@@global.rocksdb_tmpdir
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_trace_sst_api_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_trace_sst_api_basic.result
new file mode 100644
index 00000000..d4ffde80
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_trace_sst_api_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_TRACE_SST_API;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_TRACE_SST_API;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_TRACE_SST_API to 1"
+SET @@global.ROCKSDB_TRACE_SST_API = 1;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+"Trying to set variable @@global.ROCKSDB_TRACE_SST_API to 0"
+SET @@global.ROCKSDB_TRACE_SST_API = 0;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+"Trying to set variable @@global.ROCKSDB_TRACE_SST_API to on"
+SET @@global.ROCKSDB_TRACE_SST_API = on;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_TRACE_SST_API to 1"
+SET @@session.ROCKSDB_TRACE_SST_API = 1;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+0
+"Trying to set variable @@session.ROCKSDB_TRACE_SST_API to 0"
+SET @@session.ROCKSDB_TRACE_SST_API = 0;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+0
+"Trying to set variable @@session.ROCKSDB_TRACE_SST_API to on"
+SET @@session.ROCKSDB_TRACE_SST_API = on;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_TRACE_SST_API = DEFAULT;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_TRACE_SST_API to 'aaa'"
+SET @@global.ROCKSDB_TRACE_SST_API = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+"Trying to set variable @@global.ROCKSDB_TRACE_SST_API to 'bbb'"
+SET @@global.ROCKSDB_TRACE_SST_API = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+SET @@global.ROCKSDB_TRACE_SST_API = @start_global_value;
+SELECT @@global.ROCKSDB_TRACE_SST_API;
+@@global.ROCKSDB_TRACE_SST_API
+0
+SET @@session.ROCKSDB_TRACE_SST_API = @start_session_value;
+SELECT @@session.ROCKSDB_TRACE_SST_API;
+@@session.ROCKSDB_TRACE_SST_API
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_two_write_queues_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_two_write_queues_basic.result
new file mode 100644
index 00000000..5a19016b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_two_write_queues_basic.result
@@ -0,0 +1,14 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_TWO_WRITE_QUEUES;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_TWO_WRITE_QUEUES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_TWO_WRITE_QUEUES = 444;
+ERROR HY000: Variable 'rocksdb_two_write_queues' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_unsafe_for_binlog_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_unsafe_for_binlog_basic.result
new file mode 100644
index 00000000..c9748cc6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_unsafe_for_binlog_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_UNSAFE_FOR_BINLOG to 1"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = 1;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Trying to set variable @@global.ROCKSDB_UNSAFE_FOR_BINLOG to 0"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = 0;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Trying to set variable @@global.ROCKSDB_UNSAFE_FOR_BINLOG to on"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = on;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_UNSAFE_FOR_BINLOG to 1"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = 1;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Trying to set variable @@session.ROCKSDB_UNSAFE_FOR_BINLOG to 0"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = 0;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Trying to set variable @@session.ROCKSDB_UNSAFE_FOR_BINLOG to on"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = on;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = DEFAULT;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_UNSAFE_FOR_BINLOG to 'aaa'"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+"Trying to set variable @@global.ROCKSDB_UNSAFE_FOR_BINLOG to 'bbb'"
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+SET @@global.ROCKSDB_UNSAFE_FOR_BINLOG = @start_global_value;
+SELECT @@global.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@global.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+SET @@session.ROCKSDB_UNSAFE_FOR_BINLOG = @start_session_value;
+SELECT @@session.ROCKSDB_UNSAFE_FOR_BINLOG;
+@@session.ROCKSDB_UNSAFE_FOR_BINLOG
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options.result
new file mode 100644
index 00000000..126b4cff
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options.result
@@ -0,0 +1,38 @@
+CREATE TABLE t1 (a INT, PRIMARY KEY (a) COMMENT 'update_cf1') ENGINE=ROCKSDB;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+update_cf1={write_buffer_size=8m;target_file_size_base=2m};
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS="";
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options update_cf1={write_buffer_size=8m;target_file_size_base=2m};
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf2={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options update_cf2={write_buffer_size=8m;target_file_size_base=2m};
+DROP TABLE t1;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options update_cf1={write_buffer_size=8m;target_file_size_base=2m};
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=DEFAULT;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+Variable_name Value
+rocksdb_update_cf_options
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options_basic.result
new file mode 100644
index 00000000..dcc7e1f6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_update_cf_options_basic.result
@@ -0,0 +1,126 @@
+call mtr.add_suppression("MyRocks: NULL is not a valid option for updates to column family settings.");
+call mtr.add_suppression("Invalid cf options, '=' expected *");
+call mtr.add_suppression("MyRocks: failed to parse the updated column family options = *");
+call mtr.add_suppression("Invalid cf config for default in override options *");
+DROP TABLE IF EXISTS t1;
+Warnings:
+Note 1051 Unknown table 'test.t1'
+CREATE TABLE `t1` (
+`col1` bigint(20) NOT NULL,
+`col2` varbinary(64) NOT NULL,
+`col3` varbinary(256) NOT NULL,
+`col4` bigint(20) NOT NULL,
+`col5` mediumblob NOT NULL,
+PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=cf1;custom_p1_cfname=cf2',
+UNIQUE KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=cf3'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+PARTITION BY LIST COLUMNS (`col2`) (
+PARTITION custom_p0 VALUES IN (0x12345),
+PARTITION custom_p1 VALUES IN (0x23456),
+PARTITION custom_p2 VALUES IN (0x34567),
+PARTITION custom_p3 VALUES IN (0x45678),
+PARTITION custom_p4 VALUES IN (0x56789),
+PARTITION custom_p5 VALUES IN (0x6789A),
+PARTITION custom_p6 VALUES IN (0x789AB),
+PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+USE information_schema;
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+NULL
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+NULL
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+NULL
+SET @@global.rocksdb_update_cf_options = '';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+
+SET @@global.rocksdb_update_cf_options = 'aaaaa';;
+ERROR 42000: Variable 'rocksdb_update_cf_options' can't be set to the value of 'aaaaa'
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+default WRITE_BUFFER_SIZE 67108864
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+default TARGET_FILE_SIZE_BASE 67108864
+SET @@global.rocksdb_update_cf_options = 'default={write_buffer_size=8m;target_file_size_base=2m};';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+default={write_buffer_size=8m;target_file_size_base=2m};
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+default WRITE_BUFFER_SIZE 8388608
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+default TARGET_FILE_SIZE_BASE 2097152
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+cf1 WRITE_BUFFER_SIZE 67108864
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf1 TARGET_FILE_SIZE_BASE 67108864
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+cf2 WRITE_BUFFER_SIZE 67108864
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='MAX_BYTES_FOR_LEVEL_MULTIPLIER';
+CF_NAME OPTION_TYPE VALUE
+cf2 MAX_BYTES_FOR_LEVEL_MULTIPLIER 10.000000
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf3 TARGET_FILE_SIZE_BASE 67108864
+SET @@global.rocksdb_update_cf_options = 'cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};cf3={target_file_size_base=4m};';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};cf3={target_file_size_base=4m};
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+cf1 WRITE_BUFFER_SIZE 8388608
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf1 TARGET_FILE_SIZE_BASE 2097152
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+CF_NAME OPTION_TYPE VALUE
+cf2 WRITE_BUFFER_SIZE 16777216
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='MAX_BYTES_FOR_LEVEL_MULTIPLIER';
+CF_NAME OPTION_TYPE VALUE
+cf2 MAX_BYTES_FOR_LEVEL_MULTIPLIER 8.000000
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf3 TARGET_FILE_SIZE_BASE 4194304
+SET @@global.rocksdb_update_cf_options = 'cf3={target_file_size_base=24m};';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+cf3={target_file_size_base=24m};
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf3 TARGET_FILE_SIZE_BASE 25165824
+SET @@global.rocksdb_update_cf_options = 'cf1={target_file_size_base=24m};foo={max_bytes_for_level_multiplier=8};';
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+cf1={target_file_size_base=24m};foo={max_bytes_for_level_multiplier=8};
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+CF_NAME OPTION_TYPE VALUE
+cf1 TARGET_FILE_SIZE_BASE 25165824
+SET @@global.rocksdb_update_cf_options = 'default={foo=bar};';;
+ERROR 42000: Variable 'rocksdb_update_cf_options' can't be set to the value of 'default={foo=bar};'
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+cf1={target_file_size_base=24m};foo={max_bytes_for_level_multiplier=8};
+SET @@global.rocksdb_update_cf_options = 'default={write_buffer_size=67108864;target_file_size_base=67108864};';
+SET @@global.rocksdb_update_cf_options = 'cf1={write_buffer_size=67108864;target_file_size_base=67108864};';
+SET @@global.rocksdb_update_cf_options = 'cf2={write_buffer_size=67108864;target_file_size_base=67108864;max_bytes_for_level_multiplier=10.000000};';
+SET @@global.rocksdb_update_cf_options = 'cf3={write_buffer_size=67108864;target_file_size_base=67108864};';
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+@@global.rocksdb_update_cf_options
+NULL
+USE test;
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_adaptive_mutex_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_adaptive_mutex_basic.result
new file mode 100644
index 00000000..ef4007c7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_adaptive_mutex_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_USE_ADAPTIVE_MUTEX;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_USE_ADAPTIVE_MUTEX to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_USE_ADAPTIVE_MUTEX = 444;
+ERROR HY000: Variable 'rocksdb_use_adaptive_mutex' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_clock_cache_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_clock_cache_basic.result
new file mode 100644
index 00000000..f23d1889
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_clock_cache_basic.result
@@ -0,0 +1,19 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_USE_CLOCK_CACHE;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_USE_CLOCK_CACHE to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_USE_CLOCK_CACHE = 444;
+ERROR HY000: Variable 'rocksdb_use_clock_cache' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_io_for_flush_and_compaction_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_io_for_flush_and_compaction_basic.result
new file mode 100644
index 00000000..219cdb73
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_io_for_flush_and_compaction_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION = 444;
+ERROR HY000: Variable 'rocksdb_use_direct_io_for_flush_and_compaction' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_reads_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_reads_basic.result
new file mode 100644
index 00000000..ec36c309
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_direct_reads_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_USE_DIRECT_READS;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_USE_DIRECT_READS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_USE_DIRECT_READS = 444;
+ERROR HY000: Variable 'rocksdb_use_direct_reads' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_fsync_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_fsync_basic.result
new file mode 100644
index 00000000..254cc2ce
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_use_fsync_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_USE_FSYNC;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_USE_FSYNC to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_USE_FSYNC = 444;
+ERROR HY000: Variable 'rocksdb_use_fsync' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_validate_tables_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_validate_tables_basic.result
new file mode 100644
index 00000000..c7b87487
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_validate_tables_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_VALIDATE_TABLES;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_VALIDATE_TABLES to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_VALIDATE_TABLES = 444;
+ERROR HY000: Variable 'rocksdb_validate_tables' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_verify_row_debug_checksums_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_verify_row_debug_checksums_basic.result
new file mode 100644
index 00000000..ad71c890
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_verify_row_debug_checksums_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 1"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 1;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 0"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 0;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to on"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = on;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 1"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 1;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 0"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 0;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to on"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = on;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = DEFAULT;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 'aaa'"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+"Trying to set variable @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS to 'bbb'"
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+SET @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = @start_global_value;
+SELECT @@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@global.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+SET @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS = @start_session_value;
+SELECT @@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS;
+@@session.ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_bytes_per_sync_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_bytes_per_sync_basic.result
new file mode 100644
index 00000000..f432f1f7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_bytes_per_sync_basic.result
@@ -0,0 +1,85 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+SET @start_global_value = @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+SELECT @start_global_value;
+@start_global_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to 100"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = 100;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+100
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to 1"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = 1;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to 0"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = 0;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = DEFAULT;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@session.ROCKSDB_WAL_BYTES_PER_SYNC to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_WAL_BYTES_PER_SYNC = 444;
+ERROR HY000: Variable 'rocksdb_wal_bytes_per_sync' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to 'aaa'"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to 'bbb'"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to '-1'"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = '-1';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to '101'"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = '101';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+"Trying to set variable @@global.ROCKSDB_WAL_BYTES_PER_SYNC to '484436'"
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = '484436';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+SET @@global.ROCKSDB_WAL_BYTES_PER_SYNC = @start_global_value;
+SELECT @@global.ROCKSDB_WAL_BYTES_PER_SYNC;
+@@global.ROCKSDB_WAL_BYTES_PER_SYNC
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_dir_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_dir_basic.result
new file mode 100644
index 00000000..fd76a5ec
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_dir_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_WAL_DIR;
+SELECT @start_global_value;
+@start_global_value
+
+"Trying to set variable @@global.ROCKSDB_WAL_DIR to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_WAL_DIR = 444;
+ERROR HY000: Variable 'rocksdb_wal_dir' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_recovery_mode_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_recovery_mode_basic.result
new file mode 100644
index 00000000..9fec4a24
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_recovery_mode_basic.result
@@ -0,0 +1,46 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_WAL_RECOVERY_MODE;
+SELECT @start_global_value;
+@start_global_value
+1
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_WAL_RECOVERY_MODE to 1"
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = 1;
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = DEFAULT;
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+1
+"Trying to set variable @@global.ROCKSDB_WAL_RECOVERY_MODE to 0"
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = 0;
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = DEFAULT;
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+1
+"Trying to set variable @@session.ROCKSDB_WAL_RECOVERY_MODE to 444. It should fail because it is not session."
+SET @@session.ROCKSDB_WAL_RECOVERY_MODE = 444;
+ERROR HY000: Variable 'rocksdb_wal_recovery_mode' is a GLOBAL variable and should be set with SET GLOBAL
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_WAL_RECOVERY_MODE to 'aaa'"
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+1
+SET @@global.ROCKSDB_WAL_RECOVERY_MODE = @start_global_value;
+SELECT @@global.ROCKSDB_WAL_RECOVERY_MODE;
+@@global.ROCKSDB_WAL_RECOVERY_MODE
+1
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_size_limit_mb_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_size_limit_mb_basic.result
new file mode 100644
index 00000000..5f03597d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_size_limit_mb_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_WAL_SIZE_LIMIT_MB;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_WAL_SIZE_LIMIT_MB to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_WAL_SIZE_LIMIT_MB = 444;
+ERROR HY000: Variable 'rocksdb_wal_size_limit_mb' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_ttl_seconds_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_ttl_seconds_basic.result
new file mode 100644
index 00000000..23f7fc81
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_wal_ttl_seconds_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_WAL_TTL_SECONDS;
+SELECT @start_global_value;
+@start_global_value
+0
+"Trying to set variable @@global.ROCKSDB_WAL_TTL_SECONDS to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_WAL_TTL_SECONDS = 444;
+ERROR HY000: Variable 'rocksdb_wal_ttl_seconds' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_whole_key_filtering_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_whole_key_filtering_basic.result
new file mode 100644
index 00000000..0d6f7216
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_whole_key_filtering_basic.result
@@ -0,0 +1,7 @@
+SET @start_global_value = @@global.ROCKSDB_WHOLE_KEY_FILTERING;
+SELECT @start_global_value;
+@start_global_value
+1
+"Trying to set variable @@global.ROCKSDB_WHOLE_KEY_FILTERING to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_WHOLE_KEY_FILTERING = 444;
+ERROR HY000: Variable 'rocksdb_whole_key_filtering' is a read only variable
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_batch_max_bytes_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_batch_max_bytes_basic.result
new file mode 100644
index 00000000..8f6d91b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_batch_max_bytes_basic.result
@@ -0,0 +1,15 @@
+create table t (i int);
+insert into t values (1), (2), (3), (4), (5);
+set session rocksdb_write_batch_max_bytes = 1000;
+insert into t values (1), (2), (3), (4), (5);
+set session rocksdb_write_batch_max_bytes = 10;
+insert into t values (1), (2), (3), (4), (5);
+ERROR HY000: Got error 10 'Operation aborted: Memory limit reached' from ROCKSDB
+set session rocksdb_write_batch_max_bytes = 0;
+insert into t values (1), (2), (3), (4), (5);
+set session rocksdb_write_batch_max_bytes = 10;
+begin;
+insert into t values (1), (2), (3), (4), (5);
+ERROR HY000: Got error 10 'Operation aborted: Memory limit reached' from ROCKSDB
+rollback;
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_disable_wal_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_disable_wal_basic.result
new file mode 100644
index 00000000..b71ee7f9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_disable_wal_basic.result
@@ -0,0 +1,114 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_WRITE_DISABLE_WAL;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_WRITE_DISABLE_WAL;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_WRITE_DISABLE_WAL to 1"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = 1;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_DISABLE_WAL to 0"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = 0;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_DISABLE_WAL to on"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = on;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_DISABLE_WAL to off"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = off;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_WRITE_DISABLE_WAL to 1"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = 1;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@session.ROCKSDB_WRITE_DISABLE_WAL to 0"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = 0;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@session.ROCKSDB_WRITE_DISABLE_WAL to on"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = on;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Trying to set variable @@session.ROCKSDB_WRITE_DISABLE_WAL to off"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = off;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_WRITE_DISABLE_WAL to 'aaa'"
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+SET @@global.ROCKSDB_WRITE_DISABLE_WAL = @start_global_value;
+SELECT @@global.ROCKSDB_WRITE_DISABLE_WAL;
+@@global.ROCKSDB_WRITE_DISABLE_WAL
+0
+SET @@session.ROCKSDB_WRITE_DISABLE_WAL = @start_session_value;
+SELECT @@session.ROCKSDB_WRITE_DISABLE_WAL;
+@@session.ROCKSDB_WRITE_DISABLE_WAL
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_ignore_missing_column_families_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_ignore_missing_column_families_basic.result
new file mode 100644
index 00000000..dbe46858
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_ignore_missing_column_families_basic.result
@@ -0,0 +1,100 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+SET @start_global_value = @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+SELECT @start_global_value;
+@start_global_value
+0
+SET @start_session_value = @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+SELECT @start_session_value;
+@start_session_value
+0
+'# Setting to valid values in global scope#'
+"Trying to set variable @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 1"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 1;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 0"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 0;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to on"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = on;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+1
+"Setting the global scope variable back to default"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+'# Setting to valid values in session scope#'
+"Trying to set variable @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 1"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 1;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Trying to set variable @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 0"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 0;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Trying to set variable @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to on"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = on;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+1
+"Setting the session scope variable back to default"
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = DEFAULT;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+'# Testing with invalid values in global scope #'
+"Trying to set variable @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 'aaa'"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 'aaa';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+"Trying to set variable @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES to 'bbb'"
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = 'bbb';
+Got one of the listed errors
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+SET @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = @start_global_value;
+SELECT @@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@global.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+SET @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES = @start_session_value;
+SELECT @@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES;
+@@session.ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+0
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_policy_basic.result b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_policy_basic.result
new file mode 100644
index 00000000..58e040e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/r/rocksdb_write_policy_basic.result
@@ -0,0 +1,15 @@
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES("write_committed");
+INSERT INTO valid_values VALUES("write_prepared");
+INSERT INTO valid_values VALUES("write_unprepared");
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+SET @start_global_value = @@global.ROCKSDB_WRITE_POLICY;
+SELECT @start_global_value;
+@start_global_value
+write_committed
+"Trying to set variable @@global.ROCKSDB_WRITE_POLICY to 444. It should fail because it is readonly."
+SET @@global.ROCKSDB_WRITE_POLICY = 444;
+ERROR HY000: Variable 'rocksdb_write_policy' is a read only variable
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.opt b/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.opt
new file mode 100644
index 00000000..f6640ca5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.opt
@@ -0,0 +1 @@
+--ignore-db-dirs=#rocksdb --plugin-load=$HA_ROCKSDB_SO
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.pm b/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.pm
new file mode 100644
index 00000000..5264d657
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/suite.pm
@@ -0,0 +1,21 @@
+package My::Suite::Rocksdb_sys_vars;
+
+#
+# Note: The below is copied from ../rocksdb/suite.pm
+#
+@ISA = qw(My::Suite);
+use My::Find;
+use File::Basename;
+use strict;
+
+#sub is_default { not $::opt_embedded_server }
+
+my $sst_dump=
+::mtr_exe_maybe_exists(
+ "$::bindir/storage/rocksdb$::opt_vs_config/sst_dump",
+ "$::path_client_bindir/sst_dump");
+return "RocksDB is not compiled, no sst_dump" unless $sst_dump;
+$ENV{MARIAROCKS_SST_DUMP}="$sst_dump";
+
+bless { };
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/all_vars.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/all_vars.test
new file mode 100644
index 00000000..7943d3e4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/all_vars.test
@@ -0,0 +1,39 @@
+--source include/have_rocksdb.inc
+--source include/not_embedded.inc
+
+# This test verifies that *all* MyRocks system variables are tested by the
+# rocksdb_sys_vars suite. For every MyRocks system variable there must be a
+# <variable_name>_basic.test file.
+#
+
+#
+# we can diff in perl or in sql, as it's my_SQL_test suite, do it in sql
+#
+
+perl;
+ use File::Basename;
+ my $dirname=dirname($ENV{MYSQLTEST_FILE});
+ my @all_tests=<$dirname/*_basic{,_32,_64}.test>;
+ open(F, '>', "$ENV{MYSQLTEST_VARDIR}/tmp/rocksdb_sys_vars.all_vars.txt") or die;
+ binmode F;
+ print F join "\n", sort map { s/_basic(_32|_64)?\.test$//; basename $_ } @all_tests;
+EOF
+
+create table t1 (test_name text) engine=MyISAM;
+create table t2 (variable_name text) engine=MyISAM;
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+eval load data infile "$MYSQLTEST_VARDIR/tmp/rocksdb_sys_vars.all_vars.txt" into table t1;
+
+insert into t2 select variable_name from information_schema.global_variables where variable_name like "rocksdb_%";
+insert into t2 select variable_name from information_schema.session_variables where variable_name like "rocksdb_%";
+
+--sorted_result
+select variable_name as `There should be *no* long test name listed below:` from t2
+ where length(variable_name) > 50;
+
+--sorted_result
+select variable_name as `There should be *no* variables listed below:` from t2
+ left join t1 on variable_name=test_name where test_name is null ORDER BY variable_name;
+
+drop table t1;
+drop table t2;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/disabled.def b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/disabled.def
new file mode 100644
index 00000000..efa82ff6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/disabled.def
@@ -0,0 +1,5 @@
+# Disabled tests
+
+rocksdb_read_free_rpl_basic : MariaDB: Read-Free replication is not supported
+rocksdb_read_free_rpl_tables_basic : MariaDB: Read-Free replication is not supported
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_access_hint_on_compaction_start_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_access_hint_on_compaction_start_basic.test
new file mode 100644
index 00000000..51765574
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_access_hint_on_compaction_start_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ACCESS_HINT_ON_COMPACTION_START
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_advise_random_on_open_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_advise_random_on_open_basic.test
new file mode 100644
index 00000000..06593363
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_advise_random_on_open_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ADVISE_RANDOM_ON_OPEN
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_concurrent_memtable_write_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_concurrent_memtable_write_basic.test
new file mode 100644
index 00000000..5d4e3c05
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_concurrent_memtable_write_basic.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+--let $sys_var=ROCKSDB_ALLOW_CONCURRENT_MEMTABLE_WRITE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_reads_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_reads_basic.test
new file mode 100644
index 00000000..72fed6b2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_reads_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ALLOW_MMAP_READS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_writes_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_writes_basic.test
new file mode 100644
index 00000000..8b8da14f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_mmap_writes_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ALLOW_MMAP_WRITES
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_to_start_after_corruption_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_to_start_after_corruption_basic.test
new file mode 100644
index 00000000..64fb2458
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_allow_to_start_after_corruption_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ALLOW_TO_START_AFTER_CORRUPTION
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_blind_delete_primary_key_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_blind_delete_primary_key_basic.test
new file mode 100644
index 00000000..da972ccc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_blind_delete_primary_key_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_BLIND_DELETE_PRIMARY_KEY
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_cache_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_cache_size_basic.test
new file mode 100644
index 00000000..8d3bab4e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_cache_size_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(65536);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(1*1024*1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_BLOCK_CACHE_SIZE
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_restart_interval_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_restart_interval_basic.test
new file mode 100644
index 00000000..0688ef73
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_restart_interval_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_BLOCK_RESTART_INTERVAL
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_basic.test
new file mode 100644
index 00000000..150c1e53
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_BLOCK_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_deviation_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_deviation_basic.test
new file mode 100644
index 00000000..98d179c0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_block_size_deviation_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_BLOCK_SIZE_DEVIATION
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_sk_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_sk_basic.test
new file mode 100644
index 00000000..14e5f38b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_sk_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_BULK_LOAD_ALLOW_SK
+--let $read_only=0
+--let $session=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_unsorted_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_unsorted_basic.test
new file mode 100644
index 00000000..e57396e0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_allow_unsorted_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_BULK_LOAD_ALLOW_UNSORTED
+--let $read_only=0
+--let $session=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_basic.test
new file mode 100644
index 00000000..dd55c849
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_BULK_LOAD
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_size_basic.test
new file mode 100644
index 00000000..70d1c448
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bulk_load_size_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_BULK_LOAD_SIZE
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bytes_per_sync_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bytes_per_sync_basic.test
new file mode 100644
index 00000000..bf78f578
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_bytes_per_sync_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_BYTES_PER_SYNC
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_dump_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_dump_basic.test
new file mode 100644
index 00000000..70557621
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_dump_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_CACHE_DUMP
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_high_pri_pool_ratio_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_high_pri_pool_ratio_basic.test
new file mode 100644
index 00000000..d97c4e20
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_high_pri_pool_ratio_basic.test
@@ -0,0 +1,24 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1.0);
+INSERT INTO valid_values VALUES(0.0);
+INSERT INTO valid_values VALUES(0.5);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES(2.0);
+INSERT INTO invalid_values VALUES(-0.5);
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+INSERT INTO invalid_values VALUES('\'0.5\'');
+
+--let $sys_var=ROCKSDB_CACHE_HIGH_PRI_POOL_RATIO
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_blocks_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_blocks_basic.test
new file mode 100644
index 00000000..27d0aa99
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_blocks_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_CACHE_INDEX_AND_FILTER_BLOCKS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_with_high_priority_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_with_high_priority_basic.test
new file mode 100644
index 00000000..5ed7927f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_cache_index_and_filter_with_high_priority_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_CACHE_INDEX_AND_FILTER_WITH_HIGH_PRIORITY
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_checksums_pct_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_checksums_pct_basic.test
new file mode 100644
index 00000000..b595cb62
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_checksums_pct_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(99);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_CHECKSUMS_PCT
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_collect_sst_properties_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_collect_sst_properties_basic.test
new file mode 100644
index 00000000..9c0e111d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_collect_sst_properties_basic.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_COLLECT_SST_PROPERTIES
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_in_the_middle_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_in_the_middle_basic.test
new file mode 100644
index 00000000..ec860cfc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_in_the_middle_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_COMMIT_IN_THE_MIDDLE
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_time_batch_for_recovery_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_time_batch_for_recovery_basic.test
new file mode 100644
index 00000000..0d36e0a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_commit_time_batch_for_recovery_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255));
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255));
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_COMMIT_TIME_BATCH_FOR_RECOVERY
+--let $read_only=0
+--let $session=1
+--let $sticky=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compact_cf_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compact_cf_basic.test
new file mode 100644
index 00000000..736f6754
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compact_cf_basic.test
@@ -0,0 +1,19 @@
+
+call mtr.add_suppression(" Column family '[a-z]*' not found.");
+
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('abc');
+INSERT INTO valid_values VALUES('def');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+
+--let $sys_var=ROCKSDB_COMPACT_CF
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_readahead_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_readahead_size_basic.test
new file mode 100644
index 00000000..c0651a3a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_readahead_size_basic.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+# Attempt to set the value to -1 - this should first truncate to 0 and then generate a warning as
+# we can't set it to or from 0
+SET @@global.rocksdb_compaction_readahead_size = -1;
+SELECT @@global.rocksdb_compaction_readahead_size;
+
+--let $sys_var=ROCKSDB_COMPACTION_READAHEAD_SIZE
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_basic.test
new file mode 100644
index 00000000..24399c85
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(2000000);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'2000001\'');
+
+--let $sys_var=ROCKSDB_COMPACTION_SEQUENTIAL_DELETES
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_count_sd_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_count_sd_basic.test
new file mode 100644
index 00000000..b3a437d6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_count_sd_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_COUNT_SD
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_file_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_file_size_basic.test
new file mode 100644
index 00000000..aaf71179
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_file_size_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_FILE_SIZE
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_window_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_window_basic.test
new file mode 100644
index 00000000..d5be3469
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_compaction_sequential_deletes_window_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(2000000);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'2000001\'');
+
+--let $sys_var=ROCKSDB_COMPACTION_SEQUENTIAL_DELETES_WINDOW
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_checkpoint_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_checkpoint_basic.test
new file mode 100644
index 00000000..32d4a6a2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_checkpoint_basic.test
@@ -0,0 +1,29 @@
+--source include/have_rocksdb.inc
+
+--eval SET @start_value = @@global.ROCKSDB_CREATE_CHECKPOINT
+
+# Test using tmp/abc
+--replace_result $MYSQL_TMP_DIR TMP
+--eval SET @@global.ROCKSDB_CREATE_CHECKPOINT = '$MYSQL_TMP_DIR/abc'
+--eval SELECT @@global.ROCKSDB_CREATE_CHECKPOINT
+--eval SET @@global.ROCKSDB_CREATE_CHECKPOINT = DEFAULT
+
+# Test using tmp/def
+--replace_result $MYSQL_TMP_DIR TMP
+--eval SET @@global.ROCKSDB_CREATE_CHECKPOINT = '$MYSQL_TMP_DIR/def'
+--eval SELECT @@global.ROCKSDB_CREATE_CHECKPOINT
+--eval SET @@global.ROCKSDB_CREATE_CHECKPOINT = DEFAULT
+
+# Should fail because it is not a session
+--Error ER_GLOBAL_VARIABLE
+--eval SET @@session.ROCKSDB_CREATE_CHECKPOINT = 444
+
+# Set back to original value
+# validate that DEFAULT causes failure in creating checkpoint since
+# DEFAULT == ''
+--error ER_GET_ERRMSG
+--eval SET @@global.ROCKSDB_CREATE_CHECKPOINT = @start_value
+
+# clean up
+--exec rm -r $MYSQL_TMP_DIR/abc
+--exec rm -r $MYSQL_TMP_DIR/def
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_if_missing_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_if_missing_basic.test
new file mode 100644
index 00000000..ab92a0a0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_if_missing_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_CREATE_IF_MISSING
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_missing_column_families_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_missing_column_families_basic.test
new file mode 100644
index 00000000..21c0f0ea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_create_missing_column_families_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_CREATE_MISSING_COLUMN_FAMILIES
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_datadir_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_datadir_basic.test
new file mode 100644
index 00000000..fd3569c8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_datadir_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_DATADIR
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_db_write_buffer_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_db_write_buffer_size_basic.test
new file mode 100644
index 00000000..df6a2490
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_db_write_buffer_size_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_DB_WRITE_BUFFER_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_basic.test
new file mode 100644
index 00000000..980be0f3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255));
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255));
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_DEADLOCK_DETECT
+--let $read_only=0
+--let $session=1
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_depth_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_depth_basic.test
new file mode 100644
index 00000000..cab72a11
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_deadlock_detect_depth_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(2);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+
+--let $sys_var=ROCKSDB_DEADLOCK_DETECT_DEPTH
+--let $read_only=0
+--let $session=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_manual_compaction_delay_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_manual_compaction_delay_basic.test
new file mode 100644
index 00000000..518c284c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_manual_compaction_delay_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(100000);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_DEBUG_MANUAL_COMPACTION_DELAY
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_optimizer_no_zero_cardinality_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_optimizer_no_zero_cardinality_basic.test
new file mode 100644
index 00000000..41c4ae63
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_optimizer_no_zero_cardinality_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_DEBUG_OPTIMIZER_NO_ZERO_CARDINALITY
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_ignore_pk_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_ignore_pk_basic.test
new file mode 100644
index 00000000..8ad071e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_ignore_pk_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_DEBUG_TTL_IGNORE_PK
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_read_filter_ts_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_read_filter_ts_basic.test
new file mode 100644
index 00000000..c3837ff1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_read_filter_ts_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_DEBUG_TTL_READ_FILTER_TS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_rec_ts_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_rec_ts_basic.test
new file mode 100644
index 00000000..14c3e3d3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_rec_ts_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_DEBUG_TTL_REC_TS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_snapshot_ts_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_snapshot_ts_basic.test
new file mode 100644
index 00000000..af507fbe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_debug_ttl_snapshot_ts_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2400);
+INSERT INTO valid_values VALUES(-2400);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_DEBUG_TTL_SNAPSHOT_TS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_default_cf_options_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_default_cf_options_basic.test
new file mode 100644
index 00000000..1febc6db
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_default_cf_options_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_DEFAULT_CF_OPTIONS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delayed_write_rate_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delayed_write_rate_basic.test
new file mode 100644
index 00000000..8068b6b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delayed_write_rate_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_DELAYED_WRITE_RATE
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic-master.opt b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic-master.opt
new file mode 100644
index 00000000..ae43ab33
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic-master.opt
@@ -0,0 +1 @@
+--loose-rocksdb-dummy-option-instead-of-force-restart
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic.test
new file mode 100644
index 00000000..0875e492
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_cf_basic.test
@@ -0,0 +1,75 @@
+--disable_query_log
+call mtr.add_suppression("Failed to drop column family");
+call mtr.add_suppression("Column family '[a-z_]+' not found");
+--enable_query_log
+
+--source include/have_rocksdb.inc
+
+# should fail for not existing CF
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = 'nonexistent_cf'
+
+# should fail for default system cf
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = '__system__'
+
+# should fail for default cf
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = 'default'
+
+--disable_parsing
+# should succeed for not existing CF
+--eval SET @@global.ROCKSDB_DELETE_CF = 'nonexistent_cf'
+
+# should fail for default system cf
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = '__system__'
+
+alter table mysql.slave_worker_info engine = MyISAM;
+alter table mysql.slave_relay_log_info engine = MyISAM;
+alter table mysql.slave_gtid_info engine = MyISAM;
+alter table mysql.slave_master_info engine = MyISAM;
+
+select count(*) from information_schema.rocksdb_ddl where cf = 'default';
+
+# should fail for default cf
+--error ER_GET_ERRMSG
+--eval SET @@global.ROCKSDB_DELETE_CF = 'default'
+
+CREATE TABLE cf_deletion_test_table1 (
+ id1 int(10) unsigned NOT NULL DEFAULT '0',
+ id2 int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (id1) COMMENT 'cf_primary_key',
+ KEY `sec_key` (id2) COMMENT 'cf_secondary_key'
+) ENGINE=ROCKSDB;
+
+# should fail, CFs are still in use
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = 'cf_primary_key'
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = 'cf_secondary_key'
+
+CREATE TABLE cf_deletion_test_table2 (
+ id1 int(10) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (id1) COMMENT 'cf_primary_key'
+) ENGINE=ROCKSDB;
+
+DROP TABLE cf_deletion_test_table1;
+
+# should fail, still used by second table
+--error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.ROCKSDB_DELETE_CF = 'cf_primary_key'
+
+# should succeed, no one is using it anymore
+--eval SET @@global.ROCKSDB_DELETE_CF = 'cf_secondary_key'
+
+DROP TABLE cf_deletion_test_table2;
+
+# should succeed now
+--eval SET @@global.ROCKSDB_DELETE_CF = 'cf_primary_key'
+
+alter table mysql.slave_worker_info engine = ROCKSDB;
+alter table mysql.slave_relay_log_info engine = ROCKSDB;
+alter table mysql.slave_gtid_info engine = ROCKSDB;
+alter table mysql.slave_master_info engine = ROCKSDB;
+--enable_parsing
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_obsolete_files_period_micros_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_obsolete_files_period_micros_basic.test
new file mode 100644
index 00000000..3c2cd2db
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_delete_obsolete_files_period_micros_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_DELETE_OBSOLETE_FILES_PERIOD_MICROS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_2pc_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_2pc_basic.test
new file mode 100644
index 00000000..0a38895c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_2pc_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_ENABLE_2PC
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_bulk_load_api_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_bulk_load_api_basic.test
new file mode 100644
index 00000000..52313ffb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_bulk_load_api_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_ENABLE_BULK_LOAD_API
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_insert_with_update_caching_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_insert_with_update_caching_basic.test
new file mode 100644
index 00000000..cd643cfe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_insert_with_update_caching_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_ENABLE_INSERT_WITH_UPDATE_CACHING
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_thread_tracking_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_thread_tracking_basic.test
new file mode 100644
index 00000000..566d5656
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_thread_tracking_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ENABLE_THREAD_TRACKING
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_basic.test
new file mode 100644
index 00000000..209530bd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_ENABLE_TTL
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_read_filtering_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_read_filtering_basic.test
new file mode 100644
index 00000000..cc034ed4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_ttl_read_filtering_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_ENABLE_TTL_READ_FILTERING
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_write_thread_adaptive_yield_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_write_thread_adaptive_yield_basic.test
new file mode 100644
index 00000000..b5c844a9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_enable_write_thread_adaptive_yield_basic.test
@@ -0,0 +1,5 @@
+--source include/have_rocksdb.inc
+--let $sys_var=ROCKSDB_ENABLE_WRITE_THREAD_ADAPTIVE_YIELD
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_if_exists_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_if_exists_basic.test
new file mode 100644
index 00000000..933642a7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_if_exists_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_ERROR_IF_EXISTS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_on_suboptimal_collation_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_on_suboptimal_collation_basic.test
new file mode 100644
index 00000000..9278fa31
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_error_on_suboptimal_collation_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_ERROR_ON_SUBOPTIMAL_COLLATION
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_flush_log_at_trx_commit_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_flush_log_at_trx_commit_basic.test
new file mode 100644
index 00000000..02c533dc
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_flush_log_at_trx_commit_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(2);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_FLUSH_LOG_AT_TRX_COMMIT
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_basic.test
new file mode 100644
index 00000000..318ae1ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_basic.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+--disable_warnings
+DROP TABLE IF EXISTS t;
+--enable_warnings
+
+CREATE TABLE t (a INT PRIMARY KEY, b CHAR(8)) ENGINE=rocksdb;
+INSERT INTO t (a,b) VALUES (1,'bar'),(2,'foo');
+
+SET @ORIG_FORCE_COMPUTE_MEMTABLE_STATS = @@rocksdb_force_compute_memtable_stats;
+set global rocksdb_force_flush_memtable_now = true;
+
+INSERT INTO t (a,b) VALUES (3,'dead'),(4,'beef'),(5,'a'),(6,'bbb'),(7,'c'),(8,'d');
+
+set global rocksdb_force_compute_memtable_stats=0;
+SELECT TABLE_ROWS INTO @ROWS_EXCLUDE_MEMTABLE FROM information_schema.TABLES WHERE table_name = 't';
+
+set global rocksdb_force_compute_memtable_stats=1;
+SELECT TABLE_ROWS INTO @ROWS_INCLUDE_MEMTABLE FROM information_schema.TABLES WHERE table_name = 't';
+
+select case when @ROWS_INCLUDE_MEMTABLE-@ROWS_EXCLUDE_MEMTABLE > 0 then 'true' else 'false' end;
+
+DROP TABLE t;
+set global rocksdb_force_compute_memtable_stats = @ORIG_FORCE_COMPUTE_MEMTABLE_STATS;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_cachetime_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_cachetime_basic.test
new file mode 100644
index 00000000..20180ec1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_compute_memtable_stats_cachetime_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(1024 * 1024 * 1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_FORCE_COMPUTE_MEMTABLE_STATS_CACHETIME
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_and_lzero_now_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_and_lzero_now_basic.test
new file mode 100644
index 00000000..5eeac501
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_and_lzero_now_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+
+--let $sys_var=ROCKSDB_FORCE_FLUSH_MEMTABLE_AND_LZERO_NOW
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_now_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_now_basic.test
new file mode 100644
index 00000000..4386af1e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_flush_memtable_now_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+
+--let $sys_var=ROCKSDB_FORCE_FLUSH_MEMTABLE_NOW
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_index_records_in_range_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_index_records_in_range_basic.test
new file mode 100644
index 00000000..30263ea4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_force_index_records_in_range_basic.test
@@ -0,0 +1,23 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+# Attempt to set the value to -1 - this should first truncate to 0 and then generate a warning as
+# we can't set it to or from 0
+SET @@session.rocksdb_force_index_records_in_range = -1;
+SELECT @@session.rocksdb_force_index_records_in_range;
+
+--let $sys_var=ROCKSDB_FORCE_INDEX_RECORDS_IN_RANGE
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_git_hash_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_git_hash_basic.test
new file mode 100644
index 00000000..7b314e47
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_git_hash_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_GIT_HASH
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_hash_index_allow_collision_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_hash_index_allow_collision_basic.test
new file mode 100644
index 00000000..e787dd33
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_hash_index_allow_collision_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_HASH_INDEX_ALLOW_COLLISION
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_ignore_unknown_options_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_ignore_unknown_options_basic.test
new file mode 100644
index 00000000..f10ff2c6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_ignore_unknown_options_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_IGNORE_UNKNOWN_OPTIONS
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_index_type_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_index_type_basic.test
new file mode 100644
index 00000000..49369ffd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_index_type_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_INDEX_TYPE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_info_log_level_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_info_log_level_basic.test
new file mode 100644
index 00000000..fb2ce5e7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_info_log_level_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('fatal_level');
+INSERT INTO valid_values VALUES('error_level');
+INSERT INTO valid_values VALUES('warn_level');
+INSERT INTO valid_values VALUES('info_level');
+INSERT INTO valid_values VALUES('debug_level');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES(5);
+INSERT INTO invalid_values VALUES(6);
+INSERT INTO invalid_values VALUES('foo');
+
+--let $sys_var=ROCKSDB_INFO_LOG_LEVEL
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_io_write_timeout_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_io_write_timeout_basic.test
new file mode 100644
index 00000000..4433eb26
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_io_write_timeout_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(10);
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(42);
+INSERT INTO valid_values VALUES(142);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_IO_WRITE_TIMEOUT
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_is_fd_close_on_exec_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_is_fd_close_on_exec_basic.test
new file mode 100644
index 00000000..4d39c2a3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_is_fd_close_on_exec_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_IS_FD_CLOSE_ON_EXEC
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_keep_log_file_num_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_keep_log_file_num_basic.test
new file mode 100644
index 00000000..0eff718c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_keep_log_file_num_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_KEEP_LOG_FILE_NUM
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_large_prefix_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_large_prefix_basic.test
new file mode 100644
index 00000000..5ed3c741
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_large_prefix_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_LARGE_PREFIX
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_scanned_rows_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_scanned_rows_basic.test
new file mode 100644
index 00000000..35b4128c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_scanned_rows_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES(2);
+INSERT INTO invalid_values VALUES(1000);
+
+--let $sys_var=ROCKSDB_LOCK_SCANNED_ROWS
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_wait_timeout_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_wait_timeout_basic.test
new file mode 100644
index 00000000..24096677
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_lock_wait_timeout_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_LOCK_WAIT_TIMEOUT
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_log_file_time_to_roll_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_log_file_time_to_roll_basic.test
new file mode 100644
index 00000000..63a7c5fe
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_log_file_time_to_roll_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_LOG_FILE_TIME_TO_ROLL
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manifest_preallocation_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manifest_preallocation_size_basic.test
new file mode 100644
index 00000000..6f248ece
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manifest_preallocation_size_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_MANIFEST_PREALLOCATION_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_compaction_threads_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_compaction_threads_basic.test
new file mode 100644
index 00000000..4f166a9c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_compaction_threads_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(99);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_MANUAL_COMPACTION_THREADS
+--let $read_only=0
+--let $session=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_wal_flush_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_wal_flush_basic.test
new file mode 100644
index 00000000..3e01722d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_manual_wal_flush_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_MANUAL_WAL_FLUSH
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_master_skip_tx_api_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_master_skip_tx_api_basic.test
new file mode 100644
index 00000000..e0d5925c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_master_skip_tx_api_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_MASTER_SKIP_TX_API
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_background_jobs_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_background_jobs_basic.test
new file mode 100644
index 00000000..375a4fdd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_background_jobs_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(64);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'abc\'');
+
+--let $sys_var=ROCKSDB_MAX_BACKGROUND_JOBS
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_latest_deadlocks_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_latest_deadlocks_basic.test
new file mode 100644
index 00000000..9917ec31
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_latest_deadlocks_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+
+--let $sys_var=ROCKSDB_MAX_LATEST_DEADLOCKS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_log_file_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_log_file_size_basic.test
new file mode 100644
index 00000000..cbe5d925
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_log_file_size_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_MAX_LOG_FILE_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manifest_file_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manifest_file_size_basic.test
new file mode 100644
index 00000000..f399b296
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manifest_file_size_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_MAX_MANIFEST_FILE_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manual_compactions_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manual_compactions_basic.test
new file mode 100644
index 00000000..1c663168
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_manual_compactions_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(512*1024*1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_MAX_MANUAL_COMPACTIONS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_open_files_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_open_files_basic.test
new file mode 100644
index 00000000..36996761
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_open_files_basic.test
@@ -0,0 +1,8 @@
+--source include/have_rocksdb.inc
+
+# We can not use rocksdb_sys_var.inc here as this is a global, read only option
+# whose value is dependent on the servers open_files_limit. It is more fully
+# tested in the rocksdb.max_open_files test.
+
+--replace_column 2 #
+show variables like 'rocksdb_max_open_files';
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_row_locks_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_row_locks_basic.test
new file mode 100644
index 00000000..8a26ae91
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_row_locks_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+INSERT INTO valid_values VALUES(512*1024*1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_MAX_ROW_LOCKS
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_subcompactions_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_subcompactions_basic.test
new file mode 100644
index 00000000..a4494dd8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_subcompactions_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_MAX_SUBCOMPACTIONS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_total_wal_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_total_wal_size_basic.test
new file mode 100644
index 00000000..35ba859c
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_max_total_wal_size_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_MAX_TOTAL_WAL_SIZE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_buf_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_buf_size_basic.test
new file mode 100644
index 00000000..8e2dda64
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_buf_size_basic.test
@@ -0,0 +1,50 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, FLOOR(RAND() * 100));
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+DROP INDEX kj on t1;
+DROP INDEX kij ON t1;
+
+ALTER TABLE t1 ADD INDEX kj(j), ADD INDEX kij(i,j), ADD INDEX kji(j,i), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
+
+# Reverse CF testing, needs to be added to SSTFileWriter in reverse order
+CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=RocksDB;
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, FLOOR(RAND() * 100));
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+ALTER TABLE t1 ADD INDEX kb(b) comment 'rev:cf1', ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+SELECT COUNT(*) FROM t1 FORCE INDEX(kb);
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_combine_read_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_combine_read_size_basic.test
new file mode 100644
index 00000000..48e89137
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_combine_read_size_basic.test
@@ -0,0 +1,32 @@
+--source include/have_rocksdb.inc
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, $i);
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+ALTER TABLE t1 ADD INDEX kij(i,j), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+DROP INDEX kj on t1;
+DROP INDEX kij ON t1;
+
+ALTER TABLE t1 ADD INDEX kj(j), ADD INDEX kij(i,j), ADD INDEX kji(j,i), ALGORITHM=INPLACE;
+SHOW CREATE TABLE t1;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_tmp_file_removal_delay_ms_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_tmp_file_removal_delay_ms_basic.test
new file mode 100644
index 00000000..03cc0b11
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_merge_tmp_file_removal_delay_ms_basic.test
@@ -0,0 +1,49 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('on');
+
+--let $sys_var=ROCKSDB_MERGE_TMP_FILE_REMOVAL_DELAY_MS
+--let $read_only=0
+--let $session=1
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
+set session rocksdb_merge_buf_size=250;
+set session rocksdb_merge_combine_read_size=1000;
+set session rocksdb_merge_tmp_file_removal_delay_ms=1000;
+
+CREATE TABLE t1 (i INT, j INT, PRIMARY KEY (i)) ENGINE = ROCKSDB;
+
+--disable_query_log
+let $max = 100;
+let $i = 1;
+while ($i <= $max) {
+ let $insert = INSERT INTO t1 VALUES ($i, FLOOR(RAND() * 100));
+ inc $i;
+ eval $insert;
+}
+--enable_query_log
+
+let $start= `SELECT UNIX_TIMESTAMP()`;
+# this should take a lot longer than normal because each deleted merge file
+# will sleep for 1 secs. There should be about 13 buffers.
+# So it should take at least 13 secs
+ALTER TABLE t1 ADD INDEX kj(j), ALGORITHM=INPLACE;
+let $stop_exec= `SELECT UNIX_TIMESTAMP()`;
+
+let $time_diff= `SELECT ($stop_exec - $start)`;
+let $assert_text= Alter should have taken at least 10 seconds;
+let $assert_cond= $time_diff >= 10;
+source include/assert.inc;
+
+DROP TABLE t1;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_new_table_reader_for_compaction_inputs_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_new_table_reader_for_compaction_inputs_basic.test
new file mode 100644
index 00000000..1d2ea6e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_new_table_reader_for_compaction_inputs_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_NEW_TABLE_READER_FOR_COMPACTION_INPUTS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_no_block_cache_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_no_block_cache_basic.test
new file mode 100644
index 00000000..be1e3e88
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_no_block_cache_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_NO_BLOCK_CACHE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_override_cf_options_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_override_cf_options_basic.test
new file mode 100644
index 00000000..1f4325b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_override_cf_options_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_OVERRIDE_CF_OPTIONS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_paranoid_checks_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_paranoid_checks_basic.test
new file mode 100644
index 00000000..5bdd9d3d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_paranoid_checks_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_PARANOID_CHECKS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pause_background_work_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pause_background_work_basic.test
new file mode 100644
index 00000000..3f2f6bc7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pause_background_work_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_PAUSE_BACKGROUND_WORK
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_perf_context_level_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_perf_context_level_basic.test
new file mode 100644
index 00000000..46f74578
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_perf_context_level_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(2);
+INSERT INTO valid_values VALUES(3);
+INSERT INTO valid_values VALUES(4);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_PERF_CONTEXT_LEVEL
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_path_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_path_basic.test
new file mode 100644
index 00000000..1a1146a1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_path_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('abc');
+INSERT INTO valid_values VALUES('def');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+
+--let $sys_var=ROCKSDB_PERSISTENT_CACHE_PATH
+--let $read_only=1
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_size_mb_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_size_mb_basic.test
new file mode 100644
index 00000000..7f21d96f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_persistent_cache_size_mb_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_PERSISTENT_CACHE_SIZE_MB
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.test
new file mode 100644
index 00000000..d2513106
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_pin_l0_filter_and_index_blocks_in_cache_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_PIN_L0_FILTER_AND_INDEX_BLOCKS_IN_CACHE
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_print_snapshot_conflict_queries_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_print_snapshot_conflict_queries_basic.test
new file mode 100644
index 00000000..24d2f182
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_print_snapshot_conflict_queries_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_PRINT_SNAPSHOT_CONFLICT_QUERIES
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rate_limiter_bytes_per_sec_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rate_limiter_bytes_per_sec_basic.test
new file mode 100644
index 00000000..743f942a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rate_limiter_bytes_per_sec_basic.test
@@ -0,0 +1,63 @@
+--source include/have_rocksdb.inc
+
+# Attempt to set the value - this should generate a warning as we can't set it to or from 0
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = 10000;
+
+# Now shut down and come back up with the rate limiter enabled and retest setting the variable
+
+# Write file to make mysql-test-run.pl expect the "crash", but don't restart the
+# server until it is told to
+--let $_server_id= `SELECT @@server_id`
+--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
+--exec echo "wait" >$_expect_file_name
+
+# Send shutdown to the connected server and give it 10 seconds to die before
+# zapping it
+shutdown_server;
+
+# Attempt to restart the server with the rate limiter on
+--exec echo "restart:--rocksdb_rate_limiter_bytes_per_sec=10000" >$_expect_file_name
+--sleep 5
+
+# Wait for reconnect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
+
+# The valid_values table lists the values that we want to make sure that the system will allow
+# us to set for rocksdb_rate_limiter_bytes_per_sec
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1), (1000), (1000000), (1000000000), (1000000000000);
+
+# The invalid_values table lists the values that we don't want to allow for the variable
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\''), (3.14);
+
+# Test all the valid and invalid values
+--let $sys_var=ROCKSDB_RATE_LIMITER_BYTES_PER_SEC
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
+# Zero is an invalid value if the rate limiter is turned on, but it won't be rejected by the
+# SET command but will generate a warning.
+
+# Attempt to set the value to 0 - this should generate a warning as we can't set it to or from 0
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = 0;
+
+# Attempt to set the value to -1 - this should first truncate to 0 and then generate a warning as
+# we can't set it to or from 0
+SET @@global.rocksdb_rate_limiter_bytes_per_sec = -1;
+
+# Restart the server without the rate limiter
+--exec echo "wait" >$_expect_file_name
+shutdown_server;
+--exec echo "restart" >$_expect_file_name
+--sleep 5
+
+# Wait for reconnect
+--enable_reconnect
+--source include/wait_until_connected_again.inc
+--disable_reconnect
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_basic.test
new file mode 100644
index 00000000..f37f75b4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_basic.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('PK_SK');
+INSERT INTO valid_values VALUES('OFF');
+INSERT INTO valid_values VALUES('PK_ONLY');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('a');
+
+--let $sys_var=ROCKSDB_READ_FREE_RPL
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+SET GLOBAL ROCKSDB_READ_FREE_RPL=DEFAULT;
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_tables_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_tables_basic.test
new file mode 100644
index 00000000..a2c900c9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_read_free_rpl_tables_basic.test
@@ -0,0 +1,20 @@
+--source include/have_rocksdb.inc
+
+call mtr.add_suppression(".*Invalid pattern in rocksdb_read_free_rpl_tables.*");
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES('a');
+INSERT INTO valid_values VALUES('b');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'*\'');
+
+--let $sys_var=ROCKSDB_READ_FREE_RPL_TABLES
+--let $read_only=0
+--source include/rocksdb_sys_var.inc
+
+SET GLOBAL ROCKSDB_READ_FREE_RPL_TABLES=NULL;
+SET GLOBAL ROCKSDB_READ_FREE_RPL_TABLES=DEFAULT;
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_records_in_range_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_records_in_range_basic.test
new file mode 100644
index 00000000..21503475
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_records_in_range_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(222333);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_RECORDS_IN_RANGE
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_remove_mariabackup_checkpoint_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_remove_mariabackup_checkpoint_basic.test
new file mode 100644
index 00000000..30f38283
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_remove_mariabackup_checkpoint_basic.test
@@ -0,0 +1,5 @@
+# Simulate creating and removing mariabackup checkpoint twice
+SET GLOBAL rocksdb_create_checkpoint=CONCAT(@@rocksdb_datadir,'/mariabackup-checkpoint');
+SET GLOBAL rocksdb_remove_mariabackup_checkpoint=ON;
+SET GLOBAL rocksdb_create_checkpoint=CONCAT(@@rocksdb_datadir,'/mariabackup-checkpoint');
+SET GLOBAL rocksdb_remove_mariabackup_checkpoint=ON;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_reset_stats_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_reset_stats_basic.test
new file mode 100644
index 00000000..62f75a3b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_reset_stats_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_RESET_STATS
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rollback_on_timeout_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rollback_on_timeout_basic.test
new file mode 100644
index 00000000..793b7752
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_rollback_on_timeout_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_ROLLBACK_ON_TIMEOUT
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_seconds_between_stat_computes_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_seconds_between_stat_computes_basic.test
new file mode 100644
index 00000000..53c2e6e6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_seconds_between_stat_computes_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_SECONDS_BETWEEN_STAT_COMPUTES
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_signal_drop_index_thread_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_signal_drop_index_thread_basic.test
new file mode 100644
index 00000000..ea90c7b7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_signal_drop_index_thread_basic.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_SIGNAL_DROP_INDEX_THREAD
+--let $read_only=0
+--let $session=0
+--let $sticky=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sim_cache_size_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sim_cache_size_basic.test
new file mode 100644
index 00000000..a82d50e1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sim_cache_size_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_SIM_CACHE_SIZE
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_bloom_filter_on_read_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_bloom_filter_on_read_basic.test
new file mode 100644
index 00000000..82b56e0b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_bloom_filter_on_read_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_SKIP_BLOOM_FILTER_ON_READ
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_fill_cache_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_fill_cache_basic.test
new file mode 100644
index 00000000..cc1b608b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_fill_cache_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_SKIP_FILL_CACHE
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_unique_check_tables_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_unique_check_tables_basic.test
new file mode 100644
index 00000000..50c2354d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_skip_unique_check_tables_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES("aaa");
+INSERT INTO valid_values VALUES("bbb");
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+
+--let $sys_var=ROCKSDB_SKIP_UNIQUE_CHECK_TABLES
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+SET GLOBAL ROCKSDB_SKIP_UNIQUE_CHECK_TABLES=NULL;
+SET GLOBAL ROCKSDB_SKIP_UNIQUE_CHECK_TABLES=DEFAULT;
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sst_mgr_rate_bytes_per_sec_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sst_mgr_rate_bytes_per_sec_basic.test
new file mode 100644
index 00000000..3492596d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_sst_mgr_rate_bytes_per_sec_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_SST_MGR_RATE_BYTES_PER_SEC
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_dump_period_sec_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_dump_period_sec_basic.test
new file mode 100644
index 00000000..2fbb0c6e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_dump_period_sec_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_STATS_DUMP_PERIOD_SEC
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_level_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_level_basic.test
new file mode 100644
index 00000000..89b0878f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_level_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES(4);
+INSERT INTO valid_values VALUES(2);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_STATS_LEVEL
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_recalc_rate_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_recalc_rate_basic.test
new file mode 100644
index 00000000..a3b9059b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_stats_recalc_rate_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'123\'');
+
+--let $sys_var=ROCKSDB_STATS_RECALC_RATE
+--let $read_only=0
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_store_row_debug_checksums_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_store_row_debug_checksums_basic.test
new file mode 100644
index 00000000..e3faca86
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_store_row_debug_checksums_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_STORE_ROW_DEBUG_CHECKSUMS
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_check_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_check_basic.test
new file mode 100644
index 00000000..17aa63b8
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_check_basic.test
@@ -0,0 +1,19 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_STRICT_COLLATION_CHECK
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_exceptions_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_exceptions_basic.test
new file mode 100644
index 00000000..4eb96488
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_strict_collation_exceptions_basic.test
@@ -0,0 +1,35 @@
+--source include/have_rocksdb.inc
+
+# We cannot use the rocskdb_sys_var.inc script as some of the strings we set
+# need to be quoted and that doesn't work with this script. Run through
+# valid options by hand.
+
+SET @start_global_value = @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+SELECT @start_global_value;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to simple table name."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = mytable;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to regex table name(s)."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "t.*";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to multiple regex table names."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "s.*,t.*";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to empty."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = "";
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to default."
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = DEFAULT;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
+
+--echo "Trying to set @session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS to 444. It should fail because it is not session."
+--Error ER_GLOBAL_VARIABLE
+SET @@session.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = 444;
+
+SET @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS = @start_global_value;
+SELECT @@global.ROCKSDB_STRICT_COLLATION_EXCEPTIONS;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_supported_compression_types_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_supported_compression_types_basic.test
new file mode 100644
index 00000000..52bf63c2
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_supported_compression_types_basic.test
@@ -0,0 +1,7 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_SUPPORTED_COMPRESSION_TYPES
+--let $read_only=1
+--let $session=0
+--let $suppress_default_value=1
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_cache_numshardbits_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_cache_numshardbits_basic.test
new file mode 100644
index 00000000..11bdd6ab
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_cache_numshardbits_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_TABLE_CACHE_NUMSHARDBITS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_stats_sampling_pct_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_stats_sampling_pct_basic.test
new file mode 100644
index 00000000..3bed5e6e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_table_stats_sampling_pct_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_TABLE_STATS_SAMPLING_PCT
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_tmpdir_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_tmpdir_basic.test
new file mode 100644
index 00000000..8865914d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_tmpdir_basic.test
@@ -0,0 +1,38 @@
+--source include/have_rocksdb.inc
+
+SET @start_global_value = @@global.rocksdb_tmpdir;
+SELECT @start_global_value;
+
+#
+# exists as global and session
+#
+select @@session.rocksdb_tmpdir;
+
+show global variables like 'rocksdb_tmpdir';
+show session variables like 'rocksdb_tmpdir';
+
+select * from information_schema.global_variables where variable_name='rocksdb_tmpdir';
+select * from information_schema.session_variables where variable_name='rocksdb_tmpdir';
+
+#
+# Show that it is writable
+#
+
+set global rocksdb_tmpdir='value';
+set session rocksdb_tmpdir='value';
+
+#
+# incorrect types
+#
+--error ER_WRONG_TYPE_FOR_VAR
+set global rocksdb_tmpdir=1.1;
+--error ER_WRONG_TYPE_FOR_VAR
+set global rocksdb_tmpdir=1e1;
+
+#
+# Cleanup
+#
+
+SET @@global.rocksdb_tmpdir = @start_global_value;
+SELECT @@global.rocksdb_tmpdir;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_trace_sst_api_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_trace_sst_api_basic.test
new file mode 100644
index 00000000..259021d3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_trace_sst_api_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_TRACE_SST_API
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_two_write_queues_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_two_write_queues_basic.test
new file mode 100644
index 00000000..43579fab
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_two_write_queues_basic.test
@@ -0,0 +1,16 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(1024);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_TWO_WRITE_QUEUES
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_unsafe_for_binlog_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_unsafe_for_binlog_basic.test
new file mode 100644
index 00000000..f5f4536d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_unsafe_for_binlog_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_UNSAFE_FOR_BINLOG
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options.test
new file mode 100644
index 00000000..03626260
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE t1 (a INT, PRIMARY KEY (a) COMMENT 'update_cf1') ENGINE=ROCKSDB;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SELECT @@global.rocksdb_update_cf_options;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS="";
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=NULL;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf2={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+DROP TABLE t1;
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS='update_cf1={write_buffer_size=8m;target_file_size_base=2m};';
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
+SET @@GLOBAL.ROCKSDB_UPDATE_CF_OPTIONS=DEFAULT;
+SHOW GLOBAL VARIABLES LIKE 'rocksdb_update_cf_options';
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options_basic.test
new file mode 100644
index 00000000..9462e40a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_update_cf_options_basic.test
@@ -0,0 +1,119 @@
+--source include/have_rocksdb.inc
+--source include/have_partition.inc
+
+call mtr.add_suppression("MyRocks: NULL is not a valid option for updates to column family settings.");
+call mtr.add_suppression("Invalid cf options, '=' expected *");
+call mtr.add_suppression("MyRocks: failed to parse the updated column family options = *");
+call mtr.add_suppression("Invalid cf config for default in override options *");
+
+DROP TABLE IF EXISTS t1;
+
+# Need a table which has multiple partitions and column families associated
+# with them to make sure that we're testing the valid scenario.
+CREATE TABLE `t1` (
+ `col1` bigint(20) NOT NULL,
+ `col2` varbinary(64) NOT NULL,
+ `col3` varbinary(256) NOT NULL,
+ `col4` bigint(20) NOT NULL,
+ `col5` mediumblob NOT NULL,
+ PRIMARY KEY (`col1`,`col2`,`col3`) COMMENT 'custom_p0_cfname=cf1;custom_p1_cfname=cf2',
+ UNIQUE KEY (`col2`, `col4`) COMMENT 'custom_p5_cfname=cf3'
+) ENGINE=ROCKSDB DEFAULT CHARSET=latin1
+ PARTITION BY LIST COLUMNS (`col2`) (
+ PARTITION custom_p0 VALUES IN (0x12345),
+ PARTITION custom_p1 VALUES IN (0x23456),
+ PARTITION custom_p2 VALUES IN (0x34567),
+ PARTITION custom_p3 VALUES IN (0x45678),
+ PARTITION custom_p4 VALUES IN (0x56789),
+ PARTITION custom_p5 VALUES IN (0x6789A),
+ PARTITION custom_p6 VALUES IN (0x789AB),
+ PARTITION custom_p7 VALUES IN (0x89ABC)
+);
+
+USE information_schema;
+
+# We should start with NULL.
+SELECT @@global.rocksdb_update_cf_options;
+
+# ... and we should be able to handle NULL and issue a reasonable warning.
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+
+# Make sure that we do not double free the NULL string
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+
+# Attempt setting an empty string
+SET @@global.rocksdb_update_cf_options = '';
+SELECT @@global.rocksdb_update_cf_options;
+
+# Will fail to parse. Value not updated.
+--Error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.rocksdb_update_cf_options = 'aaaaa';
+SELECT @@global.rocksdb_update_cf_options;
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+# Save these off to reset later
+--let $ORIG_WRITE_BUFFER_SIZE=`SELECT VALUE FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='WRITE_BUFFER_SIZE'`
+--let $ORIG_TARGET_FILE_SIZE_BASE=`SELECT VALUE FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE'`
+--let $ORIG_MAX_BYTES_FOR_LEVEL_MULTIPLIER=`SELECT VALUE FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='MAX_BYTES_FOR_LEVEL_MULTIPLIER'`
+
+# All good. Use default CF.
+SET @@global.rocksdb_update_cf_options = 'default={write_buffer_size=8m;target_file_size_base=2m};';
+SELECT @@global.rocksdb_update_cf_options;
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='default' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='MAX_BYTES_FOR_LEVEL_MULTIPLIER';
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+# All good. Use multiple valid CF-s.
+SET @@global.rocksdb_update_cf_options = 'cf1={write_buffer_size=8m;target_file_size_base=2m};cf2={write_buffer_size=16m;max_bytes_for_level_multiplier=8};cf3={target_file_size_base=4m};';
+SELECT @@global.rocksdb_update_cf_options;
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='WRITE_BUFFER_SIZE';
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf2' AND OPTION_TYPE='MAX_BYTES_FOR_LEVEL_MULTIPLIER';
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+# All good. Use a single valid CF.
+SET @@global.rocksdb_update_cf_options = 'cf3={target_file_size_base=24m};';
+SELECT @@global.rocksdb_update_cf_options;
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf3' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+# Some parts are good. Value still updated.
+SET @@global.rocksdb_update_cf_options = 'cf1={target_file_size_base=24m};foo={max_bytes_for_level_multiplier=8};';
+SELECT @@global.rocksdb_update_cf_options;
+
+SELECT * FROM ROCKSDB_CF_OPTIONS WHERE CF_NAME='cf1' AND OPTION_TYPE='TARGET_FILE_SIZE_BASE';
+
+# Will fail to parse. No valid assignments included. Value not updated and
+# reset to NULL.
+--Error ER_WRONG_VALUE_FOR_VAR
+--eval SET @@global.rocksdb_update_cf_options = 'default={foo=bar};';
+SELECT @@global.rocksdb_update_cf_options;
+
+# Reset the cf options so the test passes with --repeat=2
+--eval SET @@global.rocksdb_update_cf_options = 'default={write_buffer_size=$ORIG_WRITE_BUFFER_SIZE;target_file_size_base=$ORIG_TARGET_FILE_SIZE_BASE};'
+--eval SET @@global.rocksdb_update_cf_options = 'cf1={write_buffer_size=$ORIG_WRITE_BUFFER_SIZE;target_file_size_base=$ORIG_TARGET_FILE_SIZE_BASE};'
+--eval SET @@global.rocksdb_update_cf_options = 'cf2={write_buffer_size=$ORIG_WRITE_BUFFER_SIZE;target_file_size_base=$ORIG_TARGET_FILE_SIZE_BASE;max_bytes_for_level_multiplier=$ORIG_MAX_BYTES_FOR_LEVEL_MULTIPLIER};'
+--eval SET @@global.rocksdb_update_cf_options = 'cf3={write_buffer_size=$ORIG_WRITE_BUFFER_SIZE;target_file_size_base=$ORIG_TARGET_FILE_SIZE_BASE};'
+
+SET @@global.rocksdb_update_cf_options = NULL;
+SELECT @@global.rocksdb_update_cf_options;
+
+USE test;
+
+DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_adaptive_mutex_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_adaptive_mutex_basic.test
new file mode 100644
index 00000000..7ce7bec1
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_adaptive_mutex_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_USE_ADAPTIVE_MUTEX
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_clock_cache_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_clock_cache_basic.test
new file mode 100644
index 00000000..d3e7b652
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_clock_cache_basic.test
@@ -0,0 +1,21 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+INSERT INTO valid_values VALUES('true');
+INSERT INTO valid_values VALUES('false');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_USE_CLOCK_CACHE
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_io_for_flush_and_compaction_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_io_for_flush_and_compaction_basic.test
new file mode 100644
index 00000000..f5dde2aa
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_io_for_flush_and_compaction_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_reads_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_reads_basic.test
new file mode 100644
index 00000000..323b517f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_direct_reads_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_USE_DIRECT_READS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_fsync_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_fsync_basic.test
new file mode 100644
index 00000000..90b41c4a
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_use_fsync_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_USE_FSYNC
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_validate_tables_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_validate_tables_basic.test
new file mode 100644
index 00000000..ed12b319
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_validate_tables_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_VALIDATE_TABLES
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_verify_row_debug_checksums_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_verify_row_debug_checksums_basic.test
new file mode 100644
index 00000000..352bc9d9
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_verify_row_debug_checksums_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_VERIFY_ROW_DEBUG_CHECKSUMS
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_bytes_per_sync_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_bytes_per_sync_basic.test
new file mode 100644
index 00000000..9c2a1f4f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_bytes_per_sync_basic.test
@@ -0,0 +1,22 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(100);
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+INSERT INTO invalid_values VALUES('\'-1\'');
+INSERT INTO invalid_values VALUES('\'101\'');
+INSERT INTO invalid_values VALUES('\'484436\'');
+
+--let $sys_var=ROCKSDB_WAL_BYTES_PER_SYNC
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_dir_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_dir_basic.test
new file mode 100644
index 00000000..a40c7766
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_dir_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_WAL_DIR
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_recovery_mode_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_recovery_mode_basic.test
new file mode 100644
index 00000000..ce202f2e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_recovery_mode_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_WAL_RECOVERY_MODE
+--let $read_only=0
+--let $session=0
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
+
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_size_limit_mb_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_size_limit_mb_basic.test
new file mode 100644
index 00000000..95880ea3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_size_limit_mb_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_WAL_SIZE_LIMIT_MB
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_ttl_seconds_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_ttl_seconds_basic.test
new file mode 100644
index 00000000..e65d3851
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_wal_ttl_seconds_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_WAL_TTL_SECONDS
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_whole_key_filtering_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_whole_key_filtering_basic.test
new file mode 100644
index 00000000..83e8e238
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_whole_key_filtering_basic.test
@@ -0,0 +1,6 @@
+--source include/have_rocksdb.inc
+
+--let $sys_var=ROCKSDB_WHOLE_KEY_FILTERING
+--let $read_only=1
+--let $session=0
+--source include/rocksdb_sys_var.inc
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_batch_max_bytes_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_batch_max_bytes_basic.test
new file mode 100644
index 00000000..40d22373
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_batch_max_bytes_basic.test
@@ -0,0 +1,26 @@
+--source include/have_rocksdb.inc
+
+create table t (i int);
+
+insert into t values (1), (2), (3), (4), (5);
+
+set session rocksdb_write_batch_max_bytes = 1000;
+
+insert into t values (1), (2), (3), (4), (5);
+
+set session rocksdb_write_batch_max_bytes = 10;
+
+--error ER_GET_ERRMSG
+insert into t values (1), (2), (3), (4), (5);
+
+set session rocksdb_write_batch_max_bytes = 0;
+
+insert into t values (1), (2), (3), (4), (5);
+
+set session rocksdb_write_batch_max_bytes = 10;
+begin;
+--error ER_GET_ERRMSG
+insert into t values (1), (2), (3), (4), (5);
+rollback;
+
+drop table t;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_disable_wal_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_disable_wal_basic.test
new file mode 100644
index 00000000..d732beba
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_disable_wal_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+INSERT INTO valid_values VALUES('off');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_WRITE_DISABLE_WAL
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_ignore_missing_column_families_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_ignore_missing_column_families_basic.test
new file mode 100644
index 00000000..f38b7c96
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_ignore_missing_column_families_basic.test
@@ -0,0 +1,18 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES(1);
+INSERT INTO valid_values VALUES(0);
+INSERT INTO valid_values VALUES('on');
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+INSERT INTO invalid_values VALUES('\'bbb\'');
+
+--let $sys_var=ROCKSDB_WRITE_IGNORE_MISSING_COLUMN_FAMILIES
+--let $read_only=0
+--let $session=1
+--source include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_policy_basic.test b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_policy_basic.test
new file mode 100644
index 00000000..720b9d67
--- /dev/null
+++ b/storage/rocksdb/mysql-test/rocksdb_sys_vars/t/rocksdb_write_policy_basic.test
@@ -0,0 +1,17 @@
+--source include/have_rocksdb.inc
+
+CREATE TABLE valid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO valid_values VALUES("write_committed");
+INSERT INTO valid_values VALUES("write_prepared");
+INSERT INTO valid_values VALUES("write_unprepared");
+
+CREATE TABLE invalid_values (value varchar(255)) ENGINE=myisam;
+INSERT INTO invalid_values VALUES('\'aaa\'');
+
+--let $sys_var=ROCKSDB_WRITE_POLICY
+--let $read_only=1
+--let $session=0
+--source ../include/rocksdb_sys_var.inc
+
+DROP TABLE valid_values;
+DROP TABLE invalid_values;
diff --git a/storage/rocksdb/mysql-test/storage_engine/cache_index.rdiff b/storage/rocksdb/mysql-test/storage_engine/cache_index.rdiff
new file mode 100644
index 00000000..76e7705e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/cache_index.rdiff
@@ -0,0 +1,71 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/cache_index.result 2017-06-22 00:33:46.419995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/cache_index.reject 2017-06-22 01:11:22.479981459 +0300
+@@ -12,31 +12,31 @@
+ SET GLOBAL <CACHE_NAME>.key_buffer_size=128*1024;
+ CACHE INDEX t1 INDEX (a), t2 IN <CACHE_NAME>;
+ Table Op Msg_type Msg_text
+-test.t1 assign_to_keycache status OK
+-test.t2 assign_to_keycache status OK
++test.t1 assign_to_keycache note The storage engine for the table doesn't support assign_to_keycache
++test.t2 assign_to_keycache note The storage engine for the table doesn't support assign_to_keycache
+ LOAD INDEX INTO CACHE t1, t2;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
+-test.t2 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
++test.t2 preload_keys note The storage engine for the table doesn't support preload_keys
+ INSERT INTO t1 (a,b) VALUES (3,'c'),(4,'d');
+ SET GLOBAL <CACHE_NAME>.key_buffer_size=8*1024;
+ LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
+-test.t2 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
++test.t2 preload_keys note The storage engine for the table doesn't support preload_keys
+ SET GLOBAL <CACHE_NAME>.key_cache_age_threshold = 100, <CACHE_NAME>.key_cache_block_size = 512, <CACHE_NAME>.key_cache_division_limit = 1, <CACHE_NAME>.key_cache_segments=2;
+ INSERT INTO t1 (a,b) VALUES (5,'e'),(6,'f');
+ LOAD INDEX INTO CACHE t1;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
+ SET GLOBAL new_<CACHE_NAME>.key_buffer_size=128*1024;
+ CACHE INDEX t1 IN new_<CACHE_NAME>;
+ Table Op Msg_type Msg_text
+-test.t1 assign_to_keycache status OK
++test.t1 assign_to_keycache note The storage engine for the table doesn't support assign_to_keycache
+ INSERT INTO t1 (a,b) VALUES (7,'g'),(8,'h');
+ LOAD INDEX INTO CACHE t1 IGNORE LEAVES;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
+ INSERT INTO t1 (a,b) VALUES (9,'i');
+ DROP TABLE t2;
+ DROP TABLE t1;
+@@ -47,11 +47,11 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ CACHE INDEX t1 IN <CACHE_NAME>;
+ Table Op Msg_type Msg_text
+-test.t1 assign_to_keycache status OK
++test.t1 assign_to_keycache note The storage engine for the table doesn't support assign_to_keycache
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ LOAD INDEX INTO CACHE t1;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -59,11 +59,11 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ CACHE INDEX t1 IN <CACHE_NAME>;
+ Table Op Msg_type Msg_text
+-test.t1 assign_to_keycache status OK
++test.t1 assign_to_keycache note The storage engine for the table doesn't support assign_to_keycache
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ LOAD INDEX INTO CACHE t1;
+ Table Op Msg_type Msg_text
+-test.t1 preload_keys status OK
++test.t1 preload_keys note The storage engine for the table doesn't support preload_keys
+ DROP TABLE t1;
+ SET GLOBAL <CACHE_NAME>.key_buffer_size=0;
+ SET GLOBAL new_<CACHE_NAME>.key_buffer_size=0;
diff --git a/storage/rocksdb/mysql-test/storage_engine/checksum_table_live.rdiff b/storage/rocksdb/mysql-test/storage_engine/checksum_table_live.rdiff
new file mode 100644
index 00000000..094136ee
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/checksum_table_live.rdiff
@@ -0,0 +1,13 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/checksum_table_live.result 2017-06-22 00:33:46.419995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/checksum_table_live.reject 2017-06-22 01:12:38.695980980 +0300
+@@ -11,8 +11,8 @@
+ test.t1 4272806499
+ CHECKSUM TABLE t1, t2 QUICK;
+ Table Checksum
+-test.t1 4272806499
+-test.t2 0
++test.t1 NULL
++test.t2 NULL
+ CHECKSUM TABLE t1, t2 EXTENDED;
+ Table Checksum
+ test.t1 4272806499
diff --git a/storage/rocksdb/mysql-test/storage_engine/cleanup_engine.inc b/storage/rocksdb/mysql-test/storage_engine/cleanup_engine.inc
new file mode 100644
index 00000000..e6fe915e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/cleanup_engine.inc
@@ -0,0 +1,25 @@
+###########################################
+#
+# This is a stub of the include file cleanup_engine.inc which
+# should be placed in storage/<engine>/mysql-test/storage_engine folder.
+#
+################################
+#
+# Here you can add whatever is needed to cleanup
+# in case your define_engine.inc created any artefacts,
+# e.g. an additional schema and/or tables.
+
+--let $datadir= `SELECT @@datadir`
+
+--error 0,1
+--file_exists $datadir/\#rocksdb/*
+if (!$mysql_errno)
+{
+ --enable_reconnect
+ --exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+ --shutdown_server
+ --source include/wait_until_disconnected.inc
+ --rmdir $datadir/\#rocksdb
+ --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
+ --source include/wait_until_connected_again.inc
+}
diff --git a/storage/rocksdb/mysql-test/storage_engine/define_engine.inc b/storage/rocksdb/mysql-test/storage_engine/define_engine.inc
new file mode 100644
index 00000000..1c77a6b6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/define_engine.inc
@@ -0,0 +1,45 @@
+###########################################
+#
+# This is a template of the include file define_engine.inc which
+# should be placed in storage/<engine>/mysql-test/storage_engine folder.
+#
+################################
+#
+# The name of the engine under test must be defined in $ENGINE variable.
+# You can set it either here (uncomment and edit) or in your environment.
+#
+let $ENGINE = RocksDB;
+#
+################################
+#
+# The following three variables define specific options for columns and tables.
+# Normally there should be none needed, but for some engines it can be different.
+# If the engine requires specific column option for all or indexed columns,
+# set them inside the comment, e.g. /*!NOT NULL*/.
+# Do the same for table options if needed, e.g. /*!INSERT_METHOD=LAST*/
+
+let $default_col_opts = /*!*/;
+let $default_col_indexed_opts = /*!*/;
+let $default_tbl_opts = /*!*/;
+
+# INDEX, UNIQUE INDEX, PRIMARY KEY, special index type - choose the fist that the engine allows,
+# or set it to /*!*/ if none is supported
+
+let $default_index = /*!INDEX*/;
+
+# If the engine does not support the following types, replace them with the closest possible
+
+let $default_int_type = INT(11);
+let $default_char_type = CHAR(8);
+
+################################
+
+--disable_query_log
+--disable_result_log
+
+# Here you can place your custom MTR code which needs to be executed before each test,
+# e.g. creation of an additional schema or table, etc.
+# The cleanup part should be defined in cleanup_engine.inc
+
+--enable_query_log
+--enable_result_log
diff --git a/storage/rocksdb/mysql-test/storage_engine/disabled.def b/storage/rocksdb/mysql-test/storage_engine/disabled.def
new file mode 100644
index 00000000..7ed4fac6
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/disabled.def
@@ -0,0 +1,27 @@
+alter_tablespace : Not supported
+autoinc_secondary : Not supported
+create_table : MDEV-12914 - Engine for temporary tables which are implicitly created as RocksDB is substituted silently
+delete_low_prio : Not supported
+foreign_keys : Not supported
+fulltext_search : Not supported
+handler : Not supported
+index_enable_disable : Not supported
+insert_delayed : Not supported
+insert_high_prio : Not supported
+insert_low_prio : Not supported
+lock : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
+lock_concurrent : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
+optimize_table : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
+repair_table : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
+select_high_prio : Not supported
+show_engine : SHOW ENGINE produces different number of lines depending on previous tests
+show_table_status : MDEV-13152 - Indeterministic row number in SHOW TABLE STATUS on RocksDB table
+tbl_opt_data_dir : Not supported
+tbl_opt_index_dir : Not supported
+type_binary_indexes : MDEV-16387 - Wrong execution plan
+type_spatial : Not supported
+type_spatial_indexes : Not supported
+update_low_prio : Not supported
+update_ignore : MDEV-13151 - Indeterministic results of multi-table update on RocksDB tables
+update_multi : MDEV-13151 - Indeterministic results of multi-table update on RocksDB tables
+vcol : Not supported
diff --git a/storage/rocksdb/mysql-test/storage_engine/index.rdiff b/storage/rocksdb/mysql-test/storage_engine/index.rdiff
new file mode 100644
index 00000000..76d6c6dd
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/index.rdiff
@@ -0,0 +1,60 @@
+--- suite/storage_engine/index.result 2017-03-12 04:57:07.169911845 +0200
++++ suite/storage_engine/index.reject 2017-08-14 22:41:06.548555663 +0300
+@@ -4,7 +4,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE
++t1 1 a 1 a # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -12,8 +12,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a_b 1 a # # NULL NULL # BTREE a_b index
+-t1 1 a_b 2 b # # NULL NULL # BTREE a_b index
++t1 1 a_b 1 a # # NULL NULL # LSMTREE a_b index
++t1 1 a_b 2 b # # NULL NULL # LSMTREE a_b index
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -22,8 +22,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE
+-t1 1 b 1 b # # NULL NULL # BTREE
++t1 1 a 1 a # # NULL NULL # LSMTREE
++t1 1 b 1 b # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -31,7 +31,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # BTREE
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
+@@ -43,7 +43,7 @@
+ ALTER TABLE t1 ADD <CUSTOM_INDEX> (a) COMMENT 'simple index on a';
+ SHOW INDEX FROM t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE simple index on a
++t1 1 a 1 a # # NULL NULL # LSMTREE simple index on a
+ ALTER TABLE t1 DROP KEY a;
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+@@ -52,7 +52,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # BTREE
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
diff --git a/storage/rocksdb/mysql-test/storage_engine/index_type_btree.rdiff b/storage/rocksdb/mysql-test/storage_engine/index_type_btree.rdiff
new file mode 100644
index 00000000..5fcffbea
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/index_type_btree.rdiff
@@ -0,0 +1,60 @@
+--- suite/storage_engine/index_type_btree.result 2017-03-12 04:57:07.169911845 +0200
++++ suite/storage_engine/index_type_btree.reject 2017-08-14 22:50:47.264555216 +0300
+@@ -4,7 +4,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE
++t1 1 a 1 a # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -12,8 +12,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a_b 1 a # # NULL NULL # BTREE a_b index
+-t1 1 a_b 2 b # # NULL NULL # BTREE a_b index
++t1 1 a_b 1 a # # NULL NULL # LSMTREE a_b index
++t1 1 a_b 2 b # # NULL NULL # LSMTREE a_b index
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -22,8 +22,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE
+-t1 1 b 1 b # # NULL NULL # BTREE
++t1 1 a 1 a # # NULL NULL # LSMTREE
++t1 1 b 1 b # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -31,7 +31,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # BTREE
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
+@@ -43,7 +43,7 @@
+ ALTER TABLE t1 ADD <CUSTOM_INDEX> (a) USING BTREE COMMENT 'simple index on a';
+ SHOW INDEX FROM t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # BTREE simple index on a
++t1 1 a 1 a # # NULL NULL # LSMTREE simple index on a
+ ALTER TABLE t1 DROP KEY a;
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+@@ -52,7 +52,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # BTREE
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
diff --git a/storage/rocksdb/mysql-test/storage_engine/index_type_hash.rdiff b/storage/rocksdb/mysql-test/storage_engine/index_type_hash.rdiff
new file mode 100644
index 00000000..815b2983
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/index_type_hash.rdiff
@@ -0,0 +1,60 @@
+--- suite/storage_engine/index_type_hash.result 2017-03-12 04:57:07.169911845 +0200
++++ suite/storage_engine/index_type_hash.reject 2017-08-14 22:51:55.644555163 +0300
+@@ -4,7 +4,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # HASH
++t1 1 a 1 a # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -12,8 +12,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a_b 1 a # # NULL NULL # HASH a_b index
+-t1 1 a_b 2 b # # NULL NULL # HASH a_b index
++t1 1 a_b 1 a # # NULL NULL # LSMTREE a_b index
++t1 1 a_b 2 b # # NULL NULL # LSMTREE a_b index
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -22,8 +22,8 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # HASH
+-t1 1 b 1 b # # NULL NULL # HASH
++t1 1 a 1 a # # NULL NULL # LSMTREE
++t1 1 b 1 b # # NULL NULL # LSMTREE
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+ b <CHAR_COLUMN>,
+@@ -31,7 +31,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # HASH
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
+@@ -43,7 +43,7 @@
+ ALTER TABLE t1 ADD <CUSTOM_INDEX> (a) USING HASH COMMENT 'simple index on a';
+ SHOW INDEX FROM t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 1 a 1 a # # NULL NULL # HASH simple index on a
++t1 1 a 1 a # # NULL NULL # LSMTREE simple index on a
+ ALTER TABLE t1 DROP KEY a;
+ DROP TABLE t1;
+ CREATE TABLE t1 (a <INT_COLUMN>,
+@@ -52,7 +52,7 @@
+ ) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SHOW KEYS IN t1;
+ Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+-t1 0 a 1 a # # NULL NULL # HASH
++t1 0 a 1 a # # NULL NULL # LSMTREE
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b');
+ INSERT INTO t1 (a,b) VALUES (1,'c');
+ ERROR 23000: Duplicate entry '1' for key 'a'
diff --git a/storage/rocksdb/mysql-test/storage_engine/mask_engine.inc b/storage/rocksdb/mysql-test/storage_engine/mask_engine.inc
new file mode 100644
index 00000000..fc6cd02e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/mask_engine.inc
@@ -0,0 +1,15 @@
+#
+# This include file just replaces the storage engine under test by the generic string <STORAGE_ENGINE>
+# in the next statement. More masks can be added by defining $add_regex, e.g.
+# let $add_regex = /$data_dir/<DATA_DIR>/ /$index_dir/<INDEX_DIR>/
+#
+
+--let $regex = /$storage_engine/<STORAGE_ENGINE>/i / COLLATE[= ]latin1_bin//
+if ($add_regex)
+{
+ --let $regex = $regex $add_regex
+}
+
+--let $add_regex =
+--replace_regex $regex
+
diff --git a/storage/rocksdb/mysql-test/storage_engine/misc.rdiff b/storage/rocksdb/mysql-test/storage_engine/misc.rdiff
new file mode 100644
index 00000000..cdbad003
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/misc.rdiff
@@ -0,0 +1,34 @@
+--- suite/storage_engine/misc.result 2018-02-23 03:01:49.673249912 +0200
++++ suite/storage_engine/misc.reject 2018-02-23 03:02:05.669249564 +0200
+@@ -28,6 +28,10 @@
+ SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE ORDER BY TABLE_NAME;
+ TABLE_NAME COLUMN_NAME REFERENCED_TABLE_NAME REFERENCED_COLUMN_NAME
++Warning 1286 Unknown storage engine 'InnoDB'
++Warning 1286 Unknown storage engine 'InnoDB'
++Warning 1286 Unknown storage engine 'InnoDB'
++Warnings:
+ column_stats column_name NULL NULL
+ column_stats db_name NULL NULL
+ column_stats table_name NULL NULL
+@@ -58,12 +62,6 @@
+ index_stats index_name NULL NULL
+ index_stats prefix_arity NULL NULL
+ index_stats table_name NULL NULL
+-innodb_index_stats database_name NULL NULL
+-innodb_index_stats index_name NULL NULL
+-innodb_index_stats stat_name NULL NULL
+-innodb_index_stats table_name NULL NULL
+-innodb_table_stats database_name NULL NULL
+-innodb_table_stats table_name NULL NULL
+ plugin name NULL NULL
+ proc db NULL NULL
+ proc name NULL NULL
+@@ -94,7 +92,5 @@
+ time_zone_transition Transition_time NULL NULL
+ time_zone_transition_type Time_zone_id NULL NULL
+ time_zone_transition_type Transition_type_id NULL NULL
+-transaction_registry commit_id NULL NULL
+-transaction_registry transaction_id NULL NULL
+ user Host NULL NULL
+ user User NULL NULL
diff --git a/storage/rocksdb/mysql-test/storage_engine/parts/checksum_table.rdiff b/storage/rocksdb/mysql-test/storage_engine/parts/checksum_table.rdiff
new file mode 100644
index 00000000..bf3347a4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/parts/checksum_table.rdiff
@@ -0,0 +1,13 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/parts/checksum_table.result 2017-06-22 00:33:46.419995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/parts/checksum_table.reject 2017-06-22 19:25:02.935568998 +0300
+@@ -31,8 +31,8 @@
+ test.t1 4272806499
+ CHECKSUM TABLE t1, t2 QUICK;
+ Table Checksum
+-test.t1 4272806499
+-test.t2 0
++test.t1 NULL
++test.t2 NULL
+ CHECKSUM TABLE t1, t2 EXTENDED;
+ Table Checksum
+ test.t1 4272806499
diff --git a/storage/rocksdb/mysql-test/storage_engine/parts/create_table.rdiff b/storage/rocksdb/mysql-test/storage_engine/parts/create_table.rdiff
new file mode 100644
index 00000000..b2cb47a0
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/parts/create_table.rdiff
@@ -0,0 +1,20 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/parts/create_table.result 2017-06-22 00:33:46.419995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/parts/create_table.reject 2017-06-22 19:25:05.335568983 +0300
+@@ -65,7 +65,7 @@
+ 1 SIMPLE t1 abc,def # # # # # # #
+ EXPLAIN PARTITIONS SELECT a FROM t1 WHERE a = 100;
+ id select_type table partitions type possible_keys key key_len ref rows Extra
+-1 SIMPLE NULL NULL # # # # # # #
++1 SIMPLE t1 def # # # # # # #
+ INSERT INTO t1 (a) VALUES (50);
+ ERROR HY000: Table has no partition for value 50
+ DROP TABLE t1;
+@@ -81,7 +81,7 @@
+ 1 SIMPLE t1 abc_abcsp0,def_defsp0 # # # # # # #
+ EXPLAIN PARTITIONS SELECT a FROM t1 WHERE a = 100;
+ id select_type table partitions type possible_keys key key_len ref rows Extra
+-1 SIMPLE NULL NULL # # # # # # #
++1 SIMPLE t1 def_defsp0 # # # # # # #
+ SELECT TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME, SUBPARTITION_NAME, PARTITION_METHOD, SUBPARTITION_METHOD
+ FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 't1';
+ TABLE_SCHEMA TABLE_NAME PARTITION_NAME SUBPARTITION_NAME PARTITION_METHOD SUBPARTITION_METHOD
diff --git a/storage/rocksdb/mysql-test/storage_engine/parts/disabled.def b/storage/rocksdb/mysql-test/storage_engine/parts/disabled.def
new file mode 100644
index 00000000..ef8ad5b3
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/parts/disabled.def
@@ -0,0 +1,3 @@
+alter_table : MDEV-13153 - Assertion `global_status_var.global_memory_used == 0'
+optimize_table : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
+repair_table : MDEV-13148 - LOCK TABLE on RocksDB table fails with a bogus error message
diff --git a/storage/rocksdb/mysql-test/storage_engine/parts/suite.opt b/storage/rocksdb/mysql-test/storage_engine/parts/suite.opt
new file mode 100644
index 00000000..d77a8227
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/parts/suite.opt
@@ -0,0 +1 @@
+--ignore-db-dirs=#rocksdb --plugin-load=$HA_ROCKSDB_SO --binlog_format=ROW --loose-rocksdb_flush_log_at_trx_commit=0
diff --git a/storage/rocksdb/mysql-test/storage_engine/show_engine.rdiff b/storage/rocksdb/mysql-test/storage_engine/show_engine.rdiff
new file mode 100644
index 00000000..15a9bb6f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/show_engine.rdiff
@@ -0,0 +1,15 @@
+--- suite/storage_engine/show_engine.result 2017-03-12 04:57:07.169911845 +0200
++++ suite/storage_engine/show_engine.reject 2017-08-14 22:58:15.508554871 +0300
+@@ -4,7 +4,11 @@
+ # volatile data (timestamps, memory info, etc.)
+ SHOW ENGINE <STORAGE_ENGINE> STATUS;
+ Type Name Status
+-<STORAGE_ENGINE> ### Engine status, can be long and changeable ###
++STATISTICS <STORAGE_ENGINE> ### Engine status, can be long and changeable ###
++DBSTATS <STORAGE_ENGINE> ### Engine status, can be long and changeable ###
++CF_COMPACTION __system__ ### Engine status, can be long and changeable ###
++CF_COMPACTION default ### Engine status, can be long and changeable ###
++MEMORY_STATS <STORAGE_ENGINE> ### Engine status, can be long and changeable ###
+ # For SHOW MUTEX even the number of lines is volatile, so the result logging is disabled,
+ # the test only checks that the command does not produce any errors
+ SHOW ENGINE <STORAGE_ENGINE> MUTEX;
diff --git a/storage/rocksdb/mysql-test/storage_engine/show_table_status.rdiff b/storage/rocksdb/mysql-test/storage_engine/show_table_status.rdiff
new file mode 100644
index 00000000..d7252eb5
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/show_table_status.rdiff
@@ -0,0 +1,20 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/show_table_status.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/show_table_status.reject 2017-06-22 14:04:10.723690009 +0300
+@@ -19,7 +19,7 @@
+ Create_time ###
+ Update_time ###
+ Check_time NULL
+-Collation latin1_swedish_ci
++Collation latin1_bin
+ Checksum NULL
+ Create_options
+ Comment
+@@ -37,7 +37,7 @@
+ Create_time ###
+ Update_time ###
+ Check_time NULL
+-Collation latin1_swedish_ci
++Collation latin1_bin
+ Checksum NULL
+ Create_options
+ Comment
diff --git a/storage/rocksdb/mysql-test/storage_engine/suite.opt b/storage/rocksdb/mysql-test/storage_engine/suite.opt
new file mode 100644
index 00000000..e6122c7e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/suite.opt
@@ -0,0 +1 @@
+--ignore-db-dirs=#rocksdb --plugin-load=$HA_ROCKSDB_SO --binlog_format=ROW --collation-server=latin1_bin --loose-rocksdb_flush_log_at_trx_commit=0
diff --git a/storage/rocksdb/mysql-test/storage_engine/tbl_opt_insert_method.rdiff b/storage/rocksdb/mysql-test/storage_engine/tbl_opt_insert_method.rdiff
new file mode 100644
index 00000000..20f594fb
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/tbl_opt_insert_method.rdiff
@@ -0,0 +1,11 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_opt_insert_method.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_opt_insert_method.reject 2017-06-22 02:39:45.243948128 +0300
+@@ -5,7 +5,7 @@
+ t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` char(8) DEFAULT NULL
+-) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1 INSERT_METHOD=FIRST
++) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1
+ ALTER TABLE t1 INSERT_METHOD=NO;
+ SHOW CREATE TABLE t1;
+ Table Create Table
diff --git a/storage/rocksdb/mysql-test/storage_engine/tbl_opt_union.rdiff b/storage/rocksdb/mysql-test/storage_engine/tbl_opt_union.rdiff
new file mode 100644
index 00000000..0d65ad07
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/tbl_opt_union.rdiff
@@ -0,0 +1,16 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_opt_union.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_opt_union.reject 2017-06-22 02:41:02.719947641 +0300
+@@ -4,11 +4,11 @@
+ Table Create Table
+ t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL
+-) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1 UNION=(`child1`)
++) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1
+ ALTER TABLE t1 UNION = (child1,child2);
+ SHOW CREATE TABLE t1;
+ Table Create Table
+ t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL
+-) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1 UNION=(`child1`,`child2`)
++) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1
+ DROP TABLE t1, child1, child2;
diff --git a/storage/rocksdb/mysql-test/storage_engine/tbl_temporary.rdiff b/storage/rocksdb/mysql-test/storage_engine/tbl_temporary.rdiff
new file mode 100644
index 00000000..d24806e7
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/tbl_temporary.rdiff
@@ -0,0 +1,24 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_temporary.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/tbl_temporary.reject 2017-06-22 15:27:50.643658456 +0300
+@@ -1,11 +1,14 @@
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1 (c CHAR(1)) ENGINE=MyISAM;
+ CREATE TEMPORARY TABLE t1 (a <INT_COLUMN>, b <CHAR_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+-SHOW CREATE TABLE t1;
+-Table Create Table
+-t1 CREATE TEMPORARY TABLE `t1` (
+- `a` int(11) DEFAULT NULL,
+- `b` char(8) DEFAULT NULL
+-) ENGINE=<STORAGE_ENGINE> DEFAULT CHARSET=latin1
+-DROP TEMPORARY TABLE t1;
++ERROR HY000: Table storage engine 'ROCKSDB' does not support the create option 'TEMPORARY'
++# ERROR: Statement ended with errno 1478, errname ER_ILLEGAL_HA_CREATE_OPTION (expected to succeed)
++# ------------ UNEXPECTED RESULT ------------
++# [ CREATE TEMPORARY TABLE t1 (a INT(11) /*!*/ /*Custom column options*/, b CHAR(8) /*!*/ /*Custom column options*/) ENGINE=RocksDB /*!*/ /*Custom table options*/ ]
++# The statement|command finished with ER_ILLEGAL_HA_CREATE_OPTION.
++# Temporary tables or the mix could be unsupported|malfunctioning, or the problem was caused by previous errors.
++# You can change the engine code, or create an rdiff, or disable the test by adding it to disabled.def.
++# Further in this test, the message might sometimes be suppressed; a part of the test might be skipped.
++# Also, this problem may cause a chain effect (more errors of different kinds in the test).
++# -------------------------------------------
+ DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/storage_engine/truncate_table.rdiff b/storage/rocksdb/mysql-test/storage_engine/truncate_table.rdiff
new file mode 100644
index 00000000..9ca7861d
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/truncate_table.rdiff
@@ -0,0 +1,24 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/truncate_table.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/truncate_table.reject 2017-06-22 02:43:27.183946733 +0300
+@@ -29,13 +29,12 @@
+ CREATE TABLE t1 (a <INT_COLUMN>, b <CHAR_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ INSERT INTO t1 (a,b) VALUES (1,'a'),(2,'b'),(3,'c');
+ HANDLER t1 OPEN AS h1;
+-HANDLER h1 READ FIRST;
+-a b
+-1 a
+-TRUNCATE TABLE t1;
+-HANDLER h1 READ NEXT;
+-ERROR 42S02: Unknown table 'h1' in HANDLER
+-HANDLER t1 OPEN AS h2;
+-HANDLER h2 READ FIRST;
+-a b
++ERROR HY000: Storage engine ROCKSDB of the table `test`.`t1` doesn't have this option
++# ------------ UNEXPECTED RESULT ------------
++# The statement|command finished with ER_ILLEGAL_HA.
++# HANDLER or the syntax or the mix could be unsupported.
++# You can change the engine code, or create an rdiff, or disable the test by adding it to disabled.def.
++# Further in this test, the message might sometimes be suppressed; a part of the test might be skipped.
++# Also, this problem may cause a chain effect (more errors of different kinds in the test).
++# -------------------------------------------
+ DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/delete.rdiff b/storage/rocksdb/mysql-test/storage_engine/trx/delete.rdiff
new file mode 100644
index 00000000..dac23b83
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/delete.rdiff
@@ -0,0 +1,10 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/delete.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/delete.reject 2017-06-22 19:29:36.827567276 +0300
+@@ -68,5 +68,7 @@
+ DELETE FROM t1;
+ INSERT INTO t1 (a,b) VALUES (1,'a');
+ ROLLBACK TO SAVEPOINT spt1;
++ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+ COMMIT;
++ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+ DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/disabled.def b/storage/rocksdb/mysql-test/storage_engine/trx/disabled.def
new file mode 100644
index 00000000..4e227c10
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/disabled.def
@@ -0,0 +1,4 @@
+cons_snapshot_serializable : Not supported
+level_read_uncommitted : Not supported
+level_serializable : Not supported
+xa_recovery : MDEV-13155 - XA recovery not supported for RocksDB
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/insert.rdiff b/storage/rocksdb/mysql-test/storage_engine/trx/insert.rdiff
new file mode 100644
index 00000000..36a71076
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/insert.rdiff
@@ -0,0 +1,24 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/insert.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/insert.reject 2017-06-22 19:29:39.131567262 +0300
+@@ -37,18 +37,18 @@
+ INSERT INTO t1 SET a = 11, b = 'f';
+ INSERT t1 SET b = DEFAULT;
+ ROLLBACK TO SAVEPOINT spt1;
++ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+ INSERT INTO t1 (b,a) VALUES ('test1',10);
++ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+ COMMIT;
++ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+ SELECT a,b FROM t1;
+ a b
+ 1 a
+-10 NULL
+ 10 foo
+-10 test1
+ 100 foo
+ 11 abc
+ 2 b
+-20 NULL
+ 3 c
+ 4 d
+ 5 e
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/level_read_committed.rdiff b/storage/rocksdb/mysql-test/storage_engine/trx/level_read_committed.rdiff
new file mode 100644
index 00000000..6b9e4a3f
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/level_read_committed.rdiff
@@ -0,0 +1,10 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/level_read_committed.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/level_read_committed.reject 2017-06-22 19:29:41.459567247 +0300
+@@ -77,6 +77,7 @@
+ CREATE TABLE t1 (a <INT_COLUMN>) ENGINE=<STORAGE_ENGINE> <CUSTOM_TABLE_OPTIONS>;
+ SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+ START TRANSACTION WITH CONSISTENT SNAPSHOT;
++ERROR HY000: Only REPEATABLE READ isolation level is supported for START TRANSACTION WITH CONSISTENT SNAPSHOT in RocksDB Storage Engine.
+ connection con2;
+ INSERT INTO t1 (a) VALUES (1);
+ connection con1;
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/level_repeatable_read.rdiff b/storage/rocksdb/mysql-test/storage_engine/trx/level_repeatable_read.rdiff
new file mode 100644
index 00000000..cf770755
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/level_repeatable_read.rdiff
@@ -0,0 +1,35 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/level_repeatable_read.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/level_repeatable_read.reject 2017-06-22 20:33:13.935543284 +0300
+@@ -24,8 +24,7 @@
+ SELECT a FROM t1;
+ a
+ INSERT INTO t1 (a) SELECT a+100 FROM t1;
+-ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+-# WARNING: Statement ended with errno 1205, errname 'ER_LOCK_WAIT_TIMEOUT'.
++# WARNING: Statement ended with errno 0, errname ''.
+ # If it differs from the result file, it might indicate a problem.
+ SELECT a FROM t1;
+ a
+@@ -47,22 +46,16 @@
+ # If it differs from the result file, it might indicate a problem.
+ SELECT a FROM t1;
+ a
+-201
+-202
+ COMMIT;
+ SELECT a FROM t1;
+ a
+ 1
+ 2
+-201
+-202
+ connection con2;
+ SELECT a FROM t1;
+ a
+ 1
+ 2
+-201
+-202
+ connection default;
+ disconnect con1;
+ disconnect con2;
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/suite.opt b/storage/rocksdb/mysql-test/storage_engine/trx/suite.opt
new file mode 100644
index 00000000..d77a8227
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/suite.opt
@@ -0,0 +1 @@
+--ignore-db-dirs=#rocksdb --plugin-load=$HA_ROCKSDB_SO --binlog_format=ROW --loose-rocksdb_flush_log_at_trx_commit=0
diff --git a/storage/rocksdb/mysql-test/storage_engine/trx/update.rdiff b/storage/rocksdb/mysql-test/storage_engine/trx/update.rdiff
new file mode 100644
index 00000000..ab181947
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/trx/update.rdiff
@@ -0,0 +1,38 @@
+--- /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/update.result 2017-06-22 00:33:46.423995639 +0300
++++ /data/src/bb-10.2-mdev12528/mysql-test/suite/storage_engine/trx/update.reject 2017-06-22 19:29:57.267567148 +0300
+@@ -29,20 +29,23 @@
+ SAVEPOINT spt1;
+ UPDATE t1 SET b = '';
+ ROLLBACK TO SAVEPOINT spt1;
++ERROR HY000: MyRocks currently does not support ROLLBACK TO SAVEPOINT if modifying rows.
+ UPDATE t1 SET b = 'upd' WHERE a = 10050;
++ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+ COMMIT;
++ERROR HY000: This transaction was rolled back and cannot be committed. Only supported operation is to roll it back, so all pending changes will be discarded. Please restart another transaction.
+ SELECT a,b FROM t1;
+ a b
+-10050 upd
+-10050 upd
+-51 update2
+-51 update2
+-52 update2
+-52 update2
+-53 update2
+-53 update2
+-54 update2
+-54 update2
+-55 update2
+-55 update2
++10050 NULL
++10050 NULL
++51 NULL
++51 NULL
++52 NULL
++52 NULL
++53 NULL
++53 NULL
++54 NULL
++54 NULL
++55 NULL
++55 NULL
+ DROP TABLE t1;
diff --git a/storage/rocksdb/mysql-test/storage_engine/type_binary_indexes.rdiff b/storage/rocksdb/mysql-test/storage_engine/type_binary_indexes.rdiff
new file mode 100644
index 00000000..5eec9d24
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/type_binary_indexes.rdiff
@@ -0,0 +1,11 @@
+--- suite/storage_engine/type_binary_indexes.result 2017-03-12 04:57:07.173911845 +0200
++++ suite/storage_engine/type_binary_indexes.reject 2017-08-14 22:54:02.144555066 +0300
+@@ -91,7 +91,7 @@
+ INSERT INTO t1 (b,b20,v16,v128) SELECT b,b20,v16,v128 FROM t1;
+ EXPLAIN SELECT HEX(SUBSTRING(v16,0,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # NULL # # # #
++# # # # # v16 # # # #
+ SELECT HEX(SUBSTRING(v16,7,3)) FROM t1 WHERE v16 LIKE 'varchar%';
+ HEX(SUBSTRING(v16,7,3))
+ 723161
diff --git a/storage/rocksdb/mysql-test/storage_engine/type_bit_indexes.rdiff b/storage/rocksdb/mysql-test/storage_engine/type_bit_indexes.rdiff
new file mode 100644
index 00000000..e53a33b4
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/type_bit_indexes.rdiff
@@ -0,0 +1,20 @@
+--- suite/storage_engine/type_bit_indexes.result 2017-12-12 20:34:34.000000000 +0200
++++ suite/storage_engine/type_bit_indexes.reject 2017-12-12 20:35:24.539330056 +0200
+@@ -69,7 +69,7 @@
+ (1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF);
+ EXPLAIN SELECT HEX(b+c) FROM t1 WHERE c > 1 OR HEX(b) < 0xFFFFFF;
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # b_c # # # #
++# # # # # NULL # # # #
+ SELECT HEX(b+c) FROM t1 WHERE c > 1 OR HEX(b) < 0xFFFFFF;
+ HEX(b+c)
+ 10
+@@ -98,7 +98,7 @@
+ (1,0xFFFF,0xFFFFFFFF,0xFFFFFFFFFFFFFFFF);
+ EXPLAIN SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # a # # # #
++# # # # # NULL # # # #
+ SELECT DISTINCT a+0 FROM t1 ORDER BY a;
+ a+0
+ 0
diff --git a/storage/rocksdb/mysql-test/storage_engine/type_enum_indexes.rdiff b/storage/rocksdb/mysql-test/storage_engine/type_enum_indexes.rdiff
new file mode 100644
index 00000000..be83fb6e
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/type_enum_indexes.rdiff
@@ -0,0 +1,11 @@
+--- suite/storage_engine/type_enum_indexes.result 2017-03-12 04:38:50.000000000 +0200
++++ suite/storage_engine/type_enum_indexes.reject 2017-12-12 20:36:47.455331726 +0200
+@@ -30,7 +30,7 @@
+ t1 0 a_b 2 b # # NULL NULL # #
+ EXPLAIN SELECT a FROM t1 WHERE b > 'test2' ORDER BY a;
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # a_b # # # #
++# # # # # NULL # # # #
+ SELECT a FROM t1 WHERE b > 'test2' ORDER BY a;
+ a
+ Africa
diff --git a/storage/rocksdb/mysql-test/storage_engine/type_set_indexes.rdiff b/storage/rocksdb/mysql-test/storage_engine/type_set_indexes.rdiff
new file mode 100644
index 00000000..2703e81b
--- /dev/null
+++ b/storage/rocksdb/mysql-test/storage_engine/type_set_indexes.rdiff
@@ -0,0 +1,20 @@
+--- suite/storage_engine/type_set_indexes.result 2017-03-12 04:38:50.000000000 +0200
++++ suite/storage_engine/type_set_indexes.reject 2017-12-12 20:37:16.187332305 +0200
+@@ -97,7 +97,7 @@
+ Warning 1265 Data truncated for column 'b' at row 7
+ EXPLAIN SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # a # # # #
++# # # # # NULL # # # #
+ SELECT a FROM t1 WHERE FIND_IN_SET('Europe',a) > 0;
+ a
+ Africa,Europe,Asia
+@@ -124,7 +124,7 @@
+ Warning 1265 Data truncated for column 'b' at row 7
+ EXPLAIN SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+ id select_type table type possible_keys key key_len ref rows Extra
+-# # # # # b_a # # # #
++# # # # # NULL # # # #
+ SELECT DISTINCT a, b FROM t1 ORDER BY b DESC, a;
+ a b
+ test1,test3
diff --git a/storage/rocksdb/nosql_access.cc b/storage/rocksdb/nosql_access.cc
new file mode 100644
index 00000000..9dcbe3fb
--- /dev/null
+++ b/storage/rocksdb/nosql_access.cc
@@ -0,0 +1,53 @@
+/*
+ Copyright (c) 2019, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#define MYSQL_SERVER 1
+
+#include <my_global.h>
+/* This C++ file's header */
+#include "./nosql_access.h"
+
+/* C++ standard header files */
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+/* C standard header files */
+#include <ctype.h>
+
+/* MySQL header files */
+#include "../../sql/item.h"
+#include "../../sql/sql_base.h"
+#include "../../sql/sql_class.h"
+#include "../../sql/strfunc.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./rdb_buff.h"
+#include "./rdb_datadic.h"
+
+namespace myrocks {
+
+bool rocksdb_handle_single_table_select(THD * /* unused */,
+ st_select_lex * /* unused */) {
+ return false;
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/nosql_access.h b/storage/rocksdb/nosql_access.h
new file mode 100644
index 00000000..70aaa400
--- /dev/null
+++ b/storage/rocksdb/nosql_access.h
@@ -0,0 +1,36 @@
+/*
+ Copyright (c) 2019, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/* C++ standard header files */
+#include <array>
+#include <string>
+#include <vector>
+
+/* C standard header files */
+#include <ctype.h>
+
+/* MySQL header files */
+#include "../../sql/protocol.h"
+#include "./sql_string.h"
+
+#pragma once
+
+namespace myrocks {
+
+// Not needed in MyRocks:
+//bool rocksdb_handle_single_table_select(THD *thd, st_select_lex *select_lex);
+
+} // namespace myrocks
diff --git a/storage/rocksdb/properties_collector.cc b/storage/rocksdb/properties_collector.cc
new file mode 100644
index 00000000..da6afa71
--- /dev/null
+++ b/storage/rocksdb/properties_collector.cc
@@ -0,0 +1,546 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef _WIN32
+#define _CRT_RAND_S
+#endif
+#include <my_global.h>
+#ifdef _WIN32
+#include <stdlib.h>
+#define rand_r rand_s
+#endif
+/* This C++ file's header file */
+#include "./properties_collector.h"
+
+/* Standard C++ header files */
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./log.h"
+#include "./my_stacktrace.h"
+#include "./sql_array.h"
+
+/* MyRocks header files */
+#include "./rdb_datadic.h"
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+std::atomic<uint64_t> rocksdb_num_sst_entry_put(0);
+std::atomic<uint64_t> rocksdb_num_sst_entry_delete(0);
+std::atomic<uint64_t> rocksdb_num_sst_entry_singledelete(0);
+std::atomic<uint64_t> rocksdb_num_sst_entry_merge(0);
+std::atomic<uint64_t> rocksdb_num_sst_entry_other(0);
+my_bool rocksdb_compaction_sequential_deletes_count_sd = false;
+
+Rdb_tbl_prop_coll::Rdb_tbl_prop_coll(Rdb_ddl_manager *const ddl_manager,
+ const Rdb_compact_params &params,
+ const uint32_t cf_id,
+ const uint8_t table_stats_sampling_pct)
+ : m_cf_id(cf_id),
+ m_ddl_manager(ddl_manager),
+ m_last_stats(nullptr),
+ m_rows(0l),
+ m_window_pos(0l),
+ m_deleted_rows(0l),
+ m_max_deleted_rows(0l),
+ m_file_size(0),
+ m_params(params),
+ m_cardinality_collector(table_stats_sampling_pct),
+ m_recorded(false) {
+ DBUG_ASSERT(ddl_manager != nullptr);
+
+ m_deleted_rows_window.resize(m_params.m_window, false);
+}
+
+/*
+ This function is called by RocksDB for every key in the SST file
+*/
+rocksdb::Status Rdb_tbl_prop_coll::AddUserKey(const rocksdb::Slice &key,
+ const rocksdb::Slice &value,
+ rocksdb::EntryType type,
+ rocksdb::SequenceNumber seq,
+ uint64_t file_size) {
+ if (key.size() >= 4) {
+ AdjustDeletedRows(type);
+
+ m_rows++;
+
+ CollectStatsForRow(key, value, type, file_size);
+ }
+
+ return rocksdb::Status::OK();
+}
+
+void Rdb_tbl_prop_coll::AdjustDeletedRows(rocksdb::EntryType type) {
+ if (m_params.m_window > 0) {
+ // record the "is deleted" flag into the sliding window
+ // the sliding window is implemented as a circular buffer
+ // in m_deleted_rows_window vector
+ // the current position in the circular buffer is pointed at by
+ // m_rows % m_deleted_rows_window.size()
+ // m_deleted_rows is the current number of 1's in the vector
+ // --update the counter for the element which will be overridden
+ const bool is_delete = (type == rocksdb::kEntryDelete ||
+ (type == rocksdb::kEntrySingleDelete &&
+ rocksdb_compaction_sequential_deletes_count_sd));
+
+ // Only make changes if the value at the current position needs to change
+ if (is_delete != m_deleted_rows_window[m_window_pos]) {
+ // Set or clear the flag at the current position as appropriate
+ m_deleted_rows_window[m_window_pos] = is_delete;
+ if (!is_delete) {
+ m_deleted_rows--;
+ } else if (++m_deleted_rows > m_max_deleted_rows) {
+ m_max_deleted_rows = m_deleted_rows;
+ }
+ }
+
+ if (++m_window_pos == m_params.m_window) {
+ m_window_pos = 0;
+ }
+ }
+}
+
+Rdb_index_stats *Rdb_tbl_prop_coll::AccessStats(const rocksdb::Slice &key) {
+ GL_INDEX_ID gl_index_id;
+ gl_index_id.cf_id = m_cf_id;
+ gl_index_id.index_id = rdb_netbuf_to_uint32(reinterpret_cast<const uchar*>(key.data()));
+
+ if (m_last_stats == nullptr || m_last_stats->m_gl_index_id != gl_index_id) {
+ m_keydef = nullptr;
+
+ // starting a new table
+ // add the new element into m_stats
+ m_stats.emplace_back(gl_index_id);
+ m_last_stats = &m_stats.back();
+
+ if (m_ddl_manager) {
+ // safe_find() returns a std::shared_ptr<Rdb_key_def> with the count
+ // incremented (so it can't be deleted out from under us) and with
+ // the mutex locked (if setup has not occurred yet). We must make
+ // sure to free the mutex (via unblock_setup()) when we are done
+ // with this object. Currently this happens earlier in this function
+ // when we are switching to a new Rdb_key_def and when this object
+ // is destructed.
+ m_keydef = m_ddl_manager->safe_find(gl_index_id);
+ if (m_keydef != nullptr) {
+ // resize the array to the number of columns.
+ // It will be initialized with zeroes
+ m_last_stats->m_distinct_keys_per_prefix.resize(
+ m_keydef->get_key_parts());
+ m_last_stats->m_name = m_keydef->get_name();
+ }
+ }
+ m_cardinality_collector.Reset();
+ }
+
+ return m_last_stats;
+}
+
+void Rdb_tbl_prop_coll::CollectStatsForRow(const rocksdb::Slice &key,
+ const rocksdb::Slice &value,
+ const rocksdb::EntryType &type,
+ const uint64_t file_size) {
+ auto stats = AccessStats(key);
+
+ stats->m_data_size += key.size() + value.size();
+
+ // Incrementing per-index entry-type statistics
+ switch (type) {
+ case rocksdb::kEntryPut:
+ stats->m_rows++;
+ break;
+ case rocksdb::kEntryDelete:
+ stats->m_entry_deletes++;
+ break;
+ case rocksdb::kEntrySingleDelete:
+ stats->m_entry_single_deletes++;
+ break;
+ case rocksdb::kEntryMerge:
+ stats->m_entry_merges++;
+ break;
+ case rocksdb::kEntryOther:
+ stats->m_entry_others++;
+ break;
+ default:
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Unexpected entry type found: %u. "
+ "This should not happen so aborting the system.",
+ type);
+ abort();
+ break;
+ }
+
+ stats->m_actual_disk_size += file_size - m_file_size;
+ m_file_size = file_size;
+
+ if (m_keydef != nullptr) {
+ m_cardinality_collector.ProcessKey(key, m_keydef.get(), stats);
+ }
+}
+
+const char *Rdb_tbl_prop_coll::INDEXSTATS_KEY = "__indexstats__";
+
+/*
+ This function is called by RocksDB to compute properties to store in sst file
+*/
+rocksdb::Status Rdb_tbl_prop_coll::Finish(
+ rocksdb::UserCollectedProperties *const properties) {
+ uint64_t num_sst_entry_put = 0;
+ uint64_t num_sst_entry_delete = 0;
+ uint64_t num_sst_entry_singledelete = 0;
+ uint64_t num_sst_entry_merge = 0;
+ uint64_t num_sst_entry_other = 0;
+
+ DBUG_ASSERT(properties != nullptr);
+
+ for (auto it = m_stats.begin(); it != m_stats.end(); it++) {
+ num_sst_entry_put += it->m_rows;
+ num_sst_entry_delete += it->m_entry_deletes;
+ num_sst_entry_singledelete += it->m_entry_single_deletes;
+ num_sst_entry_merge += it->m_entry_merges;
+ num_sst_entry_other += it->m_entry_others;
+ }
+
+ if (!m_recorded) {
+ if (num_sst_entry_put > 0) {
+ rocksdb_num_sst_entry_put += num_sst_entry_put;
+ }
+
+ if (num_sst_entry_delete > 0) {
+ rocksdb_num_sst_entry_delete += num_sst_entry_delete;
+ }
+
+ if (num_sst_entry_singledelete > 0) {
+ rocksdb_num_sst_entry_singledelete += num_sst_entry_singledelete;
+ }
+
+ if (num_sst_entry_merge > 0) {
+ rocksdb_num_sst_entry_merge += num_sst_entry_merge;
+ }
+
+ if (num_sst_entry_other > 0) {
+ rocksdb_num_sst_entry_other += num_sst_entry_other;
+ }
+
+ for (Rdb_index_stats &stat : m_stats) {
+ m_cardinality_collector.AdjustStats(&stat);
+ }
+ m_recorded = true;
+ }
+ properties->insert({INDEXSTATS_KEY, Rdb_index_stats::materialize(m_stats)});
+ return rocksdb::Status::OK();
+}
+
+bool Rdb_tbl_prop_coll::NeedCompact() const {
+ return m_params.m_deletes && (m_params.m_window > 0) &&
+ (m_file_size > m_params.m_file_size) &&
+ (m_max_deleted_rows > m_params.m_deletes);
+}
+
+/*
+ Returns the same as above, but in human-readable way for logging
+*/
+rocksdb::UserCollectedProperties Rdb_tbl_prop_coll::GetReadableProperties()
+ const {
+ std::string s;
+#ifdef DBUG_OFF
+ s.append("[...");
+ s.append(std::to_string(m_stats.size()));
+ s.append(" records...]");
+#else
+ bool first = true;
+ for (auto it : m_stats) {
+ if (first) {
+ first = false;
+ } else {
+ s.append(",");
+ }
+ s.append(GetReadableStats(it));
+ }
+#endif
+ return rocksdb::UserCollectedProperties{{INDEXSTATS_KEY, s}};
+}
+
+std::string Rdb_tbl_prop_coll::GetReadableStats(const Rdb_index_stats &it) {
+ std::string s;
+ s.append("(");
+ s.append(std::to_string(it.m_gl_index_id.cf_id));
+ s.append(", ");
+ s.append(std::to_string(it.m_gl_index_id.index_id));
+ s.append("):{name:");
+ s.append(it.m_name);
+ s.append(", size:");
+ s.append(std::to_string(it.m_data_size));
+ s.append(", m_rows:");
+ s.append(std::to_string(it.m_rows));
+ s.append(", m_actual_disk_size:");
+ s.append(std::to_string(it.m_actual_disk_size));
+ s.append(", deletes:");
+ s.append(std::to_string(it.m_entry_deletes));
+ s.append(", single_deletes:");
+ s.append(std::to_string(it.m_entry_single_deletes));
+ s.append(", merges:");
+ s.append(std::to_string(it.m_entry_merges));
+ s.append(", others:");
+ s.append(std::to_string(it.m_entry_others));
+ s.append(", distincts per prefix: [");
+ for (auto num : it.m_distinct_keys_per_prefix) {
+ s.append(std::to_string(num));
+ s.append(" ");
+ }
+ s.append("]}");
+ return s;
+}
+
+/*
+ Given the properties of an SST file, reads the stats from it and returns it.
+*/
+
+void Rdb_tbl_prop_coll::read_stats_from_tbl_props(
+ const std::shared_ptr<const rocksdb::TableProperties> &table_props,
+ std::vector<Rdb_index_stats> *const out_stats_vector) {
+ DBUG_ASSERT(out_stats_vector != nullptr);
+ const auto &user_properties = table_props->user_collected_properties;
+ const auto it2 = user_properties.find(std::string(INDEXSTATS_KEY));
+ if (it2 != user_properties.end()) {
+ auto result MY_ATTRIBUTE((__unused__)) =
+ Rdb_index_stats::unmaterialize(it2->second, out_stats_vector);
+ DBUG_ASSERT(result == 0);
+ }
+}
+
+/*
+ Serializes an array of Rdb_index_stats into a network string.
+*/
+std::string Rdb_index_stats::materialize(
+ const std::vector<Rdb_index_stats> &stats) {
+ String ret;
+ rdb_netstr_append_uint16(&ret, INDEX_STATS_VERSION_ENTRY_TYPES);
+ for (const auto &i : stats) {
+ rdb_netstr_append_uint32(&ret, i.m_gl_index_id.cf_id);
+ rdb_netstr_append_uint32(&ret, i.m_gl_index_id.index_id);
+ DBUG_ASSERT(sizeof i.m_data_size <= 8);
+ rdb_netstr_append_uint64(&ret, i.m_data_size);
+ rdb_netstr_append_uint64(&ret, i.m_rows);
+ rdb_netstr_append_uint64(&ret, i.m_actual_disk_size);
+ rdb_netstr_append_uint64(&ret, i.m_distinct_keys_per_prefix.size());
+ rdb_netstr_append_uint64(&ret, i.m_entry_deletes);
+ rdb_netstr_append_uint64(&ret, i.m_entry_single_deletes);
+ rdb_netstr_append_uint64(&ret, i.m_entry_merges);
+ rdb_netstr_append_uint64(&ret, i.m_entry_others);
+ for (const auto &num_keys : i.m_distinct_keys_per_prefix) {
+ rdb_netstr_append_uint64(&ret, num_keys);
+ }
+ }
+
+ return std::string((char *)ret.ptr(), ret.length());
+}
+
+/**
+ @brief
+ Reads an array of Rdb_index_stats from a string.
+ @return HA_EXIT_FAILURE if it detects any inconsistency in the input
+ @return HA_EXIT_SUCCESS if completes successfully
+*/
+int Rdb_index_stats::unmaterialize(const std::string &s,
+ std::vector<Rdb_index_stats> *const ret) {
+ const uchar *p = rdb_std_str_to_uchar_ptr(s);
+ const uchar *const p2 = p + s.size();
+
+ DBUG_ASSERT(ret != nullptr);
+
+ if (p + 2 > p2) {
+ return HA_EXIT_FAILURE;
+ }
+
+ const int version = rdb_netbuf_read_uint16(&p);
+ Rdb_index_stats stats;
+ // Make sure version is within supported range.
+ if (version < INDEX_STATS_VERSION_INITIAL ||
+ version > INDEX_STATS_VERSION_ENTRY_TYPES) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "Index stats version %d was outside of supported range. "
+ "This should not happen so aborting the system.",
+ version);
+ abort();
+ }
+
+ size_t needed = sizeof(stats.m_gl_index_id.cf_id) +
+ sizeof(stats.m_gl_index_id.index_id) +
+ sizeof(stats.m_data_size) + sizeof(stats.m_rows) +
+ sizeof(stats.m_actual_disk_size) + sizeof(uint64);
+ if (version >= INDEX_STATS_VERSION_ENTRY_TYPES) {
+ needed += sizeof(stats.m_entry_deletes) +
+ sizeof(stats.m_entry_single_deletes) +
+ sizeof(stats.m_entry_merges) + sizeof(stats.m_entry_others);
+ }
+
+ while (p < p2) {
+ if (p + needed > p2) {
+ return HA_EXIT_FAILURE;
+ }
+ rdb_netbuf_read_gl_index(&p, &stats.m_gl_index_id);
+ stats.m_data_size = rdb_netbuf_read_uint64(&p);
+ stats.m_rows = rdb_netbuf_read_uint64(&p);
+ stats.m_actual_disk_size = rdb_netbuf_read_uint64(&p);
+ stats.m_distinct_keys_per_prefix.resize(rdb_netbuf_read_uint64(&p));
+ if (version >= INDEX_STATS_VERSION_ENTRY_TYPES) {
+ stats.m_entry_deletes = rdb_netbuf_read_uint64(&p);
+ stats.m_entry_single_deletes = rdb_netbuf_read_uint64(&p);
+ stats.m_entry_merges = rdb_netbuf_read_uint64(&p);
+ stats.m_entry_others = rdb_netbuf_read_uint64(&p);
+ }
+ if (p + stats.m_distinct_keys_per_prefix.size() *
+ sizeof(stats.m_distinct_keys_per_prefix[0]) >
+ p2) {
+ return HA_EXIT_FAILURE;
+ }
+ for (std::size_t i = 0; i < stats.m_distinct_keys_per_prefix.size(); i++) {
+ stats.m_distinct_keys_per_prefix[i] = rdb_netbuf_read_uint64(&p);
+ }
+ ret->push_back(stats);
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Merges one Rdb_index_stats into another. Can be used to come up with the stats
+ for the index based on stats for each sst
+*/
+void Rdb_index_stats::merge(const Rdb_index_stats &s, const bool increment,
+ const int64_t estimated_data_len) {
+ std::size_t i;
+
+ DBUG_ASSERT(estimated_data_len >= 0);
+
+ m_gl_index_id = s.m_gl_index_id;
+ if (m_distinct_keys_per_prefix.size() < s.m_distinct_keys_per_prefix.size()) {
+ m_distinct_keys_per_prefix.resize(s.m_distinct_keys_per_prefix.size());
+ }
+ if (increment) {
+ m_rows += s.m_rows;
+ m_data_size += s.m_data_size;
+
+ /*
+ The Data_length and Avg_row_length are trailing statistics, meaning
+ they don't get updated for the current SST until the next SST is
+ written. So, if rocksdb reports the data_length as 0,
+ we make a reasoned estimate for the data_file_length for the
+ index in the current SST.
+ */
+ m_actual_disk_size += s.m_actual_disk_size ? s.m_actual_disk_size
+ : estimated_data_len * s.m_rows;
+ m_entry_deletes += s.m_entry_deletes;
+ m_entry_single_deletes += s.m_entry_single_deletes;
+ m_entry_merges += s.m_entry_merges;
+ m_entry_others += s.m_entry_others;
+ for (i = 0; i < s.m_distinct_keys_per_prefix.size(); i++) {
+ m_distinct_keys_per_prefix[i] += s.m_distinct_keys_per_prefix[i];
+ }
+ } else {
+ m_rows -= s.m_rows;
+ m_data_size -= s.m_data_size;
+ m_actual_disk_size -= s.m_actual_disk_size ? s.m_actual_disk_size
+ : estimated_data_len * s.m_rows;
+ m_entry_deletes -= s.m_entry_deletes;
+ m_entry_single_deletes -= s.m_entry_single_deletes;
+ m_entry_merges -= s.m_entry_merges;
+ m_entry_others -= s.m_entry_others;
+ for (i = 0; i < s.m_distinct_keys_per_prefix.size(); i++) {
+ m_distinct_keys_per_prefix[i] -= s.m_distinct_keys_per_prefix[i];
+ }
+ }
+}
+
+Rdb_tbl_card_coll::Rdb_tbl_card_coll(const uint8_t table_stats_sampling_pct)
+ : m_table_stats_sampling_pct(table_stats_sampling_pct),
+ m_seed(time(nullptr)) {}
+
+bool Rdb_tbl_card_coll::IsSampingDisabled() {
+ // Zero means that we'll use all the keys to update statistics.
+ return m_table_stats_sampling_pct == 0 ||
+ RDB_TBL_STATS_SAMPLE_PCT_MAX == m_table_stats_sampling_pct;
+}
+
+bool Rdb_tbl_card_coll::ShouldCollectStats() {
+ if (IsSampingDisabled()) {
+ return true; // collect every key
+ }
+
+ const int val = rand_r(&m_seed) % (RDB_TBL_STATS_SAMPLE_PCT_MAX -
+ RDB_TBL_STATS_SAMPLE_PCT_MIN + 1) +
+ RDB_TBL_STATS_SAMPLE_PCT_MIN;
+
+ DBUG_ASSERT(val >= RDB_TBL_STATS_SAMPLE_PCT_MIN);
+ DBUG_ASSERT(val <= RDB_TBL_STATS_SAMPLE_PCT_MAX);
+
+ return val <= m_table_stats_sampling_pct;
+}
+
+void Rdb_tbl_card_coll::ProcessKey(const rocksdb::Slice &key,
+ const Rdb_key_def *keydef,
+ Rdb_index_stats *stats) {
+ if (ShouldCollectStats()) {
+ std::size_t column = 0;
+ bool new_key = true;
+
+ if (!m_last_key.empty()) {
+ rocksdb::Slice last(m_last_key.data(), m_last_key.size());
+ new_key = (keydef->compare_keys(&last, &key, &column) == 0);
+ }
+
+ if (new_key) {
+ DBUG_ASSERT(column <= stats->m_distinct_keys_per_prefix.size());
+
+ for (auto i = column; i < stats->m_distinct_keys_per_prefix.size(); i++) {
+ stats->m_distinct_keys_per_prefix[i]++;
+ }
+
+ // assign new last_key for the next call
+ // however, we only need to change the last key
+ // if one of the first n-1 columns is different
+ // If the n-1 prefix is the same, no sense in storing
+ // the new key
+ if (column < stats->m_distinct_keys_per_prefix.size()) {
+ m_last_key.assign(key.data(), key.size());
+ }
+ }
+ }
+}
+
+void Rdb_tbl_card_coll::Reset() { m_last_key.clear(); }
+
+// We need to adjust the index cardinality numbers based on the sampling
+// rate so that the output of "SHOW INDEX" command will reflect reality
+// more closely. It will still be an approximation, just a better one.
+void Rdb_tbl_card_coll::AdjustStats(Rdb_index_stats *stats) {
+ if (IsSampingDisabled()) {
+ // no sampling was done, return as stats is
+ return;
+ }
+ for (int64_t &num_keys : stats->m_distinct_keys_per_prefix) {
+ num_keys = num_keys * 100 / m_table_stats_sampling_pct;
+ }
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/properties_collector.h b/storage/rocksdb/properties_collector.h
new file mode 100644
index 00000000..ce2773cd
--- /dev/null
+++ b/storage/rocksdb/properties_collector.h
@@ -0,0 +1,215 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ system header files */
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+/* RocksDB header files */
+#include "rocksdb/db.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+
+namespace myrocks {
+
+class Rdb_ddl_manager;
+class Rdb_key_def;
+
+extern std::atomic<uint64_t> rocksdb_num_sst_entry_put;
+extern std::atomic<uint64_t> rocksdb_num_sst_entry_delete;
+extern std::atomic<uint64_t> rocksdb_num_sst_entry_singledelete;
+extern std::atomic<uint64_t> rocksdb_num_sst_entry_merge;
+extern std::atomic<uint64_t> rocksdb_num_sst_entry_other;
+extern my_bool rocksdb_compaction_sequential_deletes_count_sd;
+
+struct Rdb_compact_params {
+ uint64_t m_deletes, m_window, m_file_size;
+};
+
+struct Rdb_index_stats {
+ enum {
+ INDEX_STATS_VERSION_INITIAL = 1,
+ INDEX_STATS_VERSION_ENTRY_TYPES = 2,
+ };
+ GL_INDEX_ID m_gl_index_id;
+ int64_t m_data_size, m_rows, m_actual_disk_size;
+ int64_t m_entry_deletes, m_entry_single_deletes;
+ int64_t m_entry_merges, m_entry_others;
+ std::vector<int64_t> m_distinct_keys_per_prefix;
+ std::string m_name; // name is not persisted
+
+ static std::string materialize(const std::vector<Rdb_index_stats> &stats);
+ static int unmaterialize(const std::string &s,
+ std::vector<Rdb_index_stats> *const ret);
+
+ Rdb_index_stats() : Rdb_index_stats({0, 0}) {}
+ explicit Rdb_index_stats(GL_INDEX_ID gl_index_id)
+ : m_gl_index_id(gl_index_id),
+ m_data_size(0),
+ m_rows(0),
+ m_actual_disk_size(0),
+ m_entry_deletes(0),
+ m_entry_single_deletes(0),
+ m_entry_merges(0),
+ m_entry_others(0) {}
+
+ void merge(const Rdb_index_stats &s, const bool increment = true,
+ const int64_t estimated_data_len = 0);
+};
+
+// The helper class to calculate index cardinality
+class Rdb_tbl_card_coll {
+ public:
+ explicit Rdb_tbl_card_coll(const uint8_t table_stats_sampling_pct);
+
+ public:
+ void ProcessKey(const rocksdb::Slice &key, const Rdb_key_def *keydef,
+ Rdb_index_stats *stats);
+ /*
+ * Resets the state of the collector to start calculating statistics for a
+ * next index.
+ */
+ void Reset();
+
+ /*
+ * Cardinality statistics might be calculated using some sampling strategy.
+ * This method adjusts gathered statistics according to the sampling
+ * strategy used. Note that adjusted cardinality value is just an estimate
+ * and can return a value exeeding number of rows in a table, so the
+ * returned value should be capped by row count before using it by
+ * an optrimizer or displaying it to a clent.
+ */
+ void AdjustStats(Rdb_index_stats *stats);
+
+ private:
+ bool ShouldCollectStats();
+ bool IsSampingDisabled();
+
+ private:
+ std::string m_last_key;
+ uint8_t m_table_stats_sampling_pct;
+ unsigned int m_seed;
+};
+
+class Rdb_tbl_prop_coll : public rocksdb::TablePropertiesCollector {
+ public:
+ Rdb_tbl_prop_coll(Rdb_ddl_manager *const ddl_manager,
+ const Rdb_compact_params &params, const uint32_t cf_id,
+ const uint8_t table_stats_sampling_pct);
+
+ /*
+ Override parent class's virtual methods of interest.
+ */
+
+ virtual rocksdb::Status AddUserKey(const rocksdb::Slice &key,
+ const rocksdb::Slice &value,
+ rocksdb::EntryType type,
+ rocksdb::SequenceNumber seq,
+ uint64_t file_size) override;
+
+ virtual rocksdb::Status Finish(
+ rocksdb::UserCollectedProperties *properties) override;
+
+ virtual const char *Name() const override { return "Rdb_tbl_prop_coll"; }
+
+ rocksdb::UserCollectedProperties GetReadableProperties() const override;
+
+ bool NeedCompact() const override;
+
+ public:
+ uint64_t GetMaxDeletedRows() const { return m_max_deleted_rows; }
+
+ static void read_stats_from_tbl_props(
+ const std::shared_ptr<const rocksdb::TableProperties> &table_props,
+ std::vector<Rdb_index_stats> *out_stats_vector);
+
+ private:
+ static std::string GetReadableStats(const Rdb_index_stats &it);
+
+ bool ShouldCollectStats();
+ void CollectStatsForRow(const rocksdb::Slice &key,
+ const rocksdb::Slice &value,
+ const rocksdb::EntryType &type,
+ const uint64_t file_size);
+ Rdb_index_stats *AccessStats(const rocksdb::Slice &key);
+ void AdjustDeletedRows(rocksdb::EntryType type);
+
+ private:
+ uint32_t m_cf_id;
+ std::shared_ptr<const Rdb_key_def> m_keydef;
+ Rdb_ddl_manager *m_ddl_manager;
+ std::vector<Rdb_index_stats> m_stats;
+ Rdb_index_stats *m_last_stats;
+ static const char *INDEXSTATS_KEY;
+
+ // last added key
+ std::string m_last_key;
+
+ // floating window to count deleted rows
+ std::vector<bool> m_deleted_rows_window;
+ uint64_t m_rows, m_window_pos, m_deleted_rows, m_max_deleted_rows;
+ uint64_t m_file_size;
+ Rdb_compact_params m_params;
+ Rdb_tbl_card_coll m_cardinality_collector;
+ bool m_recorded;
+};
+
+class Rdb_tbl_prop_coll_factory
+ : public rocksdb::TablePropertiesCollectorFactory {
+ public:
+ Rdb_tbl_prop_coll_factory(const Rdb_tbl_prop_coll_factory &) = delete;
+ Rdb_tbl_prop_coll_factory &operator=(const Rdb_tbl_prop_coll_factory &) =
+ delete;
+
+ explicit Rdb_tbl_prop_coll_factory(Rdb_ddl_manager *ddl_manager)
+ : m_ddl_manager(ddl_manager) {}
+
+ /*
+ Override parent class's virtual methods of interest.
+ */
+
+ virtual rocksdb::TablePropertiesCollector *CreateTablePropertiesCollector(
+ rocksdb::TablePropertiesCollectorFactory::Context context) override {
+ return new Rdb_tbl_prop_coll(m_ddl_manager, m_params,
+ context.column_family_id,
+ m_table_stats_sampling_pct);
+ }
+
+ virtual const char *Name() const override {
+ return "Rdb_tbl_prop_coll_factory";
+ }
+
+ public:
+ void SetCompactionParams(const Rdb_compact_params &params) {
+ m_params = params;
+ }
+
+ void SetTableStatsSamplingPct(const uint8_t table_stats_sampling_pct) {
+ m_table_stats_sampling_pct = table_stats_sampling_pct;
+ }
+
+ private:
+ Rdb_ddl_manager *const m_ddl_manager;
+ Rdb_compact_params m_params;
+ uint8_t m_table_stats_sampling_pct;
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_buff.h b/storage/rocksdb/rdb_buff.h
new file mode 100644
index 00000000..30052111
--- /dev/null
+++ b/storage/rocksdb/rdb_buff.h
@@ -0,0 +1,549 @@
+/*
+ Portions Copyright (c) 2016-Present, Facebook, Inc.
+ Portions Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+#include <stdlib.h>
+#define htobe64 _byteswap_uint64
+#define be64toh _byteswap_uint64
+#define htobe32 _byteswap_ulong
+#define be32toh _byteswap_ulong
+#define htobe16 _byteswap_ushort
+#define be16toh _byteswap_ushort
+#endif
+
+#if defined(__APPLE__)
+#include <libkern/OSByteOrder.h>
+#define htobe64(x) OSSwapHostToBigInt64(x)
+#define be64toh(x) OSSwapBigToHostInt64(x)
+#define htobe32(x) OSSwapHostToBigInt32(x)
+#define be32toh(x) OSSwapBigToHostInt32(x)
+#define htobe16(x) OSSwapHostToBigInt16(x)
+#define be16toh(x) OSSwapBigToHostInt16(x)
+#endif
+
+/* MySQL header files */
+#include "./my_global.h"
+
+/* MyRocks header files */
+#include "./rdb_global.h"
+#include "./rdb_utils.h"
+
+/* RocksDB header files */
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+namespace myrocks {
+
+/*
+ Basic composition functions for a network buffer presented as a MySQL String
+ ("netstr") which stores data in Network Byte Order (Big Endian).
+*/
+
+inline void rdb_netstr_append_uint64(my_core::String *const out_netstr,
+ const uint64 val) {
+ DBUG_ASSERT(out_netstr != nullptr);
+
+ // Convert from host machine byte order (usually Little Endian) to network
+ // byte order (Big Endian).
+ uint64 net_val = htobe64(val);
+ out_netstr->append(reinterpret_cast<char *>(&net_val), sizeof(net_val));
+}
+
+inline void rdb_netstr_append_uint32(my_core::String *const out_netstr,
+ const uint32 val) {
+ DBUG_ASSERT(out_netstr != nullptr);
+
+ // Convert from host machine byte order (usually Little Endian) to network
+ // byte order (Big Endian).
+ uint32 net_val = htobe32(val);
+ out_netstr->append(reinterpret_cast<char *>(&net_val), sizeof(net_val));
+}
+
+inline void rdb_netstr_append_uint16(my_core::String *const out_netstr,
+ const uint16 val) {
+ DBUG_ASSERT(out_netstr != nullptr);
+
+ // Convert from host machine byte order (usually Little Endian) to network
+ // byte order (Big Endian).
+ uint16 net_val = htobe16(val);
+ out_netstr->append(reinterpret_cast<char *>(&net_val), sizeof(net_val));
+}
+
+/*
+ Basic network buffer ("netbuf") write helper functions.
+*/
+
+inline void rdb_netbuf_store_uint64(uchar *const dst_netbuf, const uint64 n) {
+ DBUG_ASSERT(dst_netbuf != nullptr);
+
+ // Convert from host byte order (usually Little Endian) to network byte order
+ // (Big Endian).
+ uint64 net_val = htobe64(n);
+ memcpy(dst_netbuf, &net_val, sizeof(net_val));
+}
+
+inline void rdb_netbuf_store_uint32(uchar *const dst_netbuf, const uint32 n) {
+ DBUG_ASSERT(dst_netbuf != nullptr);
+
+ // Convert from host byte order (usually Little Endian) to network byte order
+ // (Big Endian).
+ uint32 net_val = htobe32(n);
+ memcpy(dst_netbuf, &net_val, sizeof(net_val));
+}
+
+inline void rdb_netbuf_store_uint16(uchar *const dst_netbuf, const uint16 n) {
+ DBUG_ASSERT(dst_netbuf != nullptr);
+
+ // Convert from host byte order (usually Little Endian) to network byte order
+ // (Big Endian).
+ uint16 net_val = htobe16(n);
+ memcpy(dst_netbuf, &net_val, sizeof(net_val));
+}
+
+inline void rdb_netbuf_store_byte(uchar *const dst_netbuf, const uchar c) {
+ DBUG_ASSERT(dst_netbuf != nullptr);
+
+ *dst_netbuf = c;
+}
+
+inline void rdb_netbuf_store_index(uchar *const dst_netbuf,
+ const uint32 number) {
+ DBUG_ASSERT(dst_netbuf != nullptr);
+
+ rdb_netbuf_store_uint32(dst_netbuf, number);
+}
+
+/*
+ Basic conversion helper functions from network byte order (Big Endian) to host
+ machine byte order (usually Little Endian).
+*/
+
+inline uint64 rdb_netbuf_to_uint64(const uchar *const netbuf) {
+ DBUG_ASSERT(netbuf != nullptr);
+
+ uint64 net_val;
+ memcpy(&net_val, netbuf, sizeof(net_val));
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ return be64toh(net_val);
+}
+
+inline uint32 rdb_netbuf_to_uint32(const uchar *const netbuf) {
+ DBUG_ASSERT(netbuf != nullptr);
+
+ uint32 net_val;
+ memcpy(&net_val, netbuf, sizeof(net_val));
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ return be32toh(net_val);
+}
+
+inline uint16 rdb_netbuf_to_uint16(const uchar *const netbuf) {
+ DBUG_ASSERT(netbuf != nullptr);
+
+ uint16 net_val;
+ memcpy(&net_val, netbuf, sizeof(net_val));
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ return be16toh(net_val);
+}
+
+inline uchar rdb_netbuf_to_byte(const uchar *const netbuf) {
+ DBUG_ASSERT(netbuf != nullptr);
+
+ return (uchar)netbuf[0];
+}
+
+/*
+ Basic network buffer ("netbuf") read helper functions.
+ Network buffer stores data in Network Byte Order (Big Endian).
+ NB: The netbuf is passed as an input/output param, hence after reading,
+ the netbuf pointer gets advanced to the following byte.
+*/
+
+inline uint64 rdb_netbuf_read_uint64(const uchar **netbuf_ptr) {
+ DBUG_ASSERT(netbuf_ptr != nullptr);
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ const uint64 host_val = rdb_netbuf_to_uint64(*netbuf_ptr);
+
+ // Advance pointer.
+ *netbuf_ptr += sizeof(host_val);
+
+ return host_val;
+}
+
+inline uint32 rdb_netbuf_read_uint32(const uchar **netbuf_ptr) {
+ DBUG_ASSERT(netbuf_ptr != nullptr);
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ const uint32 host_val = rdb_netbuf_to_uint32(*netbuf_ptr);
+
+ // Advance pointer.
+ *netbuf_ptr += sizeof(host_val);
+
+ return host_val;
+}
+
+inline uint16 rdb_netbuf_read_uint16(const uchar **netbuf_ptr) {
+ DBUG_ASSERT(netbuf_ptr != nullptr);
+
+ // Convert from network byte order (Big Endian) to host machine byte order
+ // (usually Little Endian).
+ const uint16 host_val = rdb_netbuf_to_uint16(*netbuf_ptr);
+
+ // Advance pointer.
+ *netbuf_ptr += sizeof(host_val);
+
+ return host_val;
+}
+
+inline void rdb_netbuf_read_gl_index(const uchar **netbuf_ptr,
+ GL_INDEX_ID *const gl_index_id) {
+ DBUG_ASSERT(gl_index_id != nullptr);
+ DBUG_ASSERT(netbuf_ptr != nullptr);
+
+ gl_index_id->cf_id = rdb_netbuf_read_uint32(netbuf_ptr);
+ gl_index_id->index_id = rdb_netbuf_read_uint32(netbuf_ptr);
+}
+
+/*
+ A simple string reader:
+ - it keeps position within the string that we read from
+ - it prevents one from reading beyond the end of the string.
+*/
+
+class Rdb_string_reader {
+ const char *m_ptr;
+ uint m_len;
+
+ private:
+ Rdb_string_reader &operator=(const Rdb_string_reader &) = default;
+
+ public:
+ Rdb_string_reader(const Rdb_string_reader &) = default;
+ /* named constructor */
+ static Rdb_string_reader read_or_empty(const rocksdb::Slice *const slice) {
+ if (!slice) {
+ return Rdb_string_reader("");
+ } else {
+ return Rdb_string_reader(slice);
+ }
+ }
+
+ explicit Rdb_string_reader(const std::string &str) {
+ m_len = str.length();
+ if (m_len) {
+ m_ptr = &str.at(0);
+ } else {
+ /*
+ One can a create a Rdb_string_reader for reading from an empty string
+ (although attempts to read anything will fail).
+ We must not access str.at(0), since len==0, we can set ptr to any
+ value.
+ */
+ m_ptr = nullptr;
+ }
+ }
+
+ explicit Rdb_string_reader(const rocksdb::Slice *const slice) {
+ m_ptr = slice->data();
+ m_len = slice->size();
+ }
+
+ /*
+ Read the next @param size bytes. Returns pointer to the bytes read, or
+ nullptr if the remaining string doesn't have that many bytes.
+ */
+ const char *read(const uint size) {
+ const char *res;
+ if (m_len < size) {
+ res = nullptr;
+ } else {
+ res = m_ptr;
+ m_ptr += size;
+ m_len -= size;
+ }
+ return res;
+ }
+
+ bool read_uint8(uint *const res) {
+ const uchar *p;
+ if (!(p = reinterpret_cast<const uchar *>(read(1)))) {
+ return true; // error
+ } else {
+ *res = *p;
+ return false; // Ok
+ }
+ }
+
+ bool read_uint16(uint *const res) {
+ const uchar *p;
+ if (!(p = reinterpret_cast<const uchar *>(read(2)))) {
+ return true; // error
+ } else {
+ *res = rdb_netbuf_to_uint16(p);
+ return false; // Ok
+ }
+ }
+
+ bool read_uint64(uint64 *const res) {
+ const uchar *p;
+ if (!(p = reinterpret_cast<const uchar *>(read(sizeof(uint64))))) {
+ return true; // error
+ } else {
+ *res = rdb_netbuf_to_uint64(p);
+ return false; // Ok
+ }
+ }
+
+ uint remaining_bytes() const { return m_len; }
+
+ /*
+ Return pointer to data that will be read by next read() call (if there is
+ nothing left to read, returns pointer to beyond the end of previous read()
+ call)
+ */
+ const char *get_current_ptr() const { return m_ptr; }
+};
+
+/*
+ @brief
+ A buffer one can write the data to.
+
+ @detail
+ Suggested usage pattern:
+
+ writer->clear();
+ writer->write_XXX(...);
+ ...
+ // Ok, writer->ptr() points to the data written so far,
+ // and writer->get_current_pos() is the length of the data
+
+*/
+
+class Rdb_string_writer {
+ std::vector<uchar> m_data;
+
+ public:
+ Rdb_string_writer(const Rdb_string_writer &) = delete;
+ Rdb_string_writer &operator=(const Rdb_string_writer &) = delete;
+ Rdb_string_writer() = default;
+
+ void clear() { m_data.clear(); }
+ void write_uint8(const uint val) {
+ m_data.push_back(static_cast<uchar>(val));
+ }
+
+ void write_uint16(const uint val) {
+ const auto size = m_data.size();
+ m_data.resize(size + 2);
+ rdb_netbuf_store_uint16(m_data.data() + size, val);
+ }
+
+ void write_uint32(const uint val) {
+ const auto size = m_data.size();
+ m_data.resize(size + 4);
+ rdb_netbuf_store_uint32(m_data.data() + size, val);
+ }
+
+ void write(const uchar *const new_data, const size_t len) {
+ DBUG_ASSERT(new_data != nullptr);
+ m_data.insert(m_data.end(), new_data, new_data + len);
+ }
+
+ uchar *ptr() { return m_data.data(); }
+ size_t get_current_pos() const { return m_data.size(); }
+
+ void write_uint8_at(const size_t pos, const uint new_val) {
+ // This function will only overwrite what was written
+ DBUG_ASSERT(pos < get_current_pos());
+ m_data.data()[pos] = new_val;
+ }
+
+ void write_uint16_at(const size_t pos, const uint new_val) {
+ // This function will only overwrite what was written
+ DBUG_ASSERT(pos < get_current_pos() && (pos + 1) < get_current_pos());
+ rdb_netbuf_store_uint16(m_data.data() + pos, new_val);
+ }
+
+ void truncate(const size_t pos) {
+ DBUG_ASSERT(pos < m_data.size());
+ m_data.resize(pos);
+ }
+
+ void allocate(const size_t len, const uchar val = 0) {
+ DBUG_ASSERT(len > 0);
+ m_data.resize(m_data.size() + len, val);
+ }
+
+ /*
+ An awful hack to deallocate the buffer without relying on the deconstructor.
+ This is needed to suppress valgrind errors in rocksdb.partition
+ */
+ void free() { std::vector<uchar>().swap(m_data); }
+};
+
+/*
+ A helper class for writing bits into Rdb_string_writer.
+
+ The class assumes (but doesn't check) that nobody tries to write
+ anything to the Rdb_string_writer that it is writing to.
+*/
+class Rdb_bit_writer {
+ Rdb_string_writer *m_writer;
+ uchar m_offset;
+
+ public:
+ Rdb_bit_writer(const Rdb_bit_writer &) = delete;
+ Rdb_bit_writer &operator=(const Rdb_bit_writer &) = delete;
+
+ explicit Rdb_bit_writer(Rdb_string_writer *writer_arg)
+ : m_writer(writer_arg), m_offset(0) {}
+
+ void write(uint size, const uint value) {
+ DBUG_ASSERT((value & ((1 << size) - 1)) == value);
+
+ while (size > 0) {
+ if (m_offset == 0) {
+ m_writer->write_uint8(0);
+ }
+ // number of bits to put in this byte
+ const uint bits = std::min(size, (uint)(8 - m_offset));
+ uchar *const last_byte =
+ m_writer->ptr() + m_writer->get_current_pos() - 1;
+ *last_byte |= (uchar)((value >> (size - bits)) & ((1 << bits) - 1))
+ << m_offset;
+ size -= bits;
+ m_offset = (m_offset + bits) & 0x7;
+ }
+ }
+};
+
+class Rdb_bit_reader {
+ const uchar *m_cur;
+ uchar m_offset;
+ uint m_ret;
+ Rdb_string_reader *const m_reader;
+
+ public:
+ Rdb_bit_reader(const Rdb_bit_reader &) = delete;
+ Rdb_bit_reader &operator=(const Rdb_bit_reader &) = delete;
+
+ explicit Rdb_bit_reader(Rdb_string_reader *const reader)
+ : m_cur(nullptr), m_offset(0), m_reader(reader) {}
+
+ // Returns a pointer to an uint containing the bits read. On subsequent
+ // reads, the value being pointed to will be overwritten. Returns nullptr
+ // on failure.
+ uint *read(uint size) {
+ m_ret = 0;
+ DBUG_ASSERT(size <= 32);
+
+ while (size > 0) {
+ if (m_offset == 0) {
+ m_cur = (const uchar *)m_reader->read(1);
+ if (m_cur == nullptr) {
+ return nullptr;
+ }
+ }
+ // how many bits from the current byte?
+ const uint bits = std::min((uint)(8 - m_offset), size);
+ m_ret <<= bits;
+ m_ret |= (*m_cur >> m_offset) & ((1 << bits) - 1);
+ size -= bits;
+ m_offset = (m_offset + bits) & 0x7;
+ }
+
+ return &m_ret;
+ }
+};
+
+template <size_t buf_length>
+class Rdb_buf_writer {
+ public:
+ Rdb_buf_writer(const Rdb_buf_writer &) = delete;
+ Rdb_buf_writer &operator=(const Rdb_buf_writer &) = delete;
+ Rdb_buf_writer() { reset(); }
+
+ void write_uint32(const uint32 n) {
+ DBUG_ASSERT(m_ptr + sizeof(n) <= m_buf.data() + buf_length);
+ rdb_netbuf_store_uint32(m_ptr, n);
+ m_ptr += sizeof(n);
+ }
+
+ void write_uint64(const uint64 n) {
+ DBUG_ASSERT(m_ptr + sizeof(n) <= m_buf.data() + buf_length);
+ rdb_netbuf_store_uint64(m_ptr, n);
+ m_ptr += sizeof(n);
+ }
+
+ void write_uint16(const uint16 n) {
+ DBUG_ASSERT(m_ptr + sizeof(n) <= m_buf.data() + buf_length);
+ rdb_netbuf_store_uint16(m_ptr, n);
+ m_ptr += sizeof(n);
+ }
+
+ void write_byte(const uchar c) {
+ DBUG_ASSERT(m_ptr + sizeof(c) <= m_buf.data() + buf_length);
+ rdb_netbuf_store_byte(m_ptr, c);
+ m_ptr += sizeof(c);
+ }
+
+ void write_index(const uint32 n) { write_uint32(n); }
+
+ void write(const char *buf, const size_t size) {
+ DBUG_ASSERT(m_ptr + size <= m_buf.data() + buf_length);
+ memcpy(m_ptr, buf, size);
+ m_ptr += size;
+ }
+
+ void write(const uchar *buf, const size_t size) {
+ DBUG_ASSERT(m_ptr + size <= m_buf.data() + buf_length);
+ memcpy(m_ptr, buf, size);
+ m_ptr += size;
+ }
+
+ void reset() { m_ptr = m_buf.data(); }
+
+ const char *data() const {
+ return reinterpret_cast<const char *>(m_buf.data());
+ }
+
+ size_t capacity() { return buf_length; }
+
+ /** Returns actual size of the buffer that has data */
+ size_t size() { return m_ptr - m_buf.data(); }
+
+ rocksdb::Slice to_slice() { return rocksdb::Slice(data(), size()); }
+
+ private:
+ std::array<uchar, buf_length> m_buf;
+ uchar *m_ptr;
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_cf_manager.cc b/storage/rocksdb/rdb_cf_manager.cc
new file mode 100644
index 00000000..789481c5
--- /dev/null
+++ b/storage/rocksdb/rdb_cf_manager.cc
@@ -0,0 +1,273 @@
+/*
+ Copyright (c) 2014, SkySQL Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+/* For use of 'PRIu64': */
+#define __STDC_FORMAT_MACROS
+
+#include <my_global.h>
+
+#include <inttypes.h>
+
+/* This C++ files header file */
+#include "./rdb_cf_manager.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./rdb_datadic.h"
+#include "./rdb_psi.h"
+
+#include <string>
+
+namespace myrocks {
+
+/* Check if ColumnFamily name says it's a reverse-ordered CF */
+bool Rdb_cf_manager::is_cf_name_reverse(const char *const name) {
+ /* nullptr means the default CF is used.. (TODO: can the default CF be
+ * reverse?) */
+ return (name && !strncmp(name, "rev:", 4));
+}
+
+void Rdb_cf_manager::init(
+ std::unique_ptr<Rdb_cf_options> &&cf_options,
+ std::vector<rocksdb::ColumnFamilyHandle *> *const handles) {
+ mysql_mutex_init(rdb_cfm_mutex_key, &m_mutex, MY_MUTEX_INIT_FAST);
+
+ DBUG_ASSERT(cf_options != nullptr);
+ DBUG_ASSERT(handles != nullptr);
+ DBUG_ASSERT(handles->size() > 0);
+
+ m_cf_options = std::move(cf_options);
+
+ for (auto cfh : *handles) {
+ DBUG_ASSERT(cfh != nullptr);
+ m_cf_name_map[cfh->GetName()] = cfh;
+ m_cf_id_map[cfh->GetID()] = cfh;
+ }
+}
+
+void Rdb_cf_manager::cleanup() {
+ for (auto it : m_cf_name_map) {
+ delete it.second;
+ }
+ mysql_mutex_destroy(&m_mutex);
+ m_cf_options = nullptr;
+}
+
+/*
+ @brief
+ Find column family by name. If it doesn't exist, create it
+
+ @detail
+ See Rdb_cf_manager::get_cf
+*/
+rocksdb::ColumnFamilyHandle *Rdb_cf_manager::get_or_create_cf(
+ rocksdb::DB *const rdb, const std::string &cf_name_arg) {
+ DBUG_ASSERT(rdb != nullptr);
+
+ rocksdb::ColumnFamilyHandle *cf_handle = nullptr;
+
+ if (cf_name_arg == PER_INDEX_CF_NAME) {
+ // per-index column families is no longer supported.
+ my_error(ER_PER_INDEX_CF_DEPRECATED, MYF(0));
+ return nullptr;
+ }
+
+ const std::string &cf_name =
+ cf_name_arg.empty() ? DEFAULT_CF_NAME : cf_name_arg;
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+
+ const auto it = m_cf_name_map.find(cf_name);
+
+ if (it != m_cf_name_map.end()) {
+ cf_handle = it->second;
+ } else {
+ /* Create a Column Family. */
+ rocksdb::ColumnFamilyOptions opts;
+ m_cf_options->get_cf_options(cf_name, &opts);
+
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: creating a column family %s",
+ cf_name.c_str());
+ // NO_LINT_DEBUG
+ sql_print_information(" write_buffer_size=%ld", opts.write_buffer_size);
+
+ // NO_LINT_DEBUG
+ sql_print_information(" target_file_size_base=%" PRIu64,
+ opts.target_file_size_base);
+
+ const rocksdb::Status s =
+ rdb->CreateColumnFamily(opts, cf_name, &cf_handle);
+
+ if (s.ok()) {
+ m_cf_name_map[cf_handle->GetName()] = cf_handle;
+ m_cf_id_map[cf_handle->GetID()] = cf_handle;
+ } else {
+ cf_handle = nullptr;
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return cf_handle;
+}
+
+/*
+ Find column family by its cf_name.
+*/
+
+rocksdb::ColumnFamilyHandle *Rdb_cf_manager::get_cf(
+ const std::string &cf_name_arg, const bool lock_held_by_caller) const {
+ rocksdb::ColumnFamilyHandle *cf_handle;
+
+ if (!lock_held_by_caller) {
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ }
+ std::string cf_name = cf_name_arg.empty() ? DEFAULT_CF_NAME : cf_name_arg;
+
+ const auto it = m_cf_name_map.find(cf_name);
+ cf_handle = (it != m_cf_name_map.end()) ? it->second : nullptr;
+
+ if (!cf_handle) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Column family '%s' not found.", cf_name.c_str());
+ }
+
+ if (!lock_held_by_caller) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ }
+
+ return cf_handle;
+}
+
+rocksdb::ColumnFamilyHandle *Rdb_cf_manager::get_cf(const uint32_t id) const {
+ rocksdb::ColumnFamilyHandle *cf_handle = nullptr;
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ const auto it = m_cf_id_map.find(id);
+ if (it != m_cf_id_map.end()) cf_handle = it->second;
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return cf_handle;
+}
+
+std::vector<std::string> Rdb_cf_manager::get_cf_names(void) const {
+ std::vector<std::string> names;
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ for (auto it : m_cf_name_map) {
+ names.push_back(it.first);
+ }
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return names;
+}
+
+std::vector<rocksdb::ColumnFamilyHandle *> Rdb_cf_manager::get_all_cf(
+ void) const {
+ std::vector<rocksdb::ColumnFamilyHandle *> list;
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+
+ for (auto it : m_cf_id_map) {
+ DBUG_ASSERT(it.second != nullptr);
+ list.push_back(it.second);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return list;
+}
+
+struct Rdb_cf_scanner : public Rdb_tables_scanner {
+ uint32_t m_cf_id;
+ int m_is_cf_used;
+
+ explicit Rdb_cf_scanner(uint32_t cf_id)
+ : m_cf_id(cf_id), m_is_cf_used(false) {}
+
+ int add_table(Rdb_tbl_def *tdef) override {
+ DBUG_ASSERT(tdef != nullptr);
+
+ for (uint i = 0; i < tdef->m_key_count; i++) {
+ const Rdb_key_def &kd = *tdef->m_key_descr_arr[i];
+
+ if (kd.get_cf()->GetID() == m_cf_id) {
+ m_is_cf_used = true;
+ return HA_EXIT_SUCCESS;
+ }
+ }
+ return HA_EXIT_SUCCESS;
+ }
+};
+
+int Rdb_cf_manager::drop_cf(const std::string &cf_name) {
+ auto ddl_manager = rdb_get_ddl_manager();
+ uint32_t cf_id = 0;
+
+ if (cf_name == DEFAULT_SYSTEM_CF_NAME) {
+ return HA_EXIT_FAILURE;
+ }
+
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ auto cf_handle = get_cf(cf_name, true /* lock_held_by_caller */);
+ if (cf_handle == nullptr) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return HA_EXIT_SUCCESS;
+ }
+
+ cf_id = cf_handle->GetID();
+ Rdb_cf_scanner scanner(cf_id);
+
+ auto ret = ddl_manager->scan_for_tables(&scanner);
+ if (ret) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return ret;
+ }
+
+ if (scanner.m_is_cf_used) {
+ // column family is used by existing key
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return HA_EXIT_FAILURE;
+ }
+
+ auto rdb = rdb_get_rocksdb_db();
+ auto status = rdb->DropColumnFamily(cf_handle);
+ if (!status.ok()) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return ha_rocksdb::rdb_error_to_mysql(status);
+ }
+
+ delete cf_handle;
+
+ auto id_iter = m_cf_id_map.find(cf_id);
+ DBUG_ASSERT(id_iter != m_cf_id_map.end());
+ m_cf_id_map.erase(id_iter);
+
+ auto name_iter = m_cf_name_map.find(cf_name);
+ DBUG_ASSERT(name_iter != m_cf_name_map.end());
+ m_cf_name_map.erase(name_iter);
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return HA_EXIT_SUCCESS;
+}
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_cf_manager.h b/storage/rocksdb/rdb_cf_manager.h
new file mode 100644
index 00000000..cf7b3d6c
--- /dev/null
+++ b/storage/rocksdb/rdb_cf_manager.h
@@ -0,0 +1,108 @@
+/*
+ Copyright (c) 2014, SkySQL Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* C++ system header files */
+#include <map>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./sql_class.h"
+
+/* RocksDB header files */
+#include "rocksdb/db.h"
+
+/* MyRocks header files */
+#include "./rdb_cf_options.h"
+
+namespace myrocks {
+
+/*
+ We need a Column Family (CF) manager. Its functions:
+ - create column families (synchronized, don't create the same twice)
+ - keep count in each column family.
+ = the count is kept on-disk.
+ = there are no empty CFs. initially count=1.
+ = then, when doing DDL, we increase or decrease it.
+ (atomicity is maintained by being in the same WriteBatch with DDLs)
+ = if DROP discovers that now count=0, it removes the CF.
+
+ Current state is:
+ - CFs are created in a synchronized way. We can't remove them, yet.
+*/
+
+class Rdb_cf_manager {
+ std::map<std::string, rocksdb::ColumnFamilyHandle *> m_cf_name_map;
+ std::map<uint32_t, rocksdb::ColumnFamilyHandle *> m_cf_id_map;
+
+ mutable mysql_mutex_t m_mutex;
+
+ std::unique_ptr<Rdb_cf_options> m_cf_options = nullptr;
+
+ public:
+ Rdb_cf_manager(const Rdb_cf_manager &) = delete;
+ Rdb_cf_manager &operator=(const Rdb_cf_manager &) = delete;
+ Rdb_cf_manager() = default;
+
+ static bool is_cf_name_reverse(const char *const name);
+
+ /*
+ This is called right after the DB::Open() call. The parameters describe
+ column
+ families that are present in the database. The first CF is the default CF.
+ */
+ void init(std::unique_ptr<Rdb_cf_options> &&cf_options,
+ std::vector<rocksdb::ColumnFamilyHandle *> *const handles);
+ void cleanup();
+
+ /*
+ Used by CREATE TABLE.
+ - cf_name=nullptr means use default column family
+ */
+ rocksdb::ColumnFamilyHandle *get_or_create_cf(rocksdb::DB *const rdb,
+ const std::string &cf_name);
+
+ /* Used by table open */
+ rocksdb::ColumnFamilyHandle *get_cf(
+ const std::string &cf_name, const bool lock_held_by_caller = false) const;
+
+ /* Look up cf by id; used by datadic */
+ rocksdb::ColumnFamilyHandle *get_cf(const uint32_t id) const;
+
+ /* Used to iterate over column families for show status */
+ std::vector<std::string> get_cf_names(void) const;
+
+ /* Used to iterate over column families */
+ std::vector<rocksdb::ColumnFamilyHandle *> get_all_cf(void) const;
+
+ /* Used to delete cf by name */
+ int drop_cf(const std::string &cf_name);
+
+ void get_cf_options(const std::string &cf_name,
+ rocksdb::ColumnFamilyOptions *const opts)
+ MY_ATTRIBUTE((__nonnull__)) {
+ m_cf_options->get_cf_options(cf_name, opts);
+ }
+
+ void update_options_map(const std::string &cf_name,
+ const std::string &updated_options) {
+ m_cf_options->update(cf_name, updated_options);
+ }
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_cf_options.cc b/storage/rocksdb/rdb_cf_options.cc
new file mode 100644
index 00000000..c0d16893
--- /dev/null
+++ b/storage/rocksdb/rdb_cf_options.cc
@@ -0,0 +1,341 @@
+/*
+ Copyright (c) 2014, SkySQL Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+
+/* This C++ files header file */
+#include "./rdb_cf_options.h"
+
+/* C++ system header files */
+#include <string>
+
+/* MySQL header files */
+#include "./log.h"
+
+/* RocksDB header files */
+#include "rocksdb/utilities/convenience.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./rdb_cf_manager.h"
+#include "./rdb_compact_filter.h"
+
+namespace myrocks {
+
+Rdb_pk_comparator Rdb_cf_options::s_pk_comparator;
+Rdb_rev_comparator Rdb_cf_options::s_rev_pk_comparator;
+
+bool Rdb_cf_options::init(
+ const rocksdb::BlockBasedTableOptions &table_options,
+ std::shared_ptr<rocksdb::TablePropertiesCollectorFactory> prop_coll_factory,
+ const char *const default_cf_options,
+ const char *const override_cf_options) {
+ DBUG_ASSERT(default_cf_options != nullptr);
+ DBUG_ASSERT(override_cf_options != nullptr);
+
+ m_default_cf_opts.comparator = &s_pk_comparator;
+ m_default_cf_opts.compaction_filter_factory.reset(
+ new Rdb_compact_filter_factory);
+
+ m_default_cf_opts.table_factory.reset(
+ rocksdb::NewBlockBasedTableFactory(table_options));
+
+ if (prop_coll_factory) {
+ m_default_cf_opts.table_properties_collector_factories.push_back(
+ prop_coll_factory);
+ }
+
+ if (!set_default(std::string(default_cf_options)) ||
+ !set_override(std::string(override_cf_options))) {
+ return false;
+ }
+
+ return true;
+}
+
+void Rdb_cf_options::get(const std::string &cf_name,
+ rocksdb::ColumnFamilyOptions *const opts) {
+ DBUG_ASSERT(opts != nullptr);
+
+ // Get defaults.
+ rocksdb::GetColumnFamilyOptionsFromString(*opts, m_default_config, opts);
+
+ // Get a custom confguration if we have one.
+ Name_to_config_t::iterator it = m_name_map.find(cf_name);
+
+ if (it != m_name_map.end()) {
+ rocksdb::GetColumnFamilyOptionsFromString(*opts, it->second, opts);
+ }
+}
+
+void Rdb_cf_options::update(const std::string &cf_name,
+ const std::string &cf_options) {
+ DBUG_ASSERT(!cf_name.empty());
+ DBUG_ASSERT(!cf_options.empty());
+
+ // Always update. If we didn't have an entry before then add it.
+ m_name_map[cf_name] = cf_options;
+
+ DBUG_ASSERT(!m_name_map.empty());
+}
+
+bool Rdb_cf_options::set_default(const std::string &default_config) {
+ rocksdb::ColumnFamilyOptions options;
+
+ if (!default_config.empty() && !rocksdb::GetColumnFamilyOptionsFromString(
+ options, default_config, &options)
+ .ok()) {
+ // NO_LINT_DEBUG
+ fprintf(stderr, "Invalid default column family config: %s\n",
+ default_config.c_str());
+ return false;
+ }
+
+ m_default_config = default_config;
+ return true;
+}
+
+// Skip over any spaces in the input string.
+void Rdb_cf_options::skip_spaces(const std::string &input, size_t *const pos) {
+ DBUG_ASSERT(pos != nullptr);
+
+ while (*pos < input.size() && isspace(input[*pos])) ++(*pos);
+}
+
+// Find a valid column family name. Note that all characters except a
+// semicolon are valid (should this change?) and all spaces are trimmed from
+// the beginning and end but are not removed between other characters.
+bool Rdb_cf_options::find_column_family(const std::string &input,
+ size_t *const pos,
+ std::string *const key) {
+ DBUG_ASSERT(pos != nullptr);
+ DBUG_ASSERT(key != nullptr);
+
+ const size_t beg_pos = *pos;
+ size_t end_pos = *pos - 1;
+
+ // Loop through the characters in the string until we see a '='.
+ for (; *pos < input.size() && input[*pos] != '='; ++(*pos)) {
+ // If this is not a space, move the end position to the current position.
+ if (input[*pos] != ' ') end_pos = *pos;
+ }
+
+ if (end_pos == beg_pos - 1) {
+ // NO_LINT_DEBUG
+ sql_print_warning("No column family found (options: %s)", input.c_str());
+ return false;
+ }
+
+ *key = input.substr(beg_pos, end_pos - beg_pos + 1);
+ return true;
+}
+
+// Find a valid options portion. Everything is deemed valid within the options
+// portion until we hit as many close curly braces as we have seen open curly
+// braces.
+bool Rdb_cf_options::find_options(const std::string &input, size_t *const pos,
+ std::string *const options) {
+ DBUG_ASSERT(pos != nullptr);
+ DBUG_ASSERT(options != nullptr);
+
+ // Make sure we have an open curly brace at the current position.
+ if (*pos < input.size() && input[*pos] != '{') {
+ // NO_LINT_DEBUG
+ sql_print_warning("Invalid cf options, '{' expected (options: %s)",
+ input.c_str());
+ return false;
+ }
+
+ // Skip the open curly brace and any spaces.
+ ++(*pos);
+ skip_spaces(input, pos);
+
+ // Set up our brace_count, the begin position and current end position.
+ size_t brace_count = 1;
+ const size_t beg_pos = *pos;
+
+ // Loop through the characters in the string until we find the appropriate
+ // number of closing curly braces.
+ while (*pos < input.size()) {
+ switch (input[*pos]) {
+ case '}':
+ // If this is a closing curly brace and we bring the count down to zero
+ // we can exit the loop with a valid options string.
+ if (--brace_count == 0) {
+ *options = input.substr(beg_pos, *pos - beg_pos);
+ ++(*pos); // Move past the last closing curly brace
+ return true;
+ }
+
+ break;
+
+ case '{':
+ // If this is an open curly brace increment the count.
+ ++brace_count;
+ break;
+
+ default:
+ break;
+ }
+
+ // Move to the next character.
+ ++(*pos);
+ }
+
+ // We never found the correct number of closing curly braces.
+ // Generate an error.
+ // NO_LINT_DEBUG
+ sql_print_warning("Mismatched cf options, '}' expected (options: %s)",
+ input.c_str());
+ return false;
+}
+
+bool Rdb_cf_options::find_cf_options_pair(const std::string &input,
+ size_t *const pos,
+ std::string *const cf,
+ std::string *const opt_str) {
+ DBUG_ASSERT(pos != nullptr);
+ DBUG_ASSERT(cf != nullptr);
+ DBUG_ASSERT(opt_str != nullptr);
+
+ // Skip any spaces.
+ skip_spaces(input, pos);
+
+ // We should now have a column family name.
+ if (!find_column_family(input, pos, cf)) return false;
+
+ // If we are at the end of the input then we generate an error.
+ if (*pos == input.size()) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Invalid cf options, '=' expected (options: %s)",
+ input.c_str());
+ return false;
+ }
+
+ // Skip equal sign and any spaces after it
+ ++(*pos);
+ skip_spaces(input, pos);
+
+ // Find the options for this column family. This should be in the format
+ // {<options>} where <options> may contain embedded pairs of curly braces.
+ if (!find_options(input, pos, opt_str)) return false;
+
+ // Skip any trailing spaces after the option string.
+ skip_spaces(input, pos);
+
+ // We should either be at the end of the input string or at a semicolon.
+ if (*pos < input.size()) {
+ if (input[*pos] != ';') {
+ // NO_LINT_DEBUG
+ sql_print_warning("Invalid cf options, ';' expected (options: %s)",
+ input.c_str());
+ return false;
+ }
+
+ ++(*pos);
+ }
+
+ return true;
+}
+
+bool Rdb_cf_options::parse_cf_options(const std::string &cf_options,
+ Name_to_config_t *option_map) {
+ std::string cf;
+ std::string opt_str;
+ rocksdb::ColumnFamilyOptions options;
+
+ DBUG_ASSERT(option_map != nullptr);
+ DBUG_ASSERT(option_map->empty());
+
+ // Loop through the characters of the string until we reach the end.
+ size_t pos = 0;
+
+ while (pos < cf_options.size()) {
+ // Attempt to find <cf>={<opt_str>}.
+ if (!find_cf_options_pair(cf_options, &pos, &cf, &opt_str)) {
+ return false;
+ }
+
+ // Generate an error if we have already seen this column family.
+ if (option_map->find(cf) != option_map->end()) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "Duplicate entry for %s in override options (options: %s)",
+ cf.c_str(), cf_options.c_str());
+ return false;
+ }
+
+ // Generate an error if the <opt_str> is not valid according to RocksDB.
+ if (!rocksdb::GetColumnFamilyOptionsFromString(options, opt_str, &options)
+ .ok()) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "Invalid cf config for %s in override options (options: %s)",
+ cf.c_str(), cf_options.c_str());
+ return false;
+ }
+
+ // If everything is good, add this cf/opt_str pair to the map.
+ (*option_map)[cf] = opt_str;
+ }
+
+ return true;
+}
+
+bool Rdb_cf_options::set_override(const std::string &override_config) {
+ Name_to_config_t configs;
+
+ if (!parse_cf_options(override_config, &configs)) {
+ return false;
+ }
+
+ // Everything checked out - make the map live
+ m_name_map = configs;
+
+ return true;
+}
+
+const rocksdb::Comparator *Rdb_cf_options::get_cf_comparator(
+ const std::string &cf_name) {
+ if (Rdb_cf_manager::is_cf_name_reverse(cf_name.c_str())) {
+ return &s_rev_pk_comparator;
+ } else {
+ return &s_pk_comparator;
+ }
+}
+
+std::shared_ptr<rocksdb::MergeOperator> Rdb_cf_options::get_cf_merge_operator(
+ const std::string &cf_name) {
+ return (cf_name == DEFAULT_SYSTEM_CF_NAME)
+ ? std::make_shared<Rdb_system_merge_op>()
+ : nullptr;
+}
+
+void Rdb_cf_options::get_cf_options(const std::string &cf_name,
+ rocksdb::ColumnFamilyOptions *const opts) {
+ *opts = m_default_cf_opts;
+ get(cf_name, opts);
+
+ // Set the comparator according to 'rev:'
+ opts->comparator = get_cf_comparator(cf_name);
+ opts->merge_operator = get_cf_merge_operator(cf_name);
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_cf_options.h b/storage/rocksdb/rdb_cf_options.h
new file mode 100644
index 00000000..360356f7
--- /dev/null
+++ b/storage/rocksdb/rdb_cf_options.h
@@ -0,0 +1,104 @@
+/*
+ Copyright (c) 2014, SkySQL Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* C++ system header files */
+#include <string>
+#include <unordered_map>
+
+/* RocksDB header files */
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/options_util.h"
+
+/* MyRocks header files */
+#include "./rdb_comparator.h"
+
+namespace myrocks {
+
+/*
+ Per-column family options configs.
+
+ Per-column family option can be set
+ - Globally (the same value applies to all column families)
+ - Per column family: there is a {cf_name -> value} map,
+ and also there is a default value which applies to column
+ families not found in the map.
+*/
+class Rdb_cf_options {
+ public:
+ using Name_to_config_t = std::unordered_map<std::string, std::string>;
+
+ Rdb_cf_options(const Rdb_cf_options &) = delete;
+ Rdb_cf_options &operator=(const Rdb_cf_options &) = delete;
+ Rdb_cf_options() = default;
+
+ void get(const std::string &cf_name,
+ rocksdb::ColumnFamilyOptions *const opts);
+
+ void update(const std::string &cf_name, const std::string &cf_options);
+
+ bool init(const rocksdb::BlockBasedTableOptions &table_options,
+ std::shared_ptr<rocksdb::TablePropertiesCollectorFactory>
+ prop_coll_factory,
+ const char *const default_cf_options,
+ const char *const override_cf_options);
+
+ const rocksdb::ColumnFamilyOptions &get_defaults() const {
+ return m_default_cf_opts;
+ }
+
+ static const rocksdb::Comparator *get_cf_comparator(
+ const std::string &cf_name);
+
+ std::shared_ptr<rocksdb::MergeOperator> get_cf_merge_operator(
+ const std::string &cf_name);
+
+ void get_cf_options(const std::string &cf_name,
+ rocksdb::ColumnFamilyOptions *const opts)
+ MY_ATTRIBUTE((__nonnull__));
+
+ static bool parse_cf_options(const std::string &cf_options,
+ Name_to_config_t *option_map);
+
+ private:
+ bool set_default(const std::string &default_config);
+ bool set_override(const std::string &overide_config);
+
+ /* Helper string manipulation functions */
+ static void skip_spaces(const std::string &input, size_t *const pos);
+ static bool find_column_family(const std::string &input, size_t *const pos,
+ std::string *const key);
+ static bool find_options(const std::string &input, size_t *const pos,
+ std::string *const options);
+ static bool find_cf_options_pair(const std::string &input, size_t *const pos,
+ std::string *const cf,
+ std::string *const opt_str);
+
+ private:
+ static Rdb_pk_comparator s_pk_comparator;
+ static Rdb_rev_comparator s_rev_pk_comparator;
+
+ /* CF name -> value map */
+ Name_to_config_t m_name_map;
+
+ /* The default value (if there is only one value, it is stored here) */
+ std::string m_default_config;
+
+ rocksdb::ColumnFamilyOptions m_default_cf_opts;
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_compact_filter.h b/storage/rocksdb/rdb_compact_filter.h
new file mode 100644
index 00000000..1cd27273
--- /dev/null
+++ b/storage/rocksdb/rdb_compact_filter.h
@@ -0,0 +1,220 @@
+/*
+ Portions Copyright (c) 2016-Present, Facebook, Inc.
+ Portions Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+/* C++ system header files */
+#include <time.h>
+#include <string>
+#include <ctime>
+
+/* RocksDB includes */
+#include "rocksdb/compaction_filter.h"
+
+/* MyRocks includes */
+#include "./ha_rocksdb_proto.h"
+#include "./rdb_datadic.h"
+
+namespace myrocks {
+
+class Rdb_compact_filter : public rocksdb::CompactionFilter {
+ public:
+ Rdb_compact_filter(const Rdb_compact_filter &) = delete;
+ Rdb_compact_filter &operator=(const Rdb_compact_filter &) = delete;
+
+ explicit Rdb_compact_filter(uint32_t _cf_id) : m_cf_id(_cf_id) {}
+ ~Rdb_compact_filter() {
+ // Increment stats by num expired at the end of compaction
+ rdb_update_global_stats(ROWS_EXPIRED, m_num_expired);
+ }
+
+ // keys are passed in sorted order within the same sst.
+ // V1 Filter is thread safe on our usage (creating from Factory).
+ // Make sure to protect instance variables when switching to thread
+ // unsafe in the future.
+ virtual bool Filter(int level, const rocksdb::Slice &key,
+ const rocksdb::Slice &existing_value,
+ std::string *new_value,
+ bool *value_changed) const override {
+ DBUG_ASSERT(key.size() >= sizeof(uint32));
+
+ GL_INDEX_ID gl_index_id;
+ gl_index_id.cf_id = m_cf_id;
+ gl_index_id.index_id = rdb_netbuf_to_uint32((const uchar *)key.data());
+ DBUG_ASSERT(gl_index_id.index_id >= 1);
+
+ if (gl_index_id != m_prev_index) {
+ m_should_delete =
+ rdb_get_dict_manager()->is_drop_index_ongoing(gl_index_id);
+
+ if (!m_should_delete) {
+ get_ttl_duration_and_offset(gl_index_id, &m_ttl_duration,
+ &m_ttl_offset);
+
+ if (m_ttl_duration != 0 && m_snapshot_timestamp == 0) {
+ /*
+ For efficiency reasons, we lazily call GetIntProperty to get the
+ oldest snapshot time (occurs once per compaction).
+ */
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+ if (!rdb->GetIntProperty(rocksdb::DB::Properties::kOldestSnapshotTime,
+ &m_snapshot_timestamp) ||
+ m_snapshot_timestamp == 0) {
+ m_snapshot_timestamp = static_cast<uint64_t>(std::time(nullptr));
+ }
+
+#ifndef DBUG_OFF
+ int snapshot_ts = rdb_dbug_set_ttl_snapshot_ts();
+ if (snapshot_ts) {
+ m_snapshot_timestamp =
+ static_cast<uint64_t>(std::time(nullptr)) + snapshot_ts;
+ }
+#endif
+ }
+ }
+
+ m_prev_index = gl_index_id;
+ }
+
+ if (m_should_delete) {
+ m_num_deleted++;
+ return true;
+ } else if (m_ttl_duration > 0 &&
+ should_filter_ttl_rec(key, existing_value)) {
+ m_num_expired++;
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual bool IgnoreSnapshots() const override { return true; }
+
+ virtual const char *Name() const override { return "Rdb_compact_filter"; }
+
+ void get_ttl_duration_and_offset(const GL_INDEX_ID &gl_index_id,
+ uint64 *ttl_duration,
+ uint32 *ttl_offset) const {
+ DBUG_ASSERT(ttl_duration != nullptr);
+ /*
+ If TTL is disabled set ttl_duration to 0. This prevents the compaction
+ filter from dropping expired records.
+ */
+ if (!rdb_is_ttl_enabled()) {
+ *ttl_duration = 0;
+ return;
+ }
+
+ /*
+ If key is part of system column family, it's definitely not a TTL key.
+ */
+ rocksdb::ColumnFamilyHandle *s_cf = rdb_get_dict_manager()->get_system_cf();
+ if (s_cf == nullptr || gl_index_id.cf_id == s_cf->GetID()) {
+ *ttl_duration = 0;
+ return;
+ }
+
+ struct Rdb_index_info index_info;
+ if (!rdb_get_dict_manager()->get_index_info(gl_index_id, &index_info)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Could not get index information "
+ "for Index Number (%u,%u)",
+ gl_index_id.cf_id, gl_index_id.index_id);
+ }
+
+#ifndef DBUG_OFF
+ if (rdb_dbug_set_ttl_ignore_pk() &&
+ index_info.m_index_type == Rdb_key_def::INDEX_TYPE_PRIMARY) {
+ *ttl_duration = 0;
+ return;
+ }
+#endif
+
+ *ttl_duration = index_info.m_ttl_duration;
+ if (Rdb_key_def::has_index_flag(index_info.m_index_flags,
+ Rdb_key_def::TTL_FLAG)) {
+ *ttl_offset = Rdb_key_def::calculate_index_flag_offset(
+ index_info.m_index_flags, Rdb_key_def::TTL_FLAG);
+ }
+ }
+
+ bool should_filter_ttl_rec(const rocksdb::Slice &key,
+ const rocksdb::Slice &existing_value) const {
+ uint64 ttl_timestamp;
+ Rdb_string_reader reader(&existing_value);
+ if (!reader.read(m_ttl_offset) || reader.read_uint64(&ttl_timestamp)) {
+ std::string buf;
+ buf = rdb_hexdump(existing_value.data(), existing_value.size(),
+ RDB_MAX_HEXDUMP_LEN);
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "Decoding ttl from PK value failed in compaction filter, "
+ "for index (%u,%u), val: %s",
+ m_prev_index.cf_id, m_prev_index.index_id, buf.c_str());
+ abort();
+ }
+
+ /*
+ Filter out the record only if it is older than the oldest snapshot
+ timestamp. This prevents any rows from expiring in the middle of
+ long-running transactions.
+ */
+ return ttl_timestamp + m_ttl_duration <= m_snapshot_timestamp;
+ }
+
+ private:
+ // Column family for this compaction filter
+ const uint32_t m_cf_id;
+ // Index id of the previous record
+ mutable GL_INDEX_ID m_prev_index = {0, 0};
+ // Number of rows deleted for the same index id
+ mutable uint64 m_num_deleted = 0;
+ // Number of rows expired for the TTL index
+ mutable uint64 m_num_expired = 0;
+ // Current index id should be deleted or not (should be deleted if true)
+ mutable bool m_should_delete = false;
+ // TTL duration for the current index if TTL is enabled
+ mutable uint64 m_ttl_duration = 0;
+ // TTL offset for all records in the current index
+ mutable uint32 m_ttl_offset = 0;
+ // Oldest snapshot timestamp at the time a TTL index is discovered
+ mutable uint64_t m_snapshot_timestamp = 0;
+};
+
+class Rdb_compact_filter_factory : public rocksdb::CompactionFilterFactory {
+ public:
+ Rdb_compact_filter_factory(const Rdb_compact_filter_factory &) = delete;
+ Rdb_compact_filter_factory &operator=(const Rdb_compact_filter_factory &) =
+ delete;
+ Rdb_compact_filter_factory() {}
+
+ ~Rdb_compact_filter_factory() {}
+
+ const char *Name() const override { return "Rdb_compact_filter_factory"; }
+
+ std::unique_ptr<rocksdb::CompactionFilter> CreateCompactionFilter(
+ const rocksdb::CompactionFilter::Context &context) override {
+ return std::unique_ptr<rocksdb::CompactionFilter>(
+ new Rdb_compact_filter(context.column_family_id));
+ }
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_comparator.h b/storage/rocksdb/rdb_comparator.h
new file mode 100644
index 00000000..9cb25925
--- /dev/null
+++ b/storage/rocksdb/rdb_comparator.h
@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2012,2015 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ system header files */
+#include <string>
+
+/* MySQL includes */
+#include "./m_ctype.h"
+
+/* RocksDB header files */
+#include "rocksdb/comparator.h"
+
+/* MyRocks header files */
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+/*
+ The keys are in form: {index_number} {mem-comparable-key}
+
+ (todo: knowledge about this format is shared between this class and
+ Rdb_key_def)
+*/
+class Rdb_pk_comparator : public rocksdb::Comparator {
+ public:
+ Rdb_pk_comparator(const Rdb_pk_comparator &) = delete;
+ Rdb_pk_comparator &operator=(const Rdb_pk_comparator &) = delete;
+ Rdb_pk_comparator() = default;
+
+ // extracting from rocksdb::BytewiseComparator()->Compare() for optimization
+ int Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const override {
+ return a.compare(b);
+ }
+
+ const char *Name() const override { return "RocksDB_SE_v3.10"; }
+
+ // TODO: advanced funcs:
+ // - FindShortestSeparator
+ // - FindShortSuccessor
+
+ // for now, do-nothing implementations:
+ void FindShortestSeparator(std::string *start,
+ const rocksdb::Slice &limit) const override {
+ rocksdb::BytewiseComparator()->FindShortestSeparator(start, limit);
+ }
+ void FindShortSuccessor(std::string *key) const override {
+ rocksdb::BytewiseComparator()->FindShortSuccessor(key);
+ }
+};
+
+class Rdb_rev_comparator : public rocksdb::Comparator {
+ public:
+ Rdb_rev_comparator(const Rdb_rev_comparator &) = delete;
+ Rdb_rev_comparator &operator=(const Rdb_rev_comparator &) = delete;
+ Rdb_rev_comparator() = default;
+
+ // extracting from rocksdb::BytewiseComparator()->Compare() for optimization
+ int Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const override {
+ return -a.compare(b);
+ }
+ const char *Name() const override { return "rev:RocksDB_SE_v3.10"; }
+ void FindShortestSeparator(std::string *start,
+ const rocksdb::Slice &limit) const override {
+ rocksdb::ReverseBytewiseComparator()->FindShortestSeparator(start, limit);
+ }
+ void FindShortSuccessor(std::string *key) const override {
+ rocksdb::ReverseBytewiseComparator()->FindShortSuccessor(key);
+ }
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_converter.cc b/storage/rocksdb/rdb_converter.cc
new file mode 100644
index 00000000..65f0b81c
--- /dev/null
+++ b/storage/rocksdb/rdb_converter.cc
@@ -0,0 +1,838 @@
+/*
+ Copyright (c) 2015, Facebook, Inc.
+
+ This program is f
+ i the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <my_global.h>
+
+/* This C++ file's header file */
+#include "./rdb_converter.h"
+
+/* Standard C++ header files */
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./field.h"
+#include "./key.h"
+#include "./m_ctype.h"
+#include "./my_bit.h"
+#include "./my_bitmap.h"
+#include "./sql_table.h"
+
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./my_stacktrace.h"
+#include "./rdb_cf_manager.h"
+#include "./rdb_psi.h"
+#include "./rdb_utils.h"
+
+
+namespace myrocks {
+
+void dbug_modify_key_varchar8(String *on_disk_rec) {
+ std::string res;
+ // The key starts with index number
+ res.append(on_disk_rec->ptr(), Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ // Then, a mem-comparable form of a varchar(8) value.
+ res.append("ABCDE\0\0\0\xFC", 9);
+ on_disk_rec->length(0);
+ on_disk_rec->append(res.data(), res.size());
+}
+
+/*
+ Convert field from rocksdb storage format into Mysql Record format
+ @param buf OUT start memory to fill converted data
+ @param offset IN/OUT decoded data is stored in buf + offset
+ @param table IN current table
+ @param field IN current field
+ @param reader IN rocksdb value slice reader
+ @param decode IN whether to decode current field
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_convert_to_record_value_decoder::decode(uchar *const buf, uint *offset,
+ TABLE *table,
+ my_core::Field *field,
+ Rdb_field_encoder *field_dec,
+ Rdb_string_reader *reader,
+ bool decode, bool is_null) {
+ int err = HA_EXIT_SUCCESS;
+
+ uint field_offset = field->ptr - table->record[0];
+ *offset = field_offset;
+ uint null_offset = field->null_offset();
+ bool maybe_null = field->real_maybe_null();
+ field->move_field(buf + field_offset,
+ maybe_null ? buf + null_offset : nullptr, field->null_bit);
+
+ if (is_null) {
+ if (decode) {
+ // This sets the NULL-bit of this record
+ field->set_null();
+ /*
+ Besides that, set the field value to default value. CHECKSUM TABLE
+ depends on this.
+ */
+ memcpy(field->ptr, table->s->default_values + field_offset,
+ field->pack_length());
+ }
+ } else {
+ if (decode) {
+ // sets non-null bits for this record
+ field->set_notnull();
+ }
+
+ if (field_dec->m_field_type == MYSQL_TYPE_BLOB) {
+ err = decode_blob(table, field, reader, decode);
+ } else if (field_dec->m_field_type == MYSQL_TYPE_VARCHAR) {
+ err = decode_varchar(field, reader, decode);
+ } else {
+ err = decode_fixed_length_field(field, field_dec, reader, decode);
+ }
+ }
+
+ // Restore field->ptr and field->null_ptr
+ field->move_field(table->record[0] + field_offset,
+ maybe_null ? table->record[0] + null_offset : nullptr,
+ field->null_bit);
+
+ return err;
+}
+
+/*
+ Convert blob from rocksdb storage format into Mysql Record format
+ @param table IN current table
+ @param field IN current field
+ @param reader IN rocksdb value slice reader
+ @param decode IN whether to decode current field
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_convert_to_record_value_decoder::decode_blob(TABLE *table, Field *field,
+ Rdb_string_reader *reader,
+ bool decode) {
+ my_core::Field_blob *blob = (my_core::Field_blob *)field;
+
+ // Get the number of bytes needed to store length
+ const uint length_bytes = blob->pack_length() - portable_sizeof_char_ptr;
+
+ const char *data_len_str;
+ if (!(data_len_str = reader->read(length_bytes))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ memcpy(blob->ptr, data_len_str, length_bytes);
+ uint32 data_len =
+ blob->get_length(reinterpret_cast<const uchar *>(data_len_str),
+ length_bytes);
+ const char *blob_ptr;
+ if (!(blob_ptr = reader->read(data_len))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ if (decode) {
+ // set 8-byte pointer to 0, like innodb does (relevant for 32-bit
+ // platforms)
+ memset(blob->ptr + length_bytes, 0, 8);
+ memcpy(blob->ptr + length_bytes, &blob_ptr, sizeof(uchar **));
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Convert fixed length field from rocksdb storage format into Mysql Record
+ format
+ @param field IN current field
+ @param field_dec IN data structure conttain field encoding data
+ @param reader IN rocksdb value slice reader
+ @param decode IN whether to decode current field
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_convert_to_record_value_decoder::decode_fixed_length_field(
+ my_core::Field *const field, Rdb_field_encoder *field_dec,
+ Rdb_string_reader *const reader, bool decode) {
+ uint len = field_dec->m_pack_length_in_rec;
+ if (len > 0) {
+ const char *data_bytes;
+ if ((data_bytes = reader->read(len)) == nullptr) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ if (decode) {
+ memcpy(field->ptr, data_bytes, len);
+ }
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Convert varchar field from rocksdb storage format into Mysql Record format
+ @param field IN current field
+ @param field_dec IN data structure conttain field encoding data
+ @param reader IN rocksdb value slice reader
+ @param decode IN whether to decode current field
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_convert_to_record_value_decoder::decode_varchar(
+ Field *field, Rdb_string_reader *const reader, bool decode) {
+ my_core::Field_varstring *const field_var = (my_core::Field_varstring *)field;
+
+ const char *data_len_str;
+ if (!(data_len_str = reader->read(field_var->length_bytes))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ uint data_len;
+ // field_var->length_bytes is 1 or 2
+ if (field_var->length_bytes == 1) {
+ data_len = (uchar)data_len_str[0];
+ } else {
+ DBUG_ASSERT(field_var->length_bytes == 2);
+ data_len = uint2korr(data_len_str);
+ }
+
+ if (data_len > field_var->field_length) {
+ // The data on disk is longer than table DDL allows?
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ if (!reader->read(data_len)) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ if (decode) {
+ memcpy(field_var->ptr, data_len_str, field_var->length_bytes + data_len);
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+template <typename value_field_decoder>
+Rdb_value_field_iterator<value_field_decoder>::Rdb_value_field_iterator(
+ TABLE *table, Rdb_string_reader *value_slice_reader,
+ const Rdb_converter *rdb_converter, uchar *const buf)
+ : m_buf(buf) {
+ DBUG_ASSERT(table != nullptr);
+ DBUG_ASSERT(buf != nullptr);
+
+ m_table = table;
+ m_value_slice_reader = value_slice_reader;
+ auto fields = rdb_converter->get_decode_fields();
+ m_field_iter = fields->begin();
+ m_field_end = fields->end();
+ m_null_bytes = rdb_converter->get_null_bytes();
+ m_offset = 0;
+}
+
+// Iterate each requested field and decode one by one
+template <typename value_field_decoder>
+int Rdb_value_field_iterator<value_field_decoder>::next() {
+ int err = HA_EXIT_SUCCESS;
+ while (m_field_iter != m_field_end) {
+ m_field_dec = m_field_iter->m_field_enc;
+ bool decode = m_field_iter->m_decode;
+ bool maybe_null = m_field_dec->maybe_null();
+ // This is_null value is bind to how stroage format store its value
+ m_is_null = maybe_null && ((m_null_bytes[m_field_dec->m_null_offset] &
+ m_field_dec->m_null_mask) != 0);
+
+ // Skip the bytes we need to skip
+ int skip = m_field_iter->m_skip;
+ if (skip && !m_value_slice_reader->read(skip)) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ m_field = m_table->field[m_field_dec->m_field_index];
+ // Decode each field
+ err = value_field_decoder::decode(m_buf, &m_offset, m_table, m_field,
+ m_field_dec, m_value_slice_reader, decode,
+ m_is_null);
+ if (err != HA_EXIT_SUCCESS) {
+ return err;
+ }
+ m_field_iter++;
+ // Only break for the field that are actually decoding rather than skipping
+ if (decode) {
+ break;
+ }
+ }
+ return err;
+}
+
+template <typename value_field_decoder>
+bool Rdb_value_field_iterator<value_field_decoder>::end_of_fields() const {
+ return m_field_iter == m_field_end;
+}
+
+template <typename value_field_decoder>
+Field *Rdb_value_field_iterator<value_field_decoder>::get_field() const {
+ DBUG_ASSERT(m_field != nullptr);
+ return m_field;
+}
+
+template <typename value_field_decoder>
+void *Rdb_value_field_iterator<value_field_decoder>::get_dst() const {
+ DBUG_ASSERT(m_buf != nullptr);
+ return m_buf + m_offset;
+}
+
+template <typename value_field_decoder>
+int Rdb_value_field_iterator<value_field_decoder>::get_field_index() const {
+ DBUG_ASSERT(m_field_dec != nullptr);
+ return m_field_dec->m_field_index;
+}
+
+template <typename value_field_decoder>
+enum_field_types Rdb_value_field_iterator<value_field_decoder>::get_field_type()
+ const {
+ DBUG_ASSERT(m_field_dec != nullptr);
+ return m_field_dec->m_field_type;
+}
+
+template <typename value_field_decoder>
+bool Rdb_value_field_iterator<value_field_decoder>::is_null() const {
+ DBUG_ASSERT(m_field != nullptr);
+ return m_is_null;
+}
+
+/*
+ Initialize Rdb_converter with table data
+ @param thd IN Thread context
+ @param tbl_def IN MyRocks table definition
+ @param table IN Current open table
+*/
+Rdb_converter::Rdb_converter(const THD *thd, const Rdb_tbl_def *tbl_def,
+ TABLE *table)
+ : m_thd(thd), m_tbl_def(tbl_def), m_table(table) {
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tbl_def != nullptr);
+ DBUG_ASSERT(table != nullptr);
+
+ m_key_requested = false;
+ m_verify_row_debug_checksums = false;
+ m_maybe_unpack_info = false;
+ m_row_checksums_checked = 0;
+ m_null_bytes = nullptr;
+ setup_field_encoders();
+}
+
+Rdb_converter::~Rdb_converter() {
+ my_free(m_encoder_arr);
+ m_encoder_arr = nullptr;
+ // These are needed to suppress valgrind errors in rocksdb.partition
+ m_storage_record.free();
+}
+
+/*
+ Decide storage type for each encoder
+*/
+void Rdb_converter::get_storage_type(Rdb_field_encoder *const encoder,
+ const uint kp) {
+ auto pk_descr =
+ m_tbl_def->m_key_descr_arr[ha_rocksdb::pk_index(m_table, m_tbl_def)];
+ // STORE_SOME uses unpack_info.
+ if (pk_descr->has_unpack_info(kp)) {
+ DBUG_ASSERT(pk_descr->can_unpack(kp));
+ encoder->m_storage_type = Rdb_field_encoder::STORE_SOME;
+ m_maybe_unpack_info = true;
+ } else if (pk_descr->can_unpack(kp)) {
+ encoder->m_storage_type = Rdb_field_encoder::STORE_NONE;
+ }
+}
+
+/*
+ @brief
+ Setup which fields will be unpacked when reading rows
+
+ @detail
+ Three special cases when we still unpack all fields:
+ - When client requires decode_all_fields, such as this table is being
+ updated (m_lock_rows==RDB_LOCK_WRITE).
+ - When @@rocksdb_verify_row_debug_checksums is ON (In this mode, we need to
+ read all fields to find whether there is a row checksum at the end. We could
+ skip the fields instead of decoding them, but currently we do decoding.)
+ - On index merge as bitmap is cleared during that operation
+
+ @seealso
+ Rdb_converter::setup_field_encoders()
+ Rdb_converter::convert_record_from_storage_format()
+*/
+void Rdb_converter::setup_field_decoders(const MY_BITMAP *field_map,
+ bool decode_all_fields) {
+ m_key_requested = false;
+ m_decoders_vect.clear();
+ int last_useful = 0;
+ int skip_size = 0;
+
+ for (uint i = 0; i < m_table->s->fields; i++) {
+ // bitmap is cleared on index merge, but it still needs to decode columns
+ bool field_requested =
+ decode_all_fields || m_verify_row_debug_checksums ||
+ bitmap_is_clear_all(field_map) ||
+ bitmap_is_set(field_map, m_table->field[i]->field_index);
+
+ // We only need the decoder if the whole record is stored.
+ if (m_encoder_arr[i].m_storage_type != Rdb_field_encoder::STORE_ALL) {
+ // the field potentially needs unpacking
+ if (field_requested) {
+ // the field is in the read set
+ m_key_requested = true;
+ }
+ continue;
+ }
+
+ if (field_requested) {
+ // We will need to decode this field
+ m_decoders_vect.push_back({&m_encoder_arr[i], true, skip_size});
+ last_useful = m_decoders_vect.size();
+ skip_size = 0;
+ } else {
+ if (m_encoder_arr[i].uses_variable_len_encoding() ||
+ m_encoder_arr[i].maybe_null()) {
+ // For variable-length field, we need to read the data and skip it
+ m_decoders_vect.push_back({&m_encoder_arr[i], false, skip_size});
+ skip_size = 0;
+ } else {
+ // Fixed-width field can be skipped without looking at it.
+ // Add appropriate skip_size to the next field.
+ skip_size += m_encoder_arr[i].m_pack_length_in_rec;
+ }
+ }
+ }
+
+ // It could be that the last few elements are varchars that just do
+ // skipping. Remove them.
+ m_decoders_vect.erase(m_decoders_vect.begin() + last_useful,
+ m_decoders_vect.end());
+}
+
+void Rdb_converter::setup_field_encoders() {
+ uint null_bytes_length = 0;
+ uchar cur_null_mask = 0x1;
+
+ m_encoder_arr = static_cast<Rdb_field_encoder *>(
+ my_malloc(PSI_INSTRUMENT_ME, m_table->s->fields * sizeof(Rdb_field_encoder), MYF(0)));
+ if (m_encoder_arr == nullptr) {
+ return;
+ }
+
+ for (uint i = 0; i < m_table->s->fields; i++) {
+ Field *const field = m_table->field[i];
+ m_encoder_arr[i].m_storage_type = Rdb_field_encoder::STORE_ALL;
+
+ /*
+ Check if this field is
+ - a part of primary key, and
+ - it can be decoded back from its key image.
+ If both hold, we don't need to store this field in the value part of
+ RocksDB's key-value pair.
+
+ If hidden pk exists, we skip this check since the field will never be
+ part of the hidden pk.
+ */
+ if (!Rdb_key_def::table_has_hidden_pk(m_table)) {
+ KEY *const pk_info = &m_table->key_info[m_table->s->primary_key];
+ for (uint kp = 0; kp < pk_info->user_defined_key_parts; kp++) {
+ // key_part->fieldnr is counted from 1
+ if (field->field_index + 1 == pk_info->key_part[kp].fieldnr) {
+ get_storage_type(&m_encoder_arr[i], kp);
+ break;
+ }
+ }
+ }
+
+ m_encoder_arr[i].m_field_type = field->real_type();
+ m_encoder_arr[i].m_field_index = i;
+ m_encoder_arr[i].m_pack_length_in_rec = field->pack_length_in_rec();
+
+ if (field->real_maybe_null()) {
+ m_encoder_arr[i].m_null_mask = cur_null_mask;
+ m_encoder_arr[i].m_null_offset = null_bytes_length;
+ if (cur_null_mask == 0x80) {
+ cur_null_mask = 0x1;
+ null_bytes_length++;
+ } else {
+ cur_null_mask = cur_null_mask << 1;
+ }
+ } else {
+ m_encoder_arr[i].m_null_mask = 0;
+ }
+ }
+
+ // Count the last, unfinished NULL-bits byte
+ if (cur_null_mask != 0x1) {
+ null_bytes_length++;
+ }
+
+ m_null_bytes_length_in_record = null_bytes_length;
+}
+
+/*
+ EntryPoint for Decode:
+ Decode key slice(if requested) and value slice using built-in field
+ decoders
+ @param key_def IN key definition to decode
+ @param dst OUT Mysql buffer to fill decoded content
+ @param key_slice IN RocksDB key slice to decode
+ @param value_slice IN RocksDB value slice to decode
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_converter::decode(const std::shared_ptr<Rdb_key_def> &key_def,
+ uchar *dst, // address to fill data
+ const rocksdb::Slice *key_slice,
+ const rocksdb::Slice *value_slice) {
+ // Currently only support decode primary key, Will add decode secondary later
+ DBUG_ASSERT(key_def->m_index_type == Rdb_key_def::INDEX_TYPE_PRIMARY ||
+ key_def->m_index_type == Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY);
+
+ const rocksdb::Slice *updated_key_slice = key_slice;
+#ifndef DBUG_OFF
+ String last_rowkey;
+ last_rowkey.copy(key_slice->data(), key_slice->size(), &my_charset_bin);
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_pk_read1",
+ { dbug_modify_key_varchar8(&last_rowkey); });
+ rocksdb::Slice rowkey_slice(last_rowkey.ptr(), last_rowkey.length());
+ updated_key_slice = &rowkey_slice;
+#endif
+ return convert_record_from_storage_format(key_def, updated_key_slice,
+ value_slice, dst);
+}
+
+/*
+ Decode value slice header
+ @param reader IN value slice reader
+ @param pk_def IN key definition to decode
+ @param unpack_slice OUT unpack info slice
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_converter::decode_value_header(
+ Rdb_string_reader *reader, const std::shared_ptr<Rdb_key_def> &pk_def,
+ rocksdb::Slice *unpack_slice) {
+ /* If it's a TTL record, skip the 8 byte TTL value */
+ if (pk_def->has_ttl()) {
+ const char *ttl_bytes;
+ if ((ttl_bytes = reader->read(ROCKSDB_SIZEOF_TTL_RECORD))) {
+ memcpy(m_ttl_bytes, ttl_bytes, ROCKSDB_SIZEOF_TTL_RECORD);
+ } else {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ }
+
+ /* Other fields are decoded from the value */
+ if (m_null_bytes_length_in_record &&
+ !(m_null_bytes = reader->read(m_null_bytes_length_in_record))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ if (m_maybe_unpack_info) {
+ const char *unpack_info = reader->get_current_ptr();
+ if (!unpack_info || !Rdb_key_def::is_unpack_data_tag(unpack_info[0]) ||
+ !reader->read(Rdb_key_def::get_unpack_header_size(unpack_info[0]))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ uint16 unpack_info_len =
+ rdb_netbuf_to_uint16(reinterpret_cast<const uchar *>(unpack_info + 1));
+ *unpack_slice = rocksdb::Slice(unpack_info, unpack_info_len);
+
+ reader->read(unpack_info_len -
+ Rdb_key_def::get_unpack_header_size(unpack_info[0]));
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Convert RocksDb key slice and value slice to Mysql format
+ @param key_def IN key definition to decode
+ @param key_slice IN RocksDB key slice
+ @param value_slice IN RocksDB value slice
+ @param dst OUT MySql format address
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_converter::convert_record_from_storage_format(
+ const std::shared_ptr<Rdb_key_def> &pk_def,
+ const rocksdb::Slice *const key_slice,
+ const rocksdb::Slice *const value_slice, uchar *const dst) {
+ int err = HA_EXIT_SUCCESS;
+
+ Rdb_string_reader value_slice_reader(value_slice);
+ rocksdb::Slice unpack_slice;
+ err = decode_value_header(&value_slice_reader, pk_def, &unpack_slice);
+ if (err != HA_EXIT_SUCCESS) {
+ return err;
+ }
+
+ /*
+ Decode PK fields from the key
+ */
+ if (m_key_requested) {
+ err = pk_def->unpack_record(m_table, dst, key_slice,
+ !unpack_slice.empty() ? &unpack_slice : nullptr,
+ false /* verify_checksum */);
+ }
+ if (err != HA_EXIT_SUCCESS) {
+ return err;
+ }
+
+ Rdb_value_field_iterator<Rdb_convert_to_record_value_decoder>
+ value_field_iterator(m_table, &value_slice_reader, this, dst);
+
+ // Decode value slices
+ while (!value_field_iterator.end_of_fields()) {
+ err = value_field_iterator.next();
+
+ if (err != HA_EXIT_SUCCESS) {
+ return err;
+ }
+ }
+
+ if (m_verify_row_debug_checksums) {
+ return verify_row_debug_checksum(pk_def, &value_slice_reader, key_slice,
+ value_slice);
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Verify checksum for row
+ @param pk_def IN key def
+ @param reader IN RocksDB value slice reader
+ @param key IN RocksDB key slice
+ @param value IN RocksDB value slice
+ @return
+ 0 OK
+ other HA_ERR error code (can be SE-specific)
+*/
+int Rdb_converter::verify_row_debug_checksum(
+ const std::shared_ptr<Rdb_key_def> &pk_def, Rdb_string_reader *reader,
+ const rocksdb::Slice *key, const rocksdb::Slice *value) {
+ if (reader->remaining_bytes() == RDB_CHECKSUM_CHUNK_SIZE &&
+ reader->read(1)[0] == RDB_CHECKSUM_DATA_TAG) {
+ uint32_t stored_key_chksum =
+ rdb_netbuf_to_uint32((const uchar *)reader->read(RDB_CHECKSUM_SIZE));
+ uint32_t stored_val_chksum =
+ rdb_netbuf_to_uint32((const uchar *)reader->read(RDB_CHECKSUM_SIZE));
+
+ const uint32_t computed_key_chksum =
+ my_core::my_checksum(0, rdb_slice_to_uchar_ptr(key), key->size());
+ const uint32_t computed_val_chksum =
+ my_core::my_checksum(0, rdb_slice_to_uchar_ptr(value),
+ value->size() - RDB_CHECKSUM_CHUNK_SIZE);
+
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_pk_checksum1", stored_key_chksum++;);
+
+ if (stored_key_chksum != computed_key_chksum) {
+ pk_def->report_checksum_mismatch(true, key->data(), key->size());
+ return HA_ERR_ROCKSDB_CHECKSUM_MISMATCH;
+ }
+
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_pk_checksum2", stored_val_chksum++;);
+ if (stored_val_chksum != computed_val_chksum) {
+ pk_def->report_checksum_mismatch(false, value->data(), value->size());
+ return HA_ERR_ROCKSDB_CHECKSUM_MISMATCH;
+ }
+
+ m_row_checksums_checked++;
+ }
+ if (reader->remaining_bytes()) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Convert record from table->record[0] form into a form that can be written
+ into rocksdb.
+
+ @param pk_def IN Current key def
+ @pk_unpack_info IN Unpack info generated during key pack
+ @is_update_row IN Whether it is update row
+ @store_row_debug_checksums IN Whether to store checksums
+ @param ttl_bytes IN/OUT Old ttl value from previous record and
+ ttl value during current encode
+ @is_ttl_bytes_updated OUT Whether ttl bytes is updated
+ @param value_slice OUT Data slice with record data.
+*/
+int Rdb_converter::encode_value_slice(
+ const std::shared_ptr<Rdb_key_def> &pk_def,
+ const rocksdb::Slice &pk_packed_slice, Rdb_string_writer *pk_unpack_info,
+ bool is_update_row, bool store_row_debug_checksums, char *ttl_bytes,
+ bool *is_ttl_bytes_updated, rocksdb::Slice *const value_slice) {
+ DBUG_ASSERT(pk_def != nullptr);
+ // Currently only primary key will store value slice
+ DBUG_ASSERT(pk_def->m_index_type == Rdb_key_def::INDEX_TYPE_PRIMARY ||
+ pk_def->m_index_type == Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY);
+ DBUG_ASSERT_IMP(m_maybe_unpack_info, pk_unpack_info);
+
+ bool has_ttl = pk_def->has_ttl();
+ bool has_ttl_column = !pk_def->m_ttl_column.empty();
+
+ m_storage_record.length(0);
+
+ if (has_ttl) {
+ /* If it's a TTL record, reserve space for 8 byte TTL value in front. */
+ m_storage_record.fill(
+ ROCKSDB_SIZEOF_TTL_RECORD + m_null_bytes_length_in_record, 0);
+ // NOTE: is_ttl_bytes_updated is only used for update case
+ // During update, skip update sk key/values slice iff none of sk fields
+ // have changed and ttl bytes isn't changed. see
+ // ha_rocksdb::update_write_sk() for more info
+ *is_ttl_bytes_updated = false;
+ char *const data = const_cast<char *>(m_storage_record.ptr());
+ if (has_ttl_column) {
+ DBUG_ASSERT(pk_def->get_ttl_field_index() != UINT_MAX);
+ Field *const field = m_table->field[pk_def->get_ttl_field_index()];
+ DBUG_ASSERT(field->pack_length_in_rec() == ROCKSDB_SIZEOF_TTL_RECORD);
+ DBUG_ASSERT(field->real_type() == MYSQL_TYPE_LONGLONG);
+
+ uint64 ts = uint8korr(field->ptr);
+#ifndef DBUG_OFF
+ ts += rdb_dbug_set_ttl_rec_ts();
+#endif
+ rdb_netbuf_store_uint64(reinterpret_cast<uchar *>(data), ts);
+ if (is_update_row) {
+ *is_ttl_bytes_updated =
+ memcmp(ttl_bytes, data, ROCKSDB_SIZEOF_TTL_RECORD);
+ }
+ // Also store in m_ttl_bytes to propagate to update_write_sk
+ memcpy(ttl_bytes, data, ROCKSDB_SIZEOF_TTL_RECORD);
+ } else {
+ /*
+ For implicitly generated TTL records we need to copy over the old
+ TTL value from the old record in the event of an update. It was stored
+ in m_ttl_bytes.
+
+ Otherwise, generate a timestamp using the current time.
+ */
+ if (is_update_row) {
+ memcpy(data, ttl_bytes, sizeof(uint64));
+ } else {
+ uint64 ts = static_cast<uint64>(std::time(nullptr));
+#ifndef DBUG_OFF
+ ts += rdb_dbug_set_ttl_rec_ts();
+#endif
+ rdb_netbuf_store_uint64(reinterpret_cast<uchar *>(data), ts);
+ // Also store in m_ttl_bytes to propagate to update_write_sk
+ memcpy(ttl_bytes, data, ROCKSDB_SIZEOF_TTL_RECORD);
+ }
+ }
+ } else {
+ /* All NULL bits are initially 0 */
+ m_storage_record.fill(m_null_bytes_length_in_record, 0);
+ }
+
+ // If a primary key may have non-empty unpack_info for certain values,
+ // (m_maybe_unpack_info=TRUE), we write the unpack_info block. The block
+ // itself was prepared in Rdb_key_def::pack_record.
+ if (m_maybe_unpack_info) {
+ m_storage_record.append(reinterpret_cast<char *>(pk_unpack_info->ptr()),
+ pk_unpack_info->get_current_pos());
+ }
+ for (uint i = 0; i < m_table->s->fields; i++) {
+ Rdb_field_encoder &encoder = m_encoder_arr[i];
+ /* Don't pack decodable PK key parts */
+ if (encoder.m_storage_type != Rdb_field_encoder::STORE_ALL) {
+ continue;
+ }
+
+ Field *const field = m_table->field[i];
+ if (encoder.maybe_null()) {
+ char *data = const_cast<char *>(m_storage_record.ptr());
+ if (has_ttl) {
+ data += ROCKSDB_SIZEOF_TTL_RECORD;
+ }
+
+ if (field->is_null()) {
+ data[encoder.m_null_offset] |= encoder.m_null_mask;
+ /* Don't write anything for NULL values */
+ continue;
+ }
+ }
+
+ if (encoder.m_field_type == MYSQL_TYPE_BLOB) {
+ my_core::Field_blob *blob =
+ reinterpret_cast<my_core::Field_blob *>(field);
+ /* Get the number of bytes needed to store length*/
+ const uint length_bytes = blob->pack_length() - portable_sizeof_char_ptr;
+
+ /* Store the length of the value */
+ m_storage_record.append(reinterpret_cast<char *>(blob->ptr),
+ length_bytes);
+
+ /* Store the blob value itself */
+ char *data_ptr;
+ memcpy(&data_ptr, blob->ptr + length_bytes, sizeof(uchar **));
+ m_storage_record.append(data_ptr, blob->get_length());
+ } else if (encoder.m_field_type == MYSQL_TYPE_VARCHAR) {
+ Field_varstring *const field_var =
+ reinterpret_cast<Field_varstring *>(field);
+ uint data_len;
+ /* field_var->length_bytes is 1 or 2 */
+ if (field_var->length_bytes == 1) {
+ data_len = field_var->ptr[0];
+ } else {
+ DBUG_ASSERT(field_var->length_bytes == 2);
+ data_len = uint2korr(field_var->ptr);
+ }
+ m_storage_record.append(reinterpret_cast<char *>(field_var->ptr),
+ field_var->length_bytes + data_len);
+ } else {
+ /* Copy the field data */
+ const uint len = field->pack_length_in_rec();
+ m_storage_record.append(reinterpret_cast<char *>(field->ptr), len);
+ }
+ }
+
+ if (store_row_debug_checksums) {
+ const uint32_t key_crc32 = my_core::my_checksum(
+ 0, rdb_slice_to_uchar_ptr(&pk_packed_slice), pk_packed_slice.size());
+ const uint32_t val_crc32 =
+ my_core::my_checksum(0, rdb_mysql_str_to_uchar_str(&m_storage_record),
+ m_storage_record.length());
+ uchar key_crc_buf[RDB_CHECKSUM_SIZE];
+ uchar val_crc_buf[RDB_CHECKSUM_SIZE];
+ rdb_netbuf_store_uint32(key_crc_buf, key_crc32);
+ rdb_netbuf_store_uint32(val_crc_buf, val_crc32);
+ m_storage_record.append((const char *)&RDB_CHECKSUM_DATA_TAG, 1);
+ m_storage_record.append((const char *)key_crc_buf, RDB_CHECKSUM_SIZE);
+ m_storage_record.append((const char *)val_crc_buf, RDB_CHECKSUM_SIZE);
+ }
+
+ *value_slice =
+ rocksdb::Slice(m_storage_record.ptr(), m_storage_record.length());
+
+ return HA_EXIT_SUCCESS;
+}
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_converter.h b/storage/rocksdb/rdb_converter.h
new file mode 100644
index 00000000..a4eae341
--- /dev/null
+++ b/storage/rocksdb/rdb_converter.h
@@ -0,0 +1,247 @@
+/*
+ Copyright (c) 2018, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#pragma once
+
+// C++ standard header files
+#include <string>
+#include <vector>
+
+// MySQL header files
+#include "./handler.h" // handler
+#include "./my_global.h" // ulonglong
+#include "./sql_string.h"
+#include "./ut0counter.h"
+
+// MyRocks header files
+#include "./ha_rocksdb.h"
+#include "./rdb_datadic.h"
+
+namespace myrocks {
+class Rdb_field_encoder;
+
+/**
+ Describes instructions on how to decode the field for value slice
+*/
+struct READ_FIELD {
+ // Points to Rdb_field_encoder describing the field
+ Rdb_field_encoder *m_field_enc;
+ // if true, decode the field, otherwise skip it
+ bool m_decode;
+ // Skip this many bytes before reading (or skipping) this field
+ int m_skip;
+};
+
+/**
+ Class to convert rocksdb value slice from storage format to mysql record
+ format.
+*/
+class Rdb_convert_to_record_value_decoder {
+ public:
+ Rdb_convert_to_record_value_decoder() = delete;
+ Rdb_convert_to_record_value_decoder(
+ const Rdb_convert_to_record_value_decoder &decoder) = delete;
+ Rdb_convert_to_record_value_decoder &operator=(
+ const Rdb_convert_to_record_value_decoder &decoder) = delete;
+
+ static int decode(uchar *const buf, uint *offset, TABLE *table,
+ my_core::Field *field, Rdb_field_encoder *field_dec,
+ Rdb_string_reader *reader, bool decode, bool is_null);
+
+ private:
+ static int decode_blob(TABLE *table, Field *field, Rdb_string_reader *reader,
+ bool decode);
+ static int decode_fixed_length_field(Field *const field,
+ Rdb_field_encoder *field_dec,
+ Rdb_string_reader *const reader,
+ bool decode);
+
+ static int decode_varchar(Field *const field, Rdb_string_reader *const reader,
+ bool decode);
+};
+
+/**
+ Class to iterator fields in RocksDB value slice
+ A template class instantiation represent a way to decode the data.
+ The reason to use template class instead of normal class is to elimate
+ virtual method call.
+*/
+template <typename value_field_decoder>
+class Rdb_value_field_iterator {
+ private:
+ bool m_is_null;
+ std::vector<READ_FIELD>::const_iterator m_field_iter;
+ std::vector<READ_FIELD>::const_iterator m_field_end;
+ Rdb_string_reader *m_value_slice_reader;
+ // null value map
+ const char *m_null_bytes;
+ // The current open table
+ TABLE *m_table;
+ // The current field
+ Field *m_field;
+ Rdb_field_encoder *m_field_dec;
+ uchar *const m_buf;
+ uint m_offset;
+
+ public:
+ Rdb_value_field_iterator(TABLE *table, Rdb_string_reader *value_slice_reader,
+ const Rdb_converter *rdb_converter,
+ uchar *const buf);
+ Rdb_value_field_iterator(const Rdb_value_field_iterator &field_iterator) =
+ delete;
+ Rdb_value_field_iterator &operator=(
+ const Rdb_value_field_iterator &field_iterator) = delete;
+
+ /*
+ Move and decode next field
+ Run next() before accessing data
+ */
+ int next();
+ // Whether current field is the end of fields
+ bool end_of_fields() const;
+ void *get_dst() const;
+ // Whether the value of current field is null
+ bool is_null() const;
+ // get current field index
+ int get_field_index() const;
+ // get current field type
+ enum_field_types get_field_type() const;
+ // get current field
+ Field *get_field() const;
+};
+
+/**
+ Class to convert Mysql formats to rocksdb storage format, and vice versa.
+*/
+class Rdb_converter {
+ public:
+ /*
+ Initialize converter with table data
+ */
+ Rdb_converter(const THD *thd, const Rdb_tbl_def *tbl_def, TABLE *table);
+ Rdb_converter(const Rdb_converter &decoder) = delete;
+ Rdb_converter &operator=(const Rdb_converter &decoder) = delete;
+ ~Rdb_converter();
+
+ void setup_field_decoders(const MY_BITMAP *field_map,
+ bool decode_all_fields = false);
+
+ int decode(const std::shared_ptr<Rdb_key_def> &key_def, uchar *dst,
+ const rocksdb::Slice *key_slice,
+ const rocksdb::Slice *value_slice);
+
+ int encode_value_slice(const std::shared_ptr<Rdb_key_def> &pk_def,
+ const rocksdb::Slice &pk_packed_slice,
+ Rdb_string_writer *pk_unpack_info, bool is_update_row,
+ bool store_row_debug_checksums, char *ttl_bytes,
+ bool *is_ttl_bytes_updated,
+ rocksdb::Slice *const value_slice);
+
+ my_core::ha_rows get_row_checksums_checked() const {
+ return m_row_checksums_checked;
+ }
+ bool get_verify_row_debug_checksums() const {
+ return m_verify_row_debug_checksums;
+ }
+ void set_verify_row_debug_checksums(bool verify_row_debug_checksums) {
+ m_verify_row_debug_checksums = verify_row_debug_checksums;
+ }
+
+ const Rdb_field_encoder *get_encoder_arr() const { return m_encoder_arr; }
+ int get_null_bytes_in_record() { return m_null_bytes_length_in_record; }
+ const char *get_null_bytes() const { return m_null_bytes; }
+ void set_is_key_requested(bool key_requested) {
+ m_key_requested = key_requested;
+ }
+ bool get_maybe_unpack_info() const { return m_maybe_unpack_info; }
+
+ char *get_ttl_bytes_buffer() { return m_ttl_bytes; }
+
+ const std::vector<READ_FIELD> *get_decode_fields() const {
+ return &m_decoders_vect;
+ }
+
+ private:
+ int decode_value_header(Rdb_string_reader *reader,
+ const std::shared_ptr<Rdb_key_def> &pk_def,
+ rocksdb::Slice *unpack_slice);
+
+ void setup_field_encoders();
+
+ void get_storage_type(Rdb_field_encoder *const encoder, const uint kp);
+
+ int convert_record_from_storage_format(
+ const std::shared_ptr<Rdb_key_def> &pk_def,
+ const rocksdb::Slice *const key, const rocksdb::Slice *const value,
+ uchar *const buf);
+
+ int verify_row_debug_checksum(const std::shared_ptr<Rdb_key_def> &pk_def,
+ Rdb_string_reader *reader,
+ const rocksdb::Slice *key,
+ const rocksdb::Slice *value);
+
+ private:
+ /*
+ This tells if any field which is part of the key needs to be unpacked and
+ decoded.
+ */
+ bool m_key_requested;
+ /*
+ Controls whether verifying checksums during reading, This is updated from
+ the session variable at the start of each query.
+ */
+ bool m_verify_row_debug_checksums;
+ // Thread handle
+ const THD *m_thd;
+ /* MyRocks table definition*/
+ const Rdb_tbl_def *m_tbl_def;
+ /* The current open table */
+ TABLE *m_table;
+ /*
+ Number of bytes in on-disk (storage) record format that are used for
+ storing SQL NULL flags.
+ */
+ int m_null_bytes_length_in_record;
+ /*
+ Pointer to null bytes value
+ */
+ const char *m_null_bytes;
+ /*
+ TRUE <=> Some fields in the PK may require unpack_info.
+ */
+ bool m_maybe_unpack_info;
+ /*
+ Pointer to the original TTL timestamp value (8 bytes) during UPDATE.
+ */
+ char m_ttl_bytes[ROCKSDB_SIZEOF_TTL_RECORD];
+ /*
+ Array of table->s->fields elements telling how to store fields in the
+ record.
+ */
+ Rdb_field_encoder *m_encoder_arr;
+ /*
+ Array of request fields telling how to decode data in RocksDB format
+ */
+ std::vector<READ_FIELD> m_decoders_vect;
+ /*
+ A counter of how many row checksums were checked for this table. Note that
+ this does not include checksums for secondary index entries.
+ */
+ my_core::ha_rows m_row_checksums_checked;
+ // buffer to hold data during encode_value_slice
+ String m_storage_record;
+};
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_datadic.cc b/storage/rocksdb/rdb_datadic.cc
new file mode 100644
index 00000000..5f46fd8a
--- /dev/null
+++ b/storage/rocksdb/rdb_datadic.cc
@@ -0,0 +1,5411 @@
+/*
+ Copyright (c) 2012,2013 Monty Program Ab
+ Copyright (c) 2020, MariaDB Corporation.
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+/* For use of 'PRIu64': */
+#define __STDC_FORMAT_MACROS
+
+#include <my_global.h>
+/* This C++ file's header file */
+#include "./rdb_datadic.h"
+
+#include <inttypes.h>
+/* C++ standard header files */
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+/* MySQL header files */
+#include "./field.h"
+#include "./key.h"
+#include "./m_ctype.h"
+#include "./my_bit.h"
+#include "./my_bitmap.h"
+#include "./sql_table.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./my_stacktrace.h"
+#include "./rdb_cf_manager.h"
+#include "./rdb_psi.h"
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+void get_mem_comparable_space(const CHARSET_INFO *cs,
+ const std::vector<uchar> **xfrm, size_t *xfrm_len,
+ size_t *mb_len);
+
+/*
+ MariaDB's replacement for FB/MySQL Field::check_field_name_match :
+*/
+inline bool field_check_field_name_match(Field *field, const char *name)
+{
+ return (0 == my_strcasecmp(system_charset_info,
+ field->field_name.str,
+ name));
+}
+
+
+/*
+ Decode current key field
+ @param fpi IN data structure contains field metadata
+ @param field IN current field
+ @param reader IN key slice reader
+ @param unp_reader IN unpack information reader
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code
+*/
+int Rdb_convert_to_record_key_decoder::decode_field(
+ Rdb_field_packing *fpi, Field *field, Rdb_string_reader *reader,
+ const uchar *const default_value, Rdb_string_reader *unpack_reader) {
+ if (fpi->m_maybe_null) {
+ const char *nullp;
+ if (!(nullp = reader->read(1))) {
+ return HA_EXIT_FAILURE;
+ }
+
+ if (*nullp == 0) {
+ /* Set the NULL-bit of this field */
+ field->set_null();
+ /* Also set the field to its default value */
+ memcpy(field->ptr, default_value, field->pack_length());
+ return HA_EXIT_SUCCESS;
+ } else if (*nullp == 1) {
+ field->set_notnull();
+ } else {
+ return HA_EXIT_FAILURE;
+ }
+ }
+
+ return (fpi->m_unpack_func)(fpi, field, field->ptr, reader, unpack_reader);
+}
+
+/*
+ Decode current key field
+
+ @param buf OUT the buf starting address
+ @param offset OUT the bytes offset when data is written
+ @param fpi IN data structure contains field metadata
+ @param table IN current table
+ @param field IN current field
+ @param has_unpack_inf IN whether contains unpack inf
+ @param reader IN key slice reader
+ @param unp_reader IN unpack information reader
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code
+*/
+int Rdb_convert_to_record_key_decoder::decode(
+ uchar *const buf, uint *offset, Rdb_field_packing *fpi, TABLE *table,
+ Field *field, bool has_unpack_info, Rdb_string_reader *reader,
+ Rdb_string_reader *unpack_reader) {
+ DBUG_ASSERT(buf != nullptr);
+ DBUG_ASSERT(offset != nullptr);
+
+ uint field_offset = field->ptr - table->record[0];
+ *offset = field_offset;
+ uint null_offset = field->null_offset();
+ bool maybe_null = field->real_maybe_null();
+
+ field->move_field(buf + field_offset,
+ maybe_null ? buf + null_offset : nullptr, field->null_bit);
+
+ // If we need unpack info, but there is none, tell the unpack function
+ // this by passing unp_reader as nullptr. If we never read unpack_info
+ // during unpacking anyway, then there won't an error.
+ bool maybe_missing_unpack = !has_unpack_info && fpi->uses_unpack_info();
+
+ int res =
+ decode_field(fpi, field, reader, table->s->default_values + field_offset,
+ maybe_missing_unpack ? nullptr : unpack_reader);
+
+ // Restore field->ptr and field->null_ptr
+ field->move_field(table->record[0] + field_offset,
+ maybe_null ? table->record[0] + null_offset : nullptr,
+ field->null_bit);
+ if (res != UNPACK_SUCCESS) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Skip current key field
+
+ @param fpi IN data structure contains field metadata
+ @param field IN current field
+ @param reader IN key slice reader
+ @param unp_reader IN unpack information reader
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code
+*/
+int Rdb_convert_to_record_key_decoder::skip(const Rdb_field_packing *fpi,
+ const Field *field,
+ Rdb_string_reader *reader,
+ Rdb_string_reader *unp_reader) {
+ /* It is impossible to unpack the column. Skip it. */
+ if (fpi->m_maybe_null) {
+ const char *nullp;
+ if (!(nullp = reader->read(1))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ if (*nullp == 0) {
+ /* This is a NULL value */
+ return HA_EXIT_SUCCESS;
+ }
+ /* If NULL marker is not '0', it can be only '1' */
+ if (*nullp != 1) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ }
+ if ((fpi->m_skip_func)(fpi, field, reader)) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ // If this is a space padded varchar, we need to skip the indicator
+ // bytes for trailing bytes. They're useless since we can't restore the
+ // field anyway.
+ //
+ // There is a special case for prefixed varchars where we do not
+ // generate unpack info, because we know prefixed varchars cannot be
+ // unpacked. In this case, it is not necessary to skip.
+ if (fpi->m_skip_func == &Rdb_key_def::skip_variable_space_pad &&
+ !fpi->m_unpack_info_stores_value) {
+ unp_reader->read(fpi->m_unpack_info_uses_two_bytes ? 2 : 1);
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+Rdb_key_field_iterator::Rdb_key_field_iterator(
+ const Rdb_key_def *key_def, Rdb_field_packing *pack_info,
+ Rdb_string_reader *reader, Rdb_string_reader *unp_reader, TABLE *table,
+ bool has_unpack_info, const MY_BITMAP *covered_bitmap, uchar *const buf) {
+ m_key_def = key_def;
+ m_pack_info = pack_info;
+ m_iter_index = 0;
+ m_iter_end = key_def->get_key_parts();
+ m_reader = reader;
+ m_unp_reader = unp_reader;
+ m_table = table;
+ m_has_unpack_info = has_unpack_info;
+ m_covered_bitmap = covered_bitmap;
+ m_buf = buf;
+ m_secondary_key =
+ (key_def->m_index_type == Rdb_key_def::INDEX_TYPE_SECONDARY);
+ m_hidden_pk_exists = Rdb_key_def::table_has_hidden_pk(table);
+ m_is_hidden_pk =
+ (key_def->m_index_type == Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY);
+ m_curr_bitmap_pos = 0;
+ m_offset = 0;
+}
+
+void *Rdb_key_field_iterator::get_dst() const { return m_buf + m_offset; }
+
+int Rdb_key_field_iterator::get_field_index() const {
+ DBUG_ASSERT(m_field != nullptr);
+ return m_field->field_index;
+}
+
+bool Rdb_key_field_iterator::get_is_null() const { return m_is_null; }
+Field *Rdb_key_field_iterator::get_field() const {
+ DBUG_ASSERT(m_field != nullptr);
+ return m_field;
+}
+
+bool Rdb_key_field_iterator::has_next() { return m_iter_index < m_iter_end; }
+
+/**
+ Iterate each field in the key and decode/skip one by one
+*/
+int Rdb_key_field_iterator::next() {
+ int status = HA_EXIT_SUCCESS;
+ while (m_iter_index < m_iter_end) {
+ int curr_index = m_iter_index++;
+
+ m_fpi = &m_pack_info[curr_index];
+ /*
+ Hidden pk field is packed at the end of the secondary keys, but the SQL
+ layer does not know about it. Skip retrieving field if hidden pk.
+ */
+ if ((m_secondary_key && m_hidden_pk_exists &&
+ curr_index + 1 == m_iter_end) ||
+ m_is_hidden_pk) {
+ DBUG_ASSERT(m_fpi->m_unpack_func);
+ if ((m_fpi->m_skip_func)(m_fpi, nullptr, m_reader)) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ return HA_EXIT_SUCCESS;
+ }
+
+ m_field = m_fpi->get_field_in_table(m_table);
+
+ bool covered_column = true;
+ if (m_covered_bitmap != nullptr &&
+ m_field->real_type() == MYSQL_TYPE_VARCHAR && !m_fpi->m_covered) {
+ uint tmp= m_curr_bitmap_pos++;
+ covered_column = m_curr_bitmap_pos < MAX_REF_PARTS &&
+ bitmap_is_set(m_covered_bitmap, tmp);
+ }
+
+ if (m_fpi->m_unpack_func && covered_column) {
+ /* It is possible to unpack this column. Do it. */
+ status = Rdb_convert_to_record_key_decoder::decode(
+ m_buf, &m_offset, m_fpi, m_table, m_field, m_has_unpack_info,
+ m_reader, m_unp_reader);
+ if (status) {
+ return status;
+ }
+ break;
+ } else {
+ status = Rdb_convert_to_record_key_decoder::skip(m_fpi, m_field, m_reader,
+ m_unp_reader);
+ if (status) {
+ return status;
+ }
+ }
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Rdb_key_def class implementation
+*/
+Rdb_key_def::Rdb_key_def(uint indexnr_arg, uint keyno_arg,
+ rocksdb::ColumnFamilyHandle *cf_handle_arg,
+ uint16_t index_dict_version_arg, uchar index_type_arg,
+ uint16_t kv_format_version_arg, bool is_reverse_cf_arg,
+ bool is_per_partition_cf_arg, const char *_name,
+ Rdb_index_stats _stats, uint32 index_flags_bitmap,
+ uint32 ttl_rec_offset, uint64 ttl_duration)
+ : m_index_number(indexnr_arg),
+ m_cf_handle(cf_handle_arg),
+ m_index_dict_version(index_dict_version_arg),
+ m_index_type(index_type_arg),
+ m_kv_format_version(kv_format_version_arg),
+ m_is_reverse_cf(is_reverse_cf_arg),
+ m_is_per_partition_cf(is_per_partition_cf_arg),
+ m_name(_name),
+ m_stats(_stats),
+ m_index_flags_bitmap(index_flags_bitmap),
+ m_ttl_rec_offset(ttl_rec_offset),
+ m_ttl_duration(ttl_duration),
+ m_ttl_column(""),
+ m_pk_part_no(nullptr),
+ m_pack_info(nullptr),
+ m_keyno(keyno_arg),
+ m_key_parts(0),
+ m_ttl_pk_key_part_offset(UINT_MAX),
+ m_ttl_field_index(UINT_MAX),
+ m_prefix_extractor(nullptr),
+ m_maxlength(0) // means 'not intialized'
+{
+ mysql_mutex_init(0, &m_mutex, MY_MUTEX_INIT_FAST);
+ rdb_netbuf_store_index(m_index_number_storage_form, m_index_number);
+ m_total_index_flags_length =
+ calculate_index_flag_offset(m_index_flags_bitmap, MAX_FLAG);
+ DBUG_ASSERT_IMP(m_index_type == INDEX_TYPE_SECONDARY &&
+ m_kv_format_version <= SECONDARY_FORMAT_VERSION_UPDATE2,
+ m_total_index_flags_length == 0);
+ DBUG_ASSERT_IMP(m_index_type == INDEX_TYPE_PRIMARY &&
+ m_kv_format_version <= PRIMARY_FORMAT_VERSION_UPDATE2,
+ m_total_index_flags_length == 0);
+ DBUG_ASSERT(m_cf_handle != nullptr);
+}
+
+Rdb_key_def::Rdb_key_def(const Rdb_key_def &k)
+ : m_index_number(k.m_index_number),
+ m_cf_handle(k.m_cf_handle),
+ m_is_reverse_cf(k.m_is_reverse_cf),
+ m_is_per_partition_cf(k.m_is_per_partition_cf),
+ m_name(k.m_name),
+ m_stats(k.m_stats),
+ m_index_flags_bitmap(k.m_index_flags_bitmap),
+ m_ttl_rec_offset(k.m_ttl_rec_offset),
+ m_ttl_duration(k.m_ttl_duration),
+ m_ttl_column(k.m_ttl_column),
+ m_pk_part_no(k.m_pk_part_no),
+ m_pack_info(k.m_pack_info),
+ m_keyno(k.m_keyno),
+ m_key_parts(k.m_key_parts),
+ m_ttl_pk_key_part_offset(k.m_ttl_pk_key_part_offset),
+ m_ttl_field_index(UINT_MAX),
+ m_prefix_extractor(k.m_prefix_extractor),
+ m_maxlength(k.m_maxlength) {
+ mysql_mutex_init(0, &m_mutex, MY_MUTEX_INIT_FAST);
+ rdb_netbuf_store_index(m_index_number_storage_form, m_index_number);
+ m_total_index_flags_length =
+ calculate_index_flag_offset(m_index_flags_bitmap, MAX_FLAG);
+ DBUG_ASSERT_IMP(m_index_type == INDEX_TYPE_SECONDARY &&
+ m_kv_format_version <= SECONDARY_FORMAT_VERSION_UPDATE2,
+ m_total_index_flags_length == 0);
+ DBUG_ASSERT_IMP(m_index_type == INDEX_TYPE_PRIMARY &&
+ m_kv_format_version <= PRIMARY_FORMAT_VERSION_UPDATE2,
+ m_total_index_flags_length == 0);
+ if (k.m_pack_info) {
+ const size_t size = sizeof(Rdb_field_packing) * k.m_key_parts;
+ void *pack_info= my_malloc(PSI_INSTRUMENT_ME, size, MYF(0));
+ memcpy(pack_info, k.m_pack_info, size);
+ m_pack_info = reinterpret_cast<Rdb_field_packing *>(pack_info);
+ }
+
+ if (k.m_pk_part_no) {
+ const size_t size = sizeof(uint) * m_key_parts;
+ m_pk_part_no = reinterpret_cast<uint *>(my_malloc(PSI_INSTRUMENT_ME, size, MYF(0)));
+ memcpy(m_pk_part_no, k.m_pk_part_no, size);
+ }
+}
+
+Rdb_key_def::~Rdb_key_def() {
+ mysql_mutex_destroy(&m_mutex);
+
+ my_free(m_pk_part_no);
+ m_pk_part_no = nullptr;
+
+ my_free(m_pack_info);
+ m_pack_info = nullptr;
+}
+
+void Rdb_key_def::setup(const TABLE *const tbl,
+ const Rdb_tbl_def *const tbl_def) {
+ DBUG_ASSERT(tbl != nullptr);
+ DBUG_ASSERT(tbl_def != nullptr);
+
+ /*
+ Set max_length based on the table. This can be called concurrently from
+ multiple threads, so there is a mutex to protect this code.
+ */
+ const bool is_hidden_pk = (m_index_type == INDEX_TYPE_HIDDEN_PRIMARY);
+ const bool hidden_pk_exists = table_has_hidden_pk(tbl);
+ const bool secondary_key = (m_index_type == INDEX_TYPE_SECONDARY);
+ if (!m_maxlength) {
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ if (m_maxlength != 0) {
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ return;
+ }
+
+ KEY *key_info = nullptr;
+ KEY *pk_info = nullptr;
+ if (!is_hidden_pk) {
+ key_info = &tbl->key_info[m_keyno];
+ if (!hidden_pk_exists) pk_info = &tbl->key_info[tbl->s->primary_key];
+ m_name = std::string(key_info->name.str);
+ } else {
+ m_name = HIDDEN_PK_NAME;
+ }
+
+ if (secondary_key) {
+ m_pk_key_parts= hidden_pk_exists ? 1 : pk_info->ext_key_parts;
+ } else {
+ pk_info = nullptr;
+ m_pk_key_parts = 0;
+ }
+
+ // "unique" secondary keys support:
+ m_key_parts= is_hidden_pk ? 1 : key_info->ext_key_parts;
+
+ if (secondary_key) {
+ /*
+ In most cases, SQL layer puts PK columns as invisible suffix at the
+ end of secondary key. There are cases where this doesn't happen:
+ - unique secondary indexes.
+ - partitioned tables.
+
+ Internally, we always need PK columns as suffix (and InnoDB does,
+ too, if you were wondering).
+
+ The loop below will attempt to put all PK columns at the end of key
+ definition. Columns that are already included in the index (either
+ by the user or by "extended keys" feature) are not included for the
+ second time.
+ */
+ m_key_parts += m_pk_key_parts;
+ }
+
+ if (secondary_key) {
+ m_pk_part_no = reinterpret_cast<uint *>(
+ my_malloc(PSI_INSTRUMENT_ME, sizeof(uint) * m_key_parts, MYF(0)));
+ } else {
+ m_pk_part_no = nullptr;
+ }
+
+ const size_t size = sizeof(Rdb_field_packing) * m_key_parts;
+ m_pack_info =
+ reinterpret_cast<Rdb_field_packing *>(my_malloc(PSI_INSTRUMENT_ME, size, MYF(0)));
+
+ /*
+ Guaranteed not to error here as checks have been made already during
+ table creation.
+ */
+ Rdb_key_def::extract_ttl_col(tbl, tbl_def, &m_ttl_column,
+ &m_ttl_field_index, true);
+
+ size_t max_len = INDEX_NUMBER_SIZE;
+ int unpack_len = 0;
+ int max_part_len = 0;
+ bool simulating_extkey = false;
+ uint dst_i = 0;
+
+ uint keyno_to_set = m_keyno;
+ uint keypart_to_set = 0;
+
+ if (is_hidden_pk) {
+ Field *field = nullptr;
+ m_pack_info[dst_i].setup(this, field, keyno_to_set, 0, 0);
+ m_pack_info[dst_i].m_unpack_data_offset = unpack_len;
+ max_len += m_pack_info[dst_i].m_max_image_len;
+ max_part_len = std::max(max_part_len, m_pack_info[dst_i].m_max_image_len);
+ dst_i++;
+ } else {
+ KEY_PART_INFO *key_part = key_info->key_part;
+
+ /* this loop also loops over the 'extended key' tail */
+ for (uint src_i = 0; src_i < m_key_parts; src_i++, keypart_to_set++) {
+ Field *const field = key_part ? key_part->field : nullptr;
+
+ if (simulating_extkey && !hidden_pk_exists) {
+ DBUG_ASSERT(secondary_key);
+ /* Check if this field is already present in the key definition */
+ bool found = false;
+ for (uint j= 0; j < key_info->ext_key_parts; j++) {
+ if (field->field_index ==
+ key_info->key_part[j].field->field_index &&
+ key_part->length == key_info->key_part[j].length) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ key_part++;
+ continue;
+ }
+ }
+
+ if (field && field->real_maybe_null()) max_len += 1; // NULL-byte
+
+ m_pack_info[dst_i].setup(this, field, keyno_to_set, keypart_to_set,
+ key_part ? key_part->length : 0);
+ m_pack_info[dst_i].m_unpack_data_offset = unpack_len;
+
+ if (pk_info) {
+ m_pk_part_no[dst_i] = -1;
+ for (uint j = 0; j < m_pk_key_parts; j++) {
+ if (field->field_index == pk_info->key_part[j].field->field_index) {
+ m_pk_part_no[dst_i] = j;
+ break;
+ }
+ }
+ } else if (secondary_key && hidden_pk_exists) {
+ /*
+ The hidden pk can never be part of the sk. So it is always
+ appended to the end of the sk.
+ */
+ m_pk_part_no[dst_i] = -1;
+ if (simulating_extkey) m_pk_part_no[dst_i] = 0;
+ }
+
+ max_len += m_pack_info[dst_i].m_max_image_len;
+
+ max_part_len =
+ std::max(max_part_len, m_pack_info[dst_i].m_max_image_len);
+
+ /*
+ Check key part name here, if it matches the TTL column then we store
+ the offset of the TTL key part here.
+ */
+ if (!m_ttl_column.empty() &&
+ field_check_field_name_match(field, m_ttl_column.c_str())) {
+ DBUG_ASSERT(field->real_type() == MYSQL_TYPE_LONGLONG);
+ DBUG_ASSERT(field->key_type() == HA_KEYTYPE_ULONGLONG);
+ DBUG_ASSERT(!field->real_maybe_null());
+ m_ttl_pk_key_part_offset = dst_i;
+ }
+
+ key_part++;
+ /*
+ For "unique" secondary indexes, pretend they have
+ "index extensions".
+
+ MariaDB also has this property: if an index has a partially-covered
+ column like KEY(varchar_col(N)), then the SQL layer will think it is
+ not "extended" with PK columns. The code below handles this case,
+ also.
+ */
+ if (secondary_key && src_i+1 == key_info->ext_key_parts) {
+ simulating_extkey = true;
+ if (!hidden_pk_exists) {
+ keyno_to_set = tbl->s->primary_key;
+ key_part = pk_info->key_part;
+ keypart_to_set = (uint)-1;
+ } else {
+ keyno_to_set = tbl_def->m_key_count - 1;
+ key_part = nullptr;
+ keypart_to_set = 0;
+ }
+ }
+
+ dst_i++;
+ }
+ }
+
+ m_key_parts = dst_i;
+
+ /* Initialize the memory needed by the stats structure */
+ m_stats.m_distinct_keys_per_prefix.resize(get_key_parts());
+
+ /* Cache prefix extractor for bloom filter usage later */
+ rocksdb::Options opt = rdb_get_rocksdb_db()->GetOptions(get_cf());
+ m_prefix_extractor = opt.prefix_extractor;
+
+ /*
+ This should be the last member variable set before releasing the mutex
+ so that other threads can't see the object partially set up.
+ */
+ m_maxlength = max_len;
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+ }
+}
+
+/*
+ Determine if the table has TTL enabled by parsing the table comment.
+
+ @param[IN] table_arg
+ @param[IN] tbl_def_arg
+ @param[OUT] ttl_duration Default TTL value parsed from table comment
+*/
+uint Rdb_key_def::extract_ttl_duration(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ uint64 *ttl_duration) {
+ DBUG_ASSERT(table_arg != nullptr);
+ DBUG_ASSERT(tbl_def_arg != nullptr);
+ DBUG_ASSERT(ttl_duration != nullptr);
+ std::string table_comment(table_arg->s->comment.str,
+ table_arg->s->comment.length);
+
+ bool ttl_duration_per_part_match_found = false;
+ std::string ttl_duration_str = Rdb_key_def::parse_comment_for_qualifier(
+ table_comment, table_arg, tbl_def_arg, &ttl_duration_per_part_match_found,
+ RDB_TTL_DURATION_QUALIFIER);
+
+ /* If we don't have a ttl duration, nothing to do here. */
+ if (ttl_duration_str.empty()) {
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ Catch errors where a non-integral value was used as ttl duration, strtoull
+ will return 0.
+ */
+ *ttl_duration = std::strtoull(ttl_duration_str.c_str(), nullptr, 0);
+ if (!*ttl_duration) {
+ my_error(ER_RDB_TTL_DURATION_FORMAT, MYF(0), ttl_duration_str.c_str());
+ return HA_EXIT_FAILURE;
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Determine if the table has TTL enabled by parsing the table comment.
+
+ @param[IN] table_arg
+ @param[IN] tbl_def_arg
+ @param[OUT] ttl_column TTL column in the table
+ @param[IN] skip_checks Skip validation checks (when called in
+ setup())
+*/
+uint Rdb_key_def::extract_ttl_col(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ std::string *ttl_column,
+ uint *ttl_field_index, bool skip_checks) {
+ std::string table_comment(table_arg->s->comment.str,
+ table_arg->s->comment.length);
+ /*
+ Check if there is a TTL column specified. Note that this is not required
+ and if omitted, an 8-byte ttl field will be prepended to each record
+ implicitly.
+ */
+ bool ttl_col_per_part_match_found = false;
+ std::string ttl_col_str = Rdb_key_def::parse_comment_for_qualifier(
+ table_comment, table_arg, tbl_def_arg, &ttl_col_per_part_match_found,
+ RDB_TTL_COL_QUALIFIER);
+
+ if (skip_checks) {
+ for (uint i = 0; i < table_arg->s->fields; i++) {
+ Field *const field = table_arg->field[i];
+ if (field_check_field_name_match(field, ttl_col_str.c_str())) {
+ *ttl_column = ttl_col_str;
+ *ttl_field_index = i;
+ }
+ }
+ return HA_EXIT_SUCCESS;
+ }
+
+ /* Check if TTL column exists in table */
+ if (!ttl_col_str.empty()) {
+ bool found = false;
+ for (uint i = 0; i < table_arg->s->fields; i++) {
+ Field *const field = table_arg->field[i];
+ if (field_check_field_name_match(field, ttl_col_str.c_str()) &&
+ field->real_type() == MYSQL_TYPE_LONGLONG &&
+ field->key_type() == HA_KEYTYPE_ULONGLONG &&
+ !field->real_maybe_null()) {
+ *ttl_column = ttl_col_str;
+ *ttl_field_index = i;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ my_error(ER_RDB_TTL_COL_FORMAT, MYF(0), ttl_col_str.c_str());
+ return HA_EXIT_FAILURE;
+ }
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+const std::string Rdb_key_def::gen_qualifier_for_table(
+ const char *const qualifier, const std::string &partition_name) {
+ bool has_partition = !partition_name.empty();
+ std::string qualifier_str = "";
+
+ if (!strcmp(qualifier, RDB_CF_NAME_QUALIFIER)) {
+ return has_partition ? gen_cf_name_qualifier_for_partition(partition_name)
+ : qualifier_str + RDB_CF_NAME_QUALIFIER +
+ RDB_QUALIFIER_VALUE_SEP;
+ } else if (!strcmp(qualifier, RDB_TTL_DURATION_QUALIFIER)) {
+ return has_partition
+ ? gen_ttl_duration_qualifier_for_partition(partition_name)
+ : qualifier_str + RDB_TTL_DURATION_QUALIFIER +
+ RDB_QUALIFIER_VALUE_SEP;
+ } else if (!strcmp(qualifier, RDB_TTL_COL_QUALIFIER)) {
+ return has_partition ? gen_ttl_col_qualifier_for_partition(partition_name)
+ : qualifier_str + RDB_TTL_COL_QUALIFIER +
+ RDB_QUALIFIER_VALUE_SEP;
+ } else {
+ DBUG_ASSERT(0);
+ }
+
+ return qualifier_str;
+}
+
+/*
+ Formats the string and returns the column family name assignment part for a
+ specific partition.
+*/
+const std::string Rdb_key_def::gen_cf_name_qualifier_for_partition(
+ const std::string &prefix) {
+ DBUG_ASSERT(!prefix.empty());
+
+ return prefix + RDB_PER_PARTITION_QUALIFIER_NAME_SEP + RDB_CF_NAME_QUALIFIER +
+ RDB_QUALIFIER_VALUE_SEP;
+}
+
+const std::string Rdb_key_def::gen_ttl_duration_qualifier_for_partition(
+ const std::string &prefix) {
+ DBUG_ASSERT(!prefix.empty());
+
+ return prefix + RDB_PER_PARTITION_QUALIFIER_NAME_SEP +
+ RDB_TTL_DURATION_QUALIFIER + RDB_QUALIFIER_VALUE_SEP;
+}
+
+const std::string Rdb_key_def::gen_ttl_col_qualifier_for_partition(
+ const std::string &prefix) {
+ DBUG_ASSERT(!prefix.empty());
+
+ return prefix + RDB_PER_PARTITION_QUALIFIER_NAME_SEP + RDB_TTL_COL_QUALIFIER +
+ RDB_QUALIFIER_VALUE_SEP;
+}
+
+const std::string Rdb_key_def::parse_comment_for_qualifier(
+ const std::string &comment, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg, bool *per_part_match_found,
+ const char *const qualifier) {
+ DBUG_ASSERT(table_arg != nullptr);
+ DBUG_ASSERT(tbl_def_arg != nullptr);
+ DBUG_ASSERT(per_part_match_found != nullptr);
+ DBUG_ASSERT(qualifier != nullptr);
+
+ std::string empty_result;
+
+ // Flag which marks if partition specific options were found.
+ *per_part_match_found = false;
+
+ if (comment.empty()) {
+ return empty_result;
+ }
+
+ // Let's fetch the comment for a index and check if there's a custom key
+ // name specified for a partition we are handling.
+ std::vector<std::string> v =
+ myrocks::parse_into_tokens(comment, RDB_QUALIFIER_SEP);
+
+ std::string search_str = gen_qualifier_for_table(qualifier);
+
+ // If table has partitions then we need to check if user has requested
+ // qualifiers on a per partition basis.
+ //
+ // NOTE: this means if you specify a qualifier for a specific partition it
+ // will take precedence the 'table level' qualifier if one exists.
+ std::string search_str_part;
+ if (IF_PARTITIONING(table_arg->part_info,nullptr) != nullptr) {
+ std::string partition_name = tbl_def_arg->base_partition();
+ DBUG_ASSERT(!partition_name.empty());
+ search_str_part = gen_qualifier_for_table(qualifier, partition_name);
+ }
+
+ DBUG_ASSERT(!search_str.empty());
+
+ // Basic O(N) search for a matching assignment. At most we expect maybe
+ // ten or so elements here.
+ if (!search_str_part.empty()) {
+ for (const auto &it : v) {
+ if (it.substr(0, search_str_part.length()) == search_str_part) {
+ // We found a prefix match. Try to parse it as an assignment.
+ std::vector<std::string> tokens =
+ myrocks::parse_into_tokens(it, RDB_QUALIFIER_VALUE_SEP);
+
+ // We found a custom qualifier, it was in the form we expected it to be.
+ // Return that instead of whatever we initially wanted to return. In
+ // a case below the `foo` part will be returned to the caller.
+ //
+ // p3_cfname=foo
+ //
+ // If no value was specified then we'll return an empty string which
+ // later gets translated into using a default CF.
+ if (tokens.size() == 2) {
+ *per_part_match_found = true;
+ return tokens[1];
+ } else {
+ return empty_result;
+ }
+ }
+ }
+ }
+
+ // Do this loop again, this time searching for 'table level' qualifiers if we
+ // didn't find any partition level qualifiers above.
+ for (const auto &it : v) {
+ if (it.substr(0, search_str.length()) == search_str) {
+ std::vector<std::string> tokens =
+ myrocks::parse_into_tokens(it, RDB_QUALIFIER_VALUE_SEP);
+ if (tokens.size() == 2) {
+ return tokens[1];
+ } else {
+ return empty_result;
+ }
+ }
+ }
+
+ // If we didn't find any partitioned/non-partitioned qualifiers, return an
+ // empty string.
+ return empty_result;
+}
+
+/**
+ Read a memcmp key part from a slice using the passed in reader.
+
+ Returns -1 if field was null, 1 if error, 0 otherwise.
+*/
+int Rdb_key_def::read_memcmp_key_part(const TABLE *table_arg,
+ Rdb_string_reader *reader,
+ const uint part_num) const {
+ /* It is impossible to unpack the column. Skip it. */
+ if (m_pack_info[part_num].m_maybe_null) {
+ const char *nullp;
+ if (!(nullp = reader->read(1))) return 1;
+ if (*nullp == 0) {
+ /* This is a NULL value */
+ return -1;
+ } else {
+ /* If NULL marker is not '0', it can be only '1' */
+ if (*nullp != 1) return 1;
+ }
+ }
+
+ Rdb_field_packing *fpi = &m_pack_info[part_num];
+ DBUG_ASSERT(table_arg->s != nullptr);
+
+ bool is_hidden_pk_part = (part_num + 1 == m_key_parts) &&
+ (table_arg->s->primary_key == MAX_INDEXES);
+ Field *field = nullptr;
+ if (!is_hidden_pk_part) {
+ field = fpi->get_field_in_table(table_arg);
+ }
+ if ((fpi->m_skip_func)(fpi, field, reader)) {
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ Get a mem-comparable form of Primary Key from mem-comparable form of this key
+
+ @param
+ pk_descr Primary Key descriptor
+ key Index tuple from this key in mem-comparable form
+ pk_buffer OUT Put here mem-comparable form of the Primary Key.
+
+ @note
+ It may or may not be possible to restore primary key columns to their
+ mem-comparable form. To handle all cases, this function copies mem-
+ comparable forms directly.
+
+ RocksDB SE supports "Extended keys". This means that PK columns are present
+ at the end of every key. If the key already includes PK columns, then
+ these columns are not present at the end of the key.
+
+ Because of the above, we copy each primary key column.
+
+ @todo
+ If we checked crc32 checksums in this function, we would catch some CRC
+ violations that we currently don't. On the other hand, there is a broader
+ set of queries for which we would check the checksum twice.
+*/
+
+uint Rdb_key_def::get_primary_key_tuple(const TABLE *const table,
+ const Rdb_key_def &pk_descr,
+ const rocksdb::Slice *const key,
+ uchar *const pk_buffer) const {
+ DBUG_ASSERT(table != nullptr);
+ DBUG_ASSERT(key != nullptr);
+ DBUG_ASSERT(m_index_type == Rdb_key_def::INDEX_TYPE_SECONDARY);
+ DBUG_ASSERT(pk_buffer);
+
+ uint size = 0;
+ uchar *buf = pk_buffer;
+ DBUG_ASSERT(m_pk_key_parts);
+
+ /* Put the PK number */
+ rdb_netbuf_store_index(buf, pk_descr.m_index_number);
+ buf += INDEX_NUMBER_SIZE;
+ size += INDEX_NUMBER_SIZE;
+
+ const char *start_offs[MAX_REF_PARTS];
+ const char *end_offs[MAX_REF_PARTS];
+ int pk_key_part;
+ uint i;
+ Rdb_string_reader reader(key);
+
+ // Skip the index number
+ if ((!reader.read(INDEX_NUMBER_SIZE))) return RDB_INVALID_KEY_LEN;
+
+ for (i = 0; i < m_key_parts; i++) {
+ if ((pk_key_part = m_pk_part_no[i]) != -1) {
+ start_offs[pk_key_part] = reader.get_current_ptr();
+ }
+
+ if (read_memcmp_key_part(table, &reader, i) > 0) {
+ return RDB_INVALID_KEY_LEN;
+ }
+
+ if (pk_key_part != -1) {
+ end_offs[pk_key_part] = reader.get_current_ptr();
+ }
+ }
+
+ for (i = 0; i < m_pk_key_parts; i++) {
+ const uint part_size = end_offs[i] - start_offs[i];
+ memcpy(buf, start_offs[i], end_offs[i] - start_offs[i]);
+ buf += part_size;
+ size += part_size;
+ }
+
+ return size;
+}
+
+/**
+ Get a mem-comparable form of Secondary Key from mem-comparable form of this
+ key, without the extended primary key tail.
+
+ @param
+ key Index tuple from this key in mem-comparable form
+ sk_buffer OUT Put here mem-comparable form of the Secondary Key.
+ n_null_fields OUT Put number of null fields contained within sk entry
+*/
+uint Rdb_key_def::get_memcmp_sk_parts(const TABLE *table,
+ const rocksdb::Slice &key,
+ uchar *sk_buffer,
+ uint *n_null_fields) const {
+ DBUG_ASSERT(table != nullptr);
+ DBUG_ASSERT(sk_buffer != nullptr);
+ DBUG_ASSERT(n_null_fields != nullptr);
+ DBUG_ASSERT(m_keyno != table->s->primary_key && !table_has_hidden_pk(table));
+
+ uchar *buf = sk_buffer;
+
+ int res;
+ Rdb_string_reader reader(&key);
+ const char *start = reader.get_current_ptr();
+
+ // Skip the index number
+ if ((!reader.read(INDEX_NUMBER_SIZE))) return RDB_INVALID_KEY_LEN;
+
+ for (uint i = 0; i < table->key_info[m_keyno].user_defined_key_parts; i++) {
+ if ((res = read_memcmp_key_part(table, &reader, i)) > 0) {
+ return RDB_INVALID_KEY_LEN;
+ } else if (res == -1) {
+ (*n_null_fields)++;
+ }
+ }
+
+ uint sk_memcmp_len = reader.get_current_ptr() - start;
+ memcpy(buf, start, sk_memcmp_len);
+ return sk_memcmp_len;
+}
+
+/**
+ Convert index tuple into storage (i.e. mem-comparable) format
+
+ @detail
+ Currently this is done by unpacking into record_buffer and then
+ packing index columns into storage format.
+
+ @param pack_buffer Temporary area for packing varchar columns. Its
+ size is at least max_storage_fmt_length() bytes.
+*/
+
+uint Rdb_key_def::pack_index_tuple(TABLE *const tbl, uchar *const pack_buffer,
+ uchar *const packed_tuple,
+ uchar *const record_buffer,
+ const uchar *const key_tuple,
+ const key_part_map &keypart_map) const {
+ DBUG_ASSERT(tbl != nullptr);
+ DBUG_ASSERT(pack_buffer != nullptr);
+ DBUG_ASSERT(packed_tuple != nullptr);
+ DBUG_ASSERT(key_tuple != nullptr);
+
+ /* We were given a record in KeyTupleFormat. First, save it to record */
+ const uint key_len = calculate_key_len(tbl, m_keyno, key_tuple, keypart_map);
+ key_restore(record_buffer, key_tuple, &tbl->key_info[m_keyno], key_len);
+
+ uint n_used_parts = my_count_bits(keypart_map);
+ if (keypart_map == HA_WHOLE_KEY) n_used_parts = 0; // Full key is used
+
+ /* Then, convert the record into a mem-comparable form */
+ return pack_record(tbl, pack_buffer, record_buffer, packed_tuple, nullptr,
+ false, 0, n_used_parts);
+}
+
+/**
+ @brief
+ Check if "unpack info" data includes checksum.
+
+ @detail
+ This is used only by CHECK TABLE to count the number of rows that have
+ checksums.
+*/
+
+bool Rdb_key_def::unpack_info_has_checksum(const rocksdb::Slice &unpack_info) {
+ size_t size = unpack_info.size();
+ if (size == 0) {
+ return false;
+ }
+ const uchar *ptr = (const uchar *)unpack_info.data();
+
+ // Skip unpack info if present.
+ if (is_unpack_data_tag(ptr[0]) && size >= get_unpack_header_size(ptr[0])) {
+ const uint16 skip_len = rdb_netbuf_to_uint16(ptr + 1);
+ SHIP_ASSERT(size >= skip_len);
+
+ size -= skip_len;
+ ptr += skip_len;
+ }
+
+ return (size == RDB_CHECKSUM_CHUNK_SIZE && ptr[0] == RDB_CHECKSUM_DATA_TAG);
+}
+
+/*
+ @return Number of bytes that were changed
+*/
+int Rdb_key_def::successor(uchar *const packed_tuple, const uint len) {
+ DBUG_ASSERT(packed_tuple != nullptr);
+
+ int changed = 0;
+ uchar *p = packed_tuple + len - 1;
+ for (; p > packed_tuple; p--) {
+ changed++;
+ if (*p != uchar(0xFF)) {
+ *p = *p + 1;
+ break;
+ }
+ *p = '\0';
+ }
+ return changed;
+}
+
+/*
+ @return Number of bytes that were changed
+*/
+int Rdb_key_def::predecessor(uchar *const packed_tuple, const uint len) {
+ DBUG_ASSERT(packed_tuple != nullptr);
+
+ int changed = 0;
+ uchar *p = packed_tuple + len - 1;
+ for (; p > packed_tuple; p--) {
+ changed++;
+ if (*p != uchar(0x00)) {
+ *p = *p - 1;
+ break;
+ }
+ *p = 0xFF;
+ }
+ return changed;
+}
+
+static const std::map<char, size_t> UNPACK_HEADER_SIZES = {
+ {RDB_UNPACK_DATA_TAG, RDB_UNPACK_HEADER_SIZE},
+ {RDB_UNPACK_COVERED_DATA_TAG, RDB_UNPACK_COVERED_HEADER_SIZE}};
+
+/*
+ @return The length in bytes of the header specified by the given tag
+*/
+size_t Rdb_key_def::get_unpack_header_size(char tag) {
+ DBUG_ASSERT(is_unpack_data_tag(tag));
+ return UNPACK_HEADER_SIZES.at(tag);
+}
+
+/*
+ Get a bitmap indicating which varchar columns must be covered for this
+ lookup to be covered. If the bitmap is a subset of the covered bitmap, then
+ the lookup is covered. If it can already be determined that the lookup is
+ not covered, map->bitmap will be set to null.
+ */
+void Rdb_key_def::get_lookup_bitmap(const TABLE *table, MY_BITMAP *map) const {
+ DBUG_ASSERT(map->bitmap == nullptr);
+ bitmap_init(map, nullptr, MAX_REF_PARTS, false);
+ uint curr_bitmap_pos = 0;
+
+ // Indicates which columns in the read set might be covered.
+ MY_BITMAP maybe_covered_bitmap;
+ bitmap_init(&maybe_covered_bitmap, nullptr, table->read_set->n_bits, false);
+
+ for (uint i = 0; i < m_key_parts; i++) {
+ if (table_has_hidden_pk(table) && i + 1 == m_key_parts) {
+ continue;
+ }
+
+ Field *const field = m_pack_info[i].get_field_in_table(table);
+
+ // Columns which are always covered are not stored in the covered bitmap so
+ // we can ignore them here too.
+ if (m_pack_info[i].m_covered &&
+ bitmap_is_set(table->read_set, field->field_index)) {
+ bitmap_set_bit(&maybe_covered_bitmap, field->field_index);
+ continue;
+ }
+
+ switch (field->real_type()) {
+ // This type may be covered depending on the record. If it was requested,
+ // we require the covered bitmap to have this bit set.
+ case MYSQL_TYPE_VARCHAR:
+ if (curr_bitmap_pos < MAX_REF_PARTS) {
+ if (bitmap_is_set(table->read_set, field->field_index)) {
+ bitmap_set_bit(map, curr_bitmap_pos);
+ bitmap_set_bit(&maybe_covered_bitmap, field->field_index);
+ }
+ curr_bitmap_pos++;
+ } else {
+ bitmap_free(&maybe_covered_bitmap);
+ bitmap_free(map);
+ return;
+ }
+ break;
+ // This column is a type which is never covered. If it was requested, we
+ // know this lookup will never be covered.
+ default:
+ if (bitmap_is_set(table->read_set, field->field_index)) {
+ bitmap_free(&maybe_covered_bitmap);
+ bitmap_free(map);
+ return;
+ }
+ break;
+ }
+ }
+
+ // If there are columns which are not covered in the read set, the lookup
+ // can't be covered.
+ if (!bitmap_cmp(table->read_set, &maybe_covered_bitmap)) {
+ bitmap_free(map);
+ }
+ bitmap_free(&maybe_covered_bitmap);
+}
+
+/*
+ Return true if for this secondary index
+ - All of the requested columns are in the index
+ - All values for columns that are prefix-only indexes are shorter or equal
+ in length to the prefix
+ */
+bool Rdb_key_def::covers_lookup(const rocksdb::Slice *const unpack_info,
+ const MY_BITMAP *const lookup_bitmap) const {
+ DBUG_ASSERT(lookup_bitmap != nullptr);
+ if (!use_covered_bitmap_format() || lookup_bitmap->bitmap == nullptr) {
+ return false;
+ }
+
+ Rdb_string_reader unp_reader = Rdb_string_reader::read_or_empty(unpack_info);
+
+ // Check if this unpack_info has a covered_bitmap
+ const char *unpack_header = unp_reader.get_current_ptr();
+ const bool has_covered_unpack_info =
+ unp_reader.remaining_bytes() &&
+ unpack_header[0] == RDB_UNPACK_COVERED_DATA_TAG;
+ if (!has_covered_unpack_info ||
+ !unp_reader.read(RDB_UNPACK_COVERED_HEADER_SIZE)) {
+ return false;
+ }
+
+ MY_BITMAP covered_bitmap;
+ my_bitmap_map covered_bits;
+ bitmap_init(&covered_bitmap, &covered_bits, MAX_REF_PARTS, false);
+ covered_bits = rdb_netbuf_to_uint16((const uchar *)unpack_header +
+ sizeof(RDB_UNPACK_COVERED_DATA_TAG) +
+ RDB_UNPACK_COVERED_DATA_LEN_SIZE);
+
+ return bitmap_is_subset(lookup_bitmap, &covered_bitmap);
+}
+
+/* Indicates that all key parts can be unpacked to cover a secondary lookup */
+bool Rdb_key_def::can_cover_lookup() const {
+ for (uint i = 0; i < m_key_parts; i++) {
+ if (!m_pack_info[i].m_covered) return false;
+ }
+ return true;
+}
+
+uchar *Rdb_key_def::pack_field(Field *const field, Rdb_field_packing *pack_info,
+ uchar *tuple, uchar *const packed_tuple,
+ uchar *const pack_buffer,
+ Rdb_string_writer *const unpack_info,
+ uint *const n_null_fields) const {
+ if (field->real_maybe_null()) {
+ DBUG_ASSERT(is_storage_available(tuple - packed_tuple, 1));
+ if (field->is_real_null()) {
+ /* NULL value. store '\0' so that it sorts before non-NULL values */
+ *tuple++ = 0;
+ /* That's it, don't store anything else */
+ if (n_null_fields) (*n_null_fields)++;
+ return tuple;
+ } else {
+ /* Not a NULL value. Store '1' */
+ *tuple++ = 1;
+ }
+ }
+
+ const bool create_unpack_info =
+ (unpack_info && // we were requested to generate unpack_info
+ pack_info->uses_unpack_info()); // and this keypart uses it
+ Rdb_pack_field_context pack_ctx(unpack_info);
+
+ // Set the offset for methods which do not take an offset as an argument
+ DBUG_ASSERT(
+ is_storage_available(tuple - packed_tuple, pack_info->m_max_image_len));
+
+ (pack_info->m_pack_func)(pack_info, field, pack_buffer, &tuple, &pack_ctx);
+
+ /* Make "unpack info" to be stored in the value */
+ if (create_unpack_info) {
+ (pack_info->m_make_unpack_info_func)(pack_info->m_charset_codec, field,
+ &pack_ctx);
+ }
+
+ return tuple;
+}
+
+/**
+ Get index columns from the record and pack them into mem-comparable form.
+
+ @param
+ tbl Table we're working on
+ record IN Record buffer with fields in table->record format
+ pack_buffer IN Temporary area for packing varchars. The size is
+ at least max_storage_fmt_length() bytes.
+ packed_tuple OUT Key in the mem-comparable form
+ unpack_info OUT Unpack data
+ unpack_info_len OUT Unpack data length
+ n_key_parts Number of keyparts to process. 0 means all of them.
+ n_null_fields OUT Number of key fields with NULL value.
+ ttl_bytes IN Previous ttl bytes from old record for update case or
+ current ttl bytes from just packed primary key/value
+ @detail
+ Some callers do not need the unpack information, they can pass
+ unpack_info=nullptr, unpack_info_len=nullptr.
+
+ @return
+ Length of the packed tuple
+*/
+
+uint Rdb_key_def::pack_record(const TABLE *const tbl, uchar *const pack_buffer,
+ const uchar *const record,
+ uchar *const packed_tuple,
+ Rdb_string_writer *const unpack_info,
+ const bool should_store_row_debug_checksums,
+ const longlong hidden_pk_id, uint n_key_parts,
+ uint *const n_null_fields,
+ const char *const ttl_bytes) const {
+ DBUG_ASSERT(tbl != nullptr);
+ DBUG_ASSERT(pack_buffer != nullptr);
+ DBUG_ASSERT(record != nullptr);
+ DBUG_ASSERT(packed_tuple != nullptr);
+ // Checksums for PKs are made when record is packed.
+ // We should never attempt to make checksum just from PK values
+ DBUG_ASSERT_IMP(should_store_row_debug_checksums,
+ (m_index_type == INDEX_TYPE_SECONDARY));
+
+ uchar *tuple = packed_tuple;
+ size_t unpack_start_pos = size_t(-1);
+ size_t unpack_len_pos = size_t(-1);
+ size_t covered_bitmap_pos = size_t(-1);
+ const bool hidden_pk_exists = table_has_hidden_pk(tbl);
+
+ rdb_netbuf_store_index(tuple, m_index_number);
+ tuple += INDEX_NUMBER_SIZE;
+
+ // If n_key_parts is 0, it means all columns.
+ // The following includes the 'extended key' tail.
+ // The 'extended key' includes primary key. This is done to 'uniqify'
+ // non-unique indexes
+ const bool use_all_columns = n_key_parts == 0 || n_key_parts == MAX_REF_PARTS;
+
+ // If hidden pk exists, but hidden pk wasnt passed in, we can't pack the
+ // hidden key part. So we skip it (its always 1 part).
+ if (hidden_pk_exists && !hidden_pk_id && use_all_columns) {
+ n_key_parts = m_key_parts - 1;
+ } else if (use_all_columns) {
+ n_key_parts = m_key_parts;
+ }
+
+ if (n_null_fields) *n_null_fields = 0;
+
+ // Check if we need a covered bitmap. If it is certain that all key parts are
+ // covering, we don't need one.
+ bool store_covered_bitmap = false;
+ if (unpack_info && use_covered_bitmap_format()) {
+ for (uint i = 0; i < n_key_parts; i++) {
+ if (!m_pack_info[i].m_covered) {
+ store_covered_bitmap = true;
+ break;
+ }
+ }
+ }
+
+ const char tag =
+ store_covered_bitmap ? RDB_UNPACK_COVERED_DATA_TAG : RDB_UNPACK_DATA_TAG;
+
+ if (unpack_info) {
+ unpack_info->clear();
+
+ if (m_index_type == INDEX_TYPE_SECONDARY &&
+ m_total_index_flags_length > 0) {
+ // Reserve space for index flag fields
+ unpack_info->allocate(m_total_index_flags_length);
+
+ // Insert TTL timestamp
+ if (has_ttl() && ttl_bytes) {
+ write_index_flag_field(unpack_info,
+ reinterpret_cast<const uchar *>(ttl_bytes),
+ Rdb_key_def::TTL_FLAG);
+ }
+ }
+
+ unpack_start_pos = unpack_info->get_current_pos();
+ unpack_info->write_uint8(tag);
+ unpack_len_pos = unpack_info->get_current_pos();
+ // we don't know the total length yet, so write a zero
+ unpack_info->write_uint16(0);
+
+ if (store_covered_bitmap) {
+ // Reserve two bytes for the covered bitmap. This will store, for key
+ // parts which are not always covering, whether or not it is covering
+ // for this record.
+ covered_bitmap_pos = unpack_info->get_current_pos();
+ unpack_info->write_uint16(0);
+ }
+ }
+
+ MY_BITMAP covered_bitmap;
+ my_bitmap_map covered_bits;
+ uint curr_bitmap_pos = 0;
+ bitmap_init(&covered_bitmap, &covered_bits, MAX_REF_PARTS, false);
+
+ for (uint i = 0; i < n_key_parts; i++) {
+ // Fill hidden pk id into the last key part for secondary keys for tables
+ // with no pk
+ if (hidden_pk_exists && hidden_pk_id && i + 1 == n_key_parts) {
+ m_pack_info[i].fill_hidden_pk_val(&tuple, hidden_pk_id);
+ break;
+ }
+
+ Field *const field = m_pack_info[i].get_field_in_table(tbl);
+ DBUG_ASSERT(field != nullptr);
+
+ uint field_offset = field->ptr - tbl->record[0];
+ uint null_offset = field->null_offset(tbl->record[0]);
+ bool maybe_null = field->real_maybe_null();
+
+ field->move_field(
+ const_cast<uchar *>(record) + field_offset,
+ maybe_null ? const_cast<uchar *>(record) + null_offset : nullptr,
+ field->null_bit);
+ // WARNING! Don't return without restoring field->ptr and field->null_ptr
+
+ tuple = pack_field(field, &m_pack_info[i], tuple, packed_tuple, pack_buffer,
+ unpack_info, n_null_fields);
+
+ // If this key part is a prefix of a VARCHAR field, check if it's covered.
+ if (store_covered_bitmap && field->real_type() == MYSQL_TYPE_VARCHAR &&
+ !m_pack_info[i].m_covered && curr_bitmap_pos < MAX_REF_PARTS) {
+ size_t data_length = field->data_length();
+ uint16 key_length;
+ if (m_pk_part_no[i] == (uint)-1) {
+ key_length = tbl->key_info[get_keyno()].key_part[i].length;
+ } else {
+ key_length =
+ tbl->key_info[tbl->s->primary_key].key_part[m_pk_part_no[i]].length;
+ }
+
+ if (m_pack_info[i].m_unpack_func != nullptr &&
+ data_length <= key_length) {
+ bitmap_set_bit(&covered_bitmap, curr_bitmap_pos);
+ }
+ curr_bitmap_pos++;
+ }
+
+ // Restore field->ptr and field->null_ptr
+ field->move_field(tbl->record[0] + field_offset,
+ maybe_null ? tbl->record[0] + null_offset : nullptr,
+ field->null_bit);
+ }
+
+ if (unpack_info) {
+ const size_t len = unpack_info->get_current_pos() - unpack_start_pos;
+ DBUG_ASSERT(len <= std::numeric_limits<uint16_t>::max());
+
+ // Don't store the unpack_info if it has only the header (that is, there's
+ // no meaningful content).
+ // Primary Keys are special: for them, store the unpack_info even if it's
+ // empty (provided m_maybe_unpack_info==true, see
+ // ha_rocksdb::convert_record_to_storage_format)
+ if (m_index_type == Rdb_key_def::INDEX_TYPE_SECONDARY) {
+ if (len == get_unpack_header_size(tag) && !covered_bits) {
+ unpack_info->truncate(unpack_start_pos);
+ } else if (store_covered_bitmap) {
+ unpack_info->write_uint16_at(covered_bitmap_pos, covered_bits);
+ }
+ } else {
+ unpack_info->write_uint16_at(unpack_len_pos, len);
+ }
+
+ //
+ // Secondary keys have key and value checksums in the value part
+ // Primary key is a special case (the value part has non-indexed columns),
+ // so the checksums are computed and stored by
+ // ha_rocksdb::convert_record_to_storage_format
+ //
+ if (should_store_row_debug_checksums) {
+ const uint32_t key_crc32 =
+ my_checksum(0, packed_tuple, tuple - packed_tuple);
+ const uint32_t val_crc32 =
+ my_checksum(0, unpack_info->ptr(), unpack_info->get_current_pos());
+
+ unpack_info->write_uint8(RDB_CHECKSUM_DATA_TAG);
+ unpack_info->write_uint32(key_crc32);
+ unpack_info->write_uint32(val_crc32);
+ }
+ }
+
+ DBUG_ASSERT(is_storage_available(tuple - packed_tuple, 0));
+
+ return tuple - packed_tuple;
+}
+
+/**
+ Pack the hidden primary key into mem-comparable form.
+
+ @param
+ tbl Table we're working on
+ hidden_pk_id IN New value to be packed into key
+ packed_tuple OUT Key in the mem-comparable form
+
+ @return
+ Length of the packed tuple
+*/
+
+uint Rdb_key_def::pack_hidden_pk(const longlong hidden_pk_id,
+ uchar *const packed_tuple) const {
+ DBUG_ASSERT(packed_tuple != nullptr);
+
+ uchar *tuple = packed_tuple;
+ rdb_netbuf_store_index(tuple, m_index_number);
+ tuple += INDEX_NUMBER_SIZE;
+ DBUG_ASSERT(m_key_parts == 1);
+ DBUG_ASSERT(is_storage_available(tuple - packed_tuple,
+ m_pack_info[0].m_max_image_len));
+
+ m_pack_info[0].fill_hidden_pk_val(&tuple, hidden_pk_id);
+
+ DBUG_ASSERT(is_storage_available(tuple - packed_tuple, 0));
+ return tuple - packed_tuple;
+}
+
+/*
+ Function of type rdb_index_field_pack_t
+*/
+
+void Rdb_key_def::pack_with_make_sort_key(
+ Rdb_field_packing *const fpi, Field *const field,
+ uchar *const buf MY_ATTRIBUTE((__unused__)), uchar **dst,
+ Rdb_pack_field_context *const pack_ctx MY_ATTRIBUTE((__unused__))) {
+ DBUG_ASSERT(fpi != nullptr);
+ DBUG_ASSERT(field != nullptr);
+ DBUG_ASSERT(dst != nullptr);
+ DBUG_ASSERT(*dst != nullptr);
+
+ const int max_len = fpi->m_max_image_len;
+ MY_BITMAP*old_map;
+
+ old_map= dbug_tmp_use_all_columns(field->table,
+ &field->table->read_set);
+ field->sort_string(*dst, max_len);
+ dbug_tmp_restore_column_map(&field->table->read_set, old_map);
+ *dst += max_len;
+}
+
+/*
+ Compares two keys without unpacking
+
+ @detail
+ @return
+ 0 - Ok. column_index is the index of the first column which is different.
+ -1 if two kes are equal
+ 1 - Data format error.
+*/
+int Rdb_key_def::compare_keys(const rocksdb::Slice *key1,
+ const rocksdb::Slice *key2,
+ std::size_t *const column_index) const {
+ DBUG_ASSERT(key1 != nullptr);
+ DBUG_ASSERT(key2 != nullptr);
+ DBUG_ASSERT(column_index != nullptr);
+
+ // the caller should check the return value and
+ // not rely on column_index being valid
+ *column_index = 0xbadf00d;
+
+ Rdb_string_reader reader1(key1);
+ Rdb_string_reader reader2(key2);
+
+ // Skip the index number
+ if ((!reader1.read(INDEX_NUMBER_SIZE))) return HA_EXIT_FAILURE;
+
+ if ((!reader2.read(INDEX_NUMBER_SIZE))) return HA_EXIT_FAILURE;
+
+ for (uint i = 0; i < m_key_parts; i++) {
+ const Rdb_field_packing *const fpi = &m_pack_info[i];
+ if (fpi->m_maybe_null) {
+ const auto nullp1 = reader1.read(1);
+ const auto nullp2 = reader2.read(1);
+
+ if (nullp1 == nullptr || nullp2 == nullptr) {
+ return HA_EXIT_FAILURE;
+ }
+
+ if (*nullp1 != *nullp2) {
+ *column_index = i;
+ return HA_EXIT_SUCCESS;
+ }
+
+ if (*nullp1 == 0) {
+ /* This is a NULL value */
+ continue;
+ }
+ }
+
+ const auto before_skip1 = reader1.get_current_ptr();
+ const auto before_skip2 = reader2.get_current_ptr();
+ DBUG_ASSERT(fpi->m_skip_func);
+ if ((fpi->m_skip_func)(fpi, nullptr, &reader1)) {
+ return HA_EXIT_FAILURE;
+ }
+ if ((fpi->m_skip_func)(fpi, nullptr, &reader2)) {
+ return HA_EXIT_FAILURE;
+ }
+ const auto size1 = reader1.get_current_ptr() - before_skip1;
+ const auto size2 = reader2.get_current_ptr() - before_skip2;
+ if (size1 != size2) {
+ *column_index = i;
+ return HA_EXIT_SUCCESS;
+ }
+
+ if (memcmp(before_skip1, before_skip2, size1) != 0) {
+ *column_index = i;
+ return HA_EXIT_SUCCESS;
+ }
+ }
+
+ *column_index = m_key_parts;
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ @brief
+ Given a zero-padded key, determine its real key length
+
+ @detail
+ Fixed-size skip functions just read.
+*/
+
+size_t Rdb_key_def::key_length(const TABLE *const table,
+ const rocksdb::Slice &key) const {
+ DBUG_ASSERT(table != nullptr);
+
+ Rdb_string_reader reader(&key);
+
+ if ((!reader.read(INDEX_NUMBER_SIZE))) {
+ return size_t(-1);
+ }
+ for (uint i = 0; i < m_key_parts; i++) {
+ const Rdb_field_packing *fpi = &m_pack_info[i];
+ const Field *field = nullptr;
+ if (m_index_type != INDEX_TYPE_HIDDEN_PRIMARY) {
+ field = fpi->get_field_in_table(table);
+ }
+ if ((fpi->m_skip_func)(fpi, field, &reader)) {
+ return size_t(-1);
+ }
+ }
+ return key.size() - reader.remaining_bytes();
+}
+
+/*
+ Take mem-comparable form and unpack_info and unpack it to Table->record
+
+ @detail
+ not all indexes support this
+
+ @return
+ HA_EXIT_SUCCESS OK
+ other HA_ERR error code
+*/
+
+int Rdb_key_def::unpack_record(TABLE *const table, uchar *const buf,
+ const rocksdb::Slice *const packed_key,
+ const rocksdb::Slice *const unpack_info,
+ const bool verify_row_debug_checksums) const {
+ Rdb_string_reader reader(packed_key);
+ Rdb_string_reader unp_reader = Rdb_string_reader::read_or_empty(unpack_info);
+
+ // There is no checksuming data after unpack_info for primary keys, because
+ // the layout there is different. The checksum is verified in
+ // ha_rocksdb::convert_record_from_storage_format instead.
+ DBUG_ASSERT_IMP(!(m_index_type == INDEX_TYPE_SECONDARY),
+ !verify_row_debug_checksums);
+
+ // Skip the index number
+ if ((!reader.read(INDEX_NUMBER_SIZE))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+
+ // For secondary keys, we expect the value field to contain index flags,
+ // unpack data, and checksum data in that order. One or all can be missing,
+ // but they cannot be reordered.
+ if (unp_reader.remaining_bytes()) {
+ if (m_index_type == INDEX_TYPE_SECONDARY &&
+ m_total_index_flags_length > 0 &&
+ !unp_reader.read(m_total_index_flags_length)) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ }
+
+ const char *unpack_header = unp_reader.get_current_ptr();
+ bool has_unpack_info =
+ unp_reader.remaining_bytes() && is_unpack_data_tag(unpack_header[0]);
+ if (has_unpack_info) {
+ if (!unp_reader.read(get_unpack_header_size(unpack_header[0]))) {
+ return HA_ERR_ROCKSDB_CORRUPT_DATA;
+ }
+ }
+
+ // Read the covered bitmap
+ MY_BITMAP covered_bitmap;
+ my_bitmap_map covered_bits;
+ bool has_covered_bitmap =
+ has_unpack_info && (unpack_header[0] == RDB_UNPACK_COVERED_DATA_TAG);
+ if (has_covered_bitmap) {
+ bitmap_init(&covered_bitmap, &covered_bits, MAX_REF_PARTS, false);
+ covered_bits = rdb_netbuf_to_uint16((const uchar *)unpack_header +
+ sizeof(RDB_UNPACK_COVERED_DATA_TAG) +
+ RDB_UNPACK_COVERED_DATA_LEN_SIZE);
+ }
+
+ int err = HA_EXIT_SUCCESS;
+
+
+ Rdb_key_field_iterator iter(
+ this, m_pack_info, &reader, &unp_reader, table, has_unpack_info,
+ has_covered_bitmap ? &covered_bitmap : nullptr, buf);
+ while (iter.has_next()) {
+ err = iter.next();
+ if (err) {
+ return err;
+ }
+ }
+
+ /*
+ Check checksum values if present
+ */
+ const char *ptr;
+ if ((ptr = unp_reader.read(1)) && *ptr == RDB_CHECKSUM_DATA_TAG) {
+ if (verify_row_debug_checksums) {
+ uint32_t stored_key_chksum = rdb_netbuf_to_uint32(
+ (const uchar *)unp_reader.read(RDB_CHECKSUM_SIZE));
+ const uint32_t stored_val_chksum = rdb_netbuf_to_uint32(
+ (const uchar *)unp_reader.read(RDB_CHECKSUM_SIZE));
+
+ const uint32_t computed_key_chksum =
+ my_checksum(0, packed_key->data(), packed_key->size());
+ const uint32_t computed_val_chksum =
+ my_checksum(0, unpack_info->data(),
+ unpack_info->size() - RDB_CHECKSUM_CHUNK_SIZE);
+
+ DBUG_EXECUTE_IF("myrocks_simulate_bad_key_checksum1",
+ stored_key_chksum++;);
+
+ if (stored_key_chksum != computed_key_chksum) {
+ report_checksum_mismatch(true, packed_key->data(), packed_key->size());
+ return HA_ERR_ROCKSDB_CHECKSUM_MISMATCH;
+ }
+
+ if (stored_val_chksum != computed_val_chksum) {
+ report_checksum_mismatch(false, unpack_info->data(),
+ unpack_info->size() - RDB_CHECKSUM_CHUNK_SIZE);
+ return HA_ERR_ROCKSDB_CHECKSUM_MISMATCH;
+ }
+ } else {
+ /* The checksums are present but we are not checking checksums */
+ }
+ }
+
+ if (reader.remaining_bytes()) return HA_ERR_ROCKSDB_CORRUPT_DATA;
+
+ return HA_EXIT_SUCCESS;
+}
+
+bool Rdb_key_def::table_has_hidden_pk(const TABLE *const table) {
+ return table->s->primary_key == MAX_INDEXES;
+}
+
+void Rdb_key_def::report_checksum_mismatch(const bool is_key,
+ const char *const data,
+ const size_t data_size) const {
+ // NO_LINT_DEBUG
+ sql_print_error("Checksum mismatch in %s of key-value pair for index 0x%x",
+ is_key ? "key" : "value", get_index_number());
+
+ const std::string buf = rdb_hexdump(data, data_size, RDB_MAX_HEXDUMP_LEN);
+ // NO_LINT_DEBUG
+ sql_print_error("Data with incorrect checksum (%" PRIu64 " bytes): %s",
+ (uint64_t)data_size, buf.c_str());
+
+ my_error(ER_INTERNAL_ERROR, MYF(0), "Record checksum mismatch");
+}
+
+bool Rdb_key_def::index_format_min_check(const int pk_min,
+ const int sk_min) const {
+ switch (m_index_type) {
+ case INDEX_TYPE_PRIMARY:
+ case INDEX_TYPE_HIDDEN_PRIMARY:
+ return (m_kv_format_version >= pk_min);
+ case INDEX_TYPE_SECONDARY:
+ return (m_kv_format_version >= sk_min);
+ default:
+ DBUG_ASSERT(0);
+ return false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// Rdb_field_packing
+///////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ Function of type rdb_index_field_skip_t
+*/
+
+int Rdb_key_def::skip_max_length(const Rdb_field_packing *const fpi,
+ const Field *const field
+ MY_ATTRIBUTE((__unused__)),
+ Rdb_string_reader *const reader) {
+ if (!reader->read(fpi->m_max_image_len)) return HA_EXIT_FAILURE;
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ (RDB_ESCAPE_LENGTH-1) must be an even number so that pieces of lines are not
+ split in the middle of an UTF-8 character. See the implementation of
+ unpack_binary_or_utf8_varchar.
+*/
+#define RDB_ESCAPE_LENGTH 9
+#define RDB_LEGACY_ESCAPE_LENGTH RDB_ESCAPE_LENGTH
+static_assert((RDB_ESCAPE_LENGTH - 1) % 2 == 0,
+ "RDB_ESCAPE_LENGTH-1 must be even.");
+
+#define RDB_ENCODED_SIZE(len) \
+ ((len + (RDB_ESCAPE_LENGTH - 2)) / (RDB_ESCAPE_LENGTH - 1)) * \
+ RDB_ESCAPE_LENGTH
+
+#define RDB_LEGACY_ENCODED_SIZE(len) \
+ ((len + (RDB_LEGACY_ESCAPE_LENGTH - 1)) / (RDB_LEGACY_ESCAPE_LENGTH - 1)) * \
+ RDB_LEGACY_ESCAPE_LENGTH
+
+/*
+ Function of type rdb_index_field_skip_t
+*/
+
+int Rdb_key_def::skip_variable_length(const Rdb_field_packing *const fpi,
+ const Field *const field,
+ Rdb_string_reader *const reader) {
+ const uchar *ptr;
+ bool finished = false;
+
+ size_t dst_len; /* How much data can be there */
+ if (field) {
+ const Field_varstring *const field_var =
+ static_cast<const Field_varstring *>(field);
+ dst_len = field_var->pack_length() - field_var->length_bytes;
+ } else {
+ dst_len = UINT_MAX;
+ }
+
+ bool use_legacy_format = fpi->m_use_legacy_varbinary_format;
+
+ /* Decode the length-emitted encoding here */
+ while ((ptr = (const uchar *)reader->read(RDB_ESCAPE_LENGTH))) {
+ uint used_bytes;
+
+ /* See pack_with_varchar_encoding. */
+ if (use_legacy_format) {
+ used_bytes = calc_unpack_legacy_variable_format(
+ ptr[RDB_ESCAPE_LENGTH - 1], &finished);
+ } else {
+ used_bytes =
+ calc_unpack_variable_format(ptr[RDB_ESCAPE_LENGTH - 1], &finished);
+ }
+
+ if (used_bytes == (uint)-1 || dst_len < used_bytes) {
+ return HA_EXIT_FAILURE; // Corruption in the data
+ }
+
+ if (finished) {
+ break;
+ }
+
+ dst_len -= used_bytes;
+ }
+
+ if (!finished) {
+ return HA_EXIT_FAILURE;
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+const int VARCHAR_CMP_LESS_THAN_SPACES = 1;
+const int VARCHAR_CMP_EQUAL_TO_SPACES = 2;
+const int VARCHAR_CMP_GREATER_THAN_SPACES = 3;
+
+/*
+ Skip a keypart that uses Variable-Length Space-Padded encoding
+*/
+
+int Rdb_key_def::skip_variable_space_pad(const Rdb_field_packing *const fpi,
+ const Field *const field,
+ Rdb_string_reader *const reader) {
+ const uchar *ptr;
+ bool finished = false;
+
+ size_t dst_len = UINT_MAX; /* How much data can be there */
+
+ if (field) {
+ const Field_varstring *const field_var =
+ static_cast<const Field_varstring *>(field);
+ dst_len = field_var->pack_length() - field_var->length_bytes;
+ }
+
+ /* Decode the length-emitted encoding here */
+ while ((ptr = (const uchar *)reader->read(fpi->m_segment_size))) {
+ // See pack_with_varchar_space_pad
+ const uchar c = ptr[fpi->m_segment_size - 1];
+ if (c == VARCHAR_CMP_EQUAL_TO_SPACES) {
+ // This is the last segment
+ finished = true;
+ break;
+ } else if (c == VARCHAR_CMP_LESS_THAN_SPACES ||
+ c == VARCHAR_CMP_GREATER_THAN_SPACES) {
+ // This is not the last segment
+ if ((fpi->m_segment_size - 1) > dst_len) {
+ // The segment is full of data but the table field can't hold that
+ // much! This must be data corruption.
+ return HA_EXIT_FAILURE;
+ }
+ dst_len -= (fpi->m_segment_size - 1);
+ } else {
+ // Encountered a value that's none of the VARCHAR_CMP* constants
+ // It's data corruption.
+ return HA_EXIT_FAILURE;
+ }
+ }
+ return finished ? HA_EXIT_SUCCESS : HA_EXIT_FAILURE;
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+*/
+
+int Rdb_key_def::unpack_integer(
+ Rdb_field_packing *const fpi, Field *const field, uchar *const to,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ const int length = fpi->m_max_image_len;
+
+ const uchar *from;
+ if (!(from = (const uchar *)reader->read(length))) {
+ return UNPACK_FAILURE; /* Mem-comparable image doesn't have enough bytes */
+ }
+
+#ifdef WORDS_BIGENDIAN
+ {
+ if (static_cast<Field_num *>(field)->unsigned_flag) {
+ to[0] = from[0];
+ } else {
+ to[0] = static_cast<char>(from[0] ^ 128); // Reverse the sign bit.
+ }
+ memcpy(to + 1, from + 1, length - 1);
+ }
+#else
+ {
+ const int sign_byte = from[0];
+ if (static_cast<Field_num *>(field)->unsigned_flag) {
+ to[length - 1] = sign_byte;
+ } else {
+ to[length - 1] =
+ static_cast<char>(sign_byte ^ 128); // Reverse the sign bit.
+ }
+ for (int i = 0, j = length - 1; i < length - 1; ++i, --j) to[i] = from[j];
+ }
+#endif
+ return UNPACK_SUCCESS;
+}
+
+#if !defined(WORDS_BIGENDIAN)
+static void rdb_swap_double_bytes(uchar *const dst, const uchar *const src) {
+#if defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)
+ // A few systems store the most-significant _word_ first on little-endian
+ dst[0] = src[3];
+ dst[1] = src[2];
+ dst[2] = src[1];
+ dst[3] = src[0];
+ dst[4] = src[7];
+ dst[5] = src[6];
+ dst[6] = src[5];
+ dst[7] = src[4];
+#else
+ dst[0] = src[7];
+ dst[1] = src[6];
+ dst[2] = src[5];
+ dst[3] = src[4];
+ dst[4] = src[3];
+ dst[5] = src[2];
+ dst[6] = src[1];
+ dst[7] = src[0];
+#endif
+}
+
+static void rdb_swap_float_bytes(uchar *const dst, const uchar *const src) {
+ dst[0] = src[3];
+ dst[1] = src[2];
+ dst[2] = src[1];
+ dst[3] = src[0];
+}
+#else
+#define rdb_swap_double_bytes nullptr
+#define rdb_swap_float_bytes nullptr
+#endif
+
+int Rdb_key_def::unpack_floating_point(
+ uchar *const dst, Rdb_string_reader *const reader, const size_t size,
+ const int exp_digit, const uchar *const zero_pattern,
+ const uchar *const zero_val, void (*swap_func)(uchar *, const uchar *)) {
+ const uchar *const from = (const uchar *)reader->read(size);
+ if (from == nullptr) {
+ /* Mem-comparable image doesn't have enough bytes */
+ return UNPACK_FAILURE;
+ }
+
+ /* Check to see if the value is zero */
+ if (memcmp(from, zero_pattern, size) == 0) {
+ memcpy(dst, zero_val, size);
+ return UNPACK_SUCCESS;
+ }
+
+#if defined(WORDS_BIGENDIAN)
+ // On big-endian, output can go directly into result
+ uchar *const tmp = dst;
+#else
+ // Otherwise use a temporary buffer to make byte-swapping easier later
+ uchar tmp[8];
+#endif
+
+ memcpy(tmp, from, size);
+
+ if (tmp[0] & 0x80) {
+ // If the high bit is set the original value was positive so
+ // remove the high bit and subtract one from the exponent.
+ ushort exp_part = ((ushort)tmp[0] << 8) | (ushort)tmp[1];
+ exp_part &= 0x7FFF; // clear high bit;
+ exp_part -= (ushort)1 << (16 - 1 - exp_digit); // subtract from exponent
+ tmp[0] = (uchar)(exp_part >> 8);
+ tmp[1] = (uchar)exp_part;
+ } else {
+ // Otherwise the original value was negative and all bytes have been
+ // negated.
+ for (size_t ii = 0; ii < size; ii++) tmp[ii] ^= 0xFF;
+ }
+
+#if !defined(WORDS_BIGENDIAN)
+ // On little-endian, swap the bytes around
+ swap_func(dst, tmp);
+#else
+ DBUG_ASSERT(swap_func == nullptr);
+#endif
+
+ return UNPACK_SUCCESS;
+}
+
+#if !defined(DBL_EXP_DIG)
+#define DBL_EXP_DIG (sizeof(double) * 8 - DBL_MANT_DIG)
+#endif
+
+/*
+ Function of type rdb_index_field_unpack_t
+
+ Unpack a double by doing the reverse action of change_double_for_sort
+ (sql/filesort.cc). Note that this only works on IEEE values.
+ Note also that this code assumes that NaN and +/-Infinity are never
+ allowed in the database.
+*/
+int Rdb_key_def::unpack_double(
+ Rdb_field_packing *const fpi MY_ATTRIBUTE((__unused__)),
+ Field *const field MY_ATTRIBUTE((__unused__)), uchar *const field_ptr,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ static double zero_val = 0.0;
+ static const uchar zero_pattern[8] = {128, 0, 0, 0, 0, 0, 0, 0};
+
+ return unpack_floating_point(field_ptr, reader, sizeof(double), DBL_EXP_DIG,
+ zero_pattern, (const uchar *)&zero_val,
+ rdb_swap_double_bytes);
+}
+
+#if !defined(FLT_EXP_DIG)
+#define FLT_EXP_DIG (sizeof(float) * 8 - FLT_MANT_DIG)
+#endif
+
+/*
+ Function of type rdb_index_field_unpack_t
+
+ Unpack a float by doing the reverse action of Field_float::make_sort_key
+ (sql/field.cc). Note that this only works on IEEE values.
+ Note also that this code assumes that NaN and +/-Infinity are never
+ allowed in the database.
+*/
+int Rdb_key_def::unpack_float(
+ Rdb_field_packing *const fpi, Field *const field MY_ATTRIBUTE((__unused__)),
+ uchar *const field_ptr, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ static float zero_val = 0.0;
+ static const uchar zero_pattern[4] = {128, 0, 0, 0};
+
+ return unpack_floating_point(field_ptr, reader, sizeof(float), FLT_EXP_DIG,
+ zero_pattern, (const uchar *)&zero_val,
+ rdb_swap_float_bytes);
+}
+
+/*
+ Function of type rdb_index_field_unpack_t used to
+ Unpack by doing the reverse action to Field_newdate::make_sort_key.
+*/
+
+int Rdb_key_def::unpack_newdate(
+ Rdb_field_packing *const fpi, Field *const field MY_ATTRIBUTE((__unused__)),
+ uchar *const field_ptr, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ const char *from;
+ DBUG_ASSERT(fpi->m_max_image_len == 3);
+
+ if (!(from = reader->read(3))) {
+ /* Mem-comparable image doesn't have enough bytes */
+ return UNPACK_FAILURE;
+ }
+
+ field_ptr[0] = from[2];
+ field_ptr[1] = from[1];
+ field_ptr[2] = from[0];
+ return UNPACK_SUCCESS;
+}
+
+/*
+ Function of type rdb_index_field_unpack_t, used to
+ Unpack the string by copying it over.
+ This is for BINARY(n) where the value occupies the whole length.
+*/
+
+int Rdb_key_def::unpack_binary_str(
+ Rdb_field_packing *const fpi, Field *const field, uchar *const to,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ const char *from;
+ if (!(from = reader->read(fpi->m_max_image_len))) {
+ /* Mem-comparable image doesn't have enough bytes */
+ return UNPACK_FAILURE;
+ }
+
+ memcpy(to, from, fpi->m_max_image_len);
+ return UNPACK_SUCCESS;
+}
+
+/*
+ Function of type rdb_index_field_unpack_t.
+ For UTF-8, we need to convert 2-byte wide-character entities back into
+ UTF8 sequences.
+*/
+
+int Rdb_key_def::unpack_utf8_str(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ my_core::CHARSET_INFO *const cset = (my_core::CHARSET_INFO *)field->charset();
+ const uchar *src;
+ if (!(src = (const uchar *)reader->read(fpi->m_max_image_len))) {
+ /* Mem-comparable image doesn't have enough bytes */
+ return UNPACK_FAILURE;
+ }
+
+ const uchar *const src_end = src + fpi->m_max_image_len;
+ uchar *const dst_end = dst + field->pack_length();
+
+ while (src < src_end) {
+ my_wc_t wc = (src[0] << 8) | src[1];
+ src += 2;
+ int res = cset->wc_mb(wc, dst, dst_end);
+ DBUG_ASSERT(res > 0 && res <= 3);
+ if (res < 0) return UNPACK_FAILURE;
+ dst += res;
+ }
+
+ cset->fill(reinterpret_cast<char *>(dst), dst_end - dst,
+ cset->pad_char);
+ return UNPACK_SUCCESS;
+}
+
+/*
+ This is the original algorithm to encode a variable binary field. It
+ sets a flag byte every Nth byte. The flag value is (255 - #pad) where
+ #pad is the number of padding bytes that were needed (0 if all N-1
+ bytes were used).
+
+ If N=8 and the field is:
+ * 3 bytes (1, 2, 3) this is encoded as: 1, 2, 3, 0, 0, 0, 0, 251
+ * 4 bytes (1, 2, 3, 0) this is encoded as: 1, 2, 3, 0, 0, 0, 0, 252
+ And the 4 byte string compares as greater than the 3 byte string
+
+ Unfortunately the algorithm has a flaw. If the input is exactly a
+ multiple of N-1, an extra N bytes are written. Since we usually use
+ N=9, an 8 byte input will generate 18 bytes of output instead of the
+ 9 bytes of output that is optimal.
+
+ See pack_variable_format for the newer algorithm.
+*/
+void Rdb_key_def::pack_legacy_variable_format(
+ const uchar *src, // The data to encode
+ size_t src_len, // The length of the data to encode
+ uchar **dst) // The location to encode the data
+{
+ size_t copy_len;
+ size_t padding_bytes;
+ uchar *ptr = *dst;
+
+ do {
+ copy_len = std::min((size_t)RDB_LEGACY_ESCAPE_LENGTH - 1, src_len);
+ padding_bytes = RDB_LEGACY_ESCAPE_LENGTH - 1 - copy_len;
+ memcpy(ptr, src, copy_len);
+ ptr += copy_len;
+ src += copy_len;
+ // pad with zeros if necessary
+ if (padding_bytes > 0) {
+ memset(ptr, 0, padding_bytes);
+ ptr += padding_bytes;
+ }
+
+ *(ptr++) = 255 - padding_bytes;
+
+ src_len -= copy_len;
+ } while (padding_bytes == 0);
+
+ *dst = ptr;
+}
+
+/*
+ This is the new algorithm. Similarly to the legacy format the input
+ is split up into N-1 bytes and a flag byte is used as the Nth byte
+ in the output.
+
+ - If the previous segment needed any padding the flag is set to the
+ number of bytes used (0..N-2). 0 is possible in the first segment
+ if the input is 0 bytes long.
+ - If no padding was used and there is no more data left in the input
+ the flag is set to N-1
+ - If no padding was used and there is still data left in the input the
+ flag is set to N.
+
+ For N=9, the following input values encode to the specified
+ outout (where 'X' indicates a byte of the original input):
+ - 0 bytes is encoded as 0 0 0 0 0 0 0 0 0
+ - 1 byte is encoded as X 0 0 0 0 0 0 0 1
+ - 2 bytes is encoded as X X 0 0 0 0 0 0 2
+ - 7 bytes is encoded as X X X X X X X 0 7
+ - 8 bytes is encoded as X X X X X X X X 8
+ - 9 bytes is encoded as X X X X X X X X 9 X 0 0 0 0 0 0 0 1
+ - 10 bytes is encoded as X X X X X X X X 9 X X 0 0 0 0 0 0 2
+*/
+void Rdb_key_def::pack_variable_format(
+ const uchar *src, // The data to encode
+ size_t src_len, // The length of the data to encode
+ uchar **dst) // The location to encode the data
+{
+ uchar *ptr = *dst;
+
+ for (;;) {
+ // Figure out how many bytes to copy, copy them and adjust pointers
+ const size_t copy_len = std::min((size_t)RDB_ESCAPE_LENGTH - 1, src_len);
+ memcpy(ptr, src, copy_len);
+ ptr += copy_len;
+ src += copy_len;
+ src_len -= copy_len;
+
+ // Are we at the end of the input?
+ if (src_len == 0) {
+ // pad with zeros if necessary;
+ const size_t padding_bytes = RDB_ESCAPE_LENGTH - 1 - copy_len;
+ if (padding_bytes > 0) {
+ memset(ptr, 0, padding_bytes);
+ ptr += padding_bytes;
+ }
+
+ // Put the flag byte (0 - N-1) in the output
+ *(ptr++) = (uchar)copy_len;
+ break;
+ }
+
+ // We have more data - put the flag byte (N) in and continue
+ *(ptr++) = RDB_ESCAPE_LENGTH;
+ }
+
+ *dst = ptr;
+}
+
+/*
+ Function of type rdb_index_field_pack_t
+*/
+
+void Rdb_key_def::pack_with_varchar_encoding(
+ Rdb_field_packing *const fpi, Field *const field, uchar *buf, uchar **dst,
+ Rdb_pack_field_context *const pack_ctx MY_ATTRIBUTE((__unused__))) {
+ const CHARSET_INFO *const charset = field->charset();
+ Field_varstring *const field_var = (Field_varstring *)field;
+
+ const size_t value_length = (field_var->length_bytes == 1)
+ ? (uint)*field->ptr
+ : uint2korr(field->ptr);
+ size_t xfrm_len = charset->strnxfrm(
+ buf, fpi->m_max_image_len, field_var->char_length(),
+ field_var->ptr + field_var->length_bytes, value_length, 0);
+
+ /* Got a mem-comparable image in 'buf'. Now, produce varlength encoding */
+ if (fpi->m_use_legacy_varbinary_format) {
+ pack_legacy_variable_format(buf, xfrm_len, dst);
+ } else {
+ pack_variable_format(buf, xfrm_len, dst);
+ }
+}
+
+/*
+ Compare the string in [buf..buf_end) with a string that is an infinite
+ sequence of strings in space_xfrm
+*/
+
+static int rdb_compare_string_with_spaces(
+ const uchar *buf, const uchar *const buf_end,
+ const std::vector<uchar> *const space_xfrm) {
+ int cmp = 0;
+ while (buf < buf_end) {
+ size_t bytes = std::min((size_t)(buf_end - buf), space_xfrm->size());
+ if ((cmp = memcmp(buf, space_xfrm->data(), bytes)) != 0) break;
+ buf += bytes;
+ }
+ return cmp;
+}
+
+static const int RDB_TRIMMED_CHARS_OFFSET = 8;
+/*
+ Pack the data with Variable-Length Space-Padded Encoding.
+
+ The encoding is there to meet two goals:
+
+ Goal#1. Comparison. The SQL standard says
+
+ " If the collation for the comparison has the PAD SPACE characteristic,
+ for the purposes of the comparison, the shorter value is effectively
+ extended to the length of the longer by concatenation of <space>s on the
+ right.
+
+ At the moment, all MySQL collations except one have the PAD SPACE
+ characteristic. The exception is the "binary" collation that is used by
+ [VAR]BINARY columns. (Note that binary collations for specific charsets,
+ like utf8_bin or latin1_bin are not the same as "binary" collation, they have
+ the PAD SPACE characteristic).
+
+ Goal#2 is to preserve the number of trailing spaces in the original value.
+
+ This is achieved by using the following encoding:
+ The key part:
+ - Stores mem-comparable image of the column
+ - It is stored in chunks of fpi->m_segment_size bytes (*)
+ = If the remainder of the chunk is not occupied, it is padded with mem-
+ comparable image of the space character (cs->pad_char to be precise).
+ - The last byte of the chunk shows how the rest of column's mem-comparable
+ image would compare to mem-comparable image of the column extended with
+ spaces. There are three possible values.
+ - VARCHAR_CMP_LESS_THAN_SPACES,
+ - VARCHAR_CMP_EQUAL_TO_SPACES
+ - VARCHAR_CMP_GREATER_THAN_SPACES
+
+ VARCHAR_CMP_EQUAL_TO_SPACES means that this chunk is the last one (the rest
+ is spaces, or something that sorts as spaces, so there is no reason to store
+ it).
+
+ Example: if fpi->m_segment_size=5, and the collation is latin1_bin:
+
+ 'abcd\0' => [ 'abcd' <VARCHAR_CMP_LESS> ]['\0 ' <VARCHAR_CMP_EQUAL> ]
+ 'abcd' => [ 'abcd' <VARCHAR_CMP_EQUAL>]
+ 'abcd ' => [ 'abcd' <VARCHAR_CMP_EQUAL>]
+ 'abcdZZZZ' => [ 'abcd' <VARCHAR_CMP_GREATER>][ 'ZZZZ' <VARCHAR_CMP_EQUAL>]
+
+ As mentioned above, the last chunk is padded with mem-comparable images of
+ cs->pad_char. It can be 1-byte long (latin1), 2 (utf8_bin), 3 (utf8mb4), etc.
+
+ fpi->m_segment_size depends on the used collation. It is chosen to be such
+ that no mem-comparable image of space will ever stretch across the segments
+ (see get_segment_size_from_collation).
+
+ == The value part (aka unpack_info) ==
+ The value part stores the number of space characters that one needs to add
+ when unpacking the string.
+ - If the number is positive, it means add this many spaces at the end
+ - If the number is negative, it means padding has added extra spaces which
+ must be removed.
+
+ Storage considerations
+ - depending on column's max size, the number may occupy 1 or 2 bytes
+ - the number of spaces that need to be removed is not more than
+ RDB_TRIMMED_CHARS_OFFSET=8, so we offset the number by that value and
+ then store it as unsigned.
+
+ @seealso
+ unpack_binary_or_utf8_varchar_space_pad
+ unpack_simple_varchar_space_pad
+ dummy_make_unpack_info
+ skip_variable_space_pad
+*/
+
+void Rdb_key_def::pack_with_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *buf, uchar **dst,
+ Rdb_pack_field_context *const pack_ctx) {
+ Rdb_string_writer *const unpack_info = pack_ctx->writer;
+ const CHARSET_INFO *const charset = field->charset();
+ const auto field_var = static_cast<Field_varstring *>(field);
+
+ const size_t value_length = (field_var->length_bytes == 1)
+ ? (uint)*field->ptr
+ : uint2korr(field->ptr);
+
+ const size_t trimmed_len = charset->lengthsp(
+ (const char *)field_var->ptr + field_var->length_bytes,
+ value_length);
+ const size_t xfrm_len = charset->strnxfrm(
+ buf, fpi->m_max_image_len, field_var->char_length(),
+ field_var->ptr + field_var->length_bytes, trimmed_len, 0);
+
+ /* Got a mem-comparable image in 'buf'. Now, produce varlength encoding */
+ uchar *const buf_end = buf + xfrm_len;
+
+ size_t encoded_size = 0;
+ uchar *ptr = *dst;
+ size_t padding_bytes;
+ while (true) {
+ const size_t copy_len =
+ std::min<size_t>(fpi->m_segment_size - 1, buf_end - buf);
+ padding_bytes = fpi->m_segment_size - 1 - copy_len;
+ memcpy(ptr, buf, copy_len);
+ ptr += copy_len;
+ buf += copy_len;
+
+ if (padding_bytes) {
+ memcpy(ptr, fpi->space_xfrm->data(), padding_bytes);
+ ptr += padding_bytes;
+ *ptr = VARCHAR_CMP_EQUAL_TO_SPACES; // last segment
+ } else {
+ // Compare the string suffix with a hypothetical infinite string of
+ // spaces. It could be that the first difference is beyond the end of
+ // current chunk.
+ const int cmp =
+ rdb_compare_string_with_spaces(buf, buf_end, fpi->space_xfrm);
+
+ if (cmp < 0) {
+ *ptr = VARCHAR_CMP_LESS_THAN_SPACES;
+ } else if (cmp > 0) {
+ *ptr = VARCHAR_CMP_GREATER_THAN_SPACES;
+ } else {
+ // It turns out all the rest are spaces.
+ *ptr = VARCHAR_CMP_EQUAL_TO_SPACES;
+ }
+ }
+ encoded_size += fpi->m_segment_size;
+
+ if (*(ptr++) == VARCHAR_CMP_EQUAL_TO_SPACES) break;
+ }
+
+ // m_unpack_info_stores_value means unpack_info stores the whole original
+ // value. There is no need to store the number of trimmed/padded endspaces
+ // in that case.
+ if (unpack_info && !fpi->m_unpack_info_stores_value) {
+ // (value_length - trimmed_len) is the number of trimmed space *characters*
+ // then, padding_bytes is the number of *bytes* added as padding
+ // then, we add 8, because we don't store negative values.
+ DBUG_ASSERT(padding_bytes % fpi->space_xfrm_len == 0);
+ DBUG_ASSERT((value_length - trimmed_len) % fpi->space_mb_len == 0);
+ const size_t removed_chars =
+ RDB_TRIMMED_CHARS_OFFSET +
+ (value_length - trimmed_len) / fpi->space_mb_len -
+ padding_bytes / fpi->space_xfrm_len;
+
+ if (fpi->m_unpack_info_uses_two_bytes) {
+ unpack_info->write_uint16(removed_chars);
+ } else {
+ DBUG_ASSERT(removed_chars < 0x100);
+ unpack_info->write_uint8(removed_chars);
+ }
+ }
+
+ *dst += encoded_size;
+}
+
+/*
+ Calculate the number of used bytes in the chunk and whether this is the
+ last chunk in the input. This is based on the old legacy format - see
+ pack_legacy_variable_format.
+ */
+uint Rdb_key_def::calc_unpack_legacy_variable_format(uchar flag, bool *done) {
+ uint pad = 255 - flag;
+ uint used_bytes = RDB_LEGACY_ESCAPE_LENGTH - 1 - pad;
+ if (used_bytes > RDB_LEGACY_ESCAPE_LENGTH - 1) {
+ return (uint)-1;
+ }
+
+ *done = used_bytes < RDB_LEGACY_ESCAPE_LENGTH - 1;
+ return used_bytes;
+}
+
+/*
+ Calculate the number of used bytes in the chunk and whether this is the
+ last chunk in the input. This is based on the new format - see
+ pack_variable_format.
+ */
+uint Rdb_key_def::calc_unpack_variable_format(uchar flag, bool *done) {
+ // Check for invalid flag values
+ if (flag > RDB_ESCAPE_LENGTH) {
+ return (uint)-1;
+ }
+
+ // Values from 1 to N-1 indicate this is the last chunk and that is how
+ // many bytes were used
+ if (flag < RDB_ESCAPE_LENGTH) {
+ *done = true;
+ return flag;
+ }
+
+ // A value of N means we used N-1 bytes and had more to go
+ *done = false;
+ return RDB_ESCAPE_LENGTH - 1;
+}
+
+/*
+ Unpack data that has charset information. Each two bytes of the input is
+ treated as a wide-character and converted to its multibyte equivalent in
+ the output.
+ */
+static int unpack_charset(
+ const CHARSET_INFO *cset, // character set information
+ const uchar *src, // source data to unpack
+ uint src_len, // length of source data
+ uchar *dst, // destination of unpacked data
+ uint dst_len, // length of destination data
+ uint *used_bytes) // output number of bytes used
+{
+ if (src_len & 1) {
+ /*
+ UTF-8 characters are encoded into two-byte entities. There is no way
+ we can have an odd number of bytes after encoding.
+ */
+ return UNPACK_FAILURE;
+ }
+
+ uchar *dst_end = dst + dst_len;
+ uint used = 0;
+
+ for (uint ii = 0; ii < src_len; ii += 2) {
+ my_wc_t wc = (src[ii] << 8) | src[ii + 1];
+ int res = cset->wc_mb(wc, dst + used, dst_end);
+ DBUG_ASSERT(res > 0 && res <= 3);
+ if (res < 0) {
+ return UNPACK_FAILURE;
+ }
+
+ used += res;
+ }
+
+ *used_bytes = used;
+ return UNPACK_SUCCESS;
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+*/
+
+int Rdb_key_def::unpack_binary_or_utf8_varchar(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__))) {
+ const uchar *ptr;
+ size_t len = 0;
+ bool finished = false;
+ uchar *d0 = dst;
+ Field_varstring *const field_var = (Field_varstring *)field;
+ dst += field_var->length_bytes;
+ // How much we can unpack
+ size_t dst_len = field_var->pack_length() - field_var->length_bytes;
+
+ bool use_legacy_format = fpi->m_use_legacy_varbinary_format;
+
+ /* Decode the length-emitted encoding here */
+ while ((ptr = (const uchar *)reader->read(RDB_ESCAPE_LENGTH))) {
+ uint used_bytes;
+
+ /* See pack_with_varchar_encoding. */
+ if (use_legacy_format) {
+ used_bytes = calc_unpack_legacy_variable_format(
+ ptr[RDB_ESCAPE_LENGTH - 1], &finished);
+ } else {
+ used_bytes =
+ calc_unpack_variable_format(ptr[RDB_ESCAPE_LENGTH - 1], &finished);
+ }
+
+ if (used_bytes == (uint)-1 || dst_len < used_bytes) {
+ return UNPACK_FAILURE; // Corruption in the data
+ }
+
+ /*
+ Now, we need to decode used_bytes of data and append them to the value.
+ */
+ if (fpi->m_varchar_charset->number == COLLATION_UTF8_BIN) {
+ int err = unpack_charset(fpi->m_varchar_charset, ptr, used_bytes, dst,
+ dst_len, &used_bytes);
+ if (err != UNPACK_SUCCESS) {
+ return err;
+ }
+ } else {
+ memcpy(dst, ptr, used_bytes);
+ }
+
+ dst += used_bytes;
+ dst_len -= used_bytes;
+ len += used_bytes;
+
+ if (finished) {
+ break;
+ }
+ }
+
+ if (!finished) {
+ return UNPACK_FAILURE;
+ }
+
+ /* Save the length */
+ if (field_var->length_bytes == 1) {
+ d0[0] = (uchar)len;
+ } else {
+ DBUG_ASSERT(field_var->length_bytes == 2);
+ int2store(d0, len);
+ }
+ return UNPACK_SUCCESS;
+}
+
+/*
+ @seealso
+ pack_with_varchar_space_pad - packing function
+ unpack_simple_varchar_space_pad - unpacking function for 'simple'
+ charsets.
+ skip_variable_space_pad - skip function
+*/
+int Rdb_key_def::unpack_binary_or_utf8_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader, Rdb_string_reader *const unp_reader) {
+ const uchar *ptr;
+ size_t len = 0;
+ bool finished = false;
+ Field_varstring *const field_var = static_cast<Field_varstring *>(field);
+ uchar *d0 = dst;
+ uchar *dst_end = dst + field_var->pack_length();
+ dst += field_var->length_bytes;
+
+ uint space_padding_bytes = 0;
+ uint extra_spaces;
+ if ((fpi->m_unpack_info_uses_two_bytes
+ ? unp_reader->read_uint16(&extra_spaces)
+ : unp_reader->read_uint8(&extra_spaces))) {
+ return UNPACK_FAILURE;
+ }
+
+ if (extra_spaces <= RDB_TRIMMED_CHARS_OFFSET) {
+ space_padding_bytes =
+ -(static_cast<int>(extra_spaces) - RDB_TRIMMED_CHARS_OFFSET);
+ extra_spaces = 0;
+ } else {
+ extra_spaces -= RDB_TRIMMED_CHARS_OFFSET;
+ }
+
+ space_padding_bytes *= fpi->space_xfrm_len;
+
+ /* Decode the length-emitted encoding here */
+ while ((ptr = (const uchar *)reader->read(fpi->m_segment_size))) {
+ const char last_byte = ptr[fpi->m_segment_size - 1];
+ size_t used_bytes;
+ if (last_byte == VARCHAR_CMP_EQUAL_TO_SPACES) // this is the last segment
+ {
+ if (space_padding_bytes > (fpi->m_segment_size - 1)) {
+ return UNPACK_FAILURE; // Cannot happen, corrupted data
+ }
+ used_bytes = (fpi->m_segment_size - 1) - space_padding_bytes;
+ finished = true;
+ } else {
+ if (last_byte != VARCHAR_CMP_LESS_THAN_SPACES &&
+ last_byte != VARCHAR_CMP_GREATER_THAN_SPACES) {
+ return UNPACK_FAILURE; // Invalid value
+ }
+ used_bytes = fpi->m_segment_size - 1;
+ }
+
+ // Now, need to decode used_bytes of data and append them to the value.
+ if (fpi->m_varchar_charset->number == COLLATION_UTF8_BIN) {
+ if (used_bytes & 1) {
+ /*
+ UTF-8 characters are encoded into two-byte entities. There is no way
+ we can have an odd number of bytes after encoding.
+ */
+ return UNPACK_FAILURE;
+ }
+
+ const uchar *src = ptr;
+ const uchar *const src_end = ptr + used_bytes;
+ while (src < src_end) {
+ my_wc_t wc = (src[0] << 8) | src[1];
+ src += 2;
+ const CHARSET_INFO *cset = fpi->m_varchar_charset;
+ int res = cset->wc_mb(wc, dst, dst_end);
+ DBUG_ASSERT(res <= 3);
+ if (res <= 0) return UNPACK_FAILURE;
+ dst += res;
+ len += res;
+ }
+ } else {
+ if (dst + used_bytes > dst_end) return UNPACK_FAILURE;
+ memcpy(dst, ptr, used_bytes);
+ dst += used_bytes;
+ len += used_bytes;
+ }
+
+ if (finished) {
+ if (extra_spaces) {
+ // Both binary and UTF-8 charset store space as ' ',
+ // so the following is ok:
+ if (dst + extra_spaces > dst_end) return UNPACK_FAILURE;
+ memset(dst, fpi->m_varchar_charset->pad_char, extra_spaces);
+ len += extra_spaces;
+ }
+ break;
+ }
+ }
+
+ if (!finished) return UNPACK_FAILURE;
+
+ /* Save the length */
+ if (field_var->length_bytes == 1) {
+ d0[0] = (uchar)len;
+ } else {
+ DBUG_ASSERT(field_var->length_bytes == 2);
+ int2store(d0, len);
+ }
+ return UNPACK_SUCCESS;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+/*
+ Function of type rdb_make_unpack_info_t
+*/
+
+void Rdb_key_def::make_unpack_unknown(
+ const Rdb_collation_codec *codec MY_ATTRIBUTE((__unused__)),
+ const Field *const field, Rdb_pack_field_context *const pack_ctx) {
+ pack_ctx->writer->write(field->ptr, field->pack_length());
+}
+
+/*
+ This point of this function is only to indicate that unpack_info is
+ available.
+
+ The actual unpack_info data is produced by the function that packs the key,
+ that is, pack_with_varchar_space_pad.
+*/
+
+void Rdb_key_def::dummy_make_unpack_info(
+ const Rdb_collation_codec *codec MY_ATTRIBUTE((__unused__)),
+ const Field *field MY_ATTRIBUTE((__unused__)),
+ Rdb_pack_field_context *pack_ctx MY_ATTRIBUTE((__unused__))) {
+ // Do nothing
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+*/
+
+int Rdb_key_def::unpack_unknown(Rdb_field_packing *const fpi,
+ Field *const field, uchar *const dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader) {
+ const uchar *ptr;
+ const uint len = fpi->m_unpack_data_len;
+ // We don't use anything from the key, so skip over it.
+ if (skip_max_length(fpi, field, reader)) {
+ return UNPACK_FAILURE;
+ }
+
+ DBUG_ASSERT_IMP(len > 0, unp_reader != nullptr);
+
+ if ((ptr = (const uchar *)unp_reader->read(len))) {
+ memcpy(dst, ptr, len);
+ return UNPACK_SUCCESS;
+ }
+ return UNPACK_FAILURE;
+}
+
+/*
+ Function of type rdb_make_unpack_info_t
+*/
+
+void Rdb_key_def::make_unpack_unknown_varchar(
+ const Rdb_collation_codec *const codec MY_ATTRIBUTE((__unused__)),
+ const Field *const field, Rdb_pack_field_context *const pack_ctx) {
+ const auto f = static_cast<const Field_varstring *>(field);
+ uint len = f->length_bytes == 1 ? (uint)*f->ptr : uint2korr(f->ptr);
+ len += f->length_bytes;
+ pack_ctx->writer->write(field->ptr, len);
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+
+ @detail
+ Unpack a key part in an "unknown" collation from its
+ (mem_comparable_form, unpack_info) form.
+
+ "Unknown" means we have no clue about how mem_comparable_form is made from
+ the original string, so we keep the whole original string in the unpack_info.
+
+ @seealso
+ make_unpack_unknown, unpack_unknown
+*/
+
+int Rdb_key_def::unpack_unknown_varchar(Rdb_field_packing *const fpi,
+ Field *const field, uchar *dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader) {
+ const uchar *ptr;
+ uchar *const d0 = dst;
+ const auto f = static_cast<Field_varstring *>(field);
+ dst += f->length_bytes;
+ const uint len_bytes = f->length_bytes;
+ // We don't use anything from the key, so skip over it.
+ if ((fpi->m_skip_func)(fpi, field, reader)) {
+ return UNPACK_FAILURE;
+ }
+
+ DBUG_ASSERT(len_bytes > 0);
+ DBUG_ASSERT(unp_reader != nullptr);
+
+ if ((ptr = (const uchar *)unp_reader->read(len_bytes))) {
+ memcpy(d0, ptr, len_bytes);
+ const uint len = len_bytes == 1 ? (uint)*ptr : uint2korr(ptr);
+ if ((ptr = (const uchar *)unp_reader->read(len))) {
+ memcpy(dst, ptr, len);
+ return UNPACK_SUCCESS;
+ }
+ }
+ return UNPACK_FAILURE;
+}
+
+/*
+ Write unpack_data for a "simple" collation
+*/
+static void rdb_write_unpack_simple(Rdb_bit_writer *const writer,
+ const Rdb_collation_codec *const codec,
+ const uchar *const src,
+ const size_t src_len) {
+ for (uint i = 0; i < src_len; i++) {
+ writer->write(codec->m_enc_size[src[i]], codec->m_enc_idx[src[i]]);
+ }
+}
+
+static uint rdb_read_unpack_simple(Rdb_bit_reader *const reader,
+ const Rdb_collation_codec *const codec,
+ const uchar *const src, const size_t src_len,
+ uchar *const dst) {
+ for (uint i = 0; i < src_len; i++) {
+ if (codec->m_dec_size[src[i]] > 0) {
+ uint *ret;
+ DBUG_ASSERT(reader != nullptr);
+
+ if ((ret = reader->read(codec->m_dec_size[src[i]])) == nullptr) {
+ return UNPACK_FAILURE;
+ }
+ dst[i] = codec->m_dec_idx[*ret][src[i]];
+ } else {
+ dst[i] = codec->m_dec_idx[0][src[i]];
+ }
+ }
+
+ return UNPACK_SUCCESS;
+}
+
+/*
+ Function of type rdb_make_unpack_info_t
+
+ @detail
+ Make unpack_data for VARCHAR(n) in a "simple" charset.
+*/
+
+void Rdb_key_def::make_unpack_simple_varchar(
+ const Rdb_collation_codec *const codec, const Field *const field,
+ Rdb_pack_field_context *const pack_ctx) {
+ const auto f = static_cast<const Field_varstring *>(field);
+ uchar *const src = f->ptr + f->length_bytes;
+ const size_t src_len =
+ f->length_bytes == 1 ? (uint)*f->ptr : uint2korr(f->ptr);
+ Rdb_bit_writer bit_writer(pack_ctx->writer);
+ // The std::min compares characters with bytes, but for simple collations,
+ // mbmaxlen = 1.
+ rdb_write_unpack_simple(&bit_writer, codec, src,
+ std::min((size_t)f->char_length(), src_len));
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+
+ @seealso
+ pack_with_varchar_space_pad - packing function
+ unpack_binary_or_utf8_varchar_space_pad - a similar unpacking function
+*/
+
+int Rdb_key_def::unpack_simple_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader, Rdb_string_reader *const unp_reader) {
+ const uchar *ptr;
+ size_t len = 0;
+ bool finished = false;
+ uchar *d0 = dst;
+ const Field_varstring *const field_var =
+ static_cast<Field_varstring *>(field);
+ // For simple collations, char_length is also number of bytes.
+ DBUG_ASSERT((size_t)fpi->m_max_image_len >= field_var->char_length());
+ uchar *dst_end = dst + field_var->pack_length();
+ dst += field_var->length_bytes;
+ Rdb_bit_reader bit_reader(unp_reader);
+
+ uint space_padding_bytes = 0;
+ uint extra_spaces;
+ DBUG_ASSERT(unp_reader != nullptr);
+
+ if ((fpi->m_unpack_info_uses_two_bytes
+ ? unp_reader->read_uint16(&extra_spaces)
+ : unp_reader->read_uint8(&extra_spaces))) {
+ return UNPACK_FAILURE;
+ }
+
+ if (extra_spaces <= 8) {
+ space_padding_bytes = -(static_cast<int>(extra_spaces) - 8);
+ extra_spaces = 0;
+ } else {
+ extra_spaces -= 8;
+ }
+
+ space_padding_bytes *= fpi->space_xfrm_len;
+
+ /* Decode the length-emitted encoding here */
+ while ((ptr = (const uchar *)reader->read(fpi->m_segment_size))) {
+ const char last_byte =
+ ptr[fpi->m_segment_size - 1]; // number of padding bytes
+ size_t used_bytes;
+ if (last_byte == VARCHAR_CMP_EQUAL_TO_SPACES) {
+ // this is the last one
+ if (space_padding_bytes > (fpi->m_segment_size - 1)) {
+ return UNPACK_FAILURE; // Cannot happen, corrupted data
+ }
+ used_bytes = (fpi->m_segment_size - 1) - space_padding_bytes;
+ finished = true;
+ } else {
+ if (last_byte != VARCHAR_CMP_LESS_THAN_SPACES &&
+ last_byte != VARCHAR_CMP_GREATER_THAN_SPACES) {
+ return UNPACK_FAILURE;
+ }
+ used_bytes = fpi->m_segment_size - 1;
+ }
+
+ if (dst + used_bytes > dst_end) {
+ // The value on disk is longer than the field definition allows?
+ return UNPACK_FAILURE;
+ }
+
+ uint ret;
+ if ((ret = rdb_read_unpack_simple(&bit_reader, fpi->m_charset_codec, ptr,
+ used_bytes, dst)) != UNPACK_SUCCESS) {
+ return ret;
+ }
+
+ dst += used_bytes;
+ len += used_bytes;
+
+ if (finished) {
+ if (extra_spaces) {
+ if (dst + extra_spaces > dst_end) return UNPACK_FAILURE;
+ // pad_char has a 1-byte form in all charsets that
+ // are handled by rdb_init_collation_mapping.
+ memset(dst, field_var->charset()->pad_char, extra_spaces);
+ len += extra_spaces;
+ }
+ break;
+ }
+ }
+
+ if (!finished) return UNPACK_FAILURE;
+
+ /* Save the length */
+ if (field_var->length_bytes == 1) {
+ d0[0] = (uchar)len;
+ } else {
+ DBUG_ASSERT(field_var->length_bytes == 2);
+ int2store(d0, len);
+ }
+ return UNPACK_SUCCESS;
+}
+
+/*
+ Function of type rdb_make_unpack_info_t
+
+ @detail
+ Make unpack_data for CHAR(n) value in a "simple" charset.
+ It is CHAR(N), so SQL layer has padded the value with spaces up to N chars.
+
+ @seealso
+ The VARCHAR variant is in make_unpack_simple_varchar
+*/
+
+void Rdb_key_def::make_unpack_simple(const Rdb_collation_codec *const codec,
+ const Field *const field,
+ Rdb_pack_field_context *const pack_ctx) {
+ const uchar *const src = field->ptr;
+ Rdb_bit_writer bit_writer(pack_ctx->writer);
+ rdb_write_unpack_simple(&bit_writer, codec, src, field->pack_length());
+}
+
+/*
+ Function of type rdb_index_field_unpack_t
+*/
+
+int Rdb_key_def::unpack_simple(Rdb_field_packing *const fpi,
+ Field *const field MY_ATTRIBUTE((__unused__)),
+ uchar *const dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader) {
+ const uchar *ptr;
+ const uint len = fpi->m_max_image_len;
+ Rdb_bit_reader bit_reader(unp_reader);
+
+ if (!(ptr = (const uchar *)reader->read(len))) {
+ return UNPACK_FAILURE;
+ }
+
+ return rdb_read_unpack_simple(unp_reader ? &bit_reader : nullptr,
+ fpi->m_charset_codec, ptr, len, dst);
+}
+
+// See Rdb_charset_space_info::spaces_xfrm
+const int RDB_SPACE_XFRM_SIZE = 32;
+
+// A class holding information about how space character is represented in a
+// charset.
+class Rdb_charset_space_info {
+ public:
+ Rdb_charset_space_info(const Rdb_charset_space_info &) = delete;
+ Rdb_charset_space_info &operator=(const Rdb_charset_space_info &) = delete;
+ Rdb_charset_space_info() = default;
+
+ // A few strxfrm'ed space characters, at least RDB_SPACE_XFRM_SIZE bytes
+ std::vector<uchar> spaces_xfrm;
+
+ // length(strxfrm(' '))
+ size_t space_xfrm_len;
+
+ // length of the space character itself
+ // Typically space is just 0x20 (length=1) but in ucs2 it is 0x00 0x20
+ // (length=2)
+ size_t space_mb_len;
+};
+
+static std::array<std::unique_ptr<Rdb_charset_space_info>, MY_ALL_CHARSETS_SIZE>
+ rdb_mem_comparable_space;
+
+/*
+ @brief
+ For a given charset, get
+ - strxfrm(' '), a sample that is at least RDB_SPACE_XFRM_SIZE bytes long.
+ - length of strxfrm(charset, ' ')
+ - length of the space character in the charset
+
+ @param cs IN Charset to get the space for
+ @param ptr OUT A few space characters
+ @param len OUT Return length of the space (in bytes)
+
+ @detail
+ It is tempting to pre-generate mem-comparable form of space character for
+ every charset on server startup.
+ One can't do that: some charsets are not initialized until somebody
+ attempts to use them (e.g. create or open a table that has a field that
+ uses the charset).
+*/
+
+static void rdb_get_mem_comparable_space(const CHARSET_INFO *const cs,
+ const std::vector<uchar> **xfrm,
+ size_t *const xfrm_len,
+ size_t *const mb_len) {
+ DBUG_ASSERT(cs->number < MY_ALL_CHARSETS_SIZE);
+ if (!rdb_mem_comparable_space[cs->number].get()) {
+ RDB_MUTEX_LOCK_CHECK(rdb_mem_cmp_space_mutex);
+ if (!rdb_mem_comparable_space[cs->number].get()) {
+ // Upper bound of how many bytes can be occupied by multi-byte form of a
+ // character in any charset.
+ const int MAX_MULTI_BYTE_CHAR_SIZE = 4;
+ DBUG_ASSERT(cs->mbmaxlen <= MAX_MULTI_BYTE_CHAR_SIZE);
+
+ // multi-byte form of the ' ' (space) character
+ uchar space_mb[MAX_MULTI_BYTE_CHAR_SIZE];
+
+ const size_t space_mb_len = cs->wc_mb(
+ (my_wc_t)cs->pad_char, space_mb, space_mb + sizeof(space_mb));
+
+ // mem-comparable image of the space character
+ std::array<uchar, 20> space;
+
+ const size_t space_len = cs->strnxfrm(
+ space.data(), sizeof(space), 1, space_mb, space_mb_len, 0);
+ Rdb_charset_space_info *const info = new Rdb_charset_space_info;
+ info->space_xfrm_len = space_len;
+ info->space_mb_len = space_mb_len;
+ while (info->spaces_xfrm.size() < RDB_SPACE_XFRM_SIZE) {
+ info->spaces_xfrm.insert(info->spaces_xfrm.end(), space.data(),
+ space.data() + space_len);
+ }
+ rdb_mem_comparable_space[cs->number].reset(info);
+ }
+ RDB_MUTEX_UNLOCK_CHECK(rdb_mem_cmp_space_mutex);
+ }
+
+ *xfrm = &rdb_mem_comparable_space[cs->number]->spaces_xfrm;
+ *xfrm_len = rdb_mem_comparable_space[cs->number]->space_xfrm_len;
+ *mb_len = rdb_mem_comparable_space[cs->number]->space_mb_len;
+}
+
+mysql_mutex_t rdb_mem_cmp_space_mutex;
+
+std::array<const Rdb_collation_codec *, MY_ALL_CHARSETS_SIZE>
+ rdb_collation_data;
+mysql_mutex_t rdb_collation_data_mutex;
+
+bool rdb_is_collation_supported(const my_core::CHARSET_INFO *const cs) {
+ return cs->strxfrm_multiply==1 && cs->mbmaxlen == 1 &&
+ !(cs->state & (MY_CS_BINSORT | MY_CS_NOPAD));
+}
+
+static const Rdb_collation_codec *rdb_init_collation_mapping(
+ const my_core::CHARSET_INFO *const cs) {
+ DBUG_ASSERT(cs && cs->state & MY_CS_AVAILABLE);
+ const Rdb_collation_codec *codec = rdb_collation_data[cs->number];
+
+ if (codec == nullptr && rdb_is_collation_supported(cs)) {
+ RDB_MUTEX_LOCK_CHECK(rdb_collation_data_mutex);
+
+ codec = rdb_collation_data[cs->number];
+ if (codec == nullptr) {
+ Rdb_collation_codec *cur = nullptr;
+
+ // Compute reverse mapping for simple collations.
+ if (rdb_is_collation_supported(cs)) {
+ cur = new Rdb_collation_codec;
+ std::map<uchar, std::vector<uchar>> rev_map;
+ size_t max_conflict_size = 0;
+ for (int src = 0; src < 256; src++) {
+ uchar dst = cs->sort_order[src];
+ rev_map[dst].push_back(src);
+ max_conflict_size = std::max(max_conflict_size, rev_map[dst].size());
+ }
+ cur->m_dec_idx.resize(max_conflict_size);
+
+ for (auto const &p : rev_map) {
+ uchar dst = p.first;
+ for (uint idx = 0; idx < p.second.size(); idx++) {
+ uchar src = p.second[idx];
+ uchar bits =
+ my_bit_log2_uint32(my_round_up_to_next_power(p.second.size()));
+ cur->m_enc_idx[src] = idx;
+ cur->m_enc_size[src] = bits;
+ cur->m_dec_size[dst] = bits;
+ cur->m_dec_idx[idx][dst] = src;
+ }
+ }
+
+ cur->m_make_unpack_info_func = {Rdb_key_def::make_unpack_simple_varchar,
+ Rdb_key_def::make_unpack_simple};
+ cur->m_unpack_func = {Rdb_key_def::unpack_simple_varchar_space_pad,
+ Rdb_key_def::unpack_simple};
+ } else {
+ // Out of luck for now.
+ }
+
+ if (cur != nullptr) {
+ codec = cur;
+ cur->m_cs = cs;
+ rdb_collation_data[cs->number] = cur;
+ }
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(rdb_collation_data_mutex);
+ }
+
+ return codec;
+}
+
+static int get_segment_size_from_collation(const CHARSET_INFO *const cs) {
+ int ret;
+ if (cs->number == COLLATION_UTF8MB4_BIN || cs->number == COLLATION_UTF16_BIN ||
+ cs->number == COLLATION_UTF16LE_BIN || cs->number == COLLATION_UTF32_BIN) {
+ /*
+ In these collations, a character produces one weight, which is 3 bytes.
+ Segment has 3 characters, add one byte for VARCHAR_CMP_* marker, and we
+ get 3*3+1=10
+ */
+ ret = 10;
+ } else {
+ /*
+ All other collations. There are two classes:
+ - Unicode-based, except for collations mentioned in the if-condition.
+ For these all weights are 2 bytes long, a character may produce 0..8
+ weights.
+ in any case, 8 bytes of payload in the segment guarantee that the last
+ space character won't span across segments.
+
+ - Collations not based on unicode. These have length(strxfrm(' '))=1,
+ there nothing to worry about.
+
+ In both cases, take 8 bytes payload + 1 byte for VARCHAR_CMP* marker.
+ */
+ ret = 9;
+ }
+ DBUG_ASSERT(ret < RDB_SPACE_XFRM_SIZE);
+ return ret;
+}
+
+/*
+ @brief
+ Setup packing of index field into its mem-comparable form
+
+ @detail
+ - It is possible produce mem-comparable form for any datatype.
+ - Some datatypes also allow to unpack the original value from its
+ mem-comparable form.
+ = Some of these require extra information to be stored in "unpack_info".
+ unpack_info is not a part of mem-comparable form, it is only used to
+ restore the original value
+
+ @param
+ field IN field to be packed/un-packed
+
+ @return
+ TRUE - Field can be read with index-only reads
+ FALSE - Otherwise
+*/
+
+bool Rdb_field_packing::setup(const Rdb_key_def *const key_descr,
+ const Field *const field, const uint keynr_arg,
+ const uint key_part_arg,
+ const uint16 key_length) {
+ int res = false;
+ enum_field_types type = field ? field->real_type() : MYSQL_TYPE_LONGLONG;
+
+ m_keynr = keynr_arg;
+ m_key_part = key_part_arg;
+
+ m_maybe_null = field ? field->real_maybe_null() : false;
+ m_unpack_func = nullptr;
+ m_make_unpack_info_func = nullptr;
+ m_unpack_data_len = 0;
+ space_xfrm = nullptr; // safety
+ // whether to use legacy format for varchar
+ m_use_legacy_varbinary_format = false;
+ // ha_rocksdb::index_flags() will pass key_descr == null to
+ // see whether field(column) can be read-only reads through return value,
+ // but the legacy vs. new varchar format doesn't affect return value.
+ // Just change m_use_legacy_varbinary_format to true if key_descr isn't given.
+ if (!key_descr || key_descr->use_legacy_varbinary_format()) {
+ m_use_legacy_varbinary_format = true;
+ }
+ /* Calculate image length. By default, is is pack_length() */
+ m_max_image_len =
+ field ? field->pack_length() : ROCKSDB_SIZEOF_HIDDEN_PK_COLUMN;
+ m_skip_func = Rdb_key_def::skip_max_length;
+ m_pack_func = Rdb_key_def::pack_with_make_sort_key;
+
+ m_covered = false;
+
+ switch (type) {
+ case MYSQL_TYPE_LONGLONG:
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_INT24:
+ case MYSQL_TYPE_SHORT:
+ case MYSQL_TYPE_TINY:
+ m_unpack_func = Rdb_key_def::unpack_integer;
+ m_covered = true;
+ return true;
+
+ case MYSQL_TYPE_DOUBLE:
+ m_unpack_func = Rdb_key_def::unpack_double;
+ m_covered = true;
+ return true;
+
+ case MYSQL_TYPE_FLOAT:
+ m_unpack_func = Rdb_key_def::unpack_float;
+ m_covered = true;
+ return true;
+
+ case MYSQL_TYPE_NEWDECIMAL:
+ /*
+ Decimal is packed with Field_new_decimal::make_sort_key, which just
+ does memcpy.
+ Unpacking decimal values was supported only after fix for issue#253,
+ because of that ha_rocksdb::get_storage_type() handles decimal values
+ in a special way.
+ */
+ case MYSQL_TYPE_DATETIME2:
+ case MYSQL_TYPE_TIMESTAMP2:
+ /* These are packed with Field_temporal_with_date_and_timef::make_sort_key
+ */
+ case MYSQL_TYPE_TIME2: /* TIME is packed with Field_timef::make_sort_key */
+ case MYSQL_TYPE_YEAR: /* YEAR is packed with Field_tiny::make_sort_key */
+ /* Everything that comes here is packed with just a memcpy(). */
+ m_unpack_func = Rdb_key_def::unpack_binary_str;
+ m_covered = true;
+ return true;
+
+ case MYSQL_TYPE_NEWDATE:
+ /*
+ This is packed by Field_newdate::make_sort_key. It assumes the data is
+ 3 bytes, and packing is done by swapping the byte order (for both big-
+ and little-endian)
+ */
+ m_unpack_func = Rdb_key_def::unpack_newdate;
+ m_covered = true;
+ return true;
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB: {
+ if (key_descr) {
+ // The my_charset_bin collation is special in that it will consider
+ // shorter strings sorting as less than longer strings.
+ //
+ // See Field_blob::make_sort_key for details.
+ m_max_image_len =
+ key_length + (field->charset()->number == COLLATION_BINARY
+ ? reinterpret_cast<const Field_blob *>(field)
+ ->pack_length_no_ptr()
+ : 0);
+ // Return false because indexes on text/blob will always require
+ // a prefix. With a prefix, the optimizer will not be able to do an
+ // index-only scan since there may be content occuring after the prefix
+ // length.
+ return false;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ m_unpack_info_stores_value = false;
+ /* Handle [VAR](CHAR|BINARY) */
+
+ if (type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_STRING) {
+ /*
+ For CHAR-based columns, check how strxfrm image will take.
+ field->field_length = field->char_length() * cs->mbmaxlen.
+ */
+ const CHARSET_INFO *cs = field->charset();
+ m_max_image_len = cs->strnxfrmlen(type == MYSQL_TYPE_STRING ?
+ field->pack_length() :
+ field->field_length);
+ }
+ const bool is_varchar = (type == MYSQL_TYPE_VARCHAR);
+ const CHARSET_INFO *cs = field->charset();
+ // max_image_len before chunking is taken into account
+ const int max_image_len_before_chunks = m_max_image_len;
+
+ if (is_varchar) {
+ // The default for varchar is variable-length, without space-padding for
+ // comparisons
+ m_varchar_charset = cs;
+ m_skip_func = Rdb_key_def::skip_variable_length;
+ m_pack_func = Rdb_key_def::pack_with_varchar_encoding;
+ if (!key_descr || key_descr->use_legacy_varbinary_format()) {
+ m_max_image_len = RDB_LEGACY_ENCODED_SIZE(m_max_image_len);
+ } else {
+ // Calculate the maximum size of the short section plus the
+ // maximum size of the long section
+ m_max_image_len = RDB_ENCODED_SIZE(m_max_image_len);
+ }
+
+ const auto field_var = static_cast<const Field_varstring *>(field);
+ m_unpack_info_uses_two_bytes = (field_var->field_length + 8 >= 0x100);
+ }
+
+ if (type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_STRING) {
+ // See http://dev.mysql.com/doc/refman/5.7/en/string-types.html for
+ // information about character-based datatypes are compared.
+ bool use_unknown_collation = false;
+ DBUG_EXECUTE_IF("myrocks_enable_unknown_collation_index_only_scans",
+ use_unknown_collation = true;);
+
+ if (cs->number == COLLATION_BINARY) {
+ // - SQL layer pads BINARY(N) so that it always is N bytes long.
+ // - For VARBINARY(N), values may have different lengths, so we're using
+ // variable-length encoding. This is also the only charset where the
+ // values are not space-padded for comparison.
+ m_unpack_func = is_varchar ? Rdb_key_def::unpack_binary_or_utf8_varchar
+ : Rdb_key_def::unpack_binary_str;
+ res = true;
+ } else if (cs->number == COLLATION_LATIN1_BIN || cs->number == COLLATION_UTF8_BIN) {
+ // For _bin collations, mem-comparable form of the string is the string
+ // itself.
+
+ if (is_varchar) {
+ // VARCHARs - are compared as if they were space-padded - but are
+ // not actually space-padded (reading the value back produces the
+ // original value, without the padding)
+ m_unpack_func = Rdb_key_def::unpack_binary_or_utf8_varchar_space_pad;
+ m_skip_func = Rdb_key_def::skip_variable_space_pad;
+ m_pack_func = Rdb_key_def::pack_with_varchar_space_pad;
+ m_make_unpack_info_func = Rdb_key_def::dummy_make_unpack_info;
+ m_segment_size = get_segment_size_from_collation(cs);
+ m_max_image_len =
+ (max_image_len_before_chunks / (m_segment_size - 1) + 1) *
+ m_segment_size;
+ rdb_get_mem_comparable_space(cs, &space_xfrm, &space_xfrm_len,
+ &space_mb_len);
+ } else {
+ // SQL layer pads CHAR(N) values to their maximum length.
+ // We just store that and restore it back.
+ m_unpack_func = (cs->number == COLLATION_LATIN1_BIN)
+ ? Rdb_key_def::unpack_binary_str
+ : Rdb_key_def::unpack_utf8_str;
+ }
+ res = true;
+ } else {
+ // This is [VAR]CHAR(n) and the collation is not $(charset_name)_bin
+
+ res = true; // index-only scans are possible
+ m_unpack_data_len = is_varchar ? 0 : field->field_length;
+ const uint idx = is_varchar ? 0 : 1;
+ const Rdb_collation_codec *codec = nullptr;
+
+ if (is_varchar) {
+ // VARCHAR requires space-padding for doing comparisons
+ //
+ // The check for cs->levels_for_order is to catch
+ // latin2_czech_cs and cp1250_czech_cs - multi-level collations
+ // that Variable-Length Space Padded Encoding can't handle.
+ // It is not expected to work for any other multi-level collations,
+ // either.
+ // Currently we handle these collations as NO_PAD, even if they have
+ // PAD_SPACE attribute.
+ if (cs->levels_for_order == 1) {
+ m_pack_func = Rdb_key_def::pack_with_varchar_space_pad;
+ m_skip_func = Rdb_key_def::skip_variable_space_pad;
+ m_segment_size = get_segment_size_from_collation(cs);
+ m_max_image_len =
+ (max_image_len_before_chunks / (m_segment_size - 1) + 1) *
+ m_segment_size;
+ rdb_get_mem_comparable_space(cs, &space_xfrm, &space_xfrm_len,
+ &space_mb_len);
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "RocksDB: you're trying to create an index "
+ "with a multi-level collation %s",
+ cs->name);
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "MyRocks will handle this collation internally "
+ " as if it had a NO_PAD attribute.");
+ m_pack_func = Rdb_key_def::pack_with_varchar_encoding;
+ m_skip_func = Rdb_key_def::skip_variable_length;
+ }
+ }
+
+ if ((codec = rdb_init_collation_mapping(cs)) != nullptr) {
+ // The collation allows to store extra information in the unpack_info
+ // which can be used to restore the original value from the
+ // mem-comparable form.
+ m_make_unpack_info_func = codec->m_make_unpack_info_func[idx];
+ m_unpack_func = codec->m_unpack_func[idx];
+ m_charset_codec = codec;
+ } else if (use_unknown_collation) {
+ // We have no clue about how this collation produces mem-comparable
+ // form. Our way of restoring the original value is to keep a copy of
+ // the original value in unpack_info.
+ m_unpack_info_stores_value = true;
+ m_make_unpack_info_func = is_varchar
+ ? Rdb_key_def::make_unpack_unknown_varchar
+ : Rdb_key_def::make_unpack_unknown;
+ m_unpack_func = is_varchar ? Rdb_key_def::unpack_unknown_varchar
+ : Rdb_key_def::unpack_unknown;
+ } else {
+ // Same as above: we don't know how to restore the value from its
+ // mem-comparable form.
+ // Here, we just indicate to the SQL layer we can't do it.
+ DBUG_ASSERT(m_unpack_func == nullptr);
+ m_unpack_info_stores_value = false;
+ res = false; // Indicate that index-only reads are not possible
+ }
+ }
+
+ // Make an adjustment: if this column is partially covered, tell the SQL
+ // layer we can't do index-only scans. Later when we perform an index read,
+ // we'll check on a record-by-record basis if we can do an index-only scan
+ // or not.
+ uint field_length;
+ if (field->table) {
+ field_length = field->table->field[field->field_index]->field_length;
+ } else {
+ field_length = field->field_length;
+ }
+
+ if (field_length != key_length) {
+ res = false;
+ // If this index doesn't support covered bitmaps, then we won't know
+ // during a read if the column is actually covered or not. If so, we need
+ // to assume the column isn't covered and skip it during unpacking.
+ //
+ // If key_descr == NULL, then this is a dummy field and we probably don't
+ // need to perform this step. However, to preserve the behavior before
+ // this change, we'll only skip this step if we have an index which
+ // supports covered bitmaps.
+ if (!key_descr || !key_descr->use_covered_bitmap_format()) {
+ m_unpack_func = nullptr;
+ m_make_unpack_info_func = nullptr;
+ m_unpack_info_stores_value = true;
+ }
+ }
+ }
+
+ m_covered = res;
+ return res;
+}
+
+Field *Rdb_field_packing::get_field_in_table(const TABLE *const tbl) const {
+ return tbl->key_info[m_keynr].key_part[m_key_part].field;
+}
+
+void Rdb_field_packing::fill_hidden_pk_val(uchar **dst,
+ const longlong hidden_pk_id) const {
+ DBUG_ASSERT(m_max_image_len == 8);
+
+ String to;
+ rdb_netstr_append_uint64(&to, hidden_pk_id);
+ memcpy(*dst, to.ptr(), m_max_image_len);
+
+ *dst += m_max_image_len;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// Rdb_ddl_manager
+///////////////////////////////////////////////////////////////////////////////////////////
+
+Rdb_tbl_def::~Rdb_tbl_def() {
+ auto ddl_manager = rdb_get_ddl_manager();
+ /* Don't free key definitions */
+ if (m_key_descr_arr) {
+ for (uint i = 0; i < m_key_count; i++) {
+ if (ddl_manager && m_key_descr_arr[i]) {
+ ddl_manager->erase_index_num(m_key_descr_arr[i]->get_gl_index_id());
+ }
+
+ m_key_descr_arr[i] = nullptr;
+ }
+
+ delete[] m_key_descr_arr;
+ m_key_descr_arr = nullptr;
+ }
+}
+
+/*
+ Put table definition DDL entry. Actual write is done at
+ Rdb_dict_manager::commit.
+
+ We write
+ dbname.tablename -> version + {key_entry, key_entry, key_entry, ... }
+
+ Where key entries are a tuple of
+ ( cf_id, index_nr )
+*/
+
+bool Rdb_tbl_def::put_dict(Rdb_dict_manager *const dict,
+ rocksdb::WriteBatch *const batch,
+ const rocksdb::Slice &key) {
+ StringBuffer<8 * Rdb_key_def::PACKED_SIZE> indexes;
+ indexes.alloc(Rdb_key_def::VERSION_SIZE +
+ m_key_count * Rdb_key_def::PACKED_SIZE * 2);
+ rdb_netstr_append_uint16(&indexes, Rdb_key_def::DDL_ENTRY_INDEX_VERSION);
+
+ for (uint i = 0; i < m_key_count; i++) {
+ const Rdb_key_def &kd = *m_key_descr_arr[i];
+
+ uchar flags =
+ (kd.m_is_reverse_cf ? Rdb_key_def::REVERSE_CF_FLAG : 0) |
+ (kd.m_is_per_partition_cf ? Rdb_key_def::PER_PARTITION_CF_FLAG : 0);
+
+ const uint cf_id = kd.get_cf()->GetID();
+ /*
+ If cf_id already exists, cf_flags must be the same.
+ To prevent race condition, reading/modifying/committing CF flags
+ need to be protected by mutex (dict_manager->lock()).
+ When RocksDB supports transaction with pessimistic concurrency
+ control, we can switch to use it and removing mutex.
+ */
+ uint existing_cf_flags;
+ const std::string cf_name = kd.get_cf()->GetName();
+
+ if (dict->get_cf_flags(cf_id, &existing_cf_flags)) {
+ // For the purposes of comparison we'll clear the partitioning bit. The
+ // intent here is to make sure that both partitioned and non-partitioned
+ // tables can refer to the same CF.
+ existing_cf_flags &= ~Rdb_key_def::CF_FLAGS_TO_IGNORE;
+ flags &= ~Rdb_key_def::CF_FLAGS_TO_IGNORE;
+
+ if (existing_cf_flags != flags) {
+ my_error(ER_CF_DIFFERENT, MYF(0), cf_name.c_str(), flags,
+ existing_cf_flags);
+ return true;
+ }
+ } else {
+ dict->add_cf_flags(batch, cf_id, flags);
+ }
+
+ rdb_netstr_append_uint32(&indexes, cf_id);
+
+ uint32 index_number = kd.get_index_number();
+ rdb_netstr_append_uint32(&indexes, index_number);
+
+ struct Rdb_index_info index_info;
+ index_info.m_gl_index_id = {cf_id, index_number};
+ index_info.m_index_dict_version = Rdb_key_def::INDEX_INFO_VERSION_LATEST;
+ index_info.m_index_type = kd.m_index_type;
+ index_info.m_kv_version = kd.m_kv_format_version;
+ index_info.m_index_flags = kd.m_index_flags_bitmap;
+ index_info.m_ttl_duration = kd.m_ttl_duration;
+
+ dict->add_or_update_index_cf_mapping(batch, &index_info);
+ }
+
+ const rocksdb::Slice svalue(indexes.c_ptr(), indexes.length());
+
+ dict->put_key(batch, key, svalue);
+ return false;
+}
+
+time_t Rdb_tbl_def::get_create_time() {
+ time_t create_time = m_create_time;
+
+ if (create_time == CREATE_TIME_UNKNOWN) {
+ // Read it from the .frm file. It's not a problem if several threads do this
+ // concurrently
+ char path[FN_REFLEN];
+ snprintf(path, sizeof(path), "%s/%s/%s%s", mysql_data_home,
+ m_dbname.c_str(), m_tablename.c_str(), reg_ext);
+ unpack_filename(path,path);
+ MY_STAT f_stat;
+ if (my_stat(path, &f_stat, MYF(0)))
+ create_time = f_stat.st_ctime;
+ else
+ create_time = 0; // will be shown as SQL NULL
+ m_create_time = create_time;
+ }
+ return create_time;
+}
+
+// Length that each index flag takes inside the record.
+// Each index in the array maps to the enum INDEX_FLAG
+static const std::array<uint, 1> index_flag_lengths = {
+ {ROCKSDB_SIZEOF_TTL_RECORD}};
+
+bool Rdb_key_def::has_index_flag(uint32 index_flags, enum INDEX_FLAG flag) {
+ return flag & index_flags;
+}
+
+uint32 Rdb_key_def::calculate_index_flag_offset(uint32 index_flags,
+ enum INDEX_FLAG flag,
+ uint *const length) {
+ DBUG_ASSERT_IMP(flag != MAX_FLAG,
+ Rdb_key_def::has_index_flag(index_flags, flag));
+
+ uint offset = 0;
+ for (size_t bit = 0; bit < sizeof(index_flags) * CHAR_BIT; ++bit) {
+ int mask = 1 << bit;
+
+ /* Exit once we've reached the proper flag */
+ if (flag & mask) {
+ if (length != nullptr) {
+ *length = index_flag_lengths[bit];
+ }
+ break;
+ }
+
+ if (index_flags & mask) {
+ offset += index_flag_lengths[bit];
+ }
+ }
+
+ return offset;
+}
+
+void Rdb_key_def::write_index_flag_field(Rdb_string_writer *const buf,
+ const uchar *const val,
+ enum INDEX_FLAG flag) const {
+ uint len;
+ uint offset = calculate_index_flag_offset(m_index_flags_bitmap, flag, &len);
+ DBUG_ASSERT(offset + len <= buf->get_current_pos());
+ memcpy(buf->ptr() + offset, val, len);
+}
+
+void Rdb_tbl_def::check_if_is_mysql_system_table() {
+ static const char *const system_dbs[] = {
+ "mysql",
+ "performance_schema",
+ "information_schema",
+ };
+
+ m_is_mysql_system_table = false;
+ for (uint ii = 0; ii < array_elements(system_dbs); ii++) {
+ if (strcmp(m_dbname.c_str(), system_dbs[ii]) == 0) {
+ m_is_mysql_system_table = true;
+ break;
+ }
+ }
+}
+
+void Rdb_tbl_def::check_and_set_read_free_rpl_table() {
+ m_is_read_free_rpl_table =
+#if 0 // MARIAROCKS_NOT_YET : read-free replication is not supported
+ rdb_read_free_regex_handler.matches(base_tablename());
+#else
+ false;
+#endif
+}
+
+void Rdb_tbl_def::set_name(const std::string &name) {
+ int err MY_ATTRIBUTE((__unused__));
+
+ m_dbname_tablename = name;
+ err = rdb_split_normalized_tablename(name, &m_dbname, &m_tablename,
+ &m_partition);
+ DBUG_ASSERT(err == 0);
+
+ check_if_is_mysql_system_table();
+}
+
+GL_INDEX_ID Rdb_tbl_def::get_autoincr_gl_index_id() {
+ for (uint i = 0; i < m_key_count; i++) {
+ auto &k = m_key_descr_arr[i];
+ if (k->m_index_type == Rdb_key_def::INDEX_TYPE_PRIMARY ||
+ k->m_index_type == Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY) {
+ return k->get_gl_index_id();
+ }
+ }
+
+ // Every table must have a primary key, even if it's hidden.
+ abort();
+ return GL_INDEX_ID();
+}
+
+void Rdb_ddl_manager::erase_index_num(const GL_INDEX_ID &gl_index_id) {
+ m_index_num_to_keydef.erase(gl_index_id);
+}
+
+void Rdb_ddl_manager::add_uncommitted_keydefs(
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes) {
+ mysql_rwlock_wrlock(&m_rwlock);
+ for (const auto &index : indexes) {
+ m_index_num_to_uncommitted_keydef[index->get_gl_index_id()] = index;
+ }
+ mysql_rwlock_unlock(&m_rwlock);
+}
+
+void Rdb_ddl_manager::remove_uncommitted_keydefs(
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes) {
+ mysql_rwlock_wrlock(&m_rwlock);
+ for (const auto &index : indexes) {
+ m_index_num_to_uncommitted_keydef.erase(index->get_gl_index_id());
+ }
+ mysql_rwlock_unlock(&m_rwlock);
+}
+
+namespace // anonymous namespace = not visible outside this source file
+{
+struct Rdb_validate_tbls : public Rdb_tables_scanner {
+ using tbl_info_t = std::pair<std::string, bool>;
+ using tbl_list_t = std::map<std::string, std::set<tbl_info_t>>;
+
+ tbl_list_t m_list;
+
+ int add_table(Rdb_tbl_def *tdef) override;
+
+ bool compare_to_actual_tables(const std::string &datadir, bool *has_errors);
+
+ bool scan_for_frms(const std::string &datadir, const std::string &dbname,
+ bool *has_errors);
+
+ bool check_frm_file(const std::string &fullpath, const std::string &dbname,
+ const std::string &tablename, bool *has_errors);
+};
+} // anonymous namespace
+
+/*
+ Get a list of tables that we expect to have .frm files for. This will use the
+ information just read from the RocksDB data dictionary.
+*/
+int Rdb_validate_tbls::add_table(Rdb_tbl_def *tdef) {
+ DBUG_ASSERT(tdef != nullptr);
+
+ /* Add the database/table into the list that are not temp table */
+ if (tdef->base_tablename().find(tmp_file_prefix) == std::string::npos) {
+ bool is_partition = tdef->base_partition().size() != 0;
+ m_list[tdef->base_dbname()].insert(
+ tbl_info_t(tdef->base_tablename(), is_partition));
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Access the .frm file for this dbname/tablename and see if it is a RocksDB
+ table (or partition table).
+*/
+bool Rdb_validate_tbls::check_frm_file(const std::string &fullpath,
+ const std::string &dbname,
+ const std::string &tablename,
+ bool *has_errors) {
+ /* Check this .frm file to see what engine it uses */
+ String fullfilename(fullpath.c_str(), &my_charset_bin);
+ fullfilename.append(FN_DIRSEP);
+ fullfilename.append(tablename.c_str());
+ fullfilename.append(".frm");
+
+ /*
+ This function will return the legacy_db_type of the table. Currently
+ it does not reference the first parameter (THD* thd), but if it ever
+ did in the future we would need to make a version that does it without
+ the connection handle as we don't have one here.
+ */
+ char eng_type_buf[NAME_CHAR_LEN+1];
+ LEX_CSTRING eng_type_str = {eng_type_buf, 0};
+ enum Table_type type = dd_frm_type(nullptr, fullfilename.c_ptr(), &eng_type_str);
+ if (type == TABLE_TYPE_UNKNOWN) {
+ // NO_LINT_DEBUG
+ sql_print_warning("RocksDB: Failed to open/read .from file: %s",
+ fullfilename.ptr());
+ return false;
+ }
+
+ if (type == TABLE_TYPE_NORMAL) {
+ /* For a RocksDB table do we have a reference in the data dictionary? */
+ if (!strncmp(eng_type_str.str, "ROCKSDB", eng_type_str.length)) {
+ /*
+ Attempt to remove the table entry from the list of tables. If this
+ fails then we know we had a .frm file that wasn't registered in RocksDB.
+ */
+ tbl_info_t element(tablename, false);
+ if (m_list.count(dbname) == 0 || m_list[dbname].erase(element) == 0) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "RocksDB: Schema mismatch - "
+ "A .frm file exists for table %s.%s, "
+ "but that table is not registered in RocksDB",
+ dbname.c_str(), tablename.c_str());
+ *has_errors = true;
+ }
+ } else if (!strncmp(eng_type_str.str, "partition", eng_type_str.length)) {
+ /*
+ For partition tables, see if it is in the m_list as a partition,
+ but don't generate an error if it isn't there - we don't know that the
+ .frm is for RocksDB.
+ */
+ if (m_list.count(dbname) > 0) {
+ m_list[dbname].erase(tbl_info_t(tablename, true));
+ }
+ }
+ }
+
+ return true;
+}
+
+/* Scan the database subdirectory for .frm files */
+bool Rdb_validate_tbls::scan_for_frms(const std::string &datadir,
+ const std::string &dbname,
+ bool *has_errors) {
+ bool result = true;
+ std::string fullpath = datadir + dbname;
+ struct st_my_dir *dir_info = my_dir(fullpath.c_str(), MYF(MY_DONT_SORT));
+
+ /* Access the directory */
+ if (dir_info == nullptr) {
+ // NO_LINT_DEBUG
+ sql_print_warning("RocksDB: Could not open database directory: %s",
+ fullpath.c_str());
+ return false;
+ }
+
+ /* Scan through the files in the directory */
+ struct fileinfo *file_info = dir_info->dir_entry;
+ for (uint ii = 0; ii < dir_info->number_of_files; ii++, file_info++) {
+ /* Find .frm files that are not temp files (those that contain '#sql') */
+ const char *ext = strrchr(file_info->name, '.');
+ if (ext != nullptr && strstr(file_info->name, tmp_file_prefix) == nullptr &&
+ strcmp(ext, ".frm") == 0) {
+ std::string tablename =
+ std::string(file_info->name, ext - file_info->name);
+
+ /* Check to see if the .frm file is from RocksDB */
+ if (!check_frm_file(fullpath, dbname, tablename, has_errors)) {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ /* Remove any databases who have no more tables listed */
+ if (m_list.count(dbname) == 1 && m_list[dbname].size() == 0) {
+ m_list.erase(dbname);
+ }
+
+ /* Release the directory entry */
+ my_dirend(dir_info);
+
+ return result;
+}
+
+/*
+ Scan the datadir for all databases (subdirectories) and get a list of .frm
+ files they contain
+*/
+bool Rdb_validate_tbls::compare_to_actual_tables(const std::string &datadir,
+ bool *has_errors) {
+ bool result = true;
+ struct st_my_dir *dir_info;
+ struct fileinfo *file_info;
+
+ dir_info = my_dir(datadir.c_str(), MYF(MY_DONT_SORT | MY_WANT_STAT));
+ if (dir_info == nullptr) {
+ // NO_LINT_DEBUG
+ sql_print_warning("RocksDB: could not open datadir: %s", datadir.c_str());
+ return false;
+ }
+
+ file_info = dir_info->dir_entry;
+ for (uint ii = 0; ii < dir_info->number_of_files; ii++, file_info++) {
+ /* Ignore files/dirs starting with '.' */
+ if (file_info->name[0] == '.') continue;
+
+ /* Ignore all non-directory files */
+ if (!MY_S_ISDIR(file_info->mystat->st_mode)) continue;
+
+ /* Scan all the .frm files in the directory */
+ if (!scan_for_frms(datadir, file_info->name, has_errors)) {
+ result = false;
+ break;
+ }
+ }
+
+ /* Release the directory info */
+ my_dirend(dir_info);
+
+ return result;
+}
+
+/*
+ Validate that all auto increment values in the data dictionary are on a
+ supported version.
+*/
+bool Rdb_ddl_manager::validate_auto_incr() {
+ std::unique_ptr<rocksdb::Iterator> it(m_dict->new_iterator());
+
+ uchar auto_incr_entry[Rdb_key_def::INDEX_NUMBER_SIZE];
+ rdb_netbuf_store_index(auto_incr_entry, Rdb_key_def::AUTO_INC);
+ const rocksdb::Slice auto_incr_entry_slice(
+ reinterpret_cast<char *>(auto_incr_entry),
+ Rdb_key_def::INDEX_NUMBER_SIZE);
+ for (it->Seek(auto_incr_entry_slice); it->Valid(); it->Next()) {
+ const rocksdb::Slice key = it->key();
+ const rocksdb::Slice val = it->value();
+ GL_INDEX_ID gl_index_id;
+
+ if (key.size() >= Rdb_key_def::INDEX_NUMBER_SIZE &&
+ memcmp(key.data(), auto_incr_entry, Rdb_key_def::INDEX_NUMBER_SIZE)) {
+ break;
+ }
+
+ if (key.size() != Rdb_key_def::INDEX_NUMBER_SIZE * 3) {
+ return false;
+ }
+
+ if (val.size() <= Rdb_key_def::VERSION_SIZE) {
+ return false;
+ }
+
+ // Check if we have orphaned entries for whatever reason by cross
+ // referencing ddl entries.
+ auto ptr = reinterpret_cast<const uchar *>(key.data());
+ ptr += Rdb_key_def::INDEX_NUMBER_SIZE;
+ rdb_netbuf_read_gl_index(&ptr, &gl_index_id);
+ if (!m_dict->get_index_info(gl_index_id, nullptr)) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "RocksDB: AUTOINC mismatch - "
+ "Index number (%u, %u) found in AUTOINC "
+ "but does not exist as a DDL entry",
+ gl_index_id.cf_id, gl_index_id.index_id);
+ return false;
+ }
+
+ ptr = reinterpret_cast<const uchar *>(val.data());
+ const int version = rdb_netbuf_read_uint16(&ptr);
+ if (version > Rdb_key_def::AUTO_INCREMENT_VERSION) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "RocksDB: AUTOINC mismatch - "
+ "Index number (%u, %u) found in AUTOINC "
+ "is on unsupported version %d",
+ gl_index_id.cf_id, gl_index_id.index_id, version);
+ return false;
+ }
+ }
+
+ if (!it->status().ok()) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ Validate that all the tables in the RocksDB database dictionary match the .frm
+ files in the datadir
+*/
+bool Rdb_ddl_manager::validate_schemas(void) {
+ bool has_errors = false;
+ const std::string datadir = std::string(mysql_real_data_home);
+ Rdb_validate_tbls table_list;
+
+ /* Get the list of tables from the database dictionary */
+ if (scan_for_tables(&table_list) != 0) {
+ return false;
+ }
+
+ /* Compare that to the list of actual .frm files */
+ if (!table_list.compare_to_actual_tables(datadir, &has_errors)) {
+ return false;
+ }
+
+ /*
+ Any tables left in the tables list are ones that are registered in RocksDB
+ but don't have .frm files.
+ */
+ for (const auto &db : table_list.m_list) {
+ for (const auto &table : db.second) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "RocksDB: Schema mismatch - "
+ "Table %s.%s is registered in RocksDB "
+ "but does not have a .frm file",
+ db.first.c_str(), table.first.c_str());
+ has_errors = true;
+ }
+ }
+
+ return !has_errors;
+}
+
+bool Rdb_ddl_manager::init(Rdb_dict_manager *const dict_arg,
+ Rdb_cf_manager *const cf_manager,
+ const uint32_t validate_tables) {
+ m_dict = dict_arg;
+ mysql_rwlock_init(0, &m_rwlock);
+
+ /* Read the data dictionary and populate the hash */
+ uchar ddl_entry[Rdb_key_def::INDEX_NUMBER_SIZE];
+ rdb_netbuf_store_index(ddl_entry, Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER);
+ const rocksdb::Slice ddl_entry_slice((char *)ddl_entry,
+ Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ /* Reading data dictionary should always skip bloom filter */
+ rocksdb::Iterator *it = m_dict->new_iterator();
+ int i = 0;
+
+ uint max_index_id_in_dict = 0;
+ m_dict->get_max_index_id(&max_index_id_in_dict);
+
+ for (it->Seek(ddl_entry_slice); it->Valid(); it->Next()) {
+ const uchar *ptr;
+ const uchar *ptr_end;
+ const rocksdb::Slice key = it->key();
+ const rocksdb::Slice val = it->value();
+
+ if (key.size() >= Rdb_key_def::INDEX_NUMBER_SIZE &&
+ memcmp(key.data(), ddl_entry, Rdb_key_def::INDEX_NUMBER_SIZE)) {
+ break;
+ }
+
+ if (key.size() <= Rdb_key_def::INDEX_NUMBER_SIZE) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Table_store: key has length %d (corruption?)",
+ (int)key.size());
+ return true;
+ }
+
+ Rdb_tbl_def *const tdef =
+ new Rdb_tbl_def(key, Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ // Now, read the DDLs.
+ const int real_val_size = val.size() - Rdb_key_def::VERSION_SIZE;
+ if (real_val_size % Rdb_key_def::PACKED_SIZE * 2 > 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Table_store: invalid keylist for table %s",
+ tdef->full_tablename().c_str());
+ return true;
+ }
+ tdef->m_key_count = real_val_size / (Rdb_key_def::PACKED_SIZE * 2);
+ tdef->m_key_descr_arr = new std::shared_ptr<Rdb_key_def>[tdef->m_key_count];
+
+ ptr = reinterpret_cast<const uchar *>(val.data());
+ const int version = rdb_netbuf_read_uint16(&ptr);
+ if (version != Rdb_key_def::DDL_ENTRY_INDEX_VERSION) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: DDL ENTRY Version was not expected."
+ "Expected: %d, Actual: %d",
+ Rdb_key_def::DDL_ENTRY_INDEX_VERSION, version);
+ return true;
+ }
+ ptr_end = ptr + real_val_size;
+ for (uint keyno = 0; ptr < ptr_end; keyno++) {
+ GL_INDEX_ID gl_index_id;
+ rdb_netbuf_read_gl_index(&ptr, &gl_index_id);
+ uint flags = 0;
+ struct Rdb_index_info index_info;
+ if (!m_dict->get_index_info(gl_index_id, &index_info)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Could not get index information "
+ "for Index Number (%u,%u), table %s",
+ gl_index_id.cf_id, gl_index_id.index_id,
+ tdef->full_tablename().c_str());
+ return true;
+ }
+ if (max_index_id_in_dict < gl_index_id.index_id) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Found max index id %u from data dictionary "
+ "but also found larger index id %u from dictionary. "
+ "This should never happen and possibly a bug.",
+ max_index_id_in_dict, gl_index_id.index_id);
+ return true;
+ }
+ if (!m_dict->get_cf_flags(gl_index_id.cf_id, &flags)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Could not get Column Family Flags "
+ "for CF Number %d, table %s",
+ gl_index_id.cf_id, tdef->full_tablename().c_str());
+ return true;
+ }
+
+ if ((flags & Rdb_key_def::AUTO_CF_FLAG) != 0) {
+ // The per-index cf option is deprecated. Make sure we don't have the
+ // flag set in any existing database. NO_LINT_DEBUG
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: The defunct AUTO_CF_FLAG is enabled for CF "
+ "number %d, table %s",
+ gl_index_id.cf_id, tdef->full_tablename().c_str());
+ }
+
+ rocksdb::ColumnFamilyHandle *const cfh =
+ cf_manager->get_cf(gl_index_id.cf_id);
+ DBUG_ASSERT(cfh != nullptr);
+
+ uint32 ttl_rec_offset =
+ Rdb_key_def::has_index_flag(index_info.m_index_flags,
+ Rdb_key_def::TTL_FLAG)
+ ? Rdb_key_def::calculate_index_flag_offset(
+ index_info.m_index_flags, Rdb_key_def::TTL_FLAG)
+ : UINT_MAX;
+
+ /*
+ We can't fully initialize Rdb_key_def object here, because full
+ initialization requires that there is an open TABLE* where we could
+ look at Field* objects and set max_length and other attributes
+ */
+ tdef->m_key_descr_arr[keyno] = std::make_shared<Rdb_key_def>(
+ gl_index_id.index_id, keyno, cfh, index_info.m_index_dict_version,
+ index_info.m_index_type, index_info.m_kv_version,
+ flags & Rdb_key_def::REVERSE_CF_FLAG,
+ flags & Rdb_key_def::PER_PARTITION_CF_FLAG, "",
+ m_dict->get_stats(gl_index_id), index_info.m_index_flags,
+ ttl_rec_offset, index_info.m_ttl_duration);
+ }
+ put(tdef);
+ i++;
+ }
+
+ /*
+ If validate_tables is greater than 0 run the validation. Only fail the
+ initialzation if the setting is 1. If the setting is 2 we continue.
+ */
+ if (validate_tables > 0) {
+ std::string msg;
+ if (!validate_schemas()) {
+ msg =
+ "RocksDB: Problems validating data dictionary "
+ "against .frm files, exiting";
+ } else if (!validate_auto_incr()) {
+ msg =
+ "RocksDB: Problems validating auto increment values in "
+ "data dictionary, exiting";
+ }
+ if (validate_tables == 1 && !msg.empty()) {
+ // NO_LINT_DEBUG
+ sql_print_error("%s", msg.c_str());
+ return true;
+ }
+ }
+
+ // index ids used by applications should not conflict with
+ // data dictionary index ids
+ if (max_index_id_in_dict < Rdb_key_def::END_DICT_INDEX_ID) {
+ max_index_id_in_dict = Rdb_key_def::END_DICT_INDEX_ID;
+ }
+
+ m_sequence.init(max_index_id_in_dict + 1);
+
+ if (!it->status().ok()) {
+ rdb_log_status_error(it->status(), "Table_store load error");
+ return true;
+ }
+ delete it;
+ // NO_LINT_DEBUG
+ sql_print_information("RocksDB: Table_store: loaded DDL data for %d tables",
+ i);
+ return false;
+}
+
+Rdb_tbl_def *Rdb_ddl_manager::find(const std::string &table_name,
+ const bool lock) {
+ if (lock) {
+ mysql_rwlock_rdlock(&m_rwlock);
+ }
+
+ Rdb_tbl_def *rec = nullptr;
+ const auto it = m_ddl_map.find(table_name);
+ if (it != m_ddl_map.end()) {
+ rec = it->second;
+ }
+
+ if (lock) {
+ mysql_rwlock_unlock(&m_rwlock);
+ }
+
+ return rec;
+}
+
+// this is a safe version of the find() function below. It acquires a read
+// lock on m_rwlock to make sure the Rdb_key_def is not discarded while we
+// are finding it. Copying it into 'ret' increments the count making sure
+// that the object will not be discarded until we are finished with it.
+std::shared_ptr<const Rdb_key_def> Rdb_ddl_manager::safe_find(
+ GL_INDEX_ID gl_index_id) {
+ std::shared_ptr<const Rdb_key_def> ret(nullptr);
+
+ mysql_rwlock_rdlock(&m_rwlock);
+
+ auto it = m_index_num_to_keydef.find(gl_index_id);
+ if (it != m_index_num_to_keydef.end()) {
+ const auto table_def = find(it->second.first, false);
+ if (table_def && it->second.second < table_def->m_key_count) {
+ const auto &kd = table_def->m_key_descr_arr[it->second.second];
+ if (kd->max_storage_fmt_length() != 0) {
+ ret = kd;
+ }
+ }
+ } else {
+ auto it = m_index_num_to_uncommitted_keydef.find(gl_index_id);
+ if (it != m_index_num_to_uncommitted_keydef.end()) {
+ const auto &kd = it->second;
+ if (kd->max_storage_fmt_length() != 0) {
+ ret = kd;
+ }
+ }
+ }
+
+ mysql_rwlock_unlock(&m_rwlock);
+
+ return ret;
+}
+
+// this method assumes at least read-only lock on m_rwlock
+const std::shared_ptr<Rdb_key_def> &Rdb_ddl_manager::find(
+ GL_INDEX_ID gl_index_id) {
+ auto it = m_index_num_to_keydef.find(gl_index_id);
+ if (it != m_index_num_to_keydef.end()) {
+ auto table_def = find(it->second.first, false);
+ if (table_def) {
+ if (it->second.second < table_def->m_key_count) {
+ return table_def->m_key_descr_arr[it->second.second];
+ }
+ }
+ } else {
+ auto it = m_index_num_to_uncommitted_keydef.find(gl_index_id);
+ if (it != m_index_num_to_uncommitted_keydef.end()) {
+ return it->second;
+ }
+ }
+
+ static std::shared_ptr<Rdb_key_def> empty = nullptr;
+
+ return empty;
+}
+
+// this method returns the name of the table based on an index id. It acquires
+// a read lock on m_rwlock.
+const std::string Rdb_ddl_manager::safe_get_table_name(
+ const GL_INDEX_ID &gl_index_id) {
+ std::string ret;
+ mysql_rwlock_rdlock(&m_rwlock);
+ auto it = m_index_num_to_keydef.find(gl_index_id);
+ if (it != m_index_num_to_keydef.end()) {
+ ret = it->second.first;
+ }
+ mysql_rwlock_unlock(&m_rwlock);
+ return ret;
+}
+
+void Rdb_ddl_manager::set_stats(
+ const std::unordered_map<GL_INDEX_ID, Rdb_index_stats> &stats) {
+ mysql_rwlock_wrlock(&m_rwlock);
+ for (auto src : stats) {
+ const auto &keydef = find(src.second.m_gl_index_id);
+ if (keydef) {
+ keydef->m_stats = src.second;
+ m_stats2store[keydef->m_stats.m_gl_index_id] = keydef->m_stats;
+ }
+ }
+ mysql_rwlock_unlock(&m_rwlock);
+}
+
+void Rdb_ddl_manager::adjust_stats(
+ const std::vector<Rdb_index_stats> &new_data,
+ const std::vector<Rdb_index_stats> &deleted_data) {
+ mysql_rwlock_wrlock(&m_rwlock);
+ int i = 0;
+ for (const auto &data : {new_data, deleted_data}) {
+ for (const auto &src : data) {
+ const auto &keydef = find(src.m_gl_index_id);
+ if (keydef) {
+ keydef->m_stats.m_distinct_keys_per_prefix.resize(
+ keydef->get_key_parts());
+ keydef->m_stats.merge(src, i == 0, keydef->max_storage_fmt_length());
+ m_stats2store[keydef->m_stats.m_gl_index_id] = keydef->m_stats;
+ }
+ }
+ i++;
+ }
+ const bool should_save_stats = !m_stats2store.empty();
+ mysql_rwlock_unlock(&m_rwlock);
+ if (should_save_stats) {
+ // Queue an async persist_stats(false) call to the background thread.
+ rdb_queue_save_stats_request();
+ }
+}
+
+void Rdb_ddl_manager::persist_stats(const bool sync) {
+ mysql_rwlock_wrlock(&m_rwlock);
+ const auto local_stats2store = std::move(m_stats2store);
+ m_stats2store.clear();
+ mysql_rwlock_unlock(&m_rwlock);
+
+ // Persist stats
+ const std::unique_ptr<rocksdb::WriteBatch> wb = m_dict->begin();
+ std::vector<Rdb_index_stats> stats;
+ std::transform(local_stats2store.begin(), local_stats2store.end(),
+ std::back_inserter(stats),
+ [](const std::pair<GL_INDEX_ID, Rdb_index_stats> &s) {
+ return s.second;
+ });
+ m_dict->add_stats(wb.get(), stats);
+ m_dict->commit(wb.get(), sync);
+}
+
+/*
+ Put table definition of `tbl` into the mapping, and also write it to the
+ on-disk data dictionary.
+*/
+
+int Rdb_ddl_manager::put_and_write(Rdb_tbl_def *const tbl,
+ rocksdb::WriteBatch *const batch) {
+ Rdb_buf_writer<FN_LEN * 2 + Rdb_key_def::INDEX_NUMBER_SIZE> buf_writer;
+
+ buf_writer.write_index(Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER);
+
+ const std::string &dbname_tablename = tbl->full_tablename();
+ buf_writer.write(dbname_tablename.c_str(), dbname_tablename.size());
+
+ int res;
+ if ((res = tbl->put_dict(m_dict, batch, buf_writer.to_slice()))) {
+ return res;
+ }
+ if ((res = put(tbl))) {
+ return res;
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+/* Return 0 - ok, other value - error */
+/* TODO:
+ This function modifies m_ddl_map and m_index_num_to_keydef.
+ However, these changes need to be reversed if dict_manager.commit fails
+ See the discussion here: https://reviews.facebook.net/D35925#inline-259167
+ Tracked by https://github.com/facebook/mysql-5.6/issues/33
+*/
+int Rdb_ddl_manager::put(Rdb_tbl_def *const tbl, const bool lock) {
+ Rdb_tbl_def *rec;
+ const std::string &dbname_tablename = tbl->full_tablename();
+
+ if (lock) mysql_rwlock_wrlock(&m_rwlock);
+
+ // We have to do this find because 'tbl' is not yet in the list. We need
+ // to find the one we are replacing ('rec')
+ rec = find(dbname_tablename, false);
+ if (rec) {
+ // Free the old record.
+ delete rec;
+ m_ddl_map.erase(dbname_tablename);
+ }
+ m_ddl_map.emplace(dbname_tablename, tbl);
+
+ for (uint keyno = 0; keyno < tbl->m_key_count; keyno++) {
+ m_index_num_to_keydef[tbl->m_key_descr_arr[keyno]->get_gl_index_id()] =
+ std::make_pair(dbname_tablename, keyno);
+ }
+ tbl->check_and_set_read_free_rpl_table();
+
+ if (lock) mysql_rwlock_unlock(&m_rwlock);
+ return 0;
+}
+
+void Rdb_ddl_manager::remove(Rdb_tbl_def *const tbl,
+ rocksdb::WriteBatch *const batch,
+ const bool lock) {
+ if (lock) mysql_rwlock_wrlock(&m_rwlock);
+
+ Rdb_buf_writer<FN_LEN * 2 + Rdb_key_def::INDEX_NUMBER_SIZE> key_writer;
+ key_writer.write_index(Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER);
+ const std::string &dbname_tablename = tbl->full_tablename();
+ key_writer.write(dbname_tablename.c_str(), dbname_tablename.size());
+
+ m_dict->delete_key(batch, key_writer.to_slice());
+
+ const auto it = m_ddl_map.find(dbname_tablename);
+ if (it != m_ddl_map.end()) {
+ // Free Rdb_tbl_def
+ delete it->second;
+
+ m_ddl_map.erase(it);
+ }
+
+ if (lock) mysql_rwlock_unlock(&m_rwlock);
+}
+
+bool Rdb_ddl_manager::rename(const std::string &from, const std::string &to,
+ rocksdb::WriteBatch *const batch) {
+ Rdb_tbl_def *rec;
+ Rdb_tbl_def *new_rec;
+ bool res = true;
+ Rdb_buf_writer<FN_LEN * 2 + Rdb_key_def::INDEX_NUMBER_SIZE> new_buf_writer;
+
+ mysql_rwlock_wrlock(&m_rwlock);
+ if (!(rec = find(from, false))) {
+ mysql_rwlock_unlock(&m_rwlock);
+ return true;
+ }
+
+ new_rec = new Rdb_tbl_def(to);
+
+ new_rec->m_key_count = rec->m_key_count;
+ new_rec->m_auto_incr_val =
+ rec->m_auto_incr_val.load(std::memory_order_relaxed);
+ new_rec->m_key_descr_arr = rec->m_key_descr_arr;
+
+ new_rec->m_hidden_pk_val =
+ rec->m_hidden_pk_val.load(std::memory_order_relaxed);
+
+ // so that it's not free'd when deleting the old rec
+ rec->m_key_descr_arr = nullptr;
+
+ // Create a new key
+ new_buf_writer.write_index(Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER);
+
+ const std::string &dbname_tablename = new_rec->full_tablename();
+ new_buf_writer.write(dbname_tablename.c_str(), dbname_tablename.size());
+
+ // Create a key to add
+ if (!new_rec->put_dict(m_dict, batch, new_buf_writer.to_slice())) {
+ remove(rec, batch, false);
+ put(new_rec, false);
+ res = false; // ok
+ }
+
+ mysql_rwlock_unlock(&m_rwlock);
+ return res;
+}
+
+void Rdb_ddl_manager::cleanup() {
+ for (const auto &kv : m_ddl_map) {
+ delete kv.second;
+ }
+ m_ddl_map.clear();
+
+ mysql_rwlock_destroy(&m_rwlock);
+ m_sequence.cleanup();
+}
+
+int Rdb_ddl_manager::scan_for_tables(Rdb_tables_scanner *const tables_scanner) {
+ int ret;
+ Rdb_tbl_def *rec;
+
+ DBUG_ASSERT(tables_scanner != nullptr);
+
+ mysql_rwlock_rdlock(&m_rwlock);
+
+ ret = 0;
+
+ for (const auto &kv : m_ddl_map) {
+ rec = kv.second;
+ ret = tables_scanner->add_table(rec);
+ if (ret) break;
+ }
+
+ mysql_rwlock_unlock(&m_rwlock);
+ return ret;
+}
+
+/*
+ Rdb_binlog_manager class implementation
+*/
+
+bool Rdb_binlog_manager::init(Rdb_dict_manager *const dict_arg) {
+ DBUG_ASSERT(dict_arg != nullptr);
+ m_dict = dict_arg;
+
+ m_key_writer.reset();
+ m_key_writer.write_index(Rdb_key_def::BINLOG_INFO_INDEX_NUMBER);
+ m_key_slice = m_key_writer.to_slice();
+ return false;
+}
+
+void Rdb_binlog_manager::cleanup() {}
+
+/**
+ Set binlog name, pos and optionally gtid into WriteBatch.
+ This function should be called as part of transaction commit,
+ since binlog info is set only at transaction commit.
+ Actual write into RocksDB is not done here, so checking if
+ write succeeded or not is not possible here.
+ @param binlog_name Binlog name
+ @param binlog_pos Binlog pos
+ @param batch WriteBatch
+*/
+void Rdb_binlog_manager::update(const char *const binlog_name,
+ const my_off_t binlog_pos,
+ rocksdb::WriteBatchBase *const batch) {
+ if (binlog_name && binlog_pos) {
+ // max binlog length (512) + binlog pos (4) + binlog gtid (57) < 1024
+ const size_t RDB_MAX_BINLOG_INFO_LEN = 1024;
+ Rdb_buf_writer<RDB_MAX_BINLOG_INFO_LEN> value_writer;
+
+ // store version
+ value_writer.write_uint16(Rdb_key_def::BINLOG_INFO_INDEX_NUMBER_VERSION);
+
+ // store binlog file name length
+ DBUG_ASSERT(strlen(binlog_name) <= FN_REFLEN);
+ const uint16_t binlog_name_len = strlen(binlog_name);
+ value_writer.write_uint16(binlog_name_len);
+
+ // store binlog file name
+ value_writer.write(binlog_name, binlog_name_len);
+
+ // store binlog pos
+ value_writer.write_uint32(binlog_pos);
+
+#ifdef MARIADB_MERGE_2019
+ // store binlog gtid length.
+ // If gtid was not set, store 0 instead
+ const uint16_t binlog_max_gtid_len =
+ binlog_max_gtid ? strlen(binlog_max_gtid) : 0;
+ value_writer.write_uint16(binlog_max_gtid_len);
+
+ if (binlog_max_gtid_len > 0) {
+ // store binlog gtid
+ value_writer.write(binlog_max_gtid, binlog_max_gtid_len);
+ }
+#endif
+
+ m_dict->put_key(batch, m_key_slice, value_writer.to_slice());
+ }
+}
+
+/**
+ Read binlog committed entry stored in RocksDB, then unpack
+ @param[OUT] binlog_name Binlog name
+ @param[OUT] binlog_pos Binlog pos
+ @param[OUT] binlog_gtid Binlog GTID
+ @return
+ true is binlog info was found (valid behavior)
+ false otherwise
+*/
+bool Rdb_binlog_manager::read(char *const binlog_name,
+ my_off_t *const binlog_pos,
+ char *const binlog_gtid) const {
+ bool ret = false;
+ if (binlog_name) {
+ std::string value;
+ rocksdb::Status status = m_dict->get_value(m_key_slice, &value);
+ if (status.ok()) {
+ if (!unpack_value((const uchar *)value.c_str(), value.size(), binlog_name, binlog_pos,
+ binlog_gtid)) {
+ ret = true;
+ }
+ }
+ }
+ return ret;
+}
+
+/**
+ Unpack value then split into binlog_name, binlog_pos (and binlog_gtid)
+ @param[IN] value Binlog state info fetched from RocksDB
+ @param[OUT] binlog_name Binlog name
+ @param[OUT] binlog_pos Binlog pos
+ @param[OUT] binlog_gtid Binlog GTID
+ @return true on error
+*/
+bool Rdb_binlog_manager::unpack_value(const uchar *const value,
+ size_t value_size_arg,
+ char *const binlog_name,
+ my_off_t *const binlog_pos,
+ char *const binlog_gtid) const {
+ uint pack_len = 0;
+ intmax_t value_size= value_size_arg;
+
+ DBUG_ASSERT(binlog_pos != nullptr);
+
+ if ((value_size -= Rdb_key_def::VERSION_SIZE) < 0)
+ return true;
+ // read version
+ const uint16_t version = rdb_netbuf_to_uint16(value);
+
+ pack_len += Rdb_key_def::VERSION_SIZE;
+ if (version != Rdb_key_def::BINLOG_INFO_INDEX_NUMBER_VERSION) return true;
+
+ if ((value_size -= sizeof(uint16)) < 0)
+ return true;
+
+ // read binlog file name length
+ const uint16_t binlog_name_len = rdb_netbuf_to_uint16(value + pack_len);
+ pack_len += sizeof(uint16);
+
+ if (binlog_name_len >= (FN_REFLEN+1))
+ return true;
+
+ if ((value_size -= binlog_name_len) < 0)
+ return true;
+
+ if (binlog_name_len) {
+ // read and set binlog name
+ memcpy(binlog_name, value + pack_len, binlog_name_len);
+ binlog_name[binlog_name_len] = '\0';
+ pack_len += binlog_name_len;
+
+ if ((value_size -= sizeof(uint32)) < 0)
+ return true;
+ // read and set binlog pos
+ *binlog_pos = rdb_netbuf_to_uint32(value + pack_len);
+ pack_len += sizeof(uint32);
+
+ if ((value_size -= sizeof(uint16)) < 0)
+ return true;
+ // read gtid length
+ const uint16_t binlog_gtid_len = rdb_netbuf_to_uint16(value + pack_len);
+ pack_len += sizeof(uint16);
+
+ if (binlog_gtid_len >= GTID_BUF_LEN)
+ return true;
+ if ((value_size -= binlog_gtid_len) < 0)
+ return true;
+
+ if (binlog_gtid && binlog_gtid_len > 0) {
+ // read and set gtid
+ memcpy(binlog_gtid, value + pack_len, binlog_gtid_len);
+ binlog_gtid[binlog_gtid_len] = '\0';
+ pack_len += binlog_gtid_len;
+ }
+ }
+ return false;
+}
+
+/**
+ Inserts a row into mysql.slave_gtid_info table. Doing this inside
+ storage engine is more efficient than inserting/updating through MySQL.
+
+ @param[IN] id Primary key of the table.
+ @param[IN] db Database name. This is column 2 of the table.
+ @param[IN] gtid Gtid in human readable form. This is column 3 of the table.
+ @param[IN] write_batch Handle to storage engine writer.
+*/
+void Rdb_binlog_manager::update_slave_gtid_info(
+ const uint id, const char *const db, const char *const gtid,
+ rocksdb::WriteBatchBase *const write_batch) {
+ if (id && db && gtid) {
+ // Make sure that if the slave_gtid_info table exists we have a
+ // pointer to it via m_slave_gtid_info_tbl.
+ if (!m_slave_gtid_info_tbl.load()) {
+ m_slave_gtid_info_tbl.store(
+ rdb_get_ddl_manager()->find("mysql.slave_gtid_info"));
+ }
+ if (!m_slave_gtid_info_tbl.load()) {
+ // slave_gtid_info table is not present. Simply return.
+ return;
+ }
+ DBUG_ASSERT(m_slave_gtid_info_tbl.load()->m_key_count == 1);
+
+ const std::shared_ptr<const Rdb_key_def> &kd =
+ m_slave_gtid_info_tbl.load()->m_key_descr_arr[0];
+ String value;
+
+ // Build key
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE + 4> key_writer;
+ key_writer.write_index(kd->get_index_number());
+ key_writer.write_uint32(id);
+
+ // Build value
+ Rdb_buf_writer<128> value_writer;
+ DBUG_ASSERT(gtid);
+ const uint db_len = strlen(db);
+ const uint gtid_len = strlen(gtid);
+ // 1 byte used for flags. Empty here.
+ value_writer.write_byte(0);
+
+ // Write column 1.
+ DBUG_ASSERT(strlen(db) <= 64);
+ value_writer.write_byte(db_len);
+ value_writer.write(db, db_len);
+
+ // Write column 2.
+ DBUG_ASSERT(gtid_len <= 56);
+ value_writer.write_byte(gtid_len);
+ value_writer.write(gtid, gtid_len);
+
+ write_batch->Put(kd->get_cf(), key_writer.to_slice(),
+ value_writer.to_slice());
+ }
+}
+
+bool Rdb_dict_manager::init(rocksdb::TransactionDB *const rdb_dict,
+ Rdb_cf_manager *const cf_manager) {
+ DBUG_ASSERT(rdb_dict != nullptr);
+ DBUG_ASSERT(cf_manager != nullptr);
+
+ mysql_mutex_init(0, &m_mutex, MY_MUTEX_INIT_FAST);
+
+ m_db = rdb_dict;
+
+ m_system_cfh = cf_manager->get_or_create_cf(m_db, DEFAULT_SYSTEM_CF_NAME);
+ rocksdb::ColumnFamilyHandle *default_cfh =
+ cf_manager->get_cf(DEFAULT_CF_NAME);
+
+ // System CF and default CF should be initialized
+ if (m_system_cfh == nullptr || default_cfh == nullptr) {
+ return HA_EXIT_FAILURE;
+ }
+
+ rdb_netbuf_store_index(m_key_buf_max_index_id, Rdb_key_def::MAX_INDEX_ID);
+
+ m_key_slice_max_index_id =
+ rocksdb::Slice(reinterpret_cast<char *>(m_key_buf_max_index_id),
+ Rdb_key_def::INDEX_NUMBER_SIZE);
+
+ resume_drop_indexes();
+ rollback_ongoing_index_creation();
+
+ // Initialize system CF and default CF flags
+ const std::unique_ptr<rocksdb::WriteBatch> wb = begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ add_cf_flags(batch, m_system_cfh->GetID(), 0);
+ add_cf_flags(batch, default_cfh->GetID(), 0);
+ commit(batch);
+
+ return HA_EXIT_SUCCESS;
+}
+
+std::unique_ptr<rocksdb::WriteBatch> Rdb_dict_manager::begin() const {
+ return std::unique_ptr<rocksdb::WriteBatch>(new rocksdb::WriteBatch);
+}
+
+void Rdb_dict_manager::put_key(rocksdb::WriteBatchBase *const batch,
+ const rocksdb::Slice &key,
+ const rocksdb::Slice &value) const {
+ batch->Put(m_system_cfh, key, value);
+}
+
+rocksdb::Status Rdb_dict_manager::get_value(const rocksdb::Slice &key,
+ std::string *const value) const {
+ rocksdb::ReadOptions options;
+ options.total_order_seek = true;
+ return m_db->Get(options, m_system_cfh, key, value);
+}
+
+void Rdb_dict_manager::delete_key(rocksdb::WriteBatchBase *batch,
+ const rocksdb::Slice &key) const {
+ batch->Delete(m_system_cfh, key);
+}
+
+rocksdb::Iterator *Rdb_dict_manager::new_iterator() const {
+ /* Reading data dictionary should always skip bloom filter */
+ rocksdb::ReadOptions read_options;
+ read_options.total_order_seek = true;
+ return m_db->NewIterator(read_options, m_system_cfh);
+}
+
+int Rdb_dict_manager::commit(rocksdb::WriteBatch *const batch,
+ const bool sync) const {
+ if (!batch) return HA_ERR_ROCKSDB_COMMIT_FAILED;
+ int res = HA_EXIT_SUCCESS;
+ rocksdb::WriteOptions options;
+ options.sync = sync;
+ rocksdb::TransactionDBWriteOptimizations optimize;
+ optimize.skip_concurrency_control = true;
+ rocksdb::Status s = m_db->Write(options, optimize, batch);
+ res = !s.ok(); // we return true when something failed
+ if (res) {
+ rdb_handle_io_error(s, RDB_IO_ERROR_DICT_COMMIT);
+ }
+ batch->Clear();
+ return res;
+}
+
+void Rdb_dict_manager::dump_index_id(uchar *const netbuf,
+ Rdb_key_def::DATA_DICT_TYPE dict_type,
+ const GL_INDEX_ID &gl_index_id) {
+ rdb_netbuf_store_uint32(netbuf, dict_type);
+ rdb_netbuf_store_uint32(netbuf + Rdb_key_def::INDEX_NUMBER_SIZE,
+ gl_index_id.cf_id);
+ rdb_netbuf_store_uint32(netbuf + 2 * Rdb_key_def::INDEX_NUMBER_SIZE,
+ gl_index_id.index_id);
+}
+
+void Rdb_dict_manager::delete_with_prefix(
+ rocksdb::WriteBatch *const batch, Rdb_key_def::DATA_DICT_TYPE dict_type,
+ const GL_INDEX_ID &gl_index_id) const {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, dict_type, gl_index_id);
+
+ delete_key(batch, key_writer.to_slice());
+}
+
+void Rdb_dict_manager::add_or_update_index_cf_mapping(
+ rocksdb::WriteBatch *batch, struct Rdb_index_info *const index_info) const {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::INDEX_INFO,
+ index_info->m_gl_index_id);
+
+ Rdb_buf_writer<256> value_writer;
+
+ value_writer.write_uint16(Rdb_key_def::INDEX_INFO_VERSION_LATEST);
+ value_writer.write_byte(index_info->m_index_type);
+ value_writer.write_uint16(index_info->m_kv_version);
+ value_writer.write_uint32(index_info->m_index_flags);
+ value_writer.write_uint64(index_info->m_ttl_duration);
+
+ batch->Put(m_system_cfh, key_writer.to_slice(), value_writer.to_slice());
+}
+
+void Rdb_dict_manager::add_cf_flags(rocksdb::WriteBatch *const batch,
+ const uint32_t cf_id,
+ const uint32_t cf_flags) const {
+ DBUG_ASSERT(batch != nullptr);
+
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 2> key_writer;
+ key_writer.write_uint32(Rdb_key_def::CF_DEFINITION);
+ key_writer.write_uint32(cf_id);
+
+ Rdb_buf_writer<Rdb_key_def::VERSION_SIZE + Rdb_key_def::INDEX_NUMBER_SIZE>
+ value_writer;
+ value_writer.write_uint16(Rdb_key_def::CF_DEFINITION_VERSION);
+ value_writer.write_uint32(cf_flags);
+
+ batch->Put(m_system_cfh, key_writer.to_slice(), value_writer.to_slice());
+}
+
+void Rdb_dict_manager::delete_index_info(rocksdb::WriteBatch *batch,
+ const GL_INDEX_ID &gl_index_id) const {
+ delete_with_prefix(batch, Rdb_key_def::INDEX_INFO, gl_index_id);
+ delete_with_prefix(batch, Rdb_key_def::INDEX_STATISTICS, gl_index_id);
+ delete_with_prefix(batch, Rdb_key_def::AUTO_INC, gl_index_id);
+}
+
+bool Rdb_dict_manager::get_index_info(
+ const GL_INDEX_ID &gl_index_id,
+ struct Rdb_index_info *const index_info) const {
+ if (index_info) {
+ index_info->m_gl_index_id = gl_index_id;
+ }
+
+ bool found = false;
+ bool error = false;
+ std::string value;
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::INDEX_INFO, gl_index_id);
+
+ const rocksdb::Status &status = get_value(key_writer.to_slice(), &value);
+ if (status.ok()) {
+ if (!index_info) {
+ return true;
+ }
+
+ const uchar *const val = (const uchar *)value.c_str();
+ const uchar *ptr = val;
+ index_info->m_index_dict_version = rdb_netbuf_to_uint16(val);
+ ptr += RDB_SIZEOF_INDEX_INFO_VERSION;
+
+ switch (index_info->m_index_dict_version) {
+ case Rdb_key_def::INDEX_INFO_VERSION_FIELD_FLAGS:
+ /* Sanity check to prevent reading bogus TTL record. */
+ if (value.size() != RDB_SIZEOF_INDEX_INFO_VERSION +
+ RDB_SIZEOF_INDEX_TYPE + RDB_SIZEOF_KV_VERSION +
+ RDB_SIZEOF_INDEX_FLAGS +
+ ROCKSDB_SIZEOF_TTL_RECORD) {
+ error = true;
+ break;
+ }
+ index_info->m_index_type = rdb_netbuf_to_byte(ptr);
+ ptr += RDB_SIZEOF_INDEX_TYPE;
+ index_info->m_kv_version = rdb_netbuf_to_uint16(ptr);
+ ptr += RDB_SIZEOF_KV_VERSION;
+ index_info->m_index_flags = rdb_netbuf_to_uint32(ptr);
+ ptr += RDB_SIZEOF_INDEX_FLAGS;
+ index_info->m_ttl_duration = rdb_netbuf_to_uint64(ptr);
+ found = true;
+ break;
+
+ case Rdb_key_def::INDEX_INFO_VERSION_TTL:
+ /* Sanity check to prevent reading bogus into TTL record. */
+ if (value.size() != RDB_SIZEOF_INDEX_INFO_VERSION +
+ RDB_SIZEOF_INDEX_TYPE + RDB_SIZEOF_KV_VERSION +
+ ROCKSDB_SIZEOF_TTL_RECORD) {
+ error = true;
+ break;
+ }
+ index_info->m_index_type = rdb_netbuf_to_byte(ptr);
+ ptr += RDB_SIZEOF_INDEX_TYPE;
+ index_info->m_kv_version = rdb_netbuf_to_uint16(ptr);
+ ptr += RDB_SIZEOF_KV_VERSION;
+ index_info->m_ttl_duration = rdb_netbuf_to_uint64(ptr);
+ if ((index_info->m_kv_version ==
+ Rdb_key_def::PRIMARY_FORMAT_VERSION_TTL) &&
+ index_info->m_ttl_duration > 0) {
+ index_info->m_index_flags = Rdb_key_def::TTL_FLAG;
+ }
+ found = true;
+ break;
+
+ case Rdb_key_def::INDEX_INFO_VERSION_VERIFY_KV_FORMAT:
+ case Rdb_key_def::INDEX_INFO_VERSION_GLOBAL_ID:
+ index_info->m_index_type = rdb_netbuf_to_byte(ptr);
+ ptr += RDB_SIZEOF_INDEX_TYPE;
+ index_info->m_kv_version = rdb_netbuf_to_uint16(ptr);
+ found = true;
+ break;
+
+ default:
+ error = true;
+ break;
+ }
+
+ switch (index_info->m_index_type) {
+ case Rdb_key_def::INDEX_TYPE_PRIMARY:
+ case Rdb_key_def::INDEX_TYPE_HIDDEN_PRIMARY: {
+ error = index_info->m_kv_version >
+ Rdb_key_def::PRIMARY_FORMAT_VERSION_LATEST;
+ break;
+ }
+ case Rdb_key_def::INDEX_TYPE_SECONDARY:
+ error = index_info->m_kv_version >
+ Rdb_key_def::SECONDARY_FORMAT_VERSION_LATEST;
+ break;
+ default:
+ error = true;
+ break;
+ }
+ }
+
+ if (error) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Found invalid key version number (%u, %u, %u, %llu) "
+ "from data dictionary. This should never happen "
+ "and it may be a bug.",
+ index_info->m_index_dict_version, index_info->m_index_type,
+ index_info->m_kv_version, index_info->m_ttl_duration);
+ abort();
+ }
+
+ return found;
+}
+
+bool Rdb_dict_manager::get_cf_flags(const uint32_t cf_id,
+ uint32_t *const cf_flags) const {
+ DBUG_ASSERT(cf_flags != nullptr);
+
+ bool found = false;
+ std::string value;
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 2> key_writer;
+
+ key_writer.write_uint32(Rdb_key_def::CF_DEFINITION);
+ key_writer.write_uint32(cf_id);
+
+ const rocksdb::Status status = get_value(key_writer.to_slice(), &value);
+
+ if (status.ok()) {
+ const uchar *val = (const uchar *)value.c_str();
+ DBUG_ASSERT(val);
+
+ const uint16_t version = rdb_netbuf_to_uint16(val);
+
+ if (version == Rdb_key_def::CF_DEFINITION_VERSION) {
+ *cf_flags = rdb_netbuf_to_uint32(val + Rdb_key_def::VERSION_SIZE);
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+/*
+ Returning index ids that were marked as deleted (via DROP TABLE) but
+ still not removed by drop_index_thread yet, or indexes that are marked as
+ ongoing creation.
+ */
+void Rdb_dict_manager::get_ongoing_index_operation(
+ std::unordered_set<GL_INDEX_ID> *gl_index_ids,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const {
+ DBUG_ASSERT(dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING ||
+ dd_type == Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE> index_writer;
+ index_writer.write_uint32(dd_type);
+ const rocksdb::Slice index_slice = index_writer.to_slice();
+
+ rocksdb::Iterator *it = new_iterator();
+ for (it->Seek(index_slice); it->Valid(); it->Next()) {
+ rocksdb::Slice key = it->key();
+ const uchar *const ptr = (const uchar *)key.data();
+
+ /*
+ Ongoing drop/create index operations require key to be of the form:
+ dd_type + cf_id + index_id (== INDEX_NUMBER_SIZE * 3)
+
+ This may need to be changed in the future if we want to process a new
+ ddl_type with different format.
+ */
+ if (key.size() != Rdb_key_def::INDEX_NUMBER_SIZE * 3 ||
+ rdb_netbuf_to_uint32(ptr) != dd_type) {
+ break;
+ }
+
+ // We don't check version right now since currently we always store only
+ // Rdb_key_def::DDL_DROP_INDEX_ONGOING_VERSION = 1 as a value.
+ // If increasing version number, we need to add version check logic here.
+ GL_INDEX_ID gl_index_id;
+ gl_index_id.cf_id =
+ rdb_netbuf_to_uint32(ptr + Rdb_key_def::INDEX_NUMBER_SIZE);
+ gl_index_id.index_id =
+ rdb_netbuf_to_uint32(ptr + 2 * Rdb_key_def::INDEX_NUMBER_SIZE);
+ gl_index_ids->insert(gl_index_id);
+ }
+ delete it;
+}
+
+/*
+ Returning true if index_id is create/delete ongoing (undergoing creation or
+ marked as deleted via DROP TABLE but drop_index_thread has not wiped yet)
+ or not.
+ */
+bool Rdb_dict_manager::is_index_operation_ongoing(
+ const GL_INDEX_ID &gl_index_id, Rdb_key_def::DATA_DICT_TYPE dd_type) const {
+ DBUG_ASSERT(dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING ||
+ dd_type == Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ bool found = false;
+ std::string value;
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, dd_type, gl_index_id);
+
+ const rocksdb::Status status = get_value(key_writer.to_slice(), &value);
+ if (status.ok()) {
+ found = true;
+ }
+ return found;
+}
+
+/*
+ Adding index_id to data dictionary so that the index id is removed
+ by drop_index_thread, or to track online index creation.
+ */
+void Rdb_dict_manager::start_ongoing_index_operation(
+ rocksdb::WriteBatch *const batch, const GL_INDEX_ID &gl_index_id,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const {
+ DBUG_ASSERT(dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING ||
+ dd_type == Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ Rdb_buf_writer<Rdb_key_def::VERSION_SIZE> value_writer;
+
+ dump_index_id(&key_writer, dd_type, gl_index_id);
+
+ // version as needed
+ if (dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING) {
+ value_writer.write_uint16(Rdb_key_def::DDL_DROP_INDEX_ONGOING_VERSION);
+ } else {
+ value_writer.write_uint16(Rdb_key_def::DDL_CREATE_INDEX_ONGOING_VERSION);
+ }
+
+ batch->Put(m_system_cfh, key_writer.to_slice(), value_writer.to_slice());
+}
+
+/*
+ Removing index_id from data dictionary to confirm drop_index_thread
+ completed dropping entire key/values of the index_id
+ */
+void Rdb_dict_manager::end_ongoing_index_operation(
+ rocksdb::WriteBatch *const batch, const GL_INDEX_ID &gl_index_id,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const {
+ DBUG_ASSERT(dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING ||
+ dd_type == Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ delete_with_prefix(batch, dd_type, gl_index_id);
+}
+
+/*
+ Returning true if there is no target index ids to be removed
+ by drop_index_thread
+ */
+bool Rdb_dict_manager::is_drop_index_empty() const {
+ std::unordered_set<GL_INDEX_ID> gl_index_ids;
+ get_ongoing_drop_indexes(&gl_index_ids);
+ return gl_index_ids.empty();
+}
+
+/*
+ This function is supposed to be called by DROP TABLE. Logging messages
+ that dropping indexes started, and adding data dictionary so that
+ all associated indexes to be removed
+ */
+void Rdb_dict_manager::add_drop_table(
+ std::shared_ptr<Rdb_key_def> *const key_descr, const uint32 n_keys,
+ rocksdb::WriteBatch *const batch) const {
+ std::unordered_set<GL_INDEX_ID> dropped_index_ids;
+ for (uint32 i = 0; i < n_keys; i++) {
+ dropped_index_ids.insert(key_descr[i]->get_gl_index_id());
+ }
+
+ add_drop_index(dropped_index_ids, batch);
+}
+
+/*
+ Called during inplace index drop operations. Logging messages
+ that dropping indexes started, and adding data dictionary so that
+ all associated indexes to be removed
+ */
+void Rdb_dict_manager::add_drop_index(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ rocksdb::WriteBatch *const batch) const {
+ for (const auto &gl_index_id : gl_index_ids) {
+ log_start_drop_index(gl_index_id, "Begin");
+ start_drop_index(batch, gl_index_id);
+ }
+}
+
+/*
+ Called during inplace index creation operations. Logging messages
+ that adding indexes started, and updates data dictionary with all associated
+ indexes to be added.
+ */
+void Rdb_dict_manager::add_create_index(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ rocksdb::WriteBatch *const batch) const {
+ for (const auto &gl_index_id : gl_index_ids) {
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("RocksDB: Begin index creation (%u,%u)",
+ gl_index_id.cf_id, gl_index_id.index_id);
+ start_create_index(batch, gl_index_id);
+ }
+}
+
+/*
+ This function is supposed to be called by drop_index_thread, when it
+ finished dropping any index, or at the completion of online index creation.
+ */
+void Rdb_dict_manager::finish_indexes_operation(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const {
+ DBUG_ASSERT(dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING ||
+ dd_type == Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+
+ const std::unique_ptr<rocksdb::WriteBatch> wb = begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ std::unordered_set<GL_INDEX_ID> incomplete_create_indexes;
+ get_ongoing_create_indexes(&incomplete_create_indexes);
+
+ for (const auto &gl_index_id : gl_index_ids) {
+ if (is_index_operation_ongoing(gl_index_id, dd_type)) {
+ end_ongoing_index_operation(batch, gl_index_id, dd_type);
+
+ /*
+ Remove the corresponding incomplete create indexes from data
+ dictionary as well
+ */
+ if (dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING) {
+ if (incomplete_create_indexes.count(gl_index_id)) {
+ end_ongoing_index_operation(batch, gl_index_id,
+ Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+ }
+ }
+ }
+
+ if (dd_type == Rdb_key_def::DDL_DROP_INDEX_ONGOING) {
+ delete_index_info(batch, gl_index_id);
+ }
+ }
+ commit(batch);
+}
+
+/*
+ This function is supposed to be called when initializing
+ Rdb_dict_manager (at startup). If there is any index ids that are
+ drop ongoing, printing out messages for diagnostics purposes.
+ */
+void Rdb_dict_manager::resume_drop_indexes() const {
+ std::unordered_set<GL_INDEX_ID> gl_index_ids;
+ get_ongoing_drop_indexes(&gl_index_ids);
+
+ uint max_index_id_in_dict = 0;
+ get_max_index_id(&max_index_id_in_dict);
+
+ for (const auto &gl_index_id : gl_index_ids) {
+ log_start_drop_index(gl_index_id, "Resume");
+ if (max_index_id_in_dict < gl_index_id.index_id) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Found max index id %u from data dictionary "
+ "but also found dropped index id (%u,%u) from drop_index "
+ "dictionary. This should never happen and is possibly a "
+ "bug.",
+ max_index_id_in_dict, gl_index_id.cf_id, gl_index_id.index_id);
+ abort();
+ }
+ }
+}
+
+void Rdb_dict_manager::rollback_ongoing_index_creation() const {
+ const std::unique_ptr<rocksdb::WriteBatch> wb = begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ std::unordered_set<GL_INDEX_ID> gl_index_ids;
+ get_ongoing_create_indexes(&gl_index_ids);
+
+ for (const auto &gl_index_id : gl_index_ids) {
+ // NO_LINT_DEBUG
+ sql_print_verbose_info("RocksDB: Removing incomplete create index (%u,%u)",
+ gl_index_id.cf_id, gl_index_id.index_id);
+
+ start_drop_index(batch, gl_index_id);
+ }
+
+ commit(batch);
+}
+
+void Rdb_dict_manager::log_start_drop_table(
+ const std::shared_ptr<Rdb_key_def> *const key_descr, const uint32 n_keys,
+ const char *const log_action) const {
+ for (uint32 i = 0; i < n_keys; i++) {
+ log_start_drop_index(key_descr[i]->get_gl_index_id(), log_action);
+ }
+}
+
+void Rdb_dict_manager::log_start_drop_index(GL_INDEX_ID gl_index_id,
+ const char *log_action) const {
+ struct Rdb_index_info index_info;
+ if (!get_index_info(gl_index_id, &index_info)) {
+ /*
+ If we don't find the index info, it could be that it's because it was a
+ partially created index that isn't in the data dictionary yet that needs
+ to be rolled back.
+ */
+ std::unordered_set<GL_INDEX_ID> incomplete_create_indexes;
+ get_ongoing_create_indexes(&incomplete_create_indexes);
+
+ if (!incomplete_create_indexes.count(gl_index_id)) {
+ /* If it's not a partially created index, something is very wrong. */
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Failed to get column family info "
+ "from index id (%u,%u). MyRocks data dictionary may "
+ "get corrupted.",
+ gl_index_id.cf_id, gl_index_id.index_id);
+ abort();
+ }
+ }
+}
+
+bool Rdb_dict_manager::get_max_index_id(uint32_t *const index_id) const {
+ bool found = false;
+ std::string value;
+
+ const rocksdb::Status status = get_value(m_key_slice_max_index_id, &value);
+ if (status.ok()) {
+ const uchar *const val = (const uchar *)value.c_str();
+ const uint16_t version = rdb_netbuf_to_uint16(val);
+ if (version == Rdb_key_def::MAX_INDEX_ID_VERSION) {
+ *index_id = rdb_netbuf_to_uint32(val + Rdb_key_def::VERSION_SIZE);
+ found = true;
+ }
+ }
+ return found;
+}
+
+bool Rdb_dict_manager::update_max_index_id(rocksdb::WriteBatch *const batch,
+ const uint32_t index_id) const {
+ DBUG_ASSERT(batch != nullptr);
+
+ uint32_t old_index_id = -1;
+ if (get_max_index_id(&old_index_id)) {
+ if (old_index_id > index_id) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Found max index id %u from data dictionary "
+ "but trying to update to older value %u. This should "
+ "never happen and possibly a bug.",
+ old_index_id, index_id);
+ return true;
+ }
+ }
+
+ Rdb_buf_writer<Rdb_key_def::VERSION_SIZE + Rdb_key_def::INDEX_NUMBER_SIZE>
+ value_writer;
+ value_writer.write_uint16(Rdb_key_def::MAX_INDEX_ID_VERSION);
+ value_writer.write_uint32(index_id);
+
+ batch->Put(m_system_cfh, m_key_slice_max_index_id, value_writer.to_slice());
+ return false;
+}
+
+void Rdb_dict_manager::add_stats(
+ rocksdb::WriteBatch *const batch,
+ const std::vector<Rdb_index_stats> &stats) const {
+ DBUG_ASSERT(batch != nullptr);
+
+ for (const auto &it : stats) {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::INDEX_STATISTICS, it.m_gl_index_id);
+
+ // IndexStats::materialize takes complete care of serialization including
+ // storing the version
+ const auto value =
+ Rdb_index_stats::materialize(std::vector<Rdb_index_stats>{it});
+
+ batch->Put(m_system_cfh, key_writer.to_slice(), value);
+ }
+}
+
+Rdb_index_stats Rdb_dict_manager::get_stats(GL_INDEX_ID gl_index_id) const {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::INDEX_STATISTICS, gl_index_id);
+
+ std::string value;
+ const rocksdb::Status status = get_value(key_writer.to_slice(), &value);
+ if (status.ok()) {
+ std::vector<Rdb_index_stats> v;
+ // unmaterialize checks if the version matches
+ if (Rdb_index_stats::unmaterialize(value, &v) == 0 && v.size() == 1) {
+ return v[0];
+ }
+ }
+
+ return Rdb_index_stats();
+}
+
+rocksdb::Status Rdb_dict_manager::put_auto_incr_val(
+ rocksdb::WriteBatchBase *batch, const GL_INDEX_ID &gl_index_id,
+ ulonglong val, bool overwrite) const {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::AUTO_INC, gl_index_id);
+
+ // Value is constructed by storing the version and the value.
+ Rdb_buf_writer<RDB_SIZEOF_AUTO_INCREMENT_VERSION +
+ ROCKSDB_SIZEOF_AUTOINC_VALUE>
+ value_writer;
+ value_writer.write_uint16(Rdb_key_def::AUTO_INCREMENT_VERSION);
+ value_writer.write_uint64(val);
+
+ if (overwrite) {
+ return batch->Put(m_system_cfh, key_writer.to_slice(),
+ value_writer.to_slice());
+ }
+ return batch->Merge(m_system_cfh, key_writer.to_slice(),
+ value_writer.to_slice());
+}
+
+bool Rdb_dict_manager::get_auto_incr_val(const GL_INDEX_ID &gl_index_id,
+ ulonglong *new_val) const {
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE * 3> key_writer;
+ dump_index_id(&key_writer, Rdb_key_def::AUTO_INC, gl_index_id);
+
+ std::string value;
+ const rocksdb::Status status = get_value(key_writer.to_slice(), &value);
+
+ if (status.ok()) {
+ const uchar *const val = reinterpret_cast<const uchar *>(value.data());
+
+ if (rdb_netbuf_to_uint16(val) <= Rdb_key_def::AUTO_INCREMENT_VERSION) {
+ *new_val = rdb_netbuf_to_uint64(val + RDB_SIZEOF_AUTO_INCREMENT_VERSION);
+ return true;
+ }
+ }
+ return false;
+}
+
+uint Rdb_seq_generator::get_and_update_next_number(
+ Rdb_dict_manager *const dict) {
+ DBUG_ASSERT(dict != nullptr);
+
+ uint res;
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+
+ res = m_next_number++;
+
+ const std::unique_ptr<rocksdb::WriteBatch> wb = dict->begin();
+ rocksdb::WriteBatch *const batch = wb.get();
+
+ DBUG_ASSERT(batch != nullptr);
+ dict->update_max_index_id(batch, res);
+ dict->commit(batch);
+
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+
+ return res;
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_datadic.h b/storage/rocksdb/rdb_datadic.h
new file mode 100644
index 00000000..903cecdc
--- /dev/null
+++ b/storage/rocksdb/rdb_datadic.h
@@ -0,0 +1,1637 @@
+/*
+ Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ standard header files */
+#include <cstdlib>
+#include <algorithm>
+#include <atomic>
+#include <map>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+#include <array>
+
+/* C standard header files */
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./properties_collector.h"
+#include "./rdb_buff.h"
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+class Rdb_dict_manager;
+class Rdb_key_def;
+class Rdb_field_packing;
+class Rdb_cf_manager;
+class Rdb_ddl_manager;
+
+const uint32_t GTID_BUF_LEN = 60;
+
+class Rdb_convert_to_record_key_decoder {
+ public:
+ Rdb_convert_to_record_key_decoder() = default;
+ Rdb_convert_to_record_key_decoder(
+ const Rdb_convert_to_record_key_decoder &decoder) = delete;
+ Rdb_convert_to_record_key_decoder &operator=(
+ const Rdb_convert_to_record_key_decoder &decoder) = delete;
+ static int decode(uchar *const buf, uint *offset, Rdb_field_packing *fpi,
+ TABLE *table, Field *field, bool has_unpack_info,
+ Rdb_string_reader *reader,
+ Rdb_string_reader *unpack_reader);
+ static int skip(const Rdb_field_packing *fpi, const Field *field,
+ Rdb_string_reader *reader, Rdb_string_reader *unpack_reader);
+
+ private:
+ static int decode_field(Rdb_field_packing *fpi, Field *field,
+ Rdb_string_reader *reader,
+ const uchar *const default_value,
+ Rdb_string_reader *unpack_reader);
+};
+
+/*
+ @brief
+ Field packing context.
+ The idea is to ensure that a call to rdb_index_field_pack_t function
+ is followed by a call to rdb_make_unpack_info_t.
+
+ @detail
+ For some datatypes, unpack_info is produced as a side effect of
+ rdb_index_field_pack_t function call.
+ For other datatypes, packing is just calling make_sort_key(), while
+ rdb_make_unpack_info_t is a custom function.
+ In order to accommodate both cases, we require both calls to be made and
+ unpack_info is passed as context data between the two.
+*/
+class Rdb_pack_field_context {
+ public:
+ Rdb_pack_field_context(const Rdb_pack_field_context &) = delete;
+ Rdb_pack_field_context &operator=(const Rdb_pack_field_context &) = delete;
+
+ explicit Rdb_pack_field_context(Rdb_string_writer *const writer_arg)
+ : writer(writer_arg) {}
+
+ // NULL means we're not producing unpack_info.
+ Rdb_string_writer *writer;
+};
+
+class Rdb_key_field_iterator {
+ private:
+ Rdb_field_packing *m_pack_info;
+ int m_iter_index;
+ int m_iter_end;
+ TABLE *m_table;
+ Rdb_string_reader *m_reader;
+ Rdb_string_reader *m_unp_reader;
+ uint m_curr_bitmap_pos;
+ const MY_BITMAP *m_covered_bitmap;
+ uchar *m_buf;
+ bool m_has_unpack_info;
+ const Rdb_key_def *m_key_def;
+ bool m_secondary_key;
+ bool m_hidden_pk_exists;
+ bool m_is_hidden_pk;
+ bool m_is_null;
+ Field *m_field;
+ uint m_offset;
+ Rdb_field_packing *m_fpi;
+
+ public:
+ Rdb_key_field_iterator(const Rdb_key_field_iterator &) = delete;
+ Rdb_key_field_iterator &operator=(const Rdb_key_field_iterator &) = delete;
+ Rdb_key_field_iterator(const Rdb_key_def *key_def,
+ Rdb_field_packing *pack_info,
+ Rdb_string_reader *reader,
+ Rdb_string_reader *unp_reader, TABLE *table,
+ bool has_unpack_info, const MY_BITMAP *covered_bitmap,
+ uchar *buf);
+
+ int next();
+ bool has_next();
+ bool get_is_null() const;
+ Field *get_field() const;
+ int get_field_index() const;
+ void *get_dst() const;
+};
+
+struct Rdb_collation_codec;
+struct Rdb_index_info;
+
+/*
+ C-style "virtual table" allowing different handling of packing logic based
+ on the field type. See Rdb_field_packing::setup() implementation.
+ */
+using rdb_make_unpack_info_t = void (*)(const Rdb_collation_codec *codec,
+ const Field *field,
+ Rdb_pack_field_context *pack_ctx);
+using rdb_index_field_unpack_t = int (*)(Rdb_field_packing *fpi, Field *field,
+ uchar *field_ptr,
+ Rdb_string_reader *reader,
+ Rdb_string_reader *unpack_reader);
+using rdb_index_field_skip_t = int (*)(const Rdb_field_packing *fpi,
+ const Field *field,
+ Rdb_string_reader *reader);
+using rdb_index_field_pack_t = void (*)(Rdb_field_packing *fpi, Field *field,
+ uchar *buf, uchar **dst,
+ Rdb_pack_field_context *pack_ctx);
+
+const uint RDB_INVALID_KEY_LEN = uint(-1);
+
+/* How much one checksum occupies when stored in the record */
+const size_t RDB_CHECKSUM_SIZE = sizeof(uint32_t);
+
+/*
+ How much the checksum data occupies in record, in total.
+ It is storing two checksums plus 1 tag-byte.
+*/
+const size_t RDB_CHECKSUM_CHUNK_SIZE = 2 * RDB_CHECKSUM_SIZE + 1;
+
+/*
+ Checksum data starts from CHECKSUM_DATA_TAG which is followed by two CRC32
+ checksums.
+*/
+const char RDB_CHECKSUM_DATA_TAG = 0x01;
+
+/*
+ Unpack data is variable length. The header is 1 tag-byte plus a two byte
+ length field. The length field includes the header as well.
+*/
+const char RDB_UNPACK_DATA_TAG = 0x02;
+const size_t RDB_UNPACK_DATA_LEN_SIZE = sizeof(uint16_t);
+const size_t RDB_UNPACK_HEADER_SIZE =
+ sizeof(RDB_UNPACK_DATA_TAG) + RDB_UNPACK_DATA_LEN_SIZE;
+
+/*
+ This header format is 1 tag-byte plus a two byte length field plus a two byte
+ covered bitmap. The length field includes the header size.
+*/
+const char RDB_UNPACK_COVERED_DATA_TAG = 0x03;
+const size_t RDB_UNPACK_COVERED_DATA_LEN_SIZE = sizeof(uint16_t);
+const size_t RDB_COVERED_BITMAP_SIZE = sizeof(uint16_t);
+const size_t RDB_UNPACK_COVERED_HEADER_SIZE =
+ sizeof(RDB_UNPACK_COVERED_DATA_TAG) + RDB_UNPACK_COVERED_DATA_LEN_SIZE +
+ RDB_COVERED_BITMAP_SIZE;
+
+/*
+ Data dictionary index info field sizes.
+*/
+const size_t RDB_SIZEOF_INDEX_INFO_VERSION = sizeof(uint16);
+const size_t RDB_SIZEOF_INDEX_TYPE = sizeof(uchar);
+const size_t RDB_SIZEOF_KV_VERSION = sizeof(uint16);
+const size_t RDB_SIZEOF_INDEX_FLAGS = sizeof(uint32);
+const size_t RDB_SIZEOF_AUTO_INCREMENT_VERSION = sizeof(uint16);
+
+// Possible return values for rdb_index_field_unpack_t functions.
+enum {
+ UNPACK_SUCCESS = 0,
+ UNPACK_FAILURE = 1,
+};
+
+/*
+ An object of this class represents information about an index in an SQL
+ table. It provides services to encode and decode index tuples.
+
+ Note: a table (as in, on-disk table) has a single Rdb_key_def object which
+ is shared across multiple TABLE* objects and may be used simultaneously from
+ different threads.
+
+ There are several data encodings:
+
+ === SQL LAYER ===
+ SQL layer uses two encodings:
+
+ - "Table->record format". This is the format that is used for the data in
+ the record buffers, table->record[i]
+
+ - KeyTupleFormat (see opt_range.cc) - this is used in parameters to index
+ lookup functions, like handler::index_read_map().
+
+ === Inside RocksDB ===
+ Primary Key is stored as a mapping:
+
+ index_tuple -> StoredRecord
+
+ StoredRecord is in Table->record format, except for blobs, which are stored
+ in-place. See ha_rocksdb::convert_record_to_storage_format for details.
+
+ Secondary indexes are stored as one of two variants:
+
+ index_tuple -> unpack_info
+ index_tuple -> empty_string
+
+ index_tuple here is the form of key that can be compared with memcmp(), aka
+ "mem-comparable form".
+
+ unpack_info is extra data that allows to restore the original value from its
+ mem-comparable form. It is present only if the index supports index-only
+ reads.
+*/
+
+class Rdb_key_def {
+ public:
+ /* Convert a key from KeyTupleFormat to mem-comparable form */
+ uint pack_index_tuple(TABLE *const tbl, uchar *const pack_buffer,
+ uchar *const packed_tuple, uchar *const record_buffer,
+ const uchar *const key_tuple,
+ const key_part_map &keypart_map) const;
+
+ uchar *pack_field(Field *const field, Rdb_field_packing *pack_info,
+ uchar *tuple, uchar *const packed_tuple,
+ uchar *const pack_buffer,
+ Rdb_string_writer *const unpack_info,
+ uint *const n_null_fields) const;
+ /* Convert a key from Table->record format to mem-comparable form */
+ uint pack_record(const TABLE *const tbl, uchar *const pack_buffer,
+ const uchar *const record, uchar *const packed_tuple,
+ Rdb_string_writer *const unpack_info,
+ const bool should_store_row_debug_checksums,
+ const longlong hidden_pk_id = 0, uint n_key_parts = 0,
+ uint *const n_null_fields = nullptr,
+ const char *const ttl_bytes = nullptr) const;
+ /* Pack the hidden primary key into mem-comparable form. */
+ uint pack_hidden_pk(const longlong hidden_pk_id,
+ uchar *const packed_tuple) const;
+ int unpack_record(TABLE *const table, uchar *const buf,
+ const rocksdb::Slice *const packed_key,
+ const rocksdb::Slice *const unpack_info,
+ const bool verify_row_debug_checksums) const;
+
+ static bool unpack_info_has_checksum(const rocksdb::Slice &unpack_info);
+ int compare_keys(const rocksdb::Slice *key1, const rocksdb::Slice *key2,
+ std::size_t *const column_index) const;
+
+ size_t key_length(const TABLE *const table, const rocksdb::Slice &key) const;
+
+ /* Get the key that is the "infimum" for this index */
+ inline void get_infimum_key(uchar *const key, uint *const size) const {
+ rdb_netbuf_store_index(key, m_index_number);
+ *size = INDEX_NUMBER_SIZE;
+ }
+
+ /* Get the key that is a "supremum" for this index */
+ inline void get_supremum_key(uchar *const key, uint *const size) const {
+ rdb_netbuf_store_index(key, m_index_number + 1);
+ *size = INDEX_NUMBER_SIZE;
+ }
+
+ /*
+ Get the first key that you need to position at to start iterating.
+ Stores into *key a "supremum" or "infimum" key value for the index.
+ @parameters key OUT Big Endian, value is m_index_number or
+ m_index_number + 1
+ @parameters size OUT key size, value is INDEX_NUMBER_SIZE
+ @return Number of bytes in the key that are usable for bloom filter use.
+ */
+ inline int get_first_key(uchar *const key, uint *const size) const {
+ if (m_is_reverse_cf) {
+ get_supremum_key(key, size);
+ /* Find out how many bytes of infimum are the same as m_index_number */
+ uchar unmodified_key[INDEX_NUMBER_SIZE];
+ rdb_netbuf_store_index(unmodified_key, m_index_number);
+ int i;
+ for (i = 0; i < INDEX_NUMBER_SIZE; i++) {
+ if (key[i] != unmodified_key[i]) {
+ break;
+ }
+ }
+ return i;
+ } else {
+ get_infimum_key(key, size);
+ // For infimum key, its value will be m_index_number
+ // Thus return its own size instead.
+ return INDEX_NUMBER_SIZE;
+ }
+ }
+
+ /*
+ The same as get_first_key, but get the key for the last entry in the index
+ @parameters key OUT Big Endian, value is m_index_number or
+ m_index_number + 1
+ @parameters size OUT key size, value is INDEX_NUMBER_SIZE
+
+ @return Number of bytes in the key that are usable for bloom filter use.
+ */
+ inline int get_last_key(uchar *const key, uint *const size) const {
+ if (m_is_reverse_cf) {
+ get_infimum_key(key, size);
+ // For infimum key, its value will be m_index_number
+ // Thus return its own size instead.
+ return INDEX_NUMBER_SIZE;
+ } else {
+ get_supremum_key(key, size);
+ /* Find out how many bytes are the same as m_index_number */
+ uchar unmodified_key[INDEX_NUMBER_SIZE];
+ rdb_netbuf_store_index(unmodified_key, m_index_number);
+ int i;
+ for (i = 0; i < INDEX_NUMBER_SIZE; i++) {
+ if (key[i] != unmodified_key[i]) {
+ break;
+ }
+ }
+ return i;
+ }
+ }
+
+ /* Make a key that is right after the given key. */
+ static int successor(uchar *const packed_tuple, const uint len);
+
+ /* Make a key that is right before the given key. */
+ static int predecessor(uchar *const packed_tuple, const uint len);
+
+ /*
+ This can be used to compare prefixes.
+ if X is a prefix of Y, then we consider that X = Y.
+ */
+ // b describes the lookup key, which can be a prefix of a.
+ // b might be outside of the index_number range, if successor() is called.
+ int cmp_full_keys(const rocksdb::Slice &a, const rocksdb::Slice &b) const {
+ DBUG_ASSERT(covers_key(a));
+
+ return memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
+ }
+
+ /* Check if given mem-comparable key belongs to this index */
+ bool covers_key(const rocksdb::Slice &slice) const {
+ if (slice.size() < INDEX_NUMBER_SIZE) return false;
+
+ if (memcmp(slice.data(), m_index_number_storage_form, INDEX_NUMBER_SIZE)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ void get_lookup_bitmap(const TABLE *table, MY_BITMAP *map) const;
+
+ bool covers_lookup(const rocksdb::Slice *const unpack_info,
+ const MY_BITMAP *const map) const;
+
+ inline bool use_covered_bitmap_format() const {
+ return m_index_type == INDEX_TYPE_SECONDARY &&
+ m_kv_format_version >= SECONDARY_FORMAT_VERSION_UPDATE3;
+ }
+
+ /* Indicates that all key parts can be unpacked to cover a secondary lookup */
+ bool can_cover_lookup() const;
+
+ /*
+ Return true if the passed mem-comparable key
+ - is from this index, and
+ - it matches the passed key prefix (the prefix is also in mem-comparable
+ form)
+ */
+ bool value_matches_prefix(const rocksdb::Slice &value,
+ const rocksdb::Slice &prefix) const {
+ return covers_key(value) && !cmp_full_keys(value, prefix);
+ }
+
+ uint32 get_keyno() const { return m_keyno; }
+
+ uint32 get_index_number() const { return m_index_number; }
+
+ GL_INDEX_ID get_gl_index_id() const {
+ const GL_INDEX_ID gl_index_id = {m_cf_handle->GetID(), m_index_number};
+ return gl_index_id;
+ }
+
+ int read_memcmp_key_part(const TABLE *table_arg, Rdb_string_reader *reader,
+ const uint part_num) const;
+
+ /* Must only be called for secondary keys: */
+ uint get_primary_key_tuple(const TABLE *const tbl,
+ const Rdb_key_def &pk_descr,
+ const rocksdb::Slice *const key,
+ uchar *const pk_buffer) const;
+
+ uint get_memcmp_sk_parts(const TABLE *table, const rocksdb::Slice &key,
+ uchar *sk_buffer, uint *n_null_fields) const;
+
+ /* Return max length of mem-comparable form */
+ uint max_storage_fmt_length() const { return m_maxlength; }
+
+ uint get_key_parts() const { return m_key_parts; }
+
+ uint get_ttl_field_index() const { return m_ttl_field_index; }
+
+ /*
+ Get a field object for key part #part_no
+
+ @detail
+ SQL layer thinks unique secondary indexes and indexes in partitioned
+ tables are not "Extended" with Primary Key columns.
+
+ Internally, we always extend all indexes with PK columns. This function
+ uses our definition of how the index is Extended.
+ */
+ inline Field *get_table_field_for_part_no(TABLE *table, uint part_no) const;
+
+ const std::string &get_name() const { return m_name; }
+
+ const rocksdb::SliceTransform *get_extractor() const {
+ return m_prefix_extractor.get();
+ }
+
+ static size_t get_unpack_header_size(char tag);
+
+ Rdb_key_def &operator=(const Rdb_key_def &) = delete;
+ Rdb_key_def(const Rdb_key_def &k);
+ Rdb_key_def(uint indexnr_arg, uint keyno_arg,
+ rocksdb::ColumnFamilyHandle *cf_handle_arg,
+ uint16_t index_dict_version_arg, uchar index_type_arg,
+ uint16_t kv_format_version_arg, bool is_reverse_cf_arg,
+ bool is_per_partition_cf, const char *name,
+ Rdb_index_stats stats = Rdb_index_stats(), uint32 index_flags = 0,
+ uint32 ttl_rec_offset = UINT_MAX, uint64 ttl_duration = 0);
+ ~Rdb_key_def();
+
+ enum {
+ INDEX_NUMBER_SIZE = 4,
+ VERSION_SIZE = 2,
+ CF_NUMBER_SIZE = 4,
+ CF_FLAG_SIZE = 4,
+ PACKED_SIZE = 4, // one int
+ };
+
+ // bit flags for combining bools when writing to disk
+ enum {
+ REVERSE_CF_FLAG = 1,
+ AUTO_CF_FLAG = 2, // Deprecated
+ PER_PARTITION_CF_FLAG = 4,
+ };
+
+ // bit flags which denote myrocks specific fields stored in the record
+ // currently only used for TTL.
+ enum INDEX_FLAG {
+ TTL_FLAG = 1 << 0,
+
+ // MAX_FLAG marks where the actual record starts
+ // This flag always needs to be set to the last index flag enum.
+ MAX_FLAG = TTL_FLAG << 1,
+ };
+
+ // Set of flags to ignore when comparing two CF-s and determining if
+ // they're same.
+ static const uint CF_FLAGS_TO_IGNORE = PER_PARTITION_CF_FLAG;
+
+ // Data dictionary types
+ enum DATA_DICT_TYPE {
+ DDL_ENTRY_INDEX_START_NUMBER = 1,
+ INDEX_INFO = 2,
+ CF_DEFINITION = 3,
+ BINLOG_INFO_INDEX_NUMBER = 4,
+ DDL_DROP_INDEX_ONGOING = 5,
+ INDEX_STATISTICS = 6,
+ MAX_INDEX_ID = 7,
+ DDL_CREATE_INDEX_ONGOING = 8,
+ AUTO_INC = 9,
+ END_DICT_INDEX_ID = 255
+ };
+
+ // Data dictionary schema version. Introduce newer versions
+ // if changing schema layout
+ enum {
+ DDL_ENTRY_INDEX_VERSION = 1,
+ CF_DEFINITION_VERSION = 1,
+ BINLOG_INFO_INDEX_NUMBER_VERSION = 1,
+ DDL_DROP_INDEX_ONGOING_VERSION = 1,
+ MAX_INDEX_ID_VERSION = 1,
+ DDL_CREATE_INDEX_ONGOING_VERSION = 1,
+ AUTO_INCREMENT_VERSION = 1,
+ // Version for index stats is stored in IndexStats struct
+ };
+
+ // Index info version. Introduce newer versions when changing the
+ // INDEX_INFO layout. Update INDEX_INFO_VERSION_LATEST to point to the
+ // latest version number.
+ enum {
+ INDEX_INFO_VERSION_INITIAL = 1, // Obsolete
+ INDEX_INFO_VERSION_KV_FORMAT,
+ INDEX_INFO_VERSION_GLOBAL_ID,
+ // There is no change to data format in this version, but this version
+ // verifies KV format version, whereas previous versions do not. A version
+ // bump is needed to prevent older binaries from skipping the KV version
+ // check inadvertently.
+ INDEX_INFO_VERSION_VERIFY_KV_FORMAT,
+ // This changes the data format to include a 8 byte TTL duration for tables
+ INDEX_INFO_VERSION_TTL,
+ // This changes the data format to include a bitmap before the TTL duration
+ // which will indicate in the future whether TTL or other special fields
+ // are turned on or off.
+ INDEX_INFO_VERSION_FIELD_FLAGS,
+ // This normally point to the latest (currently it does).
+ INDEX_INFO_VERSION_LATEST = INDEX_INFO_VERSION_FIELD_FLAGS,
+ };
+
+ // MyRocks index types
+ enum {
+ INDEX_TYPE_PRIMARY = 1,
+ INDEX_TYPE_SECONDARY = 2,
+ INDEX_TYPE_HIDDEN_PRIMARY = 3,
+ };
+
+ // Key/Value format version for each index type
+ enum {
+ PRIMARY_FORMAT_VERSION_INITIAL = 10,
+ // This change includes:
+ // - For columns that can be unpacked with unpack_info, PK
+ // stores the unpack_info.
+ // - DECIMAL datatype is no longer stored in the row (because
+ // it can be decoded from its mem-comparable form)
+ // - VARCHAR-columns use endspace-padding.
+ PRIMARY_FORMAT_VERSION_UPDATE1 = 11,
+ // This change includes:
+ // - Binary encoded variable length fields have a new format that avoids
+ // an inefficient where data that was a multiple of 8 bytes in length
+ // had an extra 9 bytes of encoded data.
+ PRIMARY_FORMAT_VERSION_UPDATE2 = 12,
+ // This change includes support for TTL
+ // - This means that when TTL is specified for the table an 8-byte TTL
+ // field is prepended in front of each value.
+ PRIMARY_FORMAT_VERSION_TTL = 13,
+ PRIMARY_FORMAT_VERSION_LATEST = PRIMARY_FORMAT_VERSION_TTL,
+
+ SECONDARY_FORMAT_VERSION_INITIAL = 10,
+ // This change the SK format to include unpack_info.
+ SECONDARY_FORMAT_VERSION_UPDATE1 = 11,
+ // This change includes:
+ // - Binary encoded variable length fields have a new format that avoids
+ // an inefficient where data that was a multiple of 8 bytes in length
+ // had an extra 9 bytes of encoded data.
+ SECONDARY_FORMAT_VERSION_UPDATE2 = 12,
+ // This change includes support for TTL
+ // - This means that when TTL is specified for the table an 8-byte TTL
+ // field is prepended in front of each value.
+ SECONDARY_FORMAT_VERSION_TTL = 13,
+ SECONDARY_FORMAT_VERSION_LATEST = SECONDARY_FORMAT_VERSION_TTL,
+ // This change includes support for covering SK lookups for varchars. A
+ // 2-byte bitmap is added after the tag-byte to unpack_info only for
+ // records which have covered varchar columns. Currently waiting before
+ // enabling in prod.
+ SECONDARY_FORMAT_VERSION_UPDATE3 = 65535,
+ };
+
+ void setup(const TABLE *const table, const Rdb_tbl_def *const tbl_def);
+
+ static uint extract_ttl_duration(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ uint64 *ttl_duration);
+ static uint extract_ttl_col(const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg,
+ std::string *ttl_column, uint *ttl_field_index,
+ bool skip_checks = false);
+ inline bool has_ttl() const { return m_ttl_duration > 0; }
+
+ static bool has_index_flag(uint32 index_flags, enum INDEX_FLAG flag);
+ static uint32 calculate_index_flag_offset(uint32 index_flags,
+ enum INDEX_FLAG flag,
+ uint *const field_length = nullptr);
+ void write_index_flag_field(Rdb_string_writer *const buf,
+ const uchar *const val,
+ enum INDEX_FLAG flag) const;
+
+ static const std::string gen_qualifier_for_table(
+ const char *const qualifier, const std::string &partition_name = "");
+ static const std::string gen_cf_name_qualifier_for_partition(
+ const std::string &s);
+ static const std::string gen_ttl_duration_qualifier_for_partition(
+ const std::string &s);
+ static const std::string gen_ttl_col_qualifier_for_partition(
+ const std::string &s);
+
+ static const std::string parse_comment_for_qualifier(
+ const std::string &comment, const TABLE *const table_arg,
+ const Rdb_tbl_def *const tbl_def_arg, bool *per_part_match_found,
+ const char *const qualifier);
+
+ rocksdb::ColumnFamilyHandle *get_cf() const { return m_cf_handle; }
+
+ /* Check if keypart #kp can be unpacked from index tuple */
+ inline bool can_unpack(const uint kp) const;
+ /* Check if keypart #kp needs unpack info */
+ inline bool has_unpack_info(const uint kp) const;
+
+ /* Check if given table has a primary key */
+ static bool table_has_hidden_pk(const TABLE *const table);
+
+ void report_checksum_mismatch(const bool is_key, const char *const data,
+ const size_t data_size) const;
+
+ /* Check if index is at least pk_min if it is a PK,
+ or at least sk_min if SK.*/
+ bool index_format_min_check(const int pk_min, const int sk_min) const;
+
+ static void pack_with_make_sort_key(
+ Rdb_field_packing *const fpi, Field *const field,
+ uchar *buf MY_ATTRIBUTE((__unused__)), uchar **dst,
+ Rdb_pack_field_context *const pack_ctx MY_ATTRIBUTE((__unused__)));
+
+ static void pack_with_varchar_encoding(
+ Rdb_field_packing *const fpi, Field *const field, uchar *buf, uchar **dst,
+ Rdb_pack_field_context *const pack_ctx MY_ATTRIBUTE((__unused__)));
+
+ static void pack_with_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *buf, uchar **dst,
+ Rdb_pack_field_context *const pack_ctx);
+
+ static int unpack_integer(Rdb_field_packing *const fpi, Field *const field,
+ uchar *const to, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader
+ MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_double(
+ Rdb_field_packing *const fpi MY_ATTRIBUTE((__unused__)),
+ Field *const field MY_ATTRIBUTE((__unused__)), uchar *const field_ptr,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_float(
+ Rdb_field_packing *const fpi,
+ Field *const field MY_ATTRIBUTE((__unused__)), uchar *const field_ptr,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_binary_str(Rdb_field_packing *const fpi, Field *const field,
+ uchar *const to, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader
+ MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_binary_or_utf8_varchar(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_binary_or_utf8_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader, Rdb_string_reader *const unp_reader);
+
+ static int unpack_newdate(
+ Rdb_field_packing *const fpi,
+ Field *const field MY_ATTRIBUTE((__unused__)), uchar *const field_ptr,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_utf8_str(Rdb_field_packing *const fpi, Field *const field,
+ uchar *dst, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader
+ MY_ATTRIBUTE((__unused__)));
+
+ static int unpack_unknown_varchar(Rdb_field_packing *const fpi,
+ Field *const field, uchar *dst,
+ Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader);
+
+ static int unpack_simple_varchar_space_pad(
+ Rdb_field_packing *const fpi, Field *const field, uchar *dst,
+ Rdb_string_reader *const reader, Rdb_string_reader *const unp_reader);
+
+ static int unpack_simple(Rdb_field_packing *const fpi,
+ Field *const field MY_ATTRIBUTE((__unused__)),
+ uchar *const dst, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader);
+
+ static int unpack_unknown(Rdb_field_packing *const fpi, Field *const field,
+ uchar *const dst, Rdb_string_reader *const reader,
+ Rdb_string_reader *const unp_reader);
+
+ static int unpack_floating_point(uchar *const dst,
+ Rdb_string_reader *const reader,
+ const size_t size, const int exp_digit,
+ const uchar *const zero_pattern,
+ const uchar *const zero_val,
+ void (*swap_func)(uchar *, const uchar *));
+
+ static void make_unpack_simple_varchar(
+ const Rdb_collation_codec *const codec, const Field *const field,
+ Rdb_pack_field_context *const pack_ctx);
+
+ static void make_unpack_simple(const Rdb_collation_codec *const codec,
+ const Field *const field,
+ Rdb_pack_field_context *const pack_ctx);
+
+ static void make_unpack_unknown(
+ const Rdb_collation_codec *codec MY_ATTRIBUTE((__unused__)),
+ const Field *const field, Rdb_pack_field_context *const pack_ctx);
+
+ static void make_unpack_unknown_varchar(
+ const Rdb_collation_codec *const codec MY_ATTRIBUTE((__unused__)),
+ const Field *const field, Rdb_pack_field_context *const pack_ctx);
+
+ static void dummy_make_unpack_info(
+ const Rdb_collation_codec *codec MY_ATTRIBUTE((__unused__)),
+ const Field *field MY_ATTRIBUTE((__unused__)),
+ Rdb_pack_field_context *pack_ctx MY_ATTRIBUTE((__unused__)));
+
+ static int skip_max_length(const Rdb_field_packing *const fpi,
+ const Field *const field
+ MY_ATTRIBUTE((__unused__)),
+ Rdb_string_reader *const reader);
+
+ static int skip_variable_length(const Rdb_field_packing *const fpi,
+ const Field *const field,
+ Rdb_string_reader *const reader);
+
+ static int skip_variable_space_pad(const Rdb_field_packing *const fpi,
+ const Field *const field,
+ Rdb_string_reader *const reader);
+
+ inline bool use_legacy_varbinary_format() const {
+ return !index_format_min_check(PRIMARY_FORMAT_VERSION_UPDATE2,
+ SECONDARY_FORMAT_VERSION_UPDATE2);
+ }
+
+ static inline bool is_unpack_data_tag(char c) {
+ return c == RDB_UNPACK_DATA_TAG || c == RDB_UNPACK_COVERED_DATA_TAG;
+ }
+
+ private:
+#ifndef DBUG_OFF
+ inline bool is_storage_available(const int offset, const int needed) const {
+ const int storage_length = static_cast<int>(max_storage_fmt_length());
+ return (storage_length - offset) >= needed;
+ }
+#else
+ inline bool is_storage_available(const int &offset, const int &needed) const {
+ return 1;
+ }
+#endif // DBUG_OFF
+
+ /* Global number of this index (used as prefix in StorageFormat) */
+ const uint32 m_index_number;
+
+ uchar m_index_number_storage_form[INDEX_NUMBER_SIZE];
+
+ rocksdb::ColumnFamilyHandle *m_cf_handle;
+
+ static void pack_legacy_variable_format(const uchar *src, size_t src_len,
+ uchar **dst);
+
+ static void pack_variable_format(const uchar *src, size_t src_len,
+ uchar **dst);
+
+ static uint calc_unpack_legacy_variable_format(uchar flag, bool *done);
+
+ static uint calc_unpack_variable_format(uchar flag, bool *done);
+
+ public:
+ uint16_t m_index_dict_version;
+ uchar m_index_type;
+ /* KV format version for the index id */
+ uint16_t m_kv_format_version;
+ /* If true, the column family stores data in the reverse order */
+ bool m_is_reverse_cf;
+
+ /* If true, then column family is created per partition. */
+ bool m_is_per_partition_cf;
+
+ std::string m_name;
+ mutable Rdb_index_stats m_stats;
+
+ /*
+ Bitmap containing information about whether TTL or other special fields
+ are enabled for the given index.
+ */
+ uint32 m_index_flags_bitmap;
+
+ /*
+ How much space in bytes the index flag fields occupy.
+ */
+ uint32 m_total_index_flags_length;
+
+ /*
+ Offset in the records where the 8-byte TTL is stored (UINT_MAX if no TTL)
+ */
+ uint32 m_ttl_rec_offset;
+
+ /* Default TTL duration */
+ uint64 m_ttl_duration;
+
+ /* TTL column (if defined by user, otherwise implicit TTL is used) */
+ std::string m_ttl_column;
+
+ private:
+ /* Number of key parts in the primary key*/
+ uint m_pk_key_parts;
+
+ /*
+ pk_part_no[X]=Y means that keypart #X of this key is key part #Y of the
+ primary key. Y==-1 means this column is not present in the primary key.
+ */
+ uint *m_pk_part_no;
+
+ /* Array of index-part descriptors. */
+ Rdb_field_packing *m_pack_info;
+
+ uint m_keyno; /* number of this index in the table */
+
+ /*
+ Number of key parts in the index (including "index extension"). This is how
+ many elements are in the m_pack_info array.
+ */
+ uint m_key_parts;
+
+ /*
+ If TTL column is part of the PK, offset of the column within pk.
+ Default is UINT_MAX to denote that TTL col is not part of PK.
+ */
+ uint m_ttl_pk_key_part_offset;
+
+ /*
+ Index of the TTL column in table->s->fields, if it exists.
+ Default is UINT_MAX to denote that it does not exist.
+ */
+ uint m_ttl_field_index;
+
+ /* Prefix extractor for the column family of the key definiton */
+ std::shared_ptr<const rocksdb::SliceTransform> m_prefix_extractor;
+
+ /* Maximum length of the mem-comparable form. */
+ uint m_maxlength;
+
+ /* mutex to protect setup */
+ mysql_mutex_t m_mutex;
+};
+
+// "Simple" collations (those specified in strings/ctype-simple.c) are simple
+// because their strnxfrm function maps one byte to one byte. However, the
+// mapping is not injective, so the inverse function will take in an extra
+// index parameter containing information to disambiguate what the original
+// character was.
+//
+// The m_enc* members are for encoding. Generally, we want encoding to be:
+// src -> (dst, idx)
+//
+// Since strnxfrm already gives us dst, we just need m_enc_idx[src] to give us
+// idx.
+//
+// For the inverse, we have:
+// (dst, idx) -> src
+//
+// We have m_dec_idx[idx][dst] = src to get our original character back.
+//
+struct Rdb_collation_codec {
+ const my_core::CHARSET_INFO *m_cs;
+ // The first element unpacks VARCHAR(n), the second one - CHAR(n).
+ std::array<rdb_make_unpack_info_t, 2> m_make_unpack_info_func;
+ std::array<rdb_index_field_unpack_t, 2> m_unpack_func;
+
+ std::array<uchar, 256> m_enc_idx;
+ std::array<uchar, 256> m_enc_size;
+
+ std::array<uchar, 256> m_dec_size;
+ std::vector<std::array<uchar, 256>> m_dec_idx;
+};
+
+extern mysql_mutex_t rdb_collation_data_mutex;
+extern mysql_mutex_t rdb_mem_cmp_space_mutex;
+extern std::array<const Rdb_collation_codec *, MY_ALL_CHARSETS_SIZE>
+ rdb_collation_data;
+
+class Rdb_field_packing {
+ public:
+ Rdb_field_packing(const Rdb_field_packing &) = delete;
+ Rdb_field_packing &operator=(const Rdb_field_packing &) = delete;
+ Rdb_field_packing() = default;
+
+ /* Length of mem-comparable image of the field, in bytes */
+ int m_max_image_len;
+
+ /* Length of image in the unpack data */
+ int m_unpack_data_len;
+ int m_unpack_data_offset;
+
+ bool m_maybe_null; /* TRUE <=> NULL-byte is stored */
+
+ /*
+ Valid only for VARCHAR fields.
+ */
+ const CHARSET_INFO *m_varchar_charset;
+ bool m_use_legacy_varbinary_format;
+
+ // (Valid when Variable Length Space Padded Encoding is used):
+ uint m_segment_size; // size of segment used
+
+ // number of bytes used to store number of trimmed (or added)
+ // spaces in the upack_info
+ bool m_unpack_info_uses_two_bytes;
+
+ /*
+ True implies that an index-only read is always possible for this field.
+ False means an index-only read may be possible depending on the record and
+ field type.
+ */
+ bool m_covered;
+
+ const std::vector<uchar> *space_xfrm;
+ size_t space_xfrm_len;
+ size_t space_mb_len;
+
+ const Rdb_collation_codec *m_charset_codec;
+
+ /*
+ @return TRUE: this field makes use of unpack_info.
+ */
+ bool uses_unpack_info() const { return (m_make_unpack_info_func != nullptr); }
+
+ /* TRUE means unpack_info stores the original field value */
+ bool m_unpack_info_stores_value;
+
+ rdb_index_field_pack_t m_pack_func;
+ rdb_make_unpack_info_t m_make_unpack_info_func;
+
+ /*
+ This function takes
+ - mem-comparable form
+ - unpack_info data
+ and restores the original value.
+ */
+ rdb_index_field_unpack_t m_unpack_func;
+
+ /*
+ This function skips over mem-comparable form.
+ */
+ rdb_index_field_skip_t m_skip_func;
+
+ private:
+ /*
+ Location of the field in the table (key number and key part number).
+
+ Note that this describes not the field, but rather a position of field in
+ the index. Consider an example:
+
+ col1 VARCHAR (100),
+ INDEX idx1 (col1)),
+ INDEX idx2 (col1(10)),
+
+ Here, idx2 has a special Field object that is set to describe a 10-char
+ prefix of col1.
+
+ We must also store the keynr. It is needed for implicit "extended keys".
+ Every key in MyRocks needs to include PK columns. Generally, SQL layer
+ includes PK columns as part of its "Extended Keys" feature, but sometimes
+ it does not (known examples are unique secondary indexes and partitioned
+ tables).
+ In that case, MyRocks's index descriptor has invisible suffix of PK
+ columns (and the point is that these columns are parts of PK, not parts
+ of the current index).
+ */
+ uint m_keynr;
+ uint m_key_part;
+
+ public:
+ bool setup(const Rdb_key_def *const key_descr, const Field *const field,
+ const uint keynr_arg, const uint key_part_arg,
+ const uint16 key_length);
+ Field *get_field_in_table(const TABLE *const tbl) const;
+ void fill_hidden_pk_val(uchar **dst, const longlong hidden_pk_id) const;
+};
+
+/*
+ Descriptor telling how to decode/encode a field to on-disk record storage
+ format. Not all information is in the structure yet, but eventually we
+ want to have as much as possible there to avoid virtual calls.
+
+ For encoding/decoding of index tuples, see Rdb_key_def.
+ */
+class Rdb_field_encoder {
+ public:
+ Rdb_field_encoder(const Rdb_field_encoder &) = delete;
+ Rdb_field_encoder &operator=(const Rdb_field_encoder &) = delete;
+ /*
+ STORE_NONE is set when a column can be decoded solely from their
+ mem-comparable form.
+ STORE_SOME is set when a column can be decoded from their mem-comparable
+ form plus unpack_info.
+ STORE_ALL is set when a column cannot be decoded, so its original value
+ must be stored in the PK records.
+ */
+ enum STORAGE_TYPE {
+ STORE_NONE,
+ STORE_SOME,
+ STORE_ALL,
+ };
+ STORAGE_TYPE m_storage_type;
+
+ uint m_null_offset;
+ uint16 m_field_index;
+
+ uchar m_null_mask; // 0 means the field cannot be null
+
+ my_core::enum_field_types m_field_type;
+
+ uint m_pack_length_in_rec;
+
+ bool maybe_null() const { return m_null_mask != 0; }
+
+ bool uses_variable_len_encoding() const {
+ return (m_field_type == MYSQL_TYPE_BLOB ||
+ m_field_type == MYSQL_TYPE_VARCHAR);
+ }
+};
+
+inline Field *Rdb_key_def::get_table_field_for_part_no(TABLE *table,
+ uint part_no) const {
+ DBUG_ASSERT(part_no < get_key_parts());
+ return m_pack_info[part_no].get_field_in_table(table);
+}
+
+inline bool Rdb_key_def::can_unpack(const uint kp) const {
+ DBUG_ASSERT(kp < m_key_parts);
+ return (m_pack_info[kp].m_unpack_func != nullptr);
+}
+
+inline bool Rdb_key_def::has_unpack_info(const uint kp) const {
+ DBUG_ASSERT(kp < m_key_parts);
+ return m_pack_info[kp].uses_unpack_info();
+}
+
+/*
+ A table definition. This is an entry in the mapping
+
+ dbname.tablename -> {index_nr, index_nr, ... }
+
+ There is only one Rdb_tbl_def object for a given table.
+ That's why we keep auto_increment value here, too.
+*/
+
+class Rdb_tbl_def {
+ private:
+ void check_if_is_mysql_system_table();
+
+ /* Stores 'dbname.tablename' */
+ std::string m_dbname_tablename;
+
+ /* Store the db name, table name, and partition name */
+ std::string m_dbname;
+ std::string m_tablename;
+ std::string m_partition;
+
+ void set_name(const std::string &name);
+
+ public:
+ Rdb_tbl_def(const Rdb_tbl_def &) = delete;
+ Rdb_tbl_def &operator=(const Rdb_tbl_def &) = delete;
+
+ explicit Rdb_tbl_def(const std::string &name)
+ : m_key_descr_arr(nullptr), m_hidden_pk_val(0), m_auto_incr_val(0),
+ m_update_time(0), m_create_time(CREATE_TIME_UNKNOWN) {
+ set_name(name);
+ }
+
+ Rdb_tbl_def(const char *const name, const size_t len)
+ : m_key_descr_arr(nullptr), m_hidden_pk_val(0), m_auto_incr_val(0),
+ m_update_time(0), m_create_time(CREATE_TIME_UNKNOWN) {
+ set_name(std::string(name, len));
+ }
+
+ explicit Rdb_tbl_def(const rocksdb::Slice &slice, const size_t pos = 0)
+ : m_key_descr_arr(nullptr), m_hidden_pk_val(0), m_auto_incr_val(0),
+ m_update_time(0), m_create_time(CREATE_TIME_UNKNOWN) {
+ set_name(std::string(slice.data() + pos, slice.size() - pos));
+ }
+
+ ~Rdb_tbl_def();
+
+ void check_and_set_read_free_rpl_table();
+
+ /* Number of indexes */
+ uint m_key_count;
+
+ /* Array of index descriptors */
+ std::shared_ptr<Rdb_key_def> *m_key_descr_arr;
+
+ std::atomic<longlong> m_hidden_pk_val;
+ std::atomic<ulonglong> m_auto_incr_val;
+
+ /* Is this a system table */
+ bool m_is_mysql_system_table;
+
+ /* Is this table read free repl enabled */
+ std::atomic_bool m_is_read_free_rpl_table{false};
+
+ bool put_dict(Rdb_dict_manager *const dict, rocksdb::WriteBatch *const batch,
+ const rocksdb::Slice &key);
+
+ const std::string &full_tablename() const { return m_dbname_tablename; }
+ const std::string &base_dbname() const { return m_dbname; }
+ const std::string &base_tablename() const { return m_tablename; }
+ const std::string &base_partition() const { return m_partition; }
+ GL_INDEX_ID get_autoincr_gl_index_id();
+
+ time_t get_create_time();
+ std::atomic<time_t> m_update_time; // in-memory only value
+
+ private:
+ const time_t CREATE_TIME_UNKNOWN= 1;
+ // CREATE_TIME_UNKNOWN means "didn't try to read, yet"
+ // 0 means "no data available"
+ std::atomic<time_t> m_create_time;
+};
+
+/*
+ A thread-safe sequential number generator. Its performance is not a concern
+ hence it is ok to protect it by a mutex.
+*/
+
+class Rdb_seq_generator {
+ uint m_next_number = 0;
+
+ mysql_mutex_t m_mutex;
+
+ public:
+ Rdb_seq_generator(const Rdb_seq_generator &) = delete;
+ Rdb_seq_generator &operator=(const Rdb_seq_generator &) = delete;
+ Rdb_seq_generator() = default;
+
+ void init(const uint initial_number) {
+ mysql_mutex_init(0, &m_mutex, MY_MUTEX_INIT_FAST);
+ m_next_number = initial_number;
+ }
+
+ uint get_and_update_next_number(Rdb_dict_manager *const dict);
+
+ void cleanup() { mysql_mutex_destroy(&m_mutex); }
+};
+
+interface Rdb_tables_scanner {
+ virtual int add_table(Rdb_tbl_def * tdef) = 0;
+ virtual ~Rdb_tables_scanner() {} /* Keep the compiler happy */
+};
+
+/*
+ This contains a mapping of
+
+ dbname.table_name -> array{Rdb_key_def}.
+
+ objects are shared among all threads.
+*/
+
+class Rdb_ddl_manager {
+ Rdb_dict_manager *m_dict = nullptr;
+
+ // Contains Rdb_tbl_def elements
+ std::unordered_map<std::string, Rdb_tbl_def *> m_ddl_map;
+
+ // Maps index id to <table_name, index number>
+ std::map<GL_INDEX_ID, std::pair<std::string, uint>> m_index_num_to_keydef;
+
+ // Maps index id to key definitons not yet committed to data dictionary.
+ // This is mainly used to store key definitions during ALTER TABLE.
+ std::map<GL_INDEX_ID, std::shared_ptr<Rdb_key_def>>
+ m_index_num_to_uncommitted_keydef;
+ mysql_rwlock_t m_rwlock;
+
+ Rdb_seq_generator m_sequence;
+ // A queue of table stats to write into data dictionary
+ // It is produced by event listener (ie compaction and flush threads)
+ // and consumed by the rocksdb background thread
+ std::map<GL_INDEX_ID, Rdb_index_stats> m_stats2store;
+
+ const std::shared_ptr<Rdb_key_def> &find(GL_INDEX_ID gl_index_id);
+
+ public:
+ Rdb_ddl_manager(const Rdb_ddl_manager &) = delete;
+ Rdb_ddl_manager &operator=(const Rdb_ddl_manager &) = delete;
+ Rdb_ddl_manager() {}
+
+ /* Load the data dictionary from on-disk storage */
+ bool init(Rdb_dict_manager *const dict_arg, Rdb_cf_manager *const cf_manager,
+ const uint32_t validate_tables);
+
+ void cleanup();
+
+ Rdb_tbl_def *find(const std::string &table_name, const bool lock = true);
+ std::shared_ptr<const Rdb_key_def> safe_find(GL_INDEX_ID gl_index_id);
+ void set_stats(const std::unordered_map<GL_INDEX_ID, Rdb_index_stats> &stats);
+ void adjust_stats(const std::vector<Rdb_index_stats> &new_data,
+ const std::vector<Rdb_index_stats> &deleted_data =
+ std::vector<Rdb_index_stats>());
+ void persist_stats(const bool sync = false);
+
+ /* Modify the mapping and write it to on-disk storage */
+ int put_and_write(Rdb_tbl_def *const key_descr,
+ rocksdb::WriteBatch *const batch);
+ void remove(Rdb_tbl_def *const rec, rocksdb::WriteBatch *const batch,
+ const bool lock = true);
+ bool rename(const std::string &from, const std::string &to,
+ rocksdb::WriteBatch *const batch);
+
+ uint get_and_update_next_number(Rdb_dict_manager *const dict) {
+ return m_sequence.get_and_update_next_number(dict);
+ }
+
+ const std::string safe_get_table_name(const GL_INDEX_ID &gl_index_id);
+
+ /* Walk the data dictionary */
+ int scan_for_tables(Rdb_tables_scanner *tables_scanner);
+
+ void erase_index_num(const GL_INDEX_ID &gl_index_id);
+ void add_uncommitted_keydefs(
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes);
+ void remove_uncommitted_keydefs(
+ const std::unordered_set<std::shared_ptr<Rdb_key_def>> &indexes);
+
+ private:
+ /* Put the data into in-memory table (only) */
+ int put(Rdb_tbl_def *const key_descr, const bool lock = true);
+
+ /* Helper functions to be passed to my_core::HASH object */
+ static const uchar *get_hash_key(Rdb_tbl_def *const rec, size_t *const length,
+ my_bool not_used MY_ATTRIBUTE((unused)));
+ static void free_hash_elem(void *const data);
+
+ bool validate_schemas();
+
+ bool validate_auto_incr();
+};
+
+/*
+ Writing binlog information into RocksDB at commit(),
+ and retrieving binlog information at crash recovery.
+ commit() and recovery are always executed by at most single client
+ at the same time, so concurrency control is not needed.
+
+ Binlog info is stored in RocksDB as the following.
+ key: BINLOG_INFO_INDEX_NUMBER
+ value: packed single row:
+ binlog_name_length (2 byte form)
+ binlog_name
+ binlog_position (4 byte form)
+ binlog_gtid_length (2 byte form)
+ binlog_gtid
+*/
+class Rdb_binlog_manager {
+ public:
+ Rdb_binlog_manager(const Rdb_binlog_manager &) = delete;
+ Rdb_binlog_manager &operator=(const Rdb_binlog_manager &) = delete;
+ Rdb_binlog_manager() = default;
+
+ bool init(Rdb_dict_manager *const dict);
+ void cleanup();
+ void update(const char *const binlog_name, const my_off_t binlog_pos,
+ rocksdb::WriteBatchBase *const batch);
+ bool read(char *const binlog_name, my_off_t *const binlog_pos,
+ char *const binlog_gtid) const;
+ void update_slave_gtid_info(const uint id, const char *const db,
+ const char *const gtid,
+ rocksdb::WriteBatchBase *const write_batch);
+
+ private:
+ Rdb_dict_manager *m_dict = nullptr;
+ Rdb_buf_writer<Rdb_key_def::INDEX_NUMBER_SIZE> m_key_writer;
+ rocksdb::Slice m_key_slice;
+
+ bool unpack_value(const uchar *const value, size_t value_size,
+ char *const binlog_name,
+ my_off_t *const binlog_pos, char *const binlog_gtid) const;
+
+ std::atomic<Rdb_tbl_def *> m_slave_gtid_info_tbl;
+};
+
+/*
+ Rdb_dict_manager manages how MySQL on RocksDB (MyRocks) stores its
+ internal data dictionary.
+ MyRocks stores data dictionary on dedicated system column family
+ named __system__. The system column family is used by MyRocks
+ internally only, and not used by applications.
+
+ Currently MyRocks has the following data dictionary data models.
+
+ 1. Table Name => internal index id mappings
+ key: Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER(0x1) + dbname.tablename
+ value: version, {cf_id, index_id}*n_indexes_of_the_table
+ version is 2 bytes. cf_id and index_id are 4 bytes.
+
+ 2. internal cf_id, index id => index information
+ key: Rdb_key_def::INDEX_INFO(0x2) + cf_id + index_id
+ value: version, index_type, kv_format_version, index_flags, ttl_duration
+ index_type is 1 byte, version and kv_format_version are 2 bytes.
+ index_flags is 4 bytes.
+ ttl_duration is 8 bytes.
+
+ 3. CF id => CF flags
+ key: Rdb_key_def::CF_DEFINITION(0x3) + cf_id
+ value: version, {is_reverse_cf, is_auto_cf (deprecated), is_per_partition_cf}
+ cf_flags is 4 bytes in total.
+
+ 4. Binlog entry (updated at commit)
+ key: Rdb_key_def::BINLOG_INFO_INDEX_NUMBER (0x4)
+ value: version, {binlog_name,binlog_pos,binlog_gtid}
+
+ 5. Ongoing drop index entry
+ key: Rdb_key_def::DDL_DROP_INDEX_ONGOING(0x5) + cf_id + index_id
+ value: version
+
+ 6. index stats
+ key: Rdb_key_def::INDEX_STATISTICS(0x6) + cf_id + index_id
+ value: version, {materialized PropertiesCollector::IndexStats}
+
+ 7. maximum index id
+ key: Rdb_key_def::MAX_INDEX_ID(0x7)
+ value: index_id
+ index_id is 4 bytes
+
+ 8. Ongoing create index entry
+ key: Rdb_key_def::DDL_CREATE_INDEX_ONGOING(0x8) + cf_id + index_id
+ value: version
+
+ 9. auto_increment values
+ key: Rdb_key_def::AUTO_INC(0x9) + cf_id + index_id
+ value: version, {max auto_increment so far}
+ max auto_increment is 8 bytes
+
+ Data dictionary operations are atomic inside RocksDB. For example,
+ when creating a table with two indexes, it is necessary to call Put
+ three times. They have to be atomic. Rdb_dict_manager has a wrapper function
+ begin() and commit() to make it easier to do atomic operations.
+
+*/
+class Rdb_dict_manager {
+ private:
+ mysql_mutex_t m_mutex;
+ rocksdb::TransactionDB *m_db = nullptr;
+ rocksdb::ColumnFamilyHandle *m_system_cfh = nullptr;
+ /* Utility to put INDEX_INFO and CF_DEFINITION */
+
+ uchar m_key_buf_max_index_id[Rdb_key_def::INDEX_NUMBER_SIZE] = {0};
+ rocksdb::Slice m_key_slice_max_index_id;
+
+ static void dump_index_id(uchar *const netbuf,
+ Rdb_key_def::DATA_DICT_TYPE dict_type,
+ const GL_INDEX_ID &gl_index_id);
+ template <size_t T>
+ static void dump_index_id(Rdb_buf_writer<T> *buf_writer,
+ Rdb_key_def::DATA_DICT_TYPE dict_type,
+ const GL_INDEX_ID &gl_index_id) {
+ buf_writer->write_uint32(dict_type);
+ buf_writer->write_uint32(gl_index_id.cf_id);
+ buf_writer->write_uint32(gl_index_id.index_id);
+ }
+
+ void delete_with_prefix(rocksdb::WriteBatch *const batch,
+ Rdb_key_def::DATA_DICT_TYPE dict_type,
+ const GL_INDEX_ID &gl_index_id) const;
+ /* Functions for fast DROP TABLE/INDEX */
+ void resume_drop_indexes() const;
+ void log_start_drop_table(const std::shared_ptr<Rdb_key_def> *const key_descr,
+ const uint32 n_keys,
+ const char *const log_action) const;
+ void log_start_drop_index(GL_INDEX_ID gl_index_id,
+ const char *log_action) const;
+
+ public:
+ Rdb_dict_manager(const Rdb_dict_manager &) = delete;
+ Rdb_dict_manager &operator=(const Rdb_dict_manager &) = delete;
+ Rdb_dict_manager() = default;
+
+ bool init(rocksdb::TransactionDB *const rdb_dict,
+ Rdb_cf_manager *const cf_manager);
+
+ inline void cleanup() { mysql_mutex_destroy(&m_mutex); }
+
+ inline void lock() { RDB_MUTEX_LOCK_CHECK(m_mutex); }
+
+ inline void unlock() { RDB_MUTEX_UNLOCK_CHECK(m_mutex); }
+
+ inline rocksdb::ColumnFamilyHandle *get_system_cf() const {
+ return m_system_cfh;
+ }
+
+ /* Raw RocksDB operations */
+ std::unique_ptr<rocksdb::WriteBatch> begin() const;
+ int commit(rocksdb::WriteBatch *const batch, const bool sync = true) const;
+ rocksdb::Status get_value(const rocksdb::Slice &key,
+ std::string *const value) const;
+ void put_key(rocksdb::WriteBatchBase *const batch, const rocksdb::Slice &key,
+ const rocksdb::Slice &value) const;
+ void delete_key(rocksdb::WriteBatchBase *batch,
+ const rocksdb::Slice &key) const;
+ rocksdb::Iterator *new_iterator() const;
+
+ /* Internal Index id => CF */
+ void add_or_update_index_cf_mapping(
+ rocksdb::WriteBatch *batch,
+ struct Rdb_index_info *const index_info) const;
+ void delete_index_info(rocksdb::WriteBatch *batch,
+ const GL_INDEX_ID &index_id) const;
+ bool get_index_info(const GL_INDEX_ID &gl_index_id,
+ struct Rdb_index_info *const index_info) const;
+
+ /* CF id => CF flags */
+ void add_cf_flags(rocksdb::WriteBatch *const batch, const uint cf_id,
+ const uint cf_flags) const;
+ bool get_cf_flags(const uint cf_id, uint *const cf_flags) const;
+
+ /* Functions for fast CREATE/DROP TABLE/INDEX */
+ void get_ongoing_index_operation(
+ std::unordered_set<GL_INDEX_ID> *gl_index_ids,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const;
+ bool is_index_operation_ongoing(const GL_INDEX_ID &gl_index_id,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const;
+ void start_ongoing_index_operation(rocksdb::WriteBatch *batch,
+ const GL_INDEX_ID &gl_index_id,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const;
+ void end_ongoing_index_operation(rocksdb::WriteBatch *const batch,
+ const GL_INDEX_ID &gl_index_id,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const;
+ bool is_drop_index_empty() const;
+ void add_drop_table(std::shared_ptr<Rdb_key_def> *const key_descr,
+ const uint32 n_keys,
+ rocksdb::WriteBatch *const batch) const;
+ void add_drop_index(const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ rocksdb::WriteBatch *const batch) const;
+ void add_create_index(const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ rocksdb::WriteBatch *const batch) const;
+ void finish_indexes_operation(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids,
+ Rdb_key_def::DATA_DICT_TYPE dd_type) const;
+ void rollback_ongoing_index_creation() const;
+
+ inline void get_ongoing_drop_indexes(
+ std::unordered_set<GL_INDEX_ID> *gl_index_ids) const {
+ get_ongoing_index_operation(gl_index_ids,
+ Rdb_key_def::DDL_DROP_INDEX_ONGOING);
+ }
+ inline void get_ongoing_create_indexes(
+ std::unordered_set<GL_INDEX_ID> *gl_index_ids) const {
+ get_ongoing_index_operation(gl_index_ids,
+ Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+ }
+ inline void start_drop_index(rocksdb::WriteBatch *wb,
+ const GL_INDEX_ID &gl_index_id) const {
+ start_ongoing_index_operation(wb, gl_index_id,
+ Rdb_key_def::DDL_DROP_INDEX_ONGOING);
+ }
+ inline void start_create_index(rocksdb::WriteBatch *wb,
+ const GL_INDEX_ID &gl_index_id) const {
+ start_ongoing_index_operation(wb, gl_index_id,
+ Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+ }
+ inline void finish_drop_indexes(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids) const {
+ finish_indexes_operation(gl_index_ids, Rdb_key_def::DDL_DROP_INDEX_ONGOING);
+ }
+ inline void finish_create_indexes(
+ const std::unordered_set<GL_INDEX_ID> &gl_index_ids) const {
+ finish_indexes_operation(gl_index_ids,
+ Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+ }
+ inline bool is_drop_index_ongoing(const GL_INDEX_ID &gl_index_id) const {
+ return is_index_operation_ongoing(gl_index_id,
+ Rdb_key_def::DDL_DROP_INDEX_ONGOING);
+ }
+ inline bool is_create_index_ongoing(const GL_INDEX_ID &gl_index_id) const {
+ return is_index_operation_ongoing(gl_index_id,
+ Rdb_key_def::DDL_CREATE_INDEX_ONGOING);
+ }
+
+ bool get_max_index_id(uint32_t *const index_id) const;
+ bool update_max_index_id(rocksdb::WriteBatch *const batch,
+ const uint32_t index_id) const;
+ void add_stats(rocksdb::WriteBatch *const batch,
+ const std::vector<Rdb_index_stats> &stats) const;
+ Rdb_index_stats get_stats(GL_INDEX_ID gl_index_id) const;
+
+ rocksdb::Status put_auto_incr_val(rocksdb::WriteBatchBase *batch,
+ const GL_INDEX_ID &gl_index_id,
+ ulonglong val,
+ bool overwrite = false) const;
+ bool get_auto_incr_val(const GL_INDEX_ID &gl_index_id,
+ ulonglong *new_val) const;
+};
+
+struct Rdb_index_info {
+ GL_INDEX_ID m_gl_index_id;
+ uint16_t m_index_dict_version = 0;
+ uchar m_index_type = 0;
+ uint16_t m_kv_version = 0;
+ uint32 m_index_flags = 0;
+ uint64 m_ttl_duration = 0;
+};
+
+/*
+ @brief
+ Merge Operator for the auto_increment value in the system_cf
+
+ @detail
+ This class implements the rocksdb Merge Operator for auto_increment values
+ that are stored to the data dictionary every transaction.
+
+ The actual Merge function is triggered on compaction, memtable flushes, or
+ when get() is called on the same key.
+
+ */
+class Rdb_system_merge_op : public rocksdb::AssociativeMergeOperator {
+ public:
+ /*
+ Updates the new value associated with a key to be the maximum of the
+ passed in value and the existing value.
+
+ @param[IN] key
+ @param[IN] existing_value existing value for a key; nullptr if nonexistent
+ key
+ @param[IN] value
+ @param[OUT] new_value new value after Merge
+ @param[IN] logger
+ */
+ bool Merge(const rocksdb::Slice &key, const rocksdb::Slice *existing_value,
+ const rocksdb::Slice &value, std::string *new_value,
+ rocksdb::Logger *logger) const override {
+ DBUG_ASSERT(new_value != nullptr);
+
+ if (key.size() != Rdb_key_def::INDEX_NUMBER_SIZE * 3 ||
+ GetKeyType(key) != Rdb_key_def::AUTO_INC ||
+ value.size() !=
+ RDB_SIZEOF_AUTO_INCREMENT_VERSION + ROCKSDB_SIZEOF_AUTOINC_VALUE ||
+ GetVersion(value) > Rdb_key_def::AUTO_INCREMENT_VERSION) {
+ abort();
+ }
+
+ uint64_t merged_value = Deserialize(value);
+
+ if (existing_value != nullptr) {
+ if (existing_value->size() != RDB_SIZEOF_AUTO_INCREMENT_VERSION +
+ ROCKSDB_SIZEOF_AUTOINC_VALUE ||
+ GetVersion(*existing_value) > Rdb_key_def::AUTO_INCREMENT_VERSION) {
+ abort();
+ }
+
+ merged_value = std::max(merged_value, Deserialize(*existing_value));
+ }
+ Serialize(merged_value, new_value);
+ return true;
+ }
+
+ virtual const char *Name() const override { return "Rdb_system_merge_op"; }
+
+ private:
+ /*
+ Serializes the integer data to the new_value buffer or the target buffer
+ the merge operator will update to
+ */
+ void Serialize(const uint64_t data, std::string *new_value) const {
+ uchar value_buf[RDB_SIZEOF_AUTO_INCREMENT_VERSION +
+ ROCKSDB_SIZEOF_AUTOINC_VALUE] = {0};
+ uchar *ptr = value_buf;
+ /* fill in the auto increment version */
+ rdb_netbuf_store_uint16(ptr, Rdb_key_def::AUTO_INCREMENT_VERSION);
+ ptr += RDB_SIZEOF_AUTO_INCREMENT_VERSION;
+ /* fill in the auto increment value */
+ rdb_netbuf_store_uint64(ptr, data);
+ ptr += ROCKSDB_SIZEOF_AUTOINC_VALUE;
+ new_value->assign(reinterpret_cast<char *>(value_buf), ptr - value_buf);
+ }
+
+ /*
+ Gets the value of auto_increment type in the data dictionary from the
+ value slice
+
+ @Note Only to be used on data dictionary keys for the auto_increment type
+ */
+ uint64_t Deserialize(const rocksdb::Slice &s) const {
+ return rdb_netbuf_to_uint64(reinterpret_cast<const uchar *>(s.data()) +
+ RDB_SIZEOF_AUTO_INCREMENT_VERSION);
+ }
+
+ /*
+ Gets the type of the key of the key in the data dictionary.
+
+ @Note Only to be used on data dictionary keys for the auto_increment type
+ */
+ uint16_t GetKeyType(const rocksdb::Slice &s) const {
+ return rdb_netbuf_to_uint32(reinterpret_cast<const uchar *>(s.data()));
+ }
+
+ /*
+ Gets the version of the auto_increment value in the data dictionary.
+
+ @Note Only to be used on data dictionary value for the auto_increment type
+ */
+ uint16_t GetVersion(const rocksdb::Slice &s) const {
+ return rdb_netbuf_to_uint16(reinterpret_cast<const uchar *>(s.data()));
+ }
+};
+
+bool rdb_is_collation_supported(const my_core::CHARSET_INFO *const cs);
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_global.h b/storage/rocksdb/rdb_global.h
new file mode 100644
index 00000000..7213571b
--- /dev/null
+++ b/storage/rocksdb/rdb_global.h
@@ -0,0 +1,392 @@
+/*
+ Copyright (c) 2018, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/* MyRocks global type definitions goes here */
+
+#pragma once
+
+/* C++ standard header files */
+#include <limits>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./handler.h" /* handler */
+#include "./my_global.h" /* ulonglong */
+#include "./sql_string.h"
+#include "./ut0counter.h"
+
+namespace myrocks {
+/*
+ * class for exporting transaction information for
+ * information_schema.rocksdb_trx
+ */
+struct Rdb_trx_info {
+ std::string name;
+ ulonglong trx_id;
+ ulonglong write_count;
+ ulonglong lock_count;
+ int timeout_sec;
+ std::string state;
+ std::string waiting_key;
+ ulonglong waiting_cf_id;
+ int is_replication;
+ int skip_trx_api;
+ int read_only;
+ int deadlock_detect;
+ int num_ongoing_bulk_load;
+ ulong thread_id;
+ std::string query_str;
+};
+
+std::vector<Rdb_trx_info> rdb_get_all_trx_info();
+
+/*
+ * class for exporting deadlock transaction information for
+ * information_schema.rocksdb_deadlock
+ */
+struct Rdb_deadlock_info {
+ struct Rdb_dl_trx_info {
+ ulonglong trx_id;
+ std::string cf_name;
+ std::string waiting_key;
+ bool exclusive_lock;
+ std::string index_name;
+ std::string table_name;
+ };
+ std::vector<Rdb_dl_trx_info> path;
+ int64_t deadlock_time;
+ ulonglong victim_trx_id;
+};
+
+std::vector<Rdb_deadlock_info> rdb_get_deadlock_info();
+
+/*
+ This is
+ - the name of the default Column Family (the CF which stores indexes which
+ didn't explicitly specify which CF they are in)
+ - the name used to set the default column family parameter for per-cf
+ arguments.
+*/
+extern const std::string DEFAULT_CF_NAME;
+
+/*
+ This is the name of the Column Family used for storing the data dictionary.
+*/
+extern const std::string DEFAULT_SYSTEM_CF_NAME;
+
+/*
+ This is the name of the hidden primary key for tables with no pk.
+*/
+const char *const HIDDEN_PK_NAME = "HIDDEN_PK_ID";
+
+/*
+ Column family name which means "put this index into its own column family".
+ DEPRECATED!!!
+*/
+extern const std::string PER_INDEX_CF_NAME;
+
+/*
+ Name for the background thread.
+*/
+const char *const BG_THREAD_NAME = "myrocks-bg";
+
+/*
+ Name for the drop index thread.
+*/
+const char *const INDEX_THREAD_NAME = "myrocks-index";
+
+/*
+ Name for the manual compaction thread.
+*/
+const char *const MANUAL_COMPACTION_THREAD_NAME = "myrocks-mc";
+
+/*
+ Separator between partition name and the qualifier. Sample usage:
+
+ - p0_cfname=foo
+ - p3_tts_col=bar
+*/
+const char RDB_PER_PARTITION_QUALIFIER_NAME_SEP = '_';
+
+/*
+ Separator between qualifier name and value. Sample usage:
+
+ - p0_cfname=foo
+ - p3_tts_col=bar
+*/
+const char RDB_QUALIFIER_VALUE_SEP = '=';
+
+/*
+ Separator between multiple qualifier assignments. Sample usage:
+
+ - p0_cfname=foo;p1_cfname=bar;p2_cfname=baz
+*/
+const char RDB_QUALIFIER_SEP = ';';
+
+/*
+ Qualifier name for a custom per partition column family.
+*/
+const char *const RDB_CF_NAME_QUALIFIER = "cfname";
+
+/*
+ Qualifier name for a custom per partition ttl duration.
+*/
+const char *const RDB_TTL_DURATION_QUALIFIER = "ttl_duration";
+
+/*
+ Qualifier name for a custom per partition ttl duration.
+*/
+const char *const RDB_TTL_COL_QUALIFIER = "ttl_col";
+
+/*
+ Default, minimal valid, and maximum valid sampling rate values when collecting
+ statistics about table.
+*/
+#define RDB_DEFAULT_TBL_STATS_SAMPLE_PCT 10
+#define RDB_TBL_STATS_SAMPLE_PCT_MIN 1
+#define RDB_TBL_STATS_SAMPLE_PCT_MAX 100
+
+/*
+ Default and maximum values for rocksdb-compaction-sequential-deletes and
+ rocksdb-compaction-sequential-deletes-window to add basic boundary checking.
+*/
+#define DEFAULT_COMPACTION_SEQUENTIAL_DELETES 0
+#define MAX_COMPACTION_SEQUENTIAL_DELETES 2000000
+
+#define DEFAULT_COMPACTION_SEQUENTIAL_DELETES_WINDOW 0
+#define MAX_COMPACTION_SEQUENTIAL_DELETES_WINDOW 2000000
+
+/*
+ Default and maximum values for various compaction and flushing related
+ options. Numbers are based on the hardware we currently use and our internal
+ benchmarks which indicate that parallelization helps with the speed of
+ compactions.
+
+ Ideally of course we'll use heuristic technique to determine the number of
+ CPU-s and derive the values from there. This however has its own set of
+ problems and we'll choose simplicity for now.
+*/
+#define MAX_BACKGROUND_JOBS 64
+
+#define DEFAULT_SUBCOMPACTIONS 1
+#define MAX_SUBCOMPACTIONS 64
+
+/*
+ Default value for rocksdb_sst_mgr_rate_bytes_per_sec = 0 (disabled).
+*/
+#define DEFAULT_SST_MGR_RATE_BYTES_PER_SEC 0
+
+/*
+ Defines the field sizes for serializing XID object to a string representation.
+ string byte format: [field_size: field_value, ...]
+ [
+ 8: XID.formatID,
+ 1: XID.gtrid_length,
+ 1: XID.bqual_length,
+ XID.gtrid_length + XID.bqual_length: XID.data
+ ]
+*/
+#define RDB_FORMATID_SZ 8
+#define RDB_GTRID_SZ 1
+#define RDB_BQUAL_SZ 1
+#define RDB_XIDHDR_LEN (RDB_FORMATID_SZ + RDB_GTRID_SZ + RDB_BQUAL_SZ)
+
+/*
+ To fix an unhandled exception we specify the upper bound as LONGLONGMAX
+ instead of ULONGLONGMAX because the latter is -1 and causes an exception when
+ cast to jlong (signed) of JNI
+
+ The reason behind the cast issue is the lack of unsigned int support in Java.
+*/
+#define MAX_RATE_LIMITER_BYTES_PER_SEC static_cast<uint64_t>(LLONG_MAX)
+
+/*
+ Hidden PK column (for tables with no primary key) is a longlong (aka 8 bytes).
+ static_assert() in code will validate this assumption.
+*/
+#define ROCKSDB_SIZEOF_HIDDEN_PK_COLUMN sizeof(longlong)
+
+/*
+ Bytes used to store TTL, in the beginning of all records for tables with TTL
+ enabled.
+*/
+#define ROCKSDB_SIZEOF_TTL_RECORD sizeof(longlong)
+
+#define ROCKSDB_SIZEOF_AUTOINC_VALUE sizeof(longlong)
+
+/*
+ Maximum index prefix length in bytes.
+*/
+#define MAX_INDEX_COL_LEN_LARGE 3072
+#define MAX_INDEX_COL_LEN_SMALL 767
+
+/*
+ MyRocks specific error codes. NB! Please make sure that you will update
+ HA_ERR_ROCKSDB_LAST when adding new ones. Also update the strings in
+ rdb_error_messages to include any new error messages.
+*/
+#define HA_ERR_ROCKSDB_FIRST (HA_ERR_LAST + 1)
+#define HA_ERR_ROCKSDB_PK_REQUIRED (HA_ERR_ROCKSDB_FIRST + 0)
+#define HA_ERR_ROCKSDB_TABLE_DATA_DIRECTORY_NOT_SUPPORTED \
+ (HA_ERR_ROCKSDB_FIRST + 1)
+#define HA_ERR_ROCKSDB_TABLE_INDEX_DIRECTORY_NOT_SUPPORTED \
+ (HA_ERR_ROCKSDB_FIRST + 2)
+#define HA_ERR_ROCKSDB_COMMIT_FAILED (HA_ERR_ROCKSDB_FIRST + 3)
+#define HA_ERR_ROCKSDB_BULK_LOAD (HA_ERR_ROCKSDB_FIRST + 4)
+#define HA_ERR_ROCKSDB_CORRUPT_DATA (HA_ERR_ROCKSDB_FIRST + 5)
+#define HA_ERR_ROCKSDB_CHECKSUM_MISMATCH (HA_ERR_ROCKSDB_FIRST + 6)
+#define HA_ERR_ROCKSDB_INVALID_TABLE (HA_ERR_ROCKSDB_FIRST + 7)
+#define HA_ERR_ROCKSDB_PROPERTIES (HA_ERR_ROCKSDB_FIRST + 8)
+#define HA_ERR_ROCKSDB_MERGE_FILE_ERR (HA_ERR_ROCKSDB_FIRST + 9)
+/*
+ Each error code below maps to a RocksDB status code found in:
+ rocksdb/include/rocksdb/status.h
+*/
+#define HA_ERR_ROCKSDB_STATUS_NOT_FOUND (HA_ERR_LAST + 10)
+#define HA_ERR_ROCKSDB_STATUS_CORRUPTION (HA_ERR_LAST + 11)
+#define HA_ERR_ROCKSDB_STATUS_NOT_SUPPORTED (HA_ERR_LAST + 12)
+#define HA_ERR_ROCKSDB_STATUS_INVALID_ARGUMENT (HA_ERR_LAST + 13)
+#define HA_ERR_ROCKSDB_STATUS_IO_ERROR (HA_ERR_LAST + 14)
+#define HA_ERR_ROCKSDB_STATUS_NO_SPACE (HA_ERR_LAST + 15)
+#define HA_ERR_ROCKSDB_STATUS_MERGE_IN_PROGRESS (HA_ERR_LAST + 16)
+#define HA_ERR_ROCKSDB_STATUS_INCOMPLETE (HA_ERR_LAST + 17)
+#define HA_ERR_ROCKSDB_STATUS_SHUTDOWN_IN_PROGRESS (HA_ERR_LAST + 18)
+#define HA_ERR_ROCKSDB_STATUS_TIMED_OUT (HA_ERR_LAST + 19)
+#define HA_ERR_ROCKSDB_STATUS_ABORTED (HA_ERR_LAST + 20)
+#define HA_ERR_ROCKSDB_STATUS_LOCK_LIMIT (HA_ERR_LAST + 21)
+#define HA_ERR_ROCKSDB_STATUS_BUSY (HA_ERR_LAST + 22)
+#define HA_ERR_ROCKSDB_STATUS_DEADLOCK (HA_ERR_LAST + 23)
+#define HA_ERR_ROCKSDB_STATUS_EXPIRED (HA_ERR_LAST + 24)
+#define HA_ERR_ROCKSDB_STATUS_TRY_AGAIN (HA_ERR_LAST + 25)
+#define HA_ERR_ROCKSDB_LAST HA_ERR_ROCKSDB_STATUS_TRY_AGAIN
+
+const char *const rocksdb_hton_name = "ROCKSDB";
+
+typedef struct _gl_index_id_s {
+ uint32_t cf_id;
+ uint32_t index_id;
+ bool operator==(const struct _gl_index_id_s &other) const {
+ return cf_id == other.cf_id && index_id == other.index_id;
+ }
+ bool operator!=(const struct _gl_index_id_s &other) const {
+ return cf_id != other.cf_id || index_id != other.index_id;
+ }
+ bool operator<(const struct _gl_index_id_s &other) const {
+ return cf_id < other.cf_id ||
+ (cf_id == other.cf_id && index_id < other.index_id);
+ }
+ bool operator<=(const struct _gl_index_id_s &other) const {
+ return cf_id < other.cf_id ||
+ (cf_id == other.cf_id && index_id <= other.index_id);
+ }
+ bool operator>(const struct _gl_index_id_s &other) const {
+ return cf_id > other.cf_id ||
+ (cf_id == other.cf_id && index_id > other.index_id);
+ }
+ bool operator>=(const struct _gl_index_id_s &other) const {
+ return cf_id > other.cf_id ||
+ (cf_id == other.cf_id && index_id >= other.index_id);
+ }
+} GL_INDEX_ID;
+
+enum operation_type : int {
+ ROWS_DELETED = 0,
+ ROWS_INSERTED,
+ ROWS_READ,
+ ROWS_UPDATED,
+ ROWS_DELETED_BLIND,
+ ROWS_EXPIRED,
+ ROWS_FILTERED,
+ ROWS_HIDDEN_NO_SNAPSHOT,
+ ROWS_MAX
+};
+
+enum query_type : int { QUERIES_POINT = 0, QUERIES_RANGE, QUERIES_MAX };
+
+#if defined(HAVE_SCHED_GETCPU)
+#define RDB_INDEXER get_sched_indexer_t
+#else
+#define RDB_INDEXER thread_id_indexer_t
+#endif
+
+/* Global statistics struct used inside MyRocks */
+struct st_global_stats {
+ ib_counter_t<ulonglong, 64, RDB_INDEXER> rows[ROWS_MAX];
+
+ // system_rows_ stats are only for system
+ // tables. They are not counted in rows_* stats.
+ ib_counter_t<ulonglong, 64, RDB_INDEXER> system_rows[ROWS_MAX];
+
+ ib_counter_t<ulonglong, 64, RDB_INDEXER> queries[QUERIES_MAX];
+
+ ib_counter_t<ulonglong, 64, RDB_INDEXER> covered_secondary_key_lookups;
+};
+
+/* Struct used for exporting status to MySQL */
+struct st_export_stats {
+ ulonglong rows_deleted;
+ ulonglong rows_inserted;
+ ulonglong rows_read;
+ ulonglong rows_updated;
+ ulonglong rows_deleted_blind;
+ ulonglong rows_expired;
+ ulonglong rows_filtered;
+ ulonglong rows_hidden_no_snapshot;
+
+ ulonglong system_rows_deleted;
+ ulonglong system_rows_inserted;
+ ulonglong system_rows_read;
+ ulonglong system_rows_updated;
+
+ ulonglong queries_point;
+ ulonglong queries_range;
+
+ ulonglong covered_secondary_key_lookups;
+};
+
+/* Struct used for exporting RocksDB memory status */
+struct st_memory_stats {
+ ulonglong memtable_total;
+ ulonglong memtable_unflushed;
+};
+
+/* Struct used for exporting RocksDB IO stalls stats */
+struct st_io_stall_stats {
+ ulonglong level0_slowdown;
+ ulonglong level0_slowdown_with_compaction;
+ ulonglong level0_numfiles;
+ ulonglong level0_numfiles_with_compaction;
+ ulonglong stop_for_pending_compaction_bytes;
+ ulonglong slowdown_for_pending_compaction_bytes;
+ ulonglong memtable_compaction;
+ ulonglong memtable_slowdown;
+ ulonglong total_stop;
+ ulonglong total_slowdown;
+
+ st_io_stall_stats()
+ : level0_slowdown(0),
+ level0_slowdown_with_compaction(0),
+ level0_numfiles(0),
+ level0_numfiles_with_compaction(0),
+ stop_for_pending_compaction_bytes(0),
+ slowdown_for_pending_compaction_bytes(0),
+ memtable_compaction(0),
+ memtable_slowdown(0),
+ total_stop(0),
+ total_slowdown(0) {}
+};
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_i_s.cc b/storage/rocksdb/rdb_i_s.cc
new file mode 100644
index 00000000..5350ec3b
--- /dev/null
+++ b/storage/rocksdb/rdb_i_s.cc
@@ -0,0 +1,1997 @@
+/*
+ Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+/* C++ standard header files */
+#include <algorithm>
+#include <cctype>
+#include <map>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include <sql_show.h>
+
+/* RocksDB header files */
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/utilities/transaction_db.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./rdb_cf_manager.h"
+#include "./rdb_datadic.h"
+#include "./rdb_utils.h"
+#include "./rdb_mariadb_server_port.h"
+
+#include "./rdb_mariadb_port.h"
+
+namespace myrocks {
+
+/**
+ Define the INFORMATION_SCHEMA (I_S) structures needed by MyRocks storage
+ engine.
+*/
+
+#define ROCKSDB_FIELD_INFO(_name_, _len_, _type_, _flag_) \
+ { _name_, _len_, _type_, 0, _flag_, nullptr, 0 }
+
+#define ROCKSDB_FIELD_INFO_END \
+ ROCKSDB_FIELD_INFO(nullptr, 0, MYSQL_TYPE_NULL, 0)
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_CFSTATS dynamic table
+ */
+namespace RDB_CFSTATS_FIELD {
+enum { CF_NAME = 0, STAT_TYPE, VALUE };
+} // namespace RDB_CFSTATS_FIELD
+
+using namespace Show;
+
+static ST_FIELD_INFO rdb_i_s_cfstats_fields_info[] = {
+ Column("CF_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("STAT_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("VALUE", SLonglong(), NOT_NULL),
+ CEnd()
+};
+
+static int rdb_i_s_cfstats_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ int ret = 0;
+ uint64_t val;
+
+ const std::vector<std::pair<const std::string, std::string>> cf_properties = {
+ {rocksdb::DB::Properties::kNumImmutableMemTable,
+ "NUM_IMMUTABLE_MEM_TABLE"},
+ {rocksdb::DB::Properties::kMemTableFlushPending,
+ "MEM_TABLE_FLUSH_PENDING"},
+ {rocksdb::DB::Properties::kCompactionPending, "COMPACTION_PENDING"},
+ {rocksdb::DB::Properties::kCurSizeActiveMemTable,
+ "CUR_SIZE_ACTIVE_MEM_TABLE"},
+ {rocksdb::DB::Properties::kCurSizeAllMemTables,
+ "CUR_SIZE_ALL_MEM_TABLES"},
+ {rocksdb::DB::Properties::kNumEntriesActiveMemTable,
+ "NUM_ENTRIES_ACTIVE_MEM_TABLE"},
+ {rocksdb::DB::Properties::kNumEntriesImmMemTables,
+ "NUM_ENTRIES_IMM_MEM_TABLES"},
+ {rocksdb::DB::Properties::kEstimateTableReadersMem,
+ "NON_BLOCK_CACHE_SST_MEM_USAGE"},
+ {rocksdb::DB::Properties::kNumLiveVersions, "NUM_LIVE_VERSIONS"}};
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (const auto &cf_name : cf_manager.get_cf_names()) {
+ DBUG_ASSERT(!cf_name.empty());
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(cf_name);
+ if (cfh == nullptr) {
+ continue;
+ }
+
+ for (const auto &property : cf_properties) {
+ if (!rdb->GetIntProperty(cfh, property.first, &val)) {
+ continue;
+ }
+
+ tables->table->field[RDB_CFSTATS_FIELD::CF_NAME]->store(
+ cf_name.c_str(), cf_name.size(), system_charset_info);
+ tables->table->field[RDB_CFSTATS_FIELD::STAT_TYPE]->store(
+ property.second.c_str(), property.second.size(), system_charset_info);
+ tables->table->field[RDB_CFSTATS_FIELD::VALUE]->store(val, true);
+
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_cfstats_init(void *p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_cfstats_fields_info;
+ schema->fill_table = rdb_i_s_cfstats_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_DBSTATS dynamic table
+ */
+namespace RDB_DBSTATS_FIELD {
+enum { STAT_TYPE = 0, VALUE };
+} // namespace RDB_DBSTATS_FIELD
+
+static ST_FIELD_INFO rdb_i_s_dbstats_fields_info[] = {
+ Column("STAT_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("VALUE", SLonglong(), NOT_NULL),
+ CEnd()};
+
+static int rdb_i_s_dbstats_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ int ret = 0;
+ uint64_t val;
+
+ const std::vector<std::pair<std::string, std::string>> db_properties = {
+ {rocksdb::DB::Properties::kBackgroundErrors, "DB_BACKGROUND_ERRORS"},
+ {rocksdb::DB::Properties::kNumSnapshots, "DB_NUM_SNAPSHOTS"},
+ {rocksdb::DB::Properties::kOldestSnapshotTime,
+ "DB_OLDEST_SNAPSHOT_TIME"}};
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const rocksdb::BlockBasedTableOptions &table_options =
+ rdb_get_table_options();
+
+ for (const auto &property : db_properties) {
+ if (!rdb->GetIntProperty(property.first, &val)) {
+ continue;
+ }
+
+ tables->table->field[RDB_DBSTATS_FIELD::STAT_TYPE]->store(
+ property.second.c_str(), property.second.size(), system_charset_info);
+ tables->table->field[RDB_DBSTATS_FIELD::VALUE]->store(val, true);
+
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ }
+
+ /*
+ Currently, this can only show the usage of a block cache allocated
+ directly by the handlerton. If the column family config specifies a block
+ cache (i.e. the column family option has a parameter such as
+ block_based_table_factory={block_cache=1G}), then the block cache is
+ allocated within the rocksdb::GetColumnFamilyOptionsFromString().
+
+ There is no interface to retrieve this block cache, nor fetch the usage
+ information from the column family.
+ */
+ val = (table_options.block_cache ? table_options.block_cache->GetUsage() : 0);
+
+ tables->table->field[RDB_DBSTATS_FIELD::STAT_TYPE]->store(
+ STRING_WITH_LEN("DB_BLOCK_CACHE_USAGE"), system_charset_info);
+ tables->table->field[RDB_DBSTATS_FIELD::VALUE]->store(val, true);
+
+ ret =
+ static_cast<int>(my_core::schema_table_store_record(thd, tables->table));
+
+ DBUG_RETURN(ret);
+}
+
+static int rdb_i_s_dbstats_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_dbstats_fields_info;
+ schema->fill_table = rdb_i_s_dbstats_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT dynamic table
+ */
+namespace RDB_PERF_CONTEXT_FIELD {
+enum { TABLE_SCHEMA = 0, TABLE_NAME, PARTITION_NAME, STAT_TYPE, VALUE };
+} // namespace RDB_PERF_CONTEXT_FIELD
+
+static ST_FIELD_INFO rdb_i_s_perf_context_fields_info[] = {
+ Column("TABLE_SCHEMA", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("TABLE_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("PARTITION_NAME", Varchar(NAME_LEN + 1), NULLABLE),
+ Column("STAT_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("VALUE", SLonglong(), NOT_NULL),
+ CEnd()};
+
+static int rdb_i_s_perf_context_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+
+ int ret = 0;
+ Field **field = tables->table->field;
+ DBUG_ASSERT(field != nullptr);
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const std::vector<std::string> tablenames = rdb_get_open_table_names();
+
+ for (const auto &it : tablenames) {
+ std::string str, dbname, tablename, partname;
+ Rdb_perf_counters counters;
+
+ int rc = rdb_normalize_tablename(it, &str);
+
+ if (rc != HA_EXIT_SUCCESS) {
+ DBUG_RETURN(rc);
+ }
+
+ if (rdb_split_normalized_tablename(str, &dbname, &tablename, &partname)) {
+ continue;
+ }
+
+ if (rdb_get_table_perf_counters(it.c_str(), &counters)) {
+ continue;
+ }
+
+ field[RDB_PERF_CONTEXT_FIELD::TABLE_SCHEMA]->store(
+ dbname.c_str(), dbname.size(), system_charset_info);
+ field[RDB_PERF_CONTEXT_FIELD::TABLE_NAME]->store(
+ tablename.c_str(), tablename.size(), system_charset_info);
+
+ if (partname.size() == 0) {
+ field[RDB_PERF_CONTEXT_FIELD::PARTITION_NAME]->set_null();
+ } else {
+ field[RDB_PERF_CONTEXT_FIELD::PARTITION_NAME]->set_notnull();
+ field[RDB_PERF_CONTEXT_FIELD::PARTITION_NAME]->store(
+ partname.c_str(), partname.size(), system_charset_info);
+ }
+
+ for (int i = 0; i < PC_MAX_IDX; i++) {
+ field[RDB_PERF_CONTEXT_FIELD::STAT_TYPE]->store(
+ rdb_pc_stat_types[i].c_str(), rdb_pc_stat_types[i].size(),
+ system_charset_info);
+ field[RDB_PERF_CONTEXT_FIELD::VALUE]->store(counters.m_value[i], true);
+
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_perf_context_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_perf_context_fields_info;
+ schema->fill_table = rdb_i_s_perf_context_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_PERF_CONTEXT_GLOBAL dynamic table
+ */
+namespace RDB_PERF_CONTEXT_GLOBAL_FIELD {
+enum { STAT_TYPE = 0, VALUE };
+} // namespace RDB_PERF_CONTEXT_GLOBAL_FIELD
+
+static ST_FIELD_INFO rdb_i_s_perf_context_global_fields_info[] = {
+ Column("STAT_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("VALUE", SLonglong(), NOT_NULL),
+ CEnd()};
+
+static int rdb_i_s_perf_context_global_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ int ret = 0;
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ // Get a copy of the global perf counters.
+ Rdb_perf_counters global_counters;
+ rdb_get_global_perf_counters(&global_counters);
+
+ for (int i = 0; i < PC_MAX_IDX; i++) {
+ tables->table->field[RDB_PERF_CONTEXT_GLOBAL_FIELD::STAT_TYPE]->store(
+ rdb_pc_stat_types[i].c_str(), rdb_pc_stat_types[i].size(),
+ system_charset_info);
+ tables->table->field[RDB_PERF_CONTEXT_GLOBAL_FIELD::VALUE]->store(
+ global_counters.m_value[i], true);
+
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_perf_context_global_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_perf_context_global_fields_info;
+ schema->fill_table = rdb_i_s_perf_context_global_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_CFOPTIONS dynamic table
+ */
+namespace RDB_CFOPTIONS_FIELD {
+enum { CF_NAME = 0, OPTION_TYPE, VALUE };
+} // namespace RDB_CFOPTIONS_FIELD
+
+static ST_FIELD_INFO rdb_i_s_cfoptions_fields_info[] = {
+ Column("CF_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("OPTION_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("VALUE", Varchar(NAME_LEN + 1), NOT_NULL),
+ CEnd()};
+
+static int rdb_i_s_cfoptions_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+
+ int ret = 0;
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (const auto &cf_name : cf_manager.get_cf_names()) {
+ std::string val;
+ rocksdb::ColumnFamilyOptions opts;
+
+ DBUG_ASSERT(!cf_name.empty());
+ cf_manager.get_cf_options(cf_name, &opts);
+
+ std::vector<std::pair<std::string, std::string>> cf_option_types = {
+ {"COMPARATOR", opts.comparator == nullptr
+ ? "NULL"
+ : std::string(opts.comparator->Name())},
+ {"MERGE_OPERATOR", opts.merge_operator == nullptr
+ ? "NULL"
+ : std::string(opts.merge_operator->Name())},
+ {"COMPACTION_FILTER",
+ opts.compaction_filter == nullptr
+ ? "NULL"
+ : std::string(opts.compaction_filter->Name())},
+ {"COMPACTION_FILTER_FACTORY",
+ opts.compaction_filter_factory == nullptr
+ ? "NULL"
+ : std::string(opts.compaction_filter_factory->Name())},
+ {"WRITE_BUFFER_SIZE", std::to_string(opts.write_buffer_size)},
+ {"MAX_WRITE_BUFFER_NUMBER",
+ std::to_string(opts.max_write_buffer_number)},
+ {"MIN_WRITE_BUFFER_NUMBER_TO_MERGE",
+ std::to_string(opts.min_write_buffer_number_to_merge)},
+ {"NUM_LEVELS", std::to_string(opts.num_levels)},
+ {"LEVEL0_FILE_NUM_COMPACTION_TRIGGER",
+ std::to_string(opts.level0_file_num_compaction_trigger)},
+ {"LEVEL0_SLOWDOWN_WRITES_TRIGGER",
+ std::to_string(opts.level0_slowdown_writes_trigger)},
+ {"LEVEL0_STOP_WRITES_TRIGGER",
+ std::to_string(opts.level0_stop_writes_trigger)},
+ {"MAX_MEM_COMPACTION_LEVEL",
+ std::to_string(opts.max_mem_compaction_level)},
+ {"TARGET_FILE_SIZE_BASE", std::to_string(opts.target_file_size_base)},
+ {"TARGET_FILE_SIZE_MULTIPLIER",
+ std::to_string(opts.target_file_size_multiplier)},
+ {"MAX_BYTES_FOR_LEVEL_BASE",
+ std::to_string(opts.max_bytes_for_level_base)},
+ {"LEVEL_COMPACTION_DYNAMIC_LEVEL_BYTES",
+ opts.level_compaction_dynamic_level_bytes ? "ON" : "OFF"},
+ {"MAX_BYTES_FOR_LEVEL_MULTIPLIER",
+ std::to_string(opts.max_bytes_for_level_multiplier)},
+ {"SOFT_RATE_LIMIT", std::to_string(opts.soft_rate_limit)},
+ {"HARD_RATE_LIMIT", std::to_string(opts.hard_rate_limit)},
+ {"RATE_LIMIT_DELAY_MAX_MILLISECONDS",
+ std::to_string(opts.rate_limit_delay_max_milliseconds)},
+ {"ARENA_BLOCK_SIZE", std::to_string(opts.arena_block_size)},
+ {"DISABLE_AUTO_COMPACTIONS",
+ opts.disable_auto_compactions ? "ON" : "OFF"},
+ {"PURGE_REDUNDANT_KVS_WHILE_FLUSH",
+ opts.purge_redundant_kvs_while_flush ? "ON" : "OFF"},
+ {"MAX_SEQUENTIAL_SKIP_IN_ITERATIONS",
+ std::to_string(opts.max_sequential_skip_in_iterations)},
+ {"MEMTABLE_FACTORY", opts.memtable_factory == nullptr
+ ? "NULL"
+ : opts.memtable_factory->Name()},
+ {"INPLACE_UPDATE_SUPPORT", opts.inplace_update_support ? "ON" : "OFF"},
+ {"INPLACE_UPDATE_NUM_LOCKS",
+ opts.inplace_update_num_locks ? "ON" : "OFF"},
+ {"MEMTABLE_PREFIX_BLOOM_BITS_RATIO",
+ std::to_string(opts.memtable_prefix_bloom_size_ratio)},
+ {"MEMTABLE_PREFIX_BLOOM_HUGE_PAGE_TLB_SIZE",
+ std::to_string(opts.memtable_huge_page_size)},
+ {"BLOOM_LOCALITY", std::to_string(opts.bloom_locality)},
+ {"MAX_SUCCESSIVE_MERGES", std::to_string(opts.max_successive_merges)},
+ {"OPTIMIZE_FILTERS_FOR_HITS",
+ (opts.optimize_filters_for_hits ? "ON" : "OFF")},
+ };
+
+ // get MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL option value
+ val = opts.max_bytes_for_level_multiplier_additional.empty() ? "NULL" : "";
+
+ for (const auto &level : opts.max_bytes_for_level_multiplier_additional) {
+ val.append(std::to_string(level) + ":");
+ }
+
+ val.pop_back();
+ cf_option_types.push_back(
+ {"MAX_BYTES_FOR_LEVEL_MULTIPLIER_ADDITIONAL", val});
+
+ // get COMPRESSION_TYPE option value
+ GetStringFromCompressionType(&val, opts.compression);
+
+ if (val.empty()) {
+ val = "NULL";
+ }
+
+ cf_option_types.push_back({"COMPRESSION_TYPE", val});
+
+ // get COMPRESSION_PER_LEVEL option value
+ val = opts.compression_per_level.empty() ? "NULL" : "";
+
+ for (const auto &compression_type : opts.compression_per_level) {
+ std::string res;
+
+ GetStringFromCompressionType(&res, compression_type);
+
+ if (!res.empty()) {
+ val.append(res + ":");
+ }
+ }
+
+ val.pop_back();
+ cf_option_types.push_back({"COMPRESSION_PER_LEVEL", val});
+
+ // get compression_opts value
+ val = std::to_string(opts.compression_opts.window_bits) + ":";
+ val.append(std::to_string(opts.compression_opts.level) + ":");
+ val.append(std::to_string(opts.compression_opts.strategy));
+
+ cf_option_types.push_back({"COMPRESSION_OPTS", val});
+
+ // bottommost_compression
+ if (opts.bottommost_compression) {
+ std::string res;
+
+ GetStringFromCompressionType(&res, opts.bottommost_compression);
+
+ if (!res.empty()) {
+ cf_option_types.push_back({"BOTTOMMOST_COMPRESSION", res});
+ }
+ }
+
+ // get PREFIX_EXTRACTOR option
+ cf_option_types.push_back(
+ {"PREFIX_EXTRACTOR", opts.prefix_extractor == nullptr
+ ? "NULL"
+ : std::string(opts.prefix_extractor->Name())});
+
+ // get COMPACTION_STYLE option
+ switch (opts.compaction_style) {
+ case rocksdb::kCompactionStyleLevel:
+ val = "kCompactionStyleLevel";
+ break;
+ case rocksdb::kCompactionStyleUniversal:
+ val = "kCompactionStyleUniversal";
+ break;
+ case rocksdb::kCompactionStyleFIFO:
+ val = "kCompactionStyleFIFO";
+ break;
+ case rocksdb::kCompactionStyleNone:
+ val = "kCompactionStyleNone";
+ break;
+ default:
+ val = "NULL";
+ }
+
+ cf_option_types.push_back({"COMPACTION_STYLE", val});
+
+ // get COMPACTION_OPTIONS_UNIVERSAL related options
+ const rocksdb::CompactionOptionsUniversal compac_opts =
+ opts.compaction_options_universal;
+
+ val = "{SIZE_RATIO=";
+
+ val.append(std::to_string(compac_opts.size_ratio));
+ val.append("; MIN_MERGE_WIDTH=");
+ val.append(std::to_string(compac_opts.min_merge_width));
+ val.append("; MAX_MERGE_WIDTH=");
+ val.append(std::to_string(compac_opts.max_merge_width));
+ val.append("; MAX_SIZE_AMPLIFICATION_PERCENT=");
+ val.append(std::to_string(compac_opts.max_size_amplification_percent));
+ val.append("; COMPRESSION_SIZE_PERCENT=");
+ val.append(std::to_string(compac_opts.compression_size_percent));
+ val.append("; STOP_STYLE=");
+
+ switch (compac_opts.stop_style) {
+ case rocksdb::kCompactionStopStyleSimilarSize:
+ val.append("kCompactionStopStyleSimilarSize}");
+ break;
+ case rocksdb::kCompactionStopStyleTotalSize:
+ val.append("kCompactionStopStyleTotalSize}");
+ break;
+ default:
+ val.append("}");
+ }
+
+ cf_option_types.push_back({"COMPACTION_OPTIONS_UNIVERSAL", val});
+
+ // get COMPACTION_OPTION_FIFO option
+ cf_option_types.push_back(
+ {"COMPACTION_OPTION_FIFO::MAX_TABLE_FILES_SIZE",
+ std::to_string(opts.compaction_options_fifo.max_table_files_size)});
+
+ // get table related options
+ std::vector<std::string> table_options =
+ split_into_vector(opts.table_factory->GetPrintableTableOptions(), '\n');
+
+ for (auto option : table_options) {
+ option.erase(std::remove(option.begin(), option.end(), ' '),
+ option.end());
+
+ int pos = option.find(":");
+ std::string option_name = option.substr(0, pos);
+ std::string option_value = option.substr(pos + 1, option.length());
+ std::transform(option_name.begin(), option_name.end(),
+ option_name.begin(),
+ [](unsigned char c) { return std::toupper(c); });
+
+ cf_option_types.push_back(
+ {"TABLE_FACTORY::" + option_name, option_value});
+ }
+
+ for (const auto &cf_option_type : cf_option_types) {
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ tables->table->field[RDB_CFOPTIONS_FIELD::CF_NAME]->store(
+ cf_name.c_str(), cf_name.size(), system_charset_info);
+ tables->table->field[RDB_CFOPTIONS_FIELD::OPTION_TYPE]->store(
+ cf_option_type.first.c_str(), cf_option_type.first.size(),
+ system_charset_info);
+ tables->table->field[RDB_CFOPTIONS_FIELD::VALUE]->store(
+ cf_option_type.second.c_str(), cf_option_type.second.size(),
+ system_charset_info);
+
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret) {
+ DBUG_RETURN(ret);
+ }
+ }
+ }
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_GLOBAL_INFO dynamic table
+ */
+namespace RDB_GLOBAL_INFO_FIELD {
+enum { TYPE = 0, NAME, VALUE };
+}
+
+static ST_FIELD_INFO rdb_i_s_global_info_fields_info[] = {
+ Column("TYPE", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("NAME", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("VALUE", Varchar(FN_REFLEN + 1), NOT_NULL),
+ CEnd()};
+
+/*
+ * helper function for rdb_i_s_global_info_fill_table
+ * to insert (TYPE, KEY, VALUE) rows into
+ * information_schema.rocksdb_global_info
+ */
+static int rdb_global_info_fill_row(my_core::THD *const thd,
+ my_core::TABLE_LIST *const tables,
+ const char *const type,
+ const char *const name,
+ const char *const value) {
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(type != nullptr);
+ DBUG_ASSERT(name != nullptr);
+ DBUG_ASSERT(value != nullptr);
+
+ Field **field = tables->table->field;
+ DBUG_ASSERT(field != nullptr);
+
+ field[RDB_GLOBAL_INFO_FIELD::TYPE]->store(type, strlen(type),
+ system_charset_info);
+ field[RDB_GLOBAL_INFO_FIELD::NAME]->store(name, strlen(name),
+ system_charset_info);
+ field[RDB_GLOBAL_INFO_FIELD::VALUE]->store(value, strlen(value),
+ system_charset_info);
+
+ return my_core::schema_table_store_record(thd, tables->table);
+}
+
+static int rdb_i_s_global_info_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+
+ static const uint32_t INT_BUF_LEN = 21;
+ static const uint32_t CF_ID_INDEX_BUF_LEN = 60;
+
+ int ret = 0;
+
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ /* binlog info */
+ Rdb_binlog_manager *const blm = rdb_get_binlog_manager();
+ DBUG_ASSERT(blm != nullptr);
+
+ char file_buf[FN_REFLEN + 1] = {0};
+ my_off_t pos = 0;
+ char pos_buf[INT_BUF_LEN] = {0};
+ char gtid_buf[GTID_BUF_LEN] = {0};
+
+ if (blm->read(file_buf, &pos, gtid_buf)) {
+ snprintf(pos_buf, INT_BUF_LEN, "%llu", (ulonglong)pos);
+
+ ret |= rdb_global_info_fill_row(thd, tables, "BINLOG", "FILE", file_buf);
+ ret |= rdb_global_info_fill_row(thd, tables, "BINLOG", "POS", pos_buf);
+ ret |= rdb_global_info_fill_row(thd, tables, "BINLOG", "GTID", gtid_buf);
+ }
+
+ /* max index info */
+ const Rdb_dict_manager *const dict_manager = rdb_get_dict_manager();
+ DBUG_ASSERT(dict_manager != nullptr);
+
+ uint32_t max_index_id;
+ char max_index_id_buf[INT_BUF_LEN] = {0};
+
+ if (dict_manager->get_max_index_id(&max_index_id)) {
+ snprintf(max_index_id_buf, INT_BUF_LEN, "%u", max_index_id);
+
+ ret |= rdb_global_info_fill_row(thd, tables, "MAX_INDEX_ID", "MAX_INDEX_ID",
+ max_index_id_buf);
+ }
+
+ /* cf_id -> cf_flags */
+ char cf_id_buf[INT_BUF_LEN] = {0};
+ char cf_value_buf[FN_REFLEN + 1] = {0};
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ DBUG_ASSERT(cf_handle != nullptr);
+
+ uint flags;
+
+ if (!dict_manager->get_cf_flags(cf_handle->GetID(), &flags)) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Failed to get column family flags "
+ "from CF with id = %u. MyRocks data dictionary may "
+ "be corrupted.",
+ cf_handle->GetID());
+ abort();
+ }
+
+ snprintf(cf_id_buf, INT_BUF_LEN, "%u", cf_handle->GetID());
+ snprintf(cf_value_buf, FN_REFLEN, "%s [%u]", cf_handle->GetName().c_str(),
+ flags);
+
+ ret |= rdb_global_info_fill_row(thd, tables, "CF_FLAGS", cf_id_buf,
+ cf_value_buf);
+
+ if (ret) {
+ break;
+ }
+ }
+
+ /* DDL_DROP_INDEX_ONGOING */
+ std::unordered_set<GL_INDEX_ID> gl_index_ids;
+ dict_manager->get_ongoing_index_operation(
+ &gl_index_ids, Rdb_key_def::DDL_DROP_INDEX_ONGOING);
+ char cf_id_index_buf[CF_ID_INDEX_BUF_LEN] = {0};
+
+ for (auto gl_index_id : gl_index_ids) {
+ snprintf(cf_id_index_buf, CF_ID_INDEX_BUF_LEN, "cf_id:%u,index_id:%u",
+ gl_index_id.cf_id, gl_index_id.index_id);
+
+ ret |= rdb_global_info_fill_row(thd, tables, "DDL_DROP_INDEX_ONGOING",
+ cf_id_index_buf, "");
+
+ if (ret) {
+ break;
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_COMPACTION_STATS dynamic table
+ */
+static int rdb_i_s_compact_stats_fill_table(
+ my_core::THD *thd, my_core::TABLE_LIST *tables,
+ my_core::Item *cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+
+ DBUG_ENTER_FUNC();
+
+ int ret = 0;
+ rocksdb::DB *rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (auto cf_name : cf_manager.get_cf_names()) {
+ rocksdb::ColumnFamilyHandle *cfh = cf_manager.get_cf(cf_name);
+
+ if (cfh == nullptr) {
+ continue;
+ }
+
+ std::map<std::string, std::string> props;
+ bool bool_ret MY_ATTRIBUTE((__unused__));
+ bool_ret = rdb->GetMapProperty(cfh, "rocksdb.cfstats", &props);
+ DBUG_ASSERT(bool_ret);
+
+ const std::string prop_name_prefix = "compaction.";
+ for (auto const &prop_ent : props) {
+ std::string prop_name = prop_ent.first;
+ if (prop_name.find(prop_name_prefix) != 0) {
+ continue;
+ }
+ std::string value = prop_ent.second;
+ std::size_t del_pos = prop_name.find('.', prop_name_prefix.size());
+ DBUG_ASSERT(del_pos != std::string::npos);
+ std::string level_str = prop_name.substr(
+ prop_name_prefix.size(), del_pos - prop_name_prefix.size());
+ std::string type_str = prop_name.substr(del_pos + 1);
+
+ Field **field = tables->table->field;
+ DBUG_ASSERT(field != nullptr);
+
+ field[0]->store(cf_name.c_str(), cf_name.size(), system_charset_info);
+ field[1]->store(level_str.c_str(), level_str.size(), system_charset_info);
+ field[2]->store(type_str.c_str(), type_str.size(), system_charset_info);
+ field[3]->store(std::stod(value));
+
+ ret |= static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ DBUG_RETURN(ret);
+ }
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+static ST_FIELD_INFO rdb_i_s_compact_stats_fields_info[] = {
+ Column("CF_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("LEVEL", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("TYPE", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("VALUE", Double(MY_INT64_NUM_DECIMAL_DIGITS), NOT_NULL),
+ CEnd()};
+
+namespace // anonymous namespace = not visible outside this source file
+{
+struct Rdb_ddl_scanner : public Rdb_tables_scanner {
+ my_core::THD *m_thd;
+ my_core::TABLE *m_table;
+
+ int add_table(Rdb_tbl_def *tdef) override;
+};
+} // anonymous namespace
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_DDL dynamic table
+ */
+namespace RDB_DDL_FIELD {
+enum {
+ TABLE_SCHEMA = 0,
+ TABLE_NAME,
+ PARTITION_NAME,
+ INDEX_NAME,
+ COLUMN_FAMILY,
+ INDEX_NUMBER,
+ INDEX_TYPE,
+ KV_FORMAT_VERSION,
+ TTL_DURATION,
+ INDEX_FLAGS,
+ CF,
+ AUTO_INCREMENT
+};
+} // namespace RDB_DDL_FIELD
+
+static ST_FIELD_INFO rdb_i_s_ddl_fields_info[] = {
+ Column("TABLE_SCHEMA", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("TABLE_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("PARTITION_NAME", Varchar(NAME_LEN + 1), NULLABLE),
+ Column("INDEX_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("COLUMN_FAMILY", SLong(), NOT_NULL),
+ Column("INDEX_NUMBER", SLong(), NOT_NULL),
+ Column("INDEX_TYPE", SShort(6), NOT_NULL),
+ Column("KV_FORMAT_VERSION", SShort(6), NOT_NULL),
+ Column("TTL_DURATION", SLonglong(), NOT_NULL),
+ Column("INDEX_FLAGS", SLonglong(), NOT_NULL),
+ Column("CF", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("AUTO_INCREMENT", ULonglong(), NULLABLE),
+ CEnd()};
+
+int Rdb_ddl_scanner::add_table(Rdb_tbl_def *tdef) {
+ DBUG_ASSERT(tdef != nullptr);
+
+ int ret = 0;
+
+ DBUG_ASSERT(m_table != nullptr);
+ Field **field = m_table->field;
+ DBUG_ASSERT(field != nullptr);
+ const Rdb_dict_manager *dict_manager = rdb_get_dict_manager();
+
+ const std::string &dbname = tdef->base_dbname();
+ field[RDB_DDL_FIELD::TABLE_SCHEMA]->store(dbname.c_str(), dbname.size(),
+ system_charset_info);
+
+ const std::string &tablename = tdef->base_tablename();
+ field[RDB_DDL_FIELD::TABLE_NAME]->store(tablename.c_str(), tablename.size(),
+ system_charset_info);
+
+ const std::string &partname = tdef->base_partition();
+ if (partname.length() == 0) {
+ field[RDB_DDL_FIELD::PARTITION_NAME]->set_null();
+ } else {
+ field[RDB_DDL_FIELD::PARTITION_NAME]->set_notnull();
+ field[RDB_DDL_FIELD::PARTITION_NAME]->store(
+ partname.c_str(), partname.size(), system_charset_info);
+ }
+
+ for (uint i = 0; i < tdef->m_key_count; i++) {
+ const Rdb_key_def &kd = *tdef->m_key_descr_arr[i];
+
+ field[RDB_DDL_FIELD::INDEX_NAME]->store(kd.m_name.c_str(), kd.m_name.size(),
+ system_charset_info);
+
+ GL_INDEX_ID gl_index_id = kd.get_gl_index_id();
+ field[RDB_DDL_FIELD::COLUMN_FAMILY]->store(gl_index_id.cf_id, true);
+ field[RDB_DDL_FIELD::INDEX_NUMBER]->store(gl_index_id.index_id, true);
+ field[RDB_DDL_FIELD::INDEX_TYPE]->store(kd.m_index_type, true);
+ field[RDB_DDL_FIELD::KV_FORMAT_VERSION]->store(kd.m_kv_format_version,
+ true);
+ field[RDB_DDL_FIELD::TTL_DURATION]->store(kd.m_ttl_duration, true);
+ field[RDB_DDL_FIELD::INDEX_FLAGS]->store(kd.m_index_flags_bitmap, true);
+
+ std::string cf_name = kd.get_cf()->GetName();
+ field[RDB_DDL_FIELD::CF]->store(cf_name.c_str(), cf_name.size(),
+ system_charset_info);
+ ulonglong auto_incr;
+ if (dict_manager->get_auto_incr_val(tdef->get_autoincr_gl_index_id(),
+ &auto_incr)) {
+ field[RDB_DDL_FIELD::AUTO_INCREMENT]->set_notnull();
+ field[RDB_DDL_FIELD::AUTO_INCREMENT]->store(auto_incr, true);
+ } else {
+ field[RDB_DDL_FIELD::AUTO_INCREMENT]->set_null();
+ }
+
+ ret = my_core::schema_table_store_record(m_thd, m_table);
+ if (ret) return ret;
+ }
+ return HA_EXIT_SUCCESS;
+}
+
+static int rdb_i_s_ddl_fill_table(my_core::THD *const thd,
+ my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+
+ int ret = 0;
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ Rdb_ddl_scanner ddl_arg;
+
+ ddl_arg.m_thd = thd;
+ ddl_arg.m_table = tables->table;
+
+ Rdb_ddl_manager *ddl_manager = rdb_get_ddl_manager();
+ DBUG_ASSERT(ddl_manager != nullptr);
+
+ ret = ddl_manager->scan_for_tables(&ddl_arg);
+
+ DBUG_RETURN(ret);
+}
+
+static int rdb_i_s_ddl_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ DBUG_ASSERT(p != nullptr);
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_ddl_fields_info;
+ schema->fill_table = rdb_i_s_ddl_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_cfoptions_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_cfoptions_fields_info;
+ schema->fill_table = rdb_i_s_cfoptions_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_global_info_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = reinterpret_cast<my_core::ST_SCHEMA_TABLE *>(p);
+
+ schema->fields_info = rdb_i_s_global_info_fields_info;
+ schema->fill_table = rdb_i_s_global_info_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_compact_stats_init(void *p) {
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ schema = reinterpret_cast<my_core::ST_SCHEMA_TABLE *>(p);
+
+ schema->fields_info = rdb_i_s_compact_stats_fields_info;
+ schema->fill_table = rdb_i_s_compact_stats_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/* Given a path to a file return just the filename portion. */
+static std::string rdb_filename_without_path(const std::string &path) {
+ /* Find last slash in path */
+ const size_t pos = path.rfind('/');
+
+ /* None found? Just return the original string */
+ if (pos == std::string::npos) {
+ return std::string(path);
+ }
+
+ /* Return everything after the slash (or backslash) */
+ return path.substr(pos + 1);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_SST_PROPS dynamic table
+ */
+namespace RDB_SST_PROPS_FIELD {
+enum {
+ SST_NAME = 0,
+ COLUMN_FAMILY,
+ DATA_BLOCKS,
+ ENTRIES,
+ RAW_KEY_SIZE,
+ RAW_VALUE_SIZE,
+ DATA_BLOCK_SIZE,
+ INDEX_BLOCK_SIZE,
+ INDEX_PARTITIONS,
+ TOP_LEVEL_INDEX_SIZE,
+ FILTER_BLOCK_SIZE,
+ COMPRESSION_ALGO,
+ CREATION_TIME,
+ FILE_CREATION_TIME,
+ OLDEST_KEY_TIME,
+ FILTER_POLICY,
+ COMPRESSION_OPTIONS,
+};
+} // namespace RDB_SST_PROPS_FIELD
+
+static ST_FIELD_INFO rdb_i_s_sst_props_fields_info[] = {
+ Column("SST_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("COLUMN_FAMILY", SLong(), NOT_NULL),
+ Column("DATA_BLOCKS", SLonglong(), NOT_NULL),
+ Column("ENTRIES", SLonglong(), NOT_NULL),
+ Column("RAW_KEY_SIZE", SLonglong(), NOT_NULL),
+ Column("RAW_VALUE_SIZE", SLonglong(), NOT_NULL),
+ Column("DATA_BLOCK_SIZE", SLonglong(), NOT_NULL),
+ Column("INDEX_BLOCK_SIZE", SLonglong(), NOT_NULL),
+ Column("INDEX_PARTITIONS", SLong(), NOT_NULL),
+ Column("TOP_LEVEL_INDEX_SIZE", SLonglong(), NOT_NULL),
+ Column("FILTER_BLOCK_SIZE", SLonglong(), NOT_NULL),
+ Column("COMPRESSION_ALGO", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("CREATION_TIME", SLonglong(), NOT_NULL),
+ Column("FILE_CREATION_TIME", SLonglong(), NOT_NULL),
+ Column("OLDEST_KEY_TIME", SLonglong(), NOT_NULL),
+ Column("FILTER_POLICY", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("COMPRESSION_OPTIONS", Varchar(NAME_LEN + 1), NOT_NULL),
+ CEnd()};
+
+static int rdb_i_s_sst_props_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+
+ int ret = 0;
+ Field **field = tables->table->field;
+ DBUG_ASSERT(field != nullptr);
+
+ /* Iterate over all the column families */
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ /* Grab the the properties of all the tables in the column family */
+ rocksdb::TablePropertiesCollection table_props_collection;
+ const rocksdb::Status s =
+ rdb->GetPropertiesOfAllTables(cf_handle, &table_props_collection);
+
+ if (!s.ok()) {
+ continue;
+ }
+
+ /* Iterate over all the items in the collection, each of which contains a
+ * name and the actual properties */
+ for (const auto &props : table_props_collection) {
+ /* Add the SST name into the output */
+ const std::string sst_name = rdb_filename_without_path(props.first);
+
+ field[RDB_SST_PROPS_FIELD::SST_NAME]->store(
+ sst_name.data(), sst_name.size(), system_charset_info);
+
+ field[RDB_SST_PROPS_FIELD::COLUMN_FAMILY]->store(
+ props.second->column_family_id, true);
+ field[RDB_SST_PROPS_FIELD::DATA_BLOCKS]->store(
+ props.second->num_data_blocks, true);
+ field[RDB_SST_PROPS_FIELD::ENTRIES]->store(props.second->num_entries,
+ true);
+ field[RDB_SST_PROPS_FIELD::RAW_KEY_SIZE]->store(
+ props.second->raw_key_size, true);
+ field[RDB_SST_PROPS_FIELD::RAW_VALUE_SIZE]->store(
+ props.second->raw_value_size, true);
+ field[RDB_SST_PROPS_FIELD::DATA_BLOCK_SIZE]->store(
+ props.second->data_size, true);
+ field[RDB_SST_PROPS_FIELD::INDEX_BLOCK_SIZE]->store(
+ props.second->index_size, true);
+ field[RDB_SST_PROPS_FIELD::INDEX_PARTITIONS]->store(
+ props.second->index_partitions, true);
+ field[RDB_SST_PROPS_FIELD::TOP_LEVEL_INDEX_SIZE]->store(
+ props.second->top_level_index_size, true);
+ field[RDB_SST_PROPS_FIELD::FILTER_BLOCK_SIZE]->store(
+ props.second->filter_size, true);
+ if (props.second->compression_name.empty()) {
+ field[RDB_SST_PROPS_FIELD::COMPRESSION_ALGO]->set_null();
+ } else {
+ field[RDB_SST_PROPS_FIELD::COMPRESSION_ALGO]->store(
+ props.second->compression_name.c_str(),
+ props.second->compression_name.size(), system_charset_info);
+ }
+ field[RDB_SST_PROPS_FIELD::CREATION_TIME]->store(
+ props.second->creation_time, true);
+ field[RDB_SST_PROPS_FIELD::FILE_CREATION_TIME]->store(
+ props.second->file_creation_time, true);
+ field[RDB_SST_PROPS_FIELD::OLDEST_KEY_TIME]->store(
+ props.second->oldest_key_time, true);
+ if (props.second->filter_policy_name.empty()) {
+ field[RDB_SST_PROPS_FIELD::FILTER_POLICY]->set_null();
+ } else {
+ field[RDB_SST_PROPS_FIELD::FILTER_POLICY]->store(
+ props.second->filter_policy_name.c_str(),
+ props.second->filter_policy_name.size(), system_charset_info);
+ }
+ if (props.second->compression_options.empty()) {
+ field[RDB_SST_PROPS_FIELD::COMPRESSION_OPTIONS]->set_null();
+ } else {
+ field[RDB_SST_PROPS_FIELD::COMPRESSION_OPTIONS]->store(
+ props.second->compression_options.c_str(),
+ props.second->compression_options.size(), system_charset_info);
+ }
+
+ /* Tell MySQL about this row in the virtual table */
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ DBUG_RETURN(ret);
+ }
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/* Initialize the information_schema.rocksdb_sst_props virtual table */
+static int rdb_i_s_sst_props_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_sst_props_fields_info;
+ schema->fill_table = rdb_i_s_sst_props_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_INDEX_FILE_MAP dynamic table
+ */
+namespace RDB_INDEX_FILE_MAP_FIELD {
+enum {
+ COLUMN_FAMILY = 0,
+ INDEX_NUMBER,
+ SST_NAME,
+ NUM_ROWS,
+ DATA_SIZE,
+ ENTRY_DELETES,
+ ENTRY_SINGLEDELETES,
+ ENTRY_MERGES,
+ ENTRY_OTHERS,
+ DISTINCT_KEYS_PREFIX
+};
+} // namespace RDB_INDEX_FILE_MAP_FIELD
+
+static ST_FIELD_INFO rdb_i_s_index_file_map_fields_info[] = {
+ /* The information_schema.rocksdb_index_file_map virtual table has four
+ * fields:
+ * COLUMN_FAMILY => the index's column family contained in the SST file
+ * INDEX_NUMBER => the index id contained in the SST file
+ * SST_NAME => the name of the SST file containing some indexes
+ * NUM_ROWS => the number of entries of this index id in this SST file
+ * DATA_SIZE => the data size stored in this SST file for this index id */
+ Column("COLUMN_FAMILY", SLong(), NOT_NULL),
+ Column("INDEX_NUMBER", SLong(), NOT_NULL),
+ Column("SST_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("NUM_ROWS", SLonglong(), NOT_NULL),
+ Column("DATA_SIZE", SLonglong(), NOT_NULL),
+ Column("ENTRY_DELETES", SLonglong(), NOT_NULL),
+ Column("ENTRY_SINGLEDELETES", SLonglong(), NOT_NULL),
+ Column("ENTRY_MERGES", SLonglong(), NOT_NULL),
+ Column("ENTRY_OTHERS", SLonglong(), NOT_NULL),
+ Column("DISTINCT_KEYS_PREFIX",Varchar(MAX_REF_PARTS * 25), NOT_NULL),
+ CEnd()};
+
+/* Fill the information_schema.rocksdb_index_file_map virtual table */
+static int rdb_i_s_index_file_map_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+
+ int ret = 0;
+ Field **field = tables->table->field;
+ DBUG_ASSERT(field != nullptr);
+
+ /* Iterate over all the column families */
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const Rdb_cf_manager &cf_manager = rdb_get_cf_manager();
+
+ for (const auto &cf_handle : cf_manager.get_all_cf()) {
+ /* Grab the the properties of all the tables in the column family */
+ rocksdb::TablePropertiesCollection table_props_collection;
+ const rocksdb::Status s =
+ rdb->GetPropertiesOfAllTables(cf_handle, &table_props_collection);
+
+ if (!s.ok()) {
+ continue;
+ }
+
+ /* Iterate over all the items in the collection, each of which contains a
+ * name and the actual properties */
+ for (const auto &props : table_props_collection) {
+ /* Add the SST name into the output */
+ const std::string sst_name = rdb_filename_without_path(props.first);
+
+ field[RDB_INDEX_FILE_MAP_FIELD::SST_NAME]->store(
+ sst_name.data(), sst_name.size(), system_charset_info);
+
+ /* Get the __indexstats__ data out of the table property */
+ std::vector<Rdb_index_stats> stats;
+ Rdb_tbl_prop_coll::read_stats_from_tbl_props(props.second, &stats);
+
+ if (stats.empty()) {
+ field[RDB_INDEX_FILE_MAP_FIELD::COLUMN_FAMILY]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::INDEX_NUMBER]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::NUM_ROWS]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::DATA_SIZE]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_DELETES]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_SINGLEDELETES]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_MERGES]->store(-1, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_OTHERS]->store(-1, true);
+ } else {
+ for (const auto &it : stats) {
+ /* Add the index number, the number of rows, and data size to the
+ * output */
+ field[RDB_INDEX_FILE_MAP_FIELD::COLUMN_FAMILY]->store(
+ it.m_gl_index_id.cf_id, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::INDEX_NUMBER]->store(
+ it.m_gl_index_id.index_id, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::NUM_ROWS]->store(it.m_rows, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::DATA_SIZE]->store(it.m_data_size,
+ true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_DELETES]->store(
+ it.m_entry_deletes, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_SINGLEDELETES]->store(
+ it.m_entry_single_deletes, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_MERGES]->store(
+ it.m_entry_merges, true);
+ field[RDB_INDEX_FILE_MAP_FIELD::ENTRY_OTHERS]->store(
+ it.m_entry_others, true);
+
+ std::string distinct_keys_prefix;
+
+ for (size_t i = 0; i < it.m_distinct_keys_per_prefix.size(); i++) {
+ if (i > 0) {
+ distinct_keys_prefix += ",";
+ }
+
+ distinct_keys_prefix +=
+ std::to_string(it.m_distinct_keys_per_prefix[i]);
+ }
+
+ field[RDB_INDEX_FILE_MAP_FIELD::DISTINCT_KEYS_PREFIX]->store(
+ distinct_keys_prefix.data(), distinct_keys_prefix.size(),
+ system_charset_info);
+
+ /* Tell MySQL about this row in the virtual table */
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/* Initialize the information_schema.rocksdb_index_file_map virtual table */
+static int rdb_i_s_index_file_map_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_index_file_map_fields_info;
+ schema->fill_table = rdb_i_s_index_file_map_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_LOCKS dynamic table
+ */
+namespace RDB_LOCKS_FIELD {
+enum { COLUMN_FAMILY_ID = 0, TRANSACTION_ID, KEY, MODE };
+} // namespace RDB_LOCKS_FIELD
+
+static ST_FIELD_INFO rdb_i_s_lock_info_fields_info[] = {
+ Column("COLUMN_FAMILY_ID", SLong(), NOT_NULL),
+ Column("TRANSACTION_ID", SLong(), NOT_NULL),
+ Column("KEY", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("MODE", Varchar(32), NOT_NULL),
+ CEnd()};
+
+/* Fill the information_schema.rocksdb_locks virtual table */
+static int rdb_i_s_lock_info_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ int ret = 0;
+
+ rocksdb::TransactionDB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ /* cf id -> rocksdb::KeyLockInfo */
+ std::unordered_multimap<uint32_t, rocksdb::KeyLockInfo> lock_info =
+ rdb->GetLockStatusData();
+
+ for (const auto &lock : lock_info) {
+ const uint32_t cf_id = lock.first;
+ const auto &key_lock_info = lock.second;
+ const auto key_hexstr = rdb_hexdump(key_lock_info.key.c_str(),
+ key_lock_info.key.length(), FN_REFLEN);
+
+ for (const auto &id : key_lock_info.ids) {
+ tables->table->field[RDB_LOCKS_FIELD::COLUMN_FAMILY_ID]->store(cf_id,
+ true);
+ tables->table->field[RDB_LOCKS_FIELD::TRANSACTION_ID]->store(id, true);
+
+ tables->table->field[RDB_LOCKS_FIELD::KEY]->store(
+ key_hexstr.c_str(), key_hexstr.size(), system_charset_info);
+ tables->table->field[RDB_LOCKS_FIELD::MODE]->store(
+ key_lock_info.exclusive ? "X" : "S", 1, system_charset_info);
+
+ /* Tell MySQL about this row in the virtual table */
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ break;
+ }
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/* Initialize the information_schema.rocksdb_lock_info virtual table */
+static int rdb_i_s_lock_info_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_lock_info_fields_info;
+ schema->fill_table = rdb_i_s_lock_info_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_TRX dynamic table
+ */
+namespace RDB_TRX_FIELD {
+enum {
+ TRANSACTION_ID = 0,
+ STATE,
+ NAME,
+ WRITE_COUNT,
+ LOCK_COUNT,
+ TIMEOUT_SEC,
+ WAITING_KEY,
+ WAITING_COLUMN_FAMILY_ID,
+ IS_REPLICATION,
+ SKIP_TRX_API,
+ READ_ONLY,
+ HAS_DEADLOCK_DETECTION,
+ NUM_ONGOING_BULKLOAD,
+ THREAD_ID,
+ QUERY
+};
+} // namespace RDB_TRX_FIELD
+
+static ST_FIELD_INFO rdb_i_s_trx_info_fields_info[] = {
+ Column("TRANSACTION_ID", SLonglong(), NOT_NULL),
+ Column("STATE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("WRITE_COUNT", SLonglong(), NOT_NULL),
+ Column("LOCK_COUNT", SLonglong(), NOT_NULL),
+ Column("TIMEOUT_SEC", SLong(), NOT_NULL),
+ Column("WAITING_KEY", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("WAITING_COLUMN_FAMILY_ID",SLong(), NOT_NULL),
+ Column("IS_REPLICATION", SLong(), NOT_NULL),
+ Column("SKIP_TRX_API", SLong(), NOT_NULL),
+ Column("READ_ONLY", SLong(), NOT_NULL),
+ Column("HAS_DEADLOCK_DETECTION", SLong(), NOT_NULL),
+ Column("NUM_ONGOING_BULKLOAD", SLong(), NOT_NULL),
+ Column("THREAD_ID", SLong(), NOT_NULL),
+ Column("QUERY", Varchar(NAME_LEN + 1), NOT_NULL),
+ CEnd()};
+
+/* Fill the information_schema.rocksdb_trx virtual table */
+static int rdb_i_s_trx_info_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ int ret = 0;
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const std::vector<Rdb_trx_info> &all_trx_info = rdb_get_all_trx_info();
+
+ for (const auto &info : all_trx_info) {
+ auto name_hexstr =
+ rdb_hexdump(info.name.c_str(), info.name.length(), NAME_LEN);
+ auto key_hexstr = rdb_hexdump(info.waiting_key.c_str(),
+ info.waiting_key.length(), FN_REFLEN);
+
+ tables->table->field[RDB_TRX_FIELD::TRANSACTION_ID]->store(info.trx_id,
+ true);
+ tables->table->field[RDB_TRX_FIELD::STATE]->store(
+ info.state.c_str(), info.state.length(), system_charset_info);
+ tables->table->field[RDB_TRX_FIELD::NAME]->store(
+ name_hexstr.c_str(), name_hexstr.length(), system_charset_info);
+ tables->table->field[RDB_TRX_FIELD::WRITE_COUNT]->store(info.write_count,
+ true);
+ tables->table->field[RDB_TRX_FIELD::LOCK_COUNT]->store(info.lock_count,
+ true);
+ tables->table->field[RDB_TRX_FIELD::TIMEOUT_SEC]->store(info.timeout_sec,
+ false);
+ tables->table->field[RDB_TRX_FIELD::WAITING_KEY]->store(
+ key_hexstr.c_str(), key_hexstr.length(), system_charset_info);
+ tables->table->field[RDB_TRX_FIELD::WAITING_COLUMN_FAMILY_ID]->store(
+ info.waiting_cf_id, true);
+ tables->table->field[RDB_TRX_FIELD::IS_REPLICATION]->store(
+ info.is_replication, false);
+ tables->table->field[RDB_TRX_FIELD::SKIP_TRX_API]->store(info.skip_trx_api,
+ false);
+ tables->table->field[RDB_TRX_FIELD::READ_ONLY]->store(info.read_only,
+ false);
+ tables->table->field[RDB_TRX_FIELD::HAS_DEADLOCK_DETECTION]->store(
+ info.deadlock_detect, false);
+ tables->table->field[RDB_TRX_FIELD::NUM_ONGOING_BULKLOAD]->store(
+ info.num_ongoing_bulk_load, false);
+ tables->table->field[RDB_TRX_FIELD::THREAD_ID]->store(info.thread_id, true);
+ tables->table->field[RDB_TRX_FIELD::QUERY]->store(
+ info.query_str.c_str(), info.query_str.length(), system_charset_info);
+
+ /* Tell MySQL about this row in the virtual table */
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ break;
+ }
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/* Initialize the information_schema.rocksdb_trx_info virtual table */
+static int rdb_i_s_trx_info_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ if (prevent_myrocks_loading)
+ DBUG_RETURN(1);
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_trx_info_fields_info;
+ schema->fill_table = rdb_i_s_trx_info_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Support for INFORMATION_SCHEMA.ROCKSDB_DEADLOCK dynamic table
+ */
+namespace RDB_DEADLOCK_FIELD {
+enum {
+ DEADLOCK_ID = 0,
+ TIMESTAMP,
+ TRANSACTION_ID,
+ CF_NAME,
+ WAITING_KEY,
+ LOCK_TYPE,
+ INDEX_NAME,
+ TABLE_NAME,
+ ROLLED_BACK,
+};
+} // namespace RDB_DEADLOCK_FIELD
+
+static ST_FIELD_INFO rdb_i_s_deadlock_info_fields_info[] = {
+ Column("DEADLOCK_ID", SLonglong(), NOT_NULL),
+ Column("TIMESTAMP", SLonglong(), NOT_NULL),
+ Column("TRANSACTION_ID", SLonglong(), NOT_NULL),
+ Column("CF_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("WAITING_KEY", Varchar(FN_REFLEN + 1), NOT_NULL),
+ Column("LOCK_TYPE", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("INDEX_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("TABLE_NAME", Varchar(NAME_LEN + 1), NOT_NULL),
+ Column("ROLLED_BACK", SLonglong(), NOT_NULL),
+ CEnd()};
+
+/* Fill the information_schema.rocksdb_trx virtual table */
+static int rdb_i_s_deadlock_info_fill_table(
+ my_core::THD *const thd, my_core::TABLE_LIST *const tables,
+ my_core::Item *const cond MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(thd != nullptr);
+ DBUG_ASSERT(tables != nullptr);
+ DBUG_ASSERT(tables->table != nullptr);
+ DBUG_ASSERT(tables->table->field != nullptr);
+
+ static const std::string str_exclusive("EXCLUSIVE");
+ static const std::string str_shared("SHARED");
+
+ int ret = 0;
+ rocksdb::DB *const rdb = rdb_get_rocksdb_db();
+
+ if (!rdb) {
+ DBUG_RETURN(ret);
+ }
+
+ const std::vector<Rdb_deadlock_info> &all_dl_info = rdb_get_deadlock_info();
+
+ ulonglong id = 0;
+ for (const auto &info : all_dl_info) {
+ auto deadlock_time = info.deadlock_time;
+ for (const auto &trx_info : info.path) {
+ tables->table->field[RDB_DEADLOCK_FIELD::DEADLOCK_ID]->store(id, true);
+ tables->table->field[RDB_DEADLOCK_FIELD::TIMESTAMP]->store(deadlock_time,
+ true);
+ tables->table->field[RDB_DEADLOCK_FIELD::TRANSACTION_ID]->store(
+ trx_info.trx_id, true);
+ tables->table->field[RDB_DEADLOCK_FIELD::CF_NAME]->store(
+ trx_info.cf_name.c_str(), trx_info.cf_name.length(),
+ system_charset_info);
+ tables->table->field[RDB_DEADLOCK_FIELD::WAITING_KEY]->store(
+ trx_info.waiting_key.c_str(), trx_info.waiting_key.length(),
+ system_charset_info);
+ if (trx_info.exclusive_lock) {
+ tables->table->field[RDB_DEADLOCK_FIELD::LOCK_TYPE]->store(
+ str_exclusive.c_str(), str_exclusive.length(), system_charset_info);
+ } else {
+ tables->table->field[RDB_DEADLOCK_FIELD::LOCK_TYPE]->store(
+ str_shared.c_str(), str_shared.length(), system_charset_info);
+ }
+ tables->table->field[RDB_DEADLOCK_FIELD::INDEX_NAME]->store(
+ trx_info.index_name.c_str(), trx_info.index_name.length(),
+ system_charset_info);
+ tables->table->field[RDB_DEADLOCK_FIELD::TABLE_NAME]->store(
+ trx_info.table_name.c_str(), trx_info.table_name.length(),
+ system_charset_info);
+ tables->table->field[RDB_DEADLOCK_FIELD::ROLLED_BACK]->store(
+ trx_info.trx_id == info.victim_trx_id, true);
+
+ /* Tell MySQL about this row in the virtual table */
+ ret = static_cast<int>(
+ my_core::schema_table_store_record(thd, tables->table));
+
+ if (ret != 0) {
+ break;
+ }
+ }
+ id++;
+ }
+
+ DBUG_RETURN(ret);
+}
+
+/* Initialize the information_schema.rocksdb_trx_info virtual table */
+static int rdb_i_s_deadlock_info_init(void *const p) {
+ DBUG_ENTER_FUNC();
+
+ DBUG_ASSERT(p != nullptr);
+
+ my_core::ST_SCHEMA_TABLE *schema;
+
+ schema = (my_core::ST_SCHEMA_TABLE *)p;
+
+ schema->fields_info = rdb_i_s_deadlock_info_fields_info;
+ schema->fill_table = rdb_i_s_deadlock_info_fill_table;
+
+ DBUG_RETURN(0);
+}
+
+static int rdb_i_s_deinit(void *p MY_ATTRIBUTE((__unused__))) {
+ DBUG_ENTER_FUNC();
+ DBUG_RETURN(0);
+}
+
+static struct st_mysql_information_schema rdb_i_s_info = {
+ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION};
+
+struct st_maria_plugin rdb_i_s_cfstats = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_CFSTATS",
+ "Facebook",
+ "RocksDB column family stats",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_cfstats_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_dbstats = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_DBSTATS",
+ "Facebook",
+ "RocksDB database stats",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_dbstats_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_perf_context = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_PERF_CONTEXT",
+ "Facebook",
+ "RocksDB perf context stats",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_perf_context_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_perf_context_global = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_PERF_CONTEXT_GLOBAL",
+ "Facebook",
+ "RocksDB perf context stats (all)",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_perf_context_global_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_cfoptions = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_CF_OPTIONS",
+ "Facebook",
+ "RocksDB column family options",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_cfoptions_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_global_info = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_GLOBAL_INFO",
+ "Facebook",
+ "RocksDB global info",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_global_info_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_compact_stats = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_COMPACTION_STATS",
+ "Facebook",
+ "RocksDB compaction stats",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_compact_stats_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_ddl = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_DDL",
+ "Facebook",
+ "RocksDB Data Dictionary",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_ddl_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_sst_props = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_SST_PROPS",
+ "Facebook",
+ "RocksDB SST Properties",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_sst_props_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_index_file_map = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_INDEX_FILE_MAP",
+ "Facebook",
+ "RocksDB index file map",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_index_file_map_init,
+ rdb_i_s_deinit,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_lock_info = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_LOCKS",
+ "Facebook",
+ "RocksDB lock information",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_lock_info_init,
+ nullptr,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_trx_info = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_TRX",
+ "Facebook",
+ "RocksDB transaction information",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_trx_info_init,
+ nullptr,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+
+struct st_maria_plugin rdb_i_s_deadlock_info = {
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &rdb_i_s_info,
+ "ROCKSDB_DEADLOCK",
+ "Facebook",
+ "RocksDB transaction information",
+ PLUGIN_LICENSE_GPL,
+ rdb_i_s_deadlock_info_init,
+ nullptr,
+ 0x0001, /* version number (0.1) */
+ nullptr, /* status variables */
+ nullptr, /* system variables */
+ nullptr, /* config options */
+ MYROCKS_MARIADB_PLUGIN_MATURITY_LEVEL
+};
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_i_s.h b/storage/rocksdb/rdb_i_s.h
new file mode 100644
index 00000000..6001742d
--- /dev/null
+++ b/storage/rocksdb/rdb_i_s.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+namespace myrocks {
+
+/*
+ Declare INFORMATION_SCHEMA (I_S) plugins needed by MyRocks storage engine.
+*/
+
+extern struct st_maria_plugin rdb_i_s_cfstats;
+extern struct st_maria_plugin rdb_i_s_dbstats;
+extern struct st_maria_plugin rdb_i_s_perf_context;
+extern struct st_maria_plugin rdb_i_s_perf_context_global;
+extern struct st_maria_plugin rdb_i_s_cfoptions;
+extern struct st_maria_plugin rdb_i_s_compact_stats;
+extern struct st_maria_plugin rdb_i_s_global_info;
+extern struct st_maria_plugin rdb_i_s_ddl;
+extern struct st_maria_plugin rdb_i_s_sst_props;
+extern struct st_maria_plugin rdb_i_s_index_file_map;
+extern struct st_maria_plugin rdb_i_s_lock_info;
+extern struct st_maria_plugin rdb_i_s_trx_info;
+extern struct st_maria_plugin rdb_i_s_deadlock_info;
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_index_merge.cc b/storage/rocksdb/rdb_index_merge.cc
new file mode 100644
index 00000000..2aac7c7a
--- /dev/null
+++ b/storage/rocksdb/rdb_index_merge.cc
@@ -0,0 +1,630 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+/* This C++ file's header file */
+#include "./rdb_index_merge.h"
+
+/* MySQL header files */
+#include "../sql/sql_class.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./rdb_datadic.h"
+
+namespace myrocks {
+
+Rdb_index_merge::Rdb_index_merge(const char *const tmpfile_path,
+ const ulonglong merge_buf_size,
+ const ulonglong merge_combine_read_size,
+ const ulonglong merge_tmp_file_removal_delay,
+ rocksdb::ColumnFamilyHandle *cf)
+ : m_tmpfile_path(tmpfile_path),
+ m_merge_buf_size(merge_buf_size),
+ m_merge_combine_read_size(merge_combine_read_size),
+ m_merge_tmp_file_removal_delay(merge_tmp_file_removal_delay),
+ m_cf_handle(cf),
+ m_rec_buf_unsorted(nullptr),
+ m_output_buf(nullptr) {}
+
+Rdb_index_merge::~Rdb_index_merge() {
+ /*
+ If merge_tmp_file_removal_delay is set, sleep between calls to chsize.
+
+ This helps mitigate potential trim stalls on flash when large files are
+ being deleted too quickly.
+ */
+ if (m_merge_tmp_file_removal_delay > 0) {
+ uint64 curr_size = m_merge_buf_size * m_merge_file.m_num_sort_buffers;
+ for (uint i = 0; i < m_merge_file.m_num_sort_buffers; i++) {
+ if (my_chsize(m_merge_file.m_fd, curr_size, 0, MYF(MY_WME))) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error truncating file during fast index creation.");
+ }
+
+ my_sleep(m_merge_tmp_file_removal_delay * 1000);
+ // Not aborting on fsync error since the tmp file is not used anymore
+ if (mysql_file_sync(m_merge_file.m_fd, MYF(MY_WME))) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error flushing truncated MyRocks merge buffer.");
+ }
+ curr_size -= m_merge_buf_size;
+ }
+ }
+
+ /*
+ Close file descriptor, we don't need to worry about deletion,
+ mysql handles it.
+ */
+ my_close(m_merge_file.m_fd, MYF(MY_WME));
+}
+
+int Rdb_index_merge::init() {
+ /*
+ Create a temporary merge file on disk to store sorted chunks during
+ inplace index creation.
+ */
+ if (merge_file_create()) {
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ /*
+ Then, allocate buffer to store unsorted records before they are written
+ to disk. They will be written to disk sorted. A sorted tree is used to
+ keep track of the offset of each record within the unsorted buffer.
+ */
+ m_rec_buf_unsorted =
+ std::shared_ptr<merge_buf_info>(new merge_buf_info(m_merge_buf_size));
+
+ /*
+ Allocate output buffer that will contain sorted block that is written to
+ disk.
+ */
+ m_output_buf =
+ std::shared_ptr<merge_buf_info>(new merge_buf_info(m_merge_buf_size));
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Create a merge file in the given location.
+*/
+int Rdb_index_merge::merge_file_create() {
+ DBUG_ASSERT(m_merge_file.m_fd == -1);
+
+ int fd;
+#ifdef MARIAROCKS_NOT_YET // mysql_tmpfile_path use
+ /* If no path set for tmpfile, use mysql_tmpdir by default */
+ if (m_tmpfile_path == nullptr) {
+ fd = mysql_tmpfile("myrocks");
+ } else {
+ fd = mysql_tmpfile_path(m_tmpfile_path, "myrocks");
+ }
+#else
+ fd = mysql_tmpfile("myrocks");
+#endif
+ if (fd < 0) {
+ // NO_LINT_DEBUG
+ sql_print_error("Failed to create temp file during fast index creation.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ m_merge_file.m_fd = fd;
+ m_merge_file.m_num_sort_buffers = 0;
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Add record to offset tree (and unsorted merge buffer) in preparation for
+ writing out to disk in sorted chunks.
+
+ If buffer in memory is full, write the buffer out to disk sorted using the
+ offset tree, and clear the tree. (Happens in merge_buf_write)
+*/
+int Rdb_index_merge::add(const rocksdb::Slice &key, const rocksdb::Slice &val) {
+ /* Adding a record after heap is already created results in error */
+ DBUG_ASSERT(m_merge_min_heap.empty());
+
+ /*
+ Check if sort buffer is going to be out of space, if so write it
+ out to disk in sorted order using offset tree.
+ */
+ const uint total_offset = RDB_MERGE_CHUNK_LEN +
+ m_rec_buf_unsorted->m_curr_offset +
+ RDB_MERGE_KEY_DELIMITER + RDB_MERGE_VAL_DELIMITER +
+ key.size() + val.size();
+ if (total_offset >= m_rec_buf_unsorted->m_total_size) {
+ /*
+ If the offset tree is empty here, that means that the proposed key to
+ add is too large for the buffer.
+ */
+ if (m_offset_tree.empty()) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "Sort buffer size is too small to process merge. "
+ "Please set merge buffer size to a higher value.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ if (merge_buf_write()) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error writing sort buffer to disk.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+ }
+
+ const ulonglong rec_offset = m_rec_buf_unsorted->m_curr_offset;
+
+ /*
+ Store key and value in temporary unsorted in memory buffer pointed to by
+ offset tree.
+ */
+ m_rec_buf_unsorted->store_key_value(key, val);
+
+ /* Find sort order of the new record */
+ auto res =
+ m_offset_tree.emplace(m_rec_buf_unsorted->m_block.get() + rec_offset,
+ m_cf_handle->GetComparator());
+ if (!res.second) {
+ my_printf_error(ER_DUP_ENTRY,
+ "Failed to insert the record: the key already exists",
+ MYF(0));
+ return ER_DUP_ENTRY;
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Sort + write merge buffer chunk out to disk.
+*/
+int Rdb_index_merge::merge_buf_write() {
+ DBUG_ASSERT(m_merge_file.m_fd != -1);
+ DBUG_ASSERT(m_rec_buf_unsorted != nullptr);
+ DBUG_ASSERT(m_output_buf != nullptr);
+ DBUG_ASSERT(!m_offset_tree.empty());
+
+ /* Write actual chunk size to first 8 bytes of the merge buffer */
+ merge_store_uint64(m_output_buf->m_block.get(),
+ m_rec_buf_unsorted->m_curr_offset + RDB_MERGE_CHUNK_LEN);
+ m_output_buf->m_curr_offset += RDB_MERGE_CHUNK_LEN;
+
+ /*
+ Iterate through the offset tree. Should be ordered by the secondary key
+ at this point.
+ */
+ for (const auto &rec : m_offset_tree) {
+ DBUG_ASSERT(m_output_buf->m_curr_offset <= m_merge_buf_size);
+
+ /* Read record from offset (should never fail) */
+ rocksdb::Slice key;
+ rocksdb::Slice val;
+ merge_read_rec(rec.m_block, &key, &val);
+
+ /* Store key and value into sorted output buffer */
+ m_output_buf->store_key_value(key, val);
+ }
+
+ DBUG_ASSERT(m_output_buf->m_curr_offset <= m_output_buf->m_total_size);
+
+ /*
+ Write output buffer to disk.
+
+ Need to position cursor to the chunk it needs to be at on filesystem
+ then write into the respective merge buffer.
+ */
+ if (my_seek(m_merge_file.m_fd,
+ m_merge_file.m_num_sort_buffers * m_merge_buf_size, SEEK_SET,
+ MYF(0)) == MY_FILEPOS_ERROR) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error seeking to location in merge file on disk.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ /*
+ Add a file sync call here to flush the data out. Otherwise, the filesystem
+ cache can flush out all of the files at the same time, causing a write
+ burst.
+ */
+ if (my_write(m_merge_file.m_fd, m_output_buf->m_block.get(),
+ m_output_buf->m_total_size, MYF(MY_WME | MY_NABP)) ||
+ mysql_file_sync(m_merge_file.m_fd, MYF(MY_WME))) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error writing sorted merge buffer to disk.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ /* Increment merge file offset to track number of merge buffers written */
+ m_merge_file.m_num_sort_buffers += 1;
+
+ /* Reset everything for next run */
+ merge_reset();
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Prepare n-way merge of n sorted buffers on disk, using a heap sorted by
+ secondary key records.
+*/
+int Rdb_index_merge::merge_heap_prepare() {
+ DBUG_ASSERT(m_merge_min_heap.empty());
+
+ /*
+ If the offset tree is not empty, there are still some records that need to
+ be written to disk. Write them out now.
+ */
+ if (!m_offset_tree.empty() && merge_buf_write()) {
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ DBUG_ASSERT(m_merge_file.m_num_sort_buffers > 0);
+
+ /*
+ For an n-way merge, we need to read chunks of each merge file
+ simultaneously.
+ */
+ ulonglong chunk_size =
+ m_merge_combine_read_size / m_merge_file.m_num_sort_buffers;
+ if (chunk_size >= m_merge_buf_size) {
+ chunk_size = m_merge_buf_size;
+ }
+
+ /* Allocate buffers for each chunk */
+ for (ulonglong i = 0; i < m_merge_file.m_num_sort_buffers; i++) {
+ const auto entry =
+ std::make_shared<merge_heap_entry>(m_cf_handle->GetComparator());
+
+ /*
+ Read chunk_size bytes from each chunk on disk, and place inside
+ respective chunk buffer.
+ */
+ const size_t total_size =
+ entry->prepare(m_merge_file.m_fd, i * m_merge_buf_size, chunk_size);
+
+ if (total_size == (size_t)-1) {
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ /* Can reach this condition if an index was added on table w/ no rows */
+ if (total_size - RDB_MERGE_CHUNK_LEN == 0) {
+ break;
+ }
+
+ /* Read the first record from each buffer to initially populate the heap */
+ if (entry->read_rec(&entry->m_key, &entry->m_val)) {
+ // NO_LINT_DEBUG
+ sql_print_error("Chunk size is too small to process merge.");
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ m_merge_min_heap.push(std::move(entry));
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Create and/or iterate through keys in the merge heap.
+*/
+int Rdb_index_merge::next(rocksdb::Slice *const key,
+ rocksdb::Slice *const val) {
+ /*
+ If table fits in one sort buffer, we can optimize by writing
+ the sort buffer directly through to the sstfilewriter instead of
+ needing to create tmp files/heap to merge the sort buffers.
+
+ If there are no sort buffer records (alters on empty tables),
+ also exit here.
+ */
+ if (m_merge_file.m_num_sort_buffers == 0) {
+ if (m_offset_tree.empty()) {
+ return -1;
+ }
+
+ const auto rec = m_offset_tree.begin();
+
+ /* Read record from offset */
+ merge_read_rec(rec->m_block, key, val);
+
+ m_offset_tree.erase(rec);
+ return HA_EXIT_SUCCESS;
+ }
+
+ int res;
+
+ /*
+ If heap and heap chunk info are empty, we must be beginning the merge phase
+ of the external sort. Populate the heap with initial values from each
+ disk chunk.
+ */
+ if (m_merge_min_heap.empty()) {
+ if ((res = merge_heap_prepare())) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error during preparation of heap.");
+ return res;
+ }
+
+ /*
+ Return the first top record without popping, as we haven't put this
+ inside the SST file yet.
+ */
+ merge_heap_top(key, val);
+ return HA_EXIT_SUCCESS;
+ }
+
+ DBUG_ASSERT(!m_merge_min_heap.empty());
+ return merge_heap_pop_and_get_next(key, val);
+}
+
+/**
+ Get current top record from the heap.
+*/
+void Rdb_index_merge::merge_heap_top(rocksdb::Slice *const key,
+ rocksdb::Slice *const val) {
+ DBUG_ASSERT(!m_merge_min_heap.empty());
+
+ const std::shared_ptr<merge_heap_entry> &entry = m_merge_min_heap.top();
+ *key = entry->m_key;
+ *val = entry->m_val;
+}
+
+/**
+ Pops the top record, and uses it to read next record from the
+ corresponding sort buffer and push onto the heap.
+
+ Returns -1 when there are no more records in the heap.
+*/
+int Rdb_index_merge::merge_heap_pop_and_get_next(rocksdb::Slice *const key,
+ rocksdb::Slice *const val) {
+ /*
+ Make a new reference to shared ptr so it doesn't get destroyed
+ during pop(). We are going to push this entry back onto the heap.
+ */
+ const std::shared_ptr<merge_heap_entry> entry = m_merge_min_heap.top();
+ m_merge_min_heap.pop();
+
+ /*
+ We are finished w/ current chunk if:
+ current_offset + disk_offset == m_total_size
+
+ Return without adding entry back onto heap.
+ If heap is also empty, we must be finished with merge.
+ */
+ if (entry->m_chunk_info->is_chunk_finished()) {
+ if (m_merge_min_heap.empty()) {
+ return -1;
+ }
+
+ merge_heap_top(key, val);
+ return HA_EXIT_SUCCESS;
+ }
+
+ /*
+ Make sure we haven't reached the end of the chunk.
+ */
+ DBUG_ASSERT(!entry->m_chunk_info->is_chunk_finished());
+
+ /*
+ If merge_read_rec fails, it means the either the chunk was cut off
+ or we've reached the end of the respective chunk.
+ */
+ if (entry->read_rec(&entry->m_key, &entry->m_val)) {
+ if (entry->read_next_chunk_from_disk(m_merge_file.m_fd)) {
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+
+ /* Try reading record again, should never fail. */
+ if (entry->read_rec(&entry->m_key, &entry->m_val)) {
+ return HA_ERR_ROCKSDB_MERGE_FILE_ERR;
+ }
+ }
+
+ /* Push entry back on to the heap w/ updated buffer + offset ptr */
+ m_merge_min_heap.push(std::move(entry));
+
+ /* Return the current top record on heap */
+ merge_heap_top(key, val);
+ return HA_EXIT_SUCCESS;
+}
+
+int Rdb_index_merge::merge_heap_entry::read_next_chunk_from_disk(File fd) {
+ if (m_chunk_info->read_next_chunk_from_disk(fd)) {
+ return HA_EXIT_FAILURE;
+ }
+
+ m_block = m_chunk_info->m_block.get();
+ return HA_EXIT_SUCCESS;
+}
+
+int Rdb_index_merge::merge_buf_info::read_next_chunk_from_disk(File fd) {
+ m_disk_curr_offset += m_curr_offset;
+
+ if (my_seek(fd, m_disk_curr_offset, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error seeking to location in merge file on disk.");
+ return HA_EXIT_FAILURE;
+ }
+
+ /* Overwrite the old block */
+ const size_t bytes_read =
+ my_read(fd, m_block.get(), m_block_len, MYF(MY_WME));
+ if (bytes_read == (size_t)-1) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error reading merge file from disk.");
+ return HA_EXIT_FAILURE;
+ }
+
+ m_curr_offset = 0;
+ return HA_EXIT_SUCCESS;
+}
+
+/**
+ Get records from offset within sort buffer and compare them.
+ Sort by least to greatest.
+*/
+int Rdb_index_merge::merge_record_compare(
+ const uchar *const a_block, const uchar *const b_block,
+ const rocksdb::Comparator *const comparator) {
+ return comparator->Compare(as_slice(a_block), as_slice(b_block));
+}
+
+/**
+ Given an offset in a merge sort buffer, read out the keys + values.
+ After this, block will point to the next record in the buffer.
+**/
+void Rdb_index_merge::merge_read_rec(const uchar *const block,
+ rocksdb::Slice *const key,
+ rocksdb::Slice *const val) {
+ /* Read key at block offset into key slice and the value into value slice*/
+ read_slice(key, block);
+ read_slice(val, block + RDB_MERGE_REC_DELIMITER + key->size());
+}
+
+void Rdb_index_merge::read_slice(rocksdb::Slice *slice,
+ const uchar *block_ptr) {
+ uint64 slice_len;
+ merge_read_uint64(&block_ptr, &slice_len);
+
+ *slice = rocksdb::Slice(reinterpret_cast<const char *>(block_ptr), slice_len);
+}
+
+int Rdb_index_merge::merge_heap_entry::read_rec(rocksdb::Slice *const key,
+ rocksdb::Slice *const val) {
+ const uchar *block_ptr = m_block;
+ const auto orig_offset = m_chunk_info->m_curr_offset;
+ const auto orig_block = m_block;
+
+ /* Read key at block offset into key slice and the value into value slice*/
+ if (read_slice(key, &block_ptr) != 0) {
+ return HA_EXIT_FAILURE;
+ }
+
+ m_chunk_info->m_curr_offset += (uintptr_t)block_ptr - (uintptr_t)m_block;
+ m_block += (uintptr_t)block_ptr - (uintptr_t)m_block;
+
+ if (read_slice(val, &block_ptr) != 0) {
+ m_chunk_info->m_curr_offset = orig_offset;
+ m_block = orig_block;
+ return HA_EXIT_FAILURE;
+ }
+
+ m_chunk_info->m_curr_offset += (uintptr_t)block_ptr - (uintptr_t)m_block;
+ m_block += (uintptr_t)block_ptr - (uintptr_t)m_block;
+
+ return HA_EXIT_SUCCESS;
+}
+
+int Rdb_index_merge::merge_heap_entry::read_slice(rocksdb::Slice *const slice,
+ const uchar **block_ptr) {
+ if (!m_chunk_info->has_space(RDB_MERGE_REC_DELIMITER)) {
+ return HA_EXIT_FAILURE;
+ }
+
+ uint64 slice_len;
+ merge_read_uint64(block_ptr, &slice_len);
+ if (!m_chunk_info->has_space(RDB_MERGE_REC_DELIMITER + slice_len)) {
+ return HA_EXIT_FAILURE;
+ }
+
+ *slice =
+ rocksdb::Slice(reinterpret_cast<const char *>(*block_ptr), slice_len);
+ *block_ptr += slice_len;
+ return HA_EXIT_SUCCESS;
+}
+
+size_t Rdb_index_merge::merge_heap_entry::prepare(File fd, ulonglong f_offset,
+ ulonglong chunk_size) {
+ m_chunk_info = std::make_shared<merge_buf_info>(chunk_size);
+ const size_t res = m_chunk_info->prepare(fd, f_offset);
+ if (res != (size_t)-1) {
+ m_block = m_chunk_info->m_block.get() + RDB_MERGE_CHUNK_LEN;
+ }
+
+ return res;
+}
+
+size_t Rdb_index_merge::merge_buf_info::prepare(File fd, ulonglong f_offset) {
+ m_disk_start_offset = f_offset;
+ m_disk_curr_offset = f_offset;
+
+ /*
+ Need to position cursor to the chunk it needs to be at on filesystem
+ then read 'chunk_size' bytes into the respective chunk buffer.
+ */
+ if (my_seek(fd, f_offset, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error seeking to location in merge file on disk.");
+ return (size_t)-1;
+ }
+
+ const size_t bytes_read =
+ my_read(fd, m_block.get(), m_total_size, MYF(MY_WME));
+ if (bytes_read == (size_t)-1) {
+ // NO_LINT_DEBUG
+ sql_print_error("Error reading merge file from disk.");
+ return (size_t)-1;
+ }
+
+ /*
+ Read the first 8 bytes of each chunk, this gives us the actual
+ size of each chunk.
+ */
+ const uchar *block_ptr = m_block.get();
+ merge_read_uint64(&block_ptr, &m_total_size);
+ m_curr_offset += RDB_MERGE_CHUNK_LEN;
+ return m_total_size;
+}
+
+/* Store key and value w/ their respective delimiters at the given offset */
+void Rdb_index_merge::merge_buf_info::store_key_value(
+ const rocksdb::Slice &key, const rocksdb::Slice &val) {
+ store_slice(key);
+ store_slice(val);
+}
+
+void Rdb_index_merge::merge_buf_info::store_slice(const rocksdb::Slice &slice) {
+ /* Store length delimiter */
+ merge_store_uint64(&m_block[m_curr_offset], slice.size());
+
+ /* Store slice data */
+ memcpy(&m_block[m_curr_offset + RDB_MERGE_REC_DELIMITER], slice.data(),
+ slice.size());
+
+ m_curr_offset += slice.size() + RDB_MERGE_REC_DELIMITER;
+}
+
+void Rdb_index_merge::merge_reset() {
+ /*
+ Either error, or all values in the sort buffer have been written to disk,
+ so we need to clear the offset tree.
+ */
+ m_offset_tree.clear();
+
+ /* Reset sort buffer block */
+ if (m_rec_buf_unsorted && m_rec_buf_unsorted->m_block) {
+ m_rec_buf_unsorted->m_curr_offset = 0;
+ }
+
+ /* Reset output buf */
+ if (m_output_buf && m_output_buf->m_block) {
+ m_output_buf->m_curr_offset = 0;
+ }
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_index_merge.h b/storage/rocksdb/rdb_index_merge.h
new file mode 100644
index 00000000..756b99ca
--- /dev/null
+++ b/storage/rocksdb/rdb_index_merge.h
@@ -0,0 +1,227 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* MySQL header files */
+#include "../sql/log.h"
+#include "./handler.h" /* handler */
+#include "./my_global.h" /* ulonglong */
+
+/* C++ standard header files */
+#include <queue>
+#include <set>
+#include <vector>
+
+/* RocksDB header files */
+#include "rocksdb/db.h"
+
+/* MyRocks header files */
+#include "./rdb_comparator.h"
+
+namespace myrocks {
+
+/*
+ Length of delimiters used during inplace index creation.
+*/
+#define RDB_MERGE_CHUNK_LEN sizeof(size_t)
+#define RDB_MERGE_REC_DELIMITER sizeof(size_t)
+#define RDB_MERGE_KEY_DELIMITER RDB_MERGE_REC_DELIMITER
+#define RDB_MERGE_VAL_DELIMITER RDB_MERGE_REC_DELIMITER
+
+class Rdb_key_def;
+class Rdb_tbl_def;
+
+class Rdb_index_merge {
+ Rdb_index_merge(const Rdb_index_merge &p) = delete;
+ Rdb_index_merge &operator=(const Rdb_index_merge &p) = delete;
+
+ public:
+ /* Information about temporary files used in external merge sort */
+ struct merge_file_info {
+ File m_fd = -1; /* file descriptor */
+ ulong m_num_sort_buffers = 0; /* number of sort buffers in temp file */
+ };
+
+ /* Buffer for sorting in main memory. */
+ struct merge_buf_info {
+ /* heap memory allocated for main memory sort/merge */
+ std::unique_ptr<uchar[]> m_block;
+ const ulonglong
+ m_block_len; /* amount of data bytes allocated for block above */
+ ulonglong m_curr_offset; /* offset of the record pointer for the block */
+ ulonglong m_disk_start_offset; /* where the chunk starts on disk */
+ ulonglong m_disk_curr_offset; /* current offset on disk */
+ ulonglong m_total_size; /* total # of data bytes in chunk */
+
+ void store_key_value(const rocksdb::Slice &key, const rocksdb::Slice &val)
+ MY_ATTRIBUTE((__nonnull__));
+
+ void store_slice(const rocksdb::Slice &slice) MY_ATTRIBUTE((__nonnull__));
+
+ size_t prepare(File fd, ulonglong f_offset) MY_ATTRIBUTE((__nonnull__));
+
+ int read_next_chunk_from_disk(File fd)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ inline bool is_chunk_finished() const {
+ return m_curr_offset + m_disk_curr_offset - m_disk_start_offset ==
+ m_total_size;
+ }
+
+ inline bool has_space(uint64 needed) const {
+ return m_curr_offset + needed <= m_block_len;
+ }
+
+ explicit merge_buf_info(const ulonglong merge_block_size)
+ : m_block(nullptr),
+ m_block_len(merge_block_size),
+ m_curr_offset(0),
+ m_disk_start_offset(0),
+ m_disk_curr_offset(0),
+ m_total_size(merge_block_size) {
+ /* Will throw an exception if it runs out of memory here */
+ m_block = std::unique_ptr<uchar[]>(new uchar[merge_block_size]);
+
+ /* Initialize entire buffer to 0 to avoid valgrind errors */
+ memset(m_block.get(), 0, merge_block_size);
+ }
+ };
+
+ /* Represents an entry in the heap during merge phase of external sort */
+ struct merge_heap_entry {
+ std::shared_ptr<merge_buf_info> m_chunk_info; /* pointer to buffer info */
+ uchar *m_block; /* pointer to heap memory where record is stored */
+ const rocksdb::Comparator *const m_comparator;
+ rocksdb::Slice m_key; /* current key pointed to by block ptr */
+ rocksdb::Slice m_val;
+
+ size_t prepare(File fd, ulonglong f_offset, ulonglong chunk_size)
+ MY_ATTRIBUTE((__nonnull__));
+
+ int read_next_chunk_from_disk(File fd)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int read_rec(rocksdb::Slice *const key, rocksdb::Slice *const val)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int read_slice(rocksdb::Slice *const slice, const uchar **block_ptr)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ explicit merge_heap_entry(const rocksdb::Comparator *const comparator)
+ : m_chunk_info(nullptr), m_block(nullptr), m_comparator(comparator) {}
+ };
+
+ struct merge_heap_comparator {
+ bool operator()(const std::shared_ptr<merge_heap_entry> &lhs,
+ const std::shared_ptr<merge_heap_entry> &rhs) {
+ return lhs->m_comparator->Compare(rhs->m_key, lhs->m_key) < 0;
+ }
+ };
+
+ /* Represents a record in unsorted buffer */
+ struct merge_record {
+ uchar *m_block; /* points to offset of key in sort buffer */
+ const rocksdb::Comparator *const m_comparator;
+
+ bool operator<(const merge_record &record) const {
+ return merge_record_compare(this->m_block, record.m_block, m_comparator) <
+ 0;
+ }
+
+ merge_record(uchar *const block,
+ const rocksdb::Comparator *const comparator)
+ : m_block(block), m_comparator(comparator) {}
+ };
+
+ private:
+ const char *m_tmpfile_path;
+ const ulonglong m_merge_buf_size;
+ const ulonglong m_merge_combine_read_size;
+ const ulonglong m_merge_tmp_file_removal_delay;
+ rocksdb::ColumnFamilyHandle *m_cf_handle;
+ struct merge_file_info m_merge_file;
+ std::shared_ptr<merge_buf_info> m_rec_buf_unsorted;
+ std::shared_ptr<merge_buf_info> m_output_buf;
+ std::set<merge_record> m_offset_tree;
+ std::priority_queue<std::shared_ptr<merge_heap_entry>,
+ std::vector<std::shared_ptr<merge_heap_entry>>,
+ merge_heap_comparator>
+ m_merge_min_heap;
+
+ static inline void merge_store_uint64(uchar *const dst, uint64 n) {
+ memcpy(dst, &n, sizeof(n));
+ }
+
+ static inline void merge_read_uint64(const uchar **buf_ptr,
+ uint64 *const dst) {
+ DBUG_ASSERT(buf_ptr != nullptr);
+ memcpy(dst, *buf_ptr, sizeof(uint64));
+ *buf_ptr += sizeof(uint64);
+ }
+
+ static inline rocksdb::Slice as_slice(const uchar *block) {
+ uint64 len;
+ merge_read_uint64(&block, &len);
+
+ return rocksdb::Slice(reinterpret_cast<const char *>(block), len);
+ }
+
+ static int merge_record_compare(const uchar *a_block, const uchar *b_block,
+ const rocksdb::Comparator *const comparator)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ void merge_read_rec(const uchar *const block, rocksdb::Slice *const key,
+ rocksdb::Slice *const val) MY_ATTRIBUTE((__nonnull__));
+
+ void read_slice(rocksdb::Slice *slice, const uchar *block_ptr)
+ MY_ATTRIBUTE((__nonnull__));
+
+ public:
+ Rdb_index_merge(const char *const tmpfile_path,
+ const ulonglong merge_buf_size,
+ const ulonglong merge_combine_read_size,
+ const ulonglong merge_tmp_file_removal_delay,
+ rocksdb::ColumnFamilyHandle *cf);
+ ~Rdb_index_merge();
+
+ int init() MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int merge_file_create() MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int add(const rocksdb::Slice &key, const rocksdb::Slice &val)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int merge_buf_write() MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int next(rocksdb::Slice *const key, rocksdb::Slice *const val)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ int merge_heap_prepare() MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ void merge_heap_top(rocksdb::Slice *key, rocksdb::Slice *val)
+ MY_ATTRIBUTE((__nonnull__));
+
+ int merge_heap_pop_and_get_next(rocksdb::Slice *const key,
+ rocksdb::Slice *const val)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+ void merge_reset();
+
+ rocksdb::ColumnFamilyHandle *get_cf() const { return m_cf_handle; }
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_io_watchdog.cc b/storage/rocksdb/rdb_io_watchdog.cc
new file mode 100644
index 00000000..07834118
--- /dev/null
+++ b/storage/rocksdb/rdb_io_watchdog.cc
@@ -0,0 +1,240 @@
+/*
+ Copyright (c) 2017, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/* This C++ file's header */
+#include "./rdb_io_watchdog.h"
+
+/* C++ standard header files */
+#include <string>
+#include <vector>
+
+/* Rdb_io_watchdog doesn't work on Windows [yet] */
+#ifdef HAVE_TIMER_DELETE
+
+namespace myrocks {
+
+void Rdb_io_watchdog::expire_io_callback(union sigval timer_data) {
+ DBUG_ASSERT(timer_data.sival_ptr != nullptr);
+
+ // The treatment of any pending signal generated by the deleted timer is
+ // unspecified. Therefore we still need to handle the rare case where we
+ // finished the I/O operation right before the timer was deleted and callback
+ // was in flight.
+ if (!m_io_in_progress.load()) {
+ return;
+ }
+
+ // At this point we know that I/O has been stuck in `write()` for more than
+ // `m_write_timeout` seconds. We'll log a message and shut down the service.
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "MyRocks has detected a combination of I/O requests which "
+ "have cumulatively been blocking for more than %u seconds. "
+ "Shutting the service down.",
+ m_write_timeout);
+
+ abort();
+}
+
+void Rdb_io_watchdog::io_check_callback(union sigval timer_data) {
+ RDB_MUTEX_LOCK_CHECK(m_reset_mutex);
+
+ DBUG_ASSERT(timer_data.sival_ptr != nullptr);
+
+ struct sigevent e;
+
+ e.sigev_notify = SIGEV_THREAD;
+ e.sigev_notify_function = &Rdb_io_watchdog::expire_io_callback_wrapper;
+ e.sigev_value.sival_ptr = this;
+ e.sigev_notify_attributes = nullptr;
+
+ int ret = timer_create(CLOCK_MONOTONIC, &e, &m_io_check_watchdog_timer);
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Creating a watchdog I/O timer failed with %d.", errno);
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ return;
+ }
+
+ struct itimerspec timer_spec;
+ memset(&timer_spec, 0, sizeof(timer_spec));
+
+ // One time execution only for the watchdog. No interval.
+ timer_spec.it_value.tv_sec = m_write_timeout;
+
+ ret = timer_settime(m_io_check_watchdog_timer, 0, &timer_spec, nullptr);
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Setting time for a watchdog I/O timer failed with %d.",
+ errno);
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ return;
+ }
+
+ m_io_in_progress.store(true);
+
+ // Verify the write access to all directories we care about.
+ for (const std::string &directory : m_dirs_to_check) {
+ ret = check_write_access(directory);
+
+ // We'll log a warning and attept to continue to see if the problem happens
+ // in other cases as well.
+ if (unlikely(ret != HA_EXIT_SUCCESS)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Unable to verify write access to %s (error code %d).",
+ directory.c_str(), ret);
+ }
+ }
+
+ m_io_in_progress.store(false);
+
+ // Clean up the watchdog timer.
+ ret = timer_delete(m_io_check_watchdog_timer);
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Deleting the watchdog I/O timer failed with %d.", errno);
+ }
+
+ m_io_check_watchdog_timer = nullptr;
+
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+}
+
+int Rdb_io_watchdog::check_write_access(const std::string &dirname) const {
+ DBUG_ASSERT(!dirname.empty());
+ DBUG_ASSERT(m_buf != nullptr);
+
+ const std::string fname = dirname + FN_DIRSEP + RDB_IO_DUMMY_FILE_NAME;
+
+ // O_DIRECT is a key flag here to make sure that we'll bypass the kernel's
+ // buffer cache.
+ int fd = open(fname.c_str(), O_WRONLY | O_DIRECT | O_CREAT | O_SYNC,
+ S_IRWXU | S_IWUSR);
+
+ if (unlikely(fd == -1)) {
+ return fd;
+ }
+
+ int ret = write(fd, m_buf, RDB_IO_WRITE_BUFFER_SIZE);
+
+ if (unlikely(ret != RDB_IO_WRITE_BUFFER_SIZE)) {
+ return ret;
+ }
+
+ ret = close(fd);
+
+ if (unlikely(ret)) {
+ return ret;
+ }
+
+ ret = unlink(fname.c_str());
+
+ if (unlikely(ret)) {
+ return ret;
+ }
+
+ return HA_EXIT_SUCCESS;
+}
+
+int Rdb_io_watchdog::reset_timeout(const uint32_t write_timeout) {
+ // This function will be called either from a thread initializing MyRocks
+ // engine or handling system variable changes. We need to account for the
+ // possibility of I/O callback executing at the same time. If that happens
+ // then we'll wait for it to finish.
+ RDB_MUTEX_LOCK_CHECK(m_reset_mutex);
+
+ struct sigevent e;
+
+ // In all the cases all the active timers needs to be stopped.
+ int ret = stop_timers();
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Stopping I/O timers failed with %d.", errno);
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ return ret;
+ }
+
+ m_write_timeout = write_timeout;
+ m_io_in_progress.store(false);
+
+ // Zero means that the I/O timer will be disabled. Therefore there's nothing
+ // for us to do here.
+ if (!write_timeout) {
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ return HA_EXIT_SUCCESS;
+ }
+
+ free(m_buf);
+
+ ret = posix_memalign(reinterpret_cast<void **>(&m_buf),
+ RDB_IO_WRITE_BUFFER_SIZE, RDB_IO_WRITE_BUFFER_SIZE);
+
+ if (unlikely(ret)) {
+ m_buf = nullptr;
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ // NB! The value of errno is not set.
+ return ret;
+ }
+
+ DBUG_ASSERT(m_buf != nullptr);
+ memset(m_buf, 0, RDB_IO_WRITE_BUFFER_SIZE);
+
+ // Common case gets handled here - we'll create a timer with a specific
+ // interval to check a set of directories for write access.
+ DBUG_ASSERT(m_dirs_to_check.size() > 0);
+
+ e.sigev_notify = SIGEV_THREAD;
+ e.sigev_notify_function = &Rdb_io_watchdog::io_check_callback_wrapper;
+ e.sigev_value.sival_ptr = this;
+ e.sigev_notify_attributes = nullptr;
+
+ ret = timer_create(CLOCK_MONOTONIC, &e, &m_io_check_timer);
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Creating a I/O timer failed with %d.", errno);
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+ return ret;
+ }
+
+ struct itimerspec timer_spec;
+ memset(&timer_spec, 0, sizeof(timer_spec));
+
+ // I/O timer will need to execute on a certain interval.
+ timer_spec.it_value.tv_sec = m_write_timeout;
+ timer_spec.it_interval.tv_sec = m_write_timeout;
+
+ ret = timer_settime(m_io_check_timer, 0, &timer_spec, nullptr);
+
+ if (unlikely(ret)) {
+ // NO_LINT_DEBUG
+ sql_print_warning("Setting time for a watchdog I/O timer failed with %d.",
+ errno);
+ }
+
+ RDB_MUTEX_UNLOCK_CHECK(m_reset_mutex);
+
+ return HA_EXIT_SUCCESS;
+}
+
+} // namespace myrocks
+
+#endif
+
diff --git a/storage/rocksdb/rdb_io_watchdog.h b/storage/rocksdb/rdb_io_watchdog.h
new file mode 100644
index 00000000..8ee5b1f6
--- /dev/null
+++ b/storage/rocksdb/rdb_io_watchdog.h
@@ -0,0 +1,119 @@
+/*
+ Copyright (c) 2017, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* C++ standard header files */
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <atomic>
+#include <string>
+#include <vector>
+
+/* MySQL header files */
+#include "./my_global.h"
+#include "./my_stacktrace.h"
+
+/* MyRocks header files */
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+// Rdb_io_watchdog does not support Windows ATM.
+#ifdef HAVE_TIMER_DELETE
+
+class Rdb_io_watchdog {
+ const int RDB_IO_WRITE_BUFFER_SIZE = 4096;
+ const char *const RDB_IO_DUMMY_FILE_NAME = "myrocks_io_watchdog_write_file";
+
+ private:
+ timer_t m_io_check_timer, m_io_check_watchdog_timer;
+ std::atomic<bool> m_io_in_progress;
+ std::vector<std::string> m_dirs_to_check;
+ uint32_t m_write_timeout;
+ mysql_mutex_t m_reset_mutex;
+ char *m_buf;
+
+ int check_write_access(const std::string &dirname) const;
+ void io_check_callback(union sigval timer_data);
+ void expire_io_callback(union sigval timer_data);
+
+ int stop_timers() {
+ int ret = 0;
+
+ if (m_io_check_watchdog_timer) {
+ ret = timer_delete(m_io_check_watchdog_timer);
+
+ if (!ret) {
+ m_io_check_watchdog_timer = nullptr;
+ }
+ }
+
+ if (m_io_check_timer && !ret) {
+ ret = timer_delete(m_io_check_timer);
+
+ if (!ret) {
+ m_io_check_timer = nullptr;
+ }
+ }
+
+ return ret;
+ }
+
+ static void io_check_callback_wrapper(union sigval timer_data) {
+ Rdb_io_watchdog *io_watchdog =
+ static_cast<Rdb_io_watchdog *>(timer_data.sival_ptr);
+ DBUG_ASSERT(io_watchdog != nullptr);
+
+ io_watchdog->io_check_callback(timer_data);
+ }
+
+ static void expire_io_callback_wrapper(union sigval timer_data) {
+ Rdb_io_watchdog *io_watchdog =
+ static_cast<Rdb_io_watchdog *>(timer_data.sival_ptr);
+ DBUG_ASSERT(io_watchdog != nullptr);
+
+ io_watchdog->expire_io_callback(timer_data);
+ }
+
+ public:
+ explicit Rdb_io_watchdog(std::vector<std::string> &&directories)
+ : m_io_check_timer(nullptr),
+ m_io_check_watchdog_timer(nullptr),
+ m_io_in_progress(false),
+ m_dirs_to_check(std::move(directories)),
+ m_buf(nullptr) {
+ DBUG_ASSERT(m_dirs_to_check.size() > 0);
+ mysql_mutex_init(0, &m_reset_mutex, MY_MUTEX_INIT_FAST);
+ }
+
+ ~Rdb_io_watchdog() {
+ // We're shutting down. Ignore errors possibly coming from timer deletion.
+ static_cast<void>(stop_timers());
+ mysql_mutex_destroy(&m_reset_mutex);
+ free(m_buf);
+ }
+
+ int reset_timeout(const uint32_t write_timeout);
+
+ Rdb_io_watchdog(const Rdb_io_watchdog &) = delete;
+ Rdb_io_watchdog &operator=(const Rdb_io_watchdog &) = delete;
+};
+
+#endif
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_mariadb_port.h b/storage/rocksdb/rdb_mariadb_port.h
new file mode 100644
index 00000000..62767490
--- /dev/null
+++ b/storage/rocksdb/rdb_mariadb_port.h
@@ -0,0 +1,55 @@
+/*
+ A temporary header to resolve WebScaleSQL vs MariaDB differences
+ when porting MyRocks to MariaDB.
+*/
+#ifndef RDB_MARIADB_PORT_H
+#define RDB_MARIADB_PORT_H
+
+#include "my_global.h" /* ulonglong */
+#include "atomic_stat.h"
+
+// These are for split_into_vector:
+#include <vector>
+#include <string>
+
+/* The following is copied from storage/innobase/univ.i: */
+#ifndef MY_ATTRIBUTE
+#if defined(__GNUC__)
+# define MY_ATTRIBUTE(A) __attribute__(A)
+#else
+# define MY_ATTRIBUTE(A)
+#endif
+#endif
+
+/* Struct used for IO performance counters, shared among multiple threads */
+struct my_io_perf_atomic_struct {
+ atomic_stat<ulonglong> bytes;
+ atomic_stat<ulonglong> requests;
+ atomic_stat<ulonglong> svc_time; /*!< time to do read or write operation */
+ atomic_stat<ulonglong> svc_time_max;
+ atomic_stat<ulonglong> wait_time; /*!< total time in the request array */
+ atomic_stat<ulonglong> wait_time_max;
+ atomic_stat<ulonglong> slow_ios; /*!< requests that take too long */
+};
+typedef struct my_io_perf_atomic_struct my_io_perf_atomic_t;
+
+////////////////////////////////////////////////////////////////////////////
+
+/*
+ Temporary stand-in for
+ fae59683dc116be2cc78b0b30d61c84659c33bd3
+ Print stack traces before committing suicide
+
+*/
+#define abort_with_stack_traces() { abort(); }
+
+////////////////////////////////////////////////////////////////////////////
+typedef struct my_io_perf_struct my_io_perf_t;
+
+std::vector<std::string> split_into_vector(const std::string& input,
+ char delimiter);
+
+void
+mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file);
+
+#endif
diff --git a/storage/rocksdb/rdb_mariadb_server_port.cc b/storage/rocksdb/rdb_mariadb_server_port.cc
new file mode 100644
index 00000000..e74c792c
--- /dev/null
+++ b/storage/rocksdb/rdb_mariadb_server_port.cc
@@ -0,0 +1,122 @@
+#include <my_global.h>
+
+
+/* MySQL includes */
+#include "./debug_sync.h"
+#include "./my_bit.h"
+#include "./my_stacktrace.h"
+#include "./sql_table.h"
+#include "./log.h"
+#include <mysys_err.h>
+#include <mysql/psi/mysql_table.h>
+//#include <mysql/thread_pool_priv.h>
+
+#include <string>
+
+/* MyRocks includes */
+#include "./rdb_threads.h"
+
+#include "rdb_mariadb_server_port.h"
+
+void warn_about_bad_patterns(const Regex_list_handler* regex_list_handler,
+ const char *name)
+{
+ // There was some invalid regular expression data in the patterns supplied
+
+ // NO_LINT_DEBUG
+ sql_print_warning("Invalid pattern in %s: %s", name,
+ regex_list_handler->bad_pattern().c_str());
+}
+
+
+/*
+ Set the patterns string. If there are invalid regex patterns they will
+ be stored in m_bad_patterns and the result will be false, otherwise the
+ result will be true.
+*/
+bool Regex_list_handler::set_patterns(const std::string& pattern_str)
+{
+ bool pattern_valid= true;
+
+ // Create a normalized version of the pattern string with all delimiters
+ // replaced by the '|' character
+ std::string norm_pattern= pattern_str;
+ std::replace(norm_pattern.begin(), norm_pattern.end(), m_delimiter, '|');
+
+ // Make sure no one else is accessing the list while we are changing it.
+ mysql_rwlock_wrlock(&m_rwlock);
+
+ // Clear out any old error information
+ m_bad_pattern_str.clear();
+
+ try
+ {
+ // Replace all delimiters with the '|' operator and create the regex
+ // Note that this means the delimiter can not be part of a regular
+ // expression. This is currently not a problem as we are using the comma
+ // character as a delimiter and commas are not valid in table names.
+ const std::regex* pattern= new std::regex(norm_pattern);
+
+ // Free any existing regex information and setup the new one
+ delete m_pattern;
+ m_pattern= pattern;
+ }
+ catch (const std::regex_error&)
+ {
+ // This pattern is invalid.
+ pattern_valid= false;
+
+ // Put the bad pattern into a member variable so it can be retrieved later.
+ m_bad_pattern_str= pattern_str;
+ }
+
+ // Release the lock
+ mysql_rwlock_unlock(&m_rwlock);
+
+ return pattern_valid;
+}
+
+bool Regex_list_handler::matches(const std::string& str) const
+{
+ DBUG_ASSERT(m_pattern != nullptr);
+
+ // Make sure no one else changes the list while we are accessing it.
+ mysql_rwlock_rdlock(&m_rwlock);
+
+ // See if the table name matches the regex we have created
+ bool found= std::regex_match(str, *m_pattern);
+
+ // Release the lock
+ mysql_rwlock_unlock(&m_rwlock);
+
+ return found;
+}
+
+// Split a string based on a delimiter. Two delimiters in a row will not add
+// an empty string in the set.
+std::vector<std::string> split_into_vector(const std::string& input,
+ char delimiter)
+{
+ size_t pos;
+ size_t start = 0;
+ std::vector<std::string> elems;
+
+ // Find next delimiter
+ while ((pos = input.find(delimiter, start)) != std::string::npos)
+ {
+ // If there is any data since the last delimiter add it to the list
+ if (pos > start)
+ elems.push_back(input.substr(start, pos - start));
+
+ // Set our start position to the character after the delimiter
+ start = pos + 1;
+ }
+
+ // Add a possible string since the last delimiter
+ if (input.length() > start)
+ elems.push_back(input.substr(start));
+
+ // Return the resulting list back to the caller
+ return elems;
+}
+
diff --git a/storage/rocksdb/rdb_mariadb_server_port.h b/storage/rocksdb/rdb_mariadb_server_port.h
new file mode 100644
index 00000000..fe963446
--- /dev/null
+++ b/storage/rocksdb/rdb_mariadb_server_port.h
@@ -0,0 +1,76 @@
+/*
+ A temporary header to resolve WebScaleSQL vs MariaDB differences
+ when porting MyRocks to MariaDB.
+*/
+#ifndef RDB_MARIADB_SERVER_PORT_H
+#define RDB_MARIADB_SERVER_PORT_H
+
+#include "my_global.h" /* ulonglong */
+#include "atomic_stat.h"
+#include "my_pthread.h"
+#include <mysql/psi/mysql_table.h>
+#include <mysql/psi/mysql_thread.h>
+
+/*
+ Code that is on SQL layer in facebook/mysql-5.6,
+ but is part of the storage engine in MariaRocks
+*/
+#include <regex>
+
+class Regex_list_handler
+{
+ private:
+#if defined(HAVE_PSI_INTERFACE)
+ const PSI_rwlock_key& m_key;
+#endif
+
+ char m_delimiter;
+ std::string m_bad_pattern_str;
+ const std::regex* m_pattern;
+
+ mutable mysql_rwlock_t m_rwlock;
+
+ Regex_list_handler(const Regex_list_handler& other)= delete;
+ Regex_list_handler& operator=(const Regex_list_handler& other)= delete;
+
+ public:
+#if defined(HAVE_PSI_INTERFACE)
+ Regex_list_handler(const PSI_rwlock_key& key,
+ char delimiter= ',') :
+ m_key(key),
+#else
+ Regex_list_handler(char delimiter= ',') :
+#endif
+ m_delimiter(delimiter),
+ m_bad_pattern_str(""),
+ m_pattern(nullptr)
+ {
+ mysql_rwlock_init(key, &m_rwlock);
+ }
+
+ ~Regex_list_handler()
+ {
+ mysql_rwlock_destroy(&m_rwlock);
+ delete m_pattern;
+ }
+
+ // Set the list of patterns
+ bool set_patterns(const std::string& patterns);
+
+ // See if a string matches at least one pattern
+ bool matches(const std::string& str) const;
+
+ // See the list of bad patterns
+ const std::string& bad_pattern() const
+ {
+ return m_bad_pattern_str;
+ }
+};
+
+void warn_about_bad_patterns(const Regex_list_handler* regex_list_handler,
+ const char *name);
+
+void print_keydup_error(TABLE *table, KEY *key, myf errflag,
+ const THD *thd, const char *org_table_name=NULL);
+
+#endif
diff --git a/storage/rocksdb/rdb_mutex_wrapper.cc b/storage/rocksdb/rdb_mutex_wrapper.cc
new file mode 100644
index 00000000..5e0591fe
--- /dev/null
+++ b/storage/rocksdb/rdb_mutex_wrapper.cc
@@ -0,0 +1,214 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+/* This C++ file's header file */
+#include "./rdb_mutex_wrapper.h"
+
+/* The following are for THD_ENTER_COND: */
+#define MYSQL_SERVER 1
+#include "sql_priv.h"
+#include "my_decimal.h"
+#include "sql_class.h"
+//psergey-merge-todo: does MariaDB have/need: #include "../sql/replication.h"
+
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./rdb_utils.h"
+
+
+using namespace rocksdb;
+
+namespace myrocks {
+
+static PSI_stage_info stage_waiting_on_row_lock2 = {0, "Waiting for row lock",
+ 0};
+
+static const int64_t ONE_SECOND_IN_MICROSECS = 1000 * 1000;
+// A timeout as long as one full non-leap year worth of microseconds is as
+// good as infinite timeout.
+static const int64_t ONE_YEAR_IN_MICROSECS =
+ ONE_SECOND_IN_MICROSECS * 60 * 60 * 24 * 365;
+
+Rdb_cond_var::Rdb_cond_var() { mysql_cond_init(0, &m_cond, nullptr); }
+
+Rdb_cond_var::~Rdb_cond_var() { mysql_cond_destroy(&m_cond); }
+
+Status Rdb_cond_var::Wait(const std::shared_ptr<TransactionDBMutex> mutex_arg) {
+ return WaitFor(mutex_arg, ONE_YEAR_IN_MICROSECS);
+}
+
+/*
+ @brief
+ Wait on condition variable. The caller must make sure that we own
+ *mutex_ptr. The mutex is released and re-acquired by the wait function.
+
+ @param
+ timeout_micros Timeout in microseconds. Negative value means no timeout.
+
+ @return
+ Status::OK() - Wait successfull
+ Status::TimedOut() - Timed out or wait killed (the caller can check
+ thd_killed() to determine which occurred)
+*/
+
+Status Rdb_cond_var::WaitFor(
+ const std::shared_ptr<TransactionDBMutex> mutex_arg,
+ int64_t timeout_micros) {
+ auto *mutex_obj = reinterpret_cast<Rdb_mutex *>(mutex_arg.get());
+ DBUG_ASSERT(mutex_obj != nullptr);
+
+ mysql_mutex_t *const mutex_ptr = &mutex_obj->m_mutex;
+
+ int res = 0;
+ struct timespec wait_timeout;
+
+ if (timeout_micros < 0) timeout_micros = ONE_YEAR_IN_MICROSECS;
+ set_timespec_nsec(wait_timeout, timeout_micros * 1000);
+
+#ifndef STANDALONE_UNITTEST
+ PSI_stage_info old_stage;
+ mysql_mutex_assert_owner(mutex_ptr);
+
+ if (current_thd && mutex_obj->m_old_stage_info.count(current_thd) == 0) {
+ THD_ENTER_COND(current_thd, &m_cond, mutex_ptr, &stage_waiting_on_row_lock2,
+ &old_stage);
+ /*
+ After the mysql_cond_timedwait we need make this call
+
+ THD_EXIT_COND(thd, &old_stage);
+
+ to inform the SQL layer that KILLable wait has ended. However,
+ that will cause mutex to be released. Defer the release until the mutex
+ that is unlocked by RocksDB's Pessimistic Transactions system.
+ */
+ mutex_obj->set_unlock_action(&old_stage);
+ }
+
+#endif
+ bool killed = false;
+
+ do {
+ res = mysql_cond_timedwait(&m_cond, mutex_ptr, &wait_timeout);
+
+#ifndef STANDALONE_UNITTEST
+ if (current_thd) killed = thd_killed(current_thd);
+#endif
+ } while (!killed && res == EINTR);
+
+ if (res || killed) {
+ return Status::TimedOut();
+ } else {
+ return Status::OK();
+ }
+}
+
+/*
+
+ @note
+ This function may be called while not holding the mutex that is used to wait
+ on the condition variable.
+
+ The manual page says ( http://linux.die.net/man/3/pthread_cond_signal):
+
+ The pthread_cond_broadcast() or pthread_cond_signal() functions may be called
+ by a thread whether or not it currently owns the mutex that threads calling
+ pthread_cond_wait() or pthread_cond_timedwait() have associated with the
+ condition variable during their waits; however, IF PREDICTABLE SCHEDULING
+ BEHAVIOR IS REQUIRED, THEN THAT MUTEX SHALL BE LOCKED by the thread calling
+ pthread_cond_broadcast() or pthread_cond_signal().
+
+ What's "predicate scheduling" and do we need it? The explanation is here:
+
+ https://groups.google.com/forum/?hl=ky#!msg/comp.programming.threads/wEUgPq541v8/ZByyyS8acqMJ
+ "The problem (from the realtime side) with condition variables is that
+ if you can signal/broadcast without holding the mutex, and any thread
+ currently running can acquire an unlocked mutex and check a predicate
+ without reference to the condition variable, then you can have an
+ indirect priority inversion."
+
+ Another possible consequence is that one can create spurious wake-ups when
+ there are multiple threads signaling the condition.
+
+ None of this looks like a problem for our use case.
+*/
+
+void Rdb_cond_var::Notify() { mysql_cond_signal(&m_cond); }
+
+/*
+ @note
+ This is called without holding the mutex that's used for waiting on the
+ condition. See ::Notify().
+*/
+void Rdb_cond_var::NotifyAll() { mysql_cond_broadcast(&m_cond); }
+
+Rdb_mutex::Rdb_mutex() {
+ mysql_mutex_init(0 /* Don't register in P_S. */, &m_mutex,
+ MY_MUTEX_INIT_FAST);
+}
+
+Rdb_mutex::~Rdb_mutex() { mysql_mutex_destroy(&m_mutex); }
+
+Status Rdb_mutex::Lock() {
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ DBUG_ASSERT(m_old_stage_info.count(current_thd) == 0);
+ return Status::OK();
+}
+
+// Attempt to acquire lock. If timeout is non-negative, operation may be
+// failed after this many milliseconds.
+// If implementing a custom version of this class, the implementation may
+// choose to ignore the timeout.
+// Return OK on success, or other Status on failure.
+Status Rdb_mutex::TryLockFor(int64_t timeout_time MY_ATTRIBUTE((__unused__))) {
+ /*
+ Note: PThreads API has pthread_mutex_timedlock(), but mysql's
+ mysql_mutex_* wrappers do not wrap that function.
+ */
+ RDB_MUTEX_LOCK_CHECK(m_mutex);
+ return Status::OK();
+}
+
+#ifndef STANDALONE_UNITTEST
+void Rdb_mutex::set_unlock_action(const PSI_stage_info *const old_stage_arg) {
+ DBUG_ASSERT(old_stage_arg != nullptr);
+
+ mysql_mutex_assert_owner(&m_mutex);
+ DBUG_ASSERT(m_old_stage_info.count(current_thd) == 0);
+
+ m_old_stage_info[current_thd] =
+ std::make_shared<PSI_stage_info>(*old_stage_arg);
+}
+#endif
+
+// Unlock Mutex that was successfully locked by Lock() or TryLockUntil()
+void Rdb_mutex::UnLock() {
+#ifndef STANDALONE_UNITTEST
+ if (m_old_stage_info.count(current_thd) > 0) {
+ const std::shared_ptr<PSI_stage_info> old_stage =
+ m_old_stage_info[current_thd];
+ m_old_stage_info.erase(current_thd);
+ /* The following will call mysql_mutex_unlock */
+ THD_EXIT_COND(current_thd, old_stage.get());
+ return;
+ }
+#endif
+ RDB_MUTEX_UNLOCK_CHECK(m_mutex);
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_mutex_wrapper.h b/storage/rocksdb/rdb_mutex_wrapper.h
new file mode 100644
index 00000000..33eefe9d
--- /dev/null
+++ b/storage/rocksdb/rdb_mutex_wrapper.h
@@ -0,0 +1,143 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* C++ standard header file */
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+/* MySQL header files */
+#include "./my_sys.h"
+#include "mysql/plugin.h"
+
+/* RocksDB header files */
+#include "rocksdb/utilities/transaction_db_mutex.h"
+#include "rdb_mariadb_port.h"
+
+namespace myrocks {
+
+class Rdb_mutex : public rocksdb::TransactionDBMutex {
+ Rdb_mutex(const Rdb_mutex &p) = delete;
+ Rdb_mutex &operator=(const Rdb_mutex &p) = delete;
+
+ public:
+ Rdb_mutex();
+ virtual ~Rdb_mutex() override;
+
+ /*
+ Override parent class's virtual methods of interrest.
+ */
+
+ // Attempt to acquire lock. Return OK on success, or other Status on failure.
+ // If returned status is OK, TransactionDB will eventually call UnLock().
+ virtual rocksdb::Status Lock() override;
+
+ // Attempt to acquire lock. If timeout is non-negative, operation should be
+ // failed after this many microseconds.
+ // Returns OK on success,
+ // TimedOut if timed out,
+ // or other Status on failure.
+ // If returned status is OK, TransactionDB will eventually call UnLock().
+ virtual rocksdb::Status TryLockFor(
+ int64_t timeout_time MY_ATTRIBUTE((__unused__))) override;
+
+ // Unlock Mutex that was successfully locked by Lock() or TryLockUntil()
+ virtual void UnLock() override;
+
+ private:
+ mysql_mutex_t m_mutex;
+ friend class Rdb_cond_var;
+
+#ifndef STANDALONE_UNITTEST
+ void set_unlock_action(const PSI_stage_info *const old_stage_arg);
+ std::unordered_map<THD *, std::shared_ptr<PSI_stage_info>> m_old_stage_info;
+#endif
+};
+
+class Rdb_cond_var : public rocksdb::TransactionDBCondVar {
+ Rdb_cond_var(const Rdb_cond_var &) = delete;
+ Rdb_cond_var &operator=(const Rdb_cond_var &) = delete;
+
+ public:
+ Rdb_cond_var();
+ virtual ~Rdb_cond_var() override;
+
+ /*
+ Override parent class's virtual methods of interrest.
+ */
+
+ // Block current thread until condition variable is notified by a call to
+ // Notify() or NotifyAll(). Wait() will be called with mutex locked.
+ // Returns OK if notified.
+ // Returns non-OK if TransactionDB should stop waiting and fail the operation.
+ // May return OK spuriously even if not notified.
+ virtual rocksdb::Status Wait(
+ const std::shared_ptr<rocksdb::TransactionDBMutex> mutex) override;
+
+ // Block current thread until condition variable is notifiesd by a call to
+ // Notify() or NotifyAll(), or if the timeout is reached.
+ // If timeout is non-negative, operation should be failed after this many
+ // microseconds.
+ // If implementing a custom version of this class, the implementation may
+ // choose to ignore the timeout.
+ //
+ // Returns OK if notified.
+ // Returns TimedOut if timeout is reached.
+ // Returns other status if TransactionDB should otherwis stop waiting and
+ // fail the operation.
+ // May return OK spuriously even if not notified.
+ virtual rocksdb::Status WaitFor(
+ const std::shared_ptr<rocksdb::TransactionDBMutex> mutex,
+ int64_t timeout_time) override;
+
+ // If any threads are waiting on *this, unblock at least one of the
+ // waiting threads.
+ virtual void Notify() override;
+
+ // Unblocks all threads waiting on *this.
+ virtual void NotifyAll() override;
+
+ private:
+ mysql_cond_t m_cond;
+};
+
+class Rdb_mutex_factory : public rocksdb::TransactionDBMutexFactory {
+ public:
+ Rdb_mutex_factory(const Rdb_mutex_factory &) = delete;
+ Rdb_mutex_factory &operator=(const Rdb_mutex_factory &) = delete;
+ Rdb_mutex_factory() {}
+ /*
+ Override parent class's virtual methods of interrest.
+ */
+
+ virtual std::shared_ptr<rocksdb::TransactionDBMutex> AllocateMutex()
+ override {
+ return std::make_shared<Rdb_mutex>();
+ }
+
+ virtual std::shared_ptr<rocksdb::TransactionDBCondVar> AllocateCondVar()
+ override {
+ return std::make_shared<Rdb_cond_var>();
+ }
+
+ virtual ~Rdb_mutex_factory() override {}
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_perf_context.cc b/storage/rocksdb/rdb_perf_context.cc
new file mode 100644
index 00000000..0b22a758
--- /dev/null
+++ b/storage/rocksdb/rdb_perf_context.cc
@@ -0,0 +1,285 @@
+/*
+ Portions Copyright (c) 2015-Present, Facebook, Inc.
+ Portions Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+#include "rdb_mariadb_port.h"
+/* This C++ file's header file */
+#include "./rdb_perf_context.h"
+
+/* C++ system header files */
+#include <string>
+
+/* RocksDB header files */
+#include "rocksdb/iostats_context.h"
+#include "rocksdb/perf_context.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb_proto.h"
+
+namespace myrocks {
+
+// To add a new metric:
+// 1. Update the PC enum in rdb_perf_context.h
+// 2. Update sections (A), (B), and (C) below
+// 3. Update perf_context.test and show_engine.test
+
+std::string rdb_pc_stat_types[] = {
+ // (A) These should be in the same order as the PC enum
+ "USER_KEY_COMPARISON_COUNT",
+ "BLOCK_CACHE_HIT_COUNT",
+ "BLOCK_READ_COUNT",
+ "BLOCK_READ_BYTE",
+ "BLOCK_READ_TIME",
+ "BLOCK_CHECKSUM_TIME",
+ "BLOCK_DECOMPRESS_TIME",
+ "GET_READ_BYTES",
+ "MULTIGET_READ_BYTES",
+ "ITER_READ_BYTES",
+ "INTERNAL_KEY_SKIPPED_COUNT",
+ "INTERNAL_DELETE_SKIPPED_COUNT",
+ "INTERNAL_RECENT_SKIPPED_COUNT",
+ "INTERNAL_MERGE_COUNT",
+ "GET_SNAPSHOT_TIME",
+ "GET_FROM_MEMTABLE_TIME",
+ "GET_FROM_MEMTABLE_COUNT",
+ "GET_POST_PROCESS_TIME",
+ "GET_FROM_OUTPUT_FILES_TIME",
+ "SEEK_ON_MEMTABLE_TIME",
+ "SEEK_ON_MEMTABLE_COUNT",
+ "NEXT_ON_MEMTABLE_COUNT",
+ "PREV_ON_MEMTABLE_COUNT",
+ "SEEK_CHILD_SEEK_TIME",
+ "SEEK_CHILD_SEEK_COUNT",
+ "SEEK_MIN_HEAP_TIME",
+ "SEEK_MAX_HEAP_TIME",
+ "SEEK_INTERNAL_SEEK_TIME",
+ "FIND_NEXT_USER_ENTRY_TIME",
+ "WRITE_WAL_TIME",
+ "WRITE_MEMTABLE_TIME",
+ "WRITE_DELAY_TIME",
+ "WRITE_PRE_AND_POST_PROCESS_TIME",
+ "DB_MUTEX_LOCK_NANOS",
+ "DB_CONDITION_WAIT_NANOS",
+ "MERGE_OPERATOR_TIME_NANOS",
+ "READ_INDEX_BLOCK_NANOS",
+ "READ_FILTER_BLOCK_NANOS",
+ "NEW_TABLE_BLOCK_ITER_NANOS",
+ "NEW_TABLE_ITERATOR_NANOS",
+ "BLOCK_SEEK_NANOS",
+ "FIND_TABLE_NANOS",
+ "BLOOM_MEMTABLE_HIT_COUNT",
+ "BLOOM_MEMTABLE_MISS_COUNT",
+ "BLOOM_SST_HIT_COUNT",
+ "BLOOM_SST_MISS_COUNT",
+ "KEY_LOCK_WAIT_TIME",
+ "KEY_LOCK_WAIT_COUNT",
+ "IO_THREAD_POOL_ID",
+ "IO_BYTES_WRITTEN",
+ "IO_BYTES_READ",
+ "IO_OPEN_NANOS",
+ "IO_ALLOCATE_NANOS",
+ "IO_WRITE_NANOS",
+ "IO_READ_NANOS",
+ "IO_RANGE_SYNC_NANOS",
+ "IO_LOGGER_NANOS"};
+
+#define IO_PERF_RECORD(_field_) \
+ do { \
+ if (rocksdb::get_perf_context()->_field_ > 0) { \
+ counters->m_value[idx] += rocksdb::get_perf_context()->_field_; \
+ } \
+ idx++; \
+ } while (0)
+#define IO_STAT_RECORD(_field_) \
+ do { \
+ if (rocksdb::get_iostats_context()->_field_ > 0) { \
+ counters->m_value[idx] += rocksdb::get_iostats_context()->_field_; \
+ } \
+ idx++; \
+ } while (0)
+
+static void harvest_diffs(Rdb_atomic_perf_counters *const counters) {
+ // (C) These should be in the same order as the PC enum
+ size_t idx = 0;
+ IO_PERF_RECORD(user_key_comparison_count);
+ IO_PERF_RECORD(block_cache_hit_count);
+ IO_PERF_RECORD(block_read_count);
+ IO_PERF_RECORD(block_read_byte);
+ IO_PERF_RECORD(block_read_time);
+ IO_PERF_RECORD(block_checksum_time);
+ IO_PERF_RECORD(block_decompress_time);
+ IO_PERF_RECORD(get_read_bytes);
+ IO_PERF_RECORD(multiget_read_bytes);
+ IO_PERF_RECORD(iter_read_bytes);
+ IO_PERF_RECORD(internal_key_skipped_count);
+ IO_PERF_RECORD(internal_delete_skipped_count);
+ IO_PERF_RECORD(internal_recent_skipped_count);
+ IO_PERF_RECORD(internal_merge_count);
+ IO_PERF_RECORD(get_snapshot_time);
+ IO_PERF_RECORD(get_from_memtable_time);
+ IO_PERF_RECORD(get_from_memtable_count);
+ IO_PERF_RECORD(get_post_process_time);
+ IO_PERF_RECORD(get_from_output_files_time);
+ IO_PERF_RECORD(seek_on_memtable_time);
+ IO_PERF_RECORD(seek_on_memtable_count);
+ IO_PERF_RECORD(next_on_memtable_count);
+ IO_PERF_RECORD(prev_on_memtable_count);
+ IO_PERF_RECORD(seek_child_seek_time);
+ IO_PERF_RECORD(seek_child_seek_count);
+ IO_PERF_RECORD(seek_min_heap_time);
+ IO_PERF_RECORD(seek_max_heap_time);
+ IO_PERF_RECORD(seek_internal_seek_time);
+ IO_PERF_RECORD(find_next_user_entry_time);
+ IO_PERF_RECORD(write_wal_time);
+ IO_PERF_RECORD(write_memtable_time);
+ IO_PERF_RECORD(write_delay_time);
+ IO_PERF_RECORD(write_pre_and_post_process_time);
+ IO_PERF_RECORD(db_mutex_lock_nanos);
+ IO_PERF_RECORD(db_condition_wait_nanos);
+ IO_PERF_RECORD(merge_operator_time_nanos);
+ IO_PERF_RECORD(read_index_block_nanos);
+ IO_PERF_RECORD(read_filter_block_nanos);
+ IO_PERF_RECORD(new_table_block_iter_nanos);
+ IO_PERF_RECORD(new_table_iterator_nanos);
+ IO_PERF_RECORD(block_seek_nanos);
+ IO_PERF_RECORD(find_table_nanos);
+ IO_PERF_RECORD(bloom_memtable_hit_count);
+ IO_PERF_RECORD(bloom_memtable_miss_count);
+ IO_PERF_RECORD(bloom_sst_hit_count);
+ IO_PERF_RECORD(bloom_sst_miss_count);
+ IO_PERF_RECORD(key_lock_wait_time);
+ IO_PERF_RECORD(key_lock_wait_count);
+
+ IO_STAT_RECORD(thread_pool_id);
+ IO_STAT_RECORD(bytes_written);
+ IO_STAT_RECORD(bytes_read);
+ IO_STAT_RECORD(open_nanos);
+ IO_STAT_RECORD(allocate_nanos);
+ IO_STAT_RECORD(write_nanos);
+ IO_STAT_RECORD(read_nanos);
+ IO_STAT_RECORD(range_sync_nanos);
+ IO_STAT_RECORD(logger_nanos);
+}
+
+#undef IO_PERF_DIFF
+#undef IO_STAT_DIFF
+
+static Rdb_atomic_perf_counters rdb_global_perf_counters;
+
+void rdb_get_global_perf_counters(Rdb_perf_counters *const counters) {
+ counters->load(rdb_global_perf_counters);
+}
+
+void Rdb_perf_counters::load(const Rdb_atomic_perf_counters &atomic_counters) {
+ for (int i = 0; i < PC_MAX_IDX; i++) {
+ m_value[i] = atomic_counters.m_value[i].load(std::memory_order_relaxed);
+ }
+}
+
+bool Rdb_io_perf::start(const uint32_t perf_context_level) {
+ const rocksdb::PerfLevel perf_level =
+ static_cast<rocksdb::PerfLevel>(perf_context_level);
+
+ if (rocksdb::GetPerfLevel() != perf_level) {
+ rocksdb::SetPerfLevel(perf_level);
+ }
+
+ if (perf_level == rocksdb::kDisable) {
+ return false;
+ }
+
+ rocksdb::get_perf_context()->Reset();
+ rocksdb::get_iostats_context()->Reset();
+ return true;
+}
+
+void Rdb_io_perf::update_bytes_written(const uint32_t perf_context_level,
+ ulonglong bytes_written) {
+ const rocksdb::PerfLevel perf_level =
+ static_cast<rocksdb::PerfLevel>(perf_context_level);
+ if (perf_level != rocksdb::kDisable && m_shared_io_perf_write) {
+ io_write_bytes += bytes_written;
+ io_write_requests += 1;
+ }
+}
+
+void Rdb_io_perf::end_and_record(const uint32_t perf_context_level) {
+ const rocksdb::PerfLevel perf_level =
+ static_cast<rocksdb::PerfLevel>(perf_context_level);
+
+ if (perf_level == rocksdb::kDisable) {
+ return;
+ }
+
+ if (m_atomic_counters) {
+ harvest_diffs(m_atomic_counters);
+ }
+ harvest_diffs(&rdb_global_perf_counters);
+
+ if (m_shared_io_perf_read &&
+ (rocksdb::get_perf_context()->block_read_byte != 0 ||
+ rocksdb::get_perf_context()->block_read_count != 0 ||
+ rocksdb::get_perf_context()->block_read_time != 0))
+ {
+#ifdef MARIAROCKS_NOT_YET
+ my_io_perf_t io_perf_read;
+
+ io_perf_read.init();
+ io_perf_read.bytes = rocksdb::get_perf_context()->block_read_byte;
+ io_perf_read.requests = rocksdb::get_perf_context()->block_read_count;
+
+ /*
+ Rocksdb does not distinguish between I/O service and wait time, so just
+ use svc time.
+ */
+ io_perf_read.svc_time_max = io_perf_read.svc_time =
+ rocksdb::get_perf_context()->block_read_time;
+
+ m_shared_io_perf_read->sum(io_perf_read);
+ m_stats->table_io_perf_read.sum(io_perf_read);
+#endif
+ }
+
+#ifdef MARIAROCKS_NOT_YET
+ if (m_shared_io_perf_write &&
+ (io_write_bytes != 0 || io_write_requests != 0)) {
+ my_io_perf_t io_perf_write;
+ io_perf_write.init();
+ io_perf_write.bytes = io_write_bytes;
+ io_perf_write.requests = io_write_requests;
+ m_shared_io_perf_write->sum(io_perf_write);
+ m_stats->table_io_perf_write.sum(io_perf_write);
+ io_write_bytes = 0;
+ io_write_requests = 0;
+ }
+
+ if (m_stats) {
+ if (rocksdb::get_perf_context()->internal_key_skipped_count != 0) {
+ m_stats->key_skipped +=
+ rocksdb::get_perf_context()->internal_key_skipped_count;
+ }
+
+ if (rocksdb::get_perf_context()->internal_delete_skipped_count != 0) {
+ m_stats->delete_skipped +=
+ rocksdb::get_perf_context()->internal_delete_skipped_count;
+ }
+ }
+#endif
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_perf_context.h b/storage/rocksdb/rdb_perf_context.h
new file mode 100644
index 00000000..d8381b8e
--- /dev/null
+++ b/storage/rocksdb/rdb_perf_context.h
@@ -0,0 +1,168 @@
+/*
+ Portions Copyright (c) 2015-Present, Facebook, Inc.
+ Portions Copyright (c) 2012,2013 Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ standard header files */
+#include <atomic>
+#include <cstdint>
+#include <string>
+
+/* MySQL header files */
+#include <my_global.h>
+#include "./handler.h"
+
+#include "rdb_mariadb_port.h"
+
+namespace myrocks {
+
+enum {
+ PC_USER_KEY_COMPARISON_COUNT = 0,
+ PC_BLOCK_CACHE_HIT_COUNT,
+ PC_BLOCK_READ_COUNT,
+ PC_BLOCK_READ_BYTE,
+ PC_BLOCK_READ_TIME,
+ PC_BLOCK_CHECKSUM_TIME,
+ PC_BLOCK_DECOMPRESS_TIME,
+ PC_GET_READ_BYTES,
+ PC_MULTIGET_READ_BYTES,
+ PC_ITER_READ_BYTES,
+ PC_KEY_SKIPPED,
+ PC_DELETE_SKIPPED,
+ PC_RECENT_SKIPPED,
+ PC_MERGE,
+ PC_GET_SNAPSHOT_TIME,
+ PC_GET_FROM_MEMTABLE_TIME,
+ PC_GET_FROM_MEMTABLE_COUNT,
+ PC_GET_POST_PROCESS_TIME,
+ PC_GET_FROM_OUTPUT_FILES_TIME,
+ PC_SEEK_ON_MEMTABLE_TIME,
+ PC_SEEK_ON_MEMTABLE_COUNT,
+ PC_NEXT_ON_MEMTABLE_COUNT,
+ PC_PREV_ON_MEMTABLE_COUNT,
+ PC_SEEK_CHILD_SEEK_TIME,
+ PC_SEEK_CHILD_SEEK_COUNT,
+ PC_SEEK_MIN_HEAP_TIME,
+ PC_SEEK_MAX_HEAP_TIME,
+ PC_SEEK_INTERNAL_SEEK_TIME,
+ PC_FIND_NEXT_USER_ENTRY_TIME,
+ PC_WRITE_WAL_TIME,
+ PC_WRITE_MEMTABLE_TIME,
+ PC_WRITE_DELAY_TIME,
+ PC_WRITE_PRE_AND_POST_PROCESSS_TIME,
+ PC_DB_MUTEX_LOCK_NANOS,
+ PC_DB_CONDITION_WAIT_NANOS,
+ PC_MERGE_OPERATOR_TIME_NANOS,
+ PC_READ_INDEX_BLOCK_NANOS,
+ PC_READ_FILTER_BLOCK_NANOS,
+ PC_NEW_TABLE_BLOCK_ITER_NANOS,
+ PC_NEW_TABLE_ITERATOR_NANOS,
+ PC_BLOCK_SEEK_NANOS,
+ PC_FIND_TABLE_NANOS,
+ PC_BLOOM_MEMTABLE_HIT_COUNT,
+ PC_BLOOM_MEMTABLE_MISS_COUNT,
+ PC_BLOOM_SST_HIT_COUNT,
+ PC_BLOOM_SST_MISS_COUNT,
+ PC_KEY_LOCK_WAIT_TIME,
+ PC_KEY_LOCK_WAIT_COUNT,
+ PC_IO_THREAD_POOL_ID,
+ PC_IO_BYTES_WRITTEN,
+ PC_IO_BYTES_READ,
+ PC_IO_OPEN_NANOS,
+ PC_IO_ALLOCATE_NANOS,
+ PC_IO_WRITE_NANOS,
+ PC_IO_READ_NANOS,
+ PC_IO_RANGE_SYNC_NANOS,
+ PC_IO_LOGGER_NANOS,
+ PC_MAX_IDX
+};
+
+class Rdb_perf_counters;
+
+/*
+ A collection of performance counters that can be safely incremented by
+ multiple threads since it stores atomic datapoints.
+*/
+struct Rdb_atomic_perf_counters {
+ std::atomic_ullong m_value[PC_MAX_IDX];
+};
+
+/*
+ A collection of performance counters that is meant to be incremented by
+ a single thread.
+*/
+class Rdb_perf_counters {
+ Rdb_perf_counters(const Rdb_perf_counters &) = delete;
+ Rdb_perf_counters &operator=(const Rdb_perf_counters &) = delete;
+
+ public:
+ Rdb_perf_counters() = default;
+ uint64_t m_value[PC_MAX_IDX];
+
+ void load(const Rdb_atomic_perf_counters &atomic_counters);
+};
+
+extern std::string rdb_pc_stat_types[PC_MAX_IDX];
+
+/*
+ Perf timers for data reads
+ */
+class Rdb_io_perf {
+ // Context management
+ Rdb_atomic_perf_counters *m_atomic_counters = nullptr;
+ my_io_perf_atomic_t *m_shared_io_perf_read = nullptr;
+ my_io_perf_atomic_t *m_shared_io_perf_write = nullptr;
+ ha_statistics *m_stats = nullptr;
+
+ uint64_t io_write_bytes;
+ uint64_t io_write_requests;
+
+ public:
+ Rdb_io_perf(const Rdb_io_perf &) = delete;
+ Rdb_io_perf &operator=(const Rdb_io_perf &) = delete;
+
+ void init(Rdb_atomic_perf_counters *const atomic_counters,
+ my_io_perf_atomic_t *const shared_io_perf_read,
+ my_io_perf_atomic_t *const shared_io_perf_write,
+ ha_statistics *const stats) {
+ DBUG_ASSERT(atomic_counters != nullptr);
+ DBUG_ASSERT(shared_io_perf_read != nullptr);
+ DBUG_ASSERT(shared_io_perf_write != nullptr);
+ DBUG_ASSERT(stats != nullptr);
+
+ m_atomic_counters = atomic_counters;
+ m_shared_io_perf_read = shared_io_perf_read;
+ m_shared_io_perf_write = shared_io_perf_write;
+ m_stats = stats;
+
+ io_write_bytes = 0;
+ io_write_requests = 0;
+ }
+
+ bool start(const uint32_t perf_context_level);
+ void update_bytes_written(const uint32_t perf_context_level,
+ ulonglong bytes_written);
+ void end_and_record(const uint32_t perf_context_level);
+
+ explicit Rdb_io_perf()
+ : m_atomic_counters(nullptr),
+ m_shared_io_perf_read(nullptr),
+ m_stats(nullptr),
+ io_write_bytes(0),
+ io_write_requests(0) {}
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_psi.cc b/storage/rocksdb/rdb_psi.cc
new file mode 100644
index 00000000..77003b1b
--- /dev/null
+++ b/storage/rocksdb/rdb_psi.cc
@@ -0,0 +1,115 @@
+/* Copyright (c) 2017, Percona and/or its affiliates. All rights reserved.
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#define MYSQL_SERVER 1
+
+/* The C++ file's header */
+#include "./rdb_psi.h"
+
+namespace myrocks {
+
+/*
+ The following is needed as an argument for mysql_stage_register,
+ irrespectively of whether we're compiling with P_S or not.
+*/
+my_core::PSI_stage_info stage_waiting_on_row_lock = {0, "Waiting for row lock",
+ 0};
+
+#ifdef HAVE_PSI_INTERFACE
+my_core::PSI_stage_info *all_rocksdb_stages[] = {&stage_waiting_on_row_lock};
+
+my_core::PSI_thread_key rdb_background_psi_thread_key,
+ rdb_drop_idx_psi_thread_key, rdb_mc_psi_thread_key;
+
+my_core::PSI_thread_info all_rocksdb_threads[] = {
+ {&rdb_background_psi_thread_key, "background", PSI_FLAG_GLOBAL},
+ {&rdb_drop_idx_psi_thread_key, "drop index", PSI_FLAG_GLOBAL},
+ {&rdb_mc_psi_thread_key, "manual compaction", PSI_FLAG_GLOBAL},
+};
+
+my_core::PSI_mutex_key rdb_psi_open_tbls_mutex_key, rdb_signal_bg_psi_mutex_key,
+ rdb_signal_drop_idx_psi_mutex_key, rdb_signal_mc_psi_mutex_key,
+ rdb_collation_data_mutex_key, rdb_mem_cmp_space_mutex_key,
+ key_mutex_tx_list, rdb_sysvars_psi_mutex_key, rdb_cfm_mutex_key,
+ rdb_sst_commit_key, rdb_block_cache_resize_mutex_key;
+
+my_core::PSI_mutex_info all_rocksdb_mutexes[] = {
+ {&rdb_psi_open_tbls_mutex_key, "open tables", PSI_FLAG_GLOBAL},
+ {&rdb_signal_bg_psi_mutex_key, "stop background", PSI_FLAG_GLOBAL},
+ {&rdb_signal_drop_idx_psi_mutex_key, "signal drop index", PSI_FLAG_GLOBAL},
+ {&rdb_signal_mc_psi_mutex_key, "signal manual compaction", PSI_FLAG_GLOBAL},
+ {&rdb_collation_data_mutex_key, "collation data init", PSI_FLAG_GLOBAL},
+ {&rdb_mem_cmp_space_mutex_key, "collation space char data init",
+ PSI_FLAG_GLOBAL},
+ {&key_mutex_tx_list, "tx_list", PSI_FLAG_GLOBAL},
+ {&rdb_sysvars_psi_mutex_key, "setting sysvar", PSI_FLAG_GLOBAL},
+ {&rdb_cfm_mutex_key, "column family manager", PSI_FLAG_GLOBAL},
+ {&rdb_sst_commit_key, "sst commit", PSI_FLAG_GLOBAL},
+ {&rdb_block_cache_resize_mutex_key, "resizing block cache",
+ PSI_FLAG_GLOBAL},
+};
+
+my_core::PSI_rwlock_key key_rwlock_collation_exception_list,
+ key_rwlock_read_free_rpl_tables, key_rwlock_skip_unique_check_tables;
+
+my_core::PSI_rwlock_info all_rocksdb_rwlocks[] = {
+ {&key_rwlock_collation_exception_list, "collation_exception_list",
+ PSI_FLAG_GLOBAL},
+ {&key_rwlock_read_free_rpl_tables, "read_free_rpl_tables", PSI_FLAG_GLOBAL},
+ {&key_rwlock_skip_unique_check_tables, "skip_unique_check_tables",
+ PSI_FLAG_GLOBAL},
+};
+
+my_core::PSI_cond_key rdb_signal_bg_psi_cond_key,
+ rdb_signal_drop_idx_psi_cond_key, rdb_signal_mc_psi_cond_key;
+
+my_core::PSI_cond_info all_rocksdb_conds[] = {
+ {&rdb_signal_bg_psi_cond_key, "cond signal background", PSI_FLAG_GLOBAL},
+ {&rdb_signal_drop_idx_psi_cond_key, "cond signal drop index",
+ PSI_FLAG_GLOBAL},
+ {&rdb_signal_mc_psi_cond_key, "cond signal manual compaction",
+ PSI_FLAG_GLOBAL},
+};
+
+void init_rocksdb_psi_keys() {
+ const char *const category = "rocksdb";
+ int count;
+
+ count = array_elements(all_rocksdb_mutexes);
+ mysql_mutex_register(category, all_rocksdb_mutexes, count);
+
+ count = array_elements(all_rocksdb_rwlocks);
+ mysql_rwlock_register(category, all_rocksdb_rwlocks, count);
+
+ count = array_elements(all_rocksdb_conds);
+ // TODO(jay) Disabling PFS for conditions due to the bug
+ // https://github.com/MySQLOnRocksDB/mysql-5.6/issues/92
+ // PSI_server->register_cond(category, all_rocksdb_conds, count);
+
+ count = array_elements(all_rocksdb_stages);
+ mysql_stage_register(category, all_rocksdb_stages, count);
+
+ count = array_elements(all_rocksdb_threads);
+ mysql_thread_register(category, all_rocksdb_threads, count);
+}
+#else // HAVE_PSI_INTERFACE
+void init_rocksdb_psi_keys() {}
+#endif // HAVE_PSI_INTERFACE
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_psi.h b/storage/rocksdb/rdb_psi.h
new file mode 100644
index 00000000..2703837a
--- /dev/null
+++ b/storage/rocksdb/rdb_psi.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2017, Percona and/or its affiliates. All rights reserved.
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+#pragma once
+
+#ifndef _rdb_psi_h_
+#define _rdb_psi_h_
+
+/* MySQL header files */
+#include <my_global.h>
+#include <my_pthread.h>
+
+#include <mysql/psi/mysql_stage.h>
+
+/* MyRocks header files */
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+/*
+ The following is needed as an argument for mysql_stage_register,
+ irrespectively of whether we're compiling with P_S or not.
+*/
+extern my_core::PSI_stage_info stage_waiting_on_row_lock;
+
+#ifdef HAVE_PSI_INTERFACE
+extern my_core::PSI_thread_key rdb_background_psi_thread_key,
+ rdb_drop_idx_psi_thread_key, rdb_mc_psi_thread_key;
+
+extern my_core::PSI_mutex_key rdb_psi_open_tbls_mutex_key,
+ rdb_signal_bg_psi_mutex_key, rdb_signal_drop_idx_psi_mutex_key,
+ rdb_signal_mc_psi_mutex_key, rdb_collation_data_mutex_key,
+ rdb_mem_cmp_space_mutex_key, key_mutex_tx_list, rdb_sysvars_psi_mutex_key,
+ rdb_cfm_mutex_key, rdb_sst_commit_key, rdb_block_cache_resize_mutex_key;
+
+extern my_core::PSI_rwlock_key key_rwlock_collation_exception_list,
+ key_rwlock_read_free_rpl_tables, key_rwlock_skip_unique_check_tables;
+
+extern my_core::PSI_cond_key rdb_signal_bg_psi_cond_key,
+ rdb_signal_drop_idx_psi_cond_key, rdb_signal_mc_psi_cond_key;
+#endif // HAVE_PSI_INTERFACE
+
+void init_rocksdb_psi_keys();
+
+} // namespace myrocks
+
+#endif // _rdb_psi_h_
diff --git a/storage/rocksdb/rdb_source_revision.h b/storage/rocksdb/rdb_source_revision.h
new file mode 100644
index 00000000..d0d81aa1
--- /dev/null
+++ b/storage/rocksdb/rdb_source_revision.h
@@ -0,0 +1 @@
+#define ROCKSDB_GIT_HASH "bba5e7bc21093d7cfa765e1280a7c4fdcd284288"
diff --git a/storage/rocksdb/rdb_source_revision.h.in b/storage/rocksdb/rdb_source_revision.h.in
new file mode 100644
index 00000000..617b39c9
--- /dev/null
+++ b/storage/rocksdb/rdb_source_revision.h.in
@@ -0,0 +1 @@
+#define ROCKSDB_GIT_HASH "@ROCKSDB_GIT_HASH@"
diff --git a/storage/rocksdb/rdb_sst_info.cc b/storage/rocksdb/rdb_sst_info.cc
new file mode 100644
index 00000000..45ae89c1
--- /dev/null
+++ b/storage/rocksdb/rdb_sst_info.cc
@@ -0,0 +1,562 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/* For PRIu64 use below: */
+#define __STDC_FORMAT_MACROS
+
+#include <my_global.h>
+
+/* This C++ file's header file */
+#include "./rdb_sst_info.h"
+
+#include <inttypes.h>
+
+/* C++ standard header files */
+#include <cstdio>
+#include <string>
+#include <utility>
+#include <vector>
+
+/* MySQL header files */
+#include <mysqld_error.h>
+#include "../sql/log.h"
+#include "./my_dir.h"
+
+/* RocksDB header files */
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+#include "./ha_rocksdb_proto.h"
+#include "./rdb_cf_options.h"
+#include "./rdb_psi.h"
+
+namespace myrocks {
+
+Rdb_sst_file_ordered::Rdb_sst_file::Rdb_sst_file(
+ rocksdb::DB *const db, rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options, const std::string &name,
+ const bool tracing)
+ : m_db(db),
+ m_cf(cf),
+ m_db_options(db_options),
+ m_sst_file_writer(nullptr),
+ m_name(name),
+ m_tracing(tracing),
+ m_comparator(cf->GetComparator()) {
+ DBUG_ASSERT(db != nullptr);
+ DBUG_ASSERT(cf != nullptr);
+}
+
+Rdb_sst_file_ordered::Rdb_sst_file::~Rdb_sst_file() {
+ // Make sure we clean up
+ delete m_sst_file_writer;
+ m_sst_file_writer = nullptr;
+}
+
+rocksdb::Status Rdb_sst_file_ordered::Rdb_sst_file::open() {
+ DBUG_ASSERT(m_sst_file_writer == nullptr);
+
+ rocksdb::ColumnFamilyDescriptor cf_descr;
+
+ rocksdb::Status s = m_cf->GetDescriptor(&cf_descr);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Create an sst file writer with the current options and comparator
+ const rocksdb::EnvOptions env_options(m_db_options);
+ const rocksdb::Options options(m_db_options, cf_descr.options);
+
+ m_sst_file_writer =
+ new rocksdb::SstFileWriter(env_options, options, m_comparator, m_cf, true,
+ rocksdb::Env::IOPriority::IO_TOTAL,
+ cf_descr.options.optimize_filters_for_hits);
+
+ s = m_sst_file_writer->Open(m_name);
+ if (m_tracing) {
+ // NO_LINT_DEBUG
+ sql_print_information("SST Tracing: Open(%s) returned %s", m_name.c_str(),
+ s.ok() ? "ok" : "not ok");
+ }
+
+ if (!s.ok()) {
+ delete m_sst_file_writer;
+ m_sst_file_writer = nullptr;
+ }
+
+ return s;
+}
+
+rocksdb::Status Rdb_sst_file_ordered::Rdb_sst_file::put(
+ const rocksdb::Slice &key, const rocksdb::Slice &value) {
+ DBUG_ASSERT(m_sst_file_writer != nullptr);
+
+#ifdef __GNUC__
+ // Add the specified key/value to the sst file writer
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+#ifdef _MSC_VER
+#pragma warning (disable : 4996)
+#endif
+ return m_sst_file_writer->Add(key, value);
+}
+
+std::string Rdb_sst_file_ordered::Rdb_sst_file::generateKey(
+ const std::string &key) {
+ static char const hexdigit[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ std::string res;
+
+ res.reserve(key.size() * 2);
+
+ for (auto ch : key) {
+ res += hexdigit[((uint8_t)ch) >> 4];
+ res += hexdigit[((uint8_t)ch) & 0x0F];
+ }
+
+ return res;
+}
+
+// This function is run by the background thread
+rocksdb::Status Rdb_sst_file_ordered::Rdb_sst_file::commit() {
+ DBUG_ASSERT(m_sst_file_writer != nullptr);
+
+ rocksdb::Status s;
+ rocksdb::ExternalSstFileInfo fileinfo; /// Finish may should be modified
+
+ // Close out the sst file
+ s = m_sst_file_writer->Finish(&fileinfo);
+ if (m_tracing) {
+ // NO_LINT_DEBUG
+ sql_print_information("SST Tracing: Finish returned %s",
+ s.ok() ? "ok" : "not ok");
+ }
+
+ if (s.ok()) {
+ if (m_tracing) {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "SST Tracing: Adding file %s, smallest key: %s, "
+ "largest key: %s, file size: %" PRIu64
+ ", "
+ "num_entries: %" PRIu64,
+ fileinfo.file_path.c_str(),
+ generateKey(fileinfo.smallest_key).c_str(),
+ generateKey(fileinfo.largest_key).c_str(), fileinfo.file_size,
+ fileinfo.num_entries);
+ }
+ }
+
+ delete m_sst_file_writer;
+ m_sst_file_writer = nullptr;
+
+ return s;
+}
+
+void Rdb_sst_file_ordered::Rdb_sst_stack::push(const rocksdb::Slice &key,
+ const rocksdb::Slice &value) {
+ if (m_buffer == nullptr) {
+ m_buffer = new char[m_buffer_size];
+ }
+
+ // Put the actual key and value data unto our stack
+ size_t key_offset = m_offset;
+ memcpy(m_buffer + m_offset, key.data(), key.size());
+ m_offset += key.size();
+ memcpy(m_buffer + m_offset, value.data(), value.size());
+ m_offset += value.size();
+
+ // Push just the offset, the key length and the value length onto the stack
+ m_stack.push(std::make_tuple(key_offset, key.size(), value.size()));
+}
+
+std::pair<rocksdb::Slice, rocksdb::Slice>
+Rdb_sst_file_ordered::Rdb_sst_stack::top() {
+ size_t offset, key_len, value_len;
+ // Pop the next item off the internal stack
+ std::tie(offset, key_len, value_len) = m_stack.top();
+
+ // Make slices from the offset (first), key length (second), and value
+ // length (third)
+ DBUG_ASSERT(m_buffer != nullptr);
+ rocksdb::Slice key(m_buffer + offset, key_len);
+ rocksdb::Slice value(m_buffer + offset + key_len, value_len);
+
+ return std::make_pair(key, value);
+}
+
+Rdb_sst_file_ordered::Rdb_sst_file_ordered(
+ rocksdb::DB *const db, rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options, const std::string &name,
+ const bool tracing, size_t max_size)
+ : m_use_stack(false),
+ m_first(true),
+ m_stack(max_size),
+ m_file(db, cf, db_options, name, tracing) {
+ m_stack.reset();
+}
+
+rocksdb::Status Rdb_sst_file_ordered::apply_first() {
+ rocksdb::Slice first_key_slice(m_first_key);
+ rocksdb::Slice first_value_slice(m_first_value);
+ rocksdb::Status s;
+
+ if (m_use_stack) {
+ // Put the first key onto the stack
+ m_stack.push(first_key_slice, first_value_slice);
+ } else {
+ // Put the first key into the SST
+ s = m_file.put(first_key_slice, first_value_slice);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // Clear out the 'first' strings for next key/value
+ m_first_key.clear();
+ m_first_value.clear();
+
+ return s;
+}
+
+rocksdb::Status Rdb_sst_file_ordered::put(const rocksdb::Slice &key,
+ const rocksdb::Slice &value) {
+ rocksdb::Status s;
+
+ // If this is the first key, just store a copy of the key and value
+ if (m_first) {
+ m_first_key = key.ToString();
+ m_first_value = value.ToString();
+ m_first = false;
+ return rocksdb::Status::OK();
+ }
+
+ // If the first key is not empty we must be the second key. Compare the
+ // new key with the first key to determine if the data will go straight
+ // the SST or be put on the stack to be retrieved later.
+ if (!m_first_key.empty()) {
+ rocksdb::Slice first_key_slice(m_first_key);
+ int cmp = m_file.compare(first_key_slice, key);
+ m_use_stack = (cmp > 0);
+
+ // Apply the first key to the stack or SST
+ s = apply_first();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // Put this key on the stack or into the SST
+ if (m_use_stack) {
+ m_stack.push(key, value);
+ } else {
+ s = m_file.put(key, value);
+ }
+
+ return s;
+}
+
+rocksdb::Status Rdb_sst_file_ordered::commit() {
+ rocksdb::Status s;
+
+ // Make sure we get the first key if it was the only key given to us.
+ if (!m_first_key.empty()) {
+ s = apply_first();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ if (m_use_stack) {
+ rocksdb::Slice key;
+ rocksdb::Slice value;
+
+ // We are ready to commit, pull each entry off the stack (which reverses
+ // the original data) and send it to the SST file.
+ while (!m_stack.empty()) {
+ std::tie(key, value) = m_stack.top();
+ s = m_file.put(key, value);
+ if (!s.ok()) {
+ return s;
+ }
+
+ m_stack.pop();
+ }
+
+ // We have pulled everything off the stack, reset for the next time
+ m_stack.reset();
+ m_use_stack = false;
+ }
+
+ // reset m_first
+ m_first = true;
+
+ return m_file.commit();
+}
+
+Rdb_sst_info::Rdb_sst_info(rocksdb::DB *const db, const std::string &tablename,
+ const std::string &indexname,
+ rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options,
+ const bool tracing)
+ : m_db(db),
+ m_cf(cf),
+ m_db_options(db_options),
+ m_curr_size(0),
+ m_sst_count(0),
+ m_background_error(HA_EXIT_SUCCESS),
+ m_done(false),
+ m_sst_file(nullptr),
+ m_tracing(tracing),
+ m_print_client_error(true) {
+ m_prefix = db->GetName() + "/";
+
+ std::string normalized_table;
+ if (rdb_normalize_tablename(tablename.c_str(), &normalized_table)) {
+ // We failed to get a normalized table name. This should never happen,
+ // but handle it anyway.
+ m_prefix += "fallback_" +
+ std::to_string(reinterpret_cast<intptr_t>(
+ reinterpret_cast<void *>(this))) +
+ "_" + indexname + "_";
+ } else {
+ m_prefix += normalized_table + "_" + indexname + "_";
+ }
+
+ // Unique filename generated to prevent collisions when the same table
+ // is loaded in parallel
+ m_prefix += std::to_string(m_prefix_counter.fetch_add(1)) + "_";
+
+ rocksdb::ColumnFamilyDescriptor cf_descr;
+ const rocksdb::Status s = m_cf->GetDescriptor(&cf_descr);
+ if (!s.ok()) {
+ // Default size if we can't get the cf's target size
+ m_max_size = 64 * 1024 * 1024;
+ } else {
+ // Set the maximum size to 3 times the cf's target size
+ m_max_size = cf_descr.options.target_file_size_base * 3;
+ }
+ mysql_mutex_init(rdb_sst_commit_key, &m_commit_mutex, MY_MUTEX_INIT_FAST);
+}
+
+Rdb_sst_info::~Rdb_sst_info() {
+ DBUG_ASSERT(m_sst_file == nullptr);
+
+ for (auto sst_file : m_committed_files) {
+ // In case something went wrong attempt to delete the temporary file.
+ // If everything went fine that file will have been renamed and this
+ // function call will fail.
+ std::remove(sst_file.c_str());
+ }
+ m_committed_files.clear();
+
+ mysql_mutex_destroy(&m_commit_mutex);
+}
+
+int Rdb_sst_info::open_new_sst_file() {
+ DBUG_ASSERT(m_sst_file == nullptr);
+
+ // Create the new sst file's name
+ const std::string name = m_prefix + std::to_string(m_sst_count++) + m_suffix;
+
+ // Create the new sst file object
+ m_sst_file = new Rdb_sst_file_ordered(m_db, m_cf, m_db_options, name,
+ m_tracing, m_max_size);
+
+ // Open the sst file
+ const rocksdb::Status s = m_sst_file->open();
+ if (!s.ok()) {
+ set_error_msg(m_sst_file->get_name(), s);
+ delete m_sst_file;
+ m_sst_file = nullptr;
+ return HA_ERR_ROCKSDB_BULK_LOAD;
+ }
+
+ m_curr_size = 0;
+
+ return HA_EXIT_SUCCESS;
+}
+
+void Rdb_sst_info::commit_sst_file(Rdb_sst_file_ordered *sst_file) {
+ const rocksdb::Status s = sst_file->commit();
+ if (!s.ok()) {
+ set_error_msg(sst_file->get_name(), s);
+ set_background_error(HA_ERR_ROCKSDB_BULK_LOAD);
+ }
+
+ m_committed_files.push_back(sst_file->get_name());
+
+ delete sst_file;
+}
+
+void Rdb_sst_info::close_curr_sst_file() {
+ DBUG_ASSERT(m_sst_file != nullptr);
+ DBUG_ASSERT(m_curr_size > 0);
+
+ commit_sst_file(m_sst_file);
+
+ // Reset for next sst file
+ m_sst_file = nullptr;
+ m_curr_size = 0;
+}
+
+int Rdb_sst_info::put(const rocksdb::Slice &key, const rocksdb::Slice &value) {
+ int rc;
+
+ DBUG_ASSERT(!m_done);
+
+ if (m_curr_size + key.size() + value.size() >= m_max_size) {
+ // The current sst file has reached its maximum, close it out
+ close_curr_sst_file();
+
+ // While we are here, check to see if we have had any errors from the
+ // background thread - we don't want to wait for the end to report them
+ if (have_background_error()) {
+ return get_and_reset_background_error();
+ }
+ }
+
+ if (m_curr_size == 0) {
+ // We don't have an sst file open - open one
+ rc = open_new_sst_file();
+ if (rc != 0) {
+ return rc;
+ }
+ }
+
+ DBUG_ASSERT(m_sst_file != nullptr);
+
+ // Add the key/value to the current sst file
+ const rocksdb::Status s = m_sst_file->put(key, value);
+ if (!s.ok()) {
+ set_error_msg(m_sst_file->get_name(), s);
+ return HA_ERR_ROCKSDB_BULK_LOAD;
+ }
+
+ m_curr_size += key.size() + value.size();
+
+ return HA_EXIT_SUCCESS;
+}
+
+/*
+ Finish the current work and return the list of SST files ready to be
+ ingested. This function need to be idempotent and atomic
+ */
+int Rdb_sst_info::finish(Rdb_sst_commit_info *commit_info,
+ bool print_client_error) {
+ int ret = HA_EXIT_SUCCESS;
+
+ // Both the transaction clean up and the ha_rocksdb handler have
+ // references to this Rdb_sst_info and both can call commit, so
+ // synchronize on the object here.
+ // This also means in such case the bulk loading operation stop being truly
+ // atomic, and we should consider fixing this in the future
+ RDB_MUTEX_LOCK_CHECK(m_commit_mutex);
+
+ if (is_done()) {
+ RDB_MUTEX_UNLOCK_CHECK(m_commit_mutex);
+ return ret;
+ }
+
+ m_print_client_error = print_client_error;
+
+ if (m_curr_size > 0) {
+ // Close out any existing files
+ close_curr_sst_file();
+ }
+
+ // This checks out the list of files so that the caller can collect/group
+ // them and ingest them all in one go, and any racing calls to commit
+ // won't see them at all
+ commit_info->init(m_cf, std::move(m_committed_files));
+ DBUG_ASSERT(m_committed_files.size() == 0);
+
+ m_done = true;
+ RDB_MUTEX_UNLOCK_CHECK(m_commit_mutex);
+
+ // Did we get any errors?
+ if (have_background_error()) {
+ ret = get_and_reset_background_error();
+ }
+
+ m_print_client_error = true;
+ return ret;
+}
+
+void Rdb_sst_info::set_error_msg(const std::string &sst_file_name,
+ const rocksdb::Status &s) {
+ if (!m_print_client_error) return;
+
+ report_error_msg(s, sst_file_name.c_str());
+}
+
+void Rdb_sst_info::report_error_msg(const rocksdb::Status &s,
+ const char *sst_file_name) {
+ if (s.IsInvalidArgument() &&
+ strcmp(s.getState(), "Keys must be added in strict ascending order.") == 0) {
+ my_printf_error(ER_KEYS_OUT_OF_ORDER,
+ "Rows must be inserted in primary key order "
+ "during bulk load operation",
+ MYF(0));
+ } else if (s.IsInvalidArgument() &&
+ strcmp(s.getState(), "Global seqno is required, but disabled") ==
+ 0) {
+ my_printf_error(ER_OVERLAPPING_KEYS,
+ "Rows inserted during bulk load "
+ "must not overlap existing rows",
+ MYF(0));
+ } else {
+ my_printf_error(ER_UNKNOWN_ERROR, "[%s] bulk load error: %s", MYF(0),
+ sst_file_name, s.ToString().c_str());
+ }
+}
+
+void Rdb_sst_info::init(const rocksdb::DB *const db) {
+ const std::string path = db->GetName() + FN_DIRSEP;
+ struct st_my_dir *const dir_info = my_dir(path.c_str(), MYF(MY_DONT_SORT));
+
+ // Access the directory
+ if (dir_info == nullptr) {
+ // NO_LINT_DEBUG
+ sql_print_warning("RocksDB: Could not access database directory: %s",
+ path.c_str());
+ return;
+ }
+
+ // Scan through the files in the directory
+ const struct fileinfo *file_info = dir_info->dir_entry;
+ for (uint ii= 0; ii < dir_info->number_of_files; ii++, file_info++) {
+ // find any files ending with m_suffix ...
+ const std::string name = file_info->name;
+ const size_t pos = name.find(m_suffix);
+ if (pos != std::string::npos && name.size() - pos == m_suffix.size()) {
+ // ... and remove them
+ const std::string fullname = path + name;
+ my_delete(fullname.c_str(), MYF(0));
+ }
+ }
+
+ // Release the directory entry
+ my_dirend(dir_info);
+}
+
+std::atomic<uint64_t> Rdb_sst_info::m_prefix_counter(0);
+std::string Rdb_sst_info::m_suffix = ".bulk_load.tmp";
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_sst_info.h b/storage/rocksdb/rdb_sst_info.h
new file mode 100644
index 00000000..66da3b7c
--- /dev/null
+++ b/storage/rocksdb/rdb_sst_info.h
@@ -0,0 +1,265 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#pragma once
+
+/* C++ standard header files */
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+#include <stack>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+/* RocksDB header files */
+#include "rocksdb/db.h"
+#include "rocksdb/sst_file_writer.h"
+
+/* MyRocks header files */
+#include "./rdb_utils.h"
+
+namespace myrocks {
+
+class Rdb_sst_file_ordered {
+ private:
+ class Rdb_sst_file {
+ private:
+ Rdb_sst_file(const Rdb_sst_file &p) = delete;
+ Rdb_sst_file &operator=(const Rdb_sst_file &p) = delete;
+
+ rocksdb::DB *const m_db;
+ rocksdb::ColumnFamilyHandle *const m_cf;
+ const rocksdb::DBOptions &m_db_options;
+ rocksdb::SstFileWriter *m_sst_file_writer;
+ const std::string m_name;
+ const bool m_tracing;
+ const rocksdb::Comparator *m_comparator;
+
+ std::string generateKey(const std::string &key);
+
+ public:
+ Rdb_sst_file(rocksdb::DB *const db, rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options, const std::string &name,
+ const bool tracing);
+ ~Rdb_sst_file();
+
+ rocksdb::Status open();
+ rocksdb::Status put(const rocksdb::Slice &key, const rocksdb::Slice &value);
+ rocksdb::Status commit();
+
+ inline const std::string get_name() const { return m_name; }
+ inline int compare(rocksdb::Slice key1, rocksdb::Slice key2) {
+ return m_comparator->Compare(key1, key2);
+ }
+ };
+
+ class Rdb_sst_stack {
+ private:
+ char *m_buffer;
+ size_t m_buffer_size;
+ size_t m_offset;
+ std::stack<std::tuple<size_t, size_t, size_t>> m_stack;
+
+ public:
+ explicit Rdb_sst_stack(size_t max_size)
+ : m_buffer(nullptr), m_buffer_size(max_size) {}
+ ~Rdb_sst_stack() { delete[] m_buffer; }
+
+ void reset() { m_offset = 0; }
+ bool empty() { return m_stack.empty(); }
+ void push(const rocksdb::Slice &key, const rocksdb::Slice &value);
+ std::pair<rocksdb::Slice, rocksdb::Slice> top();
+ void pop() { m_stack.pop(); }
+ size_t size() { return m_stack.size(); }
+ };
+
+ bool m_use_stack;
+ bool m_first;
+ std::string m_first_key;
+ std::string m_first_value;
+ Rdb_sst_stack m_stack;
+ Rdb_sst_file m_file;
+
+ rocksdb::Status apply_first();
+
+ public:
+ Rdb_sst_file_ordered(rocksdb::DB *const db,
+ rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options,
+ const std::string &name, const bool tracing,
+ size_t max_size);
+
+ inline rocksdb::Status open() { return m_file.open(); }
+ rocksdb::Status put(const rocksdb::Slice &key, const rocksdb::Slice &value);
+ rocksdb::Status commit();
+ inline const std::string get_name() const { return m_file.get_name(); }
+};
+
+class Rdb_sst_info {
+ private:
+ Rdb_sst_info(const Rdb_sst_info &p) = delete;
+ Rdb_sst_info &operator=(const Rdb_sst_info &p) = delete;
+
+ rocksdb::DB *const m_db;
+ rocksdb::ColumnFamilyHandle *const m_cf;
+ const rocksdb::DBOptions &m_db_options;
+ uint64_t m_curr_size;
+ uint64_t m_max_size;
+ uint32_t m_sst_count;
+ std::atomic<int> m_background_error;
+ bool m_done;
+ std::string m_prefix;
+ static std::atomic<uint64_t> m_prefix_counter;
+ static std::string m_suffix;
+ mysql_mutex_t m_commit_mutex;
+ Rdb_sst_file_ordered *m_sst_file;
+
+ // List of committed SST files - we'll ingest them later in one single batch
+ std::vector<std::string> m_committed_files;
+
+ const bool m_tracing;
+ bool m_print_client_error;
+
+ int open_new_sst_file();
+ void close_curr_sst_file();
+ void commit_sst_file(Rdb_sst_file_ordered *sst_file);
+
+ void set_error_msg(const std::string &sst_file_name,
+ const rocksdb::Status &s);
+
+ public:
+ Rdb_sst_info(rocksdb::DB *const db, const std::string &tablename,
+ const std::string &indexname,
+ rocksdb::ColumnFamilyHandle *const cf,
+ const rocksdb::DBOptions &db_options, const bool tracing);
+ ~Rdb_sst_info();
+
+ /*
+ This is the unit of work returned from Rdb_sst_info::finish and represents
+ a group of SST to be ingested atomically with other Rdb_sst_commit_info.
+ This is always local to the bulk loading complete operation so no locking
+ is required
+ */
+ class Rdb_sst_commit_info {
+ public:
+ Rdb_sst_commit_info() : m_committed(true), m_cf(nullptr) {}
+
+ Rdb_sst_commit_info(Rdb_sst_commit_info &&rhs) noexcept
+ : m_committed(rhs.m_committed),
+ m_cf(rhs.m_cf),
+ m_committed_files(std::move(rhs.m_committed_files)) {
+ rhs.m_committed = true;
+ rhs.m_cf = nullptr;
+ }
+
+ Rdb_sst_commit_info &operator=(Rdb_sst_commit_info &&rhs) noexcept {
+ reset();
+
+ m_cf = rhs.m_cf;
+ m_committed_files = std::move(rhs.m_committed_files);
+ m_committed = rhs.m_committed;
+
+ rhs.m_committed = true;
+ rhs.m_cf = nullptr;
+
+ return *this;
+ }
+
+ Rdb_sst_commit_info(const Rdb_sst_commit_info &) = delete;
+ Rdb_sst_commit_info &operator=(const Rdb_sst_commit_info &) = delete;
+
+ ~Rdb_sst_commit_info() { reset(); }
+
+ void reset() {
+ if (!m_committed) {
+ for (auto sst_file : m_committed_files) {
+ // In case something went wrong attempt to delete the temporary file.
+ // If everything went fine that file will have been renamed and this
+ // function call will fail.
+ std::remove(sst_file.c_str());
+ }
+ }
+ m_committed_files.clear();
+ m_cf = nullptr;
+ m_committed = true;
+ }
+
+ bool has_work() const {
+ return m_cf != nullptr && m_committed_files.size() > 0;
+ }
+
+ void init(rocksdb::ColumnFamilyHandle *cf,
+ std::vector<std::string> &&files) {
+ DBUG_ASSERT(m_cf == nullptr && m_committed_files.size() == 0 &&
+ m_committed);
+ m_cf = cf;
+ m_committed_files = std::move(files);
+ m_committed = false;
+ }
+
+ rocksdb::ColumnFamilyHandle *get_cf() const { return m_cf; }
+
+ const std::vector<std::string> &get_committed_files() const {
+ return m_committed_files;
+ }
+
+ void commit() { m_committed = true; }
+
+ private:
+ bool m_committed;
+ rocksdb::ColumnFamilyHandle *m_cf;
+ std::vector<std::string> m_committed_files;
+ };
+
+ int put(const rocksdb::Slice &key, const rocksdb::Slice &value);
+ int finish(Rdb_sst_commit_info *commit_info, bool print_client_error = true);
+
+ bool is_done() const { return m_done; }
+
+ bool have_background_error() { return m_background_error != 0; }
+
+ int get_and_reset_background_error() {
+ int ret = m_background_error;
+ while (!m_background_error.compare_exchange_weak(ret, HA_EXIT_SUCCESS)) {
+ // Do nothing
+ }
+
+ return ret;
+ }
+
+ void set_background_error(int code) {
+ int expected = HA_EXIT_SUCCESS;
+ // Only assign 'code' into the error if it is already 0, otherwise ignore it
+ m_background_error.compare_exchange_strong(expected, code);
+ }
+
+ /** Return the list of committed files later to be ingested **/
+ const std::vector<std::string> &get_committed_files() {
+ return m_committed_files;
+ }
+
+ rocksdb::ColumnFamilyHandle *get_cf() const { return m_cf; }
+
+ static void init(const rocksdb::DB *const db);
+
+ static void report_error_msg(const rocksdb::Status &s,
+ const char *sst_file_name);
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_threads.cc b/storage/rocksdb/rdb_threads.cc
new file mode 100644
index 00000000..06683d6e
--- /dev/null
+++ b/storage/rocksdb/rdb_threads.cc
@@ -0,0 +1,83 @@
+/*
+ Portions Copyright (c) 2015-Present, Facebook, Inc.
+ Portions Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+
+/* The C++ file's header */
+#include "./rdb_threads.h"
+
+namespace myrocks {
+
+void *Rdb_thread::thread_func(void *const thread_ptr) {
+ DBUG_ASSERT(thread_ptr != nullptr);
+ Rdb_thread *const thread = static_cast<Rdb_thread *>(thread_ptr);
+ if (!thread->m_run_once.exchange(true)) {
+ thread->setname();
+ thread->run();
+ thread->uninit();
+ }
+ return nullptr;
+}
+
+void Rdb_thread::init(
+#ifdef HAVE_PSI_INTERFACE
+ my_core::PSI_mutex_key stop_bg_psi_mutex_key,
+ my_core::PSI_cond_key stop_bg_psi_cond_key
+#endif
+) {
+ DBUG_ASSERT(!m_run_once);
+ mysql_mutex_init(stop_bg_psi_mutex_key, &m_signal_mutex, MY_MUTEX_INIT_FAST);
+ mysql_cond_init(stop_bg_psi_cond_key, &m_signal_cond, nullptr);
+}
+
+void Rdb_thread::uninit() {
+ mysql_mutex_destroy(&m_signal_mutex);
+ mysql_cond_destroy(&m_signal_cond);
+}
+
+int Rdb_thread::create_thread(const std::string &thread_name
+#ifdef HAVE_PSI_INTERFACE
+ ,
+ PSI_thread_key background_psi_thread_key
+#endif
+) {
+ // Make a copy of the name so we can return without worrying that the
+ // caller will free the memory
+ m_name = thread_name;
+
+ return mysql_thread_create(background_psi_thread_key, &m_handle, nullptr,
+ thread_func, this);
+
+}
+
+void Rdb_thread::signal(const bool stop_thread) {
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+
+ if (stop_thread) {
+ m_stop = true;
+ }
+
+ mysql_cond_signal(&m_signal_cond);
+
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_threads.h b/storage/rocksdb/rdb_threads.h
new file mode 100644
index 00000000..7d89fe06
--- /dev/null
+++ b/storage/rocksdb/rdb_threads.h
@@ -0,0 +1,195 @@
+/*
+ Portions Copyright (c) 2015-Present, Facebook, Inc.
+ Portions Copyright (c) 2012, Monty Program Ab
+
+ 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+/* C++ standard header files */
+#include <map>
+#include <string>
+
+/* MySQL includes */
+#include "./my_global.h"
+#ifdef _WIN32
+#include <my_pthread.h>
+/*
+ Rocksdb implements their own pthread_key functions
+ undefine some my_pthread.h macros
+*/
+#undef pthread_key_create
+#undef pthread_key_delete
+#undef pthread_setspecific
+#undef pthread_getspecific
+#endif
+#include <mysql/psi/mysql_table.h>
+// #include <mysql/thread_pool_priv.h>
+
+/* MyRocks header files */
+#include "./rdb_utils.h"
+#include "rocksdb/db.h"
+
+namespace myrocks {
+
+class Rdb_thread {
+ private:
+ // Disable Copying
+ Rdb_thread(const Rdb_thread &);
+ Rdb_thread &operator=(const Rdb_thread &);
+
+ // Make sure we run only once
+ std::atomic_bool m_run_once;
+
+ pthread_t m_handle;
+
+ std::string m_name;
+
+ protected:
+ mysql_mutex_t m_signal_mutex;
+ mysql_cond_t m_signal_cond;
+ bool m_stop = false;
+
+ public:
+ Rdb_thread() : m_run_once(false) {}
+
+#ifdef HAVE_PSI_INTERFACE
+ void init(my_core::PSI_mutex_key stop_bg_psi_mutex_key,
+ my_core::PSI_cond_key stop_bg_psi_cond_key);
+ int create_thread(const std::string &thread_name,
+ my_core::PSI_thread_key background_psi_thread_key);
+#else
+ void init();
+ int create_thread(const std::string &thread_name);
+#endif
+
+ virtual void run(void) = 0;
+
+ void signal(const bool stop_thread = false);
+
+ int join()
+ {
+#ifndef _WIN32
+ return pthread_join(m_handle, nullptr);
+#else
+ /*
+ mysys on Windows creates "detached" threads in pthread_create().
+
+ m_handle here is the thread id I(it is not reused by the OS
+ thus it is safe to state there can't be other thread with
+ the same id at this point).
+
+ If thread is already finished before pthread_join(),
+ we get EINVAL, and it is safe to ignore and handle this as success.
+ */
+ pthread_join(m_handle, nullptr);
+ return 0;
+#endif
+ }
+
+ void setname() {
+ /*
+ mysql_thread_create() ends up doing some work underneath and setting the
+ thread name as "my-func". This isn't what we want. Our intent is to name
+ the threads according to their purpose so that when displayed under the
+ debugger then they'll be more easily identifiable. Therefore we'll reset
+ the name if thread was successfully created.
+ */
+
+ /*
+ We originally had the creator also set the thread name, but that seems to
+ not work correctly in all situations. Having the created thread do the
+ pthread_setname_np resolves the issue.
+ */
+ DBUG_ASSERT(!m_name.empty());
+#ifdef __linux__
+ int err = pthread_setname_np(m_handle, m_name.c_str());
+ if (err) {
+ // NO_LINT_DEBUG
+ sql_print_warning(
+ "MyRocks: Failed to set name (%s) for current thread, errno=%d,%d",
+ m_name.c_str(), errno, err);
+ }
+#endif
+ }
+
+ void uninit();
+
+ virtual ~Rdb_thread() {}
+
+ private:
+ static void *thread_func(void *const thread_ptr);
+};
+
+/**
+ MyRocks background thread control
+ N.B. This is on top of RocksDB's own background threads
+ (@see rocksdb::CancelAllBackgroundWork())
+*/
+
+class Rdb_background_thread : public Rdb_thread {
+ private:
+ bool m_save_stats = false;
+
+ void reset() {
+ mysql_mutex_assert_owner(&m_signal_mutex);
+ m_stop = false;
+ m_save_stats = false;
+ }
+
+ public:
+ virtual void run() override;
+
+ void request_save_stats() {
+ RDB_MUTEX_LOCK_CHECK(m_signal_mutex);
+
+ m_save_stats = true;
+
+ RDB_MUTEX_UNLOCK_CHECK(m_signal_mutex);
+ }
+};
+
+class Rdb_manual_compaction_thread : public Rdb_thread {
+ private:
+ struct Manual_compaction_request {
+ int mc_id;
+ enum mc_state { INITED = 0, RUNNING } state;
+ rocksdb::ColumnFamilyHandle *cf;
+ rocksdb::Slice *start;
+ rocksdb::Slice *limit;
+ int concurrency = 0;
+ };
+
+ int m_latest_mc_id;
+ mysql_mutex_t m_mc_mutex;
+ std::map<int, Manual_compaction_request> m_requests;
+
+ public:
+ virtual void run() override;
+ int request_manual_compaction(rocksdb::ColumnFamilyHandle *cf,
+ rocksdb::Slice *start, rocksdb::Slice *limit,
+ int concurrency = 0);
+ bool is_manual_compaction_finished(int mc_id);
+ void clear_manual_compaction_request(int mc_id, bool init_only = false);
+ void clear_all_manual_compaction_requests();
+};
+
+/*
+ Drop index thread control
+*/
+
+struct Rdb_drop_index_thread : public Rdb_thread {
+ virtual void run() override;
+};
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_utils.cc b/storage/rocksdb/rdb_utils.cc
new file mode 100644
index 00000000..dc1b7c88
--- /dev/null
+++ b/storage/rocksdb/rdb_utils.cc
@@ -0,0 +1,369 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <my_global.h>
+
+/* This C++ file's header */
+#include "./rdb_utils.h"
+
+/* C++ standard header files */
+#include <array>
+#include <sstream>
+#include <string>
+#include <vector>
+
+/* C standard header files */
+#include <ctype.h>
+
+/* MyRocks header files */
+#include "./ha_rocksdb.h"
+
+/*
+ Both innobase/include/ut0counter.h and rocksdb/port/port_posix.h define
+ CACHE_LINE_SIZE.
+*/
+#ifdef CACHE_LINE_SIZE
+# undef CACHE_LINE_SIZE
+#endif
+
+/* RocksDB header files */
+#include "util/compression.h"
+
+namespace myrocks {
+
+/*
+ Skip past any spaces in the input
+*/
+const char *rdb_skip_spaces(const struct charset_info_st *const cs,
+ const char *str) {
+ while (my_isspace(cs, *str)) {
+ str++;
+ }
+
+ return str;
+}
+
+/*
+ Compare (ignoring case) to see if str2 is the next data in str1.
+ Note that str1 can be longer but we only compare up to the number
+ of characters in str2.
+*/
+bool rdb_compare_strings_ic(const char *const str1, const char *const str2) {
+ // Scan through the strings
+ size_t ii;
+ for (ii = 0; str2[ii]; ii++) {
+ if (toupper(static_cast<int>(str1[ii])) !=
+ toupper(static_cast<int>(str2[ii]))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ Scan through an input string looking for pattern, ignoring case
+ and skipping all data enclosed in quotes.
+*/
+const char *rdb_find_in_string(const char *str, const char *pattern,
+ bool *const succeeded) {
+ char quote = '\0';
+ bool escape = false;
+
+ *succeeded = false;
+
+ for (; *str; str++) {
+ /* If we found a our starting quote character */
+ if (*str == quote) {
+ /* If it was escaped ignore it */
+ if (escape) {
+ escape = false;
+ }
+ /* Otherwise we are now outside of the quoted string */
+ else {
+ quote = '\0';
+ }
+ }
+ /* Else if we are currently inside a quoted string? */
+ else if (quote != '\0') {
+ /* If so, check for the escape character */
+ escape = !escape && *str == '\\';
+ }
+ /* Else if we found a quote we are starting a quoted string */
+ else if (*str == '"' || *str == '\'' || *str == '`') {
+ quote = *str;
+ }
+ /* Else we are outside of a quoted string - look for our pattern */
+ else {
+ if (rdb_compare_strings_ic(str, pattern)) {
+ *succeeded = true;
+ return str;
+ }
+ }
+ }
+
+ // Return the character after the found pattern or the null terminateor
+ // if the pattern wasn't found.
+ return str;
+}
+
+/*
+ See if the next valid token matches the specified string
+*/
+const char *rdb_check_next_token(const struct charset_info_st *const cs,
+ const char *str, const char *const pattern,
+ bool *const succeeded) {
+ // Move past any spaces
+ str = rdb_skip_spaces(cs, str);
+
+ // See if the next characters match the pattern
+ if (rdb_compare_strings_ic(str, pattern)) {
+ *succeeded = true;
+ return str + strlen(pattern);
+ }
+
+ *succeeded = false;
+ return str;
+}
+
+/*
+ Parse id
+*/
+const char *rdb_parse_id(const struct charset_info_st *const cs,
+ const char *str, std::string *const id) {
+ // Move past any spaces
+ str = rdb_skip_spaces(cs, str);
+
+ if (*str == '\0') {
+ return str;
+ }
+
+ char quote = '\0';
+ if (*str == '`' || *str == '"') {
+ quote = *str++;
+ }
+
+ size_t len = 0;
+ const char *start = str;
+
+ if (quote != '\0') {
+ for (;;) {
+ if (*str == '\0') {
+ return str;
+ }
+
+ if (*str == quote) {
+ str++;
+ if (*str != quote) {
+ break;
+ }
+ }
+
+ str++;
+ len++;
+ }
+ } else {
+ while (!my_isspace(cs, *str) && *str != '(' && *str != ')' && *str != '.' &&
+ *str != ',' && *str != '\0') {
+ str++;
+ len++;
+ }
+ }
+
+ // If the user requested the id create it and return it
+ if (id != nullptr) {
+ *id = std::string("");
+ id->reserve(len);
+ while (len--) {
+ *id += *start;
+ if (*start++ == quote) {
+ start++;
+ }
+ }
+ }
+
+ return str;
+}
+
+/*
+ Skip id
+*/
+const char *rdb_skip_id(const struct charset_info_st *const cs,
+ const char *str) {
+ return rdb_parse_id(cs, str, nullptr);
+}
+
+/*
+ Parses a given string into tokens (if any) separated by a specific delimiter.
+*/
+const std::vector<std::string> parse_into_tokens(const std::string &s,
+ const char delim) {
+ std::vector<std::string> tokens;
+ std::string t;
+ std::stringstream ss(s);
+
+ while (getline(ss, t, delim)) {
+ tokens.push_back(t);
+ }
+
+ return tokens;
+}
+
+static const std::size_t rdb_hex_bytes_per_char = 2;
+static const std::array<char, 16> rdb_hexdigit = {{'0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'a', 'b',
+ 'c', 'd', 'e', 'f'}};
+
+/*
+ Convert data into a hex string with optional maximum length.
+ If the data is larger than the maximum length trancate it and append "..".
+*/
+std::string rdb_hexdump(const char *data, const std::size_t data_len,
+ const std::size_t maxsize) {
+ // Count the elements in the string
+ std::size_t elems = data_len;
+ // Calculate the amount of output needed
+ std::size_t len = elems * rdb_hex_bytes_per_char;
+ std::string str;
+
+ if (maxsize != 0 && len > maxsize) {
+ // If the amount of output is too large adjust the settings
+ // and leave room for the ".." at the end
+ elems = (maxsize - 2) / rdb_hex_bytes_per_char;
+ len = elems * rdb_hex_bytes_per_char + 2;
+ }
+
+ // Reserve sufficient space to avoid reallocations
+ str.reserve(len);
+
+ // Loop through the input data and build the output string
+ for (std::size_t ii = 0; ii < elems; ii++, data++) {
+ uint8_t ch = (uint8_t)*data;
+ str += rdb_hexdigit[ch >> 4];
+ str += rdb_hexdigit[ch & 0x0F];
+ }
+
+ // If we can't fit it all add the ".."
+ if (elems != data_len) {
+ str += "..";
+ }
+
+ return str;
+}
+
+/*
+ Attempt to access the database subdirectory to see if it exists
+*/
+bool rdb_database_exists(const std::string &db_name) {
+ const std::string dir =
+ std::string(mysql_real_data_home) + FN_DIRSEP + db_name;
+ struct st_my_dir *const dir_info =
+ my_dir(dir.c_str(), MYF(MY_DONT_SORT | MY_WANT_STAT));
+ if (dir_info == nullptr) {
+ return false;
+ }
+
+ my_dirend(dir_info);
+ return true;
+}
+
+void rdb_log_status_error(const rocksdb::Status &s, const char *msg) {
+ if (msg == nullptr) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: status error, code: %d, error message: %s",
+ s.code(), s.ToString().c_str());
+ return;
+ }
+
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: %s, Status Code: %d, Status: %s", msg, s.code(),
+ s.ToString().c_str());
+}
+
+/*
+ @brief
+ Return a comma-separated string with compiled-in compression types.
+ Not thread-safe.
+*/
+const char *get_rocksdb_supported_compression_types()
+{
+ static std::string compression_methods_buf;
+ static bool inited=false;
+ if (!inited)
+ {
+ inited= true;
+ std::vector<rocksdb::CompressionType> known_types=
+ {
+ rocksdb::kSnappyCompression,
+ rocksdb::kZlibCompression,
+ rocksdb::kBZip2Compression,
+ rocksdb::kLZ4Compression,
+ rocksdb::kLZ4HCCompression,
+ rocksdb::kXpressCompression,
+ rocksdb::kZSTDNotFinalCompression
+ };
+
+ for (auto typ : known_types)
+ {
+ if (CompressionTypeSupported(typ))
+ {
+ if (compression_methods_buf.size())
+ compression_methods_buf.append(",");
+ compression_methods_buf.append(CompressionTypeToString(typ));
+ }
+ }
+ }
+ return compression_methods_buf.c_str();
+}
+
+bool rdb_check_rocksdb_corruption() {
+ return !my_access(myrocks::rdb_corruption_marker_file_name().c_str(), F_OK);
+}
+
+void rdb_persist_corruption_marker() {
+ const std::string &fileName(myrocks::rdb_corruption_marker_file_name());
+ /* O_SYNC is not supported on windows */
+ int fd = my_open(fileName.c_str(), O_CREAT | IF_WIN(0, O_SYNC), MYF(MY_WME));
+ if (fd < 0) {
+ // NO_LINT_DEBUG
+ sql_print_error(
+ "RocksDB: Can't create file %s to mark rocksdb as "
+ "corrupted.",
+ fileName.c_str());
+ } else {
+ // NO_LINT_DEBUG
+ sql_print_information(
+ "RocksDB: Creating the file %s to abort mysqld "
+ "restarts. Remove this file from the data directory "
+ "after fixing the corruption to recover. ",
+ fileName.c_str());
+ }
+
+#ifdef _WIN32
+ /* A replacement for O_SYNC flag above */
+ if (fd >= 0)
+ my_sync(fd, MYF(0));
+#endif
+
+ int ret = my_close(fd, MYF(MY_WME));
+ if (ret) {
+ // NO_LINT_DEBUG
+ sql_print_error("RocksDB: Error (%d) closing the file %s", ret,
+ fileName.c_str());
+ }
+}
+
+} // namespace myrocks
diff --git a/storage/rocksdb/rdb_utils.h b/storage/rocksdb/rdb_utils.h
new file mode 100644
index 00000000..0ef74b9f
--- /dev/null
+++ b/storage/rocksdb/rdb_utils.h
@@ -0,0 +1,335 @@
+/*
+ Copyright (c) 2016, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+#pragma once
+
+#include "rdb_mariadb_port.h"
+
+/* C++ standard header files */
+#include <chrono>
+#include <string>
+#include <vector>
+#include <functional>
+
+/* MySQL header files */
+#include "../sql/log.h"
+#include "./my_stacktrace.h"
+#include "./sql_string.h"
+
+/* RocksDB header files */
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+#ifdef HAVE_JEMALLOC
+#include <jemalloc/jemalloc.h>
+#endif
+
+namespace myrocks {
+
+/*
+ Guess what?
+ An interface is a class where all members are public by default.
+*/
+
+#ifndef interface
+#define interface struct
+#endif // interface
+
+/*
+ Introduce C-style pseudo-namespaces, a handy way to make code more readble
+ when calling into a legacy API, which does not have any namespace defined.
+ Since we cannot or don't want to change the API in any way, we can use this
+ mechanism to define readability tokens that look like C++ namespaces, but are
+ not enforced in any way by the compiler, since the pre-compiler strips them
+ out. However, on the calling side, code looks like my_core::thd_get_ha_data()
+ rather than plain a thd_get_ha_data() call. This technique adds an immediate
+ visible cue on what type of API we are calling into.
+*/
+
+#ifndef my_core
+// C-style pseudo-namespace for MySQL Core API, to be used in decorating calls
+// to non-obvious MySQL functions, like the ones that do not start with well
+// known prefixes: "my_", "sql_", and "mysql_".
+#define my_core
+#endif // my_core
+
+/*
+ The intent behind a SHIP_ASSERT() macro is to have a mechanism for validating
+ invariants in retail builds. Traditionally assertions (such as macros defined
+ in <cassert>) are evaluated for performance reasons only in debug builds and
+ become NOOP in retail builds when DBUG_OFF is defined.
+
+ This macro is intended to validate the invariants which are critical for
+ making sure that data corruption and data loss won't take place. Proper
+ intended usage can be described as "If a particular condition is not true then
+ stop everything what's going on and terminate the process because continued
+ execution will cause really bad things to happen".
+
+ Use the power of SHIP_ASSERT() wisely.
+*/
+
+#ifndef SHIP_ASSERT
+#define SHIP_ASSERT(expr) \
+ do { \
+ if (!(expr)) { \
+ my_safe_printf_stderr("\nShip assert failure: \'%s\'\n", #expr); \
+ abort(); \
+ } \
+ } while (0)
+#endif // SHIP_ASSERT
+
+/*
+ Assert a implies b.
+ If a is true, then b must be true.
+ If a is false, then the value is b does not matter.
+*/
+#ifndef DBUG_ASSERT_IMP
+#define DBUG_ASSERT_IMP(a, b) DBUG_ASSERT(!(a) || (b))
+#endif
+
+/*
+ Assert a if and only if b.
+ a and b must be both true or both false.
+*/
+#ifndef DBUG_ASSERT_IFF
+#define DBUG_ASSERT_IFF(a, b) \
+ DBUG_ASSERT(static_cast<bool>(a) == static_cast<bool>(b))
+#endif
+
+
+/*
+ Portability: use __PRETTY_FUNCTION__ when available, otherwise use __func__
+ which is in the standard.
+*/
+
+#ifdef __GNUC__
+# define __MYROCKS_PORTABLE_PRETTY_FUNCTION__ __PRETTY_FUNCTION__
+#else
+# define __MYROCKS_PORTABLE_PRETTY_FUNCTION__ __func__
+#endif
+
+/*
+ Intent behind this macro is to avoid manually typing the function name every
+ time we want to add the debugging statement and use the compiler for this
+ work. This avoids typical refactoring problems when one renames a function,
+ but the tracing message doesn't get updated.
+
+ We could use __func__ or __FUNCTION__ macros, but __PRETTY_FUNCTION__
+ contains the signature of the function as well as its bare name and provides
+ therefore more context when interpreting the logs.
+*/
+#define DBUG_ENTER_FUNC() DBUG_ENTER(__MYROCKS_PORTABLE_PRETTY_FUNCTION__)
+
+/*
+ Error handling pattern used across MySQL abides by the following rules: "All
+ functions that can report an error (usually an allocation error), should
+ return 0/FALSE/false on success, 1/TRUE/true on failure."
+
+ https://dev.mysql.com/doc/internals/en/additional-suggestions.html has more
+ details.
+
+ To increase the comprehension and readability of MyRocks codebase we'll use
+ constants similar to ones from C standard (EXIT_SUCCESS and EXIT_FAILURE) to
+ make sure that both failure and success paths are clearly identifiable. The
+ definitions of FALSE and TRUE come from <my_global.h>.
+*/
+#define HA_EXIT_SUCCESS FALSE
+#define HA_EXIT_FAILURE TRUE
+
+/*
+ Macros to better convey the intent behind checking the results from locking
+ and unlocking mutexes.
+*/
+#define RDB_MUTEX_LOCK_CHECK(m) \
+ rdb_check_mutex_call_result(__MYROCKS_PORTABLE_PRETTY_FUNCTION__, true, \
+ mysql_mutex_lock(&m))
+#define RDB_MUTEX_UNLOCK_CHECK(m) \
+ rdb_check_mutex_call_result(__MYROCKS_PORTABLE_PRETTY_FUNCTION__, false, \
+ mysql_mutex_unlock(&m))
+
+/*
+ Generic constant.
+*/
+const size_t RDB_MAX_HEXDUMP_LEN = 1000;
+
+/*
+ Helper function to get an NULL terminated uchar* out of a given MySQL String.
+*/
+
+inline uchar *rdb_mysql_str_to_uchar_str(my_core::String *str) {
+ DBUG_ASSERT(str != nullptr);
+ return reinterpret_cast<uchar *>(str->c_ptr());
+}
+
+/*
+ Helper function to get plain (not necessary NULL terminated) uchar* out of a
+ given STL string.
+*/
+
+inline const uchar *rdb_std_str_to_uchar_ptr(const std::string &str) {
+ return reinterpret_cast<const uchar *>(str.data());
+}
+
+/*
+ Helper function to convert seconds to milliseconds.
+*/
+
+constexpr int rdb_convert_sec_to_ms(int sec) {
+ return std::chrono::milliseconds(std::chrono::seconds(sec)).count();
+}
+
+/*
+ Helper function to get plain (not necessary NULL terminated) uchar* out of a
+ given RocksDB item.
+*/
+
+inline const uchar *rdb_slice_to_uchar_ptr(const rocksdb::Slice *item) {
+ DBUG_ASSERT(item != nullptr);
+ return reinterpret_cast<const uchar *>(item->data());
+}
+
+/*
+ Call this function in cases when you can't rely on garbage collector and need
+ to explicitly purge all unused dirty pages. This should be a relatively rare
+ scenario for cases where it has been verified that this intervention has
+ noticeable benefits.
+*/
+inline int purge_all_jemalloc_arenas() {
+#ifdef HAVE_JEMALLOC
+ unsigned narenas = 0;
+ size_t sz = sizeof(unsigned);
+ char name[25] = {0};
+
+ // Get the number of arenas first. Please see `jemalloc` documentation for
+ // all the various options.
+ int result = mallctl("arenas.narenas", &narenas, &sz, nullptr, 0);
+
+ // `mallctl` returns 0 on success and we really want caller to know if all the
+ // trickery actually works.
+ if (result) {
+ return result;
+ }
+
+ // Form the command to be passed to `mallctl` and purge all the unused dirty
+ // pages.
+ snprintf(name, sizeof(name) / sizeof(char), "arena.%d.purge", narenas);
+ result = mallctl(name, nullptr, nullptr, nullptr, 0);
+
+ return result;
+#else
+ return EXIT_SUCCESS;
+#endif
+}
+
+/*
+ Helper function to check the result of locking or unlocking a mutex. We'll
+ intentionally abort in case of a failure because it's better to terminate
+ the process instead of continuing in an undefined state and corrupting data
+ as a result.
+*/
+inline void rdb_check_mutex_call_result(const char *function_name,
+ const bool attempt_lock,
+ const int result) {
+ if (unlikely(result)) {
+ /* NO_LINT_DEBUG */
+ sql_print_error(
+ "%s a mutex inside %s failed with an "
+ "error code %d.",
+ attempt_lock ? "Locking" : "Unlocking", function_name, result);
+
+ // This will hopefully result in a meaningful stack trace which we can use
+ // to efficiently debug the root cause.
+ abort();
+ }
+}
+
+void rdb_log_status_error(const rocksdb::Status &s, const char *msg = nullptr);
+
+// return true if the marker file exists which indicates that the corruption
+// has been detected
+bool rdb_check_rocksdb_corruption();
+
+// stores a marker file in the data directory so that after restart server
+// is still aware that rocksdb data is corrupted
+void rdb_persist_corruption_marker();
+
+/*
+ Helper functions to parse strings.
+*/
+
+const char *rdb_skip_spaces(const struct charset_info_st *const cs,
+ const char *str)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+bool rdb_compare_strings_ic(const char *const str1, const char *const str2)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+const char *rdb_find_in_string(const char *str, const char *pattern,
+ bool *const succeeded)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+const char *rdb_check_next_token(const struct charset_info_st *const cs,
+ const char *str, const char *const pattern,
+ bool *const succeeded)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+const char *rdb_parse_id(const struct charset_info_st *const cs,
+ const char *str, std::string *const id)
+ MY_ATTRIBUTE((__nonnull__(1, 2), __warn_unused_result__));
+
+const char *rdb_skip_id(const struct charset_info_st *const cs, const char *str)
+ MY_ATTRIBUTE((__nonnull__, __warn_unused_result__));
+
+const std::vector<std::string> parse_into_tokens(const std::string &s,
+ const char delim);
+
+/*
+ Helper functions to populate strings.
+*/
+
+std::string rdb_hexdump(const char *data, const std::size_t data_len,
+ const std::size_t maxsize = 0)
+ MY_ATTRIBUTE((__nonnull__));
+
+/*
+ Helper function to see if a database exists
+ */
+bool rdb_database_exists(const std::string &db_name);
+
+const char *get_rocksdb_supported_compression_types();
+
+/*
+ Helper class to make sure cleanup always happens. Helpful for complicated
+ logic where there can be multiple exits/returns requiring cleanup
+ */
+class Ensure_cleanup {
+ public:
+ explicit Ensure_cleanup(std::function<void()> cleanup)
+ : m_cleanup(cleanup), m_skip_cleanup(false) {}
+
+ ~Ensure_cleanup() {
+ if (!m_skip_cleanup) {
+ m_cleanup();
+ }
+ }
+
+ // If you want to skip cleanup (such as when the operation is successful)
+ void skip() { m_skip_cleanup = true; }
+
+ private:
+ std::function<void()> m_cleanup;
+ bool m_skip_cleanup;
+};
+} // namespace myrocks
diff --git a/storage/rocksdb/rocksdb-range-access.txt b/storage/rocksdb/rocksdb-range-access.txt
new file mode 100644
index 00000000..6b5a0db9
--- /dev/null
+++ b/storage/rocksdb/rocksdb-range-access.txt
@@ -0,0 +1,292 @@
+
+This file describes how MySQL index navigation commands are translated into
+RocksDB index navigation commands.
+
+Index tuples are shown as
+
+ ( kv )-aaa-pkN
+
+here
+ * '(kv)' is the 4-byte index number.
+ * '-' is just for readability
+ * everything that follows the '-' is mem-comparable form of the key.
+ In ascii encoding, aaa < bbb < ccc < xxx.
+
+Tuples that start with '#' do not exist in the database. They are only shown
+to demonstrate where Seek() calls end up with.
+
+== HA_READ_KEY_EXACT, forward CF ==
+
+ (kv-1)-xxx-pk
+# ( kv )-aaa <-- "kv-aaa" doesn't exist in the database, but it would be
+ here.
+ ( kv )-aaa-pk <--- Seek("kv-aaa") will put us here on the next record.
+ ( kv )-aaa-pk2
+ ( kv )-bbb-...
+
+RocksDB calls:
+
+ it->Seek(kv);
+ if (it->Valid() && kd->covers_key(..) && kd->cmp_full_keys(...))
+ return record.
+
+== HA_READ_KEY_EXACT, backward CF ==
+
+When we need to seek to a tuple that is a prefix of a full key:
+
+ (kv+1)-xxx-pk
+ ( kv )-ccc-pk
+ ( kv )-bbb-pk3
+ ( kv )-bbb-pk2
+ ( kv )-bbb-pk1 <--- SeekForPrev("kv-bbb") will put us here on the previous
+ record.
+# ( kv )-bbb <--- "kv-bbb" doesn't exist in the database, but it would be
+ ( kv )-aaa-pk here.
+
+Even when (kv)-bbb-pk1 is the last record in the CF, SeekForPrev() will find the
+last record before "kv-bbb", so it already takes care of this case for us.
+
+RocksDB calls:
+
+ it->SeekForPrev(kv);
+ if (it->Valid() && kd->covers_key(..) && kd->cmp_full_keys(...))
+ return record.
+
+== HA_READ_KEY_OR_NEXT, forward CF ==
+
+This is finding min(key) such that key >= lookup_tuple.
+
+If lookup tuple is kv-bbb:
+
+ ( kv )-aaa-pk
+# ( kv )-bbb <-- "kv-bbb" doesn't exist in the database, but it would be
+ here.
+ ( kv )-bbb-pk1 <--- Seek("kv-bbb") will put us here on the next record.
+ ( kv )-bbb-pk2
+ ( kv )-bbb-...
+
+RocksDB calls:
+
+ Seek(kv);
+ if (it->Valid() && kd->covers_key(..))
+ return record.
+
+== HA_READ_KEY_OR_NEXT, backward CF ==
+
+When specified key tuple is a key prefix:
+
+ (kv+1)-xxx-pk
+ ( kv )-ccc-pk
+ ( kv )-bbb-pk3
+ ( kv )-bbb-pk2
+ ( kv )-bbb-pk1 <--- Seek("kv-bbb") will put us here on the previous record.
+# ( kv )-bbb <--- "kv-bbb" doesn't exist in the database, but it would be
+ here.
+ ( kv )-aaa-pk
+
+Even when (kv)-bbb-pk1 is the last record in the CF, SeekForPrev() will find the
+last record before "kv-bbb", so it already takes care of this case for us.
+
+Another kind of special case is when we need to seek to the full value.
+Suppose, the lookup tuple is kv-bbb-pk1:
+
+ (kv+1)-xxx-pk
+ ( kv )-ccc-pk
+ ( kv )-bbb-pk3
+ ( kv )-bbb-pk2
+ ( kv )-bbb-pk1 < -- SeekForPrev(kv-bbb-pk1)
+ ( kv )-bbb-pk0
+
+Then, SeekForPrev(kv-bbb-pk1) may position us exactly at the tuple we need.
+Even If kv-bbb-pk1 is not present in the database, we will be positioned on
+kv-bbb-pk2 no matter whether kv-bbb-pk2 is the last key or not.
+
+RocksDB calls:
+
+ SeekForPrev(...);
+ if (it->Valid() && kd->covers_key(..))
+ return record.
+
+== HA_READ_AFTER_KEY, forward CF ==
+
+This is finding min(key) such that key > lookup_key.
+
+Suppose lookup_key = kv-bbb
+
+ ( kv )-aaa-pk
+# ( kv )-bbb
+ ( kv )-bbb-pk1 <--- Seek("kv-bbb") will put us here. We need to
+ ( kv )-bbb-pk2 get to the value that is next after 'bbb'.
+ ( kv )-bbb-pk3
+ ( kv )-bbb-pk4
+ ( kv )-bbb-pk5
+ ( kv )-ccc-pkN <--- That is, we need to be here.
+
+However, we don't know that the next value is kv-ccc. Instead, we seek to the
+first value that strictly greater than 'kv-bbb'. It is Successor(kv-bbb).
+
+It doesn't matter if we're using a full extended key or not.
+
+RocksDB calls:
+
+ Seek(Successor(kv-bbb));
+ if (it->Valid() && kd->covers_key(...))
+ return record;
+
+Note that the code is the same as with HA_READ_KEY_OR_NEXT, except that
+we seek to Successor($lookup_key) instead of $lookup_key itself.
+
+== HA_READ_AFTER_KEY, backward CF ==
+
+Suppose, the lookup key is 'kv-bbb':
+
+ (kv+1)-xxx-pk
+ ( kv )-ccc-pk7
+ ( kv )-ccc-pk6 <-- We get here when we call Seek(Successor(kv-bbb))
+# Successor(kv-bbb)
+ ( kv )-bbb-pk5
+ ( kv )-bbb-pk4
+ ( kv )-bbb-pk3
+ ( kv )-bbb-pk2
+ ( kv )-bbb-pk1
+# ( kv )-bbb <-- We would get here if we called SeekForPrev(kv-bbb).
+ ( kv )-aaa-pk
+
+RocksDB calls:
+
+ SeekForPrev(Successor(kv-bbb));
+ if (it->Valid() && kd->covers_key(...))
+ return record.
+
+Note that the code is the same as with HA_READ_KEY_OR_NEXT, except that
+we seek to Successor($lookup_key) instead of $lookup_key itself.
+
+== HA_READ_BEFORE_KEY, forward CF ==
+
+This is finding max(key) such that key < lookup_tuple.
+
+Suppose, lookup_tuple=kv-bbb.
+
+ ( kv )-aaa-pk1
+ ( kv )-aaa-pk2
+ ( kv )-aaa-pk3 <-- SeekForPrev("kv-bbb") will put us here.
+# ( kv )-bbb
+ ( kv )-bbb-pk4
+ ( kv )-bbb-pk5
+ ( kv )-bbb-pk6
+
+If the lookup tuple is a full key (e.g. kv-bbb-pk3), and the key is present in
+the database, the iterator will be positioned on the key. We will need to call
+Prev() to get the next key.
+
+RocksDB calls:
+
+ it->SeekForPrev(kv-bbb);
+ if (it->Valid() && using_full_key &&
+ kd->value_matches_prefix(...))
+ {
+ /* We are using full key and we've hit an exact match */
+ it->Prev();
+ }
+
+ if (it->Valid() && kd->covers_key(...))
+ return record;
+
+== HA_READ_BEFORE_KEY, backward CF ==
+
+This is finding max(key) such that key < lookup_tuple.
+Suppose, lookup_tuple=kv-bbb, a prefix of the full key.
+
+ ( kv )-bbb-pk6
+ ( kv )-bbb-pk5
+ ( kv )-bbb-pk4
+# ( kv )-bbb
+ ( kv )-aaa-pk3 <-- Need to be here, and Seek("kv-bbb") will put us here
+ ( kv )-aaa-pk2
+ ( kv )-aaa-pk1
+
+If the lookup tuple is a full key (e.g. kv-bbb-pk4), and the key is present in
+the database, the iterator will be positioned on the key. We will need to call
+Next() to get the next key.
+
+RocksDB calls:
+
+ it->Seek(kv-bbb);
+ if (it->Valid() && using_full_key &&
+ kd->value_matches_prefix(...))
+ {
+ /* We are using full key and we've hit an exact match */
+ it->Next();
+ }
+
+ if (it->Valid() && kd->covers_key(...))
+ return record;
+
+== HA_READ_PREFIX_LAST, forward CF ==
+
+Find the last record with the specified index prefix lookup_tuple.
+
+Suppose, lookup_tuple='kv-bbb'
+
+ ( kv )-aaa-pk2
+ ( kv )-aaa-pk3
+# ( kv )-bbb
+ ( kv )-bbb-pk4
+ ( kv )-bbb-pk5
+ ( kv )-bbb-pk6
+ ( kv )-bbb-pk7 <--- SeekForPrev(Successor(kv-bbb)) will get us here
+# ( kv )-ccc
+ ( kv )-ccc-pk8
+ ( kv )-ccc-pk9
+
+RocksDB calls:
+
+ SeekForPrev(Successor(kv-bbb));
+ if (using_full_key && it->Valid() && !cmp_full_keys(Sucessor(lookup_key)))
+ it->Prev();
+ if (it->Valid() && kd->covers_key(...))
+ {
+ if (!cmp_full_keys(lookup_tuple)) // not needed in _OR_PREV
+ {
+ // the record's prefix matches lookup_tuple.
+ return record;
+ }
+ }
+
+== HA_READ_PREFIX_LAST, backward CF ==
+
+Suppose, lookup_tuple='kv-bbb'
+
+ ( kv )-ccc-pk9
+ ( kv )-ccc-pk8
+# ( kv )-ccc <-- 2. Seek(Successor(kv-bbb)) will point here
+ and it will fall down to the next row.
+ ( kv )-bbb-pk7 <--- 1. Need to be here.
+ ( kv )-bbb-pk6
+ ( kv )-bbb-pk5
+ ( kv )-bbb-pk4
+# ( kv )-bbb
+ ( kv )-aaa-pk3
+ ( kv )-aaa-pk2
+
+
+RocksDB calls:
+
+ it->Seek(Successor(kv-bbb));
+
+ if (using_full_key && it->Valid() && !cmp_full_keys(Sucessor(lookup_key)))
+ it->Next();
+
+ if (it->Valid() && kd->covers_key(..))
+ {
+ if (!cmp_full_keys(...)) // not needed in _OR_PREV
+ {
+ // the record's prefix matches lookup_tuple.
+ return record;
+ }
+ }
+
+== HA_READ_PREFIX_LAST_OR_PREV, forward or backward CF ==
+
+This is just like HA_READ_PREFIX_LAST but we don't need to check that the key
+we've got is in the search prefix. (search for "not needed in _OR_PREV" above)
diff --git a/storage/rocksdb/rocksdb/.clang-format b/storage/rocksdb/rocksdb/.clang-format
new file mode 100644
index 00000000..7c279811
--- /dev/null
+++ b/storage/rocksdb/rocksdb/.clang-format
@@ -0,0 +1,5 @@
+# Complete list of style options can be found at:
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+---
+BasedOnStyle: Google
+...
diff --git a/storage/rocksdb/rocksdb/.gitignore b/storage/rocksdb/rocksdb/.gitignore
new file mode 100644
index 00000000..dc7a1a8e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/.gitignore
@@ -0,0 +1,84 @@
+make_config.mk
+
+*.a
+*.arc
+*.d
+*.dylib*
+*.gcda
+*.gcno
+*.o
+*.so
+*.so.*
+*_test
+*_bench
+*_stress
+*.out
+*.class
+*.jar
+*.*jnilib*
+*.d-e
+*.o-*
+*.swp
+*~
+*.vcxproj
+*.vcxproj.filters
+*.sln
+*.cmake
+.watchmanconfig
+CMakeCache.txt
+CMakeFiles/
+build/
+
+ldb
+manifest_dump
+sst_dump
+blob_dump
+block_cache_trace_analyzer
+tools/block_cache_analyzer/*.pyc
+column_aware_encoding_exp
+util/build_version.cc
+build_tools/VALGRIND_LOGS/
+coverage/COVERAGE_REPORT
+.gdbhistory
+.gdb_history
+package/
+unity.a
+tags
+etags
+rocksdb_dump
+rocksdb_undump
+db_test2
+trace_analyzer
+trace_analyzer_test
+block_cache_trace_analyzer
+.DS_Store
+.vs
+.vscode
+
+java/out
+java/target
+java/test-libs
+java/*.log
+java/include/org_rocksdb_*.h
+
+.idea/
+*.iml
+
+rocksdb.cc
+rocksdb.h
+unity.cc
+java/crossbuild/.vagrant
+.vagrant/
+java/**/*.asc
+java/javadoc
+
+scan_build_report/
+t
+LOG
+
+db_logs/
+tp2/
+fbcode/
+fbcode
+buckifier/*.pyc
+buckifier/__pycache__
diff --git a/storage/rocksdb/rocksdb/.lgtm.yml b/storage/rocksdb/rocksdb/.lgtm.yml
new file mode 100644
index 00000000..12d6f1d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/.lgtm.yml
@@ -0,0 +1,4 @@
+extraction:
+ cpp:
+ index:
+ build_command: make static_lib
diff --git a/storage/rocksdb/rocksdb/.travis.yml b/storage/rocksdb/rocksdb/.travis.yml
new file mode 100644
index 00000000..feca073a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/.travis.yml
@@ -0,0 +1,135 @@
+dist: xenial
+language: cpp
+os:
+ - linux
+ - osx
+compiler:
+ - clang
+ - gcc
+osx_image: xcode9.4
+jdk:
+ - openjdk7
+cache:
+ - ccache
+
+addons:
+ apt:
+ sources:
+ - ubuntu-toolchain-r-test
+ packages:
+ - libgflags-dev
+ - libbz2-dev
+ - liblz4-dev
+ - libsnappy-dev
+ - liblzma-dev # xv
+ - libzstd-dev
+ - zlib1g-dev
+ homebrew:
+ update: true
+ packages:
+ - ccache
+ - gflags
+ - lz4
+ - snappy
+ - xz
+ - zstd
+
+env:
+ - TEST_GROUP=platform_dependent # 16-18 minutes
+ - TEST_GROUP=1 # 33-35 minutes
+ - TEST_GROUP=2 # 18-20 minutes
+ - TEST_GROUP=3 # 20-22 minutes
+ - TEST_GROUP=4 # 12-14 minutes
+ # Run java tests
+ - JOB_NAME=java_test # 4-11 minutes
+ # Build ROCKSDB_LITE
+ - JOB_NAME=lite_build # 3-4 minutes
+ # Build examples
+ - JOB_NAME=examples # 5-7 minutes
+ - JOB_NAME=cmake # 3-5 minutes
+ - JOB_NAME=cmake-gcc8 # 3-5 minutes
+ - JOB_NAME=cmake-mingw # 3 minutes
+
+matrix:
+ exclude:
+ - os: osx
+ env: TEST_GROUP=1
+ - os: osx
+ env: TEST_GROUP=2
+ - os: osx
+ env: TEST_GROUP=3
+ - os: osx
+ env: TEST_GROUP=4
+ - os: osx
+ env: JOB_NAME=cmake-gcc8
+ - os : osx
+ env: JOB_NAME=cmake-mingw
+ - os : linux
+ compiler: clang
+ - os : osx
+ compiler: gcc
+
+install:
+ - if [ "${TRAVIS_OS_NAME}" == osx ]; then
+ PATH=$PATH:/usr/local/opt/ccache/libexec;
+ fi
+ - if [ "${JOB_NAME}" == cmake-gcc8 ]; then
+ sudo apt-get install -y g++-8;
+ CC=gcc-8 && CXX=g++-8;
+ fi
+ - if [ "${JOB_NAME}" == cmake-mingw ]; then
+ sudo apt-get install -y mingw-w64 ;
+ fi
+ - if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
+ mkdir cmake-dist && curl --silent --fail --show-error --location https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH;
+ fi
+ - if [[ "${JOB_NAME}" == java_test ]]; then
+ java -version && echo "JAVA_HOME=${JAVA_HOME}";
+ fi
+
+before_script:
+ # Increase the maximum number of open file descriptors, since some tests use
+ # more FDs than the default limit.
+ - ulimit -n 8192
+
+script:
+ - ${CXX} --version
+ - if [ `command -v ccache` ]; then ccache -C; fi
+ - case $TEST_GROUP in
+ platform_dependent)
+ OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some
+ ;;
+ 1)
+ OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=db_iter_test make -j4 check_some
+ ;;
+ 2)
+ OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 ROCKSDBTESTS_START=db_iter_test ROCKSDBTESTS_END=options_file_test make -j4 check_some
+ ;;
+ 3)
+ OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=options_file_test ROCKSDBTESTS_END=write_prepared_transaction_test make -j4 check_some
+ ;;
+ 4)
+ OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_prepared_transaction_test make -j4 check_some
+ ;;
+ esac
+ - case $JOB_NAME in
+ java_test)
+ OPT=-DTRAVIS V=1 make rocksdbjava jtest
+ ;;
+ lite_build)
+ OPT='-DTRAVIS -DROCKSDB_LITE' V=1 make -j4 static_lib tools
+ ;;
+ examples)
+ OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4
+ ;;
+ cmake-mingw)
+ sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix;
+ 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
+ ;;
+ cmake*)
+ mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release && make -j4 rocksdb rocksdbjni
+ ;;
+ esac
+notifications:
+ email:
+ - leveldb@fb.com
diff --git a/storage/rocksdb/rocksdb/.watchmanconfig b/storage/rocksdb/rocksdb/.watchmanconfig
new file mode 100644
index 00000000..e5b450d7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/.watchmanconfig
@@ -0,0 +1,6 @@
+{
+ "content_hash_warming": true,
+ "content_hash_max_items": 333333,
+ "hint_num_files_per_dir": 8,
+ "fsevents_latency": 0.05
+}
diff --git a/storage/rocksdb/rocksdb/AUTHORS b/storage/rocksdb/rocksdb/AUTHORS
new file mode 100644
index 00000000..a451875f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/AUTHORS
@@ -0,0 +1,12 @@
+Facebook Inc.
+Facebook Engineering Team
+
+Google Inc.
+# Initial version authors:
+Jeffrey Dean <jeff@google.com>
+Sanjay Ghemawat <sanjay@google.com>
+
+# Partial list of contributors:
+Kevin Regan <kevin.d.regan@gmail.com>
+Johan Bilien <jobi@litl.com>
+Matthew Von-Maszewski <https://github.com/matthewvon> (Basho Technologies)
diff --git a/storage/rocksdb/rocksdb/CMakeLists.txt b/storage/rocksdb/rocksdb/CMakeLists.txt
new file mode 100644
index 00000000..aeef92e2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/CMakeLists.txt
@@ -0,0 +1,1178 @@
+# Prerequisites for Windows:
+# This cmake build is for Windows 64-bit only.
+#
+# Prerequisites:
+# You must have at least Visual Studio 2015 Update 3. Start the Developer Command Prompt window that is a part of Visual Studio installation.
+# Run the build commands from within the Developer Command Prompt window to have paths to the compiler and runtime libraries set.
+# You must have git.exe in your %PATH% environment variable.
+#
+# To build Rocksdb for Windows is as easy as 1-2-3-4-5:
+#
+# 1. Update paths to third-party libraries in thirdparty.inc file
+# 2. Create a new directory for build artifacts
+# mkdir build
+# cd build
+# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
+# See thirdparty.inc for more information.
+# sample command: cmake -G "Visual Studio 15 Win64" -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 ..
+# 4. Then build the project in debug mode (you may want to add /m[:<N>] flag to run msbuild in <N> parallel threads
+# or simply /m to use all avail cores)
+# msbuild rocksdb.sln
+#
+# rocksdb.sln build features exclusions of test only code in Release. If you build ALL_BUILD then everything
+# will be attempted but test only code does not build in Release mode.
+#
+# 5. And release mode (/m[:<N>] is also supported)
+# msbuild rocksdb.sln /p:Configuration=Release
+#
+# Linux:
+#
+# 1. Install a recent toolchain such as devtoolset-3 if you're on a older distro. C++11 required.
+# 2. mkdir build; cd build
+# 3. cmake ..
+# 4. make -j
+
+cmake_minimum_required(VERSION 3.5.1)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
+include(ReadVersion)
+get_rocksdb_version(rocksdb_VERSION)
+project(rocksdb
+ VERSION ${rocksdb_VERSION}
+ LANGUAGES CXX C ASM)
+
+if(POLICY CMP0042)
+ cmake_policy(SET CMP0042 NEW)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+ if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
+ set(default_build_type "Debug")
+ else()
+ set(default_build_type "RelWithDebInfo")
+ endif()
+ set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
+ "Default BUILD_TYPE is ${default_build_type}" FORCE)
+endif()
+
+find_program(CCACHE_FOUND ccache)
+if(CCACHE_FOUND)
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
+endif(CCACHE_FOUND)
+
+option(WITH_JEMALLOC "build with JeMalloc" OFF)
+option(WITH_SNAPPY "build with SNAPPY" OFF)
+option(WITH_LZ4 "build with lz4" OFF)
+option(WITH_ZLIB "build with zlib" OFF)
+option(WITH_ZSTD "build with zstd" OFF)
+option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files, regardles of the system code page" OFF)
+if (WITH_WINDOWS_UTF8_FILENAMES)
+ add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES)
+endif()
+# third-party/folly is only validated to work on Linux and Windows for now.
+# So only turn it on there by default.
+if(CMAKE_SYSTEM_NAME MATCHES "Linux|Windows")
+ if(MSVC AND MSVC_VERSION LESS 1910)
+ # Folly does not compile with MSVC older than VS2017
+ option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
+ else()
+ option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" ON)
+ endif()
+else()
+ option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
+endif()
+
+include(CMakeDependentOption)
+CMAKE_DEPENDENT_OPTION(WITH_GFLAGS "build with GFlags" ON
+ "NOT MSVC;NOT MINGW" OFF)
+
+if(MSVC)
+ option(WITH_XPRESS "build with windows built in compression" OFF)
+ include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
+else()
+ if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ # FreeBSD has jemalloc as default malloc
+ # but it does not have all the jemalloc files in include/...
+ set(WITH_JEMALLOC ON)
+ else()
+ if(WITH_JEMALLOC)
+ find_package(JeMalloc REQUIRED)
+ add_definitions(-DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE)
+ list(APPEND THIRDPARTY_LIBS JeMalloc::JeMalloc)
+ endif()
+ endif()
+
+ # No config file for this
+ if(WITH_GFLAGS)
+ find_package(gflags REQUIRED)
+ add_definitions(-DGFLAGS=1)
+ include_directories(${gflags_INCLUDE_DIR})
+ list(APPEND THIRDPARTY_LIBS gflags::gflags)
+ endif()
+
+ if(WITH_SNAPPY)
+ find_package(snappy REQUIRED)
+ add_definitions(-DSNAPPY)
+ list(APPEND THIRDPARTY_LIBS snappy::snappy)
+ endif()
+
+ if(WITH_ZLIB)
+ find_package(ZLIB REQUIRED)
+ add_definitions(-DZLIB)
+ list(APPEND THIRDPARTY_LIBS ZLIB::ZLIB)
+ endif()
+
+ option(WITH_BZ2 "build with bzip2" OFF)
+ if(WITH_BZ2)
+ find_package(BZip2 REQUIRED)
+ add_definitions(-DBZIP2)
+ if(BZIP2_INCLUDE_DIRS)
+ include_directories(${BZIP2_INCLUDE_DIRS})
+ else()
+ include_directories(${BZIP2_INCLUDE_DIR})
+ endif()
+ list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES})
+ endif()
+
+ if(WITH_LZ4)
+ find_package(lz4 REQUIRED)
+ add_definitions(-DLZ4)
+ list(APPEND THIRDPARTY_LIBS lz4::lz4)
+ endif()
+
+ if(WITH_ZSTD)
+ find_package(zstd REQUIRED)
+ add_definitions(-DZSTD)
+ include_directories(${ZSTD_INCLUDE_DIR})
+ list(APPEND THIRDPARTY_LIBS zstd::zstd)
+ endif()
+endif()
+
+string(TIMESTAMP TS "%Y/%m/%d %H:%M:%S" UTC)
+set(GIT_DATE_TIME "${TS}" CACHE STRING "the time we first built rocksdb")
+
+find_package(Git)
+
+if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
+ if(WIN32)
+ execute_process(COMMAND $ENV{COMSPEC} /C ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse HEAD OUTPUT_VARIABLE GIT_SHA)
+ else()
+ execute_process(COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse HEAD OUTPUT_VARIABLE GIT_SHA)
+ endif()
+else()
+ set(GIT_SHA 0)
+endif()
+
+string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
+
+
+option(WITH_MD_LIBRARY "build with MD" ON)
+if(WIN32 AND MSVC)
+ if(WITH_MD_LIBRARY)
+ set(RUNTIME_LIBRARY "MD")
+ else()
+ set(RUNTIME_LIBRARY "MT")
+ endif()
+endif()
+
+set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
+configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
+add_library(build_version OBJECT ${BUILD_VERSION_CC})
+target_include_directories(build_version PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/util)
+if(MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
+else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
+ if(MINGW)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
+ add_definitions(-D_POSIX_C_SOURCE=1)
+ endif()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+ if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
+ include(CheckCXXCompilerFlag)
+ CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER)
+ if(HAVE_OMIT_LEAF_FRAME_POINTER)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -momit-leaf-frame-pointer")
+ endif()
+ endif()
+endif()
+
+include(CheckCCompilerFlag)
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
+ CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
+ if(HAS_ALTIVEC)
+ message(STATUS " HAS_ALTIVEC yes")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8")
+ endif(HAS_ALTIVEC)
+endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
+
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
+ CHECK_C_COMPILER_FLAG("-march=armv8-a+crc+crypto" HAS_ARMV8_CRC)
+ if(HAS_ARMV8_CRC)
+ message(STATUS " HAS_ARMV8_CRC yes")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
+ endif(HAS_ARMV8_CRC)
+endif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
+
+option(PORTABLE "build a portable binary" OFF)
+option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
+if(PORTABLE)
+ # MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h
+ # is available, it is available by default.
+ if(FORCE_SSE42 AND NOT MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul")
+ endif()
+else()
+ if(MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
+ else()
+ if(NOT HAVE_POWER8 AND NOT HAS_ARMV8_CRC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
+ endif()
+ endif()
+endif()
+
+include(CheckCXXSourceCompiles)
+if(NOT MSVC)
+ set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
+endif()
+CHECK_CXX_SOURCE_COMPILES("
+#include <cstdint>
+#include <nmmintrin.h>
+#include <wmmintrin.h>
+int main() {
+ volatile uint32_t x = _mm_crc32_u32(0, 0);
+ 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);
+}
+" HAVE_SSE42)
+unset(CMAKE_REQUIRED_FLAGS)
+if(HAVE_SSE42)
+ add_definitions(-DHAVE_SSE42)
+ add_definitions(-DHAVE_PCLMUL)
+elseif(FORCE_SSE42)
+ message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
+endif()
+
+CHECK_CXX_SOURCE_COMPILES("
+#if defined(_MSC_VER) && !defined(__thread)
+#define __thread __declspec(thread)
+#endif
+int main() {
+ static __thread int tls;
+}
+" HAVE_THREAD_LOCAL)
+if(HAVE_THREAD_LOCAL)
+ add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)
+endif()
+
+option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
+if(FAIL_ON_WARNINGS)
+ if(MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX")
+ else() # assume GCC
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
+ endif()
+endif()
+
+option(WITH_ASAN "build with ASAN" OFF)
+if(WITH_ASAN)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+ if(WITH_JEMALLOC)
+ message(FATAL "ASAN does not work well with JeMalloc")
+ endif()
+endif()
+
+option(WITH_TSAN "build with TSAN" OFF)
+if(WITH_TSAN)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread -pie")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fPIC")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fPIC")
+ if(WITH_JEMALLOC)
+ message(FATAL "TSAN does not work well with JeMalloc")
+ endif()
+endif()
+
+option(WITH_UBSAN "build with UBSAN" OFF)
+if(WITH_UBSAN)
+ add_definitions(-DROCKSDB_UBSAN_RUN)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined")
+ if(WITH_JEMALLOC)
+ message(FATAL "UBSAN does not work well with JeMalloc")
+ endif()
+endif()
+
+option(WITH_NUMA "build with NUMA policy support" OFF)
+if(WITH_NUMA)
+ find_package(NUMA REQUIRED)
+ add_definitions(-DNUMA)
+ include_directories(${NUMA_INCLUDE_DIR})
+ list(APPEND THIRDPARTY_LIBS NUMA::NUMA)
+endif()
+
+option(WITH_TBB "build with Threading Building Blocks (TBB)" OFF)
+if(WITH_TBB)
+ find_package(TBB REQUIRED)
+ add_definitions(-DTBB)
+ list(APPEND THIRDPARTY_LIBS TBB::TBB)
+endif()
+
+# Stall notifications eat some performance from inserts
+option(DISABLE_STALL_NOTIF "Build with stall notifications" OFF)
+if(DISABLE_STALL_NOTIF)
+ add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
+endif()
+
+option(WITH_DYNAMIC_EXTENSION "build with dynamic extension support" OFF)
+if(NOT WITH_DYNAMIC_EXTENSION)
+ add_definitions(-DROCKSDB_NO_DYNAMIC_EXTENSION)
+endif()
+
+if(DEFINED USE_RTTI)
+ if(USE_RTTI)
+ message(STATUS "Enabling RTTI")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI")
+ else()
+ if(MSVC)
+ message(STATUS "Disabling RTTI in Release builds. Always on in Debug.")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
+ else()
+ message(STATUS "Disabling RTTI in Release builds")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
+ endif()
+ endif()
+else()
+ message(STATUS "Enabling RTTI in Debug builds only (default)")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
+ if(MSVC)
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
+ else()
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
+ endif()
+endif()
+
+# Used to run CI build and tests so we can run faster
+option(OPTDBG "Build optimized debug build with MSVC" OFF)
+option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
+if(MSVC)
+ if(OPTDBG)
+ message(STATUS "Debug optimization is enabled")
+ set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
+ else()
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm")
+ endif()
+ if(WITH_RUNTIME_DEBUG)
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
+ else()
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}")
+ endif()
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")
+
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp")
+endif()
+
+option(ROCKSDB_LITE "Build RocksDBLite version" OFF)
+if(ROCKSDB_LITE)
+ add_definitions(-DROCKSDB_LITE)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -Os")
+endif()
+
+if(CMAKE_SYSTEM_NAME MATCHES "Cygwin")
+ add_definitions(-fno-builtin-memcmp -DCYGWIN)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ add_definitions(-DOS_MACOSX)
+ if(CMAKE_SYSTEM_PROCESSOR MATCHES arm)
+ add_definitions(-DIOS_CROSS_COMPILE -DROCKSDB_LITE)
+ # no debug info for IOS, that will make our library big
+ add_definitions(-DNDEBUG)
+ endif()
+elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ add_definitions(-DOS_LINUX)
+elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
+ add_definitions(-DOS_SOLARIS)
+elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ add_definitions(-DOS_FREEBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
+ add_definitions(-DOS_NETBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ add_definitions(-DOS_OPENBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly")
+ add_definitions(-DOS_DRAGONFLYBSD)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Android")
+ add_definitions(-DOS_ANDROID)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64 -DNOMINMAX)
+ if(MINGW)
+ add_definitions(-D_WIN32_WINNT=_WIN32_WINNT_VISTA)
+ endif()
+endif()
+
+if(NOT WIN32)
+ add_definitions(-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX)
+endif()
+
+option(WITH_FALLOCATE "build with fallocate" ON)
+if(WITH_FALLOCATE)
+ CHECK_CXX_SOURCE_COMPILES("
+#include <fcntl.h>
+#include <linux/falloc.h>
+int main() {
+ int fd = open(\"/dev/null\", 0);
+ fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
+}
+" HAVE_FALLOCATE)
+ if(HAVE_FALLOCATE)
+ add_definitions(-DROCKSDB_FALLOCATE_PRESENT)
+ endif()
+endif()
+
+CHECK_CXX_SOURCE_COMPILES("
+#include <fcntl.h>
+int main() {
+ int fd = open(\"/dev/null\", 0);
+ sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE);
+}
+" HAVE_SYNC_FILE_RANGE_WRITE)
+if(HAVE_SYNC_FILE_RANGE_WRITE)
+ add_definitions(-DROCKSDB_RANGESYNC_PRESENT)
+endif()
+
+CHECK_CXX_SOURCE_COMPILES("
+#include <pthread.h>
+int main() {
+ (void) PTHREAD_MUTEX_ADAPTIVE_NP;
+}
+" HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
+if(HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
+ add_definitions(-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX)
+endif()
+
+include(CheckCXXSymbolExists)
+check_cxx_symbol_exists(malloc_usable_size malloc.h HAVE_MALLOC_USABLE_SIZE)
+if(HAVE_MALLOC_USABLE_SIZE)
+ add_definitions(-DROCKSDB_MALLOC_USABLE_SIZE)
+endif()
+
+check_cxx_symbol_exists(sched_getcpu sched.h HAVE_SCHED_GETCPU)
+if(HAVE_SCHED_GETCPU)
+ add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR})
+include_directories(${PROJECT_SOURCE_DIR}/include)
+include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.8.1/fused-src)
+if(WITH_FOLLY_DISTRIBUTED_MUTEX)
+ include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
+endif()
+find_package(Threads REQUIRED)
+
+# Main library source code
+
+set(SOURCES
+ cache/clock_cache.cc
+ cache/lru_cache.cc
+ cache/sharded_cache.cc
+ db/arena_wrapped_db_iter.cc
+ db/builder.cc
+ db/c.cc
+ db/column_family.cc
+ db/compacted_db_impl.cc
+ db/compaction/compaction.cc
+ db/compaction/compaction_iterator.cc
+ db/compaction/compaction_picker.cc
+ db/compaction/compaction_job.cc
+ db/compaction/compaction_picker_fifo.cc
+ db/compaction/compaction_picker_level.cc
+ db/compaction/compaction_picker_universal.cc
+ db/convenience.cc
+ db/db_filesnapshot.cc
+ db/db_impl/db_impl.cc
+ db/db_impl/db_impl_write.cc
+ db/db_impl/db_impl_compaction_flush.cc
+ db/db_impl/db_impl_files.cc
+ db/db_impl/db_impl_open.cc
+ db/db_impl/db_impl_debug.cc
+ db/db_impl/db_impl_experimental.cc
+ db/db_impl/db_impl_readonly.cc
+ db/db_impl/db_impl_secondary.cc
+ db/db_info_dumper.cc
+ db/db_iter.cc
+ db/dbformat.cc
+ db/error_handler.cc
+ db/event_helpers.cc
+ db/experimental.cc
+ db/external_sst_file_ingestion_job.cc
+ db/file_indexer.cc
+ db/flush_job.cc
+ db/flush_scheduler.cc
+ db/forward_iterator.cc
+ db/import_column_family_job.cc
+ db/internal_stats.cc
+ db/logs_with_prep_tracker.cc
+ db/log_reader.cc
+ db/log_writer.cc
+ db/malloc_stats.cc
+ db/memtable.cc
+ db/memtable_list.cc
+ db/merge_helper.cc
+ db/merge_operator.cc
+ db/range_del_aggregator.cc
+ db/range_tombstone_fragmenter.cc
+ db/repair.cc
+ db/snapshot_impl.cc
+ db/table_cache.cc
+ db/table_properties_collector.cc
+ db/transaction_log_impl.cc
+ db/trim_history_scheduler.cc
+ db/version_builder.cc
+ db/version_edit.cc
+ db/version_set.cc
+ db/wal_manager.cc
+ db/write_batch.cc
+ db/write_batch_base.cc
+ db/write_controller.cc
+ db/write_thread.cc
+ env/env.cc
+ env/env_chroot.cc
+ env/env_encryption.cc
+ env/env_hdfs.cc
+ env/file_system.cc
+ env/mock_env.cc
+ file/delete_scheduler.cc
+ file/file_prefetch_buffer.cc
+ file/file_util.cc
+ file/filename.cc
+ file/random_access_file_reader.cc
+ file/read_write_util.cc
+ file/readahead_raf.cc
+ file/sequence_file_reader.cc
+ file/sst_file_manager_impl.cc
+ file/writable_file_writer.cc
+ logging/auto_roll_logger.cc
+ logging/event_logger.cc
+ logging/log_buffer.cc
+ memory/arena.cc
+ memory/concurrent_arena.cc
+ memory/jemalloc_nodump_allocator.cc
+ memtable/alloc_tracker.cc
+ memtable/hash_linklist_rep.cc
+ memtable/hash_skiplist_rep.cc
+ memtable/skiplistrep.cc
+ memtable/vectorrep.cc
+ memtable/write_buffer_manager.cc
+ monitoring/histogram.cc
+ monitoring/histogram_windowing.cc
+ monitoring/in_memory_stats_history.cc
+ monitoring/instrumented_mutex.cc
+ monitoring/iostats_context.cc
+ monitoring/perf_context.cc
+ monitoring/perf_level.cc
+ monitoring/persistent_stats_history.cc
+ monitoring/statistics.cc
+ monitoring/thread_status_impl.cc
+ monitoring/thread_status_updater.cc
+ monitoring/thread_status_util.cc
+ monitoring/thread_status_util_debug.cc
+ options/cf_options.cc
+ options/db_options.cc
+ options/options.cc
+ options/options_helper.cc
+ options/options_parser.cc
+ options/options_sanity_check.cc
+ port/stack_trace.cc
+ table/adaptive/adaptive_table_factory.cc
+ table/block_based/block.cc
+ table/block_based/block_based_filter_block.cc
+ table/block_based/block_based_table_builder.cc
+ table/block_based/block_based_table_factory.cc
+ table/block_based/block_based_table_reader.cc
+ table/block_based/block_builder.cc
+ table/block_based/block_prefix_index.cc
+ table/block_based/data_block_hash_index.cc
+ table/block_based/data_block_footer.cc
+ table/block_based/filter_block_reader_common.cc
+ table/block_based/filter_policy.cc
+ table/block_based/flush_block_policy.cc
+ table/block_based/full_filter_block.cc
+ table/block_based/index_builder.cc
+ table/block_based/parsed_full_filter_block.cc
+ table/block_based/partitioned_filter_block.cc
+ table/block_based/uncompression_dict_reader.cc
+ table/block_fetcher.cc
+ table/cuckoo/cuckoo_table_builder.cc
+ table/cuckoo/cuckoo_table_factory.cc
+ table/cuckoo/cuckoo_table_reader.cc
+ table/format.cc
+ table/get_context.cc
+ table/iterator.cc
+ table/merging_iterator.cc
+ table/meta_blocks.cc
+ table/persistent_cache_helper.cc
+ table/plain/plain_table_bloom.cc
+ table/plain/plain_table_builder.cc
+ table/plain/plain_table_factory.cc
+ table/plain/plain_table_index.cc
+ table/plain/plain_table_key_coding.cc
+ table/plain/plain_table_reader.cc
+ table/sst_file_reader.cc
+ table/sst_file_writer.cc
+ table/table_properties.cc
+ table/two_level_iterator.cc
+ test_util/sync_point.cc
+ test_util/sync_point_impl.cc
+ test_util/testutil.cc
+ test_util/transaction_test_util.cc
+ tools/block_cache_analyzer/block_cache_trace_analyzer.cc
+ tools/dump/db_dump_tool.cc
+ tools/ldb_cmd.cc
+ tools/ldb_tool.cc
+ tools/sst_dump_tool.cc
+ tools/trace_analyzer_tool.cc
+ trace_replay/trace_replay.cc
+ trace_replay/block_cache_tracer.cc
+ util/coding.cc
+ util/compaction_job_stats_impl.cc
+ util/comparator.cc
+ util/compression_context_cache.cc
+ util/concurrent_task_limiter_impl.cc
+ util/crc32c.cc
+ util/dynamic_bloom.cc
+ util/hash.cc
+ util/murmurhash.cc
+ util/random.cc
+ util/rate_limiter.cc
+ util/slice.cc
+ util/file_checksum_helper.cc
+ util/status.cc
+ util/string_util.cc
+ util/thread_local.cc
+ util/threadpool_imp.cc
+ util/xxhash.cc
+ utilities/backupable/backupable_db.cc
+ utilities/blob_db/blob_compaction_filter.cc
+ utilities/blob_db/blob_db.cc
+ utilities/blob_db/blob_db_impl.cc
+ utilities/blob_db/blob_db_impl_filesnapshot.cc
+ utilities/blob_db/blob_dump_tool.cc
+ utilities/blob_db/blob_file.cc
+ utilities/blob_db/blob_log_reader.cc
+ utilities/blob_db/blob_log_writer.cc
+ utilities/blob_db/blob_log_format.cc
+ utilities/cassandra/cassandra_compaction_filter.cc
+ utilities/cassandra/format.cc
+ utilities/cassandra/merge_operator.cc
+ utilities/checkpoint/checkpoint_impl.cc
+ utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
+ utilities/debug.cc
+ utilities/env_mirror.cc
+ utilities/env_timed.cc
+ utilities/leveldb_options/leveldb_options.cc
+ utilities/memory/memory_util.cc
+ utilities/merge_operators/bytesxor.cc
+ utilities/merge_operators/max.cc
+ utilities/merge_operators/put.cc
+ utilities/merge_operators/sortlist.cc
+ utilities/merge_operators/string_append/stringappend.cc
+ utilities/merge_operators/string_append/stringappend2.cc
+ utilities/merge_operators/uint64add.cc
+ utilities/object_registry.cc
+ utilities/option_change_migration/option_change_migration.cc
+ utilities/options/options_util.cc
+ utilities/persistent_cache/block_cache_tier.cc
+ utilities/persistent_cache/block_cache_tier_file.cc
+ utilities/persistent_cache/block_cache_tier_metadata.cc
+ utilities/persistent_cache/persistent_cache_tier.cc
+ utilities/persistent_cache/volatile_tier_impl.cc
+ utilities/simulator_cache/cache_simulator.cc
+ utilities/simulator_cache/sim_cache.cc
+ utilities/table_properties_collectors/compact_on_deletion_collector.cc
+ utilities/trace/file_trace_reader_writer.cc
+ utilities/transactions/optimistic_transaction_db_impl.cc
+ utilities/transactions/optimistic_transaction.cc
+ utilities/transactions/pessimistic_transaction.cc
+ utilities/transactions/pessimistic_transaction_db.cc
+ utilities/transactions/snapshot_checker.cc
+ utilities/transactions/transaction_base.cc
+ utilities/transactions/transaction_db_mutex_impl.cc
+ utilities/transactions/transaction_lock_mgr.cc
+ utilities/transactions/transaction_util.cc
+ utilities/transactions/write_prepared_txn.cc
+ utilities/transactions/write_prepared_txn_db.cc
+ utilities/transactions/write_unprepared_txn.cc
+ utilities/transactions/write_unprepared_txn_db.cc
+ utilities/ttl/db_ttl_impl.cc
+ utilities/write_batch_with_index/write_batch_with_index.cc
+ utilities/write_batch_with_index/write_batch_with_index_internal.cc
+ $<TARGET_OBJECTS:build_version>)
+
+if(HAVE_SSE42 AND NOT MSVC)
+ set_source_files_properties(
+ util/crc32c.cc
+ PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
+endif()
+
+if(HAVE_POWER8)
+ list(APPEND SOURCES
+ util/crc32c_ppc.c
+ util/crc32c_ppc_asm.S)
+endif(HAVE_POWER8)
+
+if(HAS_ARMV8_CRC)
+ list(APPEND SOURCES
+ util/crc32c_arm64.cc)
+endif(HAS_ARMV8_CRC)
+
+if(WIN32)
+ list(APPEND SOURCES
+ port/win/io_win.cc
+ port/win/env_win.cc
+ port/win/env_default.cc
+ port/win/port_win.cc
+ port/win/win_logger.cc
+ port/win/win_thread.cc)
+
+if(WITH_XPRESS)
+ list(APPEND SOURCES
+ port/win/xpress_win.cc)
+endif()
+
+if(WITH_JEMALLOC)
+ list(APPEND SOURCES
+ port/win/win_jemalloc.cc)
+endif()
+
+else()
+ list(APPEND SOURCES
+ port/port_posix.cc
+ env/env_posix.cc
+ env/fs_posix.cc
+ env/io_posix.cc)
+endif()
+
+if(WITH_FOLLY_DISTRIBUTED_MUTEX)
+ list(APPEND SOURCES
+ third-party/folly/folly/detail/Futex.cpp
+ third-party/folly/folly/synchronization/AtomicNotification.cpp
+ third-party/folly/folly/synchronization/DistributedMutex.cpp
+ third-party/folly/folly/synchronization/ParkingLot.cpp
+ third-party/folly/folly/synchronization/WaitOptions.cpp)
+endif()
+
+set(ROCKSDB_STATIC_LIB rocksdb${ARTIFACT_SUFFIX})
+set(ROCKSDB_SHARED_LIB rocksdb-shared${ARTIFACT_SUFFIX})
+
+option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)
+
+option(WITH_LIBRADOS "Build with librados" OFF)
+if(WITH_LIBRADOS)
+ list(APPEND SOURCES
+ utilities/env_librados.cc)
+ list(APPEND THIRDPARTY_LIBS rados)
+endif()
+
+if(WIN32)
+ set(SYSTEM_LIBS ${SYSTEM_LIBS} shlwapi.lib rpcrt4.lib)
+else()
+ set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT})
+endif()
+
+add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES})
+target_link_libraries(${ROCKSDB_STATIC_LIB}
+ ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
+
+if(ROCKSDB_BUILD_SHARED)
+ add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES})
+ target_link_libraries(${ROCKSDB_SHARED_LIB}
+ ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
+
+ if(WIN32)
+ set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
+ COMPILE_DEFINITIONS "ROCKSDB_DLL;ROCKSDB_LIBRARY_EXPORTS")
+ if(MSVC)
+ set_target_properties(${ROCKSDB_STATIC_LIB} PROPERTIES
+ COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/${ROCKSDB_STATIC_LIB}.pdb")
+ set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
+ COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/${ROCKSDB_SHARED_LIB}.pdb")
+ endif()
+ else()
+ set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
+ LINKER_LANGUAGE CXX
+ VERSION ${rocksdb_VERSION}
+ SOVERSION ${rocksdb_VERSION_MAJOR}
+ CXX_STANDARD 11
+ OUTPUT_NAME "rocksdb")
+ endif()
+endif()
+
+if(ROCKSDB_BUILD_SHARED AND NOT WIN32)
+ set(ROCKSDB_LIB ${ROCKSDB_SHARED_LIB})
+else()
+ set(ROCKSDB_LIB ${ROCKSDB_STATIC_LIB})
+endif()
+
+option(WITH_JNI "build with JNI" OFF)
+if(WITH_JNI OR JNI)
+ message(STATUS "JNI library is enabled")
+ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/java)
+else()
+ message(STATUS "JNI library is disabled")
+endif()
+
+# Installation and packaging
+if(WIN32)
+ option(ROCKSDB_INSTALL_ON_WINDOWS "Enable install target on Windows" OFF)
+endif()
+if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
+ if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ # Change default installation prefix on Linux to /usr
+ set(CMAKE_INSTALL_PREFIX /usr CACHE PATH "Install path prefix, prepended onto install directories." FORCE)
+ endif()
+ endif()
+
+ include(GNUInstallDirs)
+ include(CMakePackageConfigHelpers)
+
+ set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb)
+
+ configure_package_config_file(
+ ${CMAKE_CURRENT_LIST_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
+ INSTALL_DESTINATION ${package_config_destination}
+ )
+
+ write_basic_package_version_file(
+ RocksDBConfigVersion.cmake
+ VERSION ${rocksdb_VERSION}
+ COMPATIBILITY SameMajorVersion
+ )
+
+ install(DIRECTORY include/rocksdb COMPONENT devel DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+
+ install(
+ TARGETS ${ROCKSDB_STATIC_LIB}
+ EXPORT RocksDBTargets
+ COMPONENT devel
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+ )
+
+ if(ROCKSDB_BUILD_SHARED)
+ install(
+ TARGETS ${ROCKSDB_SHARED_LIB}
+ EXPORT RocksDBTargets
+ COMPONENT runtime
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+ )
+ endif()
+
+ install(
+ EXPORT RocksDBTargets
+ COMPONENT devel
+ DESTINATION ${package_config_destination}
+ NAMESPACE RocksDB::
+ )
+
+ install(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfigVersion.cmake
+ COMPONENT devel
+ DESTINATION ${package_config_destination}
+ )
+endif()
+
+# Tests are excluded from Release builds
+CMAKE_DEPENDENT_OPTION(WITH_TESTS "build with tests" ON
+ "CMAKE_BUILD_TYPE STREQUAL Debug" OFF)
+if(WITH_TESTS)
+ add_subdirectory(third-party/gtest-1.8.1/fused-src/gtest)
+ add_library(testharness STATIC
+ test_util/testharness.cc)
+ target_link_libraries(testharness gtest)
+
+ set(TESTS
+ cache/cache_test.cc
+ cache/lru_cache_test.cc
+ db/column_family_test.cc
+ db/compact_files_test.cc
+ db/compaction/compaction_job_stats_test.cc
+ db/compaction/compaction_job_test.cc
+ db/compaction/compaction_iterator_test.cc
+ db/compaction/compaction_picker_test.cc
+ db/comparator_db_test.cc
+ db/corruption_test.cc
+ db/cuckoo_table_db_test.cc
+ db/db_basic_test.cc
+ db/db_blob_index_test.cc
+ db/db_block_cache_test.cc
+ db/db_bloom_filter_test.cc
+ db/db_compaction_filter_test.cc
+ db/db_compaction_test.cc
+ db/db_dynamic_level_test.cc
+ db/db_flush_test.cc
+ db/db_inplace_update_test.cc
+ db/db_io_failure_test.cc
+ db/db_iter_test.cc
+ db/db_iter_stress_test.cc
+ db/db_iterator_test.cc
+ db/db_log_iter_test.cc
+ db/db_memtable_test.cc
+ db/db_merge_operator_test.cc
+ db/db_merge_operand_test.cc
+ db/db_options_test.cc
+ db/db_properties_test.cc
+ db/db_range_del_test.cc
+ db/db_impl/db_secondary_test.cc
+ db/db_sst_test.cc
+ db/db_statistics_test.cc
+ db/db_table_properties_test.cc
+ db/db_tailing_iter_test.cc
+ db/db_test.cc
+ db/db_test2.cc
+ db/db_universal_compaction_test.cc
+ db/db_wal_test.cc
+ db/db_write_test.cc
+ db/dbformat_test.cc
+ db/deletefile_test.cc
+ db/error_handler_test.cc
+ db/obsolete_files_test.cc
+ db/external_sst_file_basic_test.cc
+ db/external_sst_file_test.cc
+ db/fault_injection_test.cc
+ db/file_indexer_test.cc
+ db/filename_test.cc
+ db/flush_job_test.cc
+ db/listener_test.cc
+ db/log_test.cc
+ db/manual_compaction_test.cc
+ db/memtable_list_test.cc
+ db/merge_helper_test.cc
+ db/merge_test.cc
+ db/options_file_test.cc
+ db/perf_context_test.cc
+ db/plain_table_db_test.cc
+ db/prefix_test.cc
+ db/range_del_aggregator_test.cc
+ db/range_tombstone_fragmenter_test.cc
+ db/repair_test.cc
+ db/table_properties_collector_test.cc
+ db/version_builder_test.cc
+ db/version_edit_test.cc
+ db/version_set_test.cc
+ db/wal_manager_test.cc
+ db/write_batch_test.cc
+ db/write_callback_test.cc
+ db/write_controller_test.cc
+ env/env_basic_test.cc
+ env/env_test.cc
+ env/mock_env_test.cc
+ file/delete_scheduler_test.cc
+ logging/auto_roll_logger_test.cc
+ logging/env_logger_test.cc
+ logging/event_logger_test.cc
+ memory/arena_test.cc
+ memtable/inlineskiplist_test.cc
+ memtable/skiplist_test.cc
+ memtable/write_buffer_manager_test.cc
+ monitoring/histogram_test.cc
+ monitoring/iostats_context_test.cc
+ monitoring/statistics_test.cc
+ monitoring/stats_history_test.cc
+ options/options_settable_test.cc
+ options/options_test.cc
+ table/block_based/block_based_filter_block_test.cc
+ table/block_based/block_test.cc
+ table/block_based/data_block_hash_index_test.cc
+ table/block_based/full_filter_block_test.cc
+ table/block_based/partitioned_filter_block_test.cc
+ table/cleanable_test.cc
+ table/cuckoo/cuckoo_table_builder_test.cc
+ table/cuckoo/cuckoo_table_reader_test.cc
+ table/merger_test.cc
+ table/sst_file_reader_test.cc
+ table/table_test.cc
+ tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
+ tools/ldb_cmd_test.cc
+ tools/reduce_levels_test.cc
+ tools/sst_dump_test.cc
+ tools/trace_analyzer_test.cc
+ util/autovector_test.cc
+ util/bloom_test.cc
+ util/coding_test.cc
+ util/crc32c_test.cc
+ util/defer_test.cc
+ util/dynamic_bloom_test.cc
+ util/file_reader_writer_test.cc
+ util/filelock_test.cc
+ util/hash_test.cc
+ util/heap_test.cc
+ util/random_test.cc
+ util/rate_limiter_test.cc
+ util/repeatable_thread_test.cc
+ util/slice_test.cc
+ util/slice_transform_test.cc
+ util/timer_queue_test.cc
+ util/thread_list_test.cc
+ util/thread_local_test.cc
+ utilities/backupable/backupable_db_test.cc
+ utilities/blob_db/blob_db_test.cc
+ utilities/cassandra/cassandra_functional_test.cc
+ utilities/cassandra/cassandra_format_test.cc
+ utilities/cassandra/cassandra_row_merge_test.cc
+ utilities/cassandra/cassandra_serialize_test.cc
+ utilities/checkpoint/checkpoint_test.cc
+ utilities/memory/memory_test.cc
+ utilities/merge_operators/string_append/stringappend_test.cc
+ utilities/object_registry_test.cc
+ utilities/option_change_migration/option_change_migration_test.cc
+ utilities/options/options_util_test.cc
+ utilities/persistent_cache/hash_table_test.cc
+ utilities/persistent_cache/persistent_cache_test.cc
+ utilities/simulator_cache/cache_simulator_test.cc
+ utilities/simulator_cache/sim_cache_test.cc
+ utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
+ utilities/transactions/optimistic_transaction_test.cc
+ utilities/transactions/transaction_test.cc
+ utilities/transactions/write_prepared_transaction_test.cc
+ utilities/transactions/write_unprepared_transaction_test.cc
+ utilities/ttl/ttl_test.cc
+ utilities/write_batch_with_index/write_batch_with_index_test.cc
+ )
+ if(WITH_LIBRADOS)
+ list(APPEND TESTS utilities/env_librados_test.cc)
+ endif()
+
+ if(WITH_FOLLY_DISTRIBUTED_MUTEX)
+ list(APPEND TESTS third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp)
+ endif()
+
+ set(TESTUTIL_SOURCE
+ db/db_test_util.cc
+ monitoring/thread_status_updater_debug.cc
+ table/mock_table.cc
+ test_util/fault_injection_test_env.cc
+ utilities/cassandra/test_utils.cc
+ )
+ enable_testing()
+ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
+ set(TESTUTILLIB testutillib${ARTIFACT_SUFFIX})
+ add_library(${TESTUTILLIB} STATIC ${TESTUTIL_SOURCE})
+ target_link_libraries(${TESTUTILLIB} ${ROCKSDB_LIB})
+ if(MSVC)
+ set_target_properties(${TESTUTILLIB} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/testutillib${ARTIFACT_SUFFIX}.pdb")
+ endif()
+ set_target_properties(${TESTUTILLIB}
+ PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
+ EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
+ EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
+ )
+
+ foreach(sourcefile ${TESTS})
+ get_filename_component(exename ${sourcefile} NAME_WE)
+ add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile})
+ set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}
+ PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
+ EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
+ EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
+ OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
+ )
+ target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${ROCKSDB_LIB})
+ if(NOT "${exename}" MATCHES "db_sanity_test")
+ add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
+ add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX})
+ endif()
+ endforeach(sourcefile ${TESTS})
+
+ if(WIN32)
+ # C executables must link to a shared object
+ if(ROCKSDB_BUILD_SHARED)
+ set(ROCKSDB_LIB_FOR_C ${ROCKSDB_SHARED_LIB})
+ else()
+ set(ROCKSDB_LIB_FOR_C OFF)
+ endif()
+ else()
+ set(ROCKSDB_LIB_FOR_C ${ROCKSDB_LIB})
+ endif()
+
+ if(ROCKSDB_LIB_FOR_C)
+ set(C_TESTS db/c_test.c)
+ # C executables must link to a shared object
+ add_executable(c_test db/c_test.c)
+ target_link_libraries(c_test ${ROCKSDB_SHARED_LIB} testharness)
+ add_test(NAME c_test COMMAND c_test${ARTIFACT_SUFFIX})
+ add_dependencies(check c_test)
+ endif()
+endif()
+
+option(WITH_BENCHMARK_TOOLS "build with benchmarks" ON)
+if(WITH_BENCHMARK_TOOLS)
+ add_executable(db_bench
+ tools/db_bench.cc
+ tools/db_bench_tool.cc)
+ target_link_libraries(db_bench
+ ${ROCKSDB_LIB})
+
+ add_executable(cache_bench
+ cache/cache_bench.cc)
+ target_link_libraries(cache_bench
+ ${ROCKSDB_LIB})
+
+ add_executable(memtablerep_bench
+ memtable/memtablerep_bench.cc)
+ target_link_libraries(memtablerep_bench
+ ${ROCKSDB_LIB})
+
+ add_executable(range_del_aggregator_bench
+ db/range_del_aggregator_bench.cc)
+ target_link_libraries(range_del_aggregator_bench
+ ${ROCKSDB_LIB})
+
+ add_executable(table_reader_bench
+ table/table_reader_bench.cc)
+ target_link_libraries(table_reader_bench
+ ${ROCKSDB_LIB} testharness)
+
+ add_executable(filter_bench
+ util/filter_bench.cc)
+ target_link_libraries(filter_bench
+ ${ROCKSDB_LIB})
+
+ add_executable(hash_table_bench
+ utilities/persistent_cache/hash_table_bench.cc)
+ target_link_libraries(hash_table_bench
+ ${ROCKSDB_LIB})
+endif()
+
+option(WITH_TOOLS "build with tools" ON)
+if(WITH_TOOLS)
+ add_subdirectory(tools)
+ add_subdirectory(db_stress_tool)
+ add_custom_target(tools
+ DEPENDS ${tool_deps})
+endif()
diff --git a/storage/rocksdb/rocksdb/CODE_OF_CONDUCT.md b/storage/rocksdb/rocksdb/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..d1abc700
--- /dev/null
+++ b/storage/rocksdb/rocksdb/CODE_OF_CONDUCT.md
@@ -0,0 +1,77 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all project spaces, and it also applies when
+an individual is representing the project or its community in public spaces.
+Examples of representing a project or community include using an official
+project e-mail address, posting via an official social media account, or acting
+as an appointed representative at an online or offline event. Representation of
+a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at <opensource-conduct@fb.com>. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
+
diff --git a/storage/rocksdb/rocksdb/CONTRIBUTING.md b/storage/rocksdb/rocksdb/CONTRIBUTING.md
new file mode 100644
index 00000000..190100b4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+# Contributing to RocksDB
+
+## Code of Conduct
+The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md)
+
+## Contributor License Agreement ("CLA")
+
+In order to accept your pull request, we need you to submit a CLA. You
+only need to do this once, so if you've done this for another Facebook
+open source project, you're good to go. If you are submitting a pull
+request for the first time, just let us know that you have completed
+the CLA and we can cross-check with your GitHub username.
+
+Complete your CLA here: <https://code.facebook.com/cla>
+
+If you prefer to sign a paper copy, we can send you a PDF. Send us an
+e-mail or create a new github issue to request the CLA in PDF format.
diff --git a/storage/rocksdb/rocksdb/COPYING b/storage/rocksdb/rocksdb/COPYING
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/storage/rocksdb/rocksdb/DEFAULT_OPTIONS_HISTORY.md b/storage/rocksdb/rocksdb/DEFAULT_OPTIONS_HISTORY.md
new file mode 100644
index 00000000..26280ee3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/DEFAULT_OPTIONS_HISTORY.md
@@ -0,0 +1,24 @@
+# RocksDB default options change log
+## Unreleased
+* delayed_write_rate takes the rate given by rate_limiter if not specified.
+
+## 5.2
+* Change the default of delayed slowdown value to 16MB/s and further increase the L0 stop condition to 36 files.
+
+## 5.0 (11/17/2016)
+* Options::allow_concurrent_memtable_write and Options::enable_write_thread_adaptive_yield are now true by default
+* Options.level0_stop_writes_trigger default value changes from 24 to 32.
+
+## 4.8.0 (5/2/2016)
+* options.max_open_files changes from 5000 to -1. It improves performance, but users need to set file descriptor limit to be large enough and watch memory usage for index and bloom filters.
+* options.base_background_compactions changes from max_background_compactions to 1. When users set higher max_background_compactions but the write throughput is not high, the writes are less spiky to disks.
+* options.wal_recovery_mode changes from kTolerateCorruptedTailRecords to kPointInTimeRecovery. Avoid some false positive when file system or hardware reorder the writes for file data and metadata.
+
+## 4.7.0 (4/8/2016)
+* options.write_buffer_size changes from 4MB to 64MB.
+* options.target_file_size_base changes from 2MB to 64MB.
+* options.max_bytes_for_level_base changes from 10MB to 256MB.
+* options.soft_pending_compaction_bytes_limit changes from 0 (disabled) to 64GB.
+* options.hard_pending_compaction_bytes_limit changes from 0 (disabled) to 256GB.
+* table_cache_numshardbits changes from 4 to 6.
+* max_file_opening_threads changes from 1 to 16.
diff --git a/storage/rocksdb/rocksdb/DUMP_FORMAT.md b/storage/rocksdb/rocksdb/DUMP_FORMAT.md
new file mode 100644
index 00000000..009dabad
--- /dev/null
+++ b/storage/rocksdb/rocksdb/DUMP_FORMAT.md
@@ -0,0 +1,16 @@
+## RocksDB dump format
+
+The version 1 RocksDB dump format is fairly simple:
+
+1) The dump starts with the magic 8 byte identifier "ROCKDUMP"
+
+2) The magic is followed by an 8 byte big-endian version which is 0x00000001.
+
+3) Next are arbitrarily sized chunks of bytes prepended by 4 byte little endian number indicating how large each chunk is.
+
+4) The first chunk is special and is a json string indicating some things about the creation of this dump. It contains the following keys:
+* database-path: The path of the database this dump was created from.
+* hostname: The hostname of the machine where the dump was created.
+* creation-time: Unix seconds since epoc when this dump was created.
+
+5) Following the info dump the slices paired into are key/value pairs.
diff --git a/storage/rocksdb/rocksdb/HISTORY.md b/storage/rocksdb/rocksdb/HISTORY.md
new file mode 100644
index 00000000..1279035e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/HISTORY.md
@@ -0,0 +1,1137 @@
+# Rocksdb Change Log
+## Unreleased
+### Bug Fixes
+* Fix a data race that might cause crash when calling DB::GetCreationTimeOfOldestFile() by a small chance. The bug was introduced in 6.6 Release.
+
+## 6.8.0 (02/24/2020)
+### Java API Changes
+* Major breaking changes to Java comparators, toward standardizing on ByteBuffer for performant, locale-neutral operations on keys (#6252).
+* Added overloads of common API methods using direct ByteBuffers for keys and values (#2283).
+
+### Bug Fixes
+* Fix incorrect results while block-based table uses kHashSearch, together with Prev()/SeekForPrev().
+* Fix a bug that prevents opening a DB after two consecutive crash with TransactionDB, where the first crash recovers from a corrupted WAL with kPointInTimeRecovery but the second cannot.
+* Fixed issue #6316 that can cause a corruption of the MANIFEST file in the middle when writing to it fails due to no disk space.
+* Add DBOptions::skip_checking_sst_file_sizes_on_db_open. It disables potentially expensive checking of all sst file sizes in DB::Open().
+* BlobDB now ignores trivially moved files when updating the mapping between blob files and SSTs. This should mitigate issue #6338 where out of order flush/compaction notifications could trigger an assertion with the earlier code.
+* Batched MultiGet() ignores IO errors while reading data blocks, causing it to potentially continue looking for a key and returning stale results.
+* `WriteBatchWithIndex::DeleteRange` returns `Status::NotSupported`. Previously it returned success even though reads on the batch did not account for range tombstones. The corresponding language bindings now cannot be used. In C, that includes `rocksdb_writebatch_wi_delete_range`, `rocksdb_writebatch_wi_delete_range_cf`, `rocksdb_writebatch_wi_delete_rangev`, and `rocksdb_writebatch_wi_delete_rangev_cf`. In Java, that includes `WriteBatchWithIndex::deleteRange`.
+* Assign new MANIFEST file number when caller tries to create a new MANIFEST by calling LogAndApply(..., new_descriptor_log=true). This bug can cause MANIFEST being overwritten during recovery if options.write_dbid_to_manifest = true and there are WAL file(s).
+
+### Performance Improvements
+* Perfom readahead when reading from option files. Inside DB, options.log_readahead_size will be used as the readahead size. In other cases, a default 512KB is used.
+
+### Public API Change
+* The BlobDB garbage collector now emits the statistics `BLOB_DB_GC_NUM_FILES` (number of blob files obsoleted during GC), `BLOB_DB_GC_NUM_NEW_FILES` (number of new blob files generated during GC), `BLOB_DB_GC_FAILURES` (number of failed GC passes), `BLOB_DB_GC_NUM_KEYS_RELOCATED` (number of blobs relocated during GC), and `BLOB_DB_GC_BYTES_RELOCATED` (total size of blobs relocated during GC). On the other hand, the following statistics, which are not relevant for the new GC implementation, are now deprecated: `BLOB_DB_GC_NUM_KEYS_OVERWRITTEN`, `BLOB_DB_GC_NUM_KEYS_EXPIRED`, `BLOB_DB_GC_BYTES_OVERWRITTEN`, `BLOB_DB_GC_BYTES_EXPIRED`, and `BLOB_DB_GC_MICROS`.
+* Disable recycle_log_file_num when an inconsistent recovery modes are requested: kPointInTimeRecovery and kAbsoluteConsistency
+
+### New Features
+* Added the checksum for each SST file generated by Flush or Compaction. Added sst_file_checksum_func to Options such that user can plugin their own SST file checksum function via override the FileChecksumFunc class. If user does not set the sst_file_checksum_func, SST file checksum calculation will not be enabled. The checksum information inlcuding uint32_t checksum value and a checksum function name (string). The checksum information is stored in FileMetadata in version store and also logged to MANIFEST. A new tool is added to LDB such that user can dump out a list of file checksum information from MANIFEST (stored in an unordered_map).
+* `db_bench` now supports `value_size_distribution_type`, `value_size_min`, `value_size_max` options for generating random variable sized value. Added `blob_db_compression_type` option for BlobDB to enable blob compression.
+* Replace RocksDB namespace "rocksdb" with flag "ROCKSDB_NAMESPACE" which if is not defined, defined as "rocksdb" in header file rocksdb_namespace.h.
+
+## 6.7.0 (01/21/2020)
+### Public API Change
+* Added a rocksdb::FileSystem class in include/rocksdb/file_system.h to encapsulate file creation/read/write operations, and an option DBOptions::file_system to allow a user to pass in an instance of rocksdb::FileSystem. If its a non-null value, this will take precendence over DBOptions::env for file operations. A new API rocksdb::FileSystem::Default() returns a platform default object. The DBOptions::env option and Env::Default() API will continue to be used for threading and other OS related functions, and where DBOptions::file_system is not specified, for file operations. For storage developers who are accustomed to rocksdb::Env, the interface in rocksdb::FileSystem is new and will probably undergo some changes as more storage systems are ported to it from rocksdb::Env. As of now, no env other than Posix has been ported to the new interface.
+* A new rocksdb::NewSstFileManager() API that allows the caller to pass in separate Env and FileSystem objects.
+* Changed Java API for RocksDB.keyMayExist functions to use Holder<byte[]> instead of StringBuilder, so that retrieved values need not decode to Strings.
+* A new `OptimisticTransactionDBOptions` Option that allows users to configure occ validation policy. The default policy changes from kValidateSerial to kValidateParallel to reduce mutex contention.
+
+### Bug Fixes
+* Fix a bug that can cause unnecessary bg thread to be scheduled(#6104).
+* Fix crash caused by concurrent CF iterations and drops(#6147).
+* Fix a race condition for cfd->log_number_ between manifest switch and memtable switch (PR 6249) when number of column families is greater than 1.
+* Fix a bug on fractional cascading index when multiple files at the same level contain the same smallest user key, and those user keys are for merge operands. In this case, Get() the exact key may miss some merge operands.
+* Delcare kHashSearch index type feature-incompatible with index_block_restart_interval larger than 1.
+* Fixed an issue where the thread pools were not resized upon setting `max_background_jobs` dynamically through the `SetDBOptions` interface.
+* Fix a bug that can cause write threads to hang when a slowdown/stall happens and there is a mix of writers with WriteOptions::no_slowdown set/unset.
+* Fixed an issue where an incorrect "number of input records" value was used to compute the "records dropped" statistics for compactions.
+* Fix a regression bug that causes segfault when hash is used, max_open_files != -1 and total order seek is used and switched back.
+
+### New Features
+* It is now possible to enable periodic compactions for the base DB when using BlobDB.
+* BlobDB now garbage collects non-TTL blobs when `enable_garbage_collection` is set to `true` in `BlobDBOptions`. Garbage collection is performed during compaction: any valid blobs located in the oldest N files (where N is the number of non-TTL blob files multiplied by the value of `BlobDBOptions::garbage_collection_cutoff`) encountered during compaction get relocated to new blob files, and old blob files are dropped once they are no longer needed. Note: we recommend enabling periodic compactions for the base DB when using this feature to deal with the case when some old blob files are kept alive by SSTs that otherwise do not get picked for compaction.
+* `db_bench` now supports the `garbage_collection_cutoff` option for BlobDB.
+* Introduce ReadOptions.auto_prefix_mode. When set to true, iterator will return the same result as total order seek, but may choose to use prefix seek internally based on seek key and iterator upper bound.
+* MultiGet() can use IO Uring to parallelize read from the same SST file. This featuer is by default disabled. It can be enabled with environment variable ROCKSDB_USE_IO_URING.
+
+## 6.6.2 (01/13/2020)
+### Bug Fixes
+* Fixed a bug where non-L0 compaction input files were not considered to compute the `creation_time` of new compaction outputs.
+
+## 6.6.1 (01/02/2020)
+### Bug Fixes
+* Fix a bug in WriteBatchWithIndex::MultiGetFromBatchAndDB, which is called by Transaction::MultiGet, that causes due to stale pointer access when the number of keys is > 32
+* Fixed two performance issues related to memtable history trimming. First, a new SuperVersion is now created only if some memtables were actually trimmed. Second, trimming is only scheduled if there is at least one flushed memtable that is kept in memory for the purposes of transaction conflict checking.
+* BlobDB no longer updates the SST to blob file mapping upon failed compactions.
+* Fix a bug in which a snapshot read through an iterator could be affected by a DeleteRange after the snapshot (#6062).
+* Fixed a bug where BlobDB was comparing the `ColumnFamilyHandle` pointers themselves instead of only the column family IDs when checking whether an API call uses the default column family or not.
+* Delete superversions in BackgroundCallPurge.
+* Fix use-after-free and double-deleting files in BackgroundCallPurge().
+
+## 6.6.0 (11/25/2019)
+### Bug Fixes
+* Fix data corruption caused by output of intra-L0 compaction on ingested file not being placed in correct order in L0.
+* Fix a data race between Version::GetColumnFamilyMetaData() and Compaction::MarkFilesBeingCompacted() for access to being_compacted (#6056). The current fix acquires the db mutex during Version::GetColumnFamilyMetaData(), which may cause regression.
+* Fix a bug in DBIter that is_blob_ state isn't updated when iterating backward using seek.
+* Fix a bug when format_version=3, partitioned filters, and prefix search are used in conjunction. The bug could result into Seek::(prefix) returning NotFound for an existing prefix.
+* Revert the feature "Merging iterator to avoid child iterator reseek for some cases (#5286)" since it might cause strong results when reseek happens with a different iterator upper bound.
+* Fix a bug causing a crash during ingest external file when background compaction cause severe error (file not found).
+* Fix a bug when partitioned filters and prefix search are used in conjunction, ::SeekForPrev could return invalid for an existing prefix. ::SeekForPrev might be called by the user, or internally on ::Prev, or within ::Seek if the return value involves Delete or a Merge operand.
+* Fix OnFlushCompleted fired before flush result persisted in MANIFEST when there's concurrent flush job. The bug exists since OnFlushCompleted was introduced in rocksdb 3.8.
+* Fixed an sst_dump crash on some plain table SST files.
+* Fixed a memory leak in some error cases of opening plain table SST files.
+* Fix a bug when a crash happens while calling WriteLevel0TableForRecovery for multiple column families, leading to a column family's log number greater than the first corrutped log number when the DB is being opened in PointInTime recovery mode during next recovery attempt (#5856).
+
+### New Features
+* Universal compaction to support options.periodic_compaction_seconds. A full compaction will be triggered if any file is over the threshold.
+* `GetLiveFilesMetaData` and `GetColumnFamilyMetaData` now expose the file number of SST files as well as the oldest blob file referenced by each SST.
+* A batched MultiGet API (DB::MultiGet()) that supports retrieving keys from multiple column families.
+* Full and partitioned filters in the block-based table use an improved Bloom filter implementation, enabled with format_version 5 (or above) because previous releases cannot read this filter. This replacement is faster and more accurate, especially for high bits per key or millions of keys in a single (full) filter. For example, the new Bloom filter has the same false positive rate at 9.55 bits per key as the old one at 10 bits per key, and a lower false positive rate at 16 bits per key than the old one at 100 bits per key.
+* Added AVX2 instructions to USE_SSE builds to accelerate the new Bloom filter and XXH3-based hash function on compatible x86_64 platforms (Haswell and later, ~2014).
+* Support options.ttl or options.periodic_compaction_seconds with options.max_open_files = -1. File's oldest ancester time and file creation time will be written to manifest. If it is availalbe, this information will be used instead of creation_time and file_creation_time in table properties.
+* Setting options.ttl for universal compaction now has the same meaning as setting periodic_compaction_seconds.
+* SstFileMetaData also returns file creation time and oldest ancester time.
+* The `sst_dump` command line tool `recompress` command now displays how many blocks were compressed and how many were not, in particular how many were not compressed because the compression ratio was not met (12.5% threshold for GoodCompressionRatio), as seen in the `number.block.not_compressed` counter stat since version 6.0.0.
+* The block cache usage is now takes into account the overhead of metadata per each entry. This results into more accurate management of memory. A side-effect of this feature is that less items are fit into the block cache of the same size, which would result to higher cache miss rates. This can be remedied by increasing the block cache size or passing kDontChargeCacheMetadata to its constuctor to restore the old behavior.
+* When using BlobDB, a mapping is maintained and persisted in the MANIFEST between each SST file and the oldest non-TTL blob file it references.
+* `db_bench` now supports and by default issues non-TTL Puts to BlobDB. TTL Puts can be enabled by specifying a non-zero value for the `blob_db_max_ttl_range` command line parameter explicitly.
+* `sst_dump` now supports printing BlobDB blob indexes in a human-readable format. This can be enabled by specifying the `decode_blob_index` flag on the command line.
+* A number of new information elements are now exposed through the EventListener interface. For flushes, the file numbers of the new SST file and the oldest blob file referenced by the SST are propagated. For compactions, the level, file number, and the oldest blob file referenced are passed to the client for each compaction input and output file.
+
+### Public API Change
+* RocksDB release 4.1 or older will not be able to open DB generated by the new release. 4.2 was released on Feb 23, 2016.
+* TTL Compactions in Level compaction style now initiate successive cascading compactions on a key range so that it reaches the bottom level quickly on TTL expiry. `creation_time` table property for compaction output files is now set to the minimum of the creation times of all compaction inputs.
+* With FIFO compaction style, options.periodic_compaction_seconds will have the same meaning as options.ttl. Whichever stricter will be used. With the default options.periodic_compaction_seconds value with options.ttl's default of 0, RocksDB will give a default of 30 days.
+* Added an API GetCreationTimeOfOldestFile(uint64_t* creation_time) to get the file_creation_time of the oldest SST file in the DB.
+* FilterPolicy now exposes additional API to make it possible to choose filter configurations based on context, such as table level and compaction style. See `LevelAndStyleCustomFilterPolicy` in db_bloom_filter_test.cc. While most existing custom implementations of FilterPolicy should continue to work as before, those wrapping the return of NewBloomFilterPolicy will require overriding new function `GetBuilderWithContext()`, because calling `GetFilterBitsBuilder()` on the FilterPolicy returned by NewBloomFilterPolicy is no longer supported.
+* An unlikely usage of FilterPolicy is no longer supported. Calling GetFilterBitsBuilder() on the FilterPolicy returned by NewBloomFilterPolicy will now cause an assertion violation in debug builds, because RocksDB has internally migrated to a more elaborate interface that is expected to evolve further. Custom implementations of FilterPolicy should work as before, except those wrapping the return of NewBloomFilterPolicy, which will require a new override of a protected function in FilterPolicy.
+* NewBloomFilterPolicy now takes bits_per_key as a double instead of an int. This permits finer control over the memory vs. accuracy trade-off in the new Bloom filter implementation and should not change source code compatibility.
+* The option BackupableDBOptions::max_valid_backups_to_open is now only used when opening BackupEngineReadOnly. When opening a read/write BackupEngine, anything but the default value logs a warning and is treated as the default. This change ensures that backup deletion has proper accounting of shared files to ensure they are deleted when no longer referenced by a backup.
+* Deprecate `snap_refresh_nanos` option.
+* Added DisableManualCompaction/EnableManualCompaction to stop and resume manual compaction.
+* Add TryCatchUpWithPrimary() to StackableDB in non-LITE mode.
+* Add a new Env::LoadEnv() overloaded function to return a shared_ptr to Env.
+* Flush sets file name to "(nil)" for OnTableFileCreationCompleted() if the flush does not produce any L0. This can happen if the file is empty thus delete by RocksDB.
+
+### Default Option Changes
+* Changed the default value of periodic_compaction_seconds to `UINT64_MAX - 1` which allows RocksDB to auto-tune periodic compaction scheduling. When using the default value, periodic compactions are now auto-enabled if a compaction filter is used. A value of `0` will turn off the feature completely.
+* Changed the default value of ttl to `UINT64_MAX - 1` which allows RocksDB to auto-tune ttl value. When using the default value, TTL will be auto-enabled to 30 days, when the feature is supported. To revert the old behavior, you can explicitly set it to 0.
+
+### Performance Improvements
+* For 64-bit hashing, RocksDB is standardizing on a slightly modified preview version of XXH3. This function is now used for many non-persisted hashes, along with fastrange64() in place of the modulus operator, and some benchmarks show a slight improvement.
+* Level iterator to invlidate the iterator more often in prefix seek and the level is filtered out by prefix bloom.
+
+## 6.5.2 (11/15/2019)
+### Bug Fixes
+* Fix a assertion failure in MultiGet() when BlockBasedTableOptions::no_block_cache is true and there is no compressed block cache
+* Fix a buffer overrun problem in BlockBasedTable::MultiGet() when compression is enabled and no compressed block cache is configured.
+* If a call to BackupEngine::PurgeOldBackups or BackupEngine::DeleteBackup suffered a crash, power failure, or I/O error, files could be left over from old backups that could only be purged with a call to GarbageCollect. Any call to PurgeOldBackups, DeleteBackup, or GarbageCollect should now suffice to purge such files.
+
+## 6.5.1 (10/16/2019)
+### Bug Fixes
+* Revert the feature "Merging iterator to avoid child iterator reseek for some cases (#5286)" since it might cause strange results when reseek happens with a different iterator upper bound.
+* Fix a bug in BlockBasedTableIterator that might return incorrect results when reseek happens with a different iterator upper bound.
+* Fix a bug when partitioned filters and prefix search are used in conjunction, ::SeekForPrev could return invalid for an existing prefix. ::SeekForPrev might be called by the user, or internally on ::Prev, or within ::Seek if the return value involves Delete or a Merge operand.
+
+## 6.5.0 (9/13/2019)
+### Bug Fixes
+* Fixed a number of data races in BlobDB.
+* Fix a bug where the compaction snapshot refresh feature is not disabled as advertised when `snap_refresh_nanos` is set to 0..
+* Fix bloom filter lookups by the MultiGet batching API when BlockBasedTableOptions::whole_key_filtering is false, by checking that a key is in the perfix_extractor domain and extracting the prefix before looking up.
+* Fix a bug in file ingestion caused by incorrect file number allocation when the number of column families involved in the ingestion exceeds 2.
+
+### New Features
+* Introduced DBOptions::max_write_batch_group_size_bytes to configure maximum limit on 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.
+* VerifyChecksum() by default will issue readahead. Allow ReadOptions to be passed in to those functions to override the readhead size. For checksum verifying before external SST file ingestion, a new option IngestExternalFileOptions.verify_checksums_readahead_size, is added for this readahead setting.
+* When user uses options.force_consistency_check in RocksDb, instead of crashing the process, we now pass the error back to the users without killing the process.
+* Add an option `memtable_insert_hint_per_batch` to WriteOptions. If it is true, each WriteBatch will maintain its own insert hints for each memtable in concurrent write. See include/rocksdb/options.h for more details.
+
+### Public API Change
+* Added max_write_buffer_size_to_maintain option to better control memory usage of immutable memtables.
+* Added a lightweight API GetCurrentWalFile() to get last live WAL filename and size. Meant to be used as a helper for backup/restore tooling in a larger ecosystem such as MySQL with a MyRocks storage engine.
+* The MemTable Bloom filter, when enabled, now always uses cache locality. Options::bloom_locality now only affects the PlainTable SST format.
+
+### Performance Improvements
+* Improve the speed of the MemTable Bloom filter, reducing the write overhead of enabling it by 1/3 to 1/2, with similar benefit to read performance.
+
+## 6.4.0 (7/30/2019)
+### Default Option Change
+* LRUCacheOptions.high_pri_pool_ratio is set to 0.5 (previously 0.0) by default, which means that by default midpoint insertion is enabled. The same change is made for the default value of high_pri_pool_ratio argument in NewLRUCache(). When block cache is not explicitly created, the small block cache created by BlockBasedTable will still has this option to be 0.0.
+* Change BlockBasedTableOptions.cache_index_and_filter_blocks_with_high_priority's default value from false to true.
+
+### Public API Change
+* Filter and compression dictionary blocks are now handled similarly to data blocks with regards to the block cache: instead of storing objects in the cache, only the blocks themselves are cached. In addition, filter and compression dictionary blocks (as well as filter partitions) no longer get evicted from the cache when a table is closed.
+* Due to the above refactoring, block cache eviction statistics for filter and compression dictionary blocks are temporarily broken. We plan to reintroduce them in a later phase.
+* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
+* Errors related to the retrieval of the compression dictionary are now propagated to the user.
+* db_bench adds a "benchmark" stats_history, which prints out the whole stats history.
+* Overload GetAllKeyVersions() to support non-default column family.
+* Added new APIs ExportColumnFamily() and CreateColumnFamilyWithImport() to support export and import of a Column Family. https://github.com/facebook/rocksdb/issues/3469
+* ldb sometimes uses a string-append merge operator if no merge operator is passed in. This is to allow users to print keys from a DB with a merge operator.
+* Replaces old Registra with ObjectRegistry to allow user to create custom object from string, also add LoadEnv() to Env.
+* Added new overload of GetApproximateSizes which gets SizeApproximationOptions object and returns a Status. The older overloads are redirecting their calls to this new method and no longer assert if the include_flags doesn't have either of INCLUDE_MEMTABLES or INCLUDE_FILES bits set. It's recommended to use the new method only, as it is more type safe and returns a meaningful status in case of errors.
+* LDBCommandRunner::RunCommand() to return the status code as an integer, rather than call exit() using the code.
+
+### New Features
+* Add argument `--secondary_path` to ldb to open the database as the secondary instance. This would keep the original DB intact.
+* Compression dictionary blocks are now prefetched and pinned in the cache (based on the customer's settings) the same way as index and filter blocks.
+* Added DBOptions::log_readahead_size which specifies 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 (default), then the prefetching is disabled.
+* Added new option in SizeApproximationOptions used with DB::GetApproximateSizes. When approximating the files total size that is used to store a keys range, allow approximation with an error margin of up to total_files_size * files_size_error_margin. This allows to take some shortcuts in files size approximation, resulting in better performance, while guaranteeing the resulting error is within a reasonable margin.
+* Support loading custom objects in unit tests. In the affected unit tests, RocksDB will create custom Env objects based on environment variable TEST_ENV_URI. Users need to make sure custom object types are properly registered. For example, a static library should expose a `RegisterCustomObjects` function. By linking the unit test binary with the static library, the unit test can execute this function.
+
+### Performance Improvements
+* Reduce iterator key comparison for upper/lower bound check.
+* Improve performance of row_cache: make reads with newer snapshots than data in an SST file share the same cache key, except in some transaction cases.
+* The compression dictionary is no longer copied to a new object upon retrieval.
+
+### Bug Fixes
+* Fix ingested file and directory not being fsync.
+* Return TryAgain status in place of Corruption when new tail is not visible to TransactionLogIterator.
+* Fixed a regression where the fill_cache read option also affected index blocks.
+* Fixed an issue where using cache_index_and_filter_blocks==false affected partitions of partitioned indexes/filters as well.
+
+## 6.3.2 (8/15/2019)
+### Public API Change
+* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
+
+### Bug Fixes
+* Fixed a regression where the fill_cache read option also affected index blocks.
+* Fixed an issue where using cache_index_and_filter_blocks==false affected partitions of partitioned indexes as well.
+
+## 6.3.1 (7/24/2019)
+### Bug Fixes
+* Fix auto rolling bug introduced in 6.3.0, which causes segfault if log file creation fails.
+
+## 6.3.0 (6/18/2019)
+### Public API Change
+* Now DB::Close() will return Aborted() error when there is unreleased snapshot. Users can retry after all snapshots are released.
+* Index blocks are now handled similarly to data blocks with regards to the block cache: instead of storing objects in the cache, only the blocks themselves are cached. In addition, index blocks no longer get evicted from the cache when a table is closed, can now use the compressed block cache (if any), and can be shared among multiple table readers.
+* Partitions of partitioned indexes no longer affect the read amplification statistics.
+* Due to the above refactoring, block cache eviction statistics for indexes are temporarily broken. We plan to reintroduce them in a later phase.
+* options.keep_log_file_num will be enforced strictly all the time. File names of all log files will be tracked, which may take significantly amount of memory if options.keep_log_file_num is large and either of options.max_log_file_size or options.log_file_time_to_roll is set.
+* Add initial support for Get/Put with user timestamps. Users can specify timestamps via ReadOptions and WriteOptions when calling DB::Get and DB::Put.
+* Accessing a partition of a partitioned filter or index through a pinned reference is no longer considered a cache hit.
+* Add C bindings for secondary instance, i.e. DBImplSecondary.
+* Rate limited deletion of WALs is only enabled if DBOptions::wal_dir is not set, or explicitly set to db_name passed to DB::Open and DBOptions::db_paths is empty, or same as db_paths[0].path
+
+### New Features
+* Add an option `snap_refresh_nanos` (default to 0) to periodically refresh the snapshot list in compaction jobs. Assign to 0 to disable the feature.
+* Add an option `unordered_write` which trades snapshot guarantees with higher write throughput. When used with WRITE_PREPARED transactions with two_write_queues=true, it offers higher throughput with however no compromise on guarantees.
+* Allow DBImplSecondary to remove memtables with obsolete data after replaying MANIFEST and WAL.
+* Add an option `failed_move_fall_back_to_copy` (default is true) for external SST ingestion. When `move_files` is true and hard link fails, ingestion falls back to copy if `failed_move_fall_back_to_copy` is true. Otherwise, ingestion reports an error.
+* Add command `list_file_range_deletes` in ldb, which prints out tombstones in SST files.
+
+### Performance Improvements
+* Reduce binary search when iterator reseek into the same data block.
+* DBIter::Next() can skip user key checking if previous entry's seqnum is 0.
+* Merging iterator to avoid child iterator reseek for some cases
+* Log Writer will flush after finishing the whole record, rather than a fragment.
+* Lower MultiGet batching API latency by reading data blocks from disk in parallel
+
+### General Improvements
+* Added new status code kColumnFamilyDropped to distinguish between Column Family Dropped and DB Shutdown in progress.
+* Improve ColumnFamilyOptions validation when creating a new column family.
+
+### Bug Fixes
+* Fix a bug in WAL replay of secondary instance by skipping write batches with older sequence numbers than the current last sequence number.
+* Fix flush's/compaction's merge processing logic which allowed `Put`s covered by range tombstones to reappear. Note `Put`s may exist even if the user only ever called `Merge()` due to an internal conversion during compaction to the bottommost level.
+* Fix/improve memtable earliest sequence assignment and WAL replay so that WAL entries of unflushed column families will not be skipped after replaying the MANIFEST and increasing db sequence due to another flushed/compacted column family.
+* Fix a bug caused by secondary not skipping the beginning of new MANIFEST.
+* On DB open, delete WAL trash files left behind in wal_dir
+
+## 6.2.0 (4/30/2019)
+### New Features
+* Add an option `strict_bytes_per_sync` that causes a file-writing thread to block rather than exceed the limit on bytes pending writeback specified by `bytes_per_sync` or `wal_bytes_per_sync`.
+* Improve range scan performance by avoiding per-key upper bound check in BlockBasedTableIterator.
+* Introduce Periodic Compaction for Level style compaction. Files are re-compacted periodically and put in the same level.
+* Block-based table index now contains exact highest key in the file, rather than an upper bound. This may improve Get() and iterator Seek() performance in some situations, especially when direct IO is enabled and block cache is disabled. A setting BlockBasedTableOptions::index_shortening is introduced to control this behavior. Set it to kShortenSeparatorsAndSuccessor to get the old behavior.
+* When reading from option file/string/map, customized envs can be filled according to object registry.
+* Improve range scan performance when using explicit user readahead by not creating new table readers for every iterator.
+* Add index type BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey. It significantly reduces read amplification in some setups, especially for iterator seeks. It's not fully implemented yet: IO errors are not handled right.
+
+### Public API Change
+* Change the behavior of OptimizeForPointLookup(): move away from hash-based block-based-table index, and use whole key memtable filtering.
+* Change the behavior of OptimizeForSmallDb(): use a 16MB block cache, put index and filter blocks into it, and cost the memtable size to it. DBOptions.OptimizeForSmallDb() and ColumnFamilyOptions.OptimizeForSmallDb() start to take an optional cache object.
+* Added BottommostLevelCompaction::kForceOptimized to avoid double compacting newly compacted files in the bottommost level compaction of manual compaction. Note this option may prohibit the manual compaction to produce a single file in the bottommost level.
+
+### Bug Fixes
+* Adjust WriteBufferManager's dummy entry size to block cache from 1MB to 256KB.
+* Fix a race condition between WritePrepared::Get and ::Put with duplicate keys.
+* Fix crash when memtable prefix bloom is enabled and read/write a key out of domain of prefix extractor.
+* Close a WAL file before another thread deletes it.
+* Fix an assertion failure `IsFlushPending() == true` caused by one bg thread releasing the db mutex in ~ColumnFamilyData and another thread clearing `flush_requested_` flag.
+
+## 6.1.1 (4/9/2019)
+### New Features
+* When reading from option file/string/map, customized comparators and/or merge operators can be filled according to object registry.
+
+### Public API Change
+
+### Bug Fixes
+* Fix a bug in 2PC where a sequence of txn prepare, memtable flush, and crash could result in losing the prepared transaction.
+* Fix a bug in Encryption Env which could cause encrypted files to be read beyond file boundaries.
+
+## 6.1.0 (3/27/2019)
+### New Features
+* Introduce two more stats levels, kExceptHistogramOrTimers and kExceptTimers.
+* Added a feature to perform data-block sampling for compressibility, and report stats to user.
+* Add support for trace filtering.
+* Add DBOptions.avoid_unnecessary_blocking_io. If true, we avoid file deletion when destroying ColumnFamilyHandle and Iterator. Instead, a job is scheduled to delete the files in background.
+
+### Public API Change
+* Remove bundled fbson library.
+* statistics.stats_level_ becomes atomic. It is preferred to use statistics.set_stats_level() and statistics.get_stats_level() to access it.
+* Introduce a new IOError subcode, PathNotFound, to indicate trying to open a nonexistent file or directory for read.
+* Add initial support for multiple db instances sharing the same data in single-writer, multi-reader mode.
+* Removed some "using std::xxx" from public headers.
+
+### Bug Fixes
+* Fix JEMALLOC_CXX_THROW macro missing from older Jemalloc versions, causing build failures on some platforms.
+* Fix SstFileReader not able to open file ingested with write_glbal_seqno=true.
+
+## 6.0.0 (2/19/2019)
+### New Features
+* Enabled checkpoint on readonly db (DBImplReadOnly).
+* Make DB ignore dropped column families while committing results of atomic flush.
+* RocksDB may choose to preopen some files even if options.max_open_files != -1. This may make DB open slightly longer.
+* For users of dictionary compression with ZSTD v0.7.0+, we now reuse the same digested dictionary when compressing each of an SST file's data blocks for faster compression speeds.
+* For all users of dictionary compression who set `cache_index_and_filter_blocks == true`, we now store dictionary data used for decompression in the block cache for better control over memory usage. For users of ZSTD v1.1.4+ who compile with -DZSTD_STATIC_LINKING_ONLY, this includes a digested dictionary, which is used to increase decompression speed.
+* Add support for block checksums verification for external SST files before ingestion.
+* Introduce stats history which periodically saves Statistics snapshots and added `GetStatsHistory` API to retrieve these snapshots.
+* Add a place holder in manifest which indicate a record from future that can be safely ignored.
+* Add support for trace sampling.
+* Enable properties block checksum verification for block-based tables.
+* For all users of dictionary compression, we now generate a separate dictionary for compressing each bottom-level SST file. Previously we reused a single dictionary for a whole compaction to bottom level. The new approach achieves better compression ratios; however, it uses more memory and CPU for buffering/sampling data blocks and training dictionaries.
+* Add whole key bloom filter support in memtable.
+* Files written by `SstFileWriter` will now use dictionary compression if it is configured in the file writer's `CompressionOptions`.
+
+### Public API Change
+* Disallow CompactionFilter::IgnoreSnapshots() = false, because it is not very useful and the behavior is confusing. The filter will filter everything if there is no snapshot declared by the time the compaction starts. However, users can define a snapshot after the compaction starts and before it finishes and this new snapshot won't be repeatable, because after the compaction finishes, some keys may be dropped.
+* CompactionPri = kMinOverlappingRatio also uses compensated file size, which boosts file with lots of tombstones to be compacted first.
+* Transaction::GetForUpdate is extended with a do_validate parameter with default value of true. If false it skips validating the snapshot before doing the read. Similarly ::Merge, ::Put, ::Delete, and ::SingleDelete are extended with assume_tracked with default value of false. If true it indicates that call is assumed to be after a ::GetForUpdate.
+* `TableProperties::num_entries` and `TableProperties::num_deletions` now also account for number of range tombstones.
+* Remove geodb, spatial_db, document_db, json_document, date_tiered_db, and redis_lists.
+* With "ldb ----try_load_options", when wal_dir specified by the option file doesn't exist, ignore it.
+* Change time resolution in FileOperationInfo.
+* Deleting Blob files also go through SStFileManager.
+* Remove CuckooHash memtable.
+* The counter stat `number.block.not_compressed` now also counts blocks not compressed due to poor compression ratio.
+* Remove ttl option from `CompactionOptionsFIFO`. The option has been deprecated and ttl in `ColumnFamilyOptions` is used instead.
+* Support SST file ingestion across multiple column families via DB::IngestExternalFiles. See the function's comment about atomicity.
+* Remove Lua compaction filter.
+
+### Bug Fixes
+* Fix a deadlock caused by compaction and file ingestion waiting for each other in the event of write stalls.
+* Fix a memory leak when files with range tombstones are read in mmap mode and block cache is enabled
+* Fix handling of corrupt range tombstone blocks such that corruptions cannot cause deleted keys to reappear
+* Lock free MultiGet
+* Fix incorrect `NotFound` point lookup result when querying the endpoint of a file that has been extended by a range tombstone.
+* Fix with pipelined write, write leaders's callback failure lead to the whole write group fail.
+
+### Change Default Options
+* Change options.compaction_pri's default to kMinOverlappingRatio
+
+## 5.18.0 (11/30/2018)
+### New Features
+* Introduced `JemallocNodumpAllocator` memory allocator. When being use, block cache will be excluded from core dump.
+* Introduced `PerfContextByLevel` as part of `PerfContext` which allows storing perf context at each level. Also replaced `__thread` with `thread_local` keyword for perf_context. Added per-level perf context for bloom filter and `Get` query.
+* With level_compaction_dynamic_level_bytes = true, level multiplier may be adjusted automatically when Level 0 to 1 compaction is lagged behind.
+* Introduced DB option `atomic_flush`. If true, RocksDB supports flushing multiple column families and atomically committing the result to MANIFEST. Useful when WAL is disabled.
+* Added `num_deletions` and `num_merge_operands` members to `TableProperties`.
+* Added "rocksdb.min-obsolete-sst-number-to-keep" DB property that reports the lower bound on SST file numbers that are being kept from deletion, even if the SSTs are obsolete.
+* Add xxhash64 checksum support
+* Introduced `MemoryAllocator`, which lets the user specify custom memory allocator for block based table.
+* Improved `DeleteRange` to prevent read performance degradation. The feature is no longer marked as experimental.
+
+### Public API Change
+* `DBOptions::use_direct_reads` now affects reads issued by `BackupEngine` on the database's SSTs.
+* `NO_ITERATORS` is divided into two counters `NO_ITERATOR_CREATED` and `NO_ITERATOR_DELETE`. Both of them are only increasing now, just as other counters.
+
+### Bug Fixes
+* Fix corner case where a write group leader blocked due to write stall blocks other writers in queue with WriteOptions::no_slowdown set.
+* Fix in-memory range tombstone truncation to avoid erroneously covering newer keys at a lower level, and include range tombstones in compacted files whose largest key is the range tombstone's start key.
+* Properly set the stop key for a truncated manual CompactRange
+* Fix slow flush/compaction when DB contains many snapshots. The problem became noticeable to us in DBs with 100,000+ snapshots, though it will affect others at different thresholds.
+* Fix the bug that WriteBatchWithIndex's SeekForPrev() doesn't see the entries with the same key.
+* Fix the bug where user comparator was sometimes fed with InternalKey instead of the user key. The bug manifests when during GenerateBottommostFiles.
+* Fix a bug in WritePrepared txns where if the number of old snapshots goes beyond the snapshot cache size (128 default) the rest will not be checked when evicting a commit entry from the commit cache.
+* Fixed Get correctness bug in the presence of range tombstones where merge operands covered by a range tombstone always result in NotFound.
+* Start populating `NO_FILE_CLOSES` ticker statistic, which was always zero previously.
+* The default value of NewBloomFilterPolicy()'s argument use_block_based_builder is changed to false. Note that this new default may cause large temp memory usage when building very large SST files.
+
+## 5.17.0 (10/05/2018)
+### Public API Change
+* `OnTableFileCreated` will now be called for empty files generated during compaction. In that case, `TableFileCreationInfo::file_path` will be "(nil)" and `TableFileCreationInfo::file_size` will be zero.
+* Add `FlushOptions::allow_write_stall`, which controls whether Flush calls start working immediately, even if it causes user writes to stall, or will wait until flush can be performed without causing write stall (similar to `CompactRangeOptions::allow_write_stall`). Note that the default value is false, meaning we add delay to Flush calls until stalling can be avoided when possible. This is behavior change compared to previous RocksDB versions, where Flush calls didn't check if they might cause stall or not.
+* Application using PessimisticTransactionDB is expected to rollback/commit recovered transactions before starting new ones. This assumption is used to skip concurrency control during recovery.
+* Expose column family id to `OnCompactionCompleted`.
+
+### New Features
+* TransactionOptions::skip_concurrency_control allows pessimistic transactions to skip the overhead of concurrency control. Could be used for optimizing certain transactions or during recovery.
+
+### Bug Fixes
+* Avoid creating empty SSTs and subsequently deleting them in certain cases during compaction.
+* Sync CURRENT file contents during checkpoint.
+
+## 5.16.3 (10/1/2018)
+### Bug Fixes
+* Fix crash caused when `CompactFiles` run with `CompactionOptions::compression == CompressionType::kDisableCompressionOption`. Now that setting causes the compression type to be chosen according to the column family-wide compression options.
+
+## 5.16.2 (9/21/2018)
+### Bug Fixes
+* Fix bug in partition filters with format_version=4.
+
+## 5.16.1 (9/17/2018)
+### Bug Fixes
+* Remove trace_analyzer_tool from rocksdb_lib target in TARGETS file.
+* Fix RocksDB Java build and tests.
+* Remove sync point in Block destructor.
+
+## 5.16.0 (8/21/2018)
+### Public API Change
+* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons
+* GetAllKeyVersions() to take an extra argument of `max_num_ikeys`.
+* Using ZSTD dictionary trainer (i.e., setting `CompressionOptions::zstd_max_train_bytes` to a nonzero value) now requires ZSTD version 1.1.3 or later.
+
+### New Features
+* 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. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 4 or above is used.
+* Add a new tool: trace_analyzer. Trace_analyzer analyzes the trace file generated by using trace_replay API. It can convert the binary format trace file to a human readable txt file, output the statistics of the analyzed query types such as access statistics and size statistics, combining the dumped whole key space file to analyze, support query correlation analyzing, and etc. Current supported query types are: Get, Put, Delete, SingleDelete, DeleteRange, Merge, Iterator (Seek, SeekForPrev only).
+* Add hash index support to data blocks, which helps reducing the cpu utilization of point-lookup operations. This feature is backward compatible with the data block created without the hash index. It is disabled by default unless BlockBasedTableOptions::data_block_index_type is set to data_block_index_type = kDataBlockBinaryAndHash.
+
+### Bug Fixes
+* Fix a bug in misreporting the estimated partition index size in properties block.
+
+## 5.15.0 (7/17/2018)
+### Public API Change
+* Remove managed iterator. ReadOptions.managed is not effective anymore.
+* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default.
+* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
+* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
+* The "rocksdb.num.entries" table property no longer counts range deletion tombstones as entries.
+
+### New Features
+* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 3 or above is used.
+* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1.
+* Support dynamically changing `ColumnFamilyOptions::ttl` via `SetOptions()`.
+* Add a new table property, "rocksdb.num.range-deletions", which counts the number of range deletion tombstones in the table.
+* Improve the performance of iterators doing long range scans by using readahead, when using direct IO.
+* pin_top_level_index_and_filter (default true) in BlockBasedTableOptions can be used in combination with cache_index_and_filter_blocks to prefetch and pin the top-level index of partitioned index and filter blocks in cache. It has no impact when cache_index_and_filter_blocks is false.
+* Write properties meta-block at the end of block-based table to save read-ahead IO.
+
+### Bug Fixes
+* Fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
+* Check conflict at output level in CompactFiles.
+* Fix corruption in non-iterator reads when mmap is used for file reads
+* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
+* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints.
+* Fix a bug caused by not copying the block trailer with compressed SST file, direct IO, prefetcher and no compressed block cache.
+* Fix write can stuck indefinitely if enable_pipelined_write=true. The issue exists since pipelined write was introduced in 5.5.0.
+
+## 5.14.0 (5/16/2018)
+### Public API Change
+* Add a BlockBasedTableOption to align uncompressed data blocks on the smaller of block size or page size boundary, to reduce flash reads by avoiding reads spanning 4K pages.
+* The background thread naming convention changed (on supporting platforms) to "rocksdb:<thread pool priority><thread number>", e.g., "rocksdb:low0".
+* Add a new ticker stat rocksdb.number.multiget.keys.found to count number of keys successfully read in MultiGet calls
+* Touch-up to write-related counters in PerfContext. New counters added: write_scheduling_flushes_compactions_time, write_thread_wait_nanos. Counters whose behavior was fixed or modified: write_memtable_time, write_pre_and_post_process_time, write_delay_time.
+* Posix Env's NewRandomRWFile() will fail if the file doesn't exist.
+* Now, `DBOptions::use_direct_io_for_flush_and_compaction` only applies to background writes, and `DBOptions::use_direct_reads` applies to both user reads and background reads. This conforms with Linux's `open(2)` manpage, which advises against simultaneously reading a file in buffered and direct modes, due to possibly undefined behavior and degraded performance.
+* Iterator::Valid() always returns false if !status().ok(). So, now when doing a Seek() followed by some Next()s, there's no need to check status() after every operation.
+* Iterator::Seek()/SeekForPrev()/SeekToFirst()/SeekToLast() always resets status().
+* Introduced `CompressionOptions::kDefaultCompressionLevel`, which is a generic way to tell RocksDB to use the compression library's default level. It is now the default value for `CompressionOptions::level`. Previously the level defaulted to -1, which gave poor compression ratios in ZSTD.
+
+### New Features
+* Introduce TTL for level compaction so that all files older than ttl go through the compaction process to get rid of old data.
+* TransactionDBOptions::write_policy can be configured to enable WritePrepared 2PC transactions. Read more about them in the wiki.
+* Add DB properties "rocksdb.block-cache-capacity", "rocksdb.block-cache-usage", "rocksdb.block-cache-pinned-usage" to show block cache usage.
+* Add `Env::LowerThreadPoolCPUPriority(Priority)` method, which lowers the CPU priority of background (esp. compaction) threads to minimize interference with foreground tasks.
+* Fsync parent directory after deleting a file in delete scheduler.
+* In level-based compaction, if bottom-pri thread pool was setup via `Env::SetBackgroundThreads()`, compactions to the bottom level will be delegated to that thread pool.
+* `prefix_extractor` has been moved from ImmutableCFOptions to MutableCFOptions, meaning it can be dynamically changed without a DB restart.
+
+### Bug Fixes
+* Fsync after writing global seq number to the ingestion file in ExternalSstFileIngestionJob.
+* Fix WAL corruption caused by race condition between user write thread and FlushWAL when two_write_queue is not set.
+* Fix `BackupableDBOptions::max_valid_backups_to_open` to not delete backup files when refcount cannot be accurately determined.
+* Fix memory leak when pin_l0_filter_and_index_blocks_in_cache is used with partitioned filters
+* Disable rollback of merge operands in WritePrepared transactions to work around an issue in MyRocks. It can be enabled back by setting TransactionDBOptions::rollback_merge_operands to true.
+* Fix wrong results by ReverseBytewiseComparator::FindShortSuccessor()
+
+### Java API Changes
+* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.
+* Added SstFileManager to the Java API to allow managing SST files across DB instances.
+
+## 5.13.0 (3/20/2018)
+### Public API Change
+* RocksDBOptionsParser::Parse()'s `ignore_unknown_options` argument will only be effective if the option file shows it is generated using a higher version of RocksDB than the current version.
+* Remove CompactionEventListener.
+
+### New Features
+* SstFileManager now can cancel compactions if they will result in max space errors. SstFileManager users can also use SetCompactionBufferSize to specify how much space must be leftover during a compaction for auxiliary file functions such as logging and flushing.
+* Avoid unnecessarily flushing in `CompactRange()` when the range specified by the user does not overlap unflushed memtables.
+* If `ColumnFamilyOptions::max_subcompactions` is set greater than one, we now parallelize large manual level-based compactions.
+* Add "rocksdb.live-sst-files-size" DB property to return total bytes of all SST files belong to the latest LSM tree.
+* NewSstFileManager to add an argument bytes_max_delete_chunk with default 64MB. With this argument, a file larger than 64MB will be ftruncated multiple times based on this size.
+
+### Bug Fixes
+* Fix a leak in prepared_section_completed_ where the zeroed entries would not removed from the map.
+* Fix WAL corruption caused by race condition between user write thread and backup/checkpoint thread.
+
+## 5.12.0 (2/14/2018)
+### Public API Change
+* Iterator::SeekForPrev is now a pure virtual method. This is to prevent user who implement the Iterator interface fail to implement SeekForPrev by mistake.
+* Add `include_end` option to make the range end exclusive when `include_end == false` in `DeleteFilesInRange()`.
+* Add `CompactRangeOptions::allow_write_stall`, which makes `CompactRange` start working immediately, even if it causes user writes to stall. The default value is false, meaning we add delay to `CompactRange` calls until stalling can be avoided when possible. Note this delay is not present in previous RocksDB versions.
+* Creating checkpoint with empty directory now returns `Status::InvalidArgument`; previously, it returned `Status::IOError`.
+* Adds a BlockBasedTableOption to turn off index block compression.
+* Close() method now returns a status when closing a db.
+
+### New Features
+* Improve the performance of iterators doing long range scans by using readahead.
+* Add new function `DeleteFilesInRanges()` to delete files in multiple ranges at once for better performance.
+* FreeBSD build support for RocksDB and RocksJava.
+* Improved performance of long range scans with readahead.
+* Updated to and now continuously tested in Visual Studio 2017.
+
+### Bug Fixes
+* Fix `DisableFileDeletions()` followed by `GetSortedWalFiles()` to not return obsolete WAL files that `PurgeObsoleteFiles()` is going to delete.
+* Fix Handle error return from WriteBuffer() during WAL file close and DB close.
+* Fix advance reservation of arena block addresses.
+* Fix handling of empty string as checkpoint directory.
+
+## 5.11.0 (01/08/2018)
+### Public API Change
+* Add `autoTune` and `getBytesPerSecond()` to RocksJava RateLimiter
+
+### New Features
+* Add a new histogram stat called rocksdb.db.flush.micros for memtable flush.
+* Add "--use_txn" option to use transactional API in db_stress.
+* Disable onboard cache for compaction output in Windows platform.
+* Improve the performance of iterators doing long range scans by using readahead.
+
+### Bug Fixes
+* Fix a stack-use-after-scope bug in ForwardIterator.
+* Fix builds on platforms including Linux, Windows, and PowerPC.
+* Fix buffer overrun in backup engine for DBs with huge number of files.
+* Fix a mislabel bug for bottom-pri compaction threads.
+* Fix DB::Flush() keep waiting after flush finish under certain condition.
+
+## 5.10.0 (12/11/2017)
+### 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
+* 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.
+
+## 5.9.0 (11/1/2017)
+### Public API Change
+* `BackupableDBOptions::max_valid_backups_to_open == 0` now means no backups will be opened during BackupEngine initialization. Previously this condition disabled limiting backups opened.
+* `DBOptions::preserve_deletes` is a new option that allows one to specify that DB should not drop tombstones for regular deletes if they have sequence number larger than what was set by the new API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)`. Disabled by default.
+* API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)` was added, users who wish to preserve deletes are expected to periodically call this function to advance the cutoff seqnum (all deletes made before this seqnum can be dropped by DB). It's user responsibility to figure out how to advance the seqnum in the way so the tombstones are kept for the desired period of time, yet are eventually processed in time and don't eat up too much space.
+* `ReadOptions::iter_start_seqnum` was added;
+if set to something > 0 user will see 2 changes in iterators behavior 1) only keys written with sequence larger than this parameter would be returned and 2) the `Slice` returned by iter->key() now points to the memory that keep User-oriented representation of the internal key, rather than user key. New struct `FullKey` was added to represent internal keys, along with a new helper function `ParseFullKey(const Slice& internal_key, FullKey* result);`.
+* Deprecate trash_dir param in NewSstFileManager, right now we will rename deleted files to <name>.trash instead of moving them to trash directory
+* Allow setting a custom trash/DB size ratio limit in the SstFileManager, after which files that are to be scheduled for deletion are deleted immediately, regardless of any delete ratelimit.
+* Return an error on write if write_options.sync = true and write_options.disableWAL = true to warn user of inconsistent options. Previously we will not write to WAL and not respecting the sync options in this case.
+
+### 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.
+* `DBOptions::writable_file_max_buffer_size` can now be changed dynamically.
+* `DBOptions::bytes_per_sync`, `DBOptions::compaction_readahead_size`, and `DBOptions::wal_bytes_per_sync` can now be changed dynamically, `DBOptions::wal_bytes_per_sync` will flush all memtables and switch to a new WAL file.
+* Support dynamic adjustment of rate limit according to demand for background I/O. It can be enabled by passing `true` to the `auto_tuned` parameter in `NewGenericRateLimiter()`. The value passed as `rate_bytes_per_sec` will still be respected as an upper-bound.
+* Support dynamically changing `ColumnFamilyOptions::compaction_options_fifo`.
+* Introduce `EventListener::OnStallConditionsChanged()` callback. Users can implement it to be notified when user writes are stalled, stopped, or resumed.
+* Add a new db property "rocksdb.estimate-oldest-key-time" to return oldest data timestamp. The property is available only for FIFO compaction with compaction_options_fifo.allow_compaction = false.
+* Upon snapshot release, recompact bottommost files containing deleted/overwritten keys that previously could not be dropped due to the snapshot. This alleviates space-amp caused by long-held snapshots.
+* Support lower bound on iterators specified via `ReadOptions::iterate_lower_bound`.
+* Support for differential snapshots (via iterator emitting the sequence of key-values representing the difference between DB state at two different sequence numbers). Supports preserving and emitting puts and regular deletes, doesn't support SingleDeletes, MergeOperator, Blobs and Range Deletes.
+
+### Bug Fixes
+* Fix a potential data inconsistency issue during point-in-time recovery. `DB:Open()` will abort if column family inconsistency is found during PIT recovery.
+* Fix possible metadata corruption in databases using `DeleteRange()`.
+
+## 5.8.0 (08/30/2017)
+### 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.
+
+## 5.7.0 (07/13/2017)
+### Public API Change
+* DB property "rocksdb.sstables" now prints keys in hex form.
+
+### New Features
+* Measure estimated number of reads per file. The information can be accessed through DB::GetColumnFamilyMetaData or "rocksdb.sstables" DB property.
+* RateLimiter support for throttling background reads, or throttling the sum of background reads and writes. This can give more predictable I/O usage when compaction reads more data than it writes, e.g., due to lots of deletions.
+* [Experimental] FIFO compaction with TTL support. It can be enabled by setting CompactionOptionsFIFO.ttl > 0.
+* Introduce `EventListener::OnBackgroundError()` callback. Users can implement it to be notified of errors causing the DB to enter read-only mode, and optionally override them.
+* Partitioned Index/Filters exiting the experimental mode. To enable partitioned indexes set index_type to kTwoLevelIndexSearch and to further enable partitioned filters set partition_filters to true. To configure the partition size set metadata_block_size.
+
+
+### Bug Fixes
+* Fix discarding empty compaction output files when `DeleteRange()` is used together with subcompactions.
+
+## 5.6.0 (06/06/2017)
+### 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.
+* 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.
+* 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.
+* Introduce WriteOptions.low_pri. If it is true, low priority writes will be throttled if the compaction is behind.
+* `DB::IngestExternalFile()` now supports ingesting files into a database containing range deletions.
+
+### Bug Fixes
+* Shouldn't ignore return value of fsync() in flush.
+
+## 5.5.0 (05/17/2017)
+### New Features
+* FIFO compaction to support Intra L0 compaction too with CompactionOptionsFIFO.allow_compaction=true.
+* DB::ResetStats() to reset internal stats.
+* 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
+
+## 5.4.0 (04/11/2017)
+### Public API Change
+* random_access_max_buffer_size no longer has any effect
+* Removed Env::EnableReadAhead(), Env::ShouldForwardRawRequest()
+* Support dynamically change `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 when 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. Read Direct IO wiki for details.
+* Added CompactionEventListener and EventListener::OnFlushBegin interfaces.
+
+### 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.
+
+## 5.3.0 (03/08/2017)
+### Public API Change
+* Remove disableDataSync option.
+* Remove timeout_hint_us option from WriteOptions. The option has been deprecated and has no effect since 3.13.0.
+* Remove option min_partial_merge_operands. Partial merge operands will always be merged in flush or compaction if there are more than one.
+* Remove option verify_checksums_in_compaction. Compaction will always verify checksum.
+
+### Bug Fixes
+* Fix the bug that iterator may skip keys
+
+## 5.2.0 (02/08/2017)
+### 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 explicit provide one.
+* Change the default of delayed slowdown value to 16MB/s and further increase the L0 stop condition to 36 files.
+* Options::use_direct_writes and Options::use_direct_reads are now ready to use.
+* (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.
+
+### 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.
+* Add Direct I/O mode for SST file I/O
+
+### 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 bugs of data corruption in direct I/O
+
+## 5.1.0 (01/13/2017)
+* 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.
+
+## 5.0.0 (11/17/2016)
+### 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.
+
+## 4.13.0 (10/18/2016)
+### Public API Change
+* DB::GetOptions() reflect dynamic changed options (i.e. through DB::SetOptions()) and return copy of options instead of reference.
+* Added Statistics::getAndResetTickerCount().
+
+### New Features
+* Add DB::SetDBOptions() to dynamic change base_background_compactions and max_background_compactions.
+* Added Iterator::SeekForPrev(). This new API will seek to the last key that less than or equal to the target key.
+
+## 4.12.0 (9/12/2016)
+### Public API Change
+* CancelAllBackgroundWork() flushes all memtables for databases containing writes that have bypassed the WAL (writes issued with WriteOptions::disableWAL=true) before shutting down background threads.
+* Merge options source_compaction_factor, max_grandparent_overlap_bytes and expanded_compaction_factor into max_compaction_bytes.
+* Remove ImmutableCFOptions.
+* Add a compression type ZSTD, which can work with ZSTD 0.8.0 or up. Still keep ZSTDNotFinal for compatibility reasons.
+
+### New Features
+* Introduce NewClockCache, which is based on CLOCK algorithm with better concurrent performance in some cases. It can be used to replace the default LRU-based block cache and table cache. To use it, RocksDB need to be linked with TBB lib.
+* Change ticker/histogram statistics implementations to accumulate data in thread-local storage, which improves CPU performance by reducing cache coherency costs. Callers of CreateDBStatistics do not need to change anything to use this feature.
+* Block cache mid-point insertion, where index and filter block are inserted into LRU block cache with higher priority. The feature can be enabled by setting BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority to true and high_pri_pool_ratio > 0 when creating NewLRUCache.
+
+## 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 destroying 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.
+
+## 4.9.0 (6/9/2016)
+### Public API changes
+* Add bottommost_compression option, This option can be used to set a specific compression algorithm for the bottommost level (Last level containing files in the DB).
+* Introduce CompactionJobInfo::compression, This field state the compression algorithm used to generate the output files of the compaction.
+* Deprecate BlockBaseTableOptions.hash_index_allow_collision=false
+* Deprecate options builder (GetOptions()).
+
+### New Features
+* Introduce NewSimCache() in rocksdb/utilities/sim_cache.h. This function creates a block cache that is able to give simulation results (mainly hit rate) of simulating block behavior with a configurable cache size.
+
+## 4.8.0 (5/2/2016)
+### 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
+* 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.
+
+### New Features
+* Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size.
+
+## 4.7.0 (4/8/2016)
+### 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 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.
+
+## 4.6.0 (3/10/2016)
+### 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.
+
+### 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"
+
+## 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.
+
+## 4.2.0 (11/9/2015)
+### New Features
+* Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions.
+* Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families.
+* 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
+* CompactionFilter::Context includes information of Column Family ID
+* 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.
+* TablePropertiesCollectorFactory::CreateTablePropertiesCollector() now takes an option Context, containing the information of column family ID for the file being written.
+* Remove DefaultCompactionFilterFactory.
+
+
+## 4.1.0 (10/8/2015)
+### New Features
+* Added single delete operation as a more efficient way to delete keys that have not been overwritten.
+* Added experimental AddFile() to DB interface that allow users to add files created by SstFileWriter into an empty Database, see include/rocksdb/sst_file_writer.h and DB::AddFile() for more info.
+* Added support for opening SST files with .ldb suffix which enables opening LevelDB databases.
+* CompactionFilter now supports filtering of merge operands and merge results.
+
+### Public API Changes
+* Added SingleDelete() to the DB interface.
+* Added AddFile() to DB interface.
+* Added SstFileWriter class.
+* CompactionFilter has a new method FilterMergeOperand() that RocksDB applies to every merge operand during compaction to decide whether to filter the operand.
+* We removed CompactionFilterV2 interfaces from include/rocksdb/compaction_filter.h. The functionality was deprecated already in version 3.13.
+
+## 4.0.0 (9/9/2015)
+### New Features
+* Added support for transactions. See include/rocksdb/utilities/transaction.h for more info.
+* DB::GetProperty() now accepts "rocksdb.aggregated-table-properties" and "rocksdb.aggregated-table-properties-at-levelN", in which case it returns aggregated table properties of the target column family, or the aggregated table properties of the specified level N if the "at-level" version is used.
+* Add compression option kZSTDNotFinalCompression for people to experiment ZSTD although its format is not finalized.
+* We removed the need for LATEST_BACKUP file in BackupEngine. We still keep writing it when we create new backups (because of backward compatibility), but we don't read it anymore.
+
+### Public API Changes
+* Removed class Env::RandomRWFile and Env::NewRandomRWFile().
+* Renamed DBOptions.num_subcompactions to DBOptions.max_subcompactions to make the name better match the actual functionality of the option.
+* Added Equal() method to the Comparator interface that can optionally be overwritten in cases where equality comparisons can be done more efficiently than three-way comparisons.
+* Previous 'experimental' OptimisticTransaction class has been replaced by Transaction class.
+
+## 3.13.0 (8/6/2015)
+### New Features
+* RollbackToSavePoint() in WriteBatch/WriteBatchWithIndex
+* Add NewCompactOnDeletionCollectorFactory() in utilities/table_properties_collectors, which allows rocksdb to mark a SST file as need-compaction when it observes at least D deletion entries in any N consecutive entries in that SST file. Note that this feature depends on an experimental NeedCompact() API --- the result of this API will not persist after DB restart.
+* Add DBOptions::delete_scheduler. Use NewDeleteScheduler() in include/rocksdb/delete_scheduler.h to create a DeleteScheduler that can be shared among multiple RocksDB instances to control the file deletion rate of SST files that exist in the first db_path.
+
+### Public API Changes
+* Deprecated WriteOptions::timeout_hint_us. We no longer support write timeout. If you really need this option, talk to us and we might consider returning it.
+* Deprecated purge_redundant_kvs_while_flush option.
+* Removed BackupEngine::NewBackupEngine() and NewReadOnlyBackupEngine() that were deprecated in RocksDB 3.8. Please use BackupEngine::Open() instead.
+* Deprecated Compaction Filter V2. We are not aware of any existing use-cases. If you use this filter, your compile will break with RocksDB 3.13. Please let us know if you use it and we'll put it back in RocksDB 3.14.
+* Env::FileExists now returns a Status instead of a boolean
+* Add statistics::getHistogramString() to print detailed distribution of a histogram metric.
+* Add DBOptions::skip_stats_update_on_db_open. When it is on, DB::Open() will run faster as it skips the random reads required for loading necessary stats from SST files to optimize compaction.
+
+## 3.12.0 (7/2/2015)
+### New Features
+* Added experimental support for optimistic transactions. See include/rocksdb/utilities/optimistic_transaction.h for more info.
+* Added a new way to report QPS from db_bench (check out --report_file and --report_interval_seconds)
+* Added a cache for individual rows. See DBOptions::row_cache for more info.
+* Several new features on EventListener (see include/rocksdb/listener.h):
+ - OnCompationCompleted() now returns per-compaction job statistics, defined in include/rocksdb/compaction_job_stats.h.
+ - Added OnTableFileCreated() and OnTableFileDeleted().
+* Add compaction_options_universal.enable_trivial_move to true, to allow trivial move while performing universal compaction. Trivial move will happen only when all the input files are non overlapping.
+
+### Public API changes
+* EventListener::OnFlushCompleted() now passes FlushJobInfo instead of a list of parameters.
+* DB::GetDbIdentity() is now a const function. If this function is overridden in your application, be sure to also make GetDbIdentity() const to avoid compile error.
+* Move listeners from ColumnFamilyOptions to DBOptions.
+* Add max_write_buffer_number_to_maintain option
+* DB::CompactRange()'s parameter reduce_level is changed to change_level, to allow users to move levels to lower levels if allowed. It can be used to migrate a DB from options.level_compaction_dynamic_level_bytes=false to options.level_compaction_dynamic_level_bytes.true.
+* Change default value for options.compaction_filter_factory and options.compaction_filter_factory_v2 to nullptr instead of DefaultCompactionFilterFactory and DefaultCompactionFilterFactoryV2.
+* If CancelAllBackgroundWork is called without doing a flush after doing loads with WAL disabled, the changes which haven't been flushed before the call to CancelAllBackgroundWork will be lost.
+* WBWIIterator::Entry() now returns WriteEntry instead of `const WriteEntry&`
+* options.hard_rate_limit is deprecated.
+* When options.soft_rate_limit or options.level0_slowdown_writes_trigger is triggered, the way to slow down writes is changed to: write rate to DB is limited to to options.delayed_write_rate.
+* DB::GetApproximateSizes() adds a parameter to allow the estimation to include data in mem table, with default to be not to include. It is now only supported in skip list mem table.
+* DB::CompactRange() now accept CompactRangeOptions instead of multiple parameters. CompactRangeOptions is defined in include/rocksdb/options.h.
+* CompactRange() will now skip bottommost level compaction for level based compaction if there is no compaction filter, bottommost_level_compaction is introduced in CompactRangeOptions to control when it's possible to skip bottommost level compaction. This mean that if you want the compaction to produce a single file you need to set bottommost_level_compaction to BottommostLevelCompaction::kForce.
+* Add Cache.GetPinnedUsage() to get the size of memory occupied by entries that are in use by the system.
+* DB:Open() will fail if the compression specified in Options is not linked with the binary. If you see this failure, recompile RocksDB with compression libraries present on your system. Also, previously our default compression was snappy. This behavior is now changed. Now, the default compression is snappy only if it's available on the system. If it isn't we change the default to kNoCompression.
+* We changed how we account for memory used in block cache. Previously, we only counted the sum of block sizes currently present in block cache. Now, we count the actual memory usage of the blocks. For example, a block of size 4.5KB will use 8KB memory with jemalloc. This might decrease your memory usage and possibly decrease performance. Increase block cache size if you see this happening after an upgrade.
+* Add BackupEngineImpl.options_.max_background_operations to specify the maximum number of operations that may be performed in parallel. Add support for parallelized backup and restore.
+* Add DB::SyncWAL() that does a WAL sync without blocking writers.
+
+## 3.11.0 (5/19/2015)
+### New Features
+* Added a new API Cache::SetCapacity(size_t capacity) to dynamically change the maximum configured capacity of the cache. If the new capacity is less than the existing cache usage, the implementation will try to lower the usage by evicting the necessary number of elements following a strict LRU policy.
+* Added an experimental API for handling flashcache devices (blacklists background threads from caching their reads) -- NewFlashcacheAwareEnv
+* If universal compaction is used and options.num_levels > 1, compact files are tried to be stored in none-L0 with smaller files based on options.target_file_size_base. The limitation of DB size when using universal compaction is greatly mitigated by using more levels. You can set num_levels = 1 to make universal compaction behave as before. If you set num_levels > 1 and want to roll back to a previous version, you need to compact all files to a big file in level 0 (by setting target_file_size_base to be large and CompactRange(<cf_handle>, nullptr, nullptr, true, 0) and reopen the DB with the same version to rewrite the manifest, and then you can open it using previous releases.
+* More information about rocksdb background threads are available in Env::GetThreadList(), including the number of bytes read / written by a compaction job, mem-table size and current number of bytes written by a flush job and many more. Check include/rocksdb/thread_status.h for more detail.
+
+### Public API changes
+* TablePropertiesCollector::AddUserKey() is added to replace TablePropertiesCollector::Add(). AddUserKey() exposes key type, sequence number and file size up to now to users.
+* DBOptions::bytes_per_sync used to apply to both WAL and table files. As of 3.11 it applies only to table files. If you want to use this option to sync WAL in the background, please use wal_bytes_per_sync
+
+## 3.10.0 (3/24/2015)
+### New Features
+* GetThreadStatus() is now able to report detailed thread status, including:
+ - Thread Operation including flush and compaction.
+ - The stage of the current thread operation.
+ - The elapsed time in micros since the current thread operation started.
+ More information can be found in include/rocksdb/thread_status.h. In addition, when running db_bench with --thread_status_per_interval, db_bench will also report thread status periodically.
+* Changed the LRU caching algorithm so that referenced blocks (by iterators) are never evicted. This change made parameter removeScanCountLimit obsolete. Because of that NewLRUCache doesn't take three arguments anymore. table_cache_remove_scan_limit option is also removed
+* By default we now optimize the compilation for the compilation platform (using -march=native). If you want to build portable binary, use 'PORTABLE=1' before the make command.
+* We now allow level-compaction to place files in different paths by
+ specifying them in db_paths along with the target_size.
+ Lower numbered levels will be placed earlier in the db_paths and higher
+ numbered levels will be placed later in the db_paths vector.
+* Potentially big performance improvements if you're using RocksDB with lots of column families (100-1000)
+* Added BlockBasedTableOptions.format_version option, which allows user to specify which version of block based table he wants. As a general guideline, newer versions have more features, but might not be readable by older versions of RocksDB.
+* Added new block based table format (version 2), which you can enable by setting BlockBasedTableOptions.format_version = 2. This format changes how we encode size information in compressed blocks and should help with memory allocations if you're using Zlib or BZip2 compressions.
+* MemEnv (env that stores data in memory) is now available in default library build. You can create it by calling NewMemEnv().
+* Add SliceTransform.SameResultWhenAppended() to help users determine it is safe to apply prefix bloom/hash.
+* Block based table now makes use of prefix bloom filter if it is a full fulter.
+* Block based table remembers whether a whole key or prefix based bloom filter is supported in SST files. Do a sanity check when reading the file with users' configuration.
+* Fixed a bug in ReadOnlyBackupEngine that deleted corrupted backups in some cases, even though the engine was ReadOnly
+* options.level_compaction_dynamic_level_bytes, a feature to allow RocksDB to pick dynamic base of bytes for levels. With this feature turned on, we will automatically adjust max bytes for each level. The goal of this feature is to have lower bound on size amplification. For more details, see comments in options.h.
+* Added an abstract base class WriteBatchBase for write batches
+* Fixed a bug where we start deleting files of a dropped column families even if there are still live references to it
+
+### Public API changes
+* Deprecated skip_log_error_on_recovery and table_cache_remove_scan_count_limit options.
+* Logger method logv with log level parameter is now virtual
+
+### RocksJava
+* 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`.
+* Overflowing a size_t when setting rocksdb options now throws an IllegalArgumentException, which removes the necessity for a developer to catch these Exceptions explicitly.
+
+## 3.9.0 (12/8/2014)
+
+### New Features
+* Add rocksdb::GetThreadList(), which in the future will return the current status of all
+ rocksdb-related threads. We will have more code instruments in the following RocksDB
+ releases.
+* Change convert function in rocksdb/utilities/convenience.h to return Status instead of boolean.
+ Also add support for nested options in convert function
+
+### Public API changes
+* New API to create a checkpoint added. Given a directory name, creates a new
+ database which is an image of the existing database.
+* New API LinkFile added to Env. If you implement your own Env class, an
+ implementation of the API LinkFile will have to be provided.
+* MemTableRep takes MemTableAllocator instead of Arena
+
+### Improvements
+* RocksDBLite library now becomes smaller and will be compiled with -fno-exceptions flag.
+
+## 3.8.0 (11/14/2014)
+
+### Public API changes
+* BackupEngine::NewBackupEngine() was deprecated; please use BackupEngine::Open() from now on.
+* BackupableDB/RestoreBackupableDB have new GarbageCollect() methods, which will clean up files from corrupt and obsolete backups.
+* BackupableDB/RestoreBackupableDB have new GetCorruptedBackups() methods which list corrupt backups.
+
+### Cleanup
+* Bunch of code cleanup, some extra warnings turned on (-Wshadow, -Wshorten-64-to-32, -Wnon-virtual-dtor)
+
+### New features
+* CompactFiles and EventListener, although they are still in experimental state
+* Full ColumnFamily support in RocksJava.
+
+## 3.7.0 (11/6/2014)
+### Public API changes
+* Introduce SetOptions() API to allow adjusting a subset of options dynamically online
+* Introduce 4 new convenient functions for converting Options from string: GetColumnFamilyOptionsFromMap(), GetColumnFamilyOptionsFromString(), GetDBOptionsFromMap(), GetDBOptionsFromString()
+* Remove WriteBatchWithIndex.Delete() overloads using SliceParts
+* When opening a DB, if options.max_background_compactions is larger than the existing low pri pool of options.env, it will enlarge it. Similarly, options.max_background_flushes is larger than the existing high pri pool of options.env, it will enlarge it.
+
+## 3.6.0 (10/7/2014)
+### Disk format changes
+* If you're using RocksDB on ARM platforms and you're using default bloom filter, there is a disk format change you need to be aware of. There are three steps you need to do when you convert to new release: 1. turn off filter policy, 2. compact the whole database, 3. turn on filter policy
+
+### Behavior changes
+* We have refactored our system of stalling writes. Any stall-related statistics' meanings are changed. Instead of per-write stall counts, we now count stalls per-epoch, where epochs are periods between flushes and compactions. You'll find more information in our Tuning Perf Guide once we release RocksDB 3.6.
+* When disableDataSync=true, we no longer sync the MANIFEST file.
+* Add identity_as_first_hash property to CuckooTable. SST file needs to be rebuilt to be opened by reader properly.
+
+### Public API changes
+* Change target_file_size_base type to uint64_t from int.
+* Remove allow_thread_local. This feature was proved to be stable, so we are turning it always-on.
+
+## 3.5.0 (9/3/2014)
+### New Features
+* Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it.
+* 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.
+* Remove deprecated options: disable_seek_compaction and db_stats_log_interval
+* OptimizeForPointLookup() takes one parameter for block cache size. It now builds hash index, bloom filter, and block cache.
+
+### Public API changes
+* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
+
+## 3.4.0 (8/18/2014)
+### New Features
+* Support Multiple DB paths in universal style compactions
+* Add feature of storing plain table index and bloom filter in SST file.
+* CompactRange() will never output compacted files to level 0. This used to be the case when all the compaction input files were at level 0.
+* Added iterate_upper_bound to define the extent upto which the forward iterator will return entries. This will prevent iterating over delete markers and overwritten entries for edge cases where you want to break out the iterator anyways. This may improve performance in case there are a large number of delete markers or overwritten entries.
+
+### Public API changes
+* DBOptions.db_paths now is a vector of a DBPath structure which indicates both of path and target size
+* NewPlainTableFactory instead of bunch of parameters now accepts PlainTableOptions, which is defined in include/rocksdb/table.h
+* Moved include/utilities/*.h to include/rocksdb/utilities/*.h
+* Statistics APIs now take uint32_t as type instead of Tickers. Also make two access functions getTickerCount and histogramData const
+* Add DB property rocksdb.estimate-num-keys, estimated number of live keys in DB.
+* Add DB::GetIntProperty(), which returns DB properties that are integer as uint64_t.
+* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
+
+## 3.3.0 (7/10/2014)
+### New Features
+* Added JSON API prototype.
+* HashLinklist reduces performance outlier caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory().
+* 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 triggered by deletion entries more easily.
+* Add 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.
+* Add a rate_limiter option, which 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.
+
+## 3.2.0 (06/20/2014)
+
+### Public API changes
+* We removed seek compaction as a concept from RocksDB because:
+1) It makes more sense for spinning disk workloads, while RocksDB is primarily designed for flash and memory,
+2) It added some complexity to the important code-paths,
+3) None of our internal customers were really using it.
+Because of that, Options::disable_seek_compaction is now obsolete. It is still a parameter in Options, so it does not break the build, but it does not have any effect. We plan to completely remove it at some point, so we ask users to please remove this option from your code base.
+* Add two parameters 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.
+
+### New Features
+* 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 parameter 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()
+
+### Performance Improvements
+* Tailing Iterator re-implemeted with ForwardIterator + Cascading Search Hint , see ~20% throughput improvement.
+
+## 3.1.0 (05/21/2014)
+
+### Public API changes
+* Replaced ColumnFamilyOptions::table_properties_collectors with ColumnFamilyOptions::table_properties_collector_factories
+
+### New Features
+* Hash index for block-based table will be materialized and reconstructed more efficiently. Previously hash index is constructed by scanning the whole table during every table open.
+* FIFO compaction style
+
+## 3.0.0 (05/05/2014)
+
+### Public API changes
+* Added _LEVEL to all InfoLogLevel enums
+* Deprecated ReadOptions.prefix and ReadOptions.prefix_seek. Seek() defaults to prefix-based seek when Options.prefix_extractor is supplied. More detail is documented in https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes
+* MemTableRepFactory::CreateMemTableRep() takes info logger as an extra parameter.
+
+### New Features
+* Column family support
+* Added an option to use different checksum functions in BlockBasedTableOptions
+* Added ApplyToAllCacheEntries() function to Cache
+
+## 2.8.0 (04/04/2014)
+
+* Removed arena.h from public header files.
+* By default, checksums are verified on every read from database
+* Change default value of several options, including: paranoid_checks=true, max_open_files=5000, level0_slowdown_writes_trigger=20, level0_stop_writes_trigger=24, disable_seek_compaction=true, max_background_flushes=1 and allow_mmap_writes=false
+* Added is_manual_compaction to CompactionFilter::Context
+* Added "virtual void WaitForJoin()" in class Env. Default operation is no-op.
+* Removed BackupEngine::DeleteBackupsNewerThan() function
+* Added new option -- verify_checksums_in_compaction
+* Changed Options.prefix_extractor from raw pointer to shared_ptr (take ownership)
+ Changed HashSkipListRepFactory and HashLinkListRepFactory constructor to not take SliceTransform object (use Options.prefix_extractor implicitly)
+* Added Env::GetThreadPoolQueueLen(), which returns the waiting queue length of thread pools
+* Added a command "checkconsistency" in ldb tool, which checks
+ if file system state matches DB state (file existence and file sizes)
+* Separate options related to block based table to a new struct BlockBasedTableOptions.
+* WriteBatch has a new function Count() to return total size in the batch, and Data() now returns a reference instead of a copy
+* Add more counters to perf context.
+* Supports several more DB properties: compaction-pending, background-errors and cur-size-active-mem-table.
+
+### New Features
+* If we find one truncated record at the end of the MANIFEST or WAL files,
+ we will ignore it. We assume that writers of these records were interrupted
+ and that we can safely ignore it.
+* A new SST format "PlainTable" is added, which is optimized for memory-only workloads. It can be created through NewPlainTableFactory() or NewTotalOrderPlainTableFactory().
+* A new mem table implementation hash linked list optimizing for the case that there are only few keys for each prefix, which can be created through NewHashLinkListRepFactory().
+* Merge operator supports a new function PartialMergeMulti() to allow users to do partial merges against multiple operands.
+* Now compaction filter has a V2 interface. It buffers the kv-pairs sharing the same key prefix, process them in batches, and return the batched results back to DB. The new interface uses a new structure CompactionFilterContext for the same purpose as CompactionFilter::Context in V1.
+* Geo-spatial support for locations and radial-search.
+
+## 2.7.0 (01/28/2014)
+
+### Public API changes
+
+* Renamed `StackableDB::GetRawDB()` to `StackableDB::GetBaseDB()`.
+* Renamed `WriteBatch::Data()` `const std::string& Data() const`.
+* Renamed class `TableStats` to `TableProperties`.
+* Deleted class `PrefixHashRepFactory`. Please use `NewHashSkipListRepFactory()` instead.
+* Supported multi-threaded `EnableFileDeletions()` and `DisableFileDeletions()`.
+* Added `DB::GetOptions()`.
+* Added `DB::GetDbIdentity()`.
+
+### New Features
+
+* Added [BackupableDB](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F)
+* Implemented [TailingIterator](https://github.com/facebook/rocksdb/wiki/Tailing-Iterator), a special type of iterator that
+ doesn't create a snapshot (can be used to read newly inserted data)
+ and is optimized for doing sequential reads.
+* Added property block for table, which allows (1) a table to store
+ its metadata and (2) end user to collect and store properties they
+ are interested in.
+* Enabled caching index and filter block in block cache (turned off by default).
+* Supported error report when doing manual compaction.
+* Supported additional Linux platform flavors and Mac OS.
+* Put with `SliceParts` - Variant of `Put()` that gathers output like `writev(2)`
+* Bug fixes and code refactor for compatibility with upcoming Column
+ Family feature.
+
+### Performance Improvements
+
+* Huge benchmark performance improvements by multiple efforts. For example, increase in readonly QPS from about 530k in 2.6 release to 1.1 million in 2.7 [1]
+* Speeding up a way RocksDB deleted obsolete files - no longer listing the whole directory under a lock -- decrease in p99
+* Use raw pointer instead of shared pointer for statistics: [5b825d](https://github.com/facebook/rocksdb/commit/5b825d6964e26ec3b4bb6faa708ebb1787f1d7bd) -- huge increase in performance -- shared pointers are slow
+* Optimized locking for `Get()` -- [1fdb3f](https://github.com/facebook/rocksdb/commit/1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c) -- 1.5x QPS increase for some workloads
+* Cache speedup - [e8d40c3](https://github.com/facebook/rocksdb/commit/e8d40c31b3cca0c3e1ae9abe9b9003b1288026a9)
+* Implemented autovector, which allocates first N elements on stack. Most of vectors in RocksDB are small. Also, we never want to allocate heap objects while holding a mutex. -- [c01676e4](https://github.com/facebook/rocksdb/commit/c01676e46d3be08c3c140361ef1f5884f47d3b3c)
+* Lots of efforts to move malloc, memcpy and IO outside of locks
diff --git a/storage/rocksdb/rocksdb/INSTALL.md b/storage/rocksdb/rocksdb/INSTALL.md
new file mode 100644
index 00000000..91a0935b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/INSTALL.md
@@ -0,0 +1,202 @@
+## Compilation
+
+**Important**: If you plan to run RocksDB in production, don't compile using default
+`make` or `make all`. That will compile RocksDB in debug mode, which is much slower
+than release mode.
+
+RocksDB's library should be able to compile without any dependency installed,
+although we recommend installing some compression libraries (see below).
+We do depend on newer gcc/clang with C++11 support.
+
+There are few options when compiling RocksDB:
+
+* [recommended] `make static_lib` will compile librocksdb.a, RocksDB static library. Compiles static library in release mode.
+
+* `make shared_lib` will compile librocksdb.so, RocksDB shared library. Compiles shared library in release mode.
+
+* `make check` will compile and run all the unit tests. `make check` will compile RocksDB in debug mode.
+
+* `make all` will compile our static library, and all our tools and unit tests. Our tools
+depend on gflags. You will need to have gflags installed to run `make all`. This will compile RocksDB in debug mode. Don't
+use binaries compiled by `make all` in production.
+
+* By default the binary we produce is optimized for the platform you're compiling on
+(`-march=native` or the equivalent). SSE4.2 will thus be enabled automatically if your
+CPU supports it. To print a warning if your CPU does not support SSE4.2, build with
+`USE_SSE=1 make static_lib` or, if using CMake, `cmake -DFORCE_SSE42=ON`. If you want
+to build a portable binary, add `PORTABLE=1` before your make commands, like this:
+`PORTABLE=1 make static_lib`.
+
+## Dependencies
+
+* You can link RocksDB with following compression libraries:
+ - [zlib](http://www.zlib.net/) - a library for data compression.
+ - [bzip2](http://www.bzip.org/) - a library for data compression.
+ - [lz4](https://github.com/lz4/lz4) - a library for extremely fast data compression.
+ - [snappy](http://google.github.io/snappy/) - a library for fast
+ data compression.
+ - [zstandard](http://www.zstd.net) - Fast real-time compression
+ algorithm.
+
+* All our tools depend on:
+ - [gflags](https://gflags.github.io/gflags/) - a library that handles
+ command line flags processing. You can compile rocksdb library even
+ if you don't have gflags installed.
+
+* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
+
+## Supported platforms
+
+* **Linux - Ubuntu**
+ * Upgrade your gcc to version at least 4.8 to get C++11 support.
+ * Install gflags. First, try: `sudo apt-get install libgflags-dev`
+ If this doesn't work and you're using Ubuntu, here's a nice tutorial:
+ (http://askubuntu.com/questions/312173/installing-gflags-12-04)
+ * Install snappy. This is usually as easy as:
+ `sudo apt-get install libsnappy-dev`.
+ * Install zlib. Try: `sudo apt-get install zlib1g-dev`.
+ * Install bzip2: `sudo apt-get install libbz2-dev`.
+ * Install lz4: `sudo apt-get install liblz4-dev`.
+ * Install zstandard: `sudo apt-get install libzstd-dev`.
+
+* **Linux - CentOS / RHEL**
+ * Upgrade your gcc to version at least 4.8 to get C++11 support:
+ `yum install gcc48-c++`
+ * Install gflags:
+
+ git clone https://github.com/gflags/gflags.git
+ cd gflags
+ git checkout v2.0
+ ./configure && make && sudo make install
+
+ **Notice**: Once installed, please add the include path for gflags to your `CPATH` environment variable and the
+ lib path to `LIBRARY_PATH`. If installed with default settings, the include path will be `/usr/local/include`
+ and the lib path will be `/usr/local/lib`.
+
+ * Install snappy:
+
+ sudo yum install snappy snappy-devel
+
+ * Install zlib:
+
+ sudo yum install zlib zlib-devel
+
+ * Install bzip2:
+
+ sudo yum install bzip2 bzip2-devel
+
+ * Install lz4:
+
+ sudo yum install lz4-devel
+
+ * Install ASAN (optional for debugging):
+
+ sudo yum install libasan
+
+ * Install zstandard:
+
+ wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
+ mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
+ tar zxvf zstd-1.1.3.tar.gz
+ cd zstd-1.1.3
+ make && sudo make install
+
+* **OS X**:
+ * Install latest C++ compiler that supports C++ 11:
+ * Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
+ * Install via [homebrew](http://brew.sh/).
+ * If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
+ * run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher).
+ * run `brew install rocksdb`
+
+* **FreeBSD** (11.01):
+
+ * You can either install RocksDB from the Ports system using `cd /usr/ports/databases/rocksdb && make install`, or you can follow the details below to install dependencies and compile from source code:
+
+ * Install the dependencies for RocksDB:
+
+ export BATCH=YES
+ cd /usr/ports/devel/gmake && make install
+ cd /usr/ports/devel/gflags && make install
+
+ cd /usr/ports/archivers/snappy && make install
+ cd /usr/ports/archivers/bzip2 && make install
+ cd /usr/ports/archivers/liblz4 && make install
+ cd /usr/ports/archivesrs/zstd && make install
+
+ cd /usr/ports/devel/git && make install
+
+
+ * Install the dependencies for RocksJava (optional):
+
+ export BATCH=yes
+ cd /usr/ports/java/openjdk7 && make install
+
+ * Build RocksDB from source:
+ cd ~
+ git clone https://github.com/facebook/rocksdb.git
+ cd rocksdb
+ gmake static_lib
+
+ * Build RocksJava from source (optional):
+ cd rocksdb
+ export JAVA_HOME=/usr/local/openjdk7
+ gmake rocksdbjava
+
+* **OpenBSD** (6.3/-current):
+
+ * As RocksDB is not available in the ports yet you have to build it on your own:
+
+ * Install the dependencies for RocksDB:
+
+ pkg_add gmake gflags snappy bzip2 lz4 zstd git jdk bash findutils gnuwatch
+
+ * Build RocksDB from source:
+
+ cd ~
+ git clone https://github.com/facebook/rocksdb.git
+ cd rocksdb
+ gmake static_lib
+
+ * Build RocksJava from source (optional):
+
+ cd rocksdb
+ export JAVA_HOME=/usr/local/jdk-1.8.0
+ export PATH=$PATH:/usr/local/jdk-1.8.0/bin
+ gmake rocksdbjava
+
+* **iOS**:
+ * Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`.
+
+* **Windows**:
+ * For building with MS Visual Studio 13 you will need Update 4 installed.
+ * Read and follow the instructions at CMakeLists.txt
+ * Or install via [vcpkg](https://github.com/microsoft/vcpkg)
+ * run `vcpkg install rocksdb:x64-windows`
+
+* **AIX 6.1**
+ * Install AIX Toolbox rpms with gcc
+ * Use these environment variables:
+
+ export PORTABLE=1
+ export CC=gcc
+ export AR="ar -X64"
+ export EXTRA_ARFLAGS=-X64
+ export EXTRA_CFLAGS=-maix64
+ export EXTRA_CXXFLAGS=-maix64
+ export PLATFORM_LDFLAGS="-static-libstdc++ -static-libgcc"
+ export LIBPATH=/opt/freeware/lib
+ export JAVA_HOME=/usr/java8_64
+ export PATH=/opt/freeware/bin:$PATH
+
+* **Solaris Sparc**
+ * Install GCC 4.8.2 and higher.
+ * Use these environment variables:
+
+ export CC=gcc
+ export EXTRA_CFLAGS=-m64
+ export EXTRA_CXXFLAGS=-m64
+ export EXTRA_LDFLAGS=-m64
+ export PORTABLE=1
+ export PLATFORM_LDFLAGS="-static-libstdc++ -static-libgcc"
+
diff --git a/storage/rocksdb/rocksdb/LANGUAGE-BINDINGS.md b/storage/rocksdb/rocksdb/LANGUAGE-BINDINGS.md
new file mode 100644
index 00000000..73c2355a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/LANGUAGE-BINDINGS.md
@@ -0,0 +1,22 @@
+This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it.
+
+* Java - https://github.com/facebook/rocksdb/tree/master/java
+* Python
+ * http://python-rocksdb.readthedocs.io/en/latest/
+ * http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
+* Perl - https://metacpan.org/pod/RocksDB
+* Node.js - https://npmjs.org/package/rocksdb
+* Go - https://github.com/tecbot/gorocksdb
+* Ruby - http://rubygems.org/gems/rocksdb-ruby
+* Haskell - https://hackage.haskell.org/package/rocksdb-haskell
+* PHP - https://github.com/Photonios/rocksdb-php
+* C# - https://github.com/warrenfalk/rocksdb-sharp
+* Rust
+ * https://github.com/pingcap/rust-rocksdb (used in production fork of https://github.com/spacejam/rust-rocksdb)
+ * https://github.com/spacejam/rust-rocksdb
+ * https://github.com/bh1xuw/rust-rocks
+* D programming language - https://github.com/b1naryth1ef/rocksdb
+* Erlang - https://gitlab.com/barrel-db/erlang-rocksdb
+* Elixir - https://github.com/urbint/rox
+* Nim - https://github.com/status-im/nim-rocksdb
+* Swift and Objective-C (iOS/OSX) - https://github.com/iabudiab/ObjectiveRocks
diff --git a/storage/rocksdb/rocksdb/LICENSE.Apache b/storage/rocksdb/rocksdb/LICENSE.Apache
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/storage/rocksdb/rocksdb/LICENSE.Apache
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/storage/rocksdb/rocksdb/LICENSE.leveldb b/storage/rocksdb/rocksdb/LICENSE.leveldb
new file mode 100644
index 00000000..7108b0bf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/LICENSE.leveldb
@@ -0,0 +1,29 @@
+This contains code that is from LevelDB, and that code is under the following license:
+
+Copyright (c) 2011 The LevelDB Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/storage/rocksdb/rocksdb/Makefile b/storage/rocksdb/rocksdb/Makefile
new file mode 100644
index 00000000..ce2e581c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/Makefile
@@ -0,0 +1,2185 @@
+# 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.
+
+# Inherit some settings from environment variables, if available
+
+#-----------------------------------------------
+
+BASH_EXISTS := $(shell which bash)
+SHELL := $(shell which bash)
+
+CLEAN_FILES = # deliberately empty, so we can append below.
+CFLAGS += ${EXTRA_CFLAGS}
+CXXFLAGS += ${EXTRA_CXXFLAGS}
+LDFLAGS += $(EXTRA_LDFLAGS)
+MACHINE ?= $(shell uname -m)
+ARFLAGS = ${EXTRA_ARFLAGS} rs
+STRIPFLAGS = -S -x
+
+# Transform parallel LOG output into something more readable.
+perl_command = perl -n \
+ -e '@a=split("\t",$$_,-1); $$t=$$a[8];' \
+ -e '$$t =~ /.*if\s\[\[\s"(.*?\.[\w\/]+)/ and $$t=$$1;' \
+ -e '$$t =~ s,^\./,,;' \
+ -e '$$t =~ s, >.*,,; chomp $$t;' \
+ -e '$$t =~ /.*--gtest_filter=(.*?\.[\w\/]+)/ and $$t=$$1;' \
+ -e 'printf "%7.3f %s %s\n", $$a[3], $$a[6] == 0 ? "PASS" : "FAIL", $$t'
+quoted_perl_command = $(subst ','\'',$(perl_command))
+
+# DEBUG_LEVEL can have three values:
+# * DEBUG_LEVEL=2; this is the ultimate debug mode. It will compile rocksdb
+# without any optimizations. To compile with level 2, issue `make dbg`
+# * DEBUG_LEVEL=1; debug level 1 enables all assertions and debug code, but
+# compiles rocksdb with -O2 optimizations. this is the default debug level.
+# `make all` or `make <binary_target>` compile RocksDB with debug level 1.
+# We use this debug level when developing RocksDB.
+# * DEBUG_LEVEL=0; this is the debug level we use for release. If you're
+# running rocksdb in production you most definitely want to compile RocksDB
+# with debug level 0. To compile with level 0, run `make shared_lib`,
+# `make install-shared`, `make static_lib`, `make install-static` or
+# `make install`
+
+# Set the default DEBUG_LEVEL to 1
+DEBUG_LEVEL?=1
+
+ifeq ($(MAKECMDGOALS),dbg)
+ DEBUG_LEVEL=2
+endif
+
+ifeq ($(MAKECMDGOALS),clean)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),release)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),shared_lib)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),install-shared)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),static_lib)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),install-static)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),install)
+ DEBUG_LEVEL=0
+endif
+
+ifeq ($(MAKECMDGOALS),rocksdbjavastatic)
+ ifneq ($(DEBUG_LEVEL),2)
+ DEBUG_LEVEL=0
+ endif
+endif
+
+ifeq ($(MAKECMDGOALS),rocksdbjavastaticrelease)
+ ifneq ($(DEBUG_LEVEL),2)
+ DEBUG_LEVEL=0
+ endif
+endif
+
+ifeq ($(MAKECMDGOALS),rocksdbjavastaticreleasedocker)
+ ifneq ($(DEBUG_LEVEL),2)
+ DEBUG_LEVEL=0
+ endif
+endif
+
+ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
+ DEBUG_LEVEL=0
+endif
+
+$(info $$DEBUG_LEVEL is ${DEBUG_LEVEL})
+
+# Lite build flag.
+LITE ?= 0
+ifeq ($(LITE), 0)
+ifneq ($(filter -DROCKSDB_LITE,$(OPT)),)
+ # Be backward compatible and support older format where OPT=-DROCKSDB_LITE is
+ # specified instead of LITE=1 on the command line.
+ LITE=1
+endif
+else ifeq ($(LITE), 1)
+ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
+ OPT += -DROCKSDB_LITE
+endif
+endif
+
+# Figure out optimize level.
+ifneq ($(DEBUG_LEVEL), 2)
+ifeq ($(LITE), 0)
+ OPT += -O2
+else
+ OPT += -Os
+endif
+endif
+
+# compile with -O2 if debug level is not 2
+ifneq ($(DEBUG_LEVEL), 2)
+OPT += -fno-omit-frame-pointer
+# Skip for archs that don't support -momit-leaf-frame-pointer
+ifeq (,$(shell $(CXX) -fsyntax-only -momit-leaf-frame-pointer -xc /dev/null 2>&1))
+OPT += -momit-leaf-frame-pointer
+endif
+endif
+
+ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1))
+CXXFLAGS += -DHAS_ALTIVEC
+CFLAGS += -DHAS_ALTIVEC
+HAS_ALTIVEC=1
+endif
+
+ifeq (,$(shell $(CXX) -fsyntax-only -mcpu=power8 -xc /dev/null 2>&1))
+CXXFLAGS += -DHAVE_POWER8
+CFLAGS += -DHAVE_POWER8
+HAVE_POWER8=1
+endif
+
+ifeq (,$(shell $(CXX) -fsyntax-only -march=armv8-a+crc+crypto -xc /dev/null 2>&1))
+CXXFLAGS += -march=armv8-a+crc+crypto
+CFLAGS += -march=armv8-a+crc+crypto
+ARMCRC_SOURCE=1
+endif
+
+# if we're compiling for release, compile without debug code (-DNDEBUG)
+ifeq ($(DEBUG_LEVEL),0)
+OPT += -DNDEBUG
+
+ifneq ($(USE_RTTI), 1)
+ CXXFLAGS += -fno-rtti
+else
+ CXXFLAGS += -DROCKSDB_USE_RTTI
+endif
+else
+ifneq ($(USE_RTTI), 0)
+ CXXFLAGS += -DROCKSDB_USE_RTTI
+else
+ CXXFLAGS += -fno-rtti
+endif
+
+$(warning Warning: Compiling in debug mode. Don't use the resulting binary in production)
+endif
+
+#-----------------------------------------------
+include src.mk
+
+AM_DEFAULT_VERBOSITY = 0
+
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+am__v_at_1 =
+
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+
+ifdef ROCKSDB_USE_LIBRADOS
+LIB_SOURCES += utilities/env_librados.cc
+LDFLAGS += -lrados
+endif
+
+AM_LINK = $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
+# detect what platform we're building on
+dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; export PORTABLE="$(PORTABLE)"; "$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
+# this file is generated by the previous line to set build flags and sources
+include make_config.mk
+CLEAN_FILES += make_config.mk
+
+missing_make_config_paths := $(shell \
+ grep "\./\S*\|/\S*" -o $(CURDIR)/make_config.mk | \
+ while read path; \
+ do [ -e $$path ] || echo $$path; \
+ done | sort | uniq)
+
+$(foreach path, $(missing_make_config_paths), \
+ $(warning Warning: $(path) dont exist))
+
+ifeq ($(PLATFORM), OS_AIX)
+# no debug info
+else ifneq ($(PLATFORM), IOS)
+CFLAGS += -g
+CXXFLAGS += -g
+else
+# no debug info for IOS, that will make our library big
+OPT += -DNDEBUG
+endif
+
+ifeq ($(PLATFORM), OS_AIX)
+ARFLAGS = -X64 rs
+STRIPFLAGS = -X64 -x
+endif
+
+ifeq ($(PLATFORM), OS_SOLARIS)
+ PLATFORM_CXXFLAGS += -D _GLIBCXX_USE_C99
+endif
+ifneq ($(filter -DROCKSDB_LITE,$(OPT)),)
+ # found
+ CFLAGS += -fno-exceptions
+ CXXFLAGS += -fno-exceptions
+ # LUA is not supported under ROCKSDB_LITE
+ LUA_PATH =
+endif
+
+# ASAN doesn't work well with jemalloc. If we're compiling with ASAN, we should use regular malloc.
+ifdef COMPILE_WITH_ASAN
+ DISABLE_JEMALLOC=1
+ EXEC_LDFLAGS += -fsanitize=address
+ PLATFORM_CCFLAGS += -fsanitize=address
+ PLATFORM_CXXFLAGS += -fsanitize=address
+endif
+
+# TSAN doesn't work well with jemalloc. If we're compiling with TSAN, we should use regular malloc.
+ifdef COMPILE_WITH_TSAN
+ DISABLE_JEMALLOC=1
+ EXEC_LDFLAGS += -fsanitize=thread
+ PLATFORM_CCFLAGS += -fsanitize=thread -fPIC -DFOLLY_SANITIZE_THREAD
+ PLATFORM_CXXFLAGS += -fsanitize=thread -fPIC -DFOLLY_SANITIZE_THREAD
+ # Turn off -pg when enabling TSAN testing, because that induces
+ # a link failure. TODO: find the root cause
+ PROFILING_FLAGS =
+ # LUA is not supported under TSAN
+ LUA_PATH =
+ # Limit keys for crash test under TSAN to avoid error:
+ # "ThreadSanitizer: DenseSlabAllocator overflow. Dying."
+ CRASH_TEST_EXT_ARGS += --max_key=1000000
+endif
+
+# AIX doesn't work with -pg
+ifeq ($(PLATFORM), OS_AIX)
+ PROFILING_FLAGS =
+endif
+
+# USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc.
+ifdef COMPILE_WITH_UBSAN
+ DISABLE_JEMALLOC=1
+ # Suppress alignment warning because murmurhash relies on casting unaligned
+ # memory to integer. Fixing it may cause performance regression. 3-way crc32
+ # relies on it too, although it can be rewritten to eliminate with minimal
+ # performance regression.
+ EXEC_LDFLAGS += -fsanitize=undefined -fno-sanitize-recover=all
+ PLATFORM_CCFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
+ PLATFORM_CXXFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
+endif
+
+ifdef ROCKSDB_VALGRIND_RUN
+ PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN
+ PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN
+endif
+
+ifndef DISABLE_JEMALLOC
+ ifdef JEMALLOC
+ PLATFORM_CXXFLAGS += -DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE
+ PLATFORM_CCFLAGS += -DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE
+ endif
+ ifdef WITH_JEMALLOC_FLAG
+ PLATFORM_LDFLAGS += -ljemalloc
+ JAVA_LDFLAGS += -ljemalloc
+ endif
+ EXEC_LDFLAGS := $(JEMALLOC_LIB) $(EXEC_LDFLAGS)
+ PLATFORM_CXXFLAGS += $(JEMALLOC_INCLUDE)
+ PLATFORM_CCFLAGS += $(JEMALLOC_INCLUDE)
+endif
+
+ifndef USE_FOLLY_DISTRIBUTED_MUTEX
+ USE_FOLLY_DISTRIBUTED_MUTEX=0
+endif
+
+export GTEST_THROW_ON_FAILURE=1
+export GTEST_HAS_EXCEPTIONS=1
+GTEST_DIR = ./third-party/gtest-1.8.1/fused-src
+# AIX: pre-defined system headers are surrounded by an extern "C" block
+ifeq ($(PLATFORM), OS_AIX)
+ PLATFORM_CCFLAGS += -I$(GTEST_DIR)
+ PLATFORM_CXXFLAGS += -I$(GTEST_DIR)
+else
+ PLATFORM_CCFLAGS += -isystem $(GTEST_DIR)
+ PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
+endif
+
+ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
+ FOLLY_DIR = ./third-party/folly
+ # AIX: pre-defined system headers are surrounded by an extern "C" block
+ ifeq ($(PLATFORM), OS_AIX)
+ PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
+ PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
+ else
+ PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
+ PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
+ endif
+endif
+
+ifdef TEST_CACHE_LINE_SIZE
+ PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
+ PLATFORM_CXXFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
+endif
+
+# This (the first rule) must depend on "all".
+default: all
+
+WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
+ -Wunused-parameter
+
+ifeq ($(PLATFORM), OS_OPENBSD)
+ WARNING_FLAGS += -Wno-unused-lambda-capture
+endif
+
+ifndef DISABLE_WARNING_AS_ERROR
+ WARNING_FLAGS += -Werror
+endif
+
+
+ifdef LUA_PATH
+
+ifndef LUA_INCLUDE
+LUA_INCLUDE=$(LUA_PATH)/include
+endif
+
+LUA_INCLUDE_FILE=$(LUA_INCLUDE)/lualib.h
+
+ifeq ("$(wildcard $(LUA_INCLUDE_FILE))", "")
+# LUA_INCLUDE_FILE does not exist
+$(error Cannot find lualib.h under $(LUA_INCLUDE). Try to specify both LUA_PATH and LUA_INCLUDE manually)
+endif
+LUA_FLAGS = -I$(LUA_INCLUDE) -DLUA -DLUA_COMPAT_ALL
+CFLAGS += $(LUA_FLAGS)
+CXXFLAGS += $(LUA_FLAGS)
+
+ifndef LUA_LIB
+LUA_LIB = $(LUA_PATH)/lib/liblua.a
+endif
+ifeq ("$(wildcard $(LUA_LIB))", "") # LUA_LIB does not exist
+$(error $(LUA_LIB) does not exist. Try to specify both LUA_PATH and LUA_LIB manually)
+endif
+EXEC_LDFLAGS += $(LUA_LIB)
+
+endif
+
+ifeq ($(NO_THREEWAY_CRC32C), 1)
+ CXXFLAGS += -DNO_THREEWAY_CRC32C
+endif
+
+CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
+CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
+
+LDFLAGS += $(PLATFORM_LDFLAGS)
+
+# If NO_UPDATE_BUILD_VERSION is set we don't update util/build_version.cc, but
+# the file needs to already exist or else the build will fail
+ifndef NO_UPDATE_BUILD_VERSION
+date := $(shell date +%F)
+ifdef FORCE_GIT_SHA
+ git_sha := $(FORCE_GIT_SHA)
+else
+ git_sha := $(shell git rev-parse HEAD 2>/dev/null)
+endif
+gen_build_version = sed -e s/@@GIT_SHA@@/$(git_sha)/ -e s/@@GIT_DATE_TIME@@/$(date)/ util/build_version.cc.in
+
+# Record the version of the source that we are compiling.
+# We keep a record of the git revision in this file. It is then built
+# as a regular source file as part of the compilation process.
+# One can run "strings executable_filename | grep _build_" to find
+# the version of the source that we used to build the executable file.
+FORCE:
+util/build_version.cc: FORCE
+ $(AM_V_GEN)rm -f $@-t
+ $(AM_V_at)$(gen_build_version) > $@-t
+ $(AM_V_at)if test -f $@; then \
+ cmp -s $@-t $@ && rm -f $@-t || mv -f $@-t $@; \
+ else mv -f $@-t $@; fi
+endif
+
+LIBOBJECTS = $(LIB_SOURCES:.cc=.o)
+ifeq ($(HAVE_POWER8),1)
+LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
+LIBOBJECTS += $(LIB_SOURCES_C:.c=.o)
+LIBOBJECTS += $(LIB_SOURCES_ASM:.S=.o)
+else
+LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
+endif
+
+LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o)
+MOCKOBJECTS = $(MOCK_LIB_SOURCES:.cc=.o)
+ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
+ FOLLYOBJECTS = $(FOLLY_SOURCES:.cpp=.o)
+endif
+
+GTEST = $(GTEST_DIR)/gtest/gtest-all.o
+TESTUTIL = ./test_util/testutil.o
+TESTHARNESS = ./test_util/testharness.o $(TESTUTIL) $(MOCKOBJECTS) $(GTEST)
+VALGRIND_ERROR = 2
+VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
+
+VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
+
+BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
+
+ANALYZETOOLOBJECTS = $(ANALYZER_LIB_SOURCES:.cc=.o)
+
+STRESSTOOLOBJECTS = $(STRESS_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
+
+EXPOBJECTS = $(LIBOBJECTS) $(TESTUTIL)
+
+TESTS = \
+ db_basic_test \
+ db_encryption_test \
+ db_test2 \
+ external_sst_file_basic_test \
+ auto_roll_logger_test \
+ bloom_test \
+ dynamic_bloom_test \
+ c_test \
+ checkpoint_test \
+ crc32c_test \
+ coding_test \
+ inlineskiplist_test \
+ env_basic_test \
+ env_test \
+ env_logger_test \
+ hash_test \
+ random_test \
+ thread_local_test \
+ rate_limiter_test \
+ perf_context_test \
+ iostats_context_test \
+ db_wal_test \
+ db_block_cache_test \
+ db_test \
+ db_blob_index_test \
+ db_iter_test \
+ db_iter_stress_test \
+ db_log_iter_test \
+ db_bloom_filter_test \
+ db_compaction_filter_test \
+ db_compaction_test \
+ db_dynamic_level_test \
+ db_flush_test \
+ db_inplace_update_test \
+ db_iterator_test \
+ db_memtable_test \
+ db_merge_operator_test \
+ db_merge_operand_test \
+ db_options_test \
+ db_range_del_test \
+ db_secondary_test \
+ db_sst_test \
+ db_tailing_iter_test \
+ db_io_failure_test \
+ db_properties_test \
+ db_table_properties_test \
+ db_statistics_test \
+ db_write_test \
+ error_handler_test \
+ autovector_test \
+ blob_db_test \
+ cleanable_test \
+ column_family_test \
+ table_properties_collector_test \
+ arena_test \
+ block_test \
+ data_block_hash_index_test \
+ cache_test \
+ corruption_test \
+ slice_test \
+ slice_transform_test \
+ dbformat_test \
+ fault_injection_test \
+ filelock_test \
+ filename_test \
+ file_reader_writer_test \
+ block_based_filter_block_test \
+ full_filter_block_test \
+ partitioned_filter_block_test \
+ hash_table_test \
+ histogram_test \
+ log_test \
+ manual_compaction_test \
+ mock_env_test \
+ memtable_list_test \
+ merge_helper_test \
+ memory_test \
+ merge_test \
+ merger_test \
+ util_merge_operators_test \
+ options_file_test \
+ reduce_levels_test \
+ plain_table_db_test \
+ comparator_db_test \
+ external_sst_file_test \
+ import_column_family_test \
+ prefix_test \
+ skiplist_test \
+ write_buffer_manager_test \
+ stringappend_test \
+ cassandra_format_test \
+ cassandra_functional_test \
+ cassandra_row_merge_test \
+ cassandra_serialize_test \
+ ttl_test \
+ backupable_db_test \
+ cache_simulator_test \
+ sim_cache_test \
+ version_edit_test \
+ version_set_test \
+ compaction_picker_test \
+ version_builder_test \
+ file_indexer_test \
+ write_batch_test \
+ write_batch_with_index_test \
+ write_controller_test\
+ deletefile_test \
+ obsolete_files_test \
+ table_test \
+ delete_scheduler_test \
+ options_test \
+ options_settable_test \
+ options_util_test \
+ event_logger_test \
+ timer_queue_test \
+ cuckoo_table_builder_test \
+ cuckoo_table_reader_test \
+ cuckoo_table_db_test \
+ flush_job_test \
+ wal_manager_test \
+ listener_test \
+ compaction_iterator_test \
+ compaction_job_test \
+ thread_list_test \
+ sst_dump_test \
+ compact_files_test \
+ optimistic_transaction_test \
+ write_callback_test \
+ heap_test \
+ compact_on_deletion_collector_test \
+ compaction_job_stats_test \
+ option_change_migration_test \
+ transaction_test \
+ ldb_cmd_test \
+ persistent_cache_test \
+ statistics_test \
+ stats_history_test \
+ lru_cache_test \
+ object_registry_test \
+ repair_test \
+ env_timed_test \
+ write_prepared_transaction_test \
+ write_unprepared_transaction_test \
+ db_universal_compaction_test \
+ trace_analyzer_test \
+ repeatable_thread_test \
+ range_tombstone_fragmenter_test \
+ range_del_aggregator_test \
+ sst_file_reader_test \
+ db_secondary_test \
+ block_cache_tracer_test \
+ block_cache_trace_analyzer_test \
+ defer_test \
+
+ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
+ TESTS += folly_synchronization_distributed_mutex_test
+endif
+
+PARALLEL_TEST = \
+ backupable_db_test \
+ db_bloom_filter_test \
+ db_compaction_filter_test \
+ db_compaction_test \
+ db_merge_operator_test \
+ db_sst_test \
+ db_test \
+ db_universal_compaction_test \
+ db_wal_test \
+ external_sst_file_test \
+ import_column_family_test \
+ fault_injection_test \
+ file_reader_writer_test \
+ inlineskiplist_test \
+ manual_compaction_test \
+ persistent_cache_test \
+ table_test \
+ transaction_test \
+ write_prepared_transaction_test \
+ write_unprepared_transaction_test \
+
+# options_settable_test doesn't pass with UBSAN as we use hack in the test
+ifdef COMPILE_WITH_UBSAN
+ TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
+endif
+SUBSET := $(TESTS)
+ifdef ROCKSDBTESTS_START
+ SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/')
+endif
+
+ifdef ROCKSDBTESTS_END
+ SUBSET := $(shell echo $(SUBSET) | sed 's/$(ROCKSDBTESTS_END).*//')
+endif
+
+TOOLS = \
+ sst_dump \
+ db_sanity_test \
+ db_stress \
+ write_stress \
+ ldb \
+ db_repl_stress \
+ rocksdb_dump \
+ rocksdb_undump \
+ blob_dump \
+ trace_analyzer \
+ block_cache_trace_analyzer \
+
+TEST_LIBS = \
+ librocksdb_env_basic_test.a
+
+# TODO: add back forward_iterator_bench, after making it build in all environemnts.
+BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench filter_bench persistent_cache_bench range_del_aggregator_bench
+
+# if user didn't config LIBNAME, set the default
+ifeq ($(LIBNAME),)
+# we should only run rocksdb in production with DEBUG_LEVEL 0
+ifeq ($(DEBUG_LEVEL),0)
+ LIBNAME=librocksdb
+else
+ LIBNAME=librocksdb_debug
+endif
+endif
+LIBRARY = ${LIBNAME}.a
+TOOLS_LIBRARY = ${LIBNAME}_tools.a
+STRESS_LIBRARY = ${LIBNAME}_stress.a
+
+ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
+ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
+ROCKSDB_PATCH = $(shell egrep "ROCKSDB_PATCH.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
+
+default: all
+
+#-----------------------------------------------
+# Create platform independent shared libraries.
+#-----------------------------------------------
+ifneq ($(PLATFORM_SHARED_EXT),)
+
+ifneq ($(PLATFORM_SHARED_VERSIONED),true)
+SHARED1 = ${LIBNAME}.$(PLATFORM_SHARED_EXT)
+SHARED2 = $(SHARED1)
+SHARED3 = $(SHARED1)
+SHARED4 = $(SHARED1)
+SHARED = $(SHARED1)
+else
+SHARED_MAJOR = $(ROCKSDB_MAJOR)
+SHARED_MINOR = $(ROCKSDB_MINOR)
+SHARED_PATCH = $(ROCKSDB_PATCH)
+SHARED1 = ${LIBNAME}.$(PLATFORM_SHARED_EXT)
+ifeq ($(PLATFORM), OS_MACOSX)
+SHARED_OSX = $(LIBNAME).$(SHARED_MAJOR)
+SHARED2 = $(SHARED_OSX).$(PLATFORM_SHARED_EXT)
+SHARED3 = $(SHARED_OSX).$(SHARED_MINOR).$(PLATFORM_SHARED_EXT)
+SHARED4 = $(SHARED_OSX).$(SHARED_MINOR).$(SHARED_PATCH).$(PLATFORM_SHARED_EXT)
+else
+SHARED2 = $(SHARED1).$(SHARED_MAJOR)
+SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
+SHARED4 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR).$(SHARED_PATCH)
+endif
+SHARED = $(SHARED1) $(SHARED2) $(SHARED3) $(SHARED4)
+$(SHARED1): $(SHARED4)
+ ln -fs $(SHARED4) $(SHARED1)
+$(SHARED2): $(SHARED4)
+ ln -fs $(SHARED4) $(SHARED2)
+$(SHARED3): $(SHARED4)
+ ln -fs $(SHARED4) $(SHARED3)
+endif
+ifeq ($(HAVE_POWER8),1)
+SHARED_C_OBJECTS = $(LIB_SOURCES_C:.c=.o)
+SHARED_ASM_OBJECTS = $(LIB_SOURCES_ASM:.S=.o)
+SHARED_C_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_C_OBJECTS))
+SHARED_ASM_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_ASM_OBJECTS))
+shared_libobjects = $(patsubst %,shared-objects/%,$(LIB_CC_OBJECTS))
+else
+shared_libobjects = $(patsubst %,shared-objects/%,$(LIBOBJECTS))
+endif
+
+CLEAN_FILES += shared-objects
+shared_all_libobjects = $(shared_libobjects)
+
+ifeq ($(HAVE_POWER8),1)
+shared-ppc-objects = $(SHARED_C_LIBOBJECTS) $(SHARED_ASM_LIBOBJECTS)
+
+shared-objects/util/crc32c_ppc.o: util/crc32c_ppc.c
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+
+shared-objects/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+endif
+$(shared_libobjects): shared-objects/%.o: %.cc
+ $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) -c $< -o $@
+
+ifeq ($(HAVE_POWER8),1)
+shared_all_libobjects = $(shared_libobjects) $(shared-ppc-objects)
+endif
+$(SHARED4): $(shared_all_libobjects)
+ $(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(shared_all_libobjects) $(LDFLAGS) -o $@
+
+endif # PLATFORM_SHARED_EXT
+
+.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
+ release tags tags0 valgrind_check whitebox_crash_test format static_lib shared_lib all \
+ dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
+ analyze tools tools_lib \
+ blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush \
+ blackbox_crash_test_with_txn whitebox_crash_test_with_txn
+
+
+all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
+
+all_but_some_tests: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(SUBSET)
+
+static_lib: $(LIBRARY)
+
+shared_lib: $(SHARED)
+
+stress_lib: $(STRESS_LIBRARY)
+
+tools: $(TOOLS)
+
+tools_lib: $(TOOLS_LIBRARY)
+
+test_libs: $(TEST_LIBS)
+
+dbg: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
+
+# creates static library and programs
+release:
+ $(MAKE) clean
+ DEBUG_LEVEL=0 $(MAKE) static_lib tools db_bench
+
+coverage:
+ $(MAKE) clean
+ COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) J=1 all check
+ cd coverage && ./coverage_test.sh
+ # Delete intermediate files
+ $(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
+
+ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
+# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
+# and create a randomly-named rocksdb.XXXX directory therein.
+# We'll use that directory in the "make check" rules.
+ifeq ($(TMPD),)
+TMPDIR := $(shell echo $${TMPDIR:-/tmp})
+TMPD := $(shell f=/dev/shm; test -k $$f || f=$(TMPDIR); \
+ perl -le 'use File::Temp "tempdir";' \
+ -e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
+endif
+endif
+
+# Run all tests in parallel, accumulating per-test logs in t/log-*.
+#
+# Each t/run-* file is a tiny generated bourne shell script that invokes one of
+# sub-tests. Why use a file for this? Because that makes the invocation of
+# parallel below simpler, which in turn makes the parsing of parallel's
+# LOG simpler (the latter is for live monitoring as parallel
+# tests run).
+#
+# Test names are extracted by running tests 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
+#
+# into this:
+#
+# DBTest.Empty
+# DBTest.WriteEmptyBatch
+# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
+# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
+#
+
+parallel_tests = $(patsubst %,parallel_%,$(PARALLEL_TEST))
+.PHONY: gen_parallel_tests $(parallel_tests)
+$(parallel_tests): $(PARALLEL_TEST)
+ $(AM_V_at)TEST_BINARY=$(patsubst parallel_%,%,$@); \
+ TEST_NAMES=` \
+ ./$$TEST_BINARY --gtest_list_tests \
+ | perl -n \
+ -e 's/ *\#.*//;' \
+ -e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
+ -e 'print qq! $$p$$2!'`; \
+ for TEST_NAME in $$TEST_NAMES; do \
+ TEST_SCRIPT=t/run-$$TEST_BINARY-$${TEST_NAME//\//-}; \
+ echo " GEN " $$TEST_SCRIPT; \
+ printf '%s\n' \
+ '#!/bin/sh' \
+ "d=\$(TMPD)$$TEST_SCRIPT" \
+ 'mkdir -p $$d' \
+ "TEST_TMPDIR=\$$d $(DRIVER) ./$$TEST_BINARY --gtest_filter=$$TEST_NAME" \
+ > $$TEST_SCRIPT; \
+ chmod a=rx $$TEST_SCRIPT; \
+ done
+
+gen_parallel_tests:
+ $(AM_V_at)mkdir -p t
+ $(AM_V_at)rm -f t/run-*
+ $(MAKE) $(parallel_tests)
+
+# Reorder input lines (which are one per test) so that the
+# longest-running tests appear first in the output.
+# Do this by prefixing each selected name with its duration,
+# sort the resulting names, and remove the leading numbers.
+# FIXME: the "100" we prepend is a fake time, for now.
+# FIXME: squirrel away timings from each run and use them
+# (when present) on subsequent runs to order these tests.
+#
+# Without this reordering, these two tests would happen to start only
+# after almost all other tests had completed, thus adding 100 seconds
+# to the duration of parallel "make check". That's the difference
+# between 4 minutes (old) and 2m20s (new).
+#
+# 152.120 PASS t/DBTest.FileCreationRandomFailure
+# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
+#
+slow_test_regexp = \
+ ^.*SnapshotConcurrentAccessTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
+prioritize_long_running_tests = \
+ perl -pe 's,($(slow_test_regexp)),100 $$1,' \
+ | sort -k1,1gr \
+ | sed 's/^[.0-9]* //'
+
+# "make check" uses
+# Run with "make J=1 check" to disable parallelism in "make check".
+# Run with "make J=200% check" to run two parallel jobs per core.
+# The default is to run one job per core (J=100%).
+# See "man parallel" for its "-j ..." option.
+J ?= 100%
+
+# Use this regexp to select the subset of tests whose names match.
+tests-regexp = .
+
+.PHONY: check_0
+check_0:
+ $(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
+ printf '%s\n' '' \
+ 'To monitor subtest <duration,pass/fail,name>,' \
+ ' run "make watch-log" in a separate window' ''; \
+ test -t 1 && eta=--eta || eta=; \
+ { \
+ printf './%s\n' $(filter-out $(PARALLEL_TEST),$(TESTS)); \
+ find t -name 'run-*' -print; \
+ } \
+ | $(prioritize_long_running_tests) \
+ | grep -E '$(tests-regexp)' \
+ | build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
+
+valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest
+
+.PHONY: valgrind_check_0
+valgrind_check_0:
+ $(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
+ printf '%s\n' '' \
+ 'To monitor subtest <duration,pass/fail,name>,' \
+ ' run "make watch-log" in a separate window' ''; \
+ test -t 1 && eta=--eta || eta=; \
+ { \
+ printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \
+ find t -name 'run-*' -print; \
+ } \
+ | $(prioritize_long_running_tests) \
+ | grep -E '$(tests-regexp)' \
+ | grep -E -v '$(valgrind-blacklist-regexp)' \
+ | build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu \
+ '(if [[ "{}" == "./"* ]] ; then $(DRIVER) {}; else {}; fi) ' \
+ '>& t/valgrind_log-{/}'
+
+CLEAN_FILES += t LOG $(TMPD)
+
+# When running parallel "make check", you can monitor its progress
+# from another window.
+# Run "make watch_LOG" to show the duration,PASS/FAIL,name of parallel
+# tests as they are being run. We sort them so that longer-running ones
+# appear at the top of the list and any failing tests remain at the top
+# regardless of their duration. As with any use of "watch", hit ^C to
+# interrupt.
+watch-log:
+ $(WATCH) --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)'
+
+# If J != 1 and GNU parallel is installed, run the tests in parallel,
+# via the check_0 rule above. Otherwise, run them sequentially.
+check: all
+ $(MAKE) gen_parallel_tests
+ $(AM_V_GEN)if test "$(J)" != 1 \
+ && (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
+ grep -q 'GNU Parallel'; \
+ then \
+ $(MAKE) T="$$t" TMPD=$(TMPD) check_0; \
+ else \
+ for t in $(TESTS); do \
+ echo "===== Running $$t"; ./$$t || exit 1; done; \
+ fi
+ rm -rf $(TMPD)
+ifneq ($(PLATFORM), OS_AIX)
+ python tools/check_all_python.py
+ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
+ python tools/ldb_test.py
+ sh tools/rocksdb_dump_test.sh
+endif
+endif
+
+# TODO add ldb_tests
+check_some: $(SUBSET)
+ for t in $(SUBSET); do echo "===== Running $$t"; ./$$t || exit 1; done
+
+.PHONY: ldb_tests
+ldb_tests: ldb
+ python tools/ldb_test.py
+
+crash_test: whitebox_crash_test blackbox_crash_test
+
+crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_crash_test_with_atomic_flush
+
+crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
+
+blackbox_crash_test: db_stress
+ python -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
+ python -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
+
+blackbox_crash_test_with_atomic_flush: db_stress
+ python -u tools/db_crashtest.py --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
+
+blackbox_crash_test_with_txn: db_stress
+ python -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
+
+ifeq ($(CRASH_TEST_KILL_ODD),)
+ CRASH_TEST_KILL_ODD=888887
+endif
+
+whitebox_crash_test: db_stress
+ python -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
+ $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
+ python -u tools/db_crashtest.py whitebox --random_kill_odd \
+ $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
+
+whitebox_crash_test_with_atomic_flush: db_stress
+ python -u tools/db_crashtest.py --cf_consistency whitebox --random_kill_odd \
+ $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
+
+whitebox_crash_test_with_txn: db_stress
+ python -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
+ $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
+
+asan_check:
+ $(MAKE) clean
+ COMPILE_WITH_ASAN=1 $(MAKE) check -j32
+ $(MAKE) clean
+
+asan_crash_test:
+ $(MAKE) clean
+ COMPILE_WITH_ASAN=1 $(MAKE) crash_test
+ $(MAKE) clean
+
+asan_crash_test_with_atomic_flush:
+ $(MAKE) clean
+ COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_atomic_flush
+ $(MAKE) clean
+
+asan_crash_test_with_txn:
+ $(MAKE) clean
+ COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_txn
+ $(MAKE) clean
+
+ubsan_check:
+ $(MAKE) clean
+ COMPILE_WITH_UBSAN=1 $(MAKE) check -j32
+ $(MAKE) clean
+
+ubsan_crash_test:
+ $(MAKE) clean
+ COMPILE_WITH_UBSAN=1 $(MAKE) crash_test
+ $(MAKE) clean
+
+ubsan_crash_test_with_atomic_flush:
+ $(MAKE) clean
+ COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_atomic_flush
+ $(MAKE) clean
+
+ubsan_crash_test_with_txn:
+ $(MAKE) clean
+ COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_txn
+ $(MAKE) clean
+
+valgrind_test:
+ ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
+
+valgrind_check: $(TESTS)
+ $(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests
+ $(AM_V_GEN)if test "$(J)" != 1 \
+ && (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
+ grep -q 'GNU Parallel'; \
+ then \
+ $(MAKE) TMPD=$(TMPD) \
+ DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" valgrind_check_0; \
+ else \
+ for t in $(filter-out %skiplist_test options_settable_test,$(TESTS)); do \
+ $(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
+ ret_code=$$?; \
+ if [ $$ret_code -ne 0 ]; then \
+ exit $$ret_code; \
+ fi; \
+ done; \
+ fi
+
+
+ifneq ($(PAR_TEST),)
+parloop:
+ ret_bad=0; \
+ for t in $(PAR_TEST); do \
+ echo "===== Running $$t in parallel $(NUM_PAR)";\
+ if [ $(db_test) -eq 1 ]; then \
+ seq $(J) | v="$$t" build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{}; export TEST_TMPDIR=$$s;' \
+ 'timeout 2m ./db_test --gtest_filter=$$v >> $$s/log-{} 2>1'; \
+ else\
+ seq $(J) | v="./$$t" build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{};' \
+ 'export TEST_TMPDIR=$$s; timeout 10m $$v >> $$s/log-{} 2>1'; \
+ fi; \
+ ret_code=$$?; \
+ if [ $$ret_code -ne 0 ]; then \
+ ret_bad=$$ret_code; \
+ echo $$t exited with $$ret_code; \
+ fi; \
+ done; \
+ exit $$ret_bad;
+endif
+
+test_names = \
+ ./db_test --gtest_list_tests \
+ | perl -n \
+ -e 's/ *\#.*//;' \
+ -e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
+ -e 'print qq! $$p$$2!'
+
+parallel_check: $(TESTS)
+ $(AM_V_GEN)if test "$(J)" > 1 \
+ && (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
+ grep -q 'GNU Parallel'; \
+ then \
+ echo Running in parallel $(J); \
+ else \
+ echo "Need to have GNU Parallel and J > 1"; exit 1; \
+ fi; \
+ ret_bad=0; \
+ echo $(J);\
+ echo Test Dir: $(TMPD); \
+ seq $(J) | build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{}; rm -rf $$s; mkdir $$s'; \
+ $(MAKE) PAR_TEST="$(shell $(test_names))" TMPD=$(TMPD) \
+ J=$(J) db_test=1 parloop; \
+ $(MAKE) PAR_TEST="$(filter-out db_test, $(TESTS))" \
+ TMPD=$(TMPD) J=$(J) db_test=0 parloop;
+
+analyze: clean
+ USE_CLANG=1 $(MAKE) analyze_incremental
+
+analyze_incremental:
+ $(CLANG_SCAN_BUILD) --use-analyzer=$(CLANG_ANALYZER) \
+ --use-c++=$(CXX) --use-cc=$(CC) --status-bugs \
+ -o $(CURDIR)/scan_build_report \
+ $(MAKE) dbg
+
+CLEAN_FILES += unity.cc
+unity.cc: Makefile
+ rm -f $@ $@-t
+ for source_file in $(LIB_SOURCES); do \
+ echo "#include \"$$source_file\"" >> $@-t; \
+ done
+ chmod a=r $@-t
+ mv $@-t $@
+
+unity.a: unity.o
+ $(AM_V_AR)rm -f $@
+ $(AM_V_at)$(AR) $(ARFLAGS) $@ unity.o
+
+
+TOOLLIBOBJECTS = $(TOOL_LIB_SOURCES:.cc=.o)
+# try compiling db_test with unity
+unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) $(TOOLLIBOBJECTS) unity.a
+ $(AM_LINK)
+ ./unity_test
+
+rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc
+ build_tools/amalgamate.py -I. -i./include unity.cc -x include/rocksdb/c.h -H rocksdb.h -o rocksdb.cc
+
+clean: clean-ext-libraries-all clean-rocks clean-rocksjava
+
+clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-rocksjava
+
+clean-rocks:
+ rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(LIBRARY) $(SHARED)
+ rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
+ $(FIND) . -name "*.[oda]" -exec rm -f {} \;
+ $(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
+
+clean-rocksjava:
+ cd java && $(MAKE) clean
+
+clean-not-downloaded-rocksjava:
+ cd java && $(MAKE) clean-not-downloaded
+
+clean-ext-libraries-all:
+ rm -rf bzip2* snappy* zlib* lz4* zstd*
+
+clean-ext-libraries-bin:
+ find . -maxdepth 1 -type d \( -name bzip2\* -or -name snappy\* -or -name zlib\* -or -name lz4\* -or -name zstd\* \) -prune -exec rm -rf {} \;
+
+tags:
+ ctags -R .
+ cscope -b `$(FIND) . -name '*.cc'` `$(FIND) . -name '*.h'` `$(FIND) . -name '*.c'`
+ ctags -e -R -o etags *
+
+tags0:
+ ctags -R .
+ cscope -b `$(FIND) . -name '*.cc' -and ! -name '*_test.cc'` \
+ `$(FIND) . -name '*.c' -and ! -name '*_test.c'` \
+ `$(FIND) . -name '*.h' -and ! -name '*_test.h'`
+ ctags -e -R -o etags *
+
+format:
+ build_tools/format-diff.sh
+
+package:
+ bash build_tools/make_package.sh $(SHARED_MAJOR).$(SHARED_MINOR)
+
+# ---------------------------------------------------------------------------
+# Unit tests and tools
+# ---------------------------------------------------------------------------
+$(LIBRARY): $(LIBOBJECTS)
+ $(AM_V_AR)rm -f $@
+ $(AM_V_at)$(AR) $(ARFLAGS) $@ $(LIBOBJECTS)
+
+$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL) $(ANALYZER_LIB_SOURCES:.cc=.o)
+ $(AM_V_AR)rm -f $@
+ $(AM_V_at)$(AR) $(ARFLAGS) $@ $^
+
+$(STRESS_LIBRARY): $(LIB_SOURCES:.cc=.o) $(TESTUTIL) $(ANALYZER_LIB_SOURCES:.cc=.o) $(STRESS_LIB_SOURCES:.cc=.o)
+ $(AM_V_AR)rm -f $@
+ $(AM_V_at)$(AR) $(ARFLAGS) $@ $^
+
+librocksdb_env_basic_test.a: env/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_V_AR)rm -f $@
+ $(AM_V_at)$(AR) $(ARFLAGS) $@ $^
+
+db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS)
+ $(AM_LINK)
+
+trace_analyzer: tools/trace_analyzer.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
+ $(AM_LINK)
+
+block_cache_trace_analyzer: tools/block_cache_analyzer/block_cache_trace_analyzer_tool.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
+ $(AM_LINK)
+
+ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
+folly_synchronization_distributed_mutex_test: $(LIBOBJECTS) $(TESTHARNESS) $(FOLLYOBJECTS) third-party/folly/folly/synchronization/test/DistributedMutexTest.o
+ $(AM_LINK)
+endif
+
+cache_bench: cache/cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+persistent_cache_bench: utilities/persistent_cache/persistent_cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+memtablerep_bench: memtable/memtablerep_bench.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+filter_bench: util/filter_bench.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+db_stress: db_stress_tool/db_stress.o $(STRESSTOOLOBJECTS)
+ $(AM_LINK)
+
+write_stress: tools/write_stress.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+db_sanity_test: tools/db_sanity_test.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+db_repl_stress: tools/db_repl_stress.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+arena_test: memory/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+autovector_test: util/autovector_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+column_family_test: db/column_family_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+table_properties_collector_test: db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+bloom_test: util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+dynamic_bloom_test: util/dynamic_bloom_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+c_test: db/c_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cache_test: cache/cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+coding_test: util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+hash_test: util/hash_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+random_test: util/random_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+option_change_migration_test: utilities/option_change_migration/option_change_migration_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+stringappend_test: utilities/merge_operators/string_append/stringappend_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cassandra_format_test: utilities/cassandra/cassandra_format_test.o utilities/cassandra/test_utils.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cassandra_functional_test: utilities/cassandra/cassandra_functional_test.o utilities/cassandra/test_utils.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cassandra_row_merge_test: utilities/cassandra/cassandra_row_merge_test.o utilities/cassandra/test_utils.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cassandra_serialize_test: utilities/cassandra/cassandra_serialize_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+hash_table_test: utilities/persistent_cache/hash_table_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+histogram_test: monitoring/histogram_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+thread_local_test: util/thread_local_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+corruption_test: db/corruption_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+crc32c_test: util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+slice_test: util/slice_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+slice_transform_test: util/slice_transform_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_basic_test: db/db_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_encryption_test: db/db_encryption_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_test: db/db_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_test2: db/db_test2.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_blob_index_test: db/db_blob_index_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_block_cache_test: db/db_block_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_bloom_filter_test: db/db_bloom_filter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_log_iter_test: db/db_log_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_compaction_filter_test: db/db_compaction_filter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_compaction_test: db/db_compaction_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_dynamic_level_test: db/db_dynamic_level_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_flush_test: db/db_flush_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_inplace_update_test: db/db_inplace_update_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_memtable_test: db/db_memtable_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_merge_operator_test: db/db_merge_operator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_merge_operand_test: db/db_merge_operand_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_options_test: db/db_options_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_range_del_test: db/db_range_del_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_sst_test: db/db_sst_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_statistics_test: db/db_statistics_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_write_test: db/db_write_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+error_handler_test: db/error_handler_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+external_sst_file_basic_test: db/external_sst_file_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+external_sst_file_test: db/external_sst_file_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+import_column_family_test: db/import_column_family_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_tailing_iter_test: db/db_tailing_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_iter_test: db/db_iter_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_iter_stress_test: db/db_iter_stress_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_universal_compaction_test: db/db_universal_compaction_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_wal_test: db/db_wal_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_io_failure_test: db/db_io_failure_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_properties_test: db/db_properties_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_table_properties_test: db/db_table_properties_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+log_write_bench: util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK) $(PROFILING_FLAGS)
+
+plain_table_db_test: db/plain_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+comparator_db_test: db/comparator_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+table_reader_bench: table/table_reader_bench.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK) $(PROFILING_FLAGS)
+
+perf_context_test: db/perf_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
+
+prefix_test: db/prefix_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
+
+backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cache_simulator_test: utilities/simulator_cache/cache_simulator_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+env_timed_test: utilities/env_timed_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+ifdef ROCKSDB_USE_LIBRADOS
+env_librados_test: utilities/env_librados_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
+endif
+
+object_registry_test: utilities/object_registry_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_batch_with_index_test: utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+flush_job_test: db/flush_job_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compaction_iterator_test: db/compaction/compaction_iterator_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compaction_job_test: db/compaction/compaction_job_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compaction_job_stats_test: db/compaction/compaction_job_stats_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compact_on_deletion_collector_test: utilities/table_properties_collectors/compact_on_deletion_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+wal_manager_test: db/wal_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+env_basic_test: env/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+env_test: env/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+fault_injection_test: db/fault_injection_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+rate_limiter_test: util/rate_limiter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+delete_scheduler_test: file/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+file_reader_writer_test: util/file_reader_writer_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+block_based_filter_block_test: table/block_based/block_based_filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+full_filter_block_test: table/block_based/full_filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+partitioned_filter_block_test: table/block_based/partitioned_filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+log_test: db/log_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cleanable_test: table/cleanable_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+block_test: table/block_based/block_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+data_block_hash_index_test: table/block_based/data_block_hash_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+inlineskiplist_test: memtable/inlineskiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+skiplist_test: memtable/skiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_buffer_manager_test: memtable/write_buffer_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+version_edit_test: db/version_edit_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+version_set_test: db/version_set_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compaction_picker_test: db/compaction/compaction_picker_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+version_builder_test: db/version_builder_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+file_indexer_test: db/file_indexer_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+reduce_levels_test: tools/reduce_levels_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_batch_test: db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_controller_test: db/write_controller_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+merge_helper_test: db/merge_helper_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+memory_test: utilities/memory/memory_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+merger_test: table/merger_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+util_merge_operators_test: utilities/util_merge_operators_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+options_file_test: db/options_file_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+deletefile_test: db/deletefile_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+obsolete_files_test: db/obsolete_files_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+rocksdb_dump: tools/dump/rocksdb_dump.o $(LIBOBJECTS)
+ $(AM_LINK)
+
+rocksdb_undump: tools/dump/rocksdb_undump.o $(LIBOBJECTS)
+ $(AM_LINK)
+
+cuckoo_table_builder_test: table/cuckoo/cuckoo_table_builder_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cuckoo_table_reader_test: table/cuckoo/cuckoo_table_reader_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+cuckoo_table_db_test: db/cuckoo_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+listener_test: db/listener_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+thread_list_test: util/thread_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+compact_files_test: db/compact_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+options_test: options/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+options_settable_test: options/options_settable_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_bench_tool_test: tools/db_bench_tool_test.o $(BENCHTOOLOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+trace_analyzer_test: tools/trace_analyzer_test.o $(LIBOBJECTS) $(ANALYZETOOLOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+event_logger_test: logging/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+timer_queue_test: util/timer_queue_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+sst_dump_test: tools/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+mock_env_test : env/mock_env_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+manual_compaction_test: db/manual_compaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+filelock_test: util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+auto_roll_logger_test: logging/auto_roll_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+env_logger_test: logging/env_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+memtable_list_test: db/memtable_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_callback_test: db/write_callback_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+heap_test: util/heap_test.o $(GTEST)
+ $(AM_LINK)
+
+transaction_test: utilities/transactions/transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_prepared_transaction_test: utilities/transactions/write_prepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+write_unprepared_transaction_test: utilities/transactions/write_unprepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+sst_dump: tools/sst_dump.o $(LIBOBJECTS)
+ $(AM_LINK)
+
+blob_dump: tools/blob_dump.o $(LIBOBJECTS)
+ $(AM_LINK)
+
+repair_test: db/repair_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+ldb_cmd_test: tools/ldb_cmd_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+ldb: tools/ldb.o $(LIBOBJECTS)
+ $(AM_LINK)
+
+iostats_context_test: monitoring/iostats_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
+
+persistent_cache_test: utilities/persistent_cache/persistent_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+statistics_test: monitoring/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+stats_history_test: monitoring/stats_history_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+lru_cache_test: cache/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+range_del_aggregator_bench: db/range_del_aggregator_bench.o $(LIBOBJECTS) $(TESTUTIL)
+ $(AM_LINK)
+
+blob_db_test: utilities/blob_db/blob_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+repeatable_thread_test: util/repeatable_thread_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+range_tombstone_fragmenter_test: db/range_tombstone_fragmenter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+sst_file_reader_test: table/sst_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+db_secondary_test: db/db_impl/db_secondary_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+block_cache_tracer_test: trace_replay/block_cache_tracer_test.o trace_replay/block_cache_tracer.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+block_cache_trace_analyzer_test: tools/block_cache_analyzer/block_cache_trace_analyzer_test.o tools/block_cache_analyzer/block_cache_trace_analyzer.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+defer_test: util/defer_test.o $(LIBOBJECTS) $(TESTHARNESS)
+ $(AM_LINK)
+
+#-------------------------------------------------
+# make install related stuff
+INSTALL_PATH ?= /usr/local
+
+uninstall:
+ rm -rf $(INSTALL_PATH)/include/rocksdb \
+ $(INSTALL_PATH)/lib/$(LIBRARY) \
+ $(INSTALL_PATH)/lib/$(SHARED4) \
+ $(INSTALL_PATH)/lib/$(SHARED3) \
+ $(INSTALL_PATH)/lib/$(SHARED2) \
+ $(INSTALL_PATH)/lib/$(SHARED1)
+
+install-headers:
+ install -d $(INSTALL_PATH)/lib
+ for header_dir in `$(FIND) "include/rocksdb" -type d`; do \
+ install -d $(INSTALL_PATH)/$$header_dir; \
+ done
+ for header in `$(FIND) "include/rocksdb" -type f -name *.h`; do \
+ install -C -m 644 $$header $(INSTALL_PATH)/$$header; \
+ done
+
+install-static: install-headers $(LIBRARY)
+ install -C -m 755 $(LIBRARY) $(INSTALL_PATH)/lib
+
+install-shared: install-headers $(SHARED4)
+ install -C -m 755 $(SHARED4) $(INSTALL_PATH)/lib && \
+ ln -fs $(SHARED4) $(INSTALL_PATH)/lib/$(SHARED3) && \
+ ln -fs $(SHARED4) $(INSTALL_PATH)/lib/$(SHARED2) && \
+ ln -fs $(SHARED4) $(INSTALL_PATH)/lib/$(SHARED1)
+
+# install static by default + install shared if it exists
+install: install-static
+ [ -e $(SHARED4) ] && $(MAKE) install-shared || :
+
+#-------------------------------------------------
+
+
+# ---------------------------------------------------------------------------
+# Jni stuff
+# ---------------------------------------------------------------------------
+
+JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
+ifeq ($(PLATFORM), OS_SOLARIS)
+ ARCH := $(shell isainfo -b)
+else ifeq ($(PLATFORM), OS_OPENBSD)
+ ifneq (,$(filter amd64 ppc64 ppc64le arm64 aarch64 sparc64, $(MACHINE)))
+ ARCH := 64
+ else
+ ARCH := 32
+ endif
+else
+ ARCH := $(shell getconf LONG_BIT)
+endif
+
+ifeq ($(shell ldd /usr/bin/env 2>/dev/null | grep -q musl; echo $$?),0)
+ JNI_LIBC = musl
+# GNU LibC (or glibc) is so pervasive we can assume it is the default
+# else
+# JNI_LIBC = glibc
+endif
+
+ifneq ($(origin JNI_LIBC), undefined)
+ JNI_LIBC_POSTFIX = -$(JNI_LIBC)
+endif
+
+ifneq (,$(filter ppc% arm64 aarch64 sparc64, $(MACHINE)))
+ ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE)$(JNI_LIBC_POSTFIX).so
+else
+ ROCKSDBJNILIB = librocksdbjni-linux$(ARCH)$(JNI_LIBC_POSTFIX).so
+endif
+ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux$(ARCH)$(JNI_LIBC_POSTFIX).jar
+ROCKSDB_JAR_ALL = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar
+ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-javadoc.jar
+ROCKSDB_SOURCES_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-sources.jar
+SHA256_CMD = sha256sum
+
+ZLIB_VER ?= 1.2.11
+ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
+ZLIB_DOWNLOAD_BASE ?= http://zlib.net
+BZIP2_VER ?= 1.0.6
+BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
+BZIP2_DOWNLOAD_BASE ?= https://downloads.sourceforge.net/project/bzip2
+SNAPPY_VER ?= 1.1.7
+SNAPPY_SHA256 ?= 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4
+SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
+LZ4_VER ?= 1.9.2
+LZ4_SHA256 ?= 658ba6191fa44c92280d4aa2c271b0f4fbc0e34d249578dd05e50e76d0e5efcc
+LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
+ZSTD_VER ?= 1.4.4
+ZSTD_SHA256 ?= a364f5162c7d1a455cc915e8e3cf5f4bd8b75d09bc0f53965b0c9ca1383c52c8
+ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
+CURL_SSL_OPTS ?= --tlsv1
+
+ifeq ($(PLATFORM), OS_MACOSX)
+ ROCKSDBJNILIB = librocksdbjni-osx.jnilib
+ ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx.jar
+ SHA256_CMD = openssl sha256 -r
+ifneq ("$(wildcard $(JAVA_HOME)/include/darwin)","")
+ JAVA_INCLUDE = -I$(JAVA_HOME)/include -I $(JAVA_HOME)/include/darwin
+else
+ JAVA_INCLUDE = -I/System/Library/Frameworks/JavaVM.framework/Headers/
+endif
+endif
+ifeq ($(PLATFORM), OS_FREEBSD)
+ JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/freebsd
+ ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so
+ ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-freebsd$(ARCH).jar
+endif
+ifeq ($(PLATFORM), OS_SOLARIS)
+ ROCKSDBJNILIB = librocksdbjni-solaris$(ARCH).so
+ ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-solaris$(ARCH).jar
+ JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/solaris
+ SHA256_CMD = digest -a sha256
+endif
+ifeq ($(PLATFORM), OS_AIX)
+ JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/aix
+ ROCKSDBJNILIB = librocksdbjni-aix.so
+ EXTRACT_SOURCES = gunzip < TAR_GZ | tar xvf -
+ SNAPPY_MAKE_TARGET = libsnappy.la
+endif
+ifeq ($(PLATFORM), OS_OPENBSD)
+ JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
+ ROCKSDBJNILIB = librocksdbjni-openbsd$(ARCH).so
+ ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-openbsd$(ARCH).jar
+endif
+
+libz.a:
+ -rm -rf zlib-$(ZLIB_VER)
+ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz))
+ curl --fail --output zlib-$(ZLIB_VER).tar.gz --location ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
+endif
+ ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
+ if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
+ echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \
+ exit 1; \
+ fi
+ tar xvzf zlib-$(ZLIB_VER).tar.gz
+ cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
+ cp zlib-$(ZLIB_VER)/libz.a .
+
+libbz2.a:
+ -rm -rf bzip2-$(BZIP2_VER)
+ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz))
+ curl --fail --output bzip2-$(BZIP2_VER).tar.gz --location ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
+endif
+ BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
+ if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
+ echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \
+ exit 1; \
+ fi
+ tar xvzf bzip2-$(BZIP2_VER).tar.gz
+ cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
+ cp bzip2-$(BZIP2_VER)/libbz2.a .
+
+libsnappy.a:
+ -rm -rf snappy-$(SNAPPY_VER)
+ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz))
+ curl --fail --output snappy-$(SNAPPY_VER).tar.gz --location ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
+endif
+ SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
+ if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
+ echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
+ exit 1; \
+ fi
+ tar xvzf snappy-$(SNAPPY_VER).tar.gz
+ mkdir snappy-$(SNAPPY_VER)/build
+ cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
+ cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
+
+liblz4.a:
+ -rm -rf lz4-$(LZ4_VER)
+ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz))
+ curl --fail --output lz4-$(LZ4_VER).tar.gz --location ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
+endif
+ LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
+ if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
+ echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \
+ exit 1; \
+ fi
+ tar xvzf lz4-$(LZ4_VER).tar.gz
+ cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
+ cp lz4-$(LZ4_VER)/lib/liblz4.a .
+
+libzstd.a:
+ -rm -rf zstd-$(ZSTD_VER)
+ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz))
+ curl --fail --output zstd-$(ZSTD_VER).tar.gz --location ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
+endif
+ ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
+ if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
+ echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \
+ exit 1; \
+ fi
+ tar xvzf zstd-$(ZSTD_VER).tar.gz
+ cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
+ cp zstd-$(ZSTD_VER)/lib/libzstd.a .
+
+# A version of each $(LIBOBJECTS) compiled with -fPIC and a fixed set of static compression libraries
+java_static_libobjects = $(patsubst %,jls/%,$(LIB_CC_OBJECTS))
+CLEAN_FILES += jls
+java_static_all_libobjects = $(java_static_libobjects)
+
+ifneq ($(ROCKSDB_JAVA_NO_COMPRESSION), 1)
+JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a
+endif
+
+JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD
+JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include
+
+ifeq ($(HAVE_POWER8),1)
+JAVA_STATIC_C_LIBOBJECTS = $(patsubst %.c.o,jls/%.c.o,$(LIB_SOURCES_C:.c=.o))
+JAVA_STATIC_ASM_LIBOBJECTS = $(patsubst %.S.o,jls/%.S.o,$(LIB_SOURCES_ASM:.S=.o))
+
+java_static_ppc_libobjects = $(JAVA_STATIC_C_LIBOBJECTS) $(JAVA_STATIC_ASM_LIBOBJECTS)
+
+jls/util/crc32c_ppc.o: util/crc32c_ppc.c
+ $(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
+
+jls/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
+ $(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
+
+java_static_all_libobjects += $(java_static_ppc_libobjects)
+endif
+
+$(java_static_libobjects): jls/%.o: %.cc $(JAVA_COMPRESSIONS)
+ $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
+
+rocksdbjavastatic: $(java_static_all_libobjects)
+ cd java;$(MAKE) javalib;
+ rm -f ./java/target/$(ROCKSDBJNILIB)
+ $(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
+ -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
+ $(java_static_all_libobjects) $(COVERAGEFLAGS) \
+ $(JAVA_COMPRESSIONS) $(JAVA_STATIC_LDFLAGS)
+ cd java/target;if [ "$(DEBUG_LEVEL)" == "0" ]; then \
+ strip $(STRIPFLAGS) $(ROCKSDBJNILIB); \
+ fi
+ cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
+ cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB)
+ cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class
+ cd java/target/apidocs;jar -cf ../$(ROCKSDB_JAVADOCS_JAR) *
+ cd java/src/main/java;jar -cf ../../../target/$(ROCKSDB_SOURCES_JAR) org
+
+rocksdbjavastaticrelease: rocksdbjavastatic
+ cd java/crossbuild && (vagrant destroy -f || true) && vagrant up linux32 && vagrant halt linux32 && vagrant up linux64 && vagrant halt linux64 && vagrant up linux64-musl && vagrant halt linux64-musl
+ cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
+ cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
+ cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
+
+rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 rocksdbjavastaticdockerx86_64 rocksdbjavastaticdockerx86musl rocksdbjavastaticdockerx86_64musl
+ cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
+ cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
+ cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
+
+rocksdbjavastaticdockerx86:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_x86-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerx86_64:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_x64-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerppc64le:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_ppc64le-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerarm64v8:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_arm64v8-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerx86musl:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_x86-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerx86_64musl:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_x64-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerppc64lemusl:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_ppc64le-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticdockerarm64v8musl:
+ mkdir -p java/target
+ docker run --rm --name rocksdb_linux_arm64v8-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
+
+rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
+
+rocksdbjavastaticpublishdocker: rocksdbjavastaticreleasedocker rocksdbjavastaticpublishcentral
+
+rocksdbjavastaticpublishcentral:
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-javadoc.jar -Dclassifier=javadoc
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-sources.jar -Dclassifier=sources
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux64.jar -Dclassifier=linux64
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux32.jar -Dclassifier=linux32
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux64-musl.jar -Dclassifier=linux64-musl
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux32-musl.jar -Dclassifier=linux32-musl
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx.jar -Dclassifier=osx
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-win64.jar -Dclassifier=win64
+ mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar
+
+# A version of each $(LIBOBJECTS) compiled with -fPIC
+ifeq ($(HAVE_POWER8),1)
+JAVA_CC_OBJECTS = $(SHARED_CC_OBJECTS)
+JAVA_C_OBJECTS = $(SHARED_C_OBJECTS)
+JAVA_ASM_OBJECTS = $(SHARED_ASM_OBJECTS)
+
+JAVA_C_LIBOBJECTS = $(patsubst %.c.o,jl/%.c.o,$(JAVA_C_OBJECTS))
+JAVA_ASM_LIBOBJECTS = $(patsubst %.S.o,jl/%.S.o,$(JAVA_ASM_OBJECTS))
+endif
+
+java_libobjects = $(patsubst %,jl/%,$(LIB_CC_OBJECTS))
+CLEAN_FILES += jl
+java_all_libobjects = $(java_libobjects)
+
+ifeq ($(HAVE_POWER8),1)
+java_ppc_libobjects = $(JAVA_C_LIBOBJECTS) $(JAVA_ASM_LIBOBJECTS)
+
+jl/crc32c_ppc.o: util/crc32c_ppc.c
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+
+jl/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+java_all_libobjects += $(java_ppc_libobjects)
+endif
+
+$(java_libobjects): jl/%.o: %.cc
+ $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
+
+
+
+rocksdbjava: $(java_all_libobjects)
+ $(AM_V_GEN)cd java;$(MAKE) javalib;
+ $(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
+ $(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_all_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
+ $(AM_V_at)cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
+ $(AM_V_at)cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB)
+ $(AM_V_at)cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class
+
+jclean:
+ cd java;$(MAKE) clean;
+
+jtest_compile: rocksdbjava
+ cd java;$(MAKE) java_test
+
+jtest_run:
+ cd java;$(MAKE) run_test
+
+jtest: rocksdbjava
+ cd java;$(MAKE) sample;$(MAKE) test;
+ python tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
+
+jdb_bench:
+ cd java;$(MAKE) db_bench;
+
+commit_prereq: build_tools/rocksdb-lego-determinator \
+ build_tools/precommit_checker.py
+ J=$(J) build_tools/precommit_checker.py unit unit_481 clang_unit release release_481 clang_release tsan asan ubsan lite unit_non_shm
+ $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
+
+# ---------------------------------------------------------------------------
+# Platform-specific compilation
+# ---------------------------------------------------------------------------
+
+ifeq ($(PLATFORM), IOS)
+# For iOS, create universal object files to be used on both the simulator and
+# a device.
+XCODEROOT=$(shell xcode-select -print-path)
+PLATFORMSROOT=$(XCODEROOT)/Platforms
+SIMULATORROOT=$(PLATFORMSROOT)/iPhoneSimulator.platform/Developer
+DEVICEROOT=$(PLATFORMSROOT)/iPhoneOS.platform/Developer
+IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBundleShortVersionString)
+
+.cc.o:
+ mkdir -p ios-x86/$(dir $@)
+ $(CXX) $(CXXFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -arch x86_64 -c $< -o ios-x86/$@
+ mkdir -p ios-arm/$(dir $@)
+ xcrun -sdk iphoneos $(CXX) $(CXXFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk -arch armv6 -arch armv7 -arch armv7s -arch arm64 -c $< -o ios-arm/$@
+ lipo ios-x86/$@ ios-arm/$@ -create -output $@
+
+.c.o:
+ mkdir -p ios-x86/$(dir $@)
+ $(CC) $(CFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -arch x86_64 -c $< -o ios-x86/$@
+ mkdir -p ios-arm/$(dir $@)
+ xcrun -sdk iphoneos $(CC) $(CFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk -arch armv6 -arch armv7 -arch armv7s -arch arm64 -c $< -o ios-arm/$@
+ lipo ios-x86/$@ ios-arm/$@ -create -output $@
+
+else
+ifeq ($(HAVE_POWER8),1)
+util/crc32c_ppc.o: util/crc32c_ppc.c
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+
+util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+endif
+.cc.o:
+ $(AM_V_CC)$(CXX) $(CXXFLAGS) -c $< -o $@ $(COVERAGEFLAGS)
+
+.cpp.o:
+ $(AM_V_CC)$(CXX) $(CXXFLAGS) -c $< -o $@ $(COVERAGEFLAGS)
+
+.c.o:
+ $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
+endif
+# ---------------------------------------------------------------------------
+# Source files dependencies detection
+# ---------------------------------------------------------------------------
+
+all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
+DEPFILES = $(all_sources:.cc=.cc.d)
+
+ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
+ DEPFILES += $(FOLLY_SOURCES:.cpp=.cpp.d)
+endif
+
+# Add proper dependency support so changing a .h file forces a .cc file to
+# rebuild.
+
+# The .d file indicates .cc file's dependencies on .h files. We generate such
+# dependency by g++'s -MM option, whose output is a make dependency rule.
+%.cc.d: %.cc
+ @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
+ -MM -MT'$@' -MT'$(<:.cc=.o)' "$<" -o '$@'
+
+%.cpp.d: %.cpp
+ @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
+ -MM -MT'$@' -MT'$(<:.cpp=.o)' "$<" -o '$@'
+
+ifeq ($(HAVE_POWER8),1)
+DEPFILES_C = $(LIB_SOURCES_C:.c=.c.d)
+DEPFILES_ASM = $(LIB_SOURCES_ASM:.S=.S.d)
+
+%.c.d: %.c
+ @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
+ -MM -MT'$@' -MT'$(<:.c=.o)' "$<" -o '$@'
+
+%.S.d: %.S
+ @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
+ -MM -MT'$@' -MT'$(<:.S=.o)' "$<" -o '$@'
+
+$(DEPFILES_C): %.c.d
+
+$(DEPFILES_ASM): %.S.d
+depend: $(DEPFILES) $(DEPFILES_C) $(DEPFILES_ASM)
+else
+depend: $(DEPFILES)
+endif
+
+# if the make goal is either "clean" or "format", we shouldn't
+# try to import the *.d files.
+# TODO(kailiu) The unfamiliarity of Make's conditions leads to the ugly
+# working solution.
+ifneq ($(MAKECMDGOALS),clean)
+ifneq ($(MAKECMDGOALS),format)
+ifneq ($(MAKECMDGOALS),jclean)
+ifneq ($(MAKECMDGOALS),jtest)
+ifneq ($(MAKECMDGOALS),package)
+ifneq ($(MAKECMDGOALS),analyze)
+-include $(DEPFILES)
+endif
+endif
+endif
+endif
+endif
+endif
diff --git a/storage/rocksdb/rocksdb/README.md b/storage/rocksdb/rocksdb/README.md
new file mode 100644
index 00000000..9ef21ee5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/README.md
@@ -0,0 +1,31 @@
+## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
+
+[![Linux/Mac Build Status](https://travis-ci.org/facebook/rocksdb.svg?branch=master)](https://travis-ci.org/facebook/rocksdb)
+[![Windows Build status](https://ci.appveyor.com/api/projects/status/fbgfu0so3afcno78/branch/master?svg=true)](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
+[![PPC64le Build Status](http://140.211.168.68:8080/buildStatus/icon?job=Rocksdb)](http://140.211.168.68:8080/job/Rocksdb)
+
+RocksDB is developed and maintained by Facebook Database Engineering Team.
+It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
+and Jeff Dean (jeff@google.com)
+
+This code is a library that forms the core building block for a fast
+key-value server, especially suited for storing data on flash drives.
+It has a Log-Structured-Merge-Database (LSM) design with flexible tradeoffs
+between Write-Amplification-Factor (WAF), Read-Amplification-Factor (RAF)
+and Space-Amplification-Factor (SAF). It has multi-threaded compactions,
+making it especially suitable for storing multiple terabytes of data in a
+single database.
+
+Start with example usage here: https://github.com/facebook/rocksdb/tree/master/examples
+
+See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
+
+The public interface is in `include/`. Callers should not include or
+rely on the details of any other header files in this package. Those
+internal APIs may be changed without warning.
+
+Design discussions are conducted in https://www.facebook.com/groups/rocksdb.dev/
+
+## License
+
+RocksDB is dual-licensed under both the GPLv2 (found in the COPYING file in the root directory) and Apache 2.0 License (found in the LICENSE.Apache file in the root directory). You may select, at your option, one of the above-listed licenses.
diff --git a/storage/rocksdb/rocksdb/ROCKSDB_LITE.md b/storage/rocksdb/rocksdb/ROCKSDB_LITE.md
new file mode 100644
index 00000000..8991b950
--- /dev/null
+++ b/storage/rocksdb/rocksdb/ROCKSDB_LITE.md
@@ -0,0 +1,21 @@
+# RocksDBLite
+
+RocksDBLite is a project focused on mobile use cases, which don't need a lot of fancy things we've built for server workloads and they are very sensitive to binary size. For that reason, we added a compile flag ROCKSDB_LITE that comments out a lot of the nonessential code and keeps the binary lean.
+
+Some examples of the features disabled by ROCKSDB_LITE:
+* compiled-in support for LDB tool
+* No backupable DB
+* No support for replication (which we provide in form of TransactionalIterator)
+* No advanced monitoring tools
+* No special-purpose memtables that are highly optimized for specific use cases
+* No Transactions
+
+When adding a new big feature to RocksDB, please add ROCKSDB_LITE compile guard if:
+* Nobody from mobile really needs your feature,
+* Your feature is adding a lot of weight to the binary.
+
+Don't add ROCKSDB_LITE compile guard if:
+* It would introduce a lot of code complexity. Compile guards make code harder to read. It's a trade-off.
+* Your feature is not adding a lot of weight.
+
+If unsure, ask. :)
diff --git a/storage/rocksdb/rocksdb/TARGETS b/storage/rocksdb/rocksdb/TARGETS
new file mode 100644
index 00000000..23952528
--- /dev/null
+++ b/storage/rocksdb/rocksdb/TARGETS
@@ -0,0 +1,1530 @@
+# This file @generated by `python buckifier/buckify_rocksdb.py`
+# --> DO NOT EDIT MANUALLY <--
+# This file is a Facebook-specific integration for buck builds, so can
+# only be validated by Facebook employees.
+#
+load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
+load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library")
+load(":defs.bzl", "test_binary")
+
+REPO_PATH = package_name() + "/"
+
+ROCKSDB_COMPILER_FLAGS = [
+ "-fno-builtin-memcmp",
+ # Needed to compile in fbcode
+ "-Wno-expansion-to-defined",
+ # Added missing flags from output of build_detect_platform
+ "-Wnarrowing",
+ "-DROCKSDB_NO_DYNAMIC_EXTENSION",
+]
+
+ROCKSDB_EXTERNAL_DEPS = [
+ ("bzip2", None, "bz2"),
+ ("snappy", None, "snappy"),
+ ("zlib", None, "z"),
+ ("gflags", None, "gflags"),
+ ("lz4", None, "lz4"),
+ ("zstd", None),
+ ("tbb", None),
+ ("googletest", None, "gtest"),
+]
+
+ROCKSDB_OS_DEPS = [
+ (
+ "linux",
+ ["third-party//numa:numa", "third-party//liburing:uring"],
+ ),
+]
+
+ROCKSDB_OS_PREPROCESSOR_FLAGS = [
+ (
+ "linux",
+ [
+ "-DOS_LINUX",
+ "-DROCKSDB_FALLOCATE_PRESENT",
+ "-DROCKSDB_MALLOC_USABLE_SIZE",
+ "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
+ "-DROCKSDB_RANGESYNC_PRESENT",
+ "-DROCKSDB_SCHED_GETCPU_PRESENT",
+ "-DROCKSDB_IOURING_PRESENT",
+ "-DHAVE_SSE42",
+ "-DLIBURING",
+ "-DNUMA",
+ ],
+ ),
+ (
+ "macos",
+ ["-DOS_MACOSX"],
+ ),
+]
+
+ROCKSDB_PREPROCESSOR_FLAGS = [
+ "-DROCKSDB_PLATFORM_POSIX",
+ "-DROCKSDB_LIB_IO_POSIX",
+ "-DROCKSDB_SUPPORT_THREAD_LOCAL",
+
+ # Flags to enable libs we include
+ "-DSNAPPY",
+ "-DZLIB",
+ "-DBZIP2",
+ "-DLZ4",
+ "-DZSTD",
+ "-DZSTD_STATIC_LINKING_ONLY",
+ "-DGFLAGS=gflags",
+ "-DTBB",
+
+ # Added missing flags from output of build_detect_platform
+ "-DROCKSDB_BACKTRACE",
+
+ # Directories with files for #include
+ "-I" + REPO_PATH + "include/",
+ "-I" + REPO_PATH,
+]
+
+ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
+ "x86_64": [
+ "-DHAVE_PCLMUL",
+ ],
+}
+
+build_mode = read_config("fbcode", "build_mode")
+
+is_opt_mode = build_mode.startswith("opt")
+
+# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
+# doesn't harm and avoid forgetting to add it.
+ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
+
+sanitizer = read_config("fbcode", "sanitizer")
+
+# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
+# whether the binary is linked with jemalloc at runtime.
+ROCKSDB_OS_PREPROCESSOR_FLAGS += ([(
+ "linux",
+ ["-DROCKSDB_JEMALLOC"],
+)] if sanitizer == "" else [])
+
+ROCKSDB_OS_DEPS += ([(
+ "linux",
+ ["third-party//jemalloc:headers"],
+)] if sanitizer == "" else [])
+
+cpp_library(
+ name = "rocksdb_lib",
+ srcs = [
+ "cache/clock_cache.cc",
+ "cache/lru_cache.cc",
+ "cache/sharded_cache.cc",
+ "db/arena_wrapped_db_iter.cc",
+ "db/builder.cc",
+ "db/c.cc",
+ "db/column_family.cc",
+ "db/compacted_db_impl.cc",
+ "db/compaction/compaction.cc",
+ "db/compaction/compaction_iterator.cc",
+ "db/compaction/compaction_job.cc",
+ "db/compaction/compaction_picker.cc",
+ "db/compaction/compaction_picker_fifo.cc",
+ "db/compaction/compaction_picker_level.cc",
+ "db/compaction/compaction_picker_universal.cc",
+ "db/convenience.cc",
+ "db/db_filesnapshot.cc",
+ "db/db_impl/db_impl.cc",
+ "db/db_impl/db_impl_compaction_flush.cc",
+ "db/db_impl/db_impl_debug.cc",
+ "db/db_impl/db_impl_experimental.cc",
+ "db/db_impl/db_impl_files.cc",
+ "db/db_impl/db_impl_open.cc",
+ "db/db_impl/db_impl_readonly.cc",
+ "db/db_impl/db_impl_secondary.cc",
+ "db/db_impl/db_impl_write.cc",
+ "db/db_info_dumper.cc",
+ "db/db_iter.cc",
+ "db/dbformat.cc",
+ "db/error_handler.cc",
+ "db/event_helpers.cc",
+ "db/experimental.cc",
+ "db/external_sst_file_ingestion_job.cc",
+ "db/file_indexer.cc",
+ "db/flush_job.cc",
+ "db/flush_scheduler.cc",
+ "db/forward_iterator.cc",
+ "db/import_column_family_job.cc",
+ "db/internal_stats.cc",
+ "db/log_reader.cc",
+ "db/log_writer.cc",
+ "db/logs_with_prep_tracker.cc",
+ "db/malloc_stats.cc",
+ "db/memtable.cc",
+ "db/memtable_list.cc",
+ "db/merge_helper.cc",
+ "db/merge_operator.cc",
+ "db/range_del_aggregator.cc",
+ "db/range_tombstone_fragmenter.cc",
+ "db/repair.cc",
+ "db/snapshot_impl.cc",
+ "db/table_cache.cc",
+ "db/table_properties_collector.cc",
+ "db/transaction_log_impl.cc",
+ "db/trim_history_scheduler.cc",
+ "db/version_builder.cc",
+ "db/version_edit.cc",
+ "db/version_set.cc",
+ "db/wal_manager.cc",
+ "db/write_batch.cc",
+ "db/write_batch_base.cc",
+ "db/write_controller.cc",
+ "db/write_thread.cc",
+ "env/env.cc",
+ "env/env_chroot.cc",
+ "env/env_encryption.cc",
+ "env/env_hdfs.cc",
+ "env/env_posix.cc",
+ "env/file_system.cc",
+ "env/fs_posix.cc",
+ "env/io_posix.cc",
+ "env/mock_env.cc",
+ "file/delete_scheduler.cc",
+ "file/file_prefetch_buffer.cc",
+ "file/file_util.cc",
+ "file/filename.cc",
+ "file/random_access_file_reader.cc",
+ "file/read_write_util.cc",
+ "file/readahead_raf.cc",
+ "file/sequence_file_reader.cc",
+ "file/sst_file_manager_impl.cc",
+ "file/writable_file_writer.cc",
+ "logging/auto_roll_logger.cc",
+ "logging/event_logger.cc",
+ "logging/log_buffer.cc",
+ "memory/arena.cc",
+ "memory/concurrent_arena.cc",
+ "memory/jemalloc_nodump_allocator.cc",
+ "memtable/alloc_tracker.cc",
+ "memtable/hash_linklist_rep.cc",
+ "memtable/hash_skiplist_rep.cc",
+ "memtable/skiplistrep.cc",
+ "memtable/vectorrep.cc",
+ "memtable/write_buffer_manager.cc",
+ "monitoring/histogram.cc",
+ "monitoring/histogram_windowing.cc",
+ "monitoring/in_memory_stats_history.cc",
+ "monitoring/instrumented_mutex.cc",
+ "monitoring/iostats_context.cc",
+ "monitoring/perf_context.cc",
+ "monitoring/perf_level.cc",
+ "monitoring/persistent_stats_history.cc",
+ "monitoring/statistics.cc",
+ "monitoring/thread_status_impl.cc",
+ "monitoring/thread_status_updater.cc",
+ "monitoring/thread_status_updater_debug.cc",
+ "monitoring/thread_status_util.cc",
+ "monitoring/thread_status_util_debug.cc",
+ "options/cf_options.cc",
+ "options/db_options.cc",
+ "options/options.cc",
+ "options/options_helper.cc",
+ "options/options_parser.cc",
+ "options/options_sanity_check.cc",
+ "port/port_posix.cc",
+ "port/stack_trace.cc",
+ "table/adaptive/adaptive_table_factory.cc",
+ "table/block_based/block.cc",
+ "table/block_based/block_based_filter_block.cc",
+ "table/block_based/block_based_table_builder.cc",
+ "table/block_based/block_based_table_factory.cc",
+ "table/block_based/block_based_table_reader.cc",
+ "table/block_based/block_builder.cc",
+ "table/block_based/block_prefix_index.cc",
+ "table/block_based/data_block_footer.cc",
+ "table/block_based/data_block_hash_index.cc",
+ "table/block_based/filter_block_reader_common.cc",
+ "table/block_based/filter_policy.cc",
+ "table/block_based/flush_block_policy.cc",
+ "table/block_based/full_filter_block.cc",
+ "table/block_based/index_builder.cc",
+ "table/block_based/parsed_full_filter_block.cc",
+ "table/block_based/partitioned_filter_block.cc",
+ "table/block_based/uncompression_dict_reader.cc",
+ "table/block_fetcher.cc",
+ "table/cuckoo/cuckoo_table_builder.cc",
+ "table/cuckoo/cuckoo_table_factory.cc",
+ "table/cuckoo/cuckoo_table_reader.cc",
+ "table/format.cc",
+ "table/get_context.cc",
+ "table/iterator.cc",
+ "table/merging_iterator.cc",
+ "table/meta_blocks.cc",
+ "table/persistent_cache_helper.cc",
+ "table/plain/plain_table_bloom.cc",
+ "table/plain/plain_table_builder.cc",
+ "table/plain/plain_table_factory.cc",
+ "table/plain/plain_table_index.cc",
+ "table/plain/plain_table_key_coding.cc",
+ "table/plain/plain_table_reader.cc",
+ "table/sst_file_reader.cc",
+ "table/sst_file_writer.cc",
+ "table/table_properties.cc",
+ "table/two_level_iterator.cc",
+ "test_util/sync_point.cc",
+ "test_util/sync_point_impl.cc",
+ "test_util/transaction_test_util.cc",
+ "tools/dump/db_dump_tool.cc",
+ "tools/ldb_cmd.cc",
+ "tools/ldb_tool.cc",
+ "tools/sst_dump_tool.cc",
+ "trace_replay/block_cache_tracer.cc",
+ "trace_replay/trace_replay.cc",
+ "util/build_version.cc",
+ "util/coding.cc",
+ "util/compaction_job_stats_impl.cc",
+ "util/comparator.cc",
+ "util/compression_context_cache.cc",
+ "util/concurrent_task_limiter_impl.cc",
+ "util/crc32c.cc",
+ "util/dynamic_bloom.cc",
+ "util/file_checksum_helper.cc",
+ "util/hash.cc",
+ "util/murmurhash.cc",
+ "util/random.cc",
+ "util/rate_limiter.cc",
+ "util/slice.cc",
+ "util/status.cc",
+ "util/string_util.cc",
+ "util/thread_local.cc",
+ "util/threadpool_imp.cc",
+ "util/xxhash.cc",
+ "utilities/backupable/backupable_db.cc",
+ "utilities/blob_db/blob_compaction_filter.cc",
+ "utilities/blob_db/blob_db.cc",
+ "utilities/blob_db/blob_db_impl.cc",
+ "utilities/blob_db/blob_db_impl_filesnapshot.cc",
+ "utilities/blob_db/blob_dump_tool.cc",
+ "utilities/blob_db/blob_file.cc",
+ "utilities/blob_db/blob_log_format.cc",
+ "utilities/blob_db/blob_log_reader.cc",
+ "utilities/blob_db/blob_log_writer.cc",
+ "utilities/cassandra/cassandra_compaction_filter.cc",
+ "utilities/cassandra/format.cc",
+ "utilities/cassandra/merge_operator.cc",
+ "utilities/checkpoint/checkpoint_impl.cc",
+ "utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
+ "utilities/convenience/info_log_finder.cc",
+ "utilities/debug.cc",
+ "utilities/env_mirror.cc",
+ "utilities/env_timed.cc",
+ "utilities/leveldb_options/leveldb_options.cc",
+ "utilities/memory/memory_util.cc",
+ "utilities/merge_operators/bytesxor.cc",
+ "utilities/merge_operators/max.cc",
+ "utilities/merge_operators/put.cc",
+ "utilities/merge_operators/sortlist.cc",
+ "utilities/merge_operators/string_append/stringappend.cc",
+ "utilities/merge_operators/string_append/stringappend2.cc",
+ "utilities/merge_operators/uint64add.cc",
+ "utilities/object_registry.cc",
+ "utilities/option_change_migration/option_change_migration.cc",
+ "utilities/options/options_util.cc",
+ "utilities/persistent_cache/block_cache_tier.cc",
+ "utilities/persistent_cache/block_cache_tier_file.cc",
+ "utilities/persistent_cache/block_cache_tier_metadata.cc",
+ "utilities/persistent_cache/persistent_cache_tier.cc",
+ "utilities/persistent_cache/volatile_tier_impl.cc",
+ "utilities/simulator_cache/cache_simulator.cc",
+ "utilities/simulator_cache/sim_cache.cc",
+ "utilities/table_properties_collectors/compact_on_deletion_collector.cc",
+ "utilities/trace/file_trace_reader_writer.cc",
+ "utilities/transactions/optimistic_transaction.cc",
+ "utilities/transactions/optimistic_transaction_db_impl.cc",
+ "utilities/transactions/pessimistic_transaction.cc",
+ "utilities/transactions/pessimistic_transaction_db.cc",
+ "utilities/transactions/snapshot_checker.cc",
+ "utilities/transactions/transaction_base.cc",
+ "utilities/transactions/transaction_db_mutex_impl.cc",
+ "utilities/transactions/transaction_lock_mgr.cc",
+ "utilities/transactions/transaction_util.cc",
+ "utilities/transactions/write_prepared_txn.cc",
+ "utilities/transactions/write_prepared_txn_db.cc",
+ "utilities/transactions/write_unprepared_txn.cc",
+ "utilities/transactions/write_unprepared_txn_db.cc",
+ "utilities/ttl/db_ttl_impl.cc",
+ "utilities/write_batch_with_index/write_batch_with_index.cc",
+ "utilities/write_batch_with_index/write_batch_with_index_internal.cc",
+ ],
+ auto_headers = AutoHeaders.RECURSIVE_GLOB,
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+
+cpp_library(
+ name = "rocksdb_test_lib",
+ srcs = [
+ "db/db_test_util.cc",
+ "table/mock_table.cc",
+ "test_util/fault_injection_test_env.cc",
+ "test_util/testharness.cc",
+ "test_util/testutil.cc",
+ "tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
+ "tools/trace_analyzer_tool.cc",
+ "utilities/cassandra/test_utils.cc",
+ ],
+ auto_headers = AutoHeaders.RECURSIVE_GLOB,
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [":rocksdb_lib"],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+
+cpp_library(
+ name = "rocksdb_tools_lib",
+ srcs = [
+ "test_util/testutil.cc",
+ "tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
+ "tools/db_bench_tool.cc",
+ "tools/trace_analyzer_tool.cc",
+ ],
+ auto_headers = AutoHeaders.RECURSIVE_GLOB,
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [":rocksdb_lib"],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+
+cpp_library(
+ name = "rocksdb_stress_lib",
+ srcs = [
+ "db_stress_tool/batched_ops_stress.cc",
+ "db_stress_tool/cf_consistency_stress.cc",
+ "db_stress_tool/db_stress_common.cc",
+ "db_stress_tool/db_stress_driver.cc",
+ "db_stress_tool/db_stress_gflags.cc",
+ "db_stress_tool/db_stress_shared_state.cc",
+ "db_stress_tool/db_stress_test_base.cc",
+ "db_stress_tool/db_stress_tool.cc",
+ "db_stress_tool/no_batched_ops_stress.cc",
+ "test_util/testutil.cc",
+ "tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
+ "tools/trace_analyzer_tool.cc",
+ ],
+ auto_headers = AutoHeaders.RECURSIVE_GLOB,
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [":rocksdb_lib"],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+
+cpp_library(
+ name = "env_basic_test_lib",
+ srcs = ["env/env_basic_test.cc"],
+ auto_headers = AutoHeaders.RECURSIVE_GLOB,
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [":rocksdb_test_lib"],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+
+# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
+ROCKS_TESTS = [
+ [
+ "arena_test",
+ "memory/arena_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "auto_roll_logger_test",
+ "logging/auto_roll_logger_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "autovector_test",
+ "util/autovector_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "backupable_db_test",
+ "utilities/backupable/backupable_db_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "blob_db_test",
+ "utilities/blob_db/blob_db_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "block_based_filter_block_test",
+ "table/block_based/block_based_filter_block_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "block_cache_trace_analyzer_test",
+ "tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "block_cache_tracer_test",
+ "trace_replay/block_cache_tracer_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "block_test",
+ "table/block_based/block_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "bloom_test",
+ "util/bloom_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "c_test",
+ "db/c_test.c",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cache_simulator_test",
+ "utilities/simulator_cache/cache_simulator_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cache_test",
+ "cache/cache_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cassandra_format_test",
+ "utilities/cassandra/cassandra_format_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cassandra_functional_test",
+ "utilities/cassandra/cassandra_functional_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cassandra_row_merge_test",
+ "utilities/cassandra/cassandra_row_merge_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cassandra_serialize_test",
+ "utilities/cassandra/cassandra_serialize_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "checkpoint_test",
+ "utilities/checkpoint/checkpoint_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cleanable_test",
+ "table/cleanable_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "coding_test",
+ "util/coding_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "column_family_test",
+ "db/column_family_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compact_files_test",
+ "db/compact_files_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compact_on_deletion_collector_test",
+ "utilities/table_properties_collectors/compact_on_deletion_collector_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compaction_iterator_test",
+ "db/compaction/compaction_iterator_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compaction_job_stats_test",
+ "db/compaction/compaction_job_stats_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compaction_job_test",
+ "db/compaction/compaction_job_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "compaction_picker_test",
+ "db/compaction/compaction_picker_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "comparator_db_test",
+ "db/comparator_db_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "corruption_test",
+ "db/corruption_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "crc32c_test",
+ "util/crc32c_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cuckoo_table_builder_test",
+ "table/cuckoo/cuckoo_table_builder_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cuckoo_table_db_test",
+ "db/cuckoo_table_db_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "cuckoo_table_reader_test",
+ "table/cuckoo/cuckoo_table_reader_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "data_block_hash_index_test",
+ "table/block_based/data_block_hash_index_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_basic_test",
+ "db/db_basic_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_blob_index_test",
+ "db/db_blob_index_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_block_cache_test",
+ "db/db_block_cache_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_bloom_filter_test",
+ "db/db_bloom_filter_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_compaction_filter_test",
+ "db/db_compaction_filter_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_compaction_test",
+ "db/db_compaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_dynamic_level_test",
+ "db/db_dynamic_level_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_encryption_test",
+ "db/db_encryption_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_flush_test",
+ "db/db_flush_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_inplace_update_test",
+ "db/db_inplace_update_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_io_failure_test",
+ "db/db_io_failure_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_iter_stress_test",
+ "db/db_iter_stress_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_iter_test",
+ "db/db_iter_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_iterator_test",
+ "db/db_iterator_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_log_iter_test",
+ "db/db_log_iter_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_memtable_test",
+ "db/db_memtable_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_merge_operand_test",
+ "db/db_merge_operand_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_merge_operator_test",
+ "db/db_merge_operator_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_options_test",
+ "db/db_options_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_properties_test",
+ "db/db_properties_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_range_del_test",
+ "db/db_range_del_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_secondary_test",
+ "db/db_impl/db_secondary_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_sst_test",
+ "db/db_sst_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_statistics_test",
+ "db/db_statistics_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_table_properties_test",
+ "db/db_table_properties_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_tailing_iter_test",
+ "db/db_tailing_iter_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_test",
+ "db/db_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_test2",
+ "db/db_test2.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "db_universal_compaction_test",
+ "db/db_universal_compaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_wal_test",
+ "db/db_wal_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "db_write_test",
+ "db/db_write_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "dbformat_test",
+ "db/dbformat_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "defer_test",
+ "util/defer_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "delete_scheduler_test",
+ "file/delete_scheduler_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "deletefile_test",
+ "db/deletefile_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "dynamic_bloom_test",
+ "util/dynamic_bloom_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "env_basic_test",
+ "env/env_basic_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "env_logger_test",
+ "logging/env_logger_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "env_test",
+ "env/env_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "env_timed_test",
+ "utilities/env_timed_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "error_handler_test",
+ "db/error_handler_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "event_logger_test",
+ "logging/event_logger_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "external_sst_file_basic_test",
+ "db/external_sst_file_basic_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "external_sst_file_test",
+ "db/external_sst_file_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "fault_injection_test",
+ "db/fault_injection_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "file_indexer_test",
+ "db/file_indexer_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "file_reader_writer_test",
+ "util/file_reader_writer_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "filelock_test",
+ "util/filelock_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "filename_test",
+ "db/filename_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "flush_job_test",
+ "db/flush_job_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "full_filter_block_test",
+ "table/block_based/full_filter_block_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "hash_table_test",
+ "utilities/persistent_cache/hash_table_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "hash_test",
+ "util/hash_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "heap_test",
+ "util/heap_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "histogram_test",
+ "monitoring/histogram_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "import_column_family_test",
+ "db/import_column_family_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "inlineskiplist_test",
+ "memtable/inlineskiplist_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "iostats_context_test",
+ "monitoring/iostats_context_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "ldb_cmd_test",
+ "tools/ldb_cmd_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "listener_test",
+ "db/listener_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "log_test",
+ "db/log_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "lru_cache_test",
+ "cache/lru_cache_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "manual_compaction_test",
+ "db/manual_compaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "memory_test",
+ "utilities/memory/memory_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "memtable_list_test",
+ "db/memtable_list_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "merge_helper_test",
+ "db/merge_helper_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "merge_test",
+ "db/merge_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "merger_test",
+ "table/merger_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "mock_env_test",
+ "env/mock_env_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "object_registry_test",
+ "utilities/object_registry_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "obsolete_files_test",
+ "db/obsolete_files_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "optimistic_transaction_test",
+ "utilities/transactions/optimistic_transaction_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "option_change_migration_test",
+ "utilities/option_change_migration/option_change_migration_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "options_file_test",
+ "db/options_file_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "options_settable_test",
+ "options/options_settable_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "options_test",
+ "options/options_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "options_util_test",
+ "utilities/options/options_util_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "partitioned_filter_block_test",
+ "table/block_based/partitioned_filter_block_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "perf_context_test",
+ "db/perf_context_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "persistent_cache_test",
+ "utilities/persistent_cache/persistent_cache_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "plain_table_db_test",
+ "db/plain_table_db_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "prefix_test",
+ "db/prefix_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "random_test",
+ "util/random_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "range_del_aggregator_test",
+ "db/range_del_aggregator_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "range_tombstone_fragmenter_test",
+ "db/range_tombstone_fragmenter_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "rate_limiter_test",
+ "util/rate_limiter_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "reduce_levels_test",
+ "tools/reduce_levels_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "repair_test",
+ "db/repair_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "repeatable_thread_test",
+ "util/repeatable_thread_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "sim_cache_test",
+ "utilities/simulator_cache/sim_cache_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "skiplist_test",
+ "memtable/skiplist_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "slice_test",
+ "util/slice_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "slice_transform_test",
+ "util/slice_transform_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "sst_dump_test",
+ "tools/sst_dump_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "sst_file_reader_test",
+ "table/sst_file_reader_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "statistics_test",
+ "monitoring/statistics_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "stats_history_test",
+ "monitoring/stats_history_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "stringappend_test",
+ "utilities/merge_operators/string_append/stringappend_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "table_properties_collector_test",
+ "db/table_properties_collector_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "table_test",
+ "table/table_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "thread_list_test",
+ "util/thread_list_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "thread_local_test",
+ "util/thread_local_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "timer_queue_test",
+ "util/timer_queue_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "trace_analyzer_test",
+ "tools/trace_analyzer_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "transaction_test",
+ "utilities/transactions/transaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "ttl_test",
+ "utilities/ttl/ttl_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "util_merge_operators_test",
+ "utilities/util_merge_operators_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "version_builder_test",
+ "db/version_builder_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "version_edit_test",
+ "db/version_edit_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "version_set_test",
+ "db/version_set_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "wal_manager_test",
+ "db/wal_manager_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_batch_test",
+ "db/write_batch_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_batch_with_index_test",
+ "utilities/write_batch_with_index/write_batch_with_index_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_buffer_manager_test",
+ "memtable/write_buffer_manager_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_callback_test",
+ "db/write_callback_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_controller_test",
+ "db/write_controller_test.cc",
+ "serial",
+ [],
+ [],
+ ],
+ [
+ "write_prepared_transaction_test",
+ "utilities/transactions/write_prepared_transaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+ [
+ "write_unprepared_transaction_test",
+ "utilities/transactions/write_unprepared_transaction_test.cc",
+ "parallel",
+ [],
+ [],
+ ],
+]
+
+# Generate a test rule for each entry in ROCKS_TESTS
+# Do not build the tests in opt mode, since SyncPoint and other test code
+# will not be included.
+[
+ test_binary(
+ extra_compiler_flags = extra_compiler_flags,
+ extra_deps = extra_deps,
+ parallelism = parallelism,
+ rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
+ rocksdb_os_deps = ROCKSDB_OS_DEPS,
+ rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ test_cc = test_cc,
+ test_name = test_name,
+ )
+ for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
+ if not is_opt_mode
+]
diff --git a/storage/rocksdb/rocksdb/USERS.md b/storage/rocksdb/rocksdb/USERS.md
new file mode 100644
index 00000000..07294743
--- /dev/null
+++ b/storage/rocksdb/rocksdb/USERS.md
@@ -0,0 +1,108 @@
+This document lists users of RocksDB and their use cases. If you are using RocksDB, please open a pull request and add yourself to the list.
+
+## Facebook
+At Facebook, we use RocksDB as storage engines in multiple data management services and a backend for many different stateful services, including:
+
+1. MyRocks -- https://github.com/MySQLOnRocksDB/mysql-5.6
+2. MongoRocks -- https://github.com/mongodb-partners/mongo-rocks
+3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[1] https://www.youtube.com/watch?v=DfiN7pG0D0khtt
+4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[1]
+4. Dragon -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/
+5. Stylus -- a low-level stream processing framework writtenin C++.[1]
+6. LogDevice -- a distributed data store for logs [2]
+
+[1] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
+
+[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/
+
+## LinkedIn
+Two different use cases at Linkedin are using RocksDB as a storage engine:
+
+1. LinkedIn's follow feed for storing user's activities. Check out the blog post: https://engineering.linkedin.com/blog/2016/03/followfeed--linkedin-s-feed-made-faster-and-smarter
+2. Apache Samza, open source framework for stream processing
+
+Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
+
+## Yahoo
+Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
+
+## CockroachDB
+CockroachDB is an open-source geo-replicated transactional database. They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach
+
+## DNANexus
+DNANexus is using RocksDB to speed up processing of genomics data.
+You can learn more from this great blog post by Mike Lin: http://devblog.dnanexus.com/faster-bam-sorting-with-samtools-and-rocksdb/
+
+## Iron.io
+Iron.io is using RocksDB as a storage engine for their distributed queueing system.
+Learn more from Tech Talk by Reed Allman: http://www.youtube.com/watch?v=HTjt6oj-RL4
+
+## Tango Me
+Tango is using RocksDB as a graph storage to store all users' connection data and other social activity data.
+
+## Turn
+Turn is using RocksDB as a storage layer for their key/value store, serving at peak 2.4MM QPS out of different datacenters.
+Check out our RocksDB Protobuf merge operator at: https://github.com/vladb38/rocksdb_protobuf
+
+## Santanader UK/Cloudera Profession Services
+Check out their blog post: http://blog.cloudera.com/blog/2015/08/inside-santanders-near-real-time-data-ingest-architecture/
+
+## Airbnb
+Airbnb is using RocksDB as a storage engine for their personalized search service. You can learn more about it here: https://www.youtube.com/watch?v=ASQ6XMtogMs
+
+## Alluxio
+[Alluxio](https://www.alluxio.io) uses RocksDB to serve and scale file system metadata to beyond 1 Billion files. The detailed design and implementation is described in this engineering blog:
+https://www.alluxio.io/blog/scalable-metadata-service-in-alluxio-storing-billions-of-files/
+
+## Pinterest
+Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtube.com/watch?v=MtFEVEs_2Vo
+
+## Smyte
+[Smyte](https://www.smyte.com/) uses RocksDB as the storage layer for their core key-value storage, high-performance counters and time-windowed HyperLogLog services.
+
+## Rakuten Marketing
+[Rakuten Marketing](https://marketing.rakuten.com/) uses RocksDB as the disk cache layer for the real-time bidding service in their Performance DSP.
+
+## VWO, Wingify
+[VWO's](https://vwo.com/) Smart Code checker and URL helper uses RocksDB to store all the URLs where VWO's Smart Code is installed.
+
+## quasardb
+[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
+quasardb uses a heavily tuned RocksDB as its persistence layer.
+
+## Netflix
+[Netflix](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) Netflix uses RocksDB on AWS EC2 instances with local SSD drives to cache application data.
+
+## TiKV
+[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
+
+## Apache Flink
+[Apache Flink](https://flink.apache.org/news/2016/03/08/release-1.0.0.html) uses RocksDB to store state locally on a machine.
+
+## Dgraph
+[Dgraph](https://github.com/dgraph-io/dgraph) is an open-source, scalable, distributed, low latency, high throughput Graph database .They use RocksDB to store state locally on a machine.
+
+## Uber
+[Uber](http://eng.uber.com/cherami/) uses RocksDB as a durable and scalable task queue.
+
+## 360 Pika
+[360](http://www.360.cn/) [Pika](https://github.com/Qihoo360/pika) is a nosql compatible with redis. With the huge amount of data stored, redis may suffer for a capacity bottleneck, and pika was born for solving it. It has widely been widely used in many company
+
+## LzLabs
+LzLabs is using RocksDB as a storage engine in their multi-database distributed framework to store application configuration and user data.
+
+## ProfaneDB
+[ProfaneDB](https://profanedb.gitlab.io/) is a database for Protocol Buffers, and uses RocksDB for storage. It is accessible via gRPC, and the schema is defined using directly `.proto` files.
+
+## IOTA Foundation
+ [IOTA Foundation](https://www.iota.org/) is using RocksDB in the [IOTA Reference Implementation (IRI)](https://github.com/iotaledger/iri) to store the local state of the Tangle. The Tangle is the first open-source distributed ledger powering the future of the Internet of Things.
+
+## Avrio Project
+ [Avrio Project](http://avrio-project.github.io/avrio.network/) is using RocksDB in [Avrio ](https://github.com/avrio-project/avrio) to store blocks, account balances and data and other blockchain-releated data. Avrio is a multiblockchain decentralized cryptocurrency empowering monetary transactions.
+
+## Crux
+[Crux](https://github.com/juxt/crux) is a document database that uses RocksDB for local [EAV](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model) index storage to enable point-in-time bitemporal Datalog queries. The "unbundled" architecture uses Kafka to provide horizontal scalability.
+
+## Nebula Graph
+
+[Nebula Graph](https://github.com/vesoft-inc/nebula) is a distributed, scalable, lightning-fast, open source graph database capable of hosting super large scale graphs with dozens of billions of vertices (nodes) and trillions of edges, with milliseconds of latency.
diff --git a/storage/rocksdb/rocksdb/Vagrantfile b/storage/rocksdb/rocksdb/Vagrantfile
new file mode 100644
index 00000000..07f2e99f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/Vagrantfile
@@ -0,0 +1,39 @@
+# Vagrant file
+Vagrant.configure("2") do |config|
+
+ config.vm.provider "virtualbox" do |v|
+ v.memory = 4096
+ v.cpus = 2
+ end
+
+ config.vm.define "ubuntu14" do |box|
+ box.vm.box = "ubuntu/trusty64"
+ end
+
+ config.vm.define "centos65" do |box|
+ box.vm.box = "chef/centos-6.5"
+ end
+
+ config.vm.define "centos7" do |box|
+ box.vm.box = "centos/7"
+ box.vm.provision "shell", path: "build_tools/setup_centos7.sh"
+ end
+
+ config.vm.define "FreeBSD10" do |box|
+ box.vm.guest = :freebsd
+ box.vm.box = "robin/freebsd-10"
+ # FreeBSD does not support 'mount_virtualbox_shared_folder', use NFS
+ box.vm.synced_folder ".", "/vagrant", :nfs => true, id: "vagrant-root"
+ box.vm.network "private_network", ip: "10.0.1.10"
+
+ # build everything after creating VM, skip using --no-provision
+ box.vm.provision "shell", inline: <<-SCRIPT
+ pkg install -y gmake clang35
+ export CXX=/usr/local/bin/clang++35
+ cd /vagrant
+ gmake clean
+ gmake all OPT=-g
+ SCRIPT
+ end
+
+end
diff --git a/storage/rocksdb/rocksdb/WINDOWS_PORT.md b/storage/rocksdb/rocksdb/WINDOWS_PORT.md
new file mode 100644
index 00000000..57293c97
--- /dev/null
+++ b/storage/rocksdb/rocksdb/WINDOWS_PORT.md
@@ -0,0 +1,228 @@
+# Microsoft Contribution Notes
+
+## Contributors
+* Alexander Zinoviev https://github.com/zinoale
+* Dmitri Smirnov https://github.com/yuslepukhin
+* Praveen Rao https://github.com/PraveenSinghRao
+* Sherlock Huang https://github.com/SherlockNoMad
+
+## Introduction
+RocksDB is a well proven open source key-value persistent store, optimized for fast storage. It provides scalability with number of CPUs and storage IOPS, to support IO-bound, in-memory and write-once workloads, most importantly, to be flexible to allow for innovation.
+
+As Microsoft Bing team we have been continuously pushing hard to improve the scalability, efficiency of platform and eventually benefit Bing end-user satisfaction. We would like to explore the opportunity to embrace open source, RocksDB here, to use, enhance and customize for our usage, and also contribute back to the RocksDB community. Herein, we are pleased to offer this RocksDB port for Windows platform.
+
+These notes describe some decisions and changes we had to make with regards to porting RocksDB on Windows. We hope this will help both reviewers and users of the Windows port.
+We are open for comments and improvements.
+
+## OS specifics
+All of the porting, testing and benchmarking was done on Windows Server 2012 R2 Datacenter 64-bit but to the best of our knowledge there is not a specific API we used during porting that is unsupported on other Windows OS after Vista.
+
+## Porting goals
+We strive to achieve the following goals:
+* make use of the existing porting interface of RocksDB
+* make minimum [WY2]modifications within platform independent code.
+* make all unit test pass both in debug and release builds.
+ * Note: latest introduction of SyncPoint seems to disable running db_test in Release.
+* make performance on par with published benchmarks accounting for HW differences
+* we would like to keep the port code inline with the master branch with no forking
+
+## Build system
+We have chosen CMake as a widely accepted build system to build the Windows port. It is very fast and convenient.
+
+At the same time it generates Visual Studio projects that are both usable from a command line and IDE.
+
+The top-level CMakeLists.txt file contains description of all targets and build rules. It also provides brief instructions on how to build the software for Windows. One more build related file is thirdparty.inc that also resides on the top level. This file must be edited to point to actual third party libraries location.
+We think that it would be beneficial to merge the existing make-based build system and the new cmake-based build system into a single one to use on all platforms.
+
+All building and testing was done for 64-bit. We have not conducted any testing for 32-bit and early reports indicate that it will not run on 32-bit.
+
+## C++ and STL notes
+We had to make some minimum changes within the portable files that either account for OS differences or the shortcomings of C++11 support in the current version of the MS compiler. Most or all of them are expected to be fixed in the upcoming compiler releases.
+
+We plan to use this port for our business purposes here at Bing and this provided business justification for this port. This also means, we do not have at present to choose the compiler version at will.
+
+* Certain headers that are not present and not necessary on Windows were simply `#ifndef OS_WIN` in a few places (`unistd.h`)
+* All posix specific headers were replaced to port/port.h which worked well
+* Replaced `dirent.h` for `port/port_dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
+* Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port`
+* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `zu` on posix systems and to `Iu` on windows.
+* in class member initialization were moved to a __ctors in some cases
+* `constexpr` is not supported. We had to replace `std::numeric_limits<>::max/min()` to its C macros for constants. Sometimes we had to make class members `static const` and place a definition within a .cc file.
+* `constexpr` for functions was replaced to a template specialization (1 place)
+* Union members that have non-trivial constructors were replaced to `char[]` in one place along with bug fixes (spatial experimental feature)
+* Zero-sized arrays are deemed a non-standard extension which we converted to 1 size array and that should work well for the purposes of these classes.
+* `std::chrono` lacks nanoseconds support (fixed in the upcoming release of the STL) and we had to use `QueryPerfCounter()` within env_win.cc
+* Function local statics initialization is still not safe. Used `std::once` to mitigate within WinEnv.
+
+## Windows Environments notes
+We endeavored to make it functionally on par with posix_env. This means we replicated the functionality of the thread pool and other things as precise as possible, including:
+* Replicate posix logic using std:thread primitives.
+* Implement all posix_env disk access functionality.
+* Set `use_os_buffer=false` to disable OS disk buffering for WinWritableFile and WinRandomAccessFile.
+* Replace `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure.
+* Use `SetFileInformationByHandle` to compensate absence of `fallocate`.
+
+### In detail
+Even though Windows provides its own efficient thread-pool implementation we chose to replicate posix logic using `std::thread` primitives. This allows anyone to quickly detect any changes within the posix source code and replicate them within windows env. This has proven to work very well. At the same time for anyone who wishes to replace the built-in thread-pool can do so using RocksDB stackable environments.
+
+For disk access we implemented all of the functionality present within the posix_env which includes memory mapped files, random access, rate-limiter support etc.
+The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It’s not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
+
+We have replaced `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure so we can atomically seek to the position of the disk operation but still perform the operation synchronously. Thus we able to emulate that functionality of `pread/pwrite` reasonably well. The only difference is that the file pointer is not returned to its original position but that hardly matters given the random nature of access.
+
+We used `SetFileInformationByHandle` both to truncate files after writing a full final page to disk and to pre-allocate disk space for faster I/O thus compensating for the absence of `fallocate` although some differences remain. For example, the pre-allocated space is not filled with zeros like on Linux, however, on a positive note, the end of file position is also not modified after pre-allocation.
+
+RocksDB renames, copies and deletes files at will even though they may be opened with another handle at the same time. We had to relax and allow nearly all the concurrent access permissions possible.
+
+## Thread-Local Storage
+Thread-Local storage plays a significant role for RocksDB performance. Rather than creating a separate implementation we chose to create inline wrappers that forward `pthread_specific` calls to Windows `Tls` interfaces within `rocksdb::port` namespace. This leaves the existing meat of the logic in tact and unchanged and just as maintainable.
+
+To mitigate the lack of thread local storage cleanup on thread-exit we added a limited amount of windows specific code within the same thread_local.cc file that injects a cleanup callback into a `"__tls"` structure within `".CRT$XLB"` data segment. This approach guarantees that the callback is invoked regardless of whether RocksDB used within an executable, standalone DLL or within another DLL.
+
+## Jemalloc usage
+
+When RocksDB is used with Jemalloc the latter needs to be initialized before any of the C++ globals or statics. To accomplish that we injected an initialization routine into `".CRT$XCT"` that is automatically invoked by the runtime before initializing static objects. je-uninit is queued to `atexit()`.
+
+The jemalloc redirecting `new/delete` global operators are used by the linker providing certain conditions are met. See build section in these notes.
+
+## Stack Trace and Unhandled Exception Handler
+
+We decided not to implement these two features because the hosting program as a rule has these two things in it.
+We experienced no inconveniences debugging issues in the debugger or analyzing process dumps if need be and thus we did not
+see this as a priority.
+
+## Performance results
+### Setup
+All of the benchmarks are run on the same set of machines. Here are the details of the test setup:
+* 2 Intel(R) Xeon(R) E5 2450 0 @ 2.10 GHz (total 16 cores)
+* 2 XK0480GDQPH SSD Device, total 894GB free disk
+* Machine has 128 GB of RAM
+* Operating System: Windows Server 2012 R2 Datacenter
+* 100 Million keys; each key is of size 10 bytes, each value is of size 800 bytes
+* total database size is ~76GB
+* The performance result is based on RocksDB 3.11.
+* The parameters used, unless specified, were exactly the same as published in the GitHub Wiki page.
+
+### RocksDB on flash storage
+
+#### Test 1. Bulk Load of keys in Random Order
+
+Version 3.11
+
+* Total Run Time: 17.6 min
+* Fillrandom: 5.480 micros/op 182465 ops/sec; 142.0 MB/s
+* Compact: 486056544.000 micros/op 0 ops/sec
+
+Version 3.10
+
+* Total Run Time: 16.2 min
+* Fillrandom: 5.018 micros/op 199269 ops/sec; 155.1 MB/s
+* Compact: 441313173.000 micros/op 0 ops/sec;
+
+
+#### Test 2. Bulk Load of keys in Sequential Order
+
+Version 3.11
+
+* Fillseq: 4.944 micros/op 202k ops/sec; 157.4 MB/s
+
+Version 3.10
+
+* Fillseq: 4.105 micros/op 243.6k ops/sec; 189.6 MB/s
+
+
+#### Test 3. Random Write
+
+Version 3.11
+
+* Unbuffered I/O enabled
+* Overwrite: 52.661 micros/op 18.9k ops/sec; 14.8 MB/s
+
+Version 3.10
+
+* Unbuffered I/O enabled
+* Overwrite: 52.661 micros/op 18.9k ops/sec;
+
+
+#### Test 4. Random Read
+
+Version 3.11
+
+* Unbuffered I/O enabled
+* Readrandom: 15.716 micros/op 63.6k ops/sec; 49.5 MB/s
+
+Version 3.10
+
+* Unbuffered I/O enabled
+* Readrandom: 15.548 micros/op 64.3k ops/sec;
+
+
+#### Test 5. Multi-threaded read and single-threaded write
+
+Version 3.11
+
+* Unbuffered I/O enabled
+* Readwhilewriting: 25.128 micros/op 39.7k ops/sec;
+
+Version 3.10
+
+* Unbuffered I/O enabled
+* Readwhilewriting: 24.854 micros/op 40.2k ops/sec;
+
+
+### RocksDB In Memory
+
+#### Test 1. Point Lookup
+
+Version 3.11
+
+80K writes/sec
+* Write Rate Achieved: 40.5k write/sec;
+* Readwhilewriting: 0.314 micros/op 3187455 ops/sec; 364.8 MB/s (715454999 of 715454999 found)
+
+Version 3.10
+
+* Write Rate Achieved: 50.6k write/sec
+* Readwhilewriting: 0.316 micros/op 3162028 ops/sec; (719576999 of 719576999 found)
+
+
+*10K writes/sec*
+
+Version 3.11
+
+* Write Rate Achieved: 5.8k/s write/sec
+* Readwhilewriting: 0.246 micros/op 4062669 ops/sec; 464.9 MB/s (915481999 of 915481999 found)
+
+Version 3.10
+
+* Write Rate Achieved: 5.8k/s write/sec
+* Readwhilewriting: 0.244 micros/op 4106253 ops/sec; (927986999 of 927986999 found)
+
+
+#### Test 2. Prefix Range Query
+
+Version 3.11
+
+80K writes/sec
+* Write Rate Achieved: 46.3k/s write/sec
+* Readwhilewriting: 0.362 micros/op 2765052 ops/sec; 316.4 MB/s (611549999 of 611549999 found)
+
+Version 3.10
+
+* Write Rate Achieved: 45.8k/s write/sec
+* Readwhilewriting: 0.317 micros/op 3154941 ops/sec; (708158999 of 708158999 found)
+
+Version 3.11
+
+10K writes/sec
+* Write Rate Achieved: 5.78k write/sec
+* Readwhilewriting: 0.269 micros/op 3716692 ops/sec; 425.3 MB/s (837401999 of 837401999 found)
+
+Version 3.10
+
+* Write Rate Achieved: 5.7k write/sec
+* Readwhilewriting: 0.261 micros/op 3830152 ops/sec; (863482999 of 863482999 found)
+
+
+We think that there is still big room to improve the performance, which will be an ongoing effort for us.
+
diff --git a/storage/rocksdb/rocksdb/appveyor.yml b/storage/rocksdb/rocksdb/appveyor.yml
new file mode 100644
index 00000000..416b6550
--- /dev/null
+++ b/storage/rocksdb/rocksdb/appveyor.yml
@@ -0,0 +1,75 @@
+version: 1.0.{build}
+
+image: Visual Studio 2017
+
+environment:
+ JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+ THIRDPARTY_HOME: $(APPVEYOR_BUILD_FOLDER)\thirdparty
+ SNAPPY_HOME: $(THIRDPARTY_HOME)\snappy-1.1.7
+ SNAPPY_INCLUDE: $(SNAPPY_HOME);$(SNAPPY_HOME)\build
+ SNAPPY_LIB_DEBUG: $(SNAPPY_HOME)\build\Debug\snappy.lib
+ SNAPPY_LIB_RELEASE: $(SNAPPY_HOME)\build\Release\snappy.lib
+ LZ4_HOME: $(THIRDPARTY_HOME)\lz4-1.8.3
+ LZ4_INCLUDE: $(LZ4_HOME)\lib
+ LZ4_LIB_DEBUG: $(LZ4_HOME)\visual\VS2010\bin\x64_Debug\liblz4_static.lib
+ LZ4_LIB_RELEASE: $(LZ4_HOME)\visual\VS2010\bin\x64_Release\liblz4_static.lib
+ ZSTD_HOME: $(THIRDPARTY_HOME)\zstd-1.4.0
+ ZSTD_INCLUDE: $(ZSTD_HOME)\lib;$(ZSTD_HOME)\lib\dictBuilder
+ ZSTD_LIB_DEBUG: $(ZSTD_HOME)\build\VS2010\bin\x64_Debug\libzstd_static.lib
+ ZSTD_LIB_RELEASE: $(ZSTD_HOME)\build\VS2010\bin\x64_Release\libzstd_static.lib
+ matrix:
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+ CMAKE_GENERATOR: Visual Studio 14 Win64
+ DEV_ENV: C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.com
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ CMAKE_GENERATOR: Visual Studio 15 Win64
+ DEV_ENV: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.com
+
+install:
+ - md %THIRDPARTY_HOME%
+ - echo "Building Snappy dependency..."
+ - cd %THIRDPARTY_HOME%
+ - curl --fail --silent --show-error --output snappy-1.1.7.zip --location https://github.com/google/snappy/archive/1.1.7.zip
+ - unzip snappy-1.1.7.zip
+ - cd snappy-1.1.7
+ - mkdir build
+ - cd build
+ - cmake -G "%CMAKE_GENERATOR%" ..
+ - msbuild Snappy.sln /p:Configuration=Debug /p:Platform=x64
+ - msbuild Snappy.sln /p:Configuration=Release /p:Platform=x64
+ - echo "Building LZ4 dependency..."
+ - cd %THIRDPARTY_HOME%
+ - curl --fail --silent --show-error --output lz4-1.8.3.zip --location https://github.com/lz4/lz4/archive/v1.8.3.zip
+ - unzip lz4-1.8.3.zip
+ - cd lz4-1.8.3\visual\VS2010
+ - ps: $CMD="$Env:DEV_ENV"; & $CMD lz4.sln /upgrade
+ - msbuild lz4.sln /p:Configuration=Debug /p:Platform=x64
+ - msbuild lz4.sln /p:Configuration=Release /p:Platform=x64
+ - echo "Building ZStd dependency..."
+ - cd %THIRDPARTY_HOME%
+ - curl --fail --silent --show-error --output zstd-1.4.0.zip --location https://github.com/facebook/zstd/archive/v1.4.0.zip
+ - unzip zstd-1.4.0.zip
+ - cd zstd-1.4.0\build\VS2010
+ - ps: $CMD="$Env:DEV_ENV"; & $CMD zstd.sln /upgrade
+ - msbuild zstd.sln /p:Configuration=Debug /p:Platform=x64
+ - msbuild zstd.sln /p:Configuration=Release /p:Platform=x64
+
+before_build:
+ - md %APPVEYOR_BUILD_FOLDER%\build
+ - cd %APPVEYOR_BUILD_FOLDER%\build
+ - cmake -G "%CMAKE_GENERATOR%" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DLZ4=1 -DZSTD=1 -DXPRESS=1 -DJNI=1 ..
+ - cd ..
+
+build:
+ project: build\rocksdb.sln
+ parallel: true
+ verbosity: normal
+
+test:
+
+test_script:
+ - ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test,db_merge_operand_test -Concurrency 8
+
+on_failure:
+ - cmd: 7z a build-failed.zip %APPVEYOR_BUILD_FOLDER%\build\ && appveyor PushArtifact build-failed.zip
+
diff --git a/storage/rocksdb/rocksdb/buckifier/buckify_rocksdb.py b/storage/rocksdb/rocksdb/buckifier/buckify_rocksdb.py
new file mode 100644
index 00000000..d2bba594
--- /dev/null
+++ b/storage/rocksdb/rocksdb/buckifier/buckify_rocksdb.py
@@ -0,0 +1,236 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+try:
+ from builtins import str
+except ImportError:
+ from __builtin__ import str
+from targets_builder import TARGETSBuilder
+import json
+import os
+import fnmatch
+import sys
+
+from util import ColorString
+
+# This script generates TARGETS file for Buck.
+# Buck is a build tool specifying dependencies among different build targets.
+# User can pass extra dependencies as a JSON object via command line, and this
+# script can include these dependencies in the generate TARGETS file.
+# Usage:
+# $python buckifier/buckify_rocksdb.py
+# (This generates a TARGET file without user-specified dependency for unit
+# tests.)
+# $python buckifier/buckify_rocksdb.py \
+# '{"fake": { \
+# "extra_deps": [":test_dep", "//fakes/module:mock1"], \
+# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"], \
+# } \
+# }'
+# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
+# unit tests, and will use the extra_compiler_flags to compile the unit test
+# source.)
+
+# tests to export as libraries for inclusion in other projects
+_EXPORTED_TEST_LIBS = ["env_basic_test"]
+
+# Parse src.mk files as a Dictionary of
+# VAR_NAME => list of files
+def parse_src_mk(repo_path):
+ src_mk = repo_path + "/src.mk"
+ src_files = {}
+ for line in open(src_mk):
+ line = line.strip()
+ if len(line) == 0 or line[0] == '#':
+ continue
+ if '=' in line:
+ current_src = line.split('=')[0].strip()
+ src_files[current_src] = []
+ elif '.cc' in line:
+ src_path = line.split('.cc')[0].strip() + '.cc'
+ src_files[current_src].append(src_path)
+ return src_files
+
+
+# get all .cc / .c files
+def get_cc_files(repo_path):
+ cc_files = []
+ for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in
+ root = root[(len(repo_path) + 1):]
+ if "java" in root:
+ # Skip java
+ continue
+ for filename in fnmatch.filter(filenames, '*.cc'):
+ cc_files.append(os.path.join(root, filename))
+ for filename in fnmatch.filter(filenames, '*.c'):
+ cc_files.append(os.path.join(root, filename))
+ return cc_files
+
+
+# Get tests from Makefile
+def get_tests(repo_path):
+ Makefile = repo_path + "/Makefile"
+
+ # Dictionary TEST_NAME => IS_PARALLEL
+ tests = {}
+
+ found_tests = False
+ for line in open(Makefile):
+ line = line.strip()
+ if line.startswith("TESTS ="):
+ found_tests = True
+ elif found_tests:
+ if line.endswith("\\"):
+ # remove the trailing \
+ line = line[:-1]
+ line = line.strip()
+ tests[line] = False
+ else:
+ # we consumed all the tests
+ break
+
+ found_parallel_tests = False
+ for line in open(Makefile):
+ line = line.strip()
+ if line.startswith("PARALLEL_TEST ="):
+ found_parallel_tests = True
+ elif found_parallel_tests:
+ if line.endswith("\\"):
+ # remove the trailing \
+ line = line[:-1]
+ line = line.strip()
+ tests[line] = True
+ else:
+ # we consumed all the parallel tests
+ break
+
+ return tests
+
+
+# Parse extra dependencies passed by user from command line
+def get_dependencies():
+ deps_map = {
+ '': {
+ 'extra_deps': [],
+ 'extra_compiler_flags': []
+ }
+ }
+ if len(sys.argv) < 2:
+ return deps_map
+
+ def encode_dict(data):
+ rv = {}
+ for k, v in data.items():
+ if isinstance(v, dict):
+ v = encode_dict(v)
+ rv[k] = v
+ return rv
+ extra_deps = json.loads(sys.argv[1], object_hook=encode_dict)
+ for target_alias, deps in extra_deps.items():
+ deps_map[target_alias] = deps
+ return deps_map
+
+
+# Prepare TARGETS file for buck
+def generate_targets(repo_path, deps_map):
+ print(ColorString.info("Generating TARGETS"))
+ # parsed src.mk file
+ src_mk = parse_src_mk(repo_path)
+ # get all .cc files
+ cc_files = get_cc_files(repo_path)
+ # get tests from Makefile
+ tests = get_tests(repo_path)
+
+ if src_mk is None or cc_files is None or tests is None:
+ return False
+
+ TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path)
+ # rocksdb_lib
+ TARGETS.add_library(
+ "rocksdb_lib",
+ src_mk["LIB_SOURCES"] +
+ src_mk["TOOL_LIB_SOURCES"])
+ # rocksdb_test_lib
+ TARGETS.add_library(
+ "rocksdb_test_lib",
+ src_mk.get("MOCK_LIB_SOURCES", []) +
+ src_mk.get("TEST_LIB_SOURCES", []) +
+ src_mk.get("EXP_LIB_SOURCES", []) +
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
+ [":rocksdb_lib"])
+ # rocksdb_tools_lib
+ TARGETS.add_library(
+ "rocksdb_tools_lib",
+ src_mk.get("BENCH_LIB_SOURCES", []) +
+ src_mk.get("ANALYZER_LIB_SOURCES", []) +
+ ["test_util/testutil.cc"],
+ [":rocksdb_lib"])
+ # rocksdb_stress_lib
+ TARGETS.add_library(
+ "rocksdb_stress_lib",
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
+ + src_mk.get('STRESS_LIB_SOURCES', [])
+ + ["test_util/testutil.cc"],
+ [":rocksdb_lib"])
+
+ print("Extra dependencies:\n{0}".format(str(deps_map)))
+ # test for every test we found in the Makefile
+ for target_alias, deps in deps_map.items():
+ for test in sorted(tests):
+ match_src = [src for src in cc_files if ("/%s.c" % test) in src]
+ if len(match_src) == 0:
+ print(ColorString.warning("Cannot find .cc file for %s" % test))
+ continue
+ elif len(match_src) > 1:
+ print(ColorString.warning("Found more than one .cc for %s" % test))
+ print(match_src)
+ continue
+
+ assert(len(match_src) == 1)
+ is_parallel = tests[test]
+ test_target_name = \
+ test if not target_alias else test + "_" + target_alias
+ TARGETS.register_test(
+ test_target_name,
+ match_src[0],
+ is_parallel,
+ deps['extra_deps'],
+ deps['extra_compiler_flags'])
+
+ if test in _EXPORTED_TEST_LIBS:
+ test_library = "%s_lib" % test_target_name
+ TARGETS.add_library(test_library, match_src, [":rocksdb_test_lib"])
+ TARGETS.flush_tests()
+
+ print(ColorString.info("Generated TARGETS Summary:"))
+ print(ColorString.info("- %d libs" % TARGETS.total_lib))
+ print(ColorString.info("- %d binarys" % TARGETS.total_bin))
+ print(ColorString.info("- %d tests" % TARGETS.total_test))
+ return True
+
+
+def get_rocksdb_path():
+ # rocksdb = {script_dir}/..
+ script_dir = os.path.dirname(sys.argv[0])
+ script_dir = os.path.abspath(script_dir)
+ rocksdb_path = os.path.abspath(
+ os.path.join(script_dir, "../"))
+
+ return rocksdb_path
+
+def exit_with_error(msg):
+ print(ColorString.error(msg))
+ sys.exit(1)
+
+
+def main():
+ deps_map = get_dependencies()
+ # Generate TARGETS file for buck
+ ok = generate_targets(get_rocksdb_path(), deps_map)
+ if not ok:
+ exit_with_error("Failed to generate TARGETS files")
+
+if __name__ == "__main__":
+ main()
diff --git a/storage/rocksdb/rocksdb/buckifier/rocks_test_runner.sh b/storage/rocksdb/rocksdb/buckifier/rocks_test_runner.sh
new file mode 100755
index 00000000..77f8f23c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/buckifier/rocks_test_runner.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# Create a tmp directory for the test to use
+TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX)
+# shellcheck disable=SC2068
+TEST_TMPDIR="$TEST_DIR" $@ && rm -rf "$TEST_DIR"
diff --git a/storage/rocksdb/rocksdb/buckifier/targets_builder.py b/storage/rocksdb/rocksdb/buckifier/targets_builder.py
new file mode 100644
index 00000000..ba90bc61
--- /dev/null
+++ b/storage/rocksdb/rocksdb/buckifier/targets_builder.py
@@ -0,0 +1,80 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+try:
+ from builtins import object
+ from builtins import str
+except ImportError:
+ from __builtin__ import object
+ from __builtin__ import str
+import targets_cfg
+
+def pretty_list(lst, indent=8):
+ if lst is None or len(lst) == 0:
+ return ""
+
+ if len(lst) == 1:
+ return "\"%s\"" % lst[0]
+
+ separator = "\",\n%s\"" % (" " * indent)
+ res = separator.join(sorted(lst))
+ res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 4))
+ return res
+
+
+class TARGETSBuilder(object):
+ def __init__(self, path):
+ self.path = path
+ self.targets_file = open(path, 'w')
+ self.targets_file.write(targets_cfg.rocksdb_target_header)
+ self.total_lib = 0
+ self.total_bin = 0
+ self.total_test = 0
+ self.tests_cfg = ""
+
+ def __del__(self):
+ self.targets_file.close()
+
+ def add_library(self, name, srcs, deps=None, headers=None):
+ headers_attr_prefix = ""
+ if headers is None:
+ headers_attr_prefix = "auto_"
+ headers = "AutoHeaders.RECURSIVE_GLOB"
+ self.targets_file.write(targets_cfg.library_template.format(
+ name=name,
+ srcs=pretty_list(srcs),
+ headers_attr_prefix=headers_attr_prefix,
+ headers=headers,
+ deps=pretty_list(deps)))
+ self.total_lib = self.total_lib + 1
+
+ def add_binary(self, name, srcs, deps=None):
+ self.targets_file.write(targets_cfg.binary_template % (
+ name,
+ pretty_list(srcs),
+ pretty_list(deps)))
+ self.total_bin = self.total_bin + 1
+
+ def register_test(self,
+ test_name,
+ src,
+ is_parallel,
+ extra_deps,
+ extra_compiler_flags):
+ exec_mode = "serial"
+ if is_parallel:
+ exec_mode = "parallel"
+ self.tests_cfg += targets_cfg.test_cfg_template % (
+ test_name,
+ str(src),
+ str(exec_mode),
+ extra_deps,
+ extra_compiler_flags)
+
+ self.total_test = self.total_test + 1
+
+ def flush_tests(self):
+ self.targets_file.write(targets_cfg.unittests_template % self.tests_cfg)
+ self.tests_cfg = ""
diff --git a/storage/rocksdb/rocksdb/buckifier/targets_cfg.py b/storage/rocksdb/rocksdb/buckifier/targets_cfg.py
new file mode 100644
index 00000000..c92b1020
--- /dev/null
+++ b/storage/rocksdb/rocksdb/buckifier/targets_cfg.py
@@ -0,0 +1,181 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+rocksdb_target_header = """# This file \100generated by `python buckifier/buckify_rocksdb.py`
+# --> DO NOT EDIT MANUALLY <--
+# This file is a Facebook-specific integration for buck builds, so can
+# only be validated by Facebook employees.
+#
+load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
+load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library")
+load(":defs.bzl", "test_binary")
+
+REPO_PATH = package_name() + "/"
+
+ROCKSDB_COMPILER_FLAGS = [
+ "-fno-builtin-memcmp",
+ # Needed to compile in fbcode
+ "-Wno-expansion-to-defined",
+ # Added missing flags from output of build_detect_platform
+ "-Wnarrowing",
+ "-DROCKSDB_NO_DYNAMIC_EXTENSION",
+]
+
+ROCKSDB_EXTERNAL_DEPS = [
+ ("bzip2", None, "bz2"),
+ ("snappy", None, "snappy"),
+ ("zlib", None, "z"),
+ ("gflags", None, "gflags"),
+ ("lz4", None, "lz4"),
+ ("zstd", None),
+ ("tbb", None),
+ ("googletest", None, "gtest"),
+]
+
+ROCKSDB_OS_DEPS = [
+ (
+ "linux",
+ ["third-party//numa:numa", "third-party//liburing:uring"],
+ ),
+]
+
+ROCKSDB_OS_PREPROCESSOR_FLAGS = [
+ (
+ "linux",
+ [
+ "-DOS_LINUX",
+ "-DROCKSDB_FALLOCATE_PRESENT",
+ "-DROCKSDB_MALLOC_USABLE_SIZE",
+ "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
+ "-DROCKSDB_RANGESYNC_PRESENT",
+ "-DROCKSDB_SCHED_GETCPU_PRESENT",
+ "-DROCKSDB_IOURING_PRESENT",
+ "-DHAVE_SSE42",
+ "-DLIBURING",
+ "-DNUMA",
+ ],
+ ),
+ (
+ "macos",
+ ["-DOS_MACOSX"],
+ ),
+]
+
+ROCKSDB_PREPROCESSOR_FLAGS = [
+ "-DROCKSDB_PLATFORM_POSIX",
+ "-DROCKSDB_LIB_IO_POSIX",
+ "-DROCKSDB_SUPPORT_THREAD_LOCAL",
+
+ # Flags to enable libs we include
+ "-DSNAPPY",
+ "-DZLIB",
+ "-DBZIP2",
+ "-DLZ4",
+ "-DZSTD",
+ "-DZSTD_STATIC_LINKING_ONLY",
+ "-DGFLAGS=gflags",
+ "-DTBB",
+
+ # Added missing flags from output of build_detect_platform
+ "-DROCKSDB_BACKTRACE",
+
+ # Directories with files for #include
+ "-I" + REPO_PATH + "include/",
+ "-I" + REPO_PATH,
+]
+
+ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
+ "x86_64": [
+ "-DHAVE_PCLMUL",
+ ],
+}
+
+build_mode = read_config("fbcode", "build_mode")
+
+is_opt_mode = build_mode.startswith("opt")
+
+# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
+# doesn't harm and avoid forgetting to add it.
+ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
+
+sanitizer = read_config("fbcode", "sanitizer")
+
+# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
+# whether the binary is linked with jemalloc at runtime.
+ROCKSDB_OS_PREPROCESSOR_FLAGS += ([(
+ "linux",
+ ["-DROCKSDB_JEMALLOC"],
+)] if sanitizer == "" else [])
+
+ROCKSDB_OS_DEPS += ([(
+ "linux",
+ ["third-party//jemalloc:headers"],
+)] if sanitizer == "" else [])
+"""
+
+
+library_template = """
+cpp_library(
+ name = "{name}",
+ srcs = [{srcs}],
+ {headers_attr_prefix}headers = {headers},
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ os_deps = ROCKSDB_OS_DEPS,
+ os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [{deps}],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+"""
+
+binary_template = """
+cpp_binary(
+ name = "%s",
+ srcs = [%s],
+ arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ deps = [%s],
+ external_deps = ROCKSDB_EXTERNAL_DEPS,
+)
+"""
+
+test_cfg_template = """ [
+ "%s",
+ "%s",
+ "%s",
+ %s,
+ %s,
+ ],
+"""
+
+unittests_template = """
+# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
+ROCKS_TESTS = [
+%s]
+
+# Generate a test rule for each entry in ROCKS_TESTS
+# Do not build the tests in opt mode, since SyncPoint and other test code
+# will not be included.
+[
+ test_binary(
+ extra_compiler_flags = extra_compiler_flags,
+ extra_deps = extra_deps,
+ parallelism = parallelism,
+ rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
+ rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
+ rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
+ rocksdb_os_deps = ROCKSDB_OS_DEPS,
+ rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
+ rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
+ test_cc = test_cc,
+ test_name = test_name,
+ )
+ for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
+ if not is_opt_mode
+]
+"""
diff --git a/storage/rocksdb/rocksdb/buckifier/util.py b/storage/rocksdb/rocksdb/buckifier/util.py
new file mode 100644
index 00000000..f04929a2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/buckifier/util.py
@@ -0,0 +1,119 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+"""
+This module keeps commonly used components.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+try:
+ from builtins import object
+except ImportError:
+ from __builtin__ import object
+import subprocess
+import sys
+import os
+import time
+
+class ColorString(object):
+ """ Generate colorful strings on terminal """
+ HEADER = '\033[95m'
+ BLUE = '\033[94m'
+ GREEN = '\033[92m'
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+
+ @staticmethod
+ def _make_color_str(text, color):
+ # In Python2, default encoding for unicode string is ASCII
+ if sys.version_info.major <= 2:
+ return "".join(
+ [color, text.encode('utf-8'), ColorString.ENDC])
+ # From Python3, default encoding for unicode string is UTF-8
+ return "".join(
+ [color, text, ColorString.ENDC])
+
+ @staticmethod
+ def ok(text):
+ if ColorString.is_disabled:
+ return text
+ return ColorString._make_color_str(text, ColorString.GREEN)
+
+ @staticmethod
+ def info(text):
+ if ColorString.is_disabled:
+ return text
+ return ColorString._make_color_str(text, ColorString.BLUE)
+
+ @staticmethod
+ def header(text):
+ if ColorString.is_disabled:
+ return text
+ return ColorString._make_color_str(text, ColorString.HEADER)
+
+ @staticmethod
+ def error(text):
+ if ColorString.is_disabled:
+ return text
+ return ColorString._make_color_str(text, ColorString.FAIL)
+
+ @staticmethod
+ def warning(text):
+ if ColorString.is_disabled:
+ return text
+ return ColorString._make_color_str(text, ColorString.WARNING)
+
+ is_disabled = False
+
+
+def run_shell_command(shell_cmd, cmd_dir=None):
+ """ Run a single shell command.
+ @returns a tuple of shell command return code, stdout, stderr """
+
+ if cmd_dir is not None and not os.path.exists(cmd_dir):
+ run_shell_command("mkdir -p %s" % cmd_dir)
+
+ start = time.time()
+ print("\t>>> Running: " + shell_cmd)
+ p = subprocess.Popen(shell_cmd,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=cmd_dir)
+ stdout, stderr = p.communicate()
+ end = time.time()
+
+ # Report time if we spent more than 5 minutes executing a command
+ execution_time = end - start
+ if execution_time > (60 * 5):
+ mins = (execution_time / 60)
+ secs = (execution_time % 60)
+ print("\t>time spent: %d minutes %d seconds" % (mins, secs))
+
+
+ return p.returncode, stdout, stderr
+
+
+def run_shell_commands(shell_cmds, cmd_dir=None, verbose=False):
+ """ Execute a sequence of shell commands, which is equivalent to
+ running `cmd1 && cmd2 && cmd3`
+ @returns boolean indication if all commands succeeds.
+ """
+
+ if cmd_dir:
+ print("\t=== Set current working directory => %s" % cmd_dir)
+
+ for shell_cmd in shell_cmds:
+ ret_code, stdout, stderr = run_shell_command(shell_cmd, cmd_dir)
+ if stdout:
+ if verbose or ret_code != 0:
+ print(ColorString.info("stdout: \n"), stdout)
+ if stderr:
+ # contents in stderr is not necessarily to be error messages.
+ if verbose or ret_code != 0:
+ print(ColorString.error("stderr: \n"), stderr)
+ if ret_code != 0:
+ return False
+
+ return True
diff --git a/storage/rocksdb/rocksdb/build_tools/amalgamate.py b/storage/rocksdb/rocksdb/build_tools/amalgamate.py
new file mode 100755
index 00000000..c5cbb3f0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/amalgamate.py
@@ -0,0 +1,111 @@
+#!/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
+from os import path
+import re
+import sys
+
+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/storage/rocksdb/rocksdb/build_tools/build_detect_platform b/storage/rocksdb/rocksdb/build_tools/build_detect_platform
new file mode 100755
index 00000000..158c161a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/build_detect_platform
@@ -0,0 +1,730 @@
+#!/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
+# 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
+#
+# 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 <output-filename>" >&2
+ exit 1
+fi
+
+# we depend on C++11
+PLATFORM_CXXFLAGS="-std=c++11"
+# 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 we need pic build
+ PIC_BUILD=$COMPILE_WITH_TSAN
+ if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
+ # we need this to build with MySQL. Don't use for other purposes.
+ source "$PWD/build_tools/fbcode_config4.8.1.sh"
+ elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
+ source "$PWD/build_tools/fbcode_config.sh"
+ else
+ source "$PWD/build_tools/fbcode_config_platform007.sh"
+ fi
+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
+
+# 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 -DROCKSDB_LITE"
+ 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"
+ if test $ROCKSDB_USE_IO_URING; then
+ # check for liburing
+ $CXX $CFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
+ #include <liburing.h>
+ 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
+ if test -z "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
+ USE_FOLLY_DISTRIBUTED_MUTEX=1
+ 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
+ ;;
+ 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"
+
+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
+ true
+else
+ if ! test $ROCKSDB_DISABLE_FALLOCATE; then
+ # Test whether fallocate is available
+ $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <fcntl.h>
+ #include <linux/falloc.h>
+ 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 $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <snappy.h>
+ 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
+ $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
+ #include <gflags/gflags.h>
+ int main() {}
+EOF
+ if [ "$?" = 0 ]; then
+ COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
+ PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
+ else
+ # check if namespace is google
+ $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
+ #include <gflags/gflags.h>
+ using namespace google;
+ int main() {}
+EOF
+ if [ "$?" = 0 ]; then
+ COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google"
+ PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
+ fi
+ fi
+ fi
+
+ if ! test $ROCKSDB_DISABLE_ZLIB; then
+ # Test whether zlib library is installed
+ $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <zlib.h>
+ 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 $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <bzlib.h>
+ 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 $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <lz4.h>
+ #include <lz4hc.h>
+ 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 $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <zstd.h>
+ 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 $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
+ #include <numa.h>
+ #include <numaif.h>
+ 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 $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
+ #include <tbb/tbb.h>
+ 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 $CFLAGS -x c++ - -o /dev/null -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 $CFLAGS -x c++ - -o /dev/null \
+ -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 $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <malloc.h>
+ 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_PTHREAD_MUTEX_ADAPTIVE_NP; then
+ # Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
+ $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <pthread.h>
+ 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 $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <execinfo.h>
+ 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 $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <execinfo.h>
+ 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 $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
+ int main() {
+ return 0;
+ }
+EOF
+ if [ "$?" = 0 ]; then
+ PROFILING_FLAGS=-pg
+ fi
+ fi
+
+ if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then
+ # Test whether sync_file_range is supported for compatibility with an old glibc
+ $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <fcntl.h>
+ 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 $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <sched.h>
+ 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_ALIGNED_NEW; then
+ # Test whether c++17 aligned-new is supported
+ $CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o /dev/null 2>/dev/null <<EOF
+ struct alignas(1024) t {int a;};
+ int main() {}
+EOF
+ if [ "$?" = 0 ]; then
+ PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS -faligned-new -DHAVE_ALIGNED_NEW"
+ 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 i386
+if ! [ "$TARGET_OS" = FreeBSD -a "$TARGET_ARCHITECTURE" = i386 ]; then
+ # Test whether -Wshorten-64-to-32 is available
+ $CXX $CFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
+ int main() {}
+EOF
+ if [ "$?" = 0 ]; then
+ COMMON_FLAGS="$COMMON_FLAGS -Wshorten-64-to-32"
+ fi
+fi
+
+# shall we use HDFS?
+
+if test "$USE_HDFS"; then
+ if test -z "$JAVA_HOME"; then
+ echo "JAVA_HOME has to be set for HDFS usage." >&2
+ exit 1
+ fi
+ HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS -I$HADOOP_HOME/include"
+ HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64 -L$HADOOP_HOME/lib/native"
+ HDFS_LDFLAGS="$HDFS_LDFLAGS -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib"
+ HDFS_LDFLAGS="$HDFS_LDFLAGS -ldl -lverify -ljava -ljvm"
+ COMMON_FLAGS="$COMMON_FLAGS $HDFS_CCFLAGS"
+ PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $HDFS_LDFLAGS"
+ JAVA_LDFLAGS="$JAVA_LDFLAGS $HDFS_LDFLAGS"
+fi
+
+if test "0$PORTABLE" -eq 0; then
+ if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
+ # Tune for this POWER processor, treating '+' models as base models
+ POWER=`LD_SHOW_AUXV=1 /bin/true | grep AT_PLATFORM | grep -E -o power[0-9]+`
+ COMMON_FLAGS="$COMMON_FLAGS -mcpu=$POWER -mtune=$POWER "
+ elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
+ COMMON_FLAGS="$COMMON_FLAGS -march=z10 "
+ elif test -n "`echo $TARGET_ARCHITECTURE | grep -e^arm -e^aarch64`"; then
+ # TODO: Handle this with approprite options.
+ COMMON_FLAGS="$COMMON_FLAGS"
+ elif test -n "`echo $TARGET_ARCHITECTURE | grep ^aarch64`"; then
+ COMMON_FLAGS="$COMMON_FLAGS"
+ 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
+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
+ TRY_SSE42="-msse4.2"
+ # PCLMUL available since westmere, ca. 2010-2011
+ TRY_PCLMUL="-mpclmul"
+ # AVX2 available since haswell, ca. 2013-2015
+ TRY_AVX2="-mavx2"
+fi
+
+$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <cstdint>
+ #include <nmmintrin.h>
+ 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 /dev/null 2>/dev/null <<EOF
+ #include <cstdint>
+ #include <wmmintrin.h>
+ 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 /dev/null 2>/dev/null <<EOF
+ #include <cstdint>
+ #include <immintrin.h>
+ 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 -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #include <cstdint>
+ int main() {
+ uint64_t a = 0xffffFFFFffffFFFF;
+ __uint128_t b = __uint128_t(a) * a;
+ a = static_cast<uint64_t>(b >> 64);
+ (void)a;
+ }
+EOF
+if [ "$?" = 0 ]; then
+ COMMON_FLAGS="$COMMON_FLAGS -DHAVE_UINT128_EXTENSION"
+fi
+
+# iOS doesn't support thread-local storage, but this check would erroneously
+# succeed because the cross-compiler flags are added by the Makefile, not this
+# script.
+if [ "$PLATFORM" != IOS ]; then
+ $CXX $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
+ #if defined(_MSC_VER) && !defined(__thread)
+ #define __thread __declspec(thread)
+ #endif
+ int main() {
+ static __thread int tls;
+ (void)tls;
+ }
+EOF
+ if [ "$?" = 0 ]; then
+ COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SUPPORT_THREAD_LOCAL"
+ fi
+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 <<EOF
+ void dummy_func() {}
+EOF
+ if [ "$?" = 0 ]; then
+ $CXX $COMMON_FLAGS $PLATFORM_SHARED_LDFLAGS test_dl.o -o /dev/null 2>/dev/null
+ if [ "$?" = 0 ]; then
+ EXEC_LDFLAGS+="-ldl"
+ rm -f test_dl.o
+ fi
+ 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 "PLATFORM=$PLATFORM" >> "$OUTPUT"
+echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT"
+echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT"
+echo "JAVA_STATIC_LDFLAGS=$JAVA_STATIC_LDFLAGS" >> "$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"
+# 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_DISTRIBUTED_MUTEX"; then
+ echo "USE_FOLLY_DISTRIBUTED_MUTEX=$USE_FOLLY_DISTRIBUTED_MUTEX" >> "$OUTPUT"
+fi
diff --git a/storage/rocksdb/rocksdb/build_tools/dependencies.sh b/storage/rocksdb/rocksdb/build_tools/dependencies.sh
new file mode 100644
index 00000000..22454c76
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/dependencies.sh
@@ -0,0 +1,19 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/5.x/centos7-native/c447969
+CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/1bd23f9917738974ad0ff305aa23eb5f93f18305/9.0.0/centos7-native/c9f9104
+LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/5.x/gcc-5-glibc-2.23/339d858
+GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.23/gcc-5-glibc-2.23/ca1d1c0
+SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/gcc-5-glibc-2.23/9bc6787
+ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/gcc-5-glibc-2.23/9bc6787
+BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/gcc-5-glibc-2.23/9bc6787
+LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/gcc-5-glibc-2.23/9bc6787
+ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/gcc-5-glibc-2.23/03859b5
+GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/gcc-5-glibc-2.23/9bc6787
+JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/gcc-5-glibc-2.23/0c8f76d
+NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/gcc-5-glibc-2.23/9bc6787
+LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/gcc-5-glibc-2.23/b443de1
+TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/gcc-5-glibc-2.23/9bc6787
+KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
+BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/2e3cb7d119b3cea5f1e738cc13a1ac69f49eb875/2.29.1/centos7-native/da39a3e
+VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/gcc-5-glibc-2.23/9bc6787
+LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.2.3/gcc-5-glibc-2.23/65372bd
diff --git a/storage/rocksdb/rocksdb/build_tools/dependencies_4.8.1.sh b/storage/rocksdb/rocksdb/build_tools/dependencies_4.8.1.sh
new file mode 100644
index 00000000..5c8f7e4f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/dependencies_4.8.1.sh
@@ -0,0 +1,20 @@
+# shellcheck disable=SC2148
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
+CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
+LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
+GLIBC_BASE=/mnt/gvfs/third-party2/glibc/d282e6e8f3d20f4e40a516834847bdc038e07973/2.17/gcc-4.8.1-glibc-2.17/99df8fc
+SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
+ZLIB_BASE=/mnt/gvfs/third-party2/zlib/0882df3713c7a84f15abe368dc004581f20b39d7/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
+BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
+LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-4.8.1-glibc-2.17/c3f970a
+ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9455f75ff7f4831dc9fda02a6a0f8c68922fad8f/1.0.0/gcc-4.8.1-glibc-2.17/c3f970a
+GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f001a51b2854957676d07306ef3abf67186b5c8b/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
+JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/fc8a13ca1fffa4d0765c716c5a0b49f0c107518f/master/gcc-4.8.1-glibc-2.17/8d31e51
+NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
+LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-4.8.1-glibc-2.17/675d945
+TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-4.8.1-glibc-2.17/c3f970a
+KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/7c111ff27e0c466235163f00f280a9d617c3d2ec/4.0.9-36_fbk5_2933_gd092e3f/gcc-4.8.1-glibc-2.17/da39a3e
+BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b7fd454c4b10c6a81015d4524ed06cdeab558490/2.26/centos6-native/da39a3e
+VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
+LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/centos6-native/730f94e
diff --git a/storage/rocksdb/rocksdb/build_tools/dependencies_platform007.sh b/storage/rocksdb/rocksdb/build_tools/dependencies_platform007.sh
new file mode 100644
index 00000000..e95f2979
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/dependencies_platform007.sh
@@ -0,0 +1,20 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
+CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
+LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
+GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
+SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
+ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
+BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
+LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
+ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
+GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
+JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
+NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
+LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
+TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
+LIBURING_BASE=/mnt/gvfs/third-party2/liburing/79427253fd0d42677255aacfe6d13bfe63f752eb/20190828/platform007/ca4da3d
+KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
+BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
+VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
+LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
diff --git a/storage/rocksdb/rocksdb/build_tools/dockerbuild.sh b/storage/rocksdb/rocksdb/build_tools/dockerbuild.sh
new file mode 100755
index 00000000..c0caede4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/dockerbuild.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+docker run -v $PWD:/rocks -w /rocks buildpack-deps make
diff --git a/storage/rocksdb/rocksdb/build_tools/error_filter.py b/storage/rocksdb/rocksdb/build_tools/error_filter.py
new file mode 100644
index 00000000..5d840b2f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/error_filter.py
@@ -0,0 +1,177 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+'''Filter for error messages in test output:
+ - Receives merged stdout/stderr from test on stdin
+ - Finds patterns of known error messages for test name (first argument)
+ - Prints those error messages to stdout
+'''
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import re
+import sys
+
+
+class ErrorParserBase(object):
+ def parse_error(self, line):
+ '''Parses a line of test output. If it contains an error, returns a
+ formatted message describing the error; otherwise, returns None.
+ Subclasses must override this method.
+ '''
+ raise NotImplementedError
+
+
+class GTestErrorParser(ErrorParserBase):
+ '''A parser that remembers the last test that began running so it can print
+ that test's name upon detecting failure.
+ '''
+ _GTEST_NAME_PATTERN = re.compile(r'\[ RUN \] (\S+)$')
+ # format: '<filename or "unknown file">:<line #>: 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):
+ # '<filename>:<line #>:<column #>: error: <error msg>'
+ # format (link error):
+ # '<filename>:<line #>: error: <error msg>'
+ # 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: '<filename>:<line #>:<column #>: runtime error: <error msg>'
+ 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 <test name>' % 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/storage/rocksdb/rocksdb/build_tools/fb_compile_mongo.sh b/storage/rocksdb/rocksdb/build_tools/fb_compile_mongo.sh
new file mode 100755
index 00000000..ec733cdf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/fb_compile_mongo.sh
@@ -0,0 +1,55 @@
+#!/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/storage/rocksdb/rocksdb/build_tools/fbcode_config.sh b/storage/rocksdb/rocksdb/build_tools/fbcode_config.sh
new file mode 100644
index 00000000..4834be5f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/fbcode_config.sh
@@ -0,0 +1,165 @@
+#!/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"
+
+# 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"
+
+if test -z $PIC_BUILD; then
+ # location of zlib headers and libraries
+ ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
+ ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
+ CFLAGS+=" -DZLIB"
+
+ # location of bzip headers and libraries
+ BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
+ BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
+ CFLAGS+=" -DBZIP2"
+
+ LZ4_INCLUDE=" -I $LZ4_BASE/include/"
+ LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
+ CFLAGS+=" -DLZ4"
+fi
+
+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"
+
+# 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++"
+
+ 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++"
+
+ 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 -DROCKSDB_SUPPORT_THREAD_LOCAL -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
+
+USE_FOLLY_DISTRIBUTED_MUTEX=1
+
+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/storage/rocksdb/rocksdb/build_tools/fbcode_config4.8.1.sh b/storage/rocksdb/rocksdb/build_tools/fbcode_config4.8.1.sh
new file mode 100644
index 00000000..5f0813a0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/fbcode_config4.8.1.sh
@@ -0,0 +1,118 @@
+#!/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++ compiler and also
+# uses jemalloc
+
+BASEDIR=`dirname $BASH_SOURCE`
+source "$BASEDIR/dependencies_4.8.1.sh"
+
+# location of libgcc
+LIBGCC_INCLUDE="$LIBGCC_BASE/include"
+LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
+
+# location of glibc
+GLIBC_INCLUDE="$GLIBC_BASE/include"
+GLIBC_LIBS=" -L $GLIBC_BASE/lib"
+
+# location of snappy headers and libraries
+SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include"
+SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
+
+# location of zlib headers and libraries
+ZLIB_INCLUDE=" -I $ZLIB_BASE/include"
+ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
+
+# location of bzip headers and libraries
+BZIP2_INCLUDE=" -I $BZIP2_BASE/include/"
+BZIP2_LIBS=" $BZIP2_BASE/lib/libbz2.a"
+
+LZ4_INCLUDE=" -I $LZ4_BASE/include"
+LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
+
+ZSTD_INCLUDE=" -I $ZSTD_BASE/include"
+ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
+
+# location of gflags headers and libraries
+GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
+GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
+
+# location of jemalloc
+JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include"
+JEMALLOC_LIB="$JEMALLOC_BASE/lib/libjemalloc.a"
+
+# location of numa
+NUMA_INCLUDE=" -I $NUMA_BASE/include/"
+NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
+
+# location of libunwind
+LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
+
+# location of tbb
+TBB_INCLUDE=" -isystem $TBB_BASE/include/"
+TBB_LIBS="$TBB_BASE/lib/libtbb.a"
+
+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 $BZIP2_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
+
+STDLIBS="-L $GCC_BASE/lib64"
+
+if [ -z "$USE_CLANG" ]; then
+ # gcc
+ CC="$GCC_BASE/bin/gcc"
+ CXX="$GCC_BASE/bin/g++"
+
+ CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic"
+ CFLAGS+=" -isystem $GLIBC_INCLUDE"
+ CFLAGS+=" -isystem $LIBGCC_INCLUDE"
+ JEMALLOC=1
+else
+ # clang
+ CLANG_BIN="$CLANG_BASE/bin"
+ CLANG_LIB="$CLANG_BASE/lib"
+ CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
+ CC="$CLANG_BIN/clang"
+ CXX="$CLANG_BIN/clang++"
+
+ KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include/"
+
+ CFLAGS="-B$BINUTILS/gold -nostdinc -nostdlib"
+ CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1 "
+ CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1/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 "
+ 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 -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
+CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DZSTD -DNUMA -DTBB"
+CXXFLAGS+=" $CFLAGS"
+
+EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
+EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"
+EXEC_LDFLAGS+=" $LIBUNWIND"
+EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib"
+# required by libtbb
+EXEC_LDFLAGS+=" -ldl"
+
+PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
+
+EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
+
+VALGRIND_VER="$VALGRIND_BASE/bin/"
+
+LUA_PATH="$LUA_BASE"
+
+export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE LUA_PATH
diff --git a/storage/rocksdb/rocksdb/build_tools/fbcode_config_platform007.sh b/storage/rocksdb/rocksdb/build_tools/fbcode_config_platform007.sh
new file mode 100644
index 00000000..60b51800
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/fbcode_config_platform007.sh
@@ -0,0 +1,170 @@
+#!/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_platform007.sh"
+
+CFLAGS=""
+
+# libgcc
+LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
+LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
+
+# glibc
+GLIBC_INCLUDE="$GLIBC_BASE/include"
+GLIBC_LIBS=" -L $GLIBC_BASE/lib"
+
+# 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"
+
+if test -z $PIC_BUILD; then
+ # location of zlib headers and libraries
+ ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
+ ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
+ CFLAGS+=" -DZLIB"
+
+ # location of bzip headers and libraries
+ BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
+ BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
+ CFLAGS+=" -DBZIP2"
+
+ LZ4_INCLUDE=" -I $LZ4_BASE/include/"
+ LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
+ CFLAGS+=" -DLZ4"
+fi
+
+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"
+
+# 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"
+
+# location of LIBURING
+LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
+if test -z $PIC_BUILD; then
+ LIBURING_LIBS="$LIBURING_BASE/lib/liburing.a"
+else
+ LIBURING_LIBS="$LIBURING_BASE/lib/liburing_pic.a"
+fi
+CFLAGS+=" -DLIBURING"
+
+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 $LIBURING_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++"
+
+ CFLAGS+=" -B$BINUTILS/gold"
+ CFLAGS+=" -isystem $LIBGCC_INCLUDE"
+ CFLAGS+=" -isystem $GLIBC_INCLUDE"
+ JEMALLOC=1
+else
+ # clang
+ CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
+ CC="$CLANG_BIN/clang"
+ CXX="$CLANG_BIN/clang++"
+
+ KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
+
+ CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
+ CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
+ CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.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 -DROCKSDB_SUPPORT_THREAD_LOCAL -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"
+EXEC_LDFLAGS+=" -B$BINUTILS/gold"
+EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
+EXEC_LDFLAGS+=" $LIBUNWIND"
+EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/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 $LIBURING_LIBS"
+
+VALGRIND_VER="$VALGRIND_BASE/bin/"
+
+# lua not supported because it's on track for deprecation, I think
+LUA_PATH=
+LUA_LIB=
+
+USE_FOLLY_DISTRIBUTED_MUTEX=1
+
+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/storage/rocksdb/rocksdb/build_tools/format-diff.sh b/storage/rocksdb/rocksdb/build_tools/format-diff.sh
new file mode 100755
index 00000000..0fd3417f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/format-diff.sh
@@ -0,0 +1,138 @@
+#!/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.
+if [ -z $CLANG_FORMAT_DIFF ]
+then
+CLANG_FORMAT_DIFF="clang-format-diff.py"
+fi
+
+# Check clang-format-diff.py
+if ! which $CLANG_FORMAT_DIFF &> /dev/null
+then
+ 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 http://goo.gl/iUW1u2 -o ${CLANG_FORMAT_DIFF}"
+ 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, move both files (i.e. ${CLANG_FORMAT_DIFF} and clang-format) to some directory within PATH=${PATH}"
+ echo "and make sure ${CLANG_FORMAT_DIFF} is executable."
+ exit 128
+fi
+
+# Check argparse, a library that clang-format-diff.py requires.
+python 2>/dev/null << EOF
+import argparse
+EOF
+
+if [ "$?" != 0 ]
+then
+ 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
+
+# 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 master 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="$(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 master branch from that remote
+ [ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/master"
+ # 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)
+else
+ # Check the format of uncommitted lines,
+ diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1)
+fi
+
+if [ -z "$diffs" ]
+then
+ echo "Nothing needs to be reformatted!"
+ exit 0
+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\"`/"
+
+if [[ "$OPT" == *"-DTRAVIS"* ]]
+then
+ exit 1
+fi
+
+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/storage/rocksdb/rocksdb/build_tools/gnu_parallel b/storage/rocksdb/rocksdb/build_tools/gnu_parallel
new file mode 100755
index 00000000..1cf164ff
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/gnu_parallel
@@ -0,0 +1,7936 @@
+#!/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 <http://www.gnu.org/licenses/>
+# 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 | egrep "/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: $!");
+}
+
+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;
+ }
+}
+}
+
+sub init_progress {
+ # Uses:
+ # $opt::bar
+ # Returns:
+ # list of computers for progress output
+ $|=1;
+ 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;
+ 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");
+ }
+ }
+ if($opt::progress) {
+ my %progress = progress();
+ if($last_header ne $progress{'header'}) {
+ print $Global::original_stderr "\n", $progress{'header'}, "\n";
+ $last_header = $progress{'header'};
+ }
+ print $Global::original_stderr "\r",$progress{'status'};
+ 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 "\r", $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);
+ }
+ 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 <parallel\@gnu.org> 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 <http://gnu.org/licenses/gpl.html>",
+ "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 = <STDIN>;
+ 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("</proc/$pid/stat");
+
+ my $data = <$fh>;
+ 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 </dev/null 2>&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 dont 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", "<<joboutput @command\n");
+}
+
+sub linebuffer_print {
+ my $self = shift;
+ my ($fdno,$in_fh,$out_fd) = @_;
+ my $partial = \$self->{'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-<name> Each
+# file in ~/.parallel/semaphores/id-<name>/ 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/storage/rocksdb/rocksdb/build_tools/make_package.sh b/storage/rocksdb/rocksdb/build_tools/make_package.sh
new file mode 100755
index 00000000..7b5c52cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/make_package.sh
@@ -0,0 +1,134 @@
+# 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 <rocksdb_version>"
+ 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
+ make install INSTALL_PATH=package
+
+ cd package
+
+ LIB_DIR=lib
+ if [[ -z "$ARCH" ]]; then
+ ARCH=$(getconf LONG_BIT)
+ fi
+ if [[ ("$FPM_OUTPUT" = "rpm") && ($ARCH -eq 64) ]]; then
+ mv lib lib64
+ LIB_DIR=lib64
+ fi
+
+ fpm \
+ -s dir \
+ -t $FPM_OUTPUT \
+ -n rocksdb \
+ -v $1 \
+ --prefix /usr \
+ --url http://rocksdb.org/ \
+ -m rocksdb@fb.com \
+ --license BSD \
+ --vendor Facebook \
+ --description "RocksDB is an embeddable persistent key-value store for fast storage." \
+ include $LIB_DIR
+}
+
+# shellcheck disable=SC2068
+main $@
diff --git a/storage/rocksdb/rocksdb/build_tools/precommit_checker.py b/storage/rocksdb/rocksdb/build_tools/precommit_checker.py
new file mode 100755
index 00000000..7d5eeeca
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/precommit_checker.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python2.7
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+import argparse
+import commands
+import subprocess
+import sys
+import re
+import os
+import time
+
+
+#
+# Simple logger
+#
+
+class Log:
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.f = open(self.filename, 'w+', 0)
+
+ def caption(self, str):
+ line = "\n##### %s #####\n" % str
+ if self.f:
+ self.f.write("%s \n" % line)
+ else:
+ print(line)
+
+ def error(self, str):
+ data = "\n\n##### ERROR ##### %s" % str
+ if self.f:
+ self.f.write("%s \n" % data)
+ else:
+ print(data)
+
+ def log(self, str):
+ if self.f:
+ self.f.write("%s \n" % str)
+ else:
+ print(str)
+
+#
+# Shell Environment
+#
+
+
+class Env(object):
+
+ def __init__(self, logfile, tests):
+ self.tests = tests
+ self.log = Log(logfile)
+
+ def shell(self, cmd, path=os.getcwd()):
+ if path:
+ os.chdir(path)
+
+ self.log.log("==== shell session ===========================")
+ self.log.log("%s> %s" % (path, cmd))
+ status = subprocess.call("cd %s; %s" % (path, cmd), shell=True,
+ stdout=self.log.f, stderr=self.log.f)
+ self.log.log("status = %s" % status)
+ self.log.log("============================================== \n\n")
+ return status
+
+ def GetOutput(self, cmd, path=os.getcwd()):
+ if path:
+ os.chdir(path)
+
+ self.log.log("==== shell session ===========================")
+ self.log.log("%s> %s" % (path, cmd))
+ status, out = commands.getstatusoutput(cmd)
+ self.log.log("status = %s" % status)
+ self.log.log("out = %s" % out)
+ self.log.log("============================================== \n\n")
+ return status, out
+
+#
+# Pre-commit checker
+#
+
+
+class PreCommitChecker(Env):
+
+ def __init__(self, args):
+ Env.__init__(self, args.logfile, args.tests)
+ self.ignore_failure = args.ignore_failure
+
+ #
+ # Get commands for a given job from the determinator file
+ #
+ def get_commands(self, test):
+ status, out = self.GetOutput(
+ "RATIO=1 build_tools/rocksdb-lego-determinator %s" % test, ".")
+ return status, out
+
+ #
+ # Run a specific CI job
+ #
+ def run_test(self, test):
+ self.log.caption("Running test %s locally" % test)
+
+ # get commands for the CI job determinator
+ status, cmds = self.get_commands(test)
+ if status != 0:
+ self.log.error("Error getting commands for test %s" % test)
+ return False
+
+ # Parse the JSON to extract the commands to run
+ cmds = re.findall("'shell':'([^\']*)'", cmds)
+
+ if len(cmds) == 0:
+ self.log.log("No commands found")
+ return False
+
+ # Run commands
+ for cmd in cmds:
+ # Replace J=<..> with the local environment variable
+ if "J" in os.environ:
+ cmd = cmd.replace("J=1", "J=%s" % os.environ["J"])
+ cmd = cmd.replace("make ", "make -j%s " % os.environ["J"])
+ # Run the command
+ status = self.shell(cmd, ".")
+ if status != 0:
+ self.log.error("Error running command %s for test %s"
+ % (cmd, test))
+ return False
+
+ return True
+
+ #
+ # Run specified CI jobs
+ #
+ def run_tests(self):
+ if not self.tests:
+ self.log.error("Invalid args. Please provide tests")
+ return False
+
+ self.print_separator()
+ self.print_row("TEST", "RESULT")
+ self.print_separator()
+
+ result = True
+ for test in self.tests:
+ start_time = time.time()
+ self.print_test(test)
+ result = self.run_test(test)
+ elapsed_min = (time.time() - start_time) / 60
+ if not result:
+ self.log.error("Error running test %s" % test)
+ self.print_result("FAIL (%dm)" % elapsed_min)
+ if not self.ignore_failure:
+ return False
+ result = False
+ else:
+ self.print_result("PASS (%dm)" % elapsed_min)
+
+ self.print_separator()
+ return result
+
+ #
+ # Print a line
+ #
+ def print_separator(self):
+ print("".ljust(60, "-"))
+
+ #
+ # Print two colums
+ #
+ def print_row(self, c0, c1):
+ print("%s%s" % (c0.ljust(40), c1.ljust(20)))
+
+ def print_test(self, test):
+ print(test.ljust(40), end="")
+ sys.stdout.flush()
+
+ def print_result(self, result):
+ print(result.ljust(20))
+
+#
+# Main
+#
+parser = argparse.ArgumentParser(description='RocksDB pre-commit checker.')
+
+# --log <logfile>
+parser.add_argument('--logfile', default='/tmp/precommit-check.log',
+ help='Log file. Default is /tmp/precommit-check.log')
+# --ignore_failure
+parser.add_argument('--ignore_failure', action='store_true', default=False,
+ help='Stop when an error occurs')
+# <test ....>
+parser.add_argument('tests', nargs='+',
+ help='CI test(s) to run. e.g: unit punit asan tsan ubsan')
+
+args = parser.parse_args()
+checker = PreCommitChecker(args)
+
+print("Please follow log %s" % checker.log.filename)
+
+if not checker.run_tests():
+ print("Error running tests. Please check log file %s"
+ % checker.log.filename)
+ sys.exit(1)
+
+sys.exit(0)
diff --git a/storage/rocksdb/rocksdb/build_tools/regression_build_test.sh b/storage/rocksdb/rocksdb/build_tools/regression_build_test.sh
new file mode 100755
index 00000000..31fb90eb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/regression_build_test.sh
@@ -0,0 +1,414 @@
+#!/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.fillseq
+ rm -f $STAT_FILE.readrandom
+ rm -f $STAT_FILE.overwrite
+ rm -f $STAT_FILE.memtablefillreadrandom
+}
+
+trap cleanup EXIT
+
+if [ -z $GIT_BRANCH ]; then
+ git_br=`git rev-parse --abbrev-ref HEAD`
+else
+ git_br=$(basename $GIT_BRANCH)
+fi
+
+if [ $git_br == "master" ]; then
+ git_br=""
+else
+ git_br="."$git_br
+fi
+
+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 \
+ --hard_rate_limit=2 \
+ --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 \
+ --max_grandparent_overlap_factor=10 \
+ --use_plain_table=1 \
+ --open_files=-1 \
+ --mmap_read=1 \
+ --mmap_write=0 \
+ --memtablerep=prefix_hash \
+ --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.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build$git_br&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/storage/rocksdb/rocksdb/build_tools/rocksdb-lego-determinator b/storage/rocksdb/rocksdb/build_tools/rocksdb-lego-determinator
new file mode 100755
index 00000000..948488c4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/rocksdb-lego-determinator
@@ -0,0 +1,1068 @@
+#!/usr/bin/env bash
+# This script is executed by Sandcastle
+# to determine next steps to run
+
+# Usage:
+# EMAIL=<email> ONCALL=<email> TRIGGER=<trigger> SUBSCRIBER=<email> rocks_ci.py <test-name>
+#
+# Input Value
+# -------------------------------------------------------------------------
+# EMAIL Email address to report on trigger conditions
+# ONCALL Email address to raise a task on failure
+# TRIGGER Trigger conditions for email. Valid values are fail, warn, all
+# SUBSCRIBER Email addresss to add as subscriber for task
+#
+
+#
+# Report configuration
+#
+REPORT_EMAIL=
+if [ ! -z $EMAIL ]; then
+ if [ -z $TRIGGER ]; then
+ TRIGGER="fail"
+ fi
+
+ REPORT_EMAIL="
+ {
+ 'type':'email',
+ 'triggers': [ '$TRIGGER' ],
+ 'emails':['$EMAIL']
+ },"
+fi
+
+CREATE_TASK=
+if [ ! -z $ONCALL ]; then
+ CREATE_TASK="
+ {
+ 'type':'task',
+ 'triggers':[ 'fail' ],
+ 'priority':0,
+ 'subscribers':[ '$SUBSCRIBER' ],
+ 'tags':[ 'rocksdb', 'ci' ],
+ },"
+fi
+
+# For now, create the tasks using only the dedicated task creation tool.
+CREATE_TASK=
+
+REPORT=
+if [[ ! -z $REPORT_EMAIL || ! -z $CREATE_TASK ]]; then
+ REPORT="'report': [
+ $REPORT_EMAIL
+ $CREATE_TASK
+ ]"
+fi
+
+#
+# Helper variables
+#
+CLEANUP_ENV="
+{
+ 'name':'Cleanup environment',
+ 'shell':'rm -rf /dev/shm/rocksdb && mkdir /dev/shm/rocksdb && (chmod +t /dev/shm || true) && make clean',
+ 'user':'root'
+}"
+
+UPLOAD_DB_DIR="
+{
+ 'name':'Upload database directory',
+ 'shell':'tar -cvzf rocksdb_db.tar.gz /dev/shm/rocksdb/',
+ 'user':'root',
+ 'cleanup':true,
+ 'provide_artifacts': [
+ {
+ 'name':'rocksdb_db_dir',
+ 'paths': ['rocksdb_db.tar.gz'],
+ 'bundle': false,
+ },
+ ],
+}"
+
+# We will eventually set the RATIO to 1, but we want do this
+# in steps. RATIO=$(nproc) will make it work as J=1
+if [ -z $RATIO ]; then
+ RATIO=$(nproc)
+fi
+
+if [ -z $PARALLEL_J ]; then
+ PARALLEL_J="J=$(expr $(nproc) / ${RATIO})"
+fi
+
+if [ -z $PARALLEL_j ]; then
+ PARALLEL_j="-j$(expr $(nproc) / ${RATIO})"
+fi
+
+PARALLELISM="$PARALLEL_J $PARALLEL_j"
+
+DEBUG="OPT=-g"
+SHM="TEST_TMPDIR=/dev/shm/rocksdb"
+NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
+GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
+ASAN="COMPILE_WITH_ASAN=1"
+CLANG="USE_CLANG=1"
+# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
+# using platform007 gives us gcc-8 or higher which has that bug fixed.
+TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
+UBSAN="COMPILE_WITH_UBSAN=1"
+TSAN_CRASH='CRASH_TEST_EXT_ARGS="--compression_type=zstd --log2_keys_per_lock=22"'
+NON_TSAN_CRASH="CRASH_TEST_EXT_ARGS=--compression_type=zstd"
+DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
+HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
+SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-8u60-64/; export PATH=\$JAVA_HOME/bin:\$PATH"
+PARSER="'parser':'python build_tools/error_filter.py $1'"
+
+CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
+
+# This code is getting called under various scenarios. What we care about is to
+# understand when it's called from nightly contruns because in that case we'll
+# create tasks for any failures. To follow the existing pattern, we'll check
+# the value of $ONCALL. If it's a diff then just call `false` to make sure
+# that errors will be properly propagated to the caller.
+if [ ! -z $ONCALL ]; then
+ TASK_CREATION_TOOL="/usr/local/bin/mysql_mtr_filter --rocksdb --oncall $ONCALL"
+else
+ TASK_CREATION_TOOL="false"
+fi
+
+#
+# A mechanism to disable tests temporarily
+#
+DISABLE_COMMANDS="[
+ {
+ 'name':'Disable test',
+ 'oncall':'$ONCALL',
+ 'steps': [
+ {
+ 'name':'Job disabled. Please contact test owner',
+ 'shell':'exit 1',
+ 'user':'root'
+ },
+ ],
+ }
+]"
+
+#
+# RocksDB unit test
+#
+UNIT_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and test RocksDB debug version',
+ 'shell':'$SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB unit test not under /dev/shm
+#
+UNIT_TEST_NON_SHM_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and test RocksDB debug version',
+ 'timeout': 86400,
+ 'shell':'$NON_SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=non_shm_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB release build and unit tests
+#
+RELEASE_BUILD_COMMANDS="[
+ {
+ 'name':'Rocksdb Release Build',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build RocksDB release',
+ 'shell':'make $PARALLEL_j release || $CONTRUN_NAME=release $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB unit test on gcc-4.8.1
+#
+UNIT_TEST_COMMANDS_481="[
+ {
+ 'name':'Rocksdb Unit Test on GCC 4.8.1',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and test RocksDB debug version',
+ 'shell':'$SHM $GCC_481 $DEBUG make $PARALLELISM check || $CONTRUN_NAME=unit_gcc_481_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB release build and unit tests
+#
+RELEASE_BUILD_COMMANDS_481="[
+ {
+ 'name':'Rocksdb Release on GCC 4.8.1',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build RocksDB release on GCC 4.8.1',
+ 'shell':'$GCC_481 make $PARALLEL_j release || $CONTRUN_NAME=release_gcc481 $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB unit test with CLANG
+#
+CLANG_UNIT_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and test RocksDB debug',
+ 'shell':'$CLANG $SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=clang_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB release build with CLANG
+#
+CLANG_RELEASE_BUILD_COMMANDS="[
+ {
+ 'name':'Rocksdb CLANG Release Build',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build RocksDB release',
+ 'shell':'$CLANG make $PARALLEL_j release|| $CONTRUN_NAME=clang_release $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB analyze
+#
+CLANG_ANALYZE_COMMANDS="[
+ {
+ 'name':'Rocksdb analyze',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'RocksDB build and analyze',
+ 'shell':'$CLANG $SHM $DEBUG make $PARALLEL_j analyze || $CONTRUN_NAME=clang_analyze $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB code coverage
+#
+CODE_COV_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test Code Coverage',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build, test and collect code coverage info',
+ 'shell':'$SHM $DEBUG make $PARALLELISM coverage || $CONTRUN_NAME=coverage $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB unity
+#
+UNITY_COMMANDS="[
+ {
+ 'name':'Rocksdb Unity',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build, test unity test',
+ 'shell':'$SHM $DEBUG V=1 make J=1 unity_test || $CONTRUN_NAME=unity_test $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# Build RocksDB lite
+#
+LITE_BUILD_COMMANDS="[
+ {
+ 'name':'Rocksdb Lite build',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build RocksDB debug version',
+ 'shell':'make J=1 LITE=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# Report RocksDB lite binary size to scuba
+REPORT_LITE_BINARY_SIZE_COMMANDS="[
+ {
+ 'name':'Rocksdb Lite Binary Size',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Report RocksDB Lite binary size to scuba',
+ 'shell':'tools/report_lite_binary_size.sh',
+ 'user':'root',
+ },
+ ],
+]"
+
+#
+# RocksDB stress/crash test
+#
+STRESS_CRASH_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Stress and Crash Test',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug stress tests',
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ {
+ 'name':'Build and run RocksDB debug crash tests',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB stress/crash test with atomic flush
+#
+STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
+ {
+ 'name':'Rocksdb Stress and Crash Test with atomic flush',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug stress tests',
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ {
+ 'name':'Build and run RocksDB debug crash tests with atomic flush',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB stress/crash test with txn
+#
+STRESS_CRASH_TEST_WITH_TXN_COMMANDS="[
+ {
+ 'name':'Rocksdb Stress and Crash Test with txn',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug stress tests',
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ {
+ 'name':'Build and run RocksDB debug crash tests with txn',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_txn || $CONTRUN_NAME=crash_test_with_txn $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+# RocksDB write stress test.
+# We run on disk device on purpose (i.e. no $SHM)
+# because we want to add some randomness to fsync commands
+WRITE_STRESS_COMMANDS="[
+ {
+ 'name':'Rocksdb Write Stress Test',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB write stress tests',
+ 'shell':'make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ }
+ ],
+ 'artifacts': [{'name': 'database', 'paths': ['/tmp/rocksdb_write_stress']}],
+ $REPORT
+ }
+]"
+
+
+#
+# RocksDB test under address sanitizer
+#
+ASAN_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test under ASAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Test RocksDB debug under ASAN',
+'shell':'set -o pipefail && ($SHM $ASAN $DEBUG make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL) |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
+ 'user':'root',
+ $PARSER
+ }
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing under address sanitizer
+#
+ASAN_CRASH_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test under ASAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug asan_crash_test',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing with atomic flush under address sanitizer
+#
+ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test with atomic flush under ASAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug asan_crash_test_with_atomic_flush',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_atomic_flush || $CONTRUN_NAME=asan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing with txn under address sanitizer
+#
+ASAN_CRASH_TEST_WITH_TXN_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test with txn under ASAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug asan_crash_test_with_txn',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_txn || $CONTRUN_NAME=asan_crash_test_with_txn $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB test under undefined behavior sanitizer
+#
+UBSAN_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test under UBSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Test RocksDB debug under UBSAN',
+ 'shell':'set -o pipefail && $SHM $UBSAN $CLANG $DEBUG make $PARALLELISM ubsan_check || $CONTRUN_NAME=ubsan_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ }
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing under udnefined behavior sanitizer
+#
+UBSAN_CRASH_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test under UBSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug ubsan_crash_test',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing with atomic flush under undefined behavior sanitizer
+#
+UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test with atomic flush under UBSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug ubsan_crash_test_with_atomic_flush',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test_with_atomic_flush || $CONTRUN_NAME=ubsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash testing with txn under undefined behavior sanitizer
+#
+UBSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
+ {
+ 'name':'Rocksdb crash test with txn under UBSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build and run RocksDB debug ubsan_crash_test_with_txn',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test_with_txn || $CONTRUN_NAME=ubsan_crash_test_with_txn $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB unit test under valgrind
+#
+VALGRIND_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test under valgrind',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Run RocksDB debug unit tests',
+ 'timeout': 86400,
+ 'shell':'$SHM $DEBUG make $PARALLELISM valgrind_test || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB test under TSAN
+#
+TSAN_UNIT_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Unit Test under TSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Run RocksDB debug unit test',
+ 'timeout': 86400,
+ 'shell':'set -o pipefail && $SHM $DEBUG $TSAN make $PARALLELISM check || $CONTRUN_NAME=tsan_check $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash test under TSAN
+#
+TSAN_CRASH_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Crash Test under TSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Compile and run',
+ 'timeout': 86400,
+ 'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash test with atomic flush under TSAN
+#
+TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
+ {
+ 'name':'Rocksdb Crash Test with atomic flush under TSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Compile and run',
+ 'timeout': 86400,
+ 'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=tsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB crash test with txn under TSAN
+#
+TSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
+ {
+ 'name':'Rocksdb Crash Test with txn under TSAN',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'timeout': 86400,
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Compile and run',
+ 'timeout': 86400,
+ 'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_txn || $CONTRUN_NAME=tsan_crash_test_with_txn $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ $UPLOAD_DB_DIR,
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB format compatible
+#
+
+run_format_compatible()
+{
+ export TEST_TMPDIR=/dev/shm/rocksdb
+ rm -rf /dev/shm/rocksdb
+ mkdir /dev/shm/rocksdb
+
+ tools/check_format_compatible.sh
+}
+
+FORMAT_COMPATIBLE_COMMANDS="[
+ {
+ 'name':'Rocksdb Format Compatible tests',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Run RocksDB debug unit test',
+ 'shell':'build_tools/rocksdb-lego-determinator run_format_compatible || $CONTRUN_NAME=run_format_compatible $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB no compression
+#
+run_no_compression()
+{
+ export TEST_TMPDIR=/dev/shm/rocksdb
+ rm -rf /dev/shm/rocksdb
+ mkdir /dev/shm/rocksdb
+ make clean
+ cat build_tools/fbcode_config.sh | grep -iv dzstd | grep -iv dzlib | grep -iv dlz4 | grep -iv dsnappy | grep -iv dbzip2 > .tmp.fbcode_config.sh
+ mv .tmp.fbcode_config.sh build_tools/fbcode_config.sh
+ cat Makefile | grep -v tools/ldb_test.py > .tmp.Makefile
+ mv .tmp.Makefile Makefile
+ make $DEBUG J=1 check
+}
+
+NO_COMPRESSION_COMMANDS="[
+ {
+ 'name':'Rocksdb No Compression tests',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Run RocksDB debug unit test',
+ 'shell':'build_tools/rocksdb-lego-determinator run_no_compression || $CONTRUN_NAME=run_no_compression $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB regression
+#
+run_regression()
+{
+ time -v bash -vx ./build_tools/regression_build_test.sh $(mktemp -d $WORKSPACE/leveldb.XXXX) $(mktemp leveldb_test_stats.XXXX)
+
+ # ======= report size to ODS ========
+
+ # parameters: $1 -- key, $2 -- value
+ function send_size_to_ods {
+ curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=rocksdb.build_size.$1&value=$2" \
+ --connect-timeout 60
+ }
+
+ # === normal build ===
+ make clean
+ make -j$(nproc) static_lib
+ send_size_to_ods static_lib $(stat --printf="%s" librocksdb.a)
+ strip librocksdb.a
+ send_size_to_ods static_lib_stripped $(stat --printf="%s" librocksdb.a)
+
+ make -j$(nproc) shared_lib
+ send_size_to_ods shared_lib $(stat --printf="%s" `readlink -f librocksdb.so`)
+ strip `readlink -f librocksdb.so`
+ send_size_to_ods shared_lib_stripped $(stat --printf="%s" `readlink -f librocksdb.so`)
+
+ # === lite build ===
+ make clean
+ make LITE=1 -j$(nproc) static_lib
+ send_size_to_ods static_lib_lite $(stat --printf="%s" librocksdb.a)
+ strip librocksdb.a
+ send_size_to_ods static_lib_lite_stripped $(stat --printf="%s" librocksdb.a)
+
+ make LITE=1 -j$(nproc) shared_lib
+ send_size_to_ods shared_lib_lite $(stat --printf="%s" `readlink -f librocksdb.so`)
+ strip `readlink -f librocksdb.so`
+ send_size_to_ods shared_lib_lite_stripped $(stat --printf="%s" `readlink -f librocksdb.so`)
+}
+
+REGRESSION_COMMANDS="[
+ {
+ 'name':'Rocksdb regression commands',
+ 'oncall':'$ONCALL',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Make and run script',
+ 'shell':'build_tools/rocksdb-lego-determinator run_regression || $CONTRUN_NAME=run_regression $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+#
+# RocksDB Java build
+#
+JAVA_BUILD_TEST_COMMANDS="[
+ {
+ 'name':'Rocksdb Java Build',
+ 'oncall':'$ONCALL',
+ 'executeLocal': 'true',
+ 'steps': [
+ $CLEANUP_ENV,
+ {
+ 'name':'Build RocksDB for Java',
+ 'shell':'$SETUP_JAVA_ENV; $SHM make rocksdbjava || $CONTRUN_NAME=rocksdbjava $TASK_CREATION_TOOL',
+ 'user':'root',
+ $PARSER
+ },
+ ],
+ $REPORT
+ }
+]"
+
+
+case $1 in
+ unit)
+ echo $UNIT_TEST_COMMANDS
+ ;;
+ unit_non_shm)
+ echo $UNIT_TEST_NON_SHM_COMMANDS
+ ;;
+ release)
+ echo $RELEASE_BUILD_COMMANDS
+ ;;
+ unit_481)
+ echo $UNIT_TEST_COMMANDS_481
+ ;;
+ release_481)
+ echo $RELEASE_BUILD_COMMANDS_481
+ ;;
+ clang_unit)
+ echo $CLANG_UNIT_TEST_COMMANDS
+ ;;
+ clang_release)
+ echo $CLANG_RELEASE_BUILD_COMMANDS
+ ;;
+ clang_analyze)
+ echo $CLANG_ANALYZE_COMMANDS
+ ;;
+ code_cov)
+ echo $CODE_COV_COMMANDS
+ ;;
+ unity)
+ echo $UNITY_COMMANDS
+ ;;
+ lite)
+ echo $LITE_BUILD_COMMANDS
+ ;;
+ report_lite_binary_size)
+ echo $REPORT_LITE_BINARY_SIZE_COMMANDS
+ ;;
+ stress_crash)
+ echo $STRESS_CRASH_TEST_COMMANDS
+ ;;
+ stress_crash_with_atomic_flush)
+ echo $STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
+ ;;
+ stress_crash_with_txn)
+ echo $STRESS_CRASH_TEST_WITH_TXN_COMMANDS
+ ;;
+ write_stress)
+ echo $WRITE_STRESS_COMMANDS
+ ;;
+ asan)
+ echo $ASAN_TEST_COMMANDS
+ ;;
+ asan_crash)
+ echo $ASAN_CRASH_TEST_COMMANDS
+ ;;
+ asan_crash_with_atomic_flush)
+ echo $ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
+ ;;
+ asan_crash_with_txn)
+ echo $ASAN_CRASH_TEST_WITH_TXN_COMMANDS
+ ;;
+ ubsan)
+ echo $UBSAN_TEST_COMMANDS
+ ;;
+ ubsan_crash)
+ echo $UBSAN_CRASH_TEST_COMMANDS
+ ;;
+ ubsan_crash_with_atomic_flush)
+ echo $UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
+ ;;
+ ubsan_crash_with_txn)
+ echo $UBSAN_CRASH_TEST_WITH_TXN_COMMANDS
+ ;;
+ valgrind)
+ echo $VALGRIND_TEST_COMMANDS
+ ;;
+ tsan)
+ echo $TSAN_UNIT_TEST_COMMANDS
+ ;;
+ tsan_crash)
+ echo $TSAN_CRASH_TEST_COMMANDS
+ ;;
+ tsan_crash_with_atomic_flush)
+ echo $TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
+ ;;
+ tsan_crash_with_txn)
+ echo $TSAN_CRASH_TEST_WITH_TXN_COMMANDS
+ ;;
+ format_compatible)
+ echo $FORMAT_COMPATIBLE_COMMANDS
+ ;;
+ run_format_compatible)
+ run_format_compatible
+ ;;
+ no_compression)
+ echo $NO_COMPRESSION_COMMANDS
+ ;;
+ run_no_compression)
+ run_no_compression
+ ;;
+ regression)
+ echo $REGRESSION_COMMANDS
+ ;;
+ run_regression)
+ run_regression
+ ;;
+ java_build)
+ echo $JAVA_BUILD_TEST_COMMANDS
+ ;;
+ *)
+ echo "Invalid determinator command"
+ exit 1
+ ;;
+esac
diff --git a/storage/rocksdb/rocksdb/build_tools/run_ci_db_test.ps1 b/storage/rocksdb/rocksdb/build_tools/run_ci_db_test.ps1
new file mode 100644
index 00000000..883d4e2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/run_ci_db_test.ps1
@@ -0,0 +1,487 @@
+# 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("backupable_db_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
+#
+# into this:
+#
+# DBTest.Empty
+# DBTest.WriteEmptyBatch
+# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
+# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
+#
+# 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) {
+
+ # 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
+ # remove trailing comment if any and create a log name
+ $test = $test -replace '\s+\#.*',''
+ $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/storage/rocksdb/rocksdb/build_tools/setup_centos7.sh b/storage/rocksdb/rocksdb/build_tools/setup_centos7.sh
new file mode 100755
index 00000000..060614dd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/setup_centos7.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+set -e
+
+ROCKSDB_VERSION="5.10.3"
+ZSTD_VERSION="1.1.3"
+
+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 make all
+sudo -u vagrant ./c_simple_example
diff --git a/storage/rocksdb/rocksdb/build_tools/update_dependencies.sh b/storage/rocksdb/rocksdb/build_tools/update_dependencies.sh
new file mode 100755
index 00000000..99099a40
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/update_dependencies.sh
@@ -0,0 +1,182 @@
+#!/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"
+}
+
+
+function log_variable()
+{
+ echo "$1=${!1}" >> "$OUTPUT"
+}
+
+
+TP2_LATEST="/mnt/vol/engshare/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`
+
+ # 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
+}
+
+###########################################################
+# platform007 dependencies #
+###########################################################
+
+OUTPUT="$BASEDIR/dependencies_platform007.sh"
+
+rm -f "$OUTPUT"
+touch "$OUTPUT"
+
+echo "Writing dependencies to $OUTPUT"
+
+# Compilers locations
+GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/`
+CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
+
+log_header
+log_variable GCC_BASE
+log_variable CLANG_BASE
+
+# Libraries locations
+get_lib_base libgcc 7.x platform007
+get_lib_base glibc 2.26 platform007
+get_lib_base snappy LATEST platform007
+get_lib_base zlib LATEST platform007
+get_lib_base bzip2 LATEST platform007
+get_lib_base lz4 LATEST platform007
+get_lib_base zstd LATEST platform007
+get_lib_base gflags LATEST platform007
+get_lib_base jemalloc LATEST platform007
+get_lib_base numa LATEST platform007
+get_lib_base libunwind LATEST platform007
+get_lib_base tbb LATEST platform007
+get_lib_base liburing LATEST platform007
+
+get_lib_base kernel-headers fb platform007
+get_lib_base binutils LATEST centos7-native
+get_lib_base valgrind LATEST platform007
+get_lib_base lua 5.3.4 platform007
+
+git diff $OUTPUT
+
+###########################################################
+# 5.x dependencies #
+###########################################################
+
+OUTPUT="$BASEDIR/dependencies.sh"
+
+rm -f "$OUTPUT"
+touch "$OUTPUT"
+
+echo "Writing dependencies to $OUTPUT"
+
+# Compilers locations
+GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos7-native/*/`
+CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
+
+log_header
+log_variable GCC_BASE
+log_variable CLANG_BASE
+
+# Libraries locations
+get_lib_base libgcc 5.x gcc-5-glibc-2.23
+get_lib_base glibc 2.23 gcc-5-glibc-2.23
+get_lib_base snappy LATEST gcc-5-glibc-2.23
+get_lib_base zlib LATEST gcc-5-glibc-2.23
+get_lib_base bzip2 LATEST gcc-5-glibc-2.23
+get_lib_base lz4 LATEST gcc-5-glibc-2.23
+get_lib_base zstd LATEST gcc-5-glibc-2.23
+get_lib_base gflags LATEST gcc-5-glibc-2.23
+get_lib_base jemalloc LATEST gcc-5-glibc-2.23
+get_lib_base numa LATEST gcc-5-glibc-2.23
+get_lib_base libunwind LATEST gcc-5-glibc-2.23
+get_lib_base tbb LATEST gcc-5-glibc-2.23
+
+get_lib_base kernel-headers 4.0.9-36_fbk5_2933_gd092e3f gcc-5-glibc-2.23
+get_lib_base binutils LATEST centos7-native
+get_lib_base valgrind LATEST gcc-5-glibc-2.23
+get_lib_base lua 5.2.3 gcc-5-glibc-2.23
+
+git diff $OUTPUT
+
+###########################################################
+# 4.8.1 dependencies #
+###########################################################
+
+OUTPUT="$BASEDIR/dependencies_4.8.1.sh"
+
+rm -f "$OUTPUT"
+touch "$OUTPUT"
+
+echo "Writing 4.8.1 dependencies to $OUTPUT"
+
+# Compilers locations
+GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.8.1/centos6-native/*/`
+CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
+
+log_header
+log_variable GCC_BASE
+log_variable CLANG_BASE
+
+# Libraries locations
+get_lib_base libgcc 4.8.1 gcc-4.8.1-glibc-2.17
+get_lib_base glibc 2.17 gcc-4.8.1-glibc-2.17
+get_lib_base snappy LATEST gcc-4.8.1-glibc-2.17
+get_lib_base zlib LATEST gcc-4.8.1-glibc-2.17
+get_lib_base bzip2 LATEST gcc-4.8.1-glibc-2.17
+get_lib_base lz4 LATEST gcc-4.8.1-glibc-2.17
+get_lib_base zstd LATEST gcc-4.8.1-glibc-2.17
+get_lib_base gflags LATEST gcc-4.8.1-glibc-2.17
+get_lib_base jemalloc LATEST gcc-4.8.1-glibc-2.17
+get_lib_base numa LATEST gcc-4.8.1-glibc-2.17
+get_lib_base libunwind LATEST gcc-4.8.1-glibc-2.17
+get_lib_base tbb 4.0_update2 gcc-4.8.1-glibc-2.17
+
+get_lib_base kernel-headers LATEST gcc-4.8.1-glibc-2.17
+get_lib_base binutils LATEST centos6-native
+get_lib_base valgrind 3.8.1 gcc-4.8.1-glibc-2.17
+get_lib_base lua 5.2.3 centos6-native
+
+git diff $OUTPUT
diff --git a/storage/rocksdb/rocksdb/build_tools/version.sh b/storage/rocksdb/rocksdb/build_tools/version.sh
new file mode 100755
index 00000000..dbc1a929
--- /dev/null
+++ b/storage/rocksdb/rocksdb/build_tools/version.sh
@@ -0,0 +1,23 @@
+#!/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/storage/rocksdb/rocksdb/cache/cache_bench.cc b/storage/rocksdb/rocksdb/cache/cache_bench.cc
new file mode 100644
index 00000000..6ff36a32
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/cache_bench.cc
@@ -0,0 +1,281 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <cinttypes>
+
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "util/gflags_compat.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+
+static const uint32_t KB = 1024;
+
+DEFINE_int32(threads, 16, "Number of concurrent threads to run.");
+DEFINE_int64(cache_size, 8 * KB * KB,
+ "Number of bytes to use as a cache of uncompressed data.");
+DEFINE_int32(num_shard_bits, 4, "shard_bits.");
+
+DEFINE_int64(max_key, 1 * KB * KB * KB, "Max number of key to place in cache");
+DEFINE_uint64(ops_per_thread, 1200000, "Number of operations per thread.");
+
+DEFINE_bool(populate_cache, false, "Populate cache before operations");
+DEFINE_int32(insert_percent, 40,
+ "Ratio of insert to total workload (expressed as a percentage)");
+DEFINE_int32(lookup_percent, 50,
+ "Ratio of lookup to total workload (expressed as a percentage)");
+DEFINE_int32(erase_percent, 10,
+ "Ratio of erase to total workload (expressed as a percentage)");
+
+DEFINE_bool(use_clock_cache, false, "");
+
+namespace ROCKSDB_NAMESPACE {
+
+class CacheBench;
+namespace {
+void deleter(const Slice& /*key*/, void* value) {
+ delete reinterpret_cast<char *>(value);
+}
+
+// State shared by all concurrent executions of the same benchmark.
+class SharedState {
+ public:
+ explicit SharedState(CacheBench* cache_bench)
+ : cv_(&mu_),
+ num_threads_(FLAGS_threads),
+ num_initialized_(0),
+ start_(false),
+ num_done_(0),
+ cache_bench_(cache_bench) {
+ }
+
+ ~SharedState() {}
+
+ port::Mutex* GetMutex() {
+ return &mu_;
+ }
+
+ port::CondVar* GetCondVar() {
+ return &cv_;
+ }
+
+ CacheBench* GetCacheBench() const {
+ return cache_bench_;
+ }
+
+ void IncInitialized() {
+ num_initialized_++;
+ }
+
+ void IncDone() {
+ num_done_++;
+ }
+
+ bool AllInitialized() const {
+ return num_initialized_ >= num_threads_;
+ }
+
+ bool AllDone() const {
+ return num_done_ >= num_threads_;
+ }
+
+ void SetStart() {
+ start_ = true;
+ }
+
+ bool Started() const {
+ return start_;
+ }
+
+ private:
+ port::Mutex mu_;
+ port::CondVar cv_;
+
+ const uint64_t num_threads_;
+ uint64_t num_initialized_;
+ bool start_;
+ uint64_t num_done_;
+
+ CacheBench* cache_bench_;
+};
+
+// Per-thread state for concurrent executions of the same benchmark.
+struct ThreadState {
+ uint32_t tid;
+ Random rnd;
+ SharedState* shared;
+
+ ThreadState(uint32_t index, SharedState* _shared)
+ : tid(index), rnd(1000 + index), shared(_shared) {}
+};
+} // namespace
+
+class CacheBench {
+ public:
+ CacheBench() : num_threads_(FLAGS_threads) {
+ if (FLAGS_use_clock_cache) {
+ cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
+ if (!cache_) {
+ fprintf(stderr, "Clock cache not supported.\n");
+ exit(1);
+ }
+ } else {
+ cache_ = NewLRUCache(FLAGS_cache_size, FLAGS_num_shard_bits);
+ }
+ }
+
+ ~CacheBench() {}
+
+ void PopulateCache() {
+ Random rnd(1);
+ for (int64_t i = 0; i < FLAGS_cache_size; i++) {
+ uint64_t rand_key = rnd.Next() % FLAGS_max_key;
+ // Cast uint64* to be char*, data would be copied to cache
+ Slice key(reinterpret_cast<char*>(&rand_key), 8);
+ // do insert
+ cache_->Insert(key, new char[10], 1, &deleter);
+ }
+ }
+
+ bool Run() {
+ ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
+
+ PrintEnv();
+ SharedState shared(this);
+ std::vector<ThreadState*> threads(num_threads_);
+ for (uint32_t i = 0; i < num_threads_; i++) {
+ threads[i] = new ThreadState(i, &shared);
+ env->StartThread(ThreadBody, threads[i]);
+ }
+ {
+ MutexLock l(shared.GetMutex());
+ while (!shared.AllInitialized()) {
+ shared.GetCondVar()->Wait();
+ }
+ // Record start time
+ uint64_t start_time = env->NowMicros();
+
+ // Start all threads
+ shared.SetStart();
+ shared.GetCondVar()->SignalAll();
+
+ // Wait threads to complete
+ while (!shared.AllDone()) {
+ shared.GetCondVar()->Wait();
+ }
+
+ // Record end time
+ uint64_t end_time = env->NowMicros();
+ double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
+ uint32_t qps = static_cast<uint32_t>(
+ static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
+ fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
+ }
+ return true;
+ }
+
+ private:
+ std::shared_ptr<Cache> cache_;
+ uint32_t num_threads_;
+
+ static void ThreadBody(void* v) {
+ ThreadState* thread = reinterpret_cast<ThreadState*>(v);
+ SharedState* shared = thread->shared;
+
+ {
+ MutexLock l(shared->GetMutex());
+ shared->IncInitialized();
+ if (shared->AllInitialized()) {
+ shared->GetCondVar()->SignalAll();
+ }
+ while (!shared->Started()) {
+ shared->GetCondVar()->Wait();
+ }
+ }
+ thread->shared->GetCacheBench()->OperateCache(thread);
+
+ {
+ MutexLock l(shared->GetMutex());
+ shared->IncDone();
+ if (shared->AllDone()) {
+ shared->GetCondVar()->SignalAll();
+ }
+ }
+ }
+
+ void OperateCache(ThreadState* thread) {
+ for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
+ uint64_t rand_key = thread->rnd.Next() % FLAGS_max_key;
+ // Cast uint64* to be char*, data would be copied to cache
+ Slice key(reinterpret_cast<char*>(&rand_key), 8);
+ int32_t prob_op = thread->rnd.Uniform(100);
+ if (prob_op >= 0 && prob_op < FLAGS_insert_percent) {
+ // do insert
+ cache_->Insert(key, new char[10], 1, &deleter);
+ } else if (prob_op -= FLAGS_insert_percent &&
+ prob_op < FLAGS_lookup_percent) {
+ // do lookup
+ auto handle = cache_->Lookup(key);
+ if (handle) {
+ cache_->Release(handle);
+ }
+ } else if (prob_op -= FLAGS_lookup_percent &&
+ prob_op < FLAGS_erase_percent) {
+ // do erase
+ cache_->Erase(key);
+ }
+ }
+ }
+
+ void PrintEnv() const {
+ printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
+ printf("Number of threads : %d\n", FLAGS_threads);
+ printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
+ printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
+ printf("Num shard bits : %d\n", FLAGS_num_shard_bits);
+ printf("Max key : %" PRIu64 "\n", FLAGS_max_key);
+ printf("Populate cache : %d\n", FLAGS_populate_cache);
+ printf("Insert percentage : %d%%\n", FLAGS_insert_percent);
+ printf("Lookup percentage : %d%%\n", FLAGS_lookup_percent);
+ printf("Erase percentage : %d%%\n", FLAGS_erase_percent);
+ printf("----------------------------\n");
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ if (FLAGS_threads <= 0) {
+ fprintf(stderr, "threads number <= 0\n");
+ exit(1);
+ }
+
+ ROCKSDB_NAMESPACE::CacheBench bench;
+ if (FLAGS_populate_cache) {
+ bench.PopulateCache();
+ }
+ if (bench.Run()) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/cache/cache_test.cc b/storage/rocksdb/rocksdb/cache/cache_test.cc
new file mode 100644
index 00000000..ceafefe6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/cache_test.cc
@@ -0,0 +1,773 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <forward_list>
+#include <functional>
+#include <iostream>
+#include <string>
+#include <vector>
+#include "cache/clock_cache.h"
+#include "cache/lru_cache.h"
+#include "test_util/testharness.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Conversions between numeric keys/values and the types expected by Cache.
+static std::string EncodeKey(int k) {
+ std::string result;
+ PutFixed32(&result, k);
+ return result;
+}
+static int DecodeKey(const Slice& k) {
+ assert(k.size() == 4);
+ return DecodeFixed32(k.data());
+}
+static void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
+static int DecodeValue(void* v) {
+ return static_cast<int>(reinterpret_cast<uintptr_t>(v));
+}
+
+const std::string kLRU = "lru";
+const std::string kClock = "clock";
+
+void dumbDeleter(const Slice& /*key*/, void* /*value*/) {}
+
+void eraseDeleter(const Slice& /*key*/, void* value) {
+ Cache* cache = reinterpret_cast<Cache*>(value);
+ cache->Erase("foo");
+}
+
+class CacheTest : public testing::TestWithParam<std::string> {
+ public:
+ static CacheTest* current_;
+
+ static void Deleter(const Slice& key, void* v) {
+ current_->deleted_keys_.push_back(DecodeKey(key));
+ current_->deleted_values_.push_back(DecodeValue(v));
+ }
+
+ static const int kCacheSize = 1000;
+ static const int kNumShardBits = 4;
+
+ static const int kCacheSize2 = 100;
+ static const int kNumShardBits2 = 2;
+
+ std::vector<int> deleted_keys_;
+ std::vector<int> deleted_values_;
+ std::shared_ptr<Cache> cache_;
+ std::shared_ptr<Cache> cache2_;
+
+ CacheTest()
+ : cache_(NewCache(kCacheSize, kNumShardBits, false)),
+ cache2_(NewCache(kCacheSize2, kNumShardBits2, false)) {
+ current_ = this;
+ }
+
+ ~CacheTest() override {}
+
+ std::shared_ptr<Cache> NewCache(size_t capacity) {
+ auto type = GetParam();
+ if (type == kLRU) {
+ return NewLRUCache(capacity);
+ }
+ if (type == kClock) {
+ return NewClockCache(capacity);
+ }
+ return nullptr;
+ }
+
+ std::shared_ptr<Cache> NewCache(
+ size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ CacheMetadataChargePolicy charge_policy = kDontChargeCacheMetadata) {
+ auto type = GetParam();
+ if (type == kLRU) {
+ LRUCacheOptions co;
+ co.capacity = capacity;
+ co.num_shard_bits = num_shard_bits;
+ co.strict_capacity_limit = strict_capacity_limit;
+ co.high_pri_pool_ratio = 0;
+ co.metadata_charge_policy = charge_policy;
+ return NewLRUCache(co);
+ }
+ if (type == kClock) {
+ return NewClockCache(capacity, num_shard_bits, strict_capacity_limit,
+ charge_policy);
+ }
+ return nullptr;
+ }
+
+ int Lookup(std::shared_ptr<Cache> 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> cache, int key, int value,
+ int charge = 1) {
+ cache->Insert(EncodeKey(key), EncodeValue(value), charge,
+ &CacheTest::Deleter);
+ }
+
+ void Erase(std::shared_ptr<Cache> 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);
+ }
+};
+CacheTest* CacheTest::current_;
+
+class LRUCacheTest : public CacheTest {};
+
+TEST_P(CacheTest, UsageTest) {
+ // cache is std::shared_ptr and will be automatically cleaned up.
+ const uint64_t kCapacity = 100000;
+ auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
+ auto precise_cache = NewCache(kCapacity, 0, false, kFullChargeCacheMetadata);
+ ASSERT_EQ(0, cache->GetUsage());
+ ASSERT_EQ(0, precise_cache->GetUsage());
+
+ size_t usage = 0;
+ char value[10] = "abcdef";
+ // make sure everything will be cached
+ for (int i = 1; i < 100; ++i) {
+ std::string key(i, 'a');
+ auto kv_size = key.size() + 5;
+ cache->Insert(key, reinterpret_cast<void*>(value), kv_size, dumbDeleter);
+ precise_cache->Insert(key, reinterpret_cast<void*>(value), kv_size,
+ dumbDeleter);
+ usage += kv_size;
+ ASSERT_EQ(usage, cache->GetUsage());
+ ASSERT_LT(usage, precise_cache->GetUsage());
+ }
+
+ cache->EraseUnRefEntries();
+ precise_cache->EraseUnRefEntries();
+ ASSERT_EQ(0, cache->GetUsage());
+ ASSERT_EQ(0, precise_cache->GetUsage());
+
+ // make sure the cache will be overloaded
+ for (uint64_t i = 1; i < kCapacity; ++i) {
+ auto key = ToString(i);
+ cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
+ dumbDeleter);
+ precise_cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
+ dumbDeleter);
+ }
+
+ // 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());
+ ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage());
+}
+
+TEST_P(CacheTest, PinnedUsageTest) {
+ // cache is std::shared_ptr and will be automatically cleaned up.
+ const uint64_t kCapacity = 200000;
+ auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
+ auto precise_cache = NewCache(kCapacity, 8, false, kFullChargeCacheMetadata);
+
+ size_t pinned_usage = 0;
+ char value[10] = "abcdef";
+
+ std::forward_list<Cache::Handle*> unreleased_handles;
+ std::forward_list<Cache::Handle*> 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(i, 'a');
+ auto kv_size = key.size() + 5;
+ Cache::Handle* handle;
+ Cache::Handle* handle_in_precise_cache;
+ cache->Insert(key, reinterpret_cast<void*>(value), kv_size, dumbDeleter,
+ &handle);
+ assert(handle);
+ precise_cache->Insert(key, reinterpret_cast<void*>(value), kv_size,
+ dumbDeleter, &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 (uint64_t i = 1; i < 2 * kCapacity; ++i) {
+ auto key = ToString(i);
+ cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
+ dumbDeleter);
+ precise_cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
+ dumbDeleter);
+ }
+ 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(0, 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);
+ ASSERT_EQ(102, Lookup(100));
+ ASSERT_EQ(201, Lookup(200));
+ ASSERT_EQ(-1, Lookup(300));
+
+ ASSERT_EQ(1U, deleted_keys_.size());
+ ASSERT_EQ(100, deleted_keys_[0]);
+ ASSERT_EQ(101, deleted_values_[0]);
+}
+
+TEST_P(CacheTest, InsertSameKey) {
+ Insert(1, 1);
+ Insert(1, 2);
+ ASSERT_EQ(2, Lookup(1));
+}
+
+TEST_P(CacheTest, Erase) {
+ Erase(200);
+ ASSERT_EQ(0U, deleted_keys_.size());
+
+ Insert(100, 101);
+ Insert(200, 201);
+ Erase(100);
+ ASSERT_EQ(-1, Lookup(100));
+ ASSERT_EQ(201, Lookup(200));
+ ASSERT_EQ(1U, deleted_keys_.size());
+ ASSERT_EQ(100, deleted_keys_[0]);
+ ASSERT_EQ(101, deleted_values_[0]);
+
+ Erase(100);
+ ASSERT_EQ(-1, Lookup(100));
+ ASSERT_EQ(201, Lookup(200));
+ ASSERT_EQ(1U, deleted_keys_.size());
+}
+
+TEST_P(CacheTest, EntriesArePinned) {
+ 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_keys_.size());
+ ASSERT_EQ(2U, cache_->GetUsage());
+
+ cache_->Release(h1);
+ ASSERT_EQ(1U, deleted_keys_.size());
+ ASSERT_EQ(100, deleted_keys_[0]);
+ ASSERT_EQ(101, deleted_values_[0]);
+ ASSERT_EQ(1U, cache_->GetUsage());
+
+ Erase(100);
+ ASSERT_EQ(-1, Lookup(100));
+ ASSERT_EQ(1U, deleted_keys_.size());
+ ASSERT_EQ(1U, cache_->GetUsage());
+
+ cache_->Release(h2);
+ ASSERT_EQ(2U, deleted_keys_.size());
+ ASSERT_EQ(100, deleted_keys_[1]);
+ 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 < kCacheSize * 2; 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);
+ }
+ 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 < kCacheSize * 2; 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) {
+ // Insert item large than capacity to trigger eviction on empty cache.
+ auto cache = NewCache(1, 0, false);
+ ASSERT_OK(cache->Insert("foo", nullptr, 10, dumbDeleter));
+}
+
+TEST_P(CacheTest, EraseFromDeleter) {
+ // Have deleter which will erase item from cache, which will re-enter
+ // the cache at that point.
+ std::shared_ptr<Cache> cache = NewCache(10, 0, false);
+ ASSERT_OK(cache->Insert("foo", nullptr, 1, dumbDeleter));
+ ASSERT_OK(cache->Insert("bar", cache.get(), 1, eraseDeleter));
+ 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);
+}
+
+
+class Value {
+ public:
+ explicit Value(size_t v) : v_(v) { }
+
+ size_t v_;
+};
+
+namespace {
+void deleter(const Slice& /*key*/, void* value) {
+ delete static_cast<Value *>(value);
+}
+} // namespace
+
+TEST_P(CacheTest, ReleaseAndErase) {
+ std::shared_ptr<Cache> cache = NewCache(5, 0, false);
+ Cache::Handle* handle;
+ Status s = cache->Insert(EncodeKey(100), EncodeValue(100), 1,
+ &CacheTest::Deleter, &handle);
+ ASSERT_TRUE(s.ok());
+ ASSERT_EQ(5U, cache->GetCapacity());
+ ASSERT_EQ(1U, cache->GetUsage());
+ ASSERT_EQ(0U, deleted_keys_.size());
+ auto erased = cache->Release(handle, true);
+ ASSERT_TRUE(erased);
+ // This tests that deleter has been called
+ ASSERT_EQ(1U, deleted_keys_.size());
+}
+
+TEST_P(CacheTest, ReleaseWithoutErase) {
+ std::shared_ptr<Cache> cache = NewCache(5, 0, false);
+ Cache::Handle* handle;
+ Status s = cache->Insert(EncodeKey(100), EncodeValue(100), 1,
+ &CacheTest::Deleter, &handle);
+ ASSERT_TRUE(s.ok());
+ ASSERT_EQ(5U, cache->GetCapacity());
+ ASSERT_EQ(1U, cache->GetUsage());
+ ASSERT_EQ(0U, deleted_keys_.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_keys_.size());
+}
+
+TEST_P(CacheTest, SetCapacity) {
+ // 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
+ std::shared_ptr<Cache> cache = NewCache(5, 0, false);
+ std::vector<Cache::Handle*> handles(10);
+ // Insert 5 entries, but not releasing.
+ for (size_t i = 0; i < 5; i++) {
+ std::string key = ToString(i+1);
+ Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
+ ASSERT_TRUE(s.ok());
+ }
+ ASSERT_EQ(5U, cache->GetCapacity());
+ ASSERT_EQ(5U, cache->GetUsage());
+ cache->SetCapacity(10);
+ ASSERT_EQ(10U, cache->GetCapacity());
+ ASSERT_EQ(5U, cache->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 (size_t i = 5; i < 10; i++) {
+ std::string key = ToString(i+1);
+ Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
+ ASSERT_TRUE(s.ok());
+ }
+ ASSERT_EQ(10U, cache->GetCapacity());
+ ASSERT_EQ(10U, cache->GetUsage());
+ for (size_t i = 0; i < 5; i++) {
+ cache->Release(handles[i]);
+ }
+ ASSERT_EQ(10U, cache->GetCapacity());
+ ASSERT_EQ(10U, cache->GetUsage());
+ cache->SetCapacity(7);
+ ASSERT_EQ(7, cache->GetCapacity());
+ ASSERT_EQ(7, cache->GetUsage());
+
+ // release remaining 5 to keep valgrind happy
+ for (size_t i = 5; i < 10; i++) {
+ cache->Release(handles[i]);
+ }
+}
+
+TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
+ // test1: set the flag to false. Insert more keys than capacity. See if they
+ // all go through.
+ std::shared_ptr<Cache> cache = NewCache(5, 0, false);
+ std::vector<Cache::Handle*> handles(10);
+ Status s;
+ for (size_t i = 0; i < 10; i++) {
+ std::string key = ToString(i + 1);
+ s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
+ ASSERT_OK(s);
+ ASSERT_NE(nullptr, handles[i]);
+ }
+ ASSERT_EQ(10, cache->GetUsage());
+
+ // test2: set the flag to true. Insert and check if it fails.
+ std::string extra_key = "extra";
+ Value* extra_value = new Value(0);
+ cache->SetStrictCapacityLimit(true);
+ Cache::Handle* handle;
+ s = cache->Insert(extra_key, extra_value, 1, &deleter, &handle);
+ ASSERT_TRUE(s.IsIncomplete());
+ ASSERT_EQ(nullptr, handle);
+ ASSERT_EQ(10, cache->GetUsage());
+
+ for (size_t i = 0; i < 10; i++) {
+ cache->Release(handles[i]);
+ }
+
+ // test3: init with flag being true.
+ std::shared_ptr<Cache> cache2 = NewCache(5, 0, true);
+ for (size_t i = 0; i < 5; i++) {
+ std::string key = ToString(i + 1);
+ s = cache2->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
+ ASSERT_OK(s);
+ ASSERT_NE(nullptr, handles[i]);
+ }
+ s = cache2->Insert(extra_key, extra_value, 1, &deleter, &handle);
+ ASSERT_TRUE(s.IsIncomplete());
+ ASSERT_EQ(nullptr, handle);
+ // test insert without handle
+ s = cache2->Insert(extra_key, extra_value, 1, &deleter);
+ // AS if the key have been inserted into cache but get evicted immediately.
+ ASSERT_OK(s);
+ ASSERT_EQ(5, cache2->GetUsage());
+ ASSERT_EQ(nullptr, cache2->Lookup(extra_key));
+
+ for (size_t 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
+ std::shared_ptr<Cache> cache = NewCache(n, 0, false);
+
+ std::vector<Cache::Handle*> handles(n+1);
+
+ // Insert n+1 entries, but not releasing.
+ for (size_t i = 0; i < n + 1; i++) {
+ std::string key = ToString(i+1);
+ Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
+ ASSERT_TRUE(s.ok());
+ }
+
+ // Guess what's in the cache now?
+ for (size_t i = 0; i < n + 1; i++) {
+ std::string key = ToString(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->GetUsage());
+ for (size_t i = 0; i < n + 1; i++) {
+ cache->Release(handles[i]);
+ }
+ // Make sure eviction is triggered.
+ cache->SetCapacity(n);
+
+ // cache is under capacity now since elements were released
+ ASSERT_EQ(n, cache->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 (size_t i = 0; i < n + 1; i++) {
+ std::string key = ToString(i+1);
+ auto h = cache->Lookup(key);
+ if (h) {
+ ASSERT_NE(i, 0U);
+ cache->Release(h);
+ } else {
+ ASSERT_EQ(i, 0U);
+ }
+ }
+}
+
+namespace {
+std::vector<std::pair<int, int>> callback_state;
+void callback(void* entry, size_t charge) {
+ callback_state.push_back({DecodeValue(entry), static_cast<int>(charge)});
+}
+};
+
+TEST_P(CacheTest, ApplyToAllCacheEntiresTest) {
+ std::vector<std::pair<int, int>> inserted;
+ callback_state.clear();
+
+ for (int i = 0; i < 10; ++i) {
+ Insert(i, i * 2, i + 1);
+ inserted.push_back({i * 2, i + 1});
+ }
+ cache_->ApplyToAllCacheEntries(callback, true);
+
+ std::sort(inserted.begin(), inserted.end());
+ std::sort(callback_state.begin(), callback_state.end());
+ ASSERT_TRUE(inserted == callback_state);
+}
+
+TEST_P(CacheTest, DefaultShardBits) {
+ // test1: set the flag to false. Insert more keys than capacity. See if they
+ // all go through.
+ std::shared_ptr<Cache> cache = NewCache(16 * 1024L * 1024L);
+ ShardedCache* sc = dynamic_cast<ShardedCache*>(cache.get());
+ ASSERT_EQ(5, sc->GetNumShardBits());
+
+ cache = NewLRUCache(511 * 1024L, -1, true);
+ sc = dynamic_cast<ShardedCache*>(cache.get());
+ ASSERT_EQ(0, sc->GetNumShardBits());
+
+ cache = NewLRUCache(1024L * 1024L * 1024L, -1, true);
+ sc = dynamic_cast<ShardedCache*>(cache.get());
+ ASSERT_EQ(6, sc->GetNumShardBits());
+}
+
+TEST_P(CacheTest, GetCharge) {
+ Insert(1, 2);
+ Cache::Handle* h1 = cache_->Lookup(EncodeKey(1));
+ ASSERT_EQ(2, DecodeValue(cache_->Value(h1)));
+ ASSERT_EQ(1, cache_->GetCharge(h1));
+ cache_->Release(h1);
+}
+
+#ifdef SUPPORT_CLOCK_CACHE
+std::shared_ptr<Cache> (*new_clock_cache_func)(
+ size_t, int, bool, CacheMetadataChargePolicy) = NewClockCache;
+INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest,
+ testing::Values(kLRU, kClock));
+#else
+INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest, testing::Values(kLRU));
+#endif // SUPPORT_CLOCK_CACHE
+INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest, testing::Values(kLRU));
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/cache/clock_cache.cc b/storage/rocksdb/rocksdb/cache/clock_cache.cc
new file mode 100644
index 00000000..797a44fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/clock_cache.cc
@@ -0,0 +1,761 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/clock_cache.h"
+
+#ifndef SUPPORT_CLOCK_CACHE
+
+namespace ROCKSDB_NAMESPACE {
+
+std::shared_ptr<Cache> NewClockCache(
+ size_t /*capacity*/, int /*num_shard_bits*/, bool /*strict_capacity_limit*/,
+ CacheMetadataChargePolicy /*metadata_charge_policy*/) {
+ // Clock cache not supported.
+ return nullptr;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#else
+
+#include <assert.h>
+#include <atomic>
+#include <deque>
+
+// "tbb/concurrent_hash_map.h" requires RTTI if exception is enabled.
+// Disable it so users can chooose to disable RTTI.
+#ifndef ROCKSDB_USE_RTTI
+#define TBB_USE_EXCEPTIONS 0
+#endif
+#include "tbb/concurrent_hash_map.h"
+
+#include "cache/sharded_cache.h"
+#include "port/malloc.h"
+#include "port/port.h"
+#include "util/autovector.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// An implementation of the Cache interface based on CLOCK algorithm, with
+// better concurrent performance than LRUCache. The idea of CLOCK algorithm
+// is to maintain all cache entries in a circular list, and an iterator
+// (the "head") pointing to the last examined entry. Eviction starts from the
+// current head. Each entry is given a second chance before eviction, if it
+// has been access since last examine. In contrast to LRU, no modification
+// to the internal data-structure (except for flipping the usage bit) needs
+// to be done upon lookup. This gives us oppertunity to implement a cache
+// with better concurrency.
+//
+// Each cache entry is represented by a cache handle, and all the handles
+// are arranged in a circular list, as describe above. Upon erase of an entry,
+// we never remove the handle. Instead, the handle is put into a recycle bin
+// to be re-use. This is to avoid memory dealocation, which is hard to deal
+// with in concurrent environment.
+//
+// The cache also maintains a concurrent hash map for lookup. Any concurrent
+// hash map implementation should do the work. We currently use
+// tbb::concurrent_hash_map because it supports concurrent erase.
+//
+// Each cache handle has the following flags and counters, which are squeeze
+// in an atomic interger, to make sure the handle always be in a consistent
+// state:
+//
+// * In-cache bit: whether the entry is reference by the cache itself. If
+// an entry is in cache, its key would also be available in the hash map.
+// * Usage bit: whether the entry has been access by user since last
+// examine for eviction. Can be reset by eviction.
+// * Reference count: reference count by user.
+//
+// An entry can be reference only when it's in cache. An entry can be evicted
+// only when it is in cache, has no usage since last examine, and reference
+// count is zero.
+//
+// The follow figure shows a possible layout of the cache. Boxes represents
+// cache handles and numbers in each box being in-cache bit, usage bit and
+// reference count respectively.
+//
+// hash map:
+// +-------+--------+
+// | key | handle |
+// +-------+--------+
+// | "foo" | 5 |-------------------------------------+
+// +-------+--------+ |
+// | "bar" | 2 |--+ |
+// +-------+--------+ | |
+// | |
+// head | |
+// | | |
+// circular list: | | |
+// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
+// |(0,0,0)|---|(1,1,0)|---|(0,0,0)|---|(0,1,3)|---|(1,0,0)|---| ...
+// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
+// | |
+// +-------+ +-----------+
+// | |
+// +---+---+
+// recycle bin: | 1 | 3 |
+// +---+---+
+//
+// Suppose we try to insert "baz" into the cache at this point and the cache is
+// full. The cache will first look for entries to evict, starting from where
+// head points to (the second entry). It resets usage bit of the second entry,
+// skips the third and fourth entry since they are not in cache, and finally
+// evict the fifth entry ("foo"). It looks at recycle bin for available handle,
+// grabs handle 3, and insert the key into the handle. The following figure
+// shows the resulting layout.
+//
+// hash map:
+// +-------+--------+
+// | key | handle |
+// +-------+--------+
+// | "baz" | 3 |-------------+
+// +-------+--------+ |
+// | "bar" | 2 |--+ |
+// +-------+--------+ | |
+// | |
+// | | head
+// | | |
+// circular list: | | |
+// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
+// |(0,0,0)|---|(1,0,0)|---|(1,0,0)|---|(0,1,3)|---|(0,0,0)|---| ...
+// +-------+ +-------+ +-------+ +-------+ +-------+ +-------
+// | |
+// +-------+ +-----------------------------------+
+// | |
+// +---+---+
+// recycle bin: | 1 | 5 |
+// +---+---+
+//
+// A global mutex guards the circular list, the head, and the recycle bin.
+// We additionally require that modifying the hash map needs to hold the mutex.
+// As such, Modifying the cache (such as Insert() and Erase()) require to
+// hold the mutex. Lookup() only access the hash map and the flags associated
+// with each handle, and don't require explicit locking. Release() has to
+// acquire the mutex only when it releases the last reference to the entry and
+// the entry has been erased from cache explicitly. A future improvement could
+// be to remove the mutex completely.
+//
+// Benchmark:
+// We run readrandom db_bench on a test DB of size 13GB, with size of each
+// level:
+//
+// Level Files Size(MB)
+// -------------------------
+// L0 1 0.01
+// L1 18 17.32
+// L2 230 182.94
+// L3 1186 1833.63
+// L4 4602 8140.30
+//
+// We test with both 32 and 16 read threads, with 2GB cache size (the whole DB
+// doesn't fits in) and 64GB cache size (the whole DB can fit in cache), and
+// whether to put index and filter blocks in block cache. The benchmark runs
+// with
+// with RocksDB 4.10. We got the following result:
+//
+// Threads Cache Cache ClockCache LRUCache
+// Size Index/Filter Throughput(MB/s) Hit Throughput(MB/s) Hit
+// 32 2GB yes 466.7 85.9% 433.7 86.5%
+// 32 2GB no 529.9 72.7% 532.7 73.9%
+// 32 64GB yes 649.9 99.9% 507.9 99.9%
+// 32 64GB no 740.4 99.9% 662.8 99.9%
+// 16 2GB yes 278.4 85.9% 283.4 86.5%
+// 16 2GB no 318.6 72.7% 335.8 73.9%
+// 16 64GB yes 391.9 99.9% 353.3 99.9%
+// 16 64GB no 433.8 99.8% 419.4 99.8%
+
+// Cache entry meta data.
+struct CacheHandle {
+ Slice key;
+ uint32_t hash;
+ void* value;
+ size_t charge;
+ void (*deleter)(const Slice&, void* value);
+
+ // Flags and counters associated with the cache handle:
+ // lowest bit: n-cache bit
+ // second lowest bit: usage bit
+ // the rest bits: reference count
+ // The handle is unused when flags equals to 0. The thread decreases the count
+ // to 0 is responsible to put the handle back to recycle_ and cleanup memory.
+ std::atomic<uint32_t> flags;
+
+ CacheHandle() = default;
+
+ CacheHandle(const CacheHandle& a) { *this = a; }
+
+ CacheHandle(const Slice& k, void* v,
+ void (*del)(const Slice& key, void* value))
+ : key(k), value(v), deleter(del) {}
+
+ CacheHandle& operator=(const CacheHandle& a) {
+ // Only copy members needed for deletion.
+ key = a.key;
+ value = a.value;
+ deleter = a.deleter;
+ return *this;
+ }
+
+ inline static size_t CalcTotalCharge(
+ Slice key, size_t charge,
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ size_t meta_charge = 0;
+ if (metadata_charge_policy == kFullChargeCacheMetadata) {
+ meta_charge += sizeof(CacheHandle);
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ meta_charge +=
+ malloc_usable_size(static_cast<void*>(const_cast<char*>(key.data())));
+#else
+ meta_charge += key.size();
+#endif
+ }
+ return charge + meta_charge;
+ }
+
+ inline size_t CalcTotalCharge(
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ return CalcTotalCharge(key, charge, metadata_charge_policy);
+ }
+};
+
+// Key of hash map. We store hash value with the key for convenience.
+struct CacheKey {
+ Slice key;
+ uint32_t hash_value;
+
+ CacheKey() = default;
+
+ CacheKey(const Slice& k, uint32_t h) {
+ key = k;
+ hash_value = h;
+ }
+
+ static bool equal(const CacheKey& a, const CacheKey& b) {
+ return a.hash_value == b.hash_value && a.key == b.key;
+ }
+
+ static size_t hash(const CacheKey& a) {
+ return static_cast<size_t>(a.hash_value);
+ }
+};
+
+struct CleanupContext {
+ // List of values to be deleted, along with the key and deleter.
+ autovector<CacheHandle> to_delete_value;
+
+ // List of keys to be deleted.
+ autovector<const char*> to_delete_key;
+};
+
+// A cache shard which maintains its own CLOCK cache.
+class ClockCacheShard final : public CacheShard {
+ public:
+ // Hash map type.
+ typedef tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey> HashTable;
+
+ ClockCacheShard();
+ ~ClockCacheShard() override;
+
+ // Interfaces
+ void SetCapacity(size_t capacity) override;
+ void SetStrictCapacityLimit(bool strict_capacity_limit) override;
+ Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Cache::Handle** handle, Cache::Priority priority) override;
+ Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
+ // If the entry in in cache, increase reference count and return true.
+ // Return false otherwise.
+ //
+ // Not necessary to hold mutex_ before being called.
+ bool Ref(Cache::Handle* handle) override;
+ bool Release(Cache::Handle* handle, bool force_erase = false) override;
+ void Erase(const Slice& key, uint32_t hash) override;
+ bool EraseAndConfirm(const Slice& key, uint32_t hash,
+ CleanupContext* context);
+ size_t GetUsage() const override;
+ size_t GetPinnedUsage() const override;
+ void EraseUnRefEntries() override;
+ void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) override;
+
+ private:
+ static const uint32_t kInCacheBit = 1;
+ static const uint32_t kUsageBit = 2;
+ static const uint32_t kRefsOffset = 2;
+ static const uint32_t kOneRef = 1 << kRefsOffset;
+
+ // Helper functions to extract cache handle flags and counters.
+ static bool InCache(uint32_t flags) { return flags & kInCacheBit; }
+ static bool HasUsage(uint32_t flags) { return flags & kUsageBit; }
+ static uint32_t CountRefs(uint32_t flags) { return flags >> kRefsOffset; }
+
+ // Decrease reference count of the entry. If this decreases the count to 0,
+ // recycle the entry. If set_usage is true, also set the usage bit.
+ //
+ // returns true if a value is erased.
+ //
+ // Not necessary to hold mutex_ before being called.
+ bool Unref(CacheHandle* handle, bool set_usage, CleanupContext* context);
+
+ // Unset in-cache bit of the entry. Recycle the handle if necessary.
+ //
+ // returns true if a value is erased.
+ //
+ // Has to hold mutex_ before being called.
+ bool UnsetInCache(CacheHandle* handle, CleanupContext* context);
+
+ // Put the handle back to recycle_ list, and put the value associated with
+ // it into to-be-deleted list. It doesn't cleanup the key as it might be
+ // reused by another handle.
+ //
+ // Has to hold mutex_ before being called.
+ void RecycleHandle(CacheHandle* handle, CleanupContext* context);
+
+ // Delete keys and values in to-be-deleted list. Call the method without
+ // holding mutex, as destructors can be expensive.
+ void Cleanup(const CleanupContext& context);
+
+ // Examine the handle for eviction. If the handle is in cache, usage bit is
+ // not set, and referece count is 0, evict it from cache. Otherwise unset
+ // the usage bit.
+ //
+ // Has to hold mutex_ before being called.
+ bool TryEvict(CacheHandle* value, CleanupContext* context);
+
+ // Scan through the circular list, evict entries until we get enough capacity
+ // for new cache entry of specific size. Return true if success, false
+ // otherwise.
+ //
+ // Has to hold mutex_ before being called.
+ bool EvictFromCache(size_t charge, CleanupContext* context);
+
+ CacheHandle* Insert(const Slice& key, uint32_t hash, void* value,
+ size_t change,
+ void (*deleter)(const Slice& key, void* value),
+ bool hold_reference, CleanupContext* context);
+
+ // Guards list_, head_, and recycle_. In addition, updating table_ also has
+ // to hold the mutex, to avoid the cache being in inconsistent state.
+ mutable port::Mutex mutex_;
+
+ // The circular list of cache handles. Initially the list is empty. Once a
+ // handle is needed by insertion, and no more handles are available in
+ // recycle bin, one more handle is appended to the end.
+ //
+ // We use std::deque for the circular list because we want to make sure
+ // pointers to handles are valid through out the life-cycle of the cache
+ // (in contrast to std::vector), and be able to grow the list (in contrast
+ // to statically allocated arrays).
+ std::deque<CacheHandle> list_;
+
+ // Pointer to the next handle in the circular list to be examine for
+ // eviction.
+ size_t head_;
+
+ // Recycle bin of cache handles.
+ autovector<CacheHandle*> recycle_;
+
+ // Maximum cache size.
+ std::atomic<size_t> capacity_;
+
+ // Current total size of the cache.
+ std::atomic<size_t> usage_;
+
+ // Total un-released cache size.
+ std::atomic<size_t> pinned_usage_;
+
+ // Whether allow insert into cache if cache is full.
+ std::atomic<bool> strict_capacity_limit_;
+
+ // Hash table (tbb::concurrent_hash_map) for lookup.
+ HashTable table_;
+};
+
+ClockCacheShard::ClockCacheShard()
+ : head_(0), usage_(0), pinned_usage_(0), strict_capacity_limit_(false) {}
+
+ClockCacheShard::~ClockCacheShard() {
+ for (auto& handle : list_) {
+ uint32_t flags = handle.flags.load(std::memory_order_relaxed);
+ if (InCache(flags) || CountRefs(flags) > 0) {
+ if (handle.deleter != nullptr) {
+ (*handle.deleter)(handle.key, handle.value);
+ }
+ delete[] handle.key.data();
+ }
+ }
+}
+
+size_t ClockCacheShard::GetUsage() const {
+ return usage_.load(std::memory_order_relaxed);
+}
+
+size_t ClockCacheShard::GetPinnedUsage() const {
+ return pinned_usage_.load(std::memory_order_relaxed);
+}
+
+void ClockCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) {
+ if (thread_safe) {
+ mutex_.Lock();
+ }
+ for (auto& handle : list_) {
+ // Use relaxed semantics instead of acquire semantics since we are either
+ // holding mutex, or don't have thread safe requirement.
+ uint32_t flags = handle.flags.load(std::memory_order_relaxed);
+ if (InCache(flags)) {
+ callback(handle.value, handle.charge);
+ }
+ }
+ if (thread_safe) {
+ mutex_.Unlock();
+ }
+}
+
+void ClockCacheShard::RecycleHandle(CacheHandle* handle,
+ CleanupContext* context) {
+ mutex_.AssertHeld();
+ assert(!InCache(handle->flags) && CountRefs(handle->flags) == 0);
+ context->to_delete_key.push_back(handle->key.data());
+ context->to_delete_value.emplace_back(*handle);
+ size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
+ handle->key.clear();
+ handle->value = nullptr;
+ handle->deleter = nullptr;
+ recycle_.push_back(handle);
+ usage_.fetch_sub(total_charge, std::memory_order_relaxed);
+}
+
+void ClockCacheShard::Cleanup(const CleanupContext& context) {
+ for (const CacheHandle& handle : context.to_delete_value) {
+ if (handle.deleter) {
+ (*handle.deleter)(handle.key, handle.value);
+ }
+ }
+ for (const char* key : context.to_delete_key) {
+ delete[] key;
+ }
+}
+
+bool ClockCacheShard::Ref(Cache::Handle* h) {
+ auto handle = reinterpret_cast<CacheHandle*>(h);
+ // CAS loop to increase reference count.
+ uint32_t flags = handle->flags.load(std::memory_order_relaxed);
+ while (InCache(flags)) {
+ // Use acquire semantics on success, as further operations on the cache
+ // entry has to be order after reference count is increased.
+ if (handle->flags.compare_exchange_weak(flags, flags + kOneRef,
+ std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ if (CountRefs(flags) == 0) {
+ // No reference count before the operation.
+ size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
+ pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ClockCacheShard::Unref(CacheHandle* handle, bool set_usage,
+ CleanupContext* context) {
+ if (set_usage) {
+ handle->flags.fetch_or(kUsageBit, std::memory_order_relaxed);
+ }
+ // Use acquire-release semantics as previous operations on the cache entry
+ // has to be order before reference count is decreased, and potential cleanup
+ // of the entry has to be order after.
+ uint32_t flags = handle->flags.fetch_sub(kOneRef, std::memory_order_acq_rel);
+ assert(CountRefs(flags) > 0);
+ if (CountRefs(flags) == 1) {
+ // this is the last reference.
+ size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
+ pinned_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
+ // Cleanup if it is the last reference.
+ if (!InCache(flags)) {
+ MutexLock l(&mutex_);
+ RecycleHandle(handle, context);
+ }
+ }
+ return context->to_delete_value.size();
+}
+
+bool ClockCacheShard::UnsetInCache(CacheHandle* handle,
+ CleanupContext* context) {
+ mutex_.AssertHeld();
+ // Use acquire-release semantics as previous operations on the cache entry
+ // has to be order before reference count is decreased, and potential cleanup
+ // of the entry has to be order after.
+ uint32_t flags =
+ handle->flags.fetch_and(~kInCacheBit, std::memory_order_acq_rel);
+ // Cleanup if it is the last reference.
+ if (InCache(flags) && CountRefs(flags) == 0) {
+ RecycleHandle(handle, context);
+ }
+ return context->to_delete_value.size();
+}
+
+bool ClockCacheShard::TryEvict(CacheHandle* handle, CleanupContext* context) {
+ mutex_.AssertHeld();
+ uint32_t flags = kInCacheBit;
+ if (handle->flags.compare_exchange_strong(flags, 0, std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ bool erased __attribute__((__unused__)) =
+ table_.erase(CacheKey(handle->key, handle->hash));
+ assert(erased);
+ RecycleHandle(handle, context);
+ return true;
+ }
+ handle->flags.fetch_and(~kUsageBit, std::memory_order_relaxed);
+ return false;
+}
+
+bool ClockCacheShard::EvictFromCache(size_t charge, CleanupContext* context) {
+ size_t usage = usage_.load(std::memory_order_relaxed);
+ size_t capacity = capacity_.load(std::memory_order_relaxed);
+ if (usage == 0) {
+ return charge <= capacity;
+ }
+ size_t new_head = head_;
+ bool second_iteration = false;
+ while (usage + charge > capacity) {
+ assert(new_head < list_.size());
+ if (TryEvict(&list_[new_head], context)) {
+ usage = usage_.load(std::memory_order_relaxed);
+ }
+ new_head = (new_head + 1 >= list_.size()) ? 0 : new_head + 1;
+ if (new_head == head_) {
+ if (second_iteration) {
+ return false;
+ } else {
+ second_iteration = true;
+ }
+ }
+ }
+ head_ = new_head;
+ return true;
+}
+
+void ClockCacheShard::SetCapacity(size_t capacity) {
+ CleanupContext context;
+ {
+ MutexLock l(&mutex_);
+ capacity_.store(capacity, std::memory_order_relaxed);
+ EvictFromCache(0, &context);
+ }
+ Cleanup(context);
+}
+
+void ClockCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
+ strict_capacity_limit_.store(strict_capacity_limit,
+ std::memory_order_relaxed);
+}
+
+CacheHandle* ClockCacheShard::Insert(
+ const Slice& key, uint32_t hash, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value), bool hold_reference,
+ CleanupContext* context) {
+ size_t total_charge =
+ CacheHandle::CalcTotalCharge(key, charge, metadata_charge_policy_);
+ MutexLock l(&mutex_);
+ bool success = EvictFromCache(total_charge, context);
+ bool strict = strict_capacity_limit_.load(std::memory_order_relaxed);
+ if (!success && (strict || !hold_reference)) {
+ context->to_delete_key.push_back(key.data());
+ if (!hold_reference) {
+ context->to_delete_value.emplace_back(key, value, deleter);
+ }
+ return nullptr;
+ }
+ // Grab available handle from recycle bin. If recycle bin is empty, create
+ // and append new handle to end of circular list.
+ CacheHandle* handle = nullptr;
+ if (!recycle_.empty()) {
+ handle = recycle_.back();
+ recycle_.pop_back();
+ } else {
+ list_.emplace_back();
+ handle = &list_.back();
+ }
+ // Fill handle.
+ handle->key = key;
+ handle->hash = hash;
+ handle->value = value;
+ handle->charge = charge;
+ handle->deleter = deleter;
+ uint32_t flags = hold_reference ? kInCacheBit + kOneRef : kInCacheBit;
+ handle->flags.store(flags, std::memory_order_relaxed);
+ HashTable::accessor accessor;
+ if (table_.find(accessor, CacheKey(key, hash))) {
+ CacheHandle* existing_handle = accessor->second;
+ table_.erase(accessor);
+ UnsetInCache(existing_handle, context);
+ }
+ table_.insert(HashTable::value_type(CacheKey(key, hash), handle));
+ if (hold_reference) {
+ pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
+ }
+ usage_.fetch_add(total_charge, std::memory_order_relaxed);
+ return handle;
+}
+
+Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
+ size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Cache::Handle** out_handle,
+ Cache::Priority /*priority*/) {
+ CleanupContext context;
+ HashTable::accessor accessor;
+ char* key_data = new char[key.size()];
+ memcpy(key_data, key.data(), key.size());
+ Slice key_copy(key_data, key.size());
+ CacheHandle* handle = Insert(key_copy, hash, value, charge, deleter,
+ out_handle != nullptr, &context);
+ Status s;
+ if (out_handle != nullptr) {
+ if (handle == nullptr) {
+ s = Status::Incomplete("Insert failed due to LRU cache being full.");
+ } else {
+ *out_handle = reinterpret_cast<Cache::Handle*>(handle);
+ }
+ }
+ Cleanup(context);
+ return s;
+}
+
+Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t hash) {
+ HashTable::const_accessor accessor;
+ if (!table_.find(accessor, CacheKey(key, hash))) {
+ return nullptr;
+ }
+ CacheHandle* handle = accessor->second;
+ accessor.release();
+ // Ref() could fail if another thread sneak in and evict/erase the cache
+ // entry before we are able to hold reference.
+ if (!Ref(reinterpret_cast<Cache::Handle*>(handle))) {
+ return nullptr;
+ }
+ // Double check the key since the handle may now representing another key
+ // if other threads sneak in, evict/erase the entry and re-used the handle
+ // for another cache entry.
+ if (hash != handle->hash || key != handle->key) {
+ CleanupContext context;
+ Unref(handle, false, &context);
+ // It is possible Unref() delete the entry, so we need to cleanup.
+ Cleanup(context);
+ return nullptr;
+ }
+ return reinterpret_cast<Cache::Handle*>(handle);
+}
+
+bool ClockCacheShard::Release(Cache::Handle* h, bool force_erase) {
+ CleanupContext context;
+ CacheHandle* handle = reinterpret_cast<CacheHandle*>(h);
+ bool erased = Unref(handle, true, &context);
+ if (force_erase && !erased) {
+ erased = EraseAndConfirm(handle->key, handle->hash, &context);
+ }
+ Cleanup(context);
+ return erased;
+}
+
+void ClockCacheShard::Erase(const Slice& key, uint32_t hash) {
+ CleanupContext context;
+ EraseAndConfirm(key, hash, &context);
+ Cleanup(context);
+}
+
+bool ClockCacheShard::EraseAndConfirm(const Slice& key, uint32_t hash,
+ CleanupContext* context) {
+ MutexLock l(&mutex_);
+ HashTable::accessor accessor;
+ bool erased = false;
+ if (table_.find(accessor, CacheKey(key, hash))) {
+ CacheHandle* handle = accessor->second;
+ table_.erase(accessor);
+ erased = UnsetInCache(handle, context);
+ }
+ return erased;
+}
+
+void ClockCacheShard::EraseUnRefEntries() {
+ CleanupContext context;
+ {
+ MutexLock l(&mutex_);
+ table_.clear();
+ for (auto& handle : list_) {
+ UnsetInCache(&handle, &context);
+ }
+ }
+ Cleanup(context);
+}
+
+class ClockCache final : public ShardedCache {
+ public:
+ ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ CacheMetadataChargePolicy metadata_charge_policy)
+ : ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
+ int num_shards = 1 << num_shard_bits;
+ shards_ = new ClockCacheShard[num_shards];
+ for (int i = 0; i < num_shards; i++) {
+ shards_[i].set_metadata_charge_policy(metadata_charge_policy);
+ }
+ SetCapacity(capacity);
+ SetStrictCapacityLimit(strict_capacity_limit);
+ }
+
+ ~ClockCache() override { delete[] shards_; }
+
+ const char* Name() const override { return "ClockCache"; }
+
+ CacheShard* GetShard(int shard) override {
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
+ }
+
+ const CacheShard* GetShard(int shard) const override {
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
+ }
+
+ void* Value(Handle* handle) override {
+ return reinterpret_cast<const CacheHandle*>(handle)->value;
+ }
+
+ size_t GetCharge(Handle* handle) const override {
+ return reinterpret_cast<const CacheHandle*>(handle)->charge;
+ }
+
+ uint32_t GetHash(Handle* handle) const override {
+ return reinterpret_cast<const CacheHandle*>(handle)->hash;
+ }
+
+ void DisownData() override { shards_ = nullptr; }
+
+ private:
+ ClockCacheShard* shards_;
+};
+
+} // end anonymous namespace
+
+std::shared_ptr<Cache> NewClockCache(
+ size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ if (num_shard_bits < 0) {
+ num_shard_bits = GetDefaultCacheShardBits(capacity);
+ }
+ return std::make_shared<ClockCache>(
+ capacity, num_shard_bits, strict_capacity_limit, metadata_charge_policy);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // SUPPORT_CLOCK_CACHE
diff --git a/storage/rocksdb/rocksdb/cache/clock_cache.h b/storage/rocksdb/rocksdb/cache/clock_cache.h
new file mode 100644
index 00000000..1614c0ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/clock_cache.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/cache.h"
+
+#if defined(TBB) && !defined(ROCKSDB_LITE)
+#define SUPPORT_CLOCK_CACHE
+#endif
diff --git a/storage/rocksdb/rocksdb/cache/lru_cache.cc b/storage/rocksdb/rocksdb/cache/lru_cache.cc
new file mode 100644
index 00000000..98741780
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/lru_cache.cc
@@ -0,0 +1,574 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/lru_cache.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+LRUHandleTable::LRUHandleTable() : list_(nullptr), length_(0), elems_(0) {
+ Resize();
+}
+
+LRUHandleTable::~LRUHandleTable() {
+ ApplyToAllCacheEntries([](LRUHandle* h) {
+ if (!h->HasRefs()) {
+ h->Free();
+ }
+ });
+ delete[] list_;
+}
+
+LRUHandle* LRUHandleTable::Lookup(const Slice& key, uint32_t hash) {
+ return *FindPointer(key, hash);
+}
+
+LRUHandle* LRUHandleTable::Insert(LRUHandle* h) {
+ LRUHandle** ptr = FindPointer(h->key(), h->hash);
+ LRUHandle* old = *ptr;
+ h->next_hash = (old == nullptr ? nullptr : old->next_hash);
+ *ptr = h;
+ if (old == nullptr) {
+ ++elems_;
+ if (elems_ > length_) {
+ // Since each cache entry is fairly large, we aim for a small
+ // average linked list length (<= 1).
+ Resize();
+ }
+ }
+ return old;
+}
+
+LRUHandle* LRUHandleTable::Remove(const Slice& key, uint32_t hash) {
+ LRUHandle** ptr = FindPointer(key, hash);
+ LRUHandle* result = *ptr;
+ if (result != nullptr) {
+ *ptr = result->next_hash;
+ --elems_;
+ }
+ return result;
+}
+
+LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
+ LRUHandle** ptr = &list_[hash & (length_ - 1)];
+ while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
+ ptr = &(*ptr)->next_hash;
+ }
+ return ptr;
+}
+
+void LRUHandleTable::Resize() {
+ uint32_t new_length = 16;
+ while (new_length < elems_ * 1.5) {
+ new_length *= 2;
+ }
+ LRUHandle** new_list = new LRUHandle*[new_length];
+ memset(new_list, 0, sizeof(new_list[0]) * new_length);
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < length_; i++) {
+ LRUHandle* h = list_[i];
+ while (h != nullptr) {
+ LRUHandle* next = h->next_hash;
+ uint32_t hash = h->hash;
+ LRUHandle** ptr = &new_list[hash & (new_length - 1)];
+ h->next_hash = *ptr;
+ *ptr = h;
+ h = next;
+ count++;
+ }
+ }
+ assert(elems_ == count);
+ delete[] list_;
+ list_ = new_list;
+ length_ = new_length;
+}
+
+LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
+ double high_pri_pool_ratio,
+ bool use_adaptive_mutex,
+ CacheMetadataChargePolicy metadata_charge_policy)
+ : capacity_(0),
+ high_pri_pool_usage_(0),
+ strict_capacity_limit_(strict_capacity_limit),
+ high_pri_pool_ratio_(high_pri_pool_ratio),
+ high_pri_pool_capacity_(0),
+ usage_(0),
+ lru_usage_(0),
+ mutex_(use_adaptive_mutex) {
+ set_metadata_charge_policy(metadata_charge_policy);
+ // Make empty circular linked list
+ lru_.next = &lru_;
+ lru_.prev = &lru_;
+ lru_low_pri_ = &lru_;
+ SetCapacity(capacity);
+}
+
+void LRUCacheShard::EraseUnRefEntries() {
+ autovector<LRUHandle*> last_reference_list;
+ {
+ MutexLock l(&mutex_);
+ while (lru_.next != &lru_) {
+ LRUHandle* old = lru_.next;
+ // LRU list contains only elements which can be evicted
+ assert(old->InCache() && !old->HasRefs());
+ LRU_Remove(old);
+ table_.Remove(old->key(), old->hash);
+ old->SetInCache(false);
+ size_t total_charge = old->CalcTotalCharge(metadata_charge_policy_);
+ assert(usage_ >= total_charge);
+ usage_ -= total_charge;
+ last_reference_list.push_back(old);
+ }
+ }
+
+ for (auto entry : last_reference_list) {
+ entry->Free();
+ }
+}
+
+void LRUCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) {
+ const auto applyCallback = [&]() {
+ table_.ApplyToAllCacheEntries(
+ [callback](LRUHandle* h) { callback(h->value, h->charge); });
+ };
+
+ if (thread_safe) {
+ MutexLock l(&mutex_);
+ applyCallback();
+ } else {
+ applyCallback();
+ }
+}
+
+void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
+ MutexLock l(&mutex_);
+ *lru = &lru_;
+ *lru_low_pri = lru_low_pri_;
+}
+
+size_t LRUCacheShard::TEST_GetLRUSize() {
+ MutexLock l(&mutex_);
+ LRUHandle* lru_handle = lru_.next;
+ size_t lru_size = 0;
+ while (lru_handle != &lru_) {
+ lru_size++;
+ lru_handle = lru_handle->next;
+ }
+ return lru_size;
+}
+
+double LRUCacheShard::GetHighPriPoolRatio() {
+ MutexLock l(&mutex_);
+ return high_pri_pool_ratio_;
+}
+
+void LRUCacheShard::LRU_Remove(LRUHandle* e) {
+ assert(e->next != nullptr);
+ assert(e->prev != nullptr);
+ if (lru_low_pri_ == e) {
+ lru_low_pri_ = e->prev;
+ }
+ e->next->prev = e->prev;
+ e->prev->next = e->next;
+ e->prev = e->next = nullptr;
+ size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
+ assert(lru_usage_ >= total_charge);
+ lru_usage_ -= total_charge;
+ if (e->InHighPriPool()) {
+ assert(high_pri_pool_usage_ >= total_charge);
+ high_pri_pool_usage_ -= total_charge;
+ }
+}
+
+void LRUCacheShard::LRU_Insert(LRUHandle* e) {
+ assert(e->next == nullptr);
+ assert(e->prev == nullptr);
+ size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
+ if (high_pri_pool_ratio_ > 0 && (e->IsHighPri() || e->HasHit())) {
+ // Inset "e" to head of LRU list.
+ e->next = &lru_;
+ e->prev = lru_.prev;
+ e->prev->next = e;
+ e->next->prev = e;
+ e->SetInHighPriPool(true);
+ high_pri_pool_usage_ += total_charge;
+ MaintainPoolSize();
+ } else {
+ // Insert "e" to the head of low-pri pool. Note that when
+ // high_pri_pool_ratio is 0, head of low-pri pool is also head of LRU list.
+ e->next = lru_low_pri_->next;
+ e->prev = lru_low_pri_;
+ e->prev->next = e;
+ e->next->prev = e;
+ e->SetInHighPriPool(false);
+ lru_low_pri_ = e;
+ }
+ lru_usage_ += total_charge;
+}
+
+void LRUCacheShard::MaintainPoolSize() {
+ while (high_pri_pool_usage_ > high_pri_pool_capacity_) {
+ // Overflow last entry in high-pri pool to low-pri pool.
+ lru_low_pri_ = lru_low_pri_->next;
+ assert(lru_low_pri_ != &lru_);
+ lru_low_pri_->SetInHighPriPool(false);
+ size_t total_charge =
+ lru_low_pri_->CalcTotalCharge(metadata_charge_policy_);
+ assert(high_pri_pool_usage_ >= total_charge);
+ high_pri_pool_usage_ -= total_charge;
+ }
+}
+
+void LRUCacheShard::EvictFromLRU(size_t charge,
+ autovector<LRUHandle*>* deleted) {
+ while ((usage_ + charge) > capacity_ && lru_.next != &lru_) {
+ LRUHandle* old = lru_.next;
+ // LRU list contains only elements which can be evicted
+ assert(old->InCache() && !old->HasRefs());
+ LRU_Remove(old);
+ table_.Remove(old->key(), old->hash);
+ old->SetInCache(false);
+ size_t old_total_charge = old->CalcTotalCharge(metadata_charge_policy_);
+ assert(usage_ >= old_total_charge);
+ usage_ -= old_total_charge;
+ deleted->push_back(old);
+ }
+}
+
+void LRUCacheShard::SetCapacity(size_t capacity) {
+ autovector<LRUHandle*> last_reference_list;
+ {
+ MutexLock l(&mutex_);
+ capacity_ = capacity;
+ high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
+ EvictFromLRU(0, &last_reference_list);
+ }
+
+ // Free the entries outside of mutex for performance reasons
+ for (auto entry : last_reference_list) {
+ entry->Free();
+ }
+}
+
+void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
+ MutexLock l(&mutex_);
+ strict_capacity_limit_ = strict_capacity_limit;
+}
+
+Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
+ MutexLock l(&mutex_);
+ LRUHandle* e = table_.Lookup(key, hash);
+ if (e != nullptr) {
+ assert(e->InCache());
+ if (!e->HasRefs()) {
+ // The entry is in LRU since it's in hash and has no external references
+ LRU_Remove(e);
+ }
+ e->Ref();
+ e->SetHit();
+ }
+ return reinterpret_cast<Cache::Handle*>(e);
+}
+
+bool LRUCacheShard::Ref(Cache::Handle* h) {
+ LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
+ MutexLock l(&mutex_);
+ // To create another reference - entry must be already externally referenced
+ assert(e->HasRefs());
+ e->Ref();
+ return true;
+}
+
+void LRUCacheShard::SetHighPriorityPoolRatio(double high_pri_pool_ratio) {
+ MutexLock l(&mutex_);
+ high_pri_pool_ratio_ = high_pri_pool_ratio;
+ high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
+ MaintainPoolSize();
+}
+
+bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
+ if (handle == nullptr) {
+ return false;
+ }
+ LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
+ bool last_reference = false;
+ {
+ MutexLock l(&mutex_);
+ last_reference = e->Unref();
+ if (last_reference && e->InCache()) {
+ // The item is still in cache, and nobody else holds a reference to it
+ if (usage_ > capacity_ || force_erase) {
+ // The LRU list must be empty since the cache is full
+ assert(lru_.next == &lru_ || force_erase);
+ // Take this opportunity and remove the item
+ table_.Remove(e->key(), e->hash);
+ e->SetInCache(false);
+ } else {
+ // Put the item back on the LRU list, and don't free it
+ LRU_Insert(e);
+ last_reference = false;
+ }
+ }
+ if (last_reference) {
+ size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
+ assert(usage_ >= total_charge);
+ usage_ -= total_charge;
+ }
+ }
+
+ // Free the entry here outside of mutex for performance reasons
+ if (last_reference) {
+ e->Free();
+ }
+ return last_reference;
+}
+
+Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
+ size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Cache::Handle** handle, Cache::Priority priority) {
+ // Allocate the memory here outside of the mutex
+ // If the cache is full, we'll have to release it
+ // It shouldn't happen very often though.
+ LRUHandle* e = reinterpret_cast<LRUHandle*>(
+ new char[sizeof(LRUHandle) - 1 + key.size()]);
+ Status s = Status::OK();
+ autovector<LRUHandle*> last_reference_list;
+
+ e->value = value;
+ e->deleter = deleter;
+ e->charge = charge;
+ e->key_length = key.size();
+ e->flags = 0;
+ e->hash = hash;
+ e->refs = 0;
+ e->next = e->prev = nullptr;
+ e->SetInCache(true);
+ e->SetPriority(priority);
+ memcpy(e->key_data, key.data(), key.size());
+ size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
+
+ {
+ MutexLock l(&mutex_);
+
+ // Free the space following strict LRU policy until enough space
+ // is freed or the lru list is empty
+ EvictFromLRU(total_charge, &last_reference_list);
+
+ if ((usage_ + total_charge) > capacity_ &&
+ (strict_capacity_limit_ || handle == nullptr)) {
+ if (handle == nullptr) {
+ // Don't insert the entry but still return ok, as if the entry inserted
+ // into cache and get evicted immediately.
+ e->SetInCache(false);
+ last_reference_list.push_back(e);
+ } else {
+ delete[] reinterpret_cast<char*>(e);
+ *handle = nullptr;
+ s = Status::Incomplete("Insert failed due to LRU cache being full.");
+ }
+ } else {
+ // Insert into the cache. Note that the cache might get larger than its
+ // capacity if not enough space was freed up.
+ LRUHandle* old = table_.Insert(e);
+ usage_ += total_charge;
+ if (old != nullptr) {
+ assert(old->InCache());
+ old->SetInCache(false);
+ if (!old->HasRefs()) {
+ // old is on LRU because it's in cache and its reference count is 0
+ LRU_Remove(old);
+ size_t old_total_charge =
+ old->CalcTotalCharge(metadata_charge_policy_);
+ assert(usage_ >= old_total_charge);
+ usage_ -= old_total_charge;
+ last_reference_list.push_back(old);
+ }
+ }
+ if (handle == nullptr) {
+ LRU_Insert(e);
+ } else {
+ e->Ref();
+ *handle = reinterpret_cast<Cache::Handle*>(e);
+ }
+ }
+ }
+
+ // Free the entries here outside of mutex for performance reasons
+ for (auto entry : last_reference_list) {
+ entry->Free();
+ }
+
+ return s;
+}
+
+void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
+ LRUHandle* e;
+ bool last_reference = false;
+ {
+ MutexLock l(&mutex_);
+ e = table_.Remove(key, hash);
+ if (e != nullptr) {
+ assert(e->InCache());
+ e->SetInCache(false);
+ if (!e->HasRefs()) {
+ // The entry is in LRU since it's in hash and has no external references
+ LRU_Remove(e);
+ size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
+ assert(usage_ >= total_charge);
+ usage_ -= total_charge;
+ last_reference = true;
+ }
+ }
+ }
+
+ // Free the entry here outside of mutex for performance reasons
+ // last_reference will only be true if e != nullptr
+ if (last_reference) {
+ e->Free();
+ }
+}
+
+size_t LRUCacheShard::GetUsage() const {
+ MutexLock l(&mutex_);
+ return usage_;
+}
+
+size_t LRUCacheShard::GetPinnedUsage() const {
+ MutexLock l(&mutex_);
+ assert(usage_ >= lru_usage_);
+ return usage_ - lru_usage_;
+}
+
+std::string LRUCacheShard::GetPrintableOptions() const {
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+ {
+ MutexLock l(&mutex_);
+ snprintf(buffer, kBufferSize, " high_pri_pool_ratio: %.3lf\n",
+ high_pri_pool_ratio_);
+ }
+ return std::string(buffer);
+}
+
+LRUCache::LRUCache(size_t capacity, int num_shard_bits,
+ bool strict_capacity_limit, double high_pri_pool_ratio,
+ std::shared_ptr<MemoryAllocator> allocator,
+ bool use_adaptive_mutex,
+ CacheMetadataChargePolicy metadata_charge_policy)
+ : ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
+ std::move(allocator)) {
+ num_shards_ = 1 << num_shard_bits;
+ shards_ = reinterpret_cast<LRUCacheShard*>(
+ port::cacheline_aligned_alloc(sizeof(LRUCacheShard) * num_shards_));
+ size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
+ for (int i = 0; i < num_shards_; i++) {
+ new (&shards_[i])
+ LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
+ use_adaptive_mutex, metadata_charge_policy);
+ }
+}
+
+LRUCache::~LRUCache() {
+ if (shards_ != nullptr) {
+ assert(num_shards_ > 0);
+ for (int i = 0; i < num_shards_; i++) {
+ shards_[i].~LRUCacheShard();
+ }
+ port::cacheline_aligned_free(shards_);
+ }
+}
+
+CacheShard* LRUCache::GetShard(int shard) {
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
+}
+
+const CacheShard* LRUCache::GetShard(int shard) const {
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
+}
+
+void* LRUCache::Value(Handle* handle) {
+ return reinterpret_cast<const LRUHandle*>(handle)->value;
+}
+
+size_t LRUCache::GetCharge(Handle* handle) const {
+ return reinterpret_cast<const LRUHandle*>(handle)->charge;
+}
+
+uint32_t LRUCache::GetHash(Handle* handle) const {
+ return reinterpret_cast<const LRUHandle*>(handle)->hash;
+}
+
+void LRUCache::DisownData() {
+// Do not drop data if compile with ASAN to suppress leak warning.
+#if defined(__clang__)
+#if !defined(__has_feature) || !__has_feature(address_sanitizer)
+ shards_ = nullptr;
+ num_shards_ = 0;
+#endif
+#else // __clang__
+#ifndef __SANITIZE_ADDRESS__
+ shards_ = nullptr;
+ num_shards_ = 0;
+#endif // !__SANITIZE_ADDRESS__
+#endif // __clang__
+}
+
+size_t LRUCache::TEST_GetLRUSize() {
+ size_t lru_size_of_all_shards = 0;
+ for (int i = 0; i < num_shards_; i++) {
+ lru_size_of_all_shards += shards_[i].TEST_GetLRUSize();
+ }
+ return lru_size_of_all_shards;
+}
+
+double LRUCache::GetHighPriPoolRatio() {
+ double result = 0.0;
+ if (num_shards_ > 0) {
+ result = shards_[0].GetHighPriPoolRatio();
+ }
+ return result;
+}
+
+std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
+ return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
+ cache_opts.strict_capacity_limit,
+ cache_opts.high_pri_pool_ratio,
+ cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
+ cache_opts.metadata_charge_policy);
+}
+
+std::shared_ptr<Cache> NewLRUCache(
+ size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ double high_pri_pool_ratio,
+ std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ if (num_shard_bits >= 20) {
+ return nullptr; // the cache cannot be sharded into too many fine pieces
+ }
+ if (high_pri_pool_ratio < 0.0 || high_pri_pool_ratio > 1.0) {
+ // invalid high_pri_pool_ratio
+ return nullptr;
+ }
+ if (num_shard_bits < 0) {
+ num_shard_bits = GetDefaultCacheShardBits(capacity);
+ }
+ return std::make_shared<LRUCache>(
+ capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
+ std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/cache/lru_cache.h b/storage/rocksdb/rocksdb/cache/lru_cache.h
new file mode 100644
index 00000000..827e0bec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/lru_cache.h
@@ -0,0 +1,339 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "cache/sharded_cache.h"
+
+#include "port/malloc.h"
+#include "port/port.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// LRU cache implementation. This class is not thread-safe.
+
+// An entry is a variable length heap-allocated structure.
+// Entries are referenced by cache and/or by any external entity.
+// The cache keeps all its entries in a hash table. Some elements
+// are also stored on LRU list.
+//
+// LRUHandle can be in these states:
+// 1. Referenced externally AND in hash table.
+// In that case the entry is *not* in the LRU list
+// (refs >= 1 && in_cache == true)
+// 2. Not referenced externally AND in hash table.
+// In that case the entry is in the LRU list and can be freed.
+// (refs == 0 && in_cache == true)
+// 3. Referenced externally AND not in hash table.
+// In that case the entry is not in the LRU list and not in hash table.
+// The entry can be freed when refs becomes 0.
+// (refs >= 1 && in_cache == false)
+//
+// All newly created LRUHandles are in state 1. If you call
+// LRUCacheShard::Release on entry in state 1, it will go into state 2.
+// To move from state 1 to state 3, either call LRUCacheShard::Erase or
+// LRUCacheShard::Insert with the same key (but possibly different value).
+// To move from state 2 to state 1, use LRUCacheShard::Lookup.
+// Before destruction, make sure that no handles are in state 1. This means
+// that any successful LRUCacheShard::Lookup/LRUCacheShard::Insert have a
+// matching LRUCache::Release (to move into state 2) or LRUCacheShard::Erase
+// (to move into state 3).
+
+struct LRUHandle {
+ void* value;
+ void (*deleter)(const Slice&, void* value);
+ LRUHandle* next_hash;
+ LRUHandle* next;
+ LRUHandle* prev;
+ size_t charge; // TODO(opt): Only allow uint32_t?
+ size_t key_length;
+ // The hash of key(). Used for fast sharding and comparisons.
+ uint32_t hash;
+ // The number of external refs to this entry. The cache itself is not counted.
+ uint32_t refs;
+
+ enum Flags : uint8_t {
+ // Whether this entry is referenced by the hash table.
+ IN_CACHE = (1 << 0),
+ // Whether this entry is high priority entry.
+ IS_HIGH_PRI = (1 << 1),
+ // Whether this entry is in high-pri pool.
+ IN_HIGH_PRI_POOL = (1 << 2),
+ // Wwhether this entry has had any lookups (hits).
+ HAS_HIT = (1 << 3),
+ };
+
+ uint8_t flags;
+
+ // Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
+ char key_data[1];
+
+ Slice key() const { return Slice(key_data, key_length); }
+
+ // Increase the reference count by 1.
+ void Ref() { refs++; }
+
+ // Just reduce the reference count by 1. Return true if it was last reference.
+ bool Unref() {
+ assert(refs > 0);
+ refs--;
+ return refs == 0;
+ }
+
+ // Return true if there are external refs, false otherwise.
+ bool HasRefs() const { return refs > 0; }
+
+ bool InCache() const { return flags & IN_CACHE; }
+ bool IsHighPri() const { return flags & IS_HIGH_PRI; }
+ bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
+ bool HasHit() const { return flags & HAS_HIT; }
+
+ void SetInCache(bool in_cache) {
+ if (in_cache) {
+ flags |= IN_CACHE;
+ } else {
+ flags &= ~IN_CACHE;
+ }
+ }
+
+ void SetPriority(Cache::Priority priority) {
+ if (priority == Cache::Priority::HIGH) {
+ flags |= IS_HIGH_PRI;
+ } else {
+ flags &= ~IS_HIGH_PRI;
+ }
+ }
+
+ void SetInHighPriPool(bool in_high_pri_pool) {
+ if (in_high_pri_pool) {
+ flags |= IN_HIGH_PRI_POOL;
+ } else {
+ flags &= ~IN_HIGH_PRI_POOL;
+ }
+ }
+
+ void SetHit() { flags |= HAS_HIT; }
+
+ void Free() {
+ assert(refs == 0);
+ if (deleter) {
+ (*deleter)(key(), value);
+ }
+ delete[] reinterpret_cast<char*>(this);
+ }
+
+ // Caclculate the memory usage by metadata
+ inline size_t CalcTotalCharge(
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ size_t meta_charge = 0;
+ if (metadata_charge_policy == kFullChargeCacheMetadata) {
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ meta_charge += malloc_usable_size(static_cast<void*>(this));
+#else
+ // This is the size that is used when a new handle is created
+ meta_charge += sizeof(LRUHandle) - 1 + key_length;
+#endif
+ }
+ return charge + meta_charge;
+ }
+};
+
+// We provide our own simple hash table since it removes a whole bunch
+// of porting hacks and is also faster than some of the built-in hash
+// table implementations in some of the compiler/runtime combinations
+// we have tested. E.g., readrandom speeds up by ~5% over the g++
+// 4.4.3's builtin hashtable.
+class LRUHandleTable {
+ public:
+ LRUHandleTable();
+ ~LRUHandleTable();
+
+ LRUHandle* Lookup(const Slice& key, uint32_t hash);
+ LRUHandle* Insert(LRUHandle* h);
+ LRUHandle* Remove(const Slice& key, uint32_t hash);
+
+ template <typename T>
+ void ApplyToAllCacheEntries(T func) {
+ for (uint32_t i = 0; i < length_; i++) {
+ LRUHandle* h = list_[i];
+ while (h != nullptr) {
+ auto n = h->next_hash;
+ assert(h->InCache());
+ func(h);
+ h = n;
+ }
+ }
+ }
+
+ private:
+ // Return a pointer to slot that points to a cache entry that
+ // matches key/hash. If there is no such cache entry, return a
+ // pointer to the trailing slot in the corresponding linked list.
+ LRUHandle** FindPointer(const Slice& key, uint32_t hash);
+
+ void Resize();
+
+ // The table consists of an array of buckets where each bucket is
+ // a linked list of cache entries that hash into the bucket.
+ LRUHandle** list_;
+ uint32_t length_;
+ uint32_t elems_;
+};
+
+// A single shard of sharded cache.
+class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
+ public:
+ LRUCacheShard(size_t capacity, bool strict_capacity_limit,
+ double high_pri_pool_ratio, bool use_adaptive_mutex,
+ CacheMetadataChargePolicy metadata_charge_policy);
+ virtual ~LRUCacheShard() override = default;
+
+ // Separate from constructor so caller can easily make an array of LRUCache
+ // if current usage is more than new capacity, the function will attempt to
+ // free the needed space
+ virtual void SetCapacity(size_t capacity) override;
+
+ // Set the flag to reject insertion if cache if full.
+ virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override;
+
+ // Set percentage of capacity reserved for high-pri cache entries.
+ void SetHighPriorityPoolRatio(double high_pri_pool_ratio);
+
+ // Like Cache methods, but with an extra "hash" parameter.
+ virtual Status Insert(const Slice& key, uint32_t hash, void* value,
+ size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Cache::Handle** handle,
+ Cache::Priority priority) override;
+ virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
+ virtual bool Ref(Cache::Handle* handle) override;
+ virtual bool Release(Cache::Handle* handle,
+ bool force_erase = false) override;
+ virtual void Erase(const Slice& key, uint32_t hash) override;
+
+ // Although in some platforms the update of size_t is atomic, to make sure
+ // GetUsage() and GetPinnedUsage() work correctly under any platform, we'll
+ // protect them with mutex_.
+
+ virtual size_t GetUsage() const override;
+ virtual size_t GetPinnedUsage() const override;
+
+ virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) override;
+
+ virtual void EraseUnRefEntries() override;
+
+ virtual std::string GetPrintableOptions() const override;
+
+ void TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri);
+
+ // Retrieves number of elements in LRU, for unit test purpose only
+ // not threadsafe
+ size_t TEST_GetLRUSize();
+
+ // Retrives high pri pool ratio
+ double GetHighPriPoolRatio();
+
+ private:
+ void LRU_Remove(LRUHandle* e);
+ void LRU_Insert(LRUHandle* e);
+
+ // Overflow the last entry in high-pri pool to low-pri pool until size of
+ // high-pri pool is no larger than the size specify by high_pri_pool_pct.
+ void MaintainPoolSize();
+
+ // Free some space following strict LRU policy until enough space
+ // to hold (usage_ + charge) is freed or the lru list is empty
+ // This function is not thread safe - it needs to be executed while
+ // holding the mutex_
+ void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
+
+ // Initialized before use.
+ size_t capacity_;
+
+ // Memory size for entries in high-pri pool.
+ size_t high_pri_pool_usage_;
+
+ // Whether to reject insertion if cache reaches its full capacity.
+ bool strict_capacity_limit_;
+
+ // Ratio of capacity reserved for high priority cache entries.
+ double high_pri_pool_ratio_;
+
+ // High-pri pool size, equals to capacity * high_pri_pool_ratio.
+ // Remember the value to avoid recomputing each time.
+ double high_pri_pool_capacity_;
+
+ // Dummy head of LRU list.
+ // lru.prev is newest entry, lru.next is oldest entry.
+ // LRU contains items which can be evicted, ie reference only by cache
+ LRUHandle lru_;
+
+ // Pointer to head of low-pri pool in LRU list.
+ LRUHandle* lru_low_pri_;
+
+ // ------------^^^^^^^^^^^^^-----------
+ // Not frequently modified data members
+ // ------------------------------------
+ //
+ // We separate data members that are updated frequently from the ones that
+ // are not frequently updated so that they don't share the same cache line
+ // which will lead into false cache sharing
+ //
+ // ------------------------------------
+ // Frequently modified data members
+ // ------------vvvvvvvvvvvvv-----------
+ LRUHandleTable table_;
+
+ // Memory size for entries residing in the cache
+ size_t usage_;
+
+ // Memory size for entries residing only in the LRU list
+ size_t lru_usage_;
+
+ // mutex_ protects the following state.
+ // We don't count mutex_ as the cache's internal state so semantically we
+ // don't mind mutex_ invoking the non-const actions.
+ mutable port::Mutex mutex_;
+};
+
+class LRUCache
+#ifdef NDEBUG
+ final
+#endif
+ : public ShardedCache {
+ public:
+ LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ double high_pri_pool_ratio,
+ std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
+ bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
+ CacheMetadataChargePolicy metadata_charge_policy =
+ kDontChargeCacheMetadata);
+ virtual ~LRUCache();
+ virtual const char* Name() const override { return "LRUCache"; }
+ virtual CacheShard* GetShard(int shard) override;
+ virtual const CacheShard* GetShard(int shard) const override;
+ virtual void* Value(Handle* handle) override;
+ virtual size_t GetCharge(Handle* handle) const override;
+ virtual uint32_t GetHash(Handle* handle) const override;
+ virtual void DisownData() override;
+
+ // Retrieves number of elements in LRU, for unit test purpose only
+ size_t TEST_GetLRUSize();
+ // Retrives high pri pool ratio
+ double GetHighPriPoolRatio();
+
+ private:
+ LRUCacheShard* shards_ = nullptr;
+ int num_shards_ = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/cache/lru_cache_test.cc b/storage/rocksdb/rocksdb/cache/lru_cache_test.cc
new file mode 100644
index 00000000..f777711f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/lru_cache_test.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+#include "port/port.h"
+#include "test_util/testharness.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,
+ bool use_adaptive_mutex = kDefaultToAdaptiveMutex) {
+ DeleteCache();
+ cache_ = reinterpret_cast<LRUCacheShard*>(
+ port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
+ new (cache_) LRUCacheShard(capacity, false /*strict_capcity_limit*/,
+ high_pri_pool_ratio, use_adaptive_mutex,
+ kDontChargeCacheMetadata);
+ }
+
+ void Insert(const std::string& key,
+ Cache::Priority priority = Cache::Priority::LOW) {
+ cache_->Insert(key, 0 /*hash*/, nullptr /*value*/, 1 /*charge*/,
+ nullptr /*deleter*/, 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*/);
+ if (handle) {
+ cache_->Release(handle);
+ 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<std::string> keys,
+ size_t num_high_pri_pool_keys = 0) {
+ LRUHandle* lru;
+ LRUHandle* lru_low_pri;
+ cache_->TEST_GetLRUList(&lru, &lru_low_pri);
+ LRUHandle* iter = lru;
+ bool in_high_pri_pool = false;
+ size_t high_pri_pool_keys = 0;
+ if (iter == lru_low_pri) {
+ 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());
+ if (in_high_pri_pool) {
+ high_pri_pool_keys++;
+ }
+ if (iter == lru_low_pri) {
+ ASSERT_FALSE(in_high_pri_pool);
+ in_high_pri_pool = true;
+ }
+ }
+ ASSERT_EQ(lru, iter->next);
+ ASSERT_TRUE(in_high_pri_pool);
+ ASSERT_EQ(num_high_pri_pool_keys, high_pri_pool_keys);
+ }
+
+ private:
+ LRUCacheShard* cache_ = nullptr;
+};
+
+TEST_F(LRUCacheTest, BasicLRU) {
+ NewCache(5);
+ for (char ch = 'a'; ch <= 'e'; ch++) {
+ Insert(ch);
+ }
+ ValidateLRUList({"a", "b", "c", "d", "e"});
+ for (char ch = 'x'; ch <= 'z'; ch++) {
+ Insert(ch);
+ }
+ ValidateLRUList({"d", "e", "x", "y", "z"});
+ ASSERT_FALSE(Lookup("b"));
+ ValidateLRUList({"d", "e", "x", "y", "z"});
+ ASSERT_TRUE(Lookup("e"));
+ ValidateLRUList({"d", "x", "y", "z", "e"});
+ ASSERT_TRUE(Lookup("z"));
+ ValidateLRUList({"d", "x", "y", "e", "z"});
+ Erase("x");
+ ValidateLRUList({"d", "y", "e", "z"});
+ ASSERT_TRUE(Lookup("d"));
+ ValidateLRUList({"y", "e", "z", "d"});
+ Insert("u");
+ ValidateLRUList({"y", "e", "z", "d", "u"});
+ Insert("v");
+ ValidateLRUList({"e", "z", "d", "u", "v"});
+}
+
+TEST_F(LRUCacheTest, MidpointInsertion) {
+ // Allocate 2 cache entries to high-pri pool.
+ NewCache(5, 0.45);
+
+ 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);
+
+ // 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);
+ ASSERT_TRUE(Lookup("d"));
+ ValidateLRUList({"b", "c", "x", "y", "d"}, 2);
+
+ // High-pri entries will be inserted to the tail of full list.
+ Insert("z", Cache::Priority::HIGH);
+ ValidateLRUList({"c", "x", "y", "d", "z"}, 2);
+}
+
+TEST_F(LRUCacheTest, EntriesWithPriority) {
+ // Allocate 2 cache entries to high-pri pool.
+ NewCache(5, 0.45);
+
+ Insert("a", Cache::Priority::LOW);
+ Insert("b", Cache::Priority::LOW);
+ Insert("c", Cache::Priority::LOW);
+ ValidateLRUList({"a", "b", "c"}, 0);
+
+ // Low-pri entries can take high-pri pool capacity if available
+ Insert("u", Cache::Priority::LOW);
+ Insert("v", Cache::Priority::LOW);
+ ValidateLRUList({"a", "b", "c", "u", "v"}, 0);
+
+ Insert("X", Cache::Priority::HIGH);
+ Insert("Y", Cache::Priority::HIGH);
+ ValidateLRUList({"c", "u", "v", "X", "Y"}, 2);
+
+ // High-pri entries can overflow to low-pri pool.
+ Insert("Z", Cache::Priority::HIGH);
+ ValidateLRUList({"u", "v", "X", "Y", "Z"}, 2);
+
+ // Low-pri entries will be inserted to head of low-pri pool.
+ Insert("a", Cache::Priority::LOW);
+ ValidateLRUList({"v", "X", "a", "Y", "Z"}, 2);
+
+ // Low-pri entries will be inserted to head of high-pri pool after lookup.
+ ASSERT_TRUE(Lookup("v"));
+ ValidateLRUList({"X", "a", "Y", "Z", "v"}, 2);
+
+ // High-pri entries will be inserted to the head of the list after lookup.
+ ASSERT_TRUE(Lookup("X"));
+ ValidateLRUList({"a", "Y", "Z", "v", "X"}, 2);
+ ASSERT_TRUE(Lookup("Z"));
+ ValidateLRUList({"a", "Y", "v", "X", "Z"}, 2);
+
+ Erase("Y");
+ ValidateLRUList({"a", "v", "X", "Z"}, 2);
+ Erase("X");
+ ValidateLRUList({"a", "v", "Z"}, 1);
+ Insert("d", Cache::Priority::LOW);
+ Insert("e", Cache::Priority::LOW);
+ ValidateLRUList({"a", "v", "d", "e", "Z"}, 1);
+ Insert("f", Cache::Priority::LOW);
+ Insert("g", Cache::Priority::LOW);
+ ValidateLRUList({"d", "e", "f", "g", "Z"}, 1);
+ ASSERT_TRUE(Lookup("d"));
+ ValidateLRUList({"e", "f", "g", "Z", "d"}, 2);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/cache/sharded_cache.cc b/storage/rocksdb/rocksdb/cache/sharded_cache.cc
new file mode 100644
index 00000000..6c915df8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/sharded_cache.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sharded_cache.h"
+
+#include <string>
+
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ShardedCache::ShardedCache(size_t capacity, int num_shard_bits,
+ bool strict_capacity_limit,
+ std::shared_ptr<MemoryAllocator> allocator)
+ : Cache(std::move(allocator)),
+ num_shard_bits_(num_shard_bits),
+ capacity_(capacity),
+ strict_capacity_limit_(strict_capacity_limit),
+ last_id_(1) {}
+
+void ShardedCache::SetCapacity(size_t capacity) {
+ int num_shards = 1 << num_shard_bits_;
+ const size_t per_shard = (capacity + (num_shards - 1)) / num_shards;
+ MutexLock l(&capacity_mutex_);
+ for (int s = 0; s < num_shards; s++) {
+ GetShard(s)->SetCapacity(per_shard);
+ }
+ capacity_ = capacity;
+}
+
+void ShardedCache::SetStrictCapacityLimit(bool strict_capacity_limit) {
+ int num_shards = 1 << num_shard_bits_;
+ MutexLock l(&capacity_mutex_);
+ for (int s = 0; s < num_shards; s++) {
+ GetShard(s)->SetStrictCapacityLimit(strict_capacity_limit);
+ }
+ strict_capacity_limit_ = strict_capacity_limit;
+}
+
+Status ShardedCache::Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Handle** handle, Priority priority) {
+ uint32_t hash = HashSlice(key);
+ return GetShard(Shard(hash))
+ ->Insert(key, hash, value, charge, deleter, handle, priority);
+}
+
+Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) {
+ uint32_t hash = HashSlice(key);
+ return GetShard(Shard(hash))->Lookup(key, hash);
+}
+
+bool ShardedCache::Ref(Handle* handle) {
+ uint32_t hash = GetHash(handle);
+ return GetShard(Shard(hash))->Ref(handle);
+}
+
+bool ShardedCache::Release(Handle* handle, bool force_erase) {
+ uint32_t hash = GetHash(handle);
+ return GetShard(Shard(hash))->Release(handle, force_erase);
+}
+
+void ShardedCache::Erase(const Slice& key) {
+ uint32_t hash = HashSlice(key);
+ GetShard(Shard(hash))->Erase(key, hash);
+}
+
+uint64_t ShardedCache::NewId() {
+ return last_id_.fetch_add(1, std::memory_order_relaxed);
+}
+
+size_t ShardedCache::GetCapacity() const {
+ MutexLock l(&capacity_mutex_);
+ return capacity_;
+}
+
+bool ShardedCache::HasStrictCapacityLimit() const {
+ MutexLock l(&capacity_mutex_);
+ return strict_capacity_limit_;
+}
+
+size_t ShardedCache::GetUsage() const {
+ // We will not lock the cache when getting the usage from shards.
+ int num_shards = 1 << num_shard_bits_;
+ size_t usage = 0;
+ for (int s = 0; s < num_shards; s++) {
+ usage += GetShard(s)->GetUsage();
+ }
+ return usage;
+}
+
+size_t ShardedCache::GetUsage(Handle* handle) const {
+ return GetCharge(handle);
+}
+
+size_t ShardedCache::GetPinnedUsage() const {
+ // We will not lock the cache when getting the usage from shards.
+ int num_shards = 1 << num_shard_bits_;
+ size_t usage = 0;
+ for (int s = 0; s < num_shards; s++) {
+ usage += GetShard(s)->GetPinnedUsage();
+ }
+ return usage;
+}
+
+void ShardedCache::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) {
+ int num_shards = 1 << num_shard_bits_;
+ for (int s = 0; s < num_shards; s++) {
+ GetShard(s)->ApplyToAllCacheEntries(callback, thread_safe);
+ }
+}
+
+void ShardedCache::EraseUnRefEntries() {
+ int num_shards = 1 << num_shard_bits_;
+ for (int s = 0; s < num_shards; s++) {
+ GetShard(s)->EraseUnRefEntries();
+ }
+}
+
+std::string ShardedCache::GetPrintableOptions() const {
+ std::string ret;
+ ret.reserve(20000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+ {
+ MutexLock l(&capacity_mutex_);
+ snprintf(buffer, kBufferSize, " capacity : %" ROCKSDB_PRIszt "\n",
+ capacity_);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " num_shard_bits : %d\n", num_shard_bits_);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " strict_capacity_limit : %d\n",
+ strict_capacity_limit_);
+ ret.append(buffer);
+ }
+ snprintf(buffer, kBufferSize, " memory_allocator : %s\n",
+ memory_allocator() ? memory_allocator()->Name() : "None");
+ ret.append(buffer);
+ ret.append(GetShard(0)->GetPrintableOptions());
+ return ret;
+}
+int GetDefaultCacheShardBits(size_t capacity) {
+ int num_shard_bits = 0;
+ size_t min_shard_size = 512L * 1024L; // Every shard is at least 512KB.
+ size_t num_shards = capacity / min_shard_size;
+ while (num_shards >>= 1) {
+ if (++num_shard_bits >= 6) {
+ // No more than 6.
+ return num_shard_bits;
+ }
+ }
+ return num_shard_bits;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/cache/sharded_cache.h b/storage/rocksdb/rocksdb/cache/sharded_cache.h
new file mode 100644
index 00000000..ce9e459d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cache/sharded_cache.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <string>
+
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Single cache shard interface.
+class CacheShard {
+ public:
+ CacheShard() = default;
+ virtual ~CacheShard() = default;
+
+ virtual Status Insert(const Slice& key, uint32_t hash, void* value,
+ size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Cache::Handle** handle, Cache::Priority priority) = 0;
+ virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) = 0;
+ virtual bool Ref(Cache::Handle* handle) = 0;
+ virtual bool Release(Cache::Handle* handle, bool force_erase = false) = 0;
+ virtual void Erase(const Slice& key, uint32_t hash) = 0;
+ virtual void SetCapacity(size_t capacity) = 0;
+ virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
+ virtual size_t GetUsage() const = 0;
+ virtual size_t GetPinnedUsage() const = 0;
+ virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) = 0;
+ virtual void EraseUnRefEntries() = 0;
+ virtual std::string GetPrintableOptions() const { return ""; }
+ void set_metadata_charge_policy(
+ CacheMetadataChargePolicy metadata_charge_policy) {
+ metadata_charge_policy_ = metadata_charge_policy;
+ }
+
+ protected:
+ CacheMetadataChargePolicy metadata_charge_policy_ = kDontChargeCacheMetadata;
+};
+
+// Generic cache interface which shards cache by hash of keys. 2^num_shard_bits
+// shards will be created, with capacity split evenly to each of the shards.
+// Keys are sharded by the highest num_shard_bits bits of hash value.
+class ShardedCache : public Cache {
+ public:
+ ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
+ std::shared_ptr<MemoryAllocator> memory_allocator = nullptr);
+ virtual ~ShardedCache() = default;
+ virtual const char* Name() const override = 0;
+ virtual CacheShard* GetShard(int shard) = 0;
+ virtual const CacheShard* GetShard(int shard) const = 0;
+ virtual void* Value(Handle* handle) override = 0;
+ virtual size_t GetCharge(Handle* handle) const override = 0;
+
+ virtual uint32_t GetHash(Handle* handle) const = 0;
+ virtual void DisownData() override = 0;
+
+ virtual void SetCapacity(size_t capacity) override;
+ virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override;
+
+ virtual Status Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Handle** handle, Priority priority) override;
+ virtual Handle* Lookup(const Slice& key, Statistics* stats) override;
+ virtual bool Ref(Handle* handle) override;
+ virtual bool Release(Handle* handle, bool force_erase = false) override;
+ virtual void Erase(const Slice& key) override;
+ virtual uint64_t NewId() override;
+ virtual size_t GetCapacity() const override;
+ virtual bool HasStrictCapacityLimit() const override;
+ virtual size_t GetUsage() const override;
+ virtual size_t GetUsage(Handle* handle) const override;
+ virtual size_t GetPinnedUsage() const override;
+ virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) override;
+ virtual void EraseUnRefEntries() override;
+ virtual std::string GetPrintableOptions() const override;
+
+ int GetNumShardBits() const { return num_shard_bits_; }
+
+ private:
+ static inline uint32_t HashSlice(const Slice& s) {
+ return static_cast<uint32_t>(GetSliceNPHash64(s));
+ }
+
+ uint32_t Shard(uint32_t hash) {
+ // Note, hash >> 32 yields hash in gcc, not the zero we expect!
+ return (num_shard_bits_ > 0) ? (hash >> (32 - num_shard_bits_)) : 0;
+ }
+
+ int num_shard_bits_;
+ mutable port::Mutex capacity_mutex_;
+ size_t capacity_;
+ bool strict_capacity_limit_;
+ std::atomic<uint64_t> last_id_;
+};
+
+extern int GetDefaultCacheShardBits(size_t capacity);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/cmake/RocksDBConfig.cmake.in b/storage/rocksdb/rocksdb/cmake/RocksDBConfig.cmake.in
new file mode 100644
index 00000000..b3cb2b27
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/RocksDBConfig.cmake.in
@@ -0,0 +1,3 @@
+@PACKAGE_INIT@
+include("${CMAKE_CURRENT_LIST_DIR}/RocksDBTargets.cmake")
+check_required_components(RocksDB)
diff --git a/storage/rocksdb/rocksdb/cmake/modules/FindJeMalloc.cmake b/storage/rocksdb/rocksdb/cmake/modules/FindJeMalloc.cmake
new file mode 100644
index 00000000..f695b3ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/FindJeMalloc.cmake
@@ -0,0 +1,29 @@
+# - Find JeMalloc library
+# Find the native JeMalloc includes and library
+#
+# JeMalloc_INCLUDE_DIRS - where to find jemalloc.h, etc.
+# JeMalloc_LIBRARIES - List of libraries when using jemalloc.
+# JeMalloc_FOUND - True if jemalloc found.
+
+find_path(JeMalloc_INCLUDE_DIRS
+ NAMES jemalloc/jemalloc.h
+ HINTS ${JEMALLOC_ROOT_DIR}/include)
+
+find_library(JeMalloc_LIBRARIES
+ NAMES jemalloc
+ HINTS ${JEMALLOC_ROOT_DIR}/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(JeMalloc DEFAULT_MSG JeMalloc_LIBRARIES JeMalloc_INCLUDE_DIRS)
+
+mark_as_advanced(
+ JeMalloc_LIBRARIES
+ JeMalloc_INCLUDE_DIRS)
+
+if(JeMalloc_FOUND AND NOT (TARGET JeMalloc::JeMalloc))
+ add_library (JeMalloc::JeMalloc UNKNOWN IMPORTED)
+ set_target_properties(JeMalloc::JeMalloc
+ PROPERTIES
+ IMPORTED_LOCATION ${JeMalloc_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${JeMalloc_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/FindNUMA.cmake b/storage/rocksdb/rocksdb/cmake/modules/FindNUMA.cmake
new file mode 100644
index 00000000..69b95c9b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/FindNUMA.cmake
@@ -0,0 +1,29 @@
+# - Find NUMA
+# Find the NUMA library and includes
+#
+# NUMA_INCLUDE_DIRS - where to find numa.h, etc.
+# NUMA_LIBRARIES - List of libraries when using NUMA.
+# NUMA_FOUND - True if NUMA found.
+
+find_path(NUMA_INCLUDE_DIRS
+ NAMES numa.h numaif.h
+ HINTS ${NUMA_ROOT_DIR}/include)
+
+find_library(NUMA_LIBRARIES
+ NAMES numa
+ HINTS ${NUMA_ROOT_DIR}/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(NUMA DEFAULT_MSG NUMA_LIBRARIES NUMA_INCLUDE_DIRS)
+
+mark_as_advanced(
+ NUMA_LIBRARIES
+ NUMA_INCLUDE_DIRS)
+
+if(NUMA_FOUND AND NOT (TARGET NUMA::NUMA))
+ add_library (NUMA::NUMA UNKNOWN IMPORTED)
+ set_target_properties(NUMA::NUMA
+ PROPERTIES
+ IMPORTED_LOCATION ${NUMA_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${NUMA_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/FindTBB.cmake b/storage/rocksdb/rocksdb/cmake/modules/FindTBB.cmake
new file mode 100644
index 00000000..f6861fa5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/FindTBB.cmake
@@ -0,0 +1,33 @@
+# - Find TBB
+# Find the Thread Building Blocks library and includes
+#
+# TBB_INCLUDE_DIRS - where to find tbb.h, etc.
+# TBB_LIBRARIES - List of libraries when using TBB.
+# TBB_FOUND - True if TBB found.
+
+if(NOT DEFINED TBB_ROOT_DIR)
+ set(TBB_ROOT_DIR "$ENV{TBBROOT}")
+endif()
+
+find_path(TBB_INCLUDE_DIRS
+ NAMES tbb/tbb.h
+ HINTS ${TBB_ROOT_DIR}/include)
+
+find_library(TBB_LIBRARIES
+ NAMES tbb
+ HINTS ${TBB_ROOT_DIR}/lib ENV LIBRARY_PATH)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(TBB DEFAULT_MSG TBB_LIBRARIES TBB_INCLUDE_DIRS)
+
+mark_as_advanced(
+ TBB_LIBRARIES
+ TBB_INCLUDE_DIRS)
+
+if(TBB_FOUND AND NOT (TARGET TBB::TBB))
+ add_library (TBB::TBB UNKNOWN IMPORTED)
+ set_target_properties(TBB::TBB
+ PROPERTIES
+ IMPORTED_LOCATION ${TBB_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/Findgflags.cmake b/storage/rocksdb/rocksdb/cmake/modules/Findgflags.cmake
new file mode 100644
index 00000000..d9183fbf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/Findgflags.cmake
@@ -0,0 +1,29 @@
+# - Find gflags library
+# Find the gflags includes and library
+#
+# gflags_INCLUDE_DIR - where to find gflags.h.
+# gflags_LIBRARIES - List of libraries when using gflags.
+# gflags_FOUND - True if gflags found.
+
+find_path(GFLAGS_INCLUDE_DIR
+ NAMES gflags/gflags.h)
+
+find_library(GFLAGS_LIBRARIES
+ NAMES gflags)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(gflags
+ DEFAULT_MSG GFLAGS_LIBRARIES GFLAGS_INCLUDE_DIR)
+
+mark_as_advanced(
+ GFLAGS_LIBRARIES
+ GFLAGS_INCLUDE_DIR)
+
+if(gflags_FOUND AND NOT (TARGET gflags::gflags))
+ add_library(gflags::gflags UNKNOWN IMPORTED)
+ set_target_properties(gflags::gflags
+ PROPERTIES
+ IMPORTED_LOCATION ${GFLAGS_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${GFLAGS_INCLUDE_DIR}
+ IMPORTED_LINK_INTERFACE_LANGUAGES "CXX")
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/Findlz4.cmake b/storage/rocksdb/rocksdb/cmake/modules/Findlz4.cmake
new file mode 100644
index 00000000..7cf7d7f5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/Findlz4.cmake
@@ -0,0 +1,29 @@
+# - Find Lz4
+# Find the lz4 compression library and includes
+#
+# lz4_INCLUDE_DIRS - where to find lz4.h, etc.
+# lz4_LIBRARIES - List of libraries when using lz4.
+# lz4_FOUND - True if lz4 found.
+
+find_path(lz4_INCLUDE_DIRS
+ NAMES lz4.h
+ HINTS ${lz4_ROOT_DIR}/include)
+
+find_library(lz4_LIBRARIES
+ NAMES lz4
+ HINTS ${lz4_ROOT_DIR}/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(lz4 DEFAULT_MSG lz4_LIBRARIES lz4_INCLUDE_DIRS)
+
+mark_as_advanced(
+ lz4_LIBRARIES
+ lz4_INCLUDE_DIRS)
+
+if(lz4_FOUND AND NOT (TARGET lz4::lz4))
+ add_library(lz4::lz4 UNKNOWN IMPORTED)
+ set_target_properties(lz4::lz4
+ PROPERTIES
+ IMPORTED_LOCATION ${lz4_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${lz4_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/Findsnappy.cmake b/storage/rocksdb/rocksdb/cmake/modules/Findsnappy.cmake
new file mode 100644
index 00000000..39bba6bd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/Findsnappy.cmake
@@ -0,0 +1,29 @@
+# - Find Snappy
+# Find the snappy compression library and includes
+#
+# snappy_INCLUDE_DIRS - where to find snappy.h, etc.
+# snappy_LIBRARIES - List of libraries when using snappy.
+# snappy_FOUND - True if snappy found.
+
+find_path(snappy_INCLUDE_DIRS
+ NAMES snappy.h
+ HINTS ${snappy_ROOT_DIR}/include)
+
+find_library(snappy_LIBRARIES
+ NAMES snappy
+ HINTS ${snappy_ROOT_DIR}/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(snappy DEFAULT_MSG snappy_LIBRARIES snappy_INCLUDE_DIRS)
+
+mark_as_advanced(
+ snappy_LIBRARIES
+ snappy_INCLUDE_DIRS)
+
+if(snappy_FOUND AND NOT (TARGET snappy::snappy))
+ add_library (snappy::snappy UNKNOWN IMPORTED)
+ set_target_properties(snappy::snappy
+ PROPERTIES
+ IMPORTED_LOCATION ${snappy_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${snappy_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/Findzstd.cmake b/storage/rocksdb/rocksdb/cmake/modules/Findzstd.cmake
new file mode 100644
index 00000000..9430821d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/Findzstd.cmake
@@ -0,0 +1,29 @@
+# - Find zstd
+# Find the zstd compression library and includes
+#
+# zstd_INCLUDE_DIRS - where to find zstd.h, etc.
+# zstd_LIBRARIES - List of libraries when using zstd.
+# zstd_FOUND - True if zstd found.
+
+find_path(zstd_INCLUDE_DIRS
+ NAMES zstd.h
+ HINTS ${zstd_ROOT_DIR}/include)
+
+find_library(zstd_LIBRARIES
+ NAMES zstd
+ HINTS ${zstd_ROOT_DIR}/lib)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(zstd DEFAULT_MSG zstd_LIBRARIES zstd_INCLUDE_DIRS)
+
+mark_as_advanced(
+ zstd_LIBRARIES
+ zstd_INCLUDE_DIRS)
+
+if(zstd_FOUND AND NOT (TARGET zstd::zstd))
+ add_library (zstd::zstd UNKNOWN IMPORTED)
+ set_target_properties(zstd::zstd
+ PROPERTIES
+ IMPORTED_LOCATION ${zstd_LIBRARIES}
+ INTERFACE_INCLUDE_DIRECTORIES ${zstd_INCLUDE_DIRS})
+endif()
diff --git a/storage/rocksdb/rocksdb/cmake/modules/ReadVersion.cmake b/storage/rocksdb/rocksdb/cmake/modules/ReadVersion.cmake
new file mode 100644
index 00000000..ebfd7d6f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/cmake/modules/ReadVersion.cmake
@@ -0,0 +1,10 @@
+# Read rocksdb version from version.h header file.
+
+function(get_rocksdb_version version_var)
+ file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/rocksdb/version.h" version_header_file)
+ foreach(component MAJOR MINOR PATCH)
+ string(REGEX MATCH "#define ROCKSDB_${component} ([0-9]+)" _ ${version_header_file})
+ set(ROCKSDB_VERSION_${component} ${CMAKE_MATCH_1})
+ endforeach()
+ set(${version_var} "${ROCKSDB_VERSION_MAJOR}.${ROCKSDB_VERSION_MINOR}.${ROCKSDB_VERSION_PATCH}" PARENT_SCOPE)
+endfunction()
diff --git a/storage/rocksdb/rocksdb/coverage/coverage_test.sh b/storage/rocksdb/rocksdb/coverage/coverage_test.sh
new file mode 100755
index 00000000..2f40ed61
--- /dev/null
+++ b/storage/rocksdb/rocksdb/coverage/coverage_test.sh
@@ -0,0 +1,79 @@
+#!/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.sh
+ GCOV=$GCC_BASE/bin/gcov
+else
+ GCOV=$(which gcov)
+fi
+
+COVERAGE_DIR="$PWD/COVERAGE_REPORT"
+mkdir -p $COVERAGE_DIR
+
+# Find all gcno files to generate the coverage report
+
+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/storage/rocksdb/rocksdb/coverage/parse_gcov_output.py b/storage/rocksdb/rocksdb/coverage/parse_gcov_output.py
new file mode 100644
index 00000000..a5e98722
--- /dev/null
+++ b/storage/rocksdb/rocksdb/coverage/parse_gcov_output.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python2
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+import re
+import sys
+
+from optparse import OptionParser
+
+# 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 = 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 = set(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 >> sys.stderr, "Cannot find coverage info for the given files."
+ return
+ display_file_coverage(per_file_coverage, total_coverage)
+
+if __name__ == "__main__":
+ report_coverage()
diff --git a/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.cc b/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.cc
new file mode 100644
index 00000000..f43282a7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/arena_wrapped_db_iter.h"
+#include "memory/arena.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "table/internal_iterator.h"
+#include "table/iterator_wrapper.h"
+#include "util/user_comparator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
+ std::string* prop) {
+ if (prop_name == "rocksdb.iterator.super-version-number") {
+ // First try to pass the value returned from inner iterator.
+ if (!db_iter_->GetProperty(prop_name, prop).ok()) {
+ *prop = ToString(sv_number_);
+ }
+ return Status::OK();
+ }
+ return db_iter_->GetProperty(prop_name, prop);
+}
+
+void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ const SequenceNumber& sequence,
+ uint64_t max_sequential_skip_in_iteration,
+ uint64_t version_number,
+ ReadCallback* read_callback, DBImpl* db_impl,
+ ColumnFamilyData* cfd, bool allow_blob,
+ bool allow_refresh) {
+ auto mem = arena_.AllocateAligned(sizeof(DBIter));
+ db_iter_ = new (mem) DBIter(env, read_options, cf_options, mutable_cf_options,
+ cf_options.user_comparator, nullptr, sequence,
+ true, max_sequential_skip_in_iteration,
+ read_callback, db_impl, cfd, allow_blob);
+ sv_number_ = version_number;
+ allow_refresh_ = allow_refresh;
+}
+
+Status ArenaWrappedDBIter::Refresh() {
+ if (cfd_ == nullptr || db_impl_ == nullptr || !allow_refresh_) {
+ return Status::NotSupported("Creating renew iterator is not allowed.");
+ }
+ assert(db_iter_ != nullptr);
+ // TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the
+ // correct behavior. Will be corrected automatically when we take a snapshot
+ // here for the case of WritePreparedTxnDB.
+ SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
+ uint64_t cur_sv_number = cfd_->GetSuperVersionNumber();
+ if (sv_number_ != cur_sv_number) {
+ Env* env = db_iter_->env();
+ db_iter_->~DBIter();
+ arena_.~Arena();
+ new (&arena_) Arena();
+
+ SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_);
+ if (read_callback_) {
+ read_callback_->Refresh(latest_seq);
+ }
+ Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
+ latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
+ cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_,
+ allow_refresh_);
+
+ InternalIterator* internal_iter = db_impl_->NewInternalIterator(
+ read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator(),
+ latest_seq);
+ SetIterUnderDBIter(internal_iter);
+ } else {
+ db_iter_->set_sequence(latest_seq);
+ db_iter_->set_valid(false);
+ }
+ return Status::OK();
+}
+
+ArenaWrappedDBIter* NewArenaWrappedDbIterator(
+ Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence,
+ uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
+ ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
+ bool allow_blob, bool allow_refresh) {
+ ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
+ iter->Init(env, read_options, cf_options, mutable_cf_options, sequence,
+ max_sequential_skip_in_iterations, version_number, read_callback,
+ db_impl, cfd, allow_blob, allow_refresh);
+ if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
+ iter->StoreRefreshInfo(read_options, db_impl, cfd, read_callback,
+ allow_blob);
+ }
+
+ return iter;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.h b/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.h
new file mode 100644
index 00000000..0c135f85
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/arena_wrapped_db_iter.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+#include "db/db_impl/db_impl.h"
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "db/range_del_aggregator.h"
+#include "memory/arena.h"
+#include "options/cf_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/iterator.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena;
+
+// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
+// iterator is supposed to be allocated. This class is used as an entry point of
+// a iterator hierarchy whose memory can be allocated inline. In that way,
+// accessing the iterator tree can be more cache friendly. It is also faster
+// to allocate.
+// When using the class's Iterator interface, the behavior is exactly
+// the same as the inner DBIter.
+class ArenaWrappedDBIter : public Iterator {
+ public:
+ virtual ~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
+
+ // Get the arena to be used to allocate memory for DBIter to be wrapped,
+ // as well as child iterators in it.
+ virtual Arena* GetArena() { return &arena_; }
+ virtual ReadRangeDelAggregator* GetRangeDelAggregator() {
+ return db_iter_->GetRangeDelAggregator();
+ }
+
+ // Set the internal iterator wrapped inside the DB Iterator. Usually it is
+ // a merging iterator.
+ virtual void SetIterUnderDBIter(InternalIterator* iter) {
+ db_iter_->SetIter(iter);
+ }
+
+ bool Valid() const override { return db_iter_->Valid(); }
+ void SeekToFirst() override { db_iter_->SeekToFirst(); }
+ void SeekToLast() override { db_iter_->SeekToLast(); }
+ void Seek(const Slice& target) override { db_iter_->Seek(target); }
+ void SeekForPrev(const Slice& target) override {
+ db_iter_->SeekForPrev(target);
+ }
+ void Next() override { db_iter_->Next(); }
+ void Prev() override { db_iter_->Prev(); }
+ Slice key() const override { return db_iter_->key(); }
+ Slice value() const override { return db_iter_->value(); }
+ Status status() const override { return db_iter_->status(); }
+ bool IsBlob() const { return db_iter_->IsBlob(); }
+
+ Status GetProperty(std::string prop_name, std::string* prop) override;
+
+ Status Refresh() override;
+
+ void Init(Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ const SequenceNumber& sequence,
+ uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
+ ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
+ bool allow_blob, bool allow_refresh);
+
+ // Store some parameters so we can refresh the iterator at a later point
+ // with these same params
+ void StoreRefreshInfo(const ReadOptions& read_options, DBImpl* db_impl,
+ ColumnFamilyData* cfd, ReadCallback* read_callback,
+ bool allow_blob) {
+ read_options_ = read_options;
+ db_impl_ = db_impl;
+ cfd_ = cfd;
+ read_callback_ = read_callback;
+ allow_blob_ = allow_blob;
+ }
+
+ private:
+ DBIter* db_iter_;
+ Arena arena_;
+ uint64_t sv_number_;
+ ColumnFamilyData* cfd_ = nullptr;
+ DBImpl* db_impl_ = nullptr;
+ ReadOptions read_options_;
+ ReadCallback* read_callback_;
+ bool allow_blob_ = false;
+ bool allow_refresh_ = true;
+};
+
+// Generate the arena wrapped iterator class.
+// `db_impl` and `cfd` are used for reneweal. If left null, renewal will not
+// be supported.
+extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
+ Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence,
+ uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
+ ReadCallback* read_callback, DBImpl* db_impl = nullptr,
+ ColumnFamilyData* cfd = nullptr, bool allow_blob = false,
+ bool allow_refresh = true);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/blob_index.h b/storage/rocksdb/rocksdb/db/blob_index.h
new file mode 100644
index 00000000..483a7b97
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/blob_index.h
@@ -0,0 +1,179 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <sstream>
+#include <string>
+
+#include "rocksdb/options.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// BlobIndex is a pointer to the blob and metadata of the blob. The index is
+// stored in base DB as ValueType::kTypeBlobIndex.
+// There are three types of blob index:
+//
+// kInlinedTTL:
+// +------+------------+---------------+
+// | type | expiration | value |
+// +------+------------+---------------+
+// | char | varint64 | variable size |
+// +------+------------+---------------+
+//
+// kBlob:
+// +------+-------------+----------+----------+-------------+
+// | type | file number | offset | size | compression |
+// +------+-------------+----------+----------+-------------+
+// | char | varint64 | varint64 | varint64 | char |
+// +------+-------------+----------+----------+-------------+
+//
+// kBlobTTL:
+// +------+------------+-------------+----------+----------+-------------+
+// | type | expiration | file number | offset | size | compression |
+// +------+------------+-------------+----------+----------+-------------+
+// | char | varint64 | varint64 | varint64 | varint64 | char |
+// +------+------------+-------------+----------+----------+-------------+
+//
+// There isn't a kInlined (without TTL) type since we can store it as a plain
+// value (i.e. ValueType::kTypeValue).
+class BlobIndex {
+ public:
+ enum class Type : unsigned char {
+ kInlinedTTL = 0,
+ kBlob = 1,
+ kBlobTTL = 2,
+ kUnknown = 3,
+ };
+
+ BlobIndex() : type_(Type::kUnknown) {}
+
+ bool IsInlined() const { return type_ == Type::kInlinedTTL; }
+
+ bool HasTTL() const {
+ return type_ == Type::kInlinedTTL || type_ == Type::kBlobTTL;
+ }
+
+ uint64_t expiration() const {
+ assert(HasTTL());
+ return expiration_;
+ }
+
+ const Slice& value() const {
+ assert(IsInlined());
+ return value_;
+ }
+
+ uint64_t file_number() const {
+ assert(!IsInlined());
+ return file_number_;
+ }
+
+ uint64_t offset() const {
+ assert(!IsInlined());
+ return offset_;
+ }
+
+ uint64_t size() const {
+ assert(!IsInlined());
+ return size_;
+ }
+
+ Status DecodeFrom(Slice slice) {
+ static const std::string kErrorMessage = "Error while decoding blob index";
+ assert(slice.size() > 0);
+ type_ = static_cast<Type>(*slice.data());
+ if (type_ >= Type::kUnknown) {
+ return Status::Corruption(
+ kErrorMessage,
+ "Unknown blob index type: " + ToString(static_cast<char>(type_)));
+ }
+ slice = Slice(slice.data() + 1, slice.size() - 1);
+ if (HasTTL()) {
+ if (!GetVarint64(&slice, &expiration_)) {
+ return Status::Corruption(kErrorMessage, "Corrupted expiration");
+ }
+ }
+ if (IsInlined()) {
+ value_ = slice;
+ } else {
+ if (GetVarint64(&slice, &file_number_) && GetVarint64(&slice, &offset_) &&
+ GetVarint64(&slice, &size_) && slice.size() == 1) {
+ compression_ = static_cast<CompressionType>(*slice.data());
+ } else {
+ return Status::Corruption(kErrorMessage, "Corrupted blob offset");
+ }
+ }
+ return Status::OK();
+ }
+
+ std::string DebugString(bool output_hex) const {
+ std::ostringstream oss;
+
+ if (IsInlined()) {
+ oss << "[inlined blob] value:" << value_.ToString(output_hex);
+ } else {
+ oss << "[blob ref] file:" << file_number_ << " offset:" << offset_
+ << " size:" << size_;
+ }
+
+ if (HasTTL()) {
+ oss << " exp:" << expiration_;
+ }
+
+ return oss.str();
+ }
+
+ static void EncodeInlinedTTL(std::string* dst, uint64_t expiration,
+ const Slice& value) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(1 + kMaxVarint64Length + value.size());
+ dst->push_back(static_cast<char>(Type::kInlinedTTL));
+ PutVarint64(dst, expiration);
+ dst->append(value.data(), value.size());
+ }
+
+ static void EncodeBlob(std::string* dst, uint64_t file_number,
+ uint64_t offset, uint64_t size,
+ CompressionType compression) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(kMaxVarint64Length * 3 + 2);
+ dst->push_back(static_cast<char>(Type::kBlob));
+ PutVarint64(dst, file_number);
+ PutVarint64(dst, offset);
+ PutVarint64(dst, size);
+ dst->push_back(static_cast<char>(compression));
+ }
+
+ static void EncodeBlobTTL(std::string* dst, uint64_t expiration,
+ uint64_t file_number, uint64_t offset,
+ uint64_t size, CompressionType compression) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(kMaxVarint64Length * 4 + 2);
+ dst->push_back(static_cast<char>(Type::kBlobTTL));
+ PutVarint64(dst, expiration);
+ PutVarint64(dst, file_number);
+ PutVarint64(dst, offset);
+ PutVarint64(dst, size);
+ dst->push_back(static_cast<char>(compression));
+ }
+
+ private:
+ Type type_ = Type::kUnknown;
+ uint64_t expiration_ = 0;
+ Slice value_;
+ uint64_t file_number_ = 0;
+ uint64_t offset_ = 0;
+ uint64_t size_ = 0;
+ CompressionType compression_ = kNoCompression;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/builder.cc b/storage/rocksdb/rocksdb/db/builder.cc
new file mode 100644
index 00000000..fdb814cb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/builder.cc
@@ -0,0 +1,263 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/builder.h"
+
+#include <algorithm>
+#include <deque>
+#include <vector>
+
+#include "db/compaction/compaction_iterator.h"
+#include "db/dbformat.h"
+#include "db/event_helpers.h"
+#include "db/internal_stats.h"
+#include "db/merge_helper.h"
+#include "db/range_del_aggregator.h"
+#include "db/table_cache.h"
+#include "db/version_edit.h"
+#include "file/filename.h"
+#include "file/read_write_util.h"
+#include "file/writable_file_writer.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/thread_status_util.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_based_table_builder.h"
+#include "table/format.h"
+#include "table/internal_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TableFactory;
+
+TableBuilder* NewTableBuilder(
+ const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
+ const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, const std::string& column_family_name,
+ WritableFileWriter* file, const CompressionType compression_type,
+ uint64_t sample_for_compression, const CompressionOptions& compression_opts,
+ int level, const bool skip_filters, const uint64_t creation_time,
+ const uint64_t oldest_key_time, const uint64_t target_file_size,
+ const uint64_t file_creation_time) {
+ assert((column_family_id ==
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
+ column_family_name.empty());
+ return ioptions.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, internal_comparator,
+ int_tbl_prop_collector_factories, compression_type,
+ sample_for_compression, compression_opts,
+ skip_filters, column_family_name, level,
+ creation_time, oldest_key_time, target_file_size,
+ file_creation_time),
+ column_family_id, file);
+}
+
+Status BuildTable(
+ const std::string& dbname, Env* env, FileSystem* fs,
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
+ TableCache* table_cache, InternalIterator* iter,
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ range_del_iters,
+ FileMetaData* meta, const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, const std::string& column_family_name,
+ std::vector<SequenceNumber> snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, const CompressionType compression,
+ uint64_t sample_for_compression, const CompressionOptions& compression_opts,
+ bool paranoid_file_checks, InternalStats* internal_stats,
+ TableFileCreationReason reason, EventLogger* event_logger, int job_id,
+ const Env::IOPriority io_priority, TableProperties* table_properties,
+ int level, const uint64_t creation_time, const uint64_t oldest_key_time,
+ Env::WriteLifeTimeHint write_hint, const uint64_t file_creation_time) {
+ assert((column_family_id ==
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
+ column_family_name.empty());
+ // Reports the IOStats for flush for every following bytes.
+ const size_t kReportFlushIOStatsEvery = 1048576;
+ Status s;
+ meta->fd.file_size = 0;
+ iter->SeekToFirst();
+ std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
+ new CompactionRangeDelAggregator(&internal_comparator, snapshots));
+ for (auto& range_del_iter : range_del_iters) {
+ range_del_agg->AddTombstones(std::move(range_del_iter));
+ }
+
+ std::string fname = TableFileName(ioptions.cf_paths, meta->fd.GetNumber(),
+ meta->fd.GetPathId());
+#ifndef ROCKSDB_LITE
+ EventHelpers::NotifyTableFileCreationStarted(
+ ioptions.listeners, dbname, column_family_name, fname, job_id, reason);
+#endif // !ROCKSDB_LITE
+ TableProperties tp;
+
+ if (iter->Valid() || !range_del_agg->IsEmpty()) {
+ TableBuilder* builder;
+ std::unique_ptr<WritableFileWriter> file_writer;
+ // Currently we only enable dictionary compression during compaction to the
+ // bottommost level.
+ CompressionOptions compression_opts_for_flush(compression_opts);
+ compression_opts_for_flush.max_dict_bytes = 0;
+ compression_opts_for_flush.zstd_max_train_bytes = 0;
+ {
+ std::unique_ptr<FSWritableFile> file;
+#ifndef NDEBUG
+ bool use_direct_writes = file_options.use_direct_writes;
+ TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
+#endif // !NDEBUG
+ s = NewWritableFile(fs, fname, &file, file_options);
+ if (!s.ok()) {
+ EventHelpers::LogAndNotifyTableFileCreationFinished(
+ event_logger, ioptions.listeners, dbname, column_family_name, fname,
+ job_id, meta->fd, kInvalidBlobFileNumber, tp, reason, s);
+ return s;
+ }
+ file->SetIOPriority(io_priority);
+ file->SetWriteLifeTimeHint(write_hint);
+
+ file_writer.reset(new WritableFileWriter(
+ std::move(file), fname, file_options, env, ioptions.statistics,
+ ioptions.listeners, ioptions.sst_file_checksum_func));
+
+ builder = NewTableBuilder(
+ ioptions, mutable_cf_options, internal_comparator,
+ int_tbl_prop_collector_factories, column_family_id,
+ column_family_name, file_writer.get(), compression,
+ sample_for_compression, compression_opts_for_flush, level,
+ false /* skip_filters */, creation_time, oldest_key_time,
+ 0 /*target_file_size*/, file_creation_time);
+ }
+
+ MergeHelper merge(env, internal_comparator.user_comparator(),
+ ioptions.merge_operator, nullptr, ioptions.info_log,
+ true /* internal key corruption is not ok */,
+ snapshots.empty() ? 0 : snapshots.back(),
+ snapshot_checker);
+
+ CompactionIterator c_iter(
+ iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber,
+ &snapshots, earliest_write_conflict_snapshot, snapshot_checker, env,
+ ShouldReportDetailedTime(env, ioptions.statistics),
+ true /* internal key corruption is not ok */, range_del_agg.get());
+ c_iter.SeekToFirst();
+ for (; c_iter.Valid(); c_iter.Next()) {
+ const Slice& key = c_iter.key();
+ const Slice& value = c_iter.value();
+ const ParsedInternalKey& ikey = c_iter.ikey();
+ builder->Add(key, value);
+ meta->UpdateBoundaries(key, value, ikey.sequence, ikey.type);
+
+ // TODO(noetzli): Update stats after flush, too.
+ if (io_priority == Env::IO_HIGH &&
+ IOSTATS(bytes_written) >= kReportFlushIOStatsEvery) {
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
+ }
+ }
+
+ auto range_del_it = range_del_agg->NewIterator();
+ for (range_del_it->SeekToFirst(); range_del_it->Valid();
+ range_del_it->Next()) {
+ auto tombstone = range_del_it->Tombstone();
+ auto kv = tombstone.Serialize();
+ builder->Add(kv.first.Encode(), kv.second);
+ meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
+ tombstone.seq_, internal_comparator);
+ }
+
+ // Finish and check for builder errors
+ tp = builder->GetTableProperties();
+ bool empty = builder->NumEntries() == 0 && tp.num_range_deletions == 0;
+ s = c_iter.status();
+ if (!s.ok() || empty) {
+ builder->Abandon();
+ } else {
+ s = builder->Finish();
+ }
+
+ if (s.ok() && !empty) {
+ uint64_t file_size = builder->FileSize();
+ meta->fd.file_size = file_size;
+ meta->marked_for_compaction = builder->NeedCompact();
+ assert(meta->fd.GetFileSize() > 0);
+ tp = builder->GetTableProperties(); // refresh now that builder is finished
+ if (table_properties) {
+ *table_properties = tp;
+ }
+ // Add the checksum information to file metadata.
+ meta->file_checksum = builder->GetFileChecksum();
+ meta->file_checksum_func_name = builder->GetFileChecksumFuncName();
+ }
+ delete builder;
+
+ // Finish and check for file errors
+ if (s.ok() && !empty) {
+ StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
+ s = file_writer->Sync(ioptions.use_fsync);
+ }
+ if (s.ok() && !empty) {
+ s = file_writer->Close();
+ }
+
+ if (s.ok() && !empty) {
+ // Verify that the table is usable
+ // We set for_compaction to false and don't OptimizeForCompactionTableRead
+ // here because this is a special case after we finish the table building
+ // No matter whether use_direct_io_for_flush_and_compaction is true,
+ // we will regrad this verification as user reads since the goal is
+ // to cache it here for further user reads
+ std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
+ ReadOptions(), file_options, internal_comparator, *meta,
+ nullptr /* range_del_agg */,
+ mutable_cf_options.prefix_extractor.get(), nullptr,
+ (internal_stats == nullptr) ? nullptr
+ : internal_stats->GetFileReadHist(0),
+ TableReaderCaller::kFlush, /*arena=*/nullptr,
+ /*skip_filter=*/false, level, /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key*/ nullptr));
+ s = it->status();
+ if (s.ok() && paranoid_file_checks) {
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ }
+ s = it->status();
+ }
+ }
+ }
+
+ // Check for input iterator errors
+ if (!iter->status().ok()) {
+ s = iter->status();
+ }
+
+ if (!s.ok() || meta->fd.GetFileSize() == 0) {
+ fs->DeleteFile(fname, IOOptions(), nullptr);
+ }
+
+ if (meta->fd.GetFileSize() == 0) {
+ fname = "(nil)";
+ }
+ // Output to event logger and fire events.
+ EventHelpers::LogAndNotifyTableFileCreationFinished(
+ event_logger, ioptions.listeners, dbname, column_family_name, fname,
+ job_id, meta->fd, meta->oldest_blob_file_number, tp, reason, s);
+
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/builder.h b/storage/rocksdb/rocksdb/db/builder.h
new file mode 100644
index 00000000..062f1fb8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/builder.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <utility>
+#include <vector>
+#include "db/range_tombstone_fragmenter.h"
+#include "db/table_properties_collector.h"
+#include "logging/event_logger.h"
+#include "options/cf_options.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/types.h"
+#include "table/scoped_arena_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct Options;
+struct FileMetaData;
+
+class Env;
+struct EnvOptions;
+class Iterator;
+class SnapshotChecker;
+class TableCache;
+class VersionEdit;
+class TableBuilder;
+class WritableFileWriter;
+class InternalStats;
+
+// @param column_family_name Name of the column family that is also identified
+// by column_family_id, or empty string if unknown. It must outlive the
+// TableBuilder returned by this function.
+TableBuilder* NewTableBuilder(
+ const ImmutableCFOptions& options, const MutableCFOptions& moptions,
+ const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, const std::string& column_family_name,
+ WritableFileWriter* file, const CompressionType compression_type,
+ const uint64_t sample_for_compression,
+ const CompressionOptions& compression_opts, int level,
+ const bool skip_filters = false, const uint64_t creation_time = 0,
+ const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
+ const uint64_t file_creation_time = 0);
+
+// Build a Table file from the contents of *iter. The generated file
+// will be named according to number specified in meta. On success, the rest of
+// *meta will be filled with metadata about the generated table.
+// If no data is present in *iter, meta->file_size will be set to
+// zero, and no Table file will be produced.
+//
+// @param column_family_name Name of the column family that is also identified
+// by column_family_id, or empty string if unknown.
+extern Status BuildTable(
+ const std::string& dbname, Env* env, FileSystem* fs,
+ const ImmutableCFOptions& options,
+ const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
+ TableCache* table_cache, InternalIterator* iter,
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ range_del_iters,
+ FileMetaData* meta, const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, const std::string& column_family_name,
+ std::vector<SequenceNumber> snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, const CompressionType compression,
+ const uint64_t sample_for_compression,
+ const CompressionOptions& compression_opts, bool paranoid_file_checks,
+ InternalStats* internal_stats, TableFileCreationReason reason,
+ EventLogger* event_logger = nullptr, int job_id = 0,
+ const Env::IOPriority io_priority = Env::IO_HIGH,
+ TableProperties* table_properties = nullptr, int level = -1,
+ const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
+ Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
+ const uint64_t file_creation_time = 0);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/c.cc b/storage/rocksdb/rocksdb/db/c.cc
new file mode 100644
index 00000000..db78030d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/c.cc
@@ -0,0 +1,4451 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/c.h"
+
+#include <stdlib.h>
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "rocksdb/universal_compaction.h"
+#include "rocksdb/utilities/backupable_db.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "rocksdb/utilities/memory_util.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "rocksdb/write_batch.h"
+#include "rocksdb/perf_context.h"
+#include "utilities/merge_operators.h"
+
+#include <vector>
+#include <unordered_set>
+#include <map>
+
+using ROCKSDB_NAMESPACE::BackupableDBOptions;
+using ROCKSDB_NAMESPACE::BackupEngine;
+using ROCKSDB_NAMESPACE::BackupID;
+using ROCKSDB_NAMESPACE::BackupInfo;
+using ROCKSDB_NAMESPACE::BatchResult;
+using ROCKSDB_NAMESPACE::BlockBasedTableOptions;
+using ROCKSDB_NAMESPACE::BottommostLevelCompaction;
+using ROCKSDB_NAMESPACE::BytewiseComparator;
+using ROCKSDB_NAMESPACE::Cache;
+using ROCKSDB_NAMESPACE::Checkpoint;
+using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor;
+using ROCKSDB_NAMESPACE::ColumnFamilyHandle;
+using ROCKSDB_NAMESPACE::ColumnFamilyOptions;
+using ROCKSDB_NAMESPACE::CompactionFilter;
+using ROCKSDB_NAMESPACE::CompactionFilterContext;
+using ROCKSDB_NAMESPACE::CompactionFilterFactory;
+using ROCKSDB_NAMESPACE::CompactionOptionsFIFO;
+using ROCKSDB_NAMESPACE::CompactRangeOptions;
+using ROCKSDB_NAMESPACE::Comparator;
+using ROCKSDB_NAMESPACE::CompressionType;
+using ROCKSDB_NAMESPACE::CuckooTableOptions;
+using ROCKSDB_NAMESPACE::DB;
+using ROCKSDB_NAMESPACE::DBOptions;
+using ROCKSDB_NAMESPACE::DbPath;
+using ROCKSDB_NAMESPACE::Env;
+using ROCKSDB_NAMESPACE::EnvOptions;
+using ROCKSDB_NAMESPACE::FileLock;
+using ROCKSDB_NAMESPACE::FilterPolicy;
+using ROCKSDB_NAMESPACE::FlushOptions;
+using ROCKSDB_NAMESPACE::InfoLogLevel;
+using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
+using ROCKSDB_NAMESPACE::Iterator;
+using ROCKSDB_NAMESPACE::LiveFileMetaData;
+using ROCKSDB_NAMESPACE::Logger;
+using ROCKSDB_NAMESPACE::MemoryUtil;
+using ROCKSDB_NAMESPACE::MergeOperator;
+using ROCKSDB_NAMESPACE::MergeOperators;
+using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
+using ROCKSDB_NAMESPACE::NewGenericRateLimiter;
+using ROCKSDB_NAMESPACE::NewLRUCache;
+using ROCKSDB_NAMESPACE::OptimisticTransactionDB;
+using ROCKSDB_NAMESPACE::OptimisticTransactionOptions;
+using ROCKSDB_NAMESPACE::Options;
+using ROCKSDB_NAMESPACE::PerfContext;
+using ROCKSDB_NAMESPACE::PerfLevel;
+using ROCKSDB_NAMESPACE::PinnableSlice;
+using ROCKSDB_NAMESPACE::RandomAccessFile;
+using ROCKSDB_NAMESPACE::Range;
+using ROCKSDB_NAMESPACE::RateLimiter;
+using ROCKSDB_NAMESPACE::ReadOptions;
+using ROCKSDB_NAMESPACE::RestoreOptions;
+using ROCKSDB_NAMESPACE::SequentialFile;
+using ROCKSDB_NAMESPACE::Slice;
+using ROCKSDB_NAMESPACE::SliceParts;
+using ROCKSDB_NAMESPACE::SliceTransform;
+using ROCKSDB_NAMESPACE::Snapshot;
+using ROCKSDB_NAMESPACE::SstFileWriter;
+using ROCKSDB_NAMESPACE::Status;
+using ROCKSDB_NAMESPACE::Transaction;
+using ROCKSDB_NAMESPACE::TransactionDB;
+using ROCKSDB_NAMESPACE::TransactionDBOptions;
+using ROCKSDB_NAMESPACE::TransactionLogIterator;
+using ROCKSDB_NAMESPACE::TransactionOptions;
+using ROCKSDB_NAMESPACE::WALRecoveryMode;
+using ROCKSDB_NAMESPACE::WritableFile;
+using ROCKSDB_NAMESPACE::WriteBatch;
+using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
+using ROCKSDB_NAMESPACE::WriteOptions;
+
+using std::shared_ptr;
+using std::vector;
+using std::unordered_set;
+using std::map;
+
+extern "C" {
+
+struct rocksdb_t { DB* rep; };
+struct rocksdb_backup_engine_t { BackupEngine* rep; };
+struct rocksdb_backup_engine_info_t { std::vector<BackupInfo> rep; };
+struct rocksdb_restore_options_t { RestoreOptions rep; };
+struct rocksdb_iterator_t { Iterator* rep; };
+struct rocksdb_writebatch_t { WriteBatch rep; };
+struct rocksdb_writebatch_wi_t { WriteBatchWithIndex* rep; };
+struct rocksdb_snapshot_t { const Snapshot* rep; };
+struct rocksdb_flushoptions_t { FlushOptions rep; };
+struct rocksdb_fifo_compaction_options_t { CompactionOptionsFIFO rep; };
+struct rocksdb_readoptions_t {
+ ReadOptions rep;
+ // stack variables to set pointers to in ReadOptions
+ Slice upper_bound;
+ Slice lower_bound;
+};
+struct rocksdb_writeoptions_t { WriteOptions rep; };
+struct rocksdb_options_t { Options rep; };
+struct rocksdb_compactoptions_t {
+ CompactRangeOptions rep;
+};
+struct rocksdb_block_based_table_options_t { BlockBasedTableOptions rep; };
+struct rocksdb_cuckoo_table_options_t { CuckooTableOptions rep; };
+struct rocksdb_seqfile_t { SequentialFile* rep; };
+struct rocksdb_randomfile_t { RandomAccessFile* rep; };
+struct rocksdb_writablefile_t { WritableFile* rep; };
+struct rocksdb_wal_iterator_t { TransactionLogIterator* rep; };
+struct rocksdb_wal_readoptions_t { TransactionLogIterator::ReadOptions rep; };
+struct rocksdb_filelock_t { FileLock* rep; };
+struct rocksdb_logger_t {
+ std::shared_ptr<Logger> rep;
+};
+struct rocksdb_cache_t {
+ std::shared_ptr<Cache> rep;
+};
+struct rocksdb_livefiles_t { std::vector<LiveFileMetaData> rep; };
+struct rocksdb_column_family_handle_t { ColumnFamilyHandle* rep; };
+struct rocksdb_envoptions_t { EnvOptions rep; };
+struct rocksdb_ingestexternalfileoptions_t { IngestExternalFileOptions rep; };
+struct rocksdb_sstfilewriter_t { SstFileWriter* rep; };
+struct rocksdb_ratelimiter_t {
+ std::shared_ptr<RateLimiter> rep;
+};
+struct rocksdb_perfcontext_t { PerfContext* rep; };
+struct rocksdb_pinnableslice_t {
+ PinnableSlice rep;
+};
+struct rocksdb_transactiondb_options_t {
+ TransactionDBOptions rep;
+};
+struct rocksdb_transactiondb_t {
+ TransactionDB* rep;
+};
+struct rocksdb_transaction_options_t {
+ TransactionOptions rep;
+};
+struct rocksdb_transaction_t {
+ Transaction* rep;
+};
+struct rocksdb_checkpoint_t {
+ Checkpoint* rep;
+};
+struct rocksdb_optimistictransactiondb_t {
+ OptimisticTransactionDB* rep;
+};
+struct rocksdb_optimistictransaction_options_t {
+ OptimisticTransactionOptions rep;
+};
+
+struct rocksdb_compactionfiltercontext_t {
+ CompactionFilter::Context rep;
+};
+
+struct rocksdb_compactionfilter_t : public CompactionFilter {
+ void* state_;
+ void (*destructor_)(void*);
+ unsigned char (*filter_)(
+ void*,
+ int level,
+ const char* key, size_t key_length,
+ const char* existing_value, size_t value_length,
+ char** new_value, size_t *new_value_length,
+ unsigned char* value_changed);
+ const char* (*name_)(void*);
+ unsigned char ignore_snapshots_;
+
+ ~rocksdb_compactionfilter_t() override { (*destructor_)(state_); }
+
+ bool Filter(int level, const Slice& key, const Slice& existing_value,
+ std::string* new_value, bool* value_changed) const override {
+ char* c_new_value = nullptr;
+ size_t new_value_length = 0;
+ unsigned char c_value_changed = 0;
+ unsigned char result = (*filter_)(
+ state_,
+ level,
+ key.data(), key.size(),
+ existing_value.data(), existing_value.size(),
+ &c_new_value, &new_value_length, &c_value_changed);
+ if (c_value_changed) {
+ new_value->assign(c_new_value, new_value_length);
+ *value_changed = true;
+ }
+ return result;
+ }
+
+ const char* Name() const override { return (*name_)(state_); }
+
+ bool IgnoreSnapshots() const override { return ignore_snapshots_; }
+};
+
+struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
+ void* state_;
+ void (*destructor_)(void*);
+ rocksdb_compactionfilter_t* (*create_compaction_filter_)(
+ void*, rocksdb_compactionfiltercontext_t* context);
+ const char* (*name_)(void*);
+
+ ~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); }
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ rocksdb_compactionfiltercontext_t ccontext;
+ ccontext.rep = context;
+ CompactionFilter* cf = (*create_compaction_filter_)(state_, &ccontext);
+ return std::unique_ptr<CompactionFilter>(cf);
+ }
+
+ const char* Name() const override { return (*name_)(state_); }
+};
+
+struct rocksdb_comparator_t : public Comparator {
+ void* state_;
+ void (*destructor_)(void*);
+ int (*compare_)(
+ void*,
+ const char* a, size_t alen,
+ const char* b, size_t blen);
+ const char* (*name_)(void*);
+
+ ~rocksdb_comparator_t() override { (*destructor_)(state_); }
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ return (*compare_)(state_, a.data(), a.size(), b.data(), b.size());
+ }
+
+ const char* Name() const override { return (*name_)(state_); }
+
+ // No-ops since the C binding does not support key shortening methods.
+ void FindShortestSeparator(std::string*, const Slice&) const override {}
+ void FindShortSuccessor(std::string* /*key*/) const override {}
+};
+
+struct rocksdb_filterpolicy_t : public FilterPolicy {
+ void* state_;
+ void (*destructor_)(void*);
+ const char* (*name_)(void*);
+ char* (*create_)(
+ void*,
+ const char* const* key_array, const size_t* key_length_array,
+ int num_keys,
+ size_t* filter_length);
+ unsigned char (*key_match_)(
+ void*,
+ const char* key, size_t length,
+ const char* filter, size_t filter_length);
+ void (*delete_filter_)(
+ void*,
+ const char* filter, size_t filter_length);
+
+ ~rocksdb_filterpolicy_t() override { (*destructor_)(state_); }
+
+ const char* Name() const override { return (*name_)(state_); }
+
+ void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
+ std::vector<const char*> key_pointers(n);
+ std::vector<size_t> key_sizes(n);
+ for (int i = 0; i < n; i++) {
+ key_pointers[i] = keys[i].data();
+ key_sizes[i] = keys[i].size();
+ }
+ size_t len;
+ char* filter = (*create_)(state_, &key_pointers[0], &key_sizes[0], n, &len);
+ dst->append(filter, len);
+
+ if (delete_filter_ != nullptr) {
+ (*delete_filter_)(state_, filter, len);
+ } else {
+ free(filter);
+ }
+ }
+
+ bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
+ return (*key_match_)(state_, key.data(), key.size(),
+ filter.data(), filter.size());
+ }
+};
+
+struct rocksdb_mergeoperator_t : public MergeOperator {
+ void* state_;
+ void (*destructor_)(void*);
+ const char* (*name_)(void*);
+ char* (*full_merge_)(
+ void*,
+ const char* key, size_t key_length,
+ const char* existing_value, size_t existing_value_length,
+ const char* const* operands_list, const size_t* operands_list_length,
+ int num_operands,
+ unsigned char* success, size_t* new_value_length);
+ char* (*partial_merge_)(void*, const char* key, size_t key_length,
+ const char* const* operands_list,
+ const size_t* operands_list_length, int num_operands,
+ unsigned char* success, size_t* new_value_length);
+ void (*delete_value_)(
+ void*,
+ const char* value, size_t value_length);
+
+ ~rocksdb_mergeoperator_t() override { (*destructor_)(state_); }
+
+ const char* Name() const override { return (*name_)(state_); }
+
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ size_t n = merge_in.operand_list.size();
+ std::vector<const char*> operand_pointers(n);
+ std::vector<size_t> operand_sizes(n);
+ for (size_t i = 0; i < n; i++) {
+ Slice operand(merge_in.operand_list[i]);
+ operand_pointers[i] = operand.data();
+ operand_sizes[i] = operand.size();
+ }
+
+ const char* existing_value_data = nullptr;
+ size_t existing_value_len = 0;
+ if (merge_in.existing_value != nullptr) {
+ existing_value_data = merge_in.existing_value->data();
+ existing_value_len = merge_in.existing_value->size();
+ }
+
+ unsigned char success;
+ size_t new_value_len;
+ char* tmp_new_value = (*full_merge_)(
+ state_, merge_in.key.data(), merge_in.key.size(), existing_value_data,
+ existing_value_len, &operand_pointers[0], &operand_sizes[0],
+ static_cast<int>(n), &success, &new_value_len);
+ merge_out->new_value.assign(tmp_new_value, new_value_len);
+
+ if (delete_value_ != nullptr) {
+ (*delete_value_)(state_, tmp_new_value, new_value_len);
+ } else {
+ free(tmp_new_value);
+ }
+
+ return success;
+ }
+
+ bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* /*logger*/) const override {
+ size_t operand_count = operand_list.size();
+ std::vector<const char*> operand_pointers(operand_count);
+ std::vector<size_t> operand_sizes(operand_count);
+ for (size_t i = 0; i < operand_count; ++i) {
+ Slice operand(operand_list[i]);
+ operand_pointers[i] = operand.data();
+ operand_sizes[i] = operand.size();
+ }
+
+ unsigned char success;
+ size_t new_value_len;
+ char* tmp_new_value = (*partial_merge_)(
+ state_, key.data(), key.size(), &operand_pointers[0], &operand_sizes[0],
+ static_cast<int>(operand_count), &success, &new_value_len);
+ new_value->assign(tmp_new_value, new_value_len);
+
+ if (delete_value_ != nullptr) {
+ (*delete_value_)(state_, tmp_new_value, new_value_len);
+ } else {
+ free(tmp_new_value);
+ }
+
+ return success;
+ }
+};
+
+struct rocksdb_dbpath_t {
+ DbPath rep;
+};
+
+struct rocksdb_env_t {
+ Env* rep;
+ bool is_default;
+};
+
+struct rocksdb_slicetransform_t : public SliceTransform {
+ void* state_;
+ void (*destructor_)(void*);
+ const char* (*name_)(void*);
+ char* (*transform_)(
+ void*,
+ const char* key, size_t length,
+ size_t* dst_length);
+ unsigned char (*in_domain_)(
+ void*,
+ const char* key, size_t length);
+ unsigned char (*in_range_)(
+ void*,
+ const char* key, size_t length);
+
+ ~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
+
+ const char* Name() const override { return (*name_)(state_); }
+
+ Slice Transform(const Slice& src) const override {
+ size_t len;
+ char* dst = (*transform_)(state_, src.data(), src.size(), &len);
+ return Slice(dst, len);
+ }
+
+ bool InDomain(const Slice& src) const override {
+ return (*in_domain_)(state_, src.data(), src.size());
+ }
+
+ bool InRange(const Slice& src) const override {
+ return (*in_range_)(state_, src.data(), src.size());
+ }
+};
+
+struct rocksdb_universal_compaction_options_t {
+ ROCKSDB_NAMESPACE::CompactionOptionsUniversal* rep;
+};
+
+static bool SaveError(char** errptr, const Status& s) {
+ assert(errptr != nullptr);
+ if (s.ok()) {
+ return false;
+ } else if (*errptr == nullptr) {
+ *errptr = strdup(s.ToString().c_str());
+ } else {
+ // TODO(sanjay): Merge with existing error?
+ // This is a bug if *errptr is not created by malloc()
+ free(*errptr);
+ *errptr = strdup(s.ToString().c_str());
+ }
+ return true;
+}
+
+static char* CopyString(const std::string& str) {
+ char* result = reinterpret_cast<char*>(malloc(sizeof(char) * str.size()));
+ memcpy(result, str.data(), sizeof(char) * str.size());
+ return result;
+}
+
+rocksdb_t* rocksdb_open(
+ const rocksdb_options_t* options,
+ const char* name,
+ char** errptr) {
+ DB* db;
+ if (SaveError(errptr, DB::Open(options->rep, std::string(name), &db))) {
+ return nullptr;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_t* rocksdb_open_with_ttl(
+ const rocksdb_options_t* options,
+ const char* name,
+ int ttl,
+ char** errptr) {
+ ROCKSDB_NAMESPACE::DBWithTTL* db;
+ if (SaveError(errptr, ROCKSDB_NAMESPACE::DBWithTTL::Open(
+ options->rep, std::string(name), &db, ttl))) {
+ return nullptr;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_t* rocksdb_open_for_read_only(
+ const rocksdb_options_t* options,
+ const char* name,
+ unsigned char error_if_log_file_exist,
+ char** errptr) {
+ DB* db;
+ if (SaveError(errptr, DB::OpenForReadOnly(options->rep, std::string(name), &db, error_if_log_file_exist))) {
+ return nullptr;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_t* rocksdb_open_as_secondary(const rocksdb_options_t* options,
+ const char* name,
+ const char* secondary_path,
+ char** errptr) {
+ DB* db;
+ if (SaveError(errptr,
+ DB::OpenAsSecondary(options->rep, std::string(name),
+ std::string(secondary_path), &db))) {
+ return nullptr;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_backup_engine_t* rocksdb_backup_engine_open(
+ const rocksdb_options_t* options, const char* path, char** errptr) {
+ BackupEngine* be;
+ if (SaveError(errptr, BackupEngine::Open(options->rep.env,
+ BackupableDBOptions(path,
+ nullptr,
+ true,
+ options->rep.info_log.get()),
+ &be))) {
+ return nullptr;
+ }
+ rocksdb_backup_engine_t* result = new rocksdb_backup_engine_t;
+ result->rep = be;
+ return result;
+}
+
+void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be,
+ rocksdb_t* db,
+ char** errptr) {
+ SaveError(errptr, be->rep->CreateNewBackup(db->rep));
+}
+
+void rocksdb_backup_engine_create_new_backup_flush(rocksdb_backup_engine_t* be,
+ rocksdb_t* db,
+ unsigned char flush_before_backup,
+ char** errptr) {
+ SaveError(errptr, be->rep->CreateNewBackup(db->rep, flush_before_backup));
+}
+
+void rocksdb_backup_engine_purge_old_backups(rocksdb_backup_engine_t* be,
+ uint32_t num_backups_to_keep,
+ char** errptr) {
+ SaveError(errptr, be->rep->PurgeOldBackups(num_backups_to_keep));
+}
+
+rocksdb_restore_options_t* rocksdb_restore_options_create() {
+ return new rocksdb_restore_options_t;
+}
+
+void rocksdb_restore_options_destroy(rocksdb_restore_options_t* opt) {
+ delete opt;
+}
+
+void rocksdb_restore_options_set_keep_log_files(rocksdb_restore_options_t* opt,
+ int v) {
+ opt->rep.keep_log_files = v;
+}
+
+
+void rocksdb_backup_engine_verify_backup(rocksdb_backup_engine_t* be,
+ uint32_t backup_id, char** errptr) {
+ SaveError(errptr, be->rep->VerifyBackup(static_cast<BackupID>(backup_id)));
+}
+
+void rocksdb_backup_engine_restore_db_from_latest_backup(
+ rocksdb_backup_engine_t* be, const char* db_dir, const char* wal_dir,
+ const rocksdb_restore_options_t* restore_options, char** errptr) {
+ SaveError(errptr, be->rep->RestoreDBFromLatestBackup(std::string(db_dir),
+ std::string(wal_dir),
+ restore_options->rep));
+}
+
+const rocksdb_backup_engine_info_t* rocksdb_backup_engine_get_backup_info(
+ rocksdb_backup_engine_t* be) {
+ rocksdb_backup_engine_info_t* result = new rocksdb_backup_engine_info_t;
+ be->rep->GetBackupInfo(&result->rep);
+ return result;
+}
+
+int rocksdb_backup_engine_info_count(const rocksdb_backup_engine_info_t* info) {
+ return static_cast<int>(info->rep.size());
+}
+
+int64_t rocksdb_backup_engine_info_timestamp(
+ const rocksdb_backup_engine_info_t* info, int index) {
+ return info->rep[index].timestamp;
+}
+
+uint32_t rocksdb_backup_engine_info_backup_id(
+ const rocksdb_backup_engine_info_t* info, int index) {
+ return info->rep[index].backup_id;
+}
+
+uint64_t rocksdb_backup_engine_info_size(
+ const rocksdb_backup_engine_info_t* info, int index) {
+ return info->rep[index].size;
+}
+
+uint32_t rocksdb_backup_engine_info_number_files(
+ const rocksdb_backup_engine_info_t* info, int index) {
+ return info->rep[index].number_files;
+}
+
+void rocksdb_backup_engine_info_destroy(
+ const rocksdb_backup_engine_info_t* info) {
+ delete info;
+}
+
+void rocksdb_backup_engine_close(rocksdb_backup_engine_t* be) {
+ delete be->rep;
+ delete be;
+}
+
+rocksdb_checkpoint_t* rocksdb_checkpoint_object_create(rocksdb_t* db,
+ char** errptr) {
+ Checkpoint* checkpoint;
+ if (SaveError(errptr, Checkpoint::Create(db->rep, &checkpoint))) {
+ return nullptr;
+ }
+ rocksdb_checkpoint_t* result = new rocksdb_checkpoint_t;
+ result->rep = checkpoint;
+ return result;
+}
+
+void rocksdb_checkpoint_create(rocksdb_checkpoint_t* checkpoint,
+ const char* checkpoint_dir,
+ uint64_t log_size_for_flush, char** errptr) {
+ SaveError(errptr, checkpoint->rep->CreateCheckpoint(
+ std::string(checkpoint_dir), log_size_for_flush));
+}
+
+void rocksdb_checkpoint_object_destroy(rocksdb_checkpoint_t* checkpoint) {
+ delete checkpoint->rep;
+ delete checkpoint;
+}
+
+void rocksdb_close(rocksdb_t* db) {
+ delete db->rep;
+ delete db;
+}
+
+void rocksdb_options_set_uint64add_merge_operator(rocksdb_options_t* opt) {
+ opt->rep.merge_operator =
+ ROCKSDB_NAMESPACE::MergeOperators::CreateUInt64AddOperator();
+}
+
+rocksdb_t* rocksdb_open_column_families(
+ const rocksdb_options_t* db_options, const char* name,
+ int num_column_families, const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (int i = 0; i < num_column_families; i++) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ std::string(column_family_names[i]),
+ ColumnFamilyOptions(column_family_options[i]->rep)));
+ }
+
+ DB* db;
+ std::vector<ColumnFamilyHandle*> handles;
+ if (SaveError(errptr, DB::Open(DBOptions(db_options->rep),
+ std::string(name), column_families, &handles, &db))) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < handles.size(); i++) {
+ rocksdb_column_family_handle_t* c_handle = new rocksdb_column_family_handle_t;
+ c_handle->rep = handles[i];
+ column_family_handles[i] = c_handle;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_t* rocksdb_open_for_read_only_column_families(
+ const rocksdb_options_t* db_options, const char* name,
+ int num_column_families, const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles,
+ unsigned char error_if_log_file_exist, char** errptr) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (int i = 0; i < num_column_families; i++) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ std::string(column_family_names[i]),
+ ColumnFamilyOptions(column_family_options[i]->rep)));
+ }
+
+ DB* db;
+ std::vector<ColumnFamilyHandle*> handles;
+ if (SaveError(errptr, DB::OpenForReadOnly(DBOptions(db_options->rep),
+ std::string(name), column_families, &handles, &db, error_if_log_file_exist))) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < handles.size(); i++) {
+ rocksdb_column_family_handle_t* c_handle = new rocksdb_column_family_handle_t;
+ c_handle->rep = handles[i];
+ column_family_handles[i] = c_handle;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+rocksdb_t* rocksdb_open_as_secondary_column_families(
+ const rocksdb_options_t* db_options, const char* name,
+ const char* secondary_path, int num_column_families,
+ const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (int i = 0; i != num_column_families; ++i) {
+ column_families.emplace_back(
+ std::string(column_family_names[i]),
+ ColumnFamilyOptions(column_family_options[i]->rep));
+ }
+ DB* db;
+ std::vector<ColumnFamilyHandle*> handles;
+ if (SaveError(errptr, DB::OpenAsSecondary(DBOptions(db_options->rep),
+ std::string(name),
+ std::string(secondary_path),
+ column_families, &handles, &db))) {
+ return nullptr;
+ }
+ for (size_t i = 0; i != handles.size(); ++i) {
+ rocksdb_column_family_handle_t* c_handle =
+ new rocksdb_column_family_handle_t;
+ c_handle->rep = handles[i];
+ column_family_handles[i] = c_handle;
+ }
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = db;
+ return result;
+}
+
+char** rocksdb_list_column_families(
+ const rocksdb_options_t* options,
+ const char* name,
+ size_t* lencfs,
+ char** errptr) {
+ std::vector<std::string> fams;
+ SaveError(errptr,
+ DB::ListColumnFamilies(DBOptions(options->rep),
+ std::string(name), &fams));
+
+ *lencfs = fams.size();
+ char** column_families = static_cast<char**>(malloc(sizeof(char*) * fams.size()));
+ for (size_t i = 0; i < fams.size(); i++) {
+ column_families[i] = strdup(fams[i].c_str());
+ }
+ return column_families;
+}
+
+void rocksdb_list_column_families_destroy(char** list, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ free(list[i]);
+ }
+ free(list);
+}
+
+rocksdb_column_family_handle_t* rocksdb_create_column_family(
+ rocksdb_t* db,
+ const rocksdb_options_t* column_family_options,
+ const char* column_family_name,
+ char** errptr) {
+ rocksdb_column_family_handle_t* handle = new rocksdb_column_family_handle_t;
+ SaveError(errptr,
+ db->rep->CreateColumnFamily(ColumnFamilyOptions(column_family_options->rep),
+ std::string(column_family_name), &(handle->rep)));
+ return handle;
+}
+
+void rocksdb_drop_column_family(
+ rocksdb_t* db,
+ rocksdb_column_family_handle_t* handle,
+ char** errptr) {
+ SaveError(errptr, db->rep->DropColumnFamily(handle->rep));
+}
+
+void rocksdb_column_family_handle_destroy(rocksdb_column_family_handle_t* handle) {
+ delete handle->rep;
+ delete handle;
+}
+
+void rocksdb_put(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr,
+ db->rep->Put(options->rep, Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_put_cf(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr,
+ db->rep->Put(options->rep, column_family->rep,
+ Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_delete(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t keylen,
+ char** errptr) {
+ SaveError(errptr, db->rep->Delete(options->rep, Slice(key, keylen)));
+}
+
+void rocksdb_delete_cf(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ char** errptr) {
+ SaveError(errptr, db->rep->Delete(options->rep, column_family->rep,
+ Slice(key, keylen)));
+}
+
+void rocksdb_delete_range_cf(rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len,
+ const char* end_key, size_t end_key_len,
+ char** errptr) {
+ SaveError(errptr, db->rep->DeleteRange(options->rep, column_family->rep,
+ Slice(start_key, start_key_len),
+ Slice(end_key, end_key_len)));
+}
+
+void rocksdb_merge(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr,
+ db->rep->Merge(options->rep, Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_merge_cf(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr,
+ db->rep->Merge(options->rep, column_family->rep,
+ Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_write(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_t* batch,
+ char** errptr) {
+ SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
+}
+
+char* rocksdb_get(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = db->rep->Get(options->rep, Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_get_cf(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = db->rep->Get(options->rep, column_family->rep,
+ Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+void rocksdb_multi_get(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ size_t num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ char** values_list, size_t* values_list_sizes,
+ char** errs) {
+ std::vector<Slice> keys(num_keys);
+ for (size_t i = 0; i < num_keys; i++) {
+ keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<std::string> values(num_keys);
+ std::vector<Status> statuses = db->rep->MultiGet(options->rep, keys, &values);
+ for (size_t i = 0; i < num_keys; i++) {
+ if (statuses[i].ok()) {
+ values_list[i] = CopyString(values[i]);
+ values_list_sizes[i] = values[i].size();
+ errs[i] = nullptr;
+ } else {
+ values_list[i] = nullptr;
+ values_list_sizes[i] = 0;
+ if (!statuses[i].IsNotFound()) {
+ errs[i] = strdup(statuses[i].ToString().c_str());
+ } else {
+ errs[i] = nullptr;
+ }
+ }
+ }
+}
+
+void rocksdb_multi_get_cf(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ const rocksdb_column_family_handle_t* const* column_families,
+ size_t num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ char** values_list, size_t* values_list_sizes,
+ char** errs) {
+ std::vector<Slice> keys(num_keys);
+ std::vector<ColumnFamilyHandle*> cfs(num_keys);
+ for (size_t i = 0; i < num_keys; i++) {
+ keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ cfs[i] = column_families[i]->rep;
+ }
+ std::vector<std::string> values(num_keys);
+ std::vector<Status> statuses = db->rep->MultiGet(options->rep, cfs, keys, &values);
+ for (size_t i = 0; i < num_keys; i++) {
+ if (statuses[i].ok()) {
+ values_list[i] = CopyString(values[i]);
+ values_list_sizes[i] = values[i].size();
+ errs[i] = nullptr;
+ } else {
+ values_list[i] = nullptr;
+ values_list_sizes[i] = 0;
+ if (!statuses[i].IsNotFound()) {
+ errs[i] = strdup(statuses[i].ToString().c_str());
+ } else {
+ errs[i] = nullptr;
+ }
+ }
+ }
+}
+
+rocksdb_iterator_t* rocksdb_create_iterator(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = db->rep->NewIterator(options->rep);
+ return result;
+}
+
+rocksdb_wal_iterator_t* rocksdb_get_updates_since(
+ rocksdb_t* db, uint64_t seq_number,
+ const rocksdb_wal_readoptions_t* options,
+ char** errptr) {
+ std::unique_ptr<TransactionLogIterator> iter;
+ TransactionLogIterator::ReadOptions ro;
+ if (options!=nullptr) {
+ ro = options->rep;
+ }
+ if (SaveError(errptr, db->rep->GetUpdatesSince(seq_number, &iter, ro))) {
+ return nullptr;
+ }
+ rocksdb_wal_iterator_t* result = new rocksdb_wal_iterator_t;
+ result->rep = iter.release();
+ return result;
+}
+
+void rocksdb_wal_iter_next(rocksdb_wal_iterator_t* iter) {
+ iter->rep->Next();
+}
+
+unsigned char rocksdb_wal_iter_valid(const rocksdb_wal_iterator_t* iter) {
+ return iter->rep->Valid();
+}
+
+void rocksdb_wal_iter_status (const rocksdb_wal_iterator_t* iter, char** errptr) {
+ SaveError(errptr, iter->rep->status());
+}
+
+void rocksdb_wal_iter_destroy (const rocksdb_wal_iterator_t* iter) {
+ delete iter->rep;
+ delete iter;
+}
+
+rocksdb_writebatch_t* rocksdb_wal_iter_get_batch (const rocksdb_wal_iterator_t* iter, uint64_t* seq) {
+ rocksdb_writebatch_t* result = rocksdb_writebatch_create();
+ BatchResult wal_batch = iter->rep->GetBatch();
+ result->rep = std::move(*wal_batch.writeBatchPtr);
+ if (seq != nullptr) {
+ *seq = wal_batch.sequence;
+ }
+ return result;
+}
+
+uint64_t rocksdb_get_latest_sequence_number (rocksdb_t *db) {
+ return db->rep->GetLatestSequenceNumber();
+}
+
+rocksdb_iterator_t* rocksdb_create_iterator_cf(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = db->rep->NewIterator(options->rep, column_family->rep);
+ return result;
+}
+
+void rocksdb_create_iterators(
+ rocksdb_t *db,
+ rocksdb_readoptions_t* opts,
+ rocksdb_column_family_handle_t** column_families,
+ rocksdb_iterator_t** iterators,
+ size_t size,
+ char** errptr) {
+ std::vector<ColumnFamilyHandle*> column_families_vec;
+ for (size_t i = 0; i < size; i++) {
+ column_families_vec.push_back(column_families[i]->rep);
+ }
+
+ std::vector<Iterator*> res;
+ Status status = db->rep->NewIterators(opts->rep, column_families_vec, &res);
+ assert(res.size() == size);
+ if (SaveError(errptr, status)) {
+ return;
+ }
+
+ for (size_t i = 0; i < size; i++) {
+ iterators[i] = new rocksdb_iterator_t;
+ iterators[i]->rep = res[i];
+ }
+}
+
+const rocksdb_snapshot_t* rocksdb_create_snapshot(
+ rocksdb_t* db) {
+ rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
+ result->rep = db->rep->GetSnapshot();
+ return result;
+}
+
+void rocksdb_release_snapshot(
+ rocksdb_t* db,
+ const rocksdb_snapshot_t* snapshot) {
+ db->rep->ReleaseSnapshot(snapshot->rep);
+ delete snapshot;
+}
+
+char* rocksdb_property_value(
+ rocksdb_t* db,
+ const char* propname) {
+ std::string tmp;
+ if (db->rep->GetProperty(Slice(propname), &tmp)) {
+ // We use strdup() since we expect human readable output.
+ return strdup(tmp.c_str());
+ } else {
+ return nullptr;
+ }
+}
+
+int rocksdb_property_int(
+ rocksdb_t* db,
+ const char* propname,
+ uint64_t *out_val) {
+ if (db->rep->GetIntProperty(Slice(propname), out_val)) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+int rocksdb_property_int_cf(
+ rocksdb_t* db,
+ rocksdb_column_family_handle_t* column_family,
+ const char* propname,
+ uint64_t *out_val) {
+ if (db->rep->GetIntProperty(column_family->rep, Slice(propname), out_val)) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+char* rocksdb_property_value_cf(
+ rocksdb_t* db,
+ rocksdb_column_family_handle_t* column_family,
+ const char* propname) {
+ std::string tmp;
+ if (db->rep->GetProperty(column_family->rep, Slice(propname), &tmp)) {
+ // We use strdup() since we expect human readable output.
+ return strdup(tmp.c_str());
+ } else {
+ return nullptr;
+ }
+}
+
+void rocksdb_approximate_sizes(
+ rocksdb_t* db,
+ int num_ranges,
+ const char* const* range_start_key, const size_t* range_start_key_len,
+ const char* const* range_limit_key, const size_t* range_limit_key_len,
+ uint64_t* sizes) {
+ Range* ranges = new Range[num_ranges];
+ for (int i = 0; i < num_ranges; i++) {
+ ranges[i].start = Slice(range_start_key[i], range_start_key_len[i]);
+ ranges[i].limit = Slice(range_limit_key[i], range_limit_key_len[i]);
+ }
+ db->rep->GetApproximateSizes(ranges, num_ranges, sizes);
+ delete[] ranges;
+}
+
+void rocksdb_approximate_sizes_cf(
+ rocksdb_t* db,
+ rocksdb_column_family_handle_t* column_family,
+ int num_ranges,
+ const char* const* range_start_key, const size_t* range_start_key_len,
+ const char* const* range_limit_key, const size_t* range_limit_key_len,
+ uint64_t* sizes) {
+ Range* ranges = new Range[num_ranges];
+ for (int i = 0; i < num_ranges; i++) {
+ ranges[i].start = Slice(range_start_key[i], range_start_key_len[i]);
+ ranges[i].limit = Slice(range_limit_key[i], range_limit_key_len[i]);
+ }
+ db->rep->GetApproximateSizes(column_family->rep, ranges, num_ranges, sizes);
+ delete[] ranges;
+}
+
+void rocksdb_delete_file(
+ rocksdb_t* db,
+ const char* name) {
+ db->rep->DeleteFile(name);
+}
+
+const rocksdb_livefiles_t* rocksdb_livefiles(
+ rocksdb_t* db) {
+ rocksdb_livefiles_t* result = new rocksdb_livefiles_t;
+ db->rep->GetLiveFilesMetaData(&result->rep);
+ return result;
+}
+
+void rocksdb_compact_range(
+ rocksdb_t* db,
+ const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len) {
+ Slice a, b;
+ db->rep->CompactRange(
+ CompactRangeOptions(),
+ // Pass nullptr Slice if corresponding "const char*" is nullptr
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
+}
+
+void rocksdb_compact_range_cf(
+ rocksdb_t* db,
+ rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len) {
+ Slice a, b;
+ db->rep->CompactRange(
+ CompactRangeOptions(), column_family->rep,
+ // Pass nullptr Slice if corresponding "const char*" is nullptr
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
+}
+
+void rocksdb_compact_range_opt(rocksdb_t* db, rocksdb_compactoptions_t* opt,
+ const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len) {
+ Slice a, b;
+ db->rep->CompactRange(
+ opt->rep,
+ // Pass nullptr Slice if corresponding "const char*" is nullptr
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
+}
+
+void rocksdb_compact_range_cf_opt(rocksdb_t* db,
+ rocksdb_column_family_handle_t* column_family,
+ rocksdb_compactoptions_t* opt,
+ const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len) {
+ Slice a, b;
+ db->rep->CompactRange(
+ opt->rep, column_family->rep,
+ // Pass nullptr Slice if corresponding "const char*" is nullptr
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
+}
+
+void rocksdb_flush(
+ rocksdb_t* db,
+ const rocksdb_flushoptions_t* options,
+ char** errptr) {
+ SaveError(errptr, db->rep->Flush(options->rep));
+}
+
+void rocksdb_flush_cf(
+ rocksdb_t* db,
+ const rocksdb_flushoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ char** errptr) {
+ SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
+}
+
+void rocksdb_disable_file_deletions(
+ rocksdb_t* db,
+ char** errptr) {
+ SaveError(errptr, db->rep->DisableFileDeletions());
+}
+
+void rocksdb_enable_file_deletions(
+ rocksdb_t* db,
+ unsigned char force,
+ char** errptr) {
+ SaveError(errptr, db->rep->EnableFileDeletions(force));
+}
+
+void rocksdb_destroy_db(
+ const rocksdb_options_t* options,
+ const char* name,
+ char** errptr) {
+ SaveError(errptr, DestroyDB(name, options->rep));
+}
+
+void rocksdb_repair_db(
+ const rocksdb_options_t* options,
+ const char* name,
+ char** errptr) {
+ SaveError(errptr, RepairDB(name, options->rep));
+}
+
+void rocksdb_iter_destroy(rocksdb_iterator_t* iter) {
+ delete iter->rep;
+ delete iter;
+}
+
+unsigned char rocksdb_iter_valid(const rocksdb_iterator_t* iter) {
+ return iter->rep->Valid();
+}
+
+void rocksdb_iter_seek_to_first(rocksdb_iterator_t* iter) {
+ iter->rep->SeekToFirst();
+}
+
+void rocksdb_iter_seek_to_last(rocksdb_iterator_t* iter) {
+ iter->rep->SeekToLast();
+}
+
+void rocksdb_iter_seek(rocksdb_iterator_t* iter, const char* k, size_t klen) {
+ iter->rep->Seek(Slice(k, klen));
+}
+
+void rocksdb_iter_seek_for_prev(rocksdb_iterator_t* iter, const char* k,
+ size_t klen) {
+ iter->rep->SeekForPrev(Slice(k, klen));
+}
+
+void rocksdb_iter_next(rocksdb_iterator_t* iter) {
+ iter->rep->Next();
+}
+
+void rocksdb_iter_prev(rocksdb_iterator_t* iter) {
+ iter->rep->Prev();
+}
+
+const char* rocksdb_iter_key(const rocksdb_iterator_t* iter, size_t* klen) {
+ Slice s = iter->rep->key();
+ *klen = s.size();
+ return s.data();
+}
+
+const char* rocksdb_iter_value(const rocksdb_iterator_t* iter, size_t* vlen) {
+ Slice s = iter->rep->value();
+ *vlen = s.size();
+ return s.data();
+}
+
+void rocksdb_iter_get_error(const rocksdb_iterator_t* iter, char** errptr) {
+ SaveError(errptr, iter->rep->status());
+}
+
+rocksdb_writebatch_t* rocksdb_writebatch_create() {
+ return new rocksdb_writebatch_t;
+}
+
+rocksdb_writebatch_t* rocksdb_writebatch_create_from(const char* rep,
+ size_t size) {
+ rocksdb_writebatch_t* b = new rocksdb_writebatch_t;
+ b->rep = WriteBatch(std::string(rep, size));
+ return b;
+}
+
+void rocksdb_writebatch_destroy(rocksdb_writebatch_t* b) {
+ delete b;
+}
+
+void rocksdb_writebatch_clear(rocksdb_writebatch_t* b) {
+ b->rep.Clear();
+}
+
+int rocksdb_writebatch_count(rocksdb_writebatch_t* b) {
+ return b->rep.Count();
+}
+
+void rocksdb_writebatch_put(
+ rocksdb_writebatch_t* b,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep.Put(Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_put_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep.Put(column_family->rep, Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_putv(
+ rocksdb_writebatch_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep.Put(SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_putv_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep.Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_merge(
+ rocksdb_writebatch_t* b,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep.Merge(Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_merge_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep.Merge(column_family->rep, Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_mergev(
+ rocksdb_writebatch_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep.Merge(SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_mergev_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep.Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_delete(
+ rocksdb_writebatch_t* b,
+ const char* key, size_t klen) {
+ b->rep.Delete(Slice(key, klen));
+}
+
+void rocksdb_writebatch_delete_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen) {
+ b->rep.Delete(column_family->rep, Slice(key, klen));
+}
+
+void rocksdb_writebatch_deletev(
+ rocksdb_writebatch_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ b->rep.Delete(SliceParts(key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_deletev_cf(
+ rocksdb_writebatch_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_delete_range(rocksdb_writebatch_t* b,
+ const char* start_key,
+ size_t start_key_len, const char* end_key,
+ size_t end_key_len) {
+ b->rep.DeleteRange(Slice(start_key, start_key_len),
+ Slice(end_key, end_key_len));
+}
+
+void rocksdb_writebatch_delete_range_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* end_key,
+ size_t end_key_len) {
+ b->rep.DeleteRange(column_family->rep, Slice(start_key, start_key_len),
+ Slice(end_key, end_key_len));
+}
+
+void rocksdb_writebatch_delete_rangev(rocksdb_writebatch_t* b, int num_keys,
+ const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes,
+ const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes) {
+ std::vector<Slice> start_key_slices(num_keys);
+ std::vector<Slice> end_key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
+ end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
+ }
+ b->rep.DeleteRange(SliceParts(start_key_slices.data(), num_keys),
+ SliceParts(end_key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_delete_rangev_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes) {
+ std::vector<Slice> start_key_slices(num_keys);
+ std::vector<Slice> end_key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
+ end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
+ }
+ b->rep.DeleteRange(column_family->rep,
+ SliceParts(start_key_slices.data(), num_keys),
+ SliceParts(end_key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_put_log_data(
+ rocksdb_writebatch_t* b,
+ const char* blob, size_t len) {
+ b->rep.PutLogData(Slice(blob, len));
+}
+
+class H : public WriteBatch::Handler {
+ public:
+ void* state_;
+ void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
+ void (*deleted_)(void*, const char* k, size_t klen);
+ void Put(const Slice& key, const Slice& value) override {
+ (*put_)(state_, key.data(), key.size(), value.data(), value.size());
+ }
+ void Delete(const Slice& key) override {
+ (*deleted_)(state_, key.data(), key.size());
+ }
+};
+
+void rocksdb_writebatch_iterate(
+ rocksdb_writebatch_t* b,
+ void* state,
+ void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
+ void (*deleted)(void*, const char* k, size_t klen)) {
+ H handler;
+ handler.state_ = state;
+ handler.put_ = put;
+ handler.deleted_ = deleted;
+ b->rep.Iterate(&handler);
+}
+
+const char* rocksdb_writebatch_data(rocksdb_writebatch_t* b, size_t* size) {
+ *size = b->rep.GetDataSize();
+ return b->rep.Data().c_str();
+}
+
+void rocksdb_writebatch_set_save_point(rocksdb_writebatch_t* b) {
+ b->rep.SetSavePoint();
+}
+
+void rocksdb_writebatch_rollback_to_save_point(rocksdb_writebatch_t* b,
+ char** errptr) {
+ SaveError(errptr, b->rep.RollbackToSavePoint());
+}
+
+void rocksdb_writebatch_pop_save_point(rocksdb_writebatch_t* b, char** errptr) {
+ SaveError(errptr, b->rep.PopSavePoint());
+}
+
+rocksdb_writebatch_wi_t* rocksdb_writebatch_wi_create(size_t reserved_bytes, unsigned char overwrite_key) {
+ rocksdb_writebatch_wi_t* b = new rocksdb_writebatch_wi_t;
+ b->rep = new WriteBatchWithIndex(BytewiseComparator(), reserved_bytes, overwrite_key);
+ return b;
+}
+
+void rocksdb_writebatch_wi_destroy(rocksdb_writebatch_wi_t* b) {
+ if (b->rep) {
+ delete b->rep;
+ }
+ delete b;
+}
+
+void rocksdb_writebatch_wi_clear(rocksdb_writebatch_wi_t* b) {
+ b->rep->Clear();
+}
+
+int rocksdb_writebatch_wi_count(rocksdb_writebatch_wi_t* b) {
+ return b->rep->GetWriteBatch()->Count();
+}
+
+void rocksdb_writebatch_wi_put(
+ rocksdb_writebatch_wi_t* b,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep->Put(Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_wi_put_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep->Put(column_family->rep, Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_wi_putv(
+ rocksdb_writebatch_wi_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep->Put(SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_wi_putv_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep->Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_wi_merge(
+ rocksdb_writebatch_wi_t* b,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep->Merge(Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_wi_merge_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen,
+ const char* val, size_t vlen) {
+ b->rep->Merge(column_family->rep, Slice(key, klen), Slice(val, vlen));
+}
+
+void rocksdb_writebatch_wi_mergev(
+ rocksdb_writebatch_wi_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep->Merge(SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_wi_mergev_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ std::vector<Slice> value_slices(num_values);
+ for (int i = 0; i < num_values; i++) {
+ value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
+ }
+ b->rep->Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
+ SliceParts(value_slices.data(), num_values));
+}
+
+void rocksdb_writebatch_wi_delete(
+ rocksdb_writebatch_wi_t* b,
+ const char* key, size_t klen) {
+ b->rep->Delete(Slice(key, klen));
+}
+
+void rocksdb_writebatch_wi_delete_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen) {
+ b->rep->Delete(column_family->rep, Slice(key, klen));
+}
+
+void rocksdb_writebatch_wi_deletev(
+ rocksdb_writebatch_wi_t* b,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ b->rep->Delete(SliceParts(key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_wi_deletev_cf(
+ rocksdb_writebatch_wi_t* b,
+ rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes) {
+ std::vector<Slice> key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
+ }
+ b->rep->Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_wi_delete_range(rocksdb_writebatch_wi_t* b,
+ const char* start_key,
+ size_t start_key_len, const char* end_key,
+ size_t end_key_len) {
+ b->rep->DeleteRange(Slice(start_key, start_key_len),
+ Slice(end_key, end_key_len));
+}
+
+void rocksdb_writebatch_wi_delete_range_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* end_key,
+ size_t end_key_len) {
+ b->rep->DeleteRange(column_family->rep, Slice(start_key, start_key_len),
+ Slice(end_key, end_key_len));
+}
+
+void rocksdb_writebatch_wi_delete_rangev(rocksdb_writebatch_wi_t* b, int num_keys,
+ const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes,
+ const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes) {
+ std::vector<Slice> start_key_slices(num_keys);
+ std::vector<Slice> end_key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
+ end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
+ }
+ b->rep->DeleteRange(SliceParts(start_key_slices.data(), num_keys),
+ SliceParts(end_key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_wi_delete_rangev_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes) {
+ std::vector<Slice> start_key_slices(num_keys);
+ std::vector<Slice> end_key_slices(num_keys);
+ for (int i = 0; i < num_keys; i++) {
+ start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
+ end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
+ }
+ b->rep->DeleteRange(column_family->rep,
+ SliceParts(start_key_slices.data(), num_keys),
+ SliceParts(end_key_slices.data(), num_keys));
+}
+
+void rocksdb_writebatch_wi_put_log_data(
+ rocksdb_writebatch_wi_t* b,
+ const char* blob, size_t len) {
+ b->rep->PutLogData(Slice(blob, len));
+}
+
+void rocksdb_writebatch_wi_iterate(
+ rocksdb_writebatch_wi_t* b,
+ void* state,
+ void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
+ void (*deleted)(void*, const char* k, size_t klen)) {
+ H handler;
+ handler.state_ = state;
+ handler.put_ = put;
+ handler.deleted_ = deleted;
+ b->rep->GetWriteBatch()->Iterate(&handler);
+}
+
+const char* rocksdb_writebatch_wi_data(rocksdb_writebatch_wi_t* b, size_t* size) {
+ WriteBatch* wb = b->rep->GetWriteBatch();
+ *size = wb->GetDataSize();
+ return wb->Data().c_str();
+}
+
+void rocksdb_writebatch_wi_set_save_point(rocksdb_writebatch_wi_t* b) {
+ b->rep->SetSavePoint();
+}
+
+void rocksdb_writebatch_wi_rollback_to_save_point(rocksdb_writebatch_wi_t* b,
+ char** errptr) {
+ SaveError(errptr, b->rep->RollbackToSavePoint());
+}
+
+rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_iterator_t* base_iterator) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = wbwi->rep->NewIteratorWithBase(base_iterator->rep);
+ delete base_iterator;
+ return result;
+}
+
+rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
+ rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
+ rocksdb_column_family_handle_t* column_family) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep =
+ wbwi->rep->NewIteratorWithBase(column_family->rep, base_iterator->rep);
+ delete base_iterator;
+ return result;
+}
+
+char* rocksdb_writebatch_wi_get_from_batch(
+ rocksdb_writebatch_wi_t* wbwi,
+ const rocksdb_options_t* options,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = wbwi->rep->GetFromBatch(options->rep, Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_writebatch_wi_get_from_batch_cf(
+ rocksdb_writebatch_wi_t* wbwi,
+ const rocksdb_options_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = wbwi->rep->GetFromBatch(column_family->rep, options->rep,
+ Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_writebatch_wi_get_from_batch_and_db(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep, Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep, column_family->rep,
+ Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+void rocksdb_write_writebatch_wi(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_wi_t* wbwi,
+ char** errptr) {
+ WriteBatch* wb = wbwi->rep->GetWriteBatch();
+ SaveError(errptr, db->rep->Write(options->rep, wb));
+}
+
+rocksdb_block_based_table_options_t*
+rocksdb_block_based_options_create() {
+ return new rocksdb_block_based_table_options_t;
+}
+
+void rocksdb_block_based_options_destroy(
+ rocksdb_block_based_table_options_t* options) {
+ delete options;
+}
+
+void rocksdb_block_based_options_set_block_size(
+ rocksdb_block_based_table_options_t* options, size_t block_size) {
+ options->rep.block_size = block_size;
+}
+
+void rocksdb_block_based_options_set_block_size_deviation(
+ rocksdb_block_based_table_options_t* options, int block_size_deviation) {
+ options->rep.block_size_deviation = block_size_deviation;
+}
+
+void rocksdb_block_based_options_set_block_restart_interval(
+ rocksdb_block_based_table_options_t* options, int block_restart_interval) {
+ options->rep.block_restart_interval = block_restart_interval;
+}
+
+void rocksdb_block_based_options_set_index_block_restart_interval(
+ rocksdb_block_based_table_options_t* options, int index_block_restart_interval) {
+ options->rep.index_block_restart_interval = index_block_restart_interval;
+}
+
+void rocksdb_block_based_options_set_metadata_block_size(
+ rocksdb_block_based_table_options_t* options, uint64_t metadata_block_size) {
+ options->rep.metadata_block_size = metadata_block_size;
+}
+
+void rocksdb_block_based_options_set_partition_filters(
+ rocksdb_block_based_table_options_t* options, unsigned char partition_filters) {
+ options->rep.partition_filters = partition_filters;
+}
+
+void rocksdb_block_based_options_set_use_delta_encoding(
+ rocksdb_block_based_table_options_t* options, unsigned char use_delta_encoding) {
+ options->rep.use_delta_encoding = use_delta_encoding;
+}
+
+void rocksdb_block_based_options_set_filter_policy(
+ rocksdb_block_based_table_options_t* options,
+ rocksdb_filterpolicy_t* filter_policy) {
+ options->rep.filter_policy.reset(filter_policy);
+}
+
+void rocksdb_block_based_options_set_no_block_cache(
+ rocksdb_block_based_table_options_t* options,
+ unsigned char no_block_cache) {
+ options->rep.no_block_cache = no_block_cache;
+}
+
+void rocksdb_block_based_options_set_block_cache(
+ rocksdb_block_based_table_options_t* options,
+ rocksdb_cache_t* block_cache) {
+ if (block_cache) {
+ options->rep.block_cache = block_cache->rep;
+ }
+}
+
+void rocksdb_block_based_options_set_block_cache_compressed(
+ rocksdb_block_based_table_options_t* options,
+ rocksdb_cache_t* block_cache_compressed) {
+ if (block_cache_compressed) {
+ options->rep.block_cache_compressed = block_cache_compressed->rep;
+ }
+}
+
+void rocksdb_block_based_options_set_whole_key_filtering(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.whole_key_filtering = v;
+}
+
+void rocksdb_block_based_options_set_format_version(
+ rocksdb_block_based_table_options_t* options, int v) {
+ options->rep.format_version = v;
+}
+
+void rocksdb_block_based_options_set_index_type(
+ rocksdb_block_based_table_options_t* options, int v) {
+ options->rep.index_type = static_cast<BlockBasedTableOptions::IndexType>(v);
+}
+
+void rocksdb_block_based_options_set_data_block_index_type(
+ rocksdb_block_based_table_options_t* options, int v) {
+ options->rep.data_block_index_type =
+ static_cast<BlockBasedTableOptions::DataBlockIndexType>(v);
+}
+
+void rocksdb_block_based_options_set_data_block_hash_ratio(
+ rocksdb_block_based_table_options_t* options, double v) {
+ options->rep.data_block_hash_table_util_ratio = v;
+}
+
+void rocksdb_block_based_options_set_hash_index_allow_collision(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.hash_index_allow_collision = v;
+}
+
+void rocksdb_block_based_options_set_cache_index_and_filter_blocks(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.cache_index_and_filter_blocks = v;
+}
+
+void rocksdb_block_based_options_set_cache_index_and_filter_blocks_with_high_priority(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.cache_index_and_filter_blocks_with_high_priority = v;
+}
+
+void rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.pin_l0_filter_and_index_blocks_in_cache = v;
+}
+
+void rocksdb_block_based_options_set_pin_top_level_index_and_filter(
+ rocksdb_block_based_table_options_t* options, unsigned char v) {
+ options->rep.pin_top_level_index_and_filter = v;
+}
+
+void rocksdb_options_set_block_based_table_factory(
+ rocksdb_options_t *opt,
+ rocksdb_block_based_table_options_t* table_options) {
+ if (table_options) {
+ opt->rep.table_factory.reset(
+ ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(table_options->rep));
+ }
+}
+
+rocksdb_cuckoo_table_options_t*
+rocksdb_cuckoo_options_create() {
+ return new rocksdb_cuckoo_table_options_t;
+}
+
+void rocksdb_cuckoo_options_destroy(
+ rocksdb_cuckoo_table_options_t* options) {
+ delete options;
+}
+
+void rocksdb_cuckoo_options_set_hash_ratio(
+ rocksdb_cuckoo_table_options_t* options, double v) {
+ options->rep.hash_table_ratio = v;
+}
+
+void rocksdb_cuckoo_options_set_max_search_depth(
+ rocksdb_cuckoo_table_options_t* options, uint32_t v) {
+ options->rep.max_search_depth = v;
+}
+
+void rocksdb_cuckoo_options_set_cuckoo_block_size(
+ rocksdb_cuckoo_table_options_t* options, uint32_t v) {
+ options->rep.cuckoo_block_size = v;
+}
+
+void rocksdb_cuckoo_options_set_identity_as_first_hash(
+ rocksdb_cuckoo_table_options_t* options, unsigned char v) {
+ options->rep.identity_as_first_hash = v;
+}
+
+void rocksdb_cuckoo_options_set_use_module_hash(
+ rocksdb_cuckoo_table_options_t* options, unsigned char v) {
+ options->rep.use_module_hash = v;
+}
+
+void rocksdb_options_set_cuckoo_table_factory(
+ rocksdb_options_t *opt,
+ rocksdb_cuckoo_table_options_t* table_options) {
+ if (table_options) {
+ opt->rep.table_factory.reset(
+ ROCKSDB_NAMESPACE::NewCuckooTableFactory(table_options->rep));
+ }
+}
+
+void rocksdb_set_options(
+ rocksdb_t* db, int count, const char* const keys[], const char* const values[], char** errptr) {
+ std::unordered_map<std::string, std::string> options_map;
+ for (int i=0; i<count; i++)
+ options_map[keys[i]] = values[i];
+ SaveError(errptr,
+ db->rep->SetOptions(options_map));
+ }
+
+void rocksdb_set_options_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count, const char* const keys[], const char* const values[], char** errptr) {
+ std::unordered_map<std::string, std::string> options_map;
+ for (int i=0; i<count; i++)
+ options_map[keys[i]] = values[i];
+ SaveError(errptr,
+ db->rep->SetOptions(handle->rep, options_map));
+ }
+
+rocksdb_options_t* rocksdb_options_create() {
+ return new rocksdb_options_t;
+}
+
+void rocksdb_options_destroy(rocksdb_options_t* options) {
+ delete options;
+}
+
+void rocksdb_options_increase_parallelism(
+ rocksdb_options_t* opt, int total_threads) {
+ opt->rep.IncreaseParallelism(total_threads);
+}
+
+void rocksdb_options_optimize_for_point_lookup(
+ rocksdb_options_t* opt, uint64_t block_cache_size_mb) {
+ opt->rep.OptimizeForPointLookup(block_cache_size_mb);
+}
+
+void rocksdb_options_optimize_level_style_compaction(
+ rocksdb_options_t* opt, uint64_t memtable_memory_budget) {
+ opt->rep.OptimizeLevelStyleCompaction(memtable_memory_budget);
+}
+
+void rocksdb_options_optimize_universal_style_compaction(
+ rocksdb_options_t* opt, uint64_t memtable_memory_budget) {
+ opt->rep.OptimizeUniversalStyleCompaction(memtable_memory_budget);
+}
+
+void rocksdb_options_set_allow_ingest_behind(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.allow_ingest_behind = v;
+}
+
+void rocksdb_options_set_compaction_filter(
+ rocksdb_options_t* opt,
+ rocksdb_compactionfilter_t* filter) {
+ opt->rep.compaction_filter = filter;
+}
+
+void rocksdb_options_set_compaction_filter_factory(
+ rocksdb_options_t* opt, rocksdb_compactionfilterfactory_t* factory) {
+ opt->rep.compaction_filter_factory =
+ std::shared_ptr<CompactionFilterFactory>(factory);
+}
+
+void rocksdb_options_compaction_readahead_size(
+ rocksdb_options_t* opt, size_t s) {
+ opt->rep.compaction_readahead_size = s;
+}
+
+void rocksdb_options_set_comparator(
+ rocksdb_options_t* opt,
+ rocksdb_comparator_t* cmp) {
+ opt->rep.comparator = cmp;
+}
+
+void rocksdb_options_set_merge_operator(
+ rocksdb_options_t* opt,
+ rocksdb_mergeoperator_t* merge_operator) {
+ opt->rep.merge_operator = std::shared_ptr<MergeOperator>(merge_operator);
+}
+
+
+void rocksdb_options_set_create_if_missing(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.create_if_missing = v;
+}
+
+void rocksdb_options_set_create_missing_column_families(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.create_missing_column_families = v;
+}
+
+void rocksdb_options_set_error_if_exists(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.error_if_exists = v;
+}
+
+void rocksdb_options_set_paranoid_checks(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.paranoid_checks = v;
+}
+
+void rocksdb_options_set_db_paths(rocksdb_options_t* opt,
+ const rocksdb_dbpath_t** dbpath_values,
+ size_t num_paths) {
+ std::vector<DbPath> db_paths(num_paths);
+ for (size_t i = 0; i < num_paths; ++i) {
+ db_paths[i] = dbpath_values[i]->rep;
+ }
+ opt->rep.db_paths = db_paths;
+}
+
+void rocksdb_options_set_env(rocksdb_options_t* opt, rocksdb_env_t* env) {
+ opt->rep.env = (env ? env->rep : nullptr);
+}
+
+void rocksdb_options_set_info_log(rocksdb_options_t* opt, rocksdb_logger_t* l) {
+ if (l) {
+ opt->rep.info_log = l->rep;
+ }
+}
+
+void rocksdb_options_set_info_log_level(
+ rocksdb_options_t* opt, int v) {
+ opt->rep.info_log_level = static_cast<InfoLogLevel>(v);
+}
+
+void rocksdb_options_set_db_write_buffer_size(rocksdb_options_t* opt,
+ size_t s) {
+ opt->rep.db_write_buffer_size = s;
+}
+
+void rocksdb_options_set_write_buffer_size(rocksdb_options_t* opt, size_t s) {
+ opt->rep.write_buffer_size = s;
+}
+
+void rocksdb_options_set_max_open_files(rocksdb_options_t* opt, int n) {
+ opt->rep.max_open_files = n;
+}
+
+void rocksdb_options_set_max_file_opening_threads(rocksdb_options_t* opt, int n) {
+ opt->rep.max_file_opening_threads = n;
+}
+
+void rocksdb_options_set_max_total_wal_size(rocksdb_options_t* opt, uint64_t n) {
+ opt->rep.max_total_wal_size = n;
+}
+
+void rocksdb_options_set_target_file_size_base(
+ rocksdb_options_t* opt, uint64_t n) {
+ opt->rep.target_file_size_base = n;
+}
+
+void rocksdb_options_set_target_file_size_multiplier(
+ rocksdb_options_t* opt, int n) {
+ opt->rep.target_file_size_multiplier = n;
+}
+
+void rocksdb_options_set_max_bytes_for_level_base(
+ rocksdb_options_t* opt, uint64_t n) {
+ opt->rep.max_bytes_for_level_base = n;
+}
+
+void rocksdb_options_set_level_compaction_dynamic_level_bytes(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.level_compaction_dynamic_level_bytes = v;
+}
+
+void rocksdb_options_set_max_bytes_for_level_multiplier(rocksdb_options_t* opt,
+ double n) {
+ opt->rep.max_bytes_for_level_multiplier = n;
+}
+
+void rocksdb_options_set_max_compaction_bytes(rocksdb_options_t* opt,
+ uint64_t n) {
+ opt->rep.max_compaction_bytes = n;
+}
+
+void rocksdb_options_set_max_bytes_for_level_multiplier_additional(
+ rocksdb_options_t* opt, int* level_values, size_t num_levels) {
+ opt->rep.max_bytes_for_level_multiplier_additional.resize(num_levels);
+ for (size_t i = 0; i < num_levels; ++i) {
+ opt->rep.max_bytes_for_level_multiplier_additional[i] = level_values[i];
+ }
+}
+
+void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
+ opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+}
+
+void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt,
+ unsigned char val) {
+ opt->rep.skip_stats_update_on_db_open = val;
+}
+
+void rocksdb_options_set_skip_checking_sst_file_sizes_on_db_open(
+ rocksdb_options_t* opt, unsigned char val) {
+ opt->rep.skip_checking_sst_file_sizes_on_db_open = val;
+}
+
+void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
+ opt->rep.num_levels = n;
+}
+
+void rocksdb_options_set_level0_file_num_compaction_trigger(
+ rocksdb_options_t* opt, int n) {
+ opt->rep.level0_file_num_compaction_trigger = n;
+}
+
+void rocksdb_options_set_level0_slowdown_writes_trigger(
+ rocksdb_options_t* opt, int n) {
+ opt->rep.level0_slowdown_writes_trigger = n;
+}
+
+void rocksdb_options_set_level0_stop_writes_trigger(
+ rocksdb_options_t* opt, int n) {
+ opt->rep.level0_stop_writes_trigger = n;
+}
+
+void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* /*opt*/,
+ int /*n*/) {}
+
+void rocksdb_options_set_wal_recovery_mode(rocksdb_options_t* opt,int mode) {
+ opt->rep.wal_recovery_mode = static_cast<WALRecoveryMode>(mode);
+}
+
+void rocksdb_options_set_compression(rocksdb_options_t* opt, int t) {
+ opt->rep.compression = static_cast<CompressionType>(t);
+}
+
+void rocksdb_options_set_compression_per_level(rocksdb_options_t* opt,
+ int* level_values,
+ size_t num_levels) {
+ opt->rep.compression_per_level.resize(num_levels);
+ for (size_t i = 0; i < num_levels; ++i) {
+ opt->rep.compression_per_level[i] =
+ static_cast<CompressionType>(level_values[i]);
+ }
+}
+
+void rocksdb_options_set_bottommost_compression_options(rocksdb_options_t* opt,
+ int w_bits, int level,
+ int strategy,
+ int max_dict_bytes,
+ bool enabled) {
+ opt->rep.bottommost_compression_opts.window_bits = w_bits;
+ opt->rep.bottommost_compression_opts.level = level;
+ opt->rep.bottommost_compression_opts.strategy = strategy;
+ opt->rep.bottommost_compression_opts.max_dict_bytes = max_dict_bytes;
+ opt->rep.bottommost_compression_opts.enabled = enabled;
+}
+
+void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits,
+ int level, int strategy,
+ int max_dict_bytes) {
+ opt->rep.compression_opts.window_bits = w_bits;
+ opt->rep.compression_opts.level = level;
+ opt->rep.compression_opts.strategy = strategy;
+ opt->rep.compression_opts.max_dict_bytes = max_dict_bytes;
+}
+
+void rocksdb_options_set_prefix_extractor(
+ rocksdb_options_t* opt, rocksdb_slicetransform_t* prefix_extractor) {
+ opt->rep.prefix_extractor.reset(prefix_extractor);
+}
+
+void rocksdb_options_set_use_fsync(
+ rocksdb_options_t* opt, int use_fsync) {
+ opt->rep.use_fsync = use_fsync;
+}
+
+void rocksdb_options_set_db_log_dir(
+ rocksdb_options_t* opt, const char* db_log_dir) {
+ opt->rep.db_log_dir = db_log_dir;
+}
+
+void rocksdb_options_set_wal_dir(
+ rocksdb_options_t* opt, const char* v) {
+ opt->rep.wal_dir = v;
+}
+
+void rocksdb_options_set_WAL_ttl_seconds(rocksdb_options_t* opt, uint64_t ttl) {
+ opt->rep.WAL_ttl_seconds = ttl;
+}
+
+void rocksdb_options_set_WAL_size_limit_MB(
+ rocksdb_options_t* opt, uint64_t limit) {
+ opt->rep.WAL_size_limit_MB = limit;
+}
+
+void rocksdb_options_set_manifest_preallocation_size(
+ rocksdb_options_t* opt, size_t v) {
+ opt->rep.manifest_preallocation_size = v;
+}
+
+// noop
+void rocksdb_options_set_purge_redundant_kvs_while_flush(
+ rocksdb_options_t* /*opt*/, unsigned char /*v*/) {}
+
+void rocksdb_options_set_use_direct_reads(rocksdb_options_t* opt,
+ unsigned char v) {
+ opt->rep.use_direct_reads = v;
+}
+
+void rocksdb_options_set_use_direct_io_for_flush_and_compaction(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.use_direct_io_for_flush_and_compaction = v;
+}
+
+void rocksdb_options_set_allow_mmap_reads(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.allow_mmap_reads = v;
+}
+
+void rocksdb_options_set_allow_mmap_writes(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.allow_mmap_writes = v;
+}
+
+void rocksdb_options_set_is_fd_close_on_exec(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.is_fd_close_on_exec = v;
+}
+
+void rocksdb_options_set_skip_log_error_on_recovery(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.skip_log_error_on_recovery = v;
+}
+
+void rocksdb_options_set_stats_dump_period_sec(
+ rocksdb_options_t* opt, unsigned int v) {
+ opt->rep.stats_dump_period_sec = v;
+}
+
+void rocksdb_options_set_advise_random_on_open(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.advise_random_on_open = v;
+}
+
+void rocksdb_options_set_access_hint_on_compaction_start(
+ rocksdb_options_t* opt, int v) {
+ switch(v) {
+ case 0:
+ opt->rep.access_hint_on_compaction_start =
+ ROCKSDB_NAMESPACE::Options::NONE;
+ break;
+ case 1:
+ opt->rep.access_hint_on_compaction_start =
+ ROCKSDB_NAMESPACE::Options::NORMAL;
+ break;
+ case 2:
+ opt->rep.access_hint_on_compaction_start =
+ ROCKSDB_NAMESPACE::Options::SEQUENTIAL;
+ break;
+ case 3:
+ opt->rep.access_hint_on_compaction_start =
+ ROCKSDB_NAMESPACE::Options::WILLNEED;
+ break;
+ }
+}
+
+void rocksdb_options_set_use_adaptive_mutex(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.use_adaptive_mutex = v;
+}
+
+void rocksdb_options_set_wal_bytes_per_sync(
+ rocksdb_options_t* opt, uint64_t v) {
+ opt->rep.wal_bytes_per_sync = v;
+}
+
+void rocksdb_options_set_bytes_per_sync(
+ rocksdb_options_t* opt, uint64_t v) {
+ opt->rep.bytes_per_sync = v;
+}
+
+void rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t* opt,
+ uint64_t v) {
+ opt->rep.writable_file_max_buffer_size = static_cast<size_t>(v);
+}
+
+void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t* opt,
+ unsigned char v) {
+ opt->rep.allow_concurrent_memtable_write = v;
+}
+
+void rocksdb_options_set_enable_write_thread_adaptive_yield(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.enable_write_thread_adaptive_yield = v;
+}
+
+void rocksdb_options_set_max_sequential_skip_in_iterations(
+ rocksdb_options_t* opt, uint64_t v) {
+ opt->rep.max_sequential_skip_in_iterations = v;
+}
+
+void rocksdb_options_set_max_write_buffer_number(rocksdb_options_t* opt, int n) {
+ opt->rep.max_write_buffer_number = n;
+}
+
+void rocksdb_options_set_min_write_buffer_number_to_merge(rocksdb_options_t* opt, int n) {
+ opt->rep.min_write_buffer_number_to_merge = n;
+}
+
+void rocksdb_options_set_max_write_buffer_number_to_maintain(
+ rocksdb_options_t* opt, int n) {
+ opt->rep.max_write_buffer_number_to_maintain = n;
+}
+
+void rocksdb_options_set_max_write_buffer_size_to_maintain(
+ rocksdb_options_t* opt, int64_t n) {
+ opt->rep.max_write_buffer_size_to_maintain = n;
+}
+
+void rocksdb_options_set_enable_pipelined_write(rocksdb_options_t* opt,
+ unsigned char v) {
+ opt->rep.enable_pipelined_write = v;
+}
+
+void rocksdb_options_set_unordered_write(rocksdb_options_t* opt,
+ unsigned char v) {
+ opt->rep.unordered_write = v;
+}
+
+void rocksdb_options_set_max_subcompactions(rocksdb_options_t* opt,
+ uint32_t n) {
+ opt->rep.max_subcompactions = n;
+}
+
+void rocksdb_options_set_max_background_jobs(rocksdb_options_t* opt, int n) {
+ opt->rep.max_background_jobs = n;
+}
+
+void rocksdb_options_set_max_background_compactions(rocksdb_options_t* opt, int n) {
+ opt->rep.max_background_compactions = n;
+}
+
+void rocksdb_options_set_base_background_compactions(rocksdb_options_t* opt,
+ int n) {
+ opt->rep.base_background_compactions = n;
+}
+
+void rocksdb_options_set_max_background_flushes(rocksdb_options_t* opt, int n) {
+ opt->rep.max_background_flushes = n;
+}
+
+void rocksdb_options_set_max_log_file_size(rocksdb_options_t* opt, size_t v) {
+ opt->rep.max_log_file_size = v;
+}
+
+void rocksdb_options_set_log_file_time_to_roll(rocksdb_options_t* opt, size_t v) {
+ opt->rep.log_file_time_to_roll = v;
+}
+
+void rocksdb_options_set_keep_log_file_num(rocksdb_options_t* opt, size_t v) {
+ opt->rep.keep_log_file_num = v;
+}
+
+void rocksdb_options_set_recycle_log_file_num(rocksdb_options_t* opt,
+ size_t v) {
+ opt->rep.recycle_log_file_num = v;
+}
+
+void rocksdb_options_set_soft_rate_limit(rocksdb_options_t* opt, double v) {
+ opt->rep.soft_rate_limit = v;
+}
+
+void rocksdb_options_set_hard_rate_limit(rocksdb_options_t* opt, double v) {
+ opt->rep.hard_rate_limit = v;
+}
+
+void rocksdb_options_set_soft_pending_compaction_bytes_limit(rocksdb_options_t* opt, size_t v) {
+ opt->rep.soft_pending_compaction_bytes_limit = v;
+}
+
+void rocksdb_options_set_hard_pending_compaction_bytes_limit(rocksdb_options_t* opt, size_t v) {
+ opt->rep.hard_pending_compaction_bytes_limit = v;
+}
+
+void rocksdb_options_set_rate_limit_delay_max_milliseconds(
+ rocksdb_options_t* opt, unsigned int v) {
+ opt->rep.rate_limit_delay_max_milliseconds = v;
+}
+
+void rocksdb_options_set_max_manifest_file_size(
+ rocksdb_options_t* opt, size_t v) {
+ opt->rep.max_manifest_file_size = v;
+}
+
+void rocksdb_options_set_table_cache_numshardbits(
+ rocksdb_options_t* opt, int v) {
+ opt->rep.table_cache_numshardbits = v;
+}
+
+void rocksdb_options_set_table_cache_remove_scan_count_limit(
+ rocksdb_options_t* /*opt*/, int /*v*/) {
+ // this option is deprecated
+}
+
+void rocksdb_options_set_arena_block_size(
+ rocksdb_options_t* opt, size_t v) {
+ opt->rep.arena_block_size = v;
+}
+
+void rocksdb_options_set_disable_auto_compactions(rocksdb_options_t* opt, int disable) {
+ opt->rep.disable_auto_compactions = disable;
+}
+
+void rocksdb_options_set_optimize_filters_for_hits(rocksdb_options_t* opt, int v) {
+ opt->rep.optimize_filters_for_hits = v;
+}
+
+void rocksdb_options_set_delete_obsolete_files_period_micros(
+ rocksdb_options_t* opt, uint64_t v) {
+ opt->rep.delete_obsolete_files_period_micros = v;
+}
+
+void rocksdb_options_prepare_for_bulk_load(rocksdb_options_t* opt) {
+ opt->rep.PrepareForBulkLoad();
+}
+
+void rocksdb_options_set_memtable_vector_rep(rocksdb_options_t *opt) {
+ opt->rep.memtable_factory.reset(new ROCKSDB_NAMESPACE::VectorRepFactory);
+}
+
+void rocksdb_options_set_memtable_prefix_bloom_size_ratio(
+ rocksdb_options_t* opt, double v) {
+ opt->rep.memtable_prefix_bloom_size_ratio = v;
+}
+
+void rocksdb_options_set_memtable_huge_page_size(rocksdb_options_t* opt,
+ size_t v) {
+ opt->rep.memtable_huge_page_size = v;
+}
+
+void rocksdb_options_set_hash_skip_list_rep(
+ rocksdb_options_t *opt, size_t bucket_count,
+ int32_t skiplist_height, int32_t skiplist_branching_factor) {
+ ROCKSDB_NAMESPACE::MemTableRepFactory* factory =
+ ROCKSDB_NAMESPACE::NewHashSkipListRepFactory(
+ bucket_count, skiplist_height, skiplist_branching_factor);
+ opt->rep.memtable_factory.reset(factory);
+}
+
+void rocksdb_options_set_hash_link_list_rep(
+ rocksdb_options_t *opt, size_t bucket_count) {
+ opt->rep.memtable_factory.reset(
+ ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(bucket_count));
+}
+
+void rocksdb_options_set_plain_table_factory(
+ rocksdb_options_t *opt, uint32_t user_key_len, int bloom_bits_per_key,
+ double hash_table_ratio, size_t index_sparseness) {
+ ROCKSDB_NAMESPACE::PlainTableOptions options;
+ options.user_key_len = user_key_len;
+ options.bloom_bits_per_key = bloom_bits_per_key;
+ options.hash_table_ratio = hash_table_ratio;
+ options.index_sparseness = index_sparseness;
+
+ ROCKSDB_NAMESPACE::TableFactory* factory =
+ ROCKSDB_NAMESPACE::NewPlainTableFactory(options);
+ opt->rep.table_factory.reset(factory);
+}
+
+void rocksdb_options_set_max_successive_merges(
+ rocksdb_options_t* opt, size_t v) {
+ opt->rep.max_successive_merges = v;
+}
+
+void rocksdb_options_set_bloom_locality(
+ rocksdb_options_t* opt, uint32_t v) {
+ opt->rep.bloom_locality = v;
+}
+
+void rocksdb_options_set_inplace_update_support(
+ rocksdb_options_t* opt, unsigned char v) {
+ opt->rep.inplace_update_support = v;
+}
+
+void rocksdb_options_set_inplace_update_num_locks(
+ rocksdb_options_t* opt, size_t v) {
+ opt->rep.inplace_update_num_locks = v;
+}
+
+void rocksdb_options_set_report_bg_io_stats(
+ rocksdb_options_t* opt, int v) {
+ opt->rep.report_bg_io_stats = v;
+}
+
+void rocksdb_options_set_compaction_style(rocksdb_options_t *opt, int style) {
+ opt->rep.compaction_style =
+ static_cast<ROCKSDB_NAMESPACE::CompactionStyle>(style);
+}
+
+void rocksdb_options_set_universal_compaction_options(rocksdb_options_t *opt, rocksdb_universal_compaction_options_t *uco) {
+ opt->rep.compaction_options_universal = *(uco->rep);
+}
+
+void rocksdb_options_set_fifo_compaction_options(
+ rocksdb_options_t* opt,
+ rocksdb_fifo_compaction_options_t* fifo) {
+ opt->rep.compaction_options_fifo = fifo->rep;
+}
+
+char *rocksdb_options_statistics_get_string(rocksdb_options_t *opt) {
+ ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
+ if (statistics) {
+ return strdup(statistics->ToString().c_str());
+ }
+ return nullptr;
+}
+
+void rocksdb_options_set_ratelimiter(rocksdb_options_t *opt, rocksdb_ratelimiter_t *limiter) {
+ if (limiter) {
+ opt->rep.rate_limiter = limiter->rep;
+ }
+}
+
+void rocksdb_options_set_atomic_flush(rocksdb_options_t* opt,
+ unsigned char atomic_flush) {
+ opt->rep.atomic_flush = atomic_flush;
+}
+
+rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
+ int64_t rate_bytes_per_sec,
+ int64_t refill_period_us,
+ int32_t fairness) {
+ rocksdb_ratelimiter_t* rate_limiter = new rocksdb_ratelimiter_t;
+ rate_limiter->rep.reset(
+ NewGenericRateLimiter(rate_bytes_per_sec,
+ refill_period_us, fairness));
+ return rate_limiter;
+}
+
+void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t *limiter) {
+ delete limiter;
+}
+
+void rocksdb_options_set_row_cache(rocksdb_options_t* opt, rocksdb_cache_t* cache) {
+ if(cache) {
+ opt->rep.row_cache = cache->rep;
+ }
+}
+
+void rocksdb_set_perf_level(int v) {
+ PerfLevel level = static_cast<PerfLevel>(v);
+ SetPerfLevel(level);
+}
+
+rocksdb_perfcontext_t* rocksdb_perfcontext_create() {
+ rocksdb_perfcontext_t* context = new rocksdb_perfcontext_t;
+ context->rep = ROCKSDB_NAMESPACE::get_perf_context();
+ return context;
+}
+
+void rocksdb_perfcontext_reset(rocksdb_perfcontext_t* context) {
+ context->rep->Reset();
+}
+
+char* rocksdb_perfcontext_report(rocksdb_perfcontext_t* context,
+ unsigned char exclude_zero_counters) {
+ return strdup(context->rep->ToString(exclude_zero_counters).c_str());
+}
+
+uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context,
+ int metric) {
+ PerfContext* rep = context->rep;
+ switch (metric) {
+ case rocksdb_user_key_comparison_count:
+ return rep->user_key_comparison_count;
+ case rocksdb_block_cache_hit_count:
+ return rep->block_cache_hit_count;
+ case rocksdb_block_read_count:
+ return rep->block_read_count;
+ case rocksdb_block_read_byte:
+ return rep->block_read_byte;
+ case rocksdb_block_read_time:
+ return rep->block_read_time;
+ case rocksdb_block_checksum_time:
+ return rep->block_checksum_time;
+ case rocksdb_block_decompress_time:
+ return rep->block_decompress_time;
+ case rocksdb_get_read_bytes:
+ return rep->get_read_bytes;
+ case rocksdb_multiget_read_bytes:
+ return rep->multiget_read_bytes;
+ case rocksdb_iter_read_bytes:
+ return rep->iter_read_bytes;
+ case rocksdb_internal_key_skipped_count:
+ return rep->internal_key_skipped_count;
+ case rocksdb_internal_delete_skipped_count:
+ return rep->internal_delete_skipped_count;
+ case rocksdb_internal_recent_skipped_count:
+ return rep->internal_recent_skipped_count;
+ case rocksdb_internal_merge_count:
+ return rep->internal_merge_count;
+ case rocksdb_get_snapshot_time:
+ return rep->get_snapshot_time;
+ case rocksdb_get_from_memtable_time:
+ return rep->get_from_memtable_time;
+ case rocksdb_get_from_memtable_count:
+ return rep->get_from_memtable_count;
+ case rocksdb_get_post_process_time:
+ return rep->get_post_process_time;
+ case rocksdb_get_from_output_files_time:
+ return rep->get_from_output_files_time;
+ case rocksdb_seek_on_memtable_time:
+ return rep->seek_on_memtable_time;
+ case rocksdb_seek_on_memtable_count:
+ return rep->seek_on_memtable_count;
+ case rocksdb_next_on_memtable_count:
+ return rep->next_on_memtable_count;
+ case rocksdb_prev_on_memtable_count:
+ return rep->prev_on_memtable_count;
+ case rocksdb_seek_child_seek_time:
+ return rep->seek_child_seek_time;
+ case rocksdb_seek_child_seek_count:
+ return rep->seek_child_seek_count;
+ case rocksdb_seek_min_heap_time:
+ return rep->seek_min_heap_time;
+ case rocksdb_seek_max_heap_time:
+ return rep->seek_max_heap_time;
+ case rocksdb_seek_internal_seek_time:
+ return rep->seek_internal_seek_time;
+ case rocksdb_find_next_user_entry_time:
+ return rep->find_next_user_entry_time;
+ case rocksdb_write_wal_time:
+ return rep->write_wal_time;
+ case rocksdb_write_memtable_time:
+ return rep->write_memtable_time;
+ case rocksdb_write_delay_time:
+ return rep->write_delay_time;
+ case rocksdb_write_pre_and_post_process_time:
+ return rep->write_pre_and_post_process_time;
+ case rocksdb_db_mutex_lock_nanos:
+ return rep->db_mutex_lock_nanos;
+ case rocksdb_db_condition_wait_nanos:
+ return rep->db_condition_wait_nanos;
+ case rocksdb_merge_operator_time_nanos:
+ return rep->merge_operator_time_nanos;
+ case rocksdb_read_index_block_nanos:
+ return rep->read_index_block_nanos;
+ case rocksdb_read_filter_block_nanos:
+ return rep->read_filter_block_nanos;
+ case rocksdb_new_table_block_iter_nanos:
+ return rep->new_table_block_iter_nanos;
+ case rocksdb_new_table_iterator_nanos:
+ return rep->new_table_iterator_nanos;
+ case rocksdb_block_seek_nanos:
+ return rep->block_seek_nanos;
+ case rocksdb_find_table_nanos:
+ return rep->find_table_nanos;
+ case rocksdb_bloom_memtable_hit_count:
+ return rep->bloom_memtable_hit_count;
+ case rocksdb_bloom_memtable_miss_count:
+ return rep->bloom_memtable_miss_count;
+ case rocksdb_bloom_sst_hit_count:
+ return rep->bloom_sst_hit_count;
+ case rocksdb_bloom_sst_miss_count:
+ return rep->bloom_sst_miss_count;
+ case rocksdb_key_lock_wait_time:
+ return rep->key_lock_wait_time;
+ case rocksdb_key_lock_wait_count:
+ return rep->key_lock_wait_count;
+ case rocksdb_env_new_sequential_file_nanos:
+ return rep->env_new_sequential_file_nanos;
+ case rocksdb_env_new_random_access_file_nanos:
+ return rep->env_new_random_access_file_nanos;
+ case rocksdb_env_new_writable_file_nanos:
+ return rep->env_new_writable_file_nanos;
+ case rocksdb_env_reuse_writable_file_nanos:
+ return rep->env_reuse_writable_file_nanos;
+ case rocksdb_env_new_random_rw_file_nanos:
+ return rep->env_new_random_rw_file_nanos;
+ case rocksdb_env_new_directory_nanos:
+ return rep->env_new_directory_nanos;
+ case rocksdb_env_file_exists_nanos:
+ return rep->env_file_exists_nanos;
+ case rocksdb_env_get_children_nanos:
+ return rep->env_get_children_nanos;
+ case rocksdb_env_get_children_file_attributes_nanos:
+ return rep->env_get_children_file_attributes_nanos;
+ case rocksdb_env_delete_file_nanos:
+ return rep->env_delete_file_nanos;
+ case rocksdb_env_create_dir_nanos:
+ return rep->env_create_dir_nanos;
+ case rocksdb_env_create_dir_if_missing_nanos:
+ return rep->env_create_dir_if_missing_nanos;
+ case rocksdb_env_delete_dir_nanos:
+ return rep->env_delete_dir_nanos;
+ case rocksdb_env_get_file_size_nanos:
+ return rep->env_get_file_size_nanos;
+ case rocksdb_env_get_file_modification_time_nanos:
+ return rep->env_get_file_modification_time_nanos;
+ case rocksdb_env_rename_file_nanos:
+ return rep->env_rename_file_nanos;
+ case rocksdb_env_link_file_nanos:
+ return rep->env_link_file_nanos;
+ case rocksdb_env_lock_file_nanos:
+ return rep->env_lock_file_nanos;
+ case rocksdb_env_unlock_file_nanos:
+ return rep->env_unlock_file_nanos;
+ case rocksdb_env_new_logger_nanos:
+ return rep->env_new_logger_nanos;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void rocksdb_perfcontext_destroy(rocksdb_perfcontext_t* context) {
+ delete context;
+}
+
+/*
+TODO:
+DB::OpenForReadOnly
+DB::KeyMayExist
+DB::GetOptions
+DB::GetSortedWalFiles
+DB::GetLatestSequenceNumber
+DB::GetUpdatesSince
+DB::GetDbIdentity
+DB::RunManualCompaction
+custom cache
+table_properties_collectors
+*/
+
+rocksdb_compactionfilter_t* rocksdb_compactionfilter_create(
+ void* state,
+ void (*destructor)(void*),
+ unsigned char (*filter)(
+ void*,
+ int level,
+ const char* key, size_t key_length,
+ const char* existing_value, size_t value_length,
+ char** new_value, size_t *new_value_length,
+ unsigned char* value_changed),
+ const char* (*name)(void*)) {
+ rocksdb_compactionfilter_t* result = new rocksdb_compactionfilter_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->filter_ = filter;
+ result->ignore_snapshots_ = true;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_compactionfilter_set_ignore_snapshots(
+ rocksdb_compactionfilter_t* filter,
+ unsigned char whether_ignore) {
+ filter->ignore_snapshots_ = whether_ignore;
+}
+
+void rocksdb_compactionfilter_destroy(rocksdb_compactionfilter_t* filter) {
+ delete filter;
+}
+
+unsigned char rocksdb_compactionfiltercontext_is_full_compaction(
+ rocksdb_compactionfiltercontext_t* context) {
+ return context->rep.is_full_compaction;
+}
+
+unsigned char rocksdb_compactionfiltercontext_is_manual_compaction(
+ rocksdb_compactionfiltercontext_t* context) {
+ return context->rep.is_manual_compaction;
+}
+
+rocksdb_compactionfilterfactory_t* rocksdb_compactionfilterfactory_create(
+ void* state, void (*destructor)(void*),
+ rocksdb_compactionfilter_t* (*create_compaction_filter)(
+ void*, rocksdb_compactionfiltercontext_t* context),
+ const char* (*name)(void*)) {
+ rocksdb_compactionfilterfactory_t* result =
+ new rocksdb_compactionfilterfactory_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->create_compaction_filter_ = create_compaction_filter;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_compactionfilterfactory_destroy(
+ rocksdb_compactionfilterfactory_t* factory) {
+ delete factory;
+}
+
+rocksdb_comparator_t* rocksdb_comparator_create(
+ void* state,
+ void (*destructor)(void*),
+ int (*compare)(
+ void*,
+ const char* a, size_t alen,
+ const char* b, size_t blen),
+ const char* (*name)(void*)) {
+ rocksdb_comparator_t* result = new rocksdb_comparator_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->compare_ = compare;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_comparator_destroy(rocksdb_comparator_t* cmp) {
+ delete cmp;
+}
+
+rocksdb_filterpolicy_t* rocksdb_filterpolicy_create(
+ void* state,
+ void (*destructor)(void*),
+ char* (*create_filter)(
+ void*,
+ const char* const* key_array, const size_t* key_length_array,
+ int num_keys,
+ size_t* filter_length),
+ unsigned char (*key_may_match)(
+ void*,
+ const char* key, size_t length,
+ const char* filter, size_t filter_length),
+ void (*delete_filter)(
+ void*,
+ const char* filter, size_t filter_length),
+ const char* (*name)(void*)) {
+ rocksdb_filterpolicy_t* result = new rocksdb_filterpolicy_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->create_ = create_filter;
+ result->key_match_ = key_may_match;
+ result->delete_filter_ = delete_filter;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_filterpolicy_destroy(rocksdb_filterpolicy_t* filter) {
+ delete filter;
+}
+
+rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_key, bool original_format) {
+ // Make a rocksdb_filterpolicy_t, but override all of its methods so
+ // they delegate to a NewBloomFilterPolicy() instead of user
+ // supplied C functions.
+ struct Wrapper : public rocksdb_filterpolicy_t {
+ const FilterPolicy* rep_;
+ ~Wrapper() override { delete rep_; }
+ const char* Name() const override { return rep_->Name(); }
+ void CreateFilter(const Slice* keys, int n,
+ std::string* dst) const override {
+ return rep_->CreateFilter(keys, n, dst);
+ }
+ bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
+ return rep_->KeyMayMatch(key, filter);
+ }
+ // No need to override GetFilterBitsBuilder if this one is overridden
+ ROCKSDB_NAMESPACE::FilterBitsBuilder* GetBuilderWithContext(
+ const ROCKSDB_NAMESPACE::FilterBuildingContext& context)
+ const override {
+ return rep_->GetBuilderWithContext(context);
+ }
+ ROCKSDB_NAMESPACE::FilterBitsReader* GetFilterBitsReader(
+ const Slice& contents) const override {
+ return rep_->GetFilterBitsReader(contents);
+ }
+ static void DoNothing(void*) {}
+ };
+ Wrapper* wrapper = new Wrapper;
+ wrapper->rep_ = NewBloomFilterPolicy(bits_per_key, original_format);
+ wrapper->state_ = nullptr;
+ wrapper->delete_filter_ = nullptr;
+ wrapper->destructor_ = &Wrapper::DoNothing;
+ return wrapper;
+}
+
+rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_full(int bits_per_key) {
+ return rocksdb_filterpolicy_create_bloom_format(bits_per_key, false);
+}
+
+rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
+ return rocksdb_filterpolicy_create_bloom_format(bits_per_key, true);
+}
+
+rocksdb_mergeoperator_t* rocksdb_mergeoperator_create(
+ void* state, void (*destructor)(void*),
+ char* (*full_merge)(void*, const char* key, size_t key_length,
+ const char* existing_value,
+ size_t existing_value_length,
+ const char* const* operands_list,
+ const size_t* operands_list_length, int num_operands,
+ unsigned char* success, size_t* new_value_length),
+ char* (*partial_merge)(void*, const char* key, size_t key_length,
+ const char* const* operands_list,
+ const size_t* operands_list_length, int num_operands,
+ unsigned char* success, size_t* new_value_length),
+ void (*delete_value)(void*, const char* value, size_t value_length),
+ const char* (*name)(void*)) {
+ rocksdb_mergeoperator_t* result = new rocksdb_mergeoperator_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->full_merge_ = full_merge;
+ result->partial_merge_ = partial_merge;
+ result->delete_value_ = delete_value;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_mergeoperator_destroy(rocksdb_mergeoperator_t* merge_operator) {
+ delete merge_operator;
+}
+
+rocksdb_readoptions_t* rocksdb_readoptions_create() {
+ return new rocksdb_readoptions_t;
+}
+
+void rocksdb_readoptions_destroy(rocksdb_readoptions_t* opt) {
+ delete opt;
+}
+
+void rocksdb_readoptions_set_verify_checksums(
+ rocksdb_readoptions_t* opt,
+ unsigned char v) {
+ opt->rep.verify_checksums = v;
+}
+
+void rocksdb_readoptions_set_fill_cache(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.fill_cache = v;
+}
+
+void rocksdb_readoptions_set_snapshot(
+ rocksdb_readoptions_t* opt,
+ const rocksdb_snapshot_t* snap) {
+ opt->rep.snapshot = (snap ? snap->rep : nullptr);
+}
+
+void rocksdb_readoptions_set_iterate_upper_bound(
+ rocksdb_readoptions_t* opt,
+ const char* key, size_t keylen) {
+ if (key == nullptr) {
+ opt->upper_bound = Slice();
+ opt->rep.iterate_upper_bound = nullptr;
+
+ } else {
+ opt->upper_bound = Slice(key, keylen);
+ opt->rep.iterate_upper_bound = &opt->upper_bound;
+ }
+}
+
+void rocksdb_readoptions_set_iterate_lower_bound(
+ rocksdb_readoptions_t *opt,
+ const char* key, size_t keylen) {
+ if (key == nullptr) {
+ opt->lower_bound = Slice();
+ opt->rep.iterate_lower_bound = nullptr;
+ } else {
+ opt->lower_bound = Slice(key, keylen);
+ opt->rep.iterate_lower_bound = &opt->lower_bound;
+ }
+}
+
+void rocksdb_readoptions_set_read_tier(
+ rocksdb_readoptions_t* opt, int v) {
+ opt->rep.read_tier = static_cast<ROCKSDB_NAMESPACE::ReadTier>(v);
+}
+
+void rocksdb_readoptions_set_tailing(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.tailing = v;
+}
+
+void rocksdb_readoptions_set_managed(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.managed = v;
+}
+
+void rocksdb_readoptions_set_readahead_size(
+ rocksdb_readoptions_t* opt, size_t v) {
+ opt->rep.readahead_size = v;
+}
+
+void rocksdb_readoptions_set_prefix_same_as_start(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.prefix_same_as_start = v;
+}
+
+void rocksdb_readoptions_set_pin_data(rocksdb_readoptions_t* opt,
+ unsigned char v) {
+ opt->rep.pin_data = v;
+}
+
+void rocksdb_readoptions_set_total_order_seek(rocksdb_readoptions_t* opt,
+ unsigned char v) {
+ opt->rep.total_order_seek = v;
+}
+
+void rocksdb_readoptions_set_max_skippable_internal_keys(
+ rocksdb_readoptions_t* opt,
+ uint64_t v) {
+ opt->rep.max_skippable_internal_keys = v;
+}
+
+void rocksdb_readoptions_set_background_purge_on_iterator_cleanup(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.background_purge_on_iterator_cleanup = v;
+}
+
+void rocksdb_readoptions_set_ignore_range_deletions(
+ rocksdb_readoptions_t* opt, unsigned char v) {
+ opt->rep.ignore_range_deletions = v;
+}
+
+rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
+ return new rocksdb_writeoptions_t;
+}
+
+void rocksdb_writeoptions_destroy(rocksdb_writeoptions_t* opt) {
+ delete opt;
+}
+
+void rocksdb_writeoptions_set_sync(
+ rocksdb_writeoptions_t* opt, unsigned char v) {
+ opt->rep.sync = v;
+}
+
+void rocksdb_writeoptions_disable_WAL(rocksdb_writeoptions_t* opt, int disable) {
+ opt->rep.disableWAL = disable;
+}
+
+void rocksdb_writeoptions_set_ignore_missing_column_families(
+ rocksdb_writeoptions_t* opt,
+ unsigned char v) {
+ opt->rep.ignore_missing_column_families = v;
+}
+
+void rocksdb_writeoptions_set_no_slowdown(
+ rocksdb_writeoptions_t* opt,
+ unsigned char v) {
+ opt->rep.no_slowdown = v;
+}
+
+void rocksdb_writeoptions_set_low_pri(
+ rocksdb_writeoptions_t* opt,
+ unsigned char v) {
+ opt->rep.low_pri = v;
+}
+
+void rocksdb_writeoptions_set_memtable_insert_hint_per_batch(
+ rocksdb_writeoptions_t* opt, unsigned char v) {
+ opt->rep.memtable_insert_hint_per_batch = v;
+}
+
+rocksdb_compactoptions_t* rocksdb_compactoptions_create() {
+ return new rocksdb_compactoptions_t;
+}
+
+void rocksdb_compactoptions_destroy(rocksdb_compactoptions_t* opt) {
+ delete opt;
+}
+
+void rocksdb_compactoptions_set_bottommost_level_compaction(
+ rocksdb_compactoptions_t* opt, unsigned char v) {
+ opt->rep.bottommost_level_compaction = static_cast<BottommostLevelCompaction>(v);
+}
+
+void rocksdb_compactoptions_set_exclusive_manual_compaction(
+ rocksdb_compactoptions_t* opt, unsigned char v) {
+ opt->rep.exclusive_manual_compaction = v;
+}
+
+void rocksdb_compactoptions_set_change_level(rocksdb_compactoptions_t* opt,
+ unsigned char v) {
+ opt->rep.change_level = v;
+}
+
+void rocksdb_compactoptions_set_target_level(rocksdb_compactoptions_t* opt,
+ int n) {
+ opt->rep.target_level = n;
+}
+
+rocksdb_flushoptions_t* rocksdb_flushoptions_create() {
+ return new rocksdb_flushoptions_t;
+}
+
+void rocksdb_flushoptions_destroy(rocksdb_flushoptions_t* opt) {
+ delete opt;
+}
+
+void rocksdb_flushoptions_set_wait(
+ rocksdb_flushoptions_t* opt, unsigned char v) {
+ opt->rep.wait = v;
+}
+
+rocksdb_cache_t* rocksdb_cache_create_lru(size_t capacity) {
+ rocksdb_cache_t* c = new rocksdb_cache_t;
+ c->rep = NewLRUCache(capacity);
+ return c;
+}
+
+void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
+ delete cache;
+}
+
+void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
+ cache->rep->SetCapacity(capacity);
+}
+
+size_t rocksdb_cache_get_usage(rocksdb_cache_t* cache) {
+ return cache->rep->GetUsage();
+}
+
+size_t rocksdb_cache_get_pinned_usage(rocksdb_cache_t* cache) {
+ return cache->rep->GetPinnedUsage();
+}
+
+rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path, uint64_t target_size) {
+ rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
+ result->rep.path = std::string(path);
+ result->rep.target_size = target_size;
+ return result;
+}
+
+void rocksdb_dbpath_destroy(rocksdb_dbpath_t* dbpath) {
+ delete dbpath;
+}
+
+rocksdb_env_t* rocksdb_create_default_env() {
+ rocksdb_env_t* result = new rocksdb_env_t;
+ result->rep = Env::Default();
+ result->is_default = true;
+ return result;
+}
+
+rocksdb_env_t* rocksdb_create_mem_env() {
+ rocksdb_env_t* result = new rocksdb_env_t;
+ result->rep = ROCKSDB_NAMESPACE::NewMemEnv(Env::Default());
+ result->is_default = false;
+ return result;
+}
+
+void rocksdb_env_set_background_threads(rocksdb_env_t* env, int n) {
+ env->rep->SetBackgroundThreads(n);
+}
+
+void rocksdb_env_set_high_priority_background_threads(rocksdb_env_t* env, int n) {
+ env->rep->SetBackgroundThreads(n, Env::HIGH);
+}
+
+void rocksdb_env_join_all_threads(rocksdb_env_t* env) {
+ env->rep->WaitForJoin();
+}
+
+void rocksdb_env_lower_thread_pool_io_priority(rocksdb_env_t* env) {
+ env->rep->LowerThreadPoolIOPriority();
+}
+
+void rocksdb_env_lower_high_priority_thread_pool_io_priority(rocksdb_env_t* env) {
+ env->rep->LowerThreadPoolIOPriority(Env::HIGH);
+}
+
+void rocksdb_env_lower_thread_pool_cpu_priority(rocksdb_env_t* env) {
+ env->rep->LowerThreadPoolCPUPriority();
+}
+
+void rocksdb_env_lower_high_priority_thread_pool_cpu_priority(rocksdb_env_t* env) {
+ env->rep->LowerThreadPoolCPUPriority(Env::HIGH);
+}
+
+void rocksdb_env_destroy(rocksdb_env_t* env) {
+ if (!env->is_default) delete env->rep;
+ delete env;
+}
+
+rocksdb_envoptions_t* rocksdb_envoptions_create() {
+ rocksdb_envoptions_t* opt = new rocksdb_envoptions_t;
+ return opt;
+}
+
+void rocksdb_envoptions_destroy(rocksdb_envoptions_t* opt) { delete opt; }
+
+rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create(
+ const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options) {
+ rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t;
+ writer->rep = new SstFileWriter(env->rep, io_options->rep);
+ return writer;
+}
+
+rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create_with_comparator(
+ const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options,
+ const rocksdb_comparator_t* /*comparator*/) {
+ rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t;
+ writer->rep = new SstFileWriter(env->rep, io_options->rep);
+ return writer;
+}
+
+void rocksdb_sstfilewriter_open(rocksdb_sstfilewriter_t* writer,
+ const char* name, char** errptr) {
+ SaveError(errptr, writer->rep->Open(std::string(name)));
+}
+
+void rocksdb_sstfilewriter_add(rocksdb_sstfilewriter_t* writer, const char* key,
+ size_t keylen, const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr, writer->rep->Put(Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_sstfilewriter_put(rocksdb_sstfilewriter_t* writer, const char* key,
+ size_t keylen, const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr, writer->rep->Put(Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_sstfilewriter_merge(rocksdb_sstfilewriter_t* writer,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr, writer->rep->Merge(Slice(key, keylen), Slice(val, vallen)));
+}
+
+void rocksdb_sstfilewriter_delete(rocksdb_sstfilewriter_t* writer,
+ const char* key, size_t keylen,
+ char** errptr) {
+ SaveError(errptr, writer->rep->Delete(Slice(key, keylen)));
+}
+
+void rocksdb_sstfilewriter_finish(rocksdb_sstfilewriter_t* writer,
+ char** errptr) {
+ SaveError(errptr, writer->rep->Finish(nullptr));
+}
+
+void rocksdb_sstfilewriter_file_size(rocksdb_sstfilewriter_t* writer,
+ uint64_t* file_size) {
+ *file_size = writer->rep->FileSize();
+}
+
+void rocksdb_sstfilewriter_destroy(rocksdb_sstfilewriter_t* writer) {
+ delete writer->rep;
+ delete writer;
+}
+
+rocksdb_ingestexternalfileoptions_t*
+rocksdb_ingestexternalfileoptions_create() {
+ rocksdb_ingestexternalfileoptions_t* opt =
+ new rocksdb_ingestexternalfileoptions_t;
+ return opt;
+}
+
+void rocksdb_ingestexternalfileoptions_set_move_files(
+ rocksdb_ingestexternalfileoptions_t* opt, unsigned char move_files) {
+ opt->rep.move_files = move_files;
+}
+
+void rocksdb_ingestexternalfileoptions_set_snapshot_consistency(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char snapshot_consistency) {
+ opt->rep.snapshot_consistency = snapshot_consistency;
+}
+
+void rocksdb_ingestexternalfileoptions_set_allow_global_seqno(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char allow_global_seqno) {
+ opt->rep.allow_global_seqno = allow_global_seqno;
+}
+
+void rocksdb_ingestexternalfileoptions_set_allow_blocking_flush(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char allow_blocking_flush) {
+ opt->rep.allow_blocking_flush = allow_blocking_flush;
+}
+
+void rocksdb_ingestexternalfileoptions_set_ingest_behind(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char ingest_behind) {
+ opt->rep.ingest_behind = ingest_behind;
+}
+
+void rocksdb_ingestexternalfileoptions_destroy(
+ rocksdb_ingestexternalfileoptions_t* opt) {
+ delete opt;
+}
+
+void rocksdb_ingest_external_file(
+ rocksdb_t* db, const char* const* file_list, const size_t list_len,
+ const rocksdb_ingestexternalfileoptions_t* opt, char** errptr) {
+ std::vector<std::string> files(list_len);
+ for (size_t i = 0; i < list_len; ++i) {
+ files[i] = std::string(file_list[i]);
+ }
+ SaveError(errptr, db->rep->IngestExternalFile(files, opt->rep));
+}
+
+void rocksdb_ingest_external_file_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* handle,
+ const char* const* file_list, const size_t list_len,
+ const rocksdb_ingestexternalfileoptions_t* opt, char** errptr) {
+ std::vector<std::string> files(list_len);
+ for (size_t i = 0; i < list_len; ++i) {
+ files[i] = std::string(file_list[i]);
+ }
+ SaveError(errptr, db->rep->IngestExternalFile(handle->rep, files, opt->rep));
+}
+
+void rocksdb_try_catch_up_with_primary(rocksdb_t* db, char** errptr) {
+ SaveError(errptr, db->rep->TryCatchUpWithPrimary());
+}
+
+rocksdb_slicetransform_t* rocksdb_slicetransform_create(
+ void* state,
+ void (*destructor)(void*),
+ char* (*transform)(
+ void*,
+ const char* key, size_t length,
+ size_t* dst_length),
+ unsigned char (*in_domain)(
+ void*,
+ const char* key, size_t length),
+ unsigned char (*in_range)(
+ void*,
+ const char* key, size_t length),
+ const char* (*name)(void*)) {
+ rocksdb_slicetransform_t* result = new rocksdb_slicetransform_t;
+ result->state_ = state;
+ result->destructor_ = destructor;
+ result->transform_ = transform;
+ result->in_domain_ = in_domain;
+ result->in_range_ = in_range;
+ result->name_ = name;
+ return result;
+}
+
+void rocksdb_slicetransform_destroy(rocksdb_slicetransform_t* st) {
+ delete st;
+}
+
+struct Wrapper : public rocksdb_slicetransform_t {
+ const SliceTransform* rep_;
+ ~Wrapper() override { delete rep_; }
+ const char* Name() const override { return rep_->Name(); }
+ Slice Transform(const Slice& src) const override {
+ return rep_->Transform(src);
+ }
+ bool InDomain(const Slice& src) const override {
+ return rep_->InDomain(src);
+ }
+ bool InRange(const Slice& src) const override { return rep_->InRange(src); }
+ static void DoNothing(void*) { }
+};
+
+rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t prefixLen) {
+ Wrapper* wrapper = new Wrapper;
+ wrapper->rep_ = ROCKSDB_NAMESPACE::NewFixedPrefixTransform(prefixLen);
+ wrapper->state_ = nullptr;
+ wrapper->destructor_ = &Wrapper::DoNothing;
+ return wrapper;
+}
+
+rocksdb_slicetransform_t* rocksdb_slicetransform_create_noop() {
+ Wrapper* wrapper = new Wrapper;
+ wrapper->rep_ = ROCKSDB_NAMESPACE::NewNoopTransform();
+ wrapper->state_ = nullptr;
+ wrapper->destructor_ = &Wrapper::DoNothing;
+ return wrapper;
+}
+
+rocksdb_universal_compaction_options_t* rocksdb_universal_compaction_options_create() {
+ rocksdb_universal_compaction_options_t* result = new rocksdb_universal_compaction_options_t;
+ result->rep = new ROCKSDB_NAMESPACE::CompactionOptionsUniversal;
+ return result;
+}
+
+void rocksdb_universal_compaction_options_set_size_ratio(
+ rocksdb_universal_compaction_options_t* uco, int ratio) {
+ uco->rep->size_ratio = ratio;
+}
+
+void rocksdb_universal_compaction_options_set_min_merge_width(
+ rocksdb_universal_compaction_options_t* uco, int w) {
+ uco->rep->min_merge_width = w;
+}
+
+void rocksdb_universal_compaction_options_set_max_merge_width(
+ rocksdb_universal_compaction_options_t* uco, int w) {
+ uco->rep->max_merge_width = w;
+}
+
+void rocksdb_universal_compaction_options_set_max_size_amplification_percent(
+ rocksdb_universal_compaction_options_t* uco, int p) {
+ uco->rep->max_size_amplification_percent = p;
+}
+
+void rocksdb_universal_compaction_options_set_compression_size_percent(
+ rocksdb_universal_compaction_options_t* uco, int p) {
+ uco->rep->compression_size_percent = p;
+}
+
+void rocksdb_universal_compaction_options_set_stop_style(
+ rocksdb_universal_compaction_options_t* uco, int style) {
+ uco->rep->stop_style =
+ static_cast<ROCKSDB_NAMESPACE::CompactionStopStyle>(style);
+}
+
+void rocksdb_universal_compaction_options_destroy(
+ rocksdb_universal_compaction_options_t* uco) {
+ delete uco->rep;
+ delete uco;
+}
+
+rocksdb_fifo_compaction_options_t* rocksdb_fifo_compaction_options_create() {
+ rocksdb_fifo_compaction_options_t* result = new rocksdb_fifo_compaction_options_t;
+ result->rep = CompactionOptionsFIFO();
+ return result;
+}
+
+void rocksdb_fifo_compaction_options_set_max_table_files_size(
+ rocksdb_fifo_compaction_options_t* fifo_opts, uint64_t size) {
+ fifo_opts->rep.max_table_files_size = size;
+}
+
+void rocksdb_fifo_compaction_options_destroy(
+ rocksdb_fifo_compaction_options_t* fifo_opts) {
+ delete fifo_opts;
+}
+
+void rocksdb_options_set_min_level_to_compress(rocksdb_options_t* opt, int level) {
+ if (level >= 0) {
+ assert(level <= opt->rep.num_levels);
+ opt->rep.compression_per_level.resize(opt->rep.num_levels);
+ for (int i = 0; i < level; i++) {
+ opt->rep.compression_per_level[i] = ROCKSDB_NAMESPACE::kNoCompression;
+ }
+ for (int i = level; i < opt->rep.num_levels; i++) {
+ opt->rep.compression_per_level[i] = opt->rep.compression;
+ }
+ }
+}
+
+int rocksdb_livefiles_count(
+ const rocksdb_livefiles_t* lf) {
+ return static_cast<int>(lf->rep.size());
+}
+
+const char* rocksdb_livefiles_name(
+ const rocksdb_livefiles_t* lf,
+ int index) {
+ return lf->rep[index].name.c_str();
+}
+
+int rocksdb_livefiles_level(
+ const rocksdb_livefiles_t* lf,
+ int index) {
+ return lf->rep[index].level;
+}
+
+size_t rocksdb_livefiles_size(
+ const rocksdb_livefiles_t* lf,
+ int index) {
+ return lf->rep[index].size;
+}
+
+const char* rocksdb_livefiles_smallestkey(
+ const rocksdb_livefiles_t* lf,
+ int index,
+ size_t* size) {
+ *size = lf->rep[index].smallestkey.size();
+ return lf->rep[index].smallestkey.data();
+}
+
+const char* rocksdb_livefiles_largestkey(
+ const rocksdb_livefiles_t* lf,
+ int index,
+ size_t* size) {
+ *size = lf->rep[index].largestkey.size();
+ return lf->rep[index].largestkey.data();
+}
+
+uint64_t rocksdb_livefiles_entries(
+ const rocksdb_livefiles_t* lf,
+ int index) {
+ return lf->rep[index].num_entries;
+}
+
+uint64_t rocksdb_livefiles_deletions(
+ const rocksdb_livefiles_t* lf,
+ int index) {
+ return lf->rep[index].num_deletions;
+}
+
+extern void rocksdb_livefiles_destroy(
+ const rocksdb_livefiles_t* lf) {
+ delete lf;
+}
+
+void rocksdb_get_options_from_string(const rocksdb_options_t* base_options,
+ const char* opts_str,
+ rocksdb_options_t* new_options,
+ char** errptr) {
+ SaveError(errptr,
+ GetOptionsFromString(base_options->rep, std::string(opts_str),
+ &new_options->rep));
+}
+
+void rocksdb_delete_file_in_range(rocksdb_t* db, const char* start_key,
+ size_t start_key_len, const char* limit_key,
+ size_t limit_key_len, char** errptr) {
+ Slice a, b;
+ SaveError(
+ errptr,
+ DeleteFilesInRange(
+ db->rep, db->rep->DefaultColumnFamily(),
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
+}
+
+void rocksdb_delete_file_in_range_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* limit_key,
+ size_t limit_key_len, char** errptr) {
+ Slice a, b;
+ SaveError(
+ errptr,
+ DeleteFilesInRange(
+ db->rep, column_family->rep,
+ (start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
+ (limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
+}
+
+rocksdb_transactiondb_options_t* rocksdb_transactiondb_options_create() {
+ return new rocksdb_transactiondb_options_t;
+}
+
+void rocksdb_transactiondb_options_destroy(rocksdb_transactiondb_options_t* opt){
+ delete opt;
+}
+
+void rocksdb_transactiondb_options_set_max_num_locks(
+ rocksdb_transactiondb_options_t* opt, int64_t max_num_locks) {
+ opt->rep.max_num_locks = max_num_locks;
+}
+
+void rocksdb_transactiondb_options_set_num_stripes(
+ rocksdb_transactiondb_options_t* opt, size_t num_stripes) {
+ opt->rep.num_stripes = num_stripes;
+}
+
+void rocksdb_transactiondb_options_set_transaction_lock_timeout(
+ rocksdb_transactiondb_options_t* opt, int64_t txn_lock_timeout) {
+ opt->rep.transaction_lock_timeout = txn_lock_timeout;
+}
+
+void rocksdb_transactiondb_options_set_default_lock_timeout(
+ rocksdb_transactiondb_options_t* opt, int64_t default_lock_timeout) {
+ opt->rep.default_lock_timeout = default_lock_timeout;
+}
+
+rocksdb_transaction_options_t* rocksdb_transaction_options_create() {
+ return new rocksdb_transaction_options_t;
+}
+
+void rocksdb_transaction_options_destroy(rocksdb_transaction_options_t* opt) {
+ delete opt;
+}
+
+void rocksdb_transaction_options_set_set_snapshot(
+ rocksdb_transaction_options_t* opt, unsigned char v) {
+ opt->rep.set_snapshot = v;
+}
+
+void rocksdb_transaction_options_set_deadlock_detect(
+ rocksdb_transaction_options_t* opt, unsigned char v) {
+ opt->rep.deadlock_detect = v;
+}
+
+void rocksdb_transaction_options_set_lock_timeout(
+ rocksdb_transaction_options_t* opt, int64_t lock_timeout) {
+ opt->rep.lock_timeout = lock_timeout;
+}
+
+void rocksdb_transaction_options_set_expiration(
+ rocksdb_transaction_options_t* opt, int64_t expiration) {
+ opt->rep.expiration = expiration;
+}
+
+void rocksdb_transaction_options_set_deadlock_detect_depth(
+ rocksdb_transaction_options_t* opt, int64_t depth) {
+ opt->rep.deadlock_detect_depth = depth;
+}
+
+void rocksdb_transaction_options_set_max_write_batch_size(
+ rocksdb_transaction_options_t* opt, size_t size) {
+ opt->rep.max_write_batch_size = size;
+}
+
+rocksdb_optimistictransaction_options_t*
+rocksdb_optimistictransaction_options_create() {
+ return new rocksdb_optimistictransaction_options_t;
+}
+
+void rocksdb_optimistictransaction_options_destroy(
+ rocksdb_optimistictransaction_options_t* opt) {
+ delete opt;
+}
+
+void rocksdb_optimistictransaction_options_set_set_snapshot(
+ rocksdb_optimistictransaction_options_t* opt, unsigned char v) {
+ opt->rep.set_snapshot = v;
+}
+
+rocksdb_column_family_handle_t* rocksdb_transactiondb_create_column_family(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_options_t* column_family_options,
+ const char* column_family_name, char** errptr) {
+ rocksdb_column_family_handle_t* handle = new rocksdb_column_family_handle_t;
+ SaveError(errptr, txn_db->rep->CreateColumnFamily(
+ ColumnFamilyOptions(column_family_options->rep),
+ std::string(column_family_name), &(handle->rep)));
+ return handle;
+}
+
+rocksdb_transactiondb_t* rocksdb_transactiondb_open(
+ const rocksdb_options_t* options,
+ const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
+ char** errptr) {
+ TransactionDB* txn_db;
+ if (SaveError(errptr, TransactionDB::Open(options->rep, txn_db_options->rep,
+ std::string(name), &txn_db))) {
+ return nullptr;
+ }
+ rocksdb_transactiondb_t* result = new rocksdb_transactiondb_t;
+ result->rep = txn_db;
+ return result;
+}
+
+rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families(
+ const rocksdb_options_t* options,
+ const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
+ int num_column_families, const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (int i = 0; i < num_column_families; i++) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ std::string(column_family_names[i]),
+ ColumnFamilyOptions(column_family_options[i]->rep)));
+ }
+
+ TransactionDB* txn_db;
+ std::vector<ColumnFamilyHandle*> handles;
+ if (SaveError(errptr, TransactionDB::Open(options->rep, txn_db_options->rep,
+ std::string(name), column_families,
+ &handles, &txn_db))) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < handles.size(); i++) {
+ rocksdb_column_family_handle_t* c_handle =
+ new rocksdb_column_family_handle_t;
+ c_handle->rep = handles[i];
+ column_family_handles[i] = c_handle;
+ }
+ rocksdb_transactiondb_t* result = new rocksdb_transactiondb_t;
+ result->rep = txn_db;
+ return result;
+}
+
+const rocksdb_snapshot_t* rocksdb_transactiondb_create_snapshot(
+ rocksdb_transactiondb_t* txn_db) {
+ rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
+ result->rep = txn_db->rep->GetSnapshot();
+ return result;
+}
+
+void rocksdb_transactiondb_release_snapshot(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_snapshot_t* snapshot) {
+ txn_db->rep->ReleaseSnapshot(snapshot->rep);
+ delete snapshot;
+}
+
+rocksdb_transaction_t* rocksdb_transaction_begin(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* write_options,
+ const rocksdb_transaction_options_t* txn_options,
+ rocksdb_transaction_t* old_txn) {
+ if (old_txn == nullptr) {
+ rocksdb_transaction_t* result = new rocksdb_transaction_t;
+ result->rep = txn_db->rep->BeginTransaction(write_options->rep,
+ txn_options->rep, nullptr);
+ return result;
+ }
+ old_txn->rep = txn_db->rep->BeginTransaction(write_options->rep,
+ txn_options->rep, old_txn->rep);
+ return old_txn;
+}
+
+void rocksdb_transaction_commit(rocksdb_transaction_t* txn, char** errptr) {
+ SaveError(errptr, txn->rep->Commit());
+}
+
+void rocksdb_transaction_rollback(rocksdb_transaction_t* txn, char** errptr) {
+ SaveError(errptr, txn->rep->Rollback());
+}
+
+void rocksdb_transaction_set_savepoint(rocksdb_transaction_t* txn) {
+ txn->rep->SetSavePoint();
+}
+
+void rocksdb_transaction_rollback_to_savepoint(rocksdb_transaction_t* txn, char** errptr) {
+ SaveError(errptr, txn->rep->RollbackToSavePoint());
+}
+
+void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) {
+ delete txn->rep;
+ delete txn;
+}
+
+const rocksdb_snapshot_t* rocksdb_transaction_get_snapshot(
+ rocksdb_transaction_t* txn) {
+ rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
+ result->rep = txn->rep->GetSnapshot();
+ return result;
+}
+
+// Read a key inside a transaction
+char* rocksdb_transaction_get(rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t klen, size_t* vlen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = txn->rep->Get(options->rep, Slice(key, klen), &tmp);
+ if (s.ok()) {
+ *vlen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vlen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_transaction_get_cf(rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, size_t* vlen,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s =
+ txn->rep->Get(options->rep, column_family->rep, Slice(key, klen), &tmp);
+ if (s.ok()) {
+ *vlen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vlen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+// Read a key inside a transaction
+char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t klen,
+ size_t* vlen, unsigned char exclusive,
+ char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s =
+ txn->rep->GetForUpdate(options->rep, Slice(key, klen), &tmp, exclusive);
+ if (s.ok()) {
+ *vlen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vlen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_transaction_get_for_update_cf(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
+ size_t* vlen, unsigned char exclusive, char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
+ Slice(key, klen), &tmp, exclusive);
+ if (s.ok()) {
+ *vlen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vlen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+// Read a key outside a transaction
+char* rocksdb_transactiondb_get(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t klen,
+ size_t* vlen,
+ char** errptr){
+ char* result = nullptr;
+ std::string tmp;
+ Status s = txn_db->rep->Get(options->rep, Slice(key, klen), &tmp);
+ if (s.ok()) {
+ *vlen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vlen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+char* rocksdb_transactiondb_get_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, size_t* vallen, char** errptr) {
+ char* result = nullptr;
+ std::string tmp;
+ Status s = txn_db->rep->Get(options->rep, column_family->rep,
+ Slice(key, keylen), &tmp);
+ if (s.ok()) {
+ *vallen = tmp.size();
+ result = CopyString(tmp);
+ } else {
+ *vallen = 0;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ }
+ return result;
+}
+
+// Put a key inside a transaction
+void rocksdb_transaction_put(rocksdb_transaction_t* txn, const char* key,
+ size_t klen, const char* val, size_t vlen,
+ char** errptr) {
+ SaveError(errptr, txn->rep->Put(Slice(key, klen), Slice(val, vlen)));
+}
+
+void rocksdb_transaction_put_cf(rocksdb_transaction_t* txn,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr) {
+ SaveError(errptr, txn->rep->Put(column_family->rep, Slice(key, klen),
+ Slice(val, vlen)));
+}
+
+// Put a key outside a transaction
+void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr) {
+ SaveError(errptr,
+ txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen)));
+}
+
+void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ const char* val, size_t vallen,
+ char** errptr) {
+ SaveError(errptr, txn_db->rep->Put(options->rep, column_family->rep,
+ Slice(key, keylen), Slice(val, vallen)));
+}
+
+// Write batch into transaction db
+void rocksdb_transactiondb_write(
+ rocksdb_transactiondb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_t* batch,
+ char** errptr) {
+ SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
+}
+
+// Merge a key inside a transaction
+void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
+ size_t klen, const char* val, size_t vlen,
+ char** errptr) {
+ SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
+}
+
+void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr) {
+ SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen),
+ Slice(val, vlen)));
+}
+
+// Merge a key outside a transaction
+void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr) {
+ SaveError(errptr, txn_db->rep->Merge(options->rep, Slice(key, klen),
+ Slice(val, vlen)));
+}
+
+void rocksdb_transactiondb_merge_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
+ const char* val, size_t vlen, char** errptr) {
+ SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep,
+ Slice(key, klen), Slice(val, vlen)));
+}
+
+// Delete a key inside a transaction
+void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
+ size_t klen, char** errptr) {
+ SaveError(errptr, txn->rep->Delete(Slice(key, klen)));
+}
+
+void rocksdb_transaction_delete_cf(
+ rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, char** errptr) {
+ SaveError(errptr, txn->rep->Delete(column_family->rep, Slice(key, klen)));
+}
+
+// Delete a key outside a transaction
+void rocksdb_transactiondb_delete(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, char** errptr) {
+ SaveError(errptr, txn_db->rep->Delete(options->rep, Slice(key, klen)));
+}
+
+void rocksdb_transactiondb_delete_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, char** errptr) {
+ SaveError(errptr, txn_db->rep->Delete(options->rep, column_family->rep,
+ Slice(key, keylen)));
+}
+
+// Create an iterator inside a transaction
+rocksdb_iterator_t* rocksdb_transaction_create_iterator(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = txn->rep->GetIterator(options->rep);
+ return result;
+}
+
+// Create an iterator inside a transaction with column family
+rocksdb_iterator_t* rocksdb_transaction_create_iterator_cf(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = txn->rep->GetIterator(options->rep, column_family->rep);
+ return result;
+}
+
+// Create an iterator outside a transaction
+rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = txn_db->rep->NewIterator(options->rep);
+ return result;
+}
+
+rocksdb_iterator_t* rocksdb_transactiondb_create_iterator_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family) {
+ rocksdb_iterator_t* result = new rocksdb_iterator_t;
+ result->rep = txn_db->rep->NewIterator(options->rep, column_family->rep);
+ return result;
+}
+
+void rocksdb_transactiondb_close(rocksdb_transactiondb_t* txn_db) {
+ delete txn_db->rep;
+ delete txn_db;
+}
+
+rocksdb_checkpoint_t* rocksdb_transactiondb_checkpoint_object_create(
+ rocksdb_transactiondb_t* txn_db, char** errptr) {
+ Checkpoint* checkpoint;
+ if (SaveError(errptr, Checkpoint::Create(txn_db->rep, &checkpoint))) {
+ return nullptr;
+ }
+ rocksdb_checkpoint_t* result = new rocksdb_checkpoint_t;
+ result->rep = checkpoint;
+ return result;
+}
+
+rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(
+ const rocksdb_options_t* options, const char* name, char** errptr) {
+ OptimisticTransactionDB* otxn_db;
+ if (SaveError(errptr, OptimisticTransactionDB::Open(
+ options->rep, std::string(name), &otxn_db))) {
+ return nullptr;
+ }
+ rocksdb_optimistictransactiondb_t* result =
+ new rocksdb_optimistictransactiondb_t;
+ result->rep = otxn_db;
+ return result;
+}
+
+rocksdb_optimistictransactiondb_t*
+rocksdb_optimistictransactiondb_open_column_families(
+ const rocksdb_options_t* db_options, const char* name,
+ int num_column_families, const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (int i = 0; i < num_column_families; i++) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ std::string(column_family_names[i]),
+ ColumnFamilyOptions(column_family_options[i]->rep)));
+ }
+
+ OptimisticTransactionDB* otxn_db;
+ std::vector<ColumnFamilyHandle*> handles;
+ if (SaveError(errptr, OptimisticTransactionDB::Open(
+ DBOptions(db_options->rep), std::string(name),
+ column_families, &handles, &otxn_db))) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < handles.size(); i++) {
+ rocksdb_column_family_handle_t* c_handle =
+ new rocksdb_column_family_handle_t;
+ c_handle->rep = handles[i];
+ column_family_handles[i] = c_handle;
+ }
+ rocksdb_optimistictransactiondb_t* result =
+ new rocksdb_optimistictransactiondb_t;
+ result->rep = otxn_db;
+ return result;
+}
+
+rocksdb_t* rocksdb_optimistictransactiondb_get_base_db(
+ rocksdb_optimistictransactiondb_t* otxn_db) {
+ DB* base_db = otxn_db->rep->GetBaseDB();
+
+ if (base_db != nullptr) {
+ rocksdb_t* result = new rocksdb_t;
+ result->rep = base_db;
+ return result;
+ }
+
+ return nullptr;
+}
+
+void rocksdb_optimistictransactiondb_close_base_db(rocksdb_t* base_db) {
+ delete base_db;
+}
+
+rocksdb_transaction_t* rocksdb_optimistictransaction_begin(
+ rocksdb_optimistictransactiondb_t* otxn_db,
+ const rocksdb_writeoptions_t* write_options,
+ const rocksdb_optimistictransaction_options_t* otxn_options,
+ rocksdb_transaction_t* old_txn) {
+ if (old_txn == nullptr) {
+ rocksdb_transaction_t* result = new rocksdb_transaction_t;
+ result->rep = otxn_db->rep->BeginTransaction(write_options->rep,
+ otxn_options->rep, nullptr);
+ return result;
+ }
+ old_txn->rep = otxn_db->rep->BeginTransaction(
+ write_options->rep, otxn_options->rep, old_txn->rep);
+ return old_txn;
+}
+
+void rocksdb_optimistictransactiondb_close(
+ rocksdb_optimistictransactiondb_t* otxn_db) {
+ delete otxn_db->rep;
+ delete otxn_db;
+}
+
+void rocksdb_free(void* ptr) { free(ptr); }
+
+rocksdb_pinnableslice_t* rocksdb_get_pinned(
+ rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
+ size_t keylen, char** errptr) {
+ rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
+ Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
+ Slice(key, keylen), &v->rep);
+ if (!s.ok()) {
+ delete (v);
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ return nullptr;
+ }
+ return v;
+}
+
+rocksdb_pinnableslice_t* rocksdb_get_pinned_cf(
+ rocksdb_t* db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, char** errptr) {
+ rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
+ Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
+ &v->rep);
+ if (!s.ok()) {
+ delete v;
+ if (!s.IsNotFound()) {
+ SaveError(errptr, s);
+ }
+ return nullptr;
+ }
+ return v;
+}
+
+void rocksdb_pinnableslice_destroy(rocksdb_pinnableslice_t* v) { delete v; }
+
+const char* rocksdb_pinnableslice_value(const rocksdb_pinnableslice_t* v,
+ size_t* vlen) {
+ if (!v) {
+ *vlen = 0;
+ return nullptr;
+ }
+
+ *vlen = v->rep.size();
+ return v->rep.data();
+}
+
+// container to keep databases and caches in order to use
+// ROCKSDB_NAMESPACE::MemoryUtil
+struct rocksdb_memory_consumers_t {
+ std::vector<rocksdb_t*> dbs;
+ std::unordered_set<rocksdb_cache_t*> caches;
+};
+
+// initializes new container of memory consumers
+rocksdb_memory_consumers_t* rocksdb_memory_consumers_create() {
+ return new rocksdb_memory_consumers_t;
+}
+
+// adds datatabase to the container of memory consumers
+void rocksdb_memory_consumers_add_db(rocksdb_memory_consumers_t* consumers,
+ rocksdb_t* db) {
+ consumers->dbs.push_back(db);
+}
+
+// adds cache to the container of memory consumers
+void rocksdb_memory_consumers_add_cache(rocksdb_memory_consumers_t* consumers,
+ rocksdb_cache_t* cache) {
+ consumers->caches.insert(cache);
+}
+
+// deletes container with memory consumers
+void rocksdb_memory_consumers_destroy(rocksdb_memory_consumers_t* consumers) {
+ delete consumers;
+}
+
+// contains memory usage statistics provided by ROCKSDB_NAMESPACE::MemoryUtil
+struct rocksdb_memory_usage_t {
+ uint64_t mem_table_total;
+ uint64_t mem_table_unflushed;
+ uint64_t mem_table_readers_total;
+ uint64_t cache_total;
+};
+
+// estimates amount of memory occupied by consumers (dbs and caches)
+rocksdb_memory_usage_t* rocksdb_approximate_memory_usage_create(
+ rocksdb_memory_consumers_t* consumers, char** errptr) {
+
+ vector<DB*> dbs;
+ for (auto db : consumers->dbs) {
+ dbs.push_back(db->rep);
+ }
+
+ unordered_set<const Cache*> cache_set;
+ for (auto cache : consumers->caches) {
+ cache_set.insert(const_cast<const Cache*>(cache->rep.get()));
+ }
+
+ std::map<ROCKSDB_NAMESPACE::MemoryUtil::UsageType, uint64_t> usage_by_type;
+
+ auto status = MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set,
+ &usage_by_type);
+ if (SaveError(errptr, status)) {
+ return nullptr;
+ }
+
+ auto result = new rocksdb_memory_usage_t;
+ result->mem_table_total = usage_by_type[MemoryUtil::kMemTableTotal];
+ result->mem_table_unflushed = usage_by_type[MemoryUtil::kMemTableUnFlushed];
+ result->mem_table_readers_total = usage_by_type[MemoryUtil::kTableReadersTotal];
+ result->cache_total = usage_by_type[MemoryUtil::kCacheTotal];
+ return result;
+}
+
+uint64_t rocksdb_approximate_memory_usage_get_mem_table_total(
+ rocksdb_memory_usage_t* memory_usage) {
+ return memory_usage->mem_table_total;
+}
+
+uint64_t rocksdb_approximate_memory_usage_get_mem_table_unflushed(
+ rocksdb_memory_usage_t* memory_usage) {
+ return memory_usage->mem_table_unflushed;
+}
+
+uint64_t rocksdb_approximate_memory_usage_get_mem_table_readers_total(
+ rocksdb_memory_usage_t* memory_usage) {
+ return memory_usage->mem_table_readers_total;
+}
+
+uint64_t rocksdb_approximate_memory_usage_get_cache_total(
+ rocksdb_memory_usage_t* memory_usage) {
+ return memory_usage->cache_total;
+}
+
+// deletes container with memory usage estimates
+void rocksdb_approximate_memory_usage_destroy(rocksdb_memory_usage_t* usage) {
+ delete usage;
+}
+
+} // end extern "C"
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/c_test.c b/storage/rocksdb/rocksdb/db/c_test.c
new file mode 100644
index 00000000..cf2e266f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/c_test.c
@@ -0,0 +1,1866 @@
+/* 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 <stdio.h>
+
+#ifndef ROCKSDB_LITE // Lite does not support C API
+
+#include "rocksdb/c.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+#include <inttypes.h>
+
+// Can not use port/port.h macros as this is a c file
+#ifdef OS_WIN
+#include <windows.h>
+
+// Ok for uniqueness
+int geteuid() {
+ int result = 0;
+
+ result = ((int)GetCurrentProcessId() << 16);
+ result |= (int)GetCurrentThreadId();
+
+ return result;
+}
+
+// VS < 2015
+#if defined(_MSC_VER) && (_MSC_VER < 1900)
+#define snprintf _snprintf
+#endif
+
+#endif
+
+const char* phase = "";
+static char dbname[200];
+static char sstfilename[200];
+static char dbbackupname[200];
+static char dbcheckpointname[200];
+static char dbpathname[200];
+static char secondary_path[200];
+
+static void StartPhase(const char* name) {
+ fprintf(stderr, "=== Test %s\n", name);
+ phase = name;
+}
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning (disable: 4996) // getenv security warning
+#endif
+static const char* GetTempDir(void) {
+ const char* ret = getenv("TEST_TMPDIR");
+ if (ret == NULL || ret[0] == '\0')
+ ret = "/tmp";
+ return ret;
+}
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#define CheckNoError(err) \
+ if ((err) != NULL) { \
+ fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, phase, (err)); \
+ abort(); \
+ }
+
+#define CheckCondition(cond) \
+ if (!(cond)) { \
+ fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, phase, #cond); \
+ abort(); \
+ }
+
+static void CheckEqual(const char* expected, const char* v, size_t n) {
+ if (expected == NULL && v == NULL) {
+ // ok
+ } else if (expected != NULL && v != NULL && n == strlen(expected) &&
+ memcmp(expected, v, n) == 0) {
+ // ok
+ return;
+ } else {
+ fprintf(stderr, "%s: expected '%s', got '%s'\n",
+ phase,
+ (expected ? expected : "(null)"),
+ (v ? v : "(null"));
+ abort();
+ }
+}
+
+static void Free(char** ptr) {
+ if (*ptr) {
+ free(*ptr);
+ *ptr = NULL;
+ }
+}
+
+static void CheckValue(
+ char* err,
+ const char* expected,
+ char** actual,
+ size_t actual_length) {
+ CheckNoError(err);
+ CheckEqual(expected, *actual, actual_length);
+ Free(actual);
+}
+
+static void CheckGet(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ const char* key,
+ const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_get(db, options, key, strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+static void CheckGetCF(
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* handle,
+ const char* key,
+ const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_get_cf(db, options, handle, key, strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+static void CheckPinGet(rocksdb_t* db, const rocksdb_readoptions_t* options,
+ const char* key, const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ const char* val;
+ rocksdb_pinnableslice_t* p;
+ p = rocksdb_get_pinned(db, options, key, strlen(key), &err);
+ CheckNoError(err);
+ val = rocksdb_pinnableslice_value(p, &val_len);
+ CheckEqual(expected, val, val_len);
+ rocksdb_pinnableslice_destroy(p);
+}
+
+static void CheckPinGetCF(rocksdb_t* db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* handle,
+ const char* key, const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ const char* val;
+ rocksdb_pinnableslice_t* p;
+ p = rocksdb_get_pinned_cf(db, options, handle, key, strlen(key), &err);
+ CheckNoError(err);
+ val = rocksdb_pinnableslice_value(p, &val_len);
+ CheckEqual(expected, val, val_len);
+ rocksdb_pinnableslice_destroy(p);
+}
+
+static void CheckIter(rocksdb_iterator_t* iter,
+ const char* key, const char* val) {
+ size_t len;
+ const char* str;
+ str = rocksdb_iter_key(iter, &len);
+ CheckEqual(key, str, len);
+ str = rocksdb_iter_value(iter, &len);
+ CheckEqual(val, str, len);
+}
+
+// Callback from rocksdb_writebatch_iterate()
+static void CheckPut(void* ptr,
+ const char* k, size_t klen,
+ const char* v, size_t vlen) {
+ int* state = (int*) ptr;
+ CheckCondition(*state < 2);
+ switch (*state) {
+ case 0:
+ CheckEqual("bar", k, klen);
+ CheckEqual("b", v, vlen);
+ break;
+ case 1:
+ CheckEqual("box", k, klen);
+ CheckEqual("c", v, vlen);
+ break;
+ }
+ (*state)++;
+}
+
+// Callback from rocksdb_writebatch_iterate()
+static void CheckDel(void* ptr, const char* k, size_t klen) {
+ int* state = (int*) ptr;
+ CheckCondition(*state == 2);
+ CheckEqual("bar", k, klen);
+ (*state)++;
+}
+
+static void CmpDestroy(void* arg) { (void)arg; }
+
+static int CmpCompare(void* arg, const char* a, size_t alen,
+ const char* b, size_t blen) {
+ (void)arg;
+ size_t n = (alen < blen) ? alen : blen;
+ int r = memcmp(a, b, n);
+ if (r == 0) {
+ if (alen < blen) r = -1;
+ else if (alen > blen) r = +1;
+ }
+ return r;
+}
+
+static const char* CmpName(void* arg) {
+ (void)arg;
+ return "foo";
+}
+
+// Custom filter policy
+static unsigned char fake_filter_result = 1;
+static void FilterDestroy(void* arg) { (void)arg; }
+static const char* FilterName(void* arg) {
+ (void)arg;
+ return "TestFilter";
+}
+static char* FilterCreate(
+ void* arg,
+ const char* const* key_array, const size_t* key_length_array,
+ int num_keys,
+ size_t* filter_length) {
+ (void)arg;
+ (void)key_array;
+ (void)key_length_array;
+ (void)num_keys;
+ *filter_length = 4;
+ char* result = malloc(4);
+ memcpy(result, "fake", 4);
+ return result;
+}
+static unsigned char FilterKeyMatch(
+ void* arg,
+ const char* key, size_t length,
+ const char* filter, size_t filter_length) {
+ (void)arg;
+ (void)key;
+ (void)length;
+ CheckCondition(filter_length == 4);
+ CheckCondition(memcmp(filter, "fake", 4) == 0);
+ return fake_filter_result;
+}
+
+// Custom compaction filter
+static void CFilterDestroy(void* arg) { (void)arg; }
+static const char* CFilterName(void* arg) {
+ (void)arg;
+ return "foo";
+}
+static unsigned char CFilterFilter(void* arg, int level, const char* key,
+ size_t key_length,
+ const char* existing_value,
+ size_t value_length, char** new_value,
+ size_t* new_value_length,
+ unsigned char* value_changed) {
+ (void)arg;
+ (void)level;
+ (void)existing_value;
+ (void)value_length;
+ if (key_length == 3) {
+ if (memcmp(key, "bar", key_length) == 0) {
+ return 1;
+ } else if (memcmp(key, "baz", key_length) == 0) {
+ *value_changed = 1;
+ *new_value = "newbazvalue";
+ *new_value_length = 11;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static void CFilterFactoryDestroy(void* arg) { (void)arg; }
+static const char* CFilterFactoryName(void* arg) {
+ (void)arg;
+ return "foo";
+}
+static rocksdb_compactionfilter_t* CFilterCreate(
+ void* arg, rocksdb_compactionfiltercontext_t* context) {
+ (void)arg;
+ (void)context;
+ return rocksdb_compactionfilter_create(NULL, CFilterDestroy, CFilterFilter,
+ CFilterName);
+}
+
+static rocksdb_t* CheckCompaction(rocksdb_t* db, rocksdb_options_t* options,
+ rocksdb_readoptions_t* roptions,
+ rocksdb_writeoptions_t* woptions) {
+ char* err = NULL;
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "foo", 3, "foovalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "foovalue");
+ rocksdb_put(db, woptions, "bar", 3, "barvalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "bar", "barvalue");
+ rocksdb_put(db, woptions, "baz", 3, "bazvalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "baz", "bazvalue");
+
+ // Force compaction
+ rocksdb_compact_range(db, NULL, 0, NULL, 0);
+ // should have filtered bar, but not foo
+ CheckGet(db, roptions, "foo", "foovalue");
+ CheckGet(db, roptions, "bar", NULL);
+ CheckGet(db, roptions, "baz", "newbazvalue");
+ return db;
+}
+
+// Custom merge operator
+static void MergeOperatorDestroy(void* arg) { (void)arg; }
+static const char* MergeOperatorName(void* arg) {
+ (void)arg;
+ return "TestMergeOperator";
+}
+static char* MergeOperatorFullMerge(
+ void* arg,
+ const char* key, size_t key_length,
+ const char* existing_value, size_t existing_value_length,
+ const char* const* operands_list, const size_t* operands_list_length,
+ int num_operands,
+ unsigned char* success, size_t* new_value_length) {
+ (void)arg;
+ (void)key;
+ (void)key_length;
+ (void)existing_value;
+ (void)existing_value_length;
+ (void)operands_list;
+ (void)operands_list_length;
+ (void)num_operands;
+ *new_value_length = 4;
+ *success = 1;
+ char* result = malloc(4);
+ memcpy(result, "fake", 4);
+ return result;
+}
+static char* MergeOperatorPartialMerge(
+ void* arg,
+ const char* key, size_t key_length,
+ const char* const* operands_list, const size_t* operands_list_length,
+ int num_operands,
+ unsigned char* success, size_t* new_value_length) {
+ (void)arg;
+ (void)key;
+ (void)key_length;
+ (void)operands_list;
+ (void)operands_list_length;
+ (void)num_operands;
+ *new_value_length = 4;
+ *success = 1;
+ char* result = malloc(4);
+ memcpy(result, "fake", 4);
+ return result;
+}
+
+static void CheckTxnGet(
+ rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options,
+ const char* key,
+ const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_transaction_get(txn, options, key, strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+static void CheckTxnGetCF(rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_transaction_get_cf(txn, options, column_family, key,
+ strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+static void CheckTxnDBGet(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_readoptions_t* options,
+ const char* key,
+ const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_transactiondb_get(txn_db, options, key, strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, const char* expected) {
+ char* err = NULL;
+ size_t val_len;
+ char* val;
+ val = rocksdb_transactiondb_get_cf(txn_db, options, column_family, key,
+ strlen(key), &val_len, &err);
+ CheckNoError(err);
+ CheckEqual(expected, val, val_len);
+ Free(&val);
+}
+
+int main(int argc, char** argv) {
+ (void)argc;
+ (void)argv;
+ rocksdb_t* db;
+ rocksdb_comparator_t* cmp;
+ rocksdb_cache_t* cache;
+ rocksdb_dbpath_t *dbpath;
+ rocksdb_env_t* env;
+ rocksdb_options_t* options;
+ rocksdb_compactoptions_t* coptions;
+ rocksdb_block_based_table_options_t* table_options;
+ rocksdb_readoptions_t* roptions;
+ rocksdb_writeoptions_t* woptions;
+ rocksdb_ratelimiter_t* rate_limiter;
+ rocksdb_transactiondb_t* txn_db;
+ rocksdb_transactiondb_options_t* txn_db_options;
+ rocksdb_transaction_t* txn;
+ rocksdb_transaction_options_t* txn_options;
+ rocksdb_optimistictransactiondb_t* otxn_db;
+ rocksdb_optimistictransaction_options_t* otxn_options;
+ char* err = NULL;
+ int run = -1;
+
+ snprintf(dbname, sizeof(dbname),
+ "%s/rocksdb_c_test-%d",
+ GetTempDir(),
+ ((int) geteuid()));
+
+ snprintf(dbbackupname, sizeof(dbbackupname),
+ "%s/rocksdb_c_test-%d-backup",
+ GetTempDir(),
+ ((int) geteuid()));
+
+ snprintf(dbcheckpointname, sizeof(dbcheckpointname),
+ "%s/rocksdb_c_test-%d-checkpoint",
+ GetTempDir(),
+ ((int) geteuid()));
+
+ snprintf(sstfilename, sizeof(sstfilename),
+ "%s/rocksdb_c_test-%d-sst",
+ GetTempDir(),
+ ((int)geteuid()));
+
+ snprintf(dbpathname, sizeof(dbpathname),
+ "%s/rocksdb_c_test-%d-dbpath",
+ GetTempDir(),
+ ((int) geteuid()));
+
+ StartPhase("create_objects");
+ cmp = rocksdb_comparator_create(NULL, CmpDestroy, CmpCompare, CmpName);
+ dbpath = rocksdb_dbpath_create(dbpathname, 1024 * 1024);
+ env = rocksdb_create_default_env();
+ cache = rocksdb_cache_create_lru(100000);
+
+ options = rocksdb_options_create();
+ rocksdb_options_set_comparator(options, cmp);
+ rocksdb_options_set_error_if_exists(options, 1);
+ rocksdb_options_set_env(options, env);
+ rocksdb_options_set_info_log(options, NULL);
+ rocksdb_options_set_write_buffer_size(options, 100000);
+ rocksdb_options_set_paranoid_checks(options, 1);
+ rocksdb_options_set_max_open_files(options, 10);
+ rocksdb_options_set_base_background_compactions(options, 1);
+
+ table_options = rocksdb_block_based_options_create();
+ rocksdb_block_based_options_set_block_cache(table_options, cache);
+ rocksdb_block_based_options_set_data_block_index_type(table_options, 1);
+ rocksdb_block_based_options_set_data_block_hash_ratio(table_options, 0.75);
+ rocksdb_options_set_block_based_table_factory(options, table_options);
+
+ rocksdb_options_set_compression(options, rocksdb_no_compression);
+ rocksdb_options_set_compression_options(options, -14, -1, 0, 0);
+ int compression_levels[] = {rocksdb_no_compression, rocksdb_no_compression,
+ rocksdb_no_compression, rocksdb_no_compression};
+ rocksdb_options_set_compression_per_level(options, compression_levels, 4);
+ rate_limiter = rocksdb_ratelimiter_create(1000 * 1024 * 1024, 100 * 1000, 10);
+ rocksdb_options_set_ratelimiter(options, rate_limiter);
+ rocksdb_ratelimiter_destroy(rate_limiter);
+
+ roptions = rocksdb_readoptions_create();
+ rocksdb_readoptions_set_verify_checksums(roptions, 1);
+ rocksdb_readoptions_set_fill_cache(roptions, 1);
+
+ woptions = rocksdb_writeoptions_create();
+ rocksdb_writeoptions_set_sync(woptions, 1);
+
+ coptions = rocksdb_compactoptions_create();
+ rocksdb_compactoptions_set_exclusive_manual_compaction(coptions, 1);
+
+ StartPhase("destroy");
+ rocksdb_destroy_db(options, dbname, &err);
+ Free(&err);
+
+ StartPhase("open_error");
+ rocksdb_open(options, dbname, &err);
+ CheckCondition(err != NULL);
+ Free(&err);
+
+ StartPhase("open");
+ rocksdb_options_set_create_if_missing(options, 1);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", NULL);
+
+ StartPhase("put");
+ rocksdb_put(db, woptions, "foo", 3, "hello", 5, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "hello");
+
+ StartPhase("backup_and_restore");
+ {
+ rocksdb_destroy_db(options, dbbackupname, &err);
+ CheckNoError(err);
+
+ rocksdb_backup_engine_t *be = rocksdb_backup_engine_open(options, dbbackupname, &err);
+ CheckNoError(err);
+
+ rocksdb_backup_engine_create_new_backup(be, db, &err);
+ CheckNoError(err);
+
+ // need a change to trigger a new backup
+ rocksdb_delete(db, woptions, "does-not-exist", 14, &err);
+ CheckNoError(err);
+
+ rocksdb_backup_engine_create_new_backup(be, db, &err);
+ CheckNoError(err);
+
+ const rocksdb_backup_engine_info_t* bei = rocksdb_backup_engine_get_backup_info(be);
+ CheckCondition(rocksdb_backup_engine_info_count(bei) > 1);
+ rocksdb_backup_engine_info_destroy(bei);
+
+ rocksdb_backup_engine_purge_old_backups(be, 1, &err);
+ CheckNoError(err);
+
+ bei = rocksdb_backup_engine_get_backup_info(be);
+ CheckCondition(rocksdb_backup_engine_info_count(bei) == 1);
+ rocksdb_backup_engine_info_destroy(bei);
+
+ rocksdb_delete(db, woptions, "foo", 3, &err);
+ CheckNoError(err);
+
+ rocksdb_close(db);
+
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_restore_options_t *restore_options = rocksdb_restore_options_create();
+ rocksdb_restore_options_set_keep_log_files(restore_options, 0);
+ rocksdb_backup_engine_restore_db_from_latest_backup(be, dbname, dbname, restore_options, &err);
+ CheckNoError(err);
+ rocksdb_restore_options_destroy(restore_options);
+
+ rocksdb_options_set_error_if_exists(options, 0);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_options_set_error_if_exists(options, 1);
+
+ CheckGet(db, roptions, "foo", "hello");
+
+ rocksdb_backup_engine_close(be);
+ }
+
+ StartPhase("checkpoint");
+ {
+ rocksdb_destroy_db(options, dbcheckpointname, &err);
+ CheckNoError(err);
+
+ rocksdb_checkpoint_t* checkpoint = rocksdb_checkpoint_object_create(db, &err);
+ CheckNoError(err);
+
+ rocksdb_checkpoint_create(checkpoint, dbcheckpointname, 0, &err);
+ CheckNoError(err);
+
+ // start a new database from the checkpoint
+ rocksdb_close(db);
+ rocksdb_options_set_error_if_exists(options, 0);
+ db = rocksdb_open(options, dbcheckpointname, &err);
+ CheckNoError(err);
+
+ CheckGet(db, roptions, "foo", "hello");
+
+ rocksdb_checkpoint_object_destroy(checkpoint);
+
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbcheckpointname, &err);
+ CheckNoError(err);
+
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_options_set_error_if_exists(options, 1);
+ }
+
+ StartPhase("compactall");
+ rocksdb_compact_range(db, NULL, 0, NULL, 0);
+ CheckGet(db, roptions, "foo", "hello");
+
+ StartPhase("compactrange");
+ rocksdb_compact_range(db, "a", 1, "z", 1);
+ CheckGet(db, roptions, "foo", "hello");
+
+ StartPhase("compactallopt");
+ rocksdb_compact_range_opt(db, coptions, NULL, 0, NULL, 0);
+ CheckGet(db, roptions, "foo", "hello");
+
+ StartPhase("compactrangeopt");
+ rocksdb_compact_range_opt(db, coptions, "a", 1, "z", 1);
+ CheckGet(db, roptions, "foo", "hello");
+
+ // Simple check cache usage
+ StartPhase("cache_usage");
+ {
+ rocksdb_readoptions_set_pin_data(roptions, 1);
+ rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
+ rocksdb_iter_seek(iter, "foo", 3);
+
+ size_t usage = rocksdb_cache_get_usage(cache);
+ CheckCondition(usage > 0);
+
+ size_t pin_usage = rocksdb_cache_get_pinned_usage(cache);
+ CheckCondition(pin_usage > 0);
+
+ rocksdb_iter_next(iter);
+ rocksdb_iter_destroy(iter);
+ rocksdb_readoptions_set_pin_data(roptions, 0);
+ }
+
+ StartPhase("addfile");
+ {
+ rocksdb_envoptions_t* env_opt = rocksdb_envoptions_create();
+ rocksdb_options_t* io_options = rocksdb_options_create();
+ rocksdb_sstfilewriter_t* writer =
+ rocksdb_sstfilewriter_create(env_opt, io_options);
+
+ remove(sstfilename);
+ rocksdb_sstfilewriter_open(writer, sstfilename, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk1", 5, "v1", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk2", 5, "v2", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk3", 5, "v3", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_finish(writer, &err);
+ CheckNoError(err);
+
+ rocksdb_ingestexternalfileoptions_t* ing_opt =
+ rocksdb_ingestexternalfileoptions_create();
+ const char* file_list[1] = {sstfilename};
+ rocksdb_ingest_external_file(db, file_list, 1, ing_opt, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "sstk1", "v1");
+ CheckGet(db, roptions, "sstk2", "v2");
+ CheckGet(db, roptions, "sstk3", "v3");
+
+ remove(sstfilename);
+ rocksdb_sstfilewriter_open(writer, sstfilename, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk2", 5, "v4", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk22", 6, "v5", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_put(writer, "sstk3", 5, "v6", 2, &err);
+ CheckNoError(err);
+ rocksdb_sstfilewriter_finish(writer, &err);
+ CheckNoError(err);
+
+ rocksdb_ingest_external_file(db, file_list, 1, ing_opt, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "sstk1", "v1");
+ CheckGet(db, roptions, "sstk2", "v4");
+ CheckGet(db, roptions, "sstk22", "v5");
+ CheckGet(db, roptions, "sstk3", "v6");
+
+ rocksdb_ingestexternalfileoptions_destroy(ing_opt);
+ rocksdb_sstfilewriter_destroy(writer);
+ rocksdb_options_destroy(io_options);
+ rocksdb_envoptions_destroy(env_opt);
+
+ // Delete all keys we just ingested
+ rocksdb_delete(db, woptions, "sstk1", 5, &err);
+ CheckNoError(err);
+ rocksdb_delete(db, woptions, "sstk2", 5, &err);
+ CheckNoError(err);
+ rocksdb_delete(db, woptions, "sstk22", 6, &err);
+ CheckNoError(err);
+ rocksdb_delete(db, woptions, "sstk3", 5, &err);
+ CheckNoError(err);
+ }
+
+ StartPhase("writebatch");
+ {
+ rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
+ rocksdb_writebatch_put(wb, "foo", 3, "a", 1);
+ rocksdb_writebatch_clear(wb);
+ rocksdb_writebatch_put(wb, "bar", 3, "b", 1);
+ rocksdb_writebatch_put(wb, "box", 3, "c", 1);
+ rocksdb_writebatch_delete(wb, "bar", 3);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "hello");
+ CheckGet(db, roptions, "bar", NULL);
+ CheckGet(db, roptions, "box", "c");
+ int pos = 0;
+ rocksdb_writebatch_iterate(wb, &pos, CheckPut, CheckDel);
+ CheckCondition(pos == 3);
+ rocksdb_writebatch_clear(wb);
+ rocksdb_writebatch_put(wb, "bar", 3, "b", 1);
+ rocksdb_writebatch_put(wb, "bay", 3, "d", 1);
+ rocksdb_writebatch_delete_range(wb, "bar", 3, "bay", 3);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "bar", NULL);
+ CheckGet(db, roptions, "bay", "d");
+ rocksdb_writebatch_clear(wb);
+ const char* start_list[1] = {"bay"};
+ const size_t start_sizes[1] = {3};
+ const char* end_list[1] = {"baz"};
+ const size_t end_sizes[1] = {3};
+ rocksdb_writebatch_delete_rangev(wb, 1, start_list, start_sizes, end_list,
+ end_sizes);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "bay", NULL);
+ rocksdb_writebatch_destroy(wb);
+ }
+
+ StartPhase("writebatch_vectors");
+ {
+ rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
+ const char* k_list[2] = { "z", "ap" };
+ const size_t k_sizes[2] = { 1, 2 };
+ const char* v_list[3] = { "x", "y", "z" };
+ const size_t v_sizes[3] = { 1, 1, 1 };
+ rocksdb_writebatch_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", "xyz");
+ rocksdb_writebatch_delete(wb, "zap", 3);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", NULL);
+ rocksdb_writebatch_destroy(wb);
+ }
+
+ StartPhase("writebatch_savepoint");
+ {
+ rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
+ rocksdb_writebatch_set_save_point(wb);
+ rocksdb_writebatch_set_save_point(wb);
+ const char* k_list[2] = {"z", "ap"};
+ const size_t k_sizes[2] = {1, 2};
+ const char* v_list[3] = {"x", "y", "z"};
+ const size_t v_sizes[3] = {1, 1, 1};
+ rocksdb_writebatch_pop_save_point(wb, &err);
+ CheckNoError(err);
+ rocksdb_writebatch_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
+ rocksdb_writebatch_rollback_to_save_point(wb, &err);
+ CheckNoError(err);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", NULL);
+ rocksdb_writebatch_destroy(wb);
+ }
+
+ StartPhase("writebatch_rep");
+ {
+ rocksdb_writebatch_t* wb1 = rocksdb_writebatch_create();
+ rocksdb_writebatch_put(wb1, "baz", 3, "d", 1);
+ rocksdb_writebatch_put(wb1, "quux", 4, "e", 1);
+ rocksdb_writebatch_delete(wb1, "quux", 4);
+ size_t repsize1 = 0;
+ const char* rep = rocksdb_writebatch_data(wb1, &repsize1);
+ rocksdb_writebatch_t* wb2 = rocksdb_writebatch_create_from(rep, repsize1);
+ CheckCondition(rocksdb_writebatch_count(wb1) ==
+ rocksdb_writebatch_count(wb2));
+ size_t repsize2 = 0;
+ CheckCondition(
+ memcmp(rep, rocksdb_writebatch_data(wb2, &repsize2), repsize1) == 0);
+ rocksdb_writebatch_destroy(wb1);
+ rocksdb_writebatch_destroy(wb2);
+ }
+
+ StartPhase("writebatch_wi");
+ {
+ rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1);
+ rocksdb_writebatch_wi_put(wbi, "foo", 3, "a", 1);
+ rocksdb_writebatch_wi_clear(wbi);
+ rocksdb_writebatch_wi_put(wbi, "bar", 3, "b", 1);
+ rocksdb_writebatch_wi_put(wbi, "box", 3, "c", 1);
+ rocksdb_writebatch_wi_delete(wbi, "bar", 3);
+ int count = rocksdb_writebatch_wi_count(wbi);
+ CheckCondition(count == 3);
+ size_t size;
+ char* value;
+ value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "box", 3, &size, &err);
+ CheckValue(err, "c", &value, size);
+ value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "bar", 3, &size, &err);
+ CheckValue(err, NULL, &value, size);
+ value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions, "foo", 3, &size, &err);
+ CheckValue(err, "hello", &value, size);
+ value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions, "box", 3, &size, &err);
+ CheckValue(err, "c", &value, size);
+ rocksdb_write_writebatch_wi(db, woptions, wbi, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "hello");
+ CheckGet(db, roptions, "bar", NULL);
+ CheckGet(db, roptions, "box", "c");
+ int pos = 0;
+ rocksdb_writebatch_wi_iterate(wbi, &pos, CheckPut, CheckDel);
+ CheckCondition(pos == 3);
+ rocksdb_writebatch_wi_clear(wbi);
+ rocksdb_writebatch_wi_destroy(wbi);
+ }
+
+ StartPhase("writebatch_wi_vectors");
+ {
+ rocksdb_writebatch_wi_t* wb = rocksdb_writebatch_wi_create(0, 1);
+ const char* k_list[2] = { "z", "ap" };
+ const size_t k_sizes[2] = { 1, 2 };
+ const char* v_list[3] = { "x", "y", "z" };
+ const size_t v_sizes[3] = { 1, 1, 1 };
+ rocksdb_writebatch_wi_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
+ rocksdb_write_writebatch_wi(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", "xyz");
+ rocksdb_writebatch_wi_delete(wb, "zap", 3);
+ rocksdb_write_writebatch_wi(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", NULL);
+ rocksdb_writebatch_wi_destroy(wb);
+ }
+
+ StartPhase("writebatch_wi_savepoint");
+ {
+ rocksdb_writebatch_wi_t* wb = rocksdb_writebatch_wi_create(0, 1);
+ rocksdb_writebatch_wi_set_save_point(wb);
+ const char* k_list[2] = {"z", "ap"};
+ const size_t k_sizes[2] = {1, 2};
+ const char* v_list[3] = {"x", "y", "z"};
+ const size_t v_sizes[3] = {1, 1, 1};
+ rocksdb_writebatch_wi_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
+ rocksdb_writebatch_wi_rollback_to_save_point(wb, &err);
+ CheckNoError(err);
+ rocksdb_write_writebatch_wi(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "zap", NULL);
+ rocksdb_writebatch_wi_destroy(wb);
+ }
+
+ StartPhase("iter");
+ {
+ rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_next(iter);
+ CheckIter(iter, "foo", "hello");
+ rocksdb_iter_prev(iter);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_prev(iter);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_last(iter);
+ CheckIter(iter, "foo", "hello");
+ rocksdb_iter_seek(iter, "b", 1);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_seek_for_prev(iter, "g", 1);
+ CheckIter(iter, "foo", "hello");
+ rocksdb_iter_seek_for_prev(iter, "box", 3);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+ }
+
+ StartPhase("wbwi_iter");
+ {
+ rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, roptions);
+ rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1);
+ rocksdb_writebatch_wi_put(wbi, "bar", 3, "b", 1);
+ rocksdb_writebatch_wi_delete(wbi, "foo", 3);
+ rocksdb_iterator_t* iter =
+ rocksdb_writebatch_wi_create_iterator_with_base(wbi, base_iter);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "bar", "b");
+ rocksdb_iter_next(iter);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_prev(iter);
+ CheckIter(iter, "bar", "b");
+ rocksdb_iter_prev(iter);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_last(iter);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_seek(iter, "b", 1);
+ CheckIter(iter, "bar", "b");
+ rocksdb_iter_seek_for_prev(iter, "c", 1);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_seek_for_prev(iter, "box", 3);
+ CheckIter(iter, "box", "c");
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+ rocksdb_writebatch_wi_destroy(wbi);
+ }
+
+ StartPhase("multiget");
+ {
+ const char* keys[3] = { "box", "foo", "notfound" };
+ const size_t keys_sizes[3] = { 3, 3, 8 };
+ char* vals[3];
+ size_t vals_sizes[3];
+ char* errs[3];
+ rocksdb_multi_get(db, roptions, 3, keys, keys_sizes, vals, vals_sizes, errs);
+
+ int i;
+ for (i = 0; i < 3; i++) {
+ CheckEqual(NULL, errs[i], 0);
+ switch (i) {
+ case 0:
+ CheckEqual("c", vals[i], vals_sizes[i]);
+ break;
+ case 1:
+ CheckEqual("hello", vals[i], vals_sizes[i]);
+ break;
+ case 2:
+ CheckEqual(NULL, vals[i], vals_sizes[i]);
+ break;
+ }
+ Free(&vals[i]);
+ }
+ }
+
+ StartPhase("pin_get");
+ {
+ CheckPinGet(db, roptions, "box", "c");
+ CheckPinGet(db, roptions, "foo", "hello");
+ CheckPinGet(db, roptions, "notfound", NULL);
+ }
+
+ StartPhase("approximate_sizes");
+ {
+ int i;
+ int n = 20000;
+ char keybuf[100];
+ char valbuf[100];
+ uint64_t sizes[2];
+ const char* start[2] = { "a", "k00000000000000010000" };
+ size_t start_len[2] = { 1, 21 };
+ const char* limit[2] = { "k00000000000000010000", "z" };
+ size_t limit_len[2] = { 21, 1 };
+ rocksdb_writeoptions_set_sync(woptions, 0);
+ for (i = 0; i < n; i++) {
+ snprintf(keybuf, sizeof(keybuf), "k%020d", i);
+ snprintf(valbuf, sizeof(valbuf), "v%020d", i);
+ rocksdb_put(db, woptions, keybuf, strlen(keybuf), valbuf, strlen(valbuf),
+ &err);
+ CheckNoError(err);
+ }
+ rocksdb_approximate_sizes(db, 2, start, start_len, limit, limit_len, sizes);
+ CheckCondition(sizes[0] > 0);
+ CheckCondition(sizes[1] > 0);
+ }
+
+ StartPhase("property");
+ {
+ char* prop = rocksdb_property_value(db, "nosuchprop");
+ CheckCondition(prop == NULL);
+ prop = rocksdb_property_value(db, "rocksdb.stats");
+ CheckCondition(prop != NULL);
+ Free(&prop);
+ }
+
+ StartPhase("snapshot");
+ {
+ const rocksdb_snapshot_t* snap;
+ snap = rocksdb_create_snapshot(db);
+ rocksdb_delete(db, woptions, "foo", 3, &err);
+ CheckNoError(err);
+ rocksdb_readoptions_set_snapshot(roptions, snap);
+ CheckGet(db, roptions, "foo", "hello");
+ rocksdb_readoptions_set_snapshot(roptions, NULL);
+ CheckGet(db, roptions, "foo", NULL);
+ rocksdb_release_snapshot(db, snap);
+ }
+
+ StartPhase("repair");
+ {
+ // If we do not compact here, then the lazy deletion of
+ // files (https://reviews.facebook.net/D6123) would leave
+ // around deleted files and the repair process will find
+ // those files and put them back into the database.
+ rocksdb_compact_range(db, NULL, 0, NULL, 0);
+ rocksdb_close(db);
+ rocksdb_options_set_create_if_missing(options, 0);
+ rocksdb_options_set_error_if_exists(options, 0);
+ rocksdb_options_set_wal_recovery_mode(options, 2);
+ rocksdb_repair_db(options, dbname, &err);
+ CheckNoError(err);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", NULL);
+ CheckGet(db, roptions, "bar", NULL);
+ CheckGet(db, roptions, "box", "c");
+ rocksdb_options_set_create_if_missing(options, 1);
+ rocksdb_options_set_error_if_exists(options, 1);
+ }
+
+ StartPhase("filter");
+ for (run = 0; run <= 2; run++) {
+ // First run uses custom filter
+ // Second run uses old block-based bloom filter
+ // Third run uses full bloom filter
+ CheckNoError(err);
+ rocksdb_filterpolicy_t* policy;
+ if (run == 0) {
+ policy = rocksdb_filterpolicy_create(NULL, FilterDestroy, FilterCreate,
+ FilterKeyMatch, NULL, FilterName);
+ } else if (run == 1) {
+ policy = rocksdb_filterpolicy_create_bloom(8);
+ } else {
+ policy = rocksdb_filterpolicy_create_bloom_full(8);
+ }
+ rocksdb_block_based_options_set_filter_policy(table_options, policy);
+
+ // Create new database
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ rocksdb_options_set_block_based_table_factory(options, table_options);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "foo", 3, "foovalue", 8, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "bar", 3, "barvalue", 8, &err);
+ CheckNoError(err);
+
+ {
+ // Add enough keys to get just one reasonably populated Bloom filter
+ const int keys_to_add = 1500;
+ int i;
+ char keybuf[100];
+ for (i = 0; i < keys_to_add; i++) {
+ snprintf(keybuf, sizeof(keybuf), "yes%020d", i);
+ rocksdb_put(db, woptions, keybuf, strlen(keybuf), "val", 3, &err);
+ CheckNoError(err);
+ }
+ }
+ rocksdb_compact_range(db, NULL, 0, NULL, 0);
+
+ fake_filter_result = 1;
+ CheckGet(db, roptions, "foo", "foovalue");
+ CheckGet(db, roptions, "bar", "barvalue");
+ if (run == 0) {
+ // Must not find value when custom filter returns false
+ fake_filter_result = 0;
+ CheckGet(db, roptions, "foo", NULL);
+ CheckGet(db, roptions, "bar", NULL);
+ fake_filter_result = 1;
+
+ CheckGet(db, roptions, "foo", "foovalue");
+ CheckGet(db, roptions, "bar", "barvalue");
+ }
+
+ {
+ // Query some keys not added to identify Bloom filter implementation
+ // from false positive queries, using perfcontext to detect Bloom
+ // filter behavior
+ rocksdb_perfcontext_t* perf = rocksdb_perfcontext_create();
+ rocksdb_perfcontext_reset(perf);
+
+ const int keys_to_query = 10000;
+ int i;
+ char keybuf[100];
+ for (i = 0; i < keys_to_query; i++) {
+ fake_filter_result = i % 2;
+ snprintf(keybuf, sizeof(keybuf), "no%020d", i);
+ CheckGet(db, roptions, keybuf, NULL);
+ }
+
+ const int hits =
+ (int)rocksdb_perfcontext_metric(perf, rocksdb_bloom_sst_hit_count);
+ if (run == 0) {
+ // Due to half true, half false with fake filter result
+ CheckCondition(hits == keys_to_query / 2);
+ } else if (run == 1) {
+ // Essentially a fingerprint of the block-based Bloom schema
+ CheckCondition(hits == 241);
+ } else {
+ // Essentially a fingerprint of the full Bloom schema(s),
+ // format_version < 5, which vary for three different CACHE_LINE_SIZEs
+ CheckCondition(hits == 224 || hits == 180 || hits == 125);
+ }
+ CheckCondition(
+ (keys_to_query - hits) ==
+ (int)rocksdb_perfcontext_metric(perf, rocksdb_bloom_sst_miss_count));
+
+ rocksdb_perfcontext_destroy(perf);
+ }
+
+ // Reset the policy
+ rocksdb_block_based_options_set_filter_policy(table_options, NULL);
+ rocksdb_options_set_block_based_table_factory(options, table_options);
+ }
+
+ StartPhase("compaction_filter");
+ {
+ rocksdb_options_t* options_with_filter = rocksdb_options_create();
+ rocksdb_options_set_create_if_missing(options_with_filter, 1);
+ rocksdb_compactionfilter_t* cfilter;
+ cfilter = rocksdb_compactionfilter_create(NULL, CFilterDestroy,
+ CFilterFilter, CFilterName);
+ // Create new database
+ rocksdb_close(db);
+ rocksdb_destroy_db(options_with_filter, dbname, &err);
+ rocksdb_options_set_compaction_filter(options_with_filter, cfilter);
+ db = CheckCompaction(db, options_with_filter, roptions, woptions);
+
+ rocksdb_options_set_compaction_filter(options_with_filter, NULL);
+ rocksdb_compactionfilter_destroy(cfilter);
+ rocksdb_options_destroy(options_with_filter);
+ }
+
+ StartPhase("compaction_filter_factory");
+ {
+ rocksdb_options_t* options_with_filter_factory = rocksdb_options_create();
+ rocksdb_options_set_create_if_missing(options_with_filter_factory, 1);
+ rocksdb_compactionfilterfactory_t* factory;
+ factory = rocksdb_compactionfilterfactory_create(
+ NULL, CFilterFactoryDestroy, CFilterCreate, CFilterFactoryName);
+ // Create new database
+ rocksdb_close(db);
+ rocksdb_destroy_db(options_with_filter_factory, dbname, &err);
+ rocksdb_options_set_compaction_filter_factory(options_with_filter_factory,
+ factory);
+ db = CheckCompaction(db, options_with_filter_factory, roptions, woptions);
+
+ rocksdb_options_set_compaction_filter_factory(
+ options_with_filter_factory, NULL);
+ rocksdb_options_destroy(options_with_filter_factory);
+ }
+
+ StartPhase("merge_operator");
+ {
+ rocksdb_mergeoperator_t* merge_operator;
+ merge_operator = rocksdb_mergeoperator_create(
+ NULL, MergeOperatorDestroy, MergeOperatorFullMerge,
+ MergeOperatorPartialMerge, NULL, MergeOperatorName);
+ // Create new database
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ rocksdb_options_set_merge_operator(options, merge_operator);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "foo", 3, "foovalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "foovalue");
+ rocksdb_merge(db, woptions, "foo", 3, "barvalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "foo", "fake");
+
+ // Merge of a non-existing value
+ rocksdb_merge(db, woptions, "bar", 3, "barvalue", 8, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "bar", "fake");
+
+ }
+
+ StartPhase("columnfamilies");
+ {
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_options_t* db_options = rocksdb_options_create();
+ rocksdb_options_set_create_if_missing(db_options, 1);
+ db = rocksdb_open(db_options, dbname, &err);
+ CheckNoError(err)
+ rocksdb_column_family_handle_t* cfh;
+ cfh = rocksdb_create_column_family(db, db_options, "cf1", &err);
+ rocksdb_column_family_handle_destroy(cfh);
+ CheckNoError(err);
+ rocksdb_close(db);
+
+ size_t cflen;
+ char** column_fams = rocksdb_list_column_families(db_options, dbname, &cflen, &err);
+ CheckNoError(err);
+ CheckEqual("default", column_fams[0], 7);
+ CheckEqual("cf1", column_fams[1], 3);
+ CheckCondition(cflen == 2);
+ rocksdb_list_column_families_destroy(column_fams, cflen);
+
+ rocksdb_options_t* cf_options = rocksdb_options_create();
+
+ const char* cf_names[2] = {"default", "cf1"};
+ const rocksdb_options_t* cf_opts[2] = {cf_options, cf_options};
+ rocksdb_column_family_handle_t* handles[2];
+ db = rocksdb_open_column_families(db_options, dbname, 2, cf_names, cf_opts, handles, &err);
+ CheckNoError(err);
+
+ rocksdb_put_cf(db, woptions, handles[1], "foo", 3, "hello", 5, &err);
+ CheckNoError(err);
+
+ rocksdb_put_cf(db, woptions, handles[1], "foobar1", 7, "hello1", 6, &err);
+ CheckNoError(err);
+ rocksdb_put_cf(db, woptions, handles[1], "foobar2", 7, "hello2", 6, &err);
+ CheckNoError(err);
+ rocksdb_put_cf(db, woptions, handles[1], "foobar3", 7, "hello3", 6, &err);
+ CheckNoError(err);
+ rocksdb_put_cf(db, woptions, handles[1], "foobar4", 7, "hello4", 6, &err);
+ CheckNoError(err);
+
+ rocksdb_flushoptions_t *flush_options = rocksdb_flushoptions_create();
+ rocksdb_flushoptions_set_wait(flush_options, 1);
+ rocksdb_flush_cf(db, flush_options, handles[1], &err);
+ CheckNoError(err)
+ rocksdb_flushoptions_destroy(flush_options);
+
+ CheckGetCF(db, roptions, handles[1], "foo", "hello");
+ CheckPinGetCF(db, roptions, handles[1], "foo", "hello");
+
+ rocksdb_delete_cf(db, woptions, handles[1], "foo", 3, &err);
+ CheckNoError(err);
+
+ rocksdb_delete_range_cf(db, woptions, handles[1], "foobar2", 7, "foobar4",
+ 7, &err);
+ CheckNoError(err);
+
+ CheckGetCF(db, roptions, handles[1], "foo", NULL);
+ CheckPinGetCF(db, roptions, handles[1], "foo", NULL);
+
+ rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
+ rocksdb_writebatch_put_cf(wb, handles[1], "baz", 3, "a", 1);
+ rocksdb_writebatch_clear(wb);
+ rocksdb_writebatch_put_cf(wb, handles[1], "bar", 3, "b", 1);
+ rocksdb_writebatch_put_cf(wb, handles[1], "box", 3, "c", 1);
+ rocksdb_writebatch_delete_cf(wb, handles[1], "bar", 3);
+ rocksdb_write(db, woptions, wb, &err);
+ CheckNoError(err);
+ CheckGetCF(db, roptions, handles[1], "baz", NULL);
+ CheckGetCF(db, roptions, handles[1], "bar", NULL);
+ CheckGetCF(db, roptions, handles[1], "box", "c");
+ CheckPinGetCF(db, roptions, handles[1], "baz", NULL);
+ CheckPinGetCF(db, roptions, handles[1], "bar", NULL);
+ CheckPinGetCF(db, roptions, handles[1], "box", "c");
+ rocksdb_writebatch_destroy(wb);
+
+ const char* keys[3] = { "box", "box", "barfooxx" };
+ const rocksdb_column_family_handle_t* get_handles[3] = { handles[0], handles[1], handles[1] };
+ const size_t keys_sizes[3] = { 3, 3, 8 };
+ char* vals[3];
+ size_t vals_sizes[3];
+ char* errs[3];
+ rocksdb_multi_get_cf(db, roptions, get_handles, 3, keys, keys_sizes, vals, vals_sizes, errs);
+
+ int i;
+ for (i = 0; i < 3; i++) {
+ CheckEqual(NULL, errs[i], 0);
+ switch (i) {
+ case 0:
+ CheckEqual(NULL, vals[i], vals_sizes[i]); // wrong cf
+ break;
+ case 1:
+ CheckEqual("c", vals[i], vals_sizes[i]); // bingo
+ break;
+ case 2:
+ CheckEqual(NULL, vals[i], vals_sizes[i]); // normal not found
+ break;
+ }
+ Free(&vals[i]);
+ }
+
+ rocksdb_iterator_t* iter = rocksdb_create_iterator_cf(db, roptions, handles[1]);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+
+ for (i = 0; rocksdb_iter_valid(iter) != 0; rocksdb_iter_next(iter)) {
+ i++;
+ }
+ CheckCondition(i == 3);
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+
+ rocksdb_column_family_handle_t* iters_cf_handles[2] = { handles[0], handles[1] };
+ rocksdb_iterator_t* iters_handles[2];
+ rocksdb_create_iterators(db, roptions, iters_cf_handles, iters_handles, 2, &err);
+ CheckNoError(err);
+
+ iter = iters_handles[0];
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_destroy(iter);
+
+ iter = iters_handles[1];
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+
+ for (i = 0; rocksdb_iter_valid(iter) != 0; rocksdb_iter_next(iter)) {
+ i++;
+ }
+ CheckCondition(i == 3);
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+
+ rocksdb_drop_column_family(db, handles[1], &err);
+ CheckNoError(err);
+ for (i = 0; i < 2; i++) {
+ rocksdb_column_family_handle_destroy(handles[i]);
+ }
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ rocksdb_options_destroy(db_options);
+ rocksdb_options_destroy(cf_options);
+ }
+
+ StartPhase("prefix");
+ {
+ // Create new database
+ rocksdb_options_set_allow_mmap_reads(options, 1);
+ rocksdb_options_set_prefix_extractor(options, rocksdb_slicetransform_create_fixed_prefix(3));
+ rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
+ rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
+ rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
+
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_put(db, woptions, "foo1", 4, "foo", 3, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "foo2", 4, "foo", 3, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "foo3", 4, "foo", 3, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "bar1", 4, "bar", 3, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "bar2", 4, "bar", 3, &err);
+ CheckNoError(err);
+ rocksdb_put(db, woptions, "bar3", 4, "bar", 3, &err);
+ CheckNoError(err);
+
+ rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
+ CheckCondition(!rocksdb_iter_valid(iter));
+
+ rocksdb_iter_seek(iter, "bar", 3);
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ CheckCondition(rocksdb_iter_valid(iter));
+
+ CheckIter(iter, "bar1", "bar");
+ rocksdb_iter_next(iter);
+ CheckIter(iter, "bar2", "bar");
+ rocksdb_iter_next(iter);
+ CheckIter(iter, "bar3", "bar");
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+
+ rocksdb_readoptions_set_total_order_seek(roptions, 1);
+ iter = rocksdb_create_iterator(db, roptions);
+ CheckCondition(!rocksdb_iter_valid(iter));
+
+ rocksdb_iter_seek(iter, "ba", 2);
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "bar1", "bar");
+
+ rocksdb_iter_destroy(iter);
+ rocksdb_readoptions_set_total_order_seek(roptions, 0);
+
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ }
+
+ // Check memory usage stats
+ StartPhase("approximate_memory_usage");
+ {
+ // Create database
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_memory_consumers_t* consumers;
+ consumers = rocksdb_memory_consumers_create();
+ rocksdb_memory_consumers_add_db(consumers, db);
+ rocksdb_memory_consumers_add_cache(consumers, cache);
+
+ // take memory usage report before write-read operation
+ rocksdb_memory_usage_t* mu1;
+ mu1 = rocksdb_approximate_memory_usage_create(consumers, &err);
+ CheckNoError(err);
+
+ // Put data (this should affect memtables)
+ rocksdb_put(db, woptions, "memory", 6, "test", 4, &err);
+ CheckNoError(err);
+ CheckGet(db, roptions, "memory", "test");
+
+ // take memory usage report after write-read operation
+ rocksdb_memory_usage_t* mu2;
+ mu2 = rocksdb_approximate_memory_usage_create(consumers, &err);
+ CheckNoError(err);
+
+ // amount of memory used within memtables should grow
+ CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_total(mu2) >=
+ rocksdb_approximate_memory_usage_get_mem_table_total(mu1));
+ CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu2) >=
+ rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu1));
+
+ rocksdb_memory_consumers_destroy(consumers);
+ rocksdb_approximate_memory_usage_destroy(mu1);
+ rocksdb_approximate_memory_usage_destroy(mu2);
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+ }
+
+ StartPhase("cuckoo_options");
+ {
+ rocksdb_cuckoo_table_options_t* cuckoo_options;
+ cuckoo_options = rocksdb_cuckoo_options_create();
+ rocksdb_cuckoo_options_set_hash_ratio(cuckoo_options, 0.5);
+ rocksdb_cuckoo_options_set_max_search_depth(cuckoo_options, 200);
+ rocksdb_cuckoo_options_set_cuckoo_block_size(cuckoo_options, 10);
+ rocksdb_cuckoo_options_set_identity_as_first_hash(cuckoo_options, 1);
+ rocksdb_cuckoo_options_set_use_module_hash(cuckoo_options, 0);
+ rocksdb_options_set_cuckoo_table_factory(options, cuckoo_options);
+
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_cuckoo_options_destroy(cuckoo_options);
+ }
+
+ StartPhase("iterate_upper_bound");
+ {
+ // Create new empty database
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_options_set_prefix_extractor(options, NULL);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_put(db, woptions, "a", 1, "0", 1, &err); CheckNoError(err);
+ rocksdb_put(db, woptions, "foo", 3, "bar", 3, &err); CheckNoError(err);
+ rocksdb_put(db, woptions, "foo1", 4, "bar1", 4, &err); CheckNoError(err);
+ rocksdb_put(db, woptions, "g1", 2, "0", 1, &err); CheckNoError(err);
+
+ // testing basic case with no iterate_upper_bound and no prefix_extractor
+ {
+ rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
+ rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
+
+ rocksdb_iter_seek(iter, "foo", 3);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "foo", "bar");
+
+ rocksdb_iter_next(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "foo1", "bar1");
+
+ rocksdb_iter_next(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "g1", "0");
+
+ rocksdb_iter_destroy(iter);
+ }
+
+ // testing iterate_upper_bound and forward iterator
+ // to make sure it stops at bound
+ {
+ // iterate_upper_bound points beyond the last expected entry
+ rocksdb_readoptions_set_iterate_upper_bound(roptions, "foo2", 4);
+
+ rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
+
+ rocksdb_iter_seek(iter, "foo", 3);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "foo", "bar");
+
+ rocksdb_iter_next(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "foo1", "bar1");
+
+ rocksdb_iter_next(iter);
+ // should stop here...
+ CheckCondition(!rocksdb_iter_valid(iter));
+
+ rocksdb_iter_destroy(iter);
+ rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
+ }
+ }
+
+ StartPhase("transactions");
+ {
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+
+ // open a TransactionDB
+ txn_db_options = rocksdb_transactiondb_options_create();
+ txn_options = rocksdb_transaction_options_create();
+ rocksdb_options_set_create_if_missing(options, 1);
+ txn_db = rocksdb_transactiondb_open(options, txn_db_options, dbname, &err);
+ CheckNoError(err);
+
+ // put outside a transaction
+ rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hello", 5, &err);
+ CheckNoError(err);
+ CheckTxnDBGet(txn_db, roptions, "foo", "hello");
+
+ // delete from outside transaction
+ rocksdb_transactiondb_delete(txn_db, woptions, "foo", 3, &err);
+ CheckNoError(err);
+ CheckTxnDBGet(txn_db, roptions, "foo", NULL);
+
+ // write batch into TransactionDB
+ rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
+ rocksdb_writebatch_put(wb, "foo", 3, "a", 1);
+ rocksdb_writebatch_clear(wb);
+ rocksdb_writebatch_put(wb, "bar", 3, "b", 1);
+ rocksdb_writebatch_put(wb, "box", 3, "c", 1);
+ rocksdb_writebatch_delete(wb, "bar", 3);
+ rocksdb_transactiondb_write(txn_db, woptions, wb, &err);
+ rocksdb_writebatch_destroy(wb);
+ CheckTxnDBGet(txn_db, roptions, "box", "c");
+ CheckNoError(err);
+
+ // begin a transaction
+ txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
+ // put
+ rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
+ CheckNoError(err);
+ CheckTxnGet(txn, roptions, "foo", "hello");
+ // delete
+ rocksdb_transaction_delete(txn, "foo", 3, &err);
+ CheckNoError(err);
+ CheckTxnGet(txn, roptions, "foo", NULL);
+
+ rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
+ CheckNoError(err);
+
+ // read from outside transaction, before commit
+ CheckTxnDBGet(txn_db, roptions, "foo", NULL);
+
+ // commit
+ rocksdb_transaction_commit(txn, &err);
+ CheckNoError(err);
+
+ // read from outside transaction, after commit
+ CheckTxnDBGet(txn_db, roptions, "foo", "hello");
+
+ // reuse old transaction
+ txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, txn);
+
+ // snapshot
+ const rocksdb_snapshot_t* snapshot;
+ snapshot = rocksdb_transactiondb_create_snapshot(txn_db);
+ rocksdb_readoptions_set_snapshot(roptions, snapshot);
+
+ rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
+ CheckNoError(err);
+
+ CheckTxnDBGet(txn_db, roptions, "foo", "hello");
+ rocksdb_readoptions_set_snapshot(roptions, NULL);
+ rocksdb_transactiondb_release_snapshot(txn_db, snapshot);
+ CheckTxnDBGet(txn_db, roptions, "foo", "hey");
+
+ // iterate
+ rocksdb_transaction_put(txn, "bar", 3, "hi", 2, &err);
+ rocksdb_iterator_t* iter = rocksdb_transaction_create_iterator(txn, roptions);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "bar", "hi");
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+
+ // rollback
+ rocksdb_transaction_rollback(txn, &err);
+ CheckNoError(err);
+ CheckTxnDBGet(txn_db, roptions, "bar", NULL);
+
+ // save point
+ rocksdb_transaction_put(txn, "foo1", 4, "hi1", 3, &err);
+ rocksdb_transaction_set_savepoint(txn);
+ CheckTxnGet(txn, roptions, "foo1", "hi1");
+ rocksdb_transaction_put(txn, "foo2", 4, "hi2", 3, &err);
+ CheckTxnGet(txn, roptions, "foo2", "hi2");
+
+ // rollback to savepoint
+ rocksdb_transaction_rollback_to_savepoint(txn, &err);
+ CheckNoError(err);
+ CheckTxnGet(txn, roptions, "foo2", NULL);
+ CheckTxnGet(txn, roptions, "foo1", "hi1");
+ CheckTxnDBGet(txn_db, roptions, "foo1", NULL);
+ CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
+ rocksdb_transaction_commit(txn, &err);
+ CheckNoError(err);
+ CheckTxnDBGet(txn_db, roptions, "foo1", "hi1");
+ CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
+
+ // Column families.
+ rocksdb_column_family_handle_t* cfh;
+ cfh = rocksdb_transactiondb_create_column_family(txn_db, options,
+ "txn_db_cf", &err);
+ CheckNoError(err);
+
+ rocksdb_transactiondb_put_cf(txn_db, woptions, cfh, "cf_foo", 6, "cf_hello",
+ 8, &err);
+ CheckNoError(err);
+ CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", "cf_hello");
+
+ rocksdb_transactiondb_delete_cf(txn_db, woptions, cfh, "cf_foo", 6, &err);
+ CheckNoError(err);
+ CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
+
+ rocksdb_column_family_handle_destroy(cfh);
+
+ // close and destroy
+ rocksdb_transaction_destroy(txn);
+ rocksdb_transactiondb_close(txn_db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_transaction_options_destroy(txn_options);
+ rocksdb_transactiondb_options_destroy(txn_db_options);
+ }
+
+ StartPhase("optimistic_transactions");
+ {
+ rocksdb_options_t* db_options = rocksdb_options_create();
+ rocksdb_options_set_create_if_missing(db_options, 1);
+ rocksdb_options_set_allow_concurrent_memtable_write(db_options, 1);
+ otxn_db = rocksdb_optimistictransactiondb_open(db_options, dbname, &err);
+ otxn_options = rocksdb_optimistictransaction_options_create();
+ rocksdb_transaction_t* txn1 = rocksdb_optimistictransaction_begin(
+ otxn_db, woptions, otxn_options, NULL);
+ rocksdb_transaction_t* txn2 = rocksdb_optimistictransaction_begin(
+ otxn_db, woptions, otxn_options, NULL);
+ rocksdb_transaction_put(txn1, "key", 3, "value", 5, &err);
+ CheckNoError(err);
+ rocksdb_transaction_put(txn2, "key1", 4, "value1", 6, &err);
+ CheckNoError(err);
+ CheckTxnGet(txn1, roptions, "key", "value");
+ rocksdb_transaction_commit(txn1, &err);
+ CheckNoError(err);
+ rocksdb_transaction_commit(txn2, &err);
+ CheckNoError(err);
+ rocksdb_transaction_destroy(txn1);
+ rocksdb_transaction_destroy(txn2);
+
+ // Check column family
+ db = rocksdb_optimistictransactiondb_get_base_db(otxn_db);
+ rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
+ CheckNoError(err);
+ rocksdb_column_family_handle_t *cfh1, *cfh2;
+ cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
+ cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
+ txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
+ NULL);
+ rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
+ CheckNoError(err);
+ rocksdb_transaction_put_cf(txn, cfh2, "key_cf2", 7, "val_cf2", 7, &err);
+ CheckNoError(err);
+ rocksdb_transaction_commit(txn, &err);
+ CheckNoError(err);
+ txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
+ txn);
+ CheckGetCF(db, roptions, cfh1, "key_cf1", "val_cf1");
+ CheckTxnGetCF(txn, roptions, cfh1, "key_cf1", "val_cf1");
+
+ // Check iterator with column family
+ rocksdb_transaction_put_cf(txn, cfh1, "key1_cf", 7, "val1_cf", 7, &err);
+ CheckNoError(err);
+ rocksdb_iterator_t* iter =
+ rocksdb_transaction_create_iterator_cf(txn, roptions, cfh1);
+ CheckCondition(!rocksdb_iter_valid(iter));
+ rocksdb_iter_seek_to_first(iter);
+ CheckCondition(rocksdb_iter_valid(iter));
+ CheckIter(iter, "key1_cf", "val1_cf");
+ rocksdb_iter_get_error(iter, &err);
+ CheckNoError(err);
+ rocksdb_iter_destroy(iter);
+
+ rocksdb_transaction_destroy(txn);
+ rocksdb_column_family_handle_destroy(cfh1);
+ rocksdb_column_family_handle_destroy(cfh2);
+ rocksdb_optimistictransactiondb_close_base_db(db);
+ rocksdb_optimistictransactiondb_close(otxn_db);
+
+ // Check open optimistic transaction db with column families
+ size_t cf_len;
+ char** column_fams =
+ rocksdb_list_column_families(db_options, dbname, &cf_len, &err);
+ CheckNoError(err);
+ CheckEqual("default", column_fams[0], 7);
+ CheckEqual("txn_db_cf1", column_fams[1], 10);
+ CheckEqual("txn_db_cf2", column_fams[2], 10);
+ CheckCondition(cf_len == 3);
+ rocksdb_list_column_families_destroy(column_fams, cf_len);
+
+ const char* cf_names[3] = {"default", "txn_db_cf1", "txn_db_cf2"};
+ rocksdb_options_t* cf_options = rocksdb_options_create();
+ const rocksdb_options_t* cf_opts[3] = {cf_options, cf_options, cf_options};
+
+ rocksdb_options_set_error_if_exists(cf_options, 0);
+ rocksdb_column_family_handle_t* cf_handles[3];
+ otxn_db = rocksdb_optimistictransactiondb_open_column_families(
+ db_options, dbname, 3, cf_names, cf_opts, cf_handles, &err);
+ CheckNoError(err);
+ rocksdb_transaction_t* txn_cf = rocksdb_optimistictransaction_begin(
+ otxn_db, woptions, otxn_options, NULL);
+ CheckTxnGetCF(txn_cf, roptions, cf_handles[0], "key", "value");
+ CheckTxnGetCF(txn_cf, roptions, cf_handles[1], "key_cf1", "val_cf1");
+ CheckTxnGetCF(txn_cf, roptions, cf_handles[2], "key_cf2", "val_cf2");
+ rocksdb_transaction_destroy(txn_cf);
+ rocksdb_options_destroy(cf_options);
+ rocksdb_column_family_handle_destroy(cf_handles[0]);
+ rocksdb_column_family_handle_destroy(cf_handles[1]);
+ rocksdb_column_family_handle_destroy(cf_handles[2]);
+ rocksdb_optimistictransactiondb_close(otxn_db);
+ rocksdb_destroy_db(db_options, dbname, &err);
+ rocksdb_options_destroy(db_options);
+ rocksdb_optimistictransaction_options_destroy(otxn_options);
+ CheckNoError(err);
+ }
+
+ // Simple sanity check that setting memtable rep works.
+ StartPhase("memtable_reps");
+ {
+ // Create database with vector memtable.
+ rocksdb_options_set_memtable_vector_rep(options);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+
+ // Create database with hash skiplist memtable.
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+ CheckNoError(err);
+
+ rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ }
+
+ // Check that secondary instance works.
+ StartPhase("open_as_secondary");
+ {
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+
+ rocksdb_options_t* db_options = rocksdb_options_create();
+ rocksdb_options_set_create_if_missing(db_options, 1);
+ db = rocksdb_open(db_options, dbname, &err);
+ CheckNoError(err);
+ rocksdb_t* db1;
+ rocksdb_options_t* opts = rocksdb_options_create();
+ rocksdb_options_set_max_open_files(opts, -1);
+ rocksdb_options_set_create_if_missing(opts, 1);
+ snprintf(secondary_path, sizeof(secondary_path),
+ "%s/rocksdb_c_test_secondary-%d", GetTempDir(), ((int)geteuid()));
+ db1 = rocksdb_open_as_secondary(opts, dbname, secondary_path, &err);
+ CheckNoError(err);
+
+ rocksdb_writeoptions_set_sync(woptions, 0);
+ rocksdb_writeoptions_disable_WAL(woptions, 1);
+ rocksdb_put(db, woptions, "key0", 4, "value0", 6, &err);
+ CheckNoError(err);
+ rocksdb_flushoptions_t* flush_opts = rocksdb_flushoptions_create();
+ rocksdb_flushoptions_set_wait(flush_opts, 1);
+ rocksdb_flush(db, flush_opts, &err);
+ CheckNoError(err);
+ rocksdb_try_catch_up_with_primary(db1, &err);
+ CheckNoError(err);
+ rocksdb_readoptions_t* ropts = rocksdb_readoptions_create();
+ rocksdb_readoptions_set_verify_checksums(ropts, 1);
+ rocksdb_readoptions_set_snapshot(ropts, NULL);
+ CheckGet(db, ropts, "key0", "value0");
+ CheckGet(db1, ropts, "key0", "value0");
+
+ rocksdb_writeoptions_disable_WAL(woptions, 0);
+ rocksdb_put(db, woptions, "key1", 4, "value1", 6, &err);
+ CheckNoError(err);
+ rocksdb_try_catch_up_with_primary(db1, &err);
+ CheckNoError(err);
+ CheckGet(db1, ropts, "key0", "value0");
+ CheckGet(db1, ropts, "key1", "value1");
+
+ rocksdb_close(db1);
+ rocksdb_destroy_db(opts, secondary_path, &err);
+ CheckNoError(err);
+
+ rocksdb_options_destroy(db_options);
+ rocksdb_options_destroy(opts);
+ rocksdb_readoptions_destroy(ropts);
+ rocksdb_flushoptions_destroy(flush_opts);
+ }
+
+ // Simple sanity check that options setting db_paths work.
+ StartPhase("open_db_paths");
+ {
+ rocksdb_close(db);
+ rocksdb_destroy_db(options, dbname, &err);
+
+ const rocksdb_dbpath_t* paths[1] = {dbpath};
+ rocksdb_options_set_db_paths(options, paths, 1);
+ db = rocksdb_open(options, dbname, &err);
+ CheckNoError(err);
+ }
+
+ StartPhase("cleanup");
+ rocksdb_close(db);
+ rocksdb_options_destroy(options);
+ rocksdb_block_based_options_destroy(table_options);
+ rocksdb_readoptions_destroy(roptions);
+ rocksdb_writeoptions_destroy(woptions);
+ rocksdb_compactoptions_destroy(coptions);
+ rocksdb_cache_destroy(cache);
+ rocksdb_comparator_destroy(cmp);
+ rocksdb_dbpath_destroy(dbpath);
+ rocksdb_env_destroy(env);
+
+ fprintf(stderr, "PASS\n");
+ return 0;
+}
+
+#else
+
+int main() {
+ fprintf(stderr, "SKIPPED\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/column_family.cc b/storage/rocksdb/rocksdb/db/column_family.cc
new file mode 100644
index 00000000..928a02a1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/column_family.cc
@@ -0,0 +1,1523 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/column_family.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "db/compaction/compaction_picker.h"
+#include "db/compaction/compaction_picker_fifo.h"
+#include "db/compaction/compaction_picker_level.h"
+#include "db/compaction/compaction_picker_universal.h"
+#include "db/db_impl/db_impl.h"
+#include "db/internal_stats.h"
+#include "db/job_context.h"
+#include "db/range_del_aggregator.h"
+#include "db/table_properties_collector.h"
+#include "db/version_set.h"
+#include "db/write_controller.h"
+#include "file/sst_file_manager_impl.h"
+#include "memtable/hash_skiplist_rep.h"
+#include "monitoring/thread_status_util.h"
+#include "options/options_helper.h"
+#include "port/port.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/merging_iterator.h"
+#include "util/autovector.h"
+#include "util/compression.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ColumnFamilyHandleImpl::ColumnFamilyHandleImpl(
+ ColumnFamilyData* column_family_data, DBImpl* db, InstrumentedMutex* mutex)
+ : cfd_(column_family_data), db_(db), mutex_(mutex) {
+ if (cfd_ != nullptr) {
+ cfd_->Ref();
+ }
+}
+
+ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
+ if (cfd_ != nullptr) {
+#ifndef ROCKSDB_LITE
+ for (auto& listener : cfd_->ioptions()->listeners) {
+ listener->OnColumnFamilyHandleDeletionStarted(this);
+ }
+#endif // ROCKSDB_LITE
+ // Job id == 0 means that this is not our background process, but rather
+ // user thread
+ // Need to hold some shared pointers owned by the initial_cf_options
+ // before final cleaning up finishes.
+ ColumnFamilyOptions initial_cf_options_copy = cfd_->initial_cf_options();
+ JobContext job_context(0);
+ mutex_->Lock();
+ bool dropped = cfd_->IsDropped();
+ if (cfd_->UnrefAndTryDelete()) {
+ if (dropped) {
+ db_->FindObsoleteFiles(&job_context, false, true);
+ }
+ }
+ mutex_->Unlock();
+ if (job_context.HaveSomethingToDelete()) {
+ bool defer_purge =
+ db_->immutable_db_options().avoid_unnecessary_blocking_io;
+ db_->PurgeObsoleteFiles(job_context, defer_purge);
+ if (defer_purge) {
+ mutex_->Lock();
+ db_->SchedulePurge();
+ mutex_->Unlock();
+ }
+ }
+ job_context.Clean();
+ }
+}
+
+uint32_t ColumnFamilyHandleImpl::GetID() const { return cfd()->GetID(); }
+
+const std::string& ColumnFamilyHandleImpl::GetName() const {
+ return cfd()->GetName();
+}
+
+Status ColumnFamilyHandleImpl::GetDescriptor(ColumnFamilyDescriptor* desc) {
+#ifndef ROCKSDB_LITE
+ // accessing mutable cf-options requires db mutex.
+ InstrumentedMutexLock l(mutex_);
+ *desc = ColumnFamilyDescriptor(cfd()->GetName(), cfd()->GetLatestCFOptions());
+ return Status::OK();
+#else
+ (void)desc;
+ return Status::NotSupported();
+#endif // !ROCKSDB_LITE
+}
+
+const Comparator* ColumnFamilyHandleImpl::GetComparator() const {
+ return cfd()->user_comparator();
+}
+
+void GetIntTblPropCollectorFactory(
+ const ImmutableCFOptions& ioptions,
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories) {
+ auto& collector_factories = ioptions.table_properties_collector_factories;
+ for (size_t i = 0; i < ioptions.table_properties_collector_factories.size();
+ ++i) {
+ assert(collector_factories[i]);
+ int_tbl_prop_collector_factories->emplace_back(
+ new UserKeyTablePropertiesCollectorFactory(collector_factories[i]));
+ }
+}
+
+Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
+ if (!cf_options.compression_per_level.empty()) {
+ for (size_t level = 0; level < cf_options.compression_per_level.size();
+ ++level) {
+ if (!CompressionTypeSupported(cf_options.compression_per_level[level])) {
+ return Status::InvalidArgument(
+ "Compression type " +
+ CompressionTypeToString(cf_options.compression_per_level[level]) +
+ " is not linked with the binary.");
+ }
+ }
+ } else {
+ if (!CompressionTypeSupported(cf_options.compression)) {
+ return Status::InvalidArgument(
+ "Compression type " +
+ CompressionTypeToString(cf_options.compression) +
+ " is not linked with the binary.");
+ }
+ }
+ if (cf_options.compression_opts.zstd_max_train_bytes > 0) {
+ if (!ZSTD_TrainDictionarySupported()) {
+ return Status::InvalidArgument(
+ "zstd dictionary trainer cannot be used because ZSTD 1.1.3+ "
+ "is not linked with the binary.");
+ }
+ if (cf_options.compression_opts.max_dict_bytes == 0) {
+ return Status::InvalidArgument(
+ "The dictionary size limit (`CompressionOptions::max_dict_bytes`) "
+ "should be nonzero if we're using zstd's dictionary generator.");
+ }
+ }
+ return Status::OK();
+}
+
+Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
+ if (cf_options.inplace_update_support) {
+ return Status::InvalidArgument(
+ "In-place memtable updates (inplace_update_support) is not compatible "
+ "with concurrent writes (allow_concurrent_memtable_write)");
+ }
+ if (!cf_options.memtable_factory->IsInsertConcurrentlySupported()) {
+ return Status::InvalidArgument(
+ "Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
+ }
+ return Status::OK();
+}
+
+Status CheckCFPathsSupported(const DBOptions& db_options,
+ const ColumnFamilyOptions& cf_options) {
+ // More than one cf_paths are supported only in universal
+ // and level compaction styles. This function also checks the case
+ // in which cf_paths is not specified, which results in db_paths
+ // being used.
+ if ((cf_options.compaction_style != kCompactionStyleUniversal) &&
+ (cf_options.compaction_style != kCompactionStyleLevel)) {
+ if (cf_options.cf_paths.size() > 1) {
+ return Status::NotSupported(
+ "More than one CF paths are only supported in "
+ "universal and level compaction styles. ");
+ } else if (cf_options.cf_paths.empty() &&
+ db_options.db_paths.size() > 1) {
+ return Status::NotSupported(
+ "More than one DB paths are only supported in "
+ "universal and level compaction styles. ");
+ }
+ }
+ return Status::OK();
+}
+
+namespace {
+const uint64_t kDefaultTtl = 0xfffffffffffffffe;
+const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
+}; // namespace
+
+ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
+ const ColumnFamilyOptions& src) {
+ ColumnFamilyOptions result = src;
+ size_t clamp_max = std::conditional<
+ sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
+ std::integral_constant<uint64_t, 64ull << 30>>::type::value;
+ ClipToRange(&result.write_buffer_size, ((size_t)64) << 10, clamp_max);
+ // if user sets arena_block_size, we trust user to use this value. Otherwise,
+ // calculate a proper value from writer_buffer_size;
+ if (result.arena_block_size <= 0) {
+ result.arena_block_size = result.write_buffer_size / 8;
+
+ // Align up to 4k
+ const size_t align = 4 * 1024;
+ result.arena_block_size =
+ ((result.arena_block_size + align - 1) / align) * align;
+ }
+ result.min_write_buffer_number_to_merge =
+ std::min(result.min_write_buffer_number_to_merge,
+ result.max_write_buffer_number - 1);
+ if (result.min_write_buffer_number_to_merge < 1) {
+ result.min_write_buffer_number_to_merge = 1;
+ }
+
+ if (result.num_levels < 1) {
+ result.num_levels = 1;
+ }
+ if (result.compaction_style == kCompactionStyleLevel &&
+ result.num_levels < 2) {
+ result.num_levels = 2;
+ }
+
+ if (result.compaction_style == kCompactionStyleUniversal &&
+ db_options.allow_ingest_behind && result.num_levels < 3) {
+ result.num_levels = 3;
+ }
+
+ if (result.max_write_buffer_number < 2) {
+ result.max_write_buffer_number = 2;
+ }
+ // fall back max_write_buffer_number_to_maintain if
+ // max_write_buffer_size_to_maintain is not set
+ if (result.max_write_buffer_size_to_maintain < 0) {
+ result.max_write_buffer_size_to_maintain =
+ result.max_write_buffer_number *
+ static_cast<int64_t>(result.write_buffer_size);
+ } else if (result.max_write_buffer_size_to_maintain == 0 &&
+ result.max_write_buffer_number_to_maintain < 0) {
+ result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
+ }
+ // bloom filter size shouldn't exceed 1/4 of memtable size.
+ if (result.memtable_prefix_bloom_size_ratio > 0.25) {
+ result.memtable_prefix_bloom_size_ratio = 0.25;
+ } else if (result.memtable_prefix_bloom_size_ratio < 0) {
+ result.memtable_prefix_bloom_size_ratio = 0;
+ }
+
+ if (!result.prefix_extractor) {
+ assert(result.memtable_factory);
+ Slice name = result.memtable_factory->Name();
+ if (name.compare("HashSkipListRepFactory") == 0 ||
+ name.compare("HashLinkListRepFactory") == 0) {
+ result.memtable_factory = std::make_shared<SkipListFactory>();
+ }
+ }
+
+ if (result.compaction_style == kCompactionStyleFIFO) {
+ result.num_levels = 1;
+ // since we delete level0 files in FIFO compaction when there are too many
+ // of them, these options don't really mean anything
+ result.level0_slowdown_writes_trigger = std::numeric_limits<int>::max();
+ result.level0_stop_writes_trigger = std::numeric_limits<int>::max();
+ }
+
+ if (result.max_bytes_for_level_multiplier <= 0) {
+ result.max_bytes_for_level_multiplier = 1;
+ }
+
+ if (result.level0_file_num_compaction_trigger == 0) {
+ ROCKS_LOG_WARN(db_options.info_log.get(),
+ "level0_file_num_compaction_trigger cannot be 0");
+ result.level0_file_num_compaction_trigger = 1;
+ }
+
+ if (result.level0_stop_writes_trigger <
+ result.level0_slowdown_writes_trigger ||
+ result.level0_slowdown_writes_trigger <
+ result.level0_file_num_compaction_trigger) {
+ ROCKS_LOG_WARN(db_options.info_log.get(),
+ "This condition must be satisfied: "
+ "level0_stop_writes_trigger(%d) >= "
+ "level0_slowdown_writes_trigger(%d) >= "
+ "level0_file_num_compaction_trigger(%d)",
+ result.level0_stop_writes_trigger,
+ result.level0_slowdown_writes_trigger,
+ result.level0_file_num_compaction_trigger);
+ if (result.level0_slowdown_writes_trigger <
+ result.level0_file_num_compaction_trigger) {
+ result.level0_slowdown_writes_trigger =
+ result.level0_file_num_compaction_trigger;
+ }
+ if (result.level0_stop_writes_trigger <
+ result.level0_slowdown_writes_trigger) {
+ result.level0_stop_writes_trigger = result.level0_slowdown_writes_trigger;
+ }
+ ROCKS_LOG_WARN(db_options.info_log.get(),
+ "Adjust the value to "
+ "level0_stop_writes_trigger(%d)"
+ "level0_slowdown_writes_trigger(%d)"
+ "level0_file_num_compaction_trigger(%d)",
+ result.level0_stop_writes_trigger,
+ result.level0_slowdown_writes_trigger,
+ result.level0_file_num_compaction_trigger);
+ }
+
+ if (result.soft_pending_compaction_bytes_limit == 0) {
+ result.soft_pending_compaction_bytes_limit =
+ result.hard_pending_compaction_bytes_limit;
+ } else if (result.hard_pending_compaction_bytes_limit > 0 &&
+ result.soft_pending_compaction_bytes_limit >
+ result.hard_pending_compaction_bytes_limit) {
+ result.soft_pending_compaction_bytes_limit =
+ result.hard_pending_compaction_bytes_limit;
+ }
+
+#ifndef ROCKSDB_LITE
+ // When the DB is stopped, it's possible that there are some .trash files that
+ // were not deleted yet, when we open the DB we will find these .trash files
+ // and schedule them to be deleted (or delete immediately if SstFileManager
+ // was not used)
+ auto sfm = static_cast<SstFileManagerImpl*>(db_options.sst_file_manager.get());
+ for (size_t i = 0; i < result.cf_paths.size(); i++) {
+ DeleteScheduler::CleanupDirectory(db_options.env, sfm, result.cf_paths[i].path);
+ }
+#endif
+
+ if (result.cf_paths.empty()) {
+ result.cf_paths = db_options.db_paths;
+ }
+
+ if (result.level_compaction_dynamic_level_bytes) {
+ if (result.compaction_style != kCompactionStyleLevel ||
+ result.cf_paths.size() > 1U) {
+ // 1. level_compaction_dynamic_level_bytes only makes sense for
+ // level-based compaction.
+ // 2. we don't yet know how to make both of this feature and multiple
+ // DB path work.
+ result.level_compaction_dynamic_level_bytes = false;
+ }
+ }
+
+ if (result.max_compaction_bytes == 0) {
+ result.max_compaction_bytes = result.target_file_size_base * 25;
+ }
+
+ bool is_block_based_table =
+ (result.table_factory->Name() == BlockBasedTableFactory().Name());
+
+ const uint64_t kAdjustedTtl = 30 * 24 * 60 * 60;
+ if (result.ttl == kDefaultTtl) {
+ if (is_block_based_table &&
+ result.compaction_style != kCompactionStyleFIFO) {
+ result.ttl = kAdjustedTtl;
+ } else {
+ result.ttl = 0;
+ }
+ }
+
+ const uint64_t kAdjustedPeriodicCompSecs = 30 * 24 * 60 * 60;
+
+ // Turn on periodic compactions and set them to occur once every 30 days if
+ // compaction filters are used and periodic_compaction_seconds is set to the
+ // default value.
+ if (result.compaction_style != kCompactionStyleFIFO) {
+ if ((result.compaction_filter != nullptr ||
+ result.compaction_filter_factory != nullptr) &&
+ result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
+ is_block_based_table) {
+ result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
+ }
+ } else {
+ // result.compaction_style == kCompactionStyleFIFO
+ if (result.ttl == 0) {
+ if (is_block_based_table) {
+ if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
+ result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
+ }
+ result.ttl = result.periodic_compaction_seconds;
+ }
+ } else if (result.periodic_compaction_seconds != 0) {
+ result.ttl = std::min(result.ttl, result.periodic_compaction_seconds);
+ }
+ }
+
+ // TTL compactions would work similar to Periodic Compactions in Universal in
+ // most of the cases. So, if ttl is set, execute the periodic compaction
+ // codepath.
+ if (result.compaction_style == kCompactionStyleUniversal && result.ttl != 0) {
+ if (result.periodic_compaction_seconds != 0) {
+ result.periodic_compaction_seconds =
+ std::min(result.ttl, result.periodic_compaction_seconds);
+ } else {
+ result.periodic_compaction_seconds = result.ttl;
+ }
+ }
+
+ if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
+ result.periodic_compaction_seconds = 0;
+ }
+
+ return result;
+}
+
+int SuperVersion::dummy = 0;
+void* const SuperVersion::kSVInUse = &SuperVersion::dummy;
+void* const SuperVersion::kSVObsolete = nullptr;
+
+SuperVersion::~SuperVersion() {
+ for (auto td : to_delete) {
+ delete td;
+ }
+}
+
+SuperVersion* SuperVersion::Ref() {
+ refs.fetch_add(1, std::memory_order_relaxed);
+ return this;
+}
+
+bool SuperVersion::Unref() {
+ // fetch_sub returns the previous value of ref
+ uint32_t previous_refs = refs.fetch_sub(1);
+ assert(previous_refs > 0);
+ return previous_refs == 1;
+}
+
+void SuperVersion::Cleanup() {
+ assert(refs.load(std::memory_order_relaxed) == 0);
+ imm->Unref(&to_delete);
+ MemTable* m = mem->Unref();
+ if (m != nullptr) {
+ auto* memory_usage = current->cfd()->imm()->current_memory_usage();
+ assert(*memory_usage >= m->ApproximateMemoryUsage());
+ *memory_usage -= m->ApproximateMemoryUsage();
+ to_delete.push_back(m);
+ }
+ current->Unref();
+ if (cfd->Unref()) {
+ delete cfd;
+ }
+}
+
+void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
+ MemTableListVersion* new_imm, Version* new_current) {
+ cfd = new_cfd;
+ mem = new_mem;
+ imm = new_imm;
+ current = new_current;
+ cfd->Ref();
+ mem->Ref();
+ imm->Ref();
+ current->Ref();
+ refs.store(1, std::memory_order_relaxed);
+}
+
+namespace {
+void SuperVersionUnrefHandle(void* ptr) {
+ // UnrefHandle is called when a thread exists or a ThreadLocalPtr gets
+ // destroyed. When former happens, the thread shouldn't see kSVInUse.
+ // When latter happens, we are in ~ColumnFamilyData(), no get should happen as
+ // well.
+ SuperVersion* sv = static_cast<SuperVersion*>(ptr);
+ bool was_last_ref __attribute__((__unused__));
+ was_last_ref = sv->Unref();
+ // Thread-local SuperVersions can't outlive ColumnFamilyData::super_version_.
+ // This is important because we can't do SuperVersion cleanup here.
+ // That would require locking DB mutex, which would deadlock because
+ // SuperVersionUnrefHandle is called with locked ThreadLocalPtr mutex.
+ assert(!was_last_ref);
+}
+} // anonymous namespace
+
+ColumnFamilyData::ColumnFamilyData(
+ uint32_t id, const std::string& name, Version* _dummy_versions,
+ Cache* _table_cache, WriteBufferManager* write_buffer_manager,
+ const ColumnFamilyOptions& cf_options, const ImmutableDBOptions& db_options,
+ const FileOptions& file_options, ColumnFamilySet* column_family_set,
+ BlockCacheTracer* const block_cache_tracer)
+ : id_(id),
+ name_(name),
+ dummy_versions_(_dummy_versions),
+ current_(nullptr),
+ refs_(0),
+ initialized_(false),
+ dropped_(false),
+ internal_comparator_(cf_options.comparator),
+ initial_cf_options_(SanitizeOptions(db_options, cf_options)),
+ ioptions_(db_options, initial_cf_options_),
+ mutable_cf_options_(initial_cf_options_),
+ is_delete_range_supported_(
+ cf_options.table_factory->IsDeleteRangeSupported()),
+ write_buffer_manager_(write_buffer_manager),
+ mem_(nullptr),
+ imm_(ioptions_.min_write_buffer_number_to_merge,
+ ioptions_.max_write_buffer_number_to_maintain,
+ ioptions_.max_write_buffer_size_to_maintain),
+ super_version_(nullptr),
+ super_version_number_(0),
+ local_sv_(new ThreadLocalPtr(&SuperVersionUnrefHandle)),
+ next_(nullptr),
+ prev_(nullptr),
+ log_number_(0),
+ flush_reason_(FlushReason::kOthers),
+ column_family_set_(column_family_set),
+ queued_for_flush_(false),
+ queued_for_compaction_(false),
+ prev_compaction_needed_bytes_(0),
+ allow_2pc_(db_options.allow_2pc),
+ last_memtable_id_(0) {
+ Ref();
+
+ // Convert user defined table properties collector factories to internal ones.
+ GetIntTblPropCollectorFactory(ioptions_, &int_tbl_prop_collector_factories_);
+
+ // if _dummy_versions is nullptr, then this is a dummy column family.
+ if (_dummy_versions != nullptr) {
+ internal_stats_.reset(
+ new InternalStats(ioptions_.num_levels, db_options.env, this));
+ table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
+ block_cache_tracer));
+ if (ioptions_.compaction_style == kCompactionStyleLevel) {
+ compaction_picker_.reset(
+ new LevelCompactionPicker(ioptions_, &internal_comparator_));
+#ifndef ROCKSDB_LITE
+ } else if (ioptions_.compaction_style == kCompactionStyleUniversal) {
+ compaction_picker_.reset(
+ new UniversalCompactionPicker(ioptions_, &internal_comparator_));
+ } else if (ioptions_.compaction_style == kCompactionStyleFIFO) {
+ compaction_picker_.reset(
+ new FIFOCompactionPicker(ioptions_, &internal_comparator_));
+ } else if (ioptions_.compaction_style == kCompactionStyleNone) {
+ compaction_picker_.reset(new NullCompactionPicker(
+ ioptions_, &internal_comparator_));
+ ROCKS_LOG_WARN(ioptions_.info_log,
+ "Column family %s does not use any background compaction. "
+ "Compactions can only be done via CompactFiles\n",
+ GetName().c_str());
+#endif // !ROCKSDB_LITE
+ } else {
+ ROCKS_LOG_ERROR(ioptions_.info_log,
+ "Unable to recognize the specified compaction style %d. "
+ "Column family %s will use kCompactionStyleLevel.\n",
+ ioptions_.compaction_style, GetName().c_str());
+ compaction_picker_.reset(
+ new LevelCompactionPicker(ioptions_, &internal_comparator_));
+ }
+
+ if (column_family_set_->NumberOfColumnFamilies() < 10) {
+ ROCKS_LOG_INFO(ioptions_.info_log,
+ "--------------- Options for column family [%s]:\n",
+ name.c_str());
+ initial_cf_options_.Dump(ioptions_.info_log);
+ } else {
+ ROCKS_LOG_INFO(ioptions_.info_log, "\t(skipping printing options)\n");
+ }
+ }
+
+ RecalculateWriteStallConditions(mutable_cf_options_);
+}
+
+// DB mutex held
+ColumnFamilyData::~ColumnFamilyData() {
+ assert(refs_.load(std::memory_order_relaxed) == 0);
+ // remove from linked list
+ auto prev = prev_;
+ auto next = next_;
+ prev->next_ = next;
+ next->prev_ = prev;
+
+ if (!dropped_ && column_family_set_ != nullptr) {
+ // If it's dropped, it's already removed from column family set
+ // If column_family_set_ == nullptr, this is dummy CFD and not in
+ // ColumnFamilySet
+ column_family_set_->RemoveColumnFamily(this);
+ }
+
+ if (current_ != nullptr) {
+ current_->Unref();
+ }
+
+ // It would be wrong if this ColumnFamilyData is in flush_queue_ or
+ // compaction_queue_ and we destroyed it
+ assert(!queued_for_flush_);
+ assert(!queued_for_compaction_);
+ assert(super_version_ == nullptr);
+
+ if (dummy_versions_ != nullptr) {
+ // List must be empty
+ assert(dummy_versions_->TEST_Next() == dummy_versions_);
+ bool deleted __attribute__((__unused__));
+ deleted = dummy_versions_->Unref();
+ assert(deleted);
+ }
+
+ if (mem_ != nullptr) {
+ delete mem_->Unref();
+ }
+ autovector<MemTable*> to_delete;
+ imm_.current()->Unref(&to_delete);
+ for (MemTable* m : to_delete) {
+ delete m;
+ }
+}
+
+bool ColumnFamilyData::UnrefAndTryDelete() {
+ int old_refs = refs_.fetch_sub(1);
+ assert(old_refs > 0);
+
+ if (old_refs == 1) {
+ assert(super_version_ == nullptr);
+ delete this;
+ return true;
+ }
+
+ if (old_refs == 2 && super_version_ != nullptr) {
+ // Only the super_version_ holds me
+ SuperVersion* sv = super_version_;
+ super_version_ = nullptr;
+ // Release SuperVersion reference kept in ThreadLocalPtr.
+ // This must be done outside of mutex_ since unref handler can lock mutex.
+ sv->db_mutex->Unlock();
+ local_sv_.reset();
+ sv->db_mutex->Lock();
+
+ if (sv->Unref()) {
+ // May delete this ColumnFamilyData after calling Cleanup()
+ sv->Cleanup();
+ delete sv;
+ return true;
+ }
+ }
+ return false;
+}
+
+void ColumnFamilyData::SetDropped() {
+ // can't drop default CF
+ assert(id_ != 0);
+ dropped_ = true;
+ write_controller_token_.reset();
+
+ // remove from column_family_set
+ column_family_set_->RemoveColumnFamily(this);
+}
+
+ColumnFamilyOptions ColumnFamilyData::GetLatestCFOptions() const {
+ return BuildColumnFamilyOptions(initial_cf_options_, mutable_cf_options_);
+}
+
+uint64_t ColumnFamilyData::OldestLogToKeep() {
+ auto current_log = GetLogNumber();
+
+ if (allow_2pc_) {
+ autovector<MemTable*> empty_list;
+ auto imm_prep_log =
+ imm()->PrecomputeMinLogContainingPrepSection(empty_list);
+ auto mem_prep_log = mem()->GetMinLogContainingPrepSection();
+
+ if (imm_prep_log > 0 && imm_prep_log < current_log) {
+ current_log = imm_prep_log;
+ }
+
+ if (mem_prep_log > 0 && mem_prep_log < current_log) {
+ current_log = mem_prep_log;
+ }
+ }
+
+ return current_log;
+}
+
+const double kIncSlowdownRatio = 0.8;
+const double kDecSlowdownRatio = 1 / kIncSlowdownRatio;
+const double kNearStopSlowdownRatio = 0.6;
+const double kDelayRecoverSlowdownRatio = 1.4;
+
+namespace {
+// If penalize_stop is true, we further reduce slowdown rate.
+std::unique_ptr<WriteControllerToken> SetupDelay(
+ WriteController* write_controller, uint64_t compaction_needed_bytes,
+ uint64_t prev_compaction_need_bytes, bool penalize_stop,
+ bool auto_comapctions_disabled) {
+ const uint64_t kMinWriteRate = 16 * 1024u; // Minimum write rate 16KB/s.
+
+ uint64_t max_write_rate = write_controller->max_delayed_write_rate();
+ uint64_t write_rate = write_controller->delayed_write_rate();
+
+ if (auto_comapctions_disabled) {
+ // When auto compaction is disabled, always use the value user gave.
+ write_rate = max_write_rate;
+ } else if (write_controller->NeedsDelay() && max_write_rate > kMinWriteRate) {
+ // If user gives rate less than kMinWriteRate, don't adjust it.
+ //
+ // If already delayed, need to adjust based on previous compaction debt.
+ // When there are two or more column families require delay, we always
+ // increase or reduce write rate based on information for one single
+ // column family. It is likely to be OK but we can improve if there is a
+ // problem.
+ // Ignore compaction_needed_bytes = 0 case because compaction_needed_bytes
+ // is only available in level-based compaction
+ //
+ // If the compaction debt stays the same as previously, we also further slow
+ // down. It usually means a mem table is full. It's mainly for the case
+ // where both of flush and compaction are much slower than the speed we
+ // insert to mem tables, so we need to actively slow down before we get
+ // feedback signal from compaction and flushes to avoid the full stop
+ // because of hitting the max write buffer number.
+ //
+ // If DB just falled into the stop condition, we need to further reduce
+ // the write rate to avoid the stop condition.
+ if (penalize_stop) {
+ // Penalize the near stop or stop condition by more aggressive slowdown.
+ // This is to provide the long term slowdown increase signal.
+ // The penalty is more than the reward of recovering to the normal
+ // condition.
+ write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
+ kNearStopSlowdownRatio);
+ if (write_rate < kMinWriteRate) {
+ write_rate = kMinWriteRate;
+ }
+ } else if (prev_compaction_need_bytes > 0 &&
+ prev_compaction_need_bytes <= compaction_needed_bytes) {
+ write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
+ kIncSlowdownRatio);
+ if (write_rate < kMinWriteRate) {
+ write_rate = kMinWriteRate;
+ }
+ } else if (prev_compaction_need_bytes > compaction_needed_bytes) {
+ // We are speeding up by ratio of kSlowdownRatio when we have paid
+ // compaction debt. But we'll never speed up to faster than the write rate
+ // given by users.
+ write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
+ kDecSlowdownRatio);
+ if (write_rate > max_write_rate) {
+ write_rate = max_write_rate;
+ }
+ }
+ }
+ return write_controller->GetDelayToken(write_rate);
+}
+
+int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
+ int level0_slowdown_writes_trigger) {
+ // SanitizeOptions() ensures it.
+ assert(level0_file_num_compaction_trigger <= level0_slowdown_writes_trigger);
+
+ if (level0_file_num_compaction_trigger < 0) {
+ return std::numeric_limits<int>::max();
+ }
+
+ const int64_t twice_level0_trigger =
+ static_cast<int64_t>(level0_file_num_compaction_trigger) * 2;
+
+ const int64_t one_fourth_trigger_slowdown =
+ static_cast<int64_t>(level0_file_num_compaction_trigger) +
+ ((level0_slowdown_writes_trigger - level0_file_num_compaction_trigger) /
+ 4);
+
+ assert(twice_level0_trigger >= 0);
+ assert(one_fourth_trigger_slowdown >= 0);
+
+ // 1/4 of the way between L0 compaction trigger threshold and slowdown
+ // condition.
+ // Or twice as compaction trigger, if it is smaller.
+ int64_t res = std::min(twice_level0_trigger, one_fourth_trigger_slowdown);
+ if (res >= port::kMaxInt32) {
+ return port::kMaxInt32;
+ } else {
+ // res fits in int
+ return static_cast<int>(res);
+ }
+}
+} // namespace
+
+std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
+ColumnFamilyData::GetWriteStallConditionAndCause(
+ int num_unflushed_memtables, int num_l0_files,
+ uint64_t num_compaction_needed_bytes,
+ const MutableCFOptions& mutable_cf_options) {
+ if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
+ return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
+ } else if (!mutable_cf_options.disable_auto_compactions &&
+ num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) {
+ return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit};
+ } else if (!mutable_cf_options.disable_auto_compactions &&
+ mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
+ num_compaction_needed_bytes >=
+ mutable_cf_options.hard_pending_compaction_bytes_limit) {
+ return {WriteStallCondition::kStopped,
+ WriteStallCause::kPendingCompactionBytes};
+ } else if (mutable_cf_options.max_write_buffer_number > 3 &&
+ num_unflushed_memtables >=
+ mutable_cf_options.max_write_buffer_number - 1) {
+ return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
+ } else if (!mutable_cf_options.disable_auto_compactions &&
+ mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
+ num_l0_files >=
+ mutable_cf_options.level0_slowdown_writes_trigger) {
+ return {WriteStallCondition::kDelayed, WriteStallCause::kL0FileCountLimit};
+ } else if (!mutable_cf_options.disable_auto_compactions &&
+ mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
+ num_compaction_needed_bytes >=
+ mutable_cf_options.soft_pending_compaction_bytes_limit) {
+ return {WriteStallCondition::kDelayed,
+ WriteStallCause::kPendingCompactionBytes};
+ }
+ return {WriteStallCondition::kNormal, WriteStallCause::kNone};
+}
+
+WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
+ const MutableCFOptions& mutable_cf_options) {
+ auto write_stall_condition = WriteStallCondition::kNormal;
+ if (current_ != nullptr) {
+ auto* vstorage = current_->storage_info();
+ auto write_controller = column_family_set_->write_controller_;
+ uint64_t compaction_needed_bytes =
+ vstorage->estimated_compaction_needed_bytes();
+
+ auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
+ imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
+ vstorage->estimated_compaction_needed_bytes(), mutable_cf_options);
+ write_stall_condition = write_stall_condition_and_cause.first;
+ auto write_stall_cause = write_stall_condition_and_cause.second;
+
+ bool was_stopped = write_controller->IsStopped();
+ bool needed_delay = write_controller->NeedsDelay();
+
+ if (write_stall_condition == WriteStallCondition::kStopped &&
+ write_stall_cause == WriteStallCause::kMemtableLimit) {
+ write_controller_token_ = write_controller->GetStopToken();
+ internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_STOPS, 1);
+ ROCKS_LOG_WARN(
+ ioptions_.info_log,
+ "[%s] Stopping writes because we have %d immutable memtables "
+ "(waiting for flush), max_write_buffer_number is set to %d",
+ name_.c_str(), imm()->NumNotFlushed(),
+ mutable_cf_options.max_write_buffer_number);
+ } else if (write_stall_condition == WriteStallCondition::kStopped &&
+ write_stall_cause == WriteStallCause::kL0FileCountLimit) {
+ write_controller_token_ = write_controller->GetStopToken();
+ internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1);
+ if (compaction_picker_->IsLevel0CompactionInProgress()) {
+ internal_stats_->AddCFStats(
+ InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
+ }
+ ROCKS_LOG_WARN(ioptions_.info_log,
+ "[%s] Stopping writes because we have %d level-0 files",
+ name_.c_str(), vstorage->l0_delay_trigger_count());
+ } else if (write_stall_condition == WriteStallCondition::kStopped &&
+ write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
+ write_controller_token_ = write_controller->GetStopToken();
+ internal_stats_->AddCFStats(
+ InternalStats::PENDING_COMPACTION_BYTES_LIMIT_STOPS, 1);
+ ROCKS_LOG_WARN(
+ ioptions_.info_log,
+ "[%s] Stopping writes because of estimated pending compaction "
+ "bytes %" PRIu64,
+ name_.c_str(), compaction_needed_bytes);
+ } else if (write_stall_condition == WriteStallCondition::kDelayed &&
+ write_stall_cause == WriteStallCause::kMemtableLimit) {
+ write_controller_token_ =
+ SetupDelay(write_controller, compaction_needed_bytes,
+ prev_compaction_needed_bytes_, was_stopped,
+ mutable_cf_options.disable_auto_compactions);
+ internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
+ ROCKS_LOG_WARN(
+ ioptions_.info_log,
+ "[%s] Stalling writes because we have %d immutable memtables "
+ "(waiting for flush), max_write_buffer_number is set to %d "
+ "rate %" PRIu64,
+ name_.c_str(), imm()->NumNotFlushed(),
+ mutable_cf_options.max_write_buffer_number,
+ write_controller->delayed_write_rate());
+ } else if (write_stall_condition == WriteStallCondition::kDelayed &&
+ write_stall_cause == WriteStallCause::kL0FileCountLimit) {
+ // L0 is the last two files from stopping.
+ bool near_stop = vstorage->l0_delay_trigger_count() >=
+ mutable_cf_options.level0_stop_writes_trigger - 2;
+ write_controller_token_ =
+ SetupDelay(write_controller, compaction_needed_bytes,
+ prev_compaction_needed_bytes_, was_stopped || near_stop,
+ mutable_cf_options.disable_auto_compactions);
+ internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS,
+ 1);
+ if (compaction_picker_->IsLevel0CompactionInProgress()) {
+ internal_stats_->AddCFStats(
+ InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
+ }
+ ROCKS_LOG_WARN(ioptions_.info_log,
+ "[%s] Stalling writes because we have %d level-0 files "
+ "rate %" PRIu64,
+ name_.c_str(), vstorage->l0_delay_trigger_count(),
+ write_controller->delayed_write_rate());
+ } else if (write_stall_condition == WriteStallCondition::kDelayed &&
+ write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
+ // If the distance to hard limit is less than 1/4 of the gap between soft
+ // and
+ // hard bytes limit, we think it is near stop and speed up the slowdown.
+ bool near_stop =
+ mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
+ (compaction_needed_bytes -
+ mutable_cf_options.soft_pending_compaction_bytes_limit) >
+ 3 * (mutable_cf_options.hard_pending_compaction_bytes_limit -
+ mutable_cf_options.soft_pending_compaction_bytes_limit) /
+ 4;
+
+ write_controller_token_ =
+ SetupDelay(write_controller, compaction_needed_bytes,
+ prev_compaction_needed_bytes_, was_stopped || near_stop,
+ mutable_cf_options.disable_auto_compactions);
+ internal_stats_->AddCFStats(
+ InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
+ ROCKS_LOG_WARN(
+ ioptions_.info_log,
+ "[%s] Stalling writes because of estimated pending compaction "
+ "bytes %" PRIu64 " rate %" PRIu64,
+ name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
+ write_controller->delayed_write_rate());
+ } else {
+ assert(write_stall_condition == WriteStallCondition::kNormal);
+ if (vstorage->l0_delay_trigger_count() >=
+ GetL0ThresholdSpeedupCompaction(
+ mutable_cf_options.level0_file_num_compaction_trigger,
+ mutable_cf_options.level0_slowdown_writes_trigger)) {
+ write_controller_token_ =
+ write_controller->GetCompactionPressureToken();
+ ROCKS_LOG_INFO(
+ ioptions_.info_log,
+ "[%s] Increasing compaction threads because we have %d level-0 "
+ "files ",
+ name_.c_str(), vstorage->l0_delay_trigger_count());
+ } else if (vstorage->estimated_compaction_needed_bytes() >=
+ mutable_cf_options.soft_pending_compaction_bytes_limit / 4) {
+ // Increase compaction threads if bytes needed for compaction exceeds
+ // 1/4 of threshold for slowing down.
+ // If soft pending compaction byte limit is not set, always speed up
+ // compaction.
+ write_controller_token_ =
+ write_controller->GetCompactionPressureToken();
+ if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0) {
+ ROCKS_LOG_INFO(
+ ioptions_.info_log,
+ "[%s] Increasing compaction threads because of estimated pending "
+ "compaction "
+ "bytes %" PRIu64,
+ name_.c_str(), vstorage->estimated_compaction_needed_bytes());
+ }
+ } else {
+ write_controller_token_.reset();
+ }
+ // If the DB recovers from delay conditions, we reward with reducing
+ // double the slowdown ratio. This is to balance the long term slowdown
+ // increase signal.
+ if (needed_delay) {
+ uint64_t write_rate = write_controller->delayed_write_rate();
+ write_controller->set_delayed_write_rate(static_cast<uint64_t>(
+ static_cast<double>(write_rate) * kDelayRecoverSlowdownRatio));
+ // Set the low pri limit to be 1/4 the delayed write rate.
+ // Note we don't reset this value even after delay condition is relased.
+ // Low-pri rate will continue to apply if there is a compaction
+ // pressure.
+ write_controller->low_pri_rate_limiter()->SetBytesPerSecond(write_rate /
+ 4);
+ }
+ }
+ prev_compaction_needed_bytes_ = compaction_needed_bytes;
+ }
+ return write_stall_condition;
+}
+
+const FileOptions* ColumnFamilyData::soptions() const {
+ return &(column_family_set_->file_options_);
+}
+
+void ColumnFamilyData::SetCurrent(Version* current_version) {
+ current_ = current_version;
+}
+
+uint64_t ColumnFamilyData::GetNumLiveVersions() const {
+ return VersionSet::GetNumLiveVersions(dummy_versions_);
+}
+
+uint64_t ColumnFamilyData::GetTotalSstFilesSize() const {
+ return VersionSet::GetTotalSstFilesSize(dummy_versions_);
+}
+
+uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
+ return current_->GetSstFilesSize();
+}
+
+MemTable* ColumnFamilyData::ConstructNewMemtable(
+ const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
+ return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
+ write_buffer_manager_, earliest_seq, id_);
+}
+
+void ColumnFamilyData::CreateNewMemtable(
+ const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
+ if (mem_ != nullptr) {
+ delete mem_->Unref();
+ }
+ SetMemtable(ConstructNewMemtable(mutable_cf_options, earliest_seq));
+ mem_->Ref();
+}
+
+bool ColumnFamilyData::NeedsCompaction() const {
+ return compaction_picker_->NeedsCompaction(current_->storage_info());
+}
+
+Compaction* ColumnFamilyData::PickCompaction(
+ const MutableCFOptions& mutable_options, LogBuffer* log_buffer) {
+ SequenceNumber earliest_mem_seqno =
+ std::min(mem_->GetEarliestSequenceNumber(),
+ imm_.current()->GetEarliestSequenceNumber(false));
+ auto* result = compaction_picker_->PickCompaction(
+ GetName(), mutable_options, current_->storage_info(), log_buffer,
+ earliest_mem_seqno);
+ if (result != nullptr) {
+ result->SetInputVersion(current_);
+ }
+ return result;
+}
+
+bool ColumnFamilyData::RangeOverlapWithCompaction(
+ const Slice& smallest_user_key, const Slice& largest_user_key,
+ int level) const {
+ return compaction_picker_->RangeOverlapWithCompaction(
+ smallest_user_key, largest_user_key, level);
+}
+
+Status ColumnFamilyData::RangesOverlapWithMemtables(
+ const autovector<Range>& ranges, SuperVersion* super_version,
+ bool* overlap) {
+ assert(overlap != nullptr);
+ *overlap = false;
+ // Create an InternalIterator over all unflushed memtables
+ Arena arena;
+ ReadOptions read_opts;
+ read_opts.total_order_seek = true;
+ MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
+ merge_iter_builder.AddIterator(
+ super_version->mem->NewIterator(read_opts, &arena));
+ super_version->imm->AddIterators(read_opts, &merge_iter_builder);
+ ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
+
+ auto read_seq = super_version->current->version_set()->LastSequence();
+ ReadRangeDelAggregator range_del_agg(&internal_comparator_, read_seq);
+ auto* active_range_del_iter =
+ super_version->mem->NewRangeTombstoneIterator(read_opts, read_seq);
+ range_del_agg.AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator>(active_range_del_iter));
+ super_version->imm->AddRangeTombstoneIterators(read_opts, nullptr /* arena */,
+ &range_del_agg);
+
+ Status status;
+ for (size_t i = 0; i < ranges.size() && status.ok() && !*overlap; ++i) {
+ auto* vstorage = super_version->current->storage_info();
+ auto* ucmp = vstorage->InternalComparator()->user_comparator();
+ InternalKey range_start(ranges[i].start, kMaxSequenceNumber,
+ kValueTypeForSeek);
+ memtable_iter->Seek(range_start.Encode());
+ status = memtable_iter->status();
+ ParsedInternalKey seek_result;
+ if (status.ok()) {
+ if (memtable_iter->Valid() &&
+ !ParseInternalKey(memtable_iter->key(), &seek_result)) {
+ status = Status::Corruption("DB have corrupted keys");
+ }
+ }
+ if (status.ok()) {
+ if (memtable_iter->Valid() &&
+ ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) {
+ *overlap = true;
+ } else if (range_del_agg.IsRangeOverlapped(ranges[i].start,
+ ranges[i].limit)) {
+ *overlap = true;
+ }
+ }
+ }
+ return status;
+}
+
+const int ColumnFamilyData::kCompactAllLevels = -1;
+const int ColumnFamilyData::kCompactToBaseLevel = -2;
+
+Compaction* ColumnFamilyData::CompactRange(
+ const MutableCFOptions& mutable_cf_options, int input_level,
+ int output_level, const CompactRangeOptions& compact_range_options,
+ const InternalKey* begin, const InternalKey* end,
+ InternalKey** compaction_end, bool* conflict,
+ uint64_t max_file_num_to_ignore) {
+ auto* result = compaction_picker_->CompactRange(
+ GetName(), mutable_cf_options, current_->storage_info(), input_level,
+ output_level, compact_range_options, begin, end, compaction_end, conflict,
+ max_file_num_to_ignore);
+ if (result != nullptr) {
+ result->SetInputVersion(current_);
+ }
+ return result;
+}
+
+SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(DBImpl* db) {
+ SuperVersion* sv = GetThreadLocalSuperVersion(db);
+ sv->Ref();
+ if (!ReturnThreadLocalSuperVersion(sv)) {
+ // This Unref() corresponds to the Ref() in GetThreadLocalSuperVersion()
+ // when the thread-local pointer was populated. So, the Ref() earlier in
+ // this function still prevents the returned SuperVersion* from being
+ // deleted out from under the caller.
+ sv->Unref();
+ }
+ return sv;
+}
+
+SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(DBImpl* db) {
+ // The SuperVersion is cached in thread local storage to avoid acquiring
+ // mutex when SuperVersion does not change since the last use. When a new
+ // SuperVersion is installed, the compaction or flush thread cleans up
+ // cached SuperVersion in all existing thread local storage. To avoid
+ // acquiring mutex for this operation, we use atomic Swap() on the thread
+ // local pointer to guarantee exclusive access. If the thread local pointer
+ // is being used while a new SuperVersion is installed, the cached
+ // SuperVersion can become stale. In that case, the background thread would
+ // have swapped in kSVObsolete. We re-check the value at when returning
+ // SuperVersion back to thread local, with an atomic compare and swap.
+ // The superversion will need to be released if detected to be stale.
+ void* ptr = local_sv_->Swap(SuperVersion::kSVInUse);
+ // Invariant:
+ // (1) Scrape (always) installs kSVObsolete in ThreadLocal storage
+ // (2) the Swap above (always) installs kSVInUse, ThreadLocal storage
+ // should only keep kSVInUse before ReturnThreadLocalSuperVersion call
+ // (if no Scrape happens).
+ assert(ptr != SuperVersion::kSVInUse);
+ SuperVersion* sv = static_cast<SuperVersion*>(ptr);
+ if (sv == SuperVersion::kSVObsolete ||
+ sv->version_number != super_version_number_.load()) {
+ RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_ACQUIRES);
+ SuperVersion* sv_to_delete = nullptr;
+
+ if (sv && sv->Unref()) {
+ RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_CLEANUPS);
+ db->mutex()->Lock();
+ // NOTE: underlying resources held by superversion (sst files) might
+ // not be released until the next background job.
+ sv->Cleanup();
+ if (db->immutable_db_options().avoid_unnecessary_blocking_io) {
+ db->AddSuperVersionsToFreeQueue(sv);
+ db->SchedulePurge();
+ } else {
+ sv_to_delete = sv;
+ }
+ } else {
+ db->mutex()->Lock();
+ }
+ sv = super_version_->Ref();
+ db->mutex()->Unlock();
+
+ delete sv_to_delete;
+ }
+ assert(sv != nullptr);
+ return sv;
+}
+
+bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
+ assert(sv != nullptr);
+ // Put the SuperVersion back
+ void* expected = SuperVersion::kSVInUse;
+ if (local_sv_->CompareAndSwap(static_cast<void*>(sv), expected)) {
+ // When we see kSVInUse in the ThreadLocal, we are sure ThreadLocal
+ // storage has not been altered and no Scrape has happened. The
+ // SuperVersion is still current.
+ return true;
+ } else {
+ // ThreadLocal scrape happened in the process of this GetImpl call (after
+ // thread local Swap() at the beginning and before CompareAndSwap()).
+ // This means the SuperVersion it holds is obsolete.
+ assert(expected == SuperVersion::kSVObsolete);
+ }
+ return false;
+}
+
+void ColumnFamilyData::InstallSuperVersion(
+ SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) {
+ db_mutex->AssertHeld();
+ return InstallSuperVersion(sv_context, db_mutex, mutable_cf_options_);
+}
+
+void ColumnFamilyData::InstallSuperVersion(
+ SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
+ const MutableCFOptions& mutable_cf_options) {
+ SuperVersion* new_superversion = sv_context->new_superversion.release();
+ new_superversion->db_mutex = db_mutex;
+ new_superversion->mutable_cf_options = mutable_cf_options;
+ new_superversion->Init(this, mem_, imm_.current(), current_);
+ SuperVersion* old_superversion = super_version_;
+ super_version_ = new_superversion;
+ ++super_version_number_;
+ super_version_->version_number = super_version_number_;
+ super_version_->write_stall_condition =
+ RecalculateWriteStallConditions(mutable_cf_options);
+
+ if (old_superversion != nullptr) {
+ // Reset SuperVersions cached in thread local storage.
+ // This should be done before old_superversion->Unref(). That's to ensure
+ // that local_sv_ never holds the last reference to SuperVersion, since
+ // it has no means to safely do SuperVersion cleanup.
+ ResetThreadLocalSuperVersions();
+
+ if (old_superversion->mutable_cf_options.write_buffer_size !=
+ mutable_cf_options.write_buffer_size) {
+ mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size);
+ }
+ if (old_superversion->write_stall_condition !=
+ new_superversion->write_stall_condition) {
+ sv_context->PushWriteStallNotification(
+ old_superversion->write_stall_condition,
+ new_superversion->write_stall_condition, GetName(), ioptions());
+ }
+ if (old_superversion->Unref()) {
+ old_superversion->Cleanup();
+ sv_context->superversions_to_free.push_back(old_superversion);
+ }
+ }
+}
+
+void ColumnFamilyData::ResetThreadLocalSuperVersions() {
+ autovector<void*> sv_ptrs;
+ local_sv_->Scrape(&sv_ptrs, SuperVersion::kSVObsolete);
+ for (auto ptr : sv_ptrs) {
+ assert(ptr);
+ if (ptr == SuperVersion::kSVInUse) {
+ continue;
+ }
+ auto sv = static_cast<SuperVersion*>(ptr);
+ bool was_last_ref __attribute__((__unused__));
+ was_last_ref = sv->Unref();
+ // sv couldn't have been the last reference because
+ // ResetThreadLocalSuperVersions() is called before
+ // unref'ing super_version_.
+ assert(!was_last_ref);
+ }
+}
+
+Status ColumnFamilyData::ValidateOptions(
+ const DBOptions& db_options, const ColumnFamilyOptions& cf_options) {
+ Status s;
+ s = CheckCompressionSupported(cf_options);
+ if (s.ok() && db_options.allow_concurrent_memtable_write) {
+ s = CheckConcurrentWritesSupported(cf_options);
+ }
+ if (s.ok() && db_options.unordered_write &&
+ cf_options.max_successive_merges != 0) {
+ s = Status::InvalidArgument(
+ "max_successive_merges > 0 is incompatible with unordered_write");
+ }
+ if (s.ok()) {
+ s = CheckCFPathsSupported(db_options, cf_options);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (cf_options.ttl > 0 && cf_options.ttl != kDefaultTtl) {
+ if (cf_options.table_factory->Name() != BlockBasedTableFactory().Name()) {
+ return Status::NotSupported(
+ "TTL is only supported in Block-Based Table format. ");
+ }
+ }
+
+ if (cf_options.periodic_compaction_seconds > 0 &&
+ cf_options.periodic_compaction_seconds != kDefaultPeriodicCompSecs) {
+ if (cf_options.table_factory->Name() != BlockBasedTableFactory().Name()) {
+ return Status::NotSupported(
+ "Periodic Compaction is only supported in "
+ "Block-Based Table format. ");
+ }
+ }
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+Status ColumnFamilyData::SetOptions(
+ const DBOptions& db_options,
+ const std::unordered_map<std::string, std::string>& options_map) {
+ MutableCFOptions new_mutable_cf_options;
+ Status s =
+ GetMutableOptionsFromStrings(mutable_cf_options_, options_map,
+ ioptions_.info_log, &new_mutable_cf_options);
+ if (s.ok()) {
+ ColumnFamilyOptions cf_options =
+ BuildColumnFamilyOptions(initial_cf_options_, new_mutable_cf_options);
+ s = ValidateOptions(db_options, cf_options);
+ }
+ if (s.ok()) {
+ mutable_cf_options_ = new_mutable_cf_options;
+ mutable_cf_options_.RefreshDerivedOptions(ioptions_);
+ }
+ return s;
+}
+#endif // ROCKSDB_LITE
+
+// REQUIRES: DB mutex held
+Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
+ if (initial_cf_options_.compaction_style != kCompactionStyleLevel) {
+ return Env::WLTH_NOT_SET;
+ }
+ if (level == 0) {
+ return Env::WLTH_MEDIUM;
+ }
+ int base_level = current_->storage_info()->base_level();
+
+ // L1: medium, L2: long, ...
+ if (level - base_level >= 2) {
+ return Env::WLTH_EXTREME;
+ } else if (level < base_level) {
+ // There is no restriction which prevents level passed in to be smaller
+ // than base_level.
+ return Env::WLTH_MEDIUM;
+ }
+ return static_cast<Env::WriteLifeTimeHint>(level - base_level +
+ static_cast<int>(Env::WLTH_MEDIUM));
+}
+
+Status ColumnFamilyData::AddDirectories(
+ std::map<std::string, std::shared_ptr<Directory>>* created_dirs) {
+ Status s;
+ assert(created_dirs != nullptr);
+ assert(data_dirs_.empty());
+ for (auto& p : ioptions_.cf_paths) {
+ auto existing_dir = created_dirs->find(p.path);
+
+ if (existing_dir == created_dirs->end()) {
+ std::unique_ptr<Directory> path_directory;
+ s = DBImpl::CreateAndNewDirectory(ioptions_.env, p.path, &path_directory);
+ if (!s.ok()) {
+ return s;
+ }
+ assert(path_directory != nullptr);
+ data_dirs_.emplace_back(path_directory.release());
+ (*created_dirs)[p.path] = data_dirs_.back();
+ } else {
+ data_dirs_.emplace_back(existing_dir->second);
+ }
+ }
+ assert(data_dirs_.size() == ioptions_.cf_paths.size());
+ return s;
+}
+
+Directory* ColumnFamilyData::GetDataDir(size_t path_id) const {
+ if (data_dirs_.empty()) {
+ return nullptr;
+ }
+
+ assert(path_id < data_dirs_.size());
+ return data_dirs_[path_id].get();
+}
+
+ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
+ const ImmutableDBOptions* db_options,
+ const FileOptions& file_options,
+ Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller,
+ BlockCacheTracer* const block_cache_tracer)
+ : max_column_family_(0),
+ dummy_cfd_(new ColumnFamilyData(
+ 0, "", nullptr, nullptr, nullptr, ColumnFamilyOptions(), *db_options,
+ file_options, nullptr, block_cache_tracer)),
+ default_cfd_cache_(nullptr),
+ db_name_(dbname),
+ db_options_(db_options),
+ file_options_(file_options),
+ table_cache_(table_cache),
+ write_buffer_manager_(write_buffer_manager),
+ write_controller_(write_controller),
+ block_cache_tracer_(block_cache_tracer) {
+ // initialize linked list
+ dummy_cfd_->prev_ = dummy_cfd_;
+ dummy_cfd_->next_ = dummy_cfd_;
+}
+
+ColumnFamilySet::~ColumnFamilySet() {
+ while (column_family_data_.size() > 0) {
+ // cfd destructor will delete itself from column_family_data_
+ auto cfd = column_family_data_.begin()->second;
+ bool last_ref __attribute__((__unused__));
+ last_ref = cfd->UnrefAndTryDelete();
+ assert(last_ref);
+ }
+ bool dummy_last_ref __attribute__((__unused__));
+ dummy_last_ref = dummy_cfd_->UnrefAndTryDelete();
+ assert(dummy_last_ref);
+}
+
+ColumnFamilyData* ColumnFamilySet::GetDefault() const {
+ assert(default_cfd_cache_ != nullptr);
+ return default_cfd_cache_;
+}
+
+ColumnFamilyData* ColumnFamilySet::GetColumnFamily(uint32_t id) const {
+ auto cfd_iter = column_family_data_.find(id);
+ if (cfd_iter != column_family_data_.end()) {
+ return cfd_iter->second;
+ } else {
+ return nullptr;
+ }
+}
+
+ColumnFamilyData* ColumnFamilySet::GetColumnFamily(const std::string& name)
+ const {
+ auto cfd_iter = column_families_.find(name);
+ if (cfd_iter != column_families_.end()) {
+ auto cfd = GetColumnFamily(cfd_iter->second);
+ assert(cfd != nullptr);
+ return cfd;
+ } else {
+ return nullptr;
+ }
+}
+
+uint32_t ColumnFamilySet::GetNextColumnFamilyID() {
+ return ++max_column_family_;
+}
+
+uint32_t ColumnFamilySet::GetMaxColumnFamily() { return max_column_family_; }
+
+void ColumnFamilySet::UpdateMaxColumnFamily(uint32_t new_max_column_family) {
+ max_column_family_ = std::max(new_max_column_family, max_column_family_);
+}
+
+size_t ColumnFamilySet::NumberOfColumnFamilies() const {
+ return column_families_.size();
+}
+
+// under a DB mutex AND write thread
+ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
+ const std::string& name, uint32_t id, Version* dummy_versions,
+ const ColumnFamilyOptions& options) {
+ assert(column_families_.find(name) == column_families_.end());
+ ColumnFamilyData* new_cfd = new ColumnFamilyData(
+ id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
+ *db_options_, file_options_, this, block_cache_tracer_);
+ column_families_.insert({name, id});
+ column_family_data_.insert({id, new_cfd});
+ max_column_family_ = std::max(max_column_family_, id);
+ // add to linked list
+ new_cfd->next_ = dummy_cfd_;
+ auto prev = dummy_cfd_->prev_;
+ new_cfd->prev_ = prev;
+ prev->next_ = new_cfd;
+ dummy_cfd_->prev_ = new_cfd;
+ if (id == 0) {
+ default_cfd_cache_ = new_cfd;
+ }
+ return new_cfd;
+}
+
+// REQUIRES: DB mutex held
+void ColumnFamilySet::FreeDeadColumnFamilies() {
+ autovector<ColumnFamilyData*> to_delete;
+ for (auto cfd = dummy_cfd_->next_; cfd != dummy_cfd_; cfd = cfd->next_) {
+ if (cfd->refs_.load(std::memory_order_relaxed) == 0) {
+ to_delete.push_back(cfd);
+ }
+ }
+ for (auto cfd : to_delete) {
+ // this is very rare, so it's not a problem that we do it under a mutex
+ delete cfd;
+ }
+}
+
+// under a DB mutex AND from a write thread
+void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
+ auto cfd_iter = column_family_data_.find(cfd->GetID());
+ assert(cfd_iter != column_family_data_.end());
+ column_family_data_.erase(cfd_iter);
+ column_families_.erase(cfd->GetName());
+}
+
+// under a DB mutex OR from a write thread
+bool ColumnFamilyMemTablesImpl::Seek(uint32_t column_family_id) {
+ if (column_family_id == 0) {
+ // optimization for common case
+ current_ = column_family_set_->GetDefault();
+ } else {
+ current_ = column_family_set_->GetColumnFamily(column_family_id);
+ }
+ handle_.SetCFD(current_);
+ return current_ != nullptr;
+}
+
+uint64_t ColumnFamilyMemTablesImpl::GetLogNumber() const {
+ assert(current_ != nullptr);
+ return current_->GetLogNumber();
+}
+
+MemTable* ColumnFamilyMemTablesImpl::GetMemTable() const {
+ assert(current_ != nullptr);
+ return current_->mem();
+}
+
+ColumnFamilyHandle* ColumnFamilyMemTablesImpl::GetColumnFamilyHandle() {
+ assert(current_ != nullptr);
+ return &handle_;
+}
+
+uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family) {
+ uint32_t column_family_id = 0;
+ if (column_family != nullptr) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ column_family_id = cfh->GetID();
+ }
+ return column_family_id;
+}
+
+const Comparator* GetColumnFamilyUserComparator(
+ ColumnFamilyHandle* column_family) {
+ if (column_family != nullptr) {
+ return column_family->GetComparator();
+ }
+ return nullptr;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/column_family.h b/storage/rocksdb/rocksdb/db/column_family.h
new file mode 100644
index 00000000..fcc8ea2c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/column_family.h
@@ -0,0 +1,757 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <unordered_map>
+#include <string>
+#include <vector>
+#include <atomic>
+
+#include "db/memtable_list.h"
+#include "db/table_cache.h"
+#include "db/table_properties_collector.h"
+#include "db/write_batch_internal.h"
+#include "db/write_controller.h"
+#include "options/cf_options.h"
+#include "rocksdb/compaction_job_stats.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "trace_replay/block_cache_tracer.h"
+#include "util/thread_local.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Version;
+class VersionSet;
+class VersionStorageInfo;
+class MemTable;
+class MemTableListVersion;
+class CompactionPicker;
+class Compaction;
+class InternalKey;
+class InternalStats;
+class ColumnFamilyData;
+class DBImpl;
+class LogBuffer;
+class InstrumentedMutex;
+class InstrumentedMutexLock;
+struct SuperVersionContext;
+
+extern const double kIncSlowdownRatio;
+// This file contains a list of data structures for managing column family
+// level metadata.
+//
+// The basic relationships among classes declared here are illustrated as
+// following:
+//
+// +----------------------+ +----------------------+ +--------+
+// +---+ ColumnFamilyHandle 1 | +--+ ColumnFamilyHandle 2 | | DBImpl |
+// | +----------------------+ | +----------------------+ +----+---+
+// | +--------------------------+ |
+// | | +-----------------------------+
+// | | |
+// | | +-----------------------------v-------------------------------+
+// | | | |
+// | | | ColumnFamilySet |
+// | | | |
+// | | +-------------+--------------------------+----------------+---+
+// | | | | |
+// | +-------------------------------------+ | |
+// | | | | v
+// | +-------------v-------------+ +-----v----v---------+
+// | | | | |
+// | | ColumnFamilyData 1 | | ColumnFamilyData 2 | ......
+// | | | | |
+// +---> | | |
+// | +---------+ | |
+// | | MemTable| | |
+// | | List | | |
+// +--------+---+--+-+----+----+ +--------------------++
+// | | | |
+// | | | |
+// | | | +-----------------------+
+// | | +-----------+ |
+// v +--------+ | |
+// +--------+--------+ | | |
+// | | | | +----------v----------+
+// +---> |SuperVersion 1.a +-----------------> |
+// | +------+ | | MemTableListVersion |
+// +---+-------------+ | | | | |
+// | | | | +----+------------+---+
+// | current | | | | |
+// | +-------------+ | |mem | |
+// | | | | | |
+// +-v---v-------+ +---v--v---+ +-----v----+ +----v-----+
+// | | | | | | | |
+// | Version 1.a | | memtable | | memtable | | memtable |
+// | | | 1.a | | 1.b | | 1.c |
+// +-------------+ | | | | | |
+// +----------+ +----------+ +----------+
+//
+// DBImpl keeps a ColumnFamilySet, which references to all column families by
+// pointing to respective ColumnFamilyData object of each column family.
+// This is how DBImpl can list and operate on all the column families.
+// ColumnFamilyHandle also points to ColumnFamilyData directly, so that
+// when a user executes a query, it can directly find memtables and Version
+// as well as SuperVersion to the column family, without going through
+// ColumnFamilySet.
+//
+// ColumnFamilySet points to the latest view of the LSM-tree (list of memtables
+// and SST files) indirectly, while ongoing operations may hold references
+// to a current or an out-of-date SuperVersion, which in turn points to a
+// point-in-time view of the LSM-tree. This guarantees the memtables and SST
+// files being operated on will not go away, until the SuperVersion is
+// unreferenced to 0 and destoryed.
+//
+// The following graph illustrates a possible referencing relationships:
+//
+// Column +--------------+ current +-----------+
+// Family +---->+ +------------------->+ |
+// Data | SuperVersion +----------+ | Version A |
+// | 3 | imm | | |
+// Iter2 +----->+ | +-------v------+ +-----------+
+// +-----+--------+ | MemtableList +----------------> Empty
+// | | Version r | +-----------+
+// | +--------------+ | |
+// +------------------+ current| Version B |
+// +--------------+ | +----->+ |
+// | | | | +-----+-----+
+// Compaction +>+ SuperVersion +-------------+ ^
+// Job | 2 +------+ | |current
+// | +----+ | | mem | +------------+
+// +--------------+ | | +---------------------> |
+// | +------------------------> MemTable a |
+// | mem | | |
+// +--------------+ | | +------------+
+// | +--------------------------+
+// Iter1 +-----> SuperVersion | | +------------+
+// | 1 +------------------------------>+ |
+// | +-+ | mem | MemTable b |
+// +--------------+ | | | |
+// | | +--------------+ +-----^------+
+// | |imm | MemtableList | |
+// | +--->+ Version s +------------+
+// | +--------------+
+// | +--------------+
+// | | MemtableList |
+// +------>+ Version t +--------> Empty
+// imm +--------------+
+//
+// In this example, even if the current LSM-tree consists of Version A and
+// memtable a, which is also referenced by SuperVersion, two older SuperVersion
+// SuperVersion2 and Superversion1 still exist, and are referenced by a
+// compaction job and an old iterator Iter1, respectively. SuperVersion2
+// contains Version B, memtable a and memtable b; SuperVersion1 contains
+// Version B and memtable b (mutable). As a result, Version B and memtable b
+// are prevented from being destroyed or deleted.
+
+// ColumnFamilyHandleImpl is the class that clients use to access different
+// column families. It has non-trivial destructor, which gets called when client
+// is done using the column family
+class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
+ public:
+ // create while holding the mutex
+ ColumnFamilyHandleImpl(
+ ColumnFamilyData* cfd, DBImpl* db, InstrumentedMutex* mutex);
+ // destroy without mutex
+ virtual ~ColumnFamilyHandleImpl();
+ virtual ColumnFamilyData* cfd() const { return cfd_; }
+
+ virtual uint32_t GetID() const override;
+ virtual const std::string& GetName() const override;
+ virtual Status GetDescriptor(ColumnFamilyDescriptor* desc) override;
+ virtual const Comparator* GetComparator() const override;
+
+ private:
+ ColumnFamilyData* cfd_;
+ DBImpl* db_;
+ InstrumentedMutex* mutex_;
+};
+
+// Does not ref-count ColumnFamilyData
+// We use this dummy ColumnFamilyHandleImpl because sometimes MemTableInserter
+// calls DBImpl methods. When this happens, MemTableInserter need access to
+// ColumnFamilyHandle (same as the client would need). In that case, we feed
+// MemTableInserter dummy ColumnFamilyHandle and enable it to call DBImpl
+// methods
+class ColumnFamilyHandleInternal : public ColumnFamilyHandleImpl {
+ public:
+ ColumnFamilyHandleInternal()
+ : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), internal_cfd_(nullptr) {}
+
+ void SetCFD(ColumnFamilyData* _cfd) { internal_cfd_ = _cfd; }
+ virtual ColumnFamilyData* cfd() const override { return internal_cfd_; }
+
+ private:
+ ColumnFamilyData* internal_cfd_;
+};
+
+// holds references to memtable, all immutable memtables and version
+struct SuperVersion {
+ // Accessing members of this class is not thread-safe and requires external
+ // synchronization (ie db mutex held or on write thread).
+ ColumnFamilyData* cfd;
+ MemTable* mem;
+ MemTableListVersion* imm;
+ Version* current;
+ MutableCFOptions mutable_cf_options;
+ // Version number of the current SuperVersion
+ uint64_t version_number;
+ WriteStallCondition write_stall_condition;
+
+ InstrumentedMutex* db_mutex;
+
+ // should be called outside the mutex
+ SuperVersion() = default;
+ ~SuperVersion();
+ SuperVersion* Ref();
+ // If Unref() returns true, Cleanup() should be called with mutex held
+ // before deleting this SuperVersion.
+ bool Unref();
+
+ // call these two methods with db mutex held
+ // Cleanup unrefs mem, imm and current. Also, it stores all memtables
+ // that needs to be deleted in to_delete vector. Unrefing those
+ // objects needs to be done in the mutex
+ void Cleanup();
+ void Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
+ MemTableListVersion* new_imm, Version* new_current);
+
+ // The value of dummy is not actually used. kSVInUse takes its address as a
+ // mark in the thread local storage to indicate the SuperVersion is in use
+ // by thread. This way, the value of kSVInUse is guaranteed to have no
+ // conflict with SuperVersion object address and portable on different
+ // platform.
+ static int dummy;
+ static void* const kSVInUse;
+ static void* const kSVObsolete;
+
+ private:
+ std::atomic<uint32_t> refs;
+ // We need to_delete because during Cleanup(), imm->Unref() returns
+ // all memtables that we need to free through this vector. We then
+ // delete all those memtables outside of mutex, during destruction
+ autovector<MemTable*> to_delete;
+};
+
+extern Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options);
+
+extern Status CheckConcurrentWritesSupported(
+ const ColumnFamilyOptions& cf_options);
+
+extern Status CheckCFPathsSupported(const DBOptions& db_options,
+ const ColumnFamilyOptions& cf_options);
+
+extern ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
+ const ColumnFamilyOptions& src);
+// Wrap user defined table proproties collector factories `from cf_options`
+// into internal ones in int_tbl_prop_collector_factories. Add a system internal
+// one too.
+extern void GetIntTblPropCollectorFactory(
+ const ImmutableCFOptions& ioptions,
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories);
+
+class ColumnFamilySet;
+
+// This class keeps all the data that a column family needs.
+// Most methods require DB mutex held, unless otherwise noted
+class ColumnFamilyData {
+ public:
+ ~ColumnFamilyData();
+
+ // thread-safe
+ uint32_t GetID() const { return id_; }
+ // thread-safe
+ const std::string& GetName() const { return name_; }
+
+ // Ref() can only be called from a context where the caller can guarantee
+ // that ColumnFamilyData is alive (while holding a non-zero ref already,
+ // holding a DB mutex, or as the leader in a write batch group).
+ void Ref() { refs_.fetch_add(1); }
+
+ // Unref decreases the reference count, but does not handle deletion
+ // when the count goes to 0. If this method returns true then the
+ // caller should delete the instance immediately, or later, by calling
+ // FreeDeadColumnFamilies(). Unref() can only be called while holding
+ // a DB mutex, or during single-threaded recovery.
+ bool Unref() {
+ int old_refs = refs_.fetch_sub(1);
+ assert(old_refs > 0);
+ return old_refs == 1;
+ }
+
+ // UnrefAndTryDelete() decreases the reference count and do free if needed,
+ // return true if this is freed else false, UnrefAndTryDelete() can only
+ // be called while holding a DB mutex, or during single-threaded recovery.
+ bool UnrefAndTryDelete();
+
+ // SetDropped() can only be called under following conditions:
+ // 1) Holding a DB mutex,
+ // 2) from single-threaded write thread, AND
+ // 3) from single-threaded VersionSet::LogAndApply()
+ // After dropping column family no other operation on that column family
+ // will be executed. All the files and memory will be, however, kept around
+ // until client drops the column family handle. That way, client can still
+ // access data from dropped column family.
+ // Column family can be dropped and still alive. In that state:
+ // *) Compaction and flush is not executed on the dropped column family.
+ // *) Client can continue reading from column family. Writes will fail unless
+ // WriteOptions::ignore_missing_column_families is true
+ // When the dropped column family is unreferenced, then we:
+ // *) Remove column family from the linked list maintained by ColumnFamilySet
+ // *) delete all memory associated with that column family
+ // *) delete all the files associated with that column family
+ void SetDropped();
+ bool IsDropped() const { return dropped_.load(std::memory_order_relaxed); }
+
+ // thread-safe
+ int NumberLevels() const { return ioptions_.num_levels; }
+
+ void SetLogNumber(uint64_t log_number) { log_number_ = log_number; }
+ uint64_t GetLogNumber() const { return log_number_; }
+
+ void SetFlushReason(FlushReason flush_reason) {
+ flush_reason_ = flush_reason;
+ }
+ FlushReason GetFlushReason() const { return flush_reason_; }
+ // thread-safe
+ const FileOptions* soptions() const;
+ const ImmutableCFOptions* ioptions() const { return &ioptions_; }
+ // REQUIRES: DB mutex held
+ // This returns the MutableCFOptions used by current SuperVersion
+ // You should use this API to reference MutableCFOptions most of the time.
+ const MutableCFOptions* GetCurrentMutableCFOptions() const {
+ return &(super_version_->mutable_cf_options);
+ }
+ // REQUIRES: DB mutex held
+ // This returns the latest MutableCFOptions, which may be not in effect yet.
+ const MutableCFOptions* GetLatestMutableCFOptions() const {
+ return &mutable_cf_options_;
+ }
+
+ // REQUIRES: DB mutex held
+ // Build ColumnFamiliesOptions with immutable options and latest mutable
+ // options.
+ ColumnFamilyOptions GetLatestCFOptions() const;
+
+ bool is_delete_range_supported() { return is_delete_range_supported_; }
+
+ // Validate CF options against DB options
+ static Status ValidateOptions(const DBOptions& db_options,
+ const ColumnFamilyOptions& cf_options);
+#ifndef ROCKSDB_LITE
+ // REQUIRES: DB mutex held
+ Status SetOptions(
+ const DBOptions& db_options,
+ const std::unordered_map<std::string, std::string>& options_map);
+#endif // ROCKSDB_LITE
+
+ InternalStats* internal_stats() { return internal_stats_.get(); }
+
+ MemTableList* imm() { return &imm_; }
+ MemTable* mem() { return mem_; }
+ Version* current() { return current_; }
+ Version* dummy_versions() { return dummy_versions_; }
+ void SetCurrent(Version* _current);
+ uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
+ uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
+ uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
+ void SetMemtable(MemTable* new_mem) {
+ uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
+ new_mem->SetID(memtable_id);
+ mem_ = new_mem;
+ }
+
+ // calculate the oldest log needed for the durability of this column family
+ uint64_t OldestLogToKeep();
+
+ // See Memtable constructor for explanation of earliest_seq param.
+ MemTable* ConstructNewMemtable(const MutableCFOptions& mutable_cf_options,
+ SequenceNumber earliest_seq);
+ void CreateNewMemtable(const MutableCFOptions& mutable_cf_options,
+ SequenceNumber earliest_seq);
+
+ TableCache* table_cache() const { return table_cache_.get(); }
+
+ // See documentation in compaction_picker.h
+ // REQUIRES: DB mutex held
+ bool NeedsCompaction() const;
+ // REQUIRES: DB mutex held
+ Compaction* PickCompaction(const MutableCFOptions& mutable_options,
+ LogBuffer* log_buffer);
+
+ // Check if the passed range overlap with any running compactions.
+ // REQUIRES: DB mutex held
+ bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ int level) const;
+
+ // Check if the passed ranges overlap with any unflushed memtables
+ // (immutable or mutable).
+ //
+ // @param super_version A referenced SuperVersion that will be held for the
+ // duration of this function.
+ //
+ // Thread-safe
+ Status RangesOverlapWithMemtables(const autovector<Range>& ranges,
+ SuperVersion* super_version, bool* overlap);
+
+ // A flag to tell a manual compaction is to compact all levels together
+ // instead of a specific level.
+ static const int kCompactAllLevels;
+ // A flag to tell a manual compaction's output is base level.
+ static const int kCompactToBaseLevel;
+ // REQUIRES: DB mutex held
+ Compaction* CompactRange(const MutableCFOptions& mutable_cf_options,
+ int input_level, int output_level,
+ const CompactRangeOptions& compact_range_options,
+ const InternalKey* begin, const InternalKey* end,
+ InternalKey** compaction_end, bool* manual_conflict,
+ uint64_t max_file_num_to_ignore);
+
+ CompactionPicker* compaction_picker() { return compaction_picker_.get(); }
+ // thread-safe
+ const Comparator* user_comparator() const {
+ return internal_comparator_.user_comparator();
+ }
+ // thread-safe
+ const InternalKeyComparator& internal_comparator() const {
+ return internal_comparator_;
+ }
+
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories() const {
+ return &int_tbl_prop_collector_factories_;
+ }
+
+ SuperVersion* GetSuperVersion() { return super_version_; }
+ // thread-safe
+ // Return a already referenced SuperVersion to be used safely.
+ SuperVersion* GetReferencedSuperVersion(DBImpl* db);
+ // thread-safe
+ // Get SuperVersion stored in thread local storage. If it does not exist,
+ // get a reference from a current SuperVersion.
+ SuperVersion* GetThreadLocalSuperVersion(DBImpl* db);
+ // Try to return SuperVersion back to thread local storage. Retrun true on
+ // success and false on failure. It fails when the thread local storage
+ // contains anything other than SuperVersion::kSVInUse flag.
+ bool ReturnThreadLocalSuperVersion(SuperVersion* sv);
+ // thread-safe
+ uint64_t GetSuperVersionNumber() const {
+ return super_version_number_.load();
+ }
+ // will return a pointer to SuperVersion* if previous SuperVersion
+ // if its reference count is zero and needs deletion or nullptr if not
+ // As argument takes a pointer to allocated SuperVersion to enable
+ // the clients to allocate SuperVersion outside of mutex.
+ // IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
+ void InstallSuperVersion(SuperVersionContext* sv_context,
+ InstrumentedMutex* db_mutex,
+ const MutableCFOptions& mutable_cf_options);
+ void InstallSuperVersion(SuperVersionContext* sv_context,
+ InstrumentedMutex* db_mutex);
+
+ void ResetThreadLocalSuperVersions();
+
+ // Protected by DB mutex
+ void set_queued_for_flush(bool value) { queued_for_flush_ = value; }
+ void set_queued_for_compaction(bool value) { queued_for_compaction_ = value; }
+ bool queued_for_flush() { return queued_for_flush_; }
+ bool queued_for_compaction() { return queued_for_compaction_; }
+
+ enum class WriteStallCause {
+ kNone,
+ kMemtableLimit,
+ kL0FileCountLimit,
+ kPendingCompactionBytes,
+ };
+ static std::pair<WriteStallCondition, WriteStallCause>
+ GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files,
+ uint64_t num_compaction_needed_bytes,
+ const MutableCFOptions& mutable_cf_options);
+
+ // Recalculate some small conditions, which are changed only during
+ // compaction, adding new memtable and/or
+ // recalculation of compaction score. These values are used in
+ // DBImpl::MakeRoomForWrite function to decide, if it need to make
+ // a write stall
+ WriteStallCondition RecalculateWriteStallConditions(
+ const MutableCFOptions& mutable_cf_options);
+
+ void set_initialized() { initialized_.store(true); }
+
+ bool initialized() const { return initialized_.load(); }
+
+ const ColumnFamilyOptions& initial_cf_options() {
+ return initial_cf_options_;
+ }
+
+ Env::WriteLifeTimeHint CalculateSSTWriteHint(int level);
+
+ // created_dirs remembers directory created, so that we don't need to call
+ // the same data creation operation again.
+ Status AddDirectories(
+ std::map<std::string, std::shared_ptr<Directory>>* created_dirs);
+
+ Directory* GetDataDir(size_t path_id) const;
+
+ ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
+
+ private:
+ friend class ColumnFamilySet;
+ ColumnFamilyData(uint32_t id, const std::string& name,
+ Version* dummy_versions, Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ const ColumnFamilyOptions& options,
+ const ImmutableDBOptions& db_options,
+ const FileOptions& file_options,
+ ColumnFamilySet* column_family_set,
+ BlockCacheTracer* const block_cache_tracer);
+
+ uint32_t id_;
+ const std::string name_;
+ Version* dummy_versions_; // Head of circular doubly-linked list of versions.
+ Version* current_; // == dummy_versions->prev_
+
+ std::atomic<int> refs_; // outstanding references to ColumnFamilyData
+ std::atomic<bool> initialized_;
+ std::atomic<bool> dropped_; // true if client dropped it
+
+ const InternalKeyComparator internal_comparator_;
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories_;
+
+ const ColumnFamilyOptions initial_cf_options_;
+ const ImmutableCFOptions ioptions_;
+ MutableCFOptions mutable_cf_options_;
+
+ const bool is_delete_range_supported_;
+
+ std::unique_ptr<TableCache> table_cache_;
+
+ std::unique_ptr<InternalStats> internal_stats_;
+
+ WriteBufferManager* write_buffer_manager_;
+
+ MemTable* mem_;
+ MemTableList imm_;
+ SuperVersion* super_version_;
+
+ // An ordinal representing the current SuperVersion. Updated by
+ // InstallSuperVersion(), i.e. incremented every time super_version_
+ // changes.
+ std::atomic<uint64_t> super_version_number_;
+
+ // Thread's local copy of SuperVersion pointer
+ // This needs to be destructed before mutex_
+ std::unique_ptr<ThreadLocalPtr> local_sv_;
+
+ // pointers for a circular linked list. we use it to support iterations over
+ // all column families that are alive (note: dropped column families can also
+ // be alive as long as client holds a reference)
+ ColumnFamilyData* next_;
+ ColumnFamilyData* prev_;
+
+ // This is the earliest log file number that contains data from this
+ // Column Family. All earlier log files must be ignored and not
+ // recovered from
+ uint64_t log_number_;
+
+ std::atomic<FlushReason> flush_reason_;
+
+ // An object that keeps all the compaction stats
+ // and picks the next compaction
+ std::unique_ptr<CompactionPicker> compaction_picker_;
+
+ ColumnFamilySet* column_family_set_;
+
+ std::unique_ptr<WriteControllerToken> write_controller_token_;
+
+ // If true --> this ColumnFamily is currently present in DBImpl::flush_queue_
+ bool queued_for_flush_;
+
+ // If true --> this ColumnFamily is currently present in
+ // DBImpl::compaction_queue_
+ bool queued_for_compaction_;
+
+ uint64_t prev_compaction_needed_bytes_;
+
+ // if the database was opened with 2pc enabled
+ bool allow_2pc_;
+
+ // Memtable id to track flush.
+ std::atomic<uint64_t> last_memtable_id_;
+
+ // Directories corresponding to cf_paths.
+ std::vector<std::shared_ptr<Directory>> data_dirs_;
+};
+
+// ColumnFamilySet has interesting thread-safety requirements
+// * CreateColumnFamily() or RemoveColumnFamily() -- need to be protected by DB
+// mutex AND executed in the write thread.
+// CreateColumnFamily() should ONLY be called from VersionSet::LogAndApply() AND
+// single-threaded write thread. It is also called during Recovery and in
+// DumpManifest().
+// RemoveColumnFamily() is only called from SetDropped(). DB mutex needs to be
+// held and it needs to be executed from the write thread. SetDropped() also
+// guarantees that it will be called only from single-threaded LogAndApply(),
+// but this condition is not that important.
+// * Iteration -- hold DB mutex, but you can release it in the body of
+// iteration. If you release DB mutex in body, reference the column
+// family before the mutex and unreference after you unlock, since the column
+// family might get dropped when the DB mutex is released
+// * GetDefault() -- thread safe
+// * GetColumnFamily() -- either inside of DB mutex or from a write thread
+// * GetNextColumnFamilyID(), GetMaxColumnFamily(), UpdateMaxColumnFamily(),
+// NumberOfColumnFamilies -- inside of DB mutex
+class ColumnFamilySet {
+ public:
+ // ColumnFamilySet supports iteration
+ class iterator {
+ public:
+ explicit iterator(ColumnFamilyData* cfd)
+ : current_(cfd) {}
+ iterator& operator++() {
+ // dropped column families might still be included in this iteration
+ // (we're only removing them when client drops the last reference to the
+ // column family).
+ // dummy is never dead, so this will never be infinite
+ do {
+ current_ = current_->next_;
+ } while (current_->refs_.load(std::memory_order_relaxed) == 0);
+ return *this;
+ }
+ bool operator!=(const iterator& other) {
+ return this->current_ != other.current_;
+ }
+ ColumnFamilyData* operator*() { return current_; }
+
+ private:
+ ColumnFamilyData* current_;
+ };
+
+ ColumnFamilySet(const std::string& dbname,
+ const ImmutableDBOptions* db_options,
+ const FileOptions& file_options, Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller,
+ BlockCacheTracer* const block_cache_tracer);
+ ~ColumnFamilySet();
+
+ ColumnFamilyData* GetDefault() const;
+ // GetColumnFamily() calls return nullptr if column family is not found
+ ColumnFamilyData* GetColumnFamily(uint32_t id) const;
+ ColumnFamilyData* GetColumnFamily(const std::string& name) const;
+ // this call will return the next available column family ID. it guarantees
+ // that there is no column family with id greater than or equal to the
+ // returned value in the current running instance or anytime in RocksDB
+ // instance history.
+ uint32_t GetNextColumnFamilyID();
+ uint32_t GetMaxColumnFamily();
+ void UpdateMaxColumnFamily(uint32_t new_max_column_family);
+ size_t NumberOfColumnFamilies() const;
+
+ ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
+ Version* dummy_version,
+ const ColumnFamilyOptions& options);
+
+ iterator begin() { return iterator(dummy_cfd_->next_); }
+ iterator end() { return iterator(dummy_cfd_); }
+
+ // REQUIRES: DB mutex held
+ // Don't call while iterating over ColumnFamilySet
+ void FreeDeadColumnFamilies();
+
+ Cache* get_table_cache() { return table_cache_; }
+
+ private:
+ friend class ColumnFamilyData;
+ // helper function that gets called from cfd destructor
+ // REQUIRES: DB mutex held
+ void RemoveColumnFamily(ColumnFamilyData* cfd);
+
+ // column_families_ and column_family_data_ need to be protected:
+ // * when mutating both conditions have to be satisfied:
+ // 1. DB mutex locked
+ // 2. thread currently in single-threaded write thread
+ // * when reading, at least one condition needs to be satisfied:
+ // 1. DB mutex locked
+ // 2. accessed from a single-threaded write thread
+ std::unordered_map<std::string, uint32_t> column_families_;
+ std::unordered_map<uint32_t, ColumnFamilyData*> column_family_data_;
+
+ uint32_t max_column_family_;
+ ColumnFamilyData* dummy_cfd_;
+ // We don't hold the refcount here, since default column family always exists
+ // We are also not responsible for cleaning up default_cfd_cache_. This is
+ // just a cache that makes common case (accessing default column family)
+ // faster
+ ColumnFamilyData* default_cfd_cache_;
+
+ const std::string db_name_;
+ const ImmutableDBOptions* const db_options_;
+ const FileOptions file_options_;
+ Cache* table_cache_;
+ WriteBufferManager* write_buffer_manager_;
+ WriteController* write_controller_;
+ BlockCacheTracer* const block_cache_tracer_;
+};
+
+// We use ColumnFamilyMemTablesImpl to provide WriteBatch a way to access
+// memtables of different column families (specified by ID in the write batch)
+class ColumnFamilyMemTablesImpl : public ColumnFamilyMemTables {
+ public:
+ explicit ColumnFamilyMemTablesImpl(ColumnFamilySet* column_family_set)
+ : column_family_set_(column_family_set), current_(nullptr) {}
+
+ // Constructs a ColumnFamilyMemTablesImpl equivalent to one constructed
+ // with the arguments used to construct *orig.
+ explicit ColumnFamilyMemTablesImpl(ColumnFamilyMemTablesImpl* orig)
+ : column_family_set_(orig->column_family_set_), current_(nullptr) {}
+
+ // sets current_ to ColumnFamilyData with column_family_id
+ // returns false if column family doesn't exist
+ // REQUIRES: use this function of DBImpl::column_family_memtables_ should be
+ // under a DB mutex OR from a write thread
+ bool Seek(uint32_t column_family_id) override;
+
+ // Returns log number of the selected column family
+ // REQUIRES: under a DB mutex OR from a write thread
+ uint64_t GetLogNumber() const override;
+
+ // REQUIRES: Seek() called first
+ // REQUIRES: use this function of DBImpl::column_family_memtables_ should be
+ // under a DB mutex OR from a write thread
+ virtual MemTable* GetMemTable() const override;
+
+ // Returns column family handle for the selected column family
+ // REQUIRES: use this function of DBImpl::column_family_memtables_ should be
+ // under a DB mutex OR from a write thread
+ virtual ColumnFamilyHandle* GetColumnFamilyHandle() override;
+
+ // Cannot be called while another thread is calling Seek().
+ // REQUIRES: use this function of DBImpl::column_family_memtables_ should be
+ // under a DB mutex OR from a write thread
+ virtual ColumnFamilyData* current() override { return current_; }
+
+ private:
+ ColumnFamilySet* column_family_set_;
+ ColumnFamilyData* current_;
+ ColumnFamilyHandleInternal handle_;
+};
+
+extern uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family);
+
+extern const Comparator* GetColumnFamilyUserComparator(
+ ColumnFamilyHandle* column_family);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/column_family_test.cc b/storage/rocksdb/rocksdb/db/column_family_test.cc
new file mode 100644
index 00000000..24ff4e08
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/column_family_test.cc
@@ -0,0 +1,3387 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <vector>
+#include <string>
+#include <thread>
+
+#include "db/db_impl/db_impl.h"
+#include "db/db_test_util.h"
+#include "memtable/hash_skiplist_rep.h"
+#include "options/options_parser.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/utilities/object_registry.h"
+#include "test_util/fault_injection_test_env.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/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static const int kValueSize = 1000;
+
+namespace {
+std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+} // anonymous namespace
+
+// counts how many operations were performed
+class EnvCounter : public EnvWrapper {
+ public:
+ explicit EnvCounter(Env* base)
+ : EnvWrapper(base), num_new_writable_file_(0) {}
+ int GetNumberOfNewWritableFileCalls() {
+ return num_new_writable_file_;
+ }
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& soptions) override {
+ ++num_new_writable_file_;
+ return EnvWrapper::NewWritableFile(f, r, soptions);
+ }
+
+ private:
+ std::atomic<int> num_new_writable_file_;
+};
+
+class ColumnFamilyTestBase : public testing::Test {
+ public:
+ explicit ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
+ Env* base_env = Env::Default();
+#ifndef ROCKSDB_LITE
+ const char* test_env_uri = getenv("TEST_ENV_URI");
+ if (test_env_uri) {
+ Env* test_env = nullptr;
+ Status s = Env::LoadEnv(test_env_uri, &test_env, &env_guard_);
+ base_env = test_env;
+ EXPECT_OK(s);
+ EXPECT_NE(Env::Default(), base_env);
+ }
+#endif // !ROCKSDB_LITE
+ EXPECT_NE(nullptr, base_env);
+ env_ = new EnvCounter(base_env);
+ dbname_ = test::PerThreadDBPath("column_family_test");
+ db_options_.create_if_missing = true;
+ db_options_.fail_if_options_file_error = true;
+ db_options_.env = env_;
+ DestroyDB(dbname_, Options(db_options_, column_family_options_));
+ }
+
+ ~ColumnFamilyTestBase() override {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (auto h : handles_) {
+ ColumnFamilyDescriptor cfdescriptor;
+ h->GetDescriptor(&cfdescriptor);
+ column_families.push_back(cfdescriptor);
+ }
+ Close();
+ 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, ' ');
+ return Slice(*storage);
+ } else {
+ Random r(k);
+ return test::RandomString(&r, kValueSize, 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 = reinterpret_cast<DBImpl*>(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<int>(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) {
+ db_->DestroyColumnFamilyHandle(h);
+ }
+ }
+ handles_.clear();
+ names_.clear();
+ delete db_;
+ db_ = nullptr;
+ }
+
+ Status TryOpen(std::vector<std::string> cf,
+ std::vector<ColumnFamilyOptions> options = {}) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ names_.clear();
+ for (size_t i = 0; i < cf.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ 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<std::string> cf,
+ std::vector<ColumnFamilyOptions> options = {}) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ names_.clear();
+ for (size_t i = 0; i < cf.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ 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_);
+ }
+
+#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
+ void AssertOpenReadOnly(std::vector<std::string> cf,
+ std::vector<ColumnFamilyOptions> options = {}) {
+ ASSERT_OK(OpenReadOnly(cf, options));
+ }
+#endif // !ROCKSDB_LITE
+
+
+ void Open(std::vector<std::string> cf,
+ std::vector<ColumnFamilyOptions> options = {}) {
+ ASSERT_OK(TryOpen(cf, options));
+ }
+
+ void Open() {
+ Open({"default"});
+ }
+
+ DBImpl* dbfull() { return reinterpret_cast<DBImpl*>(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() {
+#ifndef ROCKSDB_LITE
+ uint64_t v;
+ EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.is-write-stopped", &v));
+ return (v == 1);
+#else
+ return dbfull()->TEST_write_controler().IsStopped();
+#endif // !ROCKSDB_LITE
+ }
+
+ uint64_t GetDbDelayedWriteRate() {
+#ifndef ROCKSDB_LITE
+ uint64_t v;
+ EXPECT_TRUE(
+ dbfull()->GetIntProperty("rocksdb.actual-delayed-write-rate", &v));
+ return v;
+#else
+ if (!dbfull()->TEST_write_controler().NeedsDelay()) {
+ return 0;
+ }
+ return dbfull()->TEST_write_controler().delayed_write_rate();
+#endif // !ROCKSDB_LITE
+ }
+
+ void Destroy(const std::vector<ColumnFamilyDescriptor>& column_families =
+ std::vector<ColumnFamilyDescriptor>()) {
+ Close();
+ ASSERT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_),
+ column_families));
+ }
+
+ void CreateColumnFamilies(
+ const std::vector<std::string>& cfs,
+ const std::vector<ColumnFamilyOptions> options = {}) {
+ int cfi = static_cast<int>(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];
+
+#ifndef ROCKSDB_LITE // RocksDBLite does not support GetDescriptor
+ // Verify the CF options of the returned CF handle.
+ ColumnFamilyDescriptor desc;
+ ASSERT_OK(handles_[cfi]->GetDescriptor(&desc));
+ RocksDBOptionsParser::VerifyCFOptions(desc.options, current_cf_opt);
+#endif // !ROCKSDB_LITE
+ cfi++;
+ }
+ }
+
+ void Reopen(const std::vector<ColumnFamilyOptions> options = {}) {
+ std::vector<std::string> 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<std::string>& cfs) {
+ CreateColumnFamilies(cfs);
+ Reopen();
+ }
+
+ void DropColumnFamilies(const std::vector<int>& cfs) {
+ for (auto cf : cfs) {
+ ASSERT_OK(db_->DropColumnFamily(handles_[cf]));
+ 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<int>(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),
+ RandomString(&rnd_, key_value_size - 10)));
+ } else {
+ std::string key = test::RandomKey(&rnd_, 11);
+ keys_[cf].insert(key);
+ ASSERT_OK(Put(cf, key, RandomString(&rnd_, key_value_size - 10)));
+ }
+ }
+ db_->FlushWAL(false);
+ }
+
+#ifndef ROCKSDB_LITE // TEST functions in DB are not supported in lite
+ 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());
+ }
+#endif // !ROCKSDB_LITE
+
+ 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" + ToString(level));
+ }
+
+#ifndef ROCKSDB_LITE
+ // 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<int>(result.size());
+ }
+ }
+ result.resize(last_non_zero_offset);
+ return result;
+ }
+#endif
+
+ void AssertFilesPerLevel(const std::string& value, int cf) {
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(value, FilesPerLevel(cf));
+#else
+ (void) value;
+ (void) cf;
+#endif
+ }
+
+#ifndef ROCKSDB_LITE // GetLiveFilesMetaData is not supported
+ int CountLiveFiles() {
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ return static_cast<int>(metadata.size());
+ }
+#endif // !ROCKSDB_LITE
+
+ void AssertCountLiveFiles(int expected_value) {
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(expected_value, CountLiveFiles());
+#else
+ (void) expected_value;
+#endif
+ }
+
+ // 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]));
+ }
+ }
+
+#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
+ 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;
+ }
+#endif // !ROCKSDB_LITE
+
+ void AssertCountLiveLogFiles(int value) {
+#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
+ ASSERT_EQ(value, CountLiveLogFiles());
+#else
+ (void) value;
+#endif // !ROCKSDB_LITE
+ }
+
+ void AssertNumberOfImmutableMemtables(std::vector<int> num_per_cf) {
+ assert(num_per_cf.size() == handles_.size());
+
+#ifndef ROCKSDB_LITE // GetProperty is not supported in lite
+ for (size_t i = 0; i < num_per_cf.size(); ++i) {
+ ASSERT_EQ(num_per_cf[i], GetProperty(static_cast<int>(i),
+ "rocksdb.num-immutable-mem-table"));
+ }
+#endif // !ROCKSDB_LITE
+ }
+
+ void CopyFile(const std::string& source, const std::string& destination,
+ uint64_t size = 0) {
+ const EnvOptions soptions;
+ std::unique_ptr<SequentialFile> srcfile;
+ ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions));
+ std::unique_ptr<WritableFile> 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<std::string> files;
+ DBTestBase::GetSstFiles(env_, path, &files);
+ return static_cast<int>(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<ColumnFamilyHandle*> handles_;
+ std::vector<std::string> names_;
+ std::vector<std::set<std::string>> keys_;
+ ColumnFamilyOptions column_family_options_;
+ DBOptions db_options_;
+ std::string dbname_;
+ DB* db_ = nullptr;
+ EnvCounter* env_;
+ std::shared_ptr<Env> env_guard_;
+ Random rnd_;
+ uint32_t format_;
+};
+
+class ColumnFamilyTest
+ : public ColumnFamilyTestBase,
+ virtual public ::testing::WithParamInterface<uint32_t> {
+ public:
+ ColumnFamilyTest() : ColumnFamilyTestBase(GetParam()) {}
+};
+
+INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
+ testing::Values(test::kDefaultFormatVersion));
+INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
+ testing::Values(test::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 = reinterpret_cast<ColumnFamilyHandleImpl*>(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 = reinterpret_cast<ColumnFamilyHandleImpl*>(handles_[3]);
+ ASSERT_EQ(4U, cfh3->GetID());
+ Close();
+ Destroy();
+ }
+}
+
+#ifndef ROCKSDB_LITE
+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();
+}
+#endif // !ROCKSDB_LITE
+
+class FlushEmptyCFTestWithParam
+ : public ColumnFamilyTestBase,
+ virtual public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
+ 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<FaultInjectionTestEnv> 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.
+ Flush(1);
+ Flush(0);
+ ASSERT_OK(Put(1, "bar", "v3")); // seqID 4
+ ASSERT_OK(Put(1, "foo", "v4")); // seqID 5
+ db_->FlushWAL(false);
+
+ // Preserve file system state up to here to simulate a crash condition.
+ fault_env->SetFilesystemActive(false);
+ std::vector<std::string> 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<FaultInjectionTestEnv> 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.
+ 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.
+ Flush(0);
+ Flush(1);
+ Flush(2);
+ // Write to log file D
+ ASSERT_OK(Put(1, "bar", "v4")); // seqID 7
+ ASSERT_OK(Put(1, "bar", "v5")); // seqID 8
+ db_->FlushWAL(false);
+ // Preserve file system state up to here to simulate a crash condition.
+ fault_env->SetFilesystemActive(false);
+ std::vector<std::string> 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(test::kLatestFormatVersion, true),
+ std::make_tuple(test::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<std::string> families;
+ ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families));
+ std::sort(families.begin(), families.end());
+ ASSERT_TRUE(families ==
+ std::vector<std::string>({"default", "four", "three"}));
+}
+
+TEST_P(ColumnFamilyTest, BulkAddDrop) {
+ constexpr int kNumCF = 1000;
+ ColumnFamilyOptions cf_options;
+ WriteOptions write_options;
+ Open();
+ std::vector<std::string> cf_names;
+ std::vector<ColumnFamilyHandle*> cf_handles;
+ for (int i = 1; i <= kNumCF; i++) {
+ cf_names.push_back("cf1-" + ToString(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<ColumnFamilyDescriptor> cf_descriptors;
+ for (auto* handle : cf_handles) {
+ delete handle;
+ }
+ cf_handles.clear();
+ for (int i = 1; i <= kNumCF; i++) {
+ cf_descriptors.emplace_back("cf2-" + ToString(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<std::string> families;
+ ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families));
+ std::sort(families.begin(), families.end());
+ ASSERT_TRUE(families == std::vector<std::string>({"default"}));
+}
+
+TEST_P(ColumnFamilyTest, DropTest) {
+ // first iteration - dont 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, ToString(i), "bar" + ToString(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;
+ batch.Put(handles_[0], Slice("existing"), Slice("column-family"));
+ 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;
+ 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<std::string> old_files;
+ env_->GetChildren(backup_logs, &old_files);
+ for (auto& file : old_files) {
+ if (file != "." && file != "..") {
+ 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<std::string> logs;
+ env_->GetChildren(db_options_.wal_dir, &logs);
+ for (auto& log : logs) {
+ if (log != ".." && log != ".") {
+ 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) {
+ if (log != ".." && log != ".") {
+ CopyFile(backup_logs + "/" + log, db_options_.wal_dir + "/" + log);
+ }
+ }
+ }
+ }
+}
+
+#ifndef ROCKSDB_LITE // TEST functions used are not supported
+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<Iterator*> 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();
+ Flush(i);
+ AssertMaxTotalInMemoryState(max_total_in_memory_state);
+ }
+ ASSERT_OK(Put(1, "foofoo", "bar"));
+ ASSERT_OK(Put(0, "foofoo", "bar"));
+
+ for (auto* it : iterators) {
+ 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<uint64_t>::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();
+}
+#endif // !ROCKSDB_LITE
+
+TEST_P(ColumnFamilyTest, CrashAfterFlush) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(env_));
+ db_options_.env = fault_env.get();
+ Open();
+ CreateColumnFamilies({"one"});
+
+ WriteBatch batch;
+ batch.Put(handles_[0], Slice("foo"), Slice("bar"));
+ batch.Put(handles_[1], Slice("foo"), Slice("bar"));
+ ASSERT_OK(db_->Write(WriteOptions(), &batch));
+ Flush(0);
+ fault_env->SetFilesystemActive(false);
+
+ std::vector<std::string> names;
+ for (auto name : names_) {
+ if (name != "") {
+ names.push_back(name);
+ }
+ }
+ Close();
+ 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());
+}
+
+#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
+// 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<uint64_t>::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 immediatelly
+ // "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<int>(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<int>(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<int>(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();
+}
+#endif // !ROCKSDB_LITE
+
+// 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.
+//
+// #ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
+// 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();
+// }
+// #endif // !ROCKSDB_LITE
+
+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();
+}
+
+#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
+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<uint64_t>(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(ToString(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(ToString(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();
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE
+// 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(ToString(i + 1), 1);
+ }
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:3");
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ std::vector<port::Thread> 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(ToString(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<std::string>::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();
+
+ 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) {
+ cf_1_1 = 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(ToString(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(ToString(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<std::string>::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(ToString(i + 1), 1);
+ }
+ bool cf_1_1 = true;
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
+ } else if (cf_1_2) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
+ cf_1_2 = false;
+ }
+ });
+
+ 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(ToString(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<std::string>::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(ToString(i + 1), 1);
+ }
+ bool cf_1_1 = true;
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:3");
+ } else if (cf_1_2) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:2");
+ cf_1_2 = false;
+ }
+ });
+
+ 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(ToString(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<std::string>::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(ToString(i + 1), 1);
+ }
+ bool cf_1_1 = true;
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
+ } else if (cf_1_2) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
+ cf_1_2 = false;
+ }
+ });
+
+ 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(ToString(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<std::string>::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(ToString(i + 1), 1);
+ }
+ bool cf_1_1 = true;
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3");
+ } else if (cf_1_2) {
+ TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2");
+ cf_1_2 = false;
+ }
+ });
+
+ 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(ToString(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<std::string>::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();
+
+ bool cf_1_1 = true;
+ 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) {
+ TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4");
+ cf_1_1 = false;
+ TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:3");
+ } else if (cf_1_2) {
+ TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:2");
+ cf_1_2 = false;
+ }
+ });
+
+ 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(ToString(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<std::string>::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();
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // Tailing iterator not supported
+namespace {
+std::string IterStatus(Iterator* iter) {
+ std::string result;
+ if (iter->Valid()) {
+ result = iter->key().ToString() + "->" + iter->value().ToString();
+ } else {
+ 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<Iterator*> 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();
+ }
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
+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<Iterator*> 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());
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // WaitForFlush() is not supported in lite
+TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
+ Open();
+ CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
+
+ for (size_t i = 0; i < handles_.size(); ++i) {
+ PutRandomData(static_cast<int>(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<size_t>(total_new_writable_files), handles_.size() + 1);
+ Close();
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // WaitForCompaction() is not supported in lite
+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);
+ // 3 files for default column families, 1 file for column family [two], zero
+ // files for column family [one], because it's empty
+ AssertCountLiveFiles(4);
+
+ Flush(0);
+ ASSERT_EQ(0, dbfull()->TEST_total_log_size());
+ Close();
+}
+#endif // !ROCKSDB_LITE
+
+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<CompactionStyle>(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;
+ }
+ 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> iterator(
+ db_->NewIterator(ReadOptions(), handles_[2]));
+ iterator->SeekToFirst();
+
+ if (iter == 0) {
+ // Drop CF two
+ ASSERT_OK(db_->DropColumnFamily(handles_[2]));
+ } else {
+ // delete CF two
+ 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> 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> 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);
+
+ // 1MB should create ~10 files for each CF
+ int kKeysNum = 10000;
+ PutRandomData(1, kKeysNum, 100);
+
+ std::vector<port::Thread> 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);
+ 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> 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();
+}
+
+#ifndef ROCKSDB_LITE
+// skipped as persisting options is not supported in ROCKSDB_LITE
+namespace {
+std::atomic<int> test_stage(0);
+std::atomic<bool> ordered_by_writethread(false);
+const int kMainThreadStartPersistingOptionsFile = 1;
+const int kChildThreadFinishDroppingColumnFamily = 2;
+void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id,
+ std::vector<Comparator*>* 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;
+}
+} // namespace
+
+TEST_P(ColumnFamilyTest, CreateAndDropRace) {
+ const int kCfCount = 5;
+ std::vector<ColumnFamilyOptions> cf_opts;
+ std::vector<Comparator*> 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();
+}
+#endif // !ROCKSDB_LITE
+
+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<ColumnFamilyHandleImpl*>(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<ColumnFamilyHandleImpl*>(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<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
+ VersionStorageInfo* vstorage = cfd->current()->storage_info();
+
+ ColumnFamilyData* cfd1 =
+ static_cast<ColumnFamilyHandleImpl*>(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<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
+ VersionStorageInfo* vstorage = cfd->current()->storage_info();
+
+ ColumnFamilyData* cfd1 =
+ static_cast<ColumnFamilyHandleImpl*>(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, CreateAndDestoryOptions) {
+ std::unique_ptr<ColumnFamilyOptions> 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));
+}
+
+#ifndef ROCKSDB_LITE
+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(new SpecialSkipListFactory(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);
+
+ 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();
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
+TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
+ SpecialEnv env(Env::Default());
+ db_options_.env = &env;
+ db_options_.max_background_flushes = 1;
+ column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(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]);
+ // A flush will make `it` hold the last reference of its super version.
+ 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);
+
+ 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(new SpecialSkipListFactory(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]);
+ // A flush will make `it` hold the last reference of its super version.
+ 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();
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
+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(new SpecialSkipListFactory(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"));
+ 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"));
+ 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_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();
+}
+#endif // !ROCKSDB_LITE
+
+// 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"});
+
+ Put(0, "", "");
+ 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([&] { db_->SyncWAL(); });
+
+ TEST_SYNC_POINT("ColumnFamilyTest::LogSyncConflictFlush:1");
+ Flush(1);
+ Put(1, "foo", "bar");
+ 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
+ Flush(0);
+
+ // Add some more entries
+ Build(100, 100);
+
+ std::vector<std::string> filenames;
+ ASSERT_OK(env_->GetChildren(dbname_, &filenames));
+
+ // collect wal files
+ std::vector<std::string> logfs;
+ for (size_t i = 0; i < filenames.size(); i++) {
+ uint64_t number;
+ FileType type;
+ if (!(ParseFileName(filenames[i], &number, &type))) continue;
+
+ if (type != kLogFile) 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<std::string> 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
+ 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<uint64_t>::max());
+ CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2});
+ Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2});
+
+ // Fill Column family 1.
+ PutRandomData(1, 100, 100);
+ 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);
+ 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<uint64_t>::max());
+ cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1",
+ std::numeric_limits<uint64_t>::max());
+ CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2});
+ Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2});
+
+ PutRandomData(1, 100, 100, true /* save */);
+ 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 */);
+ 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 = reinterpret_cast<DBImpl*>(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()) {
+ Slice key(it->key());
+ ASSERT_NE(keys_[cf].end(), keys_[cf].find(key.ToString()));
+ }
+ delete it;
+
+ for (const auto& key : keys_[cf]) {
+ ASSERT_NE("NOT_FOUND", Get(cf, key));
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/compact_files_test.cc b/storage/rocksdb/rocksdb/db/compact_files_test.cc
new file mode 100644
index 00000000..948ada67
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compact_files_test.cc
@@ -0,0 +1,421 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#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/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<std::mutex> lock(mutex_);
+ flushed_files_.push_back(info.file_path);
+ }
+
+ std::vector<std::string> GetFlushedFiles() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ std::vector<std::string> result;
+ for (auto fname : flushed_files_) {
+ result.push_back(fname);
+ }
+ return result;
+ }
+ void ClearFlushedFiles() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ flushed_files_.clear();
+ }
+
+ private:
+ std::vector<std::string> 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;
+ 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) {
+ db->Put(WriteOptions(), ToString(i), "");
+ db->Put(WriteOptions(), ToString(100 - i), "");
+ 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, 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;
+ DestroyDB(db_name_, options);
+ Status s = DB::Open(options, db_name_, &db);
+ assert(s.ok());
+ assert(db);
+
+ // create couple files
+ for (int i = 1000; i < 2000; ++i) {
+ db->Put(WriteOptions(), ToString(i),
+ std::string(kWriteBufferSize / 10, 'a' + (i % 26)));
+ }
+
+ auto l0_files = collector->GetFlushedFiles();
+ ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1));
+ reinterpret_cast<DBImpl*>(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;
+ 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) {
+ db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
+ }
+ reinterpret_cast<DBImpl*>(db)->TEST_WaitForFlushMemTable();
+ auto l0_files_1 = collector->GetFlushedFiles();
+ collector->ClearFlushedFiles();
+ for (int i = 0; i < 500; ++i) {
+ db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
+ }
+ reinterpret_cast<DBImpl*>(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;
+ DestroyDB(db_name_, options);
+ Status s = DB::Open(options, db_name_, &db);
+ assert(s.ok());
+ assert(db);
+
+ // Create 5 files.
+ for (int i = 0; i < 5; ++i) {
+ db->Put(WriteOptions(), "key" + ToString(i), "value");
+ db->Flush(FlushOptions());
+ }
+
+ 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");
+ db->Put(WriteOptions(), "key5", "value");
+ 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_TRUE(s.ok());
+ 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<FilterWithGet> cf(new FilterWithGet());
+
+ Options options;
+ options.create_if_missing = true;
+ options.compaction_filter = cf.get();
+
+ DB* db = nullptr;
+ DestroyDB(db_name_, options);
+ Status s = DB::Open(options, db_name_, &db);
+ ASSERT_OK(s);
+
+ cf->SetDB(db);
+
+ // Write one L0 file
+ db->Put(WriteOptions(), "K1", "V1");
+ 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}) {
+ 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));
+
+ db->Put(WriteOptions(), "key", "val");
+ db->Flush(FlushOptions());
+
+ 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;
+ 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) {
+ db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
+ }
+ reinterpret_cast<DBImpl*>(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as DBImpl::CompactFiles is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compacted_db_impl.cc b/storage/rocksdb/rocksdb/db/compacted_db_impl.cc
new file mode 100644
index 00000000..47d6ecce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compacted_db_impl.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "db/compacted_db_impl.h"
+#include "db/db_impl/db_impl.h"
+#include "db/version_set.h"
+#include "table/get_context.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern void MarkKeyMayExist(void* arg);
+extern bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
+ const Slice& v, bool hit_and_return);
+
+CompactedDBImpl::CompactedDBImpl(
+ const DBOptions& options, const std::string& dbname)
+ : DBImpl(options, dbname), cfd_(nullptr), version_(nullptr),
+ user_comparator_(nullptr) {
+}
+
+CompactedDBImpl::~CompactedDBImpl() {
+}
+
+size_t CompactedDBImpl::FindFile(const Slice& key) {
+ size_t right = files_.num_files - 1;
+ auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool {
+ return user_comparator_->Compare(ExtractUserKey(f.largest_key), k) < 0;
+ };
+ return static_cast<size_t>(std::lower_bound(files_.files,
+ files_.files + right, key, cmp) - files_.files);
+}
+
+Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
+ const Slice& key, PinnableSlice* value) {
+ GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, key, value, nullptr, nullptr,
+ true, nullptr, nullptr);
+ LookupKey lkey(key, kMaxSequenceNumber);
+ files_.files[FindFile(key)].fd.table_reader->Get(options, lkey.internal_key(),
+ &get_context, nullptr);
+ if (get_context.State() == GetContext::kFound) {
+ return Status::OK();
+ }
+ return Status::NotFound();
+}
+
+std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>&,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ autovector<TableReader*, 16> reader_list;
+ for (const auto& key : keys) {
+ const FdWithKeyRange& f = files_.files[FindFile(key)];
+ if (user_comparator_->Compare(key, ExtractUserKey(f.smallest_key)) < 0) {
+ reader_list.push_back(nullptr);
+ } else {
+ LookupKey lkey(key, kMaxSequenceNumber);
+ f.fd.table_reader->Prepare(lkey.internal_key());
+ reader_list.push_back(f.fd.table_reader);
+ }
+ }
+ std::vector<Status> statuses(keys.size(), Status::NotFound());
+ values->resize(keys.size());
+ int idx = 0;
+ for (auto* r : reader_list) {
+ if (r != nullptr) {
+ PinnableSlice pinnable_val;
+ std::string& value = (*values)[idx];
+ GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, keys[idx], &pinnable_val,
+ nullptr, nullptr, true, nullptr, nullptr);
+ LookupKey lkey(keys[idx], kMaxSequenceNumber);
+ r->Get(options, lkey.internal_key(), &get_context, nullptr);
+ value.assign(pinnable_val.data(), pinnable_val.size());
+ if (get_context.State() == GetContext::kFound) {
+ statuses[idx] = Status::OK();
+ }
+ }
+ ++idx;
+ }
+ return statuses;
+}
+
+Status CompactedDBImpl::Init(const Options& options) {
+ SuperVersionContext sv_context(/* create_superversion */ true);
+ mutex_.Lock();
+ ColumnFamilyDescriptor cf(kDefaultColumnFamilyName,
+ ColumnFamilyOptions(options));
+ Status s = Recover({cf}, true /* read only */, false, true);
+ if (s.ok()) {
+ cfd_ = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ DefaultColumnFamily())->cfd();
+ cfd_->InstallSuperVersion(&sv_context, &mutex_);
+ }
+ mutex_.Unlock();
+ sv_context.Clean();
+ if (!s.ok()) {
+ return s;
+ }
+ NewThreadStatusCfInfo(cfd_);
+ version_ = cfd_->GetSuperVersion()->current;
+ user_comparator_ = cfd_->user_comparator();
+ auto* vstorage = version_->storage_info();
+ if (vstorage->num_non_empty_levels() == 0) {
+ return Status::NotSupported("no file exists");
+ }
+ const LevelFilesBrief& l0 = vstorage->LevelFilesBrief(0);
+ // L0 should not have files
+ if (l0.num_files > 1) {
+ return Status::NotSupported("L0 contain more than 1 file");
+ }
+ if (l0.num_files == 1) {
+ if (vstorage->num_non_empty_levels() > 1) {
+ return Status::NotSupported("Both L0 and other level contain files");
+ }
+ files_ = l0;
+ return Status::OK();
+ }
+
+ for (int i = 1; i < vstorage->num_non_empty_levels() - 1; ++i) {
+ if (vstorage->LevelFilesBrief(i).num_files > 0) {
+ return Status::NotSupported("Other levels also contain files");
+ }
+ }
+
+ int level = vstorage->num_non_empty_levels() - 1;
+ if (vstorage->LevelFilesBrief(level).num_files > 0) {
+ files_ = vstorage->LevelFilesBrief(level);
+ return Status::OK();
+ }
+ return Status::NotSupported("no file exists");
+}
+
+Status CompactedDBImpl::Open(const Options& options,
+ const std::string& dbname, DB** dbptr) {
+ *dbptr = nullptr;
+
+ if (options.max_open_files != -1) {
+ return Status::InvalidArgument("require max_open_files = -1");
+ }
+ if (options.merge_operator.get() != nullptr) {
+ return Status::InvalidArgument("merge operator is not supported");
+ }
+ DBOptions db_options(options);
+ std::unique_ptr<CompactedDBImpl> db(new CompactedDBImpl(db_options, dbname));
+ Status s = db->Init(options);
+ if (s.ok()) {
+ db->StartTimedTasks();
+ ROCKS_LOG_INFO(db->immutable_db_options_.info_log,
+ "Opened the db as fully compacted mode");
+ LogFlush(db->immutable_db_options_.info_log);
+ *dbptr = db.release();
+ }
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compacted_db_impl.h b/storage/rocksdb/rocksdb/db/compacted_db_impl.h
new file mode 100644
index 00000000..7099566f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compacted_db_impl.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include <string>
+#include <vector>
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class CompactedDBImpl : public DBImpl {
+ public:
+ CompactedDBImpl(const DBOptions& options, const std::string& dbname);
+ // No copying allowed
+ CompactedDBImpl(const CompactedDBImpl&) = delete;
+ void operator=(const CompactedDBImpl&) = delete;
+
+ virtual ~CompactedDBImpl();
+
+ static Status Open(const Options& options, const std::string& dbname,
+ DB** dbptr);
+
+ // Implementations of the DB interface
+ using DB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+ using DB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>&,
+ const std::vector<Slice>& keys, std::vector<std::string>* values)
+ override;
+
+ using DBImpl::Put;
+ virtual Status Put(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ using DBImpl::Merge;
+ virtual Status Merge(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ using DBImpl::Delete;
+ virtual Status Delete(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ virtual Status Write(const WriteOptions& /*options*/,
+ WriteBatch* /*updates*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ using DBImpl::CompactRange;
+ virtual Status CompactRange(const CompactRangeOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice* /*begin*/,
+ const Slice* /*end*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+
+ virtual Status DisableFileDeletions() override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ virtual Status EnableFileDeletions(bool /*force*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ virtual Status GetLiveFiles(std::vector<std::string>& ret,
+ uint64_t* manifest_file_size,
+ bool /*flush_memtable*/) override {
+ return DBImpl::GetLiveFiles(ret, manifest_file_size,
+ false /* flush_memtable */);
+ }
+ using DBImpl::Flush;
+ virtual Status Flush(const FlushOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ using DB::IngestExternalFile;
+ virtual Status IngestExternalFile(
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*external_files*/,
+ const IngestExternalFileOptions& /*ingestion_options*/) override {
+ return Status::NotSupported("Not supported in compacted db mode.");
+ }
+ 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 supported in compacted db mode.");
+ }
+
+ private:
+ friend class DB;
+ inline size_t FindFile(const Slice& key);
+ Status Init(const Options& options);
+
+ ColumnFamilyData* cfd_;
+ Version* version_;
+ const Comparator* user_comparator_;
+ LevelFilesBrief files_;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction.cc b/storage/rocksdb/rocksdb/db/compaction/compaction.cc
new file mode 100644
index 00000000..5c34fdca
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction.cc
@@ -0,0 +1,564 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/compaction/compaction.h"
+#include "rocksdb/compaction_filter.h"
+#include "test_util/sync_point.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const uint64_t kRangeTombstoneSentinel =
+ PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
+
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
+ const InternalKey& b) {
+ auto c = user_cmp->Compare(a.user_key(), b.user_key());
+ if (c != 0) {
+ return c;
+ }
+ auto a_footer = ExtractInternalKeyFooter(a.Encode());
+ auto b_footer = ExtractInternalKeyFooter(b.Encode());
+ if (a_footer == kRangeTombstoneSentinel) {
+ if (b_footer != kRangeTombstoneSentinel) {
+ return -1;
+ }
+ } else if (b_footer == kRangeTombstoneSentinel) {
+ return 1;
+ }
+ return 0;
+}
+
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
+ const InternalKey& b) {
+ if (a == nullptr) {
+ return -1;
+ }
+ return sstableKeyCompare(user_cmp, *a, b);
+}
+
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
+ const InternalKey* b) {
+ if (b == nullptr) {
+ return -1;
+ }
+ return sstableKeyCompare(user_cmp, a, *b);
+}
+
+uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
+ uint64_t sum = 0;
+ for (size_t i = 0; i < files.size() && files[i]; i++) {
+ sum += files[i]->fd.GetFileSize();
+ }
+ return sum;
+}
+
+void Compaction::SetInputVersion(Version* _input_version) {
+ input_version_ = _input_version;
+ cfd_ = input_version_->cfd();
+
+ cfd_->Ref();
+ input_version_->Ref();
+ edit_.SetColumnFamily(cfd_->GetID());
+}
+
+void Compaction::GetBoundaryKeys(
+ VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs, Slice* smallest_user_key,
+ Slice* largest_user_key) {
+ bool initialized = false;
+ const Comparator* ucmp = vstorage->InternalComparator()->user_comparator();
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ if (inputs[i].files.empty()) {
+ continue;
+ }
+ if (inputs[i].level == 0) {
+ // we need to consider all files on level 0
+ for (const auto* f : inputs[i].files) {
+ const Slice& start_user_key = f->smallest.user_key();
+ if (!initialized ||
+ ucmp->Compare(start_user_key, *smallest_user_key) < 0) {
+ *smallest_user_key = start_user_key;
+ }
+ const Slice& end_user_key = f->largest.user_key();
+ if (!initialized ||
+ ucmp->Compare(end_user_key, *largest_user_key) > 0) {
+ *largest_user_key = end_user_key;
+ }
+ initialized = true;
+ }
+ } else {
+ // we only need to consider the first and last file
+ const Slice& start_user_key = inputs[i].files[0]->smallest.user_key();
+ if (!initialized ||
+ ucmp->Compare(start_user_key, *smallest_user_key) < 0) {
+ *smallest_user_key = start_user_key;
+ }
+ const Slice& end_user_key = inputs[i].files.back()->largest.user_key();
+ if (!initialized || ucmp->Compare(end_user_key, *largest_user_key) > 0) {
+ *largest_user_key = end_user_key;
+ }
+ initialized = true;
+ }
+ }
+}
+
+std::vector<CompactionInputFiles> Compaction::PopulateWithAtomicBoundaries(
+ VersionStorageInfo* vstorage, std::vector<CompactionInputFiles> inputs) {
+ const Comparator* ucmp = vstorage->InternalComparator()->user_comparator();
+ for (size_t i = 0; i < inputs.size(); i++) {
+ if (inputs[i].level == 0 || inputs[i].files.empty()) {
+ continue;
+ }
+ inputs[i].atomic_compaction_unit_boundaries.reserve(inputs[i].files.size());
+ AtomicCompactionUnitBoundary cur_boundary;
+ size_t first_atomic_idx = 0;
+ auto add_unit_boundary = [&](size_t to) {
+ if (first_atomic_idx == to) return;
+ for (size_t k = first_atomic_idx; k < to; k++) {
+ inputs[i].atomic_compaction_unit_boundaries.push_back(cur_boundary);
+ }
+ first_atomic_idx = to;
+ };
+ for (size_t j = 0; j < inputs[i].files.size(); j++) {
+ const auto* f = inputs[i].files[j];
+ if (j == 0) {
+ // First file in a level.
+ cur_boundary.smallest = &f->smallest;
+ cur_boundary.largest = &f->largest;
+ } else if (sstableKeyCompare(ucmp, *cur_boundary.largest, f->smallest) ==
+ 0) {
+ // SSTs overlap but the end key of the previous file was not
+ // artificially extended by a range tombstone. Extend the current
+ // boundary.
+ cur_boundary.largest = &f->largest;
+ } else {
+ // Atomic compaction unit has ended.
+ add_unit_boundary(j);
+ cur_boundary.smallest = &f->smallest;
+ cur_boundary.largest = &f->largest;
+ }
+ }
+ add_unit_boundary(inputs[i].files.size());
+ assert(inputs[i].files.size() ==
+ inputs[i].atomic_compaction_unit_boundaries.size());
+ }
+ return inputs;
+}
+
+// helper function to determine if compaction is creating files at the
+// bottommost level
+bool Compaction::IsBottommostLevel(
+ int output_level, VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs) {
+ int output_l0_idx;
+ if (output_level == 0) {
+ output_l0_idx = 0;
+ for (const auto* file : vstorage->LevelFiles(0)) {
+ if (inputs[0].files.back() == file) {
+ break;
+ }
+ ++output_l0_idx;
+ }
+ assert(static_cast<size_t>(output_l0_idx) < vstorage->LevelFiles(0).size());
+ } else {
+ output_l0_idx = -1;
+ }
+ Slice smallest_key, largest_key;
+ GetBoundaryKeys(vstorage, inputs, &smallest_key, &largest_key);
+ return !vstorage->RangeMightExistAfterSortedRun(smallest_key, largest_key,
+ output_level, output_l0_idx);
+}
+
+// test function to validate the functionality of IsBottommostLevel()
+// function -- determines if compaction with inputs and storage is bottommost
+bool Compaction::TEST_IsBottommostLevel(
+ int output_level, VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs) {
+ return IsBottommostLevel(output_level, vstorage, inputs);
+}
+
+bool Compaction::IsFullCompaction(
+ VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs) {
+ size_t num_files_in_compaction = 0;
+ size_t total_num_files = 0;
+ for (int l = 0; l < vstorage->num_levels(); l++) {
+ total_num_files += vstorage->NumLevelFiles(l);
+ }
+ for (size_t i = 0; i < inputs.size(); i++) {
+ num_files_in_compaction += inputs[i].size();
+ }
+ return num_files_in_compaction == total_num_files;
+}
+
+Compaction::Compaction(VersionStorageInfo* vstorage,
+ const ImmutableCFOptions& _immutable_cf_options,
+ const MutableCFOptions& _mutable_cf_options,
+ std::vector<CompactionInputFiles> _inputs,
+ int _output_level, uint64_t _target_file_size,
+ uint64_t _max_compaction_bytes, uint32_t _output_path_id,
+ CompressionType _compression,
+ CompressionOptions _compression_opts,
+ uint32_t _max_subcompactions,
+ std::vector<FileMetaData*> _grandparents,
+ bool _manual_compaction, double _score,
+ bool _deletion_compaction,
+ CompactionReason _compaction_reason)
+ : input_vstorage_(vstorage),
+ start_level_(_inputs[0].level),
+ output_level_(_output_level),
+ max_output_file_size_(_target_file_size),
+ max_compaction_bytes_(_max_compaction_bytes),
+ max_subcompactions_(_max_subcompactions),
+ immutable_cf_options_(_immutable_cf_options),
+ mutable_cf_options_(_mutable_cf_options),
+ input_version_(nullptr),
+ number_levels_(vstorage->num_levels()),
+ cfd_(nullptr),
+ output_path_id_(_output_path_id),
+ output_compression_(_compression),
+ output_compression_opts_(_compression_opts),
+ deletion_compaction_(_deletion_compaction),
+ inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
+ grandparents_(std::move(_grandparents)),
+ score_(_score),
+ bottommost_level_(IsBottommostLevel(output_level_, vstorage, inputs_)),
+ is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
+ is_manual_compaction_(_manual_compaction),
+ is_trivial_move_(false),
+ compaction_reason_(_compaction_reason) {
+ MarkFilesBeingCompacted(true);
+ if (is_manual_compaction_) {
+ compaction_reason_ = CompactionReason::kManualCompaction;
+ }
+ if (max_subcompactions_ == 0) {
+ max_subcompactions_ = immutable_cf_options_.max_subcompactions;
+ }
+ if (!bottommost_level_) {
+ // Currently we only enable dictionary compression during compaction to the
+ // bottommost level.
+ output_compression_opts_.max_dict_bytes = 0;
+ output_compression_opts_.zstd_max_train_bytes = 0;
+ }
+
+#ifndef NDEBUG
+ for (size_t i = 1; i < inputs_.size(); ++i) {
+ assert(inputs_[i].level > inputs_[i - 1].level);
+ }
+#endif
+
+ // setup input_levels_
+ {
+ input_levels_.resize(num_input_levels());
+ for (size_t which = 0; which < num_input_levels(); which++) {
+ DoGenerateLevelFilesBrief(&input_levels_[which], inputs_[which].files,
+ &arena_);
+ }
+ }
+
+ GetBoundaryKeys(vstorage, inputs_, &smallest_user_key_, &largest_user_key_);
+}
+
+Compaction::~Compaction() {
+ if (input_version_ != nullptr) {
+ input_version_->Unref();
+ }
+ if (cfd_ != nullptr) {
+ cfd_->UnrefAndTryDelete();
+ }
+}
+
+bool Compaction::InputCompressionMatchesOutput() const {
+ int base_level = input_vstorage_->base_level();
+ bool matches = (GetCompressionType(immutable_cf_options_, input_vstorage_,
+ mutable_cf_options_, start_level_,
+ base_level) == output_compression_);
+ if (matches) {
+ TEST_SYNC_POINT("Compaction::InputCompressionMatchesOutput:Matches");
+ return true;
+ }
+ TEST_SYNC_POINT("Compaction::InputCompressionMatchesOutput:DidntMatch");
+ return matches;
+}
+
+bool Compaction::IsTrivialMove() const {
+ // Avoid a move if there is lots of overlapping grandparent data.
+ // Otherwise, the move could create a parent file that will require
+ // a very expensive merge later on.
+ // If start_level_== output_level_, the purpose is to force compaction
+ // filter to be applied to that level, and thus cannot be a trivial move.
+
+ // Check if start level have files with overlapping ranges
+ if (start_level_ == 0 && input_vstorage_->level0_non_overlapping() == false) {
+ // We cannot move files from L0 to L1 if the files are overlapping
+ return false;
+ }
+
+ if (is_manual_compaction_ &&
+ (immutable_cf_options_.compaction_filter != nullptr ||
+ immutable_cf_options_.compaction_filter_factory != nullptr)) {
+ // This is a manual compaction and we have a compaction filter that should
+ // be executed, we cannot do a trivial move
+ return false;
+ }
+
+ // Used in universal compaction, where trivial move can be done if the
+ // input files are non overlapping
+ if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
+ (output_level_ != 0)) {
+ return is_trivial_move_;
+ }
+
+ if (!(start_level_ != output_level_ && num_input_levels() == 1 &&
+ input(0, 0)->fd.GetPathId() == output_path_id() &&
+ InputCompressionMatchesOutput())) {
+ return false;
+ }
+
+ // assert inputs_.size() == 1
+
+ for (const auto& file : inputs_.front().files) {
+ std::vector<FileMetaData*> file_grand_parents;
+ if (output_level_ + 1 >= number_levels_) {
+ continue;
+ }
+ input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
+ &file->largest, &file_grand_parents);
+ const auto compaction_size =
+ file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
+ if (compaction_size > max_compaction_bytes_) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void Compaction::AddInputDeletions(VersionEdit* out_edit) {
+ for (size_t which = 0; which < num_input_levels(); which++) {
+ for (size_t i = 0; i < inputs_[which].size(); i++) {
+ out_edit->DeleteFile(level(which), inputs_[which][i]->fd.GetNumber());
+ }
+ }
+}
+
+bool Compaction::KeyNotExistsBeyondOutputLevel(
+ const Slice& user_key, std::vector<size_t>* level_ptrs) const {
+ assert(input_version_ != nullptr);
+ assert(level_ptrs != nullptr);
+ assert(level_ptrs->size() == static_cast<size_t>(number_levels_));
+ if (bottommost_level_) {
+ return true;
+ } else if (output_level_ != 0 &&
+ cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
+ // Maybe use binary search to find right entry instead of linear search?
+ const Comparator* user_cmp = cfd_->user_comparator();
+ for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
+ const std::vector<FileMetaData*>& files =
+ input_vstorage_->LevelFiles(lvl);
+ for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
+ auto* f = files[level_ptrs->at(lvl)];
+ if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
+ // We've advanced far enough
+ if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
+ // Key falls in this file's range, so it may
+ // exist beyond output level
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+// Mark (or clear) each file that is being compacted
+void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
+ for (size_t i = 0; i < num_input_levels(); i++) {
+ for (size_t j = 0; j < inputs_[i].size(); j++) {
+ assert(mark_as_compacted ? !inputs_[i][j]->being_compacted
+ : inputs_[i][j]->being_compacted);
+ inputs_[i][j]->being_compacted = mark_as_compacted;
+ }
+ }
+}
+
+// Sample output:
+// If compacting 3 L0 files, 2 L3 files and 1 L4 file, and outputting to L5,
+// print: "3@0 + 2@3 + 1@4 files to L5"
+const char* Compaction::InputLevelSummary(
+ InputLevelSummaryBuffer* scratch) const {
+ int len = 0;
+ bool is_first = true;
+ for (auto& input_level : inputs_) {
+ if (input_level.empty()) {
+ continue;
+ }
+ if (!is_first) {
+ len +=
+ snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, " + ");
+ len = std::min(len, static_cast<int>(sizeof(scratch->buffer)));
+ } else {
+ is_first = false;
+ }
+ len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
+ "%" ROCKSDB_PRIszt "@%d", input_level.size(),
+ input_level.level);
+ len = std::min(len, static_cast<int>(sizeof(scratch->buffer)));
+ }
+ snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
+ " files to L%d", output_level());
+
+ return scratch->buffer;
+}
+
+uint64_t Compaction::CalculateTotalInputSize() const {
+ uint64_t size = 0;
+ for (auto& input_level : inputs_) {
+ for (auto f : input_level.files) {
+ size += f->fd.GetFileSize();
+ }
+ }
+ return size;
+}
+
+void Compaction::ReleaseCompactionFiles(Status status) {
+ MarkFilesBeingCompacted(false);
+ cfd_->compaction_picker()->ReleaseCompactionFiles(this, status);
+}
+
+void Compaction::ResetNextCompactionIndex() {
+ assert(input_version_ != nullptr);
+ input_vstorage_->ResetNextCompactionIndex(start_level_);
+}
+
+namespace {
+int InputSummary(const std::vector<FileMetaData*>& files, char* output,
+ int len) {
+ *output = '\0';
+ int write = 0;
+ for (size_t i = 0; i < files.size(); i++) {
+ int sz = len - write;
+ int ret;
+ char sztxt[16];
+ AppendHumanBytes(files.at(i)->fd.GetFileSize(), sztxt, 16);
+ ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
+ files.at(i)->fd.GetNumber(), sztxt);
+ if (ret < 0 || ret >= sz) break;
+ write += ret;
+ }
+ // if files.size() is non-zero, overwrite the last space
+ return write - !!files.size();
+}
+} // namespace
+
+void Compaction::Summary(char* output, int len) {
+ int write =
+ snprintf(output, len, "Base version %" PRIu64 " Base level %d, inputs: [",
+ input_version_->GetVersionNumber(), start_level_);
+ if (write < 0 || write >= len) {
+ return;
+ }
+
+ for (size_t level_iter = 0; level_iter < num_input_levels(); ++level_iter) {
+ if (level_iter > 0) {
+ write += snprintf(output + write, len - write, "], [");
+ if (write < 0 || write >= len) {
+ return;
+ }
+ }
+ write +=
+ InputSummary(inputs_[level_iter].files, output + write, len - write);
+ if (write < 0 || write >= len) {
+ return;
+ }
+ }
+
+ snprintf(output + write, len - write, "]");
+}
+
+uint64_t Compaction::OutputFilePreallocationSize() const {
+ uint64_t preallocation_size = 0;
+
+ for (const auto& level_files : inputs_) {
+ for (const auto& file : level_files.files) {
+ preallocation_size += file->fd.GetFileSize();
+ }
+ }
+
+ if (max_output_file_size_ != port::kMaxUint64 &&
+ (immutable_cf_options_.compaction_style == kCompactionStyleLevel ||
+ output_level() > 0)) {
+ preallocation_size = std::min(max_output_file_size_, preallocation_size);
+ }
+
+ // Over-estimate slightly so we don't end up just barely crossing
+ // the threshold
+ // No point to prellocate more than 1GB.
+ return std::min(uint64_t{1073741824},
+ preallocation_size + (preallocation_size / 10));
+}
+
+std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
+ if (!cfd_->ioptions()->compaction_filter_factory) {
+ return nullptr;
+ }
+
+ CompactionFilter::Context context;
+ context.is_full_compaction = is_full_compaction_;
+ context.is_manual_compaction = is_manual_compaction_;
+ context.column_family_id = cfd_->GetID();
+ return cfd_->ioptions()->compaction_filter_factory->CreateCompactionFilter(
+ context);
+}
+
+bool Compaction::IsOutputLevelEmpty() const {
+ return inputs_.back().level != output_level_ || inputs_.back().empty();
+}
+
+bool Compaction::ShouldFormSubcompactions() const {
+ if (max_subcompactions_ <= 1 || cfd_ == nullptr) {
+ return false;
+ }
+ if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
+ return (start_level_ == 0 || is_manual_compaction_) && output_level_ > 0 &&
+ !IsOutputLevelEmpty();
+ } else if (cfd_->ioptions()->compaction_style == kCompactionStyleUniversal) {
+ return number_levels_ > 1 && output_level_ > 0;
+ } else {
+ return false;
+ }
+}
+
+uint64_t Compaction::MinInputFileOldestAncesterTime() const {
+ uint64_t min_oldest_ancester_time = port::kMaxUint64;
+ for (const auto& level_files : inputs_) {
+ for (const auto& file : level_files.files) {
+ uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
+ if (oldest_ancester_time != 0) {
+ min_oldest_ancester_time =
+ std::min(min_oldest_ancester_time, oldest_ancester_time);
+ }
+ }
+ }
+ return min_oldest_ancester_time;
+}
+
+int Compaction::GetInputBaseLevel() const {
+ return input_vstorage_->base_level();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction.h b/storage/rocksdb/rocksdb/db/compaction/compaction.h
new file mode 100644
index 00000000..9358e50f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction.h
@@ -0,0 +1,384 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/version_set.h"
+#include "memory/arena.h"
+#include "options/cf_options.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+// The file contains class Compaction, as well as some helper functions
+// and data structures used by the class.
+
+// Utility for comparing sstable boundary keys. Returns -1 if either a or b is
+// null which provides the property that a==null indicates a key that is less
+// than any key and b==null indicates a key that is greater than any key. Note
+// that the comparison is performed primarily on the user-key portion of the
+// key. If the user-keys compare equal, an additional test is made to sort
+// range tombstone sentinel keys before other keys with the same user-key. The
+// result is that 2 user-keys will compare equal if they differ purely on
+// their sequence number and value, but the range tombstone sentinel for that
+// user-key will compare not equal. This is necessary because the range
+// tombstone sentinel key is set as the largest key for an sstable even though
+// that key never appears in the database. We don't want adjacent sstables to
+// be considered overlapping if they are separated by the range tombstone
+// sentinel.
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
+ const InternalKey& b);
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
+ const InternalKey& b);
+int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
+ const InternalKey* b);
+
+// An AtomicCompactionUnitBoundary represents a range of keys [smallest,
+// largest] that exactly spans one ore more neighbouring SSTs on the same
+// level. Every pair of SSTs in this range "overlap" (i.e., the largest
+// user key of one file is the smallest user key of the next file). These
+// boundaries are propagated down to RangeDelAggregator during compaction
+// to provide safe truncation boundaries for range tombstones.
+struct AtomicCompactionUnitBoundary {
+ const InternalKey* smallest = nullptr;
+ const InternalKey* largest = nullptr;
+};
+
+// The structure that manages compaction input files associated
+// with the same physical level.
+struct CompactionInputFiles {
+ int level;
+ std::vector<FileMetaData*> files;
+ std::vector<AtomicCompactionUnitBoundary> atomic_compaction_unit_boundaries;
+ inline bool empty() const { return files.empty(); }
+ inline size_t size() const { return files.size(); }
+ inline void clear() { files.clear(); }
+ inline FileMetaData* operator[](size_t i) const { return files[i]; }
+};
+
+class Version;
+class ColumnFamilyData;
+class VersionStorageInfo;
+class CompactionFilter;
+
+// A Compaction encapsulates metadata about a compaction.
+class Compaction {
+ public:
+ Compaction(VersionStorageInfo* input_version,
+ const ImmutableCFOptions& immutable_cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ std::vector<CompactionInputFiles> inputs, int output_level,
+ uint64_t target_file_size, uint64_t max_compaction_bytes,
+ uint32_t output_path_id, CompressionType compression,
+ CompressionOptions compression_opts, uint32_t max_subcompactions,
+ std::vector<FileMetaData*> grandparents,
+ bool manual_compaction = false, double score = -1,
+ bool deletion_compaction = false,
+ CompactionReason compaction_reason = CompactionReason::kUnknown);
+
+ // No copying allowed
+ Compaction(const Compaction&) = delete;
+ void operator=(const Compaction&) = delete;
+
+ ~Compaction();
+
+ // Returns the level associated to the specified compaction input level.
+ // If compaction_input_level is not specified, then input_level is set to 0.
+ int level(size_t compaction_input_level = 0) const {
+ return inputs_[compaction_input_level].level;
+ }
+
+ int start_level() const { return start_level_; }
+
+ // Outputs will go to this level
+ int output_level() const { return output_level_; }
+
+ // Returns the number of input levels in this compaction.
+ size_t num_input_levels() const { return inputs_.size(); }
+
+ // Return the object that holds the edits to the descriptor done
+ // by this compaction.
+ VersionEdit* edit() { return &edit_; }
+
+ // Returns the number of input files associated to the specified
+ // compaction input level.
+ // The function will return 0 if when "compaction_input_level" < 0
+ // or "compaction_input_level" >= "num_input_levels()".
+ size_t num_input_files(size_t compaction_input_level) const {
+ if (compaction_input_level < inputs_.size()) {
+ return inputs_[compaction_input_level].size();
+ }
+ return 0;
+ }
+
+ // Returns input version of the compaction
+ Version* input_version() const { return input_version_; }
+
+ // Returns the ColumnFamilyData associated with the compaction.
+ ColumnFamilyData* column_family_data() const { return cfd_; }
+
+ // Returns the file meta data of the 'i'th input file at the
+ // specified compaction input level.
+ // REQUIREMENT: "compaction_input_level" must be >= 0 and
+ // < "input_levels()"
+ FileMetaData* input(size_t compaction_input_level, size_t i) const {
+ assert(compaction_input_level < inputs_.size());
+ return inputs_[compaction_input_level][i];
+ }
+
+ const std::vector<AtomicCompactionUnitBoundary>* boundaries(
+ size_t compaction_input_level) const {
+ assert(compaction_input_level < inputs_.size());
+ return &inputs_[compaction_input_level].atomic_compaction_unit_boundaries;
+ }
+
+ // Returns the list of file meta data of the specified compaction
+ // input level.
+ // REQUIREMENT: "compaction_input_level" must be >= 0 and
+ // < "input_levels()"
+ const std::vector<FileMetaData*>* inputs(
+ size_t compaction_input_level) const {
+ assert(compaction_input_level < inputs_.size());
+ return &inputs_[compaction_input_level].files;
+ }
+
+ const std::vector<CompactionInputFiles>* inputs() { return &inputs_; }
+
+ // Returns the LevelFilesBrief of the specified compaction input level.
+ const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
+ return &input_levels_[compaction_input_level];
+ }
+
+ // Maximum size of files to build during this compaction.
+ uint64_t max_output_file_size() const { return max_output_file_size_; }
+
+ // What compression for output
+ CompressionType output_compression() const { return output_compression_; }
+
+ // What compression options for output
+ CompressionOptions output_compression_opts() const {
+ return output_compression_opts_;
+ }
+
+ // Whether need to write output file to second DB path.
+ uint32_t output_path_id() const { return output_path_id_; }
+
+ // Is this a trivial compaction that can be implemented by just
+ // moving a single input file to the next level (no merging or splitting)
+ bool IsTrivialMove() const;
+
+ // If true, then the compaction can be done by simply deleting input files.
+ bool deletion_compaction() const { return deletion_compaction_; }
+
+ // Add all inputs to this compaction as delete operations to *edit.
+ void AddInputDeletions(VersionEdit* edit);
+
+ // Returns true if the available information we have guarantees that
+ // the input "user_key" does not exist in any level beyond "output_level()".
+ bool KeyNotExistsBeyondOutputLevel(const Slice& user_key,
+ std::vector<size_t>* level_ptrs) const;
+
+ // Clear all files to indicate that they are not being compacted
+ // Delete this compaction from the list of running compactions.
+ //
+ // Requirement: DB mutex held
+ void ReleaseCompactionFiles(Status status);
+
+ // Returns the summary of the compaction in "output" with maximum "len"
+ // in bytes. The caller is responsible for the memory management of
+ // "output".
+ void Summary(char* output, int len);
+
+ // Return the score that was used to pick this compaction run.
+ double score() const { return score_; }
+
+ // Is this compaction creating a file in the bottom most level?
+ bool bottommost_level() const { return bottommost_level_; }
+
+ // Does this compaction include all sst files?
+ bool is_full_compaction() const { return is_full_compaction_; }
+
+ // Was this compaction triggered manually by the client?
+ bool is_manual_compaction() const { return is_manual_compaction_; }
+
+ // Used when allow_trivial_move option is set in
+ // Universal compaction. If all the input files are
+ // non overlapping, then is_trivial_move_ variable
+ // will be set true, else false
+ void set_is_trivial_move(bool trivial_move) {
+ is_trivial_move_ = trivial_move;
+ }
+
+ // Used when allow_trivial_move option is set in
+ // Universal compaction. Returns true, if the input files
+ // are non-overlapping and can be trivially moved.
+ bool is_trivial_move() const { return is_trivial_move_; }
+
+ // How many total levels are there?
+ int number_levels() const { return number_levels_; }
+
+ // Return the ImmutableCFOptions that should be used throughout the compaction
+ // procedure
+ const ImmutableCFOptions* immutable_cf_options() const {
+ return &immutable_cf_options_;
+ }
+
+ // Return the MutableCFOptions that should be used throughout the compaction
+ // procedure
+ const MutableCFOptions* mutable_cf_options() const {
+ return &mutable_cf_options_;
+ }
+
+ // Returns the size in bytes that the output file should be preallocated to.
+ // In level compaction, that is max_file_size_. In universal compaction, that
+ // is the sum of all input file sizes.
+ uint64_t OutputFilePreallocationSize() const;
+
+ void SetInputVersion(Version* input_version);
+
+ struct InputLevelSummaryBuffer {
+ char buffer[128];
+ };
+
+ const char* InputLevelSummary(InputLevelSummaryBuffer* scratch) const;
+
+ uint64_t CalculateTotalInputSize() const;
+
+ // In case of compaction error, reset the nextIndex that is used
+ // to pick up the next file to be compacted from files_by_size_
+ void ResetNextCompactionIndex();
+
+ // Create a CompactionFilter from compaction_filter_factory
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter() const;
+
+ // Is the input level corresponding to output_level_ empty?
+ bool IsOutputLevelEmpty() const;
+
+ // Should this compaction be broken up into smaller ones run in parallel?
+ bool ShouldFormSubcompactions() const;
+
+ // test function to validate the functionality of IsBottommostLevel()
+ // function -- determines if compaction with inputs and storage is bottommost
+ static bool TEST_IsBottommostLevel(
+ int output_level, VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs);
+
+ TablePropertiesCollection GetOutputTableProperties() const {
+ return output_table_properties_;
+ }
+
+ void SetOutputTableProperties(TablePropertiesCollection tp) {
+ output_table_properties_ = std::move(tp);
+ }
+
+ Slice GetSmallestUserKey() const { return smallest_user_key_; }
+
+ Slice GetLargestUserKey() const { return largest_user_key_; }
+
+ int GetInputBaseLevel() const;
+
+ CompactionReason compaction_reason() { return compaction_reason_; }
+
+ const std::vector<FileMetaData*>& grandparents() const {
+ return grandparents_;
+ }
+
+ uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
+
+ uint32_t max_subcompactions() const { return max_subcompactions_; }
+
+ uint64_t MinInputFileOldestAncesterTime() const;
+
+ private:
+ // mark (or clear) all files that are being compacted
+ void MarkFilesBeingCompacted(bool mark_as_compacted);
+
+ // get the smallest and largest key present in files to be compacted
+ static void GetBoundaryKeys(VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs,
+ Slice* smallest_key, Slice* largest_key);
+
+ // Get the atomic file boundaries for all files in the compaction. Necessary
+ // in order to avoid the scenario described in
+ // https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and plumb
+ // down appropriate key boundaries to RangeDelAggregator during compaction.
+ static std::vector<CompactionInputFiles> PopulateWithAtomicBoundaries(
+ VersionStorageInfo* vstorage, std::vector<CompactionInputFiles> inputs);
+
+ // helper function to determine if compaction with inputs and storage is
+ // bottommost
+ static bool IsBottommostLevel(
+ int output_level, VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs);
+
+ static bool IsFullCompaction(VersionStorageInfo* vstorage,
+ const std::vector<CompactionInputFiles>& inputs);
+
+ VersionStorageInfo* input_vstorage_;
+
+ const int start_level_; // the lowest level to be compacted
+ const int output_level_; // levels to which output files are stored
+ uint64_t max_output_file_size_;
+ uint64_t max_compaction_bytes_;
+ uint32_t max_subcompactions_;
+ const ImmutableCFOptions immutable_cf_options_;
+ const MutableCFOptions mutable_cf_options_;
+ Version* input_version_;
+ VersionEdit edit_;
+ const int number_levels_;
+ ColumnFamilyData* cfd_;
+ Arena arena_; // Arena used to allocate space for file_levels_
+
+ const uint32_t output_path_id_;
+ CompressionType output_compression_;
+ CompressionOptions output_compression_opts_;
+ // If true, then the comaction can be done by simply deleting input files.
+ const bool deletion_compaction_;
+
+ // Compaction input files organized by level. Constant after construction
+ const std::vector<CompactionInputFiles> inputs_;
+
+ // A copy of inputs_, organized more closely in memory
+ autovector<LevelFilesBrief, 2> input_levels_;
+
+ // State used to check for number of overlapping grandparent files
+ // (grandparent == "output_level_ + 1")
+ std::vector<FileMetaData*> grandparents_;
+ const double score_; // score that was used to pick this compaction.
+
+ // Is this compaction creating a file in the bottom most level?
+ const bool bottommost_level_;
+ // Does this compaction include all sst files?
+ const bool is_full_compaction_;
+
+ // Is this compaction requested by the client?
+ const bool is_manual_compaction_;
+
+ // True if we can do trivial move in Universal multi level
+ // compaction
+ bool is_trivial_move_;
+
+ // Does input compression match the output compression?
+ bool InputCompressionMatchesOutput() const;
+
+ // table properties of output files
+ TablePropertiesCollection output_table_properties_;
+
+ // smallest user keys in compaction
+ Slice smallest_user_key_;
+
+ // largest user keys in compaction
+ Slice largest_user_key_;
+
+ // Reason for compaction
+ CompactionReason compaction_reason_;
+};
+
+// Return sum of sizes of all files in `files`.
+extern uint64_t TotalFileSize(const std::vector<FileMetaData*>& files);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_iteration_stats.h b/storage/rocksdb/rocksdb/db/compaction/compaction_iteration_stats.h
new file mode 100644
index 00000000..963c1d8e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_iteration_stats.h
@@ -0,0 +1,37 @@
+// 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).
+
+#pragma once
+
+#include "rocksdb/rocksdb_namespace.h"
+
+struct CompactionIterationStats {
+ // Compaction statistics
+
+ // Doesn't include records skipped because of
+ // CompactionFilter::Decision::kRemoveAndSkipUntil.
+ int64_t num_record_drop_user = 0;
+
+ int64_t num_record_drop_hidden = 0;
+ int64_t num_record_drop_obsolete = 0;
+ int64_t num_record_drop_range_del = 0;
+ int64_t num_range_del_drop_obsolete = 0;
+ // Deletions obsoleted before bottom level due to file gap optimization.
+ int64_t num_optimized_del_drop_obsolete = 0;
+ uint64_t total_filter_time = 0;
+
+ // Input statistics
+ // TODO(noetzli): The stats are incomplete. They are lacking everything
+ // consumed by MergeHelper.
+ uint64_t num_input_records = 0;
+ uint64_t num_input_deletion_records = 0;
+ uint64_t num_input_corrupt_records = 0;
+ uint64_t total_input_raw_key_bytes = 0;
+ uint64_t total_input_raw_value_bytes = 0;
+
+ // Single-Delete diagnostics for exceptional situations
+ uint64_t num_single_del_fallthru = 0;
+ uint64_t num_single_del_mismatch = 0;
+};
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.cc
new file mode 100644
index 00000000..1bebfc71
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.cc
@@ -0,0 +1,774 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+
+#include "db/compaction/compaction_iterator.h"
+#include "db/snapshot_checker.h"
+#include "port/likely.h"
+#include "rocksdb/listener.h"
+#include "table/internal_iterator.h"
+#include "test_util/sync_point.h"
+
+#define DEFINITELY_IN_SNAPSHOT(seq, snapshot) \
+ ((seq) <= (snapshot) && \
+ (snapshot_checker_ == nullptr || \
+ LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
+ SnapshotCheckerResult::kInSnapshot)))
+
+#define DEFINITELY_NOT_IN_SNAPSHOT(seq, snapshot) \
+ ((seq) > (snapshot) || \
+ (snapshot_checker_ != nullptr && \
+ UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
+ SnapshotCheckerResult::kNotInSnapshot)))
+
+#define IN_EARLIEST_SNAPSHOT(seq) \
+ ((seq) <= earliest_snapshot_ && \
+ (snapshot_checker_ == nullptr || LIKELY(IsInEarliestSnapshot(seq))))
+
+namespace ROCKSDB_NAMESPACE {
+
+CompactionIterator::CompactionIterator(
+ InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
+ SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker, Env* env,
+ bool report_detailed_time, bool expect_valid_internal_key,
+ CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction,
+ const CompactionFilter* compaction_filter,
+ const std::atomic<bool>* shutting_down,
+ const SequenceNumber preserve_deletes_seqnum,
+ const std::atomic<bool>* manual_compaction_paused,
+ const std::shared_ptr<Logger> info_log)
+ : CompactionIterator(
+ input, cmp, merge_helper, last_sequence, snapshots,
+ earliest_write_conflict_snapshot, snapshot_checker, env,
+ report_detailed_time, expect_valid_internal_key, range_del_agg,
+ std::unique_ptr<CompactionProxy>(
+ compaction ? new CompactionProxy(compaction) : nullptr),
+ compaction_filter, shutting_down, preserve_deletes_seqnum,
+ manual_compaction_paused, info_log) {}
+
+CompactionIterator::CompactionIterator(
+ InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
+ SequenceNumber /*last_sequence*/, std::vector<SequenceNumber>* snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker, Env* env,
+ bool report_detailed_time, bool expect_valid_internal_key,
+ CompactionRangeDelAggregator* range_del_agg,
+ std::unique_ptr<CompactionProxy> compaction,
+ const CompactionFilter* compaction_filter,
+ const std::atomic<bool>* shutting_down,
+ const SequenceNumber preserve_deletes_seqnum,
+ const std::atomic<bool>* manual_compaction_paused,
+ const std::shared_ptr<Logger> info_log)
+ : input_(input),
+ cmp_(cmp),
+ merge_helper_(merge_helper),
+ snapshots_(snapshots),
+ earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
+ snapshot_checker_(snapshot_checker),
+ env_(env),
+ report_detailed_time_(report_detailed_time),
+ expect_valid_internal_key_(expect_valid_internal_key),
+ range_del_agg_(range_del_agg),
+ compaction_(std::move(compaction)),
+ compaction_filter_(compaction_filter),
+ shutting_down_(shutting_down),
+ manual_compaction_paused_(manual_compaction_paused),
+ preserve_deletes_seqnum_(preserve_deletes_seqnum),
+ current_user_key_sequence_(0),
+ current_user_key_snapshot_(0),
+ merge_out_iter_(merge_helper_),
+ current_key_committed_(false),
+ info_log_(info_log) {
+ assert(compaction_filter_ == nullptr || compaction_ != nullptr);
+ assert(snapshots_ != nullptr);
+ bottommost_level_ =
+ compaction_ == nullptr ? false : compaction_->bottommost_level();
+ if (compaction_ != nullptr) {
+ level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
+ }
+ if (snapshots_->size() == 0) {
+ // optimize for fast path if there are no snapshots
+ visible_at_tip_ = true;
+ earliest_snapshot_iter_ = snapshots_->end();
+ earliest_snapshot_ = kMaxSequenceNumber;
+ latest_snapshot_ = 0;
+ } else {
+ visible_at_tip_ = false;
+ earliest_snapshot_iter_ = snapshots_->begin();
+ earliest_snapshot_ = snapshots_->at(0);
+ latest_snapshot_ = snapshots_->back();
+ }
+#ifndef NDEBUG
+ // findEarliestVisibleSnapshot assumes this ordering.
+ for (size_t i = 1; i < snapshots_->size(); ++i) {
+ assert(snapshots_->at(i - 1) < snapshots_->at(i));
+ }
+#endif
+ input_->SetPinnedItersMgr(&pinned_iters_mgr_);
+ TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
+}
+
+CompactionIterator::~CompactionIterator() {
+ // input_ Iteartor lifetime is longer than pinned_iters_mgr_ lifetime
+ input_->SetPinnedItersMgr(nullptr);
+}
+
+void CompactionIterator::ResetRecordCounts() {
+ iter_stats_.num_record_drop_user = 0;
+ iter_stats_.num_record_drop_hidden = 0;
+ iter_stats_.num_record_drop_obsolete = 0;
+ iter_stats_.num_record_drop_range_del = 0;
+ iter_stats_.num_range_del_drop_obsolete = 0;
+ iter_stats_.num_optimized_del_drop_obsolete = 0;
+}
+
+void CompactionIterator::SeekToFirst() {
+ NextFromInput();
+ PrepareOutput();
+}
+
+void CompactionIterator::Next() {
+ // If there is a merge output, return it before continuing to process the
+ // input.
+ if (merge_out_iter_.Valid()) {
+ merge_out_iter_.Next();
+
+ // Check if we returned all records of the merge output.
+ if (merge_out_iter_.Valid()) {
+ key_ = merge_out_iter_.key();
+ value_ = merge_out_iter_.value();
+ bool valid_key __attribute__((__unused__));
+ valid_key = ParseInternalKey(key_, &ikey_);
+ // MergeUntil stops when it encounters a corrupt key and does not
+ // include them in the result, so we expect the keys here to be valid.
+ assert(valid_key);
+ if (!valid_key) {
+ ROCKS_LOG_FATAL(info_log_, "Invalid key (%s) in compaction",
+ key_.ToString(true).c_str());
+ }
+
+ // Keep current_key_ in sync.
+ current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
+ key_ = current_key_.GetInternalKey();
+ ikey_.user_key = current_key_.GetUserKey();
+ valid_ = true;
+ } else {
+ // We consumed all pinned merge operands, release pinned iterators
+ pinned_iters_mgr_.ReleasePinnedData();
+ // MergeHelper moves the iterator to the first record after the merged
+ // records, so even though we reached the end of the merge output, we do
+ // not want to advance the iterator.
+ NextFromInput();
+ }
+ } else {
+ // Only advance the input iterator if there is no merge output and the
+ // iterator is not already at the next record.
+ if (!at_next_) {
+ input_->Next();
+ }
+ NextFromInput();
+ }
+
+ if (valid_) {
+ // Record that we've outputted a record for the current key.
+ has_outputted_key_ = true;
+ }
+
+ PrepareOutput();
+}
+
+void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
+ Slice* skip_until) {
+ if (compaction_filter_ != nullptr &&
+ (ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex)) {
+ // If the user has specified a compaction filter and the sequence
+ // number is greater than any external snapshot, then invoke the
+ // filter. If the return value of the compaction filter is true,
+ // replace the entry with a deletion marker.
+ CompactionFilter::Decision filter;
+ compaction_filter_value_.clear();
+ compaction_filter_skip_until_.Clear();
+ CompactionFilter::ValueType value_type =
+ ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
+ : CompactionFilter::ValueType::kBlobIndex;
+ // Hack: pass internal key to BlobIndexCompactionFilter since it needs
+ // to get sequence number.
+ Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_;
+ {
+ StopWatchNano timer(env_, report_detailed_time_);
+ filter = compaction_filter_->FilterV2(
+ compaction_->level(), filter_key, value_type, value_,
+ &compaction_filter_value_, compaction_filter_skip_until_.rep());
+ iter_stats_.total_filter_time +=
+ env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
+ }
+
+ if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
+ cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
+ 0) {
+ // Can't skip to a key smaller than the current one.
+ // Keep the key as per FilterV2 documentation.
+ filter = CompactionFilter::Decision::kKeep;
+ }
+
+ if (filter == CompactionFilter::Decision::kRemove) {
+ // convert the current key to a delete; key_ is pointing into
+ // current_key_ at this point, so updating current_key_ updates key()
+ ikey_.type = kTypeDeletion;
+ current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
+ // no value associated with delete
+ value_.clear();
+ iter_stats_.num_record_drop_user++;
+ } else if (filter == CompactionFilter::Decision::kChangeValue) {
+ value_ = compaction_filter_value_;
+ } else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
+ *need_skip = true;
+ compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
+ kValueTypeForSeek);
+ *skip_until = compaction_filter_skip_until_.Encode();
+ }
+ }
+}
+
+void CompactionIterator::NextFromInput() {
+ at_next_ = false;
+ valid_ = false;
+
+ while (!valid_ && input_->Valid() && !IsPausingManualCompaction() &&
+ !IsShuttingDown()) {
+ key_ = input_->key();
+ value_ = input_->value();
+ iter_stats_.num_input_records++;
+
+ if (!ParseInternalKey(key_, &ikey_)) {
+ // If `expect_valid_internal_key_` is false, return the corrupted key
+ // and let the caller decide what to do with it.
+ // TODO(noetzli): We should have a more elegant solution for this.
+ if (expect_valid_internal_key_) {
+ assert(!"Corrupted internal key not expected.");
+ status_ = Status::Corruption("Corrupted internal key not expected.");
+ break;
+ }
+ key_ = current_key_.SetInternalKey(key_);
+ has_current_user_key_ = false;
+ current_user_key_sequence_ = kMaxSequenceNumber;
+ current_user_key_snapshot_ = 0;
+ iter_stats_.num_input_corrupt_records++;
+ valid_ = true;
+ break;
+ }
+ TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
+
+ // Update input statistics
+ if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
+ iter_stats_.num_input_deletion_records++;
+ }
+ iter_stats_.total_input_raw_key_bytes += key_.size();
+ iter_stats_.total_input_raw_value_bytes += value_.size();
+
+ // If need_skip is true, we should seek the input iterator
+ // to internal key skip_until and continue from there.
+ bool need_skip = false;
+ // Points either into compaction_filter_skip_until_ or into
+ // merge_helper_->compaction_filter_skip_until_.
+ Slice skip_until;
+
+ // Check whether the user key changed. After this if statement current_key_
+ // is a copy of the current input key (maybe converted to a delete by the
+ // compaction filter). ikey_.user_key is pointing to the copy.
+ if (!has_current_user_key_ ||
+ !cmp_->Equal(ikey_.user_key, current_user_key_)) {
+ // First occurrence of this user key
+ // Copy key for output
+ key_ = current_key_.SetInternalKey(key_, &ikey_);
+ current_user_key_ = ikey_.user_key;
+ has_current_user_key_ = true;
+ has_outputted_key_ = false;
+ current_user_key_sequence_ = kMaxSequenceNumber;
+ current_user_key_snapshot_ = 0;
+ current_key_committed_ = KeyCommitted(ikey_.sequence);
+
+ // Apply the compaction filter to the first committed version of the user
+ // key.
+ if (current_key_committed_) {
+ InvokeFilterIfNeeded(&need_skip, &skip_until);
+ }
+ } else {
+ // Update the current key to reflect the new sequence number/type without
+ // copying the user key.
+ // TODO(rven): Compaction filter does not process keys in this path
+ // Need to have the compaction filter process multiple versions
+ // if we have versions on both sides of a snapshot
+ current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
+ key_ = current_key_.GetInternalKey();
+ ikey_.user_key = current_key_.GetUserKey();
+
+ // Note that newer version of a key is ordered before older versions. If a
+ // newer version of a key is committed, so as the older version. No need
+ // to query snapshot_checker_ in that case.
+ if (UNLIKELY(!current_key_committed_)) {
+ assert(snapshot_checker_ != nullptr);
+ current_key_committed_ = KeyCommitted(ikey_.sequence);
+ // Apply the compaction filter to the first committed version of the
+ // user key.
+ if (current_key_committed_) {
+ InvokeFilterIfNeeded(&need_skip, &skip_until);
+ }
+ }
+ }
+
+ if (UNLIKELY(!current_key_committed_)) {
+ assert(snapshot_checker_ != nullptr);
+ valid_ = true;
+ break;
+ }
+
+ // If there are no snapshots, then this kv affect visibility at tip.
+ // Otherwise, search though all existing snapshots to find the earliest
+ // snapshot that is affected by this kv.
+ SequenceNumber last_sequence __attribute__((__unused__));
+ last_sequence = current_user_key_sequence_;
+ current_user_key_sequence_ = ikey_.sequence;
+ SequenceNumber last_snapshot = current_user_key_snapshot_;
+ SequenceNumber prev_snapshot = 0; // 0 means no previous snapshot
+ current_user_key_snapshot_ =
+ visible_at_tip_
+ ? earliest_snapshot_
+ : findEarliestVisibleSnapshot(ikey_.sequence, &prev_snapshot);
+
+ if (need_skip) {
+ // This case is handled below.
+ } else if (clear_and_output_next_key_) {
+ // In the previous iteration we encountered a single delete that we could
+ // not compact out. We will keep this Put, but can drop it's data.
+ // (See Optimization 3, below.)
+ assert(ikey_.type == kTypeValue);
+ if (ikey_.type != kTypeValue) {
+ ROCKS_LOG_FATAL(info_log_,
+ "Unexpected key type %d for compaction output",
+ ikey_.type);
+ }
+ assert(current_user_key_snapshot_ == last_snapshot);
+ if (current_user_key_snapshot_ != last_snapshot) {
+ ROCKS_LOG_FATAL(info_log_,
+ "current_user_key_snapshot_ (%" PRIu64
+ ") != last_snapshot (%" PRIu64 ")",
+ current_user_key_snapshot_, last_snapshot);
+ }
+
+ value_.clear();
+ valid_ = true;
+ clear_and_output_next_key_ = false;
+ } else if (ikey_.type == kTypeSingleDeletion) {
+ // We can compact out a SingleDelete if:
+ // 1) We encounter the corresponding PUT -OR- we know that this key
+ // doesn't appear past this output level
+ // =AND=
+ // 2) We've already returned a record in this snapshot -OR-
+ // there are no earlier earliest_write_conflict_snapshot.
+ //
+ // Rule 1 is needed for SingleDelete correctness. Rule 2 is needed to
+ // allow Transactions to do write-conflict checking (if we compacted away
+ // all keys, then we wouldn't know that a write happened in this
+ // snapshot). If there is no earlier snapshot, then we know that there
+ // are no active transactions that need to know about any writes.
+ //
+ // Optimization 3:
+ // If we encounter a SingleDelete followed by a PUT and Rule 2 is NOT
+ // true, then we must output a SingleDelete. In this case, we will decide
+ // to also output the PUT. While we are compacting less by outputting the
+ // PUT now, hopefully this will lead to better compaction in the future
+ // when Rule 2 is later true (Ie, We are hoping we can later compact out
+ // both the SingleDelete and the Put, while we couldn't if we only
+ // outputted the SingleDelete now).
+ // In this case, we can save space by removing the PUT's value as it will
+ // never be read.
+ //
+ // Deletes and Merges are not supported on the same key that has a
+ // SingleDelete as it is not possible to correctly do any partial
+ // compaction of such a combination of operations. The result of mixing
+ // those operations for a given key is documented as being undefined. So
+ // we can choose how to handle such a combinations of operations. We will
+ // try to compact out as much as we can in these cases.
+ // We will report counts on these anomalous cases.
+
+ // The easiest way to process a SingleDelete during iteration is to peek
+ // ahead at the next key.
+ ParsedInternalKey next_ikey;
+ input_->Next();
+
+ // Check whether the next key exists, is not corrupt, and is the same key
+ // as the single delete.
+ if (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) &&
+ cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
+ // Check whether the next key belongs to the same snapshot as the
+ // SingleDelete.
+ if (prev_snapshot == 0 ||
+ DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot)) {
+ if (next_ikey.type == kTypeSingleDeletion) {
+ // We encountered two SingleDeletes in a row. This could be due to
+ // unexpected user input.
+ // Skip the first SingleDelete and let the next iteration decide how
+ // to handle the second SingleDelete
+
+ // First SingleDelete has been skipped since we already called
+ // input_->Next().
+ ++iter_stats_.num_record_drop_obsolete;
+ ++iter_stats_.num_single_del_mismatch;
+ } else if (has_outputted_key_ ||
+ DEFINITELY_IN_SNAPSHOT(
+ ikey_.sequence, earliest_write_conflict_snapshot_)) {
+ // Found a matching value, we can drop the single delete and the
+ // value. It is safe to drop both records since we've already
+ // outputted a key in this snapshot, or there is no earlier
+ // snapshot (Rule 2 above).
+
+ // Note: it doesn't matter whether the second key is a Put or if it
+ // is an unexpected Merge or Delete. We will compact it out
+ // either way. We will maintain counts of how many mismatches
+ // happened
+ if (next_ikey.type != kTypeValue &&
+ next_ikey.type != kTypeBlobIndex) {
+ ++iter_stats_.num_single_del_mismatch;
+ }
+
+ ++iter_stats_.num_record_drop_hidden;
+ ++iter_stats_.num_record_drop_obsolete;
+ // Already called input_->Next() once. Call it a second time to
+ // skip past the second key.
+ input_->Next();
+ } else {
+ // Found a matching value, but we cannot drop both keys since
+ // there is an earlier snapshot and we need to leave behind a record
+ // to know that a write happened in this snapshot (Rule 2 above).
+ // Clear the value and output the SingleDelete. (The value will be
+ // outputted on the next iteration.)
+
+ // Setting valid_ to true will output the current SingleDelete
+ valid_ = true;
+
+ // Set up the Put to be outputted in the next iteration.
+ // (Optimization 3).
+ clear_and_output_next_key_ = true;
+ }
+ } else {
+ // We hit the next snapshot without hitting a put, so the iterator
+ // returns the single delete.
+ valid_ = true;
+ }
+ } else {
+ // We are at the end of the input, could not parse the next key, or hit
+ // a different key. The iterator returns the single delete if the key
+ // possibly exists beyond the current output level. We set
+ // has_current_user_key to false so that if the iterator is at the next
+ // key, we do not compare it again against the previous key at the next
+ // iteration. If the next key is corrupt, we return before the
+ // comparison, so the value of has_current_user_key does not matter.
+ has_current_user_key_ = false;
+ if (compaction_ != nullptr && IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
+ compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
+ &level_ptrs_)) {
+ // Key doesn't exist outside of this range.
+ // Can compact out this SingleDelete.
+ ++iter_stats_.num_record_drop_obsolete;
+ ++iter_stats_.num_single_del_fallthru;
+ if (!bottommost_level_) {
+ ++iter_stats_.num_optimized_del_drop_obsolete;
+ }
+ } else {
+ // Output SingleDelete
+ valid_ = true;
+ }
+ }
+
+ if (valid_) {
+ at_next_ = true;
+ }
+ } else if (last_snapshot == current_user_key_snapshot_ ||
+ (last_snapshot > 0 &&
+ last_snapshot < current_user_key_snapshot_)) {
+ // If the earliest snapshot is which this key is visible in
+ // is the same as the visibility of a previous instance of the
+ // same key, then this kv is not visible in any snapshot.
+ // Hidden by an newer entry for same user key
+ //
+ // Note: Dropping this key will not affect TransactionDB write-conflict
+ // checking since there has already been a record returned for this key
+ // in this snapshot.
+ assert(last_sequence >= current_user_key_sequence_);
+ if (last_sequence < current_user_key_sequence_) {
+ ROCKS_LOG_FATAL(info_log_,
+ "last_sequence (%" PRIu64
+ ") < current_user_key_sequence_ (%" PRIu64 ")",
+ last_sequence, current_user_key_sequence_);
+ }
+
+ ++iter_stats_.num_record_drop_hidden; // (A)
+ input_->Next();
+ } else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
+ IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
+ ikeyNotNeededForIncrementalSnapshot() &&
+ compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
+ &level_ptrs_)) {
+ // TODO(noetzli): This is the only place where we use compaction_
+ // (besides the constructor). We should probably get rid of this
+ // dependency and find a way to do similar filtering during flushes.
+ //
+ // For this user key:
+ // (1) there is no data in higher levels
+ // (2) data in lower levels will have larger sequence numbers
+ // (3) data in layers that are being compacted here and have
+ // smaller sequence numbers will be dropped in the next
+ // few iterations of this loop (by rule (A) above).
+ // Therefore this deletion marker is obsolete and can be dropped.
+ //
+ // Note: Dropping this Delete will not affect TransactionDB
+ // write-conflict checking since it is earlier than any snapshot.
+ //
+ // It seems that we can also drop deletion later than earliest snapshot
+ // given that:
+ // (1) The deletion is earlier than earliest_write_conflict_snapshot, and
+ // (2) No value exist earlier than the deletion.
+ ++iter_stats_.num_record_drop_obsolete;
+ if (!bottommost_level_) {
+ ++iter_stats_.num_optimized_del_drop_obsolete;
+ }
+ input_->Next();
+ } else if ((ikey_.type == kTypeDeletion) && bottommost_level_ &&
+ ikeyNotNeededForIncrementalSnapshot()) {
+ // Handle the case where we have a delete key at the bottom most level
+ // We can skip outputting the key iff there are no subsequent puts for this
+ // key
+ ParsedInternalKey next_ikey;
+ input_->Next();
+ // Skip over all versions of this key that happen to occur in the same snapshot
+ // range as the delete
+ while (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) &&
+ cmp_->Equal(ikey_.user_key, next_ikey.user_key) &&
+ (prev_snapshot == 0 ||
+ DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) {
+ input_->Next();
+ }
+ // If you find you still need to output a row with this key, we need to output the
+ // delete too
+ if (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) &&
+ cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
+ valid_ = true;
+ at_next_ = true;
+ }
+ } else if (ikey_.type == kTypeMerge) {
+ if (!merge_helper_->HasOperator()) {
+ status_ = Status::InvalidArgument(
+ "merge_operator is not properly initialized.");
+ return;
+ }
+
+ pinned_iters_mgr_.StartPinning();
+ // We know the merge type entry is not hidden, otherwise we would
+ // have hit (A)
+ // We encapsulate the merge related state machine in a different
+ // object to minimize change to the existing flow.
+ Status s = merge_helper_->MergeUntil(input_, range_del_agg_,
+ prev_snapshot, bottommost_level_);
+ merge_out_iter_.SeekToFirst();
+
+ if (!s.ok() && !s.IsMergeInProgress()) {
+ status_ = s;
+ return;
+ } else if (merge_out_iter_.Valid()) {
+ // NOTE: key, value, and ikey_ refer to old entries.
+ // These will be correctly set below.
+ key_ = merge_out_iter_.key();
+ value_ = merge_out_iter_.value();
+ bool valid_key __attribute__((__unused__));
+ valid_key = ParseInternalKey(key_, &ikey_);
+ // MergeUntil stops when it encounters a corrupt key and does not
+ // include them in the result, so we expect the keys here to valid.
+ assert(valid_key);
+ if (!valid_key) {
+ ROCKS_LOG_FATAL(info_log_, "Invalid key (%s) in compaction",
+ key_.ToString(true).c_str());
+ }
+ // Keep current_key_ in sync.
+ current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
+ key_ = current_key_.GetInternalKey();
+ ikey_.user_key = current_key_.GetUserKey();
+ valid_ = true;
+ } else {
+ // all merge operands were filtered out. reset the user key, since the
+ // batch consumed by the merge operator should not shadow any keys
+ // coming after the merges
+ has_current_user_key_ = false;
+ pinned_iters_mgr_.ReleasePinnedData();
+
+ if (merge_helper_->FilteredUntil(&skip_until)) {
+ need_skip = true;
+ }
+ }
+ } else {
+ // 1. new user key -OR-
+ // 2. different snapshot stripe
+ bool should_delete = range_del_agg_->ShouldDelete(
+ key_, RangeDelPositioningMode::kForwardTraversal);
+ if (should_delete) {
+ ++iter_stats_.num_record_drop_hidden;
+ ++iter_stats_.num_record_drop_range_del;
+ input_->Next();
+ } else {
+ valid_ = true;
+ }
+ }
+
+ if (need_skip) {
+ input_->Seek(skip_until);
+ }
+ }
+
+ if (!valid_ && IsShuttingDown()) {
+ status_ = Status::ShutdownInProgress();
+ }
+
+ if (IsPausingManualCompaction()) {
+ status_ = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
+ }
+}
+
+void CompactionIterator::PrepareOutput() {
+ if (valid_) {
+ if (compaction_filter_ && ikey_.type == kTypeBlobIndex) {
+ const auto blob_decision = compaction_filter_->PrepareBlobOutput(
+ user_key(), value_, &compaction_filter_value_);
+
+ if (blob_decision == CompactionFilter::BlobDecision::kCorruption) {
+ status_ = Status::Corruption(
+ "Corrupted blob reference encountered during GC");
+ valid_ = false;
+ } else if (blob_decision == CompactionFilter::BlobDecision::kIOError) {
+ status_ = Status::IOError("Could not relocate blob during GC");
+ valid_ = false;
+ } else if (blob_decision ==
+ CompactionFilter::BlobDecision::kChangeValue) {
+ value_ = compaction_filter_value_;
+ }
+ }
+
+ // Zeroing out the sequence number leads to better compression.
+ // If this is the bottommost level (no files in lower levels)
+ // and the earliest snapshot is larger than this seqno
+ // and the userkey differs from the last userkey in compaction
+ // then we can squash the seqno to zero.
+ //
+ // This is safe for TransactionDB write-conflict checking since transactions
+ // only care about sequence number larger than any active snapshots.
+ //
+ // Can we do the same for levels above bottom level as long as
+ // KeyNotExistsBeyondOutputLevel() return true?
+ if (valid_ && compaction_ != nullptr &&
+ !compaction_->allow_ingest_behind() &&
+ ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ &&
+ IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge) {
+ assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
+ if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
+ ROCKS_LOG_FATAL(info_log_,
+ "Unexpected key type %d for seq-zero optimization",
+ ikey_.type);
+ }
+ ikey_.sequence = 0;
+ current_key_.UpdateInternalKey(0, ikey_.type);
+ }
+ }
+}
+
+inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
+ SequenceNumber in, SequenceNumber* prev_snapshot) {
+ assert(snapshots_->size());
+ if (snapshots_->size() == 0) {
+ ROCKS_LOG_FATAL(info_log_,
+ "No snapshot left in findEarliestVisibleSnapshot");
+ }
+ auto snapshots_iter = std::lower_bound(
+ snapshots_->begin(), snapshots_->end(), in);
+ if (snapshots_iter == snapshots_->begin()) {
+ *prev_snapshot = 0;
+ } else {
+ *prev_snapshot = *std::prev(snapshots_iter);
+ assert(*prev_snapshot < in);
+ if (*prev_snapshot >= in) {
+ ROCKS_LOG_FATAL(info_log_,
+ "*prev_snapshot >= in in findEarliestVisibleSnapshot");
+ }
+ }
+ if (snapshot_checker_ == nullptr) {
+ return snapshots_iter != snapshots_->end()
+ ? *snapshots_iter : kMaxSequenceNumber;
+ }
+ bool has_released_snapshot = !released_snapshots_.empty();
+ for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) {
+ auto cur = *snapshots_iter;
+ assert(in <= cur);
+ if (in > cur) {
+ ROCKS_LOG_FATAL(info_log_, "in > cur in findEarliestVisibleSnapshot");
+ }
+ // Skip if cur is in released_snapshots.
+ if (has_released_snapshot && released_snapshots_.count(cur) > 0) {
+ continue;
+ }
+ auto res = snapshot_checker_->CheckInSnapshot(in, cur);
+ if (res == SnapshotCheckerResult::kInSnapshot) {
+ return cur;
+ } else if (res == SnapshotCheckerResult::kSnapshotReleased) {
+ released_snapshots_.insert(cur);
+ }
+ *prev_snapshot = cur;
+ }
+ return kMaxSequenceNumber;
+}
+
+// used in 2 places - prevents deletion markers to be dropped if they may be
+// needed and disables seqnum zero-out in PrepareOutput for recent keys.
+inline bool CompactionIterator::ikeyNotNeededForIncrementalSnapshot() {
+ return (!compaction_->preserve_deletes()) ||
+ (ikey_.sequence < preserve_deletes_seqnum_);
+}
+
+bool CompactionIterator::IsInEarliestSnapshot(SequenceNumber sequence) {
+ assert(snapshot_checker_ != nullptr);
+ bool pre_condition = (earliest_snapshot_ == kMaxSequenceNumber ||
+ (earliest_snapshot_iter_ != snapshots_->end() &&
+ *earliest_snapshot_iter_ == earliest_snapshot_));
+ assert(pre_condition);
+ if (!pre_condition) {
+ ROCKS_LOG_FATAL(info_log_,
+ "Pre-Condition is not hold in IsInEarliestSnapshot");
+ }
+ auto in_snapshot =
+ snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
+ while (UNLIKELY(in_snapshot == SnapshotCheckerResult::kSnapshotReleased)) {
+ // Avoid the the current earliest_snapshot_ being return as
+ // earliest visible snapshot for the next value. So if a value's sequence
+ // is zero-ed out by PrepareOutput(), the next value will be compact out.
+ released_snapshots_.insert(earliest_snapshot_);
+ earliest_snapshot_iter_++;
+
+ if (earliest_snapshot_iter_ == snapshots_->end()) {
+ earliest_snapshot_ = kMaxSequenceNumber;
+ } else {
+ earliest_snapshot_ = *earliest_snapshot_iter_;
+ }
+ in_snapshot =
+ snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
+ }
+ assert(in_snapshot != SnapshotCheckerResult::kSnapshotReleased);
+ if (in_snapshot == SnapshotCheckerResult::kSnapshotReleased) {
+ ROCKS_LOG_FATAL(info_log_,
+ "Unexpected released snapshot in IsInEarliestSnapshot");
+ }
+ return in_snapshot == SnapshotCheckerResult::kInSnapshot;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.h b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.h
new file mode 100644
index 00000000..8be60eb9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator.h
@@ -0,0 +1,240 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <deque>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/compaction/compaction.h"
+#include "db/compaction/compaction_iteration_stats.h"
+#include "db/merge_helper.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/range_del_aggregator.h"
+#include "db/snapshot_checker.h"
+#include "options/cf_options.h"
+#include "rocksdb/compaction_filter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class CompactionIterator {
+ public:
+ // A wrapper around Compaction. Has a much smaller interface, only what
+ // CompactionIterator uses. Tests can override it.
+ class CompactionProxy {
+ public:
+ explicit CompactionProxy(const Compaction* compaction)
+ : compaction_(compaction) {}
+
+ virtual ~CompactionProxy() = default;
+ virtual int level(size_t /*compaction_input_level*/ = 0) const {
+ return compaction_->level();
+ }
+ virtual bool KeyNotExistsBeyondOutputLevel(
+ const Slice& user_key, std::vector<size_t>* level_ptrs) const {
+ return compaction_->KeyNotExistsBeyondOutputLevel(user_key, level_ptrs);
+ }
+ virtual bool bottommost_level() const {
+ return compaction_->bottommost_level();
+ }
+ virtual int number_levels() const { return compaction_->number_levels(); }
+ virtual Slice GetLargestUserKey() const {
+ return compaction_->GetLargestUserKey();
+ }
+ virtual bool allow_ingest_behind() const {
+ return compaction_->immutable_cf_options()->allow_ingest_behind;
+ }
+ virtual bool preserve_deletes() const {
+ return compaction_->immutable_cf_options()->preserve_deletes;
+ }
+
+ protected:
+ CompactionProxy() = default;
+
+ private:
+ const Compaction* compaction_;
+ };
+
+ CompactionIterator(
+ InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
+ SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker, Env* env,
+ bool report_detailed_time, bool expect_valid_internal_key,
+ CompactionRangeDelAggregator* range_del_agg,
+ const Compaction* compaction = nullptr,
+ const CompactionFilter* compaction_filter = nullptr,
+ const std::atomic<bool>* shutting_down = nullptr,
+ const SequenceNumber preserve_deletes_seqnum = 0,
+ const std::atomic<bool>* manual_compaction_paused = nullptr,
+ const std::shared_ptr<Logger> info_log = nullptr);
+
+ // Constructor with custom CompactionProxy, used for tests.
+ CompactionIterator(
+ InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
+ SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker, Env* env,
+ bool report_detailed_time, bool expect_valid_internal_key,
+ CompactionRangeDelAggregator* range_del_agg,
+ std::unique_ptr<CompactionProxy> compaction,
+ const CompactionFilter* compaction_filter = nullptr,
+ const std::atomic<bool>* shutting_down = nullptr,
+ const SequenceNumber preserve_deletes_seqnum = 0,
+ const std::atomic<bool>* manual_compaction_paused = nullptr,
+ const std::shared_ptr<Logger> info_log = nullptr);
+
+ ~CompactionIterator();
+
+ void ResetRecordCounts();
+
+ // Seek to the beginning of the compaction iterator output.
+ //
+ // REQUIRED: Call only once.
+ void SeekToFirst();
+
+ // Produces the next record in the compaction.
+ //
+ // REQUIRED: SeekToFirst() has been called.
+ void Next();
+
+ // Getters
+ const Slice& key() const { return key_; }
+ const Slice& value() const { return value_; }
+ const Status& status() const { return status_; }
+ const ParsedInternalKey& ikey() const { return ikey_; }
+ bool Valid() const { return valid_; }
+ const Slice& user_key() const { return current_user_key_; }
+ const CompactionIterationStats& iter_stats() const { return iter_stats_; }
+
+ private:
+ // Processes the input stream to find the next output
+ void NextFromInput();
+
+ // Do last preparations before presenting the output to the callee. At this
+ // point this only zeroes out the sequence number if possible for better
+ // compression.
+ void PrepareOutput();
+
+ // Invoke compaction filter if needed.
+ void InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until);
+
+ // Given a sequence number, return the sequence number of the
+ // earliest snapshot that this sequence number is visible in.
+ // The snapshots themselves are arranged in ascending order of
+ // sequence numbers.
+ // Employ a sequential search because the total number of
+ // snapshots are typically small.
+ inline SequenceNumber findEarliestVisibleSnapshot(
+ SequenceNumber in, SequenceNumber* prev_snapshot);
+
+ // Checks whether the currently seen ikey_ is needed for
+ // incremental (differential) snapshot and hence can't be dropped
+ // or seqnum be zero-ed out even if all other conditions for it are met.
+ inline bool ikeyNotNeededForIncrementalSnapshot();
+
+ inline bool KeyCommitted(SequenceNumber sequence) {
+ return snapshot_checker_ == nullptr ||
+ snapshot_checker_->CheckInSnapshot(sequence, kMaxSequenceNumber) ==
+ SnapshotCheckerResult::kInSnapshot;
+ }
+
+ bool IsInEarliestSnapshot(SequenceNumber sequence);
+
+ InternalIterator* input_;
+ const Comparator* cmp_;
+ MergeHelper* merge_helper_;
+ const std::vector<SequenceNumber>* snapshots_;
+ // List of snapshots released during compaction.
+ // findEarliestVisibleSnapshot() find them out from return of
+ // snapshot_checker, and make sure they will not be returned as
+ // earliest visible snapshot of an older value.
+ // See WritePreparedTransactionTest::ReleaseSnapshotDuringCompaction3.
+ std::unordered_set<SequenceNumber> released_snapshots_;
+ std::vector<SequenceNumber>::const_iterator earliest_snapshot_iter_;
+ const SequenceNumber earliest_write_conflict_snapshot_;
+ const SnapshotChecker* const snapshot_checker_;
+ Env* env_;
+ bool report_detailed_time_;
+ bool expect_valid_internal_key_;
+ CompactionRangeDelAggregator* range_del_agg_;
+ std::unique_ptr<CompactionProxy> compaction_;
+ const CompactionFilter* compaction_filter_;
+ const std::atomic<bool>* shutting_down_;
+ const std::atomic<bool>* manual_compaction_paused_;
+ const SequenceNumber preserve_deletes_seqnum_;
+ bool bottommost_level_;
+ bool valid_ = false;
+ bool visible_at_tip_;
+ SequenceNumber earliest_snapshot_;
+ SequenceNumber latest_snapshot_;
+
+ // State
+ //
+ // Points to a copy of the current compaction iterator output (current_key_)
+ // if valid_.
+ Slice key_;
+ // Points to the value in the underlying iterator that corresponds to the
+ // current output.
+ Slice value_;
+ // The status is OK unless compaction iterator encounters a merge operand
+ // while not having a merge operator defined.
+ Status status_;
+ // Stores the user key, sequence number and type of the current compaction
+ // iterator output (or current key in the underlying iterator during
+ // NextFromInput()).
+ ParsedInternalKey ikey_;
+ // Stores whether ikey_.user_key is valid. If set to false, the user key is
+ // not compared against the current key in the underlying iterator.
+ bool has_current_user_key_ = false;
+ bool at_next_ = false; // If false, the iterator
+ // Holds a copy of the current compaction iterator output (or current key in
+ // the underlying iterator during NextFromInput()).
+ IterKey current_key_;
+ Slice current_user_key_;
+ SequenceNumber current_user_key_sequence_;
+ SequenceNumber current_user_key_snapshot_;
+
+ // True if the iterator has already returned a record for the current key.
+ bool has_outputted_key_ = false;
+
+ // truncated the value of the next key and output it without applying any
+ // compaction rules. This is used for outputting a put after a single delete.
+ bool clear_and_output_next_key_ = false;
+
+ MergeOutputIterator merge_out_iter_;
+ // PinnedIteratorsManager used to pin input_ Iterator blocks while reading
+ // merge operands and then releasing them after consuming them.
+ PinnedIteratorsManager pinned_iters_mgr_;
+ std::string compaction_filter_value_;
+ InternalKey compaction_filter_skip_until_;
+ // "level_ptrs" holds indices that remember which file of an associated
+ // level we were last checking during the last call to compaction->
+ // KeyNotExistsBeyondOutputLevel(). This allows future calls to the function
+ // to pick off where it left off since each subcompaction's key range is
+ // increasing so a later call to the function must be looking for a key that
+ // is in or beyond the last file checked during the previous call
+ std::vector<size_t> level_ptrs_;
+ CompactionIterationStats iter_stats_;
+
+ // Used to avoid purging uncommitted values. The application can specify
+ // uncommitted values by providing a SnapshotChecker object.
+ bool current_key_committed_;
+ std::shared_ptr<Logger> info_log_;
+
+ bool IsShuttingDown() {
+ // This is a best-effort facility, so memory_order_relaxed is sufficient.
+ return shutting_down_ && shutting_down_->load(std::memory_order_relaxed);
+ }
+
+ bool IsPausingManualCompaction() {
+ // This is a best-effort facility, so memory_order_relaxed is sufficient.
+ return manual_compaction_paused_ &&
+ manual_compaction_paused_->load(std::memory_order_relaxed);
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc
new file mode 100644
index 00000000..0c50fb9b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc
@@ -0,0 +1,976 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "db/compaction/compaction_iterator.h"
+#include "port/port.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Expects no merging attempts.
+class NoMergingMergeOp : public MergeOperator {
+ public:
+ bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
+ MergeOperationOutput* /*merge_out*/) const override {
+ ADD_FAILURE();
+ return false;
+ }
+ bool PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& /*operand_list*/,
+ std::string* /*new_value*/,
+ Logger* /*logger*/) const override {
+ ADD_FAILURE();
+ return false;
+ }
+ const char* Name() const override {
+ return "CompactionIteratorTest NoMergingMergeOp";
+ }
+};
+
+// Compaction filter that gets stuck when it sees a particular key,
+// then gets unstuck when told to.
+// Always returns Decition::kRemove.
+class StallingFilter : public CompactionFilter {
+ public:
+ Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/,
+ const Slice& /*existing_value*/, std::string* /*new_value*/,
+ std::string* /*skip_until*/) const override {
+ int k = std::atoi(key.ToString().c_str());
+ last_seen.store(k);
+ while (k >= stall_at.load()) {
+ std::this_thread::yield();
+ }
+ return Decision::kRemove;
+ }
+
+ const char* Name() const override {
+ return "CompactionIteratorTest StallingFilter";
+ }
+
+ // Wait until the filter sees a key >= k and stalls at that key.
+ // If `exact`, asserts that the seen key is equal to k.
+ void WaitForStall(int k, bool exact = true) {
+ stall_at.store(k);
+ while (last_seen.load() < k) {
+ std::this_thread::yield();
+ }
+ if (exact) {
+ EXPECT_EQ(k, last_seen.load());
+ }
+ }
+
+ // Filter will stall on key >= stall_at. Advance stall_at to unstall.
+ mutable std::atomic<int> stall_at{0};
+ // Last key the filter was called with.
+ mutable std::atomic<int> last_seen{0};
+};
+
+// Compaction filter that filter out all keys.
+class FilterAllKeysCompactionFilter : public CompactionFilter {
+ public:
+ Decision FilterV2(int /*level*/, const Slice& /*key*/, ValueType /*type*/,
+ const Slice& /*existing_value*/, std::string* /*new_value*/,
+ std::string* /*skip_until*/) const override {
+ return Decision::kRemove;
+ }
+
+ const char* Name() const override { return "AllKeysCompactionFilter"; }
+};
+
+class LoggingForwardVectorIterator : public InternalIterator {
+ public:
+ struct Action {
+ enum class Type {
+ SEEK_TO_FIRST,
+ SEEK,
+ NEXT,
+ };
+
+ Type type;
+ std::string arg;
+
+ explicit Action(Type _type, std::string _arg = "")
+ : type(_type), arg(_arg) {}
+
+ bool operator==(const Action& rhs) const {
+ return std::tie(type, arg) == std::tie(rhs.type, rhs.arg);
+ }
+ };
+
+ LoggingForwardVectorIterator(const std::vector<std::string>& keys,
+ const std::vector<std::string>& values)
+ : keys_(keys), values_(values), current_(keys.size()) {
+ assert(keys_.size() == values_.size());
+ }
+
+ bool Valid() const override { return current_ < keys_.size(); }
+
+ void SeekToFirst() override {
+ log.emplace_back(Action::Type::SEEK_TO_FIRST);
+ current_ = 0;
+ }
+ void SeekToLast() override { assert(false); }
+
+ void Seek(const Slice& target) override {
+ log.emplace_back(Action::Type::SEEK, target.ToString());
+ current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) -
+ keys_.begin();
+ }
+
+ void SeekForPrev(const Slice& /*target*/) override { assert(false); }
+
+ void Next() override {
+ assert(Valid());
+ log.emplace_back(Action::Type::NEXT);
+ current_++;
+ }
+ void Prev() override { assert(false); }
+
+ Slice key() const override {
+ assert(Valid());
+ return Slice(keys_[current_]);
+ }
+ Slice value() const override {
+ assert(Valid());
+ return Slice(values_[current_]);
+ }
+
+ Status status() const override { return Status::OK(); }
+
+ std::vector<Action> log;
+
+ private:
+ std::vector<std::string> keys_;
+ std::vector<std::string> values_;
+ size_t current_;
+};
+
+class FakeCompaction : public CompactionIterator::CompactionProxy {
+ public:
+ FakeCompaction() = default;
+
+ int level(size_t /*compaction_input_level*/) const override { return 0; }
+ bool KeyNotExistsBeyondOutputLevel(
+ const Slice& /*user_key*/,
+ std::vector<size_t>* /*level_ptrs*/) const override {
+ return is_bottommost_level || key_not_exists_beyond_output_level;
+ }
+ bool bottommost_level() const override { return is_bottommost_level; }
+ int number_levels() const override { return 1; }
+ Slice GetLargestUserKey() const override {
+ return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
+ }
+ bool allow_ingest_behind() const override { return false; }
+
+ bool preserve_deletes() const override { return false; }
+
+ bool key_not_exists_beyond_output_level = false;
+
+ bool is_bottommost_level = false;
+};
+
+// A simplifed snapshot checker which assumes each snapshot has a global
+// last visible sequence.
+class TestSnapshotChecker : public SnapshotChecker {
+ public:
+ explicit TestSnapshotChecker(
+ SequenceNumber last_committed_sequence,
+ const std::unordered_map<SequenceNumber, SequenceNumber>& snapshots = {{}})
+ : last_committed_sequence_(last_committed_sequence),
+ snapshots_(snapshots) {}
+
+ SnapshotCheckerResult CheckInSnapshot(
+ SequenceNumber seq, SequenceNumber snapshot_seq) const override {
+ if (snapshot_seq == kMaxSequenceNumber) {
+ return seq <= last_committed_sequence_
+ ? SnapshotCheckerResult::kInSnapshot
+ : SnapshotCheckerResult::kNotInSnapshot;
+ }
+ assert(snapshots_.count(snapshot_seq) > 0);
+ return seq <= snapshots_.at(snapshot_seq)
+ ? SnapshotCheckerResult::kInSnapshot
+ : SnapshotCheckerResult::kNotInSnapshot;
+ }
+
+ private:
+ SequenceNumber last_committed_sequence_;
+ // A map of valid snapshot to last visible sequence to the snapshot.
+ std::unordered_map<SequenceNumber, SequenceNumber> snapshots_;
+};
+
+// Test param:
+// bool: whether to pass snapshot_checker to compaction iterator.
+class CompactionIteratorTest : public testing::TestWithParam<bool> {
+ public:
+ CompactionIteratorTest()
+ : cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {}
+
+ void InitIterators(
+ const std::vector<std::string>& ks, const std::vector<std::string>& vs,
+ const std::vector<std::string>& range_del_ks,
+ const std::vector<std::string>& range_del_vs,
+ SequenceNumber last_sequence,
+ SequenceNumber last_committed_sequence = kMaxSequenceNumber,
+ MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
+ bool bottommost_level = false,
+ SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
+ std::unique_ptr<InternalIterator> unfragmented_range_del_iter(
+ new test::VectorIterator(range_del_ks, range_del_vs));
+ auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
+ std::move(unfragmented_range_del_iter), icmp_);
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ new FragmentedRangeTombstoneIterator(tombstone_list, icmp_,
+ kMaxSequenceNumber));
+ range_del_agg_.reset(new CompactionRangeDelAggregator(&icmp_, snapshots_));
+ range_del_agg_->AddTombstones(std::move(range_del_iter));
+
+ std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
+ if (filter || bottommost_level) {
+ compaction_proxy_ = new FakeCompaction();
+ compaction_proxy_->is_bottommost_level = bottommost_level;
+ compaction.reset(compaction_proxy_);
+ }
+ bool use_snapshot_checker = UseSnapshotChecker() || GetParam();
+ if (use_snapshot_checker || last_committed_sequence < kMaxSequenceNumber) {
+ snapshot_checker_.reset(
+ new TestSnapshotChecker(last_committed_sequence, snapshot_map_));
+ }
+ merge_helper_.reset(
+ new MergeHelper(Env::Default(), cmp_, merge_op, filter, nullptr, false,
+ 0 /*latest_snapshot*/, snapshot_checker_.get(),
+ 0 /*level*/, nullptr /*statistics*/, &shutting_down_));
+
+ iter_.reset(new LoggingForwardVectorIterator(ks, vs));
+ iter_->SeekToFirst();
+ c_iter_.reset(new CompactionIterator(
+ iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
+ earliest_write_conflict_snapshot, snapshot_checker_.get(),
+ Env::Default(), false /* report_detailed_time */, false,
+ range_del_agg_.get(), std::move(compaction), filter, &shutting_down_));
+ }
+
+ void AddSnapshot(SequenceNumber snapshot,
+ SequenceNumber last_visible_seq = kMaxSequenceNumber) {
+ snapshots_.push_back(snapshot);
+ snapshot_map_[snapshot] = last_visible_seq;
+ }
+
+ virtual bool UseSnapshotChecker() const { return false; }
+
+ void RunTest(
+ const std::vector<std::string>& input_keys,
+ const std::vector<std::string>& input_values,
+ const std::vector<std::string>& expected_keys,
+ const std::vector<std::string>& expected_values,
+ SequenceNumber last_committed_seq = kMaxSequenceNumber,
+ MergeOperator* merge_operator = nullptr,
+ CompactionFilter* compaction_filter = nullptr,
+ bool bottommost_level = false,
+ SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
+ InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber,
+ last_committed_seq, merge_operator, compaction_filter,
+ bottommost_level, earliest_write_conflict_snapshot);
+ c_iter_->SeekToFirst();
+ for (size_t i = 0; i < expected_keys.size(); i++) {
+ std::string info = "i = " + ToString(i);
+ ASSERT_TRUE(c_iter_->Valid()) << info;
+ ASSERT_OK(c_iter_->status()) << info;
+ ASSERT_EQ(expected_keys[i], c_iter_->key().ToString()) << info;
+ ASSERT_EQ(expected_values[i], c_iter_->value().ToString()) << info;
+ c_iter_->Next();
+ }
+ ASSERT_FALSE(c_iter_->Valid());
+ }
+
+ const Comparator* cmp_;
+ const InternalKeyComparator icmp_;
+ std::vector<SequenceNumber> snapshots_;
+ // A map of valid snapshot to last visible sequence to the snapshot.
+ std::unordered_map<SequenceNumber, SequenceNumber> snapshot_map_;
+ std::unique_ptr<MergeHelper> merge_helper_;
+ std::unique_ptr<LoggingForwardVectorIterator> iter_;
+ std::unique_ptr<CompactionIterator> c_iter_;
+ std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
+ std::unique_ptr<SnapshotChecker> snapshot_checker_;
+ std::atomic<bool> shutting_down_{false};
+ FakeCompaction* compaction_proxy_;
+};
+
+// It is possible that the output of the compaction iterator is empty even if
+// the input is not.
+TEST_P(CompactionIteratorTest, EmptyResult) {
+ InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
+ test::KeyStr("a", 3, kTypeValue)},
+ {"", "val"}, {}, {}, 5);
+ c_iter_->SeekToFirst();
+ ASSERT_FALSE(c_iter_->Valid());
+}
+
+// If there is a corruption after a single deletion, the corrupted key should
+// be preserved.
+TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
+ InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
+ test::KeyStr("a", 3, kTypeValue, true),
+ test::KeyStr("b", 10, kTypeValue)},
+ {"", "val", "val2"}, {}, {}, 10);
+ c_iter_->SeekToFirst();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("a", 5, kTypeSingleDeletion),
+ c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("a", 3, kTypeValue, true), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("b", 10, kTypeValue), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_FALSE(c_iter_->Valid());
+}
+
+TEST_P(CompactionIteratorTest, SimpleRangeDeletion) {
+ InitIterators({test::KeyStr("morning", 5, kTypeValue),
+ test::KeyStr("morning", 2, kTypeValue),
+ test::KeyStr("night", 3, kTypeValue)},
+ {"zao", "zao", "wan"},
+ {test::KeyStr("ma", 4, kTypeRangeDeletion)}, {"mz"}, 5);
+ c_iter_->SeekToFirst();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("morning", 5, kTypeValue), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("night", 3, kTypeValue), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_FALSE(c_iter_->Valid());
+}
+
+TEST_P(CompactionIteratorTest, RangeDeletionWithSnapshots) {
+ AddSnapshot(10);
+ std::vector<std::string> ks1;
+ ks1.push_back(test::KeyStr("ma", 28, kTypeRangeDeletion));
+ std::vector<std::string> vs1{"mz"};
+ std::vector<std::string> ks2{test::KeyStr("morning", 15, kTypeValue),
+ test::KeyStr("morning", 5, kTypeValue),
+ test::KeyStr("night", 40, kTypeValue),
+ test::KeyStr("night", 20, kTypeValue)};
+ std::vector<std::string> vs2{"zao 15", "zao 5", "wan 40", "wan 20"};
+ InitIterators(ks2, vs2, ks1, vs1, 40);
+ c_iter_->SeekToFirst();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("morning", 5, kTypeValue), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("night", 40, kTypeValue), c_iter_->key().ToString());
+ c_iter_->Next();
+ ASSERT_FALSE(c_iter_->Valid());
+}
+
+TEST_P(CompactionIteratorTest, CompactionFilterSkipUntil) {
+ class Filter : public CompactionFilter {
+ Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
+ const Slice& existing_value, std::string* /*new_value*/,
+ std::string* skip_until) const override {
+ std::string k = key.ToString();
+ std::string v = existing_value.ToString();
+ // See InitIterators() call below for the sequence of keys and their
+ // filtering decisions. Here we closely assert that compaction filter is
+ // called with the expected keys and only them, and with the right values.
+ if (k == "a") {
+ EXPECT_EQ(ValueType::kValue, t);
+ EXPECT_EQ("av50", v);
+ return Decision::kKeep;
+ }
+ if (k == "b") {
+ EXPECT_EQ(ValueType::kValue, t);
+ EXPECT_EQ("bv60", v);
+ *skip_until = "d+";
+ return Decision::kRemoveAndSkipUntil;
+ }
+ if (k == "e") {
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ EXPECT_EQ("em71", v);
+ return Decision::kKeep;
+ }
+ if (k == "f") {
+ if (v == "fm65") {
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ *skip_until = "f";
+ } else {
+ EXPECT_EQ("fm30", v);
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ *skip_until = "g+";
+ }
+ return Decision::kRemoveAndSkipUntil;
+ }
+ if (k == "h") {
+ EXPECT_EQ(ValueType::kValue, t);
+ EXPECT_EQ("hv91", v);
+ return Decision::kKeep;
+ }
+ if (k == "i") {
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ EXPECT_EQ("im95", v);
+ *skip_until = "z";
+ return Decision::kRemoveAndSkipUntil;
+ }
+ ADD_FAILURE();
+ return Decision::kKeep;
+ }
+
+ const char* Name() const override {
+ return "CompactionIteratorTest.CompactionFilterSkipUntil::Filter";
+ }
+ };
+
+ NoMergingMergeOp merge_op;
+ Filter filter;
+ InitIterators(
+ {test::KeyStr("a", 50, kTypeValue), // keep
+ test::KeyStr("a", 45, kTypeMerge),
+ test::KeyStr("b", 60, kTypeValue), // skip to "d+"
+ test::KeyStr("b", 40, kTypeValue), test::KeyStr("c", 35, kTypeValue),
+ test::KeyStr("d", 70, kTypeMerge),
+ test::KeyStr("e", 71, kTypeMerge), // keep
+ test::KeyStr("f", 65, kTypeMerge), // skip to "f", aka keep
+ test::KeyStr("f", 30, kTypeMerge), // skip to "g+"
+ test::KeyStr("f", 25, kTypeValue), test::KeyStr("g", 90, kTypeValue),
+ test::KeyStr("h", 91, kTypeValue), // keep
+ test::KeyStr("i", 95, kTypeMerge), // skip to "z"
+ test::KeyStr("j", 99, kTypeValue)},
+ {"av50", "am45", "bv60", "bv40", "cv35", "dm70", "em71", "fm65", "fm30",
+ "fv25", "gv90", "hv91", "im95", "jv99"},
+ {}, {}, kMaxSequenceNumber, kMaxSequenceNumber, &merge_op, &filter);
+
+ // Compaction should output just "a", "e" and "h" keys.
+ c_iter_->SeekToFirst();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("a", 50, kTypeValue), c_iter_->key().ToString());
+ ASSERT_EQ("av50", c_iter_->value().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("e", 71, kTypeMerge), c_iter_->key().ToString());
+ ASSERT_EQ("em71", c_iter_->value().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("h", 91, kTypeValue), c_iter_->key().ToString());
+ ASSERT_EQ("hv91", c_iter_->value().ToString());
+ c_iter_->Next();
+ ASSERT_FALSE(c_iter_->Valid());
+
+ // Check that the compaction iterator did the correct sequence of calls on
+ // the underlying iterator.
+ using A = LoggingForwardVectorIterator::Action;
+ using T = A::Type;
+ std::vector<A> expected_actions = {
+ A(T::SEEK_TO_FIRST),
+ A(T::NEXT),
+ A(T::NEXT),
+ A(T::SEEK, test::KeyStr("d+", kMaxSequenceNumber, kValueTypeForSeek)),
+ A(T::NEXT),
+ A(T::NEXT),
+ A(T::SEEK, test::KeyStr("g+", kMaxSequenceNumber, kValueTypeForSeek)),
+ A(T::NEXT),
+ A(T::SEEK, test::KeyStr("z", kMaxSequenceNumber, kValueTypeForSeek))};
+ ASSERT_EQ(expected_actions, iter_->log);
+}
+
+TEST_P(CompactionIteratorTest, ShuttingDownInFilter) {
+ NoMergingMergeOp merge_op;
+ StallingFilter filter;
+ InitIterators(
+ {test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeValue),
+ test::KeyStr("3", 3, kTypeValue), test::KeyStr("4", 4, kTypeValue)},
+ {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
+ &merge_op, &filter);
+ // Don't leave tombstones (kTypeDeletion) for filtered keys.
+ compaction_proxy_->key_not_exists_beyond_output_level = true;
+
+ std::atomic<bool> seek_done{false};
+ ROCKSDB_NAMESPACE::port::Thread compaction_thread([&] {
+ c_iter_->SeekToFirst();
+ EXPECT_FALSE(c_iter_->Valid());
+ EXPECT_TRUE(c_iter_->status().IsShutdownInProgress());
+ seek_done.store(true);
+ });
+
+ // Let key 1 through.
+ filter.WaitForStall(1);
+
+ // Shutdown during compaction filter call for key 2.
+ filter.WaitForStall(2);
+ shutting_down_.store(true);
+ EXPECT_FALSE(seek_done.load());
+
+ // Unstall filter and wait for SeekToFirst() to return.
+ filter.stall_at.store(3);
+ compaction_thread.join();
+ assert(seek_done.load());
+
+ // Check that filter was never called again.
+ EXPECT_EQ(2, filter.last_seen.load());
+}
+
+// Same as ShuttingDownInFilter, but shutdown happens during filter call for
+// a merge operand, not for a value.
+TEST_P(CompactionIteratorTest, ShuttingDownInMerge) {
+ NoMergingMergeOp merge_op;
+ StallingFilter filter;
+ InitIterators(
+ {test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeMerge),
+ test::KeyStr("3", 3, kTypeMerge), test::KeyStr("4", 4, kTypeValue)},
+ {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
+ &merge_op, &filter);
+ compaction_proxy_->key_not_exists_beyond_output_level = true;
+
+ std::atomic<bool> seek_done{false};
+ ROCKSDB_NAMESPACE::port::Thread compaction_thread([&] {
+ c_iter_->SeekToFirst();
+ ASSERT_FALSE(c_iter_->Valid());
+ ASSERT_TRUE(c_iter_->status().IsShutdownInProgress());
+ seek_done.store(true);
+ });
+
+ // Let key 1 through.
+ filter.WaitForStall(1);
+
+ // Shutdown during compaction filter call for key 2.
+ filter.WaitForStall(2);
+ shutting_down_.store(true);
+ EXPECT_FALSE(seek_done.load());
+
+ // Unstall filter and wait for SeekToFirst() to return.
+ filter.stall_at.store(3);
+ compaction_thread.join();
+ assert(seek_done.load());
+
+ // Check that filter was never called again.
+ EXPECT_EQ(2, filter.last_seen.load());
+}
+
+TEST_P(CompactionIteratorTest, SingleMergeOperand) {
+ class Filter : public CompactionFilter {
+ Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
+ const Slice& existing_value, std::string* /*new_value*/,
+ std::string* /*skip_until*/) const override {
+ std::string k = key.ToString();
+ std::string v = existing_value.ToString();
+
+ // See InitIterators() call below for the sequence of keys and their
+ // filtering decisions. Here we closely assert that compaction filter is
+ // called with the expected keys and only them, and with the right values.
+ if (k == "a") {
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ EXPECT_EQ("av1", v);
+ return Decision::kKeep;
+ } else if (k == "b") {
+ EXPECT_EQ(ValueType::kMergeOperand, t);
+ return Decision::kKeep;
+ } else if (k == "c") {
+ return Decision::kKeep;
+ }
+
+ ADD_FAILURE();
+ return Decision::kKeep;
+ }
+
+ const char* Name() const override {
+ return "CompactionIteratorTest.SingleMergeOperand::Filter";
+ }
+ };
+
+ class SingleMergeOp : public MergeOperator {
+ public:
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ // See InitIterators() call below for why "c" is the only key for which
+ // FullMergeV2 should be called.
+ EXPECT_EQ("c", merge_in.key.ToString());
+
+ std::string temp_value;
+ if (merge_in.existing_value != nullptr) {
+ temp_value = merge_in.existing_value->ToString();
+ }
+
+ for (auto& operand : merge_in.operand_list) {
+ temp_value.append(operand.ToString());
+ }
+ merge_out->new_value = temp_value;
+
+ return true;
+ }
+
+ bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* /*logger*/) const override {
+ std::string string_key = key.ToString();
+ EXPECT_TRUE(string_key == "a" || string_key == "b");
+
+ if (string_key == "a") {
+ EXPECT_EQ(1, operand_list.size());
+ } else if (string_key == "b") {
+ EXPECT_EQ(2, operand_list.size());
+ }
+
+ std::string temp_value;
+ for (auto& operand : operand_list) {
+ temp_value.append(operand.ToString());
+ }
+ swap(temp_value, *new_value);
+
+ return true;
+ }
+
+ const char* Name() const override {
+ return "CompactionIteratorTest SingleMergeOp";
+ }
+
+ bool AllowSingleOperand() const override { return true; }
+ };
+
+ SingleMergeOp merge_op;
+ Filter filter;
+ InitIterators(
+ // a should invoke PartialMergeMulti with a single merge operand.
+ {test::KeyStr("a", 50, kTypeMerge),
+ // b should invoke PartialMergeMulti with two operands.
+ test::KeyStr("b", 70, kTypeMerge), test::KeyStr("b", 60, kTypeMerge),
+ // c should invoke FullMerge due to kTypeValue at the beginning.
+ test::KeyStr("c", 90, kTypeMerge), test::KeyStr("c", 80, kTypeValue)},
+ {"av1", "bv2", "bv1", "cv2", "cv1"}, {}, {}, kMaxSequenceNumber,
+ kMaxSequenceNumber, &merge_op, &filter);
+
+ c_iter_->SeekToFirst();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), c_iter_->key().ToString());
+ ASSERT_EQ("av1", c_iter_->value().ToString());
+ c_iter_->Next();
+ ASSERT_TRUE(c_iter_->Valid());
+ ASSERT_EQ("bv1bv2", c_iter_->value().ToString());
+ c_iter_->Next();
+ ASSERT_EQ("cv1cv2", c_iter_->value().ToString());
+}
+
+// In bottommost level, values earlier than earliest snapshot can be output
+// with sequence = 0.
+TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
+ AddSnapshot(1);
+ RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
+ {"v1", "v2"},
+ {test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
+ {"v1", "v2"}, kMaxSequenceNumber /*last_commited_seq*/,
+ nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
+ true /*bottommost_level*/);
+}
+
+// In bottommost level, deletions earlier than earliest snapshot can be removed
+// permanently.
+TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
+ AddSnapshot(1);
+ RunTest({test::KeyStr("a", 1, kTypeDeletion),
+ test::KeyStr("b", 3, kTypeDeletion),
+ test::KeyStr("b", 1, kTypeValue)},
+ {"", "", ""},
+ {test::KeyStr("b", 3, kTypeDeletion),
+ test::KeyStr("b", 0, kTypeValue)},
+ {"", ""},
+ kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
+ nullptr /*compaction_filter*/, true /*bottommost_level*/);
+}
+
+// In bottommost level, single deletions earlier than earliest snapshot can be
+// removed permanently.
+TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) {
+ AddSnapshot(1);
+ RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
+ test::KeyStr("b", 2, kTypeSingleDeletion)},
+ {"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""},
+ kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
+ nullptr /*compaction_filter*/, true /*bottommost_level*/);
+}
+
+INSTANTIATE_TEST_CASE_P(CompactionIteratorTestInstance, CompactionIteratorTest,
+ testing::Values(true, false));
+
+// Tests how CompactionIterator work together with SnapshotChecker.
+class CompactionIteratorWithSnapshotCheckerTest
+ : public CompactionIteratorTest {
+ public:
+ bool UseSnapshotChecker() const override { return true; }
+};
+
+// Uncommitted keys (keys with seq > last_committed_seq) should be output as-is
+// while committed version of these keys should get compacted as usual.
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ PreserveUncommittedKeys_Value) {
+ RunTest(
+ {test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"v3", "v2", "v1"},
+ {test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue)},
+ {"v3", "v2"}, 2 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ PreserveUncommittedKeys_Deletion) {
+ RunTest({test::KeyStr("foo", 2, kTypeDeletion),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"", "v1"},
+ {test::KeyStr("foo", 2, kTypeDeletion),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"", "v1"}, 1 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ PreserveUncommittedKeys_Merge) {
+ auto merge_op = MergeOperators::CreateStringAppendOperator();
+ RunTest(
+ {test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"v3", "v2", "v1"},
+ {test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeValue)},
+ {"v3", "v1,v2"}, 2 /*last_committed_seq*/, merge_op.get());
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ PreserveUncommittedKeys_SingleDelete) {
+ RunTest({test::KeyStr("foo", 2, kTypeSingleDeletion),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"", "v1"},
+ {test::KeyStr("foo", 2, kTypeSingleDeletion),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"", "v1"}, 1 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ PreserveUncommittedKeys_BlobIndex) {
+ RunTest({test::KeyStr("foo", 3, kTypeBlobIndex),
+ test::KeyStr("foo", 2, kTypeBlobIndex),
+ test::KeyStr("foo", 1, kTypeBlobIndex)},
+ {"v3", "v2", "v1"},
+ {test::KeyStr("foo", 3, kTypeBlobIndex),
+ test::KeyStr("foo", 2, kTypeBlobIndex)},
+ {"v3", "v2"}, 2 /*last_committed_seq*/);
+}
+
+// Test compaction iterator dedup keys visible to the same snapshot.
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Value) {
+ AddSnapshot(2, 1);
+ RunTest(
+ {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
+ test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "v3", "v2", "v1"},
+ {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Deletion) {
+ AddSnapshot(2, 1);
+ RunTest(
+ {test::KeyStr("foo", 4, kTypeValue),
+ test::KeyStr("foo", 3, kTypeDeletion),
+ test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "", "v2", "v1"},
+ {test::KeyStr("foo", 4, kTypeValue),
+ test::KeyStr("foo", 3, kTypeDeletion),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "", "v1"}, 3 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Merge) {
+ AddSnapshot(2, 1);
+ AddSnapshot(4, 3);
+ auto merge_op = MergeOperators::CreateStringAppendOperator();
+ RunTest(
+ {test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
+ test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
+ test::KeyStr("foo", 1, kTypeValue)},
+ {"v5", "v4", "v3", "v2", "v1"},
+ {test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
+ test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 1, kTypeValue)},
+ {"v5", "v4", "v2,v3", "v1"}, 4 /*last_committed_seq*/, merge_op.get());
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ DedupSameSnapshot_SingleDeletion) {
+ AddSnapshot(2, 1);
+ RunTest(
+ {test::KeyStr("foo", 4, kTypeValue),
+ test::KeyStr("foo", 3, kTypeSingleDeletion),
+ test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "", "v2", "v1"},
+ {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
+ {"v4", "v1"}, 3 /*last_committed_seq*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_BlobIndex) {
+ AddSnapshot(2, 1);
+ RunTest({test::KeyStr("foo", 4, kTypeBlobIndex),
+ test::KeyStr("foo", 3, kTypeBlobIndex),
+ test::KeyStr("foo", 2, kTypeBlobIndex),
+ test::KeyStr("foo", 1, kTypeBlobIndex)},
+ {"v4", "v3", "v2", "v1"},
+ {test::KeyStr("foo", 4, kTypeBlobIndex),
+ test::KeyStr("foo", 3, kTypeBlobIndex),
+ test::KeyStr("foo", 1, kTypeBlobIndex)},
+ {"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
+}
+
+// At bottom level, sequence numbers can be zero out, and deletions can be
+// removed, but only when they are visible to earliest snapshot.
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ NotZeroOutSequenceIfNotVisibleToEarliestSnapshot) {
+ AddSnapshot(2, 1);
+ RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue),
+ test::KeyStr("c", 3, kTypeValue)},
+ {"v1", "v2", "v3"},
+ {test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue),
+ test::KeyStr("c", 3, kTypeValue)},
+ {"v1", "v2", "v3"}, kMaxSequenceNumber /*last_commited_seq*/,
+ nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
+ true /*bottommost_level*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ NotRemoveDeletionIfNotVisibleToEarliestSnapshot) {
+ AddSnapshot(2, 1);
+ RunTest(
+ {test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
+ test::KeyStr("c", 3, kTypeDeletion)},
+ {"", "", ""},
+ {},
+ {"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
+ nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
+ true /*bottommost_level*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ NotRemoveDeletionIfValuePresentToEarlierSnapshot) {
+ AddSnapshot(2,1);
+ RunTest(
+ {test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 1, kTypeValue),
+ test::KeyStr("b", 3, kTypeValue)},
+ {"", "", ""},
+ {test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 0, kTypeValue),
+ test::KeyStr("b", 3, kTypeValue)},
+ {"", "", ""}, kMaxSequenceNumber /*last_commited_seq*/,
+ nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
+ true /*bottommost_level*/);
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ NotRemoveSingleDeletionIfNotVisibleToEarliestSnapshot) {
+ AddSnapshot(2, 1);
+ RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
+ test::KeyStr("b", 2, kTypeSingleDeletion),
+ test::KeyStr("c", 3, kTypeSingleDeletion)},
+ {"", "", ""},
+ {test::KeyStr("b", 2, kTypeSingleDeletion),
+ test::KeyStr("c", 3, kTypeSingleDeletion)},
+ {"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
+ nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
+ true /*bottommost_level*/);
+}
+
+// Single delete should not cancel out values that not visible to the
+// same set of snapshots
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ SingleDeleteAcrossSnapshotBoundary) {
+ AddSnapshot(2, 1);
+ RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
+ test::KeyStr("a", 1, kTypeValue)},
+ {"", "v1"},
+ {test::KeyStr("a", 2, kTypeSingleDeletion),
+ test::KeyStr("a", 1, kTypeValue)},
+ {"", "v1"}, 2 /*last_committed_seq*/);
+}
+
+// Single delete should be kept in case it is not visible to the
+// earliest write conflict snapshot. If a single delete is kept for this reason,
+// corresponding value can be trimmed to save space.
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ KeepSingleDeletionForWriteConflictChecking) {
+ AddSnapshot(2, 0);
+ RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
+ test::KeyStr("a", 1, kTypeValue)},
+ {"", "v1"},
+ {test::KeyStr("a", 2, kTypeSingleDeletion),
+ test::KeyStr("a", 1, kTypeValue)},
+ {"", ""}, 2 /*last_committed_seq*/, nullptr /*merge_operator*/,
+ nullptr /*compaction_filter*/, false /*bottommost_level*/,
+ 2 /*earliest_write_conflict_snapshot*/);
+}
+
+// Compaction filter should keep uncommitted key as-is, and
+// * Convert the latest velue to deletion, and/or
+// * if latest value is a merge, apply filter to all suequent merges.
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) {
+ std::unique_ptr<CompactionFilter> compaction_filter(
+ new FilterAllKeysCompactionFilter());
+ RunTest(
+ {test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeValue),
+ test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeValue)},
+ {"v2", "v1", "v3", "v4"},
+ {test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeDeletion),
+ test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeDeletion)},
+ {"v2", "", "v3", ""}, 1 /*last_committed_seq*/,
+ nullptr /*merge_operator*/, compaction_filter.get());
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Deletion) {
+ std::unique_ptr<CompactionFilter> compaction_filter(
+ new FilterAllKeysCompactionFilter());
+ RunTest(
+ {test::KeyStr("a", 2, kTypeDeletion), test::KeyStr("a", 1, kTypeValue)},
+ {"", "v1"},
+ {test::KeyStr("a", 2, kTypeDeletion),
+ test::KeyStr("a", 1, kTypeDeletion)},
+ {"", ""}, 1 /*last_committed_seq*/, nullptr /*merge_operator*/,
+ compaction_filter.get());
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest,
+ CompactionFilter_PartialMerge) {
+ std::shared_ptr<MergeOperator> merge_op =
+ MergeOperators::CreateStringAppendOperator();
+ std::unique_ptr<CompactionFilter> compaction_filter(
+ new FilterAllKeysCompactionFilter());
+ RunTest({test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
+ test::KeyStr("a", 1, kTypeMerge)},
+ {"v3", "v2", "v1"}, {test::KeyStr("a", 3, kTypeMerge)}, {"v3"},
+ 2 /*last_committed_seq*/, merge_op.get(), compaction_filter.get());
+}
+
+TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_FullMerge) {
+ std::shared_ptr<MergeOperator> merge_op =
+ MergeOperators::CreateStringAppendOperator();
+ std::unique_ptr<CompactionFilter> compaction_filter(
+ new FilterAllKeysCompactionFilter());
+ RunTest(
+ {test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
+ test::KeyStr("a", 1, kTypeValue)},
+ {"v3", "v2", "v1"},
+ {test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 1, kTypeDeletion)},
+ {"v3", ""}, 2 /*last_committed_seq*/, merge_op.get(),
+ compaction_filter.get());
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_job.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_job.cc
new file mode 100644
index 00000000..576ec7b4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_job.cc
@@ -0,0 +1,1700 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cinttypes>
+#include <functional>
+#include <list>
+#include <memory>
+#include <random>
+#include <set>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "db/builder.h"
+#include "db/compaction/compaction_job.h"
+#include "db/db_impl/db_impl.h"
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "db/error_handler.h"
+#include "db/event_helpers.h"
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/memtable.h"
+#include "db/memtable_list.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "db/range_del_aggregator.h"
+#include "db/version_set.h"
+#include "file/filename.h"
+#include "file/read_write_util.h"
+#include "file/sst_file_manager_impl.h"
+#include "file/writable_file_writer.h"
+#include "logging/log_buffer.h"
+#include "logging/logging.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/thread_status_util.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/merging_iterator.h"
+#include "table/table_builder.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const char* GetCompactionReasonString(CompactionReason compaction_reason) {
+ switch (compaction_reason) {
+ case CompactionReason::kUnknown:
+ return "Unknown";
+ case CompactionReason::kLevelL0FilesNum:
+ return "LevelL0FilesNum";
+ case CompactionReason::kLevelMaxLevelSize:
+ return "LevelMaxLevelSize";
+ case CompactionReason::kUniversalSizeAmplification:
+ return "UniversalSizeAmplification";
+ case CompactionReason::kUniversalSizeRatio:
+ return "UniversalSizeRatio";
+ case CompactionReason::kUniversalSortedRunNum:
+ return "UniversalSortedRunNum";
+ case CompactionReason::kFIFOMaxSize:
+ return "FIFOMaxSize";
+ case CompactionReason::kFIFOReduceNumFiles:
+ return "FIFOReduceNumFiles";
+ case CompactionReason::kFIFOTtl:
+ return "FIFOTtl";
+ case CompactionReason::kManualCompaction:
+ return "ManualCompaction";
+ case CompactionReason::kFilesMarkedForCompaction:
+ return "FilesMarkedForCompaction";
+ case CompactionReason::kBottommostFiles:
+ return "BottommostFiles";
+ case CompactionReason::kTtl:
+ return "Ttl";
+ case CompactionReason::kFlush:
+ return "Flush";
+ case CompactionReason::kExternalSstIngestion:
+ return "ExternalSstIngestion";
+ case CompactionReason::kPeriodicCompaction:
+ return "PeriodicCompaction";
+ case CompactionReason::kNumOfReasons:
+ // fall through
+ default:
+ assert(false);
+ return "Invalid";
+ }
+}
+
+// Maintains state for each sub-compaction
+struct CompactionJob::SubcompactionState {
+ const Compaction* compaction;
+ std::unique_ptr<CompactionIterator> c_iter;
+
+ // The boundaries of the key-range this compaction is interested in. No two
+ // subcompactions may have overlapping key-ranges.
+ // 'start' is inclusive, 'end' is exclusive, and nullptr means unbounded
+ Slice *start, *end;
+
+ // The return status of this subcompaction
+ Status status;
+
+ // Files produced by this subcompaction
+ struct Output {
+ FileMetaData meta;
+ bool finished;
+ std::shared_ptr<const TableProperties> table_properties;
+ };
+
+ // State kept for output being generated
+ std::vector<Output> outputs;
+ std::unique_ptr<WritableFileWriter> outfile;
+ std::unique_ptr<TableBuilder> builder;
+ Output* current_output() {
+ if (outputs.empty()) {
+ // This subcompaction's outptut could be empty if compaction was aborted
+ // before this subcompaction had a chance to generate any output files.
+ // When subcompactions are executed sequentially this is more likely and
+ // will be particulalry likely for the later subcompactions to be empty.
+ // Once they are run in parallel however it should be much rarer.
+ return nullptr;
+ } else {
+ return &outputs.back();
+ }
+ }
+
+ uint64_t current_output_file_size;
+
+ // State during the subcompaction
+ uint64_t total_bytes;
+ uint64_t num_output_records;
+ CompactionJobStats compaction_job_stats;
+ uint64_t approx_size;
+ // An index that used to speed up ShouldStopBefore().
+ size_t grandparent_index = 0;
+ // The number of bytes overlapping between the current output and
+ // grandparent files used in ShouldStopBefore().
+ uint64_t overlapped_bytes = 0;
+ // A flag determine whether the key has been seen in ShouldStopBefore()
+ bool seen_key = false;
+
+ SubcompactionState(Compaction* c, Slice* _start, Slice* _end,
+ uint64_t size = 0)
+ : compaction(c),
+ start(_start),
+ end(_end),
+ outfile(nullptr),
+ builder(nullptr),
+ current_output_file_size(0),
+ total_bytes(0),
+ num_output_records(0),
+ approx_size(size),
+ grandparent_index(0),
+ overlapped_bytes(0),
+ seen_key(false) {
+ assert(compaction != nullptr);
+ }
+
+ SubcompactionState(SubcompactionState&& o) { *this = std::move(o); }
+
+ SubcompactionState& operator=(SubcompactionState&& o) {
+ compaction = std::move(o.compaction);
+ start = std::move(o.start);
+ end = std::move(o.end);
+ status = std::move(o.status);
+ outputs = std::move(o.outputs);
+ outfile = std::move(o.outfile);
+ builder = std::move(o.builder);
+ current_output_file_size = std::move(o.current_output_file_size);
+ total_bytes = std::move(o.total_bytes);
+ num_output_records = std::move(o.num_output_records);
+ compaction_job_stats = std::move(o.compaction_job_stats);
+ approx_size = std::move(o.approx_size);
+ grandparent_index = std::move(o.grandparent_index);
+ overlapped_bytes = std::move(o.overlapped_bytes);
+ seen_key = std::move(o.seen_key);
+ return *this;
+ }
+
+ // Because member std::unique_ptrs do not have these.
+ SubcompactionState(const SubcompactionState&) = delete;
+
+ SubcompactionState& operator=(const SubcompactionState&) = delete;
+
+ // Returns true iff we should stop building the current output
+ // before processing "internal_key".
+ bool ShouldStopBefore(const Slice& internal_key, uint64_t curr_file_size) {
+ const InternalKeyComparator* icmp =
+ &compaction->column_family_data()->internal_comparator();
+ const std::vector<FileMetaData*>& grandparents = compaction->grandparents();
+
+ // Scan to find earliest grandparent file that contains key.
+ while (grandparent_index < grandparents.size() &&
+ icmp->Compare(internal_key,
+ grandparents[grandparent_index]->largest.Encode()) >
+ 0) {
+ if (seen_key) {
+ overlapped_bytes += grandparents[grandparent_index]->fd.GetFileSize();
+ }
+ assert(grandparent_index + 1 >= grandparents.size() ||
+ icmp->Compare(
+ grandparents[grandparent_index]->largest.Encode(),
+ grandparents[grandparent_index + 1]->smallest.Encode()) <= 0);
+ grandparent_index++;
+ }
+ seen_key = true;
+
+ if (overlapped_bytes + curr_file_size >
+ compaction->max_compaction_bytes()) {
+ // Too much overlap for current output; start new output
+ overlapped_bytes = 0;
+ return true;
+ }
+
+ return false;
+ }
+};
+
+// Maintains state for the entire compaction
+struct CompactionJob::CompactionState {
+ Compaction* const compaction;
+
+ // REQUIRED: subcompaction states are stored in order of increasing
+ // key-range
+ std::vector<CompactionJob::SubcompactionState> sub_compact_states;
+ Status status;
+
+ uint64_t total_bytes;
+ uint64_t num_output_records;
+
+ explicit CompactionState(Compaction* c)
+ : compaction(c),
+ total_bytes(0),
+ num_output_records(0) {}
+
+ size_t NumOutputFiles() {
+ size_t total = 0;
+ for (auto& s : sub_compact_states) {
+ total += s.outputs.size();
+ }
+ return total;
+ }
+
+ Slice SmallestUserKey() {
+ for (const auto& sub_compact_state : sub_compact_states) {
+ if (!sub_compact_state.outputs.empty() &&
+ sub_compact_state.outputs[0].finished) {
+ return sub_compact_state.outputs[0].meta.smallest.user_key();
+ }
+ }
+ // If there is no finished output, return an empty slice.
+ return Slice(nullptr, 0);
+ }
+
+ Slice LargestUserKey() {
+ for (auto it = sub_compact_states.rbegin(); it < sub_compact_states.rend();
+ ++it) {
+ if (!it->outputs.empty() && it->current_output()->finished) {
+ assert(it->current_output() != nullptr);
+ return it->current_output()->meta.largest.user_key();
+ }
+ }
+ // If there is no finished output, return an empty slice.
+ return Slice(nullptr, 0);
+ }
+};
+
+void CompactionJob::AggregateStatistics() {
+ for (SubcompactionState& sc : compact_->sub_compact_states) {
+ compact_->total_bytes += sc.total_bytes;
+ compact_->num_output_records += sc.num_output_records;
+ }
+ if (compaction_job_stats_) {
+ for (SubcompactionState& sc : compact_->sub_compact_states) {
+ compaction_job_stats_->Add(sc.compaction_job_stats);
+ }
+ }
+}
+
+CompactionJob::CompactionJob(
+ int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
+ const FileOptions& file_options, VersionSet* versions,
+ const std::atomic<bool>* shutting_down,
+ const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
+ Directory* db_directory, Directory* output_directory, Statistics* stats,
+ InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
+ std::vector<SequenceNumber> existing_snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
+ EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
+ const std::string& dbname, CompactionJobStats* compaction_job_stats,
+ Env::Priority thread_pri, const std::atomic<bool>* manual_compaction_paused)
+ : job_id_(job_id),
+ compact_(new CompactionState(compaction)),
+ compaction_job_stats_(compaction_job_stats),
+ compaction_stats_(compaction->compaction_reason(), 1),
+ dbname_(dbname),
+ db_options_(db_options),
+ file_options_(file_options),
+ env_(db_options.env),
+ fs_(db_options.fs.get()),
+ file_options_for_read_(
+ fs_->OptimizeForCompactionTableRead(file_options, db_options_)),
+ versions_(versions),
+ shutting_down_(shutting_down),
+ manual_compaction_paused_(manual_compaction_paused),
+ preserve_deletes_seqnum_(preserve_deletes_seqnum),
+ log_buffer_(log_buffer),
+ db_directory_(db_directory),
+ output_directory_(output_directory),
+ stats_(stats),
+ db_mutex_(db_mutex),
+ db_error_handler_(db_error_handler),
+ existing_snapshots_(std::move(existing_snapshots)),
+ earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
+ snapshot_checker_(snapshot_checker),
+ table_cache_(std::move(table_cache)),
+ event_logger_(event_logger),
+ bottommost_level_(false),
+ paranoid_file_checks_(paranoid_file_checks),
+ measure_io_stats_(measure_io_stats),
+ write_hint_(Env::WLTH_NOT_SET),
+ thread_pri_(thread_pri) {
+ assert(log_buffer_ != nullptr);
+ const auto* cfd = compact_->compaction->column_family_data();
+ ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
+ db_options_.enable_thread_tracking);
+ ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
+ ReportStartedCompaction(compaction);
+}
+
+CompactionJob::~CompactionJob() {
+ assert(compact_ == nullptr);
+ ThreadStatusUtil::ResetThreadStatus();
+}
+
+void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
+ const auto* cfd = compact_->compaction->column_family_data();
+ ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
+ db_options_.enable_thread_tracking);
+
+ ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID,
+ job_id_);
+
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_INPUT_OUTPUT_LEVEL,
+ (static_cast<uint64_t>(compact_->compaction->start_level()) << 32) +
+ compact_->compaction->output_level());
+
+ // In the current design, a CompactionJob is always created
+ // for non-trivial compaction.
+ assert(compaction->IsTrivialMove() == false ||
+ compaction->is_manual_compaction() == true);
+
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_PROP_FLAGS,
+ compaction->is_manual_compaction() +
+ (compaction->deletion_compaction() << 1));
+
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES,
+ compaction->CalculateTotalInputSize());
+
+ IOSTATS_RESET(bytes_written);
+ IOSTATS_RESET(bytes_read);
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_BYTES_WRITTEN, 0);
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_BYTES_READ, 0);
+
+ // Set the thread operation after operation properties
+ // to ensure GetThreadList() can always show them all together.
+ ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
+
+ if (compaction_job_stats_) {
+ compaction_job_stats_->is_manual_compaction =
+ compaction->is_manual_compaction();
+ }
+}
+
+void CompactionJob::Prepare() {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_COMPACTION_PREPARE);
+
+ // Generate file_levels_ for compaction berfore making Iterator
+ auto* c = compact_->compaction;
+ assert(c->column_family_data() != nullptr);
+ assert(c->column_family_data()->current()->storage_info()->NumLevelFiles(
+ compact_->compaction->level()) > 0);
+
+ write_hint_ =
+ c->column_family_data()->CalculateSSTWriteHint(c->output_level());
+ bottommost_level_ = c->bottommost_level();
+
+ if (c->ShouldFormSubcompactions()) {
+ {
+ StopWatch sw(env_, stats_, SUBCOMPACTION_SETUP_TIME);
+ GenSubcompactionBoundaries();
+ }
+ assert(sizes_.size() == boundaries_.size() + 1);
+
+ for (size_t i = 0; i <= boundaries_.size(); i++) {
+ Slice* start = i == 0 ? nullptr : &boundaries_[i - 1];
+ Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i];
+ compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]);
+ }
+ RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
+ compact_->sub_compact_states.size());
+ } else {
+ compact_->sub_compact_states.emplace_back(c, nullptr, nullptr);
+ }
+}
+
+struct RangeWithSize {
+ Range range;
+ uint64_t size;
+
+ RangeWithSize(const Slice& a, const Slice& b, uint64_t s = 0)
+ : range(a, b), size(s) {}
+};
+
+void CompactionJob::GenSubcompactionBoundaries() {
+ auto* c = compact_->compaction;
+ auto* cfd = c->column_family_data();
+ const Comparator* cfd_comparator = cfd->user_comparator();
+ std::vector<Slice> bounds;
+ int start_lvl = c->start_level();
+ int out_lvl = c->output_level();
+
+ // Add the starting and/or ending key of certain input files as a potential
+ // boundary
+ for (size_t lvl_idx = 0; lvl_idx < c->num_input_levels(); lvl_idx++) {
+ int lvl = c->level(lvl_idx);
+ if (lvl >= start_lvl && lvl <= out_lvl) {
+ const LevelFilesBrief* flevel = c->input_levels(lvl_idx);
+ size_t num_files = flevel->num_files;
+
+ if (num_files == 0) {
+ continue;
+ }
+
+ if (lvl == 0) {
+ // For level 0 add the starting and ending key of each file since the
+ // files may have greatly differing key ranges (not range-partitioned)
+ for (size_t i = 0; i < num_files; i++) {
+ bounds.emplace_back(flevel->files[i].smallest_key);
+ bounds.emplace_back(flevel->files[i].largest_key);
+ }
+ } else {
+ // For all other levels add the smallest/largest key in the level to
+ // encompass the range covered by that level
+ bounds.emplace_back(flevel->files[0].smallest_key);
+ bounds.emplace_back(flevel->files[num_files - 1].largest_key);
+ if (lvl == out_lvl) {
+ // For the last level include the starting keys of all files since
+ // the last level is the largest and probably has the widest key
+ // range. Since it's range partitioned, the ending key of one file
+ // and the starting key of the next are very close (or identical).
+ for (size_t i = 1; i < num_files; i++) {
+ bounds.emplace_back(flevel->files[i].smallest_key);
+ }
+ }
+ }
+ }
+ }
+
+ std::sort(bounds.begin(), bounds.end(),
+ [cfd_comparator](const Slice& a, const Slice& b) -> bool {
+ return cfd_comparator->Compare(ExtractUserKey(a),
+ ExtractUserKey(b)) < 0;
+ });
+ // Remove duplicated entries from bounds
+ bounds.erase(
+ std::unique(bounds.begin(), bounds.end(),
+ [cfd_comparator](const Slice& a, const Slice& b) -> bool {
+ return cfd_comparator->Compare(ExtractUserKey(a),
+ ExtractUserKey(b)) == 0;
+ }),
+ bounds.end());
+
+ // Combine consecutive pairs of boundaries into ranges with an approximate
+ // size of data covered by keys in that range
+ uint64_t sum = 0;
+ std::vector<RangeWithSize> ranges;
+ // Get input version from CompactionState since it's already referenced
+ // earlier in SetInputVersioCompaction::SetInputVersion and will not change
+ // when db_mutex_ is released below
+ auto* v = compact_->compaction->input_version();
+ for (auto it = bounds.begin();;) {
+ const Slice a = *it;
+ ++it;
+
+ if (it == bounds.end()) {
+ break;
+ }
+
+ const Slice b = *it;
+
+ // ApproximateSize could potentially create table reader iterator to seek
+ // to the index block and may incur I/O cost in the process. Unlock db
+ // mutex to reduce contention
+ db_mutex_->Unlock();
+ uint64_t size = versions_->ApproximateSize(SizeApproximationOptions(), v, a,
+ b, start_lvl, out_lvl + 1,
+ TableReaderCaller::kCompaction);
+ db_mutex_->Lock();
+ ranges.emplace_back(a, b, size);
+ sum += size;
+ }
+
+ // Group the ranges into subcompactions
+ const double min_file_fill_percent = 4.0 / 5;
+ int base_level = v->storage_info()->base_level();
+ uint64_t max_output_files = static_cast<uint64_t>(std::ceil(
+ sum / min_file_fill_percent /
+ MaxFileSizeForLevel(*(c->mutable_cf_options()), out_lvl,
+ c->immutable_cf_options()->compaction_style, base_level,
+ c->immutable_cf_options()->level_compaction_dynamic_level_bytes)));
+ uint64_t subcompactions =
+ std::min({static_cast<uint64_t>(ranges.size()),
+ static_cast<uint64_t>(c->max_subcompactions()),
+ max_output_files});
+
+ if (subcompactions > 1) {
+ double mean = sum * 1.0 / subcompactions;
+ // Greedily add ranges to the subcompaction until the sum of the ranges'
+ // sizes becomes >= the expected mean size of a subcompaction
+ sum = 0;
+ for (size_t i = 0; i < ranges.size() - 1; i++) {
+ sum += ranges[i].size;
+ if (subcompactions == 1) {
+ // If there's only one left to schedule then it goes to the end so no
+ // need to put an end boundary
+ continue;
+ }
+ if (sum >= mean) {
+ boundaries_.emplace_back(ExtractUserKey(ranges[i].range.limit));
+ sizes_.emplace_back(sum);
+ subcompactions--;
+ sum = 0;
+ }
+ }
+ sizes_.emplace_back(sum + ranges.back().size);
+ } else {
+ // Only one range so its size is the total sum of sizes computed above
+ sizes_.emplace_back(sum);
+ }
+}
+
+Status CompactionJob::Run() {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_COMPACTION_RUN);
+ TEST_SYNC_POINT("CompactionJob::Run():Start");
+ log_buffer_->FlushBufferToLog();
+ LogCompaction();
+
+ const size_t num_threads = compact_->sub_compact_states.size();
+ assert(num_threads > 0);
+ const uint64_t start_micros = env_->NowMicros();
+
+ // Launch a thread for each of subcompactions 1...num_threads-1
+ std::vector<port::Thread> thread_pool;
+ thread_pool.reserve(num_threads - 1);
+ for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
+ thread_pool.emplace_back(&CompactionJob::ProcessKeyValueCompaction, this,
+ &compact_->sub_compact_states[i]);
+ }
+
+ // Always schedule the first subcompaction (whether or not there are also
+ // others) in the current thread to be efficient with resources
+ ProcessKeyValueCompaction(&compact_->sub_compact_states[0]);
+
+ // Wait for all other threads (if there are any) to finish execution
+ for (auto& thread : thread_pool) {
+ thread.join();
+ }
+
+ compaction_stats_.micros = env_->NowMicros() - start_micros;
+ compaction_stats_.cpu_micros = 0;
+ for (size_t i = 0; i < compact_->sub_compact_states.size(); i++) {
+ compaction_stats_.cpu_micros +=
+ compact_->sub_compact_states[i].compaction_job_stats.cpu_micros;
+ }
+
+ RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros);
+ RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
+ compaction_stats_.cpu_micros);
+
+ TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
+
+ // Check if any thread encountered an error during execution
+ Status status;
+ for (const auto& state : compact_->sub_compact_states) {
+ if (!state.status.ok()) {
+ status = state.status;
+ break;
+ }
+ }
+
+ if (status.ok() && output_directory_) {
+ status = output_directory_->Fsync();
+ }
+
+ if (status.ok()) {
+ thread_pool.clear();
+ std::vector<const FileMetaData*> files_meta;
+ for (const auto& state : compact_->sub_compact_states) {
+ for (const auto& output : state.outputs) {
+ files_meta.emplace_back(&output.meta);
+ }
+ }
+ ColumnFamilyData* cfd = compact_->compaction->column_family_data();
+ auto prefix_extractor =
+ compact_->compaction->mutable_cf_options()->prefix_extractor.get();
+ std::atomic<size_t> next_file_meta_idx(0);
+ auto verify_table = [&](Status& output_status) {
+ while (true) {
+ size_t file_idx = next_file_meta_idx.fetch_add(1);
+ if (file_idx >= files_meta.size()) {
+ break;
+ }
+ // Verify that the table is usable
+ // We set for_compaction to false and don't OptimizeForCompactionTableRead
+ // here because this is a special case after we finish the table building
+ // No matter whether use_direct_io_for_flush_and_compaction is true,
+ // we will regard this verification as user reads since the goal is
+ // to cache it here for further user reads
+ InternalIterator* iter = cfd->table_cache()->NewIterator(
+ ReadOptions(), file_options_, cfd->internal_comparator(),
+ *files_meta[file_idx], /*range_del_agg=*/nullptr, prefix_extractor,
+ /*table_reader_ptr=*/nullptr,
+ cfd->internal_stats()->GetFileReadHist(
+ compact_->compaction->output_level()),
+ TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
+ /*skip_filters=*/false, compact_->compaction->output_level(),
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr);
+ auto s = iter->status();
+
+ if (s.ok() && paranoid_file_checks_) {
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {}
+ s = iter->status();
+ }
+
+ delete iter;
+
+ if (!s.ok()) {
+ output_status = s;
+ break;
+ }
+ }
+ };
+ for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
+ thread_pool.emplace_back(verify_table,
+ std::ref(compact_->sub_compact_states[i].status));
+ }
+ verify_table(compact_->sub_compact_states[0].status);
+ for (auto& thread : thread_pool) {
+ thread.join();
+ }
+ for (const auto& state : compact_->sub_compact_states) {
+ if (!state.status.ok()) {
+ status = state.status;
+ break;
+ }
+ }
+ }
+
+ TablePropertiesCollection tp;
+ for (const auto& state : compact_->sub_compact_states) {
+ for (const auto& output : state.outputs) {
+ auto fn =
+ TableFileName(state.compaction->immutable_cf_options()->cf_paths,
+ output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
+ tp[fn] = output.table_properties;
+ }
+ }
+ compact_->compaction->SetOutputTableProperties(std::move(tp));
+
+ // Finish up all book-keeping to unify the subcompaction results
+ AggregateStatistics();
+ UpdateCompactionStats();
+ RecordCompactionIOStats();
+ LogFlush(db_options_.info_log);
+ TEST_SYNC_POINT("CompactionJob::Run():End");
+
+ compact_->status = status;
+ return status;
+}
+
+Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_COMPACTION_INSTALL);
+ db_mutex_->AssertHeld();
+ Status status = compact_->status;
+ ColumnFamilyData* cfd = compact_->compaction->column_family_data();
+ cfd->internal_stats()->AddCompactionStats(
+ compact_->compaction->output_level(), thread_pri_, compaction_stats_);
+
+ if (status.ok()) {
+ status = InstallCompactionResults(mutable_cf_options);
+ }
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ auto vstorage = cfd->current()->storage_info();
+ const auto& stats = compaction_stats_;
+
+ double read_write_amp = 0.0;
+ double write_amp = 0.0;
+ double bytes_read_per_sec = 0;
+ double bytes_written_per_sec = 0;
+
+ if (stats.bytes_read_non_output_levels > 0) {
+ read_write_amp = (stats.bytes_written + stats.bytes_read_output_level +
+ stats.bytes_read_non_output_levels) /
+ static_cast<double>(stats.bytes_read_non_output_levels);
+ write_amp = stats.bytes_written /
+ static_cast<double>(stats.bytes_read_non_output_levels);
+ }
+ if (stats.micros > 0) {
+ bytes_read_per_sec =
+ (stats.bytes_read_non_output_levels + stats.bytes_read_output_level) /
+ static_cast<double>(stats.micros);
+ bytes_written_per_sec =
+ stats.bytes_written / static_cast<double>(stats.micros);
+ }
+
+ ROCKS_LOG_BUFFER(
+ log_buffer_,
+ "[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
+ "files in(%d, %d) out(%d) "
+ "MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
+ "write-amplify(%.1f) %s, records in: %" PRIu64
+ ", records dropped: %" PRIu64 " output_compression: %s\n",
+ cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec,
+ bytes_written_per_sec, compact_->compaction->output_level(),
+ stats.num_input_files_in_non_output_levels,
+ stats.num_input_files_in_output_level, stats.num_output_files,
+ stats.bytes_read_non_output_levels / 1048576.0,
+ stats.bytes_read_output_level / 1048576.0,
+ stats.bytes_written / 1048576.0, read_write_amp, write_amp,
+ status.ToString().c_str(), stats.num_input_records,
+ stats.num_dropped_records,
+ CompressionTypeToString(compact_->compaction->output_compression())
+ .c_str());
+
+ UpdateCompactionJobStats(stats);
+
+ auto stream = event_logger_->LogToBuffer(log_buffer_);
+ stream << "job" << job_id_ << "event"
+ << "compaction_finished"
+ << "compaction_time_micros" << stats.micros
+ << "compaction_time_cpu_micros" << stats.cpu_micros << "output_level"
+ << compact_->compaction->output_level() << "num_output_files"
+ << compact_->NumOutputFiles() << "total_output_size"
+ << compact_->total_bytes << "num_input_records"
+ << stats.num_input_records << "num_output_records"
+ << compact_->num_output_records << "num_subcompactions"
+ << compact_->sub_compact_states.size() << "output_compression"
+ << CompressionTypeToString(compact_->compaction->output_compression());
+
+ if (compaction_job_stats_ != nullptr) {
+ stream << "num_single_delete_mismatches"
+ << compaction_job_stats_->num_single_del_mismatch;
+ stream << "num_single_delete_fallthrough"
+ << compaction_job_stats_->num_single_del_fallthru;
+ }
+
+ if (measure_io_stats_ && compaction_job_stats_ != nullptr) {
+ stream << "file_write_nanos" << compaction_job_stats_->file_write_nanos;
+ stream << "file_range_sync_nanos"
+ << compaction_job_stats_->file_range_sync_nanos;
+ stream << "file_fsync_nanos" << compaction_job_stats_->file_fsync_nanos;
+ stream << "file_prepare_write_nanos"
+ << compaction_job_stats_->file_prepare_write_nanos;
+ }
+
+ stream << "lsm_state";
+ stream.StartArray();
+ for (int level = 0; level < vstorage->num_levels(); ++level) {
+ stream << vstorage->NumLevelFiles(level);
+ }
+ stream.EndArray();
+
+ CleanupCompaction();
+ return status;
+}
+
+void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
+ assert(sub_compact != nullptr);
+
+ uint64_t prev_cpu_micros = env_->NowCPUNanos() / 1000;
+
+ ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
+
+ // Create compaction filter and fail the compaction if
+ // IgnoreSnapshots() = false because it is not supported anymore
+ const CompactionFilter* compaction_filter =
+ cfd->ioptions()->compaction_filter;
+ std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
+ if (compaction_filter == nullptr) {
+ compaction_filter_from_factory =
+ sub_compact->compaction->CreateCompactionFilter();
+ compaction_filter = compaction_filter_from_factory.get();
+ }
+ if (compaction_filter != nullptr && !compaction_filter->IgnoreSnapshots()) {
+ sub_compact->status = Status::NotSupported(
+ "CompactionFilter::IgnoreSnapshots() = false is not supported "
+ "anymore.");
+ return;
+ }
+
+ CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(),
+ existing_snapshots_);
+
+ // Although the v2 aggregator is what the level iterator(s) know about,
+ // the AddTombstones calls will be propagated down to the v1 aggregator.
+ std::unique_ptr<InternalIterator> input(versions_->MakeInputIterator(
+ sub_compact->compaction, &range_del_agg, file_options_for_read_));
+
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
+
+ // I/O measurement variables
+ PerfLevel prev_perf_level = PerfLevel::kEnableTime;
+ const uint64_t kRecordStatsEvery = 1000;
+ uint64_t prev_write_nanos = 0;
+ uint64_t prev_fsync_nanos = 0;
+ uint64_t prev_range_sync_nanos = 0;
+ uint64_t prev_prepare_write_nanos = 0;
+ uint64_t prev_cpu_write_nanos = 0;
+ uint64_t prev_cpu_read_nanos = 0;
+ if (measure_io_stats_) {
+ prev_perf_level = GetPerfLevel();
+ SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
+ prev_write_nanos = IOSTATS(write_nanos);
+ prev_fsync_nanos = IOSTATS(fsync_nanos);
+ prev_range_sync_nanos = IOSTATS(range_sync_nanos);
+ prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
+ prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
+ prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
+ }
+
+ MergeHelper merge(
+ env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
+ compaction_filter, db_options_.info_log.get(),
+ false /* internal key corruption is expected */,
+ existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
+ snapshot_checker_, compact_->compaction->level(),
+ db_options_.statistics.get());
+
+ TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
+ TEST_SYNC_POINT_CALLBACK(
+ "CompactionJob::Run():PausingManualCompaction:1",
+ reinterpret_cast<void*>(
+ const_cast<std::atomic<bool>*>(manual_compaction_paused_)));
+
+ Slice* start = sub_compact->start;
+ Slice* end = sub_compact->end;
+ if (start != nullptr) {
+ IterKey start_iter;
+ start_iter.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
+ input->Seek(start_iter.GetInternalKey());
+ } else {
+ input->SeekToFirst();
+ }
+
+ Status status;
+ sub_compact->c_iter.reset(new CompactionIterator(
+ input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
+ &existing_snapshots_, earliest_write_conflict_snapshot_,
+ snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
+ &range_del_agg, sub_compact->compaction, compaction_filter,
+ shutting_down_, preserve_deletes_seqnum_, manual_compaction_paused_,
+ db_options_.info_log));
+ auto c_iter = sub_compact->c_iter.get();
+ c_iter->SeekToFirst();
+ if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
+ // ShouldStopBefore() maintains state based on keys processed so far. The
+ // compaction loop always calls it on the "next" key, thus won't tell it the
+ // first key. So we do that here.
+ sub_compact->ShouldStopBefore(c_iter->key(),
+ sub_compact->current_output_file_size);
+ }
+ const auto& c_iter_stats = c_iter->iter_stats();
+
+ while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
+ // Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
+ // returns true.
+ const Slice& key = c_iter->key();
+ const Slice& value = c_iter->value();
+
+ // If an end key (exclusive) is specified, check if the current key is
+ // >= than it and exit if it is because the iterator is out of its range
+ if (end != nullptr &&
+ cfd->user_comparator()->Compare(c_iter->user_key(), *end) >= 0) {
+ break;
+ }
+ if (c_iter_stats.num_input_records % kRecordStatsEvery ==
+ kRecordStatsEvery - 1) {
+ RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
+ c_iter->ResetRecordCounts();
+ RecordCompactionIOStats();
+ }
+
+ // Open output file if necessary
+ if (sub_compact->builder == nullptr) {
+ status = OpenCompactionOutputFile(sub_compact);
+ if (!status.ok()) {
+ break;
+ }
+ }
+ assert(sub_compact->builder != nullptr);
+ assert(sub_compact->current_output() != nullptr);
+ sub_compact->builder->Add(key, value);
+ sub_compact->current_output_file_size = sub_compact->builder->FileSize();
+ const ParsedInternalKey& ikey = c_iter->ikey();
+ sub_compact->current_output()->meta.UpdateBoundaries(
+ key, value, ikey.sequence, ikey.type);
+ sub_compact->num_output_records++;
+
+ // Close output file if it is big enough. Two possibilities determine it's
+ // time to close it: (1) the current key should be this file's last key, (2)
+ // the next key should not be in this file.
+ //
+ // TODO(aekmekji): determine if file should be closed earlier than this
+ // during subcompactions (i.e. if output size, estimated by input size, is
+ // going to be 1.2MB and max_output_file_size = 1MB, prefer to have 0.6MB
+ // and 0.6MB instead of 1MB and 0.2MB)
+ bool output_file_ended = false;
+ Status input_status;
+ if (sub_compact->compaction->output_level() != 0 &&
+ sub_compact->current_output_file_size >=
+ sub_compact->compaction->max_output_file_size()) {
+ // (1) this key terminates the file. For historical reasons, the iterator
+ // status before advancing will be given to FinishCompactionOutputFile().
+ input_status = input->status();
+ output_file_ended = true;
+ }
+ TEST_SYNC_POINT_CALLBACK(
+ "CompactionJob::Run():PausingManualCompaction:2",
+ reinterpret_cast<void*>(
+ const_cast<std::atomic<bool>*>(manual_compaction_paused_)));
+ c_iter->Next();
+ if (c_iter->status().IsManualCompactionPaused()) {
+ break;
+ }
+ if (!output_file_ended && c_iter->Valid() &&
+ sub_compact->compaction->output_level() != 0 &&
+ sub_compact->ShouldStopBefore(c_iter->key(),
+ sub_compact->current_output_file_size) &&
+ sub_compact->builder != nullptr) {
+ // (2) this key belongs to the next file. For historical reasons, the
+ // iterator status after advancing will be given to
+ // FinishCompactionOutputFile().
+ input_status = input->status();
+ output_file_ended = true;
+ }
+ if (output_file_ended) {
+ const Slice* next_key = nullptr;
+ if (c_iter->Valid()) {
+ next_key = &c_iter->key();
+ }
+ CompactionIterationStats range_del_out_stats;
+ status =
+ FinishCompactionOutputFile(input_status, sub_compact, &range_del_agg,
+ &range_del_out_stats, next_key);
+ RecordDroppedKeys(range_del_out_stats,
+ &sub_compact->compaction_job_stats);
+ }
+ }
+
+ sub_compact->compaction_job_stats.num_input_deletion_records =
+ c_iter_stats.num_input_deletion_records;
+ sub_compact->compaction_job_stats.num_corrupt_keys =
+ c_iter_stats.num_input_corrupt_records;
+ sub_compact->compaction_job_stats.num_single_del_fallthru =
+ c_iter_stats.num_single_del_fallthru;
+ sub_compact->compaction_job_stats.num_single_del_mismatch =
+ c_iter_stats.num_single_del_mismatch;
+ sub_compact->compaction_job_stats.total_input_raw_key_bytes +=
+ c_iter_stats.total_input_raw_key_bytes;
+ sub_compact->compaction_job_stats.total_input_raw_value_bytes +=
+ c_iter_stats.total_input_raw_value_bytes;
+
+ RecordTick(stats_, FILTER_OPERATION_TOTAL_TIME,
+ c_iter_stats.total_filter_time);
+ RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
+ RecordCompactionIOStats();
+
+ if (status.ok() && cfd->IsDropped()) {
+ status =
+ Status::ColumnFamilyDropped("Column family dropped during compaction");
+ }
+ if ((status.ok() || status.IsColumnFamilyDropped()) &&
+ shutting_down_->load(std::memory_order_relaxed)) {
+ status = Status::ShutdownInProgress("Database shutdown");
+ }
+ if ((status.ok() || status.IsColumnFamilyDropped()) &&
+ (manual_compaction_paused_ &&
+ manual_compaction_paused_->load(std::memory_order_relaxed))) {
+ status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
+ }
+ if (status.ok()) {
+ status = input->status();
+ }
+ if (status.ok()) {
+ status = c_iter->status();
+ }
+
+ if (status.ok() && sub_compact->builder == nullptr &&
+ sub_compact->outputs.size() == 0 && !range_del_agg.IsEmpty()) {
+ // handle subcompaction containing only range deletions
+ status = OpenCompactionOutputFile(sub_compact);
+ }
+
+ // Call FinishCompactionOutputFile() even if status is not ok: it needs to
+ // close the output file.
+ if (sub_compact->builder != nullptr) {
+ CompactionIterationStats range_del_out_stats;
+ Status s = FinishCompactionOutputFile(status, sub_compact, &range_del_agg,
+ &range_del_out_stats);
+ if (status.ok()) {
+ status = s;
+ }
+ RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
+ }
+
+ sub_compact->compaction_job_stats.cpu_micros =
+ env_->NowCPUNanos() / 1000 - prev_cpu_micros;
+
+ if (measure_io_stats_) {
+ sub_compact->compaction_job_stats.file_write_nanos +=
+ IOSTATS(write_nanos) - prev_write_nanos;
+ sub_compact->compaction_job_stats.file_fsync_nanos +=
+ IOSTATS(fsync_nanos) - prev_fsync_nanos;
+ sub_compact->compaction_job_stats.file_range_sync_nanos +=
+ IOSTATS(range_sync_nanos) - prev_range_sync_nanos;
+ sub_compact->compaction_job_stats.file_prepare_write_nanos +=
+ IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos;
+ sub_compact->compaction_job_stats.cpu_micros -=
+ (IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos +
+ IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos) /
+ 1000;
+ if (prev_perf_level != PerfLevel::kEnableTimeAndCPUTimeExceptForMutex) {
+ SetPerfLevel(prev_perf_level);
+ }
+ }
+
+ sub_compact->c_iter.reset();
+ input.reset();
+ sub_compact->status = status;
+}
+
+void CompactionJob::RecordDroppedKeys(
+ const CompactionIterationStats& c_iter_stats,
+ CompactionJobStats* compaction_job_stats) {
+ if (c_iter_stats.num_record_drop_user > 0) {
+ RecordTick(stats_, COMPACTION_KEY_DROP_USER,
+ c_iter_stats.num_record_drop_user);
+ }
+ if (c_iter_stats.num_record_drop_hidden > 0) {
+ RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY,
+ c_iter_stats.num_record_drop_hidden);
+ if (compaction_job_stats) {
+ compaction_job_stats->num_records_replaced +=
+ c_iter_stats.num_record_drop_hidden;
+ }
+ }
+ if (c_iter_stats.num_record_drop_obsolete > 0) {
+ RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE,
+ c_iter_stats.num_record_drop_obsolete);
+ if (compaction_job_stats) {
+ compaction_job_stats->num_expired_deletion_records +=
+ c_iter_stats.num_record_drop_obsolete;
+ }
+ }
+ if (c_iter_stats.num_record_drop_range_del > 0) {
+ RecordTick(stats_, COMPACTION_KEY_DROP_RANGE_DEL,
+ c_iter_stats.num_record_drop_range_del);
+ }
+ if (c_iter_stats.num_range_del_drop_obsolete > 0) {
+ RecordTick(stats_, COMPACTION_RANGE_DEL_DROP_OBSOLETE,
+ c_iter_stats.num_range_del_drop_obsolete);
+ }
+ if (c_iter_stats.num_optimized_del_drop_obsolete > 0) {
+ RecordTick(stats_, COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
+ c_iter_stats.num_optimized_del_drop_obsolete);
+ }
+}
+
+Status CompactionJob::FinishCompactionOutputFile(
+ const Status& input_status, SubcompactionState* sub_compact,
+ CompactionRangeDelAggregator* range_del_agg,
+ CompactionIterationStats* range_del_out_stats,
+ const Slice* next_table_min_key /* = nullptr */) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
+ assert(sub_compact != nullptr);
+ assert(sub_compact->outfile);
+ assert(sub_compact->builder != nullptr);
+ assert(sub_compact->current_output() != nullptr);
+
+ uint64_t output_number = sub_compact->current_output()->meta.fd.GetNumber();
+ assert(output_number != 0);
+
+ ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
+ const Comparator* ucmp = cfd->user_comparator();
+
+ // Check for iterator errors
+ Status s = input_status;
+ auto meta = &sub_compact->current_output()->meta;
+ assert(meta != nullptr);
+ if (s.ok()) {
+ Slice lower_bound_guard, upper_bound_guard;
+ std::string smallest_user_key;
+ const Slice *lower_bound, *upper_bound;
+ bool lower_bound_from_sub_compact = false;
+ if (sub_compact->outputs.size() == 1) {
+ // For the first output table, include range tombstones before the min key
+ // but after the subcompaction boundary.
+ lower_bound = sub_compact->start;
+ lower_bound_from_sub_compact = true;
+ } else if (meta->smallest.size() > 0) {
+ // For subsequent output tables, only include range tombstones from min
+ // key onwards since the previous file was extended to contain range
+ // tombstones falling before min key.
+ smallest_user_key = meta->smallest.user_key().ToString(false /*hex*/);
+ lower_bound_guard = Slice(smallest_user_key);
+ lower_bound = &lower_bound_guard;
+ } else {
+ lower_bound = nullptr;
+ }
+ if (next_table_min_key != nullptr) {
+ // This may be the last file in the subcompaction in some cases, so we
+ // need to compare the end key of subcompaction with the next file start
+ // key. When the end key is chosen by the subcompaction, we know that
+ // it must be the biggest key in output file. Therefore, it is safe to
+ // use the smaller key as the upper bound of the output file, to ensure
+ // that there is no overlapping between different output files.
+ upper_bound_guard = ExtractUserKey(*next_table_min_key);
+ if (sub_compact->end != nullptr &&
+ ucmp->Compare(upper_bound_guard, *sub_compact->end) >= 0) {
+ upper_bound = sub_compact->end;
+ } else {
+ upper_bound = &upper_bound_guard;
+ }
+ } else {
+ // This is the last file in the subcompaction, so extend until the
+ // subcompaction ends.
+ upper_bound = sub_compact->end;
+ }
+ auto earliest_snapshot = kMaxSequenceNumber;
+ if (existing_snapshots_.size() > 0) {
+ earliest_snapshot = existing_snapshots_[0];
+ }
+ bool has_overlapping_endpoints;
+ if (upper_bound != nullptr && meta->largest.size() > 0) {
+ has_overlapping_endpoints =
+ ucmp->Compare(meta->largest.user_key(), *upper_bound) == 0;
+ } else {
+ has_overlapping_endpoints = false;
+ }
+
+ // The end key of the subcompaction must be bigger or equal to the upper
+ // bound. If the end of subcompaction is null or the upper bound is null,
+ // it means that this file is the last file in the compaction. So there
+ // will be no overlapping between this file and others.
+ assert(sub_compact->end == nullptr ||
+ upper_bound == nullptr ||
+ ucmp->Compare(*upper_bound , *sub_compact->end) <= 0);
+ auto it = range_del_agg->NewIterator(lower_bound, upper_bound,
+ has_overlapping_endpoints);
+ // Position the range tombstone output iterator. There may be tombstone
+ // fragments that are entirely out of range, so make sure that we do not
+ // include those.
+ if (lower_bound != nullptr) {
+ it->Seek(*lower_bound);
+ } else {
+ it->SeekToFirst();
+ }
+ for (; it->Valid(); it->Next()) {
+ auto tombstone = it->Tombstone();
+ if (upper_bound != nullptr) {
+ int cmp = ucmp->Compare(*upper_bound, tombstone.start_key_);
+ if ((has_overlapping_endpoints && cmp < 0) ||
+ (!has_overlapping_endpoints && cmp <= 0)) {
+ // Tombstones starting after upper_bound only need to be included in
+ // the next table. If the current SST ends before upper_bound, i.e.,
+ // `has_overlapping_endpoints == false`, we can also skip over range
+ // tombstones that start exactly at upper_bound. Such range tombstones
+ // will be included in the next file and are not relevant to the point
+ // keys or endpoints of the current file.
+ break;
+ }
+ }
+
+ if (bottommost_level_ && tombstone.seq_ <= earliest_snapshot) {
+ // TODO(andrewkr): tombstones that span multiple output files are
+ // counted for each compaction output file, so lots of double counting.
+ range_del_out_stats->num_range_del_drop_obsolete++;
+ range_del_out_stats->num_record_drop_obsolete++;
+ continue;
+ }
+
+ auto kv = tombstone.Serialize();
+ assert(lower_bound == nullptr ||
+ ucmp->Compare(*lower_bound, kv.second) < 0);
+ sub_compact->builder->Add(kv.first.Encode(), kv.second);
+ InternalKey smallest_candidate = std::move(kv.first);
+ if (lower_bound != nullptr &&
+ ucmp->Compare(smallest_candidate.user_key(), *lower_bound) <= 0) {
+ // Pretend the smallest key has the same user key as lower_bound
+ // (the max key in the previous table or subcompaction) in order for
+ // files to appear key-space partitioned.
+ //
+ // When lower_bound is chosen by a subcompaction, we know that
+ // subcompactions over smaller keys cannot contain any keys at
+ // lower_bound. We also know that smaller subcompactions exist, because
+ // otherwise the subcompaction woud be unbounded on the left. As a
+ // result, we know that no other files on the output level will contain
+ // actual keys at lower_bound (an output file may have a largest key of
+ // lower_bound@kMaxSequenceNumber, but this only indicates a large range
+ // tombstone was truncated). Therefore, it is safe to use the
+ // tombstone's sequence number, to ensure that keys at lower_bound at
+ // lower levels are covered by truncated tombstones.
+ //
+ // If lower_bound was chosen by the smallest data key in the file,
+ // choose lowest seqnum so this file's smallest internal key comes after
+ // the previous file's largest. The fake seqnum is OK because the read
+ // path's file-picking code only considers user key.
+ smallest_candidate = InternalKey(
+ *lower_bound, lower_bound_from_sub_compact ? tombstone.seq_ : 0,
+ kTypeRangeDeletion);
+ }
+ InternalKey largest_candidate = tombstone.SerializeEndKey();
+ if (upper_bound != nullptr &&
+ ucmp->Compare(*upper_bound, largest_candidate.user_key()) <= 0) {
+ // Pretend the largest key has the same user key as upper_bound (the
+ // min key in the following table or subcompaction) in order for files
+ // to appear key-space partitioned.
+ //
+ // Choose highest seqnum so this file's largest internal key comes
+ // before the next file's/subcompaction's smallest. The fake seqnum is
+ // OK because the read path's file-picking code only considers the user
+ // key portion.
+ //
+ // Note Seek() also creates InternalKey with (user_key,
+ // kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
+ // kTypeRangeDeletion (0xF), so the range tombstone comes before the
+ // Seek() key in InternalKey's ordering. So Seek() will look in the
+ // next file for the user key.
+ largest_candidate =
+ InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion);
+ }
+#ifndef NDEBUG
+ SequenceNumber smallest_ikey_seqnum = kMaxSequenceNumber;
+ if (meta->smallest.size() > 0) {
+ smallest_ikey_seqnum = GetInternalKeySeqno(meta->smallest.Encode());
+ }
+#endif
+ meta->UpdateBoundariesForRange(smallest_candidate, largest_candidate,
+ tombstone.seq_,
+ cfd->internal_comparator());
+
+ // The smallest key in a file is used for range tombstone truncation, so
+ // it cannot have a seqnum of 0 (unless the smallest data key in a file
+ // has a seqnum of 0). Otherwise, the truncated tombstone may expose
+ // deleted keys at lower levels.
+ assert(smallest_ikey_seqnum == 0 ||
+ ExtractInternalKeyFooter(meta->smallest.Encode()) !=
+ PackSequenceAndType(0, kTypeRangeDeletion));
+ }
+ meta->marked_for_compaction = sub_compact->builder->NeedCompact();
+ }
+ const uint64_t current_entries = sub_compact->builder->NumEntries();
+ if (s.ok()) {
+ s = sub_compact->builder->Finish();
+ } else {
+ sub_compact->builder->Abandon();
+ }
+ const uint64_t current_bytes = sub_compact->builder->FileSize();
+ if (s.ok()) {
+ // Add the checksum information to file metadata.
+ meta->file_checksum = sub_compact->builder->GetFileChecksum();
+ meta->file_checksum_func_name =
+ sub_compact->builder->GetFileChecksumFuncName();
+
+ meta->fd.file_size = current_bytes;
+ }
+ sub_compact->current_output()->finished = true;
+ sub_compact->total_bytes += current_bytes;
+
+ // Finish and check for file errors
+ if (s.ok()) {
+ StopWatch sw(env_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
+ s = sub_compact->outfile->Sync(db_options_.use_fsync);
+ }
+ if (s.ok()) {
+ s = sub_compact->outfile->Close();
+ }
+ sub_compact->outfile.reset();
+
+ TableProperties tp;
+ if (s.ok()) {
+ tp = sub_compact->builder->GetTableProperties();
+ }
+
+ if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) {
+ // If there is nothing to output, no necessary to generate a sst file.
+ // This happens when the output level is bottom level, at the same time
+ // the sub_compact output nothing.
+ std::string fname =
+ TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
+ meta->fd.GetNumber(), meta->fd.GetPathId());
+ env_->DeleteFile(fname);
+
+ // Also need to remove the file from outputs, or it will be added to the
+ // VersionEdit.
+ assert(!sub_compact->outputs.empty());
+ sub_compact->outputs.pop_back();
+ meta = nullptr;
+ }
+
+ if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) {
+ // Output to event logger and fire events.
+ sub_compact->current_output()->table_properties =
+ std::make_shared<TableProperties>(tp);
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
+ " keys, %" PRIu64 " bytes%s",
+ cfd->GetName().c_str(), job_id_, output_number,
+ current_entries, current_bytes,
+ meta->marked_for_compaction ? " (need compaction)" : "");
+ }
+ std::string fname;
+ FileDescriptor output_fd;
+ uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
+ if (meta != nullptr) {
+ fname =
+ TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
+ meta->fd.GetNumber(), meta->fd.GetPathId());
+ output_fd = meta->fd;
+ oldest_blob_file_number = meta->oldest_blob_file_number;
+ } else {
+ fname = "(nil)";
+ }
+ EventHelpers::LogAndNotifyTableFileCreationFinished(
+ event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname,
+ job_id_, output_fd, oldest_blob_file_number, tp,
+ TableFileCreationReason::kCompaction, s);
+
+#ifndef ROCKSDB_LITE
+ // Report new file to SstFileManagerImpl
+ auto sfm =
+ static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
+ if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) {
+ sfm->OnAddFile(fname);
+ if (sfm->IsMaxAllowedSpaceReached()) {
+ // TODO(ajkr): should we return OK() if max space was reached by the final
+ // compaction output file (similarly to how flush works when full)?
+ s = Status::SpaceLimit("Max allowed space was reached");
+ TEST_SYNC_POINT(
+ "CompactionJob::FinishCompactionOutputFile:"
+ "MaxAllowedSpaceReached");
+ InstrumentedMutexLock l(db_mutex_);
+ db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction);
+ }
+ }
+#endif
+
+ sub_compact->builder.reset();
+ sub_compact->current_output_file_size = 0;
+ return s;
+}
+
+Status CompactionJob::InstallCompactionResults(
+ const MutableCFOptions& mutable_cf_options) {
+ db_mutex_->AssertHeld();
+
+ auto* compaction = compact_->compaction;
+ // paranoia: verify that the files that we started with
+ // still exist in the current version and in the same original level.
+ // This ensures that a concurrent compaction did not erroneously
+ // pick the same files to compact_.
+ if (!versions_->VerifyCompactionFileConsistency(compaction)) {
+ Compaction::InputLevelSummaryBuffer inputs_summary;
+
+ ROCKS_LOG_ERROR(db_options_.info_log, "[%s] [JOB %d] Compaction %s aborted",
+ compaction->column_family_data()->GetName().c_str(),
+ job_id_, compaction->InputLevelSummary(&inputs_summary));
+ return Status::Corruption("Compaction input files inconsistent");
+ }
+
+ {
+ Compaction::InputLevelSummaryBuffer inputs_summary;
+ ROCKS_LOG_INFO(
+ db_options_.info_log, "[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
+ compaction->column_family_data()->GetName().c_str(), job_id_,
+ compaction->InputLevelSummary(&inputs_summary), compact_->total_bytes);
+ }
+
+ // Add compaction inputs
+ compaction->AddInputDeletions(compact_->compaction->edit());
+
+ for (const auto& sub_compact : compact_->sub_compact_states) {
+ for (const auto& out : sub_compact.outputs) {
+ compaction->edit()->AddFile(compaction->output_level(), out.meta);
+ }
+ }
+ return versions_->LogAndApply(compaction->column_family_data(),
+ mutable_cf_options, compaction->edit(),
+ db_mutex_, db_directory_);
+}
+
+void CompactionJob::RecordCompactionIOStats() {
+ RecordTick(stats_, COMPACT_READ_BYTES, IOSTATS(bytes_read));
+ ThreadStatusUtil::IncreaseThreadOperationProperty(
+ ThreadStatus::COMPACTION_BYTES_READ, IOSTATS(bytes_read));
+ IOSTATS_RESET(bytes_read);
+ RecordTick(stats_, COMPACT_WRITE_BYTES, IOSTATS(bytes_written));
+ ThreadStatusUtil::IncreaseThreadOperationProperty(
+ ThreadStatus::COMPACTION_BYTES_WRITTEN, IOSTATS(bytes_written));
+ IOSTATS_RESET(bytes_written);
+}
+
+Status CompactionJob::OpenCompactionOutputFile(
+ SubcompactionState* sub_compact) {
+ assert(sub_compact != nullptr);
+ assert(sub_compact->builder == nullptr);
+ // no need to lock because VersionSet::next_file_number_ is atomic
+ uint64_t file_number = versions_->NewFileNumber();
+ std::string fname =
+ TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
+ file_number, sub_compact->compaction->output_path_id());
+ // Fire events.
+ ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
+#ifndef ROCKSDB_LITE
+ EventHelpers::NotifyTableFileCreationStarted(
+ cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname, job_id_,
+ TableFileCreationReason::kCompaction);
+#endif // !ROCKSDB_LITE
+ // Make the output file
+ std::unique_ptr<FSWritableFile> writable_file;
+#ifndef NDEBUG
+ bool syncpoint_arg = file_options_.use_direct_writes;
+ TEST_SYNC_POINT_CALLBACK("CompactionJob::OpenCompactionOutputFile",
+ &syncpoint_arg);
+#endif
+ Status s = NewWritableFile(fs_, fname, &writable_file, file_options_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "[%s] [JOB %d] OpenCompactionOutputFiles for table #%" PRIu64
+ " fails at NewWritableFile with status %s",
+ sub_compact->compaction->column_family_data()->GetName().c_str(),
+ job_id_, file_number, s.ToString().c_str());
+ LogFlush(db_options_.info_log);
+ EventHelpers::LogAndNotifyTableFileCreationFinished(
+ event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(),
+ fname, job_id_, FileDescriptor(), kInvalidBlobFileNumber,
+ TableProperties(), TableFileCreationReason::kCompaction, s);
+ return s;
+ }
+
+ // Try to figure out the output file's oldest ancester time.
+ int64_t temp_current_time = 0;
+ auto get_time_status = env_->GetCurrentTime(&temp_current_time);
+ // Safe to proceed even if GetCurrentTime fails. So, log and proceed.
+ if (!get_time_status.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Failed to get current time. Status: %s",
+ get_time_status.ToString().c_str());
+ }
+ uint64_t current_time = static_cast<uint64_t>(temp_current_time);
+ uint64_t oldest_ancester_time =
+ sub_compact->compaction->MinInputFileOldestAncesterTime();
+ if (oldest_ancester_time == port::kMaxUint64) {
+ oldest_ancester_time = current_time;
+ }
+
+ // Initialize a SubcompactionState::Output and add it to sub_compact->outputs
+ {
+ SubcompactionState::Output out;
+ out.meta.fd = FileDescriptor(file_number,
+ sub_compact->compaction->output_path_id(), 0);
+ out.meta.oldest_ancester_time = oldest_ancester_time;
+ out.meta.file_creation_time = current_time;
+ out.finished = false;
+ sub_compact->outputs.push_back(out);
+ }
+
+ writable_file->SetIOPriority(Env::IOPriority::IO_LOW);
+ writable_file->SetWriteLifeTimeHint(write_hint_);
+ writable_file->SetPreallocationBlockSize(static_cast<size_t>(
+ sub_compact->compaction->OutputFilePreallocationSize()));
+ const auto& listeners =
+ sub_compact->compaction->immutable_cf_options()->listeners;
+ sub_compact->outfile.reset(
+ new WritableFileWriter(std::move(writable_file), fname, file_options_,
+ env_, db_options_.statistics.get(), listeners,
+ db_options_.sst_file_checksum_func.get()));
+
+ // If the Column family flag is to only optimize filters for hits,
+ // we can skip creating filters if this is the bottommost_level where
+ // data is going to be found
+ bool skip_filters =
+ cfd->ioptions()->optimize_filters_for_hits && bottommost_level_;
+
+ sub_compact->builder.reset(NewTableBuilder(
+ *cfd->ioptions(), *(sub_compact->compaction->mutable_cf_options()),
+ cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
+ cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(),
+ sub_compact->compaction->output_compression(),
+ 0 /*sample_for_compression */,
+ sub_compact->compaction->output_compression_opts(),
+ sub_compact->compaction->output_level(), skip_filters,
+ oldest_ancester_time, 0 /* oldest_key_time */,
+ sub_compact->compaction->max_output_file_size(), current_time));
+ LogFlush(db_options_.info_log);
+ return s;
+}
+
+void CompactionJob::CleanupCompaction() {
+ for (SubcompactionState& sub_compact : compact_->sub_compact_states) {
+ const auto& sub_status = sub_compact.status;
+
+ if (sub_compact.builder != nullptr) {
+ // May happen if we get a shutdown call in the middle of compaction
+ sub_compact.builder->Abandon();
+ sub_compact.builder.reset();
+ } else {
+ assert(!sub_status.ok() || sub_compact.outfile == nullptr);
+ }
+ for (const auto& out : sub_compact.outputs) {
+ // If this file was inserted into the table cache then remove
+ // them here because this compaction was not committed.
+ if (!sub_status.ok()) {
+ TableCache::Evict(table_cache_.get(), out.meta.fd.GetNumber());
+ }
+ }
+ }
+ delete compact_;
+ compact_ = nullptr;
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
+ assert(prefix_length > 0);
+ size_t length = src.size() > prefix_length ? prefix_length : src.size();
+ dst->assign(src.data(), length);
+}
+} // namespace
+
+#endif // !ROCKSDB_LITE
+
+void CompactionJob::UpdateCompactionStats() {
+ Compaction* compaction = compact_->compaction;
+ compaction_stats_.num_input_files_in_non_output_levels = 0;
+ compaction_stats_.num_input_files_in_output_level = 0;
+ for (int input_level = 0;
+ input_level < static_cast<int>(compaction->num_input_levels());
+ ++input_level) {
+ if (compaction->level(input_level) != compaction->output_level()) {
+ UpdateCompactionInputStatsHelper(
+ &compaction_stats_.num_input_files_in_non_output_levels,
+ &compaction_stats_.bytes_read_non_output_levels, input_level);
+ } else {
+ UpdateCompactionInputStatsHelper(
+ &compaction_stats_.num_input_files_in_output_level,
+ &compaction_stats_.bytes_read_output_level, input_level);
+ }
+ }
+
+ uint64_t num_output_records = 0;
+
+ for (const auto& sub_compact : compact_->sub_compact_states) {
+ size_t num_output_files = sub_compact.outputs.size();
+ if (sub_compact.builder != nullptr) {
+ // An error occurred so ignore the last output.
+ assert(num_output_files > 0);
+ --num_output_files;
+ }
+ compaction_stats_.num_output_files += static_cast<int>(num_output_files);
+
+ num_output_records += sub_compact.num_output_records;
+
+ for (const auto& out : sub_compact.outputs) {
+ compaction_stats_.bytes_written += out.meta.fd.file_size;
+ }
+ }
+
+ if (compaction_stats_.num_input_records > num_output_records) {
+ compaction_stats_.num_dropped_records =
+ compaction_stats_.num_input_records - num_output_records;
+ }
+}
+
+void CompactionJob::UpdateCompactionInputStatsHelper(int* num_files,
+ uint64_t* bytes_read,
+ int input_level) {
+ const Compaction* compaction = compact_->compaction;
+ auto num_input_files = compaction->num_input_files(input_level);
+ *num_files += static_cast<int>(num_input_files);
+
+ for (size_t i = 0; i < num_input_files; ++i) {
+ const auto* file_meta = compaction->input(input_level, i);
+ *bytes_read += file_meta->fd.GetFileSize();
+ compaction_stats_.num_input_records +=
+ static_cast<uint64_t>(file_meta->num_entries);
+ }
+}
+
+void CompactionJob::UpdateCompactionJobStats(
+ const InternalStats::CompactionStats& stats) const {
+#ifndef ROCKSDB_LITE
+ if (compaction_job_stats_) {
+ compaction_job_stats_->elapsed_micros = stats.micros;
+
+ // input information
+ compaction_job_stats_->total_input_bytes =
+ stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
+ compaction_job_stats_->num_input_records = stats.num_input_records;
+ compaction_job_stats_->num_input_files =
+ stats.num_input_files_in_non_output_levels +
+ stats.num_input_files_in_output_level;
+ compaction_job_stats_->num_input_files_at_output_level =
+ stats.num_input_files_in_output_level;
+
+ // output information
+ compaction_job_stats_->total_output_bytes = stats.bytes_written;
+ compaction_job_stats_->num_output_records = compact_->num_output_records;
+ compaction_job_stats_->num_output_files = stats.num_output_files;
+
+ if (compact_->NumOutputFiles() > 0U) {
+ CopyPrefix(compact_->SmallestUserKey(),
+ CompactionJobStats::kMaxPrefixLength,
+ &compaction_job_stats_->smallest_output_key_prefix);
+ CopyPrefix(compact_->LargestUserKey(),
+ CompactionJobStats::kMaxPrefixLength,
+ &compaction_job_stats_->largest_output_key_prefix);
+ }
+ }
+#else
+ (void)stats;
+#endif // !ROCKSDB_LITE
+}
+
+void CompactionJob::LogCompaction() {
+ Compaction* compaction = compact_->compaction;
+ ColumnFamilyData* cfd = compaction->column_family_data();
+
+ // Let's check if anything will get logged. Don't prepare all the info if
+ // we're not logging
+ if (db_options_.info_log_level <= InfoLogLevel::INFO_LEVEL) {
+ Compaction::InputLevelSummaryBuffer inputs_summary;
+ ROCKS_LOG_INFO(
+ db_options_.info_log, "[%s] [JOB %d] Compacting %s, score %.2f",
+ cfd->GetName().c_str(), job_id_,
+ compaction->InputLevelSummary(&inputs_summary), compaction->score());
+ char scratch[2345];
+ compaction->Summary(scratch, sizeof(scratch));
+ ROCKS_LOG_INFO(db_options_.info_log, "[%s] Compaction start summary: %s\n",
+ cfd->GetName().c_str(), scratch);
+ // build event logger report
+ auto stream = event_logger_->Log();
+ stream << "job" << job_id_ << "event"
+ << "compaction_started"
+ << "compaction_reason"
+ << GetCompactionReasonString(compaction->compaction_reason());
+ for (size_t i = 0; i < compaction->num_input_levels(); ++i) {
+ stream << ("files_L" + ToString(compaction->level(i)));
+ stream.StartArray();
+ for (auto f : *compaction->inputs(i)) {
+ stream << f->fd.GetNumber();
+ }
+ stream.EndArray();
+ }
+ stream << "score" << compaction->score() << "input_data_size"
+ << compaction->CalculateTotalInputSize();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_job.h b/storage/rocksdb/rocksdb/db/compaction/compaction_job.h
new file mode 100644
index 00000000..c15f502a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_job.h
@@ -0,0 +1,198 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <deque>
+#include <functional>
+#include <limits>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/compaction/compaction_iterator.h"
+#include "db/dbformat.h"
+#include "db/flush_scheduler.h"
+#include "db/internal_stats.h"
+#include "db/job_context.h"
+#include "db/log_writer.h"
+#include "db/memtable_list.h"
+#include "db/range_del_aggregator.h"
+#include "db/version_edit.h"
+#include "db/write_controller.h"
+#include "db/write_thread.h"
+#include "logging/event_logger.h"
+#include "options/cf_options.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/compaction_job_stats.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/transaction_log.h"
+#include "table/scoped_arena_iterator.h"
+#include "util/autovector.h"
+#include "util/stop_watch.h"
+#include "util/thread_local.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena;
+class ErrorHandler;
+class MemTable;
+class SnapshotChecker;
+class TableCache;
+class Version;
+class VersionEdit;
+class VersionSet;
+
+// CompactionJob is responsible for executing the compaction. Each (manual or
+// automated) compaction corresponds to a CompactionJob object, and usually
+// goes through the stages of `Prepare()`->`Run()`->`Install()`. CompactionJob
+// will divide the compaction into subcompactions and execute them in parallel
+// if needed.
+class CompactionJob {
+ public:
+ CompactionJob(int job_id, Compaction* compaction,
+ const ImmutableDBOptions& db_options,
+ const FileOptions& file_options, VersionSet* versions,
+ const std::atomic<bool>* shutting_down,
+ const SequenceNumber preserve_deletes_seqnum,
+ LogBuffer* log_buffer, Directory* db_directory,
+ Directory* output_directory, Statistics* stats,
+ InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
+ std::vector<SequenceNumber> existing_snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ const SnapshotChecker* snapshot_checker,
+ std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
+ bool paranoid_file_checks, bool measure_io_stats,
+ const std::string& dbname,
+ CompactionJobStats* compaction_job_stats,
+ Env::Priority thread_pri,
+ const std::atomic<bool>* manual_compaction_paused = nullptr);
+
+ ~CompactionJob();
+
+ // no copy/move
+ CompactionJob(CompactionJob&& job) = delete;
+ CompactionJob(const CompactionJob& job) = delete;
+ CompactionJob& operator=(const CompactionJob& job) = delete;
+
+ // REQUIRED: mutex held
+ // Prepare for the compaction by setting up boundaries for each subcompaction
+ void Prepare();
+ // REQUIRED mutex not held
+ // Launch threads for each subcompaction and wait for them to finish. After
+ // that, verify table is usable and finally do bookkeeping to unify
+ // subcompaction results
+ Status Run();
+
+ // REQUIRED: mutex held
+ // Add compaction input/output to the current version
+ Status Install(const MutableCFOptions& mutable_cf_options);
+
+ private:
+ struct SubcompactionState;
+
+ void AggregateStatistics();
+
+ // Generates a histogram representing potential divisions of key ranges from
+ // the input. It adds the starting and/or ending keys of certain input files
+ // to the working set and then finds the approximate size of data in between
+ // each consecutive pair of slices. Then it divides these ranges into
+ // consecutive groups such that each group has a similar size.
+ void GenSubcompactionBoundaries();
+
+ // update the thread status for starting a compaction.
+ void ReportStartedCompaction(Compaction* compaction);
+ void AllocateCompactionOutputFileNumbers();
+ // Call compaction filter. Then iterate through input and compact the
+ // kv-pairs
+ void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
+
+ Status FinishCompactionOutputFile(
+ const Status& input_status, SubcompactionState* sub_compact,
+ CompactionRangeDelAggregator* range_del_agg,
+ CompactionIterationStats* range_del_out_stats,
+ const Slice* next_table_min_key = nullptr);
+ Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
+ void RecordCompactionIOStats();
+ Status OpenCompactionOutputFile(SubcompactionState* sub_compact);
+ void CleanupCompaction();
+ void UpdateCompactionJobStats(
+ const InternalStats::CompactionStats& stats) const;
+ void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
+ CompactionJobStats* compaction_job_stats = nullptr);
+
+ void UpdateCompactionStats();
+ void UpdateCompactionInputStatsHelper(
+ int* num_files, uint64_t* bytes_read, int input_level);
+
+ void LogCompaction();
+
+ int job_id_;
+
+ // CompactionJob state
+ struct CompactionState;
+ CompactionState* compact_;
+ CompactionJobStats* compaction_job_stats_;
+ InternalStats::CompactionStats compaction_stats_;
+
+ // DBImpl state
+ const std::string& dbname_;
+ const ImmutableDBOptions& db_options_;
+ const FileOptions file_options_;
+
+ Env* env_;
+ FileSystem* fs_;
+ // env_option optimized for compaction table reads
+ FileOptions file_options_for_read_;
+ VersionSet* versions_;
+ const std::atomic<bool>* shutting_down_;
+ const std::atomic<bool>* manual_compaction_paused_;
+ const SequenceNumber preserve_deletes_seqnum_;
+ LogBuffer* log_buffer_;
+ Directory* db_directory_;
+ Directory* output_directory_;
+ Statistics* stats_;
+ InstrumentedMutex* db_mutex_;
+ ErrorHandler* db_error_handler_;
+ // If there were two snapshots with seq numbers s1 and
+ // s2 and s1 < s2, and if we find two instances of a key k1 then lies
+ // entirely within s1 and s2, then the earlier version of k1 can be safely
+ // deleted because that version is not visible in any snapshot.
+ std::vector<SequenceNumber> existing_snapshots_;
+
+ // This is the earliest snapshot that could be used for write-conflict
+ // checking by a transaction. For any user-key newer than this snapshot, we
+ // should make sure not to remove evidence that a write occurred.
+ SequenceNumber earliest_write_conflict_snapshot_;
+
+ const SnapshotChecker* const snapshot_checker_;
+
+ std::shared_ptr<Cache> table_cache_;
+
+ EventLogger* event_logger_;
+
+ // Is this compaction creating a file in the bottom most level?
+ bool bottommost_level_;
+ bool paranoid_file_checks_;
+ bool measure_io_stats_;
+ // Stores the Slices that designate the boundaries for each subcompaction
+ std::vector<Slice> boundaries_;
+ // Stores the approx size of keys covered in the range of each subcompaction
+ std::vector<uint64_t> sizes_;
+ Env::WriteLifeTimeHint write_hint_;
+ Env::Priority thread_pri_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_job_stats_test.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_job_stats_test.cc
new file mode 100644
index 00000000..51a66579
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_job_stats_test.cc
@@ -0,0 +1,1043 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cinttypes>
+#include <iostream>
+#include <mutex>
+#include <queue>
+#include <set>
+#include <thread>
+#include <unordered_set>
+#include <utility>
+
+#include "db/db_impl/db_impl.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 "logging/logging.h"
+#include "memtable/hash_linklist_rep.h"
+#include "monitoring/statistics.h"
+#include "monitoring/thread_status_util.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/table.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/thread_status.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/mock_table.h"
+#include "table/plain/plain_table_factory.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/hash.h"
+#include "util/mutexlock.h"
+#include "util/rate_limiter.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+#if !defined(IOS_CROSS_COMPILE)
+#ifndef ROCKSDB_LITE
+namespace ROCKSDB_NAMESPACE {
+
+static std::string RandomString(Random* rnd, int len, double ratio) {
+ std::string r;
+ test::CompressibleString(rnd, ratio, len, &r);
+ return r;
+}
+
+std::string Key(uint64_t key, int length) {
+ const int kBufSize = 1000;
+ char buf[kBufSize];
+ if (length > kBufSize) {
+ length = kBufSize;
+ }
+ snprintf(buf, kBufSize, "%0*" PRIu64, length, key);
+ return std::string(buf);
+}
+
+class CompactionJobStatsTest : public testing::Test,
+ public testing::WithParamInterface<bool> {
+ public:
+ std::string dbname_;
+ std::string alternative_wal_dir_;
+ Env* env_;
+ DB* db_;
+ std::vector<ColumnFamilyHandle*> handles_;
+ uint32_t max_subcompactions_;
+
+ Options last_options_;
+
+ CompactionJobStatsTest() : env_(Env::Default()) {
+ env_->SetBackgroundThreads(1, Env::LOW);
+ env_->SetBackgroundThreads(1, Env::HIGH);
+ dbname_ = test::PerThreadDBPath("compaction_job_stats_test");
+ alternative_wal_dir_ = dbname_ + "/wal";
+ Options options;
+ options.create_if_missing = true;
+ max_subcompactions_ = GetParam();
+ options.max_subcompactions = max_subcompactions_;
+ auto delete_options = options;
+ delete_options.wal_dir = alternative_wal_dir_;
+ EXPECT_OK(DestroyDB(dbname_, delete_options));
+ // Destroy it for not alternative WAL dir is used.
+ EXPECT_OK(DestroyDB(dbname_, options));
+ db_ = nullptr;
+ Reopen(options);
+ }
+
+ ~CompactionJobStatsTest() override {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ Close();
+ Options options;
+ options.db_paths.emplace_back(dbname_, 0);
+ options.db_paths.emplace_back(dbname_ + "_2", 0);
+ options.db_paths.emplace_back(dbname_ + "_3", 0);
+ options.db_paths.emplace_back(dbname_ + "_4", 0);
+ EXPECT_OK(DestroyDB(dbname_, options));
+ }
+
+ // Required if inheriting from testing::WithParamInterface<>
+ static void SetUpTestCase() {}
+ static void TearDownTestCase() {}
+
+ DBImpl* dbfull() {
+ return reinterpret_cast<DBImpl*>(db_);
+ }
+
+ void CreateColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ColumnFamilyOptions cf_opts(options);
+ size_t cfi = handles_.size();
+ handles_.resize(cfi + cfs.size());
+ for (auto cf : cfs) {
+ ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]));
+ }
+ }
+
+ void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
+ const Options& options) {
+ CreateColumnFamilies(cfs, options);
+ std::vector<std::string> cfs_plus_default = cfs;
+ cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
+ ReopenWithColumnFamilies(cfs_plus_default, options);
+ }
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const std::vector<Options>& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+ }
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+ }
+
+ Status TryReopenWithColumnFamilies(
+ const std::vector<std::string>& cfs,
+ const std::vector<Options>& options) {
+ Close();
+ EXPECT_EQ(cfs.size(), options.size());
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < cfs.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
+ }
+ DBOptions db_opts = DBOptions(options[0]);
+ return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
+ }
+
+ Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ Close();
+ std::vector<Options> v_opts(cfs.size(), options);
+ return TryReopenWithColumnFamilies(cfs, v_opts);
+ }
+
+ void Reopen(const Options& options) {
+ ASSERT_OK(TryReopen(options));
+ }
+
+ void Close() {
+ for (auto h : handles_) {
+ delete h;
+ }
+ handles_.clear();
+ delete db_;
+ db_ = nullptr;
+ }
+
+ void DestroyAndReopen(const Options& options) {
+ // Destroy using last options
+ Destroy(last_options_);
+ ASSERT_OK(TryReopen(options));
+ }
+
+ void Destroy(const Options& options) {
+ Close();
+ ASSERT_OK(DestroyDB(dbname_, options));
+ }
+
+ Status ReadOnlyReopen(const Options& options) {
+ return DB::OpenForReadOnly(options, dbname_, &db_);
+ }
+
+ Status TryReopen(const Options& options) {
+ Close();
+ last_options_ = options;
+ return DB::Open(options, dbname_, &db_);
+ }
+
+ Status Flush(int cf = 0) {
+ if (cf == 0) {
+ return db_->Flush(FlushOptions());
+ } else {
+ return db_->Flush(FlushOptions(), handles_[cf]);
+ }
+ }
+
+ Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) {
+ return db_->Put(wo, k, v);
+ }
+
+ Status Put(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo = WriteOptions()) {
+ return db_->Put(wo, handles_[cf], k, v);
+ }
+
+ Status Delete(const std::string& k) {
+ return db_->Delete(WriteOptions(), k);
+ }
+
+ Status Delete(int cf, const std::string& k) {
+ return db_->Delete(WriteOptions(), handles_[cf], k);
+ }
+
+ std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ 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;
+ }
+
+ std::string Get(int cf, const std::string& k,
+ const Snapshot* snapshot = nullptr) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ options.snapshot = snapshot;
+ std::string result;
+ Status s = db_->Get(options, handles_[cf], k, &result);
+ if (s.IsNotFound()) {
+ result = "NOT_FOUND";
+ } else if (!s.ok()) {
+ result = s.ToString();
+ }
+ return result;
+ }
+
+ int NumTableFilesAtLevel(int level, int cf = 0) {
+ std::string property;
+ if (cf == 0) {
+ // default cfd
+ EXPECT_TRUE(db_->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(level), &property));
+ } else {
+ EXPECT_TRUE(db_->GetProperty(
+ handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
+ &property));
+ }
+ return atoi(property.c_str());
+ }
+
+ // Return spread of files per level
+ std::string FilesPerLevel(int cf = 0) {
+ int num_levels =
+ (cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
+ std::string result;
+ size_t last_non_zero_offset = 0;
+ for (int level = 0; level < num_levels; 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 = result.size();
+ }
+ }
+ result.resize(last_non_zero_offset);
+ return result;
+ }
+
+ uint64_t Size(const Slice& start, const Slice& limit, int cf = 0) {
+ Range r(start, limit);
+ uint64_t size;
+ if (cf == 0) {
+ db_->GetApproximateSizes(&r, 1, &size);
+ } else {
+ db_->GetApproximateSizes(handles_[1], &r, 1, &size);
+ }
+ return size;
+ }
+
+ void Compact(int cf, const Slice& start, const Slice& limit,
+ uint32_t target_path_id) {
+ CompactRangeOptions compact_options;
+ compact_options.target_path_id = target_path_id;
+ ASSERT_OK(db_->CompactRange(compact_options, handles_[cf], &start, &limit));
+ }
+
+ void Compact(int cf, const Slice& start, const Slice& limit) {
+ ASSERT_OK(
+ db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit));
+ }
+
+ void Compact(const Slice& start, const Slice& limit) {
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &limit));
+ }
+
+ void TEST_Compact(int level, int cf, const Slice& start, const Slice& limit) {
+ ASSERT_OK(dbfull()->TEST_CompactRange(level, &start, &limit, handles_[cf],
+ true /* disallow trivial move */));
+ }
+
+ // Do n memtable compactions, each of which produces an sstable
+ // covering the range [small,large].
+ void MakeTables(int n, const std::string& small, const std::string& large,
+ int cf = 0) {
+ for (int i = 0; i < n; i++) {
+ ASSERT_OK(Put(cf, small, "begin"));
+ ASSERT_OK(Put(cf, large, "end"));
+ ASSERT_OK(Flush(cf));
+ }
+ }
+
+ static void SetDeletionCompactionStats(
+ CompactionJobStats *stats, uint64_t input_deletions,
+ uint64_t expired_deletions, uint64_t records_replaced) {
+ stats->num_input_deletion_records = input_deletions;
+ stats->num_expired_deletion_records = expired_deletions;
+ stats->num_records_replaced = records_replaced;
+ }
+
+ void MakeTableWithKeyValues(
+ Random* rnd, uint64_t smallest, uint64_t largest,
+ int key_size, int value_size, uint64_t interval,
+ double ratio, int cf = 0) {
+ for (auto key = smallest; key < largest; key += interval) {
+ ASSERT_OK(Put(cf, Slice(Key(key, key_size)),
+ Slice(RandomString(rnd, value_size, ratio))));
+ }
+ ASSERT_OK(Flush(cf));
+ }
+
+ // This function behaves with the implicit understanding that two
+ // rounds of keys are inserted into the database, as per the behavior
+ // of the DeletionStatsTest.
+ void SelectivelyDeleteKeys(uint64_t smallest, uint64_t largest,
+ uint64_t interval, int deletion_interval, int key_size,
+ uint64_t cutoff_key_num, CompactionJobStats* stats, int cf = 0) {
+
+ // interval needs to be >= 2 so that deletion entries can be inserted
+ // that are intended to not result in an actual key deletion by using
+ // an offset of 1 from another existing key
+ ASSERT_GE(interval, 2);
+
+ uint64_t ctr = 1;
+ uint32_t deletions_made = 0;
+ uint32_t num_deleted = 0;
+ uint32_t num_expired = 0;
+ for (auto key = smallest; key <= largest; key += interval, ctr++) {
+ if (ctr % deletion_interval == 0) {
+ ASSERT_OK(Delete(cf, Key(key, key_size)));
+ deletions_made++;
+ num_deleted++;
+
+ if (key > cutoff_key_num) {
+ num_expired++;
+ }
+ }
+ }
+
+ // Insert some deletions for keys that don't exist that
+ // are both in and out of the key range
+ ASSERT_OK(Delete(cf, Key(smallest+1, key_size)));
+ deletions_made++;
+
+ ASSERT_OK(Delete(cf, Key(smallest-1, key_size)));
+ deletions_made++;
+ num_expired++;
+
+ ASSERT_OK(Delete(cf, Key(smallest-9, key_size)));
+ deletions_made++;
+ num_expired++;
+
+ ASSERT_OK(Flush(cf));
+ SetDeletionCompactionStats(stats, deletions_made, num_expired,
+ num_deleted);
+ }
+};
+
+// An EventListener which helps verify the compaction results in
+// test CompactionJobStatsTest.
+class CompactionJobStatsChecker : public EventListener {
+ public:
+ CompactionJobStatsChecker()
+ : compression_enabled_(false), verify_next_comp_io_stats_(false) {}
+
+ size_t NumberOfUnverifiedStats() { return expected_stats_.size(); }
+
+ void set_verify_next_comp_io_stats(bool v) { verify_next_comp_io_stats_ = v; }
+
+ // Once a compaction completed, this function will verify the returned
+ // CompactionJobInfo with the oldest CompactionJobInfo added earlier
+ // in "expected_stats_" which has not yet being used for verification.
+ void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
+ if (verify_next_comp_io_stats_) {
+ ASSERT_GT(ci.stats.file_write_nanos, 0);
+ ASSERT_GT(ci.stats.file_range_sync_nanos, 0);
+ ASSERT_GT(ci.stats.file_fsync_nanos, 0);
+ ASSERT_GT(ci.stats.file_prepare_write_nanos, 0);
+ verify_next_comp_io_stats_ = false;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (expected_stats_.size()) {
+ Verify(ci.stats, expected_stats_.front());
+ expected_stats_.pop();
+ }
+ }
+
+ // A helper function which verifies whether two CompactionJobStats
+ // match. The verification of all compaction stats are done by
+ // ASSERT_EQ except for the total input / output bytes, which we
+ // use ASSERT_GE and ASSERT_LE with a reasonable bias ---
+ // 10% in uncompressed case and 20% when compression is used.
+ virtual void Verify(const CompactionJobStats& current_stats,
+ const CompactionJobStats& stats) {
+ // time
+ ASSERT_GT(current_stats.elapsed_micros, 0U);
+
+ ASSERT_EQ(current_stats.num_input_records,
+ stats.num_input_records);
+ ASSERT_EQ(current_stats.num_input_files,
+ stats.num_input_files);
+ ASSERT_EQ(current_stats.num_input_files_at_output_level,
+ stats.num_input_files_at_output_level);
+
+ ASSERT_EQ(current_stats.num_output_records,
+ stats.num_output_records);
+ ASSERT_EQ(current_stats.num_output_files,
+ stats.num_output_files);
+
+ ASSERT_EQ(current_stats.is_manual_compaction,
+ stats.is_manual_compaction);
+
+ // file size
+ double kFileSizeBias = compression_enabled_ ? 0.20 : 0.10;
+ ASSERT_GE(current_stats.total_input_bytes * (1.00 + kFileSizeBias),
+ stats.total_input_bytes);
+ ASSERT_LE(current_stats.total_input_bytes,
+ stats.total_input_bytes * (1.00 + kFileSizeBias));
+ ASSERT_GE(current_stats.total_output_bytes * (1.00 + kFileSizeBias),
+ stats.total_output_bytes);
+ ASSERT_LE(current_stats.total_output_bytes,
+ stats.total_output_bytes * (1.00 + kFileSizeBias));
+ ASSERT_EQ(current_stats.total_input_raw_key_bytes,
+ stats.total_input_raw_key_bytes);
+ ASSERT_EQ(current_stats.total_input_raw_value_bytes,
+ stats.total_input_raw_value_bytes);
+
+ ASSERT_EQ(current_stats.num_records_replaced,
+ stats.num_records_replaced);
+
+ ASSERT_EQ(current_stats.num_corrupt_keys,
+ stats.num_corrupt_keys);
+
+ ASSERT_EQ(
+ std::string(current_stats.smallest_output_key_prefix),
+ std::string(stats.smallest_output_key_prefix));
+ ASSERT_EQ(
+ std::string(current_stats.largest_output_key_prefix),
+ std::string(stats.largest_output_key_prefix));
+ }
+
+ // Add an expected compaction stats, which will be used to
+ // verify the CompactionJobStats returned by the OnCompactionCompleted()
+ // callback.
+ void AddExpectedStats(const CompactionJobStats& stats) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ expected_stats_.push(stats);
+ }
+
+ void EnableCompression(bool flag) {
+ compression_enabled_ = flag;
+ }
+
+ bool verify_next_comp_io_stats() const { return verify_next_comp_io_stats_; }
+
+ private:
+ std::mutex mutex_;
+ std::queue<CompactionJobStats> expected_stats_;
+ bool compression_enabled_;
+ bool verify_next_comp_io_stats_;
+};
+
+// An EventListener which helps verify the compaction statistics in
+// the test DeletionStatsTest.
+class CompactionJobDeletionStatsChecker : public CompactionJobStatsChecker {
+ public:
+ // Verifies whether two CompactionJobStats match.
+ void Verify(const CompactionJobStats& current_stats,
+ const CompactionJobStats& stats) override {
+ ASSERT_EQ(
+ current_stats.num_input_deletion_records,
+ stats.num_input_deletion_records);
+ ASSERT_EQ(
+ current_stats.num_expired_deletion_records,
+ stats.num_expired_deletion_records);
+ ASSERT_EQ(
+ current_stats.num_records_replaced,
+ stats.num_records_replaced);
+
+ ASSERT_EQ(current_stats.num_corrupt_keys,
+ stats.num_corrupt_keys);
+ }
+};
+
+namespace {
+
+uint64_t EstimatedFileSize(
+ uint64_t num_records, size_t key_size, size_t value_size,
+ double compression_ratio = 1.0,
+ size_t block_size = 4096,
+ int bloom_bits_per_key = 10) {
+ const size_t kPerKeyOverhead = 8;
+ const size_t kFooterSize = 512;
+
+ uint64_t data_size =
+ static_cast<uint64_t>(
+ num_records * (key_size + value_size * compression_ratio +
+ kPerKeyOverhead));
+
+ return data_size + kFooterSize
+ + num_records * bloom_bits_per_key / 8 // filter block
+ + data_size * (key_size + 8) / block_size; // index block
+}
+
+namespace {
+
+void CopyPrefix(
+ const Slice& src, size_t prefix_length, std::string* dst) {
+ assert(prefix_length > 0);
+ size_t length = src.size() > prefix_length ? prefix_length : src.size();
+ dst->assign(src.data(), length);
+}
+
+} // namespace
+
+CompactionJobStats NewManualCompactionJobStats(
+ const std::string& smallest_key, const std::string& largest_key,
+ size_t num_input_files, size_t num_input_files_at_output_level,
+ uint64_t num_input_records, size_t key_size, size_t value_size,
+ size_t num_output_files, uint64_t num_output_records,
+ double compression_ratio, uint64_t num_records_replaced,
+ bool is_manual = true) {
+ CompactionJobStats stats;
+ stats.Reset();
+
+ stats.num_input_records = num_input_records;
+ stats.num_input_files = num_input_files;
+ stats.num_input_files_at_output_level = num_input_files_at_output_level;
+
+ stats.num_output_records = num_output_records;
+ stats.num_output_files = num_output_files;
+
+ stats.total_input_bytes =
+ EstimatedFileSize(
+ num_input_records / num_input_files,
+ key_size, value_size, compression_ratio) * num_input_files;
+ stats.total_output_bytes =
+ EstimatedFileSize(
+ num_output_records / num_output_files,
+ key_size, value_size, compression_ratio) * num_output_files;
+ stats.total_input_raw_key_bytes =
+ num_input_records * (key_size + 8);
+ stats.total_input_raw_value_bytes =
+ num_input_records * value_size;
+
+ stats.is_manual_compaction = is_manual;
+
+ stats.num_records_replaced = num_records_replaced;
+
+ CopyPrefix(smallest_key,
+ CompactionJobStats::kMaxPrefixLength,
+ &stats.smallest_output_key_prefix);
+ CopyPrefix(largest_key,
+ CompactionJobStats::kMaxPrefixLength,
+ &stats.largest_output_key_prefix);
+
+ return stats;
+}
+
+CompressionType GetAnyCompression() {
+ if (Snappy_Supported()) {
+ return kSnappyCompression;
+ } else if (Zlib_Supported()) {
+ return kZlibCompression;
+ } else if (BZip2_Supported()) {
+ return kBZip2Compression;
+ } else if (LZ4_Supported()) {
+ return kLZ4Compression;
+ } else if (XPRESS_Supported()) {
+ return kXpressCompression;
+ }
+
+ return kNoCompression;
+}
+
+} // namespace
+
+TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
+ Random rnd(301);
+ const int kBufSize = 100;
+ char buf[kBufSize];
+ uint64_t key_base = 100000000l;
+ // Note: key_base must be multiple of num_keys_per_L0_file
+ int num_keys_per_L0_file = 100;
+ const int kTestScale = 8;
+ const int kKeySize = 10;
+ const int kValueSize = 1000;
+ const double kCompressionRatio = 0.5;
+ double compression_ratio = 1.0;
+ uint64_t key_interval = key_base / num_keys_per_L0_file;
+
+ // Whenever a compaction completes, this listener will try to
+ // verify whether the returned CompactionJobStats matches
+ // what we expect. The expected CompactionJobStats is added
+ // via AddExpectedStats().
+ auto* stats_checker = new CompactionJobStatsChecker();
+ Options options;
+ options.listeners.emplace_back(stats_checker);
+ options.create_if_missing = true;
+ // just enough setting to hold off auto-compaction.
+ options.level0_file_num_compaction_trigger = kTestScale + 1;
+ options.num_levels = 3;
+ options.compression = kNoCompression;
+ options.max_subcompactions = max_subcompactions_;
+ options.bytes_per_sync = 512 * 1024;
+
+ options.report_bg_io_stats = true;
+ for (int test = 0; test < 2; ++test) {
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ // 1st Phase: generate "num_L0_files" L0 files.
+ int num_L0_files = 0;
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * kTestScale;
+ start_key += key_base) {
+ MakeTableWithKeyValues(
+ &rnd, start_key, start_key + key_base - 1,
+ kKeySize, kValueSize, key_interval,
+ compression_ratio, 1);
+ snprintf(buf, kBufSize, "%d", ++num_L0_files);
+ ASSERT_EQ(std::string(buf), FilesPerLevel(1));
+ }
+ ASSERT_EQ(ToString(num_L0_files), FilesPerLevel(1));
+
+ // 2nd Phase: perform L0 -> L1 compaction.
+ int L0_compaction_count = 6;
+ int count = 1;
+ std::string smallest_key;
+ std::string largest_key;
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * L0_compaction_count;
+ start_key += key_base, count++) {
+ smallest_key = Key(start_key, 10);
+ largest_key = Key(start_key + key_base - key_interval, 10);
+ stats_checker->AddExpectedStats(
+ NewManualCompactionJobStats(
+ smallest_key, largest_key,
+ 1, 0, num_keys_per_L0_file,
+ kKeySize, kValueSize,
+ 1, num_keys_per_L0_file,
+ compression_ratio, 0));
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
+ TEST_Compact(0, 1, smallest_key, largest_key);
+ snprintf(buf, kBufSize, "%d,%d", num_L0_files - count, count);
+ ASSERT_EQ(std::string(buf), FilesPerLevel(1));
+ }
+
+ // compact two files into one in the last L0 -> L1 compaction
+ int num_remaining_L0 = num_L0_files - L0_compaction_count;
+ smallest_key = Key(key_base * (L0_compaction_count + 1), 10);
+ largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
+ stats_checker->AddExpectedStats(
+ NewManualCompactionJobStats(
+ smallest_key, largest_key,
+ num_remaining_L0,
+ 0, num_keys_per_L0_file * num_remaining_L0,
+ kKeySize, kValueSize,
+ 1, num_keys_per_L0_file * num_remaining_L0,
+ compression_ratio, 0));
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
+ TEST_Compact(0, 1, smallest_key, largest_key);
+
+ int num_L1_files = num_L0_files - num_remaining_L0 + 1;
+ num_L0_files = 0;
+ snprintf(buf, kBufSize, "%d,%d", num_L0_files, num_L1_files);
+ ASSERT_EQ(std::string(buf), FilesPerLevel(1));
+
+ // 3rd Phase: generate sparse L0 files (wider key-range, same num of keys)
+ int sparseness = 2;
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * kTestScale;
+ start_key += key_base * sparseness) {
+ MakeTableWithKeyValues(
+ &rnd, start_key, start_key + key_base * sparseness - 1,
+ kKeySize, kValueSize,
+ key_base * sparseness / num_keys_per_L0_file,
+ compression_ratio, 1);
+ snprintf(buf, kBufSize, "%d,%d", ++num_L0_files, num_L1_files);
+ ASSERT_EQ(std::string(buf), FilesPerLevel(1));
+ }
+
+ // 4th Phase: perform L0 -> L1 compaction again, expect higher write amp
+ // When subcompactions are enabled, the number of output files increases
+ // by 1 because multiple threads are consuming the input and generating
+ // output files without coordinating to see if the output could fit into
+ // a smaller number of files like it does when it runs sequentially
+ int num_output_files = options.max_subcompactions > 1 ? 2 : 1;
+ for (uint64_t start_key = key_base;
+ num_L0_files > 1;
+ start_key += key_base * sparseness) {
+ smallest_key = Key(start_key, 10);
+ largest_key =
+ Key(start_key + key_base * sparseness - key_interval, 10);
+ stats_checker->AddExpectedStats(
+ NewManualCompactionJobStats(
+ smallest_key, largest_key,
+ 3, 2, num_keys_per_L0_file * 3,
+ kKeySize, kValueSize,
+ num_output_files,
+ num_keys_per_L0_file * 2, // 1/3 of the data will be updated.
+ compression_ratio,
+ num_keys_per_L0_file));
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
+ Compact(1, smallest_key, largest_key);
+ if (options.max_subcompactions == 1) {
+ --num_L1_files;
+ }
+ snprintf(buf, kBufSize, "%d,%d", --num_L0_files, num_L1_files);
+ ASSERT_EQ(std::string(buf), FilesPerLevel(1));
+ }
+
+ // 5th Phase: Do a full compaction, which involves in two sub-compactions.
+ // Here we expect to have 1 L0 files and 4 L1 files
+ // In the first sub-compaction, we expect L0 compaction.
+ smallest_key = Key(key_base, 10);
+ largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
+ stats_checker->AddExpectedStats(
+ NewManualCompactionJobStats(
+ Key(key_base * (kTestScale + 1 - sparseness), 10), largest_key,
+ 2, 1, num_keys_per_L0_file * 3,
+ kKeySize, kValueSize,
+ 1, num_keys_per_L0_file * 2,
+ compression_ratio,
+ num_keys_per_L0_file));
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
+ Compact(1, smallest_key, largest_key);
+
+ num_L1_files = options.max_subcompactions > 1 ? 7 : 4;
+ char L1_buf[4];
+ snprintf(L1_buf, sizeof(L1_buf), "0,%d", num_L1_files);
+ std::string L1_files(L1_buf);
+ ASSERT_EQ(L1_files, FilesPerLevel(1));
+ options.compression = GetAnyCompression();
+ if (options.compression == kNoCompression) {
+ break;
+ }
+ stats_checker->EnableCompression(true);
+ compression_ratio = kCompressionRatio;
+
+ for (int i = 0; i < 5; i++) {
+ ASSERT_OK(Put(1, Slice(Key(key_base + i, 10)),
+ Slice(RandomString(&rnd, 512 * 1024, 1))));
+ }
+
+ ASSERT_OK(Flush(1));
+ reinterpret_cast<DBImpl*>(db_)->TEST_WaitForCompact();
+
+ stats_checker->set_verify_next_comp_io_stats(true);
+ std::atomic<bool> first_prepare_write(true);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::Append:BeforePrepareWrite", [&](void* /*arg*/) {
+ if (first_prepare_write.load()) {
+ options.env->SleepForMicroseconds(3);
+ first_prepare_write.store(false);
+ }
+ });
+
+ std::atomic<bool> first_flush(true);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::Flush:BeforeAppend", [&](void* /*arg*/) {
+ if (first_flush.load()) {
+ options.env->SleepForMicroseconds(3);
+ first_flush.store(false);
+ }
+ });
+
+ std::atomic<bool> first_sync(true);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::SyncInternal:0", [&](void* /*arg*/) {
+ if (first_sync.load()) {
+ options.env->SleepForMicroseconds(3);
+ first_sync.store(false);
+ }
+ });
+
+ std::atomic<bool> first_range_sync(true);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) {
+ if (first_range_sync.load()) {
+ options.env->SleepForMicroseconds(3);
+ first_range_sync.store(false);
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Compact(1, smallest_key, largest_key);
+
+ ASSERT_TRUE(!stats_checker->verify_next_comp_io_stats());
+ ASSERT_TRUE(!first_prepare_write.load());
+ ASSERT_TRUE(!first_flush.load());
+ ASSERT_TRUE(!first_sync.load());
+ ASSERT_TRUE(!first_range_sync.load());
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ }
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
+}
+
+TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
+ Random rnd(301);
+ uint64_t key_base = 100000l;
+ // Note: key_base must be multiple of num_keys_per_L0_file
+ int num_keys_per_L0_file = 20;
+ const int kTestScale = 8; // make sure this is even
+ const int kKeySize = 10;
+ const int kValueSize = 100;
+ double compression_ratio = 1.0;
+ uint64_t key_interval = key_base / num_keys_per_L0_file;
+ uint64_t largest_key_num = key_base * (kTestScale + 1) - key_interval;
+ uint64_t cutoff_key_num = key_base * (kTestScale / 2 + 1) - key_interval;
+ const std::string smallest_key = Key(key_base - 10, kKeySize);
+ const std::string largest_key = Key(largest_key_num + 10, kKeySize);
+
+ // Whenever a compaction completes, this listener will try to
+ // verify whether the returned CompactionJobStats matches
+ // what we expect.
+ auto* stats_checker = new CompactionJobDeletionStatsChecker();
+ Options options;
+ options.listeners.emplace_back(stats_checker);
+ options.create_if_missing = true;
+ options.level0_file_num_compaction_trigger = kTestScale+1;
+ options.num_levels = 3;
+ options.compression = kNoCompression;
+ options.max_bytes_for_level_multiplier = 2;
+ options.max_subcompactions = max_subcompactions_;
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ // Stage 1: Generate several L0 files and then send them to L2 by
+ // using CompactRangeOptions and CompactRange(). These files will
+ // have a strict subset of the keys from the full key-range
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * kTestScale / 2;
+ start_key += key_base) {
+ MakeTableWithKeyValues(
+ &rnd, start_key, start_key + key_base - 1,
+ kKeySize, kValueSize, key_interval,
+ compression_ratio, 1);
+ }
+
+ CompactRangeOptions cr_options;
+ cr_options.change_level = true;
+ cr_options.target_level = 2;
+ db_->CompactRange(cr_options, handles_[1], nullptr, nullptr);
+ ASSERT_GT(NumTableFilesAtLevel(2, 1), 0);
+
+ // Stage 2: Generate files including keys from the entire key range
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * kTestScale;
+ start_key += key_base) {
+ MakeTableWithKeyValues(
+ &rnd, start_key, start_key + key_base - 1,
+ kKeySize, kValueSize, key_interval,
+ compression_ratio, 1);
+ }
+
+ // Send these L0 files to L1
+ TEST_Compact(0, 1, smallest_key, largest_key);
+ ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
+
+ // Add a new record and flush so now there is a L0 file
+ // with a value too (not just deletions from the next step)
+ ASSERT_OK(Put(1, Key(key_base-6, kKeySize), "test"));
+ ASSERT_OK(Flush(1));
+
+ // Stage 3: Generate L0 files with some deletions so now
+ // there are files with the same key range in L0, L1, and L2
+ int deletion_interval = 3;
+ CompactionJobStats first_compaction_stats;
+ SelectivelyDeleteKeys(key_base, largest_key_num,
+ key_interval, deletion_interval, kKeySize, cutoff_key_num,
+ &first_compaction_stats, 1);
+
+ stats_checker->AddExpectedStats(first_compaction_stats);
+
+ // Stage 4: Trigger compaction and verify the stats
+ TEST_Compact(0, 1, smallest_key, largest_key);
+}
+
+namespace {
+int GetUniversalCompactionInputUnits(uint32_t num_flushes) {
+ uint32_t compaction_input_units;
+ for (compaction_input_units = 1;
+ num_flushes >= compaction_input_units;
+ compaction_input_units *= 2) {
+ if ((num_flushes & compaction_input_units) != 0) {
+ return compaction_input_units > 1 ? compaction_input_units : 0;
+ }
+ }
+ return 0;
+}
+} // namespace
+
+TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
+ Random rnd(301);
+ uint64_t key_base = 100000000l;
+ // Note: key_base must be multiple of num_keys_per_L0_file
+ int num_keys_per_table = 100;
+ const uint32_t kTestScale = 6;
+ const int kKeySize = 10;
+ const int kValueSize = 900;
+ double compression_ratio = 1.0;
+ uint64_t key_interval = key_base / num_keys_per_table;
+
+ auto* stats_checker = new CompactionJobStatsChecker();
+ Options options;
+ options.listeners.emplace_back(stats_checker);
+ options.create_if_missing = true;
+ options.num_levels = 3;
+ options.compression = kNoCompression;
+ options.level0_file_num_compaction_trigger = 2;
+ options.target_file_size_base = num_keys_per_table * 1000;
+ options.compaction_style = kCompactionStyleUniversal;
+ options.compaction_options_universal.size_ratio = 1;
+ options.compaction_options_universal.max_size_amplification_percent = 1000;
+ options.max_subcompactions = max_subcompactions_;
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ // Generates the expected CompactionJobStats for each compaction
+ for (uint32_t num_flushes = 2; num_flushes <= kTestScale; num_flushes++) {
+ // Here we treat one newly flushed file as an unit.
+ //
+ // For example, if a newly flushed file is 100k, and a compaction has
+ // 4 input units, then this compaction inputs 400k.
+ uint32_t num_input_units = GetUniversalCompactionInputUnits(num_flushes);
+ if (num_input_units == 0) {
+ continue;
+ }
+ // The following statement determines the expected smallest key
+ // based on whether it is a full compaction. A full compaction only
+ // happens when the number of flushes equals to the number of compaction
+ // input runs.
+ uint64_t smallest_key =
+ (num_flushes == num_input_units) ?
+ key_base : key_base * (num_flushes - 1);
+
+ stats_checker->AddExpectedStats(
+ NewManualCompactionJobStats(
+ Key(smallest_key, 10),
+ Key(smallest_key + key_base * num_input_units - key_interval, 10),
+ num_input_units,
+ num_input_units > 2 ? num_input_units / 2 : 0,
+ num_keys_per_table * num_input_units,
+ kKeySize, kValueSize,
+ num_input_units,
+ num_keys_per_table * num_input_units,
+ 1.0, 0, false));
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 3U);
+
+ for (uint64_t start_key = key_base;
+ start_key <= key_base * kTestScale;
+ start_key += key_base) {
+ MakeTableWithKeyValues(
+ &rnd, start_key, start_key + key_base - 1,
+ kKeySize, kValueSize, key_interval,
+ compression_ratio, 1);
+ reinterpret_cast<DBImpl*>(db_)->TEST_WaitForCompact();
+ }
+ ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
+}
+
+INSTANTIATE_TEST_CASE_P(CompactionJobStatsTest, CompactionJobStatsTest,
+ ::testing::Values(1, 4));
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED, not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
+
+#else
+
+int main(int /*argc*/, char** /*argv*/) { return 0; }
+#endif // !defined(IOS_CROSS_COMPILE)
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_job_test.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_job_test.cc
new file mode 100644
index 00000000..e7b46ef9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_job_test.cc
@@ -0,0 +1,1082 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <algorithm>
+#include <array>
+#include <cinttypes>
+#include <map>
+#include <string>
+#include <tuple>
+
+#include "db/blob_index.h"
+#include "db/column_family.h"
+#include "db/compaction/compaction_job.h"
+#include "db/db_impl/db_impl.h"
+#include "db/error_handler.h"
+#include "db/version_set.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.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"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+void VerifyInitializationOfCompactionJobStats(
+ const CompactionJobStats& compaction_job_stats) {
+#if !defined(IOS_CROSS_COMPILE)
+ ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
+
+ ASSERT_EQ(compaction_job_stats.num_input_records, 0U);
+ ASSERT_EQ(compaction_job_stats.num_input_files, 0U);
+ ASSERT_EQ(compaction_job_stats.num_input_files_at_output_level, 0U);
+
+ ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
+ ASSERT_EQ(compaction_job_stats.num_output_files, 0U);
+
+ ASSERT_EQ(compaction_job_stats.is_manual_compaction, true);
+
+ ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
+ ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
+
+ ASSERT_EQ(compaction_job_stats.total_input_raw_key_bytes, 0U);
+ ASSERT_EQ(compaction_job_stats.total_input_raw_value_bytes, 0U);
+
+ ASSERT_EQ(compaction_job_stats.smallest_output_key_prefix[0], 0);
+ ASSERT_EQ(compaction_job_stats.largest_output_key_prefix[0], 0);
+
+ ASSERT_EQ(compaction_job_stats.num_records_replaced, 0U);
+
+ ASSERT_EQ(compaction_job_stats.num_input_deletion_records, 0U);
+ ASSERT_EQ(compaction_job_stats.num_expired_deletion_records, 0U);
+
+ ASSERT_EQ(compaction_job_stats.num_corrupt_keys, 0U);
+#endif // !defined(IOS_CROSS_COMPILE)
+}
+
+} // namespace
+
+// TODO(icanadi) Make it simpler once we mock out VersionSet
+class CompactionJobTest : public testing::Test {
+ public:
+ CompactionJobTest()
+ : env_(Env::Default()),
+ fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
+ dbname_(test::PerThreadDBPath("compaction_job_test")),
+ db_options_(),
+ mutable_cf_options_(cf_options_),
+ table_cache_(NewLRUCache(50000, 16)),
+ write_buffer_manager_(db_options_.db_write_buffer_size),
+ versions_(new VersionSet(dbname_, &db_options_, env_options_,
+ table_cache_.get(), &write_buffer_manager_,
+ &write_controller_,
+ /*block_cache_tracer=*/nullptr)),
+ shutting_down_(false),
+ preserve_deletes_seqnum_(0),
+ mock_table_factory_(new mock::MockTableFactory()),
+ error_handler_(nullptr, db_options_, &mutex_) {
+ EXPECT_OK(env_->CreateDirIfMissing(dbname_));
+ db_options_.env = env_;
+ db_options_.fs = fs_;
+ db_options_.db_paths.emplace_back(dbname_,
+ std::numeric_limits<uint64_t>::max());
+ }
+
+ std::string GenerateFileName(uint64_t file_number) {
+ FileMetaData meta;
+ std::vector<DbPath> db_paths;
+ db_paths.emplace_back(dbname_, std::numeric_limits<uint64_t>::max());
+ meta.fd = FileDescriptor(file_number, 0, 0);
+ return TableFileName(db_paths, meta.fd.GetNumber(), meta.fd.GetPathId());
+ }
+
+ static std::string KeyStr(const std::string& user_key,
+ const SequenceNumber seq_num, const ValueType t) {
+ return InternalKey(user_key, seq_num, t).Encode().ToString();
+ }
+
+ 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;
+ }
+
+ static std::string BlobStrTTL(uint64_t blob_file_number, uint64_t offset,
+ uint64_t size, uint64_t expiration) {
+ std::string blob_index;
+ BlobIndex::EncodeBlobTTL(&blob_index, expiration, blob_file_number, offset,
+ size, kNoCompression);
+ return blob_index;
+ }
+
+ static std::string BlobStrInlinedTTL(const Slice& value,
+ uint64_t expiration) {
+ std::string blob_index;
+ BlobIndex::EncodeInlinedTTL(&blob_index, expiration, value);
+ return blob_index;
+ }
+
+ void AddMockFile(const stl_wrappers::KVMap& contents, int level = 0) {
+ assert(contents.size() > 0);
+
+ bool first_key = true;
+ std::string smallest, largest;
+ InternalKey smallest_key, largest_key;
+ SequenceNumber smallest_seqno = kMaxSequenceNumber;
+ SequenceNumber largest_seqno = 0;
+ uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
+ for (auto kv : contents) {
+ ParsedInternalKey key;
+ std::string skey;
+ std::string value;
+ std::tie(skey, value) = kv;
+ bool parsed = ParseInternalKey(skey, &key);
+
+ smallest_seqno = std::min(smallest_seqno, key.sequence);
+ largest_seqno = std::max(largest_seqno, key.sequence);
+
+ if (first_key ||
+ cfd_->user_comparator()->Compare(key.user_key, smallest) < 0) {
+ smallest.assign(key.user_key.data(), key.user_key.size());
+ smallest_key.DecodeFrom(skey);
+ }
+ if (first_key ||
+ cfd_->user_comparator()->Compare(key.user_key, largest) > 0) {
+ largest.assign(key.user_key.data(), key.user_key.size());
+ largest_key.DecodeFrom(skey);
+ }
+
+ first_key = false;
+
+ if (parsed && key.type == kTypeBlobIndex) {
+ BlobIndex blob_index;
+ const Status s = blob_index.DecodeFrom(value);
+ if (!s.ok()) {
+ continue;
+ }
+
+ if (blob_index.IsInlined() || blob_index.HasTTL() ||
+ blob_index.file_number() == kInvalidBlobFileNumber) {
+ continue;
+ }
+
+ if (oldest_blob_file_number == kInvalidBlobFileNumber ||
+ oldest_blob_file_number > blob_index.file_number()) {
+ oldest_blob_file_number = blob_index.file_number();
+ }
+ }
+ }
+
+ uint64_t file_number = versions_->NewFileNumber();
+ EXPECT_OK(mock_table_factory_->CreateMockTable(
+ env_, GenerateFileName(file_number), std::move(contents)));
+
+ VersionEdit edit;
+ edit.AddFile(level, file_number, 0, 10, smallest_key, largest_key,
+ smallest_seqno, largest_seqno, false, oldest_blob_file_number,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+
+ mutex_.Lock();
+ versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
+ mutable_cf_options_, &edit, &mutex_);
+ mutex_.Unlock();
+ }
+
+ void SetLastSequence(const SequenceNumber sequence_number) {
+ versions_->SetLastAllocatedSequence(sequence_number + 1);
+ versions_->SetLastPublishedSequence(sequence_number + 1);
+ versions_->SetLastSequence(sequence_number + 1);
+ }
+
+ // returns expected result after compaction
+ stl_wrappers::KVMap CreateTwoFiles(bool gen_corrupted_keys) {
+ auto expected_results = mock::MakeMockFile();
+ const int kKeysPerFile = 10000;
+ const int kCorruptKeysPerFile = 200;
+ const int kMatchingKeys = kKeysPerFile / 2;
+ SequenceNumber sequence_number = 0;
+
+ auto corrupt_id = [&](int id) {
+ return gen_corrupted_keys && id > 0 && id <= kCorruptKeysPerFile;
+ };
+
+ for (int i = 0; i < 2; ++i) {
+ auto contents = mock::MakeMockFile();
+ for (int k = 0; k < kKeysPerFile; ++k) {
+ auto key = ToString(i * kMatchingKeys + k);
+ auto value = ToString(i * kKeysPerFile + k);
+ InternalKey internal_key(key, ++sequence_number, kTypeValue);
+
+ // This is how the key will look like once it's written in bottommost
+ // file
+ InternalKey bottommost_internal_key(
+ key, 0, kTypeValue);
+
+ if (corrupt_id(k)) {
+ test::CorruptKeyType(&internal_key);
+ test::CorruptKeyType(&bottommost_internal_key);
+ }
+ contents.insert({ internal_key.Encode().ToString(), value });
+ if (i == 1 || k < kMatchingKeys || corrupt_id(k - kMatchingKeys)) {
+ expected_results.insert(
+ { bottommost_internal_key.Encode().ToString(), value });
+ }
+ }
+
+ AddMockFile(contents);
+ }
+
+ SetLastSequence(sequence_number);
+
+ return expected_results;
+ }
+
+ void NewDB() {
+ DestroyDB(dbname_, Options());
+ EXPECT_OK(env_->CreateDirIfMissing(dbname_));
+ versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
+ table_cache_.get(), &write_buffer_manager_,
+ &write_controller_,
+ /*block_cache_tracer=*/nullptr));
+ compaction_job_stats_.Reset();
+ SetIdentityFile(env_, dbname_);
+
+ VersionEdit new_db;
+ if (db_options_.write_dbid_to_manifest) {
+ DBImpl* impl = new DBImpl(DBOptions(), 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::string manifest = DescriptorFileName(dbname_, 1);
+ std::unique_ptr<WritableFile> file;
+ Status s = env_->NewWritableFile(
+ manifest, &file, env_->OptimizeForManifestWrite(env_options_));
+ ASSERT_OK(s);
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), manifest, env_options_));
+ {
+ log::Writer log(std::move(file_writer), 0, false);
+ std::string record;
+ new_db.EncodeTo(&record);
+ s = log.AddRecord(record);
+ }
+ ASSERT_OK(s);
+ // Make "CURRENT" file that points to the new manifest file.
+ s = SetCurrentFile(env_, dbname_, 1, nullptr);
+
+ std::vector<ColumnFamilyDescriptor> column_families;
+ cf_options_.table_factory = mock_table_factory_;
+ cf_options_.merge_operator = merge_op_;
+ cf_options_.compaction_filter = compaction_filter_.get();
+ column_families.emplace_back(kDefaultColumnFamilyName, cf_options_);
+
+ EXPECT_OK(versions_->Recover(column_families, false));
+ cfd_ = versions_->GetColumnFamilySet()->GetDefault();
+ }
+
+ void RunCompaction(
+ const std::vector<std::vector<FileMetaData*>>& input_files,
+ const stl_wrappers::KVMap& expected_results,
+ const std::vector<SequenceNumber>& snapshots = {},
+ SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
+ int output_level = 1, bool verify = true,
+ uint64_t expected_oldest_blob_file_number = kInvalidBlobFileNumber) {
+ auto cfd = versions_->GetColumnFamilySet()->GetDefault();
+
+ size_t num_input_files = 0;
+ std::vector<CompactionInputFiles> compaction_input_files;
+ for (size_t level = 0; level < input_files.size(); level++) {
+ auto level_files = input_files[level];
+ CompactionInputFiles compaction_level;
+ compaction_level.level = static_cast<int>(level);
+ compaction_level.files.insert(compaction_level.files.end(),
+ level_files.begin(), level_files.end());
+ compaction_input_files.push_back(compaction_level);
+ num_input_files += level_files.size();
+ }
+
+ Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
+ *cfd->GetLatestMutableCFOptions(),
+ compaction_input_files, output_level, 1024 * 1024,
+ 10 * 1024 * 1024, 0, kNoCompression,
+ cfd->ioptions()->compression_opts, 0, {}, true);
+ compaction.SetInputVersion(cfd->current());
+
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
+ mutex_.Lock();
+ EventLogger event_logger(db_options_.info_log.get());
+ // TODO(yiwu) add a mock snapshot checker and add test for it.
+ SnapshotChecker* snapshot_checker = nullptr;
+ CompactionJob compaction_job(
+ 0, &compaction, db_options_, env_options_, versions_.get(),
+ &shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
+ nullptr, nullptr, &mutex_, &error_handler_, snapshots,
+ earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
+ &event_logger, false, false, dbname_, &compaction_job_stats_,
+ Env::Priority::USER);
+ VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
+
+ compaction_job.Prepare();
+ mutex_.Unlock();
+ Status s;
+ s = compaction_job.Run();
+ ASSERT_OK(s);
+ mutex_.Lock();
+ ASSERT_OK(compaction_job.Install(*cfd->GetLatestMutableCFOptions()));
+ mutex_.Unlock();
+
+ if (verify) {
+ ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
+ ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
+
+ if (expected_results.empty()) {
+ ASSERT_EQ(compaction_job_stats_.num_output_files, 0U);
+ } else {
+ ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
+ mock_table_factory_->AssertLatestFile(expected_results);
+
+ auto output_files =
+ cfd->current()->storage_info()->LevelFiles(output_level);
+ ASSERT_EQ(output_files.size(), 1);
+ ASSERT_EQ(output_files[0]->oldest_blob_file_number,
+ expected_oldest_blob_file_number);
+ }
+ }
+ }
+
+ Env* env_;
+ std::shared_ptr<FileSystem> fs_;
+ std::string dbname_;
+ EnvOptions env_options_;
+ ImmutableDBOptions db_options_;
+ ColumnFamilyOptions cf_options_;
+ MutableCFOptions mutable_cf_options_;
+ std::shared_ptr<Cache> table_cache_;
+ WriteController write_controller_;
+ WriteBufferManager write_buffer_manager_;
+ std::unique_ptr<VersionSet> versions_;
+ InstrumentedMutex mutex_;
+ std::atomic<bool> shutting_down_;
+ SequenceNumber preserve_deletes_seqnum_;
+ std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
+ CompactionJobStats compaction_job_stats_;
+ ColumnFamilyData* cfd_;
+ std::unique_ptr<CompactionFilter> compaction_filter_;
+ std::shared_ptr<MergeOperator> merge_op_;
+ ErrorHandler error_handler_;
+};
+
+TEST_F(CompactionJobTest, Simple) {
+ NewDB();
+
+ auto expected_results = CreateTwoFiles(false);
+ auto cfd = versions_->GetColumnFamilySet()->GetDefault();
+ auto files = cfd->current()->storage_info()->LevelFiles(0);
+ ASSERT_EQ(2U, files.size());
+ RunCompaction({ files }, expected_results);
+}
+
+TEST_F(CompactionJobTest, SimpleCorrupted) {
+ NewDB();
+
+ auto expected_results = CreateTwoFiles(true);
+ auto cfd = versions_->GetColumnFamilySet()->GetDefault();
+ auto files = cfd->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+ ASSERT_EQ(compaction_job_stats_.num_corrupt_keys, 400U);
+}
+
+TEST_F(CompactionJobTest, SimpleDeletion) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({{KeyStr("c", 4U, kTypeDeletion), ""},
+ {KeyStr("c", 3U, kTypeValue), "val"}});
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({{KeyStr("b", 2U, kTypeValue), "val"},
+ {KeyStr("b", 1U, kTypeValue), "val"}});
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("b", 0U, kTypeValue), "val"}});
+
+ SetLastSequence(4U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, OutputNothing) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({{KeyStr("a", 1U, kTypeValue), "val"}});
+
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({{KeyStr("a", 2U, kTypeDeletion), ""}});
+
+ AddMockFile(file2);
+
+ auto expected_results = mock::MakeMockFile();
+
+ SetLastSequence(4U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, SimpleOverwrite) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("a", 3U, kTypeValue), "val2"},
+ {KeyStr("b", 4U, kTypeValue), "val3"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({{KeyStr("a", 1U, kTypeValue), "val"},
+ {KeyStr("b", 2U, kTypeValue), "val"}});
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "val2"},
+ {KeyStr("b", 0U, kTypeValue), "val3"}});
+
+ SetLastSequence(4U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, SimpleNonLastLevel) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("a", 5U, kTypeValue), "val2"},
+ {KeyStr("b", 6U, kTypeValue), "val3"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({{KeyStr("a", 3U, kTypeValue), "val"},
+ {KeyStr("b", 4U, kTypeValue), "val"}});
+ AddMockFile(file2, 1);
+
+ auto file3 = mock::MakeMockFile({{KeyStr("a", 1U, kTypeValue), "val"},
+ {KeyStr("b", 2U, kTypeValue), "val"}});
+ AddMockFile(file3, 2);
+
+ // Because level 1 is not the last level, the sequence numbers of a and b
+ // cannot be set to 0
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 5U, kTypeValue), "val2"},
+ {KeyStr("b", 6U, kTypeValue), "val3"}});
+
+ SetLastSequence(6U);
+ auto lvl0_files = cfd_->current()->storage_info()->LevelFiles(0);
+ auto lvl1_files = cfd_->current()->storage_info()->LevelFiles(1);
+ RunCompaction({lvl0_files, lvl1_files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, SimpleMerge) {
+ merge_op_ = MergeOperators::CreateStringAppendOperator();
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("a", 5U, kTypeMerge), "5"},
+ {KeyStr("a", 4U, kTypeMerge), "4"},
+ {KeyStr("a", 3U, kTypeValue), "3"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile(
+ {{KeyStr("b", 2U, kTypeMerge), "2"}, {KeyStr("b", 1U, kTypeValue), "1"}});
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
+ {KeyStr("b", 0U, kTypeValue), "1,2"}});
+
+ SetLastSequence(5U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, NonAssocMerge) {
+ merge_op_ = MergeOperators::CreateStringAppendTESTOperator();
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("a", 5U, kTypeMerge), "5"},
+ {KeyStr("a", 4U, kTypeMerge), "4"},
+ {KeyStr("a", 3U, kTypeMerge), "3"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile(
+ {{KeyStr("b", 2U, kTypeMerge), "2"}, {KeyStr("b", 1U, kTypeMerge), "1"}});
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
+ {KeyStr("b", 0U, kTypeValue), "1,2"}});
+
+ SetLastSequence(5U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+// Filters merge operands with value 10.
+TEST_F(CompactionJobTest, MergeOperandFilter) {
+ merge_op_ = MergeOperators::CreateUInt64AddOperator();
+ compaction_filter_.reset(new test::FilterNumber(10U));
+ NewDB();
+
+ auto file1 = mock::MakeMockFile(
+ {{KeyStr("a", 5U, kTypeMerge), test::EncodeInt(5U)},
+ {KeyStr("a", 4U, kTypeMerge), test::EncodeInt(10U)}, // Filtered
+ {KeyStr("a", 3U, kTypeMerge), test::EncodeInt(3U)}});
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({
+ {KeyStr("b", 2U, kTypeMerge), test::EncodeInt(2U)},
+ {KeyStr("b", 1U, kTypeMerge), test::EncodeInt(10U)} // Filtered
+ });
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), test::EncodeInt(8U)},
+ {KeyStr("b", 0U, kTypeValue), test::EncodeInt(2U)}});
+
+ SetLastSequence(5U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, FilterSomeMergeOperands) {
+ merge_op_ = MergeOperators::CreateUInt64AddOperator();
+ compaction_filter_.reset(new test::FilterNumber(10U));
+ NewDB();
+
+ auto file1 = mock::MakeMockFile(
+ {{KeyStr("a", 5U, kTypeMerge), test::EncodeInt(5U)},
+ {KeyStr("a", 4U, kTypeMerge), test::EncodeInt(10U)}, // Filtered
+ {KeyStr("a", 3U, kTypeValue), test::EncodeInt(5U)},
+ {KeyStr("d", 8U, kTypeMerge), test::EncodeInt(10U)}});
+ AddMockFile(file1);
+
+ auto file2 =
+ mock::MakeMockFile({{KeyStr("b", 2U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 1U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("c", 2U, kTypeMerge), test::EncodeInt(3U)},
+ {KeyStr("c", 1U, kTypeValue), test::EncodeInt(7U)},
+ {KeyStr("d", 1U, kTypeValue), test::EncodeInt(6U)}});
+ AddMockFile(file2);
+
+ auto file3 =
+ mock::MakeMockFile({{KeyStr("a", 1U, kTypeMerge), test::EncodeInt(3U)}});
+ AddMockFile(file3, 2);
+
+ auto expected_results = mock::MakeMockFile({
+ {KeyStr("a", 5U, kTypeValue), test::EncodeInt(10U)},
+ {KeyStr("c", 2U, kTypeValue), test::EncodeInt(10U)},
+ {KeyStr("d", 1U, kTypeValue), test::EncodeInt(6U)}
+ // b does not appear because the operands are filtered
+ });
+
+ SetLastSequence(5U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+// Test where all operands/merge results are filtered out.
+TEST_F(CompactionJobTest, FilterAllMergeOperands) {
+ merge_op_ = MergeOperators::CreateUInt64AddOperator();
+ compaction_filter_.reset(new test::FilterNumber(10U));
+ NewDB();
+
+ auto file1 =
+ mock::MakeMockFile({{KeyStr("a", 11U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("a", 10U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("a", 9U, kTypeMerge), test::EncodeInt(10U)}});
+ AddMockFile(file1);
+
+ auto file2 =
+ mock::MakeMockFile({{KeyStr("b", 8U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 7U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 6U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 5U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 4U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 3U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 2U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("c", 2U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("c", 1U, kTypeMerge), test::EncodeInt(10U)}});
+ AddMockFile(file2);
+
+ auto file3 =
+ mock::MakeMockFile({{KeyStr("a", 2U, kTypeMerge), test::EncodeInt(10U)},
+ {KeyStr("b", 1U, kTypeMerge), test::EncodeInt(10U)}});
+ AddMockFile(file3, 2);
+
+ SetLastSequence(11U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+
+ stl_wrappers::KVMap empty_map;
+ RunCompaction({files}, empty_map);
+}
+
+TEST_F(CompactionJobTest, SimpleSingleDelete) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("a", 5U, kTypeDeletion), ""},
+ {KeyStr("b", 6U, kTypeSingleDeletion), ""},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({{KeyStr("a", 3U, kTypeValue), "val"},
+ {KeyStr("b", 4U, kTypeValue), "val"}});
+ AddMockFile(file2);
+
+ auto file3 = mock::MakeMockFile({
+ {KeyStr("a", 1U, kTypeValue), "val"},
+ });
+ AddMockFile(file3, 2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("a", 5U, kTypeDeletion), ""}});
+
+ SetLastSequence(6U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, SingleDeleteSnapshots) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("A", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("a", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("b", 21U, kTypeSingleDeletion), ""},
+ {KeyStr("c", 22U, kTypeSingleDeletion), ""},
+ {KeyStr("d", 9U, kTypeSingleDeletion), ""},
+ {KeyStr("f", 21U, kTypeSingleDeletion), ""},
+ {KeyStr("j", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("j", 9U, kTypeSingleDeletion), ""},
+ {KeyStr("k", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("k", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("l", 3U, kTypeSingleDeletion), ""},
+ {KeyStr("l", 2U, kTypeSingleDeletion), ""},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({
+ {KeyStr("0", 2U, kTypeSingleDeletion), ""},
+ {KeyStr("a", 11U, kTypeValue), "val1"},
+ {KeyStr("b", 11U, kTypeValue), "val2"},
+ {KeyStr("c", 21U, kTypeValue), "val3"},
+ {KeyStr("d", 8U, kTypeValue), "val4"},
+ {KeyStr("e", 2U, kTypeSingleDeletion), ""},
+ {KeyStr("f", 1U, kTypeValue), "val1"},
+ {KeyStr("g", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("h", 2U, kTypeSingleDeletion), ""},
+ {KeyStr("m", 12U, kTypeValue), "val1"},
+ {KeyStr("m", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("m", 8U, kTypeValue), "val2"},
+ });
+ AddMockFile(file2);
+
+ auto file3 = mock::MakeMockFile({
+ {KeyStr("A", 1U, kTypeValue), "val"},
+ {KeyStr("e", 1U, kTypeValue), "val"},
+ });
+ AddMockFile(file3, 2);
+
+ auto expected_results = mock::MakeMockFile({
+ {KeyStr("A", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("a", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("a", 11U, kTypeValue), ""},
+ {KeyStr("b", 21U, kTypeSingleDeletion), ""},
+ {KeyStr("b", 11U, kTypeValue), "val2"},
+ {KeyStr("c", 22U, kTypeSingleDeletion), ""},
+ {KeyStr("c", 21U, kTypeValue), ""},
+ {KeyStr("e", 2U, kTypeSingleDeletion), ""},
+ {KeyStr("f", 21U, kTypeSingleDeletion), ""},
+ {KeyStr("f", 1U, kTypeValue), "val1"},
+ {KeyStr("g", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("j", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("k", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("m", 12U, kTypeValue), "val1"},
+ {KeyStr("m", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("m", 8U, kTypeValue), "val2"},
+ });
+
+ SetLastSequence(22U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results, {10U, 20U}, 10U);
+}
+
+TEST_F(CompactionJobTest, EarliestWriteConflictSnapshot) {
+ NewDB();
+
+ // Test multiple snapshots where the earliest snapshot is not a
+ // write-conflic-snapshot.
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("A", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 23U, kTypeValue), "val"},
+ {KeyStr("B", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 23U, kTypeValue), "val"},
+ {KeyStr("D", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 32U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 31U, kTypeValue), "val"},
+ {KeyStr("G", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 23U, kTypeValue), "val2"},
+ {KeyStr("H", 31U, kTypeValue), "val"},
+ {KeyStr("H", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("H", 23U, kTypeValue), "val"},
+ {KeyStr("I", 35U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 34U, kTypeValue), "val2"},
+ {KeyStr("I", 33U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 32U, kTypeValue), "val3"},
+ {KeyStr("I", 31U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 34U, kTypeValue), "val"},
+ {KeyStr("J", 33U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 25U, kTypeValue), "val2"},
+ {KeyStr("J", 24U, kTypeSingleDeletion), ""},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({
+ {KeyStr("A", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 13U, kTypeValue), "val2"},
+ {KeyStr("C", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("C", 13U, kTypeValue), "val"},
+ {KeyStr("E", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("F", 4U, kTypeSingleDeletion), ""},
+ {KeyStr("F", 3U, kTypeValue), "val"},
+ {KeyStr("G", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 13U, kTypeValue), "val3"},
+ {KeyStr("H", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("H", 13U, kTypeValue), "val2"},
+ {KeyStr("I", 13U, kTypeValue), "val4"},
+ {KeyStr("I", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 11U, kTypeValue), "val5"},
+ {KeyStr("J", 15U, kTypeValue), "val3"},
+ {KeyStr("J", 14U, kTypeSingleDeletion), ""},
+ });
+ AddMockFile(file2);
+
+ auto expected_results = mock::MakeMockFile({
+ {KeyStr("A", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 23U, kTypeValue), ""},
+ {KeyStr("B", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 23U, kTypeValue), ""},
+ {KeyStr("D", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 32U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 31U, kTypeValue), ""},
+ {KeyStr("H", 31U, kTypeValue), "val"},
+ {KeyStr("I", 35U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 34U, kTypeValue), ""},
+ {KeyStr("I", 31U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 13U, kTypeValue), "val4"},
+ {KeyStr("J", 34U, kTypeValue), "val"},
+ {KeyStr("J", 33U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 25U, kTypeValue), "val2"},
+ {KeyStr("J", 24U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 15U, kTypeValue), "val3"},
+ {KeyStr("J", 14U, kTypeSingleDeletion), ""},
+ });
+
+ SetLastSequence(24U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results, {10U, 20U, 30U}, 20U);
+}
+
+TEST_F(CompactionJobTest, SingleDeleteZeroSeq) {
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("A", 10U, kTypeSingleDeletion), ""},
+ {KeyStr("dummy", 5U, kTypeValue), "val2"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({
+ {KeyStr("A", 0U, kTypeValue), "val"},
+ });
+ AddMockFile(file2);
+
+ auto expected_results = mock::MakeMockFile({
+ {KeyStr("dummy", 0U, kTypeValue), "val2"},
+ });
+
+ SetLastSequence(22U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results, {});
+}
+
+TEST_F(CompactionJobTest, MultiSingleDelete) {
+ // Tests three scenarios involving multiple single delete/put pairs:
+ //
+ // A: Put Snapshot SDel Put SDel -> Put Snapshot SDel
+ // B: Snapshot Put SDel Put SDel Snapshot -> Snapshot SDel Snapshot
+ // C: SDel Put SDel Snapshot Put -> Snapshot Put
+ // D: (Put) SDel Snapshot Put SDel -> (Put) SDel Snapshot SDel
+ // E: Put SDel Snapshot Put SDel -> Snapshot SDel
+ // F: Put SDel Put Sdel Snapshot -> removed
+ // G: Snapshot SDel Put SDel Put -> Snapshot Put SDel
+ // H: (Put) Put SDel Put Sdel Snapshot -> Removed
+ // I: (Put) Snapshot Put SDel Put SDel -> SDel
+ // J: Put Put SDel Put SDel SDel Snapshot Put Put SDel SDel Put
+ // -> Snapshot Put
+ // K: SDel SDel Put SDel Put Put Snapshot SDel Put SDel SDel Put SDel
+ // -> Snapshot Put Snapshot SDel
+ // L: SDel Put Del Put SDel Snapshot Del Put Del SDel Put SDel
+ // -> Snapshot SDel
+ // M: (Put) SDel Put Del Put SDel Snapshot Put Del SDel Put SDel Del
+ // -> SDel Snapshot Del
+ NewDB();
+
+ auto file1 = mock::MakeMockFile({
+ {KeyStr("A", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 13U, kTypeValue), "val5"},
+ {KeyStr("A", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 13U, kTypeValue), "val2"},
+ {KeyStr("C", 14U, kTypeValue), "val3"},
+ {KeyStr("D", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("D", 11U, kTypeValue), "val4"},
+ {KeyStr("G", 15U, kTypeValue), "val"},
+ {KeyStr("G", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("G", 13U, kTypeValue), "val"},
+ {KeyStr("I", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 13U, kTypeValue), "val"},
+ {KeyStr("J", 15U, kTypeValue), "val"},
+ {KeyStr("J", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 13U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 12U, kTypeValue), "val"},
+ {KeyStr("J", 11U, kTypeValue), "val"},
+ {KeyStr("K", 16U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 15U, kTypeValue), "val1"},
+ {KeyStr("K", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 13U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 12U, kTypeValue), "val2"},
+ {KeyStr("K", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 16U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 15U, kTypeValue), "val"},
+ {KeyStr("L", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 13U, kTypeDeletion), ""},
+ {KeyStr("L", 12U, kTypeValue), "val"},
+ {KeyStr("L", 11U, kTypeDeletion), ""},
+ {KeyStr("M", 16U, kTypeDeletion), ""},
+ {KeyStr("M", 15U, kTypeSingleDeletion), ""},
+ {KeyStr("M", 14U, kTypeValue), "val"},
+ {KeyStr("M", 13U, kTypeSingleDeletion), ""},
+ {KeyStr("M", 12U, kTypeDeletion), ""},
+ {KeyStr("M", 11U, kTypeValue), "val"},
+ });
+ AddMockFile(file1);
+
+ auto file2 = mock::MakeMockFile({
+ {KeyStr("A", 10U, kTypeValue), "val"},
+ {KeyStr("B", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 11U, kTypeValue), "val2"},
+ {KeyStr("C", 10U, kTypeSingleDeletion), ""},
+ {KeyStr("C", 9U, kTypeValue), "val6"},
+ {KeyStr("C", 8U, kTypeSingleDeletion), ""},
+ {KeyStr("D", 10U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 11U, kTypeValue), "val"},
+ {KeyStr("E", 5U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 4U, kTypeValue), "val"},
+ {KeyStr("F", 6U, kTypeSingleDeletion), ""},
+ {KeyStr("F", 5U, kTypeValue), "val"},
+ {KeyStr("F", 4U, kTypeSingleDeletion), ""},
+ {KeyStr("F", 3U, kTypeValue), "val"},
+ {KeyStr("G", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("H", 6U, kTypeSingleDeletion), ""},
+ {KeyStr("H", 5U, kTypeValue), "val"},
+ {KeyStr("H", 4U, kTypeSingleDeletion), ""},
+ {KeyStr("H", 3U, kTypeValue), "val"},
+ {KeyStr("I", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 11U, kTypeValue), "val"},
+ {KeyStr("J", 6U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 5U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 4U, kTypeValue), "val"},
+ {KeyStr("J", 3U, kTypeSingleDeletion), ""},
+ {KeyStr("J", 2U, kTypeValue), "val"},
+ {KeyStr("K", 8U, kTypeValue), "val3"},
+ {KeyStr("K", 7U, kTypeValue), "val4"},
+ {KeyStr("K", 6U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 5U, kTypeValue), "val5"},
+ {KeyStr("K", 2U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 1U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 5U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 4U, kTypeValue), "val"},
+ {KeyStr("L", 3U, kTypeDeletion), ""},
+ {KeyStr("L", 2U, kTypeValue), "val"},
+ {KeyStr("L", 1U, kTypeSingleDeletion), ""},
+ {KeyStr("M", 10U, kTypeSingleDeletion), ""},
+ {KeyStr("M", 7U, kTypeValue), "val"},
+ {KeyStr("M", 5U, kTypeDeletion), ""},
+ {KeyStr("M", 4U, kTypeValue), "val"},
+ {KeyStr("M", 3U, kTypeSingleDeletion), ""},
+ });
+ AddMockFile(file2);
+
+ auto file3 = mock::MakeMockFile({
+ {KeyStr("D", 1U, kTypeValue), "val"},
+ {KeyStr("H", 1U, kTypeValue), "val"},
+ {KeyStr("I", 2U, kTypeValue), "val"},
+ });
+ AddMockFile(file3, 2);
+
+ auto file4 = mock::MakeMockFile({
+ {KeyStr("M", 1U, kTypeValue), "val"},
+ });
+ AddMockFile(file4, 2);
+
+ auto expected_results =
+ mock::MakeMockFile({{KeyStr("A", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 13U, kTypeValue), ""},
+ {KeyStr("A", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("A", 10U, kTypeValue), "val"},
+ {KeyStr("B", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("B", 13U, kTypeValue), ""},
+ {KeyStr("C", 14U, kTypeValue), "val3"},
+ {KeyStr("D", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("D", 11U, kTypeValue), ""},
+ {KeyStr("D", 10U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("E", 11U, kTypeValue), ""},
+ {KeyStr("G", 15U, kTypeValue), "val"},
+ {KeyStr("G", 12U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 14U, kTypeSingleDeletion), ""},
+ {KeyStr("I", 13U, kTypeValue), ""},
+ {KeyStr("J", 15U, kTypeValue), "val"},
+ {KeyStr("K", 16U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 15U, kTypeValue), ""},
+ {KeyStr("K", 11U, kTypeSingleDeletion), ""},
+ {KeyStr("K", 8U, kTypeValue), "val3"},
+ {KeyStr("L", 16U, kTypeSingleDeletion), ""},
+ {KeyStr("L", 15U, kTypeValue), ""},
+ {KeyStr("M", 16U, kTypeDeletion), ""},
+ {KeyStr("M", 3U, kTypeSingleDeletion), ""}});
+
+ SetLastSequence(22U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results, {10U}, 10U);
+}
+
+// This test documents the behavior where a corrupt key follows a deletion or a
+// single deletion and the (single) deletion gets removed while the corrupt key
+// gets written out. TODO(noetzli): We probably want a better way to treat
+// corrupt keys.
+TEST_F(CompactionJobTest, CorruptionAfterDeletion) {
+ NewDB();
+
+ auto file1 =
+ mock::MakeMockFile({{test::KeyStr("A", 6U, kTypeValue), "val3"},
+ {test::KeyStr("a", 5U, kTypeDeletion), ""},
+ {test::KeyStr("a", 4U, kTypeValue, true), "val"}});
+ AddMockFile(file1);
+
+ auto file2 =
+ mock::MakeMockFile({{test::KeyStr("b", 3U, kTypeSingleDeletion), ""},
+ {test::KeyStr("b", 2U, kTypeValue, true), "val"},
+ {test::KeyStr("c", 1U, kTypeValue), "val2"}});
+ AddMockFile(file2);
+
+ auto expected_results =
+ mock::MakeMockFile({{test::KeyStr("A", 0U, kTypeValue), "val3"},
+ {test::KeyStr("a", 0U, kTypeValue, true), "val"},
+ {test::KeyStr("b", 0U, kTypeValue, true), "val"},
+ {test::KeyStr("c", 0U, kTypeValue), "val2"}});
+
+ SetLastSequence(6U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results);
+}
+
+TEST_F(CompactionJobTest, OldestBlobFileNumber) {
+ NewDB();
+
+ // Note: blob1 is inlined TTL, so it will not be considered for the purposes
+ // of identifying the oldest referenced blob file. Similarly, blob6 will be
+ // ignored because it has TTL and hence refers to a TTL blob file.
+ const stl_wrappers::KVMap::value_type blob1(
+ KeyStr("a", 1U, kTypeBlobIndex), BlobStrInlinedTTL("foo", 1234567890ULL));
+ const stl_wrappers::KVMap::value_type blob2(KeyStr("b", 2U, kTypeBlobIndex),
+ BlobStr(59, 123456, 999));
+ const stl_wrappers::KVMap::value_type blob3(KeyStr("c", 3U, kTypeBlobIndex),
+ BlobStr(138, 1000, 1 << 8));
+ auto file1 = mock::MakeMockFile({blob1, blob2, blob3});
+ AddMockFile(file1);
+
+ const stl_wrappers::KVMap::value_type blob4(KeyStr("d", 4U, kTypeBlobIndex),
+ BlobStr(199, 3 << 10, 1 << 20));
+ const stl_wrappers::KVMap::value_type blob5(KeyStr("e", 5U, kTypeBlobIndex),
+ BlobStr(19, 6789, 333));
+ const stl_wrappers::KVMap::value_type blob6(
+ KeyStr("f", 6U, kTypeBlobIndex),
+ BlobStrTTL(5, 2048, 1 << 7, 1234567890ULL));
+ auto file2 = mock::MakeMockFile({blob4, blob5, blob6});
+ AddMockFile(file2);
+
+ const stl_wrappers::KVMap::value_type expected_blob1(
+ KeyStr("a", 0U, kTypeBlobIndex), blob1.second);
+ const stl_wrappers::KVMap::value_type expected_blob2(
+ KeyStr("b", 0U, kTypeBlobIndex), blob2.second);
+ const stl_wrappers::KVMap::value_type expected_blob3(
+ KeyStr("c", 0U, kTypeBlobIndex), blob3.second);
+ const stl_wrappers::KVMap::value_type expected_blob4(
+ KeyStr("d", 0U, kTypeBlobIndex), blob4.second);
+ const stl_wrappers::KVMap::value_type expected_blob5(
+ KeyStr("e", 0U, kTypeBlobIndex), blob5.second);
+ const stl_wrappers::KVMap::value_type expected_blob6(
+ KeyStr("f", 0U, kTypeBlobIndex), blob6.second);
+ auto expected_results =
+ mock::MakeMockFile({expected_blob1, expected_blob2, expected_blob3,
+ expected_blob4, expected_blob5, expected_blob6});
+
+ SetLastSequence(6U);
+ auto files = cfd_->current()->storage_info()->LevelFiles(0);
+ RunCompaction({files}, expected_results, std::vector<SequenceNumber>(),
+ kMaxSequenceNumber, /* output_level */ 1, /* verify */ true,
+ /* expected_oldest_blob_file_number */ 19);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as CompactionJobStats is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_picker.cc
new file mode 100644
index 00000000..4355d4b9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker.cc
@@ -0,0 +1,1131 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_picker.h"
+
+#include <cinttypes>
+#include <limits>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+#include "db/column_family.h"
+#include "file/filename.h"
+#include "logging/log_buffer.h"
+#include "monitoring/statistics.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+uint64_t TotalCompensatedFileSize(const std::vector<FileMetaData*>& files) {
+ uint64_t sum = 0;
+ for (size_t i = 0; i < files.size() && files[i]; i++) {
+ sum += files[i]->compensated_file_size;
+ }
+ return sum;
+}
+} // anonymous namespace
+
+bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
+ size_t min_files_to_compact,
+ uint64_t max_compact_bytes_per_del_file,
+ uint64_t max_compaction_bytes,
+ CompactionInputFiles* comp_inputs,
+ SequenceNumber earliest_mem_seqno) {
+ // Do not pick ingested file when there is at least one memtable not flushed
+ // which of seqno is overlap with the sst.
+ TEST_SYNC_POINT("FindIntraL0Compaction");
+ size_t start = 0;
+ for (; start < level_files.size(); start++) {
+ if (level_files[start]->being_compacted) {
+ return false;
+ }
+ // If there is no data in memtable, the earliest sequence number would the
+ // largest sequence number in last memtable.
+ // Because all files are sorted in descending order by largest_seqno, so we
+ // only need to check the first one.
+ if (level_files[start]->fd.largest_seqno <= earliest_mem_seqno) {
+ break;
+ }
+ }
+ if (start >= level_files.size()) {
+ return false;
+ }
+ size_t compact_bytes = static_cast<size_t>(level_files[start]->fd.file_size);
+ uint64_t compensated_compact_bytes =
+ level_files[start]->compensated_file_size;
+ size_t compact_bytes_per_del_file = port::kMaxSizet;
+ // Compaction range will be [start, limit).
+ size_t limit;
+ // Pull in files until the amount of compaction work per deleted file begins
+ // increasing or maximum total compaction size is reached.
+ size_t new_compact_bytes_per_del_file = 0;
+ for (limit = start + 1; limit < level_files.size(); ++limit) {
+ compact_bytes += static_cast<size_t>(level_files[limit]->fd.file_size);
+ compensated_compact_bytes += level_files[limit]->compensated_file_size;
+ new_compact_bytes_per_del_file = compact_bytes / (limit - start);
+ if (level_files[limit]->being_compacted ||
+ new_compact_bytes_per_del_file > compact_bytes_per_del_file ||
+ compensated_compact_bytes > max_compaction_bytes) {
+ break;
+ }
+ compact_bytes_per_del_file = new_compact_bytes_per_del_file;
+ }
+
+ if ((limit - start) >= min_files_to_compact &&
+ compact_bytes_per_del_file < max_compact_bytes_per_del_file) {
+ assert(comp_inputs != nullptr);
+ comp_inputs->level = 0;
+ for (size_t i = start; i < limit; ++i) {
+ comp_inputs->files.push_back(level_files[i]);
+ }
+ return true;
+ }
+ return false;
+}
+
+// Determine compression type, based on user options, level of the output
+// file and whether compression is disabled.
+// If enable_compression is false, then compression is always disabled no
+// matter what the values of the other two parameters are.
+// Otherwise, the compression type is determined based on options and level.
+CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
+ const VersionStorageInfo* vstorage,
+ const MutableCFOptions& mutable_cf_options,
+ int level, int base_level,
+ const bool enable_compression) {
+ if (!enable_compression) {
+ // disable compression
+ return kNoCompression;
+ }
+
+ // If bottommost_compression is set and we are compacting to the
+ // bottommost level then we should use it.
+ if (ioptions.bottommost_compression != kDisableCompressionOption &&
+ level >= (vstorage->num_non_empty_levels() - 1)) {
+ return ioptions.bottommost_compression;
+ }
+ // If the user has specified a different compression level for each level,
+ // then pick the compression for that level.
+ if (!ioptions.compression_per_level.empty()) {
+ assert(level == 0 || level >= base_level);
+ int idx = (level == 0) ? 0 : level - base_level + 1;
+
+ const int n = static_cast<int>(ioptions.compression_per_level.size()) - 1;
+ // It is possible for level_ to be -1; in that case, we use level
+ // 0's compression. This occurs mostly in backwards compatibility
+ // situations when the builder doesn't know what level the file
+ // belongs to. Likewise, if level is beyond the end of the
+ // specified compression levels, use the last value.
+ return ioptions.compression_per_level[std::max(0, std::min(idx, n))];
+ } else {
+ return mutable_cf_options.compression;
+ }
+}
+
+CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
+ const VersionStorageInfo* vstorage,
+ int level,
+ const bool enable_compression) {
+ if (!enable_compression) {
+ return ioptions.compression_opts;
+ }
+ // If bottommost_compression is set and we are compacting to the
+ // bottommost level then we should use the specified compression options
+ // for the bottmomost_compression.
+ if (ioptions.bottommost_compression != kDisableCompressionOption &&
+ level >= (vstorage->num_non_empty_levels() - 1) &&
+ ioptions.bottommost_compression_opts.enabled) {
+ return ioptions.bottommost_compression_opts;
+ }
+ return ioptions.compression_opts;
+}
+
+CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp)
+ : ioptions_(ioptions), icmp_(icmp) {}
+
+CompactionPicker::~CompactionPicker() {}
+
+// Delete this compaction from the list of running compactions.
+void CompactionPicker::ReleaseCompactionFiles(Compaction* c, Status status) {
+ UnregisterCompaction(c);
+ if (!status.ok()) {
+ c->ResetNextCompactionIndex();
+ }
+}
+
+void CompactionPicker::GetRange(const CompactionInputFiles& inputs,
+ InternalKey* smallest,
+ InternalKey* largest) const {
+ const int level = inputs.level;
+ assert(!inputs.empty());
+ smallest->Clear();
+ largest->Clear();
+
+ if (level == 0) {
+ for (size_t i = 0; i < inputs.size(); i++) {
+ FileMetaData* f = inputs[i];
+ if (i == 0) {
+ *smallest = f->smallest;
+ *largest = f->largest;
+ } else {
+ if (icmp_->Compare(f->smallest, *smallest) < 0) {
+ *smallest = f->smallest;
+ }
+ if (icmp_->Compare(f->largest, *largest) > 0) {
+ *largest = f->largest;
+ }
+ }
+ }
+ } else {
+ *smallest = inputs[0]->smallest;
+ *largest = inputs[inputs.size() - 1]->largest;
+ }
+}
+
+void CompactionPicker::GetRange(const CompactionInputFiles& inputs1,
+ const CompactionInputFiles& inputs2,
+ InternalKey* smallest,
+ InternalKey* largest) const {
+ assert(!inputs1.empty() || !inputs2.empty());
+ if (inputs1.empty()) {
+ GetRange(inputs2, smallest, largest);
+ } else if (inputs2.empty()) {
+ GetRange(inputs1, smallest, largest);
+ } else {
+ InternalKey smallest1, smallest2, largest1, largest2;
+ GetRange(inputs1, &smallest1, &largest1);
+ GetRange(inputs2, &smallest2, &largest2);
+ *smallest =
+ icmp_->Compare(smallest1, smallest2) < 0 ? smallest1 : smallest2;
+ *largest = icmp_->Compare(largest1, largest2) < 0 ? largest2 : largest1;
+ }
+}
+
+void CompactionPicker::GetRange(const std::vector<CompactionInputFiles>& inputs,
+ InternalKey* smallest,
+ InternalKey* largest) const {
+ InternalKey current_smallest;
+ InternalKey current_largest;
+ bool initialized = false;
+ for (const auto& in : inputs) {
+ if (in.empty()) {
+ continue;
+ }
+ GetRange(in, &current_smallest, &current_largest);
+ if (!initialized) {
+ *smallest = current_smallest;
+ *largest = current_largest;
+ initialized = true;
+ } else {
+ if (icmp_->Compare(current_smallest, *smallest) < 0) {
+ *smallest = current_smallest;
+ }
+ if (icmp_->Compare(current_largest, *largest) > 0) {
+ *largest = current_largest;
+ }
+ }
+ }
+ assert(initialized);
+}
+
+bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
+ VersionStorageInfo* vstorage,
+ CompactionInputFiles* inputs,
+ InternalKey** next_smallest) {
+ // This isn't good compaction
+ assert(!inputs->empty());
+
+ const int level = inputs->level;
+ // GetOverlappingInputs will always do the right thing for level-0.
+ // So we don't need to do any expansion if level == 0.
+ if (level == 0) {
+ return true;
+ }
+
+ InternalKey smallest, largest;
+
+ // Keep expanding inputs until we are sure that there is a "clean cut"
+ // boundary between the files in input and the surrounding files.
+ // This will ensure that no parts of a key are lost during compaction.
+ int hint_index = -1;
+ size_t old_size;
+ do {
+ old_size = inputs->size();
+ GetRange(*inputs, &smallest, &largest);
+ inputs->clear();
+ vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
+ hint_index, &hint_index, true,
+ next_smallest);
+ } while (inputs->size() > old_size);
+
+ // we started off with inputs non-empty and the previous loop only grew
+ // inputs. thus, inputs should be non-empty here
+ assert(!inputs->empty());
+
+ // If, after the expansion, there are files that are already under
+ // compaction, then we must drop/cancel this compaction.
+ if (AreFilesInCompaction(inputs->files)) {
+ return false;
+ }
+ return true;
+}
+
+bool CompactionPicker::RangeOverlapWithCompaction(
+ const Slice& smallest_user_key, const Slice& largest_user_key,
+ int level) const {
+ const Comparator* ucmp = icmp_->user_comparator();
+ for (Compaction* c : compactions_in_progress_) {
+ if (c->output_level() == level &&
+ ucmp->Compare(smallest_user_key, c->GetLargestUserKey()) <= 0 &&
+ ucmp->Compare(largest_user_key, c->GetSmallestUserKey()) >= 0) {
+ // Overlap
+ return true;
+ }
+ }
+ // Did not overlap with any running compaction in level `level`
+ return false;
+}
+
+bool CompactionPicker::FilesRangeOverlapWithCompaction(
+ const std::vector<CompactionInputFiles>& inputs, int level) const {
+ bool is_empty = true;
+ for (auto& in : inputs) {
+ if (!in.empty()) {
+ is_empty = false;
+ break;
+ }
+ }
+ if (is_empty) {
+ // No files in inputs
+ return false;
+ }
+
+ InternalKey smallest, largest;
+ GetRange(inputs, &smallest, &largest);
+ return RangeOverlapWithCompaction(smallest.user_key(), largest.user_key(),
+ level);
+}
+
+// Returns true if any one of specified files are being compacted
+bool CompactionPicker::AreFilesInCompaction(
+ const std::vector<FileMetaData*>& files) {
+ for (size_t i = 0; i < files.size(); i++) {
+ if (files[i]->being_compacted) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Compaction* CompactionPicker::CompactFiles(
+ const CompactionOptions& compact_options,
+ const std::vector<CompactionInputFiles>& input_files, int output_level,
+ VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
+ uint32_t output_path_id) {
+ assert(input_files.size());
+ // This compaction output should not overlap with a running compaction as
+ // `SanitizeCompactionInputFiles` should've checked earlier and db mutex
+ // shouldn't have been released since.
+ assert(!FilesRangeOverlapWithCompaction(input_files, output_level));
+
+ CompressionType compression_type;
+ if (compact_options.compression == kDisableCompressionOption) {
+ int base_level;
+ if (ioptions_.compaction_style == kCompactionStyleLevel) {
+ base_level = vstorage->base_level();
+ } else {
+ base_level = 1;
+ }
+ compression_type =
+ GetCompressionType(ioptions_, vstorage, mutable_cf_options,
+ output_level, base_level);
+ } else {
+ // TODO(ajkr): `CompactionOptions` offers configurable `CompressionType`
+ // without configurable `CompressionOptions`, which is inconsistent.
+ compression_type = compact_options.compression;
+ }
+ auto c = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, input_files, output_level,
+ compact_options.output_file_size_limit,
+ mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
+ GetCompressionOptions(ioptions_, vstorage, output_level),
+ compact_options.max_subcompactions,
+ /* grandparents */ {}, true);
+ RegisterCompaction(c);
+ return c;
+}
+
+Status CompactionPicker::GetCompactionInputsFromFileNumbers(
+ std::vector<CompactionInputFiles>* input_files,
+ std::unordered_set<uint64_t>* input_set, const VersionStorageInfo* vstorage,
+ const CompactionOptions& /*compact_options*/) const {
+ if (input_set->size() == 0U) {
+ return Status::InvalidArgument(
+ "Compaction must include at least one file.");
+ }
+ assert(input_files);
+
+ std::vector<CompactionInputFiles> matched_input_files;
+ matched_input_files.resize(vstorage->num_levels());
+ int first_non_empty_level = -1;
+ int last_non_empty_level = -1;
+ // TODO(yhchiang): use a lazy-initialized mapping from
+ // file_number to FileMetaData in Version.
+ for (int level = 0; level < vstorage->num_levels(); ++level) {
+ for (auto file : vstorage->LevelFiles(level)) {
+ auto iter = input_set->find(file->fd.GetNumber());
+ if (iter != input_set->end()) {
+ matched_input_files[level].files.push_back(file);
+ input_set->erase(iter);
+ last_non_empty_level = level;
+ if (first_non_empty_level == -1) {
+ first_non_empty_level = level;
+ }
+ }
+ }
+ }
+
+ if (!input_set->empty()) {
+ std::string message(
+ "Cannot find matched SST files for the following file numbers:");
+ for (auto fn : *input_set) {
+ message += " ";
+ message += ToString(fn);
+ }
+ return Status::InvalidArgument(message);
+ }
+
+ for (int level = first_non_empty_level; level <= last_non_empty_level;
+ ++level) {
+ matched_input_files[level].level = level;
+ input_files->emplace_back(std::move(matched_input_files[level]));
+ }
+
+ return Status::OK();
+}
+
+// Returns true if any one of the parent files are being compacted
+bool CompactionPicker::IsRangeInCompaction(VersionStorageInfo* vstorage,
+ const InternalKey* smallest,
+ const InternalKey* largest,
+ int level, int* level_index) {
+ std::vector<FileMetaData*> inputs;
+ assert(level < NumberLevels());
+
+ vstorage->GetOverlappingInputs(level, smallest, largest, &inputs,
+ level_index ? *level_index : 0, level_index);
+ return AreFilesInCompaction(inputs);
+}
+
+// Populates the set of inputs of all other levels that overlap with the
+// start level.
+// Now we assume all levels except start level and output level are empty.
+// Will also attempt to expand "start level" if that doesn't expand
+// "output level" or cause "level" to include a file for compaction that has an
+// overlapping user-key with another file.
+// REQUIRES: input_level and output_level are different
+// REQUIRES: inputs->empty() == false
+// Returns false if files on parent level are currently in compaction, which
+// means that we can't compact them
+bool CompactionPicker::SetupOtherInputs(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
+ CompactionInputFiles* output_level_inputs, int* parent_index,
+ int base_index) {
+ assert(!inputs->empty());
+ assert(output_level_inputs->empty());
+ const int input_level = inputs->level;
+ const int output_level = output_level_inputs->level;
+ if (input_level == output_level) {
+ // no possibility of conflict
+ return true;
+ }
+
+ // For now, we only support merging two levels, start level and output level.
+ // We need to assert other levels are empty.
+ for (int l = input_level + 1; l < output_level; l++) {
+ assert(vstorage->NumLevelFiles(l) == 0);
+ }
+
+ InternalKey smallest, largest;
+
+ // Get the range one last time.
+ GetRange(*inputs, &smallest, &largest);
+
+ // Populate the set of next-level files (inputs_GetOutputLevelInputs()) to
+ // include in compaction
+ vstorage->GetOverlappingInputs(output_level, &smallest, &largest,
+ &output_level_inputs->files, *parent_index,
+ parent_index);
+ if (AreFilesInCompaction(output_level_inputs->files)) {
+ return false;
+ }
+ if (!output_level_inputs->empty()) {
+ if (!ExpandInputsToCleanCut(cf_name, vstorage, output_level_inputs)) {
+ return false;
+ }
+ }
+
+ // See if we can further grow the number of inputs in "level" without
+ // changing the number of "level+1" files we pick up. We also choose NOT
+ // to expand if this would cause "level" to include some entries for some
+ // user key, while excluding other entries for the same user key. This
+ // can happen when one user key spans multiple files.
+ if (!output_level_inputs->empty()) {
+ const uint64_t limit = mutable_cf_options.max_compaction_bytes;
+ const uint64_t output_level_inputs_size =
+ TotalCompensatedFileSize(output_level_inputs->files);
+ const uint64_t inputs_size = TotalCompensatedFileSize(inputs->files);
+ bool expand_inputs = false;
+
+ CompactionInputFiles expanded_inputs;
+ expanded_inputs.level = input_level;
+ // Get closed interval of output level
+ InternalKey all_start, all_limit;
+ GetRange(*inputs, *output_level_inputs, &all_start, &all_limit);
+ bool try_overlapping_inputs = true;
+ vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
+ &expanded_inputs.files, base_index, nullptr);
+ uint64_t expanded_inputs_size =
+ TotalCompensatedFileSize(expanded_inputs.files);
+ if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
+ try_overlapping_inputs = false;
+ }
+ if (try_overlapping_inputs && expanded_inputs.size() > inputs->size() &&
+ output_level_inputs_size + expanded_inputs_size < limit &&
+ !AreFilesInCompaction(expanded_inputs.files)) {
+ InternalKey new_start, new_limit;
+ GetRange(expanded_inputs, &new_start, &new_limit);
+ CompactionInputFiles expanded_output_level_inputs;
+ expanded_output_level_inputs.level = output_level;
+ vstorage->GetOverlappingInputs(output_level, &new_start, &new_limit,
+ &expanded_output_level_inputs.files,
+ *parent_index, parent_index);
+ assert(!expanded_output_level_inputs.empty());
+ if (!AreFilesInCompaction(expanded_output_level_inputs.files) &&
+ ExpandInputsToCleanCut(cf_name, vstorage,
+ &expanded_output_level_inputs) &&
+ expanded_output_level_inputs.size() == output_level_inputs->size()) {
+ expand_inputs = true;
+ }
+ }
+ if (!expand_inputs) {
+ vstorage->GetCleanInputsWithinInterval(input_level, &all_start,
+ &all_limit, &expanded_inputs.files,
+ base_index, nullptr);
+ expanded_inputs_size = TotalCompensatedFileSize(expanded_inputs.files);
+ if (expanded_inputs.size() > inputs->size() &&
+ output_level_inputs_size + expanded_inputs_size < limit &&
+ !AreFilesInCompaction(expanded_inputs.files)) {
+ expand_inputs = true;
+ }
+ }
+ if (expand_inputs) {
+ ROCKS_LOG_INFO(ioptions_.info_log,
+ "[%s] Expanding@%d %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt
+ "(%" PRIu64 "+%" PRIu64 " bytes) to %" ROCKSDB_PRIszt
+ "+%" ROCKSDB_PRIszt " (%" PRIu64 "+%" PRIu64 " bytes)\n",
+ cf_name.c_str(), input_level, inputs->size(),
+ output_level_inputs->size(), inputs_size,
+ output_level_inputs_size, expanded_inputs.size(),
+ output_level_inputs->size(), expanded_inputs_size,
+ output_level_inputs_size);
+ inputs->files = expanded_inputs.files;
+ }
+ }
+ return true;
+}
+
+void CompactionPicker::GetGrandparents(
+ VersionStorageInfo* vstorage, const CompactionInputFiles& inputs,
+ const CompactionInputFiles& output_level_inputs,
+ std::vector<FileMetaData*>* grandparents) {
+ InternalKey start, limit;
+ GetRange(inputs, output_level_inputs, &start, &limit);
+ // Compute the set of grandparent files that overlap this compaction
+ // (parent == level+1; grandparent == level+2)
+ if (output_level_inputs.level + 1 < NumberLevels()) {
+ vstorage->GetOverlappingInputs(output_level_inputs.level + 1, &start,
+ &limit, grandparents);
+ }
+}
+
+Compaction* CompactionPicker::CompactRange(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, int input_level, int output_level,
+ const CompactRangeOptions& compact_range_options, const InternalKey* begin,
+ const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict,
+ uint64_t max_file_num_to_ignore) {
+ // CompactionPickerFIFO has its own implementation of compact range
+ assert(ioptions_.compaction_style != kCompactionStyleFIFO);
+
+ if (input_level == ColumnFamilyData::kCompactAllLevels) {
+ assert(ioptions_.compaction_style == kCompactionStyleUniversal);
+
+ // Universal compaction with more than one level always compacts all the
+ // files together to the last level.
+ assert(vstorage->num_levels() > 1);
+ // DBImpl::CompactRange() set output level to be the last level
+ if (ioptions_.allow_ingest_behind) {
+ assert(output_level == vstorage->num_levels() - 2);
+ } else {
+ assert(output_level == vstorage->num_levels() - 1);
+ }
+ // DBImpl::RunManualCompaction will make full range for universal compaction
+ assert(begin == nullptr);
+ assert(end == nullptr);
+ *compaction_end = nullptr;
+
+ int start_level = 0;
+ for (; start_level < vstorage->num_levels() &&
+ vstorage->NumLevelFiles(start_level) == 0;
+ start_level++) {
+ }
+ if (start_level == vstorage->num_levels()) {
+ return nullptr;
+ }
+
+ if ((start_level == 0) && (!level0_compactions_in_progress_.empty())) {
+ *manual_conflict = true;
+ // Only one level 0 compaction allowed
+ return nullptr;
+ }
+
+ std::vector<CompactionInputFiles> inputs(vstorage->num_levels() -
+ start_level);
+ for (int level = start_level; level < vstorage->num_levels(); level++) {
+ inputs[level - start_level].level = level;
+ auto& files = inputs[level - start_level].files;
+ for (FileMetaData* f : vstorage->LevelFiles(level)) {
+ files.push_back(f);
+ }
+ if (AreFilesInCompaction(files)) {
+ *manual_conflict = true;
+ return nullptr;
+ }
+ }
+
+ // 2 non-exclusive manual compactions could run at the same time producing
+ // overlaping outputs in the same level.
+ if (FilesRangeOverlapWithCompaction(inputs, output_level)) {
+ // This compaction output could potentially conflict with the output
+ // of a currently running compaction, we cannot run it.
+ *manual_conflict = true;
+ return nullptr;
+ }
+
+ Compaction* c = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, std::move(inputs),
+ output_level,
+ MaxFileSizeForLevel(mutable_cf_options, output_level,
+ ioptions_.compaction_style),
+ /* max_compaction_bytes */ LLONG_MAX,
+ compact_range_options.target_path_id,
+ GetCompressionType(ioptions_, vstorage, mutable_cf_options,
+ output_level, 1),
+ GetCompressionOptions(ioptions_, vstorage, output_level),
+ compact_range_options.max_subcompactions, /* grandparents */ {},
+ /* is manual */ true);
+ RegisterCompaction(c);
+ return c;
+ }
+
+ CompactionInputFiles inputs;
+ inputs.level = input_level;
+ bool covering_the_whole_range = true;
+
+ // All files are 'overlapping' in universal style compaction.
+ // We have to compact the entire range in one shot.
+ if (ioptions_.compaction_style == kCompactionStyleUniversal) {
+ begin = nullptr;
+ end = nullptr;
+ }
+
+ vstorage->GetOverlappingInputs(input_level, begin, end, &inputs.files);
+ if (inputs.empty()) {
+ return nullptr;
+ }
+
+ if ((input_level == 0) && (!level0_compactions_in_progress_.empty())) {
+ // Only one level 0 compaction allowed
+ TEST_SYNC_POINT("CompactionPicker::CompactRange:Conflict");
+ *manual_conflict = true;
+ return nullptr;
+ }
+
+ // Avoid compacting too much in one shot in case the range is large.
+ // But we cannot do this for level-0 since level-0 files can overlap
+ // and we must not pick one file and drop another older file if the
+ // two files overlap.
+ if (input_level > 0) {
+ const uint64_t limit = mutable_cf_options.max_compaction_bytes;
+ uint64_t total = 0;
+ for (size_t i = 0; i + 1 < inputs.size(); ++i) {
+ uint64_t s = inputs[i]->compensated_file_size;
+ total += s;
+ if (total >= limit) {
+ covering_the_whole_range = false;
+ inputs.files.resize(i + 1);
+ break;
+ }
+ }
+ }
+ assert(compact_range_options.target_path_id <
+ static_cast<uint32_t>(ioptions_.cf_paths.size()));
+
+ // for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
+ // files that are created during the current compaction.
+ if (compact_range_options.bottommost_level_compaction ==
+ BottommostLevelCompaction::kForceOptimized &&
+ max_file_num_to_ignore != port::kMaxUint64) {
+ assert(input_level == output_level);
+ // inputs_shrunk holds a continuous subset of input files which were all
+ // created before the current manual compaction
+ std::vector<FileMetaData*> inputs_shrunk;
+ size_t skip_input_index = inputs.size();
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
+ inputs_shrunk.push_back(inputs[i]);
+ } else if (!inputs_shrunk.empty()) {
+ // inputs[i] was created during the current manual compaction and
+ // need to be skipped
+ skip_input_index = i;
+ break;
+ }
+ }
+ if (inputs_shrunk.empty()) {
+ return nullptr;
+ }
+ if (inputs.size() != inputs_shrunk.size()) {
+ inputs.files.swap(inputs_shrunk);
+ }
+ // set covering_the_whole_range to false if there is any file that need to
+ // be compacted in the range of inputs[skip_input_index+1, inputs.size())
+ for (size_t i = skip_input_index + 1; i < inputs.size(); ++i) {
+ if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
+ covering_the_whole_range = false;
+ }
+ }
+ }
+
+ InternalKey key_storage;
+ InternalKey* next_smallest = &key_storage;
+ if (ExpandInputsToCleanCut(cf_name, vstorage, &inputs, &next_smallest) ==
+ false) {
+ // manual compaction is now multi-threaded, so it can
+ // happen that ExpandWhileOverlapping fails
+ // we handle it higher in RunManualCompaction
+ *manual_conflict = true;
+ return nullptr;
+ }
+
+ if (covering_the_whole_range || !next_smallest) {
+ *compaction_end = nullptr;
+ } else {
+ **compaction_end = *next_smallest;
+ }
+
+ CompactionInputFiles output_level_inputs;
+ if (output_level == ColumnFamilyData::kCompactToBaseLevel) {
+ assert(input_level == 0);
+ output_level = vstorage->base_level();
+ assert(output_level > 0);
+ }
+ output_level_inputs.level = output_level;
+ if (input_level != output_level) {
+ int parent_index = -1;
+ if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, &inputs,
+ &output_level_inputs, &parent_index, -1)) {
+ // manual compaction is now multi-threaded, so it can
+ // happen that SetupOtherInputs fails
+ // we handle it higher in RunManualCompaction
+ *manual_conflict = true;
+ return nullptr;
+ }
+ }
+
+ std::vector<CompactionInputFiles> compaction_inputs({inputs});
+ if (!output_level_inputs.empty()) {
+ compaction_inputs.push_back(output_level_inputs);
+ }
+ for (size_t i = 0; i < compaction_inputs.size(); i++) {
+ if (AreFilesInCompaction(compaction_inputs[i].files)) {
+ *manual_conflict = true;
+ return nullptr;
+ }
+ }
+
+ // 2 non-exclusive manual compactions could run at the same time producing
+ // overlaping outputs in the same level.
+ if (FilesRangeOverlapWithCompaction(compaction_inputs, output_level)) {
+ // This compaction output could potentially conflict with the output
+ // of a currently running compaction, we cannot run it.
+ *manual_conflict = true;
+ return nullptr;
+ }
+
+ std::vector<FileMetaData*> grandparents;
+ GetGrandparents(vstorage, inputs, output_level_inputs, &grandparents);
+ Compaction* compaction = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, std::move(compaction_inputs),
+ output_level,
+ MaxFileSizeForLevel(mutable_cf_options, output_level,
+ ioptions_.compaction_style, vstorage->base_level(),
+ ioptions_.level_compaction_dynamic_level_bytes),
+ mutable_cf_options.max_compaction_bytes,
+ compact_range_options.target_path_id,
+ GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
+ vstorage->base_level()),
+ GetCompressionOptions(ioptions_, vstorage, output_level),
+ compact_range_options.max_subcompactions, std::move(grandparents),
+ /* is manual compaction */ true);
+
+ TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
+ RegisterCompaction(compaction);
+
+ // Creating a compaction influences the compaction score because the score
+ // takes running compactions into account (by skipping files that are already
+ // being compacted). Since we just changed compaction score, we recalculate it
+ // here
+ vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
+
+ return compaction;
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+// Test whether two files have overlapping key-ranges.
+bool HaveOverlappingKeyRanges(const Comparator* c, const SstFileMetaData& a,
+ const SstFileMetaData& b) {
+ if (c->Compare(a.smallestkey, b.smallestkey) >= 0) {
+ if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
+ // b.smallestkey <= a.smallestkey <= b.largestkey
+ return true;
+ }
+ } else if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
+ // a.smallestkey < b.smallestkey <= a.largestkey
+ return true;
+ }
+ if (c->Compare(a.largestkey, b.largestkey) <= 0) {
+ if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
+ // b.smallestkey <= a.largestkey <= b.largestkey
+ return true;
+ }
+ } else if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
+ // a.smallestkey <= b.largestkey < a.largestkey
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
+ std::unordered_set<uint64_t>* input_files,
+ const ColumnFamilyMetaData& cf_meta, const int output_level) const {
+ auto& levels = cf_meta.levels;
+ auto comparator = icmp_->user_comparator();
+
+ // TODO(yhchiang): add is_adjustable to CompactionOptions
+
+ // the smallest and largest key of the current compaction input
+ std::string smallestkey;
+ std::string largestkey;
+ // a flag for initializing smallest and largest key
+ bool is_first = false;
+ const int kNotFound = -1;
+
+ // For each level, it does the following things:
+ // 1. Find the first and the last compaction input files
+ // in the current level.
+ // 2. Include all files between the first and the last
+ // compaction input files.
+ // 3. Update the compaction key-range.
+ // 4. For all remaining levels, include files that have
+ // overlapping key-range with the compaction key-range.
+ for (int l = 0; l <= output_level; ++l) {
+ auto& current_files = levels[l].files;
+ int first_included = static_cast<int>(current_files.size());
+ int last_included = kNotFound;
+
+ // identify the first and the last compaction input files
+ // in the current level.
+ for (size_t f = 0; f < current_files.size(); ++f) {
+ if (input_files->find(TableFileNameToNumber(current_files[f].name)) !=
+ input_files->end()) {
+ first_included = std::min(first_included, static_cast<int>(f));
+ last_included = std::max(last_included, static_cast<int>(f));
+ if (is_first == false) {
+ smallestkey = current_files[f].smallestkey;
+ largestkey = current_files[f].largestkey;
+ is_first = true;
+ }
+ }
+ }
+ if (last_included == kNotFound) {
+ continue;
+ }
+
+ if (l != 0) {
+ // expend the compaction input of the current level if it
+ // has overlapping key-range with other non-compaction input
+ // files in the same level.
+ while (first_included > 0) {
+ if (comparator->Compare(current_files[first_included - 1].largestkey,
+ current_files[first_included].smallestkey) <
+ 0) {
+ break;
+ }
+ first_included--;
+ }
+
+ while (last_included < static_cast<int>(current_files.size()) - 1) {
+ if (comparator->Compare(current_files[last_included + 1].smallestkey,
+ current_files[last_included].largestkey) > 0) {
+ break;
+ }
+ last_included++;
+ }
+ } else if (output_level > 0) {
+ last_included = static_cast<int>(current_files.size() - 1);
+ }
+
+ // include all files between the first and the last compaction input files.
+ for (int f = first_included; f <= last_included; ++f) {
+ if (current_files[f].being_compacted) {
+ return Status::Aborted("Necessary compaction input file " +
+ current_files[f].name +
+ " is currently being compacted.");
+ }
+ input_files->insert(TableFileNameToNumber(current_files[f].name));
+ }
+
+ // update smallest and largest key
+ if (l == 0) {
+ for (int f = first_included; f <= last_included; ++f) {
+ if (comparator->Compare(smallestkey, current_files[f].smallestkey) >
+ 0) {
+ smallestkey = current_files[f].smallestkey;
+ }
+ if (comparator->Compare(largestkey, current_files[f].largestkey) < 0) {
+ largestkey = current_files[f].largestkey;
+ }
+ }
+ } else {
+ if (comparator->Compare(smallestkey,
+ current_files[first_included].smallestkey) > 0) {
+ smallestkey = current_files[first_included].smallestkey;
+ }
+ if (comparator->Compare(largestkey,
+ current_files[last_included].largestkey) < 0) {
+ largestkey = current_files[last_included].largestkey;
+ }
+ }
+
+ SstFileMetaData aggregated_file_meta;
+ aggregated_file_meta.smallestkey = smallestkey;
+ aggregated_file_meta.largestkey = largestkey;
+
+ // For all lower levels, include all overlapping files.
+ // We need to add overlapping files from the current level too because even
+ // if there no input_files in level l, we would still need to add files
+ // which overlap with the range containing the input_files in levels 0 to l
+ // Level 0 doesn't need to be handled this way because files are sorted by
+ // time and not by key
+ for (int m = std::max(l, 1); m <= output_level; ++m) {
+ for (auto& next_lv_file : levels[m].files) {
+ if (HaveOverlappingKeyRanges(comparator, aggregated_file_meta,
+ next_lv_file)) {
+ if (next_lv_file.being_compacted) {
+ return Status::Aborted(
+ "File " + next_lv_file.name +
+ " that has overlapping key range with one of the compaction "
+ " input file is currently being compacted.");
+ }
+ input_files->insert(TableFileNameToNumber(next_lv_file.name));
+ }
+ }
+ }
+ }
+ if (RangeOverlapWithCompaction(smallestkey, largestkey, output_level)) {
+ return Status::Aborted(
+ "A running compaction is writing to the same output level in an "
+ "overlapping key range");
+ }
+ return Status::OK();
+}
+
+Status CompactionPicker::SanitizeCompactionInputFiles(
+ std::unordered_set<uint64_t>* input_files,
+ const ColumnFamilyMetaData& cf_meta, const int output_level) const {
+ assert(static_cast<int>(cf_meta.levels.size()) - 1 ==
+ cf_meta.levels[cf_meta.levels.size() - 1].level);
+ if (output_level >= static_cast<int>(cf_meta.levels.size())) {
+ return Status::InvalidArgument(
+ "Output level for column family " + cf_meta.name +
+ " must between [0, " +
+ ToString(cf_meta.levels[cf_meta.levels.size() - 1].level) + "].");
+ }
+
+ if (output_level > MaxOutputLevel()) {
+ return Status::InvalidArgument(
+ "Exceed the maximum output level defined by "
+ "the current compaction algorithm --- " +
+ ToString(MaxOutputLevel()));
+ }
+
+ if (output_level < 0) {
+ return Status::InvalidArgument("Output level cannot be negative.");
+ }
+
+ if (input_files->size() == 0) {
+ return Status::InvalidArgument(
+ "A compaction must contain at least one file.");
+ }
+
+ Status s = SanitizeCompactionInputFilesForAllLevels(input_files, cf_meta,
+ output_level);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ // for all input files, check whether the file number matches
+ // any currently-existing files.
+ for (auto file_num : *input_files) {
+ bool found = false;
+ for (const auto& level_meta : cf_meta.levels) {
+ for (const auto& file_meta : level_meta.files) {
+ if (file_num == TableFileNameToNumber(file_meta.name)) {
+ if (file_meta.being_compacted) {
+ return Status::Aborted("Specified compaction input file " +
+ MakeTableFileName("", file_num) +
+ " is already being compacted.");
+ }
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ if (!found) {
+ return Status::InvalidArgument(
+ "Specified compaction input file " + MakeTableFileName("", file_num) +
+ " does not exist in column family " + cf_meta.name + ".");
+ }
+ }
+
+ return Status::OK();
+}
+#endif // !ROCKSDB_LITE
+
+void CompactionPicker::RegisterCompaction(Compaction* c) {
+ if (c == nullptr) {
+ return;
+ }
+ assert(ioptions_.compaction_style != kCompactionStyleLevel ||
+ c->output_level() == 0 ||
+ !FilesRangeOverlapWithCompaction(*c->inputs(), c->output_level()));
+ if (c->start_level() == 0 ||
+ ioptions_.compaction_style == kCompactionStyleUniversal) {
+ level0_compactions_in_progress_.insert(c);
+ }
+ compactions_in_progress_.insert(c);
+}
+
+void CompactionPicker::UnregisterCompaction(Compaction* c) {
+ if (c == nullptr) {
+ return;
+ }
+ if (c->start_level() == 0 ||
+ ioptions_.compaction_style == kCompactionStyleUniversal) {
+ level0_compactions_in_progress_.erase(c);
+ }
+ compactions_in_progress_.erase(c);
+}
+
+void CompactionPicker::PickFilesMarkedForCompaction(
+ const std::string& cf_name, VersionStorageInfo* vstorage, int* start_level,
+ int* output_level, CompactionInputFiles* start_level_inputs) {
+ if (vstorage->FilesMarkedForCompaction().empty()) {
+ return;
+ }
+
+ auto continuation = [&, cf_name](std::pair<int, FileMetaData*> level_file) {
+ // If it's being compacted it has nothing to do here.
+ // If this assert() fails that means that some function marked some
+ // files as being_compacted, but didn't call ComputeCompactionScore()
+ assert(!level_file.second->being_compacted);
+ *start_level = level_file.first;
+ *output_level =
+ (*start_level == 0) ? vstorage->base_level() : *start_level + 1;
+
+ if (*start_level == 0 && !level0_compactions_in_progress()->empty()) {
+ return false;
+ }
+
+ start_level_inputs->files = {level_file.second};
+ start_level_inputs->level = *start_level;
+ return ExpandInputsToCleanCut(cf_name, vstorage, start_level_inputs);
+ };
+
+ // take a chance on a random file first
+ Random64 rnd(/* seed */ reinterpret_cast<uint64_t>(vstorage));
+ size_t random_file_index = static_cast<size_t>(rnd.Uniform(
+ static_cast<uint64_t>(vstorage->FilesMarkedForCompaction().size())));
+
+ if (continuation(vstorage->FilesMarkedForCompaction()[random_file_index])) {
+ // found the compaction!
+ return;
+ }
+
+ for (auto& level_file : vstorage->FilesMarkedForCompaction()) {
+ if (continuation(level_file)) {
+ // found the compaction!
+ return;
+ }
+ }
+ start_level_inputs->files.clear();
+}
+
+bool CompactionPicker::GetOverlappingL0Files(
+ VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
+ int output_level, int* parent_index) {
+ // Two level 0 compaction won't run at the same time, so don't need to worry
+ // about files on level 0 being compacted.
+ assert(level0_compactions_in_progress()->empty());
+ InternalKey smallest, largest;
+ GetRange(*start_level_inputs, &smallest, &largest);
+ // Note that the next call will discard the file we placed in
+ // c->inputs_[0] earlier and replace it with an overlapping set
+ // which will include the picked file.
+ start_level_inputs->files.clear();
+ vstorage->GetOverlappingInputs(0, &smallest, &largest,
+ &(start_level_inputs->files));
+
+ // If we include more L0 files in the same compaction run it can
+ // cause the 'smallest' and 'largest' key to get extended to a
+ // larger range. So, re-invoke GetRange to get the new key range
+ GetRange(*start_level_inputs, &smallest, &largest);
+ if (IsRangeInCompaction(vstorage, &smallest, &largest, output_level,
+ parent_index)) {
+ return false;
+ }
+ assert(!start_level_inputs->files.empty());
+
+ return true;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker.h b/storage/rocksdb/rocksdb/db/compaction/compaction_picker.h
new file mode 100644
index 00000000..36d570e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker.h
@@ -0,0 +1,313 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/compaction/compaction.h"
+#include "db/version_set.h"
+#include "options/cf_options.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// The file contains an abstract class CompactionPicker, and its two
+// sub-classes LevelCompactionPicker and NullCompactionPicker, as
+// well as some helper functions used by them.
+
+class LogBuffer;
+class Compaction;
+class VersionStorageInfo;
+struct CompactionInputFiles;
+
+// An abstract class to pick compactions from an existing LSM-tree.
+//
+// Each compaction style inherits the class and implement the
+// interface to form automatic compactions. If NeedCompaction() is true,
+// then call PickCompaction() to find what files need to be compacted
+// and where to put the output files.
+//
+// Non-virtual functions CompactRange() and CompactFiles() are used to
+// pick files to compact based on users' DB::CompactRange() and
+// DB::CompactFiles() requests, respectively. There is little
+// compaction style specific logic for them.
+class CompactionPicker {
+ public:
+ CompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp);
+ virtual ~CompactionPicker();
+
+ // Pick level and inputs for a new compaction.
+ // Returns nullptr if there is no compaction to be done.
+ // Otherwise returns a pointer to a heap-allocated object that
+ // describes the compaction. Caller should delete the result.
+ virtual Compaction* PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber earliest_memtable_seqno = kMaxSequenceNumber) = 0;
+
+ // Return a compaction object for compacting the range [begin,end] in
+ // the specified level. Returns nullptr if there is nothing in that
+ // level that overlaps the specified range. Caller should delete
+ // the result.
+ //
+ // The returned Compaction might not include the whole requested range.
+ // In that case, compaction_end will be set to the next key that needs
+ // compacting. In case the compaction will compact the whole range,
+ // compaction_end will be set to nullptr.
+ // Client is responsible for compaction_end storage -- when called,
+ // *compaction_end should point to valid InternalKey!
+ virtual Compaction* CompactRange(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, int input_level, int output_level,
+ const CompactRangeOptions& compact_range_options,
+ const InternalKey* begin, const InternalKey* end,
+ InternalKey** compaction_end, bool* manual_conflict,
+ uint64_t max_file_num_to_ignore);
+
+ // The maximum allowed output level. Default value is NumberLevels() - 1.
+ virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
+
+ virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const = 0;
+
+// Sanitize the input set of compaction input files.
+// When the input parameters do not describe a valid compaction, the
+// function will try to fix the input_files by adding necessary
+// files. If it's not possible to conver an invalid input_files
+// into a valid one by adding more files, the function will return a
+// non-ok status with specific reason.
+#ifndef ROCKSDB_LITE
+ Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files,
+ const ColumnFamilyMetaData& cf_meta,
+ const int output_level) const;
+#endif // ROCKSDB_LITE
+
+ // Free up the files that participated in a compaction
+ //
+ // Requirement: DB mutex held
+ void ReleaseCompactionFiles(Compaction* c, Status status);
+
+ // Returns true if any one of the specified files are being compacted
+ bool AreFilesInCompaction(const std::vector<FileMetaData*>& files);
+
+ // Takes a list of CompactionInputFiles and returns a (manual) Compaction
+ // object.
+ //
+ // Caller must provide a set of input files that has been passed through
+ // `SanitizeCompactionInputFiles` earlier. The lock should not be released
+ // between that call and this one.
+ Compaction* CompactFiles(const CompactionOptions& compact_options,
+ const std::vector<CompactionInputFiles>& input_files,
+ int output_level, VersionStorageInfo* vstorage,
+ const MutableCFOptions& mutable_cf_options,
+ uint32_t output_path_id);
+
+ // Converts a set of compaction input file numbers into
+ // a list of CompactionInputFiles.
+ Status GetCompactionInputsFromFileNumbers(
+ std::vector<CompactionInputFiles>* input_files,
+ std::unordered_set<uint64_t>* input_set,
+ const VersionStorageInfo* vstorage,
+ const CompactionOptions& compact_options) const;
+
+ // Is there currently a compaction involving level 0 taking place
+ bool IsLevel0CompactionInProgress() const {
+ return !level0_compactions_in_progress_.empty();
+ }
+
+ // Return true if the passed key range overlap with a compaction output
+ // that is currently running.
+ bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ int level) const;
+
+ // Stores the minimal range that covers all entries in inputs in
+ // *smallest, *largest.
+ // REQUIRES: inputs is not empty
+ void GetRange(const CompactionInputFiles& inputs, InternalKey* smallest,
+ InternalKey* largest) const;
+
+ // Stores the minimal range that covers all entries in inputs1 and inputs2
+ // in *smallest, *largest.
+ // REQUIRES: inputs is not empty
+ void GetRange(const CompactionInputFiles& inputs1,
+ const CompactionInputFiles& inputs2, InternalKey* smallest,
+ InternalKey* largest) const;
+
+ // Stores the minimal range that covers all entries in inputs
+ // in *smallest, *largest.
+ // REQUIRES: inputs is not empty (at least on entry have one file)
+ void GetRange(const std::vector<CompactionInputFiles>& inputs,
+ InternalKey* smallest, InternalKey* largest) const;
+
+ int NumberLevels() const { return ioptions_.num_levels; }
+
+ // Add more files to the inputs on "level" to make sure that
+ // no newer version of a key is compacted to "level+1" while leaving an older
+ // version in a "level". Otherwise, any Get() will search "level" first,
+ // and will likely return an old/stale value for the key, since it always
+ // searches in increasing order of level to find the value. This could
+ // also scramble the order of merge operands. This function should be
+ // called any time a new Compaction is created, and its inputs_[0] are
+ // populated.
+ //
+ // Will return false if it is impossible to apply this compaction.
+ bool ExpandInputsToCleanCut(const std::string& cf_name,
+ VersionStorageInfo* vstorage,
+ CompactionInputFiles* inputs,
+ InternalKey** next_smallest = nullptr);
+
+ // Returns true if any one of the parent files are being compacted
+ bool IsRangeInCompaction(VersionStorageInfo* vstorage,
+ const InternalKey* smallest,
+ const InternalKey* largest, int level, int* index);
+
+ // Returns true if the key range that `inputs` files cover overlap with the
+ // key range of a currently running compaction.
+ bool FilesRangeOverlapWithCompaction(
+ const std::vector<CompactionInputFiles>& inputs, int level) const;
+
+ bool SetupOtherInputs(const std::string& cf_name,
+ const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage,
+ CompactionInputFiles* inputs,
+ CompactionInputFiles* output_level_inputs,
+ int* parent_index, int base_index);
+
+ void GetGrandparents(VersionStorageInfo* vstorage,
+ const CompactionInputFiles& inputs,
+ const CompactionInputFiles& output_level_inputs,
+ std::vector<FileMetaData*>* grandparents);
+
+ void PickFilesMarkedForCompaction(const std::string& cf_name,
+ VersionStorageInfo* vstorage,
+ int* start_level, int* output_level,
+ CompactionInputFiles* start_level_inputs);
+
+ bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
+ CompactionInputFiles* start_level_inputs,
+ int output_level, int* parent_index);
+
+ // Register this compaction in the set of running compactions
+ void RegisterCompaction(Compaction* c);
+
+ // Remove this compaction from the set of running compactions
+ void UnregisterCompaction(Compaction* c);
+
+ std::set<Compaction*>* level0_compactions_in_progress() {
+ return &level0_compactions_in_progress_;
+ }
+ std::unordered_set<Compaction*>* compactions_in_progress() {
+ return &compactions_in_progress_;
+ }
+
+ protected:
+ const ImmutableCFOptions& ioptions_;
+
+// A helper function to SanitizeCompactionInputFiles() that
+// sanitizes "input_files" by adding necessary files.
+#ifndef ROCKSDB_LITE
+ virtual Status SanitizeCompactionInputFilesForAllLevels(
+ std::unordered_set<uint64_t>* input_files,
+ const ColumnFamilyMetaData& cf_meta, const int output_level) const;
+#endif // ROCKSDB_LITE
+
+ // Keeps track of all compactions that are running on Level0.
+ // Protected by DB mutex
+ std::set<Compaction*> level0_compactions_in_progress_;
+
+ // Keeps track of all compactions that are running.
+ // Protected by DB mutex
+ std::unordered_set<Compaction*> compactions_in_progress_;
+
+ const InternalKeyComparator* const icmp_;
+};
+
+#ifndef ROCKSDB_LITE
+// A dummy compaction that never triggers any automatic
+// compaction.
+class NullCompactionPicker : public CompactionPicker {
+ public:
+ NullCompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp)
+ : CompactionPicker(ioptions, icmp) {}
+ virtual ~NullCompactionPicker() {}
+
+ // Always return "nullptr"
+ Compaction* PickCompaction(
+ const std::string& /*cf_name*/,
+ const MutableCFOptions& /*mutable_cf_options*/,
+ VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
+ SequenceNumber /* earliest_memtable_seqno */) override {
+ return nullptr;
+ }
+
+ // Always return "nullptr"
+ Compaction* CompactRange(const std::string& /*cf_name*/,
+ const MutableCFOptions& /*mutable_cf_options*/,
+ VersionStorageInfo* /*vstorage*/,
+ int /*input_level*/, int /*output_level*/,
+ const CompactRangeOptions& /*compact_range_options*/,
+ const InternalKey* /*begin*/,
+ const InternalKey* /*end*/,
+ InternalKey** /*compaction_end*/,
+ bool* /*manual_conflict*/,
+ uint64_t /*max_file_num_to_ignore*/) override {
+ return nullptr;
+ }
+
+ // Always returns false.
+ virtual bool NeedsCompaction(
+ const VersionStorageInfo* /*vstorage*/) const override {
+ return false;
+ }
+};
+#endif // !ROCKSDB_LITE
+
+// Attempts to find an intra L0 compaction conforming to the given parameters.
+//
+// @param level_files Metadata for L0 files.
+// @param min_files_to_compact Minimum number of files required to
+// do the compaction.
+// @param max_compact_bytes_per_del_file Maximum average size in bytes per
+// file that is going to get deleted by
+// the compaction.
+// @param max_compaction_bytes Maximum total size in bytes (in terms
+// of compensated file size) for files
+// to be compacted.
+// @param [out] comp_inputs If a compaction was found, will be
+// initialized with corresponding input
+// files. Cannot be nullptr.
+//
+// @return true iff compaction was found.
+bool FindIntraL0Compaction(
+ const std::vector<FileMetaData*>& level_files, size_t min_files_to_compact,
+ uint64_t max_compact_bytes_per_del_file, uint64_t max_compaction_bytes,
+ CompactionInputFiles* comp_inputs,
+ SequenceNumber earliest_mem_seqno = kMaxSequenceNumber);
+
+CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
+ const VersionStorageInfo* vstorage,
+ const MutableCFOptions& mutable_cf_options,
+ int level, int base_level,
+ const bool enable_compression = true);
+
+CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
+ const VersionStorageInfo* vstorage,
+ int level,
+ const bool enable_compression = true);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc
new file mode 100644
index 00000000..030da619
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_picker_fifo.h"
+#ifndef ROCKSDB_LITE
+
+#include <cinttypes>
+#include <string>
+#include <vector>
+#include "db/column_family.h"
+#include "logging/log_buffer.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+uint64_t GetTotalFilesSize(const std::vector<FileMetaData*>& files) {
+ uint64_t total_size = 0;
+ for (const auto& f : files) {
+ total_size += f->fd.file_size;
+ }
+ return total_size;
+}
+} // anonymous namespace
+
+bool FIFOCompactionPicker::NeedsCompaction(
+ const VersionStorageInfo* vstorage) const {
+ const int kLevel0 = 0;
+ return vstorage->CompactionScore(kLevel0) >= 1;
+}
+
+Compaction* FIFOCompactionPicker::PickTTLCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
+ assert(mutable_cf_options.ttl > 0);
+
+ const int kLevel0 = 0;
+ const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
+ uint64_t total_size = GetTotalFilesSize(level_files);
+
+ int64_t _current_time;
+ auto status = ioptions_.env->GetCurrentTime(&_current_time);
+ if (!status.ok()) {
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] FIFO compaction: Couldn't get current time: %s. "
+ "Not doing compactions based on TTL. ",
+ cf_name.c_str(), status.ToString().c_str());
+ return nullptr;
+ }
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+
+ if (!level0_compactions_in_progress_.empty()) {
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] FIFO compaction: Already executing compaction. No need "
+ "to run parallel compactions since compactions are very fast",
+ cf_name.c_str());
+ return nullptr;
+ }
+
+ std::vector<CompactionInputFiles> inputs;
+ inputs.emplace_back();
+ inputs[0].level = 0;
+
+ // avoid underflow
+ if (current_time > mutable_cf_options.ttl) {
+ for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
+ auto f = *ritr;
+ if (f->fd.table_reader != nullptr &&
+ f->fd.table_reader->GetTableProperties() != nullptr) {
+ auto creation_time =
+ f->fd.table_reader->GetTableProperties()->creation_time;
+ if (creation_time == 0 ||
+ creation_time >= (current_time - mutable_cf_options.ttl)) {
+ break;
+ }
+ total_size -= f->compensated_file_size;
+ inputs[0].files.push_back(f);
+ }
+ }
+ }
+
+ // Return a nullptr and proceed to size-based FIFO compaction if:
+ // 1. there are no files older than ttl OR
+ // 2. there are a few files older than ttl, but deleting them will not bring
+ // the total size to be less than max_table_files_size threshold.
+ if (inputs[0].files.empty() ||
+ total_size >
+ mutable_cf_options.compaction_options_fifo.max_table_files_size) {
+ return nullptr;
+ }
+
+ for (const auto& f : inputs[0].files) {
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] FIFO compaction: picking file %" PRIu64
+ " with creation time %" PRIu64 " for deletion",
+ cf_name.c_str(), f->fd.GetNumber(),
+ f->fd.table_reader->GetTableProperties()->creation_time);
+ }
+
+ Compaction* c = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
+ kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
+ {}, /* is manual */ false, vstorage->CompactionScore(0),
+ /* is deletion compaction */ true, CompactionReason::kFIFOTtl);
+ return c;
+}
+
+Compaction* FIFOCompactionPicker::PickSizeCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
+ const int kLevel0 = 0;
+ const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
+ uint64_t total_size = GetTotalFilesSize(level_files);
+
+ if (total_size <=
+ mutable_cf_options.compaction_options_fifo.max_table_files_size ||
+ level_files.size() == 0) {
+ // total size not exceeded
+ if (mutable_cf_options.compaction_options_fifo.allow_compaction &&
+ level_files.size() > 0) {
+ CompactionInputFiles comp_inputs;
+ // try to prevent same files from being compacted multiple times, which
+ // could produce large files that may never TTL-expire. Achieve this by
+ // disallowing compactions with files larger than memtable (inflate its
+ // size by 10% to account for uncompressed L0 files that may have size
+ // slightly greater than memtable size limit).
+ size_t max_compact_bytes_per_del_file =
+ static_cast<size_t>(MultiplyCheckOverflow(
+ static_cast<uint64_t>(mutable_cf_options.write_buffer_size),
+ 1.1));
+ if (FindIntraL0Compaction(
+ level_files,
+ mutable_cf_options
+ .level0_file_num_compaction_trigger /* min_files_to_compact */
+ ,
+ max_compact_bytes_per_del_file,
+ mutable_cf_options.max_compaction_bytes, &comp_inputs)) {
+ Compaction* c = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, {comp_inputs}, 0,
+ 16 * 1024 * 1024 /* output file size limit */,
+ 0 /* max compaction bytes, not applicable */,
+ 0 /* output path ID */, mutable_cf_options.compression,
+ ioptions_.compression_opts, 0 /* max_subcompactions */, {},
+ /* is manual */ false, vstorage->CompactionScore(0),
+ /* is deletion compaction */ false,
+ CompactionReason::kFIFOReduceNumFiles);
+ return c;
+ }
+ }
+
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] FIFO compaction: nothing to do. Total size %" PRIu64
+ ", max size %" PRIu64 "\n",
+ cf_name.c_str(), total_size,
+ mutable_cf_options.compaction_options_fifo.max_table_files_size);
+ return nullptr;
+ }
+
+ if (!level0_compactions_in_progress_.empty()) {
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] FIFO compaction: Already executing compaction. No need "
+ "to run parallel compactions since compactions are very fast",
+ cf_name.c_str());
+ return nullptr;
+ }
+
+ std::vector<CompactionInputFiles> inputs;
+ inputs.emplace_back();
+ inputs[0].level = 0;
+
+ for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
+ auto f = *ritr;
+ total_size -= f->compensated_file_size;
+ inputs[0].files.push_back(f);
+ char tmp_fsize[16];
+ AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] FIFO compaction: picking file %" PRIu64
+ " with size %s for deletion",
+ cf_name.c_str(), f->fd.GetNumber(), tmp_fsize);
+ if (total_size <=
+ mutable_cf_options.compaction_options_fifo.max_table_files_size) {
+ break;
+ }
+ }
+
+ Compaction* c = new Compaction(
+ vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
+ kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
+ {}, /* is manual */ false, vstorage->CompactionScore(0),
+ /* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
+ return c;
+}
+
+Compaction* FIFOCompactionPicker::PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber /*earliest_memtable_seqno*/) {
+ assert(vstorage->num_levels() == 1);
+
+ Compaction* c = nullptr;
+ if (mutable_cf_options.ttl > 0) {
+ c = PickTTLCompaction(cf_name, mutable_cf_options, vstorage, log_buffer);
+ }
+ if (c == nullptr) {
+ c = PickSizeCompaction(cf_name, mutable_cf_options, vstorage, log_buffer);
+ }
+ RegisterCompaction(c);
+ return c;
+}
+
+Compaction* FIFOCompactionPicker::CompactRange(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, int input_level, int output_level,
+ const CompactRangeOptions& /*compact_range_options*/,
+ const InternalKey* /*begin*/, const InternalKey* /*end*/,
+ InternalKey** compaction_end, bool* /*manual_conflict*/,
+ uint64_t /*max_file_num_to_ignore*/) {
+#ifdef NDEBUG
+ (void)input_level;
+ (void)output_level;
+#endif
+ assert(input_level == 0);
+ assert(output_level == 0);
+ *compaction_end = nullptr;
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.info_log);
+ Compaction* c =
+ PickCompaction(cf_name, mutable_cf_options, vstorage, &log_buffer);
+ log_buffer.FlushBufferToLog();
+ return c;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.h b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.h
new file mode 100644
index 00000000..eb786e5a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "db/compaction/compaction_picker.h"
+
+namespace ROCKSDB_NAMESPACE {
+class FIFOCompactionPicker : public CompactionPicker {
+ public:
+ FIFOCompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp)
+ : CompactionPicker(ioptions, icmp) {}
+
+ virtual Compaction* PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* version, LogBuffer* log_buffer,
+ SequenceNumber earliest_memtable_seqno = kMaxSequenceNumber) override;
+
+ virtual Compaction* CompactRange(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, int input_level, int output_level,
+ const CompactRangeOptions& compact_range_options,
+ const InternalKey* begin, const InternalKey* end,
+ InternalKey** compaction_end, bool* manual_conflict,
+ uint64_t max_file_num_to_ignore) override;
+
+ // The maximum allowed output level. Always returns 0.
+ virtual int MaxOutputLevel() const override { return 0; }
+
+ virtual bool NeedsCompaction(
+ const VersionStorageInfo* vstorage) const override;
+
+ private:
+ Compaction* PickTTLCompaction(const std::string& cf_name,
+ const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* version,
+ LogBuffer* log_buffer);
+
+ Compaction* PickSizeCompaction(const std::string& cf_name,
+ const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* version,
+ LogBuffer* log_buffer);
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc
new file mode 100644
index 00000000..012edd08
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc
@@ -0,0 +1,558 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <utility>
+#include <vector>
+
+#include "db/compaction/compaction_picker_level.h"
+#include "logging/log_buffer.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool LevelCompactionPicker::NeedsCompaction(
+ const VersionStorageInfo* vstorage) const {
+ if (!vstorage->ExpiredTtlFiles().empty()) {
+ return true;
+ }
+ if (!vstorage->FilesMarkedForPeriodicCompaction().empty()) {
+ return true;
+ }
+ if (!vstorage->BottommostFilesMarkedForCompaction().empty()) {
+ return true;
+ }
+ if (!vstorage->FilesMarkedForCompaction().empty()) {
+ return true;
+ }
+ for (int i = 0; i <= vstorage->MaxInputLevel(); i++) {
+ if (vstorage->CompactionScore(i) >= 1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace {
+// A class to build a leveled compaction step-by-step.
+class LevelCompactionBuilder {
+ public:
+ LevelCompactionBuilder(const std::string& cf_name,
+ VersionStorageInfo* vstorage,
+ SequenceNumber earliest_mem_seqno,
+ CompactionPicker* compaction_picker,
+ LogBuffer* log_buffer,
+ const MutableCFOptions& mutable_cf_options,
+ const ImmutableCFOptions& ioptions)
+ : cf_name_(cf_name),
+ vstorage_(vstorage),
+ earliest_mem_seqno_(earliest_mem_seqno),
+ compaction_picker_(compaction_picker),
+ log_buffer_(log_buffer),
+ mutable_cf_options_(mutable_cf_options),
+ ioptions_(ioptions) {}
+
+ // Pick and return a compaction.
+ Compaction* PickCompaction();
+
+ // Pick the initial files to compact to the next level. (or together
+ // in Intra-L0 compactions)
+ void SetupInitialFiles();
+
+ // If the initial files are from L0 level, pick other L0
+ // files if needed.
+ bool SetupOtherL0FilesIfNeeded();
+
+ // Based on initial files, setup other files need to be compacted
+ // in this compaction, accordingly.
+ bool SetupOtherInputsIfNeeded();
+
+ Compaction* GetCompaction();
+
+ // For the specfied level, pick a file that we want to compact.
+ // Returns false if there is no file to compact.
+ // If it returns true, inputs->files.size() will be exactly one.
+ // If level is 0 and there is already a compaction on that level, this
+ // function will return false.
+ bool PickFileToCompact();
+
+ // For L0->L0, picks the longest span of files that aren't currently
+ // undergoing compaction for which work-per-deleted-file decreases. The span
+ // always starts from the newest L0 file.
+ //
+ // Intra-L0 compaction is independent of all other files, so it can be
+ // performed even when L0->base_level compactions are blocked.
+ //
+ // Returns true if `inputs` is populated with a span of files to be compacted;
+ // otherwise, returns false.
+ bool PickIntraL0Compaction();
+
+ void PickExpiredTtlFiles();
+
+ void PickFilesMarkedForPeriodicCompaction();
+
+ const std::string& cf_name_;
+ VersionStorageInfo* vstorage_;
+ SequenceNumber earliest_mem_seqno_;
+ CompactionPicker* compaction_picker_;
+ LogBuffer* log_buffer_;
+ int start_level_ = -1;
+ int output_level_ = -1;
+ int parent_index_ = -1;
+ int base_index_ = -1;
+ double start_level_score_ = 0;
+ bool is_manual_ = false;
+ CompactionInputFiles start_level_inputs_;
+ std::vector<CompactionInputFiles> compaction_inputs_;
+ CompactionInputFiles output_level_inputs_;
+ std::vector<FileMetaData*> grandparents_;
+ CompactionReason compaction_reason_ = CompactionReason::kUnknown;
+
+ const MutableCFOptions& mutable_cf_options_;
+ const ImmutableCFOptions& ioptions_;
+ // Pick a path ID to place a newly generated file, with its level
+ static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options,
+ int level);
+
+ static const int kMinFilesForIntraL0Compaction = 4;
+};
+
+void LevelCompactionBuilder::PickExpiredTtlFiles() {
+ if (vstorage_->ExpiredTtlFiles().empty()) {
+ return;
+ }
+
+ auto continuation = [&](std::pair<int, FileMetaData*> level_file) {
+ // If it's being compacted it has nothing to do here.
+ // If this assert() fails that means that some function marked some
+ // files as being_compacted, but didn't call ComputeCompactionScore()
+ assert(!level_file.second->being_compacted);
+ start_level_ = level_file.first;
+ output_level_ =
+ (start_level_ == 0) ? vstorage_->base_level() : start_level_ + 1;
+
+ if ((start_level_ == vstorage_->num_non_empty_levels() - 1) ||
+ (start_level_ == 0 &&
+ !compaction_picker_->level0_compactions_in_progress()->empty())) {
+ return false;
+ }
+
+ start_level_inputs_.files = {level_file.second};
+ start_level_inputs_.level = start_level_;
+ return compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
+ &start_level_inputs_);
+ };
+
+ for (auto& level_file : vstorage_->ExpiredTtlFiles()) {
+ if (continuation(level_file)) {
+ // found the compaction!
+ return;
+ }
+ }
+
+ start_level_inputs_.files.clear();
+}
+
+void LevelCompactionBuilder::PickFilesMarkedForPeriodicCompaction() {
+ if (vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
+ return;
+ }
+
+ auto continuation = [&](std::pair<int, FileMetaData*> level_file) {
+ // If it's being compacted it has nothing to do here.
+ // If this assert() fails that means that some function marked some
+ // files as being_compacted, but didn't call ComputeCompactionScore()
+ assert(!level_file.second->being_compacted);
+ output_level_ = start_level_ = level_file.first;
+
+ if (start_level_ == 0 &&
+ !compaction_picker_->level0_compactions_in_progress()->empty()) {
+ return false;
+ }
+
+ start_level_inputs_.files = {level_file.second};
+ start_level_inputs_.level = start_level_;
+ return compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
+ &start_level_inputs_);
+ };
+
+ for (auto& level_file : vstorage_->FilesMarkedForPeriodicCompaction()) {
+ if (continuation(level_file)) {
+ // found the compaction!
+ return;
+ }
+ }
+
+ start_level_inputs_.files.clear();
+}
+
+void LevelCompactionBuilder::SetupInitialFiles() {
+ // Find the compactions by size on all levels.
+ bool skipped_l0_to_base = false;
+ for (int i = 0; i < compaction_picker_->NumberLevels() - 1; i++) {
+ start_level_score_ = vstorage_->CompactionScore(i);
+ start_level_ = vstorage_->CompactionScoreLevel(i);
+ assert(i == 0 || start_level_score_ <= vstorage_->CompactionScore(i - 1));
+ if (start_level_score_ >= 1) {
+ if (skipped_l0_to_base && start_level_ == vstorage_->base_level()) {
+ // If L0->base_level compaction is pending, don't schedule further
+ // compaction from base level. Otherwise L0->base_level compaction
+ // may starve.
+ continue;
+ }
+ output_level_ =
+ (start_level_ == 0) ? vstorage_->base_level() : start_level_ + 1;
+ if (PickFileToCompact()) {
+ // found the compaction!
+ if (start_level_ == 0) {
+ // L0 score = `num L0 files` / `level0_file_num_compaction_trigger`
+ compaction_reason_ = CompactionReason::kLevelL0FilesNum;
+ } else {
+ // L1+ score = `Level files size` / `MaxBytesForLevel`
+ compaction_reason_ = CompactionReason::kLevelMaxLevelSize;
+ }
+ break;
+ } else {
+ // didn't find the compaction, clear the inputs
+ start_level_inputs_.clear();
+ if (start_level_ == 0) {
+ skipped_l0_to_base = true;
+ // L0->base_level may be blocked due to ongoing L0->base_level
+ // compactions. It may also be blocked by an ongoing compaction from
+ // base_level downwards.
+ //
+ // In these cases, to reduce L0 file count and thus reduce likelihood
+ // of write stalls, we can attempt compacting a span of files within
+ // L0.
+ if (PickIntraL0Compaction()) {
+ output_level_ = 0;
+ compaction_reason_ = CompactionReason::kLevelL0FilesNum;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // if we didn't find a compaction, check if there are any files marked for
+ // compaction
+ if (start_level_inputs_.empty()) {
+ parent_index_ = base_index_ = -1;
+
+ compaction_picker_->PickFilesMarkedForCompaction(
+ cf_name_, vstorage_, &start_level_, &output_level_,
+ &start_level_inputs_);
+ if (!start_level_inputs_.empty()) {
+ is_manual_ = true;
+ compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
+ return;
+ }
+ }
+
+ // Bottommost Files Compaction on deleting tombstones
+ if (start_level_inputs_.empty()) {
+ size_t i;
+ for (i = 0; i < vstorage_->BottommostFilesMarkedForCompaction().size();
+ ++i) {
+ auto& level_and_file = vstorage_->BottommostFilesMarkedForCompaction()[i];
+ assert(!level_and_file.second->being_compacted);
+ start_level_inputs_.level = output_level_ = start_level_ =
+ level_and_file.first;
+ start_level_inputs_.files = {level_and_file.second};
+ if (compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
+ &start_level_inputs_)) {
+ break;
+ }
+ }
+ if (i == vstorage_->BottommostFilesMarkedForCompaction().size()) {
+ start_level_inputs_.clear();
+ } else {
+ assert(!start_level_inputs_.empty());
+ compaction_reason_ = CompactionReason::kBottommostFiles;
+ return;
+ }
+ }
+
+ // TTL Compaction
+ if (start_level_inputs_.empty()) {
+ PickExpiredTtlFiles();
+ if (!start_level_inputs_.empty()) {
+ compaction_reason_ = CompactionReason::kTtl;
+ return;
+ }
+ }
+
+ // Periodic Compaction
+ if (start_level_inputs_.empty()) {
+ PickFilesMarkedForPeriodicCompaction();
+ if (!start_level_inputs_.empty()) {
+ compaction_reason_ = CompactionReason::kPeriodicCompaction;
+ return;
+ }
+ }
+}
+
+bool LevelCompactionBuilder::SetupOtherL0FilesIfNeeded() {
+ if (start_level_ == 0 && output_level_ != 0) {
+ return compaction_picker_->GetOverlappingL0Files(
+ vstorage_, &start_level_inputs_, output_level_, &parent_index_);
+ }
+ return true;
+}
+
+bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
+ // Setup input files from output level. For output to L0, we only compact
+ // spans of files that do not interact with any pending compactions, so don't
+ // need to consider other levels.
+ if (output_level_ != 0) {
+ output_level_inputs_.level = output_level_;
+ if (!compaction_picker_->SetupOtherInputs(
+ cf_name_, mutable_cf_options_, vstorage_, &start_level_inputs_,
+ &output_level_inputs_, &parent_index_, base_index_)) {
+ return false;
+ }
+
+ compaction_inputs_.push_back(start_level_inputs_);
+ if (!output_level_inputs_.empty()) {
+ compaction_inputs_.push_back(output_level_inputs_);
+ }
+
+ // In some edge cases we could pick a compaction that will be compacting
+ // a key range that overlap with another running compaction, and both
+ // of them have the same output level. This could happen if
+ // (1) we are running a non-exclusive manual compaction
+ // (2) AddFile ingest a new file into the LSM tree
+ // We need to disallow this from happening.
+ if (compaction_picker_->FilesRangeOverlapWithCompaction(compaction_inputs_,
+ output_level_)) {
+ // This compaction output could potentially conflict with the output
+ // of a currently running compaction, we cannot run it.
+ return false;
+ }
+ compaction_picker_->GetGrandparents(vstorage_, start_level_inputs_,
+ output_level_inputs_, &grandparents_);
+ } else {
+ compaction_inputs_.push_back(start_level_inputs_);
+ }
+ return true;
+}
+
+Compaction* LevelCompactionBuilder::PickCompaction() {
+ // Pick up the first file to start compaction. It may have been extended
+ // to a clean cut.
+ SetupInitialFiles();
+ if (start_level_inputs_.empty()) {
+ return nullptr;
+ }
+ assert(start_level_ >= 0 && output_level_ >= 0);
+
+ // If it is a L0 -> base level compaction, we need to set up other L0
+ // files if needed.
+ if (!SetupOtherL0FilesIfNeeded()) {
+ return nullptr;
+ }
+
+ // Pick files in the output level and expand more files in the start level
+ // if needed.
+ if (!SetupOtherInputsIfNeeded()) {
+ return nullptr;
+ }
+
+ // Form a compaction object containing the files we picked.
+ Compaction* c = GetCompaction();
+
+ TEST_SYNC_POINT_CALLBACK("LevelCompactionPicker::PickCompaction:Return", c);
+
+ return c;
+}
+
+Compaction* LevelCompactionBuilder::GetCompaction() {
+ auto c = new Compaction(
+ vstorage_, ioptions_, mutable_cf_options_, std::move(compaction_inputs_),
+ output_level_,
+ MaxFileSizeForLevel(mutable_cf_options_, output_level_,
+ ioptions_.compaction_style, vstorage_->base_level(),
+ ioptions_.level_compaction_dynamic_level_bytes),
+ mutable_cf_options_.max_compaction_bytes,
+ GetPathId(ioptions_, mutable_cf_options_, output_level_),
+ GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
+ output_level_, vstorage_->base_level()),
+ GetCompressionOptions(ioptions_, vstorage_, output_level_),
+ /* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
+ start_level_score_, false /* deletion_compaction */, compaction_reason_);
+
+ // If it's level 0 compaction, make sure we don't execute any other level 0
+ // compactions in parallel
+ compaction_picker_->RegisterCompaction(c);
+
+ // Creating a compaction influences the compaction score because the score
+ // takes running compactions into account (by skipping files that are already
+ // being compacted). Since we just changed compaction score, we recalculate it
+ // here
+ vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
+ return c;
+}
+
+/*
+ * Find the optimal path to place a file
+ * Given a level, finds the path where levels up to it will fit in levels
+ * up to and including this path
+ */
+uint32_t LevelCompactionBuilder::GetPathId(
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options, int level) {
+ uint32_t p = 0;
+ assert(!ioptions.cf_paths.empty());
+
+ // size remaining in the most recent path
+ uint64_t current_path_size = ioptions.cf_paths[0].target_size;
+
+ uint64_t level_size;
+ int cur_level = 0;
+
+ // max_bytes_for_level_base denotes L1 size.
+ // We estimate L0 size to be the same as L1.
+ level_size = mutable_cf_options.max_bytes_for_level_base;
+
+ // Last path is the fallback
+ while (p < ioptions.cf_paths.size() - 1) {
+ if (level_size <= current_path_size) {
+ if (cur_level == level) {
+ // Does desired level fit in this path?
+ return p;
+ } else {
+ current_path_size -= level_size;
+ if (cur_level > 0) {
+ if (ioptions.level_compaction_dynamic_level_bytes) {
+ // Currently, level_compaction_dynamic_level_bytes is ignored when
+ // multiple db paths are specified. https://github.com/facebook/
+ // rocksdb/blob/master/db/column_family.cc.
+ // Still, adding this check to avoid accidentally using
+ // max_bytes_for_level_multiplier_additional
+ level_size = static_cast<uint64_t>(
+ level_size * mutable_cf_options.max_bytes_for_level_multiplier);
+ } else {
+ level_size = static_cast<uint64_t>(
+ level_size * mutable_cf_options.max_bytes_for_level_multiplier *
+ mutable_cf_options.MaxBytesMultiplerAdditional(cur_level));
+ }
+ }
+ cur_level++;
+ continue;
+ }
+ }
+ p++;
+ current_path_size = ioptions.cf_paths[p].target_size;
+ }
+ return p;
+}
+
+bool LevelCompactionBuilder::PickFileToCompact() {
+ // level 0 files are overlapping. So we cannot pick more
+ // than one concurrent compactions at this level. This
+ // could be made better by looking at key-ranges that are
+ // being compacted at level 0.
+ if (start_level_ == 0 &&
+ !compaction_picker_->level0_compactions_in_progress()->empty()) {
+ TEST_SYNC_POINT("LevelCompactionPicker::PickCompactionBySize:0");
+ return false;
+ }
+
+ start_level_inputs_.clear();
+
+ assert(start_level_ >= 0);
+
+ // Pick the largest file in this level that is not already
+ // being compacted
+ const std::vector<int>& file_size =
+ vstorage_->FilesByCompactionPri(start_level_);
+ const std::vector<FileMetaData*>& level_files =
+ vstorage_->LevelFiles(start_level_);
+
+ unsigned int cmp_idx;
+ for (cmp_idx = vstorage_->NextCompactionIndex(start_level_);
+ cmp_idx < file_size.size(); cmp_idx++) {
+ int index = file_size[cmp_idx];
+ auto* f = level_files[index];
+
+ // do not pick a file to compact if it is being compacted
+ // from n-1 level.
+ if (f->being_compacted) {
+ continue;
+ }
+
+ start_level_inputs_.files.push_back(f);
+ start_level_inputs_.level = start_level_;
+ if (!compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
+ &start_level_inputs_) ||
+ compaction_picker_->FilesRangeOverlapWithCompaction(
+ {start_level_inputs_}, output_level_)) {
+ // A locked (pending compaction) input-level file was pulled in due to
+ // user-key overlap.
+ start_level_inputs_.clear();
+ continue;
+ }
+
+ // Now that input level is fully expanded, we check whether any output files
+ // are locked due to pending compaction.
+ //
+ // Note we rely on ExpandInputsToCleanCut() to tell us whether any output-
+ // level files are locked, not just the extra ones pulled in for user-key
+ // overlap.
+ InternalKey smallest, largest;
+ compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest);
+ CompactionInputFiles output_level_inputs;
+ output_level_inputs.level = output_level_;
+ vstorage_->GetOverlappingInputs(output_level_, &smallest, &largest,
+ &output_level_inputs.files);
+ if (!output_level_inputs.empty() &&
+ !compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
+ &output_level_inputs)) {
+ start_level_inputs_.clear();
+ continue;
+ }
+ base_index_ = index;
+ break;
+ }
+
+ // store where to start the iteration in the next call to PickCompaction
+ vstorage_->SetNextCompactionIndex(start_level_, cmp_idx);
+
+ return start_level_inputs_.size() > 0;
+}
+
+bool LevelCompactionBuilder::PickIntraL0Compaction() {
+ start_level_inputs_.clear();
+ const std::vector<FileMetaData*>& level_files =
+ vstorage_->LevelFiles(0 /* level */);
+ if (level_files.size() <
+ static_cast<size_t>(
+ mutable_cf_options_.level0_file_num_compaction_trigger + 2) ||
+ level_files[0]->being_compacted) {
+ // If L0 isn't accumulating much files beyond the regular trigger, don't
+ // resort to L0->L0 compaction yet.
+ return false;
+ }
+ return FindIntraL0Compaction(level_files, kMinFilesForIntraL0Compaction,
+ port::kMaxUint64,
+ mutable_cf_options_.max_compaction_bytes,
+ &start_level_inputs_, earliest_mem_seqno_);
+}
+} // namespace
+
+Compaction* LevelCompactionPicker::PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber earliest_mem_seqno) {
+ LevelCompactionBuilder builder(cf_name, vstorage, earliest_mem_seqno, this,
+ log_buffer, mutable_cf_options, ioptions_);
+ return builder.PickCompaction();
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.h b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.h
new file mode 100644
index 00000000..b82070e1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_level.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/compaction/compaction_picker.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Picking compactions for leveled compaction. See wiki page
+// https://github.com/facebook/rocksdb/wiki/Leveled-Compaction
+// for description of Leveled compaction.
+class LevelCompactionPicker : public CompactionPicker {
+ public:
+ LevelCompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp)
+ : CompactionPicker(ioptions, icmp) {}
+ virtual Compaction* PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber earliest_memtable_seqno = kMaxSequenceNumber) override;
+
+ virtual bool NeedsCompaction(
+ const VersionStorageInfo* vstorage) const override;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc
new file mode 100644
index 00000000..278bdb06
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc
@@ -0,0 +1,1741 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <limits>
+#include <string>
+#include <utility>
+#include "db/compaction/compaction.h"
+#include "db/compaction/compaction_picker_fifo.h"
+#include "db/compaction/compaction_picker_level.h"
+#include "db/compaction/compaction_picker_universal.h"
+
+#include "logging/logging.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class CountingLogger : public Logger {
+ public:
+ using Logger::Logv;
+ void Logv(const char* /*format*/, va_list /*ap*/) override { log_count++; }
+ size_t log_count;
+};
+
+class CompactionPickerTest : public testing::Test {
+ public:
+ const Comparator* ucmp_;
+ InternalKeyComparator icmp_;
+ Options options_;
+ ImmutableCFOptions ioptions_;
+ MutableCFOptions mutable_cf_options_;
+ LevelCompactionPicker level_compaction_picker;
+ std::string cf_name_;
+ CountingLogger logger_;
+ LogBuffer log_buffer_;
+ uint32_t file_num_;
+ CompactionOptionsFIFO fifo_options_;
+ std::unique_ptr<VersionStorageInfo> vstorage_;
+ std::vector<std::unique_ptr<FileMetaData>> files_;
+ // does not own FileMetaData
+ std::unordered_map<uint32_t, std::pair<FileMetaData*, int>> file_map_;
+ // input files to compaction process.
+ std::vector<CompactionInputFiles> input_files_;
+ int compaction_level_start_;
+
+ CompactionPickerTest()
+ : ucmp_(BytewiseComparator()),
+ icmp_(ucmp_),
+ ioptions_(options_),
+ mutable_cf_options_(options_),
+ level_compaction_picker(ioptions_, &icmp_),
+ cf_name_("dummy"),
+ log_buffer_(InfoLogLevel::INFO_LEVEL, &logger_),
+ file_num_(1),
+ vstorage_(nullptr) {
+ mutable_cf_options_.ttl = 0;
+ mutable_cf_options_.periodic_compaction_seconds = 0;
+ // ioptions_.compaction_pri = kMinOverlappingRatio has its own set of
+ // tests to cover.
+ ioptions_.compaction_pri = kByCompensatedSize;
+ fifo_options_.max_table_files_size = 1;
+ mutable_cf_options_.RefreshDerivedOptions(ioptions_);
+ ioptions_.cf_paths.emplace_back("dummy",
+ std::numeric_limits<uint64_t>::max());
+ }
+
+ ~CompactionPickerTest() override {}
+
+ void NewVersionStorage(int num_levels, CompactionStyle style) {
+ DeleteVersionStorage();
+ options_.num_levels = num_levels;
+ vstorage_.reset(new VersionStorageInfo(&icmp_, ucmp_, options_.num_levels,
+ style, nullptr, false));
+ vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ }
+
+ void DeleteVersionStorage() {
+ vstorage_.reset();
+ files_.clear();
+ file_map_.clear();
+ input_files_.clear();
+ }
+
+ void Add(int level, uint32_t file_number, const char* smallest,
+ const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
+ SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
+ size_t compensated_file_size = 0) {
+ assert(level < vstorage_->num_levels());
+ FileMetaData* f = new FileMetaData(
+ file_number, path_id, file_size,
+ InternalKey(smallest, smallest_seq, kTypeValue),
+ InternalKey(largest, largest_seq, kTypeValue), smallest_seq,
+ largest_seq, /* marked_for_compact */ false, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ f->compensated_file_size =
+ (compensated_file_size != 0) ? compensated_file_size : file_size;
+ vstorage_->AddFile(level, f);
+ files_.emplace_back(f);
+ file_map_.insert({file_number, {f, level}});
+ }
+
+ void SetCompactionInputFilesLevels(int level_count, int start_level) {
+ input_files_.resize(level_count);
+ for (int i = 0; i < level_count; ++i) {
+ input_files_[i].level = start_level + i;
+ }
+ compaction_level_start_ = start_level;
+ }
+
+ void AddToCompactionFiles(uint32_t file_number) {
+ auto iter = file_map_.find(file_number);
+ assert(iter != file_map_.end());
+ int level = iter->second.second;
+ assert(level < vstorage_->num_levels());
+ input_files_[level - compaction_level_start_].files.emplace_back(
+ iter->second.first);
+ }
+
+ void UpdateVersionStorageInfo() {
+ vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ vstorage_->UpdateFilesByCompactionPri(ioptions_.compaction_pri);
+ vstorage_->UpdateNumNonEmptyLevels();
+ vstorage_->GenerateFileIndexer();
+ vstorage_->GenerateLevelFilesBrief();
+ vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
+ vstorage_->GenerateLevel0NonOverlapping();
+ vstorage_->ComputeFilesMarkedForCompaction();
+ vstorage_->SetFinalized();
+ }
+};
+
+TEST_F(CompactionPickerTest, Empty) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ UpdateVersionStorageInfo();
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() == nullptr);
+}
+
+TEST_F(CompactionPickerTest, Single) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ Add(0, 1U, "p", "q");
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() == nullptr);
+}
+
+TEST_F(CompactionPickerTest, Level0Trigger) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ Add(0, 1U, "150", "200");
+ Add(0, 2U, "200", "250");
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, Level1Trigger) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(1, 66U, "150", "200", 1000000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, Level1Trigger2) {
+ mutable_cf_options_.target_file_size_base = 10000000000;
+ mutable_cf_options_.RefreshDerivedOptions(ioptions_);
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(1, 66U, "150", "200", 1000000001U);
+ Add(1, 88U, "201", "300", 1000000000U);
+ Add(2, 6U, "150", "179", 1000000000U);
+ Add(2, 7U, "180", "220", 1000000000U);
+ Add(2, 8U, "221", "300", 1000000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(2U, compaction->num_input_files(1));
+ ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
+ ASSERT_EQ(uint64_t{1073741824}, compaction->OutputFilePreallocationSize());
+}
+
+TEST_F(CompactionPickerTest, LevelMaxScore) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.target_file_size_base = 10000000;
+ mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
+ mutable_cf_options_.RefreshDerivedOptions(ioptions_);
+ Add(0, 1U, "150", "200", 1000000U);
+ // Level 1 score 1.2
+ Add(1, 66U, "150", "200", 6000000U);
+ Add(1, 88U, "201", "300", 6000000U);
+ // Level 2 score 1.8. File 7 is the largest. Should be picked
+ Add(2, 6U, "150", "179", 60000000U);
+ Add(2, 7U, "180", "220", 60000001U);
+ Add(2, 8U, "221", "300", 60000000U);
+ // Level 3 score slightly larger than 1
+ Add(3, 26U, "150", "170", 260000000U);
+ Add(3, 27U, "171", "179", 260000000U);
+ Add(3, 28U, "191", "220", 260000000U);
+ Add(3, 29U, "221", "300", 260000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(mutable_cf_options_.target_file_size_base +
+ mutable_cf_options_.target_file_size_base / 10,
+ compaction->OutputFilePreallocationSize());
+}
+
+TEST_F(CompactionPickerTest, NeedsCompactionLevel) {
+ const int kLevels = 6;
+ const int kFileCount = 20;
+
+ for (int level = 0; level < kLevels - 1; ++level) {
+ NewVersionStorage(kLevels, kCompactionStyleLevel);
+ uint64_t file_size = vstorage_->MaxBytesForLevel(level) * 2 / kFileCount;
+ for (int file_count = 1; file_count <= kFileCount; ++file_count) {
+ // start a brand new version in each test.
+ NewVersionStorage(kLevels, kCompactionStyleLevel);
+ for (int i = 0; i < file_count; ++i) {
+ Add(level, i, ToString((i + 100) * 1000).c_str(),
+ ToString((i + 100) * 1000 + 999).c_str(),
+ file_size, 0, i * 100, i * 100 + 99);
+ }
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(vstorage_->CompactionScoreLevel(0), level);
+ ASSERT_EQ(level_compaction_picker.NeedsCompaction(vstorage_.get()),
+ vstorage_->CompactionScore(0) >= 1);
+ // release the version storage
+ DeleteVersionStorage();
+ }
+ }
+}
+
+TEST_F(CompactionPickerTest, Level0TriggerDynamic) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200");
+ Add(0, 2U, "200", "250");
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(1, static_cast<int>(compaction->num_input_levels()));
+ ASSERT_EQ(num_levels - 1, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, Level0TriggerDynamic2) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200");
+ Add(0, 2U, "200", "250");
+ Add(num_levels - 1, 3U, "200", "250", 300U);
+
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(vstorage_->base_level(), num_levels - 2);
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(1, static_cast<int>(compaction->num_input_levels()));
+ ASSERT_EQ(num_levels - 2, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, Level0TriggerDynamic3) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200");
+ Add(0, 2U, "200", "250");
+ Add(num_levels - 1, 3U, "200", "250", 300U);
+ Add(num_levels - 1, 4U, "300", "350", 3000U);
+
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(vstorage_->base_level(), num_levels - 3);
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(1, static_cast<int>(compaction->num_input_levels()));
+ ASSERT_EQ(num_levels - 3, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, Level0TriggerDynamic4) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200");
+ Add(0, 2U, "200", "250");
+ Add(num_levels - 1, 3U, "200", "250", 300U);
+ Add(num_levels - 1, 4U, "300", "350", 3000U);
+ Add(num_levels - 3, 5U, "150", "180", 3U);
+ Add(num_levels - 3, 6U, "181", "300", 3U);
+ Add(num_levels - 3, 7U, "400", "450", 3U);
+
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(vstorage_->base_level(), num_levels - 3);
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->num_input_files(1));
+ ASSERT_EQ(num_levels - 3, compaction->level(1));
+ ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(1, 1)->fd.GetNumber());
+ ASSERT_EQ(2, static_cast<int>(compaction->num_input_levels()));
+ ASSERT_EQ(num_levels - 3, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ ioptions_.compaction_pri = kMinOverlappingRatio;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200");
+ Add(num_levels - 1, 3U, "200", "250", 300U);
+ Add(num_levels - 1, 4U, "300", "350", 3000U);
+ Add(num_levels - 1, 4U, "400", "450", 3U);
+ Add(num_levels - 2, 5U, "150", "180", 300U);
+ Add(num_levels - 2, 6U, "181", "350", 500U);
+ Add(num_levels - 2, 7U, "400", "450", 200U);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(0, compaction->num_input_files(1));
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(num_levels - 1, compaction->output_level());
+}
+
+// Universal and FIFO Compactions are not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
+ NewVersionStorage(1, kCompactionStyleUniversal);
+ UniversalCompactionPicker universal_compaction_picker(
+ ioptions_, &icmp_);
+ UpdateVersionStorageInfo();
+ // must return false when there's no files.
+ ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
+ false);
+
+ // verify the trigger given different number of L0 files.
+ for (int i = 1;
+ i <= mutable_cf_options_.level0_file_num_compaction_trigger * 2; ++i) {
+ NewVersionStorage(1, kCompactionStyleUniversal);
+ Add(0, i, ToString((i + 100) * 1000).c_str(),
+ ToString((i + 100) * 1000 + 999).c_str(), 1000000, 0, i * 100,
+ i * 100 + 99);
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(level_compaction_picker.NeedsCompaction(vstorage_.get()),
+ vstorage_->CompactionScore(0) >= 1);
+ }
+}
+
+TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
+ const uint64_t kFileSize = 100000;
+ NewVersionStorage(1, kCompactionStyleUniversal);
+ ioptions_.allow_ingest_behind = true;
+ ioptions_.num_levels = 3;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+ UpdateVersionStorageInfo();
+ // must return false when there's no files.
+ ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
+ false);
+
+ NewVersionStorage(3, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
+ Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
+ Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
+ Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
+ Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ // output level should be the one above the bottom-most
+ ASSERT_EQ(1, compaction->output_level());
+}
+// Tests if the files can be trivially moved in multi level
+// universal compaction when allow_trivial_move option is set
+// In this test as the input files overlaps, they cannot
+// be trivially moved.
+
+TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) {
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.compaction_options_universal.allow_trivial_move = true;
+ NewVersionStorage(1, kCompactionStyleUniversal);
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+ UpdateVersionStorageInfo();
+ // must return false when there's no files.
+ ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
+ false);
+
+ NewVersionStorage(3, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
+ Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
+ Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
+ Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
+ Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ ASSERT_TRUE(!compaction->is_trivial_move());
+}
+// Tests if the files can be trivially moved in multi level
+// universal compaction when allow_trivial_move option is set
+// In this test as the input files doesn't overlaps, they should
+// be trivially moved.
+TEST_F(CompactionPickerTest, AllowsTrivialMoveUniversal) {
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.compaction_options_universal.allow_trivial_move = true;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(3, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
+ Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
+ Add(1, 5U, "010", "080", kFileSize, 0, 200, 251);
+ Add(2, 3U, "301", "350", kFileSize, 0, 101, 150);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ ASSERT_TRUE(compaction->is_trivial_move());
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction1) {
+ // The case where universal periodic compaction can be picked
+ // with some newer files being compacted.
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
+ Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
+ Add(3, 5U, "010", "080", kFileSize, 0, 200, 251);
+ Add(4, 3U, "301", "350", kFileSize, 0, 101, 150);
+ Add(4, 6U, "501", "750", kFileSize, 0, 101, 150);
+
+ file_map_[2].first->being_compacted = true;
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(4, file_map_[3].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ ASSERT_TRUE(compaction);
+ ASSERT_EQ(4, compaction->output_level());
+ ASSERT_EQ(0, compaction->start_level());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction2) {
+ // The case where universal periodic compaction does not
+ // pick up only level to compact if it doesn't cover
+ // any file marked as periodic compaction.
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(3, 5U, "010", "080", kFileSize, 0, 200, 251);
+ Add(4, 3U, "301", "350", kFileSize, 0, 101, 150);
+ Add(4, 6U, "501", "750", kFileSize, 0, 101, 150);
+
+ file_map_[5].first->being_compacted = true;
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(0, file_map_[1].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ ASSERT_FALSE(compaction);
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction3) {
+ // The case where universal periodic compaction does not
+ // pick up only the last sorted run which is an L0 file if it isn't
+ // marked as periodic compaction.
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(0, 5U, "010", "080", kFileSize, 0, 200, 251);
+ Add(0, 6U, "501", "750", kFileSize, 0, 101, 150);
+
+ file_map_[5].first->being_compacted = true;
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(0, file_map_[1].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+
+ ASSERT_FALSE(compaction);
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction4) {
+ // The case where universal periodic compaction couldn't form
+ // a compaction that inlcudes any file marked for periodic compaction.
+ // Right now we form the compaction anyway if it is more than one
+ // sorted run. Just put the case here to validate that it doesn't
+ // crash.
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
+ Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
+ Add(3, 5U, "010", "080", kFileSize, 0, 200, 251);
+ Add(4, 3U, "301", "350", kFileSize, 0, 101, 150);
+ Add(4, 6U, "501", "750", kFileSize, 0, 101, 150);
+
+ file_map_[2].first->being_compacted = true;
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(0, file_map_[2].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(!compaction ||
+ compaction->start_level() != compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction5) {
+ // Test single L0 file periodic compaction triggering.
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(0, 6U, "150", "200", kFileSize, 0, 500, 550);
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(0, file_map_[6].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction);
+ ASSERT_EQ(0, compaction->start_level());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(4, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, UniversalPeriodicCompaction6) {
+ // Test single sorted run non-L0 periodic compaction
+ const uint64_t kFileSize = 100000;
+
+ mutable_cf_options_.periodic_compaction_seconds = 1000;
+ UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
+
+ NewVersionStorage(5, kCompactionStyleUniversal);
+
+ Add(4, 5U, "150", "200", kFileSize, 0, 500, 550);
+ Add(4, 6U, "350", "400", kFileSize, 0, 500, 550);
+ UpdateVersionStorageInfo();
+ vstorage_->TEST_AddFileMarkedForPeriodicCompaction(4, file_map_[6].first);
+
+ std::unique_ptr<Compaction> compaction(
+ universal_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction);
+ ASSERT_EQ(4, compaction->start_level());
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(4, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
+ NewVersionStorage(1, kCompactionStyleFIFO);
+ const int kFileCount =
+ mutable_cf_options_.level0_file_num_compaction_trigger * 3;
+ const uint64_t kFileSize = 100000;
+ const uint64_t kMaxSize = kFileSize * kFileCount / 2;
+
+ fifo_options_.max_table_files_size = kMaxSize;
+ mutable_cf_options_.compaction_options_fifo = fifo_options_;
+ FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
+ UpdateVersionStorageInfo();
+ // must return false when there's no files.
+ ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), false);
+
+ // verify whether compaction is needed based on the current
+ // size of L0 files.
+ uint64_t current_size = 0;
+ for (int i = 1; i <= kFileCount; ++i) {
+ NewVersionStorage(1, kCompactionStyleFIFO);
+ Add(0, i, ToString((i + 100) * 1000).c_str(),
+ ToString((i + 100) * 1000 + 999).c_str(),
+ kFileSize, 0, i * 100, i * 100 + 99);
+ current_size += kFileSize;
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()),
+ vstorage_->CompactionScore(0) >= 1);
+ }
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ ioptions_.compaction_pri = kMinOverlappingRatio;
+ mutable_cf_options_.target_file_size_base = 100000000000;
+ mutable_cf_options_.target_file_size_multiplier = 10;
+ mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
+ mutable_cf_options_.RefreshDerivedOptions(ioptions_);
+
+ Add(2, 6U, "150", "179", 50000000U);
+ Add(2, 7U, "180", "220", 50000000U);
+ Add(2, 8U, "321", "400", 50000000U); // File not overlapping
+ Add(2, 9U, "721", "800", 50000000U);
+
+ Add(3, 26U, "150", "170", 260000000U);
+ Add(3, 27U, "171", "179", 260000000U);
+ Add(3, 28U, "191", "220", 260000000U);
+ Add(3, 29U, "221", "300", 260000000U);
+ Add(3, 30U, "750", "900", 260000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ // Pick file 8 because it overlaps with 0 files on level 3.
+ ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber());
+ // Compaction input size * 1.1
+ ASSERT_GE(uint64_t{55000000}, compaction->OutputFilePreallocationSize());
+}
+
+TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ ioptions_.compaction_pri = kMinOverlappingRatio;
+ mutable_cf_options_.target_file_size_base = 10000000;
+ mutable_cf_options_.target_file_size_multiplier = 10;
+ mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
+
+ Add(2, 6U, "150", "175",
+ 60000000U); // Overlaps with file 26, 27, total size 521M
+ Add(2, 7U, "176", "200", 60000000U); // Overlaps with file 27, 28, total size
+ // 520M, the smalelst overlapping
+ Add(2, 8U, "201", "300",
+ 60000000U); // Overlaps with file 28, 29, total size 521M
+
+ Add(3, 26U, "100", "110", 261000000U);
+ Add(3, 26U, "150", "170", 261000000U);
+ Add(3, 27U, "171", "179", 260000000U);
+ Add(3, 28U, "191", "220", 260000000U);
+ Add(3, 29U, "221", "300", 261000000U);
+ Add(3, 30U, "321", "400", 261000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ // Picking file 7 because overlapping ratio is the biggest.
+ ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, CompactionPriMinOverlapping3) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ ioptions_.compaction_pri = kMinOverlappingRatio;
+ mutable_cf_options_.max_bytes_for_level_base = 10000000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+
+ // file 7 and 8 over lap with the same file, but file 8 is smaller so
+ // it will be picked.
+ Add(2, 6U, "150", "167", 60000000U); // Overlaps with file 26, 27
+ Add(2, 7U, "168", "169", 60000000U); // Overlaps with file 27
+ Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 28, but the file
+ // itself is larger. Should be picked.
+
+ Add(3, 26U, "160", "165", 260000000U);
+ Add(3, 27U, "166", "170", 260000000U);
+ Add(3, 28U, "180", "400", 260000000U);
+ Add(3, 29U, "401", "500", 260000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ // Picking file 8 because overlapping ratio is the biggest.
+ ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, CompactionPriMinOverlapping4) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ ioptions_.compaction_pri = kMinOverlappingRatio;
+ mutable_cf_options_.max_bytes_for_level_base = 10000000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+
+ // file 7 and 8 over lap with the same file, but file 8 is smaller so
+ // it will be picked.
+ // Overlaps with file 26, 27. And the file is compensated so will be
+ // picked up.
+ Add(2, 6U, "150", "167", 60000000U, 0, 100, 100, 180000000U);
+ Add(2, 7U, "168", "169", 60000000U); // Overlaps with file 27
+ Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 28
+
+ Add(3, 26U, "160", "165", 60000000U);
+ // Boosted file size in output level is not considered.
+ Add(3, 27U, "166", "170", 60000000U, 0, 100, 100, 260000000U);
+ Add(3, 28U, "180", "400", 60000000U);
+ Add(3, 29U, "401", "500", 60000000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ // Picking file 8 because overlapping ratio is the biggest.
+ ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber());
+}
+
+// This test exhibits the bug where we don't properly reset parent_index in
+// PickCompaction()
+TEST_F(CompactionPickerTest, ParentIndexResetBug) {
+ int num_levels = ioptions_.num_levels;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 200;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200"); // <- marked for compaction
+ Add(1, 3U, "400", "500", 600); // <- this one needs compacting
+ Add(2, 4U, "150", "200");
+ Add(2, 5U, "201", "210");
+ Add(2, 6U, "300", "310");
+ Add(2, 7U, "400", "500"); // <- being compacted
+
+ vstorage_->LevelFiles(2)[3]->being_compacted = true;
+ vstorage_->LevelFiles(0)[0]->marked_for_compaction = true;
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+}
+
+// This test checks ExpandWhileOverlapping() by having overlapping user keys
+// ranges (with different sequence numbers) in the input files.
+TEST_F(CompactionPickerTest, OverlappingUserKeys) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ ioptions_.compaction_pri = kByCompensatedSize;
+
+ Add(1, 1U, "100", "150", 1U);
+ // Overlapping user keys
+ Add(1, 2U, "200", "400", 1U);
+ Add(1, 3U, "400", "500", 1000000000U, 0, 0);
+ Add(2, 4U, "600", "700", 1U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys2) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // Overlapping user keys on same level and output level
+ Add(1, 1U, "200", "400", 1000000000U);
+ Add(1, 2U, "400", "500", 1U, 0, 0);
+ Add(2, 3U, "000", "100", 1U);
+ Add(2, 4U, "100", "600", 1U, 0, 0);
+ Add(2, 5U, "600", "700", 1U, 0, 0);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(2U, compaction->num_input_files(0));
+ ASSERT_EQ(3U, compaction->num_input_files(1));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(4U, compaction->input(1, 1)->fd.GetNumber());
+ ASSERT_EQ(5U, compaction->input(1, 2)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys3) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // Chain of overlapping user key ranges (forces ExpandWhileOverlapping() to
+ // expand multiple times)
+ Add(1, 1U, "100", "150", 1U);
+ Add(1, 2U, "150", "200", 1U, 0, 0);
+ Add(1, 3U, "200", "250", 1000000000U, 0, 0);
+ Add(1, 4U, "250", "300", 1U, 0, 0);
+ Add(1, 5U, "300", "350", 1U, 0, 0);
+ // Output level overlaps with the beginning and the end of the chain
+ Add(2, 6U, "050", "100", 1U);
+ Add(2, 7U, "350", "400", 1U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(5U, compaction->num_input_files(0));
+ ASSERT_EQ(2U, compaction->num_input_files(1));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(0, 2)->fd.GetNumber());
+ ASSERT_EQ(4U, compaction->input(0, 3)->fd.GetNumber());
+ ASSERT_EQ(5U, compaction->input(0, 4)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys4) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_bytes_for_level_base = 1000000;
+
+ Add(1, 1U, "100", "150", 1U);
+ Add(1, 2U, "150", "199", 1U, 0, 0);
+ Add(1, 3U, "200", "250", 1100000U, 0, 0);
+ Add(1, 4U, "251", "300", 1U, 0, 0);
+ Add(1, 5U, "300", "350", 1U, 0, 0);
+
+ Add(2, 6U, "100", "115", 1U);
+ Add(2, 7U, "125", "325", 1U);
+ Add(2, 8U, "350", "400", 1U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->num_input_files(1));
+ ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys5) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // Overlapping user keys on same level and output level
+ Add(1, 1U, "200", "400", 1000000000U);
+ Add(1, 2U, "400", "500", 1U, 0, 0);
+ Add(2, 3U, "000", "100", 1U);
+ Add(2, 4U, "100", "600", 1U, 0, 0);
+ Add(2, 5U, "600", "700", 1U, 0, 0);
+
+ vstorage_->LevelFiles(2)[2]->being_compacted = true;
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() == nullptr);
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys6) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // Overlapping user keys on same level and output level
+ Add(1, 1U, "200", "400", 1U, 0, 0);
+ Add(1, 2U, "401", "500", 1U, 0, 0);
+ Add(2, 3U, "000", "100", 1U);
+ Add(2, 4U, "100", "300", 1U, 0, 0);
+ Add(2, 5U, "305", "450", 1U, 0, 0);
+ Add(2, 6U, "460", "600", 1U, 0, 0);
+ Add(2, 7U, "600", "700", 1U, 0, 0);
+
+ vstorage_->LevelFiles(1)[0]->marked_for_compaction = true;
+ vstorage_->LevelFiles(1)[1]->marked_for_compaction = true;
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(3U, compaction->num_input_files(1));
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys7) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+ // Overlapping user keys on same level and output level
+ Add(1, 1U, "200", "400", 1U, 0, 0);
+ Add(1, 2U, "401", "500", 1000000000U, 0, 0);
+ Add(2, 3U, "100", "250", 1U);
+ Add(2, 4U, "300", "600", 1U, 0, 0);
+ Add(2, 5U, "600", "800", 1U, 0, 0);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_GE(1U, compaction->num_input_files(0));
+ ASSERT_GE(2U, compaction->num_input_files(1));
+ // File 5 has to be included in the compaction
+ ASSERT_EQ(5U, compaction->inputs(1)->back()->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys8) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+ // grow the number of inputs in "level" without
+ // changing the number of "level+1" files we pick up
+ // Expand input level as much as possible
+ // no overlapping case
+ Add(1, 1U, "101", "150", 1U);
+ Add(1, 2U, "151", "200", 1U);
+ Add(1, 3U, "201", "300", 1000000000U);
+ Add(1, 4U, "301", "400", 1U);
+ Add(1, 5U, "401", "500", 1U);
+ Add(2, 6U, "150", "200", 1U);
+ Add(2, 7U, "200", "450", 1U, 0, 0);
+ Add(2, 8U, "500", "600", 1U);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(3U, compaction->num_input_files(0));
+ ASSERT_EQ(2U, compaction->num_input_files(1));
+ ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(4U, compaction->input(0, 2)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys9) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+ // grow the number of inputs in "level" without
+ // changing the number of "level+1" files we pick up
+ // Expand input level as much as possible
+ // overlapping case
+ Add(1, 1U, "121", "150", 1U);
+ Add(1, 2U, "151", "200", 1U);
+ Add(1, 3U, "201", "300", 1000000000U);
+ Add(1, 4U, "301", "400", 1U);
+ Add(1, 5U, "401", "500", 1U);
+ Add(2, 6U, "100", "120", 1U);
+ Add(2, 7U, "150", "200", 1U);
+ Add(2, 8U, "200", "450", 1U, 0, 0);
+ Add(2, 9U, "501", "600", 1U);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(5U, compaction->num_input_files(0));
+ ASSERT_EQ(2U, compaction->num_input_files(1));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(0, 2)->fd.GetNumber());
+ ASSERT_EQ(4U, compaction->input(0, 3)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
+ ASSERT_EQ(8U, compaction->input(1, 1)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys10) {
+ // Locked file encountered when pulling in extra input-level files with same
+ // user keys. Verify we pick the next-best file from the same input level.
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+
+ // file_number 2U is largest and thus first choice. But it overlaps with
+ // file_number 1U which is being compacted. So instead we pick the next-
+ // biggest file, 3U, which is eligible for compaction.
+ Add(1 /* level */, 1U /* file_number */, "100" /* smallest */,
+ "150" /* largest */, 1U /* file_size */);
+ file_map_[1U].first->being_compacted = true;
+ Add(1 /* level */, 2U /* file_number */, "150" /* smallest */,
+ "200" /* largest */, 1000000000U /* file_size */, 0 /* smallest_seq */,
+ 0 /* largest_seq */);
+ Add(1 /* level */, 3U /* file_number */, "201" /* smallest */,
+ "250" /* largest */, 900000000U /* file_size */);
+ Add(2 /* level */, 4U /* file_number */, "100" /* smallest */,
+ "150" /* largest */, 1U /* file_size */);
+ Add(2 /* level */, 5U /* file_number */, "151" /* smallest */,
+ "200" /* largest */, 1U /* file_size */);
+ Add(2 /* level */, 6U /* file_number */, "201" /* smallest */,
+ "250" /* largest */, 1U /* file_size */);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->num_input_files(1));
+ ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, OverlappingUserKeys11) {
+ // Locked file encountered when pulling in extra output-level files with same
+ // user keys. Expected to skip that compaction and pick the next-best choice.
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+
+ // score(L1) = 3.7
+ // score(L2) = 1.85
+ // There is no eligible file in L1 to compact since both candidates pull in
+ // file_number 5U, which overlaps with a file pending compaction (6U). The
+ // first eligible compaction is from L2->L3.
+ Add(1 /* level */, 2U /* file_number */, "151" /* smallest */,
+ "200" /* largest */, 1000000000U /* file_size */);
+ Add(1 /* level */, 3U /* file_number */, "201" /* smallest */,
+ "250" /* largest */, 1U /* file_size */);
+ Add(2 /* level */, 4U /* file_number */, "100" /* smallest */,
+ "149" /* largest */, 5000000000U /* file_size */);
+ Add(2 /* level */, 5U /* file_number */, "150" /* smallest */,
+ "201" /* largest */, 1U /* file_size */);
+ Add(2 /* level */, 6U /* file_number */, "201" /* smallest */,
+ "249" /* largest */, 1U /* file_size */, 0 /* smallest_seq */,
+ 0 /* largest_seq */);
+ file_map_[6U].first->being_compacted = true;
+ Add(3 /* level */, 7U /* file_number */, "100" /* smallest */,
+ "149" /* largest */, 1U /* file_size */);
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->num_input_files(1));
+ ASSERT_EQ(4U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 900000000U;
+
+ // 6 L0 files, score 3.
+ Add(0, 1U, "000", "400", 1U);
+ Add(0, 2U, "001", "400", 1U, 0, 0);
+ Add(0, 3U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 31U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 32U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 33U, "001", "400", 1000000000U, 0, 0);
+
+ // L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
+ Add(1, 4U, "050", "300", 1000000000U, 0, 0);
+ file_map_[4u].first->being_compacted = true;
+ Add(1, 5U, "301", "350", 1000000000U, 0, 0);
+
+ // Output level overlaps with the beginning and the end of the chain
+ Add(2, 6U, "050", "100", 1U);
+ Add(2, 7U, "300", "400", 1U);
+
+ // No compaction should be scheduled, if L0 has higher priority than L1
+ // but L0->L1 compaction is blocked by a file in L1 being compacted.
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(0, vstorage_->CompactionScoreLevel(0));
+ ASSERT_EQ(1, vstorage_->CompactionScoreLevel(1));
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() == nullptr);
+}
+
+TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri2) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 900000000U;
+
+ // 6 L0 files, score 3.
+ Add(0, 1U, "000", "400", 1U);
+ Add(0, 2U, "001", "400", 1U, 0, 0);
+ Add(0, 3U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 31U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 32U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 33U, "001", "400", 1000000000U, 0, 0);
+
+ // L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
+ Add(1, 4U, "050", "300", 1000000000U, 0, 0);
+ Add(1, 5U, "301", "350", 1000000000U, 0, 0);
+
+ // Output level overlaps with the beginning and the end of the chain
+ Add(2, 6U, "050", "100", 1U);
+ Add(2, 7U, "300", "400", 1U);
+
+ // If no file in L1 being compacted, L0->L1 compaction will be scheduled.
+ UpdateVersionStorageInfo(); // being_compacted flag is cleared here.
+ ASSERT_EQ(0, vstorage_->CompactionScoreLevel(0));
+ ASSERT_EQ(1, vstorage_->CompactionScoreLevel(1));
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+}
+
+TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri3) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.level0_file_num_compaction_trigger = 2;
+ mutable_cf_options_.max_bytes_for_level_base = 900000000U;
+
+ // 6 L0 files, score 3.
+ Add(0, 1U, "000", "400", 1U);
+ Add(0, 2U, "001", "400", 1U, 0, 0);
+ Add(0, 3U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 31U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 32U, "001", "400", 1000000000U, 0, 0);
+ Add(0, 33U, "001", "400", 1000000000U, 0, 0);
+
+ // L1 score more than 6.
+ Add(1, 4U, "050", "300", 1000000000U, 0, 0);
+ file_map_[4u].first->being_compacted = true;
+ Add(1, 5U, "301", "350", 1000000000U, 0, 0);
+ Add(1, 51U, "351", "400", 6000000000U, 0, 0);
+
+ // Output level overlaps with the beginning and the end of the chain
+ Add(2, 6U, "050", "100", 1U);
+ Add(2, 7U, "300", "400", 1U);
+
+ // If score in L1 is larger than L0, L1 compaction goes through despite
+ // there is pending L0 compaction.
+ UpdateVersionStorageInfo();
+ ASSERT_EQ(1, vstorage_->CompactionScoreLevel(0));
+ ASSERT_EQ(0, vstorage_->CompactionScoreLevel(1));
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+}
+
+TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded1) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 4;
+ mutable_cf_options_.max_bytes_for_level_base = 1000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200", 200);
+ Add(0, 2U, "150", "200", 200);
+ Add(0, 3U, "150", "200", 200);
+ // Level 1 is over target by 200
+ Add(1, 4U, "400", "500", 600);
+ Add(1, 5U, "600", "700", 600);
+ // Level 2 is less than target 10000 even added size of level 1
+ // Size ratio of L2/L1 is 9600 / 1200 = 8
+ Add(2, 6U, "150", "200", 2500);
+ Add(2, 7U, "201", "210", 2000);
+ Add(2, 8U, "300", "310", 2600);
+ Add(2, 9U, "400", "500", 2500);
+ // Level 3 exceeds target 100,000 of 1000
+ Add(3, 10U, "400", "500", 101000);
+ // Level 4 exceeds target 1,000,000 by 900 after adding size from level 3
+ // Size ratio L4/L3 is 9.9
+ // After merge from L3, L4 size is 1000900
+ Add(4, 11U, "400", "500", 999900);
+ Add(5, 11U, "400", "500", 8007200);
+
+ UpdateVersionStorageInfo();
+
+ ASSERT_EQ(200u * 9u + 10900u + 900u * 9,
+ vstorage_->estimated_compaction_needed_bytes());
+}
+
+TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded2) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_bytes_for_level_base = 1000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200", 200);
+ Add(0, 2U, "150", "200", 200);
+ Add(0, 4U, "150", "200", 200);
+ Add(0, 5U, "150", "200", 200);
+ Add(0, 6U, "150", "200", 200);
+ // Level 1 size will be 1400 after merging with L0
+ Add(1, 7U, "400", "500", 200);
+ Add(1, 8U, "600", "700", 200);
+ // Level 2 is less than target 10000 even added size of level 1
+ Add(2, 9U, "150", "200", 9100);
+ // Level 3 over the target, but since level 4 is empty, we assume it will be
+ // a trivial move.
+ Add(3, 10U, "400", "500", 101000);
+
+ UpdateVersionStorageInfo();
+
+ // estimated L1->L2 merge: 400 * (9100.0 / 1400.0 + 1.0)
+ ASSERT_EQ(1400u + 3000u, vstorage_->estimated_compaction_needed_bytes());
+}
+
+TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded3) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_bytes_for_level_base = 1000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+ Add(0, 1U, "150", "200", 2000);
+ Add(0, 2U, "150", "200", 2000);
+ Add(0, 4U, "150", "200", 2000);
+ Add(0, 5U, "150", "200", 2000);
+ Add(0, 6U, "150", "200", 1000);
+ // Level 1 size will be 10000 after merging with L0
+ Add(1, 7U, "400", "500", 500);
+ Add(1, 8U, "600", "700", 500);
+
+ Add(2, 9U, "150", "200", 10000);
+
+ UpdateVersionStorageInfo();
+
+ ASSERT_EQ(10000u + 18000u, vstorage_->estimated_compaction_needed_bytes());
+}
+
+TEST_F(CompactionPickerTest, EstimateCompactionBytesNeededDynamicLevel) {
+ int num_levels = ioptions_.num_levels;
+ ioptions_.level_compaction_dynamic_level_bytes = true;
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_bytes_for_level_base = 1000;
+ mutable_cf_options_.max_bytes_for_level_multiplier = 10;
+ NewVersionStorage(num_levels, kCompactionStyleLevel);
+
+ // Set Last level size 50000
+ // num_levels - 1 target 5000
+ // num_levels - 2 is base level with target 1000 (rounded up to
+ // max_bytes_for_level_base).
+ Add(num_levels - 1, 10U, "400", "500", 50000);
+
+ Add(0, 1U, "150", "200", 200);
+ Add(0, 2U, "150", "200", 200);
+ Add(0, 4U, "150", "200", 200);
+ Add(0, 5U, "150", "200", 200);
+ Add(0, 6U, "150", "200", 200);
+ // num_levels - 3 is over target by 100 + 1000
+ Add(num_levels - 3, 7U, "400", "500", 550);
+ Add(num_levels - 3, 8U, "600", "700", 550);
+ // num_levels - 2 is over target by 1100 + 200
+ Add(num_levels - 2, 9U, "150", "200", 5200);
+
+ UpdateVersionStorageInfo();
+
+ // Merging to the second last level: (5200 / 2100 + 1) * 1100
+ // Merging to the last level: (50000 / 6300 + 1) * 1300
+ ASSERT_EQ(2100u + 3823u + 11617u,
+ vstorage_->estimated_compaction_needed_bytes());
+}
+
+TEST_F(CompactionPickerTest, IsBottommostLevelTest) {
+ // case 1: Higher levels are empty
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "a", "m");
+ Add(0, 2U, "c", "z");
+ Add(1, 3U, "d", "e");
+ Add(1, 4U, "l", "p");
+ Add(2, 5U, "g", "i");
+ Add(2, 6U, "x", "z");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 1);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(5U);
+ bool result =
+ Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_TRUE(result);
+
+ // case 2: Higher levels have no overlap
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "a", "m");
+ Add(0, 2U, "c", "z");
+ Add(1, 3U, "d", "e");
+ Add(1, 4U, "l", "p");
+ Add(2, 5U, "g", "i");
+ Add(2, 6U, "x", "z");
+ Add(3, 7U, "k", "p");
+ Add(3, 8U, "t", "w");
+ Add(4, 9U, "a", "b");
+ Add(5, 10U, "c", "cc");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 1);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(5U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_TRUE(result);
+
+ // case 3.1: Higher levels (level 3) have overlap
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "a", "m");
+ Add(0, 2U, "c", "z");
+ Add(1, 3U, "d", "e");
+ Add(1, 4U, "l", "p");
+ Add(2, 5U, "g", "i");
+ Add(2, 6U, "x", "z");
+ Add(3, 7U, "e", "g");
+ Add(3, 8U, "h", "k");
+ Add(4, 9U, "a", "b");
+ Add(5, 10U, "c", "cc");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 1);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(5U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_FALSE(result);
+
+ // case 3.2: Higher levels (level 5) have overlap
+ DeleteVersionStorage();
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "a", "m");
+ Add(0, 2U, "c", "z");
+ Add(1, 3U, "d", "e");
+ Add(1, 4U, "l", "p");
+ Add(2, 5U, "g", "i");
+ Add(2, 6U, "x", "z");
+ Add(3, 7U, "j", "k");
+ Add(3, 8U, "l", "m");
+ Add(4, 9U, "a", "b");
+ Add(5, 10U, "c", "cc");
+ Add(5, 11U, "h", "k");
+ Add(5, 12U, "y", "yy");
+ Add(5, 13U, "z", "zz");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 1);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(5U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_FALSE(result);
+
+ // case 3.3: Higher levels (level 5) have overlap, but it's only overlapping
+ // one key ("d")
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "a", "m");
+ Add(0, 2U, "c", "z");
+ Add(1, 3U, "d", "e");
+ Add(1, 4U, "l", "p");
+ Add(2, 5U, "g", "i");
+ Add(2, 6U, "x", "z");
+ Add(3, 7U, "j", "k");
+ Add(3, 8U, "l", "m");
+ Add(4, 9U, "a", "b");
+ Add(5, 10U, "c", "cc");
+ Add(5, 11U, "ccc", "d");
+ Add(5, 12U, "y", "yy");
+ Add(5, 13U, "z", "zz");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 1);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(5U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_FALSE(result);
+
+ // Level 0 files overlap
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "s", "t");
+ Add(0, 2U, "a", "m");
+ Add(0, 3U, "b", "z");
+ Add(0, 4U, "e", "f");
+ Add(5, 10U, "y", "z");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(1, 0);
+ AddToCompactionFiles(1U);
+ AddToCompactionFiles(2U);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(4U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_FALSE(result);
+
+ // Level 0 files don't overlap
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "s", "t");
+ Add(0, 2U, "a", "m");
+ Add(0, 3U, "b", "k");
+ Add(0, 4U, "e", "f");
+ Add(5, 10U, "y", "z");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(1, 0);
+ AddToCompactionFiles(1U);
+ AddToCompactionFiles(2U);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(4U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_TRUE(result);
+
+ // Level 1 files overlap
+ NewVersionStorage(6, kCompactionStyleLevel);
+ Add(0, 1U, "s", "t");
+ Add(0, 2U, "a", "m");
+ Add(0, 3U, "b", "k");
+ Add(0, 4U, "e", "f");
+ Add(1, 5U, "a", "m");
+ Add(1, 6U, "n", "o");
+ Add(1, 7U, "w", "y");
+ Add(5, 10U, "y", "z");
+ UpdateVersionStorageInfo();
+ SetCompactionInputFilesLevels(2, 0);
+ AddToCompactionFiles(1U);
+ AddToCompactionFiles(2U);
+ AddToCompactionFiles(3U);
+ AddToCompactionFiles(4U);
+ AddToCompactionFiles(5U);
+ AddToCompactionFiles(6U);
+ AddToCompactionFiles(7U);
+ result = Compaction::TEST_IsBottommostLevel(2, vstorage_.get(), input_files_);
+ ASSERT_FALSE(result);
+
+ DeleteVersionStorage();
+}
+
+TEST_F(CompactionPickerTest, MaxCompactionBytesHit) {
+ mutable_cf_options_.max_bytes_for_level_base = 1000000u;
+ mutable_cf_options_.max_compaction_bytes = 800000u;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // A compaction should be triggered and pick file 2 and 5.
+ // It can expand because adding file 1 and 3, the compaction size will
+ // exceed mutable_cf_options_.max_bytes_for_level_base.
+ Add(1, 1U, "100", "150", 300000U);
+ Add(1, 2U, "151", "200", 300001U, 0, 0);
+ Add(1, 3U, "201", "250", 300000U, 0, 0);
+ Add(1, 4U, "251", "300", 300000U, 0, 0);
+ Add(2, 5U, "100", "256", 1U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->num_input_files(1));
+ ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, MaxCompactionBytesNotHit) {
+ mutable_cf_options_.max_bytes_for_level_base = 800000u;
+ mutable_cf_options_.max_compaction_bytes = 1000000u;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // A compaction should be triggered and pick file 2 and 5.
+ // and it expands to file 1 and 3 too.
+ Add(1, 1U, "100", "150", 300000U);
+ Add(1, 2U, "151", "200", 300001U, 0, 0);
+ Add(1, 3U, "201", "250", 300000U, 0, 0);
+ Add(1, 4U, "251", "300", 300000U, 0, 0);
+ Add(2, 5U, "000", "251", 1U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(2U, compaction->num_input_levels());
+ ASSERT_EQ(3U, compaction->num_input_files(0));
+ ASSERT_EQ(1U, compaction->num_input_files(1));
+ ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
+ ASSERT_EQ(3U, compaction->input(0, 2)->fd.GetNumber());
+ ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
+}
+
+TEST_F(CompactionPickerTest, IsTrivialMoveOn) {
+ mutable_cf_options_.max_bytes_for_level_base = 10000u;
+ mutable_cf_options_.max_compaction_bytes = 10001u;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // A compaction should be triggered and pick file 2
+ Add(1, 1U, "100", "150", 3000U);
+ Add(1, 2U, "151", "200", 3001U);
+ Add(1, 3U, "201", "250", 3000U);
+ Add(1, 4U, "251", "300", 3000U);
+
+ Add(3, 5U, "120", "130", 7000U);
+ Add(3, 6U, "170", "180", 7000U);
+ Add(3, 5U, "220", "230", 7000U);
+ Add(3, 5U, "270", "280", 7000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_TRUE(compaction->IsTrivialMove());
+}
+
+TEST_F(CompactionPickerTest, IsTrivialMoveOff) {
+ mutable_cf_options_.max_bytes_for_level_base = 1000000u;
+ mutable_cf_options_.max_compaction_bytes = 10000u;
+ ioptions_.level_compaction_dynamic_level_bytes = false;
+ NewVersionStorage(6, kCompactionStyleLevel);
+ // A compaction should be triggered and pick all files from level 1
+ Add(1, 1U, "100", "150", 300000U, 0, 0);
+ Add(1, 2U, "150", "200", 300000U, 0, 0);
+ Add(1, 3U, "200", "250", 300000U, 0, 0);
+ Add(1, 4U, "250", "300", 300000U, 0, 0);
+
+ Add(3, 5U, "120", "130", 6000U);
+ Add(3, 6U, "140", "150", 6000U);
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_FALSE(compaction->IsTrivialMove());
+}
+
+TEST_F(CompactionPickerTest, CacheNextCompactionIndex) {
+ NewVersionStorage(6, kCompactionStyleLevel);
+ mutable_cf_options_.max_compaction_bytes = 100000000000u;
+
+ Add(1 /* level */, 1U /* file_number */, "100" /* smallest */,
+ "149" /* largest */, 1000000000U /* file_size */);
+ file_map_[1U].first->being_compacted = true;
+ Add(1 /* level */, 2U /* file_number */, "150" /* smallest */,
+ "199" /* largest */, 900000000U /* file_size */);
+ Add(1 /* level */, 3U /* file_number */, "200" /* smallest */,
+ "249" /* largest */, 800000000U /* file_size */);
+ Add(1 /* level */, 4U /* file_number */, "250" /* smallest */,
+ "299" /* largest */, 700000000U /* file_size */);
+ Add(2 /* level */, 5U /* file_number */, "150" /* smallest */,
+ "199" /* largest */, 1U /* file_size */);
+ file_map_[5U].first->being_compacted = true;
+
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(0U, compaction->num_input_files(1));
+ ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(2, vstorage_->NextCompactionIndex(1 /* level */));
+
+ compaction.reset(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(1U, compaction->num_input_files(0));
+ ASSERT_EQ(0U, compaction->num_input_files(1));
+ ASSERT_EQ(4U, compaction->input(0, 0)->fd.GetNumber());
+ ASSERT_EQ(3, vstorage_->NextCompactionIndex(1 /* level */));
+
+ compaction.reset(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() == nullptr);
+ ASSERT_EQ(4, vstorage_->NextCompactionIndex(1 /* level */));
+}
+
+TEST_F(CompactionPickerTest, IntraL0MaxCompactionBytesNotHit) {
+ // Intra L0 compaction triggers only if there are at least
+ // level0_file_num_compaction_trigger + 2 L0 files.
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_compaction_bytes = 1000000u;
+ NewVersionStorage(6, kCompactionStyleLevel);
+
+ // All 5 L0 files will be picked for intra L0 compaction. The one L1 file
+ // spans entire L0 key range and is marked as being compacted to avoid
+ // L0->L1 compaction.
+ Add(0, 1U, "100", "150", 200000U, 0, 100, 101);
+ Add(0, 2U, "151", "200", 200000U, 0, 102, 103);
+ Add(0, 3U, "201", "250", 200000U, 0, 104, 105);
+ Add(0, 4U, "251", "300", 200000U, 0, 106, 107);
+ Add(0, 5U, "301", "350", 200000U, 0, 108, 109);
+ Add(1, 6U, "100", "350", 200000U, 0, 110, 111);
+ vstorage_->LevelFiles(1)[0]->being_compacted = true;
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(5U, compaction->num_input_files(0));
+ ASSERT_EQ(CompactionReason::kLevelL0FilesNum,
+ compaction->compaction_reason());
+ ASSERT_EQ(0, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, IntraL0MaxCompactionBytesHit) {
+ // Intra L0 compaction triggers only if there are at least
+ // level0_file_num_compaction_trigger + 2 L0 files.
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_compaction_bytes = 999999u;
+ NewVersionStorage(6, kCompactionStyleLevel);
+
+ // 4 out of 5 L0 files will be picked for intra L0 compaction due to
+ // max_compaction_bytes limit (the minimum number of files for triggering
+ // intra L0 compaction is 4). The one L1 file spans entire L0 key range and
+ // is marked as being compacted to avoid L0->L1 compaction.
+ Add(0, 1U, "100", "150", 200000U, 0, 100, 101);
+ Add(0, 2U, "151", "200", 200000U, 0, 102, 103);
+ Add(0, 3U, "201", "250", 200000U, 0, 104, 105);
+ Add(0, 4U, "251", "300", 200000U, 0, 106, 107);
+ Add(0, 5U, "301", "350", 200000U, 0, 108, 109);
+ Add(1, 6U, "100", "350", 200000U, 0, 109, 110);
+ vstorage_->LevelFiles(1)[0]->being_compacted = true;
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(4U, compaction->num_input_files(0));
+ ASSERT_EQ(CompactionReason::kLevelL0FilesNum,
+ compaction->compaction_reason());
+ ASSERT_EQ(0, compaction->output_level());
+}
+
+TEST_F(CompactionPickerTest, IntraL0ForEarliestSeqno) {
+ // Intra L0 compaction triggers only if there are at least
+ // level0_file_num_compaction_trigger + 2 L0 files.
+ mutable_cf_options_.level0_file_num_compaction_trigger = 3;
+ mutable_cf_options_.max_compaction_bytes = 999999u;
+ NewVersionStorage(6, kCompactionStyleLevel);
+
+ // 4 out of 6 L0 files will be picked for intra L0 compaction due to
+ // being_compact limit. And the latest one L0 will be skipped due to earliest
+ // seqno. The one L1 file spans entire L0 key range and is marked as being
+ // compacted to avoid L0->L1 compaction.
+ Add(1, 1U, "100", "350", 200000U, 0, 110, 111);
+ Add(0, 2U, "301", "350", 1U, 0, 108, 109);
+ Add(0, 3U, "251", "300", 1U, 0, 106, 107);
+ Add(0, 4U, "201", "250", 1U, 0, 104, 105);
+ Add(0, 5U, "151", "200", 1U, 0, 102, 103);
+ Add(0, 6U, "100", "150", 1U, 0, 100, 101);
+ Add(0, 7U, "100", "100", 1U, 0, 99, 100);
+ vstorage_->LevelFiles(0)[5]->being_compacted = true;
+ vstorage_->LevelFiles(1)[0]->being_compacted = true;
+ UpdateVersionStorageInfo();
+
+ std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
+ cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_, 107));
+ ASSERT_TRUE(compaction.get() != nullptr);
+ ASSERT_EQ(1U, compaction->num_input_levels());
+ ASSERT_EQ(4U, compaction->num_input_files(0));
+ ASSERT_EQ(CompactionReason::kLevelL0FilesNum,
+ compaction->compaction_reason());
+ ASSERT_EQ(0, compaction->output_level());
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc
new file mode 100644
index 00000000..d8b63956
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc
@@ -0,0 +1,1105 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_picker_universal.h"
+#ifndef ROCKSDB_LITE
+
+#include <cinttypes>
+#include <limits>
+#include <queue>
+#include <string>
+#include <utility>
+#include "db/column_family.h"
+#include "file/filename.h"
+#include "logging/log_buffer.h"
+#include "monitoring/statistics.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+// A helper class that form universal compactions. The class is used by
+// UniversalCompactionPicker::PickCompaction().
+// The usage is to create the class, and get the compaction object by calling
+// PickCompaction().
+class UniversalCompactionBuilder {
+ public:
+ UniversalCompactionBuilder(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp,
+ const std::string& cf_name,
+ const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage,
+ UniversalCompactionPicker* picker,
+ LogBuffer* log_buffer)
+ : ioptions_(ioptions),
+ icmp_(icmp),
+ cf_name_(cf_name),
+ mutable_cf_options_(mutable_cf_options),
+ vstorage_(vstorage),
+ picker_(picker),
+ log_buffer_(log_buffer) {}
+
+ // Form and return the compaction object. The caller owns return object.
+ Compaction* PickCompaction();
+
+ private:
+ struct SortedRun {
+ SortedRun(int _level, FileMetaData* _file, uint64_t _size,
+ uint64_t _compensated_file_size, bool _being_compacted)
+ : level(_level),
+ file(_file),
+ size(_size),
+ compensated_file_size(_compensated_file_size),
+ being_compacted(_being_compacted) {
+ assert(compensated_file_size > 0);
+ assert(level != 0 || file != nullptr);
+ }
+
+ void Dump(char* out_buf, size_t out_buf_size,
+ bool print_path = false) const;
+
+ // sorted_run_count is added into the string to print
+ void DumpSizeInfo(char* out_buf, size_t out_buf_size,
+ size_t sorted_run_count) const;
+
+ int level;
+ // `file` Will be null for level > 0. For level = 0, the sorted run is
+ // for this file.
+ FileMetaData* file;
+ // For level > 0, `size` and `compensated_file_size` are sum of sizes all
+ // files in the level. `being_compacted` should be the same for all files
+ // in a non-zero level. Use the value here.
+ uint64_t size;
+ uint64_t compensated_file_size;
+ bool being_compacted;
+ };
+
+ // Pick Universal compaction to limit read amplification
+ Compaction* PickCompactionToReduceSortedRuns(
+ unsigned int ratio, unsigned int max_number_of_files_to_compact);
+
+ // Pick Universal compaction to limit space amplification.
+ Compaction* PickCompactionToReduceSizeAmp();
+
+ Compaction* PickDeleteTriggeredCompaction();
+
+ // Form a compaction from the sorted run indicated by start_index to the
+ // oldest sorted run.
+ // The caller is responsible for making sure that those files are not in
+ // compaction.
+ Compaction* PickCompactionToOldest(size_t start_index,
+ CompactionReason compaction_reason);
+
+ // Try to pick periodic compaction. The caller should only call it
+ // if there is at least one file marked for periodic compaction.
+ // null will be returned if no such a compaction can be formed
+ // because some files are being compacted.
+ Compaction* PickPeriodicCompaction();
+
+ // Used in universal compaction when the enabled_trivial_move
+ // option is set. Checks whether there are any overlapping files
+ // in the input. Returns true if the input files are non
+ // overlapping.
+ bool IsInputFilesNonOverlapping(Compaction* c);
+
+ const ImmutableCFOptions& ioptions_;
+ const InternalKeyComparator* icmp_;
+ double score_;
+ std::vector<SortedRun> sorted_runs_;
+ const std::string& cf_name_;
+ const MutableCFOptions& mutable_cf_options_;
+ VersionStorageInfo* vstorage_;
+ UniversalCompactionPicker* picker_;
+ LogBuffer* log_buffer_;
+
+ static std::vector<SortedRun> CalculateSortedRuns(
+ const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options);
+
+ // Pick a path ID to place a newly generated file, with its estimated file
+ // size.
+ static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options,
+ uint64_t file_size);
+};
+
+// Used in universal compaction when trivial move is enabled.
+// This structure is used for the construction of min heap
+// that contains the file meta data, the level of the file
+// and the index of the file in that level
+
+struct InputFileInfo {
+ InputFileInfo() : f(nullptr), level(0), index(0) {}
+
+ FileMetaData* f;
+ size_t level;
+ size_t index;
+};
+
+// Used in universal compaction when trivial move is enabled.
+// This comparator is used for the construction of min heap
+// based on the smallest key of the file.
+struct SmallestKeyHeapComparator {
+ explicit SmallestKeyHeapComparator(const Comparator* ucmp) { ucmp_ = ucmp; }
+
+ bool operator()(InputFileInfo i1, InputFileInfo i2) const {
+ return (ucmp_->Compare(i1.f->smallest.user_key(),
+ i2.f->smallest.user_key()) > 0);
+ }
+
+ private:
+ const Comparator* ucmp_;
+};
+
+typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
+ SmallestKeyHeapComparator>
+ SmallestKeyHeap;
+
+// This function creates the heap that is used to find if the files are
+// overlapping during universal compaction when the allow_trivial_move
+// is set.
+SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
+ SmallestKeyHeap smallest_key_priority_q =
+ SmallestKeyHeap(SmallestKeyHeapComparator(ucmp));
+
+ InputFileInfo input_file;
+
+ for (size_t l = 0; l < c->num_input_levels(); l++) {
+ if (c->num_input_files(l) != 0) {
+ if (l == 0 && c->start_level() == 0) {
+ for (size_t i = 0; i < c->num_input_files(0); i++) {
+ input_file.f = c->input(0, i);
+ input_file.level = 0;
+ input_file.index = i;
+ smallest_key_priority_q.push(std::move(input_file));
+ }
+ } else {
+ input_file.f = c->input(l, 0);
+ input_file.level = l;
+ input_file.index = 0;
+ smallest_key_priority_q.push(std::move(input_file));
+ }
+ }
+ }
+ return smallest_key_priority_q;
+}
+
+#ifndef NDEBUG
+// smallest_seqno and largest_seqno are set iff. `files` is not empty.
+void GetSmallestLargestSeqno(const std::vector<FileMetaData*>& files,
+ SequenceNumber* smallest_seqno,
+ SequenceNumber* largest_seqno) {
+ bool is_first = true;
+ for (FileMetaData* f : files) {
+ assert(f->fd.smallest_seqno <= f->fd.largest_seqno);
+ if (is_first) {
+ is_first = false;
+ *smallest_seqno = f->fd.smallest_seqno;
+ *largest_seqno = f->fd.largest_seqno;
+ } else {
+ if (f->fd.smallest_seqno < *smallest_seqno) {
+ *smallest_seqno = f->fd.smallest_seqno;
+ }
+ if (f->fd.largest_seqno > *largest_seqno) {
+ *largest_seqno = f->fd.largest_seqno;
+ }
+ }
+ }
+}
+#endif
+} // namespace
+
+// Algorithm that checks to see if there are any overlapping
+// files in the input
+bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
+ auto comparator = icmp_->user_comparator();
+ int first_iter = 1;
+
+ InputFileInfo prev, curr, next;
+
+ SmallestKeyHeap smallest_key_priority_q =
+ create_level_heap(c, icmp_->user_comparator());
+
+ while (!smallest_key_priority_q.empty()) {
+ curr = smallest_key_priority_q.top();
+ smallest_key_priority_q.pop();
+
+ if (first_iter) {
+ prev = curr;
+ first_iter = 0;
+ } else {
+ if (comparator->Compare(prev.f->largest.user_key(),
+ curr.f->smallest.user_key()) >= 0) {
+ // found overlapping files, return false
+ return false;
+ }
+ assert(comparator->Compare(curr.f->largest.user_key(),
+ prev.f->largest.user_key()) > 0);
+ prev = curr;
+ }
+
+ next.f = nullptr;
+
+ if (c->level(curr.level) != 0 &&
+ curr.index < c->num_input_files(curr.level) - 1) {
+ next.f = c->input(curr.level, curr.index + 1);
+ next.level = curr.level;
+ next.index = curr.index + 1;
+ }
+
+ if (next.f) {
+ smallest_key_priority_q.push(std::move(next));
+ }
+ }
+ return true;
+}
+
+bool UniversalCompactionPicker::NeedsCompaction(
+ const VersionStorageInfo* vstorage) const {
+ const int kLevel0 = 0;
+ if (vstorage->CompactionScore(kLevel0) >= 1) {
+ return true;
+ }
+ if (!vstorage->FilesMarkedForPeriodicCompaction().empty()) {
+ return true;
+ }
+ if (!vstorage->FilesMarkedForCompaction().empty()) {
+ return true;
+ }
+ return false;
+}
+
+Compaction* UniversalCompactionPicker::PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber /* earliest_memtable_seqno */) {
+ UniversalCompactionBuilder builder(ioptions_, icmp_, cf_name,
+ mutable_cf_options, vstorage, this,
+ log_buffer);
+ return builder.PickCompaction();
+}
+
+void UniversalCompactionBuilder::SortedRun::Dump(char* out_buf,
+ size_t out_buf_size,
+ bool print_path) const {
+ if (level == 0) {
+ assert(file != nullptr);
+ if (file->fd.GetPathId() == 0 || !print_path) {
+ snprintf(out_buf, out_buf_size, "file %" PRIu64, file->fd.GetNumber());
+ } else {
+ snprintf(out_buf, out_buf_size, "file %" PRIu64
+ "(path "
+ "%" PRIu32 ")",
+ file->fd.GetNumber(), file->fd.GetPathId());
+ }
+ } else {
+ snprintf(out_buf, out_buf_size, "level %d", level);
+ }
+}
+
+void UniversalCompactionBuilder::SortedRun::DumpSizeInfo(
+ char* out_buf, size_t out_buf_size, size_t sorted_run_count) const {
+ if (level == 0) {
+ assert(file != nullptr);
+ snprintf(out_buf, out_buf_size,
+ "file %" PRIu64 "[%" ROCKSDB_PRIszt
+ "] "
+ "with size %" PRIu64 " (compensated size %" PRIu64 ")",
+ file->fd.GetNumber(), sorted_run_count, file->fd.GetFileSize(),
+ file->compensated_file_size);
+ } else {
+ snprintf(out_buf, out_buf_size,
+ "level %d[%" ROCKSDB_PRIszt
+ "] "
+ "with size %" PRIu64 " (compensated size %" PRIu64 ")",
+ level, sorted_run_count, size, compensated_file_size);
+ }
+}
+
+std::vector<UniversalCompactionBuilder::SortedRun>
+UniversalCompactionBuilder::CalculateSortedRuns(
+ const VersionStorageInfo& vstorage, const ImmutableCFOptions& /*ioptions*/,
+ const MutableCFOptions& mutable_cf_options) {
+ std::vector<UniversalCompactionBuilder::SortedRun> ret;
+ for (FileMetaData* f : vstorage.LevelFiles(0)) {
+ ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
+ f->being_compacted);
+ }
+ for (int level = 1; level < vstorage.num_levels(); level++) {
+ uint64_t total_compensated_size = 0U;
+ uint64_t total_size = 0U;
+ bool being_compacted = false;
+ bool is_first = true;
+ for (FileMetaData* f : vstorage.LevelFiles(level)) {
+ total_compensated_size += f->compensated_file_size;
+ total_size += f->fd.GetFileSize();
+ if (mutable_cf_options.compaction_options_universal.allow_trivial_move ==
+ true) {
+ if (f->being_compacted) {
+ being_compacted = f->being_compacted;
+ }
+ } else {
+ // Compaction always includes all files for a non-zero level, so for a
+ // non-zero level, all the files should share the same being_compacted
+ // value.
+ // This assumption is only valid when
+ // mutable_cf_options.compaction_options_universal.allow_trivial_move
+ // is false
+ assert(is_first || f->being_compacted == being_compacted);
+ }
+ if (is_first) {
+ being_compacted = f->being_compacted;
+ is_first = false;
+ }
+ }
+ if (total_compensated_size > 0) {
+ ret.emplace_back(level, nullptr, total_size, total_compensated_size,
+ being_compacted);
+ }
+ }
+ return ret;
+}
+
+// Universal style of compaction. Pick files that are contiguous in
+// time-range to compact.
+Compaction* UniversalCompactionBuilder::PickCompaction() {
+ const int kLevel0 = 0;
+ score_ = vstorage_->CompactionScore(kLevel0);
+ sorted_runs_ =
+ CalculateSortedRuns(*vstorage_, ioptions_, mutable_cf_options_);
+
+ if (sorted_runs_.size() == 0 ||
+ (vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
+ vstorage_->FilesMarkedForCompaction().empty() &&
+ sorted_runs_.size() < (unsigned int)mutable_cf_options_
+ .level0_file_num_compaction_trigger)) {
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: nothing to do\n",
+ cf_name_.c_str());
+ TEST_SYNC_POINT_CALLBACK(
+ "UniversalCompactionBuilder::PickCompaction:Return", nullptr);
+ return nullptr;
+ }
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ ROCKS_LOG_BUFFER_MAX_SZ(
+ log_buffer_, 3072,
+ "[%s] Universal: sorted runs files(%" ROCKSDB_PRIszt "): %s\n",
+ cf_name_.c_str(), sorted_runs_.size(), vstorage_->LevelSummary(&tmp));
+
+ Compaction* c = nullptr;
+ // Periodic compaction has higher priority than other type of compaction
+ // because it's a hard requirement.
+ if (!vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
+ // Always need to do a full compaction for periodic compaction.
+ c = PickPeriodicCompaction();
+ }
+
+ // Check for size amplification.
+ if (c == nullptr &&
+ sorted_runs_.size() >=
+ static_cast<size_t>(
+ mutable_cf_options_.level0_file_num_compaction_trigger)) {
+ if ((c = PickCompactionToReduceSizeAmp()) != nullptr) {
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: compacting for size amp\n",
+ cf_name_.c_str());
+ } else {
+ // Size amplification is within limits. Try reducing read
+ // amplification while maintaining file size ratios.
+ unsigned int ratio =
+ mutable_cf_options_.compaction_options_universal.size_ratio;
+
+ if ((c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX)) != nullptr) {
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: compacting for size ratio\n",
+ cf_name_.c_str());
+ } else {
+ // Size amplification and file size ratios are within configured limits.
+ // If max read amplification is exceeding configured limits, then force
+ // compaction without looking at filesize ratios and try to reduce
+ // the number of files to fewer than level0_file_num_compaction_trigger.
+ // This is guaranteed by NeedsCompaction()
+ assert(sorted_runs_.size() >=
+ static_cast<size_t>(
+ mutable_cf_options_.level0_file_num_compaction_trigger));
+ // Get the total number of sorted runs that are not being compacted
+ int num_sr_not_compacted = 0;
+ for (size_t i = 0; i < sorted_runs_.size(); i++) {
+ if (sorted_runs_[i].being_compacted == false) {
+ num_sr_not_compacted++;
+ }
+ }
+
+ // The number of sorted runs that are not being compacted is greater
+ // than the maximum allowed number of sorted runs
+ if (num_sr_not_compacted >
+ mutable_cf_options_.level0_file_num_compaction_trigger) {
+ unsigned int num_files =
+ num_sr_not_compacted -
+ mutable_cf_options_.level0_file_num_compaction_trigger + 1;
+ if ((c = PickCompactionToReduceSortedRuns(UINT_MAX, num_files)) !=
+ nullptr) {
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: compacting for file num -- %u\n",
+ cf_name_.c_str(), num_files);
+ }
+ }
+ }
+ }
+ }
+
+ if (c == nullptr) {
+ if ((c = PickDeleteTriggeredCompaction()) != nullptr) {
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: delete triggered compaction\n",
+ cf_name_.c_str());
+ }
+ }
+
+ if (c == nullptr) {
+ TEST_SYNC_POINT_CALLBACK(
+ "UniversalCompactionBuilder::PickCompaction:Return", nullptr);
+ return nullptr;
+ }
+
+ if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
+ true &&
+ c->compaction_reason() != CompactionReason::kPeriodicCompaction) {
+ c->set_is_trivial_move(IsInputFilesNonOverlapping(c));
+ }
+
+// validate that all the chosen files of L0 are non overlapping in time
+#ifndef NDEBUG
+ SequenceNumber prev_smallest_seqno = 0U;
+ bool is_first = true;
+
+ size_t level_index = 0U;
+ if (c->start_level() == 0) {
+ for (auto f : *c->inputs(0)) {
+ assert(f->fd.smallest_seqno <= f->fd.largest_seqno);
+ if (is_first) {
+ is_first = false;
+ }
+ prev_smallest_seqno = f->fd.smallest_seqno;
+ }
+ level_index = 1U;
+ }
+ for (; level_index < c->num_input_levels(); level_index++) {
+ if (c->num_input_files(level_index) != 0) {
+ SequenceNumber smallest_seqno = 0U;
+ SequenceNumber largest_seqno = 0U;
+ GetSmallestLargestSeqno(*(c->inputs(level_index)), &smallest_seqno,
+ &largest_seqno);
+ if (is_first) {
+ is_first = false;
+ } else if (prev_smallest_seqno > 0) {
+ // A level is considered as the bottommost level if there are
+ // no files in higher levels or if files in higher levels do
+ // not overlap with the files being compacted. Sequence numbers
+ // of files in bottommost level can be set to 0 to help
+ // compression. As a result, the following assert may not hold
+ // if the prev_smallest_seqno is 0.
+ assert(prev_smallest_seqno > largest_seqno);
+ }
+ prev_smallest_seqno = smallest_seqno;
+ }
+ }
+#endif
+ // update statistics
+ RecordInHistogram(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
+ c->inputs(0)->size());
+
+ picker_->RegisterCompaction(c);
+ vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
+
+ TEST_SYNC_POINT_CALLBACK("UniversalCompactionBuilder::PickCompaction:Return",
+ c);
+ return c;
+}
+
+uint32_t UniversalCompactionBuilder::GetPathId(
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options, uint64_t file_size) {
+ // Two conditions need to be satisfied:
+ // (1) the target path needs to be able to hold the file's size
+ // (2) Total size left in this and previous paths need to be not
+ // smaller than expected future file size before this new file is
+ // compacted, which is estimated based on size_ratio.
+ // For example, if now we are compacting files of size (1, 1, 2, 4, 8),
+ // we will make sure the target file, probably with size of 16, will be
+ // placed in a path so that eventually when new files are generated and
+ // compacted to (1, 1, 2, 4, 8, 16), all those files can be stored in or
+ // before the path we chose.
+ //
+ // TODO(sdong): now the case of multiple column families is not
+ // considered in this algorithm. So the target size can be violated in
+ // that case. We need to improve it.
+ uint64_t accumulated_size = 0;
+ uint64_t future_size =
+ file_size *
+ (100 - mutable_cf_options.compaction_options_universal.size_ratio) / 100;
+ uint32_t p = 0;
+ assert(!ioptions.cf_paths.empty());
+ for (; p < ioptions.cf_paths.size() - 1; p++) {
+ uint64_t target_size = ioptions.cf_paths[p].target_size;
+ if (target_size > file_size &&
+ accumulated_size + (target_size - file_size) > future_size) {
+ return p;
+ }
+ accumulated_size += target_size;
+ }
+ return p;
+}
+
+//
+// Consider compaction files based on their size differences with
+// the next file in time order.
+//
+Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
+ unsigned int ratio, unsigned int max_number_of_files_to_compact) {
+ unsigned int min_merge_width =
+ mutable_cf_options_.compaction_options_universal.min_merge_width;
+ unsigned int max_merge_width =
+ mutable_cf_options_.compaction_options_universal.max_merge_width;
+
+ const SortedRun* sr = nullptr;
+ bool done = false;
+ size_t start_index = 0;
+ unsigned int candidate_count = 0;
+
+ unsigned int max_files_to_compact =
+ std::min(max_merge_width, max_number_of_files_to_compact);
+ min_merge_width = std::max(min_merge_width, 2U);
+
+ // Caller checks the size before executing this function. This invariant is
+ // important because otherwise we may have a possible integer underflow when
+ // dealing with unsigned types.
+ assert(sorted_runs_.size() > 0);
+
+ // Considers a candidate file only if it is smaller than the
+ // total size accumulated so far.
+ for (size_t loop = 0; loop < sorted_runs_.size(); loop++) {
+ candidate_count = 0;
+
+ // Skip files that are already being compacted
+ for (sr = nullptr; loop < sorted_runs_.size(); loop++) {
+ sr = &sorted_runs_[loop];
+
+ if (!sr->being_compacted) {
+ candidate_count = 1;
+ break;
+ }
+ char file_num_buf[kFormatFileNumberBufSize];
+ sr->Dump(file_num_buf, sizeof(file_num_buf));
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: %s"
+ "[%d] being compacted, skipping",
+ cf_name_.c_str(), file_num_buf, loop);
+
+ sr = nullptr;
+ }
+
+ // This file is not being compacted. Consider it as the
+ // first candidate to be compacted.
+ uint64_t candidate_size = sr != nullptr ? sr->compensated_file_size : 0;
+ if (sr != nullptr) {
+ char file_num_buf[kFormatFileNumberBufSize];
+ sr->Dump(file_num_buf, sizeof(file_num_buf), true);
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: Possible candidate %s[%d].",
+ cf_name_.c_str(), file_num_buf, loop);
+ }
+
+ // Check if the succeeding files need compaction.
+ for (size_t i = loop + 1;
+ candidate_count < max_files_to_compact && i < sorted_runs_.size();
+ i++) {
+ const SortedRun* succeeding_sr = &sorted_runs_[i];
+ if (succeeding_sr->being_compacted) {
+ break;
+ }
+ // Pick files if the total/last candidate file size (increased by the
+ // specified ratio) is still larger than the next candidate file.
+ // candidate_size is the total size of files picked so far with the
+ // default kCompactionStopStyleTotalSize; with
+ // kCompactionStopStyleSimilarSize, it's simply the size of the last
+ // picked file.
+ double sz = candidate_size * (100.0 + ratio) / 100.0;
+ if (sz < static_cast<double>(succeeding_sr->size)) {
+ break;
+ }
+ if (mutable_cf_options_.compaction_options_universal.stop_style ==
+ kCompactionStopStyleSimilarSize) {
+ // Similar-size stopping rule: also check the last picked file isn't
+ // far larger than the next candidate file.
+ sz = (succeeding_sr->size * (100.0 + ratio)) / 100.0;
+ if (sz < static_cast<double>(candidate_size)) {
+ // If the small file we've encountered begins a run of similar-size
+ // files, we'll pick them up on a future iteration of the outer
+ // loop. If it's some lonely straggler, it'll eventually get picked
+ // by the last-resort read amp strategy which disregards size ratios.
+ break;
+ }
+ candidate_size = succeeding_sr->compensated_file_size;
+ } else { // default kCompactionStopStyleTotalSize
+ candidate_size += succeeding_sr->compensated_file_size;
+ }
+ candidate_count++;
+ }
+
+ // Found a series of consecutive files that need compaction.
+ if (candidate_count >= (unsigned int)min_merge_width) {
+ start_index = loop;
+ done = true;
+ break;
+ } else {
+ for (size_t i = loop;
+ i < loop + candidate_count && i < sorted_runs_.size(); i++) {
+ const SortedRun* skipping_sr = &sorted_runs_[i];
+ char file_num_buf[256];
+ skipping_sr->DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop);
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Skipping %s",
+ cf_name_.c_str(), file_num_buf);
+ }
+ }
+ }
+ if (!done || candidate_count <= 1) {
+ return nullptr;
+ }
+ size_t first_index_after = start_index + candidate_count;
+ // Compression is enabled if files compacted earlier already reached
+ // size ratio of compression.
+ bool enable_compression = true;
+ int ratio_to_compress =
+ mutable_cf_options_.compaction_options_universal.compression_size_percent;
+ if (ratio_to_compress >= 0) {
+ uint64_t total_size = 0;
+ for (auto& sorted_run : sorted_runs_) {
+ total_size += sorted_run.compensated_file_size;
+ }
+
+ uint64_t older_file_size = 0;
+ for (size_t i = sorted_runs_.size() - 1; i >= first_index_after; i--) {
+ older_file_size += sorted_runs_[i].size;
+ if (older_file_size * 100L >= total_size * (long)ratio_to_compress) {
+ enable_compression = false;
+ break;
+ }
+ }
+ }
+
+ uint64_t estimated_total_size = 0;
+ for (unsigned int i = 0; i < first_index_after; i++) {
+ estimated_total_size += sorted_runs_[i].size;
+ }
+ uint32_t path_id =
+ GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
+ int start_level = sorted_runs_[start_index].level;
+ int output_level;
+ if (first_index_after == sorted_runs_.size()) {
+ output_level = vstorage_->num_levels() - 1;
+ } else if (sorted_runs_[first_index_after].level == 0) {
+ output_level = 0;
+ } else {
+ output_level = sorted_runs_[first_index_after].level - 1;
+ }
+
+ // last level is reserved for the files ingested behind
+ if (ioptions_.allow_ingest_behind &&
+ (output_level == vstorage_->num_levels() - 1)) {
+ assert(output_level > 1);
+ output_level--;
+ }
+
+ std::vector<CompactionInputFiles> inputs(vstorage_->num_levels());
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ inputs[i].level = start_level + static_cast<int>(i);
+ }
+ for (size_t i = start_index; i < first_index_after; i++) {
+ auto& picking_sr = sorted_runs_[i];
+ if (picking_sr.level == 0) {
+ FileMetaData* picking_file = picking_sr.file;
+ inputs[0].files.push_back(picking_file);
+ } else {
+ auto& files = inputs[picking_sr.level - start_level].files;
+ for (auto* f : vstorage_->LevelFiles(picking_sr.level)) {
+ files.push_back(f);
+ }
+ }
+ char file_num_buf[256];
+ picking_sr.DumpSizeInfo(file_num_buf, sizeof(file_num_buf), i);
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Picking %s",
+ cf_name_.c_str(), file_num_buf);
+ }
+
+ CompactionReason compaction_reason;
+ if (max_number_of_files_to_compact == UINT_MAX) {
+ compaction_reason = CompactionReason::kUniversalSizeRatio;
+ } else {
+ compaction_reason = CompactionReason::kUniversalSortedRunNum;
+ }
+ return new Compaction(
+ vstorage_, ioptions_, mutable_cf_options_, std::move(inputs),
+ output_level,
+ MaxFileSizeForLevel(mutable_cf_options_, output_level,
+ kCompactionStyleUniversal),
+ LLONG_MAX, path_id,
+ GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
+ 1, enable_compression),
+ GetCompressionOptions(ioptions_, vstorage_, start_level,
+ enable_compression),
+ /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
+ score_, false /* deletion_compaction */, compaction_reason);
+}
+
+// Look at overall size amplification. If size amplification
+// exceeeds the configured value, then do a compaction
+// of the candidate files all the way upto the earliest
+// base file (overrides configured values of file-size ratios,
+// min_merge_width and max_merge_width).
+//
+Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
+ // percentage flexibility while reducing size amplification
+ uint64_t ratio = mutable_cf_options_.compaction_options_universal
+ .max_size_amplification_percent;
+
+ unsigned int candidate_count = 0;
+ uint64_t candidate_size = 0;
+ size_t start_index = 0;
+ const SortedRun* sr = nullptr;
+
+ assert(!sorted_runs_.empty());
+ if (sorted_runs_.back().being_compacted) {
+ return nullptr;
+ }
+
+ // Skip files that are already being compacted
+ for (size_t loop = 0; loop < sorted_runs_.size() - 1; loop++) {
+ sr = &sorted_runs_[loop];
+ if (!sr->being_compacted) {
+ start_index = loop; // Consider this as the first candidate.
+ break;
+ }
+ char file_num_buf[kFormatFileNumberBufSize];
+ sr->Dump(file_num_buf, sizeof(file_num_buf), true);
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: skipping %s[%d] compacted %s",
+ cf_name_.c_str(), file_num_buf, loop,
+ " cannot be a candidate to reduce size amp.\n");
+ sr = nullptr;
+ }
+
+ if (sr == nullptr) {
+ return nullptr; // no candidate files
+ }
+ {
+ char file_num_buf[kFormatFileNumberBufSize];
+ sr->Dump(file_num_buf, sizeof(file_num_buf), true);
+ ROCKS_LOG_BUFFER(
+ log_buffer_,
+ "[%s] Universal: First candidate %s[%" ROCKSDB_PRIszt "] %s",
+ cf_name_.c_str(), file_num_buf, start_index, " to reduce size amp.\n");
+ }
+
+ // keep adding up all the remaining files
+ for (size_t loop = start_index; loop < sorted_runs_.size() - 1; loop++) {
+ sr = &sorted_runs_[loop];
+ if (sr->being_compacted) {
+ char file_num_buf[kFormatFileNumberBufSize];
+ sr->Dump(file_num_buf, sizeof(file_num_buf), true);
+ ROCKS_LOG_BUFFER(
+ log_buffer_, "[%s] Universal: Possible candidate %s[%d] %s",
+ cf_name_.c_str(), file_num_buf, start_index,
+ " is already being compacted. No size amp reduction possible.\n");
+ return nullptr;
+ }
+ candidate_size += sr->compensated_file_size;
+ candidate_count++;
+ }
+ if (candidate_count == 0) {
+ return nullptr;
+ }
+
+ // size of earliest file
+ uint64_t earliest_file_size = sorted_runs_.back().size;
+
+ // size amplification = percentage of additional size
+ if (candidate_size * 100 < ratio * earliest_file_size) {
+ ROCKS_LOG_BUFFER(
+ log_buffer_,
+ "[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
+ " earliest-file-size %" PRIu64,
+ cf_name_.c_str(), candidate_size, earliest_file_size);
+ return nullptr;
+ } else {
+ ROCKS_LOG_BUFFER(
+ log_buffer_,
+ "[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
+ " earliest-file-size %" PRIu64,
+ cf_name_.c_str(), candidate_size, earliest_file_size);
+ }
+ return PickCompactionToOldest(start_index,
+ CompactionReason::kUniversalSizeAmplification);
+}
+
+// Pick files marked for compaction. Typically, files are marked by
+// CompactOnDeleteCollector due to the presence of tombstones.
+Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
+ CompactionInputFiles start_level_inputs;
+ int output_level;
+ std::vector<CompactionInputFiles> inputs;
+
+ if (vstorage_->num_levels() == 1) {
+ // This is single level universal. Since we're basically trying to reclaim
+ // space by processing files marked for compaction due to high tombstone
+ // density, let's do the same thing as compaction to reduce size amp which
+ // has the same goals.
+ bool compact = false;
+
+ start_level_inputs.level = 0;
+ start_level_inputs.files.clear();
+ output_level = 0;
+ for (FileMetaData* f : vstorage_->LevelFiles(0)) {
+ if (f->marked_for_compaction) {
+ compact = true;
+ }
+ if (compact) {
+ start_level_inputs.files.push_back(f);
+ }
+ }
+ if (start_level_inputs.size() <= 1) {
+ // If only the last file in L0 is marked for compaction, ignore it
+ return nullptr;
+ }
+ inputs.push_back(start_level_inputs);
+ } else {
+ int start_level;
+
+ // For multi-level universal, the strategy is to make this look more like
+ // leveled. We pick one of the files marked for compaction and compact with
+ // overlapping files in the adjacent level.
+ picker_->PickFilesMarkedForCompaction(cf_name_, vstorage_, &start_level,
+ &output_level, &start_level_inputs);
+ if (start_level_inputs.empty()) {
+ return nullptr;
+ }
+
+ // Pick the first non-empty level after the start_level
+ for (output_level = start_level + 1; output_level < vstorage_->num_levels();
+ output_level++) {
+ if (vstorage_->NumLevelFiles(output_level) != 0) {
+ break;
+ }
+ }
+
+ // If all higher levels are empty, pick the highest level as output level
+ if (output_level == vstorage_->num_levels()) {
+ if (start_level == 0) {
+ output_level = vstorage_->num_levels() - 1;
+ } else {
+ // If start level is non-zero and all higher levels are empty, this
+ // compaction will translate into a trivial move. Since the idea is
+ // to reclaim space and trivial move doesn't help with that, we
+ // skip compaction in this case and return nullptr
+ return nullptr;
+ }
+ }
+ if (ioptions_.allow_ingest_behind &&
+ output_level == vstorage_->num_levels() - 1) {
+ assert(output_level > 1);
+ output_level--;
+ }
+
+ if (output_level != 0) {
+ if (start_level == 0) {
+ if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
+ output_level, nullptr)) {
+ return nullptr;
+ }
+ }
+
+ CompactionInputFiles output_level_inputs;
+ int parent_index = -1;
+
+ output_level_inputs.level = output_level;
+ if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
+ &start_level_inputs, &output_level_inputs,
+ &parent_index, -1)) {
+ return nullptr;
+ }
+ inputs.push_back(start_level_inputs);
+ if (!output_level_inputs.empty()) {
+ inputs.push_back(output_level_inputs);
+ }
+ if (picker_->FilesRangeOverlapWithCompaction(inputs, output_level)) {
+ return nullptr;
+ }
+ } else {
+ inputs.push_back(start_level_inputs);
+ }
+ }
+
+ uint64_t estimated_total_size = 0;
+ // Use size of the output level as estimated file size
+ for (FileMetaData* f : vstorage_->LevelFiles(output_level)) {
+ estimated_total_size += f->fd.GetFileSize();
+ }
+ uint32_t path_id =
+ GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
+ return new Compaction(
+ vstorage_, ioptions_, mutable_cf_options_, std::move(inputs),
+ output_level,
+ MaxFileSizeForLevel(mutable_cf_options_, output_level,
+ kCompactionStyleUniversal),
+ /* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
+ GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
+ output_level, 1),
+ GetCompressionOptions(ioptions_, vstorage_, output_level),
+ /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ true,
+ score_, false /* deletion_compaction */,
+ CompactionReason::kFilesMarkedForCompaction);
+}
+
+Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
+ size_t start_index, CompactionReason compaction_reason) {
+ assert(start_index < sorted_runs_.size());
+
+ // Estimate total file size
+ uint64_t estimated_total_size = 0;
+ for (size_t loop = start_index; loop < sorted_runs_.size(); loop++) {
+ estimated_total_size += sorted_runs_[loop].size;
+ }
+ uint32_t path_id =
+ GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
+ int start_level = sorted_runs_[start_index].level;
+
+ std::vector<CompactionInputFiles> inputs(vstorage_->num_levels());
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ inputs[i].level = start_level + static_cast<int>(i);
+ }
+ for (size_t loop = start_index; loop < sorted_runs_.size(); loop++) {
+ auto& picking_sr = sorted_runs_[loop];
+ if (picking_sr.level == 0) {
+ FileMetaData* f = picking_sr.file;
+ inputs[0].files.push_back(f);
+ } else {
+ auto& files = inputs[picking_sr.level - start_level].files;
+ for (auto* f : vstorage_->LevelFiles(picking_sr.level)) {
+ files.push_back(f);
+ }
+ }
+ std::string comp_reason_print_string;
+ if (compaction_reason == CompactionReason::kPeriodicCompaction) {
+ comp_reason_print_string = "periodic compaction";
+ } else if (compaction_reason ==
+ CompactionReason::kUniversalSizeAmplification) {
+ comp_reason_print_string = "size amp";
+ } else {
+ assert(false);
+ }
+
+ char file_num_buf[256];
+ picking_sr.DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop);
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: %s picking %s",
+ cf_name_.c_str(), comp_reason_print_string.c_str(),
+ file_num_buf);
+ }
+
+ // output files at the bottom most level, unless it's reserved
+ int output_level = vstorage_->num_levels() - 1;
+ // last level is reserved for the files ingested behind
+ if (ioptions_.allow_ingest_behind) {
+ assert(output_level > 1);
+ output_level--;
+ }
+
+ // We never check size for
+ // compaction_options_universal.compression_size_percent,
+ // because we always compact all the files, so always compress.
+ return new Compaction(
+ vstorage_, ioptions_, mutable_cf_options_, std::move(inputs),
+ output_level,
+ MaxFileSizeForLevel(mutable_cf_options_, output_level,
+ kCompactionStyleUniversal),
+ LLONG_MAX, path_id,
+ GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
+ 1, true /* enable_compression */),
+ GetCompressionOptions(ioptions_, vstorage_, start_level,
+ true /* enable_compression */),
+ /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
+ score_, false /* deletion_compaction */, compaction_reason);
+}
+
+Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Periodic Compaction",
+ cf_name_.c_str());
+
+ // In universal compaction, sorted runs contain older data are almost always
+ // generated earlier too. To simplify the problem, we just try to trigger
+ // a full compaction. We start from the oldest sorted run and include
+ // all sorted runs, until we hit a sorted already being compacted.
+ // Since usually the largest (which is usually the oldest) sorted run is
+ // included anyway, doing a full compaction won't increase write
+ // amplification much.
+
+ // Get some information from marked files to check whether a file is
+ // included in the compaction.
+
+ size_t start_index = sorted_runs_.size();
+ while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted) {
+ start_index--;
+ }
+ if (start_index == sorted_runs_.size()) {
+ return nullptr;
+ }
+
+ // There is a rare corner case where we can't pick up all the files
+ // because some files are being compacted and we end up with picking files
+ // but none of them need periodic compaction. Unless we simply recompact
+ // the last sorted run (either the last level or last L0 file), we would just
+ // execute the compaction, in order to simplify the logic.
+ if (start_index == sorted_runs_.size() - 1) {
+ bool included_file_marked = false;
+ int start_level = sorted_runs_[start_index].level;
+ FileMetaData* start_file = sorted_runs_[start_index].file;
+ for (const std::pair<int, FileMetaData*>& level_file_pair :
+ vstorage_->FilesMarkedForPeriodicCompaction()) {
+ if (start_level != 0) {
+ // Last sorted run is a level
+ if (start_level == level_file_pair.first) {
+ included_file_marked = true;
+ break;
+ }
+ } else {
+ // Last sorted run is a L0 file.
+ if (start_file == level_file_pair.second) {
+ included_file_marked = true;
+ break;
+ }
+ }
+ }
+ if (!included_file_marked) {
+ ROCKS_LOG_BUFFER(log_buffer_,
+ "[%s] Universal: Cannot form a compaction covering file "
+ "marked for periodic compaction",
+ cf_name_.c_str());
+ return nullptr;
+ }
+ }
+
+ Compaction* c = PickCompactionToOldest(start_index,
+ CompactionReason::kPeriodicCompaction);
+
+ TEST_SYNC_POINT_CALLBACK(
+ "UniversalCompactionPicker::PickPeriodicCompaction:Return", c);
+
+ return c;
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.h b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.h
new file mode 100644
index 00000000..c3f55f5d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/compaction/compaction_picker_universal.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "db/compaction/compaction_picker.h"
+
+namespace ROCKSDB_NAMESPACE {
+class UniversalCompactionPicker : public CompactionPicker {
+ public:
+ UniversalCompactionPicker(const ImmutableCFOptions& ioptions,
+ const InternalKeyComparator* icmp)
+ : CompactionPicker(ioptions, icmp) {}
+ virtual Compaction* PickCompaction(
+ const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
+ VersionStorageInfo* vstorage, LogBuffer* log_buffer,
+ SequenceNumber earliest_memtable_seqno = kMaxSequenceNumber) override;
+ virtual int MaxOutputLevel() const override { return NumberLevels() - 1; }
+
+ virtual bool NeedsCompaction(
+ const VersionStorageInfo* vstorage) const override;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/comparator_db_test.cc b/storage/rocksdb/rocksdb/db/comparator_db_test.cc
new file mode 100644
index 00000000..49f287a9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/comparator_db_test.cc
@@ -0,0 +1,660 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <array>
+#include <map>
+#include <string>
+
+#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/string_util.h"
+#include "utilities/merge_operators.h"
+
+using std::unique_ptr;
+
+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<std::string> 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<int>(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<Iterator> iter(db->NewIterator(ReadOptions()));
+ std::unique_ptr<Iterator> 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<int>(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<int>(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<size_t>(a[0]);
+ size_t size_b1 = static_cast<size_t>(b[0]);
+ size_t size_a2 = static_cast<size_t>(a[1]);
+ size_t size_b2 = static_cast<size_t>(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 {}
+};
+} // namespace
+
+class ComparatorDBTest
+ : public testing::Test,
+ virtual public ::testing::WithParamInterface<uint32_t> {
+ private:
+ std::string dbname_;
+ Env* env_;
+ DB* db_;
+ Options last_options_;
+ std::unique_ptr<const Comparator> 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(test::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<std::string> source_strings;
+ std::vector<std::string> source_prefixes;
+ // Randomly generate 5 prefixes
+ for (int i = 0; i < 5; i++) {
+ source_prefixes.push_back(test::RandomHumanReadableString(&rnd, 8));
+ }
+ for (int j = 0; j < 20; j++) {
+ int prefix_index = rnd.Uniform(static_cast<int>(source_prefixes.size()));
+ std::string key = source_prefixes[prefix_index] +
+ test::RandomHumanReadableString(&rnd, 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<std::string> 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<void*>(&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<std::string> 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(ToString(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<std::string> 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<std::string> 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<char>(size1));
+ str.append(1, static_cast<char>(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);
+ }
+}
+
+TEST_P(ComparatorDBTest, IsSameLengthImmediateSuccessor) {
+ {
+ // different length
+ Slice s("abcxy");
+ Slice t("abcxyz");
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ Slice s("abcxyz");
+ Slice t("abcxy");
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ // not last byte different
+ Slice s("abc1xyz");
+ Slice t("abc2xyz");
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ // same string
+ Slice s("abcxyz");
+ Slice t("abcxyz");
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ Slice s("abcxy");
+ Slice t("abcxz");
+ ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ Slice s("abcxz");
+ Slice t("abcxy");
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
+ }
+ {
+ const char s_array[] = "\x50\x8a\xac";
+ const char t_array[] = "\x50\x8a\xad";
+ Slice s(s_array);
+ Slice t(t_array);
+ ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(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);
+ ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(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);
+ ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(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);
+ ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(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<unsigned char, 6> 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<int>(rnd.Uniform(5)) - 2;
+ int tmp_size2 = static_cast<int>(size1) + diff;
+ if (tmp_size2 < 0) {
+ tmp_size2 = 0;
+ }
+ size2 = static_cast<uint32_t>(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<char>(rnd.Uniform(256));
+ } else {
+ // Use one byte in char_list
+ char c = static_cast<char>(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<char>(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<int>(rnd.Uniform(5)) - 2;
+ // char may be signed or unsigned based on platform.
+ int s1_char = static_cast<int>(static_cast<unsigned char>(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<char>(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/convenience.cc b/storage/rocksdb/rocksdb/db/convenience.cc
new file mode 100644
index 00000000..206f1f87
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/convenience.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/convenience.h"
+
+#include "db/db_impl/db_impl.h"
+#include "util/cast_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void CancelAllBackgroundWork(DB* db, bool wait) {
+ (static_cast_with_check<DBImpl, DB>(db->GetRootDB()))
+ ->CancelAllBackgroundWork(wait);
+}
+
+Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end,
+ bool include_end) {
+ RangePtr range(begin, end);
+ return DeleteFilesInRanges(db, column_family, &range, 1, include_end);
+}
+
+Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
+ const RangePtr* ranges, size_t n,
+ bool include_end) {
+ return (static_cast_with_check<DBImpl, DB>(db->GetRootDB()))
+ ->DeleteFilesInRanges(column_family, ranges, n, include_end);
+}
+
+Status VerifySstFileChecksum(const Options& options,
+ const EnvOptions& env_options,
+ const std::string& file_path) {
+ return VerifySstFileChecksum(options, env_options, ReadOptions(), file_path);
+}
+Status VerifySstFileChecksum(const Options& options,
+ const EnvOptions& env_options,
+ const ReadOptions& read_options,
+ const std::string& file_path) {
+ std::unique_ptr<FSRandomAccessFile> file;
+ uint64_t file_size;
+ InternalKeyComparator internal_comparator(options.comparator);
+ ImmutableCFOptions ioptions(options);
+
+ Status s = ioptions.fs->NewRandomAccessFile(file_path,
+ FileOptions(env_options),
+ &file, nullptr);
+ if (s.ok()) {
+ s = ioptions.fs->GetFileSize(file_path, IOOptions(), &file_size, nullptr);
+ } else {
+ return s;
+ }
+ std::unique_ptr<TableReader> table_reader;
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(std::move(file), file_path));
+ const bool kImmortal = true;
+ s = ioptions.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, options.prefix_extractor.get(), env_options,
+ internal_comparator, false /* skip_filters */,
+ !kImmortal, -1 /* level */),
+ std::move(file_reader), file_size, &table_reader,
+ false /* prefetch_index_and_filter_in_cache */);
+ if (!s.ok()) {
+ return s;
+ }
+ s = table_reader->VerifyChecksum(read_options,
+ TableReaderCaller::kUserVerifyChecksum);
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/corruption_test.cc b/storage/rocksdb/rocksdb/db/corruption_test.cc
new file mode 100644
index 00000000..203c34fa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/corruption_test.cc
@@ -0,0 +1,613 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/db.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <cinttypes>
+#include "db/db_impl/db_impl.h"
+#include "db/db_test_util.h"
+#include "db/log_format.h"
+#include "db/version_set.h"
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/env.h"
+#include "rocksdb/table.h"
+#include "rocksdb/write_batch.h"
+#include "table/block_based/block_based_table_builder.h"
+#include "table/meta_blocks.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static const int kValueSize = 1000;
+
+class CorruptionTest : public testing::Test {
+ public:
+ test::ErrorEnv env_;
+ std::string dbname_;
+ std::shared_ptr<Cache> 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);
+ options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
+ options_.env = &env_;
+ dbname_ = test::PerThreadDBPath("corruption_test");
+ DestroyDB(dbname_, options_);
+
+ 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 {
+ delete db_;
+ DestroyDB(dbname_, Options());
+ }
+
+ 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_;
+ }
+ 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 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 = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+ }
+ //if ((i % 100) == 0) fprintf(stderr, "@ %d of %d\n", i, n);
+ Slice key = Key(i, &key_space);
+ batch.Clear();
+ batch.Put(key, Value(i, &value_space));
+ ASSERT_OK(db_->Write(WriteOptions(), &batch));
+ }
+ }
+
+ void Check(int min_expected, int max_expected) {
+ 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(ReadOptions(false, true));
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ 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<int>(key), &value_space)) {
+ bad_values++;
+ } else {
+ correct++;
+ }
+ }
+ 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<unsigned long long>(missed));
+ ASSERT_LE(min_expected, correct);
+ ASSERT_GE(max_expected, correct);
+ }
+
+ void CorruptFile(const std::string& fname, int offset, int bytes_to_corrupt) {
+ struct stat sbuf;
+ if (stat(fname.c_str(), &sbuf) != 0) {
+ const char* msg = strerror(errno);
+ FAIL() << fname << ": " << msg;
+ }
+
+ if (offset < 0) {
+ // Relative to end of file; make it absolute
+ if (-offset > sbuf.st_size) {
+ offset = 0;
+ } else {
+ offset = static_cast<int>(sbuf.st_size + offset);
+ }
+ }
+ if (offset > sbuf.st_size) {
+ offset = static_cast<int>(sbuf.st_size);
+ }
+ if (offset + bytes_to_corrupt > sbuf.st_size) {
+ bytes_to_corrupt = static_cast<int>(sbuf.st_size - offset);
+ }
+
+ // Do it
+ std::string contents;
+ Status s = ReadFileToString(Env::Default(), fname, &contents);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ for (int i = 0; i < bytes_to_corrupt; i++) {
+ contents[i + offset] ^= 0x80;
+ }
+ s = WriteStringToFile(Env::Default(), contents, fname);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ Options options;
+ EnvOptions env_options;
+ options.file_system.reset(new LegacyFileSystemWrapper(options.env));
+ ASSERT_NOK(VerifySstFileChecksum(options, env_options, fname));
+ }
+
+ void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
+ // Pick file to corrupt
+ std::vector<std::string> 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<int>(number) > picked_number) { // Pick latest file
+ fname = dbname_ + "/" + filenames[i];
+ picked_number = static_cast<int>(number);
+ }
+ }
+ ASSERT_TRUE(!fname.empty()) << filetype;
+
+ CorruptFile(fname, offset, bytes_to_corrupt);
+ }
+
+ // 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<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ for (const auto& m : metadata) {
+ if (m.level == level) {
+ CorruptFile(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, ' ');
+ return Slice(*storage);
+ } else {
+ Random r(k);
+ return test::RandomString(&r, kValueSize, storage);
+ }
+ }
+};
+
+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(kLogFile, 19, 1); // WriteBatch tag for first record
+ Corrupt(kLogFile, 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, RecoverWriteError) {
+ env_.writable_file_error_ = true;
+ Status s = TryReopen();
+ ASSERT_TRUE(!s.ok());
+}
+
+TEST_F(CorruptionTest, NewFileErrorDuringWrite) {
+ // Do enough writing to force minor compaction
+ env_.writable_file_error_ = true;
+ const int num =
+ static_cast<int>(3 + (Options().write_buffer_size / kValueSize));
+ std::string value_storage;
+ Status s;
+ bool failed = false;
+ for (int i = 0; i < num; i++) {
+ WriteBatch batch;
+ 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(env_.num_writable_file_errors_, 1);
+ env_.writable_file_error_ = false;
+ Reopen();
+}
+
+TEST_F(CorruptionTest, TableFile) {
+ Build(100);
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+ dbi->TEST_CompactRange(0, nullptr, nullptr);
+ 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(Env::Default());
+ 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 = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+ dbi->TEST_CompactRange(0, nullptr, nullptr);
+ 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<DBImpl*>(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 = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+
+ // corrupt an index block of an entire file
+ Corrupt(kTableFile, -2000, 500);
+ options.paranoid_checks = false;
+ Reopen(&options);
+ dbi = reinterpret_cast<DBImpl*>(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);
+ ASSERT_NOK(dbi->VerifyChecksum());
+
+ // In paranoid mode, the db cannot be opened due to the corrupted file.
+ ASSERT_TRUE(TryReopen().IsCorruption());
+}
+
+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 = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+ dbi->TEST_CompactRange(0, 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;
+ Reopen(&options);
+ Build(10);
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+ dbi->TEST_FlushMemTable();
+ dbi->TEST_CompactRange(0, nullptr, nullptr);
+ 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.paranoid_checks = true;
+ options.write_buffer_size = 131072;
+ options.max_write_buffer_number = 2;
+ Reopen(&options);
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+
+ // Fill levels >= 1
+ for (int level = 1; level < dbi->NumberLevels(); level++) {
+ dbi->Put(WriteOptions(), "", "begin");
+ dbi->Put(WriteOptions(), "~", "end");
+ dbi->TEST_FlushMemTable();
+ for (int comp_level = 0; comp_level < dbi->NumberLevels() - level;
+ ++comp_level) {
+ dbi->TEST_CompactRange(comp_level, nullptr, nullptr);
+ }
+ }
+
+ Reopen(&options);
+
+ dbi = reinterpret_cast<DBImpl*>(db_);
+ Build(10);
+ dbi->TEST_FlushMemTable();
+ 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 = reinterpret_cast<DBImpl*>(db_);
+ 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);
+ 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<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ ASSERT_EQ(static_cast<size_t>(1), metadata.size());
+ std::string filename = dbname_ + metadata[0].name;
+
+ std::unique_ptr<RandomAccessFile> file;
+ ASSERT_OK(options_.env->NewRandomAccessFile(filename, &file, EnvOptions()));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
+ filename));
+
+ uint64_t file_size;
+ ASSERT_OK(options_.env->GetFileSize(filename, &file_size));
+
+ BlockHandle range_del_handle;
+ ASSERT_OK(FindMetaBlock(
+ file_reader.get(), file_size, kBlockBasedTableMagicNumber,
+ ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
+
+ ASSERT_OK(TryReopen());
+ CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
+ ASSERT_TRUE(TryReopen().IsCorruption());
+}
+
+TEST_F(CorruptionTest, FileSystemStateCorrupted) {
+ for (int iter = 0; iter < 2; ++iter) {
+ Options options;
+ options.paranoid_checks = true;
+ options.create_if_missing = true;
+ Reopen(&options);
+ Build(10);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+ std::vector<LiveFileMetaData> metadata;
+ dbi->GetLiveFilesMetaData(&metadata);
+ ASSERT_GT(metadata.size(), size_t(0));
+ std::string filename = dbname_ + metadata[0].name;
+
+ delete db_;
+ db_ = nullptr;
+
+ if (iter == 0) { // corrupt file size
+ std::unique_ptr<WritableFile> file;
+ env_.NewWritableFile(filename, &file, EnvOptions());
+ file->Append(Slice("corrupted sst"));
+ file.reset();
+ Status x = TryReopen(&options);
+ ASSERT_TRUE(x.IsCorruption());
+ } else { // delete the file
+ env_.DeleteFile(filename);
+ Status x = TryReopen(&options);
+ ASSERT_TRUE(x.IsPathNotFound());
+ }
+
+ DestroyDB(dbname_, options_);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as RepairDB() is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/cuckoo_table_db_test.cc b/storage/rocksdb/rocksdb/db/cuckoo_table_db_test.cc
new file mode 100644
index 00000000..9467840f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/cuckoo_table_db_test.cc
@@ -0,0 +1,351 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "db/db_impl/db_impl.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/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 reinterpret_cast<DBImpl*>(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_));
+ }
+
+ 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" + NumberToString(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"));
+ dbfull()->TEST_FlushMemTable();
+
+ TablePropertiesCollection ptc;
+ reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&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"));
+ dbfull()->TEST_FlushMemTable();
+
+ reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&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"));
+ dbfull()->TEST_FlushMemTable();
+ reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&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
+ dbfull()->TEST_FlushMemTable();
+
+ TablePropertiesCollection ptc;
+ reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&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<void*>(&i), 8);
+ return str;
+}
+} // namespace.
+
+TEST_F(CuckooTableDBTest, Uint64Comparator) {
+ Options options = CurrentOptions();
+ options.comparator = test::Uint64Comparator();
+ Reopen(&options);
+
+ ASSERT_OK(Put(Uint64Key(1), "v1"));
+ ASSERT_OK(Put(Uint64Key(2), "v2"));
+ ASSERT_OK(Put(Uint64Key(3), "v3"));
+ 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.
+ dbfull()->TEST_FlushMemTable();
+ ASSERT_OK(Put(Uint64Key(3), "v0")); // Update.
+ ASSERT_OK(Put(Uint64Key(4), "v4"));
+ 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))));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ("1", FilesPerLevel());
+
+ 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')));
+ }
+ 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))));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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"));
+ dbfull()->TEST_FlushMemTable();
+
+ // Write some keys using plain table.
+ std::shared_ptr<TableFactory> block_based_factory(
+ NewBlockBasedTableFactory());
+ std::shared_ptr<TableFactory> plain_table_factory(
+ NewPlainTableFactory());
+ std::shared_ptr<TableFactory> 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"));
+ 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"));
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+ } else {
+ fprintf(stderr, "SKIPPED as Cuckoo table doesn't support Big Endian\n");
+ return 0;
+ }
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/db_basic_test.cc b/storage/rocksdb/rocksdb/db/db_basic_test.cc
new file mode 100644
index 00000000..7573a01b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_basic_test.cc
@@ -0,0 +1,2545 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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/fault_injection_test_env.h"
+#if !defined(ROCKSDB_LITE)
+#include "test_util/sync_point.h"
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBBasicTest : public DBTestBase {
+ public:
+ DBBasicTest() : DBTestBase("/db_basic_test") {}
+};
+
+TEST_F(DBBasicTest, OpenWhenOpen) {
+ Options options = CurrentOptions();
+ options.env = env_;
+ ROCKSDB_NAMESPACE::DB* db2 = nullptr;
+ ROCKSDB_NAMESPACE::Status s = DB::Open(options, dbname_, &db2);
+
+ ASSERT_EQ(Status::Code::kIOError, s.code());
+ ASSERT_EQ(Status::SubCode::kNone, s.subcode());
+ ASSERT_TRUE(strstr(s.getState(), "lock ") != nullptr);
+
+ delete db2;
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBBasicTest, ReadOnlyDB) {
+ ASSERT_OK(Put("foo", "v1"));
+ ASSERT_OK(Put("bar", "v2"));
+ ASSERT_OK(Put("foo", "v3"));
+ Close();
+
+ auto options = CurrentOptions();
+ assert(options.env == env_);
+ ASSERT_OK(ReadOnlyReopen(options));
+ 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);
+ 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());
+}
+
+TEST_F(DBBasicTest, 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;
+ 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);
+ 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;
+ 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')));
+ 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')));
+ Flush();
+ ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, 'a')));
+ Flush();
+ ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, 'b')));
+ ASSERT_OK(Put("eee", DummyString(kFileSize / 2, 'e')));
+ Flush();
+ 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.");
+ 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')));
+ 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"));
+
+ // MultiGet
+ std::vector<std::string> values;
+ std::vector<Status> status_list = dbfull()->MultiGet(
+ ReadOptions(),
+ std::vector<Slice>({Slice("aaa"), Slice("ccc"), Slice("eee"),
+ Slice("ggg"), Slice("iii"), Slice("kkk")}),
+ &values);
+ ASSERT_EQ(status_list.size(), static_cast<uint64_t>(6));
+ ASSERT_EQ(values.size(), static_cast<uint64_t>(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));
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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));
+}
+#endif // ROCKSDB_LITE
+
+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);
+
+ Put(1, "a", Slice());
+ 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());
+}
+
+#ifndef ROCKSDB_LITE
+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());
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBBasicTest, CheckLock) {
+ do {
+ DB* localdb;
+ Options options = CurrentOptions();
+ ASSERT_OK(TryReopen(options));
+
+ // second open should fail
+ ASSERT_TRUE(!(DB::Open(options, dbname_, &localdb)).ok());
+ } 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<int64_t>(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, IdentityAcrossRestarts1) {
+ do {
+ std::string id1;
+ ASSERT_OK(db_->GetDbIdentity(id1));
+
+ Options options = CurrentOptions();
+ Reopen(options);
+ std::string id2;
+ ASSERT_OK(db_->GetDbIdentity(id2));
+ // id1 should match id2 because identity was not regenerated
+ ASSERT_EQ(id1.compare(id2), 0);
+
+ std::string idfilename = IdentityFileName(dbname_);
+ ASSERT_OK(env_->DeleteFile(idfilename));
+ Reopen(options);
+ std::string id3;
+ ASSERT_OK(db_->GetDbIdentity(id3));
+ if (options.write_dbid_to_manifest) {
+ ASSERT_EQ(id1.compare(id3), 0);
+ } else {
+ // id1 should NOT match id3 because identity was regenerated
+ ASSERT_NE(id1.compare(id3), 0);
+ }
+ } while (ChangeCompactOptions());
+}
+
+TEST_F(DBBasicTest, IdentityAcrossRestarts2) {
+ do {
+ std::string id1;
+ ASSERT_OK(db_->GetDbIdentity(id1));
+
+ Options options = CurrentOptions();
+ options.write_dbid_to_manifest = true;
+ Reopen(options);
+ std::string id2;
+ ASSERT_OK(db_->GetDbIdentity(id2));
+ // id1 should match id2 because identity was not regenerated
+ ASSERT_EQ(id1.compare(id2), 0);
+
+ std::string idfilename = IdentityFileName(dbname_);
+ ASSERT_OK(env_->DeleteFile(idfilename));
+ Reopen(options);
+ std::string id3;
+ ASSERT_OK(db_->GetDbIdentity(id3));
+ // id1 should NOT match id3 because identity was regenerated
+ ASSERT_EQ(id1, id3);
+ } while (ChangeCompactOptions());
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBBasicTest, Snapshot) {
+ anon::OptionsOverride options_override;
+ options_override.skip_policy = kSkipNoSnapshot;
+ do {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override));
+ Put(0, "foo", "0v1");
+ 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());
+ Put(0, "foo", "0v2");
+ Put(1, "foo", "1v2");
+
+ env_->addon_time_.fetch_add(1);
+
+ const Snapshot* s2 = db_->GetSnapshot();
+ ASSERT_EQ(2U, GetNumSnapshots());
+ ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
+ ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
+ Put(0, "foo", "0v3");
+ Put(1, "foo", "1v3");
+
+ {
+ ManagedSnapshot s3(db_);
+ ASSERT_EQ(3U, GetNumSnapshots());
+ ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
+ ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
+
+ Put(0, "foo", "0v4");
+ 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());
+}
+
+#endif // ROCKSDB_LITE
+
+TEST_F(DBBasicTest, CompactBetweenSnapshots) {
+ anon::OptionsOverride options_override;
+ options_override.skip_policy = kSkipNoSnapshot;
+ do {
+ Options options = CurrentOptions(options_override);
+ options.disable_auto_compactions = true;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+ FillLevels("a", "z", 1);
+
+ Put(1, "foo", "first");
+ const Snapshot* snapshot1 = db_->GetSnapshot();
+ Put(1, "foo", "second");
+ Put(1, "foo", "third");
+ Put(1, "foo", "fourth");
+ const Snapshot* snapshot2 = db_->GetSnapshot();
+ Put(1, "foo", "fifth");
+ 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);
+ 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);
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ("sixth", Get(1, "foo"));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth ]");
+ } while (ChangeOptions(kSkipFIFOCompaction));
+}
+
+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);
+
+ Put(1, "foo", "v1");
+ ASSERT_OK(Flush(1));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v1 ]");
+
+ // Write two new keys
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ Flush(1);
+
+ // Case1: Delete followed by a put
+ Delete(1, "foo");
+ 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 ]");
+
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2 ]");
+
+ // Case 2: Delete followed by another delete
+ Delete(1, "foo");
+ Delete(1, "foo");
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, DEL, v2 ]");
+ ASSERT_OK(Flush(1));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v2 ]");
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
+
+ // Case 3: Put followed by a delete
+ Put(1, "foo", "v3");
+ Delete(1, "foo");
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v3 ]");
+ ASSERT_OK(Flush(1));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL ]");
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]");
+
+ // Case 4: Put followed by another Put
+ Put(1, "foo", "v4");
+ Put(1, "foo", "v5");
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5, v4 ]");
+ ASSERT_OK(Flush(1));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]");
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]");
+
+ // clear database
+ Delete(1, "foo");
+ 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.
+ Put(1, "foo", "v6");
+ const Snapshot* snapshot = db_->GetSnapshot();
+ Put(1, "foo", "v7");
+ ASSERT_OK(Flush(1));
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ v7, v6 ]");
+ db_->ReleaseSnapshot(snapshot);
+
+ // clear database
+ Delete(1, "foo");
+ 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();
+ Put(1, "foo", "v8");
+ 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) {
+ 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<Slice> keys({"k1", "k2", "k3", "k4", "k5", "no_key"});
+
+ std::vector<std::string> values(20, "Temporary data to be overwritten");
+ std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
+
+ get_perf_context()->Reset();
+ std::vector<Status> 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<Slice> keys;
+ std::vector<std::string> values;
+ std::vector<ColumnFamilyHandle*> cfs;
+ std::vector<Status> 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<int>(s.size()), 2);
+ ASSERT_TRUE(s[0].IsNotFound() && s[1].IsNotFound());
+ } while (ChangeCompactOptions());
+}
+
+TEST_F(DBBasicTest, ChecksumTest) {
+ BlockBasedTableOptions table_options;
+ Options options = CurrentOptions();
+ // change when new checksum type added
+ int max_checksum = static_cast<int>(kxxHash64);
+ const int kNumPerFile = 2;
+
+ // generate one table with each type of checksum
+ for (int i = 0; i <= max_checksum; ++i) {
+ table_options.checksum = static_cast<ChecksumType>(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 <= max_checksum; ++i) {
+ table_options.checksum = static_cast<ChecksumType>(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 < (max_checksum + 1) * kNumPerFile; ++j) {
+ ASSERT_EQ(Key(j), Get(Key(j)));
+ }
+ }
+}
+
+// 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) {}
+
+ class TestLogger : public Logger {
+ public:
+ using Logger::Logv;
+ explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
+ ~TestLogger() override {
+ if (!closed_) {
+ CloseHelper();
+ }
+ }
+ 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<Logger>* 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<TestEnv> 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, DBCloseFlushError) {
+ std::unique_ptr<FaultInjectionTestEnv> 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();
+ fault_injection_env->SetFilesystemActive(true);
+ ASSERT_NE(s, Status::OK());
+
+ Destroy(options);
+}
+
+class DBMultiGetTestWithParam : public DBBasicTest,
+ public testing::WithParamInterface<bool> {};
+
+TEST_P(DBMultiGetTestWithParam, MultiGetMultiCF) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
+ "alyosha", "popovich"},
+ options);
+ // <CF, key, value> tuples
+ std::vector<std::tuple<int, std::string, std::string>> 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 = reinterpret_cast<DBImpl*>(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 = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ db->GetColumnFamilyHandle(i))
+ ->cfd();
+ ASSERT_EQ(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
+ }
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::vector<int> cfs;
+ std::vector<std::string> keys;
+ std::vector<std::string> 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, 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, 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, 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 = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(cf))
+ ->cfd();
+ ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
+ ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
+ }
+}
+
+TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFMutex) {
+ 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<int> cfs;
+ std::vector<std::string> keys;
+ std::vector<std::string> 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, 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 = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(i))
+ ->cfd();
+ ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
+ }
+}
+
+TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) {
+ 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 = reinterpret_cast<DBImpl*>(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 = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ 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<int> cfs;
+ std::vector<std::string> keys;
+ std::vector<std::string> 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, 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 = reinterpret_cast<ColumnFamilyHandleImpl*>(
+ reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(i))
+ ->cfd();
+ ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(DBMultiGetTestWithParam, DBMultiGetTestWithParam,
+ testing::Bool());
+
+TEST_F(DBBasicTest, MultiGetBatchedSimpleUnsorted) {
+ 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<Slice> keys({"no_key", "k5", "k4", "k3", "k2", "k1"});
+ std::vector<PinnableSlice> values(keys.size());
+ std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
+ std::vector<Status> s(keys.size());
+
+ db_->MultiGet(ReadOptions(), 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_F(DBBasicTest, MultiGetBatchedSimpleSorted) {
+ 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<Slice> keys({"k1", "k2", "k3", "k4", "k5", "no_key"});
+ std::vector<PinnableSlice> values(keys.size());
+ std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
+ std::vector<Status> s(keys.size());
+
+ db_->MultiGet(ReadOptions(), 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 (ChangeCompactOptions());
+}
+
+TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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<std::string> keys;
+ std::vector<std::string> values;
+
+ for (int i = 64; i < 80; ++i) {
+ keys.push_back("key_" + std::to_string(i));
+ }
+
+ values = MultiGet(keys, nullptr);
+ 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_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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) {
+ Flush();
+ num_keys = 0;
+ }
+ }
+ if (num_keys > 0) {
+ 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<std::string> keys;
+ std::vector<std::string> values;
+
+ for (int i = 32; i < 80; ++i) {
+ keys.push_back("key_" + std::to_string(i));
+ }
+
+ values = MultiGet(keys, nullptr);
+ 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 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<bool> {
+};
+
+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();
+
+ // First key is not in the prefix_extractor domain
+ 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<std::string> mem_keys(
+ {"k", "kk1", "kk2", "kk3", "kk4", "rofl", "lmho"});
+ std::vector<std::string> inmem_values;
+ inmem_values = MultiGet(mem_keys, nullptr);
+ ASSERT_EQ(inmem_values[0], "v0");
+ ASSERT_EQ(inmem_values[1], "v1");
+ ASSERT_EQ(inmem_values[2], "v2");
+ ASSERT_EQ(inmem_values[3], "v3");
+ ASSERT_EQ(inmem_values[4], "v4");
+ ASSERT_EQ(get_perf_context()->bloom_memtable_miss_count, 2);
+ ASSERT_EQ(get_perf_context()->bloom_memtable_hit_count, 5);
+ ASSERT_OK(Flush());
+
+ std::vector<std::string> keys({"k", "kk1", "kk2", "kk3", "kk4"});
+ std::vector<std::string> values;
+ get_perf_context()->Reset();
+ values = MultiGet(keys, nullptr);
+ ASSERT_EQ(values[0], "v0");
+ ASSERT_EQ(values[1], "v1");
+ ASSERT_EQ(values[2], "v2");
+ ASSERT_EQ(values[3], "v3");
+ ASSERT_EQ(values[4], "v4");
+ // Filter hits for 4 in-domain keys
+ ASSERT_EQ(get_perf_context()->bloom_sst_hit_count, 4);
+}
+
+INSTANTIATE_TEST_CASE_P(MultiGetPrefix, MultiGetPrefixExtractorTest,
+ ::testing::Bool());
+
+#ifndef ROCKSDB_LITE
+class DBMultiGetRowCacheTest : public DBBasicTest,
+ public ::testing::WithParamInterface<bool> {};
+
+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"));
+ Flush(1);
+ ASSERT_OK(Put(1, "k5", "v5"));
+ const Snapshot* snap1 = dbfull()->GetSnapshot();
+ ASSERT_OK(Delete(1, "k4"));
+ Flush(1);
+ const Snapshot* snap2 = dbfull()->GetSnapshot();
+
+ get_perf_context()->Reset();
+
+ std::vector<Slice> keys({"no_key", "k5", "k4", "k3", "k1"});
+ std::vector<PinnableSlice> values(keys.size());
+ std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
+ std::vector<Status> 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<KeyVersion> key_versions;
+ ASSERT_OK(ROCKSDB_NAMESPACE::GetAllKeyVersions(
+ db_, Slice(), Slice(), std::numeric_limits<size_t>::max(),
+ &key_versions));
+ ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
+ ASSERT_OK(ROCKSDB_NAMESPACE::GetAllKeyVersions(
+ db_, handles_[0], Slice(), Slice(), std::numeric_limits<size_t>::max(),
+ &key_versions));
+ ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
+
+ // Check non-default column family
+ for (size_t i = 0; i != kNumInserts - 1; ++i) {
+ ASSERT_OK(Put(1, std::to_string(i), "value"));
+ }
+ for (size_t i = 0; i != kNumUpdates - 1; ++i) {
+ ASSERT_OK(Put(1, std::to_string(i), "value1"));
+ }
+ for (size_t i = 0; i != kNumDeletes - 1; ++i) {
+ ASSERT_OK(Delete(1, std::to_string(i)));
+ }
+ ASSERT_OK(ROCKSDB_NAMESPACE::GetAllKeyVersions(
+ db_, handles_[1], Slice(), Slice(), std::numeric_limits<size_t>::max(),
+ &key_versions));
+ ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
+}
+#endif // !ROCKSDB_LITE
+
+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(table_options.block_size >
+ BlockBasedTable::kMultiGetReadStackBufSize);
+ options.table_factory.reset(new BlockBasedTableFactory(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(RandomString(&rnd, 128) + zero_str);
+ assert(Put(Key(i), value) == Status::OK());
+ }
+ Flush();
+
+ std::vector<std::string> key_data(10);
+ std::vector<Slice> keys;
+ // We cannot resize a PinnableSlice vector, so just set initial size to
+ // largest we think we will need
+ std::vector<PinnableSlice> values(10);
+ std::vector<Status> 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);
+}
+
+class DBBasicTestWithParallelIO
+ : public DBTestBase,
+ public testing::WithParamInterface<std::tuple<bool, bool, bool, bool>> {
+ public:
+ DBBasicTestWithParallelIO() : DBTestBase("/db_basic_test_with_parallel_io") {
+ bool compressed_cache = std::get<0>(GetParam());
+ bool uncompressed_cache = std::get<1>(GetParam());
+ compression_enabled_ = std::get<2>(GetParam());
+ fill_cache_ = std::get<3>(GetParam());
+
+ if (compressed_cache) {
+ std::shared_ptr<Cache> cache = NewLRUCache(1048576);
+ compressed_cache_ = std::make_shared<MyBlockCache>(cache);
+ }
+ if (uncompressed_cache) {
+ std::shared_ptr<Cache> cache = NewLRUCache(1048576);
+ uncompressed_cache_ = std::make_shared<MyBlockCache>(cache);
+ }
+
+ env_->count_random_reads_ = true;
+
+ Options options = CurrentOptions();
+ Random rnd(301);
+ BlockBasedTableOptions table_options;
+
+#ifndef ROCKSDB_LITE
+ if (compression_enabled_) {
+ std::vector<CompressionType> compression_types;
+ compression_types = GetSupportedCompressions();
+ // Not every platform may have compression libraries available, so
+ // dynamically pick based on what's available
+ if (compression_types.size() == 0) {
+ compression_enabled_ = false;
+ } else {
+ options.compression = compression_types[0];
+ }
+ }
+#else
+ // GetSupportedCompressions() is not available in LITE build
+ if (!Snappy_Supported()) {
+ compression_enabled_ = false;
+ }
+#endif //ROCKSDB_LITE
+
+ 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.block_cache_compressed = compressed_cache_;
+ table_options.flush_block_policy_factory.reset(
+ new MyFlushBlockPolicyFactory());
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ if (!compression_enabled_) {
+ options.compression = kNoCompression;
+ }
+ 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
+ values_.emplace_back(RandomString(&rnd, 128) + zero_str);
+ assert(Put(Key(i), values_[i]) == Status::OK());
+ }
+ Flush();
+
+ for (int i = 0; i < 100; ++i) {
+ // block cannot gain space by compression
+ uncompressable_values_.emplace_back(RandomString(&rnd, 256) + '\0');
+ std::string tmp_key = "a" + Key(i);
+ assert(Put(tmp_key, uncompressable_values_[i]) == Status::OK());
+ }
+ Flush();
+ }
+
+ 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;
+ }
+
+ 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; }
+
+ static void SetUpTestCase() {}
+ static void TearDownTestCase() {}
+
+ private:
+ 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 Cache {
+ public:
+ explicit MyBlockCache(std::shared_ptr<Cache>& target)
+ : target_(target), num_lookups_(0), num_found_(0), num_inserts_(0) {}
+
+ virtual const char* Name() const override { return "MyBlockCache"; }
+
+ virtual Status Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Handle** handle = nullptr,
+ Priority priority = Priority::LOW) override {
+ num_inserts_++;
+ return target_->Insert(key, value, charge, deleter, handle, priority);
+ }
+
+ virtual Handle* Lookup(const Slice& key,
+ Statistics* stats = nullptr) override {
+ num_lookups_++;
+ Handle* handle = target_->Lookup(key, stats);
+ if (handle != nullptr) {
+ num_found_++;
+ }
+ return handle;
+ }
+
+ virtual bool Ref(Handle* handle) override { return target_->Ref(handle); }
+
+ virtual bool Release(Handle* handle, bool force_erase = false) override {
+ return target_->Release(handle, force_erase);
+ }
+
+ virtual void* Value(Handle* handle) override {
+ return target_->Value(handle);
+ }
+
+ virtual void Erase(const Slice& key) override { target_->Erase(key); }
+ virtual uint64_t NewId() override { return target_->NewId(); }
+
+ virtual void SetCapacity(size_t capacity) override {
+ target_->SetCapacity(capacity);
+ }
+
+ virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override {
+ target_->SetStrictCapacityLimit(strict_capacity_limit);
+ }
+
+ virtual bool HasStrictCapacityLimit() const override {
+ return target_->HasStrictCapacityLimit();
+ }
+
+ virtual size_t GetCapacity() const override {
+ return target_->GetCapacity();
+ }
+
+ virtual size_t GetUsage() const override { return target_->GetUsage(); }
+
+ virtual size_t GetUsage(Handle* handle) const override {
+ return target_->GetUsage(handle);
+ }
+
+ virtual size_t GetPinnedUsage() const override {
+ return target_->GetPinnedUsage();
+ }
+
+ virtual size_t GetCharge(Handle* /*handle*/) const override { return 0; }
+
+ virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) override {
+ return target_->ApplyToAllCacheEntries(callback, thread_safe);
+ }
+
+ virtual void EraseUnRefEntries() override {
+ return target_->EraseUnRefEntries();
+ }
+
+ int num_lookups() { return num_lookups_; }
+
+ int num_found() { return num_found_; }
+
+ int num_inserts() { return num_inserts_; }
+
+ private:
+ std::shared_ptr<Cache> target_;
+ int num_lookups_;
+ int num_found_;
+ int num_inserts_;
+ };
+
+ std::shared_ptr<MyBlockCache> compressed_cache_;
+ std::shared_ptr<MyBlockCache> uncompressed_cache_;
+ bool compression_enabled_;
+ std::vector<std::string> values_;
+ std::vector<std::string> uncompressable_values_;
+ bool fill_cache_;
+};
+
+TEST_P(DBBasicTestWithParallelIO, MultiGet) {
+ std::vector<std::string> key_data(10);
+ std::vector<Slice> keys;
+ // We cannot resize a PinnableSlice vector, so just set initial size to
+ // largest we think we will need
+ std::vector<PinnableSlice> values(10);
+ std::vector<Status> 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<int> 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<int> 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<int> 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, MultiGetWithChecksumMismatch) {
+ std::vector<std::string> key_data(10);
+ std::vector<Slice> keys;
+ // We cannot resize a PinnableSlice vector, so just set initial size to
+ // largest we think we will need
+ std::vector<PinnableSlice> values(10);
+ std::vector<Status> statuses;
+ int read_count = 0;
+ ReadOptions ro;
+ ro.fill_cache = fill_cache();
+
+ SyncPoint::GetInstance()->SetCallBack(
+ "RetrieveMultipleBlocks:VerifyChecksum", [&](void *status) {
+ Status* s = static_cast<Status*>(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<std::string> key_data(10);
+ std::vector<Slice> keys;
+ // We cannot resize a PinnableSlice vector, so just set initial size to
+ // largest we think we will need
+ std::vector<PinnableSlice> values(10);
+ std::vector<Status> statuses;
+ ReadOptions ro;
+ ro.fill_cache = fill_cache();
+
+ SyncPoint::GetInstance()->SetCallBack(
+ "TableCache::MultiGet:FindTable", [&](void *status) {
+ Status* s = static_cast<Status*>(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 - Compressed cache enabled
+ // Param 1 - Uncompressed cache enabled
+ // Param 2 - Data compression enabled
+ // Param 3 - ReadOptions::fill_cache
+ ::testing::Combine(::testing::Bool(), ::testing::Bool(),
+ ::testing::Bool(), ::testing::Bool()));
+
+class DBBasicTestWithTimestampBase : public DBTestBase {
+ public:
+ explicit DBBasicTestWithTimestampBase(const std::string& dbname)
+ : DBTestBase(dbname) {}
+
+ protected:
+ class TestComparatorBase : public Comparator {
+ public:
+ explicit TestComparatorBase(size_t ts_sz) : Comparator(ts_sz) {}
+
+ const char* Name() const override { return "TestComparator"; }
+
+ void FindShortSuccessor(std::string*) const override {}
+
+ void FindShortestSeparator(std::string*, const Slice&) const override {}
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ int r = CompareWithoutTimestamp(a, b);
+ if (r != 0 || 0 == timestamp_size()) {
+ return r;
+ }
+ return CompareTimestamp(
+ Slice(a.data() + a.size() - timestamp_size(), timestamp_size()),
+ Slice(b.data() + b.size() - timestamp_size(), timestamp_size()));
+ }
+
+ virtual int CompareImpl(const Slice& a, const Slice& b) const = 0;
+
+ int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
+ assert(a.size() >= timestamp_size());
+ assert(b.size() >= timestamp_size());
+ Slice k1 = StripTimestampFromUserKey(a, timestamp_size());
+ Slice k2 = StripTimestampFromUserKey(b, timestamp_size());
+
+ return CompareImpl(k1, k2);
+ }
+
+ int CompareTimestamp(const Slice& ts1, const Slice& ts2) const override {
+ if (!ts1.data() && !ts2.data()) {
+ return 0;
+ } else if (ts1.data() && !ts2.data()) {
+ return 1;
+ } else if (!ts1.data() && ts2.data()) {
+ return -1;
+ }
+ assert(ts1.size() == ts2.size());
+ uint64_t low1 = 0;
+ uint64_t low2 = 0;
+ uint64_t high1 = 0;
+ uint64_t high2 = 0;
+ auto* ptr1 = const_cast<Slice*>(&ts1);
+ auto* ptr2 = const_cast<Slice*>(&ts2);
+ if (!GetFixed64(ptr1, &low1) || !GetFixed64(ptr1, &high1) ||
+ !GetFixed64(ptr2, &low2) || !GetFixed64(ptr2, &high2)) {
+ assert(false);
+ }
+ if (high1 < high2) {
+ return 1;
+ } else if (high1 > high2) {
+ return -1;
+ }
+ if (low1 < low2) {
+ return 1;
+ } else if (low1 > low2) {
+ return -1;
+ }
+ return 0;
+ }
+ };
+
+ Slice EncodeTimestamp(uint64_t low, uint64_t high, std::string* ts) {
+ assert(nullptr != ts);
+ ts->clear();
+ PutFixed64(ts, low);
+ PutFixed64(ts, high);
+ assert(ts->size() == sizeof(low) + sizeof(high));
+ return Slice(*ts);
+ }
+};
+
+class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
+ public:
+ DBBasicTestWithTimestamp()
+ : DBBasicTestWithTimestampBase("/db_basic_test_with_timestamp") {}
+
+ protected:
+ class TestComparator : public TestComparatorBase {
+ public:
+ const int kKeyPrefixLength =
+ 3; // 3: length of "key" in generated keys ("key" + std::to_string(j))
+ explicit TestComparator(size_t ts_sz) : TestComparatorBase(ts_sz) {}
+
+ int CompareImpl(const Slice& a, const Slice& b) const override {
+ int n1 = atoi(
+ std::string(a.data() + kKeyPrefixLength, a.size() - kKeyPrefixLength)
+ .c_str());
+ int n2 = atoi(
+ std::string(b.data() + kKeyPrefixLength, b.size() - kKeyPrefixLength)
+ .c_str());
+ return (n1 < n2) ? -1 : (n1 > n2) ? 1 : 0;
+ }
+ };
+};
+
+#ifndef ROCKSDB_LITE
+// 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<std::string> GetFlushedFiles() {
+ std::vector<std::string> result;
+ {
+ InstrumentedMutexLock lock(&mutex_);
+ result = flushed_files_;
+ }
+ return result;
+ }
+
+ void ClearFlushedFiles() {
+ InstrumentedMutexLock lock(&mutex_);
+ flushed_files_.clear();
+ }
+
+ private:
+ std::vector<std::string> flushed_files_;
+ InstrumentedMutex mutex_;
+};
+
+TEST_F(DBBasicTestWithTimestamp, PutAndGetWithCompaction) {
+ const int kNumKeysPerFile = 8192;
+ 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(new SpecialSkipListFactory(kNumKeysPerFile));
+
+ FlushedFileCollector* collector = new FlushedFileCollector();
+ options.listeners.emplace_back(collector);
+
+ std::string tmp;
+ size_t ts_sz = EncodeTimestamp(0, 0, &tmp).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<std::string> write_ts_strs(kNumTimestamps);
+ std::vector<std::string> read_ts_strs(kNumTimestamps);
+ std::vector<Slice> write_ts_list;
+ std::vector<Slice> read_ts_list;
+
+ for (size_t i = 0; i != kNumTimestamps; ++i) {
+ write_ts_list.emplace_back(EncodeTimestamp(i * 2, 0, &write_ts_strs[i]));
+ read_ts_list.emplace_back(EncodeTimestamp(1 + i * 2, 0, &read_ts_strs[i]));
+ const Slice& write_ts = write_ts_list.back();
+ WriteOptions wopts;
+ wopts.timestamp = &write_ts;
+ for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
+ for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
+ ASSERT_OK(Put(cf, "key" + std::to_string(j),
+ "value_" + std::to_string(j) + "_" + std::to_string(i),
+ wopts));
+ if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 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));
+
+ // 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;
+ db_->CompactFiles(compact_opt, handles_[cf],
+ collector->GetFlushedFiles(),
+ static_cast<int>(kNumTimestamps - i));
+ collector->ClearFlushedFiles();
+ }
+ }
+ }
+ }
+ const auto& verify_db_func = [&]() {
+ for (size_t i = 0; i != kNumTimestamps; ++i) {
+ ReadOptions ropts;
+ ropts.timestamp = &read_ts_list[i];
+ for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
+ ColumnFamilyHandle* cfh = handles_[cf];
+ for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
+ std::string value;
+ ASSERT_OK(db_->Get(ropts, cfh, "key" + std::to_string(j), &value));
+ ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
+ value);
+ }
+ }
+ }
+ };
+ verify_db_func();
+}
+#endif // !ROCKSDB_LITE
+
+class DBBasicTestWithTimestampWithParam
+ : public DBBasicTestWithTimestampBase,
+ public testing::WithParamInterface<bool> {
+ public:
+ DBBasicTestWithTimestampWithParam()
+ : DBBasicTestWithTimestampBase(
+ "/db_basic_test_with_timestamp_with_param") {}
+
+ protected:
+ class TestComparator : public TestComparatorBase {
+ private:
+ const Comparator* cmp_without_ts_;
+
+ public:
+ explicit TestComparator(size_t ts_sz)
+ : TestComparatorBase(ts_sz), cmp_without_ts_(nullptr) {
+ cmp_without_ts_ = BytewiseComparator();
+ }
+
+ int CompareImpl(const Slice& a, const Slice& b) const override {
+ return cmp_without_ts_->Compare(a, b);
+ }
+ };
+};
+
+TEST_P(DBBasicTestWithTimestampWithParam, PutAndGet) {
+ const int kNumKeysPerFile = 8192;
+ const size_t kNumTimestamps = 6;
+ bool memtable_only = GetParam();
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.env = env_;
+ options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
+ std::string tmp;
+ size_t ts_sz = EncodeTimestamp(0, 0, &tmp).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));
+
+ std::vector<CompressionType> compression_types;
+ compression_types.push_back(kNoCompression);
+ if (Zlib_Supported()) {
+ compression_types.push_back(kZlibCompression);
+ }
+#if LZ4_VERSION_NUMBER >= 10400 // r124+
+ compression_types.push_back(kLZ4Compression);
+ compression_types.push_back(kLZ4HCCompression);
+#endif // LZ4_VERSION_NUMBER >= 10400
+ if (ZSTD_Supported()) {
+ compression_types.push_back(kZSTD);
+ }
+
+ // Switch compression dictionary on/off to check key extraction
+ // correctness in kBuffered state
+ std::vector<uint32_t> max_dict_bytes_list = {0, 1 << 14}; // 0 or 16KB
+
+ for (auto compression_type : compression_types) {
+ for (uint32_t max_dict_bytes : max_dict_bytes_list) {
+ options.compression = compression_type;
+ options.compression_opts.max_dict_bytes = max_dict_bytes;
+ if (compression_type == kZSTD) {
+ options.compression_opts.zstd_max_train_bytes = max_dict_bytes;
+ }
+ 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<std::string> write_ts_strs(kNumTimestamps);
+ std::vector<std::string> read_ts_strs(kNumTimestamps);
+ std::vector<Slice> write_ts_list;
+ std::vector<Slice> read_ts_list;
+
+ for (size_t i = 0; i != kNumTimestamps; ++i) {
+ write_ts_list.emplace_back(
+ EncodeTimestamp(i * 2, 0, &write_ts_strs[i]));
+ read_ts_list.emplace_back(
+ EncodeTimestamp(1 + i * 2, 0, &read_ts_strs[i]));
+ const Slice& write_ts = write_ts_list.back();
+ WriteOptions wopts;
+ wopts.timestamp = &write_ts;
+ for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
+ for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) {
+ ASSERT_OK(Put(
+ cf, "key" + std::to_string(j),
+ "value_" + std::to_string(j) + "_" + std::to_string(i), wopts));
+ }
+ if (!memtable_only) {
+ ASSERT_OK(Flush(cf));
+ }
+ }
+ }
+ const auto& verify_db_func = [&]() {
+ for (size_t i = 0; i != kNumTimestamps; ++i) {
+ ReadOptions ropts;
+ ropts.timestamp = &read_ts_list[i];
+ for (int cf = 0; cf != static_cast<int>(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, "key" + std::to_string(j), &value));
+ ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
+ value);
+ }
+ }
+ }
+ };
+ verify_db_func();
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(Timestamp, DBBasicTestWithTimestampWithParam,
+ ::testing::Bool());
+
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_blob_index_test.cc b/storage/rocksdb/rocksdb/db/db_blob_index_test.cc
new file mode 100644
index 00000000..24862f77
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_blob_index_test.cc
@@ -0,0 +1,436 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/arena_wrapped_db_iter.h"
+#include "db/column_family.h"
+#include "db/db_iter.h"
+#include "db/db_test_util.h"
+#include "db/dbformat.h"
+#include "db/write_batch_internal.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// kTypeBlobIndex is a value type used by BlobDB only. The base rocksdb
+// should accept the value type on write, and report not supported value
+// for reads, unless caller request for it explicitly. The base rocksdb
+// doesn't understand format of actual blob index (the value).
+class DBBlobIndexTest : public DBTestBase {
+ public:
+ enum Tier {
+ kMemtable = 0,
+ kImmutableMemtables = 1,
+ kL0SstFile = 2,
+ kLnSstFile = 3,
+ };
+ const std::vector<Tier> kAllTiers = {Tier::kMemtable,
+ Tier::kImmutableMemtables,
+ Tier::kL0SstFile, Tier::kLnSstFile};
+
+ DBBlobIndexTest() : DBTestBase("/db_blob_index_test") {}
+
+ ColumnFamilyHandle* cfh() { return dbfull()->DefaultColumnFamily(); }
+
+ ColumnFamilyData* cfd() {
+ return reinterpret_cast<ColumnFamilyHandleImpl*>(cfh())->cfd();
+ }
+
+ Status PutBlobIndex(WriteBatch* batch, const Slice& key,
+ const Slice& blob_index) {
+ return WriteBatchInternal::PutBlobIndex(batch, cfd()->GetID(), key,
+ blob_index);
+ }
+
+ Status Write(WriteBatch* batch) {
+ return dbfull()->Write(WriteOptions(), batch);
+ }
+
+ std::string GetImpl(const Slice& key, bool* is_blob_index = nullptr,
+ const Snapshot* snapshot = nullptr) {
+ ReadOptions read_options;
+ read_options.snapshot = snapshot;
+ PinnableSlice value;
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = cfh();
+ get_impl_options.value = &value;
+ get_impl_options.is_blob_index = is_blob_index;
+ auto s = dbfull()->GetImpl(read_options, key, get_impl_options);
+ if (s.IsNotFound()) {
+ return "NOT_FOUND";
+ }
+ if (s.IsNotSupported()) {
+ return "NOT_SUPPORTED";
+ }
+ if (!s.ok()) {
+ return s.ToString();
+ }
+ return value.ToString();
+ }
+
+ std::string GetBlobIndex(const Slice& key,
+ const Snapshot* snapshot = nullptr) {
+ bool is_blob_index = false;
+ std::string value = GetImpl(key, &is_blob_index, snapshot);
+ if (!is_blob_index) {
+ return "NOT_BLOB";
+ }
+ return value;
+ }
+
+ ArenaWrappedDBIter* GetBlobIterator() {
+ return dbfull()->NewIteratorImpl(
+ ReadOptions(), cfd(), dbfull()->GetLatestSequenceNumber(),
+ nullptr /*read_callback*/, true /*allow_blob*/);
+ }
+
+ Options GetTestOptions() {
+ Options options;
+ options.create_if_missing = true;
+ options.num_levels = 2;
+ options.disable_auto_compactions = true;
+ // Disable auto flushes.
+ options.max_write_buffer_number = 10;
+ options.min_write_buffer_number_to_merge = 10;
+ options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ return options;
+ }
+
+ void MoveDataTo(Tier tier) {
+ switch (tier) {
+ case Tier::kMemtable:
+ break;
+ case Tier::kImmutableMemtables:
+ ASSERT_OK(dbfull()->TEST_SwitchMemtable());
+ break;
+ case Tier::kL0SstFile:
+ ASSERT_OK(Flush());
+ break;
+ case Tier::kLnSstFile:
+ ASSERT_OK(Flush());
+ ASSERT_OK(Put("a", "dummy"));
+ ASSERT_OK(Put("z", "dummy"));
+ ASSERT_OK(Flush());
+ ASSERT_OK(
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,1", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ break;
+ }
+ }
+};
+
+// Should be able to write kTypeBlobIndex to memtables and SST files.
+TEST_F(DBBlobIndexTest, Write) {
+ for (auto tier : kAllTiers) {
+ DestroyAndReopen(GetTestOptions());
+ for (int i = 1; i <= 5; i++) {
+ std::string index = ToString(i);
+ WriteBatch batch;
+ ASSERT_OK(PutBlobIndex(&batch, "key" + index, "blob" + index));
+ ASSERT_OK(Write(&batch));
+ }
+ MoveDataTo(tier);
+ for (int i = 1; i <= 5; i++) {
+ std::string index = ToString(i);
+ ASSERT_EQ("blob" + index, GetBlobIndex("key" + index));
+ }
+ }
+}
+
+// Get should be able to return blob index if is_blob_index is provided,
+// otherwise return Status::NotSupported status.
+TEST_F(DBBlobIndexTest, Get) {
+ for (auto tier : kAllTiers) {
+ DestroyAndReopen(GetTestOptions());
+ WriteBatch batch;
+ ASSERT_OK(batch.Put("key", "value"));
+ ASSERT_OK(PutBlobIndex(&batch, "blob_key", "blob_index"));
+ ASSERT_OK(Write(&batch));
+ MoveDataTo(tier);
+ // Verify normal value
+ bool is_blob_index = false;
+ PinnableSlice value;
+ ASSERT_EQ("value", Get("key"));
+ ASSERT_EQ("value", GetImpl("key"));
+ ASSERT_EQ("value", GetImpl("key", &is_blob_index));
+ ASSERT_FALSE(is_blob_index);
+ // Verify blob index
+ ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
+ ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
+ ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
+ ASSERT_TRUE(is_blob_index);
+ }
+}
+
+// Get should NOT return Status::NotSupported if blob index is updated with
+// a normal value.
+TEST_F(DBBlobIndexTest, Updated) {
+ for (auto tier : kAllTiers) {
+ DestroyAndReopen(GetTestOptions());
+ WriteBatch batch;
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(PutBlobIndex(&batch, "key" + ToString(i), "blob_index"));
+ }
+ ASSERT_OK(Write(&batch));
+ // Avoid blob values from being purged.
+ const Snapshot* snapshot = dbfull()->GetSnapshot();
+ ASSERT_OK(Put("key1", "new_value"));
+ ASSERT_OK(Merge("key2", "a"));
+ ASSERT_OK(Merge("key2", "b"));
+ ASSERT_OK(Merge("key2", "c"));
+ ASSERT_OK(Delete("key3"));
+ ASSERT_OK(SingleDelete("key4"));
+ ASSERT_OK(Delete("key5"));
+ ASSERT_OK(Merge("key5", "a"));
+ ASSERT_OK(Merge("key5", "b"));
+ ASSERT_OK(Merge("key5", "c"));
+ ASSERT_OK(dbfull()->DeleteRange(WriteOptions(), cfh(), "key6", "key9"));
+ MoveDataTo(tier);
+ for (int i = 0; i < 10; i++) {
+ ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
+ }
+ ASSERT_EQ("new_value", Get("key1"));
+ ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
+ ASSERT_EQ("NOT_FOUND", Get("key3"));
+ ASSERT_EQ("NOT_FOUND", Get("key4"));
+ ASSERT_EQ("a,b,c", GetImpl("key5"));
+ for (int i = 6; i < 9; i++) {
+ ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
+ }
+ ASSERT_EQ("blob_index", GetBlobIndex("key9"));
+ dbfull()->ReleaseSnapshot(snapshot);
+ }
+}
+
+// Iterator should get blob value if allow_blob flag is set,
+// otherwise return Status::NotSupported status.
+TEST_F(DBBlobIndexTest, Iterate) {
+ const std::vector<std::vector<ValueType>> data = {
+ /*00*/ {kTypeValue},
+ /*01*/ {kTypeBlobIndex},
+ /*02*/ {kTypeValue},
+ /*03*/ {kTypeBlobIndex, kTypeValue},
+ /*04*/ {kTypeValue},
+ /*05*/ {kTypeValue, kTypeBlobIndex},
+ /*06*/ {kTypeValue},
+ /*07*/ {kTypeDeletion, kTypeBlobIndex},
+ /*08*/ {kTypeValue},
+ /*09*/ {kTypeSingleDeletion, kTypeBlobIndex},
+ /*10*/ {kTypeValue},
+ /*11*/ {kTypeMerge, kTypeMerge, kTypeMerge, kTypeBlobIndex},
+ /*12*/ {kTypeValue},
+ /*13*/
+ {kTypeMerge, kTypeMerge, kTypeMerge, kTypeDeletion, kTypeBlobIndex},
+ /*14*/ {kTypeValue},
+ /*15*/ {kTypeBlobIndex},
+ /*16*/ {kTypeValue},
+ };
+
+ auto get_key = [](int index) {
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%02d", index);
+ return "key" + std::string(buf);
+ };
+
+ auto get_value = [&](int index, int version) {
+ return get_key(index) + "_value" + ToString(version);
+ };
+
+ auto check_iterator = [&](Iterator* iterator, Status::Code expected_status,
+ const Slice& expected_value) {
+ ASSERT_EQ(expected_status, iterator->status().code());
+ if (expected_status == Status::kOk) {
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ(expected_value, iterator->value());
+ } else {
+ ASSERT_FALSE(iterator->Valid());
+ }
+ };
+
+ auto create_normal_iterator = [&]() -> Iterator* {
+ return dbfull()->NewIterator(ReadOptions());
+ };
+
+ auto create_blob_iterator = [&]() -> Iterator* { return GetBlobIterator(); };
+
+ auto check_is_blob = [&](bool is_blob) {
+ return [is_blob](Iterator* iterator) {
+ ASSERT_EQ(is_blob,
+ reinterpret_cast<ArenaWrappedDBIter*>(iterator)->IsBlob());
+ };
+ };
+
+ auto verify = [&](int index, Status::Code expected_status,
+ const Slice& forward_value, const Slice& backward_value,
+ std::function<Iterator*()> create_iterator,
+ std::function<void(Iterator*)> extra_check = nullptr) {
+ // Seek
+ auto* iterator = create_iterator();
+ ASSERT_OK(iterator->Refresh());
+ iterator->Seek(get_key(index));
+ check_iterator(iterator, expected_status, forward_value);
+ if (extra_check) {
+ extra_check(iterator);
+ }
+ delete iterator;
+
+ // Next
+ iterator = create_iterator();
+ ASSERT_OK(iterator->Refresh());
+ iterator->Seek(get_key(index - 1));
+ ASSERT_TRUE(iterator->Valid());
+ iterator->Next();
+ check_iterator(iterator, expected_status, forward_value);
+ if (extra_check) {
+ extra_check(iterator);
+ }
+ delete iterator;
+
+ // SeekForPrev
+ iterator = create_iterator();
+ ASSERT_OK(iterator->Refresh());
+ iterator->SeekForPrev(get_key(index));
+ check_iterator(iterator, expected_status, backward_value);
+ if (extra_check) {
+ extra_check(iterator);
+ }
+ delete iterator;
+
+ // Prev
+ iterator = create_iterator();
+ iterator->Seek(get_key(index + 1));
+ ASSERT_TRUE(iterator->Valid());
+ iterator->Prev();
+ check_iterator(iterator, expected_status, backward_value);
+ if (extra_check) {
+ extra_check(iterator);
+ }
+ delete iterator;
+ };
+
+ for (auto tier : {Tier::kMemtable} /*kAllTiers*/) {
+ // Avoid values from being purged.
+ std::vector<const Snapshot*> snapshots;
+ DestroyAndReopen(GetTestOptions());
+
+ // fill data
+ for (int i = 0; i < static_cast<int>(data.size()); i++) {
+ for (int j = static_cast<int>(data[i].size()) - 1; j >= 0; j--) {
+ std::string key = get_key(i);
+ std::string value = get_value(i, j);
+ WriteBatch batch;
+ switch (data[i][j]) {
+ case kTypeValue:
+ ASSERT_OK(Put(key, value));
+ break;
+ case kTypeDeletion:
+ ASSERT_OK(Delete(key));
+ break;
+ case kTypeSingleDeletion:
+ ASSERT_OK(SingleDelete(key));
+ break;
+ case kTypeMerge:
+ ASSERT_OK(Merge(key, value));
+ break;
+ case kTypeBlobIndex:
+ ASSERT_OK(PutBlobIndex(&batch, key, value));
+ ASSERT_OK(Write(&batch));
+ break;
+ default:
+ assert(false);
+ };
+ }
+ snapshots.push_back(dbfull()->GetSnapshot());
+ }
+ ASSERT_OK(
+ dbfull()->DeleteRange(WriteOptions(), cfh(), get_key(15), get_key(16)));
+ snapshots.push_back(dbfull()->GetSnapshot());
+ MoveDataTo(tier);
+
+ // Normal iterator
+ verify(1, Status::kNotSupported, "", "", create_normal_iterator);
+ verify(3, Status::kNotSupported, "", "", create_normal_iterator);
+ verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
+ create_normal_iterator);
+ verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
+ create_normal_iterator);
+ verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
+ create_normal_iterator);
+ verify(11, Status::kNotSupported, "", "", create_normal_iterator);
+ verify(13, Status::kOk,
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ create_normal_iterator);
+ verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
+ create_normal_iterator);
+
+ // Iterator with blob support
+ verify(1, Status::kOk, get_value(1, 0), get_value(1, 0),
+ create_blob_iterator, check_is_blob(true));
+ verify(3, Status::kOk, get_value(3, 0), get_value(3, 0),
+ create_blob_iterator, check_is_blob(true));
+ verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(11, Status::kNotSupported, "", "", create_blob_iterator);
+ verify(13, Status::kOk,
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
+ create_blob_iterator, check_is_blob(false));
+
+#ifndef ROCKSDB_LITE
+ // Iterator with blob support and using seek.
+ ASSERT_OK(dbfull()->SetOptions(
+ cfh(), {{"max_sequential_skip_in_iterations", "0"}}));
+ verify(1, Status::kOk, get_value(1, 0), get_value(1, 0),
+ create_blob_iterator, check_is_blob(true));
+ verify(3, Status::kOk, get_value(3, 0), get_value(3, 0),
+ create_blob_iterator, check_is_blob(true));
+ verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(11, Status::kNotSupported, "", "", create_blob_iterator);
+ verify(13, Status::kOk,
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
+ create_blob_iterator, check_is_blob(false));
+ verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
+ create_blob_iterator, check_is_blob(false));
+#endif // !ROCKSDB_LITE
+
+ for (auto* snapshot : snapshots) {
+ dbfull()->ReleaseSnapshot(snapshot);
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_block_cache_test.cc b/storage/rocksdb/rocksdb/db/db_block_cache_test.cc
new file mode 100644
index 00000000..3031e56b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_block_cache_test.cc
@@ -0,0 +1,761 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdlib>
+#include "cache/lru_cache.h"
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+#include "util/compression.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;
+ size_t compressed_miss_count_ = 0;
+ size_t compressed_hit_count_ = 0;
+ size_t compressed_insert_count_ = 0;
+ size_t compressed_failure_count_ = 0;
+
+ public:
+ const size_t kNumBlocks = 10;
+ const size_t kValueSize = 100;
+
+ DBBlockCacheTest() : DBTestBase("/db_block_cache_test") {}
+
+ 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(new BlockBasedTableFactory(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(ToString(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);
+ compressed_miss_count_ =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS);
+ compressed_hit_count_ =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
+ compressed_insert_count_ =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD);
+ compressed_failure_count_ =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_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;
+ }
+
+ void CheckCompressedCacheCounters(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_COMPRESSED_MISS);
+ size_t new_hit_count =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
+ size_t new_insert_count =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD);
+ size_t new_failure_count =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD_FAILURES);
+ ASSERT_EQ(compressed_miss_count_ + expected_misses, new_miss_count);
+ ASSERT_EQ(compressed_hit_count_ + expected_hits, new_hit_count);
+ ASSERT_EQ(compressed_insert_count_ + expected_inserts, new_insert_count);
+ ASSERT_EQ(compressed_failure_count_ + expected_failures, new_failure_count);
+ compressed_miss_count_ = new_miss_count;
+ compressed_hit_count_ = new_hit_count;
+ compressed_insert_count_ = new_insert_count;
+ compressed_failure_count_ = new_failure_count;
+ }
+};
+
+TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
+ ReadOptions read_options;
+ read_options.fill_cache = false;
+ auto table_options = GetTableOptions();
+ auto options = GetOptions(table_options);
+ InitTable(options);
+
+ std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
+ table_options.block_cache = cache;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+ RecordCacheCounters(options);
+
+ std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
+ Iterator* iter = nullptr;
+
+ ASSERT_EQ(0, cache->GetUsage());
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(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);
+
+ std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
+ table_options.block_cache = cache;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+ RecordCacheCounters(options);
+
+ std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
+ Iterator* iter = nullptr;
+
+ // Load blocks into cache.
+ for (size_t i = 0; i < kNumBlocks - 1; i++) {
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(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(ToString(kNumBlocks - 1));
+ ASSERT_TRUE(iter->status().IsIncomplete());
+ CheckCacheCounters(options, 1, 0, 0, 1);
+ delete iter;
+ iter = nullptr;
+
+ // Release iterators and access cache again.
+ for (size_t i = 0; i < kNumBlocks - 1; i++) {
+ iterators[i].reset();
+ CheckCacheCounters(options, 0, 0, 0, 0);
+ }
+ ASSERT_EQ(0, cache->GetPinnedUsage());
+ for (size_t i = 0; i < kNumBlocks - 1; i++) {
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(i));
+ ASSERT_OK(iter->status());
+ CheckCacheCounters(options, 0, 1, 0, 0);
+ iterators[i].reset(iter);
+ }
+}
+
+#ifdef SNAPPY
+TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
+ ReadOptions read_options;
+ auto table_options = GetTableOptions();
+ auto options = GetOptions(table_options);
+ options.compression = CompressionType::kSnappyCompression;
+ InitTable(options);
+
+ std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
+ std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
+ table_options.block_cache = cache;
+ table_options.block_cache_compressed = compressed_cache;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+ RecordCacheCounters(options);
+
+ std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
+ Iterator* iter = nullptr;
+
+ // Load blocks into cache.
+ for (size_t i = 0; i < kNumBlocks - 1; i++) {
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(i));
+ ASSERT_OK(iter->status());
+ CheckCacheCounters(options, 1, 0, 1, 0);
+ CheckCompressedCacheCounters(options, 1, 0, 1, 0);
+ iterators[i].reset(iter);
+ }
+ size_t usage = cache->GetUsage();
+ ASSERT_LT(0, usage);
+ ASSERT_EQ(usage, cache->GetPinnedUsage());
+ size_t compressed_usage = compressed_cache->GetUsage();
+ ASSERT_LT(0, compressed_usage);
+ // Compressed block cache cannot be pinned.
+ ASSERT_EQ(0, compressed_cache->GetPinnedUsage());
+
+ // Set strict capacity limit flag. Now block will only load into compressed
+ // block cache.
+ cache->SetCapacity(usage);
+ cache->SetStrictCapacityLimit(true);
+ ASSERT_EQ(usage, cache->GetPinnedUsage());
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(kNumBlocks - 1));
+ ASSERT_TRUE(iter->status().IsIncomplete());
+ CheckCacheCounters(options, 1, 0, 0, 1);
+ CheckCompressedCacheCounters(options, 1, 0, 1, 0);
+ delete iter;
+ iter = nullptr;
+
+ // Clear strict capacity limit flag. This time we shall hit compressed block
+ // cache.
+ cache->SetStrictCapacityLimit(false);
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(kNumBlocks - 1));
+ ASSERT_OK(iter->status());
+ CheckCacheCounters(options, 1, 0, 1, 0);
+ CheckCompressedCacheCounters(options, 0, 1, 0, 0);
+ delete iter;
+ iter = nullptr;
+}
+#endif // SNAPPY
+
+#ifndef ROCKSDB_LITE
+
+// 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(new BlockBasedTableFactory(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> cache = NewLRUCache(10, 0, true);
+ table_options.block_cache = cache;
+ options.table_factory.reset(new BlockBasedTableFactory(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(ToString(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> cache = NewLRUCache(co);
+ table_options.block_cache = cache;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(20, true));
+ options.table_factory.reset(new BlockBasedTableFactory(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);
+ // The index and filter eviction statistics were broken by the refactoring
+ // that moved the readers out of the block cache. Disabling these until we can
+ // bring the stats back.
+ // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
+ // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
+ // 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);
+ // The index and filter eviction statistics were broken by the refactoring
+ // that moved the readers out of the block cache. Disabling these until we can
+ // bring the stats back.
+ // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT),
+ // index_bytes_insert);
+ // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
+ // filter_bytes_insert);
+}
+
+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*/) {
+ }
+
+ Status Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value), Handle** handle,
+ Priority priority) override {
+ if (priority == Priority::LOW) {
+ low_pri_insert_count++;
+ } else {
+ high_pri_insert_count++;
+ }
+ return LRUCache::Insert(key, value, charge, deleter, 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(new BlockBasedTableFactory(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);
+ }
+ }
+}
+
+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(new BlockBasedTableFactory(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));
+ 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));
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(3, /* Totally 3 files created up to now */
+ TestGetTickerCount(options, BLOCK_CACHE_ADD));
+}
+
+TEST_F(DBBlockCacheTest, CompressedCache) {
+ if (!Snappy_Supported()) {
+ return;
+ }
+ int num_iter = 80;
+
+ // Run this test three iterations.
+ // Iteration 1: only a uncompressed block cache
+ // Iteration 2: only a compressed block cache
+ // Iteration 3: both block cache and compressed cache
+ // Iteration 4: both block cache and compressed cache, but DB is not
+ // compressed
+ for (int iter = 0; iter < 4; iter++) {
+ Options options = CurrentOptions();
+ options.write_buffer_size = 64 * 1024; // small write buffer
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ BlockBasedTableOptions table_options;
+ switch (iter) {
+ case 0:
+ // only uncompressed block cache
+ table_options.block_cache = NewLRUCache(8 * 1024);
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 1:
+ // no block cache, only compressed cache
+ table_options.no_block_cache = true;
+ table_options.block_cache = nullptr;
+ table_options.block_cache_compressed = NewLRUCache(8 * 1024);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 2:
+ // both compressed and uncompressed block cache
+ table_options.block_cache = NewLRUCache(1024);
+ table_options.block_cache_compressed = NewLRUCache(8 * 1024);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 3:
+ // both block cache and compressed cache, but DB is not compressed
+ // also, make block cache sizes bigger, to trigger block cache hits
+ table_options.block_cache = NewLRUCache(1024 * 1024);
+ table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.compression = kNoCompression;
+ break;
+ default:
+ FAIL();
+ }
+ CreateAndReopenWithCF({"pikachu"}, options);
+ // default column family doesn't have block cache
+ Options no_block_cache_opts;
+ no_block_cache_opts.statistics = options.statistics;
+ no_block_cache_opts = CurrentOptions(no_block_cache_opts);
+ BlockBasedTableOptions table_options_no_bc;
+ table_options_no_bc.no_block_cache = true;
+ no_block_cache_opts.table_factory.reset(
+ NewBlockBasedTableFactory(table_options_no_bc));
+ ReopenWithColumnFamilies(
+ {"default", "pikachu"},
+ std::vector<Options>({no_block_cache_opts, options}));
+
+ Random rnd(301);
+
+ // Write 8MB (80 values, each 100K)
+ ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
+ std::vector<std::string> values;
+ std::string str;
+ for (int i = 0; i < num_iter; i++) {
+ if (i % 4 == 0) { // high compression ratio
+ str = RandomString(&rnd, 1000);
+ }
+ values.push_back(str);
+ ASSERT_OK(Put(1, Key(i), values[i]));
+ }
+
+ // flush all data from memtable so that reads are from block cache
+ ASSERT_OK(Flush(1));
+
+ for (int i = 0; i < num_iter; i++) {
+ ASSERT_EQ(Get(1, Key(i)), values[i]);
+ }
+
+ // check that we triggered the appropriate code paths in the cache
+ switch (iter) {
+ case 0:
+ // only uncompressed block cache
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
+ ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
+ break;
+ case 1:
+ // no block cache, only compressed cache
+ ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
+ break;
+ case 2:
+ // both compressed and uncompressed block cache
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
+ break;
+ case 3:
+ // both compressed and uncompressed block cache
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_HIT), 0);
+ ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
+ // compressed doesn't have any hits since blocks are not compressed on
+ // storage
+ ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT), 0);
+ break;
+ default:
+ FAIL();
+ }
+
+ options.create_if_missing = true;
+ DestroyAndReopen(options);
+ }
+}
+
+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<CompressionType> 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.compression = compression_type;
+ options.compression_opts.max_dict_bytes = 4096;
+ 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(new BlockBasedTableFactory(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 = RandomString(&rnd, kNumBytesPerEntry);
+ ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
+ }
+ ASSERT_OK(Flush());
+ }
+ 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 */);
+ }
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_bloom_filter_test.cc b/storage/rocksdb/rocksdb/db/db_bloom_filter_test.cc
new file mode 100644
index 00000000..dcad0032
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_bloom_filter_test.cc
@@ -0,0 +1,1910 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/perf_context.h"
+#include "table/block_based/filter_policy_internal.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+using BFP = BloomFilterPolicy;
+} // namespace
+
+// DB tests related to bloom filter.
+
+class DBBloomFilterTest : public DBTestBase {
+ public:
+ DBBloomFilterTest() : DBTestBase("/db_bloom_filter_test") {}
+};
+
+class DBBloomFilterTestWithParam : public DBTestBase,
+ public testing::WithParamInterface<
+ std::tuple<BFP::Mode, bool, uint32_t>> {
+ // public testing::WithParamInterface<bool> {
+ protected:
+ BFP::Mode bfp_impl_;
+ bool partition_filters_;
+ uint32_t format_version_;
+
+ public:
+ DBBloomFilterTestWithParam() : DBTestBase("/db_bloom_filter_tests") {}
+
+ ~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.reset(new BFP(20, bfp_impl_));
+ options_override.partition_filters = partition_filters_;
+ options_override.metadata_block_size = 32;
+ Options options = CurrentOptions(options_override);
+ if (partition_filters_ &&
+ static_cast<BlockBasedTableOptions*>(
+ options.table_factory->GetOptions())
+ ->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));
+ 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<SliceTransformLimitedDomainGeneric>();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ get_perf_context()->EnablePerLevelPerfContext();
+ BlockBasedTableOptions bbto;
+ bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ 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"));
+
+ 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;
+ ASSERT_TRUE(db_->Get(ro, "foobarbar", &value).IsNotFound());
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
+ ASSERT_EQ(
+ 2,
+ (*(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, false));
+ 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"));
+
+ 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;
+ ASSERT_TRUE(db_->Get(ro, "foobarbar", &value).IsNotFound());
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
+ ASSERT_EQ(
+ 2,
+ (*(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, false));
+ 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", ""));
+ 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", ""));
+ 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", ""));
+ 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", ""));
+ 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.reset(new BFP(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)));
+ }
+ 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);
+ }
+
+ env_->delay_sstable_sync_.store(false, std::memory_order_release);
+ Close();
+ } while (ChangeCompactOptions());
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+INSTANTIATE_TEST_CASE_P(
+ FormatDef, DBBloomFilterTestDefFormatVersion,
+ ::testing::Values(
+ std::make_tuple(BFP::kDeprecatedBlock, false,
+ test::kDefaultFormatVersion),
+ std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
+ std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
+
+INSTANTIATE_TEST_CASE_P(
+ FormatDef, DBBloomFilterTestWithParam,
+ ::testing::Values(
+ std::make_tuple(BFP::kDeprecatedBlock, false,
+ test::kDefaultFormatVersion),
+ std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
+ std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
+
+INSTANTIATE_TEST_CASE_P(
+ FormatLatest, DBBloomFilterTestWithParam,
+ ::testing::Values(
+ std::make_tuple(BFP::kDeprecatedBlock, false,
+ test::kLatestFormatVersion),
+ std::make_tuple(BFP::kAuto, true, test::kLatestFormatVersion),
+ std::make_tuple(BFP::kAuto, false, test::kLatestFormatVersion)));
+#endif // ROCKSDB_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();
+ }
+}
+
+TEST_F(DBBloomFilterTest, BloomFilterCompatibility) {
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions table_options;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ // Create with block based filter
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ const int maxKey = 10000;
+ for (int i = 0; i < maxKey; i++) {
+ ASSERT_OK(Put(1, Key(i), Key(i)));
+ }
+ ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
+ Flush(1);
+
+ // Check db with full filter
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ // 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 db with partitioned full filter
+ table_options.partition_filters = true;
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ // 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);
+}
+
+TEST_F(DBBloomFilterTest, BloomFilterReverseCompatibility) {
+ for (bool partition_filters : {true, false}) {
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions table_options;
+ if (partition_filters) {
+ table_options.partition_filters = true;
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ }
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ DestroyAndReopen(options);
+
+ // Create with full filter
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ const int maxKey = 10000;
+ for (int i = 0; i < maxKey; i++) {
+ ASSERT_OK(Put(1, Key(i), Key(i)));
+ }
+ ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
+ Flush(1);
+
+ // Check db with block_based filter
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ // 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);
+ }
+}
+
+namespace {
+// A wrapped bloom over block-based FilterPolicy
+class TestingWrappedBlockBasedFilterPolicy : public FilterPolicy {
+ public:
+ explicit TestingWrappedBlockBasedFilterPolicy(int bits_per_key)
+ : filter_(NewBloomFilterPolicy(bits_per_key, true)), counter_(0) {}
+
+ ~TestingWrappedBlockBasedFilterPolicy() override { delete filter_; }
+
+ const char* Name() const override {
+ return "TestingWrappedBlockBasedFilterPolicy";
+ }
+
+ void CreateFilter(const ROCKSDB_NAMESPACE::Slice* keys, int n,
+ std::string* dst) const override {
+ std::unique_ptr<ROCKSDB_NAMESPACE::Slice[]> user_keys(
+ new ROCKSDB_NAMESPACE::Slice[n]);
+ for (int i = 0; i < n; ++i) {
+ user_keys[i] = convertKey(keys[i]);
+ }
+ return filter_->CreateFilter(user_keys.get(), n, dst);
+ }
+
+ bool KeyMayMatch(const ROCKSDB_NAMESPACE::Slice& key,
+ const ROCKSDB_NAMESPACE::Slice& filter) const override {
+ counter_++;
+ return filter_->KeyMayMatch(convertKey(key), filter);
+ }
+
+ uint32_t GetCounter() { return counter_; }
+
+ private:
+ const FilterPolicy* filter_;
+ mutable uint32_t counter_;
+
+ ROCKSDB_NAMESPACE::Slice convertKey(
+ const ROCKSDB_NAMESPACE::Slice& key) const {
+ return key;
+ }
+};
+} // namespace
+
+TEST_F(DBBloomFilterTest, WrappedBlockBasedFilterPolicy) {
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ BlockBasedTableOptions table_options;
+ TestingWrappedBlockBasedFilterPolicy* policy =
+ new TestingWrappedBlockBasedFilterPolicy(10);
+ table_options.filter_policy.reset(policy);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ 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)));
+ ASSERT_EQ(0U, policy->GetCounter());
+ 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);
+ ASSERT_EQ(1U * maxKey, policy->GetCounter());
+
+ // 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_EQ(2U * maxKey, policy->GetCounter());
+}
+
+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)) {}
+
+ // 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* Name() const override { return policy_fifo_->Name(); }
+
+ 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);
+ }
+
+ // Defer just in case configuration uses block-based filter
+ void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
+ policy_otherwise_->CreateFilter(keys, n, dst);
+ }
+ bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
+ return policy_otherwise_->KeyMayMatch(key, filter);
+ }
+
+ private:
+ const std::unique_ptr<const FilterPolicy> policy_fifo_;
+ const std::unique_ptr<const FilterPolicy> policy_l0_other_;
+ const std::unique_ptr<const FilterPolicy> policy_otherwise_;
+};
+
+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_ += ",cs=";
+ test_report_ +=
+ OptionsHelper::compaction_style_to_string[context.compaction_style];
+ test_report_ += ",lv=";
+ test_report_ += std::to_string(context.level_at_creation);
+ 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_;
+};
+} // namespace
+
+TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
+ for (bool fifo : {true, false}) {
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.compaction_style =
+ fifo ? kCompactionStyleFIFO : kCompactionStyleLevel;
+
+ BlockBasedTableOptions table_options;
+ auto policy = std::make_shared<TestingContextCustomFilterPolicy>(15, 8, 5);
+ table_options.filter_policy = policy;
+ table_options.format_version = 5;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_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,cs=kCompactionStyleFIFO,lv=0\n"
+ : "cf=bob,cs=kCompactionStyleLevel,lv=0\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,cs=kCompactionStyleFIFO,lv=0\n"
+ : "cf=bob,cs=kCompactionStyleLevel,lv=0\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 only has L0
+ // Full compaction
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr));
+ EXPECT_EQ(policy->DumpTestReport(),
+ "cf=bob,cs=kCompactionStyleLevel,lv=1\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);
+ }
+ }
+
+ // Destroy
+ ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
+ 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, PrefixExtractorFullFilter) {
+ BlockBasedTableOptions bbto;
+ // Full Filter Block
+ bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false));
+ bbto.whole_key_filtering = false;
+
+ Options options = CurrentOptions();
+ options.prefix_extractor = std::make_shared<SliceTransformLimitedDomain>();
+ 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, PrefixExtractorBlockFilter) {
+ BlockBasedTableOptions bbto;
+ // Block Filter Block
+ bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, true));
+
+ Options options = CurrentOptions();
+ options.prefix_extractor = std::make_shared<SliceTransformLimitedDomain>();
+ 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<std::string> 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<std::string> 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<double>(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, 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<Iterator> 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());
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+namespace BFP2 {
+// Extends BFP::Mode with option to use Plain table
+using PseudoMode = int;
+static constexpr PseudoMode kPlainTable = -1;
+} // namespace BFP2
+} // namespace
+
+class BloomStatsTestWithParam
+ : public DBBloomFilterTest,
+ public testing::WithParamInterface<std::tuple<BFP2::PseudoMode, bool>> {
+ 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<double>(options_.write_buffer_size);
+ if (bfp_impl_ == BFP2::kPlainTable) {
+ assert(!partition_filters_); // not supported in plain table
+ PlainTableOptions table_options;
+ options_.table_factory.reset(NewPlainTableFactory(table_options));
+ } else {
+ BlockBasedTableOptions table_options;
+ table_options.hash_index_allow_collision = false;
+ if (partition_filters_) {
+ assert(bfp_impl_ != BFP::kDeprecatedBlock);
+ table_options.partition_filters = partition_filters_;
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ }
+ table_options.filter_policy.reset(
+ new BFP(10, static_cast<BFP::Mode>(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() {}
+
+ BFP2::PseudoMode 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<Iterator> 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());
+ // The seek doesn't check block-based bloom filter because last index key
+ // starts with the same prefix we're seeking to.
+ uint64_t expected_hits = bfp_impl_ == BFP::kDeprecatedBlock ? 1 : 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(BFP::kDeprecatedBlock, false),
+ std::make_tuple(BFP::kLegacyBloom, false),
+ std::make_tuple(BFP::kLegacyBloom, true),
+ std::make_tuple(BFP::kFastLocalBloom, false),
+ std::make_tuple(BFP::kFastLocalBloom, true),
+ std::make_tuple(BFP2::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));
+ dbtest->Flush();
+ 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();
+ }
+}
+} // 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, true));
+ 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<int> keys;
+ keys.reserve(numkeys);
+ for (int i = 0; i < numkeys; i += 2) {
+ keys.push_back(i);
+ }
+ std::random_shuffle(std::begin(keys), std::end(keys));
+
+ int num_inserted = 0;
+ for (int key : keys) {
+ ASSERT_OK(Put(1, Key(key), "val"));
+ if (++num_inserted % 1000 == 0) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+ }
+ ASSERT_OK(Put(1, Key(0), "val"));
+ ASSERT_OK(Put(1, Key(numkeys), "val"));
+ ASSERT_OK(Flush(1));
+ 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));
+ 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;
+ 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<Iterator> 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<Iterator>& iter, const Slice& key) {
+ int count = 0;
+ for (iter->Seek(key); iter->Valid() && iter->status() == Status::OK();
+ iter->Next()) {
+ count++;
+ }
+ 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 (auto bfp_impl : BFP::kAllFixedImpls) {
+ int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
+ Options options;
+ options.create_if_missing = true;
+ 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.reset(new BFP(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"));
+ 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<Iterator> 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<Iterator> 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(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
+ "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<Iterator> 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),
+ 2 + using_full_builder);
+ 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<Iterator> 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),
+ 2 + using_full_builder);
+ 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<Iterator> 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),
+ 2 + using_full_builder * 2);
+ 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<Iterator> 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),
+ 2 + using_full_builder * 2);
+ 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<Iterator> iter(db_->NewIterator(read_options));
+ ASSERT_EQ(CountIter(iter, "abc"), 4);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 2 + using_full_builder * 2);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
+ }
+ ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:4"}}));
+ {
+ // set back to capped:4 and verify BF is always read
+ Slice upper_bound("abd");
+ ReadOptions read_options;
+ read_options.prefix_same_as_start = true;
+ read_options.iterate_upper_bound = &upper_bound;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
+ ASSERT_EQ(CountIter(iter, "abc"), 0);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 3 + using_full_builder * 2);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
+ }
+ }
+}
+
+// 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 (auto bfp_impl : BFP::kAllFixedImpls) {
+ int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
+ Options options;
+ 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.reset(new BFP(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<Iterator> 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(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
+ "rocksdb.CappedPrefix.3"));
+ read_options.iterate_upper_bound = &upper_bound;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
+ ASSERT_EQ(CountIter(iter, "foo"), 2);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 1 + using_full_builder);
+ ASSERT_EQ(CountIter(iter, "gpk"), 0);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 1 + using_full_builder);
+ 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"));
+ dbfull()->Flush(FlushOptions());
+ {
+ // BF is cappped:3 now
+ std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
+ ASSERT_EQ(CountIter(iter_tmp, "foo"), 4);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 2 + using_full_builder * 2);
+ 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),
+ 3 + using_full_builder * 2);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
+ }
+
+ ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
+ ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
+ "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"));
+ dbfull()->Flush(FlushOptions());
+ {
+ // BF is fixed:2 now
+ std::unique_ptr<Iterator> 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),
+ 4 + using_full_builder * 3);
+ 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),
+ 5 + using_full_builder * 3);
+ 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),
+ 6 + using_full_builder * 3);
+ // 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),
+ 6 + using_full_builder * 4);
+
+ {
+ // 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<Iterator> iter_all(db_->NewIterator(read_options));
+ ASSERT_EQ(CountIter(iter_all, "foo"), 9);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 7 + using_full_builder * 5);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
+ ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 8 + using_full_builder * 5);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
+ }
+ ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
+ ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
+ "rocksdb.CappedPrefix.3"));
+ {
+ std::unique_ptr<Iterator> 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),
+ 9 + using_full_builder * 7);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
+ ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
+ ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
+ 10 + using_full_builder * 7);
+ 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 (auto bfp_impl : BFP::kAllFixedImpls) {
+ 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.reset(new BFP(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(0,
+ strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
+ "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<Iterator> 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(0,
+ strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
+ "rocksdb.FixedPrefix.2"));
+ {
+ std::unique_ptr<Iterator> 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]));
+ dbfull()->DestroyColumnFamilyHandle(handles_[2]);
+ handles_[2] = nullptr;
+ ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
+ 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 (auto bfp_impl : BFP::kAllFixedImpls) {
+ Options options;
+ 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.reset(new BFP(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<Iterator> 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<Iterator> 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(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
+ "rocksdb.CappedPrefix.3"));
+ {
+ std::unique_ptr<Iterator> 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);
+ }
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_compaction_filter_test.cc b/storage/rocksdb/rocksdb/db/db_compaction_filter_test.cc
new file mode 100644
index 00000000..a708c0b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_compaction_filter_test.cc
@@ -0,0 +1,872 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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") {}
+};
+
+// Param variant of DBTestBase::ChangeCompactOptions
+class DBTestCompactionFilterWithCompactParam
+ : public DBTestCompactionFilter,
+ public ::testing::WithParamInterface<DBTestBase::OptionConfig> {
+ 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);
+ }
+ TryReopen(options);
+ }
+};
+
+#ifndef ROCKSDB_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 valgrind
+INSTANTIATE_TEST_CASE_P(CompactionFilterWithOption,
+ DBTestCompactionFilterWithCompactParam,
+ ::testing::Values(DBTestBase::OptionConfig::kDefault));
+#endif // ROCKSDB_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;
+ }
+
+ 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 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_->addon_time_.fetch_add(1000);
+ return true;
+ }
+
+ const char* Name() const override { return "DelayFilter"; }
+
+ private:
+ DBTestBase* db_test;
+};
+
+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<CompactionFilter> 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<CompactionFilter>(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<uint32_t> expect_cf_id_;
+ bool compaction_filter_created_;
+};
+
+class DeleteFilterFactory : public CompactionFilterFactory {
+ public:
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ if (context.is_manual_compaction) {
+ return std::unique_ptr<CompactionFilter>(new DeleteFilter());
+ } else {
+ return std::unique_ptr<CompactionFilter>(nullptr);
+ }
+ }
+
+ const char* Name() const override { return "DeleteFilterFactory"; }
+};
+
+// Delete Filter Factory which ignores snapshots
+class DeleteISFilterFactory : public CompactionFilterFactory {
+ public:
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ if (context.is_manual_compaction) {
+ return std::unique_ptr<CompactionFilter>(new DeleteISFilter());
+ } else {
+ return std::unique_ptr<CompactionFilter>(nullptr);
+ }
+ }
+
+ const char* Name() const override { return "DeleteFilterFactory"; }
+};
+
+class SkipEvenFilterFactory : public CompactionFilterFactory {
+ public:
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ if (context.is_manual_compaction) {
+ return std::unique_ptr<CompactionFilter>(new SkipEvenFilter());
+ } else {
+ return std::unique_ptr<CompactionFilter>(nullptr);
+ }
+ }
+
+ const char* Name() const override { return "SkipEvenFilterFactory"; }
+};
+
+class DelayFilterFactory : public CompactionFilterFactory {
+ public:
+ explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
+ }
+
+ const char* Name() const override { return "DelayFilterFactory"; }
+
+ private:
+ DBTestBase* db_test;
+};
+
+class ConditionalFilterFactory : public CompactionFilterFactory {
+ public:
+ explicit ConditionalFilterFactory(const Slice& filtered_value)
+ : filtered_value_(filtered_value.ToString()) {}
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(
+ 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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new ChangeFilter());
+ }
+
+ const char* Name() const override { return "ChangeFilterFactory"; }
+};
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTestCompactionFilter, CompactionFilter) {
+ Options options = CurrentOptions();
+ options.max_open_files = -1;
+ options.num_levels = 3;
+ options.compaction_filter_factory = std::make_shared<KeepFilterFactory>();
+ 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);
+ 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;
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ ASSERT_EQ(cfilter_count, 100000);
+ cfilter_count = 0;
+ 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);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ ScopedArenaIterator iter(dbfull()->NewInternalIterator(
+ &arena, &range_del_agg, kMaxSequenceNumber, handles_[1]));
+ iter->SeekToFirst();
+ ASSERT_OK(iter->status());
+ while (iter->Valid()) {
+ ParsedInternalKey ikey(Slice(), 0, kTypeValue);
+ ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
+ total++;
+ if (ikey.sequence != 0) {
+ count++;
+ }
+ iter->Next();
+ }
+ }
+ 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;
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ ASSERT_EQ(cfilter_count, 100000);
+ cfilter_count = 0;
+ 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<DeleteFilterFactory>();
+ 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;
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ ASSERT_EQ(cfilter_count, 100000);
+ cfilter_count = 0;
+ 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<Iterator> iter(
+ db_->NewIterator(ReadOptions(), handles_[1]));
+ iter->SeekToFirst();
+ count = 0;
+ while (iter->Valid()) {
+ count++;
+ iter->Next();
+ }
+ 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);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ ScopedArenaIterator iter(dbfull()->NewInternalIterator(
+ &arena, &range_del_agg, kMaxSequenceNumber, handles_[1]));
+ iter->SeekToFirst();
+ ASSERT_OK(iter->status());
+ while (iter->Valid()) {
+ ParsedInternalKey ikey(Slice(), 0, kTypeValue);
+ ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
+ 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<DeleteFilterFactory>();
+ 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) {
+ Put(ToString(table * 100 + i), "val");
+ }
+ 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();
+ // empty db
+ ASSERT_TRUE(!itr->Valid());
+
+ delete itr;
+}
+#endif // ROCKSDB_LITE
+
+TEST_P(DBTestCompactionFilterWithCompactParam,
+ CompactionFilterWithValueChange) {
+ Options options = CurrentOptions();
+ options.num_levels = 3;
+ options.compaction_filter_factory = std::make_shared<ChangeFilterFactory>();
+ 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);
+ Put(1, key, value);
+ }
+
+ // push all files to lower levels
+ ASSERT_OK(Flush(1));
+ if (option_config_ != kUniversalCompactionMultiLevel &&
+ option_config_ != kUniversalSubcompactions) {
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
+ } else {
+ 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);
+ 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) {
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
+ } else {
+ 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<ConditionalFilterFactory>(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);
+ 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());
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ newvalue = Get("bar");
+ ASSERT_EQ("NOT_FOUND", newvalue);
+ ASSERT_OK(db_->Merge(WriteOptions(), "bar", two));
+ ASSERT_OK(Flush());
+ 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);
+ 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);
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ newvalue = Get("barfoo");
+ ASSERT_EQ(newvalue, four);
+}
+
+#ifndef ROCKSDB_LITE
+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);
+ Put(key, value);
+ }
+ dbfull()->TEST_FlushMemTable();
+ // Make sure next file is much smaller so automatic compaction will not
+ // be triggered.
+ num_keys_per_file /= 2;
+ }
+ 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);
+ 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);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* snapshots */);
+ ScopedArenaIterator iter(dbfull()->NewInternalIterator(
+ &arena, &range_del_agg, kMaxSequenceNumber));
+ iter->SeekToFirst();
+ ASSERT_OK(iter->status());
+ while (iter->Valid()) {
+ ParsedInternalKey ikey(Slice(), 0, kTypeValue);
+ ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
+ total++;
+ if (ikey.sequence != 0) {
+ count++;
+ }
+ iter->Next();
+ }
+ ASSERT_EQ(total, 700);
+ ASSERT_EQ(count, 0);
+ }
+}
+#endif // ROCKSDB_LITE
+
+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);
+ Put(1, key, value);
+ }
+ Flush(1);
+ // Make sure next file is much smaller so automatic compaction will not
+ // be triggered.
+ num_keys_per_file /= 2;
+ }
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_TRUE(filter->compaction_filter_created());
+}
+
+#ifndef ROCKSDB_LITE
+// Compaction filters aplies to all records, regardless snapshots.
+TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
+ std::string five = ToString(5);
+ Options options = CurrentOptions();
+ options.compaction_filter_factory = std::make_shared<DeleteISFilterFactory>();
+ 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) {
+ Put(ToString(table * 100 + i), "val");
+ }
+ 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<Iterator> iter(db_->NewIterator(read_options));
+ iter->SeekToFirst();
+ int count = 0;
+ while (iter->Valid()) {
+ count++;
+ iter->Next();
+ }
+ ASSERT_EQ(count, 6);
+ read_options.snapshot = nullptr;
+ std::unique_ptr<Iterator> iter1(db_->NewIterator(read_options));
+ 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);
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTestCompactionFilter, SkipUntil) {
+ Options options = CurrentOptions();
+ options.compaction_filter_factory = std::make_shared<SkipEvenFilterFactory>();
+ 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);
+ Put(key, std::to_string(table * 1000 + i));
+ }
+ 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<SkipEvenFilterFactory>();
+ options.disable_auto_compactions = true;
+ options.create_if_missing = true;
+ DestroyAndReopen(options);
+
+ Put("0000000010", "v10");
+ Put("0000000020", "v20"); // skipped
+ Put("0000000050", "v50");
+ 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);
+
+ Put("a", "v10");
+ Put("z", "v20");
+ Flush();
+
+ Put("a", "v10");
+ Put("z", "v20");
+ Flush();
+
+ // Comapction should fail because IgnoreSnapshots() = false
+ EXPECT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)
+ .IsNotSupported());
+
+ delete options.compaction_filter;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_compaction_test.cc b/storage/rocksdb/rocksdb/db/db_compaction_test.cc
new file mode 100644
index 00000000..635aca13
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_compaction_test.cc
@@ -0,0 +1,5167 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "util/concurrent_task_limiter_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// SYNC_POINT is not supported in released Windows mode.
+#if !defined(ROCKSDB_LITE)
+
+class DBCompactionTest : public DBTestBase {
+ public:
+ DBCompactionTest() : DBTestBase("/db_compaction_test") {}
+};
+
+class DBCompactionTestWithParam
+ : public DBTestBase,
+ public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
+ public:
+ DBCompactionTestWithParam() : DBTestBase("/db_compaction_test") {
+ 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 DBCompactionDirectIOTest : public DBCompactionTest,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ DBCompactionDirectIOTest() : DBCompactionTest() {}
+};
+
+namespace {
+
+class FlushedFileCollector : public EventListener {
+ public:
+ FlushedFileCollector() {}
+ ~FlushedFileCollector() override {}
+
+ void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
+ std::lock_guard<std::mutex> lock(mutex_);
+ flushed_files_.push_back(info.file_path);
+ }
+
+ std::vector<std::string> GetFlushedFiles() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ std::vector<std::string> result;
+ for (auto fname : flushed_files_) {
+ result.push_back(fname);
+ }
+ return result;
+ }
+
+ void ClearFlushedFiles() { flushed_files_.clear(); }
+
+ private:
+ std::vector<std::string> flushed_files_;
+ std::mutex mutex_;
+};
+
+class CompactionStatsCollector : public EventListener {
+public:
+ CompactionStatsCollector()
+ : compaction_completed_(static_cast<int>(CompactionReason::kNumOfReasons)) {
+ for (auto& v : compaction_completed_) {
+ v.store(0);
+ }
+ }
+
+ ~CompactionStatsCollector() override {}
+
+ void OnCompactionCompleted(DB* /* db */,
+ const CompactionJobInfo& info) override {
+ int k = static_cast<int>(info.compaction_reason);
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ assert(k >= 0 && k < num_of_reasons);
+ compaction_completed_[k]++;
+ }
+
+ void OnExternalFileIngested(
+ DB* /* db */, const ExternalFileIngestionInfo& /* info */) override {
+ int k = static_cast<int>(CompactionReason::kExternalSstIngestion);
+ compaction_completed_[k]++;
+ }
+
+ void OnFlushCompleted(DB* /* db */, const FlushJobInfo& /* info */) override {
+ int k = static_cast<int>(CompactionReason::kFlush);
+ compaction_completed_[k]++;
+ }
+
+ int NumberOfCompactions(CompactionReason reason) const {
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ int k = static_cast<int>(reason);
+ assert(k >= 0 && k < num_of_reasons);
+ return compaction_completed_.at(k).load();
+ }
+
+private:
+ std::vector<std::atomic<int>> compaction_completed_;
+};
+
+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<int> 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;
+ return options;
+}
+
+bool HaveOverlappingKeyRanges(
+ const Comparator* c,
+ const SstFileMetaData& a, const SstFileMetaData& b) {
+ if (c->Compare(a.smallestkey, b.smallestkey) >= 0) {
+ if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
+ // b.smallestkey <= a.smallestkey <= b.largestkey
+ return true;
+ }
+ } else if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
+ // a.smallestkey < b.smallestkey <= a.largestkey
+ return true;
+ }
+ if (c->Compare(a.largestkey, b.largestkey) <= 0) {
+ if (c->Compare(a.largestkey, b.smallestkey) >= 0) {
+ // b.smallestkey <= a.largestkey <= b.largestkey
+ return true;
+ }
+ } else if (c->Compare(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<std::string>* overlapping_file_names) {
+ std::set<const SstFileMetaData*> 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<std::string>& 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
+}
+
+/*
+ * 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_TRUE(internal_stats_ptr != nullptr);
+ const std::vector<InternalStats::CompactionStats>& comp_stats =
+ internal_stats_ptr->TEST_GetCompactionStats();
+ const int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ std::vector<int> 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<CompactionReason>(i)), counts[i]);
+ }
+#endif /* NDEBUG */
+}
+
+const SstFileMetaData* PickFileRandomly(
+ const ColumnFamilyMetaData& cf_meta,
+ Random* rand,
+ int* level = nullptr) {
+ auto file_id = rand->Uniform(static_cast<int>(
+ 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<uint32_t>(level_meta.files.size());
+ }
+ assert(false);
+ return nullptr;
+}
+} // anonymous namespace
+
+#ifndef ROCKSDB_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<std::string> values;
+ for (int k = 0; k < kTestSize; ++k) {
+ values.push_back(RandomString(&rnd, kCDTValueSize));
+ ASSERT_OK(Put(Key(k), values[k]));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[0] = Size(Key(0), Key(kTestSize - 1));
+
+ for (int k = 0; k < kTestSize; ++k) {
+ ASSERT_OK(Delete(Key(k)));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[1] = Size(Key(0), Key(kTestSize - 1));
+
+ // must have much smaller db size.
+ ASSERT_GT(db_size[0] / 3, db_size[1]);
+ }
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) {
+ // For each options type we test following
+ // - Enable preserve_deletes
+ // - write bunch of keys and deletes
+ // - Set start_seqnum to the beginning; compact; check that keys are present
+ // - rewind start_seqnum way forward; compact; check that keys are gone
+
+ for (int tid = 0; tid < 3; ++tid) {
+ Options options = DeletionTriggerOptions(CurrentOptions());
+ options.max_subcompactions = max_subcompactions_;
+ options.preserve_deletes=true;
+ options.num_levels = 2;
+
+ if (tid == 1) {
+ options.skip_stats_update_on_db_open = true;
+ } else if (tid == 2) {
+ // third pass with universal compaction
+ options.compaction_style = kCompactionStyleUniversal;
+ }
+
+ DestroyAndReopen(options);
+ Random rnd(301);
+ // highlight the default; all deletes should be preserved
+ SetPreserveDeletesSequenceNumber(0);
+
+ const int kTestSize = kCDTKeysPerBuffer;
+ std::vector<std::string> values;
+ for (int k = 0; k < kTestSize; ++k) {
+ values.push_back(RandomString(&rnd, kCDTValueSize));
+ ASSERT_OK(Put(Key(k), values[k]));
+ }
+
+ for (int k = 0; k < kTestSize; ++k) {
+ ASSERT_OK(Delete(Key(k)));
+ }
+ // to ensure we tackle all tombstones
+ CompactRangeOptions cro;
+ cro.change_level = true;
+ cro.target_level = 2;
+ cro.bottommost_level_compaction =
+ BottommostLevelCompaction::kForceOptimized;
+
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+
+ // check that normal user iterator doesn't see anything
+ Iterator* db_iter = dbfull()->NewIterator(ReadOptions());
+ int i = 0;
+ for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
+ i++;
+ }
+ ASSERT_EQ(i, 0);
+ delete db_iter;
+
+ // check that iterator that sees internal keys sees tombstones
+ ReadOptions ro;
+ ro.iter_start_seqnum=1;
+ db_iter = dbfull()->NewIterator(ro);
+ i = 0;
+ for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
+ i++;
+ }
+ ASSERT_EQ(i, 4);
+ delete db_iter;
+
+ // now all deletes should be gone
+ SetPreserveDeletesSequenceNumber(100000000);
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+
+ db_iter = dbfull()->NewIterator(ro);
+ i = 0;
+ for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
+ i++;
+ }
+ ASSERT_EQ(i, 0);
+ delete db_iter;
+ }
+}
+
+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<std::string> values;
+ for (int k = 0; k < kTestSize; ++k) {
+ values.push_back(RandomString(&rnd, 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.new_table_reader_for_compaction_inputs = true;
+ options.max_open_files = 20;
+ options.level0_file_num_compaction_trigger = 3;
+ 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<bool*>(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;
+ Flush();
+ 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;
+ Flush();
+ 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;
+ 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<std::string> values;
+ for (int k = 0; k < kTestSize; ++k) {
+ values.push_back(RandomString(&rnd, kCDTValueSize));
+ ASSERT_OK(Put(Key(k), values[k]));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[0] = Size(Key(0), Key(kTestSize - 1));
+ 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)));
+ }
+ db_size[1] = Size(Key(0), Key(kTestSize - 1));
+ Close();
+ // as auto_compaction is off, we shouldn't see too much reduce
+ // in db size.
+ ASSERT_LT(db_size[0] / 3, 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]));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[2] = Size(Key(0), Key(kTestSize - 1));
+ // this time we're expecting significant drop in size.
+ ASSERT_GT(db_size[0] / 3, db_size[2]);
+ }
+}
+
+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<std::string> values;
+ for (int k = 0; k < kTestSize; ++k) {
+ values.push_back(RandomString(&rnd, kCDTValueSize));
+ ASSERT_OK(Put(Key(k), values[k]));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[0] = Size(Key(0), Key(kTestSize - 1));
+ 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)));
+ }
+ db_size[1] = Size(Key(0), Key(kTestSize - 1));
+ Close();
+ // as auto_compaction is off, we shouldn't see too much reduce
+ // in db size.
+ ASSERT_LT(db_size[0] / 3, 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);
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ db_size[2] = Size(Key(0), Key(kTestSize - 1));
+
+ if (options.skip_stats_update_on_db_open) {
+ // If update stats on DB::Open is disable, we don't expect
+ // deletion entries taking effect.
+ ASSERT_LT(db_size[0] / 3, db_size[2]);
+ } else {
+ // Otherwise, we should see a significant drop in db size.
+ ASSERT_GT(db_size[0] / 3, 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(new SpecialSkipListFactory(kNumKeysPerFile));
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ Random rnd(301);
+
+ for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
+ num++) {
+ std::vector<std::string> values;
+ // Write 100KB (100 values, each 1K)
+ for (int i = 0; i < kNumKeysPerFile; i++) {
+ values.push_back(RandomString(&rnd, 990));
+ ASSERT_OK(Put(1, Key(i), values[i]));
+ }
+ // put extra key to trigger flush
+ ASSERT_OK(Put(1, "", ""));
+ 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<std::string> values;
+ for (int i = 0; i < kNumKeysPerFile; i++) {
+ values.push_back(RandomString(&rnd, 990));
+ ASSERT_OK(Put(1, Key(i), values[i]));
+ }
+ // put extra key to trigger flush
+ ASSERT_OK(Put(1, "", ""));
+ 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(new SpecialSkipListFactory(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, "", ""));
+ 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, "", ""));
+ 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();
+ }
+ 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, "", ""));
+ 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<std::string> values;
+ for (int i = 0; i < 80; i++) {
+ values.push_back(RandomString(&rnd, 100000));
+ ASSERT_OK(Put(1, Key(i), values[i]));
+ }
+
+ // Reopening moves updates to level-0
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ 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
+ Put("4", "A");
+ Put("3", "A");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+
+ Put("2", "A");
+ Delete("3");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ("NOT_FOUND", Get("3"));
+
+ // move both files down to l1
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ("NOT_FOUND", Get("3"));
+
+ for (int i = 0; i < 3; i++) {
+ Put("2", "B");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ 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
+ Put("4", "A");
+ Put("3", "A");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+
+ Put("2", "A");
+ SingleDelete("3");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ("NOT_FOUND", Get("3"));
+
+ // move both files down to l1
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ("NOT_FOUND", Get("3"));
+
+ for (int i = 0; i < 3; i++) {
+ Put("2", "B");
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_EQ("NOT_FOUND", Get("3"));
+}
+
+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<size_t>(compact_opt.output_file_size_limit) / 5;
+
+ DestroyAndReopen(options);
+
+ std::vector<const Snapshot*> snaps;
+
+ // create first file and flush to l0
+ for (auto& key : {"1", "2", "3", "3", "3", "3"}) {
+ Put(key, std::string(key_len, 'A'));
+ snaps.push_back(dbfull()->GetSnapshot());
+ }
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+
+ // create second file and flush to l0
+ for (auto& key : {"3", "4", "5", "6", "7", "8"}) {
+ Put(key, std::string(key_len, 'A'));
+ snaps.push_back(dbfull()->GetSnapshot());
+ }
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+
+ // move both files down to l1
+ 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++) {
+ Put("2", std::string(1, 'A'));
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+
+ 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++) {
+ // make l0 files' ranges overlap to avoid trivial move
+ Put(std::to_string(2 * i), std::string(1, 'A'));
+ Put(std::to_string(2 * i + 1), std::string(1, 'A'));
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0);
+ ASSERT_EQ(NumTableFilesAtLevel(1, 0), i + 1);
+ }
+
+ ColumnFamilyMetaData cf_meta;
+ dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta);
+ ASSERT_EQ(2, cf_meta.levels[1].files.size());
+ std::vector<std::string> 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;
+ 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<std::string> values;
+ for (int i = 0; i < num_keys; i++) {
+ values.push_back(RandomString(&rnd, 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<LiveFileMetaData> 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
+ 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<std::pair<int32_t, int32_t>> 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<int32_t, std::string> values;
+ for (size_t i = 0; i < ranges.size(); i++) {
+ for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
+ values[j] = RandomString(&rnd, 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
+ 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] = RandomString(&rnd, value_size);
+ ASSERT_OK(Put(Key(j), values[j]));
+ }
+ ASSERT_OK(Flush());
+ }
+
+ 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<int32_t, std::string> values;
+
+ // file 1 [0 => 300]
+ for (int32_t i = 0; i <= 300; i++) {
+ values[i] = RandomString(&rnd, 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] = RandomString(&rnd, 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, 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<int32_t, std::string> values;
+
+ // file 1 [0 => 100]
+ for (int32_t i = 0; i < 100; i++) {
+ values[i] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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");
+
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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<int32_t, std::string> values;
+
+ // file 1 [0 => 100]
+ for (int32_t i = 0; i < 100; i++) {
+ values[i] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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());
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ values[j] = RandomString(&rnd, 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<uint64_t>(target_size *
+ options.max_bytes_for_level_multiplier);
+ }
+
+ TEST_SYNC_POINT("DBCompaction::PartialFill:2");
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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);
+ Put("foo", "v1");
+ ASSERT_OK(Flush());
+
+ Put("bar", "v1");
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ port::Thread writer([&]() { 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<int32_t, std::string> values;
+
+ // file 1 [0 => 100]
+ for (int32_t i = 0; i < 100; i++) {
+ values[i] = RandomString(&rnd, 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] = RandomString(&rnd, 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] = RandomString(&rnd, 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());
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ values[j] = RandomString(&rnd, value_size);
+ ASSERT_OK(Put(Key(j), values[j]));
+ }
+ }
+ ASSERT_OK(Flush());
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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<uint64_t>(target_size *
+ options.max_bytes_for_level_multiplier);
+ }
+
+ 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));
+ 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;
+ Status s = db_->Get(roptions, Key(i), &result);
+ ASSERT_TRUE(s.IsNotFound());
+ deleted_count2++;
+ }
+ ASSERT_GT(deleted_count2, deleted_count);
+ 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<int32_t, std::string> 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] = RandomString(&rnd, 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<RangePtr> 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<RangePtr> 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] = RandomString(&rnd, kValSize);
+ Put(Key(i), vals[i]);
+ Put(Key(i + 1), vals[i]);
+ Flush();
+ if (i == 0) {
+ snapshot = db_->GetSnapshot();
+ }
+ }
+ 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<std::string> values;
+ // File with keys [ 0 => 99 ]
+ for (int i = 0; i < 100; i++) {
+ values.push_back(RandomString(&rnd, 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(RandomString(&rnd, 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(
+ new SpecialSkipListFactory(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_;
+ // options = CurrentOptions(options);
+
+ std::vector<std::string> filenames;
+ env_->GetChildren(options.db_paths[1].path, &filenames);
+ // Delete archival files.
+ for (size_t i = 0; i < filenames.size(); ++i) {
+ env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
+ }
+ 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 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(
+ new SpecialSkipListFactory(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_;
+ // options = CurrentOptions(options);
+
+ std::vector<std::string> filenames;
+ env_->GetChildren(options.db_paths[1].path, &filenames);
+ // Delete archival files.
+ for (size_t i = 0; i < filenames.size(); ++i) {
+ env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
+ }
+ env_->DeleteDir(options.db_paths[1].path);
+ Reopen(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(
+ new SpecialSkipListFactory(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<Options> 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]);
+
+ // Configura 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.
+
+ // 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 fill up first path
+ 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();
+
+ 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), RandomString(&rnd, 10000)));
+ }
+ ASSERT_OK(Flush(1));
+ 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_;
+ 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), RandomString(&rnd, 10000)));
+ }
+ dbfull()->Flush(FlushOptions());
+ ASSERT_OK(Flush(1));
+ 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]);
+ 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());
+ Put(1, "", "");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Delete(1, "e");
+ Put(1, "", "");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Put(1, "c", "cv");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Put(1, "", "");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Put(1, "", "");
+ env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Put(1, "d", "dv");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Put(1, "", "");
+ ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
+ Delete(1, "d");
+ 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();
+
+ Put(1, "foo", "");
+ Put(1, "bar", "");
+ Flush(1);
+ Put(1, "foo", "");
+ Put(1, "bar", "");
+ // Generate four files in CF 0, which should trigger an auto compaction
+ Put("foo", "");
+ Put("bar", "");
+ Flush();
+ Put("foo", "");
+ Put("bar", "");
+ Flush();
+ Put("foo", "");
+ Put("bar", "");
+ Flush();
+ Put("foo", "");
+ Put("bar", "");
+ 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.
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
+ ASSERT_EQ("0,1", FilesPerLevel(1));
+
+ // Eventually the cancelled compaction will be rescheduled and executed.
+ 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, "p1", "p9");
+ 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_;
+ 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, "p1", "p9", 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_;
+ 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, ToString(key), RandomString(&rnd, kTestValueSize)));
+ }
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForCompact();
+
+ ColumnFamilyMetaData cf_meta;
+ dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
+ int output_level = static_cast<int>(cf_meta.levels.size()) - 1;
+ for (int file_picked = 5; file_picked > 0; --file_picked) {
+ std::set<std::string> overlapping_file_names;
+ std::vector<std::string> 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, ToString(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<std::string> keys;
+ std::vector<std::string> values;
+ for (int k = 0; k < kNumInsertedKeys; ++k) {
+ keys.emplace_back(RandomString(&rnd, kKeySize));
+ values.emplace_back(RandomString(&rnd, kKvSize - kKeySize));
+ ASSERT_OK(Put(Slice(keys[k]), Slice(values[k])));
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+
+ 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), RandomString(&rnd, 10 * 1024)));
+ }
+ ASSERT_OK(Flush());
+ }
+ // this should execute L0->L1
+ 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> 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)
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_EQ("0,0,1", FilesPerLevel(0));
+
+ std::vector<LiveFileMetaData> 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), RandomString(&rnd, 10 * 1024)));
+ }
+ ASSERT_OK(Flush());
+ }
+ // this should execute both L0->L1 and L1->L2 (merge with previous file)
+ 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);
+ 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(
+ new SpecialSkipListFactory(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);
+ }
+ 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);
+ 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");
+ 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<std::string> values;
+ // File with keys [ 0 => 99 ]
+ for (int i = 0; i < 100; i++) {
+ values.push_back(RandomString(&rnd, 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(RandomString(&rnd, 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(RandomString(&rnd, 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_;
+ DestroyAndReopen(options);
+
+ const size_t kValueSize = 1 << 20;
+ Random rnd(301);
+ std::string value(RandomString(&rnd, kValueSize));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"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) {
+ ASSERT_OK(Put(Key(i + 1), value + value));
+ } else {
+ ASSERT_OK(Put(Key(i + 1), value));
+ }
+ ASSERT_OK(Flush());
+ }
+ dbfull()->TEST_WaitForCompact();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ std::vector<std::vector<FileMetaData>> 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);
+ }
+}
+
+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(RandomString(&rnd, kValueSize));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"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)));
+ }
+ ASSERT_OK(Put(Key(i + 1), value));
+ ASSERT_OK(Flush());
+ }
+ dbfull()->TEST_WaitForCompact();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ std::vector<std::vector<FileMetaData>> 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);
+ }
+ 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, 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) {
+ Put(Key(2 * i + 1), "val");
+ 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) {
+ Put(Key(0), "val"); // sentinel to prevent trivial move
+ Delete(Key(i + 1));
+ Flush();
+ }
+ 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<std::string> 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.
+ Put(Key(0), "old_val");
+ Flush();
+ Put(Key(0), "new_val");
+ 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<std::string> 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, 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), RandomString(&rnd, 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)));
+ }
+ }
+ Flush();
+ if (i < kNumLevelFiles - 1) {
+ ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
+ }
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
+
+ std::vector<LiveFileMetaData> 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<Compaction*>(arg);
+ ASSERT_TRUE(compaction->compaction_reason() ==
+ CompactionReason::kBottommostFiles);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ 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, 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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ 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), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ }
+ 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)));
+ }
+ Flush();
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ("2,0,0,2", FilesPerLevel());
+ MoveFilesToLevel(1);
+ ASSERT_EQ("0,2,0,2", FilesPerLevel());
+
+ env_->addon_time_.fetch_add(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"));
+ Flush();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(arg);
+ ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ 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.
+
+ env_->addon_time_.store(0);
+ DestroyAndReopen(options);
+
+ for (int i = 0; i < kNumLevelFiles; ++i) {
+ for (int j = 0; j < kNumKeysPerFile; ++j) {
+ ASSERT_OK(
+ Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ }
+ 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)));
+ }
+ Flush();
+ }
+ 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_->addon_time_.fetch_add(12 * 60 * 60);
+ ASSERT_OK(Put("a", "1"));
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ("1,2,0,2", FilesPerLevel());
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(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"}}));
+ 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, LevelTtlCascadingCompactions) {
+ 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<int*>(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<std::string*>(arg);
+ *encoded_fieled = "";
+ PutVarint64(encoded_fieled, 0);
+ }
+ });
+
+ env_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ DestroyAndReopen(options);
+
+ int ttl_compactions = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(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), RandomString(&rnd, kValueSize)));
+ }
+ 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<std::vector<FileMetaData>> 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_->addon_time_.fetch_add(1 * 60 * 60);
+ for (int i = 101; i <= 200; ++i) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ MoveFilesToLevel(6);
+ ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
+
+ env_->addon_time_.fetch_add(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), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ env_->addon_time_.fetch_add(1 * 60 * 60);
+ for (int i = 51; i <= 150; ++i) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ MoveFilesToLevel(4);
+ ASSERT_EQ("0,0,0,0,2,0,2", FilesPerLevel());
+
+ env_->addon_time_.fetch_add(1 * 60 * 60);
+ // Add one L1 file with key range: [26, 75].
+ for (int i = 26; i <= 75; ++i) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ 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_->addon_time_.fetch_add(25 * 60 * 60);
+
+ ASSERT_OK(Put(Key(1), "1"));
+ if (if_restart) {
+ Reopen(options);
+ } else {
+ Flush();
+ }
+ 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_->addon_time_.fetch_add(25 * 60 * 60);
+ ASSERT_OK(Put(Key(2), "1"));
+ if (if_restart) {
+ Reopen(options);
+ } else {
+ Flush();
+ }
+ 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) {
+ 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<int*>(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<std::string*>(arg);
+ *encoded_fieled = "";
+ PutVarint64(encoded_fieled, 0);
+ }
+ });
+
+ env_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ DestroyAndReopen(options);
+
+ int periodic_compactions = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(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),
+ RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ }
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_EQ("2", FilesPerLevel());
+ ASSERT_EQ(0, periodic_compactions);
+
+ // Add 50 hours and do a write
+ env_->addon_time_.fetch_add(50 * 60 * 60);
+ ASSERT_OK(Put("a", "1"));
+ Flush();
+ 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_->addon_time_.fetch_add(50 * 60 * 60);
+ ASSERT_OK(Put("b", "2"));
+ if (if_restart) {
+ Reopen(options);
+ } else {
+ Flush();
+ }
+ 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_->addon_time_.fetch_add(50 * 60 * 60);
+ ASSERT_OK(Put("c", "3"));
+ Flush();
+ 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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ 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<Compaction*>(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<TableProperties*>(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), RandomString(&rnd, kValueSize)));
+ }
+ 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_->addon_time_.fetch_add(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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ DestroyAndReopen(options);
+
+ int periodic_compactions = 0;
+ int ttl_compactions = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(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), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ }
+ 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_->addon_time_.fetch_add(50 * 60 * 60);
+ ASSERT_OK(Put("a", "1"));
+ Flush();
+ 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_->addon_time_.fetch_add(11 * 60 * 60);
+ ASSERT_OK(Put("b", "1"));
+ Flush();
+ 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_->addon_time_.fetch_add(50 * 60 * 60);
+ ASSERT_OK(Put("c", "1"));
+ Flush();
+ 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, 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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+ env_->addon_time_.store(0);
+
+ enum CompactionFilterType {
+ kUseCompactionFilter,
+ kUseCompactionFilterFactory
+ };
+
+ for (CompactionFilterType comp_filter_type :
+ {kUseCompactionFilter, kUseCompactionFilterFactory}) {
+ // Assert that periodic compactions are not enabled.
+ ASSERT_EQ(port::kMaxUint64 - 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<Compaction*>(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), RandomString(&rnd, kValueSize)));
+ }
+ Flush();
+ }
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_EQ("2", FilesPerLevel());
+ ASSERT_EQ(0, periodic_compactions);
+
+ // Add 31 days and do a write
+ env_->addon_time_.fetch_add(31 * 24 * 60 * 60);
+ ASSERT_OK(Put("a", "1"));
+ Flush();
+ 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), RandomString(&rnd, 1024)));
+ }
+ Flush();
+ }
+ auto manual_compaction_thread = port::Thread([this]() {
+ CompactRangeOptions cro;
+ cro.allow_write_stall = false;
+ db_->CompactRange(cro, nullptr, nullptr);
+ });
+
+ manual_compaction_thread.join();
+ 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), RandomString(&rnd, 1024)));
+ FlushOptions flush_opts;
+ flush_opts.wait = false;
+ flush_opts.allow_write_stall = true;
+ dbfull()->Flush(flush_opts);
+ }
+
+ auto manual_compaction_thread = port::Thread([this]() {
+ CompactRangeOptions cro;
+ cro.allow_write_stall = false;
+ db_->CompactRange(cro, nullptr, nullptr);
+ });
+
+ manual_compaction_thread.join();
+ 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), RandomString(&rnd, 1024)));
+ }
+ Flush(1);
+ }
+ auto manual_compaction_thread = port::Thread([this, i]() {
+ CompactRangeOptions cro;
+ cro.allow_write_stall = false;
+ Status s = db_->CompactRange(cro, handles_[1], nullptr, nullptr);
+ 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");
+ 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), RandomString(&rnd, 1024)));
+ }
+ dbfull()->Flush(flush_opts);
+ }
+ auto manual_compaction_thread = port::Thread([this]() {
+ CompactRangeOptions cro;
+ cro.allow_write_stall = false;
+ db_->CompactRange(cro, nullptr, nullptr);
+ });
+
+ TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush");
+ Put(ToString(0), RandomString(&rnd, 1024));
+ dbfull()->Flush(flush_opts);
+ Put(ToString(0), RandomString(&rnd, 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;
+ db_->GetProperty(DB::Properties::kNumEntriesActiveMemTable, &num_keys_in_memtable);
+ ASSERT_EQ(ToString(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
+ 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++) {
+ Put(std::to_string(j), std::string(1, 'A'));
+ }
+ ASSERT_OK(Flush());
+ ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
+ }
+ dbfull()->TEST_WaitForCompact();
+ ColumnFamilyHandleImpl* cfh =
+ static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
+ ColumnFamilyData* cfd = cfh->cfd();
+
+ VerifyCompactionStats(*cfd, *collector);
+}
+
+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<std::string> 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());
+
+ 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<ConcurrentTaskLimiter> limiter;
+ };
+
+ std::vector<CompactionLimiter> 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<ConcurrentTaskLimiter> 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<std::string, CompactionLimiter*> 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(new SpecialSkipListFactory(kNumKeysPerFile));
+ options.max_write_buffer_number = 10; // Enough memtables
+ DestroyAndReopen(options);
+
+ std::vector<Options> 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<std::string>(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<ColumnFamilyData*>(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<ColumnFamilyData*>(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++) {
+ 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, "", ""));
+ 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++) {
+ 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, "", ""));
+
+ 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 = new MockEnv(Env::Default());
+ Reopen(options);
+ bool readahead = false;
+ SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::OpenCompactionOutputFile", [&](void* arg) {
+ bool* use_direct_writes = static_cast<bool*>(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, "p1", "p9");
+ 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<uint32_t> {
+ public:
+ CompactionPriTest() : DBTestBase("/compaction_pri_test") {
+ 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<CompactionPri>(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;
+ }
+ std::random_shuffle(std::begin(keys), std::end(keys));
+
+ for (int i = 0; i < kNKeys; i++) {
+ ASSERT_OK(Put(Key(keys[i]), RandomString(&rnd, 102)));
+ }
+
+ 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));
+
+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) {
+ Merge("foo", RandomString(&rnd, 1024));
+ }
+ 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;
+ 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<FaultInjectionTestEnv> mock_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ 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.
+ Put("key1", RandomString(&rnd, 1024));
+ Put("key2", RandomString(&rnd, 1024));
+ 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", RandomString(&rnd, 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<ColumnFamilyHandleImpl*>(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), RandomString(&rnd, 1024)));
+ }
+ 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), RandomString(&rnd, 1024)));
+ }
+ Flush();
+ }
+ const std::vector<InternalStats::CompactionStats>& 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;
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+
+ const std::vector<InternalStats::CompactionStats>& comp_stats2 =
+ internal_stats_ptr->TEST_GetCompactionStats();
+ num = comp_stats2[2].num_input_files_in_output_level;
+ ASSERT_EQ(num, 0);
+}
+
+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<ColumnFamilyHandleImpl*>(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), RandomString(&rnd, 1024)));
+ }
+ Flush();
+ }
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
+ [&](void* /*arg*/) { dbfull()->shutting_down_.store(true); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ 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/";
+ test::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(new SpecialSkipListFactory(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), RandomString(&rnd, 990)));
+ }
+ if (0 == i) {
+ // When we reach here, the memtables have kNumKeysPerFile keys. Note that
+ // flush is not yet triggered. We need to write an extra key so that the
+ // write path will call PreprocessWrite and flush the previous key-value
+ // pairs to e flushed. After that, there will be the newest key in the
+ // memtable, and a bunch of L0 files. Since there is already one key in
+ // the memtable, then for i = 1, 2, ..., we do not have to write this
+ // extra key to trigger flush.
+ ASSERT_OK(Put("", ""));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(NumTableFilesAtLevel(0 /*level*/, 0 /*cf*/), i + 1);
+ }
+ // 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();
+}
+
+TEST_F(DBCompactionTest, ConsistencyFailTest) {
+ Options options = CurrentOptions();
+ DestroyAndReopen(options);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "VersionBuilder::CheckConsistency", [&](void* arg) {
+ auto p =
+ reinterpret_cast<std::pair<FileMetaData**, FileMetaData**>*>(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"));
+ Flush();
+ }
+
+ ASSERT_NOK(Put("foo", "bar"));
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+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));
+}
+
+TEST_P(DBCompactionTestWithParam,
+ FlushAfterIntraL0CompactionCheckConsistencyFail) {
+ Options options = CurrentOptions();
+ options.force_consistency_checks = true;
+ 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::atomic<int> pick_intra_l0_count(0);
+ std::string value(RandomString(&rnd, kValueSize));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBCompactionTestWithParam::FlushAfterIntraL0:1",
+ "CompactionJob::Run():Start"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "FindIntraL0Compaction",
+ [&](void* /*arg*/) { pick_intra_l0_count.fetch_add(1); });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // 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));
+
+ // Flush 5 L0 sst.
+ for (int i = 0; i < 5; ++i) {
+ ASSERT_OK(Put(Key(i + 1), value));
+ ASSERT_OK(Flush());
+ }
+ ASSERT_EQ(5, NumTableFilesAtLevel(0));
+
+ // Put one key, to make smallest log sequence number in this memtable is less
+ // than sst which would be ingested in next step.
+ ASSERT_OK(Put(Key(0), "a"));
+
+ ASSERT_EQ(5, NumTableFilesAtLevel(0));
+
+ // Ingest 5 L0 sst. And this files would trigger PickIntraL0Compaction.
+ for (int i = 5; i < 10; i++) {
+ IngestOneKeyValue(dbfull(), Key(i), value, options);
+ ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
+ }
+
+ TEST_SYNC_POINT("DBCompactionTestWithParam::FlushAfterIntraL0:1");
+ // Put one key, to make biggest log sequence number in this memtable is bigger
+ // than sst which would be ingested in next step.
+ ASSERT_OK(Put(Key(2), "b"));
+ ASSERT_EQ(10, NumTableFilesAtLevel(0));
+ dbfull()->TEST_WaitForCompact();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ std::vector<std::vector<FileMetaData>> level_to_files;
+ dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
+ &level_to_files);
+ ASSERT_GT(level_to_files[0].size(), 0);
+ ASSERT_GT(pick_intra_l0_count.load(), 0);
+
+ ASSERT_OK(Flush());
+}
+
+TEST_P(DBCompactionTestWithParam,
+ IntraL0CompactionAfterFlushCheckConsistencyFail) {
+ Options options = CurrentOptions();
+ options.force_consistency_checks = true;
+ options.compression = kNoCompression;
+ options.level0_file_num_compaction_trigger = 5;
+ options.max_background_compactions = 2;
+ options.max_subcompactions = max_subcompactions_;
+ options.write_buffer_size = 2 << 20;
+ options.max_write_buffer_number = 6;
+ DestroyAndReopen(options);
+
+ const size_t kValueSize = 1 << 20;
+ Random rnd(301);
+ std::string value(RandomString(&rnd, kValueSize));
+ std::string value2(RandomString(&rnd, kValueSize));
+ std::string bigvalue = value + value;
+
+ // 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));
+
+ std::atomic<int> pick_intra_l0_count(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBCompactionTestWithParam::IntraL0CompactionAfterFlush:1",
+ "CompactionJob::Run():Start"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "FindIntraL0Compaction",
+ [&](void* /*arg*/) { pick_intra_l0_count.fetch_add(1); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ // Make 6 L0 sst.
+ for (int i = 0; i < 6; ++i) {
+ if (i % 2 == 0) {
+ IngestOneKeyValue(dbfull(), Key(i), value, options);
+ } else {
+ ASSERT_OK(Put(Key(i), value));
+ ASSERT_OK(Flush());
+ }
+ }
+
+ ASSERT_EQ(6, NumTableFilesAtLevel(0));
+
+ // Stop run flush job
+ env_->SetBackgroundThreads(1, Env::HIGH);
+ test::SleepingBackgroundTask sleeping_tasks;
+ env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_tasks,
+ Env::Priority::HIGH);
+ sleeping_tasks.WaitUntilSleeping();
+
+ // Put many keys to make memtable request to flush
+ for (int i = 0; i < 6; ++i) {
+ ASSERT_OK(Put(Key(i), bigvalue));
+ }
+
+ ASSERT_EQ(6, NumTableFilesAtLevel(0));
+ // ingest file to trigger IntraL0Compaction
+ for (int i = 6; i < 10; ++i) {
+ ASSERT_EQ(i, NumTableFilesAtLevel(0));
+ IngestOneKeyValue(dbfull(), Key(i), value2, options);
+ }
+ ASSERT_EQ(10, NumTableFilesAtLevel(0));
+
+ // Wake up flush job
+ sleeping_tasks.WakeUp();
+ sleeping_tasks.WaitUntilDone();
+ TEST_SYNC_POINT("DBCompactionTestWithParam::IntraL0CompactionAfterFlush:1");
+ dbfull()->TEST_WaitForCompact();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ uint64_t error_count = 0;
+ db_->GetIntProperty("rocksdb.background-errors", &error_count);
+ ASSERT_EQ(error_count, 0);
+ ASSERT_GT(pick_intra_l0_count.load(), 0);
+ for (int i = 0; i < 6; ++i) {
+ ASSERT_EQ(bigvalue, Get(Key(i)));
+ }
+ for (int i = 6; i < 10; ++i) {
+ ASSERT_EQ(value2, Get(Key(i)));
+ }
+}
+
+#endif // !defined(ROCKSDB_LITE)
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+#if !defined(ROCKSDB_LITE)
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ (void) argc;
+ (void) argv;
+ return 0;
+#endif
+}
diff --git a/storage/rocksdb/rocksdb/db/db_dynamic_level_test.cc b/storage/rocksdb/rocksdb/db/db_dynamic_level_test.cc
new file mode 100644
index 00000000..c2665770
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_dynamic_level_test.cc
@@ -0,0 +1,505 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#if !defined(ROCKSDB_LITE)
+
+#include "db/db_test_util.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+
+namespace ROCKSDB_NAMESPACE {
+class DBTestDynamicLevel : public DBTestBase {
+ public:
+ DBTestDynamicLevel() : DBTestBase("/db_dynamic_level_test") {}
+};
+
+TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) {
+ if (!Snappy_Supported() || !LZ4_Supported()) {
+ return;
+ }
+ // Use InMemoryEnv, or it would be too slow.
+ std::unique_ptr<Env> env(new MockEnv(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) {
+ std::random_shuffle(std::begin(keys), std::end(keys));
+ }
+ 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.soft_rate_limit = 1.1;
+ 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), RandomString(&rnd, 102)));
+ ASSERT_OK(Put(Key(key), RandomString(&rnd, 102)));
+ ASSERT_OK(Put(Key(kNKeys * 2 + key), RandomString(&rnd, 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
+ 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
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 380)));
+ }
+ ASSERT_OK(dbfull()->SetOptions({
+ {"disable_auto_compactions", "false"},
+ }));
+ Flush();
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 380)));
+ }
+
+ ASSERT_OK(dbfull()->SetOptions({
+ {"disable_auto_compactions", "false"},
+ }));
+ Flush();
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 380)));
+ }
+ ASSERT_OK(dbfull()->SetOptions({
+ {"disable_auto_compactions", "false"},
+ }));
+ Flush();
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 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");
+ Flush();
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 380)));
+ }
+ TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:2");
+
+ 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
+ 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<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 80)));
+ }
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ if (NumTableFilesAtLevel(0) == 0) {
+ // Make sure level 0 is not empty
+ ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
+ RandomString(&rnd, 80)));
+ 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<int> output_levels;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionPicker::CompactRange:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(arg);
+ output_levels.insert(compaction->output_level());
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ 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.soft_rate_limit = 1.1;
+ 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 = RandomString(&rnd, random_part_size);
+ PutFixed32(&value, static_cast<uint32_t>(i));
+ ASSERT_OK(Put(Key(i), value));
+ }
+ Flush();
+ 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<uint32_t>(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.soft_rate_limit = 1.1;
+ 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), RandomString(&rnd, 102)));
+ ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
+ ASSERT_OK(Delete(Key(i / 10)));
+ }
+ verify_func(total_keys, false);
+ 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;
+ 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), RandomString(&rnd, 102)));
+ ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
+ ASSERT_OK(Delete(Key(i / 10)));
+ }
+
+ verify_func(total_keys2, false);
+ 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
+
+#endif // !defined(ROCKSDB_LITE)
+
+int main(int argc, char** argv) {
+#if !defined(ROCKSDB_LITE)
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ (void) argc;
+ (void) argv;
+ return 0;
+#endif
+}
diff --git a/storage/rocksdb/rocksdb/db/db_encryption_test.cc b/storage/rocksdb/rocksdb/db/db_encryption_test.cc
new file mode 100644
index 00000000..b1f3ce23
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_encryption_test.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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"
+#if !defined(ROCKSDB_LITE)
+#include "test_util/sync_point.h"
+#endif
+#include <iostream>
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBEncryptionTest : public DBTestBase {
+ public:
+ DBEncryptionTest() : DBTestBase("/db_encryption_test") {}
+};
+
+#ifndef ROCKSDB_LITE
+
+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<std::string> fileNames;
+ auto status = env_->GetChildren(dbname_, &fileNames);
+ ASSERT_OK(status);
+
+ auto defaultEnv = Env::Default();
+ int hits = 0;
+ for (auto it = fileNames.begin() ; it != fileNames.end(); ++it) {
+ if ((*it == "..") || (*it == ".")) {
+ continue;
+ }
+ auto filePath = dbname_ + "/" + *it;
+ std::unique_ptr<SequentialFile> seqFile;
+ auto envOptions = EnvOptions(CurrentOptions());
+ status = defaultEnv->NewSequentialFile(filePath, &seqFile, envOptions);
+ ASSERT_OK(status);
+
+ uint64_t fileSize;
+ status = defaultEnv->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 = Env::Default();
+
+ // create empty file for reading it back in later
+ auto envOptions = EnvOptions(CurrentOptions());
+ auto filePath = dbname_ + "/empty.empty";
+
+ Status status;
+ {
+ std::unique_ptr<WritableFile> writableFile;
+ status = defaultEnv->NewWritableFile(filePath, &writableFile, envOptions);
+ ASSERT_OK(status);
+ }
+
+ std::unique_ptr<SequentialFile> 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());
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_filesnapshot.cc b/storage/rocksdb/rocksdb/db/db_filesnapshot.cc
new file mode 100644
index 00000000..f0f22cb4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_filesnapshot.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <stdint.h>
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include "db/db_impl/db_impl.h"
+#include "db/job_context.h"
+#include "db/version_set.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status DBImpl::DisableFileDeletions() {
+ InstrumentedMutexLock l(&mutex_);
+ ++disable_delete_obsolete_files_;
+ if (disable_delete_obsolete_files_ == 1) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Disabled");
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "File Deletions Disabled, but already disabled. Counter: %d",
+ disable_delete_obsolete_files_);
+ }
+ return Status::OK();
+}
+
+Status DBImpl::EnableFileDeletions(bool force) {
+ // Job id == 0 means that this is not our background process, but rather
+ // user thread
+ JobContext job_context(0);
+ bool file_deletion_enabled = false;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ if (force) {
+ // if force, we need to enable file deletions right away
+ disable_delete_obsolete_files_ = 0;
+ } else if (disable_delete_obsolete_files_ > 0) {
+ --disable_delete_obsolete_files_;
+ }
+ if (disable_delete_obsolete_files_ == 0) {
+ file_deletion_enabled = true;
+ FindObsoleteFiles(&job_context, true);
+ bg_cv_.SignalAll();
+ }
+ }
+ if (file_deletion_enabled) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ }
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "File Deletions Enable, but not really enabled. Counter: %d",
+ disable_delete_obsolete_files_);
+ }
+ job_context.Clean();
+ LogFlush(immutable_db_options_.info_log);
+ return Status::OK();
+}
+
+int DBImpl::IsFileDeletionsEnabled() const {
+ return !disable_delete_obsolete_files_;
+}
+
+Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
+ uint64_t* manifest_file_size,
+ bool flush_memtable) {
+ *manifest_file_size = 0;
+
+ mutex_.Lock();
+
+ if (flush_memtable) {
+ // flush all dirty data to disk.
+ Status status;
+ if (immutable_db_options_.atomic_flush) {
+ autovector<ColumnFamilyData*> cfds;
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ mutex_.Unlock();
+ status = AtomicFlushMemTables(cfds, FlushOptions(),
+ FlushReason::kGetLiveFiles);
+ mutex_.Lock();
+ } else {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ cfd->Ref();
+ mutex_.Unlock();
+ status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
+ TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
+ TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
+ mutex_.Lock();
+ cfd->UnrefAndTryDelete();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ }
+ versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
+
+ if (!status.ok()) {
+ mutex_.Unlock();
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log, "Cannot Flush data %s\n",
+ status.ToString().c_str());
+ return status;
+ }
+ }
+
+ // Make a set of all of the live *.sst files
+ std::vector<FileDescriptor> live;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ cfd->current()->AddLiveFiles(&live);
+ }
+
+ ret.clear();
+ ret.reserve(live.size() + 3); // *.sst + CURRENT + MANIFEST + OPTIONS
+
+ // create names of the live files. The names are not absolute
+ // paths, instead they are relative to dbname_;
+ for (const auto& live_file : live) {
+ ret.push_back(MakeTableFileName("", live_file.GetNumber()));
+ }
+
+ ret.push_back(CurrentFileName(""));
+ ret.push_back(DescriptorFileName("", versions_->manifest_file_number()));
+ ret.push_back(OptionsFileName("", versions_->options_file_number()));
+
+ // find length of manifest file while holding the mutex lock
+ *manifest_file_size = versions_->manifest_file_size();
+
+ mutex_.Unlock();
+ return Status::OK();
+}
+
+Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
+ {
+ // If caller disabled deletions, this function should return files that are
+ // guaranteed not to be deleted until deletions are re-enabled. We need to
+ // wait for pending purges to finish since WalManager doesn't know which
+ // files are going to be purged. Additional purges won't be scheduled as
+ // long as deletions are disabled (so the below loop must terminate).
+ InstrumentedMutexLock l(&mutex_);
+ while (disable_delete_obsolete_files_ > 0 &&
+ pending_purge_obsolete_files_ > 0) {
+ bg_cv_.Wait();
+ }
+ }
+ return wal_manager_.GetSortedWalFiles(files);
+}
+
+Status DBImpl::GetCurrentWalFile(std::unique_ptr<LogFile>* current_log_file) {
+ uint64_t current_logfile_number;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ current_logfile_number = logfile_number_;
+ }
+
+ return wal_manager_.GetLiveWalFile(current_logfile_number, current_log_file);
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/db_flush_test.cc b/storage/rocksdb/rocksdb/db/db_flush_test.cc
new file mode 100644
index 00000000..bab206d3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_flush_test.cc
@@ -0,0 +1,784 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+
+#include "db/db_impl/db_impl.h"
+#include "db/db_test_util.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBFlushTest : public DBTestBase {
+ public:
+ DBFlushTest() : DBTestBase("/db_flush_test") {}
+};
+
+class DBFlushDirectIOTest : public DBFlushTest,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ DBFlushDirectIOTest() : DBFlushTest() {}
+};
+
+class DBAtomicFlushTest : public DBFlushTest,
+ public ::testing::WithParamInterface<bool> {
+ 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.
+ dbfull()->TEST_WaitForFlushMemTable();
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(2, TotalTableFiles());
+#endif // ROCKSDB_LITE
+}
+
+// Disable this test temporarily on Travis as it fails intermittently.
+// Github issue: #4151
+TEST_F(DBFlushTest, SyncFail) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
+ new FaultInjectionTestEnv(env_));
+ Options options;
+ options.disable_auto_compactions = true;
+ options.env = fault_injection_env.get();
+
+ SyncPoint::GetInstance()->LoadDependency(
+ {{"DBFlushTest::SyncFail:GetVersionRefCount:1",
+ "DBImpl::FlushMemTableToOutputFile:BeforePickMemtables"},
+ {"DBImpl::FlushMemTableToOutputFile:AfterPickMemtables",
+ "DBFlushTest::SyncFail:GetVersionRefCount:2"},
+ {"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"},
+ {"DBImpl::SyncClosedLogs:Failed", "DBFlushTest::SyncFail:2"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Put("key", "value");
+ auto* cfd =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
+ ->cfd();
+ 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.
+ auto current_before = cfd->current();
+ int refs_before = cfd->current()->TEST_refs();
+ TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:1");
+ TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:2");
+ int refs_after_picking_memtables = cfd->current()->TEST_refs();
+ ASSERT_EQ(refs_before + 1, refs_after_picking_memtables);
+ 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.
+ dbfull()->TEST_WaitForFlushMemTable();
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("", FilesPerLevel()); // flush failed.
+#endif // ROCKSDB_LITE
+ // Backgroun flush job should release ref count to current version.
+ ASSERT_EQ(current_before, cfd->current());
+ ASSERT_EQ(refs_before, cfd->current()->TEST_refs());
+ 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);
+ 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.
+ 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(new SpecialSkipListFactory(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"));
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(4, num_flushes);
+ ASSERT_EQ(1, num_compactions);
+}
+
+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<int*>(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();
+}
+
+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 = new MockEnv(Env::Default());
+ SyncPoint::GetInstance()->SetCallBack(
+ "BuildTable:create_file", [&](void* arg) {
+ bool* use_direct_writes = static_cast<bool*>(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<FaultInjectionTestEnv> 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<FaultInjectionTestEnv> 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());
+ dbfull()->TEST_WaitForFlushMemTable();
+#ifndef ROCKSDB_LITE
+ uint64_t num_bg_errors;
+ ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBackgroundErrors,
+ &num_bg_errors));
+ ASSERT_GT(num_bg_errors, 0);
+#endif // ROCKSDB_LITE
+
+ // 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<ColumnFamilyHandleImpl*>(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();
+}
+
+#ifndef ROCKSDB_LITE
+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<DBImpl>(db);
+ InstrumentedMutex* mutex = db_impl->mutex();
+ mutex->Lock();
+ auto* cfd =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(db->DefaultColumnFamily())
+ ->cfd();
+ ASSERT_LT(seq, cfd->imm()->current()->GetEarliestSequenceNumber());
+ mutex->Unlock();
+ }
+
+ std::atomic<SequenceNumber> seq1{0};
+ std::atomic<SequenceNumber> seq2{0};
+ std::atomic<bool> completed1{false};
+ std::atomic<bool> completed2{false};
+ };
+ std::shared_ptr<TestListener> listener = std::make_shared<TestListener>();
+
+ SyncPoint::GetInstance()->LoadDependency(
+ {{"DBImpl::BackgroundCallFlush:start",
+ "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<autovector<MemTable*>*>(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();
+ ASSERT_TRUE(listener->completed1);
+ ASSERT_TRUE(listener->completed2);
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+#endif // !ROCKSDB_LITE
+
+TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.atomic_flush = GetParam();
+ options.write_buffer_size = (static_cast<size_t>(64) << 20);
+
+ 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<int>(i) /*cf*/, "key", "value", wopts));
+ }
+ std::vector<int> cf_ids;
+ for (size_t i = 0; i != num_cfs; ++i) {
+ cf_ids.emplace_back(static_cast<int>(i));
+ }
+ ASSERT_OK(Flush(cf_ids));
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(handles_[i]);
+ ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed());
+ ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty());
+ }
+}
+
+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<int>(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<int>(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 != num_cfs - 1; ++i) {
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(handles_[i]);
+ ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed());
+ ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty());
+ }
+ } else {
+ for (size_t i = 0; i != num_cfs - 1; ++i) {
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(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<FaultInjectionTestEnv> 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<int>(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_) {
+ dbfull()->TEST_WaitForFlushMemTable(cfh);
+ }
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(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<int> cf_ids;
+ for (size_t i = 0; i != num_cfs; ++i) {
+ int cf_id = static_cast<int>(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<int>(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<int>(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<int>(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(
+ new SpecialSkipListFactory(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<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily())
+ ->cfd();
+ auto* cfd_pikachu = static_cast<ColumnFamilyHandleImpl*>(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<FaultInjectionTestEnv>(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<int>(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();
+}
+
+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/storage/rocksdb/rocksdb/db/db_impl/db_impl.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl.cc
new file mode 100644
index 00000000..d7880fc1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl.cc
@@ -0,0 +1,4550 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <stdint.h>
+#ifdef OS_SOLARIS
+#include <alloca.h>
+#endif
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdio>
+#include <map>
+#include <set>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "db/arena_wrapped_db_iter.h"
+#include "db/builder.h"
+#include "db/compaction/compaction_job.h"
+#include "db/db_info_dumper.h"
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "db/error_handler.h"
+#include "db/event_helpers.h"
+#include "db/external_sst_file_ingestion_job.h"
+#include "db/flush_job.h"
+#include "db/forward_iterator.h"
+#include "db/import_column_family_job.h"
+#include "db/job_context.h"
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/malloc_stats.h"
+#include "db/memtable.h"
+#include "db/memtable_list.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/table_cache.h"
+#include "db/table_properties_collector.h"
+#include "db/transaction_log_impl.h"
+#include "db/version_set.h"
+#include "db/write_batch_internal.h"
+#include "db/write_callback.h"
+#include "env/composite_env_wrapper.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "file/random_access_file_reader.h"
+#include "file/sst_file_manager_impl.h"
+#include "logging/auto_roll_logger.h"
+#include "logging/log_buffer.h"
+#include "logging/logging.h"
+#include "memtable/hash_linklist_rep.h"
+#include "memtable/hash_skiplist_rep.h"
+#include "monitoring/in_memory_stats_history.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/persistent_stats_history.h"
+#include "monitoring/thread_status_updater.h"
+#include "monitoring/thread_status_util.h"
+#include "options/cf_options.h"
+#include "options/options_helper.h"
+#include "options/options_parser.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/stats_history.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/get_context.h"
+#include "table/merging_iterator.h"
+#include "table/multiget_context.h"
+#include "table/table_builder.h"
+#include "table/two_level_iterator.h"
+#include "test_util/sync_point.h"
+#include "tools/sst_dump_tool_imp.h"
+#include "util/autovector.h"
+#include "util/build_version.h"
+#include "util/cast_util.h"
+#include "util/coding.h"
+#include "util/compression.h"
+#include "util/crc32c.h"
+#include "util/mutexlock.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const std::string kDefaultColumnFamilyName("default");
+const std::string kPersistentStatsColumnFamilyName(
+ "___rocksdb_stats_history___");
+void DumpRocksDBBuildVersion(Logger* log);
+
+CompressionType GetCompressionFlush(
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options) {
+ // Compressing memtable flushes might not help unless the sequential load
+ // optimization is used for leveled compaction. Otherwise the CPU and
+ // latency overhead is not offset by saving much space.
+ if (ioptions.compaction_style == kCompactionStyleUniversal) {
+ if (mutable_cf_options.compaction_options_universal
+ .compression_size_percent < 0) {
+ return mutable_cf_options.compression;
+ } else {
+ return kNoCompression;
+ }
+ } else if (!ioptions.compression_per_level.empty()) {
+ // For leveled compress when min_level_to_compress != 0.
+ return ioptions.compression_per_level[0];
+ } else {
+ return mutable_cf_options.compression;
+ }
+}
+
+namespace {
+void DumpSupportInfo(Logger* logger) {
+ ROCKS_LOG_HEADER(logger, "Compression algorithms supported:");
+ for (auto& compression : OptionsHelper::compression_type_string_map) {
+ if (compression.second != kNoCompression &&
+ compression.second != kDisableCompressionOption) {
+ ROCKS_LOG_HEADER(logger, "\t%s supported: %d", compression.first.c_str(),
+ CompressionTypeSupported(compression.second));
+ }
+ }
+ ROCKS_LOG_HEADER(logger, "Fast CRC32 supported: %s",
+ crc32c::IsFastCrc32Supported().c_str());
+}
+} // namespace
+
+DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
+ const bool seq_per_batch, const bool batch_per_txn)
+ : dbname_(dbname),
+ own_info_log_(options.info_log == nullptr),
+ initial_db_options_(SanitizeOptions(dbname, options)),
+ env_(initial_db_options_.env),
+ fs_(initial_db_options_.file_system),
+ immutable_db_options_(initial_db_options_),
+ mutable_db_options_(initial_db_options_),
+ stats_(immutable_db_options_.statistics.get()),
+ mutex_(stats_, env_, DB_MUTEX_WAIT_MICROS,
+ immutable_db_options_.use_adaptive_mutex),
+ default_cf_handle_(nullptr),
+ max_total_in_memory_state_(0),
+ file_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
+ file_options_for_compaction_(fs_->OptimizeForCompactionTableWrite(
+ file_options_, immutable_db_options_)),
+ seq_per_batch_(seq_per_batch),
+ batch_per_txn_(batch_per_txn),
+ db_lock_(nullptr),
+ shutting_down_(false),
+ manual_compaction_paused_(false),
+ bg_cv_(&mutex_),
+ logfile_number_(0),
+ log_dir_synced_(false),
+ log_empty_(true),
+ persist_stats_cf_handle_(nullptr),
+ log_sync_cv_(&mutex_),
+ total_log_size_(0),
+ is_snapshot_supported_(true),
+ write_buffer_manager_(immutable_db_options_.write_buffer_manager.get()),
+ write_thread_(immutable_db_options_),
+ nonmem_write_thread_(immutable_db_options_),
+ write_controller_(mutable_db_options_.delayed_write_rate),
+ last_batch_group_size_(0),
+ unscheduled_flushes_(0),
+ unscheduled_compactions_(0),
+ bg_bottom_compaction_scheduled_(0),
+ bg_compaction_scheduled_(0),
+ num_running_compactions_(0),
+ bg_flush_scheduled_(0),
+ num_running_flushes_(0),
+ bg_purge_scheduled_(0),
+ disable_delete_obsolete_files_(0),
+ pending_purge_obsolete_files_(0),
+ delete_obsolete_files_last_run_(env_->NowMicros()),
+ last_stats_dump_time_microsec_(0),
+ next_job_id_(1),
+ has_unpersisted_data_(false),
+ unable_to_release_oldest_log_(false),
+ num_running_ingest_file_(0),
+#ifndef ROCKSDB_LITE
+ wal_manager_(immutable_db_options_, file_options_, seq_per_batch),
+#endif // ROCKSDB_LITE
+ event_logger_(immutable_db_options_.info_log.get()),
+ bg_work_paused_(0),
+ bg_compaction_paused_(0),
+ refitting_level_(false),
+ opened_successfully_(false),
+ two_write_queues_(options.two_write_queues),
+ manual_wal_flush_(options.manual_wal_flush),
+ // last_sequencee_ is always maintained by the main queue that also writes
+ // to the memtable. When two_write_queues_ is disabled last seq in
+ // memtable is the same as last seq published to the readers. When it is
+ // enabled but seq_per_batch_ is disabled, last seq in memtable still
+ // indicates last published seq since wal-only writes that go to the 2nd
+ // queue do not consume a sequence number. Otherwise writes performed by
+ // the 2nd queue could change what is visible to the readers. In this
+ // cases, last_seq_same_as_publish_seq_==false, the 2nd queue maintains a
+ // separate variable to indicate the last published sequence.
+ last_seq_same_as_publish_seq_(
+ !(seq_per_batch && options.two_write_queues)),
+ // Since seq_per_batch_ is currently set only by WritePreparedTxn which
+ // requires a custom gc for compaction, we use that to set use_custom_gc_
+ // as well.
+ use_custom_gc_(seq_per_batch),
+ shutdown_initiated_(false),
+ own_sfm_(options.sst_file_manager == nullptr),
+ preserve_deletes_(options.preserve_deletes),
+ closed_(false),
+ error_handler_(this, immutable_db_options_, &mutex_),
+ atomic_flush_install_cv_(&mutex_) {
+ // !batch_per_trx_ implies seq_per_batch_ because it is only unset for
+ // WriteUnprepared, which should use seq_per_batch_.
+ assert(batch_per_txn_ || seq_per_batch_);
+ env_->GetAbsolutePath(dbname, &db_absolute_path_);
+
+ // Reserve ten files or so for other uses and give the rest to TableCache.
+ // Give a large number for setting of "infinite" open files.
+ const int table_cache_size = (mutable_db_options_.max_open_files == -1)
+ ? TableCache::kInfiniteCapacity
+ : mutable_db_options_.max_open_files - 10;
+ LRUCacheOptions co;
+ co.capacity = table_cache_size;
+ co.num_shard_bits = immutable_db_options_.table_cache_numshardbits;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ table_cache_ = NewLRUCache(co);
+
+ versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_,
+ table_cache_.get(), write_buffer_manager_,
+ &write_controller_, &block_cache_tracer_));
+ column_family_memtables_.reset(
+ new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
+
+ DumpRocksDBBuildVersion(immutable_db_options_.info_log.get());
+ DumpDBFileSummary(immutable_db_options_, dbname_);
+ immutable_db_options_.Dump(immutable_db_options_.info_log.get());
+ mutable_db_options_.Dump(immutable_db_options_.info_log.get());
+ DumpSupportInfo(immutable_db_options_.info_log.get());
+
+ // always open the DB with 0 here, which means if preserve_deletes_==true
+ // we won't drop any deletion markers until SetPreserveDeletesSequenceNumber()
+ // is called by client and this seqnum is advanced.
+ preserve_deletes_seqnum_.store(0);
+}
+
+Status DBImpl::Resume() {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "Resuming DB");
+
+ InstrumentedMutexLock db_mutex(&mutex_);
+
+ if (!error_handler_.IsDBStopped() && !error_handler_.IsBGWorkStopped()) {
+ // Nothing to do
+ return Status::OK();
+ }
+
+ if (error_handler_.IsRecoveryInProgress()) {
+ // Don't allow a mix of manual and automatic recovery
+ return Status::Busy();
+ }
+
+ mutex_.Unlock();
+ Status s = error_handler_.RecoverFromBGError(true);
+ mutex_.Lock();
+ return s;
+}
+
+// This function implements the guts of recovery from a background error. It
+// is eventually called for both manual as well as automatic recovery. It does
+// the following -
+// 1. Wait for currently scheduled background flush/compaction to exit, in
+// order to inadvertently causing an error and thinking recovery failed
+// 2. Flush memtables if there's any data for all the CFs. This may result
+// another error, which will be saved by error_handler_ and reported later
+// as the recovery status
+// 3. Find and delete any obsolete files
+// 4. Schedule compactions if needed for all the CFs. This is needed as the
+// flush in the prior step might have been a no-op for some CFs, which
+// means a new super version wouldn't have been installed
+Status DBImpl::ResumeImpl() {
+ mutex_.AssertHeld();
+ WaitForBackgroundWork();
+
+ Status bg_error = error_handler_.GetBGError();
+ Status s;
+ if (shutdown_initiated_) {
+ // Returning shutdown status to SFM during auto recovery will cause it
+ // to abort the recovery and allow the shutdown to progress
+ s = Status::ShutdownInProgress();
+ }
+ if (s.ok() && bg_error.severity() > Status::Severity::kHardError) {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "DB resume requested but failed due to Fatal/Unrecoverable error");
+ s = bg_error;
+ }
+
+ // We cannot guarantee consistency of the WAL. So force flush Memtables of
+ // all the column families
+ if (s.ok()) {
+ FlushOptions flush_opts;
+ // We allow flush to stall write since we are trying to resume from error.
+ flush_opts.allow_write_stall = true;
+ if (immutable_db_options_.atomic_flush) {
+ autovector<ColumnFamilyData*> cfds;
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ mutex_.Unlock();
+ s = AtomicFlushMemTables(cfds, flush_opts, FlushReason::kErrorRecovery);
+ mutex_.Lock();
+ } else {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ cfd->Ref();
+ mutex_.Unlock();
+ s = FlushMemTable(cfd, flush_opts, FlushReason::kErrorRecovery);
+ mutex_.Lock();
+ cfd->UnrefAndTryDelete();
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ if (!s.ok()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "DB resume requested but failed due to Flush failure [%s]",
+ s.ToString().c_str());
+ }
+ }
+
+ JobContext job_context(0);
+ FindObsoleteFiles(&job_context, true);
+ if (s.ok()) {
+ s = error_handler_.ClearBGError();
+ }
+ mutex_.Unlock();
+
+ job_context.manifest_file_number = 1;
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+
+ if (s.ok()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
+ }
+ mutex_.Lock();
+ // Check for shutdown again before scheduling further compactions,
+ // since we released and re-acquired the lock above
+ if (shutdown_initiated_) {
+ s = Status::ShutdownInProgress();
+ }
+ if (s.ok()) {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ SchedulePendingCompaction(cfd);
+ }
+ MaybeScheduleFlushOrCompaction();
+ }
+
+ // Wake up any waiters - in this case, it could be the shutdown thread
+ bg_cv_.SignalAll();
+
+ // No need to check BGError again. If something happened, event listener would
+ // be notified and the operation causing it would have failed
+ return s;
+}
+
+void DBImpl::WaitForBackgroundWork() {
+ // Wait for background work to finish
+ while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
+ bg_flush_scheduled_) {
+ bg_cv_.Wait();
+ }
+}
+
+// Will lock the mutex_, will wait for completion if wait is true
+void DBImpl::CancelAllBackgroundWork(bool wait) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Shutdown: canceling all background work");
+
+ if (thread_dump_stats_ != nullptr) {
+ thread_dump_stats_->cancel();
+ thread_dump_stats_.reset();
+ }
+ if (thread_persist_stats_ != nullptr) {
+ thread_persist_stats_->cancel();
+ thread_persist_stats_.reset();
+ }
+ InstrumentedMutexLock l(&mutex_);
+ if (!shutting_down_.load(std::memory_order_acquire) &&
+ has_unpersisted_data_.load(std::memory_order_relaxed) &&
+ !mutable_db_options_.avoid_flush_during_shutdown) {
+ if (immutable_db_options_.atomic_flush) {
+ autovector<ColumnFamilyData*> cfds;
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ mutex_.Unlock();
+ AtomicFlushMemTables(cfds, FlushOptions(), FlushReason::kShutDown);
+ mutex_.Lock();
+ } else {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) {
+ cfd->Ref();
+ mutex_.Unlock();
+ FlushMemTable(cfd, FlushOptions(), FlushReason::kShutDown);
+ mutex_.Lock();
+ cfd->UnrefAndTryDelete();
+ }
+ }
+ }
+ versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
+ }
+
+ shutting_down_.store(true, std::memory_order_release);
+ bg_cv_.SignalAll();
+ if (!wait) {
+ return;
+ }
+ WaitForBackgroundWork();
+}
+
+Status DBImpl::CloseHelper() {
+ // Guarantee that there is no background error recovery in progress before
+ // continuing with the shutdown
+ mutex_.Lock();
+ shutdown_initiated_ = true;
+ error_handler_.CancelErrorRecovery();
+ while (error_handler_.IsRecoveryInProgress()) {
+ bg_cv_.Wait();
+ }
+ mutex_.Unlock();
+
+ // CancelAllBackgroundWork called with false means we just set the shutdown
+ // marker. After this we do a variant of the waiting and unschedule work
+ // (to consider: moving all the waiting into CancelAllBackgroundWork(true))
+ CancelAllBackgroundWork(false);
+ int bottom_compactions_unscheduled =
+ env_->UnSchedule(this, Env::Priority::BOTTOM);
+ int compactions_unscheduled = env_->UnSchedule(this, Env::Priority::LOW);
+ int flushes_unscheduled = env_->UnSchedule(this, Env::Priority::HIGH);
+ Status ret;
+ mutex_.Lock();
+ bg_bottom_compaction_scheduled_ -= bottom_compactions_unscheduled;
+ bg_compaction_scheduled_ -= compactions_unscheduled;
+ bg_flush_scheduled_ -= flushes_unscheduled;
+
+ // Wait for background work to finish
+ while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
+ bg_flush_scheduled_ || bg_purge_scheduled_ ||
+ pending_purge_obsolete_files_ ||
+ error_handler_.IsRecoveryInProgress()) {
+ TEST_SYNC_POINT("DBImpl::~DBImpl:WaitJob");
+ bg_cv_.Wait();
+ }
+ TEST_SYNC_POINT_CALLBACK("DBImpl::CloseHelper:PendingPurgeFinished",
+ &files_grabbed_for_purge_);
+ EraseThreadStatusDbInfo();
+ flush_scheduler_.Clear();
+ trim_history_scheduler_.Clear();
+
+ while (!flush_queue_.empty()) {
+ const FlushRequest& flush_req = PopFirstFromFlushQueue();
+ for (const auto& iter : flush_req) {
+ iter.first->UnrefAndTryDelete();
+ }
+ }
+ while (!compaction_queue_.empty()) {
+ auto cfd = PopFirstFromCompactionQueue();
+ cfd->UnrefAndTryDelete();
+ }
+
+ if (default_cf_handle_ != nullptr || persist_stats_cf_handle_ != nullptr) {
+ // we need to delete handle outside of lock because it does its own locking
+ mutex_.Unlock();
+ if (default_cf_handle_) {
+ delete default_cf_handle_;
+ default_cf_handle_ = nullptr;
+ }
+ if (persist_stats_cf_handle_) {
+ delete persist_stats_cf_handle_;
+ persist_stats_cf_handle_ = nullptr;
+ }
+ mutex_.Lock();
+ }
+
+ // Clean up obsolete files due to SuperVersion release.
+ // (1) Need to delete to obsolete files before closing because RepairDB()
+ // scans all existing files in the file system and builds manifest file.
+ // Keeping obsolete files confuses the repair process.
+ // (2) Need to check if we Open()/Recover() the DB successfully before
+ // deleting because if VersionSet recover fails (may be due to corrupted
+ // manifest file), it is not able to identify live files correctly. As a
+ // result, all "live" files can get deleted by accident. However, corrupted
+ // manifest is recoverable by RepairDB().
+ if (opened_successfully_) {
+ JobContext job_context(next_job_id_.fetch_add(1));
+ FindObsoleteFiles(&job_context, true);
+
+ mutex_.Unlock();
+ // manifest number starting from 2
+ job_context.manifest_file_number = 1;
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ mutex_.Lock();
+ }
+
+ for (auto l : logs_to_free_) {
+ delete l;
+ }
+ for (auto& log : logs_) {
+ uint64_t log_number = log.writer->get_log_number();
+ Status s = log.ClearWriter();
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ immutable_db_options_.info_log,
+ "Unable to Sync WAL file %s with error -- %s",
+ LogFileName(immutable_db_options_.wal_dir, log_number).c_str(),
+ s.ToString().c_str());
+ // Retain the first error
+ if (ret.ok()) {
+ ret = s;
+ }
+ }
+ }
+ logs_.clear();
+
+ // Table cache may have table handles holding blocks from the block cache.
+ // We need to release them before the block cache is destroyed. The block
+ // cache may be destroyed inside versions_.reset(), when column family data
+ // list is destroyed, so leaving handles in table cache after
+ // versions_.reset() may cause issues.
+ // Here we clean all unreferenced handles in table cache.
+ // Now we assume all user queries have finished, so only version set itself
+ // can possibly hold the blocks from block cache. After releasing unreferenced
+ // handles here, only handles held by version set left and inside
+ // versions_.reset(), we will release them. There, we need to make sure every
+ // time a handle is released, we erase it from the cache too. By doing that,
+ // we can guarantee that after versions_.reset(), table cache is empty
+ // so the cache can be safely destroyed.
+ table_cache_->EraseUnRefEntries();
+
+ for (auto& txn_entry : recovered_transactions_) {
+ delete txn_entry.second;
+ }
+
+ // versions need to be destroyed before table_cache since it can hold
+ // references to table_cache.
+ versions_.reset();
+ mutex_.Unlock();
+ if (db_lock_ != nullptr) {
+ env_->UnlockFile(db_lock_);
+ }
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown complete");
+ LogFlush(immutable_db_options_.info_log);
+
+#ifndef ROCKSDB_LITE
+ // If the sst_file_manager was allocated by us during DB::Open(), ccall
+ // Close() on it before closing the info_log. Otherwise, background thread
+ // in SstFileManagerImpl might try to log something
+ if (immutable_db_options_.sst_file_manager && own_sfm_) {
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ sfm->Close();
+ }
+#endif // ROCKSDB_LITE
+
+ if (immutable_db_options_.info_log && own_info_log_) {
+ Status s = immutable_db_options_.info_log->Close();
+ if (ret.ok()) {
+ ret = s;
+ }
+ }
+
+ if (ret.IsAborted()) {
+ // Reserve IsAborted() error for those where users didn't release
+ // certain resource and they can release them and come back and
+ // retry. In this case, we wrap this exception to something else.
+ return Status::Incomplete(ret.ToString());
+ }
+ return ret;
+}
+
+Status DBImpl::CloseImpl() { return CloseHelper(); }
+
+DBImpl::~DBImpl() {
+ if (!closed_) {
+ closed_ = true;
+ CloseHelper();
+ }
+}
+
+void DBImpl::MaybeIgnoreError(Status* s) const {
+ if (s->ok() || immutable_db_options_.paranoid_checks) {
+ // No change needed
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log, "Ignoring error %s",
+ s->ToString().c_str());
+ *s = Status::OK();
+ }
+}
+
+const Status DBImpl::CreateArchivalDirectory() {
+ if (immutable_db_options_.wal_ttl_seconds > 0 ||
+ immutable_db_options_.wal_size_limit_mb > 0) {
+ std::string archivalPath = ArchivalDirectory(immutable_db_options_.wal_dir);
+ return env_->CreateDirIfMissing(archivalPath);
+ }
+ return Status::OK();
+}
+
+void DBImpl::PrintStatistics() {
+ auto dbstats = immutable_db_options_.statistics.get();
+ if (dbstats) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "STATISTICS:\n %s",
+ dbstats->ToString().c_str());
+ }
+}
+
+void DBImpl::StartTimedTasks() {
+ unsigned int stats_dump_period_sec = 0;
+ unsigned int stats_persist_period_sec = 0;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ stats_dump_period_sec = mutable_db_options_.stats_dump_period_sec;
+ if (stats_dump_period_sec > 0) {
+ if (!thread_dump_stats_) {
+ thread_dump_stats_.reset(new ROCKSDB_NAMESPACE::RepeatableThread(
+ [this]() { DBImpl::DumpStats(); }, "dump_st", env_,
+ static_cast<uint64_t>(stats_dump_period_sec) * kMicrosInSecond));
+ }
+ }
+ stats_persist_period_sec = mutable_db_options_.stats_persist_period_sec;
+ if (stats_persist_period_sec > 0) {
+ if (!thread_persist_stats_) {
+ thread_persist_stats_.reset(new ROCKSDB_NAMESPACE::RepeatableThread(
+ [this]() { DBImpl::PersistStats(); }, "pst_st", env_,
+ static_cast<uint64_t>(stats_persist_period_sec) * kMicrosInSecond));
+ }
+ }
+ }
+}
+
+// esitmate the total size of stats_history_
+size_t DBImpl::EstimateInMemoryStatsHistorySize() const {
+ size_t size_total =
+ sizeof(std::map<uint64_t, std::map<std::string, uint64_t>>);
+ if (stats_history_.size() == 0) return size_total;
+ size_t size_per_slice =
+ sizeof(uint64_t) + sizeof(std::map<std::string, uint64_t>);
+ // non-empty map, stats_history_.begin() guaranteed to exist
+ std::map<std::string, uint64_t> sample_slice(stats_history_.begin()->second);
+ for (const auto& pairs : sample_slice) {
+ size_per_slice +=
+ pairs.first.capacity() + sizeof(pairs.first) + sizeof(pairs.second);
+ }
+ size_total = size_per_slice * stats_history_.size();
+ return size_total;
+}
+
+void DBImpl::PersistStats() {
+ TEST_SYNC_POINT("DBImpl::PersistStats:Entry");
+#ifndef ROCKSDB_LITE
+ if (shutdown_initiated_) {
+ return;
+ }
+ uint64_t now_seconds = env_->NowMicros() / kMicrosInSecond;
+ Statistics* statistics = immutable_db_options_.statistics.get();
+ if (!statistics) {
+ return;
+ }
+ size_t stats_history_size_limit = 0;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ stats_history_size_limit = mutable_db_options_.stats_history_buffer_size;
+ }
+
+ std::map<std::string, uint64_t> stats_map;
+ if (!statistics->getTickerMap(&stats_map)) {
+ return;
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "------- PERSISTING STATS -------");
+
+ if (immutable_db_options_.persist_stats_to_disk) {
+ WriteBatch batch;
+ if (stats_slice_initialized_) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Reading %" ROCKSDB_PRIszt " stats from statistics\n",
+ stats_slice_.size());
+ for (const auto& stat : stats_map) {
+ char key[100];
+ int length =
+ EncodePersistentStatsKey(now_seconds, stat.first, 100, key);
+ // calculate the delta from last time
+ if (stats_slice_.find(stat.first) != stats_slice_.end()) {
+ uint64_t delta = stat.second - stats_slice_[stat.first];
+ batch.Put(persist_stats_cf_handle_, Slice(key, std::min(100, length)),
+ ToString(delta));
+ }
+ }
+ }
+ stats_slice_initialized_ = true;
+ std::swap(stats_slice_, stats_map);
+ WriteOptions wo;
+ wo.low_pri = true;
+ wo.no_slowdown = true;
+ wo.sync = false;
+ Status s = Write(wo, &batch);
+ if (!s.ok()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Writing to persistent stats CF failed -- %s",
+ s.ToString().c_str());
+ } else {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Writing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
+ " to persistent stats CF succeeded",
+ stats_slice_.size(), now_seconds);
+ }
+ // TODO(Zhongyi): add purging for persisted data
+ } else {
+ InstrumentedMutexLock l(&stats_history_mutex_);
+ // calculate the delta from last time
+ if (stats_slice_initialized_) {
+ std::map<std::string, uint64_t> stats_delta;
+ for (const auto& stat : stats_map) {
+ if (stats_slice_.find(stat.first) != stats_slice_.end()) {
+ stats_delta[stat.first] = stat.second - stats_slice_[stat.first];
+ }
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Storing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
+ " to in-memory stats history",
+ stats_slice_.size(), now_seconds);
+ stats_history_[now_seconds] = stats_delta;
+ }
+ stats_slice_initialized_ = true;
+ std::swap(stats_slice_, stats_map);
+ TEST_SYNC_POINT("DBImpl::PersistStats:StatsCopied");
+
+ // delete older stats snapshots to control memory consumption
+ size_t stats_history_size = EstimateInMemoryStatsHistorySize();
+ bool purge_needed = stats_history_size > stats_history_size_limit;
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[Pre-GC] In-memory stats history size: %" ROCKSDB_PRIszt
+ " bytes, slice count: %" ROCKSDB_PRIszt,
+ stats_history_size, stats_history_.size());
+ while (purge_needed && !stats_history_.empty()) {
+ stats_history_.erase(stats_history_.begin());
+ purge_needed =
+ EstimateInMemoryStatsHistorySize() > stats_history_size_limit;
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[Post-GC] In-memory stats history size: %" ROCKSDB_PRIszt
+ " bytes, slice count: %" ROCKSDB_PRIszt,
+ stats_history_size, stats_history_.size());
+ }
+#endif // !ROCKSDB_LITE
+}
+
+bool DBImpl::FindStatsByTime(uint64_t start_time, uint64_t end_time,
+ uint64_t* new_time,
+ std::map<std::string, uint64_t>* stats_map) {
+ assert(new_time);
+ assert(stats_map);
+ if (!new_time || !stats_map) return false;
+ // lock when search for start_time
+ {
+ InstrumentedMutexLock l(&stats_history_mutex_);
+ auto it = stats_history_.lower_bound(start_time);
+ if (it != stats_history_.end() && it->first < end_time) {
+ // make a copy for timestamp and stats_map
+ *new_time = it->first;
+ *stats_map = it->second;
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+Status DBImpl::GetStatsHistory(
+ uint64_t start_time, uint64_t end_time,
+ std::unique_ptr<StatsHistoryIterator>* stats_iterator) {
+ if (!stats_iterator) {
+ return Status::InvalidArgument("stats_iterator not preallocated.");
+ }
+ if (immutable_db_options_.persist_stats_to_disk) {
+ stats_iterator->reset(
+ new PersistentStatsHistoryIterator(start_time, end_time, this));
+ } else {
+ stats_iterator->reset(
+ new InMemoryStatsHistoryIterator(start_time, end_time, this));
+ }
+ return (*stats_iterator)->status();
+}
+
+void DBImpl::DumpStats() {
+ TEST_SYNC_POINT("DBImpl::DumpStats:1");
+#ifndef ROCKSDB_LITE
+ const DBPropertyInfo* cf_property_info =
+ GetPropertyInfo(DB::Properties::kCFStats);
+ assert(cf_property_info != nullptr);
+ const DBPropertyInfo* db_property_info =
+ GetPropertyInfo(DB::Properties::kDBStats);
+ assert(db_property_info != nullptr);
+
+ std::string stats;
+ if (shutdown_initiated_) {
+ return;
+ }
+ {
+ InstrumentedMutexLock l(&mutex_);
+ default_cf_internal_stats_->GetStringProperty(
+ *db_property_info, DB::Properties::kDBStats, &stats);
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->initialized()) {
+ cfd->internal_stats()->GetStringProperty(
+ *cf_property_info, DB::Properties::kCFStatsNoFileHistogram, &stats);
+ }
+ }
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->initialized()) {
+ cfd->internal_stats()->GetStringProperty(
+ *cf_property_info, DB::Properties::kCFFileHistogram, &stats);
+ }
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::DumpStats:2");
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "------- DUMPING STATS -------");
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", stats.c_str());
+ if (immutable_db_options_.dump_malloc_stats) {
+ stats.clear();
+ DumpMallocStats(&stats);
+ if (!stats.empty()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "------- Malloc STATS -------");
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", stats.c_str());
+ }
+ }
+#endif // !ROCKSDB_LITE
+
+ PrintStatistics();
+}
+
+Status DBImpl::TablesRangeTombstoneSummary(ColumnFamilyHandle* column_family,
+ int max_entries_to_print,
+ std::string* out_str) {
+ auto* cfh =
+ static_cast_with_check<ColumnFamilyHandleImpl, ColumnFamilyHandle>(
+ column_family);
+ ColumnFamilyData* cfd = cfh->cfd();
+
+ SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
+ Version* version = super_version->current;
+
+ Status s =
+ version->TablesRangeTombstoneSummary(max_entries_to_print, out_str);
+
+ CleanupSuperVersion(super_version);
+ return s;
+}
+
+void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) {
+ if (!job_context->logs_to_free.empty()) {
+ for (auto l : job_context->logs_to_free) {
+ AddToLogsToFreeQueue(l);
+ }
+ job_context->logs_to_free.clear();
+ }
+}
+
+Directory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
+ assert(cfd);
+ Directory* ret_dir = cfd->GetDataDir(path_id);
+ if (ret_dir == nullptr) {
+ return directories_.GetDataDir(path_id);
+ }
+ return ret_dir;
+}
+
+Status DBImpl::SetOptions(
+ ColumnFamilyHandle* column_family,
+ const std::unordered_map<std::string, std::string>& options_map) {
+#ifdef ROCKSDB_LITE
+ (void)column_family;
+ (void)options_map;
+ return Status::NotSupported("Not supported in ROCKSDB LITE");
+#else
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ if (options_map.empty()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "SetOptions() on column family [%s], empty input",
+ cfd->GetName().c_str());
+ return Status::InvalidArgument("empty input");
+ }
+
+ MutableCFOptions new_options;
+ Status s;
+ Status persist_options_status;
+ SuperVersionContext sv_context(/* create_superversion */ true);
+ {
+ auto db_options = GetDBOptions();
+ InstrumentedMutexLock l(&mutex_);
+ s = cfd->SetOptions(db_options, options_map);
+ if (s.ok()) {
+ new_options = *cfd->GetLatestMutableCFOptions();
+ // Append new version to recompute compaction score.
+ VersionEdit dummy_edit;
+ versions_->LogAndApply(cfd, new_options, &dummy_edit, &mutex_,
+ directories_.GetDbDir());
+ // Trigger possible flush/compactions. This has to be before we persist
+ // options to file, otherwise there will be a deadlock with writer
+ // thread.
+ InstallSuperVersionAndScheduleWork(cfd, &sv_context, new_options);
+
+ persist_options_status = WriteOptionsFile(
+ false /*need_mutex_lock*/, true /*need_enter_write_thread*/);
+ bg_cv_.SignalAll();
+ }
+ }
+ sv_context.Clean();
+
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "SetOptions() on column family [%s], inputs:", cfd->GetName().c_str());
+ for (const auto& o : options_map) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n", o.first.c_str(),
+ o.second.c_str());
+ }
+ if (s.ok()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] SetOptions() succeeded", cfd->GetName().c_str());
+ new_options.Dump(immutable_db_options_.info_log.get());
+ if (!persist_options_status.ok()) {
+ s = persist_options_status;
+ }
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log, "[%s] SetOptions() failed",
+ cfd->GetName().c_str());
+ }
+ LogFlush(immutable_db_options_.info_log);
+ return s;
+#endif // ROCKSDB_LITE
+}
+
+Status DBImpl::SetDBOptions(
+ const std::unordered_map<std::string, std::string>& options_map) {
+#ifdef ROCKSDB_LITE
+ (void)options_map;
+ return Status::NotSupported("Not supported in ROCKSDB LITE");
+#else
+ if (options_map.empty()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "SetDBOptions(), empty input.");
+ return Status::InvalidArgument("empty input");
+ }
+
+ MutableDBOptions new_options;
+ Status s;
+ Status persist_options_status;
+ bool wal_changed = false;
+ WriteContext write_context;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ s = GetMutableDBOptionsFromStrings(mutable_db_options_, options_map,
+ &new_options);
+ if (new_options.bytes_per_sync == 0) {
+ new_options.bytes_per_sync = 1024 * 1024;
+ }
+ DBOptions new_db_options =
+ BuildDBOptions(immutable_db_options_, new_options);
+ if (s.ok()) {
+ s = ValidateOptions(new_db_options);
+ }
+ if (s.ok()) {
+ for (auto c : *versions_->GetColumnFamilySet()) {
+ if (!c->IsDropped()) {
+ auto cf_options = c->GetLatestCFOptions();
+ s = ColumnFamilyData::ValidateOptions(new_db_options, cf_options);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ }
+ if (s.ok()) {
+ const BGJobLimits current_bg_job_limits =
+ GetBGJobLimits(immutable_db_options_.max_background_flushes,
+ mutable_db_options_.max_background_compactions,
+ mutable_db_options_.max_background_jobs,
+ /* parallelize_compactions */ true);
+ const BGJobLimits new_bg_job_limits = GetBGJobLimits(
+ immutable_db_options_.max_background_flushes,
+ new_options.max_background_compactions,
+ new_options.max_background_jobs, /* parallelize_compactions */ true);
+
+ const bool max_flushes_increased =
+ new_bg_job_limits.max_flushes > current_bg_job_limits.max_flushes;
+ const bool max_compactions_increased =
+ new_bg_job_limits.max_compactions >
+ current_bg_job_limits.max_compactions;
+
+ if (max_flushes_increased || max_compactions_increased) {
+ if (max_flushes_increased) {
+ env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_flushes,
+ Env::Priority::HIGH);
+ }
+
+ if (max_compactions_increased) {
+ env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_compactions,
+ Env::Priority::LOW);
+ }
+
+ MaybeScheduleFlushOrCompaction();
+ }
+
+ if (new_options.stats_dump_period_sec !=
+ mutable_db_options_.stats_dump_period_sec) {
+ if (thread_dump_stats_) {
+ mutex_.Unlock();
+ thread_dump_stats_->cancel();
+ mutex_.Lock();
+ }
+ if (new_options.stats_dump_period_sec > 0) {
+ thread_dump_stats_.reset(new ROCKSDB_NAMESPACE::RepeatableThread(
+ [this]() { DBImpl::DumpStats(); }, "dump_st", env_,
+ static_cast<uint64_t>(new_options.stats_dump_period_sec) *
+ kMicrosInSecond));
+ } else {
+ thread_dump_stats_.reset();
+ }
+ }
+ if (new_options.stats_persist_period_sec !=
+ mutable_db_options_.stats_persist_period_sec) {
+ if (thread_persist_stats_) {
+ mutex_.Unlock();
+ thread_persist_stats_->cancel();
+ mutex_.Lock();
+ }
+ if (new_options.stats_persist_period_sec > 0) {
+ thread_persist_stats_.reset(new ROCKSDB_NAMESPACE::RepeatableThread(
+ [this]() { DBImpl::PersistStats(); }, "pst_st", env_,
+ static_cast<uint64_t>(new_options.stats_persist_period_sec) *
+ kMicrosInSecond));
+ } else {
+ thread_persist_stats_.reset();
+ }
+ }
+ write_controller_.set_max_delayed_write_rate(
+ new_options.delayed_write_rate);
+ table_cache_.get()->SetCapacity(new_options.max_open_files == -1
+ ? TableCache::kInfiniteCapacity
+ : new_options.max_open_files - 10);
+ wal_changed = mutable_db_options_.wal_bytes_per_sync !=
+ new_options.wal_bytes_per_sync;
+ mutable_db_options_ = new_options;
+ file_options_for_compaction_ = FileOptions(new_db_options);
+ file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
+ file_options_for_compaction_, immutable_db_options_);
+ versions_->ChangeFileOptions(mutable_db_options_);
+ //TODO(xiez): clarify why apply optimize for read to write options
+ file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
+ file_options_for_compaction_, immutable_db_options_);
+ file_options_for_compaction_.compaction_readahead_size =
+ mutable_db_options_.compaction_readahead_size;
+ WriteThread::Writer w;
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ if (total_log_size_ > GetMaxTotalWalSize() || wal_changed) {
+ Status purge_wal_status = SwitchWAL(&write_context);
+ if (!purge_wal_status.ok()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "Unable to purge WAL files in SetDBOptions() -- %s",
+ purge_wal_status.ToString().c_str());
+ }
+ }
+ persist_options_status = WriteOptionsFile(
+ false /*need_mutex_lock*/, false /*need_enter_write_thread*/);
+ write_thread_.ExitUnbatched(&w);
+ }
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "SetDBOptions(), inputs:");
+ for (const auto& o : options_map) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n", o.first.c_str(),
+ o.second.c_str());
+ }
+ if (s.ok()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "SetDBOptions() succeeded");
+ new_options.Dump(immutable_db_options_.info_log.get());
+ if (!persist_options_status.ok()) {
+ if (immutable_db_options_.fail_if_options_file_error) {
+ s = Status::IOError(
+ "SetDBOptions() succeeded, but unable to persist options",
+ persist_options_status.ToString());
+ }
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "Unable to persist options in SetDBOptions() -- %s",
+ persist_options_status.ToString().c_str());
+ }
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log, "SetDBOptions failed");
+ }
+ LogFlush(immutable_db_options_.info_log);
+ return s;
+#endif // ROCKSDB_LITE
+}
+
+// return the same level if it cannot be moved
+int DBImpl::FindMinimumEmptyLevelFitting(
+ ColumnFamilyData* cfd, const MutableCFOptions& /*mutable_cf_options*/,
+ int level) {
+ mutex_.AssertHeld();
+ const auto* vstorage = cfd->current()->storage_info();
+ int minimum_level = level;
+ for (int i = level - 1; i > 0; --i) {
+ // stop if level i is not empty
+ if (vstorage->NumLevelFiles(i) > 0) break;
+ // stop if level i is too small (cannot fit the level files)
+ if (vstorage->MaxBytesForLevel(i) < vstorage->NumLevelBytes(level)) {
+ break;
+ }
+
+ minimum_level = i;
+ }
+ return minimum_level;
+}
+
+Status DBImpl::FlushWAL(bool sync) {
+ if (manual_wal_flush_) {
+ Status s;
+ {
+ // We need to lock log_write_mutex_ since logs_ might change concurrently
+ InstrumentedMutexLock wl(&log_write_mutex_);
+ log::Writer* cur_log_writer = logs_.back().writer;
+ s = cur_log_writer->WriteBuffer();
+ }
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s",
+ s.ToString().c_str());
+ // In case there is a fs error we should set it globally to prevent the
+ // future writes
+ WriteStatusCheck(s);
+ // whether sync or not, we should abort the rest of function upon error
+ return s;
+ }
+ if (!sync) {
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=false");
+ return s;
+ }
+ }
+ if (!sync) {
+ return Status::OK();
+ }
+ // sync = true
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=true");
+ return SyncWAL();
+}
+
+Status DBImpl::SyncWAL() {
+ autovector<log::Writer*, 1> logs_to_sync;
+ bool need_log_dir_sync;
+ uint64_t current_log_number;
+
+ {
+ InstrumentedMutexLock l(&mutex_);
+ assert(!logs_.empty());
+
+ // This SyncWAL() call only cares about logs up to this number.
+ current_log_number = logfile_number_;
+
+ while (logs_.front().number <= current_log_number &&
+ logs_.front().getting_synced) {
+ log_sync_cv_.Wait();
+ }
+ // First check that logs are safe to sync in background.
+ for (auto it = logs_.begin();
+ it != logs_.end() && it->number <= current_log_number; ++it) {
+ if (!it->writer->file()->writable_file()->IsSyncThreadSafe()) {
+ return Status::NotSupported(
+ "SyncWAL() is not supported for this implementation of WAL file",
+ immutable_db_options_.allow_mmap_writes
+ ? "try setting Options::allow_mmap_writes to false"
+ : Slice());
+ }
+ }
+ for (auto it = logs_.begin();
+ it != logs_.end() && it->number <= current_log_number; ++it) {
+ auto& log = *it;
+ assert(!log.getting_synced);
+ log.getting_synced = true;
+ logs_to_sync.push_back(log.writer);
+ }
+
+ need_log_dir_sync = !log_dir_synced_;
+ }
+
+ TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:1");
+ RecordTick(stats_, WAL_FILE_SYNCED);
+ Status status;
+ for (log::Writer* log : logs_to_sync) {
+ status = log->file()->SyncWithoutFlush(immutable_db_options_.use_fsync);
+ if (!status.ok()) {
+ break;
+ }
+ }
+ if (status.ok() && need_log_dir_sync) {
+ status = directories_.GetWalDir()->Fsync();
+ }
+ TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:2");
+
+ TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:1");
+ {
+ InstrumentedMutexLock l(&mutex_);
+ MarkLogsSynced(current_log_number, need_log_dir_sync, status);
+ }
+ TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:2");
+
+ return status;
+}
+
+Status DBImpl::LockWAL() {
+ log_write_mutex_.Lock();
+ auto cur_log_writer = logs_.back().writer;
+ auto status = cur_log_writer->WriteBuffer();
+ if (!status.ok()) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s",
+ status.ToString().c_str());
+ // In case there is a fs error we should set it globally to prevent the
+ // future writes
+ WriteStatusCheck(status);
+ }
+ return status;
+}
+
+Status DBImpl::UnlockWAL() {
+ log_write_mutex_.Unlock();
+ return Status::OK();
+}
+
+void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
+ const Status& status) {
+ mutex_.AssertHeld();
+ if (synced_dir && logfile_number_ == up_to && status.ok()) {
+ log_dir_synced_ = true;
+ }
+ for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
+ auto& log = *it;
+ assert(log.getting_synced);
+ if (status.ok() && logs_.size() > 1) {
+ logs_to_free_.push_back(log.ReleaseWriter());
+ // To modify logs_ both mutex_ and log_write_mutex_ must be held
+ InstrumentedMutexLock l(&log_write_mutex_);
+ it = logs_.erase(it);
+ } else {
+ log.getting_synced = false;
+ ++it;
+ }
+ }
+ assert(!status.ok() || logs_.empty() || logs_[0].number > up_to ||
+ (logs_.size() == 1 && !logs_[0].getting_synced));
+ log_sync_cv_.SignalAll();
+}
+
+SequenceNumber DBImpl::GetLatestSequenceNumber() const {
+ return versions_->LastSequence();
+}
+
+void DBImpl::SetLastPublishedSequence(SequenceNumber seq) {
+ versions_->SetLastPublishedSequence(seq);
+}
+
+bool DBImpl::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) {
+ if (seqnum > preserve_deletes_seqnum_.load()) {
+ preserve_deletes_seqnum_.store(seqnum);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+InternalIterator* DBImpl::NewInternalIterator(
+ Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence,
+ ColumnFamilyHandle* column_family) {
+ ColumnFamilyData* cfd;
+ if (column_family == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ } else {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ cfd = cfh->cfd();
+ }
+
+ mutex_.Lock();
+ SuperVersion* super_version = cfd->GetSuperVersion()->Ref();
+ mutex_.Unlock();
+ ReadOptions roptions;
+ return NewInternalIterator(roptions, cfd, super_version, arena, range_del_agg,
+ sequence);
+}
+
+void DBImpl::SchedulePurge() {
+ mutex_.AssertHeld();
+ assert(opened_successfully_);
+
+ // Purge operations are put into High priority queue
+ bg_purge_scheduled_++;
+ env_->Schedule(&DBImpl::BGWorkPurge, this, Env::Priority::HIGH, nullptr);
+}
+
+void DBImpl::BackgroundCallPurge() {
+ mutex_.Lock();
+
+ while (!logs_to_free_queue_.empty()) {
+ assert(!logs_to_free_queue_.empty());
+ log::Writer* log_writer = *(logs_to_free_queue_.begin());
+ logs_to_free_queue_.pop_front();
+ mutex_.Unlock();
+ delete log_writer;
+ mutex_.Lock();
+ }
+ while (!superversions_to_free_queue_.empty()) {
+ assert(!superversions_to_free_queue_.empty());
+ SuperVersion* sv = superversions_to_free_queue_.front();
+ superversions_to_free_queue_.pop_front();
+ mutex_.Unlock();
+ delete sv;
+ mutex_.Lock();
+ }
+
+ // Can't use iterator to go over purge_files_ because inside the loop we're
+ // unlocking the mutex that protects purge_files_.
+ while (!purge_files_.empty()) {
+ auto it = purge_files_.begin();
+ // Need to make a copy of the PurgeFilesInfo before unlocking the mutex.
+ PurgeFileInfo purge_file = it->second;
+
+ const std::string& fname = purge_file.fname;
+ const std::string& dir_to_sync = purge_file.dir_to_sync;
+ FileType type = purge_file.type;
+ uint64_t number = purge_file.number;
+ int job_id = purge_file.job_id;
+
+ purge_files_.erase(it);
+
+ mutex_.Unlock();
+ DeleteObsoleteFileImpl(job_id, fname, dir_to_sync, type, number);
+ mutex_.Lock();
+ }
+
+ bg_purge_scheduled_--;
+
+ bg_cv_.SignalAll();
+ // IMPORTANT:there should be no code after calling SignalAll. This call may
+ // signal the DB destructor that it's OK to proceed with destruction. In
+ // that case, all DB variables will be dealloacated and referencing them
+ // will cause trouble.
+ mutex_.Unlock();
+}
+
+namespace {
+struct IterState {
+ IterState(DBImpl* _db, InstrumentedMutex* _mu, SuperVersion* _super_version,
+ bool _background_purge)
+ : db(_db),
+ mu(_mu),
+ super_version(_super_version),
+ background_purge(_background_purge) {}
+
+ DBImpl* db;
+ InstrumentedMutex* mu;
+ SuperVersion* super_version;
+ bool background_purge;
+};
+
+static void CleanupIteratorState(void* arg1, void* /*arg2*/) {
+ IterState* state = reinterpret_cast<IterState*>(arg1);
+
+ if (state->super_version->Unref()) {
+ // Job id == 0 means that this is not our background process, but rather
+ // user thread
+ JobContext job_context(0);
+
+ state->mu->Lock();
+ state->super_version->Cleanup();
+ state->db->FindObsoleteFiles(&job_context, false, true);
+ if (state->background_purge) {
+ state->db->ScheduleBgLogWriterClose(&job_context);
+ state->db->AddSuperVersionsToFreeQueue(state->super_version);
+ state->db->SchedulePurge();
+ }
+ state->mu->Unlock();
+
+ if (!state->background_purge) {
+ delete state->super_version;
+ }
+ if (job_context.HaveSomethingToDelete()) {
+ if (state->background_purge) {
+ // PurgeObsoleteFiles here does not delete files. Instead, it adds the
+ // files to be deleted to a job queue, and deletes it in a separate
+ // background thread.
+ state->db->PurgeObsoleteFiles(job_context, true /* schedule only */);
+ state->mu->Lock();
+ state->db->SchedulePurge();
+ state->mu->Unlock();
+ } else {
+ state->db->PurgeObsoleteFiles(job_context);
+ }
+ }
+ job_context.Clean();
+ }
+
+ delete state;
+}
+} // namespace
+
+InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
+ ColumnFamilyData* cfd,
+ SuperVersion* super_version,
+ Arena* arena,
+ RangeDelAggregator* range_del_agg,
+ SequenceNumber sequence) {
+ InternalIterator* internal_iter;
+ assert(arena != nullptr);
+ assert(range_del_agg != nullptr);
+ // Need to create internal iterator from the arena.
+ MergeIteratorBuilder merge_iter_builder(
+ &cfd->internal_comparator(), arena,
+ !read_options.total_order_seek &&
+ super_version->mutable_cf_options.prefix_extractor != nullptr);
+ // Collect iterator for mutable mem
+ merge_iter_builder.AddIterator(
+ super_version->mem->NewIterator(read_options, arena));
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter;
+ Status s;
+ if (!read_options.ignore_range_deletions) {
+ range_del_iter.reset(
+ super_version->mem->NewRangeTombstoneIterator(read_options, sequence));
+ range_del_agg->AddTombstones(std::move(range_del_iter));
+ }
+ // Collect all needed child iterators for immutable memtables
+ if (s.ok()) {
+ super_version->imm->AddIterators(read_options, &merge_iter_builder);
+ if (!read_options.ignore_range_deletions) {
+ s = super_version->imm->AddRangeTombstoneIterators(read_options, arena,
+ range_del_agg);
+ }
+ }
+ TEST_SYNC_POINT_CALLBACK("DBImpl::NewInternalIterator:StatusCallback", &s);
+ if (s.ok()) {
+ // Collect iterators for files in L0 - Ln
+ if (read_options.read_tier != kMemtableTier) {
+ super_version->current->AddIterators(read_options, file_options_,
+ &merge_iter_builder, range_del_agg);
+ }
+ internal_iter = merge_iter_builder.Finish();
+ IterState* cleanup =
+ new IterState(this, &mutex_, super_version,
+ read_options.background_purge_on_iterator_cleanup ||
+ immutable_db_options_.avoid_unnecessary_blocking_io);
+ internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
+
+ return internal_iter;
+ } else {
+ CleanupSuperVersion(super_version);
+ }
+ return NewErrorInternalIterator<Slice>(s, arena);
+}
+
+ColumnFamilyHandle* DBImpl::DefaultColumnFamily() const {
+ return default_cf_handle_;
+}
+
+ColumnFamilyHandle* DBImpl::PersistentStatsColumnFamily() const {
+ return persist_stats_cf_handle_;
+}
+
+Status DBImpl::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) {
+ GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.value = value;
+ return GetImpl(read_options, key, get_impl_options);
+}
+
+Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
+ GetImplOptions get_impl_options) {
+ assert(get_impl_options.value != nullptr ||
+ get_impl_options.merge_operands != nullptr);
+ PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
+ StopWatch sw(env_, stats_, DB_GET);
+ PERF_TIMER_GUARD(get_snapshot_time);
+
+ auto cfh =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(get_impl_options.column_family);
+ auto cfd = cfh->cfd();
+
+ if (tracer_) {
+ // TODO: This mutex should be removed later, to improve performance when
+ // tracing is enabled.
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ tracer_->Get(get_impl_options.column_family, key);
+ }
+ }
+
+ // Acquire SuperVersion
+ SuperVersion* sv = GetAndRefSuperVersion(cfd);
+
+ TEST_SYNC_POINT("DBImpl::GetImpl:1");
+ TEST_SYNC_POINT("DBImpl::GetImpl:2");
+
+ SequenceNumber snapshot;
+ if (read_options.snapshot != nullptr) {
+ if (get_impl_options.callback) {
+ // Already calculated based on read_options.snapshot
+ snapshot = get_impl_options.callback->max_visible_seq();
+ } else {
+ snapshot =
+ reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
+ }
+ } else {
+ // Note that the snapshot is assigned AFTER referencing the super
+ // version because otherwise a flush happening in between may compact away
+ // data for the snapshot, so the reader would see neither data that was be
+ // visible to the snapshot before compaction nor the newer data inserted
+ // afterwards.
+ snapshot = last_seq_same_as_publish_seq_
+ ? versions_->LastSequence()
+ : versions_->LastPublishedSequence();
+ if (get_impl_options.callback) {
+ // The unprep_seqs are not published for write unprepared, so it could be
+ // that max_visible_seq is larger. Seek to the std::max of the two.
+ // However, we still want our callback to contain the actual snapshot so
+ // that it can do the correct visibility filtering.
+ get_impl_options.callback->Refresh(snapshot);
+
+ // Internally, WriteUnpreparedTxnReadCallback::Refresh would set
+ // max_visible_seq = max(max_visible_seq, snapshot)
+ //
+ // Currently, the commented out assert is broken by
+ // InvalidSnapshotReadCallback, but if write unprepared recovery followed
+ // the regular transaction flow, then this special read callback would not
+ // be needed.
+ //
+ // assert(callback->max_visible_seq() >= snapshot);
+ snapshot = get_impl_options.callback->max_visible_seq();
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::GetImpl:3");
+ TEST_SYNC_POINT("DBImpl::GetImpl:4");
+
+ // Prepare to store a list of merge operations if merge occurs.
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq = 0;
+
+ Status s;
+ // First look in the memtable, then in the immutable memtable (if any).
+ // s is both in/out. When in, s could either be OK or MergeInProgress.
+ // merge_operands will contain the sequence of merges in the latter case.
+ LookupKey lkey(key, snapshot, read_options.timestamp);
+ PERF_TIMER_STOP(get_snapshot_time);
+
+ bool skip_memtable = (read_options.read_tier == kPersistedTier &&
+ has_unpersisted_data_.load(std::memory_order_relaxed));
+ bool done = false;
+ if (!skip_memtable) {
+ // Get value associated with key
+ if (get_impl_options.get_value) {
+ if (sv->mem->Get(lkey, get_impl_options.value->GetSelf(), &s,
+ &merge_context, &max_covering_tombstone_seq,
+ read_options, get_impl_options.callback,
+ get_impl_options.is_blob_index)) {
+ done = true;
+ get_impl_options.value->PinSelf();
+ RecordTick(stats_, MEMTABLE_HIT);
+ } else if ((s.ok() || s.IsMergeInProgress()) &&
+ sv->imm->Get(lkey, get_impl_options.value->GetSelf(), &s,
+ &merge_context, &max_covering_tombstone_seq,
+ read_options, get_impl_options.callback,
+ get_impl_options.is_blob_index)) {
+ done = true;
+ get_impl_options.value->PinSelf();
+ RecordTick(stats_, MEMTABLE_HIT);
+ }
+ } else {
+ // Get Merge Operands associated with key, Merge Operands should not be
+ // merged and raw values should be returned to the user.
+ if (sv->mem->Get(lkey, nullptr, &s, &merge_context,
+ &max_covering_tombstone_seq, read_options, nullptr,
+ nullptr, false)) {
+ done = true;
+ RecordTick(stats_, MEMTABLE_HIT);
+ } else if ((s.ok() || s.IsMergeInProgress()) &&
+ sv->imm->GetMergeOperands(lkey, &s, &merge_context,
+ &max_covering_tombstone_seq,
+ read_options)) {
+ done = true;
+ RecordTick(stats_, MEMTABLE_HIT);
+ }
+ }
+ if (!done && !s.ok() && !s.IsMergeInProgress()) {
+ ReturnAndCleanupSuperVersion(cfd, sv);
+ return s;
+ }
+ }
+ if (!done) {
+ PERF_TIMER_GUARD(get_from_output_files_time);
+ sv->current->Get(
+ read_options, lkey, get_impl_options.value, &s, &merge_context,
+ &max_covering_tombstone_seq,
+ get_impl_options.get_value ? get_impl_options.value_found : nullptr,
+ nullptr, nullptr,
+ get_impl_options.get_value ? get_impl_options.callback : nullptr,
+ get_impl_options.get_value ? get_impl_options.is_blob_index : nullptr,
+ get_impl_options.get_value);
+ RecordTick(stats_, MEMTABLE_MISS);
+ }
+
+ {
+ PERF_TIMER_GUARD(get_post_process_time);
+
+ ReturnAndCleanupSuperVersion(cfd, sv);
+
+ RecordTick(stats_, NUMBER_KEYS_READ);
+ size_t size = 0;
+ if (s.ok()) {
+ if (get_impl_options.get_value) {
+ size = get_impl_options.value->size();
+ } else {
+ // Return all merge operands for get_impl_options.key
+ *get_impl_options.number_of_operands =
+ static_cast<int>(merge_context.GetNumOperands());
+ if (*get_impl_options.number_of_operands >
+ get_impl_options.get_merge_operands_options
+ ->expected_max_number_of_operands) {
+ s = Status::Incomplete(
+ Status::SubCode::KMergeOperandsInsufficientCapacity);
+ } else {
+ for (const Slice& sl : merge_context.GetOperands()) {
+ size += sl.size();
+ get_impl_options.merge_operands->PinSelf(sl);
+ get_impl_options.merge_operands++;
+ }
+ }
+ }
+ RecordTick(stats_, BYTES_READ, size);
+ PERF_COUNTER_ADD(get_read_bytes, size);
+ }
+ RecordInHistogram(stats_, BYTES_PER_READ, size);
+ }
+ return s;
+}
+
+std::vector<Status> DBImpl::MultiGet(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
+ StopWatch sw(env_, stats_, DB_MULTIGET);
+ PERF_TIMER_GUARD(get_snapshot_time);
+
+ SequenceNumber consistent_seqnum;
+ ;
+
+ std::unordered_map<uint32_t, MultiGetColumnFamilyData> multiget_cf_data(
+ column_family.size());
+ for (auto cf : column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(cf);
+ auto cfd = cfh->cfd();
+ if (multiget_cf_data.find(cfd->GetID()) == multiget_cf_data.end()) {
+ multiget_cf_data.emplace(cfd->GetID(),
+ MultiGetColumnFamilyData(cfh, nullptr));
+ }
+ }
+
+ std::function<MultiGetColumnFamilyData*(
+ std::unordered_map<uint32_t, MultiGetColumnFamilyData>::iterator&)>
+ iter_deref_lambda =
+ [](std::unordered_map<uint32_t, MultiGetColumnFamilyData>::iterator&
+ cf_iter) { return &cf_iter->second; };
+
+ bool unref_only =
+ MultiCFSnapshot<std::unordered_map<uint32_t, MultiGetColumnFamilyData>>(
+ read_options, nullptr, iter_deref_lambda, &multiget_cf_data,
+ &consistent_seqnum);
+
+ // Contain a list of merge operations if merge occurs.
+ MergeContext merge_context;
+
+ // Note: this always resizes the values array
+ size_t num_keys = keys.size();
+ std::vector<Status> stat_list(num_keys);
+ values->resize(num_keys);
+
+ // Keep track of bytes that we read for statistics-recording later
+ uint64_t bytes_read = 0;
+ PERF_TIMER_STOP(get_snapshot_time);
+
+ // For each of the given keys, apply the entire "get" process as follows:
+ // First look in the memtable, then in the immutable memtable (if any).
+ // s is both in/out. When in, s could either be OK or MergeInProgress.
+ // merge_operands will contain the sequence of merges in the latter case.
+ size_t num_found = 0;
+ for (size_t i = 0; i < num_keys; ++i) {
+ merge_context.Clear();
+ Status& s = stat_list[i];
+ std::string* value = &(*values)[i];
+
+ LookupKey lkey(keys[i], consistent_seqnum);
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]);
+ SequenceNumber max_covering_tombstone_seq = 0;
+ auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID());
+ assert(mgd_iter != multiget_cf_data.end());
+ auto mgd = mgd_iter->second;
+ auto super_version = mgd.super_version;
+ bool skip_memtable =
+ (read_options.read_tier == kPersistedTier &&
+ has_unpersisted_data_.load(std::memory_order_relaxed));
+ bool done = false;
+ if (!skip_memtable) {
+ if (super_version->mem->Get(lkey, value, &s, &merge_context,
+ &max_covering_tombstone_seq, read_options)) {
+ done = true;
+ RecordTick(stats_, MEMTABLE_HIT);
+ } else if (super_version->imm->Get(lkey, value, &s, &merge_context,
+ &max_covering_tombstone_seq,
+ read_options)) {
+ done = true;
+ RecordTick(stats_, MEMTABLE_HIT);
+ }
+ }
+ if (!done) {
+ PinnableSlice pinnable_val;
+ PERF_TIMER_GUARD(get_from_output_files_time);
+ super_version->current->Get(read_options, lkey, &pinnable_val, &s,
+ &merge_context, &max_covering_tombstone_seq);
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ RecordTick(stats_, MEMTABLE_MISS);
+ }
+
+ if (s.ok()) {
+ bytes_read += value->size();
+ num_found++;
+ }
+ }
+
+ // Post processing (decrement reference counts and record statistics)
+ PERF_TIMER_GUARD(get_post_process_time);
+ autovector<SuperVersion*> superversions_to_delete;
+
+ for (auto mgd_iter : multiget_cf_data) {
+ auto mgd = mgd_iter.second;
+ if (!unref_only) {
+ ReturnAndCleanupSuperVersion(mgd.cfd, mgd.super_version);
+ } else {
+ mgd.cfd->GetSuperVersion()->Unref();
+ }
+ }
+ RecordTick(stats_, NUMBER_MULTIGET_CALLS);
+ RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
+ RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found);
+ RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
+ RecordInHistogram(stats_, BYTES_PER_MULTIGET, bytes_read);
+ PERF_COUNTER_ADD(multiget_read_bytes, bytes_read);
+ PERF_TIMER_STOP(get_post_process_time);
+
+ return stat_list;
+}
+
+template <class T>
+bool DBImpl::MultiCFSnapshot(
+ const ReadOptions& read_options, ReadCallback* callback,
+ std::function<MultiGetColumnFamilyData*(typename T::iterator&)>&
+ iter_deref_func,
+ T* cf_list, SequenceNumber* snapshot) {
+ PERF_TIMER_GUARD(get_snapshot_time);
+
+ bool last_try = false;
+ if (cf_list->size() == 1) {
+ // Fast path for a single column family. We can simply get the thread loca
+ // super version
+ auto cf_iter = cf_list->begin();
+ auto node = iter_deref_func(cf_iter);
+ node->super_version = GetAndRefSuperVersion(node->cfd);
+ if (read_options.snapshot != nullptr) {
+ // Note: In WritePrepared txns this is not necessary but not harmful
+ // either. Because prep_seq > snapshot => commit_seq > snapshot so if
+ // a snapshot is specified we should be fine with skipping seq numbers
+ // that are greater than that.
+ //
+ // In WriteUnprepared, we cannot set snapshot in the lookup key because we
+ // may skip uncommitted data that should be visible to the transaction for
+ // reading own writes.
+ *snapshot =
+ static_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
+ if (callback) {
+ *snapshot = std::max(*snapshot, callback->max_visible_seq());
+ }
+ } else {
+ // Since we get and reference the super version before getting
+ // the snapshot number, without a mutex protection, it is possible
+ // that a memtable switch happened in the middle and not all the
+ // data for this snapshot is available. But it will contain all
+ // the data available in the super version we have, which is also
+ // a valid snapshot to read from.
+ // We shouldn't get snapshot before finding and referencing the super
+ // version because a flush happening in between may compact away data for
+ // the snapshot, but the snapshot is earlier than the data overwriting it,
+ // so users may see wrong results.
+ *snapshot = last_seq_same_as_publish_seq_
+ ? versions_->LastSequence()
+ : versions_->LastPublishedSequence();
+ }
+ } else {
+ // If we end up with the same issue of memtable geting sealed during 2
+ // consecutive retries, it means the write rate is very high. In that case
+ // its probably ok to take the mutex on the 3rd try so we can succeed for
+ // sure
+ static const int num_retries = 3;
+ for (int i = 0; i < num_retries; ++i) {
+ last_try = (i == num_retries - 1);
+ bool retry = false;
+
+ if (i > 0) {
+ for (auto cf_iter = cf_list->begin(); cf_iter != cf_list->end();
+ ++cf_iter) {
+ auto node = iter_deref_func(cf_iter);
+ SuperVersion* super_version = node->super_version;
+ ColumnFamilyData* cfd = node->cfd;
+ if (super_version != nullptr) {
+ ReturnAndCleanupSuperVersion(cfd, super_version);
+ }
+ node->super_version = nullptr;
+ }
+ }
+ if (read_options.snapshot == nullptr) {
+ if (last_try) {
+ TEST_SYNC_POINT("DBImpl::MultiGet::LastTry");
+ // We're close to max number of retries. For the last retry,
+ // acquire the lock so we're sure to succeed
+ mutex_.Lock();
+ }
+ *snapshot = last_seq_same_as_publish_seq_
+ ? versions_->LastSequence()
+ : versions_->LastPublishedSequence();
+ } else {
+ *snapshot = reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)
+ ->number_;
+ }
+ for (auto cf_iter = cf_list->begin(); cf_iter != cf_list->end();
+ ++cf_iter) {
+ auto node = iter_deref_func(cf_iter);
+ if (!last_try) {
+ node->super_version = GetAndRefSuperVersion(node->cfd);
+ } else {
+ node->super_version = node->cfd->GetSuperVersion()->Ref();
+ }
+ TEST_SYNC_POINT("DBImpl::MultiGet::AfterRefSV");
+ if (read_options.snapshot != nullptr || last_try) {
+ // If user passed a snapshot, then we don't care if a memtable is
+ // sealed or compaction happens because the snapshot would ensure
+ // that older key versions are kept around. If this is the last
+ // retry, then we have the lock so nothing bad can happen
+ continue;
+ }
+ // We could get the earliest sequence number for the whole list of
+ // memtables, which will include immutable memtables as well, but that
+ // might be tricky to maintain in case we decide, in future, to do
+ // memtable compaction.
+ if (!last_try) {
+ SequenceNumber seq =
+ node->super_version->mem->GetEarliestSequenceNumber();
+ if (seq > *snapshot) {
+ retry = true;
+ break;
+ }
+ }
+ }
+ if (!retry) {
+ if (last_try) {
+ mutex_.Unlock();
+ }
+ break;
+ }
+ }
+ }
+
+ // Keep track of bytes that we read for statistics-recording later
+ PERF_TIMER_STOP(get_snapshot_time);
+
+ return last_try;
+}
+
+void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
+ ColumnFamilyHandle** column_families, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input) {
+ if (num_keys == 0) {
+ return;
+ }
+ autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
+ sorted_keys.resize(num_keys);
+ for (size_t i = 0; i < num_keys; ++i) {
+ key_context.emplace_back(column_families[i], keys[i], &values[i],
+ &statuses[i]);
+ }
+ for (size_t i = 0; i < num_keys; ++i) {
+ sorted_keys[i] = &key_context[i];
+ }
+ PrepareMultiGetKeys(num_keys, sorted_input, &sorted_keys);
+
+ autovector<MultiGetColumnFamilyData, MultiGetContext::MAX_BATCH_SIZE>
+ multiget_cf_data;
+ size_t cf_start = 0;
+ ColumnFamilyHandle* cf = sorted_keys[0]->column_family;
+ for (size_t i = 0; i < num_keys; ++i) {
+ KeyContext* key_ctx = sorted_keys[i];
+ if (key_ctx->column_family != cf) {
+ multiget_cf_data.emplace_back(
+ MultiGetColumnFamilyData(cf, cf_start, i - cf_start, nullptr));
+ cf_start = i;
+ cf = key_ctx->column_family;
+ }
+ }
+ {
+ // multiget_cf_data.emplace_back(
+ // MultiGetColumnFamilyData(cf, cf_start, num_keys - cf_start, nullptr));
+ multiget_cf_data.emplace_back(cf, cf_start, num_keys - cf_start, nullptr);
+ }
+ std::function<MultiGetColumnFamilyData*(
+ autovector<MultiGetColumnFamilyData,
+ MultiGetContext::MAX_BATCH_SIZE>::iterator&)>
+ iter_deref_lambda =
+ [](autovector<MultiGetColumnFamilyData,
+ MultiGetContext::MAX_BATCH_SIZE>::iterator& cf_iter) {
+ return &(*cf_iter);
+ };
+
+ SequenceNumber consistent_seqnum;
+ bool unref_only = MultiCFSnapshot<
+ autovector<MultiGetColumnFamilyData, MultiGetContext::MAX_BATCH_SIZE>>(
+ read_options, nullptr, iter_deref_lambda, &multiget_cf_data,
+ &consistent_seqnum);
+
+ for (auto cf_iter = multiget_cf_data.begin();
+ cf_iter != multiget_cf_data.end(); ++cf_iter) {
+ MultiGetImpl(read_options, cf_iter->start, cf_iter->num_keys, &sorted_keys,
+ cf_iter->super_version, consistent_seqnum, nullptr, nullptr);
+ if (!unref_only) {
+ ReturnAndCleanupSuperVersion(cf_iter->cfd, cf_iter->super_version);
+ } else {
+ cf_iter->cfd->GetSuperVersion()->Unref();
+ }
+ }
+}
+
+namespace {
+// Order keys by CF ID, followed by key contents
+struct CompareKeyContext {
+ inline bool operator()(const KeyContext* lhs, const KeyContext* rhs) {
+ ColumnFamilyHandleImpl* cfh =
+ static_cast<ColumnFamilyHandleImpl*>(lhs->column_family);
+ uint32_t cfd_id1 = cfh->cfd()->GetID();
+ const Comparator* comparator = cfh->cfd()->user_comparator();
+ cfh = static_cast<ColumnFamilyHandleImpl*>(lhs->column_family);
+ uint32_t cfd_id2 = cfh->cfd()->GetID();
+
+ if (cfd_id1 < cfd_id2) {
+ return true;
+ } else if (cfd_id1 > cfd_id2) {
+ return false;
+ }
+
+ // Both keys are from the same column family
+ int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
+ if (cmp < 0) {
+ return true;
+ }
+ return false;
+ }
+};
+
+} // anonymous namespace
+
+void DBImpl::PrepareMultiGetKeys(
+ size_t num_keys, bool sorted_input,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
+#ifndef NDEBUG
+ if (sorted_input) {
+ for (size_t index = 0; index < sorted_keys->size(); ++index) {
+ if (index > 0) {
+ KeyContext* lhs = (*sorted_keys)[index - 1];
+ KeyContext* rhs = (*sorted_keys)[index];
+ ColumnFamilyHandleImpl* cfh =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(lhs->column_family);
+ uint32_t cfd_id1 = cfh->cfd()->GetID();
+ const Comparator* comparator = cfh->cfd()->user_comparator();
+ cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(lhs->column_family);
+ uint32_t cfd_id2 = cfh->cfd()->GetID();
+
+ assert(cfd_id1 <= cfd_id2);
+ if (cfd_id1 < cfd_id2) {
+ continue;
+ }
+
+ // Both keys are from the same column family
+ int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
+ assert(cmp <= 0);
+ }
+ index++;
+ }
+ }
+#endif
+ if (!sorted_input) {
+ CompareKeyContext sort_comparator;
+ std::sort(sorted_keys->begin(), sorted_keys->begin() + num_keys,
+ sort_comparator);
+ }
+}
+
+void DBImpl::MultiGet(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const size_t num_keys,
+ const Slice* keys, PinnableSlice* values,
+ Status* statuses, const bool sorted_input) {
+ autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
+ sorted_keys.resize(num_keys);
+ for (size_t i = 0; i < num_keys; ++i) {
+ key_context.emplace_back(column_family, keys[i], &values[i], &statuses[i]);
+ }
+ for (size_t i = 0; i < num_keys; ++i) {
+ sorted_keys[i] = &key_context[i];
+ }
+ PrepareMultiGetKeys(num_keys, sorted_input, &sorted_keys);
+ MultiGetWithCallback(read_options, column_family, nullptr, &sorted_keys);
+}
+
+void DBImpl::MultiGetWithCallback(
+ const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ ReadCallback* callback,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
+ std::array<MultiGetColumnFamilyData, 1> multiget_cf_data;
+ multiget_cf_data[0] = MultiGetColumnFamilyData(column_family, nullptr);
+ std::function<MultiGetColumnFamilyData*(
+ std::array<MultiGetColumnFamilyData, 1>::iterator&)>
+ iter_deref_lambda =
+ [](std::array<MultiGetColumnFamilyData, 1>::iterator& cf_iter) {
+ return &(*cf_iter);
+ };
+
+ size_t num_keys = sorted_keys->size();
+ SequenceNumber consistent_seqnum;
+ bool unref_only = MultiCFSnapshot<std::array<MultiGetColumnFamilyData, 1>>(
+ read_options, callback, iter_deref_lambda, &multiget_cf_data,
+ &consistent_seqnum);
+#ifndef NDEBUG
+ assert(!unref_only);
+#else
+ // Silence unused variable warning
+ (void)unref_only;
+#endif // NDEBUG
+
+ if (callback && read_options.snapshot == nullptr) {
+ // The unprep_seqs are not published for write unprepared, so it could be
+ // that max_visible_seq is larger. Seek to the std::max of the two.
+ // However, we still want our callback to contain the actual snapshot so
+ // that it can do the correct visibility filtering.
+ callback->Refresh(consistent_seqnum);
+
+ // Internally, WriteUnpreparedTxnReadCallback::Refresh would set
+ // max_visible_seq = max(max_visible_seq, snapshot)
+ //
+ // Currently, the commented out assert is broken by
+ // InvalidSnapshotReadCallback, but if write unprepared recovery followed
+ // the regular transaction flow, then this special read callback would not
+ // be needed.
+ //
+ // assert(callback->max_visible_seq() >= snapshot);
+ consistent_seqnum = callback->max_visible_seq();
+ }
+
+ MultiGetImpl(read_options, 0, num_keys, sorted_keys,
+ multiget_cf_data[0].super_version, consistent_seqnum, nullptr,
+ nullptr);
+ ReturnAndCleanupSuperVersion(multiget_cf_data[0].cfd,
+ multiget_cf_data[0].super_version);
+}
+
+void DBImpl::MultiGetImpl(
+ const ReadOptions& read_options, size_t start_key, size_t num_keys,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys,
+ SuperVersion* super_version, SequenceNumber snapshot,
+ ReadCallback* callback, bool* is_blob_index) {
+ PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
+ StopWatch sw(env_, stats_, DB_MULTIGET);
+
+ // For each of the given keys, apply the entire "get" process as follows:
+ // First look in the memtable, then in the immutable memtable (if any).
+ // s is both in/out. When in, s could either be OK or MergeInProgress.
+ // merge_operands will contain the sequence of merges in the latter case.
+ size_t keys_left = num_keys;
+ while (keys_left) {
+ size_t batch_size = (keys_left > MultiGetContext::MAX_BATCH_SIZE)
+ ? MultiGetContext::MAX_BATCH_SIZE
+ : keys_left;
+ MultiGetContext ctx(sorted_keys, start_key + num_keys - keys_left,
+ batch_size, snapshot);
+ MultiGetRange range = ctx.GetMultiGetRange();
+ bool lookup_current = false;
+
+ keys_left -= batch_size;
+ for (auto mget_iter = range.begin(); mget_iter != range.end();
+ ++mget_iter) {
+ mget_iter->merge_context.Clear();
+ *mget_iter->s = Status::OK();
+ }
+
+ bool skip_memtable =
+ (read_options.read_tier == kPersistedTier &&
+ has_unpersisted_data_.load(std::memory_order_relaxed));
+ if (!skip_memtable) {
+ super_version->mem->MultiGet(read_options, &range, callback,
+ is_blob_index);
+ if (!range.empty()) {
+ super_version->imm->MultiGet(read_options, &range, callback,
+ is_blob_index);
+ }
+ if (!range.empty()) {
+ lookup_current = true;
+ uint64_t left = range.KeysLeft();
+ RecordTick(stats_, MEMTABLE_MISS, left);
+ }
+ }
+ if (lookup_current) {
+ PERF_TIMER_GUARD(get_from_output_files_time);
+ super_version->current->MultiGet(read_options, &range, callback,
+ is_blob_index);
+ }
+ }
+
+ // Post processing (decrement reference counts and record statistics)
+ PERF_TIMER_GUARD(get_post_process_time);
+ size_t num_found = 0;
+ uint64_t bytes_read = 0;
+ for (size_t i = start_key; i < start_key + num_keys; ++i) {
+ KeyContext* key = (*sorted_keys)[i];
+ if (key->s->ok()) {
+ bytes_read += key->value->size();
+ num_found++;
+ }
+ }
+
+ RecordTick(stats_, NUMBER_MULTIGET_CALLS);
+ RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
+ RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found);
+ RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
+ RecordInHistogram(stats_, BYTES_PER_MULTIGET, bytes_read);
+ PERF_COUNTER_ADD(multiget_read_bytes, bytes_read);
+ PERF_TIMER_STOP(get_post_process_time);
+}
+
+Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
+ const std::string& column_family,
+ ColumnFamilyHandle** handle) {
+ assert(handle != nullptr);
+ Status s = CreateColumnFamilyImpl(cf_options, column_family, handle);
+ if (s.ok()) {
+ s = WriteOptionsFile(true /*need_mutex_lock*/,
+ true /*need_enter_write_thread*/);
+ }
+ return s;
+}
+
+Status DBImpl::CreateColumnFamilies(
+ const ColumnFamilyOptions& cf_options,
+ const std::vector<std::string>& column_family_names,
+ std::vector<ColumnFamilyHandle*>* handles) {
+ assert(handles != nullptr);
+ handles->clear();
+ size_t num_cf = column_family_names.size();
+ Status s;
+ bool success_once = false;
+ for (size_t i = 0; i < num_cf; i++) {
+ ColumnFamilyHandle* handle;
+ s = CreateColumnFamilyImpl(cf_options, column_family_names[i], &handle);
+ if (!s.ok()) {
+ break;
+ }
+ handles->push_back(handle);
+ success_once = true;
+ }
+ if (success_once) {
+ Status persist_options_status = WriteOptionsFile(
+ true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
+ if (s.ok() && !persist_options_status.ok()) {
+ s = persist_options_status;
+ }
+ }
+ return s;
+}
+
+Status DBImpl::CreateColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles) {
+ assert(handles != nullptr);
+ handles->clear();
+ size_t num_cf = column_families.size();
+ Status s;
+ bool success_once = false;
+ for (size_t i = 0; i < num_cf; i++) {
+ ColumnFamilyHandle* handle;
+ s = CreateColumnFamilyImpl(column_families[i].options,
+ column_families[i].name, &handle);
+ if (!s.ok()) {
+ break;
+ }
+ handles->push_back(handle);
+ success_once = true;
+ }
+ if (success_once) {
+ Status persist_options_status = WriteOptionsFile(
+ true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
+ if (s.ok() && !persist_options_status.ok()) {
+ s = persist_options_status;
+ }
+ }
+ return s;
+}
+
+Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle) {
+ Status s;
+ Status persist_options_status;
+ *handle = nullptr;
+
+ DBOptions db_options =
+ BuildDBOptions(immutable_db_options_, mutable_db_options_);
+ s = ColumnFamilyData::ValidateOptions(db_options, cf_options);
+ if (s.ok()) {
+ for (auto& cf_path : cf_options.cf_paths) {
+ s = env_->CreateDirIfMissing(cf_path.path);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ SuperVersionContext sv_context(/* create_superversion */ true);
+ {
+ InstrumentedMutexLock l(&mutex_);
+
+ if (versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name) !=
+ nullptr) {
+ return Status::InvalidArgument("Column family already exists");
+ }
+ VersionEdit edit;
+ edit.AddColumnFamily(column_family_name);
+ uint32_t new_id = versions_->GetColumnFamilySet()->GetNextColumnFamilyID();
+ edit.SetColumnFamily(new_id);
+ edit.SetLogNumber(logfile_number_);
+ edit.SetComparatorName(cf_options.comparator->Name());
+
+ // LogAndApply will both write the creation in MANIFEST and create
+ // ColumnFamilyData object
+ { // write thread
+ WriteThread::Writer w;
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ // LogAndApply will both write the creation in MANIFEST and create
+ // ColumnFamilyData object
+ s = versions_->LogAndApply(nullptr, MutableCFOptions(cf_options), &edit,
+ &mutex_, directories_.GetDbDir(), false,
+ &cf_options);
+ write_thread_.ExitUnbatched(&w);
+ }
+ if (s.ok()) {
+ auto* cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
+ assert(cfd != nullptr);
+ std::map<std::string, std::shared_ptr<Directory>> dummy_created_dirs;
+ s = cfd->AddDirectories(&dummy_created_dirs);
+ }
+ if (s.ok()) {
+ single_column_family_mode_ = false;
+ auto* cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
+ assert(cfd != nullptr);
+ InstallSuperVersionAndScheduleWork(cfd, &sv_context,
+ *cfd->GetLatestMutableCFOptions());
+
+ if (!cfd->mem()->IsSnapshotSupported()) {
+ is_snapshot_supported_ = false;
+ }
+
+ cfd->set_initialized();
+
+ *handle = new ColumnFamilyHandleImpl(cfd, this, &mutex_);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Created column family [%s] (ID %u)",
+ column_family_name.c_str(), (unsigned)cfd->GetID());
+ } else {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Creating column family [%s] FAILED -- %s",
+ column_family_name.c_str(), s.ToString().c_str());
+ }
+ } // InstrumentedMutexLock l(&mutex_)
+
+ sv_context.Clean();
+ // this is outside the mutex
+ if (s.ok()) {
+ NewThreadStatusCfInfo(
+ reinterpret_cast<ColumnFamilyHandleImpl*>(*handle)->cfd());
+ }
+ return s;
+}
+
+Status DBImpl::DropColumnFamily(ColumnFamilyHandle* column_family) {
+ assert(column_family != nullptr);
+ Status s = DropColumnFamilyImpl(column_family);
+ if (s.ok()) {
+ s = WriteOptionsFile(true /*need_mutex_lock*/,
+ true /*need_enter_write_thread*/);
+ }
+ return s;
+}
+
+Status DBImpl::DropColumnFamilies(
+ const std::vector<ColumnFamilyHandle*>& column_families) {
+ Status s;
+ bool success_once = false;
+ for (auto* handle : column_families) {
+ s = DropColumnFamilyImpl(handle);
+ if (!s.ok()) {
+ break;
+ }
+ success_once = true;
+ }
+ if (success_once) {
+ Status persist_options_status = WriteOptionsFile(
+ true /*need_mutex_lock*/, true /*need_enter_write_thread*/);
+ if (s.ok() && !persist_options_status.ok()) {
+ s = persist_options_status;
+ }
+ }
+ return s;
+}
+
+Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ if (cfd->GetID() == 0) {
+ return Status::InvalidArgument("Can't drop default column family");
+ }
+
+ bool cf_support_snapshot = cfd->mem()->IsSnapshotSupported();
+
+ VersionEdit edit;
+ edit.DropColumnFamily();
+ edit.SetColumnFamily(cfd->GetID());
+
+ Status s;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ if (cfd->IsDropped()) {
+ s = Status::InvalidArgument("Column family already dropped!\n");
+ }
+ if (s.ok()) {
+ // we drop column family from a single write thread
+ WriteThread::Writer w;
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit,
+ &mutex_);
+ write_thread_.ExitUnbatched(&w);
+ }
+ if (s.ok()) {
+ auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
+ max_total_in_memory_state_ -= mutable_cf_options->write_buffer_size *
+ mutable_cf_options->max_write_buffer_number;
+ }
+
+ if (!cf_support_snapshot) {
+ // Dropped Column Family doesn't support snapshot. Need to recalculate
+ // is_snapshot_supported_.
+ bool new_is_snapshot_supported = true;
+ for (auto c : *versions_->GetColumnFamilySet()) {
+ if (!c->IsDropped() && !c->mem()->IsSnapshotSupported()) {
+ new_is_snapshot_supported = false;
+ break;
+ }
+ }
+ is_snapshot_supported_ = new_is_snapshot_supported;
+ }
+ bg_cv_.SignalAll();
+ }
+
+ if (s.ok()) {
+ // Note that here we erase the associated cf_info of the to-be-dropped
+ // cfd before its ref-count goes to zero to avoid having to erase cf_info
+ // later inside db_mutex.
+ EraseThreadStatusCfInfo(cfd);
+ assert(cfd->IsDropped());
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Dropped column family with id %u\n", cfd->GetID());
+ } else {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Dropping column family with id %u FAILED -- %s\n",
+ cfd->GetID(), s.ToString().c_str());
+ }
+
+ return s;
+}
+
+bool DBImpl::KeyMayExist(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value, bool* value_found) {
+ assert(value != nullptr);
+ if (value_found != nullptr) {
+ // falsify later if key-may-exist but can't fetch value
+ *value_found = true;
+ }
+ ReadOptions roptions = read_options;
+ roptions.read_tier = kBlockCacheTier; // read from block cache only
+ PinnableSlice pinnable_val;
+ GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.value = &pinnable_val;
+ get_impl_options.value_found = value_found;
+ auto s = GetImpl(roptions, key, get_impl_options);
+ value->assign(pinnable_val.data(), pinnable_val.size());
+
+ // If block_cache is enabled and the index block of the table didn't
+ // not present in block_cache, the return value will be Status::Incomplete.
+ // In this case, key may still exist in the table.
+ return s.ok() || s.IsIncomplete();
+}
+
+Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) {
+ if (read_options.managed) {
+ return NewErrorIterator(
+ Status::NotSupported("Managed iterator is not supported anymore."));
+ }
+ Iterator* result = nullptr;
+ if (read_options.read_tier == kPersistedTier) {
+ return NewErrorIterator(Status::NotSupported(
+ "ReadTier::kPersistedData is not yet supported in iterators."));
+ }
+ // if iterator wants internal keys, we can only proceed if
+ // we can guarantee the deletes haven't been processed yet
+ if (immutable_db_options_.preserve_deletes &&
+ read_options.iter_start_seqnum > 0 &&
+ read_options.iter_start_seqnum < preserve_deletes_seqnum_.load()) {
+ return NewErrorIterator(Status::InvalidArgument(
+ "Iterator requested internal keys which are too old and are not"
+ " guaranteed to be preserved, try larger iter_start_seqnum opt."));
+ }
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ if (read_options.tailing) {
+#ifdef ROCKSDB_LITE
+ // not supported in lite version
+ result = nullptr;
+
+#else
+ SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
+ auto iter = new ForwardIterator(this, read_options, cfd, sv);
+ result = NewDBIterator(
+ env_, read_options, *cfd->ioptions(), sv->mutable_cf_options,
+ cfd->user_comparator(), iter, kMaxSequenceNumber,
+ sv->mutable_cf_options.max_sequential_skip_in_iterations, read_callback,
+ this, cfd);
+#endif
+ } else {
+ // Note: no need to consider the special case of
+ // last_seq_same_as_publish_seq_==false since NewIterator is overridden in
+ // WritePreparedTxnDB
+ auto snapshot = read_options.snapshot != nullptr
+ ? read_options.snapshot->GetSequenceNumber()
+ : versions_->LastSequence();
+ result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
+ }
+ return result;
+}
+
+ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
+ ColumnFamilyData* cfd,
+ SequenceNumber snapshot,
+ ReadCallback* read_callback,
+ bool allow_blob,
+ bool allow_refresh) {
+ SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
+
+ // Try to generate a DB iterator tree in continuous memory area to be
+ // cache friendly. Here is an example of result:
+ // +-------------------------------+
+ // | |
+ // | ArenaWrappedDBIter |
+ // | + |
+ // | +---> Inner Iterator ------------+
+ // | | | |
+ // | | +-- -- -- -- -- -- -- --+ |
+ // | +--- | Arena | |
+ // | | | |
+ // | Allocated Memory: | |
+ // | | +-------------------+ |
+ // | | | DBIter | <---+
+ // | | + |
+ // | | | +-> iter_ ------------+
+ // | | | | |
+ // | | +-------------------+ |
+ // | | | MergingIterator | <---+
+ // | | + |
+ // | | | +->child iter1 ------------+
+ // | | | | | |
+ // | | +->child iter2 ----------+ |
+ // | | | | | | |
+ // | | | +->child iter3 --------+ | |
+ // | | | | | |
+ // | | +-------------------+ | | |
+ // | | | Iterator1 | <--------+
+ // | | +-------------------+ | |
+ // | | | Iterator2 | <------+
+ // | | +-------------------+ |
+ // | | | Iterator3 | <----+
+ // | | +-------------------+
+ // | | |
+ // +-------+-----------------------+
+ //
+ // ArenaWrappedDBIter inlines an arena area where all the iterators in
+ // the iterator tree are allocated in the order of being accessed when
+ // querying.
+ // Laying out the iterators in the order of being accessed makes it more
+ // likely that any iterator pointer is close to the iterator it points to so
+ // that they are likely to be in the same cache line and/or page.
+ ArenaWrappedDBIter* db_iter = NewArenaWrappedDbIterator(
+ env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, snapshot,
+ sv->mutable_cf_options.max_sequential_skip_in_iterations,
+ sv->version_number, read_callback, this, cfd, allow_blob,
+ read_options.snapshot != nullptr ? false : allow_refresh);
+
+ InternalIterator* internal_iter =
+ NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
+ db_iter->GetRangeDelAggregator(), snapshot);
+ db_iter->SetIterUnderDBIter(internal_iter);
+
+ return db_iter;
+}
+
+Status DBImpl::NewIterators(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) {
+ if (read_options.managed) {
+ return Status::NotSupported("Managed iterator is not supported anymore.");
+ }
+ if (read_options.read_tier == kPersistedTier) {
+ return Status::NotSupported(
+ "ReadTier::kPersistedData is not yet supported in iterators.");
+ }
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ iterators->clear();
+ iterators->reserve(column_families.size());
+ if (read_options.tailing) {
+#ifdef ROCKSDB_LITE
+ return Status::InvalidArgument(
+ "Tailing iterator not supported in RocksDB lite");
+#else
+ for (auto cfh : column_families) {
+ auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
+ SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
+ auto iter = new ForwardIterator(this, read_options, cfd, sv);
+ iterators->push_back(NewDBIterator(
+ env_, read_options, *cfd->ioptions(), sv->mutable_cf_options,
+ cfd->user_comparator(), iter, kMaxSequenceNumber,
+ sv->mutable_cf_options.max_sequential_skip_in_iterations,
+ read_callback, this, cfd));
+ }
+#endif
+ } else {
+ // Note: no need to consider the special case of
+ // last_seq_same_as_publish_seq_==false since NewIterators is overridden in
+ // WritePreparedTxnDB
+ auto snapshot = read_options.snapshot != nullptr
+ ? read_options.snapshot->GetSequenceNumber()
+ : versions_->LastSequence();
+ for (size_t i = 0; i < column_families.size(); ++i) {
+ auto* cfd =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(column_families[i])->cfd();
+ iterators->push_back(
+ NewIteratorImpl(read_options, cfd, snapshot, read_callback));
+ }
+ }
+
+ return Status::OK();
+}
+
+const Snapshot* DBImpl::GetSnapshot() { return GetSnapshotImpl(false); }
+
+#ifndef ROCKSDB_LITE
+const Snapshot* DBImpl::GetSnapshotForWriteConflictBoundary() {
+ return GetSnapshotImpl(true);
+}
+#endif // ROCKSDB_LITE
+
+SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
+ bool lock) {
+ int64_t unix_time = 0;
+ env_->GetCurrentTime(&unix_time); // Ignore error
+ SnapshotImpl* s = new SnapshotImpl;
+
+ if (lock) {
+ mutex_.Lock();
+ }
+ // returns null if the underlying memtable does not support snapshot.
+ if (!is_snapshot_supported_) {
+ if (lock) {
+ mutex_.Unlock();
+ }
+ delete s;
+ return nullptr;
+ }
+ auto snapshot_seq = last_seq_same_as_publish_seq_
+ ? versions_->LastSequence()
+ : versions_->LastPublishedSequence();
+ SnapshotImpl* snapshot =
+ snapshots_.New(s, snapshot_seq, unix_time, is_write_conflict_boundary);
+ if (lock) {
+ mutex_.Unlock();
+ }
+ return snapshot;
+}
+
+namespace {
+typedef autovector<ColumnFamilyData*, 2> CfdList;
+bool CfdListContains(const CfdList& list, ColumnFamilyData* cfd) {
+ for (const ColumnFamilyData* t : list) {
+ if (t == cfd) {
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace
+
+void DBImpl::ReleaseSnapshot(const Snapshot* s) {
+ const SnapshotImpl* casted_s = reinterpret_cast<const SnapshotImpl*>(s);
+ {
+ InstrumentedMutexLock l(&mutex_);
+ snapshots_.Delete(casted_s);
+ uint64_t oldest_snapshot;
+ if (snapshots_.empty()) {
+ oldest_snapshot = last_seq_same_as_publish_seq_
+ ? versions_->LastSequence()
+ : versions_->LastPublishedSequence();
+ } else {
+ oldest_snapshot = snapshots_.oldest()->number_;
+ }
+ // Avoid to go through every column family by checking a global threshold
+ // first.
+ if (oldest_snapshot > bottommost_files_mark_threshold_) {
+ CfdList cf_scheduled;
+ for (auto* cfd : *versions_->GetColumnFamilySet()) {
+ cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
+ if (!cfd->current()
+ ->storage_info()
+ ->BottommostFilesMarkedForCompaction()
+ .empty()) {
+ SchedulePendingCompaction(cfd);
+ MaybeScheduleFlushOrCompaction();
+ cf_scheduled.push_back(cfd);
+ }
+ }
+
+ // Calculate a new threshold, skipping those CFs where compactions are
+ // scheduled. We do not do the same pass as the previous loop because
+ // mutex might be unlocked during the loop, making the result inaccurate.
+ SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
+ for (auto* cfd : *versions_->GetColumnFamilySet()) {
+ if (CfdListContains(cf_scheduled, cfd)) {
+ continue;
+ }
+ new_bottommost_files_mark_threshold = std::min(
+ new_bottommost_files_mark_threshold,
+ cfd->current()->storage_info()->bottommost_files_mark_threshold());
+ }
+ bottommost_files_mark_threshold_ = new_bottommost_files_mark_threshold;
+ }
+ }
+ delete casted_s;
+}
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
+ TablePropertiesCollection* props) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+
+ // Increment the ref count
+ mutex_.Lock();
+ auto version = cfd->current();
+ version->Ref();
+ mutex_.Unlock();
+
+ auto s = version->GetPropertiesOfAllTables(props);
+
+ // Decrement the ref count
+ mutex_.Lock();
+ version->Unref();
+ mutex_.Unlock();
+
+ return s;
+}
+
+Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
+ const Range* range, std::size_t n,
+ TablePropertiesCollection* props) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+
+ // Increment the ref count
+ mutex_.Lock();
+ auto version = cfd->current();
+ version->Ref();
+ mutex_.Unlock();
+
+ auto s = version->GetPropertiesOfTablesInRange(range, n, props);
+
+ // Decrement the ref count
+ mutex_.Lock();
+ version->Unref();
+ mutex_.Unlock();
+
+ return s;
+}
+
+#endif // ROCKSDB_LITE
+
+const std::string& DBImpl::GetName() const { return dbname_; }
+
+Env* DBImpl::GetEnv() const { return env_; }
+
+FileSystem* DB::GetFileSystem() const {
+ static LegacyFileSystemWrapper fs_wrap(GetEnv());
+ return &fs_wrap;
+}
+
+FileSystem* DBImpl::GetFileSystem() const {
+ return immutable_db_options_.fs.get();
+}
+
+Options DBImpl::GetOptions(ColumnFamilyHandle* column_family) const {
+ InstrumentedMutexLock l(&mutex_);
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ return Options(BuildDBOptions(immutable_db_options_, mutable_db_options_),
+ cfh->cfd()->GetLatestCFOptions());
+}
+
+DBOptions DBImpl::GetDBOptions() const {
+ InstrumentedMutexLock l(&mutex_);
+ return BuildDBOptions(immutable_db_options_, mutable_db_options_);
+}
+
+bool DBImpl::GetProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, std::string* value) {
+ const DBPropertyInfo* property_info = GetPropertyInfo(property);
+ value->clear();
+ auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ if (property_info == nullptr) {
+ return false;
+ } else if (property_info->handle_int) {
+ uint64_t int_value;
+ bool ret_value =
+ GetIntPropertyInternal(cfd, *property_info, false, &int_value);
+ if (ret_value) {
+ *value = ToString(int_value);
+ }
+ return ret_value;
+ } else if (property_info->handle_string) {
+ InstrumentedMutexLock l(&mutex_);
+ return cfd->internal_stats()->GetStringProperty(*property_info, property,
+ value);
+ } else if (property_info->handle_string_dbimpl) {
+ std::string tmp_value;
+ bool ret_value = (this->*(property_info->handle_string_dbimpl))(&tmp_value);
+ if (ret_value) {
+ *value = tmp_value;
+ }
+ return ret_value;
+ }
+ // Shouldn't reach here since exactly one of handle_string and handle_int
+ // should be non-nullptr.
+ assert(false);
+ return false;
+}
+
+bool DBImpl::GetMapProperty(ColumnFamilyHandle* column_family,
+ const Slice& property,
+ std::map<std::string, std::string>* value) {
+ const DBPropertyInfo* property_info = GetPropertyInfo(property);
+ value->clear();
+ auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ if (property_info == nullptr) {
+ return false;
+ } else if (property_info->handle_map) {
+ InstrumentedMutexLock l(&mutex_);
+ return cfd->internal_stats()->GetMapProperty(*property_info, property,
+ value);
+ }
+ // If we reach this point it means that handle_map is not provided for the
+ // requested property
+ return false;
+}
+
+bool DBImpl::GetIntProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, uint64_t* value) {
+ const DBPropertyInfo* property_info = GetPropertyInfo(property);
+ if (property_info == nullptr || property_info->handle_int == nullptr) {
+ return false;
+ }
+ auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ return GetIntPropertyInternal(cfd, *property_info, false, value);
+}
+
+bool DBImpl::GetIntPropertyInternal(ColumnFamilyData* cfd,
+ const DBPropertyInfo& property_info,
+ bool is_locked, uint64_t* value) {
+ assert(property_info.handle_int != nullptr);
+ if (!property_info.need_out_of_mutex) {
+ if (is_locked) {
+ mutex_.AssertHeld();
+ return cfd->internal_stats()->GetIntProperty(property_info, value, this);
+ } else {
+ InstrumentedMutexLock l(&mutex_);
+ return cfd->internal_stats()->GetIntProperty(property_info, value, this);
+ }
+ } else {
+ SuperVersion* sv = nullptr;
+ if (!is_locked) {
+ sv = GetAndRefSuperVersion(cfd);
+ } else {
+ sv = cfd->GetSuperVersion();
+ }
+
+ bool ret = cfd->internal_stats()->GetIntPropertyOutOfMutex(
+ property_info, sv->current, value);
+
+ if (!is_locked) {
+ ReturnAndCleanupSuperVersion(cfd, sv);
+ }
+
+ return ret;
+ }
+}
+
+bool DBImpl::GetPropertyHandleOptionsStatistics(std::string* value) {
+ assert(value != nullptr);
+ Statistics* statistics = immutable_db_options_.statistics.get();
+ if (!statistics) {
+ return false;
+ }
+ *value = statistics->ToString();
+ return true;
+}
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::ResetStats() {
+ InstrumentedMutexLock l(&mutex_);
+ for (auto* cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->initialized()) {
+ cfd->internal_stats()->Clear();
+ }
+ }
+ return Status::OK();
+}
+#endif // ROCKSDB_LITE
+
+bool DBImpl::GetAggregatedIntProperty(const Slice& property,
+ uint64_t* aggregated_value) {
+ const DBPropertyInfo* property_info = GetPropertyInfo(property);
+ if (property_info == nullptr || property_info->handle_int == nullptr) {
+ return false;
+ }
+
+ uint64_t sum = 0;
+ {
+ // Needs mutex to protect the list of column families.
+ InstrumentedMutexLock l(&mutex_);
+ uint64_t value;
+ for (auto* cfd : *versions_->GetColumnFamilySet()) {
+ if (!cfd->initialized()) {
+ continue;
+ }
+ if (GetIntPropertyInternal(cfd, *property_info, true, &value)) {
+ sum += value;
+ } else {
+ return false;
+ }
+ }
+ }
+ *aggregated_value = sum;
+ return true;
+}
+
+SuperVersion* DBImpl::GetAndRefSuperVersion(ColumnFamilyData* cfd) {
+ // TODO(ljin): consider using GetReferencedSuperVersion() directly
+ return cfd->GetThreadLocalSuperVersion(this);
+}
+
+// REQUIRED: this function should only be called on the write thread or if the
+// mutex is held.
+SuperVersion* DBImpl::GetAndRefSuperVersion(uint32_t column_family_id) {
+ auto column_family_set = versions_->GetColumnFamilySet();
+ auto cfd = column_family_set->GetColumnFamily(column_family_id);
+ if (!cfd) {
+ return nullptr;
+ }
+
+ return GetAndRefSuperVersion(cfd);
+}
+
+void DBImpl::CleanupSuperVersion(SuperVersion* sv) {
+ // Release SuperVersion
+ if (sv->Unref()) {
+ bool defer_purge =
+ immutable_db_options().avoid_unnecessary_blocking_io;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ sv->Cleanup();
+ if (defer_purge) {
+ AddSuperVersionsToFreeQueue(sv);
+ SchedulePurge();
+ }
+ }
+ if (!defer_purge) {
+ delete sv;
+ }
+ RecordTick(stats_, NUMBER_SUPERVERSION_CLEANUPS);
+ }
+ RecordTick(stats_, NUMBER_SUPERVERSION_RELEASES);
+}
+
+void DBImpl::ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd,
+ SuperVersion* sv) {
+ if (!cfd->ReturnThreadLocalSuperVersion(sv)) {
+ CleanupSuperVersion(sv);
+ }
+}
+
+// REQUIRED: this function should only be called on the write thread.
+void DBImpl::ReturnAndCleanupSuperVersion(uint32_t column_family_id,
+ SuperVersion* sv) {
+ auto column_family_set = versions_->GetColumnFamilySet();
+ auto cfd = column_family_set->GetColumnFamily(column_family_id);
+
+ // If SuperVersion is held, and we successfully fetched a cfd using
+ // GetAndRefSuperVersion(), it must still exist.
+ assert(cfd != nullptr);
+ ReturnAndCleanupSuperVersion(cfd, sv);
+}
+
+// REQUIRED: this function should only be called on the write thread or if the
+// mutex is held.
+ColumnFamilyHandle* DBImpl::GetColumnFamilyHandle(uint32_t column_family_id) {
+ ColumnFamilyMemTables* cf_memtables = column_family_memtables_.get();
+
+ if (!cf_memtables->Seek(column_family_id)) {
+ return nullptr;
+ }
+
+ return cf_memtables->GetColumnFamilyHandle();
+}
+
+// REQUIRED: mutex is NOT held.
+std::unique_ptr<ColumnFamilyHandle> DBImpl::GetColumnFamilyHandleUnlocked(
+ uint32_t column_family_id) {
+ InstrumentedMutexLock l(&mutex_);
+
+ auto* cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(column_family_id);
+ if (cfd == nullptr) {
+ return nullptr;
+ }
+
+ return std::unique_ptr<ColumnFamilyHandleImpl>(
+ new ColumnFamilyHandleImpl(cfd, this, &mutex_));
+}
+
+void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
+ const Range& range,
+ uint64_t* const count,
+ uint64_t* const size) {
+ ColumnFamilyHandleImpl* cfh =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ ColumnFamilyData* cfd = cfh->cfd();
+ SuperVersion* sv = GetAndRefSuperVersion(cfd);
+
+ // Convert user_key into a corresponding internal key.
+ InternalKey k1(range.start, kMaxSequenceNumber, kValueTypeForSeek);
+ InternalKey k2(range.limit, kMaxSequenceNumber, kValueTypeForSeek);
+ MemTable::MemTableStats memStats =
+ sv->mem->ApproximateStats(k1.Encode(), k2.Encode());
+ MemTable::MemTableStats immStats =
+ sv->imm->ApproximateStats(k1.Encode(), k2.Encode());
+ *count = memStats.count + immStats.count;
+ *size = memStats.size + immStats.size;
+
+ ReturnAndCleanupSuperVersion(cfd, sv);
+}
+
+Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Range* range, int n, uint64_t* sizes) {
+ if (!options.include_memtabtles && !options.include_files) {
+ return Status::InvalidArgument("Invalid options");
+ }
+
+ Version* v;
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ SuperVersion* sv = GetAndRefSuperVersion(cfd);
+ v = sv->current;
+
+ for (int i = 0; i < n; i++) {
+ // Convert user_key into a corresponding internal key.
+ InternalKey k1(range[i].start, kMaxSequenceNumber, kValueTypeForSeek);
+ InternalKey k2(range[i].limit, kMaxSequenceNumber, kValueTypeForSeek);
+ sizes[i] = 0;
+ if (options.include_files) {
+ sizes[i] += versions_->ApproximateSize(
+ options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
+ /*end_level=*/-1, TableReaderCaller::kUserApproximateSize);
+ }
+ if (options.include_memtabtles) {
+ sizes[i] += sv->mem->ApproximateStats(k1.Encode(), k2.Encode()).size;
+ sizes[i] += sv->imm->ApproximateStats(k1.Encode(), k2.Encode()).size;
+ }
+ }
+
+ ReturnAndCleanupSuperVersion(cfd, sv);
+ return Status::OK();
+}
+
+std::list<uint64_t>::iterator
+DBImpl::CaptureCurrentFileNumberInPendingOutputs() {
+ // We need to remember the iterator of our insert, because after the
+ // background job is done, we need to remove that element from
+ // pending_outputs_.
+ pending_outputs_.push_back(versions_->current_next_file_number());
+ auto pending_outputs_inserted_elem = pending_outputs_.end();
+ --pending_outputs_inserted_elem;
+ return pending_outputs_inserted_elem;
+}
+
+void DBImpl::ReleaseFileNumberFromPendingOutputs(
+ std::unique_ptr<std::list<uint64_t>::iterator>& v) {
+ if (v.get() != nullptr) {
+ pending_outputs_.erase(*v.get());
+ v.reset();
+ }
+}
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::GetUpdatesSince(
+ SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options) {
+ RecordTick(stats_, GET_UPDATES_SINCE_CALLS);
+ if (seq > versions_->LastSequence()) {
+ return Status::NotFound("Requested sequence not yet written in the db");
+ }
+ return wal_manager_.GetUpdatesSince(seq, iter, read_options, versions_.get());
+}
+
+Status DBImpl::DeleteFile(std::string name) {
+ uint64_t number;
+ FileType type;
+ WalFileType log_type;
+ if (!ParseFileName(name, &number, &type, &log_type) ||
+ (type != kTableFile && type != kLogFile)) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log, "DeleteFile %s failed.\n",
+ name.c_str());
+ return Status::InvalidArgument("Invalid file name");
+ }
+
+ Status status;
+ if (type == kLogFile) {
+ // Only allow deleting archived log files
+ if (log_type != kArchivedLogFile) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "DeleteFile %s failed - not archived log.\n",
+ name.c_str());
+ return Status::NotSupported("Delete only supported for archived logs");
+ }
+ status = wal_manager_.DeleteFile(name, number);
+ if (!status.ok()) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "DeleteFile %s failed -- %s.\n", name.c_str(),
+ status.ToString().c_str());
+ }
+ return status;
+ }
+
+ int level;
+ FileMetaData* metadata;
+ ColumnFamilyData* cfd;
+ VersionEdit edit;
+ JobContext job_context(next_job_id_.fetch_add(1), true);
+ {
+ InstrumentedMutexLock l(&mutex_);
+ status = versions_->GetMetadataForFile(number, &level, &metadata, &cfd);
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "DeleteFile %s failed. File not found\n", name.c_str());
+ job_context.Clean();
+ return Status::InvalidArgument("File not found");
+ }
+ assert(level < cfd->NumberLevels());
+
+ // If the file is being compacted no need to delete.
+ if (metadata->being_compacted) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "DeleteFile %s Skipped. File about to be compacted\n",
+ name.c_str());
+ job_context.Clean();
+ return Status::OK();
+ }
+
+ // Only the files in the last level can be deleted externally.
+ // This is to make sure that any deletion tombstones are not
+ // lost. Check that the level passed is the last level.
+ auto* vstoreage = cfd->current()->storage_info();
+ for (int i = level + 1; i < cfd->NumberLevels(); i++) {
+ if (vstoreage->NumLevelFiles(i) != 0) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "DeleteFile %s FAILED. File not in last level\n",
+ name.c_str());
+ job_context.Clean();
+ return Status::InvalidArgument("File not in last level");
+ }
+ }
+ // if level == 0, it has to be the oldest file
+ if (level == 0 &&
+ vstoreage->LevelFiles(0).back()->fd.GetNumber() != number) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "DeleteFile %s failed ---"
+ " target file in level 0 must be the oldest.",
+ name.c_str());
+ job_context.Clean();
+ return Status::InvalidArgument("File in level 0, but not oldest");
+ }
+ edit.SetColumnFamily(cfd->GetID());
+ edit.DeleteFile(level, number);
+ status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
+ &edit, &mutex_, directories_.GetDbDir());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd,
+ &job_context.superversion_contexts[0],
+ *cfd->GetLatestMutableCFOptions());
+ }
+ FindObsoleteFiles(&job_context, false);
+ } // lock released here
+
+ LogFlush(immutable_db_options_.info_log);
+ // remove files outside the db-lock
+ if (job_context.HaveSomethingToDelete()) {
+ // Call PurgeObsoleteFiles() without holding mutex.
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ return status;
+}
+
+Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
+ const RangePtr* ranges, size_t n,
+ bool include_end) {
+ Status status;
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ ColumnFamilyData* cfd = cfh->cfd();
+ VersionEdit edit;
+ std::set<FileMetaData*> deleted_files;
+ JobContext job_context(next_job_id_.fetch_add(1), true);
+ {
+ InstrumentedMutexLock l(&mutex_);
+ Version* input_version = cfd->current();
+
+ auto* vstorage = input_version->storage_info();
+ for (size_t r = 0; r < n; r++) {
+ auto begin = ranges[r].start, end = ranges[r].limit;
+ for (int i = 1; i < cfd->NumberLevels(); i++) {
+ if (vstorage->LevelFiles(i).empty() ||
+ !vstorage->OverlapInLevel(i, begin, end)) {
+ continue;
+ }
+ std::vector<FileMetaData*> level_files;
+ InternalKey begin_storage, end_storage, *begin_key, *end_key;
+ if (begin == nullptr) {
+ begin_key = nullptr;
+ } else {
+ begin_storage.SetMinPossibleForUserKey(*begin);
+ begin_key = &begin_storage;
+ }
+ if (end == nullptr) {
+ end_key = nullptr;
+ } else {
+ end_storage.SetMaxPossibleForUserKey(*end);
+ end_key = &end_storage;
+ }
+
+ vstorage->GetCleanInputsWithinInterval(
+ i, begin_key, end_key, &level_files, -1 /* hint_index */,
+ nullptr /* file_index */);
+ FileMetaData* level_file;
+ for (uint32_t j = 0; j < level_files.size(); j++) {
+ level_file = level_files[j];
+ if (level_file->being_compacted) {
+ continue;
+ }
+ if (deleted_files.find(level_file) != deleted_files.end()) {
+ continue;
+ }
+ if (!include_end && end != nullptr &&
+ cfd->user_comparator()->Compare(level_file->largest.user_key(),
+ *end) == 0) {
+ continue;
+ }
+ edit.SetColumnFamily(cfd->GetID());
+ edit.DeleteFile(i, level_file->fd.GetNumber());
+ deleted_files.insert(level_file);
+ level_file->being_compacted = true;
+ }
+ }
+ }
+ if (edit.GetDeletedFiles().empty()) {
+ job_context.Clean();
+ return Status::OK();
+ }
+ input_version->Ref();
+ status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
+ &edit, &mutex_, directories_.GetDbDir());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd,
+ &job_context.superversion_contexts[0],
+ *cfd->GetLatestMutableCFOptions());
+ }
+ for (auto* deleted_file : deleted_files) {
+ deleted_file->being_compacted = false;
+ }
+ input_version->Unref();
+ FindObsoleteFiles(&job_context, false);
+ } // lock released here
+
+ LogFlush(immutable_db_options_.info_log);
+ // remove files outside the db-lock
+ if (job_context.HaveSomethingToDelete()) {
+ // Call PurgeObsoleteFiles() without holding mutex.
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ return status;
+}
+
+void DBImpl::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
+ InstrumentedMutexLock l(&mutex_);
+ versions_->GetLiveFilesMetaData(metadata);
+}
+
+void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
+ ColumnFamilyMetaData* cf_meta) {
+ assert(column_family);
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ auto* sv = GetAndRefSuperVersion(cfd);
+ {
+ // Without mutex, Version::GetColumnFamilyMetaData will have data race with
+ // Compaction::MarkFilesBeingCompacted. One solution is to use mutex, but
+ // this may cause regression. An alternative is to make
+ // FileMetaData::being_compacted atomic, but it will make FileMetaData
+ // non-copy-able. Another option is to separate these variables from
+ // original FileMetaData struct, and this requires re-organization of data
+ // structures. For now, we take the easy approach. If
+ // DB::GetColumnFamilyMetaData is not called frequently, the regression
+ // should not be big. We still need to keep an eye on it.
+ InstrumentedMutexLock l(&mutex_);
+ sv->current->GetColumnFamilyMetaData(cf_meta);
+ }
+ ReturnAndCleanupSuperVersion(cfd, sv);
+}
+
+#endif // ROCKSDB_LITE
+
+Status DBImpl::CheckConsistency() {
+ mutex_.AssertHeld();
+ std::vector<LiveFileMetaData> metadata;
+ versions_->GetLiveFilesMetaData(&metadata);
+ TEST_SYNC_POINT("DBImpl::CheckConsistency:AfterGetLiveFilesMetaData");
+
+ std::string corruption_messages;
+
+ if (immutable_db_options_.skip_checking_sst_file_sizes_on_db_open) {
+ // Instead of calling GetFileSize() for each expected file, call
+ // GetChildren() for the DB directory and check that all expected files
+ // are listed, without checking their sizes.
+ // Since sst files might be in different directories, do it for each
+ // directory separately.
+ std::map<std::string, std::vector<std::string>> files_by_directory;
+ for (const auto& md : metadata) {
+ // md.name has a leading "/". Remove it.
+ std::string fname = md.name;
+ if (!fname.empty() && fname[0] == '/') {
+ fname = fname.substr(1);
+ }
+ files_by_directory[md.db_path].push_back(fname);
+ }
+ for (const auto& dir_files : files_by_directory) {
+ std::string directory = dir_files.first;
+ std::vector<std::string> existing_files;
+ Status s = env_->GetChildren(directory, &existing_files);
+ if (!s.ok()) {
+ corruption_messages +=
+ "Can't list files in " + directory + ": " + s.ToString() + "\n";
+ continue;
+ }
+ std::sort(existing_files.begin(), existing_files.end());
+
+ for (const std::string& fname : dir_files.second) {
+ if (!std::binary_search(existing_files.begin(), existing_files.end(),
+ fname) &&
+ !std::binary_search(existing_files.begin(), existing_files.end(),
+ Rocks2LevelTableFileName(fname))) {
+ corruption_messages +=
+ "Missing sst file " + fname + " in " + directory + "\n";
+ }
+ }
+ }
+ } else {
+ for (const auto& md : metadata) {
+ // md.name has a leading "/".
+ std::string file_path = md.db_path + md.name;
+
+ uint64_t fsize = 0;
+ TEST_SYNC_POINT("DBImpl::CheckConsistency:BeforeGetFileSize");
+ Status s = env_->GetFileSize(file_path, &fsize);
+ if (!s.ok() &&
+ env_->GetFileSize(Rocks2LevelTableFileName(file_path), &fsize).ok()) {
+ s = Status::OK();
+ }
+ if (!s.ok()) {
+ corruption_messages +=
+ "Can't access " + md.name + ": " + s.ToString() + "\n";
+ } else if (fsize != md.size) {
+ corruption_messages += "Sst file size mismatch: " + file_path +
+ ". Size recorded in manifest " +
+ ToString(md.size) + ", actual size " +
+ ToString(fsize) + "\n";
+ }
+ }
+ }
+
+ if (corruption_messages.size() == 0) {
+ return Status::OK();
+ } else {
+ return Status::Corruption(corruption_messages);
+ }
+}
+
+Status DBImpl::GetDbIdentity(std::string& identity) const {
+ identity.assign(db_id_);
+ return Status::OK();
+}
+
+Status DBImpl::GetDbIdentityFromIdentityFile(std::string* identity) const {
+ std::string idfilename = IdentityFileName(dbname_);
+ const FileOptions soptions;
+
+ Status s = ReadFileToString(fs_.get(), idfilename, identity);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // If last character is '\n' remove it from identity
+ if (identity->size() > 0 && identity->back() == '\n') {
+ identity->pop_back();
+ }
+ return s;
+}
+
+// Default implementation -- returns not supported status
+Status DB::CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/,
+ const std::string& /*column_family_name*/,
+ ColumnFamilyHandle** /*handle*/) {
+ return Status::NotSupported("");
+}
+
+Status DB::CreateColumnFamilies(
+ const ColumnFamilyOptions& /*cf_options*/,
+ const std::vector<std::string>& /*column_family_names*/,
+ std::vector<ColumnFamilyHandle*>* /*handles*/) {
+ return Status::NotSupported("");
+}
+
+Status DB::CreateColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
+ std::vector<ColumnFamilyHandle*>* /*handles*/) {
+ return Status::NotSupported("");
+}
+
+Status DB::DropColumnFamily(ColumnFamilyHandle* /*column_family*/) {
+ return Status::NotSupported("");
+}
+
+Status DB::DropColumnFamilies(
+ const std::vector<ColumnFamilyHandle*>& /*column_families*/) {
+ return Status::NotSupported("");
+}
+
+Status DB::DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family) {
+ delete column_family;
+ return Status::OK();
+}
+
+DB::~DB() {}
+
+Status DBImpl::Close() {
+ if (!closed_) {
+ {
+ InstrumentedMutexLock l(&mutex_);
+ // If there is unreleased snapshot, fail the close call
+ if (!snapshots_.empty()) {
+ return Status::Aborted("Cannot close DB with unreleased snapshot.");
+ }
+ }
+
+ closed_ = true;
+ return CloseImpl();
+ }
+ return Status::OK();
+}
+
+Status DB::ListColumnFamilies(const DBOptions& db_options,
+ const std::string& name,
+ std::vector<std::string>* column_families) {
+ FileSystem* fs = db_options.file_system.get();
+ LegacyFileSystemWrapper legacy_fs(db_options.env);
+ if (!fs) {
+ fs = &legacy_fs;
+ }
+ return VersionSet::ListColumnFamilies(column_families, name, fs);
+}
+
+Snapshot::~Snapshot() {}
+
+Status DestroyDB(const std::string& dbname, const Options& options,
+ const std::vector<ColumnFamilyDescriptor>& column_families) {
+ ImmutableDBOptions soptions(SanitizeOptions(dbname, options));
+ Env* env = soptions.env;
+ std::vector<std::string> filenames;
+ bool wal_in_db_path = IsWalDirSameAsDBPath(&soptions);
+
+ // Reset the logger because it holds a handle to the
+ // log file and prevents cleanup and directory removal
+ soptions.info_log.reset();
+ // Ignore error in case directory does not exist
+ env->GetChildren(dbname, &filenames);
+
+ FileLock* lock;
+ const std::string lockname = LockFileName(dbname);
+ Status result = env->LockFile(lockname, &lock);
+ if (result.ok()) {
+ uint64_t number;
+ FileType type;
+ InfoLogPrefix info_log_prefix(!soptions.db_log_dir.empty(), dbname);
+ for (const auto& fname : filenames) {
+ if (ParseFileName(fname, &number, info_log_prefix.prefix, &type) &&
+ type != kDBLockFile) { // Lock file will be deleted at end
+ Status del;
+ std::string path_to_delete = dbname + "/" + fname;
+ if (type == kMetaDatabase) {
+ del = DestroyDB(path_to_delete, options);
+ } else if (type == kTableFile || type == kLogFile) {
+ del = DeleteDBFile(&soptions, path_to_delete, dbname,
+ /*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
+ } else {
+ del = env->DeleteFile(path_to_delete);
+ }
+ if (result.ok() && !del.ok()) {
+ result = del;
+ }
+ }
+ }
+
+ std::vector<std::string> paths;
+
+ for (const auto& path : options.db_paths) {
+ paths.emplace_back(path.path);
+ }
+ for (const auto& cf : column_families) {
+ for (const auto& path : cf.options.cf_paths) {
+ paths.emplace_back(path.path);
+ }
+ }
+
+ // Remove duplicate paths.
+ // Note that we compare only the actual paths but not path ids.
+ // This reason is that same path can appear at different path_ids
+ // for different column families.
+ std::sort(paths.begin(), paths.end());
+ paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
+
+ for (const auto& path : paths) {
+ if (env->GetChildren(path, &filenames).ok()) {
+ for (const auto& fname : filenames) {
+ if (ParseFileName(fname, &number, &type) &&
+ type == kTableFile) { // Lock file will be deleted at end
+ std::string table_path = path + "/" + fname;
+ Status del = DeleteDBFile(&soptions, table_path, dbname,
+ /*force_bg=*/false, /*force_fg=*/false);
+ if (result.ok() && !del.ok()) {
+ result = del;
+ }
+ }
+ }
+ env->DeleteDir(path);
+ }
+ }
+
+ std::vector<std::string> walDirFiles;
+ std::string archivedir = ArchivalDirectory(dbname);
+ bool wal_dir_exists = false;
+ if (dbname != soptions.wal_dir) {
+ wal_dir_exists = env->GetChildren(soptions.wal_dir, &walDirFiles).ok();
+ archivedir = ArchivalDirectory(soptions.wal_dir);
+ }
+
+ // Archive dir may be inside wal dir or dbname and should be
+ // processed and removed before those otherwise we have issues
+ // removing them
+ std::vector<std::string> archiveFiles;
+ if (env->GetChildren(archivedir, &archiveFiles).ok()) {
+ // Delete archival files.
+ for (const auto& file : archiveFiles) {
+ if (ParseFileName(file, &number, &type) && type == kLogFile) {
+ Status del =
+ DeleteDBFile(&soptions, archivedir + "/" + file, archivedir,
+ /*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
+ if (result.ok() && !del.ok()) {
+ result = del;
+ }
+ }
+ }
+ env->DeleteDir(archivedir);
+ }
+
+ // Delete log files in the WAL dir
+ if (wal_dir_exists) {
+ for (const auto& file : walDirFiles) {
+ if (ParseFileName(file, &number, &type) && type == kLogFile) {
+ Status del =
+ DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number),
+ soptions.wal_dir, /*force_bg=*/false,
+ /*force_fg=*/!wal_in_db_path);
+ if (result.ok() && !del.ok()) {
+ result = del;
+ }
+ }
+ }
+ env->DeleteDir(soptions.wal_dir);
+ }
+
+ env->UnlockFile(lock); // Ignore error since state is already gone
+ env->DeleteFile(lockname);
+
+ // sst_file_manager holds a ref to the logger. Make sure the logger is
+ // gone before trying to remove the directory.
+ soptions.sst_file_manager.reset();
+
+ env->DeleteDir(dbname); // Ignore error in case dir contains other files
+ }
+ return result;
+}
+
+Status DBImpl::WriteOptionsFile(bool need_mutex_lock,
+ bool need_enter_write_thread) {
+#ifndef ROCKSDB_LITE
+ WriteThread::Writer w;
+ if (need_mutex_lock) {
+ mutex_.Lock();
+ } else {
+ mutex_.AssertHeld();
+ }
+ if (need_enter_write_thread) {
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ }
+
+ std::vector<std::string> cf_names;
+ std::vector<ColumnFamilyOptions> cf_opts;
+
+ // This part requires mutex to protect the column family options
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ cf_names.push_back(cfd->GetName());
+ cf_opts.push_back(cfd->GetLatestCFOptions());
+ }
+
+ // Unlock during expensive operations. New writes cannot get here
+ // because the single write thread ensures all new writes get queued.
+ DBOptions db_options =
+ BuildDBOptions(immutable_db_options_, mutable_db_options_);
+ mutex_.Unlock();
+
+ TEST_SYNC_POINT("DBImpl::WriteOptionsFile:1");
+ TEST_SYNC_POINT("DBImpl::WriteOptionsFile:2");
+
+ std::string file_name =
+ TempOptionsFileName(GetName(), versions_->NewFileNumber());
+ Status s = PersistRocksDBOptions(db_options, cf_names, cf_opts, file_name,
+ GetFileSystem());
+
+ if (s.ok()) {
+ s = RenameTempFileToOptionsFile(file_name);
+ }
+ // restore lock
+ if (!need_mutex_lock) {
+ mutex_.Lock();
+ }
+ if (need_enter_write_thread) {
+ write_thread_.ExitUnbatched(&w);
+ }
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "Unnable to persist options -- %s", s.ToString().c_str());
+ if (immutable_db_options_.fail_if_options_file_error) {
+ return Status::IOError("Unable to persist options.",
+ s.ToString().c_str());
+ }
+ }
+#else
+ (void)need_mutex_lock;
+ (void)need_enter_write_thread;
+#endif // !ROCKSDB_LITE
+ return Status::OK();
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+void DeleteOptionsFilesHelper(const std::map<uint64_t, std::string>& filenames,
+ const size_t num_files_to_keep,
+ const std::shared_ptr<Logger>& info_log,
+ Env* env) {
+ if (filenames.size() <= num_files_to_keep) {
+ return;
+ }
+ for (auto iter = std::next(filenames.begin(), num_files_to_keep);
+ iter != filenames.end(); ++iter) {
+ if (!env->DeleteFile(iter->second).ok()) {
+ ROCKS_LOG_WARN(info_log, "Unable to delete options file %s",
+ iter->second.c_str());
+ }
+ }
+}
+} // namespace
+#endif // !ROCKSDB_LITE
+
+Status DBImpl::DeleteObsoleteOptionsFiles() {
+#ifndef ROCKSDB_LITE
+ std::vector<std::string> filenames;
+ // use ordered map to store keep the filenames sorted from the newest
+ // to the oldest.
+ std::map<uint64_t, std::string> options_filenames;
+ Status s;
+ s = GetEnv()->GetChildren(GetName(), &filenames);
+ if (!s.ok()) {
+ return s;
+ }
+ for (auto& filename : filenames) {
+ uint64_t file_number;
+ FileType type;
+ if (ParseFileName(filename, &file_number, &type) && type == kOptionsFile) {
+ options_filenames.insert(
+ {std::numeric_limits<uint64_t>::max() - file_number,
+ GetName() + "/" + filename});
+ }
+ }
+
+ // Keeps the latest 2 Options file
+ const size_t kNumOptionsFilesKept = 2;
+ DeleteOptionsFilesHelper(options_filenames, kNumOptionsFilesKept,
+ immutable_db_options_.info_log, GetEnv());
+ return Status::OK();
+#else
+ return Status::OK();
+#endif // !ROCKSDB_LITE
+}
+
+Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
+#ifndef ROCKSDB_LITE
+ Status s;
+
+ uint64_t options_file_number = versions_->NewFileNumber();
+ std::string options_file_name =
+ OptionsFileName(GetName(), options_file_number);
+ // Retry if the file name happen to conflict with an existing one.
+ s = GetEnv()->RenameFile(file_name, options_file_name);
+ if (s.ok()) {
+ InstrumentedMutexLock l(&mutex_);
+ versions_->options_file_number_ = options_file_number;
+ }
+
+ if (0 == disable_delete_obsolete_files_) {
+ DeleteObsoleteOptionsFiles();
+ }
+ return s;
+#else
+ (void)file_name;
+ return Status::OK();
+#endif // !ROCKSDB_LITE
+}
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+
+void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
+ if (immutable_db_options_.enable_thread_tracking) {
+ ThreadStatusUtil::NewColumnFamilyInfo(this, cfd, cfd->GetName(),
+ cfd->ioptions()->env);
+ }
+}
+
+void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* cfd) const {
+ if (immutable_db_options_.enable_thread_tracking) {
+ ThreadStatusUtil::EraseColumnFamilyInfo(cfd);
+ }
+}
+
+void DBImpl::EraseThreadStatusDbInfo() const {
+ if (immutable_db_options_.enable_thread_tracking) {
+ ThreadStatusUtil::EraseDatabaseInfo(this);
+ }
+}
+
+#else
+void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
+
+void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
+
+void DBImpl::EraseThreadStatusDbInfo() const {}
+#endif // ROCKSDB_USING_THREAD_STATUS
+
+//
+// A global method that can dump out the build version
+void DumpRocksDBBuildVersion(Logger* log) {
+#if !defined(IOS_CROSS_COMPILE)
+ // if we compile with Xcode, we don't run build_detect_version, so we don't
+ // generate util/build_version.cc
+ ROCKS_LOG_HEADER(log, "RocksDB version: %d.%d.%d\n", ROCKSDB_MAJOR,
+ ROCKSDB_MINOR, ROCKSDB_PATCH);
+ ROCKS_LOG_HEADER(log, "Git sha %s", rocksdb_build_git_sha);
+ ROCKS_LOG_HEADER(log, "Compile date %s", rocksdb_build_compile_date);
+#else
+ (void)log; // ignore "-Wunused-parameter"
+#endif
+}
+
+#ifndef ROCKSDB_LITE
+SequenceNumber DBImpl::GetEarliestMemTableSequenceNumber(SuperVersion* sv,
+ bool include_history) {
+ // Find the earliest sequence number that we know we can rely on reading
+ // from the memtable without needing to check sst files.
+ SequenceNumber earliest_seq =
+ sv->imm->GetEarliestSequenceNumber(include_history);
+ if (earliest_seq == kMaxSequenceNumber) {
+ earliest_seq = sv->mem->GetEarliestSequenceNumber();
+ }
+ assert(sv->mem->GetEarliestSequenceNumber() >= earliest_seq);
+
+ return earliest_seq;
+}
+#endif // ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
+ bool cache_only,
+ SequenceNumber lower_bound_seq,
+ SequenceNumber* seq,
+ bool* found_record_for_key,
+ bool* is_blob_index) {
+ Status s;
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq = 0;
+
+ ReadOptions read_options;
+ SequenceNumber current_seq = versions_->LastSequence();
+ LookupKey lkey(key, current_seq);
+
+ *seq = kMaxSequenceNumber;
+ *found_record_for_key = false;
+
+ // Check if there is a record for this key in the latest memtable
+ sv->mem->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq,
+ seq, read_options, nullptr /*read_callback*/, is_blob_index);
+
+ if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
+ // unexpected error reading memtable.
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Unexpected status returned from MemTable::Get: %s\n",
+ s.ToString().c_str());
+
+ return s;
+ }
+
+ if (*seq != kMaxSequenceNumber) {
+ // Found a sequence number, no need to check immutable memtables
+ *found_record_for_key = true;
+ return Status::OK();
+ }
+
+ SequenceNumber lower_bound_in_mem = sv->mem->GetEarliestSequenceNumber();
+ if (lower_bound_in_mem != kMaxSequenceNumber &&
+ lower_bound_in_mem < lower_bound_seq) {
+ *found_record_for_key = false;
+ return Status::OK();
+ }
+
+ // Check if there is a record for this key in the immutable memtables
+ sv->imm->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq,
+ seq, read_options, nullptr /*read_callback*/, is_blob_index);
+
+ if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
+ // unexpected error reading memtable.
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Unexpected status returned from MemTableList::Get: %s\n",
+ s.ToString().c_str());
+
+ return s;
+ }
+
+ if (*seq != kMaxSequenceNumber) {
+ // Found a sequence number, no need to check memtable history
+ *found_record_for_key = true;
+ return Status::OK();
+ }
+
+ SequenceNumber lower_bound_in_imm = sv->imm->GetEarliestSequenceNumber();
+ if (lower_bound_in_imm != kMaxSequenceNumber &&
+ lower_bound_in_imm < lower_bound_seq) {
+ *found_record_for_key = false;
+ return Status::OK();
+ }
+
+ // Check if there is a record for this key in the immutable memtables
+ sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context,
+ &max_covering_tombstone_seq, seq, read_options,
+ is_blob_index);
+
+ if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
+ // unexpected error reading memtable.
+ ROCKS_LOG_ERROR(
+ immutable_db_options_.info_log,
+ "Unexpected status returned from MemTableList::GetFromHistory: %s\n",
+ s.ToString().c_str());
+
+ return s;
+ }
+
+ if (*seq != kMaxSequenceNumber) {
+ // Found a sequence number, no need to check SST files
+ *found_record_for_key = true;
+ return Status::OK();
+ }
+
+ // We could do a sv->imm->GetEarliestSequenceNumber(/*include_history*/ true)
+ // check here to skip the history if possible. But currently the caller
+ // already does that. Maybe we should move the logic here later.
+
+ // TODO(agiardullo): possible optimization: consider checking cached
+ // SST files if cache_only=true?
+ if (!cache_only) {
+ // Check tables
+ sv->current->Get(read_options, lkey, nullptr, &s, &merge_context,
+ &max_covering_tombstone_seq, nullptr /* value_found */,
+ found_record_for_key, seq, nullptr /*read_callback*/,
+ is_blob_index);
+
+ if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
+ // unexpected error reading SST files
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Unexpected status returned from Version::Get: %s\n",
+ s.ToString().c_str());
+ }
+ }
+
+ return s;
+}
+
+Status DBImpl::IngestExternalFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& external_files,
+ const IngestExternalFileOptions& ingestion_options) {
+ IngestExternalFileArg arg;
+ arg.column_family = column_family;
+ arg.external_files = external_files;
+ arg.options = ingestion_options;
+ return IngestExternalFiles({arg});
+}
+
+Status DBImpl::IngestExternalFiles(
+ const std::vector<IngestExternalFileArg>& args) {
+ if (args.empty()) {
+ return Status::InvalidArgument("ingestion arg list is empty");
+ }
+ {
+ std::unordered_set<ColumnFamilyHandle*> unique_cfhs;
+ for (const auto& arg : args) {
+ if (arg.column_family == nullptr) {
+ return Status::InvalidArgument("column family handle is null");
+ } else if (unique_cfhs.count(arg.column_family) > 0) {
+ return Status::InvalidArgument(
+ "ingestion args have duplicate column families");
+ }
+ unique_cfhs.insert(arg.column_family);
+ }
+ }
+ // Ingest multiple external SST files atomically.
+ size_t num_cfs = args.size();
+ for (size_t i = 0; i != num_cfs; ++i) {
+ if (args[i].external_files.empty()) {
+ char err_msg[128] = {0};
+ snprintf(err_msg, 128, "external_files[%zu] is empty", i);
+ return Status::InvalidArgument(err_msg);
+ }
+ }
+ for (const auto& arg : args) {
+ const IngestExternalFileOptions& ingest_opts = arg.options;
+ if (ingest_opts.ingest_behind &&
+ !immutable_db_options_.allow_ingest_behind) {
+ return Status::InvalidArgument(
+ "can't ingest_behind file in DB with allow_ingest_behind=false");
+ }
+ }
+
+ // TODO (yanqin) maybe handle the case in which column_families have
+ // duplicates
+ std::unique_ptr<std::list<uint64_t>::iterator> pending_output_elem;
+ size_t total = 0;
+ for (const auto& arg : args) {
+ total += arg.external_files.size();
+ }
+ uint64_t next_file_number = 0;
+ Status status = ReserveFileNumbersBeforeIngestion(
+ static_cast<ColumnFamilyHandleImpl*>(args[0].column_family)->cfd(), total,
+ pending_output_elem, &next_file_number);
+ if (!status.ok()) {
+ InstrumentedMutexLock l(&mutex_);
+ ReleaseFileNumberFromPendingOutputs(pending_output_elem);
+ return status;
+ }
+
+ std::vector<ExternalSstFileIngestionJob> ingestion_jobs;
+ for (const auto& arg : args) {
+ auto* cfd = static_cast<ColumnFamilyHandleImpl*>(arg.column_family)->cfd();
+ ingestion_jobs.emplace_back(
+ env_, versions_.get(), cfd, immutable_db_options_, file_options_,
+ &snapshots_, arg.options, &directories_, &event_logger_);
+ }
+ std::vector<std::pair<bool, Status>> exec_results;
+ for (size_t i = 0; i != num_cfs; ++i) {
+ exec_results.emplace_back(false, Status::OK());
+ }
+ // TODO(yanqin) maybe make jobs run in parallel
+ uint64_t start_file_number = next_file_number;
+ for (size_t i = 1; i != num_cfs; ++i) {
+ start_file_number += args[i - 1].external_files.size();
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
+ SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
+ exec_results[i].second = ingestion_jobs[i].Prepare(
+ args[i].external_files, start_file_number, super_version);
+ exec_results[i].first = true;
+ CleanupSuperVersion(super_version);
+ }
+ TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:0");
+ TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1");
+ {
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[0].column_family)->cfd();
+ SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
+ exec_results[0].second = ingestion_jobs[0].Prepare(
+ args[0].external_files, next_file_number, super_version);
+ exec_results[0].first = true;
+ CleanupSuperVersion(super_version);
+ }
+ for (const auto& exec_result : exec_results) {
+ if (!exec_result.second.ok()) {
+ status = exec_result.second;
+ break;
+ }
+ }
+ if (!status.ok()) {
+ for (size_t i = 0; i != num_cfs; ++i) {
+ if (exec_results[i].first) {
+ ingestion_jobs[i].Cleanup(status);
+ }
+ }
+ InstrumentedMutexLock l(&mutex_);
+ ReleaseFileNumberFromPendingOutputs(pending_output_elem);
+ return status;
+ }
+
+ std::vector<SuperVersionContext> sv_ctxs;
+ for (size_t i = 0; i != num_cfs; ++i) {
+ sv_ctxs.emplace_back(true /* create_superversion */);
+ }
+ TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeJobsRun:0");
+ TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeJobsRun:1");
+ TEST_SYNC_POINT("DBImpl::AddFile:Start");
+ {
+ InstrumentedMutexLock l(&mutex_);
+ TEST_SYNC_POINT("DBImpl::AddFile:MutexLock");
+
+ // Stop writes to the DB by entering both write threads
+ WriteThread::Writer w;
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ WriteThread::Writer nonmem_w;
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+
+ // When unordered_write is enabled, the keys are writing to memtable in an
+ // unordered way. If the ingestion job checks memtable key range before the
+ // key landing in memtable, the ingestion job may skip the necessary
+ // memtable flush.
+ // So wait here to ensure there is no pending write to memtable.
+ WaitForPendingWrites();
+
+ num_running_ingest_file_ += static_cast<int>(num_cfs);
+ TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter");
+
+ bool at_least_one_cf_need_flush = false;
+ std::vector<bool> need_flush(num_cfs, false);
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
+ if (cfd->IsDropped()) {
+ // TODO (yanqin) investigate whether we should abort ingestion or
+ // proceed with other non-dropped column families.
+ status = Status::InvalidArgument(
+ "cannot ingest an external file into a dropped CF");
+ break;
+ }
+ bool tmp = false;
+ status = ingestion_jobs[i].NeedsFlush(&tmp, cfd->GetSuperVersion());
+ need_flush[i] = tmp;
+ at_least_one_cf_need_flush = (at_least_one_cf_need_flush || tmp);
+ if (!status.ok()) {
+ break;
+ }
+ }
+ TEST_SYNC_POINT_CALLBACK("DBImpl::IngestExternalFile:NeedFlush",
+ &at_least_one_cf_need_flush);
+
+ if (status.ok() && at_least_one_cf_need_flush) {
+ FlushOptions flush_opts;
+ flush_opts.allow_write_stall = true;
+ if (immutable_db_options_.atomic_flush) {
+ autovector<ColumnFamilyData*> cfds_to_flush;
+ SelectColumnFamiliesForAtomicFlush(&cfds_to_flush);
+ mutex_.Unlock();
+ status = AtomicFlushMemTables(cfds_to_flush, flush_opts,
+ FlushReason::kExternalFileIngestion,
+ true /* writes_stopped */);
+ mutex_.Lock();
+ } else {
+ for (size_t i = 0; i != num_cfs; ++i) {
+ if (need_flush[i]) {
+ mutex_.Unlock();
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)
+ ->cfd();
+ status = FlushMemTable(cfd, flush_opts,
+ FlushReason::kExternalFileIngestion,
+ true /* writes_stopped */);
+ mutex_.Lock();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Run ingestion jobs.
+ if (status.ok()) {
+ for (size_t i = 0; i != num_cfs; ++i) {
+ status = ingestion_jobs[i].Run();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ }
+ if (status.ok()) {
+ int consumed_seqno_count =
+ ingestion_jobs[0].ConsumedSequenceNumbersCount();
+#ifndef NDEBUG
+ for (size_t i = 1; i != num_cfs; ++i) {
+ assert(!!consumed_seqno_count ==
+ !!ingestion_jobs[i].ConsumedSequenceNumbersCount());
+ consumed_seqno_count +=
+ ingestion_jobs[i].ConsumedSequenceNumbersCount();
+ }
+#endif
+ if (consumed_seqno_count > 0) {
+ const SequenceNumber last_seqno = versions_->LastSequence();
+ versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
+ versions_->SetLastPublishedSequence(last_seqno + consumed_seqno_count);
+ versions_->SetLastSequence(last_seqno + consumed_seqno_count);
+ }
+ autovector<ColumnFamilyData*> cfds_to_commit;
+ autovector<const MutableCFOptions*> mutable_cf_options_list;
+ autovector<autovector<VersionEdit*>> edit_lists;
+ uint32_t num_entries = 0;
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ cfds_to_commit.push_back(cfd);
+ mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions());
+ autovector<VersionEdit*> edit_list;
+ edit_list.push_back(ingestion_jobs[i].edit());
+ edit_lists.push_back(edit_list);
+ ++num_entries;
+ }
+ // Mark the version edits as an atomic group if the number of version
+ // edits exceeds 1.
+ if (cfds_to_commit.size() > 1) {
+ for (auto& edits : edit_lists) {
+ assert(edits.size() == 1);
+ edits[0]->MarkAtomicGroup(--num_entries);
+ }
+ assert(0 == num_entries);
+ }
+ status =
+ versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
+ edit_lists, &mutex_, directories_.GetDbDir());
+ }
+
+ if (status.ok()) {
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
+ if (!cfd->IsDropped()) {
+ InstallSuperVersionAndScheduleWork(cfd, &sv_ctxs[i],
+ *cfd->GetLatestMutableCFOptions());
+#ifndef NDEBUG
+ if (0 == i && num_cfs > 1) {
+ TEST_SYNC_POINT(
+ "DBImpl::IngestExternalFiles:InstallSVForFirstCF:0");
+ TEST_SYNC_POINT(
+ "DBImpl::IngestExternalFiles:InstallSVForFirstCF:1");
+ }
+#endif // !NDEBUG
+ }
+ }
+ }
+
+ // Resume writes to the DB
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+ write_thread_.ExitUnbatched(&w);
+
+ if (status.ok()) {
+ for (auto& job : ingestion_jobs) {
+ job.UpdateStats();
+ }
+ }
+ ReleaseFileNumberFromPendingOutputs(pending_output_elem);
+ num_running_ingest_file_ -= static_cast<int>(num_cfs);
+ if (0 == num_running_ingest_file_) {
+ bg_cv_.SignalAll();
+ }
+ TEST_SYNC_POINT("DBImpl::AddFile:MutexUnlock");
+ }
+ // mutex_ is unlocked here
+
+ // Cleanup
+ for (size_t i = 0; i != num_cfs; ++i) {
+ sv_ctxs[i].Clean();
+ // This may rollback jobs that have completed successfully. This is
+ // intended for atomicity.
+ ingestion_jobs[i].Cleanup(status);
+ }
+ if (status.ok()) {
+ for (size_t i = 0; i != num_cfs; ++i) {
+ auto* cfd =
+ static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
+ if (!cfd->IsDropped()) {
+ NotifyOnExternalFileIngested(cfd, ingestion_jobs[i]);
+ }
+ }
+ }
+ return status;
+}
+
+Status DBImpl::CreateColumnFamilyWithImport(
+ const ColumnFamilyOptions& options, const std::string& column_family_name,
+ const ImportColumnFamilyOptions& import_options,
+ const ExportImportFilesMetaData& metadata, ColumnFamilyHandle** handle) {
+ assert(handle != nullptr);
+ assert(*handle == nullptr);
+ std::string cf_comparator_name = options.comparator->Name();
+ if (cf_comparator_name != metadata.db_comparator_name) {
+ return Status::InvalidArgument("Comparator name mismatch");
+ }
+
+ // Create column family.
+ auto status = CreateColumnFamily(options, column_family_name, handle);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Import sst files from metadata.
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(*handle);
+ auto cfd = cfh->cfd();
+ ImportColumnFamilyJob import_job(env_, versions_.get(), cfd,
+ immutable_db_options_, file_options_,
+ import_options, metadata.files);
+
+ SuperVersionContext dummy_sv_ctx(/* create_superversion */ true);
+ VersionEdit dummy_edit;
+ uint64_t next_file_number = 0;
+ std::unique_ptr<std::list<uint64_t>::iterator> pending_output_elem;
+ {
+ // Lock db mutex
+ InstrumentedMutexLock l(&mutex_);
+ if (error_handler_.IsDBStopped()) {
+ // Don't import files when there is a bg_error
+ status = error_handler_.GetBGError();
+ }
+
+ // Make sure that bg cleanup wont delete the files that we are importing
+ pending_output_elem.reset(new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+
+ if (status.ok()) {
+ // If crash happen after a hard link established, Recover function may
+ // reuse the file number that has already assigned to the internal file,
+ // and this will overwrite the external file. To protect the external
+ // file, we have to make sure the file number will never being reused.
+ next_file_number = versions_->FetchAddFileNumber(metadata.files.size());
+ auto cf_options = cfd->GetLatestMutableCFOptions();
+ status = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
+ directories_.GetDbDir());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
+ }
+ }
+ }
+ dummy_sv_ctx.Clean();
+
+ if (status.ok()) {
+ SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
+ status = import_job.Prepare(next_file_number, sv);
+ CleanupSuperVersion(sv);
+ }
+
+ if (status.ok()) {
+ SuperVersionContext sv_context(true /*create_superversion*/);
+ {
+ // Lock db mutex
+ InstrumentedMutexLock l(&mutex_);
+
+ // Stop writes to the DB by entering both write threads
+ WriteThread::Writer w;
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ WriteThread::Writer nonmem_w;
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+
+ num_running_ingest_file_++;
+ assert(!cfd->IsDropped());
+ status = import_job.Run();
+
+ // Install job edit [Mutex will be unlocked here]
+ if (status.ok()) {
+ auto cf_options = cfd->GetLatestMutableCFOptions();
+ status = versions_->LogAndApply(cfd, *cf_options, import_job.edit(),
+ &mutex_, directories_.GetDbDir());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd, &sv_context, *cf_options);
+ }
+ }
+
+ // Resume writes to the DB
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+ write_thread_.ExitUnbatched(&w);
+
+ num_running_ingest_file_--;
+ if (num_running_ingest_file_ == 0) {
+ bg_cv_.SignalAll();
+ }
+ }
+ // mutex_ is unlocked here
+
+ sv_context.Clean();
+ }
+
+ {
+ InstrumentedMutexLock l(&mutex_);
+ ReleaseFileNumberFromPendingOutputs(pending_output_elem);
+ }
+
+ import_job.Cleanup(status);
+ if (!status.ok()) {
+ DropColumnFamily(*handle);
+ DestroyColumnFamilyHandle(*handle);
+ *handle = nullptr;
+ }
+ return status;
+}
+
+Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
+ Status s;
+ std::vector<ColumnFamilyData*> cfd_list;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (!cfd->IsDropped() && cfd->initialized()) {
+ cfd->Ref();
+ cfd_list.push_back(cfd);
+ }
+ }
+ }
+ std::vector<SuperVersion*> sv_list;
+ for (auto cfd : cfd_list) {
+ sv_list.push_back(cfd->GetReferencedSuperVersion(this));
+ }
+ for (auto& sv : sv_list) {
+ VersionStorageInfo* vstorage = sv->current->storage_info();
+ ColumnFamilyData* cfd = sv->current->cfd();
+ Options opts;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ opts = Options(BuildDBOptions(immutable_db_options_, mutable_db_options_),
+ cfd->GetLatestCFOptions());
+ }
+ for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) {
+ for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok();
+ j++) {
+ const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
+ std::string fname = TableFileName(cfd->ioptions()->cf_paths,
+ fd.GetNumber(), fd.GetPathId());
+ s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
+ read_options, fname);
+ }
+ }
+ if (!s.ok()) {
+ break;
+ }
+ }
+ bool defer_purge =
+ immutable_db_options().avoid_unnecessary_blocking_io;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ for (auto sv : sv_list) {
+ if (sv && sv->Unref()) {
+ sv->Cleanup();
+ if (defer_purge) {
+ AddSuperVersionsToFreeQueue(sv);
+ } else {
+ delete sv;
+ }
+ }
+ }
+ if (defer_purge) {
+ SchedulePurge();
+ }
+ for (auto cfd : cfd_list) {
+ cfd->UnrefAndTryDelete();
+ }
+ }
+ return s;
+}
+
+void DBImpl::NotifyOnExternalFileIngested(
+ ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) {
+ if (immutable_db_options_.listeners.empty()) {
+ return;
+ }
+
+ for (const IngestedFileInfo& f : ingestion_job.files_to_ingest()) {
+ ExternalFileIngestionInfo info;
+ info.cf_name = cfd->GetName();
+ info.external_file_path = f.external_file_path;
+ info.internal_file_path = f.internal_file_path;
+ info.global_seqno = f.assigned_seqno;
+ info.table_properties = f.table_properties;
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnExternalFileIngested(this, info);
+ }
+ }
+}
+
+void DBImpl::WaitForIngestFile() {
+ mutex_.AssertHeld();
+ while (num_running_ingest_file_ > 0) {
+ bg_cv_.Wait();
+ }
+}
+
+Status DBImpl::StartTrace(const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ tracer_.reset(new Tracer(env_, trace_options, std::move(trace_writer)));
+ return Status::OK();
+}
+
+Status DBImpl::EndTrace() {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ Status s;
+ if (tracer_ != nullptr) {
+ s = tracer_->Close();
+ tracer_.reset();
+ } else {
+ return Status::IOError("No trace file to close");
+ }
+ return s;
+}
+
+Status DBImpl::StartBlockCacheTrace(
+ const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer) {
+ return block_cache_tracer_.StartTrace(env_, trace_options,
+ std::move(trace_writer));
+}
+
+Status DBImpl::EndBlockCacheTrace() {
+ block_cache_tracer_.EndTrace();
+ return Status::OK();
+}
+
+Status DBImpl::TraceIteratorSeek(const uint32_t& cf_id, const Slice& key) {
+ Status s;
+ if (tracer_) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ s = tracer_->IteratorSeek(cf_id, key);
+ }
+ }
+ return s;
+}
+
+Status DBImpl::TraceIteratorSeekForPrev(const uint32_t& cf_id,
+ const Slice& key) {
+ Status s;
+ if (tracer_) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ s = tracer_->IteratorSeekForPrev(cf_id, key);
+ }
+ }
+ return s;
+}
+
+Status DBImpl::ReserveFileNumbersBeforeIngestion(
+ ColumnFamilyData* cfd, uint64_t num,
+ std::unique_ptr<std::list<uint64_t>::iterator>& pending_output_elem,
+ uint64_t* next_file_number) {
+ Status s;
+ SuperVersionContext dummy_sv_ctx(true /* create_superversion */);
+ assert(nullptr != next_file_number);
+ InstrumentedMutexLock l(&mutex_);
+ if (error_handler_.IsDBStopped()) {
+ // Do not ingest files when there is a bg_error
+ return error_handler_.GetBGError();
+ }
+ pending_output_elem.reset(new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+ *next_file_number = versions_->FetchAddFileNumber(static_cast<uint64_t>(num));
+ auto cf_options = cfd->GetLatestMutableCFOptions();
+ VersionEdit dummy_edit;
+ // If crash happen after a hard link established, Recover function may
+ // reuse the file number that has already assigned to the internal file,
+ // and this will overwrite the external file. To protect the external
+ // file, we have to make sure the file number will never being reused.
+ s = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
+ directories_.GetDbDir());
+ if (s.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
+ }
+ dummy_sv_ctx.Clean();
+ return s;
+}
+
+Status DBImpl::GetCreationTimeOfOldestFile(uint64_t* creation_time) {
+ if (mutable_db_options_.max_open_files == -1) {
+ uint64_t oldest_time = port::kMaxUint64;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (!cfd->IsDropped()) {
+ uint64_t ctime;
+ {
+ SuperVersion* sv = GetAndRefSuperVersion(cfd);
+ Version* version = sv->current;
+ version->GetCreationTimeOfOldestFile(&ctime);
+ ReturnAndCleanupSuperVersion(cfd, sv);
+ }
+
+ if (ctime < oldest_time) {
+ oldest_time = ctime;
+ }
+ if (oldest_time == 0) {
+ break;
+ }
+ }
+ }
+ *creation_time = oldest_time;
+ return Status::OK();
+ } else {
+ return Status::NotSupported("This API only works if max_open_files = -1");
+ }
+}
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl.h b/storage/rocksdb/rocksdb/db/db_impl/db_impl.h
new file mode 100644
index 00000000..119555cb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl.h
@@ -0,0 +1,2107 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <deque>
+#include <functional>
+#include <limits>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/compaction/compaction_job.h"
+#include "db/dbformat.h"
+#include "db/error_handler.h"
+#include "db/event_helpers.h"
+#include "db/external_sst_file_ingestion_job.h"
+#include "db/flush_job.h"
+#include "db/flush_scheduler.h"
+#include "db/import_column_family_job.h"
+#include "db/internal_stats.h"
+#include "db/log_writer.h"
+#include "db/logs_with_prep_tracker.h"
+#include "db/memtable_list.h"
+#include "db/pre_release_callback.h"
+#include "db/range_del_aggregator.h"
+#include "db/read_callback.h"
+#include "db/snapshot_checker.h"
+#include "db/snapshot_impl.h"
+#include "db/trim_history_scheduler.h"
+#include "db/version_edit.h"
+#include "db/wal_manager.h"
+#include "db/write_controller.h"
+#include "db/write_thread.h"
+#include "logging/event_logger.h"
+#include "monitoring/instrumented_mutex.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/status.h"
+#include "rocksdb/trace_reader_writer.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/scoped_arena_iterator.h"
+#include "trace_replay/block_cache_tracer.h"
+#include "trace_replay/trace_replay.h"
+#include "util/autovector.h"
+#include "util/hash.h"
+#include "util/repeatable_thread.h"
+#include "util/stop_watch.h"
+#include "util/thread_local.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena;
+class ArenaWrappedDBIter;
+class InMemoryStatsHistoryIterator;
+class MemTable;
+class PersistentStatsHistoryIterator;
+class TableCache;
+class TaskLimiterToken;
+class Version;
+class VersionEdit;
+class VersionSet;
+class WriteCallback;
+struct JobContext;
+struct ExternalSstFileInfo;
+struct MemTableInfo;
+
+// Class to maintain directories for all database paths other than main one.
+class Directories {
+ public:
+ Status SetDirectories(Env* env, const std::string& dbname,
+ const std::string& wal_dir,
+ const std::vector<DbPath>& data_paths);
+
+ Directory* GetDataDir(size_t path_id) const {
+ assert(path_id < data_dirs_.size());
+ Directory* ret_dir = data_dirs_[path_id].get();
+ if (ret_dir == nullptr) {
+ // Should use db_dir_
+ return db_dir_.get();
+ }
+ return ret_dir;
+ }
+
+ Directory* GetWalDir() {
+ if (wal_dir_) {
+ return wal_dir_.get();
+ }
+ return db_dir_.get();
+ }
+
+ Directory* GetDbDir() { return db_dir_.get(); }
+
+ private:
+ std::unique_ptr<Directory> db_dir_;
+ std::vector<std::unique_ptr<Directory>> data_dirs_;
+ std::unique_ptr<Directory> wal_dir_;
+};
+
+// While DB is the public interface of RocksDB, and DBImpl is the actual
+// class implementing it. It's the entrance of the core RocksdB engine.
+// All other DB implementations, e.g. TransactionDB, BlobDB, etc, wrap a
+// DBImpl internally.
+// Other than functions implementing the DB interface, some public
+// functions are there for other internal components to call. For
+// example, TransactionDB directly calls DBImpl::WriteImpl() and
+// BlobDB directly calls DBImpl::GetImpl(). Some other functions
+// are for sub-components to call. For example, ColumnFamilyHandleImpl
+// calls DBImpl::FindObsoleteFiles().
+//
+// Since it's a very large class, the definition of the functions is
+// divided in several db_impl_*.cc files, besides db_impl.cc.
+class DBImpl : public DB {
+ public:
+ DBImpl(const DBOptions& options, const std::string& dbname,
+ const bool seq_per_batch = false, const bool batch_per_txn = true);
+ // No copying allowed
+ DBImpl(const DBImpl&) = delete;
+ void operator=(const DBImpl&) = delete;
+
+ virtual ~DBImpl();
+
+ // ---- Implementations of the DB interface ----
+
+ using DB::Resume;
+ virtual Status Resume() override;
+
+ using DB::Put;
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ using DB::Merge;
+ virtual Status Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ using DB::Delete;
+ virtual Status Delete(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ using DB::SingleDelete;
+ virtual Status SingleDelete(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ using DB::Write;
+ virtual Status Write(const WriteOptions& options,
+ WriteBatch* updates) override;
+
+ using DB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ using DB::GetMergeOperands;
+ Status GetMergeOperands(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* merge_operands,
+ GetMergeOperandsOptions* get_merge_operands_options,
+ int* number_of_operands) override {
+ GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.merge_operands = merge_operands;
+ get_impl_options.get_merge_operands_options = get_merge_operands_options;
+ get_impl_options.number_of_operands = number_of_operands;
+ get_impl_options.get_value = false;
+ return GetImpl(options, key, get_impl_options);
+ }
+
+ using DB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ // This MultiGet is a batched version, which may be faster than calling Get
+ // multiple times, especially if the keys have some spatial locality that
+ // enables them to be queried in the same SST files/set of files. The larger
+ // the batch size, the more scope for batching and performance improvement
+ // The values and statuses parameters are arrays with number of elements
+ // equal to keys.size(). This allows the storage for those to be alloacted
+ // by the caller on the stack for small batches
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input = false) override;
+
+ virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
+ ColumnFamilyHandle** column_families, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input = false) override;
+
+ virtual void MultiGetWithCallback(
+ const ReadOptions& options, ColumnFamilyHandle* column_family,
+ ReadCallback* callback,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys);
+
+ virtual Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
+ const std::string& column_family,
+ ColumnFamilyHandle** handle) override;
+ virtual Status CreateColumnFamilies(
+ const ColumnFamilyOptions& cf_options,
+ const std::vector<std::string>& column_family_names,
+ std::vector<ColumnFamilyHandle*>* handles) override;
+ virtual Status CreateColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles) override;
+ virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) override;
+ virtual Status DropColumnFamilies(
+ const std::vector<ColumnFamilyHandle*>& column_families) override;
+
+ // Returns false if key doesn't exist in the database and true if it may.
+ // If value_found is not passed in as null, then return the value if found in
+ // memory. On return, if value was found, then value_found will be set to true
+ // , otherwise false.
+ using DB::KeyMayExist;
+ virtual bool KeyMayExist(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value,
+ bool* value_found = nullptr) override;
+
+ using DB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) override;
+ virtual Status NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) override;
+
+ virtual const Snapshot* GetSnapshot() override;
+ virtual void ReleaseSnapshot(const Snapshot* snapshot) override;
+ using DB::GetProperty;
+ virtual bool GetProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, std::string* value) override;
+ using DB::GetMapProperty;
+ virtual bool GetMapProperty(
+ ColumnFamilyHandle* column_family, const Slice& property,
+ std::map<std::string, std::string>* value) override;
+ using DB::GetIntProperty;
+ virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, uint64_t* value) override;
+ using DB::GetAggregatedIntProperty;
+ virtual bool GetAggregatedIntProperty(const Slice& property,
+ uint64_t* aggregated_value) override;
+ using DB::GetApproximateSizes;
+ virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Range* range, int n,
+ uint64_t* sizes) override;
+ using DB::GetApproximateMemTableStats;
+ virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
+ const Range& range,
+ uint64_t* const count,
+ uint64_t* const size) override;
+ using DB::CompactRange;
+ virtual Status CompactRange(const CompactRangeOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) override;
+
+ using DB::CompactFiles;
+ virtual Status CompactFiles(
+ const CompactionOptions& compact_options,
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) override;
+
+ virtual Status PauseBackgroundWork() override;
+ virtual Status ContinueBackgroundWork() override;
+
+ virtual Status EnableAutoCompaction(
+ const std::vector<ColumnFamilyHandle*>& column_family_handles) override;
+
+ virtual void EnableManualCompaction() override;
+ virtual void DisableManualCompaction() override;
+
+ using DB::SetOptions;
+ Status SetOptions(
+ ColumnFamilyHandle* column_family,
+ const std::unordered_map<std::string, std::string>& options_map) override;
+
+ virtual Status SetDBOptions(
+ const std::unordered_map<std::string, std::string>& options_map) override;
+
+ using DB::NumberLevels;
+ virtual int NumberLevels(ColumnFamilyHandle* column_family) override;
+ using DB::MaxMemCompactionLevel;
+ virtual int MaxMemCompactionLevel(ColumnFamilyHandle* column_family) override;
+ using DB::Level0StopWriteTrigger;
+ virtual int Level0StopWriteTrigger(
+ ColumnFamilyHandle* column_family) override;
+ virtual const std::string& GetName() const override;
+ virtual Env* GetEnv() const override;
+ virtual FileSystem* GetFileSystem() const override;
+ using DB::GetOptions;
+ virtual Options GetOptions(ColumnFamilyHandle* column_family) const override;
+ using DB::GetDBOptions;
+ virtual DBOptions GetDBOptions() const override;
+ using DB::Flush;
+ virtual Status Flush(const FlushOptions& options,
+ ColumnFamilyHandle* column_family) override;
+ virtual Status Flush(
+ const FlushOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families) override;
+ virtual Status FlushWAL(bool sync) override;
+ bool TEST_WALBufferIsEmpty(bool lock = true);
+ virtual Status SyncWAL() override;
+ virtual Status LockWAL() override;
+ virtual Status UnlockWAL() override;
+
+ virtual SequenceNumber GetLatestSequenceNumber() const override;
+
+ virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) override;
+
+ virtual Status GetDbIdentity(std::string& identity) const override;
+
+ virtual Status GetDbIdentityFromIdentityFile(std::string* identity) const;
+
+ ColumnFamilyHandle* DefaultColumnFamily() const override;
+
+ ColumnFamilyHandle* PersistentStatsColumnFamily() const;
+
+ virtual Status Close() override;
+
+ Status GetStatsHistory(
+ uint64_t start_time, uint64_t end_time,
+ std::unique_ptr<StatsHistoryIterator>* stats_iterator) override;
+
+#ifndef ROCKSDB_LITE
+ using DB::ResetStats;
+ virtual Status ResetStats() override;
+ virtual Status DisableFileDeletions() override;
+ virtual Status EnableFileDeletions(bool force) override;
+ virtual int IsFileDeletionsEnabled() const;
+ // All the returned filenames start with "/"
+ virtual Status GetLiveFiles(std::vector<std::string>&,
+ uint64_t* manifest_file_size,
+ bool flush_memtable = true) override;
+ virtual Status GetSortedWalFiles(VectorLogPtr& files) override;
+ virtual Status GetCurrentWalFile(
+ std::unique_ptr<LogFile>* current_log_file) override;
+ virtual Status GetCreationTimeOfOldestFile(
+ uint64_t* creation_time) override;
+
+ virtual Status GetUpdatesSince(
+ SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options =
+ TransactionLogIterator::ReadOptions()) override;
+ virtual Status DeleteFile(std::string name) override;
+ Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
+ const RangePtr* ranges, size_t n,
+ bool include_end = true);
+
+ virtual void GetLiveFilesMetaData(
+ std::vector<LiveFileMetaData>* metadata) override;
+
+ // Obtains the meta data of the specified column family of the DB.
+ // Status::NotFound() will be returned if the current DB does not have
+ // any column family match the specified name.
+ // TODO(yhchiang): output parameter is placed in the end in this codebase.
+ virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
+ ColumnFamilyMetaData* metadata) override;
+
+ Status SuggestCompactRange(ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) override;
+
+ Status PromoteL0(ColumnFamilyHandle* column_family,
+ int target_level) override;
+
+ using DB::IngestExternalFile;
+ virtual Status IngestExternalFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& external_files,
+ const IngestExternalFileOptions& ingestion_options) override;
+
+ using DB::IngestExternalFiles;
+ virtual Status IngestExternalFiles(
+ const std::vector<IngestExternalFileArg>& args) override;
+
+ 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;
+
+ using DB::VerifyChecksum;
+ virtual Status VerifyChecksum(const ReadOptions& /*read_options*/) override;
+
+ using DB::StartTrace;
+ virtual Status StartTrace(
+ const TraceOptions& options,
+ std::unique_ptr<TraceWriter>&& trace_writer) override;
+
+ using DB::EndTrace;
+ virtual Status EndTrace() override;
+
+ using DB::StartBlockCacheTrace;
+ Status StartBlockCacheTrace(
+ const TraceOptions& options,
+ std::unique_ptr<TraceWriter>&& trace_writer) override;
+
+ using DB::EndBlockCacheTrace;
+ Status EndBlockCacheTrace() override;
+
+ using DB::GetPropertiesOfAllTables;
+ virtual Status GetPropertiesOfAllTables(
+ ColumnFamilyHandle* column_family,
+ TablePropertiesCollection* props) override;
+ virtual Status GetPropertiesOfTablesInRange(
+ ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
+ TablePropertiesCollection* props) override;
+
+#endif // ROCKSDB_LITE
+
+ // ---- End of implementations of the DB interface ----
+
+ struct GetImplOptions {
+ ColumnFamilyHandle* column_family = nullptr;
+ PinnableSlice* value = nullptr;
+ bool* value_found = nullptr;
+ ReadCallback* callback = nullptr;
+ bool* is_blob_index = nullptr;
+ // If true return value associated with key via value pointer else return
+ // all merge operands for key via merge_operands pointer
+ bool get_value = true;
+ // Pointer to an array of size
+ // get_merge_operands_options.expected_max_number_of_operands allocated by
+ // user
+ PinnableSlice* merge_operands = nullptr;
+ GetMergeOperandsOptions* get_merge_operands_options = nullptr;
+ int* number_of_operands = nullptr;
+ };
+
+ // Function that Get and KeyMayExist call with no_io true or false
+ // Note: 'value_found' from KeyMayExist propagates here
+ // This function is also called by GetMergeOperands
+ // If get_impl_options.get_value = true get value associated with
+ // get_impl_options.key via get_impl_options.value
+ // If get_impl_options.get_value = false get merge operands associated with
+ // get_impl_options.key via get_impl_options.merge_operands
+ Status GetImpl(const ReadOptions& options, const Slice& key,
+ GetImplOptions get_impl_options);
+
+ ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& options,
+ ColumnFamilyData* cfd,
+ SequenceNumber snapshot,
+ ReadCallback* read_callback,
+ bool allow_blob = false,
+ bool allow_refresh = true);
+
+ virtual SequenceNumber GetLastPublishedSequence() const {
+ if (last_seq_same_as_publish_seq_) {
+ return versions_->LastSequence();
+ } else {
+ return versions_->LastPublishedSequence();
+ }
+ }
+
+ // REQUIRES: joined the main write queue if two_write_queues is disabled, and
+ // the second write queue otherwise.
+ virtual void SetLastPublishedSequence(SequenceNumber seq);
+ // Returns LastSequence in last_seq_same_as_publish_seq_
+ // mode and LastAllocatedSequence otherwise. This is useful when visiblility
+ // depends also on data written to the WAL but not to the memtable.
+ SequenceNumber TEST_GetLastVisibleSequence() const;
+
+#ifndef ROCKSDB_LITE
+ // Similar to Write() but will call the callback once on the single write
+ // thread to determine whether it is safe to perform the write.
+ virtual Status WriteWithCallback(const WriteOptions& write_options,
+ WriteBatch* my_batch,
+ WriteCallback* callback);
+
+ // Returns the sequence number that is guaranteed to be smaller than or equal
+ // to the sequence number of any key that could be inserted into the current
+ // memtables. 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.
+ //
+ // If the earliest sequence number could not be determined,
+ // kMaxSequenceNumber will be returned.
+ //
+ // If include_history=true, will also search Memtables in MemTableList
+ // History.
+ SequenceNumber GetEarliestMemTableSequenceNumber(SuperVersion* sv,
+ bool include_history);
+
+ // For a given key, check to see if there are any records for this key
+ // in the memtables, including memtable history. If cache_only is false,
+ // SST files will also be checked.
+ //
+ // If a key is found, *found_record_for_key will be set to true and
+ // *seq will be set to the stored sequence number for the latest
+ // operation on this key or kMaxSequenceNumber if unknown.
+ // If no key is found, *found_record_for_key will be set to false.
+ //
+ // Note: If cache_only=false, it is possible for *seq to be set to 0 if
+ // the sequence number has been cleared from the record. If the caller is
+ // holding an active db snapshot, we know the missing sequence must be less
+ // than the snapshot's sequence number (sequence numbers are only cleared
+ // when there are no earlier active snapshots).
+ //
+ // If NotFound is returned and found_record_for_key is set to false, then no
+ // record for this key was found. If the caller is holding an active db
+ // snapshot, we know that no key could have existing after this snapshot
+ // (since we do not compact keys that have an earlier snapshot).
+ //
+ // Only records newer than or at `lower_bound_seq` are guaranteed to be
+ // returned. Memtables and files may not be checked if it only contains data
+ // older than `lower_bound_seq`.
+ //
+ // Returns OK or NotFound on success,
+ // other status on unexpected error.
+ // TODO(andrewkr): this API need to be aware of range deletion operations
+ Status GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
+ bool cache_only,
+ SequenceNumber lower_bound_seq,
+ SequenceNumber* seq,
+ bool* found_record_for_key,
+ bool* is_blob_index = nullptr);
+
+ Status TraceIteratorSeek(const uint32_t& cf_id, const Slice& key);
+ Status TraceIteratorSeekForPrev(const uint32_t& cf_id, const Slice& key);
+#endif // ROCKSDB_LITE
+
+ // Similar to GetSnapshot(), but also lets the db know that this snapshot
+ // will be used for transaction write-conflict checking. The DB can then
+ // make sure not to compact any keys that would prevent a write-conflict from
+ // being detected.
+ const Snapshot* GetSnapshotForWriteConflictBoundary();
+
+ // checks if all live files exist on file system and that their file sizes
+ // match to our in-memory records
+ virtual Status CheckConsistency();
+
+ // max_file_num_to_ignore allows bottom level compaction to filter out newly
+ // compacted SST files. Setting max_file_num_to_ignore to kMaxUint64 will
+ // disable the filtering
+ Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
+ int output_level,
+ const CompactRangeOptions& compact_range_options,
+ const Slice* begin, const Slice* end,
+ bool exclusive, bool disallow_trivial_move,
+ uint64_t max_file_num_to_ignore);
+
+ // Return an internal iterator over the current state of the database.
+ // The keys of this iterator are internal keys (see format.h).
+ // The returned iterator should be deleted when no longer needed.
+ InternalIterator* NewInternalIterator(
+ Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence,
+ ColumnFamilyHandle* column_family = nullptr);
+
+ LogsWithPrepTracker* logs_with_prep_tracker() {
+ return &logs_with_prep_tracker_;
+ }
+
+ struct BGJobLimits {
+ int max_flushes;
+ int max_compactions;
+ };
+ // Returns maximum background flushes and compactions allowed to be scheduled
+ BGJobLimits GetBGJobLimits() const;
+ // Need a static version that can be called during SanitizeOptions().
+ static BGJobLimits GetBGJobLimits(int max_background_flushes,
+ int max_background_compactions,
+ int max_background_jobs,
+ bool parallelize_compactions);
+
+ // move logs pending closing from job_context to the DB queue and
+ // schedule a purge
+ void ScheduleBgLogWriterClose(JobContext* job_context);
+
+ uint64_t MinLogNumberToKeep();
+
+ // Returns the lower bound file number for SSTs that won't be deleted, even if
+ // they're obsolete. This lower bound is used internally to prevent newly
+ // created flush/compaction output files from being deleted before they're
+ // installed. This technique avoids the need for tracking the exact numbers of
+ // files pending creation, although it prevents more files than necessary from
+ // being deleted.
+ uint64_t MinObsoleteSstNumberToKeep();
+
+ // Returns the list of live files in 'live' and the list
+ // of all files in the filesystem in 'candidate_files'.
+ // If force == false and the last call was less than
+ // db_options_.delete_obsolete_files_period_micros microseconds ago,
+ // it will not fill up the job_context
+ void FindObsoleteFiles(JobContext* job_context, bool force,
+ bool no_full_scan = false);
+
+ // Diffs the files listed in filenames and those that do not
+ // belong to live files are possibly removed. Also, removes all the
+ // files in sst_delete_files and log_delete_files.
+ // It is not necessary to hold the mutex when invoking this method.
+ // If FindObsoleteFiles() was run, we need to also run
+ // PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true
+ void PurgeObsoleteFiles(JobContext& background_contet,
+ bool schedule_only = false);
+
+ // Schedule a background job to actually delete obsolete files.
+ void SchedulePurge();
+
+ const SnapshotList& snapshots() const { return snapshots_; }
+
+ // load list of snapshots to `snap_vector` that is no newer than `max_seq`
+ // in ascending order.
+ // `oldest_write_conflict_snapshot` is filled with the oldest snapshot
+ // which satisfies SnapshotImpl.is_write_conflict_boundary_ = true.
+ void LoadSnapshots(std::vector<SequenceNumber>* snap_vector,
+ SequenceNumber* oldest_write_conflict_snapshot,
+ const SequenceNumber& max_seq) const {
+ InstrumentedMutexLock l(mutex());
+ snapshots().GetAll(snap_vector, oldest_write_conflict_snapshot, max_seq);
+ }
+
+ const ImmutableDBOptions& immutable_db_options() const {
+ return immutable_db_options_;
+ }
+
+ // Cancel all background jobs, including flush, compaction, background
+ // purging, stats dumping threads, etc. If `wait` = true, wait for the
+ // running jobs to abort or finish before returning. Otherwise, only
+ // sends the signals.
+ void CancelAllBackgroundWork(bool wait);
+
+ // Find Super version and reference it. Based on options, it might return
+ // the thread local cached one.
+ // Call ReturnAndCleanupSuperVersion() when it is no longer needed.
+ SuperVersion* GetAndRefSuperVersion(ColumnFamilyData* cfd);
+
+ // Similar to the previous function but looks up based on a column family id.
+ // nullptr will be returned if this column family no longer exists.
+ // REQUIRED: this function should only be called on the write thread or if the
+ // mutex is held.
+ SuperVersion* GetAndRefSuperVersion(uint32_t column_family_id);
+
+ // Un-reference the super version and clean it up if it is the last reference.
+ void CleanupSuperVersion(SuperVersion* sv);
+
+ // Un-reference the super version and return it to thread local cache if
+ // needed. If it is the last reference of the super version. Clean it up
+ // after un-referencing it.
+ void ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd, SuperVersion* sv);
+
+ // Similar to the previous function but looks up based on a column family id.
+ // nullptr will be returned if this column family no longer exists.
+ // REQUIRED: this function should only be called on the write thread.
+ void ReturnAndCleanupSuperVersion(uint32_t colun_family_id, SuperVersion* sv);
+
+ // REQUIRED: this function should only be called on the write thread or if the
+ // mutex is held. Return value only valid until next call to this function or
+ // mutex is released.
+ ColumnFamilyHandle* GetColumnFamilyHandle(uint32_t column_family_id);
+
+ // Same as above, should called without mutex held and not on write thread.
+ std::unique_ptr<ColumnFamilyHandle> GetColumnFamilyHandleUnlocked(
+ uint32_t column_family_id);
+
+ // Returns the number of currently running flushes.
+ // REQUIREMENT: mutex_ must be held when calling this function.
+ int num_running_flushes() {
+ mutex_.AssertHeld();
+ return num_running_flushes_;
+ }
+
+ // Returns the number of currently running compactions.
+ // REQUIREMENT: mutex_ must be held when calling this function.
+ int num_running_compactions() {
+ mutex_.AssertHeld();
+ return num_running_compactions_;
+ }
+
+ const WriteController& write_controller() { return write_controller_; }
+
+ InternalIterator* NewInternalIterator(
+ const ReadOptions&, ColumnFamilyData* cfd, SuperVersion* super_version,
+ Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence);
+
+ // hollow transactions shell used for recovery.
+ // these will then be passed to TransactionDB so that
+ // locks can be reacquired before writing can resume.
+ struct RecoveredTransaction {
+ std::string name_;
+ bool unprepared_;
+
+ struct BatchInfo {
+ uint64_t log_number_;
+ // TODO(lth): For unprepared, the memory usage here can be big for
+ // unprepared transactions. This is only useful for rollbacks, and we
+ // can in theory just keep keyset for that.
+ WriteBatch* batch_;
+ // Number of sub-batches. A new sub-batch is created if txn attempts to
+ // insert a duplicate key,seq to memtable. This is currently used in
+ // WritePreparedTxn/WriteUnpreparedTxn.
+ size_t batch_cnt_;
+ };
+
+ // This maps the seq of the first key in the batch to BatchInfo, which
+ // contains WriteBatch and other information relevant to the batch.
+ //
+ // For WriteUnprepared, batches_ can have size greater than 1, but for
+ // other write policies, it must be of size 1.
+ std::map<SequenceNumber, BatchInfo> batches_;
+
+ explicit RecoveredTransaction(const uint64_t log, const std::string& name,
+ WriteBatch* batch, SequenceNumber seq,
+ size_t batch_cnt, bool unprepared)
+ : name_(name), unprepared_(unprepared) {
+ batches_[seq] = {log, batch, batch_cnt};
+ }
+
+ ~RecoveredTransaction() {
+ for (auto& it : batches_) {
+ delete it.second.batch_;
+ }
+ }
+
+ void AddBatch(SequenceNumber seq, uint64_t log_number, WriteBatch* batch,
+ size_t batch_cnt, bool unprepared) {
+ assert(batches_.count(seq) == 0);
+ batches_[seq] = {log_number, batch, batch_cnt};
+ // Prior state must be unprepared, since the prepare batch must be the
+ // last batch.
+ assert(unprepared_);
+ unprepared_ = unprepared;
+ }
+ };
+
+ bool allow_2pc() const { return immutable_db_options_.allow_2pc; }
+
+ std::unordered_map<std::string, RecoveredTransaction*>
+ recovered_transactions() {
+ return recovered_transactions_;
+ }
+
+ RecoveredTransaction* GetRecoveredTransaction(const std::string& name) {
+ auto it = recovered_transactions_.find(name);
+ if (it == recovered_transactions_.end()) {
+ return nullptr;
+ } else {
+ return it->second;
+ }
+ }
+
+ void InsertRecoveredTransaction(const uint64_t log, const std::string& name,
+ WriteBatch* batch, SequenceNumber seq,
+ size_t batch_cnt, bool unprepared_batch) {
+ // For WriteUnpreparedTxn, InsertRecoveredTransaction is called multiple
+ // times for every unprepared batch encountered during recovery.
+ //
+ // If the transaction is prepared, then the last call to
+ // InsertRecoveredTransaction will have unprepared_batch = false.
+ auto rtxn = recovered_transactions_.find(name);
+ if (rtxn == recovered_transactions_.end()) {
+ recovered_transactions_[name] = new RecoveredTransaction(
+ log, name, batch, seq, batch_cnt, unprepared_batch);
+ } else {
+ rtxn->second->AddBatch(seq, log, batch, batch_cnt, unprepared_batch);
+ }
+ logs_with_prep_tracker_.MarkLogAsContainingPrepSection(log);
+ }
+
+ void DeleteRecoveredTransaction(const std::string& name) {
+ auto it = recovered_transactions_.find(name);
+ assert(it != recovered_transactions_.end());
+ auto* trx = it->second;
+ recovered_transactions_.erase(it);
+ for (const auto& info : trx->batches_) {
+ logs_with_prep_tracker_.MarkLogAsHavingPrepSectionFlushed(
+ info.second.log_number_);
+ }
+ delete trx;
+ }
+
+ void DeleteAllRecoveredTransactions() {
+ for (auto it = recovered_transactions_.begin();
+ it != recovered_transactions_.end(); ++it) {
+ delete it->second;
+ }
+ recovered_transactions_.clear();
+ }
+
+ void AddToLogsToFreeQueue(log::Writer* log_writer) {
+ logs_to_free_queue_.push_back(log_writer);
+ }
+
+ void AddSuperVersionsToFreeQueue(SuperVersion* sv) {
+ superversions_to_free_queue_.push_back(sv);
+ }
+
+ void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
+
+ // Fill JobContext with snapshot information needed by flush and compaction.
+ void GetSnapshotContext(JobContext* job_context,
+ std::vector<SequenceNumber>* snapshot_seqs,
+ SequenceNumber* earliest_write_conflict_snapshot,
+ SnapshotChecker** snapshot_checker);
+
+ // Not thread-safe.
+ void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
+
+ InstrumentedMutex* mutex() const { return &mutex_; }
+
+ // Initialize a brand new DB. The DB directory is expected to be empty before
+ // calling it.
+ Status NewDB();
+
+ // This is to be used only by internal rocksdb classes.
+ static Status Open(const DBOptions& db_options, const std::string& name,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
+ const bool seq_per_batch, const bool batch_per_txn);
+
+ static Status CreateAndNewDirectory(Env* env, const std::string& dirname,
+ std::unique_ptr<Directory>* directory);
+
+ // find stats map from stats_history_ with smallest timestamp in
+ // the range of [start_time, end_time)
+ bool FindStatsByTime(uint64_t start_time, uint64_t end_time,
+ uint64_t* new_time,
+ std::map<std::string, uint64_t>* stats_map);
+
+ // Print information of all tombstones of all iterators to the std::string
+ // This is only used by ldb. The output might be capped. Tombstones
+ // printed out are not guaranteed to be in any order.
+ Status TablesRangeTombstoneSummary(ColumnFamilyHandle* column_family,
+ int max_entries_to_print,
+ std::string* out_str);
+
+#ifndef NDEBUG
+ // Compact any files in the named level that overlap [*begin, *end]
+ Status TEST_CompactRange(int level, const Slice* begin, const Slice* end,
+ ColumnFamilyHandle* column_family = nullptr,
+ bool disallow_trivial_move = false);
+
+ void TEST_SwitchWAL();
+
+ bool TEST_UnableToReleaseOldestLog() { return unable_to_release_oldest_log_; }
+
+ bool TEST_IsLogGettingFlushed() {
+ return alive_log_files_.begin()->getting_flushed;
+ }
+
+ Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
+
+ // Force current memtable contents to be flushed.
+ Status TEST_FlushMemTable(bool wait = true, bool allow_write_stall = false,
+ ColumnFamilyHandle* cfh = nullptr);
+
+ Status TEST_FlushMemTable(ColumnFamilyData* cfd,
+ const FlushOptions& flush_opts);
+
+ // Flush (multiple) ColumnFamilyData without using ColumnFamilyHandle. This
+ // is because in certain cases, we can flush column families, wait for the
+ // flush to complete, but delete the column family handle before the wait
+ // finishes. For example in CompactRange.
+ Status TEST_AtomicFlushMemTables(const autovector<ColumnFamilyData*>& cfds,
+ const FlushOptions& flush_opts);
+
+ // Wait for memtable compaction
+ Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
+
+ // Wait for any compaction
+ // We add a bool parameter to wait for unscheduledCompactions_ == 0, but this
+ // is only for the special test of CancelledCompactions
+ Status TEST_WaitForCompact(bool waitUnscheduled = false);
+
+ // Return the maximum overlapping data (in bytes) at next level for any
+ // file at a level >= 1.
+ int64_t TEST_MaxNextLevelOverlappingBytes(
+ ColumnFamilyHandle* column_family = nullptr);
+
+ // Return the current manifest file no.
+ uint64_t TEST_Current_Manifest_FileNo();
+
+ // Returns the number that'll be assigned to the next file that's created.
+ uint64_t TEST_Current_Next_FileNo();
+
+ // get total level0 file size. Only for testing.
+ uint64_t TEST_GetLevel0TotalSize();
+
+ void TEST_GetFilesMetaData(ColumnFamilyHandle* column_family,
+ std::vector<std::vector<FileMetaData>>* metadata);
+
+ void TEST_LockMutex();
+
+ void TEST_UnlockMutex();
+
+ // REQUIRES: mutex locked
+ void* TEST_BeginWrite();
+
+ // REQUIRES: mutex locked
+ // pass the pointer that you got from TEST_BeginWrite()
+ void TEST_EndWrite(void* w);
+
+ uint64_t TEST_MaxTotalInMemoryState() const {
+ return max_total_in_memory_state_;
+ }
+
+ size_t TEST_LogsToFreeSize();
+
+ uint64_t TEST_LogfileNumber();
+
+ uint64_t TEST_total_log_size() const { return total_log_size_; }
+
+ // Returns column family name to ImmutableCFOptions map.
+ Status TEST_GetAllImmutableCFOptions(
+ std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
+
+ // Return the lastest MutableCFOptions of a column family
+ Status TEST_GetLatestMutableCFOptions(ColumnFamilyHandle* column_family,
+ MutableCFOptions* mutable_cf_options);
+
+ Cache* TEST_table_cache() { return table_cache_.get(); }
+
+ WriteController& TEST_write_controler() { return write_controller_; }
+
+ uint64_t TEST_FindMinLogContainingOutstandingPrep();
+ uint64_t TEST_FindMinPrepLogReferencedByMemTable();
+ size_t TEST_PreparedSectionCompletedSize();
+ size_t TEST_LogsWithPrepSize();
+
+ int TEST_BGCompactionsAllowed() const;
+ int TEST_BGFlushesAllowed() const;
+ size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
+ void TEST_WaitForDumpStatsRun(std::function<void()> callback) const;
+ void TEST_WaitForPersistStatsRun(std::function<void()> callback) const;
+ bool TEST_IsPersistentStatsEnabled() const;
+ size_t TEST_EstimateInMemoryStatsHistorySize() const;
+#endif // NDEBUG
+
+ protected:
+ const std::string dbname_;
+ std::string db_id_;
+ std::unique_ptr<VersionSet> versions_;
+ // Flag to check whether we allocated and own the info log file
+ bool own_info_log_;
+ const DBOptions initial_db_options_;
+ Env* const env_;
+ std::shared_ptr<FileSystem> fs_;
+ const ImmutableDBOptions immutable_db_options_;
+ MutableDBOptions mutable_db_options_;
+ Statistics* stats_;
+ std::unordered_map<std::string, RecoveredTransaction*>
+ recovered_transactions_;
+ std::unique_ptr<Tracer> tracer_;
+ InstrumentedMutex trace_mutex_;
+ BlockCacheTracer block_cache_tracer_;
+
+ // State below is protected by mutex_
+ // With two_write_queues enabled, some of the variables that accessed during
+ // WriteToWAL need different synchronization: log_empty_, alive_log_files_,
+ // logs_, logfile_number_. Refer to the definition of each variable below for
+ // more description.
+ mutable InstrumentedMutex mutex_;
+
+ ColumnFamilyHandleImpl* default_cf_handle_;
+ InternalStats* default_cf_internal_stats_;
+
+ // only used for dynamically adjusting max_total_wal_size. it is a sum of
+ // [write_buffer_size * max_write_buffer_number] over all column families
+ uint64_t max_total_in_memory_state_;
+ // If true, we have only one (default) column family. We use this to optimize
+ // some code-paths
+ bool single_column_family_mode_;
+
+ // The options to access storage files
+ const FileOptions file_options_;
+
+ // Additonal options for compaction and flush
+ FileOptions file_options_for_compaction_;
+
+ std::unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
+
+ // Increase the sequence number after writing each batch, whether memtable is
+ // disabled for that or not. Otherwise the sequence number is increased after
+ // writing each key into memtable. This implies that when disable_memtable is
+ // set, the seq is not increased at all.
+ //
+ // Default: false
+ const bool seq_per_batch_;
+ // This determines during recovery whether we expect one writebatch per
+ // recovered transaction, or potentially multiple writebatches per
+ // transaction. For WriteUnprepared, this is set to false, since multiple
+ // batches can exist per transaction.
+ //
+ // Default: true
+ const bool batch_per_txn_;
+
+ // Except in DB::Open(), WriteOptionsFile can only be called when:
+ // Persist options to options file.
+ // If need_mutex_lock = false, the method will lock DB mutex.
+ // If need_enter_write_thread = false, the method will enter write thread.
+ Status WriteOptionsFile(bool need_mutex_lock, bool need_enter_write_thread);
+
+ // The following two functions can only be called when:
+ // 1. WriteThread::Writer::EnterUnbatched() is used.
+ // 2. db_mutex is NOT held
+ Status RenameTempFileToOptionsFile(const std::string& file_name);
+ Status DeleteObsoleteOptionsFiles();
+
+ void NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
+ const MutableCFOptions& mutable_cf_options,
+ int job_id);
+
+ void NotifyOnFlushCompleted(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ std::list<std::unique_ptr<FlushJobInfo>>* flush_jobs_info);
+
+ void NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
+ const Status& st,
+ const CompactionJobStats& job_stats, int job_id);
+
+ void NotifyOnCompactionCompleted(ColumnFamilyData* cfd, Compaction* c,
+ const Status& st,
+ const CompactionJobStats& job_stats,
+ int job_id);
+ void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
+ const MemTableInfo& mem_table_info);
+
+#ifndef ROCKSDB_LITE
+ void NotifyOnExternalFileIngested(
+ ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job);
+#endif // !ROCKSDB_LITE
+
+ void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
+
+ void EraseThreadStatusCfInfo(ColumnFamilyData* cfd) const;
+
+ void EraseThreadStatusDbInfo() const;
+
+ // If disable_memtable is set the application logic must guarantee that the
+ // batch will still be skipped from memtable during the recovery. An excption
+ // to this is seq_per_batch_ mode, in which since each batch already takes one
+ // seq, it is ok for the batch to write to memtable during recovery as long as
+ // it only takes one sequence number: i.e., no duplicate keys.
+ // In WriteCommitted it is guarnateed since disable_memtable is used for
+ // prepare batch which will be written to memtable later during the commit,
+ // and in WritePrepared it is guaranteed since it will be used only for WAL
+ // markers which will never be written to memtable. If the commit marker is
+ // accompanied with CommitTimeWriteBatch that is not written to memtable as
+ // long as it has no duplicate keys, it does not violate the one-seq-per-batch
+ // policy.
+ // batch_cnt is expected to be non-zero in seq_per_batch mode and
+ // indicates the number of sub-patches. A sub-patch is a subset of the write
+ // batch that does not have duplicate keys.
+ Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
+ WriteCallback* callback = nullptr,
+ uint64_t* log_used = nullptr, uint64_t log_ref = 0,
+ bool disable_memtable = false, uint64_t* seq_used = nullptr,
+ size_t batch_cnt = 0,
+ PreReleaseCallback* pre_release_callback = nullptr);
+
+ Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
+ WriteCallback* callback = nullptr,
+ uint64_t* log_used = nullptr, uint64_t log_ref = 0,
+ bool disable_memtable = false,
+ uint64_t* seq_used = nullptr);
+
+ // Write only to memtables without joining any write queue
+ Status UnorderedWriteMemtable(const WriteOptions& write_options,
+ WriteBatch* my_batch, WriteCallback* callback,
+ uint64_t log_ref, SequenceNumber seq,
+ const size_t sub_batch_cnt);
+
+ // Whether the batch requires to be assigned with an order
+ enum AssignOrder : bool { kDontAssignOrder, kDoAssignOrder };
+ // Whether it requires publishing last sequence or not
+ enum PublishLastSeq : bool { kDontPublishLastSeq, kDoPublishLastSeq };
+
+ // Join the write_thread to write the batch only to the WAL. It is the
+ // responsibility of the caller to also write the write batch to the memtable
+ // if it required.
+ //
+ // sub_batch_cnt is expected to be non-zero when assign_order = kDoAssignOrder
+ // indicating the number of sub-batches in my_batch. A sub-patch is a subset
+ // of the write batch that does not have duplicate keys. When seq_per_batch is
+ // not set, each key is a separate sub_batch. Otherwise each duplicate key
+ // marks start of a new sub-batch.
+ Status WriteImplWALOnly(
+ WriteThread* write_thread, const WriteOptions& options,
+ WriteBatch* updates, WriteCallback* callback, uint64_t* log_used,
+ const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
+ PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
+ const PublishLastSeq publish_last_seq, const bool disable_memtable);
+
+ // write cached_recoverable_state_ to memtable if it is not empty
+ // The writer must be the leader in write_thread_ and holding mutex_
+ Status WriteRecoverableState();
+
+ // Actual implementation of Close()
+ Status CloseImpl();
+
+ // Recover the descriptor from persistent storage. May do a significant
+ // amount of work to recover recently logged updates. Any changes to
+ // be made to the descriptor are added to *edit.
+ // recovered_seq is set to less than kMaxSequenceNumber if the log's tail is
+ // skipped.
+ virtual Status Recover(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ bool read_only = false, bool error_if_log_file_exist = false,
+ bool error_if_data_exists_in_logs = false,
+ uint64_t* recovered_seq = nullptr);
+
+ virtual bool OwnTablesAndLogs() const { return true; }
+
+ private:
+ friend class DB;
+ friend class ErrorHandler;
+ friend class InternalStats;
+ friend class PessimisticTransaction;
+ friend class TransactionBaseImpl;
+ friend class WriteCommittedTxn;
+ friend class WritePreparedTxn;
+ friend class WritePreparedTxnDB;
+ friend class WriteBatchWithIndex;
+ friend class WriteUnpreparedTxnDB;
+ friend class WriteUnpreparedTxn;
+
+#ifndef ROCKSDB_LITE
+ friend class ForwardIterator;
+#endif
+ friend struct SuperVersion;
+ friend class CompactedDBImpl;
+ friend class DBTest_ConcurrentFlushWAL_Test;
+ friend class DBTest_MixedSlowdownOptionsStop_Test;
+ friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
+ friend class DBCompactionTest_CompactionDuringShutdown_Test;
+ friend class StatsHistoryTest_PersistentStatsCreateColumnFamilies_Test;
+#ifndef NDEBUG
+ friend class DBTest2_ReadCallbackTest_Test;
+ friend class WriteCallbackTest_WriteWithCallbackTest_Test;
+ friend class XFTransactionWriteHandler;
+ friend class DBBlobIndexTest;
+ friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
+#endif
+
+ struct CompactionState;
+ struct PrepickedCompaction;
+ struct PurgeFileInfo;
+
+ struct WriteContext {
+ SuperVersionContext superversion_context;
+ autovector<MemTable*> memtables_to_free_;
+
+ explicit WriteContext(bool create_superversion = false)
+ : superversion_context(create_superversion) {}
+
+ ~WriteContext() {
+ superversion_context.Clean();
+ for (auto& m : memtables_to_free_) {
+ delete m;
+ }
+ }
+ };
+
+ struct LogFileNumberSize {
+ explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
+ void AddSize(uint64_t new_size) { size += new_size; }
+ uint64_t number;
+ uint64_t size = 0;
+ bool getting_flushed = false;
+ };
+
+ struct LogWriterNumber {
+ // pass ownership of _writer
+ LogWriterNumber(uint64_t _number, log::Writer* _writer)
+ : number(_number), writer(_writer) {}
+
+ log::Writer* ReleaseWriter() {
+ auto* w = writer;
+ writer = nullptr;
+ return w;
+ }
+ Status ClearWriter() {
+ Status s = writer->WriteBuffer();
+ delete writer;
+ writer = nullptr;
+ return s;
+ }
+
+ uint64_t number;
+ // Visual Studio doesn't support deque's member to be noncopyable because
+ // of a std::unique_ptr as a member.
+ log::Writer* writer; // own
+ // true for some prefix of logs_
+ bool getting_synced = false;
+ };
+
+ // PurgeFileInfo is a structure to hold information of files to be deleted in
+ // purge_files_
+ struct PurgeFileInfo {
+ std::string fname;
+ std::string dir_to_sync;
+ FileType type;
+ uint64_t number;
+ int job_id;
+ PurgeFileInfo(std::string fn, std::string d, FileType t, uint64_t num,
+ int jid)
+ : fname(fn), dir_to_sync(d), type(t), number(num), job_id(jid) {}
+ };
+
+ // Argument required by background flush thread.
+ struct BGFlushArg {
+ BGFlushArg()
+ : cfd_(nullptr), max_memtable_id_(0), superversion_context_(nullptr) {}
+ BGFlushArg(ColumnFamilyData* cfd, uint64_t max_memtable_id,
+ SuperVersionContext* superversion_context)
+ : cfd_(cfd),
+ max_memtable_id_(max_memtable_id),
+ superversion_context_(superversion_context) {}
+
+ // Column family to flush.
+ ColumnFamilyData* cfd_;
+ // Maximum ID of memtable to flush. In this column family, memtables with
+ // IDs smaller than this value must be flushed before this flush completes.
+ uint64_t max_memtable_id_;
+ // Pointer to a SuperVersionContext object. After flush completes, RocksDB
+ // installs a new superversion for the column family. This operation
+ // requires a SuperVersionContext object (currently embedded in JobContext).
+ SuperVersionContext* superversion_context_;
+ };
+
+ // Argument passed to flush thread.
+ struct FlushThreadArg {
+ DBImpl* db_;
+
+ Env::Priority thread_pri_;
+ };
+
+ // Information for a manual compaction
+ struct ManualCompactionState {
+ ColumnFamilyData* cfd;
+ int input_level;
+ int output_level;
+ uint32_t output_path_id;
+ Status status;
+ bool done;
+ bool in_progress; // compaction request being processed?
+ bool incomplete; // only part of requested range compacted
+ bool exclusive; // current behavior of only one manual
+ bool disallow_trivial_move; // Force actual compaction to run
+ const InternalKey* begin; // nullptr means beginning of key range
+ const InternalKey* end; // nullptr means end of key range
+ InternalKey* manual_end; // how far we are compacting
+ InternalKey tmp_storage; // Used to keep track of compaction progress
+ InternalKey tmp_storage1; // Used to keep track of compaction progress
+ };
+ struct PrepickedCompaction {
+ // background compaction takes ownership of `compaction`.
+ Compaction* compaction;
+ // caller retains ownership of `manual_compaction_state` as it is reused
+ // across background compactions.
+ ManualCompactionState* manual_compaction_state; // nullptr if non-manual
+ // task limiter token is requested during compaction picking.
+ std::unique_ptr<TaskLimiterToken> task_token;
+ };
+
+ struct CompactionArg {
+ // caller retains ownership of `db`.
+ DBImpl* db;
+ // background compaction takes ownership of `prepicked_compaction`.
+ PrepickedCompaction* prepicked_compaction;
+ };
+
+ // Initialize the built-in column family for persistent stats. Depending on
+ // whether on-disk persistent stats have been enabled before, it may either
+ // create a new column family and column family handle or just a column family
+ // handle.
+ // Required: DB mutex held
+ Status InitPersistStatsColumnFamily();
+
+ // Persistent Stats column family has two format version key which are used
+ // for compatibility check. Write format version if it's created for the
+ // first time, read format version and check compatibility if recovering
+ // from disk. This function requires DB mutex held at entrance but may
+ // release and re-acquire DB mutex in the process.
+ // Required: DB mutex held
+ Status PersistentStatsProcessFormatVersion();
+
+ Status ResumeImpl();
+
+ void MaybeIgnoreError(Status* s) const;
+
+ const Status CreateArchivalDirectory();
+
+ Status CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
+ const std::string& cf_name,
+ ColumnFamilyHandle** handle);
+
+ Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
+
+ // Delete any unneeded files and stale in-memory entries.
+ void DeleteObsoleteFiles();
+ // Delete obsolete files and log status and information of file deletion
+ void DeleteObsoleteFileImpl(int job_id, const std::string& fname,
+ const std::string& path_to_sync, FileType type,
+ uint64_t number);
+
+ // Background process needs to call
+ // auto x = CaptureCurrentFileNumberInPendingOutputs()
+ // auto file_num = versions_->NewFileNumber();
+ // <do something>
+ // ReleaseFileNumberFromPendingOutputs(x)
+ // This will protect any file with number `file_num` or greater from being
+ // deleted while <do something> is running.
+ // -----------
+ // This function will capture current file number and append it to
+ // pending_outputs_. This will prevent any background process to delete any
+ // file created after this point.
+ std::list<uint64_t>::iterator CaptureCurrentFileNumberInPendingOutputs();
+ // This function should be called with the result of
+ // CaptureCurrentFileNumberInPendingOutputs(). It then marks that any file
+ // created between the calls CaptureCurrentFileNumberInPendingOutputs() and
+ // ReleaseFileNumberFromPendingOutputs() can now be deleted (if it's not live
+ // and blocked by any other pending_outputs_ calls)
+ void ReleaseFileNumberFromPendingOutputs(
+ std::unique_ptr<std::list<uint64_t>::iterator>& v);
+
+ Status SyncClosedLogs(JobContext* job_context);
+
+ // Flush the in-memory write buffer to storage. Switches to a new
+ // log-file/memtable and writes a new descriptor iff successful. Then
+ // installs a new super version for the column family.
+ Status FlushMemTableToOutputFile(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ bool* madeProgress, JobContext* job_context,
+ SuperVersionContext* superversion_context,
+ std::vector<SequenceNumber>& snapshot_seqs,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
+ Env::Priority thread_pri);
+
+ // Flush the memtables of (multiple) column families to multiple files on
+ // persistent storage.
+ Status FlushMemTablesToOutputFiles(
+ const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
+ JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
+
+ Status AtomicFlushMemTablesToOutputFiles(
+ const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
+ JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
+
+ // REQUIRES: log_numbers are sorted in ascending order
+ // corrupted_log_found is set to true if we recover from a corrupted log file.
+ Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
+ SequenceNumber* next_sequence, bool read_only,
+ bool* corrupted_log_found);
+
+ // The following two methods are used to flush a memtable to
+ // storage. The first one is used at database RecoveryTime (when the
+ // database is opened) and is heavyweight because it holds the mutex
+ // for the entire period. The second method WriteLevel0Table supports
+ // concurrent flush memtables to storage.
+ Status WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
+ MemTable* mem, VersionEdit* edit);
+
+ // Restore alive_log_files_ and total_log_size_ after recovery.
+ // It needs to run only when there's no flush during recovery
+ // (e.g. avoid_flush_during_recovery=true). May also trigger flush
+ // in case total_log_size > max_total_wal_size.
+ Status RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers);
+
+ // num_bytes: for slowdown case, delay time is calculated based on
+ // `num_bytes` going through.
+ Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options);
+
+ Status ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
+ WriteBatch* my_batch);
+
+ // REQUIRES: mutex locked and in write thread.
+ Status ScheduleFlushes(WriteContext* context);
+
+ void MaybeFlushStatsCF(autovector<ColumnFamilyData*>* cfds);
+
+ Status TrimMemtableHistory(WriteContext* context);
+
+ Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
+
+ void SelectColumnFamiliesForAtomicFlush(autovector<ColumnFamilyData*>* cfds);
+
+ // Force current memtable contents to be flushed.
+ Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
+ FlushReason flush_reason, bool writes_stopped = false);
+
+ Status AtomicFlushMemTables(
+ const autovector<ColumnFamilyData*>& column_family_datas,
+ const FlushOptions& options, FlushReason flush_reason,
+ bool writes_stopped = false);
+
+ // Wait until flushing this column family won't stall writes
+ Status WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
+ bool* flush_needed);
+
+ // Wait for memtable flushed.
+ // If flush_memtable_id is non-null, wait until the memtable with the ID
+ // gets flush. Otherwise, wait until the column family don't have any
+ // memtable pending flush.
+ // resuming_from_bg_err indicates whether the caller is attempting to resume
+ // from background error.
+ Status WaitForFlushMemTable(ColumnFamilyData* cfd,
+ const uint64_t* flush_memtable_id = nullptr,
+ bool resuming_from_bg_err = false) {
+ return WaitForFlushMemTables({cfd}, {flush_memtable_id},
+ resuming_from_bg_err);
+ }
+ // Wait for memtables to be flushed for multiple column families.
+ Status WaitForFlushMemTables(
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const uint64_t*>& flush_memtable_ids,
+ bool resuming_from_bg_err);
+
+ inline void WaitForPendingWrites() {
+ mutex_.AssertHeld();
+ TEST_SYNC_POINT("DBImpl::WaitForPendingWrites:BeforeBlock");
+ // In case of pipelined write is enabled, wait for all pending memtable
+ // writers.
+ if (immutable_db_options_.enable_pipelined_write) {
+ // Memtable writers may call DB::Get in case max_successive_merges > 0,
+ // which may lock mutex. Unlocking mutex here to avoid deadlock.
+ mutex_.Unlock();
+ write_thread_.WaitForMemTableWriters();
+ mutex_.Lock();
+ }
+
+ if (!immutable_db_options_.unordered_write) {
+ // Then the writes are finished before the next write group starts
+ return;
+ }
+
+ // Wait for the ones who already wrote to the WAL to finish their
+ // memtable write.
+ if (pending_memtable_writes_.load() != 0) {
+ std::unique_lock<std::mutex> guard(switch_mutex_);
+ switch_cv_.wait(guard,
+ [&] { return pending_memtable_writes_.load() == 0; });
+ }
+ }
+
+ // REQUIRES: mutex locked and in write thread.
+ void AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds);
+
+ // REQUIRES: mutex locked and in write thread.
+ Status SwitchWAL(WriteContext* write_context);
+
+ // REQUIRES: mutex locked and in write thread.
+ Status HandleWriteBufferFull(WriteContext* write_context);
+
+ // REQUIRES: mutex locked
+ Status PreprocessWrite(const WriteOptions& write_options, bool* need_log_sync,
+ WriteContext* write_context);
+
+ WriteBatch* MergeBatch(const WriteThread::WriteGroup& write_group,
+ WriteBatch* tmp_batch, size_t* write_with_wal,
+ WriteBatch** to_be_cached_state);
+
+ Status WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
+ uint64_t* log_used, uint64_t* log_size);
+
+ Status WriteToWAL(const WriteThread::WriteGroup& write_group,
+ log::Writer* log_writer, uint64_t* log_used,
+ bool need_log_sync, bool need_log_dir_sync,
+ SequenceNumber sequence);
+
+ Status ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
+ uint64_t* log_used, SequenceNumber* last_sequence,
+ size_t seq_inc);
+
+ // Used by WriteImpl to update bg_error_ if paranoid check is enabled.
+ void WriteStatusCheck(const Status& status);
+
+ // Used by WriteImpl to update bg_error_ in case of memtable insert error.
+ void MemTableInsertStatusCheck(const Status& memtable_insert_status);
+
+#ifndef ROCKSDB_LITE
+
+ Status CompactFilesImpl(const CompactionOptions& compact_options,
+ ColumnFamilyData* cfd, Version* version,
+ const std::vector<std::string>& input_file_names,
+ std::vector<std::string>* const output_file_names,
+ const int output_level, int output_path_id,
+ JobContext* job_context, LogBuffer* log_buffer,
+ CompactionJobInfo* compaction_job_info);
+
+ // Wait for current IngestExternalFile() calls to finish.
+ // REQUIRES: mutex_ held
+ void WaitForIngestFile();
+
+#else
+ // IngestExternalFile is not supported in ROCKSDB_LITE so this function
+ // will be no-op
+ void WaitForIngestFile() {}
+#endif // ROCKSDB_LITE
+
+ ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
+
+ void MaybeScheduleFlushOrCompaction();
+
+ // A flush request specifies the column families to flush as well as the
+ // largest memtable id to persist for each column family. Once all the
+ // memtables whose IDs are smaller than or equal to this per-column-family
+ // specified value, this flush request is considered to have completed its
+ // work of flushing this column family. After completing the work for all
+ // column families in this request, this flush is considered complete.
+ typedef std::vector<std::pair<ColumnFamilyData*, uint64_t>> FlushRequest;
+
+ void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
+ FlushRequest* req);
+
+ void SchedulePendingFlush(const FlushRequest& req, FlushReason flush_reason);
+
+ void SchedulePendingCompaction(ColumnFamilyData* cfd);
+ void SchedulePendingPurge(std::string fname, std::string dir_to_sync,
+ FileType type, uint64_t number, int job_id);
+ static void BGWorkCompaction(void* arg);
+ // Runs a pre-chosen universal compaction involving bottom level in a
+ // separate, bottom-pri thread pool.
+ static void BGWorkBottomCompaction(void* arg);
+ static void BGWorkFlush(void* arg);
+ static void BGWorkPurge(void* arg);
+ static void UnscheduleCompactionCallback(void* arg);
+ static void UnscheduleFlushCallback(void* arg);
+ void BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
+ Env::Priority thread_pri);
+ void BackgroundCallFlush(Env::Priority thread_pri);
+ void BackgroundCallPurge();
+ Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
+ LogBuffer* log_buffer,
+ PrepickedCompaction* prepicked_compaction,
+ Env::Priority thread_pri);
+ Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
+ LogBuffer* log_buffer, FlushReason* reason,
+ Env::Priority thread_pri);
+
+ bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
+ const std::vector<CompactionInputFiles>& inputs,
+ bool* sfm_bookkeeping, LogBuffer* log_buffer);
+
+ // Request compaction tasks token from compaction thread limiter.
+ // It always succeeds if force = true or limiter is disable.
+ bool RequestCompactionToken(ColumnFamilyData* cfd, bool force,
+ std::unique_ptr<TaskLimiterToken>* token,
+ LogBuffer* log_buffer);
+
+ // Schedule background tasks
+ void StartTimedTasks();
+
+ void PrintStatistics();
+
+ size_t EstimateInMemoryStatsHistorySize() const;
+
+ // persist stats to column family "_persistent_stats"
+ void PersistStats();
+
+ // dump rocksdb.stats to LOG
+ void DumpStats();
+
+ // Return the minimum empty level that could hold the total data in the
+ // input level. Return the input level, if such level could not be found.
+ int FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd,
+ const MutableCFOptions& mutable_cf_options,
+ int level);
+
+ // Move the files in the input level to the target level.
+ // If target_level < 0, automatically calculate the minimum level that could
+ // hold the data set.
+ Status ReFitLevel(ColumnFamilyData* cfd, int level, int target_level = -1);
+
+ // helper functions for adding and removing from flush & compaction queues
+ void AddToCompactionQueue(ColumnFamilyData* cfd);
+ ColumnFamilyData* PopFirstFromCompactionQueue();
+ FlushRequest PopFirstFromFlushQueue();
+
+ // Pick the first unthrottled compaction with task token from queue.
+ ColumnFamilyData* PickCompactionFromQueue(
+ std::unique_ptr<TaskLimiterToken>* token, LogBuffer* log_buffer);
+
+ // helper function to call after some of the logs_ were synced
+ void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
+
+ SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary,
+ bool lock = true);
+
+ uint64_t GetMaxTotalWalSize() const;
+
+ Directory* GetDataDir(ColumnFamilyData* cfd, size_t path_id) const;
+
+ Status CloseHelper();
+
+ void WaitForBackgroundWork();
+
+ // Background threads call this function, which is just a wrapper around
+ // the InstallSuperVersion() function. Background threads carry
+ // sv_context which can have new_superversion already
+ // allocated.
+ // All ColumnFamily state changes go through this function. Here we analyze
+ // the new state and we schedule background work if we detect that the new
+ // state needs flush or compaction.
+ void InstallSuperVersionAndScheduleWork(
+ ColumnFamilyData* cfd, SuperVersionContext* sv_context,
+ const MutableCFOptions& mutable_cf_options);
+
+ bool GetIntPropertyInternal(ColumnFamilyData* cfd,
+ const DBPropertyInfo& property_info,
+ bool is_locked, uint64_t* value);
+ bool GetPropertyHandleOptionsStatistics(std::string* value);
+
+ bool HasPendingManualCompaction();
+ bool HasExclusiveManualCompaction();
+ void AddManualCompaction(ManualCompactionState* m);
+ void RemoveManualCompaction(ManualCompactionState* m);
+ bool ShouldntRunManualCompaction(ManualCompactionState* m);
+ bool HaveManualCompaction(ColumnFamilyData* cfd);
+ bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
+#ifndef ROCKSDB_LITE
+ void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
+ const Status& st,
+ const CompactionJobStats& compaction_job_stats,
+ const int job_id, const Version* current,
+ CompactionJobInfo* compaction_job_info) const;
+ // Reserve the next 'num' file numbers for to-be-ingested external SST files,
+ // and return the current file_number in 'next_file_number'.
+ // Write a version edit to the MANIFEST.
+ Status ReserveFileNumbersBeforeIngestion(
+ ColumnFamilyData* cfd, uint64_t num,
+ std::unique_ptr<std::list<uint64_t>::iterator>& pending_output_elem,
+ uint64_t* next_file_number);
+#endif //! ROCKSDB_LITE
+
+ bool ShouldPurge(uint64_t file_number) const;
+ void MarkAsGrabbedForPurge(uint64_t file_number);
+
+ size_t GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
+ Env::WriteLifeTimeHint CalculateWALWriteHint() { return Env::WLTH_SHORT; }
+
+ Status CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
+ size_t preallocate_block_size, log::Writer** new_log);
+
+ // Validate self-consistency of DB options
+ static Status ValidateOptions(const DBOptions& db_options);
+ // Validate self-consistency of DB options and its consistency with cf options
+ static Status ValidateOptions(
+ const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families);
+
+ // Utility function to do some debug validation and sort the given vector
+ // of MultiGet keys
+ void PrepareMultiGetKeys(
+ const size_t num_keys, bool sorted,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* key_ptrs);
+
+ // A structure to hold the information required to process MultiGet of keys
+ // belonging to one column family. For a multi column family MultiGet, there
+ // will be a container of these objects.
+ struct MultiGetColumnFamilyData {
+ ColumnFamilyHandle* cf;
+ ColumnFamilyData* cfd;
+
+ // For the batched MultiGet which relies on sorted keys, start specifies
+ // the index of first key belonging to this column family in the sorted
+ // list.
+ size_t start;
+
+ // For the batched MultiGet case, num_keys specifies the number of keys
+ // belonging to this column family in the sorted list
+ size_t num_keys;
+
+ // SuperVersion for the column family obtained in a manner that ensures a
+ // consistent view across all column families in the DB
+ SuperVersion* super_version;
+ MultiGetColumnFamilyData(ColumnFamilyHandle* column_family,
+ SuperVersion* sv)
+ : cf(column_family),
+ cfd(static_cast<ColumnFamilyHandleImpl*>(cf)->cfd()),
+ start(0),
+ num_keys(0),
+ super_version(sv) {}
+
+ MultiGetColumnFamilyData(ColumnFamilyHandle* column_family, size_t first,
+ size_t count, SuperVersion* sv)
+ : cf(column_family),
+ cfd(static_cast<ColumnFamilyHandleImpl*>(cf)->cfd()),
+ start(first),
+ num_keys(count),
+ super_version(sv) {}
+
+ MultiGetColumnFamilyData() = default;
+ };
+
+ // A common function to obtain a consistent snapshot, which can be implicit
+ // if the user doesn't specify a snapshot in read_options, across
+ // multiple column families for MultiGet. It will attempt to get an implicit
+ // snapshot without acquiring the db_mutes, but will give up after a few
+ // tries and acquire the mutex if a memtable flush happens. The template
+ // allows both the batched and non-batched MultiGet to call this with
+ // either an std::unordered_map or autovector of column families.
+ //
+ // If callback is non-null, the callback is refreshed with the snapshot
+ // sequence number
+ //
+ // A return value of true indicates that the SuperVersions were obtained
+ // from the ColumnFamilyData, whereas false indicates they are thread
+ // local
+ template <class T>
+ bool MultiCFSnapshot(
+ const ReadOptions& read_options, ReadCallback* callback,
+ std::function<MultiGetColumnFamilyData*(typename T::iterator&)>&
+ iter_deref_func,
+ T* cf_list, SequenceNumber* snapshot);
+
+ // The actual implementation of the batching MultiGet. The caller is expected
+ // to have acquired the SuperVersion and pass in a snapshot sequence number
+ // in order to construct the LookupKeys. The start_key and num_keys specify
+ // the range of keys in the sorted_keys vector for a single column family.
+ void MultiGetImpl(
+ const ReadOptions& read_options, size_t start_key, size_t num_keys,
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys,
+ SuperVersion* sv, SequenceNumber snap_seqnum, ReadCallback* callback,
+ bool* is_blob_index);
+
+ // table_cache_ provides its own synchronization
+ std::shared_ptr<Cache> table_cache_;
+
+ // Lock over the persistent DB state. Non-nullptr iff successfully acquired.
+ FileLock* db_lock_;
+
+ // In addition to mutex_, log_write_mutex_ protected writes to stats_history_
+ InstrumentedMutex stats_history_mutex_;
+ // In addition to mutex_, log_write_mutex_ protected writes to logs_ and
+ // logfile_number_. With two_write_queues it also protects alive_log_files_,
+ // and log_empty_. Refer to the definition of each variable below for more
+ // details.
+ // Note: to avoid dealock, if needed to acquire both log_write_mutex_ and
+ // mutex_, the order should be first mutex_ and then log_write_mutex_.
+ InstrumentedMutex log_write_mutex_;
+
+ std::atomic<bool> shutting_down_;
+ std::atomic<bool> manual_compaction_paused_;
+ // This condition variable is signaled on these conditions:
+ // * whenever bg_compaction_scheduled_ goes down to 0
+ // * if AnyManualCompaction, whenever a compaction finishes, even if it hasn't
+ // made any progress
+ // * whenever a compaction made any progress
+ // * whenever bg_flush_scheduled_ or bg_purge_scheduled_ value decreases
+ // (i.e. whenever a flush is done, even if it didn't make any progress)
+ // * whenever there is an error in background purge, flush or compaction
+ // * whenever num_running_ingest_file_ goes to 0.
+ // * whenever pending_purge_obsolete_files_ goes to 0.
+ // * whenever disable_delete_obsolete_files_ goes to 0.
+ // * whenever SetOptions successfully updates options.
+ // * whenever a column family is dropped.
+ InstrumentedCondVar bg_cv_;
+ // Writes are protected by locking both mutex_ and log_write_mutex_, and reads
+ // must be under either mutex_ or log_write_mutex_. Since after ::Open,
+ // logfile_number_ is currently updated only in write_thread_, it can be read
+ // from the same write_thread_ without any locks.
+ uint64_t logfile_number_;
+ std::deque<uint64_t>
+ log_recycle_files_; // a list of log files that we can recycle
+ bool log_dir_synced_;
+ // Without two_write_queues, read and writes to log_empty_ are protected by
+ // mutex_. Since it is currently updated/read only in write_thread_, it can be
+ // accessed from the same write_thread_ without any locks. With
+ // two_write_queues writes, where it can be updated in different threads,
+ // read and writes are protected by log_write_mutex_ instead. This is to avoid
+ // expesnive mutex_ lock during WAL write, which update log_empty_.
+ bool log_empty_;
+
+ ColumnFamilyHandleImpl* persist_stats_cf_handle_;
+
+ bool persistent_stats_cfd_exists_ = true;
+
+ // Without two_write_queues, read and writes to alive_log_files_ are
+ // protected by mutex_. However since back() is never popped, and push_back()
+ // is done only from write_thread_, the same thread can access the item
+ // reffered by back() without mutex_. With two_write_queues_, writes
+ // are protected by locking both mutex_ and log_write_mutex_, and reads must
+ // be under either mutex_ or log_write_mutex_.
+ std::deque<LogFileNumberSize> alive_log_files_;
+ // Log files that aren't fully synced, and the current log file.
+ // Synchronization:
+ // - push_back() is done from write_thread_ with locked mutex_ and
+ // log_write_mutex_
+ // - pop_front() is done from any thread with locked mutex_ and
+ // log_write_mutex_
+ // - reads are done with either locked mutex_ or log_write_mutex_
+ // - back() and items with getting_synced=true are not popped,
+ // - The same thread that sets getting_synced=true will reset it.
+ // - it follows that the object referred by back() can be safely read from
+ // the write_thread_ without using mutex
+ // - it follows that the items with getting_synced=true can be safely read
+ // from the same thread that has set getting_synced=true
+ std::deque<LogWriterNumber> logs_;
+ // Signaled when getting_synced becomes false for some of the logs_.
+ InstrumentedCondVar log_sync_cv_;
+ // This is the app-level state that is written to the WAL but will be used
+ // only during recovery. Using this feature enables not writing the state to
+ // memtable on normal writes and hence improving the throughput. Each new
+ // write of the state will replace the previous state entirely even if the
+ // keys in the two consecuitive states do not overlap.
+ // It is protected by log_write_mutex_ when two_write_queues_ is enabled.
+ // Otherwise only the heaad of write_thread_ can access it.
+ WriteBatch cached_recoverable_state_;
+ std::atomic<bool> cached_recoverable_state_empty_ = {true};
+ std::atomic<uint64_t> total_log_size_;
+
+ // If this is non-empty, we need to delete these log files in background
+ // threads. Protected by db mutex.
+ autovector<log::Writer*> logs_to_free_;
+
+ bool is_snapshot_supported_;
+
+ std::map<uint64_t, std::map<std::string, uint64_t>> stats_history_;
+
+ std::map<std::string, uint64_t> stats_slice_;
+
+ bool stats_slice_initialized_ = false;
+
+ Directories directories_;
+
+ WriteBufferManager* write_buffer_manager_;
+
+ WriteThread write_thread_;
+ WriteBatch tmp_batch_;
+ // The write thread when the writers have no memtable write. This will be used
+ // in 2PC to batch the prepares separately from the serial commit.
+ WriteThread nonmem_write_thread_;
+
+ WriteController write_controller_;
+
+ // Size of the last batch group. In slowdown mode, next write needs to
+ // sleep if it uses up the quota.
+ // Note: This is to protect memtable and compaction. If the batch only writes
+ // to the WAL its size need not to be included in this.
+ uint64_t last_batch_group_size_;
+
+ FlushScheduler flush_scheduler_;
+
+ TrimHistoryScheduler trim_history_scheduler_;
+
+ SnapshotList snapshots_;
+
+ // For each background job, pending_outputs_ keeps the current file number at
+ // the time that background job started.
+ // FindObsoleteFiles()/PurgeObsoleteFiles() never deletes any file that has
+ // number bigger than any of the file number in pending_outputs_. Since file
+ // numbers grow monotonically, this also means that pending_outputs_ is always
+ // sorted. After a background job is done executing, its file number is
+ // deleted from pending_outputs_, which allows PurgeObsoleteFiles() to clean
+ // it up.
+ // State is protected with db mutex.
+ std::list<uint64_t> pending_outputs_;
+
+ // flush_queue_ and compaction_queue_ hold column families that we need to
+ // flush and compact, respectively.
+ // A column family is inserted into flush_queue_ when it satisfies condition
+ // cfd->imm()->IsFlushPending()
+ // A column family is inserted into compaction_queue_ when it satisfied
+ // condition cfd->NeedsCompaction()
+ // Column families in this list are all Ref()-erenced
+ // TODO(icanadi) Provide some kind of ReferencedColumnFamily class that will
+ // do RAII on ColumnFamilyData
+ // Column families are in this queue when they need to be flushed or
+ // compacted. Consumers of these queues are flush and compaction threads. When
+ // column family is put on this queue, we increase unscheduled_flushes_ and
+ // unscheduled_compactions_. When these variables are bigger than zero, that
+ // means we need to schedule background threads for flush and compaction.
+ // Once the background threads are scheduled, we decrease unscheduled_flushes_
+ // and unscheduled_compactions_. That way we keep track of number of
+ // compaction and flush threads we need to schedule. This scheduling is done
+ // in MaybeScheduleFlushOrCompaction()
+ // invariant(column family present in flush_queue_ <==>
+ // ColumnFamilyData::pending_flush_ == true)
+ std::deque<FlushRequest> flush_queue_;
+ // invariant(column family present in compaction_queue_ <==>
+ // ColumnFamilyData::pending_compaction_ == true)
+ std::deque<ColumnFamilyData*> compaction_queue_;
+
+ // A map to store file numbers and filenames of the files to be purged
+ std::unordered_map<uint64_t, PurgeFileInfo> purge_files_;
+
+ // A vector to store the file numbers that have been assigned to certain
+ // JobContext. Current implementation tracks ssts only.
+ std::unordered_set<uint64_t> files_grabbed_for_purge_;
+
+ // A queue to store log writers to close
+ std::deque<log::Writer*> logs_to_free_queue_;
+ std::deque<SuperVersion*> superversions_to_free_queue_;
+ int unscheduled_flushes_;
+ int unscheduled_compactions_;
+
+ // count how many background compactions are running or have been scheduled in
+ // the BOTTOM pool
+ int bg_bottom_compaction_scheduled_;
+
+ // count how many background compactions are running or have been scheduled
+ int bg_compaction_scheduled_;
+
+ // stores the number of compactions are currently running
+ int num_running_compactions_;
+
+ // number of background memtable flush jobs, submitted to the HIGH pool
+ int bg_flush_scheduled_;
+
+ // stores the number of flushes are currently running
+ int num_running_flushes_;
+
+ // number of background obsolete file purge jobs, submitted to the HIGH pool
+ int bg_purge_scheduled_;
+
+ std::deque<ManualCompactionState*> manual_compaction_dequeue_;
+
+ // shall we disable deletion of obsolete files
+ // if 0 the deletion is enabled.
+ // if non-zero, files will not be getting deleted
+ // This enables two different threads to call
+ // EnableFileDeletions() and DisableFileDeletions()
+ // without any synchronization
+ int disable_delete_obsolete_files_;
+
+ // Number of times FindObsoleteFiles has found deletable files and the
+ // corresponding call to PurgeObsoleteFiles has not yet finished.
+ int pending_purge_obsolete_files_;
+
+ // last time when DeleteObsoleteFiles with full scan was executed. Originally
+ // initialized with startup time.
+ uint64_t delete_obsolete_files_last_run_;
+
+ // last time stats were dumped to LOG
+ std::atomic<uint64_t> last_stats_dump_time_microsec_;
+
+ // The thread that wants to switch memtable, can wait on this cv until the
+ // pending writes to memtable finishes.
+ std::condition_variable switch_cv_;
+ // The mutex used by switch_cv_. mutex_ should be acquired beforehand.
+ std::mutex switch_mutex_;
+ // Number of threads intending to write to memtable
+ std::atomic<size_t> pending_memtable_writes_ = {};
+
+ // Each flush or compaction gets its own job id. this counter makes sure
+ // they're unique
+ std::atomic<int> next_job_id_;
+
+ // A flag indicating whether the current rocksdb database has any
+ // data that is not yet persisted into either WAL or SST file.
+ // Used when disableWAL is true.
+ std::atomic<bool> has_unpersisted_data_;
+
+ // if an attempt was made to flush all column families that
+ // the oldest log depends on but uncommitted data in the oldest
+ // log prevents the log from being released.
+ // We must attempt to free the dependent memtables again
+ // at a later time after the transaction in the oldest
+ // log is fully commited.
+ bool unable_to_release_oldest_log_;
+
+ static const int KEEP_LOG_FILE_NUM = 1000;
+ // MSVC version 1800 still does not have constexpr for ::max()
+ static const uint64_t kNoTimeOut = port::kMaxUint64;
+
+ std::string db_absolute_path_;
+
+ // Number of running IngestExternalFile() or CreateColumnFamilyWithImport()
+ // calls.
+ // REQUIRES: mutex held
+ int num_running_ingest_file_;
+
+#ifndef ROCKSDB_LITE
+ WalManager wal_manager_;
+#endif // ROCKSDB_LITE
+
+ // Unified interface for logging events
+ EventLogger event_logger_;
+
+ // A value of > 0 temporarily disables scheduling of background work
+ int bg_work_paused_;
+
+ // A value of > 0 temporarily disables scheduling of background compaction
+ int bg_compaction_paused_;
+
+ // Guard against multiple concurrent refitting
+ bool refitting_level_;
+
+ // Indicate DB was opened successfully
+ bool opened_successfully_;
+
+ // The min threshold to triggere bottommost compaction for removing
+ // garbages, among all column families.
+ SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
+
+ LogsWithPrepTracker logs_with_prep_tracker_;
+
+ // Callback for compaction to check if a key is visible to a snapshot.
+ // REQUIRES: mutex held
+ std::unique_ptr<SnapshotChecker> snapshot_checker_;
+
+ // Callback for when the cached_recoverable_state_ is written to memtable
+ // Only to be set during initialization
+ std::unique_ptr<PreReleaseCallback> recoverable_state_pre_release_callback_;
+
+ // handle for scheduling stats dumping at fixed intervals
+ // REQUIRES: mutex locked
+ std::unique_ptr<ROCKSDB_NAMESPACE::RepeatableThread> thread_dump_stats_;
+
+ // handle for scheduling stats snapshoting at fixed intervals
+ // REQUIRES: mutex locked
+ std::unique_ptr<ROCKSDB_NAMESPACE::RepeatableThread> thread_persist_stats_;
+
+ // When set, we use a separate queue for writes that dont write to memtable.
+ // In 2PC these are the writes at Prepare phase.
+ const bool two_write_queues_;
+ const bool manual_wal_flush_;
+
+ // LastSequence also indicates last published sequence visibile to the
+ // readers. Otherwise LastPublishedSequence should be used.
+ const bool last_seq_same_as_publish_seq_;
+ // It indicates that a customized gc algorithm must be used for
+ // flush/compaction and if it is not provided vis SnapshotChecker, we should
+ // disable gc to be safe.
+ const bool use_custom_gc_;
+ // Flag to indicate that the DB instance shutdown has been initiated. This
+ // different from shutting_down_ atomic in that it is set at the beginning
+ // of shutdown sequence, specifically in order to prevent any background
+ // error recovery from going on in parallel. The latter, shutting_down_,
+ // is set a little later during the shutdown after scheduling memtable
+ // flushes
+ std::atomic<bool> shutdown_initiated_;
+ // Flag to indicate whether sst_file_manager object was allocated in
+ // DB::Open() or passed to us
+ bool own_sfm_;
+
+ // Clients must periodically call SetPreserveDeletesSequenceNumber()
+ // to advance this seqnum. Default value is 0 which means ALL deletes are
+ // preserved. Note that this has no effect if DBOptions.preserve_deletes
+ // is set to false.
+ std::atomic<SequenceNumber> preserve_deletes_seqnum_;
+ const bool preserve_deletes_;
+
+ // Flag to check whether Close() has been called on this DB
+ bool closed_;
+
+ ErrorHandler error_handler_;
+
+ // Conditional variable to coordinate installation of atomic flush results.
+ // With atomic flush, each bg thread installs the result of flushing multiple
+ // column families, and different threads can flush different column
+ // families. It's difficult to rely on one thread to perform batch
+ // installation for all threads. This is different from the non-atomic flush
+ // case.
+ // atomic_flush_install_cv_ makes sure that threads install atomic flush
+ // results sequentially. Flush results of memtables with lower IDs get
+ // installed to MANIFEST first.
+ InstrumentedCondVar atomic_flush_install_cv_;
+
+ bool wal_in_db_path_;
+};
+
+extern Options SanitizeOptions(const std::string& db, const Options& src);
+
+extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
+
+extern CompressionType GetCompressionFlush(
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options);
+
+// Return the earliest log file to keep after the memtable flush is
+// finalized.
+// `cfd_to_flush` is the column family whose memtable (specified in
+// `memtables_to_flush`) will be flushed and thus will not depend on any WAL
+// file.
+// The function is only applicable to 2pc mode.
+extern uint64_t PrecomputeMinLogNumberToKeep(
+ VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
+ autovector<VersionEdit*> edit_list,
+ const autovector<MemTable*>& memtables_to_flush,
+ LogsWithPrepTracker* prep_tracker);
+
+// `cfd_to_flush` is the column family whose memtable will be flushed and thus
+// will not depend on any WAL file. nullptr means no memtable is being flushed.
+// The function is only applicable to 2pc mode.
+extern uint64_t FindMinPrepLogReferencedByMemTable(
+ VersionSet* vset, const ColumnFamilyData* cfd_to_flush,
+ const autovector<MemTable*>& memtables_to_flush);
+
+// Fix user-supplied options to be reasonable
+template <class T, class V>
+static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
+ if (static_cast<V>(*ptr) > maxvalue) *ptr = maxvalue;
+ if (static_cast<V>(*ptr) < minvalue) *ptr = minvalue;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc
new file mode 100644
index 00000000..c7b3510c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_compaction_flush.cc
@@ -0,0 +1,3116 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <cinttypes>
+
+#include "db/builder.h"
+#include "db/error_handler.h"
+#include "db/event_helpers.h"
+#include "file/sst_file_manager_impl.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/thread_status_updater.h"
+#include "monitoring/thread_status_util.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/concurrent_task_limiter_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool DBImpl::EnoughRoomForCompaction(
+ ColumnFamilyData* cfd, const std::vector<CompactionInputFiles>& inputs,
+ bool* sfm_reserved_compact_space, LogBuffer* log_buffer) {
+ // Check if we have enough room to do the compaction
+ bool enough_room = true;
+#ifndef ROCKSDB_LITE
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ if (sfm) {
+ // Pass the current bg_error_ to SFM so it can decide what checks to
+ // perform. If this DB instance hasn't seen any error yet, the SFM can be
+ // optimistic and not do disk space checks
+ enough_room =
+ sfm->EnoughRoomForCompaction(cfd, inputs, error_handler_.GetBGError());
+ if (enough_room) {
+ *sfm_reserved_compact_space = true;
+ }
+ }
+#else
+ (void)cfd;
+ (void)inputs;
+ (void)sfm_reserved_compact_space;
+#endif // ROCKSDB_LITE
+ if (!enough_room) {
+ // Just in case tests want to change the value of enough_room
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::BackgroundCompaction():CancelledCompaction", &enough_room);
+ ROCKS_LOG_BUFFER(log_buffer,
+ "Cancelled compaction because not enough room");
+ RecordTick(stats_, COMPACTION_CANCELLED, 1);
+ }
+ return enough_room;
+}
+
+bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force,
+ std::unique_ptr<TaskLimiterToken>* token,
+ LogBuffer* log_buffer) {
+ assert(*token == nullptr);
+ auto limiter = static_cast<ConcurrentTaskLimiterImpl*>(
+ cfd->ioptions()->compaction_thread_limiter.get());
+ if (limiter == nullptr) {
+ return true;
+ }
+ *token = limiter->GetToken(force);
+ if (*token != nullptr) {
+ ROCKS_LOG_BUFFER(log_buffer,
+ "Thread limiter [%s] increase [%s] compaction task, "
+ "force: %s, tasks after: %d",
+ limiter->GetName().c_str(), cfd->GetName().c_str(),
+ force ? "true" : "false", limiter->GetOutstandingTask());
+ return true;
+ }
+ return false;
+}
+
+Status DBImpl::SyncClosedLogs(JobContext* job_context) {
+ TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
+ mutex_.AssertHeld();
+ autovector<log::Writer*, 1> logs_to_sync;
+ uint64_t current_log_number = logfile_number_;
+ while (logs_.front().number < current_log_number &&
+ logs_.front().getting_synced) {
+ log_sync_cv_.Wait();
+ }
+ for (auto it = logs_.begin();
+ it != logs_.end() && it->number < current_log_number; ++it) {
+ auto& log = *it;
+ assert(!log.getting_synced);
+ log.getting_synced = true;
+ logs_to_sync.push_back(log.writer);
+ }
+
+ Status s;
+ if (!logs_to_sync.empty()) {
+ mutex_.Unlock();
+
+ for (log::Writer* log : logs_to_sync) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[JOB %d] Syncing log #%" PRIu64, job_context->job_id,
+ log->get_log_number());
+ s = log->file()->Sync(immutable_db_options_.use_fsync);
+ if (!s.ok()) {
+ break;
+ }
+
+ if (immutable_db_options_.recycle_log_file_num > 0) {
+ s = log->Close();
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ if (s.ok()) {
+ s = directories_.GetWalDir()->Fsync();
+ }
+
+ mutex_.Lock();
+
+ // "number <= current_log_number - 1" is equivalent to
+ // "number < current_log_number".
+ MarkLogsSynced(current_log_number - 1, true, s);
+ if (!s.ok()) {
+ error_handler_.SetBGError(s, BackgroundErrorReason::kFlush);
+ TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
+ return s;
+ }
+ }
+ return s;
+}
+
+Status DBImpl::FlushMemTableToOutputFile(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ bool* made_progress, JobContext* job_context,
+ SuperVersionContext* superversion_context,
+ std::vector<SequenceNumber>& snapshot_seqs,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
+ Env::Priority thread_pri) {
+ mutex_.AssertHeld();
+ assert(cfd->imm()->NumNotFlushed() != 0);
+ assert(cfd->imm()->IsFlushPending());
+
+ FlushJob flush_job(
+ dbname_, cfd, immutable_db_options_, mutable_cf_options,
+ nullptr /* memtable_id */, file_options_for_compaction_, versions_.get(),
+ &mutex_, &shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot,
+ snapshot_checker, job_context, log_buffer, directories_.GetDbDir(),
+ GetDataDir(cfd, 0U),
+ GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
+ &event_logger_, mutable_cf_options.report_bg_io_stats,
+ true /* sync_output_directory */, true /* write_manifest */, thread_pri);
+
+ FileMetaData file_meta;
+
+ TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
+ flush_job.PickMemTable();
+ TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:AfterPickMemtables");
+
+#ifndef ROCKSDB_LITE
+ // may temporarily unlock and lock the mutex.
+ NotifyOnFlushBegin(cfd, &file_meta, mutable_cf_options, job_context->job_id);
+#endif // ROCKSDB_LITE
+
+ Status s;
+ if (logfile_number_ > 0 &&
+ versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1) {
+ // If there are more than one column families, we need to make sure that
+ // all the log files except the most recent one are synced. Otherwise if
+ // the host crashes after flushing and before WAL is persistent, the
+ // flushed SST may contain data from write batches whose updates to
+ // other column families are missing.
+ // SyncClosedLogs() may unlock and re-lock the db_mutex.
+ s = SyncClosedLogs(job_context);
+ } else {
+ TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Skip");
+ }
+
+ // Within flush_job.Run, rocksdb may call event listener to notify
+ // file creation and deletion.
+ //
+ // Note that flush_job.Run will unlock and lock the db_mutex,
+ // and EventListener callback will be called when the db_mutex
+ // is unlocked by the current thread.
+ if (s.ok()) {
+ s = flush_job.Run(&logs_with_prep_tracker_, &file_meta);
+ } else {
+ flush_job.Cancel();
+ }
+
+ if (s.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd, superversion_context,
+ mutable_cf_options);
+ if (made_progress) {
+ *made_progress = true;
+ }
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ ROCKS_LOG_BUFFER(log_buffer, "[%s] Level summary: %s\n",
+ cfd->GetName().c_str(),
+ cfd->current()->storage_info()->LevelSummary(&tmp));
+ }
+
+ if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
+ Status new_bg_error = s;
+ error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
+ }
+ if (s.ok()) {
+#ifndef ROCKSDB_LITE
+ // may temporarily unlock and lock the mutex.
+ NotifyOnFlushCompleted(cfd, mutable_cf_options,
+ flush_job.GetCommittedFlushJobsInfo());
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ if (sfm) {
+ // Notify sst_file_manager that a new file was added
+ std::string file_path = MakeTableFileName(
+ cfd->ioptions()->cf_paths[0].path, file_meta.fd.GetNumber());
+ sfm->OnAddFile(file_path);
+ if (sfm->IsMaxAllowedSpaceReached()) {
+ Status new_bg_error =
+ Status::SpaceLimit("Max allowed space was reached");
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
+ &new_bg_error);
+ error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
+ }
+ }
+#endif // ROCKSDB_LITE
+ }
+ TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:Finish");
+ return s;
+}
+
+Status DBImpl::FlushMemTablesToOutputFiles(
+ const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
+ JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
+ if (immutable_db_options_.atomic_flush) {
+ return AtomicFlushMemTablesToOutputFiles(
+ bg_flush_args, made_progress, job_context, log_buffer, thread_pri);
+ }
+ std::vector<SequenceNumber> snapshot_seqs;
+ SequenceNumber earliest_write_conflict_snapshot;
+ SnapshotChecker* snapshot_checker;
+ GetSnapshotContext(job_context, &snapshot_seqs,
+ &earliest_write_conflict_snapshot, &snapshot_checker);
+ Status status;
+ for (auto& arg : bg_flush_args) {
+ ColumnFamilyData* cfd = arg.cfd_;
+ MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
+ SuperVersionContext* superversion_context = arg.superversion_context_;
+ Status s = FlushMemTableToOutputFile(
+ cfd, mutable_cf_options, made_progress, job_context,
+ superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
+ snapshot_checker, log_buffer, thread_pri);
+ if (!s.ok()) {
+ status = s;
+ if (!s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
+ // At this point, DB is not shutting down, nor is cfd dropped.
+ // Something is wrong, thus we break out of the loop.
+ break;
+ }
+ }
+ }
+ return status;
+}
+
+/*
+ * Atomically flushes multiple column families.
+ *
+ * For each column family, all memtables with ID smaller than or equal to the
+ * ID specified in bg_flush_args will be flushed. Only after all column
+ * families finish flush will this function commit to MANIFEST. If any of the
+ * column families are not flushed successfully, this function does not have
+ * any side-effect on the state of the database.
+ */
+Status DBImpl::AtomicFlushMemTablesToOutputFiles(
+ const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
+ JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
+ mutex_.AssertHeld();
+
+ autovector<ColumnFamilyData*> cfds;
+ for (const auto& arg : bg_flush_args) {
+ cfds.emplace_back(arg.cfd_);
+ }
+
+#ifndef NDEBUG
+ for (const auto cfd : cfds) {
+ assert(cfd->imm()->NumNotFlushed() != 0);
+ assert(cfd->imm()->IsFlushPending());
+ }
+#endif /* !NDEBUG */
+
+ std::vector<SequenceNumber> snapshot_seqs;
+ SequenceNumber earliest_write_conflict_snapshot;
+ SnapshotChecker* snapshot_checker;
+ GetSnapshotContext(job_context, &snapshot_seqs,
+ &earliest_write_conflict_snapshot, &snapshot_checker);
+
+ autovector<Directory*> distinct_output_dirs;
+ autovector<std::string> distinct_output_dir_paths;
+ std::vector<std::unique_ptr<FlushJob>> jobs;
+ std::vector<MutableCFOptions> all_mutable_cf_options;
+ int num_cfs = static_cast<int>(cfds.size());
+ all_mutable_cf_options.reserve(num_cfs);
+ for (int i = 0; i < num_cfs; ++i) {
+ auto cfd = cfds[i];
+ Directory* data_dir = GetDataDir(cfd, 0U);
+ const std::string& curr_path = cfd->ioptions()->cf_paths[0].path;
+
+ // Add to distinct output directories if eligible. Use linear search. Since
+ // the number of elements in the vector is not large, performance should be
+ // tolerable.
+ bool found = false;
+ for (const auto& path : distinct_output_dir_paths) {
+ if (path == curr_path) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ distinct_output_dir_paths.emplace_back(curr_path);
+ distinct_output_dirs.emplace_back(data_dir);
+ }
+
+ all_mutable_cf_options.emplace_back(*cfd->GetLatestMutableCFOptions());
+ const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.back();
+ const uint64_t* max_memtable_id = &(bg_flush_args[i].max_memtable_id_);
+ jobs.emplace_back(new FlushJob(
+ dbname_, cfd, immutable_db_options_, mutable_cf_options,
+ max_memtable_id, file_options_for_compaction_, versions_.get(), &mutex_,
+ &shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot,
+ snapshot_checker, job_context, log_buffer, directories_.GetDbDir(),
+ data_dir, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
+ stats_, &event_logger_, mutable_cf_options.report_bg_io_stats,
+ false /* sync_output_directory */, false /* write_manifest */,
+ thread_pri));
+ jobs.back()->PickMemTable();
+ }
+
+ std::vector<FileMetaData> file_meta(num_cfs);
+ Status s;
+ assert(num_cfs == static_cast<int>(jobs.size()));
+
+#ifndef ROCKSDB_LITE
+ for (int i = 0; i != num_cfs; ++i) {
+ const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.at(i);
+ // may temporarily unlock and lock the mutex.
+ NotifyOnFlushBegin(cfds[i], &file_meta[i], mutable_cf_options,
+ job_context->job_id);
+ }
+#endif /* !ROCKSDB_LITE */
+
+ if (logfile_number_ > 0) {
+ // TODO (yanqin) investigate whether we should sync the closed logs for
+ // single column family case.
+ s = SyncClosedLogs(job_context);
+ }
+
+ // exec_status stores the execution status of flush_jobs as
+ // <bool /* executed */, Status /* status code */>
+ autovector<std::pair<bool, Status>> exec_status;
+ for (int i = 0; i != num_cfs; ++i) {
+ // Initially all jobs are not executed, with status OK.
+ exec_status.emplace_back(false, Status::OK());
+ }
+
+ if (s.ok()) {
+ // TODO (yanqin): parallelize jobs with threads.
+ for (int i = 1; i != num_cfs; ++i) {
+ exec_status[i].second =
+ jobs[i]->Run(&logs_with_prep_tracker_, &file_meta[i]);
+ exec_status[i].first = true;
+ }
+ if (num_cfs > 1) {
+ TEST_SYNC_POINT(
+ "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:1");
+ TEST_SYNC_POINT(
+ "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:2");
+ }
+ assert(exec_status.size() > 0);
+ assert(!file_meta.empty());
+ exec_status[0].second =
+ jobs[0]->Run(&logs_with_prep_tracker_, &file_meta[0]);
+ exec_status[0].first = true;
+
+ Status error_status;
+ for (const auto& e : exec_status) {
+ if (!e.second.ok()) {
+ s = e.second;
+ if (!e.second.IsShutdownInProgress() &&
+ !e.second.IsColumnFamilyDropped()) {
+ // If a flush job did not return OK, and the CF is not dropped, and
+ // the DB is not shutting down, then we have to return this result to
+ // caller later.
+ error_status = e.second;
+ }
+ }
+ }
+
+ s = error_status.ok() ? s : error_status;
+ }
+
+ if (s.IsColumnFamilyDropped()) {
+ s = Status::OK();
+ }
+
+ if (s.ok() || s.IsShutdownInProgress()) {
+ // Sync on all distinct output directories.
+ for (auto dir : distinct_output_dirs) {
+ if (dir != nullptr) {
+ Status error_status = dir->Fsync();
+ if (!error_status.ok()) {
+ s = error_status;
+ break;
+ }
+ }
+ }
+ } else {
+ // Need to undo atomic flush if something went wrong, i.e. s is not OK and
+ // it is not because of CF drop.
+ // Have to cancel the flush jobs that have NOT executed because we need to
+ // unref the versions.
+ for (int i = 0; i != num_cfs; ++i) {
+ if (!exec_status[i].first) {
+ jobs[i]->Cancel();
+ }
+ }
+ for (int i = 0; i != num_cfs; ++i) {
+ if (exec_status[i].first && exec_status[i].second.ok()) {
+ auto& mems = jobs[i]->GetMemTables();
+ cfds[i]->imm()->RollbackMemtableFlush(mems,
+ file_meta[i].fd.GetNumber());
+ }
+ }
+ }
+
+ if (s.ok()) {
+ auto wait_to_install_func = [&]() {
+ bool ready = true;
+ for (size_t i = 0; i != cfds.size(); ++i) {
+ const auto& mems = jobs[i]->GetMemTables();
+ if (cfds[i]->IsDropped()) {
+ // If the column family is dropped, then do not wait.
+ continue;
+ } else if (!mems.empty() &&
+ cfds[i]->imm()->GetEarliestMemTableID() < mems[0]->GetID()) {
+ // If a flush job needs to install the flush result for mems and
+ // mems[0] is not the earliest memtable, it means another thread must
+ // be installing flush results for the same column family, then the
+ // current thread needs to wait.
+ ready = false;
+ break;
+ } else if (mems.empty() && cfds[i]->imm()->GetEarliestMemTableID() <=
+ bg_flush_args[i].max_memtable_id_) {
+ // If a flush job does not need to install flush results, then it has
+ // to wait until all memtables up to max_memtable_id_ (inclusive) are
+ // installed.
+ ready = false;
+ break;
+ }
+ }
+ return ready;
+ };
+
+ bool resuming_from_bg_err = error_handler_.IsDBStopped();
+ while ((!error_handler_.IsDBStopped() ||
+ error_handler_.GetRecoveryError().ok()) &&
+ !wait_to_install_func()) {
+ atomic_flush_install_cv_.Wait();
+ }
+
+ s = resuming_from_bg_err ? error_handler_.GetRecoveryError()
+ : error_handler_.GetBGError();
+ }
+
+ if (s.ok()) {
+ autovector<ColumnFamilyData*> tmp_cfds;
+ autovector<const autovector<MemTable*>*> mems_list;
+ autovector<const MutableCFOptions*> mutable_cf_options_list;
+ autovector<FileMetaData*> tmp_file_meta;
+ for (int i = 0; i != num_cfs; ++i) {
+ const auto& mems = jobs[i]->GetMemTables();
+ if (!cfds[i]->IsDropped() && !mems.empty()) {
+ tmp_cfds.emplace_back(cfds[i]);
+ mems_list.emplace_back(&mems);
+ mutable_cf_options_list.emplace_back(&all_mutable_cf_options[i]);
+ tmp_file_meta.emplace_back(&file_meta[i]);
+ }
+ }
+
+ s = InstallMemtableAtomicFlushResults(
+ nullptr /* imm_lists */, tmp_cfds, mutable_cf_options_list, mems_list,
+ versions_.get(), &mutex_, tmp_file_meta,
+ &job_context->memtables_to_free, directories_.GetDbDir(), log_buffer);
+ }
+
+ if (s.ok()) {
+ assert(num_cfs ==
+ static_cast<int>(job_context->superversion_contexts.size()));
+ for (int i = 0; i != num_cfs; ++i) {
+ if (cfds[i]->IsDropped()) {
+ continue;
+ }
+ InstallSuperVersionAndScheduleWork(cfds[i],
+ &job_context->superversion_contexts[i],
+ all_mutable_cf_options[i]);
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ ROCKS_LOG_BUFFER(log_buffer, "[%s] Level summary: %s\n",
+ cfds[i]->GetName().c_str(),
+ cfds[i]->current()->storage_info()->LevelSummary(&tmp));
+ }
+ if (made_progress) {
+ *made_progress = true;
+ }
+#ifndef ROCKSDB_LITE
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ assert(all_mutable_cf_options.size() == static_cast<size_t>(num_cfs));
+ for (int i = 0; i != num_cfs; ++i) {
+ if (cfds[i]->IsDropped()) {
+ continue;
+ }
+ NotifyOnFlushCompleted(cfds[i], all_mutable_cf_options[i],
+ jobs[i]->GetCommittedFlushJobsInfo());
+ if (sfm) {
+ std::string file_path = MakeTableFileName(
+ cfds[i]->ioptions()->cf_paths[0].path, file_meta[i].fd.GetNumber());
+ sfm->OnAddFile(file_path);
+ if (sfm->IsMaxAllowedSpaceReached() &&
+ error_handler_.GetBGError().ok()) {
+ Status new_bg_error =
+ Status::SpaceLimit("Max allowed space was reached");
+ error_handler_.SetBGError(new_bg_error,
+ BackgroundErrorReason::kFlush);
+ }
+ }
+ }
+#endif // ROCKSDB_LITE
+ }
+
+ if (!s.ok() && !s.IsShutdownInProgress()) {
+ Status new_bg_error = s;
+ error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
+ }
+
+ return s;
+}
+
+void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
+ const MutableCFOptions& mutable_cf_options,
+ int job_id) {
+#ifndef ROCKSDB_LITE
+ if (immutable_db_options_.listeners.size() == 0U) {
+ return;
+ }
+ mutex_.AssertHeld();
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return;
+ }
+ bool triggered_writes_slowdown =
+ (cfd->current()->storage_info()->NumLevelFiles(0) >=
+ mutable_cf_options.level0_slowdown_writes_trigger);
+ bool triggered_writes_stop =
+ (cfd->current()->storage_info()->NumLevelFiles(0) >=
+ mutable_cf_options.level0_stop_writes_trigger);
+ // release lock while notifying events
+ mutex_.Unlock();
+ {
+ FlushJobInfo info{};
+ info.cf_id = cfd->GetID();
+ info.cf_name = cfd->GetName();
+ // TODO(yhchiang): make db_paths dynamic in case flush does not
+ // go to L0 in the future.
+ const uint64_t file_number = file_meta->fd.GetNumber();
+ info.file_path =
+ MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_number);
+ info.file_number = file_number;
+ info.thread_id = env_->GetThreadID();
+ info.job_id = job_id;
+ info.triggered_writes_slowdown = triggered_writes_slowdown;
+ info.triggered_writes_stop = triggered_writes_stop;
+ info.smallest_seqno = file_meta->fd.smallest_seqno;
+ info.largest_seqno = file_meta->fd.largest_seqno;
+ info.flush_reason = cfd->GetFlushReason();
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnFlushBegin(this, info);
+ }
+ }
+ mutex_.Lock();
+// no need to signal bg_cv_ as it will be signaled at the end of the
+// flush process.
+#else
+ (void)cfd;
+ (void)file_meta;
+ (void)mutable_cf_options;
+ (void)job_id;
+#endif // ROCKSDB_LITE
+}
+
+void DBImpl::NotifyOnFlushCompleted(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ std::list<std::unique_ptr<FlushJobInfo>>* flush_jobs_info) {
+#ifndef ROCKSDB_LITE
+ assert(flush_jobs_info != nullptr);
+ if (immutable_db_options_.listeners.size() == 0U) {
+ return;
+ }
+ mutex_.AssertHeld();
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return;
+ }
+ bool triggered_writes_slowdown =
+ (cfd->current()->storage_info()->NumLevelFiles(0) >=
+ mutable_cf_options.level0_slowdown_writes_trigger);
+ bool triggered_writes_stop =
+ (cfd->current()->storage_info()->NumLevelFiles(0) >=
+ mutable_cf_options.level0_stop_writes_trigger);
+ // release lock while notifying events
+ mutex_.Unlock();
+ {
+ for (auto& info : *flush_jobs_info) {
+ info->triggered_writes_slowdown = triggered_writes_slowdown;
+ info->triggered_writes_stop = triggered_writes_stop;
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnFlushCompleted(this, *info);
+ }
+ }
+ flush_jobs_info->clear();
+ }
+ mutex_.Lock();
+ // no need to signal bg_cv_ as it will be signaled at the end of the
+ // flush process.
+#else
+ (void)cfd;
+ (void)mutable_cf_options;
+ (void)flush_jobs_info;
+#endif // ROCKSDB_LITE
+}
+
+Status DBImpl::CompactRange(const CompactRangeOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+
+ if (options.target_path_id >= cfd->ioptions()->cf_paths.size()) {
+ return Status::InvalidArgument("Invalid target path ID");
+ }
+
+ bool exclusive = options.exclusive_manual_compaction;
+
+ bool flush_needed = true;
+ if (begin != nullptr && end != nullptr) {
+ // TODO(ajkr): We could also optimize away the flush in certain cases where
+ // one/both sides of the interval are unbounded. But it requires more
+ // changes to RangesOverlapWithMemtables.
+ Range range(*begin, *end);
+ SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
+ cfd->RangesOverlapWithMemtables({range}, super_version, &flush_needed);
+ CleanupSuperVersion(super_version);
+ }
+
+ Status s;
+ if (flush_needed) {
+ FlushOptions fo;
+ fo.allow_write_stall = options.allow_write_stall;
+ if (immutable_db_options_.atomic_flush) {
+ autovector<ColumnFamilyData*> cfds;
+ mutex_.Lock();
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ mutex_.Unlock();
+ s = AtomicFlushMemTables(cfds, fo, FlushReason::kManualCompaction,
+ false /* writes_stopped */);
+ } else {
+ s = FlushMemTable(cfd, fo, FlushReason::kManualCompaction,
+ false /* writes_stopped*/);
+ }
+ if (!s.ok()) {
+ LogFlush(immutable_db_options_.info_log);
+ return s;
+ }
+ }
+
+ int max_level_with_files = 0;
+ // max_file_num_to_ignore can be used to filter out newly created SST files,
+ // useful for bottom level compaction in a manual compaction
+ uint64_t max_file_num_to_ignore = port::kMaxUint64;
+ uint64_t next_file_number = port::kMaxUint64;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ Version* base = cfd->current();
+ for (int level = 1; level < base->storage_info()->num_non_empty_levels();
+ level++) {
+ if (base->storage_info()->OverlapInLevel(level, begin, end)) {
+ max_level_with_files = level;
+ }
+ }
+ next_file_number = versions_->current_next_file_number();
+ }
+
+ int final_output_level = 0;
+
+ if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
+ cfd->NumberLevels() > 1) {
+ // Always compact all files together.
+ final_output_level = cfd->NumberLevels() - 1;
+ // if bottom most level is reserved
+ if (immutable_db_options_.allow_ingest_behind) {
+ final_output_level--;
+ }
+ s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
+ final_output_level, options, begin, end, exclusive,
+ false, max_file_num_to_ignore);
+ } else {
+ for (int level = 0; level <= max_level_with_files; level++) {
+ int output_level;
+ // in case the compaction is universal or if we're compacting the
+ // bottom-most level, the output level will be the same as input one.
+ // level 0 can never be the bottommost level (i.e. if all files are in
+ // level 0, we will compact to level 1)
+ if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
+ cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
+ output_level = level;
+ } else if (level == max_level_with_files && level > 0) {
+ if (options.bottommost_level_compaction ==
+ BottommostLevelCompaction::kSkip) {
+ // Skip bottommost level compaction
+ continue;
+ } else if (options.bottommost_level_compaction ==
+ BottommostLevelCompaction::kIfHaveCompactionFilter &&
+ cfd->ioptions()->compaction_filter == nullptr &&
+ cfd->ioptions()->compaction_filter_factory == nullptr) {
+ // Skip bottommost level compaction since we don't have a compaction
+ // filter
+ continue;
+ }
+ output_level = level;
+ // update max_file_num_to_ignore only for bottom level compaction
+ // because data in newly compacted files in middle levels may still need
+ // to be pushed down
+ max_file_num_to_ignore = next_file_number;
+ } else {
+ output_level = level + 1;
+ if (cfd->ioptions()->compaction_style == kCompactionStyleLevel &&
+ cfd->ioptions()->level_compaction_dynamic_level_bytes &&
+ level == 0) {
+ output_level = ColumnFamilyData::kCompactToBaseLevel;
+ }
+ }
+ s = RunManualCompaction(cfd, level, output_level, options, begin, end,
+ exclusive, false, max_file_num_to_ignore);
+ if (!s.ok()) {
+ break;
+ }
+ if (output_level == ColumnFamilyData::kCompactToBaseLevel) {
+ final_output_level = cfd->NumberLevels() - 1;
+ } else if (output_level > final_output_level) {
+ final_output_level = output_level;
+ }
+ TEST_SYNC_POINT("DBImpl::RunManualCompaction()::1");
+ TEST_SYNC_POINT("DBImpl::RunManualCompaction()::2");
+ }
+ }
+ if (!s.ok()) {
+ LogFlush(immutable_db_options_.info_log);
+ return s;
+ }
+
+ if (options.change_level) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[RefitLevel] waiting for background threads to stop");
+ s = PauseBackgroundWork();
+ if (s.ok()) {
+ s = ReFitLevel(cfd, final_output_level, options.target_level);
+ }
+ ContinueBackgroundWork();
+ }
+ LogFlush(immutable_db_options_.info_log);
+
+ {
+ InstrumentedMutexLock l(&mutex_);
+ // an automatic compaction that has been scheduled might have been
+ // preempted by the manual compactions. Need to schedule it back.
+ MaybeScheduleFlushOrCompaction();
+ }
+
+ return s;
+}
+
+Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& input_file_names,
+ const int output_level, const int output_path_id,
+ std::vector<std::string>* const output_file_names,
+ CompactionJobInfo* compaction_job_info) {
+#ifdef ROCKSDB_LITE
+ (void)compact_options;
+ (void)column_family;
+ (void)input_file_names;
+ (void)output_level;
+ (void)output_path_id;
+ (void)output_file_names;
+ (void)compaction_job_info;
+ // not supported in lite version
+ return Status::NotSupported("Not supported in ROCKSDB LITE");
+#else
+ if (column_family == nullptr) {
+ return Status::InvalidArgument("ColumnFamilyHandle must be non-null.");
+ }
+
+ auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ assert(cfd);
+
+ Status s;
+ JobContext job_context(0, true);
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
+ immutable_db_options_.info_log.get());
+
+ // Perform CompactFiles
+ TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile2");
+ {
+ InstrumentedMutexLock l(&mutex_);
+
+ // This call will unlock/lock the mutex to wait for current running
+ // IngestExternalFile() calls to finish.
+ WaitForIngestFile();
+
+ // We need to get current after `WaitForIngestFile`, because
+ // `IngestExternalFile` may add files that overlap with `input_file_names`
+ auto* current = cfd->current();
+ current->Ref();
+
+ s = CompactFilesImpl(compact_options, cfd, current, input_file_names,
+ output_file_names, output_level, output_path_id,
+ &job_context, &log_buffer, compaction_job_info);
+
+ current->Unref();
+ }
+
+ // Find and delete obsolete files
+ {
+ InstrumentedMutexLock l(&mutex_);
+ // If !s.ok(), this means that Compaction failed. In that case, we want
+ // to delete all obsolete files we might have created and we force
+ // FindObsoleteFiles(). This is because job_context does not
+ // catch all created files if compaction failed.
+ FindObsoleteFiles(&job_context, !s.ok());
+ } // release the mutex
+
+ // delete unnecessary files if any, this is done outside the mutex
+ if (job_context.HaveSomethingToClean() ||
+ job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
+ // Have to flush the info logs before bg_compaction_scheduled_--
+ // because if bg_flush_scheduled_ becomes 0 and the lock is
+ // released, the deconstructor of DB can kick in and destroy all the
+ // states of DB so info_log might not be available after that point.
+ // It also applies to access other states that DB owns.
+ log_buffer.FlushBufferToLog();
+ if (job_context.HaveSomethingToDelete()) {
+ // no mutex is locked here. No need to Unlock() and Lock() here.
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ }
+
+ return s;
+#endif // ROCKSDB_LITE
+}
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::CompactFilesImpl(
+ const CompactionOptions& compact_options, ColumnFamilyData* cfd,
+ Version* version, const std::vector<std::string>& input_file_names,
+ std::vector<std::string>* const output_file_names, const int output_level,
+ int output_path_id, JobContext* job_context, LogBuffer* log_buffer,
+ CompactionJobInfo* compaction_job_info) {
+ mutex_.AssertHeld();
+
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return Status::ShutdownInProgress();
+ }
+ if (manual_compaction_paused_.load(std::memory_order_acquire)) {
+ return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
+ }
+
+ std::unordered_set<uint64_t> input_set;
+ for (const auto& file_name : input_file_names) {
+ input_set.insert(TableFileNameToNumber(file_name));
+ }
+
+ ColumnFamilyMetaData cf_meta;
+ // TODO(yhchiang): can directly use version here if none of the
+ // following functions call is pluggable to external developers.
+ version->GetColumnFamilyMetaData(&cf_meta);
+
+ if (output_path_id < 0) {
+ if (cfd->ioptions()->cf_paths.size() == 1U) {
+ output_path_id = 0;
+ } else {
+ return Status::NotSupported(
+ "Automatic output path selection is not "
+ "yet supported in CompactFiles()");
+ }
+ }
+
+ Status s = cfd->compaction_picker()->SanitizeCompactionInputFiles(
+ &input_set, cf_meta, output_level);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::vector<CompactionInputFiles> input_files;
+ s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
+ &input_files, &input_set, version->storage_info(), compact_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ for (const auto& inputs : input_files) {
+ if (cfd->compaction_picker()->AreFilesInCompaction(inputs.files)) {
+ return Status::Aborted(
+ "Some of the necessary compaction input "
+ "files are already being compacted");
+ }
+ }
+ bool sfm_reserved_compact_space = false;
+ // First check if we have enough room to do the compaction
+ bool enough_room = EnoughRoomForCompaction(
+ cfd, input_files, &sfm_reserved_compact_space, log_buffer);
+
+ if (!enough_room) {
+ // m's vars will get set properly at the end of this function,
+ // as long as status == CompactionTooLarge
+ return Status::CompactionTooLarge();
+ }
+
+ // At this point, CompactFiles will be run.
+ bg_compaction_scheduled_++;
+
+ std::unique_ptr<Compaction> c;
+ assert(cfd->compaction_picker());
+ c.reset(cfd->compaction_picker()->CompactFiles(
+ compact_options, input_files, output_level, version->storage_info(),
+ *cfd->GetLatestMutableCFOptions(), output_path_id));
+ // we already sanitized the set of input files and checked for conflicts
+ // without releasing the lock, so we're guaranteed a compaction can be formed.
+ assert(c != nullptr);
+
+ c->SetInputVersion(version);
+ // deletion compaction currently not allowed in CompactFiles.
+ assert(!c->deletion_compaction());
+
+ std::vector<SequenceNumber> snapshot_seqs;
+ SequenceNumber earliest_write_conflict_snapshot;
+ SnapshotChecker* snapshot_checker;
+ GetSnapshotContext(job_context, &snapshot_seqs,
+ &earliest_write_conflict_snapshot, &snapshot_checker);
+
+ std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
+ new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+
+ assert(is_snapshot_supported_ || snapshots_.empty());
+ CompactionJobStats compaction_job_stats;
+ CompactionJob compaction_job(
+ job_context->job_id, c.get(), immutable_db_options_,
+ file_options_for_compaction_, versions_.get(), &shutting_down_,
+ preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
+ GetDataDir(c->column_family_data(), c->output_path_id()), stats_, &mutex_,
+ &error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
+ snapshot_checker, table_cache_, &event_logger_,
+ c->mutable_cf_options()->paranoid_file_checks,
+ c->mutable_cf_options()->report_bg_io_stats, dbname_,
+ &compaction_job_stats, Env::Priority::USER, &manual_compaction_paused_);
+
+ // Creating a compaction influences the compaction score because the score
+ // takes running compactions into account (by skipping files that are already
+ // being compacted). Since we just changed compaction score, we recalculate it
+ // here.
+ version->storage_info()->ComputeCompactionScore(*cfd->ioptions(),
+ *c->mutable_cf_options());
+
+ compaction_job.Prepare();
+
+ mutex_.Unlock();
+ TEST_SYNC_POINT("CompactFilesImpl:0");
+ TEST_SYNC_POINT("CompactFilesImpl:1");
+ compaction_job.Run();
+ TEST_SYNC_POINT("CompactFilesImpl:2");
+ TEST_SYNC_POINT("CompactFilesImpl:3");
+ mutex_.Lock();
+
+ Status status = compaction_job.Install(*c->mutable_cf_options());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(c->column_family_data(),
+ &job_context->superversion_contexts[0],
+ *c->mutable_cf_options());
+ }
+ c->ReleaseCompactionFiles(s);
+#ifndef ROCKSDB_LITE
+ // Need to make sure SstFileManager does its bookkeeping
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ if (sfm && sfm_reserved_compact_space) {
+ sfm->OnCompactionCompletion(c.get());
+ }
+#endif // ROCKSDB_LITE
+
+ ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
+
+ if (compaction_job_info != nullptr) {
+ BuildCompactionJobInfo(cfd, c.get(), s, compaction_job_stats,
+ job_context->job_id, version, compaction_job_info);
+ }
+
+ if (status.ok()) {
+ // Done
+ } else if (status.IsColumnFamilyDropped() || status.IsShutdownInProgress()) {
+ // Ignore compaction errors found during shutting down
+ } else if (status.IsManualCompactionPaused()) {
+ // Don't report stopping manual compaction as error
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] [JOB %d] Stopping manual compaction",
+ c->column_family_data()->GetName().c_str(),
+ job_context->job_id);
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "[%s] [JOB %d] Compaction error: %s",
+ c->column_family_data()->GetName().c_str(),
+ job_context->job_id, status.ToString().c_str());
+ error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
+ }
+
+ if (output_file_names != nullptr) {
+ for (const auto newf : c->edit()->GetNewFiles()) {
+ (*output_file_names)
+ .push_back(TableFileName(c->immutable_cf_options()->cf_paths,
+ newf.second.fd.GetNumber(),
+ newf.second.fd.GetPathId()));
+ }
+ }
+
+ c.reset();
+
+ bg_compaction_scheduled_--;
+ if (bg_compaction_scheduled_ == 0) {
+ bg_cv_.SignalAll();
+ }
+ MaybeScheduleFlushOrCompaction();
+ TEST_SYNC_POINT("CompactFilesImpl:End");
+
+ return status;
+}
+#endif // ROCKSDB_LITE
+
+Status DBImpl::PauseBackgroundWork() {
+ InstrumentedMutexLock guard_lock(&mutex_);
+ bg_compaction_paused_++;
+ while (bg_bottom_compaction_scheduled_ > 0 || bg_compaction_scheduled_ > 0 ||
+ bg_flush_scheduled_ > 0) {
+ bg_cv_.Wait();
+ }
+ bg_work_paused_++;
+ return Status::OK();
+}
+
+Status DBImpl::ContinueBackgroundWork() {
+ InstrumentedMutexLock guard_lock(&mutex_);
+ if (bg_work_paused_ == 0) {
+ return Status::InvalidArgument();
+ }
+ assert(bg_work_paused_ > 0);
+ assert(bg_compaction_paused_ > 0);
+ bg_compaction_paused_--;
+ bg_work_paused_--;
+ // It's sufficient to check just bg_work_paused_ here since
+ // bg_work_paused_ is always no greater than bg_compaction_paused_
+ if (bg_work_paused_ == 0) {
+ MaybeScheduleFlushOrCompaction();
+ }
+ return Status::OK();
+}
+
+void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
+ const Status& st,
+ const CompactionJobStats& job_stats,
+ int job_id) {
+#ifndef ROCKSDB_LITE
+ if (immutable_db_options_.listeners.empty()) {
+ return;
+ }
+ mutex_.AssertHeld();
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return;
+ }
+ if (c->is_manual_compaction() &&
+ manual_compaction_paused_.load(std::memory_order_acquire)) {
+ return;
+ }
+ Version* current = cfd->current();
+ current->Ref();
+ // release lock while notifying events
+ mutex_.Unlock();
+ TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex");
+ {
+ CompactionJobInfo info{};
+ info.cf_name = cfd->GetName();
+ info.status = st;
+ info.thread_id = env_->GetThreadID();
+ info.job_id = job_id;
+ info.base_input_level = c->start_level();
+ info.output_level = c->output_level();
+ info.stats = job_stats;
+ info.table_properties = c->GetOutputTableProperties();
+ info.compaction_reason = c->compaction_reason();
+ info.compression = c->output_compression();
+ for (size_t i = 0; i < c->num_input_levels(); ++i) {
+ for (const auto fmd : *c->inputs(i)) {
+ const FileDescriptor& desc = fmd->fd;
+ const uint64_t file_number = desc.GetNumber();
+ auto fn = TableFileName(c->immutable_cf_options()->cf_paths,
+ file_number, desc.GetPathId());
+ info.input_files.push_back(fn);
+ info.input_file_infos.push_back(CompactionFileInfo{
+ static_cast<int>(i), file_number, fmd->oldest_blob_file_number});
+ if (info.table_properties.count(fn) == 0) {
+ std::shared_ptr<const TableProperties> tp;
+ auto s = current->GetTableProperties(&tp, fmd, &fn);
+ if (s.ok()) {
+ info.table_properties[fn] = tp;
+ }
+ }
+ }
+ }
+ for (const auto newf : c->edit()->GetNewFiles()) {
+ const FileMetaData& meta = newf.second;
+ const FileDescriptor& desc = meta.fd;
+ const uint64_t file_number = desc.GetNumber();
+ info.output_files.push_back(TableFileName(
+ c->immutable_cf_options()->cf_paths, file_number, desc.GetPathId()));
+ info.output_file_infos.push_back(CompactionFileInfo{
+ newf.first, file_number, meta.oldest_blob_file_number});
+ }
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnCompactionBegin(this, info);
+ }
+ }
+ mutex_.Lock();
+ current->Unref();
+#else
+ (void)cfd;
+ (void)c;
+ (void)st;
+ (void)job_stats;
+ (void)job_id;
+#endif // ROCKSDB_LITE
+}
+
+void DBImpl::NotifyOnCompactionCompleted(
+ ColumnFamilyData* cfd, Compaction* c, const Status& st,
+ const CompactionJobStats& compaction_job_stats, const int job_id) {
+#ifndef ROCKSDB_LITE
+ if (immutable_db_options_.listeners.size() == 0U) {
+ return;
+ }
+ mutex_.AssertHeld();
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return;
+ }
+ if (c->is_manual_compaction() &&
+ manual_compaction_paused_.load(std::memory_order_acquire)) {
+ return;
+ }
+ Version* current = cfd->current();
+ current->Ref();
+ // release lock while notifying events
+ mutex_.Unlock();
+ TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
+ {
+ CompactionJobInfo info{};
+ BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, current,
+ &info);
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnCompactionCompleted(this, info);
+ }
+ }
+ mutex_.Lock();
+ current->Unref();
+ // no need to signal bg_cv_ as it will be signaled at the end of the
+ // flush process.
+#else
+ (void)cfd;
+ (void)c;
+ (void)st;
+ (void)compaction_job_stats;
+ (void)job_id;
+#endif // ROCKSDB_LITE
+}
+
+// REQUIREMENT: block all background work by calling PauseBackgroundWork()
+// before calling this function
+Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
+ assert(level < cfd->NumberLevels());
+ if (target_level >= cfd->NumberLevels()) {
+ return Status::InvalidArgument("Target level exceeds number of levels");
+ }
+
+ SuperVersionContext sv_context(/* create_superversion */ true);
+
+ Status status;
+
+ InstrumentedMutexLock guard_lock(&mutex_);
+
+ // only allow one thread refitting
+ if (refitting_level_) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[ReFitLevel] another thread is refitting");
+ return Status::NotSupported("another thread is refitting");
+ }
+ refitting_level_ = true;
+
+ const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
+ // move to a smaller level
+ int to_level = target_level;
+ if (target_level < 0) {
+ to_level = FindMinimumEmptyLevelFitting(cfd, mutable_cf_options, level);
+ }
+
+ auto* vstorage = cfd->current()->storage_info();
+ if (to_level > level) {
+ if (level == 0) {
+ return Status::NotSupported(
+ "Cannot change from level 0 to other levels.");
+ }
+ // Check levels are empty for a trivial move
+ for (int l = level + 1; l <= to_level; l++) {
+ if (vstorage->NumLevelFiles(l) > 0) {
+ return Status::NotSupported(
+ "Levels between source and target are not empty for a move.");
+ }
+ }
+ }
+ if (to_level != level) {
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] Before refitting:\n%s", cfd->GetName().c_str(),
+ cfd->current()->DebugString().data());
+
+ VersionEdit edit;
+ edit.SetColumnFamily(cfd->GetID());
+ for (const auto& f : vstorage->LevelFiles(level)) {
+ edit.DeleteFile(level, f->fd.GetNumber());
+ edit.AddFile(to_level, f->fd.GetNumber(), f->fd.GetPathId(),
+ f->fd.GetFileSize(), f->smallest, f->largest,
+ f->fd.smallest_seqno, f->fd.largest_seqno,
+ f->marked_for_compaction, f->oldest_blob_file_number,
+ f->oldest_ancester_time, f->file_creation_time,
+ f->file_checksum, f->file_checksum_func_name);
+ }
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] Apply version edit:\n%s", cfd->GetName().c_str(),
+ edit.DebugString().data());
+
+ status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
+ directories_.GetDbDir());
+ InstallSuperVersionAndScheduleWork(cfd, &sv_context, mutable_cf_options);
+
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] LogAndApply: %s\n",
+ cfd->GetName().c_str(), status.ToString().data());
+
+ if (status.ok()) {
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] After refitting:\n%s", cfd->GetName().c_str(),
+ cfd->current()->DebugString().data());
+ }
+ }
+
+ sv_context.Clean();
+ refitting_level_ = false;
+
+ return status;
+}
+
+int DBImpl::NumberLevels(ColumnFamilyHandle* column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ return cfh->cfd()->NumberLevels();
+}
+
+int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) {
+ return 0;
+}
+
+int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ InstrumentedMutexLock l(&mutex_);
+ return cfh->cfd()
+ ->GetSuperVersion()
+ ->mutable_cf_options.level0_stop_writes_trigger;
+}
+
+Status DBImpl::Flush(const FlushOptions& flush_options,
+ ColumnFamilyHandle* column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] Manual flush start.",
+ cfh->GetName().c_str());
+ Status s;
+ if (immutable_db_options_.atomic_flush) {
+ s = AtomicFlushMemTables({cfh->cfd()}, flush_options,
+ FlushReason::kManualFlush);
+ } else {
+ s = FlushMemTable(cfh->cfd(), flush_options, FlushReason::kManualFlush);
+ }
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] Manual flush finished, status: %s\n",
+ cfh->GetName().c_str(), s.ToString().c_str());
+ return s;
+}
+
+Status DBImpl::Flush(const FlushOptions& flush_options,
+ const std::vector<ColumnFamilyHandle*>& column_families) {
+ Status s;
+ if (!immutable_db_options_.atomic_flush) {
+ for (auto cfh : column_families) {
+ s = Flush(flush_options, cfh);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ } else {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Manual atomic flush start.\n"
+ "=====Column families:=====");
+ for (auto cfh : column_families) {
+ auto cfhi = static_cast<ColumnFamilyHandleImpl*>(cfh);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s",
+ cfhi->GetName().c_str());
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "=====End of column families list=====");
+ autovector<ColumnFamilyData*> cfds;
+ std::for_each(column_families.begin(), column_families.end(),
+ [&cfds](ColumnFamilyHandle* elem) {
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(elem);
+ cfds.emplace_back(cfh->cfd());
+ });
+ s = AtomicFlushMemTables(cfds, flush_options, FlushReason::kManualFlush);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Manual atomic flush finished, status: %s\n"
+ "=====Column families:=====",
+ s.ToString().c_str());
+ for (auto cfh : column_families) {
+ auto cfhi = static_cast<ColumnFamilyHandleImpl*>(cfh);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s",
+ cfhi->GetName().c_str());
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "=====End of column families list=====");
+ }
+ return s;
+}
+
+Status DBImpl::RunManualCompaction(
+ ColumnFamilyData* cfd, int input_level, int output_level,
+ const CompactRangeOptions& compact_range_options, const Slice* begin,
+ const Slice* end, bool exclusive, bool disallow_trivial_move,
+ uint64_t max_file_num_to_ignore) {
+ assert(input_level == ColumnFamilyData::kCompactAllLevels ||
+ input_level >= 0);
+
+ InternalKey begin_storage, end_storage;
+ CompactionArg* ca;
+
+ bool scheduled = false;
+ bool manual_conflict = false;
+ ManualCompactionState manual;
+ manual.cfd = cfd;
+ manual.input_level = input_level;
+ manual.output_level = output_level;
+ manual.output_path_id = compact_range_options.target_path_id;
+ manual.done = false;
+ manual.in_progress = false;
+ manual.incomplete = false;
+ manual.exclusive = exclusive;
+ manual.disallow_trivial_move = disallow_trivial_move;
+ // For universal compaction, we enforce every manual compaction to compact
+ // all files.
+ if (begin == nullptr ||
+ cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
+ cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
+ manual.begin = nullptr;
+ } else {
+ begin_storage.SetMinPossibleForUserKey(*begin);
+ manual.begin = &begin_storage;
+ }
+ if (end == nullptr ||
+ cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
+ cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
+ manual.end = nullptr;
+ } else {
+ end_storage.SetMaxPossibleForUserKey(*end);
+ manual.end = &end_storage;
+ }
+
+ TEST_SYNC_POINT("DBImpl::RunManualCompaction:0");
+ TEST_SYNC_POINT("DBImpl::RunManualCompaction:1");
+ InstrumentedMutexLock l(&mutex_);
+
+ // When a manual compaction arrives, temporarily disable scheduling of
+ // non-manual compactions and wait until the number of scheduled compaction
+ // jobs drops to zero. This is needed to ensure that this manual compaction
+ // can compact any range of keys/files.
+ //
+ // HasPendingManualCompaction() is true when at least one thread is inside
+ // RunManualCompaction(), i.e. during that time no other compaction will
+ // get scheduled (see MaybeScheduleFlushOrCompaction).
+ //
+ // Note that the following loop doesn't stop more that one thread calling
+ // RunManualCompaction() from getting to the second while loop below.
+ // However, only one of them will actually schedule compaction, while
+ // others will wait on a condition variable until it completes.
+
+ AddManualCompaction(&manual);
+ TEST_SYNC_POINT_CALLBACK("DBImpl::RunManualCompaction:NotScheduled", &mutex_);
+ if (exclusive) {
+ while (bg_bottom_compaction_scheduled_ > 0 ||
+ bg_compaction_scheduled_ > 0) {
+ TEST_SYNC_POINT("DBImpl::RunManualCompaction:WaitScheduled");
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "[%s] Manual compaction waiting for all other scheduled background "
+ "compactions to finish",
+ cfd->GetName().c_str());
+ bg_cv_.Wait();
+ }
+ }
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] Manual compaction starting", cfd->GetName().c_str());
+
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
+ immutable_db_options_.info_log.get());
+ // We don't check bg_error_ here, because if we get the error in compaction,
+ // the compaction will set manual.status to bg_error_ and set manual.done to
+ // true.
+ while (!manual.done) {
+ assert(HasPendingManualCompaction());
+ manual_conflict = false;
+ Compaction* compaction = nullptr;
+ if (ShouldntRunManualCompaction(&manual) || (manual.in_progress == true) ||
+ scheduled ||
+ (((manual.manual_end = &manual.tmp_storage1) != nullptr) &&
+ ((compaction = manual.cfd->CompactRange(
+ *manual.cfd->GetLatestMutableCFOptions(), manual.input_level,
+ manual.output_level, compact_range_options, manual.begin,
+ manual.end, &manual.manual_end, &manual_conflict,
+ max_file_num_to_ignore)) == nullptr &&
+ manual_conflict))) {
+ // exclusive manual compactions should not see a conflict during
+ // CompactRange
+ assert(!exclusive || !manual_conflict);
+ // Running either this or some other manual compaction
+ bg_cv_.Wait();
+ if (scheduled && manual.incomplete == true) {
+ assert(!manual.in_progress);
+ scheduled = false;
+ manual.incomplete = false;
+ }
+ } else if (!scheduled) {
+ if (compaction == nullptr) {
+ manual.done = true;
+ bg_cv_.SignalAll();
+ continue;
+ }
+ ca = new CompactionArg;
+ ca->db = this;
+ ca->prepicked_compaction = new PrepickedCompaction;
+ ca->prepicked_compaction->manual_compaction_state = &manual;
+ ca->prepicked_compaction->compaction = compaction;
+ if (!RequestCompactionToken(
+ cfd, true, &ca->prepicked_compaction->task_token, &log_buffer)) {
+ // Don't throttle manual compaction, only count outstanding tasks.
+ assert(false);
+ }
+ manual.incomplete = false;
+ bg_compaction_scheduled_++;
+ env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
+ &DBImpl::UnscheduleCompactionCallback);
+ scheduled = true;
+ }
+ }
+
+ log_buffer.FlushBufferToLog();
+ assert(!manual.in_progress);
+ assert(HasPendingManualCompaction());
+ RemoveManualCompaction(&manual);
+ bg_cv_.SignalAll();
+ return manual.status;
+}
+
+void DBImpl::GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
+ FlushRequest* req) {
+ assert(req != nullptr);
+ req->reserve(cfds.size());
+ for (const auto cfd : cfds) {
+ if (nullptr == cfd) {
+ // cfd may be null, see DBImpl::ScheduleFlushes
+ continue;
+ }
+ uint64_t max_memtable_id = cfd->imm()->GetLatestMemTableID();
+ req->emplace_back(cfd, max_memtable_id);
+ }
+}
+
+Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
+ const FlushOptions& flush_options,
+ FlushReason flush_reason, bool writes_stopped) {
+ Status s;
+ uint64_t flush_memtable_id = 0;
+ if (!flush_options.allow_write_stall) {
+ bool flush_needed = true;
+ s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed);
+ TEST_SYNC_POINT("DBImpl::FlushMemTable:StallWaitDone");
+ if (!s.ok() || !flush_needed) {
+ return s;
+ }
+ }
+ FlushRequest flush_req;
+ {
+ WriteContext context;
+ InstrumentedMutexLock guard_lock(&mutex_);
+
+ WriteThread::Writer w;
+ WriteThread::Writer nonmem_w;
+ if (!writes_stopped) {
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+ }
+ WaitForPendingWrites();
+
+ if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load()) {
+ s = SwitchMemtable(cfd, &context);
+ }
+ if (s.ok()) {
+ if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
+ !cached_recoverable_state_empty_.load()) {
+ flush_memtable_id = cfd->imm()->GetLatestMemTableID();
+ flush_req.emplace_back(cfd, flush_memtable_id);
+ }
+ if (immutable_db_options_.persist_stats_to_disk) {
+ ColumnFamilyData* cfd_stats =
+ versions_->GetColumnFamilySet()->GetColumnFamily(
+ kPersistentStatsColumnFamilyName);
+ if (cfd_stats != nullptr && cfd_stats != cfd &&
+ !cfd_stats->mem()->IsEmpty()) {
+ // only force flush stats CF when it will be the only CF lagging
+ // behind after the current flush
+ bool stats_cf_flush_needed = true;
+ for (auto* loop_cfd : *versions_->GetColumnFamilySet()) {
+ if (loop_cfd == cfd_stats || loop_cfd == cfd) {
+ continue;
+ }
+ if (loop_cfd->GetLogNumber() <= cfd_stats->GetLogNumber()) {
+ stats_cf_flush_needed = false;
+ }
+ }
+ if (stats_cf_flush_needed) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Force flushing stats CF with manual flush of %s "
+ "to avoid holding old logs",
+ cfd->GetName().c_str());
+ s = SwitchMemtable(cfd_stats, &context);
+ flush_memtable_id = cfd_stats->imm()->GetLatestMemTableID();
+ flush_req.emplace_back(cfd_stats, flush_memtable_id);
+ }
+ }
+ }
+ }
+
+ if (s.ok() && !flush_req.empty()) {
+ for (auto& elem : flush_req) {
+ ColumnFamilyData* loop_cfd = elem.first;
+ loop_cfd->imm()->FlushRequested();
+ }
+ // If the caller wants to wait for this flush to complete, it indicates
+ // that the caller expects the ColumnFamilyData not to be free'ed by
+ // other threads which may drop the column family concurrently.
+ // Therefore, we increase the cfd's ref count.
+ if (flush_options.wait) {
+ for (auto& elem : flush_req) {
+ ColumnFamilyData* loop_cfd = elem.first;
+ loop_cfd->Ref();
+ }
+ }
+ SchedulePendingFlush(flush_req, flush_reason);
+ MaybeScheduleFlushOrCompaction();
+ }
+
+ if (!writes_stopped) {
+ write_thread_.ExitUnbatched(&w);
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::FlushMemTable:AfterScheduleFlush");
+ TEST_SYNC_POINT("DBImpl::FlushMemTable:BeforeWaitForBgFlush");
+ if (s.ok() && flush_options.wait) {
+ autovector<ColumnFamilyData*> cfds;
+ autovector<const uint64_t*> flush_memtable_ids;
+ for (auto& iter : flush_req) {
+ cfds.push_back(iter.first);
+ flush_memtable_ids.push_back(&(iter.second));
+ }
+ s = WaitForFlushMemTables(cfds, flush_memtable_ids,
+ (flush_reason == FlushReason::kErrorRecovery));
+ InstrumentedMutexLock lock_guard(&mutex_);
+ for (auto* tmp_cfd : cfds) {
+ tmp_cfd->UnrefAndTryDelete();
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::FlushMemTable:FlushMemTableFinished");
+ return s;
+}
+
+// Flush all elements in 'column_family_datas'
+// and atomically record the result to the MANIFEST.
+Status DBImpl::AtomicFlushMemTables(
+ const autovector<ColumnFamilyData*>& column_family_datas,
+ const FlushOptions& flush_options, FlushReason flush_reason,
+ bool writes_stopped) {
+ Status s;
+ if (!flush_options.allow_write_stall) {
+ int num_cfs_to_flush = 0;
+ for (auto cfd : column_family_datas) {
+ bool flush_needed = true;
+ s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed);
+ if (!s.ok()) {
+ return s;
+ } else if (flush_needed) {
+ ++num_cfs_to_flush;
+ }
+ }
+ if (0 == num_cfs_to_flush) {
+ return s;
+ }
+ }
+ FlushRequest flush_req;
+ autovector<ColumnFamilyData*> cfds;
+ {
+ WriteContext context;
+ InstrumentedMutexLock guard_lock(&mutex_);
+
+ WriteThread::Writer w;
+ WriteThread::Writer nonmem_w;
+ if (!writes_stopped) {
+ write_thread_.EnterUnbatched(&w, &mutex_);
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+ }
+ WaitForPendingWrites();
+
+ for (auto cfd : column_family_datas) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
+ !cached_recoverable_state_empty_.load()) {
+ cfds.emplace_back(cfd);
+ }
+ }
+ for (auto cfd : cfds) {
+ if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) {
+ continue;
+ }
+ cfd->Ref();
+ s = SwitchMemtable(cfd, &context);
+ cfd->UnrefAndTryDelete();
+ if (!s.ok()) {
+ break;
+ }
+ }
+ if (s.ok()) {
+ AssignAtomicFlushSeq(cfds);
+ for (auto cfd : cfds) {
+ cfd->imm()->FlushRequested();
+ }
+ // If the caller wants to wait for this flush to complete, it indicates
+ // that the caller expects the ColumnFamilyData not to be free'ed by
+ // other threads which may drop the column family concurrently.
+ // Therefore, we increase the cfd's ref count.
+ if (flush_options.wait) {
+ for (auto cfd : cfds) {
+ cfd->Ref();
+ }
+ }
+ GenerateFlushRequest(cfds, &flush_req);
+ SchedulePendingFlush(flush_req, flush_reason);
+ MaybeScheduleFlushOrCompaction();
+ }
+
+ if (!writes_stopped) {
+ write_thread_.ExitUnbatched(&w);
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:AfterScheduleFlush");
+ TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:BeforeWaitForBgFlush");
+ if (s.ok() && flush_options.wait) {
+ autovector<const uint64_t*> flush_memtable_ids;
+ for (auto& iter : flush_req) {
+ flush_memtable_ids.push_back(&(iter.second));
+ }
+ s = WaitForFlushMemTables(cfds, flush_memtable_ids,
+ (flush_reason == FlushReason::kErrorRecovery));
+ InstrumentedMutexLock lock_guard(&mutex_);
+ for (auto* cfd : cfds) {
+ cfd->UnrefAndTryDelete();
+ }
+ }
+ return s;
+}
+
+// Calling FlushMemTable(), whether from DB::Flush() or from Backup Engine, can
+// cause write stall, for example if one memtable is being flushed already.
+// This method tries to avoid write stall (similar to CompactRange() behavior)
+// it emulates how the SuperVersion / LSM would change if flush happens, checks
+// it against various constrains and delays flush if it'd cause write stall.
+// Called should check status and flush_needed to see if flush already happened.
+Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
+ bool* flush_needed) {
+ {
+ *flush_needed = true;
+ InstrumentedMutexLock l(&mutex_);
+ uint64_t orig_active_memtable_id = cfd->mem()->GetID();
+ WriteStallCondition write_stall_condition = WriteStallCondition::kNormal;
+ do {
+ if (write_stall_condition != WriteStallCondition::kNormal) {
+ // Same error handling as user writes: Don't wait if there's a
+ // background error, even if it's a soft error. We might wait here
+ // indefinitely as the pending flushes/compactions may never finish
+ // successfully, resulting in the stall condition lasting indefinitely
+ if (error_handler_.IsBGWorkStopped()) {
+ return error_handler_.GetBGError();
+ }
+
+ TEST_SYNC_POINT("DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait");
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] WaitUntilFlushWouldNotStallWrites"
+ " waiting on stall conditions to clear",
+ cfd->GetName().c_str());
+ bg_cv_.Wait();
+ }
+ if (cfd->IsDropped()) {
+ return Status::ColumnFamilyDropped();
+ }
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return Status::ShutdownInProgress();
+ }
+
+ uint64_t earliest_memtable_id =
+ std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID());
+ if (earliest_memtable_id > orig_active_memtable_id) {
+ // We waited so long that the memtable we were originally waiting on was
+ // flushed.
+ *flush_needed = false;
+ return Status::OK();
+ }
+
+ const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
+ const auto* vstorage = cfd->current()->storage_info();
+
+ // Skip stalling check if we're below auto-flush and auto-compaction
+ // triggers. If it stalled in these conditions, that'd mean the stall
+ // triggers are so low that stalling is needed for any background work. In
+ // that case we shouldn't wait since background work won't be scheduled.
+ if (cfd->imm()->NumNotFlushed() <
+ cfd->ioptions()->min_write_buffer_number_to_merge &&
+ vstorage->l0_delay_trigger_count() <
+ mutable_cf_options.level0_file_num_compaction_trigger) {
+ break;
+ }
+
+ // check whether one extra immutable memtable or an extra L0 file would
+ // cause write stalling mode to be entered. It could still enter stall
+ // mode due to pending compaction bytes, but that's less common
+ write_stall_condition =
+ ColumnFamilyData::GetWriteStallConditionAndCause(
+ cfd->imm()->NumNotFlushed() + 1,
+ vstorage->l0_delay_trigger_count() + 1,
+ vstorage->estimated_compaction_needed_bytes(), mutable_cf_options)
+ .first;
+ } while (write_stall_condition != WriteStallCondition::kNormal);
+ }
+ return Status::OK();
+}
+
+// Wait for memtables to be flushed for multiple column families.
+// let N = cfds.size()
+// for i in [0, N),
+// 1) if flush_memtable_ids[i] is not null, then the memtables with lower IDs
+// have to be flushed for THIS column family;
+// 2) if flush_memtable_ids[i] is null, then all memtables in THIS column
+// family have to be flushed.
+// Finish waiting when ALL column families finish flushing memtables.
+// resuming_from_bg_err indicates whether the caller is trying to resume from
+// background error or in normal processing.
+Status DBImpl::WaitForFlushMemTables(
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const uint64_t*>& flush_memtable_ids,
+ bool resuming_from_bg_err) {
+ int num = static_cast<int>(cfds.size());
+ // Wait until the compaction completes
+ InstrumentedMutexLock l(&mutex_);
+ // If the caller is trying to resume from bg error, then
+ // error_handler_.IsDBStopped() is true.
+ while (resuming_from_bg_err || !error_handler_.IsDBStopped()) {
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return Status::ShutdownInProgress();
+ }
+ // If an error has occurred during resumption, then no need to wait.
+ if (!error_handler_.GetRecoveryError().ok()) {
+ break;
+ }
+ // Number of column families that have been dropped.
+ int num_dropped = 0;
+ // Number of column families that have finished flush.
+ int num_finished = 0;
+ for (int i = 0; i < num; ++i) {
+ if (cfds[i]->IsDropped()) {
+ ++num_dropped;
+ } else if (cfds[i]->imm()->NumNotFlushed() == 0 ||
+ (flush_memtable_ids[i] != nullptr &&
+ cfds[i]->imm()->GetEarliestMemTableID() >
+ *flush_memtable_ids[i])) {
+ ++num_finished;
+ }
+ }
+ if (1 == num_dropped && 1 == num) {
+ return Status::InvalidArgument("Cannot flush a dropped CF");
+ }
+ // Column families involved in this flush request have either been dropped
+ // or finished flush. Then it's time to finish waiting.
+ if (num_dropped + num_finished == num) {
+ break;
+ }
+ bg_cv_.Wait();
+ }
+ Status s;
+ // If not resuming from bg error, and an error has caused the DB to stop,
+ // then report the bg error to caller.
+ if (!resuming_from_bg_err && error_handler_.IsDBStopped()) {
+ s = error_handler_.GetBGError();
+ }
+ return s;
+}
+
+Status DBImpl::EnableAutoCompaction(
+ const std::vector<ColumnFamilyHandle*>& column_family_handles) {
+ Status s;
+ for (auto cf_ptr : column_family_handles) {
+ Status status =
+ this->SetOptions(cf_ptr, {{"disable_auto_compactions", "false"}});
+ if (!status.ok()) {
+ s = status;
+ }
+ }
+
+ return s;
+}
+
+void DBImpl::DisableManualCompaction() {
+ manual_compaction_paused_.store(true, std::memory_order_release);
+}
+
+void DBImpl::EnableManualCompaction() {
+ manual_compaction_paused_.store(false, std::memory_order_release);
+}
+
+void DBImpl::MaybeScheduleFlushOrCompaction() {
+ mutex_.AssertHeld();
+ if (!opened_successfully_) {
+ // Compaction may introduce data race to DB open
+ return;
+ }
+ if (bg_work_paused_ > 0) {
+ // we paused the background work
+ return;
+ } else if (error_handler_.IsBGWorkStopped() &&
+ !error_handler_.IsRecoveryInProgress()) {
+ // There has been a hard error and this call is not part of the recovery
+ // sequence. Bail out here so we don't get into an endless loop of
+ // scheduling BG work which will again call this function
+ return;
+ } else if (shutting_down_.load(std::memory_order_acquire)) {
+ // DB is being deleted; no more background compactions
+ return;
+ }
+ auto bg_job_limits = GetBGJobLimits();
+ bool is_flush_pool_empty =
+ env_->GetBackgroundThreads(Env::Priority::HIGH) == 0;
+ while (!is_flush_pool_empty && unscheduled_flushes_ > 0 &&
+ bg_flush_scheduled_ < bg_job_limits.max_flushes) {
+ bg_flush_scheduled_++;
+ FlushThreadArg* fta = new FlushThreadArg;
+ fta->db_ = this;
+ fta->thread_pri_ = Env::Priority::HIGH;
+ env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::HIGH, this,
+ &DBImpl::UnscheduleFlushCallback);
+ --unscheduled_flushes_;
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::MaybeScheduleFlushOrCompaction:AfterSchedule:0",
+ &unscheduled_flushes_);
+ }
+
+ // special case -- if high-pri (flush) thread pool is empty, then schedule
+ // flushes in low-pri (compaction) thread pool.
+ if (is_flush_pool_empty) {
+ while (unscheduled_flushes_ > 0 &&
+ bg_flush_scheduled_ + bg_compaction_scheduled_ <
+ bg_job_limits.max_flushes) {
+ bg_flush_scheduled_++;
+ FlushThreadArg* fta = new FlushThreadArg;
+ fta->db_ = this;
+ fta->thread_pri_ = Env::Priority::LOW;
+ env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::LOW, this,
+ &DBImpl::UnscheduleFlushCallback);
+ --unscheduled_flushes_;
+ }
+ }
+
+ if (bg_compaction_paused_ > 0) {
+ // we paused the background compaction
+ return;
+ } else if (error_handler_.IsBGWorkStopped()) {
+ // Compaction is not part of the recovery sequence from a hard error. We
+ // might get here because recovery might do a flush and install a new
+ // super version, which will try to schedule pending compactions. Bail
+ // out here and let the higher level recovery handle compactions
+ return;
+ }
+
+ if (HasExclusiveManualCompaction()) {
+ // only manual compactions are allowed to run. don't schedule automatic
+ // compactions
+ TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Conflict");
+ return;
+ }
+
+ while (bg_compaction_scheduled_ < bg_job_limits.max_compactions &&
+ unscheduled_compactions_ > 0) {
+ CompactionArg* ca = new CompactionArg;
+ ca->db = this;
+ ca->prepicked_compaction = nullptr;
+ bg_compaction_scheduled_++;
+ unscheduled_compactions_--;
+ env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
+ &DBImpl::UnscheduleCompactionCallback);
+ }
+}
+
+DBImpl::BGJobLimits DBImpl::GetBGJobLimits() const {
+ mutex_.AssertHeld();
+ return GetBGJobLimits(immutable_db_options_.max_background_flushes,
+ mutable_db_options_.max_background_compactions,
+ mutable_db_options_.max_background_jobs,
+ write_controller_.NeedSpeedupCompaction());
+}
+
+DBImpl::BGJobLimits DBImpl::GetBGJobLimits(int max_background_flushes,
+ int max_background_compactions,
+ int max_background_jobs,
+ bool parallelize_compactions) {
+ BGJobLimits res;
+ if (max_background_flushes == -1 && max_background_compactions == -1) {
+ // for our first stab implementing max_background_jobs, simply allocate a
+ // quarter of the threads to flushes.
+ res.max_flushes = std::max(1, max_background_jobs / 4);
+ res.max_compactions = std::max(1, max_background_jobs - res.max_flushes);
+ } else {
+ // compatibility code in case users haven't migrated to max_background_jobs,
+ // which automatically computes flush/compaction limits
+ res.max_flushes = std::max(1, max_background_flushes);
+ res.max_compactions = std::max(1, max_background_compactions);
+ }
+ if (!parallelize_compactions) {
+ // throttle background compactions until we deem necessary
+ res.max_compactions = 1;
+ }
+ return res;
+}
+
+void DBImpl::AddToCompactionQueue(ColumnFamilyData* cfd) {
+ assert(!cfd->queued_for_compaction());
+ cfd->Ref();
+ compaction_queue_.push_back(cfd);
+ cfd->set_queued_for_compaction(true);
+}
+
+ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
+ assert(!compaction_queue_.empty());
+ auto cfd = *compaction_queue_.begin();
+ compaction_queue_.pop_front();
+ assert(cfd->queued_for_compaction());
+ cfd->set_queued_for_compaction(false);
+ return cfd;
+}
+
+DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() {
+ assert(!flush_queue_.empty());
+ FlushRequest flush_req = flush_queue_.front();
+ flush_queue_.pop_front();
+ // TODO: need to unset flush reason?
+ return flush_req;
+}
+
+ColumnFamilyData* DBImpl::PickCompactionFromQueue(
+ std::unique_ptr<TaskLimiterToken>* token, LogBuffer* log_buffer) {
+ assert(!compaction_queue_.empty());
+ assert(*token == nullptr);
+ autovector<ColumnFamilyData*> throttled_candidates;
+ ColumnFamilyData* cfd = nullptr;
+ while (!compaction_queue_.empty()) {
+ auto first_cfd = *compaction_queue_.begin();
+ compaction_queue_.pop_front();
+ assert(first_cfd->queued_for_compaction());
+ if (!RequestCompactionToken(first_cfd, false, token, log_buffer)) {
+ throttled_candidates.push_back(first_cfd);
+ continue;
+ }
+ cfd = first_cfd;
+ cfd->set_queued_for_compaction(false);
+ break;
+ }
+ // Add throttled compaction candidates back to queue in the original order.
+ for (auto iter = throttled_candidates.rbegin();
+ iter != throttled_candidates.rend(); ++iter) {
+ compaction_queue_.push_front(*iter);
+ }
+ return cfd;
+}
+
+void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req,
+ FlushReason flush_reason) {
+ if (flush_req.empty()) {
+ return;
+ }
+ for (auto& iter : flush_req) {
+ ColumnFamilyData* cfd = iter.first;
+ cfd->Ref();
+ cfd->SetFlushReason(flush_reason);
+ }
+ ++unscheduled_flushes_;
+ flush_queue_.push_back(flush_req);
+}
+
+void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
+ if (!cfd->queued_for_compaction() && cfd->NeedsCompaction()) {
+ AddToCompactionQueue(cfd);
+ ++unscheduled_compactions_;
+ }
+}
+
+void DBImpl::SchedulePendingPurge(std::string fname, std::string dir_to_sync,
+ FileType type, uint64_t number, int job_id) {
+ mutex_.AssertHeld();
+ PurgeFileInfo file_info(fname, dir_to_sync, type, number, job_id);
+ purge_files_.insert({{number, std::move(file_info)}});
+}
+
+void DBImpl::BGWorkFlush(void* arg) {
+ FlushThreadArg fta = *(reinterpret_cast<FlushThreadArg*>(arg));
+ delete reinterpret_cast<FlushThreadArg*>(arg);
+
+ IOSTATS_SET_THREAD_POOL_ID(fta.thread_pri_);
+ TEST_SYNC_POINT("DBImpl::BGWorkFlush");
+ static_cast_with_check<DBImpl, DB>(fta.db_)->BackgroundCallFlush(
+ fta.thread_pri_);
+ TEST_SYNC_POINT("DBImpl::BGWorkFlush:done");
+}
+
+void DBImpl::BGWorkCompaction(void* arg) {
+ CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
+ delete reinterpret_cast<CompactionArg*>(arg);
+ IOSTATS_SET_THREAD_POOL_ID(Env::Priority::LOW);
+ TEST_SYNC_POINT("DBImpl::BGWorkCompaction");
+ auto prepicked_compaction =
+ static_cast<PrepickedCompaction*>(ca.prepicked_compaction);
+ static_cast_with_check<DBImpl, DB>(ca.db)->BackgroundCallCompaction(
+ prepicked_compaction, Env::Priority::LOW);
+ delete prepicked_compaction;
+}
+
+void DBImpl::BGWorkBottomCompaction(void* arg) {
+ CompactionArg ca = *(static_cast<CompactionArg*>(arg));
+ delete static_cast<CompactionArg*>(arg);
+ IOSTATS_SET_THREAD_POOL_ID(Env::Priority::BOTTOM);
+ TEST_SYNC_POINT("DBImpl::BGWorkBottomCompaction");
+ auto* prepicked_compaction = ca.prepicked_compaction;
+ assert(prepicked_compaction && prepicked_compaction->compaction &&
+ !prepicked_compaction->manual_compaction_state);
+ ca.db->BackgroundCallCompaction(prepicked_compaction, Env::Priority::BOTTOM);
+ delete prepicked_compaction;
+}
+
+void DBImpl::BGWorkPurge(void* db) {
+ IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
+ TEST_SYNC_POINT("DBImpl::BGWorkPurge:start");
+ reinterpret_cast<DBImpl*>(db)->BackgroundCallPurge();
+ TEST_SYNC_POINT("DBImpl::BGWorkPurge:end");
+}
+
+void DBImpl::UnscheduleCompactionCallback(void* arg) {
+ CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
+ delete reinterpret_cast<CompactionArg*>(arg);
+ if (ca.prepicked_compaction != nullptr) {
+ if (ca.prepicked_compaction->compaction != nullptr) {
+ delete ca.prepicked_compaction->compaction;
+ }
+ delete ca.prepicked_compaction;
+ }
+ TEST_SYNC_POINT("DBImpl::UnscheduleCompactionCallback");
+}
+
+void DBImpl::UnscheduleFlushCallback(void* arg) {
+ delete reinterpret_cast<FlushThreadArg*>(arg);
+ TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
+}
+
+Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
+ LogBuffer* log_buffer, FlushReason* reason,
+ Env::Priority thread_pri) {
+ mutex_.AssertHeld();
+
+ Status status;
+ *reason = FlushReason::kOthers;
+ // If BG work is stopped due to an error, but a recovery is in progress,
+ // that means this flush is part of the recovery. So allow it to go through
+ if (!error_handler_.IsBGWorkStopped()) {
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ status = Status::ShutdownInProgress();
+ }
+ } else if (!error_handler_.IsRecoveryInProgress()) {
+ status = error_handler_.GetBGError();
+ }
+
+ if (!status.ok()) {
+ return status;
+ }
+
+ autovector<BGFlushArg> bg_flush_args;
+ std::vector<SuperVersionContext>& superversion_contexts =
+ job_context->superversion_contexts;
+ autovector<ColumnFamilyData*> column_families_not_to_flush;
+ while (!flush_queue_.empty()) {
+ // This cfd is already referenced
+ const FlushRequest& flush_req = PopFirstFromFlushQueue();
+ superversion_contexts.clear();
+ superversion_contexts.reserve(flush_req.size());
+
+ for (const auto& iter : flush_req) {
+ ColumnFamilyData* cfd = iter.first;
+ if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) {
+ // can't flush this CF, try next one
+ column_families_not_to_flush.push_back(cfd);
+ continue;
+ }
+ superversion_contexts.emplace_back(SuperVersionContext(true));
+ bg_flush_args.emplace_back(cfd, iter.second,
+ &(superversion_contexts.back()));
+ }
+ if (!bg_flush_args.empty()) {
+ break;
+ }
+ }
+
+ if (!bg_flush_args.empty()) {
+ auto bg_job_limits = GetBGJobLimits();
+ for (const auto& arg : bg_flush_args) {
+ ColumnFamilyData* cfd = arg.cfd_;
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "Calling FlushMemTableToOutputFile with column "
+ "family [%s], flush slots available %d, compaction slots available "
+ "%d, "
+ "flush slots scheduled %d, compaction slots scheduled %d",
+ cfd->GetName().c_str(), bg_job_limits.max_flushes,
+ bg_job_limits.max_compactions, bg_flush_scheduled_,
+ bg_compaction_scheduled_);
+ }
+ status = FlushMemTablesToOutputFiles(bg_flush_args, made_progress,
+ job_context, log_buffer, thread_pri);
+ TEST_SYNC_POINT("DBImpl::BackgroundFlush:BeforeFlush");
+ // All the CFDs in the FlushReq must have the same flush reason, so just
+ // grab the first one
+ *reason = bg_flush_args[0].cfd_->GetFlushReason();
+ for (auto& arg : bg_flush_args) {
+ ColumnFamilyData* cfd = arg.cfd_;
+ if (cfd->UnrefAndTryDelete()) {
+ arg.cfd_ = nullptr;
+ }
+ }
+ }
+ for (auto cfd : column_families_not_to_flush) {
+ cfd->UnrefAndTryDelete();
+ }
+ return status;
+}
+
+void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
+ bool made_progress = false;
+ JobContext job_context(next_job_id_.fetch_add(1), true);
+
+ TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:start");
+
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
+ immutable_db_options_.info_log.get());
+ {
+ InstrumentedMutexLock l(&mutex_);
+ assert(bg_flush_scheduled_);
+ num_running_flushes_++;
+
+ std::unique_ptr<std::list<uint64_t>::iterator>
+ pending_outputs_inserted_elem(new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+ FlushReason reason;
+
+ Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer,
+ &reason, thread_pri);
+ if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped() &&
+ reason != FlushReason::kErrorRecovery) {
+ // Wait a little bit before retrying background flush in
+ // case this is an environmental problem and we do not want to
+ // chew up resources for failed flushes for the duration of
+ // the problem.
+ uint64_t error_cnt =
+ default_cf_internal_stats_->BumpAndGetBackgroundErrorCount();
+ bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
+ mutex_.Unlock();
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Waiting after background flush error: %s"
+ "Accumulated background error counts: %" PRIu64,
+ s.ToString().c_str(), error_cnt);
+ log_buffer.FlushBufferToLog();
+ LogFlush(immutable_db_options_.info_log);
+ env_->SleepForMicroseconds(1000000);
+ mutex_.Lock();
+ }
+
+ TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FlushFinish:0");
+ ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
+
+ // If flush failed, we want to delete all temporary files that we might have
+ // created. Thus, we force full scan in FindObsoleteFiles()
+ FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
+ !s.IsColumnFamilyDropped());
+ // delete unnecessary files if any, this is done outside the mutex
+ if (job_context.HaveSomethingToClean() ||
+ job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
+ mutex_.Unlock();
+ TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
+ // Have to flush the info logs before bg_flush_scheduled_--
+ // because if bg_flush_scheduled_ becomes 0 and the lock is
+ // released, the deconstructor of DB can kick in and destroy all the
+ // states of DB so info_log might not be available after that point.
+ // It also applies to access other states that DB owns.
+ log_buffer.FlushBufferToLog();
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ mutex_.Lock();
+ }
+ TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp");
+
+ assert(num_running_flushes_ > 0);
+ num_running_flushes_--;
+ bg_flush_scheduled_--;
+ // See if there's more work to be done
+ MaybeScheduleFlushOrCompaction();
+ atomic_flush_install_cv_.SignalAll();
+ bg_cv_.SignalAll();
+ // IMPORTANT: there should be no code after calling SignalAll. This call may
+ // signal the DB destructor that it's OK to proceed with destruction. In
+ // that case, all DB variables will be dealloacated and referencing them
+ // will cause trouble.
+ }
+}
+
+void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
+ Env::Priority bg_thread_pri) {
+ bool made_progress = false;
+ JobContext job_context(next_job_id_.fetch_add(1), true);
+ TEST_SYNC_POINT("BackgroundCallCompaction:0");
+ LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
+ immutable_db_options_.info_log.get());
+ {
+ InstrumentedMutexLock l(&mutex_);
+
+ // This call will unlock/lock the mutex to wait for current running
+ // IngestExternalFile() calls to finish.
+ WaitForIngestFile();
+
+ num_running_compactions_++;
+
+ std::unique_ptr<std::list<uint64_t>::iterator>
+ pending_outputs_inserted_elem(new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+
+ assert((bg_thread_pri == Env::Priority::BOTTOM &&
+ bg_bottom_compaction_scheduled_) ||
+ (bg_thread_pri == Env::Priority::LOW && bg_compaction_scheduled_));
+ Status s = BackgroundCompaction(&made_progress, &job_context, &log_buffer,
+ prepicked_compaction, bg_thread_pri);
+ TEST_SYNC_POINT("BackgroundCallCompaction:1");
+ if (s.IsBusy()) {
+ bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
+ mutex_.Unlock();
+ env_->SleepForMicroseconds(10000); // prevent hot loop
+ mutex_.Lock();
+ } else if (!s.ok() && !s.IsShutdownInProgress() &&
+ !s.IsManualCompactionPaused() && !s.IsColumnFamilyDropped()) {
+ // Wait a little bit before retrying background compaction in
+ // case this is an environmental problem and we do not want to
+ // chew up resources for failed compactions for the duration of
+ // the problem.
+ uint64_t error_cnt =
+ default_cf_internal_stats_->BumpAndGetBackgroundErrorCount();
+ bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
+ mutex_.Unlock();
+ log_buffer.FlushBufferToLog();
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Waiting after background compaction error: %s, "
+ "Accumulated background error counts: %" PRIu64,
+ s.ToString().c_str(), error_cnt);
+ LogFlush(immutable_db_options_.info_log);
+ env_->SleepForMicroseconds(1000000);
+ mutex_.Lock();
+ } else if (s.IsManualCompactionPaused()) {
+ ManualCompactionState* m = prepicked_compaction->manual_compaction_state;
+ assert(m);
+ ROCKS_LOG_BUFFER(&log_buffer, "[%s] [JOB %d] Manual compaction paused",
+ m->cfd->GetName().c_str(), job_context.job_id);
+ }
+
+ ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
+
+ // If compaction failed, we want to delete all temporary files that we might
+ // have created (they might not be all recorded in job_context in case of a
+ // failure). Thus, we force full scan in FindObsoleteFiles()
+ FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
+ !s.IsManualCompactionPaused() &&
+ !s.IsColumnFamilyDropped());
+ TEST_SYNC_POINT("DBImpl::BackgroundCallCompaction:FoundObsoleteFiles");
+
+ // delete unnecessary files if any, this is done outside the mutex
+ if (job_context.HaveSomethingToClean() ||
+ job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
+ mutex_.Unlock();
+ // Have to flush the info logs before bg_compaction_scheduled_--
+ // because if bg_flush_scheduled_ becomes 0 and the lock is
+ // released, the deconstructor of DB can kick in and destroy all the
+ // states of DB so info_log might not be available after that point.
+ // It also applies to access other states that DB owns.
+ log_buffer.FlushBufferToLog();
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ TEST_SYNC_POINT("DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles");
+ }
+ job_context.Clean();
+ mutex_.Lock();
+ }
+
+ assert(num_running_compactions_ > 0);
+ num_running_compactions_--;
+ if (bg_thread_pri == Env::Priority::LOW) {
+ bg_compaction_scheduled_--;
+ } else {
+ assert(bg_thread_pri == Env::Priority::BOTTOM);
+ bg_bottom_compaction_scheduled_--;
+ }
+
+ versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
+
+ // See if there's more work to be done
+ MaybeScheduleFlushOrCompaction();
+ if (made_progress ||
+ (bg_compaction_scheduled_ == 0 &&
+ bg_bottom_compaction_scheduled_ == 0) ||
+ HasPendingManualCompaction() || unscheduled_compactions_ == 0) {
+ // signal if
+ // * made_progress -- need to wakeup DelayWrite
+ // * bg_{bottom,}_compaction_scheduled_ == 0 -- need to wakeup ~DBImpl
+ // * HasPendingManualCompaction -- need to wakeup RunManualCompaction
+ // If none of this is true, there is no need to signal since nobody is
+ // waiting for it
+ bg_cv_.SignalAll();
+ }
+ // IMPORTANT: there should be no code after calling SignalAll. This call may
+ // signal the DB destructor that it's OK to proceed with destruction. In
+ // that case, all DB variables will be dealloacated and referencing them
+ // will cause trouble.
+ }
+}
+
+Status DBImpl::BackgroundCompaction(bool* made_progress,
+ JobContext* job_context,
+ LogBuffer* log_buffer,
+ PrepickedCompaction* prepicked_compaction,
+ Env::Priority thread_pri) {
+ ManualCompactionState* manual_compaction =
+ prepicked_compaction == nullptr
+ ? nullptr
+ : prepicked_compaction->manual_compaction_state;
+ *made_progress = false;
+ mutex_.AssertHeld();
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Start");
+
+ bool is_manual = (manual_compaction != nullptr);
+ std::unique_ptr<Compaction> c;
+ if (prepicked_compaction != nullptr &&
+ prepicked_compaction->compaction != nullptr) {
+ c.reset(prepicked_compaction->compaction);
+ }
+ bool is_prepicked = is_manual || c;
+
+ // (manual_compaction->in_progress == false);
+ bool trivial_move_disallowed =
+ is_manual && manual_compaction->disallow_trivial_move;
+
+ CompactionJobStats compaction_job_stats;
+ Status status;
+ if (!error_handler_.IsBGWorkStopped()) {
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ status = Status::ShutdownInProgress();
+ } else if (is_manual &&
+ manual_compaction_paused_.load(std::memory_order_acquire)) {
+ status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
+ }
+ } else {
+ status = error_handler_.GetBGError();
+ // If we get here, it means a hard error happened after this compaction
+ // was scheduled by MaybeScheduleFlushOrCompaction(), but before it got
+ // a chance to execute. Since we didn't pop a cfd from the compaction
+ // queue, increment unscheduled_compactions_
+ unscheduled_compactions_++;
+ }
+
+ if (!status.ok()) {
+ if (is_manual) {
+ manual_compaction->status = status;
+ manual_compaction->done = true;
+ manual_compaction->in_progress = false;
+ manual_compaction = nullptr;
+ }
+ if (c) {
+ c->ReleaseCompactionFiles(status);
+ c.reset();
+ }
+ return status;
+ }
+
+ if (is_manual) {
+ // another thread cannot pick up the same work
+ manual_compaction->in_progress = true;
+ }
+
+ std::unique_ptr<TaskLimiterToken> task_token;
+
+ // InternalKey manual_end_storage;
+ // InternalKey* manual_end = &manual_end_storage;
+ bool sfm_reserved_compact_space = false;
+ if (is_manual) {
+ ManualCompactionState* m = manual_compaction;
+ assert(m->in_progress);
+ if (!c) {
+ m->done = true;
+ m->manual_end = nullptr;
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] Manual compaction from level-%d from %s .. "
+ "%s; nothing to do\n",
+ m->cfd->GetName().c_str(), m->input_level,
+ (m->begin ? m->begin->DebugString().c_str() : "(begin)"),
+ (m->end ? m->end->DebugString().c_str() : "(end)"));
+ } else {
+ // First check if we have enough room to do the compaction
+ bool enough_room = EnoughRoomForCompaction(
+ m->cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer);
+
+ if (!enough_room) {
+ // Then don't do the compaction
+ c->ReleaseCompactionFiles(status);
+ c.reset();
+ // m's vars will get set properly at the end of this function,
+ // as long as status == CompactionTooLarge
+ status = Status::CompactionTooLarge();
+ } else {
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] Manual compaction from level-%d to level-%d from %s .. "
+ "%s; will stop at %s\n",
+ m->cfd->GetName().c_str(), m->input_level, c->output_level(),
+ (m->begin ? m->begin->DebugString().c_str() : "(begin)"),
+ (m->end ? m->end->DebugString().c_str() : "(end)"),
+ ((m->done || m->manual_end == nullptr)
+ ? "(end)"
+ : m->manual_end->DebugString().c_str()));
+ }
+ }
+ } else if (!is_prepicked && !compaction_queue_.empty()) {
+ if (HasExclusiveManualCompaction()) {
+ // Can't compact right now, but try again later
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict");
+
+ // Stay in the compaction queue.
+ unscheduled_compactions_++;
+
+ return Status::OK();
+ }
+
+ auto cfd = PickCompactionFromQueue(&task_token, log_buffer);
+ if (cfd == nullptr) {
+ // Can't find any executable task from the compaction queue.
+ // All tasks have been throttled by compaction thread limiter.
+ ++unscheduled_compactions_;
+ return Status::Busy();
+ }
+
+ // We unreference here because the following code will take a Ref() on
+ // this cfd if it is going to use it (Compaction class holds a
+ // reference).
+ // This will all happen under a mutex so we don't have to be afraid of
+ // somebody else deleting it.
+ if (cfd->UnrefAndTryDelete()) {
+ // This was the last reference of the column family, so no need to
+ // compact.
+ return Status::OK();
+ }
+
+ // Pick up latest mutable CF Options and use it throughout the
+ // compaction job
+ // Compaction makes a copy of the latest MutableCFOptions. It should be used
+ // throughout the compaction procedure to make sure consistency. It will
+ // eventually be installed into SuperVersion
+ auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
+ if (!mutable_cf_options->disable_auto_compactions && !cfd->IsDropped()) {
+ // NOTE: try to avoid unnecessary copy of MutableCFOptions if
+ // compaction is not necessary. Need to make sure mutex is held
+ // until we make a copy in the following code
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction");
+ c.reset(cfd->PickCompaction(*mutable_cf_options, log_buffer));
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
+
+ if (c != nullptr) {
+ bool enough_room = EnoughRoomForCompaction(
+ cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer);
+
+ if (!enough_room) {
+ // Then don't do the compaction
+ c->ReleaseCompactionFiles(status);
+ c->column_family_data()
+ ->current()
+ ->storage_info()
+ ->ComputeCompactionScore(*(c->immutable_cf_options()),
+ *(c->mutable_cf_options()));
+ AddToCompactionQueue(cfd);
+ ++unscheduled_compactions_;
+
+ c.reset();
+ // Don't need to sleep here, because BackgroundCallCompaction
+ // will sleep if !s.ok()
+ status = Status::CompactionTooLarge();
+ } else {
+ // update statistics
+ RecordInHistogram(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
+ c->inputs(0)->size());
+ // There are three things that can change compaction score:
+ // 1) When flush or compaction finish. This case is covered by
+ // InstallSuperVersionAndScheduleWork
+ // 2) When MutableCFOptions changes. This case is also covered by
+ // InstallSuperVersionAndScheduleWork, because this is when the new
+ // options take effect.
+ // 3) When we Pick a new compaction, we "remove" those files being
+ // compacted from the calculation, which then influences compaction
+ // score. Here we check if we need the new compaction even without the
+ // files that are currently being compacted. If we need another
+ // compaction, we might be able to execute it in parallel, so we add
+ // it to the queue and schedule a new thread.
+ if (cfd->NeedsCompaction()) {
+ // Yes, we need more compactions!
+ AddToCompactionQueue(cfd);
+ ++unscheduled_compactions_;
+ MaybeScheduleFlushOrCompaction();
+ }
+ }
+ }
+ }
+ }
+
+ if (!c) {
+ // Nothing to do
+ ROCKS_LOG_BUFFER(log_buffer, "Compaction nothing to do");
+ } else if (c->deletion_compaction()) {
+ // TODO(icanadi) Do we want to honor snapshots here? i.e. not delete old
+ // file if there is alive snapshot pointing to it
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
+ c->column_family_data());
+ assert(c->num_input_files(1) == 0);
+ assert(c->level() == 0);
+ assert(c->column_family_data()->ioptions()->compaction_style ==
+ kCompactionStyleFIFO);
+
+ compaction_job_stats.num_input_files = c->num_input_files(0);
+
+ NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
+ compaction_job_stats, job_context->job_id);
+
+ for (const auto& f : *c->inputs(0)) {
+ c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
+ }
+ status = versions_->LogAndApply(c->column_family_data(),
+ *c->mutable_cf_options(), c->edit(),
+ &mutex_, directories_.GetDbDir());
+ InstallSuperVersionAndScheduleWork(c->column_family_data(),
+ &job_context->superversion_contexts[0],
+ *c->mutable_cf_options());
+ ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n",
+ c->column_family_data()->GetName().c_str(),
+ c->num_input_files(0));
+ *made_progress = true;
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
+ c->column_family_data());
+ } else if (!trivial_move_disallowed && c->IsTrivialMove()) {
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction:TrivialMove");
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
+ c->column_family_data());
+ // Instrument for event update
+ // TODO(yhchiang): add op details for showing trivial-move.
+ ThreadStatusUtil::SetColumnFamily(
+ c->column_family_data(), c->column_family_data()->ioptions()->env,
+ immutable_db_options_.enable_thread_tracking);
+ ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
+
+ compaction_job_stats.num_input_files = c->num_input_files(0);
+
+ NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
+ compaction_job_stats, job_context->job_id);
+
+ // Move files to next level
+ int32_t moved_files = 0;
+ int64_t moved_bytes = 0;
+ for (unsigned int l = 0; l < c->num_input_levels(); l++) {
+ if (c->level(l) == c->output_level()) {
+ continue;
+ }
+ for (size_t i = 0; i < c->num_input_files(l); i++) {
+ FileMetaData* f = c->input(l, i);
+ c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
+ c->edit()->AddFile(c->output_level(), f->fd.GetNumber(),
+ f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest,
+ f->largest, f->fd.smallest_seqno,
+ f->fd.largest_seqno, f->marked_for_compaction,
+ f->oldest_blob_file_number, f->oldest_ancester_time,
+ f->file_creation_time, f->file_checksum,
+ f->file_checksum_func_name);
+
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
+ c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
+ c->output_level(), f->fd.GetFileSize());
+ ++moved_files;
+ moved_bytes += f->fd.GetFileSize();
+ }
+ }
+
+ status = versions_->LogAndApply(c->column_family_data(),
+ *c->mutable_cf_options(), c->edit(),
+ &mutex_, directories_.GetDbDir());
+ // Use latest MutableCFOptions
+ InstallSuperVersionAndScheduleWork(c->column_family_data(),
+ &job_context->superversion_contexts[0],
+ *c->mutable_cf_options());
+
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ c->column_family_data()->internal_stats()->IncBytesMoved(c->output_level(),
+ moved_bytes);
+ {
+ event_logger_.LogToBuffer(log_buffer)
+ << "job" << job_context->job_id << "event"
+ << "trivial_move"
+ << "destination_level" << c->output_level() << "files" << moved_files
+ << "total_files_size" << moved_bytes;
+ }
+ ROCKS_LOG_BUFFER(
+ log_buffer,
+ "[%s] Moved #%d files to level-%d %" PRIu64 " bytes %s: %s\n",
+ c->column_family_data()->GetName().c_str(), moved_files,
+ c->output_level(), moved_bytes, status.ToString().c_str(),
+ c->column_family_data()->current()->storage_info()->LevelSummary(&tmp));
+ *made_progress = true;
+
+ // Clear Instrument
+ ThreadStatusUtil::ResetThreadStatus();
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
+ c->column_family_data());
+ } else if (!is_prepicked && c->output_level() > 0 &&
+ c->output_level() ==
+ c->column_family_data()
+ ->current()
+ ->storage_info()
+ ->MaxOutputLevel(
+ immutable_db_options_.allow_ingest_behind) &&
+ env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
+ // Forward compactions involving last level to the bottom pool if it exists,
+ // such that compactions unlikely to contribute to write stalls can be
+ // delayed or deprioritized.
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction:ForwardToBottomPriPool");
+ CompactionArg* ca = new CompactionArg;
+ ca->db = this;
+ ca->prepicked_compaction = new PrepickedCompaction;
+ ca->prepicked_compaction->compaction = c.release();
+ ca->prepicked_compaction->manual_compaction_state = nullptr;
+ // Transfer requested token, so it doesn't need to do it again.
+ ca->prepicked_compaction->task_token = std::move(task_token);
+ ++bg_bottom_compaction_scheduled_;
+ env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
+ this, &DBImpl::UnscheduleCompactionCallback);
+ } else {
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
+ c->column_family_data());
+ int output_level __attribute__((__unused__));
+ output_level = c->output_level();
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
+ &output_level);
+ std::vector<SequenceNumber> snapshot_seqs;
+ SequenceNumber earliest_write_conflict_snapshot;
+ SnapshotChecker* snapshot_checker;
+ GetSnapshotContext(job_context, &snapshot_seqs,
+ &earliest_write_conflict_snapshot, &snapshot_checker);
+ assert(is_snapshot_supported_ || snapshots_.empty());
+ CompactionJob compaction_job(
+ job_context->job_id, c.get(), immutable_db_options_,
+ file_options_for_compaction_, versions_.get(), &shutting_down_,
+ preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
+ GetDataDir(c->column_family_data(), c->output_path_id()), stats_,
+ &mutex_, &error_handler_, snapshot_seqs,
+ earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
+ &event_logger_, c->mutable_cf_options()->paranoid_file_checks,
+ c->mutable_cf_options()->report_bg_io_stats, dbname_,
+ &compaction_job_stats, thread_pri,
+ is_manual ? &manual_compaction_paused_ : nullptr);
+ compaction_job.Prepare();
+
+ NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
+ compaction_job_stats, job_context->job_id);
+
+ mutex_.Unlock();
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", nullptr);
+ compaction_job.Run();
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
+ mutex_.Lock();
+
+ status = compaction_job.Install(*c->mutable_cf_options());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(c->column_family_data(),
+ &job_context->superversion_contexts[0],
+ *c->mutable_cf_options());
+ }
+ *made_progress = true;
+ TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
+ c->column_family_data());
+ }
+ if (c != nullptr) {
+ c->ReleaseCompactionFiles(status);
+ *made_progress = true;
+
+#ifndef ROCKSDB_LITE
+ // Need to make sure SstFileManager does its bookkeeping
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ immutable_db_options_.sst_file_manager.get());
+ if (sfm && sfm_reserved_compact_space) {
+ sfm->OnCompactionCompletion(c.get());
+ }
+#endif // ROCKSDB_LITE
+
+ NotifyOnCompactionCompleted(c->column_family_data(), c.get(), status,
+ compaction_job_stats, job_context->job_id);
+ }
+
+ if (status.ok() || status.IsCompactionTooLarge() ||
+ status.IsManualCompactionPaused()) {
+ // Done
+ } else if (status.IsColumnFamilyDropped() || status.IsShutdownInProgress()) {
+ // Ignore compaction errors found during shutting down
+ } else {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
+ status.ToString().c_str());
+ error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
+ if (c != nullptr && !is_manual && !error_handler_.IsBGWorkStopped()) {
+ // Put this cfd back in the compaction queue so we can retry after some
+ // time
+ auto cfd = c->column_family_data();
+ assert(cfd != nullptr);
+ // Since this compaction failed, we need to recompute the score so it
+ // takes the original input files into account
+ c->column_family_data()
+ ->current()
+ ->storage_info()
+ ->ComputeCompactionScore(*(c->immutable_cf_options()),
+ *(c->mutable_cf_options()));
+ if (!cfd->queued_for_compaction()) {
+ AddToCompactionQueue(cfd);
+ ++unscheduled_compactions_;
+ }
+ }
+ }
+ // this will unref its input_version and column_family_data
+ c.reset();
+
+ if (is_manual) {
+ ManualCompactionState* m = manual_compaction;
+ if (!status.ok()) {
+ m->status = status;
+ m->done = true;
+ }
+ // For universal compaction:
+ // Because universal compaction always happens at level 0, so one
+ // compaction will pick up all overlapped files. No files will be
+ // filtered out due to size limit and left for a successive compaction.
+ // So we can safely conclude the current compaction.
+ //
+ // Also note that, if we don't stop here, then the current compaction
+ // writes a new file back to level 0, which will be used in successive
+ // compaction. Hence the manual compaction will never finish.
+ //
+ // Stop the compaction if manual_end points to nullptr -- this means
+ // that we compacted the whole range. manual_end should always point
+ // to nullptr in case of universal compaction
+ if (m->manual_end == nullptr) {
+ m->done = true;
+ }
+ if (!m->done) {
+ // We only compacted part of the requested range. Update *m
+ // to the range that is left to be compacted.
+ // Universal and FIFO compactions should always compact the whole range
+ assert(m->cfd->ioptions()->compaction_style !=
+ kCompactionStyleUniversal ||
+ m->cfd->ioptions()->num_levels > 1);
+ assert(m->cfd->ioptions()->compaction_style != kCompactionStyleFIFO);
+ m->tmp_storage = *m->manual_end;
+ m->begin = &m->tmp_storage;
+ m->incomplete = true;
+ }
+ m->in_progress = false; // not being processed anymore
+ }
+ TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Finish");
+ return status;
+}
+
+bool DBImpl::HasPendingManualCompaction() {
+ return (!manual_compaction_dequeue_.empty());
+}
+
+void DBImpl::AddManualCompaction(DBImpl::ManualCompactionState* m) {
+ manual_compaction_dequeue_.push_back(m);
+}
+
+void DBImpl::RemoveManualCompaction(DBImpl::ManualCompactionState* m) {
+ // Remove from queue
+ std::deque<ManualCompactionState*>::iterator it =
+ manual_compaction_dequeue_.begin();
+ while (it != manual_compaction_dequeue_.end()) {
+ if (m == (*it)) {
+ it = manual_compaction_dequeue_.erase(it);
+ return;
+ }
+ ++it;
+ }
+ assert(false);
+ return;
+}
+
+bool DBImpl::ShouldntRunManualCompaction(ManualCompactionState* m) {
+ if (num_running_ingest_file_ > 0) {
+ // We need to wait for other IngestExternalFile() calls to finish
+ // before running a manual compaction.
+ return true;
+ }
+ if (m->exclusive) {
+ return (bg_bottom_compaction_scheduled_ > 0 ||
+ bg_compaction_scheduled_ > 0);
+ }
+ std::deque<ManualCompactionState*>::iterator it =
+ manual_compaction_dequeue_.begin();
+ bool seen = false;
+ while (it != manual_compaction_dequeue_.end()) {
+ if (m == (*it)) {
+ ++it;
+ seen = true;
+ continue;
+ } else if (MCOverlap(m, (*it)) && (!seen && !(*it)->in_progress)) {
+ // Consider the other manual compaction *it, conflicts if:
+ // overlaps with m
+ // and (*it) is ahead in the queue and is not yet in progress
+ return true;
+ }
+ ++it;
+ }
+ return false;
+}
+
+bool DBImpl::HaveManualCompaction(ColumnFamilyData* cfd) {
+ // Remove from priority queue
+ std::deque<ManualCompactionState*>::iterator it =
+ manual_compaction_dequeue_.begin();
+ while (it != manual_compaction_dequeue_.end()) {
+ if ((*it)->exclusive) {
+ return true;
+ }
+ if ((cfd == (*it)->cfd) && (!((*it)->in_progress || (*it)->done))) {
+ // Allow automatic compaction if manual compaction is
+ // in progress
+ return true;
+ }
+ ++it;
+ }
+ return false;
+}
+
+bool DBImpl::HasExclusiveManualCompaction() {
+ // Remove from priority queue
+ std::deque<ManualCompactionState*>::iterator it =
+ manual_compaction_dequeue_.begin();
+ while (it != manual_compaction_dequeue_.end()) {
+ if ((*it)->exclusive) {
+ return true;
+ }
+ ++it;
+ }
+ return false;
+}
+
+bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
+ if ((m->exclusive) || (m1->exclusive)) {
+ return true;
+ }
+ if (m->cfd != m1->cfd) {
+ return false;
+ }
+ return true;
+}
+
+#ifndef ROCKSDB_LITE
+void DBImpl::BuildCompactionJobInfo(
+ const ColumnFamilyData* cfd, Compaction* c, const Status& st,
+ const CompactionJobStats& compaction_job_stats, const int job_id,
+ const Version* current, CompactionJobInfo* compaction_job_info) const {
+ assert(compaction_job_info != nullptr);
+ compaction_job_info->cf_id = cfd->GetID();
+ compaction_job_info->cf_name = cfd->GetName();
+ compaction_job_info->status = st;
+ compaction_job_info->thread_id = env_->GetThreadID();
+ compaction_job_info->job_id = job_id;
+ compaction_job_info->base_input_level = c->start_level();
+ compaction_job_info->output_level = c->output_level();
+ compaction_job_info->stats = compaction_job_stats;
+ compaction_job_info->table_properties = c->GetOutputTableProperties();
+ compaction_job_info->compaction_reason = c->compaction_reason();
+ compaction_job_info->compression = c->output_compression();
+ for (size_t i = 0; i < c->num_input_levels(); ++i) {
+ for (const auto fmd : *c->inputs(i)) {
+ const FileDescriptor& desc = fmd->fd;
+ const uint64_t file_number = desc.GetNumber();
+ auto fn = TableFileName(c->immutable_cf_options()->cf_paths, file_number,
+ desc.GetPathId());
+ compaction_job_info->input_files.push_back(fn);
+ compaction_job_info->input_file_infos.push_back(CompactionFileInfo{
+ static_cast<int>(i), file_number, fmd->oldest_blob_file_number});
+ if (compaction_job_info->table_properties.count(fn) == 0) {
+ std::shared_ptr<const TableProperties> tp;
+ auto s = current->GetTableProperties(&tp, fmd, &fn);
+ if (s.ok()) {
+ compaction_job_info->table_properties[fn] = tp;
+ }
+ }
+ }
+ }
+ for (const auto& newf : c->edit()->GetNewFiles()) {
+ const FileMetaData& meta = newf.second;
+ const FileDescriptor& desc = meta.fd;
+ const uint64_t file_number = desc.GetNumber();
+ compaction_job_info->output_files.push_back(TableFileName(
+ c->immutable_cf_options()->cf_paths, file_number, desc.GetPathId()));
+ compaction_job_info->output_file_infos.push_back(CompactionFileInfo{
+ newf.first, file_number, meta.oldest_blob_file_number});
+ }
+}
+#endif
+
+// SuperVersionContext gets created and destructed outside of the lock --
+// we use this conveniently to:
+// * malloc one SuperVersion() outside of the lock -- new_superversion
+// * delete SuperVersion()s outside of the lock -- superversions_to_free
+//
+// However, if InstallSuperVersionAndScheduleWork() gets called twice with the
+// same sv_context, we can't reuse the SuperVersion() that got
+// malloced because
+// first call already used it. In that rare case, we take a hit and create a
+// new SuperVersion() inside of the mutex. We do similar thing
+// for superversion_to_free
+
+void DBImpl::InstallSuperVersionAndScheduleWork(
+ ColumnFamilyData* cfd, SuperVersionContext* sv_context,
+ const MutableCFOptions& mutable_cf_options) {
+ mutex_.AssertHeld();
+
+ // Update max_total_in_memory_state_
+ size_t old_memtable_size = 0;
+ auto* old_sv = cfd->GetSuperVersion();
+ if (old_sv) {
+ old_memtable_size = old_sv->mutable_cf_options.write_buffer_size *
+ old_sv->mutable_cf_options.max_write_buffer_number;
+ }
+
+ // this branch is unlikely to step in
+ if (UNLIKELY(sv_context->new_superversion == nullptr)) {
+ sv_context->NewSuperVersion();
+ }
+ cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options);
+
+ // There may be a small data race here. The snapshot tricking bottommost
+ // compaction may already be released here. But assuming there will always be
+ // newer snapshot created and released frequently, the compaction will be
+ // triggered soon anyway.
+ bottommost_files_mark_threshold_ = kMaxSequenceNumber;
+ for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
+ bottommost_files_mark_threshold_ = std::min(
+ bottommost_files_mark_threshold_,
+ my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
+ }
+
+ // Whenever we install new SuperVersion, we might need to issue new flushes or
+ // compactions.
+ SchedulePendingCompaction(cfd);
+ MaybeScheduleFlushOrCompaction();
+
+ // Update max_total_in_memory_state_
+ max_total_in_memory_state_ = max_total_in_memory_state_ - old_memtable_size +
+ mutable_cf_options.write_buffer_size *
+ mutable_cf_options.max_write_buffer_number;
+}
+
+// ShouldPurge is called by FindObsoleteFiles when doing a full scan,
+// and db mutex (mutex_) should already be held.
+// Actually, the current implementation of FindObsoleteFiles with
+// full_scan=true can issue I/O requests to obtain list of files in
+// directories, e.g. env_->getChildren while holding db mutex.
+bool DBImpl::ShouldPurge(uint64_t file_number) const {
+ return files_grabbed_for_purge_.find(file_number) ==
+ files_grabbed_for_purge_.end() &&
+ purge_files_.find(file_number) == purge_files_.end();
+}
+
+// MarkAsGrabbedForPurge is called by FindObsoleteFiles, and db mutex
+// (mutex_) should already be held.
+void DBImpl::MarkAsGrabbedForPurge(uint64_t file_number) {
+ files_grabbed_for_purge_.insert(file_number);
+}
+
+void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) {
+ InstrumentedMutexLock l(&mutex_);
+ // snapshot_checker_ should only set once. If we need to set it multiple
+ // times, we need to make sure the old one is not deleted while it is still
+ // using by a compaction job.
+ assert(!snapshot_checker_);
+ snapshot_checker_.reset(snapshot_checker);
+}
+
+void DBImpl::GetSnapshotContext(
+ JobContext* job_context, std::vector<SequenceNumber>* snapshot_seqs,
+ SequenceNumber* earliest_write_conflict_snapshot,
+ SnapshotChecker** snapshot_checker_ptr) {
+ mutex_.AssertHeld();
+ assert(job_context != nullptr);
+ assert(snapshot_seqs != nullptr);
+ assert(earliest_write_conflict_snapshot != nullptr);
+ assert(snapshot_checker_ptr != nullptr);
+
+ *snapshot_checker_ptr = snapshot_checker_.get();
+ if (use_custom_gc_ && *snapshot_checker_ptr == nullptr) {
+ *snapshot_checker_ptr = DisableGCSnapshotChecker::Instance();
+ }
+ if (*snapshot_checker_ptr != nullptr) {
+ // If snapshot_checker is used, that means the flush/compaction may
+ // contain values not visible to snapshot taken after
+ // flush/compaction job starts. Take a snapshot and it will appear
+ // in snapshot_seqs and force compaction iterator to consider such
+ // snapshots.
+ const Snapshot* job_snapshot =
+ GetSnapshotImpl(false /*write_conflict_boundary*/, false /*lock*/);
+ job_context->job_snapshot.reset(new ManagedSnapshot(this, job_snapshot));
+ }
+ *snapshot_seqs = snapshots_.GetAll(earliest_write_conflict_snapshot);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_debug.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_debug.cc
new file mode 100644
index 00000000..610b57d3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_debug.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 NDEBUG
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/error_handler.h"
+#include "monitoring/thread_status_updater.h"
+#include "util/cast_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+uint64_t DBImpl::TEST_GetLevel0TotalSize() {
+ InstrumentedMutexLock l(&mutex_);
+ return default_cf_handle_->cfd()->current()->storage_info()->NumLevelBytes(0);
+}
+
+void DBImpl::TEST_SwitchWAL() {
+ WriteContext write_context;
+ InstrumentedMutexLock l(&mutex_);
+ void* writer = TEST_BeginWrite();
+ SwitchWAL(&write_context);
+ TEST_EndWrite(writer);
+}
+
+bool DBImpl::TEST_WALBufferIsEmpty(bool lock) {
+ if (lock) {
+ log_write_mutex_.Lock();
+ }
+ log::Writer* cur_log_writer = logs_.back().writer;
+ auto res = cur_log_writer->TEST_BufferIsEmpty();
+ if (lock) {
+ log_write_mutex_.Unlock();
+ }
+ return res;
+}
+
+int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
+ ColumnFamilyHandle* column_family) {
+ ColumnFamilyData* cfd;
+ if (column_family == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ } else {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ cfd = cfh->cfd();
+ }
+ InstrumentedMutexLock l(&mutex_);
+ return cfd->current()->storage_info()->MaxNextLevelOverlappingBytes();
+}
+
+void DBImpl::TEST_GetFilesMetaData(
+ ColumnFamilyHandle* column_family,
+ std::vector<std::vector<FileMetaData>>* metadata) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ InstrumentedMutexLock l(&mutex_);
+ metadata->resize(NumberLevels());
+ for (int level = 0; level < NumberLevels(); level++) {
+ const std::vector<FileMetaData*>& files =
+ cfd->current()->storage_info()->LevelFiles(level);
+
+ (*metadata)[level].clear();
+ for (const auto& f : files) {
+ (*metadata)[level].push_back(*f);
+ }
+ }
+}
+
+uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
+ return versions_->manifest_file_number();
+}
+
+uint64_t DBImpl::TEST_Current_Next_FileNo() {
+ return versions_->current_next_file_number();
+}
+
+Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
+ const Slice* end,
+ ColumnFamilyHandle* column_family,
+ bool disallow_trivial_move) {
+ ColumnFamilyData* cfd;
+ if (column_family == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ } else {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ cfd = cfh->cfd();
+ }
+ int output_level =
+ (cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
+ cfd->ioptions()->compaction_style == kCompactionStyleFIFO)
+ ? level
+ : level + 1;
+ return RunManualCompaction(cfd, level, output_level, CompactRangeOptions(),
+ begin, end, true, disallow_trivial_move,
+ port::kMaxUint64 /*max_file_num_to_ignore*/);
+}
+
+Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
+ WriteContext write_context;
+ InstrumentedMutexLock l(&mutex_);
+ if (cfd == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ }
+
+ Status s;
+ void* writer = TEST_BeginWrite();
+ if (two_write_queues_) {
+ WriteThread::Writer nonmem_w;
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ s = SwitchMemtable(cfd, &write_context);
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ } else {
+ s = SwitchMemtable(cfd, &write_context);
+ }
+ TEST_EndWrite(writer);
+ return s;
+}
+
+Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
+ ColumnFamilyHandle* cfh) {
+ FlushOptions fo;
+ fo.wait = wait;
+ fo.allow_write_stall = allow_write_stall;
+ ColumnFamilyData* cfd;
+ if (cfh == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ } else {
+ auto cfhi = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh);
+ cfd = cfhi->cfd();
+ }
+ return FlushMemTable(cfd, fo, FlushReason::kTest);
+}
+
+Status DBImpl::TEST_FlushMemTable(ColumnFamilyData* cfd,
+ const FlushOptions& flush_opts) {
+ return FlushMemTable(cfd, flush_opts, FlushReason::kTest);
+}
+
+Status DBImpl::TEST_AtomicFlushMemTables(
+ const autovector<ColumnFamilyData*>& cfds, const FlushOptions& flush_opts) {
+ return AtomicFlushMemTables(cfds, flush_opts, FlushReason::kTest);
+}
+
+Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
+ ColumnFamilyData* cfd;
+ if (column_family == nullptr) {
+ cfd = default_cf_handle_->cfd();
+ } else {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ cfd = cfh->cfd();
+ }
+ return WaitForFlushMemTable(cfd, nullptr, false);
+}
+
+Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
+ // Wait until the compaction completes
+
+ // TODO: a bug here. This function actually does not necessarily
+ // wait for compact. It actually waits for scheduled compaction
+ // OR flush to finish.
+
+ InstrumentedMutexLock l(&mutex_);
+ while ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
+ bg_flush_scheduled_ ||
+ (wait_unscheduled && unscheduled_compactions_)) &&
+ (error_handler_.GetBGError() == Status::OK())) {
+ bg_cv_.Wait();
+ }
+ return error_handler_.GetBGError();
+}
+
+void DBImpl::TEST_LockMutex() { mutex_.Lock(); }
+
+void DBImpl::TEST_UnlockMutex() { mutex_.Unlock(); }
+
+void* DBImpl::TEST_BeginWrite() {
+ auto w = new WriteThread::Writer();
+ write_thread_.EnterUnbatched(w, &mutex_);
+ return reinterpret_cast<void*>(w);
+}
+
+void DBImpl::TEST_EndWrite(void* w) {
+ auto writer = reinterpret_cast<WriteThread::Writer*>(w);
+ write_thread_.ExitUnbatched(writer);
+ delete writer;
+}
+
+size_t DBImpl::TEST_LogsToFreeSize() {
+ InstrumentedMutexLock l(&mutex_);
+ return logs_to_free_.size();
+}
+
+uint64_t DBImpl::TEST_LogfileNumber() {
+ InstrumentedMutexLock l(&mutex_);
+ return logfile_number_;
+}
+
+Status DBImpl::TEST_GetAllImmutableCFOptions(
+ std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map) {
+ std::vector<std::string> cf_names;
+ std::vector<const ImmutableCFOptions*> iopts;
+ {
+ InstrumentedMutexLock l(&mutex_);
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ cf_names.push_back(cfd->GetName());
+ iopts.push_back(cfd->ioptions());
+ }
+ }
+ iopts_map->clear();
+ for (size_t i = 0; i < cf_names.size(); ++i) {
+ iopts_map->insert({cf_names[i], iopts[i]});
+ }
+
+ return Status::OK();
+}
+
+uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
+ return logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep();
+}
+
+size_t DBImpl::TEST_PreparedSectionCompletedSize() {
+ return logs_with_prep_tracker_.TEST_PreparedSectionCompletedSize();
+}
+
+size_t DBImpl::TEST_LogsWithPrepSize() {
+ return logs_with_prep_tracker_.TEST_LogsWithPrepSize();
+}
+
+uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() {
+ autovector<MemTable*> empty_list;
+ return FindMinPrepLogReferencedByMemTable(versions_.get(), nullptr,
+ empty_list);
+}
+
+Status DBImpl::TEST_GetLatestMutableCFOptions(
+ ColumnFamilyHandle* column_family, MutableCFOptions* mutable_cf_options) {
+ InstrumentedMutexLock l(&mutex_);
+
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ *mutable_cf_options = *cfh->cfd()->GetLatestMutableCFOptions();
+ return Status::OK();
+}
+
+int DBImpl::TEST_BGCompactionsAllowed() const {
+ InstrumentedMutexLock l(&mutex_);
+ return GetBGJobLimits().max_compactions;
+}
+
+int DBImpl::TEST_BGFlushesAllowed() const {
+ InstrumentedMutexLock l(&mutex_);
+ return GetBGJobLimits().max_flushes;
+}
+
+SequenceNumber DBImpl::TEST_GetLastVisibleSequence() const {
+ if (last_seq_same_as_publish_seq_) {
+ return versions_->LastSequence();
+ } else {
+ return versions_->LastAllocatedSequence();
+ }
+}
+
+size_t DBImpl::TEST_GetWalPreallocateBlockSize(
+ uint64_t write_buffer_size) const {
+ InstrumentedMutexLock l(&mutex_);
+ return GetWalPreallocateBlockSize(write_buffer_size);
+}
+
+void DBImpl::TEST_WaitForDumpStatsRun(std::function<void()> callback) const {
+ if (thread_dump_stats_ != nullptr) {
+ thread_dump_stats_->TEST_WaitForRun(callback);
+ }
+}
+
+void DBImpl::TEST_WaitForPersistStatsRun(std::function<void()> callback) const {
+ if (thread_persist_stats_ != nullptr) {
+ thread_persist_stats_->TEST_WaitForRun(callback);
+ }
+}
+
+bool DBImpl::TEST_IsPersistentStatsEnabled() const {
+ return thread_persist_stats_ && thread_persist_stats_->IsRunning();
+}
+
+size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
+ return EstimateInMemoryStatsHistorySize();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // NDEBUG
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_experimental.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_experimental.cc
new file mode 100644
index 00000000..f0c17ce9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_experimental.cc
@@ -0,0 +1,151 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <cinttypes>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/job_context.h"
+#include "db/version_set.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ InternalKey start_key, end_key;
+ if (begin != nullptr) {
+ start_key.SetMinPossibleForUserKey(*begin);
+ }
+ if (end != nullptr) {
+ end_key.SetMaxPossibleForUserKey(*end);
+ }
+ {
+ InstrumentedMutexLock l(&mutex_);
+ auto vstorage = cfd->current()->storage_info();
+ for (int level = 0; level < vstorage->num_non_empty_levels() - 1; ++level) {
+ std::vector<FileMetaData*> inputs;
+ vstorage->GetOverlappingInputs(
+ level, begin == nullptr ? nullptr : &start_key,
+ end == nullptr ? nullptr : &end_key, &inputs);
+ for (auto f : inputs) {
+ f->marked_for_compaction = true;
+ }
+ }
+ // Since we have some more files to compact, we should also recompute
+ // compaction score
+ vstorage->ComputeCompactionScore(*cfd->ioptions(),
+ *cfd->GetLatestMutableCFOptions());
+ SchedulePendingCompaction(cfd);
+ MaybeScheduleFlushOrCompaction();
+ }
+ return Status::OK();
+}
+
+Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
+ assert(column_family);
+
+ if (target_level < 1) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "PromoteL0 FAILED. Invalid target level %d\n", target_level);
+ return Status::InvalidArgument("Invalid target level");
+ }
+
+ Status status;
+ VersionEdit edit;
+ JobContext job_context(next_job_id_.fetch_add(1), true);
+ {
+ InstrumentedMutexLock l(&mutex_);
+ auto* cfd = static_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ const auto* vstorage = cfd->current()->storage_info();
+
+ if (target_level >= vstorage->num_levels()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "PromoteL0 FAILED. Target level %d does not exist\n",
+ target_level);
+ job_context.Clean();
+ return Status::InvalidArgument("Target level does not exist");
+ }
+
+ // Sort L0 files by range.
+ const InternalKeyComparator* icmp = &cfd->internal_comparator();
+ auto l0_files = vstorage->LevelFiles(0);
+ std::sort(l0_files.begin(), l0_files.end(),
+ [icmp](FileMetaData* f1, FileMetaData* f2) {
+ return icmp->Compare(f1->largest, f2->largest) < 0;
+ });
+
+ // Check that no L0 file is being compacted and that they have
+ // non-overlapping ranges.
+ for (size_t i = 0; i < l0_files.size(); ++i) {
+ auto f = l0_files[i];
+ if (f->being_compacted) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "PromoteL0 FAILED. File %" PRIu64 " being compacted\n",
+ f->fd.GetNumber());
+ job_context.Clean();
+ return Status::InvalidArgument("PromoteL0 called during L0 compaction");
+ }
+
+ if (i == 0) continue;
+ auto prev_f = l0_files[i - 1];
+ if (icmp->Compare(prev_f->largest, f->smallest) >= 0) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "PromoteL0 FAILED. Files %" PRIu64 " and %" PRIu64
+ " have overlapping ranges\n",
+ prev_f->fd.GetNumber(), f->fd.GetNumber());
+ job_context.Clean();
+ return Status::InvalidArgument("L0 has overlapping files");
+ }
+ }
+
+ // Check that all levels up to target_level are empty.
+ for (int level = 1; level <= target_level; ++level) {
+ if (vstorage->NumLevelFiles(level) > 0) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "PromoteL0 FAILED. Level %d not empty\n", level);
+ job_context.Clean();
+ return Status::InvalidArgument(
+ "All levels up to target_level "
+ "must be empty");
+ }
+ }
+
+ edit.SetColumnFamily(cfd->GetID());
+ for (const auto& f : l0_files) {
+ edit.DeleteFile(0, f->fd.GetNumber());
+ edit.AddFile(target_level, f->fd.GetNumber(), f->fd.GetPathId(),
+ f->fd.GetFileSize(), f->smallest, f->largest,
+ f->fd.smallest_seqno, f->fd.largest_seqno,
+ f->marked_for_compaction, f->oldest_blob_file_number,
+ f->oldest_ancester_time, f->file_creation_time,
+ f->file_checksum, f->file_checksum_func_name);
+ }
+
+ status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
+ &edit, &mutex_, directories_.GetDbDir());
+ if (status.ok()) {
+ InstallSuperVersionAndScheduleWork(cfd,
+ &job_context.superversion_contexts[0],
+ *cfd->GetLatestMutableCFOptions());
+ }
+ } // lock released here
+ LogFlush(immutable_db_options_.info_log);
+ job_context.Clean();
+
+ return status;
+}
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_files.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_files.cc
new file mode 100644
index 00000000..c5d07dd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_files.cc
@@ -0,0 +1,667 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <cinttypes>
+#include <set>
+#include <unordered_set>
+#include "db/event_helpers.h"
+#include "db/memtable_list.h"
+#include "file/file_util.h"
+#include "file/sst_file_manager_impl.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+uint64_t DBImpl::MinLogNumberToKeep() {
+ if (allow_2pc()) {
+ return versions_->min_log_number_to_keep_2pc();
+ } else {
+ return versions_->MinLogNumberWithUnflushedData();
+ }
+}
+
+uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
+ mutex_.AssertHeld();
+ if (!pending_outputs_.empty()) {
+ return *pending_outputs_.begin();
+ }
+ return std::numeric_limits<uint64_t>::max();
+}
+
+// * Returns the list of live files in 'sst_live'
+// If it's doing full scan:
+// * Returns the list of all files in the filesystem in
+// 'full_scan_candidate_files'.
+// Otherwise, gets obsolete files from VersionSet.
+// no_full_scan = true -- never do the full scan using GetChildren()
+// force = false -- don't force the full scan, except every
+// mutable_db_options_.delete_obsolete_files_period_micros
+// force = true -- force the full scan
+void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
+ bool no_full_scan) {
+ mutex_.AssertHeld();
+
+ // if deletion is disabled, do nothing
+ if (disable_delete_obsolete_files_ > 0) {
+ return;
+ }
+
+ bool doing_the_full_scan = false;
+
+ // logic for figuring out if we're doing the full scan
+ if (no_full_scan) {
+ doing_the_full_scan = false;
+ } else if (force ||
+ mutable_db_options_.delete_obsolete_files_period_micros == 0) {
+ doing_the_full_scan = true;
+ } else {
+ const uint64_t now_micros = env_->NowMicros();
+ if ((delete_obsolete_files_last_run_ +
+ mutable_db_options_.delete_obsolete_files_period_micros) <
+ now_micros) {
+ doing_the_full_scan = true;
+ delete_obsolete_files_last_run_ = now_micros;
+ }
+ }
+
+ // don't delete files that might be currently written to from compaction
+ // threads
+ // Since job_context->min_pending_output is set, until file scan finishes,
+ // mutex_ cannot be released. Otherwise, we might see no min_pending_output
+ // here but later find newer generated unfinalized files while scanning.
+ if (!pending_outputs_.empty()) {
+ job_context->min_pending_output = *pending_outputs_.begin();
+ } else {
+ // delete all of them
+ job_context->min_pending_output = std::numeric_limits<uint64_t>::max();
+ }
+
+ // Get obsolete files. This function will also update the list of
+ // pending files in VersionSet().
+ versions_->GetObsoleteFiles(&job_context->sst_delete_files,
+ &job_context->manifest_delete_files,
+ job_context->min_pending_output);
+
+ // Mark the elements in job_context->sst_delete_files as grabbedForPurge
+ // so that other threads calling FindObsoleteFiles with full_scan=true
+ // will not add these files to candidate list for purge.
+ for (const auto& sst_to_del : job_context->sst_delete_files) {
+ MarkAsGrabbedForPurge(sst_to_del.metadata->fd.GetNumber());
+ }
+
+ // store the current filenum, lognum, etc
+ job_context->manifest_file_number = versions_->manifest_file_number();
+ job_context->pending_manifest_file_number =
+ versions_->pending_manifest_file_number();
+ job_context->log_number = MinLogNumberToKeep();
+ job_context->prev_log_number = versions_->prev_log_number();
+
+ versions_->AddLiveFiles(&job_context->sst_live);
+ if (doing_the_full_scan) {
+ InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
+ dbname_);
+ std::set<std::string> paths;
+ for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size();
+ path_id++) {
+ paths.insert(immutable_db_options_.db_paths[path_id].path);
+ }
+
+ // Note that if cf_paths is not specified in the ColumnFamilyOptions
+ // of a particular column family, we use db_paths as the cf_paths
+ // setting. Hence, there can be multiple duplicates of files from db_paths
+ // in the following code. The duplicate are removed while identifying
+ // unique files in PurgeObsoleteFiles.
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ for (size_t path_id = 0; path_id < cfd->ioptions()->cf_paths.size();
+ path_id++) {
+ auto& path = cfd->ioptions()->cf_paths[path_id].path;
+
+ if (paths.find(path) == paths.end()) {
+ paths.insert(path);
+ }
+ }
+ }
+
+ for (auto& path : paths) {
+ // set of all files in the directory. We'll exclude files that are still
+ // alive in the subsequent processings.
+ std::vector<std::string> files;
+ env_->GetChildren(path, &files); // Ignore errors
+ for (const std::string& file : files) {
+ uint64_t number;
+ FileType type;
+ // 1. If we cannot parse the file name, we skip;
+ // 2. If the file with file_number equals number has already been
+ // grabbed for purge by another compaction job, or it has already been
+ // schedule for purge, we also skip it if we
+ // are doing full scan in order to avoid double deletion of the same
+ // file under race conditions. See
+ // https://github.com/facebook/rocksdb/issues/3573
+ if (!ParseFileName(file, &number, info_log_prefix.prefix, &type) ||
+ !ShouldPurge(number)) {
+ continue;
+ }
+
+ // TODO(icanadi) clean up this mess to avoid having one-off "/" prefixes
+ job_context->full_scan_candidate_files.emplace_back("/" + file, path);
+ }
+ }
+
+ // Add log files in wal_dir
+ if (immutable_db_options_.wal_dir != dbname_) {
+ std::vector<std::string> log_files;
+ env_->GetChildren(immutable_db_options_.wal_dir,
+ &log_files); // Ignore errors
+ for (const std::string& log_file : log_files) {
+ job_context->full_scan_candidate_files.emplace_back(
+ log_file, immutable_db_options_.wal_dir);
+ }
+ }
+ // Add info log files in db_log_dir
+ if (!immutable_db_options_.db_log_dir.empty() &&
+ immutable_db_options_.db_log_dir != dbname_) {
+ std::vector<std::string> info_log_files;
+ // Ignore errors
+ env_->GetChildren(immutable_db_options_.db_log_dir, &info_log_files);
+ for (std::string& log_file : info_log_files) {
+ job_context->full_scan_candidate_files.emplace_back(
+ log_file, immutable_db_options_.db_log_dir);
+ }
+ }
+ }
+
+ // logs_ is empty when called during recovery, in which case there can't yet
+ // be any tracked obsolete logs
+ if (!alive_log_files_.empty() && !logs_.empty()) {
+ uint64_t min_log_number = job_context->log_number;
+ size_t num_alive_log_files = alive_log_files_.size();
+ // find newly obsoleted log files
+ while (alive_log_files_.begin()->number < min_log_number) {
+ auto& earliest = *alive_log_files_.begin();
+ if (immutable_db_options_.recycle_log_file_num >
+ log_recycle_files_.size()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "adding log %" PRIu64 " to recycle list\n",
+ earliest.number);
+ log_recycle_files_.push_back(earliest.number);
+ } else {
+ job_context->log_delete_files.push_back(earliest.number);
+ }
+ if (job_context->size_log_to_delete == 0) {
+ job_context->prev_total_log_size = total_log_size_;
+ job_context->num_alive_log_files = num_alive_log_files;
+ }
+ job_context->size_log_to_delete += earliest.size;
+ total_log_size_ -= earliest.size;
+ if (two_write_queues_) {
+ log_write_mutex_.Lock();
+ }
+ alive_log_files_.pop_front();
+ if (two_write_queues_) {
+ log_write_mutex_.Unlock();
+ }
+ // Current log should always stay alive since it can't have
+ // number < MinLogNumber().
+ assert(alive_log_files_.size());
+ }
+ while (!logs_.empty() && logs_.front().number < min_log_number) {
+ auto& log = logs_.front();
+ if (log.getting_synced) {
+ log_sync_cv_.Wait();
+ // logs_ could have changed while we were waiting.
+ continue;
+ }
+ logs_to_free_.push_back(log.ReleaseWriter());
+ {
+ InstrumentedMutexLock wl(&log_write_mutex_);
+ logs_.pop_front();
+ }
+ }
+ // Current log cannot be obsolete.
+ assert(!logs_.empty());
+ }
+
+ // We're just cleaning up for DB::Write().
+ assert(job_context->logs_to_free.empty());
+ job_context->logs_to_free = logs_to_free_;
+ job_context->log_recycle_files.assign(log_recycle_files_.begin(),
+ log_recycle_files_.end());
+ if (job_context->HaveSomethingToDelete()) {
+ ++pending_purge_obsolete_files_;
+ }
+ logs_to_free_.clear();
+}
+
+namespace {
+bool CompareCandidateFile(const JobContext::CandidateFileInfo& first,
+ const JobContext::CandidateFileInfo& second) {
+ if (first.file_name > second.file_name) {
+ return true;
+ } else if (first.file_name < second.file_name) {
+ return false;
+ } else {
+ return (first.file_path > second.file_path);
+ }
+}
+}; // namespace
+
+// Delete obsolete files and log status and information of file deletion
+void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
+ const std::string& path_to_sync,
+ FileType type, uint64_t number) {
+ Status file_deletion_status;
+ if (type == kTableFile || type == kLogFile) {
+ file_deletion_status =
+ DeleteDBFile(&immutable_db_options_, fname, path_to_sync,
+ /*force_bg=*/false, /*force_fg=*/!wal_in_db_path_);
+ } else {
+ file_deletion_status = env_->DeleteFile(fname);
+ }
+ TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl:AfterDeletion",
+ &file_deletion_status);
+ if (file_deletion_status.ok()) {
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", job_id,
+ fname.c_str(), type, number,
+ file_deletion_status.ToString().c_str());
+ } else if (env_->FileExists(fname).IsNotFound()) {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "[JOB %d] Tried to delete a non-existing file %s type=%d #%" PRIu64
+ " -- %s\n",
+ job_id, fname.c_str(), type, number,
+ file_deletion_status.ToString().c_str());
+ } else {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "[JOB %d] Failed to delete %s type=%d #%" PRIu64 " -- %s\n",
+ job_id, fname.c_str(), type, number,
+ file_deletion_status.ToString().c_str());
+ }
+ if (type == kTableFile) {
+ EventHelpers::LogAndNotifyTableFileDeletion(
+ &event_logger_, job_id, number, fname, file_deletion_status, GetName(),
+ immutable_db_options_.listeners);
+ }
+}
+
+// Diffs the files listed in filenames and those that do not
+// belong to live files are possibly removed. Also, removes all the
+// files in sst_delete_files and log_delete_files.
+// It is not necessary to hold the mutex when invoking this method.
+void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
+ TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:Begin");
+ // we'd better have sth to delete
+ assert(state.HaveSomethingToDelete());
+
+ // FindObsoleteFiles() should've populated this so nonzero
+ assert(state.manifest_file_number != 0);
+
+ // Now, convert live list to an unordered map, WITHOUT mutex held;
+ // set is slow.
+ std::unordered_map<uint64_t, const FileDescriptor*> sst_live_map;
+ for (const FileDescriptor& fd : state.sst_live) {
+ sst_live_map[fd.GetNumber()] = &fd;
+ }
+ std::unordered_set<uint64_t> log_recycle_files_set(
+ state.log_recycle_files.begin(), state.log_recycle_files.end());
+
+ auto candidate_files = state.full_scan_candidate_files;
+ candidate_files.reserve(
+ candidate_files.size() + state.sst_delete_files.size() +
+ state.log_delete_files.size() + state.manifest_delete_files.size());
+ // We may ignore the dbname when generating the file names.
+ for (auto& file : state.sst_delete_files) {
+ candidate_files.emplace_back(
+ MakeTableFileName(file.metadata->fd.GetNumber()), file.path);
+ if (file.metadata->table_reader_handle) {
+ table_cache_->Release(file.metadata->table_reader_handle);
+ }
+ file.DeleteMetadata();
+ }
+
+ for (auto file_num : state.log_delete_files) {
+ if (file_num > 0) {
+ candidate_files.emplace_back(LogFileName(file_num),
+ immutable_db_options_.wal_dir);
+ }
+ }
+ for (const auto& filename : state.manifest_delete_files) {
+ candidate_files.emplace_back(filename, dbname_);
+ }
+
+ // dedup state.candidate_files so we don't try to delete the same
+ // file twice
+ std::sort(candidate_files.begin(), candidate_files.end(),
+ CompareCandidateFile);
+ candidate_files.erase(
+ std::unique(candidate_files.begin(), candidate_files.end()),
+ candidate_files.end());
+
+ if (state.prev_total_log_size > 0) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[JOB %d] Try to delete WAL files size %" PRIu64
+ ", prev total WAL file size %" PRIu64
+ ", number of live WAL files %" ROCKSDB_PRIszt ".\n",
+ state.job_id, state.size_log_to_delete,
+ state.prev_total_log_size, state.num_alive_log_files);
+ }
+
+ std::vector<std::string> old_info_log_files;
+ InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
+ dbname_);
+
+ // File numbers of most recent two OPTIONS file in candidate_files (found in
+ // previos FindObsoleteFiles(full_scan=true))
+ // At this point, there must not be any duplicate file numbers in
+ // candidate_files.
+ uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
+ uint64_t optsfile_num2 = std::numeric_limits<uint64_t>::min();
+ for (const auto& candidate_file : candidate_files) {
+ const std::string& fname = candidate_file.file_name;
+ uint64_t number;
+ FileType type;
+ if (!ParseFileName(fname, &number, info_log_prefix.prefix, &type) ||
+ type != kOptionsFile) {
+ continue;
+ }
+ if (number > optsfile_num1) {
+ optsfile_num2 = optsfile_num1;
+ optsfile_num1 = number;
+ } else if (number > optsfile_num2) {
+ optsfile_num2 = number;
+ }
+ }
+
+ // Close WALs before trying to delete them.
+ for (const auto w : state.logs_to_free) {
+ // TODO: maybe check the return value of Close.
+ w->Close();
+ }
+
+ bool own_files = OwnTablesAndLogs();
+ std::unordered_set<uint64_t> files_to_del;
+ for (const auto& candidate_file : candidate_files) {
+ const std::string& to_delete = candidate_file.file_name;
+ uint64_t number;
+ FileType type;
+ // Ignore file if we cannot recognize it.
+ if (!ParseFileName(to_delete, &number, info_log_prefix.prefix, &type)) {
+ continue;
+ }
+
+ bool keep = true;
+ switch (type) {
+ case kLogFile:
+ keep = ((number >= state.log_number) ||
+ (number == state.prev_log_number) ||
+ (log_recycle_files_set.find(number) !=
+ log_recycle_files_set.end()));
+ break;
+ case kDescriptorFile:
+ // Keep my manifest file, and any newer incarnations'
+ // (can happen during manifest roll)
+ keep = (number >= state.manifest_file_number);
+ break;
+ case kTableFile:
+ // If the second condition is not there, this makes
+ // DontDeletePendingOutputs fail
+ keep = (sst_live_map.find(number) != sst_live_map.end()) ||
+ number >= state.min_pending_output;
+ if (!keep) {
+ files_to_del.insert(number);
+ }
+ break;
+ case kTempFile:
+ // Any temp files that are currently being written to must
+ // be recorded in pending_outputs_, which is inserted into "live".
+ // Also, SetCurrentFile creates a temp file when writing out new
+ // manifest, which is equal to state.pending_manifest_file_number. We
+ // should not delete that file
+ //
+ // TODO(yhchiang): carefully modify the third condition to safely
+ // remove the temp options files.
+ keep = (sst_live_map.find(number) != sst_live_map.end()) ||
+ (number == state.pending_manifest_file_number) ||
+ (to_delete.find(kOptionsFileNamePrefix) != std::string::npos);
+ break;
+ case kInfoLogFile:
+ keep = true;
+ if (number != 0) {
+ old_info_log_files.push_back(to_delete);
+ }
+ break;
+ case kOptionsFile:
+ keep = (number >= optsfile_num2);
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1",
+ reinterpret_cast<void*>(&number));
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2",
+ reinterpret_cast<void*>(&keep));
+ break;
+ case kCurrentFile:
+ case kDBLockFile:
+ case kIdentityFile:
+ case kMetaDatabase:
+ case kBlobFile:
+ keep = true;
+ break;
+ }
+
+ if (keep) {
+ continue;
+ }
+
+ std::string fname;
+ std::string dir_to_sync;
+ if (type == kTableFile) {
+ // evict from cache
+ TableCache::Evict(table_cache_.get(), number);
+ fname = MakeTableFileName(candidate_file.file_path, number);
+ dir_to_sync = candidate_file.file_path;
+ } else {
+ dir_to_sync =
+ (type == kLogFile) ? immutable_db_options_.wal_dir : dbname_;
+ fname = dir_to_sync +
+ ((!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
+ (!to_delete.empty() && to_delete.front() == '/')
+ ? ""
+ : "/") +
+ to_delete;
+ }
+
+#ifndef ROCKSDB_LITE
+ if (type == kLogFile && (immutable_db_options_.wal_ttl_seconds > 0 ||
+ immutable_db_options_.wal_size_limit_mb > 0)) {
+ wal_manager_.ArchiveWALFile(fname, number);
+ continue;
+ }
+#endif // !ROCKSDB_LITE
+
+ // If I do not own these files, e.g. secondary instance with max_open_files
+ // = -1, then no need to delete or schedule delete these files since they
+ // will be removed by their owner, e.g. the primary instance.
+ if (!own_files) {
+ continue;
+ }
+ Status file_deletion_status;
+ if (schedule_only) {
+ InstrumentedMutexLock guard_lock(&mutex_);
+ SchedulePendingPurge(fname, dir_to_sync, type, number, state.job_id);
+ } else {
+ DeleteObsoleteFileImpl(state.job_id, fname, dir_to_sync, type, number);
+ }
+ }
+
+ {
+ // After purging obsolete files, remove them from files_grabbed_for_purge_.
+ InstrumentedMutexLock guard_lock(&mutex_);
+ autovector<uint64_t> to_be_removed;
+ for (auto fn : files_grabbed_for_purge_) {
+ if (files_to_del.count(fn) != 0) {
+ to_be_removed.emplace_back(fn);
+ }
+ }
+ for (auto fn : to_be_removed) {
+ files_grabbed_for_purge_.erase(fn);
+ }
+ }
+
+ // Delete old info log files.
+ size_t old_info_log_file_count = old_info_log_files.size();
+ if (old_info_log_file_count != 0 &&
+ old_info_log_file_count >= immutable_db_options_.keep_log_file_num) {
+ std::sort(old_info_log_files.begin(), old_info_log_files.end());
+ size_t end =
+ old_info_log_file_count - immutable_db_options_.keep_log_file_num;
+ for (unsigned int i = 0; i <= end; i++) {
+ std::string& to_delete = old_info_log_files.at(i);
+ std::string full_path_to_delete =
+ (immutable_db_options_.db_log_dir.empty()
+ ? dbname_
+ : immutable_db_options_.db_log_dir) +
+ "/" + to_delete;
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[JOB %d] Delete info log file %s\n", state.job_id,
+ full_path_to_delete.c_str());
+ Status s = env_->DeleteFile(full_path_to_delete);
+ if (!s.ok()) {
+ if (env_->FileExists(full_path_to_delete).IsNotFound()) {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "[JOB %d] Tried to delete non-existing info log file %s FAILED "
+ "-- %s\n",
+ state.job_id, to_delete.c_str(), s.ToString().c_str());
+ } else {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "[JOB %d] Delete info log file %s FAILED -- %s\n",
+ state.job_id, to_delete.c_str(),
+ s.ToString().c_str());
+ }
+ }
+ }
+ }
+#ifndef ROCKSDB_LITE
+ wal_manager_.PurgeObsoleteWALFiles();
+#endif // ROCKSDB_LITE
+ LogFlush(immutable_db_options_.info_log);
+ InstrumentedMutexLock l(&mutex_);
+ --pending_purge_obsolete_files_;
+ assert(pending_purge_obsolete_files_ >= 0);
+ if (pending_purge_obsolete_files_ == 0) {
+ bg_cv_.SignalAll();
+ }
+ TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:End");
+}
+
+void DBImpl::DeleteObsoleteFiles() {
+ mutex_.AssertHeld();
+ JobContext job_context(next_job_id_.fetch_add(1));
+ FindObsoleteFiles(&job_context, true);
+
+ mutex_.Unlock();
+ if (job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(job_context);
+ }
+ job_context.Clean();
+ mutex_.Lock();
+}
+
+uint64_t FindMinPrepLogReferencedByMemTable(
+ VersionSet* vset, const ColumnFamilyData* cfd_to_flush,
+ const autovector<MemTable*>& memtables_to_flush) {
+ uint64_t min_log = 0;
+
+ // we must look through the memtables for two phase transactions
+ // that have been committed but not yet flushed
+ for (auto loop_cfd : *vset->GetColumnFamilySet()) {
+ if (loop_cfd->IsDropped() || loop_cfd == cfd_to_flush) {
+ continue;
+ }
+
+ auto log = loop_cfd->imm()->PrecomputeMinLogContainingPrepSection(
+ memtables_to_flush);
+
+ if (log > 0 && (min_log == 0 || log < min_log)) {
+ min_log = log;
+ }
+
+ log = loop_cfd->mem()->GetMinLogContainingPrepSection();
+
+ if (log > 0 && (min_log == 0 || log < min_log)) {
+ min_log = log;
+ }
+ }
+
+ return min_log;
+}
+
+uint64_t PrecomputeMinLogNumberToKeep(
+ VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
+ autovector<VersionEdit*> edit_list,
+ const autovector<MemTable*>& memtables_to_flush,
+ LogsWithPrepTracker* prep_tracker) {
+ assert(vset != nullptr);
+ assert(prep_tracker != nullptr);
+ // Calculate updated min_log_number_to_keep
+ // Since the function should only be called in 2pc mode, log number in
+ // the version edit should be sufficient.
+
+ // Precompute the min log number containing unflushed data for the column
+ // family being flushed (`cfd_to_flush`).
+ uint64_t cf_min_log_number_to_keep = 0;
+ for (auto& e : edit_list) {
+ if (e->HasLogNumber()) {
+ cf_min_log_number_to_keep =
+ std::max(cf_min_log_number_to_keep, e->GetLogNumber());
+ }
+ }
+ if (cf_min_log_number_to_keep == 0) {
+ // No version edit contains information on log number. The log number
+ // for this column family should stay the same as it is.
+ cf_min_log_number_to_keep = cfd_to_flush.GetLogNumber();
+ }
+
+ // Get min log number containing unflushed data for other column families.
+ uint64_t min_log_number_to_keep =
+ vset->PreComputeMinLogNumberWithUnflushedData(&cfd_to_flush);
+ if (cf_min_log_number_to_keep != 0) {
+ min_log_number_to_keep =
+ std::min(cf_min_log_number_to_keep, min_log_number_to_keep);
+ }
+
+ // if are 2pc we must consider logs containing prepared
+ // sections of outstanding transactions.
+ //
+ // We must check min logs with outstanding prep before we check
+ // logs references by memtables because a log referenced by the
+ // first data structure could transition to the second under us.
+ //
+ // TODO: iterating over all column families under db mutex.
+ // should find more optimal solution
+ auto min_log_in_prep_heap =
+ prep_tracker->FindMinLogContainingOutstandingPrep();
+
+ if (min_log_in_prep_heap != 0 &&
+ min_log_in_prep_heap < min_log_number_to_keep) {
+ min_log_number_to_keep = min_log_in_prep_heap;
+ }
+
+ uint64_t min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable(
+ vset, &cfd_to_flush, memtables_to_flush);
+
+ if (min_log_refed_by_mem != 0 &&
+ min_log_refed_by_mem < min_log_number_to_keep) {
+ min_log_number_to_keep = min_log_refed_by_mem;
+ }
+ return min_log_number_to_keep;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_open.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_open.cc
new file mode 100644
index 00000000..6ae4ead5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_open.cc
@@ -0,0 +1,1651 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <cinttypes>
+
+#include "db/builder.h"
+#include "db/error_handler.h"
+#include "env/composite_env_wrapper.h"
+#include "file/read_write_util.h"
+#include "file/sst_file_manager_impl.h"
+#include "file/writable_file_writer.h"
+#include "monitoring/persistent_stats_history.h"
+#include "options/options_helper.h"
+#include "rocksdb/wal_filter.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "test_util/sync_point.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+Options SanitizeOptions(const std::string& dbname, const Options& src) {
+ auto db_options = SanitizeOptions(dbname, DBOptions(src));
+ ImmutableDBOptions immutable_db_options(db_options);
+ auto cf_options =
+ SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
+ return Options(db_options, cf_options);
+}
+
+DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
+ DBOptions result(src);
+
+ if (result.file_system == nullptr) {
+ if (result.env == Env::Default()) {
+ result.file_system = FileSystem::Default();
+ } else {
+ result.file_system.reset(new LegacyFileSystemWrapper(result.env));
+ }
+ } else {
+ if (result.env == nullptr) {
+ result.env = Env::Default();
+ }
+ }
+
+ // result.max_open_files means an "infinite" open files.
+ if (result.max_open_files != -1) {
+ int max_max_open_files = port::GetMaxOpenFiles();
+ if (max_max_open_files == -1) {
+ max_max_open_files = 0x400000;
+ }
+ ClipToRange(&result.max_open_files, 20, max_max_open_files);
+ TEST_SYNC_POINT_CALLBACK("SanitizeOptions::AfterChangeMaxOpenFiles",
+ &result.max_open_files);
+ }
+
+ if (result.info_log == nullptr) {
+ Status s = CreateLoggerFromOptions(dbname, result, &result.info_log);
+ if (!s.ok()) {
+ // No place suitable for logging
+ result.info_log = nullptr;
+ }
+ }
+
+ if (!result.write_buffer_manager) {
+ result.write_buffer_manager.reset(
+ new WriteBufferManager(result.db_write_buffer_size));
+ }
+ auto bg_job_limits = DBImpl::GetBGJobLimits(
+ result.max_background_flushes, result.max_background_compactions,
+ result.max_background_jobs, true /* parallelize_compactions */);
+ result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_compactions,
+ Env::Priority::LOW);
+ result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_flushes,
+ Env::Priority::HIGH);
+
+ if (result.rate_limiter.get() != nullptr) {
+ if (result.bytes_per_sync == 0) {
+ result.bytes_per_sync = 1024 * 1024;
+ }
+ }
+
+ if (result.delayed_write_rate == 0) {
+ if (result.rate_limiter.get() != nullptr) {
+ result.delayed_write_rate = result.rate_limiter->GetBytesPerSecond();
+ }
+ if (result.delayed_write_rate == 0) {
+ result.delayed_write_rate = 16 * 1024 * 1024;
+ }
+ }
+
+ if (result.WAL_ttl_seconds > 0 || result.WAL_size_limit_MB > 0) {
+ result.recycle_log_file_num = false;
+ }
+
+ if (result.recycle_log_file_num &&
+ (result.wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery ||
+ result.wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency)) {
+ // kPointInTimeRecovery is inconsistent with recycle log file feature since
+ // we define the "end" of the log as the first corrupt record we encounter.
+ // kAbsoluteConsistency doesn't make sense because even a clean
+ // shutdown leaves old junk at the end of the log file.
+ result.recycle_log_file_num = 0;
+ }
+
+ if (result.wal_dir.empty()) {
+ // Use dbname as default
+ result.wal_dir = dbname;
+ }
+ if (result.wal_dir.back() == '/') {
+ result.wal_dir = result.wal_dir.substr(0, result.wal_dir.size() - 1);
+ }
+
+ if (result.db_paths.size() == 0) {
+ result.db_paths.emplace_back(dbname, std::numeric_limits<uint64_t>::max());
+ }
+
+ if (result.use_direct_reads && result.compaction_readahead_size == 0) {
+ TEST_SYNC_POINT_CALLBACK("SanitizeOptions:direct_io", nullptr);
+ result.compaction_readahead_size = 1024 * 1024 * 2;
+ }
+
+ if (result.compaction_readahead_size > 0 || result.use_direct_reads) {
+ result.new_table_reader_for_compaction_inputs = true;
+ }
+
+ // Force flush on DB open if 2PC is enabled, since with 2PC we have no
+ // guarantee that consecutive log files have consecutive sequence id, which
+ // make recovery complicated.
+ if (result.allow_2pc) {
+ result.avoid_flush_during_recovery = false;
+ }
+
+#ifndef ROCKSDB_LITE
+ ImmutableDBOptions immutable_db_options(result);
+ if (!IsWalDirSameAsDBPath(&immutable_db_options)) {
+ // Either the WAL dir and db_paths[0]/db_name are not the same, or we
+ // cannot tell for sure. In either case, assume they're different and
+ // explicitly cleanup the trash log files (bypass DeleteScheduler)
+ // Do this first so even if we end up calling
+ // DeleteScheduler::CleanupDirectory on the same dir later, it will be
+ // safe
+ std::vector<std::string> filenames;
+ result.env->GetChildren(result.wal_dir, &filenames);
+ for (std::string& filename : filenames) {
+ if (filename.find(".log.trash", filename.length() -
+ std::string(".log.trash").length()) !=
+ std::string::npos) {
+ std::string trash_file = result.wal_dir + "/" + filename;
+ result.env->DeleteFile(trash_file);
+ }
+ }
+ }
+ // When the DB is stopped, it's possible that there are some .trash files that
+ // were not deleted yet, when we open the DB we will find these .trash files
+ // and schedule them to be deleted (or delete immediately if SstFileManager
+ // was not used)
+ auto sfm = static_cast<SstFileManagerImpl*>(result.sst_file_manager.get());
+ for (size_t i = 0; i < result.db_paths.size(); i++) {
+ DeleteScheduler::CleanupDirectory(result.env, sfm, result.db_paths[i].path);
+ }
+
+ // Create a default SstFileManager for purposes of tracking compaction size
+ // and facilitating recovery from out of space errors.
+ if (result.sst_file_manager.get() == nullptr) {
+ std::shared_ptr<SstFileManager> sst_file_manager(
+ NewSstFileManager(result.env, result.info_log));
+ result.sst_file_manager = sst_file_manager;
+ }
+#endif
+
+ if (!result.paranoid_checks) {
+ result.skip_checking_sst_file_sizes_on_db_open = true;
+ ROCKS_LOG_INFO(result.info_log,
+ "file size check will be skipped during open.");
+ }
+
+ return result;
+}
+
+namespace {
+Status SanitizeOptionsByTable(
+ const DBOptions& db_opts,
+ const std::vector<ColumnFamilyDescriptor>& column_families) {
+ Status s;
+ for (auto cf : column_families) {
+ s = cf.options.table_factory->SanitizeOptions(db_opts, cf.options);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ return Status::OK();
+}
+} // namespace
+
+Status DBImpl::ValidateOptions(
+ const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families) {
+ Status s;
+ for (auto& cfd : column_families) {
+ s = ColumnFamilyData::ValidateOptions(db_options, cfd.options);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ s = ValidateOptions(db_options);
+ return s;
+}
+
+Status DBImpl::ValidateOptions(const DBOptions& db_options) {
+ if (db_options.db_paths.size() > 4) {
+ return Status::NotSupported(
+ "More than four DB paths are not supported yet. ");
+ }
+
+ if (db_options.allow_mmap_reads && db_options.use_direct_reads) {
+ // Protect against assert in PosixMMapReadableFile constructor
+ return Status::NotSupported(
+ "If memory mapped reads (allow_mmap_reads) are enabled "
+ "then direct I/O reads (use_direct_reads) must be disabled. ");
+ }
+
+ if (db_options.allow_mmap_writes &&
+ db_options.use_direct_io_for_flush_and_compaction) {
+ return Status::NotSupported(
+ "If memory mapped writes (allow_mmap_writes) are enabled "
+ "then direct I/O writes (use_direct_io_for_flush_and_compaction) must "
+ "be disabled. ");
+ }
+
+ if (db_options.keep_log_file_num == 0) {
+ return Status::InvalidArgument("keep_log_file_num must be greater than 0");
+ }
+
+ if (db_options.unordered_write &&
+ !db_options.allow_concurrent_memtable_write) {
+ return Status::InvalidArgument(
+ "unordered_write is incompatible with !allow_concurrent_memtable_write");
+ }
+
+ if (db_options.unordered_write && db_options.enable_pipelined_write) {
+ return Status::InvalidArgument(
+ "unordered_write is incompatible with enable_pipelined_write");
+ }
+
+ if (db_options.atomic_flush && db_options.enable_pipelined_write) {
+ return Status::InvalidArgument(
+ "atomic_flush is incompatible with enable_pipelined_write");
+ }
+
+ return Status::OK();
+}
+
+Status DBImpl::NewDB() {
+ VersionEdit new_db;
+ Status s = SetIdentityFile(env_, dbname_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (immutable_db_options_.write_dbid_to_manifest) {
+ std::string temp_db_id;
+ GetDbIdentityFromIdentityFile(&temp_db_id);
+ new_db.SetDBId(temp_db_id);
+ }
+ new_db.SetLogNumber(0);
+ new_db.SetNextFile(2);
+ new_db.SetLastSequence(0);
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "Creating manifest 1 \n");
+ const std::string manifest = DescriptorFileName(dbname_, 1);
+ {
+ std::unique_ptr<FSWritableFile> file;
+ FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
+ s = NewWritableFile(fs_.get(), manifest, &file, file_options);
+ if (!s.ok()) {
+ return s;
+ }
+ file->SetPreallocationBlockSize(
+ immutable_db_options_.manifest_preallocation_size);
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ std::move(file), manifest, file_options, env_, nullptr /* stats */,
+ immutable_db_options_.listeners));
+ log::Writer log(std::move(file_writer), 0, false);
+ std::string record;
+ new_db.EncodeTo(&record);
+ s = log.AddRecord(record);
+ if (s.ok()) {
+ s = SyncManifest(env_, &immutable_db_options_, log.file());
+ }
+ }
+ if (s.ok()) {
+ // Make "CURRENT" file that points to the new manifest file.
+ s = SetCurrentFile(env_, dbname_, 1, directories_.GetDbDir());
+ } else {
+ fs_->DeleteFile(manifest, IOOptions(), nullptr);
+ }
+ return s;
+}
+
+Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname,
+ std::unique_ptr<Directory>* directory) {
+ // We call CreateDirIfMissing() as the directory may already exist (if we
+ // are reopening a DB), when this happens we don't want creating the
+ // directory to cause an error. However, we need to check if creating the
+ // directory fails or else we may get an obscure message about the lock
+ // file not existing. One real-world example of this occurring is if
+ // env->CreateDirIfMissing() doesn't create intermediate directories, e.g.
+ // when dbname_ is "dir/db" but when "dir" doesn't exist.
+ Status s = env->CreateDirIfMissing(dirname);
+ if (!s.ok()) {
+ return s;
+ }
+ return env->NewDirectory(dirname, directory);
+}
+
+Status Directories::SetDirectories(Env* env, const std::string& dbname,
+ const std::string& wal_dir,
+ const std::vector<DbPath>& data_paths) {
+ Status s = DBImpl::CreateAndNewDirectory(env, dbname, &db_dir_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!wal_dir.empty() && dbname != wal_dir) {
+ s = DBImpl::CreateAndNewDirectory(env, wal_dir, &wal_dir_);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ data_dirs_.clear();
+ for (auto& p : data_paths) {
+ const std::string db_path = p.path;
+ if (db_path == dbname) {
+ data_dirs_.emplace_back(nullptr);
+ } else {
+ std::unique_ptr<Directory> path_directory;
+ s = DBImpl::CreateAndNewDirectory(env, db_path, &path_directory);
+ if (!s.ok()) {
+ return s;
+ }
+ data_dirs_.emplace_back(path_directory.release());
+ }
+ }
+ assert(data_dirs_.size() == data_paths.size());
+ return Status::OK();
+}
+
+Status DBImpl::Recover(
+ const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
+ bool error_if_log_file_exist, bool error_if_data_exists_in_logs,
+ uint64_t* recovered_seq) {
+ mutex_.AssertHeld();
+
+ bool is_new_db = false;
+ assert(db_lock_ == nullptr);
+ if (!read_only) {
+ Status s = directories_.SetDirectories(env_, dbname_,
+ immutable_db_options_.wal_dir,
+ immutable_db_options_.db_paths);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = env_->LockFile(LockFileName(dbname_), &db_lock_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::string current_fname = CurrentFileName(dbname_);
+ s = env_->FileExists(current_fname);
+ if (s.IsNotFound()) {
+ if (immutable_db_options_.create_if_missing) {
+ s = NewDB();
+ is_new_db = true;
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ return Status::InvalidArgument(
+ current_fname, "does not exist (create_if_missing is false)");
+ }
+ } else if (s.ok()) {
+ if (immutable_db_options_.error_if_exists) {
+ return Status::InvalidArgument(dbname_,
+ "exists (error_if_exists is true)");
+ }
+ } else {
+ // Unexpected error reading file
+ assert(s.IsIOError());
+ return s;
+ }
+ // Verify compatibility of file_options_ and filesystem
+ {
+ std::unique_ptr<FSRandomAccessFile> idfile;
+ FileOptions customized_fs(file_options_);
+ customized_fs.use_direct_reads |=
+ immutable_db_options_.use_direct_io_for_flush_and_compaction;
+ s = fs_->NewRandomAccessFile(current_fname, customized_fs, &idfile,
+ nullptr);
+ if (!s.ok()) {
+ std::string error_str = s.ToString();
+ // Check if unsupported Direct I/O is the root cause
+ customized_fs.use_direct_reads = false;
+ s = fs_->NewRandomAccessFile(current_fname, customized_fs, &idfile,
+ nullptr);
+ if (s.ok()) {
+ return Status::InvalidArgument(
+ "Direct I/O is not supported by the specified DB.");
+ } else {
+ return Status::InvalidArgument(
+ "Found options incompatible with filesystem", error_str.c_str());
+ }
+ }
+ }
+ }
+ assert(db_id_.empty());
+ Status s = versions_->Recover(column_families, read_only, &db_id_);
+ if (!s.ok()) {
+ return s;
+ }
+ // Happens when immutable_db_options_.write_dbid_to_manifest is set to true
+ // the very first time.
+ if (db_id_.empty()) {
+ // Check for the IDENTITY file and create it if not there.
+ s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
+ // Typically Identity file is created in NewDB() and for some reason if
+ // it is no longer available then at this point DB ID is not in Identity
+ // file or Manifest.
+ if (s.IsNotFound()) {
+ s = SetIdentityFile(env_, dbname_);
+ if (!s.ok()) {
+ return s;
+ }
+ } else if (!s.ok()) {
+ assert(s.IsIOError());
+ return s;
+ }
+ s = GetDbIdentityFromIdentityFile(&db_id_);
+ if (immutable_db_options_.write_dbid_to_manifest && s.ok()) {
+ VersionEdit edit;
+ edit.SetDBId(db_id_);
+ Options options;
+ MutableCFOptions mutable_cf_options(options);
+ versions_->db_id_ = db_id_;
+ s = versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
+ mutable_cf_options, &edit, &mutex_, nullptr,
+ false);
+ }
+ } else {
+ s = SetIdentityFile(env_, dbname_, db_id_);
+ }
+
+ if (immutable_db_options_.paranoid_checks && s.ok()) {
+ s = CheckConsistency();
+ }
+ if (s.ok() && !read_only) {
+ std::map<std::string, std::shared_ptr<Directory>> created_dirs;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ s = cfd->AddDirectories(&created_dirs);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+ // DB mutex is already held
+ if (s.ok() && immutable_db_options_.persist_stats_to_disk) {
+ s = InitPersistStatsColumnFamily();
+ }
+
+ if (s.ok()) {
+ // Initial max_total_in_memory_state_ before recovery logs. Log recovery
+ // may check this value to decide whether to flush.
+ max_total_in_memory_state_ = 0;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
+ max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
+ mutable_cf_options->max_write_buffer_number;
+ }
+
+ SequenceNumber next_sequence(kMaxSequenceNumber);
+ default_cf_handle_ = new ColumnFamilyHandleImpl(
+ versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
+ default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
+ // TODO(Zhongyi): handle single_column_family_mode_ when
+ // persistent_stats is enabled
+ single_column_family_mode_ =
+ versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
+
+ // Recover from all newer log files than the ones named in the
+ // descriptor (new log files may have been added by the previous
+ // incarnation without registering them in the descriptor).
+ //
+ // Note that prev_log_number() is no longer used, but we pay
+ // attention to it in case we are recovering a database
+ // produced by an older version of rocksdb.
+ std::vector<std::string> filenames;
+ s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
+ if (s.IsNotFound()) {
+ return Status::InvalidArgument("wal_dir not found",
+ immutable_db_options_.wal_dir);
+ } else if (!s.ok()) {
+ return s;
+ }
+
+ std::vector<uint64_t> logs;
+ for (size_t i = 0; i < filenames.size(); i++) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(filenames[i], &number, &type) && type == kLogFile) {
+ if (is_new_db) {
+ return Status::Corruption(
+ "While creating a new Db, wal_dir contains "
+ "existing log file: ",
+ filenames[i]);
+ } else {
+ logs.push_back(number);
+ }
+ }
+ }
+
+ if (logs.size() > 0) {
+ if (error_if_log_file_exist) {
+ return Status::Corruption(
+ "The db was opened in readonly mode with error_if_log_file_exist"
+ "flag but a log file already exists");
+ } else if (error_if_data_exists_in_logs) {
+ for (auto& log : logs) {
+ std::string fname = LogFileName(immutable_db_options_.wal_dir, log);
+ uint64_t bytes;
+ s = env_->GetFileSize(fname, &bytes);
+ if (s.ok()) {
+ if (bytes > 0) {
+ return Status::Corruption(
+ "error_if_data_exists_in_logs is set but there are data "
+ " in log files.");
+ }
+ }
+ }
+ }
+ }
+
+ if (!logs.empty()) {
+ // Recover in the order in which the logs were generated
+ std::sort(logs.begin(), logs.end());
+ bool corrupted_log_found = false;
+ s = RecoverLogFiles(logs, &next_sequence, read_only,
+ &corrupted_log_found);
+ if (corrupted_log_found && recovered_seq != nullptr) {
+ *recovered_seq = next_sequence;
+ }
+ if (!s.ok()) {
+ // Clear memtables if recovery failed
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
+ kMaxSequenceNumber);
+ }
+ }
+ }
+ }
+
+ if (read_only) {
+ // If we are opening as read-only, we need to update options_file_number_
+ // to reflect the most recent OPTIONS file. It does not matter for regular
+ // read-write db instance because options_file_number_ will later be
+ // updated to versions_->NewFileNumber() in RenameTempFileToOptionsFile.
+ std::vector<std::string> file_names;
+ if (s.ok()) {
+ s = env_->GetChildren(GetName(), &file_names);
+ }
+ if (s.ok()) {
+ uint64_t number = 0;
+ uint64_t options_file_number = 0;
+ FileType type;
+ for (const auto& fname : file_names) {
+ if (ParseFileName(fname, &number, &type) && type == kOptionsFile) {
+ options_file_number = std::max(number, options_file_number);
+ }
+ }
+ versions_->options_file_number_ = options_file_number;
+ }
+ }
+
+ return s;
+}
+
+Status DBImpl::PersistentStatsProcessFormatVersion() {
+ mutex_.AssertHeld();
+ Status s;
+ // persist version when stats CF doesn't exist
+ bool should_persist_format_version = !persistent_stats_cfd_exists_;
+ mutex_.Unlock();
+ if (persistent_stats_cfd_exists_) {
+ // Check persistent stats format version compatibility. Drop and recreate
+ // persistent stats CF if format version is incompatible
+ uint64_t format_version_recovered = 0;
+ Status s_format = DecodePersistentStatsVersionNumber(
+ this, StatsVersionKeyType::kFormatVersion, &format_version_recovered);
+ uint64_t compatible_version_recovered = 0;
+ Status s_compatible = DecodePersistentStatsVersionNumber(
+ this, StatsVersionKeyType::kCompatibleVersion,
+ &compatible_version_recovered);
+ // abort reading from existing stats CF if any of following is true:
+ // 1. failed to read format version or compatible version from disk
+ // 2. sst's format version is greater than current format version, meaning
+ // this sst is encoded with a newer RocksDB release, and current compatible
+ // version is below the sst's compatible version
+ if (!s_format.ok() || !s_compatible.ok() ||
+ (kStatsCFCurrentFormatVersion < format_version_recovered &&
+ kStatsCFCompatibleFormatVersion < compatible_version_recovered)) {
+ if (!s_format.ok() || !s_compatible.ok()) {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "Reading persistent stats version key failed. Format key: %s, "
+ "compatible key: %s",
+ s_format.ToString().c_str(), s_compatible.ToString().c_str());
+ } else {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "Disable persistent stats due to corrupted or incompatible format "
+ "version\n");
+ }
+ DropColumnFamily(persist_stats_cf_handle_);
+ DestroyColumnFamilyHandle(persist_stats_cf_handle_);
+ ColumnFamilyHandle* handle = nullptr;
+ ColumnFamilyOptions cfo;
+ OptimizeForPersistentStats(&cfo);
+ s = CreateColumnFamily(cfo, kPersistentStatsColumnFamilyName, &handle);
+ persist_stats_cf_handle_ = static_cast<ColumnFamilyHandleImpl*>(handle);
+ // should also persist version here because old stats CF is discarded
+ should_persist_format_version = true;
+ }
+ }
+ if (s.ok() && should_persist_format_version) {
+ // Persistent stats CF being created for the first time, need to write
+ // format version key
+ WriteBatch batch;
+ batch.Put(persist_stats_cf_handle_, kFormatVersionKeyString,
+ ToString(kStatsCFCurrentFormatVersion));
+ batch.Put(persist_stats_cf_handle_, kCompatibleVersionKeyString,
+ ToString(kStatsCFCompatibleFormatVersion));
+ WriteOptions wo;
+ wo.low_pri = true;
+ wo.no_slowdown = true;
+ wo.sync = false;
+ s = Write(wo, &batch);
+ }
+ mutex_.Lock();
+ return s;
+}
+
+Status DBImpl::InitPersistStatsColumnFamily() {
+ mutex_.AssertHeld();
+ assert(!persist_stats_cf_handle_);
+ ColumnFamilyData* persistent_stats_cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(
+ kPersistentStatsColumnFamilyName);
+ persistent_stats_cfd_exists_ = persistent_stats_cfd != nullptr;
+
+ Status s;
+ if (persistent_stats_cfd != nullptr) {
+ // We are recovering from a DB which already contains persistent stats CF,
+ // the CF is already created in VersionSet::ApplyOneVersionEdit, but
+ // column family handle was not. Need to explicitly create handle here.
+ persist_stats_cf_handle_ =
+ new ColumnFamilyHandleImpl(persistent_stats_cfd, this, &mutex_);
+ } else {
+ mutex_.Unlock();
+ ColumnFamilyHandle* handle = nullptr;
+ ColumnFamilyOptions cfo;
+ OptimizeForPersistentStats(&cfo);
+ s = CreateColumnFamily(cfo, kPersistentStatsColumnFamilyName, &handle);
+ persist_stats_cf_handle_ = static_cast<ColumnFamilyHandleImpl*>(handle);
+ mutex_.Lock();
+ }
+ return s;
+}
+
+// REQUIRES: log_numbers are sorted in ascending order
+Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
+ SequenceNumber* next_sequence, bool read_only,
+ bool* corrupted_log_found) {
+ struct LogReporter : public log::Reader::Reporter {
+ Env* env;
+ Logger* info_log;
+ const char* fname;
+ Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
+ void Corruption(size_t bytes, const Status& s) override {
+ ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
+ (this->status == nullptr ? "(ignoring error) " : ""),
+ fname, static_cast<int>(bytes), s.ToString().c_str());
+ if (this->status != nullptr && this->status->ok()) {
+ *this->status = s;
+ }
+ }
+ };
+
+ mutex_.AssertHeld();
+ Status status;
+ std::unordered_map<int, VersionEdit> version_edits;
+ // no need to refcount because iteration is under mutex
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ VersionEdit edit;
+ edit.SetColumnFamily(cfd->GetID());
+ version_edits.insert({cfd->GetID(), edit});
+ }
+ int job_id = next_job_id_.fetch_add(1);
+ {
+ auto stream = event_logger_.Log();
+ stream << "job" << job_id << "event"
+ << "recovery_started";
+ stream << "log_files";
+ stream.StartArray();
+ for (auto log_number : log_numbers) {
+ stream << log_number;
+ }
+ stream.EndArray();
+ }
+
+#ifndef ROCKSDB_LITE
+ if (immutable_db_options_.wal_filter != nullptr) {
+ std::map<std::string, uint32_t> cf_name_id_map;
+ std::map<uint32_t, uint64_t> cf_lognumber_map;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ cf_name_id_map.insert(std::make_pair(cfd->GetName(), cfd->GetID()));
+ cf_lognumber_map.insert(
+ std::make_pair(cfd->GetID(), cfd->GetLogNumber()));
+ }
+
+ immutable_db_options_.wal_filter->ColumnFamilyLogNumberMap(cf_lognumber_map,
+ cf_name_id_map);
+ }
+#endif
+
+ bool stop_replay_by_wal_filter = false;
+ bool stop_replay_for_corruption = false;
+ bool flushed = false;
+ uint64_t corrupted_log_number = kMaxSequenceNumber;
+ uint64_t min_log_number = MinLogNumberToKeep();
+ for (auto log_number : log_numbers) {
+ if (log_number < min_log_number) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Skipping log #%" PRIu64
+ " since it is older than min log to keep #%" PRIu64,
+ log_number, min_log_number);
+ continue;
+ }
+ // The previous incarnation may not have written any MANIFEST
+ // records after allocating this log number. So we manually
+ // update the file number allocation counter in VersionSet.
+ versions_->MarkFileNumberUsed(log_number);
+ // Open the log file
+ std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Recovering log #%" PRIu64 " mode %d", log_number,
+ static_cast<int>(immutable_db_options_.wal_recovery_mode));
+ auto logFileDropped = [this, &fname]() {
+ uint64_t bytes;
+ if (env_->GetFileSize(fname, &bytes).ok()) {
+ auto info_log = immutable_db_options_.info_log.get();
+ ROCKS_LOG_WARN(info_log, "%s: dropping %d bytes", fname.c_str(),
+ static_cast<int>(bytes));
+ }
+ };
+ if (stop_replay_by_wal_filter) {
+ logFileDropped();
+ continue;
+ }
+
+ std::unique_ptr<SequentialFileReader> file_reader;
+ {
+ std::unique_ptr<FSSequentialFile> file;
+ status = fs_->NewSequentialFile(fname,
+ fs_->OptimizeForLogRead(file_options_),
+ &file, nullptr);
+ if (!status.ok()) {
+ MaybeIgnoreError(&status);
+ if (!status.ok()) {
+ return status;
+ } else {
+ // Fail with one log file, but that's ok.
+ // Try next one.
+ continue;
+ }
+ }
+ file_reader.reset(new SequentialFileReader(
+ std::move(file), fname, immutable_db_options_.log_readahead_size));
+ }
+
+ // Create the log reader.
+ LogReporter reporter;
+ reporter.env = env_;
+ reporter.info_log = immutable_db_options_.info_log.get();
+ reporter.fname = fname.c_str();
+ if (!immutable_db_options_.paranoid_checks ||
+ immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kSkipAnyCorruptedRecords) {
+ reporter.status = nullptr;
+ } else {
+ reporter.status = &status;
+ }
+ // We intentially make log::Reader do checksumming even if
+ // paranoid_checks==false so that corruptions cause entire commits
+ // to be skipped instead of propagating bad information (like overly
+ // large sequence numbers).
+ log::Reader reader(immutable_db_options_.info_log, std::move(file_reader),
+ &reporter, true /*checksum*/, log_number);
+
+ // Determine if we should tolerate incomplete records at the tail end of the
+ // Read all the records and add to a memtable
+ std::string scratch;
+ Slice record;
+ WriteBatch batch;
+
+ while (!stop_replay_by_wal_filter &&
+ reader.ReadRecord(&record, &scratch,
+ immutable_db_options_.wal_recovery_mode) &&
+ status.ok()) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter.Corruption(record.size(),
+ Status::Corruption("log record too small"));
+ continue;
+ }
+ WriteBatchInternal::SetContents(&batch, record);
+ SequenceNumber sequence = WriteBatchInternal::Sequence(&batch);
+
+ if (immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kPointInTimeRecovery) {
+ // In point-in-time recovery mode, if sequence id of log files are
+ // consecutive, we continue recovery despite corruption. This could
+ // happen when we open and write to a corrupted DB, where sequence id
+ // will start from the last sequence id we recovered.
+ if (sequence == *next_sequence) {
+ stop_replay_for_corruption = false;
+ }
+ if (stop_replay_for_corruption) {
+ logFileDropped();
+ break;
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ if (immutable_db_options_.wal_filter != nullptr) {
+ WriteBatch new_batch;
+ bool batch_changed = false;
+
+ WalFilter::WalProcessingOption wal_processing_option =
+ immutable_db_options_.wal_filter->LogRecordFound(
+ log_number, fname, batch, &new_batch, &batch_changed);
+
+ switch (wal_processing_option) {
+ case WalFilter::WalProcessingOption::kContinueProcessing:
+ // do nothing, proceeed normally
+ break;
+ case WalFilter::WalProcessingOption::kIgnoreCurrentRecord:
+ // skip current record
+ continue;
+ case WalFilter::WalProcessingOption::kStopReplay:
+ // skip current record and stop replay
+ stop_replay_by_wal_filter = true;
+ continue;
+ case WalFilter::WalProcessingOption::kCorruptedRecord: {
+ status =
+ Status::Corruption("Corruption reported by Wal Filter ",
+ immutable_db_options_.wal_filter->Name());
+ MaybeIgnoreError(&status);
+ if (!status.ok()) {
+ reporter.Corruption(record.size(), status);
+ continue;
+ }
+ break;
+ }
+ default: {
+ assert(false); // unhandled case
+ status = Status::NotSupported(
+ "Unknown WalProcessingOption returned"
+ " by Wal Filter ",
+ immutable_db_options_.wal_filter->Name());
+ MaybeIgnoreError(&status);
+ if (!status.ok()) {
+ return status;
+ } else {
+ // Ignore the error with current record processing.
+ continue;
+ }
+ }
+ }
+
+ if (batch_changed) {
+ // Make sure that the count in the new batch is
+ // within the orignal count.
+ int new_count = WriteBatchInternal::Count(&new_batch);
+ int original_count = WriteBatchInternal::Count(&batch);
+ if (new_count > original_count) {
+ ROCKS_LOG_FATAL(
+ immutable_db_options_.info_log,
+ "Recovering log #%" PRIu64
+ " mode %d log filter %s returned "
+ "more records (%d) than original (%d) which is not allowed. "
+ "Aborting recovery.",
+ log_number,
+ static_cast<int>(immutable_db_options_.wal_recovery_mode),
+ immutable_db_options_.wal_filter->Name(), new_count,
+ original_count);
+ status = Status::NotSupported(
+ "More than original # of records "
+ "returned by Wal Filter ",
+ immutable_db_options_.wal_filter->Name());
+ return status;
+ }
+ // Set the same sequence number in the new_batch
+ // as the original batch.
+ WriteBatchInternal::SetSequence(&new_batch,
+ WriteBatchInternal::Sequence(&batch));
+ batch = new_batch;
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ // If column family was not found, it might mean that the WAL write
+ // batch references to the column family that was dropped after the
+ // insert. We don't want to fail the whole write batch in that case --
+ // we just ignore the update.
+ // That's why we set ignore missing column families to true
+ bool has_valid_writes = false;
+ status = WriteBatchInternal::InsertInto(
+ &batch, column_family_memtables_.get(), &flush_scheduler_,
+ &trim_history_scheduler_, true, log_number, this,
+ false /* concurrent_memtable_writes */, next_sequence,
+ &has_valid_writes, seq_per_batch_, batch_per_txn_);
+ MaybeIgnoreError(&status);
+ if (!status.ok()) {
+ // We are treating this as a failure while reading since we read valid
+ // blocks that do not form coherent data
+ reporter.Corruption(record.size(), status);
+ continue;
+ }
+
+ if (has_valid_writes && !read_only) {
+ // we can do this because this is called before client has access to the
+ // DB and there is only a single thread operating on DB
+ ColumnFamilyData* cfd;
+
+ while ((cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
+ cfd->UnrefAndTryDelete();
+ // If this asserts, it means that InsertInto failed in
+ // filtering updates to already-flushed column families
+ assert(cfd->GetLogNumber() <= log_number);
+ auto iter = version_edits.find(cfd->GetID());
+ assert(iter != version_edits.end());
+ VersionEdit* edit = &iter->second;
+ status = WriteLevel0TableForRecovery(job_id, cfd, cfd->mem(), edit);
+ if (!status.ok()) {
+ // Reflect errors immediately so that conditions like full
+ // file-systems cause the DB::Open() to fail.
+ return status;
+ }
+ flushed = true;
+
+ cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
+ *next_sequence);
+ }
+ }
+ }
+
+ if (!status.ok()) {
+ if (status.IsNotSupported()) {
+ // We should not treat NotSupported as corruption. It is rather a clear
+ // sign that we are processing a WAL that is produced by an incompatible
+ // version of the code.
+ return status;
+ }
+ if (immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kSkipAnyCorruptedRecords) {
+ // We should ignore all errors unconditionally
+ status = Status::OK();
+ } else if (immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kPointInTimeRecovery) {
+ // We should ignore the error but not continue replaying
+ status = Status::OK();
+ stop_replay_for_corruption = true;
+ corrupted_log_number = log_number;
+ if (corrupted_log_found != nullptr) {
+ *corrupted_log_found = true;
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Point in time recovered to log #%" PRIu64
+ " seq #%" PRIu64,
+ log_number, *next_sequence);
+ } else {
+ assert(immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kTolerateCorruptedTailRecords ||
+ immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kAbsoluteConsistency);
+ return status;
+ }
+ }
+
+ flush_scheduler_.Clear();
+ trim_history_scheduler_.Clear();
+ auto last_sequence = *next_sequence - 1;
+ if ((*next_sequence != kMaxSequenceNumber) &&
+ (versions_->LastSequence() <= last_sequence)) {
+ versions_->SetLastAllocatedSequence(last_sequence);
+ versions_->SetLastPublishedSequence(last_sequence);
+ versions_->SetLastSequence(last_sequence);
+ }
+ }
+ // Compare the corrupted log number to all columnfamily's current log number.
+ // Abort Open() if any column family's log number is greater than
+ // the corrupted log number, which means CF contains data beyond the point of
+ // corruption. This could during PIT recovery when the WAL is corrupted and
+ // some (but not all) CFs are flushed
+ // Exclude the PIT case where no log is dropped after the corruption point.
+ // This is to cover the case for empty logs after corrupted log, in which we
+ // don't reset stop_replay_for_corruption.
+ if (stop_replay_for_corruption == true &&
+ (immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kPointInTimeRecovery ||
+ immutable_db_options_.wal_recovery_mode ==
+ WALRecoveryMode::kTolerateCorruptedTailRecords)) {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->GetLogNumber() > corrupted_log_number) {
+ ROCKS_LOG_ERROR(immutable_db_options_.info_log,
+ "Column family inconsistency: SST file contains data"
+ " beyond the point of corruption.");
+ return Status::Corruption("SST file is ahead of WALs");
+ }
+ }
+ }
+
+ // True if there's any data in the WALs; if not, we can skip re-processing
+ // them later
+ bool data_seen = false;
+ if (!read_only) {
+ // no need to refcount since client still doesn't have access
+ // to the DB and can not drop column families while we iterate
+ auto max_log_number = log_numbers.back();
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ auto iter = version_edits.find(cfd->GetID());
+ assert(iter != version_edits.end());
+ VersionEdit* edit = &iter->second;
+
+ if (cfd->GetLogNumber() > max_log_number) {
+ // Column family cfd has already flushed the data
+ // from all logs. Memtable has to be empty because
+ // we filter the updates based on log_number
+ // (in WriteBatch::InsertInto)
+ assert(cfd->mem()->GetFirstSequenceNumber() == 0);
+ assert(edit->NumEntries() == 0);
+ continue;
+ }
+
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImpl::RecoverLogFiles:BeforeFlushFinalMemtable", /*arg=*/nullptr);
+
+ // flush the final memtable (if non-empty)
+ if (cfd->mem()->GetFirstSequenceNumber() != 0) {
+ // If flush happened in the middle of recovery (e.g. due to memtable
+ // being full), we flush at the end. Otherwise we'll need to record
+ // where we were on last flush, which make the logic complicated.
+ if (flushed || !immutable_db_options_.avoid_flush_during_recovery) {
+ status = WriteLevel0TableForRecovery(job_id, cfd, cfd->mem(), edit);
+ if (!status.ok()) {
+ // Recovery failed
+ break;
+ }
+ flushed = true;
+
+ cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
+ versions_->LastSequence());
+ }
+ data_seen = true;
+ }
+
+ // Update the log number info in the version edit corresponding to this
+ // column family. Note that the version edits will be written to MANIFEST
+ // together later.
+ // writing log_number in the manifest means that any log file
+ // with number strongly less than (log_number + 1) is already
+ // recovered and should be ignored on next reincarnation.
+ // Since we already recovered max_log_number, we want all logs
+ // with numbers `<= max_log_number` (includes this one) to be ignored
+ if (flushed || cfd->mem()->GetFirstSequenceNumber() == 0) {
+ edit->SetLogNumber(max_log_number + 1);
+ }
+ }
+ if (status.ok()) {
+ // we must mark the next log number as used, even though it's
+ // not actually used. that is because VersionSet assumes
+ // VersionSet::next_file_number_ always to be strictly greater than any
+ // log number
+ versions_->MarkFileNumberUsed(max_log_number + 1);
+
+ autovector<ColumnFamilyData*> cfds;
+ autovector<const MutableCFOptions*> cf_opts;
+ autovector<autovector<VersionEdit*>> edit_lists;
+ for (auto* cfd : *versions_->GetColumnFamilySet()) {
+ cfds.push_back(cfd);
+ cf_opts.push_back(cfd->GetLatestMutableCFOptions());
+ auto iter = version_edits.find(cfd->GetID());
+ assert(iter != version_edits.end());
+ edit_lists.push_back({&iter->second});
+ }
+ // write MANIFEST with update
+ status = versions_->LogAndApply(cfds, cf_opts, edit_lists, &mutex_,
+ directories_.GetDbDir(),
+ /*new_descriptor_log=*/true);
+ }
+ }
+
+ if (status.ok() && data_seen && !flushed) {
+ status = RestoreAliveLogFiles(log_numbers);
+ }
+
+ event_logger_.Log() << "job" << job_id << "event"
+ << "recovery_finished";
+
+ return status;
+}
+
+Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers) {
+ if (log_numbers.empty()) {
+ return Status::OK();
+ }
+ Status s;
+ mutex_.AssertHeld();
+ assert(immutable_db_options_.avoid_flush_during_recovery);
+ if (two_write_queues_) {
+ log_write_mutex_.Lock();
+ }
+ // Mark these as alive so they'll be considered for deletion later by
+ // FindObsoleteFiles()
+ total_log_size_ = 0;
+ log_empty_ = false;
+ for (auto log_number : log_numbers) {
+ LogFileNumberSize log(log_number);
+ std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
+ // This gets the appear size of the logs, not including preallocated space.
+ s = env_->GetFileSize(fname, &log.size);
+ if (!s.ok()) {
+ break;
+ }
+ total_log_size_ += log.size;
+ alive_log_files_.push_back(log);
+ // We preallocate space for logs, but then after a crash and restart, those
+ // preallocated space are not needed anymore. It is likely only the last
+ // log has such preallocated space, so we only truncate for the last log.
+ if (log_number == log_numbers.back()) {
+ std::unique_ptr<FSWritableFile> last_log;
+ Status truncate_status = fs_->ReopenWritableFile(
+ fname,
+ fs_->OptimizeForLogWrite(
+ file_options_,
+ BuildDBOptions(immutable_db_options_, mutable_db_options_)),
+ &last_log, nullptr);
+ if (truncate_status.ok()) {
+ truncate_status = last_log->Truncate(log.size, IOOptions(), nullptr);
+ }
+ if (truncate_status.ok()) {
+ truncate_status = last_log->Close(IOOptions(), nullptr);
+ }
+ // Not a critical error if fail to truncate.
+ if (!truncate_status.ok()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "Failed to truncate log #%" PRIu64 ": %s", log_number,
+ truncate_status.ToString().c_str());
+ }
+ }
+ }
+ if (two_write_queues_) {
+ log_write_mutex_.Unlock();
+ }
+ return s;
+}
+
+Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
+ MemTable* mem, VersionEdit* edit) {
+ mutex_.AssertHeld();
+ const uint64_t start_micros = env_->NowMicros();
+ FileMetaData meta;
+ std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
+ new std::list<uint64_t>::iterator(
+ CaptureCurrentFileNumberInPendingOutputs()));
+ meta.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ Arena arena;
+ Status s;
+ TableProperties table_properties;
+ {
+ ScopedArenaIterator iter(mem->NewIterator(ro, &arena));
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] [WriteLevel0TableForRecovery]"
+ " Level-0 table #%" PRIu64 ": started",
+ cfd->GetName().c_str(), meta.fd.GetNumber());
+
+ // Get the latest mutable cf options while the mutex is still locked
+ const MutableCFOptions mutable_cf_options =
+ *cfd->GetLatestMutableCFOptions();
+ bool paranoid_file_checks =
+ cfd->GetLatestMutableCFOptions()->paranoid_file_checks;
+
+ int64_t _current_time = 0;
+ env_->GetCurrentTime(&_current_time); // ignore error
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+ meta.oldest_ancester_time = current_time;
+
+ {
+ auto write_hint = cfd->CalculateSSTWriteHint(0);
+ mutex_.Unlock();
+
+ SequenceNumber earliest_write_conflict_snapshot;
+ std::vector<SequenceNumber> snapshot_seqs =
+ snapshots_.GetAll(&earliest_write_conflict_snapshot);
+ auto snapshot_checker = snapshot_checker_.get();
+ if (use_custom_gc_ && snapshot_checker == nullptr) {
+ snapshot_checker = DisableGCSnapshotChecker::Instance();
+ }
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ range_del_iters;
+ auto range_del_iter =
+ mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
+ if (range_del_iter != nullptr) {
+ range_del_iters.emplace_back(range_del_iter);
+ }
+ s = BuildTable(
+ dbname_, env_, fs_.get(), *cfd->ioptions(), mutable_cf_options,
+ file_options_for_compaction_, cfd->table_cache(), iter.get(),
+ std::move(range_del_iters), &meta, cfd->internal_comparator(),
+ cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
+ snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
+ GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
+ mutable_cf_options.sample_for_compression,
+ cfd->ioptions()->compression_opts, paranoid_file_checks,
+ cfd->internal_stats(), TableFileCreationReason::kRecovery,
+ &event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
+ -1 /* level */, current_time, write_hint);
+ LogFlush(immutable_db_options_.info_log);
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] [WriteLevel0TableForRecovery]"
+ " Level-0 table #%" PRIu64 ": %" PRIu64 " bytes %s",
+ cfd->GetName().c_str(), meta.fd.GetNumber(),
+ meta.fd.GetFileSize(), s.ToString().c_str());
+ mutex_.Lock();
+ }
+ }
+ ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
+
+ // Note that if file_size is zero, the file has been deleted and
+ // should not be added to the manifest.
+ int level = 0;
+ if (s.ok() && meta.fd.GetFileSize() > 0) {
+ edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(),
+ meta.fd.GetFileSize(), meta.smallest, meta.largest,
+ meta.fd.smallest_seqno, meta.fd.largest_seqno,
+ meta.marked_for_compaction, meta.oldest_blob_file_number,
+ meta.oldest_ancester_time, meta.file_creation_time,
+ meta.file_checksum, meta.file_checksum_func_name);
+ }
+
+ InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
+ stats.micros = env_->NowMicros() - start_micros;
+ stats.bytes_written = meta.fd.GetFileSize();
+ stats.num_output_files = 1;
+ cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
+ cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
+ meta.fd.GetFileSize());
+ RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
+ return s;
+}
+
+Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ if (db_options.persist_stats_to_disk) {
+ column_families.push_back(
+ ColumnFamilyDescriptor(kPersistentStatsColumnFamilyName, cf_options));
+ }
+ std::vector<ColumnFamilyHandle*> handles;
+ Status s = DB::Open(db_options, dbname, column_families, &handles, dbptr);
+ if (s.ok()) {
+ if (db_options.persist_stats_to_disk) {
+ assert(handles.size() == 2);
+ } else {
+ assert(handles.size() == 1);
+ }
+ // i can delete the handle since DBImpl is always holding a reference to
+ // default column family
+ if (db_options.persist_stats_to_disk && handles[1] != nullptr) {
+ delete handles[1];
+ }
+ delete handles[0];
+ }
+ return s;
+}
+
+Status DB::Open(const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
+ const bool kSeqPerBatch = true;
+ const bool kBatchPerTxn = true;
+ return DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
+ !kSeqPerBatch, kBatchPerTxn);
+}
+
+Status DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
+ size_t preallocate_block_size, log::Writer** new_log) {
+ Status s;
+ std::unique_ptr<FSWritableFile> lfile;
+
+ DBOptions db_options =
+ BuildDBOptions(immutable_db_options_, mutable_db_options_);
+ FileOptions opt_file_options =
+ fs_->OptimizeForLogWrite(file_options_, db_options);
+ std::string log_fname =
+ LogFileName(immutable_db_options_.wal_dir, log_file_num);
+
+ if (recycle_log_number) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "reusing log %" PRIu64 " from recycle list\n",
+ recycle_log_number);
+ std::string old_log_fname =
+ LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
+ TEST_SYNC_POINT("DBImpl::CreateWAL:BeforeReuseWritableFile1");
+ TEST_SYNC_POINT("DBImpl::CreateWAL:BeforeReuseWritableFile2");
+ s = fs_->ReuseWritableFile(log_fname, old_log_fname, opt_file_options,
+ &lfile, /*dbg=*/nullptr);
+ } else {
+ s = NewWritableFile(fs_.get(), log_fname, &lfile, opt_file_options);
+ }
+
+ if (s.ok()) {
+ lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
+ lfile->SetPreallocationBlockSize(preallocate_block_size);
+
+ const auto& listeners = immutable_db_options_.listeners;
+ std::unique_ptr<WritableFileWriter> file_writer(
+ new WritableFileWriter(std::move(lfile), log_fname, opt_file_options,
+ env_, nullptr /* stats */, listeners));
+ *new_log = new log::Writer(std::move(file_writer), log_file_num,
+ immutable_db_options_.recycle_log_file_num > 0,
+ immutable_db_options_.manual_wal_flush);
+ }
+ return s;
+}
+
+Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
+ const bool seq_per_batch, const bool batch_per_txn) {
+ Status s = SanitizeOptionsByTable(db_options, column_families);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = ValidateOptions(db_options, column_families);
+ if (!s.ok()) {
+ return s;
+ }
+
+ *dbptr = nullptr;
+ handles->clear();
+
+ size_t max_write_buffer_size = 0;
+ for (auto cf : column_families) {
+ max_write_buffer_size =
+ std::max(max_write_buffer_size, cf.options.write_buffer_size);
+ }
+
+ DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch, batch_per_txn);
+ s = impl->env_->CreateDirIfMissing(impl->immutable_db_options_.wal_dir);
+ if (s.ok()) {
+ std::vector<std::string> paths;
+ for (auto& db_path : impl->immutable_db_options_.db_paths) {
+ paths.emplace_back(db_path.path);
+ }
+ for (auto& cf : column_families) {
+ for (auto& cf_path : cf.options.cf_paths) {
+ paths.emplace_back(cf_path.path);
+ }
+ }
+ for (auto& path : paths) {
+ s = impl->env_->CreateDirIfMissing(path);
+ if (!s.ok()) {
+ break;
+ }
+ }
+
+ // For recovery from NoSpace() error, we can only handle
+ // the case where the database is stored in a single path
+ if (paths.size() <= 1) {
+ impl->error_handler_.EnableAutoRecovery();
+ }
+ }
+
+ if (!s.ok()) {
+ delete impl;
+ return s;
+ }
+
+ s = impl->CreateArchivalDirectory();
+ if (!s.ok()) {
+ delete impl;
+ return s;
+ }
+
+ impl->wal_in_db_path_ = IsWalDirSameAsDBPath(&impl->immutable_db_options_);
+
+ impl->mutex_.Lock();
+ // Handles create_if_missing, error_if_exists
+ uint64_t recovered_seq(kMaxSequenceNumber);
+ s = impl->Recover(column_families, false, false, false, &recovered_seq);
+ if (s.ok()) {
+ uint64_t new_log_number = impl->versions_->NewFileNumber();
+ log::Writer* new_log = nullptr;
+ const size_t preallocate_block_size =
+ impl->GetWalPreallocateBlockSize(max_write_buffer_size);
+ s = impl->CreateWAL(new_log_number, 0 /*recycle_log_number*/,
+ preallocate_block_size, &new_log);
+ if (s.ok()) {
+ InstrumentedMutexLock wl(&impl->log_write_mutex_);
+ impl->logfile_number_ = new_log_number;
+ assert(new_log != nullptr);
+ impl->logs_.emplace_back(new_log_number, new_log);
+ }
+
+ if (s.ok()) {
+ // set column family handles
+ for (auto cf : column_families) {
+ auto cfd =
+ impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
+ if (cfd != nullptr) {
+ handles->push_back(
+ new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
+ impl->NewThreadStatusCfInfo(cfd);
+ } else {
+ if (db_options.create_missing_column_families) {
+ // missing column family, create it
+ ColumnFamilyHandle* handle;
+ impl->mutex_.Unlock();
+ s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
+ impl->mutex_.Lock();
+ if (s.ok()) {
+ handles->push_back(handle);
+ } else {
+ break;
+ }
+ } else {
+ s = Status::InvalidArgument("Column family not found: ", cf.name);
+ break;
+ }
+ }
+ }
+ }
+ if (s.ok()) {
+ SuperVersionContext sv_context(/* create_superversion */ true);
+ for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
+ impl->InstallSuperVersionAndScheduleWork(
+ cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
+ }
+ sv_context.Clean();
+ if (impl->two_write_queues_) {
+ impl->log_write_mutex_.Lock();
+ }
+ impl->alive_log_files_.push_back(
+ DBImpl::LogFileNumberSize(impl->logfile_number_));
+ if (impl->two_write_queues_) {
+ impl->log_write_mutex_.Unlock();
+ }
+
+ impl->DeleteObsoleteFiles();
+ s = impl->directories_.GetDbDir()->Fsync();
+ }
+ if (s.ok()) {
+ // In WritePrepared there could be gap in sequence numbers. This breaks
+ // the trick we use in kPointInTimeRecovery which assumes the first seq in
+ // the log right after the corrupted log is one larger than the last seq
+ // we read from the logs. To let this trick keep working, we add a dummy
+ // entry with the expected sequence to the first log right after recovery.
+ // In non-WritePrepared case also the new log after recovery could be
+ // empty, and thus missing the consecutive seq hint to distinguish
+ // middle-log corruption to corrupted-log-remained-after-recovery. This
+ // case also will be addressed by a dummy write.
+ if (recovered_seq != kMaxSequenceNumber) {
+ WriteBatch empty_batch;
+ WriteBatchInternal::SetSequence(&empty_batch, recovered_seq);
+ WriteOptions write_options;
+ uint64_t log_used, log_size;
+ log::Writer* log_writer = impl->logs_.back().writer;
+ s = impl->WriteToWAL(empty_batch, log_writer, &log_used, &log_size);
+ if (s.ok()) {
+ // Need to fsync, otherwise it might get lost after a power reset.
+ s = impl->FlushWAL(false);
+ if (s.ok()) {
+ s = log_writer->file()->Sync(impl->immutable_db_options_.use_fsync);
+ }
+ }
+ }
+ }
+ }
+ if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
+ // try to read format version but no need to fail Open() even if it fails
+ s = impl->PersistentStatsProcessFormatVersion();
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
+ if (cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
+ auto* vstorage = cfd->current()->storage_info();
+ for (int i = 1; i < vstorage->num_levels(); ++i) {
+ int num_files = vstorage->NumLevelFiles(i);
+ if (num_files > 0) {
+ s = Status::InvalidArgument(
+ "Not all files are at level 0. Cannot "
+ "open with FIFO compaction style.");
+ break;
+ }
+ }
+ }
+ if (!cfd->mem()->IsSnapshotSupported()) {
+ impl->is_snapshot_supported_ = false;
+ }
+ if (cfd->ioptions()->merge_operator != nullptr &&
+ !cfd->mem()->IsMergeOperatorSupported()) {
+ s = Status::InvalidArgument(
+ "The memtable of column family %s does not support merge operator "
+ "its options.merge_operator is non-null",
+ cfd->GetName().c_str());
+ }
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ TEST_SYNC_POINT("DBImpl::Open:Opened");
+ Status persist_options_status;
+ if (s.ok()) {
+ // Persist RocksDB Options before scheduling the compaction.
+ // The WriteOptionsFile() will release and lock the mutex internally.
+ persist_options_status = impl->WriteOptionsFile(
+ false /*need_mutex_lock*/, false /*need_enter_write_thread*/);
+
+ *dbptr = impl;
+ impl->opened_successfully_ = true;
+ impl->MaybeScheduleFlushOrCompaction();
+ }
+ impl->mutex_.Unlock();
+
+#ifndef ROCKSDB_LITE
+ auto sfm = static_cast<SstFileManagerImpl*>(
+ impl->immutable_db_options_.sst_file_manager.get());
+ if (s.ok() && sfm) {
+ // Notify SstFileManager about all sst files that already exist in
+ // db_paths[0] and cf_paths[0] when the DB is opened.
+
+ // SstFileManagerImpl needs to know sizes of the files. For files whose size
+ // we already know (sst files that appear in manifest - typically that's the
+ // vast majority of all files), we'll pass the size to SstFileManager.
+ // For all other files SstFileManager will query the size from filesystem.
+
+ std::vector<LiveFileMetaData> metadata;
+
+ impl->mutex_.Lock();
+ impl->versions_->GetLiveFilesMetaData(&metadata);
+ impl->mutex_.Unlock();
+
+ std::unordered_map<std::string, uint64_t> known_file_sizes;
+ for (const auto& md : metadata) {
+ std::string name = md.name;
+ if (!name.empty() && name[0] == '/') {
+ name = name.substr(1);
+ }
+ known_file_sizes[name] = md.size;
+ }
+
+ std::vector<std::string> paths;
+ paths.emplace_back(impl->immutable_db_options_.db_paths[0].path);
+ for (auto& cf : column_families) {
+ if (!cf.options.cf_paths.empty()) {
+ paths.emplace_back(cf.options.cf_paths[0].path);
+ }
+ }
+ // Remove duplicate paths.
+ std::sort(paths.begin(), paths.end());
+ paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
+ for (auto& path : paths) {
+ std::vector<std::string> existing_files;
+ impl->immutable_db_options_.env->GetChildren(path, &existing_files);
+ for (auto& file_name : existing_files) {
+ uint64_t file_number;
+ FileType file_type;
+ std::string file_path = path + "/" + file_name;
+ if (ParseFileName(file_name, &file_number, &file_type) &&
+ file_type == kTableFile) {
+ if (known_file_sizes.count(file_name)) {
+ // We're assuming that each sst file name exists in at most one of
+ // the paths.
+ sfm->OnAddFile(file_path, known_file_sizes.at(file_name),
+ /* compaction */ false);
+ } else {
+ sfm->OnAddFile(file_path);
+ }
+ }
+ }
+ }
+
+ // Reserve some disk buffer space. This is a heuristic - when we run out
+ // of disk space, this ensures that there is atleast write_buffer_size
+ // amount of free space before we resume DB writes. In low disk space
+ // conditions, we want to avoid a lot of small L0 files due to frequent
+ // WAL write failures and resultant forced flushes
+ sfm->ReserveDiskBuffer(max_write_buffer_size,
+ impl->immutable_db_options_.db_paths[0].path);
+ }
+#endif // !ROCKSDB_LITE
+
+ if (s.ok()) {
+ ROCKS_LOG_HEADER(impl->immutable_db_options_.info_log, "DB pointer %p",
+ impl);
+ LogFlush(impl->immutable_db_options_.info_log);
+ assert(impl->TEST_WALBufferIsEmpty());
+ // If the assert above fails then we need to FlushWAL before returning
+ // control back to the user.
+ if (!persist_options_status.ok()) {
+ s = Status::IOError(
+ "DB::Open() failed --- Unable to persist Options file",
+ persist_options_status.ToString());
+ }
+ }
+ if (s.ok()) {
+ impl->StartTimedTasks();
+ }
+ if (!s.ok()) {
+ for (auto* h : *handles) {
+ delete h;
+ }
+ handles->clear();
+ delete impl;
+ *dbptr = nullptr;
+ }
+ return s;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.cc
new file mode 100644
index 00000000..a4242bfe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_readonly.h"
+#include "db/arena_wrapped_db_iter.h"
+
+#include "db/compacted_db_impl.h"
+#include "db/db_impl/db_impl.h"
+#include "db/db_iter.h"
+#include "db/merge_context.h"
+#include "monitoring/perf_context_imp.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+DBImplReadOnly::DBImplReadOnly(const DBOptions& db_options,
+ const std::string& dbname)
+ : DBImpl(db_options, dbname) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Opening the db in read only mode");
+ LogFlush(immutable_db_options_.info_log);
+}
+
+DBImplReadOnly::~DBImplReadOnly() {}
+
+// Implementations of the DB interface
+Status DBImplReadOnly::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* pinnable_val) {
+ assert(pinnable_val != nullptr);
+ // TODO: stopwatch DB_GET needed?, perf timer needed?
+ PERF_TIMER_GUARD(get_snapshot_time);
+ Status s;
+ SequenceNumber snapshot = versions_->LastSequence();
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ if (tracer_) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ tracer_->Get(column_family, key);
+ }
+ }
+ SuperVersion* super_version = cfd->GetSuperVersion();
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq = 0;
+ LookupKey lkey(key, snapshot);
+ PERF_TIMER_STOP(get_snapshot_time);
+ if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
+ &max_covering_tombstone_seq, read_options)) {
+ pinnable_val->PinSelf();
+ RecordTick(stats_, MEMTABLE_HIT);
+ } else {
+ PERF_TIMER_GUARD(get_from_output_files_time);
+ super_version->current->Get(read_options, lkey, pinnable_val, &s,
+ &merge_context, &max_covering_tombstone_seq);
+ RecordTick(stats_, MEMTABLE_MISS);
+ }
+ RecordTick(stats_, NUMBER_KEYS_READ);
+ size_t size = pinnable_val->size();
+ RecordTick(stats_, BYTES_READ, size);
+ RecordInHistogram(stats_, BYTES_PER_READ, size);
+ PERF_COUNTER_ADD(get_read_bytes, size);
+ return s;
+}
+
+Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ SuperVersion* super_version = cfd->GetSuperVersion()->Ref();
+ SequenceNumber latest_snapshot = versions_->LastSequence();
+ SequenceNumber read_seq =
+ read_options.snapshot != nullptr
+ ? reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)
+ ->number_
+ : latest_snapshot;
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ auto db_iter = NewArenaWrappedDbIterator(
+ env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options,
+ read_seq,
+ super_version->mutable_cf_options.max_sequential_skip_in_iterations,
+ super_version->version_number, read_callback);
+ auto internal_iter =
+ NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
+ db_iter->GetRangeDelAggregator(), read_seq);
+ db_iter->SetIterUnderDBIter(internal_iter);
+ return db_iter;
+}
+
+Status DBImplReadOnly::NewIterators(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) {
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ if (iterators == nullptr) {
+ return Status::InvalidArgument("iterators not allowed to be nullptr");
+ }
+ iterators->clear();
+ iterators->reserve(column_families.size());
+ SequenceNumber latest_snapshot = versions_->LastSequence();
+ SequenceNumber read_seq =
+ read_options.snapshot != nullptr
+ ? reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)
+ ->number_
+ : latest_snapshot;
+
+ for (auto cfh : column_families) {
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
+ auto* sv = cfd->GetSuperVersion()->Ref();
+ auto* db_iter = NewArenaWrappedDbIterator(
+ env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, read_seq,
+ sv->mutable_cf_options.max_sequential_skip_in_iterations,
+ sv->version_number, read_callback);
+ auto* internal_iter =
+ NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
+ db_iter->GetRangeDelAggregator(), read_seq);
+ db_iter->SetIterUnderDBIter(internal_iter);
+ iterators->push_back(db_iter);
+ }
+
+ return Status::OK();
+}
+
+Status DB::OpenForReadOnly(const Options& options, const std::string& dbname,
+ DB** dbptr, bool /*error_if_log_file_exist*/) {
+ *dbptr = nullptr;
+
+ // Try to first open DB as fully compacted DB
+ Status s;
+ s = CompactedDBImpl::Open(options, dbname, dbptr);
+ if (s.ok()) {
+ return s;
+ }
+
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> handles;
+
+ s = DB::OpenForReadOnly(db_options, dbname, column_families, &handles, dbptr);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ // i can delete the handle since DBImpl is always holding a
+ // reference to default column family
+ delete handles[0];
+ }
+ return s;
+}
+
+Status DB::OpenForReadOnly(
+ const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
+ bool error_if_log_file_exist) {
+ *dbptr = nullptr;
+ handles->clear();
+
+ SuperVersionContext sv_context(/* create_superversion */ true);
+ DBImplReadOnly* impl = new DBImplReadOnly(db_options, dbname);
+ impl->mutex_.Lock();
+ Status s = impl->Recover(column_families, true /* read only */,
+ error_if_log_file_exist);
+ if (s.ok()) {
+ // set column family handles
+ for (auto cf : column_families) {
+ auto cfd =
+ impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
+ if (cfd == nullptr) {
+ s = Status::InvalidArgument("Column family not found: ", cf.name);
+ break;
+ }
+ handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
+ }
+ }
+ if (s.ok()) {
+ for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
+ sv_context.NewSuperVersion();
+ cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
+ }
+ }
+ impl->mutex_.Unlock();
+ sv_context.Clean();
+ if (s.ok()) {
+ *dbptr = impl;
+ for (auto* h : *handles) {
+ impl->NewThreadStatusCfInfo(
+ reinterpret_cast<ColumnFamilyHandleImpl*>(h)->cfd());
+ }
+ } else {
+ for (auto h : *handles) {
+ delete h;
+ }
+ handles->clear();
+ delete impl;
+ }
+ return s;
+}
+
+#else // !ROCKSDB_LITE
+
+Status DB::OpenForReadOnly(const Options& /*options*/,
+ const std::string& /*dbname*/, DB** /*dbptr*/,
+ bool /*error_if_log_file_exist*/) {
+ return Status::NotSupported("Not supported in ROCKSDB_LITE.");
+}
+
+Status DB::OpenForReadOnly(
+ const DBOptions& /*db_options*/, const std::string& /*dbname*/,
+ const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
+ std::vector<ColumnFamilyHandle*>* /*handles*/, DB** /*dbptr*/,
+ bool /*error_if_log_file_exist*/) {
+ return Status::NotSupported("Not supported in ROCKSDB_LITE.");
+}
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.h b/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.h
new file mode 100644
index 00000000..04d06b4a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_readonly.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImplReadOnly : public DBImpl {
+ public:
+ DBImplReadOnly(const DBOptions& options, const std::string& dbname);
+ // No copying allowed
+ DBImplReadOnly(const DBImplReadOnly&) = delete;
+ void operator=(const DBImplReadOnly&) = delete;
+
+ virtual ~DBImplReadOnly();
+
+ // Implementations of the DB interface
+ using DB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ // TODO: Implement ReadOnly MultiGet?
+
+ using DBImpl::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions&,
+ ColumnFamilyHandle* column_family) override;
+
+ virtual Status NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) override;
+
+ using DBImpl::Put;
+ virtual Status Put(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ using DBImpl::Merge;
+ virtual Status Merge(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ using DBImpl::Delete;
+ virtual Status Delete(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ using DBImpl::SingleDelete;
+ virtual Status SingleDelete(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ virtual Status Write(const WriteOptions& /*options*/,
+ WriteBatch* /*updates*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ using DBImpl::CompactRange;
+ virtual Status CompactRange(const CompactRangeOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice* /*begin*/,
+ const Slice* /*end*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ using DBImpl::CompactFiles;
+ virtual Status CompactFiles(
+ const CompactionOptions& /*compact_options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*input_file_names*/,
+ const int /*output_level*/, const int /*output_path_id*/ = -1,
+ std::vector<std::string>* const /*output_file_names*/ = nullptr,
+ CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ virtual Status DisableFileDeletions() override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ virtual Status EnableFileDeletions(bool /*force*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+ virtual Status GetLiveFiles(std::vector<std::string>& ret,
+ uint64_t* manifest_file_size,
+ bool /*flush_memtable*/) override {
+ return DBImpl::GetLiveFiles(ret, manifest_file_size,
+ false /* flush_memtable */);
+ }
+
+ using DBImpl::Flush;
+ virtual Status Flush(const FlushOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ using DBImpl::SyncWAL;
+ virtual Status SyncWAL() override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ using DB::IngestExternalFile;
+ virtual Status IngestExternalFile(
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*external_files*/,
+ const IngestExternalFileOptions& /*ingestion_options*/) override {
+ return Status::NotSupported("Not supported operation in read only mode.");
+ }
+
+ 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 supported operation in read only mode.");
+ }
+
+ private:
+ friend class DB;
+};
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc
new file mode 100644
index 00000000..f0ec27c3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc
@@ -0,0 +1,671 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_secondary.h"
+
+#include <cinttypes>
+
+#include "db/arena_wrapped_db_iter.h"
+#include "db/merge_context.h"
+#include "logging/auto_roll_logger.h"
+#include "monitoring/perf_context_imp.h"
+#include "util/cast_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
+ const std::string& dbname)
+ : DBImpl(db_options, dbname) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Opening the db in secondary mode");
+ LogFlush(immutable_db_options_.info_log);
+}
+
+DBImplSecondary::~DBImplSecondary() {}
+
+Status DBImplSecondary::Recover(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ bool /*readonly*/, bool /*error_if_log_file_exist*/,
+ bool /*error_if_data_exists_in_logs*/, uint64_t*) {
+ mutex_.AssertHeld();
+
+ JobContext job_context(0);
+ Status s;
+ s = static_cast<ReactiveVersionSet*>(versions_.get())
+ ->Recover(column_families, &manifest_reader_, &manifest_reporter_,
+ &manifest_reader_status_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (immutable_db_options_.paranoid_checks && s.ok()) {
+ s = CheckConsistency();
+ }
+ // Initial max_total_in_memory_state_ before recovery logs.
+ max_total_in_memory_state_ = 0;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
+ max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
+ mutable_cf_options->max_write_buffer_number;
+ }
+ if (s.ok()) {
+ default_cf_handle_ = new ColumnFamilyHandleImpl(
+ versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
+ default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
+ single_column_family_mode_ =
+ versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
+
+ std::unordered_set<ColumnFamilyData*> cfds_changed;
+ s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
+ }
+
+ if (s.IsPathNotFound()) {
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Secondary tries to read WAL, but WAL file(s) have already "
+ "been purged by primary.");
+ s = Status::OK();
+ }
+ // TODO: update options_file_number_ needed?
+
+ job_context.Clean();
+ return s;
+}
+
+// find new WAL and apply them in order to the secondary instance
+Status DBImplSecondary::FindAndRecoverLogFiles(
+ std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ JobContext* job_context) {
+ assert(nullptr != cfds_changed);
+ assert(nullptr != job_context);
+ Status s;
+ std::vector<uint64_t> logs;
+ s = FindNewLogNumbers(&logs);
+ if (s.ok() && !logs.empty()) {
+ SequenceNumber next_sequence(kMaxSequenceNumber);
+ s = RecoverLogFiles(logs, &next_sequence, cfds_changed, job_context);
+ }
+ return s;
+}
+
+// List wal_dir and find all new WALs, return these log numbers
+Status DBImplSecondary::FindNewLogNumbers(std::vector<uint64_t>* logs) {
+ assert(logs != nullptr);
+ std::vector<std::string> filenames;
+ Status s;
+ s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
+ if (s.IsNotFound()) {
+ return Status::InvalidArgument("Failed to open wal_dir",
+ immutable_db_options_.wal_dir);
+ } else if (!s.ok()) {
+ return s;
+ }
+
+ // if log_readers_ is non-empty, it means we have applied all logs with log
+ // numbers smaller than the smallest log in log_readers_, so there is no
+ // need to pass these logs to RecoverLogFiles
+ uint64_t log_number_min = 0;
+ if (!log_readers_.empty()) {
+ log_number_min = log_readers_.begin()->first;
+ }
+ for (size_t i = 0; i < filenames.size(); i++) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(filenames[i], &number, &type) && type == kLogFile &&
+ number >= log_number_min) {
+ logs->push_back(number);
+ }
+ }
+ // Recover logs in the order that they were generated
+ if (!logs->empty()) {
+ std::sort(logs->begin(), logs->end());
+ }
+ return s;
+}
+
+Status DBImplSecondary::MaybeInitLogReader(
+ uint64_t log_number, log::FragmentBufferedReader** log_reader) {
+ auto iter = log_readers_.find(log_number);
+ // make sure the log file is still present
+ if (iter == log_readers_.end() ||
+ iter->second->reader_->GetLogNumber() != log_number) {
+ // delete the obsolete log reader if log number mismatch
+ if (iter != log_readers_.end()) {
+ log_readers_.erase(iter);
+ }
+ // initialize log reader from log_number
+ // TODO: min_log_number_to_keep_2pc check needed?
+ // Open the log file
+ std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Recovering log #%" PRIu64 " mode %d", log_number,
+ static_cast<int>(immutable_db_options_.wal_recovery_mode));
+
+ std::unique_ptr<SequentialFileReader> file_reader;
+ {
+ std::unique_ptr<FSSequentialFile> file;
+ Status status = fs_->NewSequentialFile(
+ fname, fs_->OptimizeForLogRead(file_options_), &file,
+ nullptr);
+ if (!status.ok()) {
+ *log_reader = nullptr;
+ return status;
+ }
+ file_reader.reset(new SequentialFileReader(
+ std::move(file), fname, immutable_db_options_.log_readahead_size));
+ }
+
+ // Create the log reader.
+ LogReaderContainer* log_reader_container = new LogReaderContainer(
+ env_, immutable_db_options_.info_log, std::move(fname),
+ std::move(file_reader), log_number);
+ log_readers_.insert(std::make_pair(
+ log_number, std::unique_ptr<LogReaderContainer>(log_reader_container)));
+ }
+ iter = log_readers_.find(log_number);
+ assert(iter != log_readers_.end());
+ *log_reader = iter->second->reader_;
+ return Status::OK();
+}
+
+// After manifest recovery, replay WALs and refresh log_readers_ if necessary
+// REQUIRES: log_numbers are sorted in ascending order
+Status DBImplSecondary::RecoverLogFiles(
+ const std::vector<uint64_t>& log_numbers, SequenceNumber* next_sequence,
+ std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ JobContext* job_context) {
+ assert(nullptr != cfds_changed);
+ assert(nullptr != job_context);
+ mutex_.AssertHeld();
+ Status status;
+ for (auto log_number : log_numbers) {
+ log::FragmentBufferedReader* reader = nullptr;
+ status = MaybeInitLogReader(log_number, &reader);
+ if (!status.ok()) {
+ return status;
+ }
+ assert(reader != nullptr);
+ }
+ for (auto log_number : log_numbers) {
+ auto it = log_readers_.find(log_number);
+ assert(it != log_readers_.end());
+ log::FragmentBufferedReader* reader = it->second->reader_;
+ // Manually update the file number allocation counter in VersionSet.
+ versions_->MarkFileNumberUsed(log_number);
+
+ // Determine if we should tolerate incomplete records at the tail end of the
+ // Read all the records and add to a memtable
+ std::string scratch;
+ Slice record;
+ WriteBatch batch;
+
+ while (reader->ReadRecord(&record, &scratch,
+ immutable_db_options_.wal_recovery_mode) &&
+ status.ok()) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reader->GetReporter()->Corruption(
+ record.size(), Status::Corruption("log record too small"));
+ continue;
+ }
+ WriteBatchInternal::SetContents(&batch, record);
+ SequenceNumber seq_of_batch = WriteBatchInternal::Sequence(&batch);
+ std::vector<uint32_t> column_family_ids;
+ status = CollectColumnFamilyIdsFromWriteBatch(batch, &column_family_ids);
+ if (status.ok()) {
+ for (const auto id : column_family_ids) {
+ ColumnFamilyData* cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(id);
+ if (cfd == nullptr) {
+ continue;
+ }
+ if (cfds_changed->count(cfd) == 0) {
+ cfds_changed->insert(cfd);
+ }
+ const std::vector<FileMetaData*>& l0_files =
+ cfd->current()->storage_info()->LevelFiles(0);
+ SequenceNumber seq =
+ l0_files.empty() ? 0 : l0_files.back()->fd.largest_seqno;
+ // If the write batch's sequence number is smaller than the last
+ // sequence number of the largest sequence persisted for this column
+ // family, then its data must reside in an SST that has already been
+ // added in the prior MANIFEST replay.
+ if (seq_of_batch <= seq) {
+ continue;
+ }
+ auto curr_log_num = port::kMaxUint64;
+ if (cfd_to_current_log_.count(cfd) > 0) {
+ curr_log_num = cfd_to_current_log_[cfd];
+ }
+ // If the active memtable contains records added by replaying an
+ // earlier WAL, then we need to seal the memtable, add it to the
+ // immutable memtable list and create a new active memtable.
+ if (!cfd->mem()->IsEmpty() && (curr_log_num == port::kMaxUint64 ||
+ curr_log_num != log_number)) {
+ const MutableCFOptions mutable_cf_options =
+ *cfd->GetLatestMutableCFOptions();
+ MemTable* new_mem =
+ cfd->ConstructNewMemtable(mutable_cf_options, seq_of_batch);
+ cfd->mem()->SetNextLogNumber(log_number);
+ cfd->imm()->Add(cfd->mem(), &job_context->memtables_to_free);
+ new_mem->Ref();
+ cfd->SetMemtable(new_mem);
+ }
+ }
+ bool has_valid_writes = false;
+ status = WriteBatchInternal::InsertInto(
+ &batch, column_family_memtables_.get(),
+ nullptr /* flush_scheduler */, nullptr /* trim_history_scheduler*/,
+ true, log_number, this, false /* concurrent_memtable_writes */,
+ next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_);
+ }
+ // If column family was not found, it might mean that the WAL write
+ // batch references to the column family that was dropped after the
+ // insert. We don't want to fail the whole write batch in that case --
+ // we just ignore the update.
+ // That's why we set ignore missing column families to true
+ // passing null flush_scheduler will disable memtable flushing which is
+ // needed for secondary instances
+ if (status.ok()) {
+ for (const auto id : column_family_ids) {
+ ColumnFamilyData* cfd =
+ versions_->GetColumnFamilySet()->GetColumnFamily(id);
+ if (cfd == nullptr) {
+ continue;
+ }
+ std::unordered_map<ColumnFamilyData*, uint64_t>::iterator iter =
+ cfd_to_current_log_.find(cfd);
+ if (iter == cfd_to_current_log_.end()) {
+ cfd_to_current_log_.insert({cfd, log_number});
+ } else if (log_number > iter->second) {
+ iter->second = log_number;
+ }
+ }
+ auto last_sequence = *next_sequence - 1;
+ if ((*next_sequence != kMaxSequenceNumber) &&
+ (versions_->LastSequence() <= last_sequence)) {
+ versions_->SetLastAllocatedSequence(last_sequence);
+ versions_->SetLastPublishedSequence(last_sequence);
+ versions_->SetLastSequence(last_sequence);
+ }
+ } else {
+ // We are treating this as a failure while reading since we read valid
+ // blocks that do not form coherent data
+ reader->GetReporter()->Corruption(record.size(), status);
+ }
+ }
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ // remove logreaders from map after successfully recovering the WAL
+ if (log_readers_.size() > 1) {
+ auto erase_iter = log_readers_.begin();
+ std::advance(erase_iter, log_readers_.size() - 1);
+ log_readers_.erase(log_readers_.begin(), erase_iter);
+ }
+ return status;
+}
+
+// Implementation of the DB interface
+Status DBImplSecondary::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) {
+ return GetImpl(read_options, column_family, key, value);
+}
+
+Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* pinnable_val) {
+ assert(pinnable_val != nullptr);
+ PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
+ StopWatch sw(env_, stats_, DB_GET);
+ PERF_TIMER_GUARD(get_snapshot_time);
+
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
+ ColumnFamilyData* cfd = cfh->cfd();
+ if (tracer_) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ tracer_->Get(column_family, key);
+ }
+ }
+ // Acquire SuperVersion
+ SuperVersion* super_version = GetAndRefSuperVersion(cfd);
+ SequenceNumber snapshot = versions_->LastSequence();
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq = 0;
+ Status s;
+ LookupKey lkey(key, snapshot);
+ PERF_TIMER_STOP(get_snapshot_time);
+
+ bool done = false;
+ if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
+ &max_covering_tombstone_seq, read_options)) {
+ done = true;
+ pinnable_val->PinSelf();
+ RecordTick(stats_, MEMTABLE_HIT);
+ } else if ((s.ok() || s.IsMergeInProgress()) &&
+ super_version->imm->Get(
+ lkey, pinnable_val->GetSelf(), &s, &merge_context,
+ &max_covering_tombstone_seq, read_options)) {
+ done = true;
+ pinnable_val->PinSelf();
+ RecordTick(stats_, MEMTABLE_HIT);
+ }
+ if (!done && !s.ok() && !s.IsMergeInProgress()) {
+ ReturnAndCleanupSuperVersion(cfd, super_version);
+ return s;
+ }
+ if (!done) {
+ PERF_TIMER_GUARD(get_from_output_files_time);
+ super_version->current->Get(read_options, lkey, pinnable_val, &s,
+ &merge_context, &max_covering_tombstone_seq);
+ RecordTick(stats_, MEMTABLE_MISS);
+ }
+ {
+ PERF_TIMER_GUARD(get_post_process_time);
+ ReturnAndCleanupSuperVersion(cfd, super_version);
+ RecordTick(stats_, NUMBER_KEYS_READ);
+ size_t size = pinnable_val->size();
+ RecordTick(stats_, BYTES_READ, size);
+ RecordTimeToHistogram(stats_, BYTES_PER_READ, size);
+ PERF_COUNTER_ADD(get_read_bytes, size);
+ }
+ return s;
+}
+
+Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) {
+ if (read_options.managed) {
+ return NewErrorIterator(
+ Status::NotSupported("Managed iterator is not supported anymore."));
+ }
+ if (read_options.read_tier == kPersistedTier) {
+ return NewErrorIterator(Status::NotSupported(
+ "ReadTier::kPersistedData is not yet supported in iterators."));
+ }
+ Iterator* result = nullptr;
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ if (read_options.tailing) {
+ return NewErrorIterator(Status::NotSupported(
+ "tailing iterator not supported in secondary mode"));
+ } else if (read_options.snapshot != nullptr) {
+ // TODO (yanqin) support snapshot.
+ return NewErrorIterator(
+ Status::NotSupported("snapshot not supported in secondary mode"));
+ } else {
+ auto snapshot = versions_->LastSequence();
+ result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
+ }
+ return result;
+}
+
+ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
+ const ReadOptions& read_options, ColumnFamilyData* cfd,
+ SequenceNumber snapshot, ReadCallback* read_callback) {
+ assert(nullptr != cfd);
+ SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
+ auto db_iter = NewArenaWrappedDbIterator(
+ env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options,
+ snapshot,
+ super_version->mutable_cf_options.max_sequential_skip_in_iterations,
+ super_version->version_number, read_callback);
+ auto internal_iter =
+ NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
+ db_iter->GetRangeDelAggregator(), snapshot);
+ db_iter->SetIterUnderDBIter(internal_iter);
+ return db_iter;
+}
+
+Status DBImplSecondary::NewIterators(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) {
+ if (read_options.managed) {
+ return Status::NotSupported("Managed iterator is not supported anymore.");
+ }
+ if (read_options.read_tier == kPersistedTier) {
+ return Status::NotSupported(
+ "ReadTier::kPersistedData is not yet supported in iterators.");
+ }
+ ReadCallback* read_callback = nullptr; // No read callback provided.
+ if (iterators == nullptr) {
+ return Status::InvalidArgument("iterators not allowed to be nullptr");
+ }
+ iterators->clear();
+ iterators->reserve(column_families.size());
+ if (read_options.tailing) {
+ return Status::NotSupported(
+ "tailing iterator not supported in secondary mode");
+ } else if (read_options.snapshot != nullptr) {
+ // TODO (yanqin) support snapshot.
+ return Status::NotSupported("snapshot not supported in secondary mode");
+ } else {
+ SequenceNumber read_seq = versions_->LastSequence();
+ for (auto cfh : column_families) {
+ ColumnFamilyData* cfd = static_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
+ iterators->push_back(
+ NewIteratorImpl(read_options, cfd, read_seq, read_callback));
+ }
+ }
+ return Status::OK();
+}
+
+Status DBImplSecondary::CheckConsistency() {
+ mutex_.AssertHeld();
+ Status s = DBImpl::CheckConsistency();
+ // If DBImpl::CheckConsistency() which is stricter returns success, then we
+ // do not need to give a second chance.
+ if (s.ok()) {
+ return s;
+ }
+ // It's possible that DBImpl::CheckConssitency() can fail because the primary
+ // may have removed certain files, causing the GetFileSize(name) call to
+ // fail and returning a PathNotFound. In this case, we take a best-effort
+ // approach and just proceed.
+ TEST_SYNC_POINT_CALLBACK(
+ "DBImplSecondary::CheckConsistency:AfterFirstAttempt", &s);
+
+ if (immutable_db_options_.skip_checking_sst_file_sizes_on_db_open) {
+ return Status::OK();
+ }
+
+ std::vector<LiveFileMetaData> metadata;
+ versions_->GetLiveFilesMetaData(&metadata);
+
+ std::string corruption_messages;
+ for (const auto& md : metadata) {
+ // md.name has a leading "/".
+ std::string file_path = md.db_path + md.name;
+
+ uint64_t fsize = 0;
+ s = env_->GetFileSize(file_path, &fsize);
+ if (!s.ok() &&
+ (env_->GetFileSize(Rocks2LevelTableFileName(file_path), &fsize).ok() ||
+ s.IsPathNotFound())) {
+ s = Status::OK();
+ }
+ if (!s.ok()) {
+ corruption_messages +=
+ "Can't access " + md.name + ": " + s.ToString() + "\n";
+ }
+ }
+ return corruption_messages.empty() ? Status::OK()
+ : Status::Corruption(corruption_messages);
+}
+
+Status DBImplSecondary::TryCatchUpWithPrimary() {
+ assert(versions_.get() != nullptr);
+ assert(manifest_reader_.get() != nullptr);
+ Status s;
+ // read the manifest and apply new changes to the secondary instance
+ std::unordered_set<ColumnFamilyData*> cfds_changed;
+ JobContext job_context(0, true /*create_superversion*/);
+ {
+ InstrumentedMutexLock lock_guard(&mutex_);
+ s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
+ ->ReadAndApply(&mutex_, &manifest_reader_, &cfds_changed);
+
+ ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
+ static_cast<uint64_t>(versions_->LastSequence()));
+ for (ColumnFamilyData* cfd : cfds_changed) {
+ if (cfd->IsDropped()) {
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] is dropped\n",
+ cfd->GetName().c_str());
+ continue;
+ }
+ VersionStorageInfo::LevelSummaryStorage tmp;
+ ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
+ "[%s] Level summary: %s\n", cfd->GetName().c_str(),
+ cfd->current()->storage_info()->LevelSummary(&tmp));
+ }
+
+ // list wal_dir to discover new WALs and apply new changes to the secondary
+ // instance
+ if (s.ok()) {
+ s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
+ }
+ if (s.IsPathNotFound()) {
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "Secondary tries to read WAL, but WAL file(s) have already "
+ "been purged by primary.");
+ s = Status::OK();
+ }
+ if (s.ok()) {
+ for (auto cfd : cfds_changed) {
+ cfd->imm()->RemoveOldMemTables(cfd->GetLogNumber(),
+ &job_context.memtables_to_free);
+ auto& sv_context = job_context.superversion_contexts.back();
+ cfd->InstallSuperVersion(&sv_context, &mutex_);
+ sv_context.NewSuperVersion();
+ }
+ }
+ }
+ job_context.Clean();
+
+ // Cleanup unused, obsolete files.
+ JobContext purge_files_job_context(0);
+ {
+ InstrumentedMutexLock lock_guard(&mutex_);
+ // Currently, secondary instance does not own the database files, thus it
+ // is unnecessary for the secondary to force full scan.
+ FindObsoleteFiles(&purge_files_job_context, /*force=*/false);
+ }
+ if (purge_files_job_context.HaveSomethingToDelete()) {
+ PurgeObsoleteFiles(purge_files_job_context);
+ }
+ purge_files_job_context.Clean();
+ return s;
+}
+
+Status DB::OpenAsSecondary(const Options& options, const std::string& dbname,
+ const std::string& secondary_path, DB** dbptr) {
+ *dbptr = nullptr;
+
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
+ std::vector<ColumnFamilyHandle*> handles;
+
+ Status s = DB::OpenAsSecondary(db_options, dbname, secondary_path,
+ column_families, &handles, dbptr);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ delete handles[0];
+ }
+ return s;
+}
+
+Status DB::OpenAsSecondary(
+ const DBOptions& db_options, const std::string& dbname,
+ const std::string& secondary_path,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
+ *dbptr = nullptr;
+ if (db_options.max_open_files != -1) {
+ // TODO (yanqin) maybe support max_open_files != -1 by creating hard links
+ // on SST files so that db secondary can still have access to old SSTs
+ // while primary instance may delete original.
+ return Status::InvalidArgument("require max_open_files to be -1");
+ }
+
+ DBOptions tmp_opts(db_options);
+ Status s;
+ if (nullptr == tmp_opts.info_log) {
+ s = CreateLoggerFromOptions(secondary_path, tmp_opts, &tmp_opts.info_log);
+ if (!s.ok()) {
+ tmp_opts.info_log = nullptr;
+ }
+ }
+
+ handles->clear();
+ DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname);
+ impl->versions_.reset(new ReactiveVersionSet(
+ dbname, &impl->immutable_db_options_, impl->file_options_,
+ impl->table_cache_.get(), impl->write_buffer_manager_,
+ &impl->write_controller_));
+ impl->column_family_memtables_.reset(
+ new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
+ impl->wal_in_db_path_ = IsWalDirSameAsDBPath(&impl->immutable_db_options_);
+
+ impl->mutex_.Lock();
+ s = impl->Recover(column_families, true, false, false);
+ if (s.ok()) {
+ for (auto cf : column_families) {
+ auto cfd =
+ impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
+ if (nullptr == cfd) {
+ s = Status::InvalidArgument("Column family not found: ", cf.name);
+ break;
+ }
+ handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
+ }
+ }
+ SuperVersionContext sv_context(true /* create_superversion */);
+ if (s.ok()) {
+ for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
+ sv_context.NewSuperVersion();
+ cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
+ }
+ }
+ impl->mutex_.Unlock();
+ sv_context.Clean();
+ if (s.ok()) {
+ *dbptr = impl;
+ for (auto h : *handles) {
+ impl->NewThreadStatusCfInfo(
+ reinterpret_cast<ColumnFamilyHandleImpl*>(h)->cfd());
+ }
+ } else {
+ for (auto h : *handles) {
+ delete h;
+ }
+ handles->clear();
+ delete impl;
+ }
+ return s;
+}
+#else // !ROCKSDB_LITE
+
+Status DB::OpenAsSecondary(const Options& /*options*/,
+ const std::string& /*name*/,
+ const std::string& /*secondary_path*/,
+ DB** /*dbptr*/) {
+ return Status::NotSupported("Not supported in ROCKSDB_LITE.");
+}
+
+Status DB::OpenAsSecondary(
+ const DBOptions& /*db_options*/, const std::string& /*dbname*/,
+ const std::string& /*secondary_path*/,
+ const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
+ std::vector<ColumnFamilyHandle*>* /*handles*/, DB** /*dbptr*/) {
+ return Status::NotSupported("Not supported in ROCKSDB_LITE.");
+}
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h b/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h
new file mode 100644
index 00000000..24f2e776
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h
@@ -0,0 +1,333 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A wrapper class to hold log reader, log reporter, log status.
+class LogReaderContainer {
+ public:
+ LogReaderContainer()
+ : reader_(nullptr), reporter_(nullptr), status_(nullptr) {}
+ LogReaderContainer(Env* env, std::shared_ptr<Logger> info_log,
+ std::string fname,
+ std::unique_ptr<SequentialFileReader>&& file_reader,
+ uint64_t log_number) {
+ LogReporter* reporter = new LogReporter();
+ status_ = new Status();
+ reporter->env = env;
+ reporter->info_log = info_log.get();
+ reporter->fname = std::move(fname);
+ reporter->status = status_;
+ reporter_ = reporter;
+ // We intentially make log::Reader do checksumming even if
+ // paranoid_checks==false so that corruptions cause entire commits
+ // to be skipped instead of propagating bad information (like overly
+ // large sequence numbers).
+ reader_ = new log::FragmentBufferedReader(info_log, std::move(file_reader),
+ reporter, true /*checksum*/,
+ log_number);
+ }
+ log::FragmentBufferedReader* reader_;
+ log::Reader::Reporter* reporter_;
+ Status* status_;
+ ~LogReaderContainer() {
+ delete reader_;
+ delete reporter_;
+ delete status_;
+ }
+ private:
+ struct LogReporter : public log::Reader::Reporter {
+ Env* env;
+ Logger* info_log;
+ std::string fname;
+ Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
+ void Corruption(size_t bytes, const Status& s) override {
+ ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
+ (this->status == nullptr ? "(ignoring error) " : ""),
+ fname.c_str(), static_cast<int>(bytes),
+ s.ToString().c_str());
+ if (this->status != nullptr && this->status->ok()) {
+ *this->status = s;
+ }
+ }
+ };
+};
+
+// The secondary instance shares access to the storage as the primary.
+// The secondary is able to read and replay changes described in both the
+// MANIFEST and the WAL files without coordination with the primary.
+// The secondary instance can be opened using `DB::OpenAsSecondary`. After
+// that, it can call `DBImplSecondary::TryCatchUpWithPrimary` to make best
+// effort attempts to catch up with the primary.
+class DBImplSecondary : public DBImpl {
+ public:
+ DBImplSecondary(const DBOptions& options, const std::string& dbname);
+ ~DBImplSecondary() override;
+
+ // Recover by replaying MANIFEST and WAL. Also initialize manifest_reader_
+ // and log_readers_ to facilitate future operations.
+ Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
+ bool read_only, bool error_if_log_file_exist,
+ bool error_if_data_exists_in_logs,
+ uint64_t* = nullptr) override;
+
+ // Implementations of the DB interface
+ using DB::Get;
+ Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value) override;
+
+ Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value);
+
+ using DBImpl::NewIterator;
+ Iterator* NewIterator(const ReadOptions&,
+ ColumnFamilyHandle* column_family) override;
+
+ ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
+ ColumnFamilyData* cfd,
+ SequenceNumber snapshot,
+ ReadCallback* read_callback);
+
+ Status NewIterators(const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) override;
+
+ using DBImpl::Put;
+ Status Put(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
+ const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::Merge;
+ Status Merge(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
+ const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::Delete;
+ Status Delete(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::SingleDelete;
+ Status SingleDelete(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ Status Write(const WriteOptions& /*options*/,
+ WriteBatch* /*updates*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::CompactRange;
+ Status CompactRange(const CompactRangeOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice* /*begin*/, const Slice* /*end*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::CompactFiles;
+ Status CompactFiles(
+ const CompactionOptions& /*compact_options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*input_file_names*/,
+ const int /*output_level*/, const int /*output_path_id*/ = -1,
+ std::vector<std::string>* const /*output_file_names*/ = nullptr,
+ CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ Status DisableFileDeletions() override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ Status EnableFileDeletions(bool /*force*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ Status GetLiveFiles(std::vector<std::string>&,
+ uint64_t* /*manifest_file_size*/,
+ bool /*flush_memtable*/ = true) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::Flush;
+ Status Flush(const FlushOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::SetDBOptions;
+ Status SetDBOptions(const std::unordered_map<std::string, std::string>&
+ /*options_map*/) override {
+ // Currently not supported because changing certain options may cause
+ // flush/compaction.
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::SetOptions;
+ Status SetOptions(
+ ColumnFamilyHandle* /*cfd*/,
+ const std::unordered_map<std::string, std::string>& /*options_map*/)
+ override {
+ // Currently not supported because changing certain options may cause
+ // flush/compaction and/or write to MANIFEST.
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DBImpl::SyncWAL;
+ Status SyncWAL() override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ using DB::IngestExternalFile;
+ Status IngestExternalFile(
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*external_files*/,
+ const IngestExternalFileOptions& /*ingestion_options*/) override {
+ return Status::NotSupported("Not supported operation in secondary mode.");
+ }
+
+ // Try to catch up with the primary by reading as much as possible from the
+ // log files until there is nothing more to read or encounters an error. If
+ // the amount of information in the log files to process is huge, this
+ // method can take long time due to all the I/O and CPU costs.
+ Status TryCatchUpWithPrimary() override;
+
+
+ // Try to find log reader using log_number from log_readers_ map, initialize
+ // if it doesn't exist
+ Status MaybeInitLogReader(uint64_t log_number,
+ log::FragmentBufferedReader** log_reader);
+
+ // Check if all live files exist on file system and that their file sizes
+ // matche to the in-memory records. It is possible that some live files may
+ // have been deleted by the primary. In this case, CheckConsistency() does
+ // not flag the missing file as inconsistency.
+ Status CheckConsistency() override;
+
+ protected:
+ // ColumnFamilyCollector is a write batch handler which does nothing
+ // except recording unique column family IDs
+ class ColumnFamilyCollector : public WriteBatch::Handler {
+ std::unordered_set<uint32_t> column_family_ids_;
+
+ Status AddColumnFamilyId(uint32_t column_family_id) {
+ if (column_family_ids_.find(column_family_id) ==
+ column_family_ids_.end()) {
+ column_family_ids_.insert(column_family_id);
+ }
+ return Status::OK();
+ }
+
+ public:
+ explicit ColumnFamilyCollector() {}
+
+ ~ColumnFamilyCollector() override {}
+
+ Status PutCF(uint32_t column_family_id, const Slice&,
+ const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ Status DeleteCF(uint32_t column_family_id, const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ Status SingleDeleteCF(uint32_t column_family_id, const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ Status DeleteRangeCF(uint32_t column_family_id, const Slice&,
+ const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ Status MergeCF(uint32_t column_family_id, const Slice&,
+ const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ Status PutBlobIndexCF(uint32_t column_family_id, const Slice&,
+ const Slice&) override {
+ return AddColumnFamilyId(column_family_id);
+ }
+
+ const std::unordered_set<uint32_t>& column_families() const {
+ return column_family_ids_;
+ }
+ };
+
+ Status CollectColumnFamilyIdsFromWriteBatch(
+ const WriteBatch& batch, std::vector<uint32_t>* column_family_ids) {
+ assert(column_family_ids != nullptr);
+ column_family_ids->clear();
+ ColumnFamilyCollector handler;
+ Status s = batch.Iterate(&handler);
+ if (s.ok()) {
+ for (const auto& cf : handler.column_families()) {
+ column_family_ids->push_back(cf);
+ }
+ }
+ return s;
+ }
+
+ bool OwnTablesAndLogs() const override {
+ // Currently, the secondary instance does not own the database files. It
+ // simply opens the files of the primary instance and tracks their file
+ // descriptors until they become obsolete. In the future, the secondary may
+ // create links to database files. OwnTablesAndLogs will return true then.
+ return false;
+ }
+
+ private:
+ friend class DB;
+
+ // No copying allowed
+ DBImplSecondary(const DBImplSecondary&);
+ void operator=(const DBImplSecondary&);
+
+ using DBImpl::Recover;
+
+ Status FindAndRecoverLogFiles(
+ std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ JobContext* job_context);
+ Status FindNewLogNumbers(std::vector<uint64_t>* logs);
+ // After manifest recovery, replay WALs and refresh log_readers_ if necessary
+ // REQUIRES: log_numbers are sorted in ascending order
+ Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
+ SequenceNumber* next_sequence,
+ std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ JobContext* job_context);
+
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
+ std::unique_ptr<Status> manifest_reader_status_;
+
+ // Cache log readers for each log number, used for continue WAL replay
+ // after recovery
+ std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
+
+ // Current WAL number replayed for each column family.
+ std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_impl_write.cc b/storage/rocksdb/rocksdb/db/db_impl/db_impl_write.cc
new file mode 100644
index 00000000..8f6f685e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_impl_write.cc
@@ -0,0 +1,1839 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <cinttypes>
+#include "db/error_handler.h"
+#include "db/event_helpers.h"
+#include "monitoring/perf_context_imp.h"
+#include "options/options_helper.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Convenience methods
+Status DBImpl::Put(const WriteOptions& o, ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& val) {
+ return DB::Put(o, column_family, key, val);
+}
+
+Status DBImpl::Merge(const WriteOptions& o, ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& val) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ if (!cfh->cfd()->ioptions()->merge_operator) {
+ return Status::NotSupported("Provide a merge_operator when opening DB");
+ } else {
+ return DB::Merge(o, column_family, key, val);
+ }
+}
+
+Status DBImpl::Delete(const WriteOptions& write_options,
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ return DB::Delete(write_options, column_family, key);
+}
+
+Status DBImpl::SingleDelete(const WriteOptions& write_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ return DB::SingleDelete(write_options, column_family, key);
+}
+
+void DBImpl::SetRecoverableStatePreReleaseCallback(
+ PreReleaseCallback* callback) {
+ recoverable_state_pre_release_callback_.reset(callback);
+}
+
+Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
+ return WriteImpl(write_options, my_batch, nullptr, nullptr);
+}
+
+#ifndef ROCKSDB_LITE
+Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
+ WriteBatch* my_batch,
+ WriteCallback* callback) {
+ return WriteImpl(write_options, my_batch, callback, nullptr);
+}
+#endif // ROCKSDB_LITE
+
+// The main write queue. This is the only write queue that updates LastSequence.
+// When using one write queue, the same sequence also indicates the last
+// published sequence.
+Status DBImpl::WriteImpl(const WriteOptions& write_options,
+ WriteBatch* my_batch, WriteCallback* callback,
+ uint64_t* log_used, uint64_t log_ref,
+ bool disable_memtable, uint64_t* seq_used,
+ size_t batch_cnt,
+ PreReleaseCallback* pre_release_callback) {
+ assert(!seq_per_batch_ || batch_cnt != 0);
+ if (my_batch == nullptr) {
+ return Status::Corruption("Batch is nullptr!");
+ }
+ if (tracer_) {
+ InstrumentedMutexLock lock(&trace_mutex_);
+ if (tracer_) {
+ tracer_->Write(my_batch);
+ }
+ }
+ if (write_options.sync && write_options.disableWAL) {
+ return Status::InvalidArgument("Sync writes has to enable WAL.");
+ }
+ if (two_write_queues_ && immutable_db_options_.enable_pipelined_write) {
+ return Status::NotSupported(
+ "pipelined_writes is not compatible with concurrent prepares");
+ }
+ if (seq_per_batch_ && immutable_db_options_.enable_pipelined_write) {
+ // TODO(yiwu): update pipeline write with seq_per_batch and batch_cnt
+ return Status::NotSupported(
+ "pipelined_writes is not compatible with seq_per_batch");
+ }
+ if (immutable_db_options_.unordered_write &&
+ immutable_db_options_.enable_pipelined_write) {
+ return Status::NotSupported(
+ "pipelined_writes is not compatible with unordered_write");
+ }
+ // Otherwise IsLatestPersistentState optimization does not make sense
+ assert(!WriteBatchInternal::IsLatestPersistentState(my_batch) ||
+ disable_memtable);
+
+ Status status;
+ if (write_options.low_pri) {
+ status = ThrottleLowPriWritesIfNeeded(write_options, my_batch);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+
+ if (two_write_queues_ && disable_memtable) {
+ AssignOrder assign_order =
+ seq_per_batch_ ? kDoAssignOrder : kDontAssignOrder;
+ // Otherwise it is WAL-only Prepare batches in WriteCommitted policy and
+ // they don't consume sequence.
+ return WriteImplWALOnly(&nonmem_write_thread_, write_options, my_batch,
+ callback, log_used, log_ref, seq_used, batch_cnt,
+ pre_release_callback, assign_order,
+ kDontPublishLastSeq, disable_memtable);
+ }
+
+ if (immutable_db_options_.unordered_write) {
+ const size_t sub_batch_cnt = batch_cnt != 0
+ ? batch_cnt
+ // every key is a sub-batch consuming a seq
+ : WriteBatchInternal::Count(my_batch);
+ uint64_t seq;
+ // Use a write thread to i) optimize for WAL write, ii) publish last
+ // sequence in in increasing order, iii) call pre_release_callback serially
+ status = WriteImplWALOnly(&write_thread_, write_options, my_batch, callback,
+ log_used, log_ref, &seq, sub_batch_cnt,
+ pre_release_callback, kDoAssignOrder,
+ kDoPublishLastSeq, disable_memtable);
+ TEST_SYNC_POINT("DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL");
+ if (!status.ok()) {
+ return status;
+ }
+ if (seq_used) {
+ *seq_used = seq;
+ }
+ if (!disable_memtable) {
+ TEST_SYNC_POINT("DBImpl::WriteImpl:BeforeUnorderedWriteMemtable");
+ status = UnorderedWriteMemtable(write_options, my_batch, callback,
+ log_ref, seq, sub_batch_cnt);
+ }
+ return status;
+ }
+
+ if (immutable_db_options_.enable_pipelined_write) {
+ return PipelinedWriteImpl(write_options, my_batch, callback, log_used,
+ log_ref, disable_memtable, seq_used);
+ }
+
+ PERF_TIMER_GUARD(write_pre_and_post_process_time);
+ WriteThread::Writer w(write_options, my_batch, callback, log_ref,
+ disable_memtable, batch_cnt, pre_release_callback);
+
+ if (!write_options.disableWAL) {
+ RecordTick(stats_, WRITE_WITH_WAL);
+ }
+
+ StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
+
+ write_thread_.JoinBatchGroup(&w);
+ if (w.state == WriteThread::STATE_PARALLEL_MEMTABLE_WRITER) {
+ // we are a non-leader in a parallel group
+
+ if (w.ShouldWriteToMemtable()) {
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+ PERF_TIMER_GUARD(write_memtable_time);
+
+ ColumnFamilyMemTablesImpl column_family_memtables(
+ versions_->GetColumnFamilySet());
+ w.status = WriteBatchInternal::InsertInto(
+ &w, w.sequence, &column_family_memtables, &flush_scheduler_,
+ &trim_history_scheduler_,
+ write_options.ignore_missing_column_families, 0 /*log_number*/, this,
+ true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt,
+ batch_per_txn_, write_options.memtable_insert_hint_per_batch);
+
+ PERF_TIMER_START(write_pre_and_post_process_time);
+ }
+
+ if (write_thread_.CompleteParallelMemTableWriter(&w)) {
+ // we're responsible for exit batch group
+ // TODO(myabandeh): propagate status to write_group
+ auto last_sequence = w.write_group->last_sequence;
+ versions_->SetLastSequence(last_sequence);
+ MemTableInsertStatusCheck(w.status);
+ write_thread_.ExitAsBatchGroupFollower(&w);
+ }
+ assert(w.state == WriteThread::STATE_COMPLETED);
+ // STATE_COMPLETED conditional below handles exit
+
+ status = w.FinalStatus();
+ }
+ if (w.state == WriteThread::STATE_COMPLETED) {
+ if (log_used != nullptr) {
+ *log_used = w.log_used;
+ }
+ if (seq_used != nullptr) {
+ *seq_used = w.sequence;
+ }
+ // write is complete and leader has updated sequence
+ return w.FinalStatus();
+ }
+ // else we are the leader of the write batch group
+ assert(w.state == WriteThread::STATE_GROUP_LEADER);
+
+ // Once reaches this point, the current writer "w" will try to do its write
+ // job. It may also pick up some of the remaining writers in the "writers_"
+ // when it finds suitable, and finish them in the same write batch.
+ // This is how a write job could be done by the other writer.
+ WriteContext write_context;
+ WriteThread::WriteGroup write_group;
+ bool in_parallel_group = false;
+ uint64_t last_sequence = kMaxSequenceNumber;
+
+ mutex_.Lock();
+
+ bool need_log_sync = write_options.sync;
+ bool need_log_dir_sync = need_log_sync && !log_dir_synced_;
+ if (!two_write_queues_ || !disable_memtable) {
+ // With concurrent writes we do preprocess only in the write thread that
+ // also does write to memtable to avoid sync issue on shared data structure
+ // with the other thread
+
+ // PreprocessWrite does its own perf timing.
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+
+ status = PreprocessWrite(write_options, &need_log_sync, &write_context);
+ if (!two_write_queues_) {
+ // Assign it after ::PreprocessWrite since the sequence might advance
+ // inside it by WriteRecoverableState
+ last_sequence = versions_->LastSequence();
+ }
+
+ PERF_TIMER_START(write_pre_and_post_process_time);
+ }
+ log::Writer* log_writer = logs_.back().writer;
+
+ mutex_.Unlock();
+
+ // Add to log and apply to memtable. We can release the lock
+ // during this phase since &w is currently responsible for logging
+ // and protects against concurrent loggers and concurrent writes
+ // into memtables
+
+ TEST_SYNC_POINT("DBImpl::WriteImpl:BeforeLeaderEnters");
+ last_batch_group_size_ =
+ write_thread_.EnterAsBatchGroupLeader(&w, &write_group);
+
+ if (status.ok()) {
+ // Rules for when we can update the memtable concurrently
+ // 1. supported by memtable
+ // 2. Puts are not okay if inplace_update_support
+ // 3. Merges are not okay
+ //
+ // Rules 1..2 are enforced by checking the options
+ // during startup (CheckConcurrentWritesSupported), so if
+ // options.allow_concurrent_memtable_write is true then they can be
+ // assumed to be true. Rule 3 is checked for each batch. We could
+ // relax rules 2 if we could prevent write batches from referring
+ // more than once to a particular key.
+ bool parallel = immutable_db_options_.allow_concurrent_memtable_write &&
+ write_group.size > 1;
+ size_t total_count = 0;
+ size_t valid_batches = 0;
+ size_t total_byte_size = 0;
+ size_t pre_release_callback_cnt = 0;
+ for (auto* writer : write_group) {
+ if (writer->CheckCallback(this)) {
+ valid_batches += writer->batch_cnt;
+ if (writer->ShouldWriteToMemtable()) {
+ total_count += WriteBatchInternal::Count(writer->batch);
+ parallel = parallel && !writer->batch->HasMerge();
+ }
+ total_byte_size = WriteBatchInternal::AppendedByteSize(
+ total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
+ if (writer->pre_release_callback) {
+ pre_release_callback_cnt++;
+ }
+ }
+ }
+ // Note about seq_per_batch_: either disableWAL is set for the entire write
+ // group or not. In either case we inc seq for each write batch with no
+ // failed callback. This means that there could be a batch with
+ // disalbe_memtable in between; although we do not write this batch to
+ // memtable it still consumes a seq. Otherwise, if !seq_per_batch_, we inc
+ // the seq per valid written key to mem.
+ size_t seq_inc = seq_per_batch_ ? valid_batches : total_count;
+
+ const bool concurrent_update = two_write_queues_;
+ // Update stats while we are an exclusive group leader, so we know
+ // that nobody else can be writing to these particular stats.
+ // We're optimistic, updating the stats before we successfully
+ // commit. That lets us release our leader status early.
+ auto stats = default_cf_internal_stats_;
+ stats->AddDBStats(InternalStats::kIntStatsNumKeysWritten, total_count,
+ concurrent_update);
+ RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
+ stats->AddDBStats(InternalStats::kIntStatsBytesWritten, total_byte_size,
+ concurrent_update);
+ RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneBySelf, 1,
+ concurrent_update);
+ RecordTick(stats_, WRITE_DONE_BY_SELF);
+ auto write_done_by_other = write_group.size - 1;
+ if (write_done_by_other > 0) {
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneByOther,
+ write_done_by_other, concurrent_update);
+ RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
+ }
+ RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
+
+ if (write_options.disableWAL) {
+ has_unpersisted_data_.store(true, std::memory_order_relaxed);
+ }
+
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+
+ if (!two_write_queues_) {
+ if (status.ok() && !write_options.disableWAL) {
+ PERF_TIMER_GUARD(write_wal_time);
+ status = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
+ need_log_dir_sync, last_sequence + 1);
+ }
+ } else {
+ if (status.ok() && !write_options.disableWAL) {
+ PERF_TIMER_GUARD(write_wal_time);
+ // LastAllocatedSequence is increased inside WriteToWAL under
+ // wal_write_mutex_ to ensure ordered events in WAL
+ status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
+ seq_inc);
+ } else {
+ // Otherwise we inc seq number for memtable writes
+ last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
+ }
+ }
+ assert(last_sequence != kMaxSequenceNumber);
+ const SequenceNumber current_sequence = last_sequence + 1;
+ last_sequence += seq_inc;
+
+ // PreReleaseCallback is called after WAL write and before memtable write
+ if (status.ok()) {
+ SequenceNumber next_sequence = current_sequence;
+ size_t index = 0;
+ // Note: the logic for advancing seq here must be consistent with the
+ // logic in WriteBatchInternal::InsertInto(write_group...) as well as
+ // with WriteBatchInternal::InsertInto(write_batch...) that is called on
+ // the merged batch during recovery from the WAL.
+ for (auto* writer : write_group) {
+ if (writer->CallbackFailed()) {
+ continue;
+ }
+ writer->sequence = next_sequence;
+ if (writer->pre_release_callback) {
+ Status ws = writer->pre_release_callback->Callback(
+ writer->sequence, disable_memtable, writer->log_used, index++,
+ pre_release_callback_cnt);
+ if (!ws.ok()) {
+ status = ws;
+ break;
+ }
+ }
+ if (seq_per_batch_) {
+ assert(writer->batch_cnt);
+ next_sequence += writer->batch_cnt;
+ } else if (writer->ShouldWriteToMemtable()) {
+ next_sequence += WriteBatchInternal::Count(writer->batch);
+ }
+ }
+ }
+
+ if (status.ok()) {
+ PERF_TIMER_GUARD(write_memtable_time);
+
+ if (!parallel) {
+ // w.sequence will be set inside InsertInto
+ w.status = WriteBatchInternal::InsertInto(
+ write_group, current_sequence, column_family_memtables_.get(),
+ &flush_scheduler_, &trim_history_scheduler_,
+ write_options.ignore_missing_column_families,
+ 0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
+ batch_per_txn_);
+ } else {
+ write_group.last_sequence = last_sequence;
+ write_thread_.LaunchParallelMemTableWriters(&write_group);
+ in_parallel_group = true;
+
+ // Each parallel follower is doing each own writes. The leader should
+ // also do its own.
+ if (w.ShouldWriteToMemtable()) {
+ ColumnFamilyMemTablesImpl column_family_memtables(
+ versions_->GetColumnFamilySet());
+ assert(w.sequence == current_sequence);
+ w.status = WriteBatchInternal::InsertInto(
+ &w, w.sequence, &column_family_memtables, &flush_scheduler_,
+ &trim_history_scheduler_,
+ write_options.ignore_missing_column_families, 0 /*log_number*/,
+ this, true /*concurrent_memtable_writes*/, seq_per_batch_,
+ w.batch_cnt, batch_per_txn_,
+ write_options.memtable_insert_hint_per_batch);
+ }
+ }
+ if (seq_used != nullptr) {
+ *seq_used = w.sequence;
+ }
+ }
+ }
+ PERF_TIMER_START(write_pre_and_post_process_time);
+
+ if (!w.CallbackFailed()) {
+ WriteStatusCheck(status);
+ }
+
+ if (need_log_sync) {
+ mutex_.Lock();
+ MarkLogsSynced(logfile_number_, need_log_dir_sync, status);
+ mutex_.Unlock();
+ // Requesting sync with two_write_queues_ is expected to be very rare. We
+ // hence provide a simple implementation that is not necessarily efficient.
+ if (two_write_queues_) {
+ if (manual_wal_flush_) {
+ status = FlushWAL(true);
+ } else {
+ status = SyncWAL();
+ }
+ }
+ }
+
+ bool should_exit_batch_group = true;
+ if (in_parallel_group) {
+ // CompleteParallelWorker returns true if this thread should
+ // handle exit, false means somebody else did
+ should_exit_batch_group = write_thread_.CompleteParallelMemTableWriter(&w);
+ }
+ if (should_exit_batch_group) {
+ if (status.ok()) {
+ // Note: if we are to resume after non-OK statuses we need to revisit how
+ // we reacts to non-OK statuses here.
+ versions_->SetLastSequence(last_sequence);
+ }
+ MemTableInsertStatusCheck(w.status);
+ write_thread_.ExitAsBatchGroupLeader(write_group, status);
+ }
+
+ if (status.ok()) {
+ status = w.FinalStatus();
+ }
+ return status;
+}
+
+Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
+ WriteBatch* my_batch, WriteCallback* callback,
+ uint64_t* log_used, uint64_t log_ref,
+ bool disable_memtable, uint64_t* seq_used) {
+ PERF_TIMER_GUARD(write_pre_and_post_process_time);
+ StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
+
+ WriteContext write_context;
+
+ WriteThread::Writer w(write_options, my_batch, callback, log_ref,
+ disable_memtable);
+ write_thread_.JoinBatchGroup(&w);
+ if (w.state == WriteThread::STATE_GROUP_LEADER) {
+ WriteThread::WriteGroup wal_write_group;
+ if (w.callback && !w.callback->AllowWriteBatching()) {
+ write_thread_.WaitForMemTableWriters();
+ }
+ mutex_.Lock();
+ bool need_log_sync = !write_options.disableWAL && write_options.sync;
+ bool need_log_dir_sync = need_log_sync && !log_dir_synced_;
+ // PreprocessWrite does its own perf timing.
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+ w.status = PreprocessWrite(write_options, &need_log_sync, &write_context);
+ PERF_TIMER_START(write_pre_and_post_process_time);
+ log::Writer* log_writer = logs_.back().writer;
+ mutex_.Unlock();
+
+ // This can set non-OK status if callback fail.
+ last_batch_group_size_ =
+ write_thread_.EnterAsBatchGroupLeader(&w, &wal_write_group);
+ const SequenceNumber current_sequence =
+ write_thread_.UpdateLastSequence(versions_->LastSequence()) + 1;
+ size_t total_count = 0;
+ size_t total_byte_size = 0;
+
+ if (w.status.ok()) {
+ SequenceNumber next_sequence = current_sequence;
+ for (auto writer : wal_write_group) {
+ if (writer->CheckCallback(this)) {
+ if (writer->ShouldWriteToMemtable()) {
+ writer->sequence = next_sequence;
+ size_t count = WriteBatchInternal::Count(writer->batch);
+ next_sequence += count;
+ total_count += count;
+ }
+ total_byte_size = WriteBatchInternal::AppendedByteSize(
+ total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
+ }
+ }
+ if (w.disable_wal) {
+ has_unpersisted_data_.store(true, std::memory_order_relaxed);
+ }
+ write_thread_.UpdateLastSequence(current_sequence + total_count - 1);
+ }
+
+ auto stats = default_cf_internal_stats_;
+ stats->AddDBStats(InternalStats::kIntStatsNumKeysWritten, total_count);
+ RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
+ stats->AddDBStats(InternalStats::kIntStatsBytesWritten, total_byte_size);
+ RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
+ RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
+
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+
+ if (w.status.ok() && !write_options.disableWAL) {
+ PERF_TIMER_GUARD(write_wal_time);
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneBySelf, 1);
+ RecordTick(stats_, WRITE_DONE_BY_SELF, 1);
+ if (wal_write_group.size > 1) {
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneByOther,
+ wal_write_group.size - 1);
+ RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
+ }
+ w.status = WriteToWAL(wal_write_group, log_writer, log_used,
+ need_log_sync, need_log_dir_sync, current_sequence);
+ }
+
+ if (!w.CallbackFailed()) {
+ WriteStatusCheck(w.status);
+ }
+
+ if (need_log_sync) {
+ mutex_.Lock();
+ MarkLogsSynced(logfile_number_, need_log_dir_sync, w.status);
+ mutex_.Unlock();
+ }
+
+ write_thread_.ExitAsBatchGroupLeader(wal_write_group, w.status);
+ }
+
+ WriteThread::WriteGroup memtable_write_group;
+ if (w.state == WriteThread::STATE_MEMTABLE_WRITER_LEADER) {
+ PERF_TIMER_GUARD(write_memtable_time);
+ assert(w.ShouldWriteToMemtable());
+ write_thread_.EnterAsMemTableWriter(&w, &memtable_write_group);
+ if (memtable_write_group.size > 1 &&
+ immutable_db_options_.allow_concurrent_memtable_write) {
+ write_thread_.LaunchParallelMemTableWriters(&memtable_write_group);
+ } else {
+ memtable_write_group.status = WriteBatchInternal::InsertInto(
+ memtable_write_group, w.sequence, column_family_memtables_.get(),
+ &flush_scheduler_, &trim_history_scheduler_,
+ write_options.ignore_missing_column_families, 0 /*log_number*/, this,
+ false /*concurrent_memtable_writes*/, seq_per_batch_, batch_per_txn_);
+ versions_->SetLastSequence(memtable_write_group.last_sequence);
+ write_thread_.ExitAsMemTableWriter(&w, memtable_write_group);
+ }
+ }
+
+ if (w.state == WriteThread::STATE_PARALLEL_MEMTABLE_WRITER) {
+ assert(w.ShouldWriteToMemtable());
+ ColumnFamilyMemTablesImpl column_family_memtables(
+ versions_->GetColumnFamilySet());
+ w.status = WriteBatchInternal::InsertInto(
+ &w, w.sequence, &column_family_memtables, &flush_scheduler_,
+ &trim_history_scheduler_, write_options.ignore_missing_column_families,
+ 0 /*log_number*/, this, true /*concurrent_memtable_writes*/,
+ false /*seq_per_batch*/, 0 /*batch_cnt*/, true /*batch_per_txn*/,
+ write_options.memtable_insert_hint_per_batch);
+ if (write_thread_.CompleteParallelMemTableWriter(&w)) {
+ MemTableInsertStatusCheck(w.status);
+ versions_->SetLastSequence(w.write_group->last_sequence);
+ write_thread_.ExitAsMemTableWriter(&w, *w.write_group);
+ }
+ }
+ if (seq_used != nullptr) {
+ *seq_used = w.sequence;
+ }
+
+ assert(w.state == WriteThread::STATE_COMPLETED);
+ return w.FinalStatus();
+}
+
+Status DBImpl::UnorderedWriteMemtable(const WriteOptions& write_options,
+ WriteBatch* my_batch,
+ WriteCallback* callback, uint64_t log_ref,
+ SequenceNumber seq,
+ const size_t sub_batch_cnt) {
+ PERF_TIMER_GUARD(write_pre_and_post_process_time);
+ StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
+
+ WriteThread::Writer w(write_options, my_batch, callback, log_ref,
+ false /*disable_memtable*/);
+
+ if (w.CheckCallback(this) && w.ShouldWriteToMemtable()) {
+ w.sequence = seq;
+ size_t total_count = WriteBatchInternal::Count(my_batch);
+ InternalStats* stats = default_cf_internal_stats_;
+ stats->AddDBStats(InternalStats::kIntStatsNumKeysWritten, total_count);
+ RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
+
+ ColumnFamilyMemTablesImpl column_family_memtables(
+ versions_->GetColumnFamilySet());
+ w.status = WriteBatchInternal::InsertInto(
+ &w, w.sequence, &column_family_memtables, &flush_scheduler_,
+ &trim_history_scheduler_, write_options.ignore_missing_column_families,
+ 0 /*log_number*/, this, true /*concurrent_memtable_writes*/,
+ seq_per_batch_, sub_batch_cnt, true /*batch_per_txn*/,
+ write_options.memtable_insert_hint_per_batch);
+
+ WriteStatusCheck(w.status);
+ if (write_options.disableWAL) {
+ has_unpersisted_data_.store(true, std::memory_order_relaxed);
+ }
+ }
+
+ size_t pending_cnt = pending_memtable_writes_.fetch_sub(1) - 1;
+ if (pending_cnt == 0) {
+ // switch_cv_ waits until pending_memtable_writes_ = 0. Locking its mutex
+ // before notify ensures that cv is in waiting state when it is notified
+ // thus not missing the update to pending_memtable_writes_ even though it is
+ // not modified under the mutex.
+ std::lock_guard<std::mutex> lck(switch_mutex_);
+ switch_cv_.notify_all();
+ }
+
+ if (!w.FinalStatus().ok()) {
+ return w.FinalStatus();
+ }
+ return Status::OK();
+}
+
+// The 2nd write queue. If enabled it will be used only for WAL-only writes.
+// This is the only queue that updates LastPublishedSequence which is only
+// applicable in a two-queue setting.
+Status DBImpl::WriteImplWALOnly(
+ WriteThread* write_thread, const WriteOptions& write_options,
+ WriteBatch* my_batch, WriteCallback* callback, uint64_t* log_used,
+ const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
+ PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
+ const PublishLastSeq publish_last_seq, const bool disable_memtable) {
+ Status status;
+ PERF_TIMER_GUARD(write_pre_and_post_process_time);
+ WriteThread::Writer w(write_options, my_batch, callback, log_ref,
+ disable_memtable, sub_batch_cnt, pre_release_callback);
+ RecordTick(stats_, WRITE_WITH_WAL);
+ StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
+
+ write_thread->JoinBatchGroup(&w);
+ assert(w.state != WriteThread::STATE_PARALLEL_MEMTABLE_WRITER);
+ if (w.state == WriteThread::STATE_COMPLETED) {
+ if (log_used != nullptr) {
+ *log_used = w.log_used;
+ }
+ if (seq_used != nullptr) {
+ *seq_used = w.sequence;
+ }
+ return w.FinalStatus();
+ }
+ // else we are the leader of the write batch group
+ assert(w.state == WriteThread::STATE_GROUP_LEADER);
+
+ if (publish_last_seq == kDoPublishLastSeq) {
+ // Currently we only use kDoPublishLastSeq in unordered_write
+ assert(immutable_db_options_.unordered_write);
+ WriteContext write_context;
+ if (error_handler_.IsDBStopped()) {
+ status = error_handler_.GetBGError();
+ }
+ // TODO(myabandeh): Make preliminary checks thread-safe so we could do them
+ // without paying the cost of obtaining the mutex.
+ if (status.ok()) {
+ InstrumentedMutexLock l(&mutex_);
+ bool need_log_sync = false;
+ status = PreprocessWrite(write_options, &need_log_sync, &write_context);
+ WriteStatusCheck(status);
+ }
+ if (!status.ok()) {
+ WriteThread::WriteGroup write_group;
+ write_thread->EnterAsBatchGroupLeader(&w, &write_group);
+ write_thread->ExitAsBatchGroupLeader(write_group, status);
+ return status;
+ }
+ }
+
+ WriteThread::WriteGroup write_group;
+ uint64_t last_sequence;
+ write_thread->EnterAsBatchGroupLeader(&w, &write_group);
+ // Note: no need to update last_batch_group_size_ here since the batch writes
+ // to WAL only
+
+ size_t pre_release_callback_cnt = 0;
+ size_t total_byte_size = 0;
+ for (auto* writer : write_group) {
+ if (writer->CheckCallback(this)) {
+ total_byte_size = WriteBatchInternal::AppendedByteSize(
+ total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
+ if (writer->pre_release_callback) {
+ pre_release_callback_cnt++;
+ }
+ }
+ }
+
+ const bool concurrent_update = true;
+ // Update stats while we are an exclusive group leader, so we know
+ // that nobody else can be writing to these particular stats.
+ // We're optimistic, updating the stats before we successfully
+ // commit. That lets us release our leader status early.
+ auto stats = default_cf_internal_stats_;
+ stats->AddDBStats(InternalStats::kIntStatsBytesWritten, total_byte_size,
+ concurrent_update);
+ RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneBySelf, 1,
+ concurrent_update);
+ RecordTick(stats_, WRITE_DONE_BY_SELF);
+ auto write_done_by_other = write_group.size - 1;
+ if (write_done_by_other > 0) {
+ stats->AddDBStats(InternalStats::kIntStatsWriteDoneByOther,
+ write_done_by_other, concurrent_update);
+ RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
+ }
+ RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
+
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+
+ PERF_TIMER_GUARD(write_wal_time);
+ // LastAllocatedSequence is increased inside WriteToWAL under
+ // wal_write_mutex_ to ensure ordered events in WAL
+ size_t seq_inc = 0 /* total_count */;
+ if (assign_order == kDoAssignOrder) {
+ size_t total_batch_cnt = 0;
+ for (auto* writer : write_group) {
+ assert(writer->batch_cnt || !seq_per_batch_);
+ if (!writer->CallbackFailed()) {
+ total_batch_cnt += writer->batch_cnt;
+ }
+ }
+ seq_inc = total_batch_cnt;
+ }
+ if (!write_options.disableWAL) {
+ status =
+ ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
+ } else {
+ // Otherwise we inc seq number to do solely the seq allocation
+ last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
+ }
+
+ size_t memtable_write_cnt = 0;
+ auto curr_seq = last_sequence + 1;
+ for (auto* writer : write_group) {
+ if (writer->CallbackFailed()) {
+ continue;
+ }
+ writer->sequence = curr_seq;
+ if (assign_order == kDoAssignOrder) {
+ assert(writer->batch_cnt || !seq_per_batch_);
+ curr_seq += writer->batch_cnt;
+ }
+ if (!writer->disable_memtable) {
+ memtable_write_cnt++;
+ }
+ // else seq advances only by memtable writes
+ }
+ if (status.ok() && write_options.sync) {
+ assert(!write_options.disableWAL);
+ // Requesting sync with two_write_queues_ is expected to be very rare. We
+ // hance provide a simple implementation that is not necessarily efficient.
+ if (manual_wal_flush_) {
+ status = FlushWAL(true);
+ } else {
+ status = SyncWAL();
+ }
+ }
+ PERF_TIMER_START(write_pre_and_post_process_time);
+
+ if (!w.CallbackFailed()) {
+ WriteStatusCheck(status);
+ }
+ if (status.ok()) {
+ size_t index = 0;
+ for (auto* writer : write_group) {
+ if (!writer->CallbackFailed() && writer->pre_release_callback) {
+ assert(writer->sequence != kMaxSequenceNumber);
+ Status ws = writer->pre_release_callback->Callback(
+ writer->sequence, disable_memtable, writer->log_used, index++,
+ pre_release_callback_cnt);
+ if (!ws.ok()) {
+ status = ws;
+ break;
+ }
+ }
+ }
+ }
+ if (publish_last_seq == kDoPublishLastSeq) {
+ versions_->SetLastSequence(last_sequence + seq_inc);
+ // Currently we only use kDoPublishLastSeq in unordered_write
+ assert(immutable_db_options_.unordered_write);
+ }
+ if (immutable_db_options_.unordered_write && status.ok()) {
+ pending_memtable_writes_ += memtable_write_cnt;
+ }
+ write_thread->ExitAsBatchGroupLeader(write_group, status);
+ if (status.ok()) {
+ status = w.FinalStatus();
+ }
+ if (seq_used != nullptr) {
+ *seq_used = w.sequence;
+ }
+ return status;
+}
+
+void DBImpl::WriteStatusCheck(const Status& status) {
+ // Is setting bg_error_ enough here? This will at least stop
+ // compaction and fail any further writes.
+ if (immutable_db_options_.paranoid_checks && !status.ok() &&
+ !status.IsBusy() && !status.IsIncomplete()) {
+ mutex_.Lock();
+ error_handler_.SetBGError(status, BackgroundErrorReason::kWriteCallback);
+ mutex_.Unlock();
+ }
+}
+
+void DBImpl::MemTableInsertStatusCheck(const Status& status) {
+ // A non-OK status here indicates that the state implied by the
+ // WAL has diverged from the in-memory state. This could be
+ // because of a corrupt write_batch (very bad), or because the
+ // client specified an invalid column family and didn't specify
+ // ignore_missing_column_families.
+ if (!status.ok()) {
+ mutex_.Lock();
+ assert(!error_handler_.IsBGWorkStopped());
+ error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
+ mutex_.Unlock();
+ }
+}
+
+Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
+ bool* need_log_sync,
+ WriteContext* write_context) {
+ mutex_.AssertHeld();
+ assert(write_context != nullptr && need_log_sync != nullptr);
+ Status status;
+
+ if (error_handler_.IsDBStopped()) {
+ status = error_handler_.GetBGError();
+ }
+
+ PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time);
+
+ assert(!single_column_family_mode_ ||
+ versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
+ if (UNLIKELY(status.ok() && !single_column_family_mode_ &&
+ total_log_size_ > GetMaxTotalWalSize())) {
+ WaitForPendingWrites();
+ status = SwitchWAL(write_context);
+ }
+
+ if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldFlush())) {
+ // Before a new memtable is added in SwitchMemtable(),
+ // write_buffer_manager_->ShouldFlush() will keep returning true. If another
+ // thread is writing to another DB with the same write buffer, they may also
+ // be flushed. We may end up with flushing much more DBs than needed. It's
+ // suboptimal but still correct.
+ WaitForPendingWrites();
+ status = HandleWriteBufferFull(write_context);
+ }
+
+ if (UNLIKELY(status.ok() && !trim_history_scheduler_.Empty())) {
+ status = TrimMemtableHistory(write_context);
+ }
+
+ if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) {
+ WaitForPendingWrites();
+ status = ScheduleFlushes(write_context);
+ }
+
+ PERF_TIMER_STOP(write_scheduling_flushes_compactions_time);
+ PERF_TIMER_GUARD(write_pre_and_post_process_time);
+
+ if (UNLIKELY(status.ok() && (write_controller_.IsStopped() ||
+ write_controller_.NeedsDelay()))) {
+ PERF_TIMER_STOP(write_pre_and_post_process_time);
+ PERF_TIMER_GUARD(write_delay_time);
+ // We don't know size of curent batch so that we always use the size
+ // for previous one. It might create a fairness issue that expiration
+ // might happen for smaller writes but larger writes can go through.
+ // Can optimize it if it is an issue.
+ status = DelayWrite(last_batch_group_size_, write_options);
+ PERF_TIMER_START(write_pre_and_post_process_time);
+ }
+
+ if (status.ok() && *need_log_sync) {
+ // Wait until the parallel syncs are finished. Any sync process has to sync
+ // the front log too so it is enough to check the status of front()
+ // We do a while loop since log_sync_cv_ is signalled when any sync is
+ // finished
+ // Note: there does not seem to be a reason to wait for parallel sync at
+ // this early step but it is not important since parallel sync (SyncWAL) and
+ // need_log_sync are usually not used together.
+ while (logs_.front().getting_synced) {
+ log_sync_cv_.Wait();
+ }
+ for (auto& log : logs_) {
+ assert(!log.getting_synced);
+ // This is just to prevent the logs to be synced by a parallel SyncWAL
+ // call. We will do the actual syncing later after we will write to the
+ // WAL.
+ // Note: there does not seem to be a reason to set this early before we
+ // actually write to the WAL
+ log.getting_synced = true;
+ }
+ } else {
+ *need_log_sync = false;
+ }
+
+ return status;
+}
+
+WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
+ WriteBatch* tmp_batch, size_t* write_with_wal,
+ WriteBatch** to_be_cached_state) {
+ assert(write_with_wal != nullptr);
+ assert(tmp_batch != nullptr);
+ assert(*to_be_cached_state == nullptr);
+ WriteBatch* merged_batch = nullptr;
+ *write_with_wal = 0;
+ auto* leader = write_group.leader;
+ assert(!leader->disable_wal); // Same holds for all in the batch group
+ if (write_group.size == 1 && !leader->CallbackFailed() &&
+ leader->batch->GetWalTerminationPoint().is_cleared()) {
+ // we simply write the first WriteBatch to WAL if the group only
+ // contains one batch, that batch should be written to the WAL,
+ // and the batch is not wanting to be truncated
+ merged_batch = leader->batch;
+ if (WriteBatchInternal::IsLatestPersistentState(merged_batch)) {
+ *to_be_cached_state = merged_batch;
+ }
+ *write_with_wal = 1;
+ } else {
+ // WAL needs all of the batches flattened into a single batch.
+ // We could avoid copying here with an iov-like AddRecord
+ // interface
+ merged_batch = tmp_batch;
+ for (auto writer : write_group) {
+ if (!writer->CallbackFailed()) {
+ WriteBatchInternal::Append(merged_batch, writer->batch,
+ /*WAL_only*/ true);
+ if (WriteBatchInternal::IsLatestPersistentState(writer->batch)) {
+ // We only need to cache the last of such write batch
+ *to_be_cached_state = writer->batch;
+ }
+ (*write_with_wal)++;
+ }
+ }
+ }
+ return merged_batch;
+}
+
+// When two_write_queues_ is disabled, this function is called from the only
+// write thread. Otherwise this must be called holding log_write_mutex_.
+Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
+ log::Writer* log_writer, uint64_t* log_used,
+ uint64_t* log_size) {
+ assert(log_size != nullptr);
+ Slice log_entry = WriteBatchInternal::Contents(&merged_batch);
+ *log_size = log_entry.size();
+ // When two_write_queues_ WriteToWAL has to be protected from concurretn calls
+ // from the two queues anyway and log_write_mutex_ is already held. Otherwise
+ // if manual_wal_flush_ is enabled we need to protect log_writer->AddRecord
+ // from possible concurrent calls via the FlushWAL by the application.
+ const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
+ // Due to performance cocerns of missed branch prediction penalize the new
+ // manual_wal_flush_ feature (by UNLIKELY) instead of the more common case
+ // when we do not need any locking.
+ if (UNLIKELY(needs_locking)) {
+ log_write_mutex_.Lock();
+ }
+ Status status = log_writer->AddRecord(log_entry);
+ if (UNLIKELY(needs_locking)) {
+ log_write_mutex_.Unlock();
+ }
+ if (log_used != nullptr) {
+ *log_used = logfile_number_;
+ }
+ total_log_size_ += log_entry.size();
+ // TODO(myabandeh): it might be unsafe to access alive_log_files_.back() here
+ // since alive_log_files_ might be modified concurrently
+ alive_log_files_.back().AddSize(log_entry.size());
+ log_empty_ = false;
+ return status;
+}
+
+Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
+ log::Writer* log_writer, uint64_t* log_used,
+ bool need_log_sync, bool need_log_dir_sync,
+ SequenceNumber sequence) {
+ Status status;
+
+ assert(!write_group.leader->disable_wal);
+ // Same holds for all in the batch group
+ size_t write_with_wal = 0;
+ WriteBatch* to_be_cached_state = nullptr;
+ WriteBatch* merged_batch = MergeBatch(write_group, &tmp_batch_,
+ &write_with_wal, &to_be_cached_state);
+ if (merged_batch == write_group.leader->batch) {
+ write_group.leader->log_used = logfile_number_;
+ } else if (write_with_wal > 1) {
+ for (auto writer : write_group) {
+ writer->log_used = logfile_number_;
+ }
+ }
+
+ WriteBatchInternal::SetSequence(merged_batch, sequence);
+
+ uint64_t log_size;
+ status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
+ if (to_be_cached_state) {
+ cached_recoverable_state_ = *to_be_cached_state;
+ cached_recoverable_state_empty_ = false;
+ }
+
+ if (status.ok() && need_log_sync) {
+ StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS);
+ // It's safe to access logs_ with unlocked mutex_ here because:
+ // - we've set getting_synced=true for all logs,
+ // so other threads won't pop from logs_ while we're here,
+ // - only writer thread can push to logs_, and we're in
+ // writer thread, so no one will push to logs_,
+ // - as long as other threads don't modify it, it's safe to read
+ // from std::deque from multiple threads concurrently.
+ for (auto& log : logs_) {
+ status = log.writer->file()->Sync(immutable_db_options_.use_fsync);
+ if (!status.ok()) {
+ break;
+ }
+ }
+ if (status.ok() && need_log_dir_sync) {
+ // We only sync WAL directory the first time WAL syncing is
+ // requested, so that in case users never turn on WAL sync,
+ // we can avoid the disk I/O in the write code path.
+ status = directories_.GetWalDir()->Fsync();
+ }
+ }
+
+ if (merged_batch == &tmp_batch_) {
+ tmp_batch_.Clear();
+ }
+ if (status.ok()) {
+ auto stats = default_cf_internal_stats_;
+ if (need_log_sync) {
+ stats->AddDBStats(InternalStats::kIntStatsWalFileSynced, 1);
+ RecordTick(stats_, WAL_FILE_SYNCED);
+ }
+ stats->AddDBStats(InternalStats::kIntStatsWalFileBytes, log_size);
+ RecordTick(stats_, WAL_FILE_BYTES, log_size);
+ stats->AddDBStats(InternalStats::kIntStatsWriteWithWal, write_with_wal);
+ RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
+ }
+ return status;
+}
+
+Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
+ uint64_t* log_used,
+ SequenceNumber* last_sequence,
+ size_t seq_inc) {
+ Status status;
+
+ assert(!write_group.leader->disable_wal);
+ // Same holds for all in the batch group
+ WriteBatch tmp_batch;
+ size_t write_with_wal = 0;
+ WriteBatch* to_be_cached_state = nullptr;
+ WriteBatch* merged_batch =
+ MergeBatch(write_group, &tmp_batch, &write_with_wal, &to_be_cached_state);
+
+ // We need to lock log_write_mutex_ since logs_ and alive_log_files might be
+ // pushed back concurrently
+ log_write_mutex_.Lock();
+ if (merged_batch == write_group.leader->batch) {
+ write_group.leader->log_used = logfile_number_;
+ } else if (write_with_wal > 1) {
+ for (auto writer : write_group) {
+ writer->log_used = logfile_number_;
+ }
+ }
+ *last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
+ auto sequence = *last_sequence + 1;
+ WriteBatchInternal::SetSequence(merged_batch, sequence);
+
+ log::Writer* log_writer = logs_.back().writer;
+ uint64_t log_size;
+ status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
+ if (to_be_cached_state) {
+ cached_recoverable_state_ = *to_be_cached_state;
+ cached_recoverable_state_empty_ = false;
+ }
+ log_write_mutex_.Unlock();
+
+ if (status.ok()) {
+ const bool concurrent = true;
+ auto stats = default_cf_internal_stats_;
+ stats->AddDBStats(InternalStats::kIntStatsWalFileBytes, log_size,
+ concurrent);
+ RecordTick(stats_, WAL_FILE_BYTES, log_size);
+ stats->AddDBStats(InternalStats::kIntStatsWriteWithWal, write_with_wal,
+ concurrent);
+ RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
+ }
+ return status;
+}
+
+Status DBImpl::WriteRecoverableState() {
+ mutex_.AssertHeld();
+ if (!cached_recoverable_state_empty_) {
+ bool dont_care_bool;
+ SequenceNumber next_seq;
+ if (two_write_queues_) {
+ log_write_mutex_.Lock();
+ }
+ SequenceNumber seq;
+ if (two_write_queues_) {
+ seq = versions_->FetchAddLastAllocatedSequence(0);
+ } else {
+ seq = versions_->LastSequence();
+ }
+ WriteBatchInternal::SetSequence(&cached_recoverable_state_, seq + 1);
+ auto status = WriteBatchInternal::InsertInto(
+ &cached_recoverable_state_, column_family_memtables_.get(),
+ &flush_scheduler_, &trim_history_scheduler_, true,
+ 0 /*recovery_log_number*/, this, false /* concurrent_memtable_writes */,
+ &next_seq, &dont_care_bool, seq_per_batch_);
+ auto last_seq = next_seq - 1;
+ if (two_write_queues_) {
+ versions_->FetchAddLastAllocatedSequence(last_seq - seq);
+ versions_->SetLastPublishedSequence(last_seq);
+ }
+ versions_->SetLastSequence(last_seq);
+ if (two_write_queues_) {
+ log_write_mutex_.Unlock();
+ }
+ if (status.ok() && recoverable_state_pre_release_callback_) {
+ const bool DISABLE_MEMTABLE = true;
+ for (uint64_t sub_batch_seq = seq + 1;
+ sub_batch_seq < next_seq && status.ok(); sub_batch_seq++) {
+ uint64_t const no_log_num = 0;
+ // Unlock it since the callback might end up locking mutex. e.g.,
+ // AddCommitted -> AdvanceMaxEvictedSeq -> GetSnapshotListFromDB
+ mutex_.Unlock();
+ status = recoverable_state_pre_release_callback_->Callback(
+ sub_batch_seq, !DISABLE_MEMTABLE, no_log_num, 0, 1);
+ mutex_.Lock();
+ }
+ }
+ if (status.ok()) {
+ cached_recoverable_state_.Clear();
+ cached_recoverable_state_empty_ = true;
+ }
+ return status;
+ }
+ return Status::OK();
+}
+
+void DBImpl::SelectColumnFamiliesForAtomicFlush(
+ autovector<ColumnFamilyData*>* cfds) {
+ for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
+ !cached_recoverable_state_empty_.load()) {
+ cfds->push_back(cfd);
+ }
+ }
+}
+
+// Assign sequence number for atomic flush.
+void DBImpl::AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds) {
+ assert(immutable_db_options_.atomic_flush);
+ auto seq = versions_->LastSequence();
+ for (auto cfd : cfds) {
+ cfd->imm()->AssignAtomicFlushSeq(seq);
+ }
+}
+
+Status DBImpl::SwitchWAL(WriteContext* write_context) {
+ mutex_.AssertHeld();
+ assert(write_context != nullptr);
+ Status status;
+
+ if (alive_log_files_.begin()->getting_flushed) {
+ return status;
+ }
+
+ auto oldest_alive_log = alive_log_files_.begin()->number;
+ bool flush_wont_release_oldest_log = false;
+ if (allow_2pc()) {
+ auto oldest_log_with_uncommitted_prep =
+ logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep();
+
+ assert(oldest_log_with_uncommitted_prep == 0 ||
+ oldest_log_with_uncommitted_prep >= oldest_alive_log);
+ if (oldest_log_with_uncommitted_prep > 0 &&
+ oldest_log_with_uncommitted_prep == oldest_alive_log) {
+ if (unable_to_release_oldest_log_) {
+ // we already attempted to flush all column families dependent on
+ // the oldest alive log but the log still contained uncommitted
+ // transactions so there is still nothing that we can do.
+ return status;
+ } else {
+ ROCKS_LOG_WARN(
+ immutable_db_options_.info_log,
+ "Unable to release oldest log due to uncommitted transaction");
+ unable_to_release_oldest_log_ = true;
+ flush_wont_release_oldest_log = true;
+ }
+ }
+ }
+ if (!flush_wont_release_oldest_log) {
+ // we only mark this log as getting flushed if we have successfully
+ // flushed all data in this log. If this log contains outstanding prepared
+ // transactions then we cannot flush this log until those transactions are
+ // commited.
+ unable_to_release_oldest_log_ = false;
+ alive_log_files_.begin()->getting_flushed = true;
+ }
+
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "Flushing all column families with data in WAL number %" PRIu64
+ ". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
+ oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
+ // no need to refcount because drop is happening in write thread, so can't
+ // happen while we're in the write thread
+ autovector<ColumnFamilyData*> cfds;
+ if (immutable_db_options_.atomic_flush) {
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ } else {
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ if (cfd->OldestLogToKeep() <= oldest_alive_log) {
+ cfds.push_back(cfd);
+ }
+ }
+ MaybeFlushStatsCF(&cfds);
+ }
+ WriteThread::Writer nonmem_w;
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+
+ for (const auto cfd : cfds) {
+ cfd->Ref();
+ status = SwitchMemtable(cfd, write_context);
+ cfd->UnrefAndTryDelete();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+
+ if (status.ok()) {
+ if (immutable_db_options_.atomic_flush) {
+ AssignAtomicFlushSeq(cfds);
+ }
+ for (auto cfd : cfds) {
+ cfd->imm()->FlushRequested();
+ }
+ FlushRequest flush_req;
+ GenerateFlushRequest(cfds, &flush_req);
+ SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
+ MaybeScheduleFlushOrCompaction();
+ }
+ return status;
+}
+
+Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
+ mutex_.AssertHeld();
+ assert(write_context != nullptr);
+ Status status;
+
+ // Before a new memtable is added in SwitchMemtable(),
+ // write_buffer_manager_->ShouldFlush() will keep returning true. If another
+ // thread is writing to another DB with the same write buffer, they may also
+ // be flushed. We may end up with flushing much more DBs than needed. It's
+ // suboptimal but still correct.
+ ROCKS_LOG_INFO(
+ immutable_db_options_.info_log,
+ "Flushing column family with oldest memtable entry. Write buffer is "
+ "using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".",
+ write_buffer_manager_->memory_usage(),
+ write_buffer_manager_->buffer_size());
+ // no need to refcount because drop is happening in write thread, so can't
+ // happen while we're in the write thread
+ autovector<ColumnFamilyData*> cfds;
+ if (immutable_db_options_.atomic_flush) {
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ } else {
+ ColumnFamilyData* cfd_picked = nullptr;
+ SequenceNumber seq_num_for_cf_picked = kMaxSequenceNumber;
+
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ if (!cfd->mem()->IsEmpty()) {
+ // We only consider active mem table, hoping immutable memtable is
+ // already in the process of flushing.
+ uint64_t seq = cfd->mem()->GetCreationSeq();
+ if (cfd_picked == nullptr || seq < seq_num_for_cf_picked) {
+ cfd_picked = cfd;
+ seq_num_for_cf_picked = seq;
+ }
+ }
+ }
+ if (cfd_picked != nullptr) {
+ cfds.push_back(cfd_picked);
+ }
+ MaybeFlushStatsCF(&cfds);
+ }
+
+ WriteThread::Writer nonmem_w;
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+ for (const auto cfd : cfds) {
+ if (cfd->mem()->IsEmpty()) {
+ continue;
+ }
+ cfd->Ref();
+ status = SwitchMemtable(cfd, write_context);
+ cfd->UnrefAndTryDelete();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+
+ if (status.ok()) {
+ if (immutable_db_options_.atomic_flush) {
+ AssignAtomicFlushSeq(cfds);
+ }
+ for (const auto cfd : cfds) {
+ cfd->imm()->FlushRequested();
+ }
+ FlushRequest flush_req;
+ GenerateFlushRequest(cfds, &flush_req);
+ SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
+ MaybeScheduleFlushOrCompaction();
+ }
+ return status;
+}
+
+uint64_t DBImpl::GetMaxTotalWalSize() const {
+ mutex_.AssertHeld();
+ return mutable_db_options_.max_total_wal_size == 0
+ ? 4 * max_total_in_memory_state_
+ : mutable_db_options_.max_total_wal_size;
+}
+
+// REQUIRES: mutex_ is held
+// REQUIRES: this thread is currently at the front of the writer queue
+Status DBImpl::DelayWrite(uint64_t num_bytes,
+ const WriteOptions& write_options) {
+ uint64_t time_delayed = 0;
+ bool delayed = false;
+ {
+ StopWatch sw(env_, stats_, WRITE_STALL, &time_delayed);
+ uint64_t delay = write_controller_.GetDelay(env_, num_bytes);
+ if (delay > 0) {
+ if (write_options.no_slowdown) {
+ return Status::Incomplete("Write stall");
+ }
+ TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
+
+ // Notify write_thread_ about the stall so it can setup a barrier and
+ // fail any pending writers with no_slowdown
+ write_thread_.BeginWriteStall();
+ TEST_SYNC_POINT("DBImpl::DelayWrite:BeginWriteStallDone");
+ mutex_.Unlock();
+ // We will delay the write until we have slept for delay ms or
+ // we don't need a delay anymore
+ const uint64_t kDelayInterval = 1000;
+ uint64_t stall_end = sw.start_time() + delay;
+ while (write_controller_.NeedsDelay()) {
+ if (env_->NowMicros() >= stall_end) {
+ // We already delayed this write `delay` microseconds
+ break;
+ }
+
+ delayed = true;
+ // Sleep for 0.001 seconds
+ env_->SleepForMicroseconds(kDelayInterval);
+ }
+ mutex_.Lock();
+ write_thread_.EndWriteStall();
+ }
+
+ // Don't wait if there's a background error, even if its a soft error. We
+ // might wait here indefinitely as the background compaction may never
+ // finish successfully, resulting in the stall condition lasting
+ // indefinitely
+ while (error_handler_.GetBGError().ok() && write_controller_.IsStopped()) {
+ if (write_options.no_slowdown) {
+ return Status::Incomplete("Write stall");
+ }
+ delayed = true;
+
+ // Notify write_thread_ about the stall so it can setup a barrier and
+ // fail any pending writers with no_slowdown
+ write_thread_.BeginWriteStall();
+ TEST_SYNC_POINT("DBImpl::DelayWrite:Wait");
+ bg_cv_.Wait();
+ write_thread_.EndWriteStall();
+ }
+ }
+ assert(!delayed || !write_options.no_slowdown);
+ if (delayed) {
+ default_cf_internal_stats_->AddDBStats(
+ InternalStats::kIntStatsWriteStallMicros, time_delayed);
+ RecordTick(stats_, STALL_MICROS, time_delayed);
+ }
+
+ // If DB is not in read-only mode and write_controller is not stopping
+ // writes, we can ignore any background errors and allow the write to
+ // proceed
+ Status s;
+ if (write_controller_.IsStopped()) {
+ // If writes are still stopped, it means we bailed due to a background
+ // error
+ s = Status::Incomplete(error_handler_.GetBGError().ToString());
+ }
+ if (error_handler_.IsDBStopped()) {
+ s = error_handler_.GetBGError();
+ }
+ return s;
+}
+
+Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
+ WriteBatch* my_batch) {
+ assert(write_options.low_pri);
+ // This is called outside the DB mutex. Although it is safe to make the call,
+ // the consistency condition is not guaranteed to hold. It's OK to live with
+ // it in this case.
+ // If we need to speed compaction, it means the compaction is left behind
+ // and we start to limit low pri writes to a limit.
+ if (write_controller_.NeedSpeedupCompaction()) {
+ if (allow_2pc() && (my_batch->HasCommit() || my_batch->HasRollback())) {
+ // For 2PC, we only rate limit prepare, not commit.
+ return Status::OK();
+ }
+ if (write_options.no_slowdown) {
+ return Status::Incomplete("Low priority write stall");
+ } else {
+ assert(my_batch != nullptr);
+ // Rate limit those writes. The reason that we don't completely wait
+ // is that in case the write is heavy, low pri writes may never have
+ // a chance to run. Now we guarantee we are still slowly making
+ // progress.
+ PERF_TIMER_GUARD(write_delay_time);
+ write_controller_.low_pri_rate_limiter()->Request(
+ my_batch->GetDataSize(), Env::IO_HIGH, nullptr /* stats */,
+ RateLimiter::OpType::kWrite);
+ }
+ }
+ return Status::OK();
+}
+
+void DBImpl::MaybeFlushStatsCF(autovector<ColumnFamilyData*>* cfds) {
+ assert(cfds != nullptr);
+ if (!cfds->empty() && immutable_db_options_.persist_stats_to_disk) {
+ ColumnFamilyData* cfd_stats =
+ versions_->GetColumnFamilySet()->GetColumnFamily(
+ kPersistentStatsColumnFamilyName);
+ if (cfd_stats != nullptr && !cfd_stats->mem()->IsEmpty()) {
+ for (ColumnFamilyData* cfd : *cfds) {
+ if (cfd == cfd_stats) {
+ // stats CF already included in cfds
+ return;
+ }
+ }
+ // force flush stats CF when its log number is less than all other CF's
+ // log numbers
+ bool force_flush_stats_cf = true;
+ for (auto* loop_cfd : *versions_->GetColumnFamilySet()) {
+ if (loop_cfd == cfd_stats) {
+ continue;
+ }
+ if (loop_cfd->GetLogNumber() <= cfd_stats->GetLogNumber()) {
+ force_flush_stats_cf = false;
+ }
+ }
+ if (force_flush_stats_cf) {
+ cfds->push_back(cfd_stats);
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "Force flushing stats CF with automated flush "
+ "to avoid holding old logs");
+ }
+ }
+ }
+}
+
+Status DBImpl::TrimMemtableHistory(WriteContext* context) {
+ autovector<ColumnFamilyData*> cfds;
+ ColumnFamilyData* tmp_cfd;
+ while ((tmp_cfd = trim_history_scheduler_.TakeNextColumnFamily()) !=
+ nullptr) {
+ cfds.push_back(tmp_cfd);
+ }
+ for (auto& cfd : cfds) {
+ autovector<MemTable*> to_delete;
+ cfd->imm()->TrimHistory(&to_delete, cfd->mem()->ApproximateMemoryUsage());
+ if (!to_delete.empty()) {
+ for (auto m : to_delete) {
+ delete m;
+ }
+ context->superversion_context.NewSuperVersion();
+ assert(context->superversion_context.new_superversion.get() != nullptr);
+ cfd->InstallSuperVersion(&context->superversion_context, &mutex_);
+ }
+
+ if (cfd->UnrefAndTryDelete()) {
+ cfd = nullptr;
+ }
+ }
+ return Status::OK();
+}
+
+Status DBImpl::ScheduleFlushes(WriteContext* context) {
+ autovector<ColumnFamilyData*> cfds;
+ if (immutable_db_options_.atomic_flush) {
+ SelectColumnFamiliesForAtomicFlush(&cfds);
+ for (auto cfd : cfds) {
+ cfd->Ref();
+ }
+ flush_scheduler_.Clear();
+ } else {
+ ColumnFamilyData* tmp_cfd;
+ while ((tmp_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
+ cfds.push_back(tmp_cfd);
+ }
+ MaybeFlushStatsCF(&cfds);
+ }
+ Status status;
+ WriteThread::Writer nonmem_w;
+ if (two_write_queues_) {
+ nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
+ }
+
+ for (auto& cfd : cfds) {
+ if (!cfd->mem()->IsEmpty()) {
+ status = SwitchMemtable(cfd, context);
+ }
+ if (cfd->UnrefAndTryDelete()) {
+ cfd = nullptr;
+ }
+ if (!status.ok()) {
+ break;
+ }
+ }
+
+ if (two_write_queues_) {
+ nonmem_write_thread_.ExitUnbatched(&nonmem_w);
+ }
+
+ if (status.ok()) {
+ if (immutable_db_options_.atomic_flush) {
+ AssignAtomicFlushSeq(cfds);
+ }
+ FlushRequest flush_req;
+ GenerateFlushRequest(cfds, &flush_req);
+ SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
+ MaybeScheduleFlushOrCompaction();
+ }
+ return status;
+}
+
+#ifndef ROCKSDB_LITE
+void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
+ const MemTableInfo& mem_table_info) {
+ if (immutable_db_options_.listeners.size() == 0U) {
+ return;
+ }
+ if (shutting_down_.load(std::memory_order_acquire)) {
+ return;
+ }
+
+ for (auto listener : immutable_db_options_.listeners) {
+ listener->OnMemTableSealed(mem_table_info);
+ }
+}
+#endif // ROCKSDB_LITE
+
+// REQUIRES: mutex_ is held
+// REQUIRES: this thread is currently at the front of the writer queue
+// REQUIRES: this thread is currently at the front of the 2nd writer queue if
+// two_write_queues_ is true (This is to simplify the reasoning.)
+Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
+ mutex_.AssertHeld();
+ WriteThread::Writer nonmem_w;
+ std::unique_ptr<WritableFile> lfile;
+ log::Writer* new_log = nullptr;
+ MemTable* new_mem = nullptr;
+
+ // Recoverable state is persisted in WAL. After memtable switch, WAL might
+ // be deleted, so we write the state to memtable to be persisted as well.
+ Status s = WriteRecoverableState();
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Attempt to switch to a new memtable and trigger flush of old.
+ // Do this without holding the dbmutex lock.
+ assert(versions_->prev_log_number() == 0);
+ if (two_write_queues_) {
+ log_write_mutex_.Lock();
+ }
+ bool creating_new_log = !log_empty_;
+ if (two_write_queues_) {
+ log_write_mutex_.Unlock();
+ }
+ uint64_t recycle_log_number = 0;
+ if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
+ !log_recycle_files_.empty()) {
+ recycle_log_number = log_recycle_files_.front();
+ }
+ uint64_t new_log_number =
+ creating_new_log ? versions_->NewFileNumber() : logfile_number_;
+ const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
+
+ // Set memtable_info for memtable sealed callback
+#ifndef ROCKSDB_LITE
+ MemTableInfo memtable_info;
+ memtable_info.cf_name = cfd->GetName();
+ memtable_info.first_seqno = cfd->mem()->GetFirstSequenceNumber();
+ memtable_info.earliest_seqno = cfd->mem()->GetEarliestSequenceNumber();
+ memtable_info.num_entries = cfd->mem()->num_entries();
+ memtable_info.num_deletes = cfd->mem()->num_deletes();
+#endif // ROCKSDB_LITE
+ // Log this later after lock release. It may be outdated, e.g., if background
+ // flush happens before logging, but that should be ok.
+ int num_imm_unflushed = cfd->imm()->NumNotFlushed();
+ const auto preallocate_block_size =
+ GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
+ mutex_.Unlock();
+ if (creating_new_log) {
+ // TODO: Write buffer size passed in should be max of all CF's instead
+ // of mutable_cf_options.write_buffer_size.
+ s = CreateWAL(new_log_number, recycle_log_number, preallocate_block_size,
+ &new_log);
+ }
+ if (s.ok()) {
+ SequenceNumber seq = versions_->LastSequence();
+ new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
+ context->superversion_context.NewSuperVersion();
+ }
+ ROCKS_LOG_INFO(immutable_db_options_.info_log,
+ "[%s] New memtable created with log file: #%" PRIu64
+ ". Immutable memtables: %d.\n",
+ cfd->GetName().c_str(), new_log_number, num_imm_unflushed);
+ mutex_.Lock();
+ if (recycle_log_number != 0) {
+ // Since renaming the file is done outside DB mutex, we need to ensure
+ // concurrent full purges don't delete the file while we're recycling it.
+ // To achieve that we hold the old log number in the recyclable list until
+ // after it has been renamed.
+ assert(log_recycle_files_.front() == recycle_log_number);
+ log_recycle_files_.pop_front();
+ }
+ if (s.ok() && creating_new_log) {
+ log_write_mutex_.Lock();
+ assert(new_log != nullptr);
+ if (!logs_.empty()) {
+ // Alway flush the buffer of the last log before switching to a new one
+ log::Writer* cur_log_writer = logs_.back().writer;
+ s = cur_log_writer->WriteBuffer();
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(immutable_db_options_.info_log,
+ "[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64
+ " WAL file\n",
+ cfd->GetName().c_str(), cur_log_writer->get_log_number(),
+ new_log_number);
+ }
+ }
+ if (s.ok()) {
+ logfile_number_ = new_log_number;
+ log_empty_ = true;
+ log_dir_synced_ = false;
+ logs_.emplace_back(logfile_number_, new_log);
+ alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
+ }
+ log_write_mutex_.Unlock();
+ }
+
+ if (!s.ok()) {
+ // how do we fail if we're not creating new log?
+ assert(creating_new_log);
+ if (new_mem) {
+ delete new_mem;
+ }
+ if (new_log) {
+ delete new_log;
+ }
+ SuperVersion* new_superversion =
+ context->superversion_context.new_superversion.release();
+ if (new_superversion != nullptr) {
+ delete new_superversion;
+ }
+ // We may have lost data from the WritableFileBuffer in-memory buffer for
+ // the current log, so treat it as a fatal error and set bg_error
+ error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
+ // Read back bg_error in order to get the right severity
+ s = error_handler_.GetBGError();
+ return s;
+ }
+
+ for (auto loop_cfd : *versions_->GetColumnFamilySet()) {
+ // all this is just optimization to delete logs that
+ // are no longer needed -- if CF is empty, that means it
+ // doesn't need that particular log to stay alive, so we just
+ // advance the log number. no need to persist this in the manifest
+ if (loop_cfd->mem()->GetFirstSequenceNumber() == 0 &&
+ loop_cfd->imm()->NumNotFlushed() == 0) {
+ if (creating_new_log) {
+ loop_cfd->SetLogNumber(logfile_number_);
+ }
+ loop_cfd->mem()->SetCreationSeq(versions_->LastSequence());
+ }
+ }
+
+ cfd->mem()->SetNextLogNumber(logfile_number_);
+ cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
+ new_mem->Ref();
+ cfd->SetMemtable(new_mem);
+ InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
+ mutable_cf_options);
+#ifndef ROCKSDB_LITE
+ mutex_.Unlock();
+ // Notify client that memtable is sealed, now that we have successfully
+ // installed a new memtable
+ NotifyOnMemTableSealed(cfd, memtable_info);
+ mutex_.Lock();
+#endif // ROCKSDB_LITE
+ return s;
+}
+
+size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const {
+ mutex_.AssertHeld();
+ size_t bsize =
+ static_cast<size_t>(write_buffer_size / 10 + write_buffer_size);
+ // Some users might set very high write_buffer_size and rely on
+ // max_total_wal_size or other parameters to control the WAL size.
+ if (mutable_db_options_.max_total_wal_size > 0) {
+ bsize = std::min<size_t>(
+ bsize, static_cast<size_t>(mutable_db_options_.max_total_wal_size));
+ }
+ if (immutable_db_options_.db_write_buffer_size > 0) {
+ bsize = std::min<size_t>(bsize, immutable_db_options_.db_write_buffer_size);
+ }
+ if (immutable_db_options_.write_buffer_manager &&
+ immutable_db_options_.write_buffer_manager->enabled()) {
+ bsize = std::min<size_t>(
+ bsize, immutable_db_options_.write_buffer_manager->buffer_size());
+ }
+
+ return bsize;
+}
+
+// Default implementations of convenience methods that subclasses of DB
+// can call if they wish
+Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ if (nullptr == opt.timestamp) {
+ // Pre-allocate size of write batch conservatively.
+ // 8 bytes are taken by header, 4 bytes for count, 1 byte for type,
+ // and we allocate 11 extra bytes for key length, as well as value length.
+ WriteBatch batch(key.size() + value.size() + 24);
+ Status s = batch.Put(column_family, key, value);
+ if (!s.ok()) {
+ return s;
+ }
+ return Write(opt, &batch);
+ }
+ const Slice* ts = opt.timestamp;
+ assert(nullptr != ts);
+ size_t ts_sz = ts->size();
+ WriteBatch batch(key.size() + ts_sz + value.size() + 24, /*max_bytes=*/0,
+ ts_sz);
+ Status s = batch.Put(column_family, key, value);
+ if (!s.ok()) {
+ return s;
+ }
+ s = batch.AssignTimestamp(*ts);
+ if (!s.ok()) {
+ return s;
+ }
+ return Write(opt, &batch);
+}
+
+Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ WriteBatch batch;
+ batch.Delete(column_family, key);
+ return Write(opt, &batch);
+}
+
+Status DB::SingleDelete(const WriteOptions& opt,
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ WriteBatch batch;
+ batch.SingleDelete(column_family, key);
+ return Write(opt, &batch);
+}
+
+Status DB::DeleteRange(const WriteOptions& opt,
+ ColumnFamilyHandle* column_family,
+ const Slice& begin_key, const Slice& end_key) {
+ WriteBatch batch;
+ batch.DeleteRange(column_family, begin_key, end_key);
+ return Write(opt, &batch);
+}
+
+Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ WriteBatch batch;
+ Status s = batch.Merge(column_family, key, value);
+ if (!s.ok()) {
+ return s;
+ }
+ return Write(opt, &batch);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_impl/db_secondary_test.cc b/storage/rocksdb/rocksdb/db/db_impl/db_secondary_test.cc
new file mode 100644
index 00000000..0b34181d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_impl/db_secondary_test.cc
@@ -0,0 +1,869 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/stack_trace.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+class DBSecondaryTest : public DBTestBase {
+ public:
+ DBSecondaryTest()
+ : DBTestBase("/db_secondary_test"),
+ secondary_path_(),
+ handles_secondary_(),
+ db_secondary_(nullptr) {
+ secondary_path_ =
+ test::PerThreadDBPath(env_, "/db_secondary_test_secondary");
+ }
+
+ ~DBSecondaryTest() 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);
+
+ void OpenSecondaryWithColumnFamilies(
+ const std::vector<std::string>& column_families, const Options& options);
+
+ void CloseSecondary() {
+ for (auto h : handles_secondary_) {
+ db_secondary_->DestroyColumnFamilyHandle(h);
+ }
+ handles_secondary_.clear();
+ delete db_secondary_;
+ db_secondary_ = nullptr;
+ }
+
+ DBImplSecondary* db_secondary_full() {
+ return static_cast<DBImplSecondary*>(db_secondary_);
+ }
+
+ void CheckFileTypeCounts(const std::string& dir, int expected_log,
+ int expected_sst, int expected_manifest) const;
+
+ std::string secondary_path_;
+ std::vector<ColumnFamilyHandle*> handles_secondary_;
+ DB* db_secondary_;
+};
+
+void DBSecondaryTest::OpenSecondary(const Options& options) {
+ Status s =
+ DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_);
+ ASSERT_OK(s);
+}
+
+void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
+ const std::vector<std::string>& column_families, const Options& options) {
+ std::vector<ColumnFamilyDescriptor> 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 DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
+ int expected_log, int expected_sst,
+ int expected_manifest) const {
+ std::vector<std::string> filenames;
+ 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 == kLogFile);
+ 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);
+}
+
+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<DBImplSecondary*>(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, 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) {}
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& env_options) override {
+ class TracedRandomAccessFile : public RandomAccessFile {
+ public:
+ TracedRandomAccessFile(std::unique_ptr<RandomAccessFile>&& target,
+ std::atomic<int>& 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<RandomAccessFile> target_;
+ std::atomic<int>& 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<int> files_closed_{0};
+};
+} // 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<Env> traced_env(new TraceFileEnv(env_));
+ options1.env = traced_env.get();
+ OpenSecondary(options1);
+
+ static const auto verify_db = [&]() {
+ std::unique_ptr<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
+ std::unique_ptr<Iterator> 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<TraceFileEnv*>(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, OpenWithNonExistColumnFamily) {
+ Options options;
+ options.env = env_;
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ Options options1;
+ options1.env = env_;
+ options1.max_open_files = -1;
+ std::vector<ColumnFamilyDescriptor> 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"},
+ {"VersionSet::ProcessManifestWrites:AfterNewManifest",
+ "ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:"
+ "1"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ // Make sure db calls RecoverLogFiles so as to trigger a manifest write,
+ // which causes the db to switch to a new MANIFEST upon start.
+ port::Thread ro_db_thread([&]() {
+ Options options1;
+ options1.env = env_;
+ options1.max_open_files = -1;
+ 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) {
+ int table_files_not_exist = 0;
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ SyncPoint::GetInstance()->SetCallBack(
+ "ReactiveVersionSet::ApplyOneVersionEditToBuilder:AfterLoadTableHandlers",
+ [&](void* arg) {
+ Status s = *reinterpret_cast<Status*>(arg);
+ if (s.IsPathNotFound()) {
+ ++table_files_not_exist;
+ } else if (!s.ok()) {
+ assert(false); // Should not reach here
+ }
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ 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_EQ(options.level0_file_num_compaction_trigger, table_files_not_exist);
+ 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;
+ Reopen(options);
+
+ Options options1;
+ options1.env = env_;
+ options1.max_open_files = -1;
+ OpenSecondary(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<Iterator> 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.
+ Reopen(options);
+ ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+
+ Reopen(options);
+ ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
+
+ ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
+ range_scan_db();
+}
+
+// Here, "Snapshot" refers to the version edits written by
+// VersionSet::WriteSnapshot() at the beginning of the new MANIFEST after
+// switching from the old one.
+TEST_F(DBSecondaryTest, SkipSnapshotAfterManifestSwitch) {
+ Options options;
+ options.env = env_;
+ options.disable_auto_compactions = true;
+ Reopen(options);
+
+ Options options1;
+ options1.env = env_;
+ options1.max_open_files = -1;
+ OpenSecondary(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);
+
+ Reopen(options);
+ ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
+ ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
+}
+
+TEST_F(DBSecondaryTest, 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(
+ new SpecialSkipListFactory(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<Iterator> it1(db1->NewIterator(read_opts));
+ std::unique_ptr<Iterator> 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, 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(
+ new SpecialSkipListFactory(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<ColumnFamilyHandle*>& handles1,
+ DB* db2,
+ const std::vector<ColumnFamilyHandle*>& 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<Iterator> it1(db1->NewIterator(read_opts, handles1[i]));
+ std::unique_ptr<Iterator> 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) {
+ 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(
+ new SpecialSkipListFactory(kNumKeysPerMemtable));
+ Reopen(options);
+
+ Options options1;
+ options1.env = env_;
+ options1.max_open_files = -1;
+ OpenSecondary(options1);
+
+ WriteOptions write_opts;
+ WriteBatch wb;
+ wb.Put("key0", "value0");
+ wb.Put("key1", "value1");
+ ASSERT_OK(dbfull()->Write(write_opts, &wb));
+ ReadOptions read_opts;
+ std::unique_ptr<Iterator> 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());
+ std::unique_ptr<Iterator> 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_EQ("value1", iter2->value());
+
+ {
+ WriteBatch wb1;
+ wb1.Put("key0", "value01");
+ wb1.Put("key1", "value11");
+ ASSERT_OK(dbfull()->Write(write_opts, &wb1));
+ }
+
+ {
+ WriteBatch wb2;
+ wb2.Put("key0", "new_value0");
+ wb2.Delete("key1");
+ ASSERT_OK(dbfull()->Write(write_opts, &wb2));
+ }
+
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
+ std::unique_ptr<Iterator> 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());
+}
+
+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<Status*>(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);
+}
+#endif //! ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_info_dumper.cc b/storage/rocksdb/rocksdb/db/db_info_dumper.cc
new file mode 100644
index 00000000..7008ca6f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_info_dumper.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_info_dumper.h"
+
+#include <stdio.h>
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#include "file/filename.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void DumpDBFileSummary(const ImmutableDBOptions& options,
+ const std::string& dbname) {
+ if (options.info_log == nullptr) {
+ return;
+ }
+
+ auto* env = options.env;
+ uint64_t number = 0;
+ FileType type = kInfoLogFile;
+
+ std::vector<std::string> files;
+ uint64_t file_num = 0;
+ uint64_t file_size;
+ std::string file_info, wal_info;
+
+ Header(options.info_log, "DB SUMMARY\n");
+ // Get files in dbname dir
+ if (!env->GetChildren(dbname, &files).ok()) {
+ Error(options.info_log,
+ "Error when reading %s dir\n", dbname.c_str());
+ }
+ std::sort(files.begin(), files.end());
+ for (const std::string& file : files) {
+ if (!ParseFileName(file, &number, &type)) {
+ continue;
+ }
+ switch (type) {
+ case kCurrentFile:
+ Header(options.info_log, "CURRENT file: %s\n", file.c_str());
+ break;
+ case kIdentityFile:
+ Header(options.info_log, "IDENTITY file: %s\n", file.c_str());
+ break;
+ case kDescriptorFile:
+ env->GetFileSize(dbname + "/" + file, &file_size);
+ Header(options.info_log, "MANIFEST file: %s size: %" PRIu64 " Bytes\n",
+ file.c_str(), file_size);
+ break;
+ case kLogFile:
+ env->GetFileSize(dbname + "/" + file, &file_size);
+ char str[16];
+ snprintf(str, sizeof(str), "%" PRIu64, file_size);
+ wal_info.append(file).append(" size: ").
+ append(str).append(" ; ");
+ break;
+ case kTableFile:
+ if (++file_num < 10) {
+ file_info.append(file).append(" ");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Get sst files in db_path dir
+ for (auto& db_path : options.db_paths) {
+ if (dbname.compare(db_path.path) != 0) {
+ if (!env->GetChildren(db_path.path, &files).ok()) {
+ Error(options.info_log,
+ "Error when reading %s dir\n",
+ db_path.path.c_str());
+ continue;
+ }
+ std::sort(files.begin(), files.end());
+ for (const std::string& file : files) {
+ if (ParseFileName(file, &number, &type)) {
+ if (type == kTableFile && ++file_num < 10) {
+ file_info.append(file).append(" ");
+ }
+ }
+ }
+ }
+ Header(options.info_log,
+ "SST files in %s dir, Total Num: %" PRIu64 ", files: %s\n",
+ db_path.path.c_str(), file_num, file_info.c_str());
+ file_num = 0;
+ file_info.clear();
+ }
+
+ // Get wal file in wal_dir
+ if (dbname.compare(options.wal_dir) != 0) {
+ if (!env->GetChildren(options.wal_dir, &files).ok()) {
+ Error(options.info_log,
+ "Error when reading %s dir\n",
+ options.wal_dir.c_str());
+ return;
+ }
+ wal_info.clear();
+ for (const std::string& file : files) {
+ if (ParseFileName(file, &number, &type)) {
+ if (type == kLogFile) {
+ env->GetFileSize(options.wal_dir + "/" + file, &file_size);
+ char str[16];
+ snprintf(str, sizeof(str), "%" PRIu64, file_size);
+ wal_info.append(file).append(" size: ").
+ append(str).append(" ; ");
+ }
+ }
+ }
+ }
+ Header(options.info_log, "Write Ahead Log file in %s: %s\n",
+ options.wal_dir.c_str(), wal_info.c_str());
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_info_dumper.h b/storage/rocksdb/rocksdb/db/db_info_dumper.h
new file mode 100644
index 00000000..91404cbd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_info_dumper.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "options/db_options.h"
+
+namespace ROCKSDB_NAMESPACE {
+void DumpDBFileSummary(const ImmutableDBOptions& options,
+ const std::string& dbname);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_inplace_update_test.cc b/storage/rocksdb/rocksdb/db/db_inplace_update_test.cc
new file mode 100644
index 00000000..26405864
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_inplace_update_test.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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") {}
+};
+
+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, 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());
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_io_failure_test.cc b/storage/rocksdb/rocksdb/db/db_io_failure_test.cc
new file mode 100644
index 00000000..f8d56244
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_io_failure_test.cc
@@ -0,0 +1,568 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 DBIOFailureTest : public DBTestBase {
+ public:
+ DBIOFailureTest() : DBTestBase("/db_io_failure_test") {}
+};
+
+#ifndef ROCKSDB_LITE
+// 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_->no_slowdown_ = true;
+ 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;
+ }
+ dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr,
+ true /* disallow trivial move */);
+ }
+ } else {
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ }
+ }
+
+ 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);
+ ASSERT_LT(CountFiles(), 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);
+
+ dbfull()->TEST_FlushMemTable(true);
+
+ 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());
+}
+#endif // ROCKSDB_LITE
+
+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());
+}
+
+#ifndef ROCKSDB_LITE
+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<bool>* 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)
+ 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);
+ 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);
+ dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
+ 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);
+ Status s;
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "foo1", "bar1"));
+ // simulate error
+ env_->log_write_error_.store(true, std::memory_order_release);
+ s = Put(1, "foo2", "bar2");
+ ASSERT_TRUE(!s.ok());
+ env_->log_write_error_.store(false, std::memory_order_release);
+ s = Put(1, "foo3", "bar3");
+ // the next put should fail, too
+ ASSERT_TRUE(!s.ok());
+ // 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);
+ s = Put(1, "foo2", "bar2");
+ ASSERT_TRUE(!s.ok());
+ env_->log_write_error_.store(false, std::memory_order_release);
+ s = Put(1, "foo3", "bar3");
+ // the next put should NOT fail
+ ASSERT_TRUE(s.ok());
+}
+#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(new SpecialSkipListFactory(10));
+ BlockBasedTableOptions table_options;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Status s;
+
+ std::atomic<int> 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<Status*>(arg);
+ *st = Status::IOError("range sync dummy error");
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Random rnd(301);
+ std::string rnd_str =
+ RandomString(&rnd, static_cast<int>(options.bytes_per_sync / 2));
+ std::string rnd_str_512kb = RandomString(&rnd, 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"));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+
+ // 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);
+ Status s;
+
+ Random rnd(301);
+ std::string rnd_str =
+ RandomString(&rnd, static_cast<int>(options.bytes_per_sync / 2));
+ std::string rnd_str_512kb = RandomString(&rnd, 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));
+ 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"));
+ Flush(1);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+
+ std::atomic<int> 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<Status*>(arg);
+ *st = Status::IOError("range sync dummy error");
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(dbfull()->SetOptions(handles_[1],
+ {
+ {"disable_auto_compactions", "false"},
+ }));
+ dbfull()->TEST_WaitForCompact();
+
+ // 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(new SpecialSkipListFactory(2));
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Status s;
+ std::atomic<int> close_called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SpecialEnv::SStableFile::Close", [&](void* arg) {
+ if (close_called.fetch_add(1) == 0) {
+ Status* st = static_cast<Status*>(arg);
+ *st = Status::IOError("close dummy error");
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "foo1", "bar1"));
+ ASSERT_OK(Put(1, "foo", "bar2"));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+
+ // 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);
+ Status s;
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ ASSERT_OK(Put(1, "foo", "bar2"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ ASSERT_OK(Put(1, "foo", "bar3"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ dbfull()->TEST_WaitForCompact();
+
+ std::atomic<int> close_called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SpecialEnv::SStableFile::Close", [&](void* arg) {
+ if (close_called.fetch_add(1) == 0) {
+ Status* st = static_cast<Status*>(arg);
+ *st = Status::IOError("close dummy error");
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ASSERT_OK(dbfull()->SetOptions(handles_[1],
+ {
+ {"disable_auto_compactions", "false"},
+ }));
+ dbfull()->TEST_WaitForCompact();
+
+ // 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(new SpecialSkipListFactory(2));
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Status s;
+ std::atomic<int> sync_called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SpecialEnv::SStableFile::Sync", [&](void* arg) {
+ if (sync_called.fetch_add(1) == 0) {
+ Status* st = static_cast<Status*>(arg);
+ *st = Status::IOError("sync dummy error");
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "foo1", "bar1"));
+ ASSERT_OK(Put(1, "foo", "bar2"));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+
+ // 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);
+ Status s;
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ ASSERT_OK(Put(1, "foo", "bar2"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ ASSERT_OK(Put(1, "foo", "bar3"));
+ ASSERT_OK(Put(1, "foo2", "bar"));
+ Flush(1);
+ dbfull()->TEST_WaitForCompact();
+
+ std::atomic<int> sync_called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SpecialEnv::SStableFile::Sync", [&](void* arg) {
+ if (sync_called.fetch_add(1) == 0) {
+ Status* st = static_cast<Status*>(arg);
+ *st = Status::IOError("close dummy error");
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ASSERT_OK(dbfull()->SetOptions(handles_[1],
+ {
+ {"disable_auto_compactions", "false"},
+ }));
+ dbfull()->TEST_WaitForCompact();
+
+ // 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)
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_iter.cc b/storage/rocksdb/rocksdb/db/db_iter.cc
new file mode 100644
index 00000000..e5d40294
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_iter.cc
@@ -0,0 +1,1310 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_iter.h"
+#include <string>
+#include <iostream>
+#include <limits>
+
+#include "db/dbformat.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "db/pinned_iterators_manager.h"
+#include "file/filename.h"
+#include "logging/logging.h"
+#include "memory/arena.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/options.h"
+#include "table/internal_iterator.h"
+#include "table/iterator_wrapper.h"
+#include "trace_replay/trace_replay.h"
+#include "util/mutexlock.h"
+#include "util/string_util.h"
+#include "util/user_comparator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+DBIter::DBIter(Env* _env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ const Comparator* cmp, InternalIterator* iter, SequenceNumber s,
+ bool arena_mode, uint64_t max_sequential_skip_in_iterations,
+ ReadCallback* read_callback, DBImpl* db_impl,
+ ColumnFamilyData* cfd, bool allow_blob)
+ : prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
+ env_(_env),
+ logger_(cf_options.info_log),
+ user_comparator_(cmp),
+ merge_operator_(cf_options.merge_operator),
+ iter_(iter),
+ read_callback_(read_callback),
+ sequence_(s),
+ statistics_(cf_options.statistics),
+ num_internal_keys_skipped_(0),
+ iterate_lower_bound_(read_options.iterate_lower_bound),
+ iterate_upper_bound_(read_options.iterate_upper_bound),
+ direction_(kForward),
+ valid_(false),
+ current_entry_is_merged_(false),
+ is_key_seqnum_zero_(false),
+ prefix_same_as_start_(mutable_cf_options.prefix_extractor
+ ? read_options.prefix_same_as_start
+ : false),
+ pin_thru_lifetime_(read_options.pin_data),
+ expect_total_order_inner_iter_(prefix_extractor_ == nullptr ||
+ read_options.total_order_seek ||
+ read_options.auto_prefix_mode),
+ allow_blob_(allow_blob),
+ is_blob_(false),
+ arena_mode_(arena_mode),
+ range_del_agg_(&cf_options.internal_comparator, s),
+ db_impl_(db_impl),
+ cfd_(cfd),
+ start_seqnum_(read_options.iter_start_seqnum) {
+ RecordTick(statistics_, NO_ITERATOR_CREATED);
+ max_skip_ = max_sequential_skip_in_iterations;
+ max_skippable_internal_keys_ = read_options.max_skippable_internal_keys;
+ if (pin_thru_lifetime_) {
+ pinned_iters_mgr_.StartPinning();
+ }
+ if (iter_.iter()) {
+ iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
+ }
+}
+
+Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
+ if (prop == nullptr) {
+ return Status::InvalidArgument("prop is nullptr");
+ }
+ if (prop_name == "rocksdb.iterator.super-version-number") {
+ // First try to pass the value returned from inner iterator.
+ return iter_.iter()->GetProperty(prop_name, prop);
+ } else if (prop_name == "rocksdb.iterator.is-key-pinned") {
+ if (valid_) {
+ *prop = (pin_thru_lifetime_ && saved_key_.IsKeyPinned()) ? "1" : "0";
+ } else {
+ *prop = "Iterator is not valid.";
+ }
+ return Status::OK();
+ } else if (prop_name == "rocksdb.iterator.internal-key") {
+ *prop = saved_key_.GetUserKey().ToString();
+ return Status::OK();
+ }
+ return Status::InvalidArgument("Unidentified property.");
+}
+
+bool DBIter::ParseKey(ParsedInternalKey* ikey) {
+ if (!ParseInternalKey(iter_.key(), ikey)) {
+ status_ = Status::Corruption("corrupted internal key in DBIter");
+ valid_ = false;
+ ROCKS_LOG_ERROR(logger_, "corrupted internal key in DBIter: %s",
+ iter_.key().ToString(true).c_str());
+ return false;
+ } else {
+ return true;
+ }
+}
+
+void DBIter::Next() {
+ assert(valid_);
+ assert(status_.ok());
+
+ PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, env_);
+ // Release temporarily pinned blocks from last operation
+ ReleaseTempPinnedData();
+ local_stats_.skip_count_ += num_internal_keys_skipped_;
+ local_stats_.skip_count_--;
+ num_internal_keys_skipped_ = 0;
+ bool ok = true;
+ if (direction_ == kReverse) {
+ is_key_seqnum_zero_ = false;
+ if (!ReverseToForward()) {
+ ok = false;
+ }
+ } else if (!current_entry_is_merged_) {
+ // If the current value is not a merge, the iter position is the
+ // current key, which is already returned. We can safely issue a
+ // Next() without checking the current key.
+ // If the current key is a merge, very likely iter already points
+ // to the next internal position.
+ assert(iter_.Valid());
+ iter_.Next();
+ PERF_COUNTER_ADD(internal_key_skipped_count, 1);
+ }
+
+ local_stats_.next_count_++;
+ if (ok && iter_.Valid()) {
+ Slice prefix;
+ if (prefix_same_as_start_) {
+ assert(prefix_extractor_ != nullptr);
+ prefix = prefix_.GetUserKey();
+ }
+ FindNextUserEntry(true /* skipping the current user key */,
+ prefix_same_as_start_ ? &prefix : nullptr);
+ } else {
+ is_key_seqnum_zero_ = false;
+ valid_ = false;
+ }
+ if (statistics_ != nullptr && valid_) {
+ local_stats_.next_found_count_++;
+ local_stats_.bytes_read_ += (key().size() + value().size());
+ }
+}
+
+// PRE: saved_key_ has the current user key if skipping_saved_key
+// POST: saved_key_ should have the next user key if valid_,
+// if the current entry is a result of merge
+// current_entry_is_merged_ => true
+// saved_value_ => the merged value
+//
+// NOTE: In between, saved_key_ can point to a user key that has
+// a delete marker or a sequence number higher than sequence_
+// saved_key_ MUST have a proper user_key before calling this function
+//
+// The prefix parameter, if not null, indicates that we need to iterate
+// within the prefix, and the iterator needs to be made invalid, if no
+// more entry for the prefix can be found.
+bool DBIter::FindNextUserEntry(bool skipping_saved_key, const Slice* prefix) {
+ PERF_TIMER_GUARD(find_next_user_entry_time);
+ return FindNextUserEntryInternal(skipping_saved_key, prefix);
+}
+
+// Actual implementation of DBIter::FindNextUserEntry()
+bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
+ const Slice* prefix) {
+ // Loop until we hit an acceptable entry to yield
+ assert(iter_.Valid());
+ assert(status_.ok());
+ assert(direction_ == kForward);
+ current_entry_is_merged_ = false;
+
+ // How many times in a row we have skipped an entry with user key less than
+ // or equal to saved_key_. We could skip these entries either because
+ // sequence numbers were too high or because skipping_saved_key = true.
+ // What saved_key_ contains throughout this method:
+ // - if skipping_saved_key : saved_key_ contains the key that we need
+ // to skip, and we haven't seen any keys greater
+ // than that,
+ // - if num_skipped > 0 : saved_key_ contains the key that we have skipped
+ // num_skipped times, and we haven't seen any keys
+ // greater than that,
+ // - none of the above : saved_key_ can contain anything, it doesn't
+ // matter.
+ uint64_t num_skipped = 0;
+ // For write unprepared, the target sequence number in reseek could be larger
+ // than the snapshot, and thus needs to be skipped again. This could result in
+ // an infinite loop of reseeks. To avoid that, we limit the number of reseeks
+ // to one.
+ bool reseek_done = false;
+
+ is_blob_ = false;
+
+ do {
+ // Will update is_key_seqnum_zero_ as soon as we parsed the current key
+ // but we need to save the previous value to be used in the loop.
+ bool is_prev_key_seqnum_zero = is_key_seqnum_zero_;
+ if (!ParseKey(&ikey_)) {
+ is_key_seqnum_zero_ = false;
+ return false;
+ }
+
+ is_key_seqnum_zero_ = (ikey_.sequence == 0);
+
+ assert(iterate_upper_bound_ == nullptr || iter_.MayBeOutOfUpperBound() ||
+ user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) < 0);
+ if (iterate_upper_bound_ != nullptr && iter_.MayBeOutOfUpperBound() &&
+ user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
+ break;
+ }
+
+ assert(prefix == nullptr || prefix_extractor_ != nullptr);
+ if (prefix != nullptr &&
+ prefix_extractor_->Transform(ikey_.user_key).compare(*prefix) != 0) {
+ assert(prefix_same_as_start_);
+ break;
+ }
+
+ if (TooManyInternalKeysSkipped()) {
+ return false;
+ }
+
+ if (IsVisible(ikey_.sequence)) {
+ // If the previous entry is of seqnum 0, the current entry will not
+ // possibly be skipped. This condition can potentially be relaxed to
+ // prev_key.seq <= ikey_.sequence. We are cautious because it will be more
+ // prone to bugs causing the same user key with the same sequence number.
+ if (!is_prev_key_seqnum_zero && skipping_saved_key &&
+ user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey()) <=
+ 0) {
+ num_skipped++; // skip this entry
+ PERF_COUNTER_ADD(internal_key_skipped_count, 1);
+ } else {
+ assert(!skipping_saved_key ||
+ user_comparator_.Compare(ikey_.user_key,
+ saved_key_.GetUserKey()) > 0);
+ num_skipped = 0;
+ reseek_done = false;
+ switch (ikey_.type) {
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ // Arrange to skip all upcoming entries for this key since
+ // they are hidden by this deletion.
+ // if iterartor specified start_seqnum we
+ // 1) return internal key, including the type
+ // 2) return ikey only if ikey.seqnum >= start_seqnum_
+ // note that if deletion seqnum is < start_seqnum_ we
+ // just skip it like in normal iterator.
+ if (start_seqnum_ > 0 && ikey_.sequence >= start_seqnum_) {
+ saved_key_.SetInternalKey(ikey_);
+ valid_ = true;
+ return true;
+ } else {
+ saved_key_.SetUserKey(
+ ikey_.user_key, !pin_thru_lifetime_ ||
+ !iter_.iter()->IsKeyPinned() /* copy */);
+ skipping_saved_key = true;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ }
+ break;
+ case kTypeValue:
+ case kTypeBlobIndex:
+ if (start_seqnum_ > 0) {
+ // we are taking incremental snapshot here
+ // incremental snapshots aren't supported on DB with range deletes
+ assert(ikey_.type != kTypeBlobIndex);
+ if (ikey_.sequence >= start_seqnum_) {
+ saved_key_.SetInternalKey(ikey_);
+ valid_ = true;
+ return true;
+ } else {
+ // this key and all previous versions shouldn't be included,
+ // skipping_saved_key
+ saved_key_.SetUserKey(
+ ikey_.user_key,
+ !pin_thru_lifetime_ ||
+ !iter_.iter()->IsKeyPinned() /* copy */);
+ skipping_saved_key = true;
+ }
+ } else {
+ saved_key_.SetUserKey(
+ ikey_.user_key, !pin_thru_lifetime_ ||
+ !iter_.iter()->IsKeyPinned() /* copy */);
+ if (range_del_agg_.ShouldDelete(
+ ikey_, RangeDelPositioningMode::kForwardTraversal)) {
+ // Arrange to skip all upcoming entries for this key since
+ // they are hidden by this deletion.
+ skipping_saved_key = true;
+ num_skipped = 0;
+ reseek_done = false;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ } else if (ikey_.type == kTypeBlobIndex) {
+ if (!allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ valid_ = false;
+ return false;
+ }
+
+ is_blob_ = true;
+ valid_ = true;
+ return true;
+ } else {
+ valid_ = true;
+ return true;
+ }
+ }
+ break;
+ case kTypeMerge:
+ saved_key_.SetUserKey(
+ ikey_.user_key,
+ !pin_thru_lifetime_ || !iter_.iter()->IsKeyPinned() /* copy */);
+ if (range_del_agg_.ShouldDelete(
+ ikey_, RangeDelPositioningMode::kForwardTraversal)) {
+ // Arrange to skip all upcoming entries for this key since
+ // they are hidden by this deletion.
+ skipping_saved_key = true;
+ num_skipped = 0;
+ reseek_done = false;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ } else {
+ // By now, we are sure the current ikey is going to yield a
+ // value
+ current_entry_is_merged_ = true;
+ valid_ = true;
+ return MergeValuesNewToOld(); // Go to a different state machine
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ } else {
+ PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
+
+ // This key was inserted after our snapshot was taken.
+ // If this happens too many times in a row for the same user key, we want
+ // to seek to the target sequence number.
+ int cmp =
+ user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey());
+ if (cmp == 0 || (skipping_saved_key && cmp < 0)) {
+ num_skipped++;
+ } else {
+ saved_key_.SetUserKey(
+ ikey_.user_key,
+ !iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
+ skipping_saved_key = false;
+ num_skipped = 0;
+ reseek_done = false;
+ }
+ }
+
+ // If we have sequentially iterated via numerous equal keys, then it's
+ // better to seek so that we can avoid too many key comparisons.
+ //
+ // To avoid infinite loops, do not reseek if we have already attempted to
+ // reseek previously.
+ //
+ // TODO(lth): If we reseek to sequence number greater than ikey_.sequence,
+ // then it does not make sense to reseek as we would actually land further
+ // away from the desired key. There is opportunity for optimization here.
+ if (num_skipped > max_skip_ && !reseek_done) {
+ is_key_seqnum_zero_ = false;
+ num_skipped = 0;
+ reseek_done = true;
+ std::string last_key;
+ if (skipping_saved_key) {
+ // We're looking for the next user-key but all we see are the same
+ // user-key with decreasing sequence numbers. Fast forward to
+ // sequence number 0 and type deletion (the smallest type).
+ AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
+ 0, kTypeDeletion));
+ // Don't set skipping_saved_key = false because we may still see more
+ // user-keys equal to saved_key_.
+ } else {
+ // We saw multiple entries with this user key and sequence numbers
+ // higher than sequence_. Fast forward to sequence_.
+ // Note that this only covers a case when a higher key was overwritten
+ // many times since our snapshot was taken, not the case when a lot of
+ // different keys were inserted after our snapshot was taken.
+ AppendInternalKey(&last_key,
+ ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
+ kValueTypeForSeek));
+ }
+ iter_.Seek(last_key);
+ RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
+ } else {
+ iter_.Next();
+ }
+ } while (iter_.Valid());
+
+ valid_ = false;
+ return iter_.status().ok();
+}
+
+// Merge values of the same user key starting from the current iter_ position
+// Scan from the newer entries to older entries.
+// PRE: iter_.key() points to the first merge type entry
+// saved_key_ stores the user key
+// POST: saved_value_ has the merged value for the user key
+// iter_ points to the next entry (or invalid)
+bool DBIter::MergeValuesNewToOld() {
+ if (!merge_operator_) {
+ ROCKS_LOG_ERROR(logger_, "Options::merge_operator is null.");
+ status_ = Status::InvalidArgument("merge_operator_ must be set.");
+ valid_ = false;
+ return false;
+ }
+
+ // Temporarily pin the blocks that hold merge operands
+ TempPinData();
+ merge_context_.Clear();
+ // Start the merge process by pushing the first operand
+ merge_context_.PushOperand(
+ iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
+ TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
+
+ ParsedInternalKey ikey;
+ Status s;
+ for (iter_.Next(); iter_.Valid(); iter_.Next()) {
+ TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+
+ if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
+ // hit the next user key, stop right here
+ break;
+ } else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
+ range_del_agg_.ShouldDelete(
+ ikey, RangeDelPositioningMode::kForwardTraversal)) {
+ // hit a delete with the same user key, stop right here
+ // iter_ is positioned after delete
+ iter_.Next();
+ break;
+ } else if (kTypeValue == ikey.type) {
+ // hit a put, merge the put value with operands and store the
+ // final result in saved_value_. We are done!
+ const Slice val = iter_.value();
+ s = MergeHelper::TimedFullMerge(
+ merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
+ &saved_value_, logger_, statistics_, env_, &pinned_value_, true);
+ if (!s.ok()) {
+ valid_ = false;
+ status_ = s;
+ return false;
+ }
+ // iter_ is positioned after put
+ iter_.Next();
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+ return true;
+ } else if (kTypeMerge == ikey.type) {
+ // hit a merge, add the value as an operand and run associative merge.
+ // when complete, add result to operands and continue.
+ merge_context_.PushOperand(
+ iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
+ PERF_COUNTER_ADD(internal_merge_count, 1);
+ } else if (kTypeBlobIndex == ikey.type) {
+ if (!allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ } else {
+ status_ =
+ Status::NotSupported("Blob DB does not support merge operator.");
+ }
+ valid_ = false;
+ return false;
+ } else {
+ assert(false);
+ }
+ }
+
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+
+ // we either exhausted all internal keys under this user key, or hit
+ // a deletion marker.
+ // feed null as the existing value to the merge operator, such that
+ // client can differentiate this scenario and do things accordingly.
+ s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetUserKey(),
+ nullptr, merge_context_.GetOperands(),
+ &saved_value_, logger_, statistics_, env_,
+ &pinned_value_, true);
+ if (!s.ok()) {
+ valid_ = false;
+ status_ = s;
+ return false;
+ }
+
+ assert(status_.ok());
+ return true;
+}
+
+void DBIter::Prev() {
+ assert(valid_);
+ assert(status_.ok());
+
+ PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, env_);
+ ReleaseTempPinnedData();
+ ResetInternalKeysSkippedCounter();
+ bool ok = true;
+ if (direction_ == kForward) {
+ if (!ReverseToBackward()) {
+ ok = false;
+ }
+ }
+ if (ok) {
+ Slice prefix;
+ if (prefix_same_as_start_) {
+ assert(prefix_extractor_ != nullptr);
+ prefix = prefix_.GetUserKey();
+ }
+ PrevInternal(prefix_same_as_start_ ? &prefix : nullptr);
+ }
+
+ if (statistics_ != nullptr) {
+ local_stats_.prev_count_++;
+ if (valid_) {
+ local_stats_.prev_found_count_++;
+ local_stats_.bytes_read_ += (key().size() + value().size());
+ }
+ }
+}
+
+bool DBIter::ReverseToForward() {
+ assert(iter_.status().ok());
+
+ // When moving backwards, iter_ is positioned on _previous_ key, which may
+ // not exist or may have different prefix than the current key().
+ // If that's the case, seek iter_ to current key.
+ if (!expect_total_order_inner_iter() || !iter_.Valid()) {
+ IterKey last_key;
+ last_key.SetInternalKey(ParsedInternalKey(
+ saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
+ iter_.Seek(last_key.GetInternalKey());
+ }
+
+ direction_ = kForward;
+ // Skip keys less than the current key() (a.k.a. saved_key_).
+ while (iter_.Valid()) {
+ ParsedInternalKey ikey;
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+ if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) >= 0) {
+ return true;
+ }
+ iter_.Next();
+ }
+
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+// Move iter_ to the key before saved_key_.
+bool DBIter::ReverseToBackward() {
+ assert(iter_.status().ok());
+
+ // When current_entry_is_merged_ is true, iter_ may be positioned on the next
+ // key, which may not exist or may have prefix different from current.
+ // If that's the case, seek to saved_key_.
+ if (current_entry_is_merged_ &&
+ (!expect_total_order_inner_iter() || !iter_.Valid())) {
+ IterKey last_key;
+ // Using kMaxSequenceNumber and kValueTypeForSeek
+ // (not kValueTypeForSeekForPrev) to seek to a key strictly smaller
+ // than saved_key_.
+ last_key.SetInternalKey(ParsedInternalKey(
+ saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
+ if (!expect_total_order_inner_iter()) {
+ iter_.SeekForPrev(last_key.GetInternalKey());
+ } else {
+ // Some iterators may not support SeekForPrev(), so we avoid using it
+ // when prefix seek mode is disabled. This is somewhat expensive
+ // (an extra Prev(), as well as an extra change of direction of iter_),
+ // so we may need to reconsider it later.
+ iter_.Seek(last_key.GetInternalKey());
+ if (!iter_.Valid() && iter_.status().ok()) {
+ iter_.SeekToLast();
+ }
+ }
+ }
+
+ direction_ = kReverse;
+ return FindUserKeyBeforeSavedKey();
+}
+
+void DBIter::PrevInternal(const Slice* prefix) {
+ while (iter_.Valid()) {
+ saved_key_.SetUserKey(
+ ExtractUserKey(iter_.key()),
+ !iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
+
+ assert(prefix == nullptr || prefix_extractor_ != nullptr);
+ if (prefix != nullptr &&
+ prefix_extractor_->Transform(saved_key_.GetUserKey())
+ .compare(*prefix) != 0) {
+ assert(prefix_same_as_start_);
+ // Current key does not have the same prefix as start
+ valid_ = false;
+ return;
+ }
+
+ assert(iterate_lower_bound_ == nullptr || iter_.MayBeOutOfLowerBound() ||
+ user_comparator_.Compare(saved_key_.GetUserKey(),
+ *iterate_lower_bound_) >= 0);
+ if (iterate_lower_bound_ != nullptr && iter_.MayBeOutOfLowerBound() &&
+ user_comparator_.Compare(saved_key_.GetUserKey(),
+ *iterate_lower_bound_) < 0) {
+ // We've iterated earlier than the user-specified lower bound.
+ valid_ = false;
+ return;
+ }
+
+ if (!FindValueForCurrentKey()) { // assigns valid_
+ return;
+ }
+
+ // Whether or not we found a value for current key, we need iter_ to end up
+ // on a smaller key.
+ if (!FindUserKeyBeforeSavedKey()) {
+ return;
+ }
+
+ if (valid_) {
+ // Found the value.
+ return;
+ }
+
+ if (TooManyInternalKeysSkipped(false)) {
+ return;
+ }
+ }
+
+ // We haven't found any key - iterator is not valid
+ valid_ = false;
+}
+
+// Used for backwards iteration.
+// Looks at the entries with user key saved_key_ and finds the most up-to-date
+// value for it, or executes a merge, or determines that the value was deleted.
+// Sets valid_ to true if the value is found and is ready to be presented to
+// the user through value().
+// Sets valid_ to false if the value was deleted, and we should try another key.
+// Returns false if an error occurred, and !status().ok() and !valid_.
+//
+// PRE: iter_ is positioned on the last entry with user key equal to saved_key_.
+// POST: iter_ is positioned on one of the entries equal to saved_key_, or on
+// the entry just before them, or on the entry just after them.
+bool DBIter::FindValueForCurrentKey() {
+ assert(iter_.Valid());
+ merge_context_.Clear();
+ current_entry_is_merged_ = false;
+ // last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
+ // kTypeValue)
+ ValueType last_not_merge_type = kTypeDeletion;
+ ValueType last_key_entry_type = kTypeDeletion;
+
+ // Temporarily pin blocks that hold (merge operands / the value)
+ ReleaseTempPinnedData();
+ TempPinData();
+ size_t num_skipped = 0;
+ while (iter_.Valid()) {
+ ParsedInternalKey ikey;
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+
+ if (!IsVisible(ikey.sequence) ||
+ !user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
+ break;
+ }
+ if (TooManyInternalKeysSkipped()) {
+ return false;
+ }
+
+ // This user key has lots of entries.
+ // We're going from old to new, and it's taking too long. Let's do a Seek()
+ // and go from new to old. This helps when a key was overwritten many times.
+ if (num_skipped >= max_skip_) {
+ return FindValueForCurrentKeyUsingSeek();
+ }
+
+ last_key_entry_type = ikey.type;
+ switch (last_key_entry_type) {
+ case kTypeValue:
+ case kTypeBlobIndex:
+ if (range_del_agg_.ShouldDelete(
+ ikey, RangeDelPositioningMode::kBackwardTraversal)) {
+ last_key_entry_type = kTypeRangeDeletion;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ } else {
+ assert(iter_.iter()->IsValuePinned());
+ pinned_value_ = iter_.value();
+ }
+ merge_context_.Clear();
+ last_not_merge_type = last_key_entry_type;
+ break;
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ merge_context_.Clear();
+ last_not_merge_type = last_key_entry_type;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ break;
+ case kTypeMerge:
+ if (range_del_agg_.ShouldDelete(
+ ikey, RangeDelPositioningMode::kBackwardTraversal)) {
+ merge_context_.Clear();
+ last_key_entry_type = kTypeRangeDeletion;
+ last_not_merge_type = last_key_entry_type;
+ PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
+ } else {
+ assert(merge_operator_ != nullptr);
+ merge_context_.PushOperandBack(
+ iter_.value(),
+ iter_.iter()->IsValuePinned() /* operand_pinned */);
+ PERF_COUNTER_ADD(internal_merge_count, 1);
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ PERF_COUNTER_ADD(internal_key_skipped_count, 1);
+ iter_.Prev();
+ ++num_skipped;
+ }
+
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+
+ Status s;
+ is_blob_ = false;
+ switch (last_key_entry_type) {
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ case kTypeRangeDeletion:
+ valid_ = false;
+ return true;
+ case kTypeMerge:
+ current_entry_is_merged_ = true;
+ if (last_not_merge_type == kTypeDeletion ||
+ last_not_merge_type == kTypeSingleDeletion ||
+ last_not_merge_type == kTypeRangeDeletion) {
+ s = MergeHelper::TimedFullMerge(
+ merge_operator_, saved_key_.GetUserKey(), nullptr,
+ merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
+ env_, &pinned_value_, true);
+ } else if (last_not_merge_type == kTypeBlobIndex) {
+ if (!allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ } else {
+ status_ =
+ Status::NotSupported("Blob DB does not support merge operator.");
+ }
+ valid_ = false;
+ return false;
+ } else {
+ assert(last_not_merge_type == kTypeValue);
+ s = MergeHelper::TimedFullMerge(
+ merge_operator_, saved_key_.GetUserKey(), &pinned_value_,
+ merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
+ env_, &pinned_value_, true);
+ }
+ break;
+ case kTypeValue:
+ // do nothing - we've already has value in pinned_value_
+ break;
+ case kTypeBlobIndex:
+ if (!allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ valid_ = false;
+ return false;
+ }
+ is_blob_ = true;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ if (!s.ok()) {
+ valid_ = false;
+ status_ = s;
+ return false;
+ }
+ valid_ = true;
+ return true;
+}
+
+// This function is used in FindValueForCurrentKey.
+// We use Seek() function instead of Prev() to find necessary value
+// TODO: This is very similar to FindNextUserEntry() and MergeValuesNewToOld().
+// Would be nice to reuse some code.
+bool DBIter::FindValueForCurrentKeyUsingSeek() {
+ // FindValueForCurrentKey will enable pinning before calling
+ // FindValueForCurrentKeyUsingSeek()
+ assert(pinned_iters_mgr_.PinningEnabled());
+ std::string last_key;
+ AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
+ sequence_, kValueTypeForSeek));
+ iter_.Seek(last_key);
+ RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
+
+ // In case read_callback presents, the value we seek to may not be visible.
+ // Find the next value that's visible.
+ ParsedInternalKey ikey;
+ is_blob_ = false;
+ while (true) {
+ if (!iter_.Valid()) {
+ valid_ = false;
+ return iter_.status().ok();
+ }
+
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+ if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
+ // No visible values for this key, even though FindValueForCurrentKey()
+ // has seen some. This is possible if we're using a tailing iterator, and
+ // the entries were discarded in a compaction.
+ valid_ = false;
+ return true;
+ }
+
+ if (IsVisible(ikey.sequence)) {
+ break;
+ }
+
+ iter_.Next();
+ }
+
+ if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
+ range_del_agg_.ShouldDelete(
+ ikey, RangeDelPositioningMode::kBackwardTraversal)) {
+ valid_ = false;
+ return true;
+ }
+ if (ikey.type == kTypeBlobIndex && !allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ valid_ = false;
+ return false;
+ }
+ if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) {
+ assert(iter_.iter()->IsValuePinned());
+ pinned_value_ = iter_.value();
+ is_blob_ = (ikey.type == kTypeBlobIndex);
+ valid_ = true;
+ return true;
+ }
+
+ // kTypeMerge. We need to collect all kTypeMerge values and save them
+ // in operands
+ assert(ikey.type == kTypeMerge);
+ current_entry_is_merged_ = true;
+ merge_context_.Clear();
+ merge_context_.PushOperand(
+ iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
+ while (true) {
+ iter_.Next();
+
+ if (!iter_.Valid()) {
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+ break;
+ }
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+ if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
+ break;
+ }
+
+ if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
+ range_del_agg_.ShouldDelete(
+ ikey, RangeDelPositioningMode::kForwardTraversal)) {
+ break;
+ } else if (ikey.type == kTypeValue) {
+ const Slice val = iter_.value();
+ Status s = MergeHelper::TimedFullMerge(
+ merge_operator_, saved_key_.GetUserKey(), &val,
+ merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
+ env_, &pinned_value_, true);
+ if (!s.ok()) {
+ valid_ = false;
+ status_ = s;
+ return false;
+ }
+ valid_ = true;
+ return true;
+ } else if (ikey.type == kTypeMerge) {
+ merge_context_.PushOperand(
+ iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
+ PERF_COUNTER_ADD(internal_merge_count, 1);
+ } else if (ikey.type == kTypeBlobIndex) {
+ if (!allow_blob_) {
+ ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
+ status_ = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ } else {
+ status_ =
+ Status::NotSupported("Blob DB does not support merge operator.");
+ }
+ valid_ = false;
+ return false;
+ } else {
+ assert(false);
+ }
+ }
+
+ Status s = MergeHelper::TimedFullMerge(
+ merge_operator_, saved_key_.GetUserKey(), nullptr,
+ merge_context_.GetOperands(), &saved_value_, logger_, statistics_, env_,
+ &pinned_value_, true);
+ if (!s.ok()) {
+ valid_ = false;
+ status_ = s;
+ return false;
+ }
+
+ // Make sure we leave iter_ in a good state. If it's valid and we don't care
+ // about prefixes, that's already good enough. Otherwise it needs to be
+ // seeked to the current key.
+ if (!expect_total_order_inner_iter() || !iter_.Valid()) {
+ if (!expect_total_order_inner_iter()) {
+ iter_.SeekForPrev(last_key);
+ } else {
+ iter_.Seek(last_key);
+ if (!iter_.Valid() && iter_.status().ok()) {
+ iter_.SeekToLast();
+ }
+ }
+ RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
+ }
+
+ valid_ = true;
+ return true;
+}
+
+// Move backwards until the key smaller than saved_key_.
+// Changes valid_ only if return value is false.
+bool DBIter::FindUserKeyBeforeSavedKey() {
+ assert(status_.ok());
+ size_t num_skipped = 0;
+ while (iter_.Valid()) {
+ ParsedInternalKey ikey;
+ if (!ParseKey(&ikey)) {
+ return false;
+ }
+
+ if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
+ return true;
+ }
+
+ if (TooManyInternalKeysSkipped()) {
+ return false;
+ }
+
+ assert(ikey.sequence != kMaxSequenceNumber);
+ if (!IsVisible(ikey.sequence)) {
+ PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
+ } else {
+ PERF_COUNTER_ADD(internal_key_skipped_count, 1);
+ }
+
+ if (num_skipped >= max_skip_) {
+ num_skipped = 0;
+ IterKey last_key;
+ last_key.SetInternalKey(ParsedInternalKey(
+ saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
+ // It would be more efficient to use SeekForPrev() here, but some
+ // iterators may not support it.
+ iter_.Seek(last_key.GetInternalKey());
+ RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
+ if (!iter_.Valid()) {
+ break;
+ }
+ } else {
+ ++num_skipped;
+ }
+
+ iter_.Prev();
+ }
+
+ if (!iter_.status().ok()) {
+ valid_ = false;
+ return false;
+ }
+
+ return true;
+}
+
+bool DBIter::TooManyInternalKeysSkipped(bool increment) {
+ if ((max_skippable_internal_keys_ > 0) &&
+ (num_internal_keys_skipped_ > max_skippable_internal_keys_)) {
+ valid_ = false;
+ status_ = Status::Incomplete("Too many internal keys skipped.");
+ return true;
+ } else if (increment) {
+ num_internal_keys_skipped_++;
+ }
+ return false;
+}
+
+bool DBIter::IsVisible(SequenceNumber sequence) {
+ if (read_callback_ == nullptr) {
+ return sequence <= sequence_;
+ } else {
+ return read_callback_->IsVisible(sequence);
+ }
+}
+
+void DBIter::SetSavedKeyToSeekTarget(const Slice& target) {
+ is_key_seqnum_zero_ = false;
+ SequenceNumber seq = sequence_;
+ saved_key_.Clear();
+ saved_key_.SetInternalKey(target, seq);
+
+ if (iterate_lower_bound_ != nullptr &&
+ user_comparator_.Compare(saved_key_.GetUserKey(), *iterate_lower_bound_) <
+ 0) {
+ // Seek key is smaller than the lower bound.
+ saved_key_.Clear();
+ saved_key_.SetInternalKey(*iterate_lower_bound_, seq);
+ }
+}
+
+void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
+ is_key_seqnum_zero_ = false;
+ saved_key_.Clear();
+ // now saved_key is used to store internal key.
+ saved_key_.SetInternalKey(target, 0 /* sequence_number */,
+ kValueTypeForSeekForPrev);
+
+ if (iterate_upper_bound_ != nullptr &&
+ user_comparator_.Compare(saved_key_.GetUserKey(),
+ *iterate_upper_bound_) >= 0) {
+ saved_key_.Clear();
+ saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber);
+ }
+}
+
+void DBIter::Seek(const Slice& target) {
+ PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
+ StopWatch sw(env_, statistics_, DB_SEEK);
+
+#ifndef ROCKSDB_LITE
+ if (db_impl_ != nullptr && cfd_ != nullptr) {
+ db_impl_->TraceIteratorSeek(cfd_->GetID(), target);
+ }
+#endif // ROCKSDB_LITE
+
+ status_ = Status::OK();
+ ReleaseTempPinnedData();
+ ResetInternalKeysSkippedCounter();
+
+ // Seek the inner iterator based on the target key.
+ {
+ PERF_TIMER_GUARD(seek_internal_seek_time);
+
+ SetSavedKeyToSeekTarget(target);
+ iter_.Seek(saved_key_.GetInternalKey());
+
+ range_del_agg_.InvalidateRangeDelMapPositions();
+ RecordTick(statistics_, NUMBER_DB_SEEK);
+ }
+ if (!iter_.Valid()) {
+ valid_ = false;
+ return;
+ }
+ direction_ = kForward;
+
+ // Now the inner iterator is placed to the target position. From there,
+ // we need to find out the next key that is visible to the user.
+ ClearSavedValue();
+ if (prefix_same_as_start_) {
+ // The case where the iterator needs to be invalidated if it has exausted
+ // keys within the same prefix of the seek key.
+ assert(prefix_extractor_ != nullptr);
+ Slice target_prefix = prefix_extractor_->Transform(target);
+ FindNextUserEntry(false /* not skipping saved_key */,
+ &target_prefix /* prefix */);
+ if (valid_) {
+ // Remember the prefix of the seek key for the future Next() call to
+ // check.
+ prefix_.SetUserKey(target_prefix);
+ }
+ } else {
+ FindNextUserEntry(false /* not skipping saved_key */, nullptr);
+ }
+ if (!valid_) {
+ return;
+ }
+
+ // Updating stats and perf context counters.
+ if (statistics_ != nullptr) {
+ // Decrement since we don't want to count this key as skipped
+ RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
+ RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
+ }
+ PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
+}
+
+void DBIter::SeekForPrev(const Slice& target) {
+ PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
+ StopWatch sw(env_, statistics_, DB_SEEK);
+
+#ifndef ROCKSDB_LITE
+ if (db_impl_ != nullptr && cfd_ != nullptr) {
+ db_impl_->TraceIteratorSeekForPrev(cfd_->GetID(), target);
+ }
+#endif // ROCKSDB_LITE
+
+ status_ = Status::OK();
+ ReleaseTempPinnedData();
+ ResetInternalKeysSkippedCounter();
+
+ // Seek the inner iterator based on the target key.
+ {
+ PERF_TIMER_GUARD(seek_internal_seek_time);
+ SetSavedKeyToSeekForPrevTarget(target);
+ iter_.SeekForPrev(saved_key_.GetInternalKey());
+ range_del_agg_.InvalidateRangeDelMapPositions();
+ RecordTick(statistics_, NUMBER_DB_SEEK);
+ }
+ if (!iter_.Valid()) {
+ valid_ = false;
+ return;
+ }
+ direction_ = kReverse;
+
+ // Now the inner iterator is placed to the target position. From there,
+ // we need to find out the first key that is visible to the user in the
+ // backward direction.
+ ClearSavedValue();
+ if (prefix_same_as_start_) {
+ // The case where the iterator needs to be invalidated if it has exausted
+ // keys within the same prefix of the seek key.
+ assert(prefix_extractor_ != nullptr);
+ Slice target_prefix = prefix_extractor_->Transform(target);
+ PrevInternal(&target_prefix);
+ if (valid_) {
+ // Remember the prefix of the seek key for the future Prev() call to
+ // check.
+ prefix_.SetUserKey(target_prefix);
+ }
+ } else {
+ PrevInternal(nullptr);
+ }
+
+ // Report stats and perf context.
+ if (statistics_ != nullptr && valid_) {
+ RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
+ RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
+ PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
+ }
+}
+
+void DBIter::SeekToFirst() {
+ if (iterate_lower_bound_ != nullptr) {
+ Seek(*iterate_lower_bound_);
+ return;
+ }
+ PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
+ // Don't use iter_::Seek() if we set a prefix extractor
+ // because prefix seek will be used.
+ if (!expect_total_order_inner_iter()) {
+ max_skip_ = std::numeric_limits<uint64_t>::max();
+ }
+ status_ = Status::OK();
+ direction_ = kForward;
+ ReleaseTempPinnedData();
+ ResetInternalKeysSkippedCounter();
+ ClearSavedValue();
+ is_key_seqnum_zero_ = false;
+
+ {
+ PERF_TIMER_GUARD(seek_internal_seek_time);
+ iter_.SeekToFirst();
+ range_del_agg_.InvalidateRangeDelMapPositions();
+ }
+
+ RecordTick(statistics_, NUMBER_DB_SEEK);
+ if (iter_.Valid()) {
+ saved_key_.SetUserKey(
+ ExtractUserKey(iter_.key()),
+ !iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
+ FindNextUserEntry(false /* not skipping saved_key */,
+ nullptr /* no prefix check */);
+ if (statistics_ != nullptr) {
+ if (valid_) {
+ RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
+ RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
+ PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
+ }
+ }
+ } else {
+ valid_ = false;
+ }
+ if (valid_ && prefix_same_as_start_) {
+ assert(prefix_extractor_ != nullptr);
+ prefix_.SetUserKey(prefix_extractor_->Transform(saved_key_.GetUserKey()));
+ }
+}
+
+void DBIter::SeekToLast() {
+ if (iterate_upper_bound_ != nullptr) {
+ // Seek to last key strictly less than ReadOptions.iterate_upper_bound.
+ SeekForPrev(*iterate_upper_bound_);
+ if (Valid() && user_comparator_.Equal(*iterate_upper_bound_, key())) {
+ ReleaseTempPinnedData();
+ PrevInternal(nullptr);
+ }
+ return;
+ }
+
+ PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
+ // Don't use iter_::Seek() if we set a prefix extractor
+ // because prefix seek will be used.
+ if (!expect_total_order_inner_iter()) {
+ max_skip_ = std::numeric_limits<uint64_t>::max();
+ }
+ status_ = Status::OK();
+ direction_ = kReverse;
+ ReleaseTempPinnedData();
+ ResetInternalKeysSkippedCounter();
+ ClearSavedValue();
+ is_key_seqnum_zero_ = false;
+
+ {
+ PERF_TIMER_GUARD(seek_internal_seek_time);
+ iter_.SeekToLast();
+ range_del_agg_.InvalidateRangeDelMapPositions();
+ }
+ PrevInternal(nullptr);
+ if (statistics_ != nullptr) {
+ RecordTick(statistics_, NUMBER_DB_SEEK);
+ if (valid_) {
+ RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
+ RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
+ PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
+ }
+ }
+ if (valid_ && prefix_same_as_start_) {
+ assert(prefix_extractor_ != nullptr);
+ prefix_.SetUserKey(prefix_extractor_->Transform(saved_key_.GetUserKey()));
+ }
+}
+
+Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ const Comparator* user_key_comparator,
+ InternalIterator* internal_iter,
+ const SequenceNumber& sequence,
+ uint64_t max_sequential_skip_in_iterations,
+ ReadCallback* read_callback, DBImpl* db_impl,
+ ColumnFamilyData* cfd, bool allow_blob) {
+ DBIter* db_iter = new DBIter(
+ env, read_options, cf_options, mutable_cf_options, user_key_comparator,
+ internal_iter, sequence, false, max_sequential_skip_in_iterations,
+ read_callback, db_impl, cfd, allow_blob);
+ return db_iter;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_iter.h b/storage/rocksdb/rocksdb/db/db_iter.h
new file mode 100644
index 00000000..32704e4d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_iter.h
@@ -0,0 +1,344 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "db/range_del_aggregator.h"
+#include "memory/arena.h"
+#include "options/cf_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/iterator.h"
+#include "table/iterator_wrapper.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This file declares the factory functions of DBIter, in its original form
+// or a wrapped form with class ArenaWrappedDBIter, which is defined here.
+// Class DBIter, which is declared and implemented inside db_iter.cc, is
+// an iterator that converts internal keys (yielded by an InternalIterator)
+// that were live at the specified sequence number into appropriate user
+// keys.
+// Each internal key consists of a user key, a sequence number, and a value
+// type. DBIter deals with multiple key versions, tombstones, merge operands,
+// etc, and exposes an Iterator.
+// For example, DBIter may wrap following InternalIterator:
+// user key: AAA value: v3 seqno: 100 type: Put
+// user key: AAA value: v2 seqno: 97 type: Put
+// user key: AAA value: v1 seqno: 95 type: Put
+// user key: BBB value: v1 seqno: 90 type: Put
+// user key: BBC value: N/A seqno: 98 type: Delete
+// user key: BBC value: v1 seqno: 95 type: Put
+// If the snapshot passed in is 102, then the DBIter is expected to
+// expose the following iterator:
+// key: AAA value: v3
+// key: BBB value: v1
+// If the snapshot passed in is 96, then it should expose:
+// key: AAA value: v1
+// key: BBB value: v1
+// key: BBC value: v1
+//
+
+// Memtables and sstables that make the DB representation contain
+// (userkey,seq,type) => uservalue entries. DBIter
+// combines multiple entries for the same userkey found in the DB
+// representation into a single entry while accounting for sequence
+// numbers, deletion markers, overwrites, etc.
+class DBIter final : public Iterator {
+ public:
+ // The following is grossly complicated. TODO: clean it up
+ // Which direction is the iterator currently moving?
+ // (1) When moving forward:
+ // (1a) if current_entry_is_merged_ = false, the internal iterator is
+ // positioned at the exact entry that yields this->key(), this->value()
+ // (1b) if current_entry_is_merged_ = true, the internal iterator is
+ // positioned immediately after the last entry that contributed to the
+ // current this->value(). That entry may or may not have key equal to
+ // this->key().
+ // (2) When moving backwards, the internal iterator is positioned
+ // just before all entries whose user key == this->key().
+ enum Direction { kForward, kReverse };
+
+ // LocalStatistics contain Statistics counters that will be aggregated per
+ // each iterator instance and then will be sent to the global statistics when
+ // the iterator is destroyed.
+ //
+ // The purpose of this approach is to avoid perf regression happening
+ // when multiple threads bump the atomic counters from a DBIter::Next().
+ struct LocalStatistics {
+ explicit LocalStatistics() { ResetCounters(); }
+
+ void ResetCounters() {
+ next_count_ = 0;
+ next_found_count_ = 0;
+ prev_count_ = 0;
+ prev_found_count_ = 0;
+ bytes_read_ = 0;
+ skip_count_ = 0;
+ }
+
+ void BumpGlobalStatistics(Statistics* global_statistics) {
+ RecordTick(global_statistics, NUMBER_DB_NEXT, next_count_);
+ RecordTick(global_statistics, NUMBER_DB_NEXT_FOUND, next_found_count_);
+ RecordTick(global_statistics, NUMBER_DB_PREV, prev_count_);
+ RecordTick(global_statistics, NUMBER_DB_PREV_FOUND, prev_found_count_);
+ RecordTick(global_statistics, ITER_BYTES_READ, bytes_read_);
+ RecordTick(global_statistics, NUMBER_ITER_SKIP, skip_count_);
+ PERF_COUNTER_ADD(iter_read_bytes, bytes_read_);
+ ResetCounters();
+ }
+
+ // Map to Tickers::NUMBER_DB_NEXT
+ uint64_t next_count_;
+ // Map to Tickers::NUMBER_DB_NEXT_FOUND
+ uint64_t next_found_count_;
+ // Map to Tickers::NUMBER_DB_PREV
+ uint64_t prev_count_;
+ // Map to Tickers::NUMBER_DB_PREV_FOUND
+ uint64_t prev_found_count_;
+ // Map to Tickers::ITER_BYTES_READ
+ uint64_t bytes_read_;
+ // Map to Tickers::NUMBER_ITER_SKIP
+ uint64_t skip_count_;
+ };
+
+ DBIter(Env* _env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
+ InternalIterator* iter, SequenceNumber s, bool arena_mode,
+ uint64_t max_sequential_skip_in_iterations,
+ ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
+ bool allow_blob);
+
+ // No copying allowed
+ DBIter(const DBIter&) = delete;
+ void operator=(const DBIter&) = delete;
+
+ ~DBIter() override {
+ // Release pinned data if any
+ if (pinned_iters_mgr_.PinningEnabled()) {
+ pinned_iters_mgr_.ReleasePinnedData();
+ }
+ RecordTick(statistics_, NO_ITERATOR_DELETED);
+ ResetInternalKeysSkippedCounter();
+ local_stats_.BumpGlobalStatistics(statistics_);
+ iter_.DeleteIter(arena_mode_);
+ }
+ void SetIter(InternalIterator* iter) {
+ assert(iter_.iter() == nullptr);
+ iter_.Set(iter);
+ iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
+ }
+ ReadRangeDelAggregator* GetRangeDelAggregator() { return &range_del_agg_; }
+
+ bool Valid() const override { return valid_; }
+ Slice key() const override {
+ assert(valid_);
+ if (start_seqnum_ > 0) {
+ return saved_key_.GetInternalKey();
+ } else {
+ return saved_key_.GetUserKey();
+ }
+ }
+ Slice value() const override {
+ assert(valid_);
+ if (current_entry_is_merged_) {
+ // If pinned_value_ is set then the result of merge operator is one of
+ // the merge operands and we should return it.
+ return pinned_value_.data() ? pinned_value_ : saved_value_;
+ } else if (direction_ == kReverse) {
+ return pinned_value_;
+ } else {
+ return iter_.value();
+ }
+ }
+ Status status() const override {
+ if (status_.ok()) {
+ return iter_.status();
+ } else {
+ assert(!valid_);
+ return status_;
+ }
+ }
+ bool IsBlob() const {
+ assert(valid_ && (allow_blob_ || !is_blob_));
+ return is_blob_;
+ }
+
+ Status GetProperty(std::string prop_name, std::string* prop) override;
+
+ void Next() final override;
+ void Prev() final override;
+ void Seek(const Slice& target) final override;
+ void SeekForPrev(const Slice& target) final override;
+ void SeekToFirst() final override;
+ void SeekToLast() final override;
+ Env* env() const { return env_; }
+ void set_sequence(uint64_t s) {
+ sequence_ = s;
+ if (read_callback_) {
+ read_callback_->Refresh(s);
+ }
+ }
+ void set_valid(bool v) { valid_ = v; }
+
+ private:
+ // For all methods in this block:
+ // PRE: iter_->Valid() && status_.ok()
+ // Return false if there was an error, and status() is non-ok, valid_ = false;
+ // in this case callers would usually stop what they were doing and return.
+ bool ReverseToForward();
+ bool ReverseToBackward();
+ // Set saved_key_ to the seek key to target, with proper sequence number set.
+ // It might get adjusted if the seek key is smaller than iterator lower bound.
+ void SetSavedKeyToSeekTarget(const Slice& target);
+ // Set saved_key_ to the seek key to target, with proper sequence number set.
+ // It might get adjusted if the seek key is larger than iterator upper bound.
+ void SetSavedKeyToSeekForPrevTarget(const Slice& target);
+ bool FindValueForCurrentKey();
+ bool FindValueForCurrentKeyUsingSeek();
+ bool FindUserKeyBeforeSavedKey();
+ // If `skipping_saved_key` is true, the function will keep iterating until it
+ // finds a user key that is larger than `saved_key_`.
+ // If `prefix` is not null, the iterator needs to stop when all keys for the
+ // prefix are exhausted and the interator is set to invalid.
+ bool FindNextUserEntry(bool skipping_saved_key, const Slice* prefix);
+ // Internal implementation of FindNextUserEntry().
+ bool FindNextUserEntryInternal(bool skipping_saved_key, const Slice* prefix);
+ bool ParseKey(ParsedInternalKey* key);
+ bool MergeValuesNewToOld();
+
+ // If prefix is not null, we need to set the iterator to invalid if no more
+ // entry can be found within the prefix.
+ void PrevInternal(const Slice* prefix);
+ bool TooManyInternalKeysSkipped(bool increment = true);
+ bool IsVisible(SequenceNumber sequence);
+
+ // Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
+ // is called
+ void TempPinData() {
+ if (!pin_thru_lifetime_) {
+ pinned_iters_mgr_.StartPinning();
+ }
+ }
+
+ // Release blocks pinned by TempPinData()
+ void ReleaseTempPinnedData() {
+ if (!pin_thru_lifetime_ && pinned_iters_mgr_.PinningEnabled()) {
+ pinned_iters_mgr_.ReleasePinnedData();
+ }
+ }
+
+ inline void ClearSavedValue() {
+ if (saved_value_.capacity() > 1048576) {
+ std::string empty;
+ swap(empty, saved_value_);
+ } else {
+ saved_value_.clear();
+ }
+ }
+
+ inline void ResetInternalKeysSkippedCounter() {
+ local_stats_.skip_count_ += num_internal_keys_skipped_;
+ if (valid_) {
+ local_stats_.skip_count_--;
+ }
+ num_internal_keys_skipped_ = 0;
+ }
+
+ bool expect_total_order_inner_iter() {
+ assert(expect_total_order_inner_iter_ || prefix_extractor_ != nullptr);
+ return expect_total_order_inner_iter_;
+ }
+
+ const SliceTransform* prefix_extractor_;
+ Env* const env_;
+ Logger* logger_;
+ UserComparatorWrapper user_comparator_;
+ const MergeOperator* const merge_operator_;
+ IteratorWrapper iter_;
+ ReadCallback* read_callback_;
+ // Max visible sequence number. It is normally the snapshot seq unless we have
+ // uncommitted data in db as in WriteUnCommitted.
+ SequenceNumber sequence_;
+
+ IterKey saved_key_;
+ // Reusable internal key data structure. This is only used inside one function
+ // and should not be used across functions. Reusing this object can reduce
+ // overhead of calling construction of the function if creating it each time.
+ ParsedInternalKey ikey_;
+ std::string saved_value_;
+ Slice pinned_value_;
+ // for prefix seek mode to support prev()
+ Statistics* statistics_;
+ uint64_t max_skip_;
+ uint64_t max_skippable_internal_keys_;
+ uint64_t num_internal_keys_skipped_;
+ const Slice* iterate_lower_bound_;
+ const Slice* iterate_upper_bound_;
+
+ // The prefix of the seek key. It is only used when prefix_same_as_start_
+ // is true and prefix extractor is not null. In Next() or Prev(), current keys
+ // will be checked against this prefix, so that the iterator can be
+ // invalidated if the keys in this prefix has been exhausted. Set it using
+ // SetUserKey() and use it using GetUserKey().
+ IterKey prefix_;
+
+ Status status_;
+ Direction direction_;
+ bool valid_;
+ bool current_entry_is_merged_;
+ // True if we know that the current entry's seqnum is 0.
+ // This information is used as that the next entry will be for another
+ // user key.
+ bool is_key_seqnum_zero_;
+ const bool prefix_same_as_start_;
+ // Means that we will pin all data blocks we read as long the Iterator
+ // is not deleted, will be true if ReadOptions::pin_data is true
+ const bool pin_thru_lifetime_;
+ // Expect the inner iterator to maintain a total order.
+ // prefix_extractor_ must be non-NULL if the value is false.
+ const bool expect_total_order_inner_iter_;
+ bool allow_blob_;
+ bool is_blob_;
+ bool arena_mode_;
+ // List of operands for merge operator.
+ MergeContext merge_context_;
+ ReadRangeDelAggregator range_del_agg_;
+ LocalStatistics local_stats_;
+ PinnedIteratorsManager pinned_iters_mgr_;
+#ifdef ROCKSDB_LITE
+ ROCKSDB_FIELD_UNUSED
+#endif
+ DBImpl* db_impl_;
+#ifdef ROCKSDB_LITE
+ ROCKSDB_FIELD_UNUSED
+#endif
+ ColumnFamilyData* cfd_;
+ // for diff snapshots we want the lower bound on the seqnum;
+ // if this value > 0 iterator will return internal keys
+ SequenceNumber start_seqnum_;
+};
+
+// Return a new iterator that converts internal keys (yielded by
+// "*internal_iter") that were live at the specified `sequence` number
+// into appropriate user keys.
+extern Iterator* NewDBIterator(
+ Env* env, const ReadOptions& read_options,
+ const ImmutableCFOptions& cf_options,
+ const MutableCFOptions& mutable_cf_options,
+ const Comparator* user_key_comparator, InternalIterator* internal_iter,
+ const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
+ ReadCallback* read_callback, DBImpl* db_impl = nullptr,
+ ColumnFamilyData* cfd = nullptr, bool allow_blob = false);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_iter_stress_test.cc b/storage/rocksdb/rocksdb/db/db_iter_stress_test.cc
new file mode 100644
index 00000000..57cd9866
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_iter_stress_test.cc
@@ -0,0 +1,654 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<Entry> entries;
+
+ // Indices in `entries` with `visible` = false.
+ std::vector<size_t> hidden;
+ // Keys of entries whose `visible` changed since the last seek of iterators.
+ std::set<std::string> 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() >=
+ std::numeric_limits<uint64_t>::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() >=
+ std::numeric_limits<uint64_t>::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() < std::numeric_limits<uint64_t>::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<Slice> 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);
+ }
+};
+
+} // 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 = ToString(rnd.Next() % static_cast<uint64_t>(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: " + ToString(num_entries) +
+ ", key_space: " + ToString(key_space) +
+ ", error_probability: " + ToString(error_probability) +
+ ", mutation_probability: " + ToString(mutation_probability) +
+ ", target_hidden_fraction: " +
+ ToString(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" + ToString(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<Iterator> db_iter;
+ std::unique_ptr<ReferenceIterator> 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, ImmutableCFOptions(options),
+ MutableCFOptions(options), BytewiseComparator(),
+ internal_iter, 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ParseCommandLineFlags(&argc, &argv, true);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_iter_test.cc b/storage/rocksdb/rocksdb/db/db_iter_test.cc
new file mode 100644
index 00000000..ddbea8d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_iter_test.cc
@@ -0,0 +1,3175 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+#include <algorithm>
+#include <utility>
+
+#include "db/db_iter.h"
+#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, std::string>(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<std::string, std::string> a,
+ std::pair<std::string, std::string> 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;
+ bool ok __attribute__((__unused__)) = ParseInternalKey(it->first, &ikey);
+ assert(ok);
+ if (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<std::pair<std::string, std::string>> 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;
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, options.max_sequential_skip_in_iterations,
+ nullptr /*read_callback*/));
+
+ db_iter->SeekToLast();
+ ASSERT_TRUE(!db_iter->Valid());
+ }
+ // 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 7, 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<int>(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 4, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, options.max_sequential_skip_in_iterations,
+ nullptr /*read_callback*/));
+
+ db_iter->SeekToLast();
+ ASSERT_TRUE(!db_iter->Valid());
+ }
+ // 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 7, 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<int>(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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());
+ }
+
+ {
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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;
+ ImmutableCFOptions cf_options = ImmutableCFOptions(options);
+ MutableCFOptions mutable_cf_options = MutableCFOptions(options);
+ ReadOptions ro;
+
+ {
+ TestIterator* internal_iter = new TestIterator(BytewiseComparator());
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 0, 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->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 0, options.max_sequential_skip_in_iterations,
+ nullptr /*read_callback*/));
+ db_iter->SeekToFirst();
+ ASSERT_TRUE(!db_iter->Valid());
+ }
+}
+
+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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 2,
+ 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_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3u);
+}
+
+TEST_F(DBIteratorTest, DBIteratorUseSkip) {
+ ReadOptions ro;
+ Options options;
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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", ToString(k));
+ }
+ internal_iter->Finish();
+
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, i + 2, 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(), ToString(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());
+ }
+ }
+
+ {
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, i + 2, 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());
+ }
+
+ {
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 202, 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());
+ }
+ }
+
+ {
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, i, options.max_sequential_skip_in_iterations,
+ nullptr /*read_callback*/));
+ db_iter->SeekToLast();
+ ASSERT_TRUE(!db_iter->Valid());
+
+ db_iter->SeekToFirst();
+ ASSERT_TRUE(!db_iter->Valid());
+ }
+
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 200, 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());
+
+ 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());
+ }
+
+ {
+ 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", ToString(k));
+ }
+
+ for (size_t k = 0; k < 200; ++k) {
+ internal_iter->AddPut("c", ToString(k));
+ }
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, i + 2, 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(), ToString(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());
+ }
+ }
+
+ {
+ 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", ToString(k));
+ }
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, i + 2, 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 += "," + ToString(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());
+ }
+ }
+}
+
+TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
+ Options options;
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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_TRUE(db_iter->status().ok());
+ }
+
+ // 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2 * i + 1, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2 * i + 1, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 1,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 0,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 2,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 4,
+ 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");
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 0, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 1, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 3, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 4, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 5, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 6, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 10, 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");
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 0, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 1, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 3, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 4, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 5, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 6, 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");
+ ImmutableCFOptions cf_options = ImmutableCFOptions(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 0, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 2, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 4, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 5, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 6, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 7, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 9, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 13, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
+ internal_iter, 14, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 1,
+ 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10, 0, 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<char>(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 2, 3, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 4, 1, 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");
+}
+
+TEST_F(DBIteratorTest, DBIteratorTestDifferentialSnapshots) {
+ { // test that KVs earlier that iter_start_seqnum are filtered out
+ ReadOptions ro;
+ ro.iter_start_seqnum=5;
+ Options options;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ TestIterator* internal_iter = new TestIterator(BytewiseComparator());
+ for (size_t i = 0; i < 10; ++i) {
+ internal_iter->AddPut(std::to_string(i), std::to_string(i) + "a");
+ internal_iter->AddPut(std::to_string(i), std::to_string(i) + "b");
+ internal_iter->AddPut(std::to_string(i), std::to_string(i) + "c");
+ }
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 13,
+ options.max_sequential_skip_in_iterations, nullptr));
+ // Expecting InternalKeys in [5,8] range with correct type
+ int seqnums[4] = {5,8,11,13};
+ std::string user_keys[4] = {"1","2","3","4"};
+ std::string values[4] = {"1c", "2c", "3c", "4b"};
+ int i = 0;
+ for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
+ FullKey fkey;
+ ParseFullKey(db_iter->key(), &fkey);
+ ASSERT_EQ(user_keys[i], fkey.user_key.ToString());
+ ASSERT_EQ(EntryType::kEntryPut, fkey.type);
+ ASSERT_EQ(seqnums[i], fkey.sequence);
+ ASSERT_EQ(values[i], db_iter->value().ToString());
+ i++;
+ }
+ ASSERT_EQ(i, 4);
+ }
+
+ { // Test that deletes are returned correctly as internal KVs
+ ReadOptions ro;
+ ro.iter_start_seqnum=5;
+ Options options;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ TestIterator* internal_iter = new TestIterator(BytewiseComparator());
+ for (size_t i = 0; i < 10; ++i) {
+ internal_iter->AddPut(std::to_string(i), std::to_string(i) + "a");
+ internal_iter->AddPut(std::to_string(i), std::to_string(i) + "b");
+ internal_iter->AddDeletion(std::to_string(i));
+ }
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 13,
+ options.max_sequential_skip_in_iterations, nullptr));
+ // Expecting InternalKeys in [5,8] range with correct type
+ int seqnums[4] = {5,8,11,13};
+ EntryType key_types[4] = {EntryType::kEntryDelete,EntryType::kEntryDelete,
+ EntryType::kEntryDelete,EntryType::kEntryPut};
+ std::string user_keys[4] = {"1","2","3","4"};
+ std::string values[4] = {"", "", "", "4b"};
+ int i = 0;
+ for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
+ FullKey fkey;
+ ParseFullKey(db_iter->key(), &fkey);
+ ASSERT_EQ(user_keys[i], fkey.user_key.ToString());
+ ASSERT_EQ(key_types[i], fkey.type);
+ ASSERT_EQ(seqnums[i], fkey.sequence);
+ ASSERT_EQ(values[i], db_iter->value().ToString());
+ i++;
+ }
+ ASSERT_EQ(i, 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<InternalIterator*> 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_, ImmutableCFOptions(options_), MutableCFOptions(options_),
+ BytewiseComparator(), merge_iter,
+ 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<Iterator> 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<IteratorWrapper*>(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<IteratorWrapper*>(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<IteratorWrapper*>(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<IteratorWrapper*>(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<IteratorWrapper*>(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 10,
+ 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<int>(get_perf_context()->internal_key_skipped_count);
+ ASSERT_EQ(skipped_keys, 0);
+
+ get_perf_context()->Reset();
+ db_iter->Seek("a");
+ skipped_keys =
+ static_cast<int>(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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 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());
+ } 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 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<Iterator> db_iter(NewDBIterator(
+ env_, ro, ImmutableCFOptions(options), MutableCFOptions(options),
+ BytewiseComparator(), internal_iter, 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" + ToString(i), "");
+ }
+ internal_iter->Finish();
+
+ std::unique_ptr<Iterator> db_iter(NewDBIterator(
+ env_, ReadOptions(), ImmutableCFOptions(options),
+ MutableCFOptions(options), BytewiseComparator(), internal_iter, 10,
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_iterator_test.cc b/storage/rocksdb/rocksdb/db/db_iterator_test.cc
new file mode 100644
index 00000000..99ffb5ce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_iterator_test.cc
@@ -0,0 +1,2998 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+
+#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"
+
+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<bool> {
+ public:
+ DBIteratorTest() : DBTestBase("/db_iterator_test") {}
+
+ Iterator* NewIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family = nullptr) {
+ if (column_family == nullptr) {
+ column_family = db_->DefaultColumnFamily();
+ }
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(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<DummyReadCallback>(read_callback));
+ }
+ return dbfull()->NewIteratorImpl(read_options, cfd, seq, read_callback);
+ }
+
+ private:
+ InstrumentedMutex mutex_;
+ std::vector<std::unique_ptr<DummyReadCallback>> read_callbacks_;
+};
+
+TEST_P(DBIteratorTest, IteratorProperty) {
+ // The test needs to be changed if kPersistedTier is supported in iterator.
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Put(1, "1", "2");
+ Delete(1, "2");
+ ReadOptions ropt;
+ ropt.pin_data = false;
+ {
+ std::unique_ptr<Iterator> 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<Iterator*> iters;
+ ASSERT_TRUE(db_->NewIterators(ropt, {handles_[1]}, &iters).IsNotSupported());
+ Close();
+}
+
+TEST_P(DBIteratorTest, NonBlockingIteration) {
+ do {
+ ReadOptions non_blocking_opts, regular_opts;
+ Options options = CurrentOptions();
+ 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"));
+ dbfull()->Flush(FlushOptions());
+ ASSERT_OK(Put("0", "f"));
+ ASSERT_OK(Put("1", "h"));
+ 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", RandomString(&rnd, 400)));
+ ASSERT_OK(Put("aabb", RandomString(&rnd, 400)));
+ ASSERT_OK(Put("aaef", RandomString(&rnd, 400)));
+ ASSERT_OK(Put("b", RandomString(&rnd, 400)));
+ 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"));
+ dbfull()->Flush(FlushOptions());
+ ASSERT_OK(Put("0", "f"));
+ ASSERT_OK(Put("1", "h"));
+ 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);
+}
+} // namespace
+
+TEST_P(DBIteratorTest, IterLongKeys) {
+ ASSERT_OK(Put(MakeLongKey(20, 0), "0"));
+ ASSERT_OK(Put(MakeLongKey(32, 2), "2"));
+ ASSERT_OK(Put("a", "b"));
+ 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"));
+ 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"));
+ 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"));
+ 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)");
+
+ 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<int>(
+ 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_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());
+ 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
+ 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')));
+ }
+ 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());
+
+ Put(1, "foo", "delete-cf-then-delete-iter");
+ 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
+ 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());
+
+ 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
+ db_->DropColumnFamily(cf);
+ db_->DestroyColumnFamilyHandle(cf);
+ handles_.erase(std::begin(handles_) + 1);
+
+ // delete Iterator after CF handle is dropped
+ delete iter;
+}
+
+// SetOptions not defined in ROCKSDB LITE
+#ifndef 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<Iterator> 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<Iterator> 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<Iterator> 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<Iterator> 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<Iterator> 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<int>(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<int>(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<Iterator> 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));
+ }
+}
+#endif
+
+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<FlushBlockEveryKeyPolicyFactory>();
+ 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<Iterator> 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<FlushBlockEveryKeyPolicyFactory>();
+ 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<Iterator> 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));
+
+ iter->Next();
+ ASSERT_TRUE(iter->Valid());
+ EXPECT_EQ("b3", iter->key().ToString());
+ EXPECT_EQ("y3", iter->value().ToString());
+ EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+ EXPECT_EQ(0, 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(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(4, 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(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(5, 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(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
+
+ iter->Next();
+ ASSERT_FALSE(iter->Valid());
+ EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
+ EXPECT_EQ(5, 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<FlushBlockEveryKeyPolicyFactory>();
+ 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<std::string>({"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;
+ db_->Merge(wopts, "1", "data1");
+ db_->Merge(wopts, "2", "data2");
+ db_->Merge(wopts, "3", "data3");
+
+ std::unique_ptr<Iterator> 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<int>(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<std::string> generated_keys(key_pool);
+ for (int i = 0; i < key_pool; i++) {
+ generated_keys[i] = RandomString(&rnd, key_size);
+ }
+
+ std::map<std::string, std::string> true_data;
+ std::vector<std::string> random_keys;
+ std::vector<std::string> deleted_keys;
+ for (int i = 0; i < puts; i++) {
+ auto& k = generated_keys[rnd.Next() % key_pool];
+ auto v = RandomString(&rnd, 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) {
+ Flush();
+ }
+ }
+ }
+
+ if (run_config == TestConfig::CLOSE_AND_OPEN) {
+ Close();
+ Reopen(options);
+ } else if (run_config == TestConfig::COMPACT_BEFORE_READ) {
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ }
+
+ ReadOptions ro;
+ ro.pin_data = true;
+ auto iter = NewIterator(ro);
+
+ {
+ // Test Seek to random keys
+ std::vector<Slice> keys_slices;
+ std::vector<std::string> 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<Slice> keys_slices;
+ std::vector<std::string> 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<Slice> 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<Slice> 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;
+}
+};
+
+TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedNormal) {
+ PinnedDataIteratorRandomized(TestConfig::NORMAL);
+}
+
+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);
+}
+
+#ifndef ROCKSDB_LITE
+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<std::string, std::string> 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 = RandomString(&rnd, 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 = RandomString(&rnd, 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 = RandomString(&rnd, 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<std::pair<Slice, std::string>> 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;
+}
+#endif
+
+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<std::pair<Slice, std::string>> 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<int>(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<std::string, std::string> true_data;
+ for (int i = 0; i < 1000; i++) {
+ std::string k = RandomString(&rnd, 10);
+ std::string v = RandomString(&rnd, 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 = RandomString(&rnd, 1000);
+ ASSERT_OK(Put(kv.first, new_val));
+ }
+ }
+
+ std::vector<std::pair<Slice, std::string>> 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());
+ delete iter;
+ }
+}
+
+TEST_P(DBIteratorTest, IterSeekForPrevCrossingFilesCustomPrefixExtractor) {
+ Options options = CurrentOptions();
+ options.prefix_extractor =
+ std::make_shared<SliceTransformLimitedDomainGeneric>();
+ 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());
+ 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() dont
+ // 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<int>(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<std::string, std::string> true_data;
+ std::string gen_key;
+ std::string gen_val;
+
+ for (int i = 0; i < kNumKeys; i++) {
+ gen_key = Key(i);
+ gen_val = RandomString(&rnd, 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 dont 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 = RandomString(&rnd, 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(RandomString(&rnd, 10), RandomString(&rnd, 10)));
+ }
+
+ std::atomic<uint64_t> total_next(0);
+ std::atomic<uint64_t> total_next_found(0);
+ std::atomic<uint64_t> total_prev(0);
+ std::atomic<uint64_t> total_prev_found(0);
+ std::atomic<uint64_t> total_bytes(0);
+
+ std::vector<port::Thread> threads;
+ std::function<void()> 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<void()> 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(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+
+ std::string value(1024, 'a');
+ for (int i = 0; i < 100; i++) {
+ Put(Key(i), value);
+ }
+ ASSERT_OK(Flush());
+ MoveFilesToLevel(2);
+
+ for (int i = 0; i < 100; i++) {
+ Put(Key(i), value);
+ }
+ ASSERT_OK(Flush());
+ MoveFilesToLevel(1);
+
+ for (int i = 0; i < 100; i++) {
+ Put(Key(i), value);
+ }
+ ASSERT_OK(Flush());
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("1,1,1", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+
+ 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;
+
+ int64_t num_file_closes = TestGetTickerCount(options, NO_FILE_CLOSES);
+ 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;
+ int64_t num_file_closes_readahead =
+ TestGetTickerCount(options, NO_FILE_CLOSES);
+ ASSERT_EQ(num_file_opens, num_file_opens_readahead);
+ ASSERT_EQ(num_file_closes, num_file_closes_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<Iterator> iter(NewIterator(ro));
+
+ // Insert a lot.
+ for (int i = 0; i < 100; ++i) {
+ ASSERT_OK(Put("b", std::to_string(i + 1).c_str()));
+ }
+
+#ifndef ROCKSDB_LITE
+ // Check that memtable wasn't flushed.
+ std::string val;
+ ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &val));
+ EXPECT_EQ("0", val);
+#endif
+
+ // 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<Iterator> iter(NewIterator(ReadOptions()));
+ 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());
+
+ 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());
+
+ 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());
+
+ 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);
+
+ 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());
+
+ Status s;
+ 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<Status*>(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"));
+ dbfull()->Flush(FlushOptions());
+ ASSERT_OK(Put("b", "2"));
+ ASSERT_OK(Put("c", "3"));
+ dbfull()->Flush(FlushOptions());
+ ASSERT_OK(Put("d", "4"));
+ ASSERT_OK(Put("e", "5"));
+ ASSERT_OK(Put("f", "6"));
+ dbfull()->Flush(FlushOptions());
+
+ // Ensure the table_filter callback is called once for each table.
+ {
+ std::set<uint64_t> 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;
+
+ Put("1", "val_1");
+ // Add more tombstones than max_skippable_internal_keys so that Next() fails.
+ Delete("2");
+ Delete("3");
+ Delete("4");
+ Delete("5");
+ Put("6", "val_6");
+
+ std::unique_ptr<Iterator> 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<Iterator> 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<FlushBlockEveryKeyPolicyFactory>();
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ DestroyAndReopen(options);
+
+ // Two records in sst file, each in its own block.
+ Put("b", "");
+ Put("d", "");
+ Flush();
+
+ // Create a nonblocking iterator before writing to memtable.
+ ReadOptions ropt;
+ ropt.read_tier = kBlockCacheTier;
+ std::unique_ptr<Iterator> 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) {
+ Put("c", "");
+ }
+
+ // Load the second block in sst file into the block cache.
+ {
+ std::unique_ptr<Iterator> 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) {
+ Put("a", "");
+ Put("b", "");
+ Flush();
+
+ ReadOptions ropt;
+ Slice ub = "b";
+ ropt.iterate_upper_bound = &ub;
+
+ std::unique_ptr<Iterator> 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 = RandomString(&rnd, 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<Iterator> 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<Iterator> 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());
+}
+
+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 =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(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", ToString(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(ToString(num_versions - 1), iter->value());
+
+ delete iter;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_log_iter_test.cc b/storage/rocksdb/rocksdb/db/db_log_iter_test.cc
new file mode 100644
index 00000000..1f9ff0d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_log_iter_test.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#if !defined(ROCKSDB_LITE)
+
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBTestXactLogIterator : public DBTestBase {
+ public:
+ DBTestXactLogIterator() : DBTestBase("/db_log_iter_test") {}
+
+ std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
+ const SequenceNumber seq) {
+ std::unique_ptr<TransactionLogIterator> iter;
+ Status status = dbfull()->GetUpdatesSince(seq, &iter);
+ EXPECT_OK(status);
+ EXPECT_TRUE(iter->Valid());
+ return iter;
+ }
+};
+
+namespace {
+SequenceNumber ReadRecords(
+ std::unique_ptr<TransactionLogIterator>& iter,
+ int& count) {
+ 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();
+ }
+ return res.sequence;
+}
+
+void ExpectRecords(
+ const int expected_no_records,
+ std::unique_ptr<TransactionLogIterator>& iter) {
+ int num_records;
+ ReadRecords(iter, num_records);
+ ASSERT_EQ(num_records, expected_no_records);
+}
+} // namespace
+
+TEST_F(DBTestXactLogIterator, TransactionLogIterator) {
+ do {
+ Options options = OptionsForLogIterTest();
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Put(0, "key1", DummyString(1024));
+ Put(1, "key2", DummyString(1024));
+ 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);
+ {
+ Put(0, "key4", DummyString(1024));
+ Put(1, "key5", DummyString(1024));
+ 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);
+ Put("key1", DummyString(1024));
+ dbfull()->Flush(FlushOptions());
+ Put("key2", DummyString(1024));
+ dbfull()->Flush(FlushOptions());
+ Put("key3", DummyString(1024));
+ dbfull()->Flush(FlushOptions());
+ Put("key4", DummyString(1024));
+ ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4U);
+ 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;
+ dbfull()->Flush(flush_options);
+
+ // "key5" would be written in a new memtable and log
+ Put("key5", DummyString(1024));
+ 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);
+ 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());
+ 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);
+ Put("key1", DummyString(1024));
+ Put("key2", DummyString(1023));
+ 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++) {
+ Put("key"+ToString(i), DummyString(10));
+ }
+ dbfull()->Flush(FlushOptions());
+ dbfull()->FlushWAL(false);
+ // Corrupt this log to create a gap
+ ROCKSDB_NAMESPACE::VectorLogPtr wal_files;
+ ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
+ const auto logfile_path = dbname_ + "/" + wal_files.front()->PathName();
+ if (mem_env_) {
+ mem_env_->Truncate(logfile_path, wal_files.front()->SizeFileBytes() / 2);
+ } else {
+ ASSERT_EQ(0, truncate(logfile_path.c_str(),
+ wal_files.front()->SizeFileBytes() / 2));
+ }
+
+ // Insert a new entry to a new log file
+ Put("key1025", DummyString(10));
+ dbfull()->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;
+ SequenceNumber last_sequence_read = ReadRecords(iter, count);
+ 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;
+ batch.Put(handles_[1], "key1", DummyString(1024));
+ batch.Put(handles_[0], "key2", DummyString(1024));
+ batch.Put(handles_[1], "key3", DummyString(1024));
+ batch.Delete(handles_[0], "key2");
+ dbfull()->Write(WriteOptions(), &batch);
+ Flush(1);
+ Flush(0);
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ 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;
+ batch.Put(handles_[1], "key1", DummyString(1024));
+ batch.Put(handles_[0], "key2", DummyString(1024));
+ batch.PutLogData(Slice("blob1"));
+ batch.Put(handles_[1], "key3", DummyString(1024));
+ batch.PutLogData(Slice("blob2"));
+ batch.Delete(handles_[0], "key2");
+ 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(" + ToString(cf) + ", " + key.ToString() + ", " +
+ ToString(value.size()) + ")";
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
+ seen += "Merge(" + ToString(cf) + ", " + key.ToString() + ", " +
+ ToString(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(" + ToString(cf) + ", " + key.ToString() + ")";
+ return Status::OK();
+ }
+ } handler;
+ 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
+
+#endif // !defined(ROCKSDB_LITE)
+
+int main(int argc, char** argv) {
+#if !defined(ROCKSDB_LITE)
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ (void) argc;
+ (void) argv;
+ return 0;
+#endif
+}
diff --git a/storage/rocksdb/rocksdb/db/db_memtable_test.cc b/storage/rocksdb/rocksdb/db/db_memtable_test.cc
new file mode 100644
index 00000000..a2f4e327
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_memtable_test.cc
@@ -0,0 +1,340 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <string>
+
+#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") {}
+};
+
+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<MemTableRep> 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 port::kMaxUint32 yet.
+ uint32_t last_column_family_id_ = static_cast<uint32_t>(-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<const char*>(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;
+ Status s;
+ 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<SkipListFactory>();
+ options.memtable_factory = factory;
+ ImmutableCFOptions 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
+ bool res;
+ res = mem->Add(seq, kTypeValue, "key", "value2");
+ ASSERT_TRUE(res);
+ res = mem->Add(seq, kTypeValue, "key", "value2");
+ ASSERT_FALSE(res);
+ // Changing the type should still cause the duplicatae key
+ res = mem->Add(seq, kTypeMerge, "key", "value2");
+ ASSERT_FALSE(res);
+ // Changing the seq number will make the key fresh
+ res = mem->Add(seq + 1, kTypeMerge, "key", "value2");
+ ASSERT_TRUE(res);
+ // Test with different types for duplicate keys
+ res = mem->Add(seq, kTypeDeletion, "key", "");
+ ASSERT_FALSE(res);
+ res = mem->Add(seq, kTypeSingleDeletion, "key", "");
+ ASSERT_FALSE(res);
+
+ // Test the duplicate keys under stress
+ for (int i = 0; i < 10000; i++) {
+ bool insert_dup = i % 10 == 1;
+ if (!insert_dup) {
+ seq++;
+ }
+ res = mem->Add(seq, kTypeValue, "foo", "value" + ToString(seq));
+ if (insert_dup) {
+ ASSERT_FALSE(res);
+ } else {
+ ASSERT_TRUE(res);
+ }
+ }
+ delete mem;
+
+ // Test with InsertWithHint
+ options.memtable_insert_with_hint_prefix_extractor.reset(
+ new TestPrefixExtractor()); // which uses _ to extract the prefix
+ ioptions = ImmutableCFOptions(options);
+ mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
+ kMaxSequenceNumber, 0 /* column_family_id */);
+ // Insert a duplicate key with _ in it
+ res = mem->Add(seq, kTypeValue, "key_1", "value");
+ ASSERT_TRUE(res);
+ res = mem->Add(seq, kTypeValue, "key_1", "value");
+ ASSERT_FALSE(res);
+ delete mem;
+
+ // Test when InsertConcurrently will be invoked
+ options.allow_concurrent_memtable_write = true;
+ ioptions = ImmutableCFOptions(options);
+ mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
+ kMaxSequenceNumber, 0 /* column_family_id */);
+ MemTablePostProcessInfo post_process_info;
+ res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
+ ASSERT_TRUE(res);
+ res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
+ ASSERT_FALSE(res);
+ 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;
+ Status s;
+ 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<SkipListFactory>();
+ options.memtable_factory = factory;
+ options.allow_concurrent_memtable_write = true;
+ ImmutableCFOptions 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<uint64_t>(0));
+ bool res = mem->Add(0, kTypeValue, "key", value);
+ ASSERT_TRUE(res);
+ 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);
+ bool res1 =
+ mem->Add(seq, kTypeMerge, "key", v1, true, &post_process_info1);
+ ASSERT_TRUE(res1);
+ 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);
+ bool res2 =
+ mem->Add(seq, kTypeMerge, "key", v2, true, &post_process_info2);
+ ASSERT_TRUE(res2);
+ v2.clear();
+ }
+ });
+ write_thread1.join();
+ write_thread2.join();
+
+ Status status;
+ ReadOptions roptions;
+ SequenceNumber max_covering_tombstone_seq = 0;
+ LookupKey lkey("key", kMaxSequenceNumber);
+ res = mem->Get(lkey, &value, &status, &merge_context,
+ &max_covering_tombstone_seq, roptions);
+ 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<MockMemTableRepFactory*>(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("whitelisted", "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("whitelisted"));
+}
+
+TEST_F(DBMemTableTest, ColumnFamilyId) {
+ // Verifies MemTableRepFactory is told the right column family id.
+ Options options;
+ 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<MockMemTableRepFactory*>(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/storage/rocksdb/rocksdb/db/db_merge_operand_test.cc b/storage/rocksdb/rocksdb/db/db_merge_operand_test.cc
new file mode 100644
index 00000000..a0ab34e0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_merge_operand_test.cc
@@ -0,0 +1,240 @@
+// 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/fault_injection_test_env.h"
+#if !defined(ROCKSDB_LITE)
+#include "test_util/sync_point.h"
+#endif
+#include "rocksdb/merge_operator.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/sortlist.h"
+#include "utilities/merge_operators/string_append/stringappend2.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBMergeOperandTest : public DBTestBase {
+ public:
+ DBMergeOperandTest() : DBTestBase("/db_merge_operand_test") {}
+};
+
+TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
+ 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<Slice>& 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<LimitedStringAppendMergeOp>(2, ',');
+ options.env = env_;
+ Reopen(options);
+ int num_records = 4;
+ int number_of_operands = 0;
+ std::vector<PinnableSlice> values(num_records);
+ GetMergeOperandsOptions merge_operands_info;
+ merge_operands_info.expected_max_number_of_operands = num_records;
+
+ // k0 value in memtable
+ Put("k0", "PutARock");
+ db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), "k0",
+ values.data(), &merge_operands_info,
+ &number_of_operands);
+ ASSERT_EQ(values[0], "PutARock");
+
+ // k0.1 value in SST
+ Put("k0.1", "RockInSST");
+ ASSERT_OK(Flush());
+ 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"));
+ Put("k1", "x");
+ ASSERT_OK(Merge("k1", "b"));
+ ASSERT_OK(Merge("k1", "c"));
+ ASSERT_OK(Merge("k1", "d"));
+ 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"));
+ Delete("k1.1");
+ ASSERT_OK(Merge("k1.1", "c"));
+ ASSERT_OK(Merge("k1.1", "k"));
+ ASSERT_OK(Merge("k1.1", "s"));
+ 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());
+ 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"));
+ Put("k2.1", "l");
+ ASSERT_OK(Merge("k2.1", "n"));
+ ASSERT_OK(Merge("k2.1", "o"));
+ ASSERT_OK(Flush());
+ 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"));
+ Delete("k2.2");
+ ASSERT_OK(Merge("k2.2", "o"));
+ ASSERT_OK(Merge("k2.2", "t"));
+ ASSERT_OK(Flush());
+ 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");
+ 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"));
+ 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());
+ Put("k3.1", "bc");
+ ASSERT_OK(Flush());
+ ASSERT_OK(Merge("k3.1", "cd"));
+ ASSERT_OK(Flush());
+ ASSERT_OK(Merge("k3.1", "de"));
+ 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());
+ Delete("k3.2");
+ ASSERT_OK(Flush());
+ ASSERT_OK(Merge("k3.2", "cd"));
+ ASSERT_OK(Flush());
+ ASSERT_OK(Merge("k3.2", "de"));
+ 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"));
+ 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());
+ Put("k5", "remember");
+ ASSERT_OK(Merge("k5", "i"));
+ ASSERT_OK(Merge("k5", "am"));
+ ASSERT_OK(Merge("k5", "rocks"));
+ dbfull()->TEST_SwitchMemtable();
+ 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");
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_merge_operator_test.cc b/storage/rocksdb/rocksdb/db/db_merge_operator_test.cc
new file mode 100644
index 00000000..4f762468
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_merge_operator_test.cc
@@ -0,0 +1,666 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "db/db_test_util.h"
+#include "db/forward_iterator.h"
+#include "port/stack_trace.h"
+#include "rocksdb/merge_operator.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") {}
+
+ 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<Slice>& 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<LimitedStringAppendMergeOp>(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_TRUE(db_->Get(ReadOptions(), "k1", &value).ok());
+ // 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_TRUE(db_->Get(ReadOptions(), "k2", &value).ok());
+ 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_TRUE(db_->Get(ReadOptions(), "k3", &value).ok());
+ 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_TRUE(db_->Get(ReadOptions(), "k4", &value).ok());
+ 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"}});
+}
+
+
+class MergeOperatorPinningTest : public DBMergeOperatorTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ MergeOperatorPinningTest() { disable_block_cache_ = GetParam(); }
+
+ bool disable_block_cache_;
+};
+
+INSTANTIATE_TEST_CASE_P(MergeOperatorPinningTest, MergeOperatorPinningTest,
+ ::testing::Bool());
+
+#ifndef ROCKSDB_LITE
+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<std::string, std::string> 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 = RandomString(&rnd, 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<MergeOperator> _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<MergeOperator> merge_op_;
+ std::function<void()> before_merge_ = []() {};
+ std::function<void()> after_merge_ = []() {};
+};
+
+TEST_P(MergeOperatorPinningTest, EvictCacheBeforeMerge) {
+ Options options = CurrentOptions();
+
+ auto merge_hook =
+ std::make_shared<MergeOperatorHook>(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<std::string, std::string> true_data;
+ for (int i = 0; i < kNumOperands; i++) {
+ for (int j = 0; j < kNumKeys; j++) {
+ std::string k = Key(j);
+ std::string v = RandomString(&rnd, kOperandSize);
+ ASSERT_OK(db_->Merge(WriteOptions(), k, v));
+
+ true_data[k] = std::max(true_data[k], v);
+ }
+ ASSERT_OK(Flush());
+ }
+
+ std::vector<uint64_t> 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
+ for (uint64_t num : file_numbers) {
+ TableCache::Evict(dbfull()->TEST_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);
+
+ 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<void()> writer_func = [&]() {
+ int k = 0;
+ for (int i = 0; i < kNumWrites; i++) {
+ 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<void()> reader_func = [&]() {
+ ReadOptions ro;
+ ro.tailing = true;
+ Iterator* iter = db_->NewIterator(ro);
+
+ 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();
+ }
+
+ 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.
+
+ db_->Merge(WriteOptions(), "key", "sst");
+ db_->Flush(FlushOptions()); // Switch to SuperVersion A
+ db_->Merge(WriteOptions(), "key", "memtable");
+
+ // Pin SuperVersion A
+ std::unique_ptr<Iterator> someone_else(db_->NewIterator(ReadOptions()));
+
+ 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;
+ 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<Iterator> iter(db_->NewIterator(ro));
+ iter->Seek("key");
+
+ ASSERT_TRUE(iter->status().ok());
+ ASSERT_TRUE(iter->Valid());
+ EXPECT_EQ(std::string("sst,memtable"), iter->value().ToString());
+ EXPECT_TRUE(pushed_first_operand);
+ EXPECT_TRUE(stepped_to_next_operand);
+}
+#endif // ROCKSDB_LITE
+
+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<std::tuple<bool, int>> {
+ 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<int>(DBTestBase::kDefault),
+ static_cast<int>(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<std::string, std::string> 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 = RandomString(&rnd, 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 = RandomString(&rnd, 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 = RandomString(&rnd, 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/storage/rocksdb/rocksdb/db/db_options_test.cc b/storage/rocksdb/rocksdb/db/db_options_test.cc
new file mode 100644
index 00000000..383f66cb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_options_test.cc
@@ -0,0 +1,870 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <limits>
+#include <string>
+#include <unordered_map>
+
+#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") {}
+
+#ifndef ROCKSDB_LITE
+ std::unordered_map<std::string, std::string> GetMutableDBOptionsMap(
+ const DBOptions& options) {
+ std::string options_str;
+ GetStringFromDBOptions(&options_str, options);
+ std::unordered_map<std::string, std::string> options_map;
+ StringToMap(options_str, &options_map);
+ std::unordered_map<std::string, std::string> mutable_map;
+ for (const auto opt : db_options_type_info) {
+ if (opt.second.is_mutable &&
+ opt.second.verification != OptionVerificationType::kDeprecated) {
+ mutable_map[opt.first] = options_map[opt.first];
+ }
+ }
+ return mutable_map;
+ }
+
+ std::unordered_map<std::string, std::string> GetMutableCFOptionsMap(
+ const ColumnFamilyOptions& options) {
+ std::string options_str;
+ GetStringFromColumnFamilyOptions(&options_str, options);
+ std::unordered_map<std::string, std::string> options_map;
+ StringToMap(options_str, &options_map);
+ std::unordered_map<std::string, std::string> mutable_map;
+ for (const auto opt : cf_options_type_info) {
+ if (opt.second.is_mutable &&
+ opt.second.verification != OptionVerificationType::kDeprecated) {
+ mutable_map[opt.first] = options_map[opt.first];
+ }
+ }
+ return mutable_map;
+ }
+
+ std::unordered_map<std::string, std::string> 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<std::string, std::string> GetRandomizedMutableDBOptionsMap(
+ Random* rnd) {
+ DBOptions db_options;
+ test::RandomInitDBOptions(&db_options, rnd);
+ auto sanitized_options = SanitizeOptions(dbname_, db_options);
+ return GetMutableDBOptionsMap(sanitized_options);
+ }
+#endif // ROCKSDB_LITE
+};
+
+// RocksDB lite don't support dynamic options.
+#ifndef ROCKSDB_LITE
+
+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, 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++) {
+ 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++) {
+ 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);
+ int counter = 0;
+ int low_bytes_per_sync = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) { counter++; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ const std::string kValue(kValueSize, 'v');
+ int i = 0;
+ for (; i < 10; i++) {
+ 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++) {
+ 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<int> match_cnt(0);
+ std::atomic<int> unmatch_cnt(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::WritableFileWriter:0", [&](void* arg) {
+ int value = static_cast<int>(reinterpret_cast<uintptr_t>(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", ToString(i)));
+ ASSERT_OK(Put("bar", ToString(i)));
+ Flush();
+ }
+ 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 WriteableFileWriter
+
+ ASSERT_EQ(buffer_size,
+ dbfull()->GetDBOptions().writable_file_max_buffer_size);
+ i = 0;
+ for (; i < 3; i++) {
+ ASSERT_OK(Put("foo", ToString(i)));
+ ASSERT_OK(Put("bar", ToString(i)));
+ Flush();
+ }
+ 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<int>::max();
+ options.level0_slowdown_writes_trigger = std::numeric_limits<int>::max();
+ options.hard_pending_compaction_bytes_limit =
+ std::numeric_limits<uint64_t>::max();
+ options.soft_pending_compaction_bytes_limit =
+ std::numeric_limits<uint64_t>::max();
+ options.env = env_;
+
+ DestroyAndReopen(options);
+ int i = 0;
+ for (; i < 1024; i++) {
+ Put(Key(i), kValue);
+ }
+ Flush();
+ for (; i < 1024 * 2; i++) {
+ Put(Key(i), kValue);
+ }
+ Flush();
+ 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);
+ 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.
+ 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", ToString(i)));
+ ASSERT_OK(Put("bar", ToString(i)));
+ Flush();
+ }
+ ASSERT_EQ("3", FilesPerLevel());
+ ASSERT_OK(
+ dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "3"}}));
+ 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, 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;
+ test::RandomString(&rnd, value_size, &value);
+
+ 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<int>(cf), Key(i), value));
+ }
+ }
+ ASSERT_OK(dbfull()->SetDBOptions({{"max_total_wal_size", "10"}}));
+
+ for (size_t cf = 0; cf < handles_.size(); ++cf) {
+ dbfull()->TEST_WaitForFlushMemTable(handles_[cf]);
+ ASSERT_EQ("1", FilesPerLevel(static_cast<int>(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", ToString(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) {
+ SpecialEnv env(env_);
+ env.time_elapse_only_sleep_ = true;
+ Options options;
+ options.env = &env;
+ 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.addon_time_.store(20);
+ assert_candidate_files_empty(dbfull(), true);
+
+ env.addon_time_.store(21);
+ 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.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.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;
+ 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.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.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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ // Test dynamically changing ttl.
+ env_->addon_time_.store(0);
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ Flush();
+ }
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ ASSERT_EQ(NumTableFilesAtLevel(0), 10);
+
+ // Add 61 seconds to the time.
+ env_->addon_time_.fetch_add(61);
+
+ // No files should be compacted as ttl is set to 1 hour.
+ ASSERT_EQ(dbfull()->GetOptions().ttl, 3600);
+ 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);
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+
+ // Test dynamically changing compaction_options_fifo.max_table_files_size
+ env_->addon_time_.store(0);
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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);
+ 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);
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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);
+ 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);
+ 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.new_table_reader_for_compaction_inputs = true;
+ 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++) {
+ Put(Key(i), kValue);
+ }
+ Flush();
+ for (int i = 0; i < 1024 * 2; i++) {
+ Put(Key(i), kValue);
+ }
+ Flush();
+ 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;
+
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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);
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_properties_test.cc b/storage/rocksdb/rocksdb/db/db_properties_test.cc
new file mode 100644
index 00000000..50dc3efe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_properties_test.cc
@@ -0,0 +1,1711 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+
+#include <algorithm>
+#include <string>
+
+#include "db/db_test_util.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 "util/random.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBPropertiesTest : public DBTestBase {
+ public:
+ DBPropertiesTest() : DBTestBase("/db_properties_test") {}
+};
+
+#ifndef ROCKSDB_LITE
+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);
+ 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);
+
+ 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));
+ Put("12345678", "");
+ ASSERT_TRUE(
+ dbfull()->GetIntProperty("rocksdb.current-super-version-number", &v2));
+ 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) {
+ db_->Put(WriteOptions(), handle, RandomString(&rnd, kKeySize),
+ RandomString(&rnd, 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.
+ 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<double>(a);
+ double dbl_b = static_cast<double>(b);
+ if (dbl_a > dbl_b) {
+ ASSERT_LT(static_cast<double>(dbl_a - dbl_b) / (dbl_a + dbl_b), bias);
+ } else {
+ ASSERT_LT(static_cast<double>(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.15 : 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++) {
+ db_->Put(WriteOptions(), std::to_string(++key), "foo");
+ }
+ 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 = 5;
+ 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.preserve_deletes = 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(new BlockBasedTableFactory(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) {
+ db_->Put(WriteOptions(), RandomString(&rnd, kKeySize),
+ RandomString(&rnd, kValueSize));
+ }
+ for (int i = 0; i < kDeletionsPerTable; i++) {
+ db_->Delete(WriteOptions(), RandomString(&rnd, kKeySize));
+ }
+ for (int i = 0; i < kMergeOperandsPerTable; i++) {
+ db_->Merge(WriteOptions(), RandomString(&rnd, kKeySize),
+ RandomString(&rnd, kValueSize));
+ }
+ for (int i = 0; i < kRangeDeletionsPerTable; i++) {
+ std::string start = RandomString(&rnd, kKeySize);
+ std::string end = start;
+ end.resize(kValueSize);
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end);
+ }
+ 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<int*>(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++) {
+ Put("foo", "bar");
+ GenerateNewFile(&rnd, &key_index);
+ dbfull()->TEST_WaitForCompact();
+ }
+ 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);
+ 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<Iterator> iter(db_->NewIterator(ReadOptions()));
+ for (iter->Seek(Key(0)); iter->Valid(); iter->Next()) {
+ }
+ }
+ 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.
+ Put(1, "foo", "bar");
+ Flush(1);
+ 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
+ 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 = 2;
+ 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.preserve_deletes = 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(new BlockBasedTableFactory(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) {
+ db_->Put(WriteOptions(), RandomString(&rnd, kKeySize),
+ RandomString(&rnd, kValueSize));
+ }
+ for (int i = 0; i < kDeletionsPerTable; i++) {
+ db_->Delete(WriteOptions(), RandomString(&rnd, kKeySize));
+ }
+ for (int i = 0; i < kMergeOperandsPerTable; i++) {
+ db_->Merge(WriteOptions(), RandomString(&rnd, kKeySize),
+ RandomString(&rnd, kValueSize));
+ }
+ for (int i = 0; i < kRangeDeletionsPerTable; i++) {
+ std::string start = RandomString(&rnd, kKeySize);
+ std::string end = start;
+ end.resize(kValueSize);
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end);
+ }
+ db_->Flush(FlushOptions());
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ResetTableProperties(&sum_tp);
+ for (int level = 0; level < kMaxLevel; ++level) {
+ db_->GetProperty(
+ DB::Properties::kAggregatedTablePropertiesAtLevel + ToString(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, 0.5, 0.4, 0.4, 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<int64_t>(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<int>(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<int>(get_perf_context()->get_from_memtable_count));
+ get_perf_context()->Reset();
+ Get(1, "k2");
+ ASSERT_EQ(1, static_cast<int>(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<int>(get_perf_context()->get_from_memtable_count));
+ get_perf_context()->Reset();
+ Get(1, "k3");
+ ASSERT_EQ(1, static_cast<int>(get_perf_context()->get_from_memtable_count));
+ get_perf_context()->Reset();
+ Get(1, "k1");
+ ASSERT_EQ(3, static_cast<int>(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.
+ 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<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
+
+ ASSERT_OK(dbfull()->Put(writeOpt, "k6", big_value));
+ 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<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
+
+ ASSERT_OK(dbfull()->Put(writeOpt, "k7", big_value));
+ 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<Iterator*> 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) {
+ Put(RandomString(&rnd, kKeySize), RandomString(&rnd, kValueSize));
+ }
+ }
+ // Make sure that there is no flush between getting the two properties.
+ 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) {
+ Put(RandomString(&rnd, kKeySize), RandomString(&rnd, kValueSize));
+ }
+ }
+ // Force flush to prevent flush from happening between getting the
+ // properties or after getting the properties and before the new round.
+ 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) {
+ 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));
+ 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));
+ 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));
+ 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();
+
+ 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.compression_per_level = {kNoCompression, kSnappyCompression};
+ options.disable_auto_compactions = true;
+ options.num_levels = 2;
+ Reopen(options);
+
+ // 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 = ToString(i) + ToString(j) + "key";
+ ASSERT_OK(dbfull()->Put(WriteOptions(), key, kVal));
+ }
+ 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);
+
+ 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);
+}
+
+#endif // ROCKSDB_LITE
+
+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", ToString(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";
+ }
+};
+
+#ifndef ROCKSDB_LITE
+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<CountingUserTblPropCollectorFactory> collector_factory =
+ std::make_shared<CountingUserTblPropCollectorFactory>(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) {
+ db_->Put(WriteOptions(), ToString(table * 100 + i), "val");
+ }
+ 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;
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr);
+ ASSERT_GT(collector_factory->num_created_, 0U);
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBPropertiesTest, UserDefinedTablePropertiesContext) {
+ Options options = CurrentOptions();
+ options.level0_file_num_compaction_trigger = 3;
+ options.table_properties_collector_factories.resize(1);
+ std::shared_ptr<CountingUserTblPropCollectorFactory> collector_factory =
+ std::make_shared<CountingUserTblPropCollectorFactory>(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) {
+ Put(1, ToString(table * 100 + i), "val");
+ }
+ 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) {
+ Put(1, ToString(table * 100 + i), "val");
+ }
+ Flush(1);
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_GT(collector_factory->num_created_, 0U);
+
+ collector_factory->num_created_ = 0;
+ 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) {
+ Put(ToString(table * 100 + i), "val");
+ }
+ 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) {
+ Put(ToString(table * 100 + i), "val");
+ }
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_GT(collector_factory->num_created_, 0U);
+
+ collector_factory->num_created_ = 0;
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr);
+ ASSERT_GT(collector_factory->num_created_, 0U);
+}
+
+#ifndef ROCKSDB_LITE
+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<TablePropertiesCollectorFactory> collector_factory =
+ std::make_shared<CountingDeleteTabPropCollectorFactory>();
+ 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), RandomString(&rnd, 102)));
+ ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
+ }
+ Flush();
+ 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), ""));
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+
+ {
+ int c = 0;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
+ iter->Seek(Key(kMaxKey - 100));
+ while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) {
+ iter->Next();
+ ++c;
+ }
+ ASSERT_EQ(c, 200);
+ }
+
+ Delete(Key(0));
+ for (int i = kMaxKey - 100; i < kMaxKey + 100; i++) {
+ Delete(Key(i));
+ }
+ Delete(Key(kMaxKey * 2));
+
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+
+ {
+ SetPerfLevel(kEnableCount);
+ get_perf_context()->Reset();
+ int c = 0;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
+ iter->Seek(Key(kMaxKey - 100));
+ while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) {
+ iter->Next();
+ }
+ 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<TablePropertiesCollectorFactory> collector_factory =
+ std::make_shared<CountingDeleteTabPropCollectorFactory>();
+ 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), ""));
+ }
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+
+ for (int i = 1; i < kMaxKey - 1; i++) {
+ Delete(Key(i));
+ }
+ Flush();
+ 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);
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+ {
+ SetPerfLevel(kEnableCount);
+ get_perf_context()->Reset();
+ int c = 0;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
+ for (iter->Seek(Key(0)); iter->Valid(); iter->Next()) {
+ c++;
+ }
+ 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);
+ }
+}
+
+TEST_F(DBPropertiesTest, EstimateNumKeysUnderflow) {
+ Options options;
+ Reopen(options);
+ Put("foo", "bar");
+ Delete("foo");
+ 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) {
+ std::unique_ptr<MockTimeEnv> mock_env(new MockTimeEnv(Env::Default()));
+ uint64_t oldest_key_time = 0;
+ Options options;
+ options.env = mock_env.get();
+
+ // "rocksdb.estimate-oldest-key-time" only available to fifo compaction.
+ mock_env->set_current_time(100);
+ 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));
+ }
+
+ options.compaction_style = kCompactionStyleFIFO;
+ options.ttl = 300;
+ options.compaction_options_fifo.allow_compaction = false;
+ DestroyAndReopen(options);
+
+ mock_env->set_current_time(100);
+ ASSERT_OK(Put("k1", "v1"));
+ ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
+ &oldest_key_time));
+ ASSERT_EQ(100, oldest_key_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_env->set_current_time(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_env->set_current_time(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_env->set_current_time(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_env->set_current_time(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_env->set_current_time(650);
+ ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_EQ("", FilesPerLevel());
+ ASSERT_FALSE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
+ &oldest_key_time));
+
+ // Close before mock_env destructs.
+ Close();
+}
+
+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<TestListener> listener = std::make_shared<TestListener>();
+
+ Options options;
+ options.disable_auto_compactions = true;
+ options.listeners.push_back(listener);
+ Reopen(options);
+
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(Put("key" + ToString(i), std::string(1000, 'v')));
+ }
+ ASSERT_OK(Flush());
+ for (int i = 0; i < 5; i++) {
+ ASSERT_OK(Delete("key" + ToString(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<TestListener> listener = std::make_shared<TestListener>();
+
+ 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
+ Put("key1", "val");
+ Put("key2", "val");
+ Flush();
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_TRUE(listener->Validated());
+}
+
+TEST_F(DBPropertiesTest, BlockCacheProperties) {
+ Options options;
+ uint64_t value;
+
+ // 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;
+ block_cache->Insert("item1", nullptr /*value*/, kSize1, nullptr /*deleter*/);
+ 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;
+ block_cache->Insert("item2", nullptr /*value*/, kSize2, nullptr /*deleter*/,
+ &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;
+ block_cache->Insert("item3", nullptr /*value*/, kSize3, nullptr /*deleter*/,
+ &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);
+}
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_range_del_test.cc b/storage/rocksdb/rocksdb/db/db_range_del_test.cc
new file mode 100644
index 00000000..15225875
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_range_del_test.cc
@@ -0,0 +1,1660 @@
+// 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 "port/stack_trace.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "test_util/testutil.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBRangeDelTest : public DBTestBase {
+ public:
+ DBRangeDelTest() : DBTestBase("/db_range_del_test") {}
+
+ std::string GetNumericStr(int key) {
+ uint64_t uint64_key = static_cast<uint64_t>(key);
+ std::string str;
+ str.resize(8);
+ memcpy(&str[0], static_cast<void*>(&uint64_key), 8);
+ return str;
+ }
+};
+
+// PlainTableFactory, WriteBatchWithIndex, and NumTableFilesAtLevel() are not
+// supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+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, 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, 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();
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z");
+ db_->Flush(FlushOptions());
+
+ ASSERT_EQ(1, NumTableFilesAtLevel(0));
+ ASSERT_EQ(0, NumTableFilesAtLevel(1));
+ 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(new SpecialSkipListFactory(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();
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), Key(1));
+
+ Random rnd(301);
+ for (int i = 0; i < kNumFiles; ++i) {
+ std::vector<std::string> values;
+ // Write 12K (4 values, each 3K)
+ for (int j = 0; j < kNumPerFile; j++) {
+ values.push_back(RandomString(&rnd, 3 << 10));
+ ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j]));
+ if (j == 0 && i > 0) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ }
+ }
+ // put extra key to trigger final flush
+ ASSERT_OK(Put("", ""));
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0));
+ ASSERT_EQ(0, NumTableFilesAtLevel(1));
+
+ 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(new SpecialSkipListFactory(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;
+ Reopen(opts);
+
+ // snapshot protects range tombstone from dropping due to becoming obsolete.
+ const Snapshot* snapshot = db_->GetSnapshot();
+
+ // 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)));
+ Random rnd(301);
+ for (int i = 0; i < kNumFiles; ++i) {
+ std::vector<std::string> values;
+ // Write 1MB (256 values, each 4K)
+ for (int j = 0; j < kNumPerFile; j++) {
+ values.push_back(RandomString(&rnd, kBytesPerVal));
+ ASSERT_OK(Put(GetNumericStr(kNumPerFile * i + j), values[j]));
+ }
+ // extra entry to trigger SpecialSkipListFactory's flush
+ ASSERT_OK(Put(GetNumericStr(kNumPerFile), ""));
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
+ }
+
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
+ true /* disallow_trivial_move */);
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GE(NumTableFilesAtLevel(1), 2);
+
+ std::vector<std::vector<FileMetaData>> files;
+ dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
+
+ for (size_t i = 0; i < files[1].size() - 1; ++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<std::pair<std::string, std::string>> 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<std::vector<FileMetaData>> files;
+ dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
+ ASSERT_GT(files[0][0].fd.smallest_seqno, 0);
+
+ db_->ReleaseSnapshot(snapshot);
+}
+
+TEST_F(DBRangeDelTest, FlushRangeDelsSameStartKey) {
+ db_->Put(WriteOptions(), "b1", "val");
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c"));
+ 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) {
+ db_->Put(WriteOptions(), "unused", "val"); // prevents empty after compaction
+ 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) {
+ 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());
+ }
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) {
+ const int kNum = 300, kRangeBegin = 50, kRangeEnd = 250;
+ Options opts = CurrentOptions();
+ opts.comparator = test::Uint64Comparator();
+ Reopen(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) {
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
+ }
+ db_->Put(WriteOptions(), GetNumericStr(i), "val");
+ }
+ 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);
+}
+
+// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+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(new SpecialSkipListFactory(kNumPerFile));
+ opts.num_levels = 2;
+ opts.statistics = CreateDBStatistics();
+ Reopen(opts);
+
+ for (int i = 0; i < kNumFiles; ++i) {
+ if (i > 0) {
+ // range tombstone covers first half of the previous file
+ 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.
+ db_->Put(WriteOptions(), GetNumericStr(kNumPerFile * kNumFiles), "val");
+
+ for (int j = 0; j < kNumPerFile; ++j) {
+ db_->Put(WriteOptions(), GetNumericStr(i * kNumPerFile + j), "val");
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
+ }
+ 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(new SpecialSkipListFactory(kNumPerFile));
+ options.num_levels = 3;
+ options.target_file_size_base = kFileBytes;
+ options.target_file_size_multiplier = 1;
+ 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;
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ Key(mid - 5), Key(mid + 5));
+ }
+ std::vector<std::string> values;
+ // Write 100KB (100 values, each 1K)
+ for (int k = 0; k < kNumPerFile; k++) {
+ values.push_back(RandomString(&rnd, 990));
+ ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k]));
+ }
+ // put extra key to trigger flush
+ ASSERT_OK(Put("", ""));
+ 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()}));
+ 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(new SpecialSkipListFactory(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;
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ Key(mid - 5), Key(mid + 5));
+ }
+ std::vector<std::string> values;
+ // Write 100KB (100 values, each 1K)
+ for (int k = 0; k < kNumPerFile; k++) {
+ values.push_back(RandomString(&rnd, 990));
+ ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k]));
+ }
+ // put extra key to trigger flush
+ ASSERT_OK(Put("", ""));
+ dbfull()->TEST_WaitForFlushMemTable();
+ if (j < kFilesPerLevel - 1) {
+ // background compaction may happen early for kFilesPerLevel'th file
+ ASSERT_EQ(NumTableFilesAtLevel(0), j + 1);
+ }
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+ 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(
+ reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
+ ->cfd(),
+ 1 /* input_level */, 2 /* output_level */, CompactRangeOptions(),
+ nullptr /* begin */, nullptr /* end */, true /* exclusive */,
+ true /* disallow_trivial_move */,
+ port::kMaxUint64 /* max_file_num_to_ignore */));
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
+ const int kNumPerFile = 3, kNumFiles = 3;
+ Options opts = CurrentOptions();
+ opts.disable_auto_compactions = true;
+ opts.memtable_factory.reset(new SpecialSkipListFactory(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
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "key",
+ "key_");
+ }
+ std::string val;
+ PutFixed64(&val, i);
+ db_->Merge(WriteOptions(), "key", val);
+ // we need to prevent trivial move using Puts so compaction will actually
+ // process the merge operands.
+ db_->Put(WriteOptions(), "prevent_trivial_move", "");
+ if (i > 0 && i % kNumPerFile == 0) {
+ 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);
+
+ 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);
+}
+
+// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+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);
+
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
+ "dr10"); // obsolete after compaction
+ db_->Put(WriteOptions(), "key", "val");
+ db_->Flush(FlushOptions());
+ const Snapshot* snapshot = db_->GetSnapshot();
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr2",
+ "dr20"); // protected by snapshot
+ db_->Put(WriteOptions(), "key", "val");
+ db_->Flush(FlushOptions());
+
+ ASSERT_EQ(2, NumTableFilesAtLevel(0));
+ ASSERT_EQ(0, NumTableFilesAtLevel(1));
+ 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(new SpecialSkipListFactory(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));
+ Reopen(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) {
+ db_->Put(WriteOptions(), GetNumericStr(i), "val");
+ if (i > 0) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ if (i >= kNum / 2 && i < kNum / 2 + kNumRanges) {
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
+ }
+ }
+ // Must be > 1 so the first L1 file can be closed before scan finishes
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_GT(NumTableFilesAtLevel(1), 1);
+ std::vector<uint64_t> file_numbers = ListTableFiles(env_, dbname_);
+
+ ReadOptions read_opts;
+ auto* iter = db_->NewIterator(read_opts);
+ 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);
+}
+
+TEST_F(DBRangeDelTest, GetCoveredKeyFromMutableMemtable) {
+ do {
+ DestroyAndReopen(CurrentOptions());
+ 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(new SpecialSkipListFactory(1));
+ DestroyAndReopen(opts);
+
+ db_->Put(WriteOptions(), "key", "val");
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
+ 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());
+ 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);
+ db_->Merge(WriteOptions(), "key", val);
+ if (i == kNumMergeOps / 2) {
+ // deletes [0, 5]
+ 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(new SpecialSkipListFactory(1));
+ Reopen(opts);
+
+ 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()));
+ db_->Put(WriteOptions(), "imm_key", "val");
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
+ 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(new SpecialSkipListFactory(kNumPerFile));
+ Reopen(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) {
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
+ }
+ db_->Put(WriteOptions(), GetNumericStr(i), "val");
+ }
+ ReadOptions read_opts;
+ auto* iter = db_->NewIterator(read_opts);
+
+ 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(new SpecialSkipListFactory(kNumPerFile));
+ Reopen(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();
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
+ GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
+ }
+ db_->Put(WriteOptions(), GetNumericStr(i), "val");
+ }
+ ReadOptions read_opts;
+ read_opts.snapshot = snapshot;
+ auto* iter = db_->NewIterator(read_opts);
+
+ 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(new SpecialSkipListFactory(1));
+ Reopen(opts);
+
+ 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()));
+ db_->Put(WriteOptions(), "imm_key", "val");
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
+ 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);
+ 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) {
+ 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));
+
+ db_->EnableAutoCompaction({db_->DefaultColumnFamily()});
+ 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<double>(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"));
+ }
+ 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(
+ new SpecialSkipListFactory(2 /* num_entries_flush */));
+ options.target_file_size_base = kValueBytes;
+ // 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();
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0),
+ Key(2 * kNumFilesPerLevel));
+
+ Random rnd(301);
+ std::string value = RandomString(&rnd, 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) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(j, NumTableFilesAtLevel(0));
+ }
+ }
+ // put extra key to trigger final flush
+ ASSERT_OK(Put("", ""));
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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"}}));
+ 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(
+ new SpecialSkipListFactory(2 /* num_entries_flush */));
+ options.target_file_size_base = kValueBytes;
+ options.disable_auto_compactions = true;
+
+ 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();
+ 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 = RandomString(&rnd, 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;
+ 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;
+ 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<std::vector<FileMetaData>> 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 = RandomString(&rnd, 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());
+
+ 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.
+ 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 = RandomString(&rnd, 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());
+ 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 = RandomString(&rnd, 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 = RandomString(&rnd, 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());
+ 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)));
+
+ db_->Flush(FlushOptions());
+
+ ReadOptions read_opts;
+ read_opts.snapshot = snapshot;
+ auto* iter = db_->NewIterator(read_opts);
+
+ 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<const Snapshot, std::function<void(const Snapshot*)>>
+ 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<Iterator> iter(db_->NewIterator(read_opts));
+
+ 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 = RandomString(&rnd, kValueBytes);
+ ASSERT_OK(Put(key, value));
+ }
+ 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"));
+ 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);
+ 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<LiveFileMetaData> all_metadata;
+ std::vector<LiveFileMetaData> 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, OverlappedTombstones) {
+ const int kNumPerFile = 4, kNumFiles = 2;
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.max_compaction_bytes = 9 * 1024;
+ DestroyAndReopen(options);
+ Random rnd(301);
+ for (int i = 0; i < kNumFiles; ++i) {
+ std::vector<std::string> values;
+ // Write 12K (4 values, each 3K)
+ for (int j = 0; j < kNumPerFile; j++) {
+ values.push_back(RandomString(&rnd, 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));
+
+ 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<std::vector<FileMetaData>> files;
+ 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.max_compaction_bytes = 9 * 1024;
+ DestroyAndReopen(options);
+ Random rnd(301);
+ for (int i = 0; i < kNumFiles; ++i) {
+ std::vector<std::string> values;
+ // Write 12K (4 values, each 3K)
+ for (int j = 0; j < kNumPerFile; j++) {
+ values.push_back(RandomString(&rnd, 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
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
+ true /* disallow_trivial_move */);
+ ASSERT_EQ(3, NumTableFilesAtLevel(1));
+
+ std::vector<std::vector<FileMetaData>> files;
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr, nullptr,
+ true /* disallow_trivial_move */);
+ ASSERT_EQ(1, NumTableFilesAtLevel(2));
+ ASSERT_EQ(0, NumTableFilesAtLevel(1));
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_sst_test.cc b/storage/rocksdb/rocksdb/db/db_sst_test.cc
new file mode 100644
index 00000000..e0ecfb64
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_sst_test.cc
@@ -0,0 +1,1227 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/sst_file_manager.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBSSTTest : public DBTestBase {
+ public:
+ DBSSTTest() : DBTestBase("/db_sst_test") {}
+};
+
+#ifndef ROCKSDB_LITE
+// 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<std::mutex> lock(mutex_);
+ flushed_files_.push_back(info.file_path);
+ }
+
+ std::vector<std::string> GetFlushedFiles() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ std::vector<std::string> result;
+ for (auto fname : flushed_files_) {
+ result.push_back(fname);
+ }
+ return result;
+ }
+ void ClearFlushedFiles() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ flushed_files_.clear();
+ }
+
+ private:
+ std::vector<std::string> flushed_files_;
+ std::mutex mutex_;
+};
+#endif // ROCKSDB_LITE
+
+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<void()> 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);
+ }
+ Flush();
+ Close();
+ int const num_files = GetSstFileCount(dbname_);
+ ASSERT_GT(num_files, 0);
+
+ Reopen(options);
+ std::vector<std::string> values;
+ values.reserve(key_id);
+ for (int k = 0; k < key_id; ++k) {
+ values.push_back(Get(Key(k)));
+ }
+ Close();
+
+ std::vector<std::string> 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.skip_checking_sst_file_sizes_on_db_open = true;
+ Reopen(options);
+
+ ASSERT_EQ("choo", Get("pika"));
+}
+
+#ifndef ROCKSDB_LITE
+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), RandomString(&rnd, 10 * 1024)));
+ }
+ ASSERT_OK(Flush());
+ }
+ // this should execute both L0->L1 and L1->(move)->L2 compactions
+ 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), RandomString(&rnd, 10 * 1024)));
+ }
+ ASSERT_OK(Flush());
+ }
+ // this should execute both L0->L1 and L1->(move)->L2 compactions
+ 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<void()> 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), RandomString(&rnd, 10 * 1024)));
+ }
+ blocking_thread.WaitUntilSleeping();
+
+ ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr));
+
+ ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
+ std::vector<LiveFileMetaData> 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();
+ dbfull()->TEST_WaitForFlushMemTable();
+ // File just flushed is too big for L0 and L1 so gets moved to L2.
+ 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_F(DBSSTTest, DBWithSstFileManager) {
+ std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
+ auto sfm = static_cast<SstFileManagerImpl*>(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());
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ // Verify that we are tracking all sst files in dbname_
+ ASSERT_EQ(sfm->GetTrackedFiles(), GetAllSSTFiles());
+ }
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ auto files_in_db = GetAllSSTFiles();
+ // 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<SstFileManagerImpl*>(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, RateLimitedDelete) {
+ Destroy(last_options_);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"DBSSTTest::RateLimitedDelete:1",
+ "DeleteScheduler::BackgroundEmptyTrash"},
+ });
+
+ std::vector<uint64_t> penalties;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DeleteScheduler::BackgroundEmptyTrash:Wait",
+ [&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(arg))); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
+ // Turn timed wait into a simulated sleep
+ uint64_t* abs_time_us = static_cast<uint64_t*>(arg);
+ int64_t cur_time = 0;
+ env_->GetCurrentTime(&cur_time);
+ if (*abs_time_us > static_cast<uint64_t>(cur_time)) {
+ env_->addon_time_.fetch_add(*abs_time_us -
+ static_cast<uint64_t>(cur_time));
+ }
+
+ // Randomly sleep shortly
+ env_->addon_time_.fetch_add(
+ static_cast<uint64_t>(Random::GetTLSInstance()->Uniform(10)));
+
+ // Set wait until time to before current to force not to sleep.
+ int64_t real_cur_time = 0;
+ Env::Default()->GetCurrentTime(&real_cur_time);
+ *abs_time_us = static_cast<uint64_t>(real_cur_time);
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ env_->no_slowdown_ = true;
+ env_->time_elapse_only_sleep_ = true;
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ // Need to disable stats dumping and persisting which also use
+ // RepeatableThread, one of whose member variables is of type
+ // InstrumentedCondVar. The callback for
+ // InstrumentedCondVar::TimedWaitInternal can be triggered by stats dumping
+ // and persisting threads and cause time_spent_deleting measurement to become
+ // incorrect.
+ options.stats_dump_period_sec = 0;
+ options.stats_persist_period_sec = 0;
+ 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<SstFileManagerImpl*>(options.sst_file_manager.get());
+ sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
+
+ WriteOptions wo;
+ wo.disableWAL = true;
+ ASSERT_OK(TryReopen(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<LiveFileMetaData> 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);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBSSTTest, RateLimitedWALDelete) {
+ Destroy(last_options_);
+
+ std::vector<uint64_t> penalties;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DeleteScheduler::BackgroundEmptyTrash:Wait",
+ [&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(arg))); });
+
+ env_->no_slowdown_ = true;
+ env_->time_elapse_only_sleep_ = true;
+ 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<SstFileManagerImpl*>(options.sst_file_manager.get());
+ sfm->delete_scheduler()->SetMaxTrashDBRatio(3.1);
+
+ 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 DBSSTTest,
+ public testing::WithParamInterface<std::tuple<std::string, bool>> {
+ public:
+ DBWALTestWithParam() {
+ 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) {}
+
+ Status DeleteFile(const std::string& fname) {
+ 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<MyEnv> env(new MyEnv(Env::Default()));
+ 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<SstFileManagerImpl*>(options.sst_file_manager.get());
+ sfm->delete_scheduler()->SetMaxTrashDBRatio(3.1);
+
+ ASSERT_OK(TryReopen(options));
+
+ // 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));
+
+ Close();
+
+ options.sst_file_manager.reset();
+ std::vector<std::string> filenames;
+ int trash_log_count = 0;
+ if (!wal_dir_same_as_dbname_) {
+ // Forcibly create some trash log files
+ std::unique_ptr<WritableFile> result;
+ env->NewWritableFile(options.wal_dir + "/1000.log.trash", &result,
+ EnvOptions());
+ result.reset();
+ }
+ 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);
+ ASSERT_OK(TryReopen(options));
+
+ filenames.clear();
+ trash_log_count = 0;
+ 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<SstFileManagerImpl*>(options.sst_file_manager.get());
+
+ Destroy(last_options_);
+
+ // Add some trash files to the db directory so the DB can clean them up
+ 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
+ ASSERT_OK(TryReopen(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<int> 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<SstFileManagerImpl*>(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" + ToString(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" + ToString(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" + ToString(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<std::string> db_files;
+ 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<SstFileManagerImpl*>(options.sst_file_manager.get());
+
+ sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
+ sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
+ 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<SstFileManager> sst_file_manager(NewSstFileManager(env_));
+ auto sfm = static_cast<SstFileManagerImpl*>(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), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+
+ uint64_t first_file_size = 0;
+ auto files_in_db = GetAllSSTFiles(&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, CancellingCompactionsWorks) {
+ std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
+ auto sfm = static_cast<SstFileManagerImpl*>(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), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+ uint64_t total_file_size = 0;
+ auto files_in_db = GetAllSSTFiles(&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), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+ 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);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBSSTTest, CancellingManualCompactionsWorks) {
+ std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
+ auto sfm = static_cast<SstFileManagerImpl*>(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), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+ uint64_t total_file_size = 0;
+ auto files_in_db = GetAllSSTFiles(&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), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+
+ // OK, now trigger a manual compaction
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ // Wait for manual compaction to get scheduled and finish
+ 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();
+ dbfull()->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), l0_files, 0);
+
+ // Wait for manual compaction to get scheduled and finish
+ 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();
+ dbfull()->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), l0_files, 0);
+ 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<int> max_space_limits_mbs = {1, 10};
+ std::atomic<bool> bg_error_set(false);
+
+ std::atomic<int> reached_max_space_on_flush(0);
+ std::atomic<int> reached_max_space_on_compaction(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
+ [&](void* arg) {
+ Status* bg_error = static_cast<Status*>(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<bool*>(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<SstFileManager> sst_file_manager(NewSstFileManager(env_));
+ auto sfm = static_cast<SstFileManagerImpl*>(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(RandomString(&rnd, 10), RandomString(&rnd, 50));
+ if (!s.ok()) {
+ break;
+ }
+ }
+ ASSERT_TRUE(bg_error_set);
+ uint64_t total_sst_files_size = 0;
+ GetAllSSTFiles(&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;
+ 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<std::vector<FileMetaData>> 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, 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.
+ std::unique_ptr<MockTimeEnv> mock_env(new MockTimeEnv(Env::Default()));
+ mock_env->set_current_time(0);
+
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.compression = kNoCompression;
+ options.env = mock_env.get();
+ 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_" + ToString(i);
+ ASSERT_OK(Put(Key(j), val));
+ }
+ Flush();
+ }
+ ASSERT_EQ("5", FilesPerLevel(0));
+
+ std::vector<LiveFileMetaData> 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<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
+
+ // 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<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
+
+ // Delete all keys and compact, this will delete all live files
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(Delete(Key(i)));
+ }
+ 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);
+
+ 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);
+
+ 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);
+
+ // Close db before mock_env destruct.
+ Close();
+}
+
+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"));
+ Flush();
+ }
+ ASSERT_EQ("5", FilesPerLevel(0));
+
+ std::vector<LiveFileMetaData> 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<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
+
+ // 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<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
+
+ // Delete all keys and compact, this will delete all live files
+ for (int i = 0; i < 5; i++) {
+ ASSERT_OK(Delete(Key(i)));
+ }
+ 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);
+
+ iter1.reset();
+ 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);
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_statistics_test.cc b/storage/rocksdb/rocksdb/db/db_statistics_test.cc
new file mode 100644
index 00000000..8fbbb96d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_statistics_test.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "db/db_test_util.h"
+#include "monitoring/thread_status_util.h"
+#include "port/stack_trace.h"
+#include "rocksdb/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBStatisticsTest : public DBTestBase {
+ public:
+ DBStatisticsTest() : DBTestBase("/db_statistics_test") {}
+};
+
+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), RandomString(&rnd, 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), RandomString(&rnd, 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);
+ options.statistics->Reset();
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_table_properties_test.cc b/storage/rocksdb/rocksdb/db/db_table_properties_test.cc
new file mode 100644
index 00000000..e3499df7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_table_properties_test.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <unordered_set>
+#include <vector>
+
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/table_properties_collectors.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+#ifndef ROCKSDB_LITE
+
+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<uint64_t> 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);
+}
+} // namespace
+
+class DBTablePropertiesTest : public DBTestBase {
+ public:
+ DBTablePropertiesTest() : DBTestBase("/db_table_properties_test") {}
+ TablePropertiesCollection TestGetPropertiesOfTablesInRange(
+ std::vector<Range> 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;
+ Reopen(options);
+ // Create 4 tables
+ for (int table = 0; table < 4; ++table) {
+ for (int i = 0; i < 10 + table; ++i) {
+ db_->Put(WriteOptions(), ToString(table * 100 + i), "val");
+ }
+ db_->Flush(FlushOptions());
+ }
+
+ // 1. Read table properties directly from file
+ Reopen(options);
+ VerifyTableProperties(db_, 10 + 11 + 12 + 13);
+
+ // 2. Put two tables to table cache and
+ Reopen(options);
+ // 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(ToString(i * 100 + 0));
+ }
+
+ VerifyTableProperties(db_, 10 + 11 + 12 + 13);
+
+ // 3. Put all tables to table cache
+ Reopen(options);
+ // fetch key from 1st and 2nd table, which will internally place that table to
+ // the table cache.
+ for (int i = 0; i < 4; ++i) {
+ Get(ToString(i * 100 + 0));
+ }
+ VerifyTableProperties(db_, 10 + 11 + 12 + 13);
+}
+
+TablePropertiesCollection
+DBTablePropertiesTest::TestGetPropertiesOfTablesInRange(
+ std::vector<Range> 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<LiveFileMetaData> 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), RandomString(&rnd, 102)));
+ }
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ if (NumTableFilesAtLevel(0) == 0) {
+ ASSERT_OK(Put(test::RandomKey(&rnd, 5), RandomString(&rnd, 102)));
+ Flush();
+ }
+
+ 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<std::string> 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<Range> 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) {
+ Put(cf, "key", "val");
+ 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<uint32_t>(
+ fname_to_props.begin()->second->column_family_id));
+ }
+}
+
+TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
+ int kNumKeys = 1000;
+ int kWindowSize = 100;
+ int kNumDelsTrigger = 90;
+ std::shared_ptr<TablePropertiesCollectorFactory> compact_on_del =
+ NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger);
+
+ Options opts = CurrentOptions();
+ opts.table_properties_collector_factories.emplace_back(compact_on_del);
+ Reopen(opts);
+
+ // add an L1 file to prevent tombstones from dropping due to obsolescence
+ // during flush
+ Put(Key(0), "val");
+ Flush();
+ MoveFilesToLevel(1);
+
+ for (int i = 0; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GT(NumTableFilesAtLevel(1), 0);
+
+ // Change the window size and deletion trigger and ensure new values take
+ // effect
+ kWindowSize = 50;
+ kNumDelsTrigger = 40;
+ static_cast<CompactOnDeletionCollectorFactory*>
+ (compact_on_del.get())->SetWindowSize(kWindowSize);
+ static_cast<CompactOnDeletionCollectorFactory*>
+ (compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger);
+ for (int i = 0; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GT(NumTableFilesAtLevel(1), 0);
+
+ // Change the window size to disable delete triggered compaction
+ kWindowSize = 0;
+ static_cast<CompactOnDeletionCollectorFactory*>
+ (compact_on_del.get())->SetWindowSize(kWindowSize);
+ static_cast<CompactOnDeletionCollectorFactory*>
+ (compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger);
+ for (int i = 0; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(1, NumTableFilesAtLevel(0));
+
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_tailing_iter_test.cc b/storage/rocksdb/rocksdb/db/db_tailing_iter_test.cc
new file mode 100644
index 00000000..39988638
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_tailing_iter_test.cc
@@ -0,0 +1,547 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#if !defined(ROCKSDB_LITE)
+
+#include "db/db_test_util.h"
+#include "db/forward_iterator.h"
+#include "port/stack_trace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBTestTailingIterator : public DBTestBase {
+ public:
+ DBTestTailingIterator() : DBTestBase("/db_tailing_iterator_test") {}
+};
+
+TEST_F(DBTestTailingIterator, TailingIteratorSingle) {
+ ReadOptions read_options;
+ read_options.tailing = true;
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
+ iter->SeekToFirst();
+ ASSERT_TRUE(!iter->Valid());
+
+ // 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_F(DBTestTailingIterator, TailingIteratorKeepAdding) {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
+ ReadOptions read_options;
+ read_options.tailing = true;
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
+ 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_F(DBTestTailingIterator, TailingIteratorSeekToNext) {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
+ ReadOptions read_options;
+ read_options.tailing = true;
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
+ std::unique_ptr<Iterator> itern(db_->NewIterator(read_options, handles_[1]));
+ 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_F(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;
+ 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<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
+ std::unique_ptr<Iterator> itern(db_->NewIterator(read_options, handles_[1]));
+ std::unique_ptr<Iterator> iterh(db_->NewIterator(read_options, handles_[1]));
+ 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<ForwardIterator*>(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<ForwardIterator*>(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));
+ 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;
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ read_options.read_tier = kBlockCacheTier;
+ std::unique_ptr<Iterator> iteri(db_->NewIterator(read_options, handles_[1]));
+ 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]));
+ 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_F(DBTestTailingIterator, TailingIteratorDeletes) {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
+ ReadOptions read_options;
+ read_options.tailing = true;
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
+
+ // 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_F(DBTestTailingIterator, TailingIteratorPrefixSeek) {
+ ReadOptions read_options;
+ read_options.tailing = 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<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
+ 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_F(DBTestTailingIterator, TailingIteratorIncomplete) {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
+ ReadOptions read_options;
+ read_options.tailing = true;
+ read_options.read_tier = kBlockCacheTier;
+
+ std::string key("key");
+ std::string value("value");
+
+ ASSERT_OK(db_->Put(WriteOptions(), key, value));
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
+ 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_F(DBTestTailingIterator, TailingIteratorSeekToSame) {
+ Options options = CurrentOptions();
+ options.compaction_style = kCompactionStyleUniversal;
+ options.write_buffer_size = 1000;
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ ReadOptions read_options;
+ read_options.tailing = 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<Iterator> iter(db_->NewIterator(read_options));
+ // 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_F(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;
+
+ 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<Iterator> it(db_->NewIterator(read_options, handles_[1]));
+ 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());
+
+ // 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_EQ(0, immutable_seeks);
+}
+
+TEST_F(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;
+
+ 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<Iterator> 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());
+}
+
+TEST_F(DBTestTailingIterator, SeekWithUpperBoundBug) {
+ ReadOptions read_options;
+ read_options.tailing = 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<Iterator> iter(db_->NewIterator(read_options));
+
+ iter->Seek("aa");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(iter->key().ToString(), "aa");
+}
+
+TEST_F(DBTestTailingIterator, SeekToFirstWithUpperBoundBug) {
+ ReadOptions read_options;
+ read_options.tailing = 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<Iterator> iter(db_->NewIterator(read_options));
+
+ 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
+
+#endif // !defined(ROCKSDB_LITE)
+
+int main(int argc, char** argv) {
+#if !defined(ROCKSDB_LITE)
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ (void) argc;
+ (void) argv;
+ return 0;
+#endif
+}
diff --git a/storage/rocksdb/rocksdb/db/db_test.cc b/storage/rocksdb/rocksdb/db/db_test.cc
new file mode 100644
index 00000000..60b4d60f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_test.cc
@@ -0,0 +1,6605 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <fcntl.h>
+#include <algorithm>
+#include <set>
+#include <thread>
+#include <unordered_set>
+#include <utility>
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+#ifdef OS_SOLARIS
+#include <alloca.h>
+#endif
+
+#include "cache/lru_cache.h"
+#include "db/blob_index.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 "memtable/hash_linklist_rep.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/utilities/checkpoint.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/mock_table.h"
+#include "table/plain/plain_table_factory.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/rate_limiter.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBTest : public DBTestBase {
+ public:
+ DBTest() : DBTestBase("/db_test") {}
+};
+
+class DBTestWithParam
+ : public DBTest,
+ public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
+ 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<MockEnv> env{new MockEnv(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;
+
+// TEST_FlushMemTable() is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(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]);
+ }
+#endif // ROCKSDB_LITE
+
+ delete db;
+}
+
+// NewMemEnv returns nullptr in ROCKSDB_LITE since class InMemoryEnv isn't
+// defined.
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest, MemEnvTest) {
+ std::unique_ptr<Env> 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 = reinterpret_cast<DBImpl*>(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;
+}
+#endif // ROCKSDB_LITE
+
+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<int> sleep_count(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::DelayWrite:Sleep",
+ [&](void* /*arg*/) { sleep_count.fetch_add(1); });
+ std::atomic<int> 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;
+ 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_NOK(dbfull()->Put(wo, "foo2", "bar2"));
+ ASSERT_GE(sleep_count.load(), 0);
+ ASSERT_GE(wait_count.load(), 0);
+ token.reset();
+
+ token = dbfull()->TEST_write_controler().GetDelayToken(1000000000);
+ wo.no_slowdown = false;
+ ASSERT_OK(dbfull()->Put(wo, "foo3", "bar3"));
+ 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<port::Thread> threads;
+ std::atomic<int> thread_num(0);
+
+ std::function<void()> 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<void()> 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<int> 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;
+ 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<port::Thread> threads;
+ std::atomic<int> thread_num(0);
+
+ std::function<void()> 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<int> 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<int> 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;
+ 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<port::Thread> threads;
+ std::atomic<int> thread_num(0);
+
+ std::function<void()> 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<void()> 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<void()> 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<int> 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;
+ 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"));
+}
+#ifndef ROCKSDB_LITE
+
+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));
+}
+#endif // ROCKSDB_LITE
+
+
+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 beccause 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<ColumnFamilyHandle*> multiget_cfs;
+ multiget_cfs.push_back(handles_[1]);
+ multiget_cfs.push_back(handles_[1]);
+ std::vector<Slice> multiget_keys;
+ multiget_keys.push_back("foo");
+ multiget_keys.push_back("bar");
+ std::vector<std::string> 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.
+ Put(1, "foo", "first");
+ Put(1, "bar", "one");
+ ASSERT_OK(Flush(1));
+ MoveFilesToLevel(2, 1);
+
+ // (Single) delete hidden by a put
+ SingleDelete(1, "foo");
+ Put(1, "foo", "second");
+ Delete(1, "bar");
+ Put(1, "bar", "two");
+ ASSERT_OK(Flush(1));
+
+ SingleDelete(1, "foo");
+ Delete(1, "bar");
+ ASSERT_OK(Flush(1));
+
+ 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 beccause 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);
+
+ Put(1, "foo", Slice());
+ Put(1, "a", Slice());
+ SingleDelete(1, "a");
+ ASSERT_OK(Flush(1));
+
+ ASSERT_EQ("[ ]", AllEntriesFor("a", 1));
+ // Skip FIFO and universal compaction beccause 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();
+
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(1, NumTableFilesAtLevel(0));
+#endif // !ROCKSDB_LITE
+
+ 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);
+ Put(1, "k1", std::string(100000, 'x')); // Fill memtable
+ 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_));
+}
+
+#ifndef ROCKSDB_LITE
+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
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ ASSERT_OK(Flush(1));
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
+ Put(1, "a", "begin");
+ 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.
+ 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
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_EQ(NumTableFilesAtLevel(0, 1), 1); // XXX
+ } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction));
+}
+#endif // ROCKSDB_LITE
+
+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());
+}
+#ifndef ROCKSDB_LITE
+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<int64_t>(options.write_buffer_size);
+ options.max_write_buffer_number = 2;
+ options.write_buffer_size = 120 * 1024;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ std::vector<port::Thread> threads;
+
+ std::atomic<int> 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<void()> 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], RandomString(&rnd, 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<uint64_t>(10));
+ ASSERT_GT(default_tables, static_cast<uint64_t>(0));
+ ASSERT_LE(pikachu_tables, static_cast<uint64_t>(10));
+ ASSERT_GT(pikachu_tables, static_cast<uint64_t>(0));
+}
+#endif // ROCKSDB_LITE
+
+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<CompactionFilter> 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<CompactionFilter>(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_->addon_time_.fetch_add(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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
+ }
+
+ const char* Name() const override { return "DelayFilterFactory"; }
+
+ private:
+ DBTestBase* db_test;
+};
+} // namespace
+
+#ifndef ROCKSDB_LITE
+
+static std::string CompressibleString(Random* rnd, int len) {
+ std::string r;
+ test::CompressibleString(rnd, 0.8, len, &r);
+ return r;
+}
+#endif // ROCKSDB_LITE
+
+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::vector<std::vector<FileMetaData>>& files_by_level,
+ uint64_t start_time, uint64_t end_time) {
+ ASSERT_EQ(cf_meta.name, kDefaultColumnFamilyName);
+ 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_GE(file_meta_from_cf.oldest_ancester_time, start_time);
+ ASSERT_LE(file_meta_from_cf.oldest_ancester_time, end_time);
+ }
+
+ 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<LiveFileMetaData>& live_file_meta,
+ const std::vector<std::vector<FileMetaData>>& 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);
+
+ ++i;
+ }
+}
+
+#ifndef ROCKSDB_LITE
+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<uint64_t>(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);
+
+ Flush();
+ }
+
+ std::vector<std::vector<FileMetaData>> files_by_level;
+ dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
+
+ options.env->GetCurrentTime(&temp_time);
+ uint64_t end_time = static_cast<uint64_t>(temp_time);
+
+ ColumnFamilyMetaData cf_meta;
+ db_->GetColumnFamilyMetaData(&cf_meta);
+ CheckColumnFamilyMeta(cf_meta, files_by_level, start_time, end_time);
+
+ std::vector<LiveFileMetaData> live_file_meta;
+ db_->GetLiveFilesMetaData(&live_file_meta);
+ CheckLiveFilesMeta(live_file_meta, files_by_level);
+}
+
+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<std::string> values;
+ // Write 120KB (12 values, each 10K)
+ for (int i = 0; i < 12; i++) {
+ values.push_back(DBTestBase::RandomString(&rnd, 10000));
+ ASSERT_OK(self->Put(DBTestBase::Key(i), values[i]));
+ }
+ 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<std::string> values;
+ for (int i = 0; i < 12; i++) {
+ values.push_back(DBTestBase::RandomString(&rnd, 10000));
+ ASSERT_OK(self->Put(DBTestBase::Key(i), values[i]));
+ }
+ 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;
+}
+} // 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 =
+ RandomString(&rnd, static_cast<int>(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());
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTest, SparseMerge) {
+ do {
+ Options options = CurrentOptions();
+ options.compression = kNoCompression;
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ FillLevels("A", "Z", 1);
+
+ // Suppose there is:
+ // small amount of data with prefix A
+ // large amount of data with prefix B
+ // small amount of data with prefix C
+ // and that recent updates have made small changes to all three prefixes.
+ // Check that we do not do a compaction that merges all of B in one shot.
+ const std::string value(1000, 'x');
+ Put(1, "A", "va");
+ // Write approximately 100MB of "B" values
+ for (int i = 0; i < 100000; i++) {
+ char key[100];
+ snprintf(key, sizeof(key), "B%010d", i);
+ Put(1, key, value);
+ }
+ Put(1, "C", "vc");
+ ASSERT_OK(Flush(1));
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+
+ // Make sparse update
+ Put(1, "A", "va2");
+ Put(1, "B100", "bvalue2");
+ Put(1, "C", "vc2");
+ ASSERT_OK(Flush(1));
+
+ // Compactions should not cause us to create a situation where
+ // a file overlaps too much data at the next level.
+ ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
+ 20 * 1048576);
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr);
+ ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
+ 20 * 1048576);
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr);
+ ASSERT_LE(dbfull()->TEST_MaxNextLevelOverlappingBytes(handles_[1]),
+ 20 * 1048576);
+ } while (ChangeCompactOptions());
+}
+
+#ifndef ROCKSDB_LITE
+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), RandomString(&rnd, 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_memtabtles = true;
+ size_approx_options.include_files = true;
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size);
+ ASSERT_GT(size, 6000);
+ ASSERT_LT(size, 204800);
+ // Zero if not including mem table
+ db_->GetApproximateSizes(&r, 1, &size);
+ ASSERT_EQ(size, 0);
+
+ start = Key(500);
+ end = Key(600);
+ r = Range(start, end);
+ 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), RandomString(&rnd, 1024)));
+ }
+
+ start = Key(500);
+ end = Key(600);
+ r = Range(start, end);
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size);
+ ASSERT_EQ(size, 0);
+
+ start = Key(100);
+ end = Key(1020);
+ r = Range(start, end);
+ 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;
+ }
+ std::random_shuffle(std::begin(keys), std::end(keys));
+
+ for (int i = 0; i < N * 3; i++) {
+ ASSERT_OK(Put(Key(keys[i] + 1000), RandomString(&rnd, 1024)));
+ }
+
+ start = Key(100);
+ end = Key(300);
+ r = Range(start, end);
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size);
+ ASSERT_EQ(size, 0);
+
+ start = Key(1050);
+ end = Key(1080);
+ r = Range(start, end);
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size);
+ ASSERT_GT(size, 6000);
+
+ start = Key(2100);
+ end = Key(2300);
+ r = Range(start, end);
+ 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;
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1,
+ &size_with_mt);
+ ASSERT_GT(size_with_mt, 6000);
+ db_->GetApproximateSizes(&r, 1, &size_without_mt);
+ ASSERT_EQ(size_without_mt, 0);
+
+ Flush();
+
+ for (int i = 0; i < N; i++) {
+ ASSERT_OK(Put(Key(i + 1000), RandomString(&rnd, 1024)));
+ }
+
+ start = Key(1050);
+ end = Key(1080);
+ r = Range(start, end);
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1,
+ &size_with_mt);
+ db_->GetApproximateSizes(&r, 1, &size_without_mt);
+ ASSERT_GT(size_with_mt, size_without_mt);
+ ASSERT_GT(size_without_mt, 6000);
+
+ // Check that include_memtabtles flag works as expected
+ size_approx_options.include_memtabtles = false;
+ 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
+ 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
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size2);
+ ASSERT_EQ(size, size2);
+}
+
+TEST_F(DBTest, ApproximateSizesFilesWithErrorMargin) {
+ Options options = CurrentOptions();
+ options.write_buffer_size = 1024 * 1024;
+ options.compression = kNoCompression;
+ options.create_if_missing = true;
+ options.target_file_size_base = 1024 * 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), RandomString(&rnd, 1024)));
+ }
+ // Flush everything to files
+ Flush();
+ // Compact the entire key space into the next level
+ db_->CompactRange(CompactRangeOptions(), default_cf, nullptr, nullptr);
+
+ // Write more keys
+ for (int i = N; i < (N + N / 4); i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
+ }
+ // Flush everything to files again
+ 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_memtabtles = 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;
+ 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;
+ db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size2);
+ ASSERT_LT(size2, size * (1 + error_margin));
+ ASSERT_GT(size2, size * (1 - error_margin));
+}
+
+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), RandomString(&rnd, 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);
+
+ 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), RandomString(&rnd, 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);
+
+ ASSERT_TRUE(Between(Size("", "xyz", 1), 0, 0));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ ASSERT_TRUE(Between(Size("", "xyz", 1), 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), RandomString(&rnd, S1)));
+ }
+
+ // 0 because GetApproximateSizes() does not account for memtable space
+ ASSERT_TRUE(Between(Size("", Key(50), 1), 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_TRUE(Between(Size("", Key(i), 1), S1 * i, S2 * i));
+ ASSERT_TRUE(Between(Size("", Key(i) + ".suffix", 1), S1 * (i + 1),
+ S2 * (i + 1)));
+ ASSERT_TRUE(Between(Size(Key(i), Key(i + 10), 1), S1 * 10, S2 * 10));
+ }
+ ASSERT_TRUE(Between(Size("", Key(50), 1), S1 * 50, S2 * 50));
+ ASSERT_TRUE(
+ Between(Size("", Key(50) + ".suffix", 1), 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;
+ 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 = RandomString(&rnd, 100000);
+ ASSERT_OK(Put(1, Key(0), RandomString(&rnd, 10000)));
+ ASSERT_OK(Put(1, Key(1), RandomString(&rnd, 10000)));
+ ASSERT_OK(Put(1, Key(2), big1));
+ ASSERT_OK(Put(1, Key(3), RandomString(&rnd, 10000)));
+ ASSERT_OK(Put(1, Key(4), big1));
+ ASSERT_OK(Put(1, Key(5), RandomString(&rnd, 10000)));
+ ASSERT_OK(Put(1, Key(6), RandomString(&rnd, 300000)));
+ ASSERT_OK(Put(1, Key(7), RandomString(&rnd, 10000)));
+
+ // Check sizes across recovery by reopening a few times
+ for (int run = 0; run < 3; run++) {
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ ASSERT_TRUE(Between(Size("", Key(0), 1), 0, 0));
+ ASSERT_TRUE(Between(Size("", Key(1), 1), 10000, 11000));
+ ASSERT_TRUE(Between(Size("", Key(2), 1), 20000, 21000));
+ ASSERT_TRUE(Between(Size("", Key(3), 1), 120000, 121000));
+ ASSERT_TRUE(Between(Size("", Key(4), 1), 130000, 131000));
+ ASSERT_TRUE(Between(Size("", Key(5), 1), 230000, 231000));
+ ASSERT_TRUE(Between(Size("", Key(6), 1), 240000, 241000));
+ ASSERT_TRUE(Between(Size("", Key(7), 1), 540000, 541000));
+ ASSERT_TRUE(Between(Size("", Key(8), 1), 550000, 560000));
+
+ ASSERT_TRUE(Between(Size(Key(3), Key(5), 1), 110000, 111000));
+
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+ }
+ // ApproximateOffsetOf() is not yet implemented in plain table format.
+ } while (ChangeOptions(kSkipPlainTable));
+}
+#endif // ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest, Snapshot) {
+ anon::OptionsOverride options_override;
+ options_override.skip_policy = kSkipNoSnapshot;
+ do {
+ CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override));
+ Put(0, "foo", "0v1");
+ 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());
+ Put(0, "foo", "0v2");
+ Put(1, "foo", "1v2");
+
+ env_->addon_time_.fetch_add(1);
+
+ const Snapshot* s2 = db_->GetSnapshot();
+ ASSERT_EQ(2U, GetNumSnapshots());
+ ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
+ ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
+ Put(0, "foo", "0v3");
+ Put(1, "foo", "1v3");
+
+ {
+ ManagedSnapshot s3(db_);
+ ASSERT_EQ(3U, GetNumSnapshots());
+ ASSERT_EQ(time_snap1, GetTimeOldestSnapshots());
+ ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber());
+
+ Put(0, "foo", "0v4");
+ 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());
+}
+
+TEST_F(DBTest, HiddenValuesAreRemoved) {
+ anon::OptionsOverride options_override;
+ options_override.skip_policy = kSkipNoSnapshot;
+ do {
+ Options options = CurrentOptions(options_override);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+ FillLevels("a", "z", 1);
+
+ std::string big = RandomString(&rnd, 50000);
+ Put(1, "foo", big);
+ Put(1, "pastfoo", "v");
+ const Snapshot* snapshot = db_->GetSnapshot();
+ Put(1, "foo", "tiny");
+ 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_TRUE(Between(Size("", "pastfoo", 1), 50000, 60000));
+ db_->ReleaseSnapshot(snapshot);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny, " + big + " ]");
+ Slice x("x");
+ 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);
+ dbfull()->TEST_CompactRange(1, nullptr, &x, handles_[1]);
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny ]");
+
+ ASSERT_TRUE(Between(Size("", "pastfoo", 1), 0, 1000));
+ // ApproximateOffsetOf() is not yet implemented in plain table format,
+ // which is used by Size().
+ } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction |
+ kSkipPlainTable));
+}
+#endif // ROCKSDB_LITE
+
+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);
+
+ Put(1, "foo", "first");
+ const Snapshot* snapshot = db_->GetSnapshot();
+ SingleDelete(1, "foo");
+ Put(1, "foo", "second");
+ ASSERT_OK(Flush(1));
+
+ ASSERT_EQ("first", Get(1, "foo", snapshot));
+ ASSERT_EQ("second", Get(1, "foo"));
+
+ dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
+ nullptr);
+ ASSERT_EQ("[ second, SDEL, first ]", AllEntriesFor("foo", 1));
+
+ SingleDelete(1, "foo");
+
+ ASSERT_EQ("first", Get(1, "foo", snapshot));
+ ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
+
+ 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 beccause 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));
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest, DeletionMarkers1) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"pikachu"}, options);
+ 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
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ Flush(1);
+ MoveFilesToLevel(last - 1, 1);
+ ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
+ ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1);
+
+ Delete(1, "foo");
+ 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");
+ 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 ]");
+ 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);
+ 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
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ Flush(1);
+ MoveFilesToLevel(last - 1, 1);
+ ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1);
+ ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1);
+
+ 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 ]");
+ dbfull()->TEST_CompactRange(last - 2, nullptr, nullptr, handles_[1]);
+ // DEL kept: "last" file overlaps
+ ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]");
+ 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"));
+ Flush(1);
+ MoveFilesToLevel(2, 1);
+ ASSERT_OK(Delete(1, "100"));
+ ASSERT_OK(Delete(1, "999"));
+ 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"));
+ Flush(1);
+ ASSERT_OK(Put(1, "200", "v200"));
+ ASSERT_OK(Put(1, "600", "v600"));
+ ASSERT_OK(Put(1, "900", "v900"));
+ Flush(1);
+ ASSERT_EQ("2,1,1", FilesPerLevel(1));
+
+ // Compact away the placeholder files we created initially
+ dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
+ 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"));
+ Flush(1);
+ ASSERT_EQ("3", FilesPerLevel(1));
+ ASSERT_EQ("NOT_FOUND", Get(1, "600"));
+ } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction));
+}
+#endif // ROCKSDB_LITE
+
+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>({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"));
+ 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());
+}
+
+#ifndef ROCKSDB_LITE
+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<std::string> values;
+ for (int i = 0; i < 80; i++) {
+ values.push_back(RandomString(&rnd, 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<std::string> files;
+ dbfull()->DisableFileDeletions();
+ 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/";
+ ASSERT_OK(env_->CreateDirIfMissing(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) {
+ if (number > manifest_number) {
+ manifest_number = number;
+ ASSERT_GE(size, manifest_size);
+ size = manifest_size; // copy only valid MANIFEST data
+ }
+ }
+ }
+ CopyFile(src, dest, size);
+ }
+
+ // release file snapshot
+ dbfull()->DisableFileDeletions();
+ // overwrite one key, this key should not appear in the snapshot
+ std::vector<std::string> extras;
+ for (unsigned int i = 0; i < 1; i++) {
+ extras.push_back(RandomString(&rnd, 100000));
+ ASSERT_OK(Put(0, Key(i), extras[i]));
+ }
+
+ // verify that data in the snapshot are correct
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.emplace_back("default", ColumnFamilyOptions());
+ column_families.emplace_back("pikachu", ColumnFamilyOptions());
+ std::vector<ColumnFamilyHandle*> 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++) {
+ stat = 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<std::string> newfiles;
+ dbfull()->DisableFileDeletions();
+ 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) {
+ if (number > new_manifest_number) {
+ 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);
+
+ // release file snapshot
+ dbfull()->DisableFileDeletions();
+ } 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<std::string> files;
+ 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;
+ env_->GetFileSize(dbname_ + "/" + f, &size_on_disk);
+ ASSERT_EQ(manifest_size, size_on_disk);
+ break;
+ }
+ }
+ }
+ Close();
+ } while (ChangeCompactOptions());
+}
+#endif
+
+TEST_F(DBTest, PurgeInfoLogs) {
+ Options options = CurrentOptions();
+ options.keep_log_file_num = 5;
+ options.create_if_missing = true;
+ for (int mode = 0; mode <= 1; mode++) {
+ if (mode == 1) {
+ options.db_log_dir = dbname_ + "_logs";
+ env_->CreateDirIfMissing(options.db_log_dir);
+ } else {
+ options.db_log_dir = "";
+ }
+ for (int i = 0; i < 8; i++) {
+ Reopen(options);
+ }
+
+ std::vector<std::string> files;
+ 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.
+ std::vector<std::string> db_files;
+ env_->GetChildren(dbname_, &db_files);
+ for (std::string file : db_files) {
+ ASSERT_TRUE(file.find("LOG") == std::string::npos);
+ }
+
+ if (mode == 1) {
+ // Cleaning up
+ env_->GetChildren(options.db_log_dir, &files);
+ for (std::string file : files) {
+ env_->DeleteFile(options.db_log_dir + "/" + file);
+ }
+ env_->DeleteDir(options.db_log_dir);
+ }
+ }
+}
+
+#ifndef ROCKSDB_LITE
+// 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<bool> stop;
+ std::atomic<int> counter[kNumThreads];
+ std::atomic<bool> thread_done[kNumThreads];
+};
+
+struct MTThread {
+ MTState* state;
+ int id;
+ bool multiget_batched;
+};
+
+static void MTThreadBody(void* arg) {
+ MTThread* t = reinterpret_cast<MTThread*>(arg);
+ int id = t->id;
+ DB* db = t->state->test->db_;
+ int counter = 0;
+ fprintf(stderr, "... starting thread %d\n", id);
+ Random rnd(1000 + id);
+ char valbuf[1500];
+ while (t->state->stop.load(std::memory_order_acquire) == false) {
+ 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 <key, my id, counter, cf, unique_id>.
+ // 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<int>(counter), cf, unique_id);
+ 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<int>(counter), cf, unique_id);
+ 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<Slice> keys(kColumnFamilies, Slice(keybuf));
+ std::vector<std::string> values;
+ std::vector<Status> statuses;
+ if (!t->multiget_batched) {
+ statuses = db->MultiGet(ReadOptions(), t->state->test->handles_, keys,
+ &values);
+ } else {
+ std::vector<PinnableSlice> 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++;
+ }
+ t->state->thread_done[id].store(true, std::memory_order_release);
+ fprintf(stderr, "... stopping thread %d after %d ops\n", id, int(counter));
+}
+
+} // namespace
+
+class MultiThreadedDBTest
+ : public DBTest,
+ public ::testing::WithParamInterface<std::tuple<int, bool>> {
+ public:
+ void SetUp() override {
+ std::tie(option_config_, multiget_batched_) = GetParam();
+ }
+
+ static std::vector<int> GenerateOptionConfigs() {
+ std::vector<int> 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<std::string> cfs;
+ for (int i = 1; i < kColumnFamilies; ++i) {
+ cfs.push_back(ToString(i));
+ }
+ Reopen(options);
+ CreateAndReopenWithCF(cfs, options);
+ // Initialize state
+ MTState mt;
+ mt.test = this;
+ mt.stop.store(false, std::memory_order_release);
+ for (int id = 0; id < kNumThreads; id++) {
+ mt.counter[id].store(0, std::memory_order_release);
+ mt.thread_done[id].store(false, 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]);
+ }
+
+ // Let them run for a while
+ env_->SleepForMicroseconds(kTestSeconds * 1000000);
+
+ // Stop the threads and wait for them to finish
+ mt.stop.store(true, std::memory_order_release);
+ for (int id = 0; id < kNumThreads; id++) {
+ while (mt.thread_done[id].load(std::memory_order_acquire) == false) {
+ env_->SleepForMicroseconds(100000);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ MultiThreaded, MultiThreadedDBTest,
+ ::testing::Combine(
+ ::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()),
+ ::testing::Bool()));
+#endif // ROCKSDB_LITE
+
+// Group commit test:
+#if !defined(TRAVIS) && !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<bool> done;
+};
+
+static void GCThreadBody(void* arg) {
+ GCThread* t = reinterpret_cast<GCThread*>(arg);
+ int id = t->id;
+ DB* db = t->db;
+ WriteOptions wo;
+
+ for (int i = 0; i < kGCNumKeys; ++i) {
+ std::string kv(ToString(i + id * kGCNumKeys));
+ ASSERT_OK(db->Put(wo, kv, kv));
+ }
+ t->done = true;
+}
+
+} // 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<std::string> expected_db;
+ for (int i = 0; i < kGCNumThreads * kGCNumKeys; ++i) {
+ expected_db.push_back(ToString(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 // TRAVIS
+
+namespace {
+typedef std::map<std::string, std::string> KVMap;
+}
+
+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;
+ }
+ };
+
+ 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;
+ batch.Put(cf, k, v);
+ return Write(o, &batch);
+ }
+ 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;
+ batch.Delete(cf, key);
+ return Write(o, &batch);
+ }
+ using DB::SingleDelete;
+ Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf,
+ const Slice& key) override {
+ WriteBatch batch;
+ batch.SingleDelete(cf, key);
+ return Write(o, &batch);
+ }
+ using DB::Merge;
+ Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k,
+ const Slice& v) override {
+ WriteBatch batch;
+ batch.Merge(cf, k, v);
+ return Write(o, &batch);
+ }
+ 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<Status> MultiGet(
+ const ReadOptions& /*options*/,
+ const std::vector<ColumnFamilyHandle*>& /*column_family*/,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* /*values*/) override {
+ std::vector<Status> s(keys.size(),
+ Status::NotSupported("Not implemented."));
+ return s;
+ }
+
+#ifndef ROCKSDB_LITE
+ using DB::IngestExternalFile;
+ Status IngestExternalFile(
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*external_files*/,
+ const IngestExternalFileOptions& /*options*/) override {
+ return Status::NotSupported("Not implemented.");
+ }
+
+ using DB::IngestExternalFiles;
+ Status IngestExternalFiles(
+ const std::vector<IngestExternalFileArg>& /*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();
+ }
+#endif // ROCKSDB_LITE
+
+ 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<const ModelSnapshot*>(options.snapshot)->map_);
+ return new ModelIter(snapshot_state, false);
+ }
+ }
+ Status NewIterators(const ReadOptions& /*options*/,
+ const std::vector<ColumnFamilyHandle*>& /*column_family*/,
+ std::vector<Iterator*>* /*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<const ModelSnapshot*>(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<std::string, std::string>* /*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<std::string, std::string>& /*new_options*/)
+ override {
+ return Status::NotSupported("Not supported operation.");
+ }
+
+ using DB::CompactFiles;
+ Status CompactFiles(
+ const CompactionOptions& /*compact_options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const std::vector<std::string>& /*input_file_names*/,
+ const int /*output_level*/, const int /*output_path_id*/ = -1,
+ std::vector<std::string>* 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<ColumnFamilyHandle*>& /*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<ColumnFamilyHandle*>& /*column_families*/) override {
+ return Status::OK();
+ }
+
+ Status SyncWAL() override { return Status::OK(); }
+
+#ifndef ROCKSDB_LITE
+ Status DisableFileDeletions() override { return Status::OK(); }
+
+ Status EnableFileDeletions(bool /*force*/) override { return Status::OK(); }
+ Status GetLiveFiles(std::vector<std::string>&, uint64_t* /*size*/,
+ bool /*flush_memtable*/ = true) override {
+ return Status::OK();
+ }
+
+ Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
+ return Status::OK();
+ }
+
+ Status GetCurrentWalFile(
+ std::unique_ptr<LogFile>* /*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<ROCKSDB_NAMESPACE::TransactionLogIterator>*,
+ const TransactionLogIterator::ReadOptions& /*read_options*/ =
+ TransactionLogIterator::ReadOptions()) override {
+ return Status::NotSupported("Not supported in Model DB");
+ }
+
+ void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
+ ColumnFamilyMetaData* /*metadata*/) override {}
+#endif // ROCKSDB_LITE
+
+ Status GetDbIdentity(std::string& /*identity*/) const override {
+ return Status::OK();
+ }
+
+ SequenceNumber GetLatestSequenceNumber() const override { return 0; }
+
+ bool SetPreserveDeletesSequenceNumber(SequenceNumber /*seqnum*/) override {
+ return true;
+ }
+
+ 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_ = "";
+};
+
+#ifndef ROCKSDB_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(miter->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<int> {
+ public:
+ void SetUp() override { option_config_ = GetParam(); }
+
+ static std::vector<int> GenerateOptionConfigs() {
+ std::vector<int> 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 = RandomString(&rnd,
+ 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 = RandomString(&rnd, rnd.Uniform(10));
+ b.Put(k, v);
+ } else {
+ 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 // ROCKSDB_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"));
+ Flush();
+ ASSERT_OK(Put("k2", "v2"));
+
+ // Reopen it without 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, 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<int*>(arg);
+ *max_open_files = 11;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Reopen(options);
+ ASSERT_OK(Put("k1", "v1"));
+ 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<Iterator> 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<Iterator> 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"));
+}
+
+#ifndef ROCKSDB_LITE
+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(ToString(i * 100 + j), RandomString(&rnd, 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(ToString(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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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(ToString(i * 20 + j + 2000), RandomString(&rnd, 980)));
+ }
+ 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" + ToString(i), RandomString(&rnd, 500)));
+ ASSERT_OK(Put("key" + ToString(i), ""));
+ ASSERT_OK(Put("z" + ToString(i), RandomString(&rnd, 500)));
+ Flush();
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 1);
+ for (int i = 0; i < 3; i++) {
+ ASSERT_EQ("", Get("key" + ToString(i)));
+ }
+ for (int i = 0; i < 3; i++) {
+ // Each file contains a different key which will be dropped later.
+ ASSERT_OK(Put("a" + ToString(i), RandomString(&rnd, 500)));
+ ASSERT_OK(Delete("key" + ToString(i)));
+ ASSERT_OK(Put("z" + ToString(i), RandomString(&rnd, 500)));
+ Flush();
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 2);
+ for (int i = 0; i < 3; i++) {
+ ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
+ }
+}
+
+// Check that FIFO-with-TTL is not supported with max_open_files != -1.
+TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) {
+ Options options;
+ options.compaction_style = kCompactionStyleFIFO;
+ options.create_if_missing = true;
+ options.ttl = 600; // seconds
+
+ // TTL is now supported with max_open_files != -1.
+ options.max_open_files = 100;
+ options = CurrentOptions(options);
+ ASSERT_OK(TryReopen(options));
+
+ 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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ // Test to make sure that all files with expired ttl are deleted on next
+ // manual compaction.
+ {
+ env_->addon_time_.store(0);
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ Flush();
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 10);
+
+ // Sleep for 2 hours -- which is much greater than TTL.
+ // Note: Couldn't use SleepForMicroseconds because it takes an int instead
+ // of uint64_t. Hence used addon_time_ directly.
+ // env_->SleepForMicroseconds(2 * 60 * 60 * 1000 * 1000);
+ env_->addon_time_.fetch_add(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);
+
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ Flush();
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 10);
+
+ // Sleep for 2 hours -- which is much greater than TTL.
+ env_->addon_time_.fetch_add(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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ Flush();
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ }
+ ASSERT_EQ(NumTableFilesAtLevel(0), 3);
+
+ // Sleep for 2 hours -- which is much greater than TTL.
+ env_->addon_time_.fetch_add(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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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_->addon_time_.fetch_add(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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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(ToString(i * 20 + j), RandomString(&rnd, 980)));
+ }
+ 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(ToString(i * 20 + j + 2000), RandomString(&rnd, 980)));
+ }
+ 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);
+ }
+}
+#endif // ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE
+/*
+ * 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(RandomString(&rnd, 32), RandomString(&rnd, (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<int64_t>(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(RandomString(&rnd, 32), RandomString(&rnd, (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<int64_t>(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(RandomString(&rnd, 32), RandomString(&rnd, (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);
+}
+
+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(new PlainTableFactory());
+ 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));
+}
+
+#endif // ROCKSDB_LITE
+
+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"));
+ Flush();
+ ASSERT_EQ("def", Get("abc"));
+ }
+}
+
+TEST_F(DBTest, WriteSingleThreadEntry) {
+ std::vector<port::Thread> threads;
+ dbfull()->TEST_LockMutex();
+ auto w = dbfull()->TEST_BeginWrite();
+ threads.emplace_back([&] { Put("a", "b"); });
+ env_->SleepForMicroseconds(10000);
+ threads.emplace_back([&] { 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;
+ 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<port::Thread> threads;
+ threads.emplace_back([&] {
+ for (size_t i = 0; i < cnt; i++) {
+ auto istr = ToString(i);
+ 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 = ToString(i);
+ WriteBatch batch;
+ batch.Put("a" + istr, "b" + istr);
+ 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
+ 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 = ToString(i);
+ ASSERT_OK(
+ db_->Get(ropt, db_->DefaultColumnFamily(), "a" + istr, &pval));
+ ASSERT_TRUE(pval == ("b" + istr));
+ }
+ }
+ }
+}
+
+#ifndef ROCKSDB_LITE
+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), RandomString(&rnd, 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) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ }
+ 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
+ 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), RandomString(&rnd, 1024), WriteOptions()));
+ count++;
+ }
+ ASSERT_GT(static_cast<double>(count), 128 * 0.8);
+ ASSERT_LT(static_cast<double>(count), 128 * 1.2);
+
+ sleeping_task_low.WaitUntilDone();
+
+ // Increase
+ ASSERT_OK(dbfull()->SetOptions({
+ {"max_write_buffer_number", "8"},
+ }));
+ // Clean up memtable and L0
+ 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), RandomString(&rnd, 1024), WriteOptions()));
+ count++;
+ }
+// Windows fails this test. Will tune in the future and figure out
+// approp number
+#ifndef OS_WIN
+ ASSERT_GT(static_cast<double>(count), 512 * 0.8);
+ ASSERT_LT(static_cast<double>(count), 512 * 1.2);
+#endif
+ sleeping_task_low.WaitUntilDone();
+
+ // Decrease
+ ASSERT_OK(dbfull()->SetOptions({
+ {"max_write_buffer_number", "4"},
+ }));
+ // Clean up memtable and L0
+ 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), RandomString(&rnd, 1024), WriteOptions()));
+ count++;
+ }
+// Windows fails this test. Will tune in the future and figure out
+// approp number
+#ifndef OS_WIN
+ ASSERT_GT(static_cast<double>(count), 256 * 0.8);
+ ASSERT_LT(static_cast<double>(count), 266 * 1.2);
+#endif
+ sleeping_task_low.WaitUntilDone();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+#endif // ROCKSDB_LITE
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+namespace {
+void VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
+ int expected_count) {
+ int op_count = 0;
+ std::vector<ThreadStatus> 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);
+}
+} // namespace
+
+TEST_F(DBTest, GetThreadStatus) {
+ Options options;
+ options.env = env_;
+ options.enable_thread_tracking = true;
+ TryReopen(options);
+
+ std::vector<ThreadStatus> 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);
+ }
+ }
+ 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;
+ db_->GetIntProperty(DB::Properties::kNumRunningFlushes, &num_running_flushes);
+ ASSERT_EQ(num_running_flushes, 0);
+
+ Put(1, "k1", std::string(100000, 'x')); // Fill memtable
+ 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);
+ 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(ToString(key + file * kEntriesPerBuffer),
+ RandomString(&rnd, kTestValueSize)));
+ }
+ 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;
+ 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);
+ }
+ 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, "p1", "p9");
+ 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_);
+ db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
+ 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<ThreadStatus> 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(ToString(key++), RandomString(&rnd, kTestValueSize)));
+ }
+
+ Status s = 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");
+ 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;
+ }
+ Status s = 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<ThreadStatus> 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(ToString(key++), RandomString(&rnd, kTestValueSize)));
+ }
+
+ Status s = 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");
+ 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;
+ }
+ Status s = 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
+
+#ifndef ROCKSDB_LITE
+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;
+ }
+ std::random_shuffle(std::begin(keys), std::end(keys));
+
+ Random rnd(301);
+ Options options;
+ 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)));
+ }
+ Flush();
+ 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)));
+ }
+ Flush();
+ 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<Iterator> 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;
+ }
+ std::random_shuffle(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.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<mock::MockTableFactory> 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<int> num_zlib(0);
+ std::atomic<int> num_lz4(0);
+ std::atomic<int> num_no(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
+ Compaction* compaction = reinterpret_cast<Compaction*>(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<CompressionType*>(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 = RandomString(&rnd, 200);
+ ASSERT_OK(Put(Key(keys[i]), value));
+ if (i % 25 == 24) {
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ }
+ }
+
+ Flush();
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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<Compaction*>(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<CompressionType*>(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 = RandomString(&rnd, 200);
+ ASSERT_OK(Put(Key(keys[i]), value));
+ if (i % 100 == 99) {
+ Flush();
+ 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), RandomString(&rnd, 1024)));
+ }
+ 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);
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ("0,1", FilesPerLevel());
+ std::vector<LiveFileMetaData> 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", ToString(k32KB)}}));
+
+ gen_l0_kb(0, 64, 1);
+ ASSERT_EQ("1,1", FilesPerLevel());
+ gen_l0_kb(0, 64, 1);
+ 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", ToString(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);
+ }
+ 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", ToString(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);
+ }
+ 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
+ dbfull()->TEST_FlushMemTable(true, true);
+ 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), RandomString(&rnd, 1024), wo));
+ 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"}}));
+ dbfull()->TEST_FlushMemTable(true);
+ 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), RandomString(&rnd, 1024), wo));
+ 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"}}));
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+
+ for (int i = 0; i < 4; ++i) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
+ // Wait for compaction so that put won't stop
+ dbfull()->TEST_FlushMemTable(true);
+ }
+ 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"}}));
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ(NumTableFilesAtLevel(0), 0);
+
+ for (int i = 0; i < 4; ++i) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
+ // Wait for compaction so that put won't stop
+ dbfull()->TEST_FlushMemTable(true);
+ }
+ 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;
+ 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;
+ 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);
+}
+#endif // ROCKSDB_LITE
+
+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);
+
+ const int kCDTKeysPerBuffer = 4;
+ const int kTestSize = kCDTKeysPerBuffer * 4096;
+ const int kTotalIteration = 100;
+ // the second half of the test involves in random failure
+ // of file creation.
+ const int kRandomFailureTest = kTotalIteration / 2;
+ std::vector<std::string> 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 = RandomString(&rnd, 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.
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ // 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]);
+ }
+}
+
+#ifndef ROCKSDB_LITE
+
+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), RandomString(&rnd, 8)));
+ for (int i = 0; i < 10; ++i) {
+ ASSERT_OK(Put(Key(key1), RandomString(&rnd, 8)));
+ }
+ ASSERT_OK(Put(Key(key2), RandomString(&rnd, 8)));
+ std::unique_ptr<Iterator> 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
+ 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
+ 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);
+}
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTest, L0L1L2AndUpHitCounter) {
+ Options options = CurrentOptions();
+ options.write_buffer_size = 32 * 1024;
+ options.target_file_size_base = 32 * 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 = 64 * 1024;
+ options.max_write_buffer_number = 2;
+ options.max_background_compactions = 8;
+ options.max_background_flushes = 8;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ CreateAndReopenWithCF({"mypikachu"}, options);
+
+ int numkeys = 20000;
+ for (int i = 0; i < numkeys; i++) {
+ ASSERT_OK(Put(1, Key(i), "val"));
+ }
+ 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));
+
+ ASSERT_OK(Flush(1));
+ dbfull()->TEST_WaitForCompact();
+
+ for (int i = 0; i < numkeys; i++) {
+ ASSERT_EQ(Get(1, Key(i)), "val");
+ }
+
+ ASSERT_GT(TestGetTickerCount(options, GET_HIT_L0), 100);
+ ASSERT_GT(TestGetTickerCount(options, GET_HIT_L1), 100);
+ ASSERT_GT(TestGetTickerCount(options, GET_HIT_L2_AND_UP), 100);
+
+ ASSERT_EQ(numkeys, 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), RandomString(&rnd, 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<std::string> filenames;
+ env_->GetChildren(dbname_, &filenames);
+ // Delete archival files.
+ for (size_t i = 0; i < filenames.size(); ++i) {
+ env_->DeleteFile(dbname_ + "/" + filenames[i]);
+ }
+ 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_->addon_time_.fetch_add(1000);
+ 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);
+ this->env_->addon_time_.store(0);
+ this->env_->time_elapse_only_sleep_ = true;
+ this->env_->no_slowdown_ = true;
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.merge_operator.reset(new DelayedMergeOperator(this));
+ DestroyAndReopen(options);
+
+ ASSERT_EQ(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 0);
+ 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;
+ db_->Get(opt, "foo", &result);
+
+ ASSERT_EQ(1000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
+
+ ReadOptions read_options;
+ std::unique_ptr<Iterator> 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(2000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
+#endif // ROCKSDB_USING_THREAD_STATUS
+ this->env_->time_elapse_only_sleep_ = false;
+}
+
+#ifndef ROCKSDB_LITE
+TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
+ SetPerfLevel(kEnableTime);
+ Options options = CurrentOptions();
+ options.compaction_filter_factory = std::make_shared<KeepFilterFactory>();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.merge_operator.reset(new DelayedMergeOperator(this));
+ options.compaction_style = kCompactionStyleUniversal;
+ options.max_subcompactions = max_subcompactions_;
+ DestroyAndReopen(options);
+
+ for (int i = 0; i < 1000; i++) {
+ ASSERT_OK(db_->Merge(WriteOptions(), "foo", "TEST"));
+ ASSERT_OK(Flush());
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_NE(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 0);
+}
+
+TEST_P(DBTestWithParam, FilterCompactionTimeTest) {
+ Options options = CurrentOptions();
+ options.compaction_filter_factory =
+ std::make_shared<DelayFilterFactory>(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_;
+ DestroyAndReopen(options);
+
+ // put some data
+ for (int table = 0; table < 4; ++table) {
+ for (int i = 0; i < 10 + table; ++i) {
+ Put(ToString(table * 100 + i), "val");
+ }
+ 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_NE(TestGetTickerCount(options, FILTER_OPERATION_TOTAL_TIME), 0);
+ delete itr;
+}
+#endif // ROCKSDB_LITE
+
+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) {
+ 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<size_t>(3));
+ }
+}
+
+#ifndef ROCKSDB_LITE
+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();
+}
+#endif // ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest, SuggestCompactRangeTest) {
+ class CompactionFilterFactoryGetContext : public CompactionFilterFactory {
+ public:
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ saved_context = context;
+ std::unique_ptr<CompactionFilter> empty_filter;
+ return empty_filter;
+ }
+ const char* Name() const override {
+ return "CompactionFilterFactoryGetContext";
+ }
+ static bool IsManual(CompactionFilterFactory* compaction_filter_factory) {
+ return reinterpret_cast<CompactionFilterFactoryGetContext*>(
+ compaction_filter_factory)
+ ->saved_context.is_manual_compaction;
+ }
+ CompactionFilter::Context saved_context;
+ };
+
+ Options options = CurrentOptions();
+ options.memtable_factory.reset(
+ new SpecialSkipListFactory(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<uint64_t>(1) << 60; // inf
+
+ Reopen(options);
+
+ Random rnd(301);
+
+ for (int num = 0; num < 3; num++) {
+ GenerateNewRandomFile(&rnd);
+ }
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("0,4", FilesPerLevel(0));
+ ASSERT_TRUE(!CompactionFilterFactoryGetContext::IsManual(
+ options.compaction_filter_factory.get()));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("1,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("2,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("3,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("0,4,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("1,4,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("2,4,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("3,4,4", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("0,4,8", FilesPerLevel(0));
+
+ GenerateNewRandomFile(&rnd);
+ ASSERT_EQ("1,4,8", FilesPerLevel(0));
+
+ // compact it three times
+ for (int i = 0; i < 3; ++i) {
+ ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr));
+ 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));
+ 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));
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_TRUE(CompactionFilterFactoryGetContext::IsManual(
+ options.compaction_filter_factory.get()));
+
+ // now it should compact the level 0 file
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_EQ(1, 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<std::pair<int32_t, int32_t>> ranges = {
+ {81, 160}, {0, 80}, {161, 240}, {241, 320}};
+
+ int32_t value_size = 10 * 1024; // 10 KB
+
+ Random rnd(301);
+ std::map<int32_t, std::string> values;
+ for (const auto& range : ranges) {
+ for (int32_t j = range.first; j < range.second; j++) {
+ values[j] = RandomString(&rnd, 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), RandomString(&rnd, 1024)));
+ 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);
+}
+#endif // ROCKSDB_LITE
+
+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<int> 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), RandomString(&rnd, 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), RandomString(&rnd, 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();
+ dbfull()->TEST_WaitForCompact();
+}
+
+#ifndef ROCKSDB_LITE
+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), RandomString(&rnd, 1024)));
+ }
+ ASSERT_OK(Flush());
+ }
+
+ ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data;
+ db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
+
+ std::vector<std::string> 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);
+ });
+
+ 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), RandomString(&rnd, 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();
+ 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);
+}
+#endif // ROCKSDB_LITE
+
+// 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 += ToString(j);
+ 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<port::Thread> 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_;
+ env_->no_slowdown_ = true;
+ 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(
+ new SpecialSkipListFactory(kEntriesPerMemTable));
+
+ 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++) {
+ Put(Key(i), std::string(10000, 'x'));
+ Flush();
+ }
+
+ // These writes will be slowed down to 1KB/s
+ uint64_t estimated_sleep_time = 0;
+ Random rnd(301);
+ 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;
+ 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);
+ }
+ }
+ 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<uint64_t>(static_cast<double>(cur_rate) *
+ kIncSlowdownRatio * kIncSlowdownRatio);
+ }
+ // Estimate the total sleep time fall into the rough range.
+ ASSERT_GT(env_->addon_time_.load(),
+ static_cast<int64_t>(estimated_sleep_time / 2));
+ ASSERT_LT(env_->addon_time_.load(),
+ static_cast<int64_t>(estimated_sleep_time * 2));
+
+ env_->no_slowdown_ = false;
+ 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(
+ new SpecialSkipListFactory(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<int> 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);
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+
+ ASSERT_EQ(0, callback_count.load());
+
+ for (int num = 0; num < 5; num++) {
+ GenerateNewFile(&rnd, &key_idx, true);
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ ASSERT_GE(callback_count.load(), 1);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ sleeping_task_low.WaitUntilDone();
+}
+
+#if !defined(ROCKSDB_LITE) && !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++) {
+ Put(Key(i), std::string(5000, 'x'));
+ if (i % 10 == 0) {
+ dbfull()->TEST_FlushMemTable(true, true);
+ }
+ }
+ dbfull()->TEST_WaitForCompact();
+ MoveFilesToLevel(3);
+
+ // Generating 360KB in Level 2
+ for (int i = 0; i < 72; i++) {
+ Put(Key(i), std::string(5000, 'x'));
+ if (i % 10 == 0) {
+ dbfull()->TEST_FlushMemTable(true, true);
+ }
+ }
+ dbfull()->TEST_WaitForCompact();
+ MoveFilesToLevel(2);
+
+ 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++) {
+ Put(Key(i), std::string(5000, 'x'));
+ Put(Key(100 - i), std::string(5000, 'x'));
+ // Flush the file. File size is around 30KB.
+ InstallFlushCallback();
+ 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();
+ dbfull()->TEST_WaitForCompact();
+
+ // Now there is one L1 file but doesn't trigger soft_rate_limit
+ // 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++) {
+ Put(Key(10 + i), std::string(5000, 'x'));
+ Put(Key(90 - i), std::string(5000, 'x'));
+ // Flush the file. File size is around 30KB.
+ InstallFlushCallback();
+ 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++) {
+ Put(Key(20 + i), std::string(5000, 'x'));
+ Put(Key(80 - i), std::string(5000, 'x'));
+ // Flush the file. File size is around 30KB.
+ InstallFlushCallback();
+ 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"},
+ }));
+
+ Put("", "");
+ 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(
+ new SpecialSkipListFactory(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++) {
+ Put(Key(j), "");
+ }
+ ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
+ }
+ // Inserting a new entry would create a new mem table, triggering slow down.
+ Put(Key(0), "");
+ ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
+
+ sleeping_task.WakeUp();
+ sleeping_task.WaitUntilDone();
+}
+#endif // !defined(ROCKSDB_LITE) && !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;
+}
+
+#ifndef ROCKSDB_LITE
+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<LRUCache*>(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<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
+ 0);
+ }
+ // After PinnableSlice destruction element is added back in LRU
+ ASSERT_EQ(
+ reinterpret_cast<LRUCache*>(options.row_cache.get())->TEST_GetLRUSize(),
+ 1);
+}
+
+#endif // ROCKSDB_LITE
+
+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<port::Thread> threads;
+ std::atomic<bool> done(false);
+ db_->PauseBackgroundWork();
+ threads.emplace_back([&]() {
+ Random rnd(301);
+ for (int i = 0; i < 10000; ++i) {
+ Put(RandomString(&rnd, 10), RandomString(&rnd, 10));
+ }
+ done.store(true);
+ });
+ env_->SleepForMicroseconds(200000);
+ // make sure the thread is not done
+ ASSERT_FALSE(done.load());
+ 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<int> flushes_done{0};
+ std::atomic<int> 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<port::Thread> thread_spawning_threads(10);
+ for (auto& t: thread_spawning_threads) {
+ t = port::Thread([&] {
+ while (!done()) {
+ {
+ port::Thread tmp_thread([&] {
+ auto it = db_->NewIterator(ReadOptions());
+ 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));
+}
+
+#ifndef ROCKSDB_LITE
+
+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_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ 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<uint64_t>(time_1);
+
+ // Add 50 hours
+ env_->addon_time_.fetch_add(50 * 60 * 60);
+
+ int64_t time_2 = 0;
+ env_->GetCurrentTime(&time_2);
+ const uint64_t uint_time_2 = static_cast<uint64_t>(time_2);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) {
+ TableProperties* props = reinterpret_cast<TableProperties*>(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<FileMetaData*>(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), RandomString(&rnd, kValueSize)));
+ }
+ 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;
+ env_->time_elapse_only_sleep_ = false;
+ options.env = env_;
+
+ env_->addon_time_.store(0);
+ DestroyAndReopen(options);
+
+ for (int i = 0; i < kNumLevelFiles; ++i) {
+ for (int j = 0; j < kNumKeysPerFile; ++j) {
+ ASSERT_OK(
+ Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
+ }
+ 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();
+}
+
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_test2.cc b/storage/rocksdb/rocksdb/db/db_test2.cc
new file mode 100644
index 00000000..f4e8e960
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_test2.cc
@@ -0,0 +1,4695 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cstdlib>
+#include <functional>
+
+#include "db/db_test_util.h"
+#include "db/read_callback.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/persistent_cache.h"
+#include "rocksdb/wal_filter.h"
+#include "test_util/fault_injection_test_env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBTest2 : public DBTestBase {
+ public:
+ DBTest2() : DBTestBase("/db_test2") {}
+};
+
+class PrefixFullBloomWithReverseComparator
+ : public DBTestBase,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ PrefixFullBloomWithReverseComparator()
+ : DBTestBase("/prefix_bloom_reverse") {}
+ void SetUp() override { if_cache_filter_ = GetParam(); }
+ bool if_cache_filter_;
+};
+
+TEST_P(PrefixFullBloomWithReverseComparator,
+ PrefixFullBloomWithReverseComparator) {
+ Options options = last_options_;
+ options.comparator = ReverseBytewiseComparator();
+ options.prefix_extractor.reset(NewCappedPrefixTransform(3));
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions bbto;
+ if (if_cache_filter_) {
+ bbto.no_block_cache = false;
+ bbto.cache_index_and_filter_blocks = true;
+ bbto.block_cache = NewLRUCache(1);
+ }
+ bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ bbto.whole_key_filtering = false;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ DestroyAndReopen(options);
+
+ ASSERT_OK(dbfull()->Put(WriteOptions(), "bar123", "foo"));
+ ASSERT_OK(dbfull()->Put(WriteOptions(), "bar234", "foo2"));
+ ASSERT_OK(dbfull()->Put(WriteOptions(), "foo123", "foo3"));
+
+ dbfull()->Flush(FlushOptions());
+
+ if (bbto.block_cache) {
+ bbto.block_cache->EraseUnRefEntries();
+ }
+
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
+ iter->Seek("bar345");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("bar234", iter->key().ToString());
+ ASSERT_EQ("foo2", iter->value().ToString());
+ iter->Next();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("bar123", iter->key().ToString());
+ ASSERT_EQ("foo", iter->value().ToString());
+
+ iter->Seek("foo234");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("foo123", iter->key().ToString());
+ ASSERT_EQ("foo3", iter->value().ToString());
+
+ iter->Seek("bar");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+}
+
+INSTANTIATE_TEST_CASE_P(PrefixFullBloomWithReverseComparator,
+ PrefixFullBloomWithReverseComparator, testing::Bool());
+
+TEST_F(DBTest2, IteratorPropertyVersionNumber) {
+ Put("", "");
+ Iterator* iter1 = db_->NewIterator(ReadOptions());
+ std::string prop_value;
+ ASSERT_OK(
+ iter1->GetProperty("rocksdb.iterator.super-version-number", &prop_value));
+ uint64_t version_number1 =
+ static_cast<uint64_t>(std::atoi(prop_value.c_str()));
+
+ Put("", "");
+ Flush();
+
+ Iterator* iter2 = db_->NewIterator(ReadOptions());
+ ASSERT_OK(
+ iter2->GetProperty("rocksdb.iterator.super-version-number", &prop_value));
+ uint64_t version_number2 =
+ static_cast<uint64_t>(std::atoi(prop_value.c_str()));
+
+ ASSERT_GT(version_number2, version_number1);
+
+ Put("", "");
+
+ Iterator* iter3 = db_->NewIterator(ReadOptions());
+ ASSERT_OK(
+ iter3->GetProperty("rocksdb.iterator.super-version-number", &prop_value));
+ uint64_t version_number3 =
+ static_cast<uint64_t>(std::atoi(prop_value.c_str()));
+
+ ASSERT_EQ(version_number2, version_number3);
+
+ iter1->SeekToFirst();
+ ASSERT_OK(
+ iter1->GetProperty("rocksdb.iterator.super-version-number", &prop_value));
+ uint64_t version_number1_new =
+ static_cast<uint64_t>(std::atoi(prop_value.c_str()));
+ ASSERT_EQ(version_number1, version_number1_new);
+
+ delete iter1;
+ delete iter2;
+ delete iter3;
+}
+
+TEST_F(DBTest2, CacheIndexAndFilterWithDBRestart) {
+ 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(new BlockBasedTableFactory(table_options));
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ ASSERT_OK(Flush(1));
+ TryReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ std::string value;
+ value = Get(1, "a");
+}
+
+TEST_F(DBTest2, MaxSuccessiveMergesChangeWithDBRecovery) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.max_successive_merges = 3;
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ options.disable_auto_compactions = true;
+ DestroyAndReopen(options);
+ Put("poi", "Finch");
+ db_->Merge(WriteOptions(), "poi", "Reese");
+ db_->Merge(WriteOptions(), "poi", "Shaw");
+ db_->Merge(WriteOptions(), "poi", "Root");
+ options.max_successive_merges = 2;
+ Reopen(options);
+}
+
+#ifndef ROCKSDB_LITE
+class DBTestSharedWriteBufferAcrossCFs
+ : public DBTestBase,
+ public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ DBTestSharedWriteBufferAcrossCFs()
+ : DBTestBase("/db_test_shared_write_buffer") {}
+ void SetUp() override {
+ use_old_interface_ = std::get<0>(GetParam());
+ cost_cache_ = std::get<1>(GetParam());
+ }
+ bool use_old_interface_;
+ bool cost_cache_;
+};
+
+TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
+ Options options = CurrentOptions();
+ options.arena_block_size = 4096;
+
+ // Avoid undeterministic value by malloc_usable_size();
+ // Force arena block size to 1
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Arena::Arena:0", [&](void* arg) {
+ size_t* block_size = static_cast<size_t*>(arg);
+ *block_size = 1;
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Arena::AllocateNewBlock:0", [&](void* arg) {
+ std::pair<size_t*, size_t*>* pair =
+ static_cast<std::pair<size_t*, size_t*>*>(arg);
+ *std::get<0>(*pair) = *std::get<1>(*pair);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // The total soft write buffer size is about 105000
+ std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
+ ASSERT_LT(cache->GetUsage(), 256 * 1024);
+
+ if (use_old_interface_) {
+ options.db_write_buffer_size = 120000; // this is the real limit
+ } else if (!cost_cache_) {
+ options.write_buffer_manager.reset(new WriteBufferManager(114285));
+ } else {
+ options.write_buffer_manager.reset(new WriteBufferManager(114285, cache));
+ }
+ options.write_buffer_size = 500000; // this is never hit
+ CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
+
+ WriteOptions wo;
+ wo.disableWAL = true;
+
+ std::function<void()> wait_flush = [&]() {
+ dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
+ };
+
+ // Create some data and flush "default" and "nikitich" so that they
+ // are newer CFs created.
+ 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);
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(1));
+
+ ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
+ if (cost_cache_) {
+ ASSERT_GE(cache->GetUsage(), 256 * 1024);
+ ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
+ }
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(60000), wo));
+ if (cost_cache_) {
+ ASSERT_GE(cache->GetUsage(), 256 * 1024);
+ ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
+ }
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ // No flush should trigger
+ wait_flush();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(1));
+ }
+
+ // Trigger a flush. Flushing "nikitich".
+ ASSERT_OK(Put(3, Key(2), DummyString(30000), wo));
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
+ wait_flush();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(2));
+ }
+
+ // Without hitting the threshold, no flush should trigger.
+ ASSERT_OK(Put(2, Key(1), DummyString(30000), wo));
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ wait_flush();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(2));
+ }
+
+ // Hit the write buffer limit again. "default"
+ // will have been flushed.
+ ASSERT_OK(Put(2, Key(2), DummyString(10000), wo));
+ wait_flush();
+ ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
+ wait_flush();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(2));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(2));
+ }
+
+ // Trigger another flush. This time "dobrynia". "pikachu" should not
+ // be flushed, althrough it was never flushed.
+ ASSERT_OK(Put(1, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(80000), wo));
+ wait_flush();
+ ASSERT_OK(Put(1, Key(1), DummyString(1), wo));
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ wait_flush();
+
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(2));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(2));
+ }
+ if (cost_cache_) {
+ ASSERT_GE(cache->GetUsage(), 256 * 1024);
+ Close();
+ options.write_buffer_manager.reset();
+ last_options_.write_buffer_manager.reset();
+ ASSERT_LT(cache->GetUsage(), 256 * 1024);
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+INSTANTIATE_TEST_CASE_P(DBTestSharedWriteBufferAcrossCFs,
+ DBTestSharedWriteBufferAcrossCFs,
+ ::testing::Values(std::make_tuple(true, false),
+ std::make_tuple(false, false),
+ std::make_tuple(false, true)));
+
+TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
+ std::string dbname2 = test::PerThreadDBPath("db_shared_wb_db2");
+ Options options = CurrentOptions();
+ options.arena_block_size = 4096;
+ // Avoid undeterministic value by malloc_usable_size();
+ // Force arena block size to 1
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Arena::Arena:0", [&](void* arg) {
+ size_t* block_size = static_cast<size_t*>(arg);
+ *block_size = 1;
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Arena::AllocateNewBlock:0", [&](void* arg) {
+ std::pair<size_t*, size_t*>* pair =
+ static_cast<std::pair<size_t*, size_t*>*>(arg);
+ *std::get<0>(*pair) = *std::get<1>(*pair);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ options.write_buffer_size = 500000; // this is never hit
+ // Use a write buffer total size so that the soft limit is about
+ // 105000.
+ options.write_buffer_manager.reset(new WriteBufferManager(120000));
+ CreateAndReopenWithCF({"cf1", "cf2"}, options);
+
+ ASSERT_OK(DestroyDB(dbname2, options));
+ DB* db2 = nullptr;
+ ASSERT_OK(DB::Open(options, dbname2, &db2));
+
+ WriteOptions wo;
+ wo.disableWAL = true;
+
+ std::function<void()> wait_flush = [&]() {
+ dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
+ static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
+ };
+
+ // Trigger a flush on cf2
+ ASSERT_OK(Put(2, Key(1), DummyString(70000), wo));
+ wait_flush();
+ ASSERT_OK(Put(0, Key(1), DummyString(20000), wo));
+ wait_flush();
+
+ // Insert to DB2
+ ASSERT_OK(db2->Put(wo, Key(2), DummyString(20000)));
+ wait_flush();
+
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ wait_flush();
+ static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default") +
+ GetNumberOfSstFilesForColumnFamily(db_, "cf1") +
+ GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
+ static_cast<uint64_t>(0));
+ }
+
+ // Triggering to flush another CF in DB1
+ ASSERT_OK(db2->Put(wo, Key(2), DummyString(70000)));
+ wait_flush();
+ ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
+ wait_flush();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
+ static_cast<uint64_t>(0));
+ }
+
+ // Triggering flush in DB2.
+ ASSERT_OK(db2->Put(wo, Key(3), DummyString(40000)));
+ wait_flush();
+ ASSERT_OK(db2->Put(wo, Key(1), DummyString(1)));
+ wait_flush();
+ static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
+ {
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
+ static_cast<uint64_t>(0));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
+ static_cast<uint64_t>(1));
+ }
+
+ delete db2;
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, TestWriteBufferNoLimitWithCache) {
+ Options options = CurrentOptions();
+ options.arena_block_size = 4096;
+ std::shared_ptr<Cache> cache =
+ NewLRUCache(LRUCacheOptions(10000000, 1, false, 0.0));
+ options.write_buffer_size = 50000; // this is never hit
+ // Use a write buffer total size so that the soft limit is about
+ // 105000.
+ options.write_buffer_manager.reset(new WriteBufferManager(0, cache));
+ Reopen(options);
+
+ ASSERT_OK(Put("foo", "bar"));
+ // One dummy entry is 256KB.
+ ASSERT_GT(cache->GetUsage(), 128000);
+}
+
+namespace {
+ void ValidateKeyExistence(DB* db, const std::vector<Slice>& keys_must_exist,
+ const std::vector<Slice>& keys_must_not_exist) {
+ // Ensure that expected keys exist
+ std::vector<std::string> values;
+ if (keys_must_exist.size() > 0) {
+ std::vector<Status> status_list =
+ db->MultiGet(ReadOptions(), keys_must_exist, &values);
+ for (size_t i = 0; i < keys_must_exist.size(); i++) {
+ ASSERT_OK(status_list[i]);
+ }
+ }
+
+ // Ensure that given keys don't exist
+ if (keys_must_not_exist.size() > 0) {
+ std::vector<Status> status_list =
+ db->MultiGet(ReadOptions(), keys_must_not_exist, &values);
+ for (size_t i = 0; i < keys_must_not_exist.size(); i++) {
+ ASSERT_TRUE(status_list[i].IsNotFound());
+ }
+ }
+ }
+
+} // namespace
+
+TEST_F(DBTest2, WalFilterTest) {
+ class TestWalFilter : public WalFilter {
+ private:
+ // Processing option that is requested to be applied at the given index
+ WalFilter::WalProcessingOption wal_processing_option_;
+ // Index at which to apply wal_processing_option_
+ // At other indexes default wal_processing_option::kContinueProcessing is
+ // returned.
+ size_t apply_option_at_record_index_;
+ // Current record index, incremented with each record encountered.
+ size_t current_record_index_;
+
+ public:
+ TestWalFilter(WalFilter::WalProcessingOption wal_processing_option,
+ size_t apply_option_for_record_index)
+ : wal_processing_option_(wal_processing_option),
+ apply_option_at_record_index_(apply_option_for_record_index),
+ current_record_index_(0) {}
+
+ WalProcessingOption LogRecord(const WriteBatch& /*batch*/,
+ WriteBatch* /*new_batch*/,
+ bool* /*batch_changed*/) const override {
+ WalFilter::WalProcessingOption option_to_return;
+
+ if (current_record_index_ == apply_option_at_record_index_) {
+ option_to_return = wal_processing_option_;
+ }
+ else {
+ option_to_return = WalProcessingOption::kContinueProcessing;
+ }
+
+ // Filter is passed as a const object for RocksDB to not modify the
+ // object, however we modify it for our own purpose here and hence
+ // cast the constness away.
+ (const_cast<TestWalFilter*>(this)->current_record_index_)++;
+
+ return option_to_return;
+ }
+
+ const char* Name() const override { return "TestWalFilter"; }
+ };
+
+ // Create 3 batches with two keys each
+ std::vector<std::vector<std::string>> batch_keys(3);
+
+ batch_keys[0].push_back("key1");
+ batch_keys[0].push_back("key2");
+ batch_keys[1].push_back("key3");
+ batch_keys[1].push_back("key4");
+ batch_keys[2].push_back("key5");
+ batch_keys[2].push_back("key6");
+
+ // Test with all WAL processing options
+ for (int option = 0;
+ option < static_cast<int>(
+ WalFilter::WalProcessingOption::kWalProcessingOptionMax);
+ option++) {
+ Options options = OptionsForLogIterTest();
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({ "pikachu" }, options);
+
+ // Write given keys in given batches
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ batch.Put(handles_[0], batch_keys[i][j], DummyString(1024));
+ }
+ dbfull()->Write(WriteOptions(), &batch);
+ }
+
+ WalFilter::WalProcessingOption wal_processing_option =
+ static_cast<WalFilter::WalProcessingOption>(option);
+
+ // Create a test filter that would apply wal_processing_option at the first
+ // record
+ size_t apply_option_for_record_index = 1;
+ TestWalFilter test_wal_filter(wal_processing_option,
+ apply_option_for_record_index);
+
+ // Reopen database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ options.wal_filter = &test_wal_filter;
+ Status status =
+ TryReopenWithColumnFamilies({ "default", "pikachu" }, options);
+ if (wal_processing_option ==
+ WalFilter::WalProcessingOption::kCorruptedRecord) {
+ assert(!status.ok());
+ // In case of corruption we can turn off paranoid_checks to reopen
+ // databse
+ options.paranoid_checks = false;
+ ReopenWithColumnFamilies({ "default", "pikachu" }, options);
+ }
+ else {
+ assert(status.ok());
+ }
+
+ // Compute which keys we expect to be found
+ // and which we expect not to be found after recovery.
+ std::vector<Slice> keys_must_exist;
+ std::vector<Slice> keys_must_not_exist;
+ switch (wal_processing_option) {
+ case WalFilter::WalProcessingOption::kCorruptedRecord:
+ case WalFilter::WalProcessingOption::kContinueProcessing: {
+ fprintf(stderr, "Testing with complete WAL processing\n");
+ // we expect all records to be processed
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ keys_must_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ }
+ break;
+ }
+ case WalFilter::WalProcessingOption::kIgnoreCurrentRecord: {
+ fprintf(stderr,
+ "Testing with ignoring record %" ROCKSDB_PRIszt " only\n",
+ apply_option_for_record_index);
+ // We expect the record with apply_option_for_record_index to be not
+ // found.
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ if (i == apply_option_for_record_index) {
+ keys_must_not_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ else {
+ keys_must_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ }
+ }
+ break;
+ }
+ case WalFilter::WalProcessingOption::kStopReplay: {
+ fprintf(stderr,
+ "Testing with stopping replay from record %" ROCKSDB_PRIszt
+ "\n",
+ apply_option_for_record_index);
+ // We expect records beyond apply_option_for_record_index to be not
+ // found.
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ if (i >= apply_option_for_record_index) {
+ keys_must_not_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ else {
+ keys_must_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ }
+ }
+ break;
+ }
+ default:
+ assert(false); // unhandled case
+ }
+
+ bool checked_after_reopen = false;
+
+ while (true) {
+ // Ensure that expected keys exists
+ // and not expected keys don't exist after recovery
+ ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
+
+ if (checked_after_reopen) {
+ break;
+ }
+
+ // reopen database again to make sure previous log(s) are not used
+ //(even if they were skipped)
+ // reopn database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ ReopenWithColumnFamilies({ "default", "pikachu" }, options);
+
+ checked_after_reopen = true;
+ }
+ }
+}
+
+TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
+ class ChangeBatchHandler : public WriteBatch::Handler {
+ private:
+ // Batch to insert keys in
+ WriteBatch* new_write_batch_;
+ // Number of keys to add in the new batch
+ size_t num_keys_to_add_in_new_batch_;
+ // Number of keys added to new batch
+ size_t num_keys_added_;
+
+ public:
+ ChangeBatchHandler(WriteBatch* new_write_batch,
+ size_t num_keys_to_add_in_new_batch)
+ : new_write_batch_(new_write_batch),
+ num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch),
+ num_keys_added_(0) {}
+ void Put(const Slice& key, const Slice& value) override {
+ if (num_keys_added_ < num_keys_to_add_in_new_batch_) {
+ new_write_batch_->Put(key, value);
+ ++num_keys_added_;
+ }
+ }
+ };
+
+ class TestWalFilterWithChangeBatch : public WalFilter {
+ private:
+ // Index at which to start changing records
+ size_t change_records_from_index_;
+ // Number of keys to add in the new batch
+ size_t num_keys_to_add_in_new_batch_;
+ // Current record index, incremented with each record encountered.
+ size_t current_record_index_;
+
+ public:
+ TestWalFilterWithChangeBatch(size_t change_records_from_index,
+ size_t num_keys_to_add_in_new_batch)
+ : change_records_from_index_(change_records_from_index),
+ num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch),
+ current_record_index_(0) {}
+
+ WalProcessingOption LogRecord(const WriteBatch& batch,
+ WriteBatch* new_batch,
+ bool* batch_changed) const override {
+ if (current_record_index_ >= change_records_from_index_) {
+ ChangeBatchHandler handler(new_batch, num_keys_to_add_in_new_batch_);
+ batch.Iterate(&handler);
+ *batch_changed = true;
+ }
+
+ // Filter is passed as a const object for RocksDB to not modify the
+ // object, however we modify it for our own purpose here and hence
+ // cast the constness away.
+ (const_cast<TestWalFilterWithChangeBatch*>(this)
+ ->current_record_index_)++;
+
+ return WalProcessingOption::kContinueProcessing;
+ }
+
+ const char* Name() const override { return "TestWalFilterWithChangeBatch"; }
+ };
+
+ std::vector<std::vector<std::string>> batch_keys(3);
+
+ batch_keys[0].push_back("key1");
+ batch_keys[0].push_back("key2");
+ batch_keys[1].push_back("key3");
+ batch_keys[1].push_back("key4");
+ batch_keys[2].push_back("key5");
+ batch_keys[2].push_back("key6");
+
+ Options options = OptionsForLogIterTest();
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({ "pikachu" }, options);
+
+ // Write given keys in given batches
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ batch.Put(handles_[0], batch_keys[i][j], DummyString(1024));
+ }
+ dbfull()->Write(WriteOptions(), &batch);
+ }
+
+ // Create a test filter that would apply wal_processing_option at the first
+ // record
+ size_t change_records_from_index = 1;
+ size_t num_keys_to_add_in_new_batch = 1;
+ TestWalFilterWithChangeBatch test_wal_filter_with_change_batch(
+ change_records_from_index, num_keys_to_add_in_new_batch);
+
+ // Reopen database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ options.wal_filter = &test_wal_filter_with_change_batch;
+ ReopenWithColumnFamilies({ "default", "pikachu" }, options);
+
+ // Ensure that all keys exist before change_records_from_index_
+ // And after that index only single key exists
+ // as our filter adds only single key for each batch
+ std::vector<Slice> keys_must_exist;
+ std::vector<Slice> keys_must_not_exist;
+
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ if (i >= change_records_from_index && j >= num_keys_to_add_in_new_batch) {
+ keys_must_not_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ else {
+ keys_must_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ }
+ }
+
+ bool checked_after_reopen = false;
+
+ while (true) {
+ // Ensure that expected keys exists
+ // and not expected keys don't exist after recovery
+ ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
+
+ if (checked_after_reopen) {
+ break;
+ }
+
+ // reopen database again to make sure previous log(s) are not used
+ //(even if they were skipped)
+ // reopn database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ ReopenWithColumnFamilies({ "default", "pikachu" }, options);
+
+ checked_after_reopen = true;
+ }
+}
+
+TEST_F(DBTest2, WalFilterTestWithChangeBatchExtraKeys) {
+ class TestWalFilterWithChangeBatchAddExtraKeys : public WalFilter {
+ public:
+ WalProcessingOption LogRecord(const WriteBatch& batch, WriteBatch* new_batch,
+ bool* batch_changed) const override {
+ *new_batch = batch;
+ new_batch->Put("key_extra", "value_extra");
+ *batch_changed = true;
+ return WalProcessingOption::kContinueProcessing;
+ }
+
+ const char* Name() const override {
+ return "WalFilterTestWithChangeBatchExtraKeys";
+ }
+ };
+
+ std::vector<std::vector<std::string>> batch_keys(3);
+
+ batch_keys[0].push_back("key1");
+ batch_keys[0].push_back("key2");
+ batch_keys[1].push_back("key3");
+ batch_keys[1].push_back("key4");
+ batch_keys[2].push_back("key5");
+ batch_keys[2].push_back("key6");
+
+ Options options = OptionsForLogIterTest();
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({ "pikachu" }, options);
+
+ // Write given keys in given batches
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ batch.Put(handles_[0], batch_keys[i][j], DummyString(1024));
+ }
+ dbfull()->Write(WriteOptions(), &batch);
+ }
+
+ // Create a test filter that would add extra keys
+ TestWalFilterWithChangeBatchAddExtraKeys test_wal_filter_extra_keys;
+
+ // Reopen database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ options.wal_filter = &test_wal_filter_extra_keys;
+ Status status = TryReopenWithColumnFamilies({"default", "pikachu"}, options);
+ ASSERT_TRUE(status.IsNotSupported());
+
+ // Reopen without filter, now reopen should succeed - previous
+ // attempt to open must not have altered the db.
+ options = OptionsForLogIterTest();
+ ReopenWithColumnFamilies({ "default", "pikachu" }, options);
+
+ std::vector<Slice> keys_must_exist;
+ std::vector<Slice> keys_must_not_exist; // empty vector
+
+ for (size_t i = 0; i < batch_keys.size(); i++) {
+ for (size_t j = 0; j < batch_keys[i].size(); j++) {
+ keys_must_exist.push_back(Slice(batch_keys[i][j]));
+ }
+ }
+
+ ValidateKeyExistence(db_, keys_must_exist, keys_must_not_exist);
+}
+
+TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
+ class TestWalFilterWithColumnFamilies : public WalFilter {
+ private:
+ // column_family_id -> log_number map (provided to WALFilter)
+ std::map<uint32_t, uint64_t> cf_log_number_map_;
+ // column_family_name -> column_family_id map (provided to WALFilter)
+ std::map<std::string, uint32_t> cf_name_id_map_;
+ // column_family_name -> keys_found_in_wal map
+ // We store keys that are applicable to the column_family
+ // during recovery (i.e. aren't already flushed to SST file(s))
+ // for verification against the keys we expect.
+ std::map<uint32_t, std::vector<std::string>> cf_wal_keys_;
+ public:
+ void ColumnFamilyLogNumberMap(
+ const std::map<uint32_t, uint64_t>& cf_lognumber_map,
+ const std::map<std::string, uint32_t>& cf_name_id_map) override {
+ cf_log_number_map_ = cf_lognumber_map;
+ cf_name_id_map_ = cf_name_id_map;
+ }
+
+ WalProcessingOption LogRecordFound(unsigned long long log_number,
+ const std::string& /*log_file_name*/,
+ const WriteBatch& batch,
+ WriteBatch* /*new_batch*/,
+ bool* /*batch_changed*/) override {
+ class LogRecordBatchHandler : public WriteBatch::Handler {
+ private:
+ const std::map<uint32_t, uint64_t> & cf_log_number_map_;
+ std::map<uint32_t, std::vector<std::string>> & cf_wal_keys_;
+ unsigned long long log_number_;
+ public:
+ LogRecordBatchHandler(unsigned long long current_log_number,
+ const std::map<uint32_t, uint64_t> & cf_log_number_map,
+ std::map<uint32_t, std::vector<std::string>> & cf_wal_keys) :
+ cf_log_number_map_(cf_log_number_map),
+ cf_wal_keys_(cf_wal_keys),
+ log_number_(current_log_number){}
+
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& /*value*/) override {
+ auto it = cf_log_number_map_.find(column_family_id);
+ assert(it != cf_log_number_map_.end());
+ unsigned long long log_number_for_cf = it->second;
+ // If the current record is applicable for column_family_id
+ // (i.e. isn't flushed to SST file(s) for column_family_id)
+ // add it to the cf_wal_keys_ map for verification.
+ if (log_number_ >= log_number_for_cf) {
+ cf_wal_keys_[column_family_id].push_back(std::string(key.data(),
+ key.size()));
+ }
+ return Status::OK();
+ }
+ } handler(log_number, cf_log_number_map_, cf_wal_keys_);
+
+ batch.Iterate(&handler);
+
+ return WalProcessingOption::kContinueProcessing;
+ }
+
+ const char* Name() const override {
+ return "WalFilterTestWithColumnFamilies";
+ }
+
+ const std::map<uint32_t, std::vector<std::string>>& GetColumnFamilyKeys() {
+ return cf_wal_keys_;
+ }
+
+ const std::map<std::string, uint32_t> & GetColumnFamilyNameIdMap() {
+ return cf_name_id_map_;
+ }
+ };
+
+ std::vector<std::vector<std::string>> batch_keys_pre_flush(3);
+
+ batch_keys_pre_flush[0].push_back("key1");
+ batch_keys_pre_flush[0].push_back("key2");
+ batch_keys_pre_flush[1].push_back("key3");
+ batch_keys_pre_flush[1].push_back("key4");
+ batch_keys_pre_flush[2].push_back("key5");
+ batch_keys_pre_flush[2].push_back("key6");
+
+ Options options = OptionsForLogIterTest();
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({ "pikachu" }, options);
+
+ // Write given keys in given batches
+ for (size_t i = 0; i < batch_keys_pre_flush.size(); i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < batch_keys_pre_flush[i].size(); j++) {
+ batch.Put(handles_[0], batch_keys_pre_flush[i][j], DummyString(1024));
+ batch.Put(handles_[1], batch_keys_pre_flush[i][j], DummyString(1024));
+ }
+ dbfull()->Write(WriteOptions(), &batch);
+ }
+
+ //Flush default column-family
+ db_->Flush(FlushOptions(), handles_[0]);
+
+ // Do some more writes
+ std::vector<std::vector<std::string>> batch_keys_post_flush(3);
+
+ batch_keys_post_flush[0].push_back("key7");
+ batch_keys_post_flush[0].push_back("key8");
+ batch_keys_post_flush[1].push_back("key9");
+ batch_keys_post_flush[1].push_back("key10");
+ batch_keys_post_flush[2].push_back("key11");
+ batch_keys_post_flush[2].push_back("key12");
+
+ // Write given keys in given batches
+ for (size_t i = 0; i < batch_keys_post_flush.size(); i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < batch_keys_post_flush[i].size(); j++) {
+ batch.Put(handles_[0], batch_keys_post_flush[i][j], DummyString(1024));
+ batch.Put(handles_[1], batch_keys_post_flush[i][j], DummyString(1024));
+ }
+ dbfull()->Write(WriteOptions(), &batch);
+ }
+
+ // On Recovery we should only find the second batch applicable to default CF
+ // But both batches applicable to pikachu CF
+
+ // Create a test filter that would add extra keys
+ TestWalFilterWithColumnFamilies test_wal_filter_column_families;
+
+ // Reopen database with option to use WAL filter
+ options = OptionsForLogIterTest();
+ options.wal_filter = &test_wal_filter_column_families;
+ Status status =
+ TryReopenWithColumnFamilies({ "default", "pikachu" }, options);
+ ASSERT_TRUE(status.ok());
+
+ // verify that handles_[0] only has post_flush keys
+ // while handles_[1] has pre and post flush keys
+ auto cf_wal_keys = test_wal_filter_column_families.GetColumnFamilyKeys();
+ auto name_id_map = test_wal_filter_column_families.GetColumnFamilyNameIdMap();
+ size_t index = 0;
+ auto keys_cf = cf_wal_keys[name_id_map[kDefaultColumnFamilyName]];
+ //default column-family, only post_flush keys are expected
+ for (size_t i = 0; i < batch_keys_post_flush.size(); i++) {
+ for (size_t j = 0; j < batch_keys_post_flush[i].size(); j++) {
+ Slice key_from_the_log(keys_cf[index++]);
+ Slice batch_key(batch_keys_post_flush[i][j]);
+ ASSERT_TRUE(key_from_the_log.compare(batch_key) == 0);
+ }
+ }
+ ASSERT_TRUE(index == keys_cf.size());
+
+ index = 0;
+ keys_cf = cf_wal_keys[name_id_map["pikachu"]];
+ //pikachu column-family, all keys are expected
+ for (size_t i = 0; i < batch_keys_pre_flush.size(); i++) {
+ for (size_t j = 0; j < batch_keys_pre_flush[i].size(); j++) {
+ Slice key_from_the_log(keys_cf[index++]);
+ Slice batch_key(batch_keys_pre_flush[i][j]);
+ ASSERT_TRUE(key_from_the_log.compare(batch_key) == 0);
+ }
+ }
+
+ for (size_t i = 0; i < batch_keys_post_flush.size(); i++) {
+ for (size_t j = 0; j < batch_keys_post_flush[i].size(); j++) {
+ Slice key_from_the_log(keys_cf[index++]);
+ Slice batch_key(batch_keys_post_flush[i][j]);
+ ASSERT_TRUE(key_from_the_log.compare(batch_key) == 0);
+ }
+ }
+ ASSERT_TRUE(index == keys_cf.size());
+}
+
+TEST_F(DBTest2, PresetCompressionDict) {
+ // Verifies that compression ratio improves when dictionary is enabled, and
+ // improves even further when the dictionary is trained by ZSTD.
+ const size_t kBlockSizeBytes = 4 << 10;
+ const size_t kL0FileBytes = 128 << 10;
+ const size_t kApproxPerBlockOverheadBytes = 50;
+ const int kNumL0Files = 5;
+
+ Options options;
+ // Make sure to use any custom env that the test is configured with.
+ options.env = CurrentOptions().env;
+ options.allow_concurrent_memtable_write = false;
+ options.arena_block_size = kBlockSizeBytes;
+ options.create_if_missing = true;
+ options.disable_auto_compactions = true;
+ options.level0_file_num_compaction_trigger = kNumL0Files;
+ options.memtable_factory.reset(
+ new SpecialSkipListFactory(kL0FileBytes / kBlockSizeBytes));
+ options.num_levels = 2;
+ options.target_file_size_base = kL0FileBytes;
+ options.target_file_size_multiplier = 2;
+ options.write_buffer_size = kL0FileBytes;
+ BlockBasedTableOptions table_options;
+ table_options.block_size = kBlockSizeBytes;
+ std::vector<CompressionType> compression_types;
+ if (Zlib_Supported()) {
+ compression_types.push_back(kZlibCompression);
+ }
+#if LZ4_VERSION_NUMBER >= 10400 // r124+
+ compression_types.push_back(kLZ4Compression);
+ compression_types.push_back(kLZ4HCCompression);
+#endif // LZ4_VERSION_NUMBER >= 10400
+ if (ZSTD_Supported()) {
+ compression_types.push_back(kZSTD);
+ }
+
+ enum DictionaryTypes : int {
+ kWithoutDict,
+ kWithDict,
+ kWithZSTDTrainedDict,
+ kDictEnd,
+ };
+
+ for (auto compression_type : compression_types) {
+ options.compression = compression_type;
+ size_t bytes_without_dict = 0;
+ size_t bytes_with_dict = 0;
+ size_t bytes_with_zstd_trained_dict = 0;
+ for (int i = kWithoutDict; i < kDictEnd; i++) {
+ // First iteration: compress without preset dictionary
+ // Second iteration: compress with preset dictionary
+ // Third iteration (zstd only): compress with zstd-trained dictionary
+ //
+ // To make sure the compression dictionary has the intended effect, we
+ // verify the compressed size is smaller in successive iterations. Also in
+ // the non-first iterations, verify the data we get out is the same data
+ // we put in.
+ switch (i) {
+ case kWithoutDict:
+ options.compression_opts.max_dict_bytes = 0;
+ options.compression_opts.zstd_max_train_bytes = 0;
+ break;
+ case kWithDict:
+ options.compression_opts.max_dict_bytes = kBlockSizeBytes;
+ options.compression_opts.zstd_max_train_bytes = 0;
+ break;
+ case kWithZSTDTrainedDict:
+ if (compression_type != kZSTD) {
+ continue;
+ }
+ options.compression_opts.max_dict_bytes = kBlockSizeBytes;
+ options.compression_opts.zstd_max_train_bytes = kL0FileBytes;
+ break;
+ default:
+ assert(false);
+ }
+
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+ std::string seq_datas[10];
+ for (int j = 0; j < 10; ++j) {
+ seq_datas[j] =
+ RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes);
+ }
+
+ ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
+ for (int j = 0; j < kNumL0Files; ++j) {
+ for (size_t k = 0; k < kL0FileBytes / kBlockSizeBytes + 1; ++k) {
+ auto key_num = j * (kL0FileBytes / kBlockSizeBytes) + k;
+ ASSERT_OK(Put(1, Key(static_cast<int>(key_num)),
+ seq_datas[(key_num / 10) % 10]));
+ }
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ ASSERT_EQ(j + 1, NumTableFilesAtLevel(0, 1));
+ }
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
+ true /* disallow_trivial_move */);
+ ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
+ ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
+
+ // Get the live sst files size
+ size_t total_sst_bytes = TotalSize(1);
+ if (i == kWithoutDict) {
+ bytes_without_dict = total_sst_bytes;
+ } else if (i == kWithDict) {
+ bytes_with_dict = total_sst_bytes;
+ } else if (i == kWithZSTDTrainedDict) {
+ bytes_with_zstd_trained_dict = total_sst_bytes;
+ }
+
+ for (size_t j = 0; j < kNumL0Files * (kL0FileBytes / kBlockSizeBytes);
+ j++) {
+ ASSERT_EQ(seq_datas[(j / 10) % 10], Get(1, Key(static_cast<int>(j))));
+ }
+ if (i == kWithDict) {
+ ASSERT_GT(bytes_without_dict, bytes_with_dict);
+ } else if (i == kWithZSTDTrainedDict) {
+ // In zstd compression, it is sometimes possible that using a trained
+ // dictionary does not get as good a compression ratio as without
+ // training.
+ // But using a dictionary (with or without training) should always get
+ // better compression ratio than not using one.
+ ASSERT_TRUE(bytes_with_dict > bytes_with_zstd_trained_dict ||
+ bytes_without_dict > bytes_with_zstd_trained_dict);
+ }
+
+ DestroyAndReopen(options);
+ }
+ }
+}
+
+TEST_F(DBTest2, PresetCompressionDictLocality) {
+ if (!ZSTD_Supported()) {
+ return;
+ }
+ // Verifies that compression dictionary is generated from local data. The
+ // verification simply checks all output SSTs have different compression
+ // dictionaries. We do not verify effectiveness as that'd likely be flaky in
+ // the future.
+ const int kNumEntriesPerFile = 1 << 10; // 1KB
+ const int kNumBytesPerEntry = 1 << 10; // 1KB
+ const int kNumFiles = 4;
+ 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
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
+ BlockBasedTableOptions table_options;
+ table_options.cache_index_and_filter_blocks = true;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+
+ Random rnd(301);
+ for (int i = 0; i < kNumFiles; ++i) {
+ for (int j = 0; j < kNumEntriesPerFile; ++j) {
+ ASSERT_OK(Put(Key(i * kNumEntriesPerFile + j),
+ RandomString(&rnd, kNumBytesPerEntry)));
+ }
+ ASSERT_OK(Flush());
+ MoveFilesToLevel(1);
+ ASSERT_EQ(NumTableFilesAtLevel(1), i + 1);
+ }
+
+ // Store all the dictionaries generated during a full compaction.
+ std::vector<std::string> compression_dicts;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict",
+ [&](void* arg) {
+ compression_dicts.emplace_back(static_cast<Slice*>(arg)->ToString());
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ CompactRangeOptions compact_range_opts;
+ compact_range_opts.bottommost_level_compaction =
+ BottommostLevelCompaction::kForceOptimized;
+ ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr));
+
+ // Dictionary compression should not be so good as to compress four totally
+ // random files into one. If it does then there's probably something wrong
+ // with the test.
+ ASSERT_GT(NumTableFilesAtLevel(1), 1);
+
+ // Furthermore, there should be one compression dictionary generated per file.
+ // And they should all be different from each other.
+ ASSERT_EQ(NumTableFilesAtLevel(1),
+ static_cast<int>(compression_dicts.size()));
+ for (size_t i = 1; i < compression_dicts.size(); ++i) {
+ std::string& a = compression_dicts[i - 1];
+ std::string& b = compression_dicts[i];
+ size_t alen = a.size();
+ size_t blen = b.size();
+ ASSERT_TRUE(alen != blen || memcmp(a.data(), b.data(), alen) != 0);
+ }
+}
+
+class CompactionCompressionListener : public EventListener {
+ public:
+ explicit CompactionCompressionListener(Options* db_options)
+ : db_options_(db_options) {}
+
+ void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override {
+ // Figure out last level with files
+ int bottommost_level = 0;
+ for (int level = 0; level < db->NumberLevels(); level++) {
+ std::string files_at_level;
+ ASSERT_TRUE(
+ db->GetProperty("rocksdb.num-files-at-level" + NumberToString(level),
+ &files_at_level));
+ if (files_at_level != "0") {
+ bottommost_level = level;
+ }
+ }
+
+ if (db_options_->bottommost_compression != kDisableCompressionOption &&
+ ci.output_level == bottommost_level) {
+ ASSERT_EQ(ci.compression, db_options_->bottommost_compression);
+ } else if (db_options_->compression_per_level.size() != 0) {
+ ASSERT_EQ(ci.compression,
+ db_options_->compression_per_level[ci.output_level]);
+ } else {
+ ASSERT_EQ(ci.compression, db_options_->compression);
+ }
+ max_level_checked = std::max(max_level_checked, ci.output_level);
+ }
+
+ int max_level_checked = 0;
+ const Options* db_options_;
+};
+
+TEST_F(DBTest2, CompressionOptions) {
+ if (!Zlib_Supported() || !Snappy_Supported()) {
+ return;
+ }
+
+ Options options = CurrentOptions();
+ options.level0_file_num_compaction_trigger = 2;
+ options.max_bytes_for_level_base = 100;
+ options.max_bytes_for_level_multiplier = 2;
+ options.num_levels = 7;
+ options.max_background_compactions = 1;
+
+ CompactionCompressionListener* listener =
+ new CompactionCompressionListener(&options);
+ options.listeners.emplace_back(listener);
+
+ const int kKeySize = 5;
+ const int kValSize = 20;
+ Random rnd(301);
+
+ for (int iter = 0; iter <= 2; iter++) {
+ listener->max_level_checked = 0;
+
+ if (iter == 0) {
+ // Use different compression algorithms for different levels but
+ // always use Zlib for bottommost level
+ options.compression_per_level = {kNoCompression, kNoCompression,
+ kNoCompression, kSnappyCompression,
+ kSnappyCompression, kSnappyCompression,
+ kZlibCompression};
+ options.compression = kNoCompression;
+ options.bottommost_compression = kZlibCompression;
+ } else if (iter == 1) {
+ // Use Snappy except for bottommost level use ZLib
+ options.compression_per_level = {};
+ options.compression = kSnappyCompression;
+ options.bottommost_compression = kZlibCompression;
+ } else if (iter == 2) {
+ // Use Snappy everywhere
+ options.compression_per_level = {};
+ options.compression = kSnappyCompression;
+ options.bottommost_compression = kDisableCompressionOption;
+ }
+
+ DestroyAndReopen(options);
+ // Write 10 random files
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 5; j++) {
+ ASSERT_OK(
+ Put(RandomString(&rnd, kKeySize), RandomString(&rnd, kValSize)));
+ }
+ ASSERT_OK(Flush());
+ dbfull()->TEST_WaitForCompact();
+ }
+
+ // Make sure that we wrote enough to check all 7 levels
+ ASSERT_EQ(listener->max_level_checked, 6);
+ }
+}
+
+class CompactionStallTestListener : public EventListener {
+ public:
+ CompactionStallTestListener() : compacting_files_cnt_(0), compacted_files_cnt_(0) {}
+
+ void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
+ ASSERT_EQ(ci.cf_name, "default");
+ ASSERT_EQ(ci.base_input_level, 0);
+ ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum);
+ compacting_files_cnt_ += ci.input_files.size();
+ }
+
+ void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
+ ASSERT_EQ(ci.cf_name, "default");
+ ASSERT_EQ(ci.base_input_level, 0);
+ ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum);
+ compacted_files_cnt_ += ci.input_files.size();
+ }
+
+ std::atomic<size_t> compacting_files_cnt_;
+ std::atomic<size_t> compacted_files_cnt_;
+};
+
+TEST_F(DBTest2, CompactionStall) {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:0"},
+ {"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:1"},
+ {"DBTest2::CompactionStall:2",
+ "DBImpl::NotifyOnCompactionBegin::UnlockMutex"},
+ {"DBTest2::CompactionStall:3",
+ "DBImpl::NotifyOnCompactionCompleted::UnlockMutex"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Options options = CurrentOptions();
+ options.level0_file_num_compaction_trigger = 4;
+ options.max_background_compactions = 40;
+ CompactionStallTestListener* listener = new CompactionStallTestListener();
+ options.listeners.emplace_back(listener);
+ DestroyAndReopen(options);
+ // make sure all background compaction jobs can be scheduled
+ auto stop_token =
+ dbfull()->TEST_write_controler().GetCompactionPressureToken();
+
+ Random rnd(301);
+
+ // 4 Files in L0
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 10; j++) {
+ ASSERT_OK(Put(RandomString(&rnd, 10), RandomString(&rnd, 10)));
+ }
+ ASSERT_OK(Flush());
+ }
+
+ // Wait for compaction to be triggered
+ TEST_SYNC_POINT("DBTest2::CompactionStall:0");
+
+ // Clear "DBImpl::BGWorkCompaction" SYNC_POINT since we want to hold it again
+ // at DBTest2::CompactionStall::1
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace();
+
+ // Another 6 L0 files to trigger compaction again
+ for (int i = 0; i < 6; i++) {
+ for (int j = 0; j < 10; j++) {
+ ASSERT_OK(Put(RandomString(&rnd, 10), RandomString(&rnd, 10)));
+ }
+ ASSERT_OK(Flush());
+ }
+
+ // Wait for another compaction to be triggered
+ TEST_SYNC_POINT("DBTest2::CompactionStall:1");
+
+ // Hold NotifyOnCompactionBegin in the unlock mutex section
+ TEST_SYNC_POINT("DBTest2::CompactionStall:2");
+
+ // Hold NotifyOnCompactionCompleted in the unlock mutex section
+ TEST_SYNC_POINT("DBTest2::CompactionStall:3");
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_LT(NumTableFilesAtLevel(0),
+ options.level0_file_num_compaction_trigger);
+ ASSERT_GT(listener->compacted_files_cnt_.load(),
+ 10 - options.level0_file_num_compaction_trigger);
+ ASSERT_EQ(listener->compacting_files_cnt_.load(), listener->compacted_files_cnt_.load());
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTest2, FirstSnapshotTest) {
+ Options options;
+ options.write_buffer_size = 100000; // Small write buffer
+ options = CurrentOptions(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ // This snapshot will have sequence number 0 what is expected behaviour.
+ const Snapshot* s1 = db_->GetSnapshot();
+
+ Put(1, "k1", std::string(100000, 'x')); // Fill memtable
+ Put(1, "k2", std::string(100000, 'y')); // Trigger flush
+
+ db_->ReleaseSnapshot(s1);
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, DuplicateSnapshot) {
+ Options options;
+ options = CurrentOptions(options);
+ std::vector<const Snapshot*> snapshots;
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+ SequenceNumber oldest_ww_snap, first_ww_snap;
+
+ Put("k", "v"); // inc seq
+ snapshots.push_back(db_->GetSnapshot());
+ snapshots.push_back(db_->GetSnapshot());
+ Put("k", "v"); // inc seq
+ snapshots.push_back(db_->GetSnapshot());
+ snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary());
+ first_ww_snap = snapshots.back()->GetSequenceNumber();
+ Put("k", "v"); // inc seq
+ snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary());
+ snapshots.push_back(db_->GetSnapshot());
+ Put("k", "v"); // inc seq
+ snapshots.push_back(db_->GetSnapshot());
+
+ {
+ InstrumentedMutexLock l(dbi->mutex());
+ auto seqs = dbi->snapshots().GetAll(&oldest_ww_snap);
+ ASSERT_EQ(seqs.size(), 4); // duplicates are not counted
+ ASSERT_EQ(oldest_ww_snap, first_ww_snap);
+ }
+
+ for (auto s : snapshots) {
+ db_->ReleaseSnapshot(s);
+ }
+}
+#endif // ROCKSDB_LITE
+
+class PinL0IndexAndFilterBlocksTest
+ : public DBTestBase,
+ public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ PinL0IndexAndFilterBlocksTest() : DBTestBase("/db_pin_l0_index_bloom_test") {}
+ void SetUp() override {
+ infinite_max_files_ = std::get<0>(GetParam());
+ disallow_preload_ = std::get<1>(GetParam());
+ }
+
+ void CreateTwoLevels(Options* options, bool close_afterwards) {
+ if (infinite_max_files_) {
+ options->max_open_files = -1;
+ }
+ options->create_if_missing = true;
+ options->statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions table_options;
+ table_options.cache_index_and_filter_blocks = true;
+ table_options.pin_l0_filter_and_index_blocks_in_cache = true;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(20));
+ options->table_factory.reset(new BlockBasedTableFactory(table_options));
+ CreateAndReopenWithCF({"pikachu"}, *options);
+
+ Put(1, "a", "begin");
+ Put(1, "z", "end");
+ ASSERT_OK(Flush(1));
+ // move this table to L1
+ dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
+
+ // reset block cache
+ table_options.block_cache = NewLRUCache(64 * 1024);
+ options->table_factory.reset(NewBlockBasedTableFactory(table_options));
+ TryReopenWithColumnFamilies({"default", "pikachu"}, *options);
+ // create new table at L0
+ Put(1, "a2", "begin2");
+ Put(1, "z2", "end2");
+ ASSERT_OK(Flush(1));
+
+ if (close_afterwards) {
+ Close(); // This ensures that there is no ref to block cache entries
+ }
+ table_options.block_cache->EraseUnRefEntries();
+ }
+
+ bool infinite_max_files_;
+ bool disallow_preload_;
+};
+
+TEST_P(PinL0IndexAndFilterBlocksTest,
+ IndexAndFilterBlocksOfNewTableAddedToCacheWithPinning) {
+ Options options = CurrentOptions();
+ if (infinite_max_files_) {
+ options.max_open_files = -1;
+ }
+ options.create_if_missing = true;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions table_options;
+ table_options.cache_index_and_filter_blocks = true;
+ table_options.pin_l0_filter_and_index_blocks_in_cache = true;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(20));
+ options.table_factory.reset(new BlockBasedTableFactory(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_FILTER_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+
+ // only index/filter were added
+ ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_ADD));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
+
+ std::string value;
+ // Miss and hit count should remain the same, they're all pinned.
+ db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+
+ // Miss and hit count should remain the same, they're all pinned.
+ value = Get(1, "key");
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+}
+
+TEST_P(PinL0IndexAndFilterBlocksTest,
+ MultiLevelIndexAndFilterBlocksCachedWithPinning) {
+ Options options = CurrentOptions();
+ PinL0IndexAndFilterBlocksTest::CreateTwoLevels(&options, false);
+ // get base cache values
+ uint64_t fm = TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS);
+ uint64_t fh = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT);
+ uint64_t im = TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS);
+ uint64_t ih = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT);
+
+ std::string value;
+ // this should be read from L0
+ // so cache values don't change
+ value = Get(1, "a2");
+ ASSERT_EQ(fm, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+
+ // this should be read from L1
+ // the file is opened, prefetching results in a cache filter miss
+ // the block is loaded and added to the cache,
+ // then the get results in a cache hit for L1
+ // When we have inifinite max_files, there is still cache miss because we have
+ // reset the block cache
+ value = Get(1, "a");
+ ASSERT_EQ(fm + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(im + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+}
+
+TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
+ Options options = CurrentOptions();
+ // This ensures that db does not ref anything in the block cache, so
+ // EraseUnRefEntries could clear them up.
+ bool close_afterwards = true;
+ PinL0IndexAndFilterBlocksTest::CreateTwoLevels(&options, close_afterwards);
+
+ // Get base cache values
+ uint64_t fm = TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS);
+ uint64_t fh = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT);
+ uint64_t im = TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS);
+ uint64_t ih = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT);
+
+ if (disallow_preload_) {
+ // Now we have two files. We narrow the max open files to allow 3 entries
+ // so that preloading SST files won't happen.
+ options.max_open_files = 13;
+ // 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<int*>(arg);
+ *max_open_files = 13;
+ });
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // Reopen database. If max_open_files is set as -1, table readers will be
+ // preloaded. This will trigger a BlockBasedTable::Open() and prefetch
+ // L0 index and filter. Level 1's prefetching is disabled in DB::Open()
+ TryReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ if (!disallow_preload_) {
+ // After reopen, cache miss are increased by one because we read (and only
+ // read) filter and index on L0
+ ASSERT_EQ(fm + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ } else {
+ // If max_open_files is not -1, we do not preload table readers, so there is
+ // no change.
+ ASSERT_EQ(fm, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ }
+ std::string value;
+ // this should be read from L0
+ value = Get(1, "a2");
+ // If max_open_files is -1, we have pinned index and filter in Rep, so there
+ // will not be changes in index and filter misses or hits. If max_open_files
+ // is not -1, Get() will open a TableReader and prefetch index and filter.
+ ASSERT_EQ(fm + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+
+ // this should be read from L1
+ value = Get(1, "a");
+ if (!disallow_preload_) {
+ // In inifinite max files case, there's a cache miss in executing Get()
+ // because index and filter are not prefetched before.
+ ASSERT_EQ(fm + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ } else {
+ // In this case, cache miss will be increased by one in
+ // BlockBasedTable::Open() because this is not in DB::Open() code path so we
+ // will prefetch L1's index and filter. Cache hit will also be increased by
+ // one because Get() will read index and filter from the block cache
+ // prefetched in previous Open() call.
+ ASSERT_EQ(fm + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ }
+
+ // Force a full compaction to one single file. There will be a block
+ // cache read for both of index and filter. If prefetch doesn't explicitly
+ // happen, it will happen when verifying the file.
+ Compact(1, "a", "zzzzz");
+ dbfull()->TEST_WaitForCompact();
+
+ if (!disallow_preload_) {
+ ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ } else {
+ ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ }
+
+ // Bloom and index hit will happen when a Get() happens.
+ value = Get(1, "a");
+ if (!disallow_preload_) {
+ ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ } else {
+ ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
+ ASSERT_EQ(fh + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
+ ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
+ ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest,
+ PinL0IndexAndFilterBlocksTest,
+ ::testing::Values(std::make_tuple(true, false),
+ std::make_tuple(false, false),
+ std::make_tuple(false, true)));
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, MaxCompactionBytesTest) {
+ Options options = CurrentOptions();
+ options.memtable_factory.reset(
+ new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
+ options.compaction_style = kCompactionStyleLevel;
+ 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 = 100 << 10;
+ // Infinite for full compaction.
+ options.max_compaction_bytes = options.target_file_size_base * 100;
+
+ Reopen(options);
+
+ Random rnd(301);
+
+ for (int num = 0; num < 8; num++) {
+ GenerateNewRandomFile(&rnd);
+ }
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+ ASSERT_EQ("0,0,8", FilesPerLevel(0));
+
+ // When compact from Ln -> Ln+1, cut a file if the file overlaps with
+ // more than three files in Ln+1.
+ options.max_compaction_bytes = options.target_file_size_base * 3;
+ Reopen(options);
+
+ GenerateNewRandomFile(&rnd);
+ // Add three more small files that overlap with the previous file
+ for (int i = 0; i < 3; i++) {
+ Put("a", "z");
+ ASSERT_OK(Flush());
+ }
+ dbfull()->TEST_WaitForCompact();
+
+ // Output files to L1 are cut to three pieces, according to
+ // options.max_compaction_bytes
+ ASSERT_EQ("0,3,8", FilesPerLevel(0));
+}
+
+static void UniqueIdCallback(void* arg) {
+ int* result = reinterpret_cast<int*>(arg);
+ if (*result == -1) {
+ *result = 0;
+ }
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
+}
+
+class MockPersistentCache : public PersistentCache {
+ public:
+ explicit MockPersistentCache(const bool is_compressed, const size_t max_size)
+ : is_compressed_(is_compressed), max_size_(max_size) {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
+ }
+
+ ~MockPersistentCache() override {}
+
+ PersistentCache::StatsType Stats() override {
+ return PersistentCache::StatsType();
+ }
+
+ Status Insert(const Slice& page_key, const char* data,
+ const size_t size) override {
+ MutexLock _(&lock_);
+
+ if (size_ > max_size_) {
+ size_ -= data_.begin()->second.size();
+ data_.erase(data_.begin());
+ }
+
+ data_.insert(std::make_pair(page_key.ToString(), std::string(data, size)));
+ size_ += size;
+ return Status::OK();
+ }
+
+ Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
+ size_t* size) override {
+ MutexLock _(&lock_);
+ auto it = data_.find(page_key.ToString());
+ if (it == data_.end()) {
+ return Status::NotFound();
+ }
+
+ assert(page_key.ToString() == it->first);
+ data->reset(new char[it->second.size()]);
+ memcpy(data->get(), it->second.c_str(), it->second.size());
+ *size = it->second.size();
+ return Status::OK();
+ }
+
+ bool IsCompressed() override { return is_compressed_; }
+
+ std::string GetPrintableOptions() const override {
+ return "MockPersistentCache";
+ }
+
+ port::Mutex lock_;
+ std::map<std::string, std::string> data_;
+ const bool is_compressed_ = true;
+ size_t size_ = 0;
+ const size_t max_size_ = 10 * 1024; // 10KiB
+};
+
+#ifdef OS_LINUX
+// Make sure that in CPU time perf context counters, Env::NowCPUNanos()
+// is used, rather than Env::CPUNanos();
+TEST_F(DBTest2, TestPerfContextGetCpuTime) {
+ // force resizing table cache so table handle is not preloaded so that
+ // we can measure find_table_nanos during Get().
+ dbfull()->TEST_table_cache()->SetCapacity(0);
+ ASSERT_OK(Put("foo", "bar"));
+ ASSERT_OK(Flush());
+ env_->now_cpu_count_.store(0);
+
+ // CPU timing is not enabled with kEnableTimeExceptForMutex
+ SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex);
+ ASSERT_EQ("bar", Get("foo"));
+ ASSERT_EQ(0, get_perf_context()->get_cpu_nanos);
+ ASSERT_EQ(0, env_->now_cpu_count_.load());
+
+ uint64_t kDummyAddonTime = uint64_t{1000000000000};
+
+ // Add time to NowNanos() reading.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TableCache::FindTable:0",
+ [&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
+ ASSERT_EQ("bar", Get("foo"));
+ ASSERT_GT(env_->now_cpu_count_.load(), 2);
+ ASSERT_LT(get_perf_context()->get_cpu_nanos, kDummyAddonTime);
+ ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime);
+
+ SetPerfLevel(PerfLevel::kDisable);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, TestPerfContextIterCpuTime) {
+ DestroyAndReopen(CurrentOptions());
+ // force resizing table cache so table handle is not preloaded so that
+ // we can measure find_table_nanos during iteration
+ dbfull()->TEST_table_cache()->SetCapacity(0);
+
+ const size_t kNumEntries = 10;
+ for (size_t i = 0; i < kNumEntries; ++i) {
+ ASSERT_OK(Put("k" + ToString(i), "v" + ToString(i)));
+ }
+ ASSERT_OK(Flush());
+ for (size_t i = 0; i < kNumEntries; ++i) {
+ ASSERT_EQ("v" + ToString(i), Get("k" + ToString(i)));
+ }
+ std::string last_key = "k" + ToString(kNumEntries - 1);
+ std::string last_value = "v" + ToString(kNumEntries - 1);
+ env_->now_cpu_count_.store(0);
+
+ // CPU timing is not enabled with kEnableTimeExceptForMutex
+ SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex);
+ Iterator* iter = db_->NewIterator(ReadOptions());
+ iter->Seek("k0");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ iter->SeekForPrev(last_key);
+ ASSERT_TRUE(iter->Valid());
+ iter->SeekToLast();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(last_value, iter->value().ToString());
+ iter->SeekToFirst();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ ASSERT_EQ(0, get_perf_context()->iter_seek_cpu_nanos);
+ iter->Next();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v1", iter->value().ToString());
+ ASSERT_EQ(0, get_perf_context()->iter_next_cpu_nanos);
+ iter->Prev();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ ASSERT_EQ(0, get_perf_context()->iter_prev_cpu_nanos);
+ ASSERT_EQ(0, env_->now_cpu_count_.load());
+ delete iter;
+
+ uint64_t kDummyAddonTime = uint64_t{1000000000000};
+
+ // Add time to NowNanos() reading.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TableCache::FindTable:0",
+ [&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
+ iter = db_->NewIterator(ReadOptions());
+ iter->Seek("k0");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ iter->SeekForPrev(last_key);
+ ASSERT_TRUE(iter->Valid());
+ iter->SeekToLast();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(last_value, iter->value().ToString());
+ iter->SeekToFirst();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, 0);
+ ASSERT_LT(get_perf_context()->iter_seek_cpu_nanos, kDummyAddonTime);
+ iter->Next();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v1", iter->value().ToString());
+ ASSERT_GT(get_perf_context()->iter_next_cpu_nanos, 0);
+ ASSERT_LT(get_perf_context()->iter_next_cpu_nanos, kDummyAddonTime);
+ iter->Prev();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v0", iter->value().ToString());
+ ASSERT_GT(get_perf_context()->iter_prev_cpu_nanos, 0);
+ ASSERT_LT(get_perf_context()->iter_prev_cpu_nanos, kDummyAddonTime);
+ ASSERT_GE(env_->now_cpu_count_.load(), 12);
+ ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime);
+
+ SetPerfLevel(PerfLevel::kDisable);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ delete iter;
+}
+#endif // OS_LINUX
+
+// GetUniqueIdFromFile is not implemented on these platforms. Persistent cache
+// breaks when that function is not implemented and no regular block cache is
+// provided.
+#if !defined(OS_SOLARIS) && !defined(OS_WIN)
+TEST_F(DBTest2, PersistentCache) {
+ int num_iter = 80;
+
+ Options options;
+ options.write_buffer_size = 64 * 1024; // small write buffer
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options = CurrentOptions(options);
+
+ auto bsizes = {/*no block cache*/ 0, /*1M*/ 1 * 1024 * 1024};
+ auto types = {/*compressed*/ 1, /*uncompressed*/ 0};
+ for (auto bsize : bsizes) {
+ for (auto type : types) {
+ BlockBasedTableOptions table_options;
+ table_options.persistent_cache.reset(
+ new MockPersistentCache(type, 10 * 1024));
+ table_options.no_block_cache = true;
+ table_options.block_cache = bsize ? NewLRUCache(bsize) : nullptr;
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ DestroyAndReopen(options);
+ CreateAndReopenWithCF({"pikachu"}, options);
+ // default column family doesn't have block cache
+ Options no_block_cache_opts;
+ no_block_cache_opts.statistics = options.statistics;
+ no_block_cache_opts = CurrentOptions(no_block_cache_opts);
+ BlockBasedTableOptions table_options_no_bc;
+ table_options_no_bc.no_block_cache = true;
+ no_block_cache_opts.table_factory.reset(
+ NewBlockBasedTableFactory(table_options_no_bc));
+ ReopenWithColumnFamilies(
+ {"default", "pikachu"},
+ std::vector<Options>({no_block_cache_opts, options}));
+
+ Random rnd(301);
+
+ // Write 8MB (80 values, each 100K)
+ ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
+ std::vector<std::string> values;
+ std::string str;
+ for (int i = 0; i < num_iter; i++) {
+ if (i % 4 == 0) { // high compression ratio
+ str = RandomString(&rnd, 1000);
+ }
+ values.push_back(str);
+ ASSERT_OK(Put(1, Key(i), values[i]));
+ }
+
+ // flush all data from memtable so that reads are from block cache
+ ASSERT_OK(Flush(1));
+
+ for (int i = 0; i < num_iter; i++) {
+ ASSERT_EQ(Get(1, Key(i)), values[i]);
+ }
+
+ auto hit = options.statistics->getTickerCount(PERSISTENT_CACHE_HIT);
+ auto miss = options.statistics->getTickerCount(PERSISTENT_CACHE_MISS);
+
+ ASSERT_GT(hit, 0);
+ ASSERT_GT(miss, 0);
+ }
+ }
+}
+#endif // !defined(OS_SOLARIS) && !defined(OS_WIN)
+
+namespace {
+void CountSyncPoint() {
+ TEST_SYNC_POINT_CALLBACK("DBTest2::MarkedPoint", nullptr /* arg */);
+}
+} // namespace
+
+TEST_F(DBTest2, SyncPointMarker) {
+ std::atomic<int> sync_point_called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBTest2::MarkedPoint",
+ [&](void* /*arg*/) { sync_point_called.fetch_add(1); });
+
+ // The first dependency enforces Marker can be loaded before MarkedPoint.
+ // The second checks that thread 1's MarkedPoint should be disabled here.
+ // Execution order:
+ // | Thread 1 | Thread 2 |
+ // | | Marker |
+ // | MarkedPoint | |
+ // | Thread1First | |
+ // | | MarkedPoint |
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependencyAndMarkers(
+ {{"DBTest2::SyncPointMarker:Thread1First", "DBTest2::MarkedPoint"}},
+ {{"DBTest2::SyncPointMarker:Marker", "DBTest2::MarkedPoint"}});
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::function<void()> func1 = [&]() {
+ CountSyncPoint();
+ TEST_SYNC_POINT("DBTest2::SyncPointMarker:Thread1First");
+ };
+
+ std::function<void()> func2 = [&]() {
+ TEST_SYNC_POINT("DBTest2::SyncPointMarker:Marker");
+ CountSyncPoint();
+ };
+
+ auto thread1 = port::Thread(func1);
+ auto thread2 = port::Thread(func2);
+ thread1.join();
+ thread2.join();
+
+ // Callback is only executed once
+ ASSERT_EQ(sync_point_called.load(), 1);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+#endif
+
+size_t GetEncodedEntrySize(size_t key_size, size_t value_size) {
+ std::string buffer;
+
+ PutVarint32(&buffer, static_cast<uint32_t>(0));
+ PutVarint32(&buffer, static_cast<uint32_t>(key_size));
+ PutVarint32(&buffer, static_cast<uint32_t>(value_size));
+
+ return buffer.size() + key_size + value_size;
+}
+
+TEST_F(DBTest2, ReadAmpBitmap) {
+ Options options = CurrentOptions();
+ BlockBasedTableOptions bbto;
+ uint32_t bytes_per_bit[2] = {1, 16};
+ for (size_t k = 0; k < 2; k++) {
+ // Disable delta encoding to make it easier to calculate read amplification
+ bbto.use_delta_encoding = false;
+ // Huge block cache to make it easier to calculate read amplification
+ bbto.block_cache = NewLRUCache(1024 * 1024 * 1024);
+ bbto.read_amp_bytes_per_bit = bytes_per_bit[k];
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ DestroyAndReopen(options);
+
+ const size_t kNumEntries = 10000;
+
+ Random rnd(301);
+ for (size_t i = 0; i < kNumEntries; i++) {
+ ASSERT_OK(Put(Key(static_cast<int>(i)), RandomString(&rnd, 100)));
+ }
+ ASSERT_OK(Flush());
+
+ Close();
+ Reopen(options);
+
+ // Read keys/values randomly and verify that reported read amp error
+ // is less than 2%
+ uint64_t total_useful_bytes = 0;
+ std::set<int> read_keys;
+ std::string value;
+ for (size_t i = 0; i < kNumEntries * 5; i++) {
+ int key_idx = rnd.Next() % kNumEntries;
+ std::string key = Key(key_idx);
+ ASSERT_OK(db_->Get(ReadOptions(), key, &value));
+
+ if (read_keys.find(key_idx) == read_keys.end()) {
+ auto internal_key = InternalKey(key, 0, ValueType::kTypeValue);
+ total_useful_bytes +=
+ GetEncodedEntrySize(internal_key.size(), value.size());
+ read_keys.insert(key_idx);
+ }
+
+ double expected_read_amp =
+ static_cast<double>(total_useful_bytes) /
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ double read_amp =
+ static_cast<double>(options.statistics->getTickerCount(
+ READ_AMP_ESTIMATE_USEFUL_BYTES)) /
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ double error_pct = fabs(expected_read_amp - read_amp) * 100;
+ // Error between reported read amp and real read amp should be less than
+ // 2%
+ EXPECT_LE(error_pct, 2);
+ }
+
+ // Make sure we read every thing in the DB (which is smaller than our cache)
+ Iterator* iter = db_->NewIterator(ReadOptions());
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ ASSERT_EQ(iter->value().ToString(), Get(iter->key().ToString()));
+ }
+ delete iter;
+
+ // Read amp is on average 100% since we read all what we loaded in memory
+ if (k == 0) {
+ ASSERT_EQ(
+ options.statistics->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES),
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES));
+ } else {
+ ASSERT_NEAR(
+ options.statistics->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES) *
+ 1.0f /
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES),
+ 1, .01);
+ }
+ }
+}
+
+#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
+TEST_F(DBTest2, ReadAmpBitmapLiveInCacheAfterDBClose) {
+ {
+ const int kIdBufLen = 100;
+ char id_buf[kIdBufLen];
+#ifndef OS_WIN
+ // You can't open a directory on windows using random access file
+ std::unique_ptr<RandomAccessFile> file;
+ ASSERT_OK(env_->NewRandomAccessFile(dbname_, &file, EnvOptions()));
+ if (file->GetUniqueId(id_buf, kIdBufLen) == 0) {
+ // fs holding db directory doesn't support getting a unique file id,
+ // this means that running this test will fail because lru_cache will load
+ // the blocks again regardless of them being already in the cache
+ return;
+ }
+#else
+ std::unique_ptr<Directory> dir;
+ ASSERT_OK(env_->NewDirectory(dbname_, &dir));
+ if (dir->GetUniqueId(id_buf, kIdBufLen) == 0) {
+ // fs holding db directory doesn't support getting a unique file id,
+ // this means that running this test will fail because lru_cache will load
+ // the blocks again regardless of them being already in the cache
+ return;
+ }
+#endif
+ }
+ uint32_t bytes_per_bit[2] = {1, 16};
+ for (size_t k = 0; k < 2; k++) {
+ std::shared_ptr<Cache> lru_cache = NewLRUCache(1024 * 1024 * 1024);
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ Options options = CurrentOptions();
+ BlockBasedTableOptions bbto;
+ // Disable delta encoding to make it easier to calculate read amplification
+ bbto.use_delta_encoding = false;
+ // Huge block cache to make it easier to calculate read amplification
+ bbto.block_cache = lru_cache;
+ bbto.read_amp_bytes_per_bit = bytes_per_bit[k];
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ options.statistics = stats;
+ DestroyAndReopen(options);
+
+ const int kNumEntries = 10000;
+
+ Random rnd(301);
+ for (int i = 0; i < kNumEntries; i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 100)));
+ }
+ ASSERT_OK(Flush());
+
+ Close();
+ Reopen(options);
+
+ uint64_t total_useful_bytes = 0;
+ std::set<int> read_keys;
+ std::string value;
+ // Iter1: Read half the DB, Read even keys
+ // Key(0), Key(2), Key(4), Key(6), Key(8), ...
+ for (int i = 0; i < kNumEntries; i += 2) {
+ std::string key = Key(i);
+ ASSERT_OK(db_->Get(ReadOptions(), key, &value));
+
+ if (read_keys.find(i) == read_keys.end()) {
+ auto internal_key = InternalKey(key, 0, ValueType::kTypeValue);
+ total_useful_bytes +=
+ GetEncodedEntrySize(internal_key.size(), value.size());
+ read_keys.insert(i);
+ }
+ }
+
+ size_t total_useful_bytes_iter1 =
+ options.statistics->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES);
+ size_t total_loaded_bytes_iter1 =
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ Close();
+ std::shared_ptr<Statistics> new_statistics =
+ ROCKSDB_NAMESPACE::CreateDBStatistics();
+ // Destroy old statistics obj that the blocks in lru_cache are pointing to
+ options.statistics.reset();
+ // Use the statistics object that we just created
+ options.statistics = new_statistics;
+ Reopen(options);
+
+ // Iter2: Read half the DB, Read odd keys
+ // Key(1), Key(3), Key(5), Key(7), Key(9), ...
+ for (int i = 1; i < kNumEntries; i += 2) {
+ std::string key = Key(i);
+ ASSERT_OK(db_->Get(ReadOptions(), key, &value));
+
+ if (read_keys.find(i) == read_keys.end()) {
+ auto internal_key = InternalKey(key, 0, ValueType::kTypeValue);
+ total_useful_bytes +=
+ GetEncodedEntrySize(internal_key.size(), value.size());
+ read_keys.insert(i);
+ }
+ }
+
+ size_t total_useful_bytes_iter2 =
+ options.statistics->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES);
+ size_t total_loaded_bytes_iter2 =
+ options.statistics->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+
+ // Read amp is on average 100% since we read all what we loaded in memory
+ if (k == 0) {
+ ASSERT_EQ(total_useful_bytes_iter1 + total_useful_bytes_iter2,
+ total_loaded_bytes_iter1 + total_loaded_bytes_iter2);
+ } else {
+ ASSERT_NEAR((total_useful_bytes_iter1 + total_useful_bytes_iter2) * 1.0f /
+ (total_loaded_bytes_iter1 + total_loaded_bytes_iter2),
+ 1, .01);
+ }
+ }
+}
+#endif // !OS_SOLARIS
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
+ Options options = CurrentOptions();
+ options.num_levels = 3;
+ options.IncreaseParallelism(20);
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put(Key(0), "a"));
+ ASSERT_OK(Put(Key(5), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put(Key(10), "a"));
+ ASSERT_OK(Put(Key(15), "a"));
+ ASSERT_OK(Flush());
+
+ CompactRangeOptions cro;
+ cro.change_level = true;
+ cro.target_level = 2;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ auto get_stat = [](std::string level_str, LevelStatType type,
+ std::map<std::string, std::string> props) {
+ auto prop_str =
+ "compaction." + level_str + "." +
+ InternalStats::compaction_level_stats.at(type).property_name.c_str();
+ auto prop_item = props.find(prop_str);
+ return prop_item == props.end() ? 0 : std::stod(prop_item->second);
+ };
+
+ // Trivial move 2 files to L2
+ ASSERT_EQ("0,0,2", FilesPerLevel());
+ // Also test that the stats GetMapProperty API reporting the same result
+ {
+ std::map<std::string, std::string> prop;
+ ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
+ ASSERT_EQ(0, get_stat("L0", LevelStatType::NUM_FILES, prop));
+ ASSERT_EQ(0, get_stat("L1", LevelStatType::NUM_FILES, prop));
+ ASSERT_EQ(2, get_stat("L2", LevelStatType::NUM_FILES, prop));
+ ASSERT_EQ(2, get_stat("Sum", LevelStatType::NUM_FILES, prop));
+ }
+
+ // While the compaction is running, we will create 2 new files that
+ // can fit in L2, these 2 files will be moved to L2 and overlap with
+ // the running compaction and break the LSM consistency.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run():Start", [&](void* /*arg*/) {
+ ASSERT_OK(
+ dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "2"},
+ {"max_bytes_for_level_base", "1"}}));
+ ASSERT_OK(Put(Key(6), "a"));
+ ASSERT_OK(Put(Key(7), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put(Key(8), "a"));
+ ASSERT_OK(Put(Key(9), "a"));
+ ASSERT_OK(Flush());
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // Run a manual compaction that will compact the 2 files in L2
+ // into 1 file in L2
+ cro.exclusive_manual_compaction = false;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ // Test that the stats GetMapProperty API reporting 1 file in L2
+ {
+ std::map<std::string, std::string> prop;
+ ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
+ ASSERT_EQ(1, get_stat("L2", LevelStatType::NUM_FILES, prop));
+ }
+}
+
+TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) {
+ Options options = CurrentOptions();
+ options.num_levels = 2;
+ options.IncreaseParallelism(20);
+ options.disable_auto_compactions = true;
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put(Key(0), "a"));
+ ASSERT_OK(Put(Key(5), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put(Key(10), "a"));
+ ASSERT_OK(Put(Key(15), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ // Trivial move 2 files to L1
+ ASSERT_EQ("0,2", FilesPerLevel());
+
+ std::function<void()> bg_manual_compact = [&]() {
+ std::string k1 = Key(6);
+ std::string k2 = Key(9);
+ Slice k1s(k1);
+ Slice k2s(k2);
+ CompactRangeOptions cro;
+ cro.exclusive_manual_compaction = false;
+ ASSERT_OK(db_->CompactRange(cro, &k1s, &k2s));
+ };
+ ROCKSDB_NAMESPACE::port::Thread bg_thread;
+
+ // While the compaction is running, we will create 2 new files that
+ // can fit in L1, these 2 files will be moved to L1 and overlap with
+ // the running compaction and break the LSM consistency.
+ std::atomic<bool> flag(false);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run():Start", [&](void* /*arg*/) {
+ if (flag.exchange(true)) {
+ // We want to make sure to call this callback only once
+ return;
+ }
+ ASSERT_OK(Put(Key(6), "a"));
+ ASSERT_OK(Put(Key(7), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put(Key(8), "a"));
+ ASSERT_OK(Put(Key(9), "a"));
+ ASSERT_OK(Flush());
+
+ // Start a non-exclusive manual compaction in a bg thread
+ bg_thread = port::Thread(bg_manual_compact);
+ // This manual compaction conflict with the other manual compaction
+ // so it should wait until the first compaction finish
+ env_->SleepForMicroseconds(1000000);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // Run a manual compaction that will compact the 2 files in L1
+ // into 1 file in L1
+ CompactRangeOptions cro;
+ cro.exclusive_manual_compaction = false;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+ bg_thread.join();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, PausingManualCompaction1) {
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.num_levels = 7;
+
+ DestroyAndReopen(options);
+ Random rnd(301);
+ // Generate a file containing 10 keys.
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+
+ // Generate another file containing same keys
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+
+ int manual_compactions_paused = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run():PausingManualCompaction:1", [&](void* arg) {
+ auto paused = reinterpret_cast<std::atomic<bool>*>(arg);
+ ASSERT_FALSE(paused->load(std::memory_order_acquire));
+ paused->store(true, std::memory_order_release);
+ manual_compactions_paused += 1;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::vector<std::string> files_before_compact, files_after_compact;
+ // Remember file name before compaction is triggered
+ std::vector<LiveFileMetaData> files_meta;
+ dbfull()->GetLiveFilesMetaData(&files_meta);
+ for (auto file : files_meta) {
+ files_before_compact.push_back(file.name);
+ }
+
+ // OK, now trigger a manual compaction
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ // Wait for compactions to get scheduled and stopped
+ dbfull()->TEST_WaitForCompact(true);
+
+ // Get file names after compaction is stopped
+ files_meta.clear();
+ dbfull()->GetLiveFilesMetaData(&files_meta);
+ for (auto file : files_meta) {
+ files_after_compact.push_back(file.name);
+ }
+
+ // Like nothing happened
+ ASSERT_EQ(files_before_compact, files_after_compact);
+ ASSERT_EQ(manual_compactions_paused, 1);
+
+ manual_compactions_paused = 0;
+ // Now make sure CompactFiles also not run
+ dbfull()->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(),
+ files_before_compact, 0);
+ // Wait for manual compaction to get scheduled and finish
+ dbfull()->TEST_WaitForCompact(true);
+
+ files_meta.clear();
+ files_after_compact.clear();
+ dbfull()->GetLiveFilesMetaData(&files_meta);
+ for (auto file : files_meta) {
+ files_after_compact.push_back(file.name);
+ }
+
+ ASSERT_EQ(files_before_compact, files_after_compact);
+ // CompactFiles returns at entry point
+ ASSERT_EQ(manual_compactions_paused, 0);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+// PausingManualCompaction does not affect auto compaction
+TEST_F(DBTest2, PausingManualCompaction2) {
+ Options options = CurrentOptions();
+ options.level0_file_num_compaction_trigger = 2;
+ options.disable_auto_compactions = false;
+
+ DestroyAndReopen(options);
+ dbfull()->DisableManualCompaction();
+
+ Random rnd(301);
+ for (int i = 0; i < 2; i++) {
+ // Generate a file containing 10 keys.
+ for (int j = 0; j < 100; j++) {
+ ASSERT_OK(Put(Key(j), RandomString(&rnd, 50)));
+ }
+ ASSERT_OK(Flush());
+ }
+ ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
+
+ std::vector<LiveFileMetaData> files_meta;
+ dbfull()->GetLiveFilesMetaData(&files_meta);
+ ASSERT_EQ(files_meta.size(), 1);
+}
+
+TEST_F(DBTest2, PausingManualCompaction3) {
+ CompactRangeOptions compact_options;
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.num_levels = 7;
+
+ Random rnd(301);
+ auto generate_files = [&]() {
+ for (int i = 0; i < options.num_levels; i++) {
+ for (int j = 0; j < options.num_levels - i + 1; j++) {
+ for (int k = 0; k < 1000; k++) {
+ ASSERT_OK(Put(Key(k + j * 1000), RandomString(&rnd, 50)));
+ }
+ Flush();
+ }
+
+ for (int l = 1; l < options.num_levels - i; l++) {
+ MoveFilesToLevel(l);
+ }
+ }
+ };
+
+ DestroyAndReopen(options);
+ generate_files();
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ int run_manual_compactions = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run():PausingManualCompaction:1",
+ [&](void* /*arg*/) { run_manual_compactions++; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ dbfull()->DisableManualCompaction();
+ dbfull()->CompactRange(compact_options, nullptr, nullptr);
+ dbfull()->TEST_WaitForCompact(true);
+ // As manual compaction disabled, not even reach sync point
+ ASSERT_EQ(run_manual_compactions, 0);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
+ "CompactionJob::Run():PausingManualCompaction:1");
+ dbfull()->EnableManualCompaction();
+ dbfull()->CompactRange(compact_options, nullptr, nullptr);
+ dbfull()->TEST_WaitForCompact(true);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, PausingManualCompaction4) {
+ CompactRangeOptions compact_options;
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.num_levels = 7;
+
+ Random rnd(301);
+ auto generate_files = [&]() {
+ for (int i = 0; i < options.num_levels; i++) {
+ for (int j = 0; j < options.num_levels - i + 1; j++) {
+ for (int k = 0; k < 1000; k++) {
+ ASSERT_OK(Put(Key(k + j * 1000), RandomString(&rnd, 50)));
+ }
+ Flush();
+ }
+
+ for (int l = 1; l < options.num_levels - i; l++) {
+ MoveFilesToLevel(l);
+ }
+ }
+ };
+
+ DestroyAndReopen(options);
+ generate_files();
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ int run_manual_compactions = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run():PausingManualCompaction:2", [&](void* arg) {
+ auto paused = reinterpret_cast<std::atomic<bool>*>(arg);
+ ASSERT_FALSE(paused->load(std::memory_order_acquire));
+ paused->store(true, std::memory_order_release);
+ run_manual_compactions++;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ dbfull()->EnableManualCompaction();
+ dbfull()->CompactRange(compact_options, nullptr, nullptr);
+ dbfull()->TEST_WaitForCompact(true);
+ ASSERT_EQ(run_manual_compactions, 1);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
+ "CompactionJob::Run():PausingManualCompaction:2");
+ dbfull()->EnableManualCompaction();
+ dbfull()->CompactRange(compact_options, nullptr, nullptr);
+ dbfull()->TEST_WaitForCompact(true);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, OptimizeForPointLookup) {
+ Options options = CurrentOptions();
+ Close();
+ options.OptimizeForPointLookup(2);
+ ASSERT_OK(DB::Open(options, dbname_, &db_));
+
+ ASSERT_OK(Put("foo", "v1"));
+ ASSERT_EQ("v1", Get("foo"));
+ Flush();
+ ASSERT_EQ("v1", Get("foo"));
+}
+
+TEST_F(DBTest2, OptimizeForSmallDB) {
+ Options options = CurrentOptions();
+ Close();
+ options.OptimizeForSmallDb();
+
+ // Find the cache object
+ ASSERT_EQ(std::string(BlockBasedTableFactory::kName),
+ std::string(options.table_factory->Name()));
+ BlockBasedTableOptions* table_options =
+ reinterpret_cast<BlockBasedTableOptions*>(
+ options.table_factory->GetOptions());
+ ASSERT_TRUE(table_options != nullptr);
+ std::shared_ptr<Cache> cache = table_options->block_cache;
+
+ ASSERT_EQ(0, cache->GetUsage());
+ ASSERT_OK(DB::Open(options, dbname_, &db_));
+ ASSERT_OK(Put("foo", "v1"));
+
+ // memtable size is costed to the block cache
+ ASSERT_NE(0, cache->GetUsage());
+
+ ASSERT_EQ("v1", Get("foo"));
+ Flush();
+
+ size_t prev_size = cache->GetUsage();
+ // Remember block cache size, so that we can find that
+ // it is filled after Get().
+ // Use pinnable slice so that it can ping the block so that
+ // when we check the size it is not evicted.
+ PinnableSlice value;
+ ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), "foo", &value));
+ ASSERT_GT(cache->GetUsage(), prev_size);
+ value.Reset();
+}
+
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTest2, GetRaceFlush1) {
+ ASSERT_OK(Put("foo", "v1"));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBImpl::GetImpl:1", "DBTest2::GetRaceFlush:1"},
+ {"DBTest2::GetRaceFlush:2", "DBImpl::GetImpl:2"}});
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread t1([&] {
+ TEST_SYNC_POINT("DBTest2::GetRaceFlush:1");
+ ASSERT_OK(Put("foo", "v2"));
+ Flush();
+ TEST_SYNC_POINT("DBTest2::GetRaceFlush:2");
+ });
+
+ // Get() is issued after the first Put(), so it should see either
+ // "v1" or "v2".
+ ASSERT_NE("NOT_FOUND", Get("foo"));
+ t1.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, GetRaceFlush2) {
+ ASSERT_OK(Put("foo", "v1"));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBImpl::GetImpl:3", "DBTest2::GetRaceFlush:1"},
+ {"DBTest2::GetRaceFlush:2", "DBImpl::GetImpl:4"}});
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ port::Thread t1([&] {
+ TEST_SYNC_POINT("DBTest2::GetRaceFlush:1");
+ ASSERT_OK(Put("foo", "v2"));
+ Flush();
+ TEST_SYNC_POINT("DBTest2::GetRaceFlush:2");
+ });
+
+ // Get() is issued after the first Put(), so it should see either
+ // "v1" or "v2".
+ ASSERT_NE("NOT_FOUND", Get("foo"));
+ t1.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, DirectIO) {
+ if (!IsDirectIOSupported()) {
+ return;
+ }
+ Options options = CurrentOptions();
+ options.use_direct_reads = options.use_direct_io_for_flush_and_compaction =
+ true;
+ options.allow_mmap_reads = options.allow_mmap_writes = false;
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put(Key(0), "a"));
+ ASSERT_OK(Put(Key(5), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put(Key(10), "a"));
+ ASSERT_OK(Put(Key(15), "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ Reopen(options);
+}
+
+TEST_F(DBTest2, MemtableOnlyIterator) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"pikachu"}, options);
+
+ ASSERT_OK(Put(1, "foo", "first"));
+ ASSERT_OK(Put(1, "bar", "second"));
+
+ ReadOptions ropt;
+ ropt.read_tier = kMemtableTier;
+ std::string value;
+ Iterator* it = nullptr;
+
+ // Before flushing
+ // point lookups
+ ASSERT_OK(db_->Get(ropt, handles_[1], "foo", &value));
+ ASSERT_EQ("first", value);
+ ASSERT_OK(db_->Get(ropt, handles_[1], "bar", &value));
+ ASSERT_EQ("second", value);
+
+ // Memtable-only iterator (read_tier=kMemtableTier); data not flushed yet.
+ it = db_->NewIterator(ropt, handles_[1]);
+ int count = 0;
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ ASSERT_TRUE(it->Valid());
+ count++;
+ }
+ ASSERT_TRUE(!it->Valid());
+ ASSERT_EQ(2, count);
+ delete it;
+
+ Flush(1);
+
+ // After flushing
+ // point lookups
+ ASSERT_OK(db_->Get(ropt, handles_[1], "foo", &value));
+ ASSERT_EQ("first", value);
+ ASSERT_OK(db_->Get(ropt, handles_[1], "bar", &value));
+ ASSERT_EQ("second", value);
+ // nothing should be returned using memtable-only iterator after flushing.
+ it = db_->NewIterator(ropt, handles_[1]);
+ count = 0;
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ ASSERT_TRUE(it->Valid());
+ count++;
+ }
+ ASSERT_TRUE(!it->Valid());
+ ASSERT_EQ(0, count);
+ delete it;
+
+ // Add a key to memtable
+ ASSERT_OK(Put(1, "foobar", "third"));
+ it = db_->NewIterator(ropt, handles_[1]);
+ count = 0;
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ("foobar", it->key().ToString());
+ ASSERT_EQ("third", it->value().ToString());
+ count++;
+ }
+ ASSERT_TRUE(!it->Valid());
+ ASSERT_EQ(1, count);
+ delete it;
+}
+
+TEST_F(DBTest2, LowPriWrite) {
+ Options options = CurrentOptions();
+ // Compaction pressure should trigger since 6 files
+ options.level0_file_num_compaction_trigger = 4;
+ options.level0_slowdown_writes_trigger = 12;
+ options.level0_stop_writes_trigger = 30;
+ options.delayed_write_rate = 8 * 1024 * 1024;
+ Reopen(options);
+
+ std::atomic<int> rate_limit_count(0);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GenericRateLimiter::Request:1", [&](void* arg) {
+ rate_limit_count.fetch_add(1);
+ int64_t* rate_bytes_per_sec = static_cast<int64_t*>(arg);
+ ASSERT_EQ(1024 * 1024, *rate_bytes_per_sec);
+ });
+ // Block compaction
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"DBTest.LowPriWrite:0", "DBImpl::BGWorkCompaction"},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ WriteOptions wo;
+ for (int i = 0; i < 6; i++) {
+ wo.low_pri = false;
+ Put("", "", wo);
+ wo.low_pri = true;
+ Put("", "", wo);
+ Flush();
+ }
+ ASSERT_EQ(0, rate_limit_count.load());
+ wo.low_pri = true;
+ Put("", "", wo);
+ ASSERT_EQ(1, rate_limit_count.load());
+ wo.low_pri = false;
+ Put("", "", wo);
+ ASSERT_EQ(1, rate_limit_count.load());
+
+ TEST_SYNC_POINT("DBTest.LowPriWrite:0");
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ dbfull()->TEST_WaitForCompact();
+ wo.low_pri = true;
+ Put("", "", wo);
+ ASSERT_EQ(1, rate_limit_count.load());
+ wo.low_pri = false;
+ Put("", "", wo);
+ ASSERT_EQ(1, rate_limit_count.load());
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, RateLimitedCompactionReads) {
+ // compaction input has 512KB data
+ const int kNumKeysPerFile = 128;
+ const int kBytesPerKey = 1024;
+ const int kNumL0Files = 4;
+
+ for (auto use_direct_io : {false, true}) {
+ if (use_direct_io && !IsDirectIOSupported()) {
+ continue;
+ }
+ Options options = CurrentOptions();
+ options.compression = kNoCompression;
+ options.level0_file_num_compaction_trigger = kNumL0Files;
+ options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
+ options.new_table_reader_for_compaction_inputs = true;
+ // takes roughly one second, split into 100 x 10ms intervals. Each interval
+ // permits 5.12KB, which is smaller than the block size, so this test
+ // exercises the code for chunking reads.
+ options.rate_limiter.reset(NewGenericRateLimiter(
+ static_cast<int64_t>(kNumL0Files * kNumKeysPerFile *
+ kBytesPerKey) /* rate_bytes_per_sec */,
+ 10 * 1000 /* refill_period_us */, 10 /* fairness */,
+ RateLimiter::Mode::kReadsOnly));
+ options.use_direct_reads = options.use_direct_io_for_flush_and_compaction =
+ use_direct_io;
+ BlockBasedTableOptions bbto;
+ bbto.block_size = 16384;
+ bbto.no_block_cache = true;
+ options.table_factory.reset(new BlockBasedTableFactory(bbto));
+ DestroyAndReopen(options);
+
+ for (int i = 0; i < kNumL0Files; ++i) {
+ for (int j = 0; j <= kNumKeysPerFile; ++j) {
+ ASSERT_OK(Put(Key(j), DummyString(kBytesPerKey)));
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
+ }
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+
+ ASSERT_EQ(0, options.rate_limiter->GetTotalBytesThrough(Env::IO_HIGH));
+ // should be slightly above 512KB due to non-data blocks read. Arbitrarily
+ // chose 1MB as the upper bound on the total bytes read.
+ size_t rate_limited_bytes =
+ options.rate_limiter->GetTotalBytesThrough(Env::IO_LOW);
+ // Include the explicit prefetch of the footer in direct I/O case.
+ size_t direct_io_extra = use_direct_io ? 512 * 1024 : 0;
+ ASSERT_GE(
+ rate_limited_bytes,
+ static_cast<size_t>(kNumKeysPerFile * kBytesPerKey * kNumL0Files));
+ ASSERT_LT(
+ rate_limited_bytes,
+ static_cast<size_t>(2 * kNumKeysPerFile * kBytesPerKey * kNumL0Files +
+ direct_io_extra));
+
+ Iterator* iter = db_->NewIterator(ReadOptions());
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ ASSERT_EQ(iter->value().ToString(), DummyString(kBytesPerKey));
+ }
+ delete iter;
+ // bytes read for user iterator shouldn't count against the rate limit.
+ ASSERT_EQ(rate_limited_bytes,
+ static_cast<size_t>(
+ options.rate_limiter->GetTotalBytesThrough(Env::IO_LOW)));
+ }
+}
+#endif // ROCKSDB_LITE
+
+// Make sure DB can be reopen with reduced number of levels, given no file
+// is on levels higher than the new num_levels.
+TEST_F(DBTest2, ReduceLevel) {
+ Options options;
+ options.disable_auto_compactions = true;
+ options.num_levels = 7;
+ Reopen(options);
+ Put("foo", "bar");
+ Flush();
+ MoveFilesToLevel(6);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ CompactRangeOptions compact_options;
+ compact_options.change_level = true;
+ compact_options.target_level = 1;
+ dbfull()->CompactRange(compact_options, nullptr, nullptr);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,1", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ options.num_levels = 3;
+ Reopen(options);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,1", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+}
+
+// Test that ReadCallback is actually used in both memtbale and sst tables
+TEST_F(DBTest2, ReadCallbackTest) {
+ Options options;
+ options.disable_auto_compactions = true;
+ options.num_levels = 7;
+ Reopen(options);
+ std::vector<const Snapshot*> snapshots;
+ // Try to create a db with multiple layers and a memtable
+ const std::string key = "foo";
+ const std::string value = "bar";
+ // This test assumes that the seq start with 1 and increased by 1 after each
+ // write batch of size 1. If that behavior changes, the test needs to be
+ // updated as well.
+ // TODO(myabandeh): update this test to use the seq number that is returned by
+ // the DB instead of assuming what seq the DB used.
+ int i = 1;
+ for (; i < 10; i++) {
+ Put(key, value + std::to_string(i));
+ // Take a snapshot to avoid the value being removed during compaction
+ auto snapshot = dbfull()->GetSnapshot();
+ snapshots.push_back(snapshot);
+ }
+ Flush();
+ for (; i < 20; i++) {
+ Put(key, value + std::to_string(i));
+ // Take a snapshot to avoid the value being removed during compaction
+ auto snapshot = dbfull()->GetSnapshot();
+ snapshots.push_back(snapshot);
+ }
+ Flush();
+ MoveFilesToLevel(6);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ for (; i < 30; i++) {
+ Put(key, value + std::to_string(i));
+ auto snapshot = dbfull()->GetSnapshot();
+ snapshots.push_back(snapshot);
+ }
+ Flush();
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("1,0,0,0,0,0,2", FilesPerLevel());
+#endif // !ROCKSDB_LITE
+ // And also add some values to the memtable
+ for (; i < 40; i++) {
+ Put(key, value + std::to_string(i));
+ auto snapshot = dbfull()->GetSnapshot();
+ snapshots.push_back(snapshot);
+ }
+
+ class TestReadCallback : public ReadCallback {
+ public:
+ explicit TestReadCallback(SequenceNumber snapshot)
+ : ReadCallback(snapshot), snapshot_(snapshot) {}
+ bool IsVisibleFullCheck(SequenceNumber seq) override {
+ return seq <= snapshot_;
+ }
+
+ private:
+ SequenceNumber snapshot_;
+ };
+
+ for (int seq = 1; seq < i; seq++) {
+ PinnableSlice pinnable_val;
+ ReadOptions roptions;
+ TestReadCallback callback(seq);
+ bool dont_care = true;
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = dbfull()->DefaultColumnFamily();
+ get_impl_options.value = &pinnable_val;
+ get_impl_options.value_found = &dont_care;
+ get_impl_options.callback = &callback;
+ Status s = dbfull()->GetImpl(roptions, key, get_impl_options);
+ ASSERT_TRUE(s.ok());
+ // Assuming that after each Put the DB increased seq by one, the value and
+ // seq number must be equal since we also inc value by 1 after each Put.
+ ASSERT_EQ(value + std::to_string(seq), pinnable_val.ToString());
+ }
+
+ for (auto snapshot : snapshots) {
+ dbfull()->ReleaseSnapshot(snapshot);
+ }
+}
+
+#ifndef ROCKSDB_LITE
+
+TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
+ // Regression test for race condition where an obsolete file is returned to
+ // user as a "live file" but then deleted, all while file deletions are
+ // disabled.
+ //
+ // It happened like this:
+ //
+ // 1. [flush thread] Log file "x.log" found by FindObsoleteFiles
+ // 2. [user thread] DisableFileDeletions, GetSortedWalFiles are called and the
+ // latter returned "x.log"
+ // 3. [flush thread] PurgeObsoleteFiles deleted "x.log"
+ // 4. [user thread] Reading "x.log" failed
+ //
+ // Unfortunately the only regression test I can come up with involves sleep.
+ // We cannot set SyncPoints to repro since, once the fix is applied, the
+ // SyncPoints would cause a deadlock as the repro's sequence of events is now
+ // prohibited.
+ //
+ // Instead, if we sleep for a second between Find and Purge, and ensure the
+ // read attempt happens after purge, then the sequence of events will almost
+ // certainly happen on the old code.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"DBImpl::BackgroundCallFlush:FilesFound",
+ "DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered"},
+ {"DBImpl::PurgeObsoleteFiles:End",
+ "DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured"},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::PurgeObsoleteFiles:Begin",
+ [&](void* /*arg*/) { env_->SleepForMicroseconds(1000000); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Put("key", "val");
+ FlushOptions flush_opts;
+ flush_opts.wait = false;
+ db_->Flush(flush_opts);
+ TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered");
+
+ db_->DisableFileDeletions();
+ VectorLogPtr log_files;
+ db_->GetSortedWalFiles(log_files);
+ TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured");
+ for (const auto& log_file : log_files) {
+ ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber())));
+ }
+
+ db_->EnableFileDeletions();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, TestNumPread) {
+ Options options = CurrentOptions();
+ // disable block cache
+ BlockBasedTableOptions table_options;
+ table_options.no_block_cache = true;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ Reopen(options);
+ env_->count_random_reads_ = true;
+
+ env_->random_file_open_counter_.store(0);
+ ASSERT_OK(Put("bar", "foo"));
+ ASSERT_OK(Put("foo", "bar"));
+ ASSERT_OK(Flush());
+ // After flush, we'll open the file and read footer, meta block,
+ // property block and index block.
+ ASSERT_EQ(4, env_->random_read_counter_.Read());
+ ASSERT_EQ(1, env_->random_file_open_counter_.load());
+
+ // One pread per a normal data block read
+ env_->random_file_open_counter_.store(0);
+ env_->random_read_counter_.Reset();
+ ASSERT_EQ("bar", Get("foo"));
+ ASSERT_EQ(1, env_->random_read_counter_.Read());
+ // All files are already opened.
+ ASSERT_EQ(0, env_->random_file_open_counter_.load());
+
+ env_->random_file_open_counter_.store(0);
+ env_->random_read_counter_.Reset();
+ ASSERT_OK(Put("bar2", "foo2"));
+ ASSERT_OK(Put("foo2", "bar2"));
+ ASSERT_OK(Flush());
+ // After flush, we'll open the file and read footer, meta block,
+ // property block and index block.
+ ASSERT_EQ(4, env_->random_read_counter_.Read());
+ ASSERT_EQ(1, env_->random_file_open_counter_.load());
+
+ // Compaction needs two input blocks, which requires 2 preads, and
+ // generate a new SST file which needs 4 preads (footer, meta block,
+ // property block and index block). In total 6.
+ env_->random_file_open_counter_.store(0);
+ env_->random_read_counter_.Reset();
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_EQ(6, env_->random_read_counter_.Read());
+ // All compactin input files should have already been opened.
+ ASSERT_EQ(1, env_->random_file_open_counter_.load());
+
+ // One pread per a normal data block read
+ env_->random_file_open_counter_.store(0);
+ env_->random_read_counter_.Reset();
+ ASSERT_EQ("foo2", Get("bar2"));
+ ASSERT_EQ(1, env_->random_read_counter_.Read());
+ // SST files are already opened.
+ ASSERT_EQ(0, env_->random_file_open_counter_.load());
+}
+
+TEST_F(DBTest2, TraceAndReplay) {
+ Options options = CurrentOptions();
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ ReadOptions ro;
+ WriteOptions wo;
+ TraceOptions trace_opts;
+ EnvOptions env_opts;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+ Iterator* single_iter = nullptr;
+
+ ASSERT_TRUE(db_->EndTrace().IsIOError());
+
+ std::string trace_filename = dbname_ + "/rocksdb.trace";
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
+ ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
+
+ ASSERT_OK(Put(0, "a", "1"));
+ ASSERT_OK(Merge(0, "b", "2"));
+ ASSERT_OK(Delete(0, "c"));
+ ASSERT_OK(SingleDelete(0, "d"));
+ ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f"));
+
+ WriteBatch batch;
+ ASSERT_OK(batch.Put("f", "11"));
+ ASSERT_OK(batch.Merge("g", "12"));
+ ASSERT_OK(batch.Delete("h"));
+ ASSERT_OK(batch.SingleDelete("i"));
+ ASSERT_OK(batch.DeleteRange("j", "k"));
+ ASSERT_OK(db_->Write(wo, &batch));
+
+ single_iter = db_->NewIterator(ro);
+ single_iter->Seek("f");
+ single_iter->SeekForPrev("g");
+ delete single_iter;
+
+ ASSERT_EQ("1", Get(0, "a"));
+ ASSERT_EQ("12", Get(0, "g"));
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "rocksdb", "rocks"));
+ ASSERT_EQ("NOT_FOUND", Get(1, "leveldb"));
+
+ ASSERT_OK(db_->EndTrace());
+ // These should not get into the trace file as it is after EndTrace.
+ Put("hello", "world");
+ Merge("foo", "bar");
+
+ // Open another db, replay, and verify the data
+ std::string value;
+ std::string dbname2 = test::TmpDir(env_) + "/db_replay";
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ // Using a different name than db2, to pacify infer's use-after-lifetime
+ // warnings (http://fbinfer.com).
+ DB* db2_init = nullptr;
+ options.create_if_missing = true;
+ ASSERT_OK(DB::Open(options, dbname2, &db2_init));
+ ColumnFamilyHandle* cf;
+ ASSERT_OK(
+ db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
+ delete cf;
+ delete db2_init;
+
+ DB* db2 = nullptr;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ ColumnFamilyOptions cf_options;
+ cf_options.merge_operator = MergeOperators::CreatePutOperator();
+ column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
+ column_families.push_back(
+ ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
+
+ env_->SleepForMicroseconds(100);
+ // Verify that the keys don't already exist
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
+
+ std::unique_ptr<TraceReader> trace_reader;
+ ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
+ Replayer replayer(db2, handles_, std::move(trace_reader));
+ ASSERT_OK(replayer.Replay());
+
+ ASSERT_OK(db2->Get(ro, handles[0], "a", &value));
+ ASSERT_EQ("1", value);
+ ASSERT_OK(db2->Get(ro, handles[0], "g", &value));
+ ASSERT_EQ("12", value);
+ ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound());
+
+ ASSERT_OK(db2->Get(ro, handles[1], "foo", &value));
+ ASSERT_EQ("bar", value);
+ ASSERT_OK(db2->Get(ro, handles[1], "rocksdb", &value));
+ ASSERT_EQ("rocks", value);
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+ delete db2;
+ ASSERT_OK(DestroyDB(dbname2, options));
+}
+
+TEST_F(DBTest2, TraceWithLimit) {
+ Options options = CurrentOptions();
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ ReadOptions ro;
+ WriteOptions wo;
+ TraceOptions trace_opts;
+ EnvOptions env_opts;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+
+ // test the max trace file size options
+ trace_opts.max_trace_file_size = 5;
+ std::string trace_filename = dbname_ + "/rocksdb.trace1";
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
+ ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
+ ASSERT_OK(Put(0, "a", "1"));
+ ASSERT_OK(Put(0, "b", "1"));
+ ASSERT_OK(Put(0, "c", "1"));
+ ASSERT_OK(db_->EndTrace());
+
+ std::string dbname2 = test::TmpDir(env_) + "/db_replay2";
+ std::string value;
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ // Using a different name than db2, to pacify infer's use-after-lifetime
+ // warnings (http://fbinfer.com).
+ DB* db2_init = nullptr;
+ options.create_if_missing = true;
+ ASSERT_OK(DB::Open(options, dbname2, &db2_init));
+ ColumnFamilyHandle* cf;
+ ASSERT_OK(
+ db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
+ delete cf;
+ delete db2_init;
+
+ DB* db2 = nullptr;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ ColumnFamilyOptions cf_options;
+ cf_options.merge_operator = MergeOperators::CreatePutOperator();
+ column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
+ column_families.push_back(
+ ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
+
+ env_->SleepForMicroseconds(100);
+ // Verify that the keys don't already exist
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
+
+ std::unique_ptr<TraceReader> trace_reader;
+ ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
+ Replayer replayer(db2, handles_, std::move(trace_reader));
+ ASSERT_OK(replayer.Replay());
+
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+ delete db2;
+ ASSERT_OK(DestroyDB(dbname2, options));
+}
+
+TEST_F(DBTest2, TraceWithSampling) {
+ Options options = CurrentOptions();
+ ReadOptions ro;
+ WriteOptions wo;
+ TraceOptions trace_opts;
+ EnvOptions env_opts;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+
+ // test the trace file sampling options
+ trace_opts.sampling_frequency = 2;
+ std::string trace_filename = dbname_ + "/rocksdb.trace_sampling";
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
+ ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
+ ASSERT_OK(Put(0, "a", "1"));
+ ASSERT_OK(Put(0, "b", "2"));
+ ASSERT_OK(Put(0, "c", "3"));
+ ASSERT_OK(Put(0, "d", "4"));
+ ASSERT_OK(Put(0, "e", "5"));
+ ASSERT_OK(db_->EndTrace());
+
+ std::string dbname2 = test::TmpDir(env_) + "/db_replay_sampling";
+ std::string value;
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ // Using a different name than db2, to pacify infer's use-after-lifetime
+ // warnings (http://fbinfer.com).
+ DB* db2_init = nullptr;
+ options.create_if_missing = true;
+ ASSERT_OK(DB::Open(options, dbname2, &db2_init));
+ ColumnFamilyHandle* cf;
+ ASSERT_OK(
+ db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
+ delete cf;
+ delete db2_init;
+
+ DB* db2 = nullptr;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ ColumnFamilyOptions cf_options;
+ column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
+ column_families.push_back(
+ ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
+
+ env_->SleepForMicroseconds(100);
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "d", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound());
+
+ std::unique_ptr<TraceReader> trace_reader;
+ ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
+ Replayer replayer(db2, handles_, std::move(trace_reader));
+ ASSERT_OK(replayer.Replay());
+
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_FALSE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
+ ASSERT_FALSE(db2->Get(ro, handles[0], "d", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound());
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+ delete db2;
+ ASSERT_OK(DestroyDB(dbname2, options));
+}
+
+TEST_F(DBTest2, TraceWithFilter) {
+ Options options = CurrentOptions();
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ ReadOptions ro;
+ WriteOptions wo;
+ TraceOptions trace_opts;
+ EnvOptions env_opts;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ Random rnd(301);
+ Iterator* single_iter = nullptr;
+
+ trace_opts.filter = TraceFilterType::kTraceFilterWrite;
+
+ std::string trace_filename = dbname_ + "/rocksdb.trace";
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
+ ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
+
+ ASSERT_OK(Put(0, "a", "1"));
+ ASSERT_OK(Merge(0, "b", "2"));
+ ASSERT_OK(Delete(0, "c"));
+ ASSERT_OK(SingleDelete(0, "d"));
+ ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f"));
+
+ WriteBatch batch;
+ ASSERT_OK(batch.Put("f", "11"));
+ ASSERT_OK(batch.Merge("g", "12"));
+ ASSERT_OK(batch.Delete("h"));
+ ASSERT_OK(batch.SingleDelete("i"));
+ ASSERT_OK(batch.DeleteRange("j", "k"));
+ ASSERT_OK(db_->Write(wo, &batch));
+
+ single_iter = db_->NewIterator(ro);
+ single_iter->Seek("f");
+ single_iter->SeekForPrev("g");
+ delete single_iter;
+
+ ASSERT_EQ("1", Get(0, "a"));
+ ASSERT_EQ("12", Get(0, "g"));
+
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Put(1, "rocksdb", "rocks"));
+ ASSERT_EQ("NOT_FOUND", Get(1, "leveldb"));
+
+ ASSERT_OK(db_->EndTrace());
+ // These should not get into the trace file as it is after EndTrace.
+ Put("hello", "world");
+ Merge("foo", "bar");
+
+ // Open another db, replay, and verify the data
+ std::string value;
+ std::string dbname2 = test::TmpDir(env_) + "/db_replay";
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ // Using a different name than db2, to pacify infer's use-after-lifetime
+ // warnings (http://fbinfer.com).
+ DB* db2_init = nullptr;
+ options.create_if_missing = true;
+ ASSERT_OK(DB::Open(options, dbname2, &db2_init));
+ ColumnFamilyHandle* cf;
+ ASSERT_OK(
+ db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
+ delete cf;
+ delete db2_init;
+
+ DB* db2 = nullptr;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ ColumnFamilyOptions cf_options;
+ cf_options.merge_operator = MergeOperators::CreatePutOperator();
+ column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
+ column_families.push_back(
+ ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
+
+ env_->SleepForMicroseconds(100);
+ // Verify that the keys don't already exist
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
+
+ std::unique_ptr<TraceReader> trace_reader;
+ ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
+ Replayer replayer(db2, handles_, std::move(trace_reader));
+ ASSERT_OK(replayer.Replay());
+
+ // All the key-values should not present since we filter out the WRITE ops.
+ ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "foo", &value).IsNotFound());
+ ASSERT_TRUE(db2->Get(ro, handles[0], "rocksdb", &value).IsNotFound());
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+ delete db2;
+ ASSERT_OK(DestroyDB(dbname2, options));
+
+ // Set up a new db.
+ std::string dbname3 = test::TmpDir(env_) + "/db_not_trace_read";
+ ASSERT_OK(DestroyDB(dbname3, options));
+
+ DB* db3_init = nullptr;
+ options.create_if_missing = true;
+ ColumnFamilyHandle* cf3;
+ ASSERT_OK(DB::Open(options, dbname3, &db3_init));
+ ASSERT_OK(
+ db3_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf3));
+ delete cf3;
+ delete db3_init;
+
+ column_families.clear();
+ column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
+ column_families.push_back(
+ ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
+ handles.clear();
+
+ DB* db3 = nullptr;
+ ASSERT_OK(DB::Open(DBOptions(), dbname3, column_families, &handles, &db3));
+
+ env_->SleepForMicroseconds(100);
+ // Verify that the keys don't already exist
+ ASSERT_TRUE(db3->Get(ro, handles[0], "a", &value).IsNotFound());
+ ASSERT_TRUE(db3->Get(ro, handles[0], "g", &value).IsNotFound());
+
+ //The tracer will not record the READ ops.
+ trace_opts.filter = TraceFilterType::kTraceFilterGet;
+ std::string trace_filename3 = dbname_ + "/rocksdb.trace_3";
+ std::unique_ptr<TraceWriter> trace_writer3;
+ ASSERT_OK(
+ NewFileTraceWriter(env_, env_opts, trace_filename3, &trace_writer3));
+ ASSERT_OK(db3->StartTrace(trace_opts, std::move(trace_writer3)));
+
+ ASSERT_OK(db3->Put(wo, handles[0], "a", "1"));
+ ASSERT_OK(db3->Merge(wo, handles[0], "b", "2"));
+ ASSERT_OK(db3->Delete(wo, handles[0], "c"));
+ ASSERT_OK(db3->SingleDelete(wo, handles[0], "d"));
+
+ ASSERT_OK(db3->Get(ro, handles[0], "a", &value));
+ ASSERT_EQ(value, "1");
+ ASSERT_TRUE(db3->Get(ro, handles[0], "c", &value).IsNotFound());
+
+ ASSERT_OK(db3->EndTrace());
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+ delete db3;
+ ASSERT_OK(DestroyDB(dbname3, options));
+
+ std::unique_ptr<TraceReader> trace_reader3;
+ ASSERT_OK(
+ NewFileTraceReader(env_, env_opts, trace_filename3, &trace_reader3));
+
+ // Count the number of records in the trace file;
+ int count = 0;
+ std::string data;
+ Status s;
+ while (true) {
+ s = trace_reader3->Read(&data);
+ if (!s.ok()) {
+ break;
+ }
+ count += 1;
+ }
+ // We also need to count the header and footer
+ // 4 WRITE + HEADER + FOOTER = 6
+ ASSERT_EQ(count, 6);
+}
+
+#endif // ROCKSDB_LITE
+
+TEST_F(DBTest2, PinnableSliceAndMmapReads) {
+ Options options = CurrentOptions();
+ options.allow_mmap_reads = true;
+ options.max_open_files = 100;
+ options.compression = kNoCompression;
+ Reopen(options);
+
+ ASSERT_OK(Put("foo", "bar"));
+ ASSERT_OK(Flush());
+
+ PinnableSlice pinned_value;
+ ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
+ // It is not safe to pin mmap files as they might disappear by compaction
+ ASSERT_FALSE(pinned_value.IsPinned());
+ ASSERT_EQ(pinned_value.ToString(), "bar");
+
+ dbfull()->TEST_CompactRange(0 /* level */, nullptr /* begin */,
+ nullptr /* end */, nullptr /* column_family */,
+ true /* disallow_trivial_move */);
+
+ // Ensure pinned_value doesn't rely on memory munmap'd by the above
+ // compaction. It crashes if it does.
+ ASSERT_EQ(pinned_value.ToString(), "bar");
+
+#ifndef ROCKSDB_LITE
+ pinned_value.Reset();
+ // Unsafe to pin mmap files when they could be kicked out of table cache
+ Close();
+ ASSERT_OK(ReadOnlyReopen(options));
+ ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
+ ASSERT_FALSE(pinned_value.IsPinned());
+ ASSERT_EQ(pinned_value.ToString(), "bar");
+
+ pinned_value.Reset();
+ // In read-only mode with infinite capacity on table cache it should pin the
+ // value and avoid the memcpy
+ Close();
+ options.max_open_files = -1;
+ ASSERT_OK(ReadOnlyReopen(options));
+ ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
+ ASSERT_TRUE(pinned_value.IsPinned());
+ ASSERT_EQ(pinned_value.ToString(), "bar");
+#endif
+}
+
+TEST_F(DBTest2, DISABLED_IteratorPinnedMemory) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockBasedTableOptions bbto;
+ bbto.no_block_cache = false;
+ bbto.cache_index_and_filter_blocks = false;
+ bbto.block_cache = NewLRUCache(100000);
+ bbto.block_size = 400; // small block size
+ options.table_factory.reset(new BlockBasedTableFactory(bbto));
+ Reopen(options);
+
+ Random rnd(301);
+ std::string v = RandomString(&rnd, 400);
+
+ // Since v is the size of a block, each key should take a block
+ // of 400+ bytes.
+ Put("1", v);
+ Put("3", v);
+ Put("5", v);
+ Put("7", v);
+ ASSERT_OK(Flush());
+
+ ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage());
+
+ // Verify that iterators don't pin more than one data block in block cache
+ // at each time.
+ {
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
+ iter->SeekToFirst();
+
+ for (int i = 0; i < 4; i++) {
+ ASSERT_TRUE(iter->Valid());
+ // Block cache should contain exactly one block.
+ ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
+ ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
+ iter->Next();
+ }
+ ASSERT_FALSE(iter->Valid());
+
+ iter->Seek("4");
+ ASSERT_TRUE(iter->Valid());
+
+ ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
+ ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
+
+ iter->Seek("3");
+ ASSERT_TRUE(iter->Valid());
+
+ ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
+ ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
+ }
+ ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage());
+
+ // Test compaction case
+ Put("2", v);
+ Put("5", v);
+ Put("6", v);
+ Put("8", v);
+ ASSERT_OK(Flush());
+
+ // Clear existing data in block cache
+ bbto.block_cache->SetCapacity(0);
+ bbto.block_cache->SetCapacity(100000);
+
+ // Verify compaction input iterators don't hold more than one data blocks at
+ // one time.
+ std::atomic<bool> finished(false);
+ std::atomic<int> block_newed(0);
+ std::atomic<int> block_destroyed(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Block::Block:0", [&](void* /*arg*/) {
+ if (finished) {
+ return;
+ }
+ // Two iterators. At most 2 outstanding blocks.
+ EXPECT_GE(block_newed.load(), block_destroyed.load());
+ EXPECT_LE(block_newed.load(), block_destroyed.load() + 1);
+ block_newed.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "Block::~Block", [&](void* /*arg*/) {
+ if (finished) {
+ return;
+ }
+ // Two iterators. At most 2 outstanding blocks.
+ EXPECT_GE(block_newed.load(), block_destroyed.load() + 1);
+ EXPECT_LE(block_newed.load(), block_destroyed.load() + 2);
+ block_destroyed.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "CompactionJob::Run:BeforeVerify",
+ [&](void* /*arg*/) { finished = true; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ // Two input files. Each of them has 4 data blocks.
+ ASSERT_EQ(8, block_newed.load());
+ ASSERT_EQ(8, block_destroyed.load());
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(DBTest2, TestBBTTailPrefetch) {
+ std::atomic<bool> called(false);
+ size_t expected_lower_bound = 512 * 1024;
+ size_t expected_higher_bound = 512 * 1024;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) {
+ size_t* prefetch_size = static_cast<size_t*>(arg);
+ EXPECT_LE(expected_lower_bound, *prefetch_size);
+ EXPECT_GE(expected_higher_bound, *prefetch_size);
+ called = true;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Put("1", "1");
+ Put("9", "1");
+ Flush();
+
+ expected_lower_bound = 0;
+ expected_higher_bound = 8 * 1024;
+
+ Put("1", "1");
+ Put("9", "1");
+ Flush();
+
+ Put("1", "1");
+ Put("9", "1");
+ Flush();
+
+ // Full compaction to make sure there is no L0 file after the open.
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ ASSERT_TRUE(called.load());
+ called = false;
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ std::atomic<bool> first_call(true);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) {
+ size_t* prefetch_size = static_cast<size_t*>(arg);
+ if (first_call) {
+ EXPECT_EQ(4 * 1024, *prefetch_size);
+ first_call = false;
+ } else {
+ EXPECT_GE(4 * 1024, *prefetch_size);
+ }
+ called = true;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Options options = CurrentOptions();
+ options.max_file_opening_threads = 1; // one thread
+ BlockBasedTableOptions table_options;
+ table_options.cache_index_and_filter_blocks = true;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.max_open_files = -1;
+ Reopen(options);
+
+ Put("1", "1");
+ Put("9", "1");
+ Flush();
+
+ Put("1", "1");
+ Put("9", "1");
+ Flush();
+
+ ASSERT_TRUE(called.load());
+ called = false;
+
+ // Parallel loading SST files
+ options.max_file_opening_threads = 16;
+ Reopen(options);
+
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ ASSERT_TRUE(called.load());
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+TEST_F(DBTest2, TestGetColumnFamilyHandleUnlocked) {
+ // Setup sync point dependency to reproduce the race condition of
+ // DBImpl::GetColumnFamilyHandleUnlocked
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked1",
+ "TestGetColumnFamilyHandleUnlocked::PreGetColumnFamilyHandleUnlocked2"},
+ {"TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked2",
+ "TestGetColumnFamilyHandleUnlocked::ReadColumnFamilyHandle1"},
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ CreateColumnFamilies({"test1", "test2"}, Options());
+ ASSERT_EQ(handles_.size(), 2);
+
+ DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
+ port::Thread user_thread1([&]() {
+ auto cfh = dbi->GetColumnFamilyHandleUnlocked(handles_[0]->GetID());
+ ASSERT_EQ(cfh->GetID(), handles_[0]->GetID());
+ TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked1");
+ TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::ReadColumnFamilyHandle1");
+ ASSERT_EQ(cfh->GetID(), handles_[0]->GetID());
+ });
+
+ port::Thread user_thread2([&]() {
+ TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::PreGetColumnFamilyHandleUnlocked2");
+ auto cfh = dbi->GetColumnFamilyHandleUnlocked(handles_[1]->GetID());
+ ASSERT_EQ(cfh->GetID(), handles_[1]->GetID());
+ TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked2");
+ ASSERT_EQ(cfh->GetID(), handles_[1]->GetID());
+ });
+
+ user_thread1.join();
+ user_thread2.join();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, TestCompactFiles) {
+ // Setup sync point dependency to reproduce the race condition of
+ // DBImpl::GetColumnFamilyHandleUnlocked
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"TestCompactFiles::IngestExternalFile1",
+ "TestCompactFiles::IngestExternalFile2"},
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ Options options;
+ options.num_levels = 2;
+ options.disable_auto_compactions = true;
+ Reopen(options);
+ auto* handle = db_->DefaultColumnFamily();
+ ASSERT_EQ(db_->NumberLevels(handle), 2);
+
+ ROCKSDB_NAMESPACE::SstFileWriter sst_file_writer{
+ ROCKSDB_NAMESPACE::EnvOptions(), options};
+ std::string external_file1 = dbname_ + "/test_compact_files1.sst_t";
+ std::string external_file2 = dbname_ + "/test_compact_files2.sst_t";
+ std::string external_file3 = dbname_ + "/test_compact_files3.sst_t";
+
+ ASSERT_OK(sst_file_writer.Open(external_file1));
+ ASSERT_OK(sst_file_writer.Put("1", "1"));
+ ASSERT_OK(sst_file_writer.Put("2", "2"));
+ ASSERT_OK(sst_file_writer.Finish());
+
+ ASSERT_OK(sst_file_writer.Open(external_file2));
+ ASSERT_OK(sst_file_writer.Put("3", "3"));
+ ASSERT_OK(sst_file_writer.Put("4", "4"));
+ ASSERT_OK(sst_file_writer.Finish());
+
+ ASSERT_OK(sst_file_writer.Open(external_file3));
+ ASSERT_OK(sst_file_writer.Put("5", "5"));
+ ASSERT_OK(sst_file_writer.Put("6", "6"));
+ ASSERT_OK(sst_file_writer.Finish());
+
+ ASSERT_OK(db_->IngestExternalFile(handle, {external_file1, external_file3},
+ IngestExternalFileOptions()));
+ ASSERT_EQ(NumTableFilesAtLevel(1, 0), 2);
+ std::vector<std::string> files;
+ GetSstFiles(env_, dbname_, &files);
+ ASSERT_EQ(files.size(), 2);
+
+ port::Thread user_thread1(
+ [&]() { db_->CompactFiles(CompactionOptions(), handle, files, 1); });
+
+ port::Thread user_thread2([&]() {
+ ASSERT_OK(db_->IngestExternalFile(handle, {external_file2},
+ IngestExternalFileOptions()));
+ TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile1");
+ });
+
+ user_thread1.join();
+ user_thread2.join();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+#endif // ROCKSDB_LITE
+
+// TODO: figure out why this test fails in appveyor
+#ifndef OS_WIN
+TEST_F(DBTest2, MultiDBParallelOpenTest) {
+ const int kNumDbs = 2;
+ Options options = CurrentOptions();
+ std::vector<std::string> dbnames;
+ for (int i = 0; i < kNumDbs; ++i) {
+ dbnames.emplace_back(test::TmpDir(env_) + "/db" + ToString(i));
+ ASSERT_OK(DestroyDB(dbnames.back(), options));
+ }
+
+ // Verify empty DBs can be created in parallel
+ std::vector<std::thread> open_threads;
+ std::vector<DB*> dbs{static_cast<unsigned int>(kNumDbs), nullptr};
+ options.create_if_missing = true;
+ for (int i = 0; i < kNumDbs; ++i) {
+ open_threads.emplace_back(
+ [&](int dbnum) {
+ ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum]));
+ },
+ i);
+ }
+
+ // Now add some data and close, so next we can verify non-empty DBs can be
+ // recovered in parallel
+ for (int i = 0; i < kNumDbs; ++i) {
+ open_threads[i].join();
+ ASSERT_OK(dbs[i]->Put(WriteOptions(), "xi", "gua"));
+ delete dbs[i];
+ }
+
+ // Verify non-empty DBs can be recovered in parallel
+ dbs.clear();
+ open_threads.clear();
+ for (int i = 0; i < kNumDbs; ++i) {
+ open_threads.emplace_back(
+ [&](int dbnum) {
+ ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum]));
+ },
+ i);
+ }
+
+ // Wait and cleanup
+ for (int i = 0; i < kNumDbs; ++i) {
+ open_threads[i].join();
+ delete dbs[i];
+ ASSERT_OK(DestroyDB(dbnames[i], options));
+ }
+}
+#endif // OS_WIN
+
+namespace {
+class DummyOldStats : public Statistics {
+ public:
+ uint64_t getTickerCount(uint32_t /*ticker_type*/) const override { return 0; }
+ void recordTick(uint32_t /* ticker_type */, uint64_t /* count */) override {
+ num_rt++;
+ }
+ void setTickerCount(uint32_t /*ticker_type*/, uint64_t /*count*/) override {}
+ uint64_t getAndResetTickerCount(uint32_t /*ticker_type*/) override {
+ return 0;
+ }
+ void measureTime(uint32_t /*histogram_type*/, uint64_t /*count*/) override {
+ num_mt++;
+ }
+ void histogramData(
+ uint32_t /*histogram_type*/,
+ ROCKSDB_NAMESPACE::HistogramData* const /*data*/) const override {}
+ std::string getHistogramString(uint32_t /*type*/) const override {
+ return "";
+ }
+ bool HistEnabledForType(uint32_t /*type*/) const override { return false; }
+ std::string ToString() const override { return ""; }
+ int num_rt = 0;
+ int num_mt = 0;
+};
+} // namespace
+
+TEST_F(DBTest2, OldStatsInterface) {
+ DummyOldStats* dos = new DummyOldStats();
+ std::shared_ptr<Statistics> stats(dos);
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.statistics = stats;
+ Reopen(options);
+
+ Put("foo", "bar");
+ ASSERT_EQ("bar", Get("foo"));
+ ASSERT_OK(Flush());
+ ASSERT_EQ("bar", Get("foo"));
+
+ ASSERT_GT(dos->num_rt, 0);
+ ASSERT_GT(dos->num_mt, 0);
+}
+
+TEST_F(DBTest2, CloseWithUnreleasedSnapshot) {
+ const Snapshot* ss = db_->GetSnapshot();
+
+ for (auto h : handles_) {
+ db_->DestroyColumnFamilyHandle(h);
+ }
+ handles_.clear();
+
+ ASSERT_NOK(db_->Close());
+ db_->ReleaseSnapshot(ss);
+ ASSERT_OK(db_->Close());
+ delete db_;
+ db_ = nullptr;
+}
+
+TEST_F(DBTest2, PrefixBloomReseek) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.prefix_extractor.reset(NewCappedPrefixTransform(3));
+ BlockBasedTableOptions bbto;
+ bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ bbto.whole_key_filtering = false;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ DestroyAndReopen(options);
+
+ // Construct two L1 files with keys:
+ // f1:[aaa1 ccc1] f2:[ddd0]
+ ASSERT_OK(Put("aaa1", ""));
+ ASSERT_OK(Put("ccc1", ""));
+ ASSERT_OK(Flush());
+ ASSERT_OK(Put("ddd0", ""));
+ ASSERT_OK(Flush());
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ ASSERT_OK(Put("bbb1", ""));
+
+ Iterator* iter = db_->NewIterator(ReadOptions());
+
+ // Seeking into f1, the iterator will check bloom filter which returns the
+ // file iterator ot be invalidate, and the cursor will put into f2, with
+ // the next key to be "ddd0".
+ iter->Seek("bbb1");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("bbb1", iter->key().ToString());
+
+ // Reseek ccc1, the L1 iterator needs to go back to f1 and reseek.
+ iter->Seek("ccc1");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("ccc1", iter->key().ToString());
+
+ delete iter;
+}
+
+TEST_F(DBTest2, PrefixBloomFilteredOut) {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.prefix_extractor.reset(NewCappedPrefixTransform(3));
+ BlockBasedTableOptions bbto;
+ bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ bbto.whole_key_filtering = false;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ DestroyAndReopen(options);
+
+ // Construct two L1 files with keys:
+ // f1:[aaa1 ccc1] f2:[ddd0]
+ ASSERT_OK(Put("aaa1", ""));
+ ASSERT_OK(Put("ccc1", ""));
+ ASSERT_OK(Flush());
+ ASSERT_OK(Put("ddd0", ""));
+ ASSERT_OK(Flush());
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ Iterator* iter = db_->NewIterator(ReadOptions());
+
+ // Bloom filter is filterd out by f1.
+ // This is just one of several valid position following the contract.
+ // Postioning to ccc1 or ddd0 is also valid. This is just to validate
+ // the behavior of the current implementation. If underlying implementation
+ // changes, the test might fail here.
+ iter->Seek("bbb1");
+ ASSERT_FALSE(iter->Valid());
+
+ delete iter;
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, RowCacheSnapshot) {
+ Options options = CurrentOptions();
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.row_cache = NewLRUCache(8 * 8192);
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put("foo", "bar1"));
+
+ const Snapshot* s1 = db_->GetSnapshot();
+
+ ASSERT_OK(Put("foo", "bar2"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put("foo2", "bar"));
+ const Snapshot* s2 = db_->GetSnapshot();
+ ASSERT_OK(Put("foo3", "bar"));
+ const Snapshot* s3 = db_->GetSnapshot();
+
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 0);
+ ASSERT_EQ(Get("foo"), "bar2");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
+ ASSERT_EQ(Get("foo"), "bar2");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
+ ASSERT_EQ(Get("foo", s1), "bar1");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
+ ASSERT_EQ(Get("foo", s2), "bar2");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 2);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
+ ASSERT_EQ(Get("foo", s1), "bar1");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 3);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
+ ASSERT_EQ(Get("foo", s3), "bar2");
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 4);
+ ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
+
+ db_->ReleaseSnapshot(s1);
+ db_->ReleaseSnapshot(s2);
+ db_->ReleaseSnapshot(s3);
+}
+#endif // ROCKSDB_LITE
+
+// When DB is reopened with multiple column families, the manifest file
+// is written after the first CF is flushed, and it is written again
+// after each flush. If DB crashes between the flushes, the flushed CF
+// flushed will pass the latest log file, and now we require it not
+// to be corrupted, and triggering a corruption report.
+// We need to fix the bug and enable the test.
+TEST_F(DBTest2, CrashInRecoveryMultipleCF) {
+ const std::vector<std::string> sync_points = {
+ "DBImpl::RecoverLogFiles:BeforeFlushFinalMemtable",
+ "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0"};
+ for (const auto& test_sync_point : sync_points) {
+ Options options = CurrentOptions();
+ // First destroy original db to ensure a clean start.
+ DestroyAndReopen(options);
+ options.create_if_missing = true;
+ options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ ASSERT_OK(Put("foo", "bar"));
+ ASSERT_OK(Flush());
+ ASSERT_OK(Put(1, "foo", "bar"));
+ ASSERT_OK(Flush(1));
+ ASSERT_OK(Put("foo", "bar"));
+ ASSERT_OK(Put(1, "foo", "bar"));
+ // The value is large enough to be divided to two blocks.
+ std::string large_value(400, ' ');
+ ASSERT_OK(Put("foo1", large_value));
+ ASSERT_OK(Put("foo2", large_value));
+ Close();
+
+ // Corrupt the log file in the middle, so that it is not corrupted
+ // in the tail.
+ std::vector<std::string> filenames;
+ ASSERT_OK(env_->GetChildren(dbname_, &filenames));
+ for (const auto& f : filenames) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(f, &number, &type) && type == FileType::kLogFile) {
+ std::string fname = dbname_ + "/" + f;
+ std::string file_content;
+ ASSERT_OK(ReadFileToString(env_, fname, &file_content));
+ file_content[400] = 'h';
+ file_content[401] = 'a';
+ ASSERT_OK(WriteStringToFile(env_, file_content, fname));
+ break;
+ }
+ }
+
+ // Reopen and freeze the file system after the first manifest write.
+ FaultInjectionTestEnv fit_env(options.env);
+ options.env = &fit_env;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ test_sync_point,
+ [&](void* /*arg*/) { fit_env.SetFilesystemActive(false); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ASSERT_NOK(TryReopenWithColumnFamilies(
+ {kDefaultColumnFamilyName, "pikachu"}, options));
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ fit_env.SetFilesystemActive(true);
+ // If we continue using failure ingestion Env, it will conplain something
+ // when renaming current file, which is not expected. Need to investigate
+ // why.
+ options.env = env_;
+ ASSERT_OK(TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"},
+ options));
+ }
+}
+
+TEST_F(DBTest2, SeekFileRangeDeleteTail) {
+ Options options = CurrentOptions();
+ options.prefix_extractor.reset(NewCappedPrefixTransform(1));
+ options.num_levels = 3;
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put("a", "a"));
+ const Snapshot* s1 = db_->GetSnapshot();
+ ASSERT_OK(
+ db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "f"));
+ ASSERT_OK(Put("b", "a"));
+ ASSERT_OK(Flush());
+
+ ASSERT_OK(Put("x", "a"));
+ ASSERT_OK(Put("z", "a"));
+ ASSERT_OK(Flush());
+
+ CompactRangeOptions cro;
+ cro.change_level = true;
+ cro.target_level = 2;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ {
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
+ iter->Seek("e");
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("x", iter->key().ToString());
+ }
+ db_->ReleaseSnapshot(s1);
+}
+
+TEST_F(DBTest2, BackgroundPurgeTest) {
+ Options options = CurrentOptions();
+ options.write_buffer_manager =
+ std::make_shared<ROCKSDB_NAMESPACE::WriteBufferManager>(1 << 20);
+ options.avoid_unnecessary_blocking_io = true;
+ DestroyAndReopen(options);
+ size_t base_value = options.write_buffer_manager->memory_usage();
+
+ ASSERT_OK(Put("a", "a"));
+ Iterator* iter = db_->NewIterator(ReadOptions());
+ ASSERT_OK(Flush());
+ size_t value = options.write_buffer_manager->memory_usage();
+ ASSERT_GT(value, base_value);
+
+ db_->GetEnv()->SetBackgroundThreads(1, Env::Priority::HIGH);
+ test::SleepingBackgroundTask sleeping_task_after;
+ db_->GetEnv()->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
+ &sleeping_task_after, Env::Priority::HIGH);
+ delete iter;
+
+ Env::Default()->SleepForMicroseconds(100000);
+ value = options.write_buffer_manager->memory_usage();
+ ASSERT_GT(value, base_value);
+
+ sleeping_task_after.WakeUp();
+ sleeping_task_after.WaitUntilDone();
+
+ test::SleepingBackgroundTask sleeping_task_after2;
+ db_->GetEnv()->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
+ &sleeping_task_after2, Env::Priority::HIGH);
+ sleeping_task_after2.WakeUp();
+ sleeping_task_after2.WaitUntilDone();
+
+ value = options.write_buffer_manager->memory_usage();
+ ASSERT_EQ(base_value, value);
+}
+
+TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
+ Options options = CurrentOptions();
+ DestroyAndReopen(options);
+ options.max_manifest_file_size = 10;
+ options.create_if_missing = true;
+ CreateAndReopenWithCF({"pikachu"}, options);
+ ASSERT_EQ(2, handles_.size());
+
+ ASSERT_OK(Put("foo", "value"));
+ const int kL0Files = options.level0_file_num_compaction_trigger;
+ for (int i = 0; i < kL0Files; ++i) {
+ ASSERT_OK(Put(/*cf=*/1, "a", std::to_string(i)));
+ ASSERT_OK(Flush(/*cf=*/1));
+ }
+
+ port::Thread thread([&]() { ASSERT_OK(Flush()); });
+ ASSERT_OK(dbfull()->TEST_WaitForCompact());
+ thread.join();
+}
+
+TEST_F(DBTest2, SameSmallestInSameLevel) {
+ // This test validates fractional casacading logic when several files at one
+ // one level only contains the same user key.
+ Options options = CurrentOptions();
+ options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put("key", "1"));
+ ASSERT_OK(Put("key", "2"));
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "3"));
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "4"));
+ Flush();
+ CompactRangeOptions cro;
+ cro.change_level = true;
+ cro.target_level = 2;
+ ASSERT_OK(dbfull()->CompactRange(cro, db_->DefaultColumnFamily(), nullptr,
+ nullptr));
+
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "5"));
+ Flush();
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "6"));
+ Flush();
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "7"));
+ Flush();
+ ASSERT_OK(db_->Merge(WriteOptions(), "key", "8"));
+ Flush();
+ dbfull()->TEST_WaitForCompact(true);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ("0,4,1", FilesPerLevel());
+#endif // ROCKSDB_LITE
+
+ ASSERT_EQ("2,3,4,5,6,7,8", Get("key"));
+}
+
+TEST_F(DBTest2, BlockBasedTablePrefixIndexSeekForPrev) {
+ // create a DB with block prefix index
+ BlockBasedTableOptions table_options;
+ Options options = CurrentOptions();
+ table_options.block_size = 300;
+ table_options.index_type = BlockBasedTableOptions::kHashSearch;
+ table_options.index_shortening =
+ BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+
+ Reopen(options);
+
+ Random rnd(301);
+ std::string large_value = RandomString(&rnd, 500);
+
+ ASSERT_OK(Put("a1", large_value));
+ ASSERT_OK(Put("x1", large_value));
+ ASSERT_OK(Put("y1", large_value));
+ Flush();
+
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ReadOptions()));
+ iterator->SeekForPrev("x3");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("x1", iterator->key().ToString());
+
+ iterator->SeekForPrev("a3");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("a1", iterator->key().ToString());
+
+ iterator->SeekForPrev("y3");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("y1", iterator->key().ToString());
+
+ // Query more than one non-existing prefix to cover the case both
+ // of empty hash bucket and hash bucket conflict.
+ iterator->SeekForPrev("b1");
+ // Result should be not valid or "a1".
+ if (iterator->Valid()) {
+ ASSERT_EQ("a1", iterator->key().ToString());
+ }
+
+ iterator->SeekForPrev("c1");
+ // Result should be not valid or "a1".
+ if (iterator->Valid()) {
+ ASSERT_EQ("a1", iterator->key().ToString());
+ }
+
+ iterator->SeekForPrev("d1");
+ // Result should be not valid or "a1".
+ if (iterator->Valid()) {
+ ASSERT_EQ("a1", iterator->key().ToString());
+ }
+
+ iterator->SeekForPrev("y3");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("y1", iterator->key().ToString());
+ }
+}
+
+TEST_F(DBTest2, ChangePrefixExtractor) {
+ for (bool use_partitioned_filter : {true, false}) {
+ // create a DB with block prefix index
+ BlockBasedTableOptions table_options;
+ Options options = CurrentOptions();
+
+ // Sometimes filter is checked based on upper bound. Assert counters
+ // for that case. Otherwise, only check data correctness.
+#ifndef ROCKSDB_LITE
+ bool expect_filter_check = !use_partitioned_filter;
+#else
+ bool expect_filter_check = false;
+#endif
+ table_options.partition_filters = use_partitioned_filter;
+ if (use_partitioned_filter) {
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ }
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.statistics = CreateDBStatistics();
+
+ options.prefix_extractor.reset(NewFixedPrefixTransform(2));
+ DestroyAndReopen(options);
+
+ Random rnd(301);
+
+ ASSERT_OK(Put("aa", ""));
+ ASSERT_OK(Put("xb", ""));
+ ASSERT_OK(Put("xx1", ""));
+ ASSERT_OK(Put("xz1", ""));
+ ASSERT_OK(Put("zz", ""));
+ Flush();
+
+ // After reopening DB with prefix size 2 => 1, prefix extractor
+ // won't take effective unless it won't change results based
+ // on upper bound and seek key.
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ Reopen(options);
+
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ReadOptions()));
+ iterator->Seek("xa");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ // It's a bug that the counter BLOOM_FILTER_PREFIX_CHECKED is not
+ // correct in this case. So don't check counters in this case.
+ if (expect_filter_check) {
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ iterator->Seek("xz");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xz1", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+
+ std::string ub_str = "xg9";
+ Slice ub(ub_str);
+ ReadOptions ro;
+ ro.iterate_upper_bound = &ub;
+
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+
+ // SeekForPrev() never uses prefix bloom if it is changed.
+ iterator->SeekForPrev("xg0");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+
+ ub_str = "xx9";
+ ub = Slice(ub_str);
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+
+ iterator->Seek("x");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ iterator->Seek("xx0");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xx1", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+
+ CompactRangeOptions compact_range_opts;
+ compact_range_opts.bottommost_level_compaction =
+ BottommostLevelCompaction::kForce;
+ ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr));
+ ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr));
+
+ // Re-execute similar queries after a full compaction
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ReadOptions()));
+
+ iterator->Seek("x");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(2, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ iterator->Seek("xg");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xx1", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(3, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ iterator->Seek("xz");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xz1", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(4, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+
+ iterator->SeekForPrev("xx0");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(5, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ iterator->Seek("xx0");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xx1", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(6, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+
+ ub_str = "xg9";
+ ub = Slice(ub_str);
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->SeekForPrev("xg0");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("xb", iterator->key().ToString());
+ if (expect_filter_check) {
+ ASSERT_EQ(7, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+ }
+ }
+}
+
+TEST_F(DBTest2, BlockBasedTablePrefixGetIndexNotFound) {
+ // create a DB with block prefix index
+ BlockBasedTableOptions table_options;
+ Options options = CurrentOptions();
+ table_options.block_size = 300;
+ table_options.index_type = BlockBasedTableOptions::kHashSearch;
+ table_options.index_shortening =
+ BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ options.level0_file_num_compaction_trigger = 8;
+
+ Reopen(options);
+
+ ASSERT_OK(Put("b1", "ok"));
+ Flush();
+
+ // Flushing several files so that the chance that hash bucket
+ // is empty fo "b" in at least one of the files is high.
+ ASSERT_OK(Put("a1", ""));
+ ASSERT_OK(Put("c1", ""));
+ Flush();
+
+ ASSERT_OK(Put("a2", ""));
+ ASSERT_OK(Put("c2", ""));
+ Flush();
+
+ ASSERT_OK(Put("a3", ""));
+ ASSERT_OK(Put("c3", ""));
+ Flush();
+
+ ASSERT_OK(Put("a4", ""));
+ ASSERT_OK(Put("c4", ""));
+ Flush();
+
+ ASSERT_OK(Put("a5", ""));
+ ASSERT_OK(Put("c5", ""));
+ Flush();
+
+ ASSERT_EQ("ok", Get("b1"));
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBTest2, AutoPrefixMode1) {
+ // create a DB with block prefix index
+ BlockBasedTableOptions table_options;
+ Options options = CurrentOptions();
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ options.statistics = CreateDBStatistics();
+
+ Reopen(options);
+
+ Random rnd(301);
+ std::string large_value = RandomString(&rnd, 500);
+
+ ASSERT_OK(Put("a1", large_value));
+ ASSERT_OK(Put("x1", large_value));
+ ASSERT_OK(Put("y1", large_value));
+ Flush();
+
+ ReadOptions ro;
+ ro.total_order_seek = false;
+ ro.auto_prefix_mode = true;
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->Seek("b1");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("x1", iterator->key().ToString());
+ ASSERT_EQ(0, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ std::string ub_str = "b9";
+ Slice ub(ub_str);
+ ro.iterate_upper_bound = &ub;
+
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->Seek("b1");
+ ASSERT_FALSE(iterator->Valid());
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ ub_str = "z";
+ ub = Slice(ub_str);
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->Seek("b1");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("x1", iterator->key().ToString());
+ ASSERT_EQ(1, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ ub_str = "c";
+ ub = Slice(ub_str);
+ {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->Seek("b1");
+ ASSERT_FALSE(iterator->Valid());
+ ASSERT_EQ(2, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+ }
+
+ // The same queries without recreating iterator
+ {
+ ub_str = "b9";
+ ub = Slice(ub_str);
+ ro.iterate_upper_bound = &ub;
+
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ro));
+ iterator->Seek("b1");
+ ASSERT_FALSE(iterator->Valid());
+ ASSERT_EQ(3, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+
+ ub_str = "z";
+ ub = Slice(ub_str);
+
+ iterator->Seek("b1");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("x1", iterator->key().ToString());
+ ASSERT_EQ(3, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+
+ ub_str = "c";
+ ub = Slice(ub_str);
+
+ iterator->Seek("b1");
+ ASSERT_FALSE(iterator->Valid());
+ ASSERT_EQ(4, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+
+ ub_str = "b9";
+ ub = Slice(ub_str);
+ ro.iterate_upper_bound = &ub;
+ iterator->SeekForPrev("b1");
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("a1", iterator->key().ToString());
+ ASSERT_EQ(4, TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED));
+
+ ub_str = "zz";
+ ub = Slice(ub_str);
+ ro.iterate_upper_bound = &ub;
+ iterator->SeekToLast();
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("y1", iterator->key().ToString());
+
+ iterator->SeekToFirst();
+ ASSERT_TRUE(iterator->Valid());
+ ASSERT_EQ("a1", iterator->key().ToString());
+ }
+}
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_test_util.cc b/storage/rocksdb/rocksdb/db/db_test_util.cc
new file mode 100644
index 00000000..c73abde4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_test_util.cc
@@ -0,0 +1,1564 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/forward_iterator.h"
+#include "rocksdb/env_encryption.h"
+#include "rocksdb/utilities/object_registry.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Special Env used to delay background operations
+
+SpecialEnv::SpecialEnv(Env* base)
+ : EnvWrapper(base),
+ rnd_(301),
+ sleep_counter_(this),
+ addon_time_(0),
+ time_elapse_only_sleep_(false),
+ no_slowdown_(false) {
+ delay_sstable_sync_.store(false, std::memory_order_release);
+ drop_writes_.store(false, std::memory_order_release);
+ no_space_.store(false, std::memory_order_release);
+ non_writable_.store(false, std::memory_order_release);
+ count_random_reads_ = false;
+ count_sequential_reads_ = false;
+ manifest_sync_error_.store(false, std::memory_order_release);
+ manifest_write_error_.store(false, std::memory_order_release);
+ log_write_error_.store(false, std::memory_order_release);
+ random_file_open_counter_.store(0, std::memory_order_relaxed);
+ delete_count_.store(0, std::memory_order_relaxed);
+ num_open_wal_file_.store(0);
+ log_write_slowdown_ = 0;
+ bytes_written_ = 0;
+ sync_counter_ = 0;
+ non_writeable_rate_ = 0;
+ new_writable_count_ = 0;
+ non_writable_count_ = 0;
+ table_write_callback_ = nullptr;
+}
+#ifndef ROCKSDB_LITE
+ROT13BlockCipher rot13Cipher_(16);
+#endif // ROCKSDB_LITE
+
+DBTestBase::DBTestBase(const std::string path)
+ : mem_env_(nullptr), encrypted_env_(nullptr), option_config_(kDefault) {
+ Env* base_env = Env::Default();
+#ifndef ROCKSDB_LITE
+ const char* test_env_uri = getenv("TEST_ENV_URI");
+ if (test_env_uri) {
+ Env* test_env = nullptr;
+ Status s = Env::LoadEnv(test_env_uri, &test_env, &env_guard_);
+ base_env = test_env;
+ EXPECT_OK(s);
+ EXPECT_NE(Env::Default(), base_env);
+ }
+#endif // !ROCKSDB_LITE
+ EXPECT_NE(nullptr, base_env);
+ if (getenv("MEM_ENV")) {
+ mem_env_ = new MockEnv(base_env);
+ }
+#ifndef ROCKSDB_LITE
+ if (getenv("ENCRYPTED_ENV")) {
+ encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env,
+ new CTREncryptionProvider(rot13Cipher_));
+ }
+#endif // !ROCKSDB_LITE
+ env_ = new SpecialEnv(encrypted_env_ ? encrypted_env_
+ : (mem_env_ ? mem_env_ : base_env));
+ env_->SetBackgroundThreads(1, Env::LOW);
+ env_->SetBackgroundThreads(1, Env::HIGH);
+ dbname_ = test::PerThreadDBPath(env_, path);
+ alternative_wal_dir_ = dbname_ + "/wal";
+ alternative_db_log_dir_ = dbname_ + "/db_log_dir";
+ auto options = CurrentOptions();
+ options.env = env_;
+ auto delete_options = options;
+ delete_options.wal_dir = alternative_wal_dir_;
+ EXPECT_OK(DestroyDB(dbname_, delete_options));
+ // Destroy it for not alternative WAL dir is used.
+ EXPECT_OK(DestroyDB(dbname_, options));
+ db_ = nullptr;
+ Reopen(options);
+ Random::GetTLSInstance()->Reset(0xdeadbeef);
+}
+
+DBTestBase::~DBTestBase() {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ Close();
+ Options options;
+ options.db_paths.emplace_back(dbname_, 0);
+ options.db_paths.emplace_back(dbname_ + "_2", 0);
+ options.db_paths.emplace_back(dbname_ + "_3", 0);
+ options.db_paths.emplace_back(dbname_ + "_4", 0);
+ options.env = env_;
+
+ if (getenv("KEEP_DB")) {
+ printf("DB is still at %s\n", dbname_.c_str());
+ } else {
+ EXPECT_OK(DestroyDB(dbname_, options));
+ }
+ delete env_;
+}
+
+bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
+#ifdef ROCKSDB_LITE
+ // These options are not supported in ROCKSDB_LITE
+ if (option_config == kHashSkipList ||
+ option_config == kPlainTableFirstBytePrefix ||
+ option_config == kPlainTableCappedPrefix ||
+ option_config == kPlainTableCappedPrefixNonMmap ||
+ option_config == kPlainTableAllBytesPrefix ||
+ option_config == kVectorRep || option_config == kHashLinkList ||
+ option_config == kUniversalCompaction ||
+ option_config == kUniversalCompactionMultiLevel ||
+ option_config == kUniversalSubcompactions ||
+ option_config == kFIFOCompaction ||
+ option_config == kConcurrentSkipList) {
+ return true;
+ }
+#endif
+
+ if ((skip_mask & kSkipUniversalCompaction) &&
+ (option_config == kUniversalCompaction ||
+ option_config == kUniversalCompactionMultiLevel ||
+ option_config == kUniversalSubcompactions)) {
+ return true;
+ }
+ if ((skip_mask & kSkipMergePut) && option_config == kMergePut) {
+ return true;
+ }
+ if ((skip_mask & kSkipNoSeekToLast) &&
+ (option_config == kHashLinkList || option_config == kHashSkipList)) {
+ return true;
+ }
+ if ((skip_mask & kSkipPlainTable) &&
+ (option_config == kPlainTableAllBytesPrefix ||
+ option_config == kPlainTableFirstBytePrefix ||
+ option_config == kPlainTableCappedPrefix ||
+ option_config == kPlainTableCappedPrefixNonMmap)) {
+ return true;
+ }
+ if ((skip_mask & kSkipHashIndex) &&
+ (option_config == kBlockBasedTableWithPrefixHashIndex ||
+ option_config == kBlockBasedTableWithWholeKeyHashIndex)) {
+ return true;
+ }
+ if ((skip_mask & kSkipFIFOCompaction) && option_config == kFIFOCompaction) {
+ return true;
+ }
+ if ((skip_mask & kSkipMmapReads) && option_config == kWalDirAndMmapReads) {
+ return true;
+ }
+ return false;
+}
+
+// Switch to a fresh database with the next option configuration to
+// test. Return false if there are no more configurations to test.
+bool DBTestBase::ChangeOptions(int skip_mask) {
+ for (option_config_++; option_config_ < kEnd; option_config_++) {
+ if (ShouldSkipOptions(option_config_, skip_mask)) {
+ continue;
+ }
+ break;
+ }
+
+ if (option_config_ >= kEnd) {
+ Destroy(last_options_);
+ return false;
+ } else {
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ DestroyAndReopen(options);
+ return true;
+ }
+}
+
+// Switch between different compaction styles.
+bool DBTestBase::ChangeCompactOptions() {
+ if (option_config_ == kDefault) {
+ option_config_ = kUniversalCompaction;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kUniversalCompaction) {
+ option_config_ = kUniversalCompactionMultiLevel;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kUniversalCompactionMultiLevel) {
+ option_config_ = kLevelSubcompactions;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ assert(options.max_subcompactions > 1);
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kLevelSubcompactions) {
+ option_config_ = kUniversalSubcompactions;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ assert(options.max_subcompactions > 1);
+ TryReopen(options);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Switch between different WAL settings
+bool DBTestBase::ChangeWalOptions() {
+ if (option_config_ == kDefault) {
+ option_config_ = kDBLogDir;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ Destroy(options);
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kDBLogDir) {
+ option_config_ = kWalDirAndMmapReads;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ Destroy(options);
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kWalDirAndMmapReads) {
+ option_config_ = kRecycleLogFiles;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ Destroy(options);
+ TryReopen(options);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Switch between different filter policy
+// Jump from kDefault to kFilter to kFullFilter
+bool DBTestBase::ChangeFilterOptions() {
+ if (option_config_ == kDefault) {
+ option_config_ = kFilter;
+ } else if (option_config_ == kFilter) {
+ option_config_ = kFullFilterWithNewTableReaderForCompactions;
+ } else if (option_config_ == kFullFilterWithNewTableReaderForCompactions) {
+ option_config_ = kPartitionedFilterWithNewTableReaderForCompactions;
+ } else {
+ return false;
+ }
+ Destroy(last_options_);
+
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+}
+
+// Switch between different DB options for file ingestion tests.
+bool DBTestBase::ChangeOptionsForFileIngestionTest() {
+ if (option_config_ == kDefault) {
+ option_config_ = kUniversalCompaction;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kUniversalCompaction) {
+ option_config_ = kUniversalCompactionMultiLevel;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kUniversalCompactionMultiLevel) {
+ option_config_ = kLevelSubcompactions;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ assert(options.max_subcompactions > 1);
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kLevelSubcompactions) {
+ option_config_ = kUniversalSubcompactions;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ assert(options.max_subcompactions > 1);
+ TryReopen(options);
+ return true;
+ } else if (option_config_ == kUniversalSubcompactions) {
+ option_config_ = kDirectIO;
+ Destroy(last_options_);
+ auto options = CurrentOptions();
+ TryReopen(options);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Return the current option configuration.
+Options DBTestBase::CurrentOptions(
+ const anon::OptionsOverride& options_override) const {
+ return GetOptions(option_config_, GetDefaultOptions(), options_override);
+}
+
+Options DBTestBase::CurrentOptions(
+ const Options& default_options,
+ const anon::OptionsOverride& options_override) const {
+ return GetOptions(option_config_, default_options, options_override);
+}
+
+Options DBTestBase::GetDefaultOptions() {
+ Options options;
+ options.write_buffer_size = 4090 * 4096;
+ options.target_file_size_base = 2 * 1024 * 1024;
+ options.max_bytes_for_level_base = 10 * 1024 * 1024;
+ options.max_open_files = 5000;
+ options.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
+ options.compaction_pri = CompactionPri::kByCompensatedSize;
+ return options;
+}
+
+Options DBTestBase::GetOptions(
+ int option_config, const Options& default_options,
+ const anon::OptionsOverride& options_override) const {
+ // this redundant copy is to minimize code change w/o having lint error.
+ Options options = default_options;
+ BlockBasedTableOptions table_options;
+ bool set_block_based_table_factory = true;
+#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
+ !defined(OS_AIX)
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
+ "NewRandomAccessFile:O_DIRECT");
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
+ "NewWritableFile:O_DIRECT");
+#endif
+
+ bool can_allow_mmap = IsMemoryMappedAccessSupported();
+ switch (option_config) {
+#ifndef ROCKSDB_LITE
+ case kHashSkipList:
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ options.memtable_factory.reset(NewHashSkipListRepFactory(16));
+ options.allow_concurrent_memtable_write = false;
+ options.unordered_write = false;
+ break;
+ case kPlainTableFirstBytePrefix:
+ options.table_factory.reset(new PlainTableFactory());
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ options.allow_mmap_reads = can_allow_mmap;
+ options.max_sequential_skip_in_iterations = 999999;
+ set_block_based_table_factory = false;
+ break;
+ case kPlainTableCappedPrefix:
+ options.table_factory.reset(new PlainTableFactory());
+ options.prefix_extractor.reset(NewCappedPrefixTransform(8));
+ options.allow_mmap_reads = can_allow_mmap;
+ options.max_sequential_skip_in_iterations = 999999;
+ set_block_based_table_factory = false;
+ break;
+ case kPlainTableCappedPrefixNonMmap:
+ options.table_factory.reset(new PlainTableFactory());
+ options.prefix_extractor.reset(NewCappedPrefixTransform(8));
+ options.allow_mmap_reads = false;
+ options.max_sequential_skip_in_iterations = 999999;
+ set_block_based_table_factory = false;
+ break;
+ case kPlainTableAllBytesPrefix:
+ options.table_factory.reset(new PlainTableFactory());
+ options.prefix_extractor.reset(NewNoopTransform());
+ options.allow_mmap_reads = can_allow_mmap;
+ options.max_sequential_skip_in_iterations = 999999;
+ set_block_based_table_factory = false;
+ break;
+ case kVectorRep:
+ options.memtable_factory.reset(new VectorRepFactory(100));
+ options.allow_concurrent_memtable_write = false;
+ options.unordered_write = false;
+ break;
+ case kHashLinkList:
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ options.memtable_factory.reset(
+ NewHashLinkListRepFactory(4, 0, 3, true, 4));
+ options.allow_concurrent_memtable_write = false;
+ options.unordered_write = false;
+ break;
+ case kDirectIO: {
+ options.use_direct_reads = true;
+ options.use_direct_io_for_flush_and_compaction = true;
+ options.compaction_readahead_size = 2 * 1024 * 1024;
+ #if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
+ !defined(OS_AIX) && !defined(OS_OPENBSD)
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewWritableFile:O_DIRECT", [&](void* arg) {
+ int* val = static_cast<int*>(arg);
+ *val &= ~O_DIRECT;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewRandomAccessFile:O_DIRECT", [&](void* arg) {
+ int* val = static_cast<int*>(arg);
+ *val &= ~O_DIRECT;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+#endif
+ break;
+ }
+#endif // ROCKSDB_LITE
+ case kMergePut:
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ break;
+ case kFilter:
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
+ break;
+ case kFullFilterWithNewTableReaderForCompactions:
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ options.new_table_reader_for_compaction_inputs = true;
+ options.compaction_readahead_size = 10 * 1024 * 1024;
+ break;
+ case kPartitionedFilterWithNewTableReaderForCompactions:
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ table_options.partition_filters = true;
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ options.new_table_reader_for_compaction_inputs = true;
+ options.compaction_readahead_size = 10 * 1024 * 1024;
+ break;
+ case kUncompressed:
+ options.compression = kNoCompression;
+ break;
+ case kNumLevel_3:
+ options.num_levels = 3;
+ break;
+ case kDBLogDir:
+ options.db_log_dir = alternative_db_log_dir_;
+ break;
+ case kWalDirAndMmapReads:
+ options.wal_dir = alternative_wal_dir_;
+ // mmap reads should be orthogonal to WalDir setting, so we piggyback to
+ // this option config to test mmap reads as well
+ options.allow_mmap_reads = can_allow_mmap;
+ break;
+ case kManifestFileSize:
+ options.max_manifest_file_size = 50; // 50 bytes
+ break;
+ case kPerfOptions:
+ options.soft_rate_limit = 2.0;
+ options.delayed_write_rate = 8 * 1024 * 1024;
+ options.report_bg_io_stats = true;
+ // TODO(3.13) -- test more options
+ break;
+ case kUniversalCompaction:
+ options.compaction_style = kCompactionStyleUniversal;
+ options.num_levels = 1;
+ break;
+ case kUniversalCompactionMultiLevel:
+ options.compaction_style = kCompactionStyleUniversal;
+ options.num_levels = 8;
+ break;
+ case kCompressedBlockCache:
+ options.allow_mmap_writes = can_allow_mmap;
+ table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
+ break;
+ case kInfiniteMaxOpenFiles:
+ options.max_open_files = -1;
+ break;
+ case kxxHashChecksum: {
+ table_options.checksum = kxxHash;
+ break;
+ }
+ case kxxHash64Checksum: {
+ table_options.checksum = kxxHash64;
+ break;
+ }
+ case kFIFOCompaction: {
+ options.compaction_style = kCompactionStyleFIFO;
+ break;
+ }
+ case kBlockBasedTableWithPrefixHashIndex: {
+ table_options.index_type = BlockBasedTableOptions::kHashSearch;
+ options.prefix_extractor.reset(NewFixedPrefixTransform(1));
+ break;
+ }
+ case kBlockBasedTableWithWholeKeyHashIndex: {
+ table_options.index_type = BlockBasedTableOptions::kHashSearch;
+ options.prefix_extractor.reset(NewNoopTransform());
+ break;
+ }
+ case kBlockBasedTableWithPartitionedIndex: {
+ table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
+ options.prefix_extractor.reset(NewNoopTransform());
+ break;
+ }
+ case kBlockBasedTableWithPartitionedIndexFormat4: {
+ table_options.format_version = 4;
+ // Format 4 changes the binary index format. Since partitioned index is a
+ // super-set of simple indexes, we are also using kTwoLevelIndexSearch to
+ // test this format.
+ table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
+ // The top-level index in partition filters are also affected by format 4.
+ table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
+ table_options.partition_filters = true;
+ table_options.index_block_restart_interval = 8;
+ break;
+ }
+ case kBlockBasedTableWithIndexRestartInterval: {
+ table_options.index_block_restart_interval = 8;
+ break;
+ }
+ case kOptimizeFiltersForHits: {
+ options.optimize_filters_for_hits = true;
+ set_block_based_table_factory = true;
+ break;
+ }
+ case kRowCache: {
+ options.row_cache = NewLRUCache(1024 * 1024);
+ break;
+ }
+ case kRecycleLogFiles: {
+ options.recycle_log_file_num = 2;
+ break;
+ }
+ case kLevelSubcompactions: {
+ options.max_subcompactions = 4;
+ break;
+ }
+ case kUniversalSubcompactions: {
+ options.compaction_style = kCompactionStyleUniversal;
+ options.num_levels = 8;
+ options.max_subcompactions = 4;
+ break;
+ }
+ case kConcurrentSkipList: {
+ options.allow_concurrent_memtable_write = true;
+ options.enable_write_thread_adaptive_yield = true;
+ break;
+ }
+ case kPipelinedWrite: {
+ options.enable_pipelined_write = true;
+ break;
+ }
+ case kConcurrentWALWrites: {
+ // This options optimize 2PC commit path
+ options.two_write_queues = true;
+ options.manual_wal_flush = true;
+ break;
+ }
+ case kUnorderedWrite: {
+ options.allow_concurrent_memtable_write = false;
+ options.unordered_write = false;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (options_override.filter_policy) {
+ table_options.filter_policy = options_override.filter_policy;
+ table_options.partition_filters = options_override.partition_filters;
+ table_options.metadata_block_size = options_override.metadata_block_size;
+ }
+ if (set_block_based_table_factory) {
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ }
+ options.env = env_;
+ options.create_if_missing = true;
+ options.fail_if_options_file_error = true;
+ return options;
+}
+
+void DBTestBase::CreateColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ColumnFamilyOptions cf_opts(options);
+ size_t cfi = handles_.size();
+ handles_.resize(cfi + cfs.size());
+ for (auto cf : cfs) {
+ Status s = db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]);
+ ASSERT_OK(s);
+ }
+}
+
+void DBTestBase::CreateAndReopenWithCF(const std::vector<std::string>& cfs,
+ const Options& options) {
+ CreateColumnFamilies(cfs, options);
+ std::vector<std::string> cfs_plus_default = cfs;
+ cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
+ ReopenWithColumnFamilies(cfs_plus_default, options);
+}
+
+void DBTestBase::ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const std::vector<Options>& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+}
+
+void DBTestBase::ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+}
+
+Status DBTestBase::TryReopenWithColumnFamilies(
+ const std::vector<std::string>& cfs, const std::vector<Options>& options) {
+ Close();
+ EXPECT_EQ(cfs.size(), options.size());
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < cfs.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
+ }
+ DBOptions db_opts = DBOptions(options[0]);
+ last_options_ = options[0];
+ return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
+}
+
+Status DBTestBase::TryReopenWithColumnFamilies(
+ const std::vector<std::string>& cfs, const Options& options) {
+ Close();
+ std::vector<Options> v_opts(cfs.size(), options);
+ return TryReopenWithColumnFamilies(cfs, v_opts);
+}
+
+void DBTestBase::Reopen(const Options& options) {
+ ASSERT_OK(TryReopen(options));
+}
+
+void DBTestBase::Close() {
+ for (auto h : handles_) {
+ db_->DestroyColumnFamilyHandle(h);
+ }
+ handles_.clear();
+ delete db_;
+ db_ = nullptr;
+}
+
+void DBTestBase::DestroyAndReopen(const Options& options) {
+ // Destroy using last options
+ Destroy(last_options_);
+ ASSERT_OK(TryReopen(options));
+}
+
+void DBTestBase::Destroy(const Options& options, bool delete_cf_paths) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ if (delete_cf_paths) {
+ for (size_t i = 0; i < handles_.size(); ++i) {
+ ColumnFamilyDescriptor cfdescriptor;
+ handles_[i]->GetDescriptor(&cfdescriptor);
+ column_families.push_back(cfdescriptor);
+ }
+ }
+ Close();
+ ASSERT_OK(DestroyDB(dbname_, options, column_families));
+}
+
+Status DBTestBase::ReadOnlyReopen(const Options& options) {
+ return DB::OpenForReadOnly(options, dbname_, &db_);
+}
+
+Status DBTestBase::TryReopen(const Options& options) {
+ Close();
+ last_options_.table_factory.reset();
+ // Note: operator= is an unsafe approach here since it destructs
+ // std::shared_ptr in the same order of their creation, in contrast to
+ // destructors which destructs them in the opposite order of creation. One
+ // particular problme is that the cache destructor might invoke callback
+ // functions that use Option members such as statistics. To work around this
+ // problem, we manually call destructor of table_facotry which eventually
+ // clears the block cache.
+ last_options_ = options;
+ return DB::Open(options, dbname_, &db_);
+}
+
+bool DBTestBase::IsDirectIOSupported() {
+ return test::IsDirectIOSupported(env_, dbname_);
+}
+
+bool DBTestBase::IsMemoryMappedAccessSupported() const {
+ return (!encrypted_env_);
+}
+
+Status DBTestBase::Flush(int cf) {
+ if (cf == 0) {
+ return db_->Flush(FlushOptions());
+ } else {
+ return db_->Flush(FlushOptions(), handles_[cf]);
+ }
+}
+
+Status DBTestBase::Flush(const std::vector<int>& cf_ids) {
+ std::vector<ColumnFamilyHandle*> cfhs;
+ std::for_each(cf_ids.begin(), cf_ids.end(),
+ [&cfhs, this](int id) { cfhs.emplace_back(handles_[id]); });
+ return db_->Flush(FlushOptions(), cfhs);
+}
+
+Status DBTestBase::Put(const Slice& k, const Slice& v, WriteOptions wo) {
+ if (kMergePut == option_config_) {
+ return db_->Merge(wo, k, v);
+ } else {
+ return db_->Put(wo, k, v);
+ }
+}
+
+Status DBTestBase::Put(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo) {
+ if (kMergePut == option_config_) {
+ return db_->Merge(wo, handles_[cf], k, v);
+ } else {
+ return db_->Put(wo, handles_[cf], k, v);
+ }
+}
+
+Status DBTestBase::Merge(const Slice& k, const Slice& v, WriteOptions wo) {
+ return db_->Merge(wo, k, v);
+}
+
+Status DBTestBase::Merge(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo) {
+ return db_->Merge(wo, handles_[cf], k, v);
+}
+
+Status DBTestBase::Delete(const std::string& k) {
+ return db_->Delete(WriteOptions(), k);
+}
+
+Status DBTestBase::Delete(int cf, const std::string& k) {
+ return db_->Delete(WriteOptions(), handles_[cf], k);
+}
+
+Status DBTestBase::SingleDelete(const std::string& k) {
+ return db_->SingleDelete(WriteOptions(), k);
+}
+
+Status DBTestBase::SingleDelete(int cf, const std::string& k) {
+ return db_->SingleDelete(WriteOptions(), handles_[cf], k);
+}
+
+bool DBTestBase::SetPreserveDeletesSequenceNumber(SequenceNumber sn) {
+ return db_->SetPreserveDeletesSequenceNumber(sn);
+}
+
+std::string DBTestBase::Get(const std::string& k, const Snapshot* snapshot) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ 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;
+}
+
+std::string DBTestBase::Get(int cf, const std::string& k,
+ const Snapshot* snapshot) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ options.snapshot = snapshot;
+ std::string result;
+ Status s = db_->Get(options, handles_[cf], k, &result);
+ if (s.IsNotFound()) {
+ result = "NOT_FOUND";
+ } else if (!s.ok()) {
+ result = s.ToString();
+ }
+ return result;
+}
+
+std::vector<std::string> DBTestBase::MultiGet(std::vector<int> cfs,
+ const std::vector<std::string>& k,
+ const Snapshot* snapshot,
+ const bool batched) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ options.snapshot = snapshot;
+ std::vector<ColumnFamilyHandle*> handles;
+ std::vector<Slice> keys;
+ std::vector<std::string> result;
+
+ for (unsigned int i = 0; i < cfs.size(); ++i) {
+ handles.push_back(handles_[cfs[i]]);
+ keys.push_back(k[i]);
+ }
+ std::vector<Status> s;
+ if (!batched) {
+ s = db_->MultiGet(options, handles, keys, &result);
+ for (unsigned int i = 0; i < s.size(); ++i) {
+ if (s[i].IsNotFound()) {
+ result[i] = "NOT_FOUND";
+ } else if (!s[i].ok()) {
+ result[i] = s[i].ToString();
+ }
+ }
+ } else {
+ std::vector<PinnableSlice> pin_values(cfs.size());
+ result.resize(cfs.size());
+ s.resize(cfs.size());
+ db_->MultiGet(options, cfs.size(), handles.data(), keys.data(),
+ pin_values.data(), s.data());
+ for (unsigned int i = 0; i < s.size(); ++i) {
+ if (s[i].IsNotFound()) {
+ result[i] = "NOT_FOUND";
+ } else if (!s[i].ok()) {
+ result[i] = s[i].ToString();
+ } else {
+ result[i].assign(pin_values[i].data(), pin_values[i].size());
+ }
+ }
+ }
+ return result;
+}
+
+std::vector<std::string> DBTestBase::MultiGet(const std::vector<std::string>& k,
+ const Snapshot* snapshot) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ options.snapshot = snapshot;
+ std::vector<Slice> keys;
+ std::vector<std::string> result;
+ std::vector<Status> statuses(k.size());
+ std::vector<PinnableSlice> pin_values(k.size());
+
+ for (unsigned int i = 0; i < k.size(); ++i) {
+ keys.push_back(k[i]);
+ }
+ db_->MultiGet(options, dbfull()->DefaultColumnFamily(), keys.size(),
+ keys.data(), pin_values.data(), statuses.data());
+ result.resize(k.size());
+ for (auto iter = result.begin(); iter != result.end(); ++iter) {
+ iter->assign(pin_values[iter - result.begin()].data(),
+ pin_values[iter - result.begin()].size());
+ }
+ for (unsigned int i = 0; i < statuses.size(); ++i) {
+ if (statuses[i].IsNotFound()) {
+ result[i] = "NOT_FOUND";
+ }
+ }
+ return result;
+}
+
+Status DBTestBase::Get(const std::string& k, PinnableSlice* v) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ Status s = dbfull()->Get(options, dbfull()->DefaultColumnFamily(), k, v);
+ return s;
+}
+
+uint64_t DBTestBase::GetNumSnapshots() {
+ uint64_t int_num;
+ EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.num-snapshots", &int_num));
+ return int_num;
+}
+
+uint64_t DBTestBase::GetTimeOldestSnapshots() {
+ uint64_t int_num;
+ EXPECT_TRUE(
+ dbfull()->GetIntProperty("rocksdb.oldest-snapshot-time", &int_num));
+ return int_num;
+}
+
+uint64_t DBTestBase::GetSequenceOldestSnapshots() {
+ uint64_t int_num;
+ EXPECT_TRUE(
+ dbfull()->GetIntProperty("rocksdb.oldest-snapshot-sequence", &int_num));
+ return int_num;
+}
+
+// Return a string that contains all key,value pairs in order,
+// formatted like "(k1->v1)(k2->v2)".
+std::string DBTestBase::Contents(int cf) {
+ std::vector<std::string> forward;
+ std::string result;
+ Iterator* iter = (cf == 0) ? db_->NewIterator(ReadOptions())
+ : db_->NewIterator(ReadOptions(), handles_[cf]);
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ std::string s = IterStatus(iter);
+ result.push_back('(');
+ result.append(s);
+ result.push_back(')');
+ forward.push_back(s);
+ }
+
+ // Check reverse iteration results are the reverse of forward results
+ unsigned int matched = 0;
+ for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
+ EXPECT_LT(matched, forward.size());
+ EXPECT_EQ(IterStatus(iter), forward[forward.size() - matched - 1]);
+ matched++;
+ }
+ EXPECT_EQ(matched, forward.size());
+
+ delete iter;
+ return result;
+}
+
+std::string DBTestBase::AllEntriesFor(const Slice& user_key, int cf) {
+ Arena arena;
+ auto options = CurrentOptions();
+ InternalKeyComparator icmp(options.comparator);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ ScopedArenaIterator iter;
+ if (cf == 0) {
+ iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg,
+ kMaxSequenceNumber));
+ } else {
+ iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg,
+ kMaxSequenceNumber, handles_[cf]));
+ }
+ InternalKey target(user_key, kMaxSequenceNumber, kTypeValue);
+ iter->Seek(target.Encode());
+ std::string result;
+ if (!iter->status().ok()) {
+ result = iter->status().ToString();
+ } else {
+ result = "[ ";
+ bool first = true;
+ while (iter->Valid()) {
+ ParsedInternalKey ikey(Slice(), 0, kTypeValue);
+ if (!ParseInternalKey(iter->key(), &ikey)) {
+ result += "CORRUPTED";
+ } else {
+ if (!last_options_.comparator->Equal(ikey.user_key, user_key)) {
+ break;
+ }
+ if (!first) {
+ result += ", ";
+ }
+ first = false;
+ switch (ikey.type) {
+ case kTypeValue:
+ result += iter->value().ToString();
+ break;
+ case kTypeMerge:
+ // keep it the same as kTypeValue for testing kMergePut
+ result += iter->value().ToString();
+ break;
+ case kTypeDeletion:
+ result += "DEL";
+ break;
+ case kTypeSingleDeletion:
+ result += "SDEL";
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ iter->Next();
+ }
+ if (!first) {
+ result += " ";
+ }
+ result += "]";
+ }
+ return result;
+}
+
+#ifndef ROCKSDB_LITE
+int DBTestBase::NumSortedRuns(int cf) {
+ ColumnFamilyMetaData cf_meta;
+ if (cf == 0) {
+ db_->GetColumnFamilyMetaData(&cf_meta);
+ } else {
+ db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
+ }
+ int num_sr = static_cast<int>(cf_meta.levels[0].files.size());
+ for (size_t i = 1U; i < cf_meta.levels.size(); i++) {
+ if (cf_meta.levels[i].files.size() > 0) {
+ num_sr++;
+ }
+ }
+ return num_sr;
+}
+
+uint64_t DBTestBase::TotalSize(int cf) {
+ ColumnFamilyMetaData cf_meta;
+ if (cf == 0) {
+ db_->GetColumnFamilyMetaData(&cf_meta);
+ } else {
+ db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
+ }
+ return cf_meta.size;
+}
+
+uint64_t DBTestBase::SizeAtLevel(int level) {
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ uint64_t sum = 0;
+ for (const auto& m : metadata) {
+ if (m.level == level) {
+ sum += m.size;
+ }
+ }
+ return sum;
+}
+
+size_t DBTestBase::TotalLiveFiles(int cf) {
+ ColumnFamilyMetaData cf_meta;
+ if (cf == 0) {
+ db_->GetColumnFamilyMetaData(&cf_meta);
+ } else {
+ db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
+ }
+ size_t num_files = 0;
+ for (auto& level : cf_meta.levels) {
+ num_files += level.files.size();
+ }
+ return num_files;
+}
+
+size_t DBTestBase::CountLiveFiles() {
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ return metadata.size();
+}
+
+int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
+ std::string property;
+ if (cf == 0) {
+ // default cfd
+ EXPECT_TRUE(db_->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(level), &property));
+ } else {
+ EXPECT_TRUE(db_->GetProperty(
+ handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
+ &property));
+ }
+ return atoi(property.c_str());
+}
+
+double DBTestBase::CompressionRatioAtLevel(int level, int cf) {
+ std::string property;
+ if (cf == 0) {
+ // default cfd
+ EXPECT_TRUE(db_->GetProperty(
+ "rocksdb.compression-ratio-at-level" + NumberToString(level),
+ &property));
+ } else {
+ EXPECT_TRUE(db_->GetProperty(
+ handles_[cf],
+ "rocksdb.compression-ratio-at-level" + NumberToString(level),
+ &property));
+ }
+ return std::stod(property);
+}
+
+int DBTestBase::TotalTableFiles(int cf, int levels) {
+ if (levels == -1) {
+ levels = (cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
+ }
+ int result = 0;
+ for (int level = 0; level < levels; level++) {
+ result += NumTableFilesAtLevel(level, cf);
+ }
+ return result;
+}
+
+// Return spread of files per level
+std::string DBTestBase::FilesPerLevel(int cf) {
+ int num_levels =
+ (cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
+ std::string result;
+ size_t last_non_zero_offset = 0;
+ for (int level = 0; level < num_levels; 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 = result.size();
+ }
+ }
+ result.resize(last_non_zero_offset);
+ return result;
+}
+#endif // !ROCKSDB_LITE
+
+size_t DBTestBase::CountFiles() {
+ std::vector<std::string> files;
+ env_->GetChildren(dbname_, &files);
+
+ std::vector<std::string> logfiles;
+ if (dbname_ != last_options_.wal_dir) {
+ env_->GetChildren(last_options_.wal_dir, &logfiles);
+ }
+
+ return files.size() + logfiles.size();
+}
+
+uint64_t DBTestBase::Size(const Slice& start, const Slice& limit, int cf) {
+ Range r(start, limit);
+ uint64_t size;
+ if (cf == 0) {
+ db_->GetApproximateSizes(&r, 1, &size);
+ } else {
+ db_->GetApproximateSizes(handles_[1], &r, 1, &size);
+ }
+ return size;
+}
+
+void DBTestBase::Compact(int cf, const Slice& start, const Slice& limit,
+ uint32_t target_path_id) {
+ CompactRangeOptions compact_options;
+ compact_options.target_path_id = target_path_id;
+ ASSERT_OK(db_->CompactRange(compact_options, handles_[cf], &start, &limit));
+}
+
+void DBTestBase::Compact(int cf, const Slice& start, const Slice& limit) {
+ ASSERT_OK(
+ db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit));
+}
+
+void DBTestBase::Compact(const Slice& start, const Slice& limit) {
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &limit));
+}
+
+// Do n memtable compactions, each of which produces an sstable
+// covering the range [small,large].
+void DBTestBase::MakeTables(int n, const std::string& small,
+ const std::string& large, int cf) {
+ for (int i = 0; i < n; i++) {
+ ASSERT_OK(Put(cf, small, "begin"));
+ ASSERT_OK(Put(cf, large, "end"));
+ ASSERT_OK(Flush(cf));
+ MoveFilesToLevel(n - i - 1, cf);
+ }
+}
+
+// Prevent pushing of new sstables into deeper levels by adding
+// tables that cover a specified range to all levels.
+void DBTestBase::FillLevels(const std::string& smallest,
+ const std::string& largest, int cf) {
+ MakeTables(db_->NumberLevels(handles_[cf]), smallest, largest, cf);
+}
+
+void DBTestBase::MoveFilesToLevel(int level, int cf) {
+ for (int l = 0; l < level; ++l) {
+ if (cf > 0) {
+ dbfull()->TEST_CompactRange(l, nullptr, nullptr, handles_[cf]);
+ } else {
+ dbfull()->TEST_CompactRange(l, nullptr, nullptr);
+ }
+ }
+}
+
+#ifndef ROCKSDB_LITE
+void DBTestBase::DumpFileCounts(const char* label) {
+ fprintf(stderr, "---\n%s:\n", label);
+ fprintf(stderr, "maxoverlap: %" PRIu64 "\n",
+ dbfull()->TEST_MaxNextLevelOverlappingBytes());
+ for (int level = 0; level < db_->NumberLevels(); level++) {
+ int num = NumTableFilesAtLevel(level);
+ if (num > 0) {
+ fprintf(stderr, " level %3d : %d files\n", level, num);
+ }
+ }
+}
+#endif // !ROCKSDB_LITE
+
+std::string DBTestBase::DumpSSTableList() {
+ std::string property;
+ db_->GetProperty("rocksdb.sstables", &property);
+ return property;
+}
+
+void DBTestBase::GetSstFiles(Env* env, std::string path,
+ std::vector<std::string>* files) {
+ env->GetChildren(path, files);
+
+ files->erase(
+ std::remove_if(files->begin(), files->end(), [](std::string name) {
+ uint64_t number;
+ FileType type;
+ return !(ParseFileName(name, &number, &type) && type == kTableFile);
+ }), files->end());
+}
+
+int DBTestBase::GetSstFileCount(std::string path) {
+ std::vector<std::string> files;
+ DBTestBase::GetSstFiles(env_, path, &files);
+ return static_cast<int>(files.size());
+}
+
+// this will generate non-overlapping files since it keeps increasing key_idx
+void DBTestBase::GenerateNewFile(int cf, Random* rnd, int* key_idx,
+ bool nowait) {
+ for (int i = 0; i < KNumKeysByGenerateNewFile; i++) {
+ ASSERT_OK(Put(cf, Key(*key_idx), RandomString(rnd, (i == 99) ? 1 : 990)));
+ (*key_idx)++;
+ }
+ if (!nowait) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+}
+
+// this will generate non-overlapping files since it keeps increasing key_idx
+void DBTestBase::GenerateNewFile(Random* rnd, int* key_idx, bool nowait) {
+ for (int i = 0; i < KNumKeysByGenerateNewFile; i++) {
+ ASSERT_OK(Put(Key(*key_idx), RandomString(rnd, (i == 99) ? 1 : 990)));
+ (*key_idx)++;
+ }
+ if (!nowait) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+}
+
+const int DBTestBase::kNumKeysByGenerateNewRandomFile = 51;
+
+void DBTestBase::GenerateNewRandomFile(Random* rnd, bool nowait) {
+ for (int i = 0; i < kNumKeysByGenerateNewRandomFile; i++) {
+ ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 2000)));
+ }
+ ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 200)));
+ if (!nowait) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+}
+
+std::string DBTestBase::IterStatus(Iterator* iter) {
+ std::string result;
+ if (iter->Valid()) {
+ result = iter->key().ToString() + "->" + iter->value().ToString();
+ } else {
+ result = "(invalid)";
+ }
+ return result;
+}
+
+Options DBTestBase::OptionsForLogIterTest() {
+ Options options = CurrentOptions();
+ options.create_if_missing = true;
+ options.WAL_ttl_seconds = 1000;
+ return options;
+}
+
+std::string DBTestBase::DummyString(size_t len, char c) {
+ return std::string(len, c);
+}
+
+void DBTestBase::VerifyIterLast(std::string expected_key, int cf) {
+ Iterator* iter;
+ ReadOptions ro;
+ if (cf == 0) {
+ iter = db_->NewIterator(ro);
+ } else {
+ iter = db_->NewIterator(ro, handles_[cf]);
+ }
+ iter->SeekToLast();
+ ASSERT_EQ(IterStatus(iter), expected_key);
+ delete iter;
+}
+
+// Used to test InplaceUpdate
+
+// If previous value is nullptr or delta is > than previous value,
+// sets newValue with delta
+// If previous value is not empty,
+// updates previous value with 'b' string of previous value size - 1.
+UpdateStatus DBTestBase::updateInPlaceSmallerSize(char* prevValue,
+ uint32_t* prevSize,
+ Slice delta,
+ std::string* newValue) {
+ if (prevValue == nullptr) {
+ *newValue = std::string(delta.size(), 'c');
+ return UpdateStatus::UPDATED;
+ } else {
+ *prevSize = *prevSize - 1;
+ std::string str_b = std::string(*prevSize, 'b');
+ memcpy(prevValue, str_b.c_str(), str_b.size());
+ return UpdateStatus::UPDATED_INPLACE;
+ }
+}
+
+UpdateStatus DBTestBase::updateInPlaceSmallerVarintSize(char* prevValue,
+ uint32_t* prevSize,
+ Slice delta,
+ std::string* newValue) {
+ if (prevValue == nullptr) {
+ *newValue = std::string(delta.size(), 'c');
+ return UpdateStatus::UPDATED;
+ } else {
+ *prevSize = 1;
+ std::string str_b = std::string(*prevSize, 'b');
+ memcpy(prevValue, str_b.c_str(), str_b.size());
+ return UpdateStatus::UPDATED_INPLACE;
+ }
+}
+
+UpdateStatus DBTestBase::updateInPlaceLargerSize(char* /*prevValue*/,
+ uint32_t* /*prevSize*/,
+ Slice delta,
+ std::string* newValue) {
+ *newValue = std::string(delta.size(), 'c');
+ return UpdateStatus::UPDATED;
+}
+
+UpdateStatus DBTestBase::updateInPlaceNoAction(char* /*prevValue*/,
+ uint32_t* /*prevSize*/,
+ Slice /*delta*/,
+ std::string* /*newValue*/) {
+ return UpdateStatus::UPDATE_FAILED;
+}
+
+// Utility method to test InplaceUpdate
+void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
+ Arena arena;
+ auto options = CurrentOptions();
+ InternalKeyComparator icmp(options.comparator);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ // This should be defined after range_del_agg so that it destructs the
+ // assigned iterator before it range_del_agg is already destructed.
+ ScopedArenaIterator iter;
+ if (cf != 0) {
+ iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg,
+ kMaxSequenceNumber, handles_[cf]));
+ } else {
+ iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg,
+ kMaxSequenceNumber));
+ }
+ iter->SeekToFirst();
+ ASSERT_EQ(iter->status().ok(), true);
+ int seq = numValues;
+ while (iter->Valid()) {
+ ParsedInternalKey ikey;
+ ikey.clear();
+ ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
+
+ // checks sequence number for updates
+ ASSERT_EQ(ikey.sequence, (unsigned)seq--);
+ iter->Next();
+ }
+ ASSERT_EQ(0, seq);
+}
+
+void DBTestBase::CopyFile(const std::string& source,
+ const std::string& destination, uint64_t size) {
+ const EnvOptions soptions;
+ std::unique_ptr<SequentialFile> srcfile;
+ ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions));
+ std::unique_ptr<WritableFile> 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());
+}
+
+std::unordered_map<std::string, uint64_t> DBTestBase::GetAllSSTFiles(
+ uint64_t* total_size) {
+ std::unordered_map<std::string, uint64_t> res;
+
+ if (total_size) {
+ *total_size = 0;
+ }
+ std::vector<std::string> files;
+ env_->GetChildren(dbname_, &files);
+ for (auto& file_name : files) {
+ uint64_t number;
+ FileType type;
+ std::string file_path = dbname_ + "/" + file_name;
+ if (ParseFileName(file_name, &number, &type) && type == kTableFile) {
+ uint64_t file_size = 0;
+ env_->GetFileSize(file_path, &file_size);
+ res[file_path] = file_size;
+ if (total_size) {
+ *total_size += file_size;
+ }
+ }
+ }
+ return res;
+}
+
+std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
+ const std::string& path) {
+ std::vector<std::string> files;
+ std::vector<uint64_t> file_numbers;
+ env->GetChildren(path, &files);
+ uint64_t number;
+ FileType type;
+ for (size_t i = 0; i < files.size(); ++i) {
+ if (ParseFileName(files[i], &number, &type)) {
+ if (type == kTableFile) {
+ file_numbers.push_back(number);
+ }
+ }
+ }
+ return file_numbers;
+}
+
+void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
+ size_t* total_reads_res, bool tailing_iter,
+ std::map<std::string, Status> status) {
+ size_t total_reads = 0;
+
+ for (auto& kv : true_data) {
+ Status s = status[kv.first];
+ if (s.ok()) {
+ ASSERT_EQ(Get(kv.first), kv.second);
+ } else {
+ std::string value;
+ ASSERT_EQ(s, db_->Get(ReadOptions(), kv.first, &value));
+ }
+ total_reads++;
+ }
+
+ // Normal Iterator
+ {
+ int iter_cnt = 0;
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ Iterator* iter = db_->NewIterator(ro);
+ // Verify Iterator::Next()
+ iter_cnt = 0;
+ auto data_iter = true_data.begin();
+ Status s;
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
+ ASSERT_EQ(iter->key().ToString(), data_iter->first);
+ Status current_status = status[data_iter->first];
+ if (!current_status.ok()) {
+ s = current_status;
+ }
+ ASSERT_EQ(iter->status(), s);
+ if (current_status.ok()) {
+ ASSERT_EQ(iter->value().ToString(), data_iter->second);
+ }
+ iter_cnt++;
+ total_reads++;
+ }
+ ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
+ << true_data.size();
+ delete iter;
+
+ // Verify Iterator::Prev()
+ // Use a new iterator to make sure its status is clean.
+ iter = db_->NewIterator(ro);
+ iter_cnt = 0;
+ s = Status::OK();
+ auto data_rev = true_data.rbegin();
+ for (iter->SeekToLast(); iter->Valid(); iter->Prev(), data_rev++) {
+ ASSERT_EQ(iter->key().ToString(), data_rev->first);
+ Status current_status = status[data_rev->first];
+ if (!current_status.ok()) {
+ s = current_status;
+ }
+ ASSERT_EQ(iter->status(), s);
+ if (current_status.ok()) {
+ ASSERT_EQ(iter->value().ToString(), data_rev->second);
+ }
+ iter_cnt++;
+ total_reads++;
+ }
+ ASSERT_EQ(data_rev, true_data.rend()) << iter_cnt << " / "
+ << true_data.size();
+
+ // Verify Iterator::Seek()
+ for (auto kv : true_data) {
+ iter->Seek(kv.first);
+ ASSERT_EQ(kv.first, iter->key().ToString());
+ ASSERT_EQ(kv.second, iter->value().ToString());
+ total_reads++;
+ }
+ delete iter;
+ }
+
+ if (tailing_iter) {
+#ifndef ROCKSDB_LITE
+ // Tailing iterator
+ int iter_cnt = 0;
+ ReadOptions ro;
+ ro.tailing = true;
+ ro.total_order_seek = true;
+ Iterator* iter = db_->NewIterator(ro);
+
+ // Verify ForwardIterator::Next()
+ iter_cnt = 0;
+ auto data_iter = true_data.begin();
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
+ ASSERT_EQ(iter->key().ToString(), data_iter->first);
+ ASSERT_EQ(iter->value().ToString(), data_iter->second);
+ iter_cnt++;
+ total_reads++;
+ }
+ ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
+ << true_data.size();
+
+ // Verify ForwardIterator::Seek()
+ for (auto kv : true_data) {
+ iter->Seek(kv.first);
+ ASSERT_EQ(kv.first, iter->key().ToString());
+ ASSERT_EQ(kv.second, iter->value().ToString());
+ total_reads++;
+ }
+
+ delete iter;
+#endif // ROCKSDB_LITE
+ }
+
+ if (total_reads_res) {
+ *total_reads_res = total_reads;
+ }
+}
+
+void DBTestBase::VerifyDBInternal(
+ std::vector<std::pair<std::string, std::string>> true_data) {
+ Arena arena;
+ InternalKeyComparator icmp(last_options_.comparator);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ auto iter =
+ dbfull()->NewInternalIterator(&arena, &range_del_agg, kMaxSequenceNumber);
+ iter->SeekToFirst();
+ for (auto p : true_data) {
+ ASSERT_TRUE(iter->Valid());
+ ParsedInternalKey ikey;
+ ASSERT_TRUE(ParseInternalKey(iter->key(), &ikey));
+ ASSERT_EQ(p.first, ikey.user_key);
+ ASSERT_EQ(p.second, iter->value());
+ iter->Next();
+ };
+ ASSERT_FALSE(iter->Valid());
+ iter->~InternalIterator();
+}
+
+#ifndef ROCKSDB_LITE
+
+uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
+ DB* db, std::string column_family_name) {
+ std::vector<LiveFileMetaData> metadata;
+ db->GetLiveFilesMetaData(&metadata);
+ uint64_t result = 0;
+ for (auto& fileMetadata : metadata) {
+ result += (fileMetadata.column_family_name == column_family_name);
+ }
+ return result;
+}
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_test_util.h b/storage/rocksdb/rocksdb/db/db_test_util.h
new file mode 100644
index 00000000..eeabea9b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_test_util.h
@@ -0,0 +1,1000 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <fcntl.h>
+#include <cinttypes>
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <string>
+#include <thread>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "env/mock_env.h"
+#include "file/filename.h"
+#include "memtable/hash_linklist_rep.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/sst_file_writer.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/mock_table.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/scoped_arena_iterator.h"
+#include "test_util/mock_time_env.h"
+#include "util/compression.h"
+#include "util/mutexlock.h"
+
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace anon {
+class AtomicCounter {
+ public:
+ explicit AtomicCounter(Env* env = NULL)
+ : env_(env), cond_count_(&mu_), count_(0) {}
+
+ void Increment() {
+ MutexLock l(&mu_);
+ count_++;
+ cond_count_.SignalAll();
+ }
+
+ int Read() {
+ MutexLock l(&mu_);
+ return count_;
+ }
+
+ bool WaitFor(int count) {
+ MutexLock l(&mu_);
+
+ uint64_t start = env_->NowMicros();
+ while (count_ < count) {
+ uint64_t now = env_->NowMicros();
+ cond_count_.TimedWait(now + /*1s*/ 1 * 1000 * 1000);
+ if (env_->NowMicros() - start > /*10s*/ 10 * 1000 * 1000) {
+ return false;
+ }
+ if (count_ < count) {
+ GTEST_LOG_(WARNING) << "WaitFor is taking more time than usual";
+ }
+ }
+
+ return true;
+ }
+
+ void Reset() {
+ MutexLock l(&mu_);
+ count_ = 0;
+ cond_count_.SignalAll();
+ }
+
+ private:
+ Env* env_;
+ port::Mutex mu_;
+ port::CondVar cond_count_;
+ int count_;
+};
+
+struct OptionsOverride {
+ std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
+ // These will be used only if filter_policy is set
+ bool partition_filters = false;
+ uint64_t metadata_block_size = 1024;
+
+ // Used as a bit mask of individual enums in which to skip an XF test point
+ int skip_policy = 0;
+};
+
+} // namespace anon
+
+enum SkipPolicy { kSkipNone = 0, kSkipNoSnapshot = 1, kSkipNoPrefix = 2 };
+
+// A hacky skip list mem table that triggers flush after number of entries.
+class SpecialMemTableRep : public MemTableRep {
+ public:
+ explicit SpecialMemTableRep(Allocator* allocator, MemTableRep* memtable,
+ int num_entries_flush)
+ : MemTableRep(allocator),
+ memtable_(memtable),
+ num_entries_flush_(num_entries_flush),
+ num_entries_(0) {}
+
+ virtual KeyHandle Allocate(const size_t len, char** buf) override {
+ return memtable_->Allocate(len, buf);
+ }
+
+ // Insert key into the list.
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ virtual void Insert(KeyHandle handle) override {
+ num_entries_++;
+ memtable_->Insert(handle);
+ }
+
+ void InsertConcurrently(KeyHandle handle) override {
+ num_entries_++;
+ memtable_->Insert(handle);
+ }
+
+ // Returns true iff an entry that compares equal to key is in the list.
+ virtual bool Contains(const char* key) const override {
+ return memtable_->Contains(key);
+ }
+
+ virtual size_t ApproximateMemoryUsage() override {
+ // Return a high memory usage when number of entries exceeds the threshold
+ // to trigger a flush.
+ return (num_entries_ < num_entries_flush_) ? 0 : 1024 * 1024 * 1024;
+ }
+
+ virtual void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg,
+ const char* entry)) override {
+ memtable_->Get(k, callback_args, callback_func);
+ }
+
+ uint64_t ApproximateNumEntries(const Slice& start_ikey,
+ const Slice& end_ikey) override {
+ return memtable_->ApproximateNumEntries(start_ikey, end_ikey);
+ }
+
+ virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override {
+ return memtable_->GetIterator(arena);
+ }
+
+ virtual ~SpecialMemTableRep() override {}
+
+ private:
+ std::unique_ptr<MemTableRep> memtable_;
+ int num_entries_flush_;
+ int num_entries_;
+};
+
+// The factory for the hacky skip list mem table that triggers flush after
+// number of entries exceeds a threshold.
+class SpecialSkipListFactory : public MemTableRepFactory {
+ public:
+ // After number of inserts exceeds `num_entries_flush` in a mem table, trigger
+ // flush.
+ explicit SpecialSkipListFactory(int num_entries_flush)
+ : num_entries_flush_(num_entries_flush) {}
+
+ using MemTableRepFactory::CreateMemTableRep;
+ virtual MemTableRep* CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* /*logger*/) override {
+ return new SpecialMemTableRep(
+ allocator, factory_.CreateMemTableRep(compare, allocator, transform, 0),
+ num_entries_flush_);
+ }
+ virtual const char* Name() const override { return "SkipListFactory"; }
+
+ bool IsInsertConcurrentlySupported() const override {
+ return factory_.IsInsertConcurrentlySupported();
+ }
+
+ private:
+ SkipListFactory factory_;
+ int num_entries_flush_;
+};
+
+// Special Env used to delay background operations
+class SpecialEnv : public EnvWrapper {
+ public:
+ explicit SpecialEnv(Env* base);
+
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& soptions) override {
+ class SSTableFile : public WritableFile {
+ private:
+ SpecialEnv* env_;
+ std::unique_ptr<WritableFile> base_;
+
+ public:
+ SSTableFile(SpecialEnv* env, std::unique_ptr<WritableFile>&& base)
+ : env_(env), base_(std::move(base)) {}
+ Status Append(const Slice& data) override {
+ if (env_->table_write_callback_) {
+ (*env_->table_write_callback_)();
+ }
+ if (env_->drop_writes_.load(std::memory_order_acquire)) {
+ // Drop writes on the floor
+ return Status::OK();
+ } else if (env_->no_space_.load(std::memory_order_acquire)) {
+ return Status::NoSpace("No space left on device");
+ } else {
+ env_->bytes_written_ += data.size();
+ return base_->Append(data);
+ }
+ }
+ Status PositionedAppend(const Slice& data, uint64_t offset) override {
+ if (env_->table_write_callback_) {
+ (*env_->table_write_callback_)();
+ }
+ if (env_->drop_writes_.load(std::memory_order_acquire)) {
+ // Drop writes on the floor
+ return Status::OK();
+ } else if (env_->no_space_.load(std::memory_order_acquire)) {
+ return Status::NoSpace("No space left on device");
+ } else {
+ env_->bytes_written_ += data.size();
+ return base_->PositionedAppend(data, offset);
+ }
+ }
+ Status Truncate(uint64_t size) override { return base_->Truncate(size); }
+ Status RangeSync(uint64_t offset, uint64_t nbytes) override {
+ Status s = base_->RangeSync(offset, nbytes);
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ TEST_SYNC_POINT_CALLBACK("SpecialEnv::SStableFile::RangeSync", &s);
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+ return s;
+ }
+ Status Close() override {
+// SyncPoint is not supported in Released Windows Mode.
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ // Check preallocation size
+ // preallocation size is never passed to base file.
+ size_t preallocation_size = preallocation_block_size();
+ TEST_SYNC_POINT_CALLBACK("DBTestWritableFile.GetPreallocationStatus",
+ &preallocation_size);
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+ Status s = base_->Close();
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ TEST_SYNC_POINT_CALLBACK("SpecialEnv::SStableFile::Close", &s);
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+ return s;
+ }
+ Status Flush() override { return base_->Flush(); }
+ Status Sync() override {
+ ++env_->sync_counter_;
+ while (env_->delay_sstable_sync_.load(std::memory_order_acquire)) {
+ env_->SleepForMicroseconds(100000);
+ }
+ Status s = base_->Sync();
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ TEST_SYNC_POINT_CALLBACK("SpecialEnv::SStableFile::Sync", &s);
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+ return s;
+ }
+ void SetIOPriority(Env::IOPriority pri) override {
+ base_->SetIOPriority(pri);
+ }
+ Env::IOPriority GetIOPriority() override {
+ return base_->GetIOPriority();
+ }
+ bool use_direct_io() const override {
+ return base_->use_direct_io();
+ }
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ return base_->Allocate(offset, len);
+ }
+ };
+ class ManifestFile : public WritableFile {
+ public:
+ ManifestFile(SpecialEnv* env, std::unique_ptr<WritableFile>&& b)
+ : env_(env), base_(std::move(b)) {}
+ Status Append(const Slice& data) override {
+ if (env_->manifest_write_error_.load(std::memory_order_acquire)) {
+ return Status::IOError("simulated writer error");
+ } else {
+ return base_->Append(data);
+ }
+ }
+ Status Truncate(uint64_t size) override { return base_->Truncate(size); }
+ Status Close() override { return base_->Close(); }
+ Status Flush() override { return base_->Flush(); }
+ Status Sync() override {
+ ++env_->sync_counter_;
+ if (env_->manifest_sync_error_.load(std::memory_order_acquire)) {
+ return Status::IOError("simulated sync error");
+ } else {
+ return base_->Sync();
+ }
+ }
+ uint64_t GetFileSize() override { return base_->GetFileSize(); }
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ return base_->Allocate(offset, len);
+ }
+
+ private:
+ SpecialEnv* env_;
+ std::unique_ptr<WritableFile> base_;
+ };
+ class WalFile : public WritableFile {
+ public:
+ WalFile(SpecialEnv* env, std::unique_ptr<WritableFile>&& b)
+ : env_(env), base_(std::move(b)) {
+ env_->num_open_wal_file_.fetch_add(1);
+ }
+ virtual ~WalFile() { env_->num_open_wal_file_.fetch_add(-1); }
+ Status Append(const Slice& data) override {
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ TEST_SYNC_POINT("SpecialEnv::WalFile::Append:1");
+#endif
+ Status s;
+ if (env_->log_write_error_.load(std::memory_order_acquire)) {
+ s = Status::IOError("simulated writer error");
+ } else {
+ int slowdown =
+ env_->log_write_slowdown_.load(std::memory_order_acquire);
+ if (slowdown > 0) {
+ env_->SleepForMicroseconds(slowdown);
+ }
+ s = base_->Append(data);
+ }
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ TEST_SYNC_POINT("SpecialEnv::WalFile::Append:2");
+#endif
+ return s;
+ }
+ Status Truncate(uint64_t size) override { return base_->Truncate(size); }
+ Status Close() override {
+// SyncPoint is not supported in Released Windows Mode.
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ // Check preallocation size
+ // preallocation size is never passed to base file.
+ size_t preallocation_size = preallocation_block_size();
+ TEST_SYNC_POINT_CALLBACK("DBTestWalFile.GetPreallocationStatus",
+ &preallocation_size);
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+
+ return base_->Close();
+ }
+ Status Flush() override { return base_->Flush(); }
+ Status Sync() override {
+ ++env_->sync_counter_;
+ return base_->Sync();
+ }
+ bool IsSyncThreadSafe() const override {
+ return env_->is_wal_sync_thread_safe_.load();
+ }
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ return base_->Allocate(offset, len);
+ }
+
+ private:
+ SpecialEnv* env_;
+ std::unique_ptr<WritableFile> base_;
+ };
+
+ if (non_writeable_rate_.load(std::memory_order_acquire) > 0) {
+ uint32_t random_number;
+ {
+ MutexLock l(&rnd_mutex_);
+ random_number = rnd_.Uniform(100);
+ }
+ if (random_number < non_writeable_rate_.load()) {
+ return Status::IOError("simulated random write error");
+ }
+ }
+
+ new_writable_count_++;
+
+ if (non_writable_count_.load() > 0) {
+ non_writable_count_--;
+ return Status::IOError("simulated write error");
+ }
+
+ EnvOptions optimized = soptions;
+ if (strstr(f.c_str(), "MANIFEST") != nullptr ||
+ strstr(f.c_str(), "log") != nullptr) {
+ optimized.use_mmap_writes = false;
+ optimized.use_direct_writes = false;
+ }
+
+ Status s = target()->NewWritableFile(f, r, optimized);
+ if (s.ok()) {
+ if (strstr(f.c_str(), ".sst") != nullptr) {
+ r->reset(new SSTableFile(this, std::move(*r)));
+ } else if (strstr(f.c_str(), "MANIFEST") != nullptr) {
+ r->reset(new ManifestFile(this, std::move(*r)));
+ } else if (strstr(f.c_str(), "log") != nullptr) {
+ r->reset(new WalFile(this, std::move(*r)));
+ }
+ }
+ return s;
+ }
+
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& soptions) override {
+ class CountingFile : public RandomAccessFile {
+ public:
+ CountingFile(std::unique_ptr<RandomAccessFile>&& target,
+ anon::AtomicCounter* counter,
+ std::atomic<size_t>* bytes_read)
+ : target_(std::move(target)),
+ counter_(counter),
+ bytes_read_(bytes_read) {}
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ counter_->Increment();
+ Status s = target_->Read(offset, n, result, scratch);
+ *bytes_read_ += result->size();
+ return s;
+ }
+
+ virtual Status Prefetch(uint64_t offset, size_t n) override {
+ Status s = target_->Prefetch(offset, n);
+ *bytes_read_ += n;
+ return s;
+ }
+
+ private:
+ std::unique_ptr<RandomAccessFile> target_;
+ anon::AtomicCounter* counter_;
+ std::atomic<size_t>* bytes_read_;
+ };
+
+ Status s = target()->NewRandomAccessFile(f, r, soptions);
+ random_file_open_counter_++;
+ if (s.ok() && count_random_reads_) {
+ r->reset(new CountingFile(std::move(*r), &random_read_counter_,
+ &random_read_bytes_counter_));
+ }
+ if (s.ok() && soptions.compaction_readahead_size > 0) {
+ compaction_readahead_size_ = soptions.compaction_readahead_size;
+ }
+ return s;
+ }
+
+ virtual Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& soptions) override {
+ class CountingFile : public SequentialFile {
+ public:
+ CountingFile(std::unique_ptr<SequentialFile>&& target,
+ anon::AtomicCounter* counter)
+ : target_(std::move(target)), counter_(counter) {}
+ virtual Status Read(size_t n, Slice* result, char* scratch) override {
+ counter_->Increment();
+ return target_->Read(n, result, scratch);
+ }
+ virtual Status Skip(uint64_t n) override { return target_->Skip(n); }
+
+ private:
+ std::unique_ptr<SequentialFile> target_;
+ anon::AtomicCounter* counter_;
+ };
+
+ Status s = target()->NewSequentialFile(f, r, soptions);
+ if (s.ok() && count_sequential_reads_) {
+ r->reset(new CountingFile(std::move(*r), &sequential_read_counter_));
+ }
+ return s;
+ }
+
+ virtual void SleepForMicroseconds(int micros) override {
+ sleep_counter_.Increment();
+ if (no_slowdown_ || time_elapse_only_sleep_) {
+ addon_time_.fetch_add(micros);
+ }
+ if (!no_slowdown_) {
+ target()->SleepForMicroseconds(micros);
+ }
+ }
+
+ virtual Status GetCurrentTime(int64_t* unix_time) override {
+ Status s;
+ if (!time_elapse_only_sleep_) {
+ s = target()->GetCurrentTime(unix_time);
+ }
+ if (s.ok()) {
+ *unix_time += addon_time_.load();
+ }
+ return s;
+ }
+
+ virtual uint64_t NowCPUNanos() override {
+ now_cpu_count_.fetch_add(1);
+ return target()->NowCPUNanos();
+ }
+
+ virtual uint64_t NowNanos() override {
+ return (time_elapse_only_sleep_ ? 0 : target()->NowNanos()) +
+ addon_time_.load() * 1000;
+ }
+
+ virtual uint64_t NowMicros() override {
+ return (time_elapse_only_sleep_ ? 0 : target()->NowMicros()) +
+ addon_time_.load();
+ }
+
+ virtual Status DeleteFile(const std::string& fname) override {
+ delete_count_.fetch_add(1);
+ return target()->DeleteFile(fname);
+ }
+
+ Random rnd_;
+ port::Mutex rnd_mutex_; // Lock to pretect rnd_
+
+ // sstable Sync() calls are blocked while this pointer is non-nullptr.
+ std::atomic<bool> delay_sstable_sync_;
+
+ // Drop writes on the floor while this pointer is non-nullptr.
+ std::atomic<bool> drop_writes_;
+
+ // Simulate no-space errors while this pointer is non-nullptr.
+ std::atomic<bool> no_space_;
+
+ // Simulate non-writable file system while this pointer is non-nullptr
+ std::atomic<bool> non_writable_;
+
+ // Force sync of manifest files to fail while this pointer is non-nullptr
+ std::atomic<bool> manifest_sync_error_;
+
+ // Force write to manifest files to fail while this pointer is non-nullptr
+ std::atomic<bool> manifest_write_error_;
+
+ // Force write to log files to fail while this pointer is non-nullptr
+ std::atomic<bool> log_write_error_;
+
+ // Slow down every log write, in micro-seconds.
+ std::atomic<int> log_write_slowdown_;
+
+ // Number of WAL files that are still open for write.
+ std::atomic<int> num_open_wal_file_;
+
+ bool count_random_reads_;
+ anon::AtomicCounter random_read_counter_;
+ std::atomic<size_t> random_read_bytes_counter_;
+ std::atomic<int> random_file_open_counter_;
+
+ bool count_sequential_reads_;
+ anon::AtomicCounter sequential_read_counter_;
+
+ anon::AtomicCounter sleep_counter_;
+
+ std::atomic<int64_t> bytes_written_;
+
+ std::atomic<int> sync_counter_;
+
+ std::atomic<uint32_t> non_writeable_rate_;
+
+ std::atomic<uint32_t> new_writable_count_;
+
+ std::atomic<uint32_t> non_writable_count_;
+
+ std::function<void()>* table_write_callback_;
+
+ std::atomic<int64_t> addon_time_;
+
+ std::atomic<int> now_cpu_count_;
+
+ std::atomic<int> delete_count_;
+
+ std::atomic<bool> time_elapse_only_sleep_;
+
+ bool no_slowdown_;
+
+ std::atomic<bool> is_wal_sync_thread_safe_{true};
+
+ std::atomic<size_t> compaction_readahead_size_{};
+};
+
+#ifndef ROCKSDB_LITE
+class OnFileDeletionListener : public EventListener {
+ public:
+ OnFileDeletionListener() : matched_count_(0), expected_file_name_("") {}
+
+ void SetExpectedFileName(const std::string file_name) {
+ expected_file_name_ = file_name;
+ }
+
+ void VerifyMatchedCount(size_t expected_value) {
+ ASSERT_EQ(matched_count_, expected_value);
+ }
+
+ void OnTableFileDeleted(const TableFileDeletionInfo& info) override {
+ if (expected_file_name_ != "") {
+ ASSERT_EQ(expected_file_name_, info.file_path);
+ expected_file_name_ = "";
+ matched_count_++;
+ }
+ }
+
+ private:
+ size_t matched_count_;
+ std::string expected_file_name_;
+};
+#endif
+
+// A test merge operator mimics put but also fails if one of merge operands is
+// "corrupted".
+class TestPutOperator : public MergeOperator {
+ public:
+ virtual bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ if (merge_in.existing_value != nullptr &&
+ *(merge_in.existing_value) == "corrupted") {
+ return false;
+ }
+ for (auto value : merge_in.operand_list) {
+ if (value == "corrupted") {
+ return false;
+ }
+ }
+ merge_out->existing_operand = merge_in.operand_list.back();
+ return true;
+ }
+
+ virtual const char* Name() const override { return "TestPutOperator"; }
+};
+
+class DBTestBase : public testing::Test {
+ public:
+ // Sequence of option configurations to try
+ enum OptionConfig : int {
+ kDefault = 0,
+ kBlockBasedTableWithPrefixHashIndex = 1,
+ kBlockBasedTableWithWholeKeyHashIndex = 2,
+ kPlainTableFirstBytePrefix = 3,
+ kPlainTableCappedPrefix = 4,
+ kPlainTableCappedPrefixNonMmap = 5,
+ kPlainTableAllBytesPrefix = 6,
+ kVectorRep = 7,
+ kHashLinkList = 8,
+ kMergePut = 9,
+ kFilter = 10,
+ kFullFilterWithNewTableReaderForCompactions = 11,
+ kUncompressed = 12,
+ kNumLevel_3 = 13,
+ kDBLogDir = 14,
+ kWalDirAndMmapReads = 15,
+ kManifestFileSize = 16,
+ kPerfOptions = 17,
+ kHashSkipList = 18,
+ kUniversalCompaction = 19,
+ kUniversalCompactionMultiLevel = 20,
+ kCompressedBlockCache = 21,
+ kInfiniteMaxOpenFiles = 22,
+ kxxHashChecksum = 23,
+ kFIFOCompaction = 24,
+ kOptimizeFiltersForHits = 25,
+ kRowCache = 26,
+ kRecycleLogFiles = 27,
+ kConcurrentSkipList = 28,
+ kPipelinedWrite = 29,
+ kConcurrentWALWrites = 30,
+ kDirectIO,
+ kLevelSubcompactions,
+ kBlockBasedTableWithIndexRestartInterval,
+ kBlockBasedTableWithPartitionedIndex,
+ kBlockBasedTableWithPartitionedIndexFormat4,
+ kPartitionedFilterWithNewTableReaderForCompactions,
+ kUniversalSubcompactions,
+ kxxHash64Checksum,
+ kUnorderedWrite,
+ // This must be the last line
+ kEnd,
+ };
+
+ public:
+ std::string dbname_;
+ std::string alternative_wal_dir_;
+ std::string alternative_db_log_dir_;
+ MockEnv* mem_env_;
+ Env* encrypted_env_;
+ SpecialEnv* env_;
+ std::shared_ptr<Env> env_guard_;
+ DB* db_;
+ std::vector<ColumnFamilyHandle*> handles_;
+
+ int option_config_;
+ Options last_options_;
+
+ // Skip some options, as they may not be applicable to a specific test.
+ // To add more skip constants, use values 4, 8, 16, etc.
+ enum OptionSkip {
+ kNoSkip = 0,
+ kSkipDeletesFilterFirst = 1,
+ kSkipUniversalCompaction = 2,
+ kSkipMergePut = 4,
+ kSkipPlainTable = 8,
+ kSkipHashIndex = 16,
+ kSkipNoSeekToLast = 32,
+ kSkipFIFOCompaction = 128,
+ kSkipMmapReads = 256,
+ };
+
+ const int kRangeDelSkipConfigs =
+ // Plain tables do not support range deletions.
+ kSkipPlainTable |
+ // MmapReads disables the iterator pinning that RangeDelAggregator
+ // requires.
+ kSkipMmapReads;
+
+ explicit DBTestBase(const std::string path);
+
+ ~DBTestBase();
+
+ static std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+ }
+
+ static std::string Key(int i) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "key%06d", i);
+ return std::string(buf);
+ }
+
+ static bool ShouldSkipOptions(int option_config, int skip_mask = kNoSkip);
+
+ // Switch to a fresh database with the next option configuration to
+ // test. Return false if there are no more configurations to test.
+ bool ChangeOptions(int skip_mask = kNoSkip);
+
+ // Switch between different compaction styles.
+ bool ChangeCompactOptions();
+
+ // Switch between different WAL-realted options.
+ bool ChangeWalOptions();
+
+ // Switch between different filter policy
+ // Jump from kDefault to kFilter to kFullFilter
+ bool ChangeFilterOptions();
+
+ // Switch between different DB options for file ingestion tests.
+ bool ChangeOptionsForFileIngestionTest();
+
+ // Return the current option configuration.
+ Options CurrentOptions(const anon::OptionsOverride& options_override =
+ anon::OptionsOverride()) const;
+
+ Options CurrentOptions(const Options& default_options,
+ const anon::OptionsOverride& options_override =
+ anon::OptionsOverride()) const;
+
+ static Options GetDefaultOptions();
+
+ Options GetOptions(int option_config,
+ const Options& default_options = GetDefaultOptions(),
+ const anon::OptionsOverride& options_override =
+ anon::OptionsOverride()) const;
+
+ DBImpl* dbfull() { return reinterpret_cast<DBImpl*>(db_); }
+
+ void CreateColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options);
+
+ void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
+ const Options& options);
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const std::vector<Options>& options);
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options);
+
+ Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const std::vector<Options>& options);
+
+ Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options);
+
+ void Reopen(const Options& options);
+
+ void Close();
+
+ void DestroyAndReopen(const Options& options);
+
+ void Destroy(const Options& options, bool delete_cf_paths = false);
+
+ Status ReadOnlyReopen(const Options& options);
+
+ Status TryReopen(const Options& options);
+
+ bool IsDirectIOSupported();
+
+ bool IsMemoryMappedAccessSupported() const;
+
+ Status Flush(int cf = 0);
+
+ Status Flush(const std::vector<int>& cf_ids);
+
+ Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions());
+
+ Status Put(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo = WriteOptions());
+
+ Status Merge(const Slice& k, const Slice& v,
+ WriteOptions wo = WriteOptions());
+
+ Status Merge(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo = WriteOptions());
+
+ Status Delete(const std::string& k);
+
+ Status Delete(int cf, const std::string& k);
+
+ Status SingleDelete(const std::string& k);
+
+ Status SingleDelete(int cf, const std::string& k);
+
+ bool SetPreserveDeletesSequenceNumber(SequenceNumber sn);
+
+ std::string Get(const std::string& k, const Snapshot* snapshot = nullptr);
+
+ std::string Get(int cf, const std::string& k,
+ const Snapshot* snapshot = nullptr);
+
+ Status Get(const std::string& k, PinnableSlice* v);
+
+ std::vector<std::string> MultiGet(std::vector<int> cfs,
+ const std::vector<std::string>& k,
+ const Snapshot* snapshot,
+ const bool batched);
+
+ std::vector<std::string> MultiGet(const std::vector<std::string>& k,
+ const Snapshot* snapshot = nullptr);
+
+ uint64_t GetNumSnapshots();
+
+ uint64_t GetTimeOldestSnapshots();
+
+ uint64_t GetSequenceOldestSnapshots();
+
+ // Return a string that contains all key,value pairs in order,
+ // formatted like "(k1->v1)(k2->v2)".
+ std::string Contents(int cf = 0);
+
+ std::string AllEntriesFor(const Slice& user_key, int cf = 0);
+
+#ifndef ROCKSDB_LITE
+ int NumSortedRuns(int cf = 0);
+
+ uint64_t TotalSize(int cf = 0);
+
+ uint64_t SizeAtLevel(int level);
+
+ size_t TotalLiveFiles(int cf = 0);
+
+ size_t CountLiveFiles();
+
+ int NumTableFilesAtLevel(int level, int cf = 0);
+
+ double CompressionRatioAtLevel(int level, int cf = 0);
+
+ int TotalTableFiles(int cf = 0, int levels = -1);
+#endif // ROCKSDB_LITE
+
+ // Return spread of files per level
+ std::string FilesPerLevel(int cf = 0);
+
+ size_t CountFiles();
+
+ uint64_t Size(const Slice& start, const Slice& limit, int cf = 0);
+
+ void Compact(int cf, const Slice& start, const Slice& limit,
+ uint32_t target_path_id);
+
+ void Compact(int cf, const Slice& start, const Slice& limit);
+
+ void Compact(const Slice& start, const Slice& limit);
+
+ // Do n memtable compactions, each of which produces an sstable
+ // covering the range [small,large].
+ void MakeTables(int n, const std::string& small, const std::string& large,
+ int cf = 0);
+
+ // Prevent pushing of new sstables into deeper levels by adding
+ // tables that cover a specified range to all levels.
+ void FillLevels(const std::string& smallest, const std::string& largest,
+ int cf);
+
+ void MoveFilesToLevel(int level, int cf = 0);
+
+#ifndef ROCKSDB_LITE
+ void DumpFileCounts(const char* label);
+#endif // ROCKSDB_LITE
+
+ std::string DumpSSTableList();
+
+ static void GetSstFiles(Env* env, std::string path,
+ std::vector<std::string>* files);
+
+ int GetSstFileCount(std::string path);
+
+ // this will generate non-overlapping files since it keeps increasing key_idx
+ void GenerateNewFile(Random* rnd, int* key_idx, bool nowait = false);
+
+ void GenerateNewFile(int fd, Random* rnd, int* key_idx, bool nowait = false);
+
+ static const int kNumKeysByGenerateNewRandomFile;
+ static const int KNumKeysByGenerateNewFile = 100;
+
+ void GenerateNewRandomFile(Random* rnd, bool nowait = false);
+
+ std::string IterStatus(Iterator* iter);
+
+ Options OptionsForLogIterTest();
+
+ std::string DummyString(size_t len, char c = 'a');
+
+ void VerifyIterLast(std::string expected_key, int cf = 0);
+
+ // Used to test InplaceUpdate
+
+ // If previous value is nullptr or delta is > than previous value,
+ // sets newValue with delta
+ // If previous value is not empty,
+ // updates previous value with 'b' string of previous value size - 1.
+ static UpdateStatus updateInPlaceSmallerSize(char* prevValue,
+ uint32_t* prevSize, Slice delta,
+ std::string* newValue);
+
+ static UpdateStatus updateInPlaceSmallerVarintSize(char* prevValue,
+ uint32_t* prevSize,
+ Slice delta,
+ std::string* newValue);
+
+ static UpdateStatus updateInPlaceLargerSize(char* prevValue,
+ uint32_t* prevSize, Slice delta,
+ std::string* newValue);
+
+ static UpdateStatus updateInPlaceNoAction(char* prevValue, uint32_t* prevSize,
+ Slice delta, std::string* newValue);
+
+ // Utility method to test InplaceUpdate
+ void validateNumberOfEntries(int numValues, int cf = 0);
+
+ void CopyFile(const std::string& source, const std::string& destination,
+ uint64_t size = 0);
+
+ std::unordered_map<std::string, uint64_t> GetAllSSTFiles(
+ uint64_t* total_size = nullptr);
+
+ std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
+
+ void VerifyDBFromMap(
+ std::map<std::string, std::string> true_data,
+ size_t* total_reads_res = nullptr, bool tailing_iter = false,
+ std::map<std::string, Status> status = std::map<std::string, Status>());
+
+ void VerifyDBInternal(
+ std::vector<std::pair<std::string, std::string>> true_data);
+
+#ifndef ROCKSDB_LITE
+ uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
+ std::string column_family_name);
+#endif // ROCKSDB_LITE
+
+ uint64_t TestGetTickerCount(const Options& options, Tickers ticker_type) {
+ return options.statistics->getTickerCount(ticker_type);
+ }
+
+ uint64_t TestGetAndResetTickerCount(const Options& options,
+ Tickers ticker_type) {
+ return options.statistics->getAndResetTickerCount(ticker_type);
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/db_universal_compaction_test.cc b/storage/rocksdb/rocksdb/db/db_universal_compaction_test.cc
new file mode 100644
index 00000000..61531ae1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_universal_compaction_test.cc
@@ -0,0 +1,2254 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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"
+#if !defined(ROCKSDB_LITE)
+#include "rocksdb/utilities/table_properties_collectors.h"
+#include "test_util/sync_point.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<std::tuple<int, bool>> {
+ public:
+ explicit DBTestUniversalCompactionBase(
+ const std::string& path) : DBTestBase(path) {}
+ 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") {}
+};
+
+namespace {
+void VerifyCompactionResult(
+ const ColumnFamilyMetaData& cf_meta,
+ const std::set<std::string>& 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<CompactionFilter> 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<CompactionFilter>(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_->addon_time_.fetch_add(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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
+ }
+
+ const char* Name() const override { return "DelayFilterFactory"; }
+
+ private:
+ DBTestBase* db_test;
+};
+} // 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);
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(NumSortedRuns(0), 1);
+ }
+ ASSERT_OK(Put(Key(key_idx), ""));
+ 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(new SpecialSkipListFactory(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++) {
+ Put(Key(num * 10), "val");
+ if (num) {
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ Put(Key(30 + num * 10), "val");
+ Put(Key(60 + num * 10), "val");
+ }
+ Put("", "");
+ 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();
+ 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<size_t*>(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);
+ 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);
+ 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), RandomString(&rnd, 10000)));
+ key_idx++;
+ }
+ 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));
+
+ 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<Compaction*>(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), RandomString(&rnd, 10000)));
+ key_idx++;
+ }
+ 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));
+ 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);
+
+ 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<Compaction*>(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), RandomString(&rnd, 10000)));
+ key_idx++;
+ }
+ 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));
+ 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);
+
+ 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, ToString(key), RandomString(&rnd, kTestValueSize)));
+ }
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForCompact();
+ ColumnFamilyMetaData cf_meta;
+ dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta);
+ std::vector<std::string> 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<std::string>(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), RandomString(&rnd, 100)));
+ }
+ ASSERT_OK(Flush());
+
+ for (int i = 200; i < 300; i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 100)));
+ }
+ ASSERT_OK(Flush());
+
+ for (int i = 250; i < 260; i++) {
+ ASSERT_OK(Put(Key(i), RandomString(&rnd, 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_;
+ db_->CompactRange(compact_options, nullptr, nullptr);
+ ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
+}
+
+#ifndef ROCKSDB_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)));
+ }
+
+ 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<int*>(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<std::string> values;
+
+ ASSERT_OK(Flush(1));
+ 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.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<int> num_compactions_running(0);
+ std::atomic<bool> 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)));
+ }
+ 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");
+ 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");
+ 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 // ROCKSDB_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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+
+ if (num < options.level0_file_num_compaction_trigger - 1) {
+ ASSERT_EQ(NumSortedRuns(1), num + 1);
+ }
+ }
+
+ 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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ 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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ 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).
+ 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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ 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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ 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), RandomString(&rnd, 990)));
+ key_idx++;
+ }
+ 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++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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++;
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_LT(TotalSize(), 120000U * 12 * 0.82 + 120000 * 2);
+}
+
+#ifndef ROCKSDB_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<int*>(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<std::string> values;
+
+ ASSERT_OK(Flush(1));
+ 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<int*>(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<std::string> values;
+
+ ASSERT_OK(Flush(1));
+ dbfull()->TEST_WaitForCompact();
+
+ ASSERT_GT(trivial_move, 0);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+#endif // ROCKSDB_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(
+ new SpecialSkipListFactory(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<std::string> filenames;
+ env_->GetChildren(options.db_paths[1].path, &filenames);
+ // Delete archival files.
+ for (size_t i = 0; i < filenames.size(); ++i) {
+ env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
+ }
+ 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(
+ new SpecialSkipListFactory(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<Options> 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<void(int)> 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(new SpecialSkipListFactory(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), RandomString(&rnd, 10000)));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_OK(Flush(1));
+ 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), RandomString(&rnd, 10000)));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_OK(Flush(1));
+ 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_;
+ 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), RandomString(&rnd, 10000)));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
+ dbfull()->TEST_WaitForCompact();
+ }
+ ASSERT_OK(Flush(1));
+ 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(
+ new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
+
+ std::vector<std::string> filenames;
+ env_->GetChildren(options.db_paths[1].path, &filenames);
+ // Delete archival files.
+ for (size_t i = 0; i < filenames.size(); ++i) {
+ env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
+ }
+ 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.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);
+
+ 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.
+ dbfull()->TEST_WaitForFlushMemTable();
+ }
+ if (i == 0) {
+ TEST_SYNC_POINT(
+ "DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0");
+ }
+ }
+ 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(
+ new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
+ options.compaction_options_universal.max_merge_width = kNumFilesTrigger / 2;
+ options.compaction_options_universal.max_size_amplification_percent =
+ static_cast<unsigned int>(-1);
+ options.compaction_style = kCompactionStyleUniversal;
+ options.level0_file_num_compaction_trigger = kNumFilesTrigger;
+ options.num_levels = num_levels_;
+ Reopen(options);
+
+ std::atomic<int> 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);
+ }
+ 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();
+
+ Put("key", "val");
+ Flush();
+ 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) {
+ Put("key", "val");
+ Flush();
+ }
+ 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);
+ 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_;
+ 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_;
+ 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) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ // MoveFilesToLevel(6);
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ for (i = 1999; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GT(NumTableFilesAtLevel(6), 0);
+}
+
+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) {
+ Put(Key(i), "val");
+ }
+ Flush();
+
+ for (i = 1999; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(1, NumTableFilesAtLevel(0));
+}
+
+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) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 500; i < 1000; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 1000; i < 1500; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 1500; i < 2000; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GT(NumTableFilesAtLevel(6), 0);
+
+ for (i = 1999; i < 2333; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 2333; i < 2666; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 2666; i < 2999; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(0, NumTableFilesAtLevel(0));
+ ASSERT_GT(NumTableFilesAtLevel(6), 0);
+ ASSERT_GT(NumTableFilesAtLevel(5), 0);
+
+ for (i = 1900; i < 2100; ++i) {
+ Delete(Key(i));
+ }
+ Flush();
+
+ 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) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 2000; i < 3000; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 3500; i < 4000; ++i) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ for (i = 2900; i < 3100; ++i) {
+ Delete(Key(i));
+ }
+ Flush();
+
+ 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) {
+ Put(Key(i), "val");
+ }
+ Flush();
+ // MoveFilesToLevel(6);
+ dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ for (i = 1999; i < kNumKeys; ++i) {
+ if (i >= kNumKeys - kWindowSize &&
+ i < kNumKeys - kWindowSize + kNumDelsTrigger) {
+ Delete(Key(i));
+ } else {
+ Put(Key(i), "val");
+ }
+ }
+ Flush();
+
+ 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;
+
+ 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_->addon_time_.store(0);
+ Reopen(opts);
+
+ 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<Compaction*>(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"));
+ Flush();
+ ASSERT_EQ(0, periodic_compactions);
+ // Move clock forward so that the flushed file would qualify periodic
+ // compaction.
+ env_->addon_time_.store(48 * 60 * 60 + 100);
+
+ // Another flush would trigger compaction the oldest file.
+ ASSERT_OK(Put("foo", "bar2"));
+ Flush();
+ 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"));
+ Flush();
+ 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_->addon_time_.fetch_add(48 * 60 * 60 + 100);
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(1, periodic_compactions);
+ ASSERT_EQ(0, start_level);
+ ASSERT_EQ(4, output_level);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !defined(ROCKSDB_LITE)
+
+int main(int argc, char** argv) {
+#if !defined(ROCKSDB_LITE)
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ (void) argc;
+ (void) argv;
+ return 0;
+#endif
+}
diff --git a/storage/rocksdb/rocksdb/db/db_wal_test.cc b/storage/rocksdb/rocksdb/db/db_wal_test.cc
new file mode 100644
index 00000000..ef81de80
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_wal_test.cc
@@ -0,0 +1,1586 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/composite_env_wrapper.h"
+#include "options/options_helper.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+class DBWALTest : public DBTestBase {
+ public:
+ DBWALTest() : DBTestBase("/db_wal_test") {}
+
+#if defined(ROCKSDB_PLATFORM_POSIX)
+ 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
+};
+
+// 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<SequentialFile>* r,
+ const EnvOptions& soptions) override {
+ InstrumentedMutexLock l(&env_mutex_);
+ if (f == skipped_wal) {
+ deleted_wal_reopened = true;
+ if (IsWAL(f) && largetest_deleted_wal.size() != 0 &&
+ f.compare(largetest_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 (largetest_deleted_wal.size() == 0 ||
+ largetest_deleted_wal.compare(fname) < 0) {
+ largetest_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 largetest_deleted_wal = "";
+ // number of WALs that were successfully deleted
+ std::atomic<size_t> deleted_wal_cnt = {0};
+ // the WAL whose delete from fs was skipped is reopened during recovery
+ std::atomic<bool> deleted_wal_reopened = {false};
+ // whether a gap in the WALs was detected during recovery
+ std::atomic<bool> gap_in_wals = {false};
+};
+
+class DBWALTestWithEnrichedEnv : public DBTestBase {
+ public:
+ DBWALTestWithEnrichedEnv() : DBTestBase("/db_wal_test") {
+ 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<int*>(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<std::vector<FileMetaData>> 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, IgnoreRecoveredLog) {
+ std::string backup_logs = dbname_ + "/backup_logs";
+
+ do {
+ // delete old files in backup_logs directory
+ env_->CreateDirIfMissing(backup_logs);
+ std::vector<std::string> old_files;
+ env_->GetChildren(backup_logs, &old_files);
+ for (auto& file : old_files) {
+ if (file != "." && file != "..") {
+ 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<std::string> logs;
+ env_->GetChildren(options.wal_dir, &logs);
+ for (auto& log : logs) {
+ if (log != ".." && log != ".") {
+ 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) {
+ if (log != ".." && log != ".") {
+ 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
+ env_->CreateDirIfMissing(options.wal_dir);
+ for (auto& log : logs) {
+ if (log != ".." && log != ".") {
+ 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
+ env_->CreateDirIfMissing(options.wal_dir);
+ for (auto& log : logs) {
+ if (log != ".." && log != ".") {
+ CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
+ // we won't be needing this file no more
+ env_->DeleteFile(backup_logs + "/" + log);
+ }
+ }
+ Status s = TryReopen(options);
+ ASSERT_TRUE(!s.ok());
+ 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<size_t>(
+ options.write_buffer_size + options.write_buffer_size / 10);
+
+ DestroyAndReopen(options);
+
+ std::atomic<int> called(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBTestWalFile.GetPreallocationStatus", [&](void* arg) {
+ ASSERT_TRUE(arg != nullptr);
+ size_t preallocation_size = *(static_cast<size_t*>(arg));
+ ASSERT_EQ(expected_preallocation_size, preallocation_size);
+ called.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Put("", "");
+ Flush();
+ Put("", "");
+ Close();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ASSERT_EQ(2, called.load());
+
+ options.max_total_wal_size = 1000 * 1000;
+ expected_preallocation_size = static_cast<size_t>(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<size_t*>(arg));
+ ASSERT_EQ(expected_preallocation_size, preallocation_size);
+ called.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Put("", "");
+ Flush();
+ Put("", "");
+ Close();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ASSERT_EQ(2, called.load());
+
+ options.db_write_buffer_size = 800 * 1000;
+ expected_preallocation_size =
+ static_cast<size_t>(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<size_t*>(arg));
+ ASSERT_EQ(expected_preallocation_size, preallocation_size);
+ called.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Put("", "");
+ Flush();
+ Put("", "");
+ Close();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ASSERT_EQ(2, called.load());
+
+ expected_preallocation_size = 700 * 1000;
+ std::shared_ptr<WriteBufferManager> write_buffer_manager =
+ std::make_shared<WriteBufferManager>(static_cast<uint64_t>(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<size_t*>(arg));
+ ASSERT_EQ(expected_preallocation_size, preallocation_size);
+ called.fetch_add(1);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Put("", "");
+ Flush();
+ Put("", "");
+ Close();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ASSERT_EQ(2, called.load());
+}
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+
+#ifndef ROCKSDB_LITE
+TEST_F(DBWALTest, FullPurgePreservesRecycledLog) {
+ // 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, FullPurgePreservesLogPendingReuse) {
+ // 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<LogFile>* bad_log_file = nullptr;
+ ASSERT_NOK(dbfull()->GetCurrentWalFile(bad_log_file));
+
+ std::unique_ptr<LogFile> 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] = port::kMaxUint64;
+ }
+ }
+ // 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)));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
+ {
+ auto tables = ListTableFiles(env_, dbname_);
+ ASSERT_EQ(tables.size(), static_cast<size_t>(1));
+ // Make sure 'dobrynia' was flushed: check sst files amount
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(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<uint64_t>(0));
+ // First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(5));
+ // 1 SST for big key + 1 SST for small one
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(2));
+ // 1 SST for all keys
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(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)));
+ dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
+ // 4 memtable are not flushed, 1 sst file
+ {
+ auto tables = ListTableFiles(env_, dbname_);
+ ASSERT_EQ(tables.size(), static_cast<size_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(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)));
+ 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<size_t>(2));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(2));
+ }
+
+ ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
+ options);
+ {
+ std::vector<uint64_t> 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<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
+ static_cast<uint64_t>(3));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
+ static_cast<uint64_t>(1));
+ ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
+ static_cast<uint64_t>(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++) {
+ batch.Put(Key(i), DummyString(128));
+ }
+
+ 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<FaultInjectionTestEnv> 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"));
+ 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 const int kWALFilesCount = 10;
+ // Starting number for the WAL file name like 00010.log
+ static const int kWALFileOffset = 10;
+ // Keys to be written per WAL file
+ static const int kKeysPerWALFile = 133;
+ // Size of the value
+ static const int kValueSize = 96;
+
+ // Create WAL files with values filled in
+ static void FillData(DBWALTest* 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<Cache> table_cache = NewLRUCache(50, 0);
+ EnvOptions env_options;
+ WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
+
+ std::unique_ptr<VersionSet> versions;
+ std::unique_ptr<WalManager> wal_manager;
+ WriteController write_controller;
+
+ versions.reset(new VersionSet(test->dbname_, &db_options, env_options,
+ table_cache.get(), &write_buffer_manager,
+ &write_controller,
+ /*block_cache_tracer=*/nullptr));
+
+ wal_manager.reset(new WalManager(db_options, env_options));
+
+ std::unique_ptr<log::Writer> 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<WritableFile> file;
+ ASSERT_OK(db_options.env->NewWritableFile(fname, &file, env_options));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), fname, env_options));
+ current_log_writer.reset(
+ new log::Writer(std::move(file_writer), current_log_number,
+ db_options.recycle_log_file_num > 0));
+
+ WriteBatch batch;
+ for (int i = 0; i < kKeysPerWALFile; i++) {
+ std::string key = "key" + ToString((*count)++);
+ std::string value = test->DummyString(kValueSize);
+ assert(current_log_writer.get() != nullptr);
+ uint64_t seq = versions->LastSequence() + 1;
+ batch.Clear();
+ batch.Put(key, value);
+ WriteBatchInternal::SetSequence(&batch, seq);
+ 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(DBWALTest* 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(DBWALTest* test) {
+ size_t count = 0;
+ for (size_t i = 0; i < kWALFilesCount * kKeysPerWALFile; i++) {
+ if (test->Get("key" + ToString(i)) != "NOT_FOUND") {
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ // Manuall corrupt the specified WAL
+ static void CorruptWAL(DBWALTest* 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_EQ(0, truncate(fname.c_str(), static_cast<int64_t>(size * off)));
+ } else {
+ InduceCorruption(fname, static_cast<size_t>(size * off + 8),
+ static_cast<size_t>(size * len));
+ }
+ }
+
+ // Overwrite data with 'a' from offset for length len
+ static void InduceCorruption(const std::string& filename, size_t offset,
+ size_t len) {
+ ASSERT_GT(len, 0U);
+
+ int fd = open(filename.c_str(), O_RDWR);
+
+ // On windows long is 32-bit
+ ASSERT_LE(offset, std::numeric_limits<long>::max());
+
+ ASSERT_GT(fd, 0);
+ ASSERT_EQ(offset, lseek(fd, static_cast<long>(offset), SEEK_SET));
+
+ void* buf = alloca(len);
+ memset(buf, 'b', len);
+ ASSERT_EQ(len, write(fd, buf, static_cast<unsigned int>(len)));
+
+ close(fd);
+ }
+};
+
+// 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_F(DBWALTest, kTolerateCorruptedTailRecords) {
+ const int jstart = RecoveryTestHelper::kWALFileOffset;
+ const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
+
+ for (auto trunc : {true, false}) { /* Corruption style */
+ for (int i = 0; i < 3; i++) { /* Corruption offset position */
+ for (int j = jstart; j < jend; j++) { /* 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, /*off=*/i * .3,
+ /*len%=*/.1, /*wal=*/j, trunc);
+
+ if (trunc) {
+ options.wal_recovery_mode =
+ WALRecoveryMode::kTolerateCorruptedTailRecords;
+ options.create_if_missing = false;
+ ASSERT_OK(TryReopen(options));
+ const size_t recovered_row_count = RecoveryTestHelper::GetData(this);
+ ASSERT_TRUE(i == 0 || recovered_row_count > 0);
+ ASSERT_LT(recovered_row_count, row_count);
+ } else {
+ options.wal_recovery_mode =
+ WALRecoveryMode::kTolerateCorruptedTailRecords;
+ 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_F(DBWALTest, kAbsoluteConsistency) {
+ const int jstart = RecoveryTestHelper::kWALFileOffset;
+ const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
+
+ // Verify clean slate behavior
+ Options options = CurrentOptions();
+ const size_t row_count = RecoveryTestHelper::FillData(this, &options);
+ options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
+ options.create_if_missing = false;
+ ASSERT_OK(TryReopen(options));
+ ASSERT_EQ(RecoveryTestHelper::GetData(this), row_count);
+
+ for (auto trunc : {true, false}) { /* Corruption style */
+ for (int i = 0; i < 4; i++) { /* Corruption offset position */
+ if (trunc && i == 0) {
+ continue;
+ }
+
+ for (int j = jstart; j < jend; j++) { /* wal files */
+ // fill with new date
+ RecoveryTestHelper::FillData(this, &options);
+ // corrupt the wal
+ RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
+ /*len%=*/.1, j, 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
+ RecoveryTestHelper::InduceCorruption(
+ fname, static_cast<size_t>(offset_to_corrupt), static_cast<size_t>(4));
+ ASSERT_OK(Put(2, "key4", "val4"));
+ ASSERT_OK(Put(1, "key5", "val5"));
+ Flush(2);
+
+ // PIT recovery & verify
+ options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
+ ASSERT_NOK(TryReopenWithColumnFamilies({"default", "one", "two"}, options));
+}
+
+// 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_F(DBWALTest, kPointInTimeRecovery) {
+ const int jstart = RecoveryTestHelper::kWALFileOffset;
+ const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
+ const int maxkeys =
+ RecoveryTestHelper::kWALFilesCount * RecoveryTestHelper::kKeysPerWALFile;
+
+ for (auto trunc : {true, false}) { /* Corruption style */
+ for (int i = 0; i < 4; i++) { /* Offset of corruption */
+ for (int j = jstart; j < jend; j++) { /* WAL file */
+ // Fill data for testing
+ Options options = CurrentOptions();
+ const size_t row_count = RecoveryTestHelper::FillData(this, &options);
+
+ // Corrupt the wal
+ RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
+ /*len%=*/.1, j, 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);
+
+ bool expect_data = true;
+ for (size_t k = 0; k < maxkeys; ++k) {
+ bool found = Get("key" + ToString(i)) != "NOT_FOUND";
+ if (expect_data && !found) {
+ expect_data = false;
+ }
+ ASSERT_EQ(found, expect_data);
+ }
+
+ const size_t min = RecoveryTestHelper::kKeysPerWALFile *
+ (j - RecoveryTestHelper::kWALFileOffset);
+ ASSERT_GE(recovered_row_count, min);
+ if (!trunc && i != 0) {
+ const size_t max = RecoveryTestHelper::kKeysPerWALFile *
+ (j - 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_F(DBWALTest, kSkipAnyCorruptedRecords) {
+ const int jstart = RecoveryTestHelper::kWALFileOffset;
+ const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
+
+ for (auto trunc : {true, false}) { /* Corruption style */
+ for (int i = 0; i < 4; i++) { /* Corruption offset */
+ for (int j = jstart; j < jend; j++) { /* wal files */
+ // Fill data for testing
+ Options options = CurrentOptions();
+ const size_t row_count = RecoveryTestHelper::FillData(this, &options);
+
+ // Corrupt the WAL
+ RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
+ /*len%=*/.1, j, 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(i != 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
+ 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" + ToString(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
+ 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;
+ dbfull()->GetSortedWalFiles(log_files);
+ 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));
+ Flush(1);
+ ASSERT_EQ(1, countWalFiles());
+ ASSERT_OK(Put(0, "key3", kSmallValue));
+ ASSERT_OK(Put(2, "key4", kLargeValue));
+ 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());
+ Flush(1);
+ ASSERT_OK(Put(2, "key7", kLargeValue));
+ 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_F(DBWALTest, RecoverFromCorruptedWALWithoutFlush) {
+ const int jstart = RecoveryTestHelper::kWALFileOffset;
+ const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
+ 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<std::pair<std::string, std::string>> 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;
+ };
+ for (auto& mode : wal_recovery_mode_string_map) {
+ options.wal_recovery_mode = mode.second;
+ for (auto trunc : {true, false}) {
+ for (int i = 0; i < 4; i++) {
+ for (int j = jstart; j < jend; j++) {
+ // Create corrupted WAL
+ RecoveryTestHelper::FillData(this, &options);
+ RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
+ /*len%=*/.1, /*wal=*/j, 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));
+ continue;
+ }
+ ASSERT_OK(TryReopen(options));
+ // Append some more data.
+ for (int k = 0; k < kAppendKeys; k++) {
+ std::string key = "extra_key" + ToString(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) {
+ class TestFlushListener : public EventListener {
+ public:
+ std::atomic<int> count{0};
+
+ TestFlushListener() = default;
+
+ void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
+ count++;
+ assert(FlushReason::kWriteBufferManager == flush_job_info.flush_reason);
+ }
+ };
+ std::shared_ptr<TestFlushListener> test_listener =
+ std::make_shared<TestFlushListener>();
+
+ 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" + ToString(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"));
+ dbfull()->TEST_WaitForFlushMemTable();
+ // 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.avoid_flush_during_recovery = true;
+ 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);
+}
+#endif // ROCKSDB_FALLOCATE_PRESENT
+#endif // ROCKSDB_PLATFORM_POSIX
+
+#endif // ROCKSDB_LITE
+
+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;
+ batch.Put("foo", "bar");
+ batch.MarkWalTerminationPoint();
+ 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"));
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/db_write_test.cc b/storage/rocksdb/rocksdb/db/db_write_test.cc
new file mode 100644
index 00000000..cc1aaac0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/db_write_test.cc
@@ -0,0 +1,329 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <memory>
+#include <thread>
+#include <vector>
+#include <fstream>
+#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/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Test variations of WriteImpl.
+class DBWriteTest : public DBTestBase, public testing::WithParamInterface<int> {
+ public:
+ DBWriteTest() : DBTestBase("/db_write_test") {}
+
+ Options GetOptions() { return DBTestBase::GetOptions(GetParam()); }
+
+ void Open() { DBTestBase::Reopen(GetOptions()); }
+};
+
+// 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, WriteThreadHangOnWriteStall) {
+ Options options = GetOptions();
+ options.level0_stop_writes_trigger = options.level0_slowdown_writes_trigger = 4;
+ std::vector<port::Thread> threads;
+ std::atomic<int> thread_num(0);
+ port::Mutex mutex;
+ port::CondVar cv(&mutex);
+
+ Reopen(options);
+
+ std::function<void()> write_slowdown_func = [&]() {
+ int a = thread_num.fetch_add(1);
+ std::string key = "foo" + std::to_string(a);
+ WriteOptions wo;
+ wo.no_slowdown = false;
+ dbfull()->Put(wo, key, "bar");
+ };
+ std::function<void()> 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;
+ dbfull()->Put(wo, key, "bar");
+ };
+ std::function<void(void *)> unblock_main_thread_func = [&](void *) {
+ mutex.Lock();
+ cv.SignalAll();
+ mutex.Unlock();
+ };
+
+ // Create 3 L0 files and schedule 4th without waiting
+ Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
+ Flush();
+ Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
+ Flush();
+ Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
+ Flush();
+ 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;
+ dbfull()->Flush(fopt);
+
+ // Create a mix of slowdown/no_slowdown write threads
+ mutex.Lock();
+ // First leader
+ threads.emplace_back(write_slowdown_func);
+ cv.Wait();
+ // Second leader. Will stall writes
+ threads.emplace_back(write_slowdown_func);
+ cv.Wait();
+ threads.emplace_back(write_no_slowdown_func);
+ cv.Wait();
+ threads.emplace_back(write_slowdown_func);
+ cv.Wait();
+ threads.emplace_back(write_no_slowdown_func);
+ cv.Wait();
+ threads.emplace_back(write_slowdown_func);
+ cv.Wait();
+ mutex.Unlock();
+
+ TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:1");
+ 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();
+ }
+}
+
+TEST_P(DBWriteTest, IOErrorOnWALWritePropagateToWriteThreadFollower) {
+ constexpr int kNumThreads = 5;
+ std::unique_ptr<FaultInjectionTestEnv> mock_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ Options options = GetOptions();
+ options.env = mock_env.get();
+ Reopen(options);
+ std::atomic<int> ready_count{0};
+ std::atomic<int> leader_count{0};
+ std::vector<port::Thread> 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<WriteThread::Writer*>(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" + ToString(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);
+ // Close before mock_env destruct.
+ Close();
+}
+
+TEST_P(DBWriteTest, ManualWalFlushInEffect) {
+ Options options = GetOptions();
+ Reopen(options);
+ // try the 1st WAL created during open
+ ASSERT_TRUE(Put("key" + ToString(0), "value").ok());
+ ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
+ ASSERT_TRUE(dbfull()->FlushWAL(false).ok());
+ ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty());
+ // try the 2nd wal created during SwitchWAL
+ dbfull()->TEST_SwitchWAL();
+ ASSERT_TRUE(Put("key" + ToString(0), "value").ok());
+ ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
+ ASSERT_TRUE(dbfull()->FlushWAL(false).ok());
+ ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty());
+}
+
+TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
+ std::unique_ptr<FaultInjectionTestEnv> mock_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ 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" + ToString(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_FALSE(res.ok());
+ }
+ }
+ // Close before mock_env destruct.
+ Close();
+}
+
+TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) {
+ Random rnd(301);
+ std::unique_ptr<FaultInjectionTestEnv> mock_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ 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), RandomString(&rnd, 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.
+TEST_P(DBWriteTest, LockWalInEffect) {
+ Options options = GetOptions();
+ Reopen(options);
+ // try the 1st WAL created during open
+ ASSERT_OK(Put("key" + ToString(0), "value"));
+ ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
+ ASSERT_OK(dbfull()->LockWAL());
+ ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
+ ASSERT_OK(dbfull()->UnlockWAL());
+ // try the 2nd wal created during SwitchWAL
+ dbfull()->TEST_SwitchWAL();
+ ASSERT_OK(Put("key" + ToString(0), "value"));
+ ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
+ ASSERT_OK(dbfull()->LockWAL());
+ ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
+ ASSERT_OK(dbfull()->UnlockWAL());
+}
+
+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);
+ 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);
+ this->Put(wal_key, wal_value, write_option_default);
+ dbfull()->SyncWAL();
+ }
+ return 0;
+ });
+ }
+ 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);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/dbformat.cc b/storage/rocksdb/rocksdb/db/dbformat.cc
new file mode 100644
index 00000000..e10af2b8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/dbformat.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <cinttypes>
+#include "monitoring/perf_context_imp.h"
+#include "port/port.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// kValueTypeForSeek defines the ValueType that should be passed when
+// constructing a ParsedInternalKey object for seeking to a particular
+// sequence number (since we sort sequence numbers in decreasing order
+// and the value type is embedded as the low 8 bits in the sequence
+// number in internal keys, we need to use the highest-numbered
+// ValueType, not the lowest).
+const ValueType kValueTypeForSeek = kTypeBlobIndex;
+const ValueType kValueTypeForSeekForPrev = kTypeDeletion;
+
+uint64_t PackSequenceAndType(uint64_t seq, ValueType t) {
+ assert(seq <= kMaxSequenceNumber);
+ assert(IsExtendedValueType(t));
+ return (seq << 8) | t;
+}
+
+EntryType GetEntryType(ValueType value_type) {
+ switch (value_type) {
+ case kTypeValue:
+ return kEntryPut;
+ case kTypeDeletion:
+ return kEntryDelete;
+ case kTypeSingleDeletion:
+ return kEntrySingleDelete;
+ case kTypeMerge:
+ return kEntryMerge;
+ case kTypeRangeDeletion:
+ return kEntryRangeDeletion;
+ case kTypeBlobIndex:
+ return kEntryBlobIndex;
+ default:
+ return kEntryOther;
+ }
+}
+
+bool ParseFullKey(const Slice& internal_key, FullKey* fkey) {
+ ParsedInternalKey ikey;
+ if (!ParseInternalKey(internal_key, &ikey)) {
+ return false;
+ }
+ fkey->user_key = ikey.user_key;
+ fkey->sequence = ikey.sequence;
+ fkey->type = GetEntryType(ikey.type);
+ return true;
+}
+
+void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t) {
+ *seq = packed >> 8;
+ *t = static_cast<ValueType>(packed & 0xff);
+
+ assert(*seq <= kMaxSequenceNumber);
+ assert(IsExtendedValueType(*t));
+}
+
+void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
+ result->append(key.user_key.data(), key.user_key.size());
+ PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
+}
+
+void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
+ ValueType t) {
+ PutFixed64(result, PackSequenceAndType(s, t));
+}
+
+std::string ParsedInternalKey::DebugString(bool hex) const {
+ char buf[50];
+ snprintf(buf, sizeof(buf), "' seq:%" PRIu64 ", type:%d", sequence,
+ static_cast<int>(type));
+ std::string result = "'";
+ result += user_key.ToString(hex);
+ result += buf;
+ return result;
+}
+
+std::string InternalKey::DebugString(bool hex) const {
+ std::string result;
+ ParsedInternalKey parsed;
+ if (ParseInternalKey(rep_, &parsed)) {
+ result = parsed.DebugString(hex);
+ } else {
+ result = "(bad)";
+ result.append(EscapeString(rep_));
+ }
+ return result;
+}
+
+const char* InternalKeyComparator::Name() const { return name_.c_str(); }
+
+int InternalKeyComparator::Compare(const ParsedInternalKey& a,
+ const ParsedInternalKey& b) const {
+ // Order by:
+ // increasing user key (according to user-supplied comparator)
+ // decreasing sequence number
+ // decreasing type (though sequence# should be enough to disambiguate)
+ int r = user_comparator_.Compare(a.user_key, b.user_key);
+ if (r == 0) {
+ if (a.sequence > b.sequence) {
+ r = -1;
+ } else if (a.sequence < b.sequence) {
+ r = +1;
+ } else if (a.type > b.type) {
+ r = -1;
+ } else if (a.type < b.type) {
+ r = +1;
+ }
+ }
+ return r;
+}
+
+void InternalKeyComparator::FindShortestSeparator(std::string* start,
+ const Slice& limit) const {
+ // Attempt to shorten the user portion of the key
+ Slice user_start = ExtractUserKey(*start);
+ Slice user_limit = ExtractUserKey(limit);
+ std::string tmp(user_start.data(), user_start.size());
+ user_comparator_.FindShortestSeparator(&tmp, user_limit);
+ if (tmp.size() <= user_start.size() &&
+ user_comparator_.Compare(user_start, tmp) < 0) {
+ // User key has become shorter physically, but larger logically.
+ // Tack on the earliest possible number to the shortened user key.
+ PutFixed64(&tmp,
+ PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek));
+ assert(this->Compare(*start, tmp) < 0);
+ assert(this->Compare(tmp, limit) < 0);
+ start->swap(tmp);
+ }
+}
+
+void InternalKeyComparator::FindShortSuccessor(std::string* key) const {
+ Slice user_key = ExtractUserKey(*key);
+ std::string tmp(user_key.data(), user_key.size());
+ user_comparator_.FindShortSuccessor(&tmp);
+ if (tmp.size() <= user_key.size() &&
+ user_comparator_.Compare(user_key, tmp) < 0) {
+ // User key has become shorter physically, but larger logically.
+ // Tack on the earliest possible number to the shortened user key.
+ PutFixed64(&tmp,
+ PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek));
+ assert(this->Compare(*key, tmp) < 0);
+ key->swap(tmp);
+ }
+}
+
+LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s,
+ const Slice* ts) {
+ size_t usize = _user_key.size();
+ size_t ts_sz = (nullptr == ts) ? 0 : ts->size();
+ size_t needed = usize + ts_sz + 13; // A conservative estimate
+ char* dst;
+ if (needed <= sizeof(space_)) {
+ dst = space_;
+ } else {
+ dst = new char[needed];
+ }
+ start_ = dst;
+ // NOTE: We don't support users keys of more than 2GB :)
+ dst = EncodeVarint32(dst, static_cast<uint32_t>(usize + ts_sz + 8));
+ kstart_ = dst;
+ memcpy(dst, _user_key.data(), usize);
+ dst += usize;
+ if (nullptr != ts) {
+ memcpy(dst, ts->data(), ts_sz);
+ dst += ts_sz;
+ }
+ EncodeFixed64(dst, PackSequenceAndType(s, kValueTypeForSeek));
+ dst += 8;
+ end_ = dst;
+}
+
+void IterKey::EnlargeBuffer(size_t key_size) {
+ // If size is smaller than buffer size, continue using current buffer,
+ // or the static allocated one, as default
+ assert(key_size > buf_size_);
+ // Need to enlarge the buffer.
+ ResetBuffer();
+ buf_ = new char[key_size];
+ buf_size_ = key_size;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/dbformat.h b/storage/rocksdb/rocksdb/db/dbformat.h
new file mode 100644
index 00000000..de98be8d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/dbformat.h
@@ -0,0 +1,671 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <memory>
+#include <string>
+#include <utility>
+#include "db/lookup_key.h"
+#include "db/merge_context.h"
+#include "logging/logging.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "rocksdb/types.h"
+#include "util/coding.h"
+#include "util/user_comparator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// The file declares data structures and functions that deal with internal
+// keys.
+// Each internal key contains a user key, a sequence number (SequenceNumber)
+// and a type (ValueType), and they are usually encoded together.
+// There are some related helper classes here.
+
+class InternalKey;
+
+// Value types encoded as the last component of internal keys.
+// DO NOT CHANGE THESE ENUM VALUES: they are embedded in the on-disk
+// data structures.
+// The highest bit of the value type needs to be reserved to SST tables
+// for them to do more flexible encoding.
+enum ValueType : unsigned char {
+ kTypeDeletion = 0x0,
+ kTypeValue = 0x1,
+ kTypeMerge = 0x2,
+ kTypeLogData = 0x3, // WAL only.
+ kTypeColumnFamilyDeletion = 0x4, // WAL only.
+ kTypeColumnFamilyValue = 0x5, // WAL only.
+ kTypeColumnFamilyMerge = 0x6, // WAL only.
+ kTypeSingleDeletion = 0x7,
+ kTypeColumnFamilySingleDeletion = 0x8, // WAL only.
+ kTypeBeginPrepareXID = 0x9, // WAL only.
+ kTypeEndPrepareXID = 0xA, // WAL only.
+ kTypeCommitXID = 0xB, // WAL only.
+ kTypeRollbackXID = 0xC, // WAL only.
+ kTypeNoop = 0xD, // WAL only.
+ kTypeColumnFamilyRangeDeletion = 0xE, // WAL only.
+ kTypeRangeDeletion = 0xF, // meta block
+ kTypeColumnFamilyBlobIndex = 0x10, // Blob DB only
+ kTypeBlobIndex = 0x11, // Blob DB only
+ // When the prepared record is also persisted in db, we use a different
+ // record. This is to ensure that the WAL that is generated by a WritePolicy
+ // is not mistakenly read by another, which would result into data
+ // inconsistency.
+ kTypeBeginPersistedPrepareXID = 0x12, // WAL only.
+ // Similar to kTypeBeginPersistedPrepareXID, this is to ensure that WAL
+ // generated by WriteUnprepared write policy is not mistakenly read by
+ // another.
+ kTypeBeginUnprepareXID = 0x13, // WAL only.
+ kMaxValue = 0x7F // Not used for storing records.
+};
+
+// Defined in dbformat.cc
+extern const ValueType kValueTypeForSeek;
+extern const ValueType kValueTypeForSeekForPrev;
+
+// Checks whether a type is an inline value type
+// (i.e. a type used in memtable skiplist and sst file datablock).
+inline bool IsValueType(ValueType t) {
+ return t <= kTypeMerge || t == kTypeSingleDeletion || t == kTypeBlobIndex;
+}
+
+// Checks whether a type is from user operation
+// kTypeRangeDeletion is in meta block so this API is separated from above
+inline bool IsExtendedValueType(ValueType t) {
+ return IsValueType(t) || t == kTypeRangeDeletion;
+}
+
+// We leave eight bits empty at the bottom so a type and sequence#
+// can be packed together into 64-bits.
+static const SequenceNumber kMaxSequenceNumber = ((0x1ull << 56) - 1);
+
+static const SequenceNumber kDisableGlobalSequenceNumber = port::kMaxUint64;
+
+// The data structure that represents an internal key in the way that user_key,
+// sequence number and type are stored in separated forms.
+struct ParsedInternalKey {
+ Slice user_key;
+ SequenceNumber sequence;
+ ValueType type;
+
+ ParsedInternalKey()
+ : sequence(kMaxSequenceNumber) // Make code analyzer happy
+ {} // Intentionally left uninitialized (for speed)
+ ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
+ : user_key(u), sequence(seq), type(t) {}
+ std::string DebugString(bool hex = false) const;
+
+ void clear() {
+ user_key.clear();
+ sequence = 0;
+ type = kTypeDeletion;
+ }
+};
+
+// Return the length of the encoding of "key".
+inline size_t InternalKeyEncodingLength(const ParsedInternalKey& key) {
+ return key.user_key.size() + 8;
+}
+
+// Pack a sequence number and a ValueType into a uint64_t
+extern uint64_t PackSequenceAndType(uint64_t seq, ValueType t);
+
+// Given the result of PackSequenceAndType, store the sequence number in *seq
+// and the ValueType in *t.
+extern void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t);
+
+EntryType GetEntryType(ValueType value_type);
+
+// Append the serialization of "key" to *result.
+extern void AppendInternalKey(std::string* result,
+ const ParsedInternalKey& key);
+// Serialized internal key consists of user key followed by footer.
+// This function appends the footer to *result, assuming that *result already
+// contains the user key at the end.
+extern void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
+ ValueType t);
+
+// Attempt to parse an internal key from "internal_key". On success,
+// stores the parsed data in "*result", and returns true.
+//
+// On error, returns false, leaves "*result" in an undefined state.
+extern bool ParseInternalKey(const Slice& internal_key,
+ ParsedInternalKey* result);
+
+// Returns the user key portion of an internal key.
+inline Slice ExtractUserKey(const Slice& internal_key) {
+ assert(internal_key.size() >= 8);
+ return Slice(internal_key.data(), internal_key.size() - 8);
+}
+
+inline Slice ExtractUserKeyAndStripTimestamp(const Slice& internal_key,
+ size_t ts_sz) {
+ assert(internal_key.size() >= 8 + ts_sz);
+ return Slice(internal_key.data(), internal_key.size() - 8 - ts_sz);
+}
+
+inline Slice StripTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
+ assert(user_key.size() >= ts_sz);
+ return Slice(user_key.data(), user_key.size() - ts_sz);
+}
+
+inline uint64_t ExtractInternalKeyFooter(const Slice& internal_key) {
+ assert(internal_key.size() >= 8);
+ const size_t n = internal_key.size();
+ return DecodeFixed64(internal_key.data() + n - 8);
+}
+
+inline ValueType ExtractValueType(const Slice& internal_key) {
+ uint64_t num = ExtractInternalKeyFooter(internal_key);
+ unsigned char c = num & 0xff;
+ return static_cast<ValueType>(c);
+}
+
+// A comparator for internal keys that uses a specified comparator for
+// the user key portion and breaks ties by decreasing sequence number.
+class InternalKeyComparator
+#ifdef NDEBUG
+ final
+#endif
+ : public Comparator {
+ private:
+ UserComparatorWrapper user_comparator_;
+ std::string name_;
+
+ public:
+ explicit InternalKeyComparator(const Comparator* c)
+ : user_comparator_(c),
+ name_("rocksdb.InternalKeyComparator:" +
+ std::string(user_comparator_.Name())) {}
+ virtual ~InternalKeyComparator() {}
+
+ virtual const char* Name() const override;
+ virtual int Compare(const Slice& a, const Slice& b) const override;
+ // Same as Compare except that it excludes the value type from comparison
+ virtual int CompareKeySeq(const Slice& a, const Slice& b) const;
+ virtual void FindShortestSeparator(std::string* start,
+ const Slice& limit) const override;
+ virtual void FindShortSuccessor(std::string* key) const override;
+
+ const Comparator* user_comparator() const {
+ return user_comparator_.user_comparator();
+ }
+
+ int Compare(const InternalKey& a, const InternalKey& b) const;
+ int Compare(const ParsedInternalKey& a, const ParsedInternalKey& b) const;
+ virtual const Comparator* GetRootComparator() const override {
+ return user_comparator_.GetRootComparator();
+ }
+};
+
+// The class represent the internal key in encoded form.
+class InternalKey {
+ private:
+ std::string rep_;
+
+ public:
+ InternalKey() {} // Leave rep_ as empty to indicate it is invalid
+ InternalKey(const Slice& _user_key, SequenceNumber s, ValueType t) {
+ AppendInternalKey(&rep_, ParsedInternalKey(_user_key, s, t));
+ }
+
+ // sets the internal key to be bigger or equal to all internal keys with this
+ // user key
+ void SetMaxPossibleForUserKey(const Slice& _user_key) {
+ AppendInternalKey(
+ &rep_, ParsedInternalKey(_user_key, 0, static_cast<ValueType>(0)));
+ }
+
+ // sets the internal key to be smaller or equal to all internal keys with this
+ // user key
+ void SetMinPossibleForUserKey(const Slice& _user_key) {
+ AppendInternalKey(&rep_, ParsedInternalKey(_user_key, kMaxSequenceNumber,
+ kValueTypeForSeek));
+ }
+
+ bool Valid() const {
+ ParsedInternalKey parsed;
+ return ParseInternalKey(Slice(rep_), &parsed);
+ }
+
+ void DecodeFrom(const Slice& s) { rep_.assign(s.data(), s.size()); }
+ Slice Encode() const {
+ assert(!rep_.empty());
+ return rep_;
+ }
+
+ Slice user_key() const { return ExtractUserKey(rep_); }
+ size_t size() { return rep_.size(); }
+
+ void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
+ SetFrom(ParsedInternalKey(_user_key, s, t));
+ }
+
+ void SetFrom(const ParsedInternalKey& p) {
+ rep_.clear();
+ AppendInternalKey(&rep_, p);
+ }
+
+ void Clear() { rep_.clear(); }
+
+ // The underlying representation.
+ // Intended only to be used together with ConvertFromUserKey().
+ std::string* rep() { return &rep_; }
+
+ // Assuming that *rep() contains a user key, this method makes internal key
+ // out of it in-place. This saves a memcpy compared to Set()/SetFrom().
+ void ConvertFromUserKey(SequenceNumber s, ValueType t) {
+ AppendInternalKeyFooter(&rep_, s, t);
+ }
+
+ std::string DebugString(bool hex = false) const;
+};
+
+inline int InternalKeyComparator::Compare(const InternalKey& a,
+ const InternalKey& b) const {
+ return Compare(a.Encode(), b.Encode());
+}
+
+inline bool ParseInternalKey(const Slice& internal_key,
+ ParsedInternalKey* result) {
+ const size_t n = internal_key.size();
+ if (n < 8) return false;
+ uint64_t num = DecodeFixed64(internal_key.data() + n - 8);
+ unsigned char c = num & 0xff;
+ result->sequence = num >> 8;
+ result->type = static_cast<ValueType>(c);
+ assert(result->type <= ValueType::kMaxValue);
+ result->user_key = Slice(internal_key.data(), n - 8);
+ return IsExtendedValueType(result->type);
+}
+
+// Update the sequence number in the internal key.
+// Guarantees not to invalidate ikey.data().
+inline void UpdateInternalKey(std::string* ikey, uint64_t seq, ValueType t) {
+ size_t ikey_sz = ikey->size();
+ assert(ikey_sz >= 8);
+ uint64_t newval = (seq << 8) | t;
+
+ // Note: Since C++11, strings are guaranteed to be stored contiguously and
+ // string::operator[]() is guaranteed not to change ikey.data().
+ EncodeFixed64(&(*ikey)[ikey_sz - 8], newval);
+}
+
+// Get the sequence number from the internal key
+inline uint64_t GetInternalKeySeqno(const Slice& internal_key) {
+ const size_t n = internal_key.size();
+ assert(n >= 8);
+ uint64_t num = DecodeFixed64(internal_key.data() + n - 8);
+ return num >> 8;
+}
+
+// The class to store keys in an efficient way. It allows:
+// 1. Users can either copy the key into it, or have it point to an unowned
+// address.
+// 2. For copied key, a short inline buffer is kept to reduce memory
+// allocation for smaller keys.
+// 3. It tracks user key or internal key, and allow conversion between them.
+class IterKey {
+ public:
+ IterKey()
+ : buf_(space_),
+ key_(buf_),
+ key_size_(0),
+ buf_size_(sizeof(space_)),
+ is_user_key_(true) {}
+ // No copying allowed
+ IterKey(const IterKey&) = delete;
+ void operator=(const IterKey&) = delete;
+
+ ~IterKey() { ResetBuffer(); }
+
+ // The bool will be picked up by the next calls to SetKey
+ void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; }
+
+ // Returns the key in whichever format that was provided to KeyIter
+ Slice GetKey() const { return Slice(key_, key_size_); }
+
+ Slice GetInternalKey() const {
+ assert(!IsUserKey());
+ return Slice(key_, key_size_);
+ }
+
+ Slice GetUserKey() const {
+ if (IsUserKey()) {
+ return Slice(key_, key_size_);
+ } else {
+ assert(key_size_ >= 8);
+ return Slice(key_, key_size_ - 8);
+ }
+ }
+
+ size_t Size() const { return key_size_; }
+
+ void Clear() { key_size_ = 0; }
+
+ // Append "non_shared_data" to its back, from "shared_len"
+ // This function is used in Block::Iter::ParseNextKey
+ // shared_len: bytes in [0, shard_len-1] would be remained
+ // non_shared_data: data to be append, its length must be >= non_shared_len
+ void TrimAppend(const size_t shared_len, const char* non_shared_data,
+ const size_t non_shared_len) {
+ assert(shared_len <= key_size_);
+ size_t total_size = shared_len + non_shared_len;
+
+ if (IsKeyPinned() /* key is not in buf_ */) {
+ // Copy the key from external memory to buf_ (copy shared_len bytes)
+ EnlargeBufferIfNeeded(total_size);
+ memcpy(buf_, key_, shared_len);
+ } else if (total_size > buf_size_) {
+ // Need to allocate space, delete previous space
+ char* p = new char[total_size];
+ memcpy(p, key_, shared_len);
+
+ if (buf_ != space_) {
+ delete[] buf_;
+ }
+
+ buf_ = p;
+ buf_size_ = total_size;
+ }
+
+ memcpy(buf_ + shared_len, non_shared_data, non_shared_len);
+ key_ = buf_;
+ key_size_ = total_size;
+ }
+
+ Slice SetKey(const Slice& key, bool copy = true) {
+ // is_user_key_ expected to be set already via SetIsUserKey
+ return SetKeyImpl(key, copy);
+ }
+
+ Slice SetUserKey(const Slice& key, bool copy = true) {
+ is_user_key_ = true;
+ return SetKeyImpl(key, copy);
+ }
+
+ Slice SetInternalKey(const Slice& key, bool copy = true) {
+ is_user_key_ = false;
+ return SetKeyImpl(key, copy);
+ }
+
+ // Copies the content of key, updates the reference to the user key in ikey
+ // and returns a Slice referencing the new copy.
+ Slice SetInternalKey(const Slice& key, ParsedInternalKey* ikey) {
+ size_t key_n = key.size();
+ assert(key_n >= 8);
+ SetInternalKey(key);
+ ikey->user_key = Slice(key_, key_n - 8);
+ return Slice(key_, key_n);
+ }
+
+ // Copy the key into IterKey own buf_
+ void OwnKey() {
+ assert(IsKeyPinned() == true);
+
+ Reserve(key_size_);
+ memcpy(buf_, key_, key_size_);
+ key_ = buf_;
+ }
+
+ // Update the sequence number in the internal key. Guarantees not to
+ // invalidate slices to the key (and the user key).
+ void UpdateInternalKey(uint64_t seq, ValueType t) {
+ assert(!IsKeyPinned());
+ assert(key_size_ >= 8);
+ uint64_t newval = (seq << 8) | t;
+ EncodeFixed64(&buf_[key_size_ - 8], newval);
+ }
+
+ bool IsKeyPinned() const { return (key_ != buf_); }
+
+ void SetInternalKey(const Slice& key_prefix, const Slice& user_key,
+ SequenceNumber s,
+ ValueType value_type = kValueTypeForSeek) {
+ size_t psize = key_prefix.size();
+ size_t usize = user_key.size();
+ EnlargeBufferIfNeeded(psize + usize + sizeof(uint64_t));
+ if (psize > 0) {
+ memcpy(buf_, key_prefix.data(), psize);
+ }
+ memcpy(buf_ + psize, user_key.data(), usize);
+ EncodeFixed64(buf_ + usize + psize, PackSequenceAndType(s, value_type));
+
+ key_ = buf_;
+ key_size_ = psize + usize + sizeof(uint64_t);
+ is_user_key_ = false;
+ }
+
+ void SetInternalKey(const Slice& user_key, SequenceNumber s,
+ ValueType value_type = kValueTypeForSeek) {
+ SetInternalKey(Slice(), user_key, s, value_type);
+ }
+
+ void Reserve(size_t size) {
+ EnlargeBufferIfNeeded(size);
+ key_size_ = size;
+ }
+
+ void SetInternalKey(const ParsedInternalKey& parsed_key) {
+ SetInternalKey(Slice(), parsed_key);
+ }
+
+ void SetInternalKey(const Slice& key_prefix,
+ const ParsedInternalKey& parsed_key_suffix) {
+ SetInternalKey(key_prefix, parsed_key_suffix.user_key,
+ parsed_key_suffix.sequence, parsed_key_suffix.type);
+ }
+
+ void EncodeLengthPrefixedKey(const Slice& key) {
+ auto size = key.size();
+ EnlargeBufferIfNeeded(size + static_cast<size_t>(VarintLength(size)));
+ char* ptr = EncodeVarint32(buf_, static_cast<uint32_t>(size));
+ memcpy(ptr, key.data(), size);
+ key_ = buf_;
+ is_user_key_ = true;
+ }
+
+ bool IsUserKey() const { return is_user_key_; }
+
+ private:
+ char* buf_;
+ const char* key_;
+ size_t key_size_;
+ size_t buf_size_;
+ char space_[32]; // Avoid allocation for short keys
+ bool is_user_key_;
+
+ Slice SetKeyImpl(const Slice& key, bool copy) {
+ size_t size = key.size();
+ if (copy) {
+ // Copy key to buf_
+ EnlargeBufferIfNeeded(size);
+ memcpy(buf_, key.data(), size);
+ key_ = buf_;
+ } else {
+ // Update key_ to point to external memory
+ key_ = key.data();
+ }
+ key_size_ = size;
+ return Slice(key_, key_size_);
+ }
+
+ void ResetBuffer() {
+ if (buf_ != space_) {
+ delete[] buf_;
+ buf_ = space_;
+ }
+ buf_size_ = sizeof(space_);
+ key_size_ = 0;
+ }
+
+ // Enlarge the buffer size if needed based on key_size.
+ // By default, static allocated buffer is used. Once there is a key
+ // larger than the static allocated buffer, another buffer is dynamically
+ // allocated, until a larger key buffer is requested. In that case, we
+ // reallocate buffer and delete the old one.
+ void EnlargeBufferIfNeeded(size_t key_size) {
+ // If size is smaller than buffer size, continue using current buffer,
+ // or the static allocated one, as default
+ if (key_size > buf_size_) {
+ EnlargeBuffer(key_size);
+ }
+ }
+
+ void EnlargeBuffer(size_t key_size);
+};
+
+// Convert from a SliceTranform of user keys, to a SliceTransform of
+// user keys.
+class InternalKeySliceTransform : public SliceTransform {
+ public:
+ explicit InternalKeySliceTransform(const SliceTransform* transform)
+ : transform_(transform) {}
+
+ virtual const char* Name() const override { return transform_->Name(); }
+
+ virtual Slice Transform(const Slice& src) const override {
+ auto user_key = ExtractUserKey(src);
+ return transform_->Transform(user_key);
+ }
+
+ virtual bool InDomain(const Slice& src) const override {
+ auto user_key = ExtractUserKey(src);
+ return transform_->InDomain(user_key);
+ }
+
+ virtual bool InRange(const Slice& dst) const override {
+ auto user_key = ExtractUserKey(dst);
+ return transform_->InRange(user_key);
+ }
+
+ const SliceTransform* user_prefix_extractor() const { return transform_; }
+
+ private:
+ // Like comparator, InternalKeySliceTransform will not take care of the
+ // deletion of transform_
+ const SliceTransform* const transform_;
+};
+
+// Read the key of a record from a write batch.
+// if this record represent the default column family then cf_record
+// must be passed as false, otherwise it must be passed as true.
+extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
+ bool cf_record);
+
+// Read record from a write batch piece from input.
+// tag, column_family, key, value and blob are return values. Callers own the
+// Slice they point to.
+// Tag is defined as ValueType.
+// input will be advanced to after the record.
+extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
+ uint32_t* column_family, Slice* key,
+ Slice* value, Slice* blob, Slice* xid);
+
+// When user call DeleteRange() to delete a range of keys,
+// we will store a serialized RangeTombstone in MemTable and SST.
+// the struct here is a easy-understood form
+// start/end_key_ is the start/end user key of the range to be deleted
+struct RangeTombstone {
+ Slice start_key_;
+ Slice end_key_;
+ SequenceNumber seq_;
+ RangeTombstone() = default;
+ RangeTombstone(Slice sk, Slice ek, SequenceNumber sn)
+ : start_key_(sk), end_key_(ek), seq_(sn) {}
+
+ RangeTombstone(ParsedInternalKey parsed_key, Slice value) {
+ start_key_ = parsed_key.user_key;
+ seq_ = parsed_key.sequence;
+ end_key_ = value;
+ }
+
+ // be careful to use Serialize(), allocates new memory
+ std::pair<InternalKey, Slice> Serialize() const {
+ auto key = InternalKey(start_key_, seq_, kTypeRangeDeletion);
+ Slice value = end_key_;
+ return std::make_pair(std::move(key), std::move(value));
+ }
+
+ // be careful to use SerializeKey(), allocates new memory
+ InternalKey SerializeKey() const {
+ return InternalKey(start_key_, seq_, kTypeRangeDeletion);
+ }
+
+ // The tombstone end-key is exclusive, so we generate an internal-key here
+ // which has a similar property. Using kMaxSequenceNumber guarantees that
+ // the returned internal-key will compare less than any other internal-key
+ // with the same user-key. This in turn guarantees that the serialized
+ // end-key for a tombstone such as [a-b] will compare less than the key "b".
+ //
+ // be careful to use SerializeEndKey(), allocates new memory
+ InternalKey SerializeEndKey() const {
+ return InternalKey(end_key_, kMaxSequenceNumber, kTypeRangeDeletion);
+ }
+};
+
+inline int InternalKeyComparator::Compare(const Slice& akey,
+ const Slice& bkey) const {
+ // Order by:
+ // increasing user key (according to user-supplied comparator)
+ // decreasing sequence number
+ // decreasing type (though sequence# should be enough to disambiguate)
+ int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
+ if (r == 0) {
+ const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);
+ const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);
+ if (anum > bnum) {
+ r = -1;
+ } else if (anum < bnum) {
+ r = +1;
+ }
+ }
+ return r;
+}
+
+inline int InternalKeyComparator::CompareKeySeq(const Slice& akey,
+ const Slice& bkey) const {
+ // Order by:
+ // increasing user key (according to user-supplied comparator)
+ // decreasing sequence number
+ int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
+ if (r == 0) {
+ // Shift the number to exclude the last byte which contains the value type
+ const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8) >> 8;
+ const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8) >> 8;
+ if (anum > bnum) {
+ r = -1;
+ } else if (anum < bnum) {
+ r = +1;
+ }
+ }
+ return r;
+}
+
+// Wrap InternalKeyComparator as a comparator class for ParsedInternalKey.
+struct ParsedInternalKeyComparator {
+ explicit ParsedInternalKeyComparator(const InternalKeyComparator* c)
+ : cmp(c) {}
+
+ bool operator()(const ParsedInternalKey& a,
+ const ParsedInternalKey& b) const {
+ return cmp->Compare(a, b) < 0;
+ }
+
+ const InternalKeyComparator* cmp;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/dbformat_test.cc b/storage/rocksdb/rocksdb/db/dbformat_test.cc
new file mode 100644
index 00000000..a2c67795
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/dbformat_test.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "logging/logging.h"
+#include "test_util/testharness.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;
+ InternalKeyComparator(BytewiseComparator()).FindShortestSeparator(&result, l);
+ return result;
+}
+
+static std::string ShortSuccessor(const std::string& s) {
+ std::string result = s;
+ InternalKeyComparator(BytewiseComparator()).FindShortSuccessor(&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_TRUE(ParseInternalKey(in, &decoded));
+ ASSERT_EQ(key, decoded.user_key.ToString());
+ ASSERT_EQ(seq, decoded.sequence);
+ ASSERT_EQ(vt, decoded.type);
+
+ ASSERT_TRUE(!ParseInternalKey(Slice("bar"), &decoded));
+}
+
+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_TRUE(ParseInternalKey(in, &decoded));
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/deletefile_test.cc b/storage/rocksdb/rocksdb/db/deletefile_test.cc
new file mode 100644
index 00000000..f202388c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/deletefile_test.cc
@@ -0,0 +1,571 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <stdlib.h>
+#include <map>
+#include <string>
+#include <vector>
+#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"),
+ numlevels_(7),
+ wal_dir_(dbname_ + "/wal_files") {}
+
+ void SetOptions(Options* options) {
+ assert(options);
+ 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 = ToString(i);
+ Slice key(temp);
+ Slice value(temp);
+ ASSERT_OK(db_->Put(options, key, value));
+ }
+ }
+
+ int numKeysInLevels(
+ std::vector<LiveFileMetaData> &metadata,
+ std::vector<int> *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<std::string> filenames;
+ 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 == kLogFile);
+ 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);
+ }
+
+ static void DoSleep(void* arg) {
+ auto test = reinterpret_cast<DeleteFileTest*>(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<LiveFileMetaData> 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.
+ ASSERT_OK(db_->DeleteFile(level2file));
+}
+
+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);
+ 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());
+ db_->CompactRange(compact_options, &first_slice, &last_slice);
+ // 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);
+ 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, 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<int64_t>(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);
+ }
+
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ SyncPoint::GetInstance()->LoadDependency(
+ {{"DeleteFileTest::BackgroundPurgeCFDropTest:1",
+ "DBImpl::BGWorkPurge:start"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ options.avoid_unnecessary_blocking_io = true;
+ options.create_if_missing = false;
+ Reopen(options);
+ {
+ 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);
+ // ReadOptions is deleted, but iterator cleanup function should not be
+ // affected
+ }
+
+ 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);
+ CreateTwoLevels();
+ Iterator* itr2 = db_->NewIterator(read_options);
+ 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);
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+
+ std::string level2file;
+
+ ASSERT_EQ(metadata.size(), static_cast<size_t>(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_TRUE(status.ok());
+ 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;
+ 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<LogFile> 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_TRUE(!db_->DeleteFile(alive_log->PathName()).ok());
+ 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;
+ db_->Flush(fopts);
+ AddKeys(10, 0);
+ db_->Flush(fopts);
+ db_->GetSortedWalFiles(logfiles);
+ ASSERT_GT(logfiles.size(), 0UL);
+ std::unique_ptr<LogFile> 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_EQ(Status::NotFound(),
+ env_->FileExists(wal_dir_ + "/" + archived_log->PathName()));
+}
+
+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<LiveFileMetaData> 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<Iterator> 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);
+ }
+
+ Close();
+ ReopenWithColumnFamilies({kDefaultColumnFamilyName, "new_cf"}, options);
+
+ {
+ std::unique_ptr<Iterator> 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
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/error_handler.cc b/storage/rocksdb/rocksdb/db/error_handler.cc
new file mode 100644
index 00000000..3ba4d9fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/error_handler.cc
@@ -0,0 +1,344 @@
+// 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/error_handler.h"
+#include "db/db_impl/db_impl.h"
+#include "db/event_helpers.h"
+#include "file/sst_file_manager_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Maps to help decide the severity of an error based on the
+// BackgroundErrorReason, Code, SubCode and whether db_options.paranoid_checks
+// is set or not. There are 3 maps, going from most specific to least specific
+// (i.e from all 4 fields in a tuple to only the BackgroundErrorReason and
+// paranoid_checks). The less specific map serves as a catch all in case we miss
+// a specific error code or subcode.
+std::map<std::tuple<BackgroundErrorReason, Status::Code, Status::SubCode, bool>,
+ Status::Severity>
+ ErrorSeverityMap = {
+ // Errors during BG compaction
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kIOError, Status::SubCode::kNoSpace,
+ true),
+ Status::Severity::kSoftError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kIOError, Status::SubCode::kNoSpace,
+ false),
+ Status::Severity::kNoError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kIOError, Status::SubCode::kSpaceLimit,
+ true),
+ Status::Severity::kHardError},
+ // Errors during BG flush
+ {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
+ Status::SubCode::kNoSpace, true),
+ Status::Severity::kHardError},
+ {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
+ Status::SubCode::kNoSpace, false),
+ Status::Severity::kNoError},
+ {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
+ Status::SubCode::kSpaceLimit, true),
+ Status::Severity::kHardError},
+ // Errors during Write
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kIOError, Status::SubCode::kNoSpace,
+ true),
+ Status::Severity::kHardError},
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kIOError, Status::SubCode::kNoSpace,
+ false),
+ Status::Severity::kHardError},
+};
+
+std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity>
+ DefaultErrorSeverityMap = {
+ // Errors during BG compaction
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kCorruption, true),
+ Status::Severity::kUnrecoverableError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kCorruption, false),
+ Status::Severity::kNoError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kIOError, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction,
+ Status::Code::kIOError, false),
+ Status::Severity::kNoError},
+ // Errors during BG flush
+ {std::make_tuple(BackgroundErrorReason::kFlush,
+ Status::Code::kCorruption, true),
+ Status::Severity::kUnrecoverableError},
+ {std::make_tuple(BackgroundErrorReason::kFlush,
+ Status::Code::kCorruption, false),
+ Status::Severity::kNoError},
+ {std::make_tuple(BackgroundErrorReason::kFlush,
+ Status::Code::kIOError, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kFlush,
+ Status::Code::kIOError, false),
+ Status::Severity::kNoError},
+ // Errors during Write
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kCorruption, true),
+ Status::Severity::kUnrecoverableError},
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kCorruption, false),
+ Status::Severity::kNoError},
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kIOError, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback,
+ Status::Code::kIOError, false),
+ Status::Severity::kNoError},
+};
+
+std::map<std::tuple<BackgroundErrorReason, bool>, Status::Severity>
+ DefaultReasonMap = {
+ // Errors during BG compaction
+ {std::make_tuple(BackgroundErrorReason::kCompaction, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kCompaction, false),
+ Status::Severity::kNoError},
+ // Errors during BG flush
+ {std::make_tuple(BackgroundErrorReason::kFlush, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kFlush, false),
+ Status::Severity::kNoError},
+ // Errors during Write
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kWriteCallback, false),
+ Status::Severity::kFatalError},
+ // Errors during Memtable update
+ {std::make_tuple(BackgroundErrorReason::kMemTable, true),
+ Status::Severity::kFatalError},
+ {std::make_tuple(BackgroundErrorReason::kMemTable, false),
+ Status::Severity::kFatalError},
+};
+
+void ErrorHandler::CancelErrorRecovery() {
+#ifndef ROCKSDB_LITE
+ db_mutex_->AssertHeld();
+
+ // We'll release the lock before calling sfm, so make sure no new
+ // recovery gets scheduled at that point
+ auto_recovery_ = false;
+ SstFileManagerImpl* sfm = reinterpret_cast<SstFileManagerImpl*>(
+ db_options_.sst_file_manager.get());
+ if (sfm) {
+ // This may or may not cancel a pending recovery
+ db_mutex_->Unlock();
+ bool cancelled = sfm->CancelErrorRecovery(this);
+ db_mutex_->Lock();
+ if (cancelled) {
+ recovery_in_prog_ = false;
+ }
+ }
+#endif
+}
+
+// This is the main function for looking at an error during a background
+// operation and deciding the severity, and error recovery strategy. The high
+// level algorithm is as follows -
+// 1. Classify the severity of the error based on the ErrorSeverityMap,
+// DefaultErrorSeverityMap and DefaultReasonMap defined earlier
+// 2. Call a Status code specific override function to adjust the severity
+// if needed. The reason for this is our ability to recover may depend on
+// the exact options enabled in DBOptions
+// 3. Determine if auto recovery is possible. A listener notification callback
+// is called, which can disable the auto recovery even if we decide its
+// feasible
+// 4. For Status::NoSpace() errors, rely on SstFileManagerImpl to control
+// the actual recovery. If no sst file manager is specified in DBOptions,
+// a default one is allocated during DB::Open(), so there will always be
+// one.
+// This can also get called as part of a recovery operation. In that case, we
+// also track the error separately in recovery_error_ so we can tell in the
+// end whether recovery succeeded or not
+Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reason) {
+ db_mutex_->AssertHeld();
+
+ if (bg_err.ok()) {
+ return Status::OK();
+ }
+
+ bool paranoid = db_options_.paranoid_checks;
+ Status::Severity sev = Status::Severity::kFatalError;
+ Status new_bg_err;
+ bool found = false;
+
+ {
+ auto entry = ErrorSeverityMap.find(std::make_tuple(reason, bg_err.code(),
+ bg_err.subcode(), paranoid));
+ if (entry != ErrorSeverityMap.end()) {
+ sev = entry->second;
+ found = true;
+ }
+ }
+
+ if (!found) {
+ auto entry = DefaultErrorSeverityMap.find(std::make_tuple(reason,
+ bg_err.code(), paranoid));
+ if (entry != DefaultErrorSeverityMap.end()) {
+ sev = entry->second;
+ found = true;
+ }
+ }
+
+ if (!found) {
+ auto entry = DefaultReasonMap.find(std::make_tuple(reason, paranoid));
+ if (entry != DefaultReasonMap.end()) {
+ sev = entry->second;
+ }
+ }
+
+ new_bg_err = Status(bg_err, sev);
+
+ // Check if recovery is currently in progress. If it is, we will save this
+ // error so we can check it at the end to see if recovery succeeded or not
+ if (recovery_in_prog_ && recovery_error_.ok()) {
+ recovery_error_ = new_bg_err;
+ }
+
+ bool auto_recovery = auto_recovery_;
+ if (new_bg_err.severity() >= Status::Severity::kFatalError && auto_recovery) {
+ auto_recovery = false;
+ }
+
+ // Allow some error specific overrides
+ if (new_bg_err == Status::NoSpace()) {
+ new_bg_err = OverrideNoSpaceError(new_bg_err, &auto_recovery);
+ }
+
+ if (!new_bg_err.ok()) {
+ Status s = new_bg_err;
+ EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
+ db_mutex_, &auto_recovery);
+ if (!s.ok() && (s.severity() > bg_error_.severity())) {
+ bg_error_ = s;
+ } else {
+ // This error is less severe than previously encountered error. Don't
+ // take any further action
+ return bg_error_;
+ }
+ }
+
+ if (auto_recovery) {
+ recovery_in_prog_ = true;
+
+ // Kick-off error specific recovery
+ if (bg_error_ == Status::NoSpace()) {
+ RecoverFromNoSpace();
+ }
+ }
+ return bg_error_;
+}
+
+Status ErrorHandler::OverrideNoSpaceError(Status bg_error,
+ bool* auto_recovery) {
+#ifndef ROCKSDB_LITE
+ if (bg_error.severity() >= Status::Severity::kFatalError) {
+ return bg_error;
+ }
+
+ if (db_options_.sst_file_manager.get() == nullptr) {
+ // We rely on SFM to poll for enough disk space and recover
+ *auto_recovery = false;
+ return bg_error;
+ }
+
+ if (db_options_.allow_2pc &&
+ (bg_error.severity() <= Status::Severity::kSoftError)) {
+ // Don't know how to recover, as the contents of the current WAL file may
+ // be inconsistent, and it may be needed for 2PC. If 2PC is not enabled,
+ // we can just flush the memtable and discard the log
+ *auto_recovery = false;
+ return Status(bg_error, Status::Severity::kFatalError);
+ }
+
+ {
+ uint64_t free_space;
+ if (db_options_.env->GetFreeSpace(db_options_.db_paths[0].path,
+ &free_space) == Status::NotSupported()) {
+ *auto_recovery = false;
+ }
+ }
+
+ return bg_error;
+#else
+ (void)auto_recovery;
+ return Status(bg_error, Status::Severity::kFatalError);
+#endif
+}
+
+void ErrorHandler::RecoverFromNoSpace() {
+#ifndef ROCKSDB_LITE
+ SstFileManagerImpl* sfm =
+ reinterpret_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
+
+ // Inform SFM of the error, so it can kick-off the recovery
+ if (sfm) {
+ sfm->StartErrorRecovery(this, bg_error_);
+ }
+#endif
+}
+
+Status ErrorHandler::ClearBGError() {
+#ifndef ROCKSDB_LITE
+ db_mutex_->AssertHeld();
+
+ // Signal that recovery succeeded
+ if (recovery_error_.ok()) {
+ Status old_bg_error = bg_error_;
+ bg_error_ = Status::OK();
+ recovery_in_prog_ = false;
+ EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners,
+ old_bg_error, db_mutex_);
+ }
+ return recovery_error_;
+#else
+ return bg_error_;
+#endif
+}
+
+Status ErrorHandler::RecoverFromBGError(bool is_manual) {
+#ifndef ROCKSDB_LITE
+ InstrumentedMutexLock l(db_mutex_);
+ if (is_manual) {
+ // If its a manual recovery and there's a background recovery in progress
+ // return busy status
+ if (recovery_in_prog_) {
+ return Status::Busy();
+ }
+ recovery_in_prog_ = true;
+ }
+
+ if (bg_error_.severity() == Status::Severity::kSoftError) {
+ // Simply clear the background error and return
+ recovery_error_ = Status::OK();
+ return ClearBGError();
+ }
+
+ // Reset recovery_error_. We will use this to record any errors that happen
+ // during the recovery process. While recovering, the only operations that
+ // can generate background errors should be the flush operations
+ recovery_error_ = Status::OK();
+ Status s = db_->ResumeImpl();
+ // For manual recover, shutdown, and fatal error cases, set
+ // recovery_in_prog_ to false. For automatic background recovery, leave it
+ // as is regardless of success or failure as it will be retried
+ if (is_manual || s.IsShutdownInProgress() ||
+ bg_error_.severity() >= Status::Severity::kFatalError) {
+ recovery_in_prog_ = false;
+ }
+ return s;
+#else
+ (void)is_manual;
+ return bg_error_;
+#endif
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/error_handler.h b/storage/rocksdb/rocksdb/db/error_handler.h
new file mode 100644
index 00000000..7276f651
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/error_handler.h
@@ -0,0 +1,75 @@
+// 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).
+#pragma once
+
+#include "monitoring/instrumented_mutex.h"
+#include "options/db_options.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+
+class ErrorHandler {
+ public:
+ ErrorHandler(DBImpl* db, const ImmutableDBOptions& db_options,
+ InstrumentedMutex* db_mutex)
+ : db_(db),
+ db_options_(db_options),
+ bg_error_(Status::OK()),
+ recovery_error_(Status::OK()),
+ db_mutex_(db_mutex),
+ auto_recovery_(false),
+ recovery_in_prog_(false) {}
+ ~ErrorHandler() {}
+
+ void EnableAutoRecovery() { auto_recovery_ = true; }
+
+ Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
+ Status::Code code,
+ Status::SubCode subcode);
+
+ Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
+
+ Status GetBGError() { return bg_error_; }
+
+ Status GetRecoveryError() { return recovery_error_; }
+
+ Status ClearBGError();
+
+ bool IsDBStopped() {
+ return !bg_error_.ok() &&
+ bg_error_.severity() >= Status::Severity::kHardError;
+ }
+
+ bool IsBGWorkStopped() {
+ return !bg_error_.ok() &&
+ (bg_error_.severity() >= Status::Severity::kHardError ||
+ !auto_recovery_);
+ }
+
+ bool IsRecoveryInProgress() { return recovery_in_prog_; }
+
+ Status RecoverFromBGError(bool is_manual = false);
+ void CancelErrorRecovery();
+
+ private:
+ DBImpl* db_;
+ const ImmutableDBOptions& db_options_;
+ Status bg_error_;
+ // A separate Status variable used to record any errors during the
+ // recovery process from hard errors
+ Status recovery_error_;
+ InstrumentedMutex* db_mutex_;
+ // A flag indicating whether automatic recovery from errors is enabled
+ bool auto_recovery_;
+ bool recovery_in_prog_;
+
+ Status OverrideNoSpaceError(Status bg_error, bool* auto_recovery);
+ void RecoverFromNoSpace();
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/error_handler_test.cc b/storage/rocksdb/rocksdb/db/error_handler_test.cc
new file mode 100644
index 00000000..b9d78490
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/error_handler_test.cc
@@ -0,0 +1,871 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/sst_file_manager.h"
+#include "test_util/fault_injection_test_env.h"
+#if !defined(ROCKSDB_LITE)
+#include "test_util/sync_point.h"
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBErrorHandlingTest : public DBTestBase {
+ public:
+ DBErrorHandlingTest() : DBTestBase("/db_error_handling_test") {}
+
+ std::string GetManifestNameFromLiveFiles() {
+ std::vector<std::string> live_files;
+ uint64_t manifest_size;
+
+ dbfull()->GetLiveFiles(live_files, &manifest_size, false);
+ for (auto& file : live_files) {
+ uint64_t num = 0;
+ FileType type;
+ if (ParseFileName(file, &num, &type) && type == kDescriptorFile) {
+ return file;
+ }
+ }
+ return "";
+ }
+};
+
+class DBErrorHandlingEnv : public EnvWrapper {
+ public:
+ DBErrorHandlingEnv() : EnvWrapper(Env::Default()),
+ trig_no_space(false), trig_io_error(false) {}
+
+ void SetTrigNoSpace() {trig_no_space = true;}
+ void SetTrigIoError() {trig_io_error = true;}
+ private:
+ bool trig_no_space;
+ bool trig_io_error;
+};
+
+class ErrorHandlerListener : public EventListener {
+ public:
+ ErrorHandlerListener()
+ : mutex_(),
+ cv_(&mutex_),
+ no_auto_recovery_(false),
+ recovery_complete_(false),
+ file_creation_started_(false),
+ override_bg_error_(false),
+ file_count_(0),
+ fault_env_(nullptr) {}
+
+ void OnTableFileCreationStarted(
+ const TableFileCreationBriefInfo& /*ti*/) override {
+ InstrumentedMutexLock l(&mutex_);
+ file_creation_started_ = true;
+ if (file_count_ > 0) {
+ if (--file_count_ == 0) {
+ fault_env_->SetFilesystemActive(false, file_creation_error_);
+ file_creation_error_ = Status::OK();
+ }
+ }
+ cv_.SignalAll();
+ }
+
+ void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/,
+ Status /*bg_error*/,
+ bool* auto_recovery) override {
+ if (*auto_recovery && no_auto_recovery_) {
+ *auto_recovery = false;
+ }
+ }
+
+ void OnErrorRecoveryCompleted(Status /*old_bg_error*/) override {
+ InstrumentedMutexLock l(&mutex_);
+ recovery_complete_ = true;
+ cv_.SignalAll();
+ }
+
+ 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(FaultInjectionTestEnv* env, int file_count,
+ Status s) {
+ fault_env_ = env;
+ file_count_ = file_count;
+ file_creation_error_ = s;
+ }
+
+ private:
+ InstrumentedMutex mutex_;
+ InstrumentedCondVar cv_;
+ bool no_auto_recovery_;
+ bool recovery_complete_;
+ bool file_creation_started_;
+ bool override_bg_error_;
+ int file_count_;
+ Status file_creation_error_;
+ Status bg_error_;
+ FaultInjectionTestEnv* fault_env_;
+};
+
+TEST_F(DBErrorHandlingTest, FLushWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+
+ listener->EnableAutoRecovery(false);
+ DestroyAndReopen(options);
+
+ Put(Key(0), "val");
+ SyncPoint::GetInstance()->SetCallBack(
+ "FlushJob::Start", [&](void *) {
+ fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ s = Flush();
+ ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
+ SyncPoint::GetInstance()->DisableProcessing();
+ fault_env->SetFilesystemActive(true);
+ s = dbfull()->Resume();
+ ASSERT_EQ(s, Status::OK());
+
+ Reopen(options);
+ ASSERT_EQ("val", Get(Key(0)));
+ Destroy(options);
+}
+
+TEST_F(DBErrorHandlingTest, ManifestWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+ std::string old_manifest;
+ std::string new_manifest;
+
+ listener->EnableAutoRecovery(false);
+ DestroyAndReopen(options);
+ old_manifest = GetManifestNameFromLiveFiles();
+
+ Put(Key(0), "val");
+ Flush();
+ Put(Key(1), "val");
+ SyncPoint::GetInstance()->SetCallBack(
+ "VersionSet::LogAndApply:WriteManifest", [&](void *) {
+ fault_env->SetFilesystemActive(false, Status::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_env->SetFilesystemActive(true);
+ s = dbfull()->Resume();
+ ASSERT_EQ(s, Status::OK());
+
+ 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(DBErrorHandlingTest, DoubleManifestWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+ std::string old_manifest;
+ std::string new_manifest;
+
+ listener->EnableAutoRecovery(false);
+ DestroyAndReopen(options);
+ old_manifest = GetManifestNameFromLiveFiles();
+
+ Put(Key(0), "val");
+ Flush();
+ Put(Key(1), "val");
+ SyncPoint::GetInstance()->SetCallBack(
+ "VersionSet::LogAndApply:WriteManifest", [&](void *) {
+ fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ s = Flush();
+ ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
+ fault_env->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_env->SetFilesystemActive(true);
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ SyncPoint::GetInstance()->DisableProcessing();
+
+ // A successful Resume() will create a new manifest file
+ s = dbfull()->Resume();
+ ASSERT_EQ(s, Status::OK());
+
+ 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(DBErrorHandlingTest, CompactionManifestWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.level0_file_num_compaction_trigger = 2;
+ options.listeners.emplace_back(listener);
+ options.env = fault_env.get();
+ Status s;
+ std::string old_manifest;
+ std::string new_manifest;
+ std::atomic<bool> fail_manifest(false);
+ DestroyAndReopen(options);
+ old_manifest = GetManifestNameFromLiveFiles();
+
+ Put(Key(0), "val");
+ Put(Key(2), "val");
+ s = Flush();
+ ASSERT_EQ(s, Status::OK());
+
+ 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_env->SetFilesystemActive(false,
+ Status::NoSpace("Out of space"));
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Put(Key(1), "val");
+ // This Flush will trigger a compaction, which will fail when appending to
+ // the manifest
+ s = Flush();
+ ASSERT_EQ(s, Status::OK());
+
+ TEST_SYNC_POINT("CompactionManifestWriteError:0");
+ // Clear all errors so when the compaction is retried, it will succeed
+ fault_env->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_EQ(s, Status::OK());
+
+ 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(DBErrorHandlingTest, CompactionWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.level0_file_num_compaction_trigger = 2;
+ options.listeners.emplace_back(listener);
+ options.env = fault_env.get();
+ Status s;
+ DestroyAndReopen(options);
+
+ Put(Key(0), "va;");
+ Put(Key(2), "va;");
+ s = Flush();
+ ASSERT_EQ(s, Status::OK());
+
+ 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_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Put(Key(1), "val");
+ s = Flush();
+ ASSERT_EQ(s, Status::OK());
+
+ s = dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
+
+ fault_env->SetFilesystemActive(true);
+ s = dbfull()->Resume();
+ ASSERT_EQ(s, Status::OK());
+ Destroy(options);
+}
+
+TEST_F(DBErrorHandlingTest, CorruptionError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.level0_file_num_compaction_trigger = 2;
+ options.env = fault_env.get();
+ Status s;
+ DestroyAndReopen(options);
+
+ Put(Key(0), "va;");
+ Put(Key(2), "va;");
+ 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_env->SetFilesystemActive(false, Status::Corruption("Corruption"));
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ Put(Key(1), "val");
+ s = Flush();
+ ASSERT_EQ(s, Status::OK());
+
+ s = dbfull()->TEST_WaitForCompact();
+ ASSERT_EQ(s.severity(),
+ ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError);
+
+ fault_env->SetFilesystemActive(true);
+ s = dbfull()->Resume();
+ ASSERT_NE(s, Status::OK());
+ Destroy(options);
+}
+
+TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+
+ listener->EnableAutoRecovery();
+ DestroyAndReopen(options);
+
+ Put(Key(0), "val");
+ SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
+ fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ s = Flush();
+ ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
+ SyncPoint::GetInstance()->DisableProcessing();
+ fault_env->SetFilesystemActive(true);
+ ASSERT_EQ(listener->WaitForRecovery(5000000), true);
+
+ s = Put(Key(1), "val");
+ ASSERT_EQ(s, Status::OK());
+
+ Reopen(options);
+ ASSERT_EQ("val", Get(Key(0)));
+ ASSERT_EQ("val", Get(Key(1)));
+ Destroy(options);
+}
+
+TEST_F(DBErrorHandlingTest, FailRecoverFlushError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+
+ listener->EnableAutoRecovery();
+ DestroyAndReopen(options);
+
+ Put(Key(0), "val");
+ SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
+ fault_env->SetFilesystemActive(false, Status::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);
+}
+
+TEST_F(DBErrorHandlingTest, WALWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.writable_file_max_buffer_size = 32768;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+ Random rnd(301);
+
+ listener->EnableAutoRecovery();
+ DestroyAndReopen(options);
+
+ {
+ WriteBatch batch;
+
+ for (auto i = 0; i<100; ++i) {
+ batch.Put(Key(i), RandomString(&rnd, 1024));
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
+ };
+
+ {
+ WriteBatch batch;
+ int write_error = 0;
+
+ for (auto i = 100; i<199; ++i) {
+ batch.Put(Key(i), RandomString(&rnd, 1024));
+ }
+
+ SyncPoint::GetInstance()->SetCallBack("WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
+ write_error++;
+ if (write_error > 2) {
+ fault_env->SetFilesystemActive(false, Status::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();
+ fault_env->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(DBErrorHandlingTest, MultiCFWALWriteError) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_env(
+ new FaultInjectionTestEnv(Env::Default()));
+ std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
+ Options options = GetDefaultOptions();
+ options.create_if_missing = true;
+ options.writable_file_max_buffer_size = 32768;
+ options.env = fault_env.get();
+ options.listeners.emplace_back(listener);
+ Status s;
+ 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) {
+ batch.Put(handles_[i], Key(j), RandomString(&rnd, 1024));
+ }
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
+ };
+
+ {
+ WriteBatch batch;
+ int write_error = 0;
+
+ // Write to one CF
+ for (auto i = 100; i < 199; ++i) {
+ batch.Put(handles_[2], Key(i), RandomString(&rnd, 1024));
+ }
+
+ SyncPoint::GetInstance()->SetCallBack(
+ "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
+ write_error++;
+ if (write_error > 2) {
+ fault_env->SetFilesystemActive(false,
+ Status::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();
+ fault_env->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(DBErrorHandlingTest, MultiDBCompactionError) {
+ FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
+ std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
+ std::vector<Options> options;
+ std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
+ std::vector<DB*> db;
+ std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
+ int kNumDbInstances = 3;
+ Random rnd(301);
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ listener.emplace_back(new ErrorHandlerListener());
+ options.emplace_back(GetDefaultOptions());
+ fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
+ options[i].create_if_missing = true;
+ options[i].level0_file_num_compaction_trigger = 2;
+ options[i].writable_file_max_buffer_size = 32768;
+ options[i].env = fault_env[i].get();
+ 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_env[i].get(), 3,
+ Status::NoSpace("Out of space"));
+ snprintf(buf, sizeof(buf), "_%d", i);
+ DestroyDB(dbname_ + std::string(buf), options[i]);
+ ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr),
+ Status::OK());
+ db.emplace_back(dbptr);
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ WriteBatch batch;
+
+ for (auto j = 0; j <= 100; ++j) {
+ batch.Put(Key(j), RandomString(&rnd, 1024));
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
+ ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
+ }
+
+ 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) {
+ batch.Put(Key(j), RandomString(&rnd, 1024));
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
+ ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true);
+ ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
+ fault_env[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_EQ(static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true),
+ Status::OK());
+ EXPECT_TRUE(db[i]->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(0), &prop));
+ EXPECT_EQ(atoi(prop.c_str()), 0);
+ EXPECT_TRUE(db[i]->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(1), &prop));
+ EXPECT_EQ(atoi(prop.c_str()), 1);
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ char buf[16];
+ snprintf(buf, sizeof(buf), "_%d", i);
+ delete db[i];
+ fault_env[i]->SetFilesystemActive(true);
+ if (getenv("KEEP_DB")) {
+ printf("DB is still at %s%s\n", dbname_.c_str(), buf);
+ } else {
+ Status s = DestroyDB(dbname_ + std::string(buf), options[i]);
+ }
+ }
+ options.clear();
+ sfm.reset();
+ delete def_env;
+}
+
+TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
+ FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
+ std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
+ std::vector<Options> options;
+ std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
+ std::vector<DB*> db;
+ std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
+ int kNumDbInstances = 3;
+ Random rnd(301);
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ listener.emplace_back(new ErrorHandlerListener());
+ options.emplace_back(GetDefaultOptions());
+ fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
+ options[i].create_if_missing = true;
+ options[i].level0_file_num_compaction_trigger = 2;
+ options[i].writable_file_max_buffer_size = 32768;
+ options[i].env = fault_env[i].get();
+ 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_env[i].get(), 3,
+ Status::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_env[i].get(), 2,
+ Status::NoSpace("Out of space"));
+ break;
+ default:
+ break;
+ }
+ snprintf(buf, sizeof(buf), "_%d", i);
+ DestroyDB(dbname_ + std::string(buf), options[i]);
+ ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr),
+ Status::OK());
+ db.emplace_back(dbptr);
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ WriteBatch batch;
+
+ for (auto j = 0; j <= 100; ++j) {
+ batch.Put(Key(j), RandomString(&rnd, 1024));
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
+ ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
+ }
+
+ 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) {
+ batch.Put(Key(j), RandomString(&rnd, 1024));
+ }
+
+ WriteOptions wopts;
+ wopts.sync = true;
+ ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
+ if (i != 1) {
+ ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
+ } else {
+ ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::NoSpace());
+ }
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ Status s = static_cast<DBImpl*>(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_EQ(s, Status::OK());
+ break;
+ }
+ fault_env[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_EQ(static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true),
+ Status::OK());
+ }
+ EXPECT_TRUE(db[i]->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(0), &prop));
+ EXPECT_EQ(atoi(prop.c_str()), 0);
+ EXPECT_TRUE(db[i]->GetProperty(
+ "rocksdb.num-files-at-level" + NumberToString(1), &prop));
+ EXPECT_EQ(atoi(prop.c_str()), 1);
+ }
+
+ for (auto i = 0; i < kNumDbInstances; ++i) {
+ char buf[16];
+ snprintf(buf, sizeof(buf), "_%d", i);
+ fault_env[i]->SetFilesystemActive(true);
+ delete db[i];
+ if (getenv("KEEP_DB")) {
+ printf("DB is still at %s%s\n", dbname_.c_str(), buf);
+ } else {
+ DestroyDB(dbname_ + std::string(buf), options[i]);
+ }
+ }
+ options.clear();
+ delete def_env;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/event_helpers.cc b/storage/rocksdb/rocksdb/db/event_helpers.cc
new file mode 100644
index 00000000..57aa711f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/event_helpers.cc
@@ -0,0 +1,223 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/event_helpers.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+template <class T>
+inline T SafeDivide(T a, T b) {
+ return b == 0 ? 0 : a / b;
+}
+} // namespace
+
+void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) {
+ *jwriter << "time_micros"
+ << std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+}
+
+#ifndef ROCKSDB_LITE
+void EventHelpers::NotifyTableFileCreationStarted(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ const std::string& db_name, const std::string& cf_name,
+ const std::string& file_path, int job_id, TableFileCreationReason reason) {
+ TableFileCreationBriefInfo info;
+ info.db_name = db_name;
+ info.cf_name = cf_name;
+ info.file_path = file_path;
+ info.job_id = job_id;
+ info.reason = reason;
+ for (auto& listener : listeners) {
+ listener->OnTableFileCreationStarted(info);
+ }
+}
+#endif // !ROCKSDB_LITE
+
+void EventHelpers::NotifyOnBackgroundError(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ BackgroundErrorReason reason, Status* bg_error, InstrumentedMutex* db_mutex,
+ bool* auto_recovery) {
+#ifndef ROCKSDB_LITE
+ if (listeners.size() == 0U) {
+ return;
+ }
+ db_mutex->AssertHeld();
+ // release lock while notifying events
+ db_mutex->Unlock();
+ for (auto& listener : listeners) {
+ listener->OnBackgroundError(reason, bg_error);
+ if (*auto_recovery) {
+ listener->OnErrorRecoveryBegin(reason, *bg_error, auto_recovery);
+ }
+ }
+ db_mutex->Lock();
+#else
+ (void)listeners;
+ (void)reason;
+ (void)bg_error;
+ (void)db_mutex;
+ (void)auto_recovery;
+#endif // ROCKSDB_LITE
+}
+
+void EventHelpers::LogAndNotifyTableFileCreationFinished(
+ EventLogger* event_logger,
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ const std::string& db_name, const std::string& cf_name,
+ const std::string& file_path, int job_id, const FileDescriptor& fd,
+ uint64_t oldest_blob_file_number, const TableProperties& table_properties,
+ TableFileCreationReason reason, const Status& s) {
+ if (s.ok() && event_logger) {
+ JSONWriter jwriter;
+ AppendCurrentTime(&jwriter);
+ jwriter << "cf_name" << cf_name << "job" << job_id << "event"
+ << "table_file_creation"
+ << "file_number" << fd.GetNumber() << "file_size"
+ << fd.GetFileSize();
+
+ // table_properties
+ {
+ jwriter << "table_properties";
+ jwriter.StartObject();
+
+ // basic properties:
+ jwriter << "data_size" << table_properties.data_size << "index_size"
+ << table_properties.index_size << "index_partitions"
+ << table_properties.index_partitions << "top_level_index_size"
+ << table_properties.top_level_index_size
+ << "index_key_is_user_key"
+ << table_properties.index_key_is_user_key
+ << "index_value_is_delta_encoded"
+ << table_properties.index_value_is_delta_encoded << "filter_size"
+ << table_properties.filter_size << "raw_key_size"
+ << table_properties.raw_key_size << "raw_average_key_size"
+ << SafeDivide(table_properties.raw_key_size,
+ table_properties.num_entries)
+ << "raw_value_size" << table_properties.raw_value_size
+ << "raw_average_value_size"
+ << SafeDivide(table_properties.raw_value_size,
+ table_properties.num_entries)
+ << "num_data_blocks" << table_properties.num_data_blocks
+ << "num_entries" << table_properties.num_entries
+ << "num_deletions" << table_properties.num_deletions
+ << "num_merge_operands" << table_properties.num_merge_operands
+ << "num_range_deletions" << table_properties.num_range_deletions
+ << "format_version" << table_properties.format_version
+ << "fixed_key_len" << table_properties.fixed_key_len
+ << "filter_policy" << table_properties.filter_policy_name
+ << "column_family_name" << table_properties.column_family_name
+ << "column_family_id" << table_properties.column_family_id
+ << "comparator" << table_properties.comparator_name
+ << "merge_operator" << table_properties.merge_operator_name
+ << "prefix_extractor_name"
+ << table_properties.prefix_extractor_name << "property_collectors"
+ << table_properties.property_collectors_names << "compression"
+ << table_properties.compression_name << "compression_options"
+ << table_properties.compression_options << "creation_time"
+ << table_properties.creation_time << "oldest_key_time"
+ << table_properties.oldest_key_time << "file_creation_time"
+ << table_properties.file_creation_time;
+
+ // user collected properties
+ for (const auto& prop : table_properties.readable_properties) {
+ jwriter << prop.first << prop.second;
+ }
+ jwriter.EndObject();
+ }
+
+ if (oldest_blob_file_number != kInvalidBlobFileNumber) {
+ jwriter << "oldest_blob_file_number" << oldest_blob_file_number;
+ }
+
+ jwriter.EndObject();
+
+ event_logger->Log(jwriter);
+ }
+
+#ifndef ROCKSDB_LITE
+ if (listeners.size() == 0) {
+ return;
+ }
+ TableFileCreationInfo info;
+ info.db_name = db_name;
+ info.cf_name = cf_name;
+ info.file_path = file_path;
+ info.file_size = fd.file_size;
+ info.job_id = job_id;
+ info.table_properties = table_properties;
+ info.reason = reason;
+ info.status = s;
+ for (auto& listener : listeners) {
+ listener->OnTableFileCreated(info);
+ }
+#else
+ (void)listeners;
+ (void)db_name;
+ (void)cf_name;
+ (void)file_path;
+ (void)reason;
+#endif // !ROCKSDB_LITE
+}
+
+void EventHelpers::LogAndNotifyTableFileDeletion(
+ EventLogger* event_logger, int job_id, uint64_t file_number,
+ const std::string& file_path, const Status& status,
+ const std::string& dbname,
+ const std::vector<std::shared_ptr<EventListener>>& listeners) {
+ JSONWriter jwriter;
+ AppendCurrentTime(&jwriter);
+
+ jwriter << "job" << job_id << "event"
+ << "table_file_deletion"
+ << "file_number" << file_number;
+ if (!status.ok()) {
+ jwriter << "status" << status.ToString();
+ }
+
+ jwriter.EndObject();
+
+ event_logger->Log(jwriter);
+
+#ifndef ROCKSDB_LITE
+ TableFileDeletionInfo info;
+ info.db_name = dbname;
+ info.job_id = job_id;
+ info.file_path = file_path;
+ info.status = status;
+ for (auto& listener : listeners) {
+ listener->OnTableFileDeleted(info);
+ }
+#else
+ (void)file_path;
+ (void)dbname;
+ (void)listeners;
+#endif // !ROCKSDB_LITE
+}
+
+void EventHelpers::NotifyOnErrorRecoveryCompleted(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ Status old_bg_error, InstrumentedMutex* db_mutex) {
+#ifndef ROCKSDB_LITE
+ if (listeners.size() == 0U) {
+ return;
+ }
+ db_mutex->AssertHeld();
+ // release lock while notifying events
+ db_mutex->Unlock();
+ for (auto& listener : listeners) {
+ listener->OnErrorRecoveryCompleted(old_bg_error);
+ }
+ db_mutex->Lock();
+#else
+ (void)listeners;
+ (void)old_bg_error;
+ (void)db_mutex;
+#endif // ROCKSDB_LITE
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/event_helpers.h b/storage/rocksdb/rocksdb/db/event_helpers.h
new file mode 100644
index 00000000..87cc1cb8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/event_helpers.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <string>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/version_edit.h"
+#include "logging/event_logger.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/table_properties.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class EventHelpers {
+ public:
+ static void AppendCurrentTime(JSONWriter* json_writer);
+#ifndef ROCKSDB_LITE
+ static void NotifyTableFileCreationStarted(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ const std::string& db_name, const std::string& cf_name,
+ const std::string& file_path, int job_id, TableFileCreationReason reason);
+#endif // !ROCKSDB_LITE
+ static void NotifyOnBackgroundError(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ BackgroundErrorReason reason, Status* bg_error,
+ InstrumentedMutex* db_mutex, bool* auto_recovery);
+ static void LogAndNotifyTableFileCreationFinished(
+ EventLogger* event_logger,
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ const std::string& db_name, const std::string& cf_name,
+ const std::string& file_path, int job_id, const FileDescriptor& fd,
+ uint64_t oldest_blob_file_number, const TableProperties& table_properties,
+ TableFileCreationReason reason, const Status& s);
+ static void LogAndNotifyTableFileDeletion(
+ EventLogger* event_logger, int job_id,
+ uint64_t file_number, const std::string& file_path,
+ const Status& status, const std::string& db_name,
+ const std::vector<std::shared_ptr<EventListener>>& listeners);
+ static void NotifyOnErrorRecoveryCompleted(
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ Status bg_error, InstrumentedMutex* db_mutex);
+
+ private:
+ static void LogAndNotifyTableFileCreation(
+ EventLogger* event_logger,
+ const std::vector<std::shared_ptr<EventListener>>& listeners,
+ const FileDescriptor& fd, const TableFileCreationInfo& info);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/experimental.cc b/storage/rocksdb/rocksdb/db/experimental.cc
new file mode 100644
index 00000000..d12882c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/experimental.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/experimental.h"
+
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace experimental {
+
+#ifndef ROCKSDB_LITE
+
+Status SuggestCompactRange(DB* db, ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) {
+ if (db == nullptr) {
+ return Status::InvalidArgument("DB is empty");
+ }
+
+ return db->SuggestCompactRange(column_family, begin, end);
+}
+
+Status PromoteL0(DB* db, ColumnFamilyHandle* column_family, int target_level) {
+ if (db == nullptr) {
+ return Status::InvalidArgument("Didn't recognize DB object");
+ }
+ return db->PromoteL0(column_family, target_level);
+}
+
+#else // ROCKSDB_LITE
+
+Status SuggestCompactRange(DB* /*db*/, ColumnFamilyHandle* /*column_family*/,
+ const Slice* /*begin*/, const Slice* /*end*/) {
+ return Status::NotSupported("Not supported in RocksDB LITE");
+}
+
+Status PromoteL0(DB* /*db*/, ColumnFamilyHandle* /*column_family*/,
+ int /*target_level*/) {
+ return Status::NotSupported("Not supported in RocksDB LITE");
+}
+
+#endif // ROCKSDB_LITE
+
+Status SuggestCompactRange(DB* db, const Slice* begin, const Slice* end) {
+ return SuggestCompactRange(db, db->DefaultColumnFamily(), begin, end);
+}
+
+} // namespace experimental
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/external_sst_file_basic_test.cc b/storage/rocksdb/rocksdb/db/external_sst_file_basic_test.cc
new file mode 100644
index 00000000..b184df20
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/external_sst_file_basic_test.cc
@@ -0,0 +1,1128 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+
+#include "db/db_test_util.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/sst_file_writer.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+class ExternalSSTFileBasicTest
+ : public DBTestBase,
+ public ::testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") {
+ sst_files_dir_ = dbname_ + "/sst_files/";
+ fault_injection_test_env_.reset(new FaultInjectionTestEnv(Env::Default()));
+ DestroyAndRecreateExternalSSTFilesDir();
+ }
+
+ void DestroyAndRecreateExternalSSTFilesDir() {
+ test::DestroyDir(env_, sst_files_dir_);
+ env_->CreateDir(sst_files_dir_);
+ }
+
+ Status DeprecatedAddFile(const std::vector<std::string>& 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 GenerateAndAddExternalFile(
+ const Options options, std::vector<int> keys,
+ const std::vector<ValueType>& value_types,
+ std::vector<std::pair<int, int>> range_deletions, int file_id,
+ bool write_global_seqno, bool verify_checksums_before_ingest,
+ std::map<std::string, std::string>* true_data) {
+ assert(value_types.size() == 1 || keys.size() == value_types.size());
+ std::string file_path = sst_files_dir_ + ToString(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]) + ToString(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<int> keys,
+ const std::vector<ValueType>& value_types, int file_id,
+ bool write_global_seqno, bool verify_checksums_before_ingest,
+ std::map<std::string, std::string>* 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<int> keys, const ValueType value_type,
+ int file_id, bool write_global_seqno, bool verify_checksums_before_ingest,
+ std::map<std::string, std::string>* true_data) {
+ return GenerateAndAddExternalFile(
+ options, keys, std::vector<ValueType>(1, value_type), file_id,
+ write_global_seqno, verify_checksums_before_ingest, true_data);
+ }
+
+ ~ExternalSSTFileBasicTest() override {
+ test::DestroyDir(env_, sst_files_dir_);
+ }
+
+ protected:
+ std::string sst_files_dir_;
+ std::unique_ptr<FaultInjectionTestEnv> fault_injection_test_env_;
+};
+
+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_TRUE(s.ok()) << 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, "");
+ // sst_file_writer already finished, cannot add this value
+ s = sst_file_writer.Put(Key(100), "bad_val");
+ ASSERT_FALSE(s.ok()) << s.ToString();
+ s = sst_file_writer.DeleteRange(Key(100), Key(200));
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ DestroyAndReopen(options);
+ // Add file using file path
+ s = DeprecatedAddFile({file1});
+ ASSERT_TRUE(s.ok()) << 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, 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_TRUE(s.ok()) << 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_TRUE(s.ok()) << 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_TRUE(s.ok()) << 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_TRUE(s.ok()) << s.ToString();
+ ASSERT_EQ(Status::NotFound(), env_->FileExists(file1));
+
+ s = DeprecatedAddFile({file2}, false /* copy file */);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ ASSERT_OK(env_->FileExists(file2));
+
+ // This file has overlapping values with the existing data
+ s = DeprecatedAddFile({file3}, true /* move file */);
+ ASSERT_FALSE(s.ok()) << 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();
+ DestroyAndReopen(options);
+ std::map<std::string, std::string> 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.merge_operator.reset(new TestPutOperator());
+ DestroyAndReopen(options);
+ std::map<std::string, std::string> 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.merge_operator.reset(new TestPutOperator());
+ DestroyAndReopen(options);
+ std::map<std::string, std::string> 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<size_t*>(arg));
+ total_fadvised_bytes += fadvise_size;
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::unique_ptr<SstFileWriter> 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<std::pair<std::string, std::string>> test_cases = {
+ {"ExternalSstFileIngestionJob::BeforeSyncIngestedFile",
+ "ExternalSstFileIngestionJob::AfterSyncIngestedFile"},
+ {"ExternalSstFileIngestionJob::BeforeSyncDir",
+ "ExternalSstFileIngestionJob::AfterSyncDir"},
+ {"ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno",
+ "ExternalSstFileIngestionJob::AfterSyncGlobalSeqno"}};
+
+ for (size_t i = 0; i < test_cases.size(); i++) {
+ 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);
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ DestroyAndReopen(options);
+ if (i == 2) {
+ ASSERT_OK(Put("foo", "v1"));
+ }
+
+ Options sst_file_writer_options;
+ std::unique_ptr<SstFileWriter> sst_file_writer(
+ new SstFileWriter(EnvOptions(), sst_file_writer_options));
+ std::string file_name =
+ sst_files_dir_ + "sync_failure_test_" + ToString(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;
+ if (i == 0) {
+ ingest_opt.move_files = true;
+ }
+ const Snapshot* snapshot = db_->GetSnapshot();
+ if (i == 2) {
+ ingest_opt.write_global_seqno = true;
+ }
+ ASSERT_FALSE(db_->IngestExternalFile({file_name}, ingest_opt).ok());
+ 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::Default());
+ options.env = &senv;
+ DestroyAndReopen(options);
+
+ Options sst_file_writer_options;
+ std::unique_ptr<SstFileWriter> 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 = DBTestBase::RandomString(&rnd, 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_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
+ int kNumLevels = 7;
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = true;
+ options.num_levels = kNumLevels;
+ Reopen(options);
+
+ std::map<std::string, std::string> 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_TRUE(s.ok()) << 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_TRUE(s.ok()) << 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_TRUE(s.ok()) << 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<char*>(arg);
+ assert(nullptr != buf);
+ buf[0] ^= 0x1;
+ change_checksum_called = true;
+ }
+ };
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ SyncPoint::GetInstance()->SetCallBack(
+ "BlockBasedTableBuilder::WriteRawBlock: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<std::string, std::string> 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) {
+ SyncPoint::GetInstance()->DisableProcessing();
+ int file_id = 0;
+ EnvOptions env_options;
+ do {
+ Options options = CurrentOptions();
+ std::string file_path = sst_files_dir_ + ToString(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) + ToString(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<RandomRWFile> 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) {
+ 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<uint64_t*>(arg);
+ };
+ const auto& get_props_block_size = [&](void* arg) {
+ props_block_size = *reinterpret_cast<uint64_t*>(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_ + ToString(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) + ToString(0);
+ ASSERT_OK(sst_file_writer.Put(key, value));
+ }
+ ASSERT_OK(sst_file_writer.Finish());
+
+ {
+ std::unique_ptr<RandomRWFile> 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<std::string> 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));
+}
+
+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)));
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.cc b/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.cc
new file mode 100644
index 00000000..4cec5d37
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.cc
@@ -0,0 +1,731 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "db/external_sst_file_ingestion_job.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "db/version_edit.h"
+#include "file/file_util.h"
+#include "file/random_access_file_reader.h"
+#include "table/merging_iterator.h"
+#include "table/scoped_arena_iterator.h"
+#include "table/sst_file_writer_collectors.h"
+#include "table/table_builder.h"
+#include "test_util/sync_point.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status ExternalSstFileIngestionJob::Prepare(
+ const std::vector<std::string>& external_files_paths,
+ uint64_t next_file_number, SuperVersion* sv) {
+ Status status;
+
+ // Read the information of files we are ingesting
+ for (const std::string& file_path : external_files_paths) {
+ IngestedFileInfo file_to_ingest;
+ status = GetIngestedFileInfo(file_path, &file_to_ingest, sv);
+ if (!status.ok()) {
+ return status;
+ }
+ files_to_ingest_.push_back(file_to_ingest);
+ }
+
+ for (const IngestedFileInfo& f : files_to_ingest_) {
+ if (f.cf_id !=
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily &&
+ f.cf_id != cfd_->GetID()) {
+ return Status::InvalidArgument(
+ "External file column family id dont match");
+ }
+ }
+
+ const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
+ auto num_files = files_to_ingest_.size();
+ if (num_files == 0) {
+ return Status::InvalidArgument("The list of files is empty");
+ } else if (num_files > 1) {
+ // Verify that passed files dont have overlapping ranges
+ autovector<const IngestedFileInfo*> sorted_files;
+ for (size_t i = 0; i < num_files; i++) {
+ sorted_files.push_back(&files_to_ingest_[i]);
+ }
+
+ std::sort(
+ sorted_files.begin(), sorted_files.end(),
+ [&ucmp](const IngestedFileInfo* info1, const IngestedFileInfo* info2) {
+ return sstableKeyCompare(ucmp, info1->smallest_internal_key,
+ info2->smallest_internal_key) < 0;
+ });
+
+ for (size_t i = 0; i < num_files - 1; i++) {
+ if (sstableKeyCompare(ucmp, sorted_files[i]->largest_internal_key,
+ sorted_files[i + 1]->smallest_internal_key) >= 0) {
+ files_overlap_ = true;
+ break;
+ }
+ }
+ }
+
+ if (ingestion_options_.ingest_behind && files_overlap_) {
+ return Status::NotSupported("Files have overlapping ranges");
+ }
+
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ if (f.num_entries == 0 && f.num_range_deletions == 0) {
+ return Status::InvalidArgument("File contain no entries");
+ }
+
+ if (!f.smallest_internal_key.Valid() || !f.largest_internal_key.Valid()) {
+ return Status::Corruption("Generated table have corrupted keys");
+ }
+ }
+
+ // Copy/Move external files into DB
+ std::unordered_set<size_t> ingestion_path_ids;
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
+ f.copy_file = false;
+ const std::string path_outside_db = f.external_file_path;
+ const std::string path_inside_db =
+ TableFileName(cfd_->ioptions()->cf_paths, f.fd.GetNumber(),
+ f.fd.GetPathId());
+ if (ingestion_options_.move_files) {
+ status =
+ fs_->LinkFile(path_outside_db, path_inside_db, IOOptions(), nullptr);
+ if (status.ok()) {
+ // It is unsafe to assume application had sync the file and file
+ // directory before ingest the file. For integrity of RocksDB we need
+ // to sync the file.
+ std::unique_ptr<FSWritableFile> file_to_sync;
+ status = fs_->ReopenWritableFile(path_inside_db, env_options_,
+ &file_to_sync, nullptr);
+ if (status.ok()) {
+ TEST_SYNC_POINT(
+ "ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
+ status = SyncIngestedFile(file_to_sync.get());
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncIngestedFile");
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Failed to sync ingested file %s: %s",
+ path_inside_db.c_str(), status.ToString().c_str());
+ }
+ }
+ } else if (status.IsNotSupported() &&
+ ingestion_options_.failed_move_fall_back_to_copy) {
+ // Original file is on a different FS, use copy instead of hard linking.
+ f.copy_file = true;
+ }
+ } else {
+ f.copy_file = true;
+ }
+
+ if (f.copy_file) {
+ TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Prepare:CopyFile",
+ nullptr);
+ // CopyFile also sync the new file.
+ status = CopyFile(fs_, path_outside_db, path_inside_db, 0,
+ db_options_.use_fsync);
+ }
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::Prepare:FileAdded");
+ if (!status.ok()) {
+ break;
+ }
+ f.internal_file_path = path_inside_db;
+ ingestion_path_ids.insert(f.fd.GetPathId());
+ }
+
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncDir");
+ if (status.ok()) {
+ for (auto path_id : ingestion_path_ids) {
+ status = directories_->GetDataDir(path_id)->Fsync();
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Failed to sync directory %" ROCKSDB_PRIszt
+ " while ingest file: %s",
+ path_id, status.ToString().c_str());
+ break;
+ }
+ }
+ }
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncDir");
+
+ // TODO: The following is duplicated with Cleanup().
+ if (!status.ok()) {
+ // We failed, remove all files that we copied into the db
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ if (f.internal_file_path.empty()) {
+ continue;
+ }
+ Status s = env_->DeleteFile(f.internal_file_path);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "AddFile() clean up for file %s failed : %s",
+ f.internal_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ }
+
+ return status;
+}
+
+Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
+ SuperVersion* super_version) {
+ autovector<Range> ranges;
+ for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
+ ranges.emplace_back(file_to_ingest.smallest_internal_key.user_key(),
+ file_to_ingest.largest_internal_key.user_key());
+ }
+ Status status =
+ cfd_->RangesOverlapWithMemtables(ranges, super_version, flush_needed);
+ if (status.ok() && *flush_needed &&
+ !ingestion_options_.allow_blocking_flush) {
+ status = Status::InvalidArgument("External file requires flush");
+ }
+ return status;
+}
+
+// REQUIRES: we have become the only writer by entering both write_thread_ and
+// nonmem_write_thread_
+Status ExternalSstFileIngestionJob::Run() {
+ Status status;
+ SuperVersion* super_version = cfd_->GetSuperVersion();
+#ifndef NDEBUG
+ // We should never run the job with a memtable that is overlapping
+ // with the files we are ingesting
+ bool need_flush = false;
+ status = NeedsFlush(&need_flush, super_version);
+ assert(status.ok() && need_flush == false);
+#endif
+
+ bool force_global_seqno = false;
+
+ if (ingestion_options_.snapshot_consistency && !db_snapshots_->empty()) {
+ // We need to assign a global sequence number to all the files even
+ // if the dont overlap with any ranges since we have snapshots
+ force_global_seqno = true;
+ }
+ // It is safe to use this instead of LastAllocatedSequence since we are
+ // the only active writer, and hence they are equal
+ SequenceNumber last_seqno = versions_->LastSequence();
+ edit_.SetColumnFamily(cfd_->GetID());
+ // The levels that the files will be ingested into
+
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ SequenceNumber assigned_seqno = 0;
+ if (ingestion_options_.ingest_behind) {
+ status = CheckLevelForIngestedBehindFile(&f);
+ } else {
+ status = AssignLevelAndSeqnoForIngestedFile(
+ super_version, force_global_seqno, cfd_->ioptions()->compaction_style,
+ last_seqno, &f, &assigned_seqno);
+ }
+ if (!status.ok()) {
+ return status;
+ }
+ status = AssignGlobalSeqnoForIngestedFile(&f, assigned_seqno);
+ TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
+ &assigned_seqno);
+ if (assigned_seqno > last_seqno) {
+ assert(assigned_seqno == last_seqno + 1);
+ last_seqno = assigned_seqno;
+ ++consumed_seqno_count_;
+ }
+ if (!status.ok()) {
+ return status;
+ }
+
+ // We use the import time as the ancester time. This is the time the data
+ // is written to the database.
+ int64_t temp_current_time = 0;
+ uint64_t current_time = kUnknownFileCreationTime;
+ uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
+ if (env_->GetCurrentTime(&temp_current_time).ok()) {
+ current_time = oldest_ancester_time =
+ static_cast<uint64_t>(temp_current_time);
+ }
+
+ edit_.AddFile(
+ f.picked_level, f.fd.GetNumber(), f.fd.GetPathId(), f.fd.GetFileSize(),
+ f.smallest_internal_key, f.largest_internal_key, f.assigned_seqno,
+ f.assigned_seqno, false, kInvalidBlobFileNumber, oldest_ancester_time,
+ current_time, kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ }
+ return status;
+}
+
+void ExternalSstFileIngestionJob::UpdateStats() {
+ // Update internal stats for new ingested files
+ uint64_t total_keys = 0;
+ uint64_t total_l0_files = 0;
+ uint64_t total_time = env_->NowMicros() - job_start_time_;
+
+ EventLoggerStream stream = event_logger_->Log();
+ stream << "event"
+ << "ingest_finished";
+ stream << "files_ingested";
+ stream.StartArray();
+
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ InternalStats::CompactionStats stats(CompactionReason::kExternalSstIngestion, 1);
+ stats.micros = total_time;
+ // If actual copy occurred for this file, then we need to count the file
+ // size as the actual bytes written. If the file was linked, then we ignore
+ // the bytes written for file metadata.
+ // TODO (yanqin) maybe account for file metadata bytes for exact accuracy?
+ if (f.copy_file) {
+ stats.bytes_written = f.fd.GetFileSize();
+ } else {
+ stats.bytes_moved = f.fd.GetFileSize();
+ }
+ stats.num_output_files = 1;
+ cfd_->internal_stats()->AddCompactionStats(f.picked_level,
+ Env::Priority::USER, stats);
+ cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE,
+ f.fd.GetFileSize());
+ total_keys += f.num_entries;
+ if (f.picked_level == 0) {
+ total_l0_files += 1;
+ }
+ ROCKS_LOG_INFO(
+ db_options_.info_log,
+ "[AddFile] External SST file %s was ingested in L%d with path %s "
+ "(global_seqno=%" PRIu64 ")\n",
+ f.external_file_path.c_str(), f.picked_level,
+ f.internal_file_path.c_str(), f.assigned_seqno);
+ stream << "file" << f.internal_file_path << "level" << f.picked_level;
+ }
+ stream.EndArray();
+
+ stream << "lsm_state";
+ stream.StartArray();
+ auto vstorage = cfd_->current()->storage_info();
+ for (int level = 0; level < vstorage->num_levels(); ++level) {
+ stream << vstorage->NumLevelFiles(level);
+ }
+ stream.EndArray();
+
+ cfd_->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_KEYS_TOTAL,
+ total_keys);
+ cfd_->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_FILES_TOTAL,
+ files_to_ingest_.size());
+ cfd_->internal_stats()->AddCFStats(
+ InternalStats::INGESTED_LEVEL0_NUM_FILES_TOTAL, total_l0_files);
+}
+
+void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
+ if (!status.ok()) {
+ // We failed to add the files to the database
+ // remove all the files we copied
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ if (f.internal_file_path.empty()) {
+ continue;
+ }
+ Status s = env_->DeleteFile(f.internal_file_path);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "AddFile() clean up for file %s failed : %s",
+ f.internal_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ consumed_seqno_count_ = 0;
+ files_overlap_ = false;
+ } else if (status.ok() && ingestion_options_.move_files) {
+ // The files were moved and added successfully, remove original file links
+ for (IngestedFileInfo& f : files_to_ingest_) {
+ Status s = env_->DeleteFile(f.external_file_path);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ db_options_.info_log,
+ "%s was added to DB successfully but failed to remove original "
+ "file link : %s",
+ f.external_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ }
+}
+
+Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
+ const std::string& external_file, IngestedFileInfo* file_to_ingest,
+ SuperVersion* sv) {
+ file_to_ingest->external_file_path = external_file;
+
+ // Get external file size
+ Status status = fs_->GetFileSize(external_file, IOOptions(),
+ &file_to_ingest->file_size, nullptr);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Create TableReader for external file
+ std::unique_ptr<TableReader> table_reader;
+ std::unique_ptr<FSRandomAccessFile> sst_file;
+ std::unique_ptr<RandomAccessFileReader> sst_file_reader;
+
+ status = fs_->NewRandomAccessFile(external_file, env_options_,
+ &sst_file, nullptr);
+ if (!status.ok()) {
+ return status;
+ }
+ sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file),
+ external_file));
+
+ status = cfd_->ioptions()->table_factory->NewTableReader(
+ TableReaderOptions(*cfd_->ioptions(),
+ sv->mutable_cf_options.prefix_extractor.get(),
+ env_options_, cfd_->internal_comparator()),
+ std::move(sst_file_reader), file_to_ingest->file_size, &table_reader);
+ if (!status.ok()) {
+ return status;
+ }
+
+ if (ingestion_options_.verify_checksums_before_ingest) {
+ // If customized readahead size is needed, we can pass a user option
+ // all the way to here. Right now we just rely on the default readahead
+ // to keep things simple.
+ ReadOptions ro;
+ ro.readahead_size = ingestion_options_.verify_checksums_readahead_size;
+ status = table_reader->VerifyChecksum(
+ ro, TableReaderCaller::kExternalSSTIngestion);
+ }
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Get the external file properties
+ auto props = table_reader->GetTableProperties();
+ const auto& uprops = props->user_collected_properties;
+
+ // Get table version
+ auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
+ if (version_iter == uprops.end()) {
+ return Status::Corruption("External file version not found");
+ }
+ file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
+
+ auto seqno_iter = uprops.find(ExternalSstFilePropertyNames::kGlobalSeqno);
+ if (file_to_ingest->version == 2) {
+ // version 2 imply that we have global sequence number
+ if (seqno_iter == uprops.end()) {
+ return Status::Corruption(
+ "External file global sequence number not found");
+ }
+
+ // Set the global sequence number
+ file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
+ auto offsets_iter = props->properties_offsets.find(
+ ExternalSstFilePropertyNames::kGlobalSeqno);
+ if (offsets_iter == props->properties_offsets.end() ||
+ offsets_iter->second == 0) {
+ file_to_ingest->global_seqno_offset = 0;
+ return Status::Corruption("Was not able to find file global seqno field");
+ }
+ file_to_ingest->global_seqno_offset = static_cast<size_t>(offsets_iter->second);
+ } else if (file_to_ingest->version == 1) {
+ // SST file V1 should not have global seqno field
+ assert(seqno_iter == uprops.end());
+ file_to_ingest->original_seqno = 0;
+ if (ingestion_options_.allow_blocking_flush ||
+ ingestion_options_.allow_global_seqno) {
+ return Status::InvalidArgument(
+ "External SST file V1 does not support global seqno");
+ }
+ } else {
+ return Status::InvalidArgument("External file version is not supported");
+ }
+ // Get number of entries in table
+ file_to_ingest->num_entries = props->num_entries;
+ file_to_ingest->num_range_deletions = props->num_range_deletions;
+
+ ParsedInternalKey key;
+ ReadOptions ro;
+ // During reading the external file we can cache blocks that we read into
+ // the block cache, if we later change the global seqno of this file, we will
+ // have block in cache that will include keys with wrong seqno.
+ // We need to disable fill_cache so that we read from the file without
+ // updating the block cache.
+ ro.fill_cache = false;
+ std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
+ ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
+ std::unique_ptr<InternalIterator> range_del_iter(
+ table_reader->NewRangeTombstoneIterator(ro));
+
+ // Get first (smallest) and last (largest) key from file.
+ file_to_ingest->smallest_internal_key =
+ InternalKey("", 0, ValueType::kTypeValue);
+ file_to_ingest->largest_internal_key =
+ InternalKey("", 0, ValueType::kTypeValue);
+ bool bounds_set = false;
+ iter->SeekToFirst();
+ if (iter->Valid()) {
+ if (!ParseInternalKey(iter->key(), &key)) {
+ return Status::Corruption("external file have corrupted keys");
+ }
+ if (key.sequence != 0) {
+ return Status::Corruption("external file have non zero sequence number");
+ }
+ file_to_ingest->smallest_internal_key.SetFrom(key);
+
+ iter->SeekToLast();
+ if (!ParseInternalKey(iter->key(), &key)) {
+ return Status::Corruption("external file have corrupted keys");
+ }
+ if (key.sequence != 0) {
+ return Status::Corruption("external file have non zero sequence number");
+ }
+ file_to_ingest->largest_internal_key.SetFrom(key);
+
+ bounds_set = true;
+ }
+
+ // We may need to adjust these key bounds, depending on whether any range
+ // deletion tombstones extend past them.
+ const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
+ if (range_del_iter != nullptr) {
+ for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
+ range_del_iter->Next()) {
+ if (!ParseInternalKey(range_del_iter->key(), &key)) {
+ return Status::Corruption("external file have corrupted keys");
+ }
+ RangeTombstone tombstone(key, range_del_iter->value());
+
+ InternalKey start_key = tombstone.SerializeKey();
+ if (!bounds_set ||
+ sstableKeyCompare(ucmp, start_key,
+ file_to_ingest->smallest_internal_key) < 0) {
+ file_to_ingest->smallest_internal_key = start_key;
+ }
+ InternalKey end_key = tombstone.SerializeEndKey();
+ if (!bounds_set ||
+ sstableKeyCompare(ucmp, end_key,
+ file_to_ingest->largest_internal_key) > 0) {
+ file_to_ingest->largest_internal_key = end_key;
+ }
+ bounds_set = true;
+ }
+ }
+
+ file_to_ingest->cf_id = static_cast<uint32_t>(props->column_family_id);
+
+ file_to_ingest->table_properties = *props;
+
+ return status;
+}
+
+Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
+ SuperVersion* sv, bool force_global_seqno, CompactionStyle compaction_style,
+ SequenceNumber last_seqno, IngestedFileInfo* file_to_ingest,
+ SequenceNumber* assigned_seqno) {
+ Status status;
+ *assigned_seqno = 0;
+ if (force_global_seqno) {
+ *assigned_seqno = last_seqno + 1;
+ if (compaction_style == kCompactionStyleUniversal || files_overlap_) {
+ file_to_ingest->picked_level = 0;
+ return status;
+ }
+ }
+
+ bool overlap_with_db = false;
+ Arena arena;
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ int target_level = 0;
+ auto* vstorage = cfd_->current()->storage_info();
+
+ for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
+ if (lvl > 0 && lvl < vstorage->base_level()) {
+ continue;
+ }
+
+ if (vstorage->NumLevelFiles(lvl) > 0) {
+ bool overlap_with_level = false;
+ status = sv->current->OverlapWithLevelIterator(
+ ro, env_options_, file_to_ingest->smallest_internal_key.user_key(),
+ file_to_ingest->largest_internal_key.user_key(), lvl,
+ &overlap_with_level);
+ if (!status.ok()) {
+ return status;
+ }
+ if (overlap_with_level) {
+ // We must use L0 or any level higher than `lvl` to be able to overwrite
+ // the keys that we overlap with in this level, We also need to assign
+ // this file a seqno to overwrite the existing keys in level `lvl`
+ overlap_with_db = true;
+ break;
+ }
+
+ if (compaction_style == kCompactionStyleUniversal && lvl != 0) {
+ const std::vector<FileMetaData*>& level_files =
+ vstorage->LevelFiles(lvl);
+ const SequenceNumber level_largest_seqno =
+ (*max_element(level_files.begin(), level_files.end(),
+ [](FileMetaData* f1, FileMetaData* f2) {
+ return f1->fd.largest_seqno < f2->fd.largest_seqno;
+ }))
+ ->fd.largest_seqno;
+ // should only assign seqno to current level's largest seqno when
+ // the file fits
+ if (level_largest_seqno != 0 &&
+ IngestedFileFitInLevel(file_to_ingest, lvl)) {
+ *assigned_seqno = level_largest_seqno;
+ } else {
+ continue;
+ }
+ }
+ } else if (compaction_style == kCompactionStyleUniversal) {
+ continue;
+ }
+
+ // We dont overlap with any keys in this level, but we still need to check
+ // if our file can fit in it
+ if (IngestedFileFitInLevel(file_to_ingest, lvl)) {
+ target_level = lvl;
+ }
+ }
+ // If files overlap, we have to ingest them at level 0 and assign the newest
+ // sequence number
+ if (files_overlap_) {
+ target_level = 0;
+ *assigned_seqno = last_seqno + 1;
+ }
+ TEST_SYNC_POINT_CALLBACK(
+ "ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile",
+ &overlap_with_db);
+ file_to_ingest->picked_level = target_level;
+ if (overlap_with_db && *assigned_seqno == 0) {
+ *assigned_seqno = last_seqno + 1;
+ }
+ return status;
+}
+
+Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
+ IngestedFileInfo* file_to_ingest) {
+ auto* vstorage = cfd_->current()->storage_info();
+ // first check if new files fit in the bottommost level
+ int bottom_lvl = cfd_->NumberLevels() - 1;
+ if(!IngestedFileFitInLevel(file_to_ingest, bottom_lvl)) {
+ return Status::InvalidArgument(
+ "Can't ingest_behind file as it doesn't fit "
+ "at the bottommost level!");
+ }
+
+ // second check if despite allow_ingest_behind=true we still have 0 seqnums
+ // at some upper level
+ for (int lvl = 0; lvl < cfd_->NumberLevels() - 1; lvl++) {
+ for (auto file : vstorage->LevelFiles(lvl)) {
+ if (file->fd.smallest_seqno == 0) {
+ return Status::InvalidArgument(
+ "Can't ingest_behind file as despite allow_ingest_behind=true "
+ "there are files with 0 seqno in database at upper levels!");
+ }
+ }
+ }
+
+ file_to_ingest->picked_level = bottom_lvl;
+ return Status::OK();
+}
+
+Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
+ IngestedFileInfo* file_to_ingest, SequenceNumber seqno) {
+ if (file_to_ingest->original_seqno == seqno) {
+ // This file already have the correct global seqno
+ return Status::OK();
+ } else if (!ingestion_options_.allow_global_seqno) {
+ return Status::InvalidArgument("Global seqno is required, but disabled");
+ } else if (file_to_ingest->global_seqno_offset == 0) {
+ return Status::InvalidArgument(
+ "Trying to set global seqno for a file that dont have a global seqno "
+ "field");
+ }
+
+ if (ingestion_options_.write_global_seqno) {
+ // Determine if we can write global_seqno to a given offset of file.
+ // If the file system does not support random write, then we should not.
+ // Otherwise we should.
+ std::unique_ptr<FSRandomRWFile> rwfile;
+ Status status =
+ fs_->NewRandomRWFile(file_to_ingest->internal_file_path, env_options_,
+ &rwfile, nullptr);
+ if (status.ok()) {
+ std::string seqno_val;
+ PutFixed64(&seqno_val, seqno);
+ status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val,
+ IOOptions(), nullptr);
+ if (status.ok()) {
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
+ status = SyncIngestedFile(rwfile.get());
+ TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncGlobalSeqno");
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Failed to sync ingested file %s after writing global "
+ "sequence number: %s",
+ file_to_ingest->internal_file_path.c_str(),
+ status.ToString().c_str());
+ }
+ }
+ if (!status.ok()) {
+ return status;
+ }
+ } else if (!status.IsNotSupported()) {
+ return status;
+ }
+ }
+
+ file_to_ingest->assigned_seqno = seqno;
+ return Status::OK();
+}
+
+bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
+ const IngestedFileInfo* file_to_ingest, int level) {
+ if (level == 0) {
+ // Files can always fit in L0
+ return true;
+ }
+
+ auto* vstorage = cfd_->current()->storage_info();
+ Slice file_smallest_user_key(
+ file_to_ingest->smallest_internal_key.user_key());
+ Slice file_largest_user_key(file_to_ingest->largest_internal_key.user_key());
+
+ if (vstorage->OverlapInLevel(level, &file_smallest_user_key,
+ &file_largest_user_key)) {
+ // File overlap with another files in this level, we cannot
+ // add it to this level
+ return false;
+ }
+ if (cfd_->RangeOverlapWithCompaction(file_smallest_user_key,
+ file_largest_user_key, level)) {
+ // File overlap with a running compaction output that will be stored
+ // in this level, we cannot add this file to this level
+ return false;
+ }
+
+ // File did not overlap with level files, our compaction output
+ return true;
+}
+
+template <typename TWritableFile>
+Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
+ assert(file != nullptr);
+ if (db_options_.use_fsync) {
+ return file->Fsync(IOOptions(), nullptr);
+ } else {
+ return file->Sync(IOOptions(), nullptr);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.h b/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.h
new file mode 100644
index 00000000..7ddb6f3e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/external_sst_file_ingestion_job.h
@@ -0,0 +1,180 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/dbformat.h"
+#include "db/internal_stats.h"
+#include "db/snapshot_impl.h"
+#include "logging/event_logger.h"
+#include "options/db_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/sst_file_writer.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Directories;
+
+struct IngestedFileInfo {
+ // External file path
+ std::string external_file_path;
+ // Smallest internal key in external file
+ InternalKey smallest_internal_key;
+ // Largest internal key in external file
+ InternalKey largest_internal_key;
+ // Sequence number for keys in external file
+ SequenceNumber original_seqno;
+ // Offset of the global sequence number field in the file, will
+ // be zero if version is 1 (global seqno is not supported)
+ size_t global_seqno_offset;
+ // External file size
+ uint64_t file_size;
+ // total number of keys in external file
+ uint64_t num_entries;
+ // total number of range deletions in external file
+ uint64_t num_range_deletions;
+ // Id of column family this file shoule be ingested into
+ uint32_t cf_id;
+ // TableProperties read from external file
+ TableProperties table_properties;
+ // Version of external file
+ int version;
+
+ // FileDescriptor for the file inside the DB
+ FileDescriptor fd;
+ // file path that we picked for file inside the DB
+ std::string internal_file_path;
+ // Global sequence number that we picked for the file inside the DB
+ SequenceNumber assigned_seqno = 0;
+ // Level inside the DB we picked for the external file.
+ int picked_level = 0;
+ // Whether to copy or link the external sst file. copy_file will be set to
+ // false if ingestion_options.move_files is true and underlying FS
+ // supports link operation. Need to provide a default value to make the
+ // undefined-behavior sanity check of llvm happy. Since
+ // ingestion_options.move_files is false by default, thus copy_file is true
+ // by default.
+ bool copy_file = true;
+};
+
+class ExternalSstFileIngestionJob {
+ public:
+ ExternalSstFileIngestionJob(
+ Env* env, VersionSet* versions, ColumnFamilyData* cfd,
+ const ImmutableDBOptions& db_options, const EnvOptions& env_options,
+ SnapshotList* db_snapshots,
+ const IngestExternalFileOptions& ingestion_options,
+ Directories* directories, EventLogger* event_logger)
+ : env_(env),
+ fs_(db_options.fs.get()),
+ versions_(versions),
+ cfd_(cfd),
+ db_options_(db_options),
+ env_options_(env_options),
+ db_snapshots_(db_snapshots),
+ ingestion_options_(ingestion_options),
+ directories_(directories),
+ event_logger_(event_logger),
+ job_start_time_(env_->NowMicros()),
+ consumed_seqno_count_(0) {
+ assert(directories != nullptr);
+ }
+
+ // Prepare the job by copying external files into the DB.
+ Status Prepare(const std::vector<std::string>& external_files_paths,
+ uint64_t next_file_number, SuperVersion* sv);
+
+ // Check if we need to flush the memtable before running the ingestion job
+ // This will be true if the files we are ingesting are overlapping with any
+ // key range in the memtable.
+ //
+ // @param super_version A referenced SuperVersion that will be held for the
+ // duration of this function.
+ //
+ // Thread-safe
+ Status NeedsFlush(bool* flush_needed, SuperVersion* super_version);
+
+ // Will execute the ingestion job and prepare edit() to be applied.
+ // REQUIRES: Mutex held
+ Status Run();
+
+ // Update column family stats.
+ // REQUIRES: Mutex held
+ void UpdateStats();
+
+ // Cleanup after successful/failed job
+ void Cleanup(const Status& status);
+
+ VersionEdit* edit() { return &edit_; }
+
+ const autovector<IngestedFileInfo>& files_to_ingest() const {
+ return files_to_ingest_;
+ }
+
+ // How many sequence numbers did we consume as part of the ingest job?
+ int ConsumedSequenceNumbersCount() const { return consumed_seqno_count_; }
+
+ private:
+ // Open the external file and populate `file_to_ingest` with all the
+ // external information we need to ingest this file.
+ Status GetIngestedFileInfo(const std::string& external_file,
+ IngestedFileInfo* file_to_ingest,
+ SuperVersion* sv);
+
+ // Assign `file_to_ingest` the appropriate sequence number and the lowest
+ // possible level that it can be ingested to according to compaction_style.
+ // REQUIRES: Mutex held
+ Status AssignLevelAndSeqnoForIngestedFile(SuperVersion* sv,
+ bool force_global_seqno,
+ CompactionStyle compaction_style,
+ SequenceNumber last_seqno,
+ IngestedFileInfo* file_to_ingest,
+ SequenceNumber* assigned_seqno);
+
+ // File that we want to ingest behind always goes to the lowest level;
+ // we just check that it fits in the level, that DB allows ingest_behind,
+ // and that we don't have 0 seqnums at the upper levels.
+ // REQUIRES: Mutex held
+ Status CheckLevelForIngestedBehindFile(IngestedFileInfo* file_to_ingest);
+
+ // Set the file global sequence number to `seqno`
+ Status AssignGlobalSeqnoForIngestedFile(IngestedFileInfo* file_to_ingest,
+ SequenceNumber seqno);
+
+ // Check if `file_to_ingest` can fit in level `level`
+ // REQUIRES: Mutex held
+ bool IngestedFileFitInLevel(const IngestedFileInfo* file_to_ingest,
+ int level);
+
+ // Helper method to sync given file.
+ template <typename TWritableFile>
+ Status SyncIngestedFile(TWritableFile* file);
+
+ Env* env_;
+ FileSystem* fs_;
+ VersionSet* versions_;
+ ColumnFamilyData* cfd_;
+ const ImmutableDBOptions& db_options_;
+ const EnvOptions& env_options_;
+ SnapshotList* db_snapshots_;
+ autovector<IngestedFileInfo> files_to_ingest_;
+ const IngestExternalFileOptions& ingestion_options_;
+ Directories* directories_;
+ EventLogger* event_logger_;
+ VersionEdit edit_;
+ uint64_t job_start_time_;
+ int consumed_seqno_count_;
+ // Set in ExternalSstFileIngestionJob::Prepare(), if true all files are
+ // ingested in L0
+ bool files_overlap_{false};
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/external_sst_file_test.cc b/storage/rocksdb/rocksdb/db/external_sst_file_test.cc
new file mode 100644
index 00000000..0b91910a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/external_sst_file_test.cc
@@ -0,0 +1,2832 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <functional>
+#include "db/db_test_util.h"
+#include "file/filename.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/sst_file_writer.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A test environment that can be configured to fail the Link operation.
+class ExternalSSTTestEnv : public EnvWrapper {
+ public:
+ ExternalSSTTestEnv(Env* t, bool fail_link)
+ : EnvWrapper(t), fail_link_(fail_link) {}
+
+ Status LinkFile(const std::string& s, const std::string& t) override {
+ if (fail_link_) {
+ return Status::NotSupported("Link failed");
+ }
+ return target()->LinkFile(s, t);
+ }
+
+ void set_fail_link(bool fail_link) { fail_link_ = fail_link; }
+
+ private:
+ bool fail_link_;
+};
+
+class ExternSSTFileLinkFailFallbackTest
+ : public DBTestBase,
+ public ::testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ ExternSSTFileLinkFailFallbackTest()
+ : DBTestBase("/external_sst_file_test"),
+ test_env_(new ExternalSSTTestEnv(env_, true)) {
+ sst_files_dir_ = dbname_ + "/sst_files/";
+ test::DestroyDir(env_, sst_files_dir_);
+ env_->CreateDir(sst_files_dir_);
+ options_ = CurrentOptions();
+ options_.disable_auto_compactions = true;
+ options_.env = test_env_;
+ }
+
+ void TearDown() override {
+ delete db_;
+ db_ = nullptr;
+ ASSERT_OK(DestroyDB(dbname_, options_));
+ delete test_env_;
+ test_env_ = nullptr;
+ }
+
+ protected:
+ std::string sst_files_dir_;
+ Options options_;
+ ExternalSSTTestEnv* test_env_;
+};
+
+class ExternalSSTFileTest
+ : public DBTestBase,
+ public ::testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ ExternalSSTFileTest() : DBTestBase("/external_sst_file_test") {
+ sst_files_dir_ = dbname_ + "/sst_files/";
+ DestroyAndRecreateExternalSSTFilesDir();
+ }
+
+ void DestroyAndRecreateExternalSSTFilesDir() {
+ test::DestroyDir(env_, sst_files_dir_);
+ env_->CreateDir(sst_files_dir_);
+ }
+
+ Status GenerateOneExternalFile(
+ const Options& options, ColumnFamilyHandle* cfh,
+ std::vector<std::pair<std::string, std::string>>& data, int file_id,
+ bool sort_data, std::string* external_file_path,
+ std::map<std::string, std::string>* 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<std::string, std::string>& e1,
+ const std::pair<std::string, std::string>& e2) {
+ return options.comparator->Compare(e1.first, e2.first) < 0;
+ });
+ auto uniq_iter = std::unique(
+ data.begin(), data.end(),
+ [&](const std::pair<std::string, std::string>& e1,
+ const std::pair<std::string, std::string>& e2) {
+ return options.comparator->Compare(e1.first, e2.first) == 0;
+ });
+ data.resize(uniq_iter - data.begin());
+ }
+ std::string file_path = sst_files_dir_ + ToString(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();
+ 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<std::pair<std::string, std::string>> 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<std::string, std::string>* 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<std::string, std::string>& e1,
+ const std::pair<std::string, std::string>& e2) {
+ return options.comparator->Compare(e1.first, e2.first) < 0;
+ });
+ auto uniq_iter = std::unique(
+ data.begin(), data.end(),
+ [&](const std::pair<std::string, std::string>& e1,
+ const std::pair<std::string, std::string>& e2) {
+ return options.comparator->Compare(e1.first, e2.first) == 0;
+ });
+ data.resize(uniq_iter - data.begin());
+ }
+ std::string file_path = sst_files_dir_ + ToString(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();
+ 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<ColumnFamilyHandle*>& column_families,
+ const std::vector<IngestExternalFileOptions>& ifos,
+ std::vector<std::vector<std::pair<std::string, std::string>>>& data,
+ int file_id, bool sort_data,
+ std::vector<std::map<std::string, std::string>>& 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);
+ Status s;
+ std::vector<IngestExternalFileArg> args(num_cfs);
+ for (size_t i = 0; i != num_cfs; ++i) {
+ std::string external_file_path;
+ 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];
+ }
+ s = db_->IngestExternalFiles(args);
+ return s;
+ }
+
+ Status GenerateAndAddExternalFile(
+ const Options options, std::vector<std::pair<int, std::string>> 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<std::string, std::string>* true_data = nullptr,
+ ColumnFamilyHandle* cfh = nullptr) {
+ std::vector<std::pair<std::string, std::string>> 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<int> 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<std::string, std::string>* true_data = nullptr,
+ ColumnFamilyHandle* cfh = nullptr) {
+ std::vector<std::pair<std::string, std::string>> file_data;
+ for (auto& k : keys) {
+ file_data.emplace_back(Key(k), Key(k) + ToString(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<std::string>& 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);
+ }
+
+ ~ExternalSSTFileTest() override { test::DestroyDir(env_, sst_files_dir_); }
+
+ protected:
+ int last_file_id_ = 0;
+ std::string sst_files_dir_;
+};
+
+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;
+ Status s = sst_file_writer.Finish(&file1_info);
+ ASSERT_TRUE(s.ok()) << 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, "");
+ // sst_file_writer already finished, cannot add this value
+ s = sst_file_writer.Put(Key(100), "bad_val");
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // 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
+ s = sst_file_writer.Put(Key(99), "bad_val");
+ ASSERT_FALSE(s.ok()) << s.ToString();
+ ExternalSstFileInfo file2_info;
+ s = sst_file_writer.Finish(&file2_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file3_info);
+
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ // 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;
+ s = sst_file_writer.Finish(&file4_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file5_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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));
+ sst_file_writer.DeleteRange(Key(400), Key(500));
+ ExternalSstFileInfo file6_info;
+ s = sst_file_writer.Finish(&file6_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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));
+ 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"));
+ }
+ 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;
+ s = sst_file_writer.Finish(&file7_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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));
+ sst_file_writer.DeleteRange(Key(600), Key(700));
+ ExternalSstFileInfo file8_info;
+ s = sst_file_writer.Finish(&file8_info);
+ ASSERT_TRUE(s.ok()) << 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(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;
+ s = sst_file_writer.Finish(&file_empty_info);
+ ASSERT_NOK(s);
+
+ DestroyAndReopen(options);
+ // Add file using file path
+ s = DeprecatedAddFile({file1});
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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
+ s = DeprecatedAddFile({file3});
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // This file has overlapping values with the existing data
+ s = DeprecatedAddFile({file4});
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // 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) dont overlap with any keys in DB
+ ASSERT_OK(DeprecatedAddFile({file5}));
+
+ // This file has overlapping values with the existing data
+ s = DeprecatedAddFile({file6});
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // Key range of file7 (500 => 598) dont overlap with any keys in DB
+ ASSERT_OK(DeprecatedAddFile({file7}));
+
+ // Key range of file7 (600 => 700) dont 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<SstFileWriterCollectorFactory>("abc");
+ auto xyz_collector = std::make_shared<SstFileWriterCollectorFactory>("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;
+ Status s = sst_file_writer.Finish(&file1_info);
+ ASSERT_TRUE(s.ok()) << 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));
+ // sst_file_writer already finished, cannot add this value
+ s = sst_file_writer.Put(Key(100), "bad_val");
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // 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
+ s = sst_file_writer.Put(Key(99), "bad_val");
+ ASSERT_FALSE(s.ok()) << s.ToString();
+ ExternalSstFileInfo file2_info;
+ s = sst_file_writer.Finish(&file2_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file3_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file4_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file5_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file6_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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;
+ s = sst_file_writer.Finish(&file7_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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<std::string> file_list0({file1, file2});
+ std::vector<std::string> file_list1({file3, file2, file1});
+ std::vector<std::string> file_list2({file5});
+ std::vector<std::string> file_list3({file3, file4});
+ std::vector<std::string> file_list4({file5, file7});
+ std::vector<std::string> file_list5({file6, file7});
+
+ DestroyAndReopen(options);
+
+ // These lists of files have key ranges that overlap with each other
+ s = DeprecatedAddFile(file_list1);
+ ASSERT_FALSE(s.ok()) << s.ToString();
+ // Both of the following overlap on the range deletion tombstone.
+ s = DeprecatedAddFile(file_list4);
+ ASSERT_FALSE(s.ok()) << s.ToString();
+ s = DeprecatedAddFile(file_list5);
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // Add files using file path list
+ s = DeprecatedAddFile(file_list0);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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
+ s = DeprecatedAddFile(file_list3);
+ ASSERT_FALSE(s.ok()) << s.ToString();
+
+ // 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<std::string> files(n);
+ std::vector<ExternalSstFileInfo> 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"));
+ }
+ Status s = sst_file_writer.Finish(&files_info[i]);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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");
+ auto s = DeprecatedAddFile(files);
+ ASSERT_NOK(s) << s.ToString();
+ 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";
+ Status s = sst_file_writer.Open(sst_file_path);
+ ASSERT_OK(s);
+ for (int i = 0; i < 500; i++) {
+ std::string k = Key(i);
+ s = sst_file_writer.Put(k, k + "_val");
+ ASSERT_OK(s);
+ }
+
+ ExternalSstFileInfo sst_file_info;
+ s = sst_file_writer.Finish(&sst_file_info);
+ ASSERT_OK(s);
+
+ 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());
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ s = DeprecatedAddFile({sst_file_path});
+ ASSERT_OK(s);
+
+ 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;
+ Status s = sst_file_writer.Finish(&file1_info);
+ ASSERT_TRUE(s.ok()) << 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_TRUE(s.ok()) << 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));
+
+ 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;
+ s = sst_file_writer.Finish(&file3_info);
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ 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) {
+ // Bulk load 10 files every file contain 1000 keys
+ int num_files = 10;
+ int keys_per_file = 1000;
+
+ // Generate file names
+ std::vector<std::string> file_names;
+ for (int i = 0; i < num_files; i++) {
+ std::string file_name = "file_" + ToString(i) + ".sst";
+ file_names.push_back(sst_files_dir_ + file_name);
+ }
+
+ do {
+ Options options = CurrentOptions();
+
+ std::atomic<int> thread_num(0);
+ std::function<void()> 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)));
+ }
+
+ Status s = sst_file_writer.Finish();
+ ASSERT_TRUE(s.ok()) << s.ToString();
+ };
+ // Write num_files files in parallel
+ std::vector<port::Thread> 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<int> 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<void()> 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<std::string> files_to_add;
+
+ files_to_add = {file_names[file_idx]};
+ if (static_cast<size_t>(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<int>(files_to_add.size());
+ }
+ };
+
+ // Bulk load num_files files in parallel
+ std::vector<port::Thread> 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);
+ Status s = Put(key, key + "_new");
+ ASSERT_TRUE(s.ok());
+ }
+
+ 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) {
+ 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<SequenceNumber*>(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<bool*>(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<bool*>(arg));
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ do {
+ Options options = CurrentOptions();
+ DestroyAndReopen(options);
+
+ SstFileWriter sst_file_writer(EnvOptions(), options);
+
+ printf("Option config = %d\n", option_config_);
+ std::vector<std::pair<int, int>> 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<std::string, std::string> 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_" + ToString(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();
+ ASSERT_OK(sst_file_writer.Open(file_name));
+ 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) {
+ Flush();
+ }
+ if (i && i % 75 == 0) {
+ 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) {
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = false;
+ options.level0_file_num_compaction_trigger = 4;
+ options.num_levels = 4;
+ DestroyAndReopen(options);
+
+ std::map<std::string, std::string> 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, false, 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");
+
+ 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, PickedLevelBug) {
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = false;
+ options.level0_file_num_compaction_trigger = 3;
+ options.num_levels = 2;
+ DestroyAndReopen(options);
+
+ std::vector<int> file_keys;
+
+ // file #1 in L0
+ file_keys = {0, 5, 7};
+ for (int k : file_keys) {
+ ASSERT_OK(Put(Key(k), Key(k)));
+ }
+ ASSERT_OK(Flush());
+
+ // file #2 in L0
+ file_keys = {4, 6, 8, 9};
+ for (int k : file_keys) {
+ ASSERT_OK(Put(Key(k), Key(k)));
+ }
+ ASSERT_OK(Flush());
+
+ // We have 2 overlapping files in L0
+ EXPECT_EQ(FilesPerLevel(), "2");
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"DBImpl::AddFile:MutexLock", "ExternalSSTFileTest::PickedLevelBug:0"},
+ {"ExternalSSTFileTest::PickedLevelBug:1", "DBImpl::AddFile:MutexUnlock"},
+ {"ExternalSSTFileTest::PickedLevelBug:2",
+ "DBImpl::RunManualCompaction:0"},
+ {"ExternalSSTFileTest::PickedLevelBug:3",
+ "DBImpl::RunManualCompaction:1"}});
+
+ std::atomic<bool> bg_compact_started(false);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::BackgroundCompaction:Start",
+ [&](void* /*arg*/) { bg_compact_started.store(true); });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // While writing the MANIFEST start a thread that will ask for compaction
+ ROCKSDB_NAMESPACE::port::Thread bg_compact([&]() {
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ });
+ TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:2");
+
+ // Start a thread that will ingest a new file
+ ROCKSDB_NAMESPACE::port::Thread bg_addfile([&]() {
+ file_keys = {1, 2, 3};
+ ASSERT_OK(GenerateAndAddExternalFile(options, file_keys, 1));
+ });
+
+ // Wait for AddFile to start picking levels and writing MANIFEST
+ TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:0");
+
+ TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:3");
+
+ // We need to verify that no compactions can run while AddFile is
+ // ingesting the files into the levels it find suitable. So we will
+ // wait for 2 seconds to give a chance for compactions to run during
+ // this period, and then make sure that no compactions where able to run
+ env_->SleepForMicroseconds(1000000 * 2);
+ ASSERT_FALSE(bg_compact_started.load());
+
+ // Hold AddFile from finishing writing the MANIFEST
+ TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:1");
+
+ bg_addfile.join();
+ bg_compact.join();
+
+ dbfull()->TEST_WaitForCompact();
+
+ int total_keys = 0;
+ Iterator* iter = db_->NewIterator(ReadOptions());
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ ASSERT_OK(iter->status());
+ total_keys++;
+ }
+ ASSERT_EQ(total_keys, 10);
+
+ delete iter;
+
+ 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<std::string> files;
+ 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);
+}
+
+TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
+ Options options = CurrentOptions();
+ options.disable_auto_compactions = false;
+ options.level0_file_num_compaction_trigger = 2;
+ options.num_levels = 2;
+ DestroyAndReopen(options);
+
+ std::function<void()> bg_compact = [&]() {
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ };
+
+ int range_id = 0;
+ std::vector<int> file_keys;
+ std::function<void()> bg_addfile = [&]() {
+ ASSERT_OK(GenerateAndAddExternalFile(options, file_keys, range_id));
+ };
+
+ const int num_of_ranges = 1000;
+ std::vector<port::Thread> 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) + ToString(rid);
+ ASSERT_EQ(Get(Key(k)), v) << rid;
+ }
+ }
+}
+
+TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
+ 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<std::string, std::string> 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, false,
+ 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
+ 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<std::string> 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<std::string> 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 dont overlap with each other
+ in_files = {generated_files[0], generated_files[2]};
+ ASSERT_OK(DeprecatedAddFile(in_files));
+
+ // These 2 files dont overlap with each other but overlap with keys in DB
+ in_files = {generated_files[3], generated_files[7]};
+ ASSERT_NOK(DeprecatedAddFile(in_files));
+
+ // Files dont overlap and dont 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 compaction so will be added
+ // to L2 but a compaction will trivially move it to L3
+ // and break LSM consistency
+ static std::atomic<bool> called = {false};
+ if (!called) {
+ called = true;
+ ASSERT_OK(dbfull()->SetOptions({{"max_bytes_for_level_base", "1"}}));
+ ASSERT_OK(GenerateAndAddExternalFile(options, {15, 16}, 7));
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ CompactRangeOptions cro;
+ cro.exclusive_manual_compaction = false;
+ ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
+
+ 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<bool*>(need_flush));
+ });
+
+ Options options = CurrentOptions();
+ options.unordered_write = true;
+ DestroyAndReopen(options);
+ Put("foo", "v1");
+ SyncPoint::GetInstance()->EnableProcessing();
+ port::Thread writer([&]() { 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();
+}
+
+TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
+ 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<std::string, std::string> true_data;
+ for (int i = 0; i < 500; i++) {
+ std::vector<std::pair<std::string, std::string>> random_data;
+ for (int j = 0; j < 100; j++) {
+ std::string k;
+ std::string v;
+ test::RandomString(&rnd, rnd.Next() % 20, &k);
+ test::RandomString(&rnd, rnd.Next() % 50, &v);
+ 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);
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ VerifyDBFromMap(true_data, &kcnt, false);
+ }
+}
+
+TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
+ Options options = CurrentOptions();
+ options.num_levels = 5;
+ options.disable_auto_compactions = true;
+ DestroyAndReopen(options);
+ std::vector<std::pair<std::string, std::string>> file_data;
+ std::map<std::string, std::string> 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 dont 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 dont 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<std::string, std::string> true_data;
+
+ for (int k : {10, 20, 40, 80}) {
+ ASSERT_OK(Put(Key(k), "memtable"));
+ true_data[Key(k)] = "memtable";
+ }
+ 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));
+ 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));
+ 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";
+ }
+ 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));
+ 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));
+ 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<std::string, std::string> 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<int> 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;
+ }
+ env_->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<SstFileWriter> 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 dont match
+ ASSERT_NOK(db_->IngestExternalFile(handles_[0], {cf1_sst}, ifo));
+ // SST CF dont match
+ ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf1_sst}, ifo));
+ // SST CF match
+ ASSERT_OK(db_->IngestExternalFile(handles_[1], {cf1_sst}, ifo));
+
+ // SST CF dont match
+ ASSERT_NOK(db_->IngestExternalFile(handles_[1], {cf_default_sst}, ifo));
+ // SST CF dont 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());
+ test_env_->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<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
+ ColumnFamilyData* cfd = cfh->cfd();
+ const InternalStats* internal_stats_ptr = cfd->internal_stats();
+ const std::vector<InternalStats::CompactionStats>& 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<ExternalFileIngestionInfo> 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<std::pair<std::string, std::string>> file_data;
+ std::map<std::string, std::string> 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";
+ }
+ 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());
+ 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<int> 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<std::pair<std::string, std::string>> random_data;
+ for (int i = 0; i < kNumEntries; i++) {
+ std::string val;
+ test::RandomString(&rnd, kNumBytesPerEntry, &val);
+ random_data.emplace_back(Key(i), std::move(val));
+ }
+ ASSERT_OK(GenerateAndAddExternalFile(options, std::move(random_data)));
+ ASSERT_EQ(1, num_compression_dicts);
+}
+
+TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
+ std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
+ new FaultInjectionTestEnv(env_));
+ Options options = CurrentOptions();
+ options.env = fault_injection_env.get();
+ CreateAndReopenWithCF({"pikachu", "eevee"}, options);
+ std::vector<ColumnFamilyHandle*> column_families;
+ column_families.push_back(handles_[0]);
+ column_families.push_back(handles_[1]);
+ column_families.push_back(handles_[2]);
+ std::vector<IngestExternalFileOptions> 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<std::vector<std::pair<std::string, std::string>>> 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<std::map<std::string, std::string>> true_data(
+ column_families.size());
+ Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data,
+ -1, true, true_data);
+ ASSERT_OK(s);
+ 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<FaultInjectionTestEnv> 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<std::map<std::string, std::string>> 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<int>(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<ColumnFamilyHandle*> column_families;
+ column_families.push_back(handles_[0]);
+ column_families.push_back(handles_[1]);
+ column_families.push_back(handles_[2]);
+ std::vector<IngestExternalFileOptions> 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<std::vector<std::pair<std::string, std::string>>> 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<std::map<std::string, std::string>> 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<Iterator*> 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<std::string, std::string>& orig_data =
+ data_before_ingestion[i];
+ std::map<std::string, std::string>::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<std::string, std::string>::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<FaultInjectionTestEnv> 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<ColumnFamilyHandle*> column_families;
+ column_families.push_back(handles_[0]);
+ column_families.push_back(handles_[1]);
+ column_families.push_back(handles_[2]);
+ std::vector<IngestExternalFileOptions> 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<std::vector<std::pair<std::string, std::string>>> 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<std::map<std::string, std::string>> true_data(
+ column_families.size());
+ port::Thread ingest_thread([&]() {
+ Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data,
+ -1, true, true_data);
+ ASSERT_NOK(s);
+ });
+ 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<FaultInjectionTestEnv> 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<ColumnFamilyHandle*> column_families;
+ column_families.push_back(handles_[0]);
+ column_families.push_back(handles_[1]);
+ column_families.push_back(handles_[2]);
+ std::vector<IngestExternalFileOptions> 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<std::vector<std::pair<std::string, std::string>>> 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<std::map<std::string, std::string>> true_data(
+ column_families.size());
+ port::Thread ingest_thread([&]() {
+ Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data,
+ -1, true, true_data);
+ ASSERT_NOK(s);
+ });
+ 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<FaultInjectionTestEnv> 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<ColumnFamilyHandle*> column_families;
+ column_families.push_back(handles_[0]);
+ column_families.push_back(handles_[1]);
+ column_families.push_back(handles_[2]);
+ std::vector<IngestExternalFileOptions> 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<std::vector<std::pair<std::string, std::string>>> 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<std::map<std::string, std::string>> true_data(
+ column_families.size());
+ port::Thread ingest_thread([&]() {
+ Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data,
+ -1, true, true_data);
+ ASSERT_NOK(s);
+ });
+ TEST_SYNC_POINT(
+ "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_"
+ "PartialManifestWriteFail:0");
+ fault_injection_env->SetFilesystemActive(false);
+ TEST_SYNC_POINT(
+ "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_"
+ "PartialManifestWriteFail:1");
+ ingest_thread.join();
+
+ 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<std::pair<std::string, std::string>> data;
+ data.push_back(std::make_pair("1001", "v2"));
+ GenerateAndAddExternalFile(options, data);
+}
+
+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();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as External SST File Writer and Ingestion are not supported "
+ "in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/fault_injection_test.cc b/storage/rocksdb/rocksdb/db/fault_injection_test.cc
new file mode 100644
index 00000000..f4ca3458
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/fault_injection_test.cc
@@ -0,0 +1,555 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "logging/logging.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/table.h"
+#include "rocksdb/write_batch.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/mutexlock.h"
+
+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<std::tuple<
+ bool, FaultInjectionOptionConfig, FaultInjectionOptionConfig>> {
+ 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_;
+
+ protected:
+ public:
+ enum ExpectedVerifResult { kValExpectFound, kValExpectNoError };
+ enum ResetMethod {
+ kResetDropUnsyncedData,
+ kResetDropRandomUnsyncedData,
+ kResetDeleteUnsyncedFiles,
+ kResetDropAndDeleteUnsynced
+ };
+
+ std::unique_ptr<Env> base_env_;
+ FaultInjectionTestEnv* env_;
+ std::string dbname_;
+ std::shared_ptr<Cache> 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) {}
+
+ ~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(new MockEnv(Env::Default()));
+ }
+ 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() : Env::Default());
+
+ 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();
+ 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<int>(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);
+ return test::RandomString(&r, kValueSize, 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()));
+ }
+
+ delete iter;
+
+ FlushOptions flush_options;
+ flush_options.wait = true;
+ 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_) {
+ 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() {
+ static_cast<DBImpl*>(db_->GetRootDB())->TEST_WaitForCompact();
+ ASSERT_OK(db_->Put(WriteOptions(), "", ""));
+ }
+};
+
+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)));
+ 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;
+ 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<bool> 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;
+ batch.Put("cats", "dogs");
+ batch.MarkWalTerminationPoint();
+ 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());
+}
+
+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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/file_indexer.cc b/storage/rocksdb/rocksdb/db/file_indexer.cc
new file mode 100644
index 00000000..523cb3c1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/file_indexer.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <functional>
+#include "db/version_edit.h"
+#include "rocksdb/comparator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+FileIndexer::FileIndexer(const Comparator* ucmp)
+ : num_levels_(0), ucmp_(ucmp), level_rb_(nullptr) {}
+
+size_t FileIndexer::NumLevelIndex() const { return next_level_index_.size(); }
+
+size_t FileIndexer::LevelIndexSize(size_t level) const {
+ if (level >= next_level_index_.size()) {
+ return 0;
+ }
+ return next_level_index_[level].num_index;
+}
+
+void FileIndexer::GetNextLevelIndex(const size_t level, const size_t file_index,
+ const int cmp_smallest,
+ const int cmp_largest, int32_t* left_bound,
+ int32_t* right_bound) const {
+ assert(level > 0);
+
+ // Last level, no hint
+ if (level == num_levels_ - 1) {
+ *left_bound = 0;
+ *right_bound = -1;
+ return;
+ }
+
+ assert(level < num_levels_ - 1);
+ assert(static_cast<int32_t>(file_index) <= level_rb_[level]);
+
+ const IndexUnit* index_units = next_level_index_[level].index_units;
+ const auto& index = index_units[file_index];
+
+ if (cmp_smallest < 0) {
+ *left_bound = (level > 0 && file_index > 0)
+ ? index_units[file_index - 1].largest_lb
+ : 0;
+ *right_bound = index.smallest_rb;
+ } else if (cmp_smallest == 0) {
+ *left_bound = index.smallest_lb;
+ *right_bound = index.smallest_rb;
+ } else if (cmp_smallest > 0 && cmp_largest < 0) {
+ *left_bound = index.smallest_lb;
+ *right_bound = index.largest_rb;
+ } else if (cmp_largest == 0) {
+ *left_bound = index.largest_lb;
+ *right_bound = index.largest_rb;
+ } else if (cmp_largest > 0) {
+ *left_bound = index.largest_lb;
+ *right_bound = level_rb_[level + 1];
+ } else {
+ assert(false);
+ }
+
+ assert(*left_bound >= 0);
+ assert(*left_bound <= *right_bound + 1);
+ assert(*right_bound <= level_rb_[level + 1]);
+}
+
+void FileIndexer::UpdateIndex(Arena* arena, const size_t num_levels,
+ std::vector<FileMetaData*>* const files) {
+ if (files == nullptr) {
+ return;
+ }
+ if (num_levels == 0) { // uint_32 0-1 would cause bad behavior
+ num_levels_ = num_levels;
+ return;
+ }
+ assert(level_rb_ == nullptr); // level_rb_ should be init here
+
+ num_levels_ = num_levels;
+ next_level_index_.resize(num_levels);
+
+ char* mem = arena->AllocateAligned(num_levels_ * sizeof(int32_t));
+ level_rb_ = new (mem) int32_t[num_levels_];
+ for (size_t i = 0; i < num_levels_; i++) {
+ level_rb_[i] = -1;
+ }
+
+ // L1 - Ln-1
+ for (size_t level = 1; level < num_levels_ - 1; ++level) {
+ const auto& upper_files = files[level];
+ const int32_t upper_size = static_cast<int32_t>(upper_files.size());
+ const auto& lower_files = files[level + 1];
+ level_rb_[level] = static_cast<int32_t>(upper_files.size()) - 1;
+ if (upper_size == 0) {
+ continue;
+ }
+ IndexLevel& index_level = next_level_index_[level];
+ index_level.num_index = upper_size;
+ mem = arena->AllocateAligned(upper_size * sizeof(IndexUnit));
+ index_level.index_units = new (mem) IndexUnit[upper_size];
+
+ CalculateLB(
+ upper_files, lower_files, &index_level,
+ [this](const FileMetaData* a, const FileMetaData* b) -> int {
+ return ucmp_->CompareWithoutTimestamp(a->smallest.user_key(),
+ b->largest.user_key());
+ },
+ [](IndexUnit* index, int32_t f_idx) { index->smallest_lb = f_idx; });
+ CalculateLB(
+ upper_files, lower_files, &index_level,
+ [this](const FileMetaData* a, const FileMetaData* b) -> int {
+ return ucmp_->CompareWithoutTimestamp(a->largest.user_key(),
+ b->largest.user_key());
+ },
+ [](IndexUnit* index, int32_t f_idx) { index->largest_lb = f_idx; });
+ CalculateRB(
+ upper_files, lower_files, &index_level,
+ [this](const FileMetaData* a, const FileMetaData* b) -> int {
+ return ucmp_->CompareWithoutTimestamp(a->smallest.user_key(),
+ b->smallest.user_key());
+ },
+ [](IndexUnit* index, int32_t f_idx) { index->smallest_rb = f_idx; });
+ CalculateRB(
+ upper_files, lower_files, &index_level,
+ [this](const FileMetaData* a, const FileMetaData* b) -> int {
+ return ucmp_->CompareWithoutTimestamp(a->largest.user_key(),
+ b->smallest.user_key());
+ },
+ [](IndexUnit* index, int32_t f_idx) { index->largest_rb = f_idx; });
+ }
+
+ level_rb_[num_levels_ - 1] =
+ static_cast<int32_t>(files[num_levels_ - 1].size()) - 1;
+}
+
+void FileIndexer::CalculateLB(
+ const std::vector<FileMetaData*>& upper_files,
+ const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
+ std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
+ std::function<void(IndexUnit*, int32_t)> set_index) {
+ const int32_t upper_size = static_cast<int32_t>(upper_files.size());
+ const int32_t lower_size = static_cast<int32_t>(lower_files.size());
+ int32_t upper_idx = 0;
+ int32_t lower_idx = 0;
+
+ IndexUnit* index = index_level->index_units;
+ while (upper_idx < upper_size && lower_idx < lower_size) {
+ int cmp = cmp_op(upper_files[upper_idx], lower_files[lower_idx]);
+
+ if (cmp == 0) {
+ set_index(&index[upper_idx], lower_idx);
+ ++upper_idx;
+ } else if (cmp > 0) {
+ // Lower level's file (largest) is smaller, a key won't hit in that
+ // file. Move to next lower file
+ ++lower_idx;
+ } else {
+ // Lower level's file becomes larger, update the index, and
+ // move to the next upper file
+ set_index(&index[upper_idx], lower_idx);
+ ++upper_idx;
+ }
+ }
+
+ while (upper_idx < upper_size) {
+ // Lower files are exhausted, that means the remaining upper files are
+ // greater than any lower files. Set the index to be the lower level size.
+ set_index(&index[upper_idx], lower_size);
+ ++upper_idx;
+ }
+}
+
+void FileIndexer::CalculateRB(
+ const std::vector<FileMetaData*>& upper_files,
+ const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
+ std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
+ std::function<void(IndexUnit*, int32_t)> set_index) {
+ const int32_t upper_size = static_cast<int32_t>(upper_files.size());
+ const int32_t lower_size = static_cast<int32_t>(lower_files.size());
+ int32_t upper_idx = upper_size - 1;
+ int32_t lower_idx = lower_size - 1;
+
+ IndexUnit* index = index_level->index_units;
+ while (upper_idx >= 0 && lower_idx >= 0) {
+ int cmp = cmp_op(upper_files[upper_idx], lower_files[lower_idx]);
+
+ if (cmp == 0) {
+ set_index(&index[upper_idx], lower_idx);
+ --upper_idx;
+ } else if (cmp < 0) {
+ // Lower level's file (smallest) is larger, a key won't hit in that
+ // file. Move to next lower file.
+ --lower_idx;
+ } else {
+ // Lower level's file becomes smaller, update the index, and move to
+ // the next the upper file
+ set_index(&index[upper_idx], lower_idx);
+ --upper_idx;
+ }
+ }
+ while (upper_idx >= 0) {
+ // Lower files are exhausted, that means the remaining upper files are
+ // smaller than any lower files. Set it to -1.
+ set_index(&index[upper_idx], -1);
+ --upper_idx;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/file_indexer.h b/storage/rocksdb/rocksdb/db/file_indexer.h
new file mode 100644
index 00000000..ad7553f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/file_indexer.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdint>
+#include <functional>
+#include <limits>
+#include <vector>
+#include "memory/arena.h"
+#include "port/port.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Comparator;
+struct FileMetaData;
+struct FdWithKeyRange;
+struct FileLevel;
+
+// The file tree structure in Version is prebuilt and the range of each file
+// is known. On Version::Get(), it uses binary search to find a potential file
+// and then check if a target key can be found in the file by comparing the key
+// to each file's smallest and largest key. The results of these comparisons
+// can be reused beyond checking if a key falls into a file's range.
+// With some pre-calculated knowledge, each key comparison that has been done
+// can serve as a hint to narrow down further searches: if a key compared to
+// be smaller than a file's smallest or largest, that comparison can be used
+// to find out the right bound of next binary search. Similarly, if a key
+// compared to be larger than a file's smallest or largest, it can be utilized
+// to find out the left bound of next binary search.
+// With these hints: it can greatly reduce the range of binary search,
+// especially for bottom levels, given that one file most likely overlaps with
+// only N files from level below (where N is max_bytes_for_level_multiplier).
+// So on level L, we will only look at ~N files instead of N^L files on the
+// naive approach.
+class FileIndexer {
+ public:
+ explicit FileIndexer(const Comparator* ucmp);
+
+ size_t NumLevelIndex() const;
+
+ size_t LevelIndexSize(size_t level) const;
+
+ // Return a file index range in the next level to search for a key based on
+ // smallest and largest key comparison for the current file specified by
+ // level and file_index. When *left_index < *right_index, both index should
+ // be valid and fit in the vector size.
+ void GetNextLevelIndex(const size_t level, const size_t file_index,
+ const int cmp_smallest, const int cmp_largest,
+ int32_t* left_bound, int32_t* right_bound) const;
+
+ void UpdateIndex(Arena* arena, const size_t num_levels,
+ std::vector<FileMetaData*>* const files);
+
+ enum {
+ // MSVC version 1800 still does not have constexpr for ::max()
+ kLevelMaxIndex = ROCKSDB_NAMESPACE::port::kMaxInt32
+ };
+
+ private:
+ size_t num_levels_;
+ const Comparator* ucmp_;
+
+ struct IndexUnit {
+ IndexUnit()
+ : smallest_lb(0), largest_lb(0), smallest_rb(-1), largest_rb(-1) {}
+ // During file search, a key is compared against smallest and largest
+ // from a FileMetaData. It can have 3 possible outcomes:
+ // (1) key is smaller than smallest, implying it is also smaller than
+ // larger. Precalculated index based on "smallest < smallest" can
+ // be used to provide right bound.
+ // (2) key is in between smallest and largest.
+ // Precalculated index based on "smallest > greatest" can be used to
+ // provide left bound.
+ // Precalculated index based on "largest < smallest" can be used to
+ // provide right bound.
+ // (3) key is larger than largest, implying it is also larger than smallest.
+ // Precalculated index based on "largest > largest" can be used to
+ // provide left bound.
+ //
+ // As a result, we will need to do:
+ // Compare smallest (<=) and largest keys from upper level file with
+ // smallest key from lower level to get a right bound.
+ // Compare smallest (>=) and largest keys from upper level file with
+ // largest key from lower level to get a left bound.
+ //
+ // Example:
+ // level 1: [50 - 60]
+ // level 2: [1 - 40], [45 - 55], [58 - 80]
+ // A key 35, compared to be less than 50, 3rd file on level 2 can be
+ // skipped according to rule (1). LB = 0, RB = 1.
+ // A key 53, sits in the middle 50 and 60. 1st file on level 2 can be
+ // skipped according to rule (2)-a, but the 3rd file cannot be skipped
+ // because 60 is greater than 58. LB = 1, RB = 2.
+ // A key 70, compared to be larger than 60. 1st and 2nd file can be skipped
+ // according to rule (3). LB = 2, RB = 2.
+ //
+ // Point to a left most file in a lower level that may contain a key,
+ // which compares greater than smallest of a FileMetaData (upper level)
+ int32_t smallest_lb;
+ // Point to a left most file in a lower level that may contain a key,
+ // which compares greater than largest of a FileMetaData (upper level)
+ int32_t largest_lb;
+ // Point to a right most file in a lower level that may contain a key,
+ // which compares smaller than smallest of a FileMetaData (upper level)
+ int32_t smallest_rb;
+ // Point to a right most file in a lower level that may contain a key,
+ // which compares smaller than largest of a FileMetaData (upper level)
+ int32_t largest_rb;
+ };
+
+ // Data structure to store IndexUnits in a whole level
+ struct IndexLevel {
+ size_t num_index;
+ IndexUnit* index_units;
+
+ IndexLevel() : num_index(0), index_units(nullptr) {}
+ };
+
+ void CalculateLB(
+ const std::vector<FileMetaData*>& upper_files,
+ const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
+ std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
+ std::function<void(IndexUnit*, int32_t)> set_index);
+
+ void CalculateRB(
+ const std::vector<FileMetaData*>& upper_files,
+ const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
+ std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
+ std::function<void(IndexUnit*, int32_t)> set_index);
+
+ autovector<IndexLevel> next_level_index_;
+ int32_t* level_rb_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/file_indexer_test.cc b/storage/rocksdb/rocksdb/db/file_indexer_test.cc
new file mode 100644
index 00000000..99ce9399
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/file_indexer_test.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#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<const int64_t*>(a.data()) -
+ *reinterpret_cast<const int64_t*>(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<FileMetaData*>[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<char*>(&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<FileMetaData*>* 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/storage/rocksdb/rocksdb/db/filename_test.cc b/storage/rocksdb/rocksdb/db/filename_test.cc
new file mode 100644
index 00000000..9a04542f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/filename_test.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "logging/logging.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, kLogFile, kAllMode},
+ {"0.log", 0, kLogFile, 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, kLogFile,
+ 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;
+ 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(kLogFile, 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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/flush_job.cc b/storage/rocksdb/rocksdb/db/flush_job.cc
new file mode 100644
index 00000000..416715a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/flush_job.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/flush_job.h"
+
+#include <cinttypes>
+
+#include <algorithm>
+#include <vector>
+
+#include "db/builder.h"
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "db/event_helpers.h"
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/memtable.h"
+#include "db/memtable_list.h"
+#include "db/merge_context.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/version_set.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "logging/event_logger.h"
+#include "logging/log_buffer.h"
+#include "logging/logging.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/thread_status_util.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/merging_iterator.h"
+#include "table/table_builder.h"
+#include "table/two_level_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/mutexlock.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const char* GetFlushReasonString (FlushReason flush_reason) {
+ switch (flush_reason) {
+ case FlushReason::kOthers:
+ return "Other Reasons";
+ case FlushReason::kGetLiveFiles:
+ return "Get Live Files";
+ case FlushReason::kShutDown:
+ return "Shut down";
+ case FlushReason::kExternalFileIngestion:
+ return "External File Ingestion";
+ case FlushReason::kManualCompaction:
+ return "Manual Compaction";
+ case FlushReason::kWriteBufferManager:
+ return "Write Buffer Manager";
+ case FlushReason::kWriteBufferFull:
+ return "Write Buffer Full";
+ case FlushReason::kTest:
+ return "Test";
+ case FlushReason::kDeleteFiles:
+ return "Delete Files";
+ case FlushReason::kAutoCompaction:
+ return "Auto Compaction";
+ case FlushReason::kManualFlush:
+ return "Manual Flush";
+ case FlushReason::kErrorRecovery:
+ return "Error Recovery";
+ default:
+ return "Invalid";
+ }
+}
+
+FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
+ const ImmutableDBOptions& db_options,
+ const MutableCFOptions& mutable_cf_options,
+ const uint64_t* max_memtable_id,
+ const FileOptions& file_options, VersionSet* versions,
+ InstrumentedMutex* db_mutex,
+ std::atomic<bool>* shutting_down,
+ std::vector<SequenceNumber> existing_snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, JobContext* job_context,
+ LogBuffer* log_buffer, Directory* db_directory,
+ Directory* output_file_directory,
+ CompressionType output_compression, Statistics* stats,
+ EventLogger* event_logger, bool measure_io_stats,
+ const bool sync_output_directory, const bool write_manifest,
+ Env::Priority thread_pri)
+ : dbname_(dbname),
+ cfd_(cfd),
+ db_options_(db_options),
+ mutable_cf_options_(mutable_cf_options),
+ max_memtable_id_(max_memtable_id),
+ file_options_(file_options),
+ versions_(versions),
+ db_mutex_(db_mutex),
+ shutting_down_(shutting_down),
+ existing_snapshots_(std::move(existing_snapshots)),
+ earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
+ snapshot_checker_(snapshot_checker),
+ job_context_(job_context),
+ log_buffer_(log_buffer),
+ db_directory_(db_directory),
+ output_file_directory_(output_file_directory),
+ output_compression_(output_compression),
+ stats_(stats),
+ event_logger_(event_logger),
+ measure_io_stats_(measure_io_stats),
+ sync_output_directory_(sync_output_directory),
+ write_manifest_(write_manifest),
+ edit_(nullptr),
+ base_(nullptr),
+ pick_memtable_called(false),
+ thread_pri_(thread_pri) {
+ // Update the thread status to indicate flush.
+ ReportStartedFlush();
+ TEST_SYNC_POINT("FlushJob::FlushJob()");
+}
+
+FlushJob::~FlushJob() {
+ ThreadStatusUtil::ResetThreadStatus();
+}
+
+void FlushJob::ReportStartedFlush() {
+ ThreadStatusUtil::SetColumnFamily(cfd_, cfd_->ioptions()->env,
+ db_options_.enable_thread_tracking);
+ ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_FLUSH);
+ ThreadStatusUtil::SetThreadOperationProperty(
+ ThreadStatus::COMPACTION_JOB_ID,
+ job_context_->job_id);
+ IOSTATS_RESET(bytes_written);
+}
+
+void FlushJob::ReportFlushInputSize(const autovector<MemTable*>& mems) {
+ uint64_t input_size = 0;
+ for (auto* mem : mems) {
+ input_size += mem->ApproximateMemoryUsage();
+ }
+ ThreadStatusUtil::IncreaseThreadOperationProperty(
+ ThreadStatus::FLUSH_BYTES_MEMTABLES,
+ input_size);
+}
+
+void FlushJob::RecordFlushIOStats() {
+ RecordTick(stats_, FLUSH_WRITE_BYTES, IOSTATS(bytes_written));
+ ThreadStatusUtil::IncreaseThreadOperationProperty(
+ ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
+ IOSTATS_RESET(bytes_written);
+}
+
+void FlushJob::PickMemTable() {
+ db_mutex_->AssertHeld();
+ assert(!pick_memtable_called);
+ pick_memtable_called = true;
+ // Save the contents of the earliest memtable as a new Table
+ cfd_->imm()->PickMemtablesToFlush(max_memtable_id_, &mems_);
+ if (mems_.empty()) {
+ return;
+ }
+
+ ReportFlushInputSize(mems_);
+
+ // entries mems are (implicitly) sorted in ascending order by their created
+ // time. We will use the first memtable's `edit` to keep the meta info for
+ // this flush.
+ MemTable* m = mems_[0];
+ edit_ = m->GetEdits();
+ edit_->SetPrevLogNumber(0);
+ // SetLogNumber(log_num) indicates logs with number smaller than log_num
+ // will no longer be picked up for recovery.
+ edit_->SetLogNumber(mems_.back()->GetNextLogNumber());
+ edit_->SetColumnFamily(cfd_->GetID());
+
+ // path 0 for level 0 file.
+ meta_.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
+
+ base_ = cfd_->current();
+ base_->Ref(); // it is likely that we do not need this reference
+}
+
+Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
+ FileMetaData* file_meta) {
+ TEST_SYNC_POINT("FlushJob::Start");
+ db_mutex_->AssertHeld();
+ assert(pick_memtable_called);
+ AutoThreadOperationStageUpdater stage_run(
+ ThreadStatus::STAGE_FLUSH_RUN);
+ if (mems_.empty()) {
+ ROCKS_LOG_BUFFER(log_buffer_, "[%s] Nothing in memtable to flush",
+ cfd_->GetName().c_str());
+ return Status::OK();
+ }
+
+ // I/O measurement variables
+ PerfLevel prev_perf_level = PerfLevel::kEnableTime;
+ uint64_t prev_write_nanos = 0;
+ uint64_t prev_fsync_nanos = 0;
+ uint64_t prev_range_sync_nanos = 0;
+ uint64_t prev_prepare_write_nanos = 0;
+ uint64_t prev_cpu_write_nanos = 0;
+ uint64_t prev_cpu_read_nanos = 0;
+ if (measure_io_stats_) {
+ prev_perf_level = GetPerfLevel();
+ SetPerfLevel(PerfLevel::kEnableTime);
+ prev_write_nanos = IOSTATS(write_nanos);
+ prev_fsync_nanos = IOSTATS(fsync_nanos);
+ prev_range_sync_nanos = IOSTATS(range_sync_nanos);
+ prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
+ prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
+ prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
+ }
+
+ // This will release and re-acquire the mutex.
+ Status s = WriteLevel0Table();
+
+ if (s.ok() && cfd_->IsDropped()) {
+ s = Status::ColumnFamilyDropped("Column family dropped during compaction");
+ }
+ if ((s.ok() || s.IsColumnFamilyDropped()) &&
+ shutting_down_->load(std::memory_order_acquire)) {
+ s = Status::ShutdownInProgress("Database shutdown");
+ }
+
+ if (!s.ok()) {
+ cfd_->imm()->RollbackMemtableFlush(mems_, meta_.fd.GetNumber());
+ } else if (write_manifest_) {
+ TEST_SYNC_POINT("FlushJob::InstallResults");
+ // Replace immutable memtable with the generated Table
+ s = cfd_->imm()->TryInstallMemtableFlushResults(
+ cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
+ meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
+ log_buffer_, &committed_flush_jobs_info_);
+ }
+
+ if (s.ok() && file_meta != nullptr) {
+ *file_meta = meta_;
+ }
+ RecordFlushIOStats();
+
+ // When measure_io_stats_ is true, the default 512 bytes is not enough.
+ auto stream = event_logger_->LogToBuffer(log_buffer_, 1024);
+ stream << "job" << job_context_->job_id << "event"
+ << "flush_finished";
+ stream << "output_compression"
+ << CompressionTypeToString(output_compression_);
+ stream << "lsm_state";
+ stream.StartArray();
+ auto vstorage = cfd_->current()->storage_info();
+ for (int level = 0; level < vstorage->num_levels(); ++level) {
+ stream << vstorage->NumLevelFiles(level);
+ }
+ stream.EndArray();
+ stream << "immutable_memtables" << cfd_->imm()->NumNotFlushed();
+
+ if (measure_io_stats_) {
+ if (prev_perf_level != PerfLevel::kEnableTime) {
+ SetPerfLevel(prev_perf_level);
+ }
+ stream << "file_write_nanos" << (IOSTATS(write_nanos) - prev_write_nanos);
+ stream << "file_range_sync_nanos"
+ << (IOSTATS(range_sync_nanos) - prev_range_sync_nanos);
+ stream << "file_fsync_nanos" << (IOSTATS(fsync_nanos) - prev_fsync_nanos);
+ stream << "file_prepare_write_nanos"
+ << (IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos);
+ stream << "file_cpu_write_nanos"
+ << (IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos);
+ stream << "file_cpu_read_nanos"
+ << (IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos);
+ }
+
+ return s;
+}
+
+void FlushJob::Cancel() {
+ db_mutex_->AssertHeld();
+ assert(base_ != nullptr);
+ base_->Unref();
+}
+
+Status FlushJob::WriteLevel0Table() {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_FLUSH_WRITE_L0);
+ db_mutex_->AssertHeld();
+ const uint64_t start_micros = db_options_.env->NowMicros();
+ const uint64_t start_cpu_micros = db_options_.env->NowCPUNanos() / 1000;
+ Status s;
+ {
+ auto write_hint = cfd_->CalculateSSTWriteHint(0);
+ db_mutex_->Unlock();
+ if (log_buffer_) {
+ log_buffer_->FlushBufferToLog();
+ }
+ // memtables and range_del_iters store internal iterators over each data
+ // memtable and its associated range deletion memtable, respectively, at
+ // corresponding indexes.
+ std::vector<InternalIterator*> memtables;
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ range_del_iters;
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ Arena arena;
+ uint64_t total_num_entries = 0, total_num_deletes = 0;
+ uint64_t total_data_size = 0;
+ size_t total_memory_usage = 0;
+ for (MemTable* m : mems_) {
+ ROCKS_LOG_INFO(
+ db_options_.info_log,
+ "[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n",
+ cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
+ memtables.push_back(m->NewIterator(ro, &arena));
+ auto* range_del_iter =
+ m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
+ if (range_del_iter != nullptr) {
+ range_del_iters.emplace_back(range_del_iter);
+ }
+ total_num_entries += m->num_entries();
+ total_num_deletes += m->num_deletes();
+ total_data_size += m->get_data_size();
+ total_memory_usage += m->ApproximateMemoryUsage();
+ }
+
+ event_logger_->Log() << "job" << job_context_->job_id << "event"
+ << "flush_started"
+ << "num_memtables" << mems_.size() << "num_entries"
+ << total_num_entries << "num_deletes"
+ << total_num_deletes << "total_data_size"
+ << total_data_size << "memory_usage"
+ << total_memory_usage << "flush_reason"
+ << GetFlushReasonString(cfd_->GetFlushReason());
+
+ {
+ ScopedArenaIterator iter(
+ NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
+ static_cast<int>(memtables.size()), &arena));
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
+ cfd_->GetName().c_str(), job_context_->job_id,
+ meta_.fd.GetNumber());
+
+ TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:output_compression",
+ &output_compression_);
+ int64_t _current_time = 0;
+ auto status = db_options_.env->GetCurrentTime(&_current_time);
+ // Safe to proceed even if GetCurrentTime fails. So, log and proceed.
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(
+ db_options_.info_log,
+ "Failed to get current time to populate creation_time property. "
+ "Status: %s",
+ status.ToString().c_str());
+ }
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+
+ uint64_t oldest_key_time =
+ mems_.front()->ApproximateOldestKeyTime();
+
+ // It's not clear whether oldest_key_time is always available. In case
+ // it is not available, use current_time.
+ meta_.oldest_ancester_time = std::min(current_time, oldest_key_time);
+ meta_.file_creation_time = current_time;
+
+ s = BuildTable(
+ dbname_, db_options_.env, db_options_.fs.get(), *cfd_->ioptions(),
+ mutable_cf_options_, file_options_, cfd_->table_cache(), iter.get(),
+ std::move(range_del_iters), &meta_, cfd_->internal_comparator(),
+ cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
+ cfd_->GetName(), existing_snapshots_,
+ earliest_write_conflict_snapshot_, snapshot_checker_,
+ output_compression_, mutable_cf_options_.sample_for_compression,
+ cfd_->ioptions()->compression_opts,
+ mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
+ TableFileCreationReason::kFlush, event_logger_, job_context_->job_id,
+ Env::IO_HIGH, &table_properties_, 0 /* level */,
+ meta_.oldest_ancester_time, oldest_key_time, write_hint,
+ current_time);
+ LogFlush(db_options_.info_log);
+ }
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": %" PRIu64
+ " bytes %s"
+ "%s",
+ cfd_->GetName().c_str(), job_context_->job_id,
+ meta_.fd.GetNumber(), meta_.fd.GetFileSize(),
+ s.ToString().c_str(),
+ meta_.marked_for_compaction ? " (needs compaction)" : "");
+
+ if (s.ok() && output_file_directory_ != nullptr && sync_output_directory_) {
+ s = output_file_directory_->Fsync();
+ }
+ TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table", &mems_);
+ db_mutex_->Lock();
+ }
+ base_->Unref();
+
+ // Note that if file_size is zero, the file has been deleted and
+ // should not be added to the manifest.
+ if (s.ok() && meta_.fd.GetFileSize() > 0) {
+ // if we have more than 1 background thread, then we cannot
+ // insert files directly into higher levels because some other
+ // threads could be concurrently producing compacted files for
+ // that key range.
+ // Add file to L0
+ edit_->AddFile(0 /* level */, meta_.fd.GetNumber(), meta_.fd.GetPathId(),
+ meta_.fd.GetFileSize(), meta_.smallest, meta_.largest,
+ meta_.fd.smallest_seqno, meta_.fd.largest_seqno,
+ meta_.marked_for_compaction, meta_.oldest_blob_file_number,
+ meta_.oldest_ancester_time, meta_.file_creation_time,
+ meta_.file_checksum, meta_.file_checksum_func_name);
+ }
+#ifndef ROCKSDB_LITE
+ // Piggyback FlushJobInfo on the first first flushed memtable.
+ mems_[0]->SetFlushJobInfo(GetFlushJobInfo());
+#endif // !ROCKSDB_LITE
+
+ // Note that here we treat flush as level 0 compaction in internal stats
+ InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
+ stats.micros = db_options_.env->NowMicros() - start_micros;
+ stats.cpu_micros = db_options_.env->NowCPUNanos() / 1000 - start_cpu_micros;
+ stats.bytes_written = meta_.fd.GetFileSize();
+ RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros);
+ cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats);
+ cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
+ meta_.fd.GetFileSize());
+ RecordFlushIOStats();
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+std::unique_ptr<FlushJobInfo> FlushJob::GetFlushJobInfo() const {
+ db_mutex_->AssertHeld();
+ std::unique_ptr<FlushJobInfo> info(new FlushJobInfo{});
+ info->cf_id = cfd_->GetID();
+ info->cf_name = cfd_->GetName();
+
+ const uint64_t file_number = meta_.fd.GetNumber();
+ info->file_path =
+ MakeTableFileName(cfd_->ioptions()->cf_paths[0].path, file_number);
+ info->file_number = file_number;
+ info->oldest_blob_file_number = meta_.oldest_blob_file_number;
+ info->thread_id = db_options_.env->GetThreadID();
+ info->job_id = job_context_->job_id;
+ info->smallest_seqno = meta_.fd.smallest_seqno;
+ info->largest_seqno = meta_.fd.largest_seqno;
+ info->table_properties = table_properties_;
+ info->flush_reason = cfd_->GetFlushReason();
+ return info;
+}
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/flush_job.h b/storage/rocksdb/rocksdb/db/flush_job.h
new file mode 100644
index 00000000..1f4435f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/flush_job.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <deque>
+#include <limits>
+#include <list>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/dbformat.h"
+#include "db/flush_scheduler.h"
+#include "db/internal_stats.h"
+#include "db/job_context.h"
+#include "db/log_writer.h"
+#include "db/logs_with_prep_tracker.h"
+#include "db/memtable_list.h"
+#include "db/snapshot_impl.h"
+#include "db/version_edit.h"
+#include "db/write_controller.h"
+#include "db/write_thread.h"
+#include "logging/event_logger.h"
+#include "monitoring/instrumented_mutex.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/transaction_log.h"
+#include "table/scoped_arena_iterator.h"
+#include "util/autovector.h"
+#include "util/stop_watch.h"
+#include "util/thread_local.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+class MemTable;
+class SnapshotChecker;
+class TableCache;
+class Version;
+class VersionEdit;
+class VersionSet;
+class Arena;
+
+class FlushJob {
+ public:
+ // TODO(icanadi) make effort to reduce number of parameters here
+ // IMPORTANT: mutable_cf_options needs to be alive while FlushJob is alive
+ FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
+ const ImmutableDBOptions& db_options,
+ const MutableCFOptions& mutable_cf_options,
+ const uint64_t* max_memtable_id, const FileOptions& file_options,
+ VersionSet* versions, InstrumentedMutex* db_mutex,
+ std::atomic<bool>* shutting_down,
+ std::vector<SequenceNumber> existing_snapshots,
+ SequenceNumber earliest_write_conflict_snapshot,
+ SnapshotChecker* snapshot_checker, JobContext* job_context,
+ LogBuffer* log_buffer, Directory* db_directory,
+ Directory* output_file_directory, CompressionType output_compression,
+ Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
+ const bool sync_output_directory, const bool write_manifest,
+ Env::Priority thread_pri);
+
+ ~FlushJob();
+
+ // Require db_mutex held.
+ // Once PickMemTable() is called, either Run() or Cancel() has to be called.
+ void PickMemTable();
+ Status Run(LogsWithPrepTracker* prep_tracker = nullptr,
+ FileMetaData* file_meta = nullptr);
+ void Cancel();
+ const autovector<MemTable*>& GetMemTables() const { return mems_; }
+
+#ifndef ROCKSDB_LITE
+ std::list<std::unique_ptr<FlushJobInfo>>* GetCommittedFlushJobsInfo() {
+ return &committed_flush_jobs_info_;
+ }
+#endif // !ROCKSDB_LITE
+
+ private:
+ void ReportStartedFlush();
+ void ReportFlushInputSize(const autovector<MemTable*>& mems);
+ void RecordFlushIOStats();
+ Status WriteLevel0Table();
+#ifndef ROCKSDB_LITE
+ std::unique_ptr<FlushJobInfo> GetFlushJobInfo() const;
+#endif // !ROCKSDB_LITE
+
+ const std::string& dbname_;
+ ColumnFamilyData* cfd_;
+ const ImmutableDBOptions& db_options_;
+ const MutableCFOptions& mutable_cf_options_;
+ // Pointer to a variable storing the largest memtable id to flush in this
+ // flush job. RocksDB uses this variable to select the memtables to flush in
+ // this job. All memtables in this column family with an ID smaller than or
+ // equal to *max_memtable_id_ will be selected for flush. If null, then all
+ // memtables in the column family will be selected.
+ const uint64_t* max_memtable_id_;
+ const FileOptions file_options_;
+ VersionSet* versions_;
+ InstrumentedMutex* db_mutex_;
+ std::atomic<bool>* shutting_down_;
+ std::vector<SequenceNumber> existing_snapshots_;
+ SequenceNumber earliest_write_conflict_snapshot_;
+ SnapshotChecker* snapshot_checker_;
+ JobContext* job_context_;
+ LogBuffer* log_buffer_;
+ Directory* db_directory_;
+ Directory* output_file_directory_;
+ CompressionType output_compression_;
+ Statistics* stats_;
+ EventLogger* event_logger_;
+ TableProperties table_properties_;
+ bool measure_io_stats_;
+ // True if this flush job should call fsync on the output directory. False
+ // otherwise.
+ // Usually sync_output_directory_ is true. A flush job needs to call sync on
+ // the output directory before committing to the MANIFEST.
+ // However, an individual flush job does not have to call sync on the output
+ // directory if it is part of an atomic flush. After all flush jobs in the
+ // atomic flush succeed, call sync once on each distinct output directory.
+ const bool sync_output_directory_;
+ // True if this flush job should write to MANIFEST after successfully
+ // flushing memtables. False otherwise.
+ // Usually write_manifest_ is true. A flush job commits to the MANIFEST after
+ // flushing the memtables.
+ // However, an individual flush job cannot rashly write to the MANIFEST
+ // immediately after it finishes the flush if it is part of an atomic flush.
+ // In this case, only after all flush jobs succeed in flush can RocksDB
+ // commit to the MANIFEST.
+ const bool write_manifest_;
+ // The current flush job can commit flush result of a concurrent flush job.
+ // We collect FlushJobInfo of all jobs committed by current job and fire
+ // OnFlushCompleted for them.
+ std::list<std::unique_ptr<FlushJobInfo>> committed_flush_jobs_info_;
+
+ // Variables below are set by PickMemTable():
+ FileMetaData meta_;
+ autovector<MemTable*> mems_;
+ VersionEdit* edit_;
+ Version* base_;
+ bool pick_memtable_called;
+ Env::Priority thread_pri_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/flush_job_test.cc b/storage/rocksdb/rocksdb/db/flush_job_test.cc
new file mode 100644
index 00000000..b77a4a2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/flush_job_test.cc
@@ -0,0 +1,498 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <array>
+#include <map>
+#include <string>
+
+#include "db/blob_index.h"
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/flush_job.h"
+#include "db/version_set.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/cache.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 everything else:
+// 1. VersionSet
+// 2. Memtable
+class FlushJobTest : public testing::Test {
+ public:
+ FlushJobTest()
+ : env_(Env::Default()),
+ fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
+ dbname_(test::PerThreadDBPath("flush_job_test")),
+ 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()) {
+ EXPECT_OK(env_->CreateDirIfMissing(dbname_));
+ db_options_.db_paths.emplace_back(dbname_,
+ std::numeric_limits<uint64_t>::max());
+ db_options_.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ // TODO(icanadi) Remove this once we mock out VersionSet
+ NewDB();
+ std::vector<ColumnFamilyDescriptor> 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_);
+ }
+
+ db_options_.env = env_;
+ db_options_.fs = fs_;
+ versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
+ table_cache_.get(), &write_buffer_manager_,
+ &write_controller_,
+ /*block_cache_tracer=*/nullptr));
+ EXPECT_OK(versions_->Recover(column_families, false));
+ }
+
+ void NewDB() {
+ SetIdentityFile(env_, dbname_);
+ VersionEdit new_db;
+ if (db_options_.write_dbid_to_manifest) {
+ DBImpl* impl = new DBImpl(DBOptions(), 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);
+
+ autovector<VersionEdit> 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.SetLogNumber(0);
+ new_cf.SetNextFile(2);
+ new_cf.SetLastSequence(last_seq++);
+ new_cfs.emplace_back(new_cf);
+ }
+
+ const std::string manifest = DescriptorFileName(dbname_, 1);
+ std::unique_ptr<WritableFile> file;
+ Status s = env_->NewWritableFile(
+ manifest, &file, env_->OptimizeForManifestWrite(env_options_));
+ ASSERT_OK(s);
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), manifest, EnvOptions()));
+ {
+ log::Writer log(std::move(file_writer), 0, false);
+ std::string record;
+ new_db.EncodeTo(&record);
+ s = log.AddRecord(record);
+
+ 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(env_, dbname_, 1, nullptr);
+ }
+
+ Env* env_;
+ std::shared_ptr<FileSystem> fs_;
+ std::string dbname_;
+ EnvOptions env_options_;
+ Options options_;
+ ImmutableDBOptions db_options_;
+ const std::vector<std::string> column_family_names_;
+ std::shared_ptr<Cache> table_cache_;
+ WriteController write_controller_;
+ WriteBufferManager write_buffer_manager_;
+ ColumnFamilyOptions cf_options_;
+ std::unique_ptr<VersionSet> versions_;
+ InstrumentedMutex mutex_;
+ std::atomic<bool> shutting_down_;
+ std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
+};
+
+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(),
+ nullptr /* memtable_id */, env_options_, versions_.get(),
+ &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
+ snapshot_checker, &job_context, nullptr, nullptr, nullptr,
+ kNoCompression, nullptr, &event_logger, false,
+ true /* sync_output_directory */,
+ true /* write_manifest */, Env::Priority::USER);
+ {
+ 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(ToString((i + 1000) % 10000));
+ std::string value("value" + key);
+ new_mem->Add(SequenceNumber(i), kTypeValue, key, value);
+ if ((i + 1000) % 10000 < 9995) {
+ InternalKey internal_key(key, SequenceNumber(i), kTypeValue);
+ inserted_keys.insert({internal_key.Encode().ToString(), value});
+ }
+ }
+
+ {
+ new_mem->Add(SequenceNumber(10000), kTypeRangeDeletion, "9995", "9999a");
+ InternalKey internal_key("9995", SequenceNumber(10000), kTypeRangeDeletion);
+ inserted_keys.insert({internal_key.Encode().ToString(), "9999a"});
+ }
+
+#ifndef ROCKSDB_LITE
+ // 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<uint64_t, 6> blob_file_numbers{
+ kInvalidBlobFileNumber, 5, 103, 17, 102, 101};
+ for (size_t i = 0; i < blob_file_numbers.size(); ++i) {
+ std::string key(ToString(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);
+ new_mem->Add(seq, kTypeBlobIndex, key, blob_index);
+
+ InternalKey internal_key(key, seq, kTypeBlobIndex);
+ inserted_keys.emplace_hint(inserted_keys.end(),
+ internal_key.Encode().ToString(), blob_index);
+ }
+#endif
+
+ autovector<MemTable*> to_delete;
+ 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(),
+ nullptr /* memtable_id */, env_options_, versions_.get(),
+ &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
+ snapshot_checker, &job_context, nullptr, nullptr, nullptr,
+ kNoCompression, db_options_.statistics.get(),
+ &event_logger, true, true /* sync_output_directory */,
+ true /* write_manifest */, Env::Priority::USER);
+
+ 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(ToString(0), file_meta.smallest.user_key().ToString());
+ ASSERT_EQ("9999a", file_meta.largest.user_key().ToString());
+ ASSERT_EQ(1, file_meta.fd.smallest_seqno);
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(10006, file_meta.fd.largest_seqno);
+ ASSERT_EQ(17, file_meta.oldest_blob_file_number);
+#else
+ ASSERT_EQ(10000, file_meta.fd.largest_seqno);
+#endif
+ 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<uint64_t> memtable_ids;
+ std::vector<MemTable*> 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(ToString(j + i * num_keys_per_table));
+ std::string value("value" + key);
+ mem->Add(SequenceNumber(j + i * num_keys_per_table), kTypeValue, key,
+ value);
+ }
+ }
+
+ autovector<MemTable*> to_delete;
+ for (auto mem : new_mems) {
+ 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, nullptr, nullptr, nullptr, kNoCompression,
+ db_options_.statistics.get(), &event_logger, true,
+ true /* sync_output_directory */,
+ true /* write_manifest */, Env::Priority::USER);
+ 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(ToString(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<ColumnFamilyData*> all_cfds;
+ for (auto cfd : *versions_->GetColumnFamilySet()) {
+ all_cfds.push_back(cfd);
+ }
+ const std::vector<size_t> 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<uint64_t> memtable_ids;
+ std::vector<SequenceNumber> smallest_seqs;
+ std::vector<SequenceNumber> largest_seqs;
+ autovector<MemTable*> 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(ToString(j + i * num_keys_per_memtable));
+ std::string value("value" + key);
+ mem->Add(curr_seqno++, kTypeValue, key, value);
+ }
+
+ 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<std::unique_ptr<FlushJob>> flush_jobs;
+ k = 0;
+ for (auto cfd : all_cfds) {
+ std::vector<SequenceNumber> 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, nullptr, nullptr, nullptr, kNoCompression,
+ db_options_.statistics.get(), &event_logger, true,
+ false /* sync_output_directory */, false /* write_manifest */,
+ Env::Priority::USER));
+ k++;
+ }
+ HistogramData hist;
+ std::vector<FileMetaData> 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<FileMetaData*> file_meta_ptrs;
+ for (auto& meta : file_metas) {
+ file_meta_ptrs.push_back(&meta);
+ }
+ autovector<const autovector<MemTable*>*> 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<const MutableCFOptions*> mutable_cf_options_list;
+ for (auto cfd : all_cfds) {
+ mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions());
+ }
+
+ Status s = InstallMemtableAtomicFlushResults(
+ nullptr /* imm_lists */, all_cfds, mutable_cf_options_list, mems_list,
+ versions_.get(), &mutex_, file_meta_ptrs, &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(ToString(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<uint64_t>::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<SequenceNumber> 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<SequenceNumber> 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(ToString(i));
+ int insertions = rnd.Uniform(max_inserts_per_keys);
+ for (int j = 0; j < insertions; ++j) {
+ std::string value(test::RandomHumanReadableString(&rnd, 10));
+ auto seqno = ++current_seqno;
+ new_mem->Add(SequenceNumber(seqno), kTypeValue, key, value);
+ // 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.insert({internal_key.Encode().ToString(), value});
+ }
+ }
+ }
+
+ autovector<MemTable*> to_delete;
+ 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(),
+ nullptr /* memtable_id */, env_options_, versions_.get(),
+ &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
+ snapshot_checker, &job_context, nullptr, nullptr, nullptr,
+ kNoCompression, db_options_.statistics.get(),
+ &event_logger, true, true /* sync_output_directory */,
+ true /* write_manifest */, Env::Priority::USER);
+ 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();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/flush_scheduler.cc b/storage/rocksdb/rocksdb/db/flush_scheduler.cc
new file mode 100644
index 00000000..6f4d3e1a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/flush_scheduler.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_scheduler.h"
+
+#include <cassert>
+
+#include "db/column_family.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void FlushScheduler::ScheduleWork(ColumnFamilyData* cfd) {
+#ifndef NDEBUG
+ {
+ std::lock_guard<std::mutex> lock(checking_mutex_);
+ assert(checking_set_.count(cfd) == 0);
+ checking_set_.insert(cfd);
+ }
+#endif // NDEBUG
+ cfd->Ref();
+// Suppress false positive clang analyzer warnings.
+#ifndef __clang_analyzer__
+ Node* node = new Node{cfd, head_.load(std::memory_order_relaxed)};
+ while (!head_.compare_exchange_strong(
+ node->next, node, std::memory_order_relaxed, std::memory_order_relaxed)) {
+ // failing CAS updates the first param, so we are already set for
+ // retry. TakeNextColumnFamily won't happen until after another
+ // inter-thread synchronization, so we don't even need release
+ // semantics for this CAS
+ }
+#endif // __clang_analyzer__
+}
+
+ColumnFamilyData* FlushScheduler::TakeNextColumnFamily() {
+ while (true) {
+ if (head_.load(std::memory_order_relaxed) == nullptr) {
+ return nullptr;
+ }
+
+ // dequeue the head
+ Node* node = head_.load(std::memory_order_relaxed);
+ head_.store(node->next, std::memory_order_relaxed);
+ ColumnFamilyData* cfd = node->column_family;
+ delete node;
+
+#ifndef NDEBUG
+ {
+ std::lock_guard<std::mutex> lock(checking_mutex_);
+ auto iter = checking_set_.find(cfd);
+ assert(iter != checking_set_.end());
+ checking_set_.erase(iter);
+ }
+#endif // NDEBUG
+
+ if (!cfd->IsDropped()) {
+ // success
+ return cfd;
+ }
+
+ // no longer relevant, retry
+ cfd->UnrefAndTryDelete();
+ }
+}
+
+bool FlushScheduler::Empty() {
+ auto rv = head_.load(std::memory_order_relaxed) == nullptr;
+#ifndef NDEBUG
+ std::lock_guard<std::mutex> lock(checking_mutex_);
+ // Empty is allowed to be called concurrnetly with ScheduleFlush. It would
+ // only miss the recent schedules.
+ assert((rv == checking_set_.empty()) || rv);
+#endif // NDEBUG
+ return rv;
+}
+
+void FlushScheduler::Clear() {
+ ColumnFamilyData* cfd;
+ while ((cfd = TakeNextColumnFamily()) != nullptr) {
+ cfd->UnrefAndTryDelete();
+ }
+ assert(head_.load(std::memory_order_relaxed) == nullptr);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/flush_scheduler.h b/storage/rocksdb/rocksdb/db/flush_scheduler.h
new file mode 100644
index 00000000..cbe17994
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/flush_scheduler.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <atomic>
+#include <mutex>
+#include <set>
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyData;
+
+// FlushScheduler keeps track of all column families whose memtable may
+// be full and require flushing. Unless otherwise noted, all methods on
+// FlushScheduler should be called only with the DB mutex held or from
+// a single-threaded recovery context.
+class FlushScheduler {
+ public:
+ FlushScheduler() : head_(nullptr) {}
+
+ // May be called from multiple threads at once, but not concurrent with
+ // any other method calls on this instance
+ void ScheduleWork(ColumnFamilyData* cfd);
+
+ // Removes and returns Ref()-ed column family. Client needs to Unref().
+ // Filters column families that have been dropped.
+ ColumnFamilyData* TakeNextColumnFamily();
+
+ // This can be called concurrently with ScheduleWork but it would miss all
+ // the scheduled flushes after the last synchronization. This would result
+ // into less precise enforcement of memtable sizes but should not matter much.
+ bool Empty();
+
+ void Clear();
+
+ private:
+ struct Node {
+ ColumnFamilyData* column_family;
+ Node* next;
+ };
+
+ std::atomic<Node*> head_;
+#ifndef NDEBUG
+ std::mutex checking_mutex_;
+ std::set<ColumnFamilyData*> checking_set_;
+#endif // NDEBUG
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/forward_iterator.cc b/storage/rocksdb/rocksdb/db/forward_iterator.cc
new file mode 100644
index 00000000..f2b88254
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/forward_iterator.cc
@@ -0,0 +1,975 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "db/forward_iterator.h"
+
+#include <limits>
+#include <string>
+#include <utility>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "db/job_context.h"
+#include "db/range_del_aggregator.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "table/merging_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Usage:
+// ForwardLevelIterator iter;
+// iter.SetFileIndex(file_index);
+// iter.Seek(target); // or iter.SeekToFirst();
+// iter.Next()
+class ForwardLevelIterator : public InternalIterator {
+ public:
+ ForwardLevelIterator(const ColumnFamilyData* const cfd,
+ const ReadOptions& read_options,
+ const std::vector<FileMetaData*>& files,
+ const SliceTransform* prefix_extractor)
+ : cfd_(cfd),
+ read_options_(read_options),
+ files_(files),
+ valid_(false),
+ file_index_(std::numeric_limits<uint32_t>::max()),
+ file_iter_(nullptr),
+ pinned_iters_mgr_(nullptr),
+ prefix_extractor_(prefix_extractor) {}
+
+ ~ForwardLevelIterator() override {
+ // Reset current pointer
+ if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
+ pinned_iters_mgr_->PinIterator(file_iter_);
+ } else {
+ delete file_iter_;
+ }
+ }
+
+ void SetFileIndex(uint32_t file_index) {
+ assert(file_index < files_.size());
+ status_ = Status::OK();
+ if (file_index != file_index_) {
+ file_index_ = file_index;
+ Reset();
+ }
+ }
+ void Reset() {
+ assert(file_index_ < files_.size());
+
+ // Reset current pointer
+ if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
+ pinned_iters_mgr_->PinIterator(file_iter_);
+ } else {
+ delete file_iter_;
+ }
+
+ ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
+ kMaxSequenceNumber /* upper_bound */);
+ file_iter_ = cfd_->table_cache()->NewIterator(
+ read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
+ *files_[file_index_],
+ read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
+ prefix_extractor_, /*table_reader_ptr=*/nullptr,
+ /*file_read_hist=*/nullptr, TableReaderCaller::kUserIterator,
+ /*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr);
+ file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
+ valid_ = false;
+ if (!range_del_agg.IsEmpty()) {
+ status_ = Status::NotSupported(
+ "Range tombstones unsupported with ForwardIterator");
+ }
+ }
+ void SeekToLast() override {
+ status_ = Status::NotSupported("ForwardLevelIterator::SeekToLast()");
+ valid_ = false;
+ }
+ void Prev() override {
+ status_ = Status::NotSupported("ForwardLevelIterator::Prev()");
+ valid_ = false;
+ }
+ bool Valid() const override {
+ return valid_;
+ }
+ void SeekToFirst() override {
+ assert(file_iter_ != nullptr);
+ if (!status_.ok()) {
+ assert(!valid_);
+ return;
+ }
+ file_iter_->SeekToFirst();
+ valid_ = file_iter_->Valid();
+ }
+ void Seek(const Slice& internal_key) override {
+ assert(file_iter_ != nullptr);
+
+ // This deviates from the usual convention for InternalIterator::Seek() in
+ // that it doesn't discard pre-existing error status. That's because this
+ // Seek() is only supposed to be called immediately after SetFileIndex()
+ // (which discards pre-existing error status), and SetFileIndex() may set
+ // an error status, which we shouldn't discard.
+ if (!status_.ok()) {
+ assert(!valid_);
+ return;
+ }
+
+ file_iter_->Seek(internal_key);
+ valid_ = file_iter_->Valid();
+ }
+ void SeekForPrev(const Slice& /*internal_key*/) override {
+ status_ = Status::NotSupported("ForwardLevelIterator::SeekForPrev()");
+ valid_ = false;
+ }
+ void Next() override {
+ assert(valid_);
+ file_iter_->Next();
+ for (;;) {
+ valid_ = file_iter_->Valid();
+ if (!file_iter_->status().ok()) {
+ assert(!valid_);
+ return;
+ }
+ if (valid_) {
+ return;
+ }
+ if (file_index_ + 1 >= files_.size()) {
+ valid_ = false;
+ return;
+ }
+ SetFileIndex(file_index_ + 1);
+ if (!status_.ok()) {
+ assert(!valid_);
+ return;
+ }
+ file_iter_->SeekToFirst();
+ }
+ }
+ Slice key() const override {
+ assert(valid_);
+ return file_iter_->key();
+ }
+ Slice value() const override {
+ assert(valid_);
+ return file_iter_->value();
+ }
+ Status status() const override {
+ if (!status_.ok()) {
+ return status_;
+ } else if (file_iter_) {
+ return file_iter_->status();
+ }
+ return Status::OK();
+ }
+ bool IsKeyPinned() const override {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ file_iter_->IsKeyPinned();
+ }
+ bool IsValuePinned() const override {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ file_iter_->IsValuePinned();
+ }
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ if (file_iter_) {
+ file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+ }
+
+ private:
+ const ColumnFamilyData* const cfd_;
+ const ReadOptions& read_options_;
+ const std::vector<FileMetaData*>& files_;
+
+ bool valid_;
+ uint32_t file_index_;
+ Status status_;
+ InternalIterator* file_iter_;
+ PinnedIteratorsManager* pinned_iters_mgr_;
+ const SliceTransform* prefix_extractor_;
+};
+
+ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
+ ColumnFamilyData* cfd,
+ SuperVersion* current_sv)
+ : db_(db),
+ read_options_(read_options),
+ cfd_(cfd),
+ prefix_extractor_(current_sv->mutable_cf_options.prefix_extractor.get()),
+ user_comparator_(cfd->user_comparator()),
+ immutable_min_heap_(MinIterComparator(&cfd_->internal_comparator())),
+ sv_(current_sv),
+ mutable_iter_(nullptr),
+ current_(nullptr),
+ valid_(false),
+ status_(Status::OK()),
+ immutable_status_(Status::OK()),
+ has_iter_trimmed_for_upper_bound_(false),
+ current_over_upper_bound_(false),
+ is_prev_set_(false),
+ is_prev_inclusive_(false),
+ pinned_iters_mgr_(nullptr) {
+ if (sv_) {
+ RebuildIterators(false);
+ }
+}
+
+ForwardIterator::~ForwardIterator() {
+ Cleanup(true);
+}
+
+void ForwardIterator::SVCleanup(DBImpl* db, SuperVersion* sv,
+ bool background_purge_on_iterator_cleanup) {
+ if (sv->Unref()) {
+ // Job id == 0 means that this is not our background process, but rather
+ // user thread
+ JobContext job_context(0);
+ db->mutex_.Lock();
+ sv->Cleanup();
+ db->FindObsoleteFiles(&job_context, false, true);
+ if (background_purge_on_iterator_cleanup) {
+ db->ScheduleBgLogWriterClose(&job_context);
+ db->AddSuperVersionsToFreeQueue(sv);
+ db->SchedulePurge();
+ }
+ db->mutex_.Unlock();
+ if (!background_purge_on_iterator_cleanup) {
+ delete sv;
+ }
+ if (job_context.HaveSomethingToDelete()) {
+ db->PurgeObsoleteFiles(job_context, background_purge_on_iterator_cleanup);
+ }
+ job_context.Clean();
+ }
+}
+
+namespace {
+struct SVCleanupParams {
+ DBImpl* db;
+ SuperVersion* sv;
+ bool background_purge_on_iterator_cleanup;
+};
+}
+
+// Used in PinnedIteratorsManager to release pinned SuperVersion
+void ForwardIterator::DeferredSVCleanup(void* arg) {
+ auto d = reinterpret_cast<SVCleanupParams*>(arg);
+ ForwardIterator::SVCleanup(
+ d->db, d->sv, d->background_purge_on_iterator_cleanup);
+ delete d;
+}
+
+void ForwardIterator::SVCleanup() {
+ if (sv_ == nullptr) {
+ return;
+ }
+ bool background_purge =
+ read_options_.background_purge_on_iterator_cleanup ||
+ db_->immutable_db_options().avoid_unnecessary_blocking_io;
+ if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
+ // pinned_iters_mgr_ tells us to make sure that all visited key-value slices
+ // are alive until pinned_iters_mgr_->ReleasePinnedData() is called.
+ // The slices may point into some memtables owned by sv_, so we need to keep
+ // sv_ referenced until pinned_iters_mgr_ unpins everything.
+ auto p = new SVCleanupParams{db_, sv_, background_purge};
+ pinned_iters_mgr_->PinPtr(p, &ForwardIterator::DeferredSVCleanup);
+ } else {
+ SVCleanup(db_, sv_, background_purge);
+ }
+}
+
+void ForwardIterator::Cleanup(bool release_sv) {
+ if (mutable_iter_ != nullptr) {
+ DeleteIterator(mutable_iter_, true /* is_arena */);
+ }
+
+ for (auto* m : imm_iters_) {
+ DeleteIterator(m, true /* is_arena */);
+ }
+ imm_iters_.clear();
+
+ for (auto* f : l0_iters_) {
+ DeleteIterator(f);
+ }
+ l0_iters_.clear();
+
+ for (auto* l : level_iters_) {
+ DeleteIterator(l);
+ }
+ level_iters_.clear();
+
+ if (release_sv) {
+ SVCleanup();
+ }
+}
+
+bool ForwardIterator::Valid() const {
+ // See UpdateCurrent().
+ return valid_ ? !current_over_upper_bound_ : false;
+}
+
+void ForwardIterator::SeekToFirst() {
+ if (sv_ == nullptr) {
+ RebuildIterators(true);
+ } else if (sv_->version_number != cfd_->GetSuperVersionNumber()) {
+ RenewIterators();
+ } else if (immutable_status_.IsIncomplete()) {
+ ResetIncompleteIterators();
+ }
+ SeekInternal(Slice(), true);
+}
+
+bool ForwardIterator::IsOverUpperBound(const Slice& internal_key) const {
+ return !(read_options_.iterate_upper_bound == nullptr ||
+ cfd_->internal_comparator().user_comparator()->Compare(
+ ExtractUserKey(internal_key),
+ *read_options_.iterate_upper_bound) < 0);
+}
+
+void ForwardIterator::Seek(const Slice& internal_key) {
+ if (sv_ == nullptr) {
+ RebuildIterators(true);
+ } else if (sv_->version_number != cfd_->GetSuperVersionNumber()) {
+ RenewIterators();
+ } else if (immutable_status_.IsIncomplete()) {
+ ResetIncompleteIterators();
+ }
+ SeekInternal(internal_key, false);
+}
+
+void ForwardIterator::SeekInternal(const Slice& internal_key,
+ bool seek_to_first) {
+ assert(mutable_iter_);
+ // mutable
+ seek_to_first ? mutable_iter_->SeekToFirst() :
+ mutable_iter_->Seek(internal_key);
+
+ // immutable
+ // TODO(ljin): NeedToSeekImmutable has negative impact on performance
+ // if it turns to need to seek immutable often. We probably want to have
+ // an option to turn it off.
+ if (seek_to_first || NeedToSeekImmutable(internal_key)) {
+ immutable_status_ = Status::OK();
+ if (has_iter_trimmed_for_upper_bound_ &&
+ (
+ // prev_ is not set yet
+ is_prev_set_ == false ||
+ // We are doing SeekToFirst() and internal_key.size() = 0
+ seek_to_first ||
+ // prev_key_ > internal_key
+ cfd_->internal_comparator().InternalKeyComparator::Compare(
+ prev_key_.GetInternalKey(), internal_key) > 0)) {
+ // Some iterators are trimmed. Need to rebuild.
+ RebuildIterators(true);
+ // Already seeked mutable iter, so seek again
+ seek_to_first ? mutable_iter_->SeekToFirst()
+ : mutable_iter_->Seek(internal_key);
+ }
+ {
+ auto tmp = MinIterHeap(MinIterComparator(&cfd_->internal_comparator()));
+ immutable_min_heap_.swap(tmp);
+ }
+ for (size_t i = 0; i < imm_iters_.size(); i++) {
+ auto* m = imm_iters_[i];
+ seek_to_first ? m->SeekToFirst() : m->Seek(internal_key);
+ if (!m->status().ok()) {
+ immutable_status_ = m->status();
+ } else if (m->Valid()) {
+ immutable_min_heap_.push(m);
+ }
+ }
+
+ Slice target_user_key;
+ if (!seek_to_first) {
+ target_user_key = ExtractUserKey(internal_key);
+ }
+ const VersionStorageInfo* vstorage = sv_->current->storage_info();
+ const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
+ for (size_t i = 0; i < l0.size(); ++i) {
+ if (!l0_iters_[i]) {
+ continue;
+ }
+ if (seek_to_first) {
+ l0_iters_[i]->SeekToFirst();
+ } else {
+ // If the target key passes over the larget key, we are sure Next()
+ // won't go over this file.
+ if (user_comparator_->Compare(target_user_key,
+ l0[i]->largest.user_key()) > 0) {
+ if (read_options_.iterate_upper_bound != nullptr) {
+ has_iter_trimmed_for_upper_bound_ = true;
+ DeleteIterator(l0_iters_[i]);
+ l0_iters_[i] = nullptr;
+ }
+ continue;
+ }
+ l0_iters_[i]->Seek(internal_key);
+ }
+
+ if (!l0_iters_[i]->status().ok()) {
+ immutable_status_ = l0_iters_[i]->status();
+ } else if (l0_iters_[i]->Valid() &&
+ !IsOverUpperBound(l0_iters_[i]->key())) {
+ immutable_min_heap_.push(l0_iters_[i]);
+ } else {
+ has_iter_trimmed_for_upper_bound_ = true;
+ DeleteIterator(l0_iters_[i]);
+ l0_iters_[i] = nullptr;
+ }
+ }
+
+ for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
+ const std::vector<FileMetaData*>& level_files =
+ vstorage->LevelFiles(level);
+ if (level_files.empty()) {
+ continue;
+ }
+ if (level_iters_[level - 1] == nullptr) {
+ continue;
+ }
+ uint32_t f_idx = 0;
+ if (!seek_to_first) {
+ f_idx = FindFileInRange(level_files, internal_key, 0,
+ static_cast<uint32_t>(level_files.size()));
+ }
+
+ // Seek
+ if (f_idx < level_files.size()) {
+ level_iters_[level - 1]->SetFileIndex(f_idx);
+ seek_to_first ? level_iters_[level - 1]->SeekToFirst() :
+ level_iters_[level - 1]->Seek(internal_key);
+
+ if (!level_iters_[level - 1]->status().ok()) {
+ immutable_status_ = level_iters_[level - 1]->status();
+ } else if (level_iters_[level - 1]->Valid() &&
+ !IsOverUpperBound(level_iters_[level - 1]->key())) {
+ immutable_min_heap_.push(level_iters_[level - 1]);
+ } else {
+ // Nothing in this level is interesting. Remove.
+ has_iter_trimmed_for_upper_bound_ = true;
+ DeleteIterator(level_iters_[level - 1]);
+ level_iters_[level - 1] = nullptr;
+ }
+ }
+ }
+
+ if (seek_to_first) {
+ is_prev_set_ = false;
+ } else {
+ prev_key_.SetInternalKey(internal_key);
+ is_prev_set_ = true;
+ is_prev_inclusive_ = true;
+ }
+
+ TEST_SYNC_POINT_CALLBACK("ForwardIterator::SeekInternal:Immutable", this);
+ } else if (current_ && current_ != mutable_iter_) {
+ // current_ is one of immutable iterators, push it back to the heap
+ immutable_min_heap_.push(current_);
+ }
+
+ UpdateCurrent();
+ TEST_SYNC_POINT_CALLBACK("ForwardIterator::SeekInternal:Return", this);
+}
+
+void ForwardIterator::Next() {
+ assert(valid_);
+ bool update_prev_key = false;
+
+ if (sv_ == nullptr ||
+ sv_->version_number != cfd_->GetSuperVersionNumber()) {
+ std::string current_key = key().ToString();
+ Slice old_key(current_key.data(), current_key.size());
+
+ if (sv_ == nullptr) {
+ RebuildIterators(true);
+ } else {
+ RenewIterators();
+ }
+ SeekInternal(old_key, false);
+ if (!valid_ || key().compare(old_key) != 0) {
+ return;
+ }
+ } else if (current_ != mutable_iter_) {
+ // It is going to advance immutable iterator
+
+ if (is_prev_set_ && prefix_extractor_) {
+ // advance prev_key_ to current_ only if they share the same prefix
+ update_prev_key =
+ prefix_extractor_->Transform(prev_key_.GetUserKey())
+ .compare(prefix_extractor_->Transform(current_->key())) == 0;
+ } else {
+ update_prev_key = true;
+ }
+
+
+ if (update_prev_key) {
+ prev_key_.SetInternalKey(current_->key());
+ is_prev_set_ = true;
+ is_prev_inclusive_ = false;
+ }
+ }
+
+ current_->Next();
+ if (current_ != mutable_iter_) {
+ if (!current_->status().ok()) {
+ immutable_status_ = current_->status();
+ } else if ((current_->Valid()) && (!IsOverUpperBound(current_->key()))) {
+ immutable_min_heap_.push(current_);
+ } else {
+ if ((current_->Valid()) && (IsOverUpperBound(current_->key()))) {
+ // remove the current iterator
+ DeleteCurrentIter();
+ current_ = nullptr;
+ }
+ if (update_prev_key) {
+ mutable_iter_->Seek(prev_key_.GetInternalKey());
+ }
+ }
+ }
+ UpdateCurrent();
+ TEST_SYNC_POINT_CALLBACK("ForwardIterator::Next:Return", this);
+}
+
+Slice ForwardIterator::key() const {
+ assert(valid_);
+ return current_->key();
+}
+
+Slice ForwardIterator::value() const {
+ assert(valid_);
+ return current_->value();
+}
+
+Status ForwardIterator::status() const {
+ if (!status_.ok()) {
+ return status_;
+ } else if (!mutable_iter_->status().ok()) {
+ return mutable_iter_->status();
+ }
+
+ return immutable_status_;
+}
+
+Status ForwardIterator::GetProperty(std::string prop_name, std::string* prop) {
+ assert(prop != nullptr);
+ if (prop_name == "rocksdb.iterator.super-version-number") {
+ *prop = ToString(sv_->version_number);
+ return Status::OK();
+ }
+ return Status::InvalidArgument();
+}
+
+void ForwardIterator::SetPinnedItersMgr(
+ PinnedIteratorsManager* pinned_iters_mgr) {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ UpdateChildrenPinnedItersMgr();
+}
+
+void ForwardIterator::UpdateChildrenPinnedItersMgr() {
+ // Set PinnedIteratorsManager for mutable memtable iterator.
+ if (mutable_iter_) {
+ mutable_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+
+ // Set PinnedIteratorsManager for immutable memtable iterators.
+ for (InternalIterator* child_iter : imm_iters_) {
+ if (child_iter) {
+ child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+ }
+
+ // Set PinnedIteratorsManager for L0 files iterators.
+ for (InternalIterator* child_iter : l0_iters_) {
+ if (child_iter) {
+ child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+ }
+
+ // Set PinnedIteratorsManager for L1+ levels iterators.
+ for (ForwardLevelIterator* child_iter : level_iters_) {
+ if (child_iter) {
+ child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+ }
+}
+
+bool ForwardIterator::IsKeyPinned() const {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ current_->IsKeyPinned();
+}
+
+bool ForwardIterator::IsValuePinned() const {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ current_->IsValuePinned();
+}
+
+void ForwardIterator::RebuildIterators(bool refresh_sv) {
+ // Clean up
+ Cleanup(refresh_sv);
+ if (refresh_sv) {
+ // New
+ sv_ = cfd_->GetReferencedSuperVersion(db_);
+ }
+ ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
+ kMaxSequenceNumber /* upper_bound */);
+ mutable_iter_ = sv_->mem->NewIterator(read_options_, &arena_);
+ sv_->imm->AddIterators(read_options_, &imm_iters_, &arena_);
+ if (!read_options_.ignore_range_deletions) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ sv_->mem->NewRangeTombstoneIterator(
+ read_options_, sv_->current->version_set()->LastSequence()));
+ range_del_agg.AddTombstones(std::move(range_del_iter));
+ sv_->imm->AddRangeTombstoneIterators(read_options_, &arena_,
+ &range_del_agg);
+ }
+ has_iter_trimmed_for_upper_bound_ = false;
+
+ const auto* vstorage = sv_->current->storage_info();
+ const auto& l0_files = vstorage->LevelFiles(0);
+ l0_iters_.reserve(l0_files.size());
+ for (const auto* l0 : l0_files) {
+ if ((read_options_.iterate_upper_bound != nullptr) &&
+ cfd_->internal_comparator().user_comparator()->Compare(
+ l0->smallest.user_key(), *read_options_.iterate_upper_bound) > 0) {
+ // No need to set has_iter_trimmed_for_upper_bound_: this ForwardIterator
+ // will never be interested in files with smallest key above
+ // iterate_upper_bound, since iterate_upper_bound can't be changed.
+ l0_iters_.push_back(nullptr);
+ continue;
+ }
+ l0_iters_.push_back(cfd_->table_cache()->NewIterator(
+ read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0,
+ read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
+ sv_->mutable_cf_options.prefix_extractor.get(),
+ /*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
+ TableReaderCaller::kUserIterator, /*arena=*/nullptr,
+ /*skip_filters=*/false, /*level=*/-1,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr));
+ }
+ BuildLevelIterators(vstorage);
+ current_ = nullptr;
+ is_prev_set_ = false;
+
+ UpdateChildrenPinnedItersMgr();
+ if (!range_del_agg.IsEmpty()) {
+ status_ = Status::NotSupported(
+ "Range tombstones unsupported with ForwardIterator");
+ valid_ = false;
+ }
+}
+
+void ForwardIterator::RenewIterators() {
+ SuperVersion* svnew;
+ assert(sv_);
+ svnew = cfd_->GetReferencedSuperVersion(db_);
+
+ if (mutable_iter_ != nullptr) {
+ DeleteIterator(mutable_iter_, true /* is_arena */);
+ }
+ for (auto* m : imm_iters_) {
+ DeleteIterator(m, true /* is_arena */);
+ }
+ imm_iters_.clear();
+
+ mutable_iter_ = svnew->mem->NewIterator(read_options_, &arena_);
+ svnew->imm->AddIterators(read_options_, &imm_iters_, &arena_);
+ ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
+ kMaxSequenceNumber /* upper_bound */);
+ if (!read_options_.ignore_range_deletions) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ svnew->mem->NewRangeTombstoneIterator(
+ read_options_, sv_->current->version_set()->LastSequence()));
+ range_del_agg.AddTombstones(std::move(range_del_iter));
+ svnew->imm->AddRangeTombstoneIterators(read_options_, &arena_,
+ &range_del_agg);
+ }
+
+ const auto* vstorage = sv_->current->storage_info();
+ const auto& l0_files = vstorage->LevelFiles(0);
+ const auto* vstorage_new = svnew->current->storage_info();
+ const auto& l0_files_new = vstorage_new->LevelFiles(0);
+ size_t iold, inew;
+ bool found;
+ std::vector<InternalIterator*> l0_iters_new;
+ l0_iters_new.reserve(l0_files_new.size());
+
+ for (inew = 0; inew < l0_files_new.size(); inew++) {
+ found = false;
+ for (iold = 0; iold < l0_files.size(); iold++) {
+ if (l0_files[iold] == l0_files_new[inew]) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ if (l0_iters_[iold] == nullptr) {
+ l0_iters_new.push_back(nullptr);
+ TEST_SYNC_POINT_CALLBACK("ForwardIterator::RenewIterators:Null", this);
+ } else {
+ l0_iters_new.push_back(l0_iters_[iold]);
+ l0_iters_[iold] = nullptr;
+ TEST_SYNC_POINT_CALLBACK("ForwardIterator::RenewIterators:Copy", this);
+ }
+ continue;
+ }
+ l0_iters_new.push_back(cfd_->table_cache()->NewIterator(
+ read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
+ *l0_files_new[inew],
+ read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
+ svnew->mutable_cf_options.prefix_extractor.get(),
+ /*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
+ TableReaderCaller::kUserIterator, /*arena=*/nullptr,
+ /*skip_filters=*/false, /*level=*/-1,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr));
+ }
+
+ for (auto* f : l0_iters_) {
+ DeleteIterator(f);
+ }
+ l0_iters_.clear();
+ l0_iters_ = l0_iters_new;
+
+ for (auto* l : level_iters_) {
+ DeleteIterator(l);
+ }
+ level_iters_.clear();
+ BuildLevelIterators(vstorage_new);
+ current_ = nullptr;
+ is_prev_set_ = false;
+ SVCleanup();
+ sv_ = svnew;
+
+ UpdateChildrenPinnedItersMgr();
+ if (!range_del_agg.IsEmpty()) {
+ status_ = Status::NotSupported(
+ "Range tombstones unsupported with ForwardIterator");
+ valid_ = false;
+ }
+}
+
+void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage) {
+ level_iters_.reserve(vstorage->num_levels() - 1);
+ for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
+ const auto& level_files = vstorage->LevelFiles(level);
+ if ((level_files.empty()) ||
+ ((read_options_.iterate_upper_bound != nullptr) &&
+ (user_comparator_->Compare(*read_options_.iterate_upper_bound,
+ level_files[0]->smallest.user_key()) <
+ 0))) {
+ level_iters_.push_back(nullptr);
+ if (!level_files.empty()) {
+ has_iter_trimmed_for_upper_bound_ = true;
+ }
+ } else {
+ level_iters_.push_back(new ForwardLevelIterator(
+ cfd_, read_options_, level_files,
+ sv_->mutable_cf_options.prefix_extractor.get()));
+ }
+ }
+}
+
+void ForwardIterator::ResetIncompleteIterators() {
+ const auto& l0_files = sv_->current->storage_info()->LevelFiles(0);
+ for (size_t i = 0; i < l0_iters_.size(); ++i) {
+ assert(i < l0_files.size());
+ if (!l0_iters_[i] || !l0_iters_[i]->status().IsIncomplete()) {
+ continue;
+ }
+ DeleteIterator(l0_iters_[i]);
+ l0_iters_[i] = cfd_->table_cache()->NewIterator(
+ read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
+ *l0_files[i], /*range_del_agg=*/nullptr,
+ sv_->mutable_cf_options.prefix_extractor.get(),
+ /*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
+ TableReaderCaller::kUserIterator, /*arena=*/nullptr,
+ /*skip_filters=*/false, /*level=*/-1,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr);
+ l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+
+ for (auto* level_iter : level_iters_) {
+ if (level_iter && level_iter->status().IsIncomplete()) {
+ level_iter->Reset();
+ }
+ }
+
+ current_ = nullptr;
+ is_prev_set_ = false;
+}
+
+void ForwardIterator::UpdateCurrent() {
+ if (immutable_min_heap_.empty() && !mutable_iter_->Valid()) {
+ current_ = nullptr;
+ } else if (immutable_min_heap_.empty()) {
+ current_ = mutable_iter_;
+ } else if (!mutable_iter_->Valid()) {
+ current_ = immutable_min_heap_.top();
+ immutable_min_heap_.pop();
+ } else {
+ current_ = immutable_min_heap_.top();
+ assert(current_ != nullptr);
+ assert(current_->Valid());
+ int cmp = cfd_->internal_comparator().InternalKeyComparator::Compare(
+ mutable_iter_->key(), current_->key());
+ assert(cmp != 0);
+ if (cmp > 0) {
+ immutable_min_heap_.pop();
+ } else {
+ current_ = mutable_iter_;
+ }
+ }
+ valid_ = current_ != nullptr && immutable_status_.ok();
+ if (!status_.ok()) {
+ status_ = Status::OK();
+ }
+
+ // Upper bound doesn't apply to the memtable iterator. We want Valid() to
+ // return false when all iterators are over iterate_upper_bound, but can't
+ // just set valid_ to false, as that would effectively disable the tailing
+ // optimization (Seek() would be called on all immutable iterators regardless
+ // of whether the target key is greater than prev_key_).
+ current_over_upper_bound_ = valid_ && IsOverUpperBound(current_->key());
+}
+
+bool ForwardIterator::NeedToSeekImmutable(const Slice& target) {
+ // We maintain the interval (prev_key_, immutable_min_heap_.top()->key())
+ // such that there are no records with keys within that range in
+ // immutable_min_heap_. Since immutable structures (SST files and immutable
+ // memtables) can't change in this version, we don't need to do a seek if
+ // 'target' belongs to that interval (immutable_min_heap_.top() is already
+ // at the correct position).
+
+ if (!valid_ || !current_ || !is_prev_set_ || !immutable_status_.ok()) {
+ return true;
+ }
+ Slice prev_key = prev_key_.GetInternalKey();
+ if (prefix_extractor_ && prefix_extractor_->Transform(target).compare(
+ prefix_extractor_->Transform(prev_key)) != 0) {
+ return true;
+ }
+ if (cfd_->internal_comparator().InternalKeyComparator::Compare(
+ prev_key, target) >= (is_prev_inclusive_ ? 1 : 0)) {
+ return true;
+ }
+
+ if (immutable_min_heap_.empty() && current_ == mutable_iter_) {
+ // Nothing to seek on.
+ return false;
+ }
+ if (cfd_->internal_comparator().InternalKeyComparator::Compare(
+ target, current_ == mutable_iter_ ? immutable_min_heap_.top()->key()
+ : current_->key()) > 0) {
+ return true;
+ }
+ return false;
+}
+
+void ForwardIterator::DeleteCurrentIter() {
+ const VersionStorageInfo* vstorage = sv_->current->storage_info();
+ const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
+ for (size_t i = 0; i < l0.size(); ++i) {
+ if (!l0_iters_[i]) {
+ continue;
+ }
+ if (l0_iters_[i] == current_) {
+ has_iter_trimmed_for_upper_bound_ = true;
+ DeleteIterator(l0_iters_[i]);
+ l0_iters_[i] = nullptr;
+ return;
+ }
+ }
+
+ for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
+ if (level_iters_[level - 1] == nullptr) {
+ continue;
+ }
+ if (level_iters_[level - 1] == current_) {
+ has_iter_trimmed_for_upper_bound_ = true;
+ DeleteIterator(level_iters_[level - 1]);
+ level_iters_[level - 1] = nullptr;
+ }
+ }
+}
+
+bool ForwardIterator::TEST_CheckDeletedIters(int* pdeleted_iters,
+ int* pnum_iters) {
+ bool retval = false;
+ int deleted_iters = 0;
+ int num_iters = 0;
+
+ const VersionStorageInfo* vstorage = sv_->current->storage_info();
+ const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
+ for (size_t i = 0; i < l0.size(); ++i) {
+ if (!l0_iters_[i]) {
+ retval = true;
+ deleted_iters++;
+ } else {
+ num_iters++;
+ }
+ }
+
+ for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
+ if ((level_iters_[level - 1] == nullptr) &&
+ (!vstorage->LevelFiles(level).empty())) {
+ retval = true;
+ deleted_iters++;
+ } else if (!vstorage->LevelFiles(level).empty()) {
+ num_iters++;
+ }
+ }
+ if ((!retval) && num_iters <= 1) {
+ retval = true;
+ }
+ if (pdeleted_iters) {
+ *pdeleted_iters = deleted_iters;
+ }
+ if (pnum_iters) {
+ *pnum_iters = num_iters;
+ }
+ return retval;
+}
+
+uint32_t ForwardIterator::FindFileInRange(
+ const std::vector<FileMetaData*>& files, const Slice& internal_key,
+ uint32_t left, uint32_t right) {
+ auto cmp = [&](const FileMetaData* f, const Slice& key) -> bool {
+ return cfd_->internal_comparator().InternalKeyComparator::Compare(
+ f->largest.Encode(), key) < 0;
+ };
+ const auto &b = files.begin();
+ return static_cast<uint32_t>(std::lower_bound(b + left,
+ b + right, internal_key, cmp) - b);
+}
+
+void ForwardIterator::DeleteIterator(InternalIterator* iter, bool is_arena) {
+ if (iter == nullptr) {
+ return;
+ }
+
+ if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
+ pinned_iters_mgr_->PinIterator(iter, is_arena);
+ } else {
+ if (is_arena) {
+ iter->~InternalIterator();
+ } else {
+ delete iter;
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/forward_iterator.h b/storage/rocksdb/rocksdb/db/forward_iterator.h
new file mode 100644
index 00000000..8c671c75
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/forward_iterator.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+#include <queue>
+
+#include "db/dbformat.h"
+#include "memory/arena.h"
+#include "rocksdb/db.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "table/internal_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+class Env;
+struct SuperVersion;
+class ColumnFamilyData;
+class ForwardLevelIterator;
+class VersionStorageInfo;
+struct FileMetaData;
+
+class MinIterComparator {
+ public:
+ explicit MinIterComparator(const Comparator* comparator) :
+ comparator_(comparator) {}
+
+ bool operator()(InternalIterator* a, InternalIterator* b) {
+ return comparator_->Compare(a->key(), b->key()) > 0;
+ }
+ private:
+ const Comparator* comparator_;
+};
+
+typedef std::priority_queue<InternalIterator*, std::vector<InternalIterator*>,
+ MinIterComparator> MinIterHeap;
+
+/**
+ * ForwardIterator is a special type of iterator that only supports Seek()
+ * and Next(). It is expected to perform better than TailingIterator by
+ * removing the encapsulation and making all information accessible within
+ * the iterator. At the current implementation, snapshot is taken at the
+ * time Seek() is called. The Next() followed do not see new values after.
+ */
+class ForwardIterator : public InternalIterator {
+ public:
+ ForwardIterator(DBImpl* db, const ReadOptions& read_options,
+ ColumnFamilyData* cfd, SuperVersion* current_sv = nullptr);
+ virtual ~ForwardIterator();
+
+ void SeekForPrev(const Slice& /*target*/) override {
+ status_ = Status::NotSupported("ForwardIterator::SeekForPrev()");
+ valid_ = false;
+ }
+ void SeekToLast() override {
+ status_ = Status::NotSupported("ForwardIterator::SeekToLast()");
+ valid_ = false;
+ }
+ void Prev() override {
+ status_ = Status::NotSupported("ForwardIterator::Prev");
+ valid_ = false;
+ }
+
+ virtual bool Valid() const override;
+ void SeekToFirst() override;
+ virtual void Seek(const Slice& target) override;
+ virtual void Next() override;
+ virtual Slice key() const override;
+ virtual Slice value() const override;
+ virtual Status status() const override;
+ virtual Status GetProperty(std::string prop_name, std::string* prop) override;
+ virtual void SetPinnedItersMgr(
+ PinnedIteratorsManager* pinned_iters_mgr) override;
+ virtual bool IsKeyPinned() const override;
+ virtual bool IsValuePinned() const override;
+
+ bool TEST_CheckDeletedIters(int* deleted_iters, int* num_iters);
+
+ private:
+ void Cleanup(bool release_sv);
+ // Unreference and, if needed, clean up the current SuperVersion. This is
+ // either done immediately or deferred until this iterator is unpinned by
+ // PinnedIteratorsManager.
+ void SVCleanup();
+ static void SVCleanup(
+ DBImpl* db, SuperVersion* sv, bool background_purge_on_iterator_cleanup);
+ static void DeferredSVCleanup(void* arg);
+
+ void RebuildIterators(bool refresh_sv);
+ void RenewIterators();
+ void BuildLevelIterators(const VersionStorageInfo* vstorage);
+ void ResetIncompleteIterators();
+ void SeekInternal(const Slice& internal_key, bool seek_to_first);
+ void UpdateCurrent();
+ bool NeedToSeekImmutable(const Slice& internal_key);
+ void DeleteCurrentIter();
+ uint32_t FindFileInRange(
+ const std::vector<FileMetaData*>& files, const Slice& internal_key,
+ uint32_t left, uint32_t right);
+
+ bool IsOverUpperBound(const Slice& internal_key) const;
+
+ // Set PinnedIteratorsManager for all children Iterators, this function should
+ // be called whenever we update children Iterators or pinned_iters_mgr_.
+ void UpdateChildrenPinnedItersMgr();
+
+ // A helper function that will release iter in the proper manner, or pass it
+ // to pinned_iters_mgr_ to release it later if pinning is enabled.
+ void DeleteIterator(InternalIterator* iter, bool is_arena = false);
+
+ DBImpl* const db_;
+ const ReadOptions read_options_;
+ ColumnFamilyData* const cfd_;
+ const SliceTransform* const prefix_extractor_;
+ const Comparator* user_comparator_;
+ MinIterHeap immutable_min_heap_;
+
+ SuperVersion* sv_;
+ InternalIterator* mutable_iter_;
+ std::vector<InternalIterator*> imm_iters_;
+ std::vector<InternalIterator*> l0_iters_;
+ std::vector<ForwardLevelIterator*> level_iters_;
+ InternalIterator* current_;
+ bool valid_;
+
+ // Internal iterator status; set only by one of the unsupported methods.
+ Status status_;
+ // Status of immutable iterators, maintained here to avoid iterating over
+ // all of them in status().
+ Status immutable_status_;
+ // Indicates that at least one of the immutable iterators pointed to a key
+ // larger than iterate_upper_bound and was therefore destroyed. Seek() may
+ // need to rebuild such iterators.
+ bool has_iter_trimmed_for_upper_bound_;
+ // Is current key larger than iterate_upper_bound? If so, makes Valid()
+ // return false.
+ bool current_over_upper_bound_;
+
+ // Left endpoint of the range of keys that immutable iterators currently
+ // cover. When Seek() is called with a key that's within that range, immutable
+ // iterators don't need to be moved; see NeedToSeekImmutable(). This key is
+ // included in the range after a Seek(), but excluded when advancing the
+ // iterator using Next().
+ IterKey prev_key_;
+ bool is_prev_set_;
+ bool is_prev_inclusive_;
+
+ PinnedIteratorsManager* pinned_iters_mgr_;
+ Arena arena_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/forward_iterator_bench.cc b/storage/rocksdb/rocksdb/db/forward_iterator_bench.cc
new file mode 100644
index 00000000..6f122353
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/forward_iterator_bench.cc
@@ -0,0 +1,377 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+#if !defined(GFLAGS) || defined(ROCKSDB_LITE)
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#elif defined(OS_MACOSX) || defined(OS_WIN)
+// Block forward_iterator_bench under MAC and Windows
+int main() { return 0; }
+#else
+#include <semaphore.h>
+#include <atomic>
+#include <bitset>
+#include <chrono>
+#include <climits>
+#include <condition_variable>
+#include <limits>
+#include <mutex>
+#include <queue>
+#include <random>
+#include <thread>
+
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "test_util/testharness.h"
+#include "util/gflags_compat.h"
+
+const int MAX_SHARDS = 100000;
+
+DEFINE_int32(writers, 8, "");
+DEFINE_int32(readers, 8, "");
+DEFINE_int64(rate, 100000, "");
+DEFINE_int64(value_size, 300, "");
+DEFINE_int64(shards, 1000, "");
+DEFINE_int64(memtable_size, 500000000, "");
+DEFINE_int64(block_cache_size, 300000000, "");
+DEFINE_int64(block_size, 65536, "");
+DEFINE_double(runtime, 300.0, "");
+DEFINE_bool(cache_only_first, true, "");
+DEFINE_bool(iterate_upper_bound, true, "");
+
+struct Stats {
+ char pad1[128] __attribute__((__unused__));
+ std::atomic<uint64_t> written{0};
+ char pad2[128] __attribute__((__unused__));
+ std::atomic<uint64_t> read{0};
+ std::atomic<uint64_t> cache_misses{0};
+ char pad3[128] __attribute__((__unused__));
+} stats;
+
+struct Key {
+ Key() {}
+ Key(uint64_t shard_in, uint64_t seqno_in)
+ : shard_be(htobe64(shard_in)), seqno_be(htobe64(seqno_in)) {}
+
+ uint64_t shard() const { return be64toh(shard_be); }
+ uint64_t seqno() const { return be64toh(seqno_be); }
+
+ private:
+ uint64_t shard_be;
+ uint64_t seqno_be;
+} __attribute__((__packed__));
+
+struct Reader;
+struct Writer;
+
+struct ShardState {
+ char pad1[128] __attribute__((__unused__));
+ std::atomic<uint64_t> last_written{0};
+ Writer* writer;
+ Reader* reader;
+ char pad2[128] __attribute__((__unused__));
+ std::atomic<uint64_t> last_read{0};
+ std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> it;
+ std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> it_cacheonly;
+ Key upper_bound;
+ ROCKSDB_NAMESPACE::Slice upper_bound_slice;
+ char pad3[128] __attribute__((__unused__));
+};
+
+struct Reader {
+ public:
+ explicit Reader(std::vector<ShardState>* shard_states,
+ ROCKSDB_NAMESPACE::DB* db)
+ : shard_states_(shard_states), db_(db) {
+ sem_init(&sem_, 0, 0);
+ thread_ = port::Thread(&Reader::run, this);
+ }
+
+ void run() {
+ while (1) {
+ sem_wait(&sem_);
+ if (done_.load()) {
+ break;
+ }
+
+ uint64_t shard;
+ {
+ std::lock_guard<std::mutex> guard(queue_mutex_);
+ assert(!shards_pending_queue_.empty());
+ shard = shards_pending_queue_.front();
+ shards_pending_queue_.pop();
+ shards_pending_set_.reset(shard);
+ }
+ readOnceFromShard(shard);
+ }
+ }
+
+ void readOnceFromShard(uint64_t shard) {
+ ShardState& state = (*shard_states_)[shard];
+ if (!state.it) {
+ // Initialize iterators
+ ROCKSDB_NAMESPACE::ReadOptions options;
+ options.tailing = true;
+ if (FLAGS_iterate_upper_bound) {
+ state.upper_bound = Key(shard, std::numeric_limits<uint64_t>::max());
+ state.upper_bound_slice = ROCKSDB_NAMESPACE::Slice(
+ (const char*)&state.upper_bound, sizeof(state.upper_bound));
+ options.iterate_upper_bound = &state.upper_bound_slice;
+ }
+
+ state.it.reset(db_->NewIterator(options));
+
+ if (FLAGS_cache_only_first) {
+ options.read_tier = ROCKSDB_NAMESPACE::ReadTier::kBlockCacheTier;
+ state.it_cacheonly.reset(db_->NewIterator(options));
+ }
+ }
+
+ const uint64_t upto = state.last_written.load();
+ for (ROCKSDB_NAMESPACE::Iterator* it :
+ {state.it_cacheonly.get(), state.it.get()}) {
+ if (it == nullptr) {
+ continue;
+ }
+ if (state.last_read.load() >= upto) {
+ break;
+ }
+ bool need_seek = true;
+ for (uint64_t seq = state.last_read.load() + 1; seq <= upto; ++seq) {
+ if (need_seek) {
+ Key from(shard, state.last_read.load() + 1);
+ it->Seek(ROCKSDB_NAMESPACE::Slice((const char*)&from, sizeof(from)));
+ need_seek = false;
+ } else {
+ it->Next();
+ }
+ if (it->status().IsIncomplete()) {
+ ++::stats.cache_misses;
+ break;
+ }
+ assert(it->Valid());
+ assert(it->key().size() == sizeof(Key));
+ Key key;
+ memcpy(&key, it->key().data(), it->key().size());
+ // fprintf(stderr, "Expecting (%ld, %ld) read (%ld, %ld)\n",
+ // shard, seq, key.shard(), key.seqno());
+ assert(key.shard() == shard);
+ assert(key.seqno() == seq);
+ state.last_read.store(seq);
+ ++::stats.read;
+ }
+ }
+ }
+
+ void onWrite(uint64_t shard) {
+ {
+ std::lock_guard<std::mutex> guard(queue_mutex_);
+ if (!shards_pending_set_.test(shard)) {
+ shards_pending_queue_.push(shard);
+ shards_pending_set_.set(shard);
+ sem_post(&sem_);
+ }
+ }
+ }
+
+ ~Reader() {
+ done_.store(true);
+ sem_post(&sem_);
+ thread_.join();
+ }
+
+ private:
+ char pad1[128] __attribute__((__unused__));
+ std::vector<ShardState>* shard_states_;
+ ROCKSDB_NAMESPACE::DB* db_;
+ ROCKSDB_NAMESPACE::port::Thread thread_;
+ sem_t sem_;
+ std::mutex queue_mutex_;
+ std::bitset<MAX_SHARDS + 1> shards_pending_set_;
+ std::queue<uint64_t> shards_pending_queue_;
+ std::atomic<bool> done_{false};
+ char pad2[128] __attribute__((__unused__));
+};
+
+struct Writer {
+ explicit Writer(std::vector<ShardState>* shard_states,
+ ROCKSDB_NAMESPACE::DB* db)
+ : shard_states_(shard_states), db_(db) {}
+
+ void start() { thread_ = port::Thread(&Writer::run, this); }
+
+ void run() {
+ std::queue<std::chrono::steady_clock::time_point> workq;
+ std::chrono::steady_clock::time_point deadline(
+ std::chrono::steady_clock::now() +
+ std::chrono::nanoseconds((uint64_t)(1000000000 * FLAGS_runtime)));
+ std::vector<uint64_t> my_shards;
+ for (int i = 1; i <= FLAGS_shards; ++i) {
+ if ((*shard_states_)[i].writer == this) {
+ my_shards.push_back(i);
+ }
+ }
+
+ std::mt19937 rng{std::random_device()()};
+ std::uniform_int_distribution<int> shard_dist(
+ 0, static_cast<int>(my_shards.size()) - 1);
+ std::string value(FLAGS_value_size, '*');
+
+ while (1) {
+ auto now = std::chrono::steady_clock::now();
+ if (FLAGS_runtime >= 0 && now >= deadline) {
+ break;
+ }
+ if (workq.empty()) {
+ for (int i = 0; i < FLAGS_rate; i += FLAGS_writers) {
+ std::chrono::nanoseconds offset(1000000000LL * i / FLAGS_rate);
+ workq.push(now + offset);
+ }
+ }
+ while (!workq.empty() && workq.front() < now) {
+ workq.pop();
+ uint64_t shard = my_shards[shard_dist(rng)];
+ ShardState& state = (*shard_states_)[shard];
+ uint64_t seqno = state.last_written.load() + 1;
+ Key key(shard, seqno);
+ // fprintf(stderr, "Writing (%ld, %ld)\n", shard, seqno);
+ ROCKSDB_NAMESPACE::Status status =
+ db_->Put(ROCKSDB_NAMESPACE::WriteOptions(),
+ ROCKSDB_NAMESPACE::Slice((const char*)&key, sizeof(key)),
+ ROCKSDB_NAMESPACE::Slice(value));
+ assert(status.ok());
+ state.last_written.store(seqno);
+ state.reader->onWrite(shard);
+ ++::stats.written;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ // fprintf(stderr, "Writer done\n");
+ }
+
+ ~Writer() { thread_.join(); }
+
+ private:
+ char pad1[128] __attribute__((__unused__));
+ std::vector<ShardState>* shard_states_;
+ ROCKSDB_NAMESPACE::DB* db_;
+ ROCKSDB_NAMESPACE::port::Thread thread_;
+ char pad2[128] __attribute__((__unused__));
+};
+
+struct StatsThread {
+ explicit StatsThread(ROCKSDB_NAMESPACE::DB* db)
+ : db_(db), thread_(&StatsThread::run, this) {}
+
+ void run() {
+ // using namespace std::chrono;
+ auto tstart = std::chrono::steady_clock::now(), tlast = tstart;
+ uint64_t wlast = 0, rlast = 0;
+ while (!done_.load()) {
+ {
+ std::unique_lock<std::mutex> lock(cvm_);
+ cv_.wait_for(lock, std::chrono::seconds(1));
+ }
+ auto now = std::chrono::steady_clock::now();
+ double elapsed =
+ std::chrono::duration_cast<std::chrono::duration<double> >(
+ now - tlast).count();
+ uint64_t w = ::stats.written.load();
+ uint64_t r = ::stats.read.load();
+ fprintf(stderr,
+ "%s elapsed %4lds | written %10ld | w/s %10.0f | read %10ld | "
+ "r/s %10.0f | cache misses %10ld\n",
+ db_->GetEnv()->TimeToString(time(nullptr)).c_str(),
+ std::chrono::duration_cast<std::chrono::seconds>(now - tstart)
+ .count(),
+ w, (w - wlast) / elapsed, r, (r - rlast) / elapsed,
+ ::stats.cache_misses.load());
+ wlast = w;
+ rlast = r;
+ tlast = now;
+ }
+ }
+
+ ~StatsThread() {
+ {
+ std::lock_guard<std::mutex> guard(cvm_);
+ done_.store(true);
+ }
+ cv_.notify_all();
+ thread_.join();
+ }
+
+ private:
+ ROCKSDB_NAMESPACE::DB* db_;
+ std::mutex cvm_;
+ std::condition_variable cv_;
+ ROCKSDB_NAMESPACE::port::Thread thread_;
+ std::atomic<bool> done_{false};
+};
+
+int main(int argc, char** argv) {
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
+
+ std::mt19937 rng{std::random_device()()};
+ ROCKSDB_NAMESPACE::Status status;
+ std::string path =
+ ROCKSDB_NAMESPACE::test::PerThreadDBPath("forward_iterator_test");
+ fprintf(stderr, "db path is %s\n", path.c_str());
+ ROCKSDB_NAMESPACE::Options options;
+ options.create_if_missing = true;
+ options.compression = ROCKSDB_NAMESPACE::CompressionType::kNoCompression;
+ options.compaction_style =
+ ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleNone;
+ options.level0_slowdown_writes_trigger = 99999;
+ options.level0_stop_writes_trigger = 99999;
+ options.use_direct_io_for_flush_and_compaction = true;
+ options.write_buffer_size = FLAGS_memtable_size;
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions table_options;
+ table_options.block_cache =
+ ROCKSDB_NAMESPACE::NewLRUCache(FLAGS_block_cache_size);
+ table_options.block_size = FLAGS_block_size;
+ options.table_factory.reset(
+ ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(table_options));
+
+ status = ROCKSDB_NAMESPACE::DestroyDB(path, options);
+ assert(status.ok());
+ ROCKSDB_NAMESPACE::DB* db_raw;
+ status = ROCKSDB_NAMESPACE::DB::Open(options, path, &db_raw);
+ assert(status.ok());
+ std::unique_ptr<ROCKSDB_NAMESPACE::DB> db(db_raw);
+
+ std::vector<ShardState> shard_states(FLAGS_shards + 1);
+ std::deque<Reader> readers;
+ while (static_cast<int>(readers.size()) < FLAGS_readers) {
+ readers.emplace_back(&shard_states, db_raw);
+ }
+ std::deque<Writer> writers;
+ while (static_cast<int>(writers.size()) < FLAGS_writers) {
+ writers.emplace_back(&shard_states, db_raw);
+ }
+
+ // Each shard gets a random reader and random writer assigned to it
+ for (int i = 1; i <= FLAGS_shards; ++i) {
+ std::uniform_int_distribution<int> reader_dist(0, FLAGS_readers - 1);
+ std::uniform_int_distribution<int> writer_dist(0, FLAGS_writers - 1);
+ shard_states[i].reader = &readers[reader_dist(rng)];
+ shard_states[i].writer = &writers[writer_dist(rng)];
+ }
+
+ StatsThread stats_thread(db_raw);
+ for (Writer& w : writers) {
+ w.start();
+ }
+
+ writers.clear();
+ readers.clear();
+}
+#endif // !defined(GFLAGS) || defined(ROCKSDB_LITE)
diff --git a/storage/rocksdb/rocksdb/db/import_column_family_job.cc b/storage/rocksdb/rocksdb/db/import_column_family_job.cc
new file mode 100644
index 00000000..15af1cf8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/import_column_family_job.cc
@@ -0,0 +1,276 @@
+#ifndef ROCKSDB_LITE
+
+#include "db/import_column_family_job.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#include "db/version_edit.h"
+#include "file/file_util.h"
+#include "file/random_access_file_reader.h"
+#include "table/merging_iterator.h"
+#include "table/scoped_arena_iterator.h"
+#include "table/sst_file_writer_collectors.h"
+#include "table/table_builder.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
+ SuperVersion* sv) {
+ Status status;
+
+ // Read the information of files we are importing
+ for (const auto& file_metadata : metadata_) {
+ const auto file_path = file_metadata.db_path + "/" + file_metadata.name;
+ IngestedFileInfo file_to_import;
+ status = GetIngestedFileInfo(file_path, &file_to_import, sv);
+ if (!status.ok()) {
+ return status;
+ }
+ files_to_import_.push_back(file_to_import);
+ }
+
+ const auto ucmp = cfd_->internal_comparator().user_comparator();
+ auto num_files = files_to_import_.size();
+ if (num_files == 0) {
+ return Status::InvalidArgument("The list of files is empty");
+ } else if (num_files > 1) {
+ // Verify that passed files don't have overlapping ranges in any particular
+ // level.
+ int min_level = 1; // Check for overlaps in Level 1 and above.
+ int max_level = -1;
+ for (const auto& file_metadata : metadata_) {
+ if (file_metadata.level > max_level) {
+ max_level = file_metadata.level;
+ }
+ }
+ for (int level = min_level; level <= max_level; ++level) {
+ autovector<const IngestedFileInfo*> sorted_files;
+ for (size_t i = 0; i < num_files; i++) {
+ if (metadata_[i].level == level) {
+ sorted_files.push_back(&files_to_import_[i]);
+ }
+ }
+
+ std::sort(sorted_files.begin(), sorted_files.end(),
+ [&ucmp](const IngestedFileInfo* info1,
+ const IngestedFileInfo* info2) {
+ return sstableKeyCompare(ucmp, info1->smallest_internal_key,
+ info2->smallest_internal_key) < 0;
+ });
+
+ for (size_t i = 0; i < sorted_files.size() - 1; i++) {
+ if (sstableKeyCompare(ucmp, sorted_files[i]->largest_internal_key,
+ sorted_files[i + 1]->smallest_internal_key) >=
+ 0) {
+ return Status::InvalidArgument("Files have overlapping ranges");
+ }
+ }
+ }
+ }
+
+ for (const auto& f : files_to_import_) {
+ if (f.num_entries == 0) {
+ return Status::InvalidArgument("File contain no entries");
+ }
+
+ if (!f.smallest_internal_key.Valid() || !f.largest_internal_key.Valid()) {
+ return Status::Corruption("File has corrupted keys");
+ }
+ }
+
+ // Copy/Move external files into DB
+ auto hardlink_files = import_options_.move_files;
+ for (auto& f : files_to_import_) {
+ f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
+
+ const auto path_outside_db = f.external_file_path;
+ const auto path_inside_db = TableFileName(
+ cfd_->ioptions()->cf_paths, f.fd.GetNumber(), f.fd.GetPathId());
+
+ if (hardlink_files) {
+ status =
+ fs_->LinkFile(path_outside_db, path_inside_db, IOOptions(), nullptr);
+ if (status.IsNotSupported()) {
+ // Original file is on a different FS, use copy instead of hard linking
+ hardlink_files = false;
+ }
+ }
+ if (!hardlink_files) {
+ status = CopyFile(fs_, path_outside_db, path_inside_db, 0,
+ db_options_.use_fsync);
+ }
+ if (!status.ok()) {
+ break;
+ }
+ f.copy_file = !hardlink_files;
+ f.internal_file_path = path_inside_db;
+ }
+
+ if (!status.ok()) {
+ // We failed, remove all files that we copied into the db
+ for (const auto& f : files_to_import_) {
+ if (f.internal_file_path.empty()) {
+ break;
+ }
+ const auto s =
+ fs_->DeleteFile(f.internal_file_path, IOOptions(), nullptr);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "AddFile() clean up for file %s failed : %s",
+ f.internal_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ }
+
+ return status;
+}
+
+// REQUIRES: we have become the only writer by entering both write_thread_ and
+// nonmem_write_thread_
+Status ImportColumnFamilyJob::Run() {
+ Status status;
+ edit_.SetColumnFamily(cfd_->GetID());
+
+ // We use the import time as the ancester time. This is the time the data
+ // is written to the database.
+ int64_t temp_current_time = 0;
+ uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
+ uint64_t current_time = kUnknownOldestAncesterTime;
+ if (env_->GetCurrentTime(&temp_current_time).ok()) {
+ current_time = oldest_ancester_time =
+ static_cast<uint64_t>(temp_current_time);
+ }
+
+ for (size_t i = 0; i < files_to_import_.size(); ++i) {
+ const auto& f = files_to_import_[i];
+ const auto& file_metadata = metadata_[i];
+
+ edit_.AddFile(file_metadata.level, f.fd.GetNumber(), f.fd.GetPathId(),
+ f.fd.GetFileSize(), f.smallest_internal_key,
+ f.largest_internal_key, file_metadata.smallest_seqno,
+ file_metadata.largest_seqno, false, kInvalidBlobFileNumber,
+ oldest_ancester_time, current_time, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+
+ // If incoming sequence number is higher, update local sequence number.
+ if (file_metadata.largest_seqno > versions_->LastSequence()) {
+ versions_->SetLastAllocatedSequence(file_metadata.largest_seqno);
+ versions_->SetLastPublishedSequence(file_metadata.largest_seqno);
+ versions_->SetLastSequence(file_metadata.largest_seqno);
+ }
+ }
+
+ return status;
+}
+
+void ImportColumnFamilyJob::Cleanup(const Status& status) {
+ if (!status.ok()) {
+ // We failed to add files to the database remove all the files we copied.
+ for (const auto& f : files_to_import_) {
+ const auto s =
+ fs_->DeleteFile(f.internal_file_path, IOOptions(), nullptr);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "AddFile() clean up for file %s failed : %s",
+ f.internal_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ } else if (status.ok() && import_options_.move_files) {
+ // The files were moved and added successfully, remove original file links
+ for (IngestedFileInfo& f : files_to_import_) {
+ const auto s =
+ fs_->DeleteFile(f.external_file_path, IOOptions(), nullptr);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ db_options_.info_log,
+ "%s was added to DB successfully but failed to remove original "
+ "file link : %s",
+ f.external_file_path.c_str(), s.ToString().c_str());
+ }
+ }
+ }
+}
+
+Status ImportColumnFamilyJob::GetIngestedFileInfo(
+ const std::string& external_file, IngestedFileInfo* file_to_import,
+ SuperVersion* sv) {
+ file_to_import->external_file_path = external_file;
+
+ // Get external file size
+ Status status = fs_->GetFileSize(external_file, IOOptions(),
+ &file_to_import->file_size, nullptr);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Create TableReader for external file
+ std::unique_ptr<TableReader> table_reader;
+ std::unique_ptr<FSRandomAccessFile> sst_file;
+ std::unique_ptr<RandomAccessFileReader> sst_file_reader;
+
+ status = fs_->NewRandomAccessFile(external_file, env_options_,
+ &sst_file, nullptr);
+ if (!status.ok()) {
+ return status;
+ }
+ sst_file_reader.reset(
+ new RandomAccessFileReader(std::move(sst_file), external_file));
+
+ status = cfd_->ioptions()->table_factory->NewTableReader(
+ TableReaderOptions(*cfd_->ioptions(),
+ sv->mutable_cf_options.prefix_extractor.get(),
+ env_options_, cfd_->internal_comparator()),
+ std::move(sst_file_reader), file_to_import->file_size, &table_reader);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Get the external file properties
+ auto props = table_reader->GetTableProperties();
+
+ // Set original_seqno to 0.
+ file_to_import->original_seqno = 0;
+
+ // Get number of entries in table
+ file_to_import->num_entries = props->num_entries;
+
+ ParsedInternalKey key;
+ ReadOptions ro;
+ // During reading the external file we can cache blocks that we read into
+ // the block cache, if we later change the global seqno of this file, we will
+ // have block in cache that will include keys with wrong seqno.
+ // We need to disable fill_cache so that we read from the file without
+ // updating the block cache.
+ ro.fill_cache = false;
+ std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
+ ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
+
+ // Get first (smallest) key from file
+ iter->SeekToFirst();
+ if (!ParseInternalKey(iter->key(), &key)) {
+ return Status::Corruption("external file have corrupted keys");
+ }
+ file_to_import->smallest_internal_key.SetFrom(key);
+
+ // Get last (largest) key from file
+ iter->SeekToLast();
+ if (!ParseInternalKey(iter->key(), &key)) {
+ return Status::Corruption("external file have corrupted keys");
+ }
+ file_to_import->largest_internal_key.SetFrom(key);
+
+ file_to_import->cf_id = static_cast<uint32_t>(props->column_family_id);
+
+ file_to_import->table_properties = *props;
+
+ return status;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/import_column_family_job.h b/storage/rocksdb/rocksdb/db/import_column_family_job.h
new file mode 100644
index 00000000..160fd124
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/import_column_family_job.h
@@ -0,0 +1,72 @@
+#pragma once
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/dbformat.h"
+#include "db/external_sst_file_ingestion_job.h"
+#include "db/snapshot_impl.h"
+#include "options/db_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/metadata.h"
+#include "rocksdb/sst_file_writer.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Imports a set of sst files as is into a new column family. Logic is similar
+// to ExternalSstFileIngestionJob.
+class ImportColumnFamilyJob {
+ public:
+ ImportColumnFamilyJob(Env* env, VersionSet* versions, ColumnFamilyData* cfd,
+ const ImmutableDBOptions& db_options,
+ const EnvOptions& env_options,
+ const ImportColumnFamilyOptions& import_options,
+ const std::vector<LiveFileMetaData>& metadata)
+ : env_(env),
+ versions_(versions),
+ cfd_(cfd),
+ db_options_(db_options),
+ fs_(db_options_.fs.get()),
+ env_options_(env_options),
+ import_options_(import_options),
+ metadata_(metadata) {}
+
+ // Prepare the job by copying external files into the DB.
+ Status Prepare(uint64_t next_file_number, SuperVersion* sv);
+
+ // Will execute the import job and prepare edit() to be applied.
+ // REQUIRES: Mutex held
+ Status Run();
+
+ // Cleanup after successful/failed job
+ void Cleanup(const Status& status);
+
+ VersionEdit* edit() { return &edit_; }
+
+ const autovector<IngestedFileInfo>& files_to_import() const {
+ return files_to_import_;
+ }
+
+ private:
+ // Open the external file and populate `file_to_import` with all the
+ // external information we need to import this file.
+ Status GetIngestedFileInfo(const std::string& external_file,
+ IngestedFileInfo* file_to_import,
+ SuperVersion* sv);
+
+ Env* env_;
+ VersionSet* versions_;
+ ColumnFamilyData* cfd_;
+ const ImmutableDBOptions& db_options_;
+ FileSystem* fs_;
+ const EnvOptions& env_options_;
+ autovector<IngestedFileInfo> files_to_import_;
+ VersionEdit edit_;
+ const ImportColumnFamilyOptions& import_options_;
+ std::vector<LiveFileMetaData> metadata_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/import_column_family_test.cc b/storage/rocksdb/rocksdb/db/import_column_family_test.cc
new file mode 100644
index 00000000..a25560b7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/import_column_family_test.cc
@@ -0,0 +1,567 @@
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+#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"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ImportColumnFamilyTest : public DBTestBase {
+ public:
+ ImportColumnFamilyTest() : DBTestBase("/import_column_family_test") {
+ sst_files_dir_ = dbname_ + "/sst_files/";
+ DestroyAndRecreateExternalSSTFilesDir();
+ export_files_dir_ = test::TmpDir(env_) + "/export";
+ import_cfh_ = nullptr;
+ import_cfh2_ = nullptr;
+ metadata_ptr_ = nullptr;
+ }
+
+ ~ImportColumnFamilyTest() {
+ if (import_cfh_) {
+ db_->DropColumnFamily(import_cfh_);
+ db_->DestroyColumnFamilyHandle(import_cfh_);
+ import_cfh_ = nullptr;
+ }
+ if (import_cfh2_) {
+ db_->DropColumnFamily(import_cfh2_);
+ db_->DestroyColumnFamilyHandle(import_cfh2_);
+ import_cfh2_ = nullptr;
+ }
+ if (metadata_ptr_) {
+ delete metadata_ptr_;
+ metadata_ptr_ = nullptr;
+ }
+ test::DestroyDir(env_, sst_files_dir_);
+ test::DestroyDir(env_, export_files_dir_);
+ }
+
+ void DestroyAndRecreateExternalSSTFilesDir() {
+ test::DestroyDir(env_, sst_files_dir_);
+ env_->CreateDir(sst_files_dir_);
+ test::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;
+ db_->Get(ReadOptions(), import_cfh_, "K1", &value);
+ ASSERT_EQ(value, "V1");
+ 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;
+ db_->Get(ReadOptions(), import_cfh_, "K3", &value);
+ ASSERT_EQ(value, "V1");
+ db_->Get(ReadOptions(), import_cfh_, "K4", &value);
+ ASSERT_EQ(value, "V2");
+ }
+}
+
+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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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;
+ 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;
+ 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;
+ 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, ImportExportedSSTFromAnotherCF) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"koko"}, options);
+
+ for (int i = 0; i < 100; ++i) {
+ 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) {
+ Put(1, Key(i), Key(i) + "_overwrite");
+ }
+
+ // Flush to create L0 file.
+ ASSERT_OK(Flush(1));
+ for (int i = 0; i < 100; ++i) {
+ 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) {
+ db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
+ ASSERT_EQ(Get(1, Key(i)), value1);
+ }
+
+ for (int i = 0; i < 100; ++i) {
+ 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) {
+ db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
+ ASSERT_EQ(Key(i) + "_overwrite3", value1);
+ }
+ for (int i = 50; i < 100; ++i) {
+ db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
+ ASSERT_EQ(Key(i) + "_overwrite2", value1);
+ }
+
+ for (int i = 0; i < 100; ++i) {
+ 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) {
+ db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
+ ASSERT_EQ(Key(i) + "_overwrite3", value1);
+ }
+ for (int i = 50; i < 100; ++i) {
+ db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
+ ASSERT_EQ(Key(i) + "_overwrite2", value1);
+ }
+
+ for (int i = 0; i < 100; ++i) {
+ 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) {
+ 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) {
+ Put(1, Key(i), Key(i) + "_overwrite");
+ }
+
+ // Flush to create L0 file.
+ ASSERT_OK(Flush(1));
+
+ for (int i = 0; i < 25; ++i) {
+ 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;
+ test::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;
+ db_copy->Get(ReadOptions(), cfh, Key(i), &value);
+ ASSERT_EQ(Get(1, Key(i)), value);
+ }
+ db_copy->DropColumnFamily(cfh);
+ db_copy->DestroyColumnFamilyHandle(cfh);
+ delete db_copy;
+ test::DestroyDir(env_, dbname_ + "/db_copy");
+}
+
+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_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
+ ImportColumnFamilyOptions(),
+ metadata, &import_cfh_),
+ Status::InvalidArgument("Files have overlapping ranges"));
+ 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();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as External SST File Writer and Import are not supported "
+ "in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/internal_stats.cc b/storage/rocksdb/rocksdb/db/internal_stats.cc
new file mode 100644
index 00000000..f729ee7c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/internal_stats.cc
@@ -0,0 +1,1424 @@
+// This source code is licensed under both the GPLv2 (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) Facebook, Inc. and its affiliates. All Rights Reserved.
+//
+// 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/internal_stats.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+const std::map<LevelStatType, LevelStat> InternalStats::compaction_level_stats =
+ {
+ {LevelStatType::NUM_FILES, LevelStat{"NumFiles", "Files"}},
+ {LevelStatType::COMPACTED_FILES,
+ LevelStat{"CompactedFiles", "CompactedFiles"}},
+ {LevelStatType::SIZE_BYTES, LevelStat{"SizeBytes", "Size"}},
+ {LevelStatType::SCORE, LevelStat{"Score", "Score"}},
+ {LevelStatType::READ_GB, LevelStat{"ReadGB", "Read(GB)"}},
+ {LevelStatType::RN_GB, LevelStat{"RnGB", "Rn(GB)"}},
+ {LevelStatType::RNP1_GB, LevelStat{"Rnp1GB", "Rnp1(GB)"}},
+ {LevelStatType::WRITE_GB, LevelStat{"WriteGB", "Write(GB)"}},
+ {LevelStatType::W_NEW_GB, LevelStat{"WnewGB", "Wnew(GB)"}},
+ {LevelStatType::MOVED_GB, LevelStat{"MovedGB", "Moved(GB)"}},
+ {LevelStatType::WRITE_AMP, LevelStat{"WriteAmp", "W-Amp"}},
+ {LevelStatType::READ_MBPS, LevelStat{"ReadMBps", "Rd(MB/s)"}},
+ {LevelStatType::WRITE_MBPS, LevelStat{"WriteMBps", "Wr(MB/s)"}},
+ {LevelStatType::COMP_SEC, LevelStat{"CompSec", "Comp(sec)"}},
+ {LevelStatType::COMP_CPU_SEC,
+ LevelStat{"CompMergeCPU", "CompMergeCPU(sec)"}},
+ {LevelStatType::COMP_COUNT, LevelStat{"CompCount", "Comp(cnt)"}},
+ {LevelStatType::AVG_SEC, LevelStat{"AvgSec", "Avg(sec)"}},
+ {LevelStatType::KEY_IN, LevelStat{"KeyIn", "KeyIn"}},
+ {LevelStatType::KEY_DROP, LevelStat{"KeyDrop", "KeyDrop"}},
+};
+
+namespace {
+const double kMB = 1048576.0;
+const double kGB = kMB * 1024;
+const double kMicrosInSec = 1000000.0;
+
+void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name,
+ const std::string& group_by) {
+ int written_size =
+ snprintf(buf, len, "\n** Compaction Stats [%s] **\n", cf_name.c_str());
+ auto hdr = [](LevelStatType t) {
+ return InternalStats::compaction_level_stats.at(t).header_name.c_str();
+ };
+ int line_size = snprintf(
+ buf + written_size, len - written_size,
+ "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
+ // Note that we skip COMPACTED_FILES and merge it with Files column
+ group_by.c_str(), hdr(LevelStatType::NUM_FILES),
+ hdr(LevelStatType::SIZE_BYTES), hdr(LevelStatType::SCORE),
+ hdr(LevelStatType::READ_GB), hdr(LevelStatType::RN_GB),
+ hdr(LevelStatType::RNP1_GB), hdr(LevelStatType::WRITE_GB),
+ hdr(LevelStatType::W_NEW_GB), hdr(LevelStatType::MOVED_GB),
+ hdr(LevelStatType::WRITE_AMP), hdr(LevelStatType::READ_MBPS),
+ hdr(LevelStatType::WRITE_MBPS), hdr(LevelStatType::COMP_SEC),
+ hdr(LevelStatType::COMP_CPU_SEC), hdr(LevelStatType::COMP_COUNT),
+ hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
+ hdr(LevelStatType::KEY_DROP));
+
+ written_size += line_size;
+ snprintf(buf + written_size, len - written_size, "%s\n",
+ std::string(line_size, '-').c_str());
+}
+
+void PrepareLevelStats(std::map<LevelStatType, double>* level_stats,
+ int num_files, int being_compacted,
+ double total_file_size, double score, double w_amp,
+ const InternalStats::CompactionStats& stats) {
+ uint64_t bytes_read =
+ stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
+ int64_t bytes_new = stats.bytes_written - stats.bytes_read_output_level;
+ double elapsed = (stats.micros + 1) / kMicrosInSec;
+
+ (*level_stats)[LevelStatType::NUM_FILES] = num_files;
+ (*level_stats)[LevelStatType::COMPACTED_FILES] = being_compacted;
+ (*level_stats)[LevelStatType::SIZE_BYTES] = total_file_size;
+ (*level_stats)[LevelStatType::SCORE] = score;
+ (*level_stats)[LevelStatType::READ_GB] = bytes_read / kGB;
+ (*level_stats)[LevelStatType::RN_GB] =
+ stats.bytes_read_non_output_levels / kGB;
+ (*level_stats)[LevelStatType::RNP1_GB] = stats.bytes_read_output_level / kGB;
+ (*level_stats)[LevelStatType::WRITE_GB] = stats.bytes_written / kGB;
+ (*level_stats)[LevelStatType::W_NEW_GB] = bytes_new / kGB;
+ (*level_stats)[LevelStatType::MOVED_GB] = stats.bytes_moved / kGB;
+ (*level_stats)[LevelStatType::WRITE_AMP] = w_amp;
+ (*level_stats)[LevelStatType::READ_MBPS] = bytes_read / kMB / elapsed;
+ (*level_stats)[LevelStatType::WRITE_MBPS] =
+ stats.bytes_written / kMB / elapsed;
+ (*level_stats)[LevelStatType::COMP_SEC] = stats.micros / kMicrosInSec;
+ (*level_stats)[LevelStatType::COMP_CPU_SEC] = stats.cpu_micros / kMicrosInSec;
+ (*level_stats)[LevelStatType::COMP_COUNT] = stats.count;
+ (*level_stats)[LevelStatType::AVG_SEC] =
+ stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count;
+ (*level_stats)[LevelStatType::KEY_IN] =
+ static_cast<double>(stats.num_input_records);
+ (*level_stats)[LevelStatType::KEY_DROP] =
+ static_cast<double>(stats.num_dropped_records);
+}
+
+void PrintLevelStats(char* buf, size_t len, const std::string& name,
+ const std::map<LevelStatType, double>& stat_value) {
+ snprintf(
+ buf, len,
+ "%4s " /* Level */
+ "%6d/%-3d " /* Files */
+ "%8s " /* Size */
+ "%5.1f " /* Score */
+ "%8.1f " /* Read(GB) */
+ "%7.1f " /* Rn(GB) */
+ "%8.1f " /* Rnp1(GB) */
+ "%9.1f " /* Write(GB) */
+ "%8.1f " /* Wnew(GB) */
+ "%9.1f " /* Moved(GB) */
+ "%5.1f " /* W-Amp */
+ "%8.1f " /* Rd(MB/s) */
+ "%8.1f " /* Wr(MB/s) */
+ "%9.2f " /* Comp(sec) */
+ "%17.2f " /* CompMergeCPU(sec) */
+ "%9d " /* Comp(cnt) */
+ "%8.3f " /* Avg(sec) */
+ "%7s " /* KeyIn */
+ "%6s\n", /* KeyDrop */
+ name.c_str(), static_cast<int>(stat_value.at(LevelStatType::NUM_FILES)),
+ static_cast<int>(stat_value.at(LevelStatType::COMPACTED_FILES)),
+ BytesToHumanString(
+ static_cast<uint64_t>(stat_value.at(LevelStatType::SIZE_BYTES)))
+ .c_str(),
+ stat_value.at(LevelStatType::SCORE),
+ stat_value.at(LevelStatType::READ_GB),
+ stat_value.at(LevelStatType::RN_GB),
+ stat_value.at(LevelStatType::RNP1_GB),
+ stat_value.at(LevelStatType::WRITE_GB),
+ stat_value.at(LevelStatType::W_NEW_GB),
+ stat_value.at(LevelStatType::MOVED_GB),
+ stat_value.at(LevelStatType::WRITE_AMP),
+ stat_value.at(LevelStatType::READ_MBPS),
+ stat_value.at(LevelStatType::WRITE_MBPS),
+ stat_value.at(LevelStatType::COMP_SEC),
+ stat_value.at(LevelStatType::COMP_CPU_SEC),
+ static_cast<int>(stat_value.at(LevelStatType::COMP_COUNT)),
+ stat_value.at(LevelStatType::AVG_SEC),
+ NumberToHumanString(
+ static_cast<std::int64_t>(stat_value.at(LevelStatType::KEY_IN)))
+ .c_str(),
+ NumberToHumanString(
+ static_cast<std::int64_t>(stat_value.at(LevelStatType::KEY_DROP)))
+ .c_str());
+}
+
+void PrintLevelStats(char* buf, size_t len, const std::string& name,
+ int num_files, int being_compacted, double total_file_size,
+ double score, double w_amp,
+ const InternalStats::CompactionStats& stats) {
+ std::map<LevelStatType, double> level_stats;
+ PrepareLevelStats(&level_stats, num_files, being_compacted, total_file_size,
+ score, w_amp, stats);
+ PrintLevelStats(buf, len, name, level_stats);
+}
+
+// Assumes that trailing numbers represent an optional argument. This requires
+// property names to not end with numbers.
+std::pair<Slice, Slice> GetPropertyNameAndArg(const Slice& property) {
+ Slice name = property, arg = property;
+ size_t sfx_len = 0;
+ while (sfx_len < property.size() &&
+ isdigit(property[property.size() - sfx_len - 1])) {
+ ++sfx_len;
+ }
+ name.remove_suffix(sfx_len);
+ arg.remove_prefix(property.size() - sfx_len);
+ return {name, arg};
+}
+} // anonymous namespace
+
+static const std::string rocksdb_prefix = "rocksdb.";
+
+static const std::string num_files_at_level_prefix = "num-files-at-level";
+static const std::string compression_ratio_at_level_prefix =
+ "compression-ratio-at-level";
+static const std::string allstats = "stats";
+static const std::string sstables = "sstables";
+static const std::string cfstats = "cfstats";
+static const std::string cfstats_no_file_histogram =
+ "cfstats-no-file-histogram";
+static const std::string cf_file_histogram = "cf-file-histogram";
+static const std::string dbstats = "dbstats";
+static const std::string levelstats = "levelstats";
+static const std::string num_immutable_mem_table = "num-immutable-mem-table";
+static const std::string num_immutable_mem_table_flushed =
+ "num-immutable-mem-table-flushed";
+static const std::string mem_table_flush_pending = "mem-table-flush-pending";
+static const std::string compaction_pending = "compaction-pending";
+static const std::string background_errors = "background-errors";
+static const std::string cur_size_active_mem_table =
+ "cur-size-active-mem-table";
+static const std::string cur_size_all_mem_tables = "cur-size-all-mem-tables";
+static const std::string size_all_mem_tables = "size-all-mem-tables";
+static const std::string num_entries_active_mem_table =
+ "num-entries-active-mem-table";
+static const std::string num_entries_imm_mem_tables =
+ "num-entries-imm-mem-tables";
+static const std::string num_deletes_active_mem_table =
+ "num-deletes-active-mem-table";
+static const std::string num_deletes_imm_mem_tables =
+ "num-deletes-imm-mem-tables";
+static const std::string estimate_num_keys = "estimate-num-keys";
+static const std::string estimate_table_readers_mem =
+ "estimate-table-readers-mem";
+static const std::string is_file_deletions_enabled =
+ "is-file-deletions-enabled";
+static const std::string num_snapshots = "num-snapshots";
+static const std::string oldest_snapshot_time = "oldest-snapshot-time";
+static const std::string oldest_snapshot_sequence = "oldest-snapshot-sequence";
+static const std::string num_live_versions = "num-live-versions";
+static const std::string current_version_number =
+ "current-super-version-number";
+static const std::string estimate_live_data_size = "estimate-live-data-size";
+static const std::string min_log_number_to_keep_str = "min-log-number-to-keep";
+static const std::string min_obsolete_sst_number_to_keep_str =
+ "min-obsolete-sst-number-to-keep";
+static const std::string base_level_str = "base-level";
+static const std::string total_sst_files_size = "total-sst-files-size";
+static const std::string live_sst_files_size = "live-sst-files-size";
+static const std::string estimate_pending_comp_bytes =
+ "estimate-pending-compaction-bytes";
+static const std::string aggregated_table_properties =
+ "aggregated-table-properties";
+static const std::string aggregated_table_properties_at_level =
+ aggregated_table_properties + "-at-level";
+static const std::string num_running_compactions = "num-running-compactions";
+static const std::string num_running_flushes = "num-running-flushes";
+static const std::string actual_delayed_write_rate =
+ "actual-delayed-write-rate";
+static const std::string is_write_stopped = "is-write-stopped";
+static const std::string estimate_oldest_key_time = "estimate-oldest-key-time";
+static const std::string block_cache_capacity = "block-cache-capacity";
+static const std::string block_cache_usage = "block-cache-usage";
+static const std::string block_cache_pinned_usage = "block-cache-pinned-usage";
+static const std::string options_statistics = "options-statistics";
+
+const std::string DB::Properties::kNumFilesAtLevelPrefix =
+ rocksdb_prefix + num_files_at_level_prefix;
+const std::string DB::Properties::kCompressionRatioAtLevelPrefix =
+ rocksdb_prefix + compression_ratio_at_level_prefix;
+const std::string DB::Properties::kStats = rocksdb_prefix + allstats;
+const std::string DB::Properties::kSSTables = rocksdb_prefix + sstables;
+const std::string DB::Properties::kCFStats = rocksdb_prefix + cfstats;
+const std::string DB::Properties::kCFStatsNoFileHistogram =
+ rocksdb_prefix + cfstats_no_file_histogram;
+const std::string DB::Properties::kCFFileHistogram =
+ rocksdb_prefix + cf_file_histogram;
+const std::string DB::Properties::kDBStats = rocksdb_prefix + dbstats;
+const std::string DB::Properties::kLevelStats = rocksdb_prefix + levelstats;
+const std::string DB::Properties::kNumImmutableMemTable =
+ rocksdb_prefix + num_immutable_mem_table;
+const std::string DB::Properties::kNumImmutableMemTableFlushed =
+ rocksdb_prefix + num_immutable_mem_table_flushed;
+const std::string DB::Properties::kMemTableFlushPending =
+ rocksdb_prefix + mem_table_flush_pending;
+const std::string DB::Properties::kCompactionPending =
+ rocksdb_prefix + compaction_pending;
+const std::string DB::Properties::kNumRunningCompactions =
+ rocksdb_prefix + num_running_compactions;
+const std::string DB::Properties::kNumRunningFlushes =
+ rocksdb_prefix + num_running_flushes;
+const std::string DB::Properties::kBackgroundErrors =
+ rocksdb_prefix + background_errors;
+const std::string DB::Properties::kCurSizeActiveMemTable =
+ rocksdb_prefix + cur_size_active_mem_table;
+const std::string DB::Properties::kCurSizeAllMemTables =
+ rocksdb_prefix + cur_size_all_mem_tables;
+const std::string DB::Properties::kSizeAllMemTables =
+ rocksdb_prefix + size_all_mem_tables;
+const std::string DB::Properties::kNumEntriesActiveMemTable =
+ rocksdb_prefix + num_entries_active_mem_table;
+const std::string DB::Properties::kNumEntriesImmMemTables =
+ rocksdb_prefix + num_entries_imm_mem_tables;
+const std::string DB::Properties::kNumDeletesActiveMemTable =
+ rocksdb_prefix + num_deletes_active_mem_table;
+const std::string DB::Properties::kNumDeletesImmMemTables =
+ rocksdb_prefix + num_deletes_imm_mem_tables;
+const std::string DB::Properties::kEstimateNumKeys =
+ rocksdb_prefix + estimate_num_keys;
+const std::string DB::Properties::kEstimateTableReadersMem =
+ rocksdb_prefix + estimate_table_readers_mem;
+const std::string DB::Properties::kIsFileDeletionsEnabled =
+ rocksdb_prefix + is_file_deletions_enabled;
+const std::string DB::Properties::kNumSnapshots =
+ rocksdb_prefix + num_snapshots;
+const std::string DB::Properties::kOldestSnapshotTime =
+ rocksdb_prefix + oldest_snapshot_time;
+const std::string DB::Properties::kOldestSnapshotSequence =
+ rocksdb_prefix + oldest_snapshot_sequence;
+const std::string DB::Properties::kNumLiveVersions =
+ rocksdb_prefix + num_live_versions;
+const std::string DB::Properties::kCurrentSuperVersionNumber =
+ rocksdb_prefix + current_version_number;
+const std::string DB::Properties::kEstimateLiveDataSize =
+ rocksdb_prefix + estimate_live_data_size;
+const std::string DB::Properties::kMinLogNumberToKeep =
+ rocksdb_prefix + min_log_number_to_keep_str;
+const std::string DB::Properties::kMinObsoleteSstNumberToKeep =
+ rocksdb_prefix + min_obsolete_sst_number_to_keep_str;
+const std::string DB::Properties::kTotalSstFilesSize =
+ rocksdb_prefix + total_sst_files_size;
+const std::string DB::Properties::kLiveSstFilesSize =
+ rocksdb_prefix + live_sst_files_size;
+const std::string DB::Properties::kBaseLevel = rocksdb_prefix + base_level_str;
+const std::string DB::Properties::kEstimatePendingCompactionBytes =
+ rocksdb_prefix + estimate_pending_comp_bytes;
+const std::string DB::Properties::kAggregatedTableProperties =
+ rocksdb_prefix + aggregated_table_properties;
+const std::string DB::Properties::kAggregatedTablePropertiesAtLevel =
+ rocksdb_prefix + aggregated_table_properties_at_level;
+const std::string DB::Properties::kActualDelayedWriteRate =
+ rocksdb_prefix + actual_delayed_write_rate;
+const std::string DB::Properties::kIsWriteStopped =
+ rocksdb_prefix + is_write_stopped;
+const std::string DB::Properties::kEstimateOldestKeyTime =
+ rocksdb_prefix + estimate_oldest_key_time;
+const std::string DB::Properties::kBlockCacheCapacity =
+ rocksdb_prefix + block_cache_capacity;
+const std::string DB::Properties::kBlockCacheUsage =
+ rocksdb_prefix + block_cache_usage;
+const std::string DB::Properties::kBlockCachePinnedUsage =
+ rocksdb_prefix + block_cache_pinned_usage;
+const std::string DB::Properties::kOptionsStatistics =
+ rocksdb_prefix + options_statistics;
+
+const std::unordered_map<std::string, DBPropertyInfo>
+ InternalStats::ppt_name_to_info = {
+ {DB::Properties::kNumFilesAtLevelPrefix,
+ {false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr,
+ nullptr}},
+ {DB::Properties::kCompressionRatioAtLevelPrefix,
+ {false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr,
+ nullptr, nullptr}},
+ {DB::Properties::kLevelStats,
+ {false, &InternalStats::HandleLevelStats, nullptr, nullptr, nullptr}},
+ {DB::Properties::kStats,
+ {false, &InternalStats::HandleStats, nullptr, nullptr, nullptr}},
+ {DB::Properties::kCFStats,
+ {false, &InternalStats::HandleCFStats, nullptr,
+ &InternalStats::HandleCFMapStats, nullptr}},
+ {DB::Properties::kCFStatsNoFileHistogram,
+ {false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr, nullptr,
+ nullptr}},
+ {DB::Properties::kCFFileHistogram,
+ {false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr,
+ nullptr}},
+ {DB::Properties::kDBStats,
+ {false, &InternalStats::HandleDBStats, nullptr, nullptr, nullptr}},
+ {DB::Properties::kSSTables,
+ {false, &InternalStats::HandleSsTables, nullptr, nullptr, nullptr}},
+ {DB::Properties::kAggregatedTableProperties,
+ {false, &InternalStats::HandleAggregatedTableProperties, nullptr,
+ nullptr, nullptr}},
+ {DB::Properties::kAggregatedTablePropertiesAtLevel,
+ {false, &InternalStats::HandleAggregatedTablePropertiesAtLevel,
+ nullptr, nullptr, nullptr}},
+ {DB::Properties::kNumImmutableMemTable,
+ {false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr,
+ nullptr}},
+ {DB::Properties::kNumImmutableMemTableFlushed,
+ {false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed,
+ nullptr, nullptr}},
+ {DB::Properties::kMemTableFlushPending,
+ {false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr,
+ nullptr}},
+ {DB::Properties::kCompactionPending,
+ {false, nullptr, &InternalStats::HandleCompactionPending, nullptr,
+ nullptr}},
+ {DB::Properties::kBackgroundErrors,
+ {false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr,
+ nullptr}},
+ {DB::Properties::kCurSizeActiveMemTable,
+ {false, nullptr, &InternalStats::HandleCurSizeActiveMemTable, nullptr,
+ nullptr}},
+ {DB::Properties::kCurSizeAllMemTables,
+ {false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr,
+ nullptr}},
+ {DB::Properties::kSizeAllMemTables,
+ {false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr,
+ nullptr}},
+ {DB::Properties::kNumEntriesActiveMemTable,
+ {false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable,
+ nullptr, nullptr}},
+ {DB::Properties::kNumEntriesImmMemTables,
+ {false, nullptr, &InternalStats::HandleNumEntriesImmMemTables, nullptr,
+ nullptr}},
+ {DB::Properties::kNumDeletesActiveMemTable,
+ {false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable,
+ nullptr, nullptr}},
+ {DB::Properties::kNumDeletesImmMemTables,
+ {false, nullptr, &InternalStats::HandleNumDeletesImmMemTables, nullptr,
+ nullptr}},
+ {DB::Properties::kEstimateNumKeys,
+ {false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr,
+ nullptr}},
+ {DB::Properties::kEstimateTableReadersMem,
+ {true, nullptr, &InternalStats::HandleEstimateTableReadersMem, nullptr,
+ nullptr}},
+ {DB::Properties::kIsFileDeletionsEnabled,
+ {false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled, nullptr,
+ nullptr}},
+ {DB::Properties::kNumSnapshots,
+ {false, nullptr, &InternalStats::HandleNumSnapshots, nullptr,
+ nullptr}},
+ {DB::Properties::kOldestSnapshotTime,
+ {false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr,
+ nullptr}},
+ {DB::Properties::kOldestSnapshotSequence,
+ {false, nullptr, &InternalStats::HandleOldestSnapshotSequence, nullptr,
+ nullptr}},
+ {DB::Properties::kNumLiveVersions,
+ {false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr,
+ nullptr}},
+ {DB::Properties::kCurrentSuperVersionNumber,
+ {false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber,
+ nullptr, nullptr}},
+ {DB::Properties::kEstimateLiveDataSize,
+ {true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr,
+ nullptr}},
+ {DB::Properties::kMinLogNumberToKeep,
+ {false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr,
+ nullptr}},
+ {DB::Properties::kMinObsoleteSstNumberToKeep,
+ {false, nullptr, &InternalStats::HandleMinObsoleteSstNumberToKeep,
+ nullptr, nullptr}},
+ {DB::Properties::kBaseLevel,
+ {false, nullptr, &InternalStats::HandleBaseLevel, nullptr, nullptr}},
+ {DB::Properties::kTotalSstFilesSize,
+ {false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr,
+ nullptr}},
+ {DB::Properties::kLiveSstFilesSize,
+ {false, nullptr, &InternalStats::HandleLiveSstFilesSize, nullptr,
+ nullptr}},
+ {DB::Properties::kEstimatePendingCompactionBytes,
+ {false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes,
+ nullptr, nullptr}},
+ {DB::Properties::kNumRunningFlushes,
+ {false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr,
+ nullptr}},
+ {DB::Properties::kNumRunningCompactions,
+ {false, nullptr, &InternalStats::HandleNumRunningCompactions, nullptr,
+ nullptr}},
+ {DB::Properties::kActualDelayedWriteRate,
+ {false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr,
+ nullptr}},
+ {DB::Properties::kIsWriteStopped,
+ {false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr,
+ nullptr}},
+ {DB::Properties::kEstimateOldestKeyTime,
+ {false, nullptr, &InternalStats::HandleEstimateOldestKeyTime, nullptr,
+ nullptr}},
+ {DB::Properties::kBlockCacheCapacity,
+ {false, nullptr, &InternalStats::HandleBlockCacheCapacity, nullptr,
+ nullptr}},
+ {DB::Properties::kBlockCacheUsage,
+ {false, nullptr, &InternalStats::HandleBlockCacheUsage, nullptr,
+ nullptr}},
+ {DB::Properties::kBlockCachePinnedUsage,
+ {false, nullptr, &InternalStats::HandleBlockCachePinnedUsage, nullptr,
+ nullptr}},
+ {DB::Properties::kOptionsStatistics,
+ {false, nullptr, nullptr, nullptr,
+ &DBImpl::GetPropertyHandleOptionsStatistics}},
+};
+
+const DBPropertyInfo* GetPropertyInfo(const Slice& property) {
+ std::string ppt_name = GetPropertyNameAndArg(property).first.ToString();
+ auto ppt_info_iter = InternalStats::ppt_name_to_info.find(ppt_name);
+ if (ppt_info_iter == InternalStats::ppt_name_to_info.end()) {
+ return nullptr;
+ }
+ return &ppt_info_iter->second;
+}
+
+bool InternalStats::GetStringProperty(const DBPropertyInfo& property_info,
+ const Slice& property,
+ std::string* value) {
+ assert(value != nullptr);
+ assert(property_info.handle_string != nullptr);
+ Slice arg = GetPropertyNameAndArg(property).second;
+ return (this->*(property_info.handle_string))(value, arg);
+}
+
+bool InternalStats::GetMapProperty(const DBPropertyInfo& property_info,
+ const Slice& /*property*/,
+ std::map<std::string, std::string>* value) {
+ assert(value != nullptr);
+ assert(property_info.handle_map != nullptr);
+ return (this->*(property_info.handle_map))(value);
+}
+
+bool InternalStats::GetIntProperty(const DBPropertyInfo& property_info,
+ uint64_t* value, DBImpl* db) {
+ assert(value != nullptr);
+ assert(property_info.handle_int != nullptr &&
+ !property_info.need_out_of_mutex);
+ db->mutex_.AssertHeld();
+ return (this->*(property_info.handle_int))(value, db, nullptr /* version */);
+}
+
+bool InternalStats::GetIntPropertyOutOfMutex(
+ const DBPropertyInfo& property_info, Version* version, uint64_t* value) {
+ assert(value != nullptr);
+ assert(property_info.handle_int != nullptr &&
+ property_info.need_out_of_mutex);
+ return (this->*(property_info.handle_int))(value, nullptr /* db */, version);
+}
+
+bool InternalStats::HandleNumFilesAtLevel(std::string* value, Slice suffix) {
+ uint64_t level;
+ const auto* vstorage = cfd_->current()->storage_info();
+ bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
+ if (!ok || static_cast<int>(level) >= number_levels_) {
+ return false;
+ } else {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%d",
+ vstorage->NumLevelFiles(static_cast<int>(level)));
+ *value = buf;
+ return true;
+ }
+}
+
+bool InternalStats::HandleCompressionRatioAtLevelPrefix(std::string* value,
+ Slice suffix) {
+ uint64_t level;
+ const auto* vstorage = cfd_->current()->storage_info();
+ bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
+ if (!ok || level >= static_cast<uint64_t>(number_levels_)) {
+ return false;
+ }
+ *value = ToString(
+ vstorage->GetEstimatedCompressionRatioAtLevel(static_cast<int>(level)));
+ return true;
+}
+
+bool InternalStats::HandleLevelStats(std::string* value, Slice /*suffix*/) {
+ char buf[1000];
+ const auto* vstorage = cfd_->current()->storage_info();
+ snprintf(buf, sizeof(buf),
+ "Level Files Size(MB)\n"
+ "--------------------\n");
+ value->append(buf);
+
+ for (int level = 0; level < number_levels_; level++) {
+ snprintf(buf, sizeof(buf), "%3d %8d %8.0f\n", level,
+ vstorage->NumLevelFiles(level),
+ vstorage->NumLevelBytes(level) / kMB);
+ value->append(buf);
+ }
+ return true;
+}
+
+bool InternalStats::HandleStats(std::string* value, Slice suffix) {
+ if (!HandleCFStats(value, suffix)) {
+ return false;
+ }
+ if (!HandleDBStats(value, suffix)) {
+ return false;
+ }
+ return true;
+}
+
+bool InternalStats::HandleCFMapStats(
+ std::map<std::string, std::string>* cf_stats) {
+ DumpCFMapStats(cf_stats);
+ return true;
+}
+
+bool InternalStats::HandleCFStats(std::string* value, Slice /*suffix*/) {
+ DumpCFStats(value);
+ return true;
+}
+
+bool InternalStats::HandleCFStatsNoFileHistogram(std::string* value,
+ Slice /*suffix*/) {
+ DumpCFStatsNoFileHistogram(value);
+ return true;
+}
+
+bool InternalStats::HandleCFFileHistogram(std::string* value,
+ Slice /*suffix*/) {
+ DumpCFFileHistogram(value);
+ return true;
+}
+
+bool InternalStats::HandleDBStats(std::string* value, Slice /*suffix*/) {
+ DumpDBStats(value);
+ return true;
+}
+
+bool InternalStats::HandleSsTables(std::string* value, Slice /*suffix*/) {
+ auto* current = cfd_->current();
+ *value = current->DebugString(true, true);
+ return true;
+}
+
+bool InternalStats::HandleAggregatedTableProperties(std::string* value,
+ Slice /*suffix*/) {
+ std::shared_ptr<const TableProperties> tp;
+ auto s = cfd_->current()->GetAggregatedTableProperties(&tp);
+ if (!s.ok()) {
+ return false;
+ }
+ *value = tp->ToString();
+ return true;
+}
+
+bool InternalStats::HandleAggregatedTablePropertiesAtLevel(std::string* value,
+ Slice suffix) {
+ uint64_t level;
+ bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
+ if (!ok || static_cast<int>(level) >= number_levels_) {
+ return false;
+ }
+ std::shared_ptr<const TableProperties> tp;
+ auto s = cfd_->current()->GetAggregatedTableProperties(
+ &tp, static_cast<int>(level));
+ if (!s.ok()) {
+ return false;
+ }
+ *value = tp->ToString();
+ return true;
+}
+
+bool InternalStats::HandleNumImmutableMemTable(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->imm()->NumNotFlushed();
+ return true;
+}
+
+bool InternalStats::HandleNumImmutableMemTableFlushed(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->imm()->NumFlushed();
+ return true;
+}
+
+bool InternalStats::HandleMemTableFlushPending(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = (cfd_->imm()->IsFlushPending() ? 1 : 0);
+ return true;
+}
+
+bool InternalStats::HandleNumRunningFlushes(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->num_running_flushes();
+ return true;
+}
+
+bool InternalStats::HandleCompactionPending(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // 1 if the system already determines at least one compaction is needed.
+ // 0 otherwise,
+ const auto* vstorage = cfd_->current()->storage_info();
+ *value = (cfd_->compaction_picker()->NeedsCompaction(vstorage) ? 1 : 0);
+ return true;
+}
+
+bool InternalStats::HandleNumRunningCompactions(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->num_running_compactions_;
+ return true;
+}
+
+bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Accumulated number of errors in background flushes or compactions.
+ *value = GetBackgroundErrorCount();
+ return true;
+}
+
+bool InternalStats::HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current size of the active memtable
+ *value = cfd_->mem()->ApproximateMemoryUsage();
+ return true;
+}
+
+bool InternalStats::HandleCurSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current size of the active memtable + immutable memtables
+ *value = cfd_->mem()->ApproximateMemoryUsage() +
+ cfd_->imm()->ApproximateUnflushedMemTablesMemoryUsage();
+ return true;
+}
+
+bool InternalStats::HandleSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->mem()->ApproximateMemoryUsage() +
+ cfd_->imm()->ApproximateMemoryUsage();
+ return true;
+}
+
+bool InternalStats::HandleNumEntriesActiveMemTable(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current number of entires in the active memtable
+ *value = cfd_->mem()->num_entries();
+ return true;
+}
+
+bool InternalStats::HandleNumEntriesImmMemTables(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current number of entries in the immutable memtables
+ *value = cfd_->imm()->current()->GetTotalNumEntries();
+ return true;
+}
+
+bool InternalStats::HandleNumDeletesActiveMemTable(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current number of entires in the active memtable
+ *value = cfd_->mem()->num_deletes();
+ return true;
+}
+
+bool InternalStats::HandleNumDeletesImmMemTables(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Current number of entries in the immutable memtables
+ *value = cfd_->imm()->current()->GetTotalNumDeletes();
+ return true;
+}
+
+bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // Estimate number of entries in the column family:
+ // Use estimated entries in tables + total entries in memtables.
+ const auto* vstorage = cfd_->current()->storage_info();
+ uint64_t estimate_keys = cfd_->mem()->num_entries() +
+ cfd_->imm()->current()->GetTotalNumEntries() +
+ vstorage->GetEstimatedActiveKeys();
+ uint64_t estimate_deletes =
+ cfd_->mem()->num_deletes() + cfd_->imm()->current()->GetTotalNumDeletes();
+ *value = estimate_keys > estimate_deletes * 2
+ ? estimate_keys - (estimate_deletes * 2)
+ : 0;
+ return true;
+}
+
+bool InternalStats::HandleNumSnapshots(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->snapshots().count();
+ return true;
+}
+
+bool InternalStats::HandleOldestSnapshotTime(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = static_cast<uint64_t>(db->snapshots().GetOldestSnapshotTime());
+ return true;
+}
+
+bool InternalStats::HandleOldestSnapshotSequence(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = static_cast<uint64_t>(db->snapshots().GetOldestSnapshotSequence());
+ return true;
+}
+
+bool InternalStats::HandleNumLiveVersions(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->GetNumLiveVersions();
+ return true;
+}
+
+bool InternalStats::HandleCurrentSuperVersionNumber(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->GetSuperVersionNumber();
+ return true;
+}
+
+bool InternalStats::HandleIsFileDeletionsEnabled(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->IsFileDeletionsEnabled();
+ return true;
+}
+
+bool InternalStats::HandleBaseLevel(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ const auto* vstorage = cfd_->current()->storage_info();
+ *value = vstorage->base_level();
+ return true;
+}
+
+bool InternalStats::HandleTotalSstFilesSize(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->GetTotalSstFilesSize();
+ return true;
+}
+
+bool InternalStats::HandleLiveSstFilesSize(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ *value = cfd_->GetLiveSstFilesSize();
+ return true;
+}
+
+bool InternalStats::HandleEstimatePendingCompactionBytes(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* /*version*/) {
+ const auto* vstorage = cfd_->current()->storage_info();
+ *value = vstorage->estimated_compaction_needed_bytes();
+ return true;
+}
+
+bool InternalStats::HandleEstimateTableReadersMem(uint64_t* value,
+ DBImpl* /*db*/,
+ Version* version) {
+ *value = (version == nullptr) ? 0 : version->GetMemoryUsageByTableReaders();
+ return true;
+}
+
+bool InternalStats::HandleEstimateLiveDataSize(uint64_t* value, DBImpl* /*db*/,
+ Version* version) {
+ const auto* vstorage = version->storage_info();
+ *value = vstorage->EstimateLiveDataSize();
+ return true;
+}
+
+bool InternalStats::HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->MinLogNumberToKeep();
+ return true;
+}
+
+bool InternalStats::HandleMinObsoleteSstNumberToKeep(uint64_t* value,
+ DBImpl* db,
+ Version* /*version*/) {
+ *value = db->MinObsoleteSstNumberToKeep();
+ return true;
+}
+
+bool InternalStats::HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ const WriteController& wc = db->write_controller();
+ if (!wc.NeedsDelay()) {
+ *value = 0;
+ } else {
+ *value = wc.delayed_write_rate();
+ }
+ return true;
+}
+
+bool InternalStats::HandleIsWriteStopped(uint64_t* value, DBImpl* db,
+ Version* /*version*/) {
+ *value = db->write_controller().IsStopped() ? 1 : 0;
+ return true;
+}
+
+bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ // TODO(yiwu): The property is currently available for fifo compaction
+ // with allow_compaction = false. This is because we don't propagate
+ // oldest_key_time on compaction.
+ if (cfd_->ioptions()->compaction_style != kCompactionStyleFIFO ||
+ cfd_->GetCurrentMutableCFOptions()
+ ->compaction_options_fifo.allow_compaction) {
+ return false;
+ }
+
+ TablePropertiesCollection collection;
+ auto s = cfd_->current()->GetPropertiesOfAllTables(&collection);
+ if (!s.ok()) {
+ return false;
+ }
+ *value = std::numeric_limits<uint64_t>::max();
+ for (auto& p : collection) {
+ *value = std::min(*value, p.second->oldest_key_time);
+ if (*value == 0) {
+ break;
+ }
+ }
+ if (*value > 0) {
+ *value = std::min({cfd_->mem()->ApproximateOldestKeyTime(),
+ cfd_->imm()->ApproximateOldestKeyTime(), *value});
+ }
+ return *value > 0 && *value < std::numeric_limits<uint64_t>::max();
+}
+
+bool InternalStats::HandleBlockCacheStat(Cache** block_cache) {
+ assert(block_cache != nullptr);
+ auto* table_factory = cfd_->ioptions()->table_factory;
+ assert(table_factory != nullptr);
+ if (BlockBasedTableFactory::kName != table_factory->Name()) {
+ return false;
+ }
+ auto* table_options =
+ reinterpret_cast<BlockBasedTableOptions*>(table_factory->GetOptions());
+ if (table_options == nullptr) {
+ return false;
+ }
+ *block_cache = table_options->block_cache.get();
+ if (table_options->no_block_cache || *block_cache == nullptr) {
+ return false;
+ }
+ return true;
+}
+
+bool InternalStats::HandleBlockCacheCapacity(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ Cache* block_cache;
+ bool ok = HandleBlockCacheStat(&block_cache);
+ if (!ok) {
+ return false;
+ }
+ *value = static_cast<uint64_t>(block_cache->GetCapacity());
+ return true;
+}
+
+bool InternalStats::HandleBlockCacheUsage(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ Cache* block_cache;
+ bool ok = HandleBlockCacheStat(&block_cache);
+ if (!ok) {
+ return false;
+ }
+ *value = static_cast<uint64_t>(block_cache->GetUsage());
+ return true;
+}
+
+bool InternalStats::HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* /*db*/,
+ Version* /*version*/) {
+ Cache* block_cache;
+ bool ok = HandleBlockCacheStat(&block_cache);
+ if (!ok) {
+ return false;
+ }
+ *value = static_cast<uint64_t>(block_cache->GetPinnedUsage());
+ return true;
+}
+
+void InternalStats::DumpDBStats(std::string* value) {
+ char buf[1000];
+ // DB-level stats, only available from default column family
+ double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec;
+ double interval_seconds_up = seconds_up - db_stats_snapshot_.seconds_up;
+ snprintf(buf, sizeof(buf),
+ "\n** DB Stats **\nUptime(secs): %.1f total, %.1f interval\n",
+ seconds_up, interval_seconds_up);
+ value->append(buf);
+ // Cumulative
+ uint64_t user_bytes_written =
+ GetDBStats(InternalStats::kIntStatsBytesWritten);
+ uint64_t num_keys_written =
+ GetDBStats(InternalStats::kIntStatsNumKeysWritten);
+ uint64_t write_other = GetDBStats(InternalStats::kIntStatsWriteDoneByOther);
+ uint64_t write_self = GetDBStats(InternalStats::kIntStatsWriteDoneBySelf);
+ uint64_t wal_bytes = GetDBStats(InternalStats::kIntStatsWalFileBytes);
+ uint64_t wal_synced = GetDBStats(InternalStats::kIntStatsWalFileSynced);
+ uint64_t write_with_wal = GetDBStats(InternalStats::kIntStatsWriteWithWal);
+ uint64_t write_stall_micros =
+ GetDBStats(InternalStats::kIntStatsWriteStallMicros);
+
+ const int kHumanMicrosLen = 32;
+ char human_micros[kHumanMicrosLen];
+
+ // Data
+ // writes: total number of write requests.
+ // keys: total number of key updates issued by all the write requests
+ // commit groups: number of group commits issued to the DB. Each group can
+ // contain one or more writes.
+ // so writes/keys is the average number of put in multi-put or put
+ // writes/groups is the average group commit size.
+ //
+ // The format is the same for interval stats.
+ snprintf(buf, sizeof(buf),
+ "Cumulative writes: %s writes, %s keys, %s commit groups, "
+ "%.1f writes per commit group, ingest: %.2f GB, %.2f MB/s\n",
+ NumberToHumanString(write_other + write_self).c_str(),
+ NumberToHumanString(num_keys_written).c_str(),
+ NumberToHumanString(write_self).c_str(),
+ (write_other + write_self) / static_cast<double>(write_self + 1),
+ user_bytes_written / kGB, user_bytes_written / kMB / seconds_up);
+ value->append(buf);
+ // WAL
+ snprintf(buf, sizeof(buf),
+ "Cumulative WAL: %s writes, %s syncs, "
+ "%.2f writes per sync, written: %.2f GB, %.2f MB/s\n",
+ NumberToHumanString(write_with_wal).c_str(),
+ NumberToHumanString(wal_synced).c_str(),
+ write_with_wal / static_cast<double>(wal_synced + 1),
+ wal_bytes / kGB, wal_bytes / kMB / seconds_up);
+ value->append(buf);
+ // Stall
+ AppendHumanMicros(write_stall_micros, human_micros, kHumanMicrosLen, true);
+ snprintf(buf, sizeof(buf), "Cumulative stall: %s, %.1f percent\n",
+ human_micros,
+ // 10000 = divide by 1M to get secs, then multiply by 100 for pct
+ write_stall_micros / 10000.0 / std::max(seconds_up, 0.001));
+ value->append(buf);
+
+ // Interval
+ uint64_t interval_write_other = write_other - db_stats_snapshot_.write_other;
+ uint64_t interval_write_self = write_self - db_stats_snapshot_.write_self;
+ uint64_t interval_num_keys_written =
+ num_keys_written - db_stats_snapshot_.num_keys_written;
+ snprintf(
+ buf, sizeof(buf),
+ "Interval writes: %s writes, %s keys, %s commit groups, "
+ "%.1f writes per commit group, ingest: %.2f MB, %.2f MB/s\n",
+ NumberToHumanString(interval_write_other + interval_write_self).c_str(),
+ NumberToHumanString(interval_num_keys_written).c_str(),
+ NumberToHumanString(interval_write_self).c_str(),
+ static_cast<double>(interval_write_other + interval_write_self) /
+ (interval_write_self + 1),
+ (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB,
+ (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB /
+ std::max(interval_seconds_up, 0.001)),
+ value->append(buf);
+
+ uint64_t interval_write_with_wal =
+ write_with_wal - db_stats_snapshot_.write_with_wal;
+ uint64_t interval_wal_synced = wal_synced - db_stats_snapshot_.wal_synced;
+ uint64_t interval_wal_bytes = wal_bytes - db_stats_snapshot_.wal_bytes;
+
+ snprintf(
+ buf, sizeof(buf),
+ "Interval WAL: %s writes, %s syncs, "
+ "%.2f writes per sync, written: %.2f MB, %.2f MB/s\n",
+ NumberToHumanString(interval_write_with_wal).c_str(),
+ NumberToHumanString(interval_wal_synced).c_str(),
+ interval_write_with_wal / static_cast<double>(interval_wal_synced + 1),
+ interval_wal_bytes / kGB,
+ interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001));
+ value->append(buf);
+
+ // Stall
+ AppendHumanMicros(write_stall_micros - db_stats_snapshot_.write_stall_micros,
+ human_micros, kHumanMicrosLen, true);
+ snprintf(buf, sizeof(buf), "Interval stall: %s, %.1f percent\n", human_micros,
+ // 10000 = divide by 1M to get secs, then multiply by 100 for pct
+ (write_stall_micros - db_stats_snapshot_.write_stall_micros) /
+ 10000.0 / std::max(interval_seconds_up, 0.001));
+ value->append(buf);
+
+ db_stats_snapshot_.seconds_up = seconds_up;
+ db_stats_snapshot_.ingest_bytes = user_bytes_written;
+ db_stats_snapshot_.write_other = write_other;
+ db_stats_snapshot_.write_self = write_self;
+ db_stats_snapshot_.num_keys_written = num_keys_written;
+ db_stats_snapshot_.wal_bytes = wal_bytes;
+ db_stats_snapshot_.wal_synced = wal_synced;
+ db_stats_snapshot_.write_with_wal = write_with_wal;
+ db_stats_snapshot_.write_stall_micros = write_stall_micros;
+}
+
+/**
+ * Dump Compaction Level stats to a map of stat name with "compaction." prefix
+ * to value in double as string. The level in stat name is represented with
+ * a prefix "Lx" where "x" is the level number. A special level "Sum"
+ * represents the sum of a stat for all levels.
+ * The result also contains IO stall counters which keys start with "io_stalls."
+ * and values represent uint64 encoded as strings.
+ */
+void InternalStats::DumpCFMapStats(
+ std::map<std::string, std::string>* cf_stats) {
+ CompactionStats compaction_stats_sum;
+ std::map<int, std::map<LevelStatType, double>> levels_stats;
+ DumpCFMapStats(&levels_stats, &compaction_stats_sum);
+ for (auto const& level_ent : levels_stats) {
+ auto level_str =
+ level_ent.first == -1 ? "Sum" : "L" + ToString(level_ent.first);
+ for (auto const& stat_ent : level_ent.second) {
+ auto stat_type = stat_ent.first;
+ auto key_str =
+ "compaction." + level_str + "." +
+ InternalStats::compaction_level_stats.at(stat_type).property_name;
+ (*cf_stats)[key_str] = std::to_string(stat_ent.second);
+ }
+ }
+
+ DumpCFMapStatsIOStalls(cf_stats);
+}
+
+void InternalStats::DumpCFMapStats(
+ std::map<int, std::map<LevelStatType, double>>* levels_stats,
+ CompactionStats* compaction_stats_sum) {
+ const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
+
+ int num_levels_to_check =
+ (cfd_->ioptions()->compaction_style != kCompactionStyleFIFO)
+ ? vstorage->num_levels() - 1
+ : 1;
+
+ // Compaction scores are sorted based on its value. Restore them to the
+ // level order
+ std::vector<double> compaction_score(number_levels_, 0);
+ for (int i = 0; i < num_levels_to_check; ++i) {
+ compaction_score[vstorage->CompactionScoreLevel(i)] =
+ vstorage->CompactionScore(i);
+ }
+ // Count # of files being compacted for each level
+ std::vector<int> files_being_compacted(number_levels_, 0);
+ for (int level = 0; level < number_levels_; ++level) {
+ for (auto* f : vstorage->LevelFiles(level)) {
+ if (f->being_compacted) {
+ ++files_being_compacted[level];
+ }
+ }
+ }
+
+ int total_files = 0;
+ int total_files_being_compacted = 0;
+ double total_file_size = 0;
+ uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
+ uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
+ uint64_t curr_ingest = flush_ingest + add_file_ingest;
+ for (int level = 0; level < number_levels_; level++) {
+ int files = vstorage->NumLevelFiles(level);
+ total_files += files;
+ total_files_being_compacted += files_being_compacted[level];
+ if (comp_stats_[level].micros > 0 || files > 0) {
+ compaction_stats_sum->Add(comp_stats_[level]);
+ total_file_size += vstorage->NumLevelBytes(level);
+ uint64_t input_bytes;
+ if (level == 0) {
+ input_bytes = curr_ingest;
+ } else {
+ input_bytes = comp_stats_[level].bytes_read_non_output_levels;
+ }
+ double w_amp =
+ (input_bytes == 0)
+ ? 0.0
+ : static_cast<double>(comp_stats_[level].bytes_written) /
+ input_bytes;
+ std::map<LevelStatType, double> level_stats;
+ PrepareLevelStats(&level_stats, files, files_being_compacted[level],
+ static_cast<double>(vstorage->NumLevelBytes(level)),
+ compaction_score[level], w_amp, comp_stats_[level]);
+ (*levels_stats)[level] = level_stats;
+ }
+ }
+ // Cumulative summary
+ double w_amp = compaction_stats_sum->bytes_written /
+ static_cast<double>(curr_ingest + 1);
+ // Stats summary across levels
+ std::map<LevelStatType, double> sum_stats;
+ PrepareLevelStats(&sum_stats, total_files, total_files_being_compacted,
+ total_file_size, 0, w_amp, *compaction_stats_sum);
+ (*levels_stats)[-1] = sum_stats; // -1 is for the Sum level
+}
+
+void InternalStats::DumpCFMapStatsByPriority(
+ std::map<int, std::map<LevelStatType, double>>* priorities_stats) {
+ for (size_t priority = 0; priority < comp_stats_by_pri_.size(); priority++) {
+ if (comp_stats_by_pri_[priority].micros > 0) {
+ std::map<LevelStatType, double> priority_stats;
+ PrepareLevelStats(&priority_stats, 0 /* num_files */,
+ 0 /* being_compacted */, 0 /* total_file_size */,
+ 0 /* compaction_score */, 0 /* w_amp */,
+ comp_stats_by_pri_[priority]);
+ (*priorities_stats)[static_cast<int>(priority)] = priority_stats;
+ }
+ }
+}
+
+void InternalStats::DumpCFMapStatsIOStalls(
+ std::map<std::string, std::string>* cf_stats) {
+ (*cf_stats)["io_stalls.level0_slowdown"] =
+ std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS]);
+ (*cf_stats)["io_stalls.level0_slowdown_with_compaction"] =
+ std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS]);
+ (*cf_stats)["io_stalls.level0_numfiles"] =
+ std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS]);
+ (*cf_stats)["io_stalls.level0_numfiles_with_compaction"] =
+ std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS]);
+ (*cf_stats)["io_stalls.stop_for_pending_compaction_bytes"] =
+ std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS]);
+ (*cf_stats)["io_stalls.slowdown_for_pending_compaction_bytes"] =
+ std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS]);
+ (*cf_stats)["io_stalls.memtable_compaction"] =
+ std::to_string(cf_stats_count_[MEMTABLE_LIMIT_STOPS]);
+ (*cf_stats)["io_stalls.memtable_slowdown"] =
+ std::to_string(cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS]);
+
+ uint64_t total_stop = cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] +
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] +
+ cf_stats_count_[MEMTABLE_LIMIT_STOPS];
+
+ uint64_t total_slowdown =
+ cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] +
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] +
+ cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS];
+
+ (*cf_stats)["io_stalls.total_stop"] = std::to_string(total_stop);
+ (*cf_stats)["io_stalls.total_slowdown"] = std::to_string(total_slowdown);
+}
+
+void InternalStats::DumpCFStats(std::string* value) {
+ DumpCFStatsNoFileHistogram(value);
+ DumpCFFileHistogram(value);
+}
+
+void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
+ char buf[2000];
+ // Per-ColumnFamily stats
+ PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Level");
+ value->append(buf);
+
+ // Print stats for each level
+ std::map<int, std::map<LevelStatType, double>> levels_stats;
+ CompactionStats compaction_stats_sum;
+ DumpCFMapStats(&levels_stats, &compaction_stats_sum);
+ for (int l = 0; l < number_levels_; ++l) {
+ if (levels_stats.find(l) != levels_stats.end()) {
+ PrintLevelStats(buf, sizeof(buf), "L" + ToString(l), levels_stats[l]);
+ value->append(buf);
+ }
+ }
+
+ // Print sum of level stats
+ PrintLevelStats(buf, sizeof(buf), "Sum", levels_stats[-1]);
+ value->append(buf);
+
+ uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
+ uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
+ uint64_t ingest_files_addfile = cf_stats_value_[INGESTED_NUM_FILES_TOTAL];
+ uint64_t ingest_l0_files_addfile =
+ cf_stats_value_[INGESTED_LEVEL0_NUM_FILES_TOTAL];
+ uint64_t ingest_keys_addfile = cf_stats_value_[INGESTED_NUM_KEYS_TOTAL];
+ // Cumulative summary
+ uint64_t total_stall_count =
+ cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] +
+ cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] +
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] +
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] +
+ cf_stats_count_[MEMTABLE_LIMIT_STOPS] +
+ cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS];
+ // Interval summary
+ uint64_t interval_flush_ingest =
+ flush_ingest - cf_stats_snapshot_.ingest_bytes_flush;
+ uint64_t interval_add_file_inget =
+ add_file_ingest - cf_stats_snapshot_.ingest_bytes_addfile;
+ uint64_t interval_ingest =
+ interval_flush_ingest + interval_add_file_inget + 1;
+ CompactionStats interval_stats(compaction_stats_sum);
+ interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
+ double w_amp =
+ interval_stats.bytes_written / static_cast<double>(interval_ingest);
+ PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
+ value->append(buf);
+
+ PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Priority");
+ value->append(buf);
+ std::map<int, std::map<LevelStatType, double>> priorities_stats;
+ DumpCFMapStatsByPriority(&priorities_stats);
+ for (size_t priority = 0; priority < comp_stats_by_pri_.size(); ++priority) {
+ if (priorities_stats.find(static_cast<int>(priority)) !=
+ priorities_stats.end()) {
+ PrintLevelStats(
+ buf, sizeof(buf),
+ Env::PriorityToString(static_cast<Env::Priority>(priority)),
+ priorities_stats[static_cast<int>(priority)]);
+ value->append(buf);
+ }
+ }
+
+ double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec;
+ double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
+ snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
+ seconds_up, interval_seconds_up);
+ value->append(buf);
+ snprintf(buf, sizeof(buf), "Flush(GB): cumulative %.3f, interval %.3f\n",
+ flush_ingest / kGB, interval_flush_ingest / kGB);
+ value->append(buf);
+ snprintf(buf, sizeof(buf), "AddFile(GB): cumulative %.3f, interval %.3f\n",
+ add_file_ingest / kGB, interval_add_file_inget / kGB);
+ value->append(buf);
+
+ uint64_t interval_ingest_files_addfile =
+ ingest_files_addfile - cf_stats_snapshot_.ingest_files_addfile;
+ snprintf(buf, sizeof(buf),
+ "AddFile(Total Files): cumulative %" PRIu64 ", interval %" PRIu64
+ "\n",
+ ingest_files_addfile, interval_ingest_files_addfile);
+ value->append(buf);
+
+ uint64_t interval_ingest_l0_files_addfile =
+ ingest_l0_files_addfile - cf_stats_snapshot_.ingest_l0_files_addfile;
+ snprintf(buf, sizeof(buf),
+ "AddFile(L0 Files): cumulative %" PRIu64 ", interval %" PRIu64 "\n",
+ ingest_l0_files_addfile, interval_ingest_l0_files_addfile);
+ value->append(buf);
+
+ uint64_t interval_ingest_keys_addfile =
+ ingest_keys_addfile - cf_stats_snapshot_.ingest_keys_addfile;
+ snprintf(buf, sizeof(buf),
+ "AddFile(Keys): cumulative %" PRIu64 ", interval %" PRIu64 "\n",
+ ingest_keys_addfile, interval_ingest_keys_addfile);
+ value->append(buf);
+
+ // Compact
+ uint64_t compact_bytes_read = 0;
+ uint64_t compact_bytes_write = 0;
+ uint64_t compact_micros = 0;
+ for (int level = 0; level < number_levels_; level++) {
+ compact_bytes_read += comp_stats_[level].bytes_read_output_level +
+ comp_stats_[level].bytes_read_non_output_levels;
+ compact_bytes_write += comp_stats_[level].bytes_written;
+ compact_micros += comp_stats_[level].micros;
+ }
+
+ snprintf(buf, sizeof(buf),
+ "Cumulative compaction: %.2f GB write, %.2f MB/s write, "
+ "%.2f GB read, %.2f MB/s read, %.1f seconds\n",
+ compact_bytes_write / kGB, compact_bytes_write / kMB / seconds_up,
+ compact_bytes_read / kGB, compact_bytes_read / kMB / seconds_up,
+ compact_micros / kMicrosInSec);
+ value->append(buf);
+
+ // Compaction interval
+ uint64_t interval_compact_bytes_write =
+ compact_bytes_write - cf_stats_snapshot_.compact_bytes_write;
+ uint64_t interval_compact_bytes_read =
+ compact_bytes_read - cf_stats_snapshot_.compact_bytes_read;
+ uint64_t interval_compact_micros =
+ compact_micros - cf_stats_snapshot_.compact_micros;
+
+ snprintf(
+ buf, sizeof(buf),
+ "Interval compaction: %.2f GB write, %.2f MB/s write, "
+ "%.2f GB read, %.2f MB/s read, %.1f seconds\n",
+ interval_compact_bytes_write / kGB,
+ interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
+ interval_compact_bytes_read / kGB,
+ interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
+ interval_compact_micros / kMicrosInSec);
+ value->append(buf);
+ cf_stats_snapshot_.compact_bytes_write = compact_bytes_write;
+ cf_stats_snapshot_.compact_bytes_read = compact_bytes_read;
+ cf_stats_snapshot_.compact_micros = compact_micros;
+
+ snprintf(buf, sizeof(buf),
+ "Stalls(count): %" PRIu64
+ " level0_slowdown, "
+ "%" PRIu64
+ " level0_slowdown_with_compaction, "
+ "%" PRIu64
+ " level0_numfiles, "
+ "%" PRIu64
+ " level0_numfiles_with_compaction, "
+ "%" PRIu64
+ " stop for pending_compaction_bytes, "
+ "%" PRIu64
+ " slowdown for pending_compaction_bytes, "
+ "%" PRIu64
+ " memtable_compaction, "
+ "%" PRIu64
+ " memtable_slowdown, "
+ "interval %" PRIu64 " total count\n",
+ cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS],
+ cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS],
+ cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS],
+ cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS],
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS],
+ cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS],
+ cf_stats_count_[MEMTABLE_LIMIT_STOPS],
+ cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS],
+ total_stall_count - cf_stats_snapshot_.stall_count);
+ value->append(buf);
+
+ cf_stats_snapshot_.seconds_up = seconds_up;
+ cf_stats_snapshot_.ingest_bytes_flush = flush_ingest;
+ cf_stats_snapshot_.ingest_bytes_addfile = add_file_ingest;
+ cf_stats_snapshot_.ingest_files_addfile = ingest_files_addfile;
+ cf_stats_snapshot_.ingest_l0_files_addfile = ingest_l0_files_addfile;
+ cf_stats_snapshot_.ingest_keys_addfile = ingest_keys_addfile;
+ cf_stats_snapshot_.comp_stats = compaction_stats_sum;
+ cf_stats_snapshot_.stall_count = total_stall_count;
+}
+
+void InternalStats::DumpCFFileHistogram(std::string* value) {
+ char buf[2000];
+ snprintf(buf, sizeof(buf),
+ "\n** File Read Latency Histogram By Level [%s] **\n",
+ cfd_->GetName().c_str());
+ value->append(buf);
+
+ for (int level = 0; level < number_levels_; level++) {
+ if (!file_read_latency_[level].Empty()) {
+ char buf2[5000];
+ snprintf(buf2, sizeof(buf2),
+ "** Level %d read latency histogram (micros):\n%s\n", level,
+ file_read_latency_[level].ToString().c_str());
+ value->append(buf2);
+ }
+ }
+}
+
+#else
+
+const DBPropertyInfo* GetPropertyInfo(const Slice& /*property*/) {
+ return nullptr;
+}
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/internal_stats.h b/storage/rocksdb/rocksdb/db/internal_stats.h
new file mode 100644
index 00000000..ce83be24
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/internal_stats.h
@@ -0,0 +1,697 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+#include <vector>
+
+#include "db/version_set.h"
+
+class ColumnFamilyData;
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+class MemTableList;
+
+// Config for retrieving a property's value.
+struct DBPropertyInfo {
+ bool need_out_of_mutex;
+
+ // gcc had an internal error for initializing union of pointer-to-member-
+ // functions. Workaround is to populate exactly one of the following function
+ // pointers with a non-nullptr value.
+
+ // @param value Value-result argument for storing the property's string value
+ // @param suffix Argument portion of the property. For example, suffix would
+ // be "5" for the property "rocksdb.num-files-at-level5". So far, only
+ // certain string properties take an argument.
+ bool (InternalStats::*handle_string)(std::string* value, Slice suffix);
+
+ // @param value Value-result argument for storing the property's uint64 value
+ // @param db Many of the int properties rely on DBImpl methods.
+ // @param version Version is needed in case the property is retrieved without
+ // holding db mutex, which is only supported for int properties.
+ bool (InternalStats::*handle_int)(uint64_t* value, DBImpl* db,
+ Version* version);
+
+ // @param props Map of general properties to populate
+ bool (InternalStats::*handle_map)(std::map<std::string, std::string>* props);
+
+ // handle the string type properties rely on DBImpl methods
+ // @param value Value-result argument for storing the property's string value
+ bool (DBImpl::*handle_string_dbimpl)(std::string* value);
+};
+
+extern const DBPropertyInfo* GetPropertyInfo(const Slice& property);
+
+#ifndef ROCKSDB_LITE
+#undef SCORE
+enum class LevelStatType {
+ INVALID = 0,
+ NUM_FILES,
+ COMPACTED_FILES,
+ SIZE_BYTES,
+ SCORE,
+ READ_GB,
+ RN_GB,
+ RNP1_GB,
+ WRITE_GB,
+ W_NEW_GB,
+ MOVED_GB,
+ WRITE_AMP,
+ READ_MBPS,
+ WRITE_MBPS,
+ COMP_SEC,
+ COMP_CPU_SEC,
+ COMP_COUNT,
+ AVG_SEC,
+ KEY_IN,
+ KEY_DROP,
+ TOTAL // total number of types
+};
+
+struct LevelStat {
+ // This what will be L?.property_name in the flat map returned to the user
+ std::string property_name;
+ // This will be what we will print in the header in the cli
+ std::string header_name;
+};
+
+class InternalStats {
+ public:
+ static const std::map<LevelStatType, LevelStat> compaction_level_stats;
+
+ enum InternalCFStatsType {
+ L0_FILE_COUNT_LIMIT_SLOWDOWNS,
+ LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS,
+ MEMTABLE_LIMIT_STOPS,
+ MEMTABLE_LIMIT_SLOWDOWNS,
+ L0_FILE_COUNT_LIMIT_STOPS,
+ LOCKED_L0_FILE_COUNT_LIMIT_STOPS,
+ PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS,
+ PENDING_COMPACTION_BYTES_LIMIT_STOPS,
+ WRITE_STALLS_ENUM_MAX,
+ BYTES_FLUSHED,
+ BYTES_INGESTED_ADD_FILE,
+ INGESTED_NUM_FILES_TOTAL,
+ INGESTED_LEVEL0_NUM_FILES_TOTAL,
+ INGESTED_NUM_KEYS_TOTAL,
+ INTERNAL_CF_STATS_ENUM_MAX,
+ };
+
+ enum InternalDBStatsType {
+ kIntStatsWalFileBytes,
+ kIntStatsWalFileSynced,
+ kIntStatsBytesWritten,
+ kIntStatsNumKeysWritten,
+ kIntStatsWriteDoneByOther,
+ kIntStatsWriteDoneBySelf,
+ kIntStatsWriteWithWal,
+ kIntStatsWriteStallMicros,
+ kIntStatsNumMax,
+ };
+
+ InternalStats(int num_levels, Env* env, ColumnFamilyData* cfd)
+ : db_stats_{},
+ cf_stats_value_{},
+ cf_stats_count_{},
+ comp_stats_(num_levels),
+ comp_stats_by_pri_(Env::Priority::TOTAL),
+ file_read_latency_(num_levels),
+ bg_error_count_(0),
+ number_levels_(num_levels),
+ env_(env),
+ cfd_(cfd),
+ started_at_(env->NowMicros()) {}
+
+ // Per level compaction stats. comp_stats_[level] stores the stats for
+ // compactions that produced data for the specified "level".
+ struct CompactionStats {
+ uint64_t micros;
+ uint64_t cpu_micros;
+
+ // The number of bytes read from all non-output levels
+ uint64_t bytes_read_non_output_levels;
+
+ // The number of bytes read from the compaction output level.
+ uint64_t bytes_read_output_level;
+
+ // Total number of bytes written during compaction
+ uint64_t bytes_written;
+
+ // Total number of bytes moved to the output level
+ uint64_t bytes_moved;
+
+ // The number of compaction input files in all non-output levels.
+ int num_input_files_in_non_output_levels;
+
+ // The number of compaction input files in the output level.
+ int num_input_files_in_output_level;
+
+ // The number of compaction output files.
+ int num_output_files;
+
+ // Total incoming entries during compaction between levels N and N+1
+ uint64_t num_input_records;
+
+ // Accumulated diff number of entries
+ // (num input entries - num output entires) for compaction levels N and N+1
+ uint64_t num_dropped_records;
+
+ // Number of compactions done
+ int count;
+
+ // Number of compactions done per CompactionReason
+ int counts[static_cast<int>(CompactionReason::kNumOfReasons)];
+
+ explicit CompactionStats()
+ : micros(0),
+ cpu_micros(0),
+ bytes_read_non_output_levels(0),
+ bytes_read_output_level(0),
+ bytes_written(0),
+ bytes_moved(0),
+ num_input_files_in_non_output_levels(0),
+ num_input_files_in_output_level(0),
+ num_output_files(0),
+ num_input_records(0),
+ num_dropped_records(0),
+ count(0) {
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] = 0;
+ }
+ }
+
+ explicit CompactionStats(CompactionReason reason, int c)
+ : micros(0),
+ cpu_micros(0),
+ bytes_read_non_output_levels(0),
+ bytes_read_output_level(0),
+ bytes_written(0),
+ bytes_moved(0),
+ num_input_files_in_non_output_levels(0),
+ num_input_files_in_output_level(0),
+ num_output_files(0),
+ num_input_records(0),
+ num_dropped_records(0),
+ count(c) {
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] = 0;
+ }
+ int r = static_cast<int>(reason);
+ if (r >= 0 && r < num_of_reasons) {
+ counts[r] = c;
+ } else {
+ count = 0;
+ }
+ }
+
+ explicit CompactionStats(const CompactionStats& c)
+ : micros(c.micros),
+ cpu_micros(c.cpu_micros),
+ bytes_read_non_output_levels(c.bytes_read_non_output_levels),
+ bytes_read_output_level(c.bytes_read_output_level),
+ bytes_written(c.bytes_written),
+ bytes_moved(c.bytes_moved),
+ num_input_files_in_non_output_levels(
+ c.num_input_files_in_non_output_levels),
+ num_input_files_in_output_level(c.num_input_files_in_output_level),
+ num_output_files(c.num_output_files),
+ num_input_records(c.num_input_records),
+ num_dropped_records(c.num_dropped_records),
+ count(c.count) {
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] = c.counts[i];
+ }
+ }
+
+ CompactionStats& operator=(const CompactionStats& c) {
+ micros = c.micros;
+ cpu_micros = c.cpu_micros;
+ bytes_read_non_output_levels = c.bytes_read_non_output_levels;
+ bytes_read_output_level = c.bytes_read_output_level;
+ bytes_written = c.bytes_written;
+ bytes_moved = c.bytes_moved;
+ num_input_files_in_non_output_levels =
+ c.num_input_files_in_non_output_levels;
+ num_input_files_in_output_level = c.num_input_files_in_output_level;
+ num_output_files = c.num_output_files;
+ num_input_records = c.num_input_records;
+ num_dropped_records = c.num_dropped_records;
+ count = c.count;
+
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] = c.counts[i];
+ }
+ return *this;
+ }
+
+ void Clear() {
+ this->micros = 0;
+ this->cpu_micros = 0;
+ this->bytes_read_non_output_levels = 0;
+ this->bytes_read_output_level = 0;
+ this->bytes_written = 0;
+ this->bytes_moved = 0;
+ this->num_input_files_in_non_output_levels = 0;
+ this->num_input_files_in_output_level = 0;
+ this->num_output_files = 0;
+ this->num_input_records = 0;
+ this->num_dropped_records = 0;
+ this->count = 0;
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] = 0;
+ }
+ }
+
+ void Add(const CompactionStats& c) {
+ this->micros += c.micros;
+ this->cpu_micros += c.cpu_micros;
+ this->bytes_read_non_output_levels += c.bytes_read_non_output_levels;
+ this->bytes_read_output_level += c.bytes_read_output_level;
+ this->bytes_written += c.bytes_written;
+ this->bytes_moved += c.bytes_moved;
+ this->num_input_files_in_non_output_levels +=
+ c.num_input_files_in_non_output_levels;
+ this->num_input_files_in_output_level +=
+ c.num_input_files_in_output_level;
+ this->num_output_files += c.num_output_files;
+ this->num_input_records += c.num_input_records;
+ this->num_dropped_records += c.num_dropped_records;
+ this->count += c.count;
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i< num_of_reasons; i++) {
+ counts[i] += c.counts[i];
+ }
+ }
+
+ void Subtract(const CompactionStats& c) {
+ this->micros -= c.micros;
+ this->cpu_micros -= c.cpu_micros;
+ this->bytes_read_non_output_levels -= c.bytes_read_non_output_levels;
+ this->bytes_read_output_level -= c.bytes_read_output_level;
+ this->bytes_written -= c.bytes_written;
+ this->bytes_moved -= c.bytes_moved;
+ this->num_input_files_in_non_output_levels -=
+ c.num_input_files_in_non_output_levels;
+ this->num_input_files_in_output_level -=
+ c.num_input_files_in_output_level;
+ this->num_output_files -= c.num_output_files;
+ this->num_input_records -= c.num_input_records;
+ this->num_dropped_records -= c.num_dropped_records;
+ this->count -= c.count;
+ int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
+ for (int i = 0; i < num_of_reasons; i++) {
+ counts[i] -= c.counts[i];
+ }
+ }
+ };
+
+ void Clear() {
+ for (int i = 0; i < kIntStatsNumMax; i++) {
+ db_stats_[i].store(0);
+ }
+ for (int i = 0; i < INTERNAL_CF_STATS_ENUM_MAX; i++) {
+ cf_stats_count_[i] = 0;
+ cf_stats_value_[i] = 0;
+ }
+ for (auto& comp_stat : comp_stats_) {
+ comp_stat.Clear();
+ }
+ for (auto& h : file_read_latency_) {
+ h.Clear();
+ }
+ cf_stats_snapshot_.Clear();
+ db_stats_snapshot_.Clear();
+ bg_error_count_ = 0;
+ started_at_ = env_->NowMicros();
+ }
+
+ void AddCompactionStats(int level, Env::Priority thread_pri,
+ const CompactionStats& stats) {
+ comp_stats_[level].Add(stats);
+ comp_stats_by_pri_[thread_pri].Add(stats);
+ }
+
+ void IncBytesMoved(int level, uint64_t amount) {
+ comp_stats_[level].bytes_moved += amount;
+ }
+
+ void AddCFStats(InternalCFStatsType type, uint64_t value) {
+ cf_stats_value_[type] += value;
+ ++cf_stats_count_[type];
+ }
+
+ void AddDBStats(InternalDBStatsType type, uint64_t value,
+ bool concurrent = false) {
+ auto& v = db_stats_[type];
+ if (concurrent) {
+ v.fetch_add(value, std::memory_order_relaxed);
+ } else {
+ v.store(v.load(std::memory_order_relaxed) + value,
+ std::memory_order_relaxed);
+ }
+ }
+
+ uint64_t GetDBStats(InternalDBStatsType type) {
+ return db_stats_[type].load(std::memory_order_relaxed);
+ }
+
+ HistogramImpl* GetFileReadHist(int level) {
+ return &file_read_latency_[level];
+ }
+
+ uint64_t GetBackgroundErrorCount() const { return bg_error_count_; }
+
+ uint64_t BumpAndGetBackgroundErrorCount() { return ++bg_error_count_; }
+
+ bool GetStringProperty(const DBPropertyInfo& property_info,
+ const Slice& property, std::string* value);
+
+ bool GetMapProperty(const DBPropertyInfo& property_info,
+ const Slice& property,
+ std::map<std::string, std::string>* value);
+
+ bool GetIntProperty(const DBPropertyInfo& property_info, uint64_t* value,
+ DBImpl* db);
+
+ bool GetIntPropertyOutOfMutex(const DBPropertyInfo& property_info,
+ Version* version, uint64_t* value);
+
+ const std::vector<CompactionStats>& TEST_GetCompactionStats() const {
+ return comp_stats_;
+ }
+
+ // Store a mapping from the user-facing DB::Properties string to our
+ // DBPropertyInfo struct used internally for retrieving properties.
+ static const std::unordered_map<std::string, DBPropertyInfo> ppt_name_to_info;
+
+ private:
+ void DumpDBStats(std::string* value);
+ void DumpCFMapStats(std::map<std::string, std::string>* cf_stats);
+ void DumpCFMapStats(
+ std::map<int, std::map<LevelStatType, double>>* level_stats,
+ CompactionStats* compaction_stats_sum);
+ void DumpCFMapStatsByPriority(
+ std::map<int, std::map<LevelStatType, double>>* priorities_stats);
+ void DumpCFMapStatsIOStalls(std::map<std::string, std::string>* cf_stats);
+ void DumpCFStats(std::string* value);
+ void DumpCFStatsNoFileHistogram(std::string* value);
+ void DumpCFFileHistogram(std::string* value);
+
+ bool HandleBlockCacheStat(Cache** block_cache);
+
+ // Per-DB stats
+ std::atomic<uint64_t> db_stats_[kIntStatsNumMax];
+ // Per-ColumnFamily stats
+ uint64_t cf_stats_value_[INTERNAL_CF_STATS_ENUM_MAX];
+ uint64_t cf_stats_count_[INTERNAL_CF_STATS_ENUM_MAX];
+ // Per-ColumnFamily/level compaction stats
+ std::vector<CompactionStats> comp_stats_;
+ std::vector<CompactionStats> comp_stats_by_pri_;
+ std::vector<HistogramImpl> file_read_latency_;
+
+ // Used to compute per-interval statistics
+ struct CFStatsSnapshot {
+ // ColumnFamily-level stats
+ CompactionStats comp_stats;
+ uint64_t ingest_bytes_flush; // Bytes written to L0 (Flush)
+ uint64_t stall_count; // Stall count
+ // Stats from compaction jobs - bytes written, bytes read, duration.
+ uint64_t compact_bytes_write;
+ uint64_t compact_bytes_read;
+ uint64_t compact_micros;
+ double seconds_up;
+
+ // AddFile specific stats
+ uint64_t ingest_bytes_addfile; // Total Bytes ingested
+ uint64_t ingest_files_addfile; // Total number of files ingested
+ uint64_t ingest_l0_files_addfile; // Total number of files ingested to L0
+ uint64_t ingest_keys_addfile; // Total number of keys ingested
+
+ CFStatsSnapshot()
+ : ingest_bytes_flush(0),
+ stall_count(0),
+ compact_bytes_write(0),
+ compact_bytes_read(0),
+ compact_micros(0),
+ seconds_up(0),
+ ingest_bytes_addfile(0),
+ ingest_files_addfile(0),
+ ingest_l0_files_addfile(0),
+ ingest_keys_addfile(0) {}
+
+ void Clear() {
+ comp_stats.Clear();
+ ingest_bytes_flush = 0;
+ stall_count = 0;
+ compact_bytes_write = 0;
+ compact_bytes_read = 0;
+ compact_micros = 0;
+ seconds_up = 0;
+ ingest_bytes_addfile = 0;
+ ingest_files_addfile = 0;
+ ingest_l0_files_addfile = 0;
+ ingest_keys_addfile = 0;
+ }
+ } cf_stats_snapshot_;
+
+ struct DBStatsSnapshot {
+ // DB-level stats
+ uint64_t ingest_bytes; // Bytes written by user
+ uint64_t wal_bytes; // Bytes written to WAL
+ uint64_t wal_synced; // Number of times WAL is synced
+ uint64_t write_with_wal; // Number of writes that request WAL
+ // These count the number of writes processed by the calling thread or
+ // another thread.
+ uint64_t write_other;
+ uint64_t write_self;
+ // Total number of keys written. write_self and write_other measure number
+ // of write requests written, Each of the write request can contain updates
+ // to multiple keys. num_keys_written is total number of keys updated by all
+ // those writes.
+ uint64_t num_keys_written;
+ // Total time writes delayed by stalls.
+ uint64_t write_stall_micros;
+ double seconds_up;
+
+ DBStatsSnapshot()
+ : ingest_bytes(0),
+ wal_bytes(0),
+ wal_synced(0),
+ write_with_wal(0),
+ write_other(0),
+ write_self(0),
+ num_keys_written(0),
+ write_stall_micros(0),
+ seconds_up(0) {}
+
+ void Clear() {
+ ingest_bytes = 0;
+ wal_bytes = 0;
+ wal_synced = 0;
+ write_with_wal = 0;
+ write_other = 0;
+ write_self = 0;
+ num_keys_written = 0;
+ write_stall_micros = 0;
+ seconds_up = 0;
+ }
+ } db_stats_snapshot_;
+
+ // Handler functions for getting property values. They use "value" as a value-
+ // result argument, and return true upon successfully setting "value".
+ bool HandleNumFilesAtLevel(std::string* value, Slice suffix);
+ bool HandleCompressionRatioAtLevelPrefix(std::string* value, Slice suffix);
+ bool HandleLevelStats(std::string* value, Slice suffix);
+ bool HandleStats(std::string* value, Slice suffix);
+ bool HandleCFMapStats(std::map<std::string, std::string>* compaction_stats);
+ bool HandleCFStats(std::string* value, Slice suffix);
+ bool HandleCFStatsNoFileHistogram(std::string* value, Slice suffix);
+ bool HandleCFFileHistogram(std::string* value, Slice suffix);
+ bool HandleDBStats(std::string* value, Slice suffix);
+ bool HandleSsTables(std::string* value, Slice suffix);
+ bool HandleAggregatedTableProperties(std::string* value, Slice suffix);
+ bool HandleAggregatedTablePropertiesAtLevel(std::string* value, Slice suffix);
+ bool HandleNumImmutableMemTable(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumImmutableMemTableFlushed(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleMemTableFlushPending(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumRunningFlushes(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleCompactionPending(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleNumRunningCompactions(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleBackgroundErrors(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleCurSizeAllMemTables(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleSizeAllMemTables(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleNumEntriesActiveMemTable(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumEntriesImmMemTables(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumDeletesActiveMemTable(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumDeletesImmMemTables(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleEstimateNumKeys(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleNumSnapshots(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleOldestSnapshotTime(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleOldestSnapshotSequence(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleNumLiveVersions(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleCurrentSuperVersionNumber(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleIsFileDeletionsEnabled(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleBaseLevel(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleTotalSstFilesSize(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleLiveSstFilesSize(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleEstimatePendingCompactionBytes(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleEstimateTableReadersMem(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleEstimateLiveDataSize(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleMinObsoleteSstNumberToKeep(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleIsWriteStopped(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* db,
+ Version* version);
+ bool HandleBlockCacheCapacity(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleBlockCacheUsage(uint64_t* value, DBImpl* db, Version* version);
+ bool HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* db,
+ Version* version);
+ // Total number of background errors encountered. Every time a flush task
+ // or compaction task fails, this counter is incremented. The failure can
+ // be caused by any possible reason, including file system errors, out of
+ // resources, or input file corruption. Failing when retrying the same flush
+ // or compaction will cause the counter to increase too.
+ uint64_t bg_error_count_;
+
+ const int number_levels_;
+ Env* env_;
+ ColumnFamilyData* cfd_;
+ uint64_t started_at_;
+};
+
+#else
+
+class InternalStats {
+ public:
+ enum InternalCFStatsType {
+ L0_FILE_COUNT_LIMIT_SLOWDOWNS,
+ LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS,
+ MEMTABLE_LIMIT_STOPS,
+ MEMTABLE_LIMIT_SLOWDOWNS,
+ L0_FILE_COUNT_LIMIT_STOPS,
+ LOCKED_L0_FILE_COUNT_LIMIT_STOPS,
+ PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS,
+ PENDING_COMPACTION_BYTES_LIMIT_STOPS,
+ WRITE_STALLS_ENUM_MAX,
+ BYTES_FLUSHED,
+ BYTES_INGESTED_ADD_FILE,
+ INGESTED_NUM_FILES_TOTAL,
+ INGESTED_LEVEL0_NUM_FILES_TOTAL,
+ INGESTED_NUM_KEYS_TOTAL,
+ INTERNAL_CF_STATS_ENUM_MAX,
+ };
+
+ enum InternalDBStatsType {
+ kIntStatsWalFileBytes,
+ kIntStatsWalFileSynced,
+ kIntStatsBytesWritten,
+ kIntStatsNumKeysWritten,
+ kIntStatsWriteDoneByOther,
+ kIntStatsWriteDoneBySelf,
+ kIntStatsWriteWithWal,
+ kIntStatsWriteStallMicros,
+ kIntStatsNumMax,
+ };
+
+ InternalStats(int /*num_levels*/, Env* /*env*/, ColumnFamilyData* /*cfd*/) {}
+
+ struct CompactionStats {
+ uint64_t micros;
+ uint64_t cpu_micros;
+ uint64_t bytes_read_non_output_levels;
+ uint64_t bytes_read_output_level;
+ uint64_t bytes_written;
+ uint64_t bytes_moved;
+ int num_input_files_in_non_output_levels;
+ int num_input_files_in_output_level;
+ int num_output_files;
+ uint64_t num_input_records;
+ uint64_t num_dropped_records;
+ int count;
+
+ explicit CompactionStats() {}
+
+ explicit CompactionStats(CompactionReason /*reason*/, int /*c*/) {}
+
+ explicit CompactionStats(const CompactionStats& /*c*/) {}
+
+ void Add(const CompactionStats& /*c*/) {}
+
+ void Subtract(const CompactionStats& /*c*/) {}
+ };
+
+ void AddCompactionStats(int /*level*/, Env::Priority /*thread_pri*/,
+ const CompactionStats& /*stats*/) {}
+
+ void IncBytesMoved(int /*level*/, uint64_t /*amount*/) {}
+
+ void AddCFStats(InternalCFStatsType /*type*/, uint64_t /*value*/) {}
+
+ void AddDBStats(InternalDBStatsType /*type*/, uint64_t /*value*/,
+ bool /*concurrent */ = false) {}
+
+ HistogramImpl* GetFileReadHist(int /*level*/) { return nullptr; }
+
+ uint64_t GetBackgroundErrorCount() const { return 0; }
+
+ uint64_t BumpAndGetBackgroundErrorCount() { return 0; }
+
+ bool GetStringProperty(const DBPropertyInfo& /*property_info*/,
+ const Slice& /*property*/, std::string* /*value*/) {
+ return false;
+ }
+
+ bool GetMapProperty(const DBPropertyInfo& /*property_info*/,
+ const Slice& /*property*/,
+ std::map<std::string, std::string>* /*value*/) {
+ return false;
+ }
+
+ bool GetIntProperty(const DBPropertyInfo& /*property_info*/, uint64_t* /*value*/,
+ DBImpl* /*db*/) const {
+ return false;
+ }
+
+ bool GetIntPropertyOutOfMutex(const DBPropertyInfo& /*property_info*/,
+ Version* /*version*/, uint64_t* /*value*/) const {
+ return false;
+ }
+};
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/job_context.h b/storage/rocksdb/rocksdb/db/job_context.h
new file mode 100644
index 00000000..31ff26c3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/job_context.h
@@ -0,0 +1,219 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "db/log_writer.h"
+#include "db/column_family.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MemTable;
+struct SuperVersion;
+
+struct SuperVersionContext {
+ struct WriteStallNotification {
+ WriteStallInfo write_stall_info;
+ const ImmutableCFOptions* immutable_cf_options;
+ };
+
+ autovector<SuperVersion*> superversions_to_free;
+#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION
+ autovector<WriteStallNotification> write_stall_notifications;
+#endif
+ std::unique_ptr<SuperVersion>
+ new_superversion; // if nullptr no new superversion
+
+ explicit SuperVersionContext(bool create_superversion = false)
+ : new_superversion(create_superversion ? new SuperVersion() : nullptr) {}
+
+ explicit SuperVersionContext(SuperVersionContext&& other)
+ : superversions_to_free(std::move(other.superversions_to_free)),
+#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION
+ write_stall_notifications(std::move(other.write_stall_notifications)),
+#endif
+ new_superversion(std::move(other.new_superversion)) {
+ }
+
+ void NewSuperVersion() {
+ new_superversion = std::unique_ptr<SuperVersion>(new SuperVersion());
+ }
+
+ inline bool HaveSomethingToDelete() const {
+#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION
+ return !superversions_to_free.empty() ||
+ !write_stall_notifications.empty();
+#else
+ return !superversions_to_free.empty();
+#endif
+ }
+
+ void PushWriteStallNotification(
+ WriteStallCondition old_cond, WriteStallCondition new_cond,
+ const std::string& name, const ImmutableCFOptions* ioptions) {
+#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
+ WriteStallNotification notif;
+ notif.write_stall_info.cf_name = name;
+ notif.write_stall_info.condition.prev = old_cond;
+ notif.write_stall_info.condition.cur = new_cond;
+ notif.immutable_cf_options = ioptions;
+ write_stall_notifications.push_back(notif);
+#else
+ (void)old_cond;
+ (void)new_cond;
+ (void)name;
+ (void)ioptions;
+#endif // !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
+ }
+
+ void Clean() {
+#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
+ // notify listeners on changed write stall conditions
+ for (auto& notif : write_stall_notifications) {
+ for (auto& listener : notif.immutable_cf_options->listeners) {
+ listener->OnStallConditionsChanged(notif.write_stall_info);
+ }
+ }
+ write_stall_notifications.clear();
+#endif // !ROCKSDB_LITE
+ // free superversions
+ for (auto s : superversions_to_free) {
+ delete s;
+ }
+ superversions_to_free.clear();
+ }
+
+ ~SuperVersionContext() {
+#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION
+ assert(write_stall_notifications.empty());
+#endif
+ assert(superversions_to_free.empty());
+ }
+};
+
+struct JobContext {
+ inline bool HaveSomethingToDelete() const {
+ return full_scan_candidate_files.size() || sst_delete_files.size() ||
+ log_delete_files.size() || manifest_delete_files.size();
+ }
+
+ inline bool HaveSomethingToClean() const {
+ bool sv_have_sth = false;
+ for (const auto& sv_ctx : superversion_contexts) {
+ if (sv_ctx.HaveSomethingToDelete()) {
+ sv_have_sth = true;
+ break;
+ }
+ }
+ return memtables_to_free.size() > 0 || logs_to_free.size() > 0 ||
+ sv_have_sth;
+ }
+
+ // Structure to store information for candidate files to delete.
+ struct CandidateFileInfo {
+ std::string file_name;
+ std::string file_path;
+ CandidateFileInfo(std::string name, std::string path)
+ : file_name(std::move(name)), file_path(std::move(path)) {}
+ bool operator==(const CandidateFileInfo& other) const {
+ return file_name == other.file_name &&
+ file_path == other.file_path;
+ }
+ };
+
+ // Unique job id
+ int job_id;
+
+ // a list of all files that we'll consider deleting
+ // (every once in a while this is filled up with all files
+ // in the DB directory)
+ // (filled only if we're doing full scan)
+ std::vector<CandidateFileInfo> full_scan_candidate_files;
+
+ // the list of all live sst files that cannot be deleted
+ std::vector<FileDescriptor> sst_live;
+
+ // a list of sst files that we need to delete
+ std::vector<ObsoleteFileInfo> sst_delete_files;
+
+ // a list of log files that we need to delete
+ std::vector<uint64_t> log_delete_files;
+
+ // a list of log files that we need to preserve during full purge since they
+ // will be reused later
+ std::vector<uint64_t> log_recycle_files;
+
+ // a list of manifest files that we need to delete
+ std::vector<std::string> manifest_delete_files;
+
+ // a list of memtables to be free
+ autovector<MemTable*> memtables_to_free;
+
+ // contexts for installing superversions for multiple column families
+ std::vector<SuperVersionContext> superversion_contexts;
+
+ autovector<log::Writer*> logs_to_free;
+
+ // the current manifest_file_number, log_number and prev_log_number
+ // that corresponds to the set of files in 'live'.
+ uint64_t manifest_file_number;
+ uint64_t pending_manifest_file_number;
+ uint64_t log_number;
+ uint64_t prev_log_number;
+
+ uint64_t min_pending_output = 0;
+ uint64_t prev_total_log_size = 0;
+ size_t num_alive_log_files = 0;
+ uint64_t size_log_to_delete = 0;
+
+ // Snapshot taken before flush/compaction job.
+ std::unique_ptr<ManagedSnapshot> job_snapshot;
+
+ explicit JobContext(int _job_id, bool create_superversion = false) {
+ job_id = _job_id;
+ manifest_file_number = 0;
+ pending_manifest_file_number = 0;
+ log_number = 0;
+ prev_log_number = 0;
+ superversion_contexts.emplace_back(
+ SuperVersionContext(create_superversion));
+ }
+
+ // For non-empty JobContext Clean() has to be called at least once before
+ // before destruction (see asserts in ~JobContext()). Should be called with
+ // unlocked DB mutex. Destructor doesn't call Clean() to avoid accidentally
+ // doing potentially slow Clean() with locked DB mutex.
+ void Clean() {
+ // free superversions
+ for (auto& sv_context : superversion_contexts) {
+ sv_context.Clean();
+ }
+ // free pending memtables
+ for (auto m : memtables_to_free) {
+ delete m;
+ }
+ for (auto l : logs_to_free) {
+ delete l;
+ }
+
+ memtables_to_free.clear();
+ logs_to_free.clear();
+ job_snapshot.reset();
+ }
+
+ ~JobContext() {
+ assert(memtables_to_free.size() == 0);
+ assert(logs_to_free.size() == 0);
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/listener_test.cc b/storage/rocksdb/rocksdb/db/listener_test.cc
new file mode 100644
index 00000000..eb1a08a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/listener_test.cc
@@ -0,0 +1,1042 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_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 "logging/logging.h"
+#include "memtable/hash_linklist_rep.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 "table/block_based/block_based_table_factory.h"
+#include "table/plain/plain_table_factory.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"
+
+#ifndef ROCKSDB_LITE
+
+namespace ROCKSDB_NAMESPACE {
+
+class EventListenerTest : public DBTestBase {
+ public:
+ EventListenerTest() : DBTestBase("/listener_test") {}
+
+ 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<std::mutex> 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<std::vector<FileMetaData>> 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<DB*> 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<TestPropertiesCollectorFactory>());
+
+ TestCompactionListener* listener = new TestCompactionListener(this);
+ options.listeners.emplace_back(listener);
+ std::vector<std::string> 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));
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[i],
+ nullptr, nullptr));
+ 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;
+ }
+ 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);
+
+#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<ThreadStatus> 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<std::vector<FileMetaData>> files_by_level;
+ 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<std::string> flushed_column_family_names_;
+ std::vector<DB*> 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<std::string> cf_names = {
+ "pikachu", "ilya", "muromec", "dobrynia",
+ "nikitich", "alyosha", "popovich"};
+ options.table_properties_collector_factories.push_back(
+ std::make_shared<TestPropertiesCollectorFactory>());
+ 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));
+ dbfull()->TEST_WaitForFlushMemTable();
+ 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
+ TestFlushListener* listener = new TestFlushListener(options.env, this);
+ options.listeners.emplace_back(listener);
+ options.table_properties_collector_factories.push_back(
+ std::make_shared<TestPropertiesCollectorFactory>());
+ std::vector<std::string> 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')));
+ for (int i = 1; i < 8; ++i) {
+ ASSERT_OK(Flush(i));
+ 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, 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<TestPropertiesCollectorFactory>());
+ std::vector<TestFlushListener*> 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<std::string> 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<DB*> dbs;
+ std::vector<std::vector<ColumnFamilyHandle *>> vec_handles;
+
+ for (int d = 0; d < kNumDBs; ++d) {
+ ASSERT_OK(DestroyDB(dbname_ + ToString(d), options));
+ DB* db;
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(DB::Open(options, dbname_ + ToString(d), &db));
+ for (size_t c = 0; c < cf_names.size(); ++c) {
+ ColumnFamilyHandle* handle;
+ 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]));
+ reinterpret_cast<DBImpl*>(dbs[d])->TEST_WaitForFlushMemTable();
+ }
+ }
+
+ 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<TestPropertiesCollectorFactory>());
+
+ 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<int>(cf_meta.file_count) < kSlowdownTrigger * 10;
+ ++i) {
+ Put(1, ToString(i), std::string(10000, 'x'), WriteOptions());
+ FlushOptions fo;
+ fo.allow_write_stall = true;
+ db_->Flush(fo, handles_[1]);
+ db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
+ }
+ ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
+}
+
+class TestCompactionReasonListener : public EventListener {
+ public:
+ void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
+ std::lock_guard<std::mutex> lock(mutex_);
+ compaction_reasons_.push_back(ci.compaction_reason);
+ }
+
+ std::vector<CompactionReason> compaction_reasons_;
+ std::mutex mutex_;
+};
+
+TEST_F(EventListenerTest, CompactionReasonLevel) {
+ Options options;
+ options.env = CurrentOptions().env;
+ options.create_if_missing = true;
+ options.memtable_factory.reset(
+ new SpecialSkipListFactory(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);
+ }
+ 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
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ options.max_bytes_for_level_base = 1;
+ Close();
+ listener->compaction_reasons_.clear();
+ Reopen(options);
+
+ 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);
+
+ 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(
+ new SpecialSkipListFactory(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);
+ }
+ 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);
+ }
+ 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);
+
+ 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(
+ new SpecialSkipListFactory(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);
+ }
+ 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 TestEnv : public EnvWrapper {
+ public:
+ TestEnv() : EnvWrapper(Env::Default()) {}
+
+ void SetStatus(Status s) { status_ = s; }
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") {
+ if (!status_.ok()) {
+ return status_;
+ }
+ }
+ return Env::Default()->NewWritableFile(fname, result, options);
+ }
+
+ private:
+ Status 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);
+ if (info.status.ok()) {
+ 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]++;
+ }
+ }
+ }
+
+ TestEnv test_env;
+ int started_[2];
+ int finished_[2];
+ int failure_[2];
+};
+
+TEST_F(EventListenerTest, TableFileCreationListenersTest) {
+ auto listener = std::make_shared<TableFileCreationListener>();
+ Options options;
+ options.create_if_missing = true;
+ options.listeners.push_back(listener);
+ options.env = &listener->test_env;
+ DestroyAndReopen(options);
+
+ ASSERT_OK(Put("foo", "aaa"));
+ ASSERT_OK(Put("bar", "bbb"));
+ ASSERT_OK(Flush());
+ dbfull()->TEST_WaitForFlushMemTable();
+ listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
+
+ ASSERT_OK(Put("foo", "aaa1"));
+ ASSERT_OK(Put("bar", "bbb1"));
+ listener->test_env.SetStatus(Status::NotSupported("not supported"));
+ ASSERT_NOK(Flush());
+ listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0);
+ listener->test_env.SetStatus(Status::OK());
+
+ Reopen(options);
+ ASSERT_OK(Put("foo", "aaa2"));
+ ASSERT_OK(Put("bar", "bbb2"));
+ ASSERT_OK(Flush());
+ dbfull()->TEST_WaitForFlushMemTable();
+ listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
+
+ const Slice kRangeStart = "a";
+ const Slice kRangeEnd = "z";
+ dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
+ dbfull()->TEST_WaitForCompact();
+ listener->CheckAndResetCounters(0, 0, 0, 1, 1, 0);
+
+ ASSERT_OK(Put("foo", "aaa3"));
+ ASSERT_OK(Put("bar", "bbb3"));
+ ASSERT_OK(Flush());
+ listener->test_env.SetStatus(Status::NotSupported("not supported"));
+ dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
+ dbfull()->TEST_WaitForCompact();
+ listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
+}
+
+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<MemTableSealedListener>();
+ Options options;
+ 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<std::string> cfs_;
+ int counter;
+
+ public:
+ explicit ColumnFamilyHandleDeletionStartedListener(
+ const std::vector<std::string>& 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<std::string> cfs{"pikachu", "eevee", "Mewtwo"};
+ auto listener =
+ std::make_shared<ColumnFamilyHandleDeletionStartedListener>(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_->no_slowdown_ = false;
+ }
+ ++counter_;
+ }
+
+ int counter() { return counter_; }
+};
+
+TEST_F(EventListenerTest, BackgroundErrorListenerFailedFlushTest) {
+ auto listener = std::make_shared<BackgroundErrorListener>(env_);
+ Options options;
+ options.create_if_missing = true;
+ options.env = env_;
+ options.listeners.push_back(listener);
+ options.memtable_factory.reset(new SpecialSkipListFactory(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_->no_slowdown_ = true;
+
+ 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<BackgroundErrorListener>(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(new SpecialSkipListFactory(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_->no_slowdown_ = true;
+ 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);
+ }
+
+ void OnFileReadFinish(const FileOperationInfo& info) override {
+ ++file_reads_;
+ if (info.status.ok()) {
+ ++file_reads_success_;
+ }
+ ReportDuration(info);
+ }
+
+ void OnFileWriteFinish(const FileOperationInfo& info) override {
+ ++file_writes_;
+ if (info.status.ok()) {
+ ++file_writes_success_;
+ }
+ ReportDuration(info);
+ }
+
+ bool ShouldBeNotifiedOnFileIO() override { return true; }
+
+ std::atomic<size_t> file_reads_;
+ std::atomic<size_t> file_reads_success_;
+ std::atomic<size_t> file_writes_;
+ std::atomic<size_t> file_writes_success_;
+
+ private:
+ void ReportDuration(const FileOperationInfo& info) const {
+ auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ info.finish_timestamp - info.start_timestamp);
+ ASSERT_GT(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);
+
+ DestroyAndReopen(options);
+ ASSERT_OK(Put("foo", "aaa"));
+ dbfull()->Flush(FlushOptions());
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_GE(listener->file_writes_.load(),
+ listener->file_writes_success_.load());
+ ASSERT_GT(listener->file_writes_.load(), 0);
+ Close();
+
+ Reopen(options);
+ ASSERT_GE(listener->file_reads_.load(), listener->file_reads_success_.load());
+ ASSERT_GT(listener->file_reads_.load(), 0);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/log_format.h b/storage/rocksdb/rocksdb/db/log_format.h
new file mode 100644
index 00000000..c22e2b6b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_format.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Log format information shared by reader and writer.
+// See ../doc/log_format.txt for more detail.
+
+#pragma once
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace log {
+
+enum RecordType {
+ // Zero is reserved for preallocated files
+ kZeroType = 0,
+ kFullType = 1,
+
+ // For fragments
+ kFirstType = 2,
+ kMiddleType = 3,
+ kLastType = 4,
+
+ // For recycled log files
+ kRecyclableFullType = 5,
+ kRecyclableFirstType = 6,
+ kRecyclableMiddleType = 7,
+ kRecyclableLastType = 8,
+};
+static const int kMaxRecordType = kRecyclableLastType;
+
+static const unsigned int kBlockSize = 32768;
+
+// Header is checksum (4 bytes), length (2 bytes), type (1 byte)
+static const int kHeaderSize = 4 + 2 + 1;
+
+// Recyclable header is checksum (4 bytes), length (2 bytes), type (1 byte),
+// log number (4 bytes).
+static const int kRecyclableHeaderSize = 4 + 2 + 1 + 4;
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/log_reader.cc b/storage/rocksdb/rocksdb/db/log_reader.cc
new file mode 100644
index 00000000..c60a814b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_reader.cc
@@ -0,0 +1,624 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include "file/sequence_file_reader.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+#include "util/util.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace log {
+
+Reader::Reporter::~Reporter() {
+}
+
+Reader::Reader(std::shared_ptr<Logger> info_log,
+ std::unique_ptr<SequentialFileReader>&& _file,
+ Reporter* reporter, bool checksum, uint64_t log_num)
+ : info_log_(info_log),
+ file_(std::move(_file)),
+ reporter_(reporter),
+ checksum_(checksum),
+ backing_store_(new char[kBlockSize]),
+ buffer_(),
+ eof_(false),
+ read_error_(false),
+ eof_offset_(0),
+ last_record_offset_(0),
+ end_of_buffer_offset_(0),
+ log_number_(log_num),
+ recycled_(false) {}
+
+Reader::~Reader() {
+ delete[] backing_store_;
+}
+
+// For kAbsoluteConsistency, on clean shutdown we don't expect any error
+// in the log files. For other modes, we can ignore only incomplete records
+// in the last log file, which are presumably due to a write in progress
+// during restart (or from log recycling).
+//
+// TODO krad: Evaluate if we need to move to a more strict mode where we
+// restrict the inconsistency to only the last log
+bool Reader::ReadRecord(Slice* record, std::string* scratch,
+ WALRecoveryMode wal_recovery_mode) {
+ scratch->clear();
+ record->clear();
+ bool in_fragmented_record = false;
+ // Record offset of the logical record that we're reading
+ // 0 is a dummy value to make compilers happy
+ uint64_t prospective_record_offset = 0;
+
+ Slice fragment;
+ while (true) {
+ uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
+ size_t drop_size = 0;
+ const unsigned int record_type = ReadPhysicalRecord(&fragment, &drop_size);
+ switch (record_type) {
+ case kFullType:
+ case kRecyclableFullType:
+ if (in_fragmented_record && !scratch->empty()) {
+ // Handle bug in earlier versions of log::Writer where
+ // it could emit an empty kFirstType record at the tail end
+ // of a block followed by a kFullType or kFirstType record
+ // at the beginning of the next block.
+ ReportCorruption(scratch->size(), "partial record without end(1)");
+ }
+ prospective_record_offset = physical_record_offset;
+ scratch->clear();
+ *record = fragment;
+ last_record_offset_ = prospective_record_offset;
+ return true;
+
+ case kFirstType:
+ case kRecyclableFirstType:
+ if (in_fragmented_record && !scratch->empty()) {
+ // Handle bug in earlier versions of log::Writer where
+ // it could emit an empty kFirstType record at the tail end
+ // of a block followed by a kFullType or kFirstType record
+ // at the beginning of the next block.
+ ReportCorruption(scratch->size(), "partial record without end(2)");
+ }
+ prospective_record_offset = physical_record_offset;
+ scratch->assign(fragment.data(), fragment.size());
+ in_fragmented_record = true;
+ break;
+
+ case kMiddleType:
+ case kRecyclableMiddleType:
+ if (!in_fragmented_record) {
+ ReportCorruption(fragment.size(),
+ "missing start of fragmented record(1)");
+ } else {
+ scratch->append(fragment.data(), fragment.size());
+ }
+ break;
+
+ case kLastType:
+ case kRecyclableLastType:
+ if (!in_fragmented_record) {
+ ReportCorruption(fragment.size(),
+ "missing start of fragmented record(2)");
+ } else {
+ scratch->append(fragment.data(), fragment.size());
+ *record = Slice(*scratch);
+ last_record_offset_ = prospective_record_offset;
+ return true;
+ }
+ break;
+
+ case kBadHeader:
+ if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
+ // in clean shutdown we don't expect any error in the log files
+ ReportCorruption(drop_size, "truncated header");
+ }
+ FALLTHROUGH_INTENDED;
+
+ case kEof:
+ if (in_fragmented_record) {
+ if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
+ // in clean shutdown we don't expect any error in the log files
+ ReportCorruption(scratch->size(), "error reading trailing data");
+ }
+ // This can be caused by the writer dying immediately after
+ // writing a physical record but before completing the next; don't
+ // treat it as a corruption, just ignore the entire logical record.
+ scratch->clear();
+ }
+ return false;
+
+ case kOldRecord:
+ if (wal_recovery_mode != WALRecoveryMode::kSkipAnyCorruptedRecords) {
+ // Treat a record from a previous instance of the log as EOF.
+ if (in_fragmented_record) {
+ if (wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency) {
+ // in clean shutdown we don't expect any error in the log files
+ ReportCorruption(scratch->size(), "error reading trailing data");
+ }
+ // This can be caused by the writer dying immediately after
+ // writing a physical record but before completing the next; don't
+ // treat it as a corruption, just ignore the entire logical record.
+ scratch->clear();
+ }
+ return false;
+ }
+ FALLTHROUGH_INTENDED;
+
+ case kBadRecord:
+ if (in_fragmented_record) {
+ ReportCorruption(scratch->size(), "error in middle of record");
+ in_fragmented_record = false;
+ scratch->clear();
+ }
+ break;
+
+ case kBadRecordLen:
+ case kBadRecordChecksum:
+ if (recycled_ &&
+ wal_recovery_mode ==
+ WALRecoveryMode::kTolerateCorruptedTailRecords) {
+ scratch->clear();
+ return false;
+ }
+ if (record_type == kBadRecordLen) {
+ ReportCorruption(drop_size, "bad record length");
+ } else {
+ ReportCorruption(drop_size, "checksum mismatch");
+ }
+ if (in_fragmented_record) {
+ ReportCorruption(scratch->size(), "error in middle of record");
+ in_fragmented_record = false;
+ scratch->clear();
+ }
+ break;
+
+ default: {
+ char buf[40];
+ snprintf(buf, sizeof(buf), "unknown record type %u", record_type);
+ ReportCorruption(
+ (fragment.size() + (in_fragmented_record ? scratch->size() : 0)),
+ buf);
+ in_fragmented_record = false;
+ scratch->clear();
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+uint64_t Reader::LastRecordOffset() {
+ return last_record_offset_;
+}
+
+void Reader::UnmarkEOF() {
+ if (read_error_) {
+ return;
+ }
+ eof_ = false;
+ if (eof_offset_ == 0) {
+ return;
+ }
+ UnmarkEOFInternal();
+}
+
+void Reader::UnmarkEOFInternal() {
+ // If the EOF was in the middle of a block (a partial block was read) we have
+ // to read the rest of the block as ReadPhysicalRecord can only read full
+ // blocks and expects the file position indicator to be aligned to the start
+ // of a block.
+ //
+ // consumed_bytes + buffer_size() + remaining == kBlockSize
+
+ size_t consumed_bytes = eof_offset_ - buffer_.size();
+ size_t remaining = kBlockSize - eof_offset_;
+
+ // backing_store_ is used to concatenate what is left in buffer_ and
+ // the remainder of the block. If buffer_ already uses backing_store_,
+ // we just append the new data.
+ if (buffer_.data() != backing_store_ + consumed_bytes) {
+ // Buffer_ does not use backing_store_ for storage.
+ // Copy what is left in buffer_ to backing_store.
+ memmove(backing_store_ + consumed_bytes, buffer_.data(), buffer_.size());
+ }
+
+ Slice read_buffer;
+ Status status = file_->Read(remaining, &read_buffer,
+ backing_store_ + eof_offset_);
+
+ size_t added = read_buffer.size();
+ end_of_buffer_offset_ += added;
+
+ if (!status.ok()) {
+ if (added > 0) {
+ ReportDrop(added, status);
+ }
+
+ read_error_ = true;
+ return;
+ }
+
+ if (read_buffer.data() != backing_store_ + eof_offset_) {
+ // Read did not write to backing_store_
+ memmove(backing_store_ + eof_offset_, read_buffer.data(),
+ read_buffer.size());
+ }
+
+ buffer_ = Slice(backing_store_ + consumed_bytes,
+ eof_offset_ + added - consumed_bytes);
+
+ if (added < remaining) {
+ eof_ = true;
+ eof_offset_ += added;
+ } else {
+ eof_offset_ = 0;
+ }
+}
+
+void Reader::ReportCorruption(size_t bytes, const char* reason) {
+ ReportDrop(bytes, Status::Corruption(reason));
+}
+
+void Reader::ReportDrop(size_t bytes, const Status& reason) {
+ if (reporter_ != nullptr) {
+ reporter_->Corruption(bytes, reason);
+ }
+}
+
+bool Reader::ReadMore(size_t* drop_size, int *error) {
+ if (!eof_ && !read_error_) {
+ // Last read was a full read, so this is a trailer to skip
+ buffer_.clear();
+ Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
+ end_of_buffer_offset_ += buffer_.size();
+ if (!status.ok()) {
+ buffer_.clear();
+ ReportDrop(kBlockSize, status);
+ read_error_ = true;
+ *error = kEof;
+ return false;
+ } else if (buffer_.size() < static_cast<size_t>(kBlockSize)) {
+ eof_ = true;
+ eof_offset_ = buffer_.size();
+ }
+ return true;
+ } else {
+ // Note that if buffer_ is non-empty, we have a truncated header at the
+ // end of the file, which can be caused by the writer crashing in the
+ // middle of writing the header. Unless explicitly requested we don't
+ // considering this an error, just report EOF.
+ if (buffer_.size()) {
+ *drop_size = buffer_.size();
+ buffer_.clear();
+ *error = kBadHeader;
+ return false;
+ }
+ buffer_.clear();
+ *error = kEof;
+ return false;
+ }
+}
+
+unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
+ while (true) {
+ // We need at least the minimum header size
+ if (buffer_.size() < static_cast<size_t>(kHeaderSize)) {
+ // the default value of r is meaningless because ReadMore will overwrite
+ // it if it returns false; in case it returns true, the return value will
+ // not be used anyway
+ int r = kEof;
+ if (!ReadMore(drop_size, &r)) {
+ return r;
+ }
+ continue;
+ }
+
+ // Parse the header
+ const char* header = buffer_.data();
+ const uint32_t a = static_cast<uint32_t>(header[4]) & 0xff;
+ const uint32_t b = static_cast<uint32_t>(header[5]) & 0xff;
+ const unsigned int type = header[6];
+ const uint32_t length = a | (b << 8);
+ int header_size = kHeaderSize;
+ if (type >= kRecyclableFullType && type <= kRecyclableLastType) {
+ if (end_of_buffer_offset_ - buffer_.size() == 0) {
+ recycled_ = true;
+ }
+ header_size = kRecyclableHeaderSize;
+ // We need enough for the larger header
+ if (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
+ int r = kEof;
+ if (!ReadMore(drop_size, &r)) {
+ return r;
+ }
+ continue;
+ }
+ const uint32_t log_num = DecodeFixed32(header + 7);
+ if (log_num != log_number_) {
+ return kOldRecord;
+ }
+ }
+ if (header_size + length > buffer_.size()) {
+ *drop_size = buffer_.size();
+ buffer_.clear();
+ if (!eof_) {
+ return kBadRecordLen;
+ }
+ // If the end of the file has been reached without reading |length|
+ // bytes of payload, assume the writer died in the middle of writing the
+ // record. Don't report a corruption unless requested.
+ if (*drop_size) {
+ return kBadHeader;
+ }
+ return kEof;
+ }
+
+ if (type == kZeroType && length == 0) {
+ // Skip zero length record without reporting any drops since
+ // such records are produced by the mmap based writing code in
+ // env_posix.cc that preallocates file regions.
+ // NOTE: this should never happen in DB written by new RocksDB versions,
+ // since we turn off mmap writes to manifest and log files
+ buffer_.clear();
+ return kBadRecord;
+ }
+
+ // Check crc
+ if (checksum_) {
+ uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header));
+ uint32_t actual_crc = crc32c::Value(header + 6, length + header_size - 6);
+ if (actual_crc != expected_crc) {
+ // Drop the rest of the buffer since "length" itself may have
+ // been corrupted and if we trust it, we could find some
+ // fragment of a real log record that just happens to look
+ // like a valid log record.
+ *drop_size = buffer_.size();
+ buffer_.clear();
+ return kBadRecordChecksum;
+ }
+ }
+
+ buffer_.remove_prefix(header_size + length);
+
+ *result = Slice(header + header_size, length);
+ return type;
+ }
+}
+
+bool FragmentBufferedReader::ReadRecord(Slice* record, std::string* scratch,
+ WALRecoveryMode /*unused*/) {
+ assert(record != nullptr);
+ assert(scratch != nullptr);
+ record->clear();
+ scratch->clear();
+
+ uint64_t prospective_record_offset = 0;
+ uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
+ size_t drop_size = 0;
+ unsigned int fragment_type_or_err = 0; // Initialize to make compiler happy
+ Slice fragment;
+ while (TryReadFragment(&fragment, &drop_size, &fragment_type_or_err)) {
+ switch (fragment_type_or_err) {
+ case kFullType:
+ case kRecyclableFullType:
+ if (in_fragmented_record_ && !fragments_.empty()) {
+ ReportCorruption(fragments_.size(), "partial record without end(1)");
+ }
+ fragments_.clear();
+ *record = fragment;
+ prospective_record_offset = physical_record_offset;
+ last_record_offset_ = prospective_record_offset;
+ in_fragmented_record_ = false;
+ return true;
+
+ case kFirstType:
+ case kRecyclableFirstType:
+ if (in_fragmented_record_ || !fragments_.empty()) {
+ ReportCorruption(fragments_.size(), "partial record without end(2)");
+ }
+ prospective_record_offset = physical_record_offset;
+ fragments_.assign(fragment.data(), fragment.size());
+ in_fragmented_record_ = true;
+ break;
+
+ case kMiddleType:
+ case kRecyclableMiddleType:
+ if (!in_fragmented_record_) {
+ ReportCorruption(fragment.size(),
+ "missing start of fragmented record(1)");
+ } else {
+ fragments_.append(fragment.data(), fragment.size());
+ }
+ break;
+
+ case kLastType:
+ case kRecyclableLastType:
+ if (!in_fragmented_record_) {
+ ReportCorruption(fragment.size(),
+ "missing start of fragmented record(2)");
+ } else {
+ fragments_.append(fragment.data(), fragment.size());
+ scratch->assign(fragments_.data(), fragments_.size());
+ fragments_.clear();
+ *record = Slice(*scratch);
+ last_record_offset_ = prospective_record_offset;
+ in_fragmented_record_ = false;
+ return true;
+ }
+ break;
+
+ case kBadHeader:
+ case kBadRecord:
+ case kEof:
+ case kOldRecord:
+ if (in_fragmented_record_) {
+ ReportCorruption(fragments_.size(), "error in middle of record");
+ in_fragmented_record_ = false;
+ fragments_.clear();
+ }
+ break;
+
+ case kBadRecordChecksum:
+ if (recycled_) {
+ fragments_.clear();
+ return false;
+ }
+ ReportCorruption(drop_size, "checksum mismatch");
+ if (in_fragmented_record_) {
+ ReportCorruption(fragments_.size(), "error in middle of record");
+ in_fragmented_record_ = false;
+ fragments_.clear();
+ }
+ break;
+
+ default: {
+ char buf[40];
+ snprintf(buf, sizeof(buf), "unknown record type %u",
+ fragment_type_or_err);
+ ReportCorruption(
+ fragment.size() + (in_fragmented_record_ ? fragments_.size() : 0),
+ buf);
+ in_fragmented_record_ = false;
+ fragments_.clear();
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+void FragmentBufferedReader::UnmarkEOF() {
+ if (read_error_) {
+ return;
+ }
+ eof_ = false;
+ UnmarkEOFInternal();
+}
+
+bool FragmentBufferedReader::TryReadMore(size_t* drop_size, int* error) {
+ if (!eof_ && !read_error_) {
+ // Last read was a full read, so this is a trailer to skip
+ buffer_.clear();
+ Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
+ end_of_buffer_offset_ += buffer_.size();
+ if (!status.ok()) {
+ buffer_.clear();
+ ReportDrop(kBlockSize, status);
+ read_error_ = true;
+ *error = kEof;
+ return false;
+ } else if (buffer_.size() < static_cast<size_t>(kBlockSize)) {
+ eof_ = true;
+ eof_offset_ = buffer_.size();
+ TEST_SYNC_POINT_CALLBACK(
+ "FragmentBufferedLogReader::TryReadMore:FirstEOF", nullptr);
+ }
+ return true;
+ } else if (!read_error_) {
+ UnmarkEOF();
+ }
+ if (!read_error_) {
+ return true;
+ }
+ *error = kEof;
+ *drop_size = buffer_.size();
+ if (buffer_.size() > 0) {
+ *error = kBadHeader;
+ }
+ buffer_.clear();
+ return false;
+}
+
+// return true if the caller should process the fragment_type_or_err.
+bool FragmentBufferedReader::TryReadFragment(
+ Slice* fragment, size_t* drop_size, unsigned int* fragment_type_or_err) {
+ assert(fragment != nullptr);
+ assert(drop_size != nullptr);
+ assert(fragment_type_or_err != nullptr);
+
+ while (buffer_.size() < static_cast<size_t>(kHeaderSize)) {
+ size_t old_size = buffer_.size();
+ int error = kEof;
+ if (!TryReadMore(drop_size, &error)) {
+ *fragment_type_or_err = error;
+ return false;
+ } else if (old_size == buffer_.size()) {
+ return false;
+ }
+ }
+ const char* header = buffer_.data();
+ const uint32_t a = static_cast<uint32_t>(header[4]) & 0xff;
+ const uint32_t b = static_cast<uint32_t>(header[5]) & 0xff;
+ const unsigned int type = header[6];
+ const uint32_t length = a | (b << 8);
+ int header_size = kHeaderSize;
+ if (type >= kRecyclableFullType && type <= kRecyclableLastType) {
+ if (end_of_buffer_offset_ - buffer_.size() == 0) {
+ recycled_ = true;
+ }
+ header_size = kRecyclableHeaderSize;
+ while (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
+ size_t old_size = buffer_.size();
+ int error = kEof;
+ if (!TryReadMore(drop_size, &error)) {
+ *fragment_type_or_err = error;
+ return false;
+ } else if (old_size == buffer_.size()) {
+ return false;
+ }
+ }
+ const uint32_t log_num = DecodeFixed32(header + 7);
+ if (log_num != log_number_) {
+ *fragment_type_or_err = kOldRecord;
+ return true;
+ }
+ }
+
+ while (header_size + length > buffer_.size()) {
+ size_t old_size = buffer_.size();
+ int error = kEof;
+ if (!TryReadMore(drop_size, &error)) {
+ *fragment_type_or_err = error;
+ return false;
+ } else if (old_size == buffer_.size()) {
+ return false;
+ }
+ }
+
+ if (type == kZeroType && length == 0) {
+ buffer_.clear();
+ *fragment_type_or_err = kBadRecord;
+ return true;
+ }
+
+ if (checksum_) {
+ uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header));
+ uint32_t actual_crc = crc32c::Value(header + 6, length + header_size - 6);
+ if (actual_crc != expected_crc) {
+ *drop_size = buffer_.size();
+ buffer_.clear();
+ *fragment_type_or_err = kBadRecordChecksum;
+ return true;
+ }
+ }
+
+ buffer_.remove_prefix(header_size + length);
+
+ *fragment = Slice(header + header_size, length);
+ *fragment_type_or_err = type;
+ return true;
+}
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/log_reader.h b/storage/rocksdb/rocksdb/db/log_reader.h
new file mode 100644
index 00000000..293ae957
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_reader.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <stdint.h>
+
+#include "db/log_format.h"
+#include "file/sequence_file_reader.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+class Logger;
+
+namespace log {
+
+/**
+ * Reader is a general purpose log stream reader implementation. The actual job
+ * of reading from the device is implemented by the SequentialFile interface.
+ *
+ * Please see Writer for details on the file and record layout.
+ */
+class Reader {
+ public:
+ // Interface for reporting errors.
+ class Reporter {
+ public:
+ virtual ~Reporter();
+
+ // Some corruption was detected. "size" is the approximate number
+ // of bytes dropped due to the corruption.
+ virtual void Corruption(size_t bytes, const Status& status) = 0;
+ };
+
+ // Create a reader that will return log records from "*file".
+ // "*file" must remain live while this Reader is in use.
+ //
+ // If "reporter" is non-nullptr, it is notified whenever some data is
+ // dropped due to a detected corruption. "*reporter" must remain
+ // live while this Reader is in use.
+ //
+ // If "checksum" is true, verify checksums if available.
+ Reader(std::shared_ptr<Logger> info_log,
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ std::unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
+ bool checksum, uint64_t log_num);
+ // No copying allowed
+ Reader(const Reader&) = delete;
+ void operator=(const Reader&) = delete;
+
+ virtual ~Reader();
+
+ // Read the next record into *record. Returns true if read
+ // successfully, false if we hit end of the input. May use
+ // "*scratch" as temporary storage. The contents filled in *record
+ // will only be valid until the next mutating operation on this
+ // reader or the next mutation to *scratch.
+ virtual bool ReadRecord(Slice* record, std::string* scratch,
+ WALRecoveryMode wal_recovery_mode =
+ WALRecoveryMode::kTolerateCorruptedTailRecords);
+
+ // Returns the physical offset of the last record returned by ReadRecord.
+ //
+ // Undefined before the first call to ReadRecord.
+ uint64_t LastRecordOffset();
+
+ // returns true if the reader has encountered an eof condition.
+ bool IsEOF() {
+ return eof_;
+ }
+
+ // returns true if the reader has encountered read error.
+ bool hasReadError() const { return read_error_; }
+
+ // when we know more data has been written to the file. we can use this
+ // function to force the reader to look again in the file.
+ // Also aligns the file position indicator to the start of the next block
+ // by reading the rest of the data from the EOF position to the end of the
+ // block that was partially read.
+ virtual void UnmarkEOF();
+
+ SequentialFileReader* file() { return file_.get(); }
+
+ Reporter* GetReporter() const { return reporter_; }
+
+ uint64_t GetLogNumber() const { return log_number_; }
+
+ size_t GetReadOffset() const {
+ return static_cast<size_t>(end_of_buffer_offset_);
+ }
+
+ protected:
+ std::shared_ptr<Logger> info_log_;
+ const std::unique_ptr<SequentialFileReader> file_;
+ Reporter* const reporter_;
+ bool const checksum_;
+ char* const backing_store_;
+
+ // Internal state variables used for reading records
+ Slice buffer_;
+ bool eof_; // Last Read() indicated EOF by returning < kBlockSize
+ bool read_error_; // Error occurred while reading from file
+
+ // Offset of the file position indicator within the last block when an
+ // EOF was detected.
+ size_t eof_offset_;
+
+ // Offset of the last record returned by ReadRecord.
+ uint64_t last_record_offset_;
+ // Offset of the first location past the end of buffer_.
+ uint64_t end_of_buffer_offset_;
+
+ // which log number this is
+ uint64_t const log_number_;
+
+ // Whether this is a recycled log file
+ bool recycled_;
+
+ // Extend record types with the following special values
+ enum {
+ kEof = kMaxRecordType + 1,
+ // Returned whenever we find an invalid physical record.
+ // Currently there are three situations in which this happens:
+ // * The record has an invalid CRC (ReadPhysicalRecord reports a drop)
+ // * The record is a 0-length record (No drop is reported)
+ kBadRecord = kMaxRecordType + 2,
+ // Returned when we fail to read a valid header.
+ kBadHeader = kMaxRecordType + 3,
+ // Returned when we read an old record from a previous user of the log.
+ kOldRecord = kMaxRecordType + 4,
+ // Returned when we get a bad record length
+ kBadRecordLen = kMaxRecordType + 5,
+ // Returned when we get a bad record checksum
+ kBadRecordChecksum = kMaxRecordType + 6,
+ };
+
+ // Return type, or one of the preceding special values
+ unsigned int ReadPhysicalRecord(Slice* result, size_t* drop_size);
+
+ // Read some more
+ bool ReadMore(size_t* drop_size, int *error);
+
+ void UnmarkEOFInternal();
+
+ // Reports dropped bytes to the reporter.
+ // buffer_ must be updated to remove the dropped bytes prior to invocation.
+ void ReportCorruption(size_t bytes, const char* reason);
+ void ReportDrop(size_t bytes, const Status& reason);
+};
+
+class FragmentBufferedReader : public Reader {
+ public:
+ FragmentBufferedReader(std::shared_ptr<Logger> info_log,
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ std::unique_ptr<SequentialFileReader>&& _file,
+ Reporter* reporter, bool checksum, uint64_t log_num)
+ : Reader(info_log, std::move(_file), reporter, checksum, log_num),
+ fragments_(),
+ in_fragmented_record_(false) {}
+ ~FragmentBufferedReader() override {}
+ bool ReadRecord(Slice* record, std::string* scratch,
+ WALRecoveryMode wal_recovery_mode =
+ WALRecoveryMode::kTolerateCorruptedTailRecords) override;
+ void UnmarkEOF() override;
+
+ private:
+ std::string fragments_;
+ bool in_fragmented_record_;
+
+ bool TryReadFragment(Slice* result, size_t* drop_size,
+ unsigned int* fragment_type_or_err);
+
+ bool TryReadMore(size_t* drop_size, int* error);
+
+ // No copy allowed
+ FragmentBufferedReader(const FragmentBufferedReader&);
+ void operator=(const FragmentBufferedReader&);
+};
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/log_test.cc b/storage/rocksdb/rocksdb/db/log_test.cc
new file mode 100644
index 00000000..849b89d8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_test.cc
@@ -0,0 +1,928 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "env/composite_env_wrapper.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"
+
+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<int, bool>
+// 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<std::tuple<int, bool>> {
+ private:
+ class StringSource : public SequentialFile {
+ 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) {}
+
+ Status Read(size_t n, Slice* result, char* scratch) 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 Status::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 Status::OK();
+ }
+
+ Status Skip(uint64_t n) override {
+ if (n > contents_.size()) {
+ contents_.clear();
+ return Status::NotFound("in-memory file skipepd past end");
+ }
+
+ contents_.remove_prefix(n);
+
+ return Status::OK();
+ }
+ };
+
+ inline StringSource* GetStringSourceFromLegacyReader(
+ SequentialFileReader* reader) {
+ LegacySequentialFileWrapper* file =
+ static_cast<LegacySequentialFileWrapper*>(reader->file());
+ return static_cast<StringSource*>(file->target());
+ }
+
+ 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() {
+ auto dest = test::GetStringSinkFromLegacyWriter(writer_.file());
+ assert(dest);
+ return dest->contents_;
+ }
+
+ const std::string& dest_contents() const {
+ auto dest = test::GetStringSinkFromLegacyWriter(writer_.file());
+ assert(dest);
+ return dest->contents_;
+ }
+
+ void reset_source_contents() {
+ auto src = GetStringSourceFromLegacyReader(reader_->file());
+ assert(src);
+ src->contents_ = dest_contents();
+ }
+
+ Slice reader_contents_;
+ std::unique_ptr<WritableFileWriter> dest_holder_;
+ std::unique_ptr<SequentialFileReader> source_holder_;
+ ReportCollector report_;
+ Writer writer_;
+ std::unique_ptr<Reader> reader_;
+
+ protected:
+ bool allow_retry_read_;
+
+ public:
+ LogTest()
+ : reader_contents_(),
+ dest_holder_(test::GetWritableFileWriter(
+ new test::StringSink(&reader_contents_), "" /* don't care */)),
+ source_holder_(test::GetSequentialFileReader(
+ new StringSource(reader_contents_, !std::get<1>(GetParam())),
+ "" /* file name */)),
+ writer_(std::move(dest_holder_), 123, std::get<0>(GetParam())),
+ allow_retry_read_(std::get<1>(GetParam())) {
+ if (allow_retry_read_) {
+ reader_.reset(new FragmentBufferedReader(
+ nullptr, std::move(source_holder_), &report_, true /* checksum */,
+ 123 /* log_number */));
+ } else {
+ reader_.reset(new Reader(nullptr, std::move(source_holder_), &report_,
+ true /* checksum */, 123 /* log_number */));
+ }
+ }
+
+ Slice* get_reader_contents() { return &reader_contents_; }
+
+ void Write(const std::string& msg) {
+ 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;
+ ret = reader_->ReadRecord(&record, &scratch, wal_recovery_mode);
+ if (ret) {
+ 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) {
+ auto dest = test::GetStringSinkFromLegacyWriter(writer_.file());
+ assert(dest);
+ dest->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) {
+ auto src = GetStringSourceFromLegacyReader(reader_->file());
+ src->force_error_ = true;
+ src->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) {
+ auto src = GetStringSourceFromLegacyReader(reader_->file());
+ src->force_eof_ = true;
+ src->force_eof_position_ = position;
+ }
+
+ void UnmarkEOF() {
+ auto src = GetStringSourceFromLegacyReader(reader_->file());
+ src->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 header"));
+}
+
+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<char>(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<char>(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<char>(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<char>(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 headerCorruption: "
+ "error reading trailing data"));
+}
+
+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<WritableFileWriter> dest_holder(test::GetWritableFileWriter(
+ new test::OverwritingStringSink(get_reader_contents()),
+ "" /* don't care */));
+ Writer recycle_writer(std::move(dest_holder), 123, true);
+ recycle_writer.AddRecord(Slice("foooo"));
+ 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());
+}
+
+INSTANTIATE_TEST_CASE_P(bool, LogTest,
+ ::testing::Values(std::make_tuple(0, false),
+ std::make_tuple(0, true),
+ std::make_tuple(1, false),
+ std::make_tuple(1, true)));
+
+class RetriableLogTest : public ::testing::TestWithParam<int> {
+ 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_;
+ std::unique_ptr<WritableFileWriter> dest_holder_;
+ std::unique_ptr<Writer> log_writer_;
+ Env* env_;
+ EnvOptions env_options_;
+ const std::string test_dir_;
+ const std::string log_file_;
+ std::unique_ptr<WritableFileWriter> writer_;
+ std::unique_ptr<SequentialFileReader> reader_;
+ ReportCollector report_;
+ std::unique_ptr<FragmentBufferedReader> log_reader_;
+
+ public:
+ RetriableLogTest()
+ : contents_(),
+ dest_holder_(nullptr),
+ 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) {}
+
+ Status SetupTestEnv() {
+ dest_holder_.reset(test::GetWritableFileWriter(
+ new test::StringSink(&contents_), "" /* file name */));
+ assert(dest_holder_ != nullptr);
+ log_writer_.reset(new Writer(std::move(dest_holder_), 123, GetParam()));
+ assert(log_writer_ != nullptr);
+
+ Status s;
+ s = env_->CreateDirIfMissing(test_dir_);
+ std::unique_ptr<WritableFile> writable_file;
+ if (s.ok()) {
+ s = env_->NewWritableFile(log_file_, &writable_file, env_options_);
+ }
+ if (s.ok()) {
+ writer_.reset(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), log_file_,
+ env_options_));
+ assert(writer_ != nullptr);
+ }
+ std::unique_ptr<SequentialFile> seq_file;
+ if (s.ok()) {
+ s = env_->NewSequentialFile(log_file_, &seq_file, env_options_);
+ }
+ if (s.ok()) {
+ reader_.reset(new SequentialFileReader(
+ NewLegacySequentialFileWrapper(seq_file), log_file_));
+ assert(reader_ != nullptr);
+ log_reader_.reset(new FragmentBufferedReader(
+ nullptr, std::move(reader_), &report_, true /* checksum */,
+ 123 /* log_number */));
+ assert(log_reader_ != nullptr);
+ }
+ return s;
+ }
+
+ std::string contents() {
+ auto file = test::GetStringSinkFromLegacyWriter(log_writer_->file());
+ assert(file != nullptr);
+ return file->contents_;
+ }
+
+ void Encode(const std::string& msg) { log_writer_->AddRecord(Slice(msg)); }
+
+ void Write(const Slice& data) {
+ writer_->Append(data);
+ 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<int> 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<int> 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));
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/log_writer.cc b/storage/rocksdb/rocksdb/db/log_writer.cc
new file mode 100644
index 00000000..0222ee2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_writer.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_writer.h"
+
+#include <stdint.h>
+#include "file/writable_file_writer.h"
+#include "rocksdb/env.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace log {
+
+Writer::Writer(std::unique_ptr<WritableFileWriter>&& dest, uint64_t log_number,
+ bool recycle_log_files, bool manual_flush)
+ : dest_(std::move(dest)),
+ block_offset_(0),
+ log_number_(log_number),
+ recycle_log_files_(recycle_log_files),
+ manual_flush_(manual_flush) {
+ for (int i = 0; i <= kMaxRecordType; i++) {
+ char t = static_cast<char>(i);
+ type_crc_[i] = crc32c::Value(&t, 1);
+ }
+}
+
+Writer::~Writer() {
+ if (dest_) {
+ WriteBuffer();
+ }
+}
+
+Status Writer::WriteBuffer() { return dest_->Flush(); }
+
+Status Writer::Close() {
+ Status s;
+ if (dest_) {
+ s = dest_->Close();
+ dest_.reset();
+ }
+ return s;
+}
+
+Status Writer::AddRecord(const Slice& slice) {
+ const char* ptr = slice.data();
+ size_t left = slice.size();
+
+ // Header size varies depending on whether we are recycling or not.
+ const int header_size =
+ recycle_log_files_ ? kRecyclableHeaderSize : kHeaderSize;
+
+ // Fragment the record if necessary and emit it. Note that if slice
+ // is empty, we still want to iterate once to emit a single
+ // zero-length record
+ Status s;
+ bool begin = true;
+ do {
+ const int64_t leftover = kBlockSize - block_offset_;
+ assert(leftover >= 0);
+ if (leftover < header_size) {
+ // Switch to a new block
+ if (leftover > 0) {
+ // Fill the trailer (literal below relies on kHeaderSize and
+ // kRecyclableHeaderSize being <= 11)
+ assert(header_size <= 11);
+ s = dest_->Append(Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ static_cast<size_t>(leftover)));
+ if (!s.ok()) {
+ break;
+ }
+ }
+ block_offset_ = 0;
+ }
+
+ // Invariant: we never leave < header_size bytes in a block.
+ assert(static_cast<int64_t>(kBlockSize - block_offset_) >= header_size);
+
+ const size_t avail = kBlockSize - block_offset_ - header_size;
+ const size_t fragment_length = (left < avail) ? left : avail;
+
+ RecordType type;
+ const bool end = (left == fragment_length);
+ if (begin && end) {
+ type = recycle_log_files_ ? kRecyclableFullType : kFullType;
+ } else if (begin) {
+ type = recycle_log_files_ ? kRecyclableFirstType : kFirstType;
+ } else if (end) {
+ type = recycle_log_files_ ? kRecyclableLastType : kLastType;
+ } else {
+ type = recycle_log_files_ ? kRecyclableMiddleType : kMiddleType;
+ }
+
+ s = EmitPhysicalRecord(type, ptr, fragment_length);
+ ptr += fragment_length;
+ left -= fragment_length;
+ begin = false;
+ } while (s.ok() && left > 0);
+
+ if (s.ok()) {
+ if (!manual_flush_) {
+ s = dest_->Flush();
+ }
+ }
+
+ return s;
+}
+
+bool Writer::TEST_BufferIsEmpty() { return dest_->TEST_BufferIsEmpty(); }
+
+Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
+ assert(n <= 0xffff); // Must fit in two bytes
+
+ size_t header_size;
+ char buf[kRecyclableHeaderSize];
+
+ // Format the header
+ buf[4] = static_cast<char>(n & 0xff);
+ buf[5] = static_cast<char>(n >> 8);
+ buf[6] = static_cast<char>(t);
+
+ uint32_t crc = type_crc_[t];
+ if (t < kRecyclableFullType) {
+ // Legacy record format
+ assert(block_offset_ + kHeaderSize + n <= kBlockSize);
+ header_size = kHeaderSize;
+ } else {
+ // Recyclable record format
+ assert(block_offset_ + kRecyclableHeaderSize + n <= kBlockSize);
+ header_size = kRecyclableHeaderSize;
+
+ // Only encode low 32-bits of the 64-bit log number. This means
+ // we will fail to detect an old record if we recycled a log from
+ // ~4 billion logs ago, but that is effectively impossible, and
+ // even if it were we'dbe far more likely to see a false positive
+ // on the 32-bit CRC.
+ EncodeFixed32(buf + 7, static_cast<uint32_t>(log_number_));
+ crc = crc32c::Extend(crc, buf + 7, 4);
+ }
+
+ // Compute the crc of the record type and the payload.
+ crc = crc32c::Extend(crc, ptr, n);
+ crc = crc32c::Mask(crc); // Adjust for storage
+ EncodeFixed32(buf, crc);
+
+ // Write the header and the payload
+ Status s = dest_->Append(Slice(buf, header_size));
+ if (s.ok()) {
+ s = dest_->Append(Slice(ptr, n));
+ }
+ block_offset_ += header_size + n;
+ return s;
+}
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/log_writer.h b/storage/rocksdb/rocksdb/db/log_writer.h
new file mode 100644
index 00000000..a7f952ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/log_writer.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+
+#include <memory>
+
+#include "db/log_format.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WritableFileWriter;
+
+namespace log {
+
+/**
+ * Writer is a general purpose log stream writer. It provides an append-only
+ * abstraction for writing data. The details of the how the data is written is
+ * handled by the WriteableFile sub-class implementation.
+ *
+ * File format:
+ *
+ * File is broken down into variable sized records. The format of each record
+ * is described below.
+ * +-----+-------------+--+----+----------+------+-- ... ----+
+ * File | r0 | r1 |P | r2 | r3 | r4 | |
+ * +-----+-------------+--+----+----------+------+-- ... ----+
+ * <--- kBlockSize ------>|<-- kBlockSize ------>|
+ * rn = variable size records
+ * P = Padding
+ *
+ * Data is written out in kBlockSize chunks. If next record does not fit
+ * into the space left, the leftover space will be padded with \0.
+ *
+ * Legacy record format:
+ *
+ * +---------+-----------+-----------+--- ... ---+
+ * |CRC (4B) | Size (2B) | Type (1B) | Payload |
+ * +---------+-----------+-----------+--- ... ---+
+ *
+ * CRC = 32bit hash computed over the record type and payload using CRC
+ * Size = Length of the payload data
+ * Type = Type of record
+ * (kZeroType, kFullType, kFirstType, kLastType, kMiddleType )
+ * The type is used to group a bunch of records together to represent
+ * blocks that are larger than kBlockSize
+ * Payload = Byte stream as long as specified by the payload size
+ *
+ * Recyclable record format:
+ *
+ * +---------+-----------+-----------+----------------+--- ... ---+
+ * |CRC (4B) | Size (2B) | Type (1B) | Log number (4B)| Payload |
+ * +---------+-----------+-----------+----------------+--- ... ---+
+ *
+ * Same as above, with the addition of
+ * Log number = 32bit log file number, so that we can distinguish between
+ * records written by the most recent log writer vs a previous one.
+ */
+class Writer {
+ public:
+ // Create a writer that will append data to "*dest".
+ // "*dest" must be initially empty.
+ // "*dest" must remain live while this Writer is in use.
+ explicit Writer(std::unique_ptr<WritableFileWriter>&& dest,
+ uint64_t log_number, bool recycle_log_files,
+ bool manual_flush = false);
+ // No copying allowed
+ Writer(const Writer&) = delete;
+ void operator=(const Writer&) = delete;
+
+ ~Writer();
+
+ Status AddRecord(const Slice& slice);
+
+ WritableFileWriter* file() { return dest_.get(); }
+ const WritableFileWriter* file() const { return dest_.get(); }
+
+ uint64_t get_log_number() const { return log_number_; }
+
+ Status WriteBuffer();
+
+ Status Close();
+
+ bool TEST_BufferIsEmpty();
+
+ private:
+ std::unique_ptr<WritableFileWriter> dest_;
+ size_t block_offset_; // Current offset in block
+ uint64_t log_number_;
+ bool recycle_log_files_;
+
+ // crc32c values for all supported record types. These are
+ // pre-computed to reduce the overhead of computing the crc of the
+ // record type stored in the header.
+ uint32_t type_crc_[kMaxRecordType + 1];
+
+ Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);
+
+ // If true, it does not flush after each write. Instead it relies on the upper
+ // layer to manually does the flush by calling ::WriteBuffer()
+ bool manual_flush_;
+};
+
+} // namespace log
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.cc b/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.cc
new file mode 100644
index 00000000..ff98155c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/logs_with_prep_tracker.h"
+
+#include "port/likely.h"
+
+namespace ROCKSDB_NAMESPACE {
+void LogsWithPrepTracker::MarkLogAsHavingPrepSectionFlushed(uint64_t log) {
+ assert(log != 0);
+ std::lock_guard<std::mutex> lock(prepared_section_completed_mutex_);
+ auto it = prepared_section_completed_.find(log);
+ if (UNLIKELY(it == prepared_section_completed_.end())) {
+ prepared_section_completed_[log] = 1;
+ } else {
+ it->second += 1;
+ }
+}
+
+void LogsWithPrepTracker::MarkLogAsContainingPrepSection(uint64_t log) {
+ assert(log != 0);
+ std::lock_guard<std::mutex> lock(logs_with_prep_mutex_);
+
+ auto rit = logs_with_prep_.rbegin();
+ bool updated = false;
+ // Most probably the last log is the one that is being marked for
+ // having a prepare section; so search from the end.
+ for (; rit != logs_with_prep_.rend() && rit->log >= log; ++rit) {
+ if (rit->log == log) {
+ rit->cnt++;
+ updated = true;
+ break;
+ }
+ }
+ if (!updated) {
+ // We are either at the start, or at a position with rit->log < log
+ logs_with_prep_.insert(rit.base(), {log, 1});
+ }
+}
+
+uint64_t LogsWithPrepTracker::FindMinLogContainingOutstandingPrep() {
+ std::lock_guard<std::mutex> lock(logs_with_prep_mutex_);
+ auto it = logs_with_prep_.begin();
+ // start with the smallest log
+ for (; it != logs_with_prep_.end();) {
+ auto min_log = it->log;
+ {
+ std::lock_guard<std::mutex> lock2(prepared_section_completed_mutex_);
+ auto completed_it = prepared_section_completed_.find(min_log);
+ if (completed_it == prepared_section_completed_.end() ||
+ completed_it->second < it->cnt) {
+ return min_log;
+ }
+ assert(completed_it != prepared_section_completed_.end() &&
+ completed_it->second == it->cnt);
+ prepared_section_completed_.erase(completed_it);
+ }
+ // erase from beginning in vector is not efficient but this function is not
+ // on the fast path.
+ it = logs_with_prep_.erase(it);
+ }
+ // no such log found
+ return 0;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.h b/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.h
new file mode 100644
index 00000000..86c88012
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/logs_with_prep_tracker.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <cassert>
+#include <cstdlib>
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This class is used to track the log files with outstanding prepare entries.
+class LogsWithPrepTracker {
+ public:
+ // Called when a transaction prepared in `log` has been committed or aborted.
+ void MarkLogAsHavingPrepSectionFlushed(uint64_t log);
+ // Called when a transaction is prepared in `log`.
+ void MarkLogAsContainingPrepSection(uint64_t log);
+ // Return the earliest log file with outstanding prepare entries.
+ uint64_t FindMinLogContainingOutstandingPrep();
+ size_t TEST_PreparedSectionCompletedSize() {
+ return prepared_section_completed_.size();
+ }
+ size_t TEST_LogsWithPrepSize() { return logs_with_prep_.size(); }
+
+ private:
+ // REQUIRES: logs_with_prep_mutex_ held
+ //
+ // sorted list of log numbers still containing prepared data.
+ // this is used by FindObsoleteFiles to determine which
+ // flushed logs we must keep around because they still
+ // contain prepared data which has not been committed or rolled back
+ struct LogCnt {
+ uint64_t log; // the log number
+ uint64_t cnt; // number of prepared sections in the log
+ };
+ std::vector<LogCnt> logs_with_prep_;
+ std::mutex logs_with_prep_mutex_;
+
+ // REQUIRES: prepared_section_completed_mutex_ held
+ //
+ // to be used in conjunction with logs_with_prep_.
+ // once a transaction with data in log L is committed or rolled back
+ // rather than updating logs_with_prep_ directly we keep track of that
+ // in prepared_section_completed_ which maps LOG -> instance_count. This helps
+ // avoiding contention between a commit thread and the prepare threads.
+ //
+ // when trying to determine the minimum log still active we first
+ // consult logs_with_prep_. while that root value maps to
+ // an equal value in prepared_section_completed_ we erase the log from
+ // both logs_with_prep_ and prepared_section_completed_.
+ std::unordered_map<uint64_t, uint64_t> prepared_section_completed_;
+ std::mutex prepared_section_completed_mutex_;
+
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/lookup_key.h b/storage/rocksdb/rocksdb/db/lookup_key.h
new file mode 100644
index 00000000..51e5daed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/lookup_key.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <utility>
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A helper class useful for DBImpl::Get()
+class LookupKey {
+ public:
+ // Initialize *this for looking up user_key at a snapshot with
+ // the specified sequence number.
+ LookupKey(const Slice& _user_key, SequenceNumber sequence,
+ const Slice* ts = nullptr);
+
+ ~LookupKey();
+
+ // Return a key suitable for lookup in a MemTable.
+ Slice memtable_key() const {
+ return Slice(start_, static_cast<size_t>(end_ - start_));
+ }
+
+ // Return an internal key (suitable for passing to an internal iterator)
+ Slice internal_key() const {
+ return Slice(kstart_, static_cast<size_t>(end_ - kstart_));
+ }
+
+ // Return the user key
+ Slice user_key() const {
+ return Slice(kstart_, static_cast<size_t>(end_ - kstart_ - 8));
+ }
+
+ private:
+ // We construct a char array of the form:
+ // klength varint32 <-- start_
+ // userkey char[klength] <-- kstart_
+ // tag uint64
+ // <-- end_
+ // The array is a suitable MemTable key.
+ // The suffix starting with "userkey" can be used as an InternalKey.
+ const char* start_;
+ const char* kstart_;
+ const char* end_;
+ char space_[200]; // Avoid allocation for short keys
+
+ // No copying allowed
+ LookupKey(const LookupKey&);
+ void operator=(const LookupKey&);
+};
+
+inline LookupKey::~LookupKey() {
+ if (start_ != space_) delete[] start_;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/malloc_stats.cc b/storage/rocksdb/rocksdb/db/malloc_stats.cc
new file mode 100644
index 00000000..12824e51
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/malloc_stats.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/malloc_stats.h"
+
+#ifndef ROCKSDB_LITE
+#include <memory>
+#include <string.h>
+
+#include "port/jemalloc_helper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_JEMALLOC
+
+typedef struct {
+ char* cur;
+ char* end;
+} MallocStatus;
+
+static void GetJemallocStatus(void* mstat_arg, const char* status) {
+ MallocStatus* mstat = reinterpret_cast<MallocStatus*>(mstat_arg);
+ size_t status_len = status ? strlen(status) : 0;
+ size_t buf_size = (size_t)(mstat->end - mstat->cur);
+ if (!status_len || status_len > buf_size) {
+ return;
+ }
+
+ snprintf(mstat->cur, buf_size, "%s", status);
+ mstat->cur += status_len;
+}
+void DumpMallocStats(std::string* stats) {
+ if (!HasJemalloc()) {
+ return;
+ }
+ MallocStatus mstat;
+ const unsigned int kMallocStatusLen = 1000000;
+ std::unique_ptr<char[]> buf{new char[kMallocStatusLen + 1]};
+ mstat.cur = buf.get();
+ mstat.end = buf.get() + kMallocStatusLen;
+ malloc_stats_print(GetJemallocStatus, &mstat, "");
+ stats->append(buf.get());
+}
+#else
+void DumpMallocStats(std::string*) {}
+#endif // ROCKSDB_JEMALLOC
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/malloc_stats.h b/storage/rocksdb/rocksdb/db/malloc_stats.h
new file mode 100644
index 00000000..18aff3ad
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/malloc_stats.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void DumpMallocStats(std::string*);
+
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/manual_compaction_test.cc b/storage/rocksdb/rocksdb/db/manual_compaction_test.cc
new file mode 100644
index 00000000..22cd919b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/manual_compaction_test.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <iostream>
+#include <sstream>
+#include <cstdlib>
+
+#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 namespace ROCKSDB_NAMESPACE;
+
+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_cbug_test");
+ DestroyDB(dbname_, ROCKSDB_NAMESPACE::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"; }
+};
+
+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 = kCompactionStyleLevel;
+ } else { // universal compaction
+ options.compaction_style = kCompactionStyleUniversal;
+ }
+ options.create_if_missing = true;
+ options.compression = ROCKSDB_NAMESPACE::kNoCompression;
+ options.compaction_filter = new DestroyAllCompactionFilter();
+ ASSERT_OK(DB::Open(options, dbname_, &db));
+
+ db->Put(WriteOptions(), Slice("key1"), Slice("destroy"));
+ db->Put(WriteOptions(), Slice("key2"), Slice("destroy"));
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ db->Put(WriteOptions(), Slice("key4"), Slice("destroy"));
+
+ Slice key4("key4");
+ 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;
+ 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.
+ ROCKSDB_NAMESPACE::DB* db;
+ ROCKSDB_NAMESPACE::Options db_options;
+ db_options.write_buffer_size = 1024;
+ db_options.create_if_missing = true;
+ db_options.compression = ROCKSDB_NAMESPACE::kNoCompression;
+ ASSERT_OK(ROCKSDB_NAMESPACE::DB::Open(db_options, dbname_, &db));
+
+ // create first key range
+ ROCKSDB_NAMESPACE::WriteBatch batch;
+ for (int i = 0; i < kNumKeys; i++) {
+ batch.Put(Key1(i), "value for range 1 key");
+ }
+ ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
+
+ // create second key range
+ batch.Clear();
+ for (int i = 0; i < kNumKeys; i++) {
+ batch.Put(Key2(i), "value for range 2 key");
+ }
+ ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
+
+ // delete second key range
+ batch.Clear();
+ for (int i = 0; i < kNumKeys; i++) {
+ batch.Delete(Key2(i));
+ }
+ ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
+
+ // compact database
+ std::string start_key = Key1(0);
+ std::string end_key = Key1(kNumKeys - 1);
+ ROCKSDB_NAMESPACE::Slice least(start_key.data(), start_key.size());
+ ROCKSDB_NAMESPACE::Slice greatest(end_key.data(), end_key.size());
+
+ // commenting out the line below causes the example to work correctly
+ db->CompactRange(CompactRangeOptions(), &least, &greatest);
+
+ // count the keys
+ ROCKSDB_NAMESPACE::Iterator* iter =
+ db->NewIterator(ROCKSDB_NAMESPACE::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;
+ DestroyDB(dbname_, ROCKSDB_NAMESPACE::Options());
+}
+
+} // anonymous namespace
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/memtable.cc b/storage/rocksdb/rocksdb/db/memtable.cc
new file mode 100644
index 00000000..45483ea0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/memtable.cc
@@ -0,0 +1,1122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/memtable.h"
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <memory>
+#include "db/dbformat.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/read_callback.h"
+#include "memory/arena.h"
+#include "memory/memory_usage.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/statistics.h"
+#include "port/port.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/internal_iterator.h"
+#include "table/iterator_wrapper.h"
+#include "table/merging_iterator.h"
+#include "util/autovector.h"
+#include "util/coding.h"
+#include "util/mutexlock.h"
+#include "util/util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ImmutableMemTableOptions::ImmutableMemTableOptions(
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options)
+ : arena_block_size(mutable_cf_options.arena_block_size),
+ memtable_prefix_bloom_bits(
+ static_cast<uint32_t>(
+ static_cast<double>(mutable_cf_options.write_buffer_size) *
+ mutable_cf_options.memtable_prefix_bloom_size_ratio) *
+ 8u),
+ memtable_huge_page_size(mutable_cf_options.memtable_huge_page_size),
+ memtable_whole_key_filtering(
+ mutable_cf_options.memtable_whole_key_filtering),
+ inplace_update_support(ioptions.inplace_update_support),
+ inplace_update_num_locks(mutable_cf_options.inplace_update_num_locks),
+ inplace_callback(ioptions.inplace_callback),
+ max_successive_merges(mutable_cf_options.max_successive_merges),
+ statistics(ioptions.statistics),
+ merge_operator(ioptions.merge_operator),
+ info_log(ioptions.info_log) {}
+
+MemTable::MemTable(const InternalKeyComparator& cmp,
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options,
+ WriteBufferManager* write_buffer_manager,
+ SequenceNumber latest_seq, uint32_t column_family_id)
+ : comparator_(cmp),
+ moptions_(ioptions, mutable_cf_options),
+ refs_(0),
+ kArenaBlockSize(OptimizeBlockSize(moptions_.arena_block_size)),
+ mem_tracker_(write_buffer_manager),
+ arena_(moptions_.arena_block_size,
+ (write_buffer_manager != nullptr &&
+ (write_buffer_manager->enabled() ||
+ write_buffer_manager->cost_to_cache()))
+ ? &mem_tracker_
+ : nullptr,
+ mutable_cf_options.memtable_huge_page_size),
+ table_(ioptions.memtable_factory->CreateMemTableRep(
+ comparator_, &arena_, mutable_cf_options.prefix_extractor.get(),
+ ioptions.info_log, column_family_id)),
+ range_del_table_(SkipListFactory().CreateMemTableRep(
+ comparator_, &arena_, nullptr /* transform */, ioptions.info_log,
+ column_family_id)),
+ is_range_del_table_empty_(true),
+ data_size_(0),
+ num_entries_(0),
+ num_deletes_(0),
+ write_buffer_size_(mutable_cf_options.write_buffer_size),
+ flush_in_progress_(false),
+ flush_completed_(false),
+ file_number_(0),
+ first_seqno_(0),
+ earliest_seqno_(latest_seq),
+ creation_seq_(latest_seq),
+ mem_next_logfile_number_(0),
+ min_prep_log_referenced_(0),
+ locks_(moptions_.inplace_update_support
+ ? moptions_.inplace_update_num_locks
+ : 0),
+ prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
+ flush_state_(FLUSH_NOT_REQUESTED),
+ env_(ioptions.env),
+ insert_with_hint_prefix_extractor_(
+ ioptions.memtable_insert_with_hint_prefix_extractor),
+ oldest_key_time_(std::numeric_limits<uint64_t>::max()),
+ atomic_flush_seqno_(kMaxSequenceNumber),
+ approximate_memory_usage_(0) {
+ UpdateFlushState();
+ // something went wrong if we need to flush before inserting anything
+ assert(!ShouldScheduleFlush());
+
+ // use bloom_filter_ for both whole key and prefix bloom filter
+ if ((prefix_extractor_ || moptions_.memtable_whole_key_filtering) &&
+ moptions_.memtable_prefix_bloom_bits > 0) {
+ bloom_filter_.reset(
+ new DynamicBloom(&arena_, moptions_.memtable_prefix_bloom_bits,
+ 6 /* hard coded 6 probes */,
+ moptions_.memtable_huge_page_size, ioptions.info_log));
+ }
+}
+
+MemTable::~MemTable() {
+ mem_tracker_.FreeMem();
+ assert(refs_ == 0);
+}
+
+size_t MemTable::ApproximateMemoryUsage() {
+ autovector<size_t> usages = {
+ arena_.ApproximateMemoryUsage(), table_->ApproximateMemoryUsage(),
+ range_del_table_->ApproximateMemoryUsage(),
+ ROCKSDB_NAMESPACE::ApproximateMemoryUsage(insert_hints_)};
+ size_t total_usage = 0;
+ for (size_t usage : usages) {
+ // If usage + total_usage >= kMaxSizet, return kMaxSizet.
+ // the following variation is to avoid numeric overflow.
+ if (usage >= port::kMaxSizet - total_usage) {
+ return port::kMaxSizet;
+ }
+ total_usage += usage;
+ }
+ approximate_memory_usage_.store(total_usage, std::memory_order_relaxed);
+ // otherwise, return the actual usage
+ return total_usage;
+}
+
+bool MemTable::ShouldFlushNow() {
+ size_t write_buffer_size = write_buffer_size_.load(std::memory_order_relaxed);
+ // In a lot of times, we cannot allocate arena blocks that exactly matches the
+ // buffer size. Thus we have to decide if we should over-allocate or
+ // under-allocate.
+ // This constant variable can be interpreted as: if we still have more than
+ // "kAllowOverAllocationRatio * kArenaBlockSize" space left, we'd try to over
+ // allocate one more block.
+ const double kAllowOverAllocationRatio = 0.6;
+
+ // If arena still have room for new block allocation, we can safely say it
+ // shouldn't flush.
+ auto allocated_memory = table_->ApproximateMemoryUsage() +
+ range_del_table_->ApproximateMemoryUsage() +
+ arena_.MemoryAllocatedBytes();
+
+ approximate_memory_usage_.store(allocated_memory, std::memory_order_relaxed);
+
+ // if we can still allocate one more block without exceeding the
+ // over-allocation ratio, then we should not flush.
+ if (allocated_memory + kArenaBlockSize <
+ write_buffer_size + kArenaBlockSize * kAllowOverAllocationRatio) {
+ return false;
+ }
+
+ // if user keeps adding entries that exceeds write_buffer_size, we need to
+ // flush earlier even though we still have much available memory left.
+ if (allocated_memory >
+ write_buffer_size + kArenaBlockSize * kAllowOverAllocationRatio) {
+ return true;
+ }
+
+ // In this code path, Arena has already allocated its "last block", which
+ // means the total allocatedmemory size is either:
+ // (1) "moderately" over allocated the memory (no more than `0.6 * arena
+ // block size`. Or,
+ // (2) the allocated memory is less than write buffer size, but we'll stop
+ // here since if we allocate a new arena block, we'll over allocate too much
+ // more (half of the arena block size) memory.
+ //
+ // In either case, to avoid over-allocate, the last block will stop allocation
+ // when its usage reaches a certain ratio, which we carefully choose "0.75
+ // full" as the stop condition because it addresses the following issue with
+ // great simplicity: What if the next inserted entry's size is
+ // bigger than AllocatedAndUnused()?
+ //
+ // The answer is: if the entry size is also bigger than 0.25 *
+ // kArenaBlockSize, a dedicated block will be allocated for it; otherwise
+ // arena will anyway skip the AllocatedAndUnused() and allocate a new, empty
+ // and regular block. In either case, we *overly* over-allocated.
+ //
+ // Therefore, setting the last block to be at most "0.75 full" avoids both
+ // cases.
+ //
+ // NOTE: the average percentage of waste space of this approach can be counted
+ // as: "arena block size * 0.25 / write buffer size". User who specify a small
+ // write buffer size and/or big arena block size may suffer.
+ return arena_.AllocatedAndUnused() < kArenaBlockSize / 4;
+}
+
+void MemTable::UpdateFlushState() {
+ auto state = flush_state_.load(std::memory_order_relaxed);
+ if (state == FLUSH_NOT_REQUESTED && ShouldFlushNow()) {
+ // ignore CAS failure, because that means somebody else requested
+ // a flush
+ flush_state_.compare_exchange_strong(state, FLUSH_REQUESTED,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+}
+
+void MemTable::UpdateOldestKeyTime() {
+ uint64_t oldest_key_time = oldest_key_time_.load(std::memory_order_relaxed);
+ if (oldest_key_time == std::numeric_limits<uint64_t>::max()) {
+ int64_t current_time = 0;
+ auto s = env_->GetCurrentTime(&current_time);
+ if (s.ok()) {
+ assert(current_time >= 0);
+ // If fail, the timestamp is already set.
+ oldest_key_time_.compare_exchange_strong(
+ oldest_key_time, static_cast<uint64_t>(current_time),
+ std::memory_order_relaxed, std::memory_order_relaxed);
+ }
+ }
+}
+
+int MemTable::KeyComparator::operator()(const char* prefix_len_key1,
+ const char* prefix_len_key2) const {
+ // Internal keys are encoded as length-prefixed strings.
+ Slice k1 = GetLengthPrefixedSlice(prefix_len_key1);
+ Slice k2 = GetLengthPrefixedSlice(prefix_len_key2);
+ return comparator.CompareKeySeq(k1, k2);
+}
+
+int MemTable::KeyComparator::operator()(const char* prefix_len_key,
+ const KeyComparator::DecodedType& key)
+ const {
+ // Internal keys are encoded as length-prefixed strings.
+ Slice a = GetLengthPrefixedSlice(prefix_len_key);
+ return comparator.CompareKeySeq(a, key);
+}
+
+void MemTableRep::InsertConcurrently(KeyHandle /*handle*/) {
+#ifndef ROCKSDB_LITE
+ throw std::runtime_error("concurrent insert not supported");
+#else
+ abort();
+#endif
+}
+
+Slice MemTableRep::UserKey(const char* key) const {
+ Slice slice = GetLengthPrefixedSlice(key);
+ return Slice(slice.data(), slice.size() - 8);
+}
+
+KeyHandle MemTableRep::Allocate(const size_t len, char** buf) {
+ *buf = allocator_->Allocate(len);
+ return static_cast<KeyHandle>(*buf);
+}
+
+// Encode a suitable internal key target for "target" and return it.
+// Uses *scratch as scratch space, and the returned pointer will point
+// into this scratch space.
+const char* EncodeKey(std::string* scratch, const Slice& target) {
+ scratch->clear();
+ PutVarint32(scratch, static_cast<uint32_t>(target.size()));
+ scratch->append(target.data(), target.size());
+ return scratch->data();
+}
+
+class MemTableIterator : public InternalIterator {
+ public:
+ MemTableIterator(const MemTable& mem, const ReadOptions& read_options,
+ Arena* arena, bool use_range_del_table = false)
+ : bloom_(nullptr),
+ prefix_extractor_(mem.prefix_extractor_),
+ comparator_(mem.comparator_),
+ valid_(false),
+ arena_mode_(arena != nullptr),
+ value_pinned_(
+ !mem.GetImmutableMemTableOptions()->inplace_update_support) {
+ if (use_range_del_table) {
+ iter_ = mem.range_del_table_->GetIterator(arena);
+ } else if (prefix_extractor_ != nullptr && !read_options.total_order_seek &&
+ !read_options.auto_prefix_mode) {
+ // Auto prefix mode is not implemented in memtable yet.
+ bloom_ = mem.bloom_filter_.get();
+ iter_ = mem.table_->GetDynamicPrefixIterator(arena);
+ } else {
+ iter_ = mem.table_->GetIterator(arena);
+ }
+ }
+ // No copying allowed
+ MemTableIterator(const MemTableIterator&) = delete;
+ void operator=(const MemTableIterator&) = delete;
+
+ ~MemTableIterator() override {
+#ifndef NDEBUG
+ // Assert that the MemTableIterator is never deleted while
+ // Pinning is Enabled.
+ assert(!pinned_iters_mgr_ || !pinned_iters_mgr_->PinningEnabled());
+#endif
+ if (arena_mode_) {
+ iter_->~Iterator();
+ } else {
+ delete iter_;
+ }
+ }
+
+#ifndef NDEBUG
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ }
+ PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
+#endif
+
+ bool Valid() const override { return valid_; }
+ void Seek(const Slice& k) override {
+ PERF_TIMER_GUARD(seek_on_memtable_time);
+ PERF_COUNTER_ADD(seek_on_memtable_count, 1);
+ if (bloom_) {
+ // iterator should only use prefix bloom filter
+ Slice user_k(ExtractUserKey(k));
+ if (prefix_extractor_->InDomain(user_k) &&
+ !bloom_->MayContain(prefix_extractor_->Transform(user_k))) {
+ PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
+ valid_ = false;
+ return;
+ } else {
+ PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
+ }
+ }
+ iter_->Seek(k, nullptr);
+ valid_ = iter_->Valid();
+ }
+ void SeekForPrev(const Slice& k) override {
+ PERF_TIMER_GUARD(seek_on_memtable_time);
+ PERF_COUNTER_ADD(seek_on_memtable_count, 1);
+ if (bloom_) {
+ Slice user_k(ExtractUserKey(k));
+ if (prefix_extractor_->InDomain(user_k) &&
+ !bloom_->MayContain(prefix_extractor_->Transform(user_k))) {
+ PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
+ valid_ = false;
+ return;
+ } else {
+ PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
+ }
+ }
+ iter_->Seek(k, nullptr);
+ valid_ = iter_->Valid();
+ if (!Valid()) {
+ SeekToLast();
+ }
+ while (Valid() && comparator_.comparator.Compare(k, key()) < 0) {
+ Prev();
+ }
+ }
+ void SeekToFirst() override {
+ iter_->SeekToFirst();
+ valid_ = iter_->Valid();
+ }
+ void SeekToLast() override {
+ iter_->SeekToLast();
+ valid_ = iter_->Valid();
+ }
+ void Next() override {
+ PERF_COUNTER_ADD(next_on_memtable_count, 1);
+ assert(Valid());
+ iter_->Next();
+ valid_ = iter_->Valid();
+ }
+ void Prev() override {
+ PERF_COUNTER_ADD(prev_on_memtable_count, 1);
+ assert(Valid());
+ iter_->Prev();
+ valid_ = iter_->Valid();
+ }
+ Slice key() const override {
+ assert(Valid());
+ return GetLengthPrefixedSlice(iter_->key());
+ }
+ Slice value() const override {
+ assert(Valid());
+ Slice key_slice = GetLengthPrefixedSlice(iter_->key());
+ return GetLengthPrefixedSlice(key_slice.data() + key_slice.size());
+ }
+
+ Status status() const override { return Status::OK(); }
+
+ bool IsKeyPinned() const override {
+ // memtable data is always pinned
+ return true;
+ }
+
+ bool IsValuePinned() const override {
+ // memtable value is always pinned, except if we allow inplace update.
+ return value_pinned_;
+ }
+
+ private:
+ DynamicBloom* bloom_;
+ const SliceTransform* const prefix_extractor_;
+ const MemTable::KeyComparator comparator_;
+ MemTableRep::Iterator* iter_;
+ bool valid_;
+ bool arena_mode_;
+ bool value_pinned_;
+};
+
+InternalIterator* MemTable::NewIterator(const ReadOptions& read_options,
+ Arena* arena) {
+ assert(arena != nullptr);
+ auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
+ return new (mem) MemTableIterator(*this, read_options, arena);
+}
+
+FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
+ const ReadOptions& read_options, SequenceNumber read_seq) {
+ if (read_options.ignore_range_deletions ||
+ is_range_del_table_empty_.load(std::memory_order_relaxed)) {
+ return nullptr;
+ }
+ auto* unfragmented_iter = new MemTableIterator(
+ *this, read_options, nullptr /* arena */, true /* use_range_del_table */);
+ if (unfragmented_iter == nullptr) {
+ return nullptr;
+ }
+ auto fragmented_tombstone_list =
+ std::make_shared<FragmentedRangeTombstoneList>(
+ std::unique_ptr<InternalIterator>(unfragmented_iter),
+ comparator_.comparator);
+
+ auto* fragmented_iter = new FragmentedRangeTombstoneIterator(
+ fragmented_tombstone_list, comparator_.comparator, read_seq);
+ return fragmented_iter;
+}
+
+port::RWMutex* MemTable::GetLock(const Slice& key) {
+ return &locks_[fastrange64(GetSliceNPHash64(key), locks_.size())];
+}
+
+MemTable::MemTableStats MemTable::ApproximateStats(const Slice& start_ikey,
+ const Slice& end_ikey) {
+ uint64_t entry_count = table_->ApproximateNumEntries(start_ikey, end_ikey);
+ entry_count += range_del_table_->ApproximateNumEntries(start_ikey, end_ikey);
+ if (entry_count == 0) {
+ return {0, 0};
+ }
+ uint64_t n = num_entries_.load(std::memory_order_relaxed);
+ if (n == 0) {
+ return {0, 0};
+ }
+ if (entry_count > n) {
+ // (range_del_)table_->ApproximateNumEntries() is just an estimate so it can
+ // be larger than actual entries we have. Cap it to entries we have to limit
+ // the inaccuracy.
+ entry_count = n;
+ }
+ uint64_t data_size = data_size_.load(std::memory_order_relaxed);
+ return {entry_count * (data_size / n), entry_count};
+}
+
+bool MemTable::Add(SequenceNumber s, ValueType type,
+ const Slice& key, /* user key */
+ const Slice& value, bool allow_concurrent,
+ MemTablePostProcessInfo* post_process_info, void** hint) {
+ // Format of an entry is concatenation of:
+ // key_size : varint32 of internal_key.size()
+ // key bytes : char[internal_key.size()]
+ // value_size : varint32 of value.size()
+ // value bytes : char[value.size()]
+ uint32_t key_size = static_cast<uint32_t>(key.size());
+ uint32_t val_size = static_cast<uint32_t>(value.size());
+ uint32_t internal_key_size = key_size + 8;
+ const uint32_t encoded_len = VarintLength(internal_key_size) +
+ internal_key_size + VarintLength(val_size) +
+ val_size;
+ char* buf = nullptr;
+ std::unique_ptr<MemTableRep>& table =
+ type == kTypeRangeDeletion ? range_del_table_ : table_;
+ KeyHandle handle = table->Allocate(encoded_len, &buf);
+
+ char* p = EncodeVarint32(buf, internal_key_size);
+ memcpy(p, key.data(), key_size);
+ Slice key_slice(p, key_size);
+ p += key_size;
+ uint64_t packed = PackSequenceAndType(s, type);
+ EncodeFixed64(p, packed);
+ p += 8;
+ p = EncodeVarint32(p, val_size);
+ memcpy(p, value.data(), val_size);
+ assert((unsigned)(p + val_size - buf) == (unsigned)encoded_len);
+ size_t ts_sz = GetInternalKeyComparator().user_comparator()->timestamp_size();
+
+ if (!allow_concurrent) {
+ // Extract prefix for insert with hint.
+ if (insert_with_hint_prefix_extractor_ != nullptr &&
+ insert_with_hint_prefix_extractor_->InDomain(key_slice)) {
+ Slice prefix = insert_with_hint_prefix_extractor_->Transform(key_slice);
+ bool res = table->InsertKeyWithHint(handle, &insert_hints_[prefix]);
+ if (UNLIKELY(!res)) {
+ return res;
+ }
+ } else {
+ bool res = table->InsertKey(handle);
+ if (UNLIKELY(!res)) {
+ return res;
+ }
+ }
+
+ // this is a bit ugly, but is the way to avoid locked instructions
+ // when incrementing an atomic
+ num_entries_.store(num_entries_.load(std::memory_order_relaxed) + 1,
+ std::memory_order_relaxed);
+ data_size_.store(data_size_.load(std::memory_order_relaxed) + encoded_len,
+ std::memory_order_relaxed);
+ if (type == kTypeDeletion) {
+ num_deletes_.store(num_deletes_.load(std::memory_order_relaxed) + 1,
+ std::memory_order_relaxed);
+ }
+
+ if (bloom_filter_ && prefix_extractor_ &&
+ prefix_extractor_->InDomain(key)) {
+ bloom_filter_->Add(prefix_extractor_->Transform(key));
+ }
+ if (bloom_filter_ && moptions_.memtable_whole_key_filtering) {
+ bloom_filter_->Add(StripTimestampFromUserKey(key, ts_sz));
+ }
+
+ // The first sequence number inserted into the memtable
+ assert(first_seqno_ == 0 || s >= first_seqno_);
+ if (first_seqno_ == 0) {
+ first_seqno_.store(s, std::memory_order_relaxed);
+
+ if (earliest_seqno_ == kMaxSequenceNumber) {
+ earliest_seqno_.store(GetFirstSequenceNumber(),
+ std::memory_order_relaxed);
+ }
+ assert(first_seqno_.load() >= earliest_seqno_.load());
+ }
+ assert(post_process_info == nullptr);
+ UpdateFlushState();
+ } else {
+ bool res = (hint == nullptr)
+ ? table->InsertKeyConcurrently(handle)
+ : table->InsertKeyWithHintConcurrently(handle, hint);
+ if (UNLIKELY(!res)) {
+ return res;
+ }
+
+ assert(post_process_info != nullptr);
+ post_process_info->num_entries++;
+ post_process_info->data_size += encoded_len;
+ if (type == kTypeDeletion) {
+ post_process_info->num_deletes++;
+ }
+
+ if (bloom_filter_ && prefix_extractor_ &&
+ prefix_extractor_->InDomain(key)) {
+ bloom_filter_->AddConcurrently(prefix_extractor_->Transform(key));
+ }
+ if (bloom_filter_ && moptions_.memtable_whole_key_filtering) {
+ bloom_filter_->AddConcurrently(StripTimestampFromUserKey(key, ts_sz));
+ }
+
+ // atomically update first_seqno_ and earliest_seqno_.
+ uint64_t cur_seq_num = first_seqno_.load(std::memory_order_relaxed);
+ while ((cur_seq_num == 0 || s < cur_seq_num) &&
+ !first_seqno_.compare_exchange_weak(cur_seq_num, s)) {
+ }
+ uint64_t cur_earliest_seqno =
+ earliest_seqno_.load(std::memory_order_relaxed);
+ while (
+ (cur_earliest_seqno == kMaxSequenceNumber || s < cur_earliest_seqno) &&
+ !first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
+ }
+ }
+ if (type == kTypeRangeDeletion) {
+ is_range_del_table_empty_.store(false, std::memory_order_relaxed);
+ }
+ UpdateOldestKeyTime();
+ return true;
+}
+
+// Callback from MemTable::Get()
+namespace {
+
+struct Saver {
+ Status* status;
+ const LookupKey* key;
+ bool* found_final_value; // Is value set correctly? Used by KeyMayExist
+ bool* merge_in_progress;
+ std::string* value;
+ SequenceNumber seq;
+ const MergeOperator* merge_operator;
+ // the merge operations encountered;
+ MergeContext* merge_context;
+ SequenceNumber max_covering_tombstone_seq;
+ MemTable* mem;
+ Logger* logger;
+ Statistics* statistics;
+ bool inplace_update_support;
+ bool do_merge;
+ Env* env_;
+ ReadCallback* callback_;
+ bool* is_blob_index;
+
+ bool CheckCallback(SequenceNumber _seq) {
+ if (callback_) {
+ return callback_->IsVisible(_seq);
+ }
+ return true;
+ }
+};
+} // namespace
+
+static bool SaveValue(void* arg, const char* entry) {
+ Saver* s = reinterpret_cast<Saver*>(arg);
+ assert(s != nullptr);
+ MergeContext* merge_context = s->merge_context;
+ SequenceNumber max_covering_tombstone_seq = s->max_covering_tombstone_seq;
+ const MergeOperator* merge_operator = s->merge_operator;
+
+ assert(merge_context != nullptr);
+
+ // entry format is:
+ // klength varint32
+ // userkey char[klength-8]
+ // tag uint64
+ // vlength varint32f
+ // value char[vlength]
+ // Check that it belongs to same user key. We do not check the
+ // sequence number since the Seek() call above should have skipped
+ // all entries with overly large sequence numbers.
+ uint32_t key_length;
+ const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
+ Slice user_key_slice = Slice(key_ptr, key_length - 8);
+ if (s->mem->GetInternalKeyComparator()
+ .user_comparator()
+ ->CompareWithoutTimestamp(user_key_slice, s->key->user_key()) == 0) {
+ // Correct user key
+ const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
+ ValueType type;
+ SequenceNumber seq;
+ UnPackSequenceAndType(tag, &seq, &type);
+ // If the value is not in the snapshot, skip it
+ if (!s->CheckCallback(seq)) {
+ return true; // to continue to the next seq
+ }
+
+ s->seq = seq;
+
+ if ((type == kTypeValue || type == kTypeMerge || type == kTypeBlobIndex) &&
+ max_covering_tombstone_seq > seq) {
+ type = kTypeRangeDeletion;
+ }
+ switch (type) {
+ case kTypeBlobIndex:
+ if (s->is_blob_index == nullptr) {
+ ROCKS_LOG_ERROR(s->logger, "Encounter unexpected blob index.");
+ *(s->status) = Status::NotSupported(
+ "Encounter unsupported blob value. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ } else if (*(s->merge_in_progress)) {
+ *(s->status) =
+ Status::NotSupported("Blob DB does not support merge operator.");
+ }
+ if (!s->status->ok()) {
+ *(s->found_final_value) = true;
+ return false;
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeValue: {
+ if (s->inplace_update_support) {
+ s->mem->GetLock(s->key->user_key())->ReadLock();
+ }
+ Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
+ *(s->status) = Status::OK();
+ if (*(s->merge_in_progress)) {
+ if (s->do_merge) {
+ if (s->value != nullptr) {
+ *(s->status) = MergeHelper::TimedFullMerge(
+ merge_operator, s->key->user_key(), &v,
+ merge_context->GetOperands(), s->value, s->logger,
+ s->statistics, s->env_, nullptr /* result_operand */, true);
+ }
+ } else {
+ // Preserve the value with the goal of returning it as part of
+ // raw merge operands to the user
+ merge_context->PushOperand(
+ v, s->inplace_update_support == false /* operand_pinned */);
+ }
+ } else if (!s->do_merge) {
+ // Preserve the value with the goal of returning it as part of
+ // raw merge operands to the user
+ merge_context->PushOperand(
+ v, s->inplace_update_support == false /* operand_pinned */);
+ } else if (s->value != nullptr) {
+ s->value->assign(v.data(), v.size());
+ }
+ if (s->inplace_update_support) {
+ s->mem->GetLock(s->key->user_key())->ReadUnlock();
+ }
+ *(s->found_final_value) = true;
+ if (s->is_blob_index != nullptr) {
+ *(s->is_blob_index) = (type == kTypeBlobIndex);
+ }
+ return false;
+ }
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ case kTypeRangeDeletion: {
+ if (*(s->merge_in_progress)) {
+ if (s->value != nullptr) {
+ *(s->status) = MergeHelper::TimedFullMerge(
+ merge_operator, s->key->user_key(), nullptr,
+ merge_context->GetOperands(), s->value, s->logger,
+ s->statistics, s->env_, nullptr /* result_operand */, true);
+ }
+ } else {
+ *(s->status) = Status::NotFound();
+ }
+ *(s->found_final_value) = true;
+ return false;
+ }
+ case kTypeMerge: {
+ if (!merge_operator) {
+ *(s->status) = Status::InvalidArgument(
+ "merge_operator is not properly initialized.");
+ // Normally we continue the loop (return true) when we see a merge
+ // operand. But in case of an error, we should stop the loop
+ // immediately and pretend we have found the value to stop further
+ // seek. Otherwise, the later call will override this error status.
+ *(s->found_final_value) = true;
+ return false;
+ }
+ Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
+ *(s->merge_in_progress) = true;
+ merge_context->PushOperand(
+ v, s->inplace_update_support == false /* operand_pinned */);
+ if (s->do_merge && merge_operator->ShouldMerge(
+ merge_context->GetOperandsDirectionBackward())) {
+ *(s->status) = MergeHelper::TimedFullMerge(
+ merge_operator, s->key->user_key(), nullptr,
+ merge_context->GetOperands(), s->value, s->logger, s->statistics,
+ s->env_, nullptr /* result_operand */, true);
+ *(s->found_final_value) = true;
+ return false;
+ }
+ return true;
+ }
+ default:
+ assert(false);
+ return true;
+ }
+ }
+
+ // s->state could be Corrupt, merge or notfound
+ return false;
+}
+
+bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ SequenceNumber* seq, const ReadOptions& read_opts,
+ ReadCallback* callback, bool* is_blob_index, bool do_merge) {
+ // The sequence number is updated synchronously in version_set.h
+ if (IsEmpty()) {
+ // Avoiding recording stats for speed.
+ return false;
+ }
+ PERF_TIMER_GUARD(get_from_memtable_time);
+
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ NewRangeTombstoneIterator(read_opts,
+ GetInternalKeySeqno(key.internal_key())));
+ if (range_del_iter != nullptr) {
+ *max_covering_tombstone_seq =
+ std::max(*max_covering_tombstone_seq,
+ range_del_iter->MaxCoveringTombstoneSeqnum(key.user_key()));
+ }
+
+ Slice user_key = key.user_key();
+ bool found_final_value = false;
+ bool merge_in_progress = s->IsMergeInProgress();
+ bool may_contain = true;
+ size_t ts_sz = GetInternalKeyComparator().user_comparator()->timestamp_size();
+ if (bloom_filter_) {
+ // when both memtable_whole_key_filtering and prefix_extractor_ are set,
+ // only do whole key filtering for Get() to save CPU
+ if (moptions_.memtable_whole_key_filtering) {
+ may_contain =
+ bloom_filter_->MayContain(StripTimestampFromUserKey(user_key, ts_sz));
+ } else {
+ assert(prefix_extractor_);
+ may_contain =
+ !prefix_extractor_->InDomain(user_key) ||
+ bloom_filter_->MayContain(prefix_extractor_->Transform(user_key));
+ }
+ }
+
+ if (bloom_filter_ && !may_contain) {
+ // iter is null if prefix bloom says the key does not exist
+ PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
+ *seq = kMaxSequenceNumber;
+ } else {
+ if (bloom_filter_) {
+ PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
+ }
+ GetFromTable(key, *max_covering_tombstone_seq, do_merge, callback,
+ is_blob_index, value, s, merge_context, seq,
+ &found_final_value, &merge_in_progress);
+ }
+
+ // No change to value, since we have not yet found a Put/Delete
+ if (!found_final_value && merge_in_progress) {
+ *s = Status::MergeInProgress();
+ }
+ PERF_COUNTER_ADD(get_from_memtable_count, 1);
+ return found_final_value;
+}
+
+void MemTable::GetFromTable(const LookupKey& key,
+ SequenceNumber max_covering_tombstone_seq,
+ bool do_merge, ReadCallback* callback,
+ bool* is_blob_index, std::string* value, Status* s,
+ MergeContext* merge_context, SequenceNumber* seq,
+ bool* found_final_value, bool* merge_in_progress) {
+ Saver saver;
+ saver.status = s;
+ saver.found_final_value = found_final_value;
+ saver.merge_in_progress = merge_in_progress;
+ saver.key = &key;
+ saver.value = value;
+ saver.seq = kMaxSequenceNumber;
+ saver.mem = this;
+ saver.merge_context = merge_context;
+ saver.max_covering_tombstone_seq = max_covering_tombstone_seq;
+ saver.merge_operator = moptions_.merge_operator;
+ saver.logger = moptions_.info_log;
+ saver.inplace_update_support = moptions_.inplace_update_support;
+ saver.statistics = moptions_.statistics;
+ saver.env_ = env_;
+ saver.callback_ = callback;
+ saver.is_blob_index = is_blob_index;
+ saver.do_merge = do_merge;
+ table_->Get(key, &saver, SaveValue);
+ *seq = saver.seq;
+}
+
+void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
+ ReadCallback* callback, bool* is_blob) {
+ // The sequence number is updated synchronously in version_set.h
+ if (IsEmpty()) {
+ // Avoiding recording stats for speed.
+ return;
+ }
+ PERF_TIMER_GUARD(get_from_memtable_time);
+
+ MultiGetRange temp_range(*range, range->begin(), range->end());
+ if (bloom_filter_) {
+ std::array<Slice*, MultiGetContext::MAX_BATCH_SIZE> keys;
+ std::array<bool, MultiGetContext::MAX_BATCH_SIZE> may_match = {{true}};
+ autovector<Slice, MultiGetContext::MAX_BATCH_SIZE> prefixes;
+ int num_keys = 0;
+ for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
+ if (!prefix_extractor_) {
+ keys[num_keys++] = &iter->ukey;
+ } else if (prefix_extractor_->InDomain(iter->ukey)) {
+ prefixes.emplace_back(prefix_extractor_->Transform(iter->ukey));
+ keys[num_keys++] = &prefixes.back();
+ }
+ }
+ bloom_filter_->MayContain(num_keys, &keys[0], &may_match[0]);
+ int idx = 0;
+ for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
+ if (prefix_extractor_ && !prefix_extractor_->InDomain(iter->ukey)) {
+ PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
+ continue;
+ }
+ if (!may_match[idx]) {
+ temp_range.SkipKey(iter);
+ PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
+ } else {
+ PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
+ }
+ idx++;
+ }
+ }
+ for (auto iter = temp_range.begin(); iter != temp_range.end(); ++iter) {
+ SequenceNumber seq = kMaxSequenceNumber;
+ bool found_final_value{false};
+ bool merge_in_progress = iter->s->IsMergeInProgress();
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ NewRangeTombstoneIterator(
+ read_options, GetInternalKeySeqno(iter->lkey->internal_key())));
+ if (range_del_iter != nullptr) {
+ iter->max_covering_tombstone_seq = std::max(
+ iter->max_covering_tombstone_seq,
+ range_del_iter->MaxCoveringTombstoneSeqnum(iter->lkey->user_key()));
+ }
+ GetFromTable(*(iter->lkey), iter->max_covering_tombstone_seq, true,
+ callback, is_blob, iter->value->GetSelf(), iter->s,
+ &(iter->merge_context), &seq, &found_final_value,
+ &merge_in_progress);
+
+ if (!found_final_value && merge_in_progress) {
+ *(iter->s) = Status::MergeInProgress();
+ }
+
+ if (found_final_value) {
+ iter->value->PinSelf();
+ range->MarkKeyDone(iter);
+ RecordTick(moptions_.statistics, MEMTABLE_HIT);
+ }
+ }
+ PERF_COUNTER_ADD(get_from_memtable_count, 1);
+}
+
+void MemTable::Update(SequenceNumber seq,
+ const Slice& key,
+ const Slice& value) {
+ LookupKey lkey(key, seq);
+ Slice mem_key = lkey.memtable_key();
+
+ std::unique_ptr<MemTableRep::Iterator> iter(
+ table_->GetDynamicPrefixIterator());
+ iter->Seek(lkey.internal_key(), mem_key.data());
+
+ if (iter->Valid()) {
+ // entry format is:
+ // key_length varint32
+ // userkey char[klength-8]
+ // tag uint64
+ // vlength varint32
+ // value char[vlength]
+ // Check that it belongs to same user key. We do not check the
+ // sequence number since the Seek() call above should have skipped
+ // all entries with overly large sequence numbers.
+ const char* entry = iter->key();
+ uint32_t key_length = 0;
+ const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
+ if (comparator_.comparator.user_comparator()->Equal(
+ Slice(key_ptr, key_length - 8), lkey.user_key())) {
+ // Correct user key
+ const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
+ ValueType type;
+ SequenceNumber existing_seq;
+ UnPackSequenceAndType(tag, &existing_seq, &type);
+ assert(existing_seq != seq);
+ if (type == kTypeValue) {
+ Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
+ uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
+ uint32_t new_size = static_cast<uint32_t>(value.size());
+
+ // Update value, if new value size <= previous value size
+ if (new_size <= prev_size) {
+ char* p =
+ EncodeVarint32(const_cast<char*>(key_ptr) + key_length, new_size);
+ WriteLock wl(GetLock(lkey.user_key()));
+ memcpy(p, value.data(), value.size());
+ assert((unsigned)((p + value.size()) - entry) ==
+ (unsigned)(VarintLength(key_length) + key_length +
+ VarintLength(value.size()) + value.size()));
+ RecordTick(moptions_.statistics, NUMBER_KEYS_UPDATED);
+ return;
+ }
+ }
+ }
+ }
+
+ // key doesn't exist
+ bool add_res __attribute__((__unused__));
+ add_res = Add(seq, kTypeValue, key, value);
+ // We already checked unused != seq above. In that case, Add should not fail.
+ assert(add_res);
+}
+
+bool MemTable::UpdateCallback(SequenceNumber seq,
+ const Slice& key,
+ const Slice& delta) {
+ LookupKey lkey(key, seq);
+ Slice memkey = lkey.memtable_key();
+
+ std::unique_ptr<MemTableRep::Iterator> iter(
+ table_->GetDynamicPrefixIterator());
+ iter->Seek(lkey.internal_key(), memkey.data());
+
+ if (iter->Valid()) {
+ // entry format is:
+ // key_length varint32
+ // userkey char[klength-8]
+ // tag uint64
+ // vlength varint32
+ // value char[vlength]
+ // Check that it belongs to same user key. We do not check the
+ // sequence number since the Seek() call above should have skipped
+ // all entries with overly large sequence numbers.
+ const char* entry = iter->key();
+ uint32_t key_length = 0;
+ const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
+ if (comparator_.comparator.user_comparator()->Equal(
+ Slice(key_ptr, key_length - 8), lkey.user_key())) {
+ // Correct user key
+ const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
+ ValueType type;
+ uint64_t unused;
+ UnPackSequenceAndType(tag, &unused, &type);
+ switch (type) {
+ case kTypeValue: {
+ Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
+ uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
+
+ char* prev_buffer = const_cast<char*>(prev_value.data());
+ uint32_t new_prev_size = prev_size;
+
+ std::string str_value;
+ WriteLock wl(GetLock(lkey.user_key()));
+ auto status = moptions_.inplace_callback(prev_buffer, &new_prev_size,
+ delta, &str_value);
+ if (status == UpdateStatus::UPDATED_INPLACE) {
+ // Value already updated by callback.
+ assert(new_prev_size <= prev_size);
+ if (new_prev_size < prev_size) {
+ // overwrite the new prev_size
+ char* p = EncodeVarint32(const_cast<char*>(key_ptr) + key_length,
+ new_prev_size);
+ if (VarintLength(new_prev_size) < VarintLength(prev_size)) {
+ // shift the value buffer as well.
+ memcpy(p, prev_buffer, new_prev_size);
+ }
+ }
+ RecordTick(moptions_.statistics, NUMBER_KEYS_UPDATED);
+ UpdateFlushState();
+ return true;
+ } else if (status == UpdateStatus::UPDATED) {
+ Add(seq, kTypeValue, key, Slice(str_value));
+ RecordTick(moptions_.statistics, NUMBER_KEYS_WRITTEN);
+ UpdateFlushState();
+ return true;
+ } else if (status == UpdateStatus::UPDATE_FAILED) {
+ // No action required. Return.
+ UpdateFlushState();
+ return true;
+ }
+ }
+ default:
+ break;
+ }
+ }
+ }
+ // If the latest value is not kTypeValue
+ // or key doesn't exist
+ return false;
+}
+
+size_t MemTable::CountSuccessiveMergeEntries(const LookupKey& key) {
+ Slice memkey = key.memtable_key();
+
+ // A total ordered iterator is costly for some memtablerep (prefix aware
+ // reps). By passing in the user key, we allow efficient iterator creation.
+ // The iterator only needs to be ordered within the same user key.
+ std::unique_ptr<MemTableRep::Iterator> iter(
+ table_->GetDynamicPrefixIterator());
+ iter->Seek(key.internal_key(), memkey.data());
+
+ size_t num_successive_merges = 0;
+
+ for (; iter->Valid(); iter->Next()) {
+ const char* entry = iter->key();
+ uint32_t key_length = 0;
+ const char* iter_key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
+ if (!comparator_.comparator.user_comparator()->Equal(
+ Slice(iter_key_ptr, key_length - 8), key.user_key())) {
+ break;
+ }
+
+ const uint64_t tag = DecodeFixed64(iter_key_ptr + key_length - 8);
+ ValueType type;
+ uint64_t unused;
+ UnPackSequenceAndType(tag, &unused, &type);
+ if (type != kTypeMerge) {
+ break;
+ }
+
+ ++num_successive_merges;
+ }
+
+ return num_successive_merges;
+}
+
+void MemTableRep::Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) {
+ auto iter = GetDynamicPrefixIterator();
+ for (iter->Seek(k.internal_key(), k.memtable_key().data());
+ iter->Valid() && callback_func(callback_args, iter->key());
+ iter->Next()) {
+ }
+}
+
+void MemTable::RefLogContainingPrepSection(uint64_t log) {
+ assert(log > 0);
+ auto cur = min_prep_log_referenced_.load();
+ while ((log < cur || cur == 0) &&
+ !min_prep_log_referenced_.compare_exchange_strong(cur, log)) {
+ cur = min_prep_log_referenced_.load();
+ }
+}
+
+uint64_t MemTable::GetMinLogContainingPrepSection() {
+ return min_prep_log_referenced_.load();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/memtable.h b/storage/rocksdb/rocksdb/db/memtable.h
new file mode 100644
index 00000000..f4e4b98a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/memtable.h
@@ -0,0 +1,542 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <deque>
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "db/dbformat.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/read_callback.h"
+#include "db/version_edit.h"
+#include "memory/allocator.h"
+#include "memory/concurrent_arena.h"
+#include "monitoring/instrumented_mutex.h"
+#include "options/cf_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/memtablerep.h"
+#include "table/multiget_context.h"
+#include "util/dynamic_bloom.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct FlushJobInfo;
+class Mutex;
+class MemTableIterator;
+class MergeContext;
+
+struct ImmutableMemTableOptions {
+ explicit ImmutableMemTableOptions(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options);
+ size_t arena_block_size;
+ uint32_t memtable_prefix_bloom_bits;
+ size_t memtable_huge_page_size;
+ bool memtable_whole_key_filtering;
+ bool inplace_update_support;
+ size_t inplace_update_num_locks;
+ UpdateStatus (*inplace_callback)(char* existing_value,
+ uint32_t* existing_value_size,
+ Slice delta_value,
+ std::string* merged_value);
+ size_t max_successive_merges;
+ Statistics* statistics;
+ MergeOperator* merge_operator;
+ Logger* info_log;
+};
+
+// Batched counters to updated when inserting keys in one write batch.
+// In post process of the write batch, these can be updated together.
+// Only used in concurrent memtable insert case.
+struct MemTablePostProcessInfo {
+ uint64_t data_size = 0;
+ uint64_t num_entries = 0;
+ uint64_t num_deletes = 0;
+};
+
+using MultiGetRange = MultiGetContext::Range;
+// Note: Many of the methods in this class have comments indicating that
+// external synchronization is required as these methods are not thread-safe.
+// It is up to higher layers of code to decide how to prevent concurrent
+// invokation of these methods. This is usually done by acquiring either
+// the db mutex or the single writer thread.
+//
+// Some of these methods are documented to only require external
+// synchronization if this memtable is immutable. Calling MarkImmutable() is
+// not sufficient to guarantee immutability. It is up to higher layers of
+// code to determine if this MemTable can still be modified by other threads.
+// Eg: The Superversion stores a pointer to the current MemTable (that can
+// be modified) and a separate list of the MemTables that can no longer be
+// written to (aka the 'immutable memtables').
+class MemTable {
+ public:
+ struct KeyComparator : public MemTableRep::KeyComparator {
+ const InternalKeyComparator comparator;
+ explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) { }
+ virtual int operator()(const char* prefix_len_key1,
+ const char* prefix_len_key2) const override;
+ virtual int operator()(const char* prefix_len_key,
+ const DecodedType& key) const override;
+ };
+
+ // MemTables are reference counted. The initial reference count
+ // is zero and the caller must call Ref() at least once.
+ //
+ // earliest_seq should be the current SequenceNumber in the db such that any
+ // key inserted into this memtable will have an equal or larger seq number.
+ // (When a db is first created, the earliest sequence number will be 0).
+ // If the earliest sequence number is not known, kMaxSequenceNumber may be
+ // used, but this may prevent some transactions from succeeding until the
+ // first key is inserted into the memtable.
+ explicit MemTable(const InternalKeyComparator& comparator,
+ const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options,
+ WriteBufferManager* write_buffer_manager,
+ SequenceNumber earliest_seq, uint32_t column_family_id);
+ // No copying allowed
+ MemTable(const MemTable&) = delete;
+ MemTable& operator=(const MemTable&) = delete;
+
+ // Do not delete this MemTable unless Unref() indicates it not in use.
+ ~MemTable();
+
+ // Increase reference count.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ void Ref() { ++refs_; }
+
+ // Drop reference count.
+ // If the refcount goes to zero return this memtable, otherwise return null.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ MemTable* Unref() {
+ --refs_;
+ assert(refs_ >= 0);
+ if (refs_ <= 0) {
+ return this;
+ }
+ return nullptr;
+ }
+
+ // Returns an estimate of the number of bytes of data in use by this
+ // data structure.
+ //
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable (unless this Memtable is immutable).
+ size_t ApproximateMemoryUsage();
+
+ // As a cheap version of `ApproximateMemoryUsage()`, this function doens't
+ // require external synchronization. The value may be less accurate though
+ size_t ApproximateMemoryUsageFast() const {
+ return approximate_memory_usage_.load(std::memory_order_relaxed);
+ }
+
+ // This method heuristically determines if the memtable should continue to
+ // host more data.
+ bool ShouldScheduleFlush() const {
+ return flush_state_.load(std::memory_order_relaxed) == FLUSH_REQUESTED;
+ }
+
+ // Returns true if a flush should be scheduled and the caller should
+ // be the one to schedule it
+ bool MarkFlushScheduled() {
+ auto before = FLUSH_REQUESTED;
+ return flush_state_.compare_exchange_strong(before, FLUSH_SCHEDULED,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+
+ // Return an iterator that yields the contents of the memtable.
+ //
+ // The caller must ensure that the underlying MemTable remains live
+ // while the returned iterator is live. The keys returned by this
+ // iterator are internal keys encoded by AppendInternalKey in the
+ // db/dbformat.{h,cc} module.
+ //
+ // By default, it returns an iterator for prefix seek if prefix_extractor
+ // is configured in Options.
+ // arena: If not null, the arena needs to be used to allocate the Iterator.
+ // Calling ~Iterator of the iterator will destroy all the states but
+ // those allocated in arena.
+ InternalIterator* NewIterator(const ReadOptions& read_options, Arena* arena);
+
+ FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
+ const ReadOptions& read_options, SequenceNumber read_seq);
+
+ // Add an entry into memtable that maps key to value at the
+ // specified sequence number and with the specified type.
+ // Typically value will be empty if type==kTypeDeletion.
+ //
+ // REQUIRES: if allow_concurrent = false, external synchronization to prevent
+ // simultaneous operations on the same MemTable.
+ //
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ bool Add(SequenceNumber seq, ValueType type, const Slice& key,
+ const Slice& value, bool allow_concurrent = false,
+ MemTablePostProcessInfo* post_process_info = nullptr,
+ void** hint = nullptr);
+
+ // Used to Get value associated with key or Get Merge Operands associated
+ // with key.
+ // If do_merge = true the default behavior which is Get value for key is
+ // executed. Expected behavior is described right below.
+ // If memtable contains a value for key, store it in *value and return true.
+ // If memtable contains a deletion for key, store a NotFound() error
+ // in *status and return true.
+ // If memtable contains Merge operation as the most recent entry for a key,
+ // and the merge process does not stop (not reaching a value or delete),
+ // prepend the current merge operand to *operands.
+ // store MergeInProgress in s, and return false.
+ // Else, return false.
+ // If any operation was found, its most recent sequence number
+ // will be stored in *seq on success (regardless of whether true/false is
+ // returned). Otherwise, *seq will be set to kMaxSequenceNumber.
+ // On success, *s may be set to OK, NotFound, or MergeInProgress. Any other
+ // status returned indicates a corruption or other unexpected error.
+ // If do_merge = false then any Merge Operands encountered for key are simply
+ // stored in merge_context.operands_list and never actually merged to get a
+ // final value. The raw Merge Operands are eventually returned to the user.
+ bool Get(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
+ const ReadOptions& read_opts, ReadCallback* callback = nullptr,
+ bool* is_blob_index = nullptr, bool do_merge = true);
+
+ bool Get(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ const ReadOptions& read_opts, ReadCallback* callback = nullptr,
+ bool* is_blob_index = nullptr, bool do_merge = true) {
+ SequenceNumber seq;
+ return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq,
+ read_opts, callback, is_blob_index, do_merge);
+ }
+
+ void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
+ ReadCallback* callback, bool* is_blob);
+
+ // Attempts to update the new_value inplace, else does normal Add
+ // Pseudocode
+ // if key exists in current memtable && prev_value is of type kTypeValue
+ // if new sizeof(new_value) <= sizeof(prev_value)
+ // update inplace
+ // else add(key, new_value)
+ // else add(key, new_value)
+ //
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ void Update(SequenceNumber seq,
+ const Slice& key,
+ const Slice& value);
+
+ // If prev_value for key exists, attempts to update it inplace.
+ // else returns false
+ // Pseudocode
+ // if key exists in current memtable && prev_value is of type kTypeValue
+ // new_value = delta(prev_value)
+ // if sizeof(new_value) <= sizeof(prev_value)
+ // update inplace
+ // else add(key, new_value)
+ // else return false
+ //
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ bool UpdateCallback(SequenceNumber seq,
+ const Slice& key,
+ const Slice& delta);
+
+ // Returns the number of successive merge entries starting from the newest
+ // entry for the key up to the last non-merge entry or last entry for the
+ // key in the memtable.
+ size_t CountSuccessiveMergeEntries(const LookupKey& key);
+
+ // Update counters and flush status after inserting a whole write batch
+ // Used in concurrent memtable inserts.
+ void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
+ num_entries_.fetch_add(update_counters.num_entries,
+ std::memory_order_relaxed);
+ data_size_.fetch_add(update_counters.data_size, std::memory_order_relaxed);
+ if (update_counters.num_deletes != 0) {
+ num_deletes_.fetch_add(update_counters.num_deletes,
+ std::memory_order_relaxed);
+ }
+ UpdateFlushState();
+ }
+
+ // Get total number of entries in the mem table.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable (unless this Memtable is immutable).
+ uint64_t num_entries() const {
+ return num_entries_.load(std::memory_order_relaxed);
+ }
+
+ // Get total number of deletes in the mem table.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable (unless this Memtable is immutable).
+ uint64_t num_deletes() const {
+ return num_deletes_.load(std::memory_order_relaxed);
+ }
+
+ uint64_t get_data_size() const {
+ return data_size_.load(std::memory_order_relaxed);
+ }
+
+ // Dynamically change the memtable's capacity. If set below the current usage,
+ // the next key added will trigger a flush. Can only increase size when
+ // memtable prefix bloom is disabled, since we can't easily allocate more
+ // space.
+ void UpdateWriteBufferSize(size_t new_write_buffer_size) {
+ if (bloom_filter_ == nullptr ||
+ new_write_buffer_size < write_buffer_size_) {
+ write_buffer_size_.store(new_write_buffer_size,
+ std::memory_order_relaxed);
+ }
+ }
+
+ // Returns the edits area that is needed for flushing the memtable
+ VersionEdit* GetEdits() { return &edit_; }
+
+ // Returns if there is no entry inserted to the mem table.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable (unless this Memtable is immutable).
+ bool IsEmpty() const { return first_seqno_ == 0; }
+
+ // Returns the sequence number of the first element that was inserted
+ // into the memtable.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable (unless this Memtable is immutable).
+ SequenceNumber GetFirstSequenceNumber() {
+ return first_seqno_.load(std::memory_order_relaxed);
+ }
+
+ // Returns 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.
+ //
+ // If the earliest sequence number could not be determined,
+ // kMaxSequenceNumber will be returned.
+ SequenceNumber GetEarliestSequenceNumber() {
+ return earliest_seqno_.load(std::memory_order_relaxed);
+ }
+
+ // DB's latest sequence ID when the memtable is created. This number
+ // may be updated to a more recent one before any key is inserted.
+ SequenceNumber GetCreationSeq() const { return creation_seq_; }
+
+ void SetCreationSeq(SequenceNumber sn) { creation_seq_ = sn; }
+
+ // Returns the next active logfile number when this memtable is about to
+ // be flushed to storage
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ uint64_t GetNextLogNumber() { return mem_next_logfile_number_; }
+
+ // Sets the next active logfile number when this memtable is about to
+ // be flushed to storage
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
+
+ // if this memtable contains data from a committed
+ // two phase transaction we must take note of the
+ // log which contains that data so we can know
+ // when to relese that log
+ void RefLogContainingPrepSection(uint64_t log);
+ uint64_t GetMinLogContainingPrepSection();
+
+ // Notify the underlying storage that no more items will be added.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ // After MarkImmutable() is called, you should not attempt to
+ // write anything to this MemTable(). (Ie. do not call Add() or Update()).
+ void MarkImmutable() {
+ table_->MarkReadOnly();
+ mem_tracker_.DoneAllocating();
+ }
+
+ // Notify the underlying storage that all data it contained has been
+ // persisted.
+ // REQUIRES: external synchronization to prevent simultaneous
+ // operations on the same MemTable.
+ void MarkFlushed() {
+ table_->MarkFlushed();
+ }
+
+ // return true if the current MemTableRep supports merge operator.
+ bool IsMergeOperatorSupported() const {
+ return table_->IsMergeOperatorSupported();
+ }
+
+ // return true if the current MemTableRep supports snapshots.
+ // inplace update prevents snapshots,
+ bool IsSnapshotSupported() const {
+ return table_->IsSnapshotSupported() && !moptions_.inplace_update_support;
+ }
+
+ struct MemTableStats {
+ uint64_t size;
+ uint64_t count;
+ };
+
+ MemTableStats ApproximateStats(const Slice& start_ikey,
+ const Slice& end_ikey);
+
+ // Get the lock associated for the key
+ port::RWMutex* GetLock(const Slice& key);
+
+ const InternalKeyComparator& GetInternalKeyComparator() const {
+ return comparator_.comparator;
+ }
+
+ const ImmutableMemTableOptions* GetImmutableMemTableOptions() const {
+ return &moptions_;
+ }
+
+ uint64_t ApproximateOldestKeyTime() const {
+ return oldest_key_time_.load(std::memory_order_relaxed);
+ }
+
+ // REQUIRES: db_mutex held.
+ void SetID(uint64_t id) { id_ = id; }
+
+ uint64_t GetID() const { return id_; }
+
+ void SetFlushCompleted(bool completed) { flush_completed_ = completed; }
+
+ uint64_t GetFileNumber() const { return file_number_; }
+
+ void SetFileNumber(uint64_t file_num) { file_number_ = file_num; }
+
+ void SetFlushInProgress(bool in_progress) {
+ flush_in_progress_ = in_progress;
+ }
+
+#ifndef ROCKSDB_LITE
+ void SetFlushJobInfo(std::unique_ptr<FlushJobInfo>&& info) {
+ flush_job_info_ = std::move(info);
+ }
+
+ std::unique_ptr<FlushJobInfo> ReleaseFlushJobInfo() {
+ return std::move(flush_job_info_);
+ }
+#endif // !ROCKSDB_LITE
+
+ private:
+ enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
+
+ friend class MemTableIterator;
+ friend class MemTableBackwardIterator;
+ friend class MemTableList;
+
+ KeyComparator comparator_;
+ const ImmutableMemTableOptions moptions_;
+ int refs_;
+ const size_t kArenaBlockSize;
+ AllocTracker mem_tracker_;
+ ConcurrentArena arena_;
+ std::unique_ptr<MemTableRep> table_;
+ std::unique_ptr<MemTableRep> range_del_table_;
+ std::atomic_bool is_range_del_table_empty_;
+
+ // Total data size of all data inserted
+ std::atomic<uint64_t> data_size_;
+ std::atomic<uint64_t> num_entries_;
+ std::atomic<uint64_t> num_deletes_;
+
+ // Dynamically changeable memtable option
+ std::atomic<size_t> write_buffer_size_;
+
+ // These are used to manage memtable flushes to storage
+ bool flush_in_progress_; // started the flush
+ bool flush_completed_; // finished the flush
+ uint64_t file_number_; // filled up after flush is complete
+
+ // The updates to be applied to the transaction log when this
+ // memtable is flushed to storage.
+ VersionEdit edit_;
+
+ // The sequence number of the kv that was inserted first
+ std::atomic<SequenceNumber> first_seqno_;
+
+ // The db sequence number at the time of creation or kMaxSequenceNumber
+ // if not set.
+ std::atomic<SequenceNumber> earliest_seqno_;
+
+ SequenceNumber creation_seq_;
+
+ // The log files earlier than this number can be deleted.
+ uint64_t mem_next_logfile_number_;
+
+ // the earliest log containing a prepared section
+ // which has been inserted into this memtable.
+ std::atomic<uint64_t> min_prep_log_referenced_;
+
+ // rw locks for inplace updates
+ std::vector<port::RWMutex> locks_;
+
+ const SliceTransform* const prefix_extractor_;
+ std::unique_ptr<DynamicBloom> bloom_filter_;
+
+ std::atomic<FlushStateEnum> flush_state_;
+
+ Env* env_;
+
+ // Extract sequential insert prefixes.
+ const SliceTransform* insert_with_hint_prefix_extractor_;
+
+ // Insert hints for each prefix.
+ std::unordered_map<Slice, void*, SliceHasher> insert_hints_;
+
+ // Timestamp of oldest key
+ std::atomic<uint64_t> oldest_key_time_;
+
+ // Memtable id to track flush.
+ uint64_t id_ = 0;
+
+ // Sequence number of the atomic flush that is responsible for this memtable.
+ // The sequence number of atomic flush is a seq, such that no writes with
+ // sequence numbers greater than or equal to seq are flushed, while all
+ // writes with sequence number smaller than seq are flushed.
+ SequenceNumber atomic_flush_seqno_;
+
+ // keep track of memory usage in table_, arena_, and range_del_table_.
+ // Gets refrshed inside `ApproximateMemoryUsage()` or `ShouldFlushNow`
+ std::atomic<uint64_t> approximate_memory_usage_;
+
+#ifndef ROCKSDB_LITE
+ // Flush job info of the current memtable.
+ std::unique_ptr<FlushJobInfo> flush_job_info_;
+#endif // !ROCKSDB_LITE
+
+ // Returns a heuristic flush decision
+ bool ShouldFlushNow();
+
+ // Updates flush_state_ using ShouldFlushNow()
+ void UpdateFlushState();
+
+ void UpdateOldestKeyTime();
+
+ void GetFromTable(const LookupKey& key,
+ SequenceNumber max_covering_tombstone_seq, bool do_merge,
+ ReadCallback* callback, bool* is_blob_index,
+ std::string* value, Status* s, MergeContext* merge_context,
+ SequenceNumber* seq, bool* found_final_value,
+ bool* merge_in_progress);
+};
+
+extern const char* EncodeKey(std::string* scratch, const Slice& target);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/memtable_list.cc b/storage/rocksdb/rocksdb/db/memtable_list.cc
new file mode 100644
index 00000000..a8b358fa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/memtable_list.cc
@@ -0,0 +1,771 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+#include <limits>
+#include <queue>
+#include <string>
+#include "db/db_impl/db_impl.h"
+#include "db/memtable.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/version_set.h"
+#include "logging/log_buffer.h"
+#include "monitoring/thread_status_util.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "table/merging_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class InternalKeyComparator;
+class Mutex;
+class VersionSet;
+
+void MemTableListVersion::AddMemTable(MemTable* m) {
+ memlist_.push_front(m);
+ *parent_memtable_list_memory_usage_ += m->ApproximateMemoryUsage();
+}
+
+void MemTableListVersion::UnrefMemTable(autovector<MemTable*>* to_delete,
+ MemTable* m) {
+ if (m->Unref()) {
+ to_delete->push_back(m);
+ assert(*parent_memtable_list_memory_usage_ >= m->ApproximateMemoryUsage());
+ *parent_memtable_list_memory_usage_ -= m->ApproximateMemoryUsage();
+ }
+}
+
+MemTableListVersion::MemTableListVersion(
+ size_t* parent_memtable_list_memory_usage, MemTableListVersion* old)
+ : max_write_buffer_number_to_maintain_(
+ old->max_write_buffer_number_to_maintain_),
+ max_write_buffer_size_to_maintain_(
+ old->max_write_buffer_size_to_maintain_),
+ parent_memtable_list_memory_usage_(parent_memtable_list_memory_usage) {
+ if (old != nullptr) {
+ memlist_ = old->memlist_;
+ for (auto& m : memlist_) {
+ m->Ref();
+ }
+
+ memlist_history_ = old->memlist_history_;
+ for (auto& m : memlist_history_) {
+ m->Ref();
+ }
+ }
+}
+
+MemTableListVersion::MemTableListVersion(
+ size_t* parent_memtable_list_memory_usage,
+ int max_write_buffer_number_to_maintain,
+ int64_t max_write_buffer_size_to_maintain)
+ : max_write_buffer_number_to_maintain_(max_write_buffer_number_to_maintain),
+ max_write_buffer_size_to_maintain_(max_write_buffer_size_to_maintain),
+ parent_memtable_list_memory_usage_(parent_memtable_list_memory_usage) {}
+
+void MemTableListVersion::Ref() { ++refs_; }
+
+// called by superversion::clean()
+void MemTableListVersion::Unref(autovector<MemTable*>* to_delete) {
+ assert(refs_ >= 1);
+ --refs_;
+ if (refs_ == 0) {
+ // if to_delete is equal to nullptr it means we're confident
+ // that refs_ will not be zero
+ assert(to_delete != nullptr);
+ for (const auto& m : memlist_) {
+ UnrefMemTable(to_delete, m);
+ }
+ for (const auto& m : memlist_history_) {
+ UnrefMemTable(to_delete, m);
+ }
+ delete this;
+ }
+}
+
+int MemTableList::NumNotFlushed() const {
+ int size = static_cast<int>(current_->memlist_.size());
+ assert(num_flush_not_started_ <= size);
+ return size;
+}
+
+int MemTableList::NumFlushed() const {
+ return static_cast<int>(current_->memlist_history_.size());
+}
+
+// Search all the memtables starting from the most recent one.
+// Return the most recent value found, if any.
+// Operands stores the list of merge operations to apply, so far.
+bool MemTableListVersion::Get(const LookupKey& key, std::string* value,
+ Status* s, MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ SequenceNumber* seq, const ReadOptions& read_opts,
+ ReadCallback* callback, bool* is_blob_index) {
+ return GetFromList(&memlist_, key, value, s, merge_context,
+ max_covering_tombstone_seq, seq, read_opts, callback,
+ is_blob_index);
+}
+
+void MemTableListVersion::MultiGet(const ReadOptions& read_options,
+ MultiGetRange* range, ReadCallback* callback,
+ bool* is_blob) {
+ for (auto memtable : memlist_) {
+ memtable->MultiGet(read_options, range, callback, is_blob);
+ if (range->empty()) {
+ return;
+ }
+ }
+}
+
+bool MemTableListVersion::GetMergeOperands(
+ const LookupKey& key, Status* s, MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq, const ReadOptions& read_opts) {
+ for (MemTable* memtable : memlist_) {
+ bool done = memtable->Get(key, nullptr, s, merge_context,
+ max_covering_tombstone_seq, read_opts, nullptr,
+ nullptr, false);
+ if (done) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MemTableListVersion::GetFromHistory(
+ const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context, SequenceNumber* max_covering_tombstone_seq,
+ SequenceNumber* seq, const ReadOptions& read_opts, bool* is_blob_index) {
+ return GetFromList(&memlist_history_, key, value, s, merge_context,
+ max_covering_tombstone_seq, seq, read_opts,
+ nullptr /*read_callback*/, is_blob_index);
+}
+
+bool MemTableListVersion::GetFromList(
+ std::list<MemTable*>* list, const LookupKey& key, std::string* value,
+ Status* s, MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
+ const ReadOptions& read_opts, ReadCallback* callback, bool* is_blob_index) {
+ *seq = kMaxSequenceNumber;
+
+ for (auto& memtable : *list) {
+ SequenceNumber current_seq = kMaxSequenceNumber;
+
+ bool done =
+ memtable->Get(key, value, s, merge_context, max_covering_tombstone_seq,
+ &current_seq, read_opts, callback, is_blob_index);
+ if (*seq == kMaxSequenceNumber) {
+ // Store the most recent sequence number of any operation on this key.
+ // Since we only care about the most recent change, we only need to
+ // return the first operation found when searching memtables in
+ // reverse-chronological order.
+ // current_seq would be equal to kMaxSequenceNumber if the value was to be
+ // skipped. This allows seq to be assigned again when the next value is
+ // read.
+ *seq = current_seq;
+ }
+
+ if (done) {
+ assert(*seq != kMaxSequenceNumber || s->IsNotFound());
+ return true;
+ }
+ if (!done && !s->ok() && !s->IsMergeInProgress() && !s->IsNotFound()) {
+ return false;
+ }
+ }
+ return false;
+}
+
+Status MemTableListVersion::AddRangeTombstoneIterators(
+ const ReadOptions& read_opts, Arena* /*arena*/,
+ RangeDelAggregator* range_del_agg) {
+ assert(range_del_agg != nullptr);
+ // Except for snapshot read, using kMaxSequenceNumber is OK because these
+ // are immutable memtables.
+ SequenceNumber read_seq = read_opts.snapshot != nullptr
+ ? read_opts.snapshot->GetSequenceNumber()
+ : kMaxSequenceNumber;
+ for (auto& m : memlist_) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ m->NewRangeTombstoneIterator(read_opts, read_seq));
+ range_del_agg->AddTombstones(std::move(range_del_iter));
+ }
+ return Status::OK();
+}
+
+void MemTableListVersion::AddIterators(
+ const ReadOptions& options, std::vector<InternalIterator*>* iterator_list,
+ Arena* arena) {
+ for (auto& m : memlist_) {
+ iterator_list->push_back(m->NewIterator(options, arena));
+ }
+}
+
+void MemTableListVersion::AddIterators(
+ const ReadOptions& options, MergeIteratorBuilder* merge_iter_builder) {
+ for (auto& m : memlist_) {
+ merge_iter_builder->AddIterator(
+ m->NewIterator(options, merge_iter_builder->GetArena()));
+ }
+}
+
+uint64_t MemTableListVersion::GetTotalNumEntries() const {
+ uint64_t total_num = 0;
+ for (auto& m : memlist_) {
+ total_num += m->num_entries();
+ }
+ return total_num;
+}
+
+MemTable::MemTableStats MemTableListVersion::ApproximateStats(
+ const Slice& start_ikey, const Slice& end_ikey) {
+ MemTable::MemTableStats total_stats = {0, 0};
+ for (auto& m : memlist_) {
+ auto mStats = m->ApproximateStats(start_ikey, end_ikey);
+ total_stats.size += mStats.size;
+ total_stats.count += mStats.count;
+ }
+ return total_stats;
+}
+
+uint64_t MemTableListVersion::GetTotalNumDeletes() const {
+ uint64_t total_num = 0;
+ for (auto& m : memlist_) {
+ total_num += m->num_deletes();
+ }
+ return total_num;
+}
+
+SequenceNumber MemTableListVersion::GetEarliestSequenceNumber(
+ bool include_history) const {
+ if (include_history && !memlist_history_.empty()) {
+ return memlist_history_.back()->GetEarliestSequenceNumber();
+ } else if (!memlist_.empty()) {
+ return memlist_.back()->GetEarliestSequenceNumber();
+ } else {
+ return kMaxSequenceNumber;
+ }
+}
+
+// caller is responsible for referencing m
+void MemTableListVersion::Add(MemTable* m, autovector<MemTable*>* to_delete) {
+ assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
+ AddMemTable(m);
+
+ TrimHistory(to_delete, m->ApproximateMemoryUsage());
+}
+
+// Removes m from list of memtables not flushed. Caller should NOT Unref m.
+void MemTableListVersion::Remove(MemTable* m,
+ autovector<MemTable*>* to_delete) {
+ assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
+ memlist_.remove(m);
+
+ m->MarkFlushed();
+ if (max_write_buffer_size_to_maintain_ > 0 ||
+ max_write_buffer_number_to_maintain_ > 0) {
+ memlist_history_.push_front(m);
+ // Unable to get size of mutable memtable at this point, pass 0 to
+ // TrimHistory as a best effort.
+ TrimHistory(to_delete, 0);
+ } else {
+ UnrefMemTable(to_delete, m);
+ }
+}
+
+// return the total memory usage assuming the oldest flushed memtable is dropped
+size_t MemTableListVersion::ApproximateMemoryUsageExcludingLast() const {
+ size_t total_memtable_size = 0;
+ for (auto& memtable : memlist_) {
+ total_memtable_size += memtable->ApproximateMemoryUsage();
+ }
+ for (auto& memtable : memlist_history_) {
+ total_memtable_size += memtable->ApproximateMemoryUsage();
+ }
+ if (!memlist_history_.empty()) {
+ total_memtable_size -= memlist_history_.back()->ApproximateMemoryUsage();
+ }
+ return total_memtable_size;
+}
+
+bool MemTableListVersion::MemtableLimitExceeded(size_t usage) {
+ if (max_write_buffer_size_to_maintain_ > 0) {
+ // calculate the total memory usage after dropping the oldest flushed
+ // memtable, compare with max_write_buffer_size_to_maintain_ to decide
+ // whether to trim history
+ return ApproximateMemoryUsageExcludingLast() + usage >=
+ static_cast<size_t>(max_write_buffer_size_to_maintain_);
+ } else if (max_write_buffer_number_to_maintain_ > 0) {
+ return memlist_.size() + memlist_history_.size() >
+ static_cast<size_t>(max_write_buffer_number_to_maintain_);
+ } else {
+ return false;
+ }
+}
+
+// Make sure we don't use up too much space in history
+void MemTableListVersion::TrimHistory(autovector<MemTable*>* to_delete,
+ size_t usage) {
+ while (MemtableLimitExceeded(usage) && !memlist_history_.empty()) {
+ MemTable* x = memlist_history_.back();
+ memlist_history_.pop_back();
+
+ UnrefMemTable(to_delete, x);
+ }
+}
+
+// Returns true if there is at least one memtable on which flush has
+// not yet started.
+bool MemTableList::IsFlushPending() const {
+ if ((flush_requested_ && num_flush_not_started_ > 0) ||
+ (num_flush_not_started_ >= min_write_buffer_number_to_merge_)) {
+ assert(imm_flush_needed.load(std::memory_order_relaxed));
+ return true;
+ }
+ return false;
+}
+
+// Returns the memtables that need to be flushed.
+void MemTableList::PickMemtablesToFlush(const uint64_t* max_memtable_id,
+ autovector<MemTable*>* ret) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_PICK_MEMTABLES_TO_FLUSH);
+ const auto& memlist = current_->memlist_;
+ bool atomic_flush = false;
+ for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
+ MemTable* m = *it;
+ if (!atomic_flush && m->atomic_flush_seqno_ != kMaxSequenceNumber) {
+ atomic_flush = true;
+ }
+ if (max_memtable_id != nullptr && m->GetID() > *max_memtable_id) {
+ break;
+ }
+ if (!m->flush_in_progress_) {
+ assert(!m->flush_completed_);
+ num_flush_not_started_--;
+ if (num_flush_not_started_ == 0) {
+ imm_flush_needed.store(false, std::memory_order_release);
+ }
+ m->flush_in_progress_ = true; // flushing will start very soon
+ ret->push_back(m);
+ }
+ }
+ if (!atomic_flush || num_flush_not_started_ == 0) {
+ flush_requested_ = false; // start-flush request is complete
+ }
+}
+
+void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
+ uint64_t /*file_number*/) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_MEMTABLE_ROLLBACK);
+ assert(!mems.empty());
+
+ // If the flush was not successful, then just reset state.
+ // Maybe a succeeding attempt to flush will be successful.
+ for (MemTable* m : mems) {
+ assert(m->flush_in_progress_);
+ assert(m->file_number_ == 0);
+
+ m->flush_in_progress_ = false;
+ m->flush_completed_ = false;
+ m->edit_.Clear();
+ num_flush_not_started_++;
+ }
+ imm_flush_needed.store(true, std::memory_order_release);
+}
+
+// Try record a successful flush in the manifest file. It might just return
+// Status::OK letting a concurrent flush to do actual the recording..
+Status MemTableList::TryInstallMemtableFlushResults(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ const autovector<MemTable*>& mems, LogsWithPrepTracker* prep_tracker,
+ VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer,
+ std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
+ mu->AssertHeld();
+
+ // Flush was successful
+ // Record the status on the memtable object. Either this call or a call by a
+ // concurrent flush thread will read the status and write it to manifest.
+ for (size_t i = 0; i < mems.size(); ++i) {
+ // All the edits are associated with the first memtable of this batch.
+ assert(i == 0 || mems[i]->GetEdits()->NumEntries() == 0);
+
+ mems[i]->flush_completed_ = true;
+ mems[i]->file_number_ = file_number;
+ }
+
+ // if some other thread is already committing, then return
+ Status s;
+ if (commit_in_progress_) {
+ TEST_SYNC_POINT("MemTableList::TryInstallMemtableFlushResults:InProgress");
+ return s;
+ }
+
+ // Only a single thread can be executing this piece of code
+ commit_in_progress_ = true;
+
+ // Retry until all completed flushes are committed. New flushes can finish
+ // while the current thread is writing manifest where mutex is released.
+ while (s.ok()) {
+ auto& memlist = current_->memlist_;
+ // The back is the oldest; if flush_completed_ is not set to it, it means
+ // that we were assigned a more recent memtable. The memtables' flushes must
+ // be recorded in manifest in order. A concurrent flush thread, who is
+ // assigned to flush the oldest memtable, will later wake up and does all
+ // the pending writes to manifest, in order.
+ if (memlist.empty() || !memlist.back()->flush_completed_) {
+ break;
+ }
+ // scan all memtables from the earliest, and commit those
+ // (in that order) that have finished flushing. Memtables
+ // are always committed in the order that they were created.
+ uint64_t batch_file_number = 0;
+ size_t batch_count = 0;
+ autovector<VersionEdit*> edit_list;
+ autovector<MemTable*> memtables_to_flush;
+ // enumerate from the last (earliest) element to see how many batch finished
+ for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
+ MemTable* m = *it;
+ if (!m->flush_completed_) {
+ break;
+ }
+ if (it == memlist.rbegin() || batch_file_number != m->file_number_) {
+ batch_file_number = m->file_number_;
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] Level-0 commit table #%" PRIu64 " started",
+ cfd->GetName().c_str(), m->file_number_);
+ edit_list.push_back(&m->edit_);
+ memtables_to_flush.push_back(m);
+#ifndef ROCKSDB_LITE
+ std::unique_ptr<FlushJobInfo> info = m->ReleaseFlushJobInfo();
+ if (info != nullptr) {
+ committed_flush_jobs_info->push_back(std::move(info));
+ }
+#else
+ (void)committed_flush_jobs_info;
+#endif // !ROCKSDB_LITE
+ }
+ batch_count++;
+ }
+
+ // TODO(myabandeh): Not sure how batch_count could be 0 here.
+ if (batch_count > 0) {
+ if (vset->db_options()->allow_2pc) {
+ assert(edit_list.size() > 0);
+ // We piggyback the information of earliest log file to keep in the
+ // manifest entry for the last file flushed.
+ edit_list.back()->SetMinLogNumberToKeep(PrecomputeMinLogNumberToKeep(
+ vset, *cfd, edit_list, memtables_to_flush, prep_tracker));
+ }
+
+ // this can release and reacquire the mutex.
+ s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
+ db_directory);
+
+ // we will be changing the version in the next code path,
+ // so we better create a new one, since versions are immutable
+ InstallNewVersion();
+
+ // All the later memtables that have the same filenum
+ // are part of the same batch. They can be committed now.
+ uint64_t mem_id = 1; // how many memtables have been flushed.
+
+ // commit new state only if the column family is NOT dropped.
+ // The reason is as follows (refer to
+ // ColumnFamilyTest.FlushAndDropRaceCondition).
+ // If the column family is dropped, then according to LogAndApply, its
+ // corresponding flush operation is NOT written to the MANIFEST. This
+ // means the DB is not aware of the L0 files generated from the flush.
+ // By committing the new state, we remove the memtable from the memtable
+ // list. Creating an iterator on this column family will not be able to
+ // read full data since the memtable is removed, and the DB is not aware
+ // of the L0 files, causing MergingIterator unable to build child
+ // iterators. RocksDB contract requires that the iterator can be created
+ // on a dropped column family, and we must be able to
+ // read full data as long as column family handle is not deleted, even if
+ // the column family is dropped.
+ if (s.ok() && !cfd->IsDropped()) { // commit new state
+ while (batch_count-- > 0) {
+ MemTable* m = current_->memlist_.back();
+ ROCKS_LOG_BUFFER(log_buffer, "[%s] Level-0 commit table #%" PRIu64
+ ": memtable #%" PRIu64 " done",
+ cfd->GetName().c_str(), m->file_number_, mem_id);
+ assert(m->file_number_ > 0);
+ current_->Remove(m, to_delete);
+ UpdateCachedValuesFromMemTableListVersion();
+ ResetTrimHistoryNeeded();
+ ++mem_id;
+ }
+ } else {
+ for (auto it = current_->memlist_.rbegin(); batch_count-- > 0; ++it) {
+ MemTable* m = *it;
+ // commit failed. setup state so that we can flush again.
+ ROCKS_LOG_BUFFER(log_buffer, "Level-0 commit table #%" PRIu64
+ ": memtable #%" PRIu64 " failed",
+ m->file_number_, mem_id);
+ m->flush_completed_ = false;
+ m->flush_in_progress_ = false;
+ m->edit_.Clear();
+ num_flush_not_started_++;
+ m->file_number_ = 0;
+ imm_flush_needed.store(true, std::memory_order_release);
+ ++mem_id;
+ }
+ }
+ }
+ }
+ commit_in_progress_ = false;
+ return s;
+}
+
+// New memtables are inserted at the front of the list.
+void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
+ assert(static_cast<int>(current_->memlist_.size()) >= num_flush_not_started_);
+ InstallNewVersion();
+ // this method is used to move mutable memtable into an immutable list.
+ // since mutable memtable is already refcounted by the DBImpl,
+ // and when moving to the imutable list we don't unref it,
+ // we don't have to ref the memtable here. we just take over the
+ // reference from the DBImpl.
+ current_->Add(m, to_delete);
+ m->MarkImmutable();
+ num_flush_not_started_++;
+ if (num_flush_not_started_ == 1) {
+ imm_flush_needed.store(true, std::memory_order_release);
+ }
+ UpdateCachedValuesFromMemTableListVersion();
+ ResetTrimHistoryNeeded();
+}
+
+void MemTableList::TrimHistory(autovector<MemTable*>* to_delete, size_t usage) {
+ InstallNewVersion();
+ current_->TrimHistory(to_delete, usage);
+ UpdateCachedValuesFromMemTableListVersion();
+ ResetTrimHistoryNeeded();
+}
+
+// Returns an estimate of the number of bytes of data in use.
+size_t MemTableList::ApproximateUnflushedMemTablesMemoryUsage() {
+ size_t total_size = 0;
+ for (auto& memtable : current_->memlist_) {
+ total_size += memtable->ApproximateMemoryUsage();
+ }
+ return total_size;
+}
+
+size_t MemTableList::ApproximateMemoryUsage() { return current_memory_usage_; }
+
+size_t MemTableList::ApproximateMemoryUsageExcludingLast() const {
+ const size_t usage =
+ current_memory_usage_excluding_last_.load(std::memory_order_relaxed);
+ return usage;
+}
+
+bool MemTableList::HasHistory() const {
+ const bool has_history = current_has_history_.load(std::memory_order_relaxed);
+ return has_history;
+}
+
+void MemTableList::UpdateCachedValuesFromMemTableListVersion() {
+ const size_t total_memtable_size =
+ current_->ApproximateMemoryUsageExcludingLast();
+ current_memory_usage_excluding_last_.store(total_memtable_size,
+ std::memory_order_relaxed);
+
+ const bool has_history = current_->HasHistory();
+ current_has_history_.store(has_history, std::memory_order_relaxed);
+}
+
+uint64_t MemTableList::ApproximateOldestKeyTime() const {
+ if (!current_->memlist_.empty()) {
+ return current_->memlist_.back()->ApproximateOldestKeyTime();
+ }
+ return std::numeric_limits<uint64_t>::max();
+}
+
+void MemTableList::InstallNewVersion() {
+ if (current_->refs_ == 1) {
+ // we're the only one using the version, just keep using it
+ } else {
+ // somebody else holds the current version, we need to create new one
+ MemTableListVersion* version = current_;
+ current_ = new MemTableListVersion(&current_memory_usage_, current_);
+ current_->Ref();
+ version->Unref();
+ }
+}
+
+uint64_t MemTableList::PrecomputeMinLogContainingPrepSection(
+ const autovector<MemTable*>& memtables_to_flush) {
+ uint64_t min_log = 0;
+
+ for (auto& m : current_->memlist_) {
+ // Assume the list is very short, we can live with O(m*n). We can optimize
+ // if the performance has some problem.
+ bool should_skip = false;
+ for (MemTable* m_to_flush : memtables_to_flush) {
+ if (m == m_to_flush) {
+ should_skip = true;
+ break;
+ }
+ }
+ if (should_skip) {
+ continue;
+ }
+
+ auto log = m->GetMinLogContainingPrepSection();
+
+ if (log > 0 && (min_log == 0 || log < min_log)) {
+ min_log = log;
+ }
+ }
+
+ return min_log;
+}
+
+// Commit a successful atomic flush in the manifest file.
+Status InstallMemtableAtomicFlushResults(
+ const autovector<MemTableList*>* imm_lists,
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
+ InstrumentedMutex* mu, const autovector<FileMetaData*>& file_metas,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer) {
+ AutoThreadOperationStageUpdater stage_updater(
+ ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
+ mu->AssertHeld();
+
+ size_t num = mems_list.size();
+ assert(cfds.size() == num);
+ if (imm_lists != nullptr) {
+ assert(imm_lists->size() == num);
+ }
+ for (size_t k = 0; k != num; ++k) {
+#ifndef NDEBUG
+ const auto* imm =
+ (imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k);
+ if (!mems_list[k]->empty()) {
+ assert((*mems_list[k])[0]->GetID() == imm->GetEarliestMemTableID());
+ }
+#endif
+ assert(nullptr != file_metas[k]);
+ for (size_t i = 0; i != mems_list[k]->size(); ++i) {
+ assert(i == 0 || (*mems_list[k])[i]->GetEdits()->NumEntries() == 0);
+ (*mems_list[k])[i]->SetFlushCompleted(true);
+ (*mems_list[k])[i]->SetFileNumber(file_metas[k]->fd.GetNumber());
+ }
+ }
+
+ Status s;
+
+ autovector<autovector<VersionEdit*>> edit_lists;
+ uint32_t num_entries = 0;
+ for (const auto mems : mems_list) {
+ assert(mems != nullptr);
+ autovector<VersionEdit*> edits;
+ assert(!mems->empty());
+ edits.emplace_back((*mems)[0]->GetEdits());
+ ++num_entries;
+ edit_lists.emplace_back(edits);
+ }
+ // Mark the version edits as an atomic group if the number of version edits
+ // exceeds 1.
+ if (cfds.size() > 1) {
+ for (auto& edits : edit_lists) {
+ assert(edits.size() == 1);
+ edits[0]->MarkAtomicGroup(--num_entries);
+ }
+ assert(0 == num_entries);
+ }
+
+ // this can release and reacquire the mutex.
+ s = vset->LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
+ db_directory);
+
+ for (size_t k = 0; k != cfds.size(); ++k) {
+ auto* imm = (imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k);
+ imm->InstallNewVersion();
+ }
+
+ if (s.ok() || s.IsColumnFamilyDropped()) {
+ for (size_t i = 0; i != cfds.size(); ++i) {
+ if (cfds[i]->IsDropped()) {
+ continue;
+ }
+ auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i);
+ for (auto m : *mems_list[i]) {
+ assert(m->GetFileNumber() > 0);
+ uint64_t mem_id = m->GetID();
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] Level-0 commit table #%" PRIu64
+ ": memtable #%" PRIu64 " done",
+ cfds[i]->GetName().c_str(), m->GetFileNumber(),
+ mem_id);
+ imm->current_->Remove(m, to_delete);
+ imm->UpdateCachedValuesFromMemTableListVersion();
+ imm->ResetTrimHistoryNeeded();
+ }
+ }
+ } else {
+ for (size_t i = 0; i != cfds.size(); ++i) {
+ auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i);
+ for (auto m : *mems_list[i]) {
+ uint64_t mem_id = m->GetID();
+ ROCKS_LOG_BUFFER(log_buffer,
+ "[%s] Level-0 commit table #%" PRIu64
+ ": memtable #%" PRIu64 " failed",
+ cfds[i]->GetName().c_str(), m->GetFileNumber(),
+ mem_id);
+ m->SetFlushCompleted(false);
+ m->SetFlushInProgress(false);
+ m->GetEdits()->Clear();
+ m->SetFileNumber(0);
+ imm->num_flush_not_started_++;
+ }
+ imm->imm_flush_needed.store(true, std::memory_order_release);
+ }
+ }
+
+ return s;
+}
+
+void MemTableList::RemoveOldMemTables(uint64_t log_number,
+ autovector<MemTable*>* to_delete) {
+ assert(to_delete != nullptr);
+ InstallNewVersion();
+ auto& memlist = current_->memlist_;
+ autovector<MemTable*> old_memtables;
+ for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
+ MemTable* mem = *it;
+ if (mem->GetNextLogNumber() > log_number) {
+ break;
+ }
+ old_memtables.push_back(mem);
+ }
+
+ for (auto it = old_memtables.begin(); it != old_memtables.end(); ++it) {
+ MemTable* mem = *it;
+ current_->Remove(mem, to_delete);
+ --num_flush_not_started_;
+ if (0 == num_flush_not_started_) {
+ imm_flush_needed.store(false, std::memory_order_release);
+ }
+ }
+
+ UpdateCachedValuesFromMemTableListVersion();
+ ResetTrimHistoryNeeded();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/memtable_list.h b/storage/rocksdb/rocksdb/db/memtable_list.h
new file mode 100644
index 00000000..a6acf6a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/memtable_list.h
@@ -0,0 +1,422 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <deque>
+#include <limits>
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/logs_with_prep_tracker.h"
+#include "db/memtable.h"
+#include "db/range_del_aggregator.h"
+#include "file/filename.h"
+#include "logging/log_buffer.h"
+#include "monitoring/instrumented_mutex.h"
+#include "rocksdb/db.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/types.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyData;
+class InternalKeyComparator;
+class InstrumentedMutex;
+class MergeIteratorBuilder;
+class MemTableList;
+
+struct FlushJobInfo;
+
+// keeps a list of immutable memtables in a vector. the list is immutable
+// if refcount is bigger than one. It is used as a state for Get() and
+// Iterator code paths
+//
+// This class is not thread-safe. External synchronization is required
+// (such as holding the db mutex or being on the write thread).
+class MemTableListVersion {
+ public:
+ explicit MemTableListVersion(size_t* parent_memtable_list_memory_usage,
+ MemTableListVersion* old = nullptr);
+ explicit MemTableListVersion(size_t* parent_memtable_list_memory_usage,
+ int max_write_buffer_number_to_maintain,
+ int64_t max_write_buffer_size_to_maintain);
+
+ void Ref();
+ void Unref(autovector<MemTable*>* to_delete = nullptr);
+
+ // Search all the memtables starting from the most recent one.
+ // Return the most recent value found, if any.
+ //
+ // If any operation was found for this key, its most recent sequence number
+ // will be stored in *seq on success (regardless of whether true/false is
+ // returned). Otherwise, *seq will be set to kMaxSequenceNumber.
+ bool Get(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
+ const ReadOptions& read_opts, ReadCallback* callback = nullptr,
+ bool* is_blob_index = nullptr);
+
+ bool Get(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ const ReadOptions& read_opts, ReadCallback* callback = nullptr,
+ bool* is_blob_index = nullptr) {
+ SequenceNumber seq;
+ return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq,
+ read_opts, callback, is_blob_index);
+ }
+
+ void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
+ ReadCallback* callback, bool* is_blob);
+
+ // Returns all the merge operands corresponding to the key by searching all
+ // memtables starting from the most recent one.
+ bool GetMergeOperands(const LookupKey& key, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ const ReadOptions& read_opts);
+
+ // Similar to Get(), but searches the Memtable history of memtables that
+ // have already been flushed. Should only be used from in-memory only
+ // queries (such as Transaction validation) as the history may contain
+ // writes that are also present in the SST files.
+ bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ SequenceNumber* seq, const ReadOptions& read_opts,
+ bool* is_blob_index = nullptr);
+ bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ const ReadOptions& read_opts,
+ bool* is_blob_index = nullptr) {
+ SequenceNumber seq;
+ return GetFromHistory(key, value, s, merge_context,
+ max_covering_tombstone_seq, &seq, read_opts,
+ is_blob_index);
+ }
+
+ Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena,
+ RangeDelAggregator* range_del_agg);
+
+ void AddIterators(const ReadOptions& options,
+ std::vector<InternalIterator*>* iterator_list,
+ Arena* arena);
+
+ void AddIterators(const ReadOptions& options,
+ MergeIteratorBuilder* merge_iter_builder);
+
+ uint64_t GetTotalNumEntries() const;
+
+ uint64_t GetTotalNumDeletes() const;
+
+ MemTable::MemTableStats ApproximateStats(const Slice& start_ikey,
+ const Slice& end_ikey);
+
+ // Returns the value of MemTable::GetEarliestSequenceNumber() on the most
+ // recent MemTable in this list or kMaxSequenceNumber if the list is empty.
+ // If include_history=true, will also search Memtables in MemTableList
+ // History.
+ SequenceNumber GetEarliestSequenceNumber(bool include_history = false) const;
+
+ private:
+ friend class MemTableList;
+
+ friend Status InstallMemtableAtomicFlushResults(
+ const autovector<MemTableList*>* imm_lists,
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<const autovector<MemTable*>*>& mems_list,
+ VersionSet* vset, InstrumentedMutex* mu,
+ const autovector<FileMetaData*>& file_meta,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer);
+
+ // REQUIRE: m is an immutable memtable
+ void Add(MemTable* m, autovector<MemTable*>* to_delete);
+ // REQUIRE: m is an immutable memtable
+ void Remove(MemTable* m, autovector<MemTable*>* to_delete);
+
+ void TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
+
+ bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
+ std::string* value, Status* s, MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ SequenceNumber* seq, const ReadOptions& read_opts,
+ ReadCallback* callback = nullptr,
+ bool* is_blob_index = nullptr);
+
+ void AddMemTable(MemTable* m);
+
+ void UnrefMemTable(autovector<MemTable*>* to_delete, MemTable* m);
+
+ // Calculate the total amount of memory used by memlist_ and memlist_history_
+ // excluding the last MemTable in memlist_history_. The reason for excluding
+ // the last MemTable is to see if dropping the last MemTable will keep total
+ // memory usage above or equal to max_write_buffer_size_to_maintain_
+ size_t ApproximateMemoryUsageExcludingLast() const;
+
+ // Whether this version contains flushed memtables that are only kept around
+ // for transaction conflict checking.
+ bool HasHistory() const { return !memlist_history_.empty(); }
+
+ bool MemtableLimitExceeded(size_t usage);
+
+ // Immutable MemTables that have not yet been flushed.
+ std::list<MemTable*> memlist_;
+
+ // MemTables that have already been flushed
+ // (used during Transaction validation)
+ std::list<MemTable*> memlist_history_;
+
+ // Maximum number of MemTables to keep in memory (including both flushed
+ const int max_write_buffer_number_to_maintain_;
+ // Maximum size of MemTables to keep in memory (including both flushed
+ // and not-yet-flushed tables).
+ const int64_t max_write_buffer_size_to_maintain_;
+
+ int refs_ = 0;
+
+ size_t* parent_memtable_list_memory_usage_;
+};
+
+// This class stores references to all the immutable memtables.
+// The memtables are flushed to L0 as soon as possible and in
+// any order. If there are more than one immutable memtable, their
+// flushes can occur concurrently. However, they are 'committed'
+// to the manifest in FIFO order to maintain correctness and
+// recoverability from a crash.
+//
+//
+// Other than imm_flush_needed and imm_trim_needed, this class is not
+// thread-safe and requires external synchronization (such as holding the db
+// mutex or being on the write thread.)
+class MemTableList {
+ public:
+ // A list of memtables.
+ explicit MemTableList(int min_write_buffer_number_to_merge,
+ int max_write_buffer_number_to_maintain,
+ int64_t max_write_buffer_size_to_maintain)
+ : imm_flush_needed(false),
+ imm_trim_needed(false),
+ min_write_buffer_number_to_merge_(min_write_buffer_number_to_merge),
+ current_(new MemTableListVersion(&current_memory_usage_,
+ max_write_buffer_number_to_maintain,
+ max_write_buffer_size_to_maintain)),
+ num_flush_not_started_(0),
+ commit_in_progress_(false),
+ flush_requested_(false),
+ current_memory_usage_(0),
+ current_memory_usage_excluding_last_(0),
+ current_has_history_(false) {
+ current_->Ref();
+ }
+
+ // Should not delete MemTableList without making sure MemTableList::current()
+ // is Unref()'d.
+ ~MemTableList() {}
+
+ MemTableListVersion* current() const { return current_; }
+
+ // so that background threads can detect non-nullptr pointer to
+ // determine whether there is anything more to start flushing.
+ std::atomic<bool> imm_flush_needed;
+
+ std::atomic<bool> imm_trim_needed;
+
+ // Returns the total number of memtables in the list that haven't yet
+ // been flushed and logged.
+ int NumNotFlushed() const;
+
+ // Returns total number of memtables in the list that have been
+ // completely flushed and logged.
+ int NumFlushed() const;
+
+ // Returns true if there is at least one memtable on which flush has
+ // not yet started.
+ bool IsFlushPending() const;
+
+ // Returns the earliest memtables that needs to be flushed. The returned
+ // memtables are guaranteed to be in the ascending order of created time.
+ void PickMemtablesToFlush(const uint64_t* max_memtable_id,
+ autovector<MemTable*>* mems);
+
+ // Reset status of the given memtable list back to pending state so that
+ // they can get picked up again on the next round of flush.
+ void RollbackMemtableFlush(const autovector<MemTable*>& mems,
+ uint64_t file_number);
+
+ // Try commit a successful flush in the manifest file. It might just return
+ // Status::OK letting a concurrent flush to do the actual the recording.
+ Status TryInstallMemtableFlushResults(
+ ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
+ const autovector<MemTable*>& m, LogsWithPrepTracker* prep_tracker,
+ VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer,
+ std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info);
+
+ // New memtables are inserted at the front of the list.
+ // Takes ownership of the referenced held on *m by the caller of Add().
+ void Add(MemTable* m, autovector<MemTable*>* to_delete);
+
+ // Returns an estimate of the number of bytes of data in use.
+ size_t ApproximateMemoryUsage();
+
+ // Returns the cached current_memory_usage_excluding_last_ value.
+ size_t ApproximateMemoryUsageExcludingLast() const;
+
+ // Returns the cached current_has_history_ value.
+ bool HasHistory() const;
+
+ // Updates current_memory_usage_excluding_last_ and current_has_history_
+ // from MemTableListVersion. Must be called whenever InstallNewVersion is
+ // called.
+ void UpdateCachedValuesFromMemTableListVersion();
+
+ // `usage` is the current size of the mutable Memtable. When
+ // max_write_buffer_size_to_maintain is used, total size of mutable and
+ // immutable memtables is checked against it to decide whether to trim
+ // memtable list.
+ void TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
+
+ // Returns an estimate of the number of bytes of data used by
+ // the unflushed mem-tables.
+ size_t ApproximateUnflushedMemTablesMemoryUsage();
+
+ // Returns an estimate of the timestamp of the earliest key.
+ uint64_t ApproximateOldestKeyTime() const;
+
+ // Request a flush of all existing memtables to storage. This will
+ // cause future calls to IsFlushPending() to return true if this list is
+ // non-empty (regardless of the min_write_buffer_number_to_merge
+ // parameter). This flush request will persist until the next time
+ // PickMemtablesToFlush() is called.
+ void FlushRequested() { flush_requested_ = true; }
+
+ bool HasFlushRequested() { return flush_requested_; }
+
+ // Returns true if a trim history should be scheduled and the caller should
+ // be the one to schedule it
+ bool MarkTrimHistoryNeeded() {
+ auto expected = false;
+ return imm_trim_needed.compare_exchange_strong(
+ expected, true, std::memory_order_relaxed, std::memory_order_relaxed);
+ }
+
+ void ResetTrimHistoryNeeded() {
+ auto expected = true;
+ imm_trim_needed.compare_exchange_strong(
+ expected, false, std::memory_order_relaxed, std::memory_order_relaxed);
+ }
+
+ // Copying allowed
+ // MemTableList(const MemTableList&);
+ // void operator=(const MemTableList&);
+
+ size_t* current_memory_usage() { return &current_memory_usage_; }
+
+ // Returns the min log containing the prep section after memtables listsed in
+ // `memtables_to_flush` are flushed and their status is persisted in manifest.
+ uint64_t PrecomputeMinLogContainingPrepSection(
+ const autovector<MemTable*>& memtables_to_flush);
+
+ uint64_t GetEarliestMemTableID() const {
+ auto& memlist = current_->memlist_;
+ if (memlist.empty()) {
+ return std::numeric_limits<uint64_t>::max();
+ }
+ return memlist.back()->GetID();
+ }
+
+ uint64_t GetLatestMemTableID() const {
+ auto& memlist = current_->memlist_;
+ if (memlist.empty()) {
+ return 0;
+ }
+ return memlist.front()->GetID();
+ }
+
+ void AssignAtomicFlushSeq(const SequenceNumber& seq) {
+ const auto& memlist = current_->memlist_;
+ // Scan the memtable list from new to old
+ for (auto it = memlist.begin(); it != memlist.end(); ++it) {
+ MemTable* mem = *it;
+ if (mem->atomic_flush_seqno_ == kMaxSequenceNumber) {
+ mem->atomic_flush_seqno_ = seq;
+ } else {
+ // Earlier memtables must have been assigned a atomic flush seq, no
+ // need to continue scan.
+ break;
+ }
+ }
+ }
+
+ // Used only by DBImplSecondary during log replay.
+ // Remove memtables whose data were written before the WAL with log_number
+ // was created, i.e. mem->GetNextLogNumber() <= log_number. The memtables are
+ // not freed, but put into a vector for future deref and reclamation.
+ void RemoveOldMemTables(uint64_t log_number,
+ autovector<MemTable*>* to_delete);
+
+ private:
+ friend Status InstallMemtableAtomicFlushResults(
+ const autovector<MemTableList*>* imm_lists,
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<const autovector<MemTable*>*>& mems_list,
+ VersionSet* vset, InstrumentedMutex* mu,
+ const autovector<FileMetaData*>& file_meta,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer);
+
+ // DB mutex held
+ void InstallNewVersion();
+
+ const int min_write_buffer_number_to_merge_;
+
+ MemTableListVersion* current_;
+
+ // the number of elements that still need flushing
+ int num_flush_not_started_;
+
+ // committing in progress
+ bool commit_in_progress_;
+
+ // Requested a flush of memtables to storage. It's possible to request that
+ // a subset of memtables be flushed.
+ bool flush_requested_;
+
+ // The current memory usage.
+ size_t current_memory_usage_;
+
+ // Cached value of current_->ApproximateMemoryUsageExcludingLast().
+ std::atomic<size_t> current_memory_usage_excluding_last_;
+
+ // Cached value of current_->HasHistory().
+ std::atomic<bool> current_has_history_;
+};
+
+// Installs memtable atomic flush results.
+// In most cases, imm_lists is nullptr, and the function simply uses the
+// immutable memtable lists associated with the cfds. There are unit tests that
+// installs flush results for external immutable memtable lists other than the
+// cfds' own immutable memtable lists, e.g. MemTableLIstTest. In this case,
+// imm_lists parameter is not nullptr.
+extern Status InstallMemtableAtomicFlushResults(
+ const autovector<MemTableList*>* imm_lists,
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
+ InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
+ autovector<MemTable*>* to_delete, Directory* db_directory,
+ LogBuffer* log_buffer);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/memtable_list_test.cc b/storage/rocksdb/rocksdb/db/memtable_list_test.cc
new file mode 100644
index 00000000..a92bc6c7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/memtable_list_test.cc
@@ -0,0 +1,922 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <string>
+#include <vector>
+#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<ColumnFamilyHandle*> handles;
+ std::atomic<uint64_t> file_number;
+
+ MemTableListTest() : db(nullptr), file_number(1) {
+ dbname = test::PerThreadDBPath("memtable_list_test");
+ options.create_if_missing = true;
+ DestroyDB(dbname, options);
+ }
+
+ // Create a test db if not yet created
+ void CreateDB() {
+ if (db == nullptr) {
+ options.create_if_missing = true;
+ DestroyDB(dbname, options);
+ // Open DB only with default column family
+ ColumnFamilyOptions cf_options;
+ std::vector<ColumnFamilyDescriptor> 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<uint64_t>::max());
+ cf_opt2.cf_paths.emplace_back(dbname + "_two_1",
+ std::numeric_limits<uint64_t>::max());
+ int sz = static_cast<int>(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<ColumnFamilyDescriptor> cf_descs(handles.size());
+ for (int i = 0; i != static_cast<int>(handles.size()); ++i) {
+ handles[i]->GetDescriptor(&cf_descs[i]);
+ }
+ for (auto h : handles) {
+ if (h) {
+ db->DestroyColumnFamilyHandle(h);
+ }
+ }
+ handles.clear();
+ delete db;
+ db = nullptr;
+ 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<MemTable*>& m, autovector<MemTable*>* to_delete) {
+ // Create a mock Logger
+ test::NullLogger logger;
+ LogBuffer log_buffer(DEBUG_LEVEL, &logger);
+
+ CreateDB();
+ // Create a mock VersionSet
+ DBOptions db_options;
+ db_options.file_system = FileSystem::Default();
+ ImmutableDBOptions immutable_db_options(db_options);
+ EnvOptions env_options;
+ std::shared_ptr<Cache> 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);
+ std::vector<ColumnFamilyDescriptor> 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);
+ // Create dummy mutex.
+ InstrumentedMutex mutex;
+ InstrumentedMutexLock l(&mutex);
+ std::list<std::unique_ptr<FlushJobInfo>> 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);
+ return s;
+ }
+
+ // Calls MemTableList::InstallMemtableFlushResults() and sets up all
+ // structures needed to call this function.
+ Status Mock_InstallMemtableAtomicFlushResults(
+ autovector<MemTableList*>& lists, const autovector<uint32_t>& cf_ids,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<const autovector<MemTable*>*>& mems_list,
+ autovector<MemTable*>* to_delete) {
+ // Create a mock Logger
+ test::NullLogger logger;
+ LogBuffer log_buffer(DEBUG_LEVEL, &logger);
+
+ CreateDB();
+ // Create a mock VersionSet
+ DBOptions db_options;
+ db_options.file_system.reset(new LegacyFileSystemWrapper(db_options.env));
+
+ ImmutableDBOptions immutable_db_options(db_options);
+ EnvOptions env_options;
+ std::shared_ptr<Cache> 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);
+ std::vector<ColumnFamilyDescriptor> 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<ColumnFamilyData*> cfds;
+ for (int i = 0; i != static_cast<int>(cf_ids.size()); ++i) {
+ cfds.emplace_back(column_family_set->GetColumnFamily(cf_ids[i]));
+ EXPECT_NE(nullptr, cfds[i]);
+ }
+ std::vector<FileMetaData> 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<FileMetaData*> file_meta_ptrs;
+ for (auto& meta : file_metas) {
+ file_meta_ptrs.push_back(&meta);
+ }
+ InstrumentedMutex mutex;
+ InstrumentedMutexLock l(&mutex);
+ return InstallMemtableAtomicFlushResults(
+ &lists, cfds, mutable_cf_options_list, mems_list, &versions, &mutex,
+ file_meta_ptrs, 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<MemTable*> mems;
+ list.PickMemtablesToFlush(nullptr /* memtable_id */, &mems);
+ ASSERT_EQ(0, mems.size());
+
+ autovector<MemTable*> 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<MemTable*> to_delete;
+
+ LookupKey lkey("key1", seq);
+ bool found = list.current()->Get(lkey, &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_FALSE(found);
+
+ // Create a MemTable
+ InternalKeyComparator cmp(BytewiseComparator());
+ auto factory = std::make_shared<SkipListFactory>();
+ options.memtable_factory = factory;
+ ImmutableCFOptions 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.
+ mem->Add(++seq, kTypeDeletion, "key1", "");
+ mem->Add(++seq, kTypeValue, "key2", "value2");
+ mem->Add(++seq, kTypeValue, "key1", "value1");
+ mem->Add(++seq, kTypeValue, "key2", "value2.2");
+
+ // Fetch the newly written keys
+ merge_context.Clear();
+ found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_TRUE(s.ok() && found);
+ ASSERT_EQ(value, "value1");
+
+ merge_context.Clear();
+ found = mem->Get(LookupKey("key1", 2), &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ // 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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ 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
+ 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();
+
+ mem2->Add(++seq, kTypeDeletion, "key1", "");
+ mem2->Add(++seq, kTypeValue, "key2", "value2.3");
+
+ // Add second memtable to list
+ list.Add(mem2, &to_delete);
+
+ // Fetch keys via MemTableList
+ merge_context.Clear();
+ found =
+ list.current()->Get(LookupKey("key1", seq), &value, &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, &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, &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, &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 = 2000;
+ 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<MemTable*> to_delete;
+
+ LookupKey lkey("key1", seq);
+ bool found = list.current()->Get(lkey, &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_FALSE(found);
+
+ // Create a MemTable
+ InternalKeyComparator cmp(BytewiseComparator());
+ auto factory = std::make_shared<SkipListFactory>();
+ options.memtable_factory = factory;
+ ImmutableCFOptions 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.
+ mem->Add(++seq, kTypeDeletion, "key1", "");
+ mem->Add(++seq, kTypeValue, "key2", "value2");
+ mem->Add(++seq, kTypeValue, "key2", "value2.2");
+
+ // Fetch the newly written keys
+ merge_context.Clear();
+ found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ // 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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_TRUE(s.ok() && found);
+ ASSERT_EQ(value, "value2.2");
+
+ // Add memtable to list
+ 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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_TRUE(found && s.IsNotFound());
+
+ merge_context.Clear();
+ found =
+ list.current()->Get(LookupKey("key2", seq), &value, &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<MemTable*> to_flush;
+ list.PickMemtablesToFlush(nullptr /* 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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_FALSE(found);
+
+ merge_context.Clear();
+ found =
+ list.current()->Get(LookupKey("key2", seq), &value, &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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_TRUE(found && s.IsNotFound());
+
+ merge_context.Clear();
+ found = list.current()->GetFromHistory(
+ LookupKey("key2", seq), &value, &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();
+
+ mem2->Add(++seq, kTypeDeletion, "key1", "");
+ mem2->Add(++seq, kTypeValue, "key3", "value3");
+
+ // Add second memtable to list
+ list.Add(mem2, &to_delete);
+ ASSERT_EQ(0, to_delete.size());
+
+ to_flush.clear();
+ list.PickMemtablesToFlush(nullptr /* 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();
+ 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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_FALSE(found);
+
+ merge_context.Clear();
+ found =
+ list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_FALSE(found);
+
+ merge_context.Clear();
+ found =
+ list.current()->Get(LookupKey("key3", seq), &value, &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, &s, &merge_context,
+ &max_covering_tombstone_seq, ReadOptions());
+ ASSERT_TRUE(found && s.IsNotFound());
+
+ merge_context.Clear();
+ found = list.current()->GetFromHistory(
+ LookupKey("key3", seq), &value, &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, &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<SkipListFactory>();
+ options.memtable_factory = factory;
+ ImmutableCFOptions ioptions(options);
+ InternalKeyComparator cmp(BytewiseComparator());
+ WriteBufferManager wb(options.db_write_buffer_size);
+ autovector<MemTable*> 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<int>(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<MemTable*> 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;
+
+ mem->Add(++seq, kTypeValue, "key1", ToString(i));
+ mem->Add(++seq, kTypeValue, "keyN" + ToString(i), "valueN");
+ mem->Add(++seq, kTypeValue, "keyX" + ToString(i), "value");
+ mem->Add(++seq, kTypeValue, "keyM" + ToString(i), "valueM");
+ mem->Add(++seq, kTypeDeletion, "keyX" + ToString(i), "");
+
+ tables.push_back(mem);
+ }
+
+ // Nothing to flush
+ ASSERT_FALSE(list.IsFlushPending());
+ ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
+ autovector<MemTable*> to_flush;
+ list.PickMemtablesToFlush(nullptr /* 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(nullptr /* 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(nullptr /* 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(nullptr /* 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<MemTable*> to_flush2;
+ list.PickMemtablesToFlush(nullptr /* 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(nullptr /* 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(nullptr /* memtable_id */, &to_flush);
+ // Should pick 4 of 5 since 1 table has been picked in to_flush2
+ ASSERT_EQ(4, to_flush.size());
+ ASSERT_EQ(5, list.NumNotFlushed());
+ ASSERT_FALSE(list.IsFlushPending());
+ ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
+
+ // Pick tables to flush again
+ autovector<MemTable*> to_flush3;
+ list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush3);
+ ASSERT_EQ(0, to_flush3.size()); // nothing not in progress of being flushed
+ ASSERT_EQ(5, list.NumNotFlushed());
+ ASSERT_FALSE(list.IsFlushPending());
+ ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
+
+ // Flush the 4 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,4]. to_flush2 contains
+ // tables[3].
+ // 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<int>(max_write_buffer_size_to_maintain) /
+ static_cast<int>(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 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<int>(max_write_buffer_size_to_maintain) /
+ static_cast<int>(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<MemTable*> to_flush4;
+ list.FlushRequested();
+ ASSERT_TRUE(list.HasFlushRequested());
+ list.PickMemtablesToFlush(&memtable_id, &to_flush4);
+ ASSERT_TRUE(to_flush4.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_flush4);
+ ASSERT_EQ(1, static_cast<int>(to_flush4.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<int>(max_write_buffer_size_to_maintain) /
+ static_cast<int>(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<MemTableList*> lists;
+ autovector<uint32_t> cf_ids;
+ autovector<const MutableCFOptions*> options_list;
+ autovector<const autovector<MemTable*>*> to_flush;
+ autovector<MemTable*> 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<SkipListFactory>();
+ options.memtable_factory = factory;
+ ImmutableCFOptions 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<int64_t>(options.write_buffer_size);
+ autovector<MemTableList*> 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<uint32_t> cf_ids;
+ std::vector<std::vector<MemTable*>> tables(num_cfs);
+ autovector<const MutableCFOptions*> 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;
+
+ mem->Add(++seq, kTypeValue, "key1", ToString(i));
+ mem->Add(++seq, kTypeValue, "keyN" + ToString(i), "valueN");
+ mem->Add(++seq, kTypeValue, "keyX" + ToString(i), "value");
+ mem->Add(++seq, kTypeValue, "keyM" + ToString(i), "valueM");
+ mem->Add(++seq, kTypeDeletion, "keyX" + ToString(i), "");
+
+ elem.push_back(mem);
+ }
+ cf_ids.push_back(cf_id++);
+ }
+
+ std::vector<autovector<MemTable*>> 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(nullptr /* 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<MemTable*> 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<uint64_t> 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<uint64_t>(flush_candidates[i].size()));
+ }
+ autovector<MemTableList*> tmp_lists;
+ autovector<uint32_t> tmp_cf_ids;
+ autovector<const MutableCFOptions*> tmp_options_list;
+ autovector<const autovector<MemTable*>*> 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<uint64_t>(j) <= flush_memtable_ids[i]) {
+ ASSERT_LT(0, tables[i][j]->GetFileNumber());
+ }
+ }
+ ASSERT_EQ(
+ static_cast<size_t>(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/merge_context.h b/storage/rocksdb/rocksdb/db/merge_context.h
new file mode 100644
index 00000000..e1869a34
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_context.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const std::vector<Slice> empty_operand_list;
+
+// The merge context for merging a user key.
+// When doing a Get(), DB will create such a class and pass it when
+// issuing Get() operation to memtables and version_set. The operands
+// will be fetched from the context when issuing partial of full merge.
+class MergeContext {
+ public:
+ // Clear all the operands
+ void Clear() {
+ if (operand_list_) {
+ operand_list_->clear();
+ copied_operands_->clear();
+ }
+ }
+
+ // Push a merge operand
+ void PushOperand(const Slice& operand_slice, bool operand_pinned = false) {
+ Initialize();
+ SetDirectionBackward();
+
+ if (operand_pinned) {
+ operand_list_->push_back(operand_slice);
+ } else {
+ // We need to have our own copy of the operand since it's not pinned
+ copied_operands_->emplace_back(
+ new std::string(operand_slice.data(), operand_slice.size()));
+ operand_list_->push_back(*copied_operands_->back());
+ }
+ }
+
+ // Push back a merge operand
+ void PushOperandBack(const Slice& operand_slice,
+ bool operand_pinned = false) {
+ Initialize();
+ SetDirectionForward();
+
+ if (operand_pinned) {
+ operand_list_->push_back(operand_slice);
+ } else {
+ // We need to have our own copy of the operand since it's not pinned
+ copied_operands_->emplace_back(
+ new std::string(operand_slice.data(), operand_slice.size()));
+ operand_list_->push_back(*copied_operands_->back());
+ }
+ }
+
+ // return total number of operands in the list
+ size_t GetNumOperands() const {
+ if (!operand_list_) {
+ return 0;
+ }
+ return operand_list_->size();
+ }
+
+ // Get the operand at the index.
+ Slice GetOperand(int index) {
+ assert(operand_list_);
+
+ SetDirectionForward();
+ return (*operand_list_)[index];
+ }
+
+ // Same as GetOperandsDirectionForward
+ const std::vector<Slice>& GetOperands() {
+ return GetOperandsDirectionForward();
+ }
+
+ // Return all the operands in the order as they were merged (passed to
+ // FullMerge or FullMergeV2)
+ const std::vector<Slice>& GetOperandsDirectionForward() {
+ if (!operand_list_) {
+ return empty_operand_list;
+ }
+
+ SetDirectionForward();
+ return *operand_list_;
+ }
+
+ // Return all the operands in the reversed order relative to how they were
+ // merged (passed to FullMerge or FullMergeV2)
+ const std::vector<Slice>& GetOperandsDirectionBackward() {
+ if (!operand_list_) {
+ return empty_operand_list;
+ }
+
+ SetDirectionBackward();
+ return *operand_list_;
+ }
+
+ private:
+ void Initialize() {
+ if (!operand_list_) {
+ operand_list_.reset(new std::vector<Slice>());
+ copied_operands_.reset(new std::vector<std::unique_ptr<std::string>>());
+ }
+ }
+
+ void SetDirectionForward() {
+ if (operands_reversed_ == true) {
+ std::reverse(operand_list_->begin(), operand_list_->end());
+ operands_reversed_ = false;
+ }
+ }
+
+ void SetDirectionBackward() {
+ if (operands_reversed_ == false) {
+ std::reverse(operand_list_->begin(), operand_list_->end());
+ operands_reversed_ = true;
+ }
+ }
+
+ // List of operands
+ std::unique_ptr<std::vector<Slice>> operand_list_;
+ // Copy of operands that are not pinned.
+ std::unique_ptr<std::vector<std::unique_ptr<std::string>>> copied_operands_;
+ bool operands_reversed_ = true;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/merge_helper.cc b/storage/rocksdb/rocksdb/db/merge_helper.cc
new file mode 100644
index 00000000..96fe7925
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_helper.cc
@@ -0,0 +1,417 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "db/dbformat.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/statistics.h"
+#include "port/likely.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/merge_operator.h"
+#include "table/format.h"
+#include "table/internal_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator,
+ const MergeOperator* user_merge_operator,
+ const CompactionFilter* compaction_filter,
+ Logger* logger, bool assert_valid_internal_key,
+ SequenceNumber latest_snapshot,
+ const SnapshotChecker* snapshot_checker, int level,
+ Statistics* stats,
+ const std::atomic<bool>* shutting_down)
+ : env_(env),
+ user_comparator_(user_comparator),
+ user_merge_operator_(user_merge_operator),
+ compaction_filter_(compaction_filter),
+ shutting_down_(shutting_down),
+ logger_(logger),
+ assert_valid_internal_key_(assert_valid_internal_key),
+ allow_single_operand_(false),
+ latest_snapshot_(latest_snapshot),
+ snapshot_checker_(snapshot_checker),
+ level_(level),
+ keys_(),
+ filter_timer_(env_),
+ total_filter_time_(0U),
+ stats_(stats) {
+ assert(user_comparator_ != nullptr);
+ if (user_merge_operator_) {
+ allow_single_operand_ = user_merge_operator_->AllowSingleOperand();
+ }
+}
+
+Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator,
+ const Slice& key, const Slice* value,
+ const std::vector<Slice>& operands,
+ std::string* result, Logger* logger,
+ Statistics* statistics, Env* env,
+ Slice* result_operand,
+ bool update_num_ops_stats) {
+ assert(merge_operator != nullptr);
+
+ if (operands.size() == 0) {
+ assert(value != nullptr && result != nullptr);
+ result->assign(value->data(), value->size());
+ return Status::OK();
+ }
+
+ if (update_num_ops_stats) {
+ RecordInHistogram(statistics, READ_NUM_MERGE_OPERANDS,
+ static_cast<uint64_t>(operands.size()));
+ }
+
+ bool success;
+ Slice tmp_result_operand(nullptr, 0);
+ const MergeOperator::MergeOperationInput merge_in(key, value, operands,
+ logger);
+ MergeOperator::MergeOperationOutput merge_out(*result, tmp_result_operand);
+ {
+ // Setup to time the merge
+ StopWatchNano timer(env, statistics != nullptr);
+ PERF_TIMER_GUARD(merge_operator_time_nanos);
+
+ // Do the merge
+ success = merge_operator->FullMergeV2(merge_in, &merge_out);
+
+ if (tmp_result_operand.data()) {
+ // FullMergeV2 result is an existing operand
+ if (result_operand != nullptr) {
+ *result_operand = tmp_result_operand;
+ } else {
+ result->assign(tmp_result_operand.data(), tmp_result_operand.size());
+ }
+ } else if (result_operand) {
+ *result_operand = Slice(nullptr, 0);
+ }
+
+ RecordTick(statistics, MERGE_OPERATION_TOTAL_TIME,
+ statistics ? timer.ElapsedNanos() : 0);
+ }
+
+ if (!success) {
+ RecordTick(statistics, NUMBER_MERGE_FAILURES);
+ return Status::Corruption("Error: Could not perform merge.");
+ }
+
+ return Status::OK();
+}
+
+// PRE: iter points to the first merge type entry
+// POST: iter points to the first entry beyond the merge process (or the end)
+// keys_, operands_ are updated to reflect the merge result.
+// keys_ stores the list of keys encountered while merging.
+// operands_ stores the list of merge operands encountered while merging.
+// keys_[i] corresponds to operands_[i] for each i.
+//
+// TODO: Avoid the snapshot stripe map lookup in CompactionRangeDelAggregator
+// and just pass the StripeRep corresponding to the stripe being merged.
+Status MergeHelper::MergeUntil(InternalIterator* iter,
+ CompactionRangeDelAggregator* range_del_agg,
+ const SequenceNumber stop_before,
+ const bool at_bottom) {
+ // Get a copy of the internal key, before it's invalidated by iter->Next()
+ // Also maintain the list of merge operands seen.
+ assert(HasOperator());
+ keys_.clear();
+ merge_context_.Clear();
+ has_compaction_filter_skip_until_ = false;
+ assert(user_merge_operator_);
+ bool first_key = true;
+
+ // We need to parse the internal key again as the parsed key is
+ // backed by the internal key!
+ // Assume no internal key corruption as it has been successfully parsed
+ // by the caller.
+ // original_key_is_iter variable is just caching the information:
+ // original_key_is_iter == (iter->key().ToString() == original_key)
+ bool original_key_is_iter = true;
+ std::string original_key = iter->key().ToString();
+ // Important:
+ // orig_ikey is backed by original_key if keys_.empty()
+ // orig_ikey is backed by keys_.back() if !keys_.empty()
+ ParsedInternalKey orig_ikey;
+ bool succ = ParseInternalKey(original_key, &orig_ikey);
+ assert(succ);
+ if (!succ) {
+ return Status::Corruption("Cannot parse key in MergeUntil");
+ }
+
+ Status s;
+ bool hit_the_next_user_key = false;
+ for (; iter->Valid(); iter->Next(), original_key_is_iter = false) {
+ if (IsShuttingDown()) {
+ return Status::ShutdownInProgress();
+ }
+
+ ParsedInternalKey ikey;
+ assert(keys_.size() == merge_context_.GetNumOperands());
+
+ if (!ParseInternalKey(iter->key(), &ikey)) {
+ // stop at corrupted key
+ if (assert_valid_internal_key_) {
+ assert(!"Corrupted internal key not expected.");
+ return Status::Corruption("Corrupted internal key not expected.");
+ }
+ break;
+ } else if (first_key) {
+ assert(user_comparator_->Equal(ikey.user_key, orig_ikey.user_key));
+ first_key = false;
+ } else if (!user_comparator_->Equal(ikey.user_key, orig_ikey.user_key)) {
+ // hit a different user key, stop right here
+ hit_the_next_user_key = true;
+ break;
+ } else if (stop_before > 0 && ikey.sequence <= stop_before &&
+ LIKELY(snapshot_checker_ == nullptr ||
+ snapshot_checker_->CheckInSnapshot(ikey.sequence,
+ stop_before) !=
+ SnapshotCheckerResult::kNotInSnapshot)) {
+ // hit an entry that's possibly visible by the previous snapshot, can't
+ // touch that
+ break;
+ }
+
+ // At this point we are guaranteed that we need to process this key.
+
+ assert(IsValueType(ikey.type));
+ if (ikey.type != kTypeMerge) {
+
+ // hit a put/delete/single delete
+ // => merge the put value or a nullptr with operands_
+ // => store result in operands_.back() (and update keys_.back())
+ // => change the entry type to kTypeValue for keys_.back()
+ // We are done! Success!
+
+ // If there are no operands, just return the Status::OK(). That will cause
+ // the compaction iterator to write out the key we're currently at, which
+ // is the put/delete we just encountered.
+ if (keys_.empty()) {
+ return Status::OK();
+ }
+
+ // TODO(noetzli) If the merge operator returns false, we are currently
+ // (almost) silently dropping the put/delete. That's probably not what we
+ // want. Also if we're in compaction and it's a put, it would be nice to
+ // run compaction filter on it.
+ const Slice val = iter->value();
+ const Slice* val_ptr;
+ if (kTypeValue == ikey.type &&
+ (range_del_agg == nullptr ||
+ !range_del_agg->ShouldDelete(
+ ikey, RangeDelPositioningMode::kForwardTraversal))) {
+ val_ptr = &val;
+ } else {
+ val_ptr = nullptr;
+ }
+ std::string merge_result;
+ s = TimedFullMerge(user_merge_operator_, ikey.user_key, val_ptr,
+ merge_context_.GetOperands(), &merge_result, logger_,
+ stats_, env_);
+
+ // We store the result in keys_.back() and operands_.back()
+ // if nothing went wrong (i.e.: no operand corruption on disk)
+ if (s.ok()) {
+ // The original key encountered
+ original_key = std::move(keys_.back());
+ orig_ikey.type = kTypeValue;
+ UpdateInternalKey(&original_key, orig_ikey.sequence, orig_ikey.type);
+ keys_.clear();
+ merge_context_.Clear();
+ keys_.emplace_front(std::move(original_key));
+ merge_context_.PushOperand(merge_result);
+ }
+
+ // move iter to the next entry
+ iter->Next();
+ return s;
+ } else {
+ // hit a merge
+ // => if there is a compaction filter, apply it.
+ // => check for range tombstones covering the operand
+ // => merge the operand into the front of the operands_ list
+ // if not filtered
+ // => then continue because we haven't yet seen a Put/Delete.
+ //
+ // Keep queuing keys and operands until we either meet a put / delete
+ // request or later did a partial merge.
+
+ Slice value_slice = iter->value();
+ // add an operand to the list if:
+ // 1) it's included in one of the snapshots. in that case we *must* write
+ // it out, no matter what compaction filter says
+ // 2) it's not filtered by a compaction filter
+ CompactionFilter::Decision filter =
+ ikey.sequence <= latest_snapshot_
+ ? CompactionFilter::Decision::kKeep
+ : FilterMerge(orig_ikey.user_key, value_slice);
+ if (filter != CompactionFilter::Decision::kRemoveAndSkipUntil &&
+ range_del_agg != nullptr &&
+ range_del_agg->ShouldDelete(
+ iter->key(), RangeDelPositioningMode::kForwardTraversal)) {
+ filter = CompactionFilter::Decision::kRemove;
+ }
+ if (filter == CompactionFilter::Decision::kKeep ||
+ filter == CompactionFilter::Decision::kChangeValue) {
+ if (original_key_is_iter) {
+ // this is just an optimization that saves us one memcpy
+ keys_.push_front(std::move(original_key));
+ } else {
+ keys_.push_front(iter->key().ToString());
+ }
+ if (keys_.size() == 1) {
+ // we need to re-anchor the orig_ikey because it was anchored by
+ // original_key before
+ ParseInternalKey(keys_.back(), &orig_ikey);
+ }
+ if (filter == CompactionFilter::Decision::kKeep) {
+ merge_context_.PushOperand(
+ value_slice, iter->IsValuePinned() /* operand_pinned */);
+ } else { // kChangeValue
+ // Compaction filter asked us to change the operand from value_slice
+ // to compaction_filter_value_.
+ merge_context_.PushOperand(compaction_filter_value_, false);
+ }
+ } else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
+ // Compaction filter asked us to remove this key altogether
+ // (not just this operand), along with some keys following it.
+ keys_.clear();
+ merge_context_.Clear();
+ has_compaction_filter_skip_until_ = true;
+ return Status::OK();
+ }
+ }
+ }
+
+ if (merge_context_.GetNumOperands() == 0) {
+ // we filtered out all the merge operands
+ return Status::OK();
+ }
+
+ // We are sure we have seen this key's entire history if:
+ // at_bottom == true (this does not necessarily mean it is the bottommost
+ // layer, but rather that we are confident the key does not appear on any of
+ // the lower layers, at_bottom == false doesn't mean it does appear, just
+ // that we can't be sure, see Compaction::IsBottommostLevel for details)
+ // AND
+ // we have either encountered another key or end of key history on this
+ // layer.
+ //
+ // When these conditions are true we are able to merge all the keys
+ // using full merge.
+ //
+ // For these cases we are not sure about, we simply miss the opportunity
+ // to combine the keys. Since VersionSet::SetupOtherInputs() always makes
+ // sure that all merge-operands on the same level get compacted together,
+ // this will simply lead to these merge operands moving to the next level.
+ bool surely_seen_the_beginning =
+ (hit_the_next_user_key || !iter->Valid()) && at_bottom;
+ if (surely_seen_the_beginning) {
+ // do a final merge with nullptr as the existing value and say
+ // bye to the merge type (it's now converted to a Put)
+ assert(kTypeMerge == orig_ikey.type);
+ assert(merge_context_.GetNumOperands() >= 1);
+ assert(merge_context_.GetNumOperands() == keys_.size());
+ std::string merge_result;
+ s = TimedFullMerge(user_merge_operator_, orig_ikey.user_key, nullptr,
+ merge_context_.GetOperands(), &merge_result, logger_,
+ stats_, env_);
+ if (s.ok()) {
+ // The original key encountered
+ // We are certain that keys_ is not empty here (see assertions couple of
+ // lines before).
+ original_key = std::move(keys_.back());
+ orig_ikey.type = kTypeValue;
+ UpdateInternalKey(&original_key, orig_ikey.sequence, orig_ikey.type);
+ keys_.clear();
+ merge_context_.Clear();
+ keys_.emplace_front(std::move(original_key));
+ merge_context_.PushOperand(merge_result);
+ }
+ } else {
+ // We haven't seen the beginning of the key nor a Put/Delete.
+ // Attempt to use the user's associative merge function to
+ // merge the stacked merge operands into a single operand.
+ s = Status::MergeInProgress();
+ if (merge_context_.GetNumOperands() >= 2 ||
+ (allow_single_operand_ && merge_context_.GetNumOperands() == 1)) {
+ bool merge_success = false;
+ std::string merge_result;
+ {
+ StopWatchNano timer(env_, stats_ != nullptr);
+ PERF_TIMER_GUARD(merge_operator_time_nanos);
+ merge_success = user_merge_operator_->PartialMergeMulti(
+ orig_ikey.user_key,
+ std::deque<Slice>(merge_context_.GetOperands().begin(),
+ merge_context_.GetOperands().end()),
+ &merge_result, logger_);
+ RecordTick(stats_, MERGE_OPERATION_TOTAL_TIME,
+ stats_ ? timer.ElapsedNanosSafe() : 0);
+ }
+ if (merge_success) {
+ // Merging of operands (associative merge) was successful.
+ // Replace operands with the merge result
+ merge_context_.Clear();
+ merge_context_.PushOperand(merge_result);
+ keys_.erase(keys_.begin(), keys_.end() - 1);
+ }
+ }
+ }
+
+ return s;
+}
+
+MergeOutputIterator::MergeOutputIterator(const MergeHelper* merge_helper)
+ : merge_helper_(merge_helper) {
+ it_keys_ = merge_helper_->keys().rend();
+ it_values_ = merge_helper_->values().rend();
+}
+
+void MergeOutputIterator::SeekToFirst() {
+ const auto& keys = merge_helper_->keys();
+ const auto& values = merge_helper_->values();
+ assert(keys.size() == values.size());
+ it_keys_ = keys.rbegin();
+ it_values_ = values.rbegin();
+}
+
+void MergeOutputIterator::Next() {
+ ++it_keys_;
+ ++it_values_;
+}
+
+CompactionFilter::Decision MergeHelper::FilterMerge(const Slice& user_key,
+ const Slice& value_slice) {
+ if (compaction_filter_ == nullptr) {
+ return CompactionFilter::Decision::kKeep;
+ }
+ if (stats_ != nullptr && ShouldReportDetailedTime(env_, stats_)) {
+ filter_timer_.Start();
+ }
+ compaction_filter_value_.clear();
+ compaction_filter_skip_until_.Clear();
+ auto ret = compaction_filter_->FilterV2(
+ level_, user_key, CompactionFilter::ValueType::kMergeOperand, value_slice,
+ &compaction_filter_value_, compaction_filter_skip_until_.rep());
+ if (ret == CompactionFilter::Decision::kRemoveAndSkipUntil) {
+ if (user_comparator_->Compare(*compaction_filter_skip_until_.rep(),
+ user_key) <= 0) {
+ // Invalid skip_until returned from compaction filter.
+ // Keep the key as per FilterV2 documentation.
+ ret = CompactionFilter::Decision::kKeep;
+ } else {
+ compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
+ kValueTypeForSeek);
+ }
+ }
+ total_filter_time_ += filter_timer_.ElapsedNanosSafe();
+ return ret;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/merge_helper.h b/storage/rocksdb/rocksdb/db/merge_helper.h
new file mode 100644
index 00000000..c0534f08
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_helper.h
@@ -0,0 +1,194 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <deque>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/merge_context.h"
+#include "db/range_del_aggregator.h"
+#include "db/snapshot_checker.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Comparator;
+class Iterator;
+class Logger;
+class MergeOperator;
+class Statistics;
+
+class MergeHelper {
+ public:
+ MergeHelper(Env* env, const Comparator* user_comparator,
+ const MergeOperator* user_merge_operator,
+ const CompactionFilter* compaction_filter, Logger* logger,
+ bool assert_valid_internal_key, SequenceNumber latest_snapshot,
+ const SnapshotChecker* snapshot_checker = nullptr, int level = 0,
+ Statistics* stats = nullptr,
+ const std::atomic<bool>* shutting_down = nullptr);
+
+ // Wrapper around MergeOperator::FullMergeV2() that records perf statistics.
+ // Result of merge will be written to result if status returned is OK.
+ // If operands is empty, the value will simply be copied to result.
+ // Set `update_num_ops_stats` to true if it is from a user read, so that
+ // the latency is sensitive.
+ // Returns one of the following statuses:
+ // - OK: Entries were successfully merged.
+ // - Corruption: Merge operator reported unsuccessful merge.
+ static Status TimedFullMerge(const MergeOperator* merge_operator,
+ const Slice& key, const Slice* value,
+ const std::vector<Slice>& operands,
+ std::string* result, Logger* logger,
+ Statistics* statistics, Env* env,
+ Slice* result_operand = nullptr,
+ bool update_num_ops_stats = false);
+
+ // Merge entries until we hit
+ // - a corrupted key
+ // - a Put/Delete,
+ // - a different user key,
+ // - a specific sequence number (snapshot boundary),
+ // - REMOVE_AND_SKIP_UNTIL returned from compaction filter,
+ // or - the end of iteration
+ // iter: (IN) points to the first merge type entry
+ // (OUT) points to the first entry not included in the merge process
+ // range_del_agg: (IN) filters merge operands covered by range tombstones.
+ // stop_before: (IN) a sequence number that merge should not cross.
+ // 0 means no restriction
+ // at_bottom: (IN) true if the iterator covers the bottem level, which means
+ // we could reach the start of the history of this user key.
+ //
+ // Returns one of the following statuses:
+ // - OK: Entries were successfully merged.
+ // - MergeInProgress: Put/Delete not encountered, and didn't reach the start
+ // of key's history. Output consists of merge operands only.
+ // - Corruption: Merge operator reported unsuccessful merge or a corrupted
+ // key has been encountered and not expected (applies only when compiling
+ // with asserts removed).
+ // - ShutdownInProgress: interrupted by shutdown (*shutting_down == true).
+ //
+ // REQUIRED: The first key in the input is not corrupted.
+ Status MergeUntil(InternalIterator* iter,
+ CompactionRangeDelAggregator* range_del_agg = nullptr,
+ const SequenceNumber stop_before = 0,
+ const bool at_bottom = false);
+
+ // Filters a merge operand using the compaction filter specified
+ // in the constructor. Returns the decision that the filter made.
+ // Uses compaction_filter_value_ and compaction_filter_skip_until_ for the
+ // optional outputs of compaction filter.
+ CompactionFilter::Decision FilterMerge(const Slice& user_key,
+ const Slice& value_slice);
+
+ // Query the merge result
+ // These are valid until the next MergeUntil call
+ // If the merging was successful:
+ // - keys() contains a single element with the latest sequence number of
+ // the merges. The type will be Put or Merge. See IMPORTANT 1 note, below.
+ // - values() contains a single element with the result of merging all the
+ // operands together
+ //
+ // IMPORTANT 1: the key type could change after the MergeUntil call.
+ // Put/Delete + Merge + ... + Merge => Put
+ // Merge + ... + Merge => Merge
+ //
+ // If the merge operator is not associative, and if a Put/Delete is not found
+ // then the merging will be unsuccessful. In this case:
+ // - keys() contains the list of internal keys seen in order of iteration.
+ // - values() contains the list of values (merges) seen in the same order.
+ // values() is parallel to keys() so that the first entry in
+ // keys() is the key associated with the first entry in values()
+ // and so on. These lists will be the same length.
+ // All of these pairs will be merges over the same user key.
+ // See IMPORTANT 2 note below.
+ //
+ // IMPORTANT 2: The entries were traversed in order from BACK to FRONT.
+ // So keys().back() was the first key seen by iterator.
+ // TODO: Re-style this comment to be like the first one
+ const std::deque<std::string>& keys() const { return keys_; }
+ const std::vector<Slice>& values() const {
+ return merge_context_.GetOperands();
+ }
+ uint64_t TotalFilterTime() const { return total_filter_time_; }
+ bool HasOperator() const { return user_merge_operator_ != nullptr; }
+
+ // If compaction filter returned REMOVE_AND_SKIP_UNTIL, this method will
+ // return true and fill *until with the key to which we should skip.
+ // If true, keys() and values() are empty.
+ bool FilteredUntil(Slice* skip_until) const {
+ if (!has_compaction_filter_skip_until_) {
+ return false;
+ }
+ assert(compaction_filter_ != nullptr);
+ assert(skip_until != nullptr);
+ assert(compaction_filter_skip_until_.Valid());
+ *skip_until = compaction_filter_skip_until_.Encode();
+ return true;
+ }
+
+ private:
+ Env* env_;
+ const Comparator* user_comparator_;
+ const MergeOperator* user_merge_operator_;
+ const CompactionFilter* compaction_filter_;
+ const std::atomic<bool>* shutting_down_;
+ Logger* logger_;
+ bool assert_valid_internal_key_; // enforce no internal key corruption?
+ bool allow_single_operand_;
+ SequenceNumber latest_snapshot_;
+ const SnapshotChecker* const snapshot_checker_;
+ int level_;
+
+ // the scratch area that holds the result of MergeUntil
+ // valid up to the next MergeUntil call
+
+ // Keeps track of the sequence of keys seen
+ std::deque<std::string> keys_;
+ // Parallel with keys_; stores the operands
+ mutable MergeContext merge_context_;
+
+ StopWatchNano filter_timer_;
+ uint64_t total_filter_time_;
+ Statistics* stats_;
+
+ bool has_compaction_filter_skip_until_ = false;
+ std::string compaction_filter_value_;
+ InternalKey compaction_filter_skip_until_;
+
+ bool IsShuttingDown() {
+ // This is a best-effort facility, so memory_order_relaxed is sufficient.
+ return shutting_down_ && shutting_down_->load(std::memory_order_relaxed);
+ }
+};
+
+// MergeOutputIterator can be used to iterate over the result of a merge.
+class MergeOutputIterator {
+ public:
+ // The MergeOutputIterator is bound to a MergeHelper instance.
+ explicit MergeOutputIterator(const MergeHelper* merge_helper);
+
+ // Seeks to the first record in the output.
+ void SeekToFirst();
+ // Advances to the next record in the output.
+ void Next();
+
+ Slice key() { return Slice(*it_keys_); }
+ Slice value() { return Slice(*it_values_); }
+ bool Valid() { return it_keys_ != merge_helper_->keys().rend(); }
+
+ private:
+ const MergeHelper* merge_helper_;
+ std::deque<std::string>::const_reverse_iterator it_keys_;
+ std::vector<Slice>::const_reverse_iterator it_values_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/merge_helper_test.cc b/storage/rocksdb/rocksdb/db/merge_helper_test.cc
new file mode 100644
index 00000000..117916c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_helper_test.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <string>
+#include <vector>
+
+#include "db/merge_helper.h"
+#include "rocksdb/comparator.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/coding.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MergeHelperTest : public testing::Test {
+ public:
+ MergeHelperTest() { env_ = Env::Default(); }
+
+ ~MergeHelperTest() override = default;
+
+ Status Run(SequenceNumber stop_before, bool at_bottom,
+ SequenceNumber latest_snapshot = 0) {
+ iter_.reset(new test::VectorIterator(ks_, vs_));
+ iter_->SeekToFirst();
+ merge_helper_.reset(new MergeHelper(env_, BytewiseComparator(),
+ merge_op_.get(), filter_.get(), nullptr,
+ false, latest_snapshot));
+ return merge_helper_->MergeUntil(iter_.get(), nullptr /* range_del_agg */,
+ stop_before, at_bottom);
+ }
+
+ 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_;
+ std::unique_ptr<test::VectorIterator> iter_;
+ std::shared_ptr<MergeOperator> merge_op_;
+ std::unique_ptr<MergeHelper> merge_helper_;
+ std::vector<std::string> ks_;
+ std::vector<std::string> vs_;
+ std::unique_ptr<test::FilterNumber> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/merge_operator.cc b/storage/rocksdb/rocksdb/db/merge_operator.cc
new file mode 100644
index 00000000..75dea432
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_operator.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+/**
+ * Back-end implementation details specific to the Merge Operator.
+ */
+
+#include "rocksdb/merge_operator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool MergeOperator::FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const {
+ // If FullMergeV2 is not implemented, we convert the operand_list to
+ // std::deque<std::string> and pass it to FullMerge
+ std::deque<std::string> operand_list_str;
+ for (auto& op : merge_in.operand_list) {
+ operand_list_str.emplace_back(op.data(), op.size());
+ }
+ return FullMerge(merge_in.key, merge_in.existing_value, operand_list_str,
+ &merge_out->new_value, merge_in.logger);
+}
+
+// The default implementation of PartialMergeMulti, which invokes
+// PartialMerge multiple times internally and merges two operands at
+// a time.
+bool MergeOperator::PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* logger) const {
+ assert(operand_list.size() >= 2);
+ // Simply loop through the operands
+ Slice temp_slice(operand_list[0]);
+
+ for (size_t i = 1; i < operand_list.size(); ++i) {
+ auto& operand = operand_list[i];
+ std::string temp_value;
+ if (!PartialMerge(key, temp_slice, operand, &temp_value, logger)) {
+ return false;
+ }
+ swap(temp_value, *new_value);
+ temp_slice = Slice(*new_value);
+ }
+
+ // The result will be in *new_value. All merges succeeded.
+ return true;
+}
+
+// Given a "real" merge from the library, call the user's
+// associative merge function one-by-one on each of the operands.
+// NOTE: It is assumed that the client's merge-operator will handle any errors.
+bool AssociativeMergeOperator::FullMergeV2(
+ const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const {
+ // Simply loop through the operands
+ Slice temp_existing;
+ const Slice* existing_value = merge_in.existing_value;
+ for (const auto& operand : merge_in.operand_list) {
+ std::string temp_value;
+ if (!Merge(merge_in.key, existing_value, operand, &temp_value,
+ merge_in.logger)) {
+ return false;
+ }
+ swap(temp_value, merge_out->new_value);
+ temp_existing = Slice(merge_out->new_value);
+ existing_value = &temp_existing;
+ }
+
+ // The result will be in *new_value. All merges succeeded.
+ return true;
+}
+
+// Call the user defined simple merge on the operands;
+// NOTE: It is assumed that the client's merge-operator will handle any errors.
+bool AssociativeMergeOperator::PartialMerge(
+ const Slice& key,
+ const Slice& left_operand,
+ const Slice& right_operand,
+ std::string* new_value,
+ Logger* logger) const {
+ return Merge(key, &left_operand, right_operand, new_value, logger);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/merge_test.cc b/storage/rocksdb/rocksdb/db/merge_test.cc
new file mode 100644
index 00000000..3f85f646
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/merge_test.cc
@@ -0,0 +1,504 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <memory>
+#include <iostream>
+
+#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 "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<Slice>& 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> mergeOperator_;
+};
+
+std::shared_ptr<DB> 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<CountMergeOperator>();
+ options.max_successive_merges = max_successive_merges;
+ Status s;
+ DestroyDB(dbname, Options());
+// DBWithTTL is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+ 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);
+ }
+#else
+ assert(!ttl);
+ s = DB::Open(options, dbname, &db);
+#endif // !ROCKSDB_LITE
+ if (!s.ok()) {
+ std::cerr << s.ToString() << std::endl;
+ assert(false);
+ }
+ return std::shared_ptr<DB>(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> db_;
+
+ WriteOptions put_option_;
+ ReadOptions get_option_;
+ WriteOptions delete_option_;
+
+ uint64_t default_;
+
+ public:
+ explicit Counters(std::shared_ptr<DB> 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> 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<Iterator>(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) db->Flush(o);
+
+ assert(counters.assert_get("a") == 1);
+
+ counters.assert_remove("b");
+
+ // defaut value is 0 if non-existent
+ assert(counters.assert_get("b") == 0);
+
+ counters.assert_add("a", 2);
+
+ if (test_compaction) db->Flush(o);
+
+ // 1+2 = 3
+ assert(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(counters.assert_get("b") == sum);
+
+ dumpDb(db);
+
+ if (test_compaction) {
+ db->Flush(o);
+
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+
+ dumpDb(db);
+
+ assert(counters.assert_get("a")== 3);
+ assert(counters.assert_get("b") == sum);
+ }
+}
+
+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(num_merge_operator_calls == max_num_merges + 1);
+ } else {
+ assert(num_merge_operator_calls == 0);
+ }
+
+ resetNumMergeOperatorCalls();
+ assert(counters.assert_get("z") == sum);
+ assert(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;
+ }
+ db->Flush(o);
+ 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;
+ db->Put(ROCKSDB_NAMESPACE::WriteOptions(), "c", "10");
+ for (size_t i = 1; i <= count; i++) {
+ counters->assert_add("c", i);
+ tmp_sum += i;
+ }
+ db->Flush(o);
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ(tmp_sum, counters->assert_get("c"));
+ ASSERT_EQ(num_partial_merge_calls, 0U);
+}
+
+void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
+ size_t num_merges) {
+ assert(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) {
+ batch.Merge(key, merge_value_slice);
+ }
+
+ // Apply to memtable and count the number of merges
+ resetNumMergeOperatorCalls();
+ {
+ Status s = db->Write(WriteOptions(), &batch);
+ assert(s.ok());
+ }
+ ASSERT_EQ(
+ num_merge_operator_calls,
+ static_cast<size_t>(num_merges - (num_merges % (max_num_merges + 1))));
+
+ // Get the value
+ resetNumMergeOperatorCalls();
+ std::string get_value_str;
+ {
+ Status s = db->Get(ReadOptions(), key, &get_value_str);
+ assert(s.ok());
+ }
+ 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<size_t>((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);
+ }
+ }
+
+ 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);
+ 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);
+ 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);
+ 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);
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ }
+
+ DB* reopen_db;
+ ASSERT_OK(DB::Open(Options(), dbname, &reopen_db));
+ std::string value;
+ ASSERT_TRUE(!(reopen_db->Get(ReadOptions(), "test-key", &value).ok()));
+ delete reopen_db;
+ 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"));
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(MergeTest, MergeDbTtlTest) {
+ runTest(test::PerThreadDBPath("merge_testdbttl"),
+ true); // Run test on TTL database
+}
+#endif // !ROCKSDB_LITE
+
+} // 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/storage/rocksdb/rocksdb/db/obsolete_files_test.cc b/storage/rocksdb/rocksdb/db/obsolete_files_test.cc
new file mode 100644
index 00000000..bf018a0e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/obsolete_files_test.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <stdlib.h>
+#include <map>
+#include <string>
+#include <vector>
+#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"
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::flush;
+
+namespace ROCKSDB_NAMESPACE {
+
+class ObsoleteFilesTest : public DBTestBase {
+ public:
+ ObsoleteFilesTest()
+ : DBTestBase("/obsolete_files_test"), 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 = ToString(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_WaitForFlushMemTable());
+ }
+ }
+
+ void CheckFileTypeCounts(const std::string& dir, int required_log,
+ int required_sst, int required_manifest) {
+ std::vector<std::string> filenames;
+ 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 == kLogFile);
+ 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_;
+ 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<Status*>(arg);
+ ASSERT_OK(*p_status);
+ });
+ SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::CloseHelper:PendingPurgeFinished", [&](void* arg) {
+ std::unordered_set<uint64_t>* files_grabbed_for_purge_ptr =
+ reinterpret_cast<std::unordered_set<uint64_t>*>(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();
+ SyncPoint::GetInstance()->DisableProcessing();
+ std::vector<uint64_t> optsfiles_nums;
+ std::vector<bool> optsfiles_keep;
+ SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1", [&](void* arg) {
+ optsfiles_nums.push_back(*reinterpret_cast<uint64_t*>(arg));
+ });
+ SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2", [&](void* arg) {
+ optsfiles_keep.push_back(*reinterpret_cast<bool*>(arg));
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ 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 */));
+ ASSERT_EQ(optsfiles_nums.size(), optsfiles_keep.size());
+
+ Close();
+
+ std::vector<std::string> 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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/options_file_test.cc b/storage/rocksdb/rocksdb/db/options_file_test.cc
new file mode 100644
index 00000000..00427de8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/options_file_test.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include <string>
+
+#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<std::string>* filename_history,
+ int* options_files_count) {
+ std::vector<std::string> filenames;
+ 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<std::string>& past_filenames) {
+ std::vector<std::string> filenames;
+ std::unordered_set<std::string> current_filenames;
+ 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);
+ }
+ }
+}
+} // namespace
+
+TEST_F(OptionsFileTest, NumberOfOptionsFiles) {
+ const int kReopenCount = 20;
+ Options opt;
+ opt.create_if_missing = true;
+ DestroyDB(dbname_, opt);
+ std::unordered_set<std::string> 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)
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ return 0;
+#endif // !(defined NDEBUG) || !defined(OS_WIN)
+}
+#else
+
+#include <cstdio>
+
+int main(int /*argc*/, char** /*argv*/) {
+ printf("Skipped as Options file is not supported in RocksDBLite.\n");
+ return 0;
+}
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/perf_context_test.cc b/storage/rocksdb/rocksdb/db/perf_context_test.cc
new file mode 100644
index 00000000..86f2db7b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/perf_context_test.cc
@@ -0,0 +1,981 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <iostream>
+#include <thread>
+#include <vector>
+
+#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/perf_context.h"
+#include "rocksdb/slice_transform.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<DB> 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) {
+#ifndef ROCKSDB_LITE
+ options.prefix_extractor.reset(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(0));
+ options.memtable_factory.reset(NewHashSkipListRepFactory());
+#endif // ROCKSDB_LITE
+ }
+
+ 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>(db);
+}
+
+class PerfContextTest : public testing::Test {};
+
+TEST_F(PerfContextTest, SeekIntoDeletion) {
+ 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" + ToString(i);
+ std::string value = "v" + ToString(i);
+
+ db->Put(write_options, key, value);
+ }
+
+ for (int i = 0; i < FLAGS_total_keys -1 ; ++i) {
+ std::string key = "k" + ToString(i);
+ 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" + ToString(i);
+ std::string value;
+
+ get_perf_context()->Reset();
+ StopWatchNano timer(Env::Default());
+ 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<Iterator> iter(db->NewIterator(read_options));
+
+ get_perf_context()->Reset();
+ StopWatchNano timer(Env::Default(), 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 uesr 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<Iterator> iter(db->NewIterator(read_options));
+ std::string key = "k" + ToString(i);
+
+ get_perf_context()->Reset();
+ StopWatchNano timer(Env::Default(), 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(Env::Default(), 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 uesr key comparison: \n" << hist_seek.ToString();
+ }
+}
+
+TEST_F(PerfContextTest, StopWatchNanoOverhead) {
+ // profile the timer cost by itself!
+ const int kTotalIterations = 1000000;
+ std::vector<uint64_t> timings(kTotalIterations);
+
+ StopWatchNano timer(Env::Default(), 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<uint64_t> timings(kTotalIterations);
+
+ StopWatch timer(Env::Default(), 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) {
+ 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<int> 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) {
+ std::random_shuffle(keys.begin(), keys.end());
+ }
+#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" + ToString(i);
+ std::string value = "v" + ToString(i);
+
+ std::vector<std::string> values;
+
+ get_perf_context()->Reset();
+ 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" + ToString(i);
+ std::string expected_value = "v" + ToString(i);
+ std::string value;
+
+ std::vector<Slice> multiget_keys = {Slice(key)};
+ std::vector<std::string> 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();
+ db->MultiGet(read_options, multiget_keys, &values);
+ 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 uesr key comparison: \n" << hist_put.ToString()
+ << "Get uesr key comparison: \n" << hist_get.ToString()
+ << "MultiGet uesr 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_GT(total_db_mutex_nanos, 2000U);
+#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" + ToString(i);
+ std::string expected_value = "v" + ToString(i);
+ std::string value;
+
+ std::vector<Slice> multiget_keys = {Slice(key)};
+ std::vector<std::string> 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();
+ db->MultiGet(read_options, multiget_keys, &values);
+ 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 uesr key comparison: \n" << hist_get.ToString()
+ << "ReadOnly MultiGet uesr 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);
+ }
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(PerfContextTest, KeyComparisonCount) {
+ SetPerfLevel(kEnableCount);
+ ProfileQueries();
+
+ SetPerfLevel(kDisable);
+ ProfileQueries();
+
+ SetPerfLevel(kEnableTime);
+ ProfileQueries(true);
+}
+#endif // ROCKSDB_LITE
+
+// 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) {
+ 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<int> keys;
+ for (int i = 0; i < FLAGS_total_keys; ++i) {
+ keys.push_back(i);
+ }
+
+ if (FLAGS_random_key) {
+ std::random_shuffle(keys.begin(), keys.end());
+ }
+
+ HistogramImpl hist_put_time;
+ HistogramImpl hist_wal_time;
+ HistogramImpl hist_time_diff;
+
+ SetPerfLevel(kEnableTime);
+ StopWatchNano timer(Env::Default());
+ for (const int i : keys) {
+ std::string key = "k" + ToString(i);
+ std::string value = "v" + ToString(i);
+
+ get_perf_context()->Reset();
+ timer.Start();
+ 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" + ToString(i);
+ std::string value = "v" + ToString(i);
+
+ std::unique_ptr<Iterator> 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<Iterator> 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);
+ }
+
+ 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<int>(DB_MUTEX_WAIT_MICROS)};
+ for (PerfLevel perf_level_test :
+ {PerfLevel::kEnableTimeExceptForMutex, PerfLevel::kEnableTime}) {
+ for (int c = 0; c < 2; ++c) {
+ InstrumentedMutex mutex(nullptr, Env::Default(), 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);
+ }
+ });
+ Env::Default()->SleepForMicroseconds(100);
+ mutex.Unlock();
+ child_thread.join();
+ }
+ }
+}
+
+TEST_F(PerfContextTest, FalseDBMutexWait) {
+ SetPerfLevel(kEnableTime);
+ int stats_code[] = {0, static_cast<int>(DB_MUTEX_WAIT_MICROS)};
+ for (int c = 0; c < 2; ++c) {
+ InstrumentedMutex mutex(nullptr, Env::Default(), stats_code[c]);
+ InstrumentedCondVar lock(&mutex);
+ get_perf_context()->Reset();
+ mutex.Lock();
+ lock.TimedWait(100);
+ mutex.Unlock();
+ if (stats_code[c] == static_cast<int>(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) {
+ 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) {
+ 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 = ToString(i);
+ std::string key = "k" + i_str;
+ std::string value = "v" + i_str;
+ max_str = max_str > i_str ? max_str : i_str;
+
+ 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<Iterator> 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" + ToString(i));
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("v" + ToString(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<Iterator> 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);
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::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/storage/rocksdb/rocksdb/db/pinned_iterators_manager.h b/storage/rocksdb/rocksdb/db/pinned_iterators_manager.h
new file mode 100644
index 00000000..5e8ad51d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/pinned_iterators_manager.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "table/internal_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// PinnedIteratorsManager will be notified whenever we need to pin an Iterator
+// and it will be responsible for deleting pinned Iterators when they are
+// not needed anymore.
+class PinnedIteratorsManager : public Cleanable {
+ public:
+ PinnedIteratorsManager() : pinning_enabled(false) {}
+ ~PinnedIteratorsManager() {
+ if (pinning_enabled) {
+ ReleasePinnedData();
+ }
+ }
+
+ // Enable Iterators pinning
+ void StartPinning() {
+ assert(pinning_enabled == false);
+ pinning_enabled = true;
+ }
+
+ // Is pinning enabled ?
+ bool PinningEnabled() { return pinning_enabled; }
+
+ // Take ownership of iter and delete it when ReleasePinnedData() is called
+ void PinIterator(InternalIterator* iter, bool arena = false) {
+ if (arena) {
+ PinPtr(iter, &PinnedIteratorsManager::ReleaseArenaInternalIterator);
+ } else {
+ PinPtr(iter, &PinnedIteratorsManager::ReleaseInternalIterator);
+ }
+ }
+
+ typedef void (*ReleaseFunction)(void* arg1);
+ void PinPtr(void* ptr, ReleaseFunction release_func) {
+ assert(pinning_enabled);
+ if (ptr == nullptr) {
+ return;
+ }
+ pinned_ptrs_.emplace_back(ptr, release_func);
+ }
+
+ // Release pinned Iterators
+ inline void ReleasePinnedData() {
+ assert(pinning_enabled == true);
+ pinning_enabled = false;
+
+ // Remove duplicate pointers
+ std::sort(pinned_ptrs_.begin(), pinned_ptrs_.end());
+ auto unique_end = std::unique(pinned_ptrs_.begin(), pinned_ptrs_.end());
+
+ for (auto i = pinned_ptrs_.begin(); i != unique_end; ++i) {
+ void* ptr = i->first;
+ ReleaseFunction release_func = i->second;
+ release_func(ptr);
+ }
+ pinned_ptrs_.clear();
+ // Also do cleanups from the base Cleanable
+ Cleanable::Reset();
+ }
+
+ private:
+ static void ReleaseInternalIterator(void* ptr) {
+ delete reinterpret_cast<InternalIterator*>(ptr);
+ }
+
+ static void ReleaseArenaInternalIterator(void* ptr) {
+ reinterpret_cast<InternalIterator*>(ptr)->~InternalIterator();
+ }
+
+ bool pinning_enabled;
+ std::vector<std::pair<void*, ReleaseFunction>> pinned_ptrs_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/plain_table_db_test.cc b/storage/rocksdb/rocksdb/db/plain_table_db_test.cc
new file mode 100644
index 00000000..d80cc4f6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/plain_table_db_test.cc
@@ -0,0 +1,1375 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <algorithm>
+#include <set>
+
+#include "db/db_impl/db_impl.h"
+#include "db/version_set.h"
+#include "db/write_batch_internal.h"
+#include "file/filename.h"
+#include "logging/logging.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/hash.h"
+#include "util/mutexlock.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+
+using std::unique_ptr;
+
+namespace ROCKSDB_NAMESPACE {
+class PlainTableKeyDecoderTest : public testing::Test {};
+
+TEST_F(PlainTableKeyDecoderTest, ReadNonMmap) {
+ std::string tmp;
+ Random rnd(301);
+ const uint32_t kLength = 2222;
+ Slice contents = test::RandomString(&rnd, kLength, &tmp);
+ test::StringSource* string_source =
+ new test::StringSource(contents, 0, false);
+
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(string_source));
+ std::unique_ptr<PlainTableReaderFileInfo> 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<std::vector<std::pair<uint32_t, uint32_t>>> 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<int> 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<bool> {
+ 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 reinterpret_cast<DBImpl*>(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" + NumberToString(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,
+ const TableProperties* table_properties,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ const ImmutableCFOptions& 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, table_properties,
+ prefix_extractor),
+ expect_bloom_not_match_(expect_bloom_not_match) {
+ Status s = MmapDataIfNeeded();
+ EXPECT_TRUE(s.ok());
+
+ s = PopulateIndex(const_cast<TableProperties*>(table_properties),
+ bloom_bits_per_key, hash_table_ratio, index_sparseness,
+ 2 * 1024 * 1024);
+ EXPECT_TRUE(s.ok());
+
+ TableProperties* props = const_cast<TableProperties*>(table_properties);
+ EXPECT_EQ(column_family_id, static_cast<uint32_t>(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_.reset(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)) {}
+
+ Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool /*prefetch_index_and_filter_in_cache*/) const override {
+ TableProperties* props = nullptr;
+ auto s =
+ ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber,
+ table_reader_options.ioptions, &props,
+ true /* compression_type_missing */);
+ EXPECT_TRUE(s.ok());
+
+ if (store_index_in_file_) {
+ BlockHandle bloom_block_handle;
+ s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
+ table_reader_options.ioptions,
+ BloomBlockBuilder::kBloomBlock, &bloom_block_handle,
+ /* compression_type_missing */ true);
+ EXPECT_TRUE(s.ok());
+
+ BlockHandle index_block_handle;
+ s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
+ table_reader_options.ioptions,
+ PlainTableIndexBuilder::kPlainTableIndexBlock,
+ &index_block_handle, /* compression_type_missing */ true);
+ 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<EncodingType>(
+ DecodeFixed32(encoding_type_prop->second.c_str()));
+
+ std::unique_ptr<PlainTableReader> 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_, props,
+ std::move(file), table_reader_options.ioptions,
+ table_reader_options.prefix_extractor, 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"));
+ 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"));
+ 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"));
+ dbfull()->TEST_FlushMemTable();
+
+ ASSERT_TRUE(dbfull()->GetIntProperty(
+ "rocksdb.estimate-table-readers-mem", &int_num));
+ ASSERT_GT(int_num, 0U);
+
+ TablePropertiesCollection ptc;
+ reinterpret_cast<DB*>(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<Iterator> 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"));
+ dbfull()->TEST_FlushMemTable();
+
+ ASSERT_OK(Put("1000000000000foo", "v2"));
+ dbfull()->TEST_FlushMemTable();
+ ASSERT_EQ("v2", Get("1000000000000foo"));
+
+ ASSERT_OK(Put("0000000000000eee", "v3"));
+ dbfull()->TEST_FlushMemTable();
+ ASSERT_EQ("v3", Get("0000000000000eee"));
+
+ ASSERT_OK(Delete("0000000000000bar"));
+ dbfull()->TEST_FlushMemTable();
+ ASSERT_EQ("NOT_FOUND", Get("0000000000000bar"));
+
+ ASSERT_OK(Put("0000000000000eee", "v5"));
+ ASSERT_OK(Put("9000000000000eee", "v5"));
+ 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"));
+ 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"));
+ 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;
+ }
+ }
+
+ 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"));
+ }
+ 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);
+}
+} // 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], ToString(i)));
+ }
+
+ 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(ToString(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);
+}
+} // 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], ToString(i)));
+ }
+
+ 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(ToString(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 = &comp;
+ 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"));
+ 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"));
+
+ 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());
+
+ 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 = &comp;
+ // 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"));
+
+ 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());
+
+ 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"));
+
+ 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());
+
+ delete iter;
+}
+
+static std::string Key(int i) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "key_______%06d", i);
+ return std::string(buf);
+}
+
+static std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+
+TEST_P(PlainTableDBTest, CompactionTrigger) {
+ Options options = CurrentOptions();
+ options.write_buffer_size = 120 << 10; // 100KB
+ 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<std::string> values;
+ // Write 120KB (10 values, each 12K)
+ for (int i = 0; i < 10; i++) {
+ values.push_back(RandomString(&rnd, 12000));
+ ASSERT_OK(Put(Key(i), values[i]));
+ }
+ ASSERT_OK(Put(Key(999), ""));
+ dbfull()->TEST_WaitForFlushMemTable();
+ ASSERT_EQ(NumTableFilesAtLevel(0), num + 1);
+ }
+
+ //generate one more file in level-0, and should trigger level-0 compaction
+ std::vector<std::string> values;
+ for (int i = 0; i < 12; i++) {
+ values.push_back(RandomString(&rnd, 10000));
+ ASSERT_OK(Put(Key(i), values[i]));
+ }
+ ASSERT_OK(Put(Key(999), ""));
+ 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"));
+ dbfull()->TEST_FlushMemTable();
+
+ options.create_if_missing = false;
+ std::shared_ptr<TableFactory> block_based_factory(
+ NewBlockBasedTableFactory());
+ std::shared_ptr<TableFactory> plain_table_factory(
+ NewPlainTableFactory());
+ std::shared_ptr<TableFactory> 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"));
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as plain table is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/pre_release_callback.h b/storage/rocksdb/rocksdb/db/pre_release_callback.h
new file mode 100644
index 00000000..b74be953
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/pre_release_callback.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DB;
+
+class PreReleaseCallback {
+ public:
+ virtual ~PreReleaseCallback() {}
+
+ // Will be called while on the write thread after the write to the WAL and
+ // before the write to memtable. This is useful if any operation needs to be
+ // done before the write gets visible to the readers, or if we want to reduce
+ // the overhead of locking by updating something sequentially while we are on
+ // the write thread. If the callback fails, this function returns a non-OK
+ // status, the sequence number will not be released, and same status will be
+ // propagated to all the writers in the write group.
+ // seq is the sequence number that is used for this write and will be
+ // released.
+ // is_mem_disabled is currently used for debugging purposes to assert that
+ // the callback is done from the right write queue.
+ // If non-zero, log_number indicates the WAL log to which we wrote.
+ // index >= 0 specifies the order of callback in the same write thread.
+ // total > index specifies the total number of callbacks in the same write
+ // thread. Together with index, could be used to reduce the redundant
+ // operations among the callbacks.
+ virtual Status Callback(SequenceNumber seq, bool is_mem_disabled,
+ uint64_t log_number, size_t index, size_t total) = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/prefix_test.cc b/storage/rocksdb/rocksdb/db/prefix_test.cc
new file mode 100644
index 00000000..c61ec2a1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/prefix_test.cc
@@ -0,0 +1,895 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run this test... Skipping...\n");
+ return 0;
+}
+#else
+
+#include <algorithm>
+#include <iostream>
+#include <vector>
+
+#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/table.h"
+#include "test_util/testharness.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; }
+};
+
+} // namespace
+
+class PrefixTest : public testing::Test {
+ public:
+ std::shared_ptr<DB> 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>(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"));
+ 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"));
+ 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;
+ 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<Iterator> 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());
+
+ SeekIterator(iter.get(), 2, 0);
+ ASSERT_TRUE(!iter->Valid());
+
+ 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());
+
+ SeekIterator(iter.get(), 2, 0);
+ ASSERT_TRUE(!iter->Valid());
+
+ // 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;
+ 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);
+ db->Flush(FlushOptions());
+ TestKey test_key(12346, 8);
+ std::string s;
+ db->Delete(write_options, TestKeyToSlice(s, test_key));
+ db->Flush(FlushOptions());
+ read_options.prefix_same_as_start = true;
+ std::unique_ptr<Iterator> 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());
+ }
+ }
+}
+
+TEST_F(PrefixTest, DynamicPrefixIterator) {
+ while (NextOptions(FLAGS_bucket_count)) {
+ std::cout << "*** Mem table: " << options.memtable_factory->Name()
+ << std::endl;
+ DestroyDB(kDbName, Options());
+ auto db = OpenDb();
+ WriteOptions write_options;
+ ReadOptions read_options;
+
+ std::vector<uint64_t> prefixes;
+ for (uint64_t i = 0; i < FLAGS_total_prefixes; ++i) {
+ prefixes.push_back(i);
+ }
+
+ if (FLAGS_random_prefix) {
+ std::random_shuffle(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(Env::Default(), 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<Iterator> 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" + ToString(0);
+
+ get_perf_context()->Reset();
+ StopWatchNano timer(Env::Default(), 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(Env::Default(), 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());
+ }
+
+ 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;
+ DestroyDB(kDbName, Options());
+ auto db = OpenDb();
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::map<TestKey, std::string, TestKeyComparator> 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<TestKey, std::string, TestKeyComparator> 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) {
+ 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<Iterator> 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;
+ }
+ 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");
+ 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");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(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");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(db.get())->TEST_WaitForFlushMemTable();
+ std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
+ SeekIterator(iter.get(), 1, 5);
+ iter->Prev();
+ 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);
+
+ {
+ 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");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(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");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(db.get())->TEST_WaitForFlushMemTable();
+ std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
+ iter->SeekToLast();
+ ASSERT_EQ(iter->value(), v14);
+ }
+ {
+ 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");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(db.get())->TEST_WaitForFlushMemTable();
+ PutKey(db.get(), write_options, TestKey(1, 1), "v11");
+ PutKey(db.get(), write_options, TestKey(1, 3), "v13");
+ db->Flush(FlushOptions());
+ reinterpret_cast<DBImpl*>(db.get())->TEST_WaitForFlushMemTable();
+ std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
+ iter->SeekToLast();
+ ASSERT_EQ(iter->value(), v14);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ParseCommandLineFlags(&argc, &argv, true);
+ return RUN_ALL_TESTS();
+}
+
+#endif // GFLAGS
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as HashSkipList and HashLinkList are not supported in "
+ "ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/range_del_aggregator.cc b/storage/rocksdb/rocksdb/db/range_del_aggregator.cc
new file mode 100644
index 00000000..1f6a7b13
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_del_aggregator.cc
@@ -0,0 +1,484 @@
+// 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 "db/compaction/compaction_iteration_stats.h"
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/range_del_aggregator.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/version_edit.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/types.h"
+#include "table/internal_iterator.h"
+#include "table/scoped_arena_iterator.h"
+#include "table/table_builder.h"
+#include "util/heap.h"
+#include "util/kv_map.h"
+#include "util/vector_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+TruncatedRangeDelIterator::TruncatedRangeDelIterator(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> iter,
+ const InternalKeyComparator* icmp, const InternalKey* smallest,
+ const InternalKey* largest)
+ : iter_(std::move(iter)),
+ icmp_(icmp),
+ smallest_ikey_(smallest),
+ largest_ikey_(largest) {
+ if (smallest != nullptr) {
+ pinned_bounds_.emplace_back();
+ auto& parsed_smallest = pinned_bounds_.back();
+ if (!ParseInternalKey(smallest->Encode(), &parsed_smallest)) {
+ assert(false);
+ }
+ smallest_ = &parsed_smallest;
+ }
+ if (largest != nullptr) {
+ pinned_bounds_.emplace_back();
+ auto& parsed_largest = pinned_bounds_.back();
+ if (!ParseInternalKey(largest->Encode(), &parsed_largest)) {
+ assert(false);
+ }
+ if (parsed_largest.type == kTypeRangeDeletion &&
+ parsed_largest.sequence == kMaxSequenceNumber) {
+ // The file boundary has been artificially extended by a range tombstone.
+ // We do not need to adjust largest to properly truncate range
+ // tombstones that extend past the boundary.
+ } else if (parsed_largest.sequence == 0) {
+ // The largest key in the sstable has a sequence number of 0. Since we
+ // guarantee that no internal keys with the same user key and sequence
+ // number can exist in a DB, we know that the largest key in this sstable
+ // cannot exist as the smallest key in the next sstable. This further
+ // implies that no range tombstone in this sstable covers largest;
+ // otherwise, the file boundary would have been artificially extended.
+ //
+ // Therefore, we will never truncate a range tombstone at largest, so we
+ // can leave it unchanged.
+ } else {
+ // The same user key may straddle two sstable boundaries. To ensure that
+ // the truncated end key can cover the largest key in this sstable, reduce
+ // its sequence number by 1.
+ parsed_largest.sequence -= 1;
+ }
+ largest_ = &parsed_largest;
+ }
+}
+
+bool TruncatedRangeDelIterator::Valid() const {
+ return iter_->Valid() &&
+ (smallest_ == nullptr ||
+ icmp_->Compare(*smallest_, iter_->parsed_end_key()) < 0) &&
+ (largest_ == nullptr ||
+ icmp_->Compare(iter_->parsed_start_key(), *largest_) < 0);
+}
+
+void TruncatedRangeDelIterator::Next() { iter_->TopNext(); }
+
+void TruncatedRangeDelIterator::Prev() { iter_->TopPrev(); }
+
+void TruncatedRangeDelIterator::InternalNext() { iter_->Next(); }
+
+// NOTE: target is a user key
+void TruncatedRangeDelIterator::Seek(const Slice& target) {
+ if (largest_ != nullptr &&
+ icmp_->Compare(*largest_, ParsedInternalKey(target, kMaxSequenceNumber,
+ kTypeRangeDeletion)) <= 0) {
+ iter_->Invalidate();
+ return;
+ }
+ if (smallest_ != nullptr &&
+ icmp_->user_comparator()->Compare(target, smallest_->user_key) < 0) {
+ iter_->Seek(smallest_->user_key);
+ return;
+ }
+ iter_->Seek(target);
+}
+
+// NOTE: target is a user key
+void TruncatedRangeDelIterator::SeekForPrev(const Slice& target) {
+ if (smallest_ != nullptr &&
+ icmp_->Compare(ParsedInternalKey(target, 0, kTypeRangeDeletion),
+ *smallest_) < 0) {
+ iter_->Invalidate();
+ return;
+ }
+ if (largest_ != nullptr &&
+ icmp_->user_comparator()->Compare(largest_->user_key, target) < 0) {
+ iter_->SeekForPrev(largest_->user_key);
+ return;
+ }
+ iter_->SeekForPrev(target);
+}
+
+void TruncatedRangeDelIterator::SeekToFirst() {
+ if (smallest_ != nullptr) {
+ iter_->Seek(smallest_->user_key);
+ return;
+ }
+ iter_->SeekToTopFirst();
+}
+
+void TruncatedRangeDelIterator::SeekToLast() {
+ if (largest_ != nullptr) {
+ iter_->SeekForPrev(largest_->user_key);
+ return;
+ }
+ iter_->SeekToTopLast();
+}
+
+std::map<SequenceNumber, std::unique_ptr<TruncatedRangeDelIterator>>
+TruncatedRangeDelIterator::SplitBySnapshot(
+ const std::vector<SequenceNumber>& snapshots) {
+ using FragmentedIterPair =
+ std::pair<const SequenceNumber,
+ std::unique_ptr<FragmentedRangeTombstoneIterator>>;
+
+ auto split_untruncated_iters = iter_->SplitBySnapshot(snapshots);
+ std::map<SequenceNumber, std::unique_ptr<TruncatedRangeDelIterator>>
+ split_truncated_iters;
+ std::for_each(
+ split_untruncated_iters.begin(), split_untruncated_iters.end(),
+ [&](FragmentedIterPair& iter_pair) {
+ std::unique_ptr<TruncatedRangeDelIterator> truncated_iter(
+ new TruncatedRangeDelIterator(std::move(iter_pair.second), icmp_,
+ smallest_ikey_, largest_ikey_));
+ split_truncated_iters.emplace(iter_pair.first,
+ std::move(truncated_iter));
+ });
+ return split_truncated_iters;
+}
+
+ForwardRangeDelIterator::ForwardRangeDelIterator(
+ const InternalKeyComparator* icmp)
+ : icmp_(icmp),
+ unused_idx_(0),
+ active_seqnums_(SeqMaxComparator()),
+ active_iters_(EndKeyMinComparator(icmp)),
+ inactive_iters_(StartKeyMinComparator(icmp)) {}
+
+bool ForwardRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) {
+ // Move active iterators that end before parsed.
+ while (!active_iters_.empty() &&
+ icmp_->Compare((*active_iters_.top())->end_key(), parsed) <= 0) {
+ TruncatedRangeDelIterator* iter = PopActiveIter();
+ do {
+ iter->Next();
+ } while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0);
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ // Move inactive iterators that start before parsed.
+ while (!inactive_iters_.empty() &&
+ icmp_->Compare(inactive_iters_.top()->start_key(), parsed) <= 0) {
+ TruncatedRangeDelIterator* iter = PopInactiveIter();
+ while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0) {
+ iter->Next();
+ }
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ return active_seqnums_.empty()
+ ? false
+ : (*active_seqnums_.begin())->seq() > parsed.sequence;
+}
+
+void ForwardRangeDelIterator::Invalidate() {
+ unused_idx_ = 0;
+ active_iters_.clear();
+ active_seqnums_.clear();
+ inactive_iters_.clear();
+}
+
+ReverseRangeDelIterator::ReverseRangeDelIterator(
+ const InternalKeyComparator* icmp)
+ : icmp_(icmp),
+ unused_idx_(0),
+ active_seqnums_(SeqMaxComparator()),
+ active_iters_(StartKeyMaxComparator(icmp)),
+ inactive_iters_(EndKeyMaxComparator(icmp)) {}
+
+bool ReverseRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) {
+ // Move active iterators that start after parsed.
+ while (!active_iters_.empty() &&
+ icmp_->Compare(parsed, (*active_iters_.top())->start_key()) < 0) {
+ TruncatedRangeDelIterator* iter = PopActiveIter();
+ do {
+ iter->Prev();
+ } while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0);
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ // Move inactive iterators that end after parsed.
+ while (!inactive_iters_.empty() &&
+ icmp_->Compare(parsed, inactive_iters_.top()->end_key()) < 0) {
+ TruncatedRangeDelIterator* iter = PopInactiveIter();
+ while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0) {
+ iter->Prev();
+ }
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ return active_seqnums_.empty()
+ ? false
+ : (*active_seqnums_.begin())->seq() > parsed.sequence;
+}
+
+void ReverseRangeDelIterator::Invalidate() {
+ unused_idx_ = 0;
+ active_iters_.clear();
+ active_seqnums_.clear();
+ inactive_iters_.clear();
+}
+
+bool RangeDelAggregator::StripeRep::ShouldDelete(
+ const ParsedInternalKey& parsed, RangeDelPositioningMode mode) {
+ if (!InStripe(parsed.sequence) || IsEmpty()) {
+ return false;
+ }
+ switch (mode) {
+ case RangeDelPositioningMode::kForwardTraversal:
+ InvalidateReverseIter();
+
+ // Pick up previously unseen iterators.
+ for (auto it = std::next(iters_.begin(), forward_iter_.UnusedIdx());
+ it != iters_.end(); ++it, forward_iter_.IncUnusedIdx()) {
+ auto& iter = *it;
+ forward_iter_.AddNewIter(iter.get(), parsed);
+ }
+
+ return forward_iter_.ShouldDelete(parsed);
+ case RangeDelPositioningMode::kBackwardTraversal:
+ InvalidateForwardIter();
+
+ // Pick up previously unseen iterators.
+ for (auto it = std::next(iters_.begin(), reverse_iter_.UnusedIdx());
+ it != iters_.end(); ++it, reverse_iter_.IncUnusedIdx()) {
+ auto& iter = *it;
+ reverse_iter_.AddNewIter(iter.get(), parsed);
+ }
+
+ return reverse_iter_.ShouldDelete(parsed);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool RangeDelAggregator::StripeRep::IsRangeOverlapped(const Slice& start,
+ const Slice& end) {
+ Invalidate();
+
+ // Set the internal start/end keys so that:
+ // - if start_ikey has the same user key and sequence number as the
+ // current end key, start_ikey will be considered greater; and
+ // - if end_ikey has the same user key and sequence number as the current
+ // start key, end_ikey will be considered greater.
+ ParsedInternalKey start_ikey(start, kMaxSequenceNumber,
+ static_cast<ValueType>(0));
+ ParsedInternalKey end_ikey(end, 0, static_cast<ValueType>(0));
+ for (auto& iter : iters_) {
+ bool checked_candidate_tombstones = false;
+ for (iter->SeekForPrev(start);
+ iter->Valid() && icmp_->Compare(iter->start_key(), end_ikey) <= 0;
+ iter->Next()) {
+ checked_candidate_tombstones = true;
+ if (icmp_->Compare(start_ikey, iter->end_key()) < 0 &&
+ icmp_->Compare(iter->start_key(), end_ikey) <= 0) {
+ return true;
+ }
+ }
+
+ if (!checked_candidate_tombstones) {
+ // Do an additional check for when the end of the range is the begin
+ // key of a tombstone, which we missed earlier since SeekForPrev'ing
+ // to the start was invalid.
+ iter->SeekForPrev(end);
+ if (iter->Valid() && icmp_->Compare(start_ikey, iter->end_key()) < 0 &&
+ icmp_->Compare(iter->start_key(), end_ikey) <= 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void ReadRangeDelAggregator::AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
+ const InternalKey* smallest, const InternalKey* largest) {
+ if (input_iter == nullptr || input_iter->empty()) {
+ return;
+ }
+ rep_.AddTombstones(
+ std::unique_ptr<TruncatedRangeDelIterator>(new TruncatedRangeDelIterator(
+ std::move(input_iter), icmp_, smallest, largest)));
+}
+
+bool ReadRangeDelAggregator::ShouldDeleteImpl(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode) {
+ return rep_.ShouldDelete(parsed, mode);
+}
+
+bool ReadRangeDelAggregator::IsRangeOverlapped(const Slice& start,
+ const Slice& end) {
+ InvalidateRangeDelMapPositions();
+ return rep_.IsRangeOverlapped(start, end);
+}
+
+void CompactionRangeDelAggregator::AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
+ const InternalKey* smallest, const InternalKey* largest) {
+ if (input_iter == nullptr || input_iter->empty()) {
+ return;
+ }
+ assert(input_iter->lower_bound() == 0);
+ assert(input_iter->upper_bound() == kMaxSequenceNumber);
+ parent_iters_.emplace_back(new TruncatedRangeDelIterator(
+ std::move(input_iter), icmp_, smallest, largest));
+
+ auto split_iters = parent_iters_.back()->SplitBySnapshot(*snapshots_);
+ for (auto& split_iter : split_iters) {
+ auto it = reps_.find(split_iter.first);
+ if (it == reps_.end()) {
+ bool inserted;
+ SequenceNumber upper_bound = split_iter.second->upper_bound();
+ SequenceNumber lower_bound = split_iter.second->lower_bound();
+ std::tie(it, inserted) = reps_.emplace(
+ split_iter.first, StripeRep(icmp_, upper_bound, lower_bound));
+ assert(inserted);
+ }
+ assert(it != reps_.end());
+ it->second.AddTombstones(std::move(split_iter.second));
+ }
+}
+
+bool CompactionRangeDelAggregator::ShouldDelete(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode) {
+ auto it = reps_.lower_bound(parsed.sequence);
+ if (it == reps_.end()) {
+ return false;
+ }
+ return it->second.ShouldDelete(parsed, mode);
+}
+
+namespace {
+
+class TruncatedRangeDelMergingIter : public InternalIterator {
+ public:
+ TruncatedRangeDelMergingIter(
+ const InternalKeyComparator* icmp, const Slice* lower_bound,
+ const Slice* upper_bound, bool upper_bound_inclusive,
+ const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>& children)
+ : icmp_(icmp),
+ lower_bound_(lower_bound),
+ upper_bound_(upper_bound),
+ upper_bound_inclusive_(upper_bound_inclusive),
+ heap_(StartKeyMinComparator(icmp)) {
+ for (auto& child : children) {
+ if (child != nullptr) {
+ assert(child->lower_bound() == 0);
+ assert(child->upper_bound() == kMaxSequenceNumber);
+ children_.push_back(child.get());
+ }
+ }
+ }
+
+ bool Valid() const override {
+ return !heap_.empty() && BeforeEndKey(heap_.top());
+ }
+ Status status() const override { return Status::OK(); }
+
+ void SeekToFirst() override {
+ heap_.clear();
+ for (auto& child : children_) {
+ if (lower_bound_ != nullptr) {
+ child->Seek(*lower_bound_);
+ } else {
+ child->SeekToFirst();
+ }
+ if (child->Valid()) {
+ heap_.push(child);
+ }
+ }
+ }
+
+ void Next() override {
+ auto* top = heap_.top();
+ top->InternalNext();
+ if (top->Valid()) {
+ heap_.replace_top(top);
+ } else {
+ heap_.pop();
+ }
+ }
+
+ Slice key() const override {
+ auto* top = heap_.top();
+ cur_start_key_.Set(top->start_key().user_key, top->seq(),
+ kTypeRangeDeletion);
+ return cur_start_key_.Encode();
+ }
+
+ Slice value() const override {
+ auto* top = heap_.top();
+ assert(top->end_key().sequence == kMaxSequenceNumber);
+ return top->end_key().user_key;
+ }
+
+ // Unused InternalIterator methods
+ void Prev() override { assert(false); }
+ void Seek(const Slice& /* target */) override { assert(false); }
+ void SeekForPrev(const Slice& /* target */) override { assert(false); }
+ void SeekToLast() override { assert(false); }
+
+ private:
+ bool BeforeEndKey(const TruncatedRangeDelIterator* iter) const {
+ if (upper_bound_ == nullptr) {
+ return true;
+ }
+ int cmp = icmp_->user_comparator()->Compare(iter->start_key().user_key,
+ *upper_bound_);
+ return upper_bound_inclusive_ ? cmp <= 0 : cmp < 0;
+ }
+
+ const InternalKeyComparator* icmp_;
+ const Slice* lower_bound_;
+ const Slice* upper_bound_;
+ bool upper_bound_inclusive_;
+ BinaryHeap<TruncatedRangeDelIterator*, StartKeyMinComparator> heap_;
+ std::vector<TruncatedRangeDelIterator*> children_;
+
+ mutable InternalKey cur_start_key_;
+};
+
+} // namespace
+
+std::unique_ptr<FragmentedRangeTombstoneIterator>
+CompactionRangeDelAggregator::NewIterator(const Slice* lower_bound,
+ const Slice* upper_bound,
+ bool upper_bound_inclusive) {
+ InvalidateRangeDelMapPositions();
+ std::unique_ptr<TruncatedRangeDelMergingIter> merging_iter(
+ new TruncatedRangeDelMergingIter(icmp_, lower_bound, upper_bound,
+ upper_bound_inclusive, parent_iters_));
+
+ auto fragmented_tombstone_list =
+ std::make_shared<FragmentedRangeTombstoneList>(
+ std::move(merging_iter), *icmp_, true /* for_compaction */,
+ *snapshots_);
+
+ return std::unique_ptr<FragmentedRangeTombstoneIterator>(
+ new FragmentedRangeTombstoneIterator(
+ fragmented_tombstone_list, *icmp_,
+ kMaxSequenceNumber /* upper_bound */));
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/range_del_aggregator.h b/storage/rocksdb/rocksdb/db/range_del_aggregator.h
new file mode 100644
index 00000000..b47cf31d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_del_aggregator.h
@@ -0,0 +1,441 @@
+// 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).
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "db/compaction/compaction_iteration_stats.h"
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/range_del_aggregator.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/version_edit.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/types.h"
+#include "table/internal_iterator.h"
+#include "table/scoped_arena_iterator.h"
+#include "table/table_builder.h"
+#include "util/heap.h"
+#include "util/kv_map.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TruncatedRangeDelIterator {
+ public:
+ TruncatedRangeDelIterator(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> iter,
+ const InternalKeyComparator* icmp, const InternalKey* smallest,
+ const InternalKey* largest);
+
+ bool Valid() const;
+
+ void Next();
+ void Prev();
+
+ void InternalNext();
+
+ // Seeks to the tombstone with the highest viisble sequence number that covers
+ // target (a user key). If no such tombstone exists, the position will be at
+ // the earliest tombstone that ends after target.
+ void Seek(const Slice& target);
+
+ // Seeks to the tombstone with the highest viisble sequence number that covers
+ // target (a user key). If no such tombstone exists, the position will be at
+ // the latest tombstone that starts before target.
+ void SeekForPrev(const Slice& target);
+
+ void SeekToFirst();
+ void SeekToLast();
+
+ ParsedInternalKey start_key() const {
+ return (smallest_ == nullptr ||
+ icmp_->Compare(*smallest_, iter_->parsed_start_key()) <= 0)
+ ? iter_->parsed_start_key()
+ : *smallest_;
+ }
+
+ ParsedInternalKey end_key() const {
+ return (largest_ == nullptr ||
+ icmp_->Compare(iter_->parsed_end_key(), *largest_) <= 0)
+ ? iter_->parsed_end_key()
+ : *largest_;
+ }
+
+ SequenceNumber seq() const { return iter_->seq(); }
+
+ std::map<SequenceNumber, std::unique_ptr<TruncatedRangeDelIterator>>
+ SplitBySnapshot(const std::vector<SequenceNumber>& snapshots);
+
+ SequenceNumber upper_bound() const { return iter_->upper_bound(); }
+
+ SequenceNumber lower_bound() const { return iter_->lower_bound(); }
+
+ private:
+ std::unique_ptr<FragmentedRangeTombstoneIterator> iter_;
+ const InternalKeyComparator* icmp_;
+ const ParsedInternalKey* smallest_ = nullptr;
+ const ParsedInternalKey* largest_ = nullptr;
+ std::list<ParsedInternalKey> pinned_bounds_;
+
+ const InternalKey* smallest_ikey_;
+ const InternalKey* largest_ikey_;
+};
+
+struct SeqMaxComparator {
+ bool operator()(const TruncatedRangeDelIterator* a,
+ const TruncatedRangeDelIterator* b) const {
+ return a->seq() > b->seq();
+ }
+};
+
+struct StartKeyMinComparator {
+ explicit StartKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
+
+ bool operator()(const TruncatedRangeDelIterator* a,
+ const TruncatedRangeDelIterator* b) const {
+ return icmp->Compare(a->start_key(), b->start_key()) > 0;
+ }
+
+ const InternalKeyComparator* icmp;
+};
+
+class ForwardRangeDelIterator {
+ public:
+ explicit ForwardRangeDelIterator(const InternalKeyComparator* icmp);
+
+ bool ShouldDelete(const ParsedInternalKey& parsed);
+ void Invalidate();
+
+ void AddNewIter(TruncatedRangeDelIterator* iter,
+ const ParsedInternalKey& parsed) {
+ iter->Seek(parsed.user_key);
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ size_t UnusedIdx() const { return unused_idx_; }
+ void IncUnusedIdx() { unused_idx_++; }
+
+ private:
+ using ActiveSeqSet =
+ std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
+
+ struct EndKeyMinComparator {
+ explicit EndKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
+
+ bool operator()(const ActiveSeqSet::const_iterator& a,
+ const ActiveSeqSet::const_iterator& b) const {
+ return icmp->Compare((*a)->end_key(), (*b)->end_key()) > 0;
+ }
+
+ const InternalKeyComparator* icmp;
+ };
+
+ void PushIter(TruncatedRangeDelIterator* iter,
+ const ParsedInternalKey& parsed) {
+ if (!iter->Valid()) {
+ // The iterator has been fully consumed, so we don't need to add it to
+ // either of the heaps.
+ return;
+ }
+ int cmp = icmp_->Compare(parsed, iter->start_key());
+ if (cmp < 0) {
+ PushInactiveIter(iter);
+ } else {
+ PushActiveIter(iter);
+ }
+ }
+
+ void PushActiveIter(TruncatedRangeDelIterator* iter) {
+ auto seq_pos = active_seqnums_.insert(iter);
+ active_iters_.push(seq_pos);
+ }
+
+ TruncatedRangeDelIterator* PopActiveIter() {
+ auto active_top = active_iters_.top();
+ auto iter = *active_top;
+ active_iters_.pop();
+ active_seqnums_.erase(active_top);
+ return iter;
+ }
+
+ void PushInactiveIter(TruncatedRangeDelIterator* iter) {
+ inactive_iters_.push(iter);
+ }
+
+ TruncatedRangeDelIterator* PopInactiveIter() {
+ auto* iter = inactive_iters_.top();
+ inactive_iters_.pop();
+ return iter;
+ }
+
+ const InternalKeyComparator* icmp_;
+ size_t unused_idx_;
+ ActiveSeqSet active_seqnums_;
+ BinaryHeap<ActiveSeqSet::const_iterator, EndKeyMinComparator> active_iters_;
+ BinaryHeap<TruncatedRangeDelIterator*, StartKeyMinComparator> inactive_iters_;
+};
+
+class ReverseRangeDelIterator {
+ public:
+ explicit ReverseRangeDelIterator(const InternalKeyComparator* icmp);
+
+ bool ShouldDelete(const ParsedInternalKey& parsed);
+ void Invalidate();
+
+ void AddNewIter(TruncatedRangeDelIterator* iter,
+ const ParsedInternalKey& parsed) {
+ iter->SeekForPrev(parsed.user_key);
+ PushIter(iter, parsed);
+ assert(active_iters_.size() == active_seqnums_.size());
+ }
+
+ size_t UnusedIdx() const { return unused_idx_; }
+ void IncUnusedIdx() { unused_idx_++; }
+
+ private:
+ using ActiveSeqSet =
+ std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
+
+ struct EndKeyMaxComparator {
+ explicit EndKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
+
+ bool operator()(const TruncatedRangeDelIterator* a,
+ const TruncatedRangeDelIterator* b) const {
+ return icmp->Compare(a->end_key(), b->end_key()) < 0;
+ }
+
+ const InternalKeyComparator* icmp;
+ };
+ struct StartKeyMaxComparator {
+ explicit StartKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
+
+ bool operator()(const ActiveSeqSet::const_iterator& a,
+ const ActiveSeqSet::const_iterator& b) const {
+ return icmp->Compare((*a)->start_key(), (*b)->start_key()) < 0;
+ }
+
+ const InternalKeyComparator* icmp;
+ };
+
+ void PushIter(TruncatedRangeDelIterator* iter,
+ const ParsedInternalKey& parsed) {
+ if (!iter->Valid()) {
+ // The iterator has been fully consumed, so we don't need to add it to
+ // either of the heaps.
+ } else if (icmp_->Compare(iter->end_key(), parsed) <= 0) {
+ PushInactiveIter(iter);
+ } else {
+ PushActiveIter(iter);
+ }
+ }
+
+ void PushActiveIter(TruncatedRangeDelIterator* iter) {
+ auto seq_pos = active_seqnums_.insert(iter);
+ active_iters_.push(seq_pos);
+ }
+
+ TruncatedRangeDelIterator* PopActiveIter() {
+ auto active_top = active_iters_.top();
+ auto iter = *active_top;
+ active_iters_.pop();
+ active_seqnums_.erase(active_top);
+ return iter;
+ }
+
+ void PushInactiveIter(TruncatedRangeDelIterator* iter) {
+ inactive_iters_.push(iter);
+ }
+
+ TruncatedRangeDelIterator* PopInactiveIter() {
+ auto* iter = inactive_iters_.top();
+ inactive_iters_.pop();
+ return iter;
+ }
+
+ const InternalKeyComparator* icmp_;
+ size_t unused_idx_;
+ ActiveSeqSet active_seqnums_;
+ BinaryHeap<ActiveSeqSet::const_iterator, StartKeyMaxComparator> active_iters_;
+ BinaryHeap<TruncatedRangeDelIterator*, EndKeyMaxComparator> inactive_iters_;
+};
+
+enum class RangeDelPositioningMode { kForwardTraversal, kBackwardTraversal };
+class RangeDelAggregator {
+ public:
+ explicit RangeDelAggregator(const InternalKeyComparator* icmp)
+ : icmp_(icmp) {}
+ virtual ~RangeDelAggregator() {}
+
+ virtual void AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
+ const InternalKey* smallest = nullptr,
+ const InternalKey* largest = nullptr) = 0;
+
+ bool ShouldDelete(const Slice& key, RangeDelPositioningMode mode) {
+ ParsedInternalKey parsed;
+ if (!ParseInternalKey(key, &parsed)) {
+ return false;
+ }
+ return ShouldDelete(parsed, mode);
+ }
+ virtual bool ShouldDelete(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode) = 0;
+
+ virtual void InvalidateRangeDelMapPositions() = 0;
+
+ virtual bool IsEmpty() const = 0;
+
+ bool AddFile(uint64_t file_number) {
+ return files_seen_.insert(file_number).second;
+ }
+
+ protected:
+ class StripeRep {
+ public:
+ StripeRep(const InternalKeyComparator* icmp, SequenceNumber upper_bound,
+ SequenceNumber lower_bound)
+ : icmp_(icmp),
+ forward_iter_(icmp),
+ reverse_iter_(icmp),
+ upper_bound_(upper_bound),
+ lower_bound_(lower_bound) {}
+
+ void AddTombstones(std::unique_ptr<TruncatedRangeDelIterator> input_iter) {
+ iters_.push_back(std::move(input_iter));
+ }
+
+ bool IsEmpty() const { return iters_.empty(); }
+
+ bool ShouldDelete(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode);
+
+ void Invalidate() {
+ if (!IsEmpty()) {
+ InvalidateForwardIter();
+ InvalidateReverseIter();
+ }
+ }
+
+ bool IsRangeOverlapped(const Slice& start, const Slice& end);
+
+ private:
+ bool InStripe(SequenceNumber seq) const {
+ return lower_bound_ <= seq && seq <= upper_bound_;
+ }
+
+ void InvalidateForwardIter() { forward_iter_.Invalidate(); }
+
+ void InvalidateReverseIter() { reverse_iter_.Invalidate(); }
+
+ const InternalKeyComparator* icmp_;
+ std::vector<std::unique_ptr<TruncatedRangeDelIterator>> iters_;
+ ForwardRangeDelIterator forward_iter_;
+ ReverseRangeDelIterator reverse_iter_;
+ SequenceNumber upper_bound_;
+ SequenceNumber lower_bound_;
+ };
+
+ const InternalKeyComparator* icmp_;
+
+ private:
+ std::set<uint64_t> files_seen_;
+};
+
+class ReadRangeDelAggregator final : public RangeDelAggregator {
+ public:
+ ReadRangeDelAggregator(const InternalKeyComparator* icmp,
+ SequenceNumber upper_bound)
+ : RangeDelAggregator(icmp),
+ rep_(icmp, upper_bound, 0 /* lower_bound */) {}
+ ~ReadRangeDelAggregator() override {}
+
+ using RangeDelAggregator::ShouldDelete;
+ void AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
+ const InternalKey* smallest = nullptr,
+ const InternalKey* largest = nullptr) override;
+
+ bool ShouldDelete(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode) final override {
+ if (rep_.IsEmpty()) {
+ return false;
+ }
+ return ShouldDeleteImpl(parsed, mode);
+ }
+
+ bool IsRangeOverlapped(const Slice& start, const Slice& end);
+
+ void InvalidateRangeDelMapPositions() override { rep_.Invalidate(); }
+
+ bool IsEmpty() const override { return rep_.IsEmpty(); }
+
+ private:
+ StripeRep rep_;
+
+ bool ShouldDeleteImpl(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode);
+};
+
+class CompactionRangeDelAggregator : public RangeDelAggregator {
+ public:
+ CompactionRangeDelAggregator(const InternalKeyComparator* icmp,
+ const std::vector<SequenceNumber>& snapshots)
+ : RangeDelAggregator(icmp), snapshots_(&snapshots) {}
+ ~CompactionRangeDelAggregator() override {}
+
+ void AddTombstones(
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
+ const InternalKey* smallest = nullptr,
+ const InternalKey* largest = nullptr) override;
+
+ using RangeDelAggregator::ShouldDelete;
+ bool ShouldDelete(const ParsedInternalKey& parsed,
+ RangeDelPositioningMode mode) override;
+
+ bool IsRangeOverlapped(const Slice& start, const Slice& end);
+
+ void InvalidateRangeDelMapPositions() override {
+ for (auto& rep : reps_) {
+ rep.second.Invalidate();
+ }
+ }
+
+ bool IsEmpty() const override {
+ for (const auto& rep : reps_) {
+ if (!rep.second.IsEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Creates an iterator over all the range tombstones in the aggregator, for
+ // use in compaction. Nullptr arguments indicate that the iterator range is
+ // unbounded.
+ // NOTE: the boundaries are used for optimization purposes to reduce the
+ // number of tombstones that are passed to the fragmenter; they do not
+ // guarantee that the resulting iterator only contains range tombstones that
+ // cover keys in the provided range. If required, these bounds must be
+ // enforced during iteration.
+ std::unique_ptr<FragmentedRangeTombstoneIterator> NewIterator(
+ const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr,
+ bool upper_bound_inclusive = false);
+
+ private:
+ std::vector<std::unique_ptr<TruncatedRangeDelIterator>> parent_iters_;
+ std::map<SequenceNumber, StripeRep> reps_;
+
+ const std::vector<SequenceNumber>* snapshots_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/range_del_aggregator_bench.cc b/storage/rocksdb/rocksdb/db/range_del_aggregator_bench.cc
new file mode 100644
index 00000000..3f3135f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_del_aggregator_bench.cc
@@ -0,0 +1,260 @@
+// 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).
+
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include <iostream>
+#include <iomanip>
+#include <memory>
+#include <random>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "db/range_del_aggregator.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "test_util/testutil.h"
+#include "util/coding.h"
+#include "util/random.h"
+#include "util/stop_watch.h"
+
+#include "util/gflags_compat.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+
+DEFINE_int32(num_range_tombstones, 1000, "number of range tombstones created");
+
+DEFINE_int32(num_runs, 1000, "number of test runs");
+
+DEFINE_int32(tombstone_start_upper_bound, 1000,
+ "exclusive upper bound on range tombstone start keys");
+
+DEFINE_int32(should_delete_upper_bound, 1000,
+ "exclusive upper bound on keys passed to ShouldDelete");
+
+DEFINE_double(tombstone_width_mean, 100.0, "average range tombstone width");
+
+DEFINE_double(tombstone_width_stddev, 0.0,
+ "standard deviation of range tombstone width");
+
+DEFINE_int32(seed, 0, "random number generator seed");
+
+DEFINE_int32(should_deletes_per_run, 1, "number of ShouldDelete calls per run");
+
+DEFINE_int32(add_tombstones_per_run, 1,
+ "number of AddTombstones calls per run");
+
+namespace {
+
+struct Stats {
+ uint64_t time_add_tombstones = 0;
+ uint64_t time_first_should_delete = 0;
+ uint64_t time_rest_should_delete = 0;
+};
+
+std::ostream& operator<<(std::ostream& os, const Stats& s) {
+ std::ios fmt_holder(nullptr);
+ fmt_holder.copyfmt(os);
+
+ os << std::left;
+ os << std::setw(25) << "AddTombstones: "
+ << s.time_add_tombstones /
+ (FLAGS_add_tombstones_per_run * FLAGS_num_runs * 1.0e3)
+ << " us\n";
+ os << std::setw(25) << "ShouldDelete (first): "
+ << s.time_first_should_delete / (FLAGS_num_runs * 1.0e3) << " us\n";
+ if (FLAGS_should_deletes_per_run > 1) {
+ os << std::setw(25) << "ShouldDelete (rest): "
+ << s.time_rest_should_delete /
+ ((FLAGS_should_deletes_per_run - 1) * FLAGS_num_runs * 1.0e3)
+ << " us\n";
+ }
+
+ os.copyfmt(fmt_holder);
+ return os;
+}
+
+auto icmp = ROCKSDB_NAMESPACE::InternalKeyComparator(
+ ROCKSDB_NAMESPACE::BytewiseComparator());
+
+} // anonymous namespace
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// A wrapper around RangeTombstones and the underlying data of its start and end
+// keys.
+struct PersistentRangeTombstone {
+ std::string start_key;
+ std::string end_key;
+ RangeTombstone tombstone;
+
+ PersistentRangeTombstone(std::string start, std::string end,
+ SequenceNumber seq)
+ : start_key(std::move(start)), end_key(std::move(end)) {
+ tombstone = RangeTombstone(start_key, end_key, seq);
+ }
+
+ PersistentRangeTombstone() = default;
+
+ PersistentRangeTombstone(const PersistentRangeTombstone& t) { *this = t; }
+
+ PersistentRangeTombstone& operator=(const PersistentRangeTombstone& t) {
+ start_key = t.start_key;
+ end_key = t.end_key;
+ tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_);
+
+ return *this;
+ }
+
+ PersistentRangeTombstone(PersistentRangeTombstone&& t) noexcept { *this = t; }
+
+ PersistentRangeTombstone& operator=(PersistentRangeTombstone&& t) {
+ start_key = std::move(t.start_key);
+ end_key = std::move(t.end_key);
+ tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_);
+
+ return *this;
+ }
+};
+
+struct TombstoneStartKeyComparator {
+ explicit TombstoneStartKeyComparator(const Comparator* c) : cmp(c) {}
+
+ bool operator()(const RangeTombstone& a, const RangeTombstone& b) const {
+ return cmp->Compare(a.start_key_, b.start_key_) < 0;
+ }
+
+ const Comparator* cmp;
+};
+
+std::unique_ptr<InternalIterator> MakeRangeDelIterator(
+ const std::vector<PersistentRangeTombstone>& range_dels) {
+ std::vector<std::string> keys, values;
+ for (const auto& range_del : range_dels) {
+ auto key_and_value = range_del.tombstone.Serialize();
+ keys.push_back(key_and_value.first.Encode().ToString());
+ values.push_back(key_and_value.second.ToString());
+ }
+ return std::unique_ptr<test::VectorIterator>(
+ new test::VectorIterator(keys, values));
+}
+
+// convert long to a big-endian slice key
+static std::string Key(int64_t val) {
+ std::string little_endian_key;
+ std::string big_endian_key;
+ PutFixed64(&little_endian_key, val);
+ assert(little_endian_key.size() == sizeof(val));
+ big_endian_key.resize(sizeof(val));
+ for (size_t i = 0; i < sizeof(val); ++i) {
+ big_endian_key[i] = little_endian_key[sizeof(val) - 1 - i];
+ }
+ return big_endian_key;
+}
+
+} // anonymous namespace
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ Stats stats;
+ ROCKSDB_NAMESPACE::Random64 rnd(FLAGS_seed);
+ std::default_random_engine random_gen(FLAGS_seed);
+ std::normal_distribution<double> normal_dist(FLAGS_tombstone_width_mean,
+ FLAGS_tombstone_width_stddev);
+ std::vector<std::vector<ROCKSDB_NAMESPACE::PersistentRangeTombstone> >
+ all_persistent_range_tombstones(FLAGS_add_tombstones_per_run);
+ for (int i = 0; i < FLAGS_add_tombstones_per_run; i++) {
+ all_persistent_range_tombstones[i] =
+ std::vector<ROCKSDB_NAMESPACE::PersistentRangeTombstone>(
+ FLAGS_num_range_tombstones);
+ }
+ auto mode = ROCKSDB_NAMESPACE::RangeDelPositioningMode::kForwardTraversal;
+
+ for (int i = 0; i < FLAGS_num_runs; i++) {
+ ROCKSDB_NAMESPACE::ReadRangeDelAggregator range_del_agg(
+ &icmp, ROCKSDB_NAMESPACE::kMaxSequenceNumber /* upper_bound */);
+
+ std::vector<
+ std::unique_ptr<ROCKSDB_NAMESPACE::FragmentedRangeTombstoneList> >
+ fragmented_range_tombstone_lists(FLAGS_add_tombstones_per_run);
+
+ for (auto& persistent_range_tombstones : all_persistent_range_tombstones) {
+ // TODO(abhimadan): consider whether creating the range tombstones right
+ // before AddTombstones is artificially warming the cache compared to
+ // real workloads.
+ for (int j = 0; j < FLAGS_num_range_tombstones; j++) {
+ uint64_t start = rnd.Uniform(FLAGS_tombstone_start_upper_bound);
+ uint64_t end = static_cast<uint64_t>(
+ std::round(start + std::max(1.0, normal_dist(random_gen))));
+ persistent_range_tombstones[j] =
+ ROCKSDB_NAMESPACE::PersistentRangeTombstone(
+ ROCKSDB_NAMESPACE::Key(start), ROCKSDB_NAMESPACE::Key(end), j);
+ }
+
+ auto range_del_iter =
+ ROCKSDB_NAMESPACE::MakeRangeDelIterator(persistent_range_tombstones);
+ fragmented_range_tombstone_lists.emplace_back(
+ new ROCKSDB_NAMESPACE::FragmentedRangeTombstoneList(
+ ROCKSDB_NAMESPACE::MakeRangeDelIterator(
+ persistent_range_tombstones),
+ icmp));
+ std::unique_ptr<ROCKSDB_NAMESPACE::FragmentedRangeTombstoneIterator>
+ fragmented_range_del_iter(
+ new ROCKSDB_NAMESPACE::FragmentedRangeTombstoneIterator(
+ fragmented_range_tombstone_lists.back().get(), icmp,
+ ROCKSDB_NAMESPACE::kMaxSequenceNumber));
+
+ ROCKSDB_NAMESPACE::StopWatchNano stop_watch_add_tombstones(
+ ROCKSDB_NAMESPACE::Env::Default(), true /* auto_start */);
+ range_del_agg.AddTombstones(std::move(fragmented_range_del_iter));
+ stats.time_add_tombstones += stop_watch_add_tombstones.ElapsedNanos();
+ }
+
+ ROCKSDB_NAMESPACE::ParsedInternalKey parsed_key;
+ parsed_key.sequence = FLAGS_num_range_tombstones / 2;
+ parsed_key.type = ROCKSDB_NAMESPACE::kTypeValue;
+
+ uint64_t first_key = rnd.Uniform(FLAGS_should_delete_upper_bound -
+ FLAGS_should_deletes_per_run + 1);
+
+ for (int j = 0; j < FLAGS_should_deletes_per_run; j++) {
+ std::string key_string = ROCKSDB_NAMESPACE::Key(first_key + j);
+ parsed_key.user_key = key_string;
+
+ ROCKSDB_NAMESPACE::StopWatchNano stop_watch_should_delete(
+ ROCKSDB_NAMESPACE::Env::Default(), true /* auto_start */);
+ range_del_agg.ShouldDelete(parsed_key, mode);
+ uint64_t call_time = stop_watch_should_delete.ElapsedNanos();
+
+ if (j == 0) {
+ stats.time_first_should_delete += call_time;
+ } else {
+ stats.time_rest_should_delete += call_time;
+ }
+ }
+ }
+
+ std::cout << "=========================\n"
+ << "Results:\n"
+ << "=========================\n"
+ << stats;
+
+ return 0;
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db/range_del_aggregator_test.cc b/storage/rocksdb/rocksdb/db/range_del_aggregator_test.cc
new file mode 100644
index 00000000..0b8b5079
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_del_aggregator_test.cc
@@ -0,0 +1,709 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "db/db_test_util.h"
+#include "db/dbformat.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RangeDelAggregatorTest : public testing::Test {};
+
+namespace {
+
+static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator());
+
+std::unique_ptr<InternalIterator> MakeRangeDelIter(
+ const std::vector<RangeTombstone>& range_dels) {
+ std::vector<std::string> 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<test::VectorIterator>(
+ new test::VectorIterator(keys, values));
+}
+
+std::vector<std::unique_ptr<FragmentedRangeTombstoneList>>
+MakeFragmentedTombstoneLists(
+ const std::vector<std::vector<RangeTombstone>>& range_dels_list) {
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneList>> 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) {
+ return ParsedInternalKey(key, seq, kTypeValue);
+}
+
+void VerifyIterator(
+ TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp,
+ const std::vector<TruncatedIterScanTestCase>& 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<TruncatedIterScanTestCase> 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<TruncatedIterSeekTestCase>& 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<TruncatedIterSeekTestCase>& 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<ShouldDeleteTestCase>& 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<IsRangeOverlappedTestCase>& 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<RangeTombstone>& 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());
+}
+
+} // namespace
+
+TEST_F(RangeDelAggregatorTest, EmptyTruncatedIter) {
+ auto range_del_iter = MakeRangeDelIter({});
+ FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
+ bytewise_icmp);
+ std::unique_ptr<FragmentedRangeTombstoneIterator> 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<FragmentedRangeTombstoneIterator> input_iter(
+ new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp,
+ kMaxSequenceNumber));
+
+ TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr,
+ nullptr);
+
+ VerifyIterator(&iter, bytewise_icmp,
+ {{UncutEndpoint("a"), UncutEndpoint("e"), 10},
+ {UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {UncutEndpoint("j"), UncutEndpoint("n"), 4}});
+
+ VerifySeek(
+ &iter, bytewise_icmp,
+ {{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4},
+ {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
+ {"", UncutEndpoint("a"), UncutEndpoint("e"), 10}});
+
+ VerifySeekForPrev(
+ &iter, bytewise_icmp,
+ {{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"n", UncutEndpoint("j"), UncutEndpoint("n"), 4},
+ {"", UncutEndpoint(""), 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<FragmentedRangeTombstoneIterator> input_iter(
+ new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp,
+ 9 /* snapshot */));
+
+ TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr,
+ nullptr);
+
+ VerifyIterator(&iter, bytewise_icmp,
+ {{UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {UncutEndpoint("j"), UncutEndpoint("n"), 4}});
+
+ VerifySeek(
+ &iter, bytewise_icmp,
+ {{"d", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4},
+ {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
+ {"", UncutEndpoint("e"), UncutEndpoint("g"), 8}});
+
+ VerifySeekForPrev(
+ &iter, bytewise_icmp,
+ {{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"n", UncutEndpoint("j"), UncutEndpoint("n"), 4},
+ {"", UncutEndpoint(""), 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<FragmentedRangeTombstoneIterator> 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), UncutEndpoint("e"), 10},
+ {UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {UncutEndpoint("j"), InternalValue("m", 8), 4}});
+
+ VerifySeek(
+ &iter, bytewise_icmp,
+ {{"d", InternalValue("d", 7), UncutEndpoint("e"), 10},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("j"), InternalValue("m", 8), 4},
+ {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
+ {"", InternalValue("d", 7), UncutEndpoint("e"), 10}});
+
+ VerifySeekForPrev(
+ &iter, bytewise_icmp,
+ {{"d", InternalValue("d", 7), UncutEndpoint("e"), 10},
+ {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
+ {"n", UncutEndpoint("j"), InternalValue("m", 8), 4},
+ {"", UncutEndpoint(""), 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<FragmentedRangeTombstoneIterator> 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), UncutEndpoint("g"), 8}});
+
+ VerifySeek(
+ &iter, bytewise_icmp,
+ {{"d", InternalValue("f", 7), UncutEndpoint("g"), 8},
+ {"f", InternalValue("f", 7), UncutEndpoint("g"), 8},
+ {"j", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}});
+
+ VerifySeekForPrev(
+ &iter, bytewise_icmp,
+ {{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
+ {"f", InternalValue("f", 7), UncutEndpoint("g"), 8},
+ {"j", InternalValue("f", 7), 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<FragmentedRangeTombstoneIterator> 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<FragmentedRangeTombstoneIterator> 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<FragmentedRangeTombstoneIterator> 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<std::pair<InternalKey, InternalKey>> 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<FragmentedRangeTombstoneIterator> 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<std::pair<InternalKey, InternalKey>> 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<FragmentedRangeTombstoneIterator> 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<SequenceNumber> snapshots;
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> 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<SequenceNumber> snapshots{9, 19};
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> 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<SequenceNumber> snapshots{9, 19};
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> 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<SequenceNumber> snapshots{9, 19};
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
+ new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp,
+ kMaxSequenceNumber));
+ range_del_agg.AddTombstones(std::move(input_iter));
+ }
+
+ Slice start("p");
+ Slice end("q");
+ auto range_del_compaction_iter1 =
+ range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(range_del_compaction_iter1.get(), {});
+
+ auto range_del_compaction_iter2 =
+ range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(range_del_compaction_iter2.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<SequenceNumber> snapshots{9, 19};
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
+ new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp,
+ kMaxSequenceNumber));
+ range_del_agg.AddTombstones(std::move(input_iter));
+ }
+
+ Slice start("bb");
+ Slice end("e");
+ auto range_del_compaction_iter1 =
+ range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(range_del_compaction_iter1.get(),
+ {{"a", "c", 10}, {"c", "e", 10}, {"c", "e", 8}});
+
+ auto range_del_compaction_iter2 =
+ range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(
+ range_del_compaction_iter2.get(),
+ {{"a", "c", 10}, {"c", "e", 10}, {"c", "e", 8}, {"e", "g", 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<SequenceNumber> snapshots{9, 19};
+ CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots);
+ for (const auto& fragment_list : fragment_lists) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
+ new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp,
+ kMaxSequenceNumber));
+ range_del_agg.AddTombstones(std::move(input_iter));
+ }
+
+ Slice start("bb");
+ Slice end("e");
+ auto range_del_compaction_iter1 =
+ range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(range_del_compaction_iter1.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}});
+
+ auto range_del_compaction_iter2 =
+ range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */);
+ VerifyFragmentedRangeDels(range_del_compaction_iter2.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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.cc b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.cc
new file mode 100644
index 00000000..58426248
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.cc
@@ -0,0 +1,439 @@
+// 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 <algorithm>
+#include <functional>
+#include <set>
+
+#include <stdio.h>
+#include <cinttypes>
+
+#include "util/autovector.h"
+#include "util/kv_map.h"
+#include "util/vector_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
+ std::unique_ptr<InternalIterator> unfragmented_tombstones,
+ const InternalKeyComparator& icmp, bool for_compaction,
+ const std::vector<SequenceNumber>& snapshots) {
+ if (unfragmented_tombstones == nullptr) {
+ return;
+ }
+ bool is_sorted = true;
+ int num_tombstones = 0;
+ InternalKey pinned_last_start_key;
+ Slice last_start_key;
+ for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid();
+ unfragmented_tombstones->Next(), num_tombstones++) {
+ if (num_tombstones > 0 &&
+ icmp.Compare(last_start_key, unfragmented_tombstones->key()) > 0) {
+ is_sorted = false;
+ break;
+ }
+ if (unfragmented_tombstones->IsKeyPinned()) {
+ last_start_key = unfragmented_tombstones->key();
+ } else {
+ pinned_last_start_key.DecodeFrom(unfragmented_tombstones->key());
+ last_start_key = pinned_last_start_key.Encode();
+ }
+ }
+ if (is_sorted) {
+ FragmentTombstones(std::move(unfragmented_tombstones), icmp, for_compaction,
+ snapshots);
+ return;
+ }
+
+ // Sort the tombstones before fragmenting them.
+ std::vector<std::string> keys, values;
+ keys.reserve(num_tombstones);
+ values.reserve(num_tombstones);
+ for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid();
+ unfragmented_tombstones->Next()) {
+ keys.emplace_back(unfragmented_tombstones->key().data(),
+ unfragmented_tombstones->key().size());
+ values.emplace_back(unfragmented_tombstones->value().data(),
+ unfragmented_tombstones->value().size());
+ }
+ // VectorIterator implicitly sorts by key during construction.
+ auto iter = std::unique_ptr<VectorIterator>(
+ new VectorIterator(std::move(keys), std::move(values), &icmp));
+ FragmentTombstones(std::move(iter), icmp, for_compaction, snapshots);
+}
+
+void FragmentedRangeTombstoneList::FragmentTombstones(
+ std::unique_ptr<InternalIterator> unfragmented_tombstones,
+ const InternalKeyComparator& icmp, bool for_compaction,
+ const std::vector<SequenceNumber>& snapshots) {
+ Slice cur_start_key(nullptr, 0);
+ auto cmp = ParsedInternalKeyComparator(&icmp);
+
+ // Stores the end keys and sequence numbers of range tombstones with a start
+ // key less than or equal to cur_start_key. Provides an ordering by end key
+ // for use in flush_current_tombstones.
+ std::set<ParsedInternalKey, ParsedInternalKeyComparator> cur_end_keys(cmp);
+
+ // Given the next start key in unfragmented_tombstones,
+ // flush_current_tombstones writes every tombstone fragment that starts
+ // and ends with a key before next_start_key, and starts with a key greater
+ // than or equal to cur_start_key.
+ auto flush_current_tombstones = [&](const Slice& next_start_key) {
+ auto it = cur_end_keys.begin();
+ bool reached_next_start_key = false;
+ for (; it != cur_end_keys.end() && !reached_next_start_key; ++it) {
+ Slice cur_end_key = it->user_key;
+ if (icmp.user_comparator()->Compare(cur_start_key, cur_end_key) == 0) {
+ // Empty tombstone.
+ continue;
+ }
+ if (icmp.user_comparator()->Compare(next_start_key, cur_end_key) <= 0) {
+ // All of the end keys in [it, cur_end_keys.end()) are after
+ // next_start_key, so the tombstones they represent can be used in
+ // fragments that start with keys greater than or equal to
+ // next_start_key. However, the end keys we already passed will not be
+ // used in any more tombstone fragments.
+ //
+ // Remove the fully fragmented tombstones and stop iteration after a
+ // final round of flushing to preserve the tombstones we can create more
+ // fragments from.
+ reached_next_start_key = true;
+ cur_end_keys.erase(cur_end_keys.begin(), it);
+ cur_end_key = next_start_key;
+ }
+
+ // Flush a range tombstone fragment [cur_start_key, cur_end_key), which
+ // should not overlap with the last-flushed tombstone fragment.
+ assert(tombstones_.empty() ||
+ icmp.user_comparator()->Compare(tombstones_.back().end_key,
+ cur_start_key) <= 0);
+
+ // Sort the sequence numbers of the tombstones being fragmented in
+ // descending order, and then flush them in that order.
+ autovector<SequenceNumber> seqnums_to_flush;
+ for (auto flush_it = it; flush_it != cur_end_keys.end(); ++flush_it) {
+ seqnums_to_flush.push_back(flush_it->sequence);
+ }
+ std::sort(seqnums_to_flush.begin(), seqnums_to_flush.end(),
+ std::greater<SequenceNumber>());
+
+ size_t start_idx = tombstone_seqs_.size();
+ size_t end_idx = start_idx + seqnums_to_flush.size();
+
+ if (for_compaction) {
+ // Drop all tombstone seqnums that are not preserved by a snapshot.
+ SequenceNumber next_snapshot = kMaxSequenceNumber;
+ for (auto seq : seqnums_to_flush) {
+ if (seq <= next_snapshot) {
+ // This seqnum is visible by a lower snapshot.
+ tombstone_seqs_.push_back(seq);
+ seq_set_.insert(seq);
+ auto upper_bound_it =
+ std::lower_bound(snapshots.begin(), snapshots.end(), seq);
+ if (upper_bound_it == snapshots.begin()) {
+ // This seqnum is the topmost one visible by the earliest
+ // snapshot. None of the seqnums below it will be visible, so we
+ // can skip them.
+ break;
+ }
+ next_snapshot = *std::prev(upper_bound_it);
+ }
+ }
+ end_idx = tombstone_seqs_.size();
+ } else {
+ // The fragmentation is being done for reads, so preserve all seqnums.
+ tombstone_seqs_.insert(tombstone_seqs_.end(), seqnums_to_flush.begin(),
+ seqnums_to_flush.end());
+ seq_set_.insert(seqnums_to_flush.begin(), seqnums_to_flush.end());
+ }
+
+ assert(start_idx < end_idx);
+ tombstones_.emplace_back(cur_start_key, cur_end_key, start_idx, end_idx);
+
+ cur_start_key = cur_end_key;
+ }
+ if (!reached_next_start_key) {
+ // There is a gap between the last flushed tombstone fragment and
+ // the next tombstone's start key. Remove all the end keys in
+ // the working set, since we have fully fragmented their corresponding
+ // tombstones.
+ cur_end_keys.clear();
+ }
+ cur_start_key = next_start_key;
+ };
+
+ pinned_iters_mgr_.StartPinning();
+
+ bool no_tombstones = true;
+ for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid();
+ unfragmented_tombstones->Next()) {
+ const Slice& ikey = unfragmented_tombstones->key();
+ Slice tombstone_start_key = ExtractUserKey(ikey);
+ SequenceNumber tombstone_seq = GetInternalKeySeqno(ikey);
+ if (!unfragmented_tombstones->IsKeyPinned()) {
+ pinned_slices_.emplace_back(tombstone_start_key.data(),
+ tombstone_start_key.size());
+ tombstone_start_key = pinned_slices_.back();
+ }
+ no_tombstones = false;
+
+ Slice tombstone_end_key = unfragmented_tombstones->value();
+ if (!unfragmented_tombstones->IsValuePinned()) {
+ pinned_slices_.emplace_back(tombstone_end_key.data(),
+ tombstone_end_key.size());
+ tombstone_end_key = pinned_slices_.back();
+ }
+ if (!cur_end_keys.empty() && icmp.user_comparator()->Compare(
+ cur_start_key, tombstone_start_key) != 0) {
+ // The start key has changed. Flush all tombstones that start before
+ // this new start key.
+ flush_current_tombstones(tombstone_start_key);
+ }
+ cur_start_key = tombstone_start_key;
+
+ cur_end_keys.emplace(tombstone_end_key, tombstone_seq, kTypeRangeDeletion);
+ }
+ if (!cur_end_keys.empty()) {
+ ParsedInternalKey last_end_key = *std::prev(cur_end_keys.end());
+ flush_current_tombstones(last_end_key.user_key);
+ }
+
+ if (!no_tombstones) {
+ pinned_iters_mgr_.PinIterator(unfragmented_tombstones.release(),
+ false /* arena */);
+ }
+}
+
+bool FragmentedRangeTombstoneList::ContainsRange(SequenceNumber lower,
+ SequenceNumber upper) const {
+ auto seq_it = seq_set_.lower_bound(lower);
+ return seq_it != seq_set_.end() && *seq_it <= upper;
+}
+
+FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator(
+ const FragmentedRangeTombstoneList* tombstones,
+ const InternalKeyComparator& icmp, SequenceNumber _upper_bound,
+ SequenceNumber _lower_bound)
+ : tombstone_start_cmp_(icmp.user_comparator()),
+ tombstone_end_cmp_(icmp.user_comparator()),
+ icmp_(&icmp),
+ ucmp_(icmp.user_comparator()),
+ tombstones_(tombstones),
+ upper_bound_(_upper_bound),
+ lower_bound_(_lower_bound) {
+ assert(tombstones_ != nullptr);
+ Invalidate();
+}
+
+FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator(
+ const std::shared_ptr<const FragmentedRangeTombstoneList>& tombstones,
+ const InternalKeyComparator& icmp, SequenceNumber _upper_bound,
+ SequenceNumber _lower_bound)
+ : tombstone_start_cmp_(icmp.user_comparator()),
+ tombstone_end_cmp_(icmp.user_comparator()),
+ icmp_(&icmp),
+ ucmp_(icmp.user_comparator()),
+ tombstones_ref_(tombstones),
+ tombstones_(tombstones_ref_.get()),
+ upper_bound_(_upper_bound),
+ lower_bound_(_lower_bound) {
+ assert(tombstones_ != nullptr);
+ Invalidate();
+}
+
+void FragmentedRangeTombstoneIterator::SeekToFirst() {
+ pos_ = tombstones_->begin();
+ seq_pos_ = tombstones_->seq_begin();
+}
+
+void FragmentedRangeTombstoneIterator::SeekToTopFirst() {
+ if (tombstones_->empty()) {
+ Invalidate();
+ return;
+ }
+ pos_ = tombstones_->begin();
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ ScanForwardToVisibleTombstone();
+}
+
+void FragmentedRangeTombstoneIterator::SeekToLast() {
+ pos_ = std::prev(tombstones_->end());
+ seq_pos_ = std::prev(tombstones_->seq_end());
+}
+
+void FragmentedRangeTombstoneIterator::SeekToTopLast() {
+ if (tombstones_->empty()) {
+ Invalidate();
+ return;
+ }
+ pos_ = std::prev(tombstones_->end());
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ ScanBackwardToVisibleTombstone();
+}
+
+void FragmentedRangeTombstoneIterator::Seek(const Slice& target) {
+ if (tombstones_->empty()) {
+ Invalidate();
+ return;
+ }
+ SeekToCoveringTombstone(target);
+ ScanForwardToVisibleTombstone();
+}
+
+void FragmentedRangeTombstoneIterator::SeekForPrev(const Slice& target) {
+ if (tombstones_->empty()) {
+ Invalidate();
+ return;
+ }
+ SeekForPrevToCoveringTombstone(target);
+ ScanBackwardToVisibleTombstone();
+}
+
+void FragmentedRangeTombstoneIterator::SeekToCoveringTombstone(
+ const Slice& target) {
+ pos_ = std::upper_bound(tombstones_->begin(), tombstones_->end(), target,
+ tombstone_end_cmp_);
+ if (pos_ == tombstones_->end()) {
+ // All tombstones end before target.
+ seq_pos_ = tombstones_->seq_end();
+ return;
+ }
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+}
+
+void FragmentedRangeTombstoneIterator::SeekForPrevToCoveringTombstone(
+ const Slice& target) {
+ if (tombstones_->empty()) {
+ Invalidate();
+ return;
+ }
+ pos_ = std::upper_bound(tombstones_->begin(), tombstones_->end(), target,
+ tombstone_start_cmp_);
+ if (pos_ == tombstones_->begin()) {
+ // All tombstones start after target.
+ Invalidate();
+ return;
+ }
+ --pos_;
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+}
+
+void FragmentedRangeTombstoneIterator::ScanForwardToVisibleTombstone() {
+ while (pos_ != tombstones_->end() &&
+ (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) ||
+ *seq_pos_ < lower_bound_)) {
+ ++pos_;
+ if (pos_ == tombstones_->end()) {
+ Invalidate();
+ return;
+ }
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ }
+}
+
+void FragmentedRangeTombstoneIterator::ScanBackwardToVisibleTombstone() {
+ while (pos_ != tombstones_->end() &&
+ (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) ||
+ *seq_pos_ < lower_bound_)) {
+ if (pos_ == tombstones_->begin()) {
+ Invalidate();
+ return;
+ }
+ --pos_;
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ }
+}
+
+void FragmentedRangeTombstoneIterator::Next() {
+ ++seq_pos_;
+ if (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx)) {
+ ++pos_;
+ }
+}
+
+void FragmentedRangeTombstoneIterator::TopNext() {
+ ++pos_;
+ if (pos_ == tombstones_->end()) {
+ return;
+ }
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ ScanForwardToVisibleTombstone();
+}
+
+void FragmentedRangeTombstoneIterator::Prev() {
+ if (seq_pos_ == tombstones_->seq_begin()) {
+ Invalidate();
+ return;
+ }
+ --seq_pos_;
+ if (pos_ == tombstones_->end() ||
+ seq_pos_ == tombstones_->seq_iter(pos_->seq_start_idx - 1)) {
+ --pos_;
+ }
+}
+
+void FragmentedRangeTombstoneIterator::TopPrev() {
+ if (pos_ == tombstones_->begin()) {
+ Invalidate();
+ return;
+ }
+ --pos_;
+ seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
+ tombstones_->seq_iter(pos_->seq_end_idx),
+ upper_bound_, std::greater<SequenceNumber>());
+ ScanBackwardToVisibleTombstone();
+}
+
+bool FragmentedRangeTombstoneIterator::Valid() const {
+ return tombstones_ != nullptr && pos_ != tombstones_->end();
+}
+
+SequenceNumber FragmentedRangeTombstoneIterator::MaxCoveringTombstoneSeqnum(
+ const Slice& target_user_key) {
+ SeekToCoveringTombstone(target_user_key);
+ return ValidPos() && ucmp_->Compare(start_key(), target_user_key) <= 0 ? seq()
+ : 0;
+}
+
+std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
+FragmentedRangeTombstoneIterator::SplitBySnapshot(
+ const std::vector<SequenceNumber>& snapshots) {
+ std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ splits;
+ SequenceNumber lower = 0;
+ SequenceNumber upper;
+ for (size_t i = 0; i <= snapshots.size(); i++) {
+ if (i >= snapshots.size()) {
+ upper = kMaxSequenceNumber;
+ } else {
+ upper = snapshots[i];
+ }
+ if (tombstones_->ContainsRange(lower, upper)) {
+ splits.emplace(upper, std::unique_ptr<FragmentedRangeTombstoneIterator>(
+ new FragmentedRangeTombstoneIterator(
+ tombstones_, *icmp_, upper, lower)));
+ }
+ lower = upper + 1;
+ }
+ return splits;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.h b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.h
new file mode 100644
index 00000000..63ec24e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter.h
@@ -0,0 +1,256 @@
+// 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).
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+#include "rocksdb/status.h"
+#include "table/internal_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct FragmentedRangeTombstoneList {
+ public:
+ // A compact representation of a "stack" of range tombstone fragments, which
+ // start and end at the same user keys but have different sequence numbers.
+ // The members seq_start_idx and seq_end_idx are intended to be parameters to
+ // seq_iter().
+ struct RangeTombstoneStack {
+ RangeTombstoneStack(const Slice& start, const Slice& end, size_t start_idx,
+ size_t end_idx)
+ : start_key(start),
+ end_key(end),
+ seq_start_idx(start_idx),
+ seq_end_idx(end_idx) {}
+
+ Slice start_key;
+ Slice end_key;
+ size_t seq_start_idx;
+ size_t seq_end_idx;
+ };
+ FragmentedRangeTombstoneList(
+ std::unique_ptr<InternalIterator> unfragmented_tombstones,
+ const InternalKeyComparator& icmp, bool for_compaction = false,
+ const std::vector<SequenceNumber>& snapshots = {});
+
+ std::vector<RangeTombstoneStack>::const_iterator begin() const {
+ return tombstones_.begin();
+ }
+
+ std::vector<RangeTombstoneStack>::const_iterator end() const {
+ return tombstones_.end();
+ }
+
+ std::vector<SequenceNumber>::const_iterator seq_iter(size_t idx) const {
+ return std::next(tombstone_seqs_.begin(), idx);
+ }
+
+ std::vector<SequenceNumber>::const_iterator seq_begin() const {
+ return tombstone_seqs_.begin();
+ }
+
+ std::vector<SequenceNumber>::const_iterator seq_end() const {
+ return tombstone_seqs_.end();
+ }
+
+ bool empty() const { return tombstones_.empty(); }
+
+ // Returns true if the stored tombstones contain with one with a sequence
+ // number in [lower, upper].
+ bool ContainsRange(SequenceNumber lower, SequenceNumber upper) const;
+
+ private:
+ // Given an ordered range tombstone iterator unfragmented_tombstones,
+ // "fragment" the tombstones into non-overlapping pieces, and store them in
+ // tombstones_ and tombstone_seqs_.
+ void FragmentTombstones(
+ std::unique_ptr<InternalIterator> unfragmented_tombstones,
+ const InternalKeyComparator& icmp, bool for_compaction,
+ const std::vector<SequenceNumber>& snapshots);
+
+ std::vector<RangeTombstoneStack> tombstones_;
+ std::vector<SequenceNumber> tombstone_seqs_;
+ std::set<SequenceNumber> seq_set_;
+ std::list<std::string> pinned_slices_;
+ PinnedIteratorsManager pinned_iters_mgr_;
+};
+
+// FragmentedRangeTombstoneIterator converts an InternalIterator of a range-del
+// meta block into an iterator over non-overlapping tombstone fragments. The
+// tombstone fragmentation process should be more efficient than the range
+// tombstone collapsing algorithm in RangeDelAggregator because this leverages
+// the internal key ordering already provided by the input iterator, if
+// applicable (when the iterator is unsorted, a new sorted iterator is created
+// before proceeding). If there are few overlaps, creating a
+// FragmentedRangeTombstoneIterator should be O(n), while the RangeDelAggregator
+// tombstone collapsing is always O(n log n).
+class FragmentedRangeTombstoneIterator : public InternalIterator {
+ public:
+ FragmentedRangeTombstoneIterator(
+ const FragmentedRangeTombstoneList* tombstones,
+ const InternalKeyComparator& icmp, SequenceNumber upper_bound,
+ SequenceNumber lower_bound = 0);
+ FragmentedRangeTombstoneIterator(
+ const std::shared_ptr<const FragmentedRangeTombstoneList>& tombstones,
+ const InternalKeyComparator& icmp, SequenceNumber upper_bound,
+ SequenceNumber lower_bound = 0);
+
+ void SeekToFirst() override;
+ void SeekToLast() override;
+
+ void SeekToTopFirst();
+ void SeekToTopLast();
+
+ // NOTE: Seek and SeekForPrev do not behave in the way InternalIterator
+ // seeking should behave. This is OK because they are not currently used, but
+ // eventually FragmentedRangeTombstoneIterator should no longer implement
+ // InternalIterator.
+ //
+ // Seeks to the range tombstone that covers target at a seqnum in the
+ // snapshot. If no such tombstone exists, seek to the earliest tombstone in
+ // the snapshot that ends after target.
+ void Seek(const Slice& target) override;
+ // Seeks to the range tombstone that covers target at a seqnum in the
+ // snapshot. If no such tombstone exists, seek to the latest tombstone in the
+ // snapshot that starts before target.
+ void SeekForPrev(const Slice& target) override;
+
+ void Next() override;
+ void Prev() override;
+
+ void TopNext();
+ void TopPrev();
+
+ bool Valid() const override;
+ Slice key() const override {
+ MaybePinKey();
+ return current_start_key_.Encode();
+ }
+ Slice value() const override { return pos_->end_key; }
+ bool IsKeyPinned() const override { return false; }
+ bool IsValuePinned() const override { return true; }
+ Status status() const override { return Status::OK(); }
+
+ bool empty() const { return tombstones_->empty(); }
+ void Invalidate() {
+ pos_ = tombstones_->end();
+ seq_pos_ = tombstones_->seq_end();
+ pinned_pos_ = tombstones_->end();
+ pinned_seq_pos_ = tombstones_->seq_end();
+ }
+
+ RangeTombstone Tombstone() const {
+ return RangeTombstone(start_key(), end_key(), seq());
+ }
+ Slice start_key() const { return pos_->start_key; }
+ Slice end_key() const { return pos_->end_key; }
+ SequenceNumber seq() const { return *seq_pos_; }
+ ParsedInternalKey parsed_start_key() const {
+ return ParsedInternalKey(pos_->start_key, kMaxSequenceNumber,
+ kTypeRangeDeletion);
+ }
+ ParsedInternalKey parsed_end_key() const {
+ return ParsedInternalKey(pos_->end_key, kMaxSequenceNumber,
+ kTypeRangeDeletion);
+ }
+
+ SequenceNumber MaxCoveringTombstoneSeqnum(const Slice& user_key);
+
+ // Splits the iterator into n+1 iterators (where n is the number of
+ // snapshots), each providing a view over a "stripe" of sequence numbers. The
+ // iterators are keyed by the upper bound of their ranges (the provided
+ // snapshots + kMaxSequenceNumber).
+ //
+ // NOTE: the iterators in the returned map are no longer valid if their
+ // parent iterator is deleted, since they do not modify the refcount of the
+ // underlying tombstone list. Therefore, this map should be deleted before
+ // the parent iterator.
+ std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ SplitBySnapshot(const std::vector<SequenceNumber>& snapshots);
+
+ SequenceNumber upper_bound() const { return upper_bound_; }
+ SequenceNumber lower_bound() const { return lower_bound_; }
+
+ private:
+ using RangeTombstoneStack = FragmentedRangeTombstoneList::RangeTombstoneStack;
+
+ struct RangeTombstoneStackStartComparator {
+ explicit RangeTombstoneStackStartComparator(const Comparator* c) : cmp(c) {}
+
+ bool operator()(const RangeTombstoneStack& a,
+ const RangeTombstoneStack& b) const {
+ return cmp->Compare(a.start_key, b.start_key) < 0;
+ }
+
+ bool operator()(const RangeTombstoneStack& a, const Slice& b) const {
+ return cmp->Compare(a.start_key, b) < 0;
+ }
+
+ bool operator()(const Slice& a, const RangeTombstoneStack& b) const {
+ return cmp->Compare(a, b.start_key) < 0;
+ }
+
+ const Comparator* cmp;
+ };
+
+ struct RangeTombstoneStackEndComparator {
+ explicit RangeTombstoneStackEndComparator(const Comparator* c) : cmp(c) {}
+
+ bool operator()(const RangeTombstoneStack& a,
+ const RangeTombstoneStack& b) const {
+ return cmp->Compare(a.end_key, b.end_key) < 0;
+ }
+
+ bool operator()(const RangeTombstoneStack& a, const Slice& b) const {
+ return cmp->Compare(a.end_key, b) < 0;
+ }
+
+ bool operator()(const Slice& a, const RangeTombstoneStack& b) const {
+ return cmp->Compare(a, b.end_key) < 0;
+ }
+
+ const Comparator* cmp;
+ };
+
+ void MaybePinKey() const {
+ if (pos_ != tombstones_->end() && seq_pos_ != tombstones_->seq_end() &&
+ (pinned_pos_ != pos_ || pinned_seq_pos_ != seq_pos_)) {
+ current_start_key_.Set(pos_->start_key, *seq_pos_, kTypeRangeDeletion);
+ pinned_pos_ = pos_;
+ pinned_seq_pos_ = seq_pos_;
+ }
+ }
+
+ void SeekToCoveringTombstone(const Slice& key);
+ void SeekForPrevToCoveringTombstone(const Slice& key);
+ void ScanForwardToVisibleTombstone();
+ void ScanBackwardToVisibleTombstone();
+ bool ValidPos() const {
+ return Valid() && seq_pos_ != tombstones_->seq_iter(pos_->seq_end_idx);
+ }
+
+ const RangeTombstoneStackStartComparator tombstone_start_cmp_;
+ const RangeTombstoneStackEndComparator tombstone_end_cmp_;
+ const InternalKeyComparator* icmp_;
+ const Comparator* ucmp_;
+ std::shared_ptr<const FragmentedRangeTombstoneList> tombstones_ref_;
+ const FragmentedRangeTombstoneList* tombstones_;
+ SequenceNumber upper_bound_;
+ SequenceNumber lower_bound_;
+ std::vector<RangeTombstoneStack>::const_iterator pos_;
+ std::vector<SequenceNumber>::const_iterator seq_pos_;
+ mutable std::vector<RangeTombstoneStack>::const_iterator pinned_pos_;
+ mutable std::vector<SequenceNumber>::const_iterator pinned_seq_pos_;
+ mutable InternalKey current_start_key_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter_test.cc b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter_test.cc
new file mode 100644
index 00000000..56234b1d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/range_tombstone_fragmenter_test.cc
@@ -0,0 +1,552 @@
+// 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 "rocksdb/comparator.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RangeTombstoneFragmenterTest : public testing::Test {};
+
+namespace {
+
+static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator());
+
+std::unique_ptr<InternalIterator> MakeRangeDelIter(
+ const std::vector<RangeTombstone>& range_dels) {
+ std::vector<std::string> 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<test::VectorIterator>(
+ new test::VectorIterator(keys, values));
+}
+
+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<RangeTombstone>& 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<RangeTombstone>& 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<SeekTestCase>& 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<SeekTestCase>& 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<MaxCoveringTombstoneSeqnumTestCase>& 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 */,
+ {20, 9} /* upper_bounds */);
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/read_callback.h b/storage/rocksdb/rocksdb/db/read_callback.h
new file mode 100644
index 00000000..fbef1dd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/read_callback.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ReadCallback {
+ public:
+ ReadCallback(SequenceNumber last_visible_seq)
+ : max_visible_seq_(last_visible_seq) {}
+ ReadCallback(SequenceNumber last_visible_seq, SequenceNumber min_uncommitted)
+ : max_visible_seq_(last_visible_seq), min_uncommitted_(min_uncommitted) {}
+
+ virtual ~ReadCallback() {}
+
+ // Will be called to see if the seq number visible; if not it moves on to
+ // the next seq number.
+ virtual bool IsVisibleFullCheck(SequenceNumber seq) = 0;
+
+ inline bool IsVisible(SequenceNumber seq) {
+ assert(min_uncommitted_ > 0);
+ assert(min_uncommitted_ >= kMinUnCommittedSeq);
+ if (seq < min_uncommitted_) { // handles seq == 0 as well
+ assert(seq <= max_visible_seq_);
+ return true;
+ } else if (max_visible_seq_ < seq) {
+ assert(seq != 0);
+ return false;
+ } else {
+ assert(seq != 0); // already handled in the first if-then clause
+ return IsVisibleFullCheck(seq);
+ }
+ }
+
+ inline SequenceNumber max_visible_seq() { return max_visible_seq_; }
+
+ // Refresh to a more recent visible seq
+ virtual void Refresh(SequenceNumber seq) { max_visible_seq_ = seq; }
+
+ protected:
+ // The max visible seq, it is usually the snapshot but could be larger if
+ // transaction has its own writes written to db.
+ SequenceNumber max_visible_seq_ = kMaxSequenceNumber;
+ // Any seq less than min_uncommitted_ is committed.
+ const SequenceNumber min_uncommitted_ = kMinUnCommittedSeq;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/repair.cc b/storage/rocksdb/rocksdb/db/repair.cc
new file mode 100644
index 00000000..383ffe3a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/repair.cc
@@ -0,0 +1,691 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Repairer does best effort recovery to recover as much data as possible after
+// a disaster without compromising consistency. It does not guarantee bringing
+// the database to a time consistent state.
+//
+// Repair process is broken into 4 phases:
+// (a) Find files
+// (b) Convert logs to tables
+// (c) Extract metadata
+// (d) Write Descriptor
+//
+// (a) Find files
+//
+// The repairer goes through all the files in the directory, and classifies them
+// based on their file name. Any file that cannot be identified by name will be
+// ignored.
+//
+// (b) Convert logs to table
+//
+// Every log file that is active is replayed. All sections of the file where the
+// checksum does not match is skipped over. We intentionally give preference to
+// data consistency.
+//
+// (c) Extract metadata
+//
+// We scan every table to compute
+// (1) smallest/largest for the table
+// (2) largest sequence number in the table
+// (3) oldest blob file referred to by the table (if applicable)
+//
+// If we are unable to scan the file, then we ignore the table.
+//
+// (d) Write Descriptor
+//
+// We generate descriptor contents:
+// - log number is set to zero
+// - next-file-number is set to 1 + largest file number we found
+// - last-sequence-number is set to largest sequence# found across
+// all tables (see 2c)
+// - compaction pointers are cleared
+// - every table file is added at level 0
+//
+// Possible optimization 1:
+// (a) Compute total size and use to pick appropriate max-level M
+// (b) Sort tables by largest sequence# in the table
+// (c) For each table: if it overlaps earlier table, place in level-0,
+// else place in level-M.
+// (d) We can provide options for time consistent recovery and unsafe recovery
+// (ignore checksum failure when applicable)
+// Possible optimization 2:
+// Store per-table metadata (smallest, largest, largest-seq#, ...)
+// in the table's meta section to speed up ScanTable.
+
+#ifndef ROCKSDB_LITE
+
+#include <cinttypes>
+#include "db/builder.h"
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/memtable.h"
+#include "db/table_cache.h"
+#include "db/version_edit.h"
+#include "db/write_batch_internal.h"
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "file/writable_file_writer.h"
+#include "options/cf_options.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/scoped_arena_iterator.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+class Repairer {
+ public:
+ Repairer(const std::string& dbname, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ const ColumnFamilyOptions& default_cf_opts,
+ const ColumnFamilyOptions& unknown_cf_opts, bool create_unknown_cfs)
+ : dbname_(dbname),
+ env_(db_options.env),
+ env_options_(),
+ db_options_(SanitizeOptions(dbname_, db_options)),
+ immutable_db_options_(ImmutableDBOptions(db_options_)),
+ icmp_(default_cf_opts.comparator),
+ default_cf_opts_(
+ SanitizeOptions(immutable_db_options_, default_cf_opts)),
+ default_cf_iopts_(
+ ImmutableCFOptions(immutable_db_options_, default_cf_opts_)),
+ unknown_cf_opts_(
+ SanitizeOptions(immutable_db_options_, unknown_cf_opts)),
+ create_unknown_cfs_(create_unknown_cfs),
+ raw_table_cache_(
+ // TableCache can be small since we expect each table to be opened
+ // once.
+ NewLRUCache(10, db_options_.table_cache_numshardbits)),
+ table_cache_(new TableCache(default_cf_iopts_, env_options_,
+ raw_table_cache_.get(),
+ /*block_cache_tracer=*/nullptr)),
+ wb_(db_options_.db_write_buffer_size),
+ wc_(db_options_.delayed_write_rate),
+ vset_(dbname_, &immutable_db_options_, env_options_,
+ raw_table_cache_.get(), &wb_, &wc_,
+ /*block_cache_tracer=*/nullptr),
+ next_file_number_(1),
+ db_lock_(nullptr) {
+ for (const auto& cfd : column_families) {
+ cf_name_to_opts_[cfd.name] = cfd.options;
+ }
+ }
+
+ const ColumnFamilyOptions* GetColumnFamilyOptions(
+ const std::string& cf_name) {
+ if (cf_name_to_opts_.find(cf_name) == cf_name_to_opts_.end()) {
+ if (create_unknown_cfs_) {
+ return &unknown_cf_opts_;
+ }
+ return nullptr;
+ }
+ return &cf_name_to_opts_[cf_name];
+ }
+
+ // Adds a column family to the VersionSet with cf_options_ and updates
+ // manifest.
+ Status AddColumnFamily(const std::string& cf_name, uint32_t cf_id) {
+ const auto* cf_opts = GetColumnFamilyOptions(cf_name);
+ if (cf_opts == nullptr) {
+ return Status::Corruption("Encountered unknown column family with name=" +
+ cf_name + ", id=" + ToString(cf_id));
+ }
+ Options opts(db_options_, *cf_opts);
+ MutableCFOptions mut_cf_opts(opts);
+
+ VersionEdit edit;
+ edit.SetComparatorName(opts.comparator->Name());
+ edit.SetLogNumber(0);
+ edit.SetColumnFamily(cf_id);
+ ColumnFamilyData* cfd;
+ cfd = nullptr;
+ edit.AddColumnFamily(cf_name);
+
+ mutex_.Lock();
+ Status status = vset_.LogAndApply(cfd, mut_cf_opts, &edit, &mutex_,
+ nullptr /* db_directory */,
+ false /* new_descriptor_log */, cf_opts);
+ mutex_.Unlock();
+ return status;
+ }
+
+ ~Repairer() {
+ if (db_lock_ != nullptr) {
+ env_->UnlockFile(db_lock_);
+ }
+ delete table_cache_;
+ }
+
+ Status Run() {
+ Status status = env_->LockFile(LockFileName(dbname_), &db_lock_);
+ if (!status.ok()) {
+ return status;
+ }
+ status = FindFiles();
+ if (status.ok()) {
+ // Discard older manifests and start a fresh one
+ for (size_t i = 0; i < manifests_.size(); i++) {
+ ArchiveFile(dbname_ + "/" + manifests_[i]);
+ }
+ // Just create a DBImpl temporarily so we can reuse NewDB()
+ DBImpl* db_impl = new DBImpl(db_options_, dbname_);
+ status = db_impl->NewDB();
+ delete db_impl;
+ }
+
+ if (status.ok()) {
+ // Recover using the fresh manifest created by NewDB()
+ status =
+ vset_.Recover({{kDefaultColumnFamilyName, default_cf_opts_}}, false);
+ }
+ if (status.ok()) {
+ // Need to scan existing SST files first so the column families are
+ // created before we process WAL files
+ ExtractMetaData();
+
+ // ExtractMetaData() uses table_fds_ to know which SST files' metadata to
+ // extract -- we need to clear it here since metadata for existing SST
+ // files has been extracted already
+ table_fds_.clear();
+ ConvertLogFilesToTables();
+ ExtractMetaData();
+ status = AddTables();
+ }
+ if (status.ok()) {
+ uint64_t bytes = 0;
+ for (size_t i = 0; i < tables_.size(); i++) {
+ bytes += tables_[i].meta.fd.GetFileSize();
+ }
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "**** Repaired rocksdb %s; "
+ "recovered %" ROCKSDB_PRIszt " files; %" PRIu64
+ " bytes. "
+ "Some data may have been lost. "
+ "****",
+ dbname_.c_str(), tables_.size(), bytes);
+ }
+ return status;
+ }
+
+ private:
+ struct TableInfo {
+ FileMetaData meta;
+ uint32_t column_family_id;
+ std::string column_family_name;
+ };
+
+ std::string const dbname_;
+ Env* const env_;
+ const EnvOptions env_options_;
+ const DBOptions db_options_;
+ const ImmutableDBOptions immutable_db_options_;
+ const InternalKeyComparator icmp_;
+ const ColumnFamilyOptions default_cf_opts_;
+ const ImmutableCFOptions default_cf_iopts_; // table_cache_ holds reference
+ const ColumnFamilyOptions unknown_cf_opts_;
+ const bool create_unknown_cfs_;
+ std::shared_ptr<Cache> raw_table_cache_;
+ TableCache* table_cache_;
+ WriteBufferManager wb_;
+ WriteController wc_;
+ VersionSet vset_;
+ std::unordered_map<std::string, ColumnFamilyOptions> cf_name_to_opts_;
+ InstrumentedMutex mutex_;
+
+ std::vector<std::string> manifests_;
+ std::vector<FileDescriptor> table_fds_;
+ std::vector<uint64_t> logs_;
+ std::vector<TableInfo> tables_;
+ uint64_t next_file_number_;
+ // Lock over the persistent DB state. Non-nullptr iff successfully
+ // acquired.
+ FileLock* db_lock_;
+
+ Status FindFiles() {
+ std::vector<std::string> filenames;
+ bool found_file = false;
+ std::vector<std::string> to_search_paths;
+
+ for (size_t path_id = 0; path_id < db_options_.db_paths.size(); path_id++) {
+ to_search_paths.push_back(db_options_.db_paths[path_id].path);
+ }
+
+ // search wal_dir if user uses a customize wal_dir
+ bool same = false;
+ Status status = env_->AreFilesSame(db_options_.wal_dir, dbname_, &same);
+ if (status.IsNotSupported()) {
+ same = db_options_.wal_dir == dbname_;
+ status = Status::OK();
+ } else if (!status.ok()) {
+ return status;
+ }
+
+ if (!same) {
+ to_search_paths.push_back(db_options_.wal_dir);
+ }
+
+ for (size_t path_id = 0; path_id < to_search_paths.size(); path_id++) {
+ status = env_->GetChildren(to_search_paths[path_id], &filenames);
+ if (!status.ok()) {
+ return status;
+ }
+ if (!filenames.empty()) {
+ found_file = true;
+ }
+
+ uint64_t number;
+ FileType type;
+ for (size_t i = 0; i < filenames.size(); i++) {
+ if (ParseFileName(filenames[i], &number, &type)) {
+ if (type == kDescriptorFile) {
+ manifests_.push_back(filenames[i]);
+ } else {
+ if (number + 1 > next_file_number_) {
+ next_file_number_ = number + 1;
+ }
+ if (type == kLogFile) {
+ logs_.push_back(number);
+ } else if (type == kTableFile) {
+ table_fds_.emplace_back(number, static_cast<uint32_t>(path_id),
+ 0);
+ } else {
+ // Ignore other files
+ }
+ }
+ }
+ }
+ }
+ if (!found_file) {
+ return Status::Corruption(dbname_, "repair found no files");
+ }
+ return Status::OK();
+ }
+
+ void ConvertLogFilesToTables() {
+ for (size_t i = 0; i < logs_.size(); i++) {
+ // we should use LogFileName(wal_dir, logs_[i]) here. user might uses wal_dir option.
+ std::string logname = LogFileName(db_options_.wal_dir, logs_[i]);
+ Status status = ConvertLogToTable(logs_[i]);
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Log #%" PRIu64 ": ignoring conversion error: %s",
+ logs_[i], status.ToString().c_str());
+ }
+ ArchiveFile(logname);
+ }
+ }
+
+ Status ConvertLogToTable(uint64_t log) {
+ struct LogReporter : public log::Reader::Reporter {
+ Env* env;
+ std::shared_ptr<Logger> info_log;
+ uint64_t lognum;
+ void Corruption(size_t bytes, const Status& s) override {
+ // We print error messages for corruption, but continue repairing.
+ ROCKS_LOG_ERROR(info_log, "Log #%" PRIu64 ": dropping %d bytes; %s",
+ lognum, static_cast<int>(bytes), s.ToString().c_str());
+ }
+ };
+
+ // Open the log file
+ std::string logname = LogFileName(db_options_.wal_dir, log);
+ std::unique_ptr<SequentialFile> lfile;
+ Status status = env_->NewSequentialFile(
+ logname, &lfile, env_->OptimizeForLogRead(env_options_));
+ if (!status.ok()) {
+ return status;
+ }
+ std::unique_ptr<SequentialFileReader> lfile_reader(new SequentialFileReader(
+ NewLegacySequentialFileWrapper(lfile), logname));
+
+ // Create the log reader.
+ LogReporter reporter;
+ reporter.env = env_;
+ reporter.info_log = db_options_.info_log;
+ reporter.lognum = log;
+ // We intentionally make log::Reader do checksumming so that
+ // corruptions cause entire commits to be skipped instead of
+ // propagating bad information (like overly large sequence
+ // numbers).
+ log::Reader reader(db_options_.info_log, std::move(lfile_reader), &reporter,
+ true /*enable checksum*/, log);
+
+ // Initialize per-column family memtables
+ for (auto* cfd : *vset_.GetColumnFamilySet()) {
+ cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
+ kMaxSequenceNumber);
+ }
+ auto cf_mems = new ColumnFamilyMemTablesImpl(vset_.GetColumnFamilySet());
+
+ // Read all the records and add to a memtable
+ std::string scratch;
+ Slice record;
+ WriteBatch batch;
+ int counter = 0;
+ while (reader.ReadRecord(&record, &scratch)) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter.Corruption(
+ record.size(), Status::Corruption("log record too small"));
+ continue;
+ }
+ WriteBatchInternal::SetContents(&batch, record);
+ status =
+ WriteBatchInternal::InsertInto(&batch, cf_mems, nullptr, nullptr);
+ if (status.ok()) {
+ counter += WriteBatchInternal::Count(&batch);
+ } else {
+ ROCKS_LOG_WARN(db_options_.info_log, "Log #%" PRIu64 ": ignoring %s",
+ log, status.ToString().c_str());
+ status = Status::OK(); // Keep going with rest of file
+ }
+ }
+
+ // Dump a table for each column family with entries in this log file.
+ for (auto* cfd : *vset_.GetColumnFamilySet()) {
+ // Do not record a version edit for this conversion to a Table
+ // since ExtractMetaData() will also generate edits.
+ MemTable* mem = cfd->mem();
+ if (mem->IsEmpty()) {
+ continue;
+ }
+
+ FileMetaData meta;
+ meta.fd = FileDescriptor(next_file_number_++, 0, 0);
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ Arena arena;
+ ScopedArenaIterator iter(mem->NewIterator(ro, &arena));
+ int64_t _current_time = 0;
+ status = env_->GetCurrentTime(&_current_time); // ignore error
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+ SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
+
+ auto write_hint = cfd->CalculateSSTWriteHint(0);
+ std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
+ range_del_iters;
+ auto range_del_iter =
+ mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
+ if (range_del_iter != nullptr) {
+ range_del_iters.emplace_back(range_del_iter);
+ }
+
+ LegacyFileSystemWrapper fs(env_);
+ status = BuildTable(
+ dbname_, env_, &fs, *cfd->ioptions(),
+ *cfd->GetLatestMutableCFOptions(), env_options_, table_cache_,
+ iter.get(), std::move(range_del_iters), &meta,
+ cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
+ cfd->GetID(), cfd->GetName(), {}, kMaxSequenceNumber,
+ snapshot_checker, kNoCompression, 0 /* sample_for_compression */,
+ CompressionOptions(), false, nullptr /* internal_stats */,
+ TableFileCreationReason::kRecovery, nullptr /* event_logger */,
+ 0 /* job_id */, Env::IO_HIGH, nullptr /* table_properties */,
+ -1 /* level */, current_time, write_hint);
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s",
+ log, counter, meta.fd.GetNumber(),
+ status.ToString().c_str());
+ if (status.ok()) {
+ if (meta.fd.GetFileSize() > 0) {
+ table_fds_.push_back(meta.fd);
+ }
+ } else {
+ break;
+ }
+ }
+ delete cf_mems;
+ return status;
+ }
+
+ void ExtractMetaData() {
+ for (size_t i = 0; i < table_fds_.size(); i++) {
+ TableInfo t;
+ t.meta.fd = table_fds_[i];
+ Status status = ScanTable(&t);
+ if (!status.ok()) {
+ std::string fname = TableFileName(
+ db_options_.db_paths, t.meta.fd.GetNumber(), t.meta.fd.GetPathId());
+ char file_num_buf[kFormatFileNumberBufSize];
+ FormatFileNumber(t.meta.fd.GetNumber(), t.meta.fd.GetPathId(),
+ file_num_buf, sizeof(file_num_buf));
+ ROCKS_LOG_WARN(db_options_.info_log, "Table #%s: ignoring %s",
+ file_num_buf, status.ToString().c_str());
+ ArchiveFile(fname);
+ } else {
+ tables_.push_back(t);
+ }
+ }
+ }
+
+ Status ScanTable(TableInfo* t) {
+ std::string fname = TableFileName(
+ db_options_.db_paths, t->meta.fd.GetNumber(), t->meta.fd.GetPathId());
+ int counter = 0;
+ uint64_t file_size;
+ Status status = env_->GetFileSize(fname, &file_size);
+ t->meta.fd = FileDescriptor(t->meta.fd.GetNumber(), t->meta.fd.GetPathId(),
+ file_size);
+ std::shared_ptr<const TableProperties> props;
+ if (status.ok()) {
+ status = table_cache_->GetTableProperties(env_options_, icmp_, t->meta.fd,
+ &props);
+ }
+ if (status.ok()) {
+ t->column_family_id = static_cast<uint32_t>(props->column_family_id);
+ if (t->column_family_id ==
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) {
+ ROCKS_LOG_WARN(
+ db_options_.info_log,
+ "Table #%" PRIu64
+ ": column family unknown (probably due to legacy format); "
+ "adding to default column family id 0.",
+ t->meta.fd.GetNumber());
+ t->column_family_id = 0;
+ }
+
+ if (vset_.GetColumnFamilySet()->GetColumnFamily(t->column_family_id) ==
+ nullptr) {
+ status =
+ AddColumnFamily(props->column_family_name, t->column_family_id);
+ }
+ t->meta.oldest_ancester_time = props->creation_time;
+ }
+ ColumnFamilyData* cfd = nullptr;
+ if (status.ok()) {
+ cfd = vset_.GetColumnFamilySet()->GetColumnFamily(t->column_family_id);
+ if (cfd->GetName() != props->column_family_name) {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Table #%" PRIu64
+ ": inconsistent column family name '%s'; expected '%s' for column "
+ "family id %" PRIu32 ".",
+ t->meta.fd.GetNumber(), props->column_family_name.c_str(),
+ cfd->GetName().c_str(), t->column_family_id);
+ status = Status::Corruption(dbname_, "inconsistent column family name");
+ }
+ }
+ if (status.ok()) {
+ ReadOptions ropts;
+ ropts.total_order_seek = true;
+ InternalIterator* iter = table_cache_->NewIterator(
+ ropts, env_options_, cfd->internal_comparator(), t->meta,
+ nullptr /* range_del_agg */,
+ cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
+ /*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
+ TableReaderCaller::kRepair, /*arena=*/nullptr, /*skip_filters=*/false,
+ /*level=*/-1, /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr);
+ ParsedInternalKey parsed;
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ Slice key = iter->key();
+ if (!ParseInternalKey(key, &parsed)) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Table #%" PRIu64 ": unparsable key %s",
+ t->meta.fd.GetNumber(), EscapeString(key).c_str());
+ continue;
+ }
+
+ counter++;
+
+ t->meta.UpdateBoundaries(key, iter->value(), parsed.sequence,
+ parsed.type);
+ }
+ if (!iter->status().ok()) {
+ status = iter->status();
+ }
+ delete iter;
+
+ ROCKS_LOG_INFO(db_options_.info_log, "Table #%" PRIu64 ": %d entries %s",
+ t->meta.fd.GetNumber(), counter,
+ status.ToString().c_str());
+ }
+ return status;
+ }
+
+ Status AddTables() {
+ std::unordered_map<uint32_t, std::vector<const TableInfo*>> cf_id_to_tables;
+ SequenceNumber max_sequence = 0;
+ for (size_t i = 0; i < tables_.size(); i++) {
+ cf_id_to_tables[tables_[i].column_family_id].push_back(&tables_[i]);
+ if (max_sequence < tables_[i].meta.fd.largest_seqno) {
+ max_sequence = tables_[i].meta.fd.largest_seqno;
+ }
+ }
+ vset_.SetLastAllocatedSequence(max_sequence);
+ vset_.SetLastPublishedSequence(max_sequence);
+ vset_.SetLastSequence(max_sequence);
+
+ for (const auto& cf_id_and_tables : cf_id_to_tables) {
+ auto* cfd =
+ vset_.GetColumnFamilySet()->GetColumnFamily(cf_id_and_tables.first);
+ VersionEdit edit;
+ edit.SetComparatorName(cfd->user_comparator()->Name());
+ edit.SetLogNumber(0);
+ edit.SetNextFile(next_file_number_);
+ edit.SetColumnFamily(cfd->GetID());
+
+ // TODO(opt): separate out into multiple levels
+ for (const auto* table : cf_id_and_tables.second) {
+ edit.AddFile(
+ 0, table->meta.fd.GetNumber(), table->meta.fd.GetPathId(),
+ table->meta.fd.GetFileSize(), table->meta.smallest,
+ table->meta.largest, table->meta.fd.smallest_seqno,
+ table->meta.fd.largest_seqno, table->meta.marked_for_compaction,
+ table->meta.oldest_blob_file_number,
+ table->meta.oldest_ancester_time, table->meta.file_creation_time,
+ table->meta.file_checksum, table->meta.file_checksum_func_name);
+ }
+ assert(next_file_number_ > 0);
+ vset_.MarkFileNumberUsed(next_file_number_ - 1);
+ mutex_.Lock();
+ Status status = vset_.LogAndApply(
+ cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
+ nullptr /* db_directory */, false /* new_descriptor_log */);
+ mutex_.Unlock();
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ return Status::OK();
+ }
+
+ void ArchiveFile(const std::string& fname) {
+ // Move into another directory. E.g., for
+ // dir/foo
+ // rename to
+ // dir/lost/foo
+ const char* slash = strrchr(fname.c_str(), '/');
+ std::string new_dir;
+ if (slash != nullptr) {
+ new_dir.assign(fname.data(), slash - fname.data());
+ }
+ new_dir.append("/lost");
+ env_->CreateDir(new_dir); // Ignore error
+ std::string new_file = new_dir;
+ new_file.append("/");
+ new_file.append((slash == nullptr) ? fname.c_str() : slash + 1);
+ Status s = env_->RenameFile(fname, new_file);
+ ROCKS_LOG_INFO(db_options_.info_log, "Archiving %s: %s\n", fname.c_str(),
+ s.ToString().c_str());
+ }
+};
+
+Status GetDefaultCFOptions(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ ColumnFamilyOptions* res) {
+ assert(res != nullptr);
+ auto iter = std::find_if(column_families.begin(), column_families.end(),
+ [](const ColumnFamilyDescriptor& cfd) {
+ return cfd.name == kDefaultColumnFamilyName;
+ });
+ if (iter == column_families.end()) {
+ return Status::InvalidArgument(
+ "column_families", "Must contain entry for default column family");
+ }
+ *res = iter->options;
+ return Status::OK();
+}
+} // anonymous namespace
+
+Status RepairDB(const std::string& dbname, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families
+ ) {
+ ColumnFamilyOptions default_cf_opts;
+ Status status = GetDefaultCFOptions(column_families, &default_cf_opts);
+ if (status.ok()) {
+ Repairer repairer(dbname, db_options, column_families,
+ default_cf_opts,
+ ColumnFamilyOptions() /* unknown_cf_opts */,
+ false /* create_unknown_cfs */);
+ status = repairer.Run();
+ }
+ return status;
+}
+
+Status RepairDB(const std::string& dbname, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ const ColumnFamilyOptions& unknown_cf_opts) {
+ ColumnFamilyOptions default_cf_opts;
+ Status status = GetDefaultCFOptions(column_families, &default_cf_opts);
+ if (status.ok()) {
+ Repairer repairer(dbname, db_options,
+ column_families, default_cf_opts,
+ unknown_cf_opts, true /* create_unknown_cfs */);
+ status = repairer.Run();
+ }
+ return status;
+}
+
+Status RepairDB(const std::string& dbname, const Options& options) {
+ Options opts(options);
+ if (opts.file_system == nullptr) {
+ opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
+ ;
+ }
+
+ DBOptions db_options(opts);
+ ColumnFamilyOptions cf_options(opts);
+ Repairer repairer(dbname, db_options,
+ {}, cf_options /* default_cf_opts */,
+ cf_options /* unknown_cf_opts */,
+ true /* create_unknown_cfs */);
+ return repairer.Run();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/repair_test.cc b/storage/rocksdb/rocksdb/db/repair_test.cc
new file mode 100644
index 00000000..ba2bae3d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/repair_test.cc
@@ -0,0 +1,369 @@
+// 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).
+
+#ifndef ROCKSDB_LITE
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#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 "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+class RepairTest : public DBTestBase {
+ public:
+ RepairTest() : DBTestBase("/repair_test") {}
+
+ std::string GetFirstSstPath() {
+ uint64_t manifest_size;
+ std::vector<std::string> files;
+ db_->GetLiveFiles(files, &manifest_size);
+ 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;
+ });
+ return sst_iter == files.end() ? "" : dbname_ + *sst_iter;
+ }
+};
+
+TEST_F(RepairTest, LostManifest) {
+ // Add a couple SST files, delete the manifest, and verify RepairDB() saves
+ // the day.
+ Put("key", "val");
+ Flush();
+ Put("key2", "val2");
+ 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()));
+ Reopen(CurrentOptions());
+
+ ASSERT_EQ(Get("key"), "val");
+ ASSERT_EQ(Get("key2"), "val2");
+}
+
+TEST_F(RepairTest, CorruptManifest) {
+ // Manifest is in an invalid format. Expect a full recovery.
+ Put("key", "val");
+ Flush();
+ Put("key2", "val2");
+ 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));
+
+ LegacyFileSystemWrapper fs(env_);
+ CreateFile(&fs, manifest_path, "blah", false /* use_fsync */);
+ ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
+ Reopen(CurrentOptions());
+
+ 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.
+ Put("key", "val");
+ Flush();
+ std::string orig_manifest_path =
+ DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
+ CopyFile(orig_manifest_path, orig_manifest_path + ".tmp");
+ Put("key2", "val2");
+ 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()));
+ Reopen(CurrentOptions());
+
+ 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.
+ Put("key", "val");
+ Flush();
+ Put("key2", "val2");
+ Flush();
+ uint64_t pre_repair_file_num = dbfull()->TEST_Current_Next_FileNo();
+ Close();
+
+ ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
+
+ Reopen(CurrentOptions());
+ 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.
+ Put("key", "val");
+ Flush();
+ Put("key2", "val2");
+ Flush();
+ auto sst_path = GetFirstSstPath();
+ ASSERT_FALSE(sst_path.empty());
+ ASSERT_OK(env_->DeleteFile(sst_path));
+
+ Close();
+ ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
+ Reopen(CurrentOptions());
+
+ // 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.
+ Put("key", "val");
+ Flush();
+ Put("key2", "val2");
+ Flush();
+ auto sst_path = GetFirstSstPath();
+ ASSERT_FALSE(sst_path.empty());
+
+ LegacyFileSystemWrapper fs(env_);
+ CreateFile(&fs, sst_path, "blah", false /* use_fsync */);
+
+ Close();
+ ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
+ Reopen(CurrentOptions());
+
+ // 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.
+ Put("key", "val");
+ VectorLogPtr wal_files;
+ ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
+ ASSERT_EQ(wal_files.size(), 1);
+ uint64_t total_ssts_size;
+ GetAllSSTFiles(&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()));
+ Reopen(CurrentOptions());
+
+ ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
+ ASSERT_EQ(wal_files.size(), 0);
+ GetAllSSTFiles(&total_ssts_size);
+ ASSERT_GT(total_ssts_size, 0);
+ ASSERT_EQ(Get("key"), "val");
+}
+
+TEST_F(RepairTest, SeparateWalDir) {
+ do {
+ Options options = CurrentOptions();
+ DestroyAndReopen(options);
+ Put("key", "val");
+ Put("foo", "bar");
+ VectorLogPtr wal_files;
+ ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
+ ASSERT_EQ(wal_files.size(), 1);
+ uint64_t total_ssts_size;
+ GetAllSSTFiles(&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 = "";
+
+ Reopen(options);
+ ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
+ ASSERT_EQ(wal_files.size(), 0);
+ GetAllSSTFiles(&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) {
+ Put(i, "key" + ToString(j), "val" + ToString(j));
+ if (j == kEntriesPerCf - 1 && i == kNumCfs - 1) {
+ // Leave one unflushed so we can verify WAL entries are properly
+ // associated with column families.
+ continue;
+ }
+ 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" + ToString(j)), "val" + ToString(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<Options>{opts, rev_opts});
+ for (int i = 0; i < kNumCfs; ++i) {
+ for (int j = 0; j < kEntriesPerCf; ++j) {
+ Put(i, "key" + ToString(j), "val" + ToString(j));
+ if (i == kNumCfs - 1 && j == kEntriesPerCf - 1) {
+ // Leave one unflushed so we can verify RepairDB's flush logic
+ continue;
+ }
+ 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<Options>{opts, rev_opts}));
+ for (int i = 0; i < kNumCfs; ++i) {
+ for (int j = 0; j < kEntriesPerCf; ++j) {
+ ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
+ }
+ }
+
+ // Examine table properties to verify RepairDB() used the right options when
+ // converting WAL->SST
+ TablePropertiesCollection fname_to_props;
+ 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 (
+ InternalKeyComparator(rev_opts.comparator).Name());
+ comparator_name = comparator_name.substr(comparator_name.find(':') + 1);
+ 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<Options>{opts, rev_opts}));
+ for (int i = 0; i < kNumCfs; ++i) {
+ for (int j = 0; j < kEntriesPerCf; ++j) {
+ ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
+ }
+ }
+}
+
+TEST_F(RepairTest, DbNameContainsTrailingSlash) {
+ {
+ bool tmp;
+ if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) {
+ fprintf(stderr,
+ "skipping RepairTest.DbNameContainsTrailingSlash due to "
+ "unsupported Env::AreFilesSame\n");
+ return;
+ }
+ }
+
+ Put("key", "val");
+ Flush();
+ Close();
+
+ ASSERT_OK(RepairDB(dbname_ + "/", CurrentOptions()));
+ Reopen(CurrentOptions());
+ ASSERT_EQ(Get("key"), "val");
+}
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/snapshot_checker.h b/storage/rocksdb/rocksdb/db/snapshot_checker.h
new file mode 100644
index 00000000..1d2c2c31
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/snapshot_checker.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+enum class SnapshotCheckerResult : int {
+ kInSnapshot = 0,
+ kNotInSnapshot = 1,
+ // In case snapshot is released and the checker has no clue whether
+ // the given sequence is visible to the snapshot.
+ kSnapshotReleased = 2,
+};
+
+// Callback class that control GC of duplicate keys in flush/compaction.
+class SnapshotChecker {
+ public:
+ virtual ~SnapshotChecker() {}
+ virtual SnapshotCheckerResult CheckInSnapshot(
+ SequenceNumber sequence, SequenceNumber snapshot_sequence) const = 0;
+};
+
+class DisableGCSnapshotChecker : public SnapshotChecker {
+ public:
+ virtual ~DisableGCSnapshotChecker() {}
+ virtual SnapshotCheckerResult CheckInSnapshot(
+ SequenceNumber /*sequence*/,
+ SequenceNumber /*snapshot_sequence*/) const override {
+ // By returning kNotInSnapshot, we prevent all the values from being GCed
+ return SnapshotCheckerResult::kNotInSnapshot;
+ }
+ static DisableGCSnapshotChecker* Instance() { return &instance_; }
+
+ protected:
+ static DisableGCSnapshotChecker instance_;
+ explicit DisableGCSnapshotChecker() {}
+};
+
+class WritePreparedTxnDB;
+
+// Callback class created by WritePreparedTxnDB to check if a key
+// is visible by a snapshot.
+class WritePreparedSnapshotChecker : public SnapshotChecker {
+ public:
+ explicit WritePreparedSnapshotChecker(WritePreparedTxnDB* txn_db);
+ virtual ~WritePreparedSnapshotChecker() {}
+
+ virtual SnapshotCheckerResult CheckInSnapshot(
+ SequenceNumber sequence, SequenceNumber snapshot_sequence) const override;
+
+ private:
+#ifndef ROCKSDB_LITE
+ const WritePreparedTxnDB* const txn_db_;
+#endif // !ROCKSDB_LITE
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/snapshot_impl.cc b/storage/rocksdb/rocksdb/db/snapshot_impl.cc
new file mode 100644
index 00000000..b9228c79
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/snapshot_impl.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/snapshot.h"
+
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ManagedSnapshot::ManagedSnapshot(DB* db) : db_(db),
+ snapshot_(db->GetSnapshot()) {}
+
+ManagedSnapshot::ManagedSnapshot(DB* db, const Snapshot* _snapshot)
+ : db_(db), snapshot_(_snapshot) {}
+
+ManagedSnapshot::~ManagedSnapshot() {
+ if (snapshot_) {
+ db_->ReleaseSnapshot(snapshot_);
+ }
+}
+
+const Snapshot* ManagedSnapshot::snapshot() { return snapshot_;}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/snapshot_impl.h b/storage/rocksdb/rocksdb/db/snapshot_impl.h
new file mode 100644
index 00000000..785f814f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/snapshot_impl.h
@@ -0,0 +1,167 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <vector>
+
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SnapshotList;
+
+// Snapshots are kept in a doubly-linked list in the DB.
+// Each SnapshotImpl corresponds to a particular sequence number.
+class SnapshotImpl : public Snapshot {
+ public:
+ SequenceNumber number_; // const after creation
+ // It indicates the smallest uncommitted data at the time the snapshot was
+ // taken. This is currently used by WritePrepared transactions to limit the
+ // scope of queries to IsInSnpashot.
+ SequenceNumber min_uncommitted_ = kMinUnCommittedSeq;
+
+ virtual SequenceNumber GetSequenceNumber() const override { return number_; }
+
+ private:
+ friend class SnapshotList;
+
+ // SnapshotImpl is kept in a doubly-linked circular list
+ SnapshotImpl* prev_;
+ SnapshotImpl* next_;
+
+ SnapshotList* list_; // just for sanity checks
+
+ int64_t unix_time_;
+
+ // Will this snapshot be used by a Transaction to do write-conflict checking?
+ bool is_write_conflict_boundary_;
+};
+
+class SnapshotList {
+ public:
+ SnapshotList() {
+ list_.prev_ = &list_;
+ list_.next_ = &list_;
+ list_.number_ = 0xFFFFFFFFL; // placeholder marker, for debugging
+ // Set all the variables to make UBSAN happy.
+ list_.list_ = nullptr;
+ list_.unix_time_ = 0;
+ list_.is_write_conflict_boundary_ = false;
+ count_ = 0;
+ }
+
+ // No copy-construct.
+ SnapshotList(const SnapshotList&) = delete;
+
+ bool empty() const { return list_.next_ == &list_; }
+ SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; }
+ SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
+
+ SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq, uint64_t unix_time,
+ bool is_write_conflict_boundary) {
+ s->number_ = seq;
+ s->unix_time_ = unix_time;
+ s->is_write_conflict_boundary_ = is_write_conflict_boundary;
+ s->list_ = this;
+ s->next_ = &list_;
+ s->prev_ = list_.prev_;
+ s->prev_->next_ = s;
+ s->next_->prev_ = s;
+ count_++;
+ return s;
+ }
+
+ // Do not responsible to free the object.
+ void Delete(const SnapshotImpl* s) {
+ assert(s->list_ == this);
+ s->prev_->next_ = s->next_;
+ s->next_->prev_ = s->prev_;
+ count_--;
+ }
+
+ // retrieve all snapshot numbers up until max_seq. They are sorted in
+ // ascending order (with no duplicates).
+ std::vector<SequenceNumber> GetAll(
+ SequenceNumber* oldest_write_conflict_snapshot = nullptr,
+ const SequenceNumber& max_seq = kMaxSequenceNumber) const {
+ std::vector<SequenceNumber> ret;
+ GetAll(&ret, oldest_write_conflict_snapshot, max_seq);
+ return ret;
+ }
+
+ void GetAll(std::vector<SequenceNumber>* snap_vector,
+ SequenceNumber* oldest_write_conflict_snapshot = nullptr,
+ const SequenceNumber& max_seq = kMaxSequenceNumber) const {
+ std::vector<SequenceNumber>& ret = *snap_vector;
+ // So far we have no use case that would pass a non-empty vector
+ assert(ret.size() == 0);
+
+ if (oldest_write_conflict_snapshot != nullptr) {
+ *oldest_write_conflict_snapshot = kMaxSequenceNumber;
+ }
+
+ if (empty()) {
+ return;
+ }
+ const SnapshotImpl* s = &list_;
+ while (s->next_ != &list_) {
+ if (s->next_->number_ > max_seq) {
+ break;
+ }
+ // Avoid duplicates
+ if (ret.empty() || ret.back() != s->next_->number_) {
+ ret.push_back(s->next_->number_);
+ }
+
+ if (oldest_write_conflict_snapshot != nullptr &&
+ *oldest_write_conflict_snapshot == kMaxSequenceNumber &&
+ s->next_->is_write_conflict_boundary_) {
+ // If this is the first write-conflict boundary snapshot in the list,
+ // it is the oldest
+ *oldest_write_conflict_snapshot = s->next_->number_;
+ }
+
+ s = s->next_;
+ }
+ return;
+ }
+
+ // get the sequence number of the most recent snapshot
+ SequenceNumber GetNewest() {
+ if (empty()) {
+ return 0;
+ }
+ return newest()->number_;
+ }
+
+ int64_t GetOldestSnapshotTime() const {
+ if (empty()) {
+ return 0;
+ } else {
+ return oldest()->unix_time_;
+ }
+ }
+
+ int64_t GetOldestSnapshotSequence() const {
+ if (empty()) {
+ return 0;
+ } else {
+ return oldest()->GetSequenceNumber();
+ }
+ }
+
+ uint64_t count() const { return count_; }
+
+ private:
+ // Dummy head of doubly-linked list of snapshots
+ SnapshotImpl list_;
+ uint64_t count_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/table_cache.cc b/storage/rocksdb/rocksdb/db/table_cache.cc
new file mode 100644
index 00000000..411959a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/table_cache.cc
@@ -0,0 +1,668 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/table_cache.h"
+
+#include "db/dbformat.h"
+#include "db/range_tombstone_fragmenter.h"
+#include "db/snapshot_impl.h"
+#include "db/version_edit.h"
+#include "file/filename.h"
+#include "file/random_access_file_reader.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/statistics.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.h"
+#include "table/iterator_wrapper.h"
+#include "table/multiget_context.h"
+#include "table/table_builder.h"
+#include "table/table_reader.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/coding.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+template <class T>
+static void DeleteEntry(const Slice& /*key*/, void* value) {
+ T* typed_value = reinterpret_cast<T*>(value);
+ delete typed_value;
+}
+
+static void UnrefEntry(void* arg1, void* arg2) {
+ Cache* cache = reinterpret_cast<Cache*>(arg1);
+ Cache::Handle* h = reinterpret_cast<Cache::Handle*>(arg2);
+ cache->Release(h);
+}
+
+static Slice GetSliceForFileNumber(const uint64_t* file_number) {
+ return Slice(reinterpret_cast<const char*>(file_number),
+ sizeof(*file_number));
+}
+
+#ifndef ROCKSDB_LITE
+
+void AppendVarint64(IterKey* key, uint64_t v) {
+ char buf[10];
+ auto ptr = EncodeVarint64(buf, v);
+ key->TrimAppend(key->Size(), buf, ptr - buf);
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace
+
+TableCache::TableCache(const ImmutableCFOptions& ioptions,
+ const FileOptions& file_options, Cache* const cache,
+ BlockCacheTracer* const block_cache_tracer)
+ : ioptions_(ioptions),
+ file_options_(file_options),
+ cache_(cache),
+ immortal_tables_(false),
+ block_cache_tracer_(block_cache_tracer) {
+ if (ioptions_.row_cache) {
+ // If the same cache is shared by multiple instances, we need to
+ // disambiguate its entries.
+ PutVarint64(&row_cache_id_, ioptions_.row_cache->NewId());
+ }
+}
+
+TableCache::~TableCache() {
+}
+
+TableReader* TableCache::GetTableReaderFromHandle(Cache::Handle* handle) {
+ return reinterpret_cast<TableReader*>(cache_->Value(handle));
+}
+
+void TableCache::ReleaseHandle(Cache::Handle* handle) {
+ cache_->Release(handle);
+}
+
+Status TableCache::GetTableReader(
+ const FileOptions& file_options,
+ const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
+ bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,
+ std::unique_ptr<TableReader>* table_reader,
+ const SliceTransform* prefix_extractor, bool skip_filters, int level,
+ bool prefetch_index_and_filter_in_cache) {
+ std::string fname =
+ TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId());
+ std::unique_ptr<FSRandomAccessFile> file;
+ Status s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
+ nullptr);
+ RecordTick(ioptions_.statistics, NO_FILE_OPENS);
+ if (s.IsPathNotFound()) {
+ fname = Rocks2LevelTableFileName(fname);
+ s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file, nullptr);
+ RecordTick(ioptions_.statistics, NO_FILE_OPENS);
+ }
+
+ if (s.ok()) {
+ if (!sequential_mode && ioptions_.advise_random_on_open) {
+ file->Hint(FSRandomAccessFile::kRandom);
+ }
+ StopWatch sw(ioptions_.env, ioptions_.statistics, TABLE_OPEN_IO_MICROS);
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(
+ std::move(file), fname, ioptions_.env,
+ record_read_stats ? ioptions_.statistics : nullptr, SST_READ_MICROS,
+ file_read_hist, ioptions_.rate_limiter, ioptions_.listeners));
+ s = ioptions_.table_factory->NewTableReader(
+ TableReaderOptions(ioptions_, prefix_extractor, file_options,
+ internal_comparator, skip_filters, immortal_tables_,
+ level, fd.largest_seqno, block_cache_tracer_),
+ std::move(file_reader), fd.GetFileSize(), table_reader,
+ prefetch_index_and_filter_in_cache);
+ TEST_SYNC_POINT("TableCache::GetTableReader:0");
+ }
+ return s;
+}
+
+void TableCache::EraseHandle(const FileDescriptor& fd, Cache::Handle* handle) {
+ ReleaseHandle(handle);
+ uint64_t number = fd.GetNumber();
+ Slice key = GetSliceForFileNumber(&number);
+ cache_->Erase(key);
+}
+
+Status TableCache::FindTable(const FileOptions& file_options,
+ const InternalKeyComparator& internal_comparator,
+ const FileDescriptor& fd, Cache::Handle** handle,
+ const SliceTransform* prefix_extractor,
+ const bool no_io, bool record_read_stats,
+ HistogramImpl* file_read_hist, bool skip_filters,
+ int level,
+ bool prefetch_index_and_filter_in_cache) {
+ PERF_TIMER_GUARD_WITH_ENV(find_table_nanos, ioptions_.env);
+ Status s;
+ uint64_t number = fd.GetNumber();
+ Slice key = GetSliceForFileNumber(&number);
+ *handle = cache_->Lookup(key);
+ TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0",
+ const_cast<bool*>(&no_io));
+
+ if (*handle == nullptr) {
+ if (no_io) { // Don't do IO and return a not-found status
+ return Status::Incomplete("Table not found in table_cache, no_io is set");
+ }
+ std::unique_ptr<TableReader> table_reader;
+ s = GetTableReader(file_options, internal_comparator, fd,
+ false /* sequential mode */, record_read_stats,
+ file_read_hist, &table_reader, prefix_extractor,
+ skip_filters, level, prefetch_index_and_filter_in_cache);
+ if (!s.ok()) {
+ assert(table_reader == nullptr);
+ RecordTick(ioptions_.statistics, NO_FILE_ERRORS);
+ // We do not cache error results so that if the error is transient,
+ // or somebody repairs the file, we recover automatically.
+ } else {
+ s = cache_->Insert(key, table_reader.get(), 1, &DeleteEntry<TableReader>,
+ handle);
+ if (s.ok()) {
+ // Release ownership of table reader.
+ table_reader.release();
+ }
+ }
+ }
+ return s;
+}
+
+InternalIterator* TableCache::NewIterator(
+ const ReadOptions& options, const FileOptions& file_options,
+ const InternalKeyComparator& icomparator, const FileMetaData& file_meta,
+ RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor,
+ TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
+ TableReaderCaller caller, Arena* arena, bool skip_filters, int level,
+ const InternalKey* smallest_compaction_key,
+ const InternalKey* largest_compaction_key) {
+ PERF_TIMER_GUARD(new_table_iterator_nanos);
+
+ Status s;
+ TableReader* table_reader = nullptr;
+ Cache::Handle* handle = nullptr;
+ if (table_reader_ptr != nullptr) {
+ *table_reader_ptr = nullptr;
+ }
+ bool for_compaction = caller == TableReaderCaller::kCompaction;
+ auto& fd = file_meta.fd;
+ table_reader = fd.table_reader;
+ if (table_reader == nullptr) {
+ s = FindTable(file_options, icomparator, fd, &handle, prefix_extractor,
+ options.read_tier == kBlockCacheTier /* no_io */,
+ !for_compaction /* record_read_stats */, file_read_hist,
+ skip_filters, level);
+ if (s.ok()) {
+ table_reader = GetTableReaderFromHandle(handle);
+ }
+ }
+ InternalIterator* result = nullptr;
+ if (s.ok()) {
+ if (options.table_filter &&
+ !options.table_filter(*table_reader->GetTableProperties())) {
+ result = NewEmptyInternalIterator<Slice>(arena);
+ } else {
+ result = table_reader->NewIterator(options, prefix_extractor, arena,
+ skip_filters, caller,
+ file_options.compaction_readahead_size);
+ }
+ if (handle != nullptr) {
+ result->RegisterCleanup(&UnrefEntry, cache_, handle);
+ handle = nullptr; // prevent from releasing below
+ }
+
+ if (for_compaction) {
+ table_reader->SetupForCompaction();
+ }
+ if (table_reader_ptr != nullptr) {
+ *table_reader_ptr = table_reader;
+ }
+ }
+ if (s.ok() && range_del_agg != nullptr && !options.ignore_range_deletions) {
+ if (range_del_agg->AddFile(fd.GetNumber())) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ static_cast<FragmentedRangeTombstoneIterator*>(
+ table_reader->NewRangeTombstoneIterator(options)));
+ if (range_del_iter != nullptr) {
+ s = range_del_iter->status();
+ }
+ if (s.ok()) {
+ const InternalKey* smallest = &file_meta.smallest;
+ const InternalKey* largest = &file_meta.largest;
+ if (smallest_compaction_key != nullptr) {
+ smallest = smallest_compaction_key;
+ }
+ if (largest_compaction_key != nullptr) {
+ largest = largest_compaction_key;
+ }
+ range_del_agg->AddTombstones(std::move(range_del_iter), smallest,
+ largest);
+ }
+ }
+ }
+
+ if (handle != nullptr) {
+ ReleaseHandle(handle);
+ }
+ if (!s.ok()) {
+ assert(result == nullptr);
+ result = NewErrorInternalIterator<Slice>(s, arena);
+ }
+ return result;
+}
+
+Status TableCache::GetRangeTombstoneIterator(
+ const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta,
+ std::unique_ptr<FragmentedRangeTombstoneIterator>* out_iter) {
+ const FileDescriptor& fd = file_meta.fd;
+ Status s;
+ TableReader* t = fd.table_reader;
+ Cache::Handle* handle = nullptr;
+ if (t == nullptr) {
+ s = FindTable(file_options_, internal_comparator, fd, &handle);
+ if (s.ok()) {
+ t = GetTableReaderFromHandle(handle);
+ }
+ }
+ if (s.ok()) {
+ out_iter->reset(t->NewRangeTombstoneIterator(options));
+ assert(out_iter);
+ }
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+void TableCache::CreateRowCacheKeyPrefix(const ReadOptions& options,
+ const FileDescriptor& fd,
+ const Slice& internal_key,
+ GetContext* get_context,
+ IterKey& row_cache_key) {
+ uint64_t fd_number = fd.GetNumber();
+ // We use the user key as cache key instead of the internal key,
+ // otherwise the whole cache would be invalidated every time the
+ // sequence key increases. However, to support caching snapshot
+ // reads, we append the sequence number (incremented by 1 to
+ // distinguish from 0) only in this case.
+ // If the snapshot is larger than the largest seqno in the file,
+ // all data should be exposed to the snapshot, so we treat it
+ // the same as there is no snapshot. The exception is that if
+ // a seq-checking callback is registered, some internal keys
+ // may still be filtered out.
+ uint64_t seq_no = 0;
+ // Maybe we can include the whole file ifsnapshot == fd.largest_seqno.
+ if (options.snapshot != nullptr &&
+ (get_context->has_callback() ||
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ options.snapshot)
+ ->GetSequenceNumber() <= fd.largest_seqno)) {
+ // We should consider to use options.snapshot->GetSequenceNumber()
+ // instead of GetInternalKeySeqno(k), which will make the code
+ // easier to understand.
+ seq_no = 1 + GetInternalKeySeqno(internal_key);
+ }
+
+ // Compute row cache key.
+ row_cache_key.TrimAppend(row_cache_key.Size(), row_cache_id_.data(),
+ row_cache_id_.size());
+ AppendVarint64(&row_cache_key, fd_number);
+ AppendVarint64(&row_cache_key, seq_no);
+}
+
+bool TableCache::GetFromRowCache(const Slice& user_key, IterKey& row_cache_key,
+ size_t prefix_size, GetContext* get_context) {
+ bool found = false;
+
+ row_cache_key.TrimAppend(prefix_size, user_key.data(), user_key.size());
+ if (auto row_handle =
+ ioptions_.row_cache->Lookup(row_cache_key.GetUserKey())) {
+ // Cleanable routine to release the cache entry
+ Cleanable value_pinner;
+ auto release_cache_entry_func = [](void* cache_to_clean,
+ void* cache_handle) {
+ ((Cache*)cache_to_clean)->Release((Cache::Handle*)cache_handle);
+ };
+ auto found_row_cache_entry =
+ static_cast<const std::string*>(ioptions_.row_cache->Value(row_handle));
+ // If it comes here value is located on the cache.
+ // found_row_cache_entry points to the value on cache,
+ // and value_pinner has cleanup procedure for the cached entry.
+ // After replayGetContextLog() returns, get_context.pinnable_slice_
+ // will point to cache entry buffer (or a copy based on that) and
+ // cleanup routine under value_pinner will be delegated to
+ // get_context.pinnable_slice_. Cache entry is released when
+ // get_context.pinnable_slice_ is reset.
+ value_pinner.RegisterCleanup(release_cache_entry_func,
+ ioptions_.row_cache.get(), row_handle);
+ replayGetContextLog(*found_row_cache_entry, user_key, get_context,
+ &value_pinner);
+ RecordTick(ioptions_.statistics, ROW_CACHE_HIT);
+ found = true;
+ } else {
+ RecordTick(ioptions_.statistics, ROW_CACHE_MISS);
+ }
+ return found;
+}
+#endif // ROCKSDB_LITE
+
+Status TableCache::Get(const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta, const Slice& k,
+ GetContext* get_context,
+ const SliceTransform* prefix_extractor,
+ HistogramImpl* file_read_hist, bool skip_filters,
+ int level) {
+ auto& fd = file_meta.fd;
+ std::string* row_cache_entry = nullptr;
+ bool done = false;
+#ifndef ROCKSDB_LITE
+ IterKey row_cache_key;
+ std::string row_cache_entry_buffer;
+
+ // Check row cache if enabled. Since row cache does not currently store
+ // sequence numbers, we cannot use it if we need to fetch the sequence.
+ if (ioptions_.row_cache && !get_context->NeedToReadSequence()) {
+ auto user_key = ExtractUserKey(k);
+ CreateRowCacheKeyPrefix(options, fd, k, get_context, row_cache_key);
+ done = GetFromRowCache(user_key, row_cache_key, row_cache_key.Size(),
+ get_context);
+ if (!done) {
+ row_cache_entry = &row_cache_entry_buffer;
+ }
+ }
+#endif // ROCKSDB_LITE
+ Status s;
+ TableReader* t = fd.table_reader;
+ Cache::Handle* handle = nullptr;
+ if (!done && s.ok()) {
+ if (t == nullptr) {
+ s = FindTable(
+ file_options_, internal_comparator, fd, &handle, prefix_extractor,
+ options.read_tier == kBlockCacheTier /* no_io */,
+ true /* record_read_stats */, file_read_hist, skip_filters, level);
+ if (s.ok()) {
+ t = GetTableReaderFromHandle(handle);
+ }
+ }
+ SequenceNumber* max_covering_tombstone_seq =
+ get_context->max_covering_tombstone_seq();
+ if (s.ok() && max_covering_tombstone_seq != nullptr &&
+ !options.ignore_range_deletions) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ t->NewRangeTombstoneIterator(options));
+ if (range_del_iter != nullptr) {
+ *max_covering_tombstone_seq = std::max(
+ *max_covering_tombstone_seq,
+ range_del_iter->MaxCoveringTombstoneSeqnum(ExtractUserKey(k)));
+ }
+ }
+ if (s.ok()) {
+ get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
+ s = t->Get(options, k, get_context, prefix_extractor, skip_filters);
+ get_context->SetReplayLog(nullptr);
+ } else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
+ // Couldn't find Table in cache but treat as kFound if no_io set
+ get_context->MarkKeyMayExist();
+ s = Status::OK();
+ done = true;
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ // Put the replay log in row cache only if something was found.
+ if (!done && s.ok() && row_cache_entry && !row_cache_entry->empty()) {
+ size_t charge =
+ row_cache_key.Size() + row_cache_entry->size() + sizeof(std::string);
+ void* row_ptr = new std::string(std::move(*row_cache_entry));
+ ioptions_.row_cache->Insert(row_cache_key.GetUserKey(), row_ptr, charge,
+ &DeleteEntry<std::string>);
+ }
+#endif // ROCKSDB_LITE
+
+ if (handle != nullptr) {
+ ReleaseHandle(handle);
+ }
+ return s;
+}
+
+// Batched version of TableCache::MultiGet.
+Status TableCache::MultiGet(const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta,
+ const MultiGetContext::Range* mget_range,
+ const SliceTransform* prefix_extractor,
+ HistogramImpl* file_read_hist, bool skip_filters,
+ int level) {
+ auto& fd = file_meta.fd;
+ Status s;
+ TableReader* t = fd.table_reader;
+ Cache::Handle* handle = nullptr;
+ MultiGetRange table_range(*mget_range, mget_range->begin(),
+ mget_range->end());
+#ifndef ROCKSDB_LITE
+ autovector<std::string, MultiGetContext::MAX_BATCH_SIZE> row_cache_entries;
+ IterKey row_cache_key;
+ size_t row_cache_key_prefix_size = 0;
+ KeyContext& first_key = *table_range.begin();
+ bool lookup_row_cache =
+ ioptions_.row_cache && !first_key.get_context->NeedToReadSequence();
+
+ // Check row cache if enabled. Since row cache does not currently store
+ // sequence numbers, we cannot use it if we need to fetch the sequence.
+ if (lookup_row_cache) {
+ GetContext* first_context = first_key.get_context;
+ CreateRowCacheKeyPrefix(options, fd, first_key.ikey, first_context,
+ row_cache_key);
+ row_cache_key_prefix_size = row_cache_key.Size();
+
+ for (auto miter = table_range.begin(); miter != table_range.end();
+ ++miter) {
+ const Slice& user_key = miter->ukey;
+ ;
+ GetContext* get_context = miter->get_context;
+
+ if (GetFromRowCache(user_key, row_cache_key, row_cache_key_prefix_size,
+ get_context)) {
+ table_range.SkipKey(miter);
+ } else {
+ row_cache_entries.emplace_back();
+ get_context->SetReplayLog(&(row_cache_entries.back()));
+ }
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ // Check that table_range is not empty. Its possible all keys may have been
+ // found in the row cache and thus the range may now be empty
+ if (s.ok() && !table_range.empty()) {
+ if (t == nullptr) {
+ s = FindTable(
+ file_options_, internal_comparator, fd, &handle, prefix_extractor,
+ options.read_tier == kBlockCacheTier /* no_io */,
+ true /* record_read_stats */, file_read_hist, skip_filters, level);
+ TEST_SYNC_POINT_CALLBACK("TableCache::MultiGet:FindTable", &s);
+ if (s.ok()) {
+ t = GetTableReaderFromHandle(handle);
+ assert(t);
+ }
+ }
+ if (s.ok() && !options.ignore_range_deletions) {
+ std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
+ t->NewRangeTombstoneIterator(options));
+ if (range_del_iter != nullptr) {
+ for (auto iter = table_range.begin(); iter != table_range.end();
+ ++iter) {
+ SequenceNumber* max_covering_tombstone_seq =
+ iter->get_context->max_covering_tombstone_seq();
+ *max_covering_tombstone_seq =
+ std::max(*max_covering_tombstone_seq,
+ range_del_iter->MaxCoveringTombstoneSeqnum(iter->ukey));
+ }
+ }
+ }
+ if (s.ok()) {
+ t->MultiGet(options, &table_range, prefix_extractor, skip_filters);
+ } else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
+ for (auto iter = table_range.begin(); iter != table_range.end(); ++iter) {
+ Status* status = iter->s;
+ if (status->IsIncomplete()) {
+ // Couldn't find Table in cache but treat as kFound if no_io set
+ iter->get_context->MarkKeyMayExist();
+ s = Status::OK();
+ }
+ }
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ if (lookup_row_cache) {
+ size_t row_idx = 0;
+
+ for (auto miter = table_range.begin(); miter != table_range.end();
+ ++miter) {
+ std::string& row_cache_entry = row_cache_entries[row_idx++];
+ const Slice& user_key = miter->ukey;
+ ;
+ GetContext* get_context = miter->get_context;
+
+ get_context->SetReplayLog(nullptr);
+ // Compute row cache key.
+ row_cache_key.TrimAppend(row_cache_key_prefix_size, user_key.data(),
+ user_key.size());
+ // Put the replay log in row cache only if something was found.
+ if (s.ok() && !row_cache_entry.empty()) {
+ size_t charge =
+ row_cache_key.Size() + row_cache_entry.size() + sizeof(std::string);
+ void* row_ptr = new std::string(std::move(row_cache_entry));
+ ioptions_.row_cache->Insert(row_cache_key.GetUserKey(), row_ptr, charge,
+ &DeleteEntry<std::string>);
+ }
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ if (handle != nullptr) {
+ ReleaseHandle(handle);
+ }
+ return s;
+}
+
+Status TableCache::GetTableProperties(
+ const FileOptions& file_options,
+ const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
+ std::shared_ptr<const TableProperties>* properties,
+ const SliceTransform* prefix_extractor, bool no_io) {
+ Status s;
+ auto table_reader = fd.table_reader;
+ // table already been pre-loaded?
+ if (table_reader) {
+ *properties = table_reader->GetTableProperties();
+
+ return s;
+ }
+
+ Cache::Handle* table_handle = nullptr;
+ s = FindTable(file_options, internal_comparator, fd, &table_handle,
+ prefix_extractor, no_io);
+ if (!s.ok()) {
+ return s;
+ }
+ assert(table_handle);
+ auto table = GetTableReaderFromHandle(table_handle);
+ *properties = table->GetTableProperties();
+ ReleaseHandle(table_handle);
+ return s;
+}
+
+size_t TableCache::GetMemoryUsageByTableReader(
+ const FileOptions& file_options,
+ const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
+ const SliceTransform* prefix_extractor) {
+ Status s;
+ auto table_reader = fd.table_reader;
+ // table already been pre-loaded?
+ if (table_reader) {
+ return table_reader->ApproximateMemoryUsage();
+ }
+
+ Cache::Handle* table_handle = nullptr;
+ s = FindTable(file_options, internal_comparator, fd, &table_handle,
+ prefix_extractor, true);
+ if (!s.ok()) {
+ return 0;
+ }
+ assert(table_handle);
+ auto table = GetTableReaderFromHandle(table_handle);
+ auto ret = table->ApproximateMemoryUsage();
+ ReleaseHandle(table_handle);
+ return ret;
+}
+
+void TableCache::Evict(Cache* cache, uint64_t file_number) {
+ cache->Erase(GetSliceForFileNumber(&file_number));
+}
+
+uint64_t TableCache::ApproximateOffsetOf(
+ const Slice& key, const FileDescriptor& fd, TableReaderCaller caller,
+ const InternalKeyComparator& internal_comparator,
+ const SliceTransform* prefix_extractor) {
+ uint64_t result = 0;
+ TableReader* table_reader = fd.table_reader;
+ Cache::Handle* table_handle = nullptr;
+ if (table_reader == nullptr) {
+ const bool for_compaction = (caller == TableReaderCaller::kCompaction);
+ Status s = FindTable(file_options_, internal_comparator, fd, &table_handle,
+ prefix_extractor, false /* no_io */,
+ !for_compaction /* record_read_stats */);
+ if (s.ok()) {
+ table_reader = GetTableReaderFromHandle(table_handle);
+ }
+ }
+
+ if (table_reader != nullptr) {
+ result = table_reader->ApproximateOffsetOf(key, caller);
+ }
+ if (table_handle != nullptr) {
+ ReleaseHandle(table_handle);
+ }
+
+ return result;
+}
+
+uint64_t TableCache::ApproximateSize(
+ const Slice& start, const Slice& end, const FileDescriptor& fd,
+ TableReaderCaller caller, const InternalKeyComparator& internal_comparator,
+ const SliceTransform* prefix_extractor) {
+ uint64_t result = 0;
+ TableReader* table_reader = fd.table_reader;
+ Cache::Handle* table_handle = nullptr;
+ if (table_reader == nullptr) {
+ const bool for_compaction = (caller == TableReaderCaller::kCompaction);
+ Status s = FindTable(file_options_, internal_comparator, fd, &table_handle,
+ prefix_extractor, false /* no_io */,
+ !for_compaction /* record_read_stats */);
+ if (s.ok()) {
+ table_reader = GetTableReaderFromHandle(table_handle);
+ }
+ }
+
+ if (table_reader != nullptr) {
+ result = table_reader->ApproximateSize(start, end, caller);
+ }
+ if (table_handle != nullptr) {
+ ReleaseHandle(table_handle);
+ }
+
+ return result;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/table_cache.h b/storage/rocksdb/rocksdb/db/table_cache.h
new file mode 100644
index 00000000..b9de824e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/table_cache.h
@@ -0,0 +1,226 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Thread-safe (provides internal synchronization)
+
+#pragma once
+#include <string>
+#include <vector>
+#include <stdint.h>
+
+#include "db/dbformat.h"
+#include "db/range_del_aggregator.h"
+#include "options/cf_options.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+#include "table/table_reader.h"
+#include "trace_replay/block_cache_tracer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Arena;
+struct FileDescriptor;
+class GetContext;
+class HistogramImpl;
+
+// Manages caching for TableReader objects for a column family. The actual
+// cache is allocated separately and passed to the constructor. TableCache
+// wraps around the underlying SST file readers by providing Get(),
+// MultiGet() and NewIterator() methods that hide the instantiation,
+// caching and access to the TableReader. The main purpose of this is
+// performance - by caching the TableReader, it avoids unnecessary file opens
+// and object allocation and instantiation. One exception is compaction, where
+// a new TableReader may be instantiated - see NewIterator() comments
+//
+// Another service provided by TableCache is managing the row cache - if the
+// DB is configured with a row cache, and the lookup key is present in the row
+// cache, lookup is very fast. The row cache is obtained from
+// ioptions.row_cache
+class TableCache {
+ public:
+ TableCache(const ImmutableCFOptions& ioptions,
+ const FileOptions& storage_options, Cache* cache,
+ BlockCacheTracer* const block_cache_tracer);
+ ~TableCache();
+
+ // Return an iterator for the specified file number (the corresponding
+ // file length must be exactly "file_size" bytes). If "table_reader_ptr"
+ // is non-nullptr, also sets "*table_reader_ptr" to point to the Table object
+ // underlying the returned iterator, or nullptr if no Table object underlies
+ // the returned iterator. The returned "*table_reader_ptr" object is owned
+ // by the cache and should not be deleted, and is valid for as long as the
+ // returned iterator is live.
+ // @param range_del_agg If non-nullptr, adds range deletions to the
+ // aggregator. If an error occurs, returns it in a NewErrorInternalIterator
+ // @param for_compaction If true, a new TableReader may be allocated (but
+ // not cached), depending on the CF options
+ // @param skip_filters Disables loading/accessing the filter block
+ // @param level The level this table is at, -1 for "not set / don't know"
+ InternalIterator* NewIterator(
+ const ReadOptions& options, const FileOptions& toptions,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
+ const SliceTransform* prefix_extractor, TableReader** table_reader_ptr,
+ HistogramImpl* file_read_hist, TableReaderCaller caller, Arena* arena,
+ bool skip_filters, int level, const InternalKey* smallest_compaction_key,
+ const InternalKey* largest_compaction_key);
+
+ // If a seek to internal key "k" in specified file finds an entry,
+ // call get_context->SaveValue() repeatedly until
+ // it returns false. As a side effect, it will insert the TableReader
+ // into the cache and potentially evict another entry
+ // @param get_context Context for get operation. The result of the lookup
+ // can be retrieved by calling get_context->State()
+ // @param file_read_hist If non-nullptr, the file reader statistics are
+ // recorded
+ // @param skip_filters Disables loading/accessing the filter block
+ // @param level The level this table is at, -1 for "not set / don't know"
+ Status Get(const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta, const Slice& k,
+ GetContext* get_context,
+ const SliceTransform* prefix_extractor = nullptr,
+ HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
+ int level = -1);
+
+ // Return the range delete tombstone iterator of the file specified by
+ // `file_meta`.
+ Status GetRangeTombstoneIterator(
+ const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta,
+ std::unique_ptr<FragmentedRangeTombstoneIterator>* out_iter);
+
+ // If a seek to internal key "k" in specified file finds an entry,
+ // call get_context->SaveValue() repeatedly until
+ // it returns false. As a side effect, it will insert the TableReader
+ // into the cache and potentially evict another entry
+ // @param mget_range Pointer to the structure describing a batch of keys to
+ // be looked up in this table file. The result is stored
+ // in the embedded GetContext
+ // @param skip_filters Disables loading/accessing the filter block
+ // @param level The level this table is at, -1 for "not set / don't know"
+ Status MultiGet(const ReadOptions& options,
+ const InternalKeyComparator& internal_comparator,
+ const FileMetaData& file_meta,
+ const MultiGetContext::Range* mget_range,
+ const SliceTransform* prefix_extractor = nullptr,
+ HistogramImpl* file_read_hist = nullptr,
+ bool skip_filters = false, int level = -1);
+
+ // Evict any entry for the specified file number
+ static void Evict(Cache* cache, uint64_t file_number);
+
+ // Clean table handle and erase it from the table cache
+ // Used in DB close, or the file is not live anymore.
+ void EraseHandle(const FileDescriptor& fd, Cache::Handle* handle);
+
+ // Find table reader
+ // @param skip_filters Disables loading/accessing the filter block
+ // @param level == -1 means not specified
+ Status FindTable(const FileOptions& toptions,
+ const InternalKeyComparator& internal_comparator,
+ const FileDescriptor& file_fd, Cache::Handle**,
+ const SliceTransform* prefix_extractor = nullptr,
+ const bool no_io = false, bool record_read_stats = true,
+ HistogramImpl* file_read_hist = nullptr,
+ bool skip_filters = false, int level = -1,
+ bool prefetch_index_and_filter_in_cache = true);
+
+ // Get TableReader from a cache handle.
+ TableReader* GetTableReaderFromHandle(Cache::Handle* handle);
+
+ // Get the table properties of a given table.
+ // @no_io: indicates if we should load table to the cache if it is not present
+ // in table cache yet.
+ // @returns: `properties` will be reset on success. Please note that we will
+ // return Status::Incomplete() if table is not present in cache and
+ // we set `no_io` to be true.
+ Status GetTableProperties(const FileOptions& toptions,
+ const InternalKeyComparator& internal_comparator,
+ const FileDescriptor& file_meta,
+ std::shared_ptr<const TableProperties>* properties,
+ const SliceTransform* prefix_extractor = nullptr,
+ bool no_io = false);
+
+ // Return total memory usage of the table reader of the file.
+ // 0 if table reader of the file is not loaded.
+ size_t GetMemoryUsageByTableReader(
+ const FileOptions& toptions,
+ const InternalKeyComparator& internal_comparator,
+ const FileDescriptor& fd,
+ const SliceTransform* prefix_extractor = nullptr);
+
+ // Returns approximated offset of a key in a file represented by fd.
+ uint64_t ApproximateOffsetOf(
+ const Slice& key, const FileDescriptor& fd, TableReaderCaller caller,
+ const InternalKeyComparator& internal_comparator,
+ const SliceTransform* prefix_extractor = nullptr);
+
+ // Returns approximated data size between start and end keys in a file
+ // represented by fd (the start key must not be greater than the end key).
+ uint64_t ApproximateSize(const Slice& start, const Slice& end,
+ const FileDescriptor& fd, TableReaderCaller caller,
+ const InternalKeyComparator& internal_comparator,
+ const SliceTransform* prefix_extractor = nullptr);
+
+ // Release the handle from a cache
+ void ReleaseHandle(Cache::Handle* handle);
+
+ Cache* get_cache() const { return cache_; }
+
+ // Capacity of the backing Cache that indicates inifinite TableCache capacity.
+ // For example when max_open_files is -1 we set the backing Cache to this.
+ static const int kInfiniteCapacity = 0x400000;
+
+ // The tables opened with this TableCache will be immortal, i.e., their
+ // lifetime is as long as that of the DB.
+ void SetTablesAreImmortal() {
+ if (cache_->GetCapacity() >= kInfiniteCapacity) {
+ immortal_tables_ = true;
+ }
+ }
+
+ private:
+ // Build a table reader
+ Status GetTableReader(const FileOptions& file_options,
+ const InternalKeyComparator& internal_comparator,
+ const FileDescriptor& fd, bool sequential_mode,
+ bool record_read_stats, HistogramImpl* file_read_hist,
+ std::unique_ptr<TableReader>* table_reader,
+ const SliceTransform* prefix_extractor = nullptr,
+ bool skip_filters = false, int level = -1,
+ bool prefetch_index_and_filter_in_cache = true);
+
+ // Create a key prefix for looking up the row cache. The prefix is of the
+ // format row_cache_id + fd_number + seq_no. Later, the user key can be
+ // appended to form the full key
+ void CreateRowCacheKeyPrefix(const ReadOptions& options,
+ const FileDescriptor& fd,
+ const Slice& internal_key,
+ GetContext* get_context, IterKey& row_cache_key);
+
+ // Helper function to lookup the row cache for a key. It appends the
+ // user key to row_cache_key at offset prefix_size
+ bool GetFromRowCache(const Slice& user_key, IterKey& row_cache_key,
+ size_t prefix_size, GetContext* get_context);
+
+ const ImmutableCFOptions& ioptions_;
+ const FileOptions& file_options_;
+ Cache* const cache_;
+ std::string row_cache_id_;
+ bool immortal_tables_;
+ BlockCacheTracer* const block_cache_tracer_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/table_properties_collector.cc b/storage/rocksdb/rocksdb/db/table_properties_collector.cc
new file mode 100644
index 00000000..d98ff5e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/table_properties_collector.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "db/dbformat.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+uint64_t GetUint64Property(const UserCollectedProperties& props,
+ const std::string& property_name,
+ bool* property_present) {
+ auto pos = props.find(property_name);
+ if (pos == props.end()) {
+ *property_present = false;
+ return 0;
+ }
+ Slice raw = pos->second;
+ uint64_t val = 0;
+ *property_present = true;
+ return GetVarint64(&raw, &val) ? val : 0;
+}
+
+} // namespace
+
+Status UserKeyTablePropertiesCollector::InternalAdd(const Slice& key,
+ const Slice& value,
+ uint64_t file_size) {
+ ParsedInternalKey ikey;
+ if (!ParseInternalKey(key, &ikey)) {
+ return Status::InvalidArgument("Invalid internal key");
+ }
+
+ return collector_->AddUserKey(ikey.user_key, value, GetEntryType(ikey.type),
+ ikey.sequence, file_size);
+}
+
+void UserKeyTablePropertiesCollector::BlockAdd(
+ uint64_t bLockRawBytes, uint64_t blockCompressedBytesFast,
+ uint64_t blockCompressedBytesSlow) {
+ return collector_->BlockAdd(bLockRawBytes, blockCompressedBytesFast,
+ blockCompressedBytesSlow);
+}
+
+Status UserKeyTablePropertiesCollector::Finish(
+ UserCollectedProperties* properties) {
+ return collector_->Finish(properties);
+}
+
+UserCollectedProperties
+UserKeyTablePropertiesCollector::GetReadableProperties() const {
+ return collector_->GetReadableProperties();
+}
+
+uint64_t GetDeletedKeys(
+ const UserCollectedProperties& props) {
+ bool property_present_ignored;
+ return GetUint64Property(props, TablePropertiesNames::kDeletedKeys,
+ &property_present_ignored);
+}
+
+uint64_t GetMergeOperands(const UserCollectedProperties& props,
+ bool* property_present) {
+ return GetUint64Property(
+ props, TablePropertiesNames::kMergeOperands, property_present);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/table_properties_collector.h b/storage/rocksdb/rocksdb/db/table_properties_collector.h
new file mode 100644
index 00000000..130eb64d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/table_properties_collector.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 defines a collection of statistics collectors.
+#pragma once
+
+#include "rocksdb/table_properties.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Base class for internal table properties collector.
+class IntTblPropCollector {
+ public:
+ virtual ~IntTblPropCollector() {}
+ virtual Status Finish(UserCollectedProperties* properties) = 0;
+
+ virtual const char* Name() const = 0;
+
+ // @params key the user key that is inserted into the table.
+ // @params value the value that is inserted into the table.
+ virtual Status InternalAdd(const Slice& key, const Slice& value,
+ uint64_t file_size) = 0;
+
+ virtual void BlockAdd(uint64_t blockRawBytes,
+ uint64_t blockCompressedBytesFast,
+ uint64_t blockCompressedBytesSlow) = 0;
+
+ virtual UserCollectedProperties GetReadableProperties() const = 0;
+
+ virtual bool NeedCompact() const { return false; }
+};
+
+// Factory for internal table properties collector.
+class IntTblPropCollectorFactory {
+ public:
+ virtual ~IntTblPropCollectorFactory() {}
+ // has to be thread-safe
+ virtual IntTblPropCollector* CreateIntTblPropCollector(
+ uint32_t column_family_id) = 0;
+
+ // The name of the properties collector can be used for debugging purpose.
+ virtual const char* Name() const = 0;
+};
+
+// When rocksdb creates a new table, it will encode all "user keys" into
+// "internal keys", which contains meta information of a given entry.
+//
+// This class extracts user key from the encoded internal key when Add() is
+// invoked.
+class UserKeyTablePropertiesCollector : public IntTblPropCollector {
+ public:
+ // transfer of ownership
+ explicit UserKeyTablePropertiesCollector(TablePropertiesCollector* collector)
+ : collector_(collector) {}
+
+ virtual ~UserKeyTablePropertiesCollector() {}
+
+ virtual Status InternalAdd(const Slice& key, const Slice& value,
+ uint64_t file_size) override;
+
+ virtual void BlockAdd(uint64_t blockRawBytes,
+ uint64_t blockCompressedBytesFast,
+ uint64_t blockCompressedBytesSlow) override;
+
+ virtual Status Finish(UserCollectedProperties* properties) override;
+
+ virtual const char* Name() const override { return collector_->Name(); }
+
+ UserCollectedProperties GetReadableProperties() const override;
+
+ virtual bool NeedCompact() const override {
+ return collector_->NeedCompact();
+ }
+
+ protected:
+ std::unique_ptr<TablePropertiesCollector> collector_;
+};
+
+class UserKeyTablePropertiesCollectorFactory
+ : public IntTblPropCollectorFactory {
+ public:
+ explicit UserKeyTablePropertiesCollectorFactory(
+ std::shared_ptr<TablePropertiesCollectorFactory> user_collector_factory)
+ : user_collector_factory_(user_collector_factory) {}
+ virtual IntTblPropCollector* CreateIntTblPropCollector(
+ uint32_t column_family_id) override {
+ TablePropertiesCollectorFactory::Context context;
+ context.column_family_id = column_family_id;
+ return new UserKeyTablePropertiesCollector(
+ user_collector_factory_->CreateTablePropertiesCollector(context));
+ }
+
+ virtual const char* Name() const override {
+ return user_collector_factory_->Name();
+ }
+
+ private:
+ std::shared_ptr<TablePropertiesCollectorFactory> user_collector_factory_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/table_properties_collector_test.cc b/storage/rocksdb/rocksdb/db/table_properties_collector_test.cc
new file mode 100644
index 00000000..5c202de8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/table_properties_collector_test.cc
@@ -0,0 +1,515 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "db/table_properties_collector.h"
+#include "env/composite_env_wrapper.h"
+#include "file/sequence_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "options/cf_options.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<bool> {
+ 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";
+
+void MakeBuilder(const Options& options, const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& moptions,
+ const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ std::unique_ptr<WritableFileWriter>* writable,
+ std::unique_ptr<TableBuilder>* builder) {
+ std::unique_ptr<WritableFile> wf(new test::StringSink);
+ writable->reset(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(wf)),
+ "" /* don't care */, EnvOptions()));
+ int unknown_level = -1;
+ builder->reset(NewTableBuilder(
+ ioptions, moptions, internal_comparator, int_tbl_prop_collector_factories,
+ kTestColumnFamilyId, kTestColumnFamilyName, writable->get(),
+ options.compression, options.sample_for_compression,
+ options.compression_opts, unknown_level));
+}
+} // 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 /* blockRawBytes */,
+ uint64_t /* blockCompressedBytesFast */,
+ uint64_t /* blockCompressedBytesSlow */) 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);
+ if (!backward_mode_) {
+ return new RegularKeysStartWithA();
+ } else {
+ return new RegularKeysStartWithABackwardCompatible();
+ }
+ }
+ IntTblPropCollector* CreateIntTblPropCollector(
+ uint32_t /*column_family_id*/) 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::pair<std::string, ValueType>, 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<TableBuilder> builder;
+ std::unique_ptr<WritableFileWriter> writer;
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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());
+ writer->Flush();
+
+ // -- Step 2: Read properties
+ LegacyWritableFileWrapper* file =
+ static_cast<LegacyWritableFileWrapper*>(writer->writable_file());
+ test::StringSink* fwf = static_cast<test::StringSink*>(file->target());
+ std::unique_ptr<RandomAccessFileReader> fake_file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(fwf->contents())));
+ TableProperties* props;
+ Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(),
+ magic_number, ioptions, &props,
+ true /* compression_type_missing */);
+ std::unique_ptr<TableProperties> props_guard(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<FlushBlockEveryThreePolicyFactory>();
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ test::PlainInternalKeyComparator ikc(options.comparator);
+ std::shared_ptr<TablePropertiesCollectorFactory> 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);
+
+#ifndef ROCKSDB_LITE // PlainTable is not supported in Lite
+ // 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<PlainTableFactory>(plain_table_options);
+ TestCustomizedTablePropertiesCollector(backward_mode_,
+ kPlainTableMagicNumber,
+ encode_as_internal, options, ikc);
+#endif // !ROCKSDB_LITE
+ }
+}
+
+namespace {
+void TestInternalKeyPropertiesCollector(
+ bool backward_mode, uint64_t magic_number, bool sanitized,
+ std::shared_ptr<TableFactory> 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<TableBuilder> builder;
+ std::unique_ptr<WritableFileWriter> writable;
+ Options options;
+ test::PlainInternalKeyComparator pikc(options.comparator);
+
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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<test::NullLogger>();
+ options = SanitizeOptions("db", // just a place holder
+ options);
+ ImmutableCFOptions ioptions(options);
+ GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories);
+ options.comparator = comparator;
+ }
+ const ImmutableCFOptions 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());
+ writable->Flush();
+
+ LegacyWritableFileWrapper* file =
+ static_cast<LegacyWritableFileWrapper*>(writable->writable_file());
+ test::StringSink* fwf = static_cast<test::StringSink*>(file->target());
+ std::unique_ptr<RandomAccessFileReader> reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(fwf->contents())));
+ TableProperties* props;
+ Status s =
+ ReadTableProperties(reader.get(), fwf->contents().size(), magic_number,
+ ioptions, &props, true /* compression_type_missing */);
+ ASSERT_OK(s);
+
+ std::unique_ptr<TableProperties> props_guard(props);
+ 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<BlockBasedTableFactory>());
+ if (backward_mode_) {
+ TestInternalKeyPropertiesCollector(
+ backward_mode_, kBlockBasedTableMagicNumber, false /* not sanitize */,
+ std::make_shared<BlockBasedTableFactory>());
+ }
+
+#ifndef ROCKSDB_LITE // PlainTable is not supported in Lite
+ 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<PlainTableFactory>(plain_table_options));
+#endif // !ROCKSDB_LITE
+}
+
+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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/transaction_log_impl.cc b/storage/rocksdb/rocksdb/db/transaction_log_impl.cc
new file mode 100644
index 00000000..56bc161a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/transaction_log_impl.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "db/transaction_log_impl.h"
+#include <cinttypes>
+#include "db/write_batch_internal.h"
+#include "file/sequence_file_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+TransactionLogIteratorImpl::TransactionLogIteratorImpl(
+ const std::string& dir, const ImmutableDBOptions* options,
+ const TransactionLogIterator::ReadOptions& read_options,
+ const EnvOptions& soptions, const SequenceNumber seq,
+ std::unique_ptr<VectorLogPtr> files, VersionSet const* const versions,
+ const bool seq_per_batch)
+ : dir_(dir),
+ options_(options),
+ read_options_(read_options),
+ soptions_(soptions),
+ starting_sequence_number_(seq),
+ files_(std::move(files)),
+ started_(false),
+ is_valid_(false),
+ current_file_index_(0),
+ current_batch_seq_(0),
+ current_last_seq_(0),
+ versions_(versions),
+ seq_per_batch_(seq_per_batch) {
+ assert(files_ != nullptr);
+ assert(versions_ != nullptr);
+
+ reporter_.env = options_->env;
+ reporter_.info_log = options_->info_log.get();
+ SeekToStartSequence(); // Seek till starting sequence
+}
+
+Status TransactionLogIteratorImpl::OpenLogFile(
+ const LogFile* log_file,
+ std::unique_ptr<SequentialFileReader>* file_reader) {
+ FileSystem* fs = options_->fs.get();
+ std::unique_ptr<FSSequentialFile> file;
+ std::string fname;
+ Status s;
+ EnvOptions optimized_env_options = fs->OptimizeForLogRead(soptions_);
+ if (log_file->Type() == kArchivedLogFile) {
+ fname = ArchivedLogFileName(dir_, log_file->LogNumber());
+ s = fs->NewSequentialFile(fname, optimized_env_options, &file, nullptr);
+ } else {
+ fname = LogFileName(dir_, log_file->LogNumber());
+ s = fs->NewSequentialFile(fname, optimized_env_options, &file, nullptr);
+ if (!s.ok()) {
+ // If cannot open file in DB directory.
+ // Try the archive dir, as it could have moved in the meanwhile.
+ fname = ArchivedLogFileName(dir_, log_file->LogNumber());
+ s = fs->NewSequentialFile(fname, optimized_env_options,
+ &file, nullptr);
+ }
+ }
+ if (s.ok()) {
+ file_reader->reset(new SequentialFileReader(std::move(file), fname));
+ }
+ return s;
+}
+
+BatchResult TransactionLogIteratorImpl::GetBatch() {
+ assert(is_valid_); // cannot call in a non valid state.
+ BatchResult result;
+ result.sequence = current_batch_seq_;
+ result.writeBatchPtr = std::move(current_batch_);
+ return result;
+}
+
+Status TransactionLogIteratorImpl::status() { return current_status_; }
+
+bool TransactionLogIteratorImpl::Valid() { return started_ && is_valid_; }
+
+bool TransactionLogIteratorImpl::RestrictedRead(Slice* record) {
+ // Don't read if no more complete entries to read from logs
+ if (current_last_seq_ >= versions_->LastSequence()) {
+ return false;
+ }
+ return current_log_reader_->ReadRecord(record, &scratch_);
+}
+
+void TransactionLogIteratorImpl::SeekToStartSequence(uint64_t start_file_index,
+ bool strict) {
+ Slice record;
+ started_ = false;
+ is_valid_ = false;
+ if (files_->size() <= start_file_index) {
+ return;
+ }
+ Status s =
+ OpenLogReader(files_->at(static_cast<size_t>(start_file_index)).get());
+ if (!s.ok()) {
+ current_status_ = s;
+ reporter_.Info(current_status_.ToString().c_str());
+ return;
+ }
+ while (RestrictedRead(&record)) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter_.Corruption(
+ record.size(), Status::Corruption("very small log record"));
+ continue;
+ }
+ UpdateCurrentWriteBatch(record);
+ if (current_last_seq_ >= starting_sequence_number_) {
+ if (strict && current_batch_seq_ != starting_sequence_number_) {
+ current_status_ = Status::Corruption(
+ "Gap in sequence number. Could not "
+ "seek to required sequence number");
+ reporter_.Info(current_status_.ToString().c_str());
+ return;
+ } else if (strict) {
+ reporter_.Info("Could seek required sequence number. Iterator will "
+ "continue.");
+ }
+ is_valid_ = true;
+ started_ = true; // set started_ as we could seek till starting sequence
+ return;
+ } else {
+ is_valid_ = false;
+ }
+ }
+
+ // Could not find start sequence in first file. Normally this must be the
+ // only file. Otherwise log the error and let the iterator return next entry
+ // If strict is set, we want to seek exactly till the start sequence and it
+ // should have been present in the file we scanned above
+ if (strict) {
+ current_status_ = Status::Corruption(
+ "Gap in sequence number. Could not "
+ "seek to required sequence number");
+ reporter_.Info(current_status_.ToString().c_str());
+ } else if (files_->size() != 1) {
+ current_status_ = Status::Corruption(
+ "Start sequence was not found, "
+ "skipping to the next available");
+ reporter_.Info(current_status_.ToString().c_str());
+ // Let NextImpl find the next available entry. started_ remains false
+ // because we don't want to check for gaps while moving to start sequence
+ NextImpl(true);
+ }
+}
+
+void TransactionLogIteratorImpl::Next() {
+ return NextImpl(false);
+}
+
+void TransactionLogIteratorImpl::NextImpl(bool internal) {
+ Slice record;
+ is_valid_ = false;
+ if (!internal && !started_) {
+ // Runs every time until we can seek to the start sequence
+ return SeekToStartSequence();
+ }
+ while(true) {
+ assert(current_log_reader_);
+ if (current_log_reader_->IsEOF()) {
+ current_log_reader_->UnmarkEOF();
+ }
+ while (RestrictedRead(&record)) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter_.Corruption(
+ record.size(), Status::Corruption("very small log record"));
+ continue;
+ } else {
+ // started_ should be true if called by application
+ assert(internal || started_);
+ // started_ should be false if called internally
+ assert(!internal || !started_);
+ UpdateCurrentWriteBatch(record);
+ if (internal && !started_) {
+ started_ = true;
+ }
+ return;
+ }
+ }
+
+ // Open the next file
+ if (current_file_index_ < files_->size() - 1) {
+ ++current_file_index_;
+ Status s = OpenLogReader(files_->at(current_file_index_).get());
+ if (!s.ok()) {
+ is_valid_ = false;
+ current_status_ = s;
+ return;
+ }
+ } else {
+ is_valid_ = false;
+ if (current_last_seq_ == versions_->LastSequence()) {
+ current_status_ = Status::OK();
+ } else {
+ const char* msg = "Create a new iterator to fetch the new tail.";
+ current_status_ = Status::TryAgain(msg);
+ }
+ return;
+ }
+ }
+}
+
+bool TransactionLogIteratorImpl::IsBatchExpected(
+ const WriteBatch* batch, const SequenceNumber expected_seq) {
+ assert(batch);
+ SequenceNumber batchSeq = WriteBatchInternal::Sequence(batch);
+ if (batchSeq != expected_seq) {
+ char buf[200];
+ snprintf(buf, sizeof(buf),
+ "Discontinuity in log records. Got seq=%" PRIu64
+ ", Expected seq=%" PRIu64 ", Last flushed seq=%" PRIu64
+ ".Log iterator will reseek the correct batch.",
+ batchSeq, expected_seq, versions_->LastSequence());
+ reporter_.Info(buf);
+ return false;
+ }
+ return true;
+}
+
+void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
+ std::unique_ptr<WriteBatch> batch(new WriteBatch());
+ WriteBatchInternal::SetContents(batch.get(), record);
+
+ SequenceNumber expected_seq = current_last_seq_ + 1;
+ // If the iterator has started, then confirm that we get continuous batches
+ if (started_ && !IsBatchExpected(batch.get(), expected_seq)) {
+ // Seek to the batch having expected sequence number
+ if (expected_seq < files_->at(current_file_index_)->StartSequence()) {
+ // Expected batch must lie in the previous log file
+ // Avoid underflow.
+ if (current_file_index_ != 0) {
+ current_file_index_--;
+ }
+ }
+ starting_sequence_number_ = expected_seq;
+ // currentStatus_ will be set to Ok if reseek succeeds
+ // Note: this is still ok in seq_pre_batch_ && two_write_queuesp_ mode
+ // that allows gaps in the WAL since it will still skip over the gap.
+ current_status_ = Status::NotFound("Gap in sequence numbers");
+ // In seq_per_batch_ mode, gaps in the seq are possible so the strict mode
+ // should be disabled
+ return SeekToStartSequence(current_file_index_, !seq_per_batch_);
+ }
+
+ struct BatchCounter : public WriteBatch::Handler {
+ SequenceNumber sequence_;
+ BatchCounter(SequenceNumber sequence) : sequence_(sequence) {}
+ Status MarkNoop(bool empty_batch) override {
+ if (!empty_batch) {
+ sequence_++;
+ }
+ return Status::OK();
+ }
+ Status MarkEndPrepare(const Slice&) override {
+ sequence_++;
+ return Status::OK();
+ }
+ Status MarkCommit(const Slice&) override {
+ sequence_++;
+ return Status::OK();
+ }
+
+ Status PutCF(uint32_t /*cf*/, const Slice& /*key*/,
+ const Slice& /*val*/) override {
+ return Status::OK();
+ }
+ Status DeleteCF(uint32_t /*cf*/, const Slice& /*key*/) override {
+ return Status::OK();
+ }
+ Status SingleDeleteCF(uint32_t /*cf*/, const Slice& /*key*/) override {
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t /*cf*/, const Slice& /*key*/,
+ const Slice& /*val*/) override {
+ return Status::OK();
+ }
+ Status MarkBeginPrepare(bool) override { return Status::OK(); }
+ Status MarkRollback(const Slice&) override { return Status::OK(); }
+ };
+
+ current_batch_seq_ = WriteBatchInternal::Sequence(batch.get());
+ if (seq_per_batch_) {
+ BatchCounter counter(current_batch_seq_);
+ batch->Iterate(&counter);
+ current_last_seq_ = counter.sequence_;
+ } else {
+ current_last_seq_ =
+ current_batch_seq_ + WriteBatchInternal::Count(batch.get()) - 1;
+ }
+ // currentBatchSeq_ can only change here
+ assert(current_last_seq_ <= versions_->LastSequence());
+
+ current_batch_ = std::move(batch);
+ is_valid_ = true;
+ current_status_ = Status::OK();
+}
+
+Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* log_file) {
+ std::unique_ptr<SequentialFileReader> file;
+ Status s = OpenLogFile(log_file, &file);
+ if (!s.ok()) {
+ return s;
+ }
+ assert(file);
+ current_log_reader_.reset(
+ new log::Reader(options_->info_log, std::move(file), &reporter_,
+ read_options_.verify_checksums_, log_file->LogNumber()));
+ return Status::OK();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/transaction_log_impl.h b/storage/rocksdb/rocksdb/db/transaction_log_impl.h
new file mode 100644
index 00000000..eb53daf2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/transaction_log_impl.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+#include <vector>
+
+#include "db/log_reader.h"
+#include "db/version_set.h"
+#include "file/filename.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class LogFileImpl : public LogFile {
+ public:
+ LogFileImpl(uint64_t logNum, WalFileType logType, SequenceNumber startSeq,
+ uint64_t sizeBytes) :
+ logNumber_(logNum),
+ type_(logType),
+ startSequence_(startSeq),
+ sizeFileBytes_(sizeBytes) {
+ }
+
+ std::string PathName() const override {
+ if (type_ == kArchivedLogFile) {
+ return ArchivedLogFileName("", logNumber_);
+ }
+ return LogFileName("", logNumber_);
+ }
+
+ uint64_t LogNumber() const override { return logNumber_; }
+
+ WalFileType Type() const override { return type_; }
+
+ SequenceNumber StartSequence() const override { return startSequence_; }
+
+ uint64_t SizeFileBytes() const override { return sizeFileBytes_; }
+
+ bool operator < (const LogFile& that) const {
+ return LogNumber() < that.LogNumber();
+ }
+
+ private:
+ uint64_t logNumber_;
+ WalFileType type_;
+ SequenceNumber startSequence_;
+ uint64_t sizeFileBytes_;
+
+};
+
+class TransactionLogIteratorImpl : public TransactionLogIterator {
+ public:
+ TransactionLogIteratorImpl(
+ const std::string& dir, const ImmutableDBOptions* options,
+ const TransactionLogIterator::ReadOptions& read_options,
+ const EnvOptions& soptions, const SequenceNumber seqNum,
+ std::unique_ptr<VectorLogPtr> files, VersionSet const* const versions,
+ const bool seq_per_batch);
+
+ virtual bool Valid() override;
+
+ virtual void Next() override;
+
+ virtual Status status() override;
+
+ virtual BatchResult GetBatch() override;
+
+ private:
+ const std::string& dir_;
+ const ImmutableDBOptions* options_;
+ const TransactionLogIterator::ReadOptions read_options_;
+ const EnvOptions& soptions_;
+ SequenceNumber starting_sequence_number_;
+ std::unique_ptr<VectorLogPtr> files_;
+ bool started_;
+ bool is_valid_; // not valid when it starts of.
+ Status current_status_;
+ size_t current_file_index_;
+ std::unique_ptr<WriteBatch> current_batch_;
+ std::unique_ptr<log::Reader> current_log_reader_;
+ std::string scratch_;
+ Status OpenLogFile(const LogFile* log_file,
+ std::unique_ptr<SequentialFileReader>* file);
+
+ struct LogReporter : public log::Reader::Reporter {
+ Env* env;
+ Logger* info_log;
+ virtual void Corruption(size_t bytes, const Status& s) override {
+ ROCKS_LOG_ERROR(info_log, "dropping %" ROCKSDB_PRIszt " bytes; %s", bytes,
+ s.ToString().c_str());
+ }
+ virtual void Info(const char* s) { ROCKS_LOG_INFO(info_log, "%s", s); }
+ } reporter_;
+
+ SequenceNumber
+ current_batch_seq_; // sequence number at start of current batch
+ SequenceNumber current_last_seq_; // last sequence in the current batch
+ // Used only to get latest seq. num
+ // TODO(icanadi) can this be just a callback?
+ VersionSet const* const versions_;
+ const bool seq_per_batch_;
+ // Reads from transaction log only if the writebatch record has been written
+ bool RestrictedRead(Slice* record);
+ // Seeks to startingSequenceNumber reading from startFileIndex in files_.
+ // If strict is set,then must get a batch starting with startingSequenceNumber
+ void SeekToStartSequence(uint64_t start_file_index = 0, bool strict = false);
+ // Implementation of Next. SeekToStartSequence calls it internally with
+ // internal=true to let it find next entry even if it has to jump gaps because
+ // the iterator may start off from the first available entry but promises to
+ // be continuous after that
+ void NextImpl(bool internal = false);
+ // Check if batch is expected, else return false
+ bool IsBatchExpected(const WriteBatch* batch, SequenceNumber expected_seq);
+ // Update current batch if a continuous batch is found, else return false
+ void UpdateCurrentWriteBatch(const Slice& record);
+ Status OpenLogReader(const LogFile* file);
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/trim_history_scheduler.cc b/storage/rocksdb/rocksdb/db/trim_history_scheduler.cc
new file mode 100644
index 00000000..d7ca0899
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/trim_history_scheduler.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/trim_history_scheduler.h"
+
+#include <cassert>
+
+#include "db/column_family.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void TrimHistoryScheduler::ScheduleWork(ColumnFamilyData* cfd) {
+ std::lock_guard<std::mutex> lock(checking_mutex_);
+ cfd->Ref();
+ cfds_.push_back(cfd);
+ is_empty_.store(false, std::memory_order_relaxed);
+}
+
+ColumnFamilyData* TrimHistoryScheduler::TakeNextColumnFamily() {
+ std::lock_guard<std::mutex> lock(checking_mutex_);
+ while (true) {
+ if (cfds_.empty()) {
+ return nullptr;
+ }
+ ColumnFamilyData* cfd = cfds_.back();
+ cfds_.pop_back();
+ if (cfds_.empty()) {
+ is_empty_.store(true, std::memory_order_relaxed);
+ }
+
+ if (!cfd->IsDropped()) {
+ // success
+ return cfd;
+ }
+ cfd->UnrefAndTryDelete();
+ }
+}
+
+bool TrimHistoryScheduler::Empty() {
+ bool is_empty = is_empty_.load(std::memory_order_relaxed);
+ return is_empty;
+}
+
+void TrimHistoryScheduler::Clear() {
+ ColumnFamilyData* cfd;
+ while ((cfd = TakeNextColumnFamily()) != nullptr) {
+ cfd->UnrefAndTryDelete();
+ }
+ assert(Empty());
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/trim_history_scheduler.h b/storage/rocksdb/rocksdb/db/trim_history_scheduler.h
new file mode 100644
index 00000000..b17f6170
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/trim_history_scheduler.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <atomic>
+#include <mutex>
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyData;
+
+// Similar to FlushScheduler, TrimHistoryScheduler is a FIFO queue that keeps
+// track of column families whose flushed immutable memtables may need to be
+// removed (aka trimmed). The actual trimming may be slightly delayed. Due to
+// the use of the mutex and atomic variable, ScheduleWork,
+// TakeNextColumnFamily, and, Empty can be called concurrently.
+class TrimHistoryScheduler {
+ public:
+ TrimHistoryScheduler() : is_empty_(true) {}
+
+ // When a column family needs history trimming, add cfd to the FIFO queue
+ void ScheduleWork(ColumnFamilyData* cfd);
+
+ // Remove the column family from the queue, the caller is responsible for
+ // calling `MemtableList::TrimHistory`
+ ColumnFamilyData* TakeNextColumnFamily();
+
+ bool Empty();
+
+ void Clear();
+
+ // Not on critical path, use mutex to ensure thread safety
+ private:
+ std::atomic<bool> is_empty_;
+ autovector<ColumnFamilyData*> cfds_;
+ std::mutex checking_mutex_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_builder.cc b/storage/rocksdb/rocksdb/db/version_builder.cc
new file mode 100644
index 00000000..4694218a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_builder.cc
@@ -0,0 +1,545 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_builder.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cinttypes>
+#include <functional>
+#include <map>
+#include <set>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/internal_stats.h"
+#include "db/table_cache.h"
+#include "db/version_set.h"
+#include "port/port.h"
+#include "table/table_reader.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
+ if (a->fd.largest_seqno != b->fd.largest_seqno) {
+ return a->fd.largest_seqno > b->fd.largest_seqno;
+ }
+ if (a->fd.smallest_seqno != b->fd.smallest_seqno) {
+ return a->fd.smallest_seqno > b->fd.smallest_seqno;
+ }
+ // Break ties by file number
+ return a->fd.GetNumber() > b->fd.GetNumber();
+}
+
+namespace {
+bool BySmallestKey(FileMetaData* a, FileMetaData* b,
+ const InternalKeyComparator* cmp) {
+ int r = cmp->Compare(a->smallest, b->smallest);
+ if (r != 0) {
+ return (r < 0);
+ }
+ // Break ties by file number
+ return (a->fd.GetNumber() < b->fd.GetNumber());
+}
+} // namespace
+
+class VersionBuilder::Rep {
+ private:
+ // Helper to sort files_ in v
+ // kLevel0 -- NewestFirstBySeqNo
+ // kLevelNon0 -- BySmallestKey
+ struct FileComparator {
+ enum SortMethod { kLevel0 = 0, kLevelNon0 = 1, } sort_method;
+ const InternalKeyComparator* internal_comparator;
+
+ FileComparator() : internal_comparator(nullptr) {}
+
+ bool operator()(FileMetaData* f1, FileMetaData* f2) const {
+ switch (sort_method) {
+ case kLevel0:
+ return NewestFirstBySeqNo(f1, f2);
+ case kLevelNon0:
+ return BySmallestKey(f1, f2, internal_comparator);
+ }
+ assert(false);
+ return false;
+ }
+ };
+
+ struct LevelState {
+ std::unordered_set<uint64_t> deleted_files;
+ // Map from file number to file meta data.
+ std::unordered_map<uint64_t, FileMetaData*> added_files;
+ };
+
+ const FileOptions& file_options_;
+ Logger* info_log_;
+ TableCache* table_cache_;
+ VersionStorageInfo* base_vstorage_;
+ int num_levels_;
+ LevelState* levels_;
+ // Store states of levels larger than num_levels_. We do this instead of
+ // storing them in levels_ to avoid regression in case there are no files
+ // on invalid levels. The version is not consistent if in the end the files
+ // on invalid levels don't cancel out.
+ std::map<int, std::unordered_set<uint64_t>> invalid_levels_;
+ // Whether there are invalid new files or invalid deletion on levels larger
+ // than num_levels_.
+ bool has_invalid_levels_;
+ FileComparator level_zero_cmp_;
+ FileComparator level_nonzero_cmp_;
+
+ public:
+ Rep(const FileOptions& file_options, Logger* info_log,
+ TableCache* table_cache,
+ VersionStorageInfo* base_vstorage)
+ : file_options_(file_options),
+ info_log_(info_log),
+ table_cache_(table_cache),
+ base_vstorage_(base_vstorage),
+ num_levels_(base_vstorage->num_levels()),
+ has_invalid_levels_(false) {
+ levels_ = new LevelState[num_levels_];
+ level_zero_cmp_.sort_method = FileComparator::kLevel0;
+ level_nonzero_cmp_.sort_method = FileComparator::kLevelNon0;
+ level_nonzero_cmp_.internal_comparator =
+ base_vstorage_->InternalComparator();
+ }
+
+ ~Rep() {
+ for (int level = 0; level < num_levels_; level++) {
+ const auto& added = levels_[level].added_files;
+ for (auto& pair : added) {
+ UnrefFile(pair.second);
+ }
+ }
+
+ delete[] levels_;
+ }
+
+ void UnrefFile(FileMetaData* f) {
+ f->refs--;
+ if (f->refs <= 0) {
+ if (f->table_reader_handle) {
+ assert(table_cache_ != nullptr);
+ table_cache_->ReleaseHandle(f->table_reader_handle);
+ f->table_reader_handle = nullptr;
+ }
+ delete f;
+ }
+ }
+
+ Status CheckConsistency(VersionStorageInfo* vstorage) {
+#ifdef NDEBUG
+ if (!vstorage->force_consistency_checks()) {
+ // Dont run consistency checks in release mode except if
+ // explicitly asked to
+ return Status::OK();
+ }
+#endif
+ // make sure the files are sorted correctly
+ for (int level = 0; level < num_levels_; level++) {
+ auto& level_files = vstorage->LevelFiles(level);
+ for (size_t i = 1; i < level_files.size(); i++) {
+ auto f1 = level_files[i - 1];
+ auto f2 = level_files[i];
+#ifndef NDEBUG
+ auto pair = std::make_pair(&f1, &f2);
+ TEST_SYNC_POINT_CALLBACK("VersionBuilder::CheckConsistency", &pair);
+#endif
+ if (level == 0) {
+ if (!level_zero_cmp_(f1, f2)) {
+ fprintf(stderr, "L0 files are not sorted properly");
+ return Status::Corruption("L0 files are not sorted properly");
+ }
+
+ if (f2->fd.smallest_seqno == f2->fd.largest_seqno) {
+ // This is an external file that we ingested
+ SequenceNumber external_file_seqno = f2->fd.smallest_seqno;
+ if (!(external_file_seqno < f1->fd.largest_seqno ||
+ external_file_seqno == 0)) {
+ fprintf(stderr,
+ "L0 file with seqno %" PRIu64 " %" PRIu64
+ " vs. file with global_seqno %" PRIu64 "\n",
+ f1->fd.smallest_seqno, f1->fd.largest_seqno,
+ external_file_seqno);
+ return Status::Corruption(
+ "L0 file with seqno " +
+ NumberToString(f1->fd.smallest_seqno) + " " +
+ NumberToString(f1->fd.largest_seqno) +
+ " vs. file with global_seqno" +
+ NumberToString(external_file_seqno) + " with fileNumber " +
+ NumberToString(f1->fd.GetNumber()));
+ }
+ } else if (f1->fd.smallest_seqno <= f2->fd.smallest_seqno) {
+ fprintf(stderr,
+ "L0 files seqno %" PRIu64 " %" PRIu64 " vs. %" PRIu64
+ " %" PRIu64 "\n",
+ f1->fd.smallest_seqno, f1->fd.largest_seqno,
+ f2->fd.smallest_seqno, f2->fd.largest_seqno);
+ return Status::Corruption(
+ "L0 files seqno " + NumberToString(f1->fd.smallest_seqno) +
+ " " + NumberToString(f1->fd.largest_seqno) + " " +
+ NumberToString(f1->fd.GetNumber()) + " vs. " +
+ NumberToString(f2->fd.smallest_seqno) + " " +
+ NumberToString(f2->fd.largest_seqno) + " " +
+ NumberToString(f2->fd.GetNumber()));
+ }
+ } else {
+ if (!level_nonzero_cmp_(f1, f2)) {
+ fprintf(stderr, "L%d files are not sorted properly", level);
+ return Status::Corruption("L" + NumberToString(level) +
+ " files are not sorted properly");
+ }
+
+ // Make sure there is no overlap in levels > 0
+ if (vstorage->InternalComparator()->Compare(f1->largest,
+ f2->smallest) >= 0) {
+ fprintf(stderr, "L%d have overlapping ranges %s vs. %s\n", level,
+ (f1->largest).DebugString(true).c_str(),
+ (f2->smallest).DebugString(true).c_str());
+ return Status::Corruption(
+ "L" + NumberToString(level) + " have overlapping ranges " +
+ (f1->largest).DebugString(true) + " vs. " +
+ (f2->smallest).DebugString(true));
+ }
+ }
+ }
+ }
+ return Status::OK();
+ }
+
+ Status CheckConsistencyForDeletes(VersionEdit* /*edit*/, uint64_t number,
+ int level) {
+#ifdef NDEBUG
+ if (!base_vstorage_->force_consistency_checks()) {
+ // Dont run consistency checks in release mode except if
+ // explicitly asked to
+ return Status::OK();
+ }
+#endif
+ // a file to be deleted better exist in the previous version
+ bool found = false;
+ for (int l = 0; !found && l < num_levels_; l++) {
+ const std::vector<FileMetaData*>& base_files =
+ base_vstorage_->LevelFiles(l);
+ for (size_t i = 0; i < base_files.size(); i++) {
+ FileMetaData* f = base_files[i];
+ if (f->fd.GetNumber() == number) {
+ found = true;
+ break;
+ }
+ }
+ }
+ // if the file did not exist in the previous version, then it
+ // is possibly moved from lower level to higher level in current
+ // version
+ for (int l = level + 1; !found && l < num_levels_; l++) {
+ auto& level_added = levels_[l].added_files;
+ auto got = level_added.find(number);
+ if (got != level_added.end()) {
+ found = true;
+ break;
+ }
+ }
+
+ // maybe this file was added in a previous edit that was Applied
+ if (!found) {
+ auto& level_added = levels_[level].added_files;
+ auto got = level_added.find(number);
+ if (got != level_added.end()) {
+ found = true;
+ }
+ }
+ if (!found) {
+ fprintf(stderr, "not found %" PRIu64 "\n", number);
+ return Status::Corruption("not found " + NumberToString(number));
+ }
+ return Status::OK();
+ }
+
+ bool CheckConsistencyForNumLevels() {
+ // Make sure there are no files on or beyond num_levels().
+ if (has_invalid_levels_) {
+ return false;
+ }
+ for (auto& level : invalid_levels_) {
+ if (level.second.size() > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Apply all of the edits in *edit to the current state.
+ Status Apply(VersionEdit* edit) {
+ Status s = CheckConsistency(base_vstorage_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Delete files
+ const auto& del = edit->GetDeletedFiles();
+ for (const auto& del_file : del) {
+ const auto level = del_file.first;
+ const auto number = del_file.second;
+ if (level < num_levels_) {
+ levels_[level].deleted_files.insert(number);
+ CheckConsistencyForDeletes(edit, number, level);
+
+ auto exising = levels_[level].added_files.find(number);
+ if (exising != levels_[level].added_files.end()) {
+ UnrefFile(exising->second);
+ levels_[level].added_files.erase(exising);
+ }
+ } else {
+ if (invalid_levels_[level].erase(number) == 0) {
+ // Deleting an non-existing file on invalid level.
+ has_invalid_levels_ = true;
+ }
+ }
+ }
+
+ // Add new files
+ for (const auto& new_file : edit->GetNewFiles()) {
+ const int level = new_file.first;
+ if (level < num_levels_) {
+ FileMetaData* f = new FileMetaData(new_file.second);
+ f->refs = 1;
+
+ assert(levels_[level].added_files.find(f->fd.GetNumber()) ==
+ levels_[level].added_files.end());
+ levels_[level].deleted_files.erase(f->fd.GetNumber());
+ levels_[level].added_files[f->fd.GetNumber()] = f;
+ } else {
+ uint64_t number = new_file.second.fd.GetNumber();
+ auto& lvls = invalid_levels_[level];
+ if (lvls.count(number) == 0) {
+ lvls.insert(number);
+ } else {
+ // Creating an already existing file on invalid level.
+ has_invalid_levels_ = true;
+ }
+ }
+ }
+ return s;
+ }
+
+ // Save the current state in *v.
+ Status SaveTo(VersionStorageInfo* vstorage) {
+ Status s = CheckConsistency(base_vstorage_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = CheckConsistency(vstorage);
+ if (!s.ok()) {
+ return s;
+ }
+
+ for (int level = 0; level < num_levels_; level++) {
+ const auto& cmp = (level == 0) ? level_zero_cmp_ : level_nonzero_cmp_;
+ // Merge the set of added files with the set of pre-existing files.
+ // Drop any deleted files. Store the result in *v.
+ const auto& base_files = base_vstorage_->LevelFiles(level);
+ const auto& unordered_added_files = levels_[level].added_files;
+ vstorage->Reserve(level,
+ base_files.size() + unordered_added_files.size());
+
+ // Sort added files for the level.
+ std::vector<FileMetaData*> added_files;
+ added_files.reserve(unordered_added_files.size());
+ for (const auto& pair : unordered_added_files) {
+ added_files.push_back(pair.second);
+ }
+ std::sort(added_files.begin(), added_files.end(), cmp);
+
+#ifndef NDEBUG
+ FileMetaData* prev_added_file = nullptr;
+ for (const auto& added : added_files) {
+ if (level > 0 && prev_added_file != nullptr) {
+ assert(base_vstorage_->InternalComparator()->Compare(
+ prev_added_file->smallest, added->smallest) <= 0);
+ }
+ prev_added_file = added;
+ }
+#endif
+
+ auto base_iter = base_files.begin();
+ auto base_end = base_files.end();
+ auto added_iter = added_files.begin();
+ auto added_end = added_files.end();
+ while (added_iter != added_end || base_iter != base_end) {
+ if (base_iter == base_end ||
+ (added_iter != added_end && cmp(*added_iter, *base_iter))) {
+ MaybeAddFile(vstorage, level, *added_iter++);
+ } else {
+ MaybeAddFile(vstorage, level, *base_iter++);
+ }
+ }
+ }
+
+ s = CheckConsistency(vstorage);
+ return s;
+ }
+
+ Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
+ bool prefetch_index_and_filter_in_cache,
+ bool is_initial_load,
+ const SliceTransform* prefix_extractor) {
+ assert(table_cache_ != nullptr);
+
+ size_t table_cache_capacity = table_cache_->get_cache()->GetCapacity();
+ bool always_load = (table_cache_capacity == TableCache::kInfiniteCapacity);
+ size_t max_load = port::kMaxSizet;
+
+ if (!always_load) {
+ // If it is initial loading and not set to always laoding all the
+ // files, we only load up to kInitialLoadLimit files, to limit the
+ // time reopening the DB.
+ const size_t kInitialLoadLimit = 16;
+ size_t load_limit;
+ // If the table cache is not 1/4 full, we pin the table handle to
+ // file metadata to avoid the cache read costs when reading the file.
+ // The downside of pinning those files is that LRU won't be followed
+ // for those files. This doesn't matter much because if number of files
+ // of the DB excceeds table cache capacity, eventually no table reader
+ // will be pinned and LRU will be followed.
+ if (is_initial_load) {
+ load_limit = std::min(kInitialLoadLimit, table_cache_capacity / 4);
+ } else {
+ load_limit = table_cache_capacity / 4;
+ }
+
+ size_t table_cache_usage = table_cache_->get_cache()->GetUsage();
+ if (table_cache_usage >= load_limit) {
+ // TODO (yanqin) find a suitable status code.
+ return Status::OK();
+ } else {
+ max_load = load_limit - table_cache_usage;
+ }
+ }
+
+ // <file metadata, level>
+ std::vector<std::pair<FileMetaData*, int>> files_meta;
+ std::vector<Status> statuses;
+ for (int level = 0; level < num_levels_; level++) {
+ for (auto& file_meta_pair : levels_[level].added_files) {
+ auto* file_meta = file_meta_pair.second;
+ // If the file has been opened before, just skip it.
+ if (!file_meta->table_reader_handle) {
+ files_meta.emplace_back(file_meta, level);
+ statuses.emplace_back(Status::OK());
+ }
+ if (files_meta.size() >= max_load) {
+ break;
+ }
+ }
+ if (files_meta.size() >= max_load) {
+ break;
+ }
+ }
+
+ std::atomic<size_t> next_file_meta_idx(0);
+ std::function<void()> load_handlers_func([&]() {
+ while (true) {
+ size_t file_idx = next_file_meta_idx.fetch_add(1);
+ if (file_idx >= files_meta.size()) {
+ break;
+ }
+
+ auto* file_meta = files_meta[file_idx].first;
+ int level = files_meta[file_idx].second;
+ statuses[file_idx] = table_cache_->FindTable(
+ file_options_, *(base_vstorage_->InternalComparator()),
+ file_meta->fd, &file_meta->table_reader_handle, prefix_extractor,
+ false /*no_io */, true /* record_read_stats */,
+ internal_stats->GetFileReadHist(level), false, level,
+ prefetch_index_and_filter_in_cache);
+ if (file_meta->table_reader_handle != nullptr) {
+ // Load table_reader
+ file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle(
+ file_meta->table_reader_handle);
+ }
+ }
+ });
+
+ std::vector<port::Thread> threads;
+ for (int i = 1; i < max_threads; i++) {
+ threads.emplace_back(load_handlers_func);
+ }
+ load_handlers_func();
+ for (auto& t : threads) {
+ t.join();
+ }
+ for (const auto& s : statuses) {
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ return Status::OK();
+ }
+
+ void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f) {
+ if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) {
+ // f is to-be-deleted table file
+ vstorage->RemoveCurrentStats(f);
+ } else {
+ vstorage->AddFile(level, f, info_log_);
+ }
+ }
+};
+
+VersionBuilder::VersionBuilder(const FileOptions& file_options,
+ TableCache* table_cache,
+ VersionStorageInfo* base_vstorage,
+ Logger* info_log)
+ : rep_(new Rep(file_options, info_log, table_cache, base_vstorage)) {}
+
+VersionBuilder::~VersionBuilder() { delete rep_; }
+
+Status VersionBuilder::CheckConsistency(VersionStorageInfo* vstorage) {
+ return rep_->CheckConsistency(vstorage);
+}
+
+Status VersionBuilder::CheckConsistencyForDeletes(VersionEdit* edit,
+ uint64_t number, int level) {
+ return rep_->CheckConsistencyForDeletes(edit, number, level);
+}
+
+bool VersionBuilder::CheckConsistencyForNumLevels() {
+ return rep_->CheckConsistencyForNumLevels();
+}
+
+Status VersionBuilder::Apply(VersionEdit* edit) { return rep_->Apply(edit); }
+
+Status VersionBuilder::SaveTo(VersionStorageInfo* vstorage) {
+ return rep_->SaveTo(vstorage);
+}
+
+Status VersionBuilder::LoadTableHandlers(
+ InternalStats* internal_stats, int max_threads,
+ bool prefetch_index_and_filter_in_cache, bool is_initial_load,
+ const SliceTransform* prefix_extractor) {
+ return rep_->LoadTableHandlers(internal_stats, max_threads,
+ prefetch_index_and_filter_in_cache,
+ is_initial_load, prefix_extractor);
+}
+
+void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level,
+ FileMetaData* f) {
+ rep_->MaybeAddFile(vstorage, level, f);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_builder.h b/storage/rocksdb/rocksdb/db/version_builder.h
new file mode 100644
index 00000000..87415ed5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_builder.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/file_system.h"
+#include "rocksdb/slice_transform.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TableCache;
+class VersionStorageInfo;
+class VersionEdit;
+struct FileMetaData;
+class InternalStats;
+
+// A helper class so we can efficiently apply a whole sequence
+// of edits to a particular state without creating intermediate
+// Versions that contain full copies of the intermediate state.
+class VersionBuilder {
+ public:
+ VersionBuilder(const FileOptions& file_options, TableCache* table_cache,
+ VersionStorageInfo* base_vstorage, Logger* info_log = nullptr);
+ ~VersionBuilder();
+ Status CheckConsistency(VersionStorageInfo* vstorage);
+ Status CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number,
+ int level);
+ bool CheckConsistencyForNumLevels();
+ Status Apply(VersionEdit* edit);
+ Status SaveTo(VersionStorageInfo* vstorage);
+ Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
+ bool prefetch_index_and_filter_in_cache,
+ bool is_initial_load,
+ const SliceTransform* prefix_extractor);
+ void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f);
+
+ private:
+ class Rep;
+ Rep* rep_;
+};
+
+extern bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_builder_test.cc b/storage/rocksdb/rocksdb/db/version_builder_test.cc
new file mode 100644
index 00000000..2dda03f3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_builder_test.cc
@@ -0,0 +1,349 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "db/version_edit.h"
+#include "db/version_set.h"
+#include "logging/logging.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_;
+ ImmutableCFOptions ioptions_;
+ MutableCFOptions mutable_cf_options_;
+ VersionStorageInfo vstorage_;
+ uint32_t file_num_;
+ CompactionOptionsFIFO fifo_options_;
+ std::vector<uint64_t> 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, uint32_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) {
+ 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, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ 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 UpdateVersionStorageInfo() {
+ vstorage_.UpdateFilesByCompactionPri(ioptions_.compaction_pri);
+ vstorage_.UpdateNumNonEmptyLevels();
+ vstorage_.GenerateFileIndexer();
+ vstorage_.GenerateLevelFilesBrief();
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ vstorage_.GenerateLevel0NonOverlapping();
+ vstorage_.SetFinalized();
+ }
+};
+
+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);
+
+ 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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.DeleteFile(3, 27U);
+
+ EnvOptions env_options;
+
+ VersionBuilder version_builder(env_options, nullptr, &vstorage_);
+
+ VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
+ kCompactionStyleLevel, nullptr, false);
+ version_builder.Apply(&version_edit);
+ version_builder.SaveTo(&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);
+ Add(0, 88U, "201", "300", 100U, 0, 100U, 100U, 0, 0, false, 100U, 100U);
+
+ 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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.DeleteFile(0, 1U);
+ version_edit.DeleteFile(0, 88U);
+
+ EnvOptions env_options;
+
+ VersionBuilder version_builder(env_options, nullptr, &vstorage_);
+
+ VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
+ kCompactionStyleLevel, nullptr, false);
+ version_builder.Apply(&version_edit);
+ version_builder.SaveTo(&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);
+ Add(0, 88U, "201", "300", 100U, 0, 100U, 100U, 0, 0, false, 100U, 100U);
+
+ 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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ 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;
+
+ VersionBuilder version_builder(env_options, nullptr, &vstorage_);
+
+ VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
+ kCompactionStyleLevel, nullptr, false);
+ version_builder.Apply(&version_edit);
+ version_builder.SaveTo(&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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
+ GetInternalKey("450"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
+ GetInternalKey("650"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
+ GetInternalKey("550"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
+ GetInternalKey("750"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+
+ EnvOptions env_options;
+
+ VersionBuilder version_builder(env_options, nullptr, &vstorage_);
+
+ VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
+ kCompactionStyleLevel, nullptr, false);
+ version_builder.Apply(&version_edit);
+ version_builder.SaveTo(&new_vstorage);
+
+ ASSERT_EQ(500U, new_vstorage.NumLevelBytes(2));
+
+ UnrefFilesInVersion(&new_vstorage);
+}
+
+TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
+ UpdateVersionStorageInfo();
+
+ EnvOptions env_options;
+ VersionBuilder version_builder(env_options, nullptr, &vstorage_);
+ 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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
+ GetInternalKey("450"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
+ GetInternalKey("650"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
+ GetInternalKey("550"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
+ GetInternalKey("750"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_builder.Apply(&version_edit);
+
+ VersionEdit version_edit2;
+ version_edit.AddFile(2, 808, 0, 100U, GetInternalKey("901"),
+ GetInternalKey("950"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_edit2.DeleteFile(2, 616);
+ version_edit2.DeleteFile(2, 636);
+ version_edit.AddFile(2, 806, 0, 100U, GetInternalKey("801"),
+ GetInternalKey("850"), 200, 200, false,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ version_builder.Apply(&version_edit2);
+
+ version_builder.SaveTo(&new_vstorage);
+
+ ASSERT_EQ(300U, new_vstorage.NumLevelBytes(2));
+
+ UnrefFilesInVersion(&new_vstorage);
+}
+
+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<int>(i / kFilesPerLevel), i + 1,
+ ToString((i + 100) * 1000).c_str(),
+ ToString((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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/version_edit.cc b/storage/rocksdb/rocksdb/db/version_edit.cc
new file mode 100644
index 00000000..5d171442
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_edit.cc
@@ -0,0 +1,802 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_index.h"
+#include "db/version_set.h"
+#include "logging/event_logger.h"
+#include "rocksdb/slice.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+// The unknown file checksum.
+const std::string kUnknownFileChecksum("");
+// The unknown sst file checksum function name.
+const std::string kUnknownFileChecksumFuncName("Unknown");
+// Mask for an identified tag from the future which can be safely ignored.
+const uint32_t kTagSafeIgnoreMask = 1 << 13;
+
+// Tag numbers for serialized VersionEdit. These numbers are written to
+// disk and should not be changed. The number should be forward compatible so
+// users can down-grade RocksDB safely. A future Tag is ignored by doing '&'
+// between Tag and kTagSafeIgnoreMask field.
+enum Tag : uint32_t {
+ kComparator = 1,
+ kLogNumber = 2,
+ kNextFileNumber = 3,
+ kLastSequence = 4,
+ kCompactPointer = 5,
+ kDeletedFile = 6,
+ kNewFile = 7,
+ // 8 was used for large value refs
+ kPrevLogNumber = 9,
+ kMinLogNumberToKeep = 10,
+ // Ignore-able field
+ kDbId = kTagSafeIgnoreMask + 1,
+
+ // these are new formats divergent from open source leveldb
+ kNewFile2 = 100,
+ kNewFile3 = 102,
+ kNewFile4 = 103, // 4th (the latest) format version of adding files
+ kColumnFamily = 200, // specify column family for version edit
+ kColumnFamilyAdd = 201,
+ kColumnFamilyDrop = 202,
+ kMaxColumnFamily = 203,
+
+ kInAtomicGroup = 300,
+};
+
+enum CustomTag : uint32_t {
+ kTerminate = 1, // The end of customized fields
+ kNeedCompaction = 2,
+ // Since Manifest is not entirely currently forward-compatible, and the only
+ // forward-compatible part is the CutsomtTag of kNewFile, we currently encode
+ // kMinLogNumberToKeep as part of a CustomTag as a hack. This should be
+ // removed when manifest becomes forward-comptabile.
+ kMinLogNumberToKeepHack = 3,
+ kOldestBlobFileNumber = 4,
+ kOldestAncesterTime = 5,
+ kFileCreationTime = 6,
+ kFileChecksum = 7,
+ kFileChecksumFuncName = 8,
+ kPathId = 65,
+};
+// If this bit for the custom tag is set, opening DB should fail if
+// we don't know this field.
+uint32_t kCustomTagNonSafeIgnoreMask = 1 << 6;
+
+uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id) {
+ assert(number <= kFileNumberMask);
+ return number | (path_id * (kFileNumberMask + 1));
+}
+
+void FileMetaData::UpdateBoundaries(const Slice& key, const Slice& value,
+ SequenceNumber seqno,
+ ValueType value_type) {
+ if (smallest.size() == 0) {
+ smallest.DecodeFrom(key);
+ }
+ largest.DecodeFrom(key);
+ fd.smallest_seqno = std::min(fd.smallest_seqno, seqno);
+ fd.largest_seqno = std::max(fd.largest_seqno, seqno);
+
+#ifndef ROCKSDB_LITE
+ if (value_type == kTypeBlobIndex) {
+ BlobIndex blob_index;
+ const Status s = blob_index.DecodeFrom(value);
+ if (!s.ok()) {
+ return;
+ }
+
+ if (blob_index.IsInlined()) {
+ return;
+ }
+
+ if (blob_index.HasTTL()) {
+ return;
+ }
+
+ // Paranoid check: this should not happen because BlobDB numbers the blob
+ // files starting from 1.
+ if (blob_index.file_number() == kInvalidBlobFileNumber) {
+ return;
+ }
+
+ if (oldest_blob_file_number == kInvalidBlobFileNumber ||
+ oldest_blob_file_number > blob_index.file_number()) {
+ oldest_blob_file_number = blob_index.file_number();
+ }
+ }
+#else
+ (void)value;
+ (void)value_type;
+#endif
+}
+
+void VersionEdit::Clear() {
+ max_level_ = 0;
+ db_id_.clear();
+ comparator_.clear();
+ log_number_ = 0;
+ prev_log_number_ = 0;
+ next_file_number_ = 0;
+ max_column_family_ = 0;
+ min_log_number_to_keep_ = 0;
+ last_sequence_ = 0;
+ has_db_id_ = false;
+ has_comparator_ = false;
+ has_log_number_ = false;
+ has_prev_log_number_ = false;
+ has_next_file_number_ = false;
+ has_max_column_family_ = false;
+ has_min_log_number_to_keep_ = false;
+ has_last_sequence_ = false;
+ deleted_files_.clear();
+ new_files_.clear();
+ column_family_ = 0;
+ is_column_family_add_ = false;
+ is_column_family_drop_ = false;
+ column_family_name_.clear();
+ is_in_atomic_group_ = false;
+ remaining_entries_ = 0;
+}
+
+bool VersionEdit::EncodeTo(std::string* dst) const {
+ if (has_db_id_) {
+ PutVarint32(dst, kDbId);
+ PutLengthPrefixedSlice(dst, db_id_);
+ }
+ if (has_comparator_) {
+ PutVarint32(dst, kComparator);
+ PutLengthPrefixedSlice(dst, comparator_);
+ }
+ if (has_log_number_) {
+ PutVarint32Varint64(dst, kLogNumber, log_number_);
+ }
+ if (has_prev_log_number_) {
+ PutVarint32Varint64(dst, kPrevLogNumber, prev_log_number_);
+ }
+ if (has_next_file_number_) {
+ PutVarint32Varint64(dst, kNextFileNumber, next_file_number_);
+ }
+ if (has_max_column_family_) {
+ PutVarint32Varint32(dst, kMaxColumnFamily, max_column_family_);
+ }
+ if (has_last_sequence_) {
+ PutVarint32Varint64(dst, kLastSequence, last_sequence_);
+ }
+ for (const auto& deleted : deleted_files_) {
+ PutVarint32Varint32Varint64(dst, kDeletedFile, deleted.first /* level */,
+ deleted.second /* file number */);
+ }
+
+ bool min_log_num_written = false;
+ for (size_t i = 0; i < new_files_.size(); i++) {
+ const FileMetaData& f = new_files_[i].second;
+ if (!f.smallest.Valid() || !f.largest.Valid()) {
+ return false;
+ }
+ PutVarint32(dst, kNewFile4);
+ PutVarint32Varint64(dst, new_files_[i].first /* level */, f.fd.GetNumber());
+ PutVarint64(dst, f.fd.GetFileSize());
+ PutLengthPrefixedSlice(dst, f.smallest.Encode());
+ PutLengthPrefixedSlice(dst, f.largest.Encode());
+ PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
+ // Customized fields' format:
+ // +-----------------------------+
+ // | 1st field's tag (varint32) |
+ // +-----------------------------+
+ // | 1st field's size (varint32) |
+ // +-----------------------------+
+ // | bytes for 1st field |
+ // | (based on size decoded) |
+ // +-----------------------------+
+ // | |
+ // | ...... |
+ // | |
+ // +-----------------------------+
+ // | last field's size (varint32)|
+ // +-----------------------------+
+ // | bytes for last field |
+ // | (based on size decoded) |
+ // +-----------------------------+
+ // | terminating tag (varint32) |
+ // +-----------------------------+
+ //
+ // Customized encoding for fields:
+ // tag kPathId: 1 byte as path_id
+ // tag kNeedCompaction:
+ // now only can take one char value 1 indicating need-compaction
+ //
+ PutVarint32(dst, CustomTag::kOldestAncesterTime);
+ std::string varint_oldest_ancester_time;
+ PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
+ TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
+ &varint_oldest_ancester_time);
+ PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
+
+ PutVarint32(dst, CustomTag::kFileCreationTime);
+ std::string varint_file_creation_time;
+ PutVarint64(&varint_file_creation_time, f.file_creation_time);
+ TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
+ &varint_file_creation_time);
+ PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
+
+ PutVarint32(dst, CustomTag::kFileChecksum);
+ PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
+
+ PutVarint32(dst, CustomTag::kFileChecksumFuncName);
+ PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
+
+ if (f.fd.GetPathId() != 0) {
+ PutVarint32(dst, CustomTag::kPathId);
+ char p = static_cast<char>(f.fd.GetPathId());
+ PutLengthPrefixedSlice(dst, Slice(&p, 1));
+ }
+ if (f.marked_for_compaction) {
+ PutVarint32(dst, CustomTag::kNeedCompaction);
+ char p = static_cast<char>(1);
+ PutLengthPrefixedSlice(dst, Slice(&p, 1));
+ }
+ if (has_min_log_number_to_keep_ && !min_log_num_written) {
+ PutVarint32(dst, CustomTag::kMinLogNumberToKeepHack);
+ std::string varint_log_number;
+ PutFixed64(&varint_log_number, min_log_number_to_keep_);
+ PutLengthPrefixedSlice(dst, Slice(varint_log_number));
+ min_log_num_written = true;
+ }
+ if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ PutVarint32(dst, CustomTag::kOldestBlobFileNumber);
+ std::string oldest_blob_file_number;
+ PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
+ PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
+ }
+ TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
+ dst);
+
+ PutVarint32(dst, CustomTag::kTerminate);
+ }
+
+ // 0 is default and does not need to be explicitly written
+ if (column_family_ != 0) {
+ PutVarint32Varint32(dst, kColumnFamily, column_family_);
+ }
+
+ if (is_column_family_add_) {
+ PutVarint32(dst, kColumnFamilyAdd);
+ PutLengthPrefixedSlice(dst, Slice(column_family_name_));
+ }
+
+ if (is_column_family_drop_) {
+ PutVarint32(dst, kColumnFamilyDrop);
+ }
+
+ if (is_in_atomic_group_) {
+ PutVarint32(dst, kInAtomicGroup);
+ PutVarint32(dst, remaining_entries_);
+ }
+ return true;
+}
+
+static bool GetInternalKey(Slice* input, InternalKey* dst) {
+ Slice str;
+ if (GetLengthPrefixedSlice(input, &str)) {
+ dst->DecodeFrom(str);
+ return dst->Valid();
+ } else {
+ return false;
+ }
+}
+
+bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) {
+ uint32_t v = 0;
+ if (GetVarint32(input, &v)) {
+ *level = v;
+ if (max_level_ < *level) {
+ max_level_ = *level;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+const char* VersionEdit::DecodeNewFile4From(Slice* input) {
+ const char* msg = nullptr;
+ int level = 0;
+ FileMetaData f;
+ uint64_t number = 0;
+ uint32_t path_id = 0;
+ uint64_t file_size = 0;
+ SequenceNumber smallest_seqno = 0;
+ SequenceNumber largest_seqno = kMaxSequenceNumber;
+ // Since this is the only forward-compatible part of the code, we hack new
+ // extension into this record. When we do, we set this boolean to distinguish
+ // the record from the normal NewFile records.
+ if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) &&
+ GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
+ GetInternalKey(input, &f.largest) &&
+ GetVarint64(input, &smallest_seqno) &&
+ GetVarint64(input, &largest_seqno)) {
+ // See comments in VersionEdit::EncodeTo() for format of customized fields
+ while (true) {
+ uint32_t custom_tag = 0;
+ Slice field;
+ if (!GetVarint32(input, &custom_tag)) {
+ return "new-file4 custom field";
+ }
+ if (custom_tag == kTerminate) {
+ break;
+ }
+ if (!GetLengthPrefixedSlice(input, &field)) {
+ return "new-file4 custom field length prefixed slice error";
+ }
+ switch (custom_tag) {
+ case kPathId:
+ if (field.size() != 1) {
+ return "path_id field wrong size";
+ }
+ path_id = field[0];
+ if (path_id > 3) {
+ return "path_id wrong vaue";
+ }
+ break;
+ case kOldestAncesterTime:
+ if (!GetVarint64(&field, &f.oldest_ancester_time)) {
+ return "invalid oldest ancester time";
+ }
+ break;
+ case kFileCreationTime:
+ if (!GetVarint64(&field, &f.file_creation_time)) {
+ return "invalid file creation time";
+ }
+ break;
+ case kFileChecksum:
+ f.file_checksum = field.ToString();
+ break;
+ case kFileChecksumFuncName:
+ f.file_checksum_func_name = field.ToString();
+ break;
+ case kNeedCompaction:
+ if (field.size() != 1) {
+ return "need_compaction field wrong size";
+ }
+ f.marked_for_compaction = (field[0] == 1);
+ break;
+ case kMinLogNumberToKeepHack:
+ // This is a hack to encode kMinLogNumberToKeep in a
+ // forward-compatible fashion.
+ if (!GetFixed64(&field, &min_log_number_to_keep_)) {
+ return "deleted log number malformatted";
+ }
+ has_min_log_number_to_keep_ = true;
+ break;
+ case kOldestBlobFileNumber:
+ if (!GetVarint64(&field, &f.oldest_blob_file_number)) {
+ return "invalid oldest blob file number";
+ }
+ break;
+ default:
+ if ((custom_tag & kCustomTagNonSafeIgnoreMask) != 0) {
+ // Should not proceed if cannot understand it
+ return "new-file4 custom field not supported";
+ }
+ break;
+ }
+ }
+ } else {
+ return "new-file4 entry";
+ }
+ f.fd =
+ FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno);
+ new_files_.push_back(std::make_pair(level, f));
+ return nullptr;
+}
+
+Status VersionEdit::DecodeFrom(const Slice& src) {
+ Clear();
+ Slice input = src;
+ const char* msg = nullptr;
+ uint32_t tag = 0;
+
+ // Temporary storage for parsing
+ int level = 0;
+ FileMetaData f;
+ Slice str;
+ InternalKey key;
+ while (msg == nullptr && GetVarint32(&input, &tag)) {
+ switch (tag) {
+ case kDbId:
+ if (GetLengthPrefixedSlice(&input, &str)) {
+ db_id_ = str.ToString();
+ has_db_id_ = true;
+ } else {
+ msg = "db id";
+ }
+ break;
+ case kComparator:
+ if (GetLengthPrefixedSlice(&input, &str)) {
+ comparator_ = str.ToString();
+ has_comparator_ = true;
+ } else {
+ msg = "comparator name";
+ }
+ break;
+
+ case kLogNumber:
+ if (GetVarint64(&input, &log_number_)) {
+ has_log_number_ = true;
+ } else {
+ msg = "log number";
+ }
+ break;
+
+ case kPrevLogNumber:
+ if (GetVarint64(&input, &prev_log_number_)) {
+ has_prev_log_number_ = true;
+ } else {
+ msg = "previous log number";
+ }
+ break;
+
+ case kNextFileNumber:
+ if (GetVarint64(&input, &next_file_number_)) {
+ has_next_file_number_ = true;
+ } else {
+ msg = "next file number";
+ }
+ break;
+
+ case kMaxColumnFamily:
+ if (GetVarint32(&input, &max_column_family_)) {
+ has_max_column_family_ = true;
+ } else {
+ msg = "max column family";
+ }
+ break;
+
+ case kMinLogNumberToKeep:
+ if (GetVarint64(&input, &min_log_number_to_keep_)) {
+ has_min_log_number_to_keep_ = true;
+ } else {
+ msg = "min log number to kee";
+ }
+ break;
+
+ case kLastSequence:
+ if (GetVarint64(&input, &last_sequence_)) {
+ has_last_sequence_ = true;
+ } else {
+ msg = "last sequence number";
+ }
+ break;
+
+ case kCompactPointer:
+ if (GetLevel(&input, &level, &msg) &&
+ GetInternalKey(&input, &key)) {
+ // we don't use compact pointers anymore,
+ // but we should not fail if they are still
+ // in manifest
+ } else {
+ if (!msg) {
+ msg = "compaction pointer";
+ }
+ }
+ break;
+
+ case kDeletedFile: {
+ uint64_t number = 0;
+ if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number)) {
+ deleted_files_.insert(std::make_pair(level, number));
+ } else {
+ if (!msg) {
+ msg = "deleted file";
+ }
+ }
+ break;
+ }
+
+ case kNewFile: {
+ uint64_t number = 0;
+ uint64_t file_size = 0;
+ if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
+ GetVarint64(&input, &file_size) &&
+ GetInternalKey(&input, &f.smallest) &&
+ GetInternalKey(&input, &f.largest)) {
+ f.fd = FileDescriptor(number, 0, file_size);
+ new_files_.push_back(std::make_pair(level, f));
+ } else {
+ if (!msg) {
+ msg = "new-file entry";
+ }
+ }
+ break;
+ }
+ case kNewFile2: {
+ uint64_t number = 0;
+ uint64_t file_size = 0;
+ SequenceNumber smallest_seqno = 0;
+ SequenceNumber largest_seqno = kMaxSequenceNumber;
+ if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
+ GetVarint64(&input, &file_size) &&
+ GetInternalKey(&input, &f.smallest) &&
+ GetInternalKey(&input, &f.largest) &&
+ GetVarint64(&input, &smallest_seqno) &&
+ GetVarint64(&input, &largest_seqno)) {
+ f.fd = FileDescriptor(number, 0, file_size, smallest_seqno,
+ largest_seqno);
+ new_files_.push_back(std::make_pair(level, f));
+ } else {
+ if (!msg) {
+ msg = "new-file2 entry";
+ }
+ }
+ break;
+ }
+
+ case kNewFile3: {
+ uint64_t number = 0;
+ uint32_t path_id = 0;
+ uint64_t file_size = 0;
+ SequenceNumber smallest_seqno = 0;
+ SequenceNumber largest_seqno = kMaxSequenceNumber;
+ if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
+ GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) &&
+ GetInternalKey(&input, &f.smallest) &&
+ GetInternalKey(&input, &f.largest) &&
+ GetVarint64(&input, &smallest_seqno) &&
+ GetVarint64(&input, &largest_seqno)) {
+ f.fd = FileDescriptor(number, path_id, file_size, smallest_seqno,
+ largest_seqno);
+ new_files_.push_back(std::make_pair(level, f));
+ } else {
+ if (!msg) {
+ msg = "new-file3 entry";
+ }
+ }
+ break;
+ }
+
+ case kNewFile4: {
+ msg = DecodeNewFile4From(&input);
+ break;
+ }
+
+ case kColumnFamily:
+ if (!GetVarint32(&input, &column_family_)) {
+ if (!msg) {
+ msg = "set column family id";
+ }
+ }
+ break;
+
+ case kColumnFamilyAdd:
+ if (GetLengthPrefixedSlice(&input, &str)) {
+ is_column_family_add_ = true;
+ column_family_name_ = str.ToString();
+ } else {
+ if (!msg) {
+ msg = "column family add";
+ }
+ }
+ break;
+
+ case kColumnFamilyDrop:
+ is_column_family_drop_ = true;
+ break;
+
+ case kInAtomicGroup:
+ is_in_atomic_group_ = true;
+ if (!GetVarint32(&input, &remaining_entries_)) {
+ if (!msg) {
+ msg = "remaining entries";
+ }
+ }
+ break;
+
+ default:
+ if (tag & kTagSafeIgnoreMask) {
+ // Tag from future which can be safely ignored.
+ // The next field must be the length of the entry.
+ uint32_t field_len;
+ if (!GetVarint32(&input, &field_len) ||
+ static_cast<size_t>(field_len) > input.size()) {
+ if (!msg) {
+ msg = "safely ignoreable tag length error";
+ }
+ } else {
+ input.remove_prefix(static_cast<size_t>(field_len));
+ }
+ } else {
+ msg = "unknown tag";
+ }
+ break;
+ }
+ }
+
+ if (msg == nullptr && !input.empty()) {
+ msg = "invalid tag";
+ }
+
+ Status result;
+ if (msg != nullptr) {
+ result = Status::Corruption("VersionEdit", msg);
+ }
+ return result;
+}
+
+std::string VersionEdit::DebugString(bool hex_key) const {
+ std::string r;
+ r.append("VersionEdit {");
+ if (has_db_id_) {
+ r.append("\n DB ID: ");
+ r.append(db_id_);
+ }
+ if (has_comparator_) {
+ r.append("\n Comparator: ");
+ r.append(comparator_);
+ }
+ if (has_log_number_) {
+ r.append("\n LogNumber: ");
+ AppendNumberTo(&r, log_number_);
+ }
+ if (has_prev_log_number_) {
+ r.append("\n PrevLogNumber: ");
+ AppendNumberTo(&r, prev_log_number_);
+ }
+ if (has_next_file_number_) {
+ r.append("\n NextFileNumber: ");
+ AppendNumberTo(&r, next_file_number_);
+ }
+ if (has_max_column_family_) {
+ r.append("\n MaxColumnFamily: ");
+ AppendNumberTo(&r, max_column_family_);
+ }
+ if (has_min_log_number_to_keep_) {
+ r.append("\n MinLogNumberToKeep: ");
+ AppendNumberTo(&r, min_log_number_to_keep_);
+ }
+ if (has_last_sequence_) {
+ r.append("\n LastSeq: ");
+ AppendNumberTo(&r, last_sequence_);
+ }
+ for (const auto& deleted_file : deleted_files_) {
+ r.append("\n DeleteFile: ");
+ AppendNumberTo(&r, deleted_file.first);
+ r.append(" ");
+ AppendNumberTo(&r, deleted_file.second);
+ }
+ for (size_t i = 0; i < new_files_.size(); i++) {
+ const FileMetaData& f = new_files_[i].second;
+ r.append("\n AddFile: ");
+ AppendNumberTo(&r, new_files_[i].first);
+ r.append(" ");
+ AppendNumberTo(&r, f.fd.GetNumber());
+ r.append(" ");
+ AppendNumberTo(&r, f.fd.GetFileSize());
+ r.append(" ");
+ r.append(f.smallest.DebugString(hex_key));
+ r.append(" .. ");
+ r.append(f.largest.DebugString(hex_key));
+ if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ r.append(" blob_file:");
+ AppendNumberTo(&r, f.oldest_blob_file_number);
+ }
+ r.append(" oldest_ancester_time:");
+ AppendNumberTo(&r, f.oldest_ancester_time);
+ r.append(" file_creation_time:");
+ AppendNumberTo(&r, f.file_creation_time);
+ r.append(" file_checksum:");
+ r.append(f.file_checksum);
+ r.append(" file_checksum_func_name: ");
+ r.append(f.file_checksum_func_name);
+ }
+ r.append("\n ColumnFamily: ");
+ AppendNumberTo(&r, column_family_);
+ if (is_column_family_add_) {
+ r.append("\n ColumnFamilyAdd: ");
+ r.append(column_family_name_);
+ }
+ if (is_column_family_drop_) {
+ r.append("\n ColumnFamilyDrop");
+ }
+ if (is_in_atomic_group_) {
+ r.append("\n AtomicGroup: ");
+ AppendNumberTo(&r, remaining_entries_);
+ r.append(" entries remains");
+ }
+ r.append("\n}\n");
+ return r;
+}
+
+std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
+ JSONWriter jw;
+ jw << "EditNumber" << edit_num;
+
+ if (has_db_id_) {
+ jw << "DB ID" << db_id_;
+ }
+ if (has_comparator_) {
+ jw << "Comparator" << comparator_;
+ }
+ if (has_log_number_) {
+ jw << "LogNumber" << log_number_;
+ }
+ if (has_prev_log_number_) {
+ jw << "PrevLogNumber" << prev_log_number_;
+ }
+ if (has_next_file_number_) {
+ jw << "NextFileNumber" << next_file_number_;
+ }
+ if (has_max_column_family_) {
+ jw << "MaxColumnFamily" << max_column_family_;
+ }
+ if (has_min_log_number_to_keep_) {
+ jw << "MinLogNumberToKeep" << min_log_number_to_keep_;
+ }
+ if (has_last_sequence_) {
+ jw << "LastSeq" << last_sequence_;
+ }
+
+ if (!deleted_files_.empty()) {
+ jw << "DeletedFiles";
+ jw.StartArray();
+
+ for (const auto& deleted_file : deleted_files_) {
+ jw.StartArrayedObject();
+ jw << "Level" << deleted_file.first;
+ jw << "FileNumber" << deleted_file.second;
+ jw.EndArrayedObject();
+ }
+
+ jw.EndArray();
+ }
+
+ if (!new_files_.empty()) {
+ jw << "AddedFiles";
+ jw.StartArray();
+
+ for (size_t i = 0; i < new_files_.size(); i++) {
+ jw.StartArrayedObject();
+ jw << "Level" << new_files_[i].first;
+ const FileMetaData& f = new_files_[i].second;
+ jw << "FileNumber" << f.fd.GetNumber();
+ jw << "FileSize" << f.fd.GetFileSize();
+ jw << "SmallestIKey" << f.smallest.DebugString(hex_key);
+ jw << "LargestIKey" << f.largest.DebugString(hex_key);
+ if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ jw << "OldestBlobFile" << f.oldest_blob_file_number;
+ }
+ jw.EndArrayedObject();
+ }
+
+ jw.EndArray();
+ }
+
+ jw << "ColumnFamily" << column_family_;
+
+ if (is_column_family_add_) {
+ jw << "ColumnFamilyAdd" << column_family_name_;
+ }
+ if (is_column_family_drop_) {
+ jw << "ColumnFamilyDrop" << column_family_name_;
+ }
+ if (is_in_atomic_group_) {
+ jw << "AtomicGroup" << remaining_entries_;
+ }
+
+ jw.EndObject();
+
+ return jw.Get();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_edit.h b/storage/rocksdb/rocksdb/db/version_edit.h
new file mode 100644
index 00000000..6d1893f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_edit.h
@@ -0,0 +1,438 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+#include "db/dbformat.h"
+#include "memory/arena.h"
+#include "rocksdb/cache.h"
+#include "table/table_reader.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class VersionSet;
+
+constexpr uint64_t kFileNumberMask = 0x3FFFFFFFFFFFFFFF;
+constexpr uint64_t kInvalidBlobFileNumber = 0;
+constexpr uint64_t kUnknownOldestAncesterTime = 0;
+constexpr uint64_t kUnknownFileCreationTime = 0;
+
+extern const std::string kUnknownFileChecksum;
+extern const std::string kUnknownFileChecksumFuncName;
+
+extern uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id);
+
+// A copyable structure contains information needed to read data from an SST
+// file. It can contain a pointer to a table reader opened for the file, or
+// file number and size, which can be used to create a new table reader for it.
+// The behavior is undefined when a copied of the structure is used when the
+// file is not in any live version any more.
+struct FileDescriptor {
+ // Table reader in table_reader_handle
+ TableReader* table_reader;
+ uint64_t packed_number_and_path_id;
+ uint64_t file_size; // File size in bytes
+ SequenceNumber smallest_seqno; // The smallest seqno in this file
+ SequenceNumber largest_seqno; // The largest seqno in this file
+
+ FileDescriptor() : FileDescriptor(0, 0, 0) {}
+
+ FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size)
+ : FileDescriptor(number, path_id, _file_size, kMaxSequenceNumber, 0) {}
+
+ FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size,
+ SequenceNumber _smallest_seqno, SequenceNumber _largest_seqno)
+ : table_reader(nullptr),
+ packed_number_and_path_id(PackFileNumberAndPathId(number, path_id)),
+ file_size(_file_size),
+ smallest_seqno(_smallest_seqno),
+ largest_seqno(_largest_seqno) {}
+
+ FileDescriptor(const FileDescriptor& fd) { *this = fd; }
+
+ FileDescriptor& operator=(const FileDescriptor& fd) {
+ table_reader = fd.table_reader;
+ packed_number_and_path_id = fd.packed_number_and_path_id;
+ file_size = fd.file_size;
+ smallest_seqno = fd.smallest_seqno;
+ largest_seqno = fd.largest_seqno;
+ return *this;
+ }
+
+ uint64_t GetNumber() const {
+ return packed_number_and_path_id & kFileNumberMask;
+ }
+ uint32_t GetPathId() const {
+ return static_cast<uint32_t>(
+ packed_number_and_path_id / (kFileNumberMask + 1));
+ }
+ uint64_t GetFileSize() const { return file_size; }
+};
+
+struct FileSampledStats {
+ FileSampledStats() : num_reads_sampled(0) {}
+ FileSampledStats(const FileSampledStats& other) { *this = other; }
+ FileSampledStats& operator=(const FileSampledStats& other) {
+ num_reads_sampled = other.num_reads_sampled.load();
+ return *this;
+ }
+
+ // number of user reads to this file.
+ mutable std::atomic<uint64_t> num_reads_sampled;
+};
+
+struct FileMetaData {
+ FileDescriptor fd;
+ InternalKey smallest; // Smallest internal key served by table
+ InternalKey largest; // Largest internal key served by table
+
+ // Needs to be disposed when refs becomes 0.
+ Cache::Handle* table_reader_handle = nullptr;
+
+ FileSampledStats stats;
+
+ // Stats for compensating deletion entries during compaction
+
+ // File size compensated by deletion entry.
+ // This is updated in Version::UpdateAccumulatedStats() first time when the
+ // file is created or loaded. After it is updated (!= 0), it is immutable.
+ uint64_t compensated_file_size = 0;
+ // These values can mutate, but they can only be read or written from
+ // single-threaded LogAndApply thread
+ uint64_t num_entries = 0; // the number of entries.
+ uint64_t num_deletions = 0; // the number of deletion entries.
+ uint64_t raw_key_size = 0; // total uncompressed key size.
+ uint64_t raw_value_size = 0; // total uncompressed value size.
+
+ int refs = 0; // Reference count
+
+ bool being_compacted = false; // Is this file undergoing compaction?
+ bool init_stats_from_file = false; // true if the data-entry stats of this
+ // file has initialized from file.
+
+ bool marked_for_compaction = false; // True if client asked us nicely to
+ // compact this file.
+
+ // Used only in BlobDB. The file number of the oldest blob file this SST file
+ // refers to. 0 is an invalid value; BlobDB numbers the files starting from 1.
+ uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
+
+ // The file could be the compaction output from other SST files, which could
+ // in turn be outputs for compact older SST files. We track the memtable
+ // flush timestamp for the oldest SST file that eventaully contribute data
+ // to this file. 0 means the information is not available.
+ uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
+
+ // Unix time when the SST file is created.
+ uint64_t file_creation_time = kUnknownFileCreationTime;
+
+ // File checksum
+ std::string file_checksum = kUnknownFileChecksum;
+
+ // File checksum function name
+ std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
+
+ FileMetaData() = default;
+
+ FileMetaData(uint64_t file, uint32_t file_path_id, uint64_t file_size,
+ const InternalKey& smallest_key, const InternalKey& largest_key,
+ const SequenceNumber& smallest_seq,
+ const SequenceNumber& largest_seq, bool marked_for_compact,
+ uint64_t oldest_blob_file, uint64_t _oldest_ancester_time,
+ uint64_t _file_creation_time, const std::string& _file_checksum,
+ const std::string& _file_checksum_func_name)
+ : fd(file, file_path_id, file_size, smallest_seq, largest_seq),
+ smallest(smallest_key),
+ largest(largest_key),
+ marked_for_compaction(marked_for_compact),
+ oldest_blob_file_number(oldest_blob_file),
+ oldest_ancester_time(_oldest_ancester_time),
+ file_creation_time(_file_creation_time),
+ file_checksum(_file_checksum),
+ file_checksum_func_name(_file_checksum_func_name) {
+ TEST_SYNC_POINT_CALLBACK("FileMetaData::FileMetaData", this);
+ }
+
+ // REQUIRED: Keys must be given to the function in sorted order (it expects
+ // the last key to be the largest).
+ void UpdateBoundaries(const Slice& key, const Slice& value,
+ SequenceNumber seqno, ValueType value_type);
+
+ // Unlike UpdateBoundaries, ranges do not need to be presented in any
+ // particular order.
+ void UpdateBoundariesForRange(const InternalKey& start,
+ const InternalKey& end, SequenceNumber seqno,
+ const InternalKeyComparator& icmp) {
+ if (smallest.size() == 0 || icmp.Compare(start, smallest) < 0) {
+ smallest = start;
+ }
+ if (largest.size() == 0 || icmp.Compare(largest, end) < 0) {
+ largest = end;
+ }
+ fd.smallest_seqno = std::min(fd.smallest_seqno, seqno);
+ fd.largest_seqno = std::max(fd.largest_seqno, seqno);
+ }
+
+ // Try to get oldest ancester time from the class itself or table properties
+ // if table reader is already pinned.
+ // 0 means the information is not available.
+ uint64_t TryGetOldestAncesterTime() {
+ if (oldest_ancester_time != kUnknownOldestAncesterTime) {
+ return oldest_ancester_time;
+ } else if (fd.table_reader != nullptr &&
+ fd.table_reader->GetTableProperties() != nullptr) {
+ return fd.table_reader->GetTableProperties()->creation_time;
+ }
+ return kUnknownOldestAncesterTime;
+ }
+
+ uint64_t TryGetFileCreationTime() {
+ if (file_creation_time != kUnknownFileCreationTime) {
+ return file_creation_time;
+ } else if (fd.table_reader != nullptr &&
+ fd.table_reader->GetTableProperties() != nullptr) {
+ return fd.table_reader->GetTableProperties()->file_creation_time;
+ }
+ return kUnknownFileCreationTime;
+ }
+};
+
+// A compressed copy of file meta data that just contain minimum data needed
+// to server read operations, while still keeping the pointer to full metadata
+// of the file in case it is needed.
+struct FdWithKeyRange {
+ FileDescriptor fd;
+ FileMetaData* file_metadata; // Point to all metadata
+ Slice smallest_key; // slice that contain smallest key
+ Slice largest_key; // slice that contain largest key
+
+ FdWithKeyRange()
+ : fd(),
+ file_metadata(nullptr),
+ smallest_key(),
+ largest_key() {
+ }
+
+ FdWithKeyRange(FileDescriptor _fd, Slice _smallest_key, Slice _largest_key,
+ FileMetaData* _file_metadata)
+ : fd(_fd),
+ file_metadata(_file_metadata),
+ smallest_key(_smallest_key),
+ largest_key(_largest_key) {}
+};
+
+// Data structure to store an array of FdWithKeyRange in one level
+// Actual data is guaranteed to be stored closely
+struct LevelFilesBrief {
+ size_t num_files;
+ FdWithKeyRange* files;
+ LevelFilesBrief() {
+ num_files = 0;
+ files = nullptr;
+ }
+};
+
+// The state of a DB at any given time is referred to as a Version.
+// Any modification to the Version is considered a Version Edit. A Version is
+// constructed by joining a sequence of Version Edits. Version Edits are written
+// to the MANIFEST file.
+class VersionEdit {
+ public:
+ void Clear();
+
+ void SetDBId(const std::string& db_id) {
+ has_db_id_ = true;
+ db_id_ = db_id;
+ }
+ bool HasDbId() const { return has_db_id_; }
+ const std::string& GetDbId() const { return db_id_; }
+
+ void SetComparatorName(const Slice& name) {
+ has_comparator_ = true;
+ comparator_ = name.ToString();
+ }
+ bool HasComparatorName() const { return has_comparator_; }
+ const std::string& GetComparatorName() const { return comparator_; }
+
+ void SetLogNumber(uint64_t num) {
+ has_log_number_ = true;
+ log_number_ = num;
+ }
+ bool HasLogNumber() const { return has_log_number_; }
+ uint64_t GetLogNumber() const { return log_number_; }
+
+ void SetPrevLogNumber(uint64_t num) {
+ has_prev_log_number_ = true;
+ prev_log_number_ = num;
+ }
+ bool HasPrevLogNumber() const { return has_prev_log_number_; }
+ uint64_t GetPrevLogNumber() const { return prev_log_number_; }
+
+ void SetNextFile(uint64_t num) {
+ has_next_file_number_ = true;
+ next_file_number_ = num;
+ }
+ bool HasNextFile() const { return has_next_file_number_; }
+ uint64_t GetNextFile() const { return next_file_number_; }
+
+ void SetMaxColumnFamily(uint32_t max_column_family) {
+ has_max_column_family_ = true;
+ max_column_family_ = max_column_family;
+ }
+ bool HasMaxColumnFamily() const { return has_max_column_family_; }
+ uint32_t GetMaxColumnFamily() const { return max_column_family_; }
+
+ void SetMinLogNumberToKeep(uint64_t num) {
+ has_min_log_number_to_keep_ = true;
+ min_log_number_to_keep_ = num;
+ }
+ bool HasMinLogNumberToKeep() const { return has_min_log_number_to_keep_; }
+ uint64_t GetMinLogNumberToKeep() const { return min_log_number_to_keep_; }
+
+ void SetLastSequence(SequenceNumber seq) {
+ has_last_sequence_ = true;
+ last_sequence_ = seq;
+ }
+ bool HasLastSequence() const { return has_last_sequence_; }
+ SequenceNumber GetLastSequence() const { return last_sequence_; }
+
+ // Delete the specified "file" from the specified "level".
+ void DeleteFile(int level, uint64_t file) {
+ deleted_files_.emplace(level, file);
+ }
+
+ // Retrieve the files deleted as well as their associated levels.
+ using DeletedFiles = std::set<std::pair<int, uint64_t>>;
+ const DeletedFiles& GetDeletedFiles() const { return deleted_files_; }
+
+ // Add the specified file at the specified level.
+ // REQUIRES: This version has not been saved (see VersionSet::SaveTo)
+ // REQUIRES: "smallest" and "largest" are smallest and largest keys in file
+ // REQUIRES: "oldest_blob_file_number" is the number of the oldest blob file
+ // referred to by this file if any, kInvalidBlobFileNumber otherwise.
+ void AddFile(int level, uint64_t file, uint32_t file_path_id,
+ uint64_t file_size, const InternalKey& smallest,
+ const InternalKey& largest, const SequenceNumber& smallest_seqno,
+ const SequenceNumber& largest_seqno, bool marked_for_compaction,
+ uint64_t oldest_blob_file_number, uint64_t oldest_ancester_time,
+ uint64_t file_creation_time, const std::string& file_checksum,
+ const std::string& file_checksum_func_name) {
+ assert(smallest_seqno <= largest_seqno);
+ new_files_.emplace_back(
+ level, FileMetaData(file, file_path_id, file_size, smallest, largest,
+ smallest_seqno, largest_seqno,
+ marked_for_compaction, oldest_blob_file_number,
+ oldest_ancester_time, file_creation_time,
+ file_checksum, file_checksum_func_name));
+ }
+
+ void AddFile(int level, const FileMetaData& f) {
+ assert(f.fd.smallest_seqno <= f.fd.largest_seqno);
+ new_files_.emplace_back(level, f);
+ }
+
+ // Retrieve the files added as well as their associated levels.
+ using NewFiles = std::vector<std::pair<int, FileMetaData>>;
+ const NewFiles& GetNewFiles() const { return new_files_; }
+
+ // Number of edits
+ size_t NumEntries() const { return new_files_.size() + deleted_files_.size(); }
+
+ void SetColumnFamily(uint32_t column_family_id) {
+ column_family_ = column_family_id;
+ }
+ uint32_t GetColumnFamily() const { return column_family_; }
+
+ // set column family ID by calling SetColumnFamily()
+ void AddColumnFamily(const std::string& name) {
+ assert(!is_column_family_drop_);
+ assert(!is_column_family_add_);
+ assert(NumEntries() == 0);
+ is_column_family_add_ = true;
+ column_family_name_ = name;
+ }
+
+ // set column family ID by calling SetColumnFamily()
+ void DropColumnFamily() {
+ assert(!is_column_family_drop_);
+ assert(!is_column_family_add_);
+ assert(NumEntries() == 0);
+ is_column_family_drop_ = true;
+ }
+
+ bool IsColumnFamilyManipulation() const {
+ return is_column_family_add_ || is_column_family_drop_;
+ }
+
+ void MarkAtomicGroup(uint32_t remaining_entries) {
+ is_in_atomic_group_ = true;
+ remaining_entries_ = remaining_entries;
+ }
+ bool IsInAtomicGroup() const { return is_in_atomic_group_; }
+ uint32_t GetRemainingEntries() const { return remaining_entries_; }
+
+ // return true on success.
+ bool EncodeTo(std::string* dst) const;
+ Status DecodeFrom(const Slice& src);
+
+ std::string DebugString(bool hex_key = false) const;
+ std::string DebugJSON(int edit_num, bool hex_key = false) const;
+
+ private:
+ friend class ReactiveVersionSet;
+ friend class VersionSet;
+ friend class Version;
+ friend class AtomicGroupReadBuffer;
+
+ bool GetLevel(Slice* input, int* level, const char** msg);
+
+ const char* DecodeNewFile4From(Slice* input);
+
+ int max_level_ = 0;
+ std::string db_id_;
+ std::string comparator_;
+ uint64_t log_number_ = 0;
+ uint64_t prev_log_number_ = 0;
+ uint64_t next_file_number_ = 0;
+ uint32_t max_column_family_ = 0;
+ // The most recent WAL log number that is deleted
+ uint64_t min_log_number_to_keep_ = 0;
+ SequenceNumber last_sequence_ = 0;
+ bool has_db_id_ = false;
+ bool has_comparator_ = false;
+ bool has_log_number_ = false;
+ bool has_prev_log_number_ = false;
+ bool has_next_file_number_ = false;
+ bool has_max_column_family_ = false;
+ bool has_min_log_number_to_keep_ = false;
+ bool has_last_sequence_ = false;
+
+ DeletedFiles deleted_files_;
+ NewFiles new_files_;
+
+ // Each version edit record should have column_family_ set
+ // If it's not set, it is default (0)
+ uint32_t column_family_ = 0;
+ // a version edit can be either column_family add or
+ // column_family drop. If it's column family add,
+ // it also includes column family name.
+ bool is_column_family_drop_ = false;
+ bool is_column_family_add_ = false;
+ std::string column_family_name_;
+
+ bool is_in_atomic_group_ = false;
+ uint32_t remaining_entries_ = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_edit_test.cc b/storage/rocksdb/rocksdb/db/version_edit_test.cc
new file mode 100644
index 00000000..8bc884df
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_edit_test.cc
@@ -0,0 +1,286 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "util/coding.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, kInvalidBlobFileNumber,
+ 888, 678, "234", "crc32c");
+ 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, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue),
+ InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501,
+ kBig + 601, false, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ edit.AddFile(5, 302, 0, 100, InternalKey("foo", kBig + 502, kTypeValue),
+ InternalKey("zoo", kBig + 602, kTypeDeletion), kBig + 502,
+ kBig + 602, true, kInvalidBlobFileNumber, 666, 888,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ edit.AddFile(5, 303, 0, 100, InternalKey("foo", kBig + 503, kTypeBlobIndex),
+ InternalKey("zoo", kBig + 603, kTypeBlobIndex), kBig + 503,
+ kBig + 603, true, 1001, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ ;
+
+ 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, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue),
+ InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501,
+ kBig + 601, false, kInvalidBlobFileNumber, 686, 868, "234",
+ "crc32c");
+ 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<std::string*>(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, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+
+ 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<std::string*>(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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ 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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/version_set.cc b/storage/rocksdb/rocksdb/db/version_set.cc
new file mode 100644
index 00000000..e913a97d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_set.cc
@@ -0,0 +1,6005 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <algorithm>
+#include <array>
+#include <cinttypes>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "compaction/compaction.h"
+#include "db/internal_stats.h"
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/memtable.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/table_cache.h"
+#include "db/version_builder.h"
+#include "file/filename.h"
+#include "file/random_access_file_reader.h"
+#include "file/read_write_util.h"
+#include "file/writable_file_writer.h"
+#include "monitoring/file_read_sample.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/persistent_stats_history.h"
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/format.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.h"
+#include "table/merging_iterator.h"
+#include "table/meta_blocks.h"
+#include "table/multiget_context.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/table_reader.h"
+#include "table/two_level_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+#include "util/user_comparator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// Find File in LevelFilesBrief data structure
+// Within an index range defined by left and right
+int FindFileInRange(const InternalKeyComparator& icmp,
+ const LevelFilesBrief& file_level,
+ const Slice& key,
+ uint32_t left,
+ uint32_t right) {
+ auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool {
+ return icmp.InternalKeyComparator::Compare(f.largest_key, k) < 0;
+ };
+ const auto &b = file_level.files;
+ return static_cast<int>(std::lower_bound(b + left,
+ b + right, key, cmp) - b);
+}
+
+Status OverlapWithIterator(const Comparator* ucmp,
+ const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ InternalIterator* iter,
+ bool* overlap) {
+ InternalKey range_start(smallest_user_key, kMaxSequenceNumber,
+ kValueTypeForSeek);
+ iter->Seek(range_start.Encode());
+ if (!iter->status().ok()) {
+ return iter->status();
+ }
+
+ *overlap = false;
+ if (iter->Valid()) {
+ ParsedInternalKey seek_result;
+ if (!ParseInternalKey(iter->key(), &seek_result)) {
+ return Status::Corruption("DB have corrupted keys");
+ }
+
+ if (ucmp->CompareWithoutTimestamp(seek_result.user_key, largest_user_key) <=
+ 0) {
+ *overlap = true;
+ }
+ }
+
+ return iter->status();
+}
+
+// Class to help choose the next file to search for the particular key.
+// Searches and returns files level by level.
+// We can search level-by-level since entries never hop across
+// levels. Therefore we are guaranteed that if we find data
+// in a smaller level, later levels are irrelevant (unless we
+// are MergeInProgress).
+class FilePicker {
+ public:
+ FilePicker(std::vector<FileMetaData*>* files, const Slice& user_key,
+ const Slice& ikey, autovector<LevelFilesBrief>* file_levels,
+ unsigned int num_levels, FileIndexer* file_indexer,
+ const Comparator* user_comparator,
+ const InternalKeyComparator* internal_comparator)
+ : num_levels_(num_levels),
+ curr_level_(static_cast<unsigned int>(-1)),
+ returned_file_level_(static_cast<unsigned int>(-1)),
+ hit_file_level_(static_cast<unsigned int>(-1)),
+ search_left_bound_(0),
+ search_right_bound_(FileIndexer::kLevelMaxIndex),
+#ifndef NDEBUG
+ files_(files),
+#endif
+ level_files_brief_(file_levels),
+ is_hit_file_last_in_level_(false),
+ curr_file_level_(nullptr),
+ user_key_(user_key),
+ ikey_(ikey),
+ file_indexer_(file_indexer),
+ user_comparator_(user_comparator),
+ internal_comparator_(internal_comparator) {
+#ifdef NDEBUG
+ (void)files;
+#endif
+ // Setup member variables to search first level.
+ search_ended_ = !PrepareNextLevel();
+ if (!search_ended_) {
+ // Prefetch Level 0 table data to avoid cache miss if possible.
+ for (unsigned int i = 0; i < (*level_files_brief_)[0].num_files; ++i) {
+ auto* r = (*level_files_brief_)[0].files[i].fd.table_reader;
+ if (r) {
+ r->Prepare(ikey);
+ }
+ }
+ }
+ }
+
+ int GetCurrentLevel() const { return curr_level_; }
+
+ FdWithKeyRange* GetNextFile() {
+ while (!search_ended_) { // Loops over different levels.
+ while (curr_index_in_curr_level_ < curr_file_level_->num_files) {
+ // Loops over all files in current level.
+ FdWithKeyRange* f = &curr_file_level_->files[curr_index_in_curr_level_];
+ hit_file_level_ = curr_level_;
+ is_hit_file_last_in_level_ =
+ curr_index_in_curr_level_ == curr_file_level_->num_files - 1;
+ int cmp_largest = -1;
+
+ // Do key range filtering of files or/and fractional cascading if:
+ // (1) not all the files are in level 0, or
+ // (2) there are more than 3 current level files
+ // If there are only 3 or less current level files in the system, we skip
+ // the key range filtering. In this case, more likely, the system is
+ // highly tuned to minimize number of tables queried by each query,
+ // so it is unlikely that key range filtering is more efficient than
+ // querying the files.
+ if (num_levels_ > 1 || curr_file_level_->num_files > 3) {
+ // Check if key is within a file's range. If search left bound and
+ // right bound point to the same find, we are sure key falls in
+ // range.
+ assert(curr_level_ == 0 ||
+ curr_index_in_curr_level_ == start_index_in_curr_level_ ||
+ user_comparator_->CompareWithoutTimestamp(
+ user_key_, ExtractUserKey(f->smallest_key)) <= 0);
+
+ int cmp_smallest = user_comparator_->CompareWithoutTimestamp(
+ user_key_, ExtractUserKey(f->smallest_key));
+ if (cmp_smallest >= 0) {
+ cmp_largest = user_comparator_->CompareWithoutTimestamp(
+ user_key_, ExtractUserKey(f->largest_key));
+ }
+
+ // Setup file search bound for the next level based on the
+ // comparison results
+ if (curr_level_ > 0) {
+ file_indexer_->GetNextLevelIndex(curr_level_,
+ curr_index_in_curr_level_,
+ cmp_smallest, cmp_largest,
+ &search_left_bound_,
+ &search_right_bound_);
+ }
+ // Key falls out of current file's range
+ if (cmp_smallest < 0 || cmp_largest > 0) {
+ if (curr_level_ == 0) {
+ ++curr_index_in_curr_level_;
+ continue;
+ } else {
+ // Search next level.
+ break;
+ }
+ }
+ }
+#ifndef NDEBUG
+ // Sanity check to make sure that the files are correctly sorted
+ if (prev_file_) {
+ if (curr_level_ != 0) {
+ int comp_sign = internal_comparator_->Compare(
+ prev_file_->largest_key, f->smallest_key);
+ assert(comp_sign < 0);
+ } else {
+ // level == 0, the current file cannot be newer than the previous
+ // one. Use compressed data structure, has no attribute seqNo
+ assert(curr_index_in_curr_level_ > 0);
+ assert(!NewestFirstBySeqNo(files_[0][curr_index_in_curr_level_],
+ files_[0][curr_index_in_curr_level_-1]));
+ }
+ }
+ prev_file_ = f;
+#endif
+ returned_file_level_ = curr_level_;
+ if (curr_level_ > 0 && cmp_largest < 0) {
+ // No more files to search in this level.
+ search_ended_ = !PrepareNextLevel();
+ } else {
+ ++curr_index_in_curr_level_;
+ }
+ return f;
+ }
+ // Start searching next level.
+ search_ended_ = !PrepareNextLevel();
+ }
+ // Search ended.
+ return nullptr;
+ }
+
+ // getter for current file level
+ // for GET_HIT_L0, GET_HIT_L1 & GET_HIT_L2_AND_UP counts
+ unsigned int GetHitFileLevel() { return hit_file_level_; }
+
+ // Returns true if the most recent "hit file" (i.e., one returned by
+ // GetNextFile()) is at the last index in its level.
+ bool IsHitFileLastInLevel() { return is_hit_file_last_in_level_; }
+
+ private:
+ unsigned int num_levels_;
+ unsigned int curr_level_;
+ unsigned int returned_file_level_;
+ unsigned int hit_file_level_;
+ int32_t search_left_bound_;
+ int32_t search_right_bound_;
+#ifndef NDEBUG
+ std::vector<FileMetaData*>* files_;
+#endif
+ autovector<LevelFilesBrief>* level_files_brief_;
+ bool search_ended_;
+ bool is_hit_file_last_in_level_;
+ LevelFilesBrief* curr_file_level_;
+ unsigned int curr_index_in_curr_level_;
+ unsigned int start_index_in_curr_level_;
+ Slice user_key_;
+ Slice ikey_;
+ FileIndexer* file_indexer_;
+ const Comparator* user_comparator_;
+ const InternalKeyComparator* internal_comparator_;
+#ifndef NDEBUG
+ FdWithKeyRange* prev_file_;
+#endif
+
+ // Setup local variables to search next level.
+ // Returns false if there are no more levels to search.
+ bool PrepareNextLevel() {
+ curr_level_++;
+ while (curr_level_ < num_levels_) {
+ curr_file_level_ = &(*level_files_brief_)[curr_level_];
+ if (curr_file_level_->num_files == 0) {
+ // When current level is empty, the search bound generated from upper
+ // level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is
+ // also empty.
+ assert(search_left_bound_ == 0);
+ assert(search_right_bound_ == -1 ||
+ search_right_bound_ == FileIndexer::kLevelMaxIndex);
+ // Since current level is empty, it will need to search all files in
+ // the next level
+ search_left_bound_ = 0;
+ search_right_bound_ = FileIndexer::kLevelMaxIndex;
+ curr_level_++;
+ continue;
+ }
+
+ // Some files may overlap each other. We find
+ // all files that overlap user_key and process them in order from
+ // newest to oldest. In the context of merge-operator, this can occur at
+ // any level. Otherwise, it only occurs at Level-0 (since Put/Deletes
+ // are always compacted into a single entry).
+ int32_t start_index;
+ if (curr_level_ == 0) {
+ // On Level-0, we read through all files to check for overlap.
+ start_index = 0;
+ } else {
+ // On Level-n (n>=1), files are sorted. Binary search to find the
+ // earliest file whose largest key >= ikey. Search left bound and
+ // right bound are used to narrow the range.
+ if (search_left_bound_ <= search_right_bound_) {
+ if (search_right_bound_ == FileIndexer::kLevelMaxIndex) {
+ search_right_bound_ =
+ static_cast<int32_t>(curr_file_level_->num_files) - 1;
+ }
+ // `search_right_bound_` is an inclusive upper-bound, but since it was
+ // determined based on user key, it is still possible the lookup key
+ // falls to the right of `search_right_bound_`'s corresponding file.
+ // So, pass a limit one higher, which allows us to detect this case.
+ start_index =
+ FindFileInRange(*internal_comparator_, *curr_file_level_, ikey_,
+ static_cast<uint32_t>(search_left_bound_),
+ static_cast<uint32_t>(search_right_bound_) + 1);
+ if (start_index == search_right_bound_ + 1) {
+ // `ikey_` comes after `search_right_bound_`. The lookup key does
+ // not exist on this level, so let's skip this level and do a full
+ // binary search on the next level.
+ search_left_bound_ = 0;
+ search_right_bound_ = FileIndexer::kLevelMaxIndex;
+ curr_level_++;
+ continue;
+ }
+ } else {
+ // search_left_bound > search_right_bound, key does not exist in
+ // this level. Since no comparison is done in this level, it will
+ // need to search all files in the next level.
+ search_left_bound_ = 0;
+ search_right_bound_ = FileIndexer::kLevelMaxIndex;
+ curr_level_++;
+ continue;
+ }
+ }
+ start_index_in_curr_level_ = start_index;
+ curr_index_in_curr_level_ = start_index;
+#ifndef NDEBUG
+ prev_file_ = nullptr;
+#endif
+ return true;
+ }
+ // curr_level_ = num_levels_. So, no more levels to search.
+ return false;
+ }
+};
+
+class FilePickerMultiGet {
+ private:
+ struct FilePickerContext;
+
+ public:
+ FilePickerMultiGet(MultiGetRange* range,
+ autovector<LevelFilesBrief>* file_levels,
+ unsigned int num_levels, FileIndexer* file_indexer,
+ const Comparator* user_comparator,
+ const InternalKeyComparator* internal_comparator)
+ : num_levels_(num_levels),
+ curr_level_(static_cast<unsigned int>(-1)),
+ returned_file_level_(static_cast<unsigned int>(-1)),
+ hit_file_level_(static_cast<unsigned int>(-1)),
+ range_(range),
+ batch_iter_(range->begin()),
+ batch_iter_prev_(range->begin()),
+ maybe_repeat_key_(false),
+ current_level_range_(*range, range->begin(), range->end()),
+ current_file_range_(*range, range->begin(), range->end()),
+ level_files_brief_(file_levels),
+ is_hit_file_last_in_level_(false),
+ curr_file_level_(nullptr),
+ file_indexer_(file_indexer),
+ user_comparator_(user_comparator),
+ internal_comparator_(internal_comparator) {
+ for (auto iter = range_->begin(); iter != range_->end(); ++iter) {
+ fp_ctx_array_[iter.index()] =
+ FilePickerContext(0, FileIndexer::kLevelMaxIndex);
+ }
+
+ // Setup member variables to search first level.
+ search_ended_ = !PrepareNextLevel();
+ if (!search_ended_) {
+ // REVISIT
+ // Prefetch Level 0 table data to avoid cache miss if possible.
+ // As of now, only PlainTableReader and CuckooTableReader do any
+ // prefetching. This may not be necessary anymore once we implement
+ // batching in those table readers
+ for (unsigned int i = 0; i < (*level_files_brief_)[0].num_files; ++i) {
+ auto* r = (*level_files_brief_)[0].files[i].fd.table_reader;
+ if (r) {
+ for (auto iter = range_->begin(); iter != range_->end(); ++iter) {
+ r->Prepare(iter->ikey);
+ }
+ }
+ }
+ }
+ }
+
+ int GetCurrentLevel() const { return curr_level_; }
+
+ // Iterates through files in the current level until it finds a file that
+ // contains atleast one key from the MultiGet batch
+ bool GetNextFileInLevelWithKeys(MultiGetRange* next_file_range,
+ size_t* file_index, FdWithKeyRange** fd,
+ bool* is_last_key_in_file) {
+ size_t curr_file_index = *file_index;
+ FdWithKeyRange* f = nullptr;
+ bool file_hit = false;
+ int cmp_largest = -1;
+ if (curr_file_index >= curr_file_level_->num_files) {
+ // In the unlikely case the next key is a duplicate of the current key,
+ // and the current key is the last in the level and the internal key
+ // was not found, we need to skip lookup for the remaining keys and
+ // reset the search bounds
+ if (batch_iter_ != current_level_range_.end()) {
+ ++batch_iter_;
+ for (; batch_iter_ != current_level_range_.end(); ++batch_iter_) {
+ struct FilePickerContext& fp_ctx = fp_ctx_array_[batch_iter_.index()];
+ fp_ctx.search_left_bound = 0;
+ fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex;
+ }
+ }
+ return false;
+ }
+ // Loops over keys in the MultiGet batch until it finds a file with
+ // atleast one of the keys. Then it keeps moving forward until the
+ // last key in the batch that falls in that file
+ while (batch_iter_ != current_level_range_.end() &&
+ (fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level ==
+ curr_file_index ||
+ !file_hit)) {
+ struct FilePickerContext& fp_ctx = fp_ctx_array_[batch_iter_.index()];
+ f = &curr_file_level_->files[fp_ctx.curr_index_in_curr_level];
+ Slice& user_key = batch_iter_->ukey;
+
+ // Do key range filtering of files or/and fractional cascading if:
+ // (1) not all the files are in level 0, or
+ // (2) there are more than 3 current level files
+ // If there are only 3 or less current level files in the system, we
+ // skip the key range filtering. In this case, more likely, the system
+ // is highly tuned to minimize number of tables queried by each query,
+ // so it is unlikely that key range filtering is more efficient than
+ // querying the files.
+ if (num_levels_ > 1 || curr_file_level_->num_files > 3) {
+ // Check if key is within a file's range. If search left bound and
+ // right bound point to the same find, we are sure key falls in
+ // range.
+ assert(curr_level_ == 0 ||
+ fp_ctx.curr_index_in_curr_level ==
+ fp_ctx.start_index_in_curr_level ||
+ user_comparator_->Compare(user_key,
+ ExtractUserKey(f->smallest_key)) <= 0);
+
+ int cmp_smallest = user_comparator_->Compare(
+ user_key, ExtractUserKey(f->smallest_key));
+ if (cmp_smallest >= 0) {
+ cmp_largest = user_comparator_->Compare(
+ user_key, ExtractUserKey(f->largest_key));
+ } else {
+ cmp_largest = -1;
+ }
+
+ // Setup file search bound for the next level based on the
+ // comparison results
+ if (curr_level_ > 0) {
+ file_indexer_->GetNextLevelIndex(
+ curr_level_, fp_ctx.curr_index_in_curr_level, cmp_smallest,
+ cmp_largest, &fp_ctx.search_left_bound,
+ &fp_ctx.search_right_bound);
+ }
+ // Key falls out of current file's range
+ if (cmp_smallest < 0 || cmp_largest > 0) {
+ next_file_range->SkipKey(batch_iter_);
+ } else {
+ file_hit = true;
+ }
+ } else {
+ file_hit = true;
+ }
+ if (cmp_largest == 0) {
+ // cmp_largest is 0, which means the next key will not be in this
+ // file, so stop looking further. Also don't increment megt_iter_
+ // as we may have to look for this key in the next file if we don't
+ // find it in this one
+ break;
+ } else {
+ if (curr_level_ == 0) {
+ // We need to look through all files in level 0
+ ++fp_ctx.curr_index_in_curr_level;
+ }
+ ++batch_iter_;
+ }
+ if (!file_hit) {
+ curr_file_index =
+ (batch_iter_ != current_level_range_.end())
+ ? fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level
+ : curr_file_level_->num_files;
+ }
+ }
+
+ *fd = f;
+ *file_index = curr_file_index;
+ *is_last_key_in_file = cmp_largest == 0;
+ return file_hit;
+ }
+
+ FdWithKeyRange* GetNextFile() {
+ while (!search_ended_) {
+ // Start searching next level.
+ if (batch_iter_ == current_level_range_.end()) {
+ search_ended_ = !PrepareNextLevel();
+ continue;
+ } else {
+ if (maybe_repeat_key_) {
+ maybe_repeat_key_ = false;
+ // Check if we found the final value for the last key in the
+ // previous lookup range. If we did, then there's no need to look
+ // any further for that key, so advance batch_iter_. Else, keep
+ // batch_iter_ positioned on that key so we look it up again in
+ // the next file
+ // For L0, always advance the key because we will look in the next
+ // file regardless for all keys not found yet
+ if (current_level_range_.CheckKeyDone(batch_iter_) ||
+ curr_level_ == 0) {
+ ++batch_iter_;
+ }
+ }
+ // batch_iter_prev_ will become the start key for the next file
+ // lookup
+ batch_iter_prev_ = batch_iter_;
+ }
+
+ MultiGetRange next_file_range(current_level_range_, batch_iter_prev_,
+ current_level_range_.end());
+ size_t curr_file_index =
+ (batch_iter_ != current_level_range_.end())
+ ? fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level
+ : curr_file_level_->num_files;
+ FdWithKeyRange* f;
+ bool is_last_key_in_file;
+ if (!GetNextFileInLevelWithKeys(&next_file_range, &curr_file_index, &f,
+ &is_last_key_in_file)) {
+ search_ended_ = !PrepareNextLevel();
+ } else {
+ MultiGetRange::Iterator upper_key = batch_iter_;
+ if (is_last_key_in_file) {
+ // Since cmp_largest is 0, batch_iter_ still points to the last key
+ // that falls in this file, instead of the next one. Increment
+ // upper_key so we can set the range properly for SST MultiGet
+ ++upper_key;
+ ++(fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level);
+ maybe_repeat_key_ = true;
+ }
+ // Set the range for this file
+ current_file_range_ =
+ MultiGetRange(next_file_range, batch_iter_prev_, upper_key);
+ returned_file_level_ = curr_level_;
+ hit_file_level_ = curr_level_;
+ is_hit_file_last_in_level_ =
+ curr_file_index == curr_file_level_->num_files - 1;
+ return f;
+ }
+ }
+
+ // Search ended
+ return nullptr;
+ }
+
+ // getter for current file level
+ // for GET_HIT_L0, GET_HIT_L1 & GET_HIT_L2_AND_UP counts
+ unsigned int GetHitFileLevel() { return hit_file_level_; }
+
+ // Returns true if the most recent "hit file" (i.e., one returned by
+ // GetNextFile()) is at the last index in its level.
+ bool IsHitFileLastInLevel() { return is_hit_file_last_in_level_; }
+
+ const MultiGetRange& CurrentFileRange() { return current_file_range_; }
+
+ private:
+ unsigned int num_levels_;
+ unsigned int curr_level_;
+ unsigned int returned_file_level_;
+ unsigned int hit_file_level_;
+
+ struct FilePickerContext {
+ int32_t search_left_bound;
+ int32_t search_right_bound;
+ unsigned int curr_index_in_curr_level;
+ unsigned int start_index_in_curr_level;
+
+ FilePickerContext(int32_t left, int32_t right)
+ : search_left_bound(left), search_right_bound(right),
+ curr_index_in_curr_level(0), start_index_in_curr_level(0) {}
+
+ FilePickerContext() = default;
+ };
+ std::array<FilePickerContext, MultiGetContext::MAX_BATCH_SIZE> fp_ctx_array_;
+ MultiGetRange* range_;
+ // Iterator to iterate through the keys in a MultiGet batch, that gets reset
+ // at the beginning of each level. Each call to GetNextFile() will position
+ // batch_iter_ at or right after the last key that was found in the returned
+ // SST file
+ MultiGetRange::Iterator batch_iter_;
+ // An iterator that records the previous position of batch_iter_, i.e last
+ // key found in the previous SST file, in order to serve as the start of
+ // the batch key range for the next SST file
+ MultiGetRange::Iterator batch_iter_prev_;
+ bool maybe_repeat_key_;
+ MultiGetRange current_level_range_;
+ MultiGetRange current_file_range_;
+ autovector<LevelFilesBrief>* level_files_brief_;
+ bool search_ended_;
+ bool is_hit_file_last_in_level_;
+ LevelFilesBrief* curr_file_level_;
+ FileIndexer* file_indexer_;
+ const Comparator* user_comparator_;
+ const InternalKeyComparator* internal_comparator_;
+
+ // Setup local variables to search next level.
+ // Returns false if there are no more levels to search.
+ bool PrepareNextLevel() {
+ if (curr_level_ == 0) {
+ MultiGetRange::Iterator mget_iter = current_level_range_.begin();
+ if (fp_ctx_array_[mget_iter.index()].curr_index_in_curr_level <
+ curr_file_level_->num_files) {
+ batch_iter_prev_ = current_level_range_.begin();
+ batch_iter_ = current_level_range_.begin();
+ return true;
+ }
+ }
+
+ curr_level_++;
+ // Reset key range to saved value
+ while (curr_level_ < num_levels_) {
+ bool level_contains_keys = false;
+ curr_file_level_ = &(*level_files_brief_)[curr_level_];
+ if (curr_file_level_->num_files == 0) {
+ // When current level is empty, the search bound generated from upper
+ // level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is
+ // also empty.
+
+ for (auto mget_iter = current_level_range_.begin();
+ mget_iter != current_level_range_.end(); ++mget_iter) {
+ struct FilePickerContext& fp_ctx = fp_ctx_array_[mget_iter.index()];
+
+ assert(fp_ctx.search_left_bound == 0);
+ assert(fp_ctx.search_right_bound == -1 ||
+ fp_ctx.search_right_bound == FileIndexer::kLevelMaxIndex);
+ // Since current level is empty, it will need to search all files in
+ // the next level
+ fp_ctx.search_left_bound = 0;
+ fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex;
+ }
+ // Skip all subsequent empty levels
+ do {
+ ++curr_level_;
+ } while ((curr_level_ < num_levels_) &&
+ (*level_files_brief_)[curr_level_].num_files == 0);
+ continue;
+ }
+
+ // Some files may overlap each other. We find
+ // all files that overlap user_key and process them in order from
+ // newest to oldest. In the context of merge-operator, this can occur at
+ // any level. Otherwise, it only occurs at Level-0 (since Put/Deletes
+ // are always compacted into a single entry).
+ int32_t start_index = -1;
+ current_level_range_ =
+ MultiGetRange(*range_, range_->begin(), range_->end());
+ for (auto mget_iter = current_level_range_.begin();
+ mget_iter != current_level_range_.end(); ++mget_iter) {
+ struct FilePickerContext& fp_ctx = fp_ctx_array_[mget_iter.index()];
+ if (curr_level_ == 0) {
+ // On Level-0, we read through all files to check for overlap.
+ start_index = 0;
+ level_contains_keys = true;
+ } else {
+ // On Level-n (n>=1), files are sorted. Binary search to find the
+ // earliest file whose largest key >= ikey. Search left bound and
+ // right bound are used to narrow the range.
+ if (fp_ctx.search_left_bound <= fp_ctx.search_right_bound) {
+ if (fp_ctx.search_right_bound == FileIndexer::kLevelMaxIndex) {
+ fp_ctx.search_right_bound =
+ static_cast<int32_t>(curr_file_level_->num_files) - 1;
+ }
+ // `search_right_bound_` is an inclusive upper-bound, but since it
+ // was determined based on user key, it is still possible the lookup
+ // key falls to the right of `search_right_bound_`'s corresponding
+ // file. So, pass a limit one higher, which allows us to detect this
+ // case.
+ Slice& ikey = mget_iter->ikey;
+ start_index = FindFileInRange(
+ *internal_comparator_, *curr_file_level_, ikey,
+ static_cast<uint32_t>(fp_ctx.search_left_bound),
+ static_cast<uint32_t>(fp_ctx.search_right_bound) + 1);
+ if (start_index == fp_ctx.search_right_bound + 1) {
+ // `ikey_` comes after `search_right_bound_`. The lookup key does
+ // not exist on this level, so let's skip this level and do a full
+ // binary search on the next level.
+ fp_ctx.search_left_bound = 0;
+ fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex;
+ current_level_range_.SkipKey(mget_iter);
+ continue;
+ } else {
+ level_contains_keys = true;
+ }
+ } else {
+ // search_left_bound > search_right_bound, key does not exist in
+ // this level. Since no comparison is done in this level, it will
+ // need to search all files in the next level.
+ fp_ctx.search_left_bound = 0;
+ fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex;
+ current_level_range_.SkipKey(mget_iter);
+ continue;
+ }
+ }
+ fp_ctx.start_index_in_curr_level = start_index;
+ fp_ctx.curr_index_in_curr_level = start_index;
+ }
+ if (level_contains_keys) {
+ batch_iter_prev_ = current_level_range_.begin();
+ batch_iter_ = current_level_range_.begin();
+ return true;
+ }
+ curr_level_++;
+ }
+ // curr_level_ = num_levels_. So, no more levels to search.
+ return false;
+ }
+};
+} // anonymous namespace
+
+VersionStorageInfo::~VersionStorageInfo() { delete[] files_; }
+
+Version::~Version() {
+ assert(refs_ == 0);
+
+ // Remove from linked list
+ prev_->next_ = next_;
+ next_->prev_ = prev_;
+
+ // Drop references to files
+ for (int level = 0; level < storage_info_.num_levels_; level++) {
+ for (size_t i = 0; i < storage_info_.files_[level].size(); i++) {
+ FileMetaData* f = storage_info_.files_[level][i];
+ assert(f->refs > 0);
+ f->refs--;
+ if (f->refs <= 0) {
+ assert(cfd_ != nullptr);
+ uint32_t path_id = f->fd.GetPathId();
+ assert(path_id < cfd_->ioptions()->cf_paths.size());
+ vset_->obsolete_files_.push_back(
+ ObsoleteFileInfo(f, cfd_->ioptions()->cf_paths[path_id].path));
+ }
+ }
+ }
+}
+
+int FindFile(const InternalKeyComparator& icmp,
+ const LevelFilesBrief& file_level,
+ const Slice& key) {
+ return FindFileInRange(icmp, file_level, key, 0,
+ static_cast<uint32_t>(file_level.num_files));
+}
+
+void DoGenerateLevelFilesBrief(LevelFilesBrief* file_level,
+ const std::vector<FileMetaData*>& files,
+ Arena* arena) {
+ assert(file_level);
+ assert(arena);
+
+ size_t num = files.size();
+ file_level->num_files = num;
+ char* mem = arena->AllocateAligned(num * sizeof(FdWithKeyRange));
+ file_level->files = new (mem)FdWithKeyRange[num];
+
+ for (size_t i = 0; i < num; i++) {
+ Slice smallest_key = files[i]->smallest.Encode();
+ Slice largest_key = files[i]->largest.Encode();
+
+ // Copy key slice to sequential memory
+ size_t smallest_size = smallest_key.size();
+ size_t largest_size = largest_key.size();
+ mem = arena->AllocateAligned(smallest_size + largest_size);
+ memcpy(mem, smallest_key.data(), smallest_size);
+ memcpy(mem + smallest_size, largest_key.data(), largest_size);
+
+ FdWithKeyRange& f = file_level->files[i];
+ f.fd = files[i]->fd;
+ f.file_metadata = files[i];
+ f.smallest_key = Slice(mem, smallest_size);
+ f.largest_key = Slice(mem + smallest_size, largest_size);
+ }
+}
+
+static bool AfterFile(const Comparator* ucmp,
+ const Slice* user_key, const FdWithKeyRange* f) {
+ // nullptr user_key occurs before all keys and is therefore never after *f
+ return (user_key != nullptr &&
+ ucmp->CompareWithoutTimestamp(*user_key,
+ ExtractUserKey(f->largest_key)) > 0);
+}
+
+static bool BeforeFile(const Comparator* ucmp,
+ const Slice* user_key, const FdWithKeyRange* f) {
+ // nullptr user_key occurs after all keys and is therefore never before *f
+ return (user_key != nullptr &&
+ ucmp->CompareWithoutTimestamp(*user_key,
+ ExtractUserKey(f->smallest_key)) < 0);
+}
+
+bool SomeFileOverlapsRange(
+ const InternalKeyComparator& icmp,
+ bool disjoint_sorted_files,
+ const LevelFilesBrief& file_level,
+ const Slice* smallest_user_key,
+ const Slice* largest_user_key) {
+ const Comparator* ucmp = icmp.user_comparator();
+ if (!disjoint_sorted_files) {
+ // Need to check against all files
+ for (size_t i = 0; i < file_level.num_files; i++) {
+ const FdWithKeyRange* f = &(file_level.files[i]);
+ if (AfterFile(ucmp, smallest_user_key, f) ||
+ BeforeFile(ucmp, largest_user_key, f)) {
+ // No overlap
+ } else {
+ return true; // Overlap
+ }
+ }
+ return false;
+ }
+
+ // Binary search over file list
+ uint32_t index = 0;
+ if (smallest_user_key != nullptr) {
+ // Find the leftmost possible internal key for smallest_user_key
+ InternalKey small;
+ small.SetMinPossibleForUserKey(*smallest_user_key);
+ index = FindFile(icmp, file_level, small.Encode());
+ }
+
+ if (index >= file_level.num_files) {
+ // beginning of range is after all files, so no overlap.
+ return false;
+ }
+
+ return !BeforeFile(ucmp, largest_user_key, &file_level.files[index]);
+}
+
+namespace {
+
+class LevelIterator final : public InternalIterator {
+ public:
+ LevelIterator(TableCache* table_cache, const ReadOptions& read_options,
+ const FileOptions& file_options,
+ const InternalKeyComparator& icomparator,
+ const LevelFilesBrief* flevel,
+ const SliceTransform* prefix_extractor, bool should_sample,
+ HistogramImpl* file_read_hist, TableReaderCaller caller,
+ bool skip_filters, int level, RangeDelAggregator* range_del_agg,
+ const std::vector<AtomicCompactionUnitBoundary>*
+ compaction_boundaries = nullptr)
+ : table_cache_(table_cache),
+ read_options_(read_options),
+ file_options_(file_options),
+ icomparator_(icomparator),
+ user_comparator_(icomparator.user_comparator()),
+ flevel_(flevel),
+ prefix_extractor_(prefix_extractor),
+ file_read_hist_(file_read_hist),
+ should_sample_(should_sample),
+ caller_(caller),
+ skip_filters_(skip_filters),
+ file_index_(flevel_->num_files),
+ level_(level),
+ range_del_agg_(range_del_agg),
+ pinned_iters_mgr_(nullptr),
+ compaction_boundaries_(compaction_boundaries) {
+ // Empty level is not supported.
+ assert(flevel_ != nullptr && flevel_->num_files > 0);
+ }
+
+ ~LevelIterator() override { delete file_iter_.Set(nullptr); }
+
+ void Seek(const Slice& target) override;
+ void SeekForPrev(const Slice& target) override;
+ void SeekToFirst() override;
+ void SeekToLast() override;
+ void Next() final override;
+ bool NextAndGetResult(IterateResult* result) override;
+ void Prev() override;
+
+ bool Valid() const override { return file_iter_.Valid(); }
+ Slice key() const override {
+ assert(Valid());
+ return file_iter_.key();
+ }
+
+ Slice value() const override {
+ assert(Valid());
+ return file_iter_.value();
+ }
+
+ Status status() const override {
+ return file_iter_.iter() ? file_iter_.status() : Status::OK();
+ }
+
+ inline bool MayBeOutOfLowerBound() override {
+ assert(Valid());
+ return may_be_out_of_lower_bound_ && file_iter_.MayBeOutOfLowerBound();
+ }
+
+ inline bool MayBeOutOfUpperBound() override {
+ assert(Valid());
+ return file_iter_.MayBeOutOfUpperBound();
+ }
+
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ if (file_iter_.iter()) {
+ file_iter_.SetPinnedItersMgr(pinned_iters_mgr);
+ }
+ }
+
+ bool IsKeyPinned() const override {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ file_iter_.iter() && file_iter_.IsKeyPinned();
+ }
+
+ bool IsValuePinned() const override {
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ file_iter_.iter() && file_iter_.IsValuePinned();
+ }
+
+ private:
+ // Return true if at least one invalid file is seen and skipped.
+ bool SkipEmptyFileForward();
+ void SkipEmptyFileBackward();
+ void SetFileIterator(InternalIterator* iter);
+ void InitFileIterator(size_t new_file_index);
+
+ // Called by both of Next() and NextAndGetResult(). Force inline.
+ void NextImpl() {
+ assert(Valid());
+ file_iter_.Next();
+ SkipEmptyFileForward();
+ }
+
+ const Slice& file_smallest_key(size_t file_index) {
+ assert(file_index < flevel_->num_files);
+ return flevel_->files[file_index].smallest_key;
+ }
+
+ bool KeyReachedUpperBound(const Slice& internal_key) {
+ return read_options_.iterate_upper_bound != nullptr &&
+ user_comparator_.CompareWithoutTimestamp(
+ ExtractUserKey(internal_key),
+ *read_options_.iterate_upper_bound) >= 0;
+ }
+
+ InternalIterator* NewFileIterator() {
+ assert(file_index_ < flevel_->num_files);
+ auto file_meta = flevel_->files[file_index_];
+ if (should_sample_) {
+ sample_file_read_inc(file_meta.file_metadata);
+ }
+
+ const InternalKey* smallest_compaction_key = nullptr;
+ const InternalKey* largest_compaction_key = nullptr;
+ if (compaction_boundaries_ != nullptr) {
+ smallest_compaction_key = (*compaction_boundaries_)[file_index_].smallest;
+ largest_compaction_key = (*compaction_boundaries_)[file_index_].largest;
+ }
+ CheckMayBeOutOfLowerBound();
+ return table_cache_->NewIterator(
+ read_options_, file_options_, icomparator_, *file_meta.file_metadata,
+ range_del_agg_, prefix_extractor_,
+ nullptr /* don't need reference to table */, file_read_hist_, caller_,
+ /*arena=*/nullptr, skip_filters_, level_, smallest_compaction_key,
+ largest_compaction_key);
+ }
+
+ // Check if current file being fully within iterate_lower_bound.
+ //
+ // Note MyRocks may update iterate bounds between seek. To workaround it,
+ // we need to check and update may_be_out_of_lower_bound_ accordingly.
+ void CheckMayBeOutOfLowerBound() {
+ if (read_options_.iterate_lower_bound != nullptr &&
+ file_index_ < flevel_->num_files) {
+ may_be_out_of_lower_bound_ =
+ user_comparator_.Compare(
+ ExtractUserKey(file_smallest_key(file_index_)),
+ *read_options_.iterate_lower_bound) < 0;
+ }
+ }
+
+ TableCache* table_cache_;
+ const ReadOptions read_options_;
+ const FileOptions& file_options_;
+ const InternalKeyComparator& icomparator_;
+ const UserComparatorWrapper user_comparator_;
+ const LevelFilesBrief* flevel_;
+ mutable FileDescriptor current_value_;
+ // `prefix_extractor_` may be non-null even for total order seek. Checking
+ // this variable is not the right way to identify whether prefix iterator
+ // is used.
+ const SliceTransform* prefix_extractor_;
+
+ HistogramImpl* file_read_hist_;
+ bool should_sample_;
+ TableReaderCaller caller_;
+ bool skip_filters_;
+ bool may_be_out_of_lower_bound_ = true;
+ size_t file_index_;
+ int level_;
+ RangeDelAggregator* range_del_agg_;
+ IteratorWrapper file_iter_; // May be nullptr
+ PinnedIteratorsManager* pinned_iters_mgr_;
+
+ // To be propagated to RangeDelAggregator in order to safely truncate range
+ // tombstones.
+ const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries_;
+};
+
+void LevelIterator::Seek(const Slice& target) {
+ // Check whether the seek key fall under the same file
+ bool need_to_reseek = true;
+ if (file_iter_.iter() != nullptr && file_index_ < flevel_->num_files) {
+ const FdWithKeyRange& cur_file = flevel_->files[file_index_];
+ if (icomparator_.InternalKeyComparator::Compare(
+ target, cur_file.largest_key) <= 0 &&
+ icomparator_.InternalKeyComparator::Compare(
+ target, cur_file.smallest_key) >= 0) {
+ need_to_reseek = false;
+ assert(static_cast<size_t>(FindFile(icomparator_, *flevel_, target)) ==
+ file_index_);
+ }
+ }
+ if (need_to_reseek) {
+ TEST_SYNC_POINT("LevelIterator::Seek:BeforeFindFile");
+ size_t new_file_index = FindFile(icomparator_, *flevel_, target);
+ InitFileIterator(new_file_index);
+ }
+
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.Seek(target);
+ }
+ if (SkipEmptyFileForward() && prefix_extractor_ != nullptr &&
+ !read_options_.total_order_seek && !read_options_.auto_prefix_mode &&
+ file_iter_.iter() != nullptr && file_iter_.Valid()) {
+ // We've skipped the file we initially positioned to. In the prefix
+ // seek case, it is likely that the file is skipped because of
+ // prefix bloom or hash, where more keys are skipped. We then check
+ // the current key and invalidate the iterator if the prefix is
+ // already passed.
+ // When doing prefix iterator seek, when keys for one prefix have
+ // been exhausted, it can jump to any key that is larger. Here we are
+ // enforcing a stricter contract than that, in order to make it easier for
+ // higher layers (merging and DB iterator) to reason the correctness:
+ // 1. Within the prefix, the result should be accurate.
+ // 2. If keys for the prefix is exhausted, it is either positioned to the
+ // next key after the prefix, or make the iterator invalid.
+ // A side benefit will be that it invalidates the iterator earlier so that
+ // the upper level merging iterator can merge fewer child iterators.
+ Slice target_user_key = ExtractUserKey(target);
+ Slice file_user_key = ExtractUserKey(file_iter_.key());
+ if (prefix_extractor_->InDomain(target_user_key) &&
+ (!prefix_extractor_->InDomain(file_user_key) ||
+ user_comparator_.Compare(
+ prefix_extractor_->Transform(target_user_key),
+ prefix_extractor_->Transform(file_user_key)) != 0)) {
+ SetFileIterator(nullptr);
+ }
+ }
+ CheckMayBeOutOfLowerBound();
+}
+
+void LevelIterator::SeekForPrev(const Slice& target) {
+ size_t new_file_index = FindFile(icomparator_, *flevel_, target);
+ if (new_file_index >= flevel_->num_files) {
+ new_file_index = flevel_->num_files - 1;
+ }
+
+ InitFileIterator(new_file_index);
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.SeekForPrev(target);
+ SkipEmptyFileBackward();
+ }
+ CheckMayBeOutOfLowerBound();
+}
+
+void LevelIterator::SeekToFirst() {
+ InitFileIterator(0);
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.SeekToFirst();
+ }
+ SkipEmptyFileForward();
+ CheckMayBeOutOfLowerBound();
+}
+
+void LevelIterator::SeekToLast() {
+ InitFileIterator(flevel_->num_files - 1);
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.SeekToLast();
+ }
+ SkipEmptyFileBackward();
+ CheckMayBeOutOfLowerBound();
+}
+
+void LevelIterator::Next() { NextImpl(); }
+
+bool LevelIterator::NextAndGetResult(IterateResult* result) {
+ NextImpl();
+ bool is_valid = Valid();
+ if (is_valid) {
+ result->key = key();
+ result->may_be_out_of_upper_bound = MayBeOutOfUpperBound();
+ }
+ return is_valid;
+}
+
+void LevelIterator::Prev() {
+ assert(Valid());
+ file_iter_.Prev();
+ SkipEmptyFileBackward();
+}
+
+bool LevelIterator::SkipEmptyFileForward() {
+ bool seen_empty_file = false;
+ while (file_iter_.iter() == nullptr ||
+ (!file_iter_.Valid() && file_iter_.status().ok() &&
+ !file_iter_.iter()->IsOutOfBound())) {
+ seen_empty_file = true;
+ // Move to next file
+ if (file_index_ >= flevel_->num_files - 1) {
+ // Already at the last file
+ SetFileIterator(nullptr);
+ break;
+ }
+ if (KeyReachedUpperBound(file_smallest_key(file_index_ + 1))) {
+ SetFileIterator(nullptr);
+ break;
+ }
+ InitFileIterator(file_index_ + 1);
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.SeekToFirst();
+ }
+ }
+ return seen_empty_file;
+}
+
+void LevelIterator::SkipEmptyFileBackward() {
+ while (file_iter_.iter() == nullptr ||
+ (!file_iter_.Valid() && file_iter_.status().ok())) {
+ // Move to previous file
+ if (file_index_ == 0) {
+ // Already the first file
+ SetFileIterator(nullptr);
+ return;
+ }
+ InitFileIterator(file_index_ - 1);
+ if (file_iter_.iter() != nullptr) {
+ file_iter_.SeekToLast();
+ }
+ }
+}
+
+void LevelIterator::SetFileIterator(InternalIterator* iter) {
+ if (pinned_iters_mgr_ && iter) {
+ iter->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+
+ InternalIterator* old_iter = file_iter_.Set(iter);
+ if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
+ pinned_iters_mgr_->PinIterator(old_iter);
+ } else {
+ delete old_iter;
+ }
+}
+
+void LevelIterator::InitFileIterator(size_t new_file_index) {
+ if (new_file_index >= flevel_->num_files) {
+ file_index_ = new_file_index;
+ SetFileIterator(nullptr);
+ return;
+ } else {
+ // If the file iterator shows incomplete, we try it again if users seek
+ // to the same file, as this time we may go to a different data block
+ // which is cached in block cache.
+ //
+ if (file_iter_.iter() != nullptr && !file_iter_.status().IsIncomplete() &&
+ new_file_index == file_index_) {
+ // file_iter_ is already constructed with this iterator, so
+ // no need to change anything
+ } else {
+ file_index_ = new_file_index;
+ InternalIterator* iter = NewFileIterator();
+ SetFileIterator(iter);
+ }
+ }
+}
+} // anonymous namespace
+
+// A wrapper of version builder which references the current version in
+// constructor and unref it in the destructor.
+// Both of the constructor and destructor need to be called inside DB Mutex.
+class BaseReferencedVersionBuilder {
+ public:
+ explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd)
+ : version_builder_(new VersionBuilder(
+ cfd->current()->version_set()->file_options(), cfd->table_cache(),
+ cfd->current()->storage_info(), cfd->ioptions()->info_log)),
+ version_(cfd->current()) {
+ version_->Ref();
+ }
+ ~BaseReferencedVersionBuilder() {
+ version_->Unref();
+ }
+ VersionBuilder* version_builder() { return version_builder_.get(); }
+
+ private:
+ std::unique_ptr<VersionBuilder> version_builder_;
+ Version* version_;
+};
+
+Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
+ const FileMetaData* file_meta,
+ const std::string* fname) const {
+ auto table_cache = cfd_->table_cache();
+ auto ioptions = cfd_->ioptions();
+ Status s = table_cache->GetTableProperties(
+ file_options_, cfd_->internal_comparator(), file_meta->fd, tp,
+ mutable_cf_options_.prefix_extractor.get(), true /* no io */);
+ if (s.ok()) {
+ return s;
+ }
+
+ // We only ignore error type `Incomplete` since it's by design that we
+ // disallow table when it's not in table cache.
+ if (!s.IsIncomplete()) {
+ return s;
+ }
+
+ // 2. Table is not present in table cache, we'll read the table properties
+ // directly from the properties block in the file.
+ std::unique_ptr<FSRandomAccessFile> file;
+ std::string file_name;
+ if (fname != nullptr) {
+ file_name = *fname;
+ } else {
+ file_name =
+ TableFileName(ioptions->cf_paths, file_meta->fd.GetNumber(),
+ file_meta->fd.GetPathId());
+ }
+ s = ioptions->fs->NewRandomAccessFile(file_name, file_options_, &file,
+ nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+
+ TableProperties* raw_table_properties;
+ // By setting the magic number to kInvalidTableMagicNumber, we can by
+ // pass the magic number check in the footer.
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(
+ std::move(file), file_name, nullptr /* env */, nullptr /* stats */,
+ 0 /* hist_type */, nullptr /* file_read_hist */,
+ nullptr /* rate_limiter */, ioptions->listeners));
+ s = ReadTableProperties(
+ file_reader.get(), file_meta->fd.GetFileSize(),
+ Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions,
+ &raw_table_properties, false /* compression_type_missing */);
+ if (!s.ok()) {
+ return s;
+ }
+ RecordTick(ioptions->statistics, NUMBER_DIRECT_LOAD_TABLE_PROPERTIES);
+
+ *tp = std::shared_ptr<const TableProperties>(raw_table_properties);
+ return s;
+}
+
+Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props) {
+ Status s;
+ for (int level = 0; level < storage_info_.num_levels_; level++) {
+ s = GetPropertiesOfAllTables(props, level);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ return Status::OK();
+}
+
+Status Version::TablesRangeTombstoneSummary(int max_entries_to_print,
+ std::string* out_str) {
+ if (max_entries_to_print <= 0) {
+ return Status::OK();
+ }
+ int num_entries_left = max_entries_to_print;
+
+ std::stringstream ss;
+
+ for (int level = 0; level < storage_info_.num_levels_; level++) {
+ for (const auto& file_meta : storage_info_.files_[level]) {
+ auto fname =
+ TableFileName(cfd_->ioptions()->cf_paths, file_meta->fd.GetNumber(),
+ file_meta->fd.GetPathId());
+
+ ss << "=== file : " << fname << " ===\n";
+
+ TableCache* table_cache = cfd_->table_cache();
+ std::unique_ptr<FragmentedRangeTombstoneIterator> tombstone_iter;
+
+ Status s = table_cache->GetRangeTombstoneIterator(
+ ReadOptions(), cfd_->internal_comparator(), *file_meta,
+ &tombstone_iter);
+ if (!s.ok()) {
+ return s;
+ }
+ if (tombstone_iter) {
+ tombstone_iter->SeekToFirst();
+
+ while (tombstone_iter->Valid() && num_entries_left > 0) {
+ ss << "start: " << tombstone_iter->start_key().ToString(true)
+ << " end: " << tombstone_iter->end_key().ToString(true)
+ << " seq: " << tombstone_iter->seq() << '\n';
+ tombstone_iter->Next();
+ num_entries_left--;
+ }
+ if (num_entries_left <= 0) {
+ break;
+ }
+ }
+ }
+ if (num_entries_left <= 0) {
+ break;
+ }
+ }
+ assert(num_entries_left >= 0);
+ if (num_entries_left <= 0) {
+ ss << "(results may not be complete)\n";
+ }
+
+ *out_str = ss.str();
+ return Status::OK();
+}
+
+Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props,
+ int level) {
+ for (const auto& file_meta : storage_info_.files_[level]) {
+ auto fname =
+ TableFileName(cfd_->ioptions()->cf_paths, file_meta->fd.GetNumber(),
+ file_meta->fd.GetPathId());
+ // 1. If the table is already present in table cache, load table
+ // properties from there.
+ std::shared_ptr<const TableProperties> table_properties;
+ Status s = GetTableProperties(&table_properties, file_meta, &fname);
+ if (s.ok()) {
+ props->insert({fname, table_properties});
+ } else {
+ return s;
+ }
+ }
+
+ return Status::OK();
+}
+
+Status Version::GetPropertiesOfTablesInRange(
+ const Range* range, std::size_t n, TablePropertiesCollection* props) const {
+ for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) {
+ for (decltype(n) i = 0; i < n; i++) {
+ // Convert user_key into a corresponding internal key.
+ InternalKey k1(range[i].start, kMaxSequenceNumber, kValueTypeForSeek);
+ InternalKey k2(range[i].limit, kMaxSequenceNumber, kValueTypeForSeek);
+ std::vector<FileMetaData*> files;
+ storage_info_.GetOverlappingInputs(level, &k1, &k2, &files, -1, nullptr,
+ false);
+ for (const auto& file_meta : files) {
+ auto fname =
+ TableFileName(cfd_->ioptions()->cf_paths,
+ file_meta->fd.GetNumber(), file_meta->fd.GetPathId());
+ if (props->count(fname) == 0) {
+ // 1. If the table is already present in table cache, load table
+ // properties from there.
+ std::shared_ptr<const TableProperties> table_properties;
+ Status s = GetTableProperties(&table_properties, file_meta, &fname);
+ if (s.ok()) {
+ props->insert({fname, table_properties});
+ } else {
+ return s;
+ }
+ }
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
+Status Version::GetAggregatedTableProperties(
+ std::shared_ptr<const TableProperties>* tp, int level) {
+ TablePropertiesCollection props;
+ Status s;
+ if (level < 0) {
+ s = GetPropertiesOfAllTables(&props);
+ } else {
+ s = GetPropertiesOfAllTables(&props, level);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ auto* new_tp = new TableProperties();
+ for (const auto& item : props) {
+ new_tp->Add(*item.second);
+ }
+ tp->reset(new_tp);
+ return Status::OK();
+}
+
+size_t Version::GetMemoryUsageByTableReaders() {
+ size_t total_usage = 0;
+ for (auto& file_level : storage_info_.level_files_brief_) {
+ for (size_t i = 0; i < file_level.num_files; i++) {
+ total_usage += cfd_->table_cache()->GetMemoryUsageByTableReader(
+ file_options_, cfd_->internal_comparator(), file_level.files[i].fd,
+ mutable_cf_options_.prefix_extractor.get());
+ }
+ }
+ return total_usage;
+}
+
+void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
+ assert(cf_meta);
+ assert(cfd_);
+
+ cf_meta->name = cfd_->GetName();
+ cf_meta->size = 0;
+ cf_meta->file_count = 0;
+ cf_meta->levels.clear();
+
+ auto* ioptions = cfd_->ioptions();
+ auto* vstorage = storage_info();
+
+ for (int level = 0; level < cfd_->NumberLevels(); level++) {
+ uint64_t level_size = 0;
+ cf_meta->file_count += vstorage->LevelFiles(level).size();
+ std::vector<SstFileMetaData> files;
+ for (const auto& file : vstorage->LevelFiles(level)) {
+ uint32_t path_id = file->fd.GetPathId();
+ std::string file_path;
+ if (path_id < ioptions->cf_paths.size()) {
+ file_path = ioptions->cf_paths[path_id].path;
+ } else {
+ assert(!ioptions->cf_paths.empty());
+ file_path = ioptions->cf_paths.back().path;
+ }
+ const uint64_t file_number = file->fd.GetNumber();
+ files.emplace_back(SstFileMetaData{
+ MakeTableFileName("", file_number), file_number, file_path,
+ static_cast<size_t>(file->fd.GetFileSize()), file->fd.smallest_seqno,
+ file->fd.largest_seqno, file->smallest.user_key().ToString(),
+ file->largest.user_key().ToString(),
+ file->stats.num_reads_sampled.load(std::memory_order_relaxed),
+ file->being_compacted, file->oldest_blob_file_number,
+ file->TryGetOldestAncesterTime(), file->TryGetFileCreationTime(),
+ file->file_checksum, file->file_checksum_func_name});
+ files.back().num_entries = file->num_entries;
+ files.back().num_deletions = file->num_deletions;
+ level_size += file->fd.GetFileSize();
+ }
+ cf_meta->levels.emplace_back(
+ level, level_size, std::move(files));
+ cf_meta->size += level_size;
+ }
+}
+
+uint64_t Version::GetSstFilesSize() {
+ uint64_t sst_files_size = 0;
+ for (int level = 0; level < storage_info_.num_levels_; level++) {
+ for (const auto& file_meta : storage_info_.LevelFiles(level)) {
+ sst_files_size += file_meta->fd.GetFileSize();
+ }
+ }
+ return sst_files_size;
+}
+
+void Version::GetCreationTimeOfOldestFile(uint64_t* creation_time) {
+ uint64_t oldest_time = port::kMaxUint64;
+ for (int level = 0; level < storage_info_.num_non_empty_levels_; level++) {
+ for (FileMetaData* meta : storage_info_.LevelFiles(level)) {
+ assert(meta->fd.table_reader != nullptr);
+ uint64_t file_creation_time = meta->TryGetFileCreationTime();
+ if (file_creation_time == kUnknownFileCreationTime) {
+ *creation_time = 0;
+ return;
+ }
+ if (file_creation_time < oldest_time) {
+ oldest_time = file_creation_time;
+ }
+ }
+ }
+ *creation_time = oldest_time;
+}
+
+uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const {
+ // Estimation will be inaccurate when:
+ // (1) there exist merge keys
+ // (2) keys are directly overwritten
+ // (3) deletion on non-existing keys
+ // (4) low number of samples
+ if (current_num_samples_ == 0) {
+ return 0;
+ }
+
+ if (current_num_non_deletions_ <= current_num_deletions_) {
+ return 0;
+ }
+
+ uint64_t est = current_num_non_deletions_ - current_num_deletions_;
+
+ uint64_t file_count = 0;
+ for (int level = 0; level < num_levels_; ++level) {
+ file_count += files_[level].size();
+ }
+
+ if (current_num_samples_ < file_count) {
+ // casting to avoid overflowing
+ return
+ static_cast<uint64_t>(
+ (est * static_cast<double>(file_count) / current_num_samples_)
+ );
+ } else {
+ return est;
+ }
+}
+
+double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel(
+ int level) const {
+ assert(level < num_levels_);
+ uint64_t sum_file_size_bytes = 0;
+ uint64_t sum_data_size_bytes = 0;
+ for (auto* file_meta : files_[level]) {
+ sum_file_size_bytes += file_meta->fd.GetFileSize();
+ sum_data_size_bytes += file_meta->raw_key_size + file_meta->raw_value_size;
+ }
+ if (sum_file_size_bytes == 0) {
+ return -1.0;
+ }
+ return static_cast<double>(sum_data_size_bytes) / sum_file_size_bytes;
+}
+
+void Version::AddIterators(const ReadOptions& read_options,
+ const FileOptions& soptions,
+ MergeIteratorBuilder* merge_iter_builder,
+ RangeDelAggregator* range_del_agg) {
+ assert(storage_info_.finalized_);
+
+ for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) {
+ AddIteratorsForLevel(read_options, soptions, merge_iter_builder, level,
+ range_del_agg);
+ }
+}
+
+void Version::AddIteratorsForLevel(const ReadOptions& read_options,
+ const FileOptions& soptions,
+ MergeIteratorBuilder* merge_iter_builder,
+ int level,
+ RangeDelAggregator* range_del_agg) {
+ assert(storage_info_.finalized_);
+ if (level >= storage_info_.num_non_empty_levels()) {
+ // This is an empty level
+ return;
+ } else if (storage_info_.LevelFilesBrief(level).num_files == 0) {
+ // No files in this level
+ return;
+ }
+
+ bool should_sample = should_sample_file_read();
+
+ auto* arena = merge_iter_builder->GetArena();
+ if (level == 0) {
+ // Merge all level zero files together since they may overlap
+ for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
+ const auto& file = storage_info_.LevelFilesBrief(0).files[i];
+ merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
+ read_options, soptions, cfd_->internal_comparator(),
+ *file.file_metadata, range_del_agg,
+ mutable_cf_options_.prefix_extractor.get(), nullptr,
+ cfd_->internal_stats()->GetFileReadHist(0),
+ TableReaderCaller::kUserIterator, arena,
+ /*skip_filters=*/false, /*level=*/0,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr));
+ }
+ if (should_sample) {
+ // Count ones for every L0 files. This is done per iterator creation
+ // rather than Seek(), while files in other levels are recored per seek.
+ // If users execute one range query per iterator, there may be some
+ // discrepancy here.
+ for (FileMetaData* meta : storage_info_.LevelFiles(0)) {
+ sample_file_read_inc(meta);
+ }
+ }
+ } else if (storage_info_.LevelFilesBrief(level).num_files > 0) {
+ // For levels > 0, we can use a concatenating iterator that sequentially
+ // walks through the non-overlapping files in the level, opening them
+ // lazily.
+ auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
+ merge_iter_builder->AddIterator(new (mem) LevelIterator(
+ cfd_->table_cache(), read_options, soptions,
+ cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
+ mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(),
+ cfd_->internal_stats()->GetFileReadHist(level),
+ TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
+ range_del_agg, /*largest_compaction_key=*/nullptr));
+ }
+}
+
+Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
+ const FileOptions& file_options,
+ const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ int level, bool* overlap) {
+ assert(storage_info_.finalized_);
+
+ auto icmp = cfd_->internal_comparator();
+ auto ucmp = icmp.user_comparator();
+
+ Arena arena;
+ Status status;
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+
+ *overlap = false;
+
+ if (level == 0) {
+ for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
+ const auto file = &storage_info_.LevelFilesBrief(0).files[i];
+ if (AfterFile(ucmp, &smallest_user_key, file) ||
+ BeforeFile(ucmp, &largest_user_key, file)) {
+ continue;
+ }
+ ScopedArenaIterator iter(cfd_->table_cache()->NewIterator(
+ read_options, file_options, cfd_->internal_comparator(),
+ *file->file_metadata, &range_del_agg,
+ mutable_cf_options_.prefix_extractor.get(), nullptr,
+ cfd_->internal_stats()->GetFileReadHist(0),
+ TableReaderCaller::kUserIterator, &arena,
+ /*skip_filters=*/false, /*level=*/0,
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr));
+ status = OverlapWithIterator(
+ ucmp, smallest_user_key, largest_user_key, iter.get(), overlap);
+ if (!status.ok() || *overlap) {
+ break;
+ }
+ }
+ } else if (storage_info_.LevelFilesBrief(level).num_files > 0) {
+ auto mem = arena.AllocateAligned(sizeof(LevelIterator));
+ ScopedArenaIterator iter(new (mem) LevelIterator(
+ cfd_->table_cache(), read_options, file_options,
+ cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
+ mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(),
+ cfd_->internal_stats()->GetFileReadHist(level),
+ TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
+ &range_del_agg));
+ status = OverlapWithIterator(
+ ucmp, smallest_user_key, largest_user_key, iter.get(), overlap);
+ }
+
+ if (status.ok() && *overlap == false &&
+ range_del_agg.IsRangeOverlapped(smallest_user_key, largest_user_key)) {
+ *overlap = true;
+ }
+ return status;
+}
+
+VersionStorageInfo::VersionStorageInfo(
+ const InternalKeyComparator* internal_comparator,
+ const Comparator* user_comparator, int levels,
+ CompactionStyle compaction_style, VersionStorageInfo* ref_vstorage,
+ bool _force_consistency_checks)
+ : internal_comparator_(internal_comparator),
+ user_comparator_(user_comparator),
+ // cfd is nullptr if Version is dummy
+ num_levels_(levels),
+ num_non_empty_levels_(0),
+ file_indexer_(user_comparator),
+ compaction_style_(compaction_style),
+ files_(new std::vector<FileMetaData*>[num_levels_]),
+ base_level_(num_levels_ == 1 ? -1 : 1),
+ level_multiplier_(0.0),
+ files_by_compaction_pri_(num_levels_),
+ level0_non_overlapping_(false),
+ next_file_to_compact_by_size_(num_levels_),
+ compaction_score_(num_levels_),
+ compaction_level_(num_levels_),
+ l0_delay_trigger_count_(0),
+ accumulated_file_size_(0),
+ accumulated_raw_key_size_(0),
+ accumulated_raw_value_size_(0),
+ accumulated_num_non_deletions_(0),
+ accumulated_num_deletions_(0),
+ current_num_non_deletions_(0),
+ current_num_deletions_(0),
+ current_num_samples_(0),
+ estimated_compaction_needed_bytes_(0),
+ finalized_(false),
+ force_consistency_checks_(_force_consistency_checks) {
+ if (ref_vstorage != nullptr) {
+ accumulated_file_size_ = ref_vstorage->accumulated_file_size_;
+ accumulated_raw_key_size_ = ref_vstorage->accumulated_raw_key_size_;
+ accumulated_raw_value_size_ = ref_vstorage->accumulated_raw_value_size_;
+ accumulated_num_non_deletions_ =
+ ref_vstorage->accumulated_num_non_deletions_;
+ accumulated_num_deletions_ = ref_vstorage->accumulated_num_deletions_;
+ current_num_non_deletions_ = ref_vstorage->current_num_non_deletions_;
+ current_num_deletions_ = ref_vstorage->current_num_deletions_;
+ current_num_samples_ = ref_vstorage->current_num_samples_;
+ oldest_snapshot_seqnum_ = ref_vstorage->oldest_snapshot_seqnum_;
+ }
+}
+
+Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
+ const FileOptions& file_opt,
+ const MutableCFOptions mutable_cf_options,
+ uint64_t version_number)
+ : env_(vset->env_),
+ cfd_(column_family_data),
+ info_log_((cfd_ == nullptr) ? nullptr : cfd_->ioptions()->info_log),
+ db_statistics_((cfd_ == nullptr) ? nullptr
+ : cfd_->ioptions()->statistics),
+ table_cache_((cfd_ == nullptr) ? nullptr : cfd_->table_cache()),
+ merge_operator_((cfd_ == nullptr) ? nullptr
+ : cfd_->ioptions()->merge_operator),
+ storage_info_(
+ (cfd_ == nullptr) ? nullptr : &cfd_->internal_comparator(),
+ (cfd_ == nullptr) ? nullptr : cfd_->user_comparator(),
+ cfd_ == nullptr ? 0 : cfd_->NumberLevels(),
+ cfd_ == nullptr ? kCompactionStyleLevel
+ : cfd_->ioptions()->compaction_style,
+ (cfd_ == nullptr || cfd_->current() == nullptr)
+ ? nullptr
+ : cfd_->current()->storage_info(),
+ cfd_ == nullptr ? false : cfd_->ioptions()->force_consistency_checks),
+ vset_(vset),
+ next_(this),
+ prev_(this),
+ refs_(0),
+ file_options_(file_opt),
+ mutable_cf_options_(mutable_cf_options),
+ version_number_(version_number) {}
+
+void Version::Get(const ReadOptions& read_options, const LookupKey& k,
+ PinnableSlice* value, Status* status,
+ MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq, bool* value_found,
+ bool* key_exists, SequenceNumber* seq, ReadCallback* callback,
+ bool* is_blob, bool do_merge) {
+ Slice ikey = k.internal_key();
+ Slice user_key = k.user_key();
+
+ assert(status->ok() || status->IsMergeInProgress());
+
+ if (key_exists != nullptr) {
+ // will falsify below if not found
+ *key_exists = true;
+ }
+
+ PinnedIteratorsManager pinned_iters_mgr;
+ uint64_t tracing_get_id = BlockCacheTraceHelper::kReservedGetId;
+ if (vset_ && vset_->block_cache_tracer_ &&
+ vset_->block_cache_tracer_->is_tracing_enabled()) {
+ tracing_get_id = vset_->block_cache_tracer_->NextGetId();
+ }
+ GetContext get_context(
+ user_comparator(), merge_operator_, info_log_, db_statistics_,
+ status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key,
+ do_merge ? value : nullptr, value_found, merge_context, do_merge,
+ max_covering_tombstone_seq, this->env_, seq,
+ merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
+ tracing_get_id);
+
+ // Pin blocks that we read to hold merge operands
+ if (merge_operator_) {
+ pinned_iters_mgr.StartPinning();
+ }
+
+ FilePicker fp(
+ storage_info_.files_, user_key, ikey, &storage_info_.level_files_brief_,
+ storage_info_.num_non_empty_levels_, &storage_info_.file_indexer_,
+ user_comparator(), internal_comparator());
+ FdWithKeyRange* f = fp.GetNextFile();
+
+ while (f != nullptr) {
+ if (*max_covering_tombstone_seq > 0) {
+ // The remaining files we look at will only contain covered keys, so we
+ // stop here.
+ break;
+ }
+ if (get_context.sample()) {
+ sample_file_read_inc(f->file_metadata);
+ }
+
+ bool timer_enabled =
+ GetPerfLevel() >= PerfLevel::kEnableTimeExceptForMutex &&
+ get_perf_context()->per_level_perf_context_enabled;
+ StopWatchNano timer(env_, timer_enabled /* auto_start */);
+ *status = table_cache_->Get(
+ read_options, *internal_comparator(), *f->file_metadata, ikey,
+ &get_context, mutable_cf_options_.prefix_extractor.get(),
+ cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
+ IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()),
+ fp.IsHitFileLastInLevel()),
+ fp.GetCurrentLevel());
+ // TODO: examine the behavior for corrupted key
+ if (timer_enabled) {
+ PERF_COUNTER_BY_LEVEL_ADD(get_from_table_nanos, timer.ElapsedNanos(),
+ fp.GetCurrentLevel());
+ }
+ if (!status->ok()) {
+ return;
+ }
+
+ // report the counters before returning
+ if (get_context.State() != GetContext::kNotFound &&
+ get_context.State() != GetContext::kMerge &&
+ db_statistics_ != nullptr) {
+ get_context.ReportCounters();
+ }
+ switch (get_context.State()) {
+ case GetContext::kNotFound:
+ // Keep searching in other files
+ break;
+ case GetContext::kMerge:
+ // TODO: update per-level perfcontext user_key_return_count for kMerge
+ break;
+ case GetContext::kFound:
+ if (fp.GetHitFileLevel() == 0) {
+ RecordTick(db_statistics_, GET_HIT_L0);
+ } else if (fp.GetHitFileLevel() == 1) {
+ RecordTick(db_statistics_, GET_HIT_L1);
+ } else if (fp.GetHitFileLevel() >= 2) {
+ RecordTick(db_statistics_, GET_HIT_L2_AND_UP);
+ }
+ PERF_COUNTER_BY_LEVEL_ADD(user_key_return_count, 1,
+ fp.GetHitFileLevel());
+ return;
+ case GetContext::kDeleted:
+ // Use empty error message for speed
+ *status = Status::NotFound();
+ return;
+ case GetContext::kCorrupt:
+ *status = Status::Corruption("corrupted key for ", user_key);
+ return;
+ case GetContext::kBlobIndex:
+ ROCKS_LOG_ERROR(info_log_, "Encounter unexpected blob index.");
+ *status = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ return;
+ }
+ f = fp.GetNextFile();
+ }
+ if (db_statistics_ != nullptr) {
+ get_context.ReportCounters();
+ }
+ if (GetContext::kMerge == get_context.State()) {
+ if (!do_merge) {
+ *status = Status::OK();
+ return;
+ }
+ if (!merge_operator_) {
+ *status = Status::InvalidArgument(
+ "merge_operator is not properly initialized.");
+ return;
+ }
+ // merge_operands are in saver and we hit the beginning of the key history
+ // do a final merge of nullptr and operands;
+ std::string* str_value = value != nullptr ? value->GetSelf() : nullptr;
+ *status = MergeHelper::TimedFullMerge(
+ merge_operator_, user_key, nullptr, merge_context->GetOperands(),
+ str_value, info_log_, db_statistics_, env_,
+ nullptr /* result_operand */, true);
+ if (LIKELY(value != nullptr)) {
+ value->PinSelf();
+ }
+ } else {
+ if (key_exists != nullptr) {
+ *key_exists = false;
+ }
+ *status = Status::NotFound(); // Use an empty error message for speed
+ }
+}
+
+void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
+ ReadCallback* callback, bool* is_blob) {
+ PinnedIteratorsManager pinned_iters_mgr;
+
+ // Pin blocks that we read to hold merge operands
+ if (merge_operator_) {
+ pinned_iters_mgr.StartPinning();
+ }
+ uint64_t tracing_mget_id = BlockCacheTraceHelper::kReservedGetId;
+
+ if (vset_ && vset_->block_cache_tracer_ &&
+ vset_->block_cache_tracer_->is_tracing_enabled()) {
+ tracing_mget_id = vset_->block_cache_tracer_->NextGetId();
+ }
+ // Even though we know the batch size won't be > MAX_BATCH_SIZE,
+ // use autovector in order to avoid unnecessary construction of GetContext
+ // objects, which is expensive
+ autovector<GetContext, 16> get_ctx;
+ for (auto iter = range->begin(); iter != range->end(); ++iter) {
+ assert(iter->s->ok() || iter->s->IsMergeInProgress());
+ get_ctx.emplace_back(
+ user_comparator(), merge_operator_, info_log_, db_statistics_,
+ iter->s->ok() ? GetContext::kNotFound : GetContext::kMerge, iter->ukey,
+ iter->value, nullptr, &(iter->merge_context), true,
+ &iter->max_covering_tombstone_seq, this->env_, nullptr,
+ merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
+ tracing_mget_id);
+ // MergeInProgress status, if set, has been transferred to the get_context
+ // state, so we set status to ok here. From now on, the iter status will
+ // be used for IO errors, and get_context state will be used for any
+ // key level errors
+ *(iter->s) = Status::OK();
+ }
+ int get_ctx_index = 0;
+ for (auto iter = range->begin(); iter != range->end();
+ ++iter, get_ctx_index++) {
+ iter->get_context = &(get_ctx[get_ctx_index]);
+ }
+
+ MultiGetRange file_picker_range(*range, range->begin(), range->end());
+ FilePickerMultiGet fp(
+ &file_picker_range,
+ &storage_info_.level_files_brief_, storage_info_.num_non_empty_levels_,
+ &storage_info_.file_indexer_, user_comparator(), internal_comparator());
+ FdWithKeyRange* f = fp.GetNextFile();
+
+ while (f != nullptr) {
+ MultiGetRange file_range = fp.CurrentFileRange();
+ bool timer_enabled =
+ GetPerfLevel() >= PerfLevel::kEnableTimeExceptForMutex &&
+ get_perf_context()->per_level_perf_context_enabled;
+ StopWatchNano timer(env_, timer_enabled /* auto_start */);
+ Status s = table_cache_->MultiGet(
+ read_options, *internal_comparator(), *f->file_metadata, &file_range,
+ mutable_cf_options_.prefix_extractor.get(),
+ cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
+ IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()),
+ fp.IsHitFileLastInLevel()),
+ fp.GetCurrentLevel());
+ // TODO: examine the behavior for corrupted key
+ if (timer_enabled) {
+ PERF_COUNTER_BY_LEVEL_ADD(get_from_table_nanos, timer.ElapsedNanos(),
+ fp.GetCurrentLevel());
+ }
+ if (!s.ok()) {
+ // TODO: Set status for individual keys appropriately
+ for (auto iter = file_range.begin(); iter != file_range.end(); ++iter) {
+ *iter->s = s;
+ file_range.MarkKeyDone(iter);
+ }
+ return;
+ }
+ uint64_t batch_size = 0;
+ for (auto iter = file_range.begin(); iter != file_range.end(); ++iter) {
+ GetContext& get_context = *iter->get_context;
+ Status* status = iter->s;
+ // The Status in the KeyContext takes precedence over GetContext state
+ // Status may be an error if there were any IO errors in the table
+ // reader. We never expect Status to be NotFound(), as that is
+ // determined by get_context
+ assert(!status->IsNotFound());
+ if (!status->ok()) {
+ file_range.MarkKeyDone(iter);
+ continue;
+ }
+
+ if (get_context.sample()) {
+ sample_file_read_inc(f->file_metadata);
+ }
+ batch_size++;
+ // report the counters before returning
+ if (get_context.State() != GetContext::kNotFound &&
+ get_context.State() != GetContext::kMerge &&
+ db_statistics_ != nullptr) {
+ get_context.ReportCounters();
+ } else {
+ if (iter->max_covering_tombstone_seq > 0) {
+ // The remaining files we look at will only contain covered keys, so
+ // we stop here for this key
+ file_picker_range.SkipKey(iter);
+ }
+ }
+ switch (get_context.State()) {
+ case GetContext::kNotFound:
+ // Keep searching in other files
+ break;
+ case GetContext::kMerge:
+ // TODO: update per-level perfcontext user_key_return_count for kMerge
+ break;
+ case GetContext::kFound:
+ if (fp.GetHitFileLevel() == 0) {
+ RecordTick(db_statistics_, GET_HIT_L0);
+ } else if (fp.GetHitFileLevel() == 1) {
+ RecordTick(db_statistics_, GET_HIT_L1);
+ } else if (fp.GetHitFileLevel() >= 2) {
+ RecordTick(db_statistics_, GET_HIT_L2_AND_UP);
+ }
+ PERF_COUNTER_BY_LEVEL_ADD(user_key_return_count, 1,
+ fp.GetHitFileLevel());
+ file_range.MarkKeyDone(iter);
+ continue;
+ case GetContext::kDeleted:
+ // Use empty error message for speed
+ *status = Status::NotFound();
+ file_range.MarkKeyDone(iter);
+ continue;
+ case GetContext::kCorrupt:
+ *status =
+ Status::Corruption("corrupted key for ", iter->lkey->user_key());
+ file_range.MarkKeyDone(iter);
+ continue;
+ case GetContext::kBlobIndex:
+ ROCKS_LOG_ERROR(info_log_, "Encounter unexpected blob index.");
+ *status = Status::NotSupported(
+ "Encounter unexpected blob index. Please open DB with "
+ "ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
+ file_range.MarkKeyDone(iter);
+ continue;
+ }
+ }
+ RecordInHistogram(db_statistics_, SST_BATCH_SIZE, batch_size);
+ if (file_picker_range.empty()) {
+ break;
+ }
+ f = fp.GetNextFile();
+ }
+
+ // Process any left over keys
+ for (auto iter = range->begin(); iter != range->end(); ++iter) {
+ GetContext& get_context = *iter->get_context;
+ Status* status = iter->s;
+ Slice user_key = iter->lkey->user_key();
+
+ if (db_statistics_ != nullptr) {
+ get_context.ReportCounters();
+ }
+ if (GetContext::kMerge == get_context.State()) {
+ if (!merge_operator_) {
+ *status = Status::InvalidArgument(
+ "merge_operator is not properly initialized.");
+ range->MarkKeyDone(iter);
+ continue;
+ }
+ // merge_operands are in saver and we hit the beginning of the key history
+ // do a final merge of nullptr and operands;
+ std::string* str_value =
+ iter->value != nullptr ? iter->value->GetSelf() : nullptr;
+ *status = MergeHelper::TimedFullMerge(
+ merge_operator_, user_key, nullptr, iter->merge_context.GetOperands(),
+ str_value, info_log_, db_statistics_, env_,
+ nullptr /* result_operand */, true);
+ if (LIKELY(iter->value != nullptr)) {
+ iter->value->PinSelf();
+ }
+ } else {
+ range->MarkKeyDone(iter);
+ *status = Status::NotFound(); // Use an empty error message for speed
+ }
+ }
+}
+
+bool Version::IsFilterSkipped(int level, bool is_file_last_in_level) {
+ // Reaching the bottom level implies misses at all upper levels, so we'll
+ // skip checking the filters when we predict a hit.
+ return cfd_->ioptions()->optimize_filters_for_hits &&
+ (level > 0 || is_file_last_in_level) &&
+ level == storage_info_.num_non_empty_levels() - 1;
+}
+
+void VersionStorageInfo::GenerateLevelFilesBrief() {
+ level_files_brief_.resize(num_non_empty_levels_);
+ for (int level = 0; level < num_non_empty_levels_; level++) {
+ DoGenerateLevelFilesBrief(
+ &level_files_brief_[level], files_[level], &arena_);
+ }
+}
+
+void Version::PrepareApply(
+ const MutableCFOptions& mutable_cf_options,
+ bool update_stats) {
+ UpdateAccumulatedStats(update_stats);
+ storage_info_.UpdateNumNonEmptyLevels();
+ storage_info_.CalculateBaseBytes(*cfd_->ioptions(), mutable_cf_options);
+ storage_info_.UpdateFilesByCompactionPri(cfd_->ioptions()->compaction_pri);
+ storage_info_.GenerateFileIndexer();
+ storage_info_.GenerateLevelFilesBrief();
+ storage_info_.GenerateLevel0NonOverlapping();
+ storage_info_.GenerateBottommostFiles();
+}
+
+bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) {
+ if (file_meta->init_stats_from_file ||
+ file_meta->compensated_file_size > 0) {
+ return false;
+ }
+ std::shared_ptr<const TableProperties> tp;
+ Status s = GetTableProperties(&tp, file_meta);
+ file_meta->init_stats_from_file = true;
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(vset_->db_options_->info_log,
+ "Unable to load table properties for file %" PRIu64
+ " --- %s\n",
+ file_meta->fd.GetNumber(), s.ToString().c_str());
+ return false;
+ }
+ if (tp.get() == nullptr) return false;
+ file_meta->num_entries = tp->num_entries;
+ file_meta->num_deletions = tp->num_deletions;
+ file_meta->raw_value_size = tp->raw_value_size;
+ file_meta->raw_key_size = tp->raw_key_size;
+
+ return true;
+}
+
+void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) {
+ TEST_SYNC_POINT_CALLBACK("VersionStorageInfo::UpdateAccumulatedStats",
+ nullptr);
+
+ assert(file_meta->init_stats_from_file);
+ accumulated_file_size_ += file_meta->fd.GetFileSize();
+ accumulated_raw_key_size_ += file_meta->raw_key_size;
+ accumulated_raw_value_size_ += file_meta->raw_value_size;
+ accumulated_num_non_deletions_ +=
+ file_meta->num_entries - file_meta->num_deletions;
+ accumulated_num_deletions_ += file_meta->num_deletions;
+
+ current_num_non_deletions_ +=
+ file_meta->num_entries - file_meta->num_deletions;
+ current_num_deletions_ += file_meta->num_deletions;
+ current_num_samples_++;
+}
+
+void VersionStorageInfo::RemoveCurrentStats(FileMetaData* file_meta) {
+ if (file_meta->init_stats_from_file) {
+ current_num_non_deletions_ -=
+ file_meta->num_entries - file_meta->num_deletions;
+ current_num_deletions_ -= file_meta->num_deletions;
+ current_num_samples_--;
+ }
+}
+
+void Version::UpdateAccumulatedStats(bool update_stats) {
+ if (update_stats) {
+ // maximum number of table properties loaded from files.
+ const int kMaxInitCount = 20;
+ int init_count = 0;
+ // here only the first kMaxInitCount files which haven't been
+ // initialized from file will be updated with num_deletions.
+ // The motivation here is to cap the maximum I/O per Version creation.
+ // The reason for choosing files from lower-level instead of higher-level
+ // is that such design is able to propagate the initialization from
+ // lower-level to higher-level: When the num_deletions of lower-level
+ // files are updated, it will make the lower-level files have accurate
+ // compensated_file_size, making lower-level to higher-level compaction
+ // will be triggered, which creates higher-level files whose num_deletions
+ // will be updated here.
+ for (int level = 0;
+ level < storage_info_.num_levels_ && init_count < kMaxInitCount;
+ ++level) {
+ for (auto* file_meta : storage_info_.files_[level]) {
+ if (MaybeInitializeFileMetaData(file_meta)) {
+ // each FileMeta will be initialized only once.
+ storage_info_.UpdateAccumulatedStats(file_meta);
+ // when option "max_open_files" is -1, all the file metadata has
+ // already been read, so MaybeInitializeFileMetaData() won't incur
+ // any I/O cost. "max_open_files=-1" means that the table cache passed
+ // to the VersionSet and then to the ColumnFamilySet has a size of
+ // TableCache::kInfiniteCapacity
+ if (vset_->GetColumnFamilySet()->get_table_cache()->GetCapacity() ==
+ TableCache::kInfiniteCapacity) {
+ continue;
+ }
+ if (++init_count >= kMaxInitCount) {
+ break;
+ }
+ }
+ }
+ }
+ // In case all sampled-files contain only deletion entries, then we
+ // load the table-property of a file in higher-level to initialize
+ // that value.
+ for (int level = storage_info_.num_levels_ - 1;
+ storage_info_.accumulated_raw_value_size_ == 0 && level >= 0;
+ --level) {
+ for (int i = static_cast<int>(storage_info_.files_[level].size()) - 1;
+ storage_info_.accumulated_raw_value_size_ == 0 && i >= 0; --i) {
+ if (MaybeInitializeFileMetaData(storage_info_.files_[level][i])) {
+ storage_info_.UpdateAccumulatedStats(storage_info_.files_[level][i]);
+ }
+ }
+ }
+ }
+
+ storage_info_.ComputeCompensatedSizes();
+}
+
+void VersionStorageInfo::ComputeCompensatedSizes() {
+ static const int kDeletionWeightOnCompaction = 2;
+ uint64_t average_value_size = GetAverageValueSize();
+
+ // compute the compensated size
+ for (int level = 0; level < num_levels_; level++) {
+ for (auto* file_meta : files_[level]) {
+ // Here we only compute compensated_file_size for those file_meta
+ // which compensated_file_size is uninitialized (== 0). This is true only
+ // for files that have been created right now and no other thread has
+ // access to them. That's why we can safely mutate compensated_file_size.
+ if (file_meta->compensated_file_size == 0) {
+ file_meta->compensated_file_size = file_meta->fd.GetFileSize();
+ // Here we only boost the size of deletion entries of a file only
+ // when the number of deletion entries is greater than the number of
+ // non-deletion entries in the file. The motivation here is that in
+ // a stable workload, the number of deletion entries should be roughly
+ // equal to the number of non-deletion entries. If we compensate the
+ // size of deletion entries in a stable workload, the deletion
+ // compensation logic might introduce unwanted effet which changes the
+ // shape of LSM tree.
+ if (file_meta->num_deletions * 2 >= file_meta->num_entries) {
+ file_meta->compensated_file_size +=
+ (file_meta->num_deletions * 2 - file_meta->num_entries) *
+ average_value_size * kDeletionWeightOnCompaction;
+ }
+ }
+ }
+ }
+}
+
+int VersionStorageInfo::MaxInputLevel() const {
+ if (compaction_style_ == kCompactionStyleLevel) {
+ return num_levels() - 2;
+ }
+ return 0;
+}
+
+int VersionStorageInfo::MaxOutputLevel(bool allow_ingest_behind) const {
+ if (allow_ingest_behind) {
+ assert(num_levels() > 1);
+ return num_levels() - 2;
+ }
+ return num_levels() - 1;
+}
+
+void VersionStorageInfo::EstimateCompactionBytesNeeded(
+ const MutableCFOptions& mutable_cf_options) {
+ // Only implemented for level-based compaction
+ if (compaction_style_ != kCompactionStyleLevel) {
+ estimated_compaction_needed_bytes_ = 0;
+ return;
+ }
+
+ // Start from Level 0, if level 0 qualifies compaction to level 1,
+ // we estimate the size of compaction.
+ // Then we move on to the next level and see whether it qualifies compaction
+ // to the next level. The size of the level is estimated as the actual size
+ // on the level plus the input bytes from the previous level if there is any.
+ // If it exceeds, take the exceeded bytes as compaction input and add the size
+ // of the compaction size to tatal size.
+ // We keep doing it to Level 2, 3, etc, until the last level and return the
+ // accumulated bytes.
+
+ uint64_t bytes_compact_to_next_level = 0;
+ uint64_t level_size = 0;
+ for (auto* f : files_[0]) {
+ level_size += f->fd.GetFileSize();
+ }
+ // Level 0
+ bool level0_compact_triggered = false;
+ if (static_cast<int>(files_[0].size()) >=
+ mutable_cf_options.level0_file_num_compaction_trigger ||
+ level_size >= mutable_cf_options.max_bytes_for_level_base) {
+ level0_compact_triggered = true;
+ estimated_compaction_needed_bytes_ = level_size;
+ bytes_compact_to_next_level = level_size;
+ } else {
+ estimated_compaction_needed_bytes_ = 0;
+ }
+
+ // Level 1 and up.
+ uint64_t bytes_next_level = 0;
+ for (int level = base_level(); level <= MaxInputLevel(); level++) {
+ level_size = 0;
+ if (bytes_next_level > 0) {
+#ifndef NDEBUG
+ uint64_t level_size2 = 0;
+ for (auto* f : files_[level]) {
+ level_size2 += f->fd.GetFileSize();
+ }
+ assert(level_size2 == bytes_next_level);
+#endif
+ level_size = bytes_next_level;
+ bytes_next_level = 0;
+ } else {
+ for (auto* f : files_[level]) {
+ level_size += f->fd.GetFileSize();
+ }
+ }
+ if (level == base_level() && level0_compact_triggered) {
+ // Add base level size to compaction if level0 compaction triggered.
+ estimated_compaction_needed_bytes_ += level_size;
+ }
+ // Add size added by previous compaction
+ level_size += bytes_compact_to_next_level;
+ bytes_compact_to_next_level = 0;
+ uint64_t level_target = MaxBytesForLevel(level);
+ if (level_size > level_target) {
+ bytes_compact_to_next_level = level_size - level_target;
+ // Estimate the actual compaction fan-out ratio as size ratio between
+ // the two levels.
+
+ assert(bytes_next_level == 0);
+ if (level + 1 < num_levels_) {
+ for (auto* f : files_[level + 1]) {
+ bytes_next_level += f->fd.GetFileSize();
+ }
+ }
+ if (bytes_next_level > 0) {
+ assert(level_size > 0);
+ estimated_compaction_needed_bytes_ += static_cast<uint64_t>(
+ static_cast<double>(bytes_compact_to_next_level) *
+ (static_cast<double>(bytes_next_level) /
+ static_cast<double>(level_size) +
+ 1));
+ }
+ }
+ }
+}
+
+namespace {
+uint32_t GetExpiredTtlFilesCount(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options,
+ const std::vector<FileMetaData*>& files) {
+ uint32_t ttl_expired_files_count = 0;
+
+ int64_t _current_time;
+ auto status = ioptions.env->GetCurrentTime(&_current_time);
+ if (status.ok()) {
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+ for (FileMetaData* f : files) {
+ if (!f->being_compacted) {
+ uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
+ if (oldest_ancester_time != 0 &&
+ oldest_ancester_time < (current_time - mutable_cf_options.ttl)) {
+ ttl_expired_files_count++;
+ }
+ }
+ }
+ }
+ return ttl_expired_files_count;
+}
+} // anonymous namespace
+
+void VersionStorageInfo::ComputeCompactionScore(
+ const ImmutableCFOptions& immutable_cf_options,
+ const MutableCFOptions& mutable_cf_options) {
+ for (int level = 0; level <= MaxInputLevel(); level++) {
+ double score;
+ if (level == 0) {
+ // We treat level-0 specially by bounding the number of files
+ // instead of number of bytes for two reasons:
+ //
+ // (1) With larger write-buffer sizes, it is nice not to do too
+ // many level-0 compactions.
+ //
+ // (2) The files in level-0 are merged on every read and
+ // therefore we wish to avoid too many files when the individual
+ // file size is small (perhaps because of a small write-buffer
+ // setting, or very high compression ratios, or lots of
+ // overwrites/deletions).
+ int num_sorted_runs = 0;
+ uint64_t total_size = 0;
+ for (auto* f : files_[level]) {
+ if (!f->being_compacted) {
+ total_size += f->compensated_file_size;
+ num_sorted_runs++;
+ }
+ }
+ if (compaction_style_ == kCompactionStyleUniversal) {
+ // For universal compaction, we use level0 score to indicate
+ // compaction score for the whole DB. Adding other levels as if
+ // they are L0 files.
+ for (int i = 1; i < num_levels(); i++) {
+ if (!files_[i].empty() && !files_[i][0]->being_compacted) {
+ num_sorted_runs++;
+ }
+ }
+ }
+
+ if (compaction_style_ == kCompactionStyleFIFO) {
+ score = static_cast<double>(total_size) /
+ mutable_cf_options.compaction_options_fifo.max_table_files_size;
+ if (mutable_cf_options.compaction_options_fifo.allow_compaction) {
+ score = std::max(
+ static_cast<double>(num_sorted_runs) /
+ mutable_cf_options.level0_file_num_compaction_trigger,
+ score);
+ }
+ if (mutable_cf_options.ttl > 0) {
+ score = std::max(
+ static_cast<double>(GetExpiredTtlFilesCount(
+ immutable_cf_options, mutable_cf_options, files_[level])),
+ score);
+ }
+
+ } else {
+ score = static_cast<double>(num_sorted_runs) /
+ mutable_cf_options.level0_file_num_compaction_trigger;
+ if (compaction_style_ == kCompactionStyleLevel && num_levels() > 1) {
+ // Level-based involves L0->L0 compactions that can lead to oversized
+ // L0 files. Take into account size as well to avoid later giant
+ // compactions to the base level.
+ score = std::max(
+ score, static_cast<double>(total_size) /
+ mutable_cf_options.max_bytes_for_level_base);
+ }
+ }
+ } else {
+ // Compute the ratio of current size to size limit.
+ uint64_t level_bytes_no_compacting = 0;
+ for (auto f : files_[level]) {
+ if (!f->being_compacted) {
+ level_bytes_no_compacting += f->compensated_file_size;
+ }
+ }
+ score = static_cast<double>(level_bytes_no_compacting) /
+ MaxBytesForLevel(level);
+ }
+ compaction_level_[level] = level;
+ compaction_score_[level] = score;
+ }
+
+ // sort all the levels based on their score. Higher scores get listed
+ // first. Use bubble sort because the number of entries are small.
+ for (int i = 0; i < num_levels() - 2; i++) {
+ for (int j = i + 1; j < num_levels() - 1; j++) {
+ if (compaction_score_[i] < compaction_score_[j]) {
+ double score = compaction_score_[i];
+ int level = compaction_level_[i];
+ compaction_score_[i] = compaction_score_[j];
+ compaction_level_[i] = compaction_level_[j];
+ compaction_score_[j] = score;
+ compaction_level_[j] = level;
+ }
+ }
+ }
+ ComputeFilesMarkedForCompaction();
+ ComputeBottommostFilesMarkedForCompaction();
+ if (mutable_cf_options.ttl > 0) {
+ ComputeExpiredTtlFiles(immutable_cf_options, mutable_cf_options.ttl);
+ }
+ if (mutable_cf_options.periodic_compaction_seconds > 0) {
+ ComputeFilesMarkedForPeriodicCompaction(
+ immutable_cf_options, mutable_cf_options.periodic_compaction_seconds);
+ }
+ EstimateCompactionBytesNeeded(mutable_cf_options);
+}
+
+void VersionStorageInfo::ComputeFilesMarkedForCompaction() {
+ files_marked_for_compaction_.clear();
+ int last_qualify_level = 0;
+
+ // Do not include files from the last level with data
+ // If table properties collector suggests a file on the last level,
+ // we should not move it to a new level.
+ for (int level = num_levels() - 1; level >= 1; level--) {
+ if (!files_[level].empty()) {
+ last_qualify_level = level - 1;
+ break;
+ }
+ }
+
+ for (int level = 0; level <= last_qualify_level; level++) {
+ for (auto* f : files_[level]) {
+ if (!f->being_compacted && f->marked_for_compaction) {
+ files_marked_for_compaction_.emplace_back(level, f);
+ }
+ }
+ }
+}
+
+void VersionStorageInfo::ComputeExpiredTtlFiles(
+ const ImmutableCFOptions& ioptions, const uint64_t ttl) {
+ assert(ttl > 0);
+
+ expired_ttl_files_.clear();
+
+ int64_t _current_time;
+ auto status = ioptions.env->GetCurrentTime(&_current_time);
+ if (!status.ok()) {
+ return;
+ }
+ const uint64_t current_time = static_cast<uint64_t>(_current_time);
+
+ for (int level = 0; level < num_levels() - 1; level++) {
+ for (FileMetaData* f : files_[level]) {
+ if (!f->being_compacted) {
+ uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
+ if (oldest_ancester_time > 0 &&
+ oldest_ancester_time < (current_time - ttl)) {
+ expired_ttl_files_.emplace_back(level, f);
+ }
+ }
+ }
+ }
+}
+
+void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
+ const ImmutableCFOptions& ioptions,
+ const uint64_t periodic_compaction_seconds) {
+ assert(periodic_compaction_seconds > 0);
+
+ files_marked_for_periodic_compaction_.clear();
+
+ int64_t temp_current_time;
+ auto status = ioptions.env->GetCurrentTime(&temp_current_time);
+ if (!status.ok()) {
+ return;
+ }
+ const uint64_t current_time = static_cast<uint64_t>(temp_current_time);
+
+ // If periodic_compaction_seconds is larger than current time, periodic
+ // compaction can't possibly be triggered.
+ if (periodic_compaction_seconds > current_time) {
+ return;
+ }
+
+ const uint64_t allowed_time_limit =
+ current_time - periodic_compaction_seconds;
+
+ for (int level = 0; level < num_levels(); level++) {
+ for (auto f : files_[level]) {
+ if (!f->being_compacted) {
+ // Compute a file's modification time in the following order:
+ // 1. Use file_creation_time table property if it is > 0.
+ // 2. Use creation_time table property if it is > 0.
+ // 3. Use file's mtime metadata if the above two table properties are 0.
+ // Don't consider the file at all if the modification time cannot be
+ // correctly determined based on the above conditions.
+ uint64_t file_modification_time = f->TryGetFileCreationTime();
+ if (file_modification_time == kUnknownFileCreationTime) {
+ file_modification_time = f->TryGetOldestAncesterTime();
+ }
+ if (file_modification_time == kUnknownOldestAncesterTime) {
+ auto file_path = TableFileName(ioptions.cf_paths, f->fd.GetNumber(),
+ f->fd.GetPathId());
+ status = ioptions.env->GetFileModificationTime(
+ file_path, &file_modification_time);
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(ioptions.info_log,
+ "Can't get file modification time: %s: %s",
+ file_path.c_str(), status.ToString().c_str());
+ continue;
+ }
+ }
+ if (file_modification_time > 0 &&
+ file_modification_time < allowed_time_limit) {
+ files_marked_for_periodic_compaction_.emplace_back(level, f);
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+// used to sort files by size
+struct Fsize {
+ size_t index;
+ FileMetaData* file;
+};
+
+// Compator that is used to sort files based on their size
+// In normal mode: descending size
+bool CompareCompensatedSizeDescending(const Fsize& first, const Fsize& second) {
+ return (first.file->compensated_file_size >
+ second.file->compensated_file_size);
+}
+} // anonymous namespace
+
+void VersionStorageInfo::AddFile(int level, FileMetaData* f, Logger* info_log) {
+ auto* level_files = &files_[level];
+ // Must not overlap
+#ifndef NDEBUG
+ if (level > 0 && !level_files->empty() &&
+ internal_comparator_->Compare(
+ (*level_files)[level_files->size() - 1]->largest, f->smallest) >= 0) {
+ auto* f2 = (*level_files)[level_files->size() - 1];
+ if (info_log != nullptr) {
+ Error(info_log, "Adding new file %" PRIu64
+ " range (%s, %s) to level %d but overlapping "
+ "with existing file %" PRIu64 " %s %s",
+ f->fd.GetNumber(), f->smallest.DebugString(true).c_str(),
+ f->largest.DebugString(true).c_str(), level, f2->fd.GetNumber(),
+ f2->smallest.DebugString(true).c_str(),
+ f2->largest.DebugString(true).c_str());
+ LogFlush(info_log);
+ }
+ assert(false);
+ }
+#else
+ (void)info_log;
+#endif
+ f->refs++;
+ level_files->push_back(f);
+}
+
+// Version::PrepareApply() need to be called before calling the function, or
+// following functions called:
+// 1. UpdateNumNonEmptyLevels();
+// 2. CalculateBaseBytes();
+// 3. UpdateFilesByCompactionPri();
+// 4. GenerateFileIndexer();
+// 5. GenerateLevelFilesBrief();
+// 6. GenerateLevel0NonOverlapping();
+// 7. GenerateBottommostFiles();
+void VersionStorageInfo::SetFinalized() {
+ finalized_ = true;
+#ifndef NDEBUG
+ if (compaction_style_ != kCompactionStyleLevel) {
+ // Not level based compaction.
+ return;
+ }
+ assert(base_level_ < 0 || num_levels() == 1 ||
+ (base_level_ >= 1 && base_level_ < num_levels()));
+ // Verify all levels newer than base_level are empty except L0
+ for (int level = 1; level < base_level(); level++) {
+ assert(NumLevelBytes(level) == 0);
+ }
+ uint64_t max_bytes_prev_level = 0;
+ for (int level = base_level(); level < num_levels() - 1; level++) {
+ if (LevelFiles(level).size() == 0) {
+ continue;
+ }
+ assert(MaxBytesForLevel(level) >= max_bytes_prev_level);
+ max_bytes_prev_level = MaxBytesForLevel(level);
+ }
+ int num_empty_non_l0_level = 0;
+ for (int level = 0; level < num_levels(); level++) {
+ assert(LevelFiles(level).size() == 0 ||
+ LevelFiles(level).size() == LevelFilesBrief(level).num_files);
+ if (level > 0 && NumLevelBytes(level) > 0) {
+ num_empty_non_l0_level++;
+ }
+ if (LevelFiles(level).size() > 0) {
+ assert(level < num_non_empty_levels());
+ }
+ }
+ assert(compaction_level_.size() > 0);
+ assert(compaction_level_.size() == compaction_score_.size());
+#endif
+}
+
+void VersionStorageInfo::UpdateNumNonEmptyLevels() {
+ num_non_empty_levels_ = num_levels_;
+ for (int i = num_levels_ - 1; i >= 0; i--) {
+ if (files_[i].size() != 0) {
+ return;
+ } else {
+ num_non_empty_levels_ = i;
+ }
+ }
+}
+
+namespace {
+// Sort `temp` based on ratio of overlapping size over file size
+void SortFileByOverlappingRatio(
+ const InternalKeyComparator& icmp, const std::vector<FileMetaData*>& files,
+ const std::vector<FileMetaData*>& next_level_files,
+ std::vector<Fsize>* temp) {
+ std::unordered_map<uint64_t, uint64_t> file_to_order;
+ auto next_level_it = next_level_files.begin();
+
+ for (auto& file : files) {
+ uint64_t overlapping_bytes = 0;
+ // Skip files in next level that is smaller than current file
+ while (next_level_it != next_level_files.end() &&
+ icmp.Compare((*next_level_it)->largest, file->smallest) < 0) {
+ next_level_it++;
+ }
+
+ while (next_level_it != next_level_files.end() &&
+ icmp.Compare((*next_level_it)->smallest, file->largest) < 0) {
+ overlapping_bytes += (*next_level_it)->fd.file_size;
+
+ if (icmp.Compare((*next_level_it)->largest, file->largest) > 0) {
+ // next level file cross large boundary of current file.
+ break;
+ }
+ next_level_it++;
+ }
+
+ assert(file->compensated_file_size != 0);
+ file_to_order[file->fd.GetNumber()] =
+ overlapping_bytes * 1024u / file->compensated_file_size;
+ }
+
+ std::sort(temp->begin(), temp->end(),
+ [&](const Fsize& f1, const Fsize& f2) -> bool {
+ return file_to_order[f1.file->fd.GetNumber()] <
+ file_to_order[f2.file->fd.GetNumber()];
+ });
+}
+} // namespace
+
+void VersionStorageInfo::UpdateFilesByCompactionPri(
+ CompactionPri compaction_pri) {
+ if (compaction_style_ == kCompactionStyleNone ||
+ compaction_style_ == kCompactionStyleFIFO ||
+ compaction_style_ == kCompactionStyleUniversal) {
+ // don't need this
+ return;
+ }
+ // No need to sort the highest level because it is never compacted.
+ for (int level = 0; level < num_levels() - 1; level++) {
+ const std::vector<FileMetaData*>& files = files_[level];
+ auto& files_by_compaction_pri = files_by_compaction_pri_[level];
+ assert(files_by_compaction_pri.size() == 0);
+
+ // populate a temp vector for sorting based on size
+ std::vector<Fsize> temp(files.size());
+ for (size_t i = 0; i < files.size(); i++) {
+ temp[i].index = i;
+ temp[i].file = files[i];
+ }
+
+ // sort the top number_of_files_to_sort_ based on file size
+ size_t num = VersionStorageInfo::kNumberFilesToSort;
+ if (num > temp.size()) {
+ num = temp.size();
+ }
+ switch (compaction_pri) {
+ case kByCompensatedSize:
+ std::partial_sort(temp.begin(), temp.begin() + num, temp.end(),
+ CompareCompensatedSizeDescending);
+ break;
+ case kOldestLargestSeqFirst:
+ std::sort(temp.begin(), temp.end(),
+ [](const Fsize& f1, const Fsize& f2) -> bool {
+ return f1.file->fd.largest_seqno <
+ f2.file->fd.largest_seqno;
+ });
+ break;
+ case kOldestSmallestSeqFirst:
+ std::sort(temp.begin(), temp.end(),
+ [](const Fsize& f1, const Fsize& f2) -> bool {
+ return f1.file->fd.smallest_seqno <
+ f2.file->fd.smallest_seqno;
+ });
+ break;
+ case kMinOverlappingRatio:
+ SortFileByOverlappingRatio(*internal_comparator_, files_[level],
+ files_[level + 1], &temp);
+ break;
+ default:
+ assert(false);
+ }
+ assert(temp.size() == files.size());
+
+ // initialize files_by_compaction_pri_
+ for (size_t i = 0; i < temp.size(); i++) {
+ files_by_compaction_pri.push_back(static_cast<int>(temp[i].index));
+ }
+ next_file_to_compact_by_size_[level] = 0;
+ assert(files_[level].size() == files_by_compaction_pri_[level].size());
+ }
+}
+
+void VersionStorageInfo::GenerateLevel0NonOverlapping() {
+ assert(!finalized_);
+ level0_non_overlapping_ = true;
+ if (level_files_brief_.size() == 0) {
+ return;
+ }
+
+ // A copy of L0 files sorted by smallest key
+ std::vector<FdWithKeyRange> level0_sorted_file(
+ level_files_brief_[0].files,
+ level_files_brief_[0].files + level_files_brief_[0].num_files);
+ std::sort(level0_sorted_file.begin(), level0_sorted_file.end(),
+ [this](const FdWithKeyRange& f1, const FdWithKeyRange& f2) -> bool {
+ return (internal_comparator_->Compare(f1.smallest_key,
+ f2.smallest_key) < 0);
+ });
+
+ for (size_t i = 1; i < level0_sorted_file.size(); ++i) {
+ FdWithKeyRange& f = level0_sorted_file[i];
+ FdWithKeyRange& prev = level0_sorted_file[i - 1];
+ if (internal_comparator_->Compare(prev.largest_key, f.smallest_key) >= 0) {
+ level0_non_overlapping_ = false;
+ break;
+ }
+ }
+}
+
+void VersionStorageInfo::GenerateBottommostFiles() {
+ assert(!finalized_);
+ assert(bottommost_files_.empty());
+ for (size_t level = 0; level < level_files_brief_.size(); ++level) {
+ for (size_t file_idx = 0; file_idx < level_files_brief_[level].num_files;
+ ++file_idx) {
+ const FdWithKeyRange& f = level_files_brief_[level].files[file_idx];
+ int l0_file_idx;
+ if (level == 0) {
+ l0_file_idx = static_cast<int>(file_idx);
+ } else {
+ l0_file_idx = -1;
+ }
+ Slice smallest_user_key = ExtractUserKey(f.smallest_key);
+ Slice largest_user_key = ExtractUserKey(f.largest_key);
+ if (!RangeMightExistAfterSortedRun(smallest_user_key, largest_user_key,
+ static_cast<int>(level),
+ l0_file_idx)) {
+ bottommost_files_.emplace_back(static_cast<int>(level),
+ f.file_metadata);
+ }
+ }
+ }
+}
+
+void VersionStorageInfo::UpdateOldestSnapshot(SequenceNumber seqnum) {
+ assert(seqnum >= oldest_snapshot_seqnum_);
+ oldest_snapshot_seqnum_ = seqnum;
+ if (oldest_snapshot_seqnum_ > bottommost_files_mark_threshold_) {
+ ComputeBottommostFilesMarkedForCompaction();
+ }
+}
+
+void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction() {
+ bottommost_files_marked_for_compaction_.clear();
+ bottommost_files_mark_threshold_ = kMaxSequenceNumber;
+ for (auto& level_and_file : bottommost_files_) {
+ if (!level_and_file.second->being_compacted &&
+ level_and_file.second->fd.largest_seqno != 0 &&
+ level_and_file.second->num_deletions > 1) {
+ // largest_seqno might be nonzero due to containing the final key in an
+ // earlier compaction, whose seqnum we didn't zero out. Multiple deletions
+ // ensures the file really contains deleted or overwritten keys.
+ if (level_and_file.second->fd.largest_seqno < oldest_snapshot_seqnum_) {
+ bottommost_files_marked_for_compaction_.push_back(level_and_file);
+ } else {
+ bottommost_files_mark_threshold_ =
+ std::min(bottommost_files_mark_threshold_,
+ level_and_file.second->fd.largest_seqno);
+ }
+ }
+ }
+}
+
+void Version::Ref() {
+ ++refs_;
+}
+
+bool Version::Unref() {
+ assert(refs_ >= 1);
+ --refs_;
+ if (refs_ == 0) {
+ delete this;
+ return true;
+ }
+ return false;
+}
+
+bool VersionStorageInfo::OverlapInLevel(int level,
+ const Slice* smallest_user_key,
+ const Slice* largest_user_key) {
+ if (level >= num_non_empty_levels_) {
+ // empty level, no overlap
+ return false;
+ }
+ return SomeFileOverlapsRange(*internal_comparator_, (level > 0),
+ level_files_brief_[level], smallest_user_key,
+ largest_user_key);
+}
+
+// Store in "*inputs" all files in "level" that overlap [begin,end]
+// If hint_index is specified, then it points to a file in the
+// overlapping range.
+// The file_index returns a pointer to any file in an overlapping range.
+void VersionStorageInfo::GetOverlappingInputs(
+ int level, const InternalKey* begin, const InternalKey* end,
+ std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
+ bool expand_range, InternalKey** next_smallest) const {
+ if (level >= num_non_empty_levels_) {
+ // this level is empty, no overlapping inputs
+ return;
+ }
+
+ inputs->clear();
+ if (file_index) {
+ *file_index = -1;
+ }
+ const Comparator* user_cmp = user_comparator_;
+ if (level > 0) {
+ GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs, hint_index,
+ file_index, false, next_smallest);
+ return;
+ }
+
+ if (next_smallest) {
+ // next_smallest key only makes sense for non-level 0, where files are
+ // non-overlapping
+ *next_smallest = nullptr;
+ }
+
+ Slice user_begin, user_end;
+ if (begin != nullptr) {
+ user_begin = begin->user_key();
+ }
+ if (end != nullptr) {
+ user_end = end->user_key();
+ }
+
+ // index stores the file index need to check.
+ std::list<size_t> index;
+ for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
+ index.emplace_back(i);
+ }
+
+ while (!index.empty()) {
+ bool found_overlapping_file = false;
+ auto iter = index.begin();
+ while (iter != index.end()) {
+ FdWithKeyRange* f = &(level_files_brief_[level].files[*iter]);
+ const Slice file_start = ExtractUserKey(f->smallest_key);
+ const Slice file_limit = ExtractUserKey(f->largest_key);
+ if (begin != nullptr &&
+ user_cmp->CompareWithoutTimestamp(file_limit, user_begin) < 0) {
+ // "f" is completely before specified range; skip it
+ iter++;
+ } else if (end != nullptr &&
+ user_cmp->CompareWithoutTimestamp(file_start, user_end) > 0) {
+ // "f" is completely after specified range; skip it
+ iter++;
+ } else {
+ // if overlap
+ inputs->emplace_back(files_[level][*iter]);
+ found_overlapping_file = true;
+ // record the first file index.
+ if (file_index && *file_index == -1) {
+ *file_index = static_cast<int>(*iter);
+ }
+ // the related file is overlap, erase to avoid checking again.
+ iter = index.erase(iter);
+ if (expand_range) {
+ if (begin != nullptr &&
+ user_cmp->CompareWithoutTimestamp(file_start, user_begin) < 0) {
+ user_begin = file_start;
+ }
+ if (end != nullptr &&
+ user_cmp->CompareWithoutTimestamp(file_limit, user_end) > 0) {
+ user_end = file_limit;
+ }
+ }
+ }
+ }
+ // if all the files left are not overlap, break
+ if (!found_overlapping_file) {
+ break;
+ }
+ }
+}
+
+// Store in "*inputs" files in "level" that within range [begin,end]
+// Guarantee a "clean cut" boundary between the files in inputs
+// and the surrounding files and the maxinum number of files.
+// This will ensure that no parts of a key are lost during compaction.
+// If hint_index is specified, then it points to a file in the range.
+// The file_index returns a pointer to any file in an overlapping range.
+void VersionStorageInfo::GetCleanInputsWithinInterval(
+ int level, const InternalKey* begin, const InternalKey* end,
+ std::vector<FileMetaData*>* inputs, int hint_index, int* file_index) const {
+ inputs->clear();
+ if (file_index) {
+ *file_index = -1;
+ }
+ if (level >= num_non_empty_levels_ || level == 0 ||
+ level_files_brief_[level].num_files == 0) {
+ // this level is empty, no inputs within range
+ // also don't support clean input interval within L0
+ return;
+ }
+
+ GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs,
+ hint_index, file_index,
+ true /* within_interval */);
+}
+
+// Store in "*inputs" all files in "level" that overlap [begin,end]
+// Employ binary search to find at least one file that overlaps the
+// specified range. From that file, iterate backwards and
+// forwards to find all overlapping files.
+// if within_range is set, then only store the maximum clean inputs
+// within range [begin, end]. "clean" means there is a boudnary
+// between the files in "*inputs" and the surrounding files
+void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
+ int level, const InternalKey* begin, const InternalKey* end,
+ std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
+ bool within_interval, InternalKey** next_smallest) const {
+ assert(level > 0);
+
+ auto user_cmp = user_comparator_;
+ const FdWithKeyRange* files = level_files_brief_[level].files;
+ const int num_files = static_cast<int>(level_files_brief_[level].num_files);
+
+ // begin to use binary search to find lower bound
+ // and upper bound.
+ int start_index = 0;
+ int end_index = num_files;
+
+ if (begin != nullptr) {
+ // if within_interval is true, with file_key would find
+ // not overlapping ranges in std::lower_bound.
+ auto cmp = [&user_cmp, &within_interval](const FdWithKeyRange& f,
+ const InternalKey* k) {
+ auto& file_key = within_interval ? f.file_metadata->smallest
+ : f.file_metadata->largest;
+ return sstableKeyCompare(user_cmp, file_key, *k) < 0;
+ };
+
+ start_index = static_cast<int>(
+ std::lower_bound(files,
+ files + (hint_index == -1 ? num_files : hint_index),
+ begin, cmp) -
+ files);
+
+ if (start_index > 0 && within_interval) {
+ bool is_overlapping = true;
+ while (is_overlapping && start_index < num_files) {
+ auto& pre_limit = files[start_index - 1].file_metadata->largest;
+ auto& cur_start = files[start_index].file_metadata->smallest;
+ is_overlapping = sstableKeyCompare(user_cmp, pre_limit, cur_start) == 0;
+ start_index += is_overlapping;
+ }
+ }
+ }
+
+ if (end != nullptr) {
+ // if within_interval is true, with file_key would find
+ // not overlapping ranges in std::upper_bound.
+ auto cmp = [&user_cmp, &within_interval](const InternalKey* k,
+ const FdWithKeyRange& f) {
+ auto& file_key = within_interval ? f.file_metadata->largest
+ : f.file_metadata->smallest;
+ return sstableKeyCompare(user_cmp, *k, file_key) < 0;
+ };
+
+ end_index = static_cast<int>(
+ std::upper_bound(files + start_index, files + num_files, end, cmp) -
+ files);
+
+ if (end_index < num_files && within_interval) {
+ bool is_overlapping = true;
+ while (is_overlapping && end_index > start_index) {
+ auto& next_start = files[end_index].file_metadata->smallest;
+ auto& cur_limit = files[end_index - 1].file_metadata->largest;
+ is_overlapping =
+ sstableKeyCompare(user_cmp, cur_limit, next_start) == 0;
+ end_index -= is_overlapping;
+ }
+ }
+ }
+
+ assert(start_index <= end_index);
+
+ // If there were no overlapping files, return immediately.
+ if (start_index == end_index) {
+ if (next_smallest) {
+ *next_smallest = nullptr;
+ }
+ return;
+ }
+
+ assert(start_index < end_index);
+
+ // returns the index where an overlap is found
+ if (file_index) {
+ *file_index = start_index;
+ }
+
+ // insert overlapping files into vector
+ for (int i = start_index; i < end_index; i++) {
+ inputs->push_back(files_[level][i]);
+ }
+
+ if (next_smallest != nullptr) {
+ // Provide the next key outside the range covered by inputs
+ if (end_index < static_cast<int>(files_[level].size())) {
+ **next_smallest = files_[level][end_index]->smallest;
+ } else {
+ *next_smallest = nullptr;
+ }
+ }
+}
+
+uint64_t VersionStorageInfo::NumLevelBytes(int level) const {
+ assert(level >= 0);
+ assert(level < num_levels());
+ return TotalFileSize(files_[level]);
+}
+
+const char* VersionStorageInfo::LevelSummary(
+ LevelSummaryStorage* scratch) const {
+ int len = 0;
+ if (compaction_style_ == kCompactionStyleLevel && num_levels() > 1) {
+ assert(base_level_ < static_cast<int>(level_max_bytes_.size()));
+ if (level_multiplier_ != 0.0) {
+ len = snprintf(
+ scratch->buffer, sizeof(scratch->buffer),
+ "base level %d level multiplier %.2f max bytes base %" PRIu64 " ",
+ base_level_, level_multiplier_, level_max_bytes_[base_level_]);
+ }
+ }
+ len +=
+ snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "files[");
+ for (int i = 0; i < num_levels(); i++) {
+ int sz = sizeof(scratch->buffer) - len;
+ int ret = snprintf(scratch->buffer + len, sz, "%d ", int(files_[i].size()));
+ if (ret < 0 || ret >= sz) break;
+ len += ret;
+ }
+ if (len > 0) {
+ // overwrite the last space
+ --len;
+ }
+ len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
+ "] max score %.2f", compaction_score_[0]);
+
+ if (!files_marked_for_compaction_.empty()) {
+ snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
+ " (%" ROCKSDB_PRIszt " files need compaction)",
+ files_marked_for_compaction_.size());
+ }
+
+ return scratch->buffer;
+}
+
+const char* VersionStorageInfo::LevelFileSummary(FileSummaryStorage* scratch,
+ int level) const {
+ int len = snprintf(scratch->buffer, sizeof(scratch->buffer), "files_size[");
+ for (const auto& f : files_[level]) {
+ int sz = sizeof(scratch->buffer) - len;
+ char sztxt[16];
+ AppendHumanBytes(f->fd.GetFileSize(), sztxt, sizeof(sztxt));
+ int ret = snprintf(scratch->buffer + len, sz,
+ "#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ",
+ f->fd.GetNumber(), f->fd.smallest_seqno, sztxt,
+ static_cast<int>(f->being_compacted));
+ if (ret < 0 || ret >= sz)
+ break;
+ len += ret;
+ }
+ // overwrite the last space (only if files_[level].size() is non-zero)
+ if (files_[level].size() && len > 0) {
+ --len;
+ }
+ snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "]");
+ return scratch->buffer;
+}
+
+int64_t VersionStorageInfo::MaxNextLevelOverlappingBytes() {
+ uint64_t result = 0;
+ std::vector<FileMetaData*> overlaps;
+ for (int level = 1; level < num_levels() - 1; level++) {
+ for (const auto& f : files_[level]) {
+ GetOverlappingInputs(level + 1, &f->smallest, &f->largest, &overlaps);
+ const uint64_t sum = TotalFileSize(overlaps);
+ if (sum > result) {
+ result = sum;
+ }
+ }
+ }
+ return result;
+}
+
+uint64_t VersionStorageInfo::MaxBytesForLevel(int level) const {
+ // Note: the result for level zero is not really used since we set
+ // the level-0 compaction threshold based on number of files.
+ assert(level >= 0);
+ assert(level < static_cast<int>(level_max_bytes_.size()));
+ return level_max_bytes_[level];
+}
+
+void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& options) {
+ // Special logic to set number of sorted runs.
+ // It is to match the previous behavior when all files are in L0.
+ int num_l0_count = static_cast<int>(files_[0].size());
+ if (compaction_style_ == kCompactionStyleUniversal) {
+ // For universal compaction, we use level0 score to indicate
+ // compaction score for the whole DB. Adding other levels as if
+ // they are L0 files.
+ for (int i = 1; i < num_levels(); i++) {
+ if (!files_[i].empty()) {
+ num_l0_count++;
+ }
+ }
+ }
+ set_l0_delay_trigger_count(num_l0_count);
+
+ level_max_bytes_.resize(ioptions.num_levels);
+ if (!ioptions.level_compaction_dynamic_level_bytes) {
+ base_level_ = (ioptions.compaction_style == kCompactionStyleLevel) ? 1 : -1;
+
+ // Calculate for static bytes base case
+ for (int i = 0; i < ioptions.num_levels; ++i) {
+ if (i == 0 && ioptions.compaction_style == kCompactionStyleUniversal) {
+ level_max_bytes_[i] = options.max_bytes_for_level_base;
+ } else if (i > 1) {
+ level_max_bytes_[i] = MultiplyCheckOverflow(
+ MultiplyCheckOverflow(level_max_bytes_[i - 1],
+ options.max_bytes_for_level_multiplier),
+ options.MaxBytesMultiplerAdditional(i - 1));
+ } else {
+ level_max_bytes_[i] = options.max_bytes_for_level_base;
+ }
+ }
+ } else {
+ uint64_t max_level_size = 0;
+
+ int first_non_empty_level = -1;
+ // Find size of non-L0 level of most data.
+ // Cannot use the size of the last level because it can be empty or less
+ // than previous levels after compaction.
+ for (int i = 1; i < num_levels_; i++) {
+ uint64_t total_size = 0;
+ for (const auto& f : files_[i]) {
+ total_size += f->fd.GetFileSize();
+ }
+ if (total_size > 0 && first_non_empty_level == -1) {
+ first_non_empty_level = i;
+ }
+ if (total_size > max_level_size) {
+ max_level_size = total_size;
+ }
+ }
+
+ // Prefill every level's max bytes to disallow compaction from there.
+ for (int i = 0; i < num_levels_; i++) {
+ level_max_bytes_[i] = std::numeric_limits<uint64_t>::max();
+ }
+
+ if (max_level_size == 0) {
+ // No data for L1 and up. L0 compacts to last level directly.
+ // No compaction from L1+ needs to be scheduled.
+ base_level_ = num_levels_ - 1;
+ } else {
+ uint64_t l0_size = 0;
+ for (const auto& f : files_[0]) {
+ l0_size += f->fd.GetFileSize();
+ }
+
+ uint64_t base_bytes_max =
+ std::max(options.max_bytes_for_level_base, l0_size);
+ uint64_t base_bytes_min = static_cast<uint64_t>(
+ base_bytes_max / options.max_bytes_for_level_multiplier);
+
+ // Try whether we can make last level's target size to be max_level_size
+ uint64_t cur_level_size = max_level_size;
+ for (int i = num_levels_ - 2; i >= first_non_empty_level; i--) {
+ // Round up after dividing
+ cur_level_size = static_cast<uint64_t>(
+ cur_level_size / options.max_bytes_for_level_multiplier);
+ }
+
+ // Calculate base level and its size.
+ uint64_t base_level_size;
+ if (cur_level_size <= base_bytes_min) {
+ // Case 1. If we make target size of last level to be max_level_size,
+ // target size of the first non-empty level would be smaller than
+ // base_bytes_min. We set it be base_bytes_min.
+ base_level_size = base_bytes_min + 1U;
+ base_level_ = first_non_empty_level;
+ ROCKS_LOG_INFO(ioptions.info_log,
+ "More existing levels in DB than needed. "
+ "max_bytes_for_level_multiplier may not be guaranteed.");
+ } else {
+ // Find base level (where L0 data is compacted to).
+ base_level_ = first_non_empty_level;
+ while (base_level_ > 1 && cur_level_size > base_bytes_max) {
+ --base_level_;
+ cur_level_size = static_cast<uint64_t>(
+ cur_level_size / options.max_bytes_for_level_multiplier);
+ }
+ if (cur_level_size > base_bytes_max) {
+ // Even L1 will be too large
+ assert(base_level_ == 1);
+ base_level_size = base_bytes_max;
+ } else {
+ base_level_size = cur_level_size;
+ }
+ }
+
+ level_multiplier_ = options.max_bytes_for_level_multiplier;
+ assert(base_level_size > 0);
+ if (l0_size > base_level_size &&
+ (l0_size > options.max_bytes_for_level_base ||
+ static_cast<int>(files_[0].size() / 2) >=
+ options.level0_file_num_compaction_trigger)) {
+ // We adjust the base level according to actual L0 size, and adjust
+ // the level multiplier accordingly, when:
+ // 1. the L0 size is larger than level size base, or
+ // 2. number of L0 files reaches twice the L0->L1 compaction trigger
+ // We don't do this otherwise to keep the LSM-tree structure stable
+ // unless the L0 compation is backlogged.
+ base_level_size = l0_size;
+ if (base_level_ == num_levels_ - 1) {
+ level_multiplier_ = 1.0;
+ } else {
+ level_multiplier_ = std::pow(
+ static_cast<double>(max_level_size) /
+ static_cast<double>(base_level_size),
+ 1.0 / static_cast<double>(num_levels_ - base_level_ - 1));
+ }
+ }
+
+ uint64_t level_size = base_level_size;
+ for (int i = base_level_; i < num_levels_; i++) {
+ if (i > base_level_) {
+ level_size = MultiplyCheckOverflow(level_size, level_multiplier_);
+ }
+ // Don't set any level below base_bytes_max. Otherwise, the LSM can
+ // assume an hourglass shape where L1+ sizes are smaller than L0. This
+ // causes compaction scoring, which depends on level sizes, to favor L1+
+ // at the expense of L0, which may fill up and stall.
+ level_max_bytes_[i] = std::max(level_size, base_bytes_max);
+ }
+ }
+ }
+}
+
+uint64_t VersionStorageInfo::EstimateLiveDataSize() const {
+ // Estimate the live data size by adding up the size of the last level for all
+ // key ranges. Note: Estimate depends on the ordering of files in level 0
+ // because files in level 0 can be overlapping.
+ uint64_t size = 0;
+
+ auto ikey_lt = [this](InternalKey* x, InternalKey* y) {
+ return internal_comparator_->Compare(*x, *y) < 0;
+ };
+ // (Ordered) map of largest keys in non-overlapping files
+ std::map<InternalKey*, FileMetaData*, decltype(ikey_lt)> ranges(ikey_lt);
+
+ for (int l = num_levels_ - 1; l >= 0; l--) {
+ bool found_end = false;
+ for (auto file : files_[l]) {
+ // Find the first file where the largest key is larger than the smallest
+ // key of the current file. If this file does not overlap with the
+ // current file, none of the files in the map does. If there is
+ // no potential overlap, we can safely insert the rest of this level
+ // (if the level is not 0) into the map without checking again because
+ // the elements in the level are sorted and non-overlapping.
+ auto lb = (found_end && l != 0) ?
+ ranges.end() : ranges.lower_bound(&file->smallest);
+ found_end = (lb == ranges.end());
+ if (found_end || internal_comparator_->Compare(
+ file->largest, (*lb).second->smallest) < 0) {
+ ranges.emplace_hint(lb, &file->largest, file);
+ size += file->fd.file_size;
+ }
+ }
+ }
+ return size;
+}
+
+bool VersionStorageInfo::RangeMightExistAfterSortedRun(
+ const Slice& smallest_user_key, const Slice& largest_user_key,
+ int last_level, int last_l0_idx) {
+ assert((last_l0_idx != -1) == (last_level == 0));
+ // TODO(ajkr): this preserves earlier behavior where we considered an L0 file
+ // bottommost only if it's the oldest L0 file and there are no files on older
+ // levels. It'd be better to consider it bottommost if there's no overlap in
+ // older levels/files.
+ if (last_level == 0 &&
+ last_l0_idx != static_cast<int>(LevelFiles(0).size() - 1)) {
+ return true;
+ }
+
+ // Checks whether there are files living beyond the `last_level`. If lower
+ // levels have files, it checks for overlap between [`smallest_key`,
+ // `largest_key`] and those files. Bottomlevel optimizations can be made if
+ // there are no files in lower levels or if there is no overlap with the files
+ // in the lower levels.
+ for (int level = last_level + 1; level < num_levels(); level++) {
+ // The range is not in the bottommost level if there are files in lower
+ // levels when the `last_level` is 0 or if there are files in lower levels
+ // which overlap with [`smallest_key`, `largest_key`].
+ if (files_[level].size() > 0 &&
+ (last_level == 0 ||
+ OverlapInLevel(level, &smallest_user_key, &largest_user_key))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Version::AddLiveFiles(std::vector<FileDescriptor>* live) {
+ for (int level = 0; level < storage_info_.num_levels(); level++) {
+ const std::vector<FileMetaData*>& files = storage_info_.files_[level];
+ for (const auto& file : files) {
+ live->push_back(file->fd);
+ }
+ }
+}
+
+std::string Version::DebugString(bool hex, bool print_stats) const {
+ std::string r;
+ for (int level = 0; level < storage_info_.num_levels_; level++) {
+ // E.g.,
+ // --- level 1 ---
+ // 17:123[1 .. 124]['a' .. 'd']
+ // 20:43[124 .. 128]['e' .. 'g']
+ //
+ // if print_stats=true:
+ // 17:123[1 .. 124]['a' .. 'd'](4096)
+ r.append("--- level ");
+ AppendNumberTo(&r, level);
+ r.append(" --- version# ");
+ AppendNumberTo(&r, version_number_);
+ r.append(" ---\n");
+ const std::vector<FileMetaData*>& files = storage_info_.files_[level];
+ for (size_t i = 0; i < files.size(); i++) {
+ r.push_back(' ');
+ AppendNumberTo(&r, files[i]->fd.GetNumber());
+ r.push_back(':');
+ AppendNumberTo(&r, files[i]->fd.GetFileSize());
+ r.append("[");
+ AppendNumberTo(&r, files[i]->fd.smallest_seqno);
+ r.append(" .. ");
+ AppendNumberTo(&r, files[i]->fd.largest_seqno);
+ r.append("]");
+ r.append("[");
+ r.append(files[i]->smallest.DebugString(hex));
+ r.append(" .. ");
+ r.append(files[i]->largest.DebugString(hex));
+ r.append("]");
+ if (files[i]->oldest_blob_file_number != kInvalidBlobFileNumber) {
+ r.append(" blob_file:");
+ AppendNumberTo(&r, files[i]->oldest_blob_file_number);
+ }
+ if (print_stats) {
+ r.append("(");
+ r.append(ToString(
+ files[i]->stats.num_reads_sampled.load(std::memory_order_relaxed)));
+ r.append(")");
+ }
+ r.append("\n");
+ }
+ }
+ return r;
+}
+
+// this is used to batch writes to the manifest file
+struct VersionSet::ManifestWriter {
+ Status status;
+ bool done;
+ InstrumentedCondVar cv;
+ ColumnFamilyData* cfd;
+ const MutableCFOptions mutable_cf_options;
+ const autovector<VersionEdit*>& edit_list;
+
+ explicit ManifestWriter(InstrumentedMutex* mu, ColumnFamilyData* _cfd,
+ const MutableCFOptions& cf_options,
+ const autovector<VersionEdit*>& e)
+ : done(false),
+ cv(mu),
+ cfd(_cfd),
+ mutable_cf_options(cf_options),
+ edit_list(e) {}
+};
+
+Status AtomicGroupReadBuffer::AddEdit(VersionEdit* edit) {
+ assert(edit);
+ if (edit->is_in_atomic_group_) {
+ TEST_SYNC_POINT("AtomicGroupReadBuffer::AddEdit:AtomicGroup");
+ if (replay_buffer_.empty()) {
+ replay_buffer_.resize(edit->remaining_entries_ + 1);
+ TEST_SYNC_POINT_CALLBACK(
+ "AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", edit);
+ }
+ read_edits_in_atomic_group_++;
+ if (read_edits_in_atomic_group_ + edit->remaining_entries_ !=
+ static_cast<uint32_t>(replay_buffer_.size())) {
+ TEST_SYNC_POINT_CALLBACK(
+ "AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize", edit);
+ return Status::Corruption("corrupted atomic group");
+ }
+ replay_buffer_[read_edits_in_atomic_group_ - 1] = *edit;
+ if (read_edits_in_atomic_group_ == replay_buffer_.size()) {
+ TEST_SYNC_POINT_CALLBACK(
+ "AtomicGroupReadBuffer::AddEdit:LastInAtomicGroup", edit);
+ return Status::OK();
+ }
+ return Status::OK();
+ }
+
+ // A normal edit.
+ if (!replay_buffer().empty()) {
+ TEST_SYNC_POINT_CALLBACK(
+ "AtomicGroupReadBuffer::AddEdit:AtomicGroupMixedWithNormalEdits", edit);
+ return Status::Corruption("corrupted atomic group");
+ }
+ return Status::OK();
+}
+
+bool AtomicGroupReadBuffer::IsFull() const {
+ return read_edits_in_atomic_group_ == replay_buffer_.size();
+}
+
+bool AtomicGroupReadBuffer::IsEmpty() const { return replay_buffer_.empty(); }
+
+void AtomicGroupReadBuffer::Clear() {
+ read_edits_in_atomic_group_ = 0;
+ replay_buffer_.clear();
+}
+
+VersionSet::VersionSet(const std::string& dbname,
+ const ImmutableDBOptions* _db_options,
+ const FileOptions& storage_options, Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller,
+ BlockCacheTracer* const block_cache_tracer)
+ : column_family_set_(new ColumnFamilySet(
+ dbname, _db_options, storage_options, table_cache,
+ write_buffer_manager, write_controller, block_cache_tracer)),
+ env_(_db_options->env),
+ fs_(_db_options->fs.get()),
+ dbname_(dbname),
+ db_options_(_db_options),
+ next_file_number_(2),
+ manifest_file_number_(0), // Filled by Recover()
+ options_file_number_(0),
+ pending_manifest_file_number_(0),
+ last_sequence_(0),
+ last_allocated_sequence_(0),
+ last_published_sequence_(0),
+ prev_log_number_(0),
+ current_version_number_(0),
+ manifest_file_size_(0),
+ file_options_(storage_options),
+ block_cache_tracer_(block_cache_tracer) {}
+
+VersionSet::~VersionSet() {
+ // we need to delete column_family_set_ because its destructor depends on
+ // VersionSet
+ Cache* table_cache = column_family_set_->get_table_cache();
+ column_family_set_.reset();
+ for (auto& file : obsolete_files_) {
+ if (file.metadata->table_reader_handle) {
+ table_cache->Release(file.metadata->table_reader_handle);
+ TableCache::Evict(table_cache, file.metadata->fd.GetNumber());
+ }
+ file.DeleteMetadata();
+ }
+ obsolete_files_.clear();
+}
+
+void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
+ Version* v) {
+ // compute new compaction score
+ v->storage_info()->ComputeCompactionScore(
+ *column_family_data->ioptions(),
+ *column_family_data->GetLatestMutableCFOptions());
+
+ // Mark v finalized
+ v->storage_info_.SetFinalized();
+
+ // Make "v" current
+ assert(v->refs_ == 0);
+ Version* current = column_family_data->current();
+ assert(v != current);
+ if (current != nullptr) {
+ assert(current->refs_ > 0);
+ current->Unref();
+ }
+ column_family_data->SetCurrent(v);
+ v->Ref();
+
+ // Append to linked list
+ v->prev_ = column_family_data->dummy_versions()->prev_;
+ v->next_ = column_family_data->dummy_versions();
+ v->prev_->next_ = v;
+ v->next_->prev_ = v;
+}
+
+Status VersionSet::ProcessManifestWrites(
+ std::deque<ManifestWriter>& writers, InstrumentedMutex* mu,
+ Directory* db_directory, bool new_descriptor_log,
+ const ColumnFamilyOptions* new_cf_options) {
+ assert(!writers.empty());
+ ManifestWriter& first_writer = writers.front();
+ ManifestWriter* last_writer = &first_writer;
+
+ assert(!manifest_writers_.empty());
+ assert(manifest_writers_.front() == &first_writer);
+
+ autovector<VersionEdit*> batch_edits;
+ autovector<Version*> versions;
+ autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
+ std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
+
+ if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
+ // No group commits for column family add or drop
+ LogAndApplyCFHelper(first_writer.edit_list.front());
+ batch_edits.push_back(first_writer.edit_list.front());
+ } else {
+ auto it = manifest_writers_.cbegin();
+ size_t group_start = std::numeric_limits<size_t>::max();
+ while (it != manifest_writers_.cend()) {
+ if ((*it)->edit_list.front()->IsColumnFamilyManipulation()) {
+ // no group commits for column family add or drop
+ break;
+ }
+ last_writer = *(it++);
+ assert(last_writer != nullptr);
+ assert(last_writer->cfd != nullptr);
+ if (last_writer->cfd->IsDropped()) {
+ // If we detect a dropped CF at this point, and the corresponding
+ // version edits belong to an atomic group, then we need to find out
+ // the preceding version edits in the same atomic group, and update
+ // their `remaining_entries_` member variable because we are NOT going
+ // to write the version edits' of dropped CF to the MANIFEST. If we
+ // don't update, then Recover can report corrupted atomic group because
+ // the `remaining_entries_` do not match.
+ if (!batch_edits.empty()) {
+ if (batch_edits.back()->is_in_atomic_group_ &&
+ batch_edits.back()->remaining_entries_ > 0) {
+ assert(group_start < batch_edits.size());
+ const auto& edit_list = last_writer->edit_list;
+ size_t k = 0;
+ while (k < edit_list.size()) {
+ if (!edit_list[k]->is_in_atomic_group_) {
+ break;
+ } else if (edit_list[k]->remaining_entries_ == 0) {
+ ++k;
+ break;
+ }
+ ++k;
+ }
+ for (auto i = group_start; i < batch_edits.size(); ++i) {
+ assert(static_cast<uint32_t>(k) <=
+ batch_edits.back()->remaining_entries_);
+ batch_edits[i]->remaining_entries_ -= static_cast<uint32_t>(k);
+ }
+ }
+ }
+ continue;
+ }
+ // We do a linear search on versions because versions is small.
+ // TODO(yanqin) maybe consider unordered_map
+ Version* version = nullptr;
+ VersionBuilder* builder = nullptr;
+ for (int i = 0; i != static_cast<int>(versions.size()); ++i) {
+ uint32_t cf_id = last_writer->cfd->GetID();
+ if (versions[i]->cfd()->GetID() == cf_id) {
+ version = versions[i];
+ assert(!builder_guards.empty() &&
+ builder_guards.size() == versions.size());
+ builder = builder_guards[i]->version_builder();
+ TEST_SYNC_POINT_CALLBACK(
+ "VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id);
+ break;
+ }
+ }
+ if (version == nullptr) {
+ version = new Version(last_writer->cfd, this, file_options_,
+ last_writer->mutable_cf_options,
+ current_version_number_++);
+ versions.push_back(version);
+ mutable_cf_options_ptrs.push_back(&last_writer->mutable_cf_options);
+ builder_guards.emplace_back(
+ new BaseReferencedVersionBuilder(last_writer->cfd));
+ builder = builder_guards.back()->version_builder();
+ }
+ assert(builder != nullptr); // make checker happy
+ for (const auto& e : last_writer->edit_list) {
+ if (e->is_in_atomic_group_) {
+ if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ ||
+ (batch_edits.back()->is_in_atomic_group_ &&
+ batch_edits.back()->remaining_entries_ == 0)) {
+ group_start = batch_edits.size();
+ }
+ } else if (group_start != std::numeric_limits<size_t>::max()) {
+ group_start = std::numeric_limits<size_t>::max();
+ }
+ Status s = LogAndApplyHelper(last_writer->cfd, builder, e, mu);
+ if (!s.ok()) {
+ // free up the allocated memory
+ for (auto v : versions) {
+ delete v;
+ }
+ return s;
+ }
+ batch_edits.push_back(e);
+ }
+ }
+ for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
+ assert(!builder_guards.empty() &&
+ builder_guards.size() == versions.size());
+ auto* builder = builder_guards[i]->version_builder();
+ Status s = builder->SaveTo(versions[i]->storage_info());
+ if (!s.ok()) {
+ // free up the allocated memory
+ for (auto v : versions) {
+ delete v;
+ }
+ return s;
+ }
+ }
+ }
+
+#ifndef NDEBUG
+ // Verify that version edits of atomic groups have correct
+ // remaining_entries_.
+ size_t k = 0;
+ while (k < batch_edits.size()) {
+ while (k < batch_edits.size() && !batch_edits[k]->is_in_atomic_group_) {
+ ++k;
+ }
+ if (k == batch_edits.size()) {
+ break;
+ }
+ size_t i = k;
+ while (i < batch_edits.size()) {
+ if (!batch_edits[i]->is_in_atomic_group_) {
+ break;
+ }
+ assert(i - k + batch_edits[i]->remaining_entries_ ==
+ batch_edits[k]->remaining_entries_);
+ if (batch_edits[i]->remaining_entries_ == 0) {
+ ++i;
+ break;
+ }
+ ++i;
+ }
+ assert(batch_edits[i - 1]->is_in_atomic_group_);
+ assert(0 == batch_edits[i - 1]->remaining_entries_);
+ std::vector<VersionEdit*> tmp;
+ for (size_t j = k; j != i; ++j) {
+ tmp.emplace_back(batch_edits[j]);
+ }
+ TEST_SYNC_POINT_CALLBACK(
+ "VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", &tmp);
+ k = i;
+ }
+#endif // NDEBUG
+
+ uint64_t new_manifest_file_size = 0;
+ Status s;
+
+ assert(pending_manifest_file_number_ == 0);
+ if (!descriptor_log_ ||
+ manifest_file_size_ > db_options_->max_manifest_file_size) {
+ TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
+ new_descriptor_log = true;
+ } else {
+ pending_manifest_file_number_ = manifest_file_number_;
+ }
+
+ // Local cached copy of state variable(s). WriteCurrentStateToManifest()
+ // reads its content after releasing db mutex to avoid race with
+ // SwitchMemtable().
+ std::unordered_map<uint32_t, MutableCFState> curr_state;
+ if (new_descriptor_log) {
+ pending_manifest_file_number_ = NewFileNumber();
+ batch_edits.back()->SetNextFile(next_file_number_.load());
+
+ // if we are writing out new snapshot make sure to persist max column
+ // family.
+ if (column_family_set_->GetMaxColumnFamily() > 0) {
+ first_writer.edit_list.front()->SetMaxColumnFamily(
+ column_family_set_->GetMaxColumnFamily());
+ }
+ for (const auto* cfd : *column_family_set_) {
+ assert(curr_state.find(cfd->GetID()) == curr_state.end());
+ curr_state[cfd->GetID()] = {cfd->GetLogNumber()};
+ }
+ }
+
+ {
+ FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
+ mu->Unlock();
+
+ TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest");
+ if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
+ for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
+ assert(!builder_guards.empty() &&
+ builder_guards.size() == versions.size());
+ assert(!mutable_cf_options_ptrs.empty() &&
+ builder_guards.size() == versions.size());
+ ColumnFamilyData* cfd = versions[i]->cfd_;
+ s = builder_guards[i]->version_builder()->LoadTableHandlers(
+ cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits,
+ true /* prefetch_index_and_filter_in_cache */,
+ false /* is_initial_load */,
+ mutable_cf_options_ptrs[i]->prefix_extractor.get());
+ if (!s.ok()) {
+ if (db_options_->paranoid_checks) {
+ break;
+ }
+ s = Status::OK();
+ }
+ }
+ }
+
+ if (s.ok() && new_descriptor_log) {
+ // This is fine because everything inside of this block is serialized --
+ // only one thread can be here at the same time
+ // create new manifest file
+ ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
+ pending_manifest_file_number_);
+ std::string descriptor_fname =
+ DescriptorFileName(dbname_, pending_manifest_file_number_);
+ std::unique_ptr<FSWritableFile> descriptor_file;
+ s = NewWritableFile(fs_, descriptor_fname, &descriptor_file,
+ opt_file_opts);
+ if (s.ok()) {
+ descriptor_file->SetPreallocationBlockSize(
+ db_options_->manifest_preallocation_size);
+
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ std::move(descriptor_file), descriptor_fname, opt_file_opts, env_,
+ nullptr, db_options_->listeners));
+ descriptor_log_.reset(
+ new log::Writer(std::move(file_writer), 0, false));
+ s = WriteCurrentStateToManifest(curr_state, descriptor_log_.get());
+ }
+ }
+
+ if (s.ok()) {
+ if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
+ for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
+ versions[i]->PrepareApply(*mutable_cf_options_ptrs[i], true);
+ }
+ }
+
+ // Write new records to MANIFEST log
+#ifndef NDEBUG
+ size_t idx = 0;
+#endif
+ for (auto& e : batch_edits) {
+ std::string record;
+ if (!e->EncodeTo(&record)) {
+ s = Status::Corruption("Unable to encode VersionEdit:" +
+ e->DebugString(true));
+ break;
+ }
+ TEST_KILL_RANDOM("VersionSet::LogAndApply:BeforeAddRecord",
+ rocksdb_kill_odds * REDUCE_ODDS2);
+#ifndef NDEBUG
+ if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
+ TEST_SYNC_POINT_CALLBACK(
+ "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
+ nullptr);
+ TEST_SYNC_POINT(
+ "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
+ }
+ ++idx;
+#endif /* !NDEBUG */
+ s = descriptor_log_->AddRecord(record);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ if (s.ok()) {
+ s = SyncManifest(env_, db_options_, descriptor_log_->file());
+ }
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n",
+ s.ToString().c_str());
+ }
+ }
+
+ // If we just created a new descriptor file, install it by writing a
+ // new CURRENT file that points to it.
+ if (s.ok() && new_descriptor_log) {
+ s = SetCurrentFile(env_, dbname_, pending_manifest_file_number_,
+ db_directory);
+ TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:AfterNewManifest");
+ }
+
+ if (s.ok()) {
+ // find offset in manifest file where this version is stored.
+ new_manifest_file_size = descriptor_log_->file()->GetFileSize();
+ }
+
+ if (first_writer.edit_list.front()->is_column_family_drop_) {
+ TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0");
+ TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1");
+ TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2");
+ }
+
+ LogFlush(db_options_->info_log);
+ TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestDone");
+ mu->Lock();
+ }
+
+ // Append the old manifest file to the obsolete_manifest_ list to be deleted
+ // by PurgeObsoleteFiles later.
+ if (s.ok() && new_descriptor_log) {
+ obsolete_manifests_.emplace_back(
+ DescriptorFileName("", manifest_file_number_));
+ }
+
+ // Install the new versions
+ if (s.ok()) {
+ if (first_writer.edit_list.front()->is_column_family_add_) {
+ assert(batch_edits.size() == 1);
+ assert(new_cf_options != nullptr);
+ CreateColumnFamily(*new_cf_options, first_writer.edit_list.front());
+ } else if (first_writer.edit_list.front()->is_column_family_drop_) {
+ assert(batch_edits.size() == 1);
+ first_writer.cfd->SetDropped();
+ first_writer.cfd->UnrefAndTryDelete();
+ } else {
+ // Each version in versions corresponds to a column family.
+ // For each column family, update its log number indicating that logs
+ // with number smaller than this should be ignored.
+ for (const auto version : versions) {
+ uint64_t max_log_number_in_batch = 0;
+ uint32_t cf_id = version->cfd_->GetID();
+ for (const auto& e : batch_edits) {
+ if (e->has_log_number_ && e->column_family_ == cf_id) {
+ max_log_number_in_batch =
+ std::max(max_log_number_in_batch, e->log_number_);
+ }
+ }
+ if (max_log_number_in_batch != 0) {
+ assert(version->cfd_->GetLogNumber() <= max_log_number_in_batch);
+ version->cfd_->SetLogNumber(max_log_number_in_batch);
+ }
+ }
+
+ uint64_t last_min_log_number_to_keep = 0;
+ for (auto& e : batch_edits) {
+ if (e->has_min_log_number_to_keep_) {
+ last_min_log_number_to_keep =
+ std::max(last_min_log_number_to_keep, e->min_log_number_to_keep_);
+ }
+ }
+
+ if (last_min_log_number_to_keep != 0) {
+ // Should only be set in 2PC mode.
+ MarkMinLogNumberToKeep2PC(last_min_log_number_to_keep);
+ }
+
+ for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
+ ColumnFamilyData* cfd = versions[i]->cfd_;
+ AppendVersion(cfd, versions[i]);
+ }
+ }
+ manifest_file_number_ = pending_manifest_file_number_;
+ manifest_file_size_ = new_manifest_file_size;
+ prev_log_number_ = first_writer.edit_list.front()->prev_log_number_;
+ } else {
+ std::string version_edits;
+ for (auto& e : batch_edits) {
+ version_edits += ("\n" + e->DebugString(true));
+ }
+ ROCKS_LOG_ERROR(db_options_->info_log,
+ "Error in committing version edit to MANIFEST: %s",
+ version_edits.c_str());
+ for (auto v : versions) {
+ delete v;
+ }
+ // If manifest append failed for whatever reason, the file could be
+ // corrupted. So we need to force the next version update to start a
+ // new manifest file.
+ descriptor_log_.reset();
+ if (new_descriptor_log) {
+ ROCKS_LOG_INFO(db_options_->info_log,
+ "Deleting manifest %" PRIu64 " current manifest %" PRIu64
+ "\n",
+ manifest_file_number_, pending_manifest_file_number_);
+ env_->DeleteFile(
+ DescriptorFileName(dbname_, pending_manifest_file_number_));
+ }
+ }
+
+ pending_manifest_file_number_ = 0;
+
+ // wake up all the waiting writers
+ while (true) {
+ ManifestWriter* ready = manifest_writers_.front();
+ manifest_writers_.pop_front();
+ bool need_signal = true;
+ for (const auto& w : writers) {
+ if (&w == ready) {
+ need_signal = false;
+ break;
+ }
+ }
+ ready->status = s;
+ ready->done = true;
+ if (need_signal) {
+ ready->cv.Signal();
+ }
+ if (ready == last_writer) {
+ break;
+ }
+ }
+ if (!manifest_writers_.empty()) {
+ manifest_writers_.front()->cv.Signal();
+ }
+ return s;
+}
+
+// 'datas' is gramatically incorrect. We still use this notation to indicate
+// that this variable represents a collection of column_family_data.
+Status VersionSet::LogAndApply(
+ const autovector<ColumnFamilyData*>& column_family_datas,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<autovector<VersionEdit*>>& edit_lists,
+ InstrumentedMutex* mu, Directory* db_directory, bool new_descriptor_log,
+ const ColumnFamilyOptions* new_cf_options) {
+ mu->AssertHeld();
+ int num_edits = 0;
+ for (const auto& elist : edit_lists) {
+ num_edits += static_cast<int>(elist.size());
+ }
+ if (num_edits == 0) {
+ return Status::OK();
+ } else if (num_edits > 1) {
+#ifndef NDEBUG
+ for (const auto& edit_list : edit_lists) {
+ for (const auto& edit : edit_list) {
+ assert(!edit->IsColumnFamilyManipulation());
+ }
+ }
+#endif /* ! NDEBUG */
+ }
+
+ int num_cfds = static_cast<int>(column_family_datas.size());
+ if (num_cfds == 1 && column_family_datas[0] == nullptr) {
+ assert(edit_lists.size() == 1 && edit_lists[0].size() == 1);
+ assert(edit_lists[0][0]->is_column_family_add_);
+ assert(new_cf_options != nullptr);
+ }
+ std::deque<ManifestWriter> writers;
+ if (num_cfds > 0) {
+ assert(static_cast<size_t>(num_cfds) == mutable_cf_options_list.size());
+ assert(static_cast<size_t>(num_cfds) == edit_lists.size());
+ }
+ for (int i = 0; i < num_cfds; ++i) {
+ writers.emplace_back(mu, column_family_datas[i],
+ *mutable_cf_options_list[i], edit_lists[i]);
+ manifest_writers_.push_back(&writers[i]);
+ }
+ assert(!writers.empty());
+ ManifestWriter& first_writer = writers.front();
+ while (!first_writer.done && &first_writer != manifest_writers_.front()) {
+ first_writer.cv.Wait();
+ }
+ if (first_writer.done) {
+ // All non-CF-manipulation operations can be grouped together and committed
+ // to MANIFEST. They should all have finished. The status code is stored in
+ // the first manifest writer.
+#ifndef NDEBUG
+ for (const auto& writer : writers) {
+ assert(writer.done);
+ }
+#endif /* !NDEBUG */
+ return first_writer.status;
+ }
+
+ int num_undropped_cfds = 0;
+ for (auto cfd : column_family_datas) {
+ // if cfd == nullptr, it is a column family add.
+ if (cfd == nullptr || !cfd->IsDropped()) {
+ ++num_undropped_cfds;
+ }
+ }
+ if (0 == num_undropped_cfds) {
+ for (int i = 0; i != num_cfds; ++i) {
+ manifest_writers_.pop_front();
+ }
+ // Notify new head of manifest write queue.
+ if (!manifest_writers_.empty()) {
+ manifest_writers_.front()->cv.Signal();
+ }
+ return Status::ColumnFamilyDropped();
+ }
+
+ return ProcessManifestWrites(writers, mu, db_directory, new_descriptor_log,
+ new_cf_options);
+}
+
+void VersionSet::LogAndApplyCFHelper(VersionEdit* edit) {
+ assert(edit->IsColumnFamilyManipulation());
+ edit->SetNextFile(next_file_number_.load());
+ // The log might have data that is not visible to memtbale and hence have not
+ // updated the last_sequence_ yet. It is also possible that the log has is
+ // expecting some new data that is not written yet. Since LastSequence is an
+ // upper bound on the sequence, it is ok to record
+ // last_allocated_sequence_ as the last sequence.
+ edit->SetLastSequence(db_options_->two_write_queues ? last_allocated_sequence_
+ : last_sequence_);
+ if (edit->is_column_family_drop_) {
+ // if we drop column family, we have to make sure to save max column family,
+ // so that we don't reuse existing ID
+ edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
+ }
+}
+
+Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd,
+ VersionBuilder* builder, VersionEdit* edit,
+ InstrumentedMutex* mu) {
+#ifdef NDEBUG
+ (void)cfd;
+#endif
+ mu->AssertHeld();
+ assert(!edit->IsColumnFamilyManipulation());
+
+ if (edit->has_log_number_) {
+ assert(edit->log_number_ >= cfd->GetLogNumber());
+ assert(edit->log_number_ < next_file_number_.load());
+ }
+
+ if (!edit->has_prev_log_number_) {
+ edit->SetPrevLogNumber(prev_log_number_);
+ }
+ edit->SetNextFile(next_file_number_.load());
+ // The log might have data that is not visible to memtbale and hence have not
+ // updated the last_sequence_ yet. It is also possible that the log has is
+ // expecting some new data that is not written yet. Since LastSequence is an
+ // upper bound on the sequence, it is ok to record
+ // last_allocated_sequence_ as the last sequence.
+ edit->SetLastSequence(db_options_->two_write_queues ? last_allocated_sequence_
+ : last_sequence_);
+
+ Status s = builder->Apply(edit);
+
+ return s;
+}
+
+Status VersionSet::ApplyOneVersionEditToBuilder(
+ VersionEdit& edit,
+ const std::unordered_map<std::string, ColumnFamilyOptions>& name_to_options,
+ std::unordered_map<int, std::string>& column_families_not_found,
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>&
+ builders,
+ VersionEditParams* version_edit_params) {
+ // Not found means that user didn't supply that column
+ // family option AND we encountered column family add
+ // record. Once we encounter column family drop record,
+ // we will delete the column family from
+ // column_families_not_found.
+ bool cf_in_not_found = (column_families_not_found.find(edit.column_family_) !=
+ column_families_not_found.end());
+ // in builders means that user supplied that column family
+ // option AND that we encountered column family add record
+ bool cf_in_builders = builders.find(edit.column_family_) != builders.end();
+
+ // they can't both be true
+ assert(!(cf_in_not_found && cf_in_builders));
+
+ ColumnFamilyData* cfd = nullptr;
+
+ if (edit.is_column_family_add_) {
+ if (cf_in_builders || cf_in_not_found) {
+ return Status::Corruption(
+ "Manifest adding the same column family twice: " +
+ edit.column_family_name_);
+ }
+ auto cf_options = name_to_options.find(edit.column_family_name_);
+ // implicitly add persistent_stats column family without requiring user
+ // to specify
+ bool is_persistent_stats_column_family =
+ edit.column_family_name_.compare(kPersistentStatsColumnFamilyName) == 0;
+ if (cf_options == name_to_options.end() &&
+ !is_persistent_stats_column_family) {
+ column_families_not_found.insert(
+ {edit.column_family_, edit.column_family_name_});
+ } else {
+ // recover persistent_stats CF from a DB that already contains it
+ if (is_persistent_stats_column_family) {
+ ColumnFamilyOptions cfo;
+ OptimizeForPersistentStats(&cfo);
+ cfd = CreateColumnFamily(cfo, &edit);
+ } else {
+ cfd = CreateColumnFamily(cf_options->second, &edit);
+ }
+ cfd->set_initialized();
+ builders.insert(std::make_pair(
+ edit.column_family_, std::unique_ptr<BaseReferencedVersionBuilder>(
+ new BaseReferencedVersionBuilder(cfd))));
+ }
+ } else if (edit.is_column_family_drop_) {
+ if (cf_in_builders) {
+ auto builder = builders.find(edit.column_family_);
+ assert(builder != builders.end());
+ builders.erase(builder);
+ cfd = column_family_set_->GetColumnFamily(edit.column_family_);
+ assert(cfd != nullptr);
+ if (cfd->UnrefAndTryDelete()) {
+ cfd = nullptr;
+ } else {
+ // who else can have reference to cfd!?
+ assert(false);
+ }
+ } else if (cf_in_not_found) {
+ column_families_not_found.erase(edit.column_family_);
+ } else {
+ return Status::Corruption(
+ "Manifest - dropping non-existing column family");
+ }
+ } else if (!cf_in_not_found) {
+ if (!cf_in_builders) {
+ return Status::Corruption(
+ "Manifest record referencing unknown column family");
+ }
+
+ cfd = column_family_set_->GetColumnFamily(edit.column_family_);
+ // this should never happen since cf_in_builders is true
+ assert(cfd != nullptr);
+
+ // if it is not column family add or column family drop,
+ // then it's a file add/delete, which should be forwarded
+ // to builder
+ auto builder = builders.find(edit.column_family_);
+ assert(builder != builders.end());
+ Status s = builder->second->version_builder()->Apply(&edit);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ return ExtractInfoFromVersionEdit(cfd, edit, version_edit_params);
+}
+
+Status VersionSet::ExtractInfoFromVersionEdit(
+ ColumnFamilyData* cfd, const VersionEdit& from_edit,
+ VersionEditParams* version_edit_params) {
+ if (cfd != nullptr) {
+ if (from_edit.has_db_id_) {
+ version_edit_params->SetDBId(from_edit.db_id_);
+ }
+ if (from_edit.has_log_number_) {
+ if (cfd->GetLogNumber() > from_edit.log_number_) {
+ ROCKS_LOG_WARN(
+ db_options_->info_log,
+ "MANIFEST corruption detected, but ignored - Log numbers in "
+ "records NOT monotonically increasing");
+ } else {
+ cfd->SetLogNumber(from_edit.log_number_);
+ version_edit_params->SetLogNumber(from_edit.log_number_);
+ }
+ }
+ if (from_edit.has_comparator_ &&
+ from_edit.comparator_ != cfd->user_comparator()->Name()) {
+ return Status::InvalidArgument(
+ cfd->user_comparator()->Name(),
+ "does not match existing comparator " + from_edit.comparator_);
+ }
+ }
+
+ if (from_edit.has_prev_log_number_) {
+ version_edit_params->SetPrevLogNumber(from_edit.prev_log_number_);
+ }
+
+ if (from_edit.has_next_file_number_) {
+ version_edit_params->SetNextFile(from_edit.next_file_number_);
+ }
+
+ if (from_edit.has_max_column_family_) {
+ version_edit_params->SetMaxColumnFamily(from_edit.max_column_family_);
+ }
+
+ if (from_edit.has_min_log_number_to_keep_) {
+ version_edit_params->min_log_number_to_keep_ =
+ std::max(version_edit_params->min_log_number_to_keep_,
+ from_edit.min_log_number_to_keep_);
+ }
+
+ if (from_edit.has_last_sequence_) {
+ version_edit_params->SetLastSequence(from_edit.last_sequence_);
+ }
+ return Status::OK();
+}
+
+Status VersionSet::GetCurrentManifestPath(const std::string& dbname,
+ FileSystem* fs,
+ std::string* manifest_path,
+ uint64_t* manifest_file_number) {
+ assert(fs != nullptr);
+ assert(manifest_path != nullptr);
+ assert(manifest_file_number != nullptr);
+
+ std::string fname;
+ Status s = ReadFileToString(fs, CurrentFileName(dbname), &fname);
+ if (!s.ok()) {
+ return s;
+ }
+ if (fname.empty() || fname.back() != '\n') {
+ return Status::Corruption("CURRENT file does not end with newline");
+ }
+ // remove the trailing '\n'
+ fname.resize(fname.size() - 1);
+ FileType type;
+ bool parse_ok = ParseFileName(fname, manifest_file_number, &type);
+ if (!parse_ok || type != kDescriptorFile) {
+ return Status::Corruption("CURRENT file corrupted");
+ }
+ *manifest_path = dbname;
+ if (dbname.back() != '/') {
+ manifest_path->push_back('/');
+ }
+ *manifest_path += fname;
+ return Status::OK();
+}
+
+Status VersionSet::ReadAndRecover(
+ log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
+ const std::unordered_map<std::string, ColumnFamilyOptions>& name_to_options,
+ std::unordered_map<int, std::string>& column_families_not_found,
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>&
+ builders,
+ VersionEditParams* version_edit_params, std::string* db_id) {
+ assert(reader != nullptr);
+ assert(read_buffer != nullptr);
+ Status s;
+ Slice record;
+ std::string scratch;
+ size_t recovered_edits = 0;
+ while (reader->ReadRecord(&record, &scratch) && s.ok()) {
+ VersionEdit edit;
+ s = edit.DecodeFrom(record);
+ if (!s.ok()) {
+ break;
+ }
+ if (edit.has_db_id_) {
+ db_id_ = edit.GetDbId();
+ if (db_id != nullptr) {
+ db_id->assign(edit.GetDbId());
+ }
+ }
+ s = read_buffer->AddEdit(&edit);
+ if (!s.ok()) {
+ break;
+ }
+ if (edit.is_in_atomic_group_) {
+ if (read_buffer->IsFull()) {
+ // Apply edits in an atomic group when we have read all edits in the
+ // group.
+ for (auto& e : read_buffer->replay_buffer()) {
+ s = ApplyOneVersionEditToBuilder(e, name_to_options,
+ column_families_not_found, builders,
+ version_edit_params);
+ if (!s.ok()) {
+ break;
+ }
+ recovered_edits++;
+ }
+ if (!s.ok()) {
+ break;
+ }
+ read_buffer->Clear();
+ }
+ } else {
+ // Apply a normal edit immediately.
+ s = ApplyOneVersionEditToBuilder(edit, name_to_options,
+ column_families_not_found, builders,
+ version_edit_params);
+ if (s.ok()) {
+ recovered_edits++;
+ }
+ }
+ }
+ if (!s.ok()) {
+ // Clear the buffer if we fail to decode/apply an edit.
+ read_buffer->Clear();
+ }
+ TEST_SYNC_POINT_CALLBACK("VersionSet::ReadAndRecover:RecoveredEdits",
+ &recovered_edits);
+ return s;
+}
+
+Status VersionSet::Recover(
+ const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
+ std::string* db_id) {
+ std::unordered_map<std::string, ColumnFamilyOptions> cf_name_to_options;
+ for (const auto& cf : column_families) {
+ cf_name_to_options.emplace(cf.name, cf.options);
+ }
+ // keeps track of column families in manifest that were not found in
+ // column families parameters. if those column families are not dropped
+ // by subsequent manifest records, Recover() will return failure status
+ std::unordered_map<int, std::string> column_families_not_found;
+
+ // Read "CURRENT" file, which contains a pointer to the current manifest file
+ std::string manifest_path;
+ Status s = GetCurrentManifestPath(dbname_, fs_, &manifest_path,
+ &manifest_file_number_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ ROCKS_LOG_INFO(db_options_->info_log, "Recovering from manifest file: %s\n",
+ manifest_path.c_str());
+
+ std::unique_ptr<SequentialFileReader> manifest_file_reader;
+ {
+ std::unique_ptr<FSSequentialFile> manifest_file;
+ s = fs_->NewSequentialFile(manifest_path,
+ fs_->OptimizeForManifestRead(file_options_),
+ &manifest_file, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ manifest_file_reader.reset(
+ new SequentialFileReader(std::move(manifest_file), manifest_path,
+ db_options_->log_readahead_size));
+ }
+
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
+ builders;
+
+ // add default column family
+ auto default_cf_iter = cf_name_to_options.find(kDefaultColumnFamilyName);
+ if (default_cf_iter == cf_name_to_options.end()) {
+ return Status::InvalidArgument("Default column family not specified");
+ }
+ VersionEdit default_cf_edit;
+ default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName);
+ default_cf_edit.SetColumnFamily(0);
+ ColumnFamilyData* default_cfd =
+ CreateColumnFamily(default_cf_iter->second, &default_cf_edit);
+ // In recovery, nobody else can access it, so it's fine to set it to be
+ // initialized earlier.
+ default_cfd->set_initialized();
+ builders.insert(
+ std::make_pair(0, std::unique_ptr<BaseReferencedVersionBuilder>(
+ new BaseReferencedVersionBuilder(default_cfd))));
+ uint64_t current_manifest_file_size = 0;
+ VersionEditParams version_edit_params;
+ {
+ VersionSet::LogReporter reporter;
+ reporter.status = &s;
+ log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
+ true /* checksum */, 0 /* log_number */);
+ Slice record;
+ std::string scratch;
+ AtomicGroupReadBuffer read_buffer;
+ s = ReadAndRecover(&reader, &read_buffer, cf_name_to_options,
+ column_families_not_found, builders,
+ &version_edit_params, db_id);
+ current_manifest_file_size = reader.GetReadOffset();
+ assert(current_manifest_file_size != 0);
+ }
+
+ if (s.ok()) {
+ if (!version_edit_params.has_next_file_number_) {
+ s = Status::Corruption("no meta-nextfile entry in descriptor");
+ } else if (!version_edit_params.has_log_number_) {
+ s = Status::Corruption("no meta-lognumber entry in descriptor");
+ } else if (!version_edit_params.has_last_sequence_) {
+ s = Status::Corruption("no last-sequence-number entry in descriptor");
+ }
+
+ if (!version_edit_params.has_prev_log_number_) {
+ version_edit_params.SetPrevLogNumber(0);
+ }
+
+ column_family_set_->UpdateMaxColumnFamily(
+ version_edit_params.max_column_family_);
+
+ // When reading DB generated using old release, min_log_number_to_keep=0.
+ // All log files will be scanned for potential prepare entries.
+ MarkMinLogNumberToKeep2PC(version_edit_params.min_log_number_to_keep_);
+ MarkFileNumberUsed(version_edit_params.prev_log_number_);
+ MarkFileNumberUsed(version_edit_params.log_number_);
+ }
+
+ // there were some column families in the MANIFEST that weren't specified
+ // in the argument. This is OK in read_only mode
+ if (read_only == false && !column_families_not_found.empty()) {
+ std::string list_of_not_found;
+ for (const auto& cf : column_families_not_found) {
+ list_of_not_found += ", " + cf.second;
+ }
+ list_of_not_found = list_of_not_found.substr(2);
+ s = Status::InvalidArgument(
+ "You have to open all column families. Column families not opened: " +
+ list_of_not_found);
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *column_family_set_) {
+ assert(builders.count(cfd->GetID()) > 0);
+ auto* builder = builders[cfd->GetID()]->version_builder();
+ if (!builder->CheckConsistencyForNumLevels()) {
+ s = Status::InvalidArgument(
+ "db has more levels than options.num_levels");
+ break;
+ }
+ }
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ if (read_only) {
+ cfd->table_cache()->SetTablesAreImmortal();
+ }
+ assert(cfd->initialized());
+ auto builders_iter = builders.find(cfd->GetID());
+ assert(builders_iter != builders.end());
+ auto builder = builders_iter->second->version_builder();
+
+ // unlimited table cache. Pre-load table handle now.
+ // Need to do it out of the mutex.
+ s = builder->LoadTableHandlers(
+ cfd->internal_stats(), db_options_->max_file_opening_threads,
+ false /* prefetch_index_and_filter_in_cache */,
+ true /* is_initial_load */,
+ cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
+ if (!s.ok()) {
+ if (db_options_->paranoid_checks) {
+ return s;
+ }
+ s = Status::OK();
+ }
+
+ Version* v = new Version(cfd, this, file_options_,
+ *cfd->GetLatestMutableCFOptions(),
+ current_version_number_++);
+ builder->SaveTo(v->storage_info());
+
+ // Install recovered version
+ v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
+ !(db_options_->skip_stats_update_on_db_open));
+ AppendVersion(cfd, v);
+ }
+
+ manifest_file_size_ = current_manifest_file_size;
+ next_file_number_.store(version_edit_params.next_file_number_ + 1);
+ last_allocated_sequence_ = version_edit_params.last_sequence_;
+ last_published_sequence_ = version_edit_params.last_sequence_;
+ last_sequence_ = version_edit_params.last_sequence_;
+ prev_log_number_ = version_edit_params.prev_log_number_;
+
+ ROCKS_LOG_INFO(
+ db_options_->info_log,
+ "Recovered from manifest file:%s succeeded,"
+ "manifest_file_number is %" PRIu64 ", next_file_number is %" PRIu64
+ ", last_sequence is %" PRIu64 ", log_number is %" PRIu64
+ ",prev_log_number is %" PRIu64 ",max_column_family is %" PRIu32
+ ",min_log_number_to_keep is %" PRIu64 "\n",
+ manifest_path.c_str(), manifest_file_number_, next_file_number_.load(),
+ last_sequence_.load(), version_edit_params.log_number_,
+ prev_log_number_, column_family_set_->GetMaxColumnFamily(),
+ min_log_number_to_keep_2pc());
+
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ ROCKS_LOG_INFO(db_options_->info_log,
+ "Column family [%s] (ID %" PRIu32
+ "), log number is %" PRIu64 "\n",
+ cfd->GetName().c_str(), cfd->GetID(), cfd->GetLogNumber());
+ }
+ }
+
+ return s;
+}
+
+Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
+ const std::string& dbname,
+ FileSystem* fs) {
+ // these are just for performance reasons, not correcntes,
+ // so we're fine using the defaults
+ FileOptions soptions;
+ // Read "CURRENT" file, which contains a pointer to the current manifest file
+ std::string manifest_path;
+ uint64_t manifest_file_number;
+ Status s =
+ GetCurrentManifestPath(dbname, fs, &manifest_path, &manifest_file_number);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<SequentialFileReader> file_reader;
+ {
+ std::unique_ptr<FSSequentialFile> file;
+ s = fs->NewSequentialFile(manifest_path, soptions, &file, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ file_reader.reset(new SequentialFileReader(std::move(file), manifest_path));
+ }
+
+ std::map<uint32_t, std::string> column_family_names;
+ // default column family is always implicitly there
+ column_family_names.insert({0, kDefaultColumnFamilyName});
+ VersionSet::LogReporter reporter;
+ reporter.status = &s;
+ log::Reader reader(nullptr, std::move(file_reader), &reporter,
+ true /* checksum */, 0 /* log_number */);
+ Slice record;
+ std::string scratch;
+ while (reader.ReadRecord(&record, &scratch) && s.ok()) {
+ VersionEdit edit;
+ s = edit.DecodeFrom(record);
+ if (!s.ok()) {
+ break;
+ }
+ if (edit.is_column_family_add_) {
+ if (column_family_names.find(edit.column_family_) !=
+ column_family_names.end()) {
+ s = Status::Corruption("Manifest adding the same column family twice");
+ break;
+ }
+ column_family_names.insert(
+ {edit.column_family_, edit.column_family_name_});
+ } else if (edit.is_column_family_drop_) {
+ if (column_family_names.find(edit.column_family_) ==
+ column_family_names.end()) {
+ s = Status::Corruption(
+ "Manifest - dropping non-existing column family");
+ break;
+ }
+ column_family_names.erase(edit.column_family_);
+ }
+ }
+
+ column_families->clear();
+ if (s.ok()) {
+ for (const auto& iter : column_family_names) {
+ column_families->push_back(iter.second);
+ }
+ }
+
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
+ const Options* options,
+ const FileOptions& file_options,
+ int new_levels) {
+ if (new_levels <= 1) {
+ return Status::InvalidArgument(
+ "Number of levels needs to be bigger than 1");
+ }
+
+ ImmutableDBOptions db_options(*options);
+ ColumnFamilyOptions cf_options(*options);
+ std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
+ options->table_cache_numshardbits));
+ WriteController wc(options->delayed_write_rate);
+ WriteBufferManager wb(options->db_write_buffer_size);
+ VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
+ /*block_cache_tracer=*/nullptr);
+ Status status;
+
+ std::vector<ColumnFamilyDescriptor> dummy;
+ ColumnFamilyDescriptor dummy_descriptor(kDefaultColumnFamilyName,
+ ColumnFamilyOptions(*options));
+ dummy.push_back(dummy_descriptor);
+ status = versions.Recover(dummy);
+ if (!status.ok()) {
+ return status;
+ }
+
+ Version* current_version =
+ versions.GetColumnFamilySet()->GetDefault()->current();
+ auto* vstorage = current_version->storage_info();
+ int current_levels = vstorage->num_levels();
+
+ if (current_levels <= new_levels) {
+ return Status::OK();
+ }
+
+ // Make sure there are file only on one level from
+ // (new_levels-1) to (current_levels-1)
+ int first_nonempty_level = -1;
+ int first_nonempty_level_filenum = 0;
+ for (int i = new_levels - 1; i < current_levels; i++) {
+ int file_num = vstorage->NumLevelFiles(i);
+ if (file_num != 0) {
+ if (first_nonempty_level < 0) {
+ first_nonempty_level = i;
+ first_nonempty_level_filenum = file_num;
+ } else {
+ char msg[255];
+ snprintf(msg, sizeof(msg),
+ "Found at least two levels containing files: "
+ "[%d:%d],[%d:%d].\n",
+ first_nonempty_level, first_nonempty_level_filenum, i,
+ file_num);
+ return Status::InvalidArgument(msg);
+ }
+ }
+ }
+
+ // we need to allocate an array with the old number of levels size to
+ // avoid SIGSEGV in WriteCurrentStatetoManifest()
+ // however, all levels bigger or equal to new_levels will be empty
+ std::vector<FileMetaData*>* new_files_list =
+ new std::vector<FileMetaData*>[current_levels];
+ for (int i = 0; i < new_levels - 1; i++) {
+ new_files_list[i] = vstorage->LevelFiles(i);
+ }
+
+ if (first_nonempty_level > 0) {
+ new_files_list[new_levels - 1] = vstorage->LevelFiles(first_nonempty_level);
+ }
+
+ delete[] vstorage -> files_;
+ vstorage->files_ = new_files_list;
+ vstorage->num_levels_ = new_levels;
+
+ MutableCFOptions mutable_cf_options(*options);
+ VersionEdit ve;
+ InstrumentedMutex dummy_mutex;
+ InstrumentedMutexLock l(&dummy_mutex);
+ return versions.LogAndApply(
+ versions.GetColumnFamilySet()->GetDefault(),
+ mutable_cf_options, &ve, &dummy_mutex, nullptr, true);
+}
+
+// Get the checksum information including the checksum and checksum function
+// name of all SST files in VersionSet. Store the information in
+// FileChecksumList which contains a map from file number to its checksum info.
+// If DB is not running, make sure call VersionSet::Recover() to load the file
+// metadata from Manifest to VersionSet before calling this function.
+Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
+ // Clean the previously stored checksum information if any.
+ if (checksum_list == nullptr) {
+ return Status::InvalidArgument("checksum_list is nullptr");
+ }
+ checksum_list->reset();
+
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped() || !cfd->initialized()) {
+ continue;
+ }
+ for (int level = 0; level < cfd->NumberLevels(); level++) {
+ for (const auto& file :
+ cfd->current()->storage_info()->LevelFiles(level)) {
+ checksum_list->InsertOneFileChecksum(file->fd.GetNumber(),
+ file->file_checksum,
+ file->file_checksum_func_name);
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status VersionSet::DumpManifest(Options& options, std::string& dscname,
+ bool verbose, bool hex, bool json) {
+ // Open the specified manifest file.
+ std::unique_ptr<SequentialFileReader> file_reader;
+ Status s;
+ {
+ std::unique_ptr<FSSequentialFile> file;
+ s = options.file_system->NewSequentialFile(
+ dscname,
+ options.file_system->OptimizeForManifestRead(file_options_), &file,
+ nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ file_reader.reset(new SequentialFileReader(
+ std::move(file), dscname, db_options_->log_readahead_size));
+ }
+
+ bool have_prev_log_number = false;
+ bool have_next_file = false;
+ bool have_last_sequence = false;
+ uint64_t next_file = 0;
+ uint64_t last_sequence = 0;
+ uint64_t previous_log_number = 0;
+ int count = 0;
+ std::unordered_map<uint32_t, std::string> comparators;
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
+ builders;
+
+ // add default column family
+ VersionEdit default_cf_edit;
+ default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName);
+ default_cf_edit.SetColumnFamily(0);
+ ColumnFamilyData* default_cfd =
+ CreateColumnFamily(ColumnFamilyOptions(options), &default_cf_edit);
+ builders.insert(
+ std::make_pair(0, std::unique_ptr<BaseReferencedVersionBuilder>(
+ new BaseReferencedVersionBuilder(default_cfd))));
+
+ {
+ VersionSet::LogReporter reporter;
+ reporter.status = &s;
+ log::Reader reader(nullptr, std::move(file_reader), &reporter,
+ true /* checksum */, 0 /* log_number */);
+ Slice record;
+ std::string scratch;
+ while (reader.ReadRecord(&record, &scratch) && s.ok()) {
+ VersionEdit edit;
+ s = edit.DecodeFrom(record);
+ if (!s.ok()) {
+ break;
+ }
+
+ // Write out each individual edit
+ if (verbose && !json) {
+ printf("%s\n", edit.DebugString(hex).c_str());
+ } else if (json) {
+ printf("%s\n", edit.DebugJSON(count, hex).c_str());
+ }
+ count++;
+
+ bool cf_in_builders =
+ builders.find(edit.column_family_) != builders.end();
+
+ if (edit.has_comparator_) {
+ comparators.insert({edit.column_family_, edit.comparator_});
+ }
+
+ ColumnFamilyData* cfd = nullptr;
+
+ if (edit.is_column_family_add_) {
+ if (cf_in_builders) {
+ s = Status::Corruption(
+ "Manifest adding the same column family twice");
+ break;
+ }
+ cfd = CreateColumnFamily(ColumnFamilyOptions(options), &edit);
+ cfd->set_initialized();
+ builders.insert(std::make_pair(
+ edit.column_family_, std::unique_ptr<BaseReferencedVersionBuilder>(
+ new BaseReferencedVersionBuilder(cfd))));
+ } else if (edit.is_column_family_drop_) {
+ if (!cf_in_builders) {
+ s = Status::Corruption(
+ "Manifest - dropping non-existing column family");
+ break;
+ }
+ auto builder_iter = builders.find(edit.column_family_);
+ builders.erase(builder_iter);
+ comparators.erase(edit.column_family_);
+ cfd = column_family_set_->GetColumnFamily(edit.column_family_);
+ assert(cfd != nullptr);
+ cfd->UnrefAndTryDelete();
+ cfd = nullptr;
+ } else {
+ if (!cf_in_builders) {
+ s = Status::Corruption(
+ "Manifest record referencing unknown column family");
+ break;
+ }
+
+ cfd = column_family_set_->GetColumnFamily(edit.column_family_);
+ // this should never happen since cf_in_builders is true
+ assert(cfd != nullptr);
+
+ // if it is not column family add or column family drop,
+ // then it's a file add/delete, which should be forwarded
+ // to builder
+ auto builder = builders.find(edit.column_family_);
+ assert(builder != builders.end());
+ s = builder->second->version_builder()->Apply(&edit);
+ if (!s.ok()) {
+ break;
+ }
+ }
+
+ if (cfd != nullptr && edit.has_log_number_) {
+ cfd->SetLogNumber(edit.log_number_);
+ }
+
+
+ if (edit.has_prev_log_number_) {
+ previous_log_number = edit.prev_log_number_;
+ have_prev_log_number = true;
+ }
+
+ if (edit.has_next_file_number_) {
+ next_file = edit.next_file_number_;
+ have_next_file = true;
+ }
+
+ if (edit.has_last_sequence_) {
+ last_sequence = edit.last_sequence_;
+ have_last_sequence = true;
+ }
+
+ if (edit.has_max_column_family_) {
+ column_family_set_->UpdateMaxColumnFamily(edit.max_column_family_);
+ }
+
+ if (edit.has_min_log_number_to_keep_) {
+ MarkMinLogNumberToKeep2PC(edit.min_log_number_to_keep_);
+ }
+ }
+ }
+ file_reader.reset();
+
+ if (s.ok()) {
+ if (!have_next_file) {
+ s = Status::Corruption("no meta-nextfile entry in descriptor");
+ printf("no meta-nextfile entry in descriptor");
+ } else if (!have_last_sequence) {
+ printf("no last-sequence-number entry in descriptor");
+ s = Status::Corruption("no last-sequence-number entry in descriptor");
+ }
+
+ if (!have_prev_log_number) {
+ previous_log_number = 0;
+ }
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ auto builders_iter = builders.find(cfd->GetID());
+ assert(builders_iter != builders.end());
+ auto builder = builders_iter->second->version_builder();
+
+ Version* v = new Version(cfd, this, file_options_,
+ *cfd->GetLatestMutableCFOptions(),
+ current_version_number_++);
+ builder->SaveTo(v->storage_info());
+ v->PrepareApply(*cfd->GetLatestMutableCFOptions(), false);
+
+ printf("--------------- Column family \"%s\" (ID %" PRIu32
+ ") --------------\n",
+ cfd->GetName().c_str(), cfd->GetID());
+ printf("log number: %" PRIu64 "\n", cfd->GetLogNumber());
+ auto comparator = comparators.find(cfd->GetID());
+ if (comparator != comparators.end()) {
+ printf("comparator: %s\n", comparator->second.c_str());
+ } else {
+ printf("comparator: <NO COMPARATOR>\n");
+ }
+ printf("%s \n", v->DebugString(hex).c_str());
+ delete v;
+ }
+
+ next_file_number_.store(next_file + 1);
+ last_allocated_sequence_ = last_sequence;
+ last_published_sequence_ = last_sequence;
+ last_sequence_ = last_sequence;
+ prev_log_number_ = previous_log_number;
+
+ printf("next_file_number %" PRIu64 " last_sequence %" PRIu64
+ " prev_log_number %" PRIu64 " max_column_family %" PRIu32
+ " min_log_number_to_keep "
+ "%" PRIu64 "\n",
+ next_file_number_.load(), last_sequence, previous_log_number,
+ column_family_set_->GetMaxColumnFamily(),
+ min_log_number_to_keep_2pc());
+ }
+
+ return s;
+}
+#endif // ROCKSDB_LITE
+
+void VersionSet::MarkFileNumberUsed(uint64_t number) {
+ // only called during recovery and repair which are single threaded, so this
+ // works because there can't be concurrent calls
+ if (next_file_number_.load(std::memory_order_relaxed) <= number) {
+ next_file_number_.store(number + 1, std::memory_order_relaxed);
+ }
+}
+// Called only either from ::LogAndApply which is protected by mutex or during
+// recovery which is single-threaded.
+void VersionSet::MarkMinLogNumberToKeep2PC(uint64_t number) {
+ if (min_log_number_to_keep_2pc_.load(std::memory_order_relaxed) < number) {
+ min_log_number_to_keep_2pc_.store(number, std::memory_order_relaxed);
+ }
+}
+
+Status VersionSet::WriteCurrentStateToManifest(
+ const std::unordered_map<uint32_t, MutableCFState>& curr_state,
+ log::Writer* log) {
+ // TODO: Break up into multiple records to reduce memory usage on recovery?
+
+ // WARNING: This method doesn't hold a mutex!!
+
+ // This is done without DB mutex lock held, but only within single-threaded
+ // LogAndApply. Column family manipulations can only happen within LogAndApply
+ // (the same single thread), so we're safe to iterate.
+
+ if (db_options_->write_dbid_to_manifest) {
+ VersionEdit edit_for_db_id;
+ assert(!db_id_.empty());
+ edit_for_db_id.SetDBId(db_id_);
+ std::string db_id_record;
+ if (!edit_for_db_id.EncodeTo(&db_id_record)) {
+ return Status::Corruption("Unable to Encode VersionEdit:" +
+ edit_for_db_id.DebugString(true));
+ }
+ Status add_record = log->AddRecord(db_id_record);
+ if (!add_record.ok()) {
+ return add_record;
+ }
+ }
+
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ assert(cfd->initialized());
+ {
+ // Store column family info
+ VersionEdit edit;
+ if (cfd->GetID() != 0) {
+ // default column family is always there,
+ // no need to explicitly write it
+ edit.AddColumnFamily(cfd->GetName());
+ edit.SetColumnFamily(cfd->GetID());
+ }
+ edit.SetComparatorName(
+ cfd->internal_comparator().user_comparator()->Name());
+ std::string record;
+ if (!edit.EncodeTo(&record)) {
+ return Status::Corruption(
+ "Unable to Encode VersionEdit:" + edit.DebugString(true));
+ }
+ Status s = log->AddRecord(record);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ {
+ // Save files
+ VersionEdit edit;
+ edit.SetColumnFamily(cfd->GetID());
+
+ for (int level = 0; level < cfd->NumberLevels(); level++) {
+ for (const auto& f :
+ cfd->current()->storage_info()->LevelFiles(level)) {
+ edit.AddFile(level, f->fd.GetNumber(), f->fd.GetPathId(),
+ f->fd.GetFileSize(), f->smallest, f->largest,
+ f->fd.smallest_seqno, f->fd.largest_seqno,
+ f->marked_for_compaction, f->oldest_blob_file_number,
+ f->oldest_ancester_time, f->file_creation_time,
+ f->file_checksum, f->file_checksum_func_name);
+ }
+ }
+ const auto iter = curr_state.find(cfd->GetID());
+ assert(iter != curr_state.end());
+ uint64_t log_number = iter->second.log_number;
+ edit.SetLogNumber(log_number);
+ std::string record;
+ if (!edit.EncodeTo(&record)) {
+ return Status::Corruption(
+ "Unable to Encode VersionEdit:" + edit.DebugString(true));
+ }
+ Status s = log->AddRecord(record);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+ return Status::OK();
+}
+
+// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
+// function is called repeatedly with consecutive pairs of slices. For example
+// if the slice list is [a, b, c, d] this function is called with arguments
+// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
+// we avoid doing binary search for the keys b and c twice and instead somehow
+// maintain state of where they first appear in the files.
+uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
+ Version* v, const Slice& start,
+ const Slice& end, int start_level,
+ int end_level, TableReaderCaller caller) {
+ const auto& icmp = v->cfd_->internal_comparator();
+
+ // pre-condition
+ assert(icmp.Compare(start, end) <= 0);
+
+ uint64_t total_full_size = 0;
+ const auto* vstorage = v->storage_info();
+ const int num_non_empty_levels = vstorage->num_non_empty_levels();
+ end_level = (end_level == -1) ? num_non_empty_levels
+ : std::min(end_level, num_non_empty_levels);
+
+ assert(start_level <= end_level);
+
+ // Outline of the optimization that uses options.files_size_error_margin.
+ // When approximating the files total size that is used to store a keys range,
+ // we first sum up the sizes of the files that fully fall into the range.
+ // Then we sum up the sizes of all the files that may intersect with the range
+ // (this includes all files in L0 as well). Then, if total_intersecting_size
+ // is smaller than total_full_size * options.files_size_error_margin - we can
+ // infer that the intersecting files have a sufficiently negligible
+ // contribution to the total size, and we can approximate the storage required
+ // for the keys in range as just half of the intersecting_files_size.
+ // E.g., if the value of files_size_error_margin is 0.1, then the error of the
+ // approximation is limited to only ~10% of the total size of files that fully
+ // fall into the keys range. In such case, this helps to avoid a costly
+ // process of binary searching the intersecting files that is required only
+ // for a more precise calculation of the total size.
+
+ autovector<FdWithKeyRange*, 32> first_files;
+ autovector<FdWithKeyRange*, 16> last_files;
+
+ // scan all the levels
+ for (int level = start_level; level < end_level; ++level) {
+ const LevelFilesBrief& files_brief = vstorage->LevelFilesBrief(level);
+ if (files_brief.num_files == 0) {
+ // empty level, skip exploration
+ continue;
+ }
+
+ if (level == 0) {
+ // level 0 files are not in sorted order, we need to iterate through
+ // the list to compute the total bytes that require scanning,
+ // so handle the case explicitly (similarly to first_files case)
+ for (size_t i = 0; i < files_brief.num_files; i++) {
+ first_files.push_back(&files_brief.files[i]);
+ }
+ continue;
+ }
+
+ assert(level > 0);
+ assert(files_brief.num_files > 0);
+
+ // identify the file position for start key
+ const int idx_start =
+ FindFileInRange(icmp, files_brief, start, 0,
+ static_cast<uint32_t>(files_brief.num_files - 1));
+ assert(static_cast<size_t>(idx_start) < files_brief.num_files);
+
+ // identify the file position for end key
+ int idx_end = idx_start;
+ if (icmp.Compare(files_brief.files[idx_end].largest_key, end) < 0) {
+ idx_end =
+ FindFileInRange(icmp, files_brief, end, idx_start,
+ static_cast<uint32_t>(files_brief.num_files - 1));
+ }
+ assert(idx_end >= idx_start &&
+ static_cast<size_t>(idx_end) < files_brief.num_files);
+
+ // scan all files from the starting index to the ending index
+ // (inferred from the sorted order)
+
+ // first scan all the intermediate full files (excluding first and last)
+ for (int i = idx_start + 1; i < idx_end; ++i) {
+ uint64_t file_size = files_brief.files[i].fd.GetFileSize();
+ // The entire file falls into the range, so we can just take its size.
+ assert(file_size ==
+ ApproximateSize(v, files_brief.files[i], start, end, caller));
+ total_full_size += file_size;
+ }
+
+ // save the first and the last files (which may be the same file), so we
+ // can scan them later.
+ first_files.push_back(&files_brief.files[idx_start]);
+ if (idx_start != idx_end) {
+ // we need to estimate size for both files, only if they are different
+ last_files.push_back(&files_brief.files[idx_end]);
+ }
+ }
+
+ // The sum of all file sizes that intersect the [start, end] keys range.
+ uint64_t total_intersecting_size = 0;
+ for (const auto* file_ptr : first_files) {
+ total_intersecting_size += file_ptr->fd.GetFileSize();
+ }
+ for (const auto* file_ptr : last_files) {
+ total_intersecting_size += file_ptr->fd.GetFileSize();
+ }
+
+ // Now scan all the first & last files at each level, and estimate their size.
+ // If the total_intersecting_size is less than X% of the total_full_size - we
+ // want to approximate the result in order to avoid the costly binary search
+ // inside ApproximateSize. We use half of file size as an approximation below.
+
+ const double margin = options.files_size_error_margin;
+ if (margin > 0 && total_intersecting_size <
+ static_cast<uint64_t>(total_full_size * margin)) {
+ total_full_size += total_intersecting_size / 2;
+ } else {
+ // Estimate for all the first files, at each level
+ for (const auto file_ptr : first_files) {
+ total_full_size += ApproximateSize(v, *file_ptr, start, end, caller);
+ }
+
+ // Estimate for all the last files, at each level
+ for (const auto file_ptr : last_files) {
+ // We could use ApproximateSize here, but calling ApproximateOffsetOf
+ // directly is just more efficient.
+ total_full_size += ApproximateOffsetOf(v, *file_ptr, end, caller);
+ }
+ }
+
+ return total_full_size;
+}
+
+uint64_t VersionSet::ApproximateOffsetOf(Version* v, const FdWithKeyRange& f,
+ const Slice& key,
+ TableReaderCaller caller) {
+ // pre-condition
+ assert(v);
+ const auto& icmp = v->cfd_->internal_comparator();
+
+ uint64_t result = 0;
+ if (icmp.Compare(f.largest_key, key) <= 0) {
+ // Entire file is before "key", so just add the file size
+ result = f.fd.GetFileSize();
+ } else if (icmp.Compare(f.smallest_key, key) > 0) {
+ // Entire file is after "key", so ignore
+ result = 0;
+ } else {
+ // "key" falls in the range for this table. Add the
+ // approximate offset of "key" within the table.
+ TableCache* table_cache = v->cfd_->table_cache();
+ if (table_cache != nullptr) {
+ result = table_cache->ApproximateOffsetOf(
+ key, f.file_metadata->fd, caller, icmp,
+ v->GetMutableCFOptions().prefix_extractor.get());
+ }
+ }
+ return result;
+}
+
+uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
+ const Slice& start, const Slice& end,
+ TableReaderCaller caller) {
+ // pre-condition
+ assert(v);
+ const auto& icmp = v->cfd_->internal_comparator();
+ assert(icmp.Compare(start, end) <= 0);
+
+ if (icmp.Compare(f.largest_key, start) <= 0 ||
+ icmp.Compare(f.smallest_key, end) > 0) {
+ // Entire file is before or after the start/end keys range
+ return 0;
+ }
+
+ if (icmp.Compare(f.smallest_key, start) >= 0) {
+ // Start of the range is before the file start - approximate by end offset
+ return ApproximateOffsetOf(v, f, end, caller);
+ }
+
+ if (icmp.Compare(f.largest_key, end) < 0) {
+ // End of the range is after the file end - approximate by subtracting
+ // start offset from the file size
+ uint64_t start_offset = ApproximateOffsetOf(v, f, start, caller);
+ assert(f.fd.GetFileSize() >= start_offset);
+ return f.fd.GetFileSize() - start_offset;
+ }
+
+ // The interval falls entirely in the range for this file.
+ TableCache* table_cache = v->cfd_->table_cache();
+ if (table_cache == nullptr) {
+ return 0;
+ }
+ return table_cache->ApproximateSize(
+ start, end, f.file_metadata->fd, caller, icmp,
+ v->GetMutableCFOptions().prefix_extractor.get());
+}
+
+void VersionSet::AddLiveFiles(std::vector<FileDescriptor>* live_list) {
+ // pre-calculate space requirement
+ int64_t total_files = 0;
+ for (auto cfd : *column_family_set_) {
+ if (!cfd->initialized()) {
+ continue;
+ }
+ Version* dummy_versions = cfd->dummy_versions();
+ for (Version* v = dummy_versions->next_; v != dummy_versions;
+ v = v->next_) {
+ const auto* vstorage = v->storage_info();
+ for (int level = 0; level < vstorage->num_levels(); level++) {
+ total_files += vstorage->LevelFiles(level).size();
+ }
+ }
+ }
+
+ // just one time extension to the right size
+ live_list->reserve(live_list->size() + static_cast<size_t>(total_files));
+
+ for (auto cfd : *column_family_set_) {
+ if (!cfd->initialized()) {
+ continue;
+ }
+ auto* current = cfd->current();
+ bool found_current = false;
+ Version* dummy_versions = cfd->dummy_versions();
+ for (Version* v = dummy_versions->next_; v != dummy_versions;
+ v = v->next_) {
+ v->AddLiveFiles(live_list);
+ if (v == current) {
+ found_current = true;
+ }
+ }
+ if (!found_current && current != nullptr) {
+ // Should never happen unless it is a bug.
+ assert(false);
+ current->AddLiveFiles(live_list);
+ }
+ }
+}
+
+InternalIterator* VersionSet::MakeInputIterator(
+ const Compaction* c, RangeDelAggregator* range_del_agg,
+ const FileOptions& file_options_compactions) {
+ auto cfd = c->column_family_data();
+ ReadOptions read_options;
+ read_options.verify_checksums = true;
+ read_options.fill_cache = false;
+ // Compaction iterators shouldn't be confined to a single prefix.
+ // Compactions use Seek() for
+ // (a) concurrent compactions,
+ // (b) CompactionFilter::Decision::kRemoveAndSkipUntil.
+ read_options.total_order_seek = true;
+
+ // Level-0 files have to be merged together. For other levels,
+ // we will make a concatenating iterator per level.
+ // TODO(opt): use concatenating iterator for level-0 if there is no overlap
+ const size_t space = (c->level() == 0 ? c->input_levels(0)->num_files +
+ c->num_input_levels() - 1
+ : c->num_input_levels());
+ InternalIterator** list = new InternalIterator* [space];
+ size_t num = 0;
+ for (size_t which = 0; which < c->num_input_levels(); which++) {
+ if (c->input_levels(which)->num_files != 0) {
+ if (c->level(which) == 0) {
+ const LevelFilesBrief* flevel = c->input_levels(which);
+ for (size_t i = 0; i < flevel->num_files; i++) {
+ list[num++] = cfd->table_cache()->NewIterator(
+ read_options, file_options_compactions,
+ cfd->internal_comparator(),
+ *flevel->files[i].file_metadata, range_del_agg,
+ c->mutable_cf_options()->prefix_extractor.get(),
+ /*table_reader_ptr=*/nullptr,
+ /*file_read_hist=*/nullptr, TableReaderCaller::kCompaction,
+ /*arena=*/nullptr,
+ /*skip_filters=*/false, /*level=*/static_cast<int>(which),
+ /*smallest_compaction_key=*/nullptr,
+ /*largest_compaction_key=*/nullptr);
+ }
+ } else {
+ // Create concatenating iterator for the files from this level
+ list[num++] = new LevelIterator(
+ cfd->table_cache(), read_options, file_options_compactions,
+ cfd->internal_comparator(), c->input_levels(which),
+ c->mutable_cf_options()->prefix_extractor.get(),
+ /*should_sample=*/false,
+ /*no per level latency histogram=*/nullptr,
+ TableReaderCaller::kCompaction, /*skip_filters=*/false,
+ /*level=*/static_cast<int>(which), range_del_agg,
+ c->boundaries(which));
+ }
+ }
+ }
+ assert(num <= space);
+ InternalIterator* result =
+ NewMergingIterator(&c->column_family_data()->internal_comparator(), list,
+ static_cast<int>(num));
+ delete[] list;
+ return result;
+}
+
+// verify that the files listed in this compaction are present
+// in the current version
+bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) {
+#ifndef NDEBUG
+ Version* version = c->column_family_data()->current();
+ const VersionStorageInfo* vstorage = version->storage_info();
+ if (c->input_version() != version) {
+ ROCKS_LOG_INFO(
+ db_options_->info_log,
+ "[%s] compaction output being applied to a different base version from"
+ " input version",
+ c->column_family_data()->GetName().c_str());
+
+ if (vstorage->compaction_style_ == kCompactionStyleLevel &&
+ c->start_level() == 0 && c->num_input_levels() > 2U) {
+ // We are doing a L0->base_level compaction. The assumption is if
+ // base level is not L1, levels from L1 to base_level - 1 is empty.
+ // This is ensured by having one compaction from L0 going on at the
+ // same time in level-based compaction. So that during the time, no
+ // compaction/flush can put files to those levels.
+ for (int l = c->start_level() + 1; l < c->output_level(); l++) {
+ if (vstorage->NumLevelFiles(l) != 0) {
+ return false;
+ }
+ }
+ }
+ }
+
+ for (size_t input = 0; input < c->num_input_levels(); ++input) {
+ int level = c->level(input);
+ for (size_t i = 0; i < c->num_input_files(input); ++i) {
+ uint64_t number = c->input(input, i)->fd.GetNumber();
+ bool found = false;
+ for (size_t j = 0; j < vstorage->files_[level].size(); j++) {
+ FileMetaData* f = vstorage->files_[level][j];
+ if (f->fd.GetNumber() == number) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false; // input files non existent in current version
+ }
+ }
+ }
+#else
+ (void)c;
+#endif
+ return true; // everything good
+}
+
+Status VersionSet::GetMetadataForFile(uint64_t number, int* filelevel,
+ FileMetaData** meta,
+ ColumnFamilyData** cfd) {
+ for (auto cfd_iter : *column_family_set_) {
+ if (!cfd_iter->initialized()) {
+ continue;
+ }
+ Version* version = cfd_iter->current();
+ const auto* vstorage = version->storage_info();
+ for (int level = 0; level < vstorage->num_levels(); level++) {
+ for (const auto& file : vstorage->LevelFiles(level)) {
+ if (file->fd.GetNumber() == number) {
+ *meta = file;
+ *filelevel = level;
+ *cfd = cfd_iter;
+ return Status::OK();
+ }
+ }
+ }
+ }
+ return Status::NotFound("File not present in any level");
+}
+
+void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped() || !cfd->initialized()) {
+ continue;
+ }
+ for (int level = 0; level < cfd->NumberLevels(); level++) {
+ for (const auto& file :
+ cfd->current()->storage_info()->LevelFiles(level)) {
+ LiveFileMetaData filemetadata;
+ filemetadata.column_family_name = cfd->GetName();
+ uint32_t path_id = file->fd.GetPathId();
+ if (path_id < cfd->ioptions()->cf_paths.size()) {
+ filemetadata.db_path = cfd->ioptions()->cf_paths[path_id].path;
+ } else {
+ assert(!cfd->ioptions()->cf_paths.empty());
+ filemetadata.db_path = cfd->ioptions()->cf_paths.back().path;
+ }
+ const uint64_t file_number = file->fd.GetNumber();
+ filemetadata.name = MakeTableFileName("", file_number);
+ filemetadata.file_number = file_number;
+ filemetadata.level = level;
+ filemetadata.size = static_cast<size_t>(file->fd.GetFileSize());
+ filemetadata.smallestkey = file->smallest.user_key().ToString();
+ filemetadata.largestkey = file->largest.user_key().ToString();
+ filemetadata.smallest_seqno = file->fd.smallest_seqno;
+ filemetadata.largest_seqno = file->fd.largest_seqno;
+ filemetadata.num_reads_sampled = file->stats.num_reads_sampled.load(
+ std::memory_order_relaxed);
+ filemetadata.being_compacted = file->being_compacted;
+ filemetadata.num_entries = file->num_entries;
+ filemetadata.num_deletions = file->num_deletions;
+ filemetadata.oldest_blob_file_number = file->oldest_blob_file_number;
+ filemetadata.file_checksum = file->file_checksum;
+ filemetadata.file_checksum_func_name = file->file_checksum_func_name;
+ metadata->push_back(filemetadata);
+ }
+ }
+ }
+}
+
+void VersionSet::GetObsoleteFiles(std::vector<ObsoleteFileInfo>* files,
+ std::vector<std::string>* manifest_filenames,
+ uint64_t min_pending_output) {
+ assert(manifest_filenames->empty());
+ obsolete_manifests_.swap(*manifest_filenames);
+ std::vector<ObsoleteFileInfo> pending_files;
+ for (auto& f : obsolete_files_) {
+ if (f.metadata->fd.GetNumber() < min_pending_output) {
+ files->push_back(std::move(f));
+ } else {
+ pending_files.push_back(std::move(f));
+ }
+ }
+ obsolete_files_.swap(pending_files);
+}
+
+ColumnFamilyData* VersionSet::CreateColumnFamily(
+ const ColumnFamilyOptions& cf_options, VersionEdit* edit) {
+ assert(edit->is_column_family_add_);
+
+ MutableCFOptions dummy_cf_options;
+ Version* dummy_versions =
+ new Version(nullptr, this, file_options_, dummy_cf_options);
+ // Ref() dummy version once so that later we can call Unref() to delete it
+ // by avoiding calling "delete" explicitly (~Version is private)
+ dummy_versions->Ref();
+ auto new_cfd = column_family_set_->CreateColumnFamily(
+ edit->column_family_name_, edit->column_family_, dummy_versions,
+ cf_options);
+
+ Version* v = new Version(new_cfd, this, file_options_,
+ *new_cfd->GetLatestMutableCFOptions(),
+ current_version_number_++);
+
+ // Fill level target base information.
+ v->storage_info()->CalculateBaseBytes(*new_cfd->ioptions(),
+ *new_cfd->GetLatestMutableCFOptions());
+ AppendVersion(new_cfd, v);
+ // GetLatestMutableCFOptions() is safe here without mutex since the
+ // cfd is not available to client
+ new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions(),
+ LastSequence());
+ new_cfd->SetLogNumber(edit->log_number_);
+ return new_cfd;
+}
+
+uint64_t VersionSet::GetNumLiveVersions(Version* dummy_versions) {
+ uint64_t count = 0;
+ for (Version* v = dummy_versions->next_; v != dummy_versions; v = v->next_) {
+ count++;
+ }
+ return count;
+}
+
+uint64_t VersionSet::GetTotalSstFilesSize(Version* dummy_versions) {
+ std::unordered_set<uint64_t> unique_files;
+ uint64_t total_files_size = 0;
+ for (Version* v = dummy_versions->next_; v != dummy_versions; v = v->next_) {
+ VersionStorageInfo* storage_info = v->storage_info();
+ for (int level = 0; level < storage_info->num_levels_; level++) {
+ for (const auto& file_meta : storage_info->LevelFiles(level)) {
+ if (unique_files.find(file_meta->fd.packed_number_and_path_id) ==
+ unique_files.end()) {
+ unique_files.insert(file_meta->fd.packed_number_and_path_id);
+ total_files_size += file_meta->fd.GetFileSize();
+ }
+ }
+ }
+ }
+ return total_files_size;
+}
+
+ReactiveVersionSet::ReactiveVersionSet(const std::string& dbname,
+ const ImmutableDBOptions* _db_options,
+ const FileOptions& _file_options,
+ Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller)
+ : VersionSet(dbname, _db_options, _file_options, table_cache,
+ write_buffer_manager, write_controller,
+ /*block_cache_tracer=*/nullptr),
+ number_of_edits_to_skip_(0) {}
+
+ReactiveVersionSet::~ReactiveVersionSet() {}
+
+Status ReactiveVersionSet::Recover(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
+ std::unique_ptr<log::Reader::Reporter>* manifest_reporter,
+ std::unique_ptr<Status>* manifest_reader_status) {
+ assert(manifest_reader != nullptr);
+ assert(manifest_reporter != nullptr);
+ assert(manifest_reader_status != nullptr);
+
+ std::unordered_map<std::string, ColumnFamilyOptions> cf_name_to_options;
+ for (const auto& cf : column_families) {
+ cf_name_to_options.insert({cf.name, cf.options});
+ }
+
+ // add default column family
+ auto default_cf_iter = cf_name_to_options.find(kDefaultColumnFamilyName);
+ if (default_cf_iter == cf_name_to_options.end()) {
+ return Status::InvalidArgument("Default column family not specified");
+ }
+ VersionEdit default_cf_edit;
+ default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName);
+ default_cf_edit.SetColumnFamily(0);
+ ColumnFamilyData* default_cfd =
+ CreateColumnFamily(default_cf_iter->second, &default_cf_edit);
+ // In recovery, nobody else can access it, so it's fine to set it to be
+ // initialized earlier.
+ default_cfd->set_initialized();
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
+ builders;
+ std::unordered_map<int, std::string> column_families_not_found;
+ builders.insert(
+ std::make_pair(0, std::unique_ptr<BaseReferencedVersionBuilder>(
+ new BaseReferencedVersionBuilder(default_cfd))));
+
+ manifest_reader_status->reset(new Status());
+ manifest_reporter->reset(new LogReporter());
+ static_cast<LogReporter*>(manifest_reporter->get())->status =
+ manifest_reader_status->get();
+ Status s = MaybeSwitchManifest(manifest_reporter->get(), manifest_reader);
+ log::Reader* reader = manifest_reader->get();
+
+ int retry = 0;
+ VersionEdit version_edit;
+ while (s.ok() && retry < 1) {
+ assert(reader != nullptr);
+ Slice record;
+ std::string scratch;
+ s = ReadAndRecover(reader, &read_buffer_, cf_name_to_options,
+ column_families_not_found, builders, &version_edit);
+ if (s.ok()) {
+ bool enough = version_edit.has_next_file_number_ &&
+ version_edit.has_log_number_ &&
+ version_edit.has_last_sequence_;
+ if (enough) {
+ for (const auto& cf : column_families) {
+ auto cfd = column_family_set_->GetColumnFamily(cf.name);
+ if (cfd == nullptr) {
+ enough = false;
+ break;
+ }
+ }
+ }
+ if (enough) {
+ for (const auto& cf : column_families) {
+ auto cfd = column_family_set_->GetColumnFamily(cf.name);
+ assert(cfd != nullptr);
+ if (!cfd->IsDropped()) {
+ auto builder_iter = builders.find(cfd->GetID());
+ assert(builder_iter != builders.end());
+ auto builder = builder_iter->second->version_builder();
+ assert(builder != nullptr);
+ s = builder->LoadTableHandlers(
+ cfd->internal_stats(), db_options_->max_file_opening_threads,
+ false /* prefetch_index_and_filter_in_cache */,
+ true /* is_initial_load */,
+ cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
+ if (!s.ok()) {
+ enough = false;
+ if (s.IsPathNotFound()) {
+ s = Status::OK();
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (enough) {
+ break;
+ }
+ }
+ ++retry;
+ }
+
+ if (s.ok()) {
+ if (!version_edit.has_prev_log_number_) {
+ version_edit.prev_log_number_ = 0;
+ }
+ column_family_set_->UpdateMaxColumnFamily(version_edit.max_column_family_);
+
+ MarkMinLogNumberToKeep2PC(version_edit.min_log_number_to_keep_);
+ MarkFileNumberUsed(version_edit.prev_log_number_);
+ MarkFileNumberUsed(version_edit.log_number_);
+
+ for (auto cfd : *column_family_set_) {
+ assert(builders.count(cfd->GetID()) > 0);
+ auto builder = builders[cfd->GetID()]->version_builder();
+ if (!builder->CheckConsistencyForNumLevels()) {
+ s = Status::InvalidArgument(
+ "db has more levels than options.num_levels");
+ break;
+ }
+ }
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ assert(cfd->initialized());
+ auto builders_iter = builders.find(cfd->GetID());
+ assert(builders_iter != builders.end());
+ auto* builder = builders_iter->second->version_builder();
+
+ Version* v = new Version(cfd, this, file_options_,
+ *cfd->GetLatestMutableCFOptions(),
+ current_version_number_++);
+ builder->SaveTo(v->storage_info());
+
+ // Install recovered version
+ v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
+ !(db_options_->skip_stats_update_on_db_open));
+ AppendVersion(cfd, v);
+ }
+ next_file_number_.store(version_edit.next_file_number_ + 1);
+ last_allocated_sequence_ = version_edit.last_sequence_;
+ last_published_sequence_ = version_edit.last_sequence_;
+ last_sequence_ = version_edit.last_sequence_;
+ prev_log_number_ = version_edit.prev_log_number_;
+ for (auto cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ ROCKS_LOG_INFO(db_options_->info_log,
+ "Column family [%s] (ID %u), log number is %" PRIu64 "\n",
+ cfd->GetName().c_str(), cfd->GetID(), cfd->GetLogNumber());
+ }
+ }
+ return s;
+}
+
+Status ReactiveVersionSet::ReadAndApply(
+ InstrumentedMutex* mu,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
+ std::unordered_set<ColumnFamilyData*>* cfds_changed) {
+ assert(manifest_reader != nullptr);
+ assert(cfds_changed != nullptr);
+ mu->AssertHeld();
+
+ Status s;
+ uint64_t applied_edits = 0;
+ while (s.ok()) {
+ Slice record;
+ std::string scratch;
+ log::Reader* reader = manifest_reader->get();
+ std::string old_manifest_path = reader->file()->file_name();
+ while (reader->ReadRecord(&record, &scratch)) {
+ VersionEdit edit;
+ s = edit.DecodeFrom(record);
+ if (!s.ok()) {
+ break;
+ }
+
+ // Skip the first VersionEdits of each MANIFEST generated by
+ // VersionSet::WriteCurrentStatetoManifest.
+ if (number_of_edits_to_skip_ > 0) {
+ ColumnFamilyData* cfd =
+ column_family_set_->GetColumnFamily(edit.column_family_);
+ if (cfd != nullptr && !cfd->IsDropped()) {
+ --number_of_edits_to_skip_;
+ }
+ continue;
+ }
+
+ s = read_buffer_.AddEdit(&edit);
+ if (!s.ok()) {
+ break;
+ }
+ VersionEdit temp_edit;
+ if (edit.is_in_atomic_group_) {
+ if (read_buffer_.IsFull()) {
+ // Apply edits in an atomic group when we have read all edits in the
+ // group.
+ for (auto& e : read_buffer_.replay_buffer()) {
+ s = ApplyOneVersionEditToBuilder(e, cfds_changed, &temp_edit);
+ if (!s.ok()) {
+ break;
+ }
+ applied_edits++;
+ }
+ if (!s.ok()) {
+ break;
+ }
+ read_buffer_.Clear();
+ }
+ } else {
+ // Apply a normal edit immediately.
+ s = ApplyOneVersionEditToBuilder(edit, cfds_changed, &temp_edit);
+ if (s.ok()) {
+ applied_edits++;
+ }
+ }
+ }
+ if (!s.ok()) {
+ // Clear the buffer if we fail to decode/apply an edit.
+ read_buffer_.Clear();
+ }
+ // It's possible that:
+ // 1) s.IsCorruption(), indicating the current MANIFEST is corrupted.
+ // 2) we have finished reading the current MANIFEST.
+ // 3) we have encountered an IOError reading the current MANIFEST.
+ // We need to look for the next MANIFEST and start from there. If we cannot
+ // find the next MANIFEST, we should exit the loop.
+ s = MaybeSwitchManifest(reader->GetReporter(), manifest_reader);
+ reader = manifest_reader->get();
+ if (s.ok()) {
+ if (reader->file()->file_name() == old_manifest_path) {
+ // Still processing the same MANIFEST, thus no need to continue this
+ // loop since no record is available if we have reached here.
+ break;
+ } else {
+ // We have switched to a new MANIFEST whose first records have been
+ // generated by VersionSet::WriteCurrentStatetoManifest. Since the
+ // secondary instance has already finished recovering upon start, there
+ // is no need for the secondary to process these records. Actually, if
+ // the secondary were to replay these records, the secondary may end up
+ // adding the same SST files AGAIN to each column family, causing
+ // consistency checks done by VersionBuilder to fail. Therefore, we
+ // record the number of records to skip at the beginning of the new
+ // MANIFEST and ignore them.
+ number_of_edits_to_skip_ = 0;
+ for (auto* cfd : *column_family_set_) {
+ if (cfd->IsDropped()) {
+ continue;
+ }
+ // Increase number_of_edits_to_skip by 2 because
+ // WriteCurrentStatetoManifest() writes 2 version edits for each
+ // column family at the beginning of the newly-generated MANIFEST.
+ // TODO(yanqin) remove hard-coded value.
+ if (db_options_->write_dbid_to_manifest) {
+ number_of_edits_to_skip_ += 3;
+ } else {
+ number_of_edits_to_skip_ += 2;
+ }
+ }
+ }
+ }
+ }
+
+ if (s.ok()) {
+ for (auto cfd : *column_family_set_) {
+ auto builder_iter = active_version_builders_.find(cfd->GetID());
+ if (builder_iter == active_version_builders_.end()) {
+ continue;
+ }
+ auto builder = builder_iter->second->version_builder();
+ if (!builder->CheckConsistencyForNumLevels()) {
+ s = Status::InvalidArgument(
+ "db has more levels than options.num_levels");
+ break;
+ }
+ }
+ }
+ TEST_SYNC_POINT_CALLBACK("ReactiveVersionSet::ReadAndApply:AppliedEdits",
+ &applied_edits);
+ return s;
+}
+
+Status ReactiveVersionSet::ApplyOneVersionEditToBuilder(
+ VersionEdit& edit, std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ VersionEdit* version_edit) {
+ ColumnFamilyData* cfd =
+ column_family_set_->GetColumnFamily(edit.column_family_);
+
+ // If we cannot find this column family in our column family set, then it
+ // may be a new column family created by the primary after the secondary
+ // starts. It is also possible that the secondary instance opens only a subset
+ // of column families. Ignore it for now.
+ if (nullptr == cfd) {
+ return Status::OK();
+ }
+ if (active_version_builders_.find(edit.column_family_) ==
+ active_version_builders_.end() &&
+ !cfd->IsDropped()) {
+ std::unique_ptr<BaseReferencedVersionBuilder> builder_guard(
+ new BaseReferencedVersionBuilder(cfd));
+ active_version_builders_.insert(
+ std::make_pair(edit.column_family_, std::move(builder_guard)));
+ }
+
+ auto builder_iter = active_version_builders_.find(edit.column_family_);
+ assert(builder_iter != active_version_builders_.end());
+ auto builder = builder_iter->second->version_builder();
+ assert(builder != nullptr);
+
+ if (edit.is_column_family_add_) {
+ // TODO (yanqin) for now the secondary ignores column families created
+ // after Open. This also simplifies handling of switching to a new MANIFEST
+ // and processing the snapshot of the system at the beginning of the
+ // MANIFEST.
+ } else if (edit.is_column_family_drop_) {
+ // Drop the column family by setting it to be 'dropped' without destroying
+ // the column family handle.
+ // TODO (haoyu) figure out how to handle column faimly drop for
+ // secondary instance. (Is it possible that the ref count for cfd is 0 but
+ // the ref count for its versions is higher than 0?)
+ cfd->SetDropped();
+ if (cfd->UnrefAndTryDelete()) {
+ cfd = nullptr;
+ }
+ active_version_builders_.erase(builder_iter);
+ } else {
+ Status s = builder->Apply(&edit);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ Status s = ExtractInfoFromVersionEdit(cfd, edit, version_edit);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (cfd != nullptr && !cfd->IsDropped()) {
+ s = builder->LoadTableHandlers(
+ cfd->internal_stats(), db_options_->max_file_opening_threads,
+ false /* prefetch_index_and_filter_in_cache */,
+ false /* is_initial_load */,
+ cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
+ TEST_SYNC_POINT_CALLBACK(
+ "ReactiveVersionSet::ApplyOneVersionEditToBuilder:"
+ "AfterLoadTableHandlers",
+ &s);
+
+ if (s.ok()) {
+ auto version = new Version(cfd, this, file_options_,
+ *cfd->GetLatestMutableCFOptions(),
+ current_version_number_++);
+ builder->SaveTo(version->storage_info());
+ version->PrepareApply(*cfd->GetLatestMutableCFOptions(), true);
+ AppendVersion(cfd, version);
+ active_version_builders_.erase(builder_iter);
+ if (cfds_changed->count(cfd) == 0) {
+ cfds_changed->insert(cfd);
+ }
+ } else if (s.IsPathNotFound()) {
+ s = Status::OK();
+ }
+ // Some other error has occurred during LoadTableHandlers.
+ }
+
+ if (version_edit->HasNextFile()) {
+ next_file_number_.store(version_edit->next_file_number_ + 1);
+ }
+ if (version_edit->has_last_sequence_) {
+ last_allocated_sequence_ = version_edit->last_sequence_;
+ last_published_sequence_ = version_edit->last_sequence_;
+ last_sequence_ = version_edit->last_sequence_;
+ }
+ if (version_edit->has_prev_log_number_) {
+ prev_log_number_ = version_edit->prev_log_number_;
+ MarkFileNumberUsed(version_edit->prev_log_number_);
+ }
+ if (version_edit->has_log_number_) {
+ MarkFileNumberUsed(version_edit->log_number_);
+ }
+ column_family_set_->UpdateMaxColumnFamily(version_edit->max_column_family_);
+ MarkMinLogNumberToKeep2PC(version_edit->min_log_number_to_keep_);
+ return s;
+}
+
+Status ReactiveVersionSet::MaybeSwitchManifest(
+ log::Reader::Reporter* reporter,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader) {
+ assert(manifest_reader != nullptr);
+ Status s;
+ do {
+ std::string manifest_path;
+ s = GetCurrentManifestPath(dbname_, fs_, &manifest_path,
+ &manifest_file_number_);
+ std::unique_ptr<FSSequentialFile> manifest_file;
+ if (s.ok()) {
+ if (nullptr == manifest_reader->get() ||
+ manifest_reader->get()->file()->file_name() != manifest_path) {
+ TEST_SYNC_POINT(
+ "ReactiveVersionSet::MaybeSwitchManifest:"
+ "AfterGetCurrentManifestPath:0");
+ TEST_SYNC_POINT(
+ "ReactiveVersionSet::MaybeSwitchManifest:"
+ "AfterGetCurrentManifestPath:1");
+ s = fs_->NewSequentialFile(manifest_path,
+ env_->OptimizeForManifestRead(file_options_),
+ &manifest_file, nullptr);
+ } else {
+ // No need to switch manifest.
+ break;
+ }
+ }
+ std::unique_ptr<SequentialFileReader> manifest_file_reader;
+ if (s.ok()) {
+ manifest_file_reader.reset(
+ new SequentialFileReader(std::move(manifest_file), manifest_path,
+ db_options_->log_readahead_size));
+ manifest_reader->reset(new log::FragmentBufferedReader(
+ nullptr, std::move(manifest_file_reader), reporter,
+ true /* checksum */, 0 /* log_number */));
+ ROCKS_LOG_INFO(db_options_->info_log, "Switched to new manifest: %s\n",
+ manifest_path.c_str());
+ // TODO (yanqin) every time we switch to a new MANIFEST, we clear the
+ // active_version_builders_ map because we choose to construct the
+ // versions from scratch, thanks to the first part of each MANIFEST
+ // written by VersionSet::WriteCurrentStatetoManifest. This is not
+ // necessary, but we choose this at present for the sake of simplicity.
+ active_version_builders_.clear();
+ }
+ } while (s.IsPathNotFound());
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_set.h b/storage/rocksdb/rocksdb/db/version_set.h
new file mode 100644
index 00000000..2ab09a5f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_set.h
@@ -0,0 +1,1251 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 representation of a DBImpl consists of a set of Versions. The
+// newest version is called "current". Older versions may be kept
+// around to provide a consistent view to live iterators.
+//
+// Each Version keeps track of a set of Table files per level. The
+// entire set of versions is maintained in a VersionSet.
+//
+// Version,VersionSet are thread-compatible, but require external
+// synchronization on all accesses.
+
+#pragma once
+#include <atomic>
+#include <deque>
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/compaction/compaction.h"
+#include "db/compaction/compaction_picker.h"
+#include "db/dbformat.h"
+#include "db/file_indexer.h"
+#include "db/log_reader.h"
+#include "db/range_del_aggregator.h"
+#include "db/read_callback.h"
+#include "db/table_cache.h"
+#include "db/version_builder.h"
+#include "db/version_edit.h"
+#include "db/write_controller.h"
+#include "monitoring/instrumented_mutex.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_checksum.h"
+#include "table/get_context.h"
+#include "table/multiget_context.h"
+#include "trace_replay/block_cache_tracer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace log {
+class Writer;
+}
+
+class Compaction;
+class LogBuffer;
+class LookupKey;
+class MemTable;
+class Version;
+class VersionSet;
+class WriteBufferManager;
+class MergeContext;
+class ColumnFamilySet;
+class MergeIteratorBuilder;
+
+// VersionEdit is always supposed to be valid and it is used to point at
+// entries in Manifest. Ideally it should not be used as a container to
+// carry around few of its fields as function params because it can cause
+// readers to think it's a valid entry from Manifest. To avoid that confusion
+// introducing VersionEditParams to simply carry around multiple VersionEdit
+// params. It need not point to a valid record in Manifest.
+using VersionEditParams = VersionEdit;
+
+// Return the smallest index i such that file_level.files[i]->largest >= key.
+// Return file_level.num_files if there is no such file.
+// REQUIRES: "file_level.files" contains a sorted list of
+// non-overlapping files.
+extern int FindFile(const InternalKeyComparator& icmp,
+ const LevelFilesBrief& file_level, const Slice& key);
+
+// Returns true iff some file in "files" overlaps the user key range
+// [*smallest,*largest].
+// smallest==nullptr represents a key smaller than all keys in the DB.
+// largest==nullptr represents a key largest than all keys in the DB.
+// REQUIRES: If disjoint_sorted_files, file_level.files[]
+// contains disjoint ranges in sorted order.
+extern bool SomeFileOverlapsRange(const InternalKeyComparator& icmp,
+ bool disjoint_sorted_files,
+ const LevelFilesBrief& file_level,
+ const Slice* smallest_user_key,
+ const Slice* largest_user_key);
+
+// Generate LevelFilesBrief from vector<FdWithKeyRange*>
+// Would copy smallest_key and largest_key data to sequential memory
+// arena: Arena used to allocate the memory
+extern void DoGenerateLevelFilesBrief(LevelFilesBrief* file_level,
+ const std::vector<FileMetaData*>& files,
+ Arena* arena);
+
+// Information of the storage associated with each Version, including number of
+// levels of LSM tree, files information at each level, files marked for
+// compaction, etc.
+class VersionStorageInfo {
+ public:
+ VersionStorageInfo(const InternalKeyComparator* internal_comparator,
+ const Comparator* user_comparator, int num_levels,
+ CompactionStyle compaction_style,
+ VersionStorageInfo* src_vstorage,
+ bool _force_consistency_checks);
+ // No copying allowed
+ VersionStorageInfo(const VersionStorageInfo&) = delete;
+ void operator=(const VersionStorageInfo&) = delete;
+ ~VersionStorageInfo();
+
+ void Reserve(int level, size_t size) { files_[level].reserve(size); }
+
+ void AddFile(int level, FileMetaData* f, Logger* info_log = nullptr);
+
+ void SetFinalized();
+
+ // Update num_non_empty_levels_.
+ void UpdateNumNonEmptyLevels();
+
+ void GenerateFileIndexer() {
+ file_indexer_.UpdateIndex(&arena_, num_non_empty_levels_, files_);
+ }
+
+ // Update the accumulated stats from a file-meta.
+ void UpdateAccumulatedStats(FileMetaData* file_meta);
+
+ // Decrease the current stat from a to-be-deleted file-meta
+ void RemoveCurrentStats(FileMetaData* file_meta);
+
+ void ComputeCompensatedSizes();
+
+ // Updates internal structures that keep track of compaction scores
+ // We use compaction scores to figure out which compaction to do next
+ // REQUIRES: db_mutex held!!
+ // TODO find a better way to pass compaction_options_fifo.
+ void ComputeCompactionScore(const ImmutableCFOptions& immutable_cf_options,
+ const MutableCFOptions& mutable_cf_options);
+
+ // Estimate est_comp_needed_bytes_
+ void EstimateCompactionBytesNeeded(
+ const MutableCFOptions& mutable_cf_options);
+
+ // This computes files_marked_for_compaction_ and is called by
+ // ComputeCompactionScore()
+ void ComputeFilesMarkedForCompaction();
+
+ // This computes ttl_expired_files_ and is called by
+ // ComputeCompactionScore()
+ void ComputeExpiredTtlFiles(const ImmutableCFOptions& ioptions,
+ const uint64_t ttl);
+
+ // This computes files_marked_for_periodic_compaction_ and is called by
+ // ComputeCompactionScore()
+ void ComputeFilesMarkedForPeriodicCompaction(
+ const ImmutableCFOptions& ioptions,
+ const uint64_t periodic_compaction_seconds);
+
+ // This computes bottommost_files_marked_for_compaction_ and is called by
+ // ComputeCompactionScore() or UpdateOldestSnapshot().
+ //
+ // Among bottommost files (assumes they've already been computed), marks the
+ // ones that have keys that would be eliminated if recompacted, according to
+ // the seqnum of the oldest existing snapshot. Must be called every time
+ // oldest snapshot changes as that is when bottom-level files can become
+ // eligible for compaction.
+ //
+ // REQUIRES: DB mutex held
+ void ComputeBottommostFilesMarkedForCompaction();
+
+ // Generate level_files_brief_ from files_
+ void GenerateLevelFilesBrief();
+ // Sort all files for this version based on their file size and
+ // record results in files_by_compaction_pri_. The largest files are listed
+ // first.
+ void UpdateFilesByCompactionPri(CompactionPri compaction_pri);
+
+ void GenerateLevel0NonOverlapping();
+ bool level0_non_overlapping() const {
+ return level0_non_overlapping_;
+ }
+
+ // Check whether each file in this version is bottommost (i.e., nothing in its
+ // key-range could possibly exist in an older file/level).
+ // REQUIRES: This version has not been saved
+ void GenerateBottommostFiles();
+
+ // Updates the oldest snapshot and related internal state, like the bottommost
+ // files marked for compaction.
+ // REQUIRES: DB mutex held
+ void UpdateOldestSnapshot(SequenceNumber oldest_snapshot_seqnum);
+
+ int MaxInputLevel() const;
+ int MaxOutputLevel(bool allow_ingest_behind) const;
+
+ // Return level number that has idx'th highest score
+ int CompactionScoreLevel(int idx) const { return compaction_level_[idx]; }
+
+ // Return idx'th highest score
+ double CompactionScore(int idx) const { return compaction_score_[idx]; }
+
+ void GetOverlappingInputs(
+ int level, const InternalKey* begin, // nullptr means before all keys
+ const InternalKey* end, // nullptr means after all keys
+ std::vector<FileMetaData*>* inputs,
+ int hint_index = -1, // index of overlap file
+ int* file_index = nullptr, // return index of overlap file
+ bool expand_range = true, // if set, returns files which overlap the
+ // range and overlap each other. If false,
+ // then just files intersecting the range
+ InternalKey** next_smallest = nullptr) // if non-null, returns the
+ const; // smallest key of next file not included
+ void GetCleanInputsWithinInterval(
+ int level, const InternalKey* begin, // nullptr means before all keys
+ const InternalKey* end, // nullptr means after all keys
+ std::vector<FileMetaData*>* inputs,
+ int hint_index = -1, // index of overlap file
+ int* file_index = nullptr) // return index of overlap file
+ const;
+
+ void GetOverlappingInputsRangeBinarySearch(
+ int level, // level > 0
+ const InternalKey* begin, // nullptr means before all keys
+ const InternalKey* end, // nullptr means after all keys
+ std::vector<FileMetaData*>* inputs,
+ int hint_index, // index of overlap file
+ int* file_index, // return index of overlap file
+ bool within_interval = false, // if set, force the inputs within interval
+ InternalKey** next_smallest = nullptr) // if non-null, returns the
+ const; // smallest key of next file not included
+
+ // Returns true iff some file in the specified level overlaps
+ // some part of [*smallest_user_key,*largest_user_key].
+ // smallest_user_key==NULL represents a key smaller than all keys in the DB.
+ // largest_user_key==NULL represents a key largest than all keys in the DB.
+ bool OverlapInLevel(int level, const Slice* smallest_user_key,
+ const Slice* largest_user_key);
+
+ // Returns true iff the first or last file in inputs contains
+ // an overlapping user key to the file "just outside" of it (i.e.
+ // just after the last file, or just before the first file)
+ // REQUIRES: "*inputs" is a sorted list of non-overlapping files
+ bool HasOverlappingUserKey(const std::vector<FileMetaData*>* inputs,
+ int level);
+
+ int num_levels() const { return num_levels_; }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ int num_non_empty_levels() const {
+ assert(finalized_);
+ return num_non_empty_levels_;
+ }
+
+ // REQUIRES: This version has been finalized.
+ // (CalculateBaseBytes() is called)
+ // This may or may not return number of level files. It is to keep backward
+ // compatible behavior in universal compaction.
+ int l0_delay_trigger_count() const { return l0_delay_trigger_count_; }
+
+ void set_l0_delay_trigger_count(int v) { l0_delay_trigger_count_ = v; }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ int NumLevelFiles(int level) const {
+ assert(finalized_);
+ return static_cast<int>(files_[level].size());
+ }
+
+ // Return the combined file size of all files at the specified level.
+ uint64_t NumLevelBytes(int level) const;
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ const std::vector<FileMetaData*>& LevelFiles(int level) const {
+ return files_[level];
+ }
+
+ const ROCKSDB_NAMESPACE::LevelFilesBrief& LevelFilesBrief(int level) const {
+ assert(level < static_cast<int>(level_files_brief_.size()));
+ return level_files_brief_[level];
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ const std::vector<int>& FilesByCompactionPri(int level) const {
+ assert(finalized_);
+ return files_by_compaction_pri_[level];
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ // REQUIRES: DB mutex held during access
+ const autovector<std::pair<int, FileMetaData*>>& FilesMarkedForCompaction()
+ const {
+ assert(finalized_);
+ return files_marked_for_compaction_;
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ // REQUIRES: DB mutex held during access
+ const autovector<std::pair<int, FileMetaData*>>& ExpiredTtlFiles() const {
+ assert(finalized_);
+ return expired_ttl_files_;
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ // REQUIRES: DB mutex held during access
+ const autovector<std::pair<int, FileMetaData*>>&
+ FilesMarkedForPeriodicCompaction() const {
+ assert(finalized_);
+ return files_marked_for_periodic_compaction_;
+ }
+
+ void TEST_AddFileMarkedForPeriodicCompaction(int level, FileMetaData* f) {
+ files_marked_for_periodic_compaction_.emplace_back(level, f);
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ // REQUIRES: DB mutex held during access
+ const autovector<std::pair<int, FileMetaData*>>&
+ BottommostFilesMarkedForCompaction() const {
+ assert(finalized_);
+ return bottommost_files_marked_for_compaction_;
+ }
+
+ int base_level() const { return base_level_; }
+ double level_multiplier() const { return level_multiplier_; }
+
+ // REQUIRES: lock is held
+ // Set the index that is used to offset into files_by_compaction_pri_ to find
+ // the next compaction candidate file.
+ void SetNextCompactionIndex(int level, int index) {
+ next_file_to_compact_by_size_[level] = index;
+ }
+
+ // REQUIRES: lock is held
+ int NextCompactionIndex(int level) const {
+ return next_file_to_compact_by_size_[level];
+ }
+
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ const FileIndexer& file_indexer() const {
+ assert(finalized_);
+ return file_indexer_;
+ }
+
+ // Only the first few entries of files_by_compaction_pri_ are sorted.
+ // There is no need to sort all the files because it is likely
+ // that on a running system, we need to look at only the first
+ // few largest files because a new version is created every few
+ // seconds/minutes (because of concurrent compactions).
+ static const size_t kNumberFilesToSort = 50;
+
+ // Return a human-readable short (single-line) summary of the number
+ // of files per level. Uses *scratch as backing store.
+ struct LevelSummaryStorage {
+ char buffer[1000];
+ };
+ struct FileSummaryStorage {
+ char buffer[3000];
+ };
+ const char* LevelSummary(LevelSummaryStorage* scratch) const;
+ // Return a human-readable short (single-line) summary of files
+ // in a specified level. Uses *scratch as backing store.
+ const char* LevelFileSummary(FileSummaryStorage* scratch, int level) const;
+
+ // Return the maximum overlapping data (in bytes) at next level for any
+ // file at a level >= 1.
+ int64_t MaxNextLevelOverlappingBytes();
+
+ // Return a human readable string that describes this version's contents.
+ std::string DebugString(bool hex = false) const;
+
+ uint64_t GetAverageValueSize() const {
+ if (accumulated_num_non_deletions_ == 0) {
+ return 0;
+ }
+ assert(accumulated_raw_key_size_ + accumulated_raw_value_size_ > 0);
+ assert(accumulated_file_size_ > 0);
+ return accumulated_raw_value_size_ / accumulated_num_non_deletions_ *
+ accumulated_file_size_ /
+ (accumulated_raw_key_size_ + accumulated_raw_value_size_);
+ }
+
+ uint64_t GetEstimatedActiveKeys() const;
+
+ double GetEstimatedCompressionRatioAtLevel(int level) const;
+
+ // re-initializes the index that is used to offset into
+ // files_by_compaction_pri_
+ // to find the next compaction candidate file.
+ void ResetNextCompactionIndex(int level) {
+ next_file_to_compact_by_size_[level] = 0;
+ }
+
+ const InternalKeyComparator* InternalComparator() {
+ return internal_comparator_;
+ }
+
+ // Returns maximum total bytes of data on a given level.
+ uint64_t MaxBytesForLevel(int level) const;
+
+ // Must be called after any change to MutableCFOptions.
+ void CalculateBaseBytes(const ImmutableCFOptions& ioptions,
+ const MutableCFOptions& options);
+
+ // Returns an estimate of the amount of live data in bytes.
+ uint64_t EstimateLiveDataSize() const;
+
+ uint64_t estimated_compaction_needed_bytes() const {
+ return estimated_compaction_needed_bytes_;
+ }
+
+ void TEST_set_estimated_compaction_needed_bytes(uint64_t v) {
+ estimated_compaction_needed_bytes_ = v;
+ }
+
+ bool force_consistency_checks() const { return force_consistency_checks_; }
+
+ SequenceNumber bottommost_files_mark_threshold() const {
+ return bottommost_files_mark_threshold_;
+ }
+
+ // Returns whether any key in [`smallest_key`, `largest_key`] could appear in
+ // an older L0 file than `last_l0_idx` or in a greater level than `last_level`
+ //
+ // @param last_level Level after which we check for overlap
+ // @param last_l0_idx If `last_level == 0`, index of L0 file after which we
+ // check for overlap; otherwise, must be -1
+ bool RangeMightExistAfterSortedRun(const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ int last_level, int last_l0_idx);
+
+ private:
+ const InternalKeyComparator* internal_comparator_;
+ const Comparator* user_comparator_;
+ int num_levels_; // Number of levels
+ int num_non_empty_levels_; // Number of levels. Any level larger than it
+ // is guaranteed to be empty.
+ // Per-level max bytes
+ std::vector<uint64_t> level_max_bytes_;
+
+ // A short brief metadata of files per level
+ autovector<ROCKSDB_NAMESPACE::LevelFilesBrief> level_files_brief_;
+ FileIndexer file_indexer_;
+ Arena arena_; // Used to allocate space for file_levels_
+
+ CompactionStyle compaction_style_;
+
+ // List of files per level, files in each level are arranged
+ // in increasing order of keys
+ std::vector<FileMetaData*>* files_;
+
+ // Level that L0 data should be compacted to. All levels < base_level_ should
+ // be empty. -1 if it is not level-compaction so it's not applicable.
+ int base_level_;
+
+ double level_multiplier_;
+
+ // A list for the same set of files that are stored in files_,
+ // but files in each level are now sorted based on file
+ // size. The file with the largest size is at the front.
+ // This vector stores the index of the file from files_.
+ std::vector<std::vector<int>> files_by_compaction_pri_;
+
+ // If true, means that files in L0 have keys with non overlapping ranges
+ bool level0_non_overlapping_;
+
+ // An index into files_by_compaction_pri_ that specifies the first
+ // file that is not yet compacted
+ std::vector<int> next_file_to_compact_by_size_;
+
+ // Only the first few entries of files_by_compaction_pri_ are sorted.
+ // There is no need to sort all the files because it is likely
+ // that on a running system, we need to look at only the first
+ // few largest files because a new version is created every few
+ // seconds/minutes (because of concurrent compactions).
+ static const size_t number_of_files_to_sort_ = 50;
+
+ // This vector contains list of files marked for compaction and also not
+ // currently being compacted. It is protected by DB mutex. It is calculated in
+ // ComputeCompactionScore()
+ autovector<std::pair<int, FileMetaData*>> files_marked_for_compaction_;
+
+ autovector<std::pair<int, FileMetaData*>> expired_ttl_files_;
+
+ autovector<std::pair<int, FileMetaData*>>
+ files_marked_for_periodic_compaction_;
+
+ // These files are considered bottommost because none of their keys can exist
+ // at lower levels. They are not necessarily all in the same level. The marked
+ // ones are eligible for compaction because they contain duplicate key
+ // versions that are no longer protected by snapshot. These variables are
+ // protected by DB mutex and are calculated in `GenerateBottommostFiles()` and
+ // `ComputeBottommostFilesMarkedForCompaction()`.
+ autovector<std::pair<int, FileMetaData*>> bottommost_files_;
+ autovector<std::pair<int, FileMetaData*>>
+ bottommost_files_marked_for_compaction_;
+
+ // Threshold for needing to mark another bottommost file. Maintain it so we
+ // can quickly check when releasing a snapshot whether more bottommost files
+ // became eligible for compaction. It's defined as the min of the max nonzero
+ // seqnums of unmarked bottommost files.
+ SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
+
+ // Monotonically increases as we release old snapshots. Zero indicates no
+ // snapshots have been released yet. When no snapshots remain we set it to the
+ // current seqnum, which needs to be protected as a snapshot can still be
+ // created that references it.
+ SequenceNumber oldest_snapshot_seqnum_ = 0;
+
+ // Level that should be compacted next and its compaction score.
+ // Score < 1 means compaction is not strictly needed. These fields
+ // are initialized by Finalize().
+ // The most critical level to be compacted is listed first
+ // These are used to pick the best compaction level
+ std::vector<double> compaction_score_;
+ std::vector<int> compaction_level_;
+ int l0_delay_trigger_count_ = 0; // Count used to trigger slow down and stop
+ // for number of L0 files.
+
+ // the following are the sampled temporary stats.
+ // the current accumulated size of sampled files.
+ uint64_t accumulated_file_size_;
+ // the current accumulated size of all raw keys based on the sampled files.
+ uint64_t accumulated_raw_key_size_;
+ // the current accumulated size of all raw keys based on the sampled files.
+ uint64_t accumulated_raw_value_size_;
+ // total number of non-deletion entries
+ uint64_t accumulated_num_non_deletions_;
+ // total number of deletion entries
+ uint64_t accumulated_num_deletions_;
+ // current number of non_deletion entries
+ uint64_t current_num_non_deletions_;
+ // current number of deletion entries
+ uint64_t current_num_deletions_;
+ // current number of file samples
+ uint64_t current_num_samples_;
+ // Estimated bytes needed to be compacted until all levels' size is down to
+ // target sizes.
+ uint64_t estimated_compaction_needed_bytes_;
+
+ bool finalized_;
+
+ // If set to true, we will run consistency checks even if RocksDB
+ // is compiled in release mode
+ bool force_consistency_checks_;
+
+ friend class Version;
+ friend class VersionSet;
+};
+
+using MultiGetRange = MultiGetContext::Range;
+// A column family's version consists of the SST files owned by the column
+// family at a certain point in time.
+class Version {
+ public:
+ // Append to *iters a sequence of iterators that will
+ // yield the contents of this Version when merged together.
+ // REQUIRES: This version has been saved (see VersionSet::SaveTo)
+ void AddIterators(const ReadOptions&, const FileOptions& soptions,
+ MergeIteratorBuilder* merger_iter_builder,
+ RangeDelAggregator* range_del_agg);
+
+ void AddIteratorsForLevel(const ReadOptions&, const FileOptions& soptions,
+ MergeIteratorBuilder* merger_iter_builder,
+ int level, RangeDelAggregator* range_del_agg);
+
+ Status OverlapWithLevelIterator(const ReadOptions&, const FileOptions&,
+ const Slice& smallest_user_key,
+ const Slice& largest_user_key,
+ int level, bool* overlap);
+
+ // Lookup the value for key or get all merge operands for key.
+ // If do_merge = true (default) then lookup value for key.
+ // Behavior if do_merge = true:
+ // If found, store it in *value and
+ // return OK. Else return a non-OK status.
+ // Uses *operands to store merge_operator operations to apply later.
+ //
+ // If the ReadOptions.read_tier is set to do a read-only fetch, then
+ // *value_found will be set to false if it cannot be determined whether
+ // this value exists without doing IO.
+ //
+ // If the key is Deleted, *status will be set to NotFound and
+ // *key_exists will be set to true.
+ // If no key was found, *status will be set to NotFound and
+ // *key_exists will be set to false.
+ // If seq is non-null, *seq will be set to the sequence number found
+ // for the key if a key was found.
+ // Behavior if do_merge = false
+ // If the key has any merge operands then store them in
+ // merge_context.operands_list and don't merge the operands
+ // REQUIRES: lock is not held
+ void Get(const ReadOptions&, const LookupKey& key, PinnableSlice* value,
+ Status* status, MergeContext* merge_context,
+ SequenceNumber* max_covering_tombstone_seq,
+ bool* value_found = nullptr, bool* key_exists = nullptr,
+ SequenceNumber* seq = nullptr, ReadCallback* callback = nullptr,
+ bool* is_blob = nullptr, bool do_merge = true);
+
+ void MultiGet(const ReadOptions&, MultiGetRange* range,
+ ReadCallback* callback = nullptr, bool* is_blob = nullptr);
+
+ // Loads some stats information from files. Call without mutex held. It needs
+ // to be called before applying the version to the version set.
+ void PrepareApply(const MutableCFOptions& mutable_cf_options,
+ bool update_stats);
+
+ // Reference count management (so Versions do not disappear out from
+ // under live iterators)
+ void Ref();
+ // Decrease reference count. Delete the object if no reference left
+ // and return true. Otherwise, return false.
+ bool Unref();
+
+ // Add all files listed in the current version to *live.
+ void AddLiveFiles(std::vector<FileDescriptor>* live);
+
+ // Return a human readable string that describes this version's contents.
+ std::string DebugString(bool hex = false, bool print_stats = false) const;
+
+ // Returns the version number of this version
+ uint64_t GetVersionNumber() const { return version_number_; }
+
+ // REQUIRES: lock is held
+ // On success, "tp" will contains the table properties of the file
+ // specified in "file_meta". If the file name of "file_meta" is
+ // known ahead, passing it by a non-null "fname" can save a
+ // file-name conversion.
+ Status GetTableProperties(std::shared_ptr<const TableProperties>* tp,
+ const FileMetaData* file_meta,
+ const std::string* fname = nullptr) const;
+
+ // REQUIRES: lock is held
+ // On success, *props will be populated with all SSTables' table properties.
+ // The keys of `props` are the sst file name, the values of `props` are the
+ // tables' properties, represented as std::shared_ptr.
+ Status GetPropertiesOfAllTables(TablePropertiesCollection* props);
+ Status GetPropertiesOfAllTables(TablePropertiesCollection* props, int level);
+ Status GetPropertiesOfTablesInRange(const Range* range, std::size_t n,
+ TablePropertiesCollection* props) const;
+
+ // Print summary of range delete tombstones in SST files into out_str,
+ // with maximum max_entries_to_print entries printed out.
+ Status TablesRangeTombstoneSummary(int max_entries_to_print,
+ std::string* out_str);
+
+ // REQUIRES: lock is held
+ // On success, "tp" will contains the aggregated table property among
+ // the table properties of all sst files in this version.
+ Status GetAggregatedTableProperties(
+ std::shared_ptr<const TableProperties>* tp, int level = -1);
+
+ uint64_t GetEstimatedActiveKeys() {
+ return storage_info_.GetEstimatedActiveKeys();
+ }
+
+ size_t GetMemoryUsageByTableReaders();
+
+ ColumnFamilyData* cfd() const { return cfd_; }
+
+ // Return the next Version in the linked list. Used for debug only
+ Version* TEST_Next() const {
+ return next_;
+ }
+
+ int TEST_refs() const { return refs_; }
+
+ VersionStorageInfo* storage_info() { return &storage_info_; }
+
+ VersionSet* version_set() { return vset_; }
+
+ void GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta);
+
+ uint64_t GetSstFilesSize();
+
+ // Retrieves the file_creation_time of the oldest file in the DB.
+ // Prerequisite for this API is max_open_files = -1
+ void GetCreationTimeOfOldestFile(uint64_t* creation_time);
+
+ const MutableCFOptions& GetMutableCFOptions() { return mutable_cf_options_; }
+
+ private:
+ Env* env_;
+ FileSystem* fs_;
+ friend class ReactiveVersionSet;
+ friend class VersionSet;
+
+ const InternalKeyComparator* internal_comparator() const {
+ return storage_info_.internal_comparator_;
+ }
+ const Comparator* user_comparator() const {
+ return storage_info_.user_comparator_;
+ }
+
+ bool PrefixMayMatch(const ReadOptions& read_options,
+ InternalIterator* level_iter,
+ const Slice& internal_prefix) const;
+
+ // Returns true if the filter blocks in the specified level will not be
+ // checked during read operations. In certain cases (trivial move or preload),
+ // the filter block may already be cached, but we still do not access it such
+ // that it eventually expires from the cache.
+ bool IsFilterSkipped(int level, bool is_file_last_in_level = false);
+
+ // The helper function of UpdateAccumulatedStats, which may fill the missing
+ // fields of file_meta from its associated TableProperties.
+ // Returns true if it does initialize FileMetaData.
+ bool MaybeInitializeFileMetaData(FileMetaData* file_meta);
+
+ // Update the accumulated stats associated with the current version.
+ // This accumulated stats will be used in compaction.
+ void UpdateAccumulatedStats(bool update_stats);
+
+ // Sort all files for this version based on their file size and
+ // record results in files_by_compaction_pri_. The largest files are listed
+ // first.
+ void UpdateFilesByCompactionPri();
+
+ ColumnFamilyData* cfd_; // ColumnFamilyData to which this Version belongs
+ Logger* info_log_;
+ Statistics* db_statistics_;
+ TableCache* table_cache_;
+ const MergeOperator* merge_operator_;
+
+ VersionStorageInfo storage_info_;
+ VersionSet* vset_; // VersionSet to which this Version belongs
+ Version* next_; // Next version in linked list
+ Version* prev_; // Previous version in linked list
+ int refs_; // Number of live refs to this version
+ const FileOptions file_options_;
+ const MutableCFOptions mutable_cf_options_;
+
+ // A version number that uniquely represents this version. This is
+ // used for debugging and logging purposes only.
+ uint64_t version_number_;
+
+ Version(ColumnFamilyData* cfd, VersionSet* vset, const FileOptions& file_opt,
+ MutableCFOptions mutable_cf_options, uint64_t version_number = 0);
+
+ ~Version();
+
+ // No copying allowed
+ Version(const Version&) = delete;
+ void operator=(const Version&) = delete;
+};
+
+struct ObsoleteFileInfo {
+ FileMetaData* metadata;
+ std::string path;
+
+ ObsoleteFileInfo() noexcept : metadata(nullptr) {}
+ ObsoleteFileInfo(FileMetaData* f, const std::string& file_path)
+ : metadata(f), path(file_path) {}
+
+ ObsoleteFileInfo(const ObsoleteFileInfo&) = delete;
+ ObsoleteFileInfo& operator=(const ObsoleteFileInfo&) = delete;
+
+ ObsoleteFileInfo(ObsoleteFileInfo&& rhs) noexcept :
+ ObsoleteFileInfo() {
+ *this = std::move(rhs);
+ }
+
+ ObsoleteFileInfo& operator=(ObsoleteFileInfo&& rhs) noexcept {
+ path = std::move(rhs.path);
+ metadata = rhs.metadata;
+ rhs.metadata = nullptr;
+
+ return *this;
+ }
+
+ void DeleteMetadata() {
+ delete metadata;
+ metadata = nullptr;
+ }
+};
+
+class BaseReferencedVersionBuilder;
+
+class AtomicGroupReadBuffer {
+ public:
+ Status AddEdit(VersionEdit* edit);
+ void Clear();
+ bool IsFull() const;
+ bool IsEmpty() const;
+
+ uint64_t TEST_read_edits_in_atomic_group() const {
+ return read_edits_in_atomic_group_;
+ }
+ std::vector<VersionEdit>& replay_buffer() { return replay_buffer_; }
+
+ private:
+ uint64_t read_edits_in_atomic_group_ = 0;
+ std::vector<VersionEdit> replay_buffer_;
+};
+
+// VersionSet is the collection of versions of all the column families of the
+// database. Each database owns one VersionSet. A VersionSet has access to all
+// column families via ColumnFamilySet, i.e. set of the column families.
+class VersionSet {
+ public:
+ VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
+ const FileOptions& file_options, Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller,
+ BlockCacheTracer* const block_cache_tracer);
+ // No copying allowed
+ VersionSet(const VersionSet&) = delete;
+ void operator=(const VersionSet&) = delete;
+
+ virtual ~VersionSet();
+
+ // Apply *edit to the current version to form a new descriptor that
+ // is both saved to persistent state and installed as the new
+ // current version. Will release *mu while actually writing to the file.
+ // column_family_options has to be set if edit is column family add
+ // REQUIRES: *mu is held on entry.
+ // REQUIRES: no other thread concurrently calls LogAndApply()
+ Status LogAndApply(
+ ColumnFamilyData* column_family_data,
+ const MutableCFOptions& mutable_cf_options, VersionEdit* edit,
+ InstrumentedMutex* mu, Directory* db_directory = nullptr,
+ bool new_descriptor_log = false,
+ const ColumnFamilyOptions* column_family_options = nullptr) {
+ autovector<ColumnFamilyData*> cfds;
+ cfds.emplace_back(column_family_data);
+ autovector<const MutableCFOptions*> mutable_cf_options_list;
+ mutable_cf_options_list.emplace_back(&mutable_cf_options);
+ autovector<autovector<VersionEdit*>> edit_lists;
+ autovector<VersionEdit*> edit_list;
+ edit_list.emplace_back(edit);
+ edit_lists.emplace_back(edit_list);
+ return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
+ db_directory, new_descriptor_log, column_family_options);
+ }
+ // The batch version. If edit_list.size() > 1, caller must ensure that
+ // no edit in the list column family add or drop
+ Status LogAndApply(
+ ColumnFamilyData* column_family_data,
+ const MutableCFOptions& mutable_cf_options,
+ const autovector<VersionEdit*>& edit_list, InstrumentedMutex* mu,
+ Directory* db_directory = nullptr, bool new_descriptor_log = false,
+ const ColumnFamilyOptions* column_family_options = nullptr) {
+ autovector<ColumnFamilyData*> cfds;
+ cfds.emplace_back(column_family_data);
+ autovector<const MutableCFOptions*> mutable_cf_options_list;
+ mutable_cf_options_list.emplace_back(&mutable_cf_options);
+ autovector<autovector<VersionEdit*>> edit_lists;
+ edit_lists.emplace_back(edit_list);
+ return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
+ db_directory, new_descriptor_log, column_family_options);
+ }
+
+ // The across-multi-cf batch version. If edit_lists contain more than
+ // 1 version edits, caller must ensure that no edit in the []list is column
+ // family manipulation.
+ virtual Status LogAndApply(
+ const autovector<ColumnFamilyData*>& cfds,
+ const autovector<const MutableCFOptions*>& mutable_cf_options_list,
+ const autovector<autovector<VersionEdit*>>& edit_lists,
+ InstrumentedMutex* mu, Directory* db_directory = nullptr,
+ bool new_descriptor_log = false,
+ const ColumnFamilyOptions* new_cf_options = nullptr);
+
+ static Status GetCurrentManifestPath(const std::string& dbname,
+ FileSystem* fs,
+ std::string* manifest_filename,
+ uint64_t* manifest_file_number);
+
+ // Recover the last saved descriptor from persistent storage.
+ // If read_only == true, Recover() will not complain if some column families
+ // are not opened
+ Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
+ bool read_only = false, std::string* db_id = nullptr);
+
+ // Reads a manifest file and returns a list of column families in
+ // column_families.
+ static Status ListColumnFamilies(std::vector<std::string>* column_families,
+ const std::string& dbname, FileSystem* fs);
+
+#ifndef ROCKSDB_LITE
+ // Try to reduce the number of levels. This call is valid when
+ // only one level from the new max level to the old
+ // max level containing files.
+ // The call is static, since number of levels is immutable during
+ // the lifetime of a RocksDB instance. It reduces number of levels
+ // in a DB by applying changes to manifest.
+ // For example, a db currently has 7 levels [0-6], and a call to
+ // to reduce to 5 [0-4] can only be executed when only one level
+ // among [4-6] contains files.
+ static Status ReduceNumberOfLevels(const std::string& dbname,
+ const Options* options,
+ const FileOptions& file_options,
+ int new_levels);
+
+ // Get the checksum information of all live files
+ Status GetLiveFilesChecksumInfo(FileChecksumList* checksum_list);
+
+ // printf contents (for debugging)
+ Status DumpManifest(Options& options, std::string& manifestFileName,
+ bool verbose, bool hex = false, bool json = false);
+
+#endif // ROCKSDB_LITE
+
+ // Return the current manifest file number
+ uint64_t manifest_file_number() const { return manifest_file_number_; }
+
+ uint64_t options_file_number() const { return options_file_number_; }
+
+ uint64_t pending_manifest_file_number() const {
+ return pending_manifest_file_number_;
+ }
+
+ uint64_t current_next_file_number() const { return next_file_number_.load(); }
+
+ uint64_t min_log_number_to_keep_2pc() const {
+ return min_log_number_to_keep_2pc_.load();
+ }
+
+ // Allocate and return a new file number
+ uint64_t NewFileNumber() { return next_file_number_.fetch_add(1); }
+
+ // Fetch And Add n new file number
+ uint64_t FetchAddFileNumber(uint64_t n) {
+ return next_file_number_.fetch_add(n);
+ }
+
+ // Return the last sequence number.
+ uint64_t LastSequence() const {
+ return last_sequence_.load(std::memory_order_acquire);
+ }
+
+ // Note: memory_order_acquire must be sufficient.
+ uint64_t LastAllocatedSequence() const {
+ return last_allocated_sequence_.load(std::memory_order_seq_cst);
+ }
+
+ // Note: memory_order_acquire must be sufficient.
+ uint64_t LastPublishedSequence() const {
+ return last_published_sequence_.load(std::memory_order_seq_cst);
+ }
+
+ // Set the last sequence number to s.
+ void SetLastSequence(uint64_t s) {
+ assert(s >= last_sequence_);
+ // Last visible sequence must always be less than last written seq
+ assert(!db_options_->two_write_queues || s <= last_allocated_sequence_);
+ last_sequence_.store(s, std::memory_order_release);
+ }
+
+ // Note: memory_order_release must be sufficient
+ void SetLastPublishedSequence(uint64_t s) {
+ assert(s >= last_published_sequence_);
+ last_published_sequence_.store(s, std::memory_order_seq_cst);
+ }
+
+ // Note: memory_order_release must be sufficient
+ void SetLastAllocatedSequence(uint64_t s) {
+ assert(s >= last_allocated_sequence_);
+ last_allocated_sequence_.store(s, std::memory_order_seq_cst);
+ }
+
+ // Note: memory_order_release must be sufficient
+ uint64_t FetchAddLastAllocatedSequence(uint64_t s) {
+ return last_allocated_sequence_.fetch_add(s, std::memory_order_seq_cst);
+ }
+
+ // Mark the specified file number as used.
+ // REQUIRED: this is only called during single-threaded recovery or repair.
+ void MarkFileNumberUsed(uint64_t number);
+
+ // Mark the specified log number as deleted
+ // REQUIRED: this is only called during single-threaded recovery or repair, or
+ // from ::LogAndApply where the global mutex is held.
+ void MarkMinLogNumberToKeep2PC(uint64_t number);
+
+ // Return the log file number for the log file that is currently
+ // being compacted, or zero if there is no such log file.
+ uint64_t prev_log_number() const { return prev_log_number_; }
+
+ // Returns the minimum log number which still has data not flushed to any SST
+ // file.
+ // In non-2PC mode, all the log numbers smaller than this number can be safely
+ // deleted.
+ uint64_t MinLogNumberWithUnflushedData() const {
+ return PreComputeMinLogNumberWithUnflushedData(nullptr);
+ }
+ // Returns the minimum log number which still has data not flushed to any SST
+ // file, except data from `cfd_to_skip`.
+ uint64_t PreComputeMinLogNumberWithUnflushedData(
+ const ColumnFamilyData* cfd_to_skip) const {
+ uint64_t min_log_num = std::numeric_limits<uint64_t>::max();
+ for (auto cfd : *column_family_set_) {
+ if (cfd == cfd_to_skip) {
+ continue;
+ }
+ // It's safe to ignore dropped column families here:
+ // cfd->IsDropped() becomes true after the drop is persisted in MANIFEST.
+ if (min_log_num > cfd->GetLogNumber() && !cfd->IsDropped()) {
+ min_log_num = cfd->GetLogNumber();
+ }
+ }
+ return min_log_num;
+ }
+
+ // Create an iterator that reads over the compaction inputs for "*c".
+ // The caller should delete the iterator when no longer needed.
+ InternalIterator* MakeInputIterator(
+ const Compaction* c, RangeDelAggregator* range_del_agg,
+ const FileOptions& file_options_compactions);
+
+ // Add all files listed in any live version to *live.
+ void AddLiveFiles(std::vector<FileDescriptor>* live_list);
+
+ // Return the approximate size of data to be scanned for range [start, end)
+ // in levels [start_level, end_level). If end_level == -1 it will search
+ // through all non-empty levels
+ uint64_t ApproximateSize(const SizeApproximationOptions& options, Version* v,
+ const Slice& start, const Slice& end,
+ int start_level, int end_level,
+ TableReaderCaller caller);
+
+ // Return the size of the current manifest file
+ uint64_t manifest_file_size() const { return manifest_file_size_; }
+
+ // verify that the files that we started with for a compaction
+ // still exist in the current version and in the same original level.
+ // This ensures that a concurrent compaction did not erroneously
+ // pick the same files to compact.
+ bool VerifyCompactionFileConsistency(Compaction* c);
+
+ Status GetMetadataForFile(uint64_t number, int* filelevel,
+ FileMetaData** metadata, ColumnFamilyData** cfd);
+
+ // This function doesn't support leveldb SST filenames
+ void GetLiveFilesMetaData(std::vector<LiveFileMetaData> *metadata);
+
+ void GetObsoleteFiles(std::vector<ObsoleteFileInfo>* files,
+ std::vector<std::string>* manifest_filenames,
+ uint64_t min_pending_output);
+
+ ColumnFamilySet* GetColumnFamilySet() { return column_family_set_.get(); }
+ const FileOptions& file_options() { return file_options_; }
+ void ChangeFileOptions(const MutableDBOptions& new_options) {
+ file_options_.writable_file_max_buffer_size =
+ new_options.writable_file_max_buffer_size;
+ }
+
+ const ImmutableDBOptions* db_options() const { return db_options_; }
+
+ static uint64_t GetNumLiveVersions(Version* dummy_versions);
+
+ static uint64_t GetTotalSstFilesSize(Version* dummy_versions);
+
+ protected:
+ struct ManifestWriter;
+
+ friend class Version;
+ friend class DBImpl;
+ friend class DBImplReadOnly;
+
+ struct LogReporter : public log::Reader::Reporter {
+ Status* status;
+ virtual void Corruption(size_t /*bytes*/, const Status& s) override {
+ if (this->status->ok()) *this->status = s;
+ }
+ };
+
+ // Returns approximated offset of a key in a file for a given version.
+ uint64_t ApproximateOffsetOf(Version* v, const FdWithKeyRange& f,
+ const Slice& key, TableReaderCaller caller);
+
+ // Returns approximated data size between start and end keys in a file
+ // for a given version.
+ uint64_t ApproximateSize(Version* v, const FdWithKeyRange& f,
+ const Slice& start, const Slice& end,
+ TableReaderCaller caller);
+
+ struct MutableCFState {
+ uint64_t log_number;
+ };
+
+ // Save current contents to *log
+ Status WriteCurrentStateToManifest(
+ const std::unordered_map<uint32_t, MutableCFState>& curr_state,
+ log::Writer* log);
+
+ void AppendVersion(ColumnFamilyData* column_family_data, Version* v);
+
+ ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
+ VersionEdit* edit);
+
+ Status ReadAndRecover(
+ log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
+ const std::unordered_map<std::string, ColumnFamilyOptions>&
+ name_to_options,
+ std::unordered_map<int, std::string>& column_families_not_found,
+ std::unordered_map<
+ uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>& builders,
+ VersionEditParams* version_edit, std::string* db_id = nullptr);
+
+ // REQUIRES db mutex
+ Status ApplyOneVersionEditToBuilder(
+ VersionEdit& edit,
+ const std::unordered_map<std::string, ColumnFamilyOptions>& name_to_opts,
+ std::unordered_map<int, std::string>& column_families_not_found,
+ std::unordered_map<
+ uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>& builders,
+ VersionEditParams* version_edit);
+
+ Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
+ const VersionEdit& from_edit,
+ VersionEditParams* version_edit_params);
+
+ std::unique_ptr<ColumnFamilySet> column_family_set_;
+
+ Env* const env_;
+ FileSystem* const fs_;
+ const std::string dbname_;
+ std::string db_id_;
+ const ImmutableDBOptions* const db_options_;
+ std::atomic<uint64_t> next_file_number_;
+ // Any log number equal or lower than this should be ignored during recovery,
+ // and is qualified for being deleted in 2PC mode. In non-2PC mode, this
+ // number is ignored.
+ std::atomic<uint64_t> min_log_number_to_keep_2pc_ = {0};
+ uint64_t manifest_file_number_;
+ uint64_t options_file_number_;
+ uint64_t pending_manifest_file_number_;
+ // The last seq visible to reads. It normally indicates the last sequence in
+ // the memtable but when using two write queues it could also indicate the
+ // last sequence in the WAL visible to reads.
+ std::atomic<uint64_t> last_sequence_;
+ // The last seq that is already allocated. It is applicable only when we have
+ // two write queues. In that case seq might or might not have appreated in
+ // memtable but it is expected to appear in the WAL.
+ // We have last_sequence <= last_allocated_sequence_
+ std::atomic<uint64_t> last_allocated_sequence_;
+ // The last allocated sequence that is also published to the readers. This is
+ // applicable only when last_seq_same_as_publish_seq_ is not set. Otherwise
+ // last_sequence_ also indicates the last published seq.
+ // We have last_sequence <= last_published_sequence_ <=
+ // last_allocated_sequence_
+ std::atomic<uint64_t> last_published_sequence_;
+ uint64_t prev_log_number_; // 0 or backing store for memtable being compacted
+
+ // Opened lazily
+ std::unique_ptr<log::Writer> descriptor_log_;
+
+ // generates a increasing version number for every new version
+ uint64_t current_version_number_;
+
+ // Queue of writers to the manifest file
+ std::deque<ManifestWriter*> manifest_writers_;
+
+ // Current size of manifest file
+ uint64_t manifest_file_size_;
+
+ std::vector<ObsoleteFileInfo> obsolete_files_;
+ std::vector<std::string> obsolete_manifests_;
+
+ // env options for all reads and writes except compactions
+ FileOptions file_options_;
+
+ BlockCacheTracer* const block_cache_tracer_;
+
+ private:
+ // REQUIRES db mutex at beginning. may release and re-acquire db mutex
+ Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
+ InstrumentedMutex* mu, Directory* db_directory,
+ bool new_descriptor_log,
+ const ColumnFamilyOptions* new_cf_options);
+
+ void LogAndApplyCFHelper(VersionEdit* edit);
+ Status LogAndApplyHelper(ColumnFamilyData* cfd, VersionBuilder* b,
+ VersionEdit* edit, InstrumentedMutex* mu);
+};
+
+// ReactiveVersionSet represents a collection of versions of the column
+// families of the database. Users of ReactiveVersionSet, e.g. DBImplSecondary,
+// need to replay the MANIFEST (description log in older terms) in order to
+// reconstruct and install versions.
+class ReactiveVersionSet : public VersionSet {
+ public:
+ ReactiveVersionSet(const std::string& dbname,
+ const ImmutableDBOptions* _db_options,
+ const FileOptions& _file_options, Cache* table_cache,
+ WriteBufferManager* write_buffer_manager,
+ WriteController* write_controller);
+
+ ~ReactiveVersionSet() override;
+
+ Status ReadAndApply(
+ InstrumentedMutex* mu,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
+ std::unordered_set<ColumnFamilyData*>* cfds_changed);
+
+ Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
+ std::unique_ptr<log::Reader::Reporter>* manifest_reporter,
+ std::unique_ptr<Status>* manifest_reader_status);
+
+ uint64_t TEST_read_edits_in_atomic_group() const {
+ return read_buffer_.TEST_read_edits_in_atomic_group();
+ }
+ std::vector<VersionEdit>& replay_buffer() {
+ return read_buffer_.replay_buffer();
+ }
+
+ protected:
+ using VersionSet::ApplyOneVersionEditToBuilder;
+
+ // REQUIRES db mutex
+ Status ApplyOneVersionEditToBuilder(
+ VersionEdit& edit, std::unordered_set<ColumnFamilyData*>* cfds_changed,
+ VersionEdit* version_edit);
+
+ Status MaybeSwitchManifest(
+ log::Reader::Reporter* reporter,
+ std::unique_ptr<log::FragmentBufferedReader>* manifest_reader);
+
+ private:
+ std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
+ active_version_builders_;
+ AtomicGroupReadBuffer read_buffer_;
+ // Number of version edits to skip by ReadAndApply at the beginning of a new
+ // MANIFEST created by primary.
+ int number_of_edits_to_skip_;
+
+ using VersionSet::LogAndApply;
+ using VersionSet::Recover;
+
+ Status LogAndApply(
+ const autovector<ColumnFamilyData*>& /*cfds*/,
+ const autovector<const MutableCFOptions*>& /*mutable_cf_options_list*/,
+ const autovector<autovector<VersionEdit*>>& /*edit_lists*/,
+ InstrumentedMutex* /*mu*/, Directory* /*db_directory*/,
+ bool /*new_descriptor_log*/,
+ const ColumnFamilyOptions* /*new_cf_option*/) override {
+ return Status::NotSupported("not supported in reactive mode");
+ }
+
+ // No copy allowed
+ ReactiveVersionSet(const ReactiveVersionSet&);
+ ReactiveVersionSet& operator=(const ReactiveVersionSet&);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/version_set_test.cc b/storage/rocksdb/rocksdb/db/version_set_test.cc
new file mode 100644
index 00000000..03e0e26d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/version_set_test.cc
@@ -0,0 +1,1287 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "db/db_impl/db_impl.h"
+#include "db/log_writer.h"
+#include "logging/logging.h"
+#include "table/mock_table.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<FileMetaData*> 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, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ 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<CountingLogger> logger) {
+ Options opt;
+ opt.num_levels = num_levels;
+ opt.info_log = logger;
+ return opt;
+}
+
+class VersionStorageInfoTest : public testing::Test {
+ public:
+ const Comparator* ucmp_;
+ InternalKeyComparator icmp_;
+ std::shared_ptr<CountingLogger> logger_;
+ Options options_;
+ ImmutableCFOptions ioptions_;
+ MutableCFOptions mutable_cf_options_;
+ VersionStorageInfo vstorage_;
+
+ InternalKey GetInternalKey(const char* ukey,
+ SequenceNumber smallest_seq = 100) {
+ return InternalKey(ukey, smallest_seq, kTypeValue);
+ }
+
+ VersionStorageInfoTest()
+ : ucmp_(BytewiseComparator()),
+ icmp_(ucmp_),
+ logger_(new CountingLogger()),
+ options_(GetOptionsWithNumLevels(6, logger_)),
+ ioptions_(options_),
+ mutable_cf_options_(options_),
+ vstorage_(&icmp_, ucmp_, 6, kCompactionStyleLevel, nullptr, false) {}
+
+ ~VersionStorageInfoTest() 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) {
+ assert(level < vstorage_.num_levels());
+ FileMetaData* f = new FileMetaData(
+ file_number, 0, file_size, GetInternalKey(smallest, 0),
+ GetInternalKey(largest, 0), /* smallest_seq */ 0, /* largest_seq */ 0,
+ /* marked_for_compact */ false, kInvalidBlobFileNumber,
+ kUnknownOldestAncesterTime, kUnknownFileCreationTime,
+ kUnknownFileChecksum, kUnknownFileChecksumFuncName);
+ f->compensated_file_size = file_size;
+ vstorage_.AddFile(level, f);
+ }
+
+ void Add(int level, uint32_t file_number, const InternalKey& smallest,
+ const InternalKey& largest, uint64_t file_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,
+ kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
+ kUnknownFileCreationTime, kUnknownFileChecksum,
+ kUnknownFileChecksumFuncName);
+ f->compensated_file_size = file_size;
+ vstorage_.AddFile(level, f);
+ }
+
+ std::string GetOverlappingFiles(int level, const InternalKey& begin,
+ const InternalKey& end) {
+ std::vector<FileMetaData*> 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;
+ }
+};
+
+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");
+ Add(5, 101U, "1", "2");
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ 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) {
+ 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);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(0, logger_->log_count);
+ ASSERT_EQ(vstorage_.base_level(), 5);
+
+ Add(5, 2U, "3", "4", 550U);
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(0, logger_->log_count);
+ ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1000U);
+ ASSERT_EQ(vstorage_.base_level(), 4);
+
+ Add(4, 3U, "3", "4", 550U);
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(0, logger_->log_count);
+ ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1000U);
+ ASSERT_EQ(vstorage_.base_level(), 4);
+
+ Add(3, 4U, "3", "4", 250U);
+ Add(3, 5U, "5", "7", 300U);
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(1, logger_->log_count);
+ ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1005U);
+ ASSERT_EQ(vstorage_.MaxBytesForLevel(3), 1000U);
+ ASSERT_EQ(vstorage_.base_level(), 3);
+
+ Add(1, 6U, "3", "4", 5U);
+ Add(1, 7U, "8", "9", 5U);
+ logger_->log_count = 0;
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ 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);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ 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);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ 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);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ 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);
+ // Level size should be around 30,000, 105,000, 367,500
+ ASSERT_EQ(40000U, vstorage_.MaxBytesForLevel(2));
+ ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3));
+ ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4));
+}
+
+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 = 2;
+
+ Add(0, 11U, "1", "2", 10000U);
+ Add(0, 12U, "1", "2", 10000U);
+ Add(0, 13U, "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);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(0, logger_->log_count);
+ ASSERT_EQ(2, vstorage_.base_level());
+ // level multiplier should be 3.5
+ ASSERT_LT(vstorage_.level_multiplier(), 3.6);
+ ASSERT_GT(vstorage_.level_multiplier(), 3.4);
+ // Level size should be around 30,000, 105,000, 367,500
+ ASSERT_EQ(30000U, vstorage_.MaxBytesForLevel(2));
+ ASSERT_LT(vstorage_.MaxBytesForLevel(3), 110000U);
+ ASSERT_GT(vstorage_.MaxBytesForLevel(3), 100000U);
+ ASSERT_LT(vstorage_.MaxBytesForLevel(4), 370000U);
+ ASSERT_GT(vstorage_.MaxBytesForLevel(4), 360000U);
+}
+
+TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_3) {
+ 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 = 2;
+
+ Add(0, 11U, "1", "2", 5000U);
+ Add(0, 12U, "1", "2", 5000U);
+ Add(0, 13U, "1", "2", 5000U);
+ Add(0, 14U, "1", "2", 5000U);
+ Add(0, 15U, "1", "2", 5000U);
+ Add(0, 16U, "1", "2", 5000U);
+
+ Add(5, 4U, "1", "2", 1286250U);
+ Add(4, 5U, "1", "2", 200000U);
+ Add(3, 6U, "1", "2", 40000U);
+ Add(2, 7U, "1", "2", 8000U);
+
+ vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
+ ASSERT_EQ(0, logger_->log_count);
+ ASSERT_EQ(2, vstorage_.base_level());
+ // level multiplier should be 3.5
+ ASSERT_LT(vstorage_.level_multiplier(), 3.6);
+ ASSERT_GT(vstorage_.level_multiplier(), 3.4);
+ // Level size should be around 30,000, 105,000, 367,500
+ ASSERT_EQ(30000U, vstorage_.MaxBytesForLevel(2));
+ ASSERT_LT(vstorage_.MaxBytesForLevel(3), 110000U);
+ ASSERT_GT(vstorage_.MaxBytesForLevel(3), 100000U);
+ ASSERT_LT(vstorage_.MaxBytesForLevel(4), 370000U);
+ ASSERT_GT(vstorage_.MaxBytesForLevel(4), 360000U);
+}
+
+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, 5U, "6", "7", 1U); // Inside range of last level
+ Add(5, 6U, "4", "7", 10U);
+ ASSERT_EQ(10U, vstorage_.EstimateLiveDataSize());
+}
+
+TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) {
+ Add(0, 1U, "9", "9", 1U); // Level 0 is not ordered
+ Add(0, 1U, "5", "6", 1U); // Ignored because of [5,6] in l1
+ Add(1, 1U, "1", "2", 1U); // Ignored because of [2,3] in l2
+ Add(1, 2U, "3", "4", 1U); // Ignored because of [2,3] in l2
+ Add(1, 3U, "5", "6", 1U);
+ Add(2, 4U, "2", "3", 1U);
+ Add(3, 5U, "7", "8", 1U);
+ 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);
+ vstorage_.UpdateNumNonEmptyLevels();
+ vstorage_.GenerateLevelFilesBrief();
+
+ 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}));
+}
+
+
+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_;
+
+ VersionSetTestBase()
+ : env_(Env::Default()),
+ fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
+ dbname_(test::PerThreadDBPath("version_set_test")),
+ db_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<mock::MockTableFactory>()) {
+ EXPECT_OK(env_->CreateDirIfMissing(dbname_));
+
+ db_options_.env = env_;
+ db_options_.fs = fs_;
+ versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
+ table_cache_.get(), &write_buffer_manager_,
+ &write_controller_,
+ /*block_cache_tracer=*/nullptr)),
+ reactive_versions_ = std::make_shared<ReactiveVersionSet>(
+ dbname_, &db_options_, env_options_, table_cache_.get(),
+ &write_buffer_manager_, &write_controller_);
+ db_options_.db_paths.emplace_back(dbname_,
+ std::numeric_limits<uint64_t>::max());
+ }
+
+ void PrepareManifest(std::vector<ColumnFamilyDescriptor>* column_families,
+ SequenceNumber* last_seqno,
+ std::unique_ptr<log::Writer>* log_writer) {
+ assert(column_families != nullptr);
+ assert(last_seqno != nullptr);
+ assert(log_writer != nullptr);
+ VersionEdit new_db;
+ if (db_options_.write_dbid_to_manifest) {
+ DBImpl* impl = new DBImpl(DBOptions(), 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<std::string> cf_names = {
+ kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
+ kColumnFamilyName3};
+ const int kInitialNumOfCfs = static_cast<int>(cf_names.size());
+ autovector<VersionEdit> 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<int>(new_cfs.size() + 1);
+ const std::string manifest = DescriptorFileName(dbname_, 1);
+ std::unique_ptr<WritableFile> file;
+ Status s = env_->NewWritableFile(
+ manifest, &file, env_->OptimizeForManifestWrite(env_options_));
+ ASSERT_OK(s);
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), manifest, env_options_));
+ {
+ 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() {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ SequenceNumber last_seqno;
+ std::unique_ptr<log::Writer> 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(env_, dbname_, 1, nullptr);
+ ASSERT_OK(s);
+
+ EXPECT_OK(versions_->Recover(column_families, false));
+ EXPECT_EQ(column_families.size(),
+ versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
+ }
+
+ Env* env_;
+ std::shared_ptr<FileSystem> fs_;
+ const std::string dbname_;
+ EnvOptions env_options_;
+ ImmutableDBOptions db_options_;
+ ColumnFamilyOptions cf_options_;
+ MutableCFOptions mutable_cf_options_;
+ std::shared_ptr<Cache> table_cache_;
+ WriteController write_controller_;
+ WriteBufferManager write_buffer_manager_;
+ std::shared_ptr<VersionSet> versions_;
+ std::shared_ptr<ReactiveVersionSet> reactive_versions_;
+ InstrumentedMutex mutex_;
+ std::atomic<bool> shutting_down_;
+ std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
+};
+
+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() {}
+};
+
+TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
+ NewDB();
+ const int kGroupSize = 5;
+ autovector<VersionEdit> edits;
+ for (int i = 0; i != kGroupSize; ++i) {
+ edits.emplace_back(VersionEdit());
+ }
+ autovector<ColumnFamilyData*> cfds;
+ autovector<const MutableCFOptions*> all_mutable_cf_options;
+ autovector<autovector<VersionEdit*>> 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<VersionEdit*> 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<uint32_t*>(arg);
+ EXPECT_EQ(0u, *cf_id);
+ ++count;
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ mutex_.Lock();
+ Status s =
+ versions_->LogAndApply(cfds, all_mutable_cf_options, edit_lists, &mutex_);
+ mutex_.Unlock();
+ EXPECT_OK(s);
+ EXPECT_EQ(kGroupSize - 1, count);
+}
+
+class VersionSetAtomicGroupTest : public VersionSetTestBase,
+ public testing::Test {
+ public:
+ VersionSetAtomicGroupTest() : VersionSetTestBase() {}
+
+ 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(env_, 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(env_, 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(env_, 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(env_, dbname_, 1, nullptr));
+ }
+
+ void SetupTestSyncPoints() {
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ SyncPoint::GetInstance()->SetCallBack(
+ "AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", [&](void* arg) {
+ VersionEdit* e = reinterpret_cast<VersionEdit*>(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<VersionEdit*>(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(
+ "VersionSet::ReadAndRecover:RecoveredEdits", [&](void* arg) {
+ num_recovered_edits_ = *reinterpret_cast<int*>(arg);
+ });
+ SyncPoint::GetInstance()->SetCallBack(
+ "ReactiveVersionSet::ReadAndApply:AppliedEdits",
+ [&](void* arg) { num_applied_edits_ = *reinterpret_cast<int*>(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<VersionEdit*>(arg);
+ });
+ SyncPoint::GetInstance()->SetCallBack(
+ "AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize",
+ [&](void* arg) {
+ edit_with_incorrect_group_size_ =
+ *reinterpret_cast<VersionEdit*>(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<ColumnFamilyDescriptor> column_families_;
+ SequenceNumber last_seqno_;
+ std::vector<VersionEdit> edits_;
+ bool first_in_atomic_group_ = false;
+ bool last_in_atomic_group_ = false;
+ int num_edits_in_atomic_group_ = 0;
+ int num_recovered_edits_ = 0;
+ int num_applied_edits_ = 0;
+ VersionEdit corrupted_edit_;
+ VersionEdit edit_with_incorrect_group_size_;
+ std::unique_ptr<log::Writer> 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_);
+ EXPECT_EQ(0, num_applied_edits_);
+}
+
+TEST_F(VersionSetAtomicGroupTest,
+ HandleValidAtomicGroupWithReactiveVersionSetRecover) {
+ const int kAtomicGroupSize = 3;
+ SetupValidAtomicGroup(kAtomicGroupSize);
+ AddNewEditsToLog(kAtomicGroupSize);
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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_);
+ EXPECT_EQ(0, num_applied_edits_);
+}
+
+TEST_F(VersionSetAtomicGroupTest,
+ HandleValidAtomicGroupWithReactiveVersionSetReadAndApply) {
+ const int kAtomicGroupSize = 3;
+ SetupValidAtomicGroup(kAtomicGroupSize);
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> manifest_reader_status;
+ EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader,
+ &manifest_reporter,
+ &manifest_reader_status));
+ AddNewEditsToLog(kAtomicGroupSize);
+ InstrumentedMutex mu;
+ std::unordered_set<ColumnFamilyData*> cfds_changed;
+ mu.Lock();
+ EXPECT_OK(
+ reactive_versions_->ReadAndApply(&mu, &manifest_reader, &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(num_initial_edits_, num_recovered_edits_);
+ EXPECT_EQ(kAtomicGroupSize, num_applied_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_);
+ EXPECT_EQ(0, num_applied_edits_);
+}
+
+TEST_F(VersionSetAtomicGroupTest,
+ HandleIncompleteTrailingAtomicGroupWithReactiveVersionSetRecover) {
+ const int kAtomicGroupSize = 4;
+ const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1;
+ SetupIncompleteTrailingAtomicGroup(kAtomicGroupSize);
+ AddNewEditsToLog(kNumberOfPersistedVersionEdits);
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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<ColumnFamilyData*> cfds_changed;
+ mu.Lock();
+ EXPECT_OK(
+ reactive_versions_->ReadAndApply(&mu, &manifest_reader, &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_);
+ EXPECT_EQ(kAtomicGroupSize, num_applied_edits_);
+}
+
+TEST_F(VersionSetAtomicGroupTest,
+ HandleIncompleteTrailingAtomicGroupWithReactiveVersionSetReadAndApply) {
+ const int kAtomicGroupSize = 4;
+ const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1;
+ SetupIncompleteTrailingAtomicGroup(kAtomicGroupSize);
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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());
+ // Write a few edits in an atomic group.
+ AddNewEditsToLog(kNumberOfPersistedVersionEdits);
+ InstrumentedMutex mu;
+ std::unordered_set<ColumnFamilyData*> cfds_changed;
+ mu.Lock();
+ EXPECT_OK(
+ reactive_versions_->ReadAndApply(&mu, &manifest_reader, &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);
+ EXPECT_EQ(num_initial_edits_, num_recovered_edits_);
+ EXPECT_EQ(0, num_applied_edits_);
+}
+
+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<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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<ColumnFamilyData*> cfds_changed;
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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_OK(
+ reactive_versions_->ReadAndApply(&mu, &manifest_reader, &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<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> 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<ColumnFamilyData*> cfds_changed;
+ std::unique_ptr<log::FragmentBufferedReader> manifest_reader;
+ std::unique_ptr<log::Reader::Reporter> manifest_reporter;
+ std::unique_ptr<Status> manifest_reader_status;
+ EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader,
+ &manifest_reporter,
+ &manifest_reader_status));
+ AddNewEditsToLog(kAtomicGroupSize);
+ mu.Lock();
+ EXPECT_OK(
+ reactive_versions_->ReadAndApply(&mu, &manifest_reader, &cfds_changed));
+ mu.Unlock();
+ EXPECT_EQ(edits_[1].DebugString(),
+ edit_with_incorrect_group_size_.DebugString());
+}
+
+class VersionSetTestDropOneCF : public VersionSetTestBase,
+ public testing::TestWithParam<std::string> {
+ public:
+ VersionSetTestDropOneCF() : VersionSetTestBase() {}
+};
+
+// 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<ColumnFamilyDescriptor> column_families;
+ SequenceNumber last_seqno;
+ std::unique_ptr<log::Writer> log_writer;
+ PrepareManifest(&column_families, &last_seqno, &log_writer);
+ Status s = SetCurrentFile(env_, 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<std::string> 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_);
+ mutex_.Unlock();
+ ASSERT_OK(s);
+
+ std::vector<VersionEdit> edits(kAtomicGroupSize);
+ uint32_t remaining = kAtomicGroupSize;
+ size_t i = 0;
+ autovector<ColumnFamilyData*> cfds;
+ autovector<const MutableCFOptions*> mutable_cf_options_list;
+ autovector<autovector<VersionEdit*>> 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<VersionEdit*> 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<VersionEdit*>* tmp_edits =
+ reinterpret_cast<std::vector<VersionEdit*>*>(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_);
+ mutex_.Unlock();
+ ASSERT_OK(s);
+ ASSERT_EQ(1, called);
+ if (cfd_to_drop->Unref()) {
+ delete cfd_to_drop;
+ cfd_to_drop = nullptr;
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ AtomicGroup, VersionSetTestDropOneCF,
+ testing::Values(VersionSetTestBase::kColumnFamilyName1,
+ VersionSetTestBase::kColumnFamilyName2,
+ VersionSetTestBase::kColumnFamilyName3));
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/wal_manager.cc b/storage/rocksdb/rocksdb/db/wal_manager.cc
new file mode 100644
index 00000000..5b699274
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/wal_manager.cc
@@ -0,0 +1,510 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/wal_manager.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <memory>
+#include <vector>
+
+#include "db/log_reader.h"
+#include "db/log_writer.h"
+#include "db/transaction_log_impl.h"
+#include "db/write_batch_internal.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "file/sequence_file_reader.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/write_batch.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/coding.h"
+#include "util/mutexlock.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+Status WalManager::DeleteFile(const std::string& fname, uint64_t number) {
+ auto s = env_->DeleteFile(db_options_.wal_dir + "/" + fname);
+ if (s.ok()) {
+ MutexLock l(&read_first_record_cache_mutex_);
+ read_first_record_cache_.erase(number);
+ }
+ return s;
+}
+
+Status WalManager::GetSortedWalFiles(VectorLogPtr& files) {
+ // First get sorted files in db dir, then get sorted files from archived
+ // dir, to avoid a race condition where a log file is moved to archived
+ // dir in between.
+ Status s;
+ // list wal files in main db dir.
+ VectorLogPtr logs;
+ s = GetSortedWalsOfType(db_options_.wal_dir, logs, kAliveLogFile);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Reproduce the race condition where a log file is moved
+ // to archived dir, between these two sync points, used in
+ // (DBTest,TransactionLogIteratorRace)
+ TEST_SYNC_POINT("WalManager::GetSortedWalFiles:1");
+ TEST_SYNC_POINT("WalManager::GetSortedWalFiles:2");
+
+ files.clear();
+ // list wal files in archive dir.
+ std::string archivedir = ArchivalDirectory(db_options_.wal_dir);
+ Status exists = env_->FileExists(archivedir);
+ if (exists.ok()) {
+ s = GetSortedWalsOfType(archivedir, files, kArchivedLogFile);
+ if (!s.ok()) {
+ return s;
+ }
+ } else if (!exists.IsNotFound()) {
+ assert(s.IsIOError());
+ return s;
+ }
+
+ uint64_t latest_archived_log_number = 0;
+ if (!files.empty()) {
+ latest_archived_log_number = files.back()->LogNumber();
+ ROCKS_LOG_INFO(db_options_.info_log, "Latest Archived log: %" PRIu64,
+ latest_archived_log_number);
+ }
+
+ files.reserve(files.size() + logs.size());
+ for (auto& log : logs) {
+ if (log->LogNumber() > latest_archived_log_number) {
+ files.push_back(std::move(log));
+ } else {
+ // When the race condition happens, we could see the
+ // same log in both db dir and archived dir. Simply
+ // ignore the one in db dir. Note that, if we read
+ // archived dir first, we would have missed the log file.
+ ROCKS_LOG_WARN(db_options_.info_log, "%s already moved to archive",
+ log->PathName().c_str());
+ }
+ }
+
+ return s;
+}
+
+Status WalManager::GetUpdatesSince(
+ SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options,
+ VersionSet* version_set) {
+
+ // Get all sorted Wal Files.
+ // Do binary search and open files and find the seq number.
+
+ std::unique_ptr<VectorLogPtr> wal_files(new VectorLogPtr);
+ Status s = GetSortedWalFiles(*wal_files);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = RetainProbableWalFiles(*wal_files, seq);
+ if (!s.ok()) {
+ return s;
+ }
+ iter->reset(new TransactionLogIteratorImpl(
+ db_options_.wal_dir, &db_options_, read_options, file_options_, seq,
+ std::move(wal_files), version_set, seq_per_batch_));
+ return (*iter)->status();
+}
+
+// 1. Go through all archived files and
+// a. if ttl is enabled, delete outdated files
+// b. if archive size limit is enabled, delete empty files,
+// compute file number and size.
+// 2. If size limit is enabled:
+// a. compute how many files should be deleted
+// b. get sorted non-empty archived logs
+// c. delete what should be deleted
+void WalManager::PurgeObsoleteWALFiles() {
+ bool const ttl_enabled = db_options_.wal_ttl_seconds > 0;
+ bool const size_limit_enabled = db_options_.wal_size_limit_mb > 0;
+ if (!ttl_enabled && !size_limit_enabled) {
+ return;
+ }
+
+ int64_t current_time;
+ Status s = env_->GetCurrentTime(&current_time);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log, "Can't get current time: %s",
+ s.ToString().c_str());
+ assert(false);
+ return;
+ }
+ uint64_t const now_seconds = static_cast<uint64_t>(current_time);
+ uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled)
+ ? db_options_.wal_ttl_seconds / 2
+ : kDefaultIntervalToDeleteObsoleteWAL;
+
+ if (purge_wal_files_last_run_ + time_to_check > now_seconds) {
+ return;
+ }
+
+ purge_wal_files_last_run_ = now_seconds;
+
+ std::string archival_dir = ArchivalDirectory(db_options_.wal_dir);
+ std::vector<std::string> files;
+ s = env_->GetChildren(archival_dir, &files);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log, "Can't get archive files: %s",
+ s.ToString().c_str());
+ assert(false);
+ return;
+ }
+
+ size_t log_files_num = 0;
+ uint64_t log_file_size = 0;
+
+ for (auto& f : files) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(f, &number, &type) && type == kLogFile) {
+ std::string const file_path = archival_dir + "/" + f;
+ if (ttl_enabled) {
+ uint64_t file_m_time;
+ s = env_->GetFileModificationTime(file_path, &file_m_time);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Can't get file mod time: %s: %s", file_path.c_str(),
+ s.ToString().c_str());
+ continue;
+ }
+ if (now_seconds - file_m_time > db_options_.wal_ttl_seconds) {
+ s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
+ /*force_fg=*/!wal_in_db_path_);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log, "Can't delete file: %s: %s",
+ file_path.c_str(), s.ToString().c_str());
+ continue;
+ } else {
+ MutexLock l(&read_first_record_cache_mutex_);
+ read_first_record_cache_.erase(number);
+ }
+ continue;
+ }
+ }
+
+ if (size_limit_enabled) {
+ uint64_t file_size;
+ s = env_->GetFileSize(file_path, &file_size);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Unable to get file size: %s: %s", file_path.c_str(),
+ s.ToString().c_str());
+ return;
+ } else {
+ if (file_size > 0) {
+ log_file_size = std::max(log_file_size, file_size);
+ ++log_files_num;
+ } else {
+ s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
+ /*force_fg=*/!wal_in_db_path_);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Unable to delete file: %s: %s", file_path.c_str(),
+ s.ToString().c_str());
+ continue;
+ } else {
+ MutexLock l(&read_first_record_cache_mutex_);
+ read_first_record_cache_.erase(number);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (0 == log_files_num || !size_limit_enabled) {
+ return;
+ }
+
+ size_t const files_keep_num =
+ static_cast<size_t>(db_options_.wal_size_limit_mb * 1024 * 1024 / log_file_size);
+ if (log_files_num <= files_keep_num) {
+ return;
+ }
+
+ size_t files_del_num = log_files_num - files_keep_num;
+ VectorLogPtr archived_logs;
+ GetSortedWalsOfType(archival_dir, archived_logs, kArchivedLogFile);
+
+ if (files_del_num > archived_logs.size()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Trying to delete more archived log files than "
+ "exist. Deleting all");
+ files_del_num = archived_logs.size();
+ }
+
+ for (size_t i = 0; i < files_del_num; ++i) {
+ std::string const file_path = archived_logs[i]->PathName();
+ s = DeleteDBFile(&db_options_, db_options_.wal_dir + "/" + file_path,
+ db_options_.wal_dir, false,
+ /*force_fg=*/!wal_in_db_path_);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_options_.info_log, "Unable to delete file: %s: %s",
+ file_path.c_str(), s.ToString().c_str());
+ continue;
+ } else {
+ MutexLock l(&read_first_record_cache_mutex_);
+ read_first_record_cache_.erase(archived_logs[i]->LogNumber());
+ }
+ }
+}
+
+void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) {
+ auto archived_log_name = ArchivedLogFileName(db_options_.wal_dir, number);
+ // The sync point below is used in (DBTest,TransactionLogIteratorRace)
+ TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:1");
+ Status s = env_->RenameFile(fname, archived_log_name);
+ // The sync point below is used in (DBTest,TransactionLogIteratorRace)
+ TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:2");
+ ROCKS_LOG_INFO(db_options_.info_log, "Move log file %s to %s -- %s\n",
+ fname.c_str(), archived_log_name.c_str(),
+ s.ToString().c_str());
+}
+
+Status WalManager::GetSortedWalsOfType(const std::string& path,
+ VectorLogPtr& log_files,
+ WalFileType log_type) {
+ std::vector<std::string> all_files;
+ const Status status = env_->GetChildren(path, &all_files);
+ if (!status.ok()) {
+ return status;
+ }
+ log_files.reserve(all_files.size());
+ for (const auto& f : all_files) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(f, &number, &type) && type == kLogFile) {
+ SequenceNumber sequence;
+ Status s = ReadFirstRecord(log_type, number, &sequence);
+ if (!s.ok()) {
+ return s;
+ }
+ if (sequence == 0) {
+ // empty file
+ continue;
+ }
+
+ // Reproduce the race condition where a log file is moved
+ // to archived dir, between these two sync points, used in
+ // (DBTest,TransactionLogIteratorRace)
+ TEST_SYNC_POINT("WalManager::GetSortedWalsOfType:1");
+ TEST_SYNC_POINT("WalManager::GetSortedWalsOfType:2");
+
+ uint64_t size_bytes;
+ s = env_->GetFileSize(LogFileName(path, number), &size_bytes);
+ // re-try in case the alive log file has been moved to archive.
+ if (!s.ok() && log_type == kAliveLogFile) {
+ std::string archived_file = ArchivedLogFileName(path, number);
+ if (env_->FileExists(archived_file).ok()) {
+ s = env_->GetFileSize(archived_file, &size_bytes);
+ if (!s.ok() && env_->FileExists(archived_file).IsNotFound()) {
+ // oops, the file just got deleted from archived dir! move on
+ s = Status::OK();
+ continue;
+ }
+ }
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ log_files.push_back(std::unique_ptr<LogFile>(
+ new LogFileImpl(number, log_type, sequence, size_bytes)));
+ }
+ }
+ std::sort(
+ log_files.begin(), log_files.end(),
+ [](const std::unique_ptr<LogFile>& a, const std::unique_ptr<LogFile>& b) {
+ LogFileImpl* a_impl =
+ static_cast_with_check<LogFileImpl, LogFile>(a.get());
+ LogFileImpl* b_impl =
+ static_cast_with_check<LogFileImpl, LogFile>(b.get());
+ return *a_impl < *b_impl;
+ });
+ return status;
+}
+
+Status WalManager::RetainProbableWalFiles(VectorLogPtr& all_logs,
+ const SequenceNumber target) {
+ int64_t start = 0; // signed to avoid overflow when target is < first file.
+ int64_t end = static_cast<int64_t>(all_logs.size()) - 1;
+ // Binary Search. avoid opening all files.
+ while (end >= start) {
+ int64_t mid = start + (end - start) / 2; // Avoid overflow.
+ SequenceNumber current_seq_num = all_logs.at(static_cast<size_t>(mid))->StartSequence();
+ if (current_seq_num == target) {
+ end = mid;
+ break;
+ } else if (current_seq_num < target) {
+ start = mid + 1;
+ } else {
+ end = mid - 1;
+ }
+ }
+ // end could be -ve.
+ size_t start_index = static_cast<size_t>(std::max(static_cast<int64_t>(0), end));
+ // The last wal file is always included
+ all_logs.erase(all_logs.begin(), all_logs.begin() + start_index);
+ return Status::OK();
+}
+
+Status WalManager::ReadFirstRecord(const WalFileType type,
+ const uint64_t number,
+ SequenceNumber* sequence) {
+ *sequence = 0;
+ if (type != kAliveLogFile && type != kArchivedLogFile) {
+ ROCKS_LOG_ERROR(db_options_.info_log, "[WalManger] Unknown file type %s",
+ ToString(type).c_str());
+ return Status::NotSupported(
+ "File Type Not Known " + ToString(type));
+ }
+ {
+ MutexLock l(&read_first_record_cache_mutex_);
+ auto itr = read_first_record_cache_.find(number);
+ if (itr != read_first_record_cache_.end()) {
+ *sequence = itr->second;
+ return Status::OK();
+ }
+ }
+ Status s;
+ if (type == kAliveLogFile) {
+ std::string fname = LogFileName(db_options_.wal_dir, number);
+ s = ReadFirstLine(fname, number, sequence);
+ if (!s.ok() && env_->FileExists(fname).ok()) {
+ // return any error that is not caused by non-existing file
+ return s;
+ }
+ }
+
+ if (type == kArchivedLogFile || !s.ok()) {
+ // check if the file got moved to archive.
+ std::string archived_file =
+ ArchivedLogFileName(db_options_.wal_dir, number);
+ s = ReadFirstLine(archived_file, number, sequence);
+ // maybe the file was deleted from archive dir. If that's the case, return
+ // Status::OK(). The caller with identify this as empty file because
+ // *sequence == 0
+ if (!s.ok() && env_->FileExists(archived_file).IsNotFound()) {
+ return Status::OK();
+ }
+ }
+
+ if (s.ok() && *sequence != 0) {
+ MutexLock l(&read_first_record_cache_mutex_);
+ read_first_record_cache_.insert({number, *sequence});
+ }
+ return s;
+}
+
+Status WalManager::GetLiveWalFile(uint64_t number,
+ std::unique_ptr<LogFile>* log_file) {
+ if (!log_file) {
+ return Status::InvalidArgument("log_file not preallocated.");
+ }
+
+ if (!number) {
+ return Status::PathNotFound("log file not available");
+ }
+
+ Status s;
+
+ uint64_t size_bytes;
+ s = env_->GetFileSize(LogFileName(db_options_.wal_dir, number), &size_bytes);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ log_file->reset(new LogFileImpl(number, kAliveLogFile,
+ 0, // SequenceNumber
+ size_bytes));
+
+ return Status::OK();
+}
+
+// the function returns status.ok() and sequence == 0 if the file exists, but is
+// empty
+Status WalManager::ReadFirstLine(const std::string& fname,
+ const uint64_t number,
+ SequenceNumber* sequence) {
+ struct LogReporter : public log::Reader::Reporter {
+ Env* env;
+ Logger* info_log;
+ const char* fname;
+
+ Status* status;
+ bool ignore_error; // true if db_options_.paranoid_checks==false
+ void Corruption(size_t bytes, const Status& s) override {
+ ROCKS_LOG_WARN(info_log, "[WalManager] %s%s: dropping %d bytes; %s",
+ (this->ignore_error ? "(ignoring error) " : ""), fname,
+ static_cast<int>(bytes), s.ToString().c_str());
+ if (this->status->ok()) {
+ // only keep the first error
+ *this->status = s;
+ }
+ }
+ };
+
+ std::unique_ptr<FSSequentialFile> file;
+ Status status = fs_->NewSequentialFile(fname,
+ fs_->OptimizeForLogRead(file_options_),
+ &file, nullptr);
+ std::unique_ptr<SequentialFileReader> file_reader(
+ new SequentialFileReader(std::move(file), fname));
+
+ if (!status.ok()) {
+ return status;
+ }
+
+ LogReporter reporter;
+ reporter.env = env_;
+ reporter.info_log = db_options_.info_log.get();
+ reporter.fname = fname.c_str();
+ reporter.status = &status;
+ reporter.ignore_error = !db_options_.paranoid_checks;
+ log::Reader reader(db_options_.info_log, std::move(file_reader), &reporter,
+ true /*checksum*/, number);
+ std::string scratch;
+ Slice record;
+
+ if (reader.ReadRecord(&record, &scratch) &&
+ (status.ok() || !db_options_.paranoid_checks)) {
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter.Corruption(record.size(),
+ Status::Corruption("log record too small"));
+ // TODO read record's till the first no corrupt entry?
+ } else {
+ WriteBatch batch;
+ WriteBatchInternal::SetContents(&batch, record);
+ *sequence = WriteBatchInternal::Sequence(&batch);
+ return Status::OK();
+ }
+ }
+
+ // ReadRecord returns false on EOF, which means that the log file is empty. we
+ // return status.ok() in that case and set sequence number to 0
+ *sequence = 0;
+ return status;
+}
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/wal_manager.h b/storage/rocksdb/rocksdb/db/wal_manager.h
new file mode 100644
index 00000000..783bfe99
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/wal_manager.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <deque>
+#include <limits>
+#include <set>
+#include <utility>
+#include <vector>
+#include <string>
+#include <memory>
+
+#include "db/version_set.h"
+#include "file/file_util.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/status.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+// WAL manager provides the abstraction for reading the WAL files as a single
+// unit. Internally, it opens and reads the files using Reader or Writer
+// abstraction.
+class WalManager {
+ public:
+ WalManager(const ImmutableDBOptions& db_options,
+ const FileOptions& file_options, const bool seq_per_batch = false)
+ : db_options_(db_options),
+ file_options_(file_options),
+ env_(db_options.env),
+ fs_(db_options.fs.get()),
+ purge_wal_files_last_run_(0),
+ seq_per_batch_(seq_per_batch),
+ wal_in_db_path_(IsWalDirSameAsDBPath(&db_options)) {}
+
+ Status GetSortedWalFiles(VectorLogPtr& files);
+
+ // Allow user to tail transaction log to find all recent changes to the
+ // database that are newer than `seq_number`.
+ Status GetUpdatesSince(
+ SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options,
+ VersionSet* version_set);
+
+ void PurgeObsoleteWALFiles();
+
+ void ArchiveWALFile(const std::string& fname, uint64_t number);
+
+ Status DeleteFile(const std::string& fname, uint64_t number);
+
+ Status GetLiveWalFile(uint64_t number, std::unique_ptr<LogFile>* log_file);
+
+ Status TEST_ReadFirstRecord(const WalFileType type, const uint64_t number,
+ SequenceNumber* sequence) {
+ return ReadFirstRecord(type, number, sequence);
+ }
+
+ Status TEST_ReadFirstLine(const std::string& fname, const uint64_t number,
+ SequenceNumber* sequence) {
+ return ReadFirstLine(fname, number, sequence);
+ }
+
+ private:
+ Status GetSortedWalsOfType(const std::string& path, VectorLogPtr& log_files,
+ WalFileType type);
+ // Requires: all_logs should be sorted with earliest log file first
+ // Retains all log files in all_logs which contain updates with seq no.
+ // Greater Than or Equal to the requested SequenceNumber.
+ Status RetainProbableWalFiles(VectorLogPtr& all_logs,
+ const SequenceNumber target);
+
+ Status ReadFirstRecord(const WalFileType type, const uint64_t number,
+ SequenceNumber* sequence);
+
+ Status ReadFirstLine(const std::string& fname, const uint64_t number,
+ SequenceNumber* sequence);
+
+ // ------- state from DBImpl ------
+ const ImmutableDBOptions& db_options_;
+ const FileOptions file_options_;
+ Env* env_;
+ FileSystem* fs_;
+
+ // ------- WalManager state -------
+ // cache for ReadFirstRecord() calls
+ std::unordered_map<uint64_t, SequenceNumber> read_first_record_cache_;
+ port::Mutex read_first_record_cache_mutex_;
+
+ // last time when PurgeObsoleteWALFiles ran.
+ uint64_t purge_wal_files_last_run_;
+
+ bool seq_per_batch_;
+
+ bool wal_in_db_path_;
+
+ // obsolete files will be deleted every this seconds if ttl deletion is
+ // enabled and archive size_limit is disabled.
+ static const uint64_t kDefaultIntervalToDeleteObsoleteWAL = 600;
+};
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/wal_manager_test.cc b/storage/rocksdb/rocksdb/db/wal_manager_test.cc
new file mode 100644
index 00000000..26bad368
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/wal_manager_test.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <map>
+#include <string>
+
+#include "rocksdb/cache.h"
+#include "rocksdb/write_batch.h"
+#include "rocksdb/write_buffer_manager.h"
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/log_writer.h"
+#include "db/version_set.h"
+#include "db/wal_manager.h"
+#include "env/mock_env.h"
+#include "file/writable_file_writer.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()
+ : env_(new MockEnv(Env::Default())),
+ 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) {
+ 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<uint64_t>::max());
+ db_options_.wal_dir = dbname_;
+ db_options_.env = env_.get();
+ fs_.reset(new LegacyFileSystemWrapper(env_.get()));
+ db_options_.fs = fs_;
+
+ versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
+ table_cache_.get(), &write_buffer_manager_,
+ &write_controller_,
+ /*block_cache_tracer=*/nullptr));
+
+ wal_manager_.reset(new WalManager(db_options_, env_options_));
+ }
+
+ void Reopen() {
+ wal_manager_.reset(new WalManager(db_options_, env_options_));
+ }
+
+ // 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;
+ batch.Put(key, value);
+ WriteBatchInternal::SetSequence(&batch, seq);
+ 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_);
+ std::unique_ptr<WritableFile> file;
+ ASSERT_OK(env_->NewWritableFile(fname, &file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), fname, env_options_));
+ 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(ToString(k), std::string(1024, 'a'));
+ }
+ }
+ }
+
+ std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
+ const SequenceNumber seq) {
+ std::unique_ptr<TransactionLogIterator> iter;
+ Status status = wal_manager_->GetUpdatesSince(
+ seq, &iter, TransactionLogIterator::ReadOptions(), versions_.get());
+ EXPECT_OK(status);
+ return iter;
+ }
+
+ std::unique_ptr<MockEnv> env_;
+ std::string dbname_;
+ ImmutableDBOptions db_options_;
+ WriteController write_controller_;
+ EnvOptions env_options_;
+ std::shared_ptr<Cache> table_cache_;
+ WriteBufferManager write_buffer_manager_;
+ std::unique_ptr<VersionSet> versions_;
+ std::unique_ptr<WalManager> wal_manager_;
+ std::shared_ptr<LegacyFileSystemWrapper> fs_;
+
+ std::unique_ptr<log::Writer> current_log_writer_;
+ uint64_t current_log_number_;
+};
+
+TEST_F(WalManagerTest, ReadFirstRecordCache) {
+ Init();
+ std::string path = dbname_ + "/000001.log";
+ std::unique_ptr<WritableFile> file;
+ ASSERT_OK(env_->NewWritableFile(path, &file, EnvOptions()));
+
+ 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<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), path, EnvOptions()));
+ log::Writer writer(std::move(file_writer), 1,
+ db_options_.recycle_log_file_num > 0);
+ WriteBatch batch;
+ batch.Put("foo", "bar");
+ WriteBatchInternal::SetSequence(&batch, 10);
+ 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<std::string> files;
+ env->GetChildren(dir_path, &files);
+ for (auto& f : files) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(f, &number, &type) && type == kLogFile) {
+ std::string const file_path = dir_path + "/" + f;
+ uint64_t file_size;
+ env->GetFileSize(file_path, &file_size);
+ dir_size += file_size;
+ }
+ }
+ return dir_size;
+}
+std::vector<std::uint64_t> ListSpecificFiles(
+ Env* env, const std::string& path, const FileType expected_file_type) {
+ std::vector<std::string> files;
+ std::vector<uint64_t> file_numbers;
+ env->GetChildren(path, &files);
+ uint64_t number;
+ FileType type;
+ 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();
+ }
+ return count;
+}
+} // 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<std::uint64_t> log_files =
+ ListSpecificFiles(env_.get(), archive_dir, kLogFile);
+ 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_->FakeSleepForMicroseconds(2 * 1000 * 1000);
+ Reopen();
+ wal_manager_->PurgeObsoleteWALFiles();
+
+ log_files = ListSpecificFiles(env_.get(), archive_dir, kLogFile);
+ 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<uint64_t> log_files =
+ ListSpecificFiles(env_.get(), archive_dir, kLogFile);
+ ASSERT_GT(log_files.size(), 0U);
+
+ db_options_.wal_ttl_seconds = 1;
+ env_->FakeSleepForMicroseconds(3 * 1000 * 1000);
+ Reopen();
+ wal_manager_->PurgeObsoleteWALFiles();
+
+ log_files = ListSpecificFiles(env_.get(), archive_dir, kLogFile);
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as WalManager is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/write_batch.cc b/storage/rocksdb/rocksdb/db/write_batch.cc
new file mode 100644
index 00000000..d578db59
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_batch.cc
@@ -0,0 +1,2092 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// WriteBatch::rep_ :=
+// sequence: fixed64
+// count: fixed32
+// data: record[count]
+// record :=
+// kTypeValue varstring varstring
+// kTypeDeletion varstring
+// kTypeSingleDeletion varstring
+// kTypeRangeDeletion varstring varstring
+// kTypeMerge varstring varstring
+// kTypeColumnFamilyValue varint32 varstring varstring
+// kTypeColumnFamilyDeletion varint32 varstring
+// kTypeColumnFamilySingleDeletion varint32 varstring
+// kTypeColumnFamilyRangeDeletion varint32 varstring varstring
+// kTypeColumnFamilyMerge varint32 varstring varstring
+// kTypeBeginPrepareXID varstring
+// kTypeEndPrepareXID
+// kTypeCommitXID varstring
+// kTypeRollbackXID varstring
+// kTypeBeginPersistedPrepareXID varstring
+// kTypeBeginUnprepareXID varstring
+// kTypeNoop
+// varstring :=
+// len: varint32
+// data: uint8[len]
+
+#include "rocksdb/write_batch.h"
+
+#include <map>
+#include <stack>
+#include <stdexcept>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "db/flush_scheduler.h"
+#include "db/memtable.h"
+#include "db/merge_context.h"
+#include "db/snapshot_impl.h"
+#include "db/trim_history_scheduler.h"
+#include "db/write_batch_internal.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/merge_operator.h"
+#include "util/autovector.h"
+#include "util/cast_util.h"
+#include "util/coding.h"
+#include "util/duplicate_detector.h"
+#include "util/string_util.h"
+#include "util/util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// anon namespace for file-local types
+namespace {
+
+enum ContentFlags : uint32_t {
+ DEFERRED = 1 << 0,
+ HAS_PUT = 1 << 1,
+ HAS_DELETE = 1 << 2,
+ HAS_SINGLE_DELETE = 1 << 3,
+ HAS_MERGE = 1 << 4,
+ HAS_BEGIN_PREPARE = 1 << 5,
+ HAS_END_PREPARE = 1 << 6,
+ HAS_COMMIT = 1 << 7,
+ HAS_ROLLBACK = 1 << 8,
+ HAS_DELETE_RANGE = 1 << 9,
+ HAS_BLOB_INDEX = 1 << 10,
+ HAS_BEGIN_UNPREPARE = 1 << 11,
+};
+
+struct BatchContentClassifier : public WriteBatch::Handler {
+ uint32_t content_flags = 0;
+
+ Status PutCF(uint32_t, const Slice&, const Slice&) override {
+ content_flags |= ContentFlags::HAS_PUT;
+ return Status::OK();
+ }
+
+ Status DeleteCF(uint32_t, const Slice&) override {
+ content_flags |= ContentFlags::HAS_DELETE;
+ return Status::OK();
+ }
+
+ Status SingleDeleteCF(uint32_t, const Slice&) override {
+ content_flags |= ContentFlags::HAS_SINGLE_DELETE;
+ return Status::OK();
+ }
+
+ Status DeleteRangeCF(uint32_t, const Slice&, const Slice&) override {
+ content_flags |= ContentFlags::HAS_DELETE_RANGE;
+ return Status::OK();
+ }
+
+ Status MergeCF(uint32_t, const Slice&, const Slice&) override {
+ content_flags |= ContentFlags::HAS_MERGE;
+ return Status::OK();
+ }
+
+ Status PutBlobIndexCF(uint32_t, const Slice&, const Slice&) override {
+ content_flags |= ContentFlags::HAS_BLOB_INDEX;
+ return Status::OK();
+ }
+
+ Status MarkBeginPrepare(bool unprepare) override {
+ content_flags |= ContentFlags::HAS_BEGIN_PREPARE;
+ if (unprepare) {
+ content_flags |= ContentFlags::HAS_BEGIN_UNPREPARE;
+ }
+ return Status::OK();
+ }
+
+ Status MarkEndPrepare(const Slice&) override {
+ content_flags |= ContentFlags::HAS_END_PREPARE;
+ return Status::OK();
+ }
+
+ Status MarkCommit(const Slice&) override {
+ content_flags |= ContentFlags::HAS_COMMIT;
+ return Status::OK();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ content_flags |= ContentFlags::HAS_ROLLBACK;
+ return Status::OK();
+ }
+};
+
+class TimestampAssigner : public WriteBatch::Handler {
+ public:
+ explicit TimestampAssigner(const Slice& ts)
+ : timestamp_(ts), timestamps_(kEmptyTimestampList) {}
+ explicit TimestampAssigner(const std::vector<Slice>& ts_list)
+ : timestamps_(ts_list) {
+ SanityCheck();
+ }
+ ~TimestampAssigner() override {}
+
+ Status PutCF(uint32_t, const Slice& key, const Slice&) override {
+ AssignTimestamp(key);
+ ++idx_;
+ return Status::OK();
+ }
+
+ Status DeleteCF(uint32_t, const Slice& key) override {
+ AssignTimestamp(key);
+ ++idx_;
+ return Status::OK();
+ }
+
+ Status SingleDeleteCF(uint32_t, const Slice& key) override {
+ AssignTimestamp(key);
+ ++idx_;
+ return Status::OK();
+ }
+
+ Status DeleteRangeCF(uint32_t, const Slice& begin_key,
+ const Slice& end_key) override {
+ AssignTimestamp(begin_key);
+ AssignTimestamp(end_key);
+ ++idx_;
+ return Status::OK();
+ }
+
+ Status MergeCF(uint32_t, const Slice& key, const Slice&) override {
+ AssignTimestamp(key);
+ ++idx_;
+ return Status::OK();
+ }
+
+ Status PutBlobIndexCF(uint32_t, const Slice&, const Slice&) override {
+ // TODO (yanqin): support blob db in the future.
+ return Status::OK();
+ }
+
+ Status MarkBeginPrepare(bool) override {
+ // TODO (yanqin): support in the future.
+ return Status::OK();
+ }
+
+ Status MarkEndPrepare(const Slice&) override {
+ // TODO (yanqin): support in the future.
+ return Status::OK();
+ }
+
+ Status MarkCommit(const Slice&) override {
+ // TODO (yanqin): support in the future.
+ return Status::OK();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ // TODO (yanqin): support in the future.
+ return Status::OK();
+ }
+
+ private:
+ void SanityCheck() const {
+ assert(!timestamps_.empty());
+#ifndef NDEBUG
+ const size_t ts_sz = timestamps_[0].size();
+ for (size_t i = 1; i != timestamps_.size(); ++i) {
+ assert(ts_sz == timestamps_[i].size());
+ }
+#endif // !NDEBUG
+ }
+
+ void AssignTimestamp(const Slice& key) {
+ assert(timestamps_.empty() || idx_ < timestamps_.size());
+ const Slice& ts = timestamps_.empty() ? timestamp_ : timestamps_[idx_];
+ size_t ts_sz = ts.size();
+ char* ptr = const_cast<char*>(key.data() + key.size() - ts_sz);
+ memcpy(ptr, ts.data(), ts_sz);
+ }
+
+ static const std::vector<Slice> kEmptyTimestampList;
+ const Slice timestamp_;
+ const std::vector<Slice>& timestamps_;
+ size_t idx_ = 0;
+
+ // No copy or move.
+ TimestampAssigner(const TimestampAssigner&) = delete;
+ TimestampAssigner(TimestampAssigner&&) = delete;
+ TimestampAssigner& operator=(const TimestampAssigner&) = delete;
+ TimestampAssigner&& operator=(TimestampAssigner&&) = delete;
+};
+const std::vector<Slice> TimestampAssigner::kEmptyTimestampList;
+
+} // anon namespace
+
+struct SavePoints {
+ std::stack<SavePoint, autovector<SavePoint>> stack;
+};
+
+WriteBatch::WriteBatch(size_t reserved_bytes, size_t max_bytes)
+ : content_flags_(0), max_bytes_(max_bytes), rep_(), timestamp_size_(0) {
+ rep_.reserve((reserved_bytes > WriteBatchInternal::kHeader)
+ ? reserved_bytes
+ : WriteBatchInternal::kHeader);
+ rep_.resize(WriteBatchInternal::kHeader);
+}
+
+WriteBatch::WriteBatch(size_t reserved_bytes, size_t max_bytes, size_t ts_sz)
+ : content_flags_(0), max_bytes_(max_bytes), rep_(), timestamp_size_(ts_sz) {
+ rep_.reserve((reserved_bytes > WriteBatchInternal::kHeader) ?
+ reserved_bytes : WriteBatchInternal::kHeader);
+ rep_.resize(WriteBatchInternal::kHeader);
+}
+
+WriteBatch::WriteBatch(const std::string& rep)
+ : content_flags_(ContentFlags::DEFERRED),
+ max_bytes_(0),
+ rep_(rep),
+ timestamp_size_(0) {}
+
+WriteBatch::WriteBatch(std::string&& rep)
+ : content_flags_(ContentFlags::DEFERRED),
+ max_bytes_(0),
+ rep_(std::move(rep)),
+ timestamp_size_(0) {}
+
+WriteBatch::WriteBatch(const WriteBatch& src)
+ : wal_term_point_(src.wal_term_point_),
+ content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
+ max_bytes_(src.max_bytes_),
+ rep_(src.rep_),
+ timestamp_size_(src.timestamp_size_) {
+ if (src.save_points_ != nullptr) {
+ save_points_.reset(new SavePoints());
+ save_points_->stack = src.save_points_->stack;
+ }
+}
+
+WriteBatch::WriteBatch(WriteBatch&& src) noexcept
+ : save_points_(std::move(src.save_points_)),
+ wal_term_point_(std::move(src.wal_term_point_)),
+ content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
+ max_bytes_(src.max_bytes_),
+ rep_(std::move(src.rep_)),
+ timestamp_size_(src.timestamp_size_) {}
+
+WriteBatch& WriteBatch::operator=(const WriteBatch& src) {
+ if (&src != this) {
+ this->~WriteBatch();
+ new (this) WriteBatch(src);
+ }
+ return *this;
+}
+
+WriteBatch& WriteBatch::operator=(WriteBatch&& src) {
+ if (&src != this) {
+ this->~WriteBatch();
+ new (this) WriteBatch(std::move(src));
+ }
+ return *this;
+}
+
+WriteBatch::~WriteBatch() { }
+
+WriteBatch::Handler::~Handler() { }
+
+void WriteBatch::Handler::LogData(const Slice& /*blob*/) {
+ // If the user has not specified something to do with blobs, then we ignore
+ // them.
+}
+
+bool WriteBatch::Handler::Continue() {
+ return true;
+}
+
+void WriteBatch::Clear() {
+ rep_.clear();
+ rep_.resize(WriteBatchInternal::kHeader);
+
+ content_flags_.store(0, std::memory_order_relaxed);
+
+ if (save_points_ != nullptr) {
+ while (!save_points_->stack.empty()) {
+ save_points_->stack.pop();
+ }
+ }
+
+ wal_term_point_.clear();
+}
+
+uint32_t WriteBatch::Count() const { return WriteBatchInternal::Count(this); }
+
+uint32_t WriteBatch::ComputeContentFlags() const {
+ auto rv = content_flags_.load(std::memory_order_relaxed);
+ if ((rv & ContentFlags::DEFERRED) != 0) {
+ BatchContentClassifier classifier;
+ Iterate(&classifier);
+ rv = classifier.content_flags;
+
+ // this method is conceptually const, because it is performing a lazy
+ // computation that doesn't affect the abstract state of the batch.
+ // content_flags_ is marked mutable so that we can perform the
+ // following assignment
+ content_flags_.store(rv, std::memory_order_relaxed);
+ }
+ return rv;
+}
+
+void WriteBatch::MarkWalTerminationPoint() {
+ wal_term_point_.size = GetDataSize();
+ wal_term_point_.count = Count();
+ wal_term_point_.content_flags = content_flags_;
+}
+
+bool WriteBatch::HasPut() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_PUT) != 0;
+}
+
+bool WriteBatch::HasDelete() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_DELETE) != 0;
+}
+
+bool WriteBatch::HasSingleDelete() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_SINGLE_DELETE) != 0;
+}
+
+bool WriteBatch::HasDeleteRange() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_DELETE_RANGE) != 0;
+}
+
+bool WriteBatch::HasMerge() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_MERGE) != 0;
+}
+
+bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key, bool cf_record) {
+ assert(input != nullptr && key != nullptr);
+ // Skip tag byte
+ input->remove_prefix(1);
+
+ if (cf_record) {
+ // Skip column_family bytes
+ uint32_t cf;
+ if (!GetVarint32(input, &cf)) {
+ return false;
+ }
+ }
+
+ // Extract key
+ return GetLengthPrefixedSlice(input, key);
+}
+
+bool WriteBatch::HasBeginPrepare() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_BEGIN_PREPARE) != 0;
+}
+
+bool WriteBatch::HasEndPrepare() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_END_PREPARE) != 0;
+}
+
+bool WriteBatch::HasCommit() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_COMMIT) != 0;
+}
+
+bool WriteBatch::HasRollback() const {
+ return (ComputeContentFlags() & ContentFlags::HAS_ROLLBACK) != 0;
+}
+
+Status ReadRecordFromWriteBatch(Slice* input, char* tag,
+ uint32_t* column_family, Slice* key,
+ Slice* value, Slice* blob, Slice* xid) {
+ assert(key != nullptr && value != nullptr);
+ *tag = (*input)[0];
+ input->remove_prefix(1);
+ *column_family = 0; // default
+ switch (*tag) {
+ case kTypeColumnFamilyValue:
+ if (!GetVarint32(input, column_family)) {
+ return Status::Corruption("bad WriteBatch Put");
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeValue:
+ if (!GetLengthPrefixedSlice(input, key) ||
+ !GetLengthPrefixedSlice(input, value)) {
+ return Status::Corruption("bad WriteBatch Put");
+ }
+ break;
+ case kTypeColumnFamilyDeletion:
+ case kTypeColumnFamilySingleDeletion:
+ if (!GetVarint32(input, column_family)) {
+ return Status::Corruption("bad WriteBatch Delete");
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ if (!GetLengthPrefixedSlice(input, key)) {
+ return Status::Corruption("bad WriteBatch Delete");
+ }
+ break;
+ case kTypeColumnFamilyRangeDeletion:
+ if (!GetVarint32(input, column_family)) {
+ return Status::Corruption("bad WriteBatch DeleteRange");
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeRangeDeletion:
+ // for range delete, "key" is begin_key, "value" is end_key
+ if (!GetLengthPrefixedSlice(input, key) ||
+ !GetLengthPrefixedSlice(input, value)) {
+ return Status::Corruption("bad WriteBatch DeleteRange");
+ }
+ break;
+ case kTypeColumnFamilyMerge:
+ if (!GetVarint32(input, column_family)) {
+ return Status::Corruption("bad WriteBatch Merge");
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeMerge:
+ if (!GetLengthPrefixedSlice(input, key) ||
+ !GetLengthPrefixedSlice(input, value)) {
+ return Status::Corruption("bad WriteBatch Merge");
+ }
+ break;
+ case kTypeColumnFamilyBlobIndex:
+ if (!GetVarint32(input, column_family)) {
+ return Status::Corruption("bad WriteBatch BlobIndex");
+ }
+ FALLTHROUGH_INTENDED;
+ case kTypeBlobIndex:
+ if (!GetLengthPrefixedSlice(input, key) ||
+ !GetLengthPrefixedSlice(input, value)) {
+ return Status::Corruption("bad WriteBatch BlobIndex");
+ }
+ break;
+ case kTypeLogData:
+ assert(blob != nullptr);
+ if (!GetLengthPrefixedSlice(input, blob)) {
+ return Status::Corruption("bad WriteBatch Blob");
+ }
+ break;
+ case kTypeNoop:
+ case kTypeBeginPrepareXID:
+ // This indicates that the prepared batch is also persisted in the db.
+ // This is used in WritePreparedTxn
+ case kTypeBeginPersistedPrepareXID:
+ // This is used in WriteUnpreparedTxn
+ case kTypeBeginUnprepareXID:
+ break;
+ case kTypeEndPrepareXID:
+ if (!GetLengthPrefixedSlice(input, xid)) {
+ return Status::Corruption("bad EndPrepare XID");
+ }
+ break;
+ case kTypeCommitXID:
+ if (!GetLengthPrefixedSlice(input, xid)) {
+ return Status::Corruption("bad Commit XID");
+ }
+ break;
+ case kTypeRollbackXID:
+ if (!GetLengthPrefixedSlice(input, xid)) {
+ return Status::Corruption("bad Rollback XID");
+ }
+ break;
+ default:
+ return Status::Corruption("unknown WriteBatch tag");
+ }
+ return Status::OK();
+}
+
+Status WriteBatch::Iterate(Handler* handler) const {
+ if (rep_.size() < WriteBatchInternal::kHeader) {
+ return Status::Corruption("malformed WriteBatch (too small)");
+ }
+
+ return WriteBatchInternal::Iterate(this, handler, WriteBatchInternal::kHeader,
+ rep_.size());
+}
+
+Status WriteBatchInternal::Iterate(const WriteBatch* wb,
+ WriteBatch::Handler* handler, size_t begin,
+ size_t end) {
+ if (begin > wb->rep_.size() || end > wb->rep_.size() || end < begin) {
+ return Status::Corruption("Invalid start/end bounds for Iterate");
+ }
+ assert(begin <= end);
+ Slice input(wb->rep_.data() + begin, static_cast<size_t>(end - begin));
+ bool whole_batch =
+ (begin == WriteBatchInternal::kHeader) && (end == wb->rep_.size());
+
+ Slice key, value, blob, xid;
+ // Sometimes a sub-batch starts with a Noop. We want to exclude such Noops as
+ // the batch boundary symbols otherwise we would mis-count the number of
+ // batches. We do that by checking whether the accumulated batch is empty
+ // before seeing the next Noop.
+ bool empty_batch = true;
+ uint32_t found = 0;
+ Status s;
+ char tag = 0;
+ uint32_t column_family = 0; // default
+ bool last_was_try_again = false;
+ bool handler_continue = true;
+ while (((s.ok() && !input.empty()) || UNLIKELY(s.IsTryAgain()))) {
+ handler_continue = handler->Continue();
+ if (!handler_continue) {
+ break;
+ }
+
+ if (LIKELY(!s.IsTryAgain())) {
+ last_was_try_again = false;
+ tag = 0;
+ column_family = 0; // default
+
+ s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
+ &blob, &xid);
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ assert(s.IsTryAgain());
+ assert(!last_was_try_again); // to detect infinite loop bugs
+ if (UNLIKELY(last_was_try_again)) {
+ return Status::Corruption(
+ "two consecutive TryAgain in WriteBatch handler; this is either a "
+ "software bug or data corruption.");
+ }
+ last_was_try_again = true;
+ s = Status::OK();
+ }
+
+ switch (tag) {
+ case kTypeColumnFamilyValue:
+ case kTypeValue:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_PUT));
+ s = handler->PutCF(column_family, key, value);
+ if (LIKELY(s.ok())) {
+ empty_batch = false;
+ found++;
+ }
+ break;
+ case kTypeColumnFamilyDeletion:
+ case kTypeDeletion:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_DELETE));
+ s = handler->DeleteCF(column_family, key);
+ if (LIKELY(s.ok())) {
+ empty_batch = false;
+ found++;
+ }
+ break;
+ case kTypeColumnFamilySingleDeletion:
+ case kTypeSingleDeletion:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_SINGLE_DELETE));
+ s = handler->SingleDeleteCF(column_family, key);
+ if (LIKELY(s.ok())) {
+ empty_batch = false;
+ found++;
+ }
+ break;
+ case kTypeColumnFamilyRangeDeletion:
+ case kTypeRangeDeletion:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_DELETE_RANGE));
+ s = handler->DeleteRangeCF(column_family, key, value);
+ if (LIKELY(s.ok())) {
+ empty_batch = false;
+ found++;
+ }
+ break;
+ case kTypeColumnFamilyMerge:
+ case kTypeMerge:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_MERGE));
+ s = handler->MergeCF(column_family, key, value);
+ if (LIKELY(s.ok())) {
+ empty_batch = false;
+ found++;
+ }
+ break;
+ case kTypeColumnFamilyBlobIndex:
+ case kTypeBlobIndex:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_BLOB_INDEX));
+ s = handler->PutBlobIndexCF(column_family, key, value);
+ if (LIKELY(s.ok())) {
+ found++;
+ }
+ break;
+ case kTypeLogData:
+ handler->LogData(blob);
+ // A batch might have nothing but LogData. It is still a batch.
+ empty_batch = false;
+ break;
+ case kTypeBeginPrepareXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_PREPARE));
+ handler->MarkBeginPrepare();
+ empty_batch = false;
+ if (!handler->WriteAfterCommit()) {
+ s = Status::NotSupported(
+ "WriteCommitted txn tag when write_after_commit_ is disabled (in "
+ "WritePrepared/WriteUnprepared mode). If it is not due to "
+ "corruption, the WAL must be emptied before changing the "
+ "WritePolicy.");
+ }
+ if (handler->WriteBeforePrepare()) {
+ s = Status::NotSupported(
+ "WriteCommitted txn tag when write_before_prepare_ is enabled "
+ "(in WriteUnprepared mode). If it is not due to corruption, the "
+ "WAL must be emptied before changing the WritePolicy.");
+ }
+ break;
+ case kTypeBeginPersistedPrepareXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_PREPARE));
+ handler->MarkBeginPrepare();
+ empty_batch = false;
+ if (handler->WriteAfterCommit()) {
+ s = Status::NotSupported(
+ "WritePrepared/WriteUnprepared txn tag when write_after_commit_ "
+ "is enabled (in default WriteCommitted mode). If it is not due "
+ "to corruption, the WAL must be emptied before changing the "
+ "WritePolicy.");
+ }
+ break;
+ case kTypeBeginUnprepareXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_UNPREPARE));
+ handler->MarkBeginPrepare(true /* unprepared */);
+ empty_batch = false;
+ if (handler->WriteAfterCommit()) {
+ s = Status::NotSupported(
+ "WriteUnprepared txn tag when write_after_commit_ is enabled (in "
+ "default WriteCommitted mode). If it is not due to corruption, "
+ "the WAL must be emptied before changing the WritePolicy.");
+ }
+ if (!handler->WriteBeforePrepare()) {
+ s = Status::NotSupported(
+ "WriteUnprepared txn tag when write_before_prepare_ is disabled "
+ "(in WriteCommitted/WritePrepared mode). If it is not due to "
+ "corruption, the WAL must be emptied before changing the "
+ "WritePolicy.");
+ }
+ break;
+ case kTypeEndPrepareXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_END_PREPARE));
+ handler->MarkEndPrepare(xid);
+ empty_batch = true;
+ break;
+ case kTypeCommitXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_COMMIT));
+ handler->MarkCommit(xid);
+ empty_batch = true;
+ break;
+ case kTypeRollbackXID:
+ assert(wb->content_flags_.load(std::memory_order_relaxed) &
+ (ContentFlags::DEFERRED | ContentFlags::HAS_ROLLBACK));
+ handler->MarkRollback(xid);
+ empty_batch = true;
+ break;
+ case kTypeNoop:
+ handler->MarkNoop(empty_batch);
+ empty_batch = true;
+ break;
+ default:
+ return Status::Corruption("unknown WriteBatch tag");
+ }
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ if (handler_continue && whole_batch &&
+ found != WriteBatchInternal::Count(wb)) {
+ return Status::Corruption("WriteBatch has wrong count");
+ } else {
+ return Status::OK();
+ }
+}
+
+bool WriteBatchInternal::IsLatestPersistentState(const WriteBatch* b) {
+ return b->is_latest_persistent_state_;
+}
+
+void WriteBatchInternal::SetAsLastestPersistentState(WriteBatch* b) {
+ b->is_latest_persistent_state_ = true;
+}
+
+uint32_t WriteBatchInternal::Count(const WriteBatch* b) {
+ return DecodeFixed32(b->rep_.data() + 8);
+}
+
+void WriteBatchInternal::SetCount(WriteBatch* b, uint32_t n) {
+ EncodeFixed32(&b->rep_[8], n);
+}
+
+SequenceNumber WriteBatchInternal::Sequence(const WriteBatch* b) {
+ return SequenceNumber(DecodeFixed64(b->rep_.data()));
+}
+
+void WriteBatchInternal::SetSequence(WriteBatch* b, SequenceNumber seq) {
+ EncodeFixed64(&b->rep_[0], seq);
+}
+
+size_t WriteBatchInternal::GetFirstOffset(WriteBatch* /*b*/) {
+ return WriteBatchInternal::kHeader;
+}
+
+Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
+ const Slice& key, const Slice& value) {
+ if (key.size() > size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("key is too large");
+ }
+ if (value.size() > size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("value is too large");
+ }
+
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeValue));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyValue));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ if (0 == b->timestamp_size_) {
+ PutLengthPrefixedSlice(&b->rep_, key);
+ } else {
+ PutVarint32(&b->rep_,
+ static_cast<uint32_t>(key.size() + b->timestamp_size_));
+ b->rep_.append(key.data(), key.size());
+ b->rep_.append(b->timestamp_size_, '\0');
+ }
+ PutLengthPrefixedSlice(&b->rep_, value);
+ b->content_flags_.store(
+ b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) {
+ return WriteBatchInternal::Put(this, GetColumnFamilyID(column_family), key,
+ value);
+}
+
+Status WriteBatchInternal::CheckSlicePartsLength(const SliceParts& key,
+ const SliceParts& value) {
+ size_t total_key_bytes = 0;
+ for (int i = 0; i < key.num_parts; ++i) {
+ total_key_bytes += key.parts[i].size();
+ }
+ if (total_key_bytes >= size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("key is too large");
+ }
+
+ size_t total_value_bytes = 0;
+ for (int i = 0; i < value.num_parts; ++i) {
+ total_value_bytes += value.parts[i].size();
+ }
+ if (total_value_bytes >= size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("value is too large");
+ }
+ return Status::OK();
+}
+
+Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
+ const SliceParts& key, const SliceParts& value) {
+ Status s = CheckSlicePartsLength(key, value);
+ if (!s.ok()) {
+ return s;
+ }
+
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeValue));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyValue));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ if (0 == b->timestamp_size_) {
+ PutLengthPrefixedSliceParts(&b->rep_, key);
+ } else {
+ PutLengthPrefixedSlicePartsWithPadding(&b->rep_, key, b->timestamp_size_);
+ }
+ PutLengthPrefixedSliceParts(&b->rep_, value);
+ b->content_flags_.store(
+ b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value) {
+ return WriteBatchInternal::Put(this, GetColumnFamilyID(column_family), key,
+ value);
+}
+
+Status WriteBatchInternal::InsertNoop(WriteBatch* b) {
+ b->rep_.push_back(static_cast<char>(kTypeNoop));
+ return Status::OK();
+}
+
+Status WriteBatchInternal::MarkEndPrepare(WriteBatch* b, const Slice& xid,
+ bool write_after_commit,
+ bool unprepared_batch) {
+ // a manually constructed batch can only contain one prepare section
+ assert(b->rep_[12] == static_cast<char>(kTypeNoop));
+
+ // all savepoints up to this point are cleared
+ if (b->save_points_ != nullptr) {
+ while (!b->save_points_->stack.empty()) {
+ b->save_points_->stack.pop();
+ }
+ }
+
+ // rewrite noop as begin marker
+ b->rep_[12] = static_cast<char>(
+ write_after_commit ? kTypeBeginPrepareXID
+ : (unprepared_batch ? kTypeBeginUnprepareXID
+ : kTypeBeginPersistedPrepareXID));
+ b->rep_.push_back(static_cast<char>(kTypeEndPrepareXID));
+ PutLengthPrefixedSlice(&b->rep_, xid);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_END_PREPARE |
+ ContentFlags::HAS_BEGIN_PREPARE,
+ std::memory_order_relaxed);
+ if (unprepared_batch) {
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_BEGIN_UNPREPARE,
+ std::memory_order_relaxed);
+ }
+ return Status::OK();
+}
+
+Status WriteBatchInternal::MarkCommit(WriteBatch* b, const Slice& xid) {
+ b->rep_.push_back(static_cast<char>(kTypeCommitXID));
+ PutLengthPrefixedSlice(&b->rep_, xid);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_COMMIT,
+ std::memory_order_relaxed);
+ return Status::OK();
+}
+
+Status WriteBatchInternal::MarkRollback(WriteBatch* b, const Slice& xid) {
+ b->rep_.push_back(static_cast<char>(kTypeRollbackXID));
+ PutLengthPrefixedSlice(&b->rep_, xid);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_ROLLBACK,
+ std::memory_order_relaxed);
+ return Status::OK();
+}
+
+Status WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
+ const Slice& key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSlice(&b->rep_, key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_DELETE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Delete(ColumnFamilyHandle* column_family, const Slice& key) {
+ return WriteBatchInternal::Delete(this, GetColumnFamilyID(column_family),
+ key);
+}
+
+Status WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
+ const SliceParts& key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSliceParts(&b->rep_, key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_DELETE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) {
+ return WriteBatchInternal::Delete(this, GetColumnFamilyID(column_family),
+ key);
+}
+
+Status WriteBatchInternal::SingleDelete(WriteBatch* b,
+ uint32_t column_family_id,
+ const Slice& key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeSingleDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilySingleDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSlice(&b->rep_, key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_SINGLE_DELETE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ return WriteBatchInternal::SingleDelete(
+ this, GetColumnFamilyID(column_family), key);
+}
+
+Status WriteBatchInternal::SingleDelete(WriteBatch* b,
+ uint32_t column_family_id,
+ const SliceParts& key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeSingleDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilySingleDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSliceParts(&b->rep_, key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_SINGLE_DELETE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) {
+ return WriteBatchInternal::SingleDelete(
+ this, GetColumnFamilyID(column_family), key);
+}
+
+Status WriteBatchInternal::DeleteRange(WriteBatch* b, uint32_t column_family_id,
+ const Slice& begin_key,
+ const Slice& end_key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeRangeDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyRangeDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSlice(&b->rep_, begin_key);
+ PutLengthPrefixedSlice(&b->rep_, end_key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_DELETE_RANGE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
+ const Slice& begin_key, const Slice& end_key) {
+ return WriteBatchInternal::DeleteRange(this, GetColumnFamilyID(column_family),
+ begin_key, end_key);
+}
+
+Status WriteBatchInternal::DeleteRange(WriteBatch* b, uint32_t column_family_id,
+ const SliceParts& begin_key,
+ const SliceParts& end_key) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeRangeDeletion));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyRangeDeletion));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSliceParts(&b->rep_, begin_key);
+ PutLengthPrefixedSliceParts(&b->rep_, end_key);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_DELETE_RANGE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
+ const SliceParts& begin_key,
+ const SliceParts& end_key) {
+ return WriteBatchInternal::DeleteRange(this, GetColumnFamilyID(column_family),
+ begin_key, end_key);
+}
+
+Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
+ const Slice& key, const Slice& value) {
+ if (key.size() > size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("key is too large");
+ }
+ if (value.size() > size_t{port::kMaxUint32}) {
+ return Status::InvalidArgument("value is too large");
+ }
+
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeMerge));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyMerge));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSlice(&b->rep_, key);
+ PutLengthPrefixedSlice(&b->rep_, value);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_MERGE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) {
+ return WriteBatchInternal::Merge(this, GetColumnFamilyID(column_family), key,
+ value);
+}
+
+Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
+ const SliceParts& key,
+ const SliceParts& value) {
+ Status s = CheckSlicePartsLength(key, value);
+ if (!s.ok()) {
+ return s;
+ }
+
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeMerge));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyMerge));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSliceParts(&b->rep_, key);
+ PutLengthPrefixedSliceParts(&b->rep_, value);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_MERGE,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::Merge(ColumnFamilyHandle* column_family,
+ const SliceParts& key, const SliceParts& value) {
+ return WriteBatchInternal::Merge(this, GetColumnFamilyID(column_family), key,
+ value);
+}
+
+Status WriteBatchInternal::PutBlobIndex(WriteBatch* b,
+ uint32_t column_family_id,
+ const Slice& key, const Slice& value) {
+ LocalSavePoint save(b);
+ WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
+ if (column_family_id == 0) {
+ b->rep_.push_back(static_cast<char>(kTypeBlobIndex));
+ } else {
+ b->rep_.push_back(static_cast<char>(kTypeColumnFamilyBlobIndex));
+ PutVarint32(&b->rep_, column_family_id);
+ }
+ PutLengthPrefixedSlice(&b->rep_, key);
+ PutLengthPrefixedSlice(&b->rep_, value);
+ b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
+ ContentFlags::HAS_BLOB_INDEX,
+ std::memory_order_relaxed);
+ return save.commit();
+}
+
+Status WriteBatch::PutLogData(const Slice& blob) {
+ LocalSavePoint save(this);
+ rep_.push_back(static_cast<char>(kTypeLogData));
+ PutLengthPrefixedSlice(&rep_, blob);
+ return save.commit();
+}
+
+void WriteBatch::SetSavePoint() {
+ if (save_points_ == nullptr) {
+ save_points_.reset(new SavePoints());
+ }
+ // Record length and count of current batch of writes.
+ save_points_->stack.push(SavePoint(
+ GetDataSize(), Count(), content_flags_.load(std::memory_order_relaxed)));
+}
+
+Status WriteBatch::RollbackToSavePoint() {
+ if (save_points_ == nullptr || save_points_->stack.size() == 0) {
+ return Status::NotFound();
+ }
+
+ // Pop the most recent savepoint off the stack
+ SavePoint savepoint = save_points_->stack.top();
+ save_points_->stack.pop();
+
+ assert(savepoint.size <= rep_.size());
+ assert(static_cast<uint32_t>(savepoint.count) <= Count());
+
+ if (savepoint.size == rep_.size()) {
+ // No changes to rollback
+ } else if (savepoint.size == 0) {
+ // Rollback everything
+ Clear();
+ } else {
+ rep_.resize(savepoint.size);
+ WriteBatchInternal::SetCount(this, savepoint.count);
+ content_flags_.store(savepoint.content_flags, std::memory_order_relaxed);
+ }
+
+ return Status::OK();
+}
+
+Status WriteBatch::PopSavePoint() {
+ if (save_points_ == nullptr || save_points_->stack.size() == 0) {
+ return Status::NotFound();
+ }
+
+ // Pop the most recent savepoint off the stack
+ save_points_->stack.pop();
+
+ return Status::OK();
+}
+
+Status WriteBatch::AssignTimestamp(const Slice& ts) {
+ TimestampAssigner ts_assigner(ts);
+ return Iterate(&ts_assigner);
+}
+
+Status WriteBatch::AssignTimestamps(const std::vector<Slice>& ts_list) {
+ TimestampAssigner ts_assigner(ts_list);
+ return Iterate(&ts_assigner);
+}
+
+class MemTableInserter : public WriteBatch::Handler {
+
+ SequenceNumber sequence_;
+ ColumnFamilyMemTables* const cf_mems_;
+ FlushScheduler* const flush_scheduler_;
+ TrimHistoryScheduler* const trim_history_scheduler_;
+ const bool ignore_missing_column_families_;
+ const uint64_t recovering_log_number_;
+ // log number that all Memtables inserted into should reference
+ uint64_t log_number_ref_;
+ DBImpl* db_;
+ const bool concurrent_memtable_writes_;
+ bool post_info_created_;
+
+ bool* has_valid_writes_;
+ // On some (!) platforms just default creating
+ // a map is too expensive in the Write() path as they
+ // cause memory allocations though unused.
+ // Make creation optional but do not incur
+ // std::unique_ptr additional allocation
+ using MemPostInfoMap = std::map<MemTable*, MemTablePostProcessInfo>;
+ using PostMapType = std::aligned_storage<sizeof(MemPostInfoMap)>::type;
+ PostMapType mem_post_info_map_;
+ // current recovered transaction we are rebuilding (recovery)
+ WriteBatch* rebuilding_trx_;
+ SequenceNumber rebuilding_trx_seq_;
+ // Increase seq number once per each write batch. Otherwise increase it once
+ // per key.
+ bool seq_per_batch_;
+ // Whether the memtable write will be done only after the commit
+ bool write_after_commit_;
+ // Whether memtable write can be done before prepare
+ bool write_before_prepare_;
+ // Whether this batch was unprepared or not
+ bool unprepared_batch_;
+ using DupDetector = std::aligned_storage<sizeof(DuplicateDetector)>::type;
+ DupDetector duplicate_detector_;
+ bool dup_dectector_on_;
+
+ bool hint_per_batch_;
+ bool hint_created_;
+ // Hints for this batch
+ using HintMap = std::unordered_map<MemTable*, void*>;
+ using HintMapType = std::aligned_storage<sizeof(HintMap)>::type;
+ HintMapType hint_;
+
+ HintMap& GetHintMap() {
+ assert(hint_per_batch_);
+ if (!hint_created_) {
+ new (&hint_) HintMap();
+ hint_created_ = true;
+ }
+ return *reinterpret_cast<HintMap*>(&hint_);
+ }
+
+ MemPostInfoMap& GetPostMap() {
+ assert(concurrent_memtable_writes_);
+ if(!post_info_created_) {
+ new (&mem_post_info_map_) MemPostInfoMap();
+ post_info_created_ = true;
+ }
+ return *reinterpret_cast<MemPostInfoMap*>(&mem_post_info_map_);
+ }
+
+ bool IsDuplicateKeySeq(uint32_t column_family_id, const Slice& key) {
+ assert(!write_after_commit_);
+ assert(rebuilding_trx_ != nullptr);
+ if (!dup_dectector_on_) {
+ new (&duplicate_detector_) DuplicateDetector(db_);
+ dup_dectector_on_ = true;
+ }
+ return reinterpret_cast<DuplicateDetector*>
+ (&duplicate_detector_)->IsDuplicateKeySeq(column_family_id, key, sequence_);
+ }
+
+ protected:
+ bool WriteBeforePrepare() const override { return write_before_prepare_; }
+ bool WriteAfterCommit() const override { return write_after_commit_; }
+
+ public:
+ // cf_mems should not be shared with concurrent inserters
+ MemTableInserter(SequenceNumber _sequence, ColumnFamilyMemTables* cf_mems,
+ FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families,
+ uint64_t recovering_log_number, DB* db,
+ bool concurrent_memtable_writes,
+ bool* has_valid_writes = nullptr, bool seq_per_batch = false,
+ bool batch_per_txn = true, bool hint_per_batch = false)
+ : sequence_(_sequence),
+ cf_mems_(cf_mems),
+ flush_scheduler_(flush_scheduler),
+ trim_history_scheduler_(trim_history_scheduler),
+ ignore_missing_column_families_(ignore_missing_column_families),
+ recovering_log_number_(recovering_log_number),
+ log_number_ref_(0),
+ db_(static_cast_with_check<DBImpl, DB>(db)),
+ concurrent_memtable_writes_(concurrent_memtable_writes),
+ post_info_created_(false),
+ has_valid_writes_(has_valid_writes),
+ rebuilding_trx_(nullptr),
+ rebuilding_trx_seq_(0),
+ seq_per_batch_(seq_per_batch),
+ // Write after commit currently uses one seq per key (instead of per
+ // batch). So seq_per_batch being false indicates write_after_commit
+ // approach.
+ write_after_commit_(!seq_per_batch),
+ // WriteUnprepared can write WriteBatches per transaction, so
+ // batch_per_txn being false indicates write_before_prepare.
+ write_before_prepare_(!batch_per_txn),
+ unprepared_batch_(false),
+ duplicate_detector_(),
+ dup_dectector_on_(false),
+ hint_per_batch_(hint_per_batch),
+ hint_created_(false) {
+ assert(cf_mems_);
+ }
+
+ ~MemTableInserter() override {
+ if (dup_dectector_on_) {
+ reinterpret_cast<DuplicateDetector*>
+ (&duplicate_detector_)->~DuplicateDetector();
+ }
+ if (post_info_created_) {
+ reinterpret_cast<MemPostInfoMap*>
+ (&mem_post_info_map_)->~MemPostInfoMap();
+ }
+ if (hint_created_) {
+ for (auto iter : GetHintMap()) {
+ delete[] reinterpret_cast<char*>(iter.second);
+ }
+ reinterpret_cast<HintMap*>(&hint_)->~HintMap();
+ }
+ delete rebuilding_trx_;
+ }
+
+ MemTableInserter(const MemTableInserter&) = delete;
+ MemTableInserter& operator=(const MemTableInserter&) = delete;
+
+ // The batch seq is regularly restarted; In normal mode it is set when
+ // MemTableInserter is constructed in the write thread and in recovery mode it
+ // is set when a batch, which is tagged with seq, is read from the WAL.
+ // Within a sequenced batch, which could be a merge of multiple batches, we
+ // have two policies to advance the seq: i) seq_per_key (default) and ii)
+ // seq_per_batch. To implement the latter we need to mark the boundary between
+ // the individual batches. The approach is this: 1) Use the terminating
+ // markers to indicate the boundary (kTypeEndPrepareXID, kTypeCommitXID,
+ // kTypeRollbackXID) 2) Terminate a batch with kTypeNoop in the absence of a
+ // natural boundary marker.
+ void MaybeAdvanceSeq(bool batch_boundry = false) {
+ if (batch_boundry == seq_per_batch_) {
+ sequence_++;
+ }
+ }
+
+ void set_log_number_ref(uint64_t log) { log_number_ref_ = log; }
+
+ SequenceNumber sequence() const { return sequence_; }
+
+ void PostProcess() {
+ assert(concurrent_memtable_writes_);
+ // If post info was not created there is nothing
+ // to process and no need to create on demand
+ if(post_info_created_) {
+ for (auto& pair : GetPostMap()) {
+ pair.first->BatchPostProcess(pair.second);
+ }
+ }
+ }
+
+ bool SeekToColumnFamily(uint32_t column_family_id, Status* s) {
+ // If we are in a concurrent mode, it is the caller's responsibility
+ // to clone the original ColumnFamilyMemTables so that each thread
+ // has its own instance. Otherwise, it must be guaranteed that there
+ // is no concurrent access
+ bool found = cf_mems_->Seek(column_family_id);
+ if (!found) {
+ if (ignore_missing_column_families_) {
+ *s = Status::OK();
+ } else {
+ *s = Status::InvalidArgument(
+ "Invalid column family specified in write batch");
+ }
+ return false;
+ }
+ if (recovering_log_number_ != 0 &&
+ recovering_log_number_ < cf_mems_->GetLogNumber()) {
+ // This is true only in recovery environment (recovering_log_number_ is
+ // always 0 in
+ // non-recovery, regular write code-path)
+ // * If recovering_log_number_ < cf_mems_->GetLogNumber(), this means that
+ // column
+ // family already contains updates from this log. We can't apply updates
+ // twice because of update-in-place or merge workloads -- ignore the
+ // update
+ *s = Status::OK();
+ return false;
+ }
+
+ if (has_valid_writes_ != nullptr) {
+ *has_valid_writes_ = true;
+ }
+
+ if (log_number_ref_ > 0) {
+ cf_mems_->GetMemTable()->RefLogContainingPrepSection(log_number_ref_);
+ }
+
+ return true;
+ }
+
+ Status PutCFImpl(uint32_t column_family_id, const Slice& key,
+ const Slice& value, ValueType value_type) {
+ // optimize for non-recovery mode
+ if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) {
+ WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value);
+ return Status::OK();
+ // else insert the values to the memtable right away
+ }
+
+ Status seek_status;
+ if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) {
+ bool batch_boundry = false;
+ if (rebuilding_trx_ != nullptr) {
+ assert(!write_after_commit_);
+ // The CF is probably flushed and hence no need for insert but we still
+ // need to keep track of the keys for upcoming rollback/commit.
+ WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value);
+ batch_boundry = IsDuplicateKeySeq(column_family_id, key);
+ }
+ MaybeAdvanceSeq(batch_boundry);
+ return seek_status;
+ }
+ Status ret_status;
+
+ MemTable* mem = cf_mems_->GetMemTable();
+ auto* moptions = mem->GetImmutableMemTableOptions();
+ // inplace_update_support is inconsistent with snapshots, and therefore with
+ // any kind of transactions including the ones that use seq_per_batch
+ assert(!seq_per_batch_ || !moptions->inplace_update_support);
+ if (!moptions->inplace_update_support) {
+ bool mem_res =
+ mem->Add(sequence_, value_type, key, value,
+ concurrent_memtable_writes_, get_post_process_info(mem),
+ hint_per_batch_ ? &GetHintMap()[mem] : nullptr);
+ if (UNLIKELY(!mem_res)) {
+ assert(seq_per_batch_);
+ ret_status = Status::TryAgain("key+seq exists");
+ const bool BATCH_BOUNDRY = true;
+ MaybeAdvanceSeq(BATCH_BOUNDRY);
+ }
+ } else if (moptions->inplace_callback == nullptr) {
+ assert(!concurrent_memtable_writes_);
+ mem->Update(sequence_, key, value);
+ } else {
+ assert(!concurrent_memtable_writes_);
+ if (mem->UpdateCallback(sequence_, key, value)) {
+ } else {
+ // key not found in memtable. Do sst get, update, add
+ SnapshotImpl read_from_snapshot;
+ read_from_snapshot.number_ = sequence_;
+ ReadOptions ropts;
+ // it's going to be overwritten for sure, so no point caching data block
+ // containing the old version
+ ropts.fill_cache = false;
+ ropts.snapshot = &read_from_snapshot;
+
+ std::string prev_value;
+ std::string merged_value;
+
+ auto cf_handle = cf_mems_->GetColumnFamilyHandle();
+ Status s = Status::NotSupported();
+ if (db_ != nullptr && recovering_log_number_ == 0) {
+ if (cf_handle == nullptr) {
+ cf_handle = db_->DefaultColumnFamily();
+ }
+ s = db_->Get(ropts, cf_handle, key, &prev_value);
+ }
+
+ char* prev_buffer = const_cast<char*>(prev_value.c_str());
+ uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
+ auto status = moptions->inplace_callback(s.ok() ? prev_buffer : nullptr,
+ s.ok() ? &prev_size : nullptr,
+ value, &merged_value);
+ if (status == UpdateStatus::UPDATED_INPLACE) {
+ // prev_value is updated in-place with final value.
+ bool mem_res __attribute__((__unused__));
+ mem_res = mem->Add(
+ sequence_, value_type, key, Slice(prev_buffer, prev_size));
+ assert(mem_res);
+ RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN);
+ } else if (status == UpdateStatus::UPDATED) {
+ // merged_value contains the final value.
+ bool mem_res __attribute__((__unused__));
+ mem_res =
+ mem->Add(sequence_, value_type, key, Slice(merged_value));
+ assert(mem_res);
+ RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN);
+ }
+ }
+ }
+ // optimize for non-recovery mode
+ if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) {
+ assert(!write_after_commit_);
+ // If the ret_status is TryAgain then let the next try to add the ky to
+ // the rebuilding transaction object.
+ WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value);
+ }
+ // Since all Puts are logged in transaction logs (if enabled), always bump
+ // sequence number. Even if the update eventually fails and does not result
+ // in memtable add/update.
+ MaybeAdvanceSeq();
+ CheckMemtableFull();
+ return ret_status;
+ }
+
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ return PutCFImpl(column_family_id, key, value, kTypeValue);
+ }
+
+ Status DeleteImpl(uint32_t /*column_family_id*/, const Slice& key,
+ const Slice& value, ValueType delete_type) {
+ Status ret_status;
+ MemTable* mem = cf_mems_->GetMemTable();
+ bool mem_res =
+ mem->Add(sequence_, delete_type, key, value,
+ concurrent_memtable_writes_, get_post_process_info(mem),
+ hint_per_batch_ ? &GetHintMap()[mem] : nullptr);
+ if (UNLIKELY(!mem_res)) {
+ assert(seq_per_batch_);
+ ret_status = Status::TryAgain("key+seq exists");
+ const bool BATCH_BOUNDRY = true;
+ MaybeAdvanceSeq(BATCH_BOUNDRY);
+ }
+ MaybeAdvanceSeq();
+ CheckMemtableFull();
+ return ret_status;
+ }
+
+ Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
+ // optimize for non-recovery mode
+ if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) {
+ WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key);
+ return Status::OK();
+ // else insert the values to the memtable right away
+ }
+
+ Status seek_status;
+ if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) {
+ bool batch_boundry = false;
+ if (rebuilding_trx_ != nullptr) {
+ assert(!write_after_commit_);
+ // The CF is probably flushed and hence no need for insert but we still
+ // need to keep track of the keys for upcoming rollback/commit.
+ WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key);
+ batch_boundry = IsDuplicateKeySeq(column_family_id, key);
+ }
+ MaybeAdvanceSeq(batch_boundry);
+ return seek_status;
+ }
+
+ auto ret_status = DeleteImpl(column_family_id, key, Slice(), kTypeDeletion);
+ // optimize for non-recovery mode
+ if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) {
+ assert(!write_after_commit_);
+ // If the ret_status is TryAgain then let the next try to add the ky to
+ // the rebuilding transaction object.
+ WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key);
+ }
+ return ret_status;
+ }
+
+ Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) override {
+ // optimize for non-recovery mode
+ if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) {
+ WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, key);
+ return Status::OK();
+ // else insert the values to the memtable right away
+ }
+
+ Status seek_status;
+ if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) {
+ bool batch_boundry = false;
+ if (rebuilding_trx_ != nullptr) {
+ assert(!write_after_commit_);
+ // The CF is probably flushed and hence no need for insert but we still
+ // need to keep track of the keys for upcoming rollback/commit.
+ WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id,
+ key);
+ batch_boundry = IsDuplicateKeySeq(column_family_id, key);
+ }
+ MaybeAdvanceSeq(batch_boundry);
+ return seek_status;
+ }
+
+ auto ret_status =
+ DeleteImpl(column_family_id, key, Slice(), kTypeSingleDeletion);
+ // optimize for non-recovery mode
+ if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) {
+ assert(!write_after_commit_);
+ // If the ret_status is TryAgain then let the next try to add the ky to
+ // the rebuilding transaction object.
+ WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, key);
+ }
+ return ret_status;
+ }
+
+ Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key,
+ const Slice& end_key) override {
+ // optimize for non-recovery mode
+ if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) {
+ WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id,
+ begin_key, end_key);
+ return Status::OK();
+ // else insert the values to the memtable right away
+ }
+
+ Status seek_status;
+ if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) {
+ bool batch_boundry = false;
+ if (rebuilding_trx_ != nullptr) {
+ assert(!write_after_commit_);
+ // The CF is probably flushed and hence no need for insert but we still
+ // need to keep track of the keys for upcoming rollback/commit.
+ WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id,
+ begin_key, end_key);
+ // TODO(myabandeh): when transactional DeleteRange support is added,
+ // check if end_key must also be added.
+ batch_boundry = IsDuplicateKeySeq(column_family_id, begin_key);
+ }
+ MaybeAdvanceSeq(batch_boundry);
+ return seek_status;
+ }
+ if (db_ != nullptr) {
+ auto cf_handle = cf_mems_->GetColumnFamilyHandle();
+ if (cf_handle == nullptr) {
+ cf_handle = db_->DefaultColumnFamily();
+ }
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cf_handle)->cfd();
+ if (!cfd->is_delete_range_supported()) {
+ return Status::NotSupported(
+ std::string("DeleteRange not supported for table type ") +
+ cfd->ioptions()->table_factory->Name() + " in CF " +
+ cfd->GetName());
+ }
+ }
+
+ auto ret_status =
+ DeleteImpl(column_family_id, begin_key, end_key, kTypeRangeDeletion);
+ // optimize for non-recovery mode
+ if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) {
+ assert(!write_after_commit_);
+ // If the ret_status is TryAgain then let the next try to add the ky to
+ // the rebuilding transaction object.
+ WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id,
+ begin_key, end_key);
+ }
+ return ret_status;
+ }
+
+ Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ // optimize for non-recovery mode
+ if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) {
+ WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, value);
+ return Status::OK();
+ // else insert the values to the memtable right away
+ }
+
+ Status seek_status;
+ if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) {
+ bool batch_boundry = false;
+ if (rebuilding_trx_ != nullptr) {
+ assert(!write_after_commit_);
+ // The CF is probably flushed and hence no need for insert but we still
+ // need to keep track of the keys for upcoming rollback/commit.
+ WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key,
+ value);
+ batch_boundry = IsDuplicateKeySeq(column_family_id, key);
+ }
+ MaybeAdvanceSeq(batch_boundry);
+ return seek_status;
+ }
+
+ Status ret_status;
+ MemTable* mem = cf_mems_->GetMemTable();
+ auto* moptions = mem->GetImmutableMemTableOptions();
+ bool perform_merge = false;
+ assert(!concurrent_memtable_writes_ ||
+ moptions->max_successive_merges == 0);
+
+ // If we pass DB through and options.max_successive_merges is hit
+ // during recovery, Get() will be issued which will try to acquire
+ // DB mutex and cause deadlock, as DB mutex is already held.
+ // So we disable merge in recovery
+ if (moptions->max_successive_merges > 0 && db_ != nullptr &&
+ recovering_log_number_ == 0) {
+ assert(!concurrent_memtable_writes_);
+ LookupKey lkey(key, sequence_);
+
+ // Count the number of successive merges at the head
+ // of the key in the memtable
+ size_t num_merges = mem->CountSuccessiveMergeEntries(lkey);
+
+ if (num_merges >= moptions->max_successive_merges) {
+ perform_merge = true;
+ }
+ }
+
+ if (perform_merge) {
+ // 1) Get the existing value
+ std::string get_value;
+
+ // Pass in the sequence number so that we also include previous merge
+ // operations in the same batch.
+ SnapshotImpl read_from_snapshot;
+ read_from_snapshot.number_ = sequence_;
+ ReadOptions read_options;
+ read_options.snapshot = &read_from_snapshot;
+
+ auto cf_handle = cf_mems_->GetColumnFamilyHandle();
+ if (cf_handle == nullptr) {
+ cf_handle = db_->DefaultColumnFamily();
+ }
+ db_->Get(read_options, cf_handle, key, &get_value);
+ Slice get_value_slice = Slice(get_value);
+
+ // 2) Apply this merge
+ auto merge_operator = moptions->merge_operator;
+ assert(merge_operator);
+
+ std::string new_value;
+
+ Status merge_status = MergeHelper::TimedFullMerge(
+ merge_operator, key, &get_value_slice, {value}, &new_value,
+ moptions->info_log, moptions->statistics, Env::Default());
+
+ if (!merge_status.ok()) {
+ // Failed to merge!
+ // Store the delta in memtable
+ perform_merge = false;
+ } else {
+ // 3) Add value to memtable
+ assert(!concurrent_memtable_writes_);
+ bool mem_res = mem->Add(sequence_, kTypeValue, key, new_value);
+ if (UNLIKELY(!mem_res)) {
+ assert(seq_per_batch_);
+ ret_status = Status::TryAgain("key+seq exists");
+ const bool BATCH_BOUNDRY = true;
+ MaybeAdvanceSeq(BATCH_BOUNDRY);
+ }
+ }
+ }
+
+ if (!perform_merge) {
+ // Add merge operator to memtable
+ bool mem_res =
+ mem->Add(sequence_, kTypeMerge, key, value,
+ concurrent_memtable_writes_, get_post_process_info(mem));
+ if (UNLIKELY(!mem_res)) {
+ assert(seq_per_batch_);
+ ret_status = Status::TryAgain("key+seq exists");
+ const bool BATCH_BOUNDRY = true;
+ MaybeAdvanceSeq(BATCH_BOUNDRY);
+ }
+ }
+
+ // optimize for non-recovery mode
+ if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) {
+ assert(!write_after_commit_);
+ // If the ret_status is TryAgain then let the next try to add the ky to
+ // the rebuilding transaction object.
+ WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, value);
+ }
+ MaybeAdvanceSeq();
+ CheckMemtableFull();
+ return ret_status;
+ }
+
+ Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ // Same as PutCF except for value type.
+ return PutCFImpl(column_family_id, key, value, kTypeBlobIndex);
+ }
+
+ void CheckMemtableFull() {
+ if (flush_scheduler_ != nullptr) {
+ auto* cfd = cf_mems_->current();
+ assert(cfd != nullptr);
+ if (cfd->mem()->ShouldScheduleFlush() &&
+ cfd->mem()->MarkFlushScheduled()) {
+ // MarkFlushScheduled only returns true if we are the one that
+ // should take action, so no need to dedup further
+ flush_scheduler_->ScheduleWork(cfd);
+ }
+ }
+ // check if memtable_list size exceeds max_write_buffer_size_to_maintain
+ if (trim_history_scheduler_ != nullptr) {
+ auto* cfd = cf_mems_->current();
+
+ assert(cfd);
+ assert(cfd->ioptions());
+
+ const size_t size_to_maintain = static_cast<size_t>(
+ cfd->ioptions()->max_write_buffer_size_to_maintain);
+
+ if (size_to_maintain > 0) {
+ MemTableList* const imm = cfd->imm();
+ assert(imm);
+
+ if (imm->HasHistory()) {
+ const MemTable* const mem = cfd->mem();
+ assert(mem);
+
+ if (mem->ApproximateMemoryUsageFast() +
+ imm->ApproximateMemoryUsageExcludingLast() >=
+ size_to_maintain &&
+ imm->MarkTrimHistoryNeeded()) {
+ trim_history_scheduler_->ScheduleWork(cfd);
+ }
+ }
+ }
+ }
+ }
+
+ // The write batch handler calls MarkBeginPrepare with unprepare set to true
+ // if it encounters the kTypeBeginUnprepareXID marker.
+ Status MarkBeginPrepare(bool unprepare) override {
+ assert(rebuilding_trx_ == nullptr);
+ assert(db_);
+
+ if (recovering_log_number_ != 0) {
+ // during recovery we rebuild a hollow transaction
+ // from all encountered prepare sections of the wal
+ if (db_->allow_2pc() == false) {
+ return Status::NotSupported(
+ "WAL contains prepared transactions. Open with "
+ "TransactionDB::Open().");
+ }
+
+ // we are now iterating through a prepared section
+ rebuilding_trx_ = new WriteBatch();
+ rebuilding_trx_seq_ = sequence_;
+ // Verify that we have matching MarkBeginPrepare/MarkEndPrepare markers.
+ // unprepared_batch_ should be false because it is false by default, and
+ // gets reset to false in MarkEndPrepare.
+ assert(!unprepared_batch_);
+ unprepared_batch_ = unprepare;
+
+ if (has_valid_writes_ != nullptr) {
+ *has_valid_writes_ = true;
+ }
+ }
+
+ return Status::OK();
+ }
+
+ Status MarkEndPrepare(const Slice& name) override {
+ assert(db_);
+ assert((rebuilding_trx_ != nullptr) == (recovering_log_number_ != 0));
+
+ if (recovering_log_number_ != 0) {
+ assert(db_->allow_2pc());
+ size_t batch_cnt =
+ write_after_commit_
+ ? 0 // 0 will disable further checks
+ : static_cast<size_t>(sequence_ - rebuilding_trx_seq_ + 1);
+ db_->InsertRecoveredTransaction(recovering_log_number_, name.ToString(),
+ rebuilding_trx_, rebuilding_trx_seq_,
+ batch_cnt, unprepared_batch_);
+ unprepared_batch_ = false;
+ rebuilding_trx_ = nullptr;
+ } else {
+ assert(rebuilding_trx_ == nullptr);
+ }
+ const bool batch_boundry = true;
+ MaybeAdvanceSeq(batch_boundry);
+
+ return Status::OK();
+ }
+
+ Status MarkNoop(bool empty_batch) override {
+ // A hack in pessimistic transaction could result into a noop at the start
+ // of the write batch, that should be ignored.
+ if (!empty_batch) {
+ // In the absence of Prepare markers, a kTypeNoop tag indicates the end of
+ // a batch. This happens when write batch commits skipping the prepare
+ // phase.
+ const bool batch_boundry = true;
+ MaybeAdvanceSeq(batch_boundry);
+ }
+ return Status::OK();
+ }
+
+ Status MarkCommit(const Slice& name) override {
+ assert(db_);
+
+ Status s;
+
+ if (recovering_log_number_ != 0) {
+ // in recovery when we encounter a commit marker
+ // we lookup this transaction in our set of rebuilt transactions
+ // and commit.
+ auto trx = db_->GetRecoveredTransaction(name.ToString());
+
+ // the log containing the prepared section may have
+ // been released in the last incarnation because the
+ // data was flushed to L0
+ if (trx != nullptr) {
+ // at this point individual CF lognumbers will prevent
+ // duplicate re-insertion of values.
+ assert(log_number_ref_ == 0);
+ if (write_after_commit_) {
+ // write_after_commit_ can only have one batch in trx.
+ assert(trx->batches_.size() == 1);
+ const auto& batch_info = trx->batches_.begin()->second;
+ // all inserts must reference this trx log number
+ log_number_ref_ = batch_info.log_number_;
+ s = batch_info.batch_->Iterate(this);
+ log_number_ref_ = 0;
+ }
+ // else the values are already inserted before the commit
+
+ if (s.ok()) {
+ db_->DeleteRecoveredTransaction(name.ToString());
+ }
+ if (has_valid_writes_ != nullptr) {
+ *has_valid_writes_ = true;
+ }
+ }
+ } else {
+ // When writes are not delayed until commit, there is no disconnect
+ // between a memtable write and the WAL that supports it. So the commit
+ // need not reference any log as the only log to which it depends.
+ assert(!write_after_commit_ || log_number_ref_ > 0);
+ }
+ const bool batch_boundry = true;
+ MaybeAdvanceSeq(batch_boundry);
+
+ return s;
+ }
+
+ Status MarkRollback(const Slice& name) override {
+ assert(db_);
+
+ if (recovering_log_number_ != 0) {
+ auto trx = db_->GetRecoveredTransaction(name.ToString());
+
+ // the log containing the transactions prep section
+ // may have been released in the previous incarnation
+ // because we knew it had been rolled back
+ if (trx != nullptr) {
+ db_->DeleteRecoveredTransaction(name.ToString());
+ }
+ } else {
+ // in non recovery we simply ignore this tag
+ }
+
+ const bool batch_boundry = true;
+ MaybeAdvanceSeq(batch_boundry);
+
+ return Status::OK();
+ }
+
+ private:
+ MemTablePostProcessInfo* get_post_process_info(MemTable* mem) {
+ if (!concurrent_memtable_writes_) {
+ // No need to batch counters locally if we don't use concurrent mode.
+ return nullptr;
+ }
+ return &GetPostMap()[mem];
+ }
+};
+
+// This function can only be called in these conditions:
+// 1) During Recovery()
+// 2) During Write(), in a single-threaded write thread
+// 3) During Write(), in a concurrent context where memtables has been cloned
+// The reason is that it calls memtables->Seek(), which has a stateful cache
+Status WriteBatchInternal::InsertInto(
+ WriteThread::WriteGroup& write_group, SequenceNumber sequence,
+ ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families, uint64_t recovery_log_number, DB* db,
+ bool concurrent_memtable_writes, bool seq_per_batch, bool batch_per_txn) {
+ MemTableInserter inserter(
+ sequence, memtables, flush_scheduler, trim_history_scheduler,
+ ignore_missing_column_families, recovery_log_number, db,
+ concurrent_memtable_writes, nullptr /*has_valid_writes*/, seq_per_batch,
+ batch_per_txn);
+ for (auto w : write_group) {
+ if (w->CallbackFailed()) {
+ continue;
+ }
+ w->sequence = inserter.sequence();
+ if (!w->ShouldWriteToMemtable()) {
+ // In seq_per_batch_ mode this advances the seq by one.
+ inserter.MaybeAdvanceSeq(true);
+ continue;
+ }
+ SetSequence(w->batch, inserter.sequence());
+ inserter.set_log_number_ref(w->log_ref);
+ w->status = w->batch->Iterate(&inserter);
+ if (!w->status.ok()) {
+ return w->status;
+ }
+ assert(!seq_per_batch || w->batch_cnt != 0);
+ assert(!seq_per_batch || inserter.sequence() - w->sequence == w->batch_cnt);
+ }
+ return Status::OK();
+}
+
+Status WriteBatchInternal::InsertInto(
+ WriteThread::Writer* writer, SequenceNumber sequence,
+ ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families, uint64_t log_number, DB* db,
+ bool concurrent_memtable_writes, bool seq_per_batch, size_t batch_cnt,
+ bool batch_per_txn, bool hint_per_batch) {
+#ifdef NDEBUG
+ (void)batch_cnt;
+#endif
+ assert(writer->ShouldWriteToMemtable());
+ MemTableInserter inserter(
+ sequence, memtables, flush_scheduler, trim_history_scheduler,
+ ignore_missing_column_families, log_number, db,
+ concurrent_memtable_writes, nullptr /*has_valid_writes*/, seq_per_batch,
+ batch_per_txn, hint_per_batch);
+ SetSequence(writer->batch, sequence);
+ inserter.set_log_number_ref(writer->log_ref);
+ Status s = writer->batch->Iterate(&inserter);
+ assert(!seq_per_batch || batch_cnt != 0);
+ assert(!seq_per_batch || inserter.sequence() - sequence == batch_cnt);
+ if (concurrent_memtable_writes) {
+ inserter.PostProcess();
+ }
+ return s;
+}
+
+Status WriteBatchInternal::InsertInto(
+ const WriteBatch* batch, ColumnFamilyMemTables* memtables,
+ FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families, uint64_t log_number, DB* db,
+ bool concurrent_memtable_writes, SequenceNumber* next_seq,
+ bool* has_valid_writes, bool seq_per_batch, bool batch_per_txn) {
+ MemTableInserter inserter(Sequence(batch), memtables, flush_scheduler,
+ trim_history_scheduler,
+ ignore_missing_column_families, log_number, db,
+ concurrent_memtable_writes, has_valid_writes,
+ seq_per_batch, batch_per_txn);
+ Status s = batch->Iterate(&inserter);
+ if (next_seq != nullptr) {
+ *next_seq = inserter.sequence();
+ }
+ if (concurrent_memtable_writes) {
+ inserter.PostProcess();
+ }
+ return s;
+}
+
+Status WriteBatchInternal::SetContents(WriteBatch* b, const Slice& contents) {
+ assert(contents.size() >= WriteBatchInternal::kHeader);
+ b->rep_.assign(contents.data(), contents.size());
+ b->content_flags_.store(ContentFlags::DEFERRED, std::memory_order_relaxed);
+ return Status::OK();
+}
+
+Status WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src,
+ const bool wal_only) {
+ size_t src_len;
+ int src_count;
+ uint32_t src_flags;
+
+ const SavePoint& batch_end = src->GetWalTerminationPoint();
+
+ if (wal_only && !batch_end.is_cleared()) {
+ src_len = batch_end.size - WriteBatchInternal::kHeader;
+ src_count = batch_end.count;
+ src_flags = batch_end.content_flags;
+ } else {
+ src_len = src->rep_.size() - WriteBatchInternal::kHeader;
+ src_count = Count(src);
+ src_flags = src->content_flags_.load(std::memory_order_relaxed);
+ }
+
+ SetCount(dst, Count(dst) + src_count);
+ assert(src->rep_.size() >= WriteBatchInternal::kHeader);
+ dst->rep_.append(src->rep_.data() + WriteBatchInternal::kHeader, src_len);
+ dst->content_flags_.store(
+ dst->content_flags_.load(std::memory_order_relaxed) | src_flags,
+ std::memory_order_relaxed);
+ return Status::OK();
+}
+
+size_t WriteBatchInternal::AppendedByteSize(size_t leftByteSize,
+ size_t rightByteSize) {
+ if (leftByteSize == 0 || rightByteSize == 0) {
+ return leftByteSize + rightByteSize;
+ } else {
+ return leftByteSize + rightByteSize - WriteBatchInternal::kHeader;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_batch_base.cc b/storage/rocksdb/rocksdb/db/write_batch_base.cc
new file mode 100644
index 00000000..e4c0e74b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_batch_base.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_batch_base.h"
+
+#include <string>
+
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Simple implementation of SlicePart variants of Put(). Child classes
+// can override these method with more performant solutions if they choose.
+Status WriteBatchBase::Put(ColumnFamilyHandle* column_family,
+ const SliceParts& key, const SliceParts& value) {
+ std::string key_buf, value_buf;
+ Slice key_slice(key, &key_buf);
+ Slice value_slice(value, &value_buf);
+
+ return Put(column_family, key_slice, value_slice);
+}
+
+Status WriteBatchBase::Put(const SliceParts& key, const SliceParts& value) {
+ std::string key_buf, value_buf;
+ Slice key_slice(key, &key_buf);
+ Slice value_slice(value, &value_buf);
+
+ return Put(key_slice, value_slice);
+}
+
+Status WriteBatchBase::Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) {
+ std::string key_buf;
+ Slice key_slice(key, &key_buf);
+ return Delete(column_family, key_slice);
+}
+
+Status WriteBatchBase::Delete(const SliceParts& key) {
+ std::string key_buf;
+ Slice key_slice(key, &key_buf);
+ return Delete(key_slice);
+}
+
+Status WriteBatchBase::SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) {
+ std::string key_buf;
+ Slice key_slice(key, &key_buf);
+ return SingleDelete(column_family, key_slice);
+}
+
+Status WriteBatchBase::SingleDelete(const SliceParts& key) {
+ std::string key_buf;
+ Slice key_slice(key, &key_buf);
+ return SingleDelete(key_slice);
+}
+
+Status WriteBatchBase::DeleteRange(ColumnFamilyHandle* column_family,
+ const SliceParts& begin_key,
+ const SliceParts& end_key) {
+ std::string begin_key_buf, end_key_buf;
+ Slice begin_key_slice(begin_key, &begin_key_buf);
+ Slice end_key_slice(end_key, &end_key_buf);
+ return DeleteRange(column_family, begin_key_slice, end_key_slice);
+}
+
+Status WriteBatchBase::DeleteRange(const SliceParts& begin_key,
+ const SliceParts& end_key) {
+ std::string begin_key_buf, end_key_buf;
+ Slice begin_key_slice(begin_key, &begin_key_buf);
+ Slice end_key_slice(end_key, &end_key_buf);
+ return DeleteRange(begin_key_slice, end_key_slice);
+}
+
+Status WriteBatchBase::Merge(ColumnFamilyHandle* column_family,
+ const SliceParts& key, const SliceParts& value) {
+ std::string key_buf, value_buf;
+ Slice key_slice(key, &key_buf);
+ Slice value_slice(value, &value_buf);
+
+ return Merge(column_family, key_slice, value_slice);
+}
+
+Status WriteBatchBase::Merge(const SliceParts& key, const SliceParts& value) {
+ std::string key_buf, value_buf;
+ Slice key_slice(key, &key_buf);
+ Slice value_slice(value, &value_buf);
+
+ return Merge(key_slice, value_slice);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_batch_internal.h b/storage/rocksdb/rocksdb/db/write_batch_internal.h
new file mode 100644
index 00000000..30c48996
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_batch_internal.h
@@ -0,0 +1,250 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <vector>
+#include "db/flush_scheduler.h"
+#include "db/trim_history_scheduler.h"
+#include "db/write_thread.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/types.h"
+#include "rocksdb/write_batch.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MemTable;
+class FlushScheduler;
+class ColumnFamilyData;
+
+class ColumnFamilyMemTables {
+ public:
+ virtual ~ColumnFamilyMemTables() {}
+ virtual bool Seek(uint32_t column_family_id) = 0;
+ // returns true if the update to memtable should be ignored
+ // (useful when recovering from log whose updates have already
+ // been processed)
+ virtual uint64_t GetLogNumber() const = 0;
+ virtual MemTable* GetMemTable() const = 0;
+ virtual ColumnFamilyHandle* GetColumnFamilyHandle() = 0;
+ virtual ColumnFamilyData* current() { return nullptr; }
+};
+
+class ColumnFamilyMemTablesDefault : public ColumnFamilyMemTables {
+ public:
+ explicit ColumnFamilyMemTablesDefault(MemTable* mem)
+ : ok_(false), mem_(mem) {}
+
+ bool Seek(uint32_t column_family_id) override {
+ ok_ = (column_family_id == 0);
+ return ok_;
+ }
+
+ uint64_t GetLogNumber() const override { return 0; }
+
+ MemTable* GetMemTable() const override {
+ assert(ok_);
+ return mem_;
+ }
+
+ ColumnFamilyHandle* GetColumnFamilyHandle() override { return nullptr; }
+
+ private:
+ bool ok_;
+ MemTable* mem_;
+};
+
+// WriteBatchInternal provides static methods for manipulating a
+// WriteBatch that we don't want in the public WriteBatch interface.
+class WriteBatchInternal {
+ public:
+
+ // WriteBatch header has an 8-byte sequence number followed by a 4-byte count.
+ static const size_t kHeader = 12;
+
+ // WriteBatch methods with column_family_id instead of ColumnFamilyHandle*
+ static Status Put(WriteBatch* batch, uint32_t column_family_id,
+ const Slice& key, const Slice& value);
+
+ static Status Put(WriteBatch* batch, uint32_t column_family_id,
+ const SliceParts& key, const SliceParts& value);
+
+ static Status Delete(WriteBatch* batch, uint32_t column_family_id,
+ const SliceParts& key);
+
+ static Status Delete(WriteBatch* batch, uint32_t column_family_id,
+ const Slice& key);
+
+ static Status SingleDelete(WriteBatch* batch, uint32_t column_family_id,
+ const SliceParts& key);
+
+ static Status SingleDelete(WriteBatch* batch, uint32_t column_family_id,
+ const Slice& key);
+
+ static Status DeleteRange(WriteBatch* b, uint32_t column_family_id,
+ const Slice& begin_key, const Slice& end_key);
+
+ static Status DeleteRange(WriteBatch* b, uint32_t column_family_id,
+ const SliceParts& begin_key,
+ const SliceParts& end_key);
+
+ static Status Merge(WriteBatch* batch, uint32_t column_family_id,
+ const Slice& key, const Slice& value);
+
+ static Status Merge(WriteBatch* batch, uint32_t column_family_id,
+ const SliceParts& key, const SliceParts& value);
+
+ static Status PutBlobIndex(WriteBatch* batch, uint32_t column_family_id,
+ const Slice& key, const Slice& value);
+
+ static Status MarkEndPrepare(WriteBatch* batch, const Slice& xid,
+ const bool write_after_commit = true,
+ const bool unprepared_batch = false);
+
+ static Status MarkRollback(WriteBatch* batch, const Slice& xid);
+
+ static Status MarkCommit(WriteBatch* batch, const Slice& xid);
+
+ static Status InsertNoop(WriteBatch* batch);
+
+ // Return the number of entries in the batch.
+ static uint32_t Count(const WriteBatch* batch);
+
+ // Set the count for the number of entries in the batch.
+ static void SetCount(WriteBatch* batch, uint32_t n);
+
+ // Return the sequence number for the start of this batch.
+ static SequenceNumber Sequence(const WriteBatch* batch);
+
+ // Store the specified number as the sequence number for the start of
+ // this batch.
+ static void SetSequence(WriteBatch* batch, SequenceNumber seq);
+
+ // Returns the offset of the first entry in the batch.
+ // This offset is only valid if the batch is not empty.
+ static size_t GetFirstOffset(WriteBatch* batch);
+
+ static Slice Contents(const WriteBatch* batch) {
+ return Slice(batch->rep_);
+ }
+
+ static size_t ByteSize(const WriteBatch* batch) {
+ return batch->rep_.size();
+ }
+
+ static Status SetContents(WriteBatch* batch, const Slice& contents);
+
+ static Status CheckSlicePartsLength(const SliceParts& key,
+ const SliceParts& value);
+
+ // Inserts batches[i] into memtable, for i in 0..num_batches-1 inclusive.
+ //
+ // If ignore_missing_column_families == true. WriteBatch
+ // referencing non-existing column family will be ignored.
+ // If ignore_missing_column_families == false, processing of the
+ // batches will be stopped if a reference is found to a non-existing
+ // column family and InvalidArgument() will be returned. The writes
+ // in batches may be only partially applied at that point.
+ //
+ // If log_number is non-zero, the memtable will be updated only if
+ // memtables->GetLogNumber() >= log_number.
+ //
+ // If flush_scheduler is non-null, it will be invoked if the memtable
+ // should be flushed.
+ //
+ // Under concurrent use, the caller is responsible for making sure that
+ // the memtables object itself is thread-local.
+ static Status InsertInto(
+ WriteThread::WriteGroup& write_group, SequenceNumber sequence,
+ ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families = false, uint64_t log_number = 0,
+ DB* db = nullptr, bool concurrent_memtable_writes = false,
+ bool seq_per_batch = false, bool batch_per_txn = true);
+
+ // Convenience form of InsertInto when you have only one batch
+ // next_seq returns the seq after last sequence number used in MemTable insert
+ static Status InsertInto(
+ const WriteBatch* batch, ColumnFamilyMemTables* memtables,
+ FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families = false, uint64_t log_number = 0,
+ DB* db = nullptr, bool concurrent_memtable_writes = false,
+ SequenceNumber* next_seq = nullptr, bool* has_valid_writes = nullptr,
+ bool seq_per_batch = false, bool batch_per_txn = true);
+
+ static Status InsertInto(WriteThread::Writer* writer, SequenceNumber sequence,
+ ColumnFamilyMemTables* memtables,
+ FlushScheduler* flush_scheduler,
+ TrimHistoryScheduler* trim_history_scheduler,
+ bool ignore_missing_column_families = false,
+ uint64_t log_number = 0, DB* db = nullptr,
+ bool concurrent_memtable_writes = false,
+ bool seq_per_batch = false, size_t batch_cnt = 0,
+ bool batch_per_txn = true,
+ bool hint_per_batch = false);
+
+ static Status Append(WriteBatch* dst, const WriteBatch* src,
+ const bool WAL_only = false);
+
+ // Returns the byte size of appending a WriteBatch with ByteSize
+ // leftByteSize and a WriteBatch with ByteSize rightByteSize
+ static size_t AppendedByteSize(size_t leftByteSize, size_t rightByteSize);
+
+ // Iterate over [begin, end) range of a write batch
+ static Status Iterate(const WriteBatch* wb, WriteBatch::Handler* handler,
+ size_t begin, size_t end);
+
+ // This write batch includes the latest state that should be persisted. Such
+ // state meant to be used only during recovery.
+ static void SetAsLastestPersistentState(WriteBatch* b);
+ static bool IsLatestPersistentState(const WriteBatch* b);
+};
+
+// LocalSavePoint is similar to a scope guard
+class LocalSavePoint {
+ public:
+ explicit LocalSavePoint(WriteBatch* batch)
+ : batch_(batch),
+ savepoint_(batch->GetDataSize(), batch->Count(),
+ batch->content_flags_.load(std::memory_order_relaxed))
+#ifndef NDEBUG
+ ,
+ committed_(false)
+#endif
+ {
+ }
+
+#ifndef NDEBUG
+ ~LocalSavePoint() { assert(committed_); }
+#endif
+ Status commit() {
+#ifndef NDEBUG
+ committed_ = true;
+#endif
+ if (batch_->max_bytes_ && batch_->rep_.size() > batch_->max_bytes_) {
+ batch_->rep_.resize(savepoint_.size);
+ WriteBatchInternal::SetCount(batch_, savepoint_.count);
+ batch_->content_flags_.store(savepoint_.content_flags,
+ std::memory_order_relaxed);
+ return Status::MemoryLimit();
+ }
+ return Status::OK();
+ }
+
+ private:
+ WriteBatch* batch_;
+ SavePoint savepoint_;
+#ifndef NDEBUG
+ bool committed_;
+#endif
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_batch_test.cc b/storage/rocksdb/rocksdb/db/write_batch_test.cc
new file mode 100644
index 00000000..84f9a45e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_batch_test.cc
@@ -0,0 +1,888 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+
+#include <memory>
+#include "db/column_family.h"
+#include "db/memtable.h"
+#include "db/write_batch_internal.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 "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static std::string PrintContents(WriteBatch* b) {
+ InternalKeyComparator cmp(BytewiseComparator());
+ auto factory = std::make_shared<SkipListFactory>();
+ Options options;
+ options.memtable_factory = factory;
+ ImmutableCFOptions 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<InternalIterator> 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 */);
+ iter_guard.reset(iter);
+ }
+ if (iter == nullptr) {
+ continue;
+ }
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ ParsedInternalKey ikey;
+ ikey.clear();
+ EXPECT_TRUE(ParseInternalKey(iter->key(), &ikey));
+ 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(NumberToString(ikey.sequence));
+ }
+ }
+ 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 (!s.ok()) {
+ state.append(s.ToString());
+ } else if (count != WriteBatchInternal::Count(b)) {
+ state.append("CountMismatch()");
+ }
+ 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;
+ batch.Put(Slice("foo"), Slice("bar"));
+ batch.Delete(Slice("box"));
+ batch.DeleteRange(Slice("bar"), Slice("foo"));
+ 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;
+ batch.Put(Slice("foo"), Slice("bar"));
+ batch.Delete(Slice("box"));
+ WriteBatchInternal::SetSequence(&batch, 200);
+ Slice contents = WriteBatchInternal::Contents(&batch);
+ 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);
+ WriteBatchInternal::Append(&b1, &b2);
+ ASSERT_EQ("",
+ PrintContents(&b1));
+ ASSERT_EQ(0u, b1.Count());
+ b2.Put("a", "va");
+ WriteBatchInternal::Append(&b1, &b2);
+ ASSERT_EQ("Put(a, va)@200",
+ PrintContents(&b1));
+ ASSERT_EQ(1u, b1.Count());
+ b2.Clear();
+ b2.Put("b", "vb");
+ WriteBatchInternal::Append(&b1, &b2);
+ ASSERT_EQ("Put(a, va)@200"
+ "Put(b, vb)@201",
+ PrintContents(&b1));
+ ASSERT_EQ(2u, b1.Count());
+ b2.Delete("foo");
+ 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();
+ b2.Put("c", "cc");
+ b2.Put("d", "dd");
+ b2.MarkWalTerminationPoint();
+ b2.Put("e", "ee");
+ 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());
+ batch.Put("a", "va");
+ ASSERT_EQ("Put(a, va)@100", PrintContents(&batch));
+ ASSERT_EQ(1u, batch.Count());
+ 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(" + ToString(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(" + ToString(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(" + ToString(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(" + ToString(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(" + ToString(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 MarkRollback(const Slice& xid) override {
+ seen += "MarkRollback(" + xid.ToString() + ")";
+ return Status::OK();
+ }
+ };
+}
+
+TEST_F(WriteBatchTest, PutNotImplemented) {
+ WriteBatch batch;
+ 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;
+ 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;
+ 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;
+ 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, Blob) {
+ WriteBatch batch;
+ batch.Put(Slice("k1"), Slice("v1"));
+ batch.Put(Slice("k2"), Slice("v2"));
+ batch.Put(Slice("k3"), Slice("v3"));
+ batch.PutLogData(Slice("blob1"));
+ batch.Delete(Slice("k2"));
+ batch.SingleDelete(Slice("k3"));
+ batch.PutLogData(Slice("blob2"));
+ 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;
+ 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;
+ WriteBatchInternal::InsertNoop(&batch);
+ batch.Put(Slice("k1"), Slice("v1"));
+ batch.Put(Slice("k2"), Slice("v2"));
+ batch.SetSavePoint();
+ WriteBatchInternal::MarkEndPrepare(&batch, Slice("xid1"));
+ Status s = batch.RollbackToSavePoint();
+ ASSERT_EQ(s, Status::NotFound());
+ WriteBatchInternal::MarkCommit(&batch, Slice("xid1"));
+ WriteBatchInternal::MarkRollback(&batch, Slice("xid1"));
+ ASSERT_EQ(2u, batch.Count());
+
+ TestHandler handler;
+ 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++;
+ 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;
+
+ 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;
+ 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;
+
+ 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;
+
+ batch.Put(Slice("k1"), Slice("v1"));
+ batch.Put(Slice("k2"), Slice("v2"));
+ batch.PutLogData(Slice("blob1"));
+ batch.Delete(Slice("k1"));
+ batch.SingleDelete(Slice("k2"));
+ batch.PutLogData(Slice("blob2"));
+ batch.Merge(Slice("foo"), Slice("bar"));
+ 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;
+ 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") };
+ 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");
+ 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) {}
+ uint32_t GetID() const override { return id_; }
+ const Comparator* GetComparator() const override {
+ return BytewiseComparator();
+ }
+
+ private:
+ uint32_t id_;
+};
+} // namespace anonymous
+
+TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
+ WriteBatch batch;
+ ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
+ batch.Put(&zero, Slice("foo"), Slice("bar"));
+ batch.Put(&two, Slice("twofoo"), Slice("bar2"));
+ batch.Put(&eight, Slice("eightfoo"), Slice("bar8"));
+ batch.Delete(&eight, Slice("eightfoo"));
+ batch.SingleDelete(&two, Slice("twofoo"));
+ batch.DeleteRange(&two, Slice("3foo"), Slice("4foo"));
+ batch.Merge(&three, Slice("threethree"), Slice("3three"));
+ batch.Put(&zero, Slice("foo"), Slice("bar"));
+ batch.Merge(Slice("omom"), Slice("nom"));
+
+ TestHandler handler;
+ 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);
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
+ WriteBatchWithIndex batch;
+ ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
+ batch.Put(&zero, Slice("foo"), Slice("bar"));
+ batch.Put(&two, Slice("twofoo"), Slice("bar2"));
+ batch.Put(&eight, Slice("eightfoo"), Slice("bar8"));
+ batch.Delete(&eight, Slice("eightfoo"));
+ batch.SingleDelete(&two, Slice("twofoo"));
+ batch.Merge(&three, Slice("threethree"), Slice("3three"));
+ batch.Put(&zero, Slice("foo"), Slice("bar"));
+ batch.Merge(Slice("omom"), Slice("nom"));
+
+ std::unique_ptr<WBWIIterator> 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;
+ 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);
+}
+#endif // !ROCKSDB_LITE
+
+TEST_F(WriteBatchTest, SavePointTest) {
+ Status s;
+ WriteBatch batch;
+ batch.SetSavePoint();
+
+ batch.Put("A", "a");
+ batch.Put("B", "b");
+ batch.SetSavePoint();
+
+ batch.Put("C", "c");
+ 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));
+
+ batch.Delete("A");
+ batch.Put("B", "bb");
+
+ ASSERT_OK(batch.RollbackToSavePoint());
+ ASSERT_EQ("", PrintContents(&batch));
+
+ s = batch.RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+ ASSERT_EQ("", PrintContents(&batch));
+
+ batch.Put("D", "d");
+ batch.Delete("A");
+
+ batch.SetSavePoint();
+
+ batch.Put("A", "aaa");
+
+ ASSERT_OK(batch.RollbackToSavePoint());
+ ASSERT_EQ(
+ "Delete(A)@1"
+ "Put(D, d)@0",
+ PrintContents(&batch));
+
+ batch.SetSavePoint();
+
+ batch.Put("D", "d");
+ 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));
+
+ 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();
+
+ 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();
+ 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 ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/write_callback.h b/storage/rocksdb/rocksdb/db/write_callback.h
new file mode 100644
index 00000000..106d0204
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_callback.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DB;
+
+class WriteCallback {
+ public:
+ virtual ~WriteCallback() {}
+
+ // Will be called while on the write thread before the write executes. If
+ // this function returns a non-OK status, the write will be aborted and this
+ // status will be returned to the caller of DB::Write().
+ virtual Status Callback(DB* db) = 0;
+
+ // return true if writes with this callback can be batched with other writes
+ virtual bool AllowWriteBatching() = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_callback_test.cc b/storage/rocksdb/rocksdb/db/write_callback_test.cc
new file mode 100644
index 00000000..df7d673a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_callback_test.cc
@@ -0,0 +1,452 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <atomic>
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "db/write_callback.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<DBImpl*> (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<bool> 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_; }
+};
+
+TEST_F(WriteCallbackTest, 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));
+ write_batch_.Put(key, val);
+ }
+
+ void Clear() {
+ kvs_.clear();
+ write_batch_.Clear();
+ callback_.was_called_.store(false);
+ }
+
+ MockWriteCallback callback_;
+ WriteBatch write_batch_;
+ std::vector<std::pair<string, string>> 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<std::vector<WriteOP>> 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& unordered_write : {true, false}) {
+ for (auto& seq_per_batch : {true, false}) {
+ for (auto& two_queues : {true, false}) {
+ for (auto& allow_parallel : {true, false}) {
+ for (auto& allow_batching : {true, false}) {
+ for (auto& enable_WAL : {true, false}) {
+ for (auto& enable_pipelined_write : {true, false}) {
+ 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;
+
+ DestroyDB(dbname, options);
+
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> 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<DBImpl*>(db);
+ ASSERT_TRUE(db_impl);
+
+ // Writers that have called JoinBatchGroup.
+ std::atomic<uint64_t> threads_joining(0);
+ // Writers that have linked to the queue
+ std::atomic<uint64_t> threads_linked(0);
+ // Writers that pass WriteThread::JoinBatchGroup:Wait sync-point.
+ std::atomic<uint64_t> threads_verified(0);
+
+ std::atomic<uint64_t> 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<WriteThread::Writer*>(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<WriteThread::Writer*>(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<uint32_t> thread_num(0);
+ std::atomic<char> dummy_key(0);
+
+ // Each write thread create a random write batch and write to DB
+ // with a write callback.
+ std::function<void()> 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;
+ 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
+ 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<port::Thread> 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;
+ DestroyDB(dbname, options);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST_F(WriteCallbackTest, WriteCallBackTest) {
+ Options options;
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ DB* db;
+ DBImpl* db_impl;
+
+ DestroyDB(dbname, options);
+
+ options.create_if_missing = true;
+ Status s = DB::Open(options, dbname, &db);
+ ASSERT_OK(s);
+
+ db_impl = dynamic_cast<DBImpl*> (db);
+ ASSERT_TRUE(db_impl);
+
+ WriteBatch wb;
+
+ wb.Put("a", "value.a");
+ 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;
+
+ 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;
+
+ 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;
+ DestroyDB(dbname, options);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as WriteWithCallback is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/db/write_controller.cc b/storage/rocksdb/rocksdb/db/write_controller.cc
new file mode 100644
index 00000000..5480aabd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_controller.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cassert>
+#include <ratio>
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+std::unique_ptr<WriteControllerToken> WriteController::GetStopToken() {
+ ++total_stopped_;
+ return std::unique_ptr<WriteControllerToken>(new StopWriteToken(this));
+}
+
+std::unique_ptr<WriteControllerToken> WriteController::GetDelayToken(
+ uint64_t write_rate) {
+ total_delayed_++;
+ // Reset counters.
+ last_refill_time_ = 0;
+ bytes_left_ = 0;
+ set_delayed_write_rate(write_rate);
+ return std::unique_ptr<WriteControllerToken>(new DelayWriteToken(this));
+}
+
+std::unique_ptr<WriteControllerToken>
+WriteController::GetCompactionPressureToken() {
+ ++total_compaction_pressure_;
+ return std::unique_ptr<WriteControllerToken>(
+ new CompactionPressureToken(this));
+}
+
+bool WriteController::IsStopped() const {
+ return total_stopped_.load(std::memory_order_relaxed) > 0;
+}
+// This is inside DB mutex, so we can't sleep and need to minimize
+// frequency to get time.
+// If it turns out to be a performance issue, we can redesign the thread
+// synchronization model here.
+// The function trust caller will sleep micros returned.
+uint64_t WriteController::GetDelay(Env* env, uint64_t num_bytes) {
+ if (total_stopped_.load(std::memory_order_relaxed) > 0) {
+ return 0;
+ }
+ if (total_delayed_.load(std::memory_order_relaxed) == 0) {
+ return 0;
+ }
+
+ const uint64_t kMicrosPerSecond = 1000000;
+ const uint64_t kRefillInterval = 1024U;
+
+ if (bytes_left_ >= num_bytes) {
+ bytes_left_ -= num_bytes;
+ return 0;
+ }
+ // The frequency to get time inside DB mutex is less than one per refill
+ // interval.
+ auto time_now = NowMicrosMonotonic(env);
+
+ uint64_t sleep_debt = 0;
+ uint64_t time_since_last_refill = 0;
+ if (last_refill_time_ != 0) {
+ if (last_refill_time_ > time_now) {
+ sleep_debt = last_refill_time_ - time_now;
+ } else {
+ time_since_last_refill = time_now - last_refill_time_;
+ bytes_left_ +=
+ static_cast<uint64_t>(static_cast<double>(time_since_last_refill) /
+ kMicrosPerSecond * delayed_write_rate_);
+ if (time_since_last_refill >= kRefillInterval &&
+ bytes_left_ > num_bytes) {
+ // If refill interval already passed and we have enough bytes
+ // return without extra sleeping.
+ last_refill_time_ = time_now;
+ bytes_left_ -= num_bytes;
+ return 0;
+ }
+ }
+ }
+
+ uint64_t single_refill_amount =
+ delayed_write_rate_ * kRefillInterval / kMicrosPerSecond;
+ if (bytes_left_ + single_refill_amount >= num_bytes) {
+ // Wait until a refill interval
+ // Never trigger expire for less than one refill interval to avoid to get
+ // time.
+ bytes_left_ = bytes_left_ + single_refill_amount - num_bytes;
+ last_refill_time_ = time_now + kRefillInterval;
+ return kRefillInterval + sleep_debt;
+ }
+
+ // Need to refill more than one interval. Need to sleep longer. Check
+ // whether expiration will hit
+
+ // Sleep just until `num_bytes` is allowed.
+ uint64_t sleep_amount =
+ static_cast<uint64_t>(num_bytes /
+ static_cast<long double>(delayed_write_rate_) *
+ kMicrosPerSecond) +
+ sleep_debt;
+ last_refill_time_ = time_now + sleep_amount;
+ return sleep_amount;
+}
+
+uint64_t WriteController::NowMicrosMonotonic(Env* env) {
+ return env->NowNanos() / std::milli::den;
+}
+
+StopWriteToken::~StopWriteToken() {
+ assert(controller_->total_stopped_ >= 1);
+ --controller_->total_stopped_;
+}
+
+DelayWriteToken::~DelayWriteToken() {
+ controller_->total_delayed_--;
+ assert(controller_->total_delayed_.load() >= 0);
+}
+
+CompactionPressureToken::~CompactionPressureToken() {
+ controller_->total_compaction_pressure_--;
+ assert(controller_->total_compaction_pressure_ >= 0);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_controller.h b/storage/rocksdb/rocksdb/db/write_controller.h
new file mode 100644
index 00000000..785ae689
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_controller.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+
+#include <atomic>
+#include <memory>
+#include "rocksdb/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class WriteControllerToken;
+
+// WriteController is controlling write stalls in our write code-path. Write
+// stalls happen when compaction can't keep up with write rate.
+// All of the methods here (including WriteControllerToken's destructors) need
+// to be called while holding DB mutex
+class WriteController {
+ public:
+ explicit WriteController(uint64_t _delayed_write_rate = 1024u * 1024u * 32u,
+ int64_t low_pri_rate_bytes_per_sec = 1024 * 1024)
+ : total_stopped_(0),
+ total_delayed_(0),
+ total_compaction_pressure_(0),
+ bytes_left_(0),
+ last_refill_time_(0),
+ low_pri_rate_limiter_(
+ NewGenericRateLimiter(low_pri_rate_bytes_per_sec)) {
+ set_max_delayed_write_rate(_delayed_write_rate);
+ }
+ ~WriteController() = default;
+
+ // When an actor (column family) requests a stop token, all writes will be
+ // stopped until the stop token is released (deleted)
+ std::unique_ptr<WriteControllerToken> GetStopToken();
+ // When an actor (column family) requests a delay token, total delay for all
+ // writes to the DB will be controlled under the delayed write rate. Every
+ // write needs to call GetDelay() with number of bytes writing to the DB,
+ // which returns number of microseconds to sleep.
+ std::unique_ptr<WriteControllerToken> GetDelayToken(
+ uint64_t delayed_write_rate);
+ // When an actor (column family) requests a moderate token, compaction
+ // threads will be increased
+ std::unique_ptr<WriteControllerToken> GetCompactionPressureToken();
+
+ // these three metods are querying the state of the WriteController
+ bool IsStopped() const;
+ bool NeedsDelay() const { return total_delayed_.load() > 0; }
+ bool NeedSpeedupCompaction() const {
+ return IsStopped() || NeedsDelay() || total_compaction_pressure_ > 0;
+ }
+ // return how many microseconds the caller needs to sleep after the call
+ // num_bytes: how many number of bytes to put into the DB.
+ // Prerequisite: DB mutex held.
+ uint64_t GetDelay(Env* env, uint64_t num_bytes);
+ void set_delayed_write_rate(uint64_t write_rate) {
+ // avoid divide 0
+ if (write_rate == 0) {
+ write_rate = 1u;
+ } else if (write_rate > max_delayed_write_rate()) {
+ write_rate = max_delayed_write_rate();
+ }
+ delayed_write_rate_ = write_rate;
+ }
+
+ void set_max_delayed_write_rate(uint64_t write_rate) {
+ // avoid divide 0
+ if (write_rate == 0) {
+ write_rate = 1u;
+ }
+ max_delayed_write_rate_ = write_rate;
+ // update delayed_write_rate_ as well
+ delayed_write_rate_ = write_rate;
+ }
+
+ uint64_t delayed_write_rate() const { return delayed_write_rate_; }
+
+ uint64_t max_delayed_write_rate() const { return max_delayed_write_rate_; }
+
+ RateLimiter* low_pri_rate_limiter() { return low_pri_rate_limiter_.get(); }
+
+ private:
+ uint64_t NowMicrosMonotonic(Env* env);
+
+ friend class WriteControllerToken;
+ friend class StopWriteToken;
+ friend class DelayWriteToken;
+ friend class CompactionPressureToken;
+
+ std::atomic<int> total_stopped_;
+ std::atomic<int> total_delayed_;
+ std::atomic<int> total_compaction_pressure_;
+ uint64_t bytes_left_;
+ uint64_t last_refill_time_;
+ // write rate set when initialization or by `DBImpl::SetDBOptions`
+ uint64_t max_delayed_write_rate_;
+ // current write rate
+ uint64_t delayed_write_rate_;
+
+ std::unique_ptr<RateLimiter> low_pri_rate_limiter_;
+};
+
+class WriteControllerToken {
+ public:
+ explicit WriteControllerToken(WriteController* controller)
+ : controller_(controller) {}
+ virtual ~WriteControllerToken() {}
+
+ protected:
+ WriteController* controller_;
+
+ private:
+ // no copying allowed
+ WriteControllerToken(const WriteControllerToken&) = delete;
+ void operator=(const WriteControllerToken&) = delete;
+};
+
+class StopWriteToken : public WriteControllerToken {
+ public:
+ explicit StopWriteToken(WriteController* controller)
+ : WriteControllerToken(controller) {}
+ virtual ~StopWriteToken();
+};
+
+class DelayWriteToken : public WriteControllerToken {
+ public:
+ explicit DelayWriteToken(WriteController* controller)
+ : WriteControllerToken(controller) {}
+ virtual ~DelayWriteToken();
+};
+
+class CompactionPressureToken : public WriteControllerToken {
+ public:
+ explicit CompactionPressureToken(WriteController* controller)
+ : WriteControllerToken(controller) {}
+ virtual ~CompactionPressureToken();
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_controller_test.cc b/storage/rocksdb/rocksdb/db/write_controller_test.cc
new file mode 100644
index 00000000..72d11679
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_controller_test.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <ratio>
+
+#include "db/write_controller.h"
+
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteControllerTest : public testing::Test {};
+
+class TimeSetEnv : public EnvWrapper {
+ public:
+ explicit TimeSetEnv() : EnvWrapper(nullptr) {}
+ uint64_t now_micros_ = 6666;
+ uint64_t NowNanos() override { return now_micros_ * std::milli::den; }
+};
+
+TEST_F(WriteControllerTest, ChangeDelayRateTest) {
+ TimeSetEnv env;
+ WriteController controller(40000000u); // also set max delayed rate
+ controller.set_delayed_write_rate(10000000u);
+ auto delay_token_0 =
+ controller.GetDelayToken(controller.delayed_write_rate());
+ ASSERT_EQ(static_cast<uint64_t>(2000000),
+ controller.GetDelay(&env, 20000000u));
+ auto delay_token_1 = controller.GetDelayToken(2000000u);
+ ASSERT_EQ(static_cast<uint64_t>(10000000),
+ controller.GetDelay(&env, 20000000u));
+ auto delay_token_2 = controller.GetDelayToken(1000000u);
+ ASSERT_EQ(static_cast<uint64_t>(20000000),
+ controller.GetDelay(&env, 20000000u));
+ auto delay_token_3 = controller.GetDelayToken(20000000u);
+ ASSERT_EQ(static_cast<uint64_t>(1000000),
+ controller.GetDelay(&env, 20000000u));
+ // This is more than max rate. Max delayed rate will be used.
+ auto delay_token_4 =
+ controller.GetDelayToken(controller.delayed_write_rate() * 3);
+ ASSERT_EQ(static_cast<uint64_t>(500000),
+ controller.GetDelay(&env, 20000000u));
+}
+
+TEST_F(WriteControllerTest, SanityTest) {
+ WriteController controller(10000000u);
+ auto stop_token_1 = controller.GetStopToken();
+ auto stop_token_2 = controller.GetStopToken();
+
+ ASSERT_TRUE(controller.IsStopped());
+ stop_token_1.reset();
+ ASSERT_TRUE(controller.IsStopped());
+ stop_token_2.reset();
+ ASSERT_FALSE(controller.IsStopped());
+
+ TimeSetEnv env;
+
+ auto delay_token_1 = controller.GetDelayToken(10000000u);
+ ASSERT_EQ(static_cast<uint64_t>(2000000),
+ controller.GetDelay(&env, 20000000u));
+
+ env.now_micros_ += 1999900u; // sleep debt 1000
+
+ auto delay_token_2 = controller.GetDelayToken(10000000u);
+ // Rate reset after changing the token.
+ ASSERT_EQ(static_cast<uint64_t>(2000000),
+ controller.GetDelay(&env, 20000000u));
+
+ env.now_micros_ += 1999900u; // sleep debt 1000
+
+ // One refill: 10240 bytes allowed, 1000 used, 9240 left
+ ASSERT_EQ(static_cast<uint64_t>(1124), controller.GetDelay(&env, 1000u));
+ env.now_micros_ += 1124u; // sleep debt 0
+
+ delay_token_2.reset();
+ // 1000 used, 8240 left
+ ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
+
+ env.now_micros_ += 100u; // sleep credit 100
+ // 1000 used, 7240 left
+ ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
+
+ env.now_micros_ += 100u; // sleep credit 200
+ // One refill: 10240 fileed, sleep credit generates 2000. 8000 used
+ // 7240 + 10240 + 2000 - 8000 = 11480 left
+ ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 8000u));
+
+ env.now_micros_ += 200u; // sleep debt 824
+ // 1000 used, 10480 left.
+ ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
+
+ env.now_micros_ += 200u; // sleep debt 624
+ // Out of bound sleep, still 10480 left
+ ASSERT_EQ(static_cast<uint64_t>(3000624u),
+ controller.GetDelay(&env, 30000000u));
+
+ env.now_micros_ += 3000724u; // sleep credit 100
+ // 6000 used, 4480 left.
+ ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 6000u));
+
+ env.now_micros_ += 200u; // sleep credit 300
+ // One refill, credit 4480 balance + 3000 credit + 10240 refill
+ // Use 8000, 9720 left
+ ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 8000u));
+
+ env.now_micros_ += 3024u; // sleep credit 2000
+
+ // 1720 left
+ ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
+
+ // 1720 balance + 20000 credit = 20170 left
+ // Use 8000, 12170 left
+ ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
+
+ // 4170 left
+ ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
+
+ // Need a refill
+ ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 9000u));
+
+ delay_token_1.reset();
+ ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 30000000u));
+ delay_token_1.reset();
+ ASSERT_FALSE(controller.IsStopped());
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/db/write_thread.cc b/storage/rocksdb/rocksdb/db/write_thread.cc
new file mode 100644
index 00000000..5f50bba6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_thread.cc
@@ -0,0 +1,777 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_thread.h"
+#include <chrono>
+#include <thread>
+#include "db/column_family.h"
+#include "monitoring/perf_context_imp.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+WriteThread::WriteThread(const ImmutableDBOptions& db_options)
+ : max_yield_usec_(db_options.enable_write_thread_adaptive_yield
+ ? db_options.write_thread_max_yield_usec
+ : 0),
+ slow_yield_usec_(db_options.write_thread_slow_yield_usec),
+ allow_concurrent_memtable_write_(
+ db_options.allow_concurrent_memtable_write),
+ enable_pipelined_write_(db_options.enable_pipelined_write),
+ max_write_batch_group_size_bytes(
+ db_options.max_write_batch_group_size_bytes),
+ newest_writer_(nullptr),
+ newest_memtable_writer_(nullptr),
+ last_sequence_(0),
+ write_stall_dummy_(),
+ stall_mu_(),
+ stall_cv_(&stall_mu_) {}
+
+uint8_t WriteThread::BlockingAwaitState(Writer* w, uint8_t goal_mask) {
+ // We're going to block. Lazily create the mutex. We guarantee
+ // propagation of this construction to the waker via the
+ // STATE_LOCKED_WAITING state. The waker won't try to touch the mutex
+ // or the condvar unless they CAS away the STATE_LOCKED_WAITING that
+ // we install below.
+ w->CreateMutex();
+
+ auto state = w->state.load(std::memory_order_acquire);
+ assert(state != STATE_LOCKED_WAITING);
+ if ((state & goal_mask) == 0 &&
+ w->state.compare_exchange_strong(state, STATE_LOCKED_WAITING)) {
+ // we have permission (and an obligation) to use StateMutex
+ std::unique_lock<std::mutex> guard(w->StateMutex());
+ w->StateCV().wait(guard, [w] {
+ return w->state.load(std::memory_order_relaxed) != STATE_LOCKED_WAITING;
+ });
+ state = w->state.load(std::memory_order_relaxed);
+ }
+ // else tricky. Goal is met or CAS failed. In the latter case the waker
+ // must have changed the state, and compare_exchange_strong has updated
+ // our local variable with the new one. At the moment WriteThread never
+ // waits for a transition across intermediate states, so we know that
+ // since a state change has occurred the goal must have been met.
+ assert((state & goal_mask) != 0);
+ return state;
+}
+
+uint8_t WriteThread::AwaitState(Writer* w, uint8_t goal_mask,
+ AdaptationContext* ctx) {
+ uint8_t state = 0;
+
+ // 1. Busy loop using "pause" for 1 micro sec
+ // 2. Else SOMETIMES busy loop using "yield" for 100 micro sec (default)
+ // 3. Else blocking wait
+
+ // On a modern Xeon each loop takes about 7 nanoseconds (most of which
+ // is the effect of the pause instruction), so 200 iterations is a bit
+ // more than a microsecond. This is long enough that waits longer than
+ // this can amortize the cost of accessing the clock and yielding.
+ for (uint32_t tries = 0; tries < 200; ++tries) {
+ state = w->state.load(std::memory_order_acquire);
+ if ((state & goal_mask) != 0) {
+ return state;
+ }
+ port::AsmVolatilePause();
+ }
+
+ // This is below the fast path, so that the stat is zero when all writes are
+ // from the same thread.
+ PERF_TIMER_GUARD(write_thread_wait_nanos);
+
+ // If we're only going to end up waiting a short period of time,
+ // it can be a lot more efficient to call std::this_thread::yield()
+ // in a loop than to block in StateMutex(). For reference, on my 4.0
+ // SELinux test server with support for syscall auditing enabled, the
+ // minimum latency between FUTEX_WAKE to returning from FUTEX_WAIT is
+ // 2.7 usec, and the average is more like 10 usec. That can be a big
+ // drag on RockDB's single-writer design. Of course, spinning is a
+ // bad idea if other threads are waiting to run or if we're going to
+ // wait for a long time. How do we decide?
+ //
+ // We break waiting into 3 categories: short-uncontended,
+ // short-contended, and long. If we had an oracle, then we would always
+ // spin for short-uncontended, always block for long, and our choice for
+ // short-contended might depend on whether we were trying to optimize
+ // RocksDB throughput or avoid being greedy with system resources.
+ //
+ // Bucketing into short or long is easy by measuring elapsed time.
+ // Differentiating short-uncontended from short-contended is a bit
+ // trickier, but not too bad. We could look for involuntary context
+ // switches using getrusage(RUSAGE_THREAD, ..), but it's less work
+ // (portability code and CPU) to just look for yield calls that take
+ // longer than we expect. sched_yield() doesn't actually result in any
+ // context switch overhead if there are no other runnable processes
+ // on the current core, in which case it usually takes less than
+ // a microsecond.
+ //
+ // There are two primary tunables here: the threshold between "short"
+ // and "long" waits, and the threshold at which we suspect that a yield
+ // is slow enough to indicate we should probably block. If these
+ // thresholds are chosen well then CPU-bound workloads that don't
+ // have more threads than cores will experience few context switches
+ // (voluntary or involuntary), and the total number of context switches
+ // (voluntary and involuntary) will not be dramatically larger (maybe
+ // 2x) than the number of voluntary context switches that occur when
+ // --max_yield_wait_micros=0.
+ //
+ // There's another constant, which is the number of slow yields we will
+ // tolerate before reversing our previous decision. Solitary slow
+ // yields are pretty common (low-priority small jobs ready to run),
+ // so this should be at least 2. We set this conservatively to 3 so
+ // that we can also immediately schedule a ctx adaptation, rather than
+ // waiting for the next update_ctx.
+
+ const size_t kMaxSlowYieldsWhileSpinning = 3;
+
+ // Whether the yield approach has any credit in this context. The credit is
+ // added by yield being succesfull before timing out, and decreased otherwise.
+ auto& yield_credit = ctx->value;
+ // Update the yield_credit based on sample runs or right after a hard failure
+ bool update_ctx = false;
+ // Should we reinforce the yield credit
+ bool would_spin_again = false;
+ // The samling base for updating the yeild credit. The sampling rate would be
+ // 1/sampling_base.
+ const int sampling_base = 256;
+
+ if (max_yield_usec_ > 0) {
+ update_ctx = Random::GetTLSInstance()->OneIn(sampling_base);
+
+ if (update_ctx || yield_credit.load(std::memory_order_relaxed) >= 0) {
+ // we're updating the adaptation statistics, or spinning has >
+ // 50% chance of being shorter than max_yield_usec_ and causing no
+ // involuntary context switches
+ auto spin_begin = std::chrono::steady_clock::now();
+
+ // this variable doesn't include the final yield (if any) that
+ // causes the goal to be met
+ size_t slow_yield_count = 0;
+
+ auto iter_begin = spin_begin;
+ while ((iter_begin - spin_begin) <=
+ std::chrono::microseconds(max_yield_usec_)) {
+ std::this_thread::yield();
+
+ state = w->state.load(std::memory_order_acquire);
+ if ((state & goal_mask) != 0) {
+ // success
+ would_spin_again = true;
+ break;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ if (now == iter_begin ||
+ now - iter_begin >= std::chrono::microseconds(slow_yield_usec_)) {
+ // conservatively count it as a slow yield if our clock isn't
+ // accurate enough to measure the yield duration
+ ++slow_yield_count;
+ if (slow_yield_count >= kMaxSlowYieldsWhileSpinning) {
+ // Not just one ivcsw, but several. Immediately update yield_credit
+ // and fall back to blocking
+ update_ctx = true;
+ break;
+ }
+ }
+ iter_begin = now;
+ }
+ }
+ }
+
+ if ((state & goal_mask) == 0) {
+ TEST_SYNC_POINT_CALLBACK("WriteThread::AwaitState:BlockingWaiting", w);
+ state = BlockingAwaitState(w, goal_mask);
+ }
+
+ if (update_ctx) {
+ // Since our update is sample based, it is ok if a thread overwrites the
+ // updates by other threads. Thus the update does not have to be atomic.
+ auto v = yield_credit.load(std::memory_order_relaxed);
+ // fixed point exponential decay with decay constant 1/1024, with +1
+ // and -1 scaled to avoid overflow for int32_t
+ //
+ // On each update the positive credit is decayed by a facor of 1/1024 (i.e.,
+ // 0.1%). If the sampled yield was successful, the credit is also increased
+ // by X. Setting X=2^17 ensures that the credit never exceeds
+ // 2^17*2^10=2^27, which is lower than 2^31 the upperbound of int32_t. Same
+ // logic applies to negative credits.
+ v = v - (v / 1024) + (would_spin_again ? 1 : -1) * 131072;
+ yield_credit.store(v, std::memory_order_relaxed);
+ }
+
+ assert((state & goal_mask) != 0);
+ return state;
+}
+
+void WriteThread::SetState(Writer* w, uint8_t new_state) {
+ auto state = w->state.load(std::memory_order_acquire);
+ if (state == STATE_LOCKED_WAITING ||
+ !w->state.compare_exchange_strong(state, new_state)) {
+ assert(state == STATE_LOCKED_WAITING);
+
+ std::lock_guard<std::mutex> guard(w->StateMutex());
+ assert(w->state.load(std::memory_order_relaxed) != new_state);
+ w->state.store(new_state, std::memory_order_relaxed);
+ w->StateCV().notify_one();
+ }
+}
+
+bool WriteThread::LinkOne(Writer* w, std::atomic<Writer*>* newest_writer) {
+ assert(newest_writer != nullptr);
+ assert(w->state == STATE_INIT);
+ Writer* writers = newest_writer->load(std::memory_order_relaxed);
+ while (true) {
+ // If write stall in effect, and w->no_slowdown is not true,
+ // block here until stall is cleared. If its true, then return
+ // immediately
+ if (writers == &write_stall_dummy_) {
+ if (w->no_slowdown) {
+ w->status = Status::Incomplete("Write stall");
+ SetState(w, STATE_COMPLETED);
+ return false;
+ }
+ // Since no_slowdown is false, wait here to be notified of the write
+ // stall clearing
+ {
+ MutexLock lock(&stall_mu_);
+ writers = newest_writer->load(std::memory_order_relaxed);
+ if (writers == &write_stall_dummy_) {
+ stall_cv_.Wait();
+ // Load newest_writers_ again since it may have changed
+ writers = newest_writer->load(std::memory_order_relaxed);
+ continue;
+ }
+ }
+ }
+ w->link_older = writers;
+ if (newest_writer->compare_exchange_weak(writers, w)) {
+ return (writers == nullptr);
+ }
+ }
+}
+
+bool WriteThread::LinkGroup(WriteGroup& write_group,
+ std::atomic<Writer*>* newest_writer) {
+ assert(newest_writer != nullptr);
+ Writer* leader = write_group.leader;
+ Writer* last_writer = write_group.last_writer;
+ Writer* w = last_writer;
+ while (true) {
+ // Unset link_newer pointers to make sure when we call
+ // CreateMissingNewerLinks later it create all missing links.
+ w->link_newer = nullptr;
+ w->write_group = nullptr;
+ if (w == leader) {
+ break;
+ }
+ w = w->link_older;
+ }
+ Writer* newest = newest_writer->load(std::memory_order_relaxed);
+ while (true) {
+ leader->link_older = newest;
+ if (newest_writer->compare_exchange_weak(newest, last_writer)) {
+ return (newest == nullptr);
+ }
+ }
+}
+
+void WriteThread::CreateMissingNewerLinks(Writer* head) {
+ while (true) {
+ Writer* next = head->link_older;
+ if (next == nullptr || next->link_newer != nullptr) {
+ assert(next == nullptr || next->link_newer == head);
+ break;
+ }
+ next->link_newer = head;
+ head = next;
+ }
+}
+
+WriteThread::Writer* WriteThread::FindNextLeader(Writer* from,
+ Writer* boundary) {
+ assert(from != nullptr && from != boundary);
+ Writer* current = from;
+ while (current->link_older != boundary) {
+ current = current->link_older;
+ assert(current != nullptr);
+ }
+ return current;
+}
+
+void WriteThread::CompleteLeader(WriteGroup& write_group) {
+ assert(write_group.size > 0);
+ Writer* leader = write_group.leader;
+ if (write_group.size == 1) {
+ write_group.leader = nullptr;
+ write_group.last_writer = nullptr;
+ } else {
+ assert(leader->link_newer != nullptr);
+ leader->link_newer->link_older = nullptr;
+ write_group.leader = leader->link_newer;
+ }
+ write_group.size -= 1;
+ SetState(leader, STATE_COMPLETED);
+}
+
+void WriteThread::CompleteFollower(Writer* w, WriteGroup& write_group) {
+ assert(write_group.size > 1);
+ assert(w != write_group.leader);
+ if (w == write_group.last_writer) {
+ w->link_older->link_newer = nullptr;
+ write_group.last_writer = w->link_older;
+ } else {
+ w->link_older->link_newer = w->link_newer;
+ w->link_newer->link_older = w->link_older;
+ }
+ write_group.size -= 1;
+ SetState(w, STATE_COMPLETED);
+}
+
+void WriteThread::BeginWriteStall() {
+ LinkOne(&write_stall_dummy_, &newest_writer_);
+
+ // Walk writer list until w->write_group != nullptr. The current write group
+ // will not have a mix of slowdown/no_slowdown, so its ok to stop at that
+ // point
+ Writer* w = write_stall_dummy_.link_older;
+ Writer* prev = &write_stall_dummy_;
+ while (w != nullptr && w->write_group == nullptr) {
+ if (w->no_slowdown) {
+ prev->link_older = w->link_older;
+ w->status = Status::Incomplete("Write stall");
+ SetState(w, STATE_COMPLETED);
+ if (prev->link_older) {
+ prev->link_older->link_newer = prev;
+ }
+ w = prev->link_older;
+ } else {
+ prev = w;
+ w = w->link_older;
+ }
+ }
+}
+
+void WriteThread::EndWriteStall() {
+ MutexLock lock(&stall_mu_);
+
+ // Unlink write_stall_dummy_ from the write queue. This will unblock
+ // pending write threads to enqueue themselves
+ assert(newest_writer_.load(std::memory_order_relaxed) == &write_stall_dummy_);
+ assert(write_stall_dummy_.link_older != nullptr);
+ write_stall_dummy_.link_older->link_newer = write_stall_dummy_.link_newer;
+ newest_writer_.exchange(write_stall_dummy_.link_older);
+
+ // Wake up writers
+ stall_cv_.SignalAll();
+}
+
+static WriteThread::AdaptationContext jbg_ctx("JoinBatchGroup");
+void WriteThread::JoinBatchGroup(Writer* w) {
+ TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:Start", w);
+ assert(w->batch != nullptr);
+
+ bool linked_as_leader = LinkOne(w, &newest_writer_);
+
+ if (linked_as_leader) {
+ SetState(w, STATE_GROUP_LEADER);
+ }
+
+ TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:Wait", w);
+
+ if (!linked_as_leader) {
+ /**
+ * Wait util:
+ * 1) An existing leader pick us as the new leader when it finishes
+ * 2) An existing leader pick us as its follewer and
+ * 2.1) finishes the memtable writes on our behalf
+ * 2.2) Or tell us to finish the memtable writes in pralallel
+ * 3) (pipelined write) An existing leader pick us as its follower and
+ * finish book-keeping and WAL write for us, enqueue us as pending
+ * memtable writer, and
+ * 3.1) we become memtable writer group leader, or
+ * 3.2) an existing memtable writer group leader tell us to finish memtable
+ * writes in parallel.
+ */
+ TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:BeganWaiting", w);
+ AwaitState(w, STATE_GROUP_LEADER | STATE_MEMTABLE_WRITER_LEADER |
+ STATE_PARALLEL_MEMTABLE_WRITER | STATE_COMPLETED,
+ &jbg_ctx);
+ TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:DoneWaiting", w);
+ }
+}
+
+size_t WriteThread::EnterAsBatchGroupLeader(Writer* leader,
+ WriteGroup* write_group) {
+ assert(leader->link_older == nullptr);
+ assert(leader->batch != nullptr);
+ assert(write_group != nullptr);
+
+ size_t size = WriteBatchInternal::ByteSize(leader->batch);
+
+ // Allow the group to grow up to a maximum size, but if the
+ // original write is small, limit the growth so we do not slow
+ // down the small write too much.
+ size_t max_size = max_write_batch_group_size_bytes;
+ const uint64_t min_batch_size_bytes = max_write_batch_group_size_bytes / 8;
+ if (size <= min_batch_size_bytes) {
+ max_size = size + min_batch_size_bytes;
+ }
+
+ leader->write_group = write_group;
+ write_group->leader = leader;
+ write_group->last_writer = leader;
+ write_group->size = 1;
+ Writer* newest_writer = newest_writer_.load(std::memory_order_acquire);
+
+ // This is safe regardless of any db mutex status of the caller. Previous
+ // calls to ExitAsGroupLeader either didn't call CreateMissingNewerLinks
+ // (they emptied the list and then we added ourself as leader) or had to
+ // explicitly wake us up (the list was non-empty when we added ourself,
+ // so we have already received our MarkJoined).
+ CreateMissingNewerLinks(newest_writer);
+
+ // Tricky. Iteration start (leader) is exclusive and finish
+ // (newest_writer) is inclusive. Iteration goes from old to new.
+ Writer* w = leader;
+ while (w != newest_writer) {
+ w = w->link_newer;
+
+ if (w->sync && !leader->sync) {
+ // Do not include a sync write into a batch handled by a non-sync write.
+ break;
+ }
+
+ if (w->no_slowdown != leader->no_slowdown) {
+ // Do not mix writes that are ok with delays with the ones that
+ // request fail on delays.
+ break;
+ }
+
+ if (w->disable_wal != leader->disable_wal) {
+ // Do not mix writes that enable WAL with the ones whose
+ // WAL disabled.
+ break;
+ }
+
+ if (w->batch == nullptr) {
+ // Do not include those writes with nullptr batch. Those are not writes,
+ // those are something else. They want to be alone
+ break;
+ }
+
+ if (w->callback != nullptr && !w->callback->AllowWriteBatching()) {
+ // dont batch writes that don't want to be batched
+ break;
+ }
+
+ auto batch_size = WriteBatchInternal::ByteSize(w->batch);
+ if (size + batch_size > max_size) {
+ // Do not make batch too big
+ break;
+ }
+
+ w->write_group = write_group;
+ size += batch_size;
+ write_group->last_writer = w;
+ write_group->size++;
+ }
+ TEST_SYNC_POINT_CALLBACK("WriteThread::EnterAsBatchGroupLeader:End", w);
+ return size;
+}
+
+void WriteThread::EnterAsMemTableWriter(Writer* leader,
+ WriteGroup* write_group) {
+ assert(leader != nullptr);
+ assert(leader->link_older == nullptr);
+ assert(leader->batch != nullptr);
+ assert(write_group != nullptr);
+
+ size_t size = WriteBatchInternal::ByteSize(leader->batch);
+
+ // Allow the group to grow up to a maximum size, but if the
+ // original write is small, limit the growth so we do not slow
+ // down the small write too much.
+ size_t max_size = max_write_batch_group_size_bytes;
+ const uint64_t min_batch_size_bytes = max_write_batch_group_size_bytes / 8;
+ if (size <= min_batch_size_bytes) {
+ max_size = size + min_batch_size_bytes;
+ }
+
+ leader->write_group = write_group;
+ write_group->leader = leader;
+ write_group->size = 1;
+ Writer* last_writer = leader;
+
+ if (!allow_concurrent_memtable_write_ || !leader->batch->HasMerge()) {
+ Writer* newest_writer = newest_memtable_writer_.load();
+ CreateMissingNewerLinks(newest_writer);
+
+ Writer* w = leader;
+ while (w != newest_writer) {
+ w = w->link_newer;
+
+ if (w->batch == nullptr) {
+ break;
+ }
+
+ if (w->batch->HasMerge()) {
+ break;
+ }
+
+ if (!allow_concurrent_memtable_write_) {
+ auto batch_size = WriteBatchInternal::ByteSize(w->batch);
+ if (size + batch_size > max_size) {
+ // Do not make batch too big
+ break;
+ }
+ size += batch_size;
+ }
+
+ w->write_group = write_group;
+ last_writer = w;
+ write_group->size++;
+ }
+ }
+
+ write_group->last_writer = last_writer;
+ write_group->last_sequence =
+ last_writer->sequence + WriteBatchInternal::Count(last_writer->batch) - 1;
+}
+
+void WriteThread::ExitAsMemTableWriter(Writer* /*self*/,
+ WriteGroup& write_group) {
+ Writer* leader = write_group.leader;
+ Writer* last_writer = write_group.last_writer;
+
+ Writer* newest_writer = last_writer;
+ if (!newest_memtable_writer_.compare_exchange_strong(newest_writer,
+ nullptr)) {
+ CreateMissingNewerLinks(newest_writer);
+ Writer* next_leader = last_writer->link_newer;
+ assert(next_leader != nullptr);
+ next_leader->link_older = nullptr;
+ SetState(next_leader, STATE_MEMTABLE_WRITER_LEADER);
+ }
+ Writer* w = leader;
+ while (true) {
+ if (!write_group.status.ok()) {
+ w->status = write_group.status;
+ }
+ Writer* next = w->link_newer;
+ if (w != leader) {
+ SetState(w, STATE_COMPLETED);
+ }
+ if (w == last_writer) {
+ break;
+ }
+ w = next;
+ }
+ // Note that leader has to exit last, since it owns the write group.
+ SetState(leader, STATE_COMPLETED);
+}
+
+void WriteThread::LaunchParallelMemTableWriters(WriteGroup* write_group) {
+ assert(write_group != nullptr);
+ write_group->running.store(write_group->size);
+ for (auto w : *write_group) {
+ SetState(w, STATE_PARALLEL_MEMTABLE_WRITER);
+ }
+}
+
+static WriteThread::AdaptationContext cpmtw_ctx("CompleteParallelMemTableWriter");
+// This method is called by both the leader and parallel followers
+bool WriteThread::CompleteParallelMemTableWriter(Writer* w) {
+
+ auto* write_group = w->write_group;
+ if (!w->status.ok()) {
+ std::lock_guard<std::mutex> guard(write_group->leader->StateMutex());
+ write_group->status = w->status;
+ }
+
+ if (write_group->running-- > 1) {
+ // we're not the last one
+ AwaitState(w, STATE_COMPLETED, &cpmtw_ctx);
+ return false;
+ }
+ // else we're the last parallel worker and should perform exit duties.
+ w->status = write_group->status;
+ return true;
+}
+
+void WriteThread::ExitAsBatchGroupFollower(Writer* w) {
+ auto* write_group = w->write_group;
+
+ assert(w->state == STATE_PARALLEL_MEMTABLE_WRITER);
+ assert(write_group->status.ok());
+ ExitAsBatchGroupLeader(*write_group, write_group->status);
+ assert(w->status.ok());
+ assert(w->state == STATE_COMPLETED);
+ SetState(write_group->leader, STATE_COMPLETED);
+}
+
+static WriteThread::AdaptationContext eabgl_ctx("ExitAsBatchGroupLeader");
+void WriteThread::ExitAsBatchGroupLeader(WriteGroup& write_group,
+ Status status) {
+ Writer* leader = write_group.leader;
+ Writer* last_writer = write_group.last_writer;
+ assert(leader->link_older == nullptr);
+
+ // Propagate memtable write error to the whole group.
+ if (status.ok() && !write_group.status.ok()) {
+ status = write_group.status;
+ }
+
+ if (enable_pipelined_write_) {
+ // Notify writers don't write to memtable to exit.
+ for (Writer* w = last_writer; w != leader;) {
+ Writer* next = w->link_older;
+ w->status = status;
+ if (!w->ShouldWriteToMemtable()) {
+ CompleteFollower(w, write_group);
+ }
+ w = next;
+ }
+ if (!leader->ShouldWriteToMemtable()) {
+ CompleteLeader(write_group);
+ }
+
+ Writer* next_leader = nullptr;
+
+ // Look for next leader before we call LinkGroup. If there isn't
+ // pending writers, place a dummy writer at the tail of the queue
+ // so we know the boundary of the current write group.
+ Writer dummy;
+ Writer* expected = last_writer;
+ bool has_dummy = newest_writer_.compare_exchange_strong(expected, &dummy);
+ if (!has_dummy) {
+ // We find at least one pending writer when we insert dummy. We search
+ // for next leader from there.
+ next_leader = FindNextLeader(expected, last_writer);
+ assert(next_leader != nullptr && next_leader != last_writer);
+ }
+
+ // Link the ramaining of the group to memtable writer list.
+ //
+ // We have to link our group to memtable writer queue before wake up the
+ // next leader or set newest_writer_ to null, otherwise the next leader
+ // can run ahead of us and link to memtable writer queue before we do.
+ if (write_group.size > 0) {
+ if (LinkGroup(write_group, &newest_memtable_writer_)) {
+ // The leader can now be different from current writer.
+ SetState(write_group.leader, STATE_MEMTABLE_WRITER_LEADER);
+ }
+ }
+
+ // If we have inserted dummy in the queue, remove it now and check if there
+ // are pending writer join the queue since we insert the dummy. If so,
+ // look for next leader again.
+ if (has_dummy) {
+ assert(next_leader == nullptr);
+ expected = &dummy;
+ bool has_pending_writer =
+ !newest_writer_.compare_exchange_strong(expected, nullptr);
+ if (has_pending_writer) {
+ next_leader = FindNextLeader(expected, &dummy);
+ assert(next_leader != nullptr && next_leader != &dummy);
+ }
+ }
+
+ if (next_leader != nullptr) {
+ next_leader->link_older = nullptr;
+ SetState(next_leader, STATE_GROUP_LEADER);
+ }
+ AwaitState(leader, STATE_MEMTABLE_WRITER_LEADER |
+ STATE_PARALLEL_MEMTABLE_WRITER | STATE_COMPLETED,
+ &eabgl_ctx);
+ } else {
+ Writer* head = newest_writer_.load(std::memory_order_acquire);
+ if (head != last_writer ||
+ !newest_writer_.compare_exchange_strong(head, nullptr)) {
+ // Either w wasn't the head during the load(), or it was the head
+ // during the load() but somebody else pushed onto the list before
+ // we did the compare_exchange_strong (causing it to fail). In the
+ // latter case compare_exchange_strong has the effect of re-reading
+ // its first param (head). No need to retry a failing CAS, because
+ // only a departing leader (which we are at the moment) can remove
+ // nodes from the list.
+ assert(head != last_writer);
+
+ // After walking link_older starting from head (if not already done)
+ // we will be able to traverse w->link_newer below. This function
+ // can only be called from an active leader, only a leader can
+ // clear newest_writer_, we didn't, and only a clear newest_writer_
+ // could cause the next leader to start their work without a call
+ // to MarkJoined, so we can definitely conclude that no other leader
+ // work is going on here (with or without db mutex).
+ CreateMissingNewerLinks(head);
+ assert(last_writer->link_newer->link_older == last_writer);
+ last_writer->link_newer->link_older = nullptr;
+
+ // Next leader didn't self-identify, because newest_writer_ wasn't
+ // nullptr when they enqueued (we were definitely enqueued before them
+ // and are still in the list). That means leader handoff occurs when
+ // we call MarkJoined
+ SetState(last_writer->link_newer, STATE_GROUP_LEADER);
+ }
+ // else nobody else was waiting, although there might already be a new
+ // leader now
+
+ while (last_writer != leader) {
+ last_writer->status = status;
+ // we need to read link_older before calling SetState, because as soon
+ // as it is marked committed the other thread's Await may return and
+ // deallocate the Writer.
+ auto next = last_writer->link_older;
+ SetState(last_writer, STATE_COMPLETED);
+
+ last_writer = next;
+ }
+ }
+}
+
+static WriteThread::AdaptationContext eu_ctx("EnterUnbatched");
+void WriteThread::EnterUnbatched(Writer* w, InstrumentedMutex* mu) {
+ assert(w != nullptr && w->batch == nullptr);
+ mu->Unlock();
+ bool linked_as_leader = LinkOne(w, &newest_writer_);
+ if (!linked_as_leader) {
+ TEST_SYNC_POINT("WriteThread::EnterUnbatched:Wait");
+ // Last leader will not pick us as a follower since our batch is nullptr
+ AwaitState(w, STATE_GROUP_LEADER, &eu_ctx);
+ }
+ if (enable_pipelined_write_) {
+ WaitForMemTableWriters();
+ }
+ mu->Lock();
+}
+
+void WriteThread::ExitUnbatched(Writer* w) {
+ assert(w != nullptr);
+ Writer* newest_writer = w;
+ if (!newest_writer_.compare_exchange_strong(newest_writer, nullptr)) {
+ CreateMissingNewerLinks(newest_writer);
+ Writer* next_leader = w->link_newer;
+ assert(next_leader != nullptr);
+ next_leader->link_older = nullptr;
+ SetState(next_leader, STATE_GROUP_LEADER);
+ }
+}
+
+static WriteThread::AdaptationContext wfmw_ctx("WaitForMemTableWriters");
+void WriteThread::WaitForMemTableWriters() {
+ assert(enable_pipelined_write_);
+ if (newest_memtable_writer_.load() == nullptr) {
+ return;
+ }
+ Writer w;
+ if (!LinkOne(&w, &newest_memtable_writer_)) {
+ AwaitState(&w, STATE_MEMTABLE_WRITER_LEADER, &wfmw_ctx);
+ }
+ newest_memtable_writer_.store(nullptr);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db/write_thread.h b/storage/rocksdb/rocksdb/db/write_thread.h
new file mode 100644
index 00000000..87819971
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db/write_thread.h
@@ -0,0 +1,431 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <stdint.h>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <type_traits>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/pre_release_callback.h"
+#include "db/write_callback.h"
+#include "monitoring/instrumented_mutex.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/write_batch.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteThread {
+ public:
+ enum State : uint8_t {
+ // The initial state of a writer. This is a Writer that is
+ // waiting in JoinBatchGroup. This state can be left when another
+ // thread informs the waiter that it has become a group leader
+ // (-> STATE_GROUP_LEADER), when a leader that has chosen to be
+ // non-parallel informs a follower that its writes have been committed
+ // (-> STATE_COMPLETED), or when a leader that has chosen to perform
+ // updates in parallel and needs this Writer to apply its batch (->
+ // STATE_PARALLEL_FOLLOWER).
+ STATE_INIT = 1,
+
+ // The state used to inform a waiting Writer that it has become the
+ // leader, and it should now build a write batch group. Tricky:
+ // this state is not used if newest_writer_ is empty when a writer
+ // enqueues itself, because there is no need to wait (or even to
+ // create the mutex and condvar used to wait) in that case. This is
+ // a terminal state unless the leader chooses to make this a parallel
+ // batch, in which case the last parallel worker to finish will move
+ // the leader to STATE_COMPLETED.
+ STATE_GROUP_LEADER = 2,
+
+ // The state used to inform a waiting writer that it has become the
+ // leader of memtable writer group. The leader will either write
+ // memtable for the whole group, or launch a parallel group write
+ // to memtable by calling LaunchParallelMemTableWrite.
+ STATE_MEMTABLE_WRITER_LEADER = 4,
+
+ // The state used to inform a waiting writer that it has become a
+ // parallel memtable writer. It can be the group leader who launch the
+ // parallel writer group, or one of the followers. The writer should then
+ // apply its batch to the memtable concurrently and call
+ // CompleteParallelMemTableWriter.
+ STATE_PARALLEL_MEMTABLE_WRITER = 8,
+
+ // A follower whose writes have been applied, or a parallel leader
+ // whose followers have all finished their work. This is a terminal
+ // state.
+ STATE_COMPLETED = 16,
+
+ // A state indicating that the thread may be waiting using StateMutex()
+ // and StateCondVar()
+ STATE_LOCKED_WAITING = 32,
+ };
+
+ struct Writer;
+
+ struct WriteGroup {
+ Writer* leader = nullptr;
+ Writer* last_writer = nullptr;
+ SequenceNumber last_sequence;
+ // before running goes to zero, status needs leader->StateMutex()
+ Status status;
+ std::atomic<size_t> running;
+ size_t size = 0;
+
+ struct Iterator {
+ Writer* writer;
+ Writer* last_writer;
+
+ explicit Iterator(Writer* w, Writer* last)
+ : writer(w), last_writer(last) {}
+
+ Writer* operator*() const { return writer; }
+
+ Iterator& operator++() {
+ assert(writer != nullptr);
+ if (writer == last_writer) {
+ writer = nullptr;
+ } else {
+ writer = writer->link_newer;
+ }
+ return *this;
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return writer != other.writer;
+ }
+ };
+
+ Iterator begin() const { return Iterator(leader, last_writer); }
+ Iterator end() const { return Iterator(nullptr, nullptr); }
+ };
+
+ // Information kept for every waiting writer.
+ struct Writer {
+ WriteBatch* batch;
+ bool sync;
+ bool no_slowdown;
+ bool disable_wal;
+ bool disable_memtable;
+ size_t batch_cnt; // if non-zero, number of sub-batches in the write batch
+ PreReleaseCallback* pre_release_callback;
+ uint64_t log_used; // log number that this batch was inserted into
+ uint64_t log_ref; // log number that memtable insert should reference
+ WriteCallback* callback;
+ bool made_waitable; // records lazy construction of mutex and cv
+ std::atomic<uint8_t> state; // write under StateMutex() or pre-link
+ WriteGroup* write_group;
+ SequenceNumber sequence; // the sequence number to use for the first key
+ Status status;
+ Status callback_status; // status returned by callback->Callback()
+
+ std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
+ std::aligned_storage<sizeof(std::condition_variable)>::type state_cv_bytes;
+ Writer* link_older; // read/write only before linking, or as leader
+ Writer* link_newer; // lazy, read/write only before linking, or as leader
+
+ Writer()
+ : batch(nullptr),
+ sync(false),
+ no_slowdown(false),
+ disable_wal(false),
+ disable_memtable(false),
+ batch_cnt(0),
+ pre_release_callback(nullptr),
+ log_used(0),
+ log_ref(0),
+ callback(nullptr),
+ made_waitable(false),
+ state(STATE_INIT),
+ write_group(nullptr),
+ sequence(kMaxSequenceNumber),
+ link_older(nullptr),
+ link_newer(nullptr) {}
+
+ Writer(const WriteOptions& write_options, WriteBatch* _batch,
+ WriteCallback* _callback, uint64_t _log_ref, bool _disable_memtable,
+ size_t _batch_cnt = 0,
+ PreReleaseCallback* _pre_release_callback = nullptr)
+ : batch(_batch),
+ sync(write_options.sync),
+ no_slowdown(write_options.no_slowdown),
+ disable_wal(write_options.disableWAL),
+ disable_memtable(_disable_memtable),
+ batch_cnt(_batch_cnt),
+ pre_release_callback(_pre_release_callback),
+ log_used(0),
+ log_ref(_log_ref),
+ callback(_callback),
+ made_waitable(false),
+ state(STATE_INIT),
+ write_group(nullptr),
+ sequence(kMaxSequenceNumber),
+ link_older(nullptr),
+ link_newer(nullptr) {}
+
+ ~Writer() {
+ if (made_waitable) {
+ StateMutex().~mutex();
+ StateCV().~condition_variable();
+ }
+ }
+
+ bool CheckCallback(DB* db) {
+ if (callback != nullptr) {
+ callback_status = callback->Callback(db);
+ }
+ return callback_status.ok();
+ }
+
+ void CreateMutex() {
+ if (!made_waitable) {
+ // Note that made_waitable is tracked separately from state
+ // transitions, because we can't atomically create the mutex and
+ // link into the list.
+ made_waitable = true;
+ new (&state_mutex_bytes) std::mutex;
+ new (&state_cv_bytes) std::condition_variable;
+ }
+ }
+
+ // returns the aggregate status of this Writer
+ Status FinalStatus() {
+ if (!status.ok()) {
+ // a non-ok memtable write status takes presidence
+ assert(callback == nullptr || callback_status.ok());
+ return status;
+ } else if (!callback_status.ok()) {
+ // if the callback failed then that is the status we want
+ // because a memtable insert should not have been attempted
+ assert(callback != nullptr);
+ assert(status.ok());
+ return callback_status;
+ } else {
+ // if there is no callback then we only care about
+ // the memtable insert status
+ assert(callback == nullptr || callback_status.ok());
+ return status;
+ }
+ }
+
+ bool CallbackFailed() {
+ return (callback != nullptr) && !callback_status.ok();
+ }
+
+ bool ShouldWriteToMemtable() {
+ return status.ok() && !CallbackFailed() && !disable_memtable;
+ }
+
+ bool ShouldWriteToWAL() {
+ return status.ok() && !CallbackFailed() && !disable_wal;
+ }
+
+ // No other mutexes may be acquired while holding StateMutex(), it is
+ // always last in the order
+ std::mutex& StateMutex() {
+ assert(made_waitable);
+ return *static_cast<std::mutex*>(static_cast<void*>(&state_mutex_bytes));
+ }
+
+ std::condition_variable& StateCV() {
+ assert(made_waitable);
+ return *static_cast<std::condition_variable*>(
+ static_cast<void*>(&state_cv_bytes));
+ }
+ };
+
+ struct AdaptationContext {
+ const char* name;
+ std::atomic<int32_t> value;
+
+ explicit AdaptationContext(const char* name0) : name(name0), value(0) {}
+ };
+
+ explicit WriteThread(const ImmutableDBOptions& db_options);
+
+ virtual ~WriteThread() = default;
+
+ // IMPORTANT: None of the methods in this class rely on the db mutex
+ // for correctness. All of the methods except JoinBatchGroup and
+ // EnterUnbatched may be called either with or without the db mutex held.
+ // Correctness is maintained by ensuring that only a single thread is
+ // a leader at a time.
+
+ // Registers w as ready to become part of a batch group, waits until the
+ // caller should perform some work, and returns the current state of the
+ // writer. If w has become the leader of a write batch group, returns
+ // STATE_GROUP_LEADER. If w has been made part of a sequential batch
+ // group and the leader has performed the write, returns STATE_DONE.
+ // If w has been made part of a parallel batch group and is responsible
+ // for updating the memtable, returns STATE_PARALLEL_FOLLOWER.
+ //
+ // The db mutex SHOULD NOT be held when calling this function, because
+ // it will block.
+ //
+ // Writer* w: Writer to be executed as part of a batch group
+ void JoinBatchGroup(Writer* w);
+
+ // Constructs a write batch group led by leader, which should be a
+ // Writer passed to JoinBatchGroup on the current thread.
+ //
+ // Writer* leader: Writer that is STATE_GROUP_LEADER
+ // WriteGroup* write_group: Out-param of group members
+ // returns: Total batch group byte size
+ size_t EnterAsBatchGroupLeader(Writer* leader, WriteGroup* write_group);
+
+ // Unlinks the Writer-s in a batch group, wakes up the non-leaders,
+ // and wakes up the next leader (if any).
+ //
+ // WriteGroup* write_group: the write group
+ // Status status: Status of write operation
+ void ExitAsBatchGroupLeader(WriteGroup& write_group, Status status);
+
+ // Exit batch group on behalf of batch group leader.
+ void ExitAsBatchGroupFollower(Writer* w);
+
+ // Constructs a write batch group led by leader from newest_memtable_writers_
+ // list. The leader should either write memtable for the whole group and
+ // call ExitAsMemTableWriter, or launch parallel memtable write through
+ // LaunchParallelMemTableWriters.
+ void EnterAsMemTableWriter(Writer* leader, WriteGroup* write_grup);
+
+ // Memtable writer group leader, or the last finished writer in a parallel
+ // write group, exit from the newest_memtable_writers_ list, and wake up
+ // the next leader if needed.
+ void ExitAsMemTableWriter(Writer* self, WriteGroup& write_group);
+
+ // Causes JoinBatchGroup to return STATE_PARALLEL_FOLLOWER for all of the
+ // non-leader members of this write batch group. Sets Writer::sequence
+ // before waking them up.
+ //
+ // WriteGroup* write_group: Extra state used to coordinate the parallel add
+ void LaunchParallelMemTableWriters(WriteGroup* write_group);
+
+ // Reports the completion of w's batch to the parallel group leader, and
+ // waits for the rest of the parallel batch to complete. Returns true
+ // if this thread is the last to complete, and hence should advance
+ // the sequence number and then call EarlyExitParallelGroup, false if
+ // someone else has already taken responsibility for that.
+ bool CompleteParallelMemTableWriter(Writer* w);
+
+ // Waits for all preceding writers (unlocking mu while waiting), then
+ // registers w as the currently proceeding writer.
+ //
+ // Writer* w: A Writer not eligible for batching
+ // InstrumentedMutex* mu: The db mutex, to unlock while waiting
+ // REQUIRES: db mutex held
+ void EnterUnbatched(Writer* w, InstrumentedMutex* mu);
+
+ // Completes a Writer begun with EnterUnbatched, unblocking subsequent
+ // writers.
+ void ExitUnbatched(Writer* w);
+
+ // Wait for all parallel memtable writers to finish, in case pipelined
+ // write is enabled.
+ void WaitForMemTableWriters();
+
+ SequenceNumber UpdateLastSequence(SequenceNumber sequence) {
+ if (sequence > last_sequence_) {
+ last_sequence_ = sequence;
+ }
+ return last_sequence_;
+ }
+
+ // Insert a dummy writer at the tail of the write queue to indicate a write
+ // stall, and fail any writers in the queue with no_slowdown set to true
+ void BeginWriteStall();
+
+ // Remove the dummy writer and wake up waiting writers
+ void EndWriteStall();
+
+ private:
+ // See AwaitState.
+ const uint64_t max_yield_usec_;
+ const uint64_t slow_yield_usec_;
+
+ // Allow multiple writers write to memtable concurrently.
+ const bool allow_concurrent_memtable_write_;
+
+ // Enable pipelined write to WAL and memtable.
+ const bool enable_pipelined_write_;
+
+ // 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.
+ const uint64_t max_write_batch_group_size_bytes;
+
+ // Points to the newest pending writer. Only leader can remove
+ // elements, adding can be done lock-free by anybody.
+ std::atomic<Writer*> newest_writer_;
+
+ // Points to the newest pending memtable writer. Used only when pipelined
+ // write is enabled.
+ std::atomic<Writer*> newest_memtable_writer_;
+
+ // The last sequence that have been consumed by a writer. The sequence
+ // is not necessary visible to reads because the writer can be ongoing.
+ SequenceNumber last_sequence_;
+
+ // A dummy writer to indicate a write stall condition. This will be inserted
+ // at the tail of the writer queue by the leader, so newer writers can just
+ // check for this and bail
+ Writer write_stall_dummy_;
+
+ // Mutex and condvar for writers to block on a write stall. During a write
+ // stall, writers with no_slowdown set to false will wait on this rather
+ // on the writer queue
+ port::Mutex stall_mu_;
+ port::CondVar stall_cv_;
+
+ // Waits for w->state & goal_mask using w->StateMutex(). Returns
+ // the state that satisfies goal_mask.
+ uint8_t BlockingAwaitState(Writer* w, uint8_t goal_mask);
+
+ // Blocks until w->state & goal_mask, returning the state value
+ // that satisfied the predicate. Uses ctx to adaptively use
+ // std::this_thread::yield() to avoid mutex overheads. ctx should be
+ // a context-dependent static.
+ uint8_t AwaitState(Writer* w, uint8_t goal_mask, AdaptationContext* ctx);
+
+ // Set writer state and wake the writer up if it is waiting.
+ void SetState(Writer* w, uint8_t new_state);
+
+ // Links w into the newest_writer list. Return true if w was linked directly
+ // into the leader position. Safe to call from multiple threads without
+ // external locking.
+ bool LinkOne(Writer* w, std::atomic<Writer*>* newest_writer);
+
+ // Link write group into the newest_writer list as a whole, while keeping the
+ // order of the writers unchanged. Return true if the group was linked
+ // directly into the leader position.
+ bool LinkGroup(WriteGroup& write_group, std::atomic<Writer*>* newest_writer);
+
+ // Computes any missing link_newer links. Should not be called
+ // concurrently with itself.
+ void CreateMissingNewerLinks(Writer* head);
+
+ // Starting from a pending writer, follow link_older to search for next
+ // leader, until we hit boundary.
+ Writer* FindNextLeader(Writer* pending_writer, Writer* boundary);
+
+ // Set the leader in write_group to completed state and remove it from the
+ // write group.
+ void CompleteLeader(WriteGroup& write_group);
+
+ // Set a follower in write_group to completed state and remove it from the
+ // write group.
+ void CompleteFollower(Writer* w, WriteGroup& write_group);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/CMakeLists.txt b/storage/rocksdb/rocksdb/db_stress_tool/CMakeLists.txt
new file mode 100644
index 00000000..219a5a75
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_executable(db_stress${ARTIFACT_SUFFIX}
+ db_stress.cc
+ db_stress_tool.cc
+ batched_ops_stress.cc
+ cf_consistency_stress.cc
+ db_stress_common.cc
+ db_stress_driver.cc
+ db_stress_test_base.cc
+ db_stress_shared_state.cc
+ db_stress_gflags.cc
+ db_stress_tool.cc
+ no_batched_ops_stress.cc)
+target_link_libraries(db_stress${ARTIFACT_SUFFIX} ${ROCKSDB_LIB})
+list(APPEND tool_deps db_stress)
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/batched_ops_stress.cc b/storage/rocksdb/rocksdb/db_stress_tool/batched_ops_stress.cc
new file mode 100644
index 00000000..ea1fab56
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/batched_ops_stress.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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() {}
+
+ // Given a key K and value V, this puts ("0"+K, "0"+V), ("1"+K, "1"+V), ...
+ // ("9"+K, "9"+V) 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys, char (&value)[100],
+ std::unique_ptr<MutexLock>& /* lock */) override {
+ uint32_t value_base =
+ thread->rand.Next() % thread->shared->UNKNOWN_SENTINEL;
+ size_t sz = GenerateValue(value_base, value, sizeof(value));
+ Slice v(value, sz);
+ std::string keys[10] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
+ std::string values[10] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
+ Slice value_slices[10];
+ WriteBatch batch;
+ 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;
+ values[i] += v.ToString();
+ value_slices[i] = values[i];
+ if (FLAGS_use_merge) {
+ batch.Merge(cfh, keys[i], value_slices[i]);
+ } else {
+ batch.Put(cfh, keys[i], value_slices[i]);
+ }
+ }
+
+ 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& /* lock */) override {
+ std::string keys[10] = {"9", "7", "5", "3", "1", "8", "6", "4", "2", "0"};
+
+ WriteBatch batch;
+ 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<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */,
+ std::unique_ptr<MutexLock>& /* lock */) override {
+ assert(false);
+ return Status::NotSupported(
+ "BatchedOpsStressTest does not support "
+ "TestDeleteRange");
+ }
+
+ void TestIngestExternalFile(
+ ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */,
+ std::unique_ptr<MutexLock>& /* lock */) 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
+ // "0"+V, "1"+V,..."9"+V.
+ // ASSUMES that BatchedOpsStressTest::TestPut was used to put (K, V) into
+ // the DB.
+ Status TestGet(ThreadState* thread, const ReadOptions& readoptions,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& 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;
+
+ char expected_prefix = (keys[i])[0];
+ char actual_prefix = (values[i])[0];
+ if (actual_prefix != expected_prefix) {
+ fprintf(stderr, "error expected prefix = %c actual = %c\n",
+ expected_prefix, actual_prefix);
+ }
+ (values[i])[0] = ' '; // blank out 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, "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<Status> TestMultiGet(
+ ThreadState* thread, const ReadOptions& readoptions,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ size_t num_keys = rand_keys.size();
+ std::vector<Status> ret_status(num_keys);
+ std::array<std::string, 10> 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<Slice> key_slices;
+ std::vector<PinnableSlice> values(num_prefixes);
+ std::vector<Status> statuses(num_prefixes);
+ ReadOptions readoptionscopy = readoptions;
+ readoptionscopy.snapshot = db_->GetSnapshot();
+ std::vector<std::string> 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, "get 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 {
+ char expected_prefix = (keys[i])[0];
+ char actual_prefix = (values[i])[0];
+ if (actual_prefix != expected_prefix) {
+ fprintf(stderr, "error expected prefix = %c actual = %c\n",
+ expected_prefix, actual_prefix);
+ }
+ std::string str;
+ str.assign(values[i].data(), values[i].size());
+ values[i].Reset();
+ str[0] = ' '; // blank out the differing character
+ values[i].PinSelf(str);
+ 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, "error : inconsistent values for key %s: %s, %s\n",
+ 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;
+ }
+
+ // 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 "0"+V, "1"+V,..."9"+V.
+ // ASSUMES that MultiPut was used to put (K, V)
+ Status TestPrefixScan(ThreadState* thread, const ReadOptions& readoptions,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ size_t prefix_to_use =
+ (FLAGS_prefix_size < 0) ? 7 : static_cast<size_t>(FLAGS_prefix_size);
+ std::string key_str = Key(rand_keys[0]);
+ Slice key = key_str;
+ auto cfh = column_families_[rand_column_families[0]];
+ std::string prefixes[10] = {"0", "1", "2", "3", "4",
+ "5", "6", "7", "8", "9"};
+ Slice prefix_slices[10];
+ ReadOptions readoptionscopy[10];
+ const Snapshot* snapshot = db_->GetSnapshot();
+ Iterator* iters[10];
+ std::string upper_bounds[10];
+ Slice ub_slices[10];
+ Status s = Status::OK();
+ for (int i = 0; i < 10; i++) {
+ prefixes[i] += key.ToString();
+ prefixes[i].resize(prefix_to_use);
+ prefix_slices[i] = Slice(prefixes[i]);
+ readoptionscopy[i] = readoptions;
+ readoptionscopy[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] = Slice(upper_bounds[i]);
+ readoptionscopy[i].iterate_upper_bound = &(ub_slices[i]);
+ }
+ iters[i] = db_->NewIterator(readoptionscopy[i], cfh);
+ iters[i]->Seek(prefix_slices[i]);
+ }
+
+ long count = 0;
+ while (iters[0]->Valid() && iters[0]->key().starts_with(prefix_slices[0])) {
+ count++;
+ std::string values[10];
+ // get list of all values for this iteration
+ for (int i = 0; i < 10; 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();
+
+ char expected_first = (prefixes[i])[0];
+ char actual_first = (values[i])[0];
+
+ if (actual_first != expected_first) {
+ fprintf(stderr, "error expected first = %c actual = %c\n",
+ expected_first, actual_first);
+ }
+ (values[i])[0] = ' '; // blank out the differing character
+ }
+ // make sure all values are equivalent
+ for (int i = 0; i < 10; i++) {
+ if (values[i] != values[0]) {
+ fprintf(stderr,
+ "error : %d, inconsistent values for prefix %s: %s, %s\n", i,
+ prefixes[i].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
+ }
+ iters[i]->Next();
+ }
+ }
+
+ // cleanup iterators and snapshot
+ for (int i = 0; i < 10; 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());
+ delete iters[i];
+ }
+ db_->ReleaseSnapshot(snapshot);
+
+ if (s.ok()) {
+ thread->stats.AddPrefixes(1, count);
+ } else {
+ fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ }
+
+ return s;
+ }
+
+ void VerifyDb(ThreadState* /* thread */) const override {}
+};
+
+StressTest* CreateBatchedOpsStressTest() { return new BatchedOpsStressTest(); }
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/cf_consistency_stress.cc b/storage/rocksdb/rocksdb/db_stress_tool/cf_consistency_stress.cc
new file mode 100644
index 00000000..ac9c014f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/cf_consistency_stress.cc
@@ -0,0 +1,583 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 CfConsistencyStressTest : public StressTest {
+ public:
+ CfConsistencyStressTest() : batch_id_(0) {}
+
+ ~CfConsistencyStressTest() override {}
+
+ Status TestPut(ThreadState* thread, WriteOptions& write_opts,
+ const ReadOptions& /* read_opts */,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys, char (&value)[100],
+ std::unique_ptr<MutexLock>& /* lock */) override {
+ std::string key_str = Key(rand_keys[0]);
+ Slice key = key_str;
+ uint64_t value_base = batch_id_.fetch_add(1);
+ size_t sz =
+ GenerateValue(static_cast<uint32_t>(value_base), value, sizeof(value));
+ Slice v(value, sz);
+ WriteBatch batch;
+ for (auto cf : rand_column_families) {
+ ColumnFamilyHandle* cfh = column_families_[cf];
+ if (FLAGS_use_merge) {
+ batch.Merge(cfh, key, v);
+ } else { /* !FLAGS_use_merge */
+ batch.Put(cfh, key, 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<long>(rand_column_families.size());
+ thread->stats.AddBytesForWrites(num, (sz + 1) * num);
+ }
+
+ return s;
+ }
+
+ Status TestDelete(ThreadState* thread, WriteOptions& write_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& /* lock */) 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<long>(rand_column_families.size()));
+ }
+ return s;
+ }
+
+ Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& /* lock */) 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<long>(rand_column_families.size()));
+ }
+ return s;
+ }
+
+ void TestIngestExternalFile(
+ ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */,
+ std::unique_ptr<MutexLock>& /* lock */) 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<int>& rand_column_families,
+ const std::vector<int64_t>& 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<Status> TestMultiGet(
+ ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ size_t num_keys = rand_keys.size();
+ std::vector<std::string> key_str;
+ std::vector<Slice> keys;
+ keys.reserve(num_keys);
+ key_str.reserve(num_keys);
+ std::vector<PinnableSlice> values(num_keys);
+ std::vector<Status> statuses(num_keys);
+ ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]];
+
+ 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(read_opts, 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;
+ }
+
+ Status TestPrefixScan(ThreadState* thread, const ReadOptions& readoptions,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ size_t prefix_to_use =
+ (FLAGS_prefix_size < 0) ? 7 : static_cast<size_t>(FLAGS_prefix_size);
+
+ std::string key_str = Key(rand_keys[0]);
+ Slice key = key_str;
+ Slice prefix = Slice(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;
+ }
+ auto cfh =
+ column_families_[rand_column_families[thread->rand.Next() %
+ rand_column_families.size()]];
+ Iterator* iter = db_->NewIterator(ro_copy, cfh);
+ unsigned long count = 0;
+ for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix);
+ iter->Next()) {
+ ++count;
+ }
+ assert(prefix_to_use == 0 ||
+ count <= GetPrefixKeyCount(prefix.ToString(), upper_bound));
+ Status s = iter->status();
+ if (s.ok()) {
+ thread->stats.AddPrefixes(1, count);
+ } else {
+ fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ }
+ delete iter;
+ return s;
+ }
+
+ 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()];
+ }
+
+#ifdef ROCKSDB_LITE
+ Status TestCheckpoint(ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */) override {
+ assert(false);
+ fprintf(stderr,
+ "RocksDB lite does not support "
+ "TestCheckpoint\n");
+ std::terminate();
+ }
+#else
+ Status TestCheckpoint(ThreadState* thread,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */) override {
+ std::string checkpoint_dir =
+ FLAGS_db + "/.checkpoint" + ToString(thread->tid);
+
+ // We need to clear DB including manifest files, so make a copy
+ Options opt_copy = options_;
+ opt_copy.env = db_stress_env->target();
+ DestroyDB(checkpoint_dir, opt_copy);
+
+ Checkpoint* checkpoint = nullptr;
+ Status s = Checkpoint::Create(db_, &checkpoint);
+ if (s.ok()) {
+ s = checkpoint->CreateCheckpoint(checkpoint_dir);
+ }
+ std::vector<ColumnFamilyHandle*> cf_handles;
+ DB* checkpoint_db = nullptr;
+ if (s.ok()) {
+ delete checkpoint;
+ checkpoint = nullptr;
+ Options options(options_);
+ options.listeners.clear();
+ std::vector<ColumnFamilyDescriptor> 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_`.
+ 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) {
+ for (auto cfh : cf_handles) {
+ delete cfh;
+ }
+ cf_handles.clear();
+ delete checkpoint_db;
+ checkpoint_db = nullptr;
+ }
+ DestroyDB(checkpoint_dir, opt_copy);
+ if (!s.ok()) {
+ fprintf(stderr, "A checkpoint operation failed with: %s\n",
+ s.ToString().c_str());
+ }
+ return s;
+ }
+#endif // !ROCKSDB_LITE
+
+ void VerifyDb(ThreadState* thread) const override {
+ 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;
+ const auto ss_deleter = [this](const Snapshot* ss) {
+ db_->ReleaseSnapshot(ss);
+ };
+ std::unique_ptr<const Snapshot, decltype(ss_deleter)> snapshot_guard(
+ db_->GetSnapshot(), ss_deleter);
+ options.snapshot = snapshot_guard.get();
+ assert(thread != nullptr);
+ auto shared = thread->shared;
+ std::vector<std::unique_ptr<Iterator>> iters(column_families_.size());
+ for (size_t i = 0; i != column_families_.size(); ++i) {
+ iters[i].reset(db_->NewIterator(options, column_families_[i]));
+ }
+ for (auto& iter : iters) {
+ iter->SeekToFirst();
+ }
+ size_t num = column_families_.size();
+ assert(num == iters.size());
+ std::vector<Status> statuses(num, Status::OK());
+ do {
+ if (shared->HasVerificationFailedYet()) {
+ break;
+ }
+ size_t valid_cnt = 0;
+ size_t idx = 0;
+ for (auto& iter : iters) {
+ if (iter->Valid()) {
+ ++valid_cnt;
+ } else {
+ statuses[idx] = iter->status();
+ }
+ ++idx;
+ }
+ if (valid_cnt == 0) {
+ Status status;
+ for (size_t i = 0; i != num; ++i) {
+ const auto& s = statuses[i];
+ if (!s.ok()) {
+ status = s;
+ fprintf(stderr, "Iterator on cf %s has error: %s\n",
+ column_families_[i]->GetName().c_str(),
+ s.ToString().c_str());
+ shared->SetVerificationFailure();
+ }
+ }
+ break;
+ } else if (valid_cnt != iters.size()) {
+ shared->SetVerificationFailure();
+ for (size_t i = 0; i != num; ++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.
+ if (shared->PrintingVerificationResults()) {
+ continue;
+ }
+ Slice key;
+ Slice value;
+ int num_mismatched_cfs = 0;
+ for (size_t i = 0; i != num; ++i) {
+ if (i == 0) {
+ key = iters[i]->key();
+ value = iters[i]->value();
+ } else {
+ 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());
+#ifndef ROCKSDB_LITE
+ 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;
+ }
+ std::vector<KeyVersion> versions;
+ const size_t kMaxNumIKeys = 8;
+ const auto print_key_versions = [&](ColumnFamilyHandle* cfh) {
+ Status s = GetAllKeyVersions(db_, cfh, begin_key, end_key,
+ kMaxNumIKeys, &versions);
+ if (!s.ok()) {
+ fprintf(stderr, "%s\n", s.ToString().c_str());
+ return;
+ }
+ assert(nullptr != 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]);
+#endif // ROCKSDB_LITE
+ shared->SetVerificationFailure();
+ }
+ }
+ }
+ shared->FinishPrintingVerificationResults();
+ for (auto& iter : iters) {
+ iter->Next();
+ }
+ } while (true);
+ }
+
+#ifndef ROCKSDB_LITE
+ 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_;
+ const auto ss_deleter = [&](const Snapshot* ss) {
+ db_ptr->ReleaseSnapshot(ss);
+ };
+ std::unique_ptr<const Snapshot, decltype(ss_deleter)> snapshot_guard(
+ db_ptr->GetSnapshot(), ss_deleter);
+ if (cmp_db_) {
+ status = cmp_db_->TryCatchUpWithPrimary();
+ }
+ SharedState* shared = thread->shared;
+ assert(shared);
+ if (!status.ok()) {
+ shared->SetShouldStopTest();
+ return;
+ }
+ assert(cmp_db_ || snapshot_guard.get());
+ 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();
+ };
+ ReadOptions ropts;
+ ropts.total_order_seek = true;
+ ropts.snapshot = snapshot_guard.get();
+ uint32_t crc = 0;
+ {
+ // Compute crc for all key-values of default column family.
+ std::unique_ptr<Iterator> it(db_ptr->NewIterator(ropts));
+ status = checksum_column_family(it.get(), &crc);
+ }
+ uint32_t tmp_crc = 0;
+ if (status.ok()) {
+ for (ColumnFamilyHandle* cfh : cfhs) {
+ if (cfh == db_ptr->DefaultColumnFamily()) {
+ continue;
+ }
+ std::unique_ptr<Iterator> it(db_ptr->NewIterator(ropts, cfh));
+ status = checksum_column_family(it.get(), &tmp_crc);
+ if (!status.ok() || tmp_crc != crc) {
+ break;
+ }
+ }
+ }
+ if (!status.ok() || tmp_crc != crc) {
+ shared->SetShouldStopTest();
+ }
+ }
+#endif // !ROCKSDB_LITE
+
+ std::vector<int> GenerateColumnFamilies(
+ const int /* num_column_families */,
+ int /* rand_column_family */) const override {
+ std::vector<int> ret;
+ int num = static_cast<int>(column_families_.size());
+ int k = 0;
+ std::generate_n(back_inserter(ret), num, [&k]() -> int { return k++; });
+ return ret;
+ }
+
+ private:
+ std::atomic<int64_t> batch_id_;
+};
+
+StressTest* CreateCfConsistencyStressTest() {
+ return new CfConsistencyStressTest();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress.cc
new file mode 100644
index 00000000..e03e9678
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+#include <rocksdb/db_stress_tool.h>
+
+int main(int argc, char** argv) {
+ return ROCKSDB_NAMESPACE::db_stress_tool(argc, argv);
+}
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc
new file mode 100644
index 00000000..953cfe50
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cmath>
+
+ROCKSDB_NAMESPACE::DbStressEnvWrapper* db_stress_env = nullptr;
+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<double> sum_probs(100001);
+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<double>(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<double>(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<ThreadState*>(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<ThreadState*>(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);
+ }
+ fprintf(stdout, "[CF %d] %" PRIi64 " == > (%" ROCKSDB_PRIszt ") %s\n", cf,
+ 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<double>(iteration) / FLAGS_ops_per_thread;
+ const int64_t base_key = static_cast<int64_t>(
+ 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<double>(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<int64_t> GenerateNKeys(ThreadState* thread, int num_keys,
+ uint64_t iteration) {
+ const double completed_ratio =
+ static_cast<double>(iteration) / FLAGS_ops_per_thread;
+ const int64_t base_key = static_cast<int64_t>(
+ completed_ratio * (FLAGS_max_key - FLAGS_active_width));
+ std::vector<int64_t> 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<double>(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;
+ *((uint32_t*)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.
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.h
new file mode 100644
index 00000000..40080550
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_common.h
@@ -0,0 +1,511 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cinttypes>
+#include <exception>
+#include <queue>
+#include <thread>
+
+#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 "hdfs/env_hdfs.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/backupable_db.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 "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"
+// 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 "test_util/testutil.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_bool(test_cf_consistency);
+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(compaction_style);
+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_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_and_wal_files_one_in);
+DECLARE_int32(set_options_one_in);
+DECLARE_int32(set_in_place_one_in);
+DECLARE_int64(cache_size);
+DECLARE_bool(cache_index_and_filter_blocks);
+DECLARE_bool(use_clock_cache);
+DECLARE_uint64(subcompactions);
+DECLARE_uint64(periodic_compaction_seconds);
+DECLARE_uint64(compaction_ttl);
+DECLARE_bool(allow_concurrent_memtable_write);
+DECLARE_bool(enable_write_thread_adaptive_yield);
+DECLARE_int32(reopen);
+DECLARE_double(bloom_bits);
+DECLARE_bool(use_block_based_filter);
+DECLARE_bool(partition_filters);
+DECLARE_int32(index_type);
+DECLARE_string(db);
+DECLARE_string(secondaries_base);
+DECLARE_bool(test_secondary);
+DECLARE_string(expected_values_path);
+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(statistics);
+DECLARE_bool(sync);
+DECLARE_bool(use_fsync);
+DECLARE_int32(kill_random_test);
+DECLARE_string(kill_prefix_blacklist);
+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(use_txn);
+DECLARE_uint64(txn_write_policy);
+DECLARE_bool(unordered_write);
+DECLARE_int32(backup_one_in);
+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(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_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_string(compression_type);
+DECLARE_string(bottommost_compression_type);
+DECLARE_int32(compression_max_dict_bytes);
+DECLARE_int32(compression_zstd_max_train_bytes);
+DECLARE_string(checksum_type);
+DECLARE_string(hdfs);
+DECLARE_string(env_uri);
+DECLARE_uint64(ops_per_thread);
+DECLARE_uint64(log2_keys_per_lock);
+DECLARE_uint64(max_manifest_file_size);
+DECLARE_bool(in_place_update);
+DECLARE_int32(secondary_catch_up_one_in);
+DECLARE_string(memtablerep);
+DECLARE_int32(prefix_size);
+DECLARE_bool(use_merge);
+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_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);
+
+#ifndef ROCKSDB_LITE
+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);
+#endif // !ROCKSDB_LITE
+DECLARE_int32(approximate_size_one_in);
+
+const long KB = 1024;
+const int kRandomValueMaxFactor = 3;
+const int kValueMaxLen = 100;
+
+// wrapped posix or hdfs environment
+extern ROCKSDB_NAMESPACE::DbStressEnvWrapper* db_stress_env;
+
+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<std::string, ROCKSDB_NAMESPACE::ChecksumType>&
+ 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<std::string> SplitString(std::string src) {
+ std::vector<std::string> 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<int>(ret.size()) - 1; i >= 0; i--) {
+ if (ret[i] != static_cast<char>(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
+
+// convert long to a big-endian slice key
+extern inline std::string GetStringFromInt(int64_t val) {
+ std::string little_endian_key;
+ std::string big_endian_key;
+ PutFixed64(&little_endian_key, val);
+ assert(little_endian_key.size() == sizeof(val));
+ big_endian_key.resize(sizeof(val));
+ for (size_t i = 0; i < sizeof(val); ++i) {
+ big_endian_key[i] = little_endian_key[sizeof(val) - 1 - i];
+ }
+ return big_endian_key;
+}
+
+// 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<uint64_t> 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;
+
+ for (size_t level = 0; level < levels; ++level) {
+ uint64_t weight = key_gen_ctx.weights[level];
+ uint64_t offset = static_cast<uint64_t>(val) % window;
+ uint64_t mult = static_cast<uint64_t>(val) / window;
+ uint64_t pfx = mult * weight + (offset >= weight ? weight - 1 : offset);
+ key.append(GetStringFromInt(pfx));
+ 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;
+ }
+ val = offset - weight;
+ window -= 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<uint64_t> prefixes;
+
+ assert(size_key <= key_gen_ctx.weights.size() * sizeof(uint64_t));
+
+ // 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_key.append(pad, '\0');
+ size_key += pad;
+ }
+
+ std::string little_endian_key;
+ little_endian_key.resize(size_key);
+ for (size_t start = 0; start < 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];
+ }
+ *key_p = key;
+ return true;
+}
+
+extern inline uint64_t GetPrefixKeyCount(const std::string& prefix,
+ const std::string& ub) {
+ uint64_t start = 0;
+ uint64_t end = 0;
+
+ if (!GetIntVal(prefix, &start) || !GetIntVal(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;
+}
+
+// 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 PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz);
+
+extern int64_t GenerateOneKey(ThreadState* thread, uint64_t iteration);
+
+extern std::vector<int64_t> GenerateNKeys(ThreadState* thread, int num_keys,
+ uint64_t iteration);
+
+extern size_t GenerateValue(uint32_t rand, char* v, size_t max_sz);
+
+extern StressTest* CreateCfConsistencyStressTest();
+extern StressTest* CreateBatchedOpsStressTest();
+extern StressTest* CreateNonBatchedOpsStressTest();
+extern void InitializeHotKeyGenerator(double alpha);
+extern int64_t GetOneHotKeyID(double rand_seed, int64_t max_key);
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc
new file mode 100644
index 00000000..59e019c6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 {
+void ThreadBody(void* v) {
+ ThreadState* thread = reinterpret_cast<ThreadState*>(v);
+ SharedState* shared = thread->shared;
+
+ if (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();
+ }
+ }
+
+ thread->shared->GetStressTest()->VerifyDb(thread);
+
+ {
+ MutexLock l(shared->GetMutex());
+ shared->IncDone();
+ if (shared->AllDone()) {
+ shared->GetCondVar()->SignalAll();
+ }
+ }
+}
+
+bool RunStressTest(StressTest* stress) {
+ stress->InitDb();
+
+ SharedState shared(db_stress_env, stress);
+ if (FLAGS_read_only) {
+ stress->InitReadonlyDb(&shared);
+ }
+
+ uint32_t n = shared.GetNumThreads();
+
+ uint64_t now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Initializing worker threads\n",
+ db_stress_env->TimeToString(now / 1000000).c_str());
+ std::vector<ThreadState*> 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");
+ }
+ }
+
+ now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Starting database operations\n",
+ db_stress_env->TimeToString(now / 1000000).c_str());
+
+ shared.SetStart();
+ shared.GetCondVar()->SignalAll();
+ while (!shared.AllOperated()) {
+ shared.GetCondVar()->Wait();
+ }
+
+ now = db_stress_env->NowMicros();
+ if (FLAGS_test_batches_snapshots) {
+ fprintf(stdout, "%s Limited verification already done during gets\n",
+ db_stress_env->TimeToString((uint64_t)now / 1000000).c_str());
+ } else {
+ fprintf(stdout, "%s Starting verification\n",
+ db_stress_env->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 = db_stress_env->NowMicros();
+ if (!FLAGS_test_batches_snapshots && !shared.HasVerificationFailedYet()) {
+ fprintf(stdout, "%s Verification successful\n",
+ db_stress_env->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 (!stress->VerifySecondaries()) {
+ return false;
+ }
+
+ if (shared.HasVerificationFailedYet()) {
+ fprintf(stderr, "Verification failed :(\n");
+ return false;
+ }
+ return true;
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.h
new file mode 100644
index 00000000..ff701fcb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_driver.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_test_base.h"
+namespace ROCKSDB_NAMESPACE {
+extern void ThreadBody(void* /*thread_state*/);
+extern bool RunStressTest(StressTest*);
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_env_wrapper.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_env_wrapper.h
new file mode 100644
index 00000000..41115069
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_env_wrapper.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 DbStressEnvWrapper : public EnvWrapper {
+ public:
+ explicit DbStressEnvWrapper(Env* t) : EnvWrapper(t) {}
+
+ Status DeleteFile(const std::string& f) 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 directory needs to be exempted.
+ if (!if_preserve_all_manifests ||
+ f.find("MANIFEST-") == std::string::npos ||
+ f.find("checkpoint") != std::string::npos) {
+ return target()->DeleteFile(f);
+ }
+ return Status::OK();
+ }
+
+ // 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/storage/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc
new file mode 100644
index 00000000..c12a9a29
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc
@@ -0,0 +1,655 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<uint32_t>::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");
+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_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_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<int32_t>(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, -1,
+ "Number of bytes to use as a cache of compressed data."
+ " Negative means use default settings.");
+
+DEFINE_int32(compaction_style, ROCKSDB_NAMESPACE::Options().compaction_style,
+ "");
+
+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<int32_t>(
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_size),
+ "Number of bytes in a block.");
+
+DEFINE_int32(format_version,
+ static_cast<int32_t>(
+ 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_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_and_wal_files_one_in, 1000000,
+ "With a chance of 1/N, call GetLiveFiles, GetSortedWalFiles "
+ "and GetCurrentWalFile to verify if it returns correctly. If "
+ "N == 0, never call the three interfaces.");
+
+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_bool(cache_index_and_filter_blocks, false,
+ "True if indexes/filters should be cached in block cache.");
+
+DEFINE_bool(use_clock_cache, false,
+ "Replace default LRU block cache with clock 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(allow_concurrent_memtable_write, false,
+ "Allow multi-writers to update mem tables in parallel.");
+
+DEFINE_bool(enable_write_thread_adaptive_yield, true,
+ "Use a yielding spin loop for brief writer thread waits.");
+
+#ifndef ROCKSDB_LITE
+// BlobDB Options
+DEFINE_bool(use_blob_db, false, "Use BlobDB.");
+
+DEFINE_uint64(blob_db_min_blob_size,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().min_blob_size,
+ "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,
+ "Sync blob files once per every N bytes written.");
+
+DEFINE_uint64(blob_db_file_size,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().blob_file_size,
+ "Target size of each blob file.");
+
+DEFINE_bool(
+ blob_db_enable_gc,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().enable_garbage_collection,
+ "Enable BlobDB garbage collection.");
+
+DEFINE_double(
+ blob_db_gc_cutoff,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().garbage_collection_cutoff,
+ "Cutoff ratio for BlobDB garbage collection.");
+#endif // !ROCKSDB_LITE
+
+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_bool(use_block_based_filter, false,
+ "use block based filter"
+ "instead of full filter for block based table");
+
+DEFINE_bool(partition_filters, false,
+ "use partitioned filters "
+ "for block-based table");
+
+DEFINE_int32(
+ index_type,
+ static_cast<int32_t>(
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions::kBinarySearch),
+ "Type of block-based table index (see `enum IndexType` 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, "Test secondary instance.");
+
+DEFINE_string(
+ expected_values_path, "",
+ "File where the array of expected uint32_t values will be stored. If "
+ "provided and non-empty, the DB state will be verified against these "
+ "values after recovery. --max_key and --column_family must be kept the "
+ "same across invocations of this program that use the same "
+ "--expected_values_path.");
+
+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(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_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);
+extern int rocksdb_kill_odds;
+
+DEFINE_string(kill_prefix_blacklist, "",
+ "If non-empty, kill points with prefix in the list given will be"
+ " skipped. Items are comma-separated.");
+extern std::vector<std::string> rocksdb_kill_prefix_blacklist;
+
+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(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_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, 1000,
+ "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(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");
+
+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)");
+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_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_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(hdfs, "", "Name of hdfs environment");
+
+DEFINE_string(env_uri, "",
+ "URI for env lookup. Mutually exclusive with --hdfs");
+
+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_int32(secondary_catch_up_one_in, 0,
+ "If non-zero, the secondaries attemp to catch up with the primary "
+ "once for every N operations on average. 0 indicates the "
+ "secondaries do not try to catch up after open.");
+
+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_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_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.");
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_listener.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_listener.h
new file mode 100644
index 00000000..bda4ec13
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_listener.h
@@ -0,0 +1,222 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/listener.h"
+#include "util/gflags_compat.h"
+
+DECLARE_int32(compact_files_one_in);
+
+namespace ROCKSDB_NAMESPACE {
+class DbStressListener : public EventListener {
+ public:
+ DbStressListener(const std::string& db_name,
+ const std::vector<DbPath>& db_paths,
+ const std::vector<ColumnFamilyDescriptor>& column_families)
+ : db_name_(db_name),
+ db_paths_(db_paths),
+ column_families_(column_families),
+ num_pending_file_creations_(0) {}
+#ifndef ROCKSDB_LITE
+ ~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));
+ if (info.file_size) {
+ VerifyFilePath(info.file_path);
+ }
+ assert(info.job_id > 0 || FLAGS_compact_files_one_in > 0);
+ if (info.status.ok() && info.file_size > 0) {
+ 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);
+ }
+ --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();
+ }
+
+ 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<bool>(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
+ }
+
+ void RandomSleep() {
+ std::this_thread::sleep_for(
+ std::chrono::microseconds(Random::GetTLSInstance()->Uniform(5000)));
+ }
+#endif // !ROCKSDB_LITE
+
+ private:
+ std::string db_name_;
+ std::vector<DbPath> db_paths_;
+ std::vector<ColumnFamilyDescriptor> column_families_;
+ std::atomic<int> num_pending_file_creations_;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.cc
new file mode 100644
index 00000000..d25a47b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 {
+const uint32_t SharedState::UNKNOWN_SENTINEL = 0xfffffffe;
+const uint32_t SharedState::DELETION_SENTINEL = 0xffffffff;
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h
new file mode 100644
index 00000000..b68670b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h
@@ -0,0 +1,390 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "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_path);
+DECLARE_int32(clear_column_family_one_in);
+DECLARE_bool(test_batches_snapshots);
+DECLARE_int32(compaction_thread_pool_adjust_interval);
+DECLARE_int32(continuous_verification_interval);
+
+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 const uint32_t UNKNOWN_SENTINEL;
+ // indicates a key should definitely be deleted
+ static const uint32_t DELETION_SENTINEL;
+
+ SharedState(Env* env, StressTest* stress_test)
+ : cv_(&mu_),
+ seed_(static_cast<uint32_t>(FLAGS_seed)),
+ max_key_(FLAGS_max_key),
+ log2_keys_per_lock_(static_cast<uint32_t>(FLAGS_log2_keys_per_lock)),
+ num_threads_(FLAGS_threads),
+ 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_(FLAGS_column_families),
+ values_(nullptr),
+ printing_verification_results_(false) {
+ // Pick random keys in each column family that will not experience
+ // overwrite
+
+ fprintf(stdout, "Choosing random keys with no overwrite\n");
+ Random64 rnd(seed_);
+ // Start with the identity permutation. Subsequent iterations of
+ // for loop below will start with perm of previous for loop
+ int64_t* permutation = new int64_t[max_key_];
+ for (int64_t i = 0; i < max_key_; i++) {
+ permutation[i] = i;
+ }
+ // Now do the Knuth shuffle
+ int64_t num_no_overwrite_keys = (max_key_ * FLAGS_nooverwritepercent) / 100;
+ // Only need to figure out first num_no_overwrite_keys of permutation
+ no_overwrite_ids_.reserve(num_no_overwrite_keys);
+ for (int64_t i = 0; i < num_no_overwrite_keys; i++) {
+ 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
+ no_overwrite_ids_.insert(permutation[i]);
+ }
+ delete[] permutation;
+
+ size_t expected_values_size =
+ sizeof(std::atomic<uint32_t>) * FLAGS_column_families * max_key_;
+ bool values_init_needed = false;
+ Status status;
+ if (!FLAGS_expected_values_path.empty()) {
+ if (!std::atomic<uint32_t>{}.is_lock_free()) {
+ status = Status::InvalidArgument(
+ "Cannot use --expected_values_path on platforms without lock-free "
+ "std::atomic<uint32_t>");
+ }
+ if (status.ok() && FLAGS_clear_column_family_one_in > 0) {
+ status = Status::InvalidArgument(
+ "Cannot use --expected_values_path on when "
+ "--clear_column_family_one_in is greater than zero.");
+ }
+ uint64_t size = 0;
+ if (status.ok()) {
+ status = env->GetFileSize(FLAGS_expected_values_path, &size);
+ }
+ std::unique_ptr<WritableFile> wfile;
+ if (status.ok() && size == 0) {
+ const EnvOptions soptions;
+ status =
+ env->NewWritableFile(FLAGS_expected_values_path, &wfile, soptions);
+ }
+ if (status.ok() && size == 0) {
+ std::string buf(expected_values_size, '\0');
+ status = wfile->Append(buf);
+ values_init_needed = true;
+ }
+ if (status.ok()) {
+ status = env->NewMemoryMappedFileBuffer(FLAGS_expected_values_path,
+ &expected_mmap_buffer_);
+ }
+ if (status.ok()) {
+ assert(expected_mmap_buffer_->GetLen() == expected_values_size);
+ values_ = static_cast<std::atomic<uint32_t>*>(
+ expected_mmap_buffer_->GetBase());
+ assert(values_ != nullptr);
+ } else {
+ fprintf(stderr, "Failed opening shared file '%s' with error: %s\n",
+ FLAGS_expected_values_path.c_str(), status.ToString().c_str());
+ assert(values_ == nullptr);
+ }
+ }
+ if (values_ == nullptr) {
+ values_allocation_.reset(
+ new std::atomic<uint32_t>[FLAGS_column_families * max_key_]);
+ values_ = &values_allocation_[0];
+ values_init_needed = true;
+ }
+ assert(values_ != nullptr);
+ if (values_init_needed) {
+ for (int i = 0; i < FLAGS_column_families; ++i) {
+ for (int j = 0; j < max_key_; ++j) {
+ Delete(i, j, false /* pending */);
+ }
+ }
+ }
+
+ if (FLAGS_test_batches_snapshots) {
+ fprintf(stdout, "No lock creation because test_batches_snapshots set\n");
+ return;
+ }
+
+ long num_locks = static_cast<long>(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].resize(num_locks);
+ for (auto& ptr : key_locks_[i]) {
+ ptr.reset(new port::Mutex);
+ }
+ }
+ if (FLAGS_compaction_thread_pool_adjust_interval > 0) {
+ ++num_bg_threads_;
+ fprintf(stdout, "Starting compaction_thread_pool_adjust_thread\n");
+ }
+ if (FLAGS_continuous_verification_interval > 0) {
+ ++num_bg_threads_;
+ fprintf(stdout, "Starting continuous_verification_thread\n");
+ }
+ }
+
+ ~SharedState() {}
+
+ 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 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(); }
+
+ port::Mutex* GetMutexForKey(int cf, int64_t key) {
+ return key_locks_[cf][key >> log2_keys_per_lock_].get();
+ }
+
+ void LockColumnFamily(int cf) {
+ for (auto& mutex : key_locks_[cf]) {
+ mutex->Lock();
+ }
+ }
+
+ void UnlockColumnFamily(int cf) {
+ for (auto& mutex : key_locks_[cf]) {
+ mutex->Unlock();
+ }
+ }
+
+ std::atomic<uint32_t>& Value(int cf, int64_t key) const {
+ return values_[cf * max_key_ + key];
+ }
+
+ void ClearColumnFamily(int cf) {
+ std::fill(&Value(cf, 0 /* key */), &Value(cf + 1, 0 /* key */),
+ DELETION_SENTINEL);
+ }
+
+ // @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.
+ void 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 ? 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 Get(int cf, int64_t key) const { return Value(cf, key); }
+
+ // @param pending See comment above Put()
+ // Returns true if the key was not yet deleted.
+ bool Delete(int cf, int64_t key, bool pending) {
+ if (Value(cf, key) == DELETION_SENTINEL) {
+ return false;
+ }
+ Put(cf, key, DELETION_SENTINEL, pending);
+ return true;
+ }
+
+ // @param pending See comment above Put()
+ // Returns true if the key was not yet deleted.
+ bool SingleDelete(int cf, int64_t key, bool pending) {
+ return Delete(cf, key, pending);
+ }
+
+ // @param pending See comment above Put()
+ // Returns number of keys deleted by the call.
+ int 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 AllowsOverwrite(int64_t key) {
+ return no_overwrite_ids_.find(key) == no_overwrite_ids_.end();
+ }
+
+ bool 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 != DELETION_SENTINEL;
+ }
+
+ uint32_t GetSeed() const { return seed_; }
+
+ void SetShouldStopBgThread() { should_stop_bg_thread_ = true; }
+
+ bool ShouldStopBgThread() { return should_stop_bg_thread_; }
+
+ void IncBgThreadsFinished() { ++bg_thread_finished_; }
+
+ bool BgThreadsFinished() const {
+ return bg_thread_finished_ == num_bg_threads_;
+ }
+
+ bool ShouldVerifyAtBeginning() const {
+ return expected_mmap_buffer_.get() != nullptr;
+ }
+
+ 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);
+ }
+
+ private:
+ port::Mutex mu_;
+ port::CondVar cv_;
+ const uint32_t seed_;
+ const int64_t max_key_;
+ const uint32_t log2_keys_per_lock_;
+ const 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<bool> verification_failure_;
+ std::atomic<bool> should_stop_test_;
+
+ // Keys that should not be overwritten
+ std::unordered_set<size_t> no_overwrite_ids_;
+
+ std::atomic<uint32_t>* values_;
+ std::unique_ptr<std::atomic<uint32_t>[]> values_allocation_;
+ // Has to make it owned by a smart ptr as port::Mutex is not copyable
+ // and storing it in the container may require copying depending on the impl.
+ std::vector<std::vector<std::unique_ptr<port::Mutex>>> key_locks_;
+ std::unique_ptr<MemoryMappedFileBuffer> expected_mmap_buffer_;
+ std::atomic<bool> printing_verification_results_;
+};
+
+// 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<bool>* key_vec;
+ };
+ std::queue<std::pair<uint64_t, SnapshotState>> 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/storage/rocksdb/rocksdb/db_stress_tool/db_stress_stat.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_stat.h
new file mode 100644
index 00000000..a38c8733
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_stat.h
@@ -0,0 +1,213 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+#include <memory>
+#include <queue>
+#include <unordered_set>
+
+#include "monitoring/histogram.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/statistics.h"
+#include "util/gflags_compat.h"
+#include "util/random.h"
+
+DECLARE_bool(histogram);
+DECLARE_bool(progress_reports);
+
+namespace ROCKSDB_NAMESPACE {
+// Database statistics
+static std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats;
+static std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> 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 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;
+ bytes_ = 0;
+ seconds_ = 0;
+ num_compact_files_succeed_ = 0;
+ num_compact_files_failed_ = 0;
+ start_ = Env::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_;
+ 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_ = Env::Default()->NowMicros();
+ seconds_ = (finish_ - start_) * 1e-6;
+ }
+
+ void FinishedSingleOp() {
+ if (FLAGS_histogram) {
+ auto now = Env::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 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/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc
new file mode 100644
index 00000000..66b85379
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc
@@ -0,0 +1,2133 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "db_stress_tool/db_stress_driver.h"
+#include "rocksdb/convenience.h"
+
+namespace ROCKSDB_NAMESPACE {
+StressTest::StressTest()
+ : cache_(NewCache(FLAGS_cache_size)),
+ compressed_cache_(NewLRUCache(FLAGS_compressed_cache_size)),
+ filter_policy_(FLAGS_bloom_bits >= 0
+ ? FLAGS_use_block_based_filter
+ ? NewBloomFilterPolicy(FLAGS_bloom_bits, true)
+ : NewBloomFilterPolicy(FLAGS_bloom_bits, false)
+ : nullptr),
+ db_(nullptr),
+#ifndef ROCKSDB_LITE
+ txn_db_(nullptr),
+#endif
+ new_column_family_name_(1),
+ num_times_reopened_(0),
+ db_preload_finished_(false),
+ cmp_db_(nullptr) {
+ if (FLAGS_destroy_db_initially) {
+ std::vector<std::string> 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;
+ // Remove files without preserving manfiest files
+#ifndef ROCKSDB_LITE
+ const Status s = !FLAGS_use_blob_db
+ ? DestroyDB(FLAGS_db, options)
+ : blob_db::DestroyBlobDB(FLAGS_db, options,
+ blob_db::BlobDBOptions());
+#else
+ const Status s = DestroyDB(FLAGS_db, options);
+#endif // !ROCKSDB_LITE
+
+ 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_;
+
+ assert(secondaries_.size() == secondary_cfh_lists_.size());
+ size_t n = secondaries_.size();
+ for (size_t i = 0; i != n; ++i) {
+ for (auto* cf : secondary_cfh_lists_[i]) {
+ delete cf;
+ }
+ secondary_cfh_lists_[i].clear();
+ delete secondaries_[i];
+ }
+ secondaries_.clear();
+
+ for (auto* cf : cmp_cfhs_) {
+ delete cf;
+ }
+ cmp_cfhs_.clear();
+ delete cmp_db_;
+}
+
+std::shared_ptr<Cache> StressTest::NewCache(size_t capacity) {
+ if (capacity <= 0) {
+ return nullptr;
+ }
+ if (FLAGS_use_clock_cache) {
+ auto cache = NewClockCache((size_t)capacity);
+ if (!cache) {
+ fprintf(stderr, "Clock cache not supported.");
+ exit(1);
+ }
+ return cache;
+ } else {
+ return NewLRUCache((size_t)capacity);
+ }
+}
+
+bool StressTest::BuildOptionsTable() {
+ if (FLAGS_set_options_one_in <= 0) {
+ return true;
+ }
+
+ std::unordered_map<std::string, std::vector<std::string>> options_tbl = {
+ {"write_buffer_size",
+ {ToString(options_.write_buffer_size),
+ ToString(options_.write_buffer_size * 2),
+ ToString(options_.write_buffer_size * 4)}},
+ {"max_write_buffer_number",
+ {ToString(options_.max_write_buffer_number),
+ ToString(options_.max_write_buffer_number * 2),
+ ToString(options_.max_write_buffer_number * 4)}},
+ {"arena_block_size",
+ {
+ ToString(options_.arena_block_size),
+ ToString(options_.write_buffer_size / 4),
+ ToString(options_.write_buffer_size / 8),
+ }},
+ {"memtable_huge_page_size", {"0", ToString(2 * 1024 * 1024)}},
+ {"max_successive_merges", {"0", "2", "4"}},
+ {"inplace_update_num_locks", {"100", "200", "300"}},
+ // TODO(ljin): enable test for this option
+ // {"disable_auto_compactions", {"100", "200", "300"}},
+ {"soft_rate_limit", {"0", "0.5", "0.9"}},
+ {"hard_rate_limit", {"0", "1.1", "2.0"}},
+ {"level0_file_num_compaction_trigger",
+ {
+ ToString(options_.level0_file_num_compaction_trigger),
+ ToString(options_.level0_file_num_compaction_trigger + 2),
+ ToString(options_.level0_file_num_compaction_trigger + 4),
+ }},
+ {"level0_slowdown_writes_trigger",
+ {
+ ToString(options_.level0_slowdown_writes_trigger),
+ ToString(options_.level0_slowdown_writes_trigger + 2),
+ ToString(options_.level0_slowdown_writes_trigger + 4),
+ }},
+ {"level0_stop_writes_trigger",
+ {
+ ToString(options_.level0_stop_writes_trigger),
+ ToString(options_.level0_stop_writes_trigger + 2),
+ ToString(options_.level0_stop_writes_trigger + 4),
+ }},
+ {"max_compaction_bytes",
+ {
+ ToString(options_.target_file_size_base * 5),
+ ToString(options_.target_file_size_base * 15),
+ ToString(options_.target_file_size_base * 100),
+ }},
+ {"target_file_size_base",
+ {
+ ToString(options_.target_file_size_base),
+ ToString(options_.target_file_size_base * 2),
+ ToString(options_.target_file_size_base * 4),
+ }},
+ {"target_file_size_multiplier",
+ {
+ ToString(options_.target_file_size_multiplier),
+ "1",
+ "2",
+ }},
+ {"max_bytes_for_level_base",
+ {
+ ToString(options_.max_bytes_for_level_base / 2),
+ ToString(options_.max_bytes_for_level_base),
+ ToString(options_.max_bytes_for_level_base * 2),
+ }},
+ {"max_bytes_for_level_multiplier",
+ {
+ ToString(options_.max_bytes_for_level_multiplier),
+ "1",
+ "2",
+ }},
+ {"max_sequential_skip_in_iterations", {"4", "8", "12"}},
+ };
+
+ options_table_ = std::move(options_tbl);
+
+ for (const auto& iter : options_table_) {
+ options_index_.push_back(iter.first);
+ }
+ return true;
+}
+
+void StressTest::InitDb() {
+ uint64_t now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Initializing db_stress\n",
+ db_stress_env->TimeToString(now / 1000000).c_str());
+ PrintEnv();
+ Open();
+ BuildOptionsTable();
+}
+
+void StressTest::InitReadonlyDb(SharedState* shared) {
+ uint64_t now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Preloading db with %" PRIu64 " KVs\n",
+ db_stress_env->TimeToString(now / 1000000).c_str(), FLAGS_max_key);
+ PreloadDbAndReopenAsReadOnly(FLAGS_max_key, shared);
+}
+
+bool StressTest::VerifySecondaries() {
+#ifndef ROCKSDB_LITE
+ if (FLAGS_test_secondary) {
+ uint64_t now = db_stress_env->NowMicros();
+ fprintf(
+ stdout, "%s Start to verify secondaries against primary\n",
+ db_stress_env->TimeToString(static_cast<uint64_t>(now) / 1000000).c_str());
+ }
+ for (size_t k = 0; k != secondaries_.size(); ++k) {
+ Status s = secondaries_[k]->TryCatchUpWithPrimary();
+ if (!s.ok()) {
+ fprintf(stderr, "Secondary failed to catch up with primary\n");
+ return false;
+ }
+ ReadOptions ropts;
+ ropts.total_order_seek = true;
+ // Verify only the default column family since the primary may have
+ // dropped other column families after most recent reopen.
+ std::unique_ptr<Iterator> iter1(db_->NewIterator(ropts));
+ std::unique_ptr<Iterator> iter2(secondaries_[k]->NewIterator(ropts));
+ for (iter1->SeekToFirst(), iter2->SeekToFirst();
+ iter1->Valid() && iter2->Valid(); iter1->Next(), iter2->Next()) {
+ if (iter1->key().compare(iter2->key()) != 0 ||
+ iter1->value().compare(iter2->value())) {
+ fprintf(stderr,
+ "Secondary %d contains different data from "
+ "primary.\nPrimary: %s : %s\nSecondary: %s : %s\n",
+ static_cast<int>(k),
+ iter1->key().ToString(/*hex=*/true).c_str(),
+ iter1->value().ToString(/*hex=*/true).c_str(),
+ iter2->key().ToString(/*hex=*/true).c_str(),
+ iter2->value().ToString(/*hex=*/true).c_str());
+ return false;
+ }
+ }
+ if (iter1->Valid() && !iter2->Valid()) {
+ fprintf(stderr,
+ "Secondary %d record count is smaller than that of primary\n",
+ static_cast<int>(k));
+ return false;
+ } else if (!iter1->Valid() && iter2->Valid()) {
+ fprintf(stderr,
+ "Secondary %d record count is larger than that of primary\n",
+ static_cast<int>(k));
+ return false;
+ }
+ }
+ if (FLAGS_test_secondary) {
+ uint64_t now = db_stress_env->NowMicros();
+ fprintf(
+ stdout, "%s Verification of secondaries succeeded\n",
+ db_stress_env->TimeToString(static_cast<uint64_t>(now) / 1000000).c_str());
+ }
+#endif // ROCKSDB_LITE
+ return true;
+}
+
+Status StressTest::AssertSame(DB* db, ColumnFamilyHandle* cf,
+ ThreadState::SnapshotState& snap_state) {
+ Status s;
+ if (cf->GetName() != snap_state.cf_at_name) {
+ return s;
+ }
+ ReadOptions ropt;
+ ropt.snapshot = snap_state.snapshot;
+ 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 " +
+ ToString(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> iterator(db->NewIterator(ropt));
+ std::unique_ptr<std::vector<bool>> tmp_bitvec(
+ new std::vector<bool>(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 {
+ fprintf(stderr,
+ "Verification failed for column family %d key %" PRIi64 ": %s\n", cf,
+ key, msg.c_str());
+ shared->SetVerificationFailure();
+}
+
+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;
+ }
+ char value[100];
+ int cf_idx = 0;
+ Status s;
+ for (auto cfh : column_families_) {
+ for (int64_t k = 0; k != number_of_keys; ++k) {
+ std::string key_str = Key(k);
+ Slice key = key_str;
+ size_t sz = GenerateValue(0 /*value_base*/, value, sizeof(value));
+ Slice v(value, sz);
+ shared->Put(cf_idx, k, 0, true /* pending */);
+
+ if (FLAGS_use_merge) {
+ if (!FLAGS_use_txn) {
+ s = db_->Merge(write_opts, cfh, key, v);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->Merge(cfh, key, v);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ } else {
+ if (!FLAGS_use_txn) {
+ s = db_->Put(write_opts, cfh, key, v);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->Put(cfh, key, v);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ }
+
+ shared->Put(cf_idx, k, 0, 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;
+#ifndef ROCKSDB_LITE
+ txn_db_ = nullptr;
+#endif
+
+ db_preload_finished_.store(true);
+ auto now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Reopening database in read-only\n",
+ db_stress_env->TimeToString(now / 1000000).c_str());
+ // Reopen as read-only, can ignore all options related to updates
+ Open();
+ } else {
+ fprintf(stderr, "Failed to preload db");
+ exit(1);
+ }
+}
+
+Status StressTest::SetOptions(ThreadState* thread) {
+ assert(FLAGS_set_options_one_in > 0);
+ std::unordered_map<std::string, std::string> opts;
+ std::string name =
+ options_index_[thread->rand.Next() % options_index_.size()];
+ int value_idx = thread->rand.Next() % options_table_[name].size();
+ if (name == "soft_rate_limit" || name == "hard_rate_limit") {
+ opts["soft_rate_limit"] = options_table_["soft_rate_limit"][value_idx];
+ opts["hard_rate_limit"] = options_table_["hard_rate_limit"][value_idx];
+ } else 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);
+}
+
+#ifndef ROCKSDB_LITE
+Status StressTest::NewTxn(WriteOptions& write_opts, Transaction** txn) {
+ if (!FLAGS_use_txn) {
+ return Status::InvalidArgument("NewTxn when FLAGS_use_txn is not set");
+ }
+ static std::atomic<uint64_t> txn_id = {0};
+ TransactionOptions txn_options;
+ *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) {
+ if (!FLAGS_use_txn) {
+ return Status::InvalidArgument("CommitTxn when FLAGS_use_txn is not set");
+ }
+ Status s = txn->Prepare();
+ if (s.ok()) {
+ s = txn->Commit();
+ }
+ 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;
+}
+#endif
+
+void StressTest::OperateDb(ThreadState* thread) {
+ ReadOptions read_opts(FLAGS_verify_checksum, true);
+ WriteOptions write_opts;
+ auto shared = thread->shared;
+ char value[100];
+ std::string from_db;
+ if (FLAGS_sync) {
+ write_opts.sync = true;
+ }
+ write_opts.disableWAL = FLAGS_disable_wal;
+ const int prefixBound = static_cast<int>(FLAGS_readpercent) +
+ static_cast<int>(FLAGS_prefixpercent);
+ const int writeBound = prefixBound + static_cast<int>(FLAGS_writepercent);
+ const int delBound = writeBound + static_cast<int>(FLAGS_delpercent);
+ const int delRangeBound = delBound + static_cast<int>(FLAGS_delrangepercent);
+ const uint64_t ops_per_open = FLAGS_ops_per_thread / (FLAGS_reopen + 1);
+
+ 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_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;
+ std::unique_ptr<MutexLock> lock;
+ if (ShouldAcquireMutexOnKey()) {
+ lock.reset(new MutexLock(
+ shared->GetMutexForKey(rand_column_family, rand_key)));
+ }
+
+ if (thread->rand.OneInOpt(FLAGS_compact_range_one_in)) {
+ TestCompactRange(thread, rand_key, key, column_family);
+ if (thread->shared->HasVerificationFailedYet()) {
+ break;
+ }
+ }
+
+ std::vector<int> 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());
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ // Every 1 in N verify the one of the following: 1) GetLiveFiles
+ // 2) GetSortedWalFiles 3) GetCurrentWalFile. Each time, randomly select
+ // one of them to run the test.
+ if (thread->rand.OneInOpt(FLAGS_get_live_files_and_wal_files_one_in)) {
+ Status status = VerifyGetLiveAndWalFiles(thread);
+ if (!status.ok()) {
+ VerificationAbort(shared, "VerifyGetLiveAndWalFiles status not OK",
+ status);
+ }
+ }
+#endif // !ROCKSDB_LITE
+
+ if (thread->rand.OneInOpt(FLAGS_pause_background_one_in)) {
+ Status status = TestPauseBackground(thread);
+ if (!status.ok()) {
+ VerificationAbort(
+ shared, "Pause/ContinueBackgroundWork status not OK", status);
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ if (thread->rand.OneInOpt(FLAGS_verify_checksum_one_in)) {
+ Status status = db_->VerifyChecksum();
+ if (!status.ok()) {
+ VerificationAbort(shared, "VerifyChecksum status not OK", status);
+ }
+ }
+#endif
+
+ std::vector<int64_t> rand_keys = GenerateKeys(rand_key);
+
+ if (thread->rand.OneInOpt(FLAGS_ingest_external_file_one_in)) {
+ TestIngestExternalFile(thread, rand_column_families, rand_keys, lock);
+ }
+
+ if (thread->rand.OneInOpt(FLAGS_backup_one_in)) {
+ 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);
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ 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);
+ }
+ }
+#endif // !ROCKSDB_LITE
+ 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);
+ }
+ }
+
+ 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<int>(FLAGS_readpercent)) {
+ assert(0 <= prob_op);
+ // OPERATION read
+ 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<int>(
+ std::min(static_cast<uint64_t>(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 < prefixBound) {
+ assert(static_cast<int>(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 < writeBound) {
+ assert(prefixBound <= prob_op);
+ // OPERATION write
+ TestPut(thread, write_opts, read_opts, rand_column_families, rand_keys,
+ value, lock);
+ } else if (prob_op < delBound) {
+ assert(writeBound <= prob_op);
+ // OPERATION delete
+ TestDelete(thread, write_opts, rand_column_families, rand_keys, lock);
+ } else if (prob_op < delRangeBound) {
+ assert(delBound <= prob_op);
+ // OPERATION delete range
+ TestDeleteRange(thread, write_opts, rand_column_families, rand_keys,
+ lock);
+ } else {
+ assert(delRangeBound <= prob_op);
+ // OPERATION iterate
+ int num_seeks = static_cast<int>(
+ std::min(static_cast<uint64_t>(thread->rand.Uniform(4)),
+ FLAGS_ops_per_thread - i - 1));
+ rand_keys = GenerateNKeys(thread, num_seeks, i);
+ i += num_seeks - 1;
+ TestIterate(thread, read_opts, rand_column_families, rand_keys);
+ }
+ thread->stats.FinishedSingleOp();
+#ifndef ROCKSDB_LITE
+ uint32_t tid = thread->tid;
+ assert(secondaries_.empty() ||
+ static_cast<size_t>(tid) < secondaries_.size());
+ if (thread->rand.OneInOpt(FLAGS_secondary_catch_up_one_in)) {
+ Status s = secondaries_[tid]->TryCatchUpWithPrimary();
+ if (!s.ok()) {
+ VerificationAbort(shared, "Secondary instance failed to catch up", s);
+ break;
+ }
+ }
+#endif
+ }
+ }
+ 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();
+}
+
+#ifndef ROCKSDB_LITE
+// 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<std::string> StressTest::GetWhiteBoxKeys(ThreadState* thread,
+ DB* db,
+ ColumnFamilyHandle* cfh,
+ size_t num_keys) {
+ ColumnFamilyMetaData cfmd;
+ db->GetColumnFamilyMetaData(cfh, &cfmd);
+ std::vector<std::string> boundaries;
+ for (const LevelMetaData& lmd : cfmd.levels) {
+ for (const SstFileMetaData& sfmd : lmd.files) {
+ boundaries.push_back(sfmd.smallestkey);
+ boundaries.push_back(sfmd.largestkey);
+ }
+ }
+ if (boundaries.empty()) {
+ return {};
+ }
+
+ std::vector<std::string> ret;
+ for (size_t j = 0; j < num_keys; j++) {
+ std::string k =
+ boundaries[thread->rand.Uniform(static_cast<int>(boundaries.size()))];
+ if (thread->rand.OneIn(3)) {
+ // Reduce one byte from the string
+ for (int i = static_cast<int>(k.length()) - 1; i >= 0; i--) {
+ uint8_t cur = k[i];
+ if (cur > 0) {
+ k[i] = static_cast<char>(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<int>(k.length()) - 1; i >= 0; i--) {
+ uint8_t cur = k[i];
+ if (cur < 255) {
+ k[i] = static_cast<char>(cur + 1);
+ break;
+ } else if (i > 0) {
+ k[i] = 0x00;
+ }
+ }
+ }
+ ret.push_back(k);
+ }
+ return ret;
+}
+#endif // !ROCKSDB_LITE
+
+// 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) {
+ Status s;
+ const Snapshot* snapshot = db_->GetSnapshot();
+ ReadOptions readoptionscopy = read_opts;
+ readoptionscopy.snapshot = snapshot;
+
+ bool expect_total_order = false;
+ if (thread->rand.OneIn(16)) {
+ // When prefix extractor is used, it's useful to cover total order seek.
+ readoptionscopy.total_order_seek = true;
+ expect_total_order = true;
+ } else if (thread->rand.OneIn(4)) {
+ readoptionscopy.total_order_seek = false;
+ readoptionscopy.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)) {
+ // in 1/16 chance, set a iterator upper bound
+ int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread);
+ upper_bound_str = Key(rand_upper_key);
+ upper_bound = Slice(upper_bound_str);
+ // uppder_bound can be smaller than seek key, but the query itself
+ // should not crash either.
+ readoptionscopy.iterate_upper_bound = &upper_bound;
+ }
+ std::string lower_bound_str;
+ Slice lower_bound;
+ if (thread->rand.OneIn(16)) {
+ // in 1/16 chance, enable iterator lower bound
+ int64_t rand_lower_key = GenerateOneKey(thread, FLAGS_ops_per_thread);
+ lower_bound_str = Key(rand_lower_key);
+ lower_bound = Slice(lower_bound_str);
+ // uppder_bound can be smaller than seek key, but the query itself
+ // should not crash either.
+ readoptionscopy.iterate_lower_bound = &lower_bound;
+ }
+
+ auto cfh = column_families_[rand_column_families[0]];
+ std::unique_ptr<Iterator> iter(db_->NewIterator(readoptionscopy, cfh));
+
+ std::vector<std::string> key_str;
+ if (thread->rand.OneIn(16)) {
+ // Generate keys close to lower or upper bound of SST files.
+ key_str = GetWhiteBoxKeys(thread, db_, cfh, rand_keys.size());
+ }
+ if (key_str.empty()) {
+ // If key string is not geneerated using white block keys,
+ // Use randomized key passe in.
+ for (int64_t rkey : rand_keys) {
+ key_str.push_back(Key(rkey));
+ }
+ }
+
+ std::string op_logs;
+ const size_t kOpLogsLimit = 10000;
+
+ for (const std::string& skey : key_str) {
+ if (op_logs.size() > kOpLogsLimit) {
+ // Shouldn't take too much memory for the history log. Clear it.
+ op_logs = "(cleared...)\n";
+ }
+
+ Slice key = skey;
+
+ if (readoptionscopy.iterate_upper_bound != nullptr &&
+ thread->rand.OneIn(2)) {
+ // 1/2 chance, change the upper bound.
+ // It is possible that it is changed without first use, but there is no
+ // problem with that.
+ int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread);
+ upper_bound_str = Key(rand_upper_key);
+ upper_bound = Slice(upper_bound_str);
+ } else if (readoptionscopy.iterate_lower_bound != nullptr &&
+ thread->rand.OneIn(4)) {
+ // 1/4 chance, change the lower bound.
+ // It is possible that it is changed without first use, but there is no
+ // problem with that.
+ 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 += (readoptionscopy.total_order_seek ? "1 " : "0 ");
+ op_logs += "auto_prefix_mode: ";
+ op_logs += (readoptionscopy.auto_prefix_mode ? "1 " : "0 ");
+ if (readoptionscopy.iterate_upper_bound != nullptr) {
+ op_logs += "ub: " + upper_bound.ToString(true) + " ";
+ }
+ if (readoptionscopy.iterate_lower_bound != nullptr) {
+ op_logs += "lb: " + lower_bound.ToString(true) + " ";
+ }
+
+ // Set up an iterator and does the same 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.
+ ReadOptions cmp_ro;
+ cmp_ro.snapshot = snapshot;
+ cmp_ro.total_order_seek = true;
+ ColumnFamilyHandle* cmp_cfh =
+ GetControlCfh(thread, rand_column_families[0]);
+ std::unique_ptr<Iterator> cmp_iter(db_->NewIterator(cmp_ro, cmp_cfh));
+ bool diverged = false;
+
+ 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, readoptionscopy, iter.get(), cmp_iter.get(),
+ last_op, key, op_logs, &diverged);
+
+ 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, readoptionscopy, iter.get(),
+ cmp_iter.get(), last_op, key, op_logs, &diverged);
+ }
+
+ if (s.ok()) {
+ thread->stats.AddIterations(1);
+ } else {
+ fprintf(stderr, "TestIterate error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ break;
+ }
+
+ op_logs += "; ";
+ }
+
+ db_->ReleaseSnapshot(snapshot);
+
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+// Test the return status of GetLiveFiles, GetSortedWalFiles, and
+// GetCurrentWalFile. Each time, randomly select one of them to run
+// and return the status.
+Status StressTest::VerifyGetLiveAndWalFiles(ThreadState* thread) {
+ int case_num = thread->rand.Uniform(3);
+ if (case_num == 0) {
+ std::vector<std::string> live_file;
+ uint64_t manifest_size;
+ return db_->GetLiveFiles(live_file, &manifest_size);
+ }
+
+ if (case_num == 1) {
+ VectorLogPtr log_ptr;
+ return db_->GetSortedWalFiles(log_ptr);
+ }
+
+ if (case_num == 2) {
+ std::unique_ptr<LogFile> cur_wal_file;
+ return db_->GetCurrentWalFile(&cur_wal_file);
+ }
+ assert(false);
+ return Status::Corruption("Undefined case happens!");
+}
+#endif // !ROCKSDB_LITE
+
+// 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) {
+ if (*diverged) {
+ 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->Compare(*ro.iterate_lower_bound, seek_key) >=
+ 0 ||
+ (ro.iterate_upper_bound != nullptr &&
+ options_.comparator->Compare(*ro.iterate_lower_bound,
+ *ro.iterate_upper_bound) >= 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->Compare(*ro.iterate_upper_bound, seek_key) <=
+ 0 ||
+ (ro.iterate_lower_bound != nullptr &&
+ options_.comparator->Compare(*ro.iterate_lower_bound,
+ *ro.iterate_upper_bound) >= 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->Compare(total_order_key, *ro.iterate_upper_bound) < 0) &&
+ (ro.iterate_lower_bound == nullptr ||
+ cmp->Compare(total_order_key, *ro.iterate_lower_bound) > 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) {
+ 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();
+ }
+}
+
+#ifdef ROCKSDB_LITE
+Status StressTest::TestBackupRestore(
+ ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */) {
+ assert(false);
+ fprintf(stderr,
+ "RocksDB lite does not support "
+ "TestBackupRestore\n");
+ std::terminate();
+}
+
+Status StressTest::TestCheckpoint(
+ ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */) {
+ assert(false);
+ fprintf(stderr,
+ "RocksDB lite does not support "
+ "TestCheckpoint\n");
+ std::terminate();
+}
+
+void StressTest::TestCompactFiles(ThreadState* /* thread */,
+ ColumnFamilyHandle* /* column_family */) {
+ assert(false);
+ fprintf(stderr,
+ "RocksDB lite does not support "
+ "CompactFiles\n");
+ std::terminate();
+}
+#else // ROCKSDB_LITE
+Status StressTest::TestBackupRestore(
+ ThreadState* thread, const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) {
+ // 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.
+ assert(rand_column_families.size() == rand_keys.size());
+ std::string backup_dir = FLAGS_db + "/.backup" + ToString(thread->tid);
+ std::string restore_dir = FLAGS_db + "/.restore" + ToString(thread->tid);
+ BackupableDBOptions backup_opts(backup_dir);
+ BackupEngine* backup_engine = nullptr;
+ Status s = BackupEngine::Open(db_stress_env, backup_opts, &backup_engine);
+ if (s.ok()) {
+ s = backup_engine->CreateNewBackup(db_);
+ }
+ if (s.ok()) {
+ delete backup_engine;
+ backup_engine = nullptr;
+ s = BackupEngine::Open(db_stress_env, backup_opts, &backup_engine);
+ }
+ if (s.ok()) {
+ s = backup_engine->RestoreDBFromLatestBackup(restore_dir /* db_dir */,
+ restore_dir /* wal_dir */);
+ }
+ if (s.ok()) {
+ s = backup_engine->PurgeOldBackups(0 /* num_backups_to_keep */);
+ }
+ DB* restored_db = nullptr;
+ std::vector<ColumnFamilyHandle*> restored_cf_handles;
+ if (s.ok()) {
+ Options restore_options(options_);
+ restore_options.listeners.clear();
+ std::vector<ColumnFamilyDescriptor> 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));
+ }
+ s = DB::Open(DBOptions(restore_options), restore_dir, cf_descriptors,
+ &restored_cf_handles, &restored_db);
+ }
+ // for simplicity, currently only verifies existence/non-existence of a few
+ // keys
+ for (size_t i = 0; s.ok() && i < rand_column_families.size(); ++i) {
+ std::string key_str = Key(rand_keys[i]);
+ Slice key = key_str;
+ std::string restored_value;
+ Status get_status = restored_db->Get(
+ ReadOptions(), restored_cf_handles[rand_column_families[i]], key,
+ &restored_value);
+ bool exists = thread->shared->Exists(rand_column_families[i], rand_keys[i]);
+ if (get_status.ok()) {
+ if (!exists) {
+ s = Status::Corruption("key exists in restore but not in original db");
+ }
+ } else if (get_status.IsNotFound()) {
+ if (exists) {
+ s = Status::Corruption("key exists in original db but not in restore");
+ }
+ } else {
+ s = get_status;
+ }
+ }
+ if (backup_engine != nullptr) {
+ delete backup_engine;
+ backup_engine = nullptr;
+ }
+ 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()) {
+ fprintf(stderr, "A backup/restore operation failed with: %s\n",
+ s.ToString().c_str());
+ }
+ return s;
+}
+
+#ifndef ROCKSDB_LITE
+Status StressTest::TestApproximateSize(
+ ThreadState* thread, uint64_t iteration,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& 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<int64_t>(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_memtabtles = thread->rand.OneIn(2);
+ if (sao.include_memtabtles) {
+ 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<double>(thread->rand.Uniform(3));
+ }
+ }
+ uint64_t result;
+ return db_->GetApproximateSizes(
+ sao, column_families_[rand_column_families[0]], &range, 1, &result);
+}
+#endif // ROCKSDB_LITE
+
+Status StressTest::TestCheckpoint(ThreadState* thread,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) {
+ // 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.
+ assert(rand_column_families.size() == rand_keys.size());
+ std::string checkpoint_dir =
+ FLAGS_db + "/.checkpoint" + ToString(thread->tid);
+ Options tmp_opts(options_);
+ tmp_opts.listeners.clear();
+ tmp_opts.env = db_stress_env->target();
+
+ DestroyDB(checkpoint_dir, tmp_opts);
+
+ Checkpoint* checkpoint = nullptr;
+ Status s = Checkpoint::Create(db_, &checkpoint);
+ if (s.ok()) {
+ s = checkpoint->CreateCheckpoint(checkpoint_dir);
+ }
+ std::vector<ColumnFamilyHandle*> cf_handles;
+ DB* checkpoint_db = nullptr;
+ if (s.ok()) {
+ delete checkpoint;
+ checkpoint = nullptr;
+ Options options(options_);
+ options.listeners.clear();
+ std::vector<ColumnFamilyDescriptor> 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_`.
+ 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) {
+ for (size_t i = 0; s.ok() && i < rand_column_families.size(); ++i) {
+ std::string key_str = Key(rand_keys[i]);
+ Slice key = key_str;
+ std::string value;
+ Status get_status = checkpoint_db->Get(
+ ReadOptions(), cf_handles[rand_column_families[i]], key, &value);
+ bool exists =
+ thread->shared->Exists(rand_column_families[i], rand_keys[i]);
+ if (get_status.ok()) {
+ if (!exists) {
+ s = Status::Corruption(
+ "key exists in checkpoint but not in original db");
+ }
+ } else if (get_status.IsNotFound()) {
+ if (exists) {
+ s = Status::Corruption(
+ "key exists in original db but not in checkpoint");
+ }
+ } else {
+ s = get_status;
+ }
+ }
+ for (auto cfh : cf_handles) {
+ delete cfh;
+ }
+ cf_handles.clear();
+ delete checkpoint_db;
+ checkpoint_db = nullptr;
+ }
+
+ DestroyDB(checkpoint_dir, tmp_opts);
+
+ if (!s.ok()) {
+ fprintf(stderr, "A checkpoint operation failed with: %s\n",
+ s.ToString().c_str());
+ }
+ return s;
+}
+
+void StressTest::TestCompactFiles(ThreadState* thread,
+ ColumnFamilyHandle* column_family) {
+ ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data;
+ db_->GetColumnFamilyMetaData(column_family, &cf_meta_data);
+
+ // 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<int>(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<int>(files.size()));
+ if (files[random_file_index].being_compacted) {
+ // Retry as the selected file is currently being compacted
+ continue;
+ }
+
+ std::vector<std::string> 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<int>(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;
+ }
+ }
+}
+#endif // ROCKSDB_LITE
+
+Status StressTest::TestFlush(const std::vector<int>& rand_column_families) {
+ FlushOptions flush_opts;
+ std::vector<ColumnFamilyHandle*> 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));
+ db_stress_env->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];
+#ifndef ROCKSDB_LITE
+ auto db_impl = reinterpret_cast<DBImpl*>(db_->GetRootDB());
+ const bool ww_snapshot = thread->rand.OneIn(10);
+ const Snapshot* snapshot =
+ ww_snapshot ? db_impl->GetSnapshotForWriteConflictBoundary()
+ : db_->GetSnapshot();
+#else
+ const Snapshot* snapshot = db_->GetSnapshot();
+#endif // !ROCKSDB_LITE
+ ReadOptions ropt;
+ ropt.snapshot = snapshot;
+ 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<bool>* key_vec = nullptr;
+
+ if (FLAGS_compare_full_db_state_snapshot && (thread->tid == 0)) {
+ key_vec = new std::vector<bool>(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> 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};
+ 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 < port::kMaxInt64 / 10);
+ hold_for *= 10;
+ // Hold 1% of snapshots for 100x more
+ if (thread->rand.OneIn(10)) {
+ assert(hold_for < port::kMaxInt64 / 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 (port::kMaxInt64 - rand_key < FLAGS_compact_range_width) {
+ end_key_num = port::kMaxInt64;
+ } 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<bool>(thread->rand.Next() % 2);
+ cro.change_level = static_cast<bool>(thread->rand.Next() % 2);
+ std::vector<BottommostLevelCompaction> bottom_level_styles = {
+ BottommostLevelCompaction::kSkip,
+ BottommostLevelCompaction::kIfHaveCompactionFilter,
+ BottommostLevelCompaction::kForce,
+ BottommostLevelCompaction::kForceOptimized};
+ cro.bottommost_level_compaction =
+ bottom_level_styles[thread->rand.Next() %
+ static_cast<uint32_t>(bottom_level_styles.size())];
+ cro.allow_write_stall = static_cast<bool>(thread->rand.Next() % 2);
+ cro.max_subcompactions = static_cast<uint32_t>(thread->rand.Next() % 4);
+
+ 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) {
+ const std::string kCrcCalculatorSepearator = ";";
+ uint32_t crc = 0;
+ ReadOptions ro;
+ ro.snapshot = snapshot;
+ ro.total_order_seek = true;
+ std::unique_ptr<Iterator> it(db_->NewIterator(ro, column_family));
+ 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.data(), 1);
+ crc = crc32c::Extend(crc, it->value().data(), it->value().size());
+ crc = crc32c::Extend(crc, kCrcCalculatorSepearator.data(), 1);
+ }
+ 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");
+#ifndef ROCKSDB_LITE
+ fprintf(stdout, "BlobDB : %s\n",
+ FLAGS_use_blob_db ? "true" : "false");
+#endif // !ROCKSDB_LITE
+ 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, "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 = NumberToString(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, "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, "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");
+
+ 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);
+
+ fprintf(stdout, "Test kill odd : %d\n", rocksdb_kill_odds);
+ if (!rocksdb_kill_prefix_blacklist.empty()) {
+ fprintf(stdout, "Skipping kill points prefixes:\n");
+ for (auto& p : rocksdb_kill_prefix_blacklist) {
+ fprintf(stdout, " %s\n", p.c_str());
+ }
+ }
+ fprintf(stdout, "Periodic Compaction Secs : %" PRIu64 "\n",
+ FLAGS_periodic_compaction_seconds);
+ fprintf(stdout, "Compaction TTL : %" PRIu64 "\n",
+ FLAGS_compaction_ttl);
+ fprintf(stdout, "Background Purge : %d\n",
+ static_cast<int>(FLAGS_avoid_unnecessary_blocking_io));
+ fprintf(stdout, "Write DB ID to manifest : %d\n",
+ static_cast<int>(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<int>(FLAGS_level_compaction_dynamic_level_bytes));
+
+ fprintf(stdout, "------------------------------------------------\n");
+}
+
+void StressTest::Open() {
+ assert(db_ == nullptr);
+#ifndef ROCKSDB_LITE
+ assert(txn_db_ == nullptr);
+#endif
+ if (FLAGS_options_file.empty()) {
+ 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.block_cache_compressed = compressed_cache_;
+ block_based_options.checksum = checksum_type_e;
+ block_based_options.block_size = FLAGS_block_size;
+ block_based_options.format_version =
+ static_cast<uint32_t>(FLAGS_format_version);
+ block_based_options.index_block_restart_interval =
+ static_cast<int32_t>(FLAGS_index_block_restart_interval);
+ block_based_options.filter_policy = filter_policy_;
+ block_based_options.partition_filters = FLAGS_partition_filters;
+ block_based_options.index_type =
+ static_cast<BlockBasedTableOptions::IndexType>(FLAGS_index_type);
+ 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_.max_background_compactions = FLAGS_max_background_compactions;
+ options_.max_background_flushes = FLAGS_max_background_flushes;
+ options_.compaction_style =
+ static_cast<ROCKSDB_NAMESPACE::CompactionStyle>(FLAGS_compaction_style);
+ 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<size_t>(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_.create_if_missing = true;
+ options_.max_manifest_file_size = FLAGS_max_manifest_file_size;
+ options_.inplace_update_support = FLAGS_in_place_update;
+ options_.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
+ options_.allow_concurrent_memtable_write =
+ FLAGS_allow_concurrent_memtable_write;
+ options_.periodic_compaction_seconds = FLAGS_periodic_compaction_seconds;
+ 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_.avoid_unnecessary_blocking_io =
+ FLAGS_avoid_unnecessary_blocking_io;
+ options_.write_dbid_to_manifest = FLAGS_write_dbid_to_manifest;
+ 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;
+ } else {
+#ifdef ROCKSDB_LITE
+ fprintf(stderr, "--options_file not supported in lite mode\n");
+ exit(1);
+#else
+ DBOptions db_options;
+ std::vector<ColumnFamilyDescriptor> cf_descriptors;
+ Status s = LoadOptionsFromFile(FLAGS_options_file, db_stress_env,
+ &db_options, &cf_descriptors);
+ db_options.env = new DbStressEnvWrapper(db_stress_env);
+ if (!s.ok()) {
+ fprintf(stderr, "Unable to load options file %s --- %s\n",
+ FLAGS_options_file.c_str(), s.ToString().c_str());
+ exit(1);
+ }
+ options_ = Options(db_options, cf_descriptors[0].options);
+#endif // ROCKSDB_LITE
+ }
+
+ 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 (FLAGS_rate_limit_bg_reads) {
+ options_.new_table_reader_for_compaction_inputs = true;
+ }
+ }
+
+ 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");
+ }
+ switch (FLAGS_rep_factory) {
+ case kSkipList:
+ // no need to do anything
+ break;
+#ifndef ROCKSDB_LITE
+ case kHashSkipList:
+ options_.memtable_factory.reset(NewHashSkipListRepFactory(10000));
+ break;
+ case kVectorRep:
+ options_.memtable_factory.reset(new VectorRepFactory());
+ break;
+#else
+ default:
+ fprintf(stderr,
+ "RocksdbLite only supports skip list mem table. Skip "
+ "--rep_factory\n");
+#endif // ROCKSDB_LITE
+ }
+
+ if (FLAGS_use_full_merge_v1) {
+ options_.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
+ } else {
+ options_.merge_operator = MergeOperators::CreatePutOperator();
+ }
+
+ fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
+
+ Status s;
+ if (FLAGS_ttl == -1) {
+ std::vector<std::string> 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<ColumnFamilyDescriptor> 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 = ToString(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));
+ options_.create_missing_column_families = true;
+ if (!FLAGS_use_txn) {
+#ifndef ROCKSDB_LITE
+ 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
+#endif // !ROCKSDB_LITE
+ {
+ 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_);
+ }
+ }
+ } else {
+#ifndef ROCKSDB_LITE
+ TransactionDBOptions txn_db_options;
+ assert(FLAGS_txn_write_policy <= TxnDBWritePolicy::WRITE_UNPREPARED);
+ txn_db_options.write_policy =
+ static_cast<TxnDBWritePolicy>(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;
+ }
+ 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());
+ db_ = txn_db_;
+ // after a crash, rollback to commit recovered transactions
+ std::vector<Transaction*> trans;
+ txn_db_->GetAllPreparedTransactions(&trans);
+ Random rand(static_cast<uint32_t>(FLAGS_seed));
+ for (auto txn : trans) {
+ if (rand.OneIn(2)) {
+ s = txn->Commit();
+ assert(s.ok());
+ } else {
+ s = txn->Rollback();
+ assert(s.ok());
+ }
+ delete txn;
+ }
+ trans.clear();
+ txn_db_->GetAllPreparedTransactions(&trans);
+ assert(trans.size() == 0);
+#endif
+ }
+ assert(!s.ok() || column_families_.size() ==
+ static_cast<size_t>(FLAGS_column_families));
+
+ if (FLAGS_test_secondary) {
+#ifndef ROCKSDB_LITE
+ secondaries_.resize(FLAGS_threads);
+ std::fill(secondaries_.begin(), secondaries_.end(), nullptr);
+ secondary_cfh_lists_.clear();
+ secondary_cfh_lists_.resize(FLAGS_threads);
+ Options tmp_opts;
+ // TODO(yanqin) support max_open_files != -1 for secondary instance.
+ tmp_opts.max_open_files = -1;
+ tmp_opts.statistics = dbstats_secondaries;
+ tmp_opts.env = db_stress_env;
+ for (size_t i = 0; i != static_cast<size_t>(FLAGS_threads); ++i) {
+ const std::string secondary_path =
+ FLAGS_secondaries_base + "/" + std::to_string(i);
+ s = DB::OpenAsSecondary(tmp_opts, FLAGS_db, secondary_path,
+ cf_descriptors, &secondary_cfh_lists_[i],
+ &secondaries_[i]);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ assert(s.ok());
+#else
+ fprintf(stderr, "Secondary is not supported in RocksDBLite\n");
+ exit(1);
+#endif
+ }
+ if (FLAGS_continuous_verification_interval > 0 && !cmp_db_) {
+ 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;
+ std::string secondary_path = FLAGS_secondaries_base + "/cmp_database";
+ s = DB::OpenAsSecondary(tmp_opts, FLAGS_db, secondary_path,
+ cf_descriptors, &cmp_cfhs_, &cmp_db_);
+ assert(!s.ok() ||
+ cmp_cfhs_.size() == static_cast<size_t>(FLAGS_column_families));
+ }
+ } else {
+#ifndef ROCKSDB_LITE
+ DBWithTTL* db_with_ttl;
+ s = DBWithTTL::Open(options_, FLAGS_db, &db_with_ttl, FLAGS_ttl);
+ db_ = db_with_ttl;
+ if (FLAGS_test_secondary) {
+ secondaries_.resize(FLAGS_threads);
+ std::fill(secondaries_.begin(), secondaries_.end(), nullptr);
+ Options tmp_opts;
+ tmp_opts.env = options_.env;
+ // TODO(yanqin) support max_open_files != -1 for secondary instance.
+ tmp_opts.max_open_files = -1;
+ for (size_t i = 0; i != static_cast<size_t>(FLAGS_threads); ++i) {
+ const std::string secondary_path =
+ FLAGS_secondaries_base + "/" + std::to_string(i);
+ s = DB::OpenAsSecondary(tmp_opts, FLAGS_db, secondary_path,
+ &secondaries_[i]);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+#else
+ fprintf(stderr, "TTL is not supported in RocksDBLite\n");
+ exit(1);
+#endif
+ }
+ if (!s.ok()) {
+ fprintf(stderr, "open error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+}
+
+void StressTest::Reopen(ThreadState* thread) {
+#ifndef ROCKSDB_LITE
+ // 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 = false;
+ if (write_prepared || thread->rand.OneIn(2)) {
+ const bool wait =
+ write_prepared || static_cast<bool>(thread->rand.OneIn(2));
+ CancelAllBackgroundWork(db_, wait);
+ bg_canceled = wait;
+ }
+ assert(!write_prepared || bg_canceled);
+ (void) bg_canceled;
+#else
+ (void) thread;
+#endif
+
+ for (auto cf : column_families_) {
+ delete cf;
+ }
+ column_families_.clear();
+
+#ifndef ROCKSDB_LITE
+ 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());
+ }
+#endif
+ delete db_;
+ db_ = nullptr;
+#ifndef ROCKSDB_LITE
+ txn_db_ = nullptr;
+#endif
+
+ assert(secondaries_.size() == secondary_cfh_lists_.size());
+ size_t n = secondaries_.size();
+ for (size_t i = 0; i != n; ++i) {
+ for (auto* cf : secondary_cfh_lists_[i]) {
+ delete cf;
+ }
+ secondary_cfh_lists_[i].clear();
+ delete secondaries_[i];
+ }
+ secondaries_.clear();
+
+ num_times_reopened_++;
+ auto now = db_stress_env->NowMicros();
+ fprintf(stdout, "%s Reopening database for the %dth time\n",
+ db_stress_env->TimeToString(now / 1000000).c_str(),
+ num_times_reopened_);
+ Open();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h
new file mode 100644
index 00000000..5d88ceb4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h
@@ -0,0 +1,231 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 Transaction;
+class TransactionDB;
+
+class StressTest {
+ public:
+ StressTest();
+
+ virtual ~StressTest();
+
+ std::shared_ptr<Cache> NewCache(size_t capacity);
+
+ bool BuildOptionsTable();
+
+ void InitDb();
+ void InitReadonlyDb(SharedState*);
+
+ // Return false if verification fails.
+ bool VerifySecondaries();
+
+ void OperateDb(ThreadState* thread);
+ virtual void VerifyDb(ThreadState* thread) const = 0;
+ virtual void ContinuouslyVerifyDb(ThreadState* /*thread*/) const {}
+
+ 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);
+
+#ifndef ROCKSDB_LITE
+ Status NewTxn(WriteOptions& write_opts, Transaction** txn);
+
+ Status CommitTxn(Transaction* txn);
+
+ Status RollbackTxn(Transaction* txn);
+#endif
+
+ virtual void MaybeClearOneColumnFamily(ThreadState* /* thread */) {}
+
+ virtual bool ShouldAcquireMutexOnKey() const { return false; }
+
+ virtual std::vector<int> GenerateColumnFamilies(
+ const int /* num_column_families */, int rand_column_family) const {
+ return {rand_column_family};
+ }
+
+ virtual std::vector<int64_t> GenerateKeys(int64_t rand_key) const {
+ return {rand_key};
+ }
+
+ virtual Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) = 0;
+
+ virtual std::vector<Status> TestMultiGet(
+ ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) = 0;
+
+ virtual Status TestPrefixScan(ThreadState* thread,
+ const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) = 0;
+
+ virtual Status TestPut(ThreadState* thread, WriteOptions& write_opts,
+ const ReadOptions& read_opts,
+ const std::vector<int>& cf_ids,
+ const std::vector<int64_t>& keys, char (&value)[100],
+ std::unique_ptr<MutexLock>& lock) = 0;
+
+ virtual Status TestDelete(ThreadState* thread, WriteOptions& write_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) = 0;
+
+ virtual Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) = 0;
+
+ virtual void TestIngestExternalFile(
+ ThreadState* thread, const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) = 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];
+ }
+
+#ifndef ROCKSDB_LITE
+ // 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<std::string> GetWhiteBoxKeys(ThreadState* thread, DB* db,
+ ColumnFamilyHandle* cfh,
+ size_t num_keys);
+#else // !ROCKSDB_LITE
+ std::vector<std::string> GetWhiteBoxKeys(ThreadState*, DB*,
+ ColumnFamilyHandle*, size_t) {
+ // Not supported in LITE mode.
+ return {};
+ }
+#endif // !ROCKSDB_LITE
+
+ // 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys);
+
+ // 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys);
+
+ virtual Status TestCheckpoint(ThreadState* thread,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys);
+
+ void TestCompactFiles(ThreadState* thread, ColumnFamilyHandle* column_family);
+
+ Status TestFlush(const std::vector<int>& 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);
+#ifndef ROCKSDB_LITE
+ Status VerifyGetLiveAndWalFiles(ThreadState* thread);
+ virtual Status TestApproximateSize(
+ ThreadState* thread, uint64_t iteration,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys);
+#endif // !ROCKSDB_LITE
+
+ void VerificationAbort(SharedState* shared, std::string msg, Status s) const;
+
+ void VerificationAbort(SharedState* shared, std::string msg, int cf,
+ int64_t key) const;
+
+ void PrintEnv() const;
+
+ void Open();
+
+ void Reopen(ThreadState* thread);
+
+ std::shared_ptr<Cache> cache_;
+ std::shared_ptr<Cache> compressed_cache_;
+ std::shared_ptr<const FilterPolicy> filter_policy_;
+ DB* db_;
+#ifndef ROCKSDB_LITE
+ TransactionDB* txn_db_;
+#endif
+ Options options_;
+ std::vector<ColumnFamilyHandle*> column_families_;
+ std::vector<std::string> column_family_names_;
+ std::atomic<int> new_column_family_name_;
+ int num_times_reopened_;
+ std::unordered_map<std::string, std::vector<std::string>> options_table_;
+ std::vector<std::string> options_index_;
+ std::atomic<bool> db_preload_finished_;
+
+ // Fields used for stress-testing secondary instance in the same process
+ std::vector<DB*> secondaries_;
+ std::vector<std::vector<ColumnFamilyHandle*>> secondary_cfh_lists_;
+
+ // Fields used for continuous verification from another thread
+ DB* cmp_db_;
+ std::vector<ColumnFamilyHandle*> cmp_cfhs_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/db_stress_tool.cc b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_tool.cc
new file mode 100644
index 00000000..ddb4c340
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/db_stress_tool.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#include "db_stress_tool/db_stress_common.h"
+#include "db_stress_tool/db_stress_driver.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+static std::shared_ptr<ROCKSDB_NAMESPACE::Env> env_guard;
+static std::shared_ptr<ROCKSDB_NAMESPACE::DbStressEnvWrapper> env_wrapper_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);
+
+ 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;
+
+ if (!FLAGS_hdfs.empty()) {
+ if (!FLAGS_env_uri.empty()) {
+ fprintf(stderr, "Cannot specify both --hdfs and --env_uri.\n");
+ exit(1);
+ }
+ raw_env = new ROCKSDB_NAMESPACE::HdfsEnv(FLAGS_hdfs);
+ } else if (!FLAGS_env_uri.empty()) {
+ Status s = Env::LoadEnv(FLAGS_env_uri, &raw_env, &env_guard);
+ if (raw_env == nullptr) {
+ fprintf(stderr, "No Env registered for URI: %s\n", FLAGS_env_uri.c_str());
+ exit(1);
+ }
+ } else {
+ raw_env = Env::Default();
+ }
+ env_wrapper_guard = std::make_shared<DbStressEnvWrapper>(raw_env);
+ 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) {
+ fprintf(stderr,
+ "Error: please specify positive prefix_size 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) != 100) {
+ fprintf(stderr,
+ "Error: Read+Prefix+Write+Delete+DeleteRange+Iterate percents != "
+ "100!\n");
+ 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 > 0) {
+ fprintf(stderr,
+ "Error: nooverwritepercent must be 0 when using file ingestion\n");
+ 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=<path>
+ 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";
+ ROCKSDB_NAMESPACE::Status 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_test_secondary && FLAGS_secondary_catch_up_one_in > 0) {
+ fprintf(
+ stderr,
+ "Must set -test_secondary=true if secondary_catch_up_one_in > 0.\n");
+ exit(1);
+ }
+
+ rocksdb_kill_odds = FLAGS_kill_random_test;
+ rocksdb_kill_prefix_blacklist = SplitString(FLAGS_kill_prefix_blacklist);
+
+ unsigned int levels = FLAGS_max_key_len;
+ std::vector<std::string> 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 < levels - 1; ++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<ROCKSDB_NAMESPACE::StressTest> stress;
+ if (FLAGS_test_cf_consistency) {
+ stress.reset(CreateCfConsistencyStressTest());
+ } else if (FLAGS_test_batches_snapshots) {
+ stress.reset(CreateBatchedOpsStressTest());
+ } else {
+ stress.reset(CreateNonBatchedOpsStressTest());
+ }
+ // Initialize the Zipfian pre-calculated array
+ InitializeHotKeyGenerator(FLAGS_hot_key_alpha);
+ if (RunStressTest(stress.get())) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc b/storage/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc
new file mode 100644
index 00000000..2e6734f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc
@@ -0,0 +1,631 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 NonBatchedOpsStressTest : public StressTest {
+ public:
+ NonBatchedOpsStressTest() {}
+
+ virtual ~NonBatchedOpsStressTest() {}
+
+ void VerifyDb(ThreadState* thread) const override {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ 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<size_t>(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;
+ }
+ if (!thread->rand.OneIn(2)) {
+ // Use iterator to verify this range
+ Slice prefix;
+ std::string seek_key = Key(start);
+ std::unique_ptr<Iterator> iter(
+ db_->NewIterator(options, column_families_[cf]));
+ iter->Seek(seek_key);
+ prefix = Slice(seek_key.data(), prefix_to_use);
+ for (auto i = start; i < end; i++) {
+ if (thread->shared->HasVerificationFailedYet()) {
+ break;
+ }
+ std::string from_db;
+ std::string keystr = Key(i);
+ Slice k = keystr;
+ Slice pfx = Slice(keystr.data(), prefix_to_use);
+ // Reseek when the prefix changes
+ if (prefix_to_use > 0 && prefix.compare(pfx) != 0) {
+ iter->Seek(k);
+ seek_key = keystr;
+ prefix = Slice(seek_key.data(), prefix_to_use);
+ }
+ Status s = iter->status();
+ if (iter->Valid()) {
+ Slice iter_key = iter->key();
+ if (iter->key().compare(k) > 0) {
+ s = Status::NotFound(Slice());
+ } else if (iter->key().compare(k) == 0) {
+ from_db = iter->value().ToString();
+ iter->Next();
+ } else if (iter_key.compare(k) < 0) {
+ VerificationAbort(shared, "An out of range key was found",
+ static_cast<int>(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();
+ }
+ VerifyValue(static_cast<int>(cf), i, options, shared, from_db, s,
+ true);
+ if (from_db.length()) {
+ PrintKeyValue(static_cast<int>(cf), static_cast<uint32_t>(i),
+ from_db.data(), from_db.length());
+ }
+ }
+ } else {
+ // Use Get to verify this range
+ for (auto i = start; i < end; i++) {
+ if (thread->shared->HasVerificationFailedYet()) {
+ break;
+ }
+ std::string from_db;
+ std::string keystr = Key(i);
+ Slice k = keystr;
+ Status s = db_->Get(options, column_families_[cf], k, &from_db);
+ VerifyValue(static_cast<int>(cf), i, options, shared, from_db, s,
+ true);
+ if (from_db.length()) {
+ PrintKeyValue(static_cast<int>(cf), static_cast<uint32_t>(i),
+ from_db.data(), from_db.length());
+ }
+ }
+ }
+ }
+ }
+
+ 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 = ToString(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; }
+
+ Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& 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;
+ Status s = db_->Get(read_opts, cfh, key, &from_db);
+ 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, "TestGet error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ }
+ return s;
+ }
+
+ std::vector<Status> TestMultiGet(
+ ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ size_t num_keys = rand_keys.size();
+ std::vector<std::string> key_str;
+ std::vector<Slice> keys;
+ key_str.reserve(num_keys);
+ keys.reserve(num_keys);
+ std::vector<PinnableSlice> values(num_keys);
+ std::vector<Status> statuses(num_keys);
+ ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]];
+
+ // 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.
+#ifndef ROCKSDB_LITE
+ Transaction* txn = nullptr;
+ if (use_txn) {
+ WriteOptions wo;
+ Status s = NewTxn(wo, &txn);
+ if (!s.ok()) {
+ fprintf(stderr, "NewTxn: %s\n", s.ToString().c_str());
+ std::terminate();
+ }
+ }
+#endif
+ for (size_t i = 0; i < num_keys; ++i) {
+ key_str.emplace_back(Key(rand_keys[i]));
+ keys.emplace_back(key_str.back());
+#ifndef ROCKSDB_LITE
+ 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();
+ }
+ }
+ }
+#endif
+ }
+
+ if (!use_txn) {
+ db_->MultiGet(read_opts, cfh, num_keys, keys.data(), values.data(),
+ statuses.data());
+ } else {
+#ifndef ROCKSDB_LITE
+ txn->MultiGet(read_opts, cfh, num_keys, keys.data(), values.data(),
+ statuses.data());
+ RollbackTxn(txn);
+#endif
+ }
+
+ for (const 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 if (s.IsMergeInProgress() && use_txn) {
+ // With txn this is sometimes expected.
+ thread->stats.AddGets(1, 1);
+ } else {
+ // errors case
+ fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ }
+ }
+ return statuses;
+ }
+
+ Status TestPrefixScan(ThreadState* thread, const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys) override {
+ auto cfh = column_families_[rand_column_families[0]];
+ std::string key_str = Key(rand_keys[0]);
+ Slice key = key_str;
+ Slice prefix = Slice(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;
+ }
+
+ Iterator* iter = db_->NewIterator(ro_copy, cfh);
+ unsigned long count = 0;
+ for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix);
+ iter->Next()) {
+ ++count;
+ }
+
+ assert(count <= GetPrefixKeyCount(prefix.ToString(), upper_bound));
+
+ Status s = iter->status();
+ if (iter->status().ok()) {
+ thread->stats.AddPrefixes(1, count);
+ } else {
+ fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
+ thread->stats.AddErrors(1);
+ }
+ delete iter;
+ return s;
+ }
+
+ Status TestPut(ThreadState* thread, WriteOptions& write_opts,
+ const ReadOptions& read_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys, char (&value)[100],
+ std::unique_ptr<MutexLock>& lock) override {
+ auto shared = thread->shared;
+ int64_t max_key = shared->GetMaxKey();
+ int64_t rand_key = rand_keys[0];
+ int rand_column_family = rand_column_families[0];
+ 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)));
+ }
+
+ std::string key_str = Key(rand_key);
+ Slice key = key_str;
+ ColumnFamilyHandle* cfh = column_families_[rand_column_family];
+
+ if (FLAGS_verify_before_write) {
+ std::string key_str2 = Key(rand_key);
+ Slice k = key_str2;
+ std::string from_db;
+ Status s = db_->Get(read_opts, cfh, k, &from_db);
+ if (!VerifyValue(rand_column_family, rand_key, read_opts, shared, from_db,
+ s, true)) {
+ return s;
+ }
+ }
+ uint32_t value_base = thread->rand.Next() % shared->UNKNOWN_SENTINEL;
+ size_t sz = GenerateValue(value_base, value, sizeof(value));
+ 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) {
+ s = db_->Merge(write_opts, cfh, key, v);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->Merge(cfh, key, v);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ } else {
+ if (!FLAGS_use_txn) {
+ s = db_->Put(write_opts, cfh, key, v);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->Put(cfh, key, v);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ }
+ shared->Put(rand_column_family, rand_key, value_base, false /* pending */);
+ if (!s.ok()) {
+ 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<uint32_t>(rand_key), value,
+ sz);
+ return s;
+ }
+
+ Status TestDelete(ThreadState* thread, WriteOptions& write_opts,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) override {
+ 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();
+
+ // OPERATION delete
+ // If the chosen key does not allow overwrite and it does not exist,
+ // choose another key.
+ while (!shared->AllowsOverwrite(rand_key) &&
+ !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)));
+ }
+
+ 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) {
+ s = db_->Delete(write_opts, cfh, key);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->Delete(cfh, key);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ shared->Delete(rand_column_family, rand_key, false /* pending */);
+ thread->stats.AddDeletes(1);
+ if (!s.ok()) {
+ 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) {
+ s = db_->SingleDelete(write_opts, cfh, key);
+ } else {
+#ifndef ROCKSDB_LITE
+ Transaction* txn;
+ s = NewTxn(write_opts, &txn);
+ if (s.ok()) {
+ s = txn->SingleDelete(cfh, key);
+ if (s.ok()) {
+ s = CommitTxn(txn);
+ }
+ }
+#endif
+ }
+ shared->SingleDelete(rand_column_family, rand_key, false /* pending */);
+ thread->stats.AddSingleDeletes(1);
+ if (!s.ok()) {
+ 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<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) override {
+ // OPERATION delete range
+ std::vector<std::unique_ptr<MutexLock>> 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) {
+ lock.reset();
+ rand_key =
+ thread->rand.Next() % (max_key - FLAGS_range_deletion_width + 1);
+ range_locks.emplace_back(
+ new MutexLock(shared->GetMutexForKey(rand_column_family, rand_key)));
+ } else {
+ range_locks.emplace_back(std::move(lock));
+ }
+ for (int j = 1; j < FLAGS_range_deletion_width; ++j) {
+ if (((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;
+ Status s = db_->DeleteRange(write_opts, cfh, key, end_key);
+ if (!s.ok()) {
+ 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;
+ }
+
+#ifdef ROCKSDB_LITE
+ void TestIngestExternalFile(
+ ThreadState* /* thread */,
+ const std::vector<int>& /* rand_column_families */,
+ const std::vector<int64_t>& /* rand_keys */,
+ std::unique_ptr<MutexLock>& /* lock */) override {
+ assert(false);
+ fprintf(stderr,
+ "RocksDB lite does not support "
+ "TestIngestExternalFile\n");
+ std::terminate();
+ }
+#else
+ void TestIngestExternalFile(ThreadState* thread,
+ const std::vector<int>& rand_column_families,
+ const std::vector<int64_t>& rand_keys,
+ std::unique_ptr<MutexLock>& lock) override {
+ const std::string sst_filename =
+ FLAGS_db + "/." + ToString(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<std::unique_ptr<MutexLock>> range_locks;
+ std::vector<uint32_t> values;
+ SharedState* shared = thread->shared;
+
+ // Grab locks, set pending state on expected values, and add keys
+ for (int64_t key = key_base;
+ s.ok() && key < std::min(key_base + FLAGS_ingest_external_file_width,
+ shared->GetMaxKey());
+ ++key) {
+ if (key == key_base) {
+ range_locks.emplace_back(std::move(lock));
+ } else if ((key & ((1 << FLAGS_log2_keys_per_lock) - 1)) == 0) {
+ range_locks.emplace_back(
+ new MutexLock(shared->GetMutexForKey(column_family, 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()) {
+ 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();
+ }
+ int64_t key = key_base;
+ for (int32_t value : values) {
+ shared->Put(column_family, key, value, false /* pending */);
+ ++key;
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ bool VerifyValue(int cf, int64_t key, const ReadOptions& /*opts*/,
+ SharedState* shared, const std::string& value_from_db,
+ const Status& s, bool strict = false) const {
+ if (shared->HasVerificationFailedYet()) {
+ return false;
+ }
+ // compare value_from_db with the value in the shared state
+ char value[kValueMaxLen];
+ uint32_t value_base = shared->Get(cf, key);
+ if (value_base == SharedState::UNKNOWN_SENTINEL) {
+ return true;
+ }
+ if (value_base == SharedState::DELETION_SENTINEL && !strict) {
+ return true;
+ }
+
+ if (s.ok()) {
+ if (value_base == SharedState::DELETION_SENTINEL) {
+ VerificationAbort(shared, "Unexpected value found", cf, key);
+ return false;
+ }
+ size_t sz = GenerateValue(value_base, value, sizeof(value));
+ if (value_from_db.length() != sz) {
+ VerificationAbort(shared, "Length of value read is not equal", cf, key);
+ return false;
+ }
+ if (memcmp(value_from_db.data(), value, sz) != 0) {
+ VerificationAbort(shared, "Contents of value read don't match", cf,
+ key);
+ return false;
+ }
+ } else {
+ if (value_base != SharedState::DELETION_SENTINEL) {
+ VerificationAbort(shared, "Value not found: " + s.ToString(), cf, key);
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+StressTest* CreateNonBatchedOpsStressTest() {
+ return new NonBatchedOpsStressTest();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/defs.bzl b/storage/rocksdb/rocksdb/defs.bzl
new file mode 100644
index 00000000..83e9a579
--- /dev/null
+++ b/storage/rocksdb/rocksdb/defs.bzl
@@ -0,0 +1,42 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+# defs.bzl - Definitions for Facebook-specific buck build integration
+# in TARGETS
+
+load("@fbcode_macros//build_defs:cpp_binary.bzl", "cpp_binary")
+load("@fbcode_macros//build_defs:custom_unittest.bzl", "custom_unittest")
+
+def test_binary(
+ test_name,
+ test_cc,
+ parallelism,
+ rocksdb_arch_preprocessor_flags,
+ rocksdb_os_preprocessor_flags,
+ rocksdb_compiler_flags,
+ rocksdb_preprocessor_flags,
+ rocksdb_external_deps,
+ rocksdb_os_deps,
+ extra_deps,
+ extra_compiler_flags):
+ TEST_RUNNER = native.package_name() + "/buckifier/rocks_test_runner.sh"
+
+ ttype = "gtest" if parallelism == "parallel" else "simple"
+ test_bin = test_name + "_bin"
+
+ cpp_binary(
+ name = test_bin,
+ srcs = [test_cc],
+ arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
+ os_preprocessor_flags = rocksdb_os_preprocessor_flags,
+ compiler_flags = rocksdb_compiler_flags + extra_compiler_flags,
+ preprocessor_flags = rocksdb_preprocessor_flags,
+ deps = [":rocksdb_test_lib"] + extra_deps,
+ os_deps = rocksdb_os_deps,
+ external_deps = rocksdb_external_deps,
+ )
+
+ custom_unittest(
+ name = test_name,
+ command = [TEST_RUNNER, "$(location :{})".format(test_bin)],
+ type = ttype,
+ )
diff --git a/storage/rocksdb/rocksdb/docs/.gitignore b/storage/rocksdb/rocksdb/docs/.gitignore
new file mode 100644
index 00000000..3938549c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/.gitignore
@@ -0,0 +1,8 @@
+.DS_STORE
+_site/
+*.swo
+*.swp
+_site
+.sass-cache
+*.psd
+*~
diff --git a/storage/rocksdb/rocksdb/docs/CNAME b/storage/rocksdb/rocksdb/docs/CNAME
new file mode 100644
index 00000000..827d1c0e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/CNAME
@@ -0,0 +1 @@
+rocksdb.org \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/CONTRIBUTING.md b/storage/rocksdb/rocksdb/docs/CONTRIBUTING.md
new file mode 100644
index 00000000..2c5842fb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/CONTRIBUTING.md
@@ -0,0 +1,115 @@
+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:
+
+```
+<div class="gridBlock">
+ <div class="blockElement twoByGridBlock alignLeft">
+ <div class="blockContent">
+ <h3>Your Features</h3>
+ <ul>
+ <li>The <a href="http://example.org/">Example</a></li>
+ <li><a href="http://example.com">Another Example</a></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="blockElement twoByGridBlock alignLeft">
+ <div class="blockContent">
+ <h3>More information</h3>
+ <p>
+ Stuff here
+ </p>
+ </div>
+ </div>
+</div>
+```
+
+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 `<!--truncate-->` 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/storage/rocksdb/rocksdb/docs/Gemfile b/storage/rocksdb/rocksdb/docs/Gemfile
new file mode 100644
index 00000000..93dc8b0d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/Gemfile
@@ -0,0 +1,2 @@
+source 'https://rubygems.org'
+gem 'github-pages', '~> 104'
diff --git a/storage/rocksdb/rocksdb/docs/Gemfile.lock b/storage/rocksdb/rocksdb/docs/Gemfile.lock
new file mode 100644
index 00000000..78dc919a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/Gemfile.lock
@@ -0,0 +1,146 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ activesupport (4.2.7)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ addressable (2.4.0)
+ coffee-script (2.4.1)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.12.2)
+ colorator (1.1.0)
+ concurrent-ruby (1.0.5)
+ ethon (0.11.0)
+ ffi (>= 1.3.0)
+ execjs (2.7.0)
+ faraday (0.15.2)
+ multipart-post (>= 1.2, < 3)
+ ffi (1.9.25)
+ forwardable-extended (2.6.0)
+ gemoji (2.1.0)
+ github-pages (104)
+ activesupport (= 4.2.7)
+ github-pages-health-check (= 1.2.0)
+ jekyll (>= 3.8.4)
+ jekyll-avatar (= 0.4.2)
+ jekyll-coffeescript (= 1.0.1)
+ jekyll-feed (= 0.8.0)
+ jekyll-gist (= 1.4.0)
+ jekyll-github-metadata (= 2.2.0)
+ jekyll-mentions (= 1.2.0)
+ jekyll-paginate (= 1.1.0)
+ jekyll-redirect-from (= 0.11.0)
+ jekyll-sass-converter (= 1.3.0)
+ jekyll-seo-tag (= 2.1.0)
+ jekyll-sitemap (= 0.12.0)
+ jekyll-swiss (= 0.4.0)
+ jemoji (= 0.7.0)
+ kramdown (= 1.11.1)
+ liquid (= 3.0.6)
+ listen (= 3.0.6)
+ mercenary (~> 0.3)
+ minima (= 2.0.0)
+ rouge (= 1.11.1)
+ terminal-table (~> 1.4)
+ github-pages-health-check (1.2.0)
+ addressable (~> 2.3)
+ net-dns (~> 0.8)
+ octokit (~> 4.0)
+ public_suffix (~> 1.4)
+ typhoeus (~> 0.7)
+ html-pipeline (2.4.2)
+ activesupport (>= 2)
+ nokogiri (~> 1.8.2)
+ i18n (0.7.0)
+ jekyll (3.8.4)
+ addressable (~> 2.4)
+ colorator (~> 1.0)
+ jekyll-sass-converter (~> 1.0)
+ jekyll-watch (~> 1.1)
+ kramdown (~> 1.3)
+ liquid (~> 3.0)
+ mercenary (~> 0.3.3)
+ pathutil (~> 0.9)
+ rouge (~> 1.7)
+ safe_yaml (~> 1.0)
+ jekyll-avatar (0.4.2)
+ jekyll (~> 3.0)
+ jekyll-coffeescript (1.0.1)
+ coffee-script (~> 2.2)
+ jekyll-feed (0.8.0)
+ jekyll (~> 3.3)
+ jekyll-gist (1.4.0)
+ octokit (~> 4.2)
+ jekyll-github-metadata (2.2.0)
+ jekyll (~> 3.1)
+ octokit (~> 4.0, != 4.4.0)
+ jekyll-mentions (1.2.0)
+ activesupport (~> 4.0)
+ html-pipeline (~> 2.3)
+ jekyll (~> 3.0)
+ jekyll-paginate (1.1.0)
+ jekyll-redirect-from (0.11.0)
+ jekyll (>= 2.0)
+ jekyll-sass-converter (1.3.0)
+ sass (~> 3.2)
+ jekyll-seo-tag (2.1.0)
+ jekyll (~> 3.3)
+ jekyll-sitemap (0.12.0)
+ jekyll (~> 3.3)
+ jekyll-swiss (0.4.0)
+ jekyll-watch (1.5.0)
+ listen (~> 3.0, < 3.1)
+ jemoji (0.7.0)
+ activesupport (~> 4.0)
+ gemoji (~> 2.0)
+ html-pipeline (~> 2.2)
+ jekyll (>= 3.0)
+ json (1.8.3)
+ kramdown (1.11.1)
+ liquid (3.0.6)
+ listen (3.0.6)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9.7)
+ mercenary (0.3.6)
+ mini_portile2 (2.3.0)
+ minima (2.0.0)
+ minitest (5.9.1)
+ multipart-post (2.0.0)
+ net-dns (0.8.0)
+ nokogiri (~> 1.8.2)
+ mini_portile2 (~> 2.3.0)
+ octokit (4.4.1)
+ sawyer (~> 0.7.0, >= 0.5.3)
+ pathutil (0.14.0)
+ forwardable-extended (~> 2.6)
+ public_suffix (1.5.3)
+ rb-fsevent (0.9.8)
+ rb-inotify (0.9.7)
+ ffi (>= 0.5.0)
+ rouge (1.11.1)
+ safe_yaml (1.0.4)
+ sass (3.4.22)
+ sawyer (0.7.0)
+ addressable (>= 2.3.5, < 2.5)
+ faraday (~> 0.8, < 0.10)
+ terminal-table (1.7.3)
+ unicode-display_width (~> 1.1.1)
+ thread_safe (0.3.5)
+ typhoeus (0.8.0)
+ ethon (>= 0.8.0)
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+ unicode-display_width (1.1.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ github-pages (~> 104)
+
+BUNDLED WITH
+ 1.13.1
diff --git a/storage/rocksdb/rocksdb/docs/LICENSE-DOCUMENTATION b/storage/rocksdb/rocksdb/docs/LICENSE-DOCUMENTATION
new file mode 100644
index 00000000..1f255c9f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/LICENSE-DOCUMENTATION
@@ -0,0 +1,385 @@
+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/storage/rocksdb/rocksdb/docs/README.md b/storage/rocksdb/rocksdb/docs/README.md
new file mode 100644
index 00000000..0ae8978b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/README.md
@@ -0,0 +1,80 @@
+## 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/storage/rocksdb/rocksdb/docs/TEMPLATE-INFORMATION.md b/storage/rocksdb/rocksdb/docs/TEMPLATE-INFORMATION.md
new file mode 100644
index 00000000..9175bc0c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/TEMPLATE-INFORMATION.md
@@ -0,0 +1,17 @@
+## 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/storage/rocksdb/rocksdb/docs/_config.yml b/storage/rocksdb/rocksdb/docs/_config.yml
new file mode 100644
index 00000000..2e5cee09
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_config.yml
@@ -0,0 +1,85 @@
+# 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]
+
+gems:
+ - jekyll-redirect-from
diff --git a/storage/rocksdb/rocksdb/docs/_data/authors.yml b/storage/rocksdb/rocksdb/docs/_data/authors.yml
new file mode 100644
index 00000000..13225be9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/authors.yml
@@ -0,0 +1,70 @@
+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
diff --git a/storage/rocksdb/rocksdb/docs/_data/features.yml b/storage/rocksdb/rocksdb/docs/_data/features.yml
new file mode 100644
index 00000000..d692c184
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/features.yml
@@ -0,0 +1,19 @@
+- 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/storage/rocksdb/rocksdb/docs/_data/nav.yml b/storage/rocksdb/rocksdb/docs/_data/nav.yml
new file mode 100644
index 00000000..108de025
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/nav.yml
@@ -0,0 +1,30 @@
+- 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/master/include/rocksdb
+ category: external
+
+- title: API (Java)
+ href: https://github.com/facebook/rocksdb/tree/master/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/storage/rocksdb/rocksdb/docs/_data/nav_docs.yml b/storage/rocksdb/rocksdb/docs/_data/nav_docs.yml
new file mode 100644
index 00000000..8cdfd2d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/nav_docs.yml
@@ -0,0 +1,3 @@
+- title: Quick Start
+ items:
+ - id: getting-started
diff --git a/storage/rocksdb/rocksdb/docs/_data/powered_by.yml b/storage/rocksdb/rocksdb/docs/_data/powered_by.yml
new file mode 100644
index 00000000..a780cfe4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/powered_by.yml
@@ -0,0 +1 @@
+# Fill in later if desired
diff --git a/storage/rocksdb/rocksdb/docs/_data/powered_by_highlight.yml b/storage/rocksdb/rocksdb/docs/_data/powered_by_highlight.yml
new file mode 100644
index 00000000..a780cfe4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/powered_by_highlight.yml
@@ -0,0 +1 @@
+# Fill in later if desired
diff --git a/storage/rocksdb/rocksdb/docs/_data/promo.yml b/storage/rocksdb/rocksdb/docs/_data/promo.yml
new file mode 100644
index 00000000..9a72aa84
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_data/promo.yml
@@ -0,0 +1,6 @@
+# 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/storage/rocksdb/rocksdb/docs/_docs/faq.md b/storage/rocksdb/rocksdb/docs/_docs/faq.md
new file mode 100644
index 00000000..0887a098
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_docs/faq.md
@@ -0,0 +1,48 @@
+---
+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/storage/rocksdb/rocksdb/docs/_docs/getting-started.md b/storage/rocksdb/rocksdb/docs/_docs/getting-started.md
new file mode 100644
index 00000000..8b01dfef
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_docs/getting-started.md
@@ -0,0 +1,78 @@
+---
+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 <assert>
+#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:
+
+- [Architecture Guide](https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide)
+- [Format of an immutable Table file](https://github.com/facebook/rocksdb/wiki/Rocksdb-Table-Format)
+- [Format of a log file](https://github.com/facebook/rocksdb/wiki/Write-Ahead-Log-File-Format)
diff --git a/storage/rocksdb/rocksdb/docs/_includes/blog_pagination.html b/storage/rocksdb/rocksdb/docs/_includes/blog_pagination.html
new file mode 100644
index 00000000..6a1f3343
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/blog_pagination.html
@@ -0,0 +1,28 @@
+<!-- Pagination links - copied from http://jekyllrb.com/docs/pagination/ -->
+{% if paginator.total_pages > 1 %}
+<br />
+<div class="pagination">
+ {% if paginator.previous_page %}
+ <a href="{{ paginator.previous_page_path | replace: '//', '/' }}">&laquo; Prev</a>
+ {% else %}
+ <span>&laquo; Prev</span>
+ {% endif %}
+
+ {% for page in (1..paginator.total_pages) %}
+ {% if page == paginator.page %}
+ <em>{{ page }}</em>
+ {% elsif page == 1 %}
+ <a href="{{ '/blog' }}">{{ page }}</a>
+ {% else %}
+ <a href="{{ site.paginate_path | replace: '//', '/' | replace: ':num', page }}">{{ page }}</a>
+ {% endif %}
+ {% endfor %}
+
+ {% if paginator.next_page %}
+ <a href="{{ paginator.next_page_path | replace: '//', '/' }}">Next &raquo;</a>
+ {% else %}
+ <span>Next &raquo;</span>
+ {% endif %}
+</div>
+<br />
+{% endif %}
diff --git a/storage/rocksdb/rocksdb/docs/_includes/content/gridblocks.html b/storage/rocksdb/rocksdb/docs/_includes/content/gridblocks.html
new file mode 100644
index 00000000..49c5e591
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/content/gridblocks.html
@@ -0,0 +1,5 @@
+<div class="gridBlock">
+{% for item in {{include.data_source}} %}
+ {% include content/items/gridblock.html item=item layout=include.layout imagealign=include.imagealign align=include.align %}
+{% endfor %}
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/content/items/gridblock.html b/storage/rocksdb/rocksdb/docs/_includes/content/items/gridblock.html
new file mode 100644
index 00000000..58c9e7fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/content/items/gridblock.html
@@ -0,0 +1,37 @@
+{% 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 %}
+
+<div class="blockElement {{ layout }} {{ imagealign }} {{ align }}">
+ {% if item.image %}
+ <div class="blockImage">
+ <img src="/static/{{ item.image }}" alt="{{ item.title }}" title="{{ item.title }}" />
+ </div>
+ {% endif %}
+ <div class="blockContent">
+ <h3>{{ item.title }}</h3>
+ {% if item.text %}
+ {{ item.text | markdownify }}
+ {% endif %}
+ </div>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/doc.html b/storage/rocksdb/rocksdb/docs/_includes/doc.html
new file mode 100644
index 00000000..a7950004
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/doc.html
@@ -0,0 +1,25 @@
+<div class="post">
+ <header class="post-header">
+ <h1 class="post-title">{% if include.truncate %}<a href="{{ page.url | absolute_url }}">{{ page.title }}</a>{% else %}{{ page.title }}{% endif %}</h1>
+ </header>
+
+ <article class="post-content">
+ {% if include.truncate %}
+ {% if page.content contains '<!--truncate-->' %}
+ {{ page.content | split:'<!--truncate-->' | first }}
+ <div class="read-more">
+ <a href="{{ page.url | absolute_url }}" >
+ ...Read More
+ </a>
+ </div>
+ {% else %}
+ {{ page.content }}
+ {% endif %}
+ {% else %}
+ {{ content }}
+
+ <p><a class="edit-page-link" href="https://github.com/{{ site.ghrepo }}/blob/master/docs/{{ page.path }}" target="_blank">Edit on GitHub</a></p>
+ {% endif %}
+ </article>
+ {% include doc_paging.html %}
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/doc_paging.html b/storage/rocksdb/rocksdb/docs/_includes/doc_paging.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/doc_paging.html
diff --git a/storage/rocksdb/rocksdb/docs/_includes/footer.html b/storage/rocksdb/rocksdb/docs/_includes/footer.html
new file mode 100644
index 00000000..dd9494ae
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/footer.html
@@ -0,0 +1,33 @@
+<div class="footerContainer">
+ <div id="footer_wrap" class="wrapper footerWrapper">
+ <div class="footerBlocks">
+ <div id="fb_oss" class="footerSection fbOpenSourceFooter">
+ <svg class="facebookOSSLogoSvg" viewBox="0 0 1133.9 1133.9" x="0px" y="0px">
+ <g>
+ <path class="logoRing outerRing" d="M 498.3 3.7 c 153.6 88.9 307.3 177.7 461.1 266.2 c 7.6 4.4 10.3 9.1 10.3 17.8 c -0.3 179.1 -0.2 358.3 0 537.4 c 0 8.1 -2.4 12.8 -9.7 17.1 c -154.5 88.9 -308.8 178.1 -462.9 267.5 c -9 5.2 -15.5 5.3 -24.6 0.1 c -153.9 -89.2 -307.9 -178 -462.1 -266.8 C 3 838.8 0 833.9 0 825.1 c 0.3 -179.1 0.2 -358.3 0 -537.4 c 0 -8.6 2.6 -13.6 10.2 -18 C 164.4 180.9 318.4 92 472.4 3 C 477 -1.5 494.3 -0.7 498.3 3.7 Z M 48.8 555.3 c 0 79.9 0.2 159.9 -0.2 239.8 c -0.1 10 3 15.6 11.7 20.6 c 137.2 78.8 274.2 157.8 411 237.3 c 9.9 5.7 17 5.7 26.8 0.1 c 137.5 -79.8 275.2 -159.2 412.9 -238.5 c 7.4 -4.3 10.5 -8.9 10.5 -17.8 c -0.3 -160.2 -0.3 -320.5 0 -480.7 c 0 -8.8 -2.8 -13.6 -10.3 -18 C 772.1 218 633.1 137.8 494.2 57.4 c -6.5 -3.8 -11.5 -4.5 -18.5 -0.5 C 336.8 137.4 197.9 217.7 58.8 297.7 c -7.7 4.4 -10.2 9.2 -10.2 17.9 C 48.9 395.5 48.8 475.4 48.8 555.3 Z" />
+ <path class="logoRing middleRing" d="M 184.4 555.9 c 0 -33.3 -1 -66.7 0.3 -100 c 1.9 -48 24.1 -86 64.7 -110.9 c 54.8 -33.6 110.7 -65.5 167 -96.6 c 45.7 -25.2 92.9 -24.7 138.6 1 c 54.4 30.6 108.7 61.5 162.2 93.7 c 44 26.5 67.3 66.8 68 118.4 c 0.9 63.2 0.9 126.5 0 189.7 c -0.7 50.6 -23.4 90.7 -66.6 116.9 c -55 33.4 -110.8 65.4 -167.1 96.5 c -43.4 24 -89 24.2 -132.3 0.5 c -57.5 -31.3 -114.2 -64 -170 -98.3 c -41 -25.1 -62.9 -63.7 -64.5 -112.2 C 183.5 621.9 184.3 588.9 184.4 555.9 Z M 232.9 556.3 c 0 29.5 0.5 59.1 -0.1 88.6 c -0.8 39.2 16.9 67.1 50.2 86.2 c 51.2 29.4 102.2 59.2 153.4 88.4 c 31.4 17.9 63.6 18.3 95 0.6 c 53.7 -30.3 107.1 -61.2 160.3 -92.5 c 29.7 -17.5 45 -44.5 45.3 -78.8 c 0.6 -61.7 0.5 -123.5 0 -185.2 c -0.3 -34.4 -15.3 -61.5 -44.9 -79 C 637.7 352.6 583 320.8 527.9 290 c -27.5 -15.4 -57.2 -16.1 -84.7 -0.7 c -56.9 31.6 -113.4 64 -169.1 97.6 c -26.4 15.9 -40.7 41.3 -41.1 72.9 C 232.6 491.9 232.9 524.1 232.9 556.3 Z" />
+ <path class="logoRing innerRing" d="M 484.9 424.4 c 69.8 -2.8 133.2 57.8 132.6 132 C 617 630 558.5 688.7 484.9 689.1 c -75.1 0.4 -132.6 -63.6 -132.7 -132.7 C 352.1 485 413.4 421.5 484.9 424.4 Z M 401.3 556.7 c -3.4 37.2 30.5 83.6 83 84.1 c 46.6 0.4 84.8 -37.6 84.9 -84 c 0.1 -46.6 -37.2 -84.4 -84.2 -84.6 C 432.2 472.1 397.9 518.3 401.3 556.7 Z" />
+ </g>
+ </svg>
+ <h2>Facebook Open Source</h2>
+ </div>
+ <div class="footerSection">
+ <a class="footerLink" href="https://code.facebook.com/projects/" target="_blank">Open Source Projects</a>
+ <a class="footerLink" href="https://github.com/facebook/" target="_blank">GitHub</a>
+ <a class="footerLink" href="https://twitter.com/fbOpenSource" target="_blank">Twitter</a>
+ </div>
+ <div class="footerSection rightAlign">
+ <a class="footerLink" href="https://github.com/{{ site.ghrepo }}" target="_blank">Contribute to this project on GitHub</a>
+ </div>
+ </div>
+ </div>
+</div>
+<script>
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+ ga('create', '{{ site.gacode }}', 'auto');
+ ga('send', 'pageview');
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/head.html b/storage/rocksdb/rocksdb/docs/_includes/head.html
new file mode 100644
index 00000000..10845ec1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/head.html
@@ -0,0 +1,23 @@
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <meta property="og:url" content="{{ page.url | replace:'index.html','' | absolute_url }}" />
+ <meta property="og:site_name" content="{{ site.title }}"/>
+ <meta property="og:title" content="{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}" />
+ <meta property="og:image" content="{{ '/static/og_image.png' | absolute_url }}" />
+ <meta property="og:description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}" />
+
+ <link rel="stylesheet" href="{{ '/css/main.css' }}" media="screen">
+ <link rel="icon" href="{{ '/static/favicon.png' }}" type="image/x-icon">
+ {% if site.searchconfig %}
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
+ {% endif %}
+
+ <title>{% if page.title %}{{ page.title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
+ <meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
+
+ <link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
+ <link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ '/feed.xml' | absolute_url }}" />
+</head>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/header.html b/storage/rocksdb/rocksdb/docs/_includes/header.html
new file mode 100644
index 00000000..8108d222
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/header.html
@@ -0,0 +1,19 @@
+<div class="headerContainer">
+ <div id="header_wrap" class="wrapper headerWrapper">
+ <div class="inner">
+ <img class="projectLogo" height="200px" src="{{ '/static/logo.svg' }}" alt="{{ site.title }}" title="{{ site.title }}" />
+ <h1 id="project_title">{{ site.title }}</h1>
+ <h2 id="project_tagline" class="fbossFontLight">{{ site.tagline }}</h2>
+
+ <section id="intro">
+ <p>{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}</p>
+ </section>
+ <div id="promo" class="section promoSection">
+ {% for promo in site.data.promo %}
+ {% include plugins/{{promo.type}}.html button_href=promo.href button_text=promo.text %}
+ <div class="gridClear"></div>
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/hero.html b/storage/rocksdb/rocksdb/docs/_includes/hero.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/hero.html
diff --git a/storage/rocksdb/rocksdb/docs/_includes/home_header.html b/storage/rocksdb/rocksdb/docs/_includes/home_header.html
new file mode 100644
index 00000000..90880d17
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/home_header.html
@@ -0,0 +1,22 @@
+<div class="homeContainer">
+ <div class="homeSplashFade">
+ <div id="home_wrap" class="wrapper homeWrapper">
+ <div id="inner">
+ <h2 id="project_tagline">{{ site.tagline }}</h2>
+ <section id="intro">
+ <p>{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}</p>
+ </section>
+ <div id="promo" class="section promoSection">
+ {% for promo in site.data.promo %}
+ <div class="promoRow">
+ {% include plugins/{{promo.type}}.html href=promo.href text=promo.text children=promo.children %}
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ <div class="projectLogo">
+ <img src="{{ '/static/logo.svg' }}" alt="{{ site.title }}">
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/katex_import.html b/storage/rocksdb/rocksdb/docs/_includes/katex_import.html
new file mode 100644
index 00000000..6d6b7cf4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/katex_import.html
@@ -0,0 +1,3 @@
+<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.2.0/katex.min.css">
+<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.2.0/katex.min.js"></script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/katex_render.html b/storage/rocksdb/rocksdb/docs/_includes/katex_render.html
new file mode 100644
index 00000000..56e2e897
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/katex_render.html
@@ -0,0 +1,210 @@
+<script type="text/javascript">
+/* global katex */
+
+var findEndOfMath = function(delimiter, text, startIndex) {
+ // Adapted from
+ // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
+ var index = startIndex;
+ var braceLevel = 0;
+
+ var delimLength = delimiter.length;
+
+ while (index < text.length) {
+ var character = text[index];
+
+ if (braceLevel <= 0 &&
+ text.slice(index, index + delimLength) === delimiter) {
+ return index;
+ } else if (character === "\\") {
+ index++;
+ } else if (character === "{") {
+ braceLevel++;
+ } else if (character === "}") {
+ braceLevel--;
+ }
+
+ index++;
+ }
+
+ return -1;
+};
+
+var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
+ var finalData = [];
+
+ for (var i = 0; i < startData.length; i++) {
+ if (startData[i].type === "text") {
+ var text = startData[i].data;
+
+ var lookingForLeft = true;
+ var currIndex = 0;
+ var nextIndex;
+
+ nextIndex = text.indexOf(leftDelim);
+ if (nextIndex !== -1) {
+ currIndex = nextIndex;
+ finalData.push({
+ type: "text",
+ data: text.slice(0, currIndex)
+ });
+ lookingForLeft = false;
+ }
+
+ while (true) {
+ if (lookingForLeft) {
+ nextIndex = text.indexOf(leftDelim, currIndex);
+ if (nextIndex === -1) {
+ break;
+ }
+
+ finalData.push({
+ type: "text",
+ data: text.slice(currIndex, nextIndex)
+ });
+
+ currIndex = nextIndex;
+ } else {
+ nextIndex = findEndOfMath(
+ rightDelim,
+ text,
+ currIndex + leftDelim.length);
+ if (nextIndex === -1) {
+ break;
+ }
+
+ finalData.push({
+ type: "math",
+ data: text.slice(
+ currIndex + leftDelim.length,
+ nextIndex),
+ rawData: text.slice(
+ currIndex,
+ nextIndex + rightDelim.length),
+ display: display
+ });
+
+ currIndex = nextIndex + rightDelim.length;
+ }
+
+ lookingForLeft = !lookingForLeft;
+ }
+
+ finalData.push({
+ type: "text",
+ data: text.slice(currIndex)
+ });
+ } else {
+ finalData.push(startData[i]);
+ }
+ }
+
+ return finalData;
+};
+
+var splitWithDelimiters = function(text, delimiters) {
+ var data = [{type: "text", data: text}];
+ for (var i = 0; i < delimiters.length; i++) {
+ var delimiter = delimiters[i];
+ data = splitAtDelimiters(
+ data, delimiter.left, delimiter.right,
+ delimiter.display || false);
+ }
+ return data;
+};
+
+var renderMathInText = function(text, delimiters) {
+ var data = splitWithDelimiters(text, delimiters);
+
+ var fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].type === "text") {
+ fragment.appendChild(document.createTextNode(data[i].data));
+ } else {
+ var span = document.createElement("span");
+ var math = data[i].data;
+ try {
+ katex.render(math, span, {
+ displayMode: data[i].display
+ });
+ } catch (e) {
+ if (!(e instanceof katex.ParseError)) {
+ throw e;
+ }
+ console.error(
+ "KaTeX auto-render: Failed to parse `" + data[i].data +
+ "` with ",
+ e
+ );
+ fragment.appendChild(document.createTextNode(data[i].rawData));
+ continue;
+ }
+ fragment.appendChild(span);
+ }
+ }
+
+ return fragment;
+};
+
+var renderElem = function(elem, delimiters, ignoredTags) {
+ for (var i = 0; i < elem.childNodes.length; i++) {
+ var childNode = elem.childNodes[i];
+ if (childNode.nodeType === 3) {
+ // Text node
+ var frag = renderMathInText(childNode.textContent, delimiters);
+ i += frag.childNodes.length - 1;
+ elem.replaceChild(frag, childNode);
+ } else if (childNode.nodeType === 1) {
+ // Element node
+ var shouldRender = ignoredTags.indexOf(
+ childNode.nodeName.toLowerCase()) === -1;
+
+ if (shouldRender) {
+ renderElem(childNode, delimiters, ignoredTags);
+ }
+ }
+ // Otherwise, it's something else, and ignore it.
+ }
+};
+
+var defaultOptions = {
+ delimiters: [
+ {left: "$$", right: "$$", display: true},
+ {left: "\\[", right: "\\]", display: true},
+ {left: "\\(", right: "\\)", display: false}
+ // LaTeX uses this, but it ruins the display of normal `$` in text:
+ // {left: "$", right: "$", display: false}
+ ],
+
+ ignoredTags: [
+ "script", "noscript", "style", "textarea", "pre", "code"
+ ]
+};
+
+var extend = function(obj) {
+ // Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
+ var source, prop;
+ for (var i = 1, length = arguments.length; i < length; i++) {
+ source = arguments[i];
+ for (prop in source) {
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
+ obj[prop] = source[prop];
+ }
+ }
+ }
+ return obj;
+};
+
+var renderMathInElement = function(elem, options) {
+ if (!elem) {
+ throw new Error("No element provided to render");
+ }
+
+ options = extend({}, defaultOptions, options);
+
+ renderElem(elem, options.delimiters, options.ignoredTags);
+};
+
+renderMathInElement(document.body);
+
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav.html b/storage/rocksdb/rocksdb/docs/_includes/nav.html
new file mode 100644
index 00000000..9c6fed06
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav.html
@@ -0,0 +1,37 @@
+<div id="fixed_header" class="fixedHeaderContainer{% if include.alwayson %} visible{% endif %}">
+ <div class="headerWrapper wrapper">
+ <header>
+ <a href="{{ '/' | absolute_url }}">
+ <img src="{{ '/static/logo.svg' }}">
+ <h2>{{ site.title }}</h2>
+ </a>
+
+ <div class="navigationWrapper navigationFull" id="flat_nav">
+ <nav class="navigation">
+ <ul>
+ {% for item in site.data.nav %}
+ <li class="navItem{% if page.collection == item.category or page.category == item.category %} navItemActive{% endif %}">
+ {% if item.category == "external" %}
+ <a href="{{ item.href }}">{{ item.title }}</a>
+ {% else %}
+ {% comment %}
+ I removed `relative_url` from here for now until the problem we are having with
+ GitHub pages is resolved. Yes, I know this is exactly the same as the if above.
+ See: https://github.com/facebook/rocksdb/commit/800e51553ee029f29581f7f338cbc988c7f6da62
+ {% endcomment %}
+ <a href="{{ item.href }}">{{ item.title }}</a>
+ {% endif %}
+ </li>
+ {% endfor %}
+ {% if site.searchconfig %}
+ {% include nav_search.html inputselector="search_input" %}
+ {% endif %}
+ </ul>
+ </nav>
+ </div>
+ <div class="navigationWrapper navigationSlider" id="navigation_wrap">
+ {% include nav/header_nav.html %}
+ </div>
+ </header>
+ </div>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav.html b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav.html
new file mode 100644
index 00000000..a3c7a2dd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav.html
@@ -0,0 +1,64 @@
+<div class="docsNavContainer">
+ <nav class="toc" id="doc_nav">
+ <div class="toggleNav" id="collection_nav">
+ <section class="navWrapper wrapper">
+ <div class="navBreadcrumb wrapper">
+ <div class="navToggle" id="collection_nav_toggler">
+ <i></i>
+ </div>
+ <h2>
+ <a href="{{ include.sectionpath }}">{{ include.sectiontitle }}</a>
+ {% if include.currentgroup %}
+ <i>›</i>
+ <span>{{ include.currentgroup }}</span>
+ {% endif %}
+ </h2>
+ </div>
+ <div class="navGroups">
+ {% if include.type == "blog" %}
+ {% assign grouptitle = "All Posts" %}
+ {% assign groupitems = include.navdata %}
+ {% include nav/collection_nav_group.html %}
+ {% else %}
+ {% for group in include.navdata %}
+ {% assign grouptitle = group.title %}
+ {% for item in group.items %}
+ {% if item.id == page.docid %}
+ {% assign currentgroup = group %}
+ {% endif %}
+ {% endfor %}
+ {% include nav/collection_nav_group.html %}
+ {% endfor %}
+ {% endif %}
+ </div>
+ </section>
+ </div>
+ </nav>
+</div>
+<script>
+ var docsevent = document.createEvent('Event');
+ docsevent.initEvent('docs_slide', true, true);
+ document.addEventListener('docs_slide', function (e) {
+ document.body.classList.toggle('docsSliderActive');
+ }, false);
+
+ var collectionNav = document.getElementById('collection_nav');
+ var collectionNavToggler =
+ document.getElementById('collection_nav_toggler');
+ collectionNavToggler.addEventListener('click', function(e) {
+ collectionNav.classList.toggle('toggleNavActive');
+ document.dispatchEvent(docsevent);
+ });
+
+ var groups = document.getElementsByClassName('navGroup');
+ for(var i = 0; i < groups.length; i++) {
+ var thisGroup = groups[i];
+ thisGroup.onclick = function() {
+ for(var j = 0; j < groups.length; j++) {
+ var group = groups[j];
+ group.classList.remove('navGroupActive');
+ }
+ this.classList.add('navGroupActive');
+ }
+ }
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group.html b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group.html
new file mode 100644
index 00000000..b236ac5e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group.html
@@ -0,0 +1,19 @@
+<div class="navGroup{% if currentgroup == group %} navGroupActive navGroupCurrent{% endif %}">
+ <h3><i>+</i><span>{{ grouptitle }}</span></h3>
+ <ul>
+ {% if include.data_collection %}
+ {% for item in group.items %}
+ {% for collectionitem in include.data_collection %}
+ {% if collectionitem.docid == item.id %}
+ {% assign groupitem = collectionitem %}
+ {% include nav/collection_nav_group_item.html %}
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% else %}
+ {% for groupitem in groupitems %}
+ {% include nav/collection_nav_group_item.html %}
+ {% endfor %}
+ {% endif %}
+ </ul>
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group_item.html b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group_item.html
new file mode 100644
index 00000000..fbb063de
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav/collection_nav_group_item.html
@@ -0,0 +1 @@
+<li class="navListItem"><a class="navItem" href="{{ groupitem.url | absolute_url }}">{{ groupitem.title }}</a></li>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav/header_nav.html b/storage/rocksdb/rocksdb/docs/_includes/nav/header_nav.html
new file mode 100644
index 00000000..0fe945cd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav/header_nav.html
@@ -0,0 +1,30 @@
+<div id="header_nav">
+ <div class="navSlideout">
+ <i class="menuExpand" id="header_nav_expander"><span></span><span></span><span></span></i>
+ </div>
+ <nav class="slidingNav">
+ <ul>
+ {% for item in site.data.nav %}
+ <li class="navItem">
+ <a href="{{ item.href }}"{% if item.category == "external" %} target="_blank"{% endif %}>{{ item.title }}</a>
+ </li>
+ {% endfor %}
+ {% if site.searchconfig %}
+ {% include nav_search.html inputselector="search_input_react" %}
+ {% endif %}
+ </ul>
+ </nav>
+</div>
+<script>
+ var event = document.createEvent('Event');
+ event.initEvent('slide', true, true);
+ document.addEventListener('slide', function (e) {
+ document.body.classList.toggle('sliderActive');
+ }, false);
+ var headerNav = document.getElementById('header_nav');
+ var headerNavExpander = document.getElementById('header_nav_expander');
+ headerNavExpander.addEventListener('click', function(e) {
+ headerNav.classList.toggle('navSlideoutActive');
+ document.dispatchEvent(event);
+ }, false);
+</script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/nav_search.html b/storage/rocksdb/rocksdb/docs/_includes/nav_search.html
new file mode 100644
index 00000000..84956b9f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/nav_search.html
@@ -0,0 +1,15 @@
+<li class="navSearchWrapper">
+ <input id="{{ include.inputselector }}" type="search" />
+</li>
+<script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
+<script>
+// For Algolia search
+(function() {
+ // Algolia
+ docsearch({
+ apiKey: '{{ site.searchconfig.apikey }}',
+ indexName: '{{ site.searchconfig.indexname }}',
+ inputSelector: '#{{ include.inputselector }}',
+ });
+}());
+</script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/all_share.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/all_share.html
new file mode 100644
index 00000000..59b00d61
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/all_share.html
@@ -0,0 +1,3 @@
+<div class="pluginBlock allShareBlock">
+ {% include plugins/like_button.html %}{% include plugins/twitter_share.html %}{% include plugins/google_share.html %}
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/ascii_cinema.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/ascii_cinema.html
new file mode 100644
index 00000000..7d3f9714
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/ascii_cinema.html
@@ -0,0 +1,2 @@
+<div class="ascii-cinema pluginBlock"></div>
+<script type="text/javascript" src="https://asciinema.org/a/{{ include.href }}.js" id="asciicast-{{ include.href }}" async data-autoplay="true" data-loop="true" data-speed="2" data-t="23"></script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/button.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/button.html
new file mode 100644
index 00000000..9e499fe3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/button.html
@@ -0,0 +1,6 @@
+<div class="pluginWrapper buttonWrapper">
+ <a
+ class="button"
+ href="{{ include.href }}"
+ >{{ include.text }}</a>
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/github_star.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/github_star.html
new file mode 100644
index 00000000..6aea70fc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/github_star.html
@@ -0,0 +1,4 @@
+<div class="pluginWrapper ghStarWrapper">
+ <a aria-label="Star {{ site.ghrepo }} on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/{{ site.ghrepo }}#stargazers_count" data-count-href="/{{ site.ghrepo }}/stargazers" data-style="mega" data-icon="octicon-star" href="https://github.com/{{ site.ghrepo }}" class="github-button">Star</a>
+</div>
+<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/github_watch.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/github_watch.html
new file mode 100644
index 00000000..64233b57
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/github_watch.html
@@ -0,0 +1,4 @@
+<div class="pluginWrapper ghWatchWrapper">
+ <a aria-label="Watch {{ site.ghrepo }} on GitHub" data-count-aria-label="# watchers on GitHub" data-count-api="/repos/{{ site.ghrepo }}#subscribers_count" data-count-href="/{{ site.ghrepo }}/watchers" data-style="mega" data-icon="octicon-eye" href="https://github.com/{{ site.ghrepo }}" class="github-button">Watch</a>
+</div>
+<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/google_share.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/google_share.html
new file mode 100644
index 00000000..1b557db8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/google_share.html
@@ -0,0 +1,5 @@
+<div class="pluginBlock">
+ <div class="g-plusone" data-size="medium"></div>
+</div>
+
+<script src="https://apis.google.com/js/platform.js" async defer></script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/iframe.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/iframe.html
new file mode 100644
index 00000000..525b59f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/iframe.html
@@ -0,0 +1,6 @@
+<div class="iframeContent">
+ <iframe class="pluginIframe" src="{{ include.href }}" seamless></iframe>
+</div>
+<div class="iframePreview">
+ {% include plugins/button.html href=include.href text=include.text %}
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/like_button.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/like_button.html
new file mode 100644
index 00000000..bcb8a7be
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/like_button.html
@@ -0,0 +1,18 @@
+<div class="fb-like pluginWrapper likeButtonWrapper" data-layout="button_count" data-action="like" data-show-faces="true" data-share="true"></div>
+<script>
+ window.fbAsyncInit = function() {
+ FB.init({
+ appId : '{{ site.fbappid }}',
+ xfbml : true,
+ version : 'v2.3'
+ });
+ };
+
+ (function(d, s, id){
+ var js, fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) {return;}
+ js = d.createElement(s); js.id = id;
+ js.src = "//connect.facebook.net/en_US/sdk.js";
+ fjs.parentNode.insertBefore(js, fjs);
+ }(document, 'script', 'facebook-jssdk'));
+</script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/plugin_row.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/plugin_row.html
new file mode 100644
index 00000000..800f50b8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/plugin_row.html
@@ -0,0 +1,5 @@
+<div class="pluginRowBlock">
+{% for child in include.children %}
+ {% include plugins/{{child.type}}.html href=child.href text=child.text %}
+{% endfor %}
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/post_social_plugins.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/post_social_plugins.html
new file mode 100644
index 00000000..a2ecb90e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/post_social_plugins.html
@@ -0,0 +1,41 @@
+<div class="postSocialPlugins">
+ <a
+ href="https://twitter.com/share"
+ class="twitter-share-button"
+ data-url="{{ page.url | replace:'index.html','' | absolute_url }}"
+ data-text="{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}"
+ data-hashtags="flowtype">Tweet</a>
+ <div
+ class="fb-like"
+ data-href="{{ page.url | replace:'index.html','' | absolute_url }}"
+ data-layout="button_count"
+ data-action="like"
+ data-show-faces="false"
+ data-share="true"></div>
+</div>
+<script>
+ window.fbAsyncInit = function() {
+ FB.init({
+ appId : '{{ site.fbappid }}',
+ xfbml : true,
+ version : 'v2.2'
+ });
+ };
+
+ (function(d, s, id){
+ var js, fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) {return;}
+ js = d.createElement(s); js.id = id;
+ js.src = "//connect.facebook.net/en_US/sdk.js";
+ fjs.parentNode.insertBefore(js, fjs);
+ }(document, 'script', 'facebook-jssdk'));
+</script>
+
+<script>!function(d,s,id){
+ var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
+ if(!d.getElementById(id)){
+ js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
+ fjs.parentNode.insertBefore(js,fjs);
+ }
+ }(document, 'script', 'twitter-wjs');
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/slideshow.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/slideshow.html
new file mode 100644
index 00000000..69fa2b30
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/slideshow.html
@@ -0,0 +1,88 @@
+<div class="slideshowBlock pluginWrapper" id="slideshow"></div>
+<script>
+ var slideshowData = [
+ {% for image in site.data.slideshow %}
+ {
+ id : "{{ image.id }}",
+ imagesrc : "{{ image.src }}",
+ tooltip : "{{ image.tooltip }}",
+ href : "{{ image.link }}",
+ },
+ {% endfor %}
+ ];
+</script>
+<script src="http://fb.me/react-with-addons-0.13.1.min.js"></script>
+<script type="text/javascript">
+ var Slideshow = React.createClass({displayName: "Slideshow",
+ getInitialState: function() {
+ return {
+ currentSlide: 0,
+ };
+ },
+ getDefaultProps: function() {
+ return {
+ data: slideshowData,
+ };
+ },
+ handleSelect: function(id) {
+ var index = this.props.data.map(function (el, elIndex) {
+ return (
+ elIndex
+ );
+ });
+ var currentIndex = index.indexOf(id);
+ this.setState({
+ currentSlide: currentIndex,
+ });
+ },
+ render: function() {
+ return (
+ React.createElement("div", {className: "slideshow"},
+ React.createElement("div", {className: "slides"},
+ this.props.data.map(this.renderSlide)
+ ),
+ React.createElement("div", {className: "pagination"},
+ this.props.data.map(this.renderPager)
+ )
+ )
+ );
+ },
+ renderSlide: function(child, index) {
+ var classes = React.addons.classSet({
+ 'slide': true,
+ 'slideActive': this.state.currentSlide === index,
+ });
+ if (child.href) {
+ return (
+ React.createElement("div", {key: index, className: classes},
+ React.createElement("a", {href: child.href, alt: child.tooltip, title: child.tooltip},
+ React.createElement("img", {src: child.imagesrc, alt: child.tooltip, title: child.tooltip})
+ )
+ )
+ );
+ }
+ return (
+ React.createElement("div", {key: index, className: classes},
+ React.createElement("img", {src: child.imagesrc, alt: child.tooltip})
+ )
+ );
+ },
+ renderPager: function(child, index) {
+ var classes = React.addons.classSet({
+ 'pager': true,
+ 'pagerActive': this.state.currentSlide === index,
+ });
+ return (
+ React.createElement("span", {key: index, className: classes, onClick: this.handleSelect.bind(this, index)})
+ );
+ },
+ });
+
+ function render(slideshowData) {
+ React.render(
+ React.createElement(Slideshow, {data: slideshowData}),
+ document.getElementById('slideshow')
+ );
+ }
+ render(slideshowData);
+</script> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_follow.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_follow.html
new file mode 100644
index 00000000..b0f25dc6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_follow.html
@@ -0,0 +1,12 @@
+<div class="pluginBlock">
+ <a href="https://twitter.com/{{ include.href }}" class="twitter-follow-button pluginBlock" data-show-count="false">Follow @{{ include.href }}</a>
+</div>
+
+<script>!function(d,s,id){
+ var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
+ if(!d.getElementById(id)){
+ js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
+ fjs.parentNode.insertBefore(js,fjs);
+ }
+ }(document, 'script', 'twitter-wjs');
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_share.html b/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_share.html
new file mode 100644
index 00000000..a60f2a8d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/plugins/twitter_share.html
@@ -0,0 +1,11 @@
+<div class="pluginWrapper twitterSharePlugin">
+ <a href="https://twitter.com/share" class="twitter-share-button" data-hashtags="{{ site.title| replace: ' ', '' }}">Tweet</a>
+</div>
+<script>!function(d,s,id){
+ var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
+ if(!d.getElementById(id)){js=d.createElement(s);
+ js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
+ fjs.parentNode.insertBefore(js,fjs);
+}
+}(document, 'script', 'twitter-wjs');
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/post.html b/storage/rocksdb/rocksdb/docs/_includes/post.html
new file mode 100644
index 00000000..3ae0a2a8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/post.html
@@ -0,0 +1,40 @@
+<div class="post">
+ <header class="post-header">
+ <div style="display: flex; align-content: center; align-items: center; justify-content: center">
+ {% for author_idx in page.author %}
+ <div style="padding: 16px; display: inline-block; text-align: center">
+ {% assign author = site.data.authors[author_idx] %}
+ {% if author.fbid %}
+ <div class="authorPhoto">
+ <img src="http://graph.facebook.com/{{ author.fbid }}/picture/" alt="{{ author.fullname }}" title="{{ author.fullname }}" />
+ </div>
+ {% endif %}
+ {% if author.full_name %}
+ <p class="post-authorName">{{ author.full_name }}</p>
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
+ <h1 class="post-title">{% if include.truncate %}<a href="{{ page.url | absolute_url }}">{{ page.title }}</a>{% else %}{{ page.title }}{% endif %}</h1>
+ <p class="post-meta">Posted {{ page.date | date: '%B %d, %Y' }}{% if page.meta %} • {{ page.meta }}{% endif %}</p>
+ </header>
+ <article class="post-content">
+ {% if include.truncate %}
+ {% if page.content contains '<!--truncate-->' %}
+ {{ page.content | split:'<!--truncate-->' | first | markdownify }}
+ <div class="read-more">
+ <a href="{{ page.url | absolute_url }}" >
+ Read More
+ </a>
+ </div>
+ {% else %}
+ {{ page.content | markdownify }}
+ {% endif %}
+ {% else %}
+ {{ content }}
+ {% endif %}
+ {% unless include.truncate %}
+ {% include plugins/like_button.html %}
+ {% endunless %}
+ </article>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/powered_by.html b/storage/rocksdb/rocksdb/docs/_includes/powered_by.html
new file mode 100644
index 00000000..c629429c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/powered_by.html
@@ -0,0 +1,28 @@
+{% if site.data.powered_by.first.items or site.data.powered_by_highlight.first.items %}
+<div class="poweredByContainer">
+ <div class="wrapper mainWrapper poweredByWrapper">
+ {% if site.data.powered_by_highlight.first.title %}
+ <h2>{{ site.data.powered_by_highlight.first.title }}</h2>
+ {% else %}
+ <h2>{{ site.data.powered_by.first.title }}</h2>
+ {% endif %}
+ {% if site.data.powered_by_highlight.first.items %}
+ <div class="poweredByItems">
+ {% for item in site.data.powered_by_highlight.first.items %}
+ <div class="poweredByItem itemLarge">
+ <a href="{{ item.url }}" target="_blank"><img src="{{ item.img }}" alt="{{ item.name }}" /></a>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+ <div class="poweredByItems">
+ {% for item in site.data.powered_by.first.items %}
+ <div class="poweredByItem itemSmall">
+ <a href="{{ item.url }}" target="_blank">{{ item.name }}</a>
+ </div>
+ {% endfor %}
+ </div>
+ <div class="poweredByMessage">Does your app use {{ site.title }}? Add it to this list with <a href="https://github.com/{{ site.ghrepo }}/edit/gh-pages/_data/powered_by.yml" target="_blank">a pull request!</a></div>
+ </div>
+</div>
+{% endif %}
diff --git a/storage/rocksdb/rocksdb/docs/_includes/social_plugins.html b/storage/rocksdb/rocksdb/docs/_includes/social_plugins.html
new file mode 100644
index 00000000..9b36580d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/social_plugins.html
@@ -0,0 +1,31 @@
+<a
+ href="https://twitter.com/share"
+ class="twitter-share-button"
+ data-url="http://facebook.github.io/fresco{{ page.url }}"
+ data-text="Fresco | {{ page.title }}"
+ data-hashtags="fresco">Tweet</a>
+<div
+ class="fb-like"
+ data-href="http://facebook.github.io/fresco{{ page.url }}"
+ data-layout="standard"
+ data-action="like"
+ data-show-faces="true"
+ data-share="true"></div>
+
+<div id="fb-root"></div>
+<script>(function(d, s, id) {
+ var js, fjs = d.getElementsByTagName(s)[0];
+ if (d.getElementById(id)) return;
+ js = d.createElement(s); js.id = id;
+ js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.0";
+ fjs.parentNode.insertBefore(js, fjs);
+}(document, 'script', 'facebook-jssdk'));</script>
+
+<script>!function(d,s,id){
+ var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
+ if(!d.getElementById(id)){
+ js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
+ fjs.parentNode.insertBefore(js,fjs);
+ }
+ }(document, 'script', 'twitter-wjs');
+</script>
diff --git a/storage/rocksdb/rocksdb/docs/_includes/ui/button.html b/storage/rocksdb/rocksdb/docs/_includes/ui/button.html
new file mode 100644
index 00000000..729ccc33
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_includes/ui/button.html
@@ -0,0 +1 @@
+<span class="buttonWrap {{ include.align }}"><a class="button blockButton fbossFontLight pluginBlock margin{{ include.margin }}" target="{{ include.button_target }}" href="{{ include.button_href }}">{{ include.button_text }}</a></span> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/basic.html b/storage/rocksdb/rocksdb/docs/_layouts/basic.html
new file mode 100644
index 00000000..65bd2106
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/basic.html
@@ -0,0 +1,12 @@
+---
+layout: doc_default
+---
+
+<div class="mainContainer blogContainer postContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ <div class="post basicPost">
+ {{ content }}
+ </div>
+ </div>
+</div>
+
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/blog.html b/storage/rocksdb/rocksdb/docs/_layouts/blog.html
new file mode 100644
index 00000000..1b0da413
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/blog.html
@@ -0,0 +1,11 @@
+---
+category: blog
+layout: blog_default
+---
+
+<div class="mainContainer blogContainer postContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ {{ content }}
+ </div>
+</div>
+
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/blog_default.html b/storage/rocksdb/rocksdb/docs/_layouts/blog_default.html
new file mode 100644
index 00000000..a29d58d3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/blog_default.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ {% include head.html %}
+ <body class="docsNavVisible">
+ {% include nav.html alwayson=true %}
+ <div class="navPusher">
+ <div class="docMainWrapper wrapper">
+ {% include nav/collection_nav.html navdata=site.posts type="blog" sectionpath="/blog/" sectiontitle="Blog" %}
+ {{ content }}
+ </div>
+ {% include footer.html %}
+ </div>
+ </body>
+</html>
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/default.html b/storage/rocksdb/rocksdb/docs/_layouts/default.html
new file mode 100644
index 00000000..0167d9fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/default.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ {% include head.html %}
+ <body>
+ {% include nav.html alwayson=true %}
+ <div class="navPusher">
+ {{ content }}
+ {% include footer.html %}
+ </div>
+ </body>
+
+</html>
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/doc_default.html b/storage/rocksdb/rocksdb/docs/_layouts/doc_default.html
new file mode 100644
index 00000000..4a413924
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/doc_default.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ {% include head.html %}
+ <body class="docsNavVisible">
+ {% include nav.html alwayson=true %}
+ <div class="navPusher">
+ <div class="docMainWrapper wrapper">
+ {% include nav/collection_nav.html navdata=site.data.nav_docs type="docs" sectionpath="/docs/" sectiontitle="Docs" data_collection=site.docs %}
+ {{ content }}
+ </div>
+ {% include footer.html %}
+ </div>
+ </body>
+</html>
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/doc_page.html b/storage/rocksdb/rocksdb/docs/_layouts/doc_page.html
new file mode 100644
index 00000000..dba761e7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/doc_page.html
@@ -0,0 +1,10 @@
+---
+layout: doc_default
+---
+
+<div class="mainContainer documentContainer postContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ {{ content }}
+ </div>
+</div>
+
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/docs.html b/storage/rocksdb/rocksdb/docs/_layouts/docs.html
new file mode 100644
index 00000000..749dafab
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/docs.html
@@ -0,0 +1,5 @@
+---
+layout: doc_page
+---
+
+{% include doc.html %} \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/home.html b/storage/rocksdb/rocksdb/docs/_layouts/home.html
new file mode 100644
index 00000000..e3c320f5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/home.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ {% include head.html %}
+ <body>
+ {% include nav.html alwayson=true %}
+ <div class="navPusher">
+ {% include home_header.html %}
+ <div class="mainContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ {{ content }}
+ </div>
+ {% include powered_by.html %}
+ </div>
+ {% include footer.html %}
+ </div>
+ </body>
+</html>
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/page.html b/storage/rocksdb/rocksdb/docs/_layouts/page.html
new file mode 100644
index 00000000..bec36805
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/page.html
@@ -0,0 +1,3 @@
+---
+layout: blog
+---
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/plain.html b/storage/rocksdb/rocksdb/docs/_layouts/plain.html
new file mode 100644
index 00000000..fccc02ce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/plain.html
@@ -0,0 +1,10 @@
+---
+layout: default
+---
+
+<div class="mainContainer blogContainer postContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ {{ content }}
+ </div>
+</div>
+
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/post.html b/storage/rocksdb/rocksdb/docs/_layouts/post.html
new file mode 100644
index 00000000..4c92cf21
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/post.html
@@ -0,0 +1,8 @@
+---
+collection: blog
+layout: blog
+---
+
+<div class="lonePost">
+{% include post.html %}
+</div> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/redirect.html b/storage/rocksdb/rocksdb/docs/_layouts/redirect.html
new file mode 100644
index 00000000..c24f8174
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/redirect.html
@@ -0,0 +1,6 @@
+<html>
+<head>
+ <meta http-equiv="refresh" content="0; {{ page.destination }}">
+</head>
+<body></body>
+</html>
diff --git a/storage/rocksdb/rocksdb/docs/_layouts/top-level.html b/storage/rocksdb/rocksdb/docs/_layouts/top-level.html
new file mode 100644
index 00000000..fccc02ce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_layouts/top-level.html
@@ -0,0 +1,10 @@
+---
+layout: default
+---
+
+<div class="mainContainer blogContainer postContainer">
+ <div id="main_wrap" class="wrapper mainWrapper">
+ {{ content }}
+ </div>
+</div>
+
diff --git a/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown
new file mode 100644
index 00000000..f9e4a544
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown
@@ -0,0 +1,135 @@
+---
+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
+
+<!--truncate-->
+
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown
new file mode 100644
index 00000000..89ffb2d9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown
@@ -0,0 +1,54 @@
+---
+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?
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown
new file mode 100644
index 00000000..7ccbdbaa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown
@@ -0,0 +1,53 @@
+---
+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.
+
+<!--truncate-->
+
+[![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/storage/rocksdb/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown
new file mode 100644
index 00000000..7be7842a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown
@@ -0,0 +1,40 @@
+---
+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).
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown
new file mode 100644
index 00000000..368055d2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown
@@ -0,0 +1,28 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-05-14-lock.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-05-14-lock.markdown
new file mode 100644
index 00000000..12009cc8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-05-14-lock.markdown
@@ -0,0 +1,88 @@
+---
+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:
+
+<!--truncate-->
+
+ * 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/storage/rocksdb/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown
new file mode 100644
index 00000000..61c90dc9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown
@@ -0,0 +1,24 @@
+---
+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
+
+<!--truncate-->
+
+Check out the full [change log](https://github.com/facebook/rocksdb/blob/3.0.fb/HISTORY.md).
diff --git a/storage/rocksdb/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown
new file mode 100644
index 00000000..30156742
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown
@@ -0,0 +1,20 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown
new file mode 100644
index 00000000..6a641f23
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown
@@ -0,0 +1,47 @@
+---
+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
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown
new file mode 100644
index 00000000..4411c7ae
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown
@@ -0,0 +1,89 @@
+---
+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).
+
+<!--truncate-->
+
+At the beginning of `GetImpl()`, it used to do this:
+
+
+ <span class="zw-portion">mutex_.Lock();
+ </span>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/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown
new file mode 100644
index 00000000..e4eba6af
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown
@@ -0,0 +1,30 @@
+---
+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()
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown
new file mode 100644
index 00000000..d858e4fa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown
@@ -0,0 +1,34 @@
+---
+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().
+
+<!--truncate-->
+
+ * **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/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown
new file mode 100644
index 00000000..22178f7c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown
@@ -0,0 +1,74 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown
new file mode 100644
index 00000000..96fa50a4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown
@@ -0,0 +1,52 @@
+---
+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.
+
+
+<!--truncate-->
+
+## 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/storage/rocksdb/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown
new file mode 100644
index 00000000..1878a5a5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown
@@ -0,0 +1,38 @@
+---
+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.
+
+<!--truncate-->
+
+**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/storage/rocksdb/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown
new file mode 100644
index 00000000..f18de0bb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown
@@ -0,0 +1,112 @@
+---
+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:
+
+<!--truncate-->
+
+LevelDB code:
+
+```c++
+#include <string>
+#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 <string>
+#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 <string>
+#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/storage/rocksdb/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown
new file mode 100644
index 00000000..cddc0dd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown
@@ -0,0 +1,41 @@
+---
+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:
+
+<!--truncate-->
+
+```c++
+#include <string>
+#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/storage/rocksdb/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown
new file mode 100644
index 00000000..7f9f7765
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown
@@ -0,0 +1,20 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown
new file mode 100644
index 00000000..1ffe2c53
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown
@@ -0,0 +1,16 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown
new file mode 100644
index 00000000..f3a55faa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown
@@ -0,0 +1,10 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown
new file mode 100644
index 00000000..b3e2703f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown
@@ -0,0 +1,92 @@
+---
+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:
+
+<!--truncate-->
+
+**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/storage/rocksdb/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown
new file mode 100644
index 00000000..fe7b7b26
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown
@@ -0,0 +1,78 @@
+---
+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.
+
+<!--truncate-->
+
+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/master/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<double>(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<double>(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/storage/rocksdb/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown
new file mode 100644
index 00000000..b6bb47d5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown
@@ -0,0 +1,30 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown
new file mode 100644
index 00000000..0ff3a054
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown
@@ -0,0 +1,29 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown
new file mode 100644
index 00000000..332a29f0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown
@@ -0,0 +1,193 @@
+---
+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:
+
+<!--truncate-->
+
+<table width="637" >
+<tbody >
+<tr style="border:2px solid #000000" >
+
+<td style="padding:3px" >ThreadID
+</td>
+
+<td >140716395198208
+</td>
+
+<td >140716416169728
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >DB
+</td>
+
+<td >db1
+</td>
+
+<td >db2
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >CF
+</td>
+
+<td >default
+</td>
+
+<td >picachu
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >ThreadType
+</td>
+
+<td >High Pri
+</td>
+
+<td >Low Pri
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >Operation
+</td>
+
+<td >Flush
+</td>
+
+<td >Compaction
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >ElapsedTime
+</td>
+
+<td >143.459 ms
+</td>
+
+<td >607.538 ms
+</td>
+</tr>
+<tr >
+
+<td style="padding:3px" >Stage
+</td>
+
+<td >FlushJob::WriteLevel0Table
+</td>
+
+<td >CompactionJob::Install
+</td>
+</tr>
+<tr >
+
+<td style="vertical-align:top;padding:3px" >OperationProperties
+</td>
+
+<td style="vertical-align:top;padding:3px" >
+BytesMemtables 4092938
+BytesWritten 1050701
+</td>
+
+<td style="vertical-align:top" >
+BaseInputLevel 1
+BytesRead 4876417
+BytesWritten 4140109
+IsDeletion 0
+IsManual 0
+IsTrivialMove 0
+JobID 146
+OutputLevel 2
+TotalInputBytes 4883044
+</td>
+</tr>
+</tbody>
+</table>
+
+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/master/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/master/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/storage/rocksdb/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown
new file mode 100644
index 00000000..6852b8ff
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown
@@ -0,0 +1,45 @@
+---
+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.
+
+<!--truncate-->
+
+
+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/storage/rocksdb/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown b/storage/rocksdb/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown
new file mode 100644
index 00000000..b21b04fe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown
@@ -0,0 +1,244 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown
new file mode 100644
index 00000000..ba9ee627
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown
@@ -0,0 +1,51 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown
new file mode 100644
index 00000000..409015cc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown
@@ -0,0 +1,41 @@
+---
+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.
+
+
+<!--truncate-->
+
+
+**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/storage/rocksdb/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown
new file mode 100644
index 00000000..2ba04f39
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown
@@ -0,0 +1,20 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown
new file mode 100644
index 00000000..703449b0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown
@@ -0,0 +1,24 @@
+---
+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.
+
+<!--truncate-->
+
+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/storage/rocksdb/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown
new file mode 100644
index 00000000..247768d3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown
@@ -0,0 +1,60 @@
+---
+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.
+
+<br/>
+
+## 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.
+
+<br/>
+
+<!--truncate-->
+
+## 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.
+
+<br/>
+
+## 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/storage/rocksdb/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown
new file mode 100644
index 00000000..b42a66e3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown
@@ -0,0 +1,48 @@
+---
+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/master/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/master/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.
+
+<br/>
+
+<!--truncate-->
+
+## [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#470-482016)4.7.0 (4/8/2016)
+
+### [](https://github.com/facebook/rocksdb/blob/master/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.
+
+<br/>
+
+## [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#460-3102016)4.6.0 (3/10/2016)
+
+### [](https://github.com/facebook/rocksdb/blob/master/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/master/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/storage/rocksdb/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown
new file mode 100644
index 00000000..87c20eb4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown
@@ -0,0 +1,49 @@
+---
+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.
+
+<!--truncate-->
+
+## 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/storage/rocksdb/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown
new file mode 100644
index 00000000..fb041305
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown
@@ -0,0 +1,26 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown
new file mode 100644
index 00000000..35bafb21
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown
@@ -0,0 +1,15 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown
new file mode 100644
index 00000000..9a43a846
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown
@@ -0,0 +1,50 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown
new file mode 100644
index 00000000..c6ce27d6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown
@@ -0,0 +1,22 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown
new file mode 100644
index 00000000..a537feb0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown
@@ -0,0 +1,34 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown
new file mode 100644
index 00000000..a806541f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown
@@ -0,0 +1,106 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown
new file mode 100644
index 00000000..561dab4c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown
@@ -0,0 +1,39 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown
new file mode 100644
index 00000000..9e838eb7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown
@@ -0,0 +1,60 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown
new file mode 100644
index 00000000..d7856088
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown
@@ -0,0 +1,22 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown
new file mode 100644
index 00000000..3b54ffd5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown
@@ -0,0 +1,22 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown
new file mode 100644
index 00000000..7ac2fec3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown
@@ -0,0 +1,37 @@
+---
+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/master/examples/simple_example.cc) demonstrates that with more examples.
diff --git a/storage/rocksdb/rocksdb/docs/_posts/2017-08-25-flushwal.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-08-25-flushwal.markdown
new file mode 100644
index 00000000..2dc5626a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-08-25-flushwal.markdown
@@ -0,0 +1,26 @@
+---
+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 acheiving 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/storage/rocksdb/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown
new file mode 100644
index 00000000..a22dcaa1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown
@@ -0,0 +1,25 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown
new file mode 100644
index 00000000..d2e6204e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown
@@ -0,0 +1,28 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown b/storage/rocksdb/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown
new file mode 100644
index 00000000..439b3f83
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown
@@ -0,0 +1,41 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown b/storage/rocksdb/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown
new file mode 100644
index 00000000..9f32d3f9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown
@@ -0,0 +1,22 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown b/storage/rocksdb/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown
new file mode 100644
index 00000000..c0e8c442
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown
@@ -0,0 +1,58 @@
+---
+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/master/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/master/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/master/tools/advisor/advisor/rule_parser_example.py)
+creates appropriate DataSource objects (for Rocksdb
+[logs](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_log_parser.py),
+[options](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_options_parser.py),
+[statistics](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_stats_fetcher.py) etc.)
+and provides them to the [Rules Engine](https://github.com/facebook/rocksdb/blob/master/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/master/tools/advisor/README.md).
diff --git a/storage/rocksdb/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown b/storage/rocksdb/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown
new file mode 100644
index 00000000..c4b24ec2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown
@@ -0,0 +1,118 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2018-11-21-delete-range.markdown b/storage/rocksdb/rocksdb/docs/_posts/2018-11-21-delete-range.markdown
new file mode 100644
index 00000000..96fc3562
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2018-11-21-delete-range.markdown
@@ -0,0 +1,292 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown b/storage/rocksdb/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown
new file mode 100644
index 00000000..ce657696
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown
@@ -0,0 +1,36 @@
+---
+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/storage/rocksdb/rocksdb/docs/_posts/2019-08-15-unordered-write.markdown b/storage/rocksdb/rocksdb/docs/_posts/2019-08-15-unordered-write.markdown
new file mode 100644
index 00000000..5f0eb288
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_posts/2019-08-15-unordered-write.markdown
@@ -0,0 +1,56 @@
+---
+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/storage/rocksdb/rocksdb/docs/_sass/_base.scss b/storage/rocksdb/rocksdb/docs/_sass/_base.scss
new file mode 100644
index 00000000..6d26d9fe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_base.scss
@@ -0,0 +1,492 @@
+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/storage/rocksdb/rocksdb/docs/_sass/_blog.scss b/storage/rocksdb/rocksdb/docs/_sass/_blog.scss
new file mode 100644
index 00000000..12a73c1f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_blog.scss
@@ -0,0 +1,47 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_buttons.scss b/storage/rocksdb/rocksdb/docs/_sass/_buttons.scss
new file mode 100644
index 00000000..a0371618
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_buttons.scss
@@ -0,0 +1,47 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_footer.scss b/storage/rocksdb/rocksdb/docs/_sass/_footer.scss
new file mode 100644
index 00000000..5b743951
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_footer.scss
@@ -0,0 +1,82 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_gridBlock.scss b/storage/rocksdb/rocksdb/docs/_sass/_gridBlock.scss
new file mode 100644
index 00000000..679b31c1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_gridBlock.scss
@@ -0,0 +1,115 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_header.scss b/storage/rocksdb/rocksdb/docs/_sass/_header.scss
new file mode 100644
index 00000000..b4cd0711
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_header.scss
@@ -0,0 +1,138 @@
+.fixedHeaderContainer {
+ background: $primary-bg;
+ color: $primary-overlay;
+ height: $header-height;
+ padding: $header-ptop 0 $header-pbot;
+ position: fixed;
+ 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;
+ }
+ }
+ */ \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/docs/_sass/_poweredby.scss b/storage/rocksdb/rocksdb/docs/_sass/_poweredby.scss
new file mode 100644
index 00000000..4155b605
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_poweredby.scss
@@ -0,0 +1,69 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_promo.scss b/storage/rocksdb/rocksdb/docs/_sass/_promo.scss
new file mode 100644
index 00000000..8c9a809d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_promo.scss
@@ -0,0 +1,55 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_react_docs_nav.scss b/storage/rocksdb/rocksdb/docs/_sass/_react_docs_nav.scss
new file mode 100644
index 00000000..f0a651e7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_react_docs_nav.scss
@@ -0,0 +1,332 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_react_header_nav.scss b/storage/rocksdb/rocksdb/docs/_sass/_react_header_nav.scss
new file mode 100644
index 00000000..13c0e562
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_react_header_nav.scss
@@ -0,0 +1,141 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_reset.scss b/storage/rocksdb/rocksdb/docs/_sass/_reset.scss
new file mode 100644
index 00000000..0e5f2e0c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_reset.scss
@@ -0,0 +1,43 @@
+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/storage/rocksdb/rocksdb/docs/_sass/_search.scss b/storage/rocksdb/rocksdb/docs/_sass/_search.scss
new file mode 100644
index 00000000..eadfa11d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_search.scss
@@ -0,0 +1,142 @@
+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/storage/rocksdb/rocksdb/docs/_sass/_slideshow.scss b/storage/rocksdb/rocksdb/docs/_sass/_slideshow.scss
new file mode 100644
index 00000000..cd98a6cd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_slideshow.scss
@@ -0,0 +1,48 @@
+.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/storage/rocksdb/rocksdb/docs/_sass/_syntax-highlighting.scss b/storage/rocksdb/rocksdb/docs/_sass/_syntax-highlighting.scss
new file mode 100644
index 00000000..e55c88a2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_syntax-highlighting.scss
@@ -0,0 +1,129 @@
+
+
+.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/storage/rocksdb/rocksdb/docs/_sass/_tables.scss b/storage/rocksdb/rocksdb/docs/_sass/_tables.scss
new file mode 100644
index 00000000..f847c701
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_sass/_tables.scss
@@ -0,0 +1,47 @@
+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/storage/rocksdb/rocksdb/docs/_top-level/support.md b/storage/rocksdb/rocksdb/docs/_top-level/support.md
new file mode 100644
index 00000000..64165751
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/_top-level/support.md
@@ -0,0 +1,22 @@
+---
+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](/docs/support/faq) about RocksDB.
diff --git a/storage/rocksdb/rocksdb/docs/blog/all.html b/storage/rocksdb/rocksdb/docs/blog/all.html
new file mode 100644
index 00000000..3be2d3bf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/blog/all.html
@@ -0,0 +1,20 @@
+---
+id: all
+layout: blog
+category: blog
+---
+
+<div class="posts">
+ <div class="post">
+ <h1>All Posts</h1>
+ {% for post in site.posts %}
+ {% assign author = site.data.authors[post.author] %}
+ <p>
+ <strong>
+ <a href="{{ post.url }}">{{ post.title }}</a>
+ </strong>
+ on {{ post.date | date: "%B %e, %Y" }} by {{ author.display_name }}
+ </p>
+ {% endfor %}
+ </div>
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/blog/index.html b/storage/rocksdb/rocksdb/docs/blog/index.html
new file mode 100644
index 00000000..9f6b25d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/blog/index.html
@@ -0,0 +1,12 @@
+---
+id: blog
+title: Blog
+layout: blog
+category: blog
+---
+
+<div class="posts">
+ {% for page in site.posts %}
+ {% include post.html truncate=true %}
+ {% endfor %}
+</div>
diff --git a/storage/rocksdb/rocksdb/docs/css/main.scss b/storage/rocksdb/rocksdb/docs/css/main.scss
new file mode 100644
index 00000000..48a3e14e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/css/main.scss
@@ -0,0 +1,149 @@
+---
+# 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;
+}
diff --git a/storage/rocksdb/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md b/storage/rocksdb/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md
new file mode 100644
index 00000000..ef954d63
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md
@@ -0,0 +1,21 @@
+---
+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.
+
+<!--truncate-->
+
+Everything below the `<!--truncate-->` 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/storage/rocksdb/rocksdb/docs/doc-type-examples/docs-hello-world.md b/storage/rocksdb/rocksdb/docs/doc-type-examples/docs-hello-world.md
new file mode 100644
index 00000000..c7094ba5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/doc-type-examples/docs-hello-world.md
@@ -0,0 +1,12 @@
+---
+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/storage/rocksdb/rocksdb/docs/doc-type-examples/top-level-example.md b/storage/rocksdb/rocksdb/docs/doc-type-examples/top-level-example.md
new file mode 100644
index 00000000..67b1fa71
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/doc-type-examples/top-level-example.md
@@ -0,0 +1,8 @@
+---
+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/storage/rocksdb/rocksdb/docs/docs/index.html b/storage/rocksdb/rocksdb/docs/docs/index.html
new file mode 100644
index 00000000..fa6ec8b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/docs/index.html
@@ -0,0 +1,6 @@
+---
+id: docs
+title: Docs
+layout: redirect
+destination: getting-started.html
+---
diff --git a/storage/rocksdb/rocksdb/docs/feed.xml b/storage/rocksdb/rocksdb/docs/feed.xml
new file mode 100644
index 00000000..725f0056
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/feed.xml
@@ -0,0 +1,30 @@
+---
+layout: null
+---
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+ <channel>
+ <title>{{ site.title | xml_escape }}</title>
+ <description>{{ site.description | xml_escape }}</description>
+ <link>https://rocksdb.org/feed.xml</link>
+ <atom:link href="{{ '/feed.xml' | absolute_url }}" rel="self" type="application/rss+xml"/>
+ <pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
+ <lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
+ <generator>Jekyll v{{ jekyll.version }}</generator>
+ {% for post in site.posts limit:10 %}
+ <item>
+ <title>{{ post.title | xml_escape }}</title>
+ <description>{{ post.content | xml_escape }}</description>
+ <pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
+ <link>{{ post.url | absolute_url }}</link>
+ <guid isPermaLink="true">{{ post.url | absolute_url }}</guid>
+ {% for tag in post.tags %}
+ <category>{{ tag | xml_escape }}</category>
+ {% endfor %}
+ {% for cat in post.categories %}
+ <category>{{ cat | xml_escape }}</category>
+ {% endfor %}
+ </item>
+ {% endfor %}
+ </channel>
+</rss>
diff --git a/storage/rocksdb/rocksdb/docs/index.md b/storage/rocksdb/rocksdb/docs/index.md
new file mode 100644
index 00000000..2b9570d2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/index.md
@@ -0,0 +1,9 @@
+---
+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/storage/rocksdb/rocksdb/docs/static/favicon.png b/storage/rocksdb/rocksdb/docs/static/favicon.png
new file mode 100644
index 00000000..7f668f38
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/favicon.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff
new file mode 100644
index 00000000..d1e2579b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff2 b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff2
new file mode 100644
index 00000000..4127b4d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Black.woff2
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff
new file mode 100644
index 00000000..142c1c9c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2 b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2
new file mode 100644
index 00000000..e9862e69
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff
new file mode 100644
index 00000000..d8cf84c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2 b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2
new file mode 100644
index 00000000..aaa5a35c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff
new file mode 100644
index 00000000..e7d4278c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff2 b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff2
new file mode 100644
index 00000000..b6d02883
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Light.woff2
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff
new file mode 100644
index 00000000..bf73a6d9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2 b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2
new file mode 100644
index 00000000..a4d084bf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg b/storage/rocksdb/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg
new file mode 100644
index 00000000..9f931510
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/binaryseek.png b/storage/rocksdb/rocksdb/docs/static/images/binaryseek.png
new file mode 100644
index 00000000..0e213f04
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/binaryseek.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/bloom_fp_vs_bpk.png b/storage/rocksdb/rocksdb/docs/static/images/bloom_fp_vs_bpk.png
new file mode 100644
index 00000000..e83f4d08
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/bloom_fp_vs_bpk.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/compaction/full-range.png b/storage/rocksdb/rocksdb/docs/static/images/compaction/full-range.png
new file mode 100644
index 00000000..5b2c9fc6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/compaction/full-range.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/compaction/l0-l1-contend.png b/storage/rocksdb/rocksdb/docs/static/images/compaction/l0-l1-contend.png
new file mode 100644
index 00000000..bcf8ec73
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/compaction/l0-l1-contend.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/compaction/l1-l2-contend.png b/storage/rocksdb/rocksdb/docs/static/images/compaction/l1-l2-contend.png
new file mode 100644
index 00000000..6dafbbbf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/compaction/l1-l2-contend.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/compaction/part-range-old.png b/storage/rocksdb/rocksdb/docs/static/images/compaction/part-range-old.png
new file mode 100644
index 00000000..1cc723d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/compaction/part-range-old.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png
new file mode 100644
index 00000000..0e213f04
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png
new file mode 100644
index 00000000..accb8639
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png
new file mode 100644
index 00000000..9acc71d8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png
new file mode 100644
index 00000000..71788735
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png
new file mode 100644
index 00000000..54948af2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_collapsed.png b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_collapsed.png
new file mode 100644
index 00000000..52246c2c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_collapsed.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_key_schema.png b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_key_schema.png
new file mode 100644
index 00000000..0a14d4a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_key_schema.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png
new file mode 100644
index 00000000..6003e42a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png
new file mode 100644
index 00000000..39c7097a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_write_path.png b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_write_path.png
new file mode 100644
index 00000000..229dfb34
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/delrange/delrange_write_path.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-blockindex.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-blockindex.jpg
new file mode 100644
index 00000000..9c18bde9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-blockindex.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-fileindex.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-fileindex.jpg
new file mode 100644
index 00000000..51f4e095
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-fileindex.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-filelayout.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-filelayout.jpg
new file mode 100644
index 00000000..771ee60c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-filelayout.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-readiopath.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-readiopath.jpg
new file mode 100644
index 00000000..4993f007
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-readiopath.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-tieredstorage.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-tieredstorage.jpg
new file mode 100644
index 00000000..c362a2d6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-tieredstorage.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/pcache-writeiopath.jpg b/storage/rocksdb/rocksdb/docs/static/images/pcache-writeiopath.jpg
new file mode 100644
index 00000000..561b5518
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/pcache-writeiopath.jpg
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/promo-adapt.svg b/storage/rocksdb/rocksdb/docs/static/images/promo-adapt.svg
new file mode 100644
index 00000000..7cd44434
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/promo-adapt.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg4136" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="256px" width="256px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 2691.7869 1102.369">
+ <g id="layer1" transform="translate(2299.2 62.602)">
+ <path id="path4167" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m392.59 123.17c0.0121-1.9688-0.10333-3.9361-0.34718-5.8898-0.15395-1.3644-0.36978-2.7214-0.64725-4.0662-0.12442-0.56729-0.25999-1.1325-0.40601-1.6946-0.17896-0.75016-0.37707-1.4955-0.59429-2.2357-0.34263-1.1286-0.72934-2.2438-1.1592-3.3423-0.0693-0.18679-0.14009-0.37234-0.21179-0.5591-4.65-11.643-13.94-20.829-25.63-25.358l-235.94-136.22c-21.65-12.935-49.703-5.641-62.314 16.201-12.61 21.841-4.902 49.778 17.123 62.066l95.659 55.229-590.22 0.00006c-278.88 0.00003-504.81 225.93-504.81 504.81l-0.68255 171.28 91.222 0.15305-0.15877-171.43c-0.00003-230.38 184.06-414.43 414.43-414.43l590.22 0.00003-95.659 55.229c-22.025 12.288-29.733 40.224-17.123 62.066s40.659 29.136 62.313 16.205l236.18-136.36c12.515-4.9182 22.19-15.154 26.396-27.926 0.17775-0.55981 0.34438-1.1222 0.5001-1.6886 0.22565-0.75133 0.43169-1.5088 0.61787-2.2712 0.26151-1.1286 0.47932-2.2671 0.65315-3.4128 0.0346-0.20366 0.0681-0.40725 0.10003-0.61218 0.28861-1.8998 0.45557-3.8159 0.50016-5.737zm-1219 630.38-493.8-0.00012c-199.29-0.00006-363.89-137.75-404.69-324.05l283.68-0.00003-95.659 55.228c-22.023 12.288-29.728 40.224-17.119 62.064 12.61 21.841 40.656 29.135 62.309 16.207l235.94-136.22c11.696-4.5293 20.985-13.716 25.642-25.361 0.072-0.18618 0.1416-0.37291 0.2109-0.56005 0.4299-1.0986 0.8167-2.2135 1.1593-3.3423 0.2169-0.74012 0.4154-1.4857 0.5944-2.236 0.1446-0.56217 0.2801-1.1271 0.4061-1.6945 0.2771-1.3448 0.4934-2.7015 0.6471-4.066 0.244-1.9537 0.36-3.9213 0.3474-5.8901-0.045-1.9211-0.2109-3.8374-0.5005-5.7372-0.033-0.20396-0.066-0.40801-0.099-0.61167-0.1747-1.1456-0.3919-2.2841-0.6534-3.413-0.1868-0.76233-0.3923-1.5197-0.6176-2.2713-0.1567-0.56618-0.3227-1.1292-0.5004-1.6889-4.206-12.772-13.881-23.008-26.396-27.926l-236.18-136.36c-21.654-12.936-49.706-5.6434-62.318 16.201s-4.9022 49.785 17.128 62.07l95.659 55.229-293.42-0.00006h-90.38l-428.37 0.00009c-25.208-0.35649-45.834 19.98-45.834 45.19s20.626 45.547 45.834 45.19l436.44-0.00015c42.578 235.73 248.71 414.43 496.74 414.43l910.97-0.00009-95.659 55.229c-22.025 12.287-29.727 40.224-17.117 62.066s40.654 29.136 62.307 16.205l236.16-136.23c11.697-4.529 20.985-13.715 25.643-25.361 0.0717-0.18589 0.1425-0.37219 0.21179-0.55904 0.42982-1.0985 0.81653-2.2134 1.1592-3.3422 0.21722-0.74013 0.41533-1.4857 0.59431-2.236 0.14612-0.56229 0.28169-1.1272 0.40602-1.6946 0.27747-1.3448 0.49336-2.7015 0.64725-4.066 0.24373-1.9537 0.35986-3.9212 0.34715-5.89-0.0446-1.921-0.21149-3.8373-0.50013-5.737-0.0319-0.20396-0.0654-0.40828-0.10003-0.61194-0.17383-1.1456-0.39174-2.284-0.65315-3.4128-0.18618-0.76233-0.39219-1.5197-0.61781-2.2713-0.15575-0.56609-0.32248-1.1291-0.50016-1.6887-4.2062-12.772-13.881-23.008-26.396-27.926l-236.18-136.36c-21.654-12.931-49.697-5.6367-62.307 16.205s-4.908 49.779 17.117 62.066l95.659 55.229h-417.17z" fill-rule="evenodd"/>
+ <path id="path6042" d="m-1139.3 565.22v-376.9" stroke="#000" stroke-linecap="round" stroke-width="90.381" fill="none"/>
+ <path id="path6044" d="m-111.15 993.93v-376.9" stroke="#000" stroke-linecap="round" stroke-width="90.381" fill="none"/>
+ </g>
+</svg>
diff --git a/storage/rocksdb/rocksdb/docs/static/images/promo-flash.svg b/storage/rocksdb/rocksdb/docs/static/images/promo-flash.svg
new file mode 100644
index 00000000..79810c30
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/promo-flash.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+]>
+<svg version="1.1" id="Ebene_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px"
+ height="256px" viewBox="0 0 815.261 584.491" enable-background="new 0 0 815.261 584.491" xml:space="preserve">
+<switch>
+ <foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1">
+ <i:pgfRef xlink:href="#adobe_illustrator_pgf">
+ </i:pgfRef>
+ </foreignObject>
+ <g i:extraneous="self">
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M571.106,229.357l86.136,62.324l-500.458,292.81l15.751-116.254L0,392.661
+ l346.5-136.874l-79.521-80.771l332.31-123.92L538.864,0h276.397l-73.986,207.161l-59.189-36.993L571.106,229.357z M630.295,51.79
+ L312.155,170.168l96.182,81.385L60.602,384.729l133.175,51.79l-14.797,96.183l421.722-251.554l-81.385-66.588l170.168-96.182
+ l44.392,22.195l66.587-125.776H593.302L630.295,51.79z"/>
+ </g>
+</switch>
+</svg>
diff --git a/storage/rocksdb/rocksdb/docs/static/images/promo-operations.svg b/storage/rocksdb/rocksdb/docs/static/images/promo-operations.svg
new file mode 100644
index 00000000..3036294a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/promo-operations.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg height="256px" width="256px" viewBox="0 0 154.875 154.91782" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(-263.3125,-563.76968)">
+<path d="m336.77535,565.51158c-1.59726.10936-3.15002.35671-4.71936.53629h-.1063l-3.75379,20.48605c-6.11896,1.39345-11.87541,3.75824-17.05404,6.97168l-16.83926-12.12002c-4.55215,3.53414-8.69458,7.65592-12.3345,12.12002l11.69091,17.05386c-3.54965,5.42465-6.21914,11.6188-7.72253,18.12639-.00018.031-.00018.10198 0,.10702l-20.37883,3.2177c-.3725,3.04312-.53624,6.18809-.53624,9.33134 0,2.57176.071,5.10917.32165,7.61524l20.37883,3.64673c1.44933,7.07687 4.20261,13.68602 8.04414,19.52075l-12.11991,16.6248c3.4711,4.30922 7.47839,8.23258 11.79812,11.69099l17.16106-11.79826c5.9977,3.82597 12.69269,6.50875 19.94983,7.82975l3.21758,20.27155c2.28662.20798 4.63161.2145 6.97192.2145 3.30389,0 6.46004-.12522 9.65312-.53628l3.86135-20.70056c6.89012-1.71472 13.36295-4.68941 18.98427-8.68781l16.51747,12.01276c4.28351-3.64433 8.20054-7.83321 11.5837-12.33452l-12.0127-17.37561c3.25344-5.61849 5.50726-11.8176 6.64976-18.44817l20.2718-3.21771c.17838-2.11543.21297-4.16701.21297-6.32815 0-3.75557-.43675-7.43787-.96556-11.04745l-20.59342-3.75397c-1.61384-5.95909-4.26171-11.51888-7.61497-16.51756l12.11974-16.6248c-3.75686-4.59442-8.04235-8.83858-12.76333-12.4418l-17.48303,12.01278c-5.02475-2.97177-10.43184-5.25192-16.30306-6.54268l-3.21759-20.37879c-2.92858-.34452-5.88149-.53628-8.90214-.53628-.81656,0-1.65672-.024-2.46715,0-.39495.0126-.78593-.024-1.17962,0-.1063.007-.21621-.007-.32269,0zm2.78876,52.1268c.39207-.0213.78323,0 1.17998,0 12.69611,0 23.06003,10.36401 23.06003,23.06023s-10.36392,22.95297-23.06003,22.95297-22.95301-10.25675-22.95301-22.95297c0-12.29946 9.6261-22.44383 21.77303-23.06023z" fill="#333" stroke="#333" stroke-width="3.43222"/>
+</g>
+</svg>
diff --git a/storage/rocksdb/rocksdb/docs/static/images/promo-performance.svg b/storage/rocksdb/rocksdb/docs/static/images/promo-performance.svg
new file mode 100644
index 00000000..be8a1012
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/promo-performance.svg
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<defs>
+<linearGradient id="linearGradient4121">
+<stop offset="0" stop-color="#fff"/>
+<stop offset="1" stop-color="#fff" stop-opacity="0"/>
+</linearGradient>
+<linearGradient gradientTransform="matrix(.30081,0,0,.20986,-1650.25,-38.87783)" gradientUnits="userSpaceOnUse" id="linearGradient4107" x1="-342.85715" x2="-376.17944" xlink:href="#linearGradient4121" y1="-397.01691" y2="-915.50836"/>
+<linearGradient gradientUnits="userSpaceOnUse" id="linearGradient15735" x1="797.06111" x2="788.0298" xlink:href="#linearGradient4121" y1="-667.70464" y2="-819.35937"/>
+</defs>
+<title>netalloy chequered flag</title>
+<g transform="matrix(.08343,-.03199,0,.08343,-73.61005,-8.84057)">
+<path d="m1688.925,115.67413h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1719.6503,115.67413h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1658.8445,136.51062h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1689.5697,136.51062h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m136.5856-1749.1506h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m158.0217-1749.1506h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m156.97238-1718.8552h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m178.40849-1718.8552h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1718.5759,177.7339h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1719.2205,198.5704h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m219.03217-1748.5061h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1748.2268,157.64693h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1748.8713,178.48341h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m198.94519-1778.157h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m220.3813-1778.157h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1777.8777,137.26013h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1778.5222,158.09662h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m177.95879-1808.6672h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m199.99451-1807.8077h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1808.8176,157.34711h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1809.4622,178.18361h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m199.24498-1838.7478h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m220.08148-1838.7478h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1657.9851,178.93314h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1658.6296,199.76964h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m220.2314-1687.9152h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1688.0657,199.31993h30.08058v20.9864h-30.08058z" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m1688.7102,220.15643h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m220.00832-1868.8394h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m221.05762-1837.2548h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-1807.9261-241.48938h30.08058v20.9864h-30.08058z" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-1777.201-241.48938h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m115.67591-1837.6415h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m116.12562-1807.7758h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-1778.4473-136.55739h30.08058v20.9864h-30.08058z" fill="url(#linearGradient4107)" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-1776.9432-156.36673h27.94272v19.09188h-27.94272z" fill="#fff" opacity=".60096" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-1869.0726-199.46429h30.08058v20.9864h-30.08058z" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-1868.4281-178.62779h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-158.16603,1839.1426h20.9864v30.08058h-20.9864z" transform="matrix(.59562,-.80326,.87909,.47665,0,0)"/>
+<path d="m-136.72992,1838.5409h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(.59562,-.80326,.87909,.47665,0,0)"/>
+<path d="m116.27553-1899.0918h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-1899.7549-220.4507h30.08058v20.9864h-30.08058z" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-1899.1104-199.6142h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m-179.15244,1869.8247h20.9864v30.08058h-20.9864z" transform="matrix(.59562,-.80326,.87909,.47665,0,0)"/>
+<path d="m-157.71632,1869.2231h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(.59562,-.80326,.87909,.47665,0,0)"/>
+<path d="m239.53888-1718.5974h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m1719.3923,239.46391h30.08058v20.9864h-30.08058z" fill="#fff" opacity=".60096" transform="matrix(.87909,.47665,-.59562,.80326,0,0)"/>
+<path d="m239.91541-1897.8026h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m240.36511-1867.9369h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-1838.6084-260.79688h30.08058v20.9864h-30.08058z" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m240.21521-1778.3398h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m115.63581-1776.621h20.9864v30.08058h-20.9864z" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-1837.9637-155.30208h27.82166v17.65242h-27.82166z" fill="#fff" opacity=".60096" transform="matrix(-.87909,-.47665,.59562,-.80326,0,0)"/>
+<path d="m1389.2368,874.53916-207.8251,263.56844" fill="none" stroke="#000" stroke-linecap="round" stroke-width="14.84459"/>
+<path d="m199.22183-1868.7964h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m156.79575-1690.0319h21.6532v29.62879h-21.6532z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m221.44484-1899.1632h18.00162v29.54019h-18.00162z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m241.58183-1687.8284h18.77428v29.87089h-18.77428z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m240.00063-1806.0569h20.9864v30.08058h-20.9864z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m114.93913-1688.511h21.01802v24.93378h-21.01802z" fill="#fff" opacity=".60096" transform="matrix(-.59562,.80326,-.87909,-.47665,0,0)"/>
+<path d="m-855.13867,658.61304h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-827.45502,658.61304h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-882.24158,687.65332h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-854.55792,687.65332h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m687.75781,800.87488h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m717.63379,800.87488h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m716.17133,828.17133h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m746.0473,828.17133h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-828.42303,745.10712h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-827.84222,774.14746h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m802.66547,801.45563h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-801.70728,717.11145h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-801.12653,746.15173h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m774.66974,774.73993h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m804.54578,774.73993h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-774.99152,688.69794h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-774.41077,717.73822h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m745.42053,747.24982h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m776.1322,748.02417h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-747.11432,716.6936h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-746.53357,745.73389h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m775.08759,720.14691h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m804.12793,720.14691h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-883.01599,746.7785h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-882.43518,775.81885h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m804.33685,856.04858h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-855.91309,775.19208h27.1029v29.24923h-27.1029z" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m-855.33228,804.23236h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m804.02594,693.03412h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m805.48834,721.49219h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m747.91748-833.9646h27.1029v29.24923h-27.1029z" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m775.6012-833.9646h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m658.61548,721.14368h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m659.24225,748.05304h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m774.47833-687.71844h27.1029v29.24923h-27.1029z" fill="url(#linearGradient15735)" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m775.83344-715.32721h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m692.82385-775.39325h27.1029v29.24923h-27.1029z" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m693.40466-746.35297h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m-717.83496-719.79126h29.24923v27.1029h-29.24923z" transform="matrix(-.42736,-.90408,-.97568,-.21922,0,0)"/>
+<path d="m-687.95892-720.33331h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(-.42736,-.90408,-.97568,-.21922,0,0)"/>
+<path d="m659.45117,665.77631h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m665.17896-804.64252h27.1029v29.24923h-27.1029z" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m665.7597-775.60217h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m-747.08417-692.1463h29.24923v27.1029h-29.24923z" transform="matrix(-.42736,-.90408,-.97568,-.21922,0,0)"/>
+<path d="m-717.20813-692.68835h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(-.42736,-.90408,-.97568,-.21922,0,0)"/>
+<path d="m831.24615,828.40363h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m-827.68732,831.14166h27.1029v29.24923h-27.1029z" fill="#fff" opacity=".60096" transform="matrix(-.97568,-.21922,.42736,.90408,0,0)"/>
+<path d="m831.77087,666.93793h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m832.39764,693.84723h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m720.27252-860.8739h27.1029v29.24923h-27.1029z" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m832.18872,774.57513h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m658.55963,776.12384h29.24923v27.1029h-29.24923z" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m720.85333-713.84338h27.18274v24.15996h-27.18274z" fill="#fff" opacity=".60096" transform="matrix(.97568,.21922,-.42736,-.90408,0,0)"/>
+<path d="m1142.0758,779.44917 192.2989,413.58843" fill="none" stroke="#000" stroke-linecap="round" stroke-width="14.20834"/>
+<path d="m775.05536,693.07288h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m715.92511,854.14148h29.44084v23.17059h-29.44084z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m805.88055,665.00702h23.23064v27.43012h-23.23064z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m833.31165,857.23889h28.21643v22.16755h-28.21643z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m833.60077,748.08258h29.24923v27.1029h-29.24923z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+<path d="m657.4411,854.80676h28.65906v24.2827h-28.65906z" fill="#fff" opacity=".60096" transform="matrix(.42736,.90408,.97568,.21922,0,0)"/>
+</g>
+</svg>
diff --git a/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png
new file mode 100644
index 00000000..b4b24849
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png
new file mode 100644
index 00000000..742f985b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png
new file mode 100644
index 00000000..c7bdcb95
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/images/tree_example1.png b/storage/rocksdb/rocksdb/docs/static/images/tree_example1.png
new file mode 100644
index 00000000..9f725860
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/images/tree_example1.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/docs/static/logo.svg b/storage/rocksdb/rocksdb/docs/static/logo.svg
new file mode 100644
index 00000000..e6e1e8af
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/logo.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 841.9 595.3" enable-background="new 0 0 841.9 595.3" xml:space="preserve">
+<g id="Layer_2" display="none">
+ <g display="inline">
+ <path fill="#0F2647" d="M213,86.8h415.9c17,0,31,13.9,31,31v359.7c0,17-14,31-31,31H213c-17.1,0-31-14-31-31V117.8
+ C182,100.7,195.9,86.8,213,86.8z"/>
+ </g>
+</g>
+<g id="Layer_4">
+ <g>
+ <path fill="#FFBE00" d="M501.9,398l-64.7,1.6l-24.4,56l-63.6,16.6l-39.8,36.3h320.4c16.6,0,30.1-13.5,30.1-30.1V319.2l-113.6,28.4
+ L501.9,398z M268.5,402.7L214.6,449c-0.5-3.3-1.8-6.6-4.1-9.4l-28.3,23.8c6,7.1,16.1,8.5,23.8,3.8l52.7-23.9L268.5,402.7z
+ M581.4,240.2l6.5,17.2c10.1-2.6,25.6-13.7,36.5-22.3c-0.4,3-0.2,6,0.9,9l34.7-12.8c-3.6-9.6-14.2-14.4-23.8-10.9
+ c-0.3,0.1-0.6,0.3-0.9,0.5l0.1-0.1L581.4,240.2z M641.2,269.2c-0.1,0-0.1,0-0.2,0l-63.6-5.5l-14.5-38.1v-40.4
+ c2.9,1.6,6.1,2.5,9.6,2.5c10.5,0,19.1-8.1,20.1-18.3l15.2-10.1l-0.7-20.5c-0.1-2.1-1.7-3.7-3.8-3.7l-19-0.3c-4-5.6-11.4-8-17.9-5
+ l-41.8,19.6c0.1,0.1,0.1,0.3,0.1,0.4c1.4,2.9,3.5,5.1,6,6.7c-50.9,26.3-72.5,61.8-72.5,61.8L263.6,323.1
+ c-11.4,6.1-25.7,1.8-31.8-9.6c-6.1-11.4-1.8-25.7,9.6-31.8l55.2-29.7c14-7.5,22.2-21.5,23.2-36.2l-33.1,17.8l-55.2,29.7
+ c-21.6,11.6-29.7,38.6-18.1,60.1c8,14.9,23.3,23.4,39.2,23.4c7.1,0,14.3-1.7,20.9-5.3l24.9-13.4c-1.8,9.8-1,20.3,2.9,30.2
+ c1.3,3.4,2.2,5.5,2.2,5.5c8.8,19.1-2.8,34.1-18,34.1h-4.8l-17.5,76.2c-2.3-2.4-5.3-4.2-8.8-5.1l-8.7,35.9
+ c8.8,2.1,17.5-2.4,21.1-10.4l39.7-71.2c50.2-4.9,76.6-38.9,75.4-86.8c52,3.2,121.3-29.5,152.1-74.2c23.4,29.7,67.6,25.5,87.3,18
+ l3.8-1.3c-1.4,2.6-2.2,5.6-2.2,8.8l37-0.4C659.7,277.3,651.4,269.1,641.2,269.2z M296.3,201.8c1.8,3.4,2.7,7,2.8,10.6l19.5-10.5
+ c-0.9-3.4-2.1-6.8-3.9-10l-1-1.9l-18.4,9.9L296.3,201.8z M289.2,188.6l18.4-9.9l-2.4-4.4c-2.7-5.1-9.1-7-14.2-4.3
+ c-5.1,2.7-7,9.1-4.3,14.2L289.2,188.6z"/>
+ <path fill="#0F2647" d="M571.4,139.9c-3.3,0-5.9,2.7-5.9,6c0,3.3,2.7,6,5.9,6c3.3,0,6-2.7,6-6
+ C577.4,142.6,574.7,139.9,571.4,139.9z M536.6,184.6c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9
+ s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3S534.4,183.8,536.6,184.6z M516.4,191.9c0,0-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9
+ s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3C515.5,192.9,516.4,191.9,516.4,191.9z M543,220.2
+ c0,0-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9
+ C540.4,223.1,543,220.2,543,220.2z M512.7,230.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9
+ s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S515,231.2,512.7,230.3z M484.1,221.6c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9
+ c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9
+ c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S486.3,222.5,484.1,221.6z M473,255.6c0,0-0.9-1.1-2.3-1.9
+ c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9
+ c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9C470.5,258.5,473,255.6,473,255.6z
+ M423.9,260.6c0,0,2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3
+ c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3
+ C424.8,259.6,423.9,260.6,423.9,260.6z M450.9,277.8c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9
+ s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S453.1,278.6,450.9,277.8z M480.5,284.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9
+ c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9
+ c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S482.7,285.2,480.5,284.3z M504.7,260.9c1.4-0.8,2.3-1.9,2.3-1.9
+ s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9
+ s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S506.9,261.7,504.7,260.9z M548.1,241.4
+ c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3
+ c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3
+ S550.3,242.2,548.1,241.4z M405.7,278.2c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9
+ c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9
+ c2.2-0.9,3.7-2.3,3.7-2.3S407.9,279.1,405.7,278.2z M434.6,306.7c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9
+ c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9
+ c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S436.8,307.5,434.6,306.7z M397.8,311.9c1.4-0.8,2.3-1.9,2.3-1.9
+ s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9
+ s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S400.1,312.7,397.8,311.9z M367.8,290.7
+ c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3
+ c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3
+ S370,291.5,367.8,290.7z M325.2,318.1c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9
+ c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9
+ c-2.2,0.9-3.7,2.3-3.7,2.3S323,317.2,325.2,318.1z M363.5,328.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9
+ s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9
+ s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S365.7,329.1,363.5,328.3z M357.2,359.5c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9
+ c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9
+ c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3S359.4,360.4,357.2,359.5z M328,355c0,0-0.9-1.1-2.3-1.9
+ c2.2-0.9,3.7-2.3,3.7-2.3s-1.5-1.4-3.7-2.3c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9
+ c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9C325.4,358,328,355,328,355z M335.6,384.5
+ c1.4-0.8,2.3-1.9,2.3-1.9s-2.5-2.9-5.6-2.9c-3.1,0-5.6,2.9-5.6,2.9s0.9,1.1,2.3,1.9c-2.2,0.9-3.7,2.3-3.7,2.3s1.5,1.4,3.7,2.3
+ c-1.4,0.8-2.3,1.9-2.3,1.9s2.5,2.9,5.6,2.9c3.1,0,5.6-2.9,5.6-2.9s-0.9-1.1-2.3-1.9c2.2-0.9,3.7-2.3,3.7-2.3
+ S337.8,385.3,335.6,384.5z"/>
+ </g>
+</g>
+</svg>
diff --git a/storage/rocksdb/rocksdb/docs/static/og_image.png b/storage/rocksdb/rocksdb/docs/static/og_image.png
new file mode 100644
index 00000000..4e2759e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/docs/static/og_image.png
Binary files differ
diff --git a/storage/rocksdb/rocksdb/env/composite_env_wrapper.h b/storage/rocksdb/rocksdb/env/composite_env_wrapper.h
new file mode 100644
index 00000000..fbc0b93f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/composite_env_wrapper.h
@@ -0,0 +1,1117 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/env.h"
+#include "rocksdb/file_system.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// The CompositeEnvWrapper class provides an interface that is compatible
+// with the old monolithic Env API, and an implementation that wraps around
+// the new Env that provides threading and other OS related functionality, and
+// the new FileSystem API that provides storage functionality. By
+// providing the old Env interface, it allows the rest of RocksDB code to
+// be agnostic of whether the underlying Env implementation is a monolithic
+// Env or an Env + FileSystem. In the former case, the user will specify
+// Options::env only, whereas in the latter case, the user will specify
+// Options::env and Options::file_system.
+
+inline IOStatus status_to_io_status(Status&& status) {
+ if (status.ok()) {
+ // Fast path
+ return IOStatus::OK();
+ } else {
+ const char* state = status.getState();
+ if (state) {
+ return IOStatus(status.code(), status.subcode(),
+ Slice(state, strlen(status.getState()) + 1),
+ Slice());
+ } else {
+ return IOStatus(status.code(), status.subcode());
+ }
+ }
+}
+
+class CompositeSequentialFileWrapper : public SequentialFile {
+ public:
+ explicit CompositeSequentialFileWrapper(
+ std::unique_ptr<FSSequentialFile>& target)
+ : target_(std::move(target)) {}
+
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Read(n, io_opts, result, scratch, &dbg);
+ }
+ Status Skip(uint64_t n) override { return target_->Skip(n); }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+ Status PositionedRead(uint64_t offset, size_t n, Slice* result,
+ char* scratch) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->PositionedRead(offset, n, io_opts, result, scratch, &dbg);
+ }
+
+ private:
+ std::unique_ptr<FSSequentialFile> target_;
+};
+
+class CompositeRandomAccessFileWrapper : public RandomAccessFile {
+ public:
+ explicit CompositeRandomAccessFileWrapper(
+ std::unique_ptr<FSRandomAccessFile>& target)
+ : target_(std::move(target)) {}
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Read(offset, n, io_opts, result, scratch, &dbg);
+ }
+ Status MultiRead(ReadRequest* reqs, size_t num_reqs) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ std::vector<FSReadRequest> fs_reqs;
+ Status status;
+
+ fs_reqs.resize(num_reqs);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ fs_reqs[i].offset = reqs[i].offset;
+ fs_reqs[i].len = reqs[i].len;
+ fs_reqs[i].scratch = reqs[i].scratch;
+ fs_reqs[i].status = IOStatus::OK();
+ }
+ status = target_->MultiRead(fs_reqs.data(), num_reqs, io_opts, &dbg);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ reqs[i].result = fs_reqs[i].result;
+ reqs[i].status = fs_reqs[i].status;
+ }
+ return status;
+ }
+ Status Prefetch(uint64_t offset, size_t n) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Prefetch(offset, n, io_opts, &dbg);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ };
+ void Hint(AccessPattern pattern) override {
+ target_->Hint((FSRandomAccessFile::AccessPattern)pattern);
+ }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ private:
+ std::unique_ptr<FSRandomAccessFile> target_;
+};
+
+class CompositeWritableFileWrapper : public WritableFile {
+ public:
+ explicit CompositeWritableFileWrapper(std::unique_ptr<FSWritableFile>& t)
+ : target_(std::move(t)) {}
+
+ Status Append(const Slice& data) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Append(data, io_opts, &dbg);
+ }
+ Status PositionedAppend(const Slice& data, uint64_t offset) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->PositionedAppend(data, offset, io_opts, &dbg);
+ }
+ Status Truncate(uint64_t size) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Truncate(size, io_opts, &dbg);
+ }
+ Status Close() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Close(io_opts, &dbg);
+ }
+ Status Flush() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Flush(io_opts, &dbg);
+ }
+ Status Sync() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Sync(io_opts, &dbg);
+ }
+ Status Fsync() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Fsync(io_opts, &dbg);
+ }
+ bool IsSyncThreadSafe() const override { return target_->IsSyncThreadSafe(); }
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+
+ void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override {
+ target_->SetWriteLifeTimeHint(hint);
+ }
+
+ Env::WriteLifeTimeHint GetWriteLifeTimeHint() override {
+ return target_->GetWriteLifeTimeHint();
+ }
+
+ uint64_t GetFileSize() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->GetFileSize(io_opts, &dbg);
+ }
+
+ void SetPreallocationBlockSize(size_t size) override {
+ target_->SetPreallocationBlockSize(size);
+ }
+
+ void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) override {
+ target_->GetPreallocationStatus(block_size, last_allocated_block);
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ Status RangeSync(uint64_t offset, uint64_t nbytes) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->RangeSync(offset, nbytes, io_opts, &dbg);
+ }
+
+ void PrepareWrite(size_t offset, size_t len) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ target_->PrepareWrite(offset, len, io_opts, &dbg);
+ }
+
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Allocate(offset, len, io_opts, &dbg);
+ }
+
+ std::unique_ptr<FSWritableFile>* target() { return &target_; }
+
+ private:
+ std::unique_ptr<FSWritableFile> target_;
+};
+
+class CompositeRandomRWFileWrapper : public RandomRWFile {
+ public:
+ explicit CompositeRandomRWFileWrapper(std::unique_ptr<FSRandomRWFile>& target)
+ : target_(std::move(target)) {}
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status Write(uint64_t offset, const Slice& data) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Write(offset, data, io_opts, &dbg);
+ }
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Read(offset, n, io_opts, result, scratch, &dbg);
+ }
+ Status Flush() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Flush(io_opts, &dbg);
+ }
+ Status Sync() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Sync(io_opts, &dbg);
+ }
+ Status Fsync() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Fsync(io_opts, &dbg);
+ }
+ Status Close() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Close(io_opts, &dbg);
+ }
+
+ private:
+ std::unique_ptr<FSRandomRWFile> target_;
+};
+
+class CompositeDirectoryWrapper : public Directory {
+ public:
+ explicit CompositeDirectoryWrapper(std::unique_ptr<FSDirectory>& target)
+ : target_(std::move(target)) {}
+
+ Status Fsync() override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return target_->Fsync(io_opts, &dbg);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ private:
+ std::unique_ptr<FSDirectory> target_;
+};
+
+class CompositeEnvWrapper : public Env {
+ public:
+ // Initialize a CompositeEnvWrapper that delegates all thread/time related
+ // calls to env, and all file operations to fs
+ explicit CompositeEnvWrapper(Env* env, FileSystem* fs)
+ : env_target_(env), fs_env_target_(fs) {}
+ ~CompositeEnvWrapper() {}
+
+ // Return the target to which this Env forwards all calls
+ Env* env_target() const { return env_target_; }
+
+ FileSystem* fs_env_target() const { return fs_env_target_; }
+
+ // The following text is boilerplate that forwards all methods to target()
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ std::unique_ptr<FSSequentialFile> file;
+ Status status;
+ status =
+ fs_env_target_->NewSequentialFile(f, FileOptions(options), &file, &dbg);
+ if (status.ok()) {
+ r->reset(new CompositeSequentialFileWrapper(file));
+ }
+ return status;
+ }
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ std::unique_ptr<FSRandomAccessFile> file;
+ Status status;
+ status = fs_env_target_->NewRandomAccessFile(f, FileOptions(options), &file,
+ &dbg);
+ if (status.ok()) {
+ r->reset(new CompositeRandomAccessFileWrapper(file));
+ }
+ return status;
+ }
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ std::unique_ptr<FSWritableFile> file;
+ Status status;
+ status =
+ fs_env_target_->NewWritableFile(f, FileOptions(options), &file, &dbg);
+ if (status.ok()) {
+ r->reset(new CompositeWritableFileWrapper(file));
+ }
+ return status;
+ }
+ Status ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ Status status;
+ std::unique_ptr<FSWritableFile> file;
+ status = fs_env_target_->ReopenWritableFile(fname, FileOptions(options),
+ &file, &dbg);
+ if (status.ok()) {
+ result->reset(new CompositeWritableFileWrapper(file));
+ }
+ return status;
+ }
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ Status status;
+ std::unique_ptr<FSWritableFile> file;
+ status = fs_env_target_->ReuseWritableFile(
+ fname, old_fname, FileOptions(options), &file, &dbg);
+ if (status.ok()) {
+ r->reset(new CompositeWritableFileWrapper(file));
+ }
+ return status;
+ }
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override {
+ IODebugContext dbg;
+ std::unique_ptr<FSRandomRWFile> file;
+ Status status;
+ status = fs_env_target_->NewRandomRWFile(fname, FileOptions(options), &file,
+ &dbg);
+ if (status.ok()) {
+ result->reset(new CompositeRandomRWFileWrapper(file));
+ }
+ return status;
+ }
+ Status NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override {
+ return fs_env_target_->NewMemoryMappedFileBuffer(fname, result);
+ }
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ std::unique_ptr<FSDirectory> dir;
+ Status status;
+ status = fs_env_target_->NewDirectory(name, io_opts, &dir, &dbg);
+ if (status.ok()) {
+ result->reset(new CompositeDirectoryWrapper(dir));
+ }
+ return status;
+ }
+ Status FileExists(const std::string& f) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->FileExists(f, io_opts, &dbg);
+ }
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* r) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetChildren(dir, io_opts, r, &dbg);
+ }
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetChildrenFileAttributes(dir, io_opts, result,
+ &dbg);
+ }
+ Status DeleteFile(const std::string& f) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->DeleteFile(f, io_opts, &dbg);
+ }
+ Status Truncate(const std::string& fname, size_t size) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->Truncate(fname, size, io_opts, &dbg);
+ }
+ Status CreateDir(const std::string& d) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->CreateDir(d, io_opts, &dbg);
+ }
+ Status CreateDirIfMissing(const std::string& d) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->CreateDirIfMissing(d, io_opts, &dbg);
+ }
+ Status DeleteDir(const std::string& d) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->DeleteDir(d, io_opts, &dbg);
+ }
+ Status GetFileSize(const std::string& f, uint64_t* s) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetFileSize(f, io_opts, s, &dbg);
+ }
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetFileModificationTime(fname, io_opts, file_mtime,
+ &dbg);
+ }
+
+ Status RenameFile(const std::string& s, const std::string& t) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->RenameFile(s, t, io_opts, &dbg);
+ }
+
+ Status LinkFile(const std::string& s, const std::string& t) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->LinkFile(s, t, io_opts, &dbg);
+ }
+
+ Status NumFileLinks(const std::string& fname, uint64_t* count) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->NumFileLinks(fname, io_opts, count, &dbg);
+ }
+
+ Status AreFilesSame(const std::string& first, const std::string& second,
+ bool* res) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->AreFilesSame(first, second, io_opts, res, &dbg);
+ }
+
+ Status LockFile(const std::string& f, FileLock** l) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->LockFile(f, io_opts, l, &dbg);
+ }
+
+ Status UnlockFile(FileLock* l) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->UnlockFile(l, io_opts, &dbg);
+ }
+
+ Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetAbsolutePath(db_path, io_opts, output_path, &dbg);
+ }
+
+#if !defined(OS_WIN) && !defined(ROCKSDB_NO_DYNAMIC_EXTENSION)
+ Status LoadLibrary(const std::string& lib_name,
+ const std::string& search_path,
+ std::shared_ptr<DynamicLibrary>* result) override {
+ return env_target_->LoadLibrary(lib_name, search_path, result);
+ }
+#endif
+
+ void Schedule(void (*f)(void* arg), void* a, Priority pri,
+ void* tag = nullptr, void (*u)(void* arg) = nullptr) override {
+ return env_target_->Schedule(f, a, pri, tag, u);
+ }
+
+ int UnSchedule(void* tag, Priority pri) override {
+ return env_target_->UnSchedule(tag, pri);
+ }
+
+ void StartThread(void (*f)(void*), void* a) override {
+ return env_target_->StartThread(f, a);
+ }
+ void WaitForJoin() override { return env_target_->WaitForJoin(); }
+ unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override {
+ return env_target_->GetThreadPoolQueueLen(pri);
+ }
+ Status GetTestDirectory(std::string* path) override {
+ return env_target_->GetTestDirectory(path);
+ }
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override {
+ return env_target_->NewLogger(fname, result);
+ }
+ uint64_t NowMicros() override { return env_target_->NowMicros(); }
+ uint64_t NowNanos() override { return env_target_->NowNanos(); }
+ uint64_t NowCPUNanos() override { return env_target_->NowCPUNanos(); }
+
+ void SleepForMicroseconds(int micros) override {
+ env_target_->SleepForMicroseconds(micros);
+ }
+ Status GetHostName(char* name, uint64_t len) override {
+ return env_target_->GetHostName(name, len);
+ }
+ Status GetCurrentTime(int64_t* unix_time) override {
+ return env_target_->GetCurrentTime(unix_time);
+ }
+ void SetBackgroundThreads(int num, Priority pri) override {
+ return env_target_->SetBackgroundThreads(num, pri);
+ }
+ int GetBackgroundThreads(Priority pri) override {
+ return env_target_->GetBackgroundThreads(pri);
+ }
+
+ Status SetAllowNonOwnerAccess(bool allow_non_owner_access) override {
+ return env_target_->SetAllowNonOwnerAccess(allow_non_owner_access);
+ }
+
+ void IncBackgroundThreadsIfNeeded(int num, Priority pri) override {
+ return env_target_->IncBackgroundThreadsIfNeeded(num, pri);
+ }
+
+ void LowerThreadPoolIOPriority(Priority pool = LOW) override {
+ env_target_->LowerThreadPoolIOPriority(pool);
+ }
+
+ void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
+ env_target_->LowerThreadPoolCPUPriority(pool);
+ }
+
+ std::string TimeToString(uint64_t time) override {
+ return env_target_->TimeToString(time);
+ }
+
+ Status GetThreadList(std::vector<ThreadStatus>* thread_list) override {
+ return env_target_->GetThreadList(thread_list);
+ }
+
+ ThreadStatusUpdater* GetThreadStatusUpdater() const override {
+ return env_target_->GetThreadStatusUpdater();
+ }
+
+ uint64_t GetThreadID() const override { return env_target_->GetThreadID(); }
+
+ std::string GenerateUniqueId() override {
+ return env_target_->GenerateUniqueId();
+ }
+
+ EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const override {
+ return fs_env_target_->OptimizeForLogRead(FileOptions(env_options));
+ }
+ EnvOptions OptimizeForManifestRead(
+ const EnvOptions& env_options) const override {
+ return fs_env_target_->OptimizeForManifestRead(
+ FileOptions(env_options));
+ }
+ EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const override {
+ return fs_env_target_->OptimizeForLogWrite(FileOptions(env_options),
+ db_options);
+ }
+ EnvOptions OptimizeForManifestWrite(
+ const EnvOptions& env_options) const override {
+ return fs_env_target_->OptimizeForManifestWrite(
+ FileOptions(env_options));
+ }
+ EnvOptions OptimizeForCompactionTableWrite(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& immutable_ops) const override {
+ return fs_env_target_->OptimizeForCompactionTableWrite(
+ FileOptions(env_options),
+ immutable_ops);
+ }
+ EnvOptions OptimizeForCompactionTableRead(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& db_options) const override {
+ return fs_env_target_->OptimizeForCompactionTableRead(
+ FileOptions(env_options),
+ db_options);
+ }
+ Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override {
+ IOOptions io_opts;
+ IODebugContext dbg;
+ return fs_env_target_->GetFreeSpace(path, io_opts, diskfree, &dbg);
+ }
+
+ private:
+ Env* env_target_;
+ FileSystem* fs_env_target_;
+};
+
+class LegacySequentialFileWrapper : public FSSequentialFile {
+ public:
+ explicit LegacySequentialFileWrapper(
+ std::unique_ptr<SequentialFile>&& _target)
+ : target_(std::move(_target)) {}
+
+ IOStatus Read(size_t n, const IOOptions& /*options*/, Slice* result,
+ char* scratch, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Read(n, result, scratch));
+ }
+ IOStatus Skip(uint64_t n) override {
+ return status_to_io_status(target_->Skip(n));
+ }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return status_to_io_status(target_->InvalidateCache(offset, length));
+ }
+ IOStatus PositionedRead(uint64_t offset, size_t n,
+ const IOOptions& /*options*/, Slice* result,
+ char* scratch, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(
+ target_->PositionedRead(offset, n, result, scratch));
+ }
+ SequentialFile* target() { return target_.get(); }
+
+ private:
+ std::unique_ptr<SequentialFile> target_;
+};
+
+class LegacyRandomAccessFileWrapper : public FSRandomAccessFile {
+ public:
+ explicit LegacyRandomAccessFileWrapper(
+ std::unique_ptr<RandomAccessFile>&& target)
+ : target_(std::move(target)) {}
+
+ IOStatus Read(uint64_t offset, size_t n, const IOOptions& /*options*/,
+ Slice* result, char* scratch,
+ IODebugContext* /*dbg*/) const override {
+ return status_to_io_status(target_->Read(offset, n, result, scratch));
+ }
+ IOStatus MultiRead(FSReadRequest* fs_reqs, size_t num_reqs,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ std::vector<ReadRequest> reqs;
+ Status status;
+
+ reqs.reserve(num_reqs);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ ReadRequest req;
+
+ req.offset = fs_reqs[i].offset;
+ req.len = fs_reqs[i].len;
+ req.scratch = fs_reqs[i].scratch;
+ req.status = Status::OK();
+
+ reqs.emplace_back(req);
+ }
+ status = target_->MultiRead(reqs.data(), num_reqs);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ fs_reqs[i].result = reqs[i].result;
+ fs_reqs[i].status = status_to_io_status(std::move(reqs[i].status));
+ }
+ return status_to_io_status(std::move(status));
+ ;
+ }
+ IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Prefetch(offset, n));
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ };
+ void Hint(AccessPattern pattern) override {
+ target_->Hint((RandomAccessFile::AccessPattern)pattern);
+ }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return status_to_io_status(target_->InvalidateCache(offset, length));
+ }
+
+ private:
+ std::unique_ptr<RandomAccessFile> target_;
+};
+
+class LegacyWritableFileWrapper : public FSWritableFile {
+ public:
+ explicit LegacyWritableFileWrapper(std::unique_ptr<WritableFile>&& _target)
+ : target_(std::move(_target)) {}
+
+ IOStatus Append(const Slice& data, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Append(data));
+ }
+ IOStatus PositionedAppend(const Slice& data, uint64_t offset,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->PositionedAppend(data, offset));
+ }
+ IOStatus Truncate(uint64_t size, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Truncate(size));
+ }
+ IOStatus Close(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Close());
+ }
+ IOStatus Flush(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Flush());
+ }
+ IOStatus Sync(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Sync());
+ }
+ IOStatus Fsync(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Fsync());
+ }
+ bool IsSyncThreadSafe() const override { return target_->IsSyncThreadSafe(); }
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+
+ void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override {
+ target_->SetWriteLifeTimeHint(hint);
+ }
+
+ Env::WriteLifeTimeHint GetWriteLifeTimeHint() override {
+ return target_->GetWriteLifeTimeHint();
+ }
+
+ uint64_t GetFileSize(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return target_->GetFileSize();
+ }
+
+ void SetPreallocationBlockSize(size_t size) override {
+ target_->SetPreallocationBlockSize(size);
+ }
+
+ void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) override {
+ target_->GetPreallocationStatus(block_size, last_allocated_block);
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return status_to_io_status(target_->InvalidateCache(offset, length));
+ }
+
+ IOStatus RangeSync(uint64_t offset, uint64_t nbytes,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->RangeSync(offset, nbytes));
+ }
+
+ void PrepareWrite(size_t offset, size_t len, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ target_->PrepareWrite(offset, len);
+ }
+
+ IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Allocate(offset, len));
+ }
+
+ WritableFile* target() { return target_.get(); }
+
+ private:
+ std::unique_ptr<WritableFile> target_;
+};
+
+class LegacyRandomRWFileWrapper : public FSRandomRWFile {
+ public:
+ explicit LegacyRandomRWFileWrapper(std::unique_ptr<RandomRWFile>&& target)
+ : target_(std::move(target)) {}
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus Write(uint64_t offset, const Slice& data,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Write(offset, data));
+ }
+ IOStatus Read(uint64_t offset, size_t n, const IOOptions& /*options*/,
+ Slice* result, char* scratch,
+ IODebugContext* /*dbg*/) const override {
+ return status_to_io_status(target_->Read(offset, n, result, scratch));
+ }
+ IOStatus Flush(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Flush());
+ }
+ IOStatus Sync(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Sync());
+ }
+ IOStatus Fsync(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Fsync());
+ }
+ IOStatus Close(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Close());
+ }
+
+ private:
+ std::unique_ptr<RandomRWFile> target_;
+};
+
+class LegacyDirectoryWrapper : public FSDirectory {
+ public:
+ explicit LegacyDirectoryWrapper(std::unique_ptr<Directory>&& target)
+ : target_(std::move(target)) {}
+
+ IOStatus Fsync(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Fsync());
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ private:
+ std::unique_ptr<Directory> target_;
+};
+
+class LegacyFileSystemWrapper : public FileSystem {
+ public:
+ // Initialize an EnvWrapper that delegates all calls to *t
+ explicit LegacyFileSystemWrapper(Env* t) : target_(t) {}
+ ~LegacyFileSystemWrapper() override {}
+
+ const char* Name() const override { return "Legacy File System"; }
+
+ // Return the target to which this Env forwards all calls
+ Env* target() const { return target_; }
+
+ // The following text is boilerplate that forwards all methods to target()
+ IOStatus NewSequentialFile(const std::string& f,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSSequentialFile>* r,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<SequentialFile> file;
+ Status s = target_->NewSequentialFile(f, &file, file_opts);
+ if (s.ok()) {
+ r->reset(new LegacySequentialFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus NewRandomAccessFile(const std::string& f,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSRandomAccessFile>* r,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<RandomAccessFile> file;
+ Status s = target_->NewRandomAccessFile(f, &file, file_opts);
+ if (s.ok()) {
+ r->reset(new LegacyRandomAccessFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* r,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<WritableFile> file;
+ Status s = target_->NewWritableFile(f, &file, file_opts);
+ if (s.ok()) {
+ r->reset(new LegacyWritableFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus ReopenWritableFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<WritableFile> file;
+ Status s = target_->ReopenWritableFile(fname, &file, file_opts);
+ if (s.ok()) {
+ result->reset(new LegacyWritableFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* r,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<WritableFile> file;
+ Status s = target_->ReuseWritableFile(fname, old_fname, &file, file_opts);
+ if (s.ok()) {
+ r->reset(new LegacyWritableFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus NewRandomRWFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSRandomRWFile>* result,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<RandomRWFile> file;
+ Status s = target_->NewRandomRWFile(fname, &file, file_opts);
+ if (s.ok()) {
+ result->reset(new LegacyRandomRWFileWrapper(std::move(file)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override {
+ return status_to_io_status(
+ target_->NewMemoryMappedFileBuffer(fname, result));
+ }
+ IOStatus NewDirectory(const std::string& name, const IOOptions& /*io_opts*/,
+ std::unique_ptr<FSDirectory>* result,
+ IODebugContext* /*dbg*/) override {
+ std::unique_ptr<Directory> dir;
+ Status s = target_->NewDirectory(name, &dir);
+ if (s.ok()) {
+ result->reset(new LegacyDirectoryWrapper(std::move(dir)));
+ }
+ return status_to_io_status(std::move(s));
+ }
+ IOStatus FileExists(const std::string& f, const IOOptions& /*io_opts*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->FileExists(f));
+ }
+ IOStatus GetChildren(const std::string& dir, const IOOptions& /*io_opts*/,
+ std::vector<std::string>* r,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetChildren(dir, r));
+ }
+ IOStatus GetChildrenFileAttributes(const std::string& dir,
+ const IOOptions& /*options*/,
+ std::vector<FileAttributes>* result,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetChildrenFileAttributes(dir, result));
+ }
+ IOStatus DeleteFile(const std::string& f, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->DeleteFile(f));
+ }
+ IOStatus Truncate(const std::string& fname, size_t size,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->Truncate(fname, size));
+ }
+ IOStatus CreateDir(const std::string& d, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->CreateDir(d));
+ }
+ IOStatus CreateDirIfMissing(const std::string& d,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->CreateDirIfMissing(d));
+ }
+ IOStatus DeleteDir(const std::string& d, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->DeleteDir(d));
+ }
+ IOStatus GetFileSize(const std::string& f, const IOOptions& /*options*/,
+ uint64_t* s, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetFileSize(f, s));
+ }
+
+ IOStatus GetFileModificationTime(const std::string& fname,
+ const IOOptions& /*options*/,
+ uint64_t* file_mtime,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(
+ target_->GetFileModificationTime(fname, file_mtime));
+ }
+
+ IOStatus GetAbsolutePath(const std::string& db_path,
+ const IOOptions& /*options*/,
+ std::string* output_path,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetAbsolutePath(db_path, output_path));
+ }
+
+ IOStatus RenameFile(const std::string& s, const std::string& t,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->RenameFile(s, t));
+ }
+
+ IOStatus LinkFile(const std::string& s, const std::string& t,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->LinkFile(s, t));
+ }
+
+ IOStatus NumFileLinks(const std::string& fname, const IOOptions& /*options*/,
+ uint64_t* count, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->NumFileLinks(fname, count));
+ }
+
+ IOStatus AreFilesSame(const std::string& first, const std::string& second,
+ const IOOptions& /*options*/, bool* res,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->AreFilesSame(first, second, res));
+ }
+
+ IOStatus LockFile(const std::string& f, const IOOptions& /*options*/,
+ FileLock** l, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->LockFile(f, l));
+ }
+
+ IOStatus UnlockFile(FileLock* l, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->UnlockFile(l));
+ }
+
+ IOStatus GetTestDirectory(const IOOptions& /*options*/, std::string* path,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetTestDirectory(path));
+ }
+ IOStatus NewLogger(const std::string& fname, const IOOptions& /*options*/,
+ std::shared_ptr<Logger>* result,
+ IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->NewLogger(fname, result));
+ }
+
+ FileOptions OptimizeForLogRead(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForLogRead(file_options);
+ }
+ FileOptions OptimizeForManifestRead(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForManifestRead(file_options);
+ }
+ FileOptions OptimizeForLogWrite(const FileOptions& file_options,
+ const DBOptions& db_options) const override {
+ return target_->OptimizeForLogWrite(file_options, db_options);
+ }
+ FileOptions OptimizeForManifestWrite(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForManifestWrite(file_options);
+ }
+ FileOptions OptimizeForCompactionTableWrite(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& immutable_ops) const override {
+ return target_->OptimizeForCompactionTableWrite(file_options,
+ immutable_ops);
+ }
+ FileOptions OptimizeForCompactionTableRead(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& db_options) const override {
+ return target_->OptimizeForCompactionTableRead(file_options, db_options);
+ }
+ IOStatus GetFreeSpace(const std::string& path, const IOOptions& /*options*/,
+ uint64_t* diskfree, IODebugContext* /*dbg*/) override {
+ return status_to_io_status(target_->GetFreeSpace(path, diskfree));
+ }
+
+ private:
+ Env* target_;
+};
+
+inline std::unique_ptr<FSSequentialFile> NewLegacySequentialFileWrapper(
+ std::unique_ptr<SequentialFile>& file) {
+ return std::unique_ptr<FSSequentialFile>(
+ new LegacySequentialFileWrapper(std::move(file)));
+}
+
+inline std::unique_ptr<FSRandomAccessFile> NewLegacyRandomAccessFileWrapper(
+ std::unique_ptr<RandomAccessFile>& file) {
+ return std::unique_ptr<FSRandomAccessFile>(
+ new LegacyRandomAccessFileWrapper(std::move(file)));
+}
+
+inline std::unique_ptr<FSWritableFile> NewLegacyWritableFileWrapper(
+ std::unique_ptr<WritableFile>&& file) {
+ return std::unique_ptr<FSWritableFile>(
+ new LegacyWritableFileWrapper(std::move(file)));
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/env.cc b/storage/rocksdb/rocksdb/env/env.cc
new file mode 100644
index 00000000..70f4b29f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env.cc
@@ -0,0 +1,475 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/env.h"
+
+#include <thread>
+#include "env/composite_env_wrapper.h"
+#include "logging/env_logger.h"
+#include "memory/arena.h"
+#include "options/db_options.h"
+#include "port/port.h"
+#include "port/sys_time.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/object_registry.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Env::~Env() {
+}
+
+Status Env::NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) {
+ return NewEnvLogger(fname, this, result);
+}
+
+Status Env::LoadEnv(const std::string& value, Env** result) {
+ Env* env = *result;
+ Status s;
+#ifndef ROCKSDB_LITE
+ s = ObjectRegistry::NewInstance()->NewStaticObject<Env>(value, &env);
+#else
+ s = Status::NotSupported("Cannot load environment in LITE mode: ", value);
+#endif
+ if (s.ok()) {
+ *result = env;
+ }
+ return s;
+}
+
+Status Env::LoadEnv(const std::string& value, Env** result,
+ std::shared_ptr<Env>* guard) {
+ assert(result);
+ Status s;
+#ifndef ROCKSDB_LITE
+ Env* env = nullptr;
+ std::unique_ptr<Env> uniq_guard;
+ std::string err_msg;
+ assert(guard != nullptr);
+ env = ObjectRegistry::NewInstance()->NewObject<Env>(value, &uniq_guard,
+ &err_msg);
+ if (!env) {
+ s = Status::NotFound(std::string("Cannot load ") + Env::Type() + ": " +
+ value);
+ env = Env::Default();
+ }
+ if (s.ok() && uniq_guard) {
+ guard->reset(uniq_guard.release());
+ *result = guard->get();
+ } else {
+ *result = env;
+ }
+#else
+ (void)result;
+ (void)guard;
+ s = Status::NotSupported("Cannot load environment in LITE mode: ", value);
+#endif
+ return s;
+}
+
+std::string Env::PriorityToString(Env::Priority priority) {
+ switch (priority) {
+ case Env::Priority::BOTTOM:
+ return "Bottom";
+ case Env::Priority::LOW:
+ return "Low";
+ case Env::Priority::HIGH:
+ return "High";
+ case Env::Priority::USER:
+ return "User";
+ case Env::Priority::TOTAL:
+ assert(false);
+ }
+ return "Invalid";
+}
+
+uint64_t Env::GetThreadID() const {
+ std::hash<std::thread::id> hasher;
+ return hasher(std::this_thread::get_id());
+}
+
+Status Env::ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) {
+ Status s = RenameFile(old_fname, fname);
+ if (!s.ok()) {
+ return s;
+ }
+ return NewWritableFile(fname, result, options);
+}
+
+Status Env::GetChildrenFileAttributes(const std::string& dir,
+ std::vector<FileAttributes>* result) {
+ assert(result != nullptr);
+ std::vector<std::string> child_fnames;
+ Status s = GetChildren(dir, &child_fnames);
+ if (!s.ok()) {
+ return s;
+ }
+ result->resize(child_fnames.size());
+ size_t result_size = 0;
+ for (size_t i = 0; i < child_fnames.size(); ++i) {
+ const std::string path = dir + "/" + child_fnames[i];
+ if (!(s = GetFileSize(path, &(*result)[result_size].size_bytes)).ok()) {
+ if (FileExists(path).IsNotFound()) {
+ // The file may have been deleted since we listed the directory
+ continue;
+ }
+ return s;
+ }
+ (*result)[result_size].name = std::move(child_fnames[i]);
+ result_size++;
+ }
+ result->resize(result_size);
+ return Status::OK();
+}
+
+SequentialFile::~SequentialFile() {
+}
+
+RandomAccessFile::~RandomAccessFile() {
+}
+
+WritableFile::~WritableFile() {
+}
+
+MemoryMappedFileBuffer::~MemoryMappedFileBuffer() {}
+
+Logger::~Logger() {}
+
+Status Logger::Close() {
+ if (!closed_) {
+ closed_ = true;
+ return CloseImpl();
+ } else {
+ return Status::OK();
+ }
+}
+
+Status Logger::CloseImpl() { return Status::NotSupported(); }
+
+FileLock::~FileLock() {
+}
+
+void LogFlush(Logger *info_log) {
+ if (info_log) {
+ info_log->Flush();
+ }
+}
+
+static void Logv(Logger *info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::INFO_LEVEL) {
+ info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap);
+ }
+}
+
+void Log(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Logv(info_log, format, ap);
+ va_end(ap);
+}
+
+void Logger::Logv(const InfoLogLevel log_level, const char* format, va_list ap) {
+ static const char* kInfoLogLevelNames[5] = { "DEBUG", "INFO", "WARN",
+ "ERROR", "FATAL" };
+ if (log_level < log_level_) {
+ return;
+ }
+
+ if (log_level == InfoLogLevel::INFO_LEVEL) {
+ // Doesn't print log level if it is INFO level.
+ // This is to avoid unexpected performance regression after we add
+ // the feature of log level. All the logs before we add the feature
+ // are INFO level. We don't want to add extra costs to those existing
+ // logging.
+ Logv(format, ap);
+ } else if (log_level == InfoLogLevel::HEADER_LEVEL) {
+ LogHeader(format, ap);
+ } else {
+ char new_format[500];
+ snprintf(new_format, sizeof(new_format) - 1, "[%s] %s",
+ kInfoLogLevelNames[log_level], format);
+ Logv(new_format, ap);
+ }
+}
+
+static void Logv(const InfoLogLevel log_level, Logger *info_log, const char *format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= log_level) {
+ if (log_level == InfoLogLevel::HEADER_LEVEL) {
+ info_log->LogHeader(format, ap);
+ } else {
+ info_log->Logv(log_level, format, ap);
+ }
+ }
+}
+
+void Log(const InfoLogLevel log_level, Logger* info_log, const char* format,
+ ...) {
+ va_list ap;
+ va_start(ap, format);
+ Logv(log_level, info_log, format, ap);
+ va_end(ap);
+}
+
+static void Headerv(Logger *info_log, const char *format, va_list ap) {
+ if (info_log) {
+ info_log->LogHeader(format, ap);
+ }
+}
+
+void Header(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Headerv(info_log, format, ap);
+ va_end(ap);
+}
+
+static void Debugv(Logger* info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::DEBUG_LEVEL) {
+ info_log->Logv(InfoLogLevel::DEBUG_LEVEL, format, ap);
+ }
+}
+
+void Debug(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Debugv(info_log, format, ap);
+ va_end(ap);
+}
+
+static void Infov(Logger* info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::INFO_LEVEL) {
+ info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap);
+ }
+}
+
+void Info(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Infov(info_log, format, ap);
+ va_end(ap);
+}
+
+static void Warnv(Logger* info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::WARN_LEVEL) {
+ info_log->Logv(InfoLogLevel::WARN_LEVEL, format, ap);
+ }
+}
+
+void Warn(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Warnv(info_log, format, ap);
+ va_end(ap);
+}
+
+static void Errorv(Logger* info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::ERROR_LEVEL) {
+ info_log->Logv(InfoLogLevel::ERROR_LEVEL, format, ap);
+ }
+}
+
+void Error(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Errorv(info_log, format, ap);
+ va_end(ap);
+}
+
+static void Fatalv(Logger* info_log, const char* format, va_list ap) {
+ if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::FATAL_LEVEL) {
+ info_log->Logv(InfoLogLevel::FATAL_LEVEL, format, ap);
+ }
+}
+
+void Fatal(Logger* info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Fatalv(info_log, format, ap);
+ va_end(ap);
+}
+
+void LogFlush(const std::shared_ptr<Logger>& info_log) {
+ LogFlush(info_log.get());
+}
+
+void Log(const InfoLogLevel log_level, const std::shared_ptr<Logger>& info_log,
+ const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Logv(log_level, info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Header(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Headerv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Debug(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Debugv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Info(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Infov(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Warn(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Warnv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Error(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Errorv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Fatal(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Fatalv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+void Log(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ Logv(info_log.get(), format, ap);
+ va_end(ap);
+}
+
+Status WriteStringToFile(Env* env, const Slice& data, const std::string& fname,
+ bool should_sync) {
+ std::unique_ptr<WritableFile> file;
+ EnvOptions soptions;
+ Status s = env->NewWritableFile(fname, &file, soptions);
+ if (!s.ok()) {
+ return s;
+ }
+ s = file->Append(data);
+ if (s.ok() && should_sync) {
+ s = file->Sync();
+ }
+ if (!s.ok()) {
+ env->DeleteFile(fname);
+ }
+ return s;
+}
+
+Status ReadFileToString(Env* env, const std::string& fname, std::string* data) {
+ LegacyFileSystemWrapper lfsw(env);
+ return ReadFileToString(&lfsw, fname, data);
+}
+
+EnvWrapper::~EnvWrapper() {
+}
+
+namespace { // anonymous namespace
+
+void AssignEnvOptions(EnvOptions* env_options, const DBOptions& options) {
+ env_options->use_mmap_reads = options.allow_mmap_reads;
+ env_options->use_mmap_writes = options.allow_mmap_writes;
+ env_options->use_direct_reads = options.use_direct_reads;
+ env_options->set_fd_cloexec = options.is_fd_close_on_exec;
+ env_options->bytes_per_sync = options.bytes_per_sync;
+ env_options->compaction_readahead_size = options.compaction_readahead_size;
+ env_options->random_access_max_buffer_size =
+ options.random_access_max_buffer_size;
+ env_options->rate_limiter = options.rate_limiter.get();
+ env_options->writable_file_max_buffer_size =
+ options.writable_file_max_buffer_size;
+ env_options->allow_fallocate = options.allow_fallocate;
+ env_options->strict_bytes_per_sync = options.strict_bytes_per_sync;
+ options.env->SanitizeEnvOptions(env_options);
+}
+
+}
+
+EnvOptions Env::OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const {
+ EnvOptions optimized_env_options(env_options);
+ optimized_env_options.bytes_per_sync = db_options.wal_bytes_per_sync;
+ optimized_env_options.writable_file_max_buffer_size =
+ db_options.writable_file_max_buffer_size;
+ return optimized_env_options;
+}
+
+EnvOptions Env::OptimizeForManifestWrite(const EnvOptions& env_options) const {
+ return env_options;
+}
+
+EnvOptions Env::OptimizeForLogRead(const EnvOptions& env_options) const {
+ EnvOptions optimized_env_options(env_options);
+ optimized_env_options.use_direct_reads = false;
+ return optimized_env_options;
+}
+
+EnvOptions Env::OptimizeForManifestRead(const EnvOptions& env_options) const {
+ EnvOptions optimized_env_options(env_options);
+ optimized_env_options.use_direct_reads = false;
+ return optimized_env_options;
+}
+
+EnvOptions Env::OptimizeForCompactionTableWrite(
+ const EnvOptions& env_options, const ImmutableDBOptions& db_options) const {
+ EnvOptions optimized_env_options(env_options);
+ optimized_env_options.use_direct_writes =
+ db_options.use_direct_io_for_flush_and_compaction;
+ return optimized_env_options;
+}
+
+EnvOptions Env::OptimizeForCompactionTableRead(
+ const EnvOptions& env_options, const ImmutableDBOptions& db_options) const {
+ EnvOptions optimized_env_options(env_options);
+ optimized_env_options.use_direct_reads = db_options.use_direct_reads;
+ return optimized_env_options;
+}
+
+EnvOptions::EnvOptions(const DBOptions& options) {
+ AssignEnvOptions(this, options);
+}
+
+EnvOptions::EnvOptions() {
+ DBOptions options;
+ AssignEnvOptions(this, options);
+}
+
+Status NewEnvLogger(const std::string& fname, Env* env,
+ std::shared_ptr<Logger>* result) {
+ EnvOptions options;
+ // TODO: Tune the buffer size.
+ options.writable_file_max_buffer_size = 1024 * 1024;
+ std::unique_ptr<WritableFile> writable_file;
+ const auto status = env->NewWritableFile(fname, &writable_file, options);
+ if (!status.ok()) {
+ return status;
+ }
+
+ *result = std::make_shared<EnvLogger>(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname, options,
+ env);
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/env_basic_test.cc b/storage/rocksdb/rocksdb/env/env_basic_test.cc
new file mode 100644
index 00000000..d2041f78
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_basic_test.cc
@@ -0,0 +1,354 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include "env/mock_env.h"
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Normalizes trivial differences across Envs such that these test cases can
+// run on all Envs.
+class NormalizingEnvWrapper : public EnvWrapper {
+ public:
+ explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {}
+
+ // Removes . and .. from directory listing
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) override {
+ Status status = EnvWrapper::GetChildren(dir, result);
+ if (status.ok()) {
+ result->erase(std::remove_if(result->begin(), result->end(),
+ [](const std::string& s) {
+ return s == "." || s == "..";
+ }),
+ result->end());
+ }
+ return status;
+ }
+
+ // Removes . and .. from directory listing
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ Status status = EnvWrapper::GetChildrenFileAttributes(dir, result);
+ if (status.ok()) {
+ result->erase(std::remove_if(result->begin(), result->end(),
+ [](const FileAttributes& fa) {
+ return fa.name == "." || fa.name == "..";
+ }),
+ result->end());
+ }
+ return status;
+ }
+};
+
+class EnvBasicTestWithParam : public testing::Test,
+ public ::testing::WithParamInterface<Env*> {
+ public:
+ Env* env_;
+ const EnvOptions soptions_;
+ std::string test_dir_;
+
+ EnvBasicTestWithParam() : env_(GetParam()) {
+ test_dir_ = test::PerThreadDBPath(env_, "env_basic_test");
+ }
+
+ void SetUp() override { env_->CreateDirIfMissing(test_dir_); }
+
+ void TearDown() override {
+ std::vector<std::string> files;
+ env_->GetChildren(test_dir_, &files);
+ for (const auto& file : files) {
+ // don't know whether it's file or directory, try both. The tests must
+ // only create files or empty directories, so one must succeed, else the
+ // directory's corrupted.
+ Status s = env_->DeleteFile(test_dir_ + "/" + file);
+ if (!s.ok()) {
+ ASSERT_OK(env_->DeleteDir(test_dir_ + "/" + file));
+ }
+ }
+ }
+};
+
+class EnvMoreTestWithParam : public EnvBasicTestWithParam {};
+
+static std::unique_ptr<Env> def_env(new NormalizingEnvWrapper(Env::Default()));
+INSTANTIATE_TEST_CASE_P(EnvDefault, EnvBasicTestWithParam,
+ ::testing::Values(def_env.get()));
+INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam,
+ ::testing::Values(def_env.get()));
+
+static std::unique_ptr<Env> mock_env(new MockEnv(Env::Default()));
+INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam,
+ ::testing::Values(mock_env.get()));
+#ifndef ROCKSDB_LITE
+static std::unique_ptr<Env> mem_env(NewMemEnv(Env::Default()));
+INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam,
+ ::testing::Values(mem_env.get()));
+
+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<Env*> GetCustomEnvs() {
+ static Env* custom_env;
+ static bool init = false;
+ if (!init) {
+ init = true;
+ const char* uri = getenv("TEST_ENV_URI");
+ if (uri != nullptr) {
+ Env::LoadEnv(uri, &custom_env);
+ }
+ }
+
+ std::vector<Env*> res;
+ if (custom_env != nullptr) {
+ res.emplace_back(custom_env);
+ }
+ return res;
+}
+
+} // anonymous namespace
+
+INSTANTIATE_TEST_CASE_P(CustomEnv, EnvBasicTestWithParam,
+ ::testing::ValuesIn(GetCustomEnvs()));
+
+INSTANTIATE_TEST_CASE_P(CustomEnv, EnvMoreTestWithParam,
+ ::testing::ValuesIn(GetCustomEnvs()));
+
+#endif // ROCKSDB_LITE
+
+TEST_P(EnvBasicTestWithParam, Basics) {
+ uint64_t file_size;
+ std::unique_ptr<WritableFile> writable_file;
+ std::vector<std::string> 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<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> rand_file;
+ ASSERT_TRUE(!env_->NewSequentialFile(test_dir_ + "/non_existent", &seq_file,
+ soptions_)
+ .ok());
+ ASSERT_TRUE(!seq_file);
+ ASSERT_TRUE(!env_->NewRandomAccessFile(test_dir_ + "/non_existent",
+ &rand_file, soptions_)
+ .ok());
+ ASSERT_TRUE(!rand_file);
+
+ // Check that deleting works.
+ ASSERT_TRUE(!env_->DeleteFile(test_dir_ + "/non_existent").ok());
+ 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());
+ ASSERT_TRUE(
+ env_->GetChildren(test_dir_ + "/non_existent", &children).IsNotFound());
+}
+
+TEST_P(EnvBasicTestWithParam, ReadWrite) {
+ std::unique_ptr<WritableFile> writable_file;
+ std::unique_ptr<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> 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<WritableFile> 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<char>(i));
+ }
+
+ std::unique_ptr<WritableFile> 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<SequentialFile> 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<std::string> children;
+ 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<std::string> children;
+ std::vector<Env::FileAttributes> 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);
+ } // necessary for default POSIX env
+
+ // non-exist directory returns IOError
+ ASSERT_OK(env_->DeleteDir(test_dir_));
+ ASSERT_TRUE(!env_->FileExists(test_dir_).ok());
+ ASSERT_TRUE(!env_->GetChildren(test_dir_, &children).ok());
+ ASSERT_TRUE(!env_->GetChildrenFileAttributes(test_dir_, &childAttr).ok());
+
+ // if dir is a file, returns IOError
+ ASSERT_OK(env_->CreateDir(test_dir_));
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(
+ env_->NewWritableFile(test_dir_ + "/file", &writable_file, soptions_));
+ ASSERT_OK(writable_file->Close());
+ writable_file.reset();
+ ASSERT_TRUE(!env_->GetChildren(test_dir_ + "/file", &children).ok());
+ ASSERT_EQ(0U, children.size());
+}
+
+} // namespace ROCKSDB_NAMESPACE
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/env/env_chroot.cc b/storage/rocksdb/rocksdb/env/env_chroot.cc
new file mode 100644
index 00000000..246437bd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_chroot.cc
@@ -0,0 +1,321 @@
+// 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).
+
+#if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+
+#include "env/env_chroot.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ChrootEnv : public EnvWrapper {
+ public:
+ ChrootEnv(Env* base_env, const std::string& chroot_dir)
+ : EnvWrapper(base_env) {
+#if defined(OS_AIX)
+ char resolvedName[PATH_MAX];
+ char* real_chroot_dir = realpath(chroot_dir.c_str(), resolvedName);
+#else
+ char* real_chroot_dir = realpath(chroot_dir.c_str(), nullptr);
+#endif
+ // chroot_dir must exist so realpath() returns non-nullptr.
+ assert(real_chroot_dir != nullptr);
+ chroot_dir_ = real_chroot_dir;
+#if !defined(OS_AIX)
+ free(real_chroot_dir);
+#endif
+ }
+
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewSequentialFile(status_and_enc_path.second, result,
+ options);
+ }
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewRandomAccessFile(status_and_enc_path.second, result,
+ options);
+ }
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewWritableFile(status_and_enc_path.second, result,
+ options);
+ }
+
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ auto status_and_old_enc_path = EncodePath(old_fname);
+ if (!status_and_old_enc_path.first.ok()) {
+ return status_and_old_enc_path.first;
+ }
+ return EnvWrapper::ReuseWritableFile(status_and_old_enc_path.second,
+ status_and_old_enc_path.second, result,
+ options);
+ }
+
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewRandomRWFile(status_and_enc_path.second, result,
+ options);
+ }
+
+ Status NewDirectory(const std::string& dir,
+ std::unique_ptr<Directory>* result) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(dir);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewDirectory(status_and_enc_path.second, result);
+ }
+
+ Status FileExists(const std::string& fname) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::FileExists(status_and_enc_path.second);
+ }
+
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) override {
+ auto status_and_enc_path = EncodePath(dir);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::GetChildren(status_and_enc_path.second, result);
+ }
+
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ auto status_and_enc_path = EncodePath(dir);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::GetChildrenFileAttributes(status_and_enc_path.second,
+ result);
+ }
+
+ Status DeleteFile(const std::string& fname) override {
+ auto status_and_enc_path = EncodePath(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::DeleteFile(status_and_enc_path.second);
+ }
+
+ Status CreateDir(const std::string& dirname) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(dirname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::CreateDir(status_and_enc_path.second);
+ }
+
+ Status CreateDirIfMissing(const std::string& dirname) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(dirname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::CreateDirIfMissing(status_and_enc_path.second);
+ }
+
+ Status DeleteDir(const std::string& dirname) override {
+ auto status_and_enc_path = EncodePath(dirname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::DeleteDir(status_and_enc_path.second);
+ }
+
+ Status GetFileSize(const std::string& fname, uint64_t* file_size) override {
+ auto status_and_enc_path = EncodePath(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::GetFileSize(status_and_enc_path.second, file_size);
+ }
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override {
+ auto status_and_enc_path = EncodePath(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::GetFileModificationTime(status_and_enc_path.second,
+ file_mtime);
+ }
+
+ Status RenameFile(const std::string& src, const std::string& dest) override {
+ auto status_and_src_enc_path = EncodePath(src);
+ if (!status_and_src_enc_path.first.ok()) {
+ return status_and_src_enc_path.first;
+ }
+ auto status_and_dest_enc_path = EncodePathWithNewBasename(dest);
+ if (!status_and_dest_enc_path.first.ok()) {
+ return status_and_dest_enc_path.first;
+ }
+ return EnvWrapper::RenameFile(status_and_src_enc_path.second,
+ status_and_dest_enc_path.second);
+ }
+
+ Status LinkFile(const std::string& src, const std::string& dest) override {
+ auto status_and_src_enc_path = EncodePath(src);
+ if (!status_and_src_enc_path.first.ok()) {
+ return status_and_src_enc_path.first;
+ }
+ auto status_and_dest_enc_path = EncodePathWithNewBasename(dest);
+ if (!status_and_dest_enc_path.first.ok()) {
+ return status_and_dest_enc_path.first;
+ }
+ return EnvWrapper::LinkFile(status_and_src_enc_path.second,
+ status_and_dest_enc_path.second);
+ }
+
+ Status LockFile(const std::string& fname, FileLock** lock) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ // FileLock subclasses may store path (e.g., PosixFileLock stores it). We
+ // can skip stripping the chroot directory from this path because callers
+ // shouldn't use it.
+ return EnvWrapper::LockFile(status_and_enc_path.second, lock);
+ }
+
+ Status GetTestDirectory(std::string* path) override {
+ // Adapted from PosixEnv's implementation since it doesn't provide a way to
+ // create directory in the chroot.
+ char buf[256];
+ snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid()));
+ *path = buf;
+
+ // Directory may already exist, so ignore return
+ CreateDir(*path);
+ return Status::OK();
+ }
+
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override {
+ auto status_and_enc_path = EncodePathWithNewBasename(fname);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::NewLogger(status_and_enc_path.second, result);
+ }
+
+ Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) override {
+ auto status_and_enc_path = EncodePath(db_path);
+ if (!status_and_enc_path.first.ok()) {
+ return status_and_enc_path.first;
+ }
+ return EnvWrapper::GetAbsolutePath(status_and_enc_path.second, output_path);
+ }
+
+ private:
+ // Returns status and expanded absolute path including the chroot directory.
+ // Checks whether the provided path breaks out of the chroot. If it returns
+ // non-OK status, the returned path should not be used.
+ std::pair<Status, std::string> EncodePath(const std::string& path) {
+ if (path.empty() || path[0] != '/') {
+ return {Status::InvalidArgument(path, "Not an absolute path"), ""};
+ }
+ std::pair<Status, std::string> res;
+ res.second = chroot_dir_ + path;
+#if defined(OS_AIX)
+ char resolvedName[PATH_MAX];
+ char* normalized_path = realpath(res.second.c_str(), resolvedName);
+#else
+ char* normalized_path = realpath(res.second.c_str(), nullptr);
+#endif
+ if (normalized_path == nullptr) {
+ res.first = Status::NotFound(res.second, strerror(errno));
+ } else if (strlen(normalized_path) < chroot_dir_.size() ||
+ strncmp(normalized_path, chroot_dir_.c_str(),
+ chroot_dir_.size()) != 0) {
+ res.first = Status::IOError(res.second,
+ "Attempted to access path outside chroot");
+ } else {
+ res.first = Status::OK();
+ }
+#if !defined(OS_AIX)
+ free(normalized_path);
+#endif
+ return res;
+ }
+
+ // Similar to EncodePath() except assumes the basename in the path hasn't been
+ // created yet.
+ std::pair<Status, std::string> EncodePathWithNewBasename(
+ const std::string& path) {
+ if (path.empty() || path[0] != '/') {
+ return {Status::InvalidArgument(path, "Not an absolute path"), ""};
+ }
+ // Basename may be followed by trailing slashes
+ size_t final_idx = path.find_last_not_of('/');
+ if (final_idx == std::string::npos) {
+ // It's only slashes so no basename to extract
+ return EncodePath(path);
+ }
+
+ // Pull off the basename temporarily since realname(3) (used by
+ // EncodePath()) requires a path that exists
+ size_t base_sep = path.rfind('/', final_idx);
+ auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1));
+ status_and_enc_path.second.append(path.substr(base_sep + 1));
+ return status_and_enc_path;
+ }
+
+ std::string chroot_dir_;
+};
+
+Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) {
+ if (!base_env->FileExists(chroot_dir).ok()) {
+ return nullptr;
+ }
+ return new ChrootEnv(base_env, chroot_dir);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
diff --git a/storage/rocksdb/rocksdb/env/env_chroot.h b/storage/rocksdb/rocksdb/env/env_chroot.h
new file mode 100644
index 00000000..cb5585b3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_chroot.h
@@ -0,0 +1,22 @@
+// 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).
+
+#pragma once
+
+#if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+
+#include <string>
+
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Returns an Env that translates paths such that the root directory appears to
+// be chroot_dir. chroot_dir should refer to an existing directory.
+Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir);
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
diff --git a/storage/rocksdb/rocksdb/env/env_encryption.cc b/storage/rocksdb/rocksdb/env/env_encryption.cc
new file mode 100644
index 00000000..5f4f65d7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_encryption.cc
@@ -0,0 +1,937 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <iostream>
+
+#include "rocksdb/env_encryption.h"
+#include "util/aligned_buffer.h"
+#include "util/coding.h"
+#include "util/random.h"
+
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+class EncryptedSequentialFile : public SequentialFile {
+ private:
+ std::unique_ptr<SequentialFile> file_;
+ std::unique_ptr<BlockAccessCipherStream> stream_;
+ uint64_t offset_;
+ size_t prefixLength_;
+
+ public:
+ // Default ctor. Given underlying sequential file is supposed to be at
+ // offset == prefixLength.
+ EncryptedSequentialFile(SequentialFile* f, BlockAccessCipherStream* s, size_t prefixLength)
+ : file_(f), stream_(s), offset_(prefixLength), prefixLength_(prefixLength) {
+ }
+
+ // Read up to "n" bytes from the file. "scratch[0..n-1]" may be
+ // written by this routine. Sets "*result" to the data that was
+ // read (including if fewer than "n" bytes were successfully read).
+ // May set "*result" to point at data in "scratch[0..n-1]", so
+ // "scratch[0..n-1]" must be live when "*result" is used.
+ // If an error was encountered, returns a non-OK status.
+ //
+ // REQUIRES: External synchronization
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ assert(scratch);
+ Status status = file_->Read(n, result, scratch);
+ if (!status.ok()) {
+ return status;
+ }
+ status = stream_->Decrypt(offset_, (char*)result->data(), result->size());
+ offset_ += result->size(); // We've already ready data from disk, so update offset_ even if decryption fails.
+ return status;
+ }
+
+ // Skip "n" bytes from the file. This is guaranteed to be no
+ // slower that reading the same data, but may be faster.
+ //
+ // If end of file is reached, skipping will stop at the end of the
+ // file, and Skip will return OK.
+ //
+ // REQUIRES: External synchronization
+ Status Skip(uint64_t n) override {
+ auto status = file_->Skip(n);
+ if (!status.ok()) {
+ return status;
+ }
+ offset_ += n;
+ return status;
+ }
+
+ // Indicates the upper layers if the current SequentialFile implementation
+ // uses direct IO.
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ size_t GetRequiredBufferAlignment() const override {
+ return file_->GetRequiredBufferAlignment();
+ }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return file_->InvalidateCache(offset + prefixLength_, length);
+ }
+
+ // Positioned Read for direct I/O
+ // If Direct I/O enabled, offset, n, and scratch should be properly aligned
+ Status PositionedRead(uint64_t offset, size_t n, Slice* result,
+ char* scratch) override {
+ assert(scratch);
+ offset += prefixLength_; // Skip prefix
+ auto status = file_->PositionedRead(offset, n, result, scratch);
+ if (!status.ok()) {
+ return status;
+ }
+ offset_ = offset + result->size();
+ status = stream_->Decrypt(offset, (char*)result->data(), result->size());
+ return status;
+ }
+};
+
+// A file abstraction for randomly reading the contents of a file.
+class EncryptedRandomAccessFile : public RandomAccessFile {
+ private:
+ std::unique_ptr<RandomAccessFile> file_;
+ std::unique_ptr<BlockAccessCipherStream> stream_;
+ size_t prefixLength_;
+
+ public:
+ EncryptedRandomAccessFile(RandomAccessFile* f, BlockAccessCipherStream* s, size_t prefixLength)
+ : file_(f), stream_(s), prefixLength_(prefixLength) { }
+
+ // Read up to "n" bytes from the file starting at "offset".
+ // "scratch[0..n-1]" may be written by this routine. Sets "*result"
+ // to the data that was read (including if fewer than "n" bytes were
+ // successfully read). May set "*result" to point at data in
+ // "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
+ // "*result" is used. If an error was encountered, returns a non-OK
+ // status.
+ //
+ // Safe for concurrent use by multiple threads.
+ // If Direct I/O enabled, offset, n, and scratch should be aligned properly.
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ assert(scratch);
+ offset += prefixLength_;
+ auto status = file_->Read(offset, n, result, scratch);
+ if (!status.ok()) {
+ return status;
+ }
+ status = stream_->Decrypt(offset, (char*)result->data(), result->size());
+ return status;
+ }
+
+ // Readahead the file starting from offset by n bytes for caching.
+ Status Prefetch(uint64_t offset, size_t n) override {
+ //return Status::OK();
+ return file_->Prefetch(offset + prefixLength_, n);
+ }
+
+ // Tries to get an unique ID for this file that will be the same each time
+ // the file is opened (and will stay the same while the file is open).
+ // Furthermore, it tries to make this ID at most "max_size" bytes. If such an
+ // ID can be created this function returns the length of the ID and places it
+ // in "id"; otherwise, this function returns 0, in which case "id"
+ // may not have been modified.
+ //
+ // This function guarantees, for IDs from a given environment, two unique ids
+ // cannot be made equal to each other by adding arbitrary bytes to one of
+ // them. That is, no unique ID is the prefix of another.
+ //
+ // This function guarantees that the returned ID will not be interpretable as
+ // a single varint.
+ //
+ // Note: these IDs are only valid for the duration of the process.
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return file_->GetUniqueId(id, max_size);
+ };
+
+ void Hint(AccessPattern pattern) override { file_->Hint(pattern); }
+
+ // Indicates the upper layers if the current RandomAccessFile implementation
+ // uses direct IO.
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ size_t GetRequiredBufferAlignment() const override {
+ return file_->GetRequiredBufferAlignment();
+ }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return file_->InvalidateCache(offset + prefixLength_, length);
+ }
+};
+
+// A file abstraction for sequential writing. The implementation
+// must provide buffering since callers may append small fragments
+// at a time to the file.
+class EncryptedWritableFile : public WritableFileWrapper {
+ private:
+ std::unique_ptr<WritableFile> file_;
+ std::unique_ptr<BlockAccessCipherStream> stream_;
+ size_t prefixLength_;
+
+ public:
+ // Default ctor. Prefix is assumed to be written already.
+ EncryptedWritableFile(WritableFile* f, BlockAccessCipherStream* s, size_t prefixLength)
+ : WritableFileWrapper(f), file_(f), stream_(s), prefixLength_(prefixLength) { }
+
+ Status Append(const Slice& data) override {
+ AlignedBuffer buf;
+ Status status;
+ Slice dataToAppend(data);
+ if (data.size() > 0) {
+ auto offset = file_->GetFileSize(); // size including prefix
+ // Encrypt in cloned buffer
+ buf.Alignment(GetRequiredBufferAlignment());
+ buf.AllocateNewBuffer(data.size());
+ // TODO (sagar0): Modify AlignedBuffer.Append to allow doing a memmove
+ // so that the next two lines can be replaced with buf.Append().
+ memmove(buf.BufferStart(), data.data(), data.size());
+ buf.Size(data.size());
+ status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
+ if (!status.ok()) {
+ return status;
+ }
+ dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
+ }
+ status = file_->Append(dataToAppend);
+ if (!status.ok()) {
+ return status;
+ }
+ return status;
+ }
+
+ Status PositionedAppend(const Slice& data, uint64_t offset) override {
+ AlignedBuffer buf;
+ Status status;
+ Slice dataToAppend(data);
+ offset += prefixLength_;
+ if (data.size() > 0) {
+ // Encrypt in cloned buffer
+ buf.Alignment(GetRequiredBufferAlignment());
+ buf.AllocateNewBuffer(data.size());
+ memmove(buf.BufferStart(), data.data(), data.size());
+ buf.Size(data.size());
+ status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
+ if (!status.ok()) {
+ return status;
+ }
+ dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
+ }
+ status = file_->PositionedAppend(dataToAppend, offset);
+ if (!status.ok()) {
+ return status;
+ }
+ return status;
+ }
+
+ // Indicates the upper layers if the current WritableFile implementation
+ // uses direct IO.
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ size_t GetRequiredBufferAlignment() const override {
+ return file_->GetRequiredBufferAlignment();
+ }
+
+ /*
+ * Get the size of valid data in the file.
+ */
+ uint64_t GetFileSize() override {
+ return file_->GetFileSize() - prefixLength_;
+ }
+
+ // Truncate is necessary to trim the file to the correct size
+ // before closing. It is not always possible to keep track of the file
+ // size due to whole pages writes. The behavior is undefined if called
+ // with other writes to follow.
+ Status Truncate(uint64_t size) override {
+ return file_->Truncate(size + prefixLength_);
+ }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ // This call has no effect on dirty pages in the cache.
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return file_->InvalidateCache(offset + prefixLength_, length);
+ }
+
+ // Sync a file range with disk.
+ // offset is the starting byte of the file range to be synchronized.
+ // nbytes specifies the length of the range to be synchronized.
+ // This asks the OS to initiate flushing the cached data to disk,
+ // without waiting for completion.
+ // Default implementation does nothing.
+ Status RangeSync(uint64_t offset, uint64_t nbytes) override {
+ return file_->RangeSync(offset + prefixLength_, nbytes);
+ }
+
+ // PrepareWrite performs any necessary preparation for a write
+ // before the write actually occurs. This allows for pre-allocation
+ // of space on devices where it can result in less file
+ // fragmentation and/or less waste from over-zealous filesystem
+ // pre-allocation.
+ void PrepareWrite(size_t offset, size_t len) override {
+ file_->PrepareWrite(offset + prefixLength_, len);
+ }
+
+ // Pre-allocates space for a file.
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ return file_->Allocate(offset + prefixLength_, len);
+ }
+};
+
+// A file abstraction for random reading and writing.
+class EncryptedRandomRWFile : public RandomRWFile {
+ private:
+ std::unique_ptr<RandomRWFile> file_;
+ std::unique_ptr<BlockAccessCipherStream> stream_;
+ size_t prefixLength_;
+
+ public:
+ EncryptedRandomRWFile(RandomRWFile* f, BlockAccessCipherStream* s, size_t prefixLength)
+ : file_(f), stream_(s), prefixLength_(prefixLength) {}
+
+ // Indicates if the class makes use of direct I/O
+ // If false you must pass aligned buffer to Write()
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ size_t GetRequiredBufferAlignment() const override {
+ return file_->GetRequiredBufferAlignment();
+ }
+
+ // Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
+ // Pass aligned buffer when use_direct_io() returns true.
+ Status Write(uint64_t offset, const Slice& data) override {
+ AlignedBuffer buf;
+ Status status;
+ Slice dataToWrite(data);
+ offset += prefixLength_;
+ if (data.size() > 0) {
+ // Encrypt in cloned buffer
+ buf.Alignment(GetRequiredBufferAlignment());
+ buf.AllocateNewBuffer(data.size());
+ memmove(buf.BufferStart(), data.data(), data.size());
+ buf.Size(data.size());
+ status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
+ if (!status.ok()) {
+ return status;
+ }
+ dataToWrite = Slice(buf.BufferStart(), buf.CurrentSize());
+ }
+ status = file_->Write(offset, dataToWrite);
+ return status;
+ }
+
+ // Read up to `n` bytes starting from offset `offset` and store them in
+ // result, provided `scratch` size should be at least `n`.
+ // Returns Status::OK() on success.
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ assert(scratch);
+ offset += prefixLength_;
+ auto status = file_->Read(offset, n, result, scratch);
+ if (!status.ok()) {
+ return status;
+ }
+ status = stream_->Decrypt(offset, (char*)result->data(), result->size());
+ return status;
+ }
+
+ Status Flush() override { return file_->Flush(); }
+
+ Status Sync() override { return file_->Sync(); }
+
+ Status Fsync() override { return file_->Fsync(); }
+
+ Status Close() override { return file_->Close(); }
+};
+
+// EncryptedEnv implements an Env wrapper that adds encryption to files stored on disk.
+class EncryptedEnv : public EnvWrapper {
+ public:
+ EncryptedEnv(Env* base_env, EncryptionProvider *provider)
+ : EnvWrapper(base_env) {
+ provider_ = provider;
+ }
+
+ // NewSequentialFile opens a file for sequential reading.
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_reads) {
+ return Status::InvalidArgument();
+ }
+ // Open file using underlying Env implementation
+ std::unique_ptr<SequentialFile> underlying;
+ auto status = EnvWrapper::NewSequentialFile(fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Read prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ // Read prefix
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ status = underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart());
+ if (!status.ok()) {
+ return status;
+ }
+ prefixBuf.Size(prefixLength);
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<SequentialFile>(new EncryptedSequentialFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // NewRandomAccessFile opens a file for random read access.
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_reads) {
+ return Status::InvalidArgument();
+ }
+ // Open file using underlying Env implementation
+ std::unique_ptr<RandomAccessFile> underlying;
+ auto status = EnvWrapper::NewRandomAccessFile(fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Read prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ // Read prefix
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart());
+ if (!status.ok()) {
+ return status;
+ }
+ prefixBuf.Size(prefixLength);
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<RandomAccessFile>(new EncryptedRandomAccessFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // NewWritableFile opens a file for sequential writing.
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_writes) {
+ return Status::InvalidArgument();
+ }
+ // Open file using underlying Env implementation
+ std::unique_ptr<WritableFile> underlying;
+ Status status = EnvWrapper::NewWritableFile(fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Initialize & write prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ // Initialize prefix
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
+ prefixBuf.Size(prefixLength);
+ prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
+ // Write prefix
+ status = underlying->Append(prefixSlice);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ Status ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_writes) {
+ return Status::InvalidArgument();
+ }
+ // Open file using underlying Env implementation
+ std::unique_ptr<WritableFile> underlying;
+ Status status = EnvWrapper::ReopenWritableFile(fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Initialize & write prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ // Initialize prefix
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
+ prefixBuf.Size(prefixLength);
+ prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
+ // Write prefix
+ status = underlying->Append(prefixSlice);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // Reuse an existing file by renaming it and opening it as writable.
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_writes) {
+ return Status::InvalidArgument();
+ }
+ // Open file using underlying Env implementation
+ std::unique_ptr<WritableFile> underlying;
+ Status status = EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Initialize & write prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ // Initialize prefix
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
+ prefixBuf.Size(prefixLength);
+ prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
+ // Write prefix
+ status = underlying->Append(prefixSlice);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // Open `fname` for random read and write, if file doesn't exist the file
+ // will be created. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override {
+ result->reset();
+ if (options.use_mmap_reads || options.use_mmap_writes) {
+ return Status::InvalidArgument();
+ }
+ // Check file exists
+ bool isNewFile = !FileExists(fname).ok();
+
+ // Open file using underlying Env implementation
+ std::unique_ptr<RandomRWFile> underlying;
+ Status status = EnvWrapper::NewRandomRWFile(fname, &underlying, options);
+ if (!status.ok()) {
+ return status;
+ }
+ // Read or Initialize & write prefix (if needed)
+ AlignedBuffer prefixBuf;
+ Slice prefixSlice;
+ size_t prefixLength = provider_->GetPrefixLength();
+ if (prefixLength > 0) {
+ prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
+ prefixBuf.AllocateNewBuffer(prefixLength);
+ if (!isNewFile) {
+ // File already exists, read prefix
+ status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart());
+ if (!status.ok()) {
+ return status;
+ }
+ prefixBuf.Size(prefixLength);
+ } else {
+ // File is new, initialize & write prefix
+ provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
+ prefixBuf.Size(prefixLength);
+ prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
+ // Write prefix
+ status = underlying->Write(0, prefixSlice);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+ }
+ // Create cipher stream
+ std::unique_ptr<BlockAccessCipherStream> stream;
+ status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
+ if (!status.ok()) {
+ return status;
+ }
+ (*result) = std::unique_ptr<RandomRWFile>(new EncryptedRandomRWFile(underlying.release(), stream.release(), prefixLength));
+ return Status::OK();
+ }
+
+ // Store in *result the attributes of the children of the specified directory.
+ // In case the implementation lists the directory prior to iterating the files
+ // and files are concurrently deleted, the deleted files will be omitted from
+ // result.
+ // The name attributes are relative to "dir".
+ // Original contents of *results are dropped.
+ // Returns OK if "dir" exists and "*result" contains its children.
+ // NotFound if "dir" does not exist, the calling process does not have
+ // permission to access "dir", or if "dir" is invalid.
+ // IOError if an IO Error was encountered
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ auto status = EnvWrapper::GetChildrenFileAttributes(dir, result);
+ if (!status.ok()) {
+ return status;
+ }
+ size_t prefixLength = provider_->GetPrefixLength();
+ for (auto it = std::begin(*result); it!=std::end(*result); ++it) {
+ assert(it->size_bytes >= prefixLength);
+ it->size_bytes -= prefixLength;
+ }
+ return Status::OK();
+ }
+
+ // Store the size of fname in *file_size.
+ Status GetFileSize(const std::string& fname, uint64_t* file_size) override {
+ auto status = EnvWrapper::GetFileSize(fname, file_size);
+ if (!status.ok()) {
+ return status;
+ }
+ size_t prefixLength = provider_->GetPrefixLength();
+ assert(*file_size >= prefixLength);
+ *file_size -= prefixLength;
+ return Status::OK();
+ }
+
+ private:
+ EncryptionProvider *provider_;
+};
+
+// Returns an Env that encrypts data when stored on disk and decrypts data when
+// read from disk.
+Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider) {
+ return new EncryptedEnv(base_env, provider);
+}
+
+// Encrypt one or more (partial) blocks of data at the file offset.
+// Length of data is given in dataSize.
+Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char *data, size_t dataSize) {
+ // Calculate block index
+ auto blockSize = BlockSize();
+ uint64_t blockIndex = fileOffset / blockSize;
+ size_t blockOffset = fileOffset % blockSize;
+ std::unique_ptr<char[]> blockBuffer;
+
+ std::string scratch;
+ AllocateScratch(scratch);
+
+ // Encrypt individual blocks.
+ while (1) {
+ char *block = data;
+ size_t n = std::min(dataSize, blockSize - blockOffset);
+ if (n != blockSize) {
+ // We're not encrypting a full block.
+ // Copy data to blockBuffer
+ if (!blockBuffer.get()) {
+ // Allocate buffer
+ blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
+ }
+ block = blockBuffer.get();
+ // Copy plain data to block buffer
+ memmove(block + blockOffset, data, n);
+ }
+ auto status = EncryptBlock(blockIndex, block, (char*)scratch.data());
+ if (!status.ok()) {
+ return status;
+ }
+ if (block != data) {
+ // Copy encrypted data back to `data`.
+ memmove(data, block + blockOffset, n);
+ }
+ dataSize -= n;
+ if (dataSize == 0) {
+ return Status::OK();
+ }
+ data += n;
+ blockOffset = 0;
+ blockIndex++;
+ }
+}
+
+// Decrypt one or more (partial) blocks of data at the file offset.
+// Length of data is given in dataSize.
+Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t dataSize) {
+ // Calculate block index
+ auto blockSize = BlockSize();
+ uint64_t blockIndex = fileOffset / blockSize;
+ size_t blockOffset = fileOffset % blockSize;
+ std::unique_ptr<char[]> blockBuffer;
+
+ std::string scratch;
+ AllocateScratch(scratch);
+
+ // Decrypt individual blocks.
+ while (1) {
+ char *block = data;
+ size_t n = std::min(dataSize, blockSize - blockOffset);
+ if (n != blockSize) {
+ // We're not decrypting a full block.
+ // Copy data to blockBuffer
+ if (!blockBuffer.get()) {
+ // Allocate buffer
+ blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
+ }
+ block = blockBuffer.get();
+ // Copy encrypted data to block buffer
+ memmove(block + blockOffset, data, n);
+ }
+ auto status = DecryptBlock(blockIndex, block, (char*)scratch.data());
+ if (!status.ok()) {
+ return status;
+ }
+ if (block != data) {
+ // Copy decrypted data back to `data`.
+ memmove(data, block + blockOffset, n);
+ }
+
+ // Simply decrementing dataSize by n could cause it to underflow,
+ // which will very likely make it read over the original bounds later
+ assert(dataSize >= n);
+ if (dataSize < n) {
+ return Status::Corruption("Cannot decrypt data at given offset");
+ }
+
+ dataSize -= n;
+ if (dataSize == 0) {
+ return Status::OK();
+ }
+ data += n;
+ blockOffset = 0;
+ blockIndex++;
+ }
+}
+
+// Encrypt a block of data.
+// Length of data is equal to BlockSize().
+Status ROT13BlockCipher::Encrypt(char *data) {
+ for (size_t i = 0; i < blockSize_; ++i) {
+ data[i] += 13;
+ }
+ return Status::OK();
+}
+
+// Decrypt a block of data.
+// Length of data is equal to BlockSize().
+Status ROT13BlockCipher::Decrypt(char *data) {
+ return Encrypt(data);
+}
+
+// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
+void CTRCipherStream::AllocateScratch(std::string& scratch) {
+ auto blockSize = cipher_.BlockSize();
+ scratch.reserve(blockSize);
+}
+
+// Encrypt a block of data at the given block index.
+// Length of data is equal to BlockSize();
+Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scratch) {
+
+ // Create nonce + counter
+ auto blockSize = cipher_.BlockSize();
+ memmove(scratch, iv_.data(), blockSize);
+ EncodeFixed64(scratch, blockIndex + initialCounter_);
+
+ // Encrypt nonce+counter
+ auto status = cipher_.Encrypt(scratch);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // XOR data with ciphertext.
+ for (size_t i = 0; i < blockSize; i++) {
+ data[i] = data[i] ^ scratch[i];
+ }
+ return Status::OK();
+}
+
+// Decrypt a block of data at the given block index.
+// Length of data is equal to BlockSize();
+Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char *data, char* scratch) {
+ // For CTR decryption & encryption are the same
+ return EncryptBlock(blockIndex, data, scratch);
+}
+
+// GetPrefixLength returns the length of the prefix that is added to every file
+// and used for storing encryption options.
+// For optimal performance, the prefix length should be a multiple of
+// the page size.
+size_t CTREncryptionProvider::GetPrefixLength() {
+ return defaultPrefixLength;
+}
+
+// decodeCTRParameters decodes the initial counter & IV from the given
+// (plain text) prefix.
+static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t &initialCounter, Slice &iv) {
+ // First block contains 64-bit initial counter
+ initialCounter = DecodeFixed64(prefix);
+ // Second block contains IV
+ iv = Slice(prefix + blockSize, blockSize);
+}
+
+// CreateNewPrefix initialized an allocated block of prefix memory
+// for a new file.
+Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
+ char* prefix,
+ size_t prefixLength) {
+ // Create & seed rnd.
+ Random rnd((uint32_t)Env::Default()->NowMicros());
+ // Fill entire prefix block with random values.
+ for (size_t i = 0; i < prefixLength; i++) {
+ prefix[i] = rnd.Uniform(256) & 0xFF;
+ }
+ // Take random data to extract initial counter & IV
+ auto blockSize = cipher_.BlockSize();
+ uint64_t initialCounter;
+ Slice prefixIV;
+ decodeCTRParameters(prefix, blockSize, initialCounter, prefixIV);
+
+ // Now populate the rest of the prefix, starting from the third block.
+ PopulateSecretPrefixPart(prefix + (2 * blockSize), prefixLength - (2 * blockSize), blockSize);
+
+ // Encrypt the prefix, starting from block 2 (leave block 0, 1 with initial counter & IV unencrypted)
+ CTRCipherStream cipherStream(cipher_, prefixIV.data(), initialCounter);
+ auto status = cipherStream.Encrypt(0, prefix + (2 * blockSize), prefixLength - (2 * blockSize));
+ if (!status.ok()) {
+ return status;
+ }
+ return Status::OK();
+}
+
+// PopulateSecretPrefixPart initializes the data into a new prefix block
+// in plain text.
+// Returns the amount of space (starting from the start of the prefix)
+// that has been initialized.
+size_t CTREncryptionProvider::PopulateSecretPrefixPart(char* /*prefix*/,
+ size_t /*prefixLength*/,
+ size_t /*blockSize*/) {
+ // Nothing to do here, put in custom data in override when needed.
+ return 0;
+}
+
+Status CTREncryptionProvider::CreateCipherStream(
+ const std::string& fname, const EnvOptions& options, Slice& prefix,
+ std::unique_ptr<BlockAccessCipherStream>* result) {
+ // Read plain text part of prefix.
+ auto blockSize = cipher_.BlockSize();
+ uint64_t initialCounter;
+ Slice iv;
+ decodeCTRParameters(prefix.data(), blockSize, initialCounter, iv);
+
+ // If the prefix is smaller than twice the block size, we would below read a
+ // very large chunk of the file (and very likely read over the bounds)
+ assert(prefix.size() >= 2 * blockSize);
+ if (prefix.size() < 2 * blockSize) {
+ return Status::Corruption("Unable to read from file " + fname +
+ ": read attempt would read beyond file bounds");
+ }
+
+ // Decrypt the encrypted part of the prefix, starting from block 2 (block 0, 1 with initial counter & IV are unencrypted)
+ CTRCipherStream cipherStream(cipher_, iv.data(), initialCounter);
+ auto status = cipherStream.Decrypt(0, (char*)prefix.data() + (2 * blockSize), prefix.size() - (2 * blockSize));
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Create cipher stream
+ return CreateCipherStreamFromPrefix(fname, options, initialCounter, iv, prefix, result);
+}
+
+// CreateCipherStreamFromPrefix creates a block access cipher stream for a file given
+// given name and options. The given prefix is already decrypted.
+Status CTREncryptionProvider::CreateCipherStreamFromPrefix(
+ const std::string& /*fname*/, const EnvOptions& /*options*/,
+ uint64_t initialCounter, const Slice& iv, const Slice& /*prefix*/,
+ std::unique_ptr<BlockAccessCipherStream>* result) {
+ (*result) = std::unique_ptr<BlockAccessCipherStream>(
+ new CTRCipherStream(cipher_, iv.data(), initialCounter));
+ return Status::OK();
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/env_hdfs.cc b/storage/rocksdb/rocksdb/env/env_hdfs.cc
new file mode 100644
index 00000000..cb84dcc6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_hdfs.cc
@@ -0,0 +1,636 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "hdfs/env_hdfs.h"
+
+#ifdef USE_HDFS
+#ifndef ROCKSDB_HDFS_FILE_C
+#define ROCKSDB_HDFS_FILE_C
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include "logging/logging.h"
+#include "rocksdb/status.h"
+#include "util/string_util.h"
+
+#define HDFS_EXISTS 0
+#define HDFS_DOESNT_EXIST -1
+#define HDFS_SUCCESS 0
+
+//
+// This file defines an HDFS environment for rocksdb. It uses the libhdfs
+// api to access HDFS. All HDFS files created by one instance of rocksdb
+// will reside on the same HDFS cluster.
+//
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// Log error message
+static Status IOError(const std::string& context, int err_number) {
+ return (err_number == ENOSPC)
+ ? Status::NoSpace(context, strerror(err_number))
+ : (err_number == ENOENT)
+ ? Status::PathNotFound(context, strerror(err_number))
+ : Status::IOError(context, strerror(err_number));
+}
+
+// assume that there is one global logger for now. It is not thread-safe,
+// but need not be because the logger is initialized at db-open time.
+static Logger* mylog = nullptr;
+
+// Used for reading a file from HDFS. It implements both sequential-read
+// access methods as well as random read access methods.
+class HdfsReadableFile : virtual public SequentialFile,
+ virtual public RandomAccessFile {
+ private:
+ hdfsFS fileSys_;
+ std::string filename_;
+ hdfsFile hfile_;
+
+ public:
+ HdfsReadableFile(hdfsFS fileSys, const std::string& fname)
+ : fileSys_(fileSys), filename_(fname), hfile_(nullptr) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile opening file %s\n",
+ filename_.c_str());
+ hfile_ = hdfsOpenFile(fileSys_, filename_.c_str(), O_RDONLY, 0, 0, 0);
+ ROCKS_LOG_DEBUG(mylog,
+ "[hdfs] HdfsReadableFile opened file %s hfile_=0x%p\n",
+ filename_.c_str(), hfile_);
+ }
+
+ virtual ~HdfsReadableFile() {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile closing file %s\n",
+ filename_.c_str());
+ hdfsCloseFile(fileSys_, hfile_);
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile closed file %s\n",
+ filename_.c_str());
+ hfile_ = nullptr;
+ }
+
+ bool isValid() {
+ return hfile_ != nullptr;
+ }
+
+ // sequential access, read data at current offset in file
+ virtual Status Read(size_t n, Slice* result, char* scratch) {
+ Status s;
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile reading %s %ld\n",
+ filename_.c_str(), n);
+
+ char* buffer = scratch;
+ size_t total_bytes_read = 0;
+ tSize bytes_read = 0;
+ tSize remaining_bytes = (tSize)n;
+
+ // Read a total of n bytes repeatedly until we hit error or eof
+ while (remaining_bytes > 0) {
+ bytes_read = hdfsRead(fileSys_, hfile_, buffer, remaining_bytes);
+ if (bytes_read <= 0) {
+ break;
+ }
+ assert(bytes_read <= remaining_bytes);
+
+ total_bytes_read += bytes_read;
+ remaining_bytes -= bytes_read;
+ buffer += bytes_read;
+ }
+ assert(total_bytes_read <= n);
+
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile read %s\n",
+ filename_.c_str());
+
+ if (bytes_read < 0) {
+ s = IOError(filename_, errno);
+ } else {
+ *result = Slice(scratch, total_bytes_read);
+ }
+
+ return s;
+ }
+
+ // random access, read data from specified offset in file
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ Status s;
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile preading %s\n",
+ filename_.c_str());
+ ssize_t bytes_read = hdfsPread(fileSys_, hfile_, offset,
+ (void*)scratch, (tSize)n);
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile pread %s\n",
+ filename_.c_str());
+ *result = Slice(scratch, (bytes_read < 0) ? 0 : bytes_read);
+ if (bytes_read < 0) {
+ // An error: return a non-ok status
+ s = IOError(filename_, errno);
+ }
+ return s;
+ }
+
+ virtual Status Skip(uint64_t n) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile skip %s\n",
+ filename_.c_str());
+ // get current offset from file
+ tOffset current = hdfsTell(fileSys_, hfile_);
+ if (current < 0) {
+ return IOError(filename_, errno);
+ }
+ // seek to new offset in file
+ tOffset newoffset = current + n;
+ int val = hdfsSeek(fileSys_, hfile_, newoffset);
+ if (val < 0) {
+ return IOError(filename_, errno);
+ }
+ return Status::OK();
+ }
+
+ private:
+
+ // returns true if we are at the end of file, false otherwise
+ bool feof() {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile feof %s\n",
+ filename_.c_str());
+ if (hdfsTell(fileSys_, hfile_) == fileSize()) {
+ return true;
+ }
+ return false;
+ }
+
+ // the current size of the file
+ tOffset fileSize() {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile fileSize %s\n",
+ filename_.c_str());
+ hdfsFileInfo* pFileInfo = hdfsGetPathInfo(fileSys_, filename_.c_str());
+ tOffset size = 0L;
+ if (pFileInfo != nullptr) {
+ size = pFileInfo->mSize;
+ hdfsFreeFileInfo(pFileInfo, 1);
+ } else {
+ throw HdfsFatalException("fileSize on unknown file " + filename_);
+ }
+ return size;
+ }
+};
+
+// Appends to an existing file in HDFS.
+class HdfsWritableFile: public WritableFile {
+ private:
+ hdfsFS fileSys_;
+ std::string filename_;
+ hdfsFile hfile_;
+
+ public:
+ HdfsWritableFile(hdfsFS fileSys, const std::string& fname,
+ const EnvOptions& options)
+ : WritableFile(options),
+ fileSys_(fileSys),
+ filename_(fname),
+ hfile_(nullptr) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile opening %s\n",
+ filename_.c_str());
+ hfile_ = hdfsOpenFile(fileSys_, filename_.c_str(), O_WRONLY, 0, 0, 0);
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile opened %s\n",
+ filename_.c_str());
+ assert(hfile_ != nullptr);
+ }
+ virtual ~HdfsWritableFile() {
+ if (hfile_ != nullptr) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile closing %s\n",
+ filename_.c_str());
+ hdfsCloseFile(fileSys_, hfile_);
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile closed %s\n",
+ filename_.c_str());
+ hfile_ = nullptr;
+ }
+ }
+
+ // If the file was successfully created, then this returns true.
+ // Otherwise returns false.
+ bool isValid() {
+ return hfile_ != nullptr;
+ }
+
+ // The name of the file, mostly needed for debug logging.
+ const std::string& getName() {
+ return filename_;
+ }
+
+ virtual Status Append(const Slice& data) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile Append %s\n",
+ filename_.c_str());
+ const char* src = data.data();
+ size_t left = data.size();
+ size_t ret = hdfsWrite(fileSys_, hfile_, src, static_cast<tSize>(left));
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile Appended %s\n",
+ filename_.c_str());
+ if (ret != left) {
+ return IOError(filename_, errno);
+ }
+ return Status::OK();
+ }
+
+ virtual Status Flush() {
+ return Status::OK();
+ }
+
+ virtual Status Sync() {
+ Status s;
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile Sync %s\n",
+ filename_.c_str());
+ if (hdfsFlush(fileSys_, hfile_) == -1) {
+ return IOError(filename_, errno);
+ }
+ if (hdfsHSync(fileSys_, hfile_) == -1) {
+ return IOError(filename_, errno);
+ }
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile Synced %s\n",
+ filename_.c_str());
+ return Status::OK();
+ }
+
+ // This is used by HdfsLogger to write data to the debug log file
+ virtual Status Append(const char* src, size_t size) {
+ if (hdfsWrite(fileSys_, hfile_, src, static_cast<tSize>(size)) !=
+ static_cast<tSize>(size)) {
+ return IOError(filename_, errno);
+ }
+ return Status::OK();
+ }
+
+ virtual Status Close() {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile closing %s\n",
+ filename_.c_str());
+ if (hdfsCloseFile(fileSys_, hfile_) != 0) {
+ return IOError(filename_, errno);
+ }
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile closed %s\n",
+ filename_.c_str());
+ hfile_ = nullptr;
+ return Status::OK();
+ }
+};
+
+// The object that implements the debug logs to reside in HDFS.
+class HdfsLogger : public Logger {
+ private:
+ HdfsWritableFile* file_;
+ uint64_t (*gettid_)(); // Return the thread id for the current thread
+
+ Status HdfsCloseHelper() {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger closed %s\n",
+ file_->getName().c_str());
+ if (mylog != nullptr && mylog == this) {
+ mylog = nullptr;
+ }
+ return Status::OK();
+ }
+
+ protected:
+ virtual Status CloseImpl() override { return HdfsCloseHelper(); }
+
+ public:
+ HdfsLogger(HdfsWritableFile* f, uint64_t (*gettid)())
+ : file_(f), gettid_(gettid) {
+ ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger opened %s\n",
+ file_->getName().c_str());
+ }
+
+ ~HdfsLogger() override {
+ if (!closed_) {
+ closed_ = true;
+ HdfsCloseHelper();
+ }
+ }
+
+ using Logger::Logv;
+ void Logv(const char* format, va_list ap) override {
+ const uint64_t thread_id = (*gettid_)();
+
+ // We try twice: the first time with a fixed-size stack allocated buffer,
+ // and the second time with a much larger dynamically allocated buffer.
+ char buffer[500];
+ for (int iter = 0; iter < 2; iter++) {
+ char* base;
+ int bufsize;
+ if (iter == 0) {
+ bufsize = sizeof(buffer);
+ base = buffer;
+ } else {
+ bufsize = 30000;
+ base = new char[bufsize];
+ }
+ char* p = base;
+ char* limit = base + bufsize;
+
+ struct timeval now_tv;
+ gettimeofday(&now_tv, nullptr);
+ const time_t seconds = now_tv.tv_sec;
+ struct tm t;
+ localtime_r(&seconds, &t);
+ p += snprintf(p, limit - p,
+ "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
+ t.tm_year + 1900,
+ t.tm_mon + 1,
+ t.tm_mday,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec,
+ static_cast<int>(now_tv.tv_usec),
+ static_cast<long long unsigned int>(thread_id));
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ p += vsnprintf(p, limit - p, format, backup_ap);
+ va_end(backup_ap);
+ }
+
+ // Truncate to available space if necessary
+ if (p >= limit) {
+ if (iter == 0) {
+ continue; // Try again with larger buffer
+ } else {
+ p = limit - 1;
+ }
+ }
+
+ // Add newline if necessary
+ if (p == base || p[-1] != '\n') {
+ *p++ = '\n';
+ }
+
+ assert(p <= limit);
+ file_->Append(base, p-base);
+ file_->Flush();
+ if (base != buffer) {
+ delete[] base;
+ }
+ break;
+ }
+ }
+};
+
+} // namespace
+
+// Finally, the hdfs environment
+
+const std::string HdfsEnv::kProto = "hdfs://";
+const std::string HdfsEnv::pathsep = "/";
+
+// open a file for sequential reading
+Status HdfsEnv::NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& /*options*/) {
+ result->reset();
+ HdfsReadableFile* f = new HdfsReadableFile(fileSys_, fname);
+ if (f == nullptr || !f->isValid()) {
+ delete f;
+ *result = nullptr;
+ return IOError(fname, errno);
+ }
+ result->reset(dynamic_cast<SequentialFile*>(f));
+ return Status::OK();
+}
+
+// open a file for random reading
+Status HdfsEnv::NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& /*options*/) {
+ result->reset();
+ HdfsReadableFile* f = new HdfsReadableFile(fileSys_, fname);
+ if (f == nullptr || !f->isValid()) {
+ delete f;
+ *result = nullptr;
+ return IOError(fname, errno);
+ }
+ result->reset(dynamic_cast<RandomAccessFile*>(f));
+ return Status::OK();
+}
+
+// create a new file for writing
+Status HdfsEnv::NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) {
+ result->reset();
+ Status s;
+ HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname, options);
+ if (f == nullptr || !f->isValid()) {
+ delete f;
+ *result = nullptr;
+ return IOError(fname, errno);
+ }
+ result->reset(dynamic_cast<WritableFile*>(f));
+ return Status::OK();
+}
+
+class HdfsDirectory : public Directory {
+ public:
+ explicit HdfsDirectory(int fd) : fd_(fd) {}
+ ~HdfsDirectory() {}
+
+ Status Fsync() override { return Status::OK(); }
+
+ int GetFd() const { return fd_; }
+
+ private:
+ int fd_;
+};
+
+Status HdfsEnv::NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) {
+ int value = hdfsExists(fileSys_, name.c_str());
+ switch (value) {
+ case HDFS_EXISTS:
+ result->reset(new HdfsDirectory(0));
+ return Status::OK();
+ default: // fail if the directory doesn't exist
+ ROCKS_LOG_FATAL(mylog, "NewDirectory hdfsExists call failed");
+ throw HdfsFatalException("hdfsExists call failed with error " +
+ ToString(value) + " on path " + name +
+ ".\n");
+ }
+}
+
+Status HdfsEnv::FileExists(const std::string& fname) {
+ int value = hdfsExists(fileSys_, fname.c_str());
+ switch (value) {
+ case HDFS_EXISTS:
+ return Status::OK();
+ case HDFS_DOESNT_EXIST:
+ return Status::NotFound();
+ default: // anything else should be an error
+ ROCKS_LOG_FATAL(mylog, "FileExists hdfsExists call failed");
+ return Status::IOError("hdfsExists call failed with error " +
+ ToString(value) + " on path " + fname + ".\n");
+ }
+}
+
+Status HdfsEnv::GetChildren(const std::string& path,
+ std::vector<std::string>* result) {
+ int value = hdfsExists(fileSys_, path.c_str());
+ switch (value) {
+ case HDFS_EXISTS: { // directory exists
+ int numEntries = 0;
+ hdfsFileInfo* pHdfsFileInfo = 0;
+ pHdfsFileInfo = hdfsListDirectory(fileSys_, path.c_str(), &numEntries);
+ if (numEntries >= 0) {
+ for(int i = 0; i < numEntries; i++) {
+ std::string pathname(pHdfsFileInfo[i].mName);
+ size_t pos = pathname.rfind("/");
+ if (std::string::npos != pos) {
+ result->push_back(pathname.substr(pos + 1));
+ }
+ }
+ if (pHdfsFileInfo != nullptr) {
+ hdfsFreeFileInfo(pHdfsFileInfo, numEntries);
+ }
+ } else {
+ // numEntries < 0 indicates error
+ ROCKS_LOG_FATAL(mylog, "hdfsListDirectory call failed with error ");
+ throw HdfsFatalException(
+ "hdfsListDirectory call failed negative error.\n");
+ }
+ break;
+ }
+ case HDFS_DOESNT_EXIST: // directory does not exist, exit
+ return Status::NotFound();
+ default: // anything else should be an error
+ ROCKS_LOG_FATAL(mylog, "GetChildren hdfsExists call failed");
+ throw HdfsFatalException("hdfsExists call failed with error " +
+ ToString(value) + ".\n");
+ }
+ return Status::OK();
+}
+
+Status HdfsEnv::DeleteFile(const std::string& fname) {
+ if (hdfsDelete(fileSys_, fname.c_str(), 1) == 0) {
+ return Status::OK();
+ }
+ return IOError(fname, errno);
+};
+
+Status HdfsEnv::CreateDir(const std::string& name) {
+ if (hdfsCreateDirectory(fileSys_, name.c_str()) == 0) {
+ return Status::OK();
+ }
+ return IOError(name, errno);
+};
+
+Status HdfsEnv::CreateDirIfMissing(const std::string& name) {
+ const int value = hdfsExists(fileSys_, name.c_str());
+ // Not atomic. state might change b/w hdfsExists and CreateDir.
+ switch (value) {
+ case HDFS_EXISTS:
+ return Status::OK();
+ case HDFS_DOESNT_EXIST:
+ return CreateDir(name);
+ default: // anything else should be an error
+ ROCKS_LOG_FATAL(mylog, "CreateDirIfMissing hdfsExists call failed");
+ throw HdfsFatalException("hdfsExists call failed with error " +
+ ToString(value) + ".\n");
+ }
+};
+
+Status HdfsEnv::DeleteDir(const std::string& name) {
+ return DeleteFile(name);
+};
+
+Status HdfsEnv::GetFileSize(const std::string& fname, uint64_t* size) {
+ *size = 0L;
+ hdfsFileInfo* pFileInfo = hdfsGetPathInfo(fileSys_, fname.c_str());
+ if (pFileInfo != nullptr) {
+ *size = pFileInfo->mSize;
+ hdfsFreeFileInfo(pFileInfo, 1);
+ return Status::OK();
+ }
+ return IOError(fname, errno);
+}
+
+Status HdfsEnv::GetFileModificationTime(const std::string& fname,
+ uint64_t* time) {
+ hdfsFileInfo* pFileInfo = hdfsGetPathInfo(fileSys_, fname.c_str());
+ if (pFileInfo != nullptr) {
+ *time = static_cast<uint64_t>(pFileInfo->mLastMod);
+ hdfsFreeFileInfo(pFileInfo, 1);
+ return Status::OK();
+ }
+ return IOError(fname, errno);
+
+}
+
+// The rename is not atomic. HDFS does not allow a renaming if the
+// target already exists. So, we delete the target before attempting the
+// rename.
+Status HdfsEnv::RenameFile(const std::string& src, const std::string& target) {
+ hdfsDelete(fileSys_, target.c_str(), 1);
+ if (hdfsRename(fileSys_, src.c_str(), target.c_str()) == 0) {
+ return Status::OK();
+ }
+ return IOError(src, errno);
+}
+
+Status HdfsEnv::LockFile(const std::string& /*fname*/, FileLock** lock) {
+ // there isn's a very good way to atomically check and create
+ // a file via libhdfs
+ *lock = nullptr;
+ return Status::OK();
+}
+
+Status HdfsEnv::UnlockFile(FileLock* /*lock*/) { return Status::OK(); }
+
+Status HdfsEnv::NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) {
+ // EnvOptions is used exclusively for its `strict_bytes_per_sync` value. That
+ // option is only intended for WAL/flush/compaction writes, so turn it off in
+ // the logger.
+ EnvOptions options;
+ options.strict_bytes_per_sync = false;
+ HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname, options);
+ if (f == nullptr || !f->isValid()) {
+ delete f;
+ *result = nullptr;
+ return IOError(fname, errno);
+ }
+ HdfsLogger* h = new HdfsLogger(f, &HdfsEnv::gettid);
+ result->reset(h);
+ if (mylog == nullptr) {
+ // mylog = h; // uncomment this for detailed logging
+ }
+ return Status::OK();
+}
+
+// The factory method for creating an HDFS Env
+Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname) {
+ *hdfs_env = new HdfsEnv(fsname);
+ return Status::OK();
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_HDFS_FILE_C
+
+#else // USE_HDFS
+
+// dummy placeholders used when HDFS is not available
+namespace ROCKSDB_NAMESPACE {
+Status HdfsEnv::NewSequentialFile(const std::string& /*fname*/,
+ std::unique_ptr<SequentialFile>* /*result*/,
+ const EnvOptions& /*options*/) {
+ return Status::NotSupported("Not compiled with hdfs support");
+}
+
+ Status NewHdfsEnv(Env** /*hdfs_env*/, const std::string& /*fsname*/) {
+ return Status::NotSupported("Not compiled with hdfs support");
+ }
+ } // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/env/env_posix.cc b/storage/rocksdb/rocksdb/env/env_posix.cc
new file mode 100644
index 00000000..861fbcf6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_posix.cc
@@ -0,0 +1,527 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <dirent.h>
+#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
+#include <dlfcn.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+
+#if defined(OS_LINUX)
+#include <linux/fs.h>
+#endif
+#if defined(ROCKSDB_IOURING_PRESENT)
+#include <liburing.h>
+#endif
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#if defined(OS_LINUX) || defined(OS_SOLARIS) || defined(OS_ANDROID)
+#include <sys/statfs.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#endif
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#if defined(ROCKSDB_IOURING_PRESENT)
+#include <sys/uio.h>
+#endif
+#include <time.h>
+#include <algorithm>
+// Get nano time includes
+#if defined(OS_LINUX) || defined(OS_FREEBSD)
+#elif defined(__MACH__)
+#include <Availability.h>
+#include <mach/clock.h>
+#include <mach/mach.h>
+#else
+#include <chrono>
+#endif
+#include <deque>
+#include <set>
+#include <vector>
+
+#include "env/composite_env_wrapper.h"
+#include "env/io_posix.h"
+#include "logging/logging.h"
+#include "logging/posix_logger.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/thread_status_updater.h"
+#include "port/port.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/compression_context_cache.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "util/thread_local.h"
+#include "util/threadpool_imp.h"
+
+#if !defined(TMPFS_MAGIC)
+#define TMPFS_MAGIC 0x01021994
+#endif
+#if !defined(XFS_SUPER_MAGIC)
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+#if !defined(EXT4_SUPER_MAGIC)
+#define EXT4_SUPER_MAGIC 0xEF53
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+#if defined(OS_WIN)
+static const std::string kSharedLibExt = ".dll";
+static const char kPathSeparator = ';';
+#else
+static const char kPathSeparator = ':';
+#if defined(OS_MACOSX)
+static const std::string kSharedLibExt = ".dylib";
+#else
+static const std::string kSharedLibExt = ".so";
+#endif
+#endif
+
+namespace {
+
+ThreadStatusUpdater* CreateThreadStatusUpdater() {
+ return new ThreadStatusUpdater();
+}
+
+#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
+class PosixDynamicLibrary : public DynamicLibrary {
+ public:
+ PosixDynamicLibrary(const std::string& name, void* handle)
+ : name_(name), handle_(handle) {}
+ ~PosixDynamicLibrary() override { dlclose(handle_); }
+
+ Status LoadSymbol(const std::string& sym_name, void** func) override {
+ assert(nullptr != func);
+ dlerror(); // Clear any old error
+ *func = dlsym(handle_, sym_name.c_str());
+ if (*func != nullptr) {
+ return Status::OK();
+ } else {
+ char* err = dlerror();
+ return Status::NotFound("Error finding symbol: " + sym_name, err);
+ }
+ }
+
+ const char* Name() const override { return name_.c_str(); }
+
+ private:
+ std::string name_;
+ void* handle_;
+};
+#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION
+
+class PosixEnv : public CompositeEnvWrapper {
+ public:
+ PosixEnv();
+
+ ~PosixEnv() override {
+ for (const auto tid : threads_to_join_) {
+ pthread_join(tid, nullptr);
+ }
+ for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
+ thread_pools_[pool_id].JoinAllThreads();
+ }
+ // Delete the thread_status_updater_ only when the current Env is not
+ // Env::Default(). This is to avoid the free-after-use error when
+ // Env::Default() is destructed while some other child threads are
+ // still trying to update thread status.
+ if (this != Env::Default()) {
+ delete thread_status_updater_;
+ }
+ }
+
+ void SetFD_CLOEXEC(int fd, const EnvOptions* options) {
+ if ((options == nullptr || options->set_fd_cloexec) && fd > 0) {
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+ }
+ }
+
+#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
+ // Loads the named library into the result.
+ // If the input name is empty, the current executable is loaded
+ // On *nix systems, a "lib" prefix is added to the name if one is not supplied
+ // Comparably, the appropriate shared library extension is added to the name
+ // if not supplied. If search_path is not specified, the shared library will
+ // be loaded using the default path (LD_LIBRARY_PATH) If search_path is
+ // specified, the shared library will be searched for in the directories
+ // provided by the search path
+ Status LoadLibrary(const std::string& name, const std::string& path,
+ std::shared_ptr<DynamicLibrary>* result) override {
+ Status status;
+ assert(result != nullptr);
+ if (name.empty()) {
+ void* hndl = dlopen(NULL, RTLD_NOW);
+ if (hndl != nullptr) {
+ result->reset(new PosixDynamicLibrary(name, hndl));
+ return Status::OK();
+ }
+ } else {
+ std::string library_name = name;
+ if (library_name.find(kSharedLibExt) == std::string::npos) {
+ library_name = library_name + kSharedLibExt;
+ }
+#if !defined(OS_WIN)
+ if (library_name.find('/') == std::string::npos &&
+ library_name.compare(0, 3, "lib") != 0) {
+ library_name = "lib" + library_name;
+ }
+#endif
+ if (path.empty()) {
+ void* hndl = dlopen(library_name.c_str(), RTLD_NOW);
+ if (hndl != nullptr) {
+ result->reset(new PosixDynamicLibrary(library_name, hndl));
+ return Status::OK();
+ }
+ } else {
+ std::string local_path;
+ std::stringstream ss(path);
+ while (getline(ss, local_path, kPathSeparator)) {
+ if (!path.empty()) {
+ std::string full_name = local_path + "/" + library_name;
+ void* hndl = dlopen(full_name.c_str(), RTLD_NOW);
+ if (hndl != nullptr) {
+ result->reset(new PosixDynamicLibrary(full_name, hndl));
+ return Status::OK();
+ }
+ }
+ }
+ }
+ }
+ return Status::IOError(
+ IOErrorMsg("Failed to open shared library: xs", name), dlerror());
+ }
+#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION
+
+ void Schedule(void (*function)(void* arg1), void* arg, Priority pri = LOW,
+ void* tag = nullptr,
+ void (*unschedFunction)(void* arg) = nullptr) override;
+
+ int UnSchedule(void* arg, Priority pri) override;
+
+ void StartThread(void (*function)(void* arg), void* arg) override;
+
+ void WaitForJoin() override;
+
+ unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override;
+
+ Status GetTestDirectory(std::string* result) override {
+ const char* env = getenv("TEST_TMPDIR");
+ if (env && env[0] != '\0') {
+ *result = env;
+ } else {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "/tmp/rocksdbtest-%d", int(geteuid()));
+ *result = buf;
+ }
+ // Directory may already exist
+ CreateDir(*result);
+ return Status::OK();
+ }
+
+ Status GetThreadList(std::vector<ThreadStatus>* thread_list) override {
+ assert(thread_status_updater_);
+ return thread_status_updater_->GetThreadList(thread_list);
+ }
+
+ static uint64_t gettid(pthread_t tid) {
+ uint64_t thread_id = 0;
+ memcpy(&thread_id, &tid, std::min(sizeof(thread_id), sizeof(tid)));
+ return thread_id;
+ }
+
+ static uint64_t gettid() {
+ pthread_t tid = pthread_self();
+ return gettid(tid);
+ }
+
+ uint64_t GetThreadID() const override { return gettid(pthread_self()); }
+
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override {
+ FILE* f;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ f = fopen(fname.c_str(),
+ "w"
+#ifdef __GLIBC_PREREQ
+#if __GLIBC_PREREQ(2, 7)
+ "e" // glibc extension to enable O_CLOEXEC
+#endif
+#endif
+ );
+ }
+ if (f == nullptr) {
+ result->reset();
+ return IOError("when fopen a file for new logger", fname, errno);
+ } else {
+ int fd = fileno(f);
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 4 * 1024);
+#endif
+ SetFD_CLOEXEC(fd, nullptr);
+ result->reset(new PosixLogger(f, &PosixEnv::gettid, this));
+ return Status::OK();
+ }
+ }
+
+ uint64_t NowMicros() override {
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
+ }
+
+ uint64_t NowNanos() override {
+#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX)
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
+#elif defined(OS_SOLARIS)
+ return gethrtime();
+#elif defined(__MACH__)
+ clock_serv_t cclock;
+ mach_timespec_t ts;
+ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+ clock_get_time(cclock, &ts);
+ mach_port_deallocate(mach_task_self(), cclock);
+ return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
+#else
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch()).count();
+#endif
+ }
+
+ uint64_t NowCPUNanos() override {
+#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX) || \
+ (defined(__MACH__) && defined(__MAC_10_12))
+ struct timespec ts;
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+ return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
+#endif
+ return 0;
+ }
+
+ void SleepForMicroseconds(int micros) override { usleep(micros); }
+
+ Status GetHostName(char* name, uint64_t len) override {
+ int ret = gethostname(name, static_cast<size_t>(len));
+ if (ret < 0) {
+ if (errno == EFAULT || errno == EINVAL) {
+ return Status::InvalidArgument(strerror(errno));
+ } else {
+ return IOError("GetHostName", name, errno);
+ }
+ }
+ return Status::OK();
+ }
+
+ Status GetCurrentTime(int64_t* unix_time) override {
+ time_t ret = time(nullptr);
+ if (ret == (time_t) -1) {
+ return IOError("GetCurrentTime", "", errno);
+ }
+ *unix_time = (int64_t) ret;
+ return Status::OK();
+ }
+
+ ThreadStatusUpdater* GetThreadStatusUpdater() const override {
+ return Env::GetThreadStatusUpdater();
+ }
+
+ std::string GenerateUniqueId() override { return Env::GenerateUniqueId(); }
+
+ // Allow increasing the number of worker threads.
+ void SetBackgroundThreads(int num, Priority pri) override {
+ assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
+ thread_pools_[pri].SetBackgroundThreads(num);
+ }
+
+ int GetBackgroundThreads(Priority pri) override {
+ assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
+ return thread_pools_[pri].GetBackgroundThreads();
+ }
+
+ Status SetAllowNonOwnerAccess(bool allow_non_owner_access) override {
+ allow_non_owner_access_ = allow_non_owner_access;
+ return Status::OK();
+ }
+
+ // Allow increasing the number of worker threads.
+ void IncBackgroundThreadsIfNeeded(int num, Priority pri) override {
+ assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
+ thread_pools_[pri].IncBackgroundThreadsIfNeeded(num);
+ }
+
+ void LowerThreadPoolIOPriority(Priority pool = LOW) override {
+ assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH);
+#ifdef OS_LINUX
+ thread_pools_[pool].LowerIOPriority();
+#else
+ (void)pool;
+#endif
+ }
+
+ void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
+ assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH);
+#ifdef OS_LINUX
+ thread_pools_[pool].LowerCPUPriority();
+#else
+ (void)pool;
+#endif
+ }
+
+ std::string TimeToString(uint64_t secondsSince1970) override {
+ const time_t seconds = (time_t)secondsSince1970;
+ struct tm t;
+ int maxsize = 64;
+ std::string dummy;
+ dummy.reserve(maxsize);
+ dummy.resize(maxsize);
+ char* p = &dummy[0];
+ localtime_r(&seconds, &t);
+ snprintf(p, maxsize,
+ "%04d/%02d/%02d-%02d:%02d:%02d ",
+ t.tm_year + 1900,
+ t.tm_mon + 1,
+ t.tm_mday,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec);
+ return dummy;
+ }
+
+ private:
+ std::vector<ThreadPoolImpl> thread_pools_;
+ pthread_mutex_t mu_;
+ std::vector<pthread_t> threads_to_join_;
+ // If true, allow non owner read access for db files. Otherwise, non-owner
+ // has no access to db files.
+ bool allow_non_owner_access_;
+};
+
+PosixEnv::PosixEnv()
+ : CompositeEnvWrapper(this, FileSystem::Default().get()),
+ thread_pools_(Priority::TOTAL),
+ allow_non_owner_access_(true) {
+ ThreadPoolImpl::PthreadCall("mutex_init", pthread_mutex_init(&mu_, nullptr));
+ for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
+ thread_pools_[pool_id].SetThreadPriority(
+ static_cast<Env::Priority>(pool_id));
+ // This allows later initializing the thread-local-env of each thread.
+ thread_pools_[pool_id].SetHostEnv(this);
+ }
+ thread_status_updater_ = CreateThreadStatusUpdater();
+}
+
+void PosixEnv::Schedule(void (*function)(void* arg1), void* arg, Priority pri,
+ void* tag, void (*unschedFunction)(void* arg)) {
+ assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
+ thread_pools_[pri].Schedule(function, arg, tag, unschedFunction);
+}
+
+int PosixEnv::UnSchedule(void* arg, Priority pri) {
+ return thread_pools_[pri].UnSchedule(arg);
+}
+
+unsigned int PosixEnv::GetThreadPoolQueueLen(Priority pri) const {
+ assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
+ return thread_pools_[pri].GetQueueLen();
+}
+
+struct StartThreadState {
+ void (*user_function)(void*);
+ void* arg;
+};
+
+static void* StartThreadWrapper(void* arg) {
+ StartThreadState* state = reinterpret_cast<StartThreadState*>(arg);
+ state->user_function(state->arg);
+ delete state;
+ return nullptr;
+}
+
+void PosixEnv::StartThread(void (*function)(void* arg), void* arg) {
+ pthread_t t;
+ StartThreadState* state = new StartThreadState;
+ state->user_function = function;
+ state->arg = arg;
+ ThreadPoolImpl::PthreadCall(
+ "start thread", pthread_create(&t, nullptr, &StartThreadWrapper, state));
+ ThreadPoolImpl::PthreadCall("lock", pthread_mutex_lock(&mu_));
+ threads_to_join_.push_back(t);
+ ThreadPoolImpl::PthreadCall("unlock", pthread_mutex_unlock(&mu_));
+}
+
+void PosixEnv::WaitForJoin() {
+ for (const auto tid : threads_to_join_) {
+ pthread_join(tid, nullptr);
+ }
+ threads_to_join_.clear();
+}
+
+} // namespace
+
+std::string Env::GenerateUniqueId() {
+ std::string uuid_file = "/proc/sys/kernel/random/uuid";
+
+ Status s = FileExists(uuid_file);
+ if (s.ok()) {
+ std::string uuid;
+ s = ReadFileToString(this, uuid_file, &uuid);
+ if (s.ok()) {
+ return uuid;
+ }
+ }
+ // Could not read uuid_file - generate uuid using "nanos-random"
+ Random64 r(time(nullptr));
+ uint64_t random_uuid_portion =
+ r.Uniform(std::numeric_limits<uint64_t>::max());
+ uint64_t nanos_uuid_portion = NowNanos();
+ char uuid2[200];
+ snprintf(uuid2,
+ 200,
+ "%lx-%lx",
+ (unsigned long)nanos_uuid_portion,
+ (unsigned long)random_uuid_portion);
+ return uuid2;
+}
+
+//
+// Default Posix Env
+//
+Env* Env::Default() {
+ // The following function call initializes the singletons of ThreadLocalPtr
+ // right before the static default_env. This guarantees default_env will
+ // always being destructed before the ThreadLocalPtr singletons get
+ // destructed as C++ guarantees that the destructions of static variables
+ // is in the reverse order of their constructions.
+ //
+ // Since static members are destructed in the reverse order
+ // of their construction, having this call here guarantees that
+ // the destructor of static PosixEnv will go first, then the
+ // the singletons of ThreadLocalPtr.
+ ThreadLocalPtr::InitSingletons();
+ CompressionContextCache::InitSingleton();
+ INIT_SYNC_POINT_SINGLETONS();
+ static PosixEnv default_env;
+ static CompositeEnvWrapper composite_env(&default_env,
+ FileSystem::Default().get());
+ return &composite_env;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/env_test.cc b/storage/rocksdb/rocksdb/env/env_test.cc
new file mode 100644
index 00000000..98f73d9a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/env_test.cc
@@ -0,0 +1,1895 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sys/ioctl.h>
+#endif
+
+#include <sys/types.h>
+
+#include <iostream>
+#include <unordered_set>
+#include <atomic>
+#include <list>
+
+#ifdef OS_LINUX
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+#include <errno.h>
+#endif
+
+#include "env/env_chroot.h"
+#include "logging/log_buffer.h"
+#include "port/malloc.h"
+#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/coding.h"
+#include "util/mutexlock.h"
+#include "util/string_util.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*);
+};
+
+std::unique_ptr<char, Deleter> NewAligned(const size_t size, const char ch) {
+ char* ptr = nullptr;
+#ifdef OS_WIN
+ if (nullptr == (ptr = reinterpret_cast<char*>(_aligned_malloc(size, kPageSize)))) {
+ return std::unique_ptr<char, Deleter>(nullptr, Deleter(_aligned_free));
+ }
+ std::unique_ptr<char, Deleter> uptr(ptr, Deleter(_aligned_free));
+#else
+ if (posix_memalign(reinterpret_cast<void**>(&ptr), kPageSize, size) != 0) {
+ return std::unique_ptr<char, Deleter>(nullptr, Deleter(free));
+ }
+ std::unique_ptr<char, Deleter> 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) {}
+};
+
+class EnvPosixTestWithParam
+ : public EnvPosixTest,
+ public ::testing::WithParamInterface<std::pair<Env*, bool>> {
+ public:
+ EnvPosixTestWithParam() {
+ std::pair<Env*, bool> 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<std::atomic<bool>*>(ptr)->store(true);
+}
+
+TEST_F(EnvPosixTest, DISABLED_RunImmediately) {
+ for (int pri = Env::BOTTOM; pri < Env::TOTAL; ++pri) {
+ std::atomic<bool> called(false);
+ env_->SetBackgroundThreads(1, static_cast<Env::Priority>(pri));
+ env_->Schedule(&SetBool, &called, static_cast<Env::Priority>(pri));
+ Env::Default()->SleepForMicroseconds(kDelayMicros);
+ ASSERT_TRUE(called.load());
+ }
+}
+
+TEST_F(EnvPosixTest, RunEventually) {
+ std::atomic<bool> 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<WritableFile> 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<std::string> fileNames{
+ test::PerThreadDBPath(env_, "testfile"),
+ test::PerThreadDBPath(env_, "testfile1")};
+ std::unique_ptr<WritableFile> wfile;
+ ASSERT_OK(env_->NewWritableFile(fileNames[0], &wfile, soptions));
+ ASSERT_OK(env_->NewWritableFile(fileNames[1], &wfile, soptions));
+ wfile.reset();
+ std::unique_ptr<RandomRWFile> 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);
+ }
+ 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);
+ }
+ env_->DeleteFile(filename);
+ }
+ }
+}
+#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<WritableFile> wfile;
+ const EnvOptions soptions;
+ ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions));
+
+ Random rnd(301);
+ test::RandomString(&rnd, kFileBytes, &expected_data);
+ ASSERT_OK(wfile->Append(expected_data));
+ }
+
+ std::unique_ptr<MemoryMappedFileBuffer> 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<const char*>(mmap_buffer->GetBase()),
+ mmap_buffer->GetLen());
+ ASSERT_EQ(expected_data, actual_data);
+}
+
+#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
+TEST_F(EnvPosixTest, LoadRocksDBLibrary) {
+ std::shared_ptr<DynamicLibrary> library;
+ std::function<void*(void*, const char*)> 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<DynamicLibrary> library;
+ std::function<void*(void*, const char*)> 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<bool> 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) {
+ std::atomic<int> last_id(0);
+
+ struct CB {
+ std::atomic<int>* last_id_ptr; // Pointer to shared slot
+ int id; // Order# for the execution of this callback
+
+ CB(std::atomic<int>* p, int i) : last_id_ptr(p), id(i) {}
+
+ static void Run(void* v) {
+ CB* cb = reinterpret_cast<CB*>(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);
+
+ Env::Default()->SleepForMicroseconds(kDelayMicros);
+ int cur = last_id.load(std::memory_order_acquire);
+ ASSERT_EQ(4, cur);
+ WaitThreadPoolsEmpty();
+}
+#endif
+
+struct State {
+ port::Mutex mu;
+ int val;
+ int num_running;
+};
+
+static void ThreadBody(void* arg) {
+ State* s = reinterpret_cast<State*>(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<CB*>(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<int> 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<test::SleepingBackgroundTask> 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();
+}
+
+#if (defined OS_LINUX || defined OS_WIN)
+// Travis doesn't support fallocate or getting unique ID from files for whatever
+// reason.
+#ifndef TRAVIS
+
+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<std::string> 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(),
+ strerror(errno));
+ }
+ }
+
+ 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_;
+ }
+
+ private:
+ std::string dir_;
+};
+
+#ifndef ROCKSDB_LITE
+TEST_F(EnvPosixTest, PositionedAppend) {
+ std::unique_ptr<WritableFile> writable_file;
+ EnvOptions options;
+ options.use_direct_writes = true;
+ options.use_mmap_writes = false;
+ IoctlFriendlyTmpdir ift;
+ ASSERT_OK(env_->NewWritableFile(ift.name() + "/f", &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<SequentialFile> seq_file;
+ ASSERT_OK(env_->NewSequentialFile(ift.name() + "/f", &seq_file, options));
+ size_t scratch_len = kPageSize * 2;
+ std::unique_ptr<char[]> 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]);
+}
+#endif // !ROCKSDB_LITE
+
+// `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;
+ std::string fname = ift.name() + "/testfile";
+ std::unique_ptr<WritableFile> wfile;
+ ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions));
+
+ std::unique_ptr<RandomAccessFile> 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
+ env_->DeleteFile(fname);
+ }
+}
+#endif // !defined(OS_WIN)
+
+// only works in linux platforms
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+TEST_P(EnvPosixTestWithParam, AllocateTest) {
+ if (env_ == Env::Default()) {
+ IoctlFriendlyTmpdir ift;
+ std::string fname = ift.name() + "/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 = ift.name() + "/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", strerror(err_number));
+ }
+ 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<WritableFile> 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<std::string>& 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;
+ std::vector<std::string> fnames;
+ for (int i = 0; i < 1000; ++i) {
+ fnames.push_back(ift.name() + "/" + "testfile" + ToString(i));
+
+ // Create file.
+ std::unique_ptr<WritableFile> wfile;
+ ASSERT_OK(env_->NewWritableFile(fnames[i], &wfile, soptions));
+ }
+
+ // Collect and check whether the IDs are unique.
+ std::unordered_set<std::string> ids;
+ for (const std::string fname : fnames) {
+ std::unique_ptr<RandomAccessFile> 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));
+ }
+}
+
+TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDDeletes) {
+ if (env_ == Env::Default()) {
+ EnvOptions soptions;
+ soptions.use_direct_reads = soptions.use_direct_writes = direct_io_;
+
+ IoctlFriendlyTmpdir ift;
+ 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<std::string> ids;
+ for (int i = 0; i < 1000; ++i) {
+ // Create file.
+ {
+ std::unique_ptr<WritableFile> wfile;
+ ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions));
+ }
+
+ // Get Unique ID
+ std::string unique_id;
+ {
+ std::unique_ptr<RandomAccessFile> 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<WritableFile> 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<char>(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(
+ "PosixRandomAccessFile::MultiRead:io_uring_result", [&](void* arg) {
+ if (attempt > 0) {
+ // No failure in the first attempt.
+ size_t& bytes_read = *static_cast<size_t*>(arg);
+ if (rnd.OneIn(4)) {
+ bytes_read = 0;
+ } else if (rnd.OneIn(3)) {
+ bytes_read = static_cast<size_t>(
+ rnd.Uniform(static_cast<int>(bytes_read)));
+ }
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::unique_ptr<RandomAccessFile> file;
+ std::vector<ReadRequest> reqs(3);
+ std::vector<std::unique_ptr<char, Deleter>> 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<char>(i * 2 + 1));
+ ASSERT_OK(reqs[i].status);
+ ASSERT_EQ(memcmp(reqs[i].scratch, buf.get(), kSectorSize), 0);
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ }
+}
+
+// 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<WritableFile> 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<RandomAccessFile> 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<SequentialFile> 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 // not TRAVIS
+#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<int>(sizeof(struct timeval))));
+ } else {
+ ASSERT_TRUE(n <= 48 + (512 - static_cast<int>(sizeof(struct 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<int>(max_log_size_ - sizeof(struct timeval)));
+ ASSERT_TRUE(n > static_cast<int>(max_log_size_ - sizeof(struct 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<WritableFile> 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<int*>(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);
+ 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);
+ 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());
+ 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;
+ for (int i = 0; i < kNumChildren; ++i) {
+ const std::string path =
+ test::TmpDir(env_) + "/" + "testfile_" + std::to_string(i);
+ std::unique_ptr<WritableFile> 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<int*>(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());
+ file->Append(buf);
+ data.append(std::string(4096, 'T'));
+ }
+
+ std::vector<Env::FileAttributes> file_attrs;
+ ASSERT_OK(env_->GetChildrenFileAttributes(test::TmpDir(env_), &file_attrs));
+ for (int i = 0; i < kNumChildren; ++i) {
+ const std::string name = "testfile_" + std::to_string(i);
+ const std::string path = test::TmpDir(env_) + "/" + 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 PositionedAppend(const Slice& /*data*/,
+ uint64_t /*offset*/) 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);
+ w.Append(Slice());
+ w.PositionedAppend(Slice(), 0);
+ w.Truncate(0);
+ w.Close();
+ w.Flush();
+ w.Sync();
+ 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);
+ w.InvalidateCache(0, 0);
+ w.RangeSync(0, 0);
+ w.PrepareWrite(0, 0);
+ 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);
+
+ std::unique_ptr<RandomRWFile> file;
+
+ // Cannot open non-existing file.
+ ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions()));
+
+ // Create the file using WriteableFile
+ {
+ std::unique_ptr<WritableFile> 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
+ 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
+ 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<char*>(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);
+
+ std::unique_ptr<RandomRWFile> file;
+
+#ifdef OS_LINUX
+ // Cannot open non-existing file.
+ ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions()));
+#endif
+
+ // Create the file using WriteableFile
+ {
+ std::unique_ptr<WritableFile> 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
+ test::RandomString(&rnd, 10, &buf);
+
+ // 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
+ env_->DeleteFile(path);
+}
+
+class TestEnv : public EnvWrapper {
+ public:
+ explicit TestEnv() : EnvWrapper(Env::Default()),
+ close_count(0) { }
+
+ class TestLogger : public Logger {
+ public:
+ using Logger::Logv;
+ TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
+ ~TestLogger() override {
+ if (!closed_) {
+ CloseHelper();
+ }
+ }
+ 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<Logger>* result) override {
+ result->reset(new TestLogger(this));
+ return Status::OK();
+ }
+
+ private:
+ int close_count;
+};
+
+class EnvTest : public testing::Test {};
+
+TEST_F(EnvTest, Close) {
+ TestEnv* env = new TestEnv();
+ std::shared_ptr<Logger> logger;
+ Status s;
+
+ s = env->NewLogger("", &logger);
+ ASSERT_EQ(s, Status::OK());
+ logger.get()->Close();
+ ASSERT_EQ(env->GetCloseCount(), 1);
+ // Call Close() again. CloseHelper() should not be called again
+ logger.get()->Close();
+ ASSERT_EQ(env->GetCloseCount(), 1);
+ logger.reset();
+ ASSERT_EQ(env->GetCloseCount(), 1);
+
+ s = env->NewLogger("", &logger);
+ ASSERT_EQ(s, Status::OK());
+ logger.reset();
+ ASSERT_EQ(env->GetCloseCount(), 2);
+
+ delete env;
+}
+
+INSTANTIATE_TEST_CASE_P(DefaultEnvWithoutDirectIO, EnvPosixTestWithParam,
+ ::testing::Values(std::pair<Env*, bool>(Env::Default(),
+ false)));
+#if !defined(ROCKSDB_LITE)
+INSTANTIATE_TEST_CASE_P(DefaultEnvWithDirectIO, EnvPosixTestWithParam,
+ ::testing::Values(std::pair<Env*, bool>(Env::Default(),
+ true)));
+#endif // !defined(ROCKSDB_LITE)
+
+#if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+static std::unique_ptr<Env> chroot_env(
+ NewChrootEnv(Env::Default(), test::TmpDir(Env::Default())));
+INSTANTIATE_TEST_CASE_P(
+ ChrootEnvWithoutDirectIO, EnvPosixTestWithParam,
+ ::testing::Values(std::pair<Env*, bool>(chroot_env.get(), false)));
+INSTANTIATE_TEST_CASE_P(
+ ChrootEnvWithDirectIO, EnvPosixTestWithParam,
+ ::testing::Values(std::pair<Env*, bool>(chroot_env.get(), true)));
+#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/env/file_system.cc b/storage/rocksdb/rocksdb/env/file_system.cc
new file mode 100644
index 00000000..646e6e3d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/file_system.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "env/composite_env_wrapper.h"
+#include "rocksdb/file_system.h"
+#include "options/db_options.h"
+#include "rocksdb/utilities/object_registry.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+FileSystem::FileSystem() {}
+
+FileSystem::~FileSystem() {}
+
+Status FileSystem::Load(const std::string& value,
+ std::shared_ptr<FileSystem>* result) {
+ Status s;
+#ifndef ROCKSDB_LITE
+ s = ObjectRegistry::NewInstance()->NewSharedObject<FileSystem>(value, result);
+#else
+ (void)result;
+ s = Status::NotSupported("Cannot load FileSystem in LITE mode: ", value);
+#endif
+ return s;
+}
+
+FileOptions FileSystem::OptimizeForLogRead(
+ const FileOptions& file_options) const {
+ FileOptions optimized_file_options(file_options);
+ optimized_file_options.use_direct_reads = false;
+ return optimized_file_options;
+}
+
+FileOptions FileSystem::OptimizeForManifestRead(
+ const FileOptions& file_options) const {
+ FileOptions optimized_file_options(file_options);
+ optimized_file_options.use_direct_reads = false;
+ return optimized_file_options;
+}
+
+FileOptions FileSystem::OptimizeForLogWrite(const FileOptions& file_options,
+ const DBOptions& db_options) const {
+ FileOptions optimized_file_options(file_options);
+ optimized_file_options.bytes_per_sync = db_options.wal_bytes_per_sync;
+ optimized_file_options.writable_file_max_buffer_size =
+ db_options.writable_file_max_buffer_size;
+ return optimized_file_options;
+}
+
+FileOptions FileSystem::OptimizeForManifestWrite(
+ const FileOptions& file_options) const {
+ return file_options;
+}
+
+FileOptions FileSystem::OptimizeForCompactionTableWrite(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& db_options) const {
+ FileOptions optimized_file_options(file_options);
+ optimized_file_options.use_direct_writes =
+ db_options.use_direct_io_for_flush_and_compaction;
+ return optimized_file_options;
+}
+
+FileOptions FileSystem::OptimizeForCompactionTableRead(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& db_options) const {
+ FileOptions optimized_file_options(file_options);
+ optimized_file_options.use_direct_reads = db_options.use_direct_reads;
+ return optimized_file_options;
+}
+
+Status ReadFileToString(FileSystem* fs, const std::string& fname,
+ std::string* data) {
+ FileOptions soptions;
+ data->clear();
+ std::unique_ptr<FSSequentialFile> file;
+ Status s = fs->NewSequentialFile(fname, soptions, &file, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ static const int kBufferSize = 8192;
+ char* space = new char[kBufferSize];
+ while (true) {
+ Slice fragment;
+ s = file->Read(kBufferSize, IOOptions(), &fragment, space,
+ nullptr);
+ if (!s.ok()) {
+ break;
+ }
+ data->append(fragment.data(), fragment.size());
+ if (fragment.empty()) {
+ break;
+ }
+ }
+ delete[] space;
+ return s;
+}
+
+#ifdef OS_WIN
+std::shared_ptr<FileSystem> FileSystem::Default() {
+ static LegacyFileSystemWrapper default_fs(Env::Default());
+ static std::shared_ptr<LegacyFileSystemWrapper> default_fs_ptr(
+ &default_fs, [](LegacyFileSystemWrapper*) {});
+ return default_fs_ptr;
+}
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/fs_posix.cc b/storage/rocksdb/rocksdb/env/fs_posix.cc
new file mode 100644
index 00000000..0f1a41ac
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/fs_posix.cc
@@ -0,0 +1,913 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <dirent.h>
+#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
+#include <dlfcn.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+
+#if defined(OS_LINUX)
+#include <linux/fs.h>
+#endif
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#if defined(OS_LINUX) || defined(OS_SOLARIS) || defined(OS_ANDROID)
+#include <sys/statfs.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#endif
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <algorithm>
+// Get nano time includes
+#if defined(OS_LINUX) || defined(OS_FREEBSD)
+#elif defined(__MACH__)
+#include <Availability.h>
+#include <mach/clock.h>
+#include <mach/mach.h>
+#else
+#include <chrono>
+#endif
+#include <deque>
+#include <set>
+#include <vector>
+
+#include "env/io_posix.h"
+#include "logging/logging.h"
+#include "logging/posix_logger.h"
+#include "monitoring/iostats_context_imp.h"
+#include "monitoring/thread_status_updater.h"
+#include "port/port.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/compression_context_cache.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "util/thread_local.h"
+#include "util/threadpool_imp.h"
+
+#if !defined(TMPFS_MAGIC)
+#define TMPFS_MAGIC 0x01021994
+#endif
+#if !defined(XFS_SUPER_MAGIC)
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+#if !defined(EXT4_SUPER_MAGIC)
+#define EXT4_SUPER_MAGIC 0xEF53
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+inline mode_t GetDBFileMode(bool allow_non_owner_access) {
+ return allow_non_owner_access ? 0644 : 0600;
+}
+
+// list of pathnames that are locked
+static std::set<std::string> lockedFiles;
+static port::Mutex mutex_lockedFiles;
+
+static int LockOrUnlock(int fd, bool lock) {
+ errno = 0;
+ struct flock f;
+ memset(&f, 0, sizeof(f));
+ f.l_type = (lock ? F_WRLCK : F_UNLCK);
+ f.l_whence = SEEK_SET;
+ f.l_start = 0;
+ f.l_len = 0; // Lock/unlock entire file
+ int value = fcntl(fd, F_SETLK, &f);
+
+ return value;
+}
+
+class PosixFileLock : public FileLock {
+ public:
+ int fd_;
+ std::string filename;
+};
+
+int cloexec_flags(int flags, const EnvOptions* options) {
+ // If the system supports opening the file with cloexec enabled,
+ // do so, as this avoids a race condition if a db is opened around
+ // the same time that a child process is forked
+#ifdef O_CLOEXEC
+ if (options == nullptr || options->set_fd_cloexec) {
+ flags |= O_CLOEXEC;
+ }
+#endif
+ return flags;
+}
+
+class PosixFileSystem : public FileSystem {
+ public:
+ PosixFileSystem();
+
+ const char* Name() const override { return "Posix File System"; }
+
+ ~PosixFileSystem() override {}
+
+ void SetFD_CLOEXEC(int fd, const EnvOptions* options) {
+ if ((options == nullptr || options->set_fd_cloexec) && fd > 0) {
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+ }
+ }
+
+ IOStatus NewSequentialFile(const std::string& fname,
+ const FileOptions& options,
+ std::unique_ptr<FSSequentialFile>* result,
+ IODebugContext* /*dbg*/) override {
+ result->reset();
+ int fd = -1;
+ int flags = cloexec_flags(O_RDONLY, &options);
+ FILE* file = nullptr;
+
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+#ifdef ROCKSDB_LITE
+ return IOStatus::IOError(fname,
+ "Direct I/O not supported in RocksDB lite");
+#endif // !ROCKSDB_LITE
+#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS)
+ flags |= O_DIRECT;
+#endif
+ }
+
+ do {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_));
+ } while (fd < 0 && errno == EINTR);
+ if (fd < 0) {
+ return IOError("While opening a file for sequentially reading", fname,
+ errno);
+ }
+
+ SetFD_CLOEXEC(fd, &options);
+
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+#ifdef OS_MACOSX
+ if (fcntl(fd, F_NOCACHE, 1) == -1) {
+ close(fd);
+ return IOError("While fcntl NoCache", fname, errno);
+ }
+#endif
+ } else {
+ do {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ file = fdopen(fd, "r");
+ } while (file == nullptr && errno == EINTR);
+ if (file == nullptr) {
+ close(fd);
+ return IOError("While opening file for sequentially read", fname,
+ errno);
+ }
+ }
+ result->reset(new PosixSequentialFile(fname, file, fd, options));
+ return IOStatus::OK();
+ }
+
+ IOStatus NewRandomAccessFile(const std::string& fname,
+ const FileOptions& options,
+ std::unique_ptr<FSRandomAccessFile>* result,
+ IODebugContext* /*dbg*/) override {
+ result->reset();
+ IOStatus s;
+ int fd;
+ int flags = cloexec_flags(O_RDONLY, &options);
+
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+#ifdef ROCKSDB_LITE
+ return IOStatus::IOError(fname,
+ "Direct I/O not supported in RocksDB lite");
+#endif // !ROCKSDB_LITE
+#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS)
+ flags |= O_DIRECT;
+ TEST_SYNC_POINT_CALLBACK("NewRandomAccessFile:O_DIRECT", &flags);
+#endif
+ }
+
+ do {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_));
+ } while (fd < 0 && errno == EINTR);
+ if (fd < 0) {
+ return IOError("While open a file for random read", fname, errno);
+ }
+ SetFD_CLOEXEC(fd, &options);
+
+ if (options.use_mmap_reads && sizeof(void*) >= 8) {
+ // Use of mmap for random reads has been removed because it
+ // kills performance when storage is fast.
+ // Use mmap when virtual address-space is plentiful.
+ uint64_t size;
+ IOOptions opts;
+ s = GetFileSize(fname, opts, &size, nullptr);
+ if (s.ok()) {
+ void* base = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (base != MAP_FAILED) {
+ result->reset(
+ new PosixMmapReadableFile(fd, fname, base, size, options));
+ } else {
+ s = IOError("while mmap file for read", fname, errno);
+ close(fd);
+ }
+ }
+ } else {
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+#ifdef OS_MACOSX
+ if (fcntl(fd, F_NOCACHE, 1) == -1) {
+ close(fd);
+ return IOError("while fcntl NoCache", fname, errno);
+ }
+#endif
+ }
+ result->reset(new PosixRandomAccessFile(fname, fd, options
+#if defined(ROCKSDB_IOURING_PRESENT)
+ ,
+ thread_local_io_urings_.get()
+#endif
+ ));
+ }
+ return s;
+ }
+
+ virtual IOStatus OpenWritableFile(const std::string& fname,
+ const FileOptions& options,
+ bool reopen,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* /*dbg*/) {
+ result->reset();
+ IOStatus s;
+ int fd = -1;
+ int flags = (reopen) ? (O_CREAT | O_APPEND) : (O_CREAT | O_TRUNC);
+ // Direct IO mode with O_DIRECT flag or F_NOCAHCE (MAC OSX)
+ if (options.use_direct_writes && !options.use_mmap_writes) {
+ // Note: we should avoid O_APPEND here due to ta the following bug:
+ // POSIX requires that opening a file with the O_APPEND flag should
+ // have no affect on the location at which pwrite() writes data.
+ // However, on Linux, if a file is opened with O_APPEND, pwrite()
+ // appends data to the end of the file, regardless of the value of
+ // offset.
+ // More info here: https://linux.die.net/man/2/pwrite
+#ifdef ROCKSDB_LITE
+ return IOStatus::IOError(fname,
+ "Direct I/O not supported in RocksDB lite");
+#endif // ROCKSDB_LITE
+ flags |= O_WRONLY;
+#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS)
+ flags |= O_DIRECT;
+#endif
+ TEST_SYNC_POINT_CALLBACK("NewWritableFile:O_DIRECT", &flags);
+ } else if (options.use_mmap_writes) {
+ // non-direct I/O
+ flags |= O_RDWR;
+ } else {
+ flags |= O_WRONLY;
+ }
+
+ flags = cloexec_flags(flags, &options);
+
+ do {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_));
+ } while (fd < 0 && errno == EINTR);
+
+ if (fd < 0) {
+ s = IOError("While open a file for appending", fname, errno);
+ return s;
+ }
+ SetFD_CLOEXEC(fd, &options);
+
+ if (options.use_mmap_writes) {
+ if (!checkedDiskForMmap_) {
+ // this will be executed once in the program's lifetime.
+ // do not use mmapWrite on non ext-3/xfs/tmpfs systems.
+ if (!SupportsFastAllocate(fname)) {
+ forceMmapOff_ = true;
+ }
+ checkedDiskForMmap_ = true;
+ }
+ }
+ if (options.use_mmap_writes && !forceMmapOff_) {
+ result->reset(new PosixMmapFile(fname, fd, page_size_, options));
+ } else if (options.use_direct_writes && !options.use_mmap_writes) {
+#ifdef OS_MACOSX
+ if (fcntl(fd, F_NOCACHE, 1) == -1) {
+ close(fd);
+ s = IOError("While fcntl NoCache an opened file for appending", fname,
+ errno);
+ return s;
+ }
+#elif defined(OS_SOLARIS)
+ if (directio(fd, DIRECTIO_ON) == -1) {
+ if (errno != ENOTTY) { // ZFS filesystems don't support DIRECTIO_ON
+ close(fd);
+ s = IOError("While calling directio()", fname, errno);
+ return s;
+ }
+ }
+#endif
+ result->reset(new PosixWritableFile(fname, fd, options));
+ } else {
+ // disable mmap writes
+ EnvOptions no_mmap_writes_options = options;
+ no_mmap_writes_options.use_mmap_writes = false;
+ result->reset(new PosixWritableFile(fname, fd, no_mmap_writes_options));
+ }
+ return s;
+ }
+
+ IOStatus NewWritableFile(const std::string& fname, const FileOptions& options,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* dbg) override {
+ return OpenWritableFile(fname, options, false, result, dbg);
+ }
+
+ IOStatus ReopenWritableFile(const std::string& fname,
+ const FileOptions& options,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* dbg) override {
+ return OpenWritableFile(fname, options, true, result, dbg);
+ }
+
+ IOStatus ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ const FileOptions& options,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* /*dbg*/) override {
+ result->reset();
+ IOStatus s;
+ int fd = -1;
+
+ int flags = 0;
+ // Direct IO mode with O_DIRECT flag or F_NOCAHCE (MAC OSX)
+ if (options.use_direct_writes && !options.use_mmap_writes) {
+#ifdef ROCKSDB_LITE
+ return IOStatus::IOError(fname,
+ "Direct I/O not supported in RocksDB lite");
+#endif // !ROCKSDB_LITE
+ flags |= O_WRONLY;
+#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS)
+ flags |= O_DIRECT;
+#endif
+ TEST_SYNC_POINT_CALLBACK("NewWritableFile:O_DIRECT", &flags);
+ } else if (options.use_mmap_writes) {
+ // mmap needs O_RDWR mode
+ flags |= O_RDWR;
+ } else {
+ flags |= O_WRONLY;
+ }
+
+ flags = cloexec_flags(flags, &options);
+
+ do {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(old_fname.c_str(), flags,
+ GetDBFileMode(allow_non_owner_access_));
+ } while (fd < 0 && errno == EINTR);
+ if (fd < 0) {
+ s = IOError("while reopen file for write", fname, errno);
+ return s;
+ }
+
+ SetFD_CLOEXEC(fd, &options);
+ // rename into place
+ if (rename(old_fname.c_str(), fname.c_str()) != 0) {
+ s = IOError("while rename file to " + fname, old_fname, errno);
+ close(fd);
+ return s;
+ }
+
+ if (options.use_mmap_writes) {
+ if (!checkedDiskForMmap_) {
+ // this will be executed once in the program's lifetime.
+ // do not use mmapWrite on non ext-3/xfs/tmpfs systems.
+ if (!SupportsFastAllocate(fname)) {
+ forceMmapOff_ = true;
+ }
+ checkedDiskForMmap_ = true;
+ }
+ }
+ if (options.use_mmap_writes && !forceMmapOff_) {
+ result->reset(new PosixMmapFile(fname, fd, page_size_, options));
+ } else if (options.use_direct_writes && !options.use_mmap_writes) {
+#ifdef OS_MACOSX
+ if (fcntl(fd, F_NOCACHE, 1) == -1) {
+ close(fd);
+ s = IOError("while fcntl NoCache for reopened file for append", fname,
+ errno);
+ return s;
+ }
+#elif defined(OS_SOLARIS)
+ if (directio(fd, DIRECTIO_ON) == -1) {
+ if (errno != ENOTTY) { // ZFS filesystems don't support DIRECTIO_ON
+ close(fd);
+ s = IOError("while calling directio()", fname, errno);
+ return s;
+ }
+ }
+#endif
+ result->reset(new PosixWritableFile(fname, fd, options));
+ } else {
+ // disable mmap writes
+ FileOptions no_mmap_writes_options = options;
+ no_mmap_writes_options.use_mmap_writes = false;
+ result->reset(new PosixWritableFile(fname, fd, no_mmap_writes_options));
+ }
+ return s;
+ }
+
+ IOStatus NewRandomRWFile(const std::string& fname, const FileOptions& options,
+ std::unique_ptr<FSRandomRWFile>* result,
+ IODebugContext* /*dbg*/) override {
+ int fd = -1;
+ int flags = cloexec_flags(O_RDWR, &options);
+
+ while (fd < 0) {
+ IOSTATS_TIMER_GUARD(open_nanos);
+
+ fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_));
+ if (fd < 0) {
+ // Error while opening the file
+ if (errno == EINTR) {
+ continue;
+ }
+ return IOError("While open file for random read/write", fname, errno);
+ }
+ }
+
+ SetFD_CLOEXEC(fd, &options);
+ result->reset(new PosixRandomRWFile(fname, fd, options));
+ return IOStatus::OK();
+ }
+
+ IOStatus NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override {
+ int fd = -1;
+ IOStatus status;
+ int flags = cloexec_flags(O_RDWR, nullptr);
+
+ while (fd < 0) {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(fname.c_str(), flags, 0644);
+ if (fd < 0) {
+ // Error while opening the file
+ if (errno == EINTR) {
+ continue;
+ }
+ status =
+ IOError("While open file for raw mmap buffer access", fname, errno);
+ break;
+ }
+ }
+ uint64_t size;
+ if (status.ok()) {
+ IOOptions opts;
+ status = GetFileSize(fname, opts, &size, nullptr);
+ }
+ void* base = nullptr;
+ if (status.ok()) {
+ base = mmap(nullptr, static_cast<size_t>(size), PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (base == MAP_FAILED) {
+ status = IOError("while mmap file for read", fname, errno);
+ }
+ }
+ if (status.ok()) {
+ result->reset(
+ new PosixMemoryMappedFileBuffer(base, static_cast<size_t>(size)));
+ }
+ if (fd >= 0) {
+ // don't need to keep it open after mmap has been called
+ close(fd);
+ }
+ return status;
+ }
+
+ IOStatus NewDirectory(const std::string& name, const IOOptions& /*opts*/,
+ std::unique_ptr<FSDirectory>* result,
+ IODebugContext* /*dbg*/) override {
+ result->reset();
+ int fd;
+ int flags = cloexec_flags(0, nullptr);
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(name.c_str(), flags);
+ }
+ if (fd < 0) {
+ return IOError("While open directory", name, errno);
+ } else {
+ result->reset(new PosixDirectory(fd));
+ }
+ return IOStatus::OK();
+ }
+
+ IOStatus NewLogger(const std::string& /*fname*/, const IOOptions& /*opts*/,
+ std::shared_ptr<ROCKSDB_NAMESPACE::Logger>* /*ptr*/,
+ IODebugContext* /*dbg*/) override {
+ return IOStatus::NotSupported();
+ }
+
+ IOStatus FileExists(const std::string& fname, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ int result = access(fname.c_str(), F_OK);
+
+ if (result == 0) {
+ return IOStatus::OK();
+ }
+
+ int err = errno;
+ switch (err) {
+ case EACCES:
+ case ELOOP:
+ case ENAMETOOLONG:
+ case ENOENT:
+ case ENOTDIR:
+ return IOStatus::NotFound();
+ default:
+ assert(err == EIO || err == ENOMEM);
+ return IOStatus::IOError("Unexpected error(" + ToString(err) +
+ ") accessing file `" + fname + "' ");
+ }
+ }
+
+ IOStatus GetChildren(const std::string& dir, const IOOptions& /*opts*/,
+ std::vector<std::string>* result,
+ IODebugContext* /*dbg*/) override {
+ result->clear();
+ DIR* d = opendir(dir.c_str());
+ if (d == nullptr) {
+ switch (errno) {
+ case EACCES:
+ case ENOENT:
+ case ENOTDIR:
+ return IOStatus::NotFound();
+ default:
+ return IOError("While opendir", dir, errno);
+ }
+ }
+ struct dirent* entry;
+ while ((entry = readdir(d)) != nullptr) {
+ result->push_back(entry->d_name);
+ }
+ closedir(d);
+ return IOStatus::OK();
+ }
+
+ IOStatus DeleteFile(const std::string& fname, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (unlink(fname.c_str()) != 0) {
+ result = IOError("while unlink() file", fname, errno);
+ }
+ return result;
+ }
+
+ IOStatus CreateDir(const std::string& name, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (mkdir(name.c_str(), 0755) != 0) {
+ result = IOError("While mkdir", name, errno);
+ }
+ return result;
+ }
+
+ IOStatus CreateDirIfMissing(const std::string& name,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (mkdir(name.c_str(), 0755) != 0) {
+ if (errno != EEXIST) {
+ result = IOError("While mkdir if missing", name, errno);
+ } else if (!DirExists(name)) { // Check that name is actually a
+ // directory.
+ // Message is taken from mkdir
+ result =
+ IOStatus::IOError("`" + name + "' exists but is not a directory");
+ }
+ }
+ return result;
+ }
+
+ IOStatus DeleteDir(const std::string& name, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (rmdir(name.c_str()) != 0) {
+ result = IOError("file rmdir", name, errno);
+ }
+ return result;
+ }
+
+ IOStatus GetFileSize(const std::string& fname, const IOOptions& /*opts*/,
+ uint64_t* size, IODebugContext* /*dbg*/) override {
+ IOStatus s;
+ struct stat sbuf;
+ if (stat(fname.c_str(), &sbuf) != 0) {
+ *size = 0;
+ s = IOError("while stat a file for size", fname, errno);
+ } else {
+ *size = sbuf.st_size;
+ }
+ return s;
+ }
+
+ IOStatus GetFileModificationTime(const std::string& fname,
+ const IOOptions& /*opts*/,
+ uint64_t* file_mtime,
+ IODebugContext* /*dbg*/) override {
+ struct stat s;
+ if (stat(fname.c_str(), &s) != 0) {
+ return IOError("while stat a file for modification time", fname, errno);
+ }
+ *file_mtime = static_cast<uint64_t>(s.st_mtime);
+ return IOStatus::OK();
+ }
+
+ IOStatus RenameFile(const std::string& src, const std::string& target,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (rename(src.c_str(), target.c_str()) != 0) {
+ result = IOError("While renaming a file to " + target, src, errno);
+ }
+ return result;
+ }
+
+ IOStatus LinkFile(const std::string& src, const std::string& target,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ IOStatus result;
+ if (link(src.c_str(), target.c_str()) != 0) {
+ if (errno == EXDEV) {
+ return IOStatus::NotSupported("No cross FS links allowed");
+ }
+ result = IOError("while link file to " + target, src, errno);
+ }
+ return result;
+ }
+
+ IOStatus NumFileLinks(const std::string& fname, const IOOptions& /*opts*/,
+ uint64_t* count, IODebugContext* /*dbg*/) override {
+ struct stat s;
+ if (stat(fname.c_str(), &s) != 0) {
+ return IOError("while stat a file for num file links", fname, errno);
+ }
+ *count = static_cast<uint64_t>(s.st_nlink);
+ return IOStatus::OK();
+ }
+
+ IOStatus AreFilesSame(const std::string& first, const std::string& second,
+ const IOOptions& /*opts*/, bool* res,
+ IODebugContext* /*dbg*/) override {
+ struct stat statbuf[2];
+ if (stat(first.c_str(), &statbuf[0]) != 0) {
+ return IOError("stat file", first, errno);
+ }
+ if (stat(second.c_str(), &statbuf[1]) != 0) {
+ return IOError("stat file", second, errno);
+ }
+
+ if (major(statbuf[0].st_dev) != major(statbuf[1].st_dev) ||
+ minor(statbuf[0].st_dev) != minor(statbuf[1].st_dev) ||
+ statbuf[0].st_ino != statbuf[1].st_ino) {
+ *res = false;
+ } else {
+ *res = true;
+ }
+ return IOStatus::OK();
+ }
+
+ IOStatus LockFile(const std::string& fname, const IOOptions& /*opts*/,
+ FileLock** lock, IODebugContext* /*dbg*/) override {
+ *lock = nullptr;
+ IOStatus result;
+
+ mutex_lockedFiles.Lock();
+ // If it already exists in the lockedFiles set, then it is already locked,
+ // and fail this lock attempt. Otherwise, insert it into lockedFiles.
+ // This check is needed because fcntl() does not detect lock conflict
+ // if the fcntl is issued by the same thread that earlier acquired
+ // this lock.
+ // We must do this check *before* opening the file:
+ // Otherwise, we will open a new file descriptor. Locks are associated with
+ // a process, not a file descriptor and when *any* file descriptor is
+ // closed, all locks the process holds for that *file* are released
+ if (lockedFiles.insert(fname).second == false) {
+ mutex_lockedFiles.Unlock();
+ errno = ENOLCK;
+ return IOError("lock ", fname, errno);
+ }
+
+ int fd;
+ int flags = cloexec_flags(O_RDWR | O_CREAT, nullptr);
+
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ fd = open(fname.c_str(), flags, 0644);
+ }
+ if (fd < 0) {
+ result = IOError("while open a file for lock", fname, errno);
+ } else if (LockOrUnlock(fd, true) == -1) {
+ // if there is an error in locking, then remove the pathname from
+ // lockedfiles
+ lockedFiles.erase(fname);
+ result = IOError("While lock file", fname, errno);
+ close(fd);
+ } else {
+ SetFD_CLOEXEC(fd, nullptr);
+ PosixFileLock* my_lock = new PosixFileLock;
+ my_lock->fd_ = fd;
+ my_lock->filename = fname;
+ *lock = my_lock;
+ }
+
+ mutex_lockedFiles.Unlock();
+ return result;
+ }
+
+ IOStatus UnlockFile(FileLock* lock, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ PosixFileLock* my_lock = reinterpret_cast<PosixFileLock*>(lock);
+ IOStatus result;
+ mutex_lockedFiles.Lock();
+ // If we are unlocking, then verify that we had locked it earlier,
+ // it should already exist in lockedFiles. Remove it from lockedFiles.
+ if (lockedFiles.erase(my_lock->filename) != 1) {
+ errno = ENOLCK;
+ result = IOError("unlock", my_lock->filename, errno);
+ } else if (LockOrUnlock(my_lock->fd_, false) == -1) {
+ result = IOError("unlock", my_lock->filename, errno);
+ }
+ close(my_lock->fd_);
+ delete my_lock;
+ mutex_lockedFiles.Unlock();
+ return result;
+ }
+
+ IOStatus GetAbsolutePath(const std::string& db_path,
+ const IOOptions& /*opts*/, std::string* output_path,
+ IODebugContext* /*dbg*/) override {
+ if (!db_path.empty() && db_path[0] == '/') {
+ *output_path = db_path;
+ return IOStatus::OK();
+ }
+
+ char the_path[256];
+ char* ret = getcwd(the_path, 256);
+ if (ret == nullptr) {
+ return IOStatus::IOError(strerror(errno));
+ }
+
+ *output_path = ret;
+ return IOStatus::OK();
+ }
+
+ IOStatus GetTestDirectory(const IOOptions& /*opts*/, std::string* result,
+ IODebugContext* /*dbg*/) override {
+ const char* env = getenv("TEST_TMPDIR");
+ if (env && env[0] != '\0') {
+ *result = env;
+ } else {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "/tmp/rocksdbtest-%d", int(geteuid()));
+ *result = buf;
+ }
+ // Directory may already exist
+ {
+ IOOptions opts;
+ CreateDir(*result, opts, nullptr);
+ }
+ return IOStatus::OK();
+ }
+
+ IOStatus GetFreeSpace(const std::string& fname, const IOOptions& /*opts*/,
+ uint64_t* free_space,
+ IODebugContext* /*dbg*/) override {
+ struct statvfs sbuf;
+
+ if (statvfs(fname.c_str(), &sbuf) < 0) {
+ return IOError("While doing statvfs", fname, errno);
+ }
+
+ *free_space = ((uint64_t)sbuf.f_bsize * sbuf.f_bfree);
+ return IOStatus::OK();
+ }
+
+ FileOptions OptimizeForLogWrite(const FileOptions& file_options,
+ const DBOptions& db_options) const override {
+ FileOptions optimized = file_options;
+ optimized.use_mmap_writes = false;
+ optimized.use_direct_writes = false;
+ optimized.bytes_per_sync = db_options.wal_bytes_per_sync;
+ // TODO(icanadi) it's faster if fallocate_with_keep_size is false, but it
+ // breaks TransactionLogIteratorStallAtLastRecord unit test. Fix the unit
+ // test and make this false
+ optimized.fallocate_with_keep_size = true;
+ optimized.writable_file_max_buffer_size =
+ db_options.writable_file_max_buffer_size;
+ return optimized;
+ }
+
+ FileOptions OptimizeForManifestWrite(
+ const FileOptions& file_options) const override {
+ FileOptions optimized = file_options;
+ optimized.use_mmap_writes = false;
+ optimized.use_direct_writes = false;
+ optimized.fallocate_with_keep_size = true;
+ return optimized;
+ }
+
+ private:
+ bool checkedDiskForMmap_;
+ bool forceMmapOff_; // do we override Env options?
+
+ // Returns true iff the named directory exists and is a directory.
+ virtual bool DirExists(const std::string& dname) {
+ struct stat statbuf;
+ if (stat(dname.c_str(), &statbuf) == 0) {
+ return S_ISDIR(statbuf.st_mode);
+ }
+ return false; // stat() failed return false
+ }
+
+ bool SupportsFastAllocate(const std::string& path) {
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ struct statfs s;
+ if (statfs(path.c_str(), &s)) {
+ return false;
+ }
+ switch (s.f_type) {
+ case EXT4_SUPER_MAGIC:
+ return true;
+ case XFS_SUPER_MAGIC:
+ return true;
+ case TMPFS_MAGIC:
+ return true;
+ default:
+ return false;
+ }
+#else
+ (void)path;
+ return false;
+#endif
+ }
+
+#if defined(ROCKSDB_IOURING_PRESENT)
+ // io_uring instance
+ std::unique_ptr<ThreadLocalPtr> thread_local_io_urings_;
+#endif
+
+ size_t page_size_;
+
+ // If true, allow non owner read access for db files. Otherwise, non-owner
+ // has no access to db files.
+ bool allow_non_owner_access_;
+};
+
+PosixFileSystem::PosixFileSystem()
+ : checkedDiskForMmap_(false),
+ forceMmapOff_(false),
+ page_size_(getpagesize()),
+ allow_non_owner_access_(true) {
+#if defined(ROCKSDB_IOURING_PRESENT)
+ // Test whether IOUring is supported, and if it does, create a managing
+ // object for thread local point so that in the future thread-local
+ // io_uring can be created.
+ struct io_uring* new_io_uring = CreateIOUring();
+ if (new_io_uring != nullptr) {
+ thread_local_io_urings_.reset(new ThreadLocalPtr(DeleteIOUring));
+ delete new_io_uring;
+ }
+#endif
+}
+
+} // namespace
+
+//
+// Default Posix FileSystem
+//
+std::shared_ptr<FileSystem> FileSystem::Default() {
+ static PosixFileSystem default_fs;
+ static std::shared_ptr<PosixFileSystem> default_fs_ptr(
+ &default_fs, [](PosixFileSystem*) {});
+ return default_fs_ptr;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/io_posix.cc b/storage/rocksdb/rocksdb/env/io_posix.cc
new file mode 100644
index 00000000..a2bbab38
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/io_posix.cc
@@ -0,0 +1,1352 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LIB_IO_POSIX
+#include "env/io_posix.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <algorithm>
+#if defined(OS_LINUX)
+#include <linux/fs.h>
+#ifndef FALLOC_FL_KEEP_SIZE
+#include <linux/falloc.h>
+#endif
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef OS_LINUX
+#include <sys/statfs.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#endif
+#include "monitoring/iostats_context_imp.h"
+#include "port/port.h"
+#include "rocksdb/slice.h"
+#include "test_util/sync_point.h"
+#include "util/autovector.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+#if defined(OS_LINUX) && !defined(F_SET_RW_HINT)
+#define F_LINUX_SPECIFIC_BASE 1024
+#define F_SET_RW_HINT (F_LINUX_SPECIFIC_BASE + 12)
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+// A wrapper for fadvise, if the platform doesn't support fadvise,
+// it will simply return 0.
+int Fadvise(int fd, off_t offset, size_t len, int advice) {
+#ifdef OS_LINUX
+ return posix_fadvise(fd, offset, len, advice);
+#else
+ (void)fd;
+ (void)offset;
+ (void)len;
+ (void)advice;
+ return 0; // simply do nothing.
+#endif
+}
+
+namespace {
+
+// On MacOS (and probably *BSD), the posix write and pwrite calls do not support
+// buffers larger than 2^31-1 bytes. These two wrappers fix this issue by
+// cutting the buffer in 1GB chunks. We use this chunk size to be sure to keep
+// the writes aligned.
+
+bool PosixWrite(int fd, const char* buf, size_t nbyte) {
+ const size_t kLimit1Gb = 1UL << 30;
+
+ const char* src = buf;
+ size_t left = nbyte;
+
+ while (left != 0) {
+ size_t bytes_to_write = std::min(left, kLimit1Gb);
+
+ ssize_t done = write(fd, src, bytes_to_write);
+ if (done < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return false;
+ }
+ left -= done;
+ src += done;
+ }
+ return true;
+}
+
+bool PosixPositionedWrite(int fd, const char* buf, size_t nbyte, off_t offset) {
+ const size_t kLimit1Gb = 1UL << 30;
+
+ const char* src = buf;
+ size_t left = nbyte;
+
+ while (left != 0) {
+ size_t bytes_to_write = std::min(left, kLimit1Gb);
+
+ ssize_t done = pwrite(fd, src, bytes_to_write, offset);
+ if (done < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return false;
+ }
+ left -= done;
+ offset += done;
+ src += done;
+ }
+
+ return true;
+}
+
+size_t GetLogicalBufferSize(int __attribute__((__unused__)) fd) {
+#ifdef OS_LINUX
+ struct stat buf;
+ int result = fstat(fd, &buf);
+ if (result == -1) {
+ return kDefaultPageSize;
+ }
+ if (major(buf.st_dev) == 0) {
+ // Unnamed devices (e.g. non-device mounts), reserved as null device number.
+ // These don't have an entry in /sys/dev/block/. Return a sensible default.
+ return kDefaultPageSize;
+ }
+
+ // Reading queue/logical_block_size does not require special permissions.
+ const int kBufferSize = 100;
+ char path[kBufferSize];
+ char real_path[PATH_MAX + 1];
+ snprintf(path, kBufferSize, "/sys/dev/block/%u:%u", major(buf.st_dev),
+ minor(buf.st_dev));
+ if (realpath(path, real_path) == nullptr) {
+ return kDefaultPageSize;
+ }
+ std::string device_dir(real_path);
+ if (!device_dir.empty() && device_dir.back() == '/') {
+ device_dir.pop_back();
+ }
+ // NOTE: sda3 and nvme0n1p1 do not have a `queue/` subdir, only the parent sda
+ // and nvme0n1 have it.
+ // $ ls -al '/sys/dev/block/8:3'
+ // lrwxrwxrwx. 1 root root 0 Jun 26 01:38 /sys/dev/block/8:3 ->
+ // ../../block/sda/sda3
+ // $ ls -al '/sys/dev/block/259:4'
+ // lrwxrwxrwx 1 root root 0 Jan 31 16:04 /sys/dev/block/259:4 ->
+ // ../../devices/pci0000:17/0000:17:00.0/0000:18:00.0/nvme/nvme0/nvme0n1/nvme0n1p1
+ size_t parent_end = device_dir.rfind('/', device_dir.length() - 1);
+ if (parent_end == std::string::npos) {
+ return kDefaultPageSize;
+ }
+ size_t parent_begin = device_dir.rfind('/', parent_end - 1);
+ if (parent_begin == std::string::npos) {
+ return kDefaultPageSize;
+ }
+ std::string parent =
+ device_dir.substr(parent_begin + 1, parent_end - parent_begin - 1);
+ std::string child = device_dir.substr(parent_end + 1, std::string::npos);
+ if (parent != "block" &&
+ (child.compare(0, 4, "nvme") || child.find('p') != std::string::npos)) {
+ device_dir = device_dir.substr(0, parent_end);
+ }
+ std::string fname = device_dir + "/queue/logical_block_size";
+ FILE* fp;
+ size_t size = 0;
+ fp = fopen(fname.c_str(), "r");
+ if (fp != nullptr) {
+ char* line = nullptr;
+ size_t len = 0;
+ if (getline(&line, &len, fp) != -1) {
+ sscanf(line, "%zu", &size);
+ }
+ free(line);
+ fclose(fp);
+ }
+ if (size != 0 && (size & (size - 1)) == 0) {
+ return size;
+ }
+#endif
+ return kDefaultPageSize;
+}
+
+#ifdef ROCKSDB_RANGESYNC_PRESENT
+
+#if !defined(ZFS_SUPER_MAGIC)
+// The magic number for ZFS was not exposed until recently. It should be fixed
+// forever so we can just copy the magic number here.
+#define ZFS_SUPER_MAGIC 0x2fc12fc1
+#endif
+
+bool IsSyncFileRangeSupported(int fd) {
+ // The approach taken in this function is to build a blacklist of cases where
+ // we know `sync_file_range` definitely will not work properly despite passing
+ // the compile-time check (`ROCKSDB_RANGESYNC_PRESENT`). If we are unsure, or
+ // if any of the checks fail in unexpected ways, we allow `sync_file_range` to
+ // be used. This way should minimize risk of impacting existing use cases.
+ struct statfs buf;
+ int ret = fstatfs(fd, &buf);
+ assert(ret == 0);
+ if (ret == 0 && buf.f_type == ZFS_SUPER_MAGIC) {
+ // Testing on ZFS showed the writeback did not happen asynchronously when
+ // `sync_file_range` was called, even though it returned success. Avoid it
+ // and use `fdatasync` instead to preserve the contract of `bytes_per_sync`,
+ // even though this'll incur extra I/O for metadata.
+ return false;
+ }
+
+ ret = sync_file_range(fd, 0 /* offset */, 0 /* nbytes */, 0 /* flags */);
+ assert(!(ret == -1 && errno != ENOSYS));
+ if (ret == -1 && errno == ENOSYS) {
+ // `sync_file_range` is not implemented on all platforms even if
+ // compile-time checks pass and a supported filesystem is in-use. For
+ // example, using ext4 on WSL (Windows Subsystem for Linux),
+ // `sync_file_range()` returns `ENOSYS`
+ // ("Function not implemented").
+ return false;
+ }
+ // None of the cases on the blacklist matched, so allow `sync_file_range` use.
+ return true;
+}
+
+#undef ZFS_SUPER_MAGIC
+
+#endif // ROCKSDB_RANGESYNC_PRESENT
+
+} // anonymous namespace
+
+/*
+ * DirectIOHelper
+ */
+#ifndef NDEBUG
+namespace {
+
+bool IsSectorAligned(const size_t off, size_t sector_size) {
+ return off % sector_size == 0;
+}
+
+bool IsSectorAligned(const void* ptr, size_t sector_size) {
+ return uintptr_t(ptr) % sector_size == 0;
+}
+
+} // namespace
+#endif
+
+/*
+ * PosixSequentialFile
+ */
+PosixSequentialFile::PosixSequentialFile(const std::string& fname, FILE* file,
+ int fd, const EnvOptions& options)
+ : filename_(fname),
+ file_(file),
+ fd_(fd),
+ use_direct_io_(options.use_direct_reads),
+ logical_sector_size_(GetLogicalBufferSize(fd_)) {
+ assert(!options.use_direct_reads || !options.use_mmap_reads);
+}
+
+PosixSequentialFile::~PosixSequentialFile() {
+ if (!use_direct_io()) {
+ assert(file_);
+ fclose(file_);
+ } else {
+ assert(fd_);
+ close(fd_);
+ }
+}
+
+IOStatus PosixSequentialFile::Read(size_t n, const IOOptions& /*opts*/,
+ Slice* result, char* scratch,
+ IODebugContext* /*dbg*/) {
+ assert(result != nullptr && !use_direct_io());
+ IOStatus s;
+ size_t r = 0;
+ do {
+ r = fread_unlocked(scratch, 1, n, file_);
+ } while (r == 0 && ferror(file_) && errno == EINTR);
+ *result = Slice(scratch, r);
+ if (r < n) {
+ if (feof(file_)) {
+ // We leave status as ok if we hit the end of the file
+ // We also clear the error so that the reads can continue
+ // if a new data is written to the file
+ clearerr(file_);
+ } else {
+ // A partial read with an error: return a non-ok status
+ s = IOError("While reading file sequentially", filename_, errno);
+ }
+ }
+ return s;
+}
+
+IOStatus PosixSequentialFile::PositionedRead(uint64_t offset, size_t n,
+ const IOOptions& /*opts*/,
+ Slice* result, char* scratch,
+ IODebugContext* /*dbg*/) {
+ assert(use_direct_io());
+ assert(IsSectorAligned(offset, GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(n, GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(scratch, GetRequiredBufferAlignment()));
+
+ IOStatus s;
+ ssize_t r = -1;
+ size_t left = n;
+ char* ptr = scratch;
+ while (left > 0) {
+ r = pread(fd_, ptr, left, static_cast<off_t>(offset));
+ if (r <= 0) {
+ if (r == -1 && errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+ ptr += r;
+ offset += r;
+ left -= r;
+ if (r % static_cast<ssize_t>(GetRequiredBufferAlignment()) != 0) {
+ // Bytes reads don't fill sectors. Should only happen at the end
+ // of the file.
+ break;
+ }
+ }
+ if (r < 0) {
+ // An error: return a non-ok status
+ s = IOError(
+ "While pread " + ToString(n) + " bytes from offset " + ToString(offset),
+ filename_, errno);
+ }
+ *result = Slice(scratch, (r < 0) ? 0 : n - left);
+ return s;
+}
+
+IOStatus PosixSequentialFile::Skip(uint64_t n) {
+ if (fseek(file_, static_cast<long int>(n), SEEK_CUR)) {
+ return IOError("While fseek to skip " + ToString(n) + " bytes", filename_,
+ errno);
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixSequentialFile::InvalidateCache(size_t offset, size_t length) {
+#ifndef OS_LINUX
+ (void)offset;
+ (void)length;
+ return IOStatus::OK();
+#else
+ if (!use_direct_io()) {
+ // free OS pages
+ int ret = Fadvise(fd_, offset, length, POSIX_FADV_DONTNEED);
+ if (ret != 0) {
+ return IOError("While fadvise NotNeeded offset " + ToString(offset) +
+ " len " + ToString(length),
+ filename_, errno);
+ }
+ }
+ return IOStatus::OK();
+#endif
+}
+
+/*
+ * PosixRandomAccessFile
+ */
+#if defined(OS_LINUX)
+size_t PosixHelper::GetUniqueIdFromFile(int fd, char* id, size_t max_size) {
+ if (max_size < kMaxVarint64Length * 3) {
+ return 0;
+ }
+
+ struct stat buf;
+ int result = fstat(fd, &buf);
+ if (result == -1) {
+ return 0;
+ }
+
+ long version = 0;
+ result = ioctl(fd, FS_IOC_GETVERSION, &version);
+ TEST_SYNC_POINT_CALLBACK("GetUniqueIdFromFile:FS_IOC_GETVERSION", &result);
+ if (result == -1) {
+ return 0;
+ }
+ uint64_t uversion = (uint64_t)version;
+
+ char* rid = id;
+ rid = EncodeVarint64(rid, buf.st_dev);
+ rid = EncodeVarint64(rid, buf.st_ino);
+ rid = EncodeVarint64(rid, uversion);
+ assert(rid >= id);
+ return static_cast<size_t>(rid - id);
+}
+#endif
+
+#if defined(OS_MACOSX) || defined(OS_AIX)
+size_t PosixHelper::GetUniqueIdFromFile(int fd, char* id, size_t max_size) {
+ if (max_size < kMaxVarint64Length * 3) {
+ return 0;
+ }
+
+ struct stat buf;
+ int result = fstat(fd, &buf);
+ if (result == -1) {
+ return 0;
+ }
+
+ char* rid = id;
+ rid = EncodeVarint64(rid, buf.st_dev);
+ rid = EncodeVarint64(rid, buf.st_ino);
+ rid = EncodeVarint64(rid, buf.st_gen);
+ assert(rid >= id);
+ return static_cast<size_t>(rid - id);
+}
+#endif
+/*
+ * PosixRandomAccessFile
+ *
+ * pread() based random-access
+ */
+PosixRandomAccessFile::PosixRandomAccessFile(
+ const std::string& fname, int fd, const EnvOptions& options
+#if defined(ROCKSDB_IOURING_PRESENT)
+ ,
+ ThreadLocalPtr* thread_local_io_urings
+#endif
+ )
+ : filename_(fname),
+ fd_(fd),
+ use_direct_io_(options.use_direct_reads),
+ logical_sector_size_(GetLogicalBufferSize(fd_))
+#if defined(ROCKSDB_IOURING_PRESENT)
+ ,
+ thread_local_io_urings_(thread_local_io_urings)
+#endif
+{
+ assert(!options.use_direct_reads || !options.use_mmap_reads);
+ assert(!options.use_mmap_reads || sizeof(void*) < 8);
+}
+
+PosixRandomAccessFile::~PosixRandomAccessFile() { close(fd_); }
+
+IOStatus PosixRandomAccessFile::Read(uint64_t offset, size_t n,
+ const IOOptions& /*opts*/, Slice* result,
+ char* scratch,
+ IODebugContext* /*dbg*/) const {
+ if (use_direct_io()) {
+ assert(IsSectorAligned(offset, GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(n, GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(scratch, GetRequiredBufferAlignment()));
+ }
+ IOStatus s;
+ ssize_t r = -1;
+ size_t left = n;
+ char* ptr = scratch;
+ while (left > 0) {
+ r = pread(fd_, ptr, left, static_cast<off_t>(offset));
+ if (r <= 0) {
+ if (r == -1 && errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+ ptr += r;
+ offset += r;
+ left -= r;
+ if (use_direct_io() &&
+ r % static_cast<ssize_t>(GetRequiredBufferAlignment()) != 0) {
+ // Bytes reads don't fill sectors. Should only happen at the end
+ // of the file.
+ break;
+ }
+ }
+ if (r < 0) {
+ // An error: return a non-ok status
+ s = IOError(
+ "While pread offset " + ToString(offset) + " len " + ToString(n),
+ filename_, errno);
+ }
+ *result = Slice(scratch, (r < 0) ? 0 : n - left);
+ return s;
+}
+
+IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs,
+ size_t num_reqs,
+ const IOOptions& options,
+ IODebugContext* dbg) {
+#if defined(ROCKSDB_IOURING_PRESENT)
+ struct io_uring* iu = nullptr;
+ if (thread_local_io_urings_) {
+ iu = static_cast<struct io_uring*>(thread_local_io_urings_->Get());
+ if (iu == nullptr) {
+ iu = CreateIOUring();
+ if (iu != nullptr) {
+ thread_local_io_urings_->Reset(iu);
+ }
+ }
+ }
+
+ // Init failed, platform doesn't support io_uring. Fall back to
+ // serialized reads
+ if (iu == nullptr) {
+ return FSRandomAccessFile::MultiRead(reqs, num_reqs, options, dbg);
+ }
+
+ struct WrappedReadRequest {
+ FSReadRequest* req;
+ struct iovec iov;
+ size_t finished_len;
+ explicit WrappedReadRequest(FSReadRequest* r) : req(r), finished_len(0) {}
+ };
+
+ autovector<WrappedReadRequest, 32> req_wraps;
+ autovector<WrappedReadRequest*, 4> incomplete_rq_list;
+
+ for (size_t i = 0; i < num_reqs; i++) {
+ req_wraps.emplace_back(&reqs[i]);
+ }
+
+ size_t reqs_off = 0;
+ while (num_reqs > reqs_off || !incomplete_rq_list.empty()) {
+ size_t this_reqs = (num_reqs - reqs_off) + incomplete_rq_list.size();
+
+ // If requests exceed depth, split it into batches
+ if (this_reqs > kIoUringDepth) this_reqs = kIoUringDepth;
+
+ assert(incomplete_rq_list.size() <= this_reqs);
+ for (size_t i = 0; i < this_reqs; i++) {
+ WrappedReadRequest* rep_to_submit;
+ if (i < incomplete_rq_list.size()) {
+ rep_to_submit = incomplete_rq_list[i];
+ } else {
+ rep_to_submit = &req_wraps[reqs_off++];
+ }
+ assert(rep_to_submit->req->len > rep_to_submit->finished_len);
+ rep_to_submit->iov.iov_base =
+ rep_to_submit->req->scratch + rep_to_submit->finished_len;
+ rep_to_submit->iov.iov_len =
+ rep_to_submit->req->len - rep_to_submit->finished_len;
+
+ struct io_uring_sqe* sqe;
+ sqe = io_uring_get_sqe(iu);
+ io_uring_prep_readv(
+ sqe, fd_, &rep_to_submit->iov, 1,
+ rep_to_submit->req->offset + rep_to_submit->finished_len);
+ io_uring_sqe_set_data(sqe, rep_to_submit);
+ }
+ incomplete_rq_list.clear();
+
+ ssize_t ret =
+ io_uring_submit_and_wait(iu, static_cast<unsigned int>(this_reqs));
+ if (static_cast<size_t>(ret) != this_reqs) {
+ fprintf(stderr, "ret = %ld this_reqs: %ld\n", (long)ret, (long)this_reqs);
+ }
+ assert(static_cast<size_t>(ret) == this_reqs);
+
+ for (size_t i = 0; i < this_reqs; i++) {
+ struct io_uring_cqe* cqe;
+ WrappedReadRequest* req_wrap;
+
+ // We could use the peek variant here, but this seems safer in terms
+ // of our initial wait not reaping all completions
+ ret = io_uring_wait_cqe(iu, &cqe);
+ assert(!ret);
+
+ req_wrap = static_cast<WrappedReadRequest*>(io_uring_cqe_get_data(cqe));
+ FSReadRequest* req = req_wrap->req;
+ if (cqe->res < 0) {
+ req->result = Slice(req->scratch, 0);
+ req->status = IOError("Req failed", filename_, cqe->res);
+ } else {
+ size_t bytes_read = static_cast<size_t>(cqe->res);
+ TEST_SYNC_POINT_CALLBACK(
+ "PosixRandomAccessFile::MultiRead:io_uring_result", &bytes_read);
+ if (bytes_read == req_wrap->iov.iov_len) {
+ req->result = Slice(req->scratch, req->len);
+ req->status = IOStatus::OK();
+ } else if (bytes_read == 0) {
+ // cqe->res == 0 can means EOF, or can mean partial results. See
+ // comment
+ // https://github.com/facebook/rocksdb/pull/6441#issuecomment-589843435
+ // Fall back to pread in this case.
+ Slice tmp_slice;
+ req->status =
+ Read(req->offset + req_wrap->finished_len,
+ req->len - req_wrap->finished_len, options, &tmp_slice,
+ req->scratch + req_wrap->finished_len, dbg);
+ req->result =
+ Slice(req->scratch, req_wrap->finished_len + tmp_slice.size());
+ } else if (bytes_read < req_wrap->iov.iov_len) {
+ assert(bytes_read > 0);
+ assert(bytes_read + req_wrap->finished_len < req->len);
+ req_wrap->finished_len += bytes_read;
+ incomplete_rq_list.push_back(req_wrap);
+ } else {
+ req->result = Slice(req->scratch, 0);
+ req->status = IOError("Req returned more bytes than requested",
+ filename_, cqe->res);
+ }
+ }
+ io_uring_cqe_seen(iu, cqe);
+ }
+ }
+ return IOStatus::OK();
+#else
+ return FSRandomAccessFile::MultiRead(reqs, num_reqs, options, dbg);
+#endif
+}
+
+IOStatus PosixRandomAccessFile::Prefetch(uint64_t offset, size_t n,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ IOStatus s;
+ if (!use_direct_io()) {
+ ssize_t r = 0;
+#ifdef OS_LINUX
+ r = readahead(fd_, offset, n);
+#endif
+#ifdef OS_MACOSX
+ radvisory advice;
+ advice.ra_offset = static_cast<off_t>(offset);
+ advice.ra_count = static_cast<int>(n);
+ r = fcntl(fd_, F_RDADVISE, &advice);
+#endif
+ if (r == -1) {
+ s = IOError("While prefetching offset " + ToString(offset) + " len " +
+ ToString(n),
+ filename_, errno);
+ }
+ }
+ return s;
+}
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_AIX)
+size_t PosixRandomAccessFile::GetUniqueId(char* id, size_t max_size) const {
+ return PosixHelper::GetUniqueIdFromFile(fd_, id, max_size);
+}
+#endif
+
+void PosixRandomAccessFile::Hint(AccessPattern pattern) {
+ if (use_direct_io()) {
+ return;
+ }
+ switch (pattern) {
+ case kNormal:
+ Fadvise(fd_, 0, 0, POSIX_FADV_NORMAL);
+ break;
+ case kRandom:
+ Fadvise(fd_, 0, 0, POSIX_FADV_RANDOM);
+ break;
+ case kSequential:
+ Fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL);
+ break;
+ case kWillNeed:
+ Fadvise(fd_, 0, 0, POSIX_FADV_WILLNEED);
+ break;
+ case kWontNeed:
+ Fadvise(fd_, 0, 0, POSIX_FADV_DONTNEED);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+IOStatus PosixRandomAccessFile::InvalidateCache(size_t offset, size_t length) {
+ if (use_direct_io()) {
+ return IOStatus::OK();
+ }
+#ifndef OS_LINUX
+ (void)offset;
+ (void)length;
+ return IOStatus::OK();
+#else
+ // free OS pages
+ int ret = Fadvise(fd_, offset, length, POSIX_FADV_DONTNEED);
+ if (ret == 0) {
+ return IOStatus::OK();
+ }
+ return IOError("While fadvise NotNeeded offset " + ToString(offset) +
+ " len " + ToString(length),
+ filename_, errno);
+#endif
+}
+
+/*
+ * PosixMmapReadableFile
+ *
+ * mmap() based random-access
+ */
+// base[0,length-1] contains the mmapped contents of the file.
+PosixMmapReadableFile::PosixMmapReadableFile(const int fd,
+ const std::string& fname,
+ void* base, size_t length,
+ const EnvOptions& options)
+ : fd_(fd), filename_(fname), mmapped_region_(base), length_(length) {
+#ifdef NDEBUG
+ (void)options;
+#endif
+ fd_ = fd_ + 0; // suppress the warning for used variables
+ assert(options.use_mmap_reads);
+ assert(!options.use_direct_reads);
+}
+
+PosixMmapReadableFile::~PosixMmapReadableFile() {
+ int ret = munmap(mmapped_region_, length_);
+ if (ret != 0) {
+ fprintf(stdout, "failed to munmap %p length %" ROCKSDB_PRIszt " \n",
+ mmapped_region_, length_);
+ }
+ close(fd_);
+}
+
+IOStatus PosixMmapReadableFile::Read(uint64_t offset, size_t n,
+ const IOOptions& /*opts*/, Slice* result,
+ char* /*scratch*/,
+ IODebugContext* /*dbg*/) const {
+ IOStatus s;
+ if (offset > length_) {
+ *result = Slice();
+ return IOError("While mmap read offset " + ToString(offset) +
+ " larger than file length " + ToString(length_),
+ filename_, EINVAL);
+ } else if (offset + n > length_) {
+ n = static_cast<size_t>(length_ - offset);
+ }
+ *result = Slice(reinterpret_cast<char*>(mmapped_region_) + offset, n);
+ return s;
+}
+
+IOStatus PosixMmapReadableFile::InvalidateCache(size_t offset, size_t length) {
+#ifndef OS_LINUX
+ (void)offset;
+ (void)length;
+ return IOStatus::OK();
+#else
+ // free OS pages
+ int ret = Fadvise(fd_, offset, length, POSIX_FADV_DONTNEED);
+ if (ret == 0) {
+ return IOStatus::OK();
+ }
+ return IOError("While fadvise not needed. Offset " + ToString(offset) +
+ " len" + ToString(length),
+ filename_, errno);
+#endif
+}
+
+/*
+ * PosixMmapFile
+ *
+ * We preallocate up to an extra megabyte and use memcpy to append new
+ * data to the file. This is safe since we either properly close the
+ * file before reading from it, or for log files, the reading code
+ * knows enough to skip zero suffixes.
+ */
+IOStatus PosixMmapFile::UnmapCurrentRegion() {
+ TEST_KILL_RANDOM("PosixMmapFile::UnmapCurrentRegion:0", rocksdb_kill_odds);
+ if (base_ != nullptr) {
+ int munmap_status = munmap(base_, limit_ - base_);
+ if (munmap_status != 0) {
+ return IOError("While munmap", filename_, munmap_status);
+ }
+ file_offset_ += limit_ - base_;
+ base_ = nullptr;
+ limit_ = nullptr;
+ last_sync_ = nullptr;
+ dst_ = nullptr;
+
+ // Increase the amount we map the next time, but capped at 1MB
+ if (map_size_ < (1 << 20)) {
+ map_size_ *= 2;
+ }
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixMmapFile::MapNewRegion() {
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ assert(base_ == nullptr);
+ TEST_KILL_RANDOM("PosixMmapFile::UnmapCurrentRegion:0", rocksdb_kill_odds);
+ // we can't fallocate with FALLOC_FL_KEEP_SIZE here
+ if (allow_fallocate_) {
+ IOSTATS_TIMER_GUARD(allocate_nanos);
+ int alloc_status = fallocate(fd_, 0, file_offset_, map_size_);
+ if (alloc_status != 0) {
+ // fallback to posix_fallocate
+ alloc_status = posix_fallocate(fd_, file_offset_, map_size_);
+ }
+ if (alloc_status != 0) {
+ return IOStatus::IOError("Error allocating space to file : " + filename_ +
+ "Error : " + strerror(alloc_status));
+ }
+ }
+
+ TEST_KILL_RANDOM("PosixMmapFile::Append:1", rocksdb_kill_odds);
+ void* ptr = mmap(nullptr, map_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_,
+ file_offset_);
+ if (ptr == MAP_FAILED) {
+ return IOStatus::IOError("MMap failed on " + filename_);
+ }
+ TEST_KILL_RANDOM("PosixMmapFile::Append:2", rocksdb_kill_odds);
+
+ base_ = reinterpret_cast<char*>(ptr);
+ limit_ = base_ + map_size_;
+ dst_ = base_;
+ last_sync_ = base_;
+ return IOStatus::OK();
+#else
+ return IOStatus::NotSupported("This platform doesn't support fallocate()");
+#endif
+}
+
+IOStatus PosixMmapFile::Msync() {
+ if (dst_ == last_sync_) {
+ return IOStatus::OK();
+ }
+ // Find the beginnings of the pages that contain the first and last
+ // bytes to be synced.
+ size_t p1 = TruncateToPageBoundary(last_sync_ - base_);
+ size_t p2 = TruncateToPageBoundary(dst_ - base_ - 1);
+ last_sync_ = dst_;
+ TEST_KILL_RANDOM("PosixMmapFile::Msync:0", rocksdb_kill_odds);
+ if (msync(base_ + p1, p2 - p1 + page_size_, MS_SYNC) < 0) {
+ return IOError("While msync", filename_, errno);
+ }
+ return IOStatus::OK();
+}
+
+PosixMmapFile::PosixMmapFile(const std::string& fname, int fd, size_t page_size,
+ const EnvOptions& options)
+ : filename_(fname),
+ fd_(fd),
+ page_size_(page_size),
+ map_size_(Roundup(65536, page_size)),
+ base_(nullptr),
+ limit_(nullptr),
+ dst_(nullptr),
+ last_sync_(nullptr),
+ file_offset_(0) {
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ allow_fallocate_ = options.allow_fallocate;
+ fallocate_with_keep_size_ = options.fallocate_with_keep_size;
+#else
+ (void)options;
+#endif
+ assert((page_size & (page_size - 1)) == 0);
+ assert(options.use_mmap_writes);
+ assert(!options.use_direct_writes);
+}
+
+PosixMmapFile::~PosixMmapFile() {
+ if (fd_ >= 0) {
+ PosixMmapFile::Close(IOOptions(), nullptr);
+ }
+}
+
+IOStatus PosixMmapFile::Append(const Slice& data, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ const char* src = data.data();
+ size_t left = data.size();
+ while (left > 0) {
+ assert(base_ <= dst_);
+ assert(dst_ <= limit_);
+ size_t avail = limit_ - dst_;
+ if (avail == 0) {
+ IOStatus s = UnmapCurrentRegion();
+ if (!s.ok()) {
+ return s;
+ }
+ s = MapNewRegion();
+ if (!s.ok()) {
+ return s;
+ }
+ TEST_KILL_RANDOM("PosixMmapFile::Append:0", rocksdb_kill_odds);
+ }
+
+ size_t n = (left <= avail) ? left : avail;
+ assert(dst_);
+ memcpy(dst_, src, n);
+ dst_ += n;
+ src += n;
+ left -= n;
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixMmapFile::Close(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ IOStatus s;
+ size_t unused = limit_ - dst_;
+
+ s = UnmapCurrentRegion();
+ if (!s.ok()) {
+ s = IOError("While closing mmapped file", filename_, errno);
+ } else if (unused > 0) {
+ // Trim the extra space at the end of the file
+ if (ftruncate(fd_, file_offset_ - unused) < 0) {
+ s = IOError("While ftruncating mmaped file", filename_, errno);
+ }
+ }
+
+ if (close(fd_) < 0) {
+ if (s.ok()) {
+ s = IOError("While closing mmapped file", filename_, errno);
+ }
+ }
+
+ fd_ = -1;
+ base_ = nullptr;
+ limit_ = nullptr;
+ return s;
+}
+
+IOStatus PosixMmapFile::Flush(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+}
+
+IOStatus PosixMmapFile::Sync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fdatasync(fd_) < 0) {
+ return IOError("While fdatasync mmapped file", filename_, errno);
+ }
+
+ return Msync();
+}
+
+/**
+ * Flush data as well as metadata to stable storage.
+ */
+IOStatus PosixMmapFile::Fsync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fsync(fd_) < 0) {
+ return IOError("While fsync mmaped file", filename_, errno);
+ }
+
+ return Msync();
+}
+
+/**
+ * Get the size of valid data in the file. This will not match the
+ * size that is returned from the filesystem because we use mmap
+ * to extend file by map_size every time.
+ */
+uint64_t PosixMmapFile::GetFileSize(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ size_t used = dst_ - base_;
+ return file_offset_ + used;
+}
+
+IOStatus PosixMmapFile::InvalidateCache(size_t offset, size_t length) {
+#ifndef OS_LINUX
+ (void)offset;
+ (void)length;
+ return IOStatus::OK();
+#else
+ // free OS pages
+ int ret = Fadvise(fd_, offset, length, POSIX_FADV_DONTNEED);
+ if (ret == 0) {
+ return IOStatus::OK();
+ }
+ return IOError("While fadvise NotNeeded mmapped file", filename_, errno);
+#endif
+}
+
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+IOStatus PosixMmapFile::Allocate(uint64_t offset, uint64_t len,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ assert(len <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ TEST_KILL_RANDOM("PosixMmapFile::Allocate:0", rocksdb_kill_odds);
+ int alloc_status = 0;
+ if (allow_fallocate_) {
+ alloc_status =
+ fallocate(fd_, fallocate_with_keep_size_ ? FALLOC_FL_KEEP_SIZE : 0,
+ static_cast<off_t>(offset), static_cast<off_t>(len));
+ }
+ if (alloc_status == 0) {
+ return IOStatus::OK();
+ } else {
+ return IOError(
+ "While fallocate offset " + ToString(offset) + " len " + ToString(len),
+ filename_, errno);
+ }
+}
+#endif
+
+/*
+ * PosixWritableFile
+ *
+ * Use posix write to write data to a file.
+ */
+PosixWritableFile::PosixWritableFile(const std::string& fname, int fd,
+ const EnvOptions& options)
+ : FSWritableFile(options),
+ filename_(fname),
+ use_direct_io_(options.use_direct_writes),
+ fd_(fd),
+ filesize_(0),
+ logical_sector_size_(GetLogicalBufferSize(fd_)) {
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ allow_fallocate_ = options.allow_fallocate;
+ fallocate_with_keep_size_ = options.fallocate_with_keep_size;
+#endif
+#ifdef ROCKSDB_RANGESYNC_PRESENT
+ sync_file_range_supported_ = IsSyncFileRangeSupported(fd_);
+#endif // ROCKSDB_RANGESYNC_PRESENT
+ assert(!options.use_mmap_writes);
+}
+
+PosixWritableFile::~PosixWritableFile() {
+ if (fd_ >= 0) {
+ PosixWritableFile::Close(IOOptions(), nullptr);
+ }
+}
+
+IOStatus PosixWritableFile::Append(const Slice& data, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (use_direct_io()) {
+ assert(IsSectorAligned(data.size(), GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(data.data(), GetRequiredBufferAlignment()));
+ }
+ const char* src = data.data();
+ size_t nbytes = data.size();
+
+ if (!PosixWrite(fd_, src, nbytes)) {
+ return IOError("While appending to file", filename_, errno);
+ }
+
+ filesize_ += nbytes;
+ return IOStatus::OK();
+}
+
+IOStatus PosixWritableFile::PositionedAppend(const Slice& data, uint64_t offset,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (use_direct_io()) {
+ assert(IsSectorAligned(offset, GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(data.size(), GetRequiredBufferAlignment()));
+ assert(IsSectorAligned(data.data(), GetRequiredBufferAlignment()));
+ }
+ assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ const char* src = data.data();
+ size_t nbytes = data.size();
+ if (!PosixPositionedWrite(fd_, src, nbytes, static_cast<off_t>(offset))) {
+ return IOError("While pwrite to file at offset " + ToString(offset),
+ filename_, errno);
+ }
+ filesize_ = offset + nbytes;
+ return IOStatus::OK();
+}
+
+IOStatus PosixWritableFile::Truncate(uint64_t size, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ IOStatus s;
+ int r = ftruncate(fd_, size);
+ if (r < 0) {
+ s = IOError("While ftruncate file to size " + ToString(size), filename_,
+ errno);
+ } else {
+ filesize_ = size;
+ }
+ return s;
+}
+
+IOStatus PosixWritableFile::Close(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ IOStatus s;
+
+ size_t block_size;
+ size_t last_allocated_block;
+ GetPreallocationStatus(&block_size, &last_allocated_block);
+ if (last_allocated_block > 0) {
+ // trim the extra space preallocated at the end of the file
+ // NOTE(ljin): we probably don't want to surface failure as an IOError,
+ // but it will be nice to log these errors.
+ int dummy __attribute__((__unused__));
+ dummy = ftruncate(fd_, filesize_);
+#if defined(ROCKSDB_FALLOCATE_PRESENT) && defined(FALLOC_FL_PUNCH_HOLE) && \
+ !defined(TRAVIS)
+ // in some file systems, ftruncate only trims trailing space if the
+ // new file size is smaller than the current size. Calling fallocate
+ // with FALLOC_FL_PUNCH_HOLE flag to explicitly release these unused
+ // blocks. FALLOC_FL_PUNCH_HOLE is supported on at least the following
+ // filesystems:
+ // XFS (since Linux 2.6.38)
+ // ext4 (since Linux 3.0)
+ // Btrfs (since Linux 3.7)
+ // tmpfs (since Linux 3.5)
+ // We ignore error since failure of this operation does not affect
+ // correctness.
+ // TRAVIS - this code does not work on TRAVIS filesystems.
+ // the FALLOC_FL_KEEP_SIZE option is expected to not change the size
+ // of the file, but it does. Simple strace report will show that.
+ // While we work with Travis-CI team to figure out if this is a
+ // quirk of Docker/AUFS, we will comment this out.
+ struct stat file_stats;
+ int result = fstat(fd_, &file_stats);
+ // After ftruncate, we check whether ftruncate has the correct behavior.
+ // If not, we should hack it with FALLOC_FL_PUNCH_HOLE
+ if (result == 0 &&
+ (file_stats.st_size + file_stats.st_blksize - 1) /
+ file_stats.st_blksize !=
+ file_stats.st_blocks / (file_stats.st_blksize / 512)) {
+ IOSTATS_TIMER_GUARD(allocate_nanos);
+ if (allow_fallocate_) {
+ fallocate(fd_, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, filesize_,
+ block_size * last_allocated_block - filesize_);
+ }
+ }
+#endif
+ }
+
+ if (close(fd_) < 0) {
+ s = IOError("While closing file after writing", filename_, errno);
+ }
+ fd_ = -1;
+ return s;
+}
+
+// write out the cached data to the OS cache
+IOStatus PosixWritableFile::Flush(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+}
+
+IOStatus PosixWritableFile::Sync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fdatasync(fd_) < 0) {
+ return IOError("While fdatasync", filename_, errno);
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixWritableFile::Fsync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fsync(fd_) < 0) {
+ return IOError("While fsync", filename_, errno);
+ }
+ return IOStatus::OK();
+}
+
+bool PosixWritableFile::IsSyncThreadSafe() const { return true; }
+
+uint64_t PosixWritableFile::GetFileSize(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ return filesize_;
+}
+
+void PosixWritableFile::SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) {
+#ifdef OS_LINUX
+// Suppress Valgrind "Unimplemented functionality" error.
+#ifndef ROCKSDB_VALGRIND_RUN
+ if (hint == write_hint_) {
+ return;
+ }
+ if (fcntl(fd_, F_SET_RW_HINT, &hint) == 0) {
+ write_hint_ = hint;
+ }
+#else
+ (void)hint;
+#endif // ROCKSDB_VALGRIND_RUN
+#else
+ (void)hint;
+#endif // OS_LINUX
+}
+
+IOStatus PosixWritableFile::InvalidateCache(size_t offset, size_t length) {
+ if (use_direct_io()) {
+ return IOStatus::OK();
+ }
+#ifndef OS_LINUX
+ (void)offset;
+ (void)length;
+ return IOStatus::OK();
+#else
+ // free OS pages
+ int ret = Fadvise(fd_, offset, length, POSIX_FADV_DONTNEED);
+ if (ret == 0) {
+ return IOStatus::OK();
+ }
+ return IOError("While fadvise NotNeeded", filename_, errno);
+#endif
+}
+
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+IOStatus PosixWritableFile::Allocate(uint64_t offset, uint64_t len,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ assert(len <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ TEST_KILL_RANDOM("PosixWritableFile::Allocate:0", rocksdb_kill_odds);
+ IOSTATS_TIMER_GUARD(allocate_nanos);
+ int alloc_status = 0;
+ if (allow_fallocate_) {
+ alloc_status =
+ fallocate(fd_, fallocate_with_keep_size_ ? FALLOC_FL_KEEP_SIZE : 0,
+ static_cast<off_t>(offset), static_cast<off_t>(len));
+ }
+ if (alloc_status == 0) {
+ return IOStatus::OK();
+ } else {
+ return IOError(
+ "While fallocate offset " + ToString(offset) + " len " + ToString(len),
+ filename_, errno);
+ }
+}
+#endif
+
+IOStatus PosixWritableFile::RangeSync(uint64_t offset, uint64_t nbytes,
+ const IOOptions& opts,
+ IODebugContext* dbg) {
+#ifdef ROCKSDB_RANGESYNC_PRESENT
+ assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ assert(nbytes <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+ if (sync_file_range_supported_) {
+ int ret;
+ if (strict_bytes_per_sync_) {
+ // Specifying `SYNC_FILE_RANGE_WAIT_BEFORE` together with an offset/length
+ // that spans all bytes written so far tells `sync_file_range` to wait for
+ // any outstanding writeback requests to finish before issuing a new one.
+ ret =
+ sync_file_range(fd_, 0, static_cast<off_t>(offset + nbytes),
+ SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
+ } else {
+ ret = sync_file_range(fd_, static_cast<off_t>(offset),
+ static_cast<off_t>(nbytes), SYNC_FILE_RANGE_WRITE);
+ }
+ if (ret != 0) {
+ return IOError("While sync_file_range returned " + ToString(ret),
+ filename_, errno);
+ }
+ return IOStatus::OK();
+ }
+#endif // ROCKSDB_RANGESYNC_PRESENT
+ return FSWritableFile::RangeSync(offset, nbytes, opts, dbg);
+}
+
+#ifdef OS_LINUX
+size_t PosixWritableFile::GetUniqueId(char* id, size_t max_size) const {
+ return PosixHelper::GetUniqueIdFromFile(fd_, id, max_size);
+}
+#endif
+
+/*
+ * PosixRandomRWFile
+ */
+
+PosixRandomRWFile::PosixRandomRWFile(const std::string& fname, int fd,
+ const EnvOptions& /*options*/)
+ : filename_(fname), fd_(fd) {}
+
+PosixRandomRWFile::~PosixRandomRWFile() {
+ if (fd_ >= 0) {
+ Close(IOOptions(), nullptr);
+ }
+}
+
+IOStatus PosixRandomRWFile::Write(uint64_t offset, const Slice& data,
+ const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ const char* src = data.data();
+ size_t nbytes = data.size();
+ if (!PosixPositionedWrite(fd_, src, nbytes, static_cast<off_t>(offset))) {
+ return IOError(
+ "While write random read/write file at offset " + ToString(offset),
+ filename_, errno);
+ }
+
+ return IOStatus::OK();
+}
+
+IOStatus PosixRandomRWFile::Read(uint64_t offset, size_t n,
+ const IOOptions& /*opts*/, Slice* result,
+ char* scratch, IODebugContext* /*dbg*/) const {
+ size_t left = n;
+ char* ptr = scratch;
+ while (left > 0) {
+ ssize_t done = pread(fd_, ptr, left, offset);
+ if (done < 0) {
+ // error while reading from file
+ if (errno == EINTR) {
+ // read was interrupted, try again.
+ continue;
+ }
+ return IOError("While reading random read/write file offset " +
+ ToString(offset) + " len " + ToString(n),
+ filename_, errno);
+ } else if (done == 0) {
+ // Nothing more to read
+ break;
+ }
+
+ // Read `done` bytes
+ ptr += done;
+ offset += done;
+ left -= done;
+ }
+
+ *result = Slice(scratch, n - left);
+ return IOStatus::OK();
+}
+
+IOStatus PosixRandomRWFile::Flush(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+}
+
+IOStatus PosixRandomRWFile::Sync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fdatasync(fd_) < 0) {
+ return IOError("While fdatasync random read/write file", filename_, errno);
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixRandomRWFile::Fsync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (fsync(fd_) < 0) {
+ return IOError("While fsync random read/write file", filename_, errno);
+ }
+ return IOStatus::OK();
+}
+
+IOStatus PosixRandomRWFile::Close(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+ if (close(fd_) < 0) {
+ return IOError("While close random read/write file", filename_, errno);
+ }
+ fd_ = -1;
+ return IOStatus::OK();
+}
+
+PosixMemoryMappedFileBuffer::~PosixMemoryMappedFileBuffer() {
+ // TODO should have error handling though not much we can do...
+ munmap(this->base_, length_);
+}
+
+/*
+ * PosixDirectory
+ */
+
+PosixDirectory::~PosixDirectory() { close(fd_); }
+
+IOStatus PosixDirectory::Fsync(const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) {
+#ifndef OS_AIX
+ if (fsync(fd_) == -1) {
+ return IOError("While fsync", "a directory", errno);
+ }
+#endif
+ return IOStatus::OK();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/env/io_posix.h b/storage/rocksdb/rocksdb/env/io_posix.h
new file mode 100644
index 00000000..535bd99c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/io_posix.h
@@ -0,0 +1,326 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <errno.h>
+#if defined(ROCKSDB_IOURING_PRESENT)
+#include <liburing.h>
+#include <sys/uio.h>
+#endif
+#include <unistd.h>
+#include <atomic>
+#include <string>
+#include "rocksdb/env.h"
+#include "util/thread_local.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/io_status.h"
+
+// For non linux platform, the following macros are used only as place
+// holder.
+#if !(defined OS_LINUX) && !(defined CYGWIN) && !(defined OS_AIX)
+#define POSIX_FADV_NORMAL 0 /* [MC1] no further special treatment */
+#define POSIX_FADV_RANDOM 1 /* [MC1] expect random page refs */
+#define POSIX_FADV_SEQUENTIAL 2 /* [MC1] expect sequential page refs */
+#define POSIX_FADV_WILLNEED 3 /* [MC1] will need these pages */
+#define POSIX_FADV_DONTNEED 4 /* [MC1] dont need these pages */
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+static std::string IOErrorMsg(const std::string& context,
+ const std::string& file_name) {
+ if (file_name.empty()) {
+ return context;
+ }
+ return context + ": " + file_name;
+}
+
+// file_name can be left empty if it is not unkown.
+static IOStatus IOError(const std::string& context,
+ const std::string& file_name, int err_number) {
+ switch (err_number) {
+ case ENOSPC: {
+ IOStatus s = IOStatus::NoSpace(IOErrorMsg(context, file_name),
+ strerror(err_number));
+ s.SetRetryable(true);
+ return s;
+ }
+ case ESTALE:
+ return IOStatus::IOError(IOStatus::kStaleFile);
+ case ENOENT:
+ return IOStatus::PathNotFound(IOErrorMsg(context, file_name),
+ strerror(err_number));
+ default:
+ return IOStatus::IOError(IOErrorMsg(context, file_name),
+ strerror(err_number));
+ }
+}
+
+class PosixHelper {
+ public:
+ static size_t GetUniqueIdFromFile(int fd, char* id, size_t max_size);
+};
+
+class PosixSequentialFile : public FSSequentialFile {
+ private:
+ std::string filename_;
+ FILE* file_;
+ int fd_;
+ bool use_direct_io_;
+ size_t logical_sector_size_;
+
+ public:
+ PosixSequentialFile(const std::string& fname, FILE* file, int fd,
+ const EnvOptions& options);
+ virtual ~PosixSequentialFile();
+
+ virtual IOStatus Read(size_t n, const IOOptions& opts, Slice* result,
+ char* scratch, IODebugContext* dbg) override;
+ virtual IOStatus PositionedRead(uint64_t offset, size_t n,
+ const IOOptions& opts, Slice* result,
+ char* scratch, IODebugContext* dbg) override;
+ virtual IOStatus Skip(uint64_t n) override;
+ virtual IOStatus InvalidateCache(size_t offset, size_t length) override;
+ virtual bool use_direct_io() const override { return use_direct_io_; }
+ virtual size_t GetRequiredBufferAlignment() const override {
+ return logical_sector_size_;
+ }
+};
+
+#if defined(ROCKSDB_IOURING_PRESENT)
+// io_uring instance queue depth
+const unsigned int kIoUringDepth = 256;
+
+inline void DeleteIOUring(void* p) {
+ struct io_uring* iu = static_cast<struct io_uring*>(p);
+ delete iu;
+}
+
+inline struct io_uring* CreateIOUring() {
+ struct io_uring* new_io_uring = new struct io_uring;
+ int ret = io_uring_queue_init(kIoUringDepth, new_io_uring, 0);
+ if (ret) {
+ delete new_io_uring;
+ new_io_uring = nullptr;
+ }
+ return new_io_uring;
+}
+#endif // defined(ROCKSDB_IOURING_PRESENT)
+
+class PosixRandomAccessFile : public FSRandomAccessFile {
+ protected:
+ std::string filename_;
+ int fd_;
+ bool use_direct_io_;
+ size_t logical_sector_size_;
+#if defined(ROCKSDB_IOURING_PRESENT)
+ ThreadLocalPtr* thread_local_io_urings_;
+#endif
+
+ public:
+ PosixRandomAccessFile(const std::string& fname, int fd,
+ const EnvOptions& options
+#if defined(ROCKSDB_IOURING_PRESENT)
+ ,
+ ThreadLocalPtr* thread_local_io_urings
+#endif
+ );
+ virtual ~PosixRandomAccessFile();
+
+ virtual IOStatus Read(uint64_t offset, size_t n, const IOOptions& opts,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const override;
+
+ virtual IOStatus MultiRead(FSReadRequest* reqs, size_t num_reqs,
+ const IOOptions& options,
+ IODebugContext* dbg) override;
+
+ virtual IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& opts,
+ IODebugContext* dbg) override;
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_AIX)
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+#endif
+ virtual void Hint(AccessPattern pattern) override;
+ virtual IOStatus InvalidateCache(size_t offset, size_t length) override;
+ virtual bool use_direct_io() const override { return use_direct_io_; }
+ virtual size_t GetRequiredBufferAlignment() const override {
+ return logical_sector_size_;
+ }
+};
+
+class PosixWritableFile : public FSWritableFile {
+ protected:
+ const std::string filename_;
+ const bool use_direct_io_;
+ int fd_;
+ uint64_t filesize_;
+ size_t logical_sector_size_;
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ bool allow_fallocate_;
+ bool fallocate_with_keep_size_;
+#endif
+#ifdef ROCKSDB_RANGESYNC_PRESENT
+ // Even if the syscall is present, the filesystem may still not properly
+ // support it, so we need to do a dynamic check too.
+ bool sync_file_range_supported_;
+#endif // ROCKSDB_RANGESYNC_PRESENT
+
+ public:
+ explicit PosixWritableFile(const std::string& fname, int fd,
+ const EnvOptions& options);
+ virtual ~PosixWritableFile();
+
+ // Need to implement this so the file is truncated correctly
+ // with direct I/O
+ virtual IOStatus Truncate(uint64_t size, const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus Close(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Append(const Slice& data, const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus PositionedAppend(const Slice& data, uint64_t offset,
+ const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus Flush(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Sync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Fsync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual bool IsSyncThreadSafe() const override;
+ virtual bool use_direct_io() const override { return use_direct_io_; }
+ virtual void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override;
+ virtual uint64_t GetFileSize(const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus InvalidateCache(size_t offset, size_t length) override;
+ virtual size_t GetRequiredBufferAlignment() const override {
+ return logical_sector_size_;
+ }
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ virtual IOStatus Allocate(uint64_t offset, uint64_t len,
+ const IOOptions& opts,
+ IODebugContext* dbg) override;
+#endif
+ virtual IOStatus RangeSync(uint64_t offset, uint64_t nbytes,
+ const IOOptions& opts,
+ IODebugContext* dbg) override;
+#ifdef OS_LINUX
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+#endif
+};
+
+// mmap() based random-access
+class PosixMmapReadableFile : public FSRandomAccessFile {
+ private:
+ int fd_;
+ std::string filename_;
+ void* mmapped_region_;
+ size_t length_;
+
+ public:
+ PosixMmapReadableFile(const int fd, const std::string& fname, void* base,
+ size_t length, const EnvOptions& options);
+ virtual ~PosixMmapReadableFile();
+ virtual IOStatus Read(uint64_t offset, size_t n, const IOOptions& opts,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const override;
+ virtual IOStatus InvalidateCache(size_t offset, size_t length) override;
+};
+
+class PosixMmapFile : public FSWritableFile {
+ private:
+ std::string filename_;
+ int fd_;
+ size_t page_size_;
+ size_t map_size_; // How much extra memory to map at a time
+ char* base_; // The mapped region
+ char* limit_; // Limit of the mapped region
+ char* dst_; // Where to write next (in range [base_,limit_])
+ char* last_sync_; // Where have we synced up to
+ uint64_t file_offset_; // Offset of base_ in file
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ bool allow_fallocate_; // If false, fallocate calls are bypassed
+ bool fallocate_with_keep_size_;
+#endif
+
+ // Roundup x to a multiple of y
+ static size_t Roundup(size_t x, size_t y) { return ((x + y - 1) / y) * y; }
+
+ size_t TruncateToPageBoundary(size_t s) {
+ s -= (s & (page_size_ - 1));
+ assert((s % page_size_) == 0);
+ return s;
+ }
+
+ IOStatus MapNewRegion();
+ IOStatus UnmapCurrentRegion();
+ IOStatus Msync();
+
+ public:
+ PosixMmapFile(const std::string& fname, int fd, size_t page_size,
+ const EnvOptions& options);
+ ~PosixMmapFile();
+
+ // Means Close() will properly take care of truncate
+ // and it does not need any additional information
+ virtual IOStatus Truncate(uint64_t /*size*/, const IOOptions& /*opts*/,
+ IODebugContext* /*dbg*/) override {
+ return IOStatus::OK();
+ }
+ virtual IOStatus Close(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Append(const Slice& data, const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus Flush(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Sync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Fsync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual uint64_t GetFileSize(const IOOptions& opts,
+ IODebugContext* dbg) override;
+ virtual IOStatus InvalidateCache(size_t offset, size_t length) override;
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ virtual IOStatus Allocate(uint64_t offset, uint64_t len,
+ const IOOptions& opts,
+ IODebugContext* dbg) override;
+#endif
+};
+
+class PosixRandomRWFile : public FSRandomRWFile {
+ public:
+ explicit PosixRandomRWFile(const std::string& fname, int fd,
+ const EnvOptions& options);
+ virtual ~PosixRandomRWFile();
+
+ virtual IOStatus Write(uint64_t offset, const Slice& data,
+ const IOOptions& opts, IODebugContext* dbg) override;
+
+ virtual IOStatus Read(uint64_t offset, size_t n, const IOOptions& opts,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const override;
+
+ virtual IOStatus Flush(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Sync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Fsync(const IOOptions& opts, IODebugContext* dbg) override;
+ virtual IOStatus Close(const IOOptions& opts, IODebugContext* dbg) override;
+
+ private:
+ const std::string filename_;
+ int fd_;
+};
+
+struct PosixMemoryMappedFileBuffer : public MemoryMappedFileBuffer {
+ PosixMemoryMappedFileBuffer(void* _base, size_t _length)
+ : MemoryMappedFileBuffer(_base, _length) {}
+ virtual ~PosixMemoryMappedFileBuffer();
+};
+
+class PosixDirectory : public FSDirectory {
+ public:
+ explicit PosixDirectory(int fd) : fd_(fd) {}
+ ~PosixDirectory();
+ virtual IOStatus Fsync(const IOOptions& opts, IODebugContext* dbg) override;
+
+ private:
+ int fd_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/mock_env.cc b/storage/rocksdb/rocksdb/env/mock_env.cc
new file mode 100644
index 00000000..c246c13e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/mock_env.cc
@@ -0,0 +1,774 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "env/mock_env.h"
+#include <algorithm>
+#include <chrono>
+#include "port/sys_time.h"
+#include "util/cast_util.h"
+#include "util/murmurhash.h"
+#include "util/random.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MemFile {
+ public:
+ explicit MemFile(Env* env, const std::string& fn, bool _is_lock_file = false)
+ : env_(env),
+ fn_(fn),
+ refs_(0),
+ is_lock_file_(_is_lock_file),
+ locked_(false),
+ size_(0),
+ modified_time_(Now()),
+ rnd_(static_cast<uint32_t>(
+ MurmurHash(fn.data(), static_cast<int>(fn.size()), 0))),
+ fsynced_bytes_(0) {}
+ // No copying allowed.
+ MemFile(const MemFile&) = delete;
+ void operator=(const MemFile&) = delete;
+
+ void Ref() {
+ MutexLock lock(&mutex_);
+ ++refs_;
+ }
+
+ bool is_lock_file() const { return is_lock_file_; }
+
+ bool Lock() {
+ assert(is_lock_file_);
+ MutexLock lock(&mutex_);
+ if (locked_) {
+ return false;
+ } else {
+ locked_ = true;
+ return true;
+ }
+ }
+
+ void Unlock() {
+ assert(is_lock_file_);
+ MutexLock lock(&mutex_);
+ locked_ = false;
+ }
+
+ void Unref() {
+ bool do_delete = false;
+ {
+ MutexLock lock(&mutex_);
+ --refs_;
+ assert(refs_ >= 0);
+ if (refs_ <= 0) {
+ do_delete = true;
+ }
+ }
+
+ if (do_delete) {
+ delete this;
+ }
+ }
+
+ uint64_t Size() const { return size_; }
+
+ void Truncate(size_t size) {
+ MutexLock lock(&mutex_);
+ if (size < size_) {
+ data_.resize(size);
+ size_ = size;
+ }
+ }
+
+ void CorruptBuffer() {
+ if (fsynced_bytes_ >= size_) {
+ return;
+ }
+ uint64_t buffered_bytes = size_ - fsynced_bytes_;
+ uint64_t start =
+ fsynced_bytes_ + rnd_.Uniform(static_cast<int>(buffered_bytes));
+ uint64_t end = std::min(start + 512, size_.load());
+ MutexLock lock(&mutex_);
+ for (uint64_t pos = start; pos < end; ++pos) {
+ data_[static_cast<size_t>(pos)] = static_cast<char>(rnd_.Uniform(256));
+ }
+ }
+
+ Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const {
+ MutexLock lock(&mutex_);
+ const uint64_t available = Size() - std::min(Size(), offset);
+ size_t offset_ = static_cast<size_t>(offset);
+ if (n > available) {
+ n = static_cast<size_t>(available);
+ }
+ if (n == 0) {
+ *result = Slice();
+ return Status::OK();
+ }
+ if (scratch) {
+ memcpy(scratch, &(data_[offset_]), n);
+ *result = Slice(scratch, n);
+ } else {
+ *result = Slice(&(data_[offset_]), n);
+ }
+ return Status::OK();
+ }
+
+ Status Write(uint64_t offset, const Slice& data) {
+ MutexLock lock(&mutex_);
+ size_t offset_ = static_cast<size_t>(offset);
+ if (offset + data.size() > data_.size()) {
+ data_.resize(offset_ + data.size());
+ }
+ data_.replace(offset_, data.size(), data.data(), data.size());
+ size_ = data_.size();
+ modified_time_ = Now();
+ return Status::OK();
+ }
+
+ Status Append(const Slice& data) {
+ MutexLock lock(&mutex_);
+ data_.append(data.data(), data.size());
+ size_ = data_.size();
+ modified_time_ = Now();
+ return Status::OK();
+ }
+
+ Status Fsync() {
+ fsynced_bytes_ = size_.load();
+ return Status::OK();
+ }
+
+ uint64_t ModifiedTime() const { return modified_time_; }
+
+ private:
+ uint64_t Now() {
+ int64_t unix_time = 0;
+ auto s = env_->GetCurrentTime(&unix_time);
+ assert(s.ok());
+ return static_cast<uint64_t>(unix_time);
+ }
+
+ // Private since only Unref() should be used to delete it.
+ ~MemFile() { assert(refs_ == 0); }
+
+ Env* env_;
+ const std::string fn_;
+ mutable port::Mutex mutex_;
+ int refs_;
+ bool is_lock_file_;
+ bool locked_;
+
+ // Data written into this file, all bytes before fsynced_bytes are
+ // persistent.
+ std::string data_;
+ std::atomic<uint64_t> size_;
+ std::atomic<uint64_t> modified_time_;
+
+ Random rnd_;
+ std::atomic<uint64_t> fsynced_bytes_;
+};
+
+namespace {
+
+class MockSequentialFile : public SequentialFile {
+ public:
+ explicit MockSequentialFile(MemFile* file) : file_(file), pos_(0) {
+ file_->Ref();
+ }
+
+ ~MockSequentialFile() override { file_->Unref(); }
+
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ Status s = file_->Read(pos_, n, result, scratch);
+ if (s.ok()) {
+ pos_ += result->size();
+ }
+ return s;
+ }
+
+ Status Skip(uint64_t n) override {
+ if (pos_ > file_->Size()) {
+ return Status::IOError("pos_ > file_->Size()");
+ }
+ const uint64_t available = file_->Size() - pos_;
+ if (n > available) {
+ n = available;
+ }
+ pos_ += static_cast<size_t>(n);
+ return Status::OK();
+ }
+
+ private:
+ MemFile* file_;
+ size_t pos_;
+};
+
+class MockRandomAccessFile : public RandomAccessFile {
+ public:
+ explicit MockRandomAccessFile(MemFile* file) : file_(file) { file_->Ref(); }
+
+ ~MockRandomAccessFile() override { file_->Unref(); }
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ return file_->Read(offset, n, result, scratch);
+ }
+
+ private:
+ MemFile* file_;
+};
+
+class MockRandomRWFile : public RandomRWFile {
+ public:
+ explicit MockRandomRWFile(MemFile* file) : file_(file) { file_->Ref(); }
+
+ ~MockRandomRWFile() override { file_->Unref(); }
+
+ Status Write(uint64_t offset, const Slice& data) override {
+ return file_->Write(offset, data);
+ }
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ return file_->Read(offset, n, result, scratch);
+ }
+
+ Status Close() override { return file_->Fsync(); }
+
+ Status Flush() override { return Status::OK(); }
+
+ Status Sync() override { return file_->Fsync(); }
+
+ private:
+ MemFile* file_;
+};
+
+class MockWritableFile : public WritableFile {
+ public:
+ MockWritableFile(MemFile* file, RateLimiter* rate_limiter)
+ : file_(file), rate_limiter_(rate_limiter) {
+ file_->Ref();
+ }
+
+ ~MockWritableFile() override { file_->Unref(); }
+
+ Status Append(const Slice& data) override {
+ size_t bytes_written = 0;
+ while (bytes_written < data.size()) {
+ auto bytes = RequestToken(data.size() - bytes_written);
+ Status s = file_->Append(Slice(data.data() + bytes_written, bytes));
+ if (!s.ok()) {
+ return s;
+ }
+ bytes_written += bytes;
+ }
+ return Status::OK();
+ }
+ Status Truncate(uint64_t size) override {
+ file_->Truncate(static_cast<size_t>(size));
+ return Status::OK();
+ }
+ Status Close() override { return file_->Fsync(); }
+
+ Status Flush() override { return Status::OK(); }
+
+ Status Sync() override { return file_->Fsync(); }
+
+ uint64_t GetFileSize() override { return file_->Size(); }
+
+ private:
+ inline size_t RequestToken(size_t bytes) {
+ if (rate_limiter_ && io_priority_ < Env::IO_TOTAL) {
+ bytes = std::min(
+ bytes, static_cast<size_t>(rate_limiter_->GetSingleBurstBytes()));
+ rate_limiter_->Request(bytes, io_priority_);
+ }
+ return bytes;
+ }
+
+ MemFile* file_;
+ RateLimiter* rate_limiter_;
+};
+
+class MockEnvDirectory : public Directory {
+ public:
+ Status Fsync() override { return Status::OK(); }
+};
+
+class MockEnvFileLock : public FileLock {
+ public:
+ explicit MockEnvFileLock(const std::string& fname) : fname_(fname) {}
+
+ std::string FileName() const { return fname_; }
+
+ private:
+ const std::string fname_;
+};
+
+class TestMemLogger : public Logger {
+ private:
+ std::unique_ptr<WritableFile> file_;
+ std::atomic_size_t log_size_;
+ static const uint64_t flush_every_seconds_ = 5;
+ std::atomic_uint_fast64_t last_flush_micros_;
+ Env* env_;
+ std::atomic<bool> flush_pending_;
+
+ public:
+ TestMemLogger(std::unique_ptr<WritableFile> f, Env* env,
+ const InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL)
+ : Logger(log_level),
+ file_(std::move(f)),
+ log_size_(0),
+ last_flush_micros_(0),
+ env_(env),
+ flush_pending_(false) {}
+ ~TestMemLogger() override {}
+
+ void Flush() override {
+ if (flush_pending_) {
+ flush_pending_ = false;
+ }
+ last_flush_micros_ = env_->NowMicros();
+ }
+
+ using Logger::Logv;
+ void Logv(const char* format, va_list ap) override {
+ // We try twice: the first time with a fixed-size stack allocated buffer,
+ // and the second time with a much larger dynamically allocated buffer.
+ char buffer[500];
+ for (int iter = 0; iter < 2; iter++) {
+ char* base;
+ int bufsize;
+ if (iter == 0) {
+ bufsize = sizeof(buffer);
+ base = buffer;
+ } else {
+ bufsize = 30000;
+ base = new char[bufsize];
+ }
+ char* p = base;
+ char* limit = base + bufsize;
+
+ struct timeval now_tv;
+ gettimeofday(&now_tv, nullptr);
+ const time_t seconds = now_tv.tv_sec;
+ struct tm t;
+ memset(&t, 0, sizeof(t));
+ struct tm* ret __attribute__((__unused__));
+ ret = localtime_r(&seconds, &t);
+ assert(ret);
+ p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d ",
+ t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+ t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec));
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ p += vsnprintf(p, limit - p, format, backup_ap);
+ va_end(backup_ap);
+ }
+
+ // Truncate to available space if necessary
+ if (p >= limit) {
+ if (iter == 0) {
+ continue; // Try again with larger buffer
+ } else {
+ p = limit - 1;
+ }
+ }
+
+ // Add newline if necessary
+ if (p == base || p[-1] != '\n') {
+ *p++ = '\n';
+ }
+
+ assert(p <= limit);
+ const size_t write_size = p - base;
+
+ file_->Append(Slice(base, write_size));
+ flush_pending_ = true;
+ log_size_ += write_size;
+ uint64_t now_micros =
+ static_cast<uint64_t>(now_tv.tv_sec) * 1000000 + now_tv.tv_usec;
+ if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
+ flush_pending_ = false;
+ last_flush_micros_ = now_micros;
+ }
+ if (base != buffer) {
+ delete[] base;
+ }
+ break;
+ }
+ }
+ size_t GetLogFileSize() const override { return log_size_; }
+};
+
+} // Anonymous namespace
+
+MockEnv::MockEnv(Env* base_env) : EnvWrapper(base_env), fake_sleep_micros_(0) {}
+
+MockEnv::~MockEnv() {
+ for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i) {
+ i->second->Unref();
+ }
+}
+
+// Partial implementation of the Env interface.
+Status MockEnv::NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& /*soptions*/) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) == file_map_.end()) {
+ *result = nullptr;
+ return Status::IOError(fn, "File not found");
+ }
+ auto* f = file_map_[fn];
+ if (f->is_lock_file()) {
+ return Status::InvalidArgument(fn, "Cannot open a lock file.");
+ }
+ result->reset(new MockSequentialFile(f));
+ return Status::OK();
+}
+
+Status MockEnv::NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& /*soptions*/) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) == file_map_.end()) {
+ *result = nullptr;
+ return Status::IOError(fn, "File not found");
+ }
+ auto* f = file_map_[fn];
+ if (f->is_lock_file()) {
+ return Status::InvalidArgument(fn, "Cannot open a lock file.");
+ }
+ result->reset(new MockRandomAccessFile(f));
+ return Status::OK();
+}
+
+Status MockEnv::NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& /*soptions*/) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) == file_map_.end()) {
+ *result = nullptr;
+ return Status::IOError(fn, "File not found");
+ }
+ auto* f = file_map_[fn];
+ if (f->is_lock_file()) {
+ return Status::InvalidArgument(fn, "Cannot open a lock file.");
+ }
+ result->reset(new MockRandomRWFile(f));
+ return Status::OK();
+}
+
+Status MockEnv::ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) {
+ auto s = RenameFile(old_fname, fname);
+ if (!s.ok()) {
+ return s;
+ }
+ result->reset();
+ return NewWritableFile(fname, result, options);
+}
+
+Status MockEnv::NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& env_options) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) != file_map_.end()) {
+ DeleteFileInternal(fn);
+ }
+ MemFile* file = new MemFile(this, fn, false);
+ file->Ref();
+ file_map_[fn] = file;
+
+ result->reset(new MockWritableFile(file, env_options.rate_limiter));
+ return Status::OK();
+}
+
+Status MockEnv::NewDirectory(const std::string& /*name*/,
+ std::unique_ptr<Directory>* result) {
+ result->reset(new MockEnvDirectory());
+ return Status::OK();
+}
+
+Status MockEnv::FileExists(const std::string& fname) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) != file_map_.end()) {
+ // File exists
+ return Status::OK();
+ }
+ // Now also check if fn exists as a dir
+ for (const auto& iter : file_map_) {
+ const std::string& filename = iter.first;
+ if (filename.size() >= fn.size() + 1 && filename[fn.size()] == '/' &&
+ Slice(filename).starts_with(Slice(fn))) {
+ return Status::OK();
+ }
+ }
+ return Status::NotFound();
+}
+
+Status MockEnv::GetChildren(const std::string& dir,
+ std::vector<std::string>* result) {
+ auto d = NormalizePath(dir);
+ bool found_dir = false;
+ {
+ MutexLock lock(&mutex_);
+ result->clear();
+ for (const auto& iter : file_map_) {
+ const std::string& filename = iter.first;
+
+ if (filename == d) {
+ found_dir = true;
+ } else if (filename.size() >= d.size() + 1 && filename[d.size()] == '/' &&
+ Slice(filename).starts_with(Slice(d))) {
+ found_dir = true;
+ size_t next_slash = filename.find('/', d.size() + 1);
+ if (next_slash != std::string::npos) {
+ result->push_back(
+ filename.substr(d.size() + 1, next_slash - d.size() - 1));
+ } else {
+ result->push_back(filename.substr(d.size() + 1));
+ }
+ }
+ }
+ }
+ result->erase(std::unique(result->begin(), result->end()), result->end());
+ return found_dir ? Status::OK() : Status::NotFound();
+}
+
+void MockEnv::DeleteFileInternal(const std::string& fname) {
+ assert(fname == NormalizePath(fname));
+ const auto& pair = file_map_.find(fname);
+ if (pair != file_map_.end()) {
+ pair->second->Unref();
+ file_map_.erase(fname);
+ }
+}
+
+Status MockEnv::DeleteFile(const std::string& fname) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) == file_map_.end()) {
+ return Status::IOError(fn, "File not found");
+ }
+
+ DeleteFileInternal(fn);
+ return Status::OK();
+}
+
+Status MockEnv::Truncate(const std::string& fname, size_t size) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ auto iter = file_map_.find(fn);
+ if (iter == file_map_.end()) {
+ return Status::IOError(fn, "File not found");
+ }
+ iter->second->Truncate(size);
+ return Status::OK();
+}
+
+Status MockEnv::CreateDir(const std::string& dirname) {
+ auto dn = NormalizePath(dirname);
+ if (file_map_.find(dn) == file_map_.end()) {
+ MemFile* file = new MemFile(this, dn, false);
+ file->Ref();
+ file_map_[dn] = file;
+ } else {
+ return Status::IOError();
+ }
+ return Status::OK();
+}
+
+Status MockEnv::CreateDirIfMissing(const std::string& dirname) {
+ CreateDir(dirname);
+ return Status::OK();
+}
+
+Status MockEnv::DeleteDir(const std::string& dirname) {
+ return DeleteFile(dirname);
+}
+
+Status MockEnv::GetFileSize(const std::string& fname, uint64_t* file_size) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ auto iter = file_map_.find(fn);
+ if (iter == file_map_.end()) {
+ return Status::IOError(fn, "File not found");
+ }
+
+ *file_size = iter->second->Size();
+ return Status::OK();
+}
+
+Status MockEnv::GetFileModificationTime(const std::string& fname,
+ uint64_t* time) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ auto iter = file_map_.find(fn);
+ if (iter == file_map_.end()) {
+ return Status::IOError(fn, "File not found");
+ }
+ *time = iter->second->ModifiedTime();
+ return Status::OK();
+}
+
+Status MockEnv::RenameFile(const std::string& src, const std::string& dest) {
+ auto s = NormalizePath(src);
+ auto t = NormalizePath(dest);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(s) == file_map_.end()) {
+ return Status::IOError(s, "File not found");
+ }
+
+ DeleteFileInternal(t);
+ file_map_[t] = file_map_[s];
+ file_map_.erase(s);
+ return Status::OK();
+}
+
+Status MockEnv::LinkFile(const std::string& src, const std::string& dest) {
+ auto s = NormalizePath(src);
+ auto t = NormalizePath(dest);
+ MutexLock lock(&mutex_);
+ if (file_map_.find(s) == file_map_.end()) {
+ return Status::IOError(s, "File not found");
+ }
+
+ DeleteFileInternal(t);
+ file_map_[t] = file_map_[s];
+ file_map_[t]->Ref(); // Otherwise it might get deleted when noone uses s
+ return Status::OK();
+}
+
+Status MockEnv::NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ auto iter = file_map_.find(fn);
+ MemFile* file = nullptr;
+ if (iter == file_map_.end()) {
+ file = new MemFile(this, fn, false);
+ file->Ref();
+ file_map_[fn] = file;
+ } else {
+ file = iter->second;
+ }
+ std::unique_ptr<WritableFile> f(new MockWritableFile(file, nullptr));
+ result->reset(new TestMemLogger(std::move(f), this));
+ return Status::OK();
+}
+
+Status MockEnv::LockFile(const std::string& fname, FileLock** flock) {
+ auto fn = NormalizePath(fname);
+ {
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) != file_map_.end()) {
+ if (!file_map_[fn]->is_lock_file()) {
+ return Status::InvalidArgument(fname, "Not a lock file.");
+ }
+ if (!file_map_[fn]->Lock()) {
+ return Status::IOError(fn, "Lock is already held.");
+ }
+ } else {
+ auto* file = new MemFile(this, fn, true);
+ file->Ref();
+ file->Lock();
+ file_map_[fn] = file;
+ }
+ }
+ *flock = new MockEnvFileLock(fn);
+ return Status::OK();
+}
+
+Status MockEnv::UnlockFile(FileLock* flock) {
+ std::string fn =
+ static_cast_with_check<MockEnvFileLock, FileLock>(flock)->FileName();
+ {
+ MutexLock lock(&mutex_);
+ if (file_map_.find(fn) != file_map_.end()) {
+ if (!file_map_[fn]->is_lock_file()) {
+ return Status::InvalidArgument(fn, "Not a lock file.");
+ }
+ file_map_[fn]->Unlock();
+ }
+ }
+ delete flock;
+ return Status::OK();
+}
+
+Status MockEnv::GetTestDirectory(std::string* path) {
+ *path = "/test";
+ return Status::OK();
+}
+
+Status MockEnv::GetCurrentTime(int64_t* unix_time) {
+ auto s = EnvWrapper::GetCurrentTime(unix_time);
+ if (s.ok()) {
+ *unix_time += fake_sleep_micros_.load() / (1000 * 1000);
+ }
+ return s;
+}
+
+uint64_t MockEnv::NowMicros() {
+ return EnvWrapper::NowMicros() + fake_sleep_micros_.load();
+}
+
+uint64_t MockEnv::NowNanos() {
+ return EnvWrapper::NowNanos() + fake_sleep_micros_.load() * 1000;
+}
+
+Status MockEnv::CorruptBuffer(const std::string& fname) {
+ auto fn = NormalizePath(fname);
+ MutexLock lock(&mutex_);
+ auto iter = file_map_.find(fn);
+ if (iter == file_map_.end()) {
+ return Status::IOError(fn, "File not found");
+ }
+ iter->second->CorruptBuffer();
+ return Status::OK();
+}
+
+std::string MockEnv::NormalizePath(const std::string path) {
+ std::string dst;
+ for (auto c : path) {
+ if (!dst.empty() && c == '/' && dst.back() == '/') {
+ continue;
+ }
+ dst.push_back(c);
+ }
+ return dst;
+}
+
+void MockEnv::FakeSleepForMicroseconds(int64_t micros) {
+ fake_sleep_micros_.fetch_add(micros);
+}
+
+#ifndef ROCKSDB_LITE
+// This is to maintain the behavior before swithcing from InMemoryEnv to MockEnv
+Env* NewMemEnv(Env* base_env) { return new MockEnv(base_env); }
+
+#else // ROCKSDB_LITE
+
+Env* NewMemEnv(Env* /*base_env*/) { return nullptr; }
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/mock_env.h b/storage/rocksdb/rocksdb/env/mock_env.h
new file mode 100644
index 00000000..145cae06
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/mock_env.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <map>
+#include <string>
+#include <vector>
+#include "rocksdb/env.h"
+#include "rocksdb/status.h"
+#include "port/port.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MemFile;
+class MockEnv : public EnvWrapper {
+ public:
+ explicit MockEnv(Env* base_env);
+
+ virtual ~MockEnv();
+
+ // Partial implementation of the Env interface.
+ virtual Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& soptions) override;
+
+ virtual Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& soptions) override;
+
+ virtual Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override;
+
+ virtual Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ virtual Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& env_options) override;
+
+ virtual Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override;
+
+ virtual Status FileExists(const std::string& fname) override;
+
+ virtual Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) override;
+
+ void DeleteFileInternal(const std::string& fname);
+
+ virtual Status DeleteFile(const std::string& fname) override;
+
+ virtual Status Truncate(const std::string& fname, size_t size) override;
+
+ virtual Status CreateDir(const std::string& dirname) override;
+
+ virtual Status CreateDirIfMissing(const std::string& dirname) override;
+
+ virtual Status DeleteDir(const std::string& dirname) override;
+
+ virtual Status GetFileSize(const std::string& fname,
+ uint64_t* file_size) override;
+
+ virtual Status GetFileModificationTime(const std::string& fname,
+ uint64_t* time) override;
+
+ virtual Status RenameFile(const std::string& src,
+ const std::string& target) override;
+
+ virtual Status LinkFile(const std::string& src,
+ const std::string& target) override;
+
+ virtual Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override;
+
+ virtual Status LockFile(const std::string& fname, FileLock** flock) override;
+
+ virtual Status UnlockFile(FileLock* flock) override;
+
+ virtual Status GetTestDirectory(std::string* path) override;
+
+ // Results of these can be affected by FakeSleepForMicroseconds()
+ virtual Status GetCurrentTime(int64_t* unix_time) override;
+ virtual uint64_t NowMicros() override;
+ virtual uint64_t NowNanos() override;
+
+ Status CorruptBuffer(const std::string& fname);
+
+ // Doesn't really sleep, just affects output of GetCurrentTime(), NowMicros()
+ // and NowNanos()
+ void FakeSleepForMicroseconds(int64_t micros);
+
+ private:
+ std::string NormalizePath(const std::string path);
+
+ // Map from filenames to MemFile objects, representing a simple file system.
+ typedef std::map<std::string, MemFile*> FileSystem;
+ port::Mutex mutex_;
+ FileSystem file_map_; // Protected by mutex_.
+
+ std::atomic<int64_t> fake_sleep_micros_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/env/mock_env_test.cc b/storage/rocksdb/rocksdb/env/mock_env_test.cc
new file mode 100644
index 00000000..7f339540
--- /dev/null
+++ b/storage/rocksdb/rocksdb/env/mock_env_test.cc
@@ -0,0 +1,85 @@
+// 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 <memory>
+#include <string>
+
+#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_(new MockEnv(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<WritableFile> 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<RandomAccessFile> 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<MockEnv*>(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<MockEnv*>(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_->FakeSleepForMicroseconds(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/examples/.gitignore b/storage/rocksdb/rocksdb/examples/.gitignore
new file mode 100644
index 00000000..823664ae
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/.gitignore
@@ -0,0 +1,9 @@
+c_simple_example
+column_families_example
+compact_files_example
+compaction_filter_example
+multi_processes_example
+optimistic_transaction_example
+options_file_example
+simple_example
+transaction_example
diff --git a/storage/rocksdb/rocksdb/examples/Makefile b/storage/rocksdb/rocksdb/examples/Makefile
new file mode 100644
index 00000000..27a6f0f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/Makefile
@@ -0,0 +1,53 @@
+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
+
+.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
+
+simple_example: librocksdb simple_example.cc
+ $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(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++11 $(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++11 $(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++11 $(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++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS)
+
+transaction_example: librocksdb transaction_example.cc
+ $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(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++11 $(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++11 $(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
+
+librocksdb:
+ cd .. && $(MAKE) static_lib
diff --git a/storage/rocksdb/rocksdb/examples/README.md b/storage/rocksdb/rocksdb/examples/README.md
new file mode 100644
index 00000000..f4ba2384
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/README.md
@@ -0,0 +1,2 @@
+1. Compile RocksDB first by executing `make static_lib` in parent dir
+2. Compile all examples: `cd examples/; make all`
diff --git a/storage/rocksdb/rocksdb/examples/c_simple_example.c b/storage/rocksdb/rocksdb/examples/c_simple_example.c
new file mode 100644
index 00000000..5564361d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/c_simple_example.c
@@ -0,0 +1,79 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "rocksdb/c.h"
+
+#include <unistd.h> // sysconf() - get CPU count
+
+const char DBPath[] = "/tmp/rocksdb_simple_example";
+const char DBBackupPath[] = "/tmp/rocksdb_simple_example_backup";
+
+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
+ long cpus = sysconf(_SC_NPROCESSORS_ONLN); // get # 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/storage/rocksdb/rocksdb/examples/column_families_example.cc b/storage/rocksdb/rocksdb/examples/column_families_example.cc
new file mode 100644
index 00000000..df936d46
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/column_families_example.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+#include <string>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/options.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+std::string kDBPath = "/tmp/rocksdb_column_families_example";
+
+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
+ delete cf;
+ delete db;
+
+ // open DB with two column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(ColumnFamilyDescriptor(
+ kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new one, too
+ column_families.push_back(ColumnFamilyDescriptor(
+ "new_cf", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> 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) {
+ delete handle;
+ }
+ delete db;
+
+ return 0;
+}
diff --git a/storage/rocksdb/rocksdb/examples/compact_files_example.cc b/storage/rocksdb/rocksdb/examples/compact_files_example.cc
new file mode 100644
index 00000000..a0a9fa90
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/compact_files_example.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <mutex>
+#include <string>
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+
+using namespace ROCKSDB_NAMESPACE;
+std::string kDBPath = "/tmp/rocksdb_compact_files_example";
+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<std::string>& _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<std::string> 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<std::string> 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<CompactionTask> task(
+ reinterpret_cast<CompactionTask*>(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 = 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;
+ 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/storage/rocksdb/rocksdb/examples/compaction_filter_example.cc b/storage/rocksdb/rocksdb/examples/compaction_filter_example.cc
new file mode 100644
index 00000000..cee76319
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/compaction_filter_example.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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;
+};
+
+int main() {
+ ROCKSDB_NAMESPACE::DB* raw_db;
+ ROCKSDB_NAMESPACE::Status status;
+
+ MyFilter filter;
+
+ int ret = system("rm -rf /tmp/rocksmergetest");
+ if (ret != 0) {
+ fprintf(stderr, "Error deleting /tmp/rocksmergetest, code: %d\n", ret);
+ return 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, "/tmp/rocksmergetest", &raw_db);
+ assert(status.ok());
+ std::unique_ptr<ROCKSDB_NAMESPACE::DB> 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/storage/rocksdb/rocksdb/examples/multi_processes_example.cc b/storage/rocksdb/rocksdb/examples/multi_processes_example.cc
new file mode 100644
index 00000000..3241e36e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/multi_processes_example.cc
@@ -0,0 +1,395 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <chrono>
+#include <cinttypes>
+#include <cstdio>
+#include <cstdlib>
+#include <ctime>
+#include <string>
+#include <thread>
+#include <vector>
+
+#if defined(OS_LINUX)
+#include <dirent.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif // !OS_LINUX
+
+#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<std::string>& GetColumnFamilyNames() {
+ static std::vector<std::string> column_family_names = {
+ ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, "pikachu"};
+ return column_family_names;
+}
+
+inline bool IsLittleEndian() {
+ uint32_t x = 1;
+ return *reinterpret_cast<char*>(&x) != 0;
+}
+
+static std::atomic<int>& ShouldSecondaryWait() {
+ static std::atomic<int> should_secondary_wait{1};
+ return should_secondary_wait;
+}
+
+static std::string Key(uint64_t k) {
+ std::string ret;
+ if (IsLittleEndian()) {
+ ret.append(reinterpret_cast<char*>(&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<uint64_t>(buf[0]);
+ ret |= (static_cast<uint64_t>(buf[1]) << 8);
+ ret |= (static_cast<uint64_t>(buf[2]) << 16);
+ ret |= (static_cast<uint64_t>(buf[3]) << 24);
+ ret |= (static_cast<uint64_t>(buf[4]) << 32);
+ ret |= (static_cast<uint64_t>(buf[5]) << 40);
+ ret |= (static_cast<uint64_t>(buf[6]) << 48);
+ ret |= (static_cast<uint64_t>(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<char>(rnd ^ i);
+ }
+ return Slice(scratch, sz);
+}
+
+static bool ShouldCloseDB() { return true; }
+
+// TODO: port this example to other systems. It should be straightforward for
+// POSIX-compliant systems.
+#if defined(OS_LINUX)
+void CreateDB() {
+ long my_pid = static_cast<long>(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<ColumnFamilyHandle*> 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<long>(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<ColumnFamilyDescriptor> column_families;
+ for (const auto& cf_name : GetColumnFamilyNames()) {
+ column_families.push_back(ColumnFamilyDescriptor(cf_name, options));
+ }
+ std::vector<ColumnFamilyHandle*> 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<uint64_t>(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<uint64_t>(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<long>(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<std::thread> test_threads;
+ test_threads.emplace_back([&]() {
+ while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) {
+ std::unique_ptr<Iterator> 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");
+ });
+
+ 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<Iterator> 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<ColumnFamilyDescriptor> column_families;
+ for (const auto& cf_name : GetColumnFamilyNames()) {
+ column_families.push_back(ColumnFamilyDescriptor(cf_name, options));
+ }
+ std::vector<ColumnFamilyHandle*> 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() {
+ fpritnf(stderr, "Not implemented.\n");
+ return 0;
+}
+#endif // !OS_LINUX
diff --git a/storage/rocksdb/rocksdb/examples/optimistic_transaction_example.cc b/storage/rocksdb/rocksdb/examples/optimistic_transaction_example.cc
new file mode 100644
index 00000000..fd6dbad6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/optimistic_transaction_example.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+std::string kDBPath = "/tmp/rocksdb_transaction_example";
+
+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;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/examples/options_file_example.cc b/storage/rocksdb/rocksdb/examples/options_file_example.cc
new file mode 100644
index 00000000..e6a1d0e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/options_file_example.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+#include <string>
+#include <vector>
+
+#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 namespace ROCKSDB_NAMESPACE;
+
+std::string kDBPath = "/tmp/rocksdb_options_file_example";
+
+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<ColumnFamilyDescriptor> cf_descs;
+ cf_descs.push_back({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<CompactionFilter> 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 = 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<ColumnFamilyDescriptor> loaded_cf_descs;
+ s = LoadLatestOptions(kDBPath, Env::Default(), &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 = reinterpret_cast<BlockBasedTableOptions*>(
+ 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<ColumnFamilyHandle*> 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/storage/rocksdb/rocksdb/examples/rocksdb_option_file_example.ini b/storage/rocksdb/rocksdb/examples/rocksdb_option_file_example.ini
new file mode 100644
index 00000000..dcbc9a30
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/rocksdb_option_file_example.ini
@@ -0,0 +1,144 @@
+# 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
+ skip_log_error_on_recovery=false
+ db_log_dir=
+ 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
+ soft_rate_limit=0.000000
+ 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
+ hash_index_allow_collision=true
+ flush_block_policy_factory=FlushBlockBySizePolicyFactory
diff --git a/storage/rocksdb/rocksdb/examples/simple_example.cc b/storage/rocksdb/rocksdb/examples/simple_example.cc
new file mode 100644
index 00000000..256c965d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/simple_example.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+#include <string>
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/options.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+std::string kDBPath = "/tmp/rocksdb_simple_example";
+
+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;
+ 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/storage/rocksdb/rocksdb/examples/transaction_example.cc b/storage/rocksdb/rocksdb/examples/transaction_example.cc
new file mode 100644
index 00000000..41b23354
--- /dev/null
+++ b/storage/rocksdb/rocksdb/examples/transaction_example.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+std::string kDBPath = "/tmp/rocksdb_transaction_example";
+
+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;
+ DestroyDB(kDBPath, options);
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/file/delete_scheduler.cc b/storage/rocksdb/rocksdb/file/delete_scheduler.cc
new file mode 100644
index 00000000..bb318e59
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/delete_scheduler.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "file/delete_scheduler.h"
+
+#include <thread>
+#include <vector>
+
+#include "file/sst_file_manager_impl.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+DeleteScheduler::DeleteScheduler(Env* env, FileSystem* fs,
+ int64_t rate_bytes_per_sec, Logger* info_log,
+ SstFileManagerImpl* sst_file_manager,
+ double max_trash_db_ratio,
+ uint64_t bytes_max_delete_chunk)
+ : env_(env),
+ fs_(fs),
+ total_trash_size_(0),
+ rate_bytes_per_sec_(rate_bytes_per_sec),
+ pending_files_(0),
+ bytes_max_delete_chunk_(bytes_max_delete_chunk),
+ closing_(false),
+ cv_(&mu_),
+ info_log_(info_log),
+ sst_file_manager_(sst_file_manager),
+ max_trash_db_ratio_(max_trash_db_ratio) {
+ assert(sst_file_manager != nullptr);
+ assert(max_trash_db_ratio >= 0);
+ bg_thread_.reset(
+ new port::Thread(&DeleteScheduler::BackgroundEmptyTrash, this));
+}
+
+DeleteScheduler::~DeleteScheduler() {
+ {
+ InstrumentedMutexLock l(&mu_);
+ closing_ = true;
+ cv_.SignalAll();
+ }
+ if (bg_thread_) {
+ bg_thread_->join();
+ }
+}
+
+Status DeleteScheduler::DeleteFile(const std::string& file_path,
+ const std::string& dir_to_sync,
+ const bool force_bg) {
+ Status s;
+ if (rate_bytes_per_sec_.load() <= 0 || (!force_bg &&
+ total_trash_size_.load() >
+ sst_file_manager_->GetTotalSize() * max_trash_db_ratio_.load())) {
+ // Rate limiting is disabled or trash size makes up more than
+ // max_trash_db_ratio_ (default 25%) of the total DB size
+ TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
+ s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
+ if (s.ok()) {
+ sst_file_manager_->OnDeleteFile(file_path);
+ }
+ return s;
+ }
+
+ // Move file to trash
+ std::string trash_file;
+ s = MarkAsTrash(file_path, &trash_file);
+
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_, "Failed to mark %s as trash -- %s",
+ file_path.c_str(), s.ToString().c_str());
+ s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
+ if (s.ok()) {
+ sst_file_manager_->OnDeleteFile(file_path);
+ }
+ return s;
+ }
+
+ // Update the total trash size
+ uint64_t trash_file_size = 0;
+ fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
+ total_trash_size_.fetch_add(trash_file_size);
+
+ // Add file to delete queue
+ {
+ InstrumentedMutexLock l(&mu_);
+ queue_.emplace(trash_file, dir_to_sync);
+ pending_files_++;
+ if (pending_files_ == 1) {
+ cv_.SignalAll();
+ }
+ }
+ return s;
+}
+
+std::map<std::string, Status> DeleteScheduler::GetBackgroundErrors() {
+ InstrumentedMutexLock l(&mu_);
+ return bg_errors_;
+}
+
+const std::string DeleteScheduler::kTrashExtension = ".trash";
+bool DeleteScheduler::IsTrashFile(const std::string& file_path) {
+ return (file_path.size() >= kTrashExtension.size() &&
+ file_path.rfind(kTrashExtension) ==
+ file_path.size() - kTrashExtension.size());
+}
+
+Status DeleteScheduler::CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
+ const std::string& path) {
+ Status s;
+ // Check if there are any files marked as trash in this path
+ std::vector<std::string> files_in_path;
+ s = env->GetChildren(path, &files_in_path);
+ if (!s.ok()) {
+ return s;
+ }
+ for (const std::string& current_file : files_in_path) {
+ if (!DeleteScheduler::IsTrashFile(current_file)) {
+ // not a trash file, skip
+ continue;
+ }
+
+ Status file_delete;
+ std::string trash_file = path + "/" + current_file;
+ if (sfm) {
+ // We have an SstFileManager that will schedule the file delete
+ sfm->OnAddFile(trash_file);
+ file_delete = sfm->ScheduleFileDeletion(trash_file, path);
+ } else {
+ // Delete the file immediately
+ file_delete = env->DeleteFile(trash_file);
+ }
+
+ if (s.ok() && !file_delete.ok()) {
+ s = file_delete;
+ }
+ }
+
+ return s;
+}
+
+Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
+ std::string* trash_file) {
+ // Sanity check of the path
+ size_t idx = file_path.rfind("/");
+ if (idx == std::string::npos || idx == file_path.size() - 1) {
+ return Status::InvalidArgument("file_path is corrupted");
+ }
+
+ Status s;
+ if (DeleteScheduler::IsTrashFile(file_path)) {
+ // This is already a trash file
+ *trash_file = file_path;
+ return s;
+ }
+
+ *trash_file = file_path + kTrashExtension;
+ // TODO(tec) : Implement Env::RenameFileIfNotExist and remove
+ // file_move_mu mutex.
+ int cnt = 0;
+ InstrumentedMutexLock l(&file_move_mu_);
+ while (true) {
+ s = fs_->FileExists(*trash_file, IOOptions(), nullptr);
+ if (s.IsNotFound()) {
+ // We found a path for our file in trash
+ s = fs_->RenameFile(file_path, *trash_file, IOOptions(), nullptr);
+ break;
+ } else if (s.ok()) {
+ // Name conflict, generate new random suffix
+ *trash_file = file_path + std::to_string(cnt) + kTrashExtension;
+ } else {
+ // Error during FileExists call, we cannot continue
+ break;
+ }
+ cnt++;
+ }
+ if (s.ok()) {
+ sst_file_manager_->OnMoveFile(file_path, *trash_file);
+ }
+ return s;
+}
+
+void DeleteScheduler::BackgroundEmptyTrash() {
+ TEST_SYNC_POINT("DeleteScheduler::BackgroundEmptyTrash");
+
+ while (true) {
+ InstrumentedMutexLock l(&mu_);
+ while (queue_.empty() && !closing_) {
+ cv_.Wait();
+ }
+
+ if (closing_) {
+ return;
+ }
+
+ // Delete all files in queue_
+ uint64_t start_time = env_->NowMicros();
+ uint64_t total_deleted_bytes = 0;
+ int64_t current_delete_rate = rate_bytes_per_sec_.load();
+ while (!queue_.empty() && !closing_) {
+ if (current_delete_rate != rate_bytes_per_sec_.load()) {
+ // User changed the delete rate
+ current_delete_rate = rate_bytes_per_sec_.load();
+ start_time = env_->NowMicros();
+ total_deleted_bytes = 0;
+ }
+
+ // Get new file to delete
+ const FileAndDir& fad = queue_.front();
+ std::string path_in_trash = fad.fname;
+
+ // We dont need to hold the lock while deleting the file
+ mu_.Unlock();
+ uint64_t deleted_bytes = 0;
+ bool is_complete = true;
+ // Delete file from trash and update total_penlty value
+ Status s =
+ DeleteTrashFile(path_in_trash, fad.dir, &deleted_bytes, &is_complete);
+ total_deleted_bytes += deleted_bytes;
+ mu_.Lock();
+ if (is_complete) {
+ queue_.pop();
+ }
+
+ if (!s.ok()) {
+ bg_errors_[path_in_trash] = s;
+ }
+
+ // Apply penlty if necessary
+ uint64_t total_penlty;
+ if (current_delete_rate > 0) {
+ // rate limiting is enabled
+ total_penlty =
+ ((total_deleted_bytes * kMicrosInSecond) / current_delete_rate);
+ while (!closing_ && !cv_.TimedWait(start_time + total_penlty)) {}
+ } else {
+ // rate limiting is disabled
+ total_penlty = 0;
+ }
+ TEST_SYNC_POINT_CALLBACK("DeleteScheduler::BackgroundEmptyTrash:Wait",
+ &total_penlty);
+
+ if (is_complete) {
+ pending_files_--;
+ }
+ if (pending_files_ == 0) {
+ // Unblock WaitForEmptyTrash since there are no more files waiting
+ // to be deleted
+ cv_.SignalAll();
+ }
+ }
+ }
+}
+
+Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
+ const std::string& dir_to_sync,
+ uint64_t* deleted_bytes,
+ bool* is_complete) {
+ uint64_t file_size;
+ Status s = fs_->GetFileSize(path_in_trash, IOOptions(), &file_size, nullptr);
+ *is_complete = true;
+ TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:DeleteFile");
+ if (s.ok()) {
+ bool need_full_delete = true;
+ if (bytes_max_delete_chunk_ != 0 && file_size > bytes_max_delete_chunk_) {
+ uint64_t num_hard_links = 2;
+ // We don't have to worry aobut data race between linking a new
+ // file after the number of file link check and ftruncte because
+ // the file is now in trash and no hardlink is supposed to create
+ // to trash files by RocksDB.
+ Status my_status = fs_->NumFileLinks(path_in_trash, IOOptions(),
+ &num_hard_links, nullptr);
+ if (my_status.ok()) {
+ if (num_hard_links == 1) {
+ std::unique_ptr<FSWritableFile> wf;
+ my_status = fs_->ReopenWritableFile(path_in_trash, FileOptions(),
+ &wf, nullptr);
+ if (my_status.ok()) {
+ my_status = wf->Truncate(file_size - bytes_max_delete_chunk_,
+ IOOptions(), nullptr);
+ if (my_status.ok()) {
+ TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:Fsync");
+ my_status = wf->Fsync(IOOptions(), nullptr);
+ }
+ }
+ if (my_status.ok()) {
+ *deleted_bytes = bytes_max_delete_chunk_;
+ need_full_delete = false;
+ *is_complete = false;
+ } else {
+ ROCKS_LOG_WARN(info_log_,
+ "Failed to partially delete %s from trash -- %s",
+ path_in_trash.c_str(), my_status.ToString().c_str());
+ }
+ } else {
+ ROCKS_LOG_INFO(info_log_,
+ "Cannot delete %s slowly through ftruncate from trash "
+ "as it has other links",
+ path_in_trash.c_str());
+ }
+ } else if (!num_link_error_printed_) {
+ ROCKS_LOG_INFO(
+ info_log_,
+ "Cannot delete files slowly through ftruncate from trash "
+ "as Env::NumFileLinks() returns error: %s",
+ my_status.ToString().c_str());
+ num_link_error_printed_ = true;
+ }
+ }
+
+ if (need_full_delete) {
+ s = fs_->DeleteFile(path_in_trash, IOOptions(), nullptr);
+ if (!dir_to_sync.empty()) {
+ std::unique_ptr<FSDirectory> dir_obj;
+ if (s.ok()) {
+ s = fs_->NewDirectory(dir_to_sync, IOOptions(), &dir_obj, nullptr);
+ }
+ if (s.ok()) {
+ s = dir_obj->Fsync(IOOptions(), nullptr);
+ TEST_SYNC_POINT_CALLBACK(
+ "DeleteScheduler::DeleteTrashFile::AfterSyncDir",
+ reinterpret_cast<void*>(const_cast<std::string*>(&dir_to_sync)));
+ }
+ }
+ *deleted_bytes = file_size;
+ sst_file_manager_->OnDeleteFile(path_in_trash);
+ }
+ }
+ if (!s.ok()) {
+ // Error while getting file size or while deleting
+ ROCKS_LOG_ERROR(info_log_, "Failed to delete %s from trash -- %s",
+ path_in_trash.c_str(), s.ToString().c_str());
+ *deleted_bytes = 0;
+ } else {
+ total_trash_size_.fetch_sub(*deleted_bytes);
+ }
+
+ return s;
+}
+
+void DeleteScheduler::WaitForEmptyTrash() {
+ InstrumentedMutexLock l(&mu_);
+ while (pending_files_ > 0 && !closing_) {
+ cv_.Wait();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/file/delete_scheduler.h b/storage/rocksdb/rocksdb/file/delete_scheduler.h
new file mode 100644
index 00000000..60948480
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/delete_scheduler.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <map>
+#include <queue>
+#include <string>
+#include <thread>
+
+#include "monitoring/instrumented_mutex.h"
+#include "port/port.h"
+
+#include "rocksdb/file_system.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Logger;
+class SstFileManagerImpl;
+
+// DeleteScheduler allows the DB to enforce a rate limit on file deletion,
+// Instead of deleteing files immediately, files are marked as trash
+// and deleted in a background thread that apply sleep penlty between deletes
+// if they are happening in a rate faster than rate_bytes_per_sec,
+//
+// Rate limiting can be turned off by setting rate_bytes_per_sec = 0, In this
+// case DeleteScheduler will delete files immediately.
+class DeleteScheduler {
+ public:
+ DeleteScheduler(Env* env, FileSystem* fs, int64_t rate_bytes_per_sec,
+ Logger* info_log, SstFileManagerImpl* sst_file_manager,
+ double max_trash_db_ratio, uint64_t bytes_max_delete_chunk);
+
+ ~DeleteScheduler();
+
+ // Return delete rate limit in bytes per second
+ int64_t GetRateBytesPerSecond() { return rate_bytes_per_sec_.load(); }
+
+ // Set delete rate limit in bytes per second
+ void SetRateBytesPerSecond(int64_t bytes_per_sec) {
+ rate_bytes_per_sec_.store(bytes_per_sec);
+ }
+
+ // Mark file as trash directory and schedule it's deletion. If force_bg is
+ // set, it forces the file to always be deleted in the background thread,
+ // except when rate limiting is disabled
+ Status DeleteFile(const std::string& fname, const std::string& dir_to_sync,
+ const bool force_bg = false);
+
+ // Wait for all files being deleteing in the background to finish or for
+ // destructor to be called.
+ void WaitForEmptyTrash();
+
+ // Return a map containing errors that happened in BackgroundEmptyTrash
+ // file_path => error status
+ std::map<std::string, Status> GetBackgroundErrors();
+
+ uint64_t GetTotalTrashSize() { return total_trash_size_.load(); }
+
+ // Return trash/DB size ratio where new files will be deleted immediately
+ double GetMaxTrashDBRatio() {
+ return max_trash_db_ratio_.load();
+ }
+
+ // Update trash/DB size ratio where new files will be deleted immediately
+ void SetMaxTrashDBRatio(double r) {
+ assert(r >= 0);
+ max_trash_db_ratio_.store(r);
+ }
+
+ static const std::string kTrashExtension;
+ static bool IsTrashFile(const std::string& file_path);
+
+ // Check if there are any .trash filse in path, and schedule their deletion
+ // Or delete immediately if sst_file_manager is nullptr
+ static Status CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
+ const std::string& path);
+
+ private:
+ Status MarkAsTrash(const std::string& file_path, std::string* path_in_trash);
+
+ Status DeleteTrashFile(const std::string& path_in_trash,
+ const std::string& dir_to_sync,
+ uint64_t* deleted_bytes, bool* is_complete);
+
+ void BackgroundEmptyTrash();
+
+ Env* env_;
+ FileSystem* fs_;
+
+ // total size of trash files
+ std::atomic<uint64_t> total_trash_size_;
+ // Maximum number of bytes that should be deleted per second
+ std::atomic<int64_t> rate_bytes_per_sec_;
+ // Mutex to protect queue_, pending_files_, bg_errors_, closing_
+ InstrumentedMutex mu_;
+
+ struct FileAndDir {
+ FileAndDir(const std::string& f, const std::string& d) : fname(f), dir(d) {}
+ std::string fname;
+ std::string dir; // empty will be skipped.
+ };
+
+ // Queue of trash files that need to be deleted
+ std::queue<FileAndDir> queue_;
+ // Number of trash files that are waiting to be deleted
+ int32_t pending_files_;
+ uint64_t bytes_max_delete_chunk_;
+ // Errors that happened in BackgroundEmptyTrash (file_path => error)
+ std::map<std::string, Status> bg_errors_;
+
+ bool num_link_error_printed_ = false;
+ // Set to true in ~DeleteScheduler() to force BackgroundEmptyTrash to stop
+ bool closing_;
+ // Condition variable signaled in these conditions
+ // - pending_files_ value change from 0 => 1
+ // - pending_files_ value change from 1 => 0
+ // - closing_ value is set to true
+ InstrumentedCondVar cv_;
+ // Background thread running BackgroundEmptyTrash
+ std::unique_ptr<port::Thread> bg_thread_;
+ // Mutex to protect threads from file name conflicts
+ InstrumentedMutex file_move_mu_;
+ Logger* info_log_;
+ SstFileManagerImpl* sst_file_manager_;
+ // 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
+ std::atomic<double> max_trash_db_ratio_;
+ static const uint64_t kMicrosInSecond = 1000 * 1000LL;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/file/delete_scheduler_test.cc b/storage/rocksdb/rocksdb/file/delete_scheduler_test.cc
new file mode 100644
index 00000000..cff645de
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/delete_scheduler_test.cc
@@ -0,0 +1,693 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cinttypes>
+#include <thread>
+#include <vector>
+
+#include "file/delete_scheduler.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 "test_util/testutil.h"
+#include "util/string_util.h"
+
+#ifndef ROCKSDB_LITE
+
+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") +
+ ToString(i));
+ DestroyAndCreateDir(dummy_files_dirs_.back());
+ }
+ }
+
+ ~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_) {
+ test::DestroyDir(env_, dummy_files_dir);
+ }
+ }
+
+ void DestroyAndCreateDir(const std::string& dir) {
+ ASSERT_OK(test::DestroyDir(env_, dir));
+ EXPECT_OK(env_->CreateDir(dir));
+ }
+
+ int CountNormalFiles(size_t dummy_files_dirs_idx = 0) {
+ std::vector<std::string> 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) && f != "." && f != "..") {
+ normal_cnt++;
+ }
+ }
+ return normal_cnt;
+ }
+
+ int CountTrashFiles(size_t dummy_files_dirs_idx = 0) {
+ std::vector<std::string> 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<WritableFile> 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, false);
+ return file_path;
+ }
+
+ void NewDeleteScheduler() {
+ // Tests in this file are for DeleteScheduler component and dont create any
+ // DBs, so we need to set max_trash_db_ratio to 100% (instead of default
+ // 25%)
+ std::shared_ptr<FileSystem>
+ fs(std::make_shared<LegacyFileSystemWrapper>(env_));
+ sst_file_mgr_.reset(
+ new SstFileManagerImpl(env_, fs, nullptr, rate_bytes_per_sec_,
+ /* max_trash_db_ratio= */ 1.1, 128 * 1024));
+ delete_scheduler_ = sst_file_mgr_->delete_scheduler();
+ }
+
+ Env* env_;
+ std::vector<std::string> dummy_files_dirs_;
+ int64_t rate_bytes_per_sec_;
+ DeleteScheduler* delete_scheduler_;
+ std::unique_ptr<SstFileManagerImpl> sst_file_mgr_;
+};
+
+// 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<uint64_t> penalties;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DeleteScheduler::BackgroundEmptyTrash:Wait",
+ [&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(arg))); });
+ int dir_synced = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DeleteScheduler::DeleteTrashFile::AfterSyncDir", [&](void* arg) {
+ dir_synced++;
+ std::string* dir = reinterpret_cast<std::string*>(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<uint64_t> 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<std::string> generated_files;
+ for (int i = 0; i < num_files; i++) {
+ std::string file_name = "file" + ToString(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);
+ 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<std::string> 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));
+ }
+
+ 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<uint64_t> penalties;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DeleteScheduler::BackgroundEmptyTrash:Wait",
+ [&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(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<uint64_t> 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<std::string> generated_files;
+ for (int i = 0; i < num_files * thread_cnt; i++) {
+ std::string file_name = "file" + ToString(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<int> thread_num(0);
+ std::vector<port::Thread> threads;
+ std::function<void()> 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);
+ 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 dont
+// 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();
+
+ for (int i = 0; i < 10; 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);
+
+ 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);
+
+ 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_" + ToString(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_" + ToString(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);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+// 1- Create 10 dummy files
+// 2- Delete 10 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) {
+ 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();
+
+ // Move files to trash, wait for empty trash, start again
+ for (int run = 1; run <= 5; run++) {
+ // Generate 10 dummy files and move them to trash
+ for (int i = 0; i < 10; i++) {
+ std::string file_name = "data_" + ToString(i) + ".data";
+ ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name), ""));
+ }
+ ASSERT_EQ(CountNormalFiles(), 0);
+ delete_scheduler_->WaitForEmptyTrash();
+ ASSERT_EQ(bg_delete_file, 10 * run);
+ ASSERT_EQ(CountTrashFiles(), 0);
+
+ auto bg_errors = delete_scheduler_->GetBackgroundErrors();
+ ASSERT_EQ(bg_errors.size(), 0);
+ }
+
+ ASSERT_EQ(bg_delete_file, 50);
+ 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_" + ToString(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<uint64_t> 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<int*>(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<int64_t> 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<std::string> generated_files;
+ for (int i = 0; i < num_files; i++) {
+ std::string file_name = "file" + ToString(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<std::string> generated_files;
+ for (int i = 0; i < num_files; i++) {
+ std::string file_name = "file" + ToString(i) + ".data";
+ generated_files.push_back(NewDummyFile(file_name, file_size));
+ }
+
+ for (std::string& file_name : generated_files) {
+ 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);
+
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+int main(int /*argc*/, char** /*argv*/) {
+ printf("DeleteScheduler is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/file/file_prefetch_buffer.cc b/storage/rocksdb/rocksdb/file/file_prefetch_buffer.cc
new file mode 100644
index 00000000..7b55bd39
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/file_prefetch_buffer.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/file_prefetch_buffer.h"
+
+#include <algorithm>
+#include <mutex>
+
+#include "file/random_access_file_reader.h"
+#include "monitoring/histogram.h"
+#include "monitoring/iostats_context_imp.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+Status FilePrefetchBuffer::Prefetch(RandomAccessFileReader* reader,
+ uint64_t offset, size_t n,
+ bool for_compaction) {
+ if (!enable_ || reader == nullptr) {
+ return Status::OK();
+ }
+ size_t alignment = reader->file()->GetRequiredBufferAlignment();
+ size_t offset_ = static_cast<size_t>(offset);
+ uint64_t rounddown_offset = Rounddown(offset_, alignment);
+ uint64_t roundup_end = Roundup(offset_ + n, alignment);
+ uint64_t roundup_len = roundup_end - rounddown_offset;
+ assert(roundup_len >= alignment);
+ assert(roundup_len % alignment == 0);
+
+ // Check if requested bytes are in the existing buffer_.
+ // If all bytes exist -- return.
+ // If only a few bytes exist -- reuse them & read only what is really needed.
+ // This is typically the case of incremental reading of data.
+ // If no bytes exist in buffer -- full pread.
+
+ Status s;
+ uint64_t chunk_offset_in_buffer = 0;
+ uint64_t chunk_len = 0;
+ bool copy_data_to_new_buffer = false;
+ if (buffer_.CurrentSize() > 0 && offset >= buffer_offset_ &&
+ offset <= buffer_offset_ + buffer_.CurrentSize()) {
+ if (offset + n <= buffer_offset_ + buffer_.CurrentSize()) {
+ // All requested bytes are already in the buffer. So no need to Read
+ // again.
+ return s;
+ } else {
+ // Only a few requested bytes are in the buffer. memmove those chunk of
+ // bytes to the beginning, and memcpy them back into the new buffer if a
+ // new buffer is created.
+ chunk_offset_in_buffer =
+ Rounddown(static_cast<size_t>(offset - buffer_offset_), alignment);
+ chunk_len = buffer_.CurrentSize() - chunk_offset_in_buffer;
+ assert(chunk_offset_in_buffer % alignment == 0);
+ assert(chunk_len % alignment == 0);
+ assert(chunk_offset_in_buffer + chunk_len <=
+ buffer_offset_ + buffer_.CurrentSize());
+ if (chunk_len > 0) {
+ copy_data_to_new_buffer = true;
+ } else {
+ // this reset is not necessary, but just to be safe.
+ chunk_offset_in_buffer = 0;
+ }
+ }
+ }
+
+ // Create a new buffer only if current capacity is not sufficient, and memcopy
+ // bytes from old buffer if needed (i.e., if chunk_len is greater than 0).
+ if (buffer_.Capacity() < roundup_len) {
+ buffer_.Alignment(alignment);
+ buffer_.AllocateNewBuffer(static_cast<size_t>(roundup_len),
+ copy_data_to_new_buffer, chunk_offset_in_buffer,
+ static_cast<size_t>(chunk_len));
+ } else if (chunk_len > 0) {
+ // New buffer not needed. But memmove bytes from tail to the beginning since
+ // chunk_len is greater than 0.
+ buffer_.RefitTail(static_cast<size_t>(chunk_offset_in_buffer),
+ static_cast<size_t>(chunk_len));
+ }
+
+ Slice result;
+ s = reader->Read(rounddown_offset + chunk_len,
+ static_cast<size_t>(roundup_len - chunk_len), &result,
+ buffer_.BufferStart() + chunk_len, for_compaction);
+ if (s.ok()) {
+ buffer_offset_ = rounddown_offset;
+ buffer_.Size(static_cast<size_t>(chunk_len) + result.size());
+ }
+ return s;
+}
+
+bool FilePrefetchBuffer::TryReadFromCache(uint64_t offset, size_t n,
+ Slice* result, bool for_compaction) {
+ if (track_min_offset_ && offset < min_offset_read_) {
+ min_offset_read_ = static_cast<size_t>(offset);
+ }
+ if (!enable_ || offset < buffer_offset_) {
+ return false;
+ }
+
+ // If the buffer contains only a few of the requested bytes:
+ // If readahead is enabled: prefetch the remaining bytes + readadhead bytes
+ // and satisfy the request.
+ // If readahead is not enabled: return false.
+ if (offset + n > buffer_offset_ + buffer_.CurrentSize()) {
+ if (readahead_size_ > 0) {
+ assert(file_reader_ != nullptr);
+ assert(max_readahead_size_ >= readahead_size_);
+ Status s;
+ if (for_compaction) {
+ s = Prefetch(file_reader_, offset, std::max(n, readahead_size_),
+ for_compaction);
+ } else {
+ s = Prefetch(file_reader_, offset, n + readahead_size_, for_compaction);
+ }
+ if (!s.ok()) {
+ return false;
+ }
+ readahead_size_ = std::min(max_readahead_size_, readahead_size_ * 2);
+ } else {
+ return false;
+ }
+ }
+
+ uint64_t offset_in_buffer = offset - buffer_offset_;
+ *result = Slice(buffer_.BufferStart() + offset_in_buffer, n);
+ return true;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/file_prefetch_buffer.h b/storage/rocksdb/rocksdb/file/file_prefetch_buffer.h
new file mode 100644
index 00000000..d53f627b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/file_prefetch_buffer.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <sstream>
+#include <string>
+#include "file/random_access_file_reader.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "util/aligned_buffer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// FilePrefetchBuffer is a smart buffer to store and read data from a file.
+class FilePrefetchBuffer {
+ public:
+ // Constructor.
+ //
+ // All arguments are optional.
+ // file_reader : the file reader to use. Can be a nullptr.
+ // readahead_size : the initial readahead size.
+ // max_readahead_size : the maximum readahead size.
+ // If max_readahead_size > readahead_size, the readahead size will be
+ // doubled on every IO until max_readahead_size is hit.
+ // Typically this is set as a multiple of readahead_size.
+ // max_readahead_size should be greater than equal to readahead_size.
+ // enable : controls whether reading from the buffer is enabled.
+ // If false, TryReadFromCache() always return false, and we only take stats
+ // for the minimum offset if track_min_offset = true.
+ // track_min_offset : Track the minimum offset ever read and collect stats on
+ // it. Used for adaptable readahead of the file footer/metadata.
+ //
+ // Automatic readhead is enabled for a file if file_reader, readahead_size,
+ // and max_readahead_size are passed in.
+ // If file_reader is a nullptr, setting readadhead_size and max_readahead_size
+ // does not make any sense. So it does nothing.
+ // A user can construct a FilePrefetchBuffer without any arguments, but use
+ // `Prefetch` to load data into the buffer.
+ FilePrefetchBuffer(RandomAccessFileReader* file_reader = nullptr,
+ size_t readadhead_size = 0, size_t max_readahead_size = 0,
+ bool enable = true, bool track_min_offset = false)
+ : buffer_offset_(0),
+ file_reader_(file_reader),
+ readahead_size_(readadhead_size),
+ max_readahead_size_(max_readahead_size),
+ min_offset_read_(port::kMaxSizet),
+ enable_(enable),
+ track_min_offset_(track_min_offset) {}
+
+ // Load data into the buffer from a file.
+ // reader : the file reader.
+ // offset : the file offset to start reading from.
+ // n : the number of bytes to read.
+ // for_compaction : if prefetch is done for compaction read.
+ Status Prefetch(RandomAccessFileReader* reader, uint64_t offset, size_t n,
+ bool for_compaction = false);
+
+ // Tries returning the data for a file raed from this buffer, if that data is
+ // in the buffer.
+ // It handles tracking the minimum read offset if track_min_offset = true.
+ // It also does the exponential readahead when readadhead_size is set as part
+ // of the constructor.
+ //
+ // offset : the file offset.
+ // n : the number of bytes.
+ // result : output buffer to put the data into.
+ // for_compaction : if cache read is done for compaction read.
+ bool TryReadFromCache(uint64_t offset, size_t n, Slice* result,
+ bool for_compaction = false);
+
+ // The minimum `offset` ever passed to TryReadFromCache(). This will nly be
+ // tracked if track_min_offset = true.
+ size_t min_offset_read() const { return min_offset_read_; }
+
+ private:
+ AlignedBuffer buffer_;
+ uint64_t buffer_offset_;
+ RandomAccessFileReader* file_reader_;
+ size_t readahead_size_;
+ size_t max_readahead_size_;
+ // The minimum `offset` ever passed to TryReadFromCache().
+ size_t min_offset_read_;
+ // if false, TryReadFromCache() always return false, and we only take stats
+ // for track_min_offset_ if track_min_offset_ = true
+ bool enable_;
+ // If true, track minimum `offset` ever passed to TryReadFromCache(), which
+ // can be fetched from min_offset_read().
+ bool track_min_offset_;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/file_util.cc b/storage/rocksdb/rocksdb/file/file_util.cc
new file mode 100644
index 00000000..c231bf7d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/file_util.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/file_util.h"
+
+#include <string>
+#include <algorithm>
+
+#include "file/random_access_file_reader.h"
+#include "file/sequence_file_reader.h"
+#include "file/sst_file_manager_impl.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Utility function to copy a file up to a specified length
+Status CopyFile(FileSystem* fs, const std::string& source,
+ const std::string& destination, uint64_t size, bool use_fsync) {
+ const FileOptions soptions;
+ Status s;
+ std::unique_ptr<SequentialFileReader> src_reader;
+ std::unique_ptr<WritableFileWriter> dest_writer;
+
+ {
+ std::unique_ptr<FSSequentialFile> srcfile;
+ s = fs->NewSequentialFile(source, soptions, &srcfile, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ std::unique_ptr<FSWritableFile> destfile;
+ s = fs->NewWritableFile(destination, soptions, &destfile, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (size == 0) {
+ // default argument means copy everything
+ s = fs->GetFileSize(source, IOOptions(), &size, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ src_reader.reset(new SequentialFileReader(std::move(srcfile), source));
+ dest_writer.reset(
+ new WritableFileWriter(std::move(destfile), destination, soptions));
+ }
+
+ char buffer[4096];
+ Slice slice;
+ while (size > 0) {
+ size_t bytes_to_read = std::min(sizeof(buffer), static_cast<size_t>(size));
+ s = src_reader->Read(bytes_to_read, &slice, buffer);
+ if (!s.ok()) {
+ return s;
+ }
+ if (slice.size() == 0) {
+ return Status::Corruption("file too small");
+ }
+ s = dest_writer->Append(slice);
+ if (!s.ok()) {
+ return s;
+ }
+ size -= slice.size();
+ }
+ return dest_writer->Sync(use_fsync);
+}
+
+// Utility function to create a file with the provided contents
+Status CreateFile(FileSystem* fs, const std::string& destination,
+ const std::string& contents, bool use_fsync) {
+ const EnvOptions soptions;
+ Status s;
+ std::unique_ptr<WritableFileWriter> dest_writer;
+
+ std::unique_ptr<FSWritableFile> destfile;
+ s = fs->NewWritableFile(destination, soptions, &destfile, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ dest_writer.reset(
+ new WritableFileWriter(std::move(destfile), destination, soptions));
+ s = dest_writer->Append(Slice(contents));
+ if (!s.ok()) {
+ return s;
+ }
+ return dest_writer->Sync(use_fsync);
+}
+
+Status DeleteDBFile(const ImmutableDBOptions* db_options,
+ const std::string& fname, const std::string& dir_to_sync,
+ const bool force_bg, const bool force_fg) {
+#ifndef ROCKSDB_LITE
+ SstFileManagerImpl* sfm =
+ static_cast<SstFileManagerImpl*>(db_options->sst_file_manager.get());
+ if (sfm && !force_fg) {
+ return sfm->ScheduleFileDeletion(fname, dir_to_sync, force_bg);
+ } else {
+ return db_options->env->DeleteFile(fname);
+ }
+#else
+ (void)dir_to_sync;
+ (void)force_bg;
+ (void)force_fg;
+ // SstFileManager is not supported in ROCKSDB_LITE
+ // Delete file immediately
+ return db_options->env->DeleteFile(fname);
+#endif
+}
+
+bool IsWalDirSameAsDBPath(const ImmutableDBOptions* db_options) {
+ bool same = false;
+ assert(!db_options->db_paths.empty());
+ Status s = db_options->env->AreFilesSame(db_options->wal_dir,
+ db_options->db_paths[0].path, &same);
+ if (s.IsNotSupported()) {
+ same = db_options->wal_dir == db_options->db_paths[0].path;
+ }
+ return same;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/file_util.h b/storage/rocksdb/rocksdb/file/file_util.h
new file mode 100644
index 00000000..333749e4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/file_util.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "file/filename.h"
+#include "options/db_options.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+// use_fsync maps to options.use_fsync, which determines the way that
+// the file is synced after copying.
+extern Status CopyFile(FileSystem* fs, const std::string& source,
+ const std::string& destination, uint64_t size,
+ bool use_fsync);
+
+extern Status CreateFile(FileSystem* fs, const std::string& destination,
+ const std::string& contents, bool use_fsync);
+
+extern Status DeleteDBFile(const ImmutableDBOptions* db_options,
+ const std::string& fname,
+ const std::string& path_to_sync, const bool force_bg,
+ const bool force_fg);
+
+extern bool IsWalDirSameAsDBPath(const ImmutableDBOptions* db_options);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/filename.cc b/storage/rocksdb/rocksdb/file/filename.cc
new file mode 100644
index 00000000..d620b735
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/filename.cc
@@ -0,0 +1,456 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <vector>
+#include "file/writable_file_writer.h"
+#include "logging/logging.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static const std::string kRocksDbTFileExt = "sst";
+static const std::string kLevelDbTFileExt = "ldb";
+static const std::string kRocksDBBlobFileExt = "blob";
+
+// Given a path, flatten the path name by replacing all chars not in
+// {[0-9,a-z,A-Z,-,_,.]} with _. And append '_LOG\0' at the end.
+// Return the number of chars stored in dest not including the trailing '\0'.
+static size_t GetInfoLogPrefix(const std::string& path, char* dest, int len) {
+ const char suffix[] = "_LOG";
+
+ size_t write_idx = 0;
+ size_t i = 0;
+ size_t src_len = path.size();
+
+ while (i < src_len && write_idx < len - sizeof(suffix)) {
+ if ((path[i] >= 'a' && path[i] <= 'z') ||
+ (path[i] >= '0' && path[i] <= '9') ||
+ (path[i] >= 'A' && path[i] <= 'Z') ||
+ path[i] == '-' ||
+ path[i] == '.' ||
+ path[i] == '_'){
+ dest[write_idx++] = path[i];
+ } else {
+ if (i > 0) {
+ dest[write_idx++] = '_';
+ }
+ }
+ i++;
+ }
+ assert(sizeof(suffix) <= len - write_idx);
+ // "\0" is automatically added by snprintf
+ snprintf(dest + write_idx, len - write_idx, suffix);
+ write_idx += sizeof(suffix) - 1;
+ return write_idx;
+}
+
+static std::string MakeFileName(uint64_t number, const char* suffix) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%06llu.%s",
+ static_cast<unsigned long long>(number), suffix);
+ return buf;
+}
+
+static std::string MakeFileName(const std::string& name, uint64_t number,
+ const char* suffix) {
+ return name + "/" + MakeFileName(number, suffix);
+}
+
+std::string LogFileName(const std::string& name, uint64_t number) {
+ assert(number > 0);
+ return MakeFileName(name, number, "log");
+}
+
+std::string LogFileName(uint64_t number) {
+ assert(number > 0);
+ return MakeFileName(number, "log");
+}
+
+std::string BlobFileName(const std::string& blobdirname, uint64_t number) {
+ assert(number > 0);
+ return MakeFileName(blobdirname, number, kRocksDBBlobFileExt.c_str());
+}
+
+std::string BlobFileName(const std::string& dbname, const std::string& blob_dir,
+ uint64_t number) {
+ assert(number > 0);
+ return MakeFileName(dbname + "/" + blob_dir, number,
+ kRocksDBBlobFileExt.c_str());
+}
+
+std::string ArchivalDirectory(const std::string& dir) {
+ return dir + "/" + ARCHIVAL_DIR;
+}
+std::string ArchivedLogFileName(const std::string& name, uint64_t number) {
+ assert(number > 0);
+ return MakeFileName(name + "/" + ARCHIVAL_DIR, number, "log");
+}
+
+std::string MakeTableFileName(const std::string& path, uint64_t number) {
+ return MakeFileName(path, number, kRocksDbTFileExt.c_str());
+}
+
+std::string MakeTableFileName(uint64_t number) {
+ return MakeFileName(number, kRocksDbTFileExt.c_str());
+}
+
+std::string Rocks2LevelTableFileName(const std::string& fullname) {
+ assert(fullname.size() > kRocksDbTFileExt.size() + 1);
+ if (fullname.size() <= kRocksDbTFileExt.size() + 1) {
+ return "";
+ }
+ return fullname.substr(0, fullname.size() - kRocksDbTFileExt.size()) +
+ kLevelDbTFileExt;
+}
+
+uint64_t TableFileNameToNumber(const std::string& name) {
+ uint64_t number = 0;
+ uint64_t base = 1;
+ int pos = static_cast<int>(name.find_last_of('.'));
+ while (--pos >= 0 && name[pos] >= '0' && name[pos] <= '9') {
+ number += (name[pos] - '0') * base;
+ base *= 10;
+ }
+ return number;
+}
+
+std::string TableFileName(const std::vector<DbPath>& db_paths, uint64_t number,
+ uint32_t path_id) {
+ assert(number > 0);
+ std::string path;
+ if (path_id >= db_paths.size()) {
+ path = db_paths.back().path;
+ } else {
+ path = db_paths[path_id].path;
+ }
+ return MakeTableFileName(path, number);
+}
+
+void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
+ size_t out_buf_size) {
+ if (path_id == 0) {
+ snprintf(out_buf, out_buf_size, "%" PRIu64, number);
+ } else {
+ snprintf(out_buf, out_buf_size, "%" PRIu64
+ "(path "
+ "%" PRIu32 ")",
+ number, path_id);
+ }
+}
+
+std::string DescriptorFileName(const std::string& dbname, uint64_t number) {
+ assert(number > 0);
+ char buf[100];
+ snprintf(buf, sizeof(buf), "/MANIFEST-%06llu",
+ static_cast<unsigned long long>(number));
+ return dbname + buf;
+}
+
+std::string CurrentFileName(const std::string& dbname) {
+ return dbname + "/CURRENT";
+}
+
+std::string LockFileName(const std::string& dbname) {
+ return dbname + "/LOCK";
+}
+
+std::string TempFileName(const std::string& dbname, uint64_t number) {
+ return MakeFileName(dbname, number, kTempFileNameSuffix.c_str());
+}
+
+InfoLogPrefix::InfoLogPrefix(bool has_log_dir,
+ const std::string& db_absolute_path) {
+ if (!has_log_dir) {
+ const char kInfoLogPrefix[] = "LOG";
+ // "\0" is automatically added to the end
+ snprintf(buf, sizeof(buf), kInfoLogPrefix);
+ prefix = Slice(buf, sizeof(kInfoLogPrefix) - 1);
+ } else {
+ size_t len = GetInfoLogPrefix(db_absolute_path, buf, sizeof(buf));
+ prefix = Slice(buf, len);
+ }
+}
+
+std::string InfoLogFileName(const std::string& dbname,
+ const std::string& db_path, const std::string& log_dir) {
+ if (log_dir.empty()) {
+ return dbname + "/LOG";
+ }
+
+ InfoLogPrefix info_log_prefix(true, db_path);
+ return log_dir + "/" + info_log_prefix.buf;
+}
+
+// Return the name of the old info log file for "dbname".
+std::string OldInfoLogFileName(const std::string& dbname, uint64_t ts,
+ const std::string& db_path, const std::string& log_dir) {
+ char buf[50];
+ snprintf(buf, sizeof(buf), "%llu", static_cast<unsigned long long>(ts));
+
+ if (log_dir.empty()) {
+ return dbname + "/LOG.old." + buf;
+ }
+
+ InfoLogPrefix info_log_prefix(true, db_path);
+ return log_dir + "/" + info_log_prefix.buf + ".old." + buf;
+}
+
+std::string OptionsFileName(const std::string& dbname, uint64_t file_num) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "%s%06" PRIu64,
+ kOptionsFileNamePrefix.c_str(), file_num);
+ return dbname + "/" + buffer;
+}
+
+std::string TempOptionsFileName(const std::string& dbname, uint64_t file_num) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "%s%06" PRIu64 ".%s",
+ kOptionsFileNamePrefix.c_str(), file_num,
+ kTempFileNameSuffix.c_str());
+ return dbname + "/" + buffer;
+}
+
+std::string MetaDatabaseName(const std::string& dbname, uint64_t number) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "/METADB-%llu",
+ static_cast<unsigned long long>(number));
+ return dbname + buf;
+}
+
+std::string IdentityFileName(const std::string& dbname) {
+ return dbname + "/IDENTITY";
+}
+
+// Owned filenames have the form:
+// dbname/IDENTITY
+// dbname/CURRENT
+// dbname/LOCK
+// dbname/<info_log_name_prefix>
+// dbname/<info_log_name_prefix>.old.[0-9]+
+// dbname/MANIFEST-[0-9]+
+// dbname/[0-9]+.(log|sst|blob)
+// dbname/METADB-[0-9]+
+// dbname/OPTIONS-[0-9]+
+// dbname/OPTIONS-[0-9]+.dbtmp
+// Disregards / at the beginning
+bool ParseFileName(const std::string& fname,
+ uint64_t* number,
+ FileType* type,
+ WalFileType* log_type) {
+ return ParseFileName(fname, number, "", type, log_type);
+}
+
+bool ParseFileName(const std::string& fname, uint64_t* number,
+ const Slice& info_log_name_prefix, FileType* type,
+ WalFileType* log_type) {
+ Slice rest(fname);
+ if (fname.length() > 1 && fname[0] == '/') {
+ rest.remove_prefix(1);
+ }
+ if (rest == "IDENTITY") {
+ *number = 0;
+ *type = kIdentityFile;
+ } else if (rest == "CURRENT") {
+ *number = 0;
+ *type = kCurrentFile;
+ } else if (rest == "LOCK") {
+ *number = 0;
+ *type = kDBLockFile;
+ } else if (info_log_name_prefix.size() > 0 &&
+ rest.starts_with(info_log_name_prefix)) {
+ rest.remove_prefix(info_log_name_prefix.size());
+ if (rest == "" || rest == ".old") {
+ *number = 0;
+ *type = kInfoLogFile;
+ } else if (rest.starts_with(".old.")) {
+ uint64_t ts_suffix;
+ // sizeof also counts the trailing '\0'.
+ rest.remove_prefix(sizeof(".old.") - 1);
+ if (!ConsumeDecimalNumber(&rest, &ts_suffix)) {
+ return false;
+ }
+ *number = ts_suffix;
+ *type = kInfoLogFile;
+ }
+ } else if (rest.starts_with("MANIFEST-")) {
+ rest.remove_prefix(strlen("MANIFEST-"));
+ uint64_t num;
+ if (!ConsumeDecimalNumber(&rest, &num)) {
+ return false;
+ }
+ if (!rest.empty()) {
+ return false;
+ }
+ *type = kDescriptorFile;
+ *number = num;
+ } else if (rest.starts_with("METADB-")) {
+ rest.remove_prefix(strlen("METADB-"));
+ uint64_t num;
+ if (!ConsumeDecimalNumber(&rest, &num)) {
+ return false;
+ }
+ if (!rest.empty()) {
+ return false;
+ }
+ *type = kMetaDatabase;
+ *number = num;
+ } else if (rest.starts_with(kOptionsFileNamePrefix)) {
+ uint64_t ts_suffix;
+ bool is_temp_file = false;
+ rest.remove_prefix(kOptionsFileNamePrefix.size());
+ const std::string kTempFileNameSuffixWithDot =
+ std::string(".") + kTempFileNameSuffix;
+ if (rest.ends_with(kTempFileNameSuffixWithDot)) {
+ rest.remove_suffix(kTempFileNameSuffixWithDot.size());
+ is_temp_file = true;
+ }
+ if (!ConsumeDecimalNumber(&rest, &ts_suffix)) {
+ return false;
+ }
+ *number = ts_suffix;
+ *type = is_temp_file ? kTempFile : kOptionsFile;
+ } else {
+ // Avoid strtoull() to keep filename format independent of the
+ // current locale
+ bool archive_dir_found = false;
+ if (rest.starts_with(ARCHIVAL_DIR)) {
+ if (rest.size() <= ARCHIVAL_DIR.size()) {
+ return false;
+ }
+ rest.remove_prefix(ARCHIVAL_DIR.size() + 1); // Add 1 to remove / also
+ if (log_type) {
+ *log_type = kArchivedLogFile;
+ }
+ archive_dir_found = true;
+ }
+ uint64_t num;
+ if (!ConsumeDecimalNumber(&rest, &num)) {
+ return false;
+ }
+ if (rest.size() <= 1 || rest[0] != '.') {
+ return false;
+ }
+ rest.remove_prefix(1);
+
+ Slice suffix = rest;
+ if (suffix == Slice("log")) {
+ *type = kLogFile;
+ if (log_type && !archive_dir_found) {
+ *log_type = kAliveLogFile;
+ }
+ } else if (archive_dir_found) {
+ return false; // Archive dir can contain only log files
+ } else if (suffix == Slice(kRocksDbTFileExt) ||
+ suffix == Slice(kLevelDbTFileExt)) {
+ *type = kTableFile;
+ } else if (suffix == Slice(kRocksDBBlobFileExt)) {
+ *type = kBlobFile;
+ } else if (suffix == Slice(kTempFileNameSuffix)) {
+ *type = kTempFile;
+ } else {
+ return false;
+ }
+ *number = num;
+ }
+ return true;
+}
+
+Status SetCurrentFile(Env* env, const std::string& dbname,
+ uint64_t descriptor_number,
+ Directory* directory_to_fsync) {
+ // Remove leading "dbname/" and add newline to manifest file name
+ std::string manifest = DescriptorFileName(dbname, descriptor_number);
+ Slice contents = manifest;
+ assert(contents.starts_with(dbname + "/"));
+ contents.remove_prefix(dbname.size() + 1);
+ std::string tmp = TempFileName(dbname, descriptor_number);
+ Status s = WriteStringToFile(env, contents.ToString() + "\n", tmp, true);
+ if (s.ok()) {
+ TEST_KILL_RANDOM("SetCurrentFile:0", rocksdb_kill_odds * REDUCE_ODDS2);
+ s = env->RenameFile(tmp, CurrentFileName(dbname));
+ TEST_KILL_RANDOM("SetCurrentFile:1", rocksdb_kill_odds * REDUCE_ODDS2);
+ }
+ if (s.ok()) {
+ if (directory_to_fsync != nullptr) {
+ s = directory_to_fsync->Fsync();
+ }
+ } else {
+ env->DeleteFile(tmp);
+ }
+ return s;
+}
+
+Status SetIdentityFile(Env* env, const std::string& dbname,
+ const std::string& db_id) {
+ std::string id;
+ if (db_id.empty()) {
+ id = env->GenerateUniqueId();
+ } else {
+ id = db_id;
+ }
+ assert(!id.empty());
+ // Reserve the filename dbname/000000.dbtmp for the temporary identity file
+ std::string tmp = TempFileName(dbname, 0);
+ Status s = WriteStringToFile(env, id, tmp, true);
+ if (s.ok()) {
+ s = env->RenameFile(tmp, IdentityFileName(dbname));
+ }
+ if (!s.ok()) {
+ env->DeleteFile(tmp);
+ }
+ return s;
+}
+
+Status SyncManifest(Env* env, const ImmutableDBOptions* db_options,
+ WritableFileWriter* file) {
+ TEST_KILL_RANDOM("SyncManifest:0", rocksdb_kill_odds * REDUCE_ODDS2);
+ StopWatch sw(env, db_options->statistics.get(), MANIFEST_FILE_SYNC_MICROS);
+ return file->Sync(db_options->use_fsync);
+}
+
+Status GetInfoLogFiles(Env* env, const std::string& db_log_dir,
+ const std::string& dbname, std::string* parent_dir,
+ std::vector<std::string>* info_log_list) {
+ assert(parent_dir != nullptr);
+ assert(info_log_list != nullptr);
+ uint64_t number = 0;
+ FileType type = kLogFile;
+
+ if (!db_log_dir.empty()) {
+ *parent_dir = db_log_dir;
+ } else {
+ *parent_dir = dbname;
+ }
+
+ InfoLogPrefix info_log_prefix(!db_log_dir.empty(), dbname);
+
+ std::vector<std::string> file_names;
+ Status s = env->GetChildren(*parent_dir, &file_names);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ for (auto& f : file_names) {
+ if (ParseFileName(f, &number, info_log_prefix.prefix, &type) &&
+ (type == kInfoLogFile)) {
+ info_log_list->push_back(f);
+ }
+ }
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/filename.h b/storage/rocksdb/rocksdb/file/filename.h
new file mode 100644
index 00000000..909287c2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/filename.h
@@ -0,0 +1,185 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// File names used by DB code
+
+#pragma once
+#include <stdint.h>
+#include <unordered_map>
+#include <string>
+#include <vector>
+
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/transaction_log.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Directory;
+class WritableFileWriter;
+
+enum FileType {
+ kLogFile,
+ kDBLockFile,
+ kTableFile,
+ kDescriptorFile,
+ kCurrentFile,
+ kTempFile,
+ kInfoLogFile, // Either the current one, or an old one
+ kMetaDatabase,
+ kIdentityFile,
+ kOptionsFile,
+ kBlobFile
+};
+
+// Return the name of the log file with the specified number
+// in the db named by "dbname". The result will be prefixed with
+// "dbname".
+extern std::string LogFileName(const std::string& dbname, uint64_t number);
+
+extern std::string LogFileName(uint64_t number);
+
+extern std::string BlobFileName(const std::string& bdirname, uint64_t number);
+
+extern std::string BlobFileName(const std::string& dbname,
+ const std::string& blob_dir, uint64_t number);
+
+static const std::string ARCHIVAL_DIR = "archive";
+
+extern std::string ArchivalDirectory(const std::string& dbname);
+
+// Return the name of the archived log file with the specified number
+// in the db named by "dbname". The result will be prefixed with "dbname".
+extern std::string ArchivedLogFileName(const std::string& dbname,
+ uint64_t num);
+
+extern std::string MakeTableFileName(const std::string& name, uint64_t number);
+
+extern std::string MakeTableFileName(uint64_t number);
+
+// Return the name of sstable with LevelDB suffix
+// created from RocksDB sstable suffixed name
+extern std::string Rocks2LevelTableFileName(const std::string& fullname);
+
+// the reverse function of MakeTableFileName
+// TODO(yhchiang): could merge this function with ParseFileName()
+extern uint64_t TableFileNameToNumber(const std::string& name);
+
+// Return the name of the sstable with the specified number
+// in the db named by "dbname". The result will be prefixed with
+// "dbname".
+extern std::string TableFileName(const std::vector<DbPath>& db_paths,
+ uint64_t number, uint32_t path_id);
+
+// Sufficient buffer size for FormatFileNumber.
+const size_t kFormatFileNumberBufSize = 38;
+
+extern void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
+ size_t out_buf_size);
+
+// Return the name of the descriptor file for the db named by
+// "dbname" and the specified incarnation number. The result will be
+// prefixed with "dbname".
+extern std::string DescriptorFileName(const std::string& dbname,
+ uint64_t number);
+
+// Return the name of the current file. This file contains the name
+// of the current manifest file. The result will be prefixed with
+// "dbname".
+extern std::string CurrentFileName(const std::string& dbname);
+
+// Return the name of the lock file for the db named by
+// "dbname". The result will be prefixed with "dbname".
+extern std::string LockFileName(const std::string& dbname);
+
+// Return the name of a temporary file owned by the db named "dbname".
+// The result will be prefixed with "dbname".
+extern std::string TempFileName(const std::string& dbname, uint64_t number);
+
+// A helper structure for prefix of info log names.
+struct InfoLogPrefix {
+ char buf[260];
+ Slice prefix;
+ // Prefix with DB absolute path encoded
+ explicit InfoLogPrefix(bool has_log_dir, const std::string& db_absolute_path);
+ // Default Prefix
+ explicit InfoLogPrefix();
+};
+
+// Return the name of the info log file for "dbname".
+extern std::string InfoLogFileName(const std::string& dbname,
+ const std::string& db_path = "",
+ const std::string& log_dir = "");
+
+// Return the name of the old info log file for "dbname".
+extern std::string OldInfoLogFileName(const std::string& dbname, uint64_t ts,
+ const std::string& db_path = "",
+ const std::string& log_dir = "");
+
+static const std::string kOptionsFileNamePrefix = "OPTIONS-";
+static const std::string kTempFileNameSuffix = "dbtmp";
+
+// Return a options file name given the "dbname" and file number.
+// Format: OPTIONS-[number].dbtmp
+extern std::string OptionsFileName(const std::string& dbname,
+ uint64_t file_num);
+
+// Return a temp options file name given the "dbname" and file number.
+// Format: OPTIONS-[number]
+extern std::string TempOptionsFileName(const std::string& dbname,
+ uint64_t file_num);
+
+// Return the name to use for a metadatabase. The result will be prefixed with
+// "dbname".
+extern std::string MetaDatabaseName(const std::string& dbname,
+ uint64_t number);
+
+// Return the name of the Identity file which stores a unique number for the db
+// that will get regenerated if the db loses all its data and is recreated fresh
+// either from a backup-image or empty
+extern std::string IdentityFileName(const std::string& dbname);
+
+// If filename is a rocksdb file, store the type of the file in *type.
+// The number encoded in the filename is stored in *number. If the
+// filename was successfully parsed, returns true. Else return false.
+// info_log_name_prefix is the path of info logs.
+extern bool ParseFileName(const std::string& filename, uint64_t* number,
+ const Slice& info_log_name_prefix, FileType* type,
+ WalFileType* log_type = nullptr);
+// Same as previous function, but skip info log files.
+extern bool ParseFileName(const std::string& filename, uint64_t* number,
+ FileType* type, WalFileType* log_type = nullptr);
+
+// Make the CURRENT file point to the descriptor file with the
+// specified number.
+extern Status SetCurrentFile(Env* env, const std::string& dbname,
+ uint64_t descriptor_number,
+ Directory* directory_to_fsync);
+
+// Make the IDENTITY file for the db
+extern Status SetIdentityFile(Env* env, const std::string& dbname,
+ const std::string& db_id = {});
+
+// Sync manifest file `file`.
+extern Status SyncManifest(Env* env, const ImmutableDBOptions* db_options,
+ WritableFileWriter* file);
+
+// Return list of file names of info logs in `file_names`.
+// The list only contains file name. The parent directory name is stored
+// in `parent_dir`.
+// `db_log_dir` should be the one as in options.db_log_dir
+extern Status GetInfoLogFiles(Env* env, const std::string& db_log_dir,
+ const std::string& dbname,
+ std::string* parent_dir,
+ std::vector<std::string>* file_names);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/random_access_file_reader.cc b/storage/rocksdb/rocksdb/file/random_access_file_reader.cc
new file mode 100644
index 00000000..46892360
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/random_access_file_reader.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/random_access_file_reader.h"
+
+#include <algorithm>
+#include <mutex>
+
+#include "monitoring/histogram.h"
+#include "monitoring/iostats_context_imp.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+Status RandomAccessFileReader::Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch, bool for_compaction) const {
+ Status s;
+ uint64_t elapsed = 0;
+ {
+ StopWatch sw(env_, stats_, hist_type_,
+ (stats_ != nullptr) ? &elapsed : nullptr, true /*overwrite*/,
+ true /*delay_enabled*/);
+ auto prev_perf_level = GetPerfLevel();
+ IOSTATS_TIMER_GUARD(read_nanos);
+ if (use_direct_io()) {
+#ifndef ROCKSDB_LITE
+ size_t alignment = file_->GetRequiredBufferAlignment();
+ size_t aligned_offset =
+ TruncateToPageBoundary(alignment, static_cast<size_t>(offset));
+ size_t offset_advance = static_cast<size_t>(offset) - aligned_offset;
+ size_t read_size =
+ Roundup(static_cast<size_t>(offset + n), alignment) - aligned_offset;
+ AlignedBuffer buf;
+ buf.Alignment(alignment);
+ buf.AllocateNewBuffer(read_size);
+ while (buf.CurrentSize() < read_size) {
+ size_t allowed;
+ if (for_compaction && rate_limiter_ != nullptr) {
+ allowed = rate_limiter_->RequestToken(
+ buf.Capacity() - buf.CurrentSize(), buf.Alignment(),
+ Env::IOPriority::IO_LOW, stats_, RateLimiter::OpType::kRead);
+ } else {
+ assert(buf.CurrentSize() == 0);
+ allowed = read_size;
+ }
+ Slice tmp;
+
+ FileOperationInfo::TimePoint start_ts;
+ uint64_t orig_offset = 0;
+ if (ShouldNotifyListeners()) {
+ start_ts = std::chrono::system_clock::now();
+ orig_offset = aligned_offset + buf.CurrentSize();
+ }
+ {
+ IOSTATS_CPU_TIMER_GUARD(cpu_read_nanos, env_);
+ s = file_->Read(aligned_offset + buf.CurrentSize(), allowed,
+ IOOptions(), &tmp, buf.Destination(), nullptr);
+ }
+ if (ShouldNotifyListeners()) {
+ auto finish_ts = std::chrono::system_clock::now();
+ NotifyOnFileReadFinish(orig_offset, tmp.size(), start_ts, finish_ts,
+ s);
+ }
+
+ buf.Size(buf.CurrentSize() + tmp.size());
+ if (!s.ok() || tmp.size() < allowed) {
+ break;
+ }
+ }
+ size_t res_len = 0;
+ if (s.ok() && offset_advance < buf.CurrentSize()) {
+ res_len = buf.Read(scratch, offset_advance,
+ std::min(buf.CurrentSize() - offset_advance, n));
+ }
+ *result = Slice(scratch, res_len);
+#endif // !ROCKSDB_LITE
+ } else {
+ size_t pos = 0;
+ const char* res_scratch = nullptr;
+ while (pos < n) {
+ size_t allowed;
+ if (for_compaction && rate_limiter_ != nullptr) {
+ if (rate_limiter_->IsRateLimited(RateLimiter::OpType::kRead)) {
+ sw.DelayStart();
+ }
+ allowed = rate_limiter_->RequestToken(n - pos, 0 /* alignment */,
+ Env::IOPriority::IO_LOW, stats_,
+ RateLimiter::OpType::kRead);
+ if (rate_limiter_->IsRateLimited(RateLimiter::OpType::kRead)) {
+ sw.DelayStop();
+ }
+ } else {
+ allowed = n;
+ }
+ Slice tmp_result;
+
+#ifndef ROCKSDB_LITE
+ FileOperationInfo::TimePoint start_ts;
+ if (ShouldNotifyListeners()) {
+ start_ts = std::chrono::system_clock::now();
+ }
+#endif
+ {
+ IOSTATS_CPU_TIMER_GUARD(cpu_read_nanos, env_);
+ s = file_->Read(offset + pos, allowed, IOOptions(), &tmp_result,
+ scratch + pos, nullptr);
+ }
+#ifndef ROCKSDB_LITE
+ if (ShouldNotifyListeners()) {
+ auto finish_ts = std::chrono::system_clock::now();
+ NotifyOnFileReadFinish(offset + pos, tmp_result.size(), start_ts,
+ finish_ts, s);
+ }
+#endif
+
+ if (res_scratch == nullptr) {
+ // we can't simply use `scratch` because reads of mmap'd files return
+ // data in a different buffer.
+ res_scratch = tmp_result.data();
+ } else {
+ // make sure chunks are inserted contiguously into `res_scratch`.
+ assert(tmp_result.data() == res_scratch + pos);
+ }
+ pos += tmp_result.size();
+ if (!s.ok() || tmp_result.size() < allowed) {
+ break;
+ }
+ }
+ *result = Slice(res_scratch, s.ok() ? pos : 0);
+ }
+ IOSTATS_ADD_IF_POSITIVE(bytes_read, result->size());
+ SetPerfLevel(prev_perf_level);
+ }
+ if (stats_ != nullptr && file_read_hist_ != nullptr) {
+ file_read_hist_->Add(elapsed);
+ }
+
+ return s;
+}
+
+Status RandomAccessFileReader::MultiRead(FSReadRequest* read_reqs,
+ size_t num_reqs) const {
+ Status s;
+ uint64_t elapsed = 0;
+ assert(!use_direct_io());
+ {
+ StopWatch sw(env_, stats_, hist_type_,
+ (stats_ != nullptr) ? &elapsed : nullptr, true /*overwrite*/,
+ true /*delay_enabled*/);
+ auto prev_perf_level = GetPerfLevel();
+ IOSTATS_TIMER_GUARD(read_nanos);
+
+#ifndef ROCKSDB_LITE
+ FileOperationInfo::TimePoint start_ts;
+ if (ShouldNotifyListeners()) {
+ start_ts = std::chrono::system_clock::now();
+ }
+#endif // ROCKSDB_LITE
+ {
+ IOSTATS_CPU_TIMER_GUARD(cpu_read_nanos, env_);
+ s = file_->MultiRead(read_reqs, num_reqs, IOOptions(), nullptr);
+ }
+ for (size_t i = 0; i < num_reqs; ++i) {
+#ifndef ROCKSDB_LITE
+ if (ShouldNotifyListeners()) {
+ auto finish_ts = std::chrono::system_clock::now();
+ NotifyOnFileReadFinish(read_reqs[i].offset, read_reqs[i].result.size(),
+ start_ts, finish_ts, read_reqs[i].status);
+ }
+#endif // ROCKSDB_LITE
+ IOSTATS_ADD_IF_POSITIVE(bytes_read, read_reqs[i].result.size());
+ }
+ SetPerfLevel(prev_perf_level);
+ }
+ if (stats_ != nullptr && file_read_hist_ != nullptr) {
+ file_read_hist_->Add(elapsed);
+ }
+
+ return s;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/random_access_file_reader.h b/storage/rocksdb/rocksdb/file/random_access_file_reader.h
new file mode 100644
index 00000000..35027bf4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/random_access_file_reader.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <sstream>
+#include <string>
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/rate_limiter.h"
+#include "util/aligned_buffer.h"
+
+namespace ROCKSDB_NAMESPACE {
+class Statistics;
+class HistogramImpl;
+
+// RandomAccessFileReader is a wrapper on top of Env::RnadomAccessFile. It is
+// responsible for:
+// - Handling Buffered and Direct reads appropriately.
+// - Rate limiting compaction reads.
+// - Notifying any interested listeners on the completion of a read.
+// - Updating IO stats.
+class RandomAccessFileReader {
+ private:
+#ifndef ROCKSDB_LITE
+ void NotifyOnFileReadFinish(uint64_t offset, size_t length,
+ const FileOperationInfo::TimePoint& start_ts,
+ const FileOperationInfo::TimePoint& finish_ts,
+ const Status& status) const {
+ FileOperationInfo info(file_name_, start_ts, finish_ts);
+ info.offset = offset;
+ info.length = length;
+ info.status = status;
+
+ for (auto& listener : listeners_) {
+ listener->OnFileReadFinish(info);
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ bool ShouldNotifyListeners() const { return !listeners_.empty(); }
+
+ std::unique_ptr<FSRandomAccessFile> file_;
+ std::string file_name_;
+ Env* env_;
+ Statistics* stats_;
+ uint32_t hist_type_;
+ HistogramImpl* file_read_hist_;
+ RateLimiter* rate_limiter_;
+ std::vector<std::shared_ptr<EventListener>> listeners_;
+
+ public:
+ explicit RandomAccessFileReader(
+ std::unique_ptr<FSRandomAccessFile>&& raf, std::string _file_name,
+ Env* env = nullptr, Statistics* stats = nullptr, uint32_t hist_type = 0,
+ HistogramImpl* file_read_hist = nullptr,
+ RateLimiter* rate_limiter = nullptr,
+ const std::vector<std::shared_ptr<EventListener>>& listeners = {})
+ : file_(std::move(raf)),
+ file_name_(std::move(_file_name)),
+ env_(env),
+ stats_(stats),
+ hist_type_(hist_type),
+ file_read_hist_(file_read_hist),
+ rate_limiter_(rate_limiter),
+ listeners_() {
+#ifndef ROCKSDB_LITE
+ std::for_each(listeners.begin(), listeners.end(),
+ [this](const std::shared_ptr<EventListener>& e) {
+ if (e->ShouldBeNotifiedOnFileIO()) {
+ listeners_.emplace_back(e);
+ }
+ });
+#else // !ROCKSDB_LITE
+ (void)listeners;
+#endif
+ }
+
+ RandomAccessFileReader(RandomAccessFileReader&& o) ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ RandomAccessFileReader& operator=(RandomAccessFileReader&& o)
+ ROCKSDB_NOEXCEPT {
+ file_ = std::move(o.file_);
+ env_ = std::move(o.env_);
+ stats_ = std::move(o.stats_);
+ hist_type_ = std::move(o.hist_type_);
+ file_read_hist_ = std::move(o.file_read_hist_);
+ rate_limiter_ = std::move(o.rate_limiter_);
+ return *this;
+ }
+
+ RandomAccessFileReader(const RandomAccessFileReader&) = delete;
+ RandomAccessFileReader& operator=(const RandomAccessFileReader&) = delete;
+
+ Status Read(uint64_t offset, size_t n, Slice* result, char* scratch,
+ bool for_compaction = false) const;
+
+ Status MultiRead(FSReadRequest* reqs, size_t num_reqs) const;
+
+ Status Prefetch(uint64_t offset, size_t n) const {
+ return file_->Prefetch(offset, n, IOOptions(), nullptr);
+ }
+
+ FSRandomAccessFile* file() { return file_.get(); }
+
+ std::string file_name() const { return file_name_; }
+
+ bool use_direct_io() const { return file_->use_direct_io(); }
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/read_write_util.cc b/storage/rocksdb/rocksdb/file/read_write_util.cc
new file mode 100644
index 00000000..b4854e11
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/read_write_util.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/read_write_util.h"
+
+#include <sstream>
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+IOStatus NewWritableFile(FileSystem* fs, const std::string& fname,
+ std::unique_ptr<FSWritableFile>* result,
+ const FileOptions& options) {
+ IOStatus s = fs->NewWritableFile(fname, options, result, nullptr);
+ TEST_KILL_RANDOM("NewWritableFile:0", rocksdb_kill_odds * REDUCE_ODDS2);
+ return s;
+}
+
+bool ReadOneLine(std::istringstream* iss, SequentialFileReader* seq_file_reader,
+ std::string* output, bool* has_data, Status* result) {
+ const int kBufferSize = 8192;
+ char buffer[kBufferSize + 1];
+ Slice input_slice;
+
+ std::string line;
+ bool has_complete_line = false;
+ while (!has_complete_line) {
+ if (std::getline(*iss, line)) {
+ has_complete_line = !iss->eof();
+ } else {
+ has_complete_line = false;
+ }
+ if (!has_complete_line) {
+ // if we're not sure whether we have a complete line,
+ // further read from the file.
+ if (*has_data) {
+ *result = seq_file_reader->Read(kBufferSize, &input_slice, buffer);
+ }
+ if (input_slice.size() == 0) {
+ // meaning we have read all the data
+ *has_data = false;
+ break;
+ } else {
+ iss->str(line + input_slice.ToString());
+ // reset the internal state of iss so that we can keep reading it.
+ iss->clear();
+ *has_data = (input_slice.size() == kBufferSize);
+ continue;
+ }
+ }
+ }
+ *output = line;
+ return *has_data || has_complete_line;
+}
+
+#ifndef NDEBUG
+bool IsFileSectorAligned(const size_t off, size_t sector_size) {
+ return off % sector_size == 0;
+}
+#endif // NDEBUG
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/read_write_util.h b/storage/rocksdb/rocksdb/file/read_write_util.h
new file mode 100644
index 00000000..22f4076b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/read_write_util.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include "file/sequence_file_reader.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Returns a WritableFile.
+//
+// env : the Env.
+// fname : the file name.
+// result : output arg. A WritableFile based on `fname` returned.
+// options : the Env Options.
+extern IOStatus NewWritableFile(FileSystem* fs, const std::string& fname,
+ std::unique_ptr<FSWritableFile>* result,
+ const FileOptions& options);
+
+// Read a single line from a file.
+bool ReadOneLine(std::istringstream* iss, SequentialFileReader* seq_file_reader,
+ std::string* output, bool* has_data, Status* result);
+
+#ifndef NDEBUG
+bool IsFileSectorAligned(const size_t off, size_t sector_size);
+#endif // NDEBUG
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/readahead_raf.cc b/storage/rocksdb/rocksdb/file/readahead_raf.cc
new file mode 100644
index 00000000..493f9d9e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/readahead_raf.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/readahead_raf.h"
+
+#include <algorithm>
+#include <mutex>
+#include "file/read_write_util.h"
+#include "util/aligned_buffer.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+class ReadaheadRandomAccessFile : public RandomAccessFile {
+ public:
+ ReadaheadRandomAccessFile(std::unique_ptr<RandomAccessFile>&& file,
+ size_t readahead_size)
+ : file_(std::move(file)),
+ alignment_(file_->GetRequiredBufferAlignment()),
+ readahead_size_(Roundup(readahead_size, alignment_)),
+ buffer_(),
+ buffer_offset_(0) {
+ buffer_.Alignment(alignment_);
+ buffer_.AllocateNewBuffer(readahead_size_);
+ }
+
+ ReadaheadRandomAccessFile(const ReadaheadRandomAccessFile&) = delete;
+
+ ReadaheadRandomAccessFile& operator=(const ReadaheadRandomAccessFile&) =
+ delete;
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ // Read-ahead only make sense if we have some slack left after reading
+ if (n + alignment_ >= readahead_size_) {
+ return file_->Read(offset, n, result, scratch);
+ }
+
+ std::unique_lock<std::mutex> lk(lock_);
+
+ size_t cached_len = 0;
+ // Check if there is a cache hit, meaning that [offset, offset + n) is
+ // either completely or partially in the buffer. If it's completely cached,
+ // including end of file case when offset + n is greater than EOF, then
+ // return.
+ if (TryReadFromCache(offset, n, &cached_len, scratch) &&
+ (cached_len == n || buffer_.CurrentSize() < readahead_size_)) {
+ // We read exactly what we needed, or we hit end of file - return.
+ *result = Slice(scratch, cached_len);
+ return Status::OK();
+ }
+ size_t advanced_offset = static_cast<size_t>(offset + cached_len);
+ // In the case of cache hit advanced_offset is already aligned, means that
+ // chunk_offset equals to advanced_offset
+ size_t chunk_offset = TruncateToPageBoundary(alignment_, advanced_offset);
+
+ Status s = ReadIntoBuffer(chunk_offset, readahead_size_);
+ if (s.ok()) {
+ // The data we need is now in cache, so we can safely read it
+ size_t remaining_len;
+ TryReadFromCache(advanced_offset, n - cached_len, &remaining_len,
+ scratch + cached_len);
+ *result = Slice(scratch, cached_len + remaining_len);
+ }
+ return s;
+ }
+
+ Status Prefetch(uint64_t offset, size_t n) override {
+ if (n < readahead_size_) {
+ // Don't allow smaller prefetches than the configured `readahead_size_`.
+ // `Read()` assumes a smaller prefetch buffer indicates EOF was reached.
+ return Status::OK();
+ }
+
+ std::unique_lock<std::mutex> lk(lock_);
+
+ size_t offset_ = static_cast<size_t>(offset);
+ size_t prefetch_offset = TruncateToPageBoundary(alignment_, offset_);
+ if (prefetch_offset == buffer_offset_) {
+ return Status::OK();
+ }
+ return ReadIntoBuffer(prefetch_offset,
+ Roundup(offset_ + n, alignment_) - prefetch_offset);
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return file_->GetUniqueId(id, max_size);
+ }
+
+ void Hint(AccessPattern pattern) override { file_->Hint(pattern); }
+
+ Status InvalidateCache(size_t offset, size_t length) override {
+ std::unique_lock<std::mutex> lk(lock_);
+ buffer_.Clear();
+ return file_->InvalidateCache(offset, length);
+ }
+
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ private:
+ // Tries to read from buffer_ n bytes starting at offset. If anything was read
+ // from the cache, it sets cached_len to the number of bytes actually read,
+ // copies these number of bytes to scratch and returns true.
+ // If nothing was read sets cached_len to 0 and returns false.
+ bool TryReadFromCache(uint64_t offset, size_t n, size_t* cached_len,
+ char* scratch) const {
+ if (offset < buffer_offset_ ||
+ offset >= buffer_offset_ + buffer_.CurrentSize()) {
+ *cached_len = 0;
+ return false;
+ }
+ uint64_t offset_in_buffer = offset - buffer_offset_;
+ *cached_len = std::min(
+ buffer_.CurrentSize() - static_cast<size_t>(offset_in_buffer), n);
+ memcpy(scratch, buffer_.BufferStart() + offset_in_buffer, *cached_len);
+ return true;
+ }
+
+ // Reads into buffer_ the next n bytes from file_ starting at offset.
+ // Can actually read less if EOF was reached.
+ // Returns the status of the read operastion on the file.
+ Status ReadIntoBuffer(uint64_t offset, size_t n) const {
+ if (n > buffer_.Capacity()) {
+ n = buffer_.Capacity();
+ }
+ assert(IsFileSectorAligned(offset, alignment_));
+ assert(IsFileSectorAligned(n, alignment_));
+ Slice result;
+ Status s = file_->Read(offset, n, &result, buffer_.BufferStart());
+ if (s.ok()) {
+ buffer_offset_ = offset;
+ buffer_.Size(result.size());
+ assert(result.size() == 0 || buffer_.BufferStart() == result.data());
+ }
+ return s;
+ }
+
+ const std::unique_ptr<RandomAccessFile> file_;
+ const size_t alignment_;
+ const size_t readahead_size_;
+
+ mutable std::mutex lock_;
+ // The buffer storing the prefetched data
+ mutable AlignedBuffer buffer_;
+ // The offset in file_, corresponding to data stored in buffer_
+ mutable uint64_t buffer_offset_;
+};
+} // namespace
+
+std::unique_ptr<RandomAccessFile> NewReadaheadRandomAccessFile(
+ std::unique_ptr<RandomAccessFile>&& file, size_t readahead_size) {
+ std::unique_ptr<RandomAccessFile> result(
+ new ReadaheadRandomAccessFile(std::move(file), readahead_size));
+ return result;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/readahead_raf.h b/storage/rocksdb/rocksdb/file/readahead_raf.h
new file mode 100644
index 00000000..cbdcb124
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/readahead_raf.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+// This file provides the following main abstractions:
+// SequentialFileReader : wrapper over Env::SequentialFile
+// RandomAccessFileReader : wrapper over Env::RandomAccessFile
+// WritableFileWriter : wrapper over Env::WritableFile
+// In addition, it also exposed NewReadaheadRandomAccessFile, NewWritableFile,
+// and ReadOneLine primitives.
+
+// NewReadaheadRandomAccessFile provides a wrapper over RandomAccessFile to
+// always prefetch additional data with every read. This is mainly used in
+// Compaction Table Readers.
+std::unique_ptr<RandomAccessFile> NewReadaheadRandomAccessFile(
+ std::unique_ptr<RandomAccessFile>&& file, size_t readahead_size);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/sequence_file_reader.cc b/storage/rocksdb/rocksdb/file/sequence_file_reader.cc
new file mode 100644
index 00000000..81c5e5d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/sequence_file_reader.cc
@@ -0,0 +1,237 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sequence_file_reader.h"
+
+#include <algorithm>
+#include <mutex>
+
+#include "file/read_write_util.h"
+#include "monitoring/histogram.h"
+#include "monitoring/iostats_context_imp.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/aligned_buffer.h"
+#include "util/random.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+Status SequentialFileReader::Read(size_t n, Slice* result, char* scratch) {
+ Status s;
+ if (use_direct_io()) {
+#ifndef ROCKSDB_LITE
+ size_t offset = offset_.fetch_add(n);
+ size_t alignment = file_->GetRequiredBufferAlignment();
+ size_t aligned_offset = TruncateToPageBoundary(alignment, offset);
+ size_t offset_advance = offset - aligned_offset;
+ size_t size = Roundup(offset + n, alignment) - aligned_offset;
+ size_t r = 0;
+ AlignedBuffer buf;
+ buf.Alignment(alignment);
+ buf.AllocateNewBuffer(size);
+ Slice tmp;
+ s = file_->PositionedRead(aligned_offset, size, IOOptions(), &tmp,
+ buf.BufferStart(), nullptr);
+ if (s.ok() && offset_advance < tmp.size()) {
+ buf.Size(tmp.size());
+ r = buf.Read(scratch, offset_advance,
+ std::min(tmp.size() - offset_advance, n));
+ }
+ *result = Slice(scratch, r);
+#endif // !ROCKSDB_LITE
+ } else {
+ s = file_->Read(n, IOOptions(), result, scratch, nullptr);
+ }
+ IOSTATS_ADD(bytes_read, result->size());
+ return s;
+}
+
+Status SequentialFileReader::Skip(uint64_t n) {
+#ifndef ROCKSDB_LITE
+ if (use_direct_io()) {
+ offset_ += static_cast<size_t>(n);
+ return Status::OK();
+ }
+#endif // !ROCKSDB_LITE
+ return file_->Skip(n);
+}
+
+namespace {
+// This class wraps a SequentialFile, exposing same API, with the differenece
+// of being able to prefetch up to readahead_size bytes and then serve them
+// from memory, avoiding the entire round-trip if, for example, the data for the
+// file is actually remote.
+class ReadaheadSequentialFile : public FSSequentialFile {
+ public:
+ ReadaheadSequentialFile(std::unique_ptr<FSSequentialFile>&& file,
+ size_t readahead_size)
+ : file_(std::move(file)),
+ alignment_(file_->GetRequiredBufferAlignment()),
+ readahead_size_(Roundup(readahead_size, alignment_)),
+ buffer_(),
+ buffer_offset_(0),
+ read_offset_(0) {
+ buffer_.Alignment(alignment_);
+ buffer_.AllocateNewBuffer(readahead_size_);
+ }
+
+ ReadaheadSequentialFile(const ReadaheadSequentialFile&) = delete;
+
+ ReadaheadSequentialFile& operator=(const ReadaheadSequentialFile&) = delete;
+
+ IOStatus Read(size_t n, const IOOptions& opts, Slice* result, char* scratch,
+ IODebugContext* dbg) override {
+ std::unique_lock<std::mutex> lk(lock_);
+
+ size_t cached_len = 0;
+ // Check if there is a cache hit, meaning that [offset, offset + n) is
+ // either completely or partially in the buffer. If it's completely cached,
+ // including end of file case when offset + n is greater than EOF, then
+ // return.
+ if (TryReadFromCache(n, &cached_len, scratch) &&
+ (cached_len == n || buffer_.CurrentSize() < readahead_size_)) {
+ // We read exactly what we needed, or we hit end of file - return.
+ *result = Slice(scratch, cached_len);
+ return IOStatus::OK();
+ }
+ n -= cached_len;
+
+ IOStatus s;
+ // Read-ahead only make sense if we have some slack left after reading
+ if (n + alignment_ >= readahead_size_) {
+ s = file_->Read(n, opts, result, scratch + cached_len, dbg);
+ if (s.ok()) {
+ read_offset_ += result->size();
+ *result = Slice(scratch, cached_len + result->size());
+ }
+ buffer_.Clear();
+ return s;
+ }
+
+ s = ReadIntoBuffer(readahead_size_, opts, dbg);
+ if (s.ok()) {
+ // The data we need is now in cache, so we can safely read it
+ size_t remaining_len;
+ TryReadFromCache(n, &remaining_len, scratch + cached_len);
+ *result = Slice(scratch, cached_len + remaining_len);
+ }
+ return s;
+ }
+
+ IOStatus Skip(uint64_t n) override {
+ std::unique_lock<std::mutex> lk(lock_);
+ IOStatus s = IOStatus::OK();
+ // First check if we need to skip already cached data
+ if (buffer_.CurrentSize() > 0) {
+ // Do we need to skip beyond cached data?
+ if (read_offset_ + n >= buffer_offset_ + buffer_.CurrentSize()) {
+ // Yes. Skip whaterver is in memory and adjust offset accordingly
+ n -= buffer_offset_ + buffer_.CurrentSize() - read_offset_;
+ read_offset_ = buffer_offset_ + buffer_.CurrentSize();
+ } else {
+ // No. The entire section to be skipped is entirely i cache.
+ read_offset_ += n;
+ n = 0;
+ }
+ }
+ if (n > 0) {
+ // We still need to skip more, so call the file API for skipping
+ s = file_->Skip(n);
+ if (s.ok()) {
+ read_offset_ += n;
+ }
+ buffer_.Clear();
+ }
+ return s;
+ }
+
+ IOStatus PositionedRead(uint64_t offset, size_t n, const IOOptions& opts,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) override {
+ return file_->PositionedRead(offset, n, opts, result, scratch, dbg);
+ }
+
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ std::unique_lock<std::mutex> lk(lock_);
+ buffer_.Clear();
+ return file_->InvalidateCache(offset, length);
+ }
+
+ bool use_direct_io() const override { return file_->use_direct_io(); }
+
+ private:
+ // Tries to read from buffer_ n bytes. If anything was read from the cache, it
+ // sets cached_len to the number of bytes actually read, copies these number
+ // of bytes to scratch and returns true.
+ // If nothing was read sets cached_len to 0 and returns false.
+ bool TryReadFromCache(size_t n, size_t* cached_len, char* scratch) {
+ if (read_offset_ < buffer_offset_ ||
+ read_offset_ >= buffer_offset_ + buffer_.CurrentSize()) {
+ *cached_len = 0;
+ return false;
+ }
+ uint64_t offset_in_buffer = read_offset_ - buffer_offset_;
+ *cached_len = std::min(
+ buffer_.CurrentSize() - static_cast<size_t>(offset_in_buffer), n);
+ memcpy(scratch, buffer_.BufferStart() + offset_in_buffer, *cached_len);
+ read_offset_ += *cached_len;
+ return true;
+ }
+
+ // Reads into buffer_ the next n bytes from file_.
+ // Can actually read less if EOF was reached.
+ // Returns the status of the read operastion on the file.
+ IOStatus ReadIntoBuffer(size_t n, const IOOptions& opts,
+ IODebugContext* dbg) {
+ if (n > buffer_.Capacity()) {
+ n = buffer_.Capacity();
+ }
+ assert(IsFileSectorAligned(n, alignment_));
+ Slice result;
+ IOStatus s = file_->Read(n, opts, &result, buffer_.BufferStart(), dbg);
+ if (s.ok()) {
+ buffer_offset_ = read_offset_;
+ buffer_.Size(result.size());
+ assert(result.size() == 0 || buffer_.BufferStart() == result.data());
+ }
+ return s;
+ }
+
+ const std::unique_ptr<FSSequentialFile> file_;
+ const size_t alignment_;
+ const size_t readahead_size_;
+
+ std::mutex lock_;
+ // The buffer storing the prefetched data
+ AlignedBuffer buffer_;
+ // The offset in file_, corresponding to data stored in buffer_
+ uint64_t buffer_offset_;
+ // The offset up to which data was read from file_. In fact, it can be larger
+ // than the actual file size, since the file_->Skip(n) call doesn't return the
+ // actual number of bytes that were skipped, which can be less than n.
+ // This is not a problemm since read_offset_ is monotonically increasing and
+ // its only use is to figure out if next piece of data should be read from
+ // buffer_ or file_ directly.
+ uint64_t read_offset_;
+};
+} // namespace
+
+std::unique_ptr<FSSequentialFile>
+SequentialFileReader::NewReadaheadSequentialFile(
+ std::unique_ptr<FSSequentialFile>&& file, size_t readahead_size) {
+ if (file->GetRequiredBufferAlignment() >= readahead_size) {
+ // Short-circuit and return the original file if readahead_size is
+ // too small and hence doesn't make sense to be used for prefetching.
+ return std::move(file);
+ }
+ std::unique_ptr<FSSequentialFile> result(
+ new ReadaheadSequentialFile(std::move(file), readahead_size));
+ return result;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/sequence_file_reader.h b/storage/rocksdb/rocksdb/file/sequence_file_reader.h
new file mode 100644
index 00000000..2f0898b4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/sequence_file_reader.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <string>
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// SequentialFileReader is a wrapper on top of Env::SequentialFile. It handles
+// Buffered (i.e when page cache is enabled) and Direct (with O_DIRECT / page
+// cache disabled) reads appropriately, and also updates the IO stats.
+class SequentialFileReader {
+ private:
+ std::unique_ptr<FSSequentialFile> file_;
+ std::string file_name_;
+ std::atomic<size_t> offset_{0}; // read offset
+
+ public:
+ explicit SequentialFileReader(std::unique_ptr<FSSequentialFile>&& _file,
+ const std::string& _file_name)
+ : file_(std::move(_file)), file_name_(_file_name) {}
+
+ explicit SequentialFileReader(std::unique_ptr<FSSequentialFile>&& _file,
+ const std::string& _file_name,
+ size_t _readahead_size)
+ : file_(NewReadaheadSequentialFile(std::move(_file), _readahead_size)),
+ file_name_(_file_name) {}
+
+ SequentialFileReader(SequentialFileReader&& o) ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ SequentialFileReader& operator=(SequentialFileReader&& o) ROCKSDB_NOEXCEPT {
+ file_ = std::move(o.file_);
+ return *this;
+ }
+
+ SequentialFileReader(const SequentialFileReader&) = delete;
+ SequentialFileReader& operator=(const SequentialFileReader&) = delete;
+
+ Status Read(size_t n, Slice* result, char* scratch);
+
+ Status Skip(uint64_t n);
+
+ FSSequentialFile* file() { return file_.get(); }
+
+ std::string file_name() { return file_name_; }
+
+ bool use_direct_io() const { return file_->use_direct_io(); }
+
+ private:
+ // NewReadaheadSequentialFile provides a wrapper over SequentialFile to
+ // always prefetch additional data with every read.
+ static std::unique_ptr<FSSequentialFile> NewReadaheadSequentialFile(
+ std::unique_ptr<FSSequentialFile>&& file, size_t readahead_size);
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/sst_file_manager_impl.cc b/storage/rocksdb/rocksdb/file/sst_file_manager_impl.cc
new file mode 100644
index 00000000..35056429
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/sst_file_manager_impl.cc
@@ -0,0 +1,558 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sst_file_manager_impl.h"
+
+#include <cinttypes>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "env/composite_env_wrapper.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/sst_file_manager.h"
+#include "test_util/sync_point.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+SstFileManagerImpl::SstFileManagerImpl(Env* env, std::shared_ptr<FileSystem> fs,
+ std::shared_ptr<Logger> logger,
+ int64_t rate_bytes_per_sec,
+ double max_trash_db_ratio,
+ uint64_t bytes_max_delete_chunk)
+ : env_(env),
+ fs_(fs),
+ logger_(logger),
+ total_files_size_(0),
+ in_progress_files_size_(0),
+ compaction_buffer_size_(0),
+ cur_compactions_reserved_size_(0),
+ max_allowed_space_(0),
+ delete_scheduler_(env, fs_.get(), rate_bytes_per_sec, logger.get(), this,
+ max_trash_db_ratio, bytes_max_delete_chunk),
+ cv_(&mu_),
+ closing_(false),
+ bg_thread_(nullptr),
+ reserved_disk_buffer_(0),
+ free_space_trigger_(0),
+ cur_instance_(nullptr) {}
+
+SstFileManagerImpl::~SstFileManagerImpl() {
+ Close();
+}
+
+void SstFileManagerImpl::Close() {
+ {
+ MutexLock l(&mu_);
+ if (closing_) {
+ return;
+ }
+ closing_ = true;
+ cv_.SignalAll();
+ }
+ if (bg_thread_) {
+ bg_thread_->join();
+ }
+}
+
+Status SstFileManagerImpl::OnAddFile(const std::string& file_path,
+ bool compaction) {
+ uint64_t file_size;
+ Status s = fs_->GetFileSize(file_path, IOOptions(), &file_size, nullptr);
+ if (s.ok()) {
+ MutexLock l(&mu_);
+ OnAddFileImpl(file_path, file_size, compaction);
+ }
+ TEST_SYNC_POINT("SstFileManagerImpl::OnAddFile");
+ return s;
+}
+
+Status SstFileManagerImpl::OnAddFile(const std::string& file_path,
+ uint64_t file_size, bool compaction) {
+ MutexLock l(&mu_);
+ OnAddFileImpl(file_path, file_size, compaction);
+ TEST_SYNC_POINT("SstFileManagerImpl::OnAddFile");
+ return Status::OK();
+}
+
+Status SstFileManagerImpl::OnDeleteFile(const std::string& file_path) {
+ {
+ MutexLock l(&mu_);
+ OnDeleteFileImpl(file_path);
+ }
+ TEST_SYNC_POINT("SstFileManagerImpl::OnDeleteFile");
+ return Status::OK();
+}
+
+void SstFileManagerImpl::OnCompactionCompletion(Compaction* c) {
+ MutexLock l(&mu_);
+ uint64_t size_added_by_compaction = 0;
+ for (size_t i = 0; i < c->num_input_levels(); i++) {
+ for (size_t j = 0; j < c->num_input_files(i); j++) {
+ FileMetaData* filemeta = c->input(i, j);
+ size_added_by_compaction += filemeta->fd.GetFileSize();
+ }
+ }
+ cur_compactions_reserved_size_ -= size_added_by_compaction;
+
+ auto new_files = c->edit()->GetNewFiles();
+ for (auto& new_file : new_files) {
+ auto fn = TableFileName(c->immutable_cf_options()->cf_paths,
+ new_file.second.fd.GetNumber(),
+ new_file.second.fd.GetPathId());
+ if (in_progress_files_.find(fn) != in_progress_files_.end()) {
+ auto tracked_file = tracked_files_.find(fn);
+ assert(tracked_file != tracked_files_.end());
+ in_progress_files_size_ -= tracked_file->second;
+ in_progress_files_.erase(fn);
+ }
+ }
+}
+
+Status SstFileManagerImpl::OnMoveFile(const std::string& old_path,
+ const std::string& new_path,
+ uint64_t* file_size) {
+ {
+ MutexLock l(&mu_);
+ if (file_size != nullptr) {
+ *file_size = tracked_files_[old_path];
+ }
+ OnAddFileImpl(new_path, tracked_files_[old_path], false);
+ OnDeleteFileImpl(old_path);
+ }
+ TEST_SYNC_POINT("SstFileManagerImpl::OnMoveFile");
+ return Status::OK();
+}
+
+void SstFileManagerImpl::SetMaxAllowedSpaceUsage(uint64_t max_allowed_space) {
+ MutexLock l(&mu_);
+ max_allowed_space_ = max_allowed_space;
+}
+
+void SstFileManagerImpl::SetCompactionBufferSize(
+ uint64_t compaction_buffer_size) {
+ MutexLock l(&mu_);
+ compaction_buffer_size_ = compaction_buffer_size;
+}
+
+bool SstFileManagerImpl::IsMaxAllowedSpaceReached() {
+ MutexLock l(&mu_);
+ if (max_allowed_space_ <= 0) {
+ return false;
+ }
+ return total_files_size_ >= max_allowed_space_;
+}
+
+bool SstFileManagerImpl::IsMaxAllowedSpaceReachedIncludingCompactions() {
+ MutexLock l(&mu_);
+ if (max_allowed_space_ <= 0) {
+ return false;
+ }
+ return total_files_size_ + cur_compactions_reserved_size_ >=
+ max_allowed_space_;
+}
+
+bool SstFileManagerImpl::EnoughRoomForCompaction(
+ ColumnFamilyData* cfd, const std::vector<CompactionInputFiles>& inputs,
+ Status bg_error) {
+ MutexLock l(&mu_);
+ uint64_t size_added_by_compaction = 0;
+ // First check if we even have the space to do the compaction
+ for (size_t i = 0; i < inputs.size(); i++) {
+ for (size_t j = 0; j < inputs[i].size(); j++) {
+ FileMetaData* filemeta = inputs[i][j];
+ size_added_by_compaction += filemeta->fd.GetFileSize();
+ }
+ }
+
+ // Update cur_compactions_reserved_size_ so concurrent compaction
+ // don't max out space
+ size_t needed_headroom =
+ cur_compactions_reserved_size_ + size_added_by_compaction +
+ compaction_buffer_size_;
+ if (max_allowed_space_ != 0 &&
+ (needed_headroom + total_files_size_ > max_allowed_space_)) {
+ return false;
+ }
+
+ // Implement more aggressive checks only if this DB instance has already
+ // seen a NoSpace() error. This is tin order to contain a single potentially
+ // misbehaving DB instance and prevent it from slowing down compactions of
+ // other DB instances
+ if (CheckFreeSpace() && bg_error == Status::NoSpace()) {
+ auto fn =
+ TableFileName(cfd->ioptions()->cf_paths, inputs[0][0]->fd.GetNumber(),
+ inputs[0][0]->fd.GetPathId());
+ uint64_t free_space = 0;
+ fs_->GetFreeSpace(fn, IOOptions(), &free_space, nullptr);
+ // needed_headroom is based on current size reserved by compactions,
+ // minus any files created by running compactions as they would count
+ // against the reserved size. If user didn't specify any compaction
+ // buffer, add reserved_disk_buffer_ that's calculated by default so the
+ // compaction doesn't end up leaving nothing for logs and flush SSTs
+ if (compaction_buffer_size_ == 0) {
+ needed_headroom += reserved_disk_buffer_;
+ }
+ needed_headroom -= in_progress_files_size_;
+ if (free_space < needed_headroom + size_added_by_compaction) {
+ // We hit the condition of not enough disk space
+ ROCKS_LOG_ERROR(logger_,
+ "free space [%" PRIu64
+ " bytes] is less than "
+ "needed headroom [%" ROCKSDB_PRIszt " bytes]\n",
+ free_space, needed_headroom);
+ return false;
+ }
+ }
+
+ cur_compactions_reserved_size_ += size_added_by_compaction;
+ // Take a snapshot of cur_compactions_reserved_size_ for when we encounter
+ // a NoSpace error.
+ free_space_trigger_ = cur_compactions_reserved_size_;
+ return true;
+}
+
+uint64_t SstFileManagerImpl::GetCompactionsReservedSize() {
+ MutexLock l(&mu_);
+ return cur_compactions_reserved_size_;
+}
+
+uint64_t SstFileManagerImpl::GetTotalSize() {
+ MutexLock l(&mu_);
+ return total_files_size_;
+}
+
+std::unordered_map<std::string, uint64_t>
+SstFileManagerImpl::GetTrackedFiles() {
+ MutexLock l(&mu_);
+ return tracked_files_;
+}
+
+int64_t SstFileManagerImpl::GetDeleteRateBytesPerSecond() {
+ return delete_scheduler_.GetRateBytesPerSecond();
+}
+
+void SstFileManagerImpl::SetDeleteRateBytesPerSecond(int64_t delete_rate) {
+ return delete_scheduler_.SetRateBytesPerSecond(delete_rate);
+}
+
+double SstFileManagerImpl::GetMaxTrashDBRatio() {
+ return delete_scheduler_.GetMaxTrashDBRatio();
+}
+
+void SstFileManagerImpl::SetMaxTrashDBRatio(double r) {
+ return delete_scheduler_.SetMaxTrashDBRatio(r);
+}
+
+uint64_t SstFileManagerImpl::GetTotalTrashSize() {
+ return delete_scheduler_.GetTotalTrashSize();
+}
+
+void SstFileManagerImpl::ReserveDiskBuffer(uint64_t size,
+ const std::string& path) {
+ MutexLock l(&mu_);
+
+ reserved_disk_buffer_ += size;
+ if (path_.empty()) {
+ path_ = path;
+ }
+}
+
+void SstFileManagerImpl::ClearError() {
+ while (true) {
+ MutexLock l(&mu_);
+
+ if (closing_) {
+ return;
+ }
+
+ uint64_t free_space = 0;
+ Status s = fs_->GetFreeSpace(path_, IOOptions(), &free_space, nullptr);
+ free_space = max_allowed_space_ > 0
+ ? std::min(max_allowed_space_, free_space)
+ : free_space;
+ if (s.ok()) {
+ // In case of multi-DB instances, some of them may have experienced a
+ // soft error and some a hard error. In the SstFileManagerImpl, a hard
+ // error will basically override previously reported soft errors. Once
+ // we clear the hard error, we don't keep track of previous errors for
+ // now
+ if (bg_err_.severity() == Status::Severity::kHardError) {
+ if (free_space < reserved_disk_buffer_) {
+ ROCKS_LOG_ERROR(logger_,
+ "free space [%" PRIu64
+ " bytes] is less than "
+ "required disk buffer [%" PRIu64 " bytes]\n",
+ free_space, reserved_disk_buffer_);
+ ROCKS_LOG_ERROR(logger_, "Cannot clear hard error\n");
+ s = Status::NoSpace();
+ }
+ } else if (bg_err_.severity() == Status::Severity::kSoftError) {
+ if (free_space < free_space_trigger_) {
+ ROCKS_LOG_WARN(logger_,
+ "free space [%" PRIu64
+ " bytes] is less than "
+ "free space for compaction trigger [%" PRIu64
+ " bytes]\n",
+ free_space, free_space_trigger_);
+ ROCKS_LOG_WARN(logger_, "Cannot clear soft error\n");
+ s = Status::NoSpace();
+ }
+ }
+ }
+
+ // Someone could have called CancelErrorRecovery() and the list could have
+ // become empty, so check again here
+ if (s.ok() && !error_handler_list_.empty()) {
+ auto error_handler = error_handler_list_.front();
+ // Since we will release the mutex, set cur_instance_ to signal to the
+ // shutdown thread, if it calls // CancelErrorRecovery() the meantime,
+ // to indicate that this DB instance is busy. The DB instance is
+ // guaranteed to not be deleted before RecoverFromBGError() returns,
+ // since the ErrorHandler::recovery_in_prog_ flag would be true
+ cur_instance_ = error_handler;
+ mu_.Unlock();
+ s = error_handler->RecoverFromBGError();
+ TEST_SYNC_POINT("SstFileManagerImpl::ErrorCleared");
+ mu_.Lock();
+ // The DB instance might have been deleted while we were
+ // waiting for the mutex, so check cur_instance_ to make sure its
+ // still non-null
+ if (cur_instance_) {
+ // Check for error again, since the instance may have recovered but
+ // immediately got another error. If that's the case, and the new
+ // error is also a NoSpace() non-fatal error, leave the instance in
+ // the list
+ Status err = cur_instance_->GetBGError();
+ if (s.ok() && err == Status::NoSpace() &&
+ err.severity() < Status::Severity::kFatalError) {
+ s = err;
+ }
+ cur_instance_ = nullptr;
+ }
+
+ if (s.ok() || s.IsShutdownInProgress() ||
+ (!s.ok() && s.severity() >= Status::Severity::kFatalError)) {
+ // If shutdown is in progress, abandon this handler instance
+ // and continue with the others
+ error_handler_list_.pop_front();
+ }
+ }
+
+ if (!error_handler_list_.empty()) {
+ // If there are more instances to be recovered, reschedule after 5
+ // seconds
+ int64_t wait_until = env_->NowMicros() + 5000000;
+ cv_.TimedWait(wait_until);
+ }
+
+ // Check again for error_handler_list_ empty, as a DB instance shutdown
+ // could have removed it from the queue while we were in timed wait
+ if (error_handler_list_.empty()) {
+ ROCKS_LOG_INFO(logger_, "Clearing error\n");
+ bg_err_ = Status::OK();
+ return;
+ }
+ }
+}
+
+void SstFileManagerImpl::StartErrorRecovery(ErrorHandler* handler,
+ Status bg_error) {
+ MutexLock l(&mu_);
+ if (bg_error.severity() == Status::Severity::kSoftError) {
+ if (bg_err_.ok()) {
+ // Setting bg_err_ basically means we're in degraded mode
+ // Assume that all pending compactions will fail similarly. The trigger
+ // for clearing this condition is set to current compaction reserved
+ // size, so we stop checking disk space available in
+ // EnoughRoomForCompaction once this much free space is available
+ bg_err_ = bg_error;
+ }
+ } else if (bg_error.severity() == Status::Severity::kHardError) {
+ bg_err_ = bg_error;
+ } else {
+ assert(false);
+ }
+
+ // If this is the first instance of this error, kick of a thread to poll
+ // and recover from this condition
+ if (error_handler_list_.empty()) {
+ error_handler_list_.push_back(handler);
+ // Release lock before calling join. Its ok to do so because
+ // error_handler_list_ is now non-empty, so no other invocation of this
+ // function will execute this piece of code
+ mu_.Unlock();
+ if (bg_thread_) {
+ bg_thread_->join();
+ }
+ // Start a new thread. The previous one would have exited.
+ bg_thread_.reset(new port::Thread(&SstFileManagerImpl::ClearError, this));
+ mu_.Lock();
+ } else {
+ // Check if this DB instance is already in the list
+ for (auto iter = error_handler_list_.begin();
+ iter != error_handler_list_.end(); ++iter) {
+ if ((*iter) == handler) {
+ return;
+ }
+ }
+ error_handler_list_.push_back(handler);
+ }
+}
+
+bool SstFileManagerImpl::CancelErrorRecovery(ErrorHandler* handler) {
+ MutexLock l(&mu_);
+
+ if (cur_instance_ == handler) {
+ // This instance is currently busy attempting to recover
+ // Nullify it so the recovery thread doesn't attempt to access it again
+ cur_instance_ = nullptr;
+ return false;
+ }
+
+ for (auto iter = error_handler_list_.begin();
+ iter != error_handler_list_.end(); ++iter) {
+ if ((*iter) == handler) {
+ error_handler_list_.erase(iter);
+ return true;
+ }
+ }
+ return false;
+}
+
+Status SstFileManagerImpl::ScheduleFileDeletion(
+ const std::string& file_path, const std::string& path_to_sync,
+ const bool force_bg) {
+ TEST_SYNC_POINT_CALLBACK("SstFileManagerImpl::ScheduleFileDeletion",
+ const_cast<std::string*>(&file_path));
+ return delete_scheduler_.DeleteFile(file_path, path_to_sync,
+ force_bg);
+}
+
+void SstFileManagerImpl::WaitForEmptyTrash() {
+ delete_scheduler_.WaitForEmptyTrash();
+}
+
+void SstFileManagerImpl::OnAddFileImpl(const std::string& file_path,
+ uint64_t file_size, bool compaction) {
+ auto tracked_file = tracked_files_.find(file_path);
+ if (tracked_file != tracked_files_.end()) {
+ // File was added before, we will just update the size
+ assert(!compaction);
+ total_files_size_ -= tracked_file->second;
+ total_files_size_ += file_size;
+ cur_compactions_reserved_size_ -= file_size;
+ } else {
+ total_files_size_ += file_size;
+ if (compaction) {
+ // Keep track of the size of files created by in-progress compactions.
+ // When calculating whether there's enough headroom for new compactions,
+ // this will be subtracted from cur_compactions_reserved_size_.
+ // Otherwise, compactions will be double counted.
+ in_progress_files_size_ += file_size;
+ in_progress_files_.insert(file_path);
+ }
+ }
+ tracked_files_[file_path] = file_size;
+}
+
+void SstFileManagerImpl::OnDeleteFileImpl(const std::string& file_path) {
+ auto tracked_file = tracked_files_.find(file_path);
+ if (tracked_file == tracked_files_.end()) {
+ // File is not tracked
+ assert(in_progress_files_.find(file_path) == in_progress_files_.end());
+ return;
+ }
+
+ total_files_size_ -= tracked_file->second;
+ // Check if it belonged to an in-progress compaction
+ if (in_progress_files_.find(file_path) != in_progress_files_.end()) {
+ in_progress_files_size_ -= tracked_file->second;
+ in_progress_files_.erase(file_path);
+ }
+ tracked_files_.erase(tracked_file);
+}
+
+SstFileManager* NewSstFileManager(Env* env, std::shared_ptr<Logger> info_log,
+ std::string trash_dir,
+ int64_t rate_bytes_per_sec,
+ bool delete_existing_trash, Status* status,
+ double max_trash_db_ratio,
+ uint64_t bytes_max_delete_chunk) {
+ std::shared_ptr<FileSystem> fs;
+
+ if (env == Env::Default()) {
+ fs = FileSystem::Default();
+ } else {
+ fs.reset(new LegacyFileSystemWrapper(env));
+ }
+
+ return NewSstFileManager(env, fs, info_log, trash_dir, rate_bytes_per_sec,
+ delete_existing_trash, status, max_trash_db_ratio,
+ bytes_max_delete_chunk);
+}
+
+SstFileManager* NewSstFileManager(Env* env, std::shared_ptr<FileSystem> fs,
+ std::shared_ptr<Logger> info_log,
+ const std::string& trash_dir,
+ int64_t rate_bytes_per_sec,
+ bool delete_existing_trash, Status* status,
+ double max_trash_db_ratio,
+ uint64_t bytes_max_delete_chunk) {
+ SstFileManagerImpl* res =
+ new SstFileManagerImpl(env, fs, info_log, rate_bytes_per_sec,
+ max_trash_db_ratio, bytes_max_delete_chunk);
+
+ // trash_dir is deprecated and not needed anymore, but if user passed it
+ // we will still remove files in it.
+ Status s;
+ if (delete_existing_trash && trash_dir != "") {
+ std::vector<std::string> files_in_trash;
+ s = fs->GetChildren(trash_dir, IOOptions(), &files_in_trash, nullptr);
+ if (s.ok()) {
+ for (const std::string& trash_file : files_in_trash) {
+ if (trash_file == "." || trash_file == "..") {
+ continue;
+ }
+
+ std::string path_in_trash = trash_dir + "/" + trash_file;
+ res->OnAddFile(path_in_trash);
+ Status file_delete =
+ res->ScheduleFileDeletion(path_in_trash, trash_dir);
+ if (s.ok() && !file_delete.ok()) {
+ s = file_delete;
+ }
+ }
+ }
+ }
+
+ if (status) {
+ *status = s;
+ }
+
+ return res;
+}
+
+#else
+
+SstFileManager* NewSstFileManager(Env* /*env*/,
+ std::shared_ptr<Logger> /*info_log*/,
+ std::string /*trash_dir*/,
+ int64_t /*rate_bytes_per_sec*/,
+ bool /*delete_existing_trash*/,
+ Status* status, double /*max_trash_db_ratio*/,
+ uint64_t /*bytes_max_delete_chunk*/) {
+ if (status) {
+ *status =
+ Status::NotSupported("SstFileManager is not supported in ROCKSDB_LITE");
+ }
+ return nullptr;
+}
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/sst_file_manager_impl.h b/storage/rocksdb/rocksdb/file/sst_file_manager_impl.h
new file mode 100644
index 00000000..323ffc7b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/sst_file_manager_impl.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+
+#include "port/port.h"
+
+#include "db/compaction/compaction.h"
+#include "db/error_handler.h"
+#include "file/delete_scheduler.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/sst_file_manager.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Logger;
+
+// SstFileManager is used to track SST files in the DB and control there
+// deletion rate.
+// All SstFileManager public functions are thread-safe.
+class SstFileManagerImpl : public SstFileManager {
+ public:
+ explicit SstFileManagerImpl(Env* env, std::shared_ptr<FileSystem> fs,
+ std::shared_ptr<Logger> logger,
+ int64_t rate_bytes_per_sec,
+ double max_trash_db_ratio,
+ uint64_t bytes_max_delete_chunk);
+
+ ~SstFileManagerImpl();
+
+ // DB will call OnAddFile whenever a new sst file is added.
+ Status OnAddFile(const std::string& file_path, bool compaction = false);
+
+ // Overload where size of the file is provided by the caller rather than
+ // queried from the filesystem. This is an optimization.
+ Status OnAddFile(const std::string& file_path, uint64_t file_size,
+ bool compaction);
+
+ // DB will call OnDeleteFile whenever an sst file is deleted.
+ Status OnDeleteFile(const std::string& file_path);
+
+ // DB will call OnMoveFile whenever an sst file is move to a new path.
+ Status OnMoveFile(const std::string& old_path, const std::string& new_path,
+ uint64_t* file_size = nullptr);
+
+ // Update the maximum allowed space that should be used by RocksDB, if
+ // the total size of the SST files exceeds max_allowed_space, writes to
+ // RocksDB will fail.
+ //
+ // Setting max_allowed_space to 0 will disable this feature, maximum allowed
+ // space will be infinite (Default value).
+ //
+ // thread-safe.
+ void SetMaxAllowedSpaceUsage(uint64_t max_allowed_space) override;
+
+ void SetCompactionBufferSize(uint64_t compaction_buffer_size) override;
+
+ // Return true if the total size of SST files exceeded the maximum allowed
+ // space usage.
+ //
+ // thread-safe.
+ bool IsMaxAllowedSpaceReached() override;
+
+ bool IsMaxAllowedSpaceReachedIncludingCompactions() override;
+
+ // Returns true is there is enough (approximate) space for the specified
+ // compaction. Space is approximate because this function conservatively
+ // estimates how much space is currently being used by compactions (i.e.
+ // if a compaction has started, this function bumps the used space by
+ // the full compaction size).
+ bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
+ const std::vector<CompactionInputFiles>& inputs,
+ Status bg_error);
+
+ // Bookkeeping so total_file_sizes_ goes back to normal after compaction
+ // finishes
+ void OnCompactionCompletion(Compaction* c);
+
+ uint64_t GetCompactionsReservedSize();
+
+ // Return the total size of all tracked files.
+ uint64_t GetTotalSize() override;
+
+ // Return a map containing all tracked files and there corresponding sizes.
+ std::unordered_map<std::string, uint64_t> GetTrackedFiles() override;
+
+ // Return delete rate limit in bytes per second.
+ virtual int64_t GetDeleteRateBytesPerSecond() override;
+
+ // Update the delete rate limit in bytes per second.
+ virtual void SetDeleteRateBytesPerSecond(int64_t delete_rate) override;
+
+ // Return trash/DB size ratio where new files will be deleted immediately
+ virtual double GetMaxTrashDBRatio() override;
+
+ // Update trash/DB size ratio where new files will be deleted immediately
+ virtual void SetMaxTrashDBRatio(double ratio) override;
+
+ // Return the total size of trash files
+ uint64_t GetTotalTrashSize() override;
+
+ // Called by each DB instance using this sst file manager to reserve
+ // disk buffer space for recovery from out of space errors
+ void ReserveDiskBuffer(uint64_t buffer, const std::string& path);
+
+ // Set a flag upon encountering disk full. May enqueue the ErrorHandler
+ // instance for background polling and recovery
+ void StartErrorRecovery(ErrorHandler* db, Status bg_error);
+
+ // Remove the given Errorhandler instance from the recovery queue. Its
+ // not guaranteed
+ bool CancelErrorRecovery(ErrorHandler* db);
+
+ // Mark file as trash and schedule it's deletion. If force_bg is set, it
+ // forces the file to be deleting in the background regardless of DB size,
+ // except when rate limited delete is disabled
+ virtual Status ScheduleFileDeletion(const std::string& file_path,
+ const std::string& dir_to_sync,
+ const bool force_bg = false);
+
+ // Wait for all files being deleteing in the background to finish or for
+ // destructor to be called.
+ virtual void WaitForEmptyTrash();
+
+ DeleteScheduler* delete_scheduler() { return &delete_scheduler_; }
+
+ // Stop the error recovery background thread. This should be called only
+ // once in the object's lifetime, and before the destructor
+ void Close();
+
+ private:
+ // REQUIRES: mutex locked
+ void OnAddFileImpl(const std::string& file_path, uint64_t file_size,
+ bool compaction);
+ // REQUIRES: mutex locked
+ void OnDeleteFileImpl(const std::string& file_path);
+
+ void ClearError();
+ bool CheckFreeSpace() {
+ return bg_err_.severity() == Status::Severity::kSoftError;
+ }
+
+ Env* env_;
+ std::shared_ptr<FileSystem> fs_;
+ std::shared_ptr<Logger> logger_;
+ // Mutex to protect tracked_files_, total_files_size_
+ port::Mutex mu_;
+ // The summation of the sizes of all files in tracked_files_ map
+ uint64_t total_files_size_;
+ // The summation of all output files of in-progress compactions
+ uint64_t in_progress_files_size_;
+ // Compactions should only execute if they can leave at least
+ // this amount of buffer space for logs and flushes
+ uint64_t compaction_buffer_size_;
+ // Estimated size of the current ongoing compactions
+ uint64_t cur_compactions_reserved_size_;
+ // A map containing all tracked files and there sizes
+ // file_path => file_size
+ std::unordered_map<std::string, uint64_t> tracked_files_;
+ // A set of files belonging to in-progress compactions
+ std::unordered_set<std::string> in_progress_files_;
+ // The maximum allowed space (in bytes) for sst files.
+ uint64_t max_allowed_space_;
+ // DeleteScheduler used to throttle file deletition.
+ DeleteScheduler delete_scheduler_;
+ port::CondVar cv_;
+ // Flag to force error recovery thread to exit
+ bool closing_;
+ // Background error recovery thread
+ std::unique_ptr<port::Thread> bg_thread_;
+ // A path in the filesystem corresponding to this SFM. This is used for
+ // calling Env::GetFreeSpace. Posix requires a path in the filesystem
+ std::string path_;
+ // Save the current background error
+ Status bg_err_;
+ // Amount of free disk headroom before allowing recovery from hard errors
+ uint64_t reserved_disk_buffer_;
+ // For soft errors, amount of free disk space before we can allow
+ // compactions to run full throttle. If disk space is below this trigger,
+ // compactions will be gated by free disk space > input size
+ uint64_t free_space_trigger_;
+ // List of database error handler instances tracked by this sst file manager
+ std::list<ErrorHandler*> error_handler_list_;
+ // Pointer to ErrorHandler instance that is currently processing recovery
+ ErrorHandler* cur_instance_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/file/writable_file_writer.cc b/storage/rocksdb/rocksdb/file/writable_file_writer.cc
new file mode 100644
index 00000000..d5894c17
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/writable_file_writer.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/writable_file_writer.h"
+
+#include <algorithm>
+#include <mutex>
+
+#include "db/version_edit.h"
+#include "monitoring/histogram.h"
+#include "monitoring/iostats_context_imp.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+#include "util/rate_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+Status WritableFileWriter::Append(const Slice& data) {
+ const char* src = data.data();
+ size_t left = data.size();
+ Status s;
+ pending_sync_ = true;
+
+ TEST_KILL_RANDOM("WritableFileWriter::Append:0",
+ rocksdb_kill_odds * REDUCE_ODDS2);
+
+ {
+ IOSTATS_TIMER_GUARD(prepare_write_nanos);
+ TEST_SYNC_POINT("WritableFileWriter::Append:BeforePrepareWrite");
+ writable_file_->PrepareWrite(static_cast<size_t>(GetFileSize()), left,
+ IOOptions(), nullptr);
+ }
+
+ // See whether we need to enlarge the buffer to avoid the flush
+ if (buf_.Capacity() - buf_.CurrentSize() < left) {
+ for (size_t cap = buf_.Capacity();
+ cap < max_buffer_size_; // There is still room to increase
+ cap *= 2) {
+ // See whether the next available size is large enough.
+ // Buffer will never be increased to more than max_buffer_size_.
+ size_t desired_capacity = std::min(cap * 2, max_buffer_size_);
+ if (desired_capacity - buf_.CurrentSize() >= left ||
+ (use_direct_io() && desired_capacity == max_buffer_size_)) {
+ buf_.AllocateNewBuffer(desired_capacity, true);
+ break;
+ }
+ }
+ }
+
+ // Flush only when buffered I/O
+ if (!use_direct_io() && (buf_.Capacity() - buf_.CurrentSize()) < left) {
+ if (buf_.CurrentSize() > 0) {
+ s = Flush();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ assert(buf_.CurrentSize() == 0);
+ }
+
+ // We never write directly to disk with direct I/O on.
+ // or we simply use it for its original purpose to accumulate many small
+ // chunks
+ if (use_direct_io() || (buf_.Capacity() >= left)) {
+ while (left > 0) {
+ size_t appended = buf_.Append(src, left);
+ left -= appended;
+ src += appended;
+
+ if (left > 0) {
+ s = Flush();
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ } else {
+ // Writing directly to file bypassing the buffer
+ assert(buf_.CurrentSize() == 0);
+ s = WriteBuffered(src, left);
+ }
+
+ TEST_KILL_RANDOM("WritableFileWriter::Append:1", rocksdb_kill_odds);
+ if (s.ok()) {
+ filesize_ += data.size();
+ CalculateFileChecksum(data);
+ }
+ return s;
+}
+
+Status WritableFileWriter::Pad(const size_t pad_bytes) {
+ assert(pad_bytes < kDefaultPageSize);
+ size_t left = pad_bytes;
+ size_t cap = buf_.Capacity() - buf_.CurrentSize();
+
+ // Assume pad_bytes is small compared to buf_ capacity. So we always
+ // use buf_ rather than write directly to file in certain cases like
+ // Append() does.
+ while (left) {
+ size_t append_bytes = std::min(cap, left);
+ buf_.PadWith(append_bytes, 0);
+ left -= append_bytes;
+ if (left > 0) {
+ Status s = Flush();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ cap = buf_.Capacity() - buf_.CurrentSize();
+ }
+ pending_sync_ = true;
+ filesize_ += pad_bytes;
+ return Status::OK();
+}
+
+Status WritableFileWriter::Close() {
+ // Do not quit immediately on failure the file MUST be closed
+ Status s;
+
+ // Possible to close it twice now as we MUST close
+ // in __dtor, simply flushing is not enough
+ // Windows when pre-allocating does not fill with zeros
+ // also with unbuffered access we also set the end of data.
+ if (!writable_file_) {
+ return s;
+ }
+
+ s = Flush(); // flush cache to OS
+
+ Status interim;
+ // In direct I/O mode we write whole pages so
+ // we need to let the file know where data ends.
+ if (use_direct_io()) {
+ interim = writable_file_->Truncate(filesize_, IOOptions(), nullptr);
+ if (interim.ok()) {
+ interim = writable_file_->Fsync(IOOptions(), nullptr);
+ }
+ if (!interim.ok() && s.ok()) {
+ s = interim;
+ }
+ }
+
+ TEST_KILL_RANDOM("WritableFileWriter::Close:0", rocksdb_kill_odds);
+ interim = writable_file_->Close(IOOptions(), nullptr);
+ if (!interim.ok() && s.ok()) {
+ s = interim;
+ }
+
+ writable_file_.reset();
+ TEST_KILL_RANDOM("WritableFileWriter::Close:1", rocksdb_kill_odds);
+
+ return s;
+}
+
+// write out the cached data to the OS cache or storage if direct I/O
+// enabled
+Status WritableFileWriter::Flush() {
+ Status s;
+ TEST_KILL_RANDOM("WritableFileWriter::Flush:0",
+ rocksdb_kill_odds * REDUCE_ODDS2);
+
+ if (buf_.CurrentSize() > 0) {
+ if (use_direct_io()) {
+#ifndef ROCKSDB_LITE
+ if (pending_sync_) {
+ s = WriteDirect();
+ }
+#endif // !ROCKSDB_LITE
+ } else {
+ s = WriteBuffered(buf_.BufferStart(), buf_.CurrentSize());
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ s = writable_file_->Flush(IOOptions(), nullptr);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ // sync OS cache to disk for every bytes_per_sync_
+ // TODO: give log file and sst file different options (log
+ // files could be potentially cached in OS for their whole
+ // life time, thus we might not want to flush at all).
+
+ // We try to avoid sync to the last 1MB of data. For two reasons:
+ // (1) avoid rewrite the same page that is modified later.
+ // (2) for older version of OS, write can block while writing out
+ // the page.
+ // Xfs does neighbor page flushing outside of the specified ranges. We
+ // need to make sure sync range is far from the write offset.
+ if (!use_direct_io() && bytes_per_sync_) {
+ const uint64_t kBytesNotSyncRange =
+ 1024 * 1024; // recent 1MB is not synced.
+ const uint64_t kBytesAlignWhenSync = 4 * 1024; // Align 4KB.
+ if (filesize_ > kBytesNotSyncRange) {
+ uint64_t offset_sync_to = filesize_ - kBytesNotSyncRange;
+ offset_sync_to -= offset_sync_to % kBytesAlignWhenSync;
+ assert(offset_sync_to >= last_sync_size_);
+ if (offset_sync_to > 0 &&
+ offset_sync_to - last_sync_size_ >= bytes_per_sync_) {
+ s = RangeSync(last_sync_size_, offset_sync_to - last_sync_size_);
+ last_sync_size_ = offset_sync_to;
+ }
+ }
+ }
+
+ return s;
+}
+
+const char* WritableFileWriter::GetFileChecksumFuncName() const {
+ if (checksum_func_ != nullptr) {
+ return checksum_func_->Name();
+ } else {
+ return kUnknownFileChecksumFuncName.c_str();
+ }
+}
+
+Status WritableFileWriter::Sync(bool use_fsync) {
+ Status s = Flush();
+ if (!s.ok()) {
+ return s;
+ }
+ TEST_KILL_RANDOM("WritableFileWriter::Sync:0", rocksdb_kill_odds);
+ if (!use_direct_io() && pending_sync_) {
+ s = SyncInternal(use_fsync);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ TEST_KILL_RANDOM("WritableFileWriter::Sync:1", rocksdb_kill_odds);
+ pending_sync_ = false;
+ return Status::OK();
+}
+
+Status WritableFileWriter::SyncWithoutFlush(bool use_fsync) {
+ if (!writable_file_->IsSyncThreadSafe()) {
+ return Status::NotSupported(
+ "Can't WritableFileWriter::SyncWithoutFlush() because "
+ "WritableFile::IsSyncThreadSafe() is false");
+ }
+ TEST_SYNC_POINT("WritableFileWriter::SyncWithoutFlush:1");
+ Status s = SyncInternal(use_fsync);
+ TEST_SYNC_POINT("WritableFileWriter::SyncWithoutFlush:2");
+ return s;
+}
+
+Status WritableFileWriter::SyncInternal(bool use_fsync) {
+ Status s;
+ IOSTATS_TIMER_GUARD(fsync_nanos);
+ TEST_SYNC_POINT("WritableFileWriter::SyncInternal:0");
+ auto prev_perf_level = GetPerfLevel();
+ IOSTATS_CPU_TIMER_GUARD(cpu_write_nanos, env_);
+ if (use_fsync) {
+ s = writable_file_->Fsync(IOOptions(), nullptr);
+ } else {
+ s = writable_file_->Sync(IOOptions(), nullptr);
+ }
+ SetPerfLevel(prev_perf_level);
+ return s;
+}
+
+Status WritableFileWriter::RangeSync(uint64_t offset, uint64_t nbytes) {
+ IOSTATS_TIMER_GUARD(range_sync_nanos);
+ TEST_SYNC_POINT("WritableFileWriter::RangeSync:0");
+ return writable_file_->RangeSync(offset, nbytes, IOOptions(), nullptr);
+}
+
+// This method writes to disk the specified data and makes use of the rate
+// limiter if available
+Status WritableFileWriter::WriteBuffered(const char* data, size_t size) {
+ Status s;
+ assert(!use_direct_io());
+ const char* src = data;
+ size_t left = size;
+
+ while (left > 0) {
+ size_t allowed;
+ if (rate_limiter_ != nullptr) {
+ allowed = rate_limiter_->RequestToken(
+ left, 0 /* alignment */, writable_file_->GetIOPriority(), stats_,
+ RateLimiter::OpType::kWrite);
+ } else {
+ allowed = left;
+ }
+
+ {
+ IOSTATS_TIMER_GUARD(write_nanos);
+ TEST_SYNC_POINT("WritableFileWriter::Flush:BeforeAppend");
+
+#ifndef ROCKSDB_LITE
+ FileOperationInfo::TimePoint start_ts;
+ uint64_t old_size = writable_file_->GetFileSize(IOOptions(), nullptr);
+ if (ShouldNotifyListeners()) {
+ start_ts = std::chrono::system_clock::now();
+ old_size = next_write_offset_;
+ }
+#endif
+ {
+ auto prev_perf_level = GetPerfLevel();
+ IOSTATS_CPU_TIMER_GUARD(cpu_write_nanos, env_);
+ s = writable_file_->Append(Slice(src, allowed), IOOptions(), nullptr);
+ SetPerfLevel(prev_perf_level);
+ }
+#ifndef ROCKSDB_LITE
+ if (ShouldNotifyListeners()) {
+ auto finish_ts = std::chrono::system_clock::now();
+ NotifyOnFileWriteFinish(old_size, allowed, start_ts, finish_ts, s);
+ }
+#endif
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ IOSTATS_ADD(bytes_written, allowed);
+ TEST_KILL_RANDOM("WritableFileWriter::WriteBuffered:0", rocksdb_kill_odds);
+
+ left -= allowed;
+ src += allowed;
+ }
+ buf_.Size(0);
+ return s;
+}
+
+void WritableFileWriter::CalculateFileChecksum(const Slice& data) {
+ if (checksum_func_ != nullptr) {
+ if (is_first_checksum_) {
+ file_checksum_ = checksum_func_->Value(data.data(), data.size());
+ is_first_checksum_ = false;
+ } else {
+ file_checksum_ =
+ checksum_func_->Extend(file_checksum_, data.data(), data.size());
+ }
+ }
+}
+
+// This flushes the accumulated data in the buffer. We pad data with zeros if
+// necessary to the whole page.
+// However, during automatic flushes padding would not be necessary.
+// We always use RateLimiter if available. We move (Refit) any buffer bytes
+// that are left over the
+// whole number of pages to be written again on the next flush because we can
+// only write on aligned
+// offsets.
+#ifndef ROCKSDB_LITE
+Status WritableFileWriter::WriteDirect() {
+ assert(use_direct_io());
+ Status s;
+ const size_t alignment = buf_.Alignment();
+ assert((next_write_offset_ % alignment) == 0);
+
+ // Calculate whole page final file advance if all writes succeed
+ size_t file_advance = TruncateToPageBoundary(alignment, buf_.CurrentSize());
+
+ // Calculate the leftover tail, we write it here padded with zeros BUT we
+ // will write
+ // it again in the future either on Close() OR when the current whole page
+ // fills out
+ size_t leftover_tail = buf_.CurrentSize() - file_advance;
+
+ // Round up and pad
+ buf_.PadToAlignmentWith(0);
+
+ const char* src = buf_.BufferStart();
+ uint64_t write_offset = next_write_offset_;
+ size_t left = buf_.CurrentSize();
+
+ while (left > 0) {
+ // Check how much is allowed
+ size_t size;
+ if (rate_limiter_ != nullptr) {
+ size = rate_limiter_->RequestToken(left, buf_.Alignment(),
+ writable_file_->GetIOPriority(),
+ stats_, RateLimiter::OpType::kWrite);
+ } else {
+ size = left;
+ }
+
+ {
+ IOSTATS_TIMER_GUARD(write_nanos);
+ TEST_SYNC_POINT("WritableFileWriter::Flush:BeforeAppend");
+ FileOperationInfo::TimePoint start_ts;
+ if (ShouldNotifyListeners()) {
+ start_ts = std::chrono::system_clock::now();
+ }
+ // direct writes must be positional
+ s = writable_file_->PositionedAppend(Slice(src, size), write_offset,
+ IOOptions(), nullptr);
+ if (ShouldNotifyListeners()) {
+ auto finish_ts = std::chrono::system_clock::now();
+ NotifyOnFileWriteFinish(write_offset, size, start_ts, finish_ts, s);
+ }
+ if (!s.ok()) {
+ buf_.Size(file_advance + leftover_tail);
+ return s;
+ }
+ }
+
+ IOSTATS_ADD(bytes_written, size);
+ left -= size;
+ src += size;
+ write_offset += size;
+ assert((next_write_offset_ % alignment) == 0);
+ }
+
+ if (s.ok()) {
+ // Move the tail to the beginning of the buffer
+ // This never happens during normal Append but rather during
+ // explicit call to Flush()/Sync() or Close()
+ buf_.RefitTail(file_advance, leftover_tail);
+ // This is where we start writing next time which may or not be
+ // the actual file size on disk. They match if the buffer size
+ // is a multiple of whole pages otherwise filesize_ is leftover_tail
+ // behind
+ next_write_offset_ += file_advance;
+ }
+ return s;
+}
+#endif // !ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/file/writable_file_writer.h b/storage/rocksdb/rocksdb/file/writable_file_writer.h
new file mode 100644
index 00000000..12311071
--- /dev/null
+++ b/storage/rocksdb/rocksdb/file/writable_file_writer.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <string>
+#include "db/version_edit.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_checksum.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/rate_limiter.h"
+#include "test_util/sync_point.h"
+#include "util/aligned_buffer.h"
+
+namespace ROCKSDB_NAMESPACE {
+class Statistics;
+
+// WritableFileWriter is a wrapper on top of Env::WritableFile. It provides
+// facilities to:
+// - Handle Buffered and Direct writes.
+// - Rate limit writes.
+// - Flush and Sync the data to the underlying filesystem.
+// - Notify any interested listeners on the completion of a write.
+// - Update IO stats.
+class WritableFileWriter {
+ private:
+#ifndef ROCKSDB_LITE
+ void NotifyOnFileWriteFinish(uint64_t offset, size_t length,
+ const FileOperationInfo::TimePoint& start_ts,
+ const FileOperationInfo::TimePoint& finish_ts,
+ const Status& status) {
+ FileOperationInfo info(file_name_, start_ts, finish_ts);
+ info.offset = offset;
+ info.length = length;
+ info.status = status;
+
+ for (auto& listener : listeners_) {
+ listener->OnFileWriteFinish(info);
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ bool ShouldNotifyListeners() const { return !listeners_.empty(); }
+ void CalculateFileChecksum(const Slice& data);
+
+ std::unique_ptr<FSWritableFile> writable_file_;
+ std::string file_name_;
+ Env* env_;
+ AlignedBuffer buf_;
+ size_t max_buffer_size_;
+ // Actually written data size can be used for truncate
+ // not counting padding data
+ uint64_t filesize_;
+#ifndef ROCKSDB_LITE
+ // This is necessary when we use unbuffered access
+ // and writes must happen on aligned offsets
+ // so we need to go back and write that page again
+ uint64_t next_write_offset_;
+#endif // ROCKSDB_LITE
+ bool pending_sync_;
+ uint64_t last_sync_size_;
+ uint64_t bytes_per_sync_;
+ RateLimiter* rate_limiter_;
+ Statistics* stats_;
+ std::vector<std::shared_ptr<EventListener>> listeners_;
+ FileChecksumFunc* checksum_func_;
+ std::string file_checksum_ = kUnknownFileChecksum;
+ bool is_first_checksum_ = true;
+
+ public:
+ WritableFileWriter(
+ std::unique_ptr<FSWritableFile>&& file, const std::string& _file_name,
+ const FileOptions& options, Env* env = nullptr,
+ Statistics* stats = nullptr,
+ const std::vector<std::shared_ptr<EventListener>>& listeners = {},
+ FileChecksumFunc* checksum_func = nullptr)
+ : writable_file_(std::move(file)),
+ file_name_(_file_name),
+ env_(env),
+ buf_(),
+ max_buffer_size_(options.writable_file_max_buffer_size),
+ filesize_(0),
+#ifndef ROCKSDB_LITE
+ next_write_offset_(0),
+#endif // ROCKSDB_LITE
+ pending_sync_(false),
+ last_sync_size_(0),
+ bytes_per_sync_(options.bytes_per_sync),
+ rate_limiter_(options.rate_limiter),
+ stats_(stats),
+ listeners_(),
+ checksum_func_(checksum_func) {
+ TEST_SYNC_POINT_CALLBACK("WritableFileWriter::WritableFileWriter:0",
+ reinterpret_cast<void*>(max_buffer_size_));
+ buf_.Alignment(writable_file_->GetRequiredBufferAlignment());
+ buf_.AllocateNewBuffer(std::min((size_t)65536, max_buffer_size_));
+#ifndef ROCKSDB_LITE
+ std::for_each(listeners.begin(), listeners.end(),
+ [this](const std::shared_ptr<EventListener>& e) {
+ if (e->ShouldBeNotifiedOnFileIO()) {
+ listeners_.emplace_back(e);
+ }
+ });
+#else // !ROCKSDB_LITE
+ (void)listeners;
+#endif
+ }
+
+ WritableFileWriter(const WritableFileWriter&) = delete;
+
+ WritableFileWriter& operator=(const WritableFileWriter&) = delete;
+
+ ~WritableFileWriter() { Close(); }
+
+ std::string file_name() const { return file_name_; }
+
+ Status Append(const Slice& data);
+
+ Status Pad(const size_t pad_bytes);
+
+ Status Flush();
+
+ Status Close();
+
+ Status Sync(bool use_fsync);
+
+ // Sync only the data that was already Flush()ed. Safe to call concurrently
+ // with Append() and Flush(). If !writable_file_->IsSyncThreadSafe(),
+ // returns NotSupported status.
+ Status SyncWithoutFlush(bool use_fsync);
+
+ uint64_t GetFileSize() const { return filesize_; }
+
+ Status InvalidateCache(size_t offset, size_t length) {
+ return writable_file_->InvalidateCache(offset, length);
+ }
+
+ FSWritableFile* writable_file() const { return writable_file_.get(); }
+
+ bool use_direct_io() { return writable_file_->use_direct_io(); }
+
+ bool TEST_BufferIsEmpty() { return buf_.CurrentSize() == 0; }
+
+ void TEST_SetFileChecksumFunc(FileChecksumFunc* checksum_func) {
+ checksum_func_ = checksum_func;
+ }
+
+ const std::string& GetFileChecksum() const { return file_checksum_; }
+
+ const char* GetFileChecksumFuncName() const;
+
+ private:
+ // Used when os buffering is OFF and we are writing
+ // DMA such as in Direct I/O mode
+#ifndef ROCKSDB_LITE
+ Status WriteDirect();
+#endif // !ROCKSDB_LITE
+ // Normal write
+ Status WriteBuffered(const char* data, size_t size);
+ Status RangeSync(uint64_t offset, uint64_t nbytes);
+ Status SyncInternal(bool use_fsync);
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/hdfs/README b/storage/rocksdb/rocksdb/hdfs/README
new file mode 100644
index 00000000..90365116
--- /dev/null
+++ b/storage/rocksdb/rocksdb/hdfs/README
@@ -0,0 +1,23 @@
+This directory contains the hdfs extensions needed to make rocksdb store
+files in HDFS.
+
+It has been compiled and testing against CDH 4.4 (2.0.0+1475-1.cdh4.4.0.p0.23~precise-cdh4.4.0).
+
+The configuration assumes that packages libhdfs0, libhdfs0-dev are
+installed which basically means that hdfs.h is in /usr/include and libhdfs in /usr/lib
+
+The env_hdfs.h file defines the rocksdb objects that are needed to talk to an
+underlying filesystem.
+
+If you want to compile rocksdb with hdfs support, please set the following
+environment variables appropriately (also defined in setup.sh for convenience)
+ USE_HDFS=1
+ JAVA_HOME=/usr/local/jdk-7u79-64
+ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/jdk-7u79-64/jre/lib/amd64/server:/usr/local/jdk-7u79-64/jre/lib/amd64/:./snappy/libs
+ make clean all db_bench
+
+To run dbbench,
+ set CLASSPATH to include your hadoop distribution
+ db_bench --hdfs="hdfs://hbaseudbperf001.snc1.facebook.com:9000"
+
+
diff --git a/storage/rocksdb/rocksdb/hdfs/env_hdfs.h b/storage/rocksdb/rocksdb/hdfs/env_hdfs.h
new file mode 100644
index 00000000..6005c366
--- /dev/null
+++ b/storage/rocksdb/rocksdb/hdfs/env_hdfs.h
@@ -0,0 +1,384 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <stdio.h>
+#include <time.h>
+#include <iostream>
+#include "port/sys_time.h"
+#include "rocksdb/env.h"
+#include "rocksdb/status.h"
+
+#ifdef USE_HDFS
+#include <hdfs.h>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Thrown during execution when there is an issue with the supplied
+// arguments.
+class HdfsUsageException : public std::exception { };
+
+// A simple exception that indicates something went wrong that is not
+// recoverable. The intention is for the message to be printed (with
+// nothing else) and the process terminate.
+class HdfsFatalException : public std::exception {
+public:
+ explicit HdfsFatalException(const std::string& s) : what_(s) { }
+ virtual ~HdfsFatalException() throw() { }
+ virtual const char* what() const throw() {
+ return what_.c_str();
+ }
+private:
+ const std::string what_;
+};
+
+//
+// The HDFS environment for rocksdb. This class overrides all the
+// file/dir access methods and delegates the thread-mgmt methods to the
+// default posix environment.
+//
+class HdfsEnv : public Env {
+
+ public:
+ explicit HdfsEnv(const std::string& fsname) : fsname_(fsname) {
+ posixEnv = Env::Default();
+ fileSys_ = connectToPath(fsname_);
+ }
+
+ virtual ~HdfsEnv() {
+ fprintf(stderr, "Destroying HdfsEnv::Default()\n");
+ hdfsDisconnect(fileSys_);
+ }
+
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override;
+
+ Status FileExists(const std::string& fname) override;
+
+ Status GetChildren(const std::string& path,
+ std::vector<std::string>* result) override;
+
+ Status DeleteFile(const std::string& fname) override;
+
+ Status CreateDir(const std::string& name) override;
+
+ Status CreateDirIfMissing(const std::string& name) override;
+
+ Status DeleteDir(const std::string& name) override;
+
+ Status GetFileSize(const std::string& fname, uint64_t* size) override;
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override;
+
+ Status RenameFile(const std::string& src, const std::string& target) override;
+
+ Status LinkFile(const std::string& /*src*/,
+ const std::string& /*target*/) override {
+ return Status::NotSupported(); // not supported
+ }
+
+ Status LockFile(const std::string& fname, FileLock** lock) override;
+
+ Status UnlockFile(FileLock* lock) override;
+
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override;
+
+ void Schedule(void (*function)(void* arg), void* arg, Priority pri = LOW,
+ void* tag = nullptr,
+ void (*unschedFunction)(void* arg) = 0) override {
+ posixEnv->Schedule(function, arg, pri, tag, unschedFunction);
+ }
+
+ int UnSchedule(void* tag, Priority pri) override {
+ return posixEnv->UnSchedule(tag, pri);
+ }
+
+ void StartThread(void (*function)(void* arg), void* arg) override {
+ posixEnv->StartThread(function, arg);
+ }
+
+ void WaitForJoin() override { posixEnv->WaitForJoin(); }
+
+ unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override {
+ return posixEnv->GetThreadPoolQueueLen(pri);
+ }
+
+ Status GetTestDirectory(std::string* path) override {
+ return posixEnv->GetTestDirectory(path);
+ }
+
+ uint64_t NowMicros() override { return posixEnv->NowMicros(); }
+
+ void SleepForMicroseconds(int micros) override {
+ posixEnv->SleepForMicroseconds(micros);
+ }
+
+ Status GetHostName(char* name, uint64_t len) override {
+ return posixEnv->GetHostName(name, len);
+ }
+
+ Status GetCurrentTime(int64_t* unix_time) override {
+ return posixEnv->GetCurrentTime(unix_time);
+ }
+
+ Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) override {
+ return posixEnv->GetAbsolutePath(db_path, output_path);
+ }
+
+ void SetBackgroundThreads(int number, Priority pri = LOW) override {
+ posixEnv->SetBackgroundThreads(number, pri);
+ }
+
+ int GetBackgroundThreads(Priority pri = LOW) override {
+ return posixEnv->GetBackgroundThreads(pri);
+ }
+
+ void IncBackgroundThreadsIfNeeded(int number, Priority pri) override {
+ posixEnv->IncBackgroundThreadsIfNeeded(number, pri);
+ }
+
+ std::string TimeToString(uint64_t number) override {
+ return posixEnv->TimeToString(number);
+ }
+
+ static uint64_t gettid() {
+ assert(sizeof(pthread_t) <= sizeof(uint64_t));
+ return (uint64_t)pthread_self();
+ }
+
+ uint64_t GetThreadID() const override { return HdfsEnv::gettid(); }
+
+ private:
+ std::string fsname_; // string of the form "hdfs://hostname:port/"
+ hdfsFS fileSys_; // a single FileSystem object for all files
+ Env* posixEnv; // This object is derived from Env, but not from
+ // posixEnv. We have posixnv as an encapsulated
+ // object here so that we can use posix timers,
+ // posix threads, etc.
+
+ static const std::string kProto;
+ static const std::string pathsep;
+
+ /**
+ * If the URI is specified of the form hdfs://server:port/path,
+ * then connect to the specified cluster
+ * else connect to default.
+ */
+ hdfsFS connectToPath(const std::string& uri) {
+ if (uri.empty()) {
+ return nullptr;
+ }
+ if (uri.find(kProto) != 0) {
+ // uri doesn't start with hdfs:// -> use default:0, which is special
+ // to libhdfs.
+ return hdfsConnectNewInstance("default", 0);
+ }
+ const std::string hostport = uri.substr(kProto.length());
+
+ std::vector <std::string> parts;
+ split(hostport, ':', parts);
+ if (parts.size() != 2) {
+ throw HdfsFatalException("Bad uri for hdfs " + uri);
+ }
+ // parts[0] = hosts, parts[1] = port/xxx/yyy
+ std::string host(parts[0]);
+ std::string remaining(parts[1]);
+
+ int rem = static_cast<int>(remaining.find(pathsep));
+ std::string portStr = (rem == 0 ? remaining :
+ remaining.substr(0, rem));
+
+ tPort port;
+ port = atoi(portStr.c_str());
+ if (port == 0) {
+ throw HdfsFatalException("Bad host-port for hdfs " + uri);
+ }
+ hdfsFS fs = hdfsConnectNewInstance(host.c_str(), port);
+ return fs;
+ }
+
+ void split(const std::string &s, char delim,
+ std::vector<std::string> &elems) {
+ elems.clear();
+ size_t prev = 0;
+ size_t pos = s.find(delim);
+ while (pos != std::string::npos) {
+ elems.push_back(s.substr(prev, pos));
+ prev = pos + 1;
+ pos = s.find(delim, prev);
+ }
+ elems.push_back(s.substr(prev, s.size()));
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#else // USE_HDFS
+
+namespace ROCKSDB_NAMESPACE {
+
+static const Status notsup;
+
+class HdfsEnv : public Env {
+
+ public:
+ explicit HdfsEnv(const std::string& /*fsname*/) {
+ fprintf(stderr, "You have not build rocksdb with HDFS support\n");
+ fprintf(stderr, "Please see hdfs/README for details\n");
+ abort();
+ }
+
+ virtual ~HdfsEnv() {
+ }
+
+ virtual Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override;
+
+ virtual Status NewRandomAccessFile(
+ const std::string& /*fname*/,
+ std::unique_ptr<RandomAccessFile>* /*result*/,
+ const EnvOptions& /*options*/) override {
+ return notsup;
+ }
+
+ virtual Status NewWritableFile(const std::string& /*fname*/,
+ std::unique_ptr<WritableFile>* /*result*/,
+ const EnvOptions& /*options*/) override {
+ return notsup;
+ }
+
+ virtual Status NewDirectory(const std::string& /*name*/,
+ std::unique_ptr<Directory>* /*result*/) override {
+ return notsup;
+ }
+
+ virtual Status FileExists(const std::string& /*fname*/) override {
+ return notsup;
+ }
+
+ virtual Status GetChildren(const std::string& /*path*/,
+ std::vector<std::string>* /*result*/) override {
+ return notsup;
+ }
+
+ virtual Status DeleteFile(const std::string& /*fname*/) override {
+ return notsup;
+ }
+
+ virtual Status CreateDir(const std::string& /*name*/) override {
+ return notsup;
+ }
+
+ virtual Status CreateDirIfMissing(const std::string& /*name*/) override {
+ return notsup;
+ }
+
+ virtual Status DeleteDir(const std::string& /*name*/) override {
+ return notsup;
+ }
+
+ virtual Status GetFileSize(const std::string& /*fname*/,
+ uint64_t* /*size*/) override {
+ return notsup;
+ }
+
+ virtual Status GetFileModificationTime(const std::string& /*fname*/,
+ uint64_t* /*time*/) override {
+ return notsup;
+ }
+
+ virtual Status RenameFile(const std::string& /*src*/,
+ const std::string& /*target*/) override {
+ return notsup;
+ }
+
+ virtual Status LinkFile(const std::string& /*src*/,
+ const std::string& /*target*/) override {
+ return notsup;
+ }
+
+ virtual Status LockFile(const std::string& /*fname*/,
+ FileLock** /*lock*/) override {
+ return notsup;
+ }
+
+ virtual Status UnlockFile(FileLock* /*lock*/) override { return notsup; }
+
+ virtual Status NewLogger(const std::string& /*fname*/,
+ std::shared_ptr<Logger>* /*result*/) override {
+ return notsup;
+ }
+
+ virtual void Schedule(void (* /*function*/)(void* arg), void* /*arg*/,
+ Priority /*pri*/ = LOW, void* /*tag*/ = nullptr,
+ void (* /*unschedFunction*/)(void* arg) = 0) override {}
+
+ virtual int UnSchedule(void* /*tag*/, Priority /*pri*/) override { return 0; }
+
+ virtual void StartThread(void (* /*function*/)(void* arg),
+ void* /*arg*/) override {}
+
+ virtual void WaitForJoin() override {}
+
+ virtual unsigned int GetThreadPoolQueueLen(
+ Priority /*pri*/ = LOW) const override {
+ return 0;
+ }
+
+ virtual Status GetTestDirectory(std::string* /*path*/) override {
+ return notsup;
+ }
+
+ virtual uint64_t NowMicros() override { return 0; }
+
+ virtual void SleepForMicroseconds(int /*micros*/) override {}
+
+ virtual Status GetHostName(char* /*name*/, uint64_t /*len*/) override {
+ return notsup;
+ }
+
+ virtual Status GetCurrentTime(int64_t* /*unix_time*/) override {
+ return notsup;
+ }
+
+ virtual Status GetAbsolutePath(const std::string& /*db_path*/,
+ std::string* /*outputpath*/) override {
+ return notsup;
+ }
+
+ virtual void SetBackgroundThreads(int /*number*/,
+ Priority /*pri*/ = LOW) override {}
+ virtual int GetBackgroundThreads(Priority /*pri*/ = LOW) override {
+ return 0;
+ }
+ virtual void IncBackgroundThreadsIfNeeded(int /*number*/,
+ Priority /*pri*/) override {}
+ virtual std::string TimeToString(uint64_t /*number*/) override { return ""; }
+
+ virtual uint64_t GetThreadID() const override {
+ return 0;
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // USE_HDFS
diff --git a/storage/rocksdb/rocksdb/hdfs/setup.sh b/storage/rocksdb/rocksdb/hdfs/setup.sh
new file mode 100755
index 00000000..2d5cda61
--- /dev/null
+++ b/storage/rocksdb/rocksdb/hdfs/setup.sh
@@ -0,0 +1,9 @@
+# shellcheck disable=SC2148
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+export USE_HDFS=1
+export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$HADOOP_HOME/lib/native
+
+export CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath --glob`
+for f in `find /usr/lib/hadoop-hdfs | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done
+for f in `find /usr/lib/hadoop | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done
+for f in `find /usr/lib/hadoop/client | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/advanced_options.h b/storage/rocksdb/rocksdb/include/rocksdb/advanced_options.h
new file mode 100644
index 00000000..a72edbe0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/advanced_options.h
@@ -0,0 +1,731 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/universal_compaction.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class SliceTransform;
+enum CompressionType : unsigned char;
+class TablePropertiesCollectorFactory;
+class TableFactory;
+struct Options;
+
+enum CompactionStyle : char {
+ // level based compaction style
+ kCompactionStyleLevel = 0x0,
+ // Universal compaction style
+ // Not supported in ROCKSDB_LITE.
+ kCompactionStyleUniversal = 0x1,
+ // FIFO compaction style
+ // Not supported in ROCKSDB_LITE
+ kCompactionStyleFIFO = 0x2,
+ // Disable background compaction. Compaction jobs are submitted
+ // via CompactFiles().
+ // Not supported in ROCKSDB_LITE
+ kCompactionStyleNone = 0x3,
+};
+
+// In Level-based compaction, it Determines which file from a level to be
+// picked to merge to the next level. We suggest people try
+// kMinOverlappingRatio first when you tune your database.
+enum CompactionPri : char {
+ // Slightly prioritize larger files by size compensated by #deletes
+ kByCompensatedSize = 0x0,
+ // First compact files whose data's latest update time is oldest.
+ // Try this if you only update some hot keys in small ranges.
+ kOldestLargestSeqFirst = 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.
+ kOldestSmallestSeqFirst = 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.
+ kMinOverlappingRatio = 0x3,
+};
+
+struct CompactionOptionsFIFO {
+ // once the total sum of table files reaches this, we will delete the oldest
+ // table file
+ // Default: 1GB
+ uint64_t max_table_files_size;
+
+ // 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;
+ bool allow_compaction = false;
+
+ CompactionOptionsFIFO() : max_table_files_size(1 * 1024 * 1024 * 1024) {}
+ CompactionOptionsFIFO(uint64_t _max_table_files_size, bool _allow_compaction)
+ : max_table_files_size(_max_table_files_size),
+ allow_compaction(_allow_compaction) {}
+};
+
+// Compression options for different compression algorithms like Zlib
+struct CompressionOptions {
+ // RocksDB's generic default compression level. Internally it'll be translated
+ // to the default compression level specific to the library being used (see
+ // comment above `ColumnFamilyOptions::compression`).
+ //
+ // The default value is the max 16-bit int as it'll be written out in OPTIONS
+ // file, which should be portable.
+ const static int kDefaultCompressionLevel = 32767;
+
+ int window_bits;
+ int level;
+ int strategy;
+
+ // Maximum size of dictionaries used to prime the compression library.
+ // Enabling dictionary can improve compression ratios when there are
+ // repetitions across data blocks.
+ //
+ // The dictionary is created by sampling the SST file data. If
+ // `zstd_max_train_bytes` is nonzero, the samples are passed through zstd's
+ // dictionary generator. Otherwise, the random samples are used directly as
+ // the dictionary.
+ //
+ // When compression dictionary is disabled, we compress and write each block
+ // before buffering data for the next one. When compression dictionary is
+ // enabled, we buffer all SST file data in-memory so we can sample it, as data
+ // can only be compressed and written after the dictionary has been finalized.
+ // So users of this feature may see increased memory usage.
+ //
+ // Default: 0.
+ uint32_t max_dict_bytes;
+
+ // 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 `max_dict_bytes` alone.
+ //
+ // The training data will be used to generate a dictionary of max_dict_bytes.
+ //
+ // Default: 0.
+ uint32_t zstd_max_train_bytes;
+
+ // 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.
+ bool enabled;
+
+ CompressionOptions()
+ : window_bits(-14),
+ level(kDefaultCompressionLevel),
+ strategy(0),
+ max_dict_bytes(0),
+ zstd_max_train_bytes(0),
+ enabled(false) {}
+ CompressionOptions(int wbits, int _lev, int _strategy, int _max_dict_bytes,
+ int _zstd_max_train_bytes, bool _enabled)
+ : window_bits(wbits),
+ level(_lev),
+ strategy(_strategy),
+ max_dict_bytes(_max_dict_bytes),
+ zstd_max_train_bytes(_zstd_max_train_bytes),
+ enabled(_enabled) {}
+};
+
+enum UpdateStatus { // Return status For inplace update callback
+ UPDATE_FAILED = 0, // Nothing to update
+ UPDATED_INPLACE = 1, // Value updated inplace
+ UPDATED = 2, // No inplace update. Merged value set
+};
+
+struct AdvancedColumnFamilyOptions {
+ // The maximum number of write buffers that are built up in memory.
+ // The default and the minimum number is 2, so that when 1 write buffer
+ // is being flushed to storage, new writes can continue to the other
+ // write buffer.
+ // If max_write_buffer_number > 3, writing will be slowed down to
+ // options.delayed_write_rate if we are writing to the last write buffer
+ // allowed.
+ //
+ // Default: 2
+ //
+ // Dynamically changeable through SetOptions() API
+ int max_write_buffer_number = 2;
+
+ // 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
+ int min_write_buffer_number_to_merge = 1;
+
+ // DEPRECATED
+ // 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 parameter is being replaced by max_write_buffer_size_to_maintain.
+ // If both parameters are set to non-zero values, this parameter will be
+ // ignored.
+ int max_write_buffer_number_to_maintain = 0;
+
+ // The total maximum size(bytes) of write buffers to maintain in memory
+ // including copies of buffers that have already been flushed. This parameter
+ // only affects trimming of flushed buffers and does not affect flushing.
+ // This controls the maximum amount of write history that will be available
+ // in memory for conflict checking when Transactions are used. The actual
+ // size of write history (flushed Memtables) might be higher than this limit
+ // if further trimming will reduce write history total size below this
+ // limit. For example, if max_write_buffer_size_to_maintain is set to 64MB,
+ // and there are three flushed Memtables, with sizes of 32MB, 20MB, 20MB.
+ // Because trimming the next Memtable of size 20MB will reduce total memory
+ // usage to 52MB which is below the limit, RocksDB will stop trimming.
+ //
+ // 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,
+ // 'max_write_buffer_number * write_buffer_size' will be used.
+ //
+ // Default:
+ // If using a TransactionDB/OptimisticTransactionDB, the default value will
+ // be set to the value of 'max_write_buffer_number * write_buffer_size'
+ // if it is not explicitly set by the user. Otherwise, the default is 0.
+ int64_t max_write_buffer_size_to_maintain = 0;
+
+ // Allows thread-safe inplace updates. If this is true, there is no way to
+ // achieve point-in-time consistency using snapshot or iterator (assuming
+ // concurrent updates). Hence iterator and multi-get will return results
+ // which are not consistent as of any point-in-time.
+ // 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.
+ bool inplace_update_support = false;
+
+ // Number of locks used for inplace update
+ // Default: 10000, if inplace_update_support = true, else 0.
+ //
+ // Dynamically changeable through SetOptions() API
+ size_t inplace_update_num_locks = 10000;
+
+ // existing_value - pointer to previous value (from both memtable and sst).
+ // nullptr if key doesn't exist
+ // existing_value_size - pointer to size of existing_value).
+ // nullptr if key doesn't exist
+ // delta_value - Delta value to be merged with the existing_value.
+ // Stored in transaction logs.
+ // merged_value - Set when delta is applied on the previous value.
+
+ // Applicable only when inplace_update_support is true,
+ // this callback function is called at the time of updating the memtable
+ // as part of a Put operation, lets say Put(key, delta_value). It allows the
+ // 'delta_value' specified as part of the Put operation to be merged with
+ // an 'existing_value' of the key in the database.
+
+ // If the merged value is smaller in size that the 'existing_value',
+ // then this function can update the 'existing_value' buffer inplace and
+ // the corresponding 'existing_value'_size pointer, if it wishes to.
+ // The callback should return UpdateStatus::UPDATED_INPLACE.
+ // In this case. (In this case, the snapshot-semantics of the rocksdb
+ // Iterator is not atomic anymore).
+
+ // If the merged value is larger in size than the 'existing_value' or the
+ // application does not wish to modify the 'existing_value' buffer inplace,
+ // then the merged value should be returned via *merge_value. It is set by
+ // merging the 'existing_value' and the Put 'delta_value'. The callback should
+ // return UpdateStatus::UPDATED in this case. This merged value will be added
+ // to the memtable.
+
+ // If merging fails or the application does not wish to take any action,
+ // then the callback should return UpdateStatus::UPDATE_FAILED.
+
+ // Please remember that the original call from the application is Put(key,
+ // delta_value). So the transaction log (if enabled) will still contain (key,
+ // delta_value). The 'merged_value' is not stored in the transaction log.
+ // Hence the inplace_callback function should be consistent across db reopens.
+
+ // Default: nullptr
+ UpdateStatus (*inplace_callback)(char* existing_value,
+ uint32_t* existing_value_size,
+ Slice delta_value,
+ std::string* merged_value) = nullptr;
+
+ // 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 sanitized to 0.25.
+ //
+ // Default: 0 (disable)
+ //
+ // Dynamically changeable through SetOptions() API
+ double memtable_prefix_bloom_size_ratio = 0.0;
+
+ // 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 (disable)
+ //
+ // Dynamically changeable through SetOptions() API
+ bool memtable_whole_key_filtering = false;
+
+ // Page size for huge page for the arena used by the memtable. If <=0, it
+ // won't allocate from huge page but from malloc.
+ // Users are responsible to reserve huge pages for it to be allocated. For
+ // example:
+ // sysctl -w vm.nr_hugepages=20
+ // See linux doc Documentation/vm/hugetlbpage.txt
+ // If there isn't enough free huge page available, it will fall back to
+ // malloc.
+ //
+ // Dynamically changeable through SetOptions() API
+ size_t memtable_huge_page_size = 0;
+
+ // If non-nullptr, memtable will use the specified function to extract
+ // prefixes for keys, and for each prefix maintain a hint of insert location
+ // to reduce CPU usage for inserting keys with the prefix. Keys out of
+ // domain of the prefix extractor will be insert without using hints.
+ //
+ // Currently only the default skiplist based memtable implements the feature.
+ // All other memtable implementation will ignore the option. It incurs ~250
+ // additional bytes of memory overhead to store a hint for each prefix.
+ // Also concurrent writes (when allow_concurrent_memtable_write is true) will
+ // ignore the option.
+ //
+ // The option is best suited for workloads where keys will likely to insert
+ // to a location close the last inserted key with the same prefix.
+ // One example could be inserting keys of the form (prefix + timestamp),
+ // and keys of the same prefix always comes in with time order. Another
+ // example would be updating the same key over and over again, in which case
+ // the prefix can be the key itself.
+ //
+ // Default: nullptr (disable)
+ std::shared_ptr<const SliceTransform>
+ memtable_insert_with_hint_prefix_extractor = nullptr;
+
+ // Control locality of bloom filter probes to improve CPU cache hit rate.
+ // This option now only applies to plaintable prefix bloom. This
+ // optimization is turned off when set to 0, and positive number to turn
+ // it on.
+ // Default: 0
+ uint32_t bloom_locality = 0;
+
+ // size of one block in arena memory allocation.
+ // If <= 0, a proper value is automatically calculated (usually 1/8 of
+ // writer_buffer_size, rounded up to a multiple of 4KB).
+ //
+ // 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
+ //
+ // Dynamically changeable through SetOptions() API
+ size_t arena_block_size = 0;
+
+ // 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 level_compaction_dynamic_level_bytes=true,
+ // compression_per_level[0] still determines L0, but other elements
+ // of the array are based on base level (the level L0 files are merged
+ // to), and may not match the level users see from info log for metadata.
+ // If L0 files are merged to level-n, then, for i>0, compression_per_level[i]
+ // determines compaction type for level n+i-1.
+ // For example, if we have three 5 levels, and we determine to merge L0
+ // data to L4 (which means L1..L3 will be empty), then the new files go to
+ // L4 uses compression type compression_per_level[1].
+ // If now L0 is merged to L2. Data goes to L2 will be compressed
+ // according to compression_per_level[1], L3 using compression_per_level[2]
+ // and L4 using compression_per_level[3]. Compaction for each level can
+ // change when data grows.
+ std::vector<CompressionType> compression_per_level;
+
+ // Number of levels for this database
+ int num_levels = 7;
+
+ // 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.
+ //
+ // Default: 20
+ //
+ // Dynamically changeable through SetOptions() API
+ int level0_slowdown_writes_trigger = 20;
+
+ // Maximum number of level-0 files. We stop writes at this point.
+ //
+ // Default: 36
+ //
+ // Dynamically changeable through SetOptions() API
+ int level0_stop_writes_trigger = 36;
+
+ // Target file size for compaction.
+ // target_file_size_base is per-file size for level-1.
+ // Target file size for level L can be calculated by
+ // target_file_size_base * (target_file_size_multiplier ^ (L-1))
+ // For example, if target_file_size_base 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.
+ //
+ // Default: 64MB.
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t target_file_size_base = 64 * 1048576;
+
+ // By default target_file_size_multiplier is 1, which means
+ // by default files in different levels will have similar size.
+ //
+ // Dynamically changeable through SetOptions() API
+ int target_file_size_multiplier = 1;
+
+ // If 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. target size of the last level (level num_levels-1) equals to extra size
+ // of the level.
+ // At the same time max_bytes_for_level_multiplier and
+ // max_bytes_for_level_multiplier_additional are still satisfied.
+ // (When L0 is too large, we make some adjustment. See below.)
+ //
+ // 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 1/max_bytes_for_level_multiplier of the last level's
+ // extra size. After the data accumulates more so that we need to move the
+ // base level to the third last one, and so on.
+ //
+ // For example, assume max_bytes_for_level_multiplier=10, num_levels=6,
+ // and max_bytes_for_level_base=10MB.
+ // Target sizes of level 1 to 5 starts with:
+ // [- - - - 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:
+ // [- - - 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:
+ // [- - - 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:
+ // [- - 1.01MB 10.1MB 101MB]
+ // In the same way, while level 5 further grows, all levels' targets grow,
+ // like
+ // [- - 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:
+ // [- 1.001MB 10.01MB 100.1MB 1001MB]
+ // and go on...
+ //
+ // By doing it, we give max_bytes_for_level_multiplier a priority against
+ // max_bytes_for_level_base, for a more predictable LSM tree shape. It is
+ // useful to limit worse case space amplification.
+ //
+ //
+ // If the compaction from L0 is lagged behind, a special mode will be turned
+ // on to prioritize write amplification against max_bytes_for_level_multiplier
+ // or max_bytes_for_level_base. The L0 compaction is lagged behind by looking
+ // at number of L0 files and total L0 size. If number of L0 files is at least
+ // the double of level0_file_num_compaction_trigger, or the total size is
+ // at least max_bytes_for_level_base, this mode is on. The target of L1 grows
+ // to the actual data size in L0, and then determine the target for each level
+ // so that each level will have the same level multiplier.
+ //
+ // For example, when L0 size is 100MB, the size of last level is 1600MB,
+ // max_bytes_for_level_base = 80MB, and max_bytes_for_level_multiplier = 10.
+ // Since L0 size is larger than max_bytes_for_level_base, this is a L0
+ // compaction backlogged mode. So that the L1 size is determined to be 100MB.
+ // Based on max_bytes_for_level_multiplier = 10, at least 3 non-0 levels will
+ // be needed. The level multiplier will be calculated to be 4 and the three
+ // levels' target to be [100MB, 400MB, 1600MB].
+ //
+ // In this mode, The number of levels will be no more than the normal mode,
+ // and the level multiplier will be lower. The write amplification will
+ // likely to be reduced.
+ //
+ //
+ // 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.
+ //
+ // Default: false
+ bool level_compaction_dynamic_level_bytes = false;
+
+ // Default: 10.
+ //
+ // Dynamically changeable through SetOptions() API
+ double max_bytes_for_level_multiplier = 10;
+
+ // 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
+ //
+ // Dynamically changeable through SetOptions() API
+ std::vector<int> max_bytes_for_level_multiplier_additional =
+ std::vector<int>(num_levels, 1);
+
+ // 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.
+ //
+ // Default: target_file_size_base * 25
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t max_compaction_bytes = 0;
+
+ // All writes will be slowed down to at least delayed_write_rate if estimated
+ // bytes needed to be compaction exceed this threshold.
+ //
+ // Default: 64GB
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t soft_pending_compaction_bytes_limit = 64 * 1073741824ull;
+
+ // All writes are stopped if estimated bytes needed to be compaction exceed
+ // this threshold.
+ //
+ // Default: 256GB
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t hard_pending_compaction_bytes_limit = 256 * 1073741824ull;
+
+ // The compaction style. Default: kCompactionStyleLevel
+ CompactionStyle compaction_style = kCompactionStyleLevel;
+
+ // If level compaction_style = kCompactionStyleLevel, for each level,
+ // which files are prioritized to be picked to compact.
+ // Default: kMinOverlappingRatio
+ CompactionPri compaction_pri = kMinOverlappingRatio;
+
+ // The options needed to support Universal Style compactions
+ //
+ // Dynamically changeable through SetOptions() API
+ // Dynamic change example:
+ // SetOptions("compaction_options_universal", "{size_ratio=2;}")
+ CompactionOptionsUniversal compaction_options_universal;
+
+ // The options for FIFO compaction style
+ //
+ // Dynamically changeable through SetOptions() API
+ // Dynamic change example:
+ // SetOptions("compaction_options_fifo", "{max_table_files_size=100;}")
+ CompactionOptionsFIFO compaction_options_fifo;
+
+ // 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
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t max_sequential_skip_in_iterations = 8;
+
+ // This is a factory that provides MemTableRep objects.
+ // Default: a factory that provides a skip-list-based implementation of
+ // MemTableRep.
+ std::shared_ptr<MemTableRepFactory> memtable_factory =
+ std::shared_ptr<SkipListFactory>(new SkipListFactory);
+
+ // Block-based table related options are moved to BlockBasedTableOptions.
+ // Related options that were originally here but now moved include:
+ // no_block_cache
+ // block_cache
+ // block_cache_compressed
+ // block_size
+ // block_size_deviation
+ // block_restart_interval
+ // filter_policy
+ // whole_key_filtering
+ // If you'd like to customize some of these options, you will need to
+ // use NewBlockBasedTableFactory() to construct a new table factory.
+
+ // This option allows user to collect their own interested statistics of
+ // the tables.
+ // Default: empty vector -- no user-defined statistics collection will be
+ // performed.
+ typedef std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
+ TablePropertiesCollectorFactories;
+ TablePropertiesCollectorFactories table_properties_collector_factories;
+
+ // 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)
+ //
+ // Dynamically changeable through SetOptions() API
+ size_t max_successive_merges = 0;
+
+ // 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
+ bool optimize_filters_for_hits = false;
+
+ // After writing every SST file, reopen it and read all the keys.
+ //
+ // Default: false
+ //
+ // Dynamically changeable through SetOptions() API
+ bool paranoid_file_checks = false;
+
+ // In debug mode, RocksDB run consistency checks on the LSM every time the LSM
+ // change (Flush, Compaction, AddFile). These checks are disabled in release
+ // mode, use this option to enable them in release mode as well.
+ // Default: false
+ bool force_consistency_checks = false;
+
+ // Measure IO stats in compactions and flushes, if true.
+ //
+ // Default: false
+ //
+ // Dynamically changeable through SetOptions() API
+ bool report_bg_io_stats = false;
+
+ // Files older than TTL will go through the compaction process.
+ // Pre-req: This needs max_open_files to be set to -1.
+ // In Level: Non-bottom-level files older than TTL will go through the
+ // compation process.
+ // In FIFO: Files older than TTL will be deleted.
+ // unit: seconds. Ex: 1 day = 1 * 24 * 60 * 60
+ // In FIFO, this option will have the same meaning as
+ // periodic_compaction_seconds. Whichever stricter will be used.
+ // 0 means disabling.
+ // UINT64_MAX - 1 (0xfffffffffffffffe) is special flag to allow RocksDB to
+ // pick default.
+ //
+ // Default: 30 days for leveled compaction + block based table. disable
+ // otherwise.
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t ttl = 0xfffffffffffffffe;
+
+ // Files older than this value will be picked up for compaction, and
+ // re-written to the same level as they were before.
+ //
+ // 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: UINT64_MAX - 1 (allow RocksDB to auto-tune)
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t periodic_compaction_seconds = 0xfffffffffffffffe;
+
+ // If this option is set then 1 in N blocks are compressed
+ // using a fast (lz4) and slow (zstd) compression algorithm.
+ // The compressibility is reported as stats and the stored
+ // data is left uncompressed (unless compression is also requested).
+ uint64_t sample_for_compression = 0;
+
+ // Create ColumnFamilyOptions with default values for all fields
+ AdvancedColumnFamilyOptions();
+ // Create ColumnFamilyOptions from Options
+ explicit AdvancedColumnFamilyOptions(const Options& options);
+
+ // ---------------- OPTIONS NOT SUPPORTED ANYMORE ----------------
+
+ // NOT SUPPORTED ANYMORE
+ // This does not do anything anymore.
+ int max_mem_compaction_level;
+
+ // NOT SUPPORTED ANYMORE -- this options is no longer used
+ // Puts are delayed to options.delayed_write_rate when any level has a
+ // compaction score that exceeds soft_rate_limit. This is ignored when == 0.0.
+ //
+ // Default: 0 (disabled)
+ //
+ // Dynamically changeable through SetOptions() API
+ double soft_rate_limit = 0.0;
+
+ // NOT SUPPORTED ANYMORE -- this options is no longer used
+ double hard_rate_limit = 0.0;
+
+ // NOT SUPPORTED ANYMORE -- this options is no longer used
+ unsigned int rate_limit_delay_max_milliseconds = 100;
+
+ // NOT SUPPORTED ANYMORE
+ // Does not have any effect.
+ bool purge_redundant_kvs_while_flush = true;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/c.h b/storage/rocksdb/rocksdb/include/rocksdb/c.h
new file mode 100644
index 00000000..dafefceb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/c.h
@@ -0,0 +1,1801 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+ C bindings for rocksdb. May be useful as a stable ABI that can be
+ used by programs that keep rocksdb in a shared library, or for
+ a JNI api.
+
+ Does not support:
+ . getters for the option types
+ . custom comparators that implement key shortening
+ . capturing post-write-snapshot
+ . custom iter, db, env, cache implementations using just the C bindings
+
+ Some conventions:
+
+ (1) We expose just opaque struct pointers and functions to clients.
+ This allows us to change internal representations without having to
+ recompile clients.
+
+ (2) For simplicity, there is no equivalent to the Slice type. Instead,
+ the caller has to pass the pointer and length as separate
+ arguments.
+
+ (3) Errors are represented by a null-terminated c string. NULL
+ means no error. All operations that can raise an error are passed
+ a "char** errptr" as the last argument. One of the following must
+ be true on entry:
+ *errptr == NULL
+ *errptr points to a malloc()ed null-terminated error message
+ On success, a leveldb routine leaves *errptr unchanged.
+ On failure, leveldb frees the old value of *errptr and
+ set *errptr to a malloc()ed error message.
+
+ (4) Bools have the type unsigned char (0 == false; rest == true)
+
+ (5) All of the pointer arguments must be non-NULL.
+*/
+
+#pragma once
+
+#ifdef _WIN32
+#ifdef ROCKSDB_DLL
+#ifdef ROCKSDB_LIBRARY_EXPORTS
+#define ROCKSDB_LIBRARY_API __declspec(dllexport)
+#else
+#define ROCKSDB_LIBRARY_API __declspec(dllimport)
+#endif
+#else
+#define ROCKSDB_LIBRARY_API
+#endif
+#else
+#define ROCKSDB_LIBRARY_API
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* Exported types */
+
+typedef struct rocksdb_t rocksdb_t;
+typedef struct rocksdb_backup_engine_t rocksdb_backup_engine_t;
+typedef struct rocksdb_backup_engine_info_t rocksdb_backup_engine_info_t;
+typedef struct rocksdb_restore_options_t rocksdb_restore_options_t;
+typedef struct rocksdb_cache_t rocksdb_cache_t;
+typedef struct rocksdb_compactionfilter_t rocksdb_compactionfilter_t;
+typedef struct rocksdb_compactionfiltercontext_t
+ rocksdb_compactionfiltercontext_t;
+typedef struct rocksdb_compactionfilterfactory_t
+ rocksdb_compactionfilterfactory_t;
+typedef struct rocksdb_comparator_t rocksdb_comparator_t;
+typedef struct rocksdb_dbpath_t rocksdb_dbpath_t;
+typedef struct rocksdb_env_t rocksdb_env_t;
+typedef struct rocksdb_fifo_compaction_options_t rocksdb_fifo_compaction_options_t;
+typedef struct rocksdb_filelock_t rocksdb_filelock_t;
+typedef struct rocksdb_filterpolicy_t rocksdb_filterpolicy_t;
+typedef struct rocksdb_flushoptions_t rocksdb_flushoptions_t;
+typedef struct rocksdb_iterator_t rocksdb_iterator_t;
+typedef struct rocksdb_logger_t rocksdb_logger_t;
+typedef struct rocksdb_mergeoperator_t rocksdb_mergeoperator_t;
+typedef struct rocksdb_options_t rocksdb_options_t;
+typedef struct rocksdb_compactoptions_t rocksdb_compactoptions_t;
+typedef struct rocksdb_block_based_table_options_t
+ rocksdb_block_based_table_options_t;
+typedef struct rocksdb_cuckoo_table_options_t
+ rocksdb_cuckoo_table_options_t;
+typedef struct rocksdb_randomfile_t rocksdb_randomfile_t;
+typedef struct rocksdb_readoptions_t rocksdb_readoptions_t;
+typedef struct rocksdb_seqfile_t rocksdb_seqfile_t;
+typedef struct rocksdb_slicetransform_t rocksdb_slicetransform_t;
+typedef struct rocksdb_snapshot_t rocksdb_snapshot_t;
+typedef struct rocksdb_writablefile_t rocksdb_writablefile_t;
+typedef struct rocksdb_writebatch_t rocksdb_writebatch_t;
+typedef struct rocksdb_writebatch_wi_t rocksdb_writebatch_wi_t;
+typedef struct rocksdb_writeoptions_t rocksdb_writeoptions_t;
+typedef struct rocksdb_universal_compaction_options_t rocksdb_universal_compaction_options_t;
+typedef struct rocksdb_livefiles_t rocksdb_livefiles_t;
+typedef struct rocksdb_column_family_handle_t rocksdb_column_family_handle_t;
+typedef struct rocksdb_envoptions_t rocksdb_envoptions_t;
+typedef struct rocksdb_ingestexternalfileoptions_t rocksdb_ingestexternalfileoptions_t;
+typedef struct rocksdb_sstfilewriter_t rocksdb_sstfilewriter_t;
+typedef struct rocksdb_ratelimiter_t rocksdb_ratelimiter_t;
+typedef struct rocksdb_perfcontext_t rocksdb_perfcontext_t;
+typedef struct rocksdb_pinnableslice_t rocksdb_pinnableslice_t;
+typedef struct rocksdb_transactiondb_options_t rocksdb_transactiondb_options_t;
+typedef struct rocksdb_transactiondb_t rocksdb_transactiondb_t;
+typedef struct rocksdb_transaction_options_t rocksdb_transaction_options_t;
+typedef struct rocksdb_optimistictransactiondb_t
+ rocksdb_optimistictransactiondb_t;
+typedef struct rocksdb_optimistictransaction_options_t
+ rocksdb_optimistictransaction_options_t;
+typedef struct rocksdb_transaction_t rocksdb_transaction_t;
+typedef struct rocksdb_checkpoint_t rocksdb_checkpoint_t;
+typedef struct rocksdb_wal_iterator_t rocksdb_wal_iterator_t;
+typedef struct rocksdb_wal_readoptions_t rocksdb_wal_readoptions_t;
+typedef struct rocksdb_memory_consumers_t rocksdb_memory_consumers_t;
+typedef struct rocksdb_memory_usage_t rocksdb_memory_usage_t;
+
+/* DB operations */
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open(
+ const rocksdb_options_t* options, const char* name, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_with_ttl(
+ const rocksdb_options_t* options, const char* name, int ttl, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_for_read_only(
+ const rocksdb_options_t* options, const char* name,
+ unsigned char error_if_log_file_exist, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_as_secondary(
+ const rocksdb_options_t* options, const char* name,
+ const char* secondary_path, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_backup_engine_t* rocksdb_backup_engine_open(
+ const rocksdb_options_t* options, const char* path, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_create_new_backup(
+ rocksdb_backup_engine_t* be, rocksdb_t* db, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_create_new_backup_flush(
+ rocksdb_backup_engine_t* be, rocksdb_t* db, unsigned char flush_before_backup,
+ char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_purge_old_backups(
+ rocksdb_backup_engine_t* be, uint32_t num_backups_to_keep, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_restore_options_t*
+rocksdb_restore_options_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_restore_options_destroy(
+ rocksdb_restore_options_t* opt);
+extern ROCKSDB_LIBRARY_API void rocksdb_restore_options_set_keep_log_files(
+ rocksdb_restore_options_t* opt, int v);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_backup_engine_verify_backup(rocksdb_backup_engine_t* be,
+ uint32_t backup_id, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_backup_engine_restore_db_from_latest_backup(
+ rocksdb_backup_engine_t* be, const char* db_dir, const char* wal_dir,
+ const rocksdb_restore_options_t* restore_options, char** errptr);
+
+extern ROCKSDB_LIBRARY_API const rocksdb_backup_engine_info_t*
+rocksdb_backup_engine_get_backup_info(rocksdb_backup_engine_t* be);
+
+extern ROCKSDB_LIBRARY_API int rocksdb_backup_engine_info_count(
+ const rocksdb_backup_engine_info_t* info);
+
+extern ROCKSDB_LIBRARY_API int64_t
+rocksdb_backup_engine_info_timestamp(const rocksdb_backup_engine_info_t* info,
+ int index);
+
+extern ROCKSDB_LIBRARY_API uint32_t
+rocksdb_backup_engine_info_backup_id(const rocksdb_backup_engine_info_t* info,
+ int index);
+
+extern ROCKSDB_LIBRARY_API uint64_t
+rocksdb_backup_engine_info_size(const rocksdb_backup_engine_info_t* info,
+ int index);
+
+extern ROCKSDB_LIBRARY_API uint32_t rocksdb_backup_engine_info_number_files(
+ const rocksdb_backup_engine_info_t* info, int index);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_info_destroy(
+ const rocksdb_backup_engine_info_t* info);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_close(
+ rocksdb_backup_engine_t* be);
+
+extern ROCKSDB_LIBRARY_API rocksdb_checkpoint_t*
+rocksdb_checkpoint_object_create(rocksdb_t* db, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_checkpoint_create(
+ rocksdb_checkpoint_t* checkpoint, const char* checkpoint_dir,
+ uint64_t log_size_for_flush, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_checkpoint_object_destroy(
+ rocksdb_checkpoint_t* checkpoint);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_column_families(
+ const rocksdb_options_t* options, const char* name, int num_column_families,
+ const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t*
+rocksdb_open_for_read_only_column_families(
+ const rocksdb_options_t* options, const char* name, int num_column_families,
+ const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles,
+ unsigned char error_if_log_file_exist, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_as_secondary_column_families(
+ const rocksdb_options_t* options, const char* name,
+ const char* secondary_path, int num_column_families,
+ const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** colummn_family_handles, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char** rocksdb_list_column_families(
+ const rocksdb_options_t* options, const char* name, size_t* lencf,
+ char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_list_column_families_destroy(
+ char** list, size_t len);
+
+extern ROCKSDB_LIBRARY_API rocksdb_column_family_handle_t*
+rocksdb_create_column_family(rocksdb_t* db,
+ const rocksdb_options_t* column_family_options,
+ const char* column_family_name, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_drop_column_family(
+ rocksdb_t* db, rocksdb_column_family_handle_t* handle, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_column_family_handle_destroy(
+ rocksdb_column_family_handle_t*);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_close(rocksdb_t* db);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_put(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
+ size_t keylen, const char* val, size_t vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_put_cf(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, const char* val, size_t vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
+ size_t keylen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete_cf(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete_range_cf(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* start_key,
+ size_t start_key_len, const char* end_key, size_t end_key_len,
+ char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_merge(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options, const char* key,
+ size_t keylen, const char* val, size_t vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_merge_cf(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, const char* val, size_t vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_write(
+ rocksdb_t* db, const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_t* batch, char** errptr);
+
+/* Returns NULL if not found. A malloc()ed array otherwise.
+ Stores the length of the array in *vallen. */
+extern ROCKSDB_LIBRARY_API char* rocksdb_get(
+ rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
+ size_t keylen, size_t* vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_get_cf(
+ rocksdb_t* db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, size_t* vallen, char** errptr);
+
+// if values_list[i] == NULL and errs[i] == NULL,
+// then we got status.IsNotFound(), which we will not return.
+// all errors except status status.ok() and status.IsNotFound() are returned.
+//
+// errs, values_list and values_list_sizes must be num_keys in length,
+// allocated by the caller.
+// errs is a list of strings as opposed to the conventional one error,
+// where errs[i] is the status for retrieval of keys_list[i].
+// each non-NULL errs entry is a malloc()ed, null terminated string.
+// each non-NULL values_list entry is a malloc()ed array, with
+// the length for each stored in values_list_sizes[i].
+extern ROCKSDB_LIBRARY_API void rocksdb_multi_get(
+ rocksdb_t* db, const rocksdb_readoptions_t* options, size_t num_keys,
+ const char* const* keys_list, const size_t* keys_list_sizes,
+ char** values_list, size_t* values_list_sizes, char** errs);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_multi_get_cf(
+ rocksdb_t* db, const rocksdb_readoptions_t* options,
+ const rocksdb_column_family_handle_t* const* column_families,
+ size_t num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes, char** values_list,
+ size_t* values_list_sizes, char** errs);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_create_iterator(
+ rocksdb_t* db, const rocksdb_readoptions_t* options);
+
+extern ROCKSDB_LIBRARY_API rocksdb_wal_iterator_t* rocksdb_get_updates_since(
+ rocksdb_t* db, uint64_t seq_number,
+ const rocksdb_wal_readoptions_t* options,
+ char** errptr
+);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_create_iterator_cf(
+ rocksdb_t* db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_create_iterators(
+ rocksdb_t *db, rocksdb_readoptions_t* opts,
+ rocksdb_column_family_handle_t** column_families,
+ rocksdb_iterator_t** iterators, size_t size, char** errptr);
+
+extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t* rocksdb_create_snapshot(
+ rocksdb_t* db);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_release_snapshot(
+ rocksdb_t* db, const rocksdb_snapshot_t* snapshot);
+
+/* Returns NULL if property name is unknown.
+ Else returns a pointer to a malloc()-ed null-terminated value. */
+extern ROCKSDB_LIBRARY_API char* rocksdb_property_value(rocksdb_t* db,
+ const char* propname);
+/* returns 0 on success, -1 otherwise */
+int rocksdb_property_int(
+ rocksdb_t* db,
+ const char* propname, uint64_t *out_val);
+
+/* returns 0 on success, -1 otherwise */
+int rocksdb_property_int_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ const char* propname, uint64_t *out_val);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_property_value_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ const char* propname);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_approximate_sizes(
+ rocksdb_t* db, int num_ranges, const char* const* range_start_key,
+ const size_t* range_start_key_len, const char* const* range_limit_key,
+ const size_t* range_limit_key_len, uint64_t* sizes);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_approximate_sizes_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ int num_ranges, const char* const* range_start_key,
+ const size_t* range_start_key_len, const char* const* range_limit_key,
+ const size_t* range_limit_key_len, uint64_t* sizes);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_compact_range(rocksdb_t* db,
+ const char* start_key,
+ size_t start_key_len,
+ const char* limit_key,
+ size_t limit_key_len);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* limit_key,
+ size_t limit_key_len);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_opt(
+ rocksdb_t* db, rocksdb_compactoptions_t* opt, const char* start_key,
+ size_t start_key_len, const char* limit_key, size_t limit_key_len);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_cf_opt(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ rocksdb_compactoptions_t* opt, const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete_file(rocksdb_t* db,
+ const char* name);
+
+extern ROCKSDB_LIBRARY_API const rocksdb_livefiles_t* rocksdb_livefiles(
+ rocksdb_t* db);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_flush(
+ rocksdb_t* db, const rocksdb_flushoptions_t* options, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_flush_cf(
+ rocksdb_t* db, const rocksdb_flushoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_disable_file_deletions(rocksdb_t* db,
+ char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_enable_file_deletions(
+ rocksdb_t* db, unsigned char force, char** errptr);
+
+/* Management operations */
+
+extern ROCKSDB_LIBRARY_API void rocksdb_destroy_db(
+ const rocksdb_options_t* options, const char* name, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_repair_db(
+ const rocksdb_options_t* options, const char* name, char** errptr);
+
+/* Iterator */
+
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_destroy(rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API unsigned char rocksdb_iter_valid(
+ const rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_seek_to_first(rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_seek_to_last(rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_seek(rocksdb_iterator_t*,
+ const char* k, size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_seek_for_prev(rocksdb_iterator_t*,
+ const char* k,
+ size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_next(rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_prev(rocksdb_iterator_t*);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_iter_key(
+ const rocksdb_iterator_t*, size_t* klen);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_iter_value(
+ const rocksdb_iterator_t*, size_t* vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_iter_get_error(
+ const rocksdb_iterator_t*, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_next(rocksdb_wal_iterator_t* iter);
+extern ROCKSDB_LIBRARY_API unsigned char rocksdb_wal_iter_valid(
+ const rocksdb_wal_iterator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_status (const rocksdb_wal_iterator_t* iter, char** errptr) ;
+extern ROCKSDB_LIBRARY_API rocksdb_writebatch_t* rocksdb_wal_iter_get_batch (const rocksdb_wal_iterator_t* iter, uint64_t* seq) ;
+extern ROCKSDB_LIBRARY_API uint64_t rocksdb_get_latest_sequence_number (rocksdb_t *db);
+extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_destroy (const rocksdb_wal_iterator_t* iter) ;
+
+/* Write batch */
+
+extern ROCKSDB_LIBRARY_API rocksdb_writebatch_t* rocksdb_writebatch_create();
+extern ROCKSDB_LIBRARY_API rocksdb_writebatch_t* rocksdb_writebatch_create_from(
+ const char* rep, size_t size);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_destroy(
+ rocksdb_writebatch_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_clear(rocksdb_writebatch_t*);
+extern ROCKSDB_LIBRARY_API int rocksdb_writebatch_count(rocksdb_writebatch_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put(rocksdb_writebatch_t*,
+ const char* key,
+ size_t klen,
+ const char* val,
+ size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_cf(
+ rocksdb_writebatch_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_putv(
+ rocksdb_writebatch_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes, int num_values,
+ const char* const* values_list, const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_putv_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_merge(rocksdb_writebatch_t*,
+ const char* key,
+ size_t klen,
+ const char* val,
+ size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_merge_cf(
+ rocksdb_writebatch_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_mergev(
+ rocksdb_writebatch_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes, int num_values,
+ const char* const* values_list, const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_mergev_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete(rocksdb_writebatch_t*,
+ const char* key,
+ size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_cf(
+ rocksdb_writebatch_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_deletev(
+ rocksdb_writebatch_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_deletev_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_range(
+ rocksdb_writebatch_t* b, const char* start_key, size_t start_key_len,
+ const char* end_key, size_t end_key_len);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_range_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* end_key,
+ size_t end_key_len);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_rangev(
+ rocksdb_writebatch_t* b, int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_rangev_cf(
+ rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_log_data(
+ rocksdb_writebatch_t*, const char* blob, size_t len);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_iterate(
+ rocksdb_writebatch_t*, void* state,
+ void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
+ void (*deleted)(void*, const char* k, size_t klen));
+extern ROCKSDB_LIBRARY_API const char* rocksdb_writebatch_data(
+ rocksdb_writebatch_t*, size_t* size);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_set_save_point(
+ rocksdb_writebatch_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_rollback_to_save_point(
+ rocksdb_writebatch_t*, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_pop_save_point(
+ rocksdb_writebatch_t*, char** errptr);
+
+/* Write batch with index */
+
+extern ROCKSDB_LIBRARY_API rocksdb_writebatch_wi_t* rocksdb_writebatch_wi_create(
+ size_t reserved_bytes,
+ unsigned char overwrite_keys);
+extern ROCKSDB_LIBRARY_API rocksdb_writebatch_wi_t* rocksdb_writebatch_wi_create_from(
+ const char* rep, size_t size);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_destroy(
+ rocksdb_writebatch_wi_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_clear(rocksdb_writebatch_wi_t*);
+extern ROCKSDB_LIBRARY_API int rocksdb_writebatch_wi_count(rocksdb_writebatch_wi_t* b);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_put(rocksdb_writebatch_wi_t*,
+ const char* key,
+ size_t klen,
+ const char* val,
+ size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_put_cf(
+ rocksdb_writebatch_wi_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_putv(
+ rocksdb_writebatch_wi_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes, int num_values,
+ const char* const* values_list, const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_putv_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_merge(rocksdb_writebatch_wi_t*,
+ const char* key,
+ size_t klen,
+ const char* val,
+ size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_merge_cf(
+ rocksdb_writebatch_wi_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_mergev(
+ rocksdb_writebatch_wi_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes, int num_values,
+ const char* const* values_list, const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_mergev_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
+ int num_values, const char* const* values_list,
+ const size_t* values_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete(rocksdb_writebatch_wi_t*,
+ const char* key,
+ size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete_cf(
+ rocksdb_writebatch_wi_t*, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_deletev(
+ rocksdb_writebatch_wi_t* b, int num_keys, const char* const* keys_list,
+ const size_t* keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_deletev_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* keys_list, const size_t* keys_list_sizes);
+// DO NOT USE - rocksdb_writebatch_wi_delete_range is not yet supported
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete_range(
+ rocksdb_writebatch_wi_t* b, const char* start_key, size_t start_key_len,
+ const char* end_key, size_t end_key_len);
+// DO NOT USE - rocksdb_writebatch_wi_delete_range_cf is not yet supported
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete_range_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* end_key,
+ size_t end_key_len);
+// DO NOT USE - rocksdb_writebatch_wi_delete_rangev is not yet supported
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete_rangev(
+ rocksdb_writebatch_wi_t* b, int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes);
+// DO NOT USE - rocksdb_writebatch_wi_delete_rangev_cf is not yet supported
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_delete_rangev_cf(
+ rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
+ int num_keys, const char* const* start_keys_list,
+ const size_t* start_keys_list_sizes, const char* const* end_keys_list,
+ const size_t* end_keys_list_sizes);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_put_log_data(
+ rocksdb_writebatch_wi_t*, const char* blob, size_t len);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_iterate(
+ rocksdb_writebatch_wi_t* b,
+ void* state,
+ void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
+ void (*deleted)(void*, const char* k, size_t klen));
+extern ROCKSDB_LIBRARY_API const char* rocksdb_writebatch_wi_data(
+ rocksdb_writebatch_wi_t* b,
+ size_t* size);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_set_save_point(
+ rocksdb_writebatch_wi_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_wi_rollback_to_save_point(
+ rocksdb_writebatch_wi_t*, char** errptr);
+extern ROCKSDB_LIBRARY_API char* rocksdb_writebatch_wi_get_from_batch(
+ rocksdb_writebatch_wi_t* wbwi,
+ const rocksdb_options_t* options,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API char* rocksdb_writebatch_wi_get_from_batch_cf(
+ rocksdb_writebatch_wi_t* wbwi,
+ const rocksdb_options_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API char* rocksdb_writebatch_wi_get_from_batch_and_db(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_t* db,
+ const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t keylen,
+ size_t* vallen,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_write_writebatch_wi(
+ rocksdb_t* db,
+ const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_wi_t* wbwi,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_iterator_t* base_iterator);
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
+ rocksdb_writebatch_wi_t* wbwi,
+ rocksdb_iterator_t* base_iterator,
+ rocksdb_column_family_handle_t* cf);
+
+/* Block based table options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_block_based_table_options_t*
+rocksdb_block_based_options_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_destroy(
+ rocksdb_block_based_table_options_t* options);
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_block_size(
+ rocksdb_block_based_table_options_t* options, size_t block_size);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_block_size_deviation(
+ rocksdb_block_based_table_options_t* options, int block_size_deviation);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_block_restart_interval(
+ rocksdb_block_based_table_options_t* options, int block_restart_interval);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_index_block_restart_interval(
+ rocksdb_block_based_table_options_t* options, int index_block_restart_interval);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_metadata_block_size(
+ rocksdb_block_based_table_options_t* options, uint64_t metadata_block_size);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_partition_filters(
+ rocksdb_block_based_table_options_t* options, unsigned char partition_filters);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_use_delta_encoding(
+ rocksdb_block_based_table_options_t* options, unsigned char use_delta_encoding);
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_filter_policy(
+ rocksdb_block_based_table_options_t* options,
+ rocksdb_filterpolicy_t* filter_policy);
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_no_block_cache(
+ rocksdb_block_based_table_options_t* options, unsigned char no_block_cache);
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_block_cache(
+ rocksdb_block_based_table_options_t* options, rocksdb_cache_t* block_cache);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_block_cache_compressed(
+ rocksdb_block_based_table_options_t* options,
+ rocksdb_cache_t* block_cache_compressed);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_whole_key_filtering(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_format_version(
+ rocksdb_block_based_table_options_t*, int);
+enum {
+ rocksdb_block_based_table_index_type_binary_search = 0,
+ rocksdb_block_based_table_index_type_hash_search = 1,
+ rocksdb_block_based_table_index_type_two_level_index_search = 2,
+};
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_index_type(
+ rocksdb_block_based_table_options_t*, int); // uses one of the above enums
+enum {
+ rocksdb_block_based_table_data_block_index_type_binary_search = 0,
+ rocksdb_block_based_table_data_block_index_type_binary_search_and_hash = 1,
+};
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_data_block_index_type(
+ rocksdb_block_based_table_options_t*, int); // uses one of the above enums
+extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_data_block_hash_ratio(
+ rocksdb_block_based_table_options_t* options, double v);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_hash_index_allow_collision(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_cache_index_and_filter_blocks(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_cache_index_and_filter_blocks_with_high_priority(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_block_based_options_set_pin_top_level_index_and_filter(
+ rocksdb_block_based_table_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_block_based_table_factory(
+ rocksdb_options_t* opt, rocksdb_block_based_table_options_t* table_options);
+
+/* Cuckoo table options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_cuckoo_table_options_t*
+rocksdb_cuckoo_options_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_destroy(
+ rocksdb_cuckoo_table_options_t* options);
+extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_set_hash_ratio(
+ rocksdb_cuckoo_table_options_t* options, double v);
+extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_set_max_search_depth(
+ rocksdb_cuckoo_table_options_t* options, uint32_t v);
+extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_set_cuckoo_block_size(
+ rocksdb_cuckoo_table_options_t* options, uint32_t v);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_cuckoo_options_set_identity_as_first_hash(
+ rocksdb_cuckoo_table_options_t* options, unsigned char v);
+extern ROCKSDB_LIBRARY_API void rocksdb_cuckoo_options_set_use_module_hash(
+ rocksdb_cuckoo_table_options_t* options, unsigned char v);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_cuckoo_table_factory(
+ rocksdb_options_t* opt, rocksdb_cuckoo_table_options_t* table_options);
+
+/* Options */
+extern ROCKSDB_LIBRARY_API void rocksdb_set_options(
+ rocksdb_t* db, int count, const char* const keys[], const char* const values[], char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_set_options_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count, const char* const keys[], const char* const values[], char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_options_t* rocksdb_options_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_options_destroy(rocksdb_options_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_increase_parallelism(
+ rocksdb_options_t* opt, int total_threads);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_optimize_for_point_lookup(
+ rocksdb_options_t* opt, uint64_t block_cache_size_mb);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_optimize_level_style_compaction(
+ rocksdb_options_t* opt, uint64_t memtable_memory_budget);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_optimize_universal_style_compaction(
+ rocksdb_options_t* opt, uint64_t memtable_memory_budget);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_allow_ingest_behind(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter(
+ rocksdb_options_t*, rocksdb_compactionfilter_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter_factory(
+ rocksdb_options_t*, rocksdb_compactionfilterfactory_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_compaction_readahead_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_comparator(
+ rocksdb_options_t*, rocksdb_comparator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_merge_operator(
+ rocksdb_options_t*, rocksdb_mergeoperator_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_uint64add_merge_operator(
+ rocksdb_options_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compression_per_level(
+ rocksdb_options_t* opt, int* level_values, size_t num_levels);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_create_if_missing(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_create_missing_column_families(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_error_if_exists(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_paranoid_checks(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_paths(rocksdb_options_t*,
+ const rocksdb_dbpath_t** path_values,
+ size_t num_paths);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_env(rocksdb_options_t*,
+ rocksdb_env_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_info_log(rocksdb_options_t*,
+ rocksdb_logger_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_info_log_level(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_buffer_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_write_buffer_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_open_files(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_file_opening_threads(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_total_wal_size(
+ rocksdb_options_t* opt, uint64_t n);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compression_options(
+ rocksdb_options_t*, int, int, int, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_prefix_extractor(
+ rocksdb_options_t*, rocksdb_slicetransform_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_num_levels(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_level0_file_num_compaction_trigger(rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_level0_slowdown_writes_trigger(rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_level0_stop_writes_trigger(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_mem_compaction_level(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_target_file_size_base(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_target_file_size_multiplier(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_bytes_for_level_base(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_level_compaction_dynamic_level_bytes(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_max_bytes_for_level_multiplier(rocksdb_options_t*, double);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_max_bytes_for_level_multiplier_additional(
+ rocksdb_options_t*, int* level_values, size_t num_levels);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_enable_statistics(
+ rocksdb_options_t*);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt,
+ unsigned char val);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_skip_checking_sst_file_sizes_on_db_open(
+ rocksdb_options_t* opt, unsigned char val);
+
+/* returns a pointer to a malloc()-ed, null terminated string */
+extern ROCKSDB_LIBRARY_API char* rocksdb_options_statistics_get_string(
+ rocksdb_options_t* opt);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_write_buffer_number(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_min_write_buffer_number_to_merge(rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_max_write_buffer_number_to_maintain(rocksdb_options_t*,
+ int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_max_write_buffer_size_to_maintain(rocksdb_options_t*,
+ int64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_enable_pipelined_write(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_unordered_write(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_subcompactions(
+ rocksdb_options_t*, uint32_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_background_jobs(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_background_compactions(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_base_background_compactions(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_background_flushes(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_log_file_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_log_file_time_to_roll(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_keep_log_file_num(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_recycle_log_file_num(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_soft_rate_limit(
+ rocksdb_options_t*, double);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hard_rate_limit(
+ rocksdb_options_t*, double);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_soft_pending_compaction_bytes_limit(
+ rocksdb_options_t* opt, size_t v);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hard_pending_compaction_bytes_limit(
+ rocksdb_options_t* opt, size_t v);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_rate_limit_delay_max_milliseconds(rocksdb_options_t*,
+ unsigned int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_manifest_file_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_table_cache_numshardbits(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_table_cache_remove_scan_count_limit(rocksdb_options_t*,
+ int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_arena_block_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_fsync(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_log_dir(
+ rocksdb_options_t*, const char*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_wal_dir(rocksdb_options_t*,
+ const char*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_WAL_ttl_seconds(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_WAL_size_limit_MB(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_manifest_preallocation_size(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_mmap_reads(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_mmap_writes(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_direct_reads(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_use_direct_io_for_flush_and_compaction(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_is_fd_close_on_exec(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_skip_log_error_on_recovery(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_stats_dump_period_sec(
+ rocksdb_options_t*, unsigned int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_advise_random_on_open(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_access_hint_on_compaction_start(rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_adaptive_mutex(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_bytes_per_sync(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_wal_bytes_per_sync(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_enable_write_thread_adaptive_yield(rocksdb_options_t*,
+ unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_max_sequential_skip_in_iterations(rocksdb_options_t*,
+ uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_disable_auto_compactions(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_optimize_filters_for_hits(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_delete_obsolete_files_period_micros(rocksdb_options_t*,
+ uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_prepare_for_bulk_load(
+ rocksdb_options_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_memtable_vector_rep(
+ rocksdb_options_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_memtable_prefix_bloom_size_ratio(
+ rocksdb_options_t*, double);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_compaction_bytes(
+ rocksdb_options_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hash_skip_list_rep(
+ rocksdb_options_t*, size_t, int32_t, int32_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hash_link_list_rep(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_plain_table_factory(
+ rocksdb_options_t*, uint32_t, int, double, size_t);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_min_level_to_compress(
+ rocksdb_options_t* opt, int level);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_memtable_huge_page_size(
+ rocksdb_options_t*, size_t);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_successive_merges(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_bloom_locality(
+ rocksdb_options_t*, uint32_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_inplace_update_support(
+ rocksdb_options_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_inplace_update_num_locks(
+ rocksdb_options_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_report_bg_io_stats(
+ rocksdb_options_t*, int);
+
+enum {
+ rocksdb_tolerate_corrupted_tail_records_recovery = 0,
+ rocksdb_absolute_consistency_recovery = 1,
+ rocksdb_point_in_time_recovery = 2,
+ rocksdb_skip_any_corrupted_records_recovery = 3
+};
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_wal_recovery_mode(
+ rocksdb_options_t*, int);
+
+enum {
+ rocksdb_no_compression = 0,
+ rocksdb_snappy_compression = 1,
+ rocksdb_zlib_compression = 2,
+ rocksdb_bz2_compression = 3,
+ rocksdb_lz4_compression = 4,
+ rocksdb_lz4hc_compression = 5,
+ rocksdb_xpress_compression = 6,
+ rocksdb_zstd_compression = 7
+};
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compression(
+ rocksdb_options_t*, int);
+
+enum {
+ rocksdb_level_compaction = 0,
+ rocksdb_universal_compaction = 1,
+ rocksdb_fifo_compaction = 2
+};
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_style(
+ rocksdb_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_options_set_universal_compaction_options(
+ rocksdb_options_t*, rocksdb_universal_compaction_options_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_fifo_compaction_options(
+ rocksdb_options_t* opt, rocksdb_fifo_compaction_options_t* fifo);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_ratelimiter(
+ rocksdb_options_t* opt, rocksdb_ratelimiter_t* limiter);
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_atomic_flush(
+ rocksdb_options_t* opt, unsigned char);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_options_set_row_cache(
+ rocksdb_options_t* opt, rocksdb_cache_t* cache
+);
+
+/* RateLimiter */
+extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
+ int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness);
+extern ROCKSDB_LIBRARY_API void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t*);
+
+/* PerfContext */
+enum {
+ rocksdb_uninitialized = 0,
+ rocksdb_disable = 1,
+ rocksdb_enable_count = 2,
+ rocksdb_enable_time_except_for_mutex = 3,
+ rocksdb_enable_time = 4,
+ rocksdb_out_of_bounds = 5
+};
+
+enum {
+ rocksdb_user_key_comparison_count = 0,
+ rocksdb_block_cache_hit_count,
+ rocksdb_block_read_count,
+ rocksdb_block_read_byte,
+ rocksdb_block_read_time,
+ rocksdb_block_checksum_time,
+ rocksdb_block_decompress_time,
+ rocksdb_get_read_bytes,
+ rocksdb_multiget_read_bytes,
+ rocksdb_iter_read_bytes,
+ rocksdb_internal_key_skipped_count,
+ rocksdb_internal_delete_skipped_count,
+ rocksdb_internal_recent_skipped_count,
+ rocksdb_internal_merge_count,
+ rocksdb_get_snapshot_time,
+ rocksdb_get_from_memtable_time,
+ rocksdb_get_from_memtable_count,
+ rocksdb_get_post_process_time,
+ rocksdb_get_from_output_files_time,
+ rocksdb_seek_on_memtable_time,
+ rocksdb_seek_on_memtable_count,
+ rocksdb_next_on_memtable_count,
+ rocksdb_prev_on_memtable_count,
+ rocksdb_seek_child_seek_time,
+ rocksdb_seek_child_seek_count,
+ rocksdb_seek_min_heap_time,
+ rocksdb_seek_max_heap_time,
+ rocksdb_seek_internal_seek_time,
+ rocksdb_find_next_user_entry_time,
+ rocksdb_write_wal_time,
+ rocksdb_write_memtable_time,
+ rocksdb_write_delay_time,
+ rocksdb_write_pre_and_post_process_time,
+ rocksdb_db_mutex_lock_nanos,
+ rocksdb_db_condition_wait_nanos,
+ rocksdb_merge_operator_time_nanos,
+ rocksdb_read_index_block_nanos,
+ rocksdb_read_filter_block_nanos,
+ rocksdb_new_table_block_iter_nanos,
+ rocksdb_new_table_iterator_nanos,
+ rocksdb_block_seek_nanos,
+ rocksdb_find_table_nanos,
+ rocksdb_bloom_memtable_hit_count,
+ rocksdb_bloom_memtable_miss_count,
+ rocksdb_bloom_sst_hit_count,
+ rocksdb_bloom_sst_miss_count,
+ rocksdb_key_lock_wait_time,
+ rocksdb_key_lock_wait_count,
+ rocksdb_env_new_sequential_file_nanos,
+ rocksdb_env_new_random_access_file_nanos,
+ rocksdb_env_new_writable_file_nanos,
+ rocksdb_env_reuse_writable_file_nanos,
+ rocksdb_env_new_random_rw_file_nanos,
+ rocksdb_env_new_directory_nanos,
+ rocksdb_env_file_exists_nanos,
+ rocksdb_env_get_children_nanos,
+ rocksdb_env_get_children_file_attributes_nanos,
+ rocksdb_env_delete_file_nanos,
+ rocksdb_env_create_dir_nanos,
+ rocksdb_env_create_dir_if_missing_nanos,
+ rocksdb_env_delete_dir_nanos,
+ rocksdb_env_get_file_size_nanos,
+ rocksdb_env_get_file_modification_time_nanos,
+ rocksdb_env_rename_file_nanos,
+ rocksdb_env_link_file_nanos,
+ rocksdb_env_lock_file_nanos,
+ rocksdb_env_unlock_file_nanos,
+ rocksdb_env_new_logger_nanos,
+ rocksdb_total_metric_count = 68
+};
+
+extern ROCKSDB_LIBRARY_API void rocksdb_set_perf_level(int);
+extern ROCKSDB_LIBRARY_API rocksdb_perfcontext_t* rocksdb_perfcontext_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_perfcontext_reset(
+ rocksdb_perfcontext_t* context);
+extern ROCKSDB_LIBRARY_API char* rocksdb_perfcontext_report(
+ rocksdb_perfcontext_t* context, unsigned char exclude_zero_counters);
+extern ROCKSDB_LIBRARY_API uint64_t rocksdb_perfcontext_metric(
+ rocksdb_perfcontext_t* context, int metric);
+extern ROCKSDB_LIBRARY_API void rocksdb_perfcontext_destroy(
+ rocksdb_perfcontext_t* context);
+
+/* Compaction Filter */
+
+extern ROCKSDB_LIBRARY_API rocksdb_compactionfilter_t*
+rocksdb_compactionfilter_create(
+ void* state, void (*destructor)(void*),
+ unsigned char (*filter)(void*, int level, const char* key,
+ size_t key_length, const char* existing_value,
+ size_t value_length, char** new_value,
+ size_t* new_value_length,
+ unsigned char* value_changed),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API void rocksdb_compactionfilter_set_ignore_snapshots(
+ rocksdb_compactionfilter_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_compactionfilter_destroy(
+ rocksdb_compactionfilter_t*);
+
+/* Compaction Filter Context */
+
+extern ROCKSDB_LIBRARY_API unsigned char
+rocksdb_compactionfiltercontext_is_full_compaction(
+ rocksdb_compactionfiltercontext_t* context);
+
+extern ROCKSDB_LIBRARY_API unsigned char
+rocksdb_compactionfiltercontext_is_manual_compaction(
+ rocksdb_compactionfiltercontext_t* context);
+
+/* Compaction Filter Factory */
+
+extern ROCKSDB_LIBRARY_API rocksdb_compactionfilterfactory_t*
+rocksdb_compactionfilterfactory_create(
+ void* state, void (*destructor)(void*),
+ rocksdb_compactionfilter_t* (*create_compaction_filter)(
+ void*, rocksdb_compactionfiltercontext_t* context),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API void rocksdb_compactionfilterfactory_destroy(
+ rocksdb_compactionfilterfactory_t*);
+
+/* Comparator */
+
+extern ROCKSDB_LIBRARY_API rocksdb_comparator_t* rocksdb_comparator_create(
+ void* state, void (*destructor)(void*),
+ int (*compare)(void*, const char* a, size_t alen, const char* b,
+ size_t blen),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API void rocksdb_comparator_destroy(
+ rocksdb_comparator_t*);
+
+/* Filter policy */
+
+extern ROCKSDB_LIBRARY_API rocksdb_filterpolicy_t* rocksdb_filterpolicy_create(
+ void* state, void (*destructor)(void*),
+ char* (*create_filter)(void*, const char* const* key_array,
+ const size_t* key_length_array, int num_keys,
+ size_t* filter_length),
+ unsigned char (*key_may_match)(void*, const char* key, size_t length,
+ const char* filter, size_t filter_length),
+ void (*delete_filter)(void*, const char* filter, size_t filter_length),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API void rocksdb_filterpolicy_destroy(
+ rocksdb_filterpolicy_t*);
+
+extern ROCKSDB_LIBRARY_API rocksdb_filterpolicy_t*
+rocksdb_filterpolicy_create_bloom(int bits_per_key);
+extern ROCKSDB_LIBRARY_API rocksdb_filterpolicy_t*
+rocksdb_filterpolicy_create_bloom_full(int bits_per_key);
+
+/* Merge Operator */
+
+extern ROCKSDB_LIBRARY_API rocksdb_mergeoperator_t*
+rocksdb_mergeoperator_create(
+ void* state, void (*destructor)(void*),
+ char* (*full_merge)(void*, const char* key, size_t key_length,
+ const char* existing_value,
+ size_t existing_value_length,
+ const char* const* operands_list,
+ const size_t* operands_list_length, int num_operands,
+ unsigned char* success, size_t* new_value_length),
+ char* (*partial_merge)(void*, const char* key, size_t key_length,
+ const char* const* operands_list,
+ const size_t* operands_list_length, int num_operands,
+ unsigned char* success, size_t* new_value_length),
+ void (*delete_value)(void*, const char* value, size_t value_length),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API void rocksdb_mergeoperator_destroy(
+ rocksdb_mergeoperator_t*);
+
+/* Read options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_readoptions_t* rocksdb_readoptions_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_destroy(
+ rocksdb_readoptions_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_verify_checksums(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_fill_cache(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_snapshot(
+ rocksdb_readoptions_t*, const rocksdb_snapshot_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_iterate_upper_bound(
+ rocksdb_readoptions_t*, const char* key, size_t keylen);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_iterate_lower_bound(
+ rocksdb_readoptions_t*, const char* key, size_t keylen);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_read_tier(
+ rocksdb_readoptions_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing(
+ rocksdb_readoptions_t*, unsigned char);
+// The functionality that this option controlled has been removed.
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_managed(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size(
+ rocksdb_readoptions_t*, size_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_prefix_same_as_start(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_pin_data(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_total_order_seek(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_max_skippable_internal_keys(
+ rocksdb_readoptions_t*, uint64_t);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_background_purge_on_iterator_cleanup(
+ rocksdb_readoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_ignore_range_deletions(
+ rocksdb_readoptions_t*, unsigned char);
+
+/* Write options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_writeoptions_t*
+rocksdb_writeoptions_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_destroy(
+ rocksdb_writeoptions_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_sync(
+ rocksdb_writeoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_disable_WAL(
+ rocksdb_writeoptions_t* opt, int disable);
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_ignore_missing_column_families(
+ rocksdb_writeoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_no_slowdown(
+ rocksdb_writeoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_low_pri(
+ rocksdb_writeoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_writeoptions_set_memtable_insert_hint_per_batch(rocksdb_writeoptions_t*,
+ unsigned char);
+
+/* Compact range options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_compactoptions_t*
+rocksdb_compactoptions_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_destroy(
+ rocksdb_compactoptions_t*);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_compactoptions_set_exclusive_manual_compaction(
+ rocksdb_compactoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_compactoptions_set_bottommost_level_compaction(
+ rocksdb_compactoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_change_level(
+ rocksdb_compactoptions_t*, unsigned char);
+extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_target_level(
+ rocksdb_compactoptions_t*, int);
+
+/* Flush options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_flushoptions_t*
+rocksdb_flushoptions_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_flushoptions_destroy(
+ rocksdb_flushoptions_t*);
+extern ROCKSDB_LIBRARY_API void rocksdb_flushoptions_set_wait(
+ rocksdb_flushoptions_t*, unsigned char);
+
+/* Cache */
+
+extern ROCKSDB_LIBRARY_API rocksdb_cache_t* rocksdb_cache_create_lru(
+ size_t capacity);
+extern ROCKSDB_LIBRARY_API void rocksdb_cache_destroy(rocksdb_cache_t* cache);
+extern ROCKSDB_LIBRARY_API void rocksdb_cache_set_capacity(
+ rocksdb_cache_t* cache, size_t capacity);
+extern ROCKSDB_LIBRARY_API size_t
+rocksdb_cache_get_usage(rocksdb_cache_t* cache);
+extern ROCKSDB_LIBRARY_API size_t
+rocksdb_cache_get_pinned_usage(rocksdb_cache_t* cache);
+
+/* DBPath */
+
+extern ROCKSDB_LIBRARY_API rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path, uint64_t target_size);
+extern ROCKSDB_LIBRARY_API void rocksdb_dbpath_destroy(rocksdb_dbpath_t*);
+
+/* Env */
+
+extern ROCKSDB_LIBRARY_API rocksdb_env_t* rocksdb_create_default_env();
+extern ROCKSDB_LIBRARY_API rocksdb_env_t* rocksdb_create_mem_env();
+extern ROCKSDB_LIBRARY_API void rocksdb_env_set_background_threads(
+ rocksdb_env_t* env, int n);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_env_set_high_priority_background_threads(rocksdb_env_t* env, int n);
+extern ROCKSDB_LIBRARY_API void rocksdb_env_join_all_threads(
+ rocksdb_env_t* env);
+extern ROCKSDB_LIBRARY_API void rocksdb_env_lower_thread_pool_io_priority(rocksdb_env_t* env);
+extern ROCKSDB_LIBRARY_API void rocksdb_env_lower_high_priority_thread_pool_io_priority(rocksdb_env_t* env);
+extern ROCKSDB_LIBRARY_API void rocksdb_env_lower_thread_pool_cpu_priority(rocksdb_env_t* env);
+extern ROCKSDB_LIBRARY_API void rocksdb_env_lower_high_priority_thread_pool_cpu_priority(rocksdb_env_t* env);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_env_destroy(rocksdb_env_t*);
+
+extern ROCKSDB_LIBRARY_API rocksdb_envoptions_t* rocksdb_envoptions_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_envoptions_destroy(
+ rocksdb_envoptions_t* opt);
+
+/* SstFile */
+
+extern ROCKSDB_LIBRARY_API rocksdb_sstfilewriter_t*
+rocksdb_sstfilewriter_create(const rocksdb_envoptions_t* env,
+ const rocksdb_options_t* io_options);
+extern ROCKSDB_LIBRARY_API rocksdb_sstfilewriter_t*
+rocksdb_sstfilewriter_create_with_comparator(
+ const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options,
+ const rocksdb_comparator_t* comparator);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_open(
+ rocksdb_sstfilewriter_t* writer, const char* name, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_add(
+ rocksdb_sstfilewriter_t* writer, const char* key, size_t keylen,
+ const char* val, size_t vallen, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_put(
+ rocksdb_sstfilewriter_t* writer, const char* key, size_t keylen,
+ const char* val, size_t vallen, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_merge(
+ rocksdb_sstfilewriter_t* writer, const char* key, size_t keylen,
+ const char* val, size_t vallen, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_delete(
+ rocksdb_sstfilewriter_t* writer, const char* key, size_t keylen,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_finish(
+ rocksdb_sstfilewriter_t* writer, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_file_size(
+ rocksdb_sstfilewriter_t* writer, uint64_t* file_size);
+extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_destroy(
+ rocksdb_sstfilewriter_t* writer);
+
+extern ROCKSDB_LIBRARY_API rocksdb_ingestexternalfileoptions_t*
+rocksdb_ingestexternalfileoptions_create();
+extern ROCKSDB_LIBRARY_API void
+rocksdb_ingestexternalfileoptions_set_move_files(
+ rocksdb_ingestexternalfileoptions_t* opt, unsigned char move_files);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_ingestexternalfileoptions_set_snapshot_consistency(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char snapshot_consistency);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_ingestexternalfileoptions_set_allow_global_seqno(
+ rocksdb_ingestexternalfileoptions_t* opt, unsigned char allow_global_seqno);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_ingestexternalfileoptions_set_allow_blocking_flush(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char allow_blocking_flush);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_ingestexternalfileoptions_set_ingest_behind(
+ rocksdb_ingestexternalfileoptions_t* opt,
+ unsigned char ingest_behind);
+extern ROCKSDB_LIBRARY_API void rocksdb_ingestexternalfileoptions_destroy(
+ rocksdb_ingestexternalfileoptions_t* opt);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_ingest_external_file(
+ rocksdb_t* db, const char* const* file_list, const size_t list_len,
+ const rocksdb_ingestexternalfileoptions_t* opt, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_ingest_external_file_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* handle,
+ const char* const* file_list, const size_t list_len,
+ const rocksdb_ingestexternalfileoptions_t* opt, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_try_catch_up_with_primary(
+ rocksdb_t* db, char** errptr);
+
+/* SliceTransform */
+
+extern ROCKSDB_LIBRARY_API rocksdb_slicetransform_t*
+rocksdb_slicetransform_create(
+ void* state, void (*destructor)(void*),
+ char* (*transform)(void*, const char* key, size_t length,
+ size_t* dst_length),
+ unsigned char (*in_domain)(void*, const char* key, size_t length),
+ unsigned char (*in_range)(void*, const char* key, size_t length),
+ const char* (*name)(void*));
+extern ROCKSDB_LIBRARY_API rocksdb_slicetransform_t*
+ rocksdb_slicetransform_create_fixed_prefix(size_t);
+extern ROCKSDB_LIBRARY_API rocksdb_slicetransform_t*
+rocksdb_slicetransform_create_noop();
+extern ROCKSDB_LIBRARY_API void rocksdb_slicetransform_destroy(
+ rocksdb_slicetransform_t*);
+
+/* Universal Compaction options */
+
+enum {
+ rocksdb_similar_size_compaction_stop_style = 0,
+ rocksdb_total_size_compaction_stop_style = 1
+};
+
+extern ROCKSDB_LIBRARY_API rocksdb_universal_compaction_options_t*
+rocksdb_universal_compaction_options_create();
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_size_ratio(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_min_merge_width(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_max_merge_width(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_max_size_amplification_percent(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_compression_size_percent(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void
+rocksdb_universal_compaction_options_set_stop_style(
+ rocksdb_universal_compaction_options_t*, int);
+extern ROCKSDB_LIBRARY_API void rocksdb_universal_compaction_options_destroy(
+ rocksdb_universal_compaction_options_t*);
+
+extern ROCKSDB_LIBRARY_API rocksdb_fifo_compaction_options_t*
+rocksdb_fifo_compaction_options_create();
+extern ROCKSDB_LIBRARY_API void
+rocksdb_fifo_compaction_options_set_max_table_files_size(
+ rocksdb_fifo_compaction_options_t* fifo_opts, uint64_t size);
+extern ROCKSDB_LIBRARY_API void rocksdb_fifo_compaction_options_destroy(
+ rocksdb_fifo_compaction_options_t* fifo_opts);
+
+extern ROCKSDB_LIBRARY_API int rocksdb_livefiles_count(
+ const rocksdb_livefiles_t*);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_livefiles_name(
+ const rocksdb_livefiles_t*, int index);
+extern ROCKSDB_LIBRARY_API int rocksdb_livefiles_level(
+ const rocksdb_livefiles_t*, int index);
+extern ROCKSDB_LIBRARY_API size_t
+rocksdb_livefiles_size(const rocksdb_livefiles_t*, int index);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_livefiles_smallestkey(
+ const rocksdb_livefiles_t*, int index, size_t* size);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_livefiles_largestkey(
+ const rocksdb_livefiles_t*, int index, size_t* size);
+extern ROCKSDB_LIBRARY_API uint64_t rocksdb_livefiles_entries(
+ const rocksdb_livefiles_t*, int index);
+extern ROCKSDB_LIBRARY_API uint64_t rocksdb_livefiles_deletions(
+ const rocksdb_livefiles_t*, int index);
+extern ROCKSDB_LIBRARY_API void rocksdb_livefiles_destroy(
+ const rocksdb_livefiles_t*);
+
+/* Utility Helpers */
+
+extern ROCKSDB_LIBRARY_API void rocksdb_get_options_from_string(
+ const rocksdb_options_t* base_options, const char* opts_str,
+ rocksdb_options_t* new_options, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete_file_in_range(
+ rocksdb_t* db, const char* start_key, size_t start_key_len,
+ const char* limit_key, size_t limit_key_len, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_delete_file_in_range_cf(
+ rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
+ const char* start_key, size_t start_key_len, const char* limit_key,
+ size_t limit_key_len, char** errptr);
+
+/* Transactions */
+
+extern ROCKSDB_LIBRARY_API rocksdb_column_family_handle_t*
+rocksdb_transactiondb_create_column_family(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_options_t* column_family_options,
+ const char* column_family_name, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_transactiondb_t* rocksdb_transactiondb_open(
+ const rocksdb_options_t* options,
+ const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
+ char** errptr);
+
+rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families(
+ const rocksdb_options_t* options,
+ const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
+ int num_column_families, const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr);
+
+extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t*
+rocksdb_transactiondb_create_snapshot(rocksdb_transactiondb_t* txn_db);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_release_snapshot(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_snapshot_t* snapshot);
+
+extern ROCKSDB_LIBRARY_API rocksdb_transaction_t* rocksdb_transaction_begin(
+ rocksdb_transactiondb_t* txn_db,
+ const rocksdb_writeoptions_t* write_options,
+ const rocksdb_transaction_options_t* txn_options,
+ rocksdb_transaction_t* old_txn);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_commit(
+ rocksdb_transaction_t* txn, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback(
+ rocksdb_transaction_t* txn, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_set_savepoint(
+ rocksdb_transaction_t* txn);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback_to_savepoint(
+ rocksdb_transaction_t* txn, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_destroy(
+ rocksdb_transaction_t* txn);
+
+// This snapshot should be freed using rocksdb_free
+extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t*
+rocksdb_transaction_get_snapshot(rocksdb_transaction_t* txn);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ const char* key, size_t klen, size_t* vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get_cf(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
+ size_t* vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get_for_update(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ const char* key, size_t klen, size_t* vlen, unsigned char exclusive,
+ char** errptr);
+
+char* rocksdb_transaction_get_for_update_cf(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
+ size_t* vlen, unsigned char exclusive, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_transactiondb_get(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
+ const char* key, size_t klen, size_t* vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API char* rocksdb_transactiondb_get_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, size_t* vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put(
+ rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_put_cf(
+ rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_put_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, const char* val, size_t vallen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_write(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_writebatch_t *batch, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge(
+ rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
+ size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge_cf(
+ rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
+ const char* val, size_t vlen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete(
+ rocksdb_transaction_t* txn, const char* key, size_t klen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete_cf(
+ rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family,
+ const char* key, size_t klen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ const char* key, size_t klen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_delete_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
+rocksdb_transaction_create_iterator(rocksdb_transaction_t* txn,
+ const rocksdb_readoptions_t* options);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
+rocksdb_transaction_create_iterator_cf(
+ rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
+rocksdb_transactiondb_create_iterator(rocksdb_transactiondb_t* txn_db,
+ const rocksdb_readoptions_t* options);
+
+extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
+rocksdb_transactiondb_create_iterator_cf(
+ rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_close(
+ rocksdb_transactiondb_t* txn_db);
+
+extern ROCKSDB_LIBRARY_API rocksdb_checkpoint_t*
+rocksdb_transactiondb_checkpoint_object_create(rocksdb_transactiondb_t* txn_db,
+ char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t*
+rocksdb_optimistictransactiondb_open(const rocksdb_options_t* options,
+ const char* name, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t*
+rocksdb_optimistictransactiondb_open_column_families(
+ const rocksdb_options_t* options, const char* name, int num_column_families,
+ const char* const* column_family_names,
+ const rocksdb_options_t* const* column_family_options,
+ rocksdb_column_family_handle_t** column_family_handles, char** errptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_t*
+rocksdb_optimistictransactiondb_get_base_db(
+ rocksdb_optimistictransactiondb_t* otxn_db);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransactiondb_close_base_db(
+ rocksdb_t* base_db);
+
+extern ROCKSDB_LIBRARY_API rocksdb_transaction_t*
+rocksdb_optimistictransaction_begin(
+ rocksdb_optimistictransactiondb_t* otxn_db,
+ const rocksdb_writeoptions_t* write_options,
+ const rocksdb_optimistictransaction_options_t* otxn_options,
+ rocksdb_transaction_t* old_txn);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransactiondb_close(
+ rocksdb_optimistictransactiondb_t* otxn_db);
+
+/* Transaction Options */
+
+extern ROCKSDB_LIBRARY_API rocksdb_transactiondb_options_t*
+rocksdb_transactiondb_options_create();
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_options_destroy(
+ rocksdb_transactiondb_options_t* opt);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_options_set_max_num_locks(
+ rocksdb_transactiondb_options_t* opt, int64_t max_num_locks);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_options_set_num_stripes(
+ rocksdb_transactiondb_options_t* opt, size_t num_stripes);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_transactiondb_options_set_transaction_lock_timeout(
+ rocksdb_transactiondb_options_t* opt, int64_t txn_lock_timeout);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_transactiondb_options_set_default_lock_timeout(
+ rocksdb_transactiondb_options_t* opt, int64_t default_lock_timeout);
+
+extern ROCKSDB_LIBRARY_API rocksdb_transaction_options_t*
+rocksdb_transaction_options_create();
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_destroy(
+ rocksdb_transaction_options_t* opt);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_set_set_snapshot(
+ rocksdb_transaction_options_t* opt, unsigned char v);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_set_deadlock_detect(
+ rocksdb_transaction_options_t* opt, unsigned char v);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_set_lock_timeout(
+ rocksdb_transaction_options_t* opt, int64_t lock_timeout);
+
+extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_set_expiration(
+ rocksdb_transaction_options_t* opt, int64_t expiration);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_transaction_options_set_deadlock_detect_depth(
+ rocksdb_transaction_options_t* opt, int64_t depth);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_transaction_options_set_max_write_batch_size(
+ rocksdb_transaction_options_t* opt, size_t size);
+
+extern ROCKSDB_LIBRARY_API rocksdb_optimistictransaction_options_t*
+rocksdb_optimistictransaction_options_create();
+
+extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransaction_options_destroy(
+ rocksdb_optimistictransaction_options_t* opt);
+
+extern ROCKSDB_LIBRARY_API void
+rocksdb_optimistictransaction_options_set_set_snapshot(
+ rocksdb_optimistictransaction_options_t* opt, unsigned char v);
+
+// referring to convention (3), this should be used by client
+// to free memory that was malloc()ed
+extern ROCKSDB_LIBRARY_API void rocksdb_free(void* ptr);
+
+extern ROCKSDB_LIBRARY_API rocksdb_pinnableslice_t* rocksdb_get_pinned(
+ rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
+ size_t keylen, char** errptr);
+extern ROCKSDB_LIBRARY_API rocksdb_pinnableslice_t* rocksdb_get_pinned_cf(
+ rocksdb_t* db, const rocksdb_readoptions_t* options,
+ rocksdb_column_family_handle_t* column_family, const char* key,
+ size_t keylen, char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_pinnableslice_destroy(
+ rocksdb_pinnableslice_t* v);
+extern ROCKSDB_LIBRARY_API const char* rocksdb_pinnableslice_value(
+ const rocksdb_pinnableslice_t* t, size_t* vlen);
+
+extern ROCKSDB_LIBRARY_API rocksdb_memory_consumers_t*
+ rocksdb_memory_consumers_create();
+extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_add_db(
+ rocksdb_memory_consumers_t* consumers, rocksdb_t* db);
+extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_add_cache(
+ rocksdb_memory_consumers_t* consumers, rocksdb_cache_t* cache);
+extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_destroy(
+ rocksdb_memory_consumers_t* consumers);
+extern ROCKSDB_LIBRARY_API rocksdb_memory_usage_t*
+rocksdb_approximate_memory_usage_create(rocksdb_memory_consumers_t* consumers,
+ char** errptr);
+extern ROCKSDB_LIBRARY_API void rocksdb_approximate_memory_usage_destroy(
+ rocksdb_memory_usage_t* usage);
+
+extern ROCKSDB_LIBRARY_API uint64_t
+rocksdb_approximate_memory_usage_get_mem_table_total(
+ rocksdb_memory_usage_t* memory_usage);
+extern ROCKSDB_LIBRARY_API uint64_t
+rocksdb_approximate_memory_usage_get_mem_table_unflushed(
+ rocksdb_memory_usage_t* memory_usage);
+extern ROCKSDB_LIBRARY_API uint64_t
+rocksdb_approximate_memory_usage_get_mem_table_readers_total(
+ rocksdb_memory_usage_t* memory_usage);
+extern ROCKSDB_LIBRARY_API uint64_t
+rocksdb_approximate_memory_usage_get_cache_total(
+ rocksdb_memory_usage_t* memory_usage);
+
+#ifdef __cplusplus
+} /* end extern "C" */
+#endif
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/cache.h b/storage/rocksdb/rocksdb/include/rocksdb/cache.h
new file mode 100644
index 00000000..77ddf525
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/cache.h
@@ -0,0 +1,278 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A Cache is an interface that maps keys to values. It has internal
+// synchronization and may be safely accessed concurrently from
+// multiple threads. It may automatically evict entries to make room
+// for new entries. Values have a specified charge against the cache
+// capacity. For example, a cache where the values are variable
+// length strings, may use the length of the string as the charge for
+// the string.
+//
+// A builtin cache implementation with a least-recently-used eviction
+// policy is provided. Clients may use their own implementations if
+// they want something more sophisticated (like scan-resistance, a
+// custom eviction policy, variable cache sizing, etc.)
+
+#pragma once
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include "rocksdb/memory_allocator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Cache;
+
+extern const bool kDefaultToAdaptiveMutex;
+
+enum CacheMetadataChargePolicy {
+ kDontChargeCacheMetadata,
+ kFullChargeCacheMetadata
+};
+const CacheMetadataChargePolicy kDefaultCacheMetadataChargePolicy =
+ kFullChargeCacheMetadata;
+
+struct LRUCacheOptions {
+ // Capacity of the cache.
+ size_t capacity = 0;
+
+ // Cache is sharded into 2^num_shard_bits shards,
+ // by hash of key. Refer to NewLRUCache for further
+ // information.
+ int num_shard_bits = -1;
+
+ // If strict_capacity_limit is set,
+ // insert to the cache will fail when cache is full.
+ bool strict_capacity_limit = false;
+
+ // Percentage of cache reserved for high priority entries.
+ // If greater than zero, the LRU list will be split into a high-pri
+ // list and a low-pri list. High-pri entries will be insert to the
+ // tail of high-pri list, while low-pri entries will be first inserted to
+ // the low-pri list (the midpoint). This is refered to as
+ // midpoint insertion strategy to make entries never get hit in cache
+ // age out faster.
+ //
+ // See also
+ // BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority.
+ double high_pri_pool_ratio = 0.5;
+
+ // If non-nullptr will use this allocator instead of system allocator when
+ // allocating memory for cache blocks. Call this method before you start using
+ // the cache!
+ //
+ // Caveat: when the cache is used as block cache, the memory allocator is
+ // ignored when dealing with compression libraries that allocate memory
+ // internally (currently only XPRESS).
+ std::shared_ptr<MemoryAllocator> memory_allocator;
+
+ // Whether to use adaptive mutexes for cache shards. Note that adaptive
+ // mutexes need to be supported by the platform in order for this to have any
+ // effect. The default value is true if RocksDB is compiled with
+ // -DROCKSDB_DEFAULT_TO_ADAPTIVE_MUTEX, false otherwise.
+ bool use_adaptive_mutex = kDefaultToAdaptiveMutex;
+
+ CacheMetadataChargePolicy metadata_charge_policy =
+ kDefaultCacheMetadataChargePolicy;
+
+ LRUCacheOptions() {}
+ LRUCacheOptions(size_t _capacity, int _num_shard_bits,
+ bool _strict_capacity_limit, double _high_pri_pool_ratio,
+ std::shared_ptr<MemoryAllocator> _memory_allocator = nullptr,
+ bool _use_adaptive_mutex = kDefaultToAdaptiveMutex,
+ CacheMetadataChargePolicy _metadata_charge_policy =
+ kDefaultCacheMetadataChargePolicy)
+ : capacity(_capacity),
+ num_shard_bits(_num_shard_bits),
+ strict_capacity_limit(_strict_capacity_limit),
+ high_pri_pool_ratio(_high_pri_pool_ratio),
+ memory_allocator(std::move(_memory_allocator)),
+ use_adaptive_mutex(_use_adaptive_mutex),
+ metadata_charge_policy(_metadata_charge_policy) {}
+};
+
+// Create a new cache with a fixed size capacity. The cache is sharded
+// to 2^num_shard_bits shards, by hash of the key. The total capacity
+// is divided and evenly assigned to each shard. If strict_capacity_limit
+// 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
+// high_pri_pool_pct.
+// num_shard_bits = -1 means it is automatically determined: every shard
+// will be at least 512KB and number of shard bits will not exceed 6.
+extern std::shared_ptr<Cache> NewLRUCache(
+ size_t capacity, int num_shard_bits = -1,
+ bool strict_capacity_limit = false, double high_pri_pool_ratio = 0.5,
+ std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
+ bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
+ CacheMetadataChargePolicy metadata_charge_policy =
+ kDefaultCacheMetadataChargePolicy);
+
+extern std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts);
+
+// Similar to NewLRUCache, but create a cache based on CLOCK algorithm with
+// better concurrent performance in some cases. See util/clock_cache.cc for
+// more detail.
+//
+// Return nullptr if it is not supported.
+extern std::shared_ptr<Cache> NewClockCache(
+ size_t capacity, int num_shard_bits = -1,
+ bool strict_capacity_limit = false,
+ CacheMetadataChargePolicy metadata_charge_policy =
+ kDefaultCacheMetadataChargePolicy);
+class Cache {
+ public:
+ // Depending on implementation, cache entries with high priority could be less
+ // likely to get evicted than low priority entries.
+ enum class Priority { HIGH, LOW };
+
+ Cache(std::shared_ptr<MemoryAllocator> allocator = nullptr)
+ : memory_allocator_(std::move(allocator)) {}
+ // No copying allowed
+ Cache(const Cache&) = delete;
+ Cache& operator=(const Cache&) = delete;
+
+ // Destroys all existing entries by calling the "deleter"
+ // function that was passed via the Insert() function.
+ //
+ // @See Insert
+ virtual ~Cache() {}
+
+ // Opaque handle to an entry stored in the cache.
+ struct Handle {};
+
+ // The type of the Cache
+ virtual const char* Name() const = 0;
+
+ // Insert a mapping from key->value into the cache and assign it
+ // the specified charge against the total cache capacity.
+ // If strict_capacity_limit is true and cache reaches its full capacity,
+ // return Status::Incomplete.
+ //
+ // If handle is not nullptr, returns a handle that corresponds to the
+ // mapping. The caller must call this->Release(handle) when the returned
+ // mapping is no longer needed. In case of error caller is responsible to
+ // cleanup the value (i.e. calling "deleter").
+ //
+ // If handle is nullptr, it is as if Release is called immediately after
+ // insert. In case of error value will be cleanup.
+ //
+ // When the inserted entry is no longer needed, the key and
+ // value will be passed to "deleter".
+ virtual Status Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value),
+ Handle** handle = nullptr,
+ Priority priority = Priority::LOW) = 0;
+
+ // If the cache has no mapping for "key", returns nullptr.
+ //
+ // Else return a handle that corresponds to the mapping. The caller
+ // must call this->Release(handle) when the returned mapping is no
+ // longer needed.
+ // If stats is not nullptr, relative tickers could be used inside the
+ // function.
+ virtual Handle* Lookup(const Slice& key, Statistics* stats = nullptr) = 0;
+
+ // Increments the reference count for the handle if it refers to an entry in
+ // the cache. Returns true if refcount was incremented; otherwise, returns
+ // false.
+ // REQUIRES: handle must have been returned by a method on *this.
+ virtual bool Ref(Handle* handle) = 0;
+
+ /**
+ * Release a mapping returned by a previous Lookup(). A released entry might
+ * still remain in cache in case it is later looked up by others. If
+ * force_erase is set then it also erase it from the cache if there is no
+ * other reference to it. Erasing it should call the deleter function that
+ * was provided when the
+ * entry was inserted.
+ *
+ * Returns true if the entry was also erased.
+ */
+ // REQUIRES: handle must not have been released yet.
+ // REQUIRES: handle must have been returned by a method on *this.
+ virtual bool Release(Handle* handle, bool force_erase = false) = 0;
+
+ // Return the value encapsulated in a handle returned by a
+ // successful Lookup().
+ // REQUIRES: handle must not have been released yet.
+ // REQUIRES: handle must have been returned by a method on *this.
+ virtual void* Value(Handle* handle) = 0;
+
+ // If the cache contains entry for key, erase it. Note that the
+ // underlying entry will be kept around until all existing handles
+ // to it have been released.
+ virtual void Erase(const Slice& key) = 0;
+ // Return a new numeric id. May be used by multiple clients who are
+ // sharding the same cache to partition the key space. Typically the
+ // client will allocate a new id at startup and prepend the id to
+ // its cache keys.
+ virtual uint64_t NewId() = 0;
+
+ // sets the maximum configured capacity of the cache. When the new
+ // capacity is less than the old capacity and the existing usage is
+ // greater than new capacity, the implementation will do its best job to
+ // purge the released entries from the cache in order to lower the usage
+ virtual void SetCapacity(size_t capacity) = 0;
+
+ // Set whether to return error on insertion when cache reaches its full
+ // capacity.
+ virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
+
+ // Get the flag whether to return error on insertion when cache reaches its
+ // full capacity.
+ virtual bool HasStrictCapacityLimit() const = 0;
+
+ // returns the maximum configured capacity of the cache
+ virtual size_t GetCapacity() const = 0;
+
+ // returns the memory size for the entries residing in the cache.
+ virtual size_t GetUsage() const = 0;
+
+ // returns the memory size for a specific entry in the cache.
+ virtual size_t GetUsage(Handle* handle) const = 0;
+
+ // returns the memory size for the entries in use by the system
+ virtual size_t GetPinnedUsage() const = 0;
+
+ // returns the charge for the specific entry in the cache.
+ virtual size_t GetCharge(Handle* handle) const = 0;
+
+ // Call this on shutdown if you want to speed it up. Cache will disown
+ // any underlying data and will not free it on delete. This call will leak
+ // memory - call this only if you're shutting down the process.
+ // Any attempts of using cache after this call will fail terribly.
+ // Always delete the DB object before calling this method!
+ virtual void DisownData(){
+ // default implementation is noop
+ }
+
+ // Apply callback to all entries in the cache
+ // If thread_safe is true, it will also lock the accesses. Otherwise, it will
+ // access the cache without the lock held
+ virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) = 0;
+
+ // Remove all entries.
+ // Prerequisite: no entry is referenced.
+ virtual void EraseUnRefEntries() = 0;
+
+ virtual std::string GetPrintableOptions() const { return ""; }
+
+ MemoryAllocator* memory_allocator() const { return memory_allocator_.get(); }
+
+ private:
+ std::shared_ptr<MemoryAllocator> memory_allocator_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/cleanable.h b/storage/rocksdb/rocksdb/include/rocksdb/cleanable.h
new file mode 100644
index 00000000..b6a70ea6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/cleanable.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Cleanable {
+ public:
+ Cleanable();
+ // No copy constructor and copy assignment allowed.
+ Cleanable(Cleanable&) = delete;
+ Cleanable& operator=(Cleanable&) = delete;
+
+ ~Cleanable();
+
+ // Move constructor and move assignment is allowed.
+ Cleanable(Cleanable&&);
+ Cleanable& operator=(Cleanable&&);
+
+ // Clients are allowed to register function/arg1/arg2 triples that
+ // will be invoked when this iterator is destroyed.
+ //
+ // Note that unlike all of the preceding methods, this method is
+ // not abstract and therefore clients should not override it.
+ typedef void (*CleanupFunction)(void* arg1, void* arg2);
+ void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
+ void DelegateCleanupsTo(Cleanable* other);
+ // DoCleanup and also resets the pointers for reuse
+ inline void Reset() {
+ DoCleanup();
+ cleanup_.function = nullptr;
+ cleanup_.next = nullptr;
+ }
+
+ protected:
+ struct Cleanup {
+ CleanupFunction function;
+ void* arg1;
+ void* arg2;
+ Cleanup* next;
+ };
+ Cleanup cleanup_;
+ // It also becomes the owner of c
+ void RegisterCleanup(Cleanup* c);
+
+ private:
+ // Performs all the cleanups. It does not reset the pointers. Making it
+ // private
+ // to prevent misuse
+ inline void DoCleanup() {
+ if (cleanup_.function != nullptr) {
+ (*cleanup_.function)(cleanup_.arg1, cleanup_.arg2);
+ for (Cleanup* c = cleanup_.next; c != nullptr;) {
+ (*c->function)(c->arg1, c->arg2);
+ Cleanup* next = c->next;
+ delete c;
+ c = next;
+ }
+ }
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/compaction_filter.h b/storage/rocksdb/rocksdb/include/rocksdb/compaction_filter.h
new file mode 100644
index 00000000..97650783
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/compaction_filter.h
@@ -0,0 +1,212 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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) 2013 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 <cassert>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class SliceTransform;
+
+// Context information of a compaction run
+struct CompactionFilterContext {
+ // Does this compaction run include all data files
+ bool is_full_compaction;
+ // Is this compaction requested by the client (true),
+ // or is it occurring as an automatic compaction process
+ bool is_manual_compaction;
+};
+
+// CompactionFilter allows an application to modify/delete a key-value at
+// the time of compaction.
+
+class CompactionFilter {
+ public:
+ enum ValueType {
+ kValue,
+ kMergeOperand,
+ kBlobIndex, // used internally by BlobDB.
+ };
+
+ enum class Decision {
+ kKeep,
+ kRemove,
+ kChangeValue,
+ kRemoveAndSkipUntil,
+ };
+
+ enum class BlobDecision { kKeep, kChangeValue, kCorruption, kIOError };
+
+ // Context information of a compaction run
+ struct Context {
+ // Does this compaction run include all data files
+ bool is_full_compaction;
+ // Is this compaction requested by the client (true),
+ // or is it occurring as an automatic compaction process
+ bool is_manual_compaction;
+ // Which column family this compaction is for.
+ uint32_t column_family_id;
+ };
+
+ virtual ~CompactionFilter() {}
+
+ // The compaction process invokes this
+ // method for kv that is being compacted. A return value
+ // of false indicates that the kv should be preserved in the
+ // output of this compaction run and a return value of true
+ // indicates that this key-value should be removed from the
+ // output of the compaction. The application can inspect
+ // the existing value of the key and make decision based on it.
+ //
+ // Key-Values that are results of merge operation during compaction are not
+ // passed into this function. Currently, when you have a mix of Put()s and
+ // Merge()s on a same key, we only guarantee to process the merge operands
+ // through the compaction filters. Put()s might be processed, or might not.
+ //
+ // When the value is to be preserved, the application has the option
+ // to modify the existing_value and pass it back through new_value.
+ // value_changed needs to be set to true in this case.
+ //
+ // Note that RocksDB snapshots (i.e. call GetSnapshot() API on a
+ // DB* object) will not guarantee to preserve the state of the DB with
+ // CompactionFilter. Data seen from a snapshot might disppear after a
+ // compaction finishes. If you use snapshots, think twice about whether you
+ // want to use compaction filter and whether you are using it in a safe way.
+ //
+ // If multithreaded compaction is being used *and* a single CompactionFilter
+ // instance was supplied via Options::compaction_filter, this method may be
+ // called from different threads concurrently. The application must ensure
+ // that the call is thread-safe.
+ //
+ // If the CompactionFilter was created by a factory, then it will only ever
+ // be used by a single thread that is doing the compaction run, and this
+ // call does not need to be thread-safe. However, multiple filters may be
+ // in existence and operating concurrently.
+ virtual bool Filter(int /*level*/, const Slice& /*key*/,
+ const Slice& /*existing_value*/,
+ std::string* /*new_value*/,
+ bool* /*value_changed*/) const {
+ return false;
+ }
+
+ // The compaction process invokes this method on every merge operand. If this
+ // method returns true, the merge operand will be ignored and not written out
+ // in the compaction output
+ //
+ // Note: If you are using a TransactionDB, it is not recommended to implement
+ // FilterMergeOperand(). If a Merge operation is filtered out, TransactionDB
+ // may not realize there is a write conflict and may allow a Transaction to
+ // Commit that should have failed. Instead, it is better to implement any
+ // Merge filtering inside the MergeOperator.
+ virtual bool FilterMergeOperand(int /*level*/, const Slice& /*key*/,
+ const Slice& /*operand*/) const {
+ return false;
+ }
+
+ // An extended API. Called for both values and merge operands.
+ // Allows changing value and skipping ranges of keys.
+ // The default implementation uses Filter() and FilterMergeOperand().
+ // If you're overriding this method, no need to override the other two.
+ // `value_type` indicates whether this key-value corresponds to a normal
+ // value (e.g. written with Put()) or a merge operand (written with Merge()).
+ //
+ // Possible return values:
+ // * kKeep - keep the key-value pair.
+ // * kRemove - remove the key-value pair or merge operand.
+ // * kChangeValue - keep the key and change the value/operand to *new_value.
+ // * kRemoveAndSkipUntil - remove this key-value pair, and also remove
+ // all key-value pairs with key in [key, *skip_until). This range
+ // of keys will be skipped without reading, potentially saving some
+ // IO operations compared to removing the keys one by one.
+ //
+ // *skip_until <= key is treated the same as Decision::kKeep
+ // (since the range [key, *skip_until) is empty).
+ //
+ // Caveats:
+ // - The keys are skipped even if there are snapshots containing them,
+ // i.e. values removed by kRemoveAndSkipUntil can disappear from a
+ // snapshot - beware if you're using TransactionDB or
+ // DB::GetSnapshot().
+ // - If value for a key was overwritten or merged into (multiple Put()s
+ // or Merge()s), and compaction filter skips this key with
+ // kRemoveAndSkipUntil, it's possible that it will remove only
+ // the new value, exposing the old value that was supposed to be
+ // overwritten.
+ // - Doesn't work with PlainTableFactory in prefix mode.
+ // - If you use kRemoveAndSkipUntil, consider also reducing
+ // compaction_readahead_size option.
+ //
+ // Note: If you are using a TransactionDB, it is not recommended to filter
+ // out or modify merge operands (ValueType::kMergeOperand).
+ // If a merge operation is filtered out, TransactionDB may not realize there
+ // is a write conflict and may allow a Transaction to Commit that should have
+ // failed. Instead, it is better to implement any Merge filtering inside the
+ // MergeOperator.
+ virtual Decision FilterV2(int level, const Slice& key, ValueType value_type,
+ const Slice& existing_value, std::string* new_value,
+ std::string* /*skip_until*/) const {
+ switch (value_type) {
+ case ValueType::kValue: {
+ bool value_changed = false;
+ bool rv = Filter(level, key, existing_value, new_value, &value_changed);
+ if (rv) {
+ return Decision::kRemove;
+ }
+ return value_changed ? Decision::kChangeValue : Decision::kKeep;
+ }
+ case ValueType::kMergeOperand: {
+ bool rv = FilterMergeOperand(level, key, existing_value);
+ return rv ? Decision::kRemove : Decision::kKeep;
+ }
+ case ValueType::kBlobIndex:
+ return Decision::kKeep;
+ }
+ assert(false);
+ return Decision::kKeep;
+ }
+
+ // Internal (BlobDB) use only. Do not override in application code.
+ virtual BlobDecision PrepareBlobOutput(const Slice& /* key */,
+ const Slice& /* existing_value */,
+ std::string* /* new_value */) const {
+ return BlobDecision::kKeep;
+ }
+
+ // This function is deprecated. Snapshots will always be ignored for
+ // compaction filters, because we realized that not ignoring snapshots doesn't
+ // provide the gurantee we initially thought it would provide. Repeatable
+ // reads will not be guaranteed anyway. If you override the function and
+ // returns false, we will fail the compaction.
+ virtual bool IgnoreSnapshots() const { return true; }
+
+ // Returns a name that identifies this compaction filter.
+ // The name will be printed to LOG file on start up for diagnosis.
+ virtual const char* Name() const = 0;
+};
+
+// Each compaction will create a new CompactionFilter allowing the
+// application to know about different compactions
+class CompactionFilterFactory {
+ public:
+ virtual ~CompactionFilterFactory() {}
+
+ virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) = 0;
+
+ // Returns a name that identifies this compaction filter factory.
+ virtual const char* Name() const = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h b/storage/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h
new file mode 100644
index 00000000..8949b43e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+#include <stdint.h>
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+struct CompactionJobStats {
+ CompactionJobStats() { Reset(); }
+ void Reset();
+ // Aggregate the CompactionJobStats from another instance with this one
+ void Add(const CompactionJobStats& stats);
+
+ // the elapsed time of this compaction in microseconds.
+ uint64_t elapsed_micros;
+
+ // the elapsed CPU time of this compaction in microseconds.
+ uint64_t cpu_micros;
+
+ // the number of compaction input records.
+ uint64_t num_input_records;
+ // the number of compaction input files.
+ size_t num_input_files;
+ // the number of compaction input files at the output level.
+ size_t num_input_files_at_output_level;
+
+ // the number of compaction output records.
+ uint64_t num_output_records;
+ // the number of compaction output files.
+ size_t num_output_files;
+
+ // true if the compaction is a manual compaction
+ bool is_manual_compaction;
+
+ // the size of the compaction input in bytes.
+ uint64_t total_input_bytes;
+ // the size of the compaction output in bytes.
+ uint64_t total_output_bytes;
+
+ // 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
+ uint64_t num_records_replaced;
+
+ // the sum of the uncompressed input keys in bytes.
+ uint64_t total_input_raw_key_bytes;
+ // the sum of the uncompressed input values in bytes.
+ uint64_t total_input_raw_value_bytes;
+
+ // the number of deletion entries before compaction. Deletion entries
+ // can disappear after compaction because they expired
+ uint64_t num_input_deletion_records;
+ // 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)
+ uint64_t num_expired_deletion_records;
+
+ // number of corrupt keys (ParseInternalKey returned false when applied to
+ // the key) encountered and written out.
+ uint64_t num_corrupt_keys;
+
+ // Following counters are only populated if
+ // options.report_bg_io_stats = true;
+
+ // Time spent on file's Append() call.
+ uint64_t file_write_nanos;
+
+ // Time spent on sync file range.
+ uint64_t file_range_sync_nanos;
+
+ // Time spent on file fsync.
+ uint64_t file_fsync_nanos;
+
+ // Time spent on preparing file write (fallocate, etc)
+ uint64_t file_prepare_write_nanos;
+
+ // 0-terminated strings storing the first 8 bytes of the smallest and
+ // largest key in the output.
+ static const size_t kMaxPrefixLength = 8;
+
+ std::string smallest_output_key_prefix;
+ std::string largest_output_key_prefix;
+
+ // number of single-deletes which do not meet a put
+ uint64_t num_single_del_fallthru;
+
+ // number of single-deletes which meet something other than a put
+ uint64_t num_single_del_mismatch;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/comparator.h b/storage/rocksdb/rocksdb/include/rocksdb/comparator.h
new file mode 100644
index 00000000..76981d10
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/comparator.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+
+// A Comparator object provides a total order across slices that are
+// used as keys in an sstable or a database. A Comparator implementation
+// must be thread-safe since rocksdb may invoke its methods concurrently
+// from multiple threads.
+class Comparator {
+ public:
+ Comparator() : timestamp_size_(0) {}
+
+ Comparator(size_t ts_sz) : timestamp_size_(ts_sz) {}
+
+ Comparator(const Comparator& orig) : timestamp_size_(orig.timestamp_size_) {}
+
+ Comparator& operator=(const Comparator& rhs) {
+ if (this != &rhs) {
+ timestamp_size_ = rhs.timestamp_size_;
+ }
+ return *this;
+ }
+
+ virtual ~Comparator() {}
+
+ static const char* Type() { return "Comparator"; }
+ // Three-way comparison. Returns value:
+ // < 0 iff "a" < "b",
+ // == 0 iff "a" == "b",
+ // > 0 iff "a" > "b"
+ virtual int Compare(const Slice& a, const Slice& b) const = 0;
+
+ // Compares two slices for equality. The following invariant should always
+ // hold (and is the default implementation):
+ // Equal(a, b) iff Compare(a, b) == 0
+ // Overwrite only if equality comparisons can be done more efficiently than
+ // three-way comparisons.
+ virtual bool Equal(const Slice& a, const Slice& b) const {
+ return Compare(a, b) == 0;
+ }
+
+ // 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.
+ //
+ // The client of this package should switch to a new name 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
+ // by any clients of this package.
+ virtual const char* Name() const = 0;
+
+ // Advanced functions: these are used to reduce the space requirements
+ // for internal data structures like index blocks.
+
+ // If *start < limit, changes *start to a short string in [start,limit).
+ // Simple comparator implementations may return with *start unchanged,
+ // i.e., an implementation of this method that does nothing is correct.
+ virtual void FindShortestSeparator(std::string* start,
+ const Slice& limit) const = 0;
+
+ // Changes *key to a short string >= *key.
+ // Simple comparator implementations may return with *key unchanged,
+ // i.e., an implementation of this method that does nothing is correct.
+ virtual void FindShortSuccessor(std::string* key) const = 0;
+
+ // if it is a wrapped comparator, may return the root one.
+ // return itself it is not wrapped.
+ virtual const Comparator* GetRootComparator() const { return this; }
+
+ // given two keys, determine if t is the successor of s
+ virtual bool IsSameLengthImmediateSuccessor(const Slice& /*s*/,
+ const Slice& /*t*/) const {
+ return false;
+ }
+
+ // return true if two keys with different byte sequences can be regarded
+ // as equal by this comparator.
+ // The major use case is to determine if DataBlockHashIndex is compatible
+ // with the customized comparator.
+ virtual bool CanKeysWithDifferentByteContentsBeEqual() const { return true; }
+
+ inline size_t timestamp_size() const { return timestamp_size_; }
+
+ virtual int CompareWithoutTimestamp(const Slice& a, const Slice& b) const {
+ return Compare(a, b);
+ }
+
+ virtual int CompareTimestamp(const Slice& /*ts1*/,
+ const Slice& /*ts2*/) const {
+ return 0;
+ }
+
+ private:
+ size_t timestamp_size_;
+};
+
+// Return a builtin comparator that uses lexicographic byte-wise
+// ordering. The result remains the property of this module and
+// must not be deleted.
+extern const Comparator* BytewiseComparator();
+
+// Return a builtin comparator that uses reverse lexicographic byte-wise
+// ordering.
+extern const Comparator* ReverseBytewiseComparator();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/concurrent_task_limiter.h b/storage/rocksdb/rocksdb/include/rocksdb/concurrent_task_limiter.h
new file mode 100644
index 00000000..4fc6b794
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/concurrent_task_limiter.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ConcurrentTaskLimiter {
+ public:
+ virtual ~ConcurrentTaskLimiter() {}
+
+ // Returns a name that identifies this concurrent task limiter.
+ virtual const std::string& GetName() const = 0;
+
+ // Set max concurrent tasks.
+ // limit = 0 means no new task allowed.
+ // limit < 0 means no limitation.
+ virtual void SetMaxOutstandingTask(int32_t limit) = 0;
+
+ // Reset to unlimited max concurrent task.
+ virtual void ResetMaxOutstandingTask() = 0;
+
+ // Returns current outstanding task count.
+ virtual int32_t GetOutstandingTask() const = 0;
+};
+
+// Create a ConcurrentTaskLimiter that can be shared with mulitple CFs
+// across RocksDB instances to control concurrent tasks.
+//
+// @param name: Name of the limiter.
+// @param limit: max concurrent tasks.
+// limit = 0 means no new task allowed.
+// limit < 0 means no limitation.
+extern ConcurrentTaskLimiter* NewConcurrentTaskLimiter(const std::string& name,
+ int32_t limit);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/convenience.h b/storage/rocksdb/rocksdb/include/rocksdb/convenience.h
new file mode 100644
index 00000000..442303d9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/convenience.h
@@ -0,0 +1,351 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <unordered_map>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+// The following set of functions provide a way to construct RocksDB Options
+// from a string or a string-to-string map. Here're the general rule of
+// setting option values from strings by type. Some RocksDB types are also
+// supported in these APIs. Please refer to the comment of the function itself
+// to find more information about how to config those RocksDB types.
+//
+// * Strings:
+// Strings will be used as values directly without any truncating or
+// trimming.
+//
+// * Booleans:
+// - "true" or "1" => true
+// - "false" or "0" => false.
+// [Example]:
+// - {"optimize_filters_for_hits", "1"} in GetColumnFamilyOptionsFromMap, or
+// - "optimize_filters_for_hits=true" in GetColumnFamilyOptionsFromString.
+//
+// * Integers:
+// Integers are converted directly from string, in addition to the following
+// units that we support:
+// - 'k' or 'K' => 2^10
+// - 'm' or 'M' => 2^20
+// - 'g' or 'G' => 2^30
+// - 't' or 'T' => 2^40 // only for unsigned int with sufficient bits.
+// [Example]:
+// - {"arena_block_size", "19G"} in GetColumnFamilyOptionsFromMap, or
+// - "arena_block_size=19G" in GetColumnFamilyOptionsFromString.
+//
+// * Doubles / Floating Points:
+// Doubles / Floating Points are converted directly from string. Note that
+// currently we do not support units.
+// [Example]:
+// - {"hard_rate_limit", "2.1"} in GetColumnFamilyOptionsFromMap, or
+// - "hard_rate_limit=2.1" in GetColumnFamilyOptionsFromString.
+// * Array / Vectors:
+// An array is specified by a list of values, where ':' is used as
+// the delimiter to separate each value.
+// [Example]:
+// - {"compression_per_level", "kNoCompression:kSnappyCompression"}
+// in GetColumnFamilyOptionsFromMap, or
+// - "compression_per_level=kNoCompression:kSnappyCompression" in
+// GetColumnFamilyOptionsFromMapString
+// * Enums:
+// The valid values of each enum are identical to the names of its constants.
+// [Example]:
+// - CompressionType: valid values are "kNoCompression",
+// "kSnappyCompression", "kZlibCompression", "kBZip2Compression", ...
+// - CompactionStyle: valid values are "kCompactionStyleLevel",
+// "kCompactionStyleUniversal", "kCompactionStyleFIFO", and
+// "kCompactionStyleNone".
+//
+
+// Take a default ColumnFamilyOptions "base_options" in addition to a
+// map "opts_map" of option name to option value to construct the new
+// ColumnFamilyOptions "new_options".
+//
+// Below are the instructions of how to config some non-primitive-typed
+// options in ColumnFOptions:
+//
+// * table_factory:
+// table_factory can be configured using our custom nested-option syntax.
+//
+// {option_a=value_a; option_b=value_b; option_c=value_c; ... }
+//
+// A nested option is enclosed by two curly braces, within which there are
+// multiple option assignments. Each assignment is of the form
+// "variable_name=value;".
+//
+// Currently we support the following types of TableFactory:
+// - BlockBasedTableFactory:
+// Use name "block_based_table_factory" to initialize table_factory with
+// BlockBasedTableFactory. Its BlockBasedTableFactoryOptions can be
+// configured using the nested-option syntax.
+// [Example]:
+// * {"block_based_table_factory", "{block_cache=1M;block_size=4k;}"}
+// is equivalent to assigning table_factory with a BlockBasedTableFactory
+// that has 1M LRU block-cache with block size equals to 4k:
+// ColumnFamilyOptions cf_opt;
+// BlockBasedTableOptions blk_opt;
+// blk_opt.block_cache = NewLRUCache(1 * 1024 * 1024);
+// blk_opt.block_size = 4 * 1024;
+// cf_opt.table_factory.reset(NewBlockBasedTableFactory(blk_opt));
+// - PlainTableFactory:
+// Use name "plain_table_factory" to initialize table_factory with
+// PlainTableFactory. Its PlainTableFactoryOptions can be configured using
+// the nested-option syntax.
+// [Example]:
+// * {"plain_table_factory", "{user_key_len=66;bloom_bits_per_key=20;}"}
+//
+// * memtable_factory:
+// Use "memtable" to config memtable_factory. Here are the supported
+// memtable factories:
+// - SkipList:
+// Pass "skip_list:<lookahead>" to config memtable to use SkipList,
+// or simply "skip_list" to use the default SkipList.
+// [Example]:
+// * {"memtable", "skip_list:5"} is equivalent to setting
+// memtable to SkipListFactory(5).
+// - PrefixHash:
+// Pass "prfix_hash:<hash_bucket_count>" to config memtable
+// to use PrefixHash, or simply "prefix_hash" to use the default
+// PrefixHash.
+// [Example]:
+// * {"memtable", "prefix_hash:1000"} is equivalent to setting
+// memtable to NewHashSkipListRepFactory(hash_bucket_count).
+// - HashLinkedList:
+// Pass "hash_linkedlist:<hash_bucket_count>" to config memtable
+// to use HashLinkedList, or simply "hash_linkedlist" to use the default
+// HashLinkedList.
+// [Example]:
+// * {"memtable", "hash_linkedlist:1000"} is equivalent to
+// setting memtable to NewHashLinkListRepFactory(1000).
+// - VectorRepFactory:
+// Pass "vector:<count>" to config memtable to use VectorRepFactory,
+// or simply "vector" to use the default Vector memtable.
+// [Example]:
+// * {"memtable", "vector:1024"} is equivalent to setting memtable
+// to VectorRepFactory(1024).
+// - HashCuckooRepFactory:
+// Pass "cuckoo:<write_buffer_size>" to use HashCuckooRepFactory with the
+// specified write buffer size, or simply "cuckoo" to use the default
+// HashCuckooRepFactory.
+// [Example]:
+// * {"memtable", "cuckoo:1024"} is equivalent to setting memtable
+// to NewHashCuckooRepFactory(1024).
+//
+// * compression_opts:
+// Use "compression_opts" to config compression_opts. The value format
+// is of the form "<window_bits>:<level>:<strategy>:<max_dict_bytes>".
+// [Example]:
+// * {"compression_opts", "4:5:6:7"} is equivalent to setting:
+// ColumnFamilyOptions cf_opt;
+// cf_opt.compression_opts.window_bits = 4;
+// cf_opt.compression_opts.level = 5;
+// cf_opt.compression_opts.strategy = 6;
+// cf_opt.compression_opts.max_dict_bytes = 7;
+//
+// @param base_options the default options of the output "new_options".
+// @param opts_map an option name to value map for specifying how "new_options"
+// should be set.
+// @param new_options the resulting options based on "base_options" with the
+// change specified in "opts_map".
+// @param input_strings_escaped when set to true, each escaped characters
+// prefixed by '\' in the values of the opts_map will be further converted
+// back to the raw string before assigning to the associated options.
+// @param ignore_unknown_options when set to true, unknown options are ignored
+// instead of resulting in an unknown-option error.
+// @return Status::OK() on success. Otherwise, a non-ok status indicating
+// error will be returned, and "new_options" will be set to "base_options".
+Status GetColumnFamilyOptionsFromMap(
+ const ColumnFamilyOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ ColumnFamilyOptions* new_options, bool input_strings_escaped = false,
+ bool ignore_unknown_options = false);
+
+// Take a default DBOptions "base_options" in addition to a
+// map "opts_map" of option name to option value to construct the new
+// DBOptions "new_options".
+//
+// Below are the instructions of how to config some non-primitive-typed
+// options in DBOptions:
+//
+// * rate_limiter_bytes_per_sec:
+// RateLimiter can be configured directly by specifying its bytes_per_sec.
+// [Example]:
+// - Passing {"rate_limiter_bytes_per_sec", "1024"} is equivalent to
+// passing NewGenericRateLimiter(1024) to rate_limiter_bytes_per_sec.
+//
+// @param base_options the default options of the output "new_options".
+// @param opts_map an option name to value map for specifying how "new_options"
+// should be set.
+// @param new_options the resulting options based on "base_options" with the
+// change specified in "opts_map".
+// @param input_strings_escaped when set to true, each escaped characters
+// prefixed by '\' in the values of the opts_map will be further converted
+// back to the raw string before assigning to the associated options.
+// @param ignore_unknown_options when set to true, unknown options are ignored
+// instead of resulting in an unknown-option error.
+// @return Status::OK() on success. Otherwise, a non-ok status indicating
+// error will be returned, and "new_options" will be set to "base_options".
+Status GetDBOptionsFromMap(
+ const DBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ DBOptions* new_options, bool input_strings_escaped = false,
+ bool ignore_unknown_options = false);
+
+// Take a default BlockBasedTableOptions "table_options" in addition to a
+// map "opts_map" of option name to option value to construct the new
+// BlockBasedTableOptions "new_table_options".
+//
+// Below are the instructions of how to config some non-primitive-typed
+// options in BlockBasedTableOptions:
+//
+// * filter_policy:
+// We currently only support the following FilterPolicy in the convenience
+// functions:
+// - BloomFilter: use "bloomfilter:[bits_per_key]:[use_block_based_builder]"
+// to specify BloomFilter. The above string is equivalent to calling
+// NewBloomFilterPolicy(bits_per_key, use_block_based_builder).
+// [Example]:
+// - Pass {"filter_policy", "bloomfilter:4:true"} in
+// GetBlockBasedTableOptionsFromMap to use a BloomFilter with 4-bits
+// per key and use_block_based_builder enabled.
+//
+// * block_cache / block_cache_compressed:
+// We currently only support LRU cache in the GetOptions API. The LRU
+// cache can be set by directly specifying its size.
+// [Example]:
+// - Passing {"block_cache", "1M"} in GetBlockBasedTableOptionsFromMap is
+// equivalent to setting block_cache using NewLRUCache(1024 * 1024).
+//
+// @param table_options the default options of the output "new_table_options".
+// @param opts_map an option name to value map for specifying how
+// "new_table_options" should be set.
+// @param new_table_options the resulting options based on "table_options"
+// with the change specified in "opts_map".
+// @param input_strings_escaped when set to true, each escaped characters
+// prefixed by '\' in the values of the opts_map will be further converted
+// back to the raw string before assigning to the associated options.
+// @param ignore_unknown_options when set to true, unknown options are ignored
+// instead of resulting in an unknown-option error.
+// @return Status::OK() on success. Otherwise, a non-ok status indicating
+// error will be returned, and "new_table_options" will be set to
+// "table_options".
+Status GetBlockBasedTableOptionsFromMap(
+ const BlockBasedTableOptions& table_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ BlockBasedTableOptions* new_table_options,
+ bool input_strings_escaped = false, bool ignore_unknown_options = false);
+
+// Take a default PlainTableOptions "table_options" in addition to a
+// map "opts_map" of option name to option value to construct the new
+// PlainTableOptions "new_table_options".
+//
+// @param table_options the default options of the output "new_table_options".
+// @param opts_map an option name to value map for specifying how
+// "new_table_options" should be set.
+// @param new_table_options the resulting options based on "table_options"
+// with the change specified in "opts_map".
+// @param input_strings_escaped when set to true, each escaped characters
+// prefixed by '\' in the values of the opts_map will be further converted
+// back to the raw string before assigning to the associated options.
+// @param ignore_unknown_options when set to true, unknown options are ignored
+// instead of resulting in an unknown-option error.
+// @return Status::OK() on success. Otherwise, a non-ok status indicating
+// error will be returned, and "new_table_options" will be set to
+// "table_options".
+Status GetPlainTableOptionsFromMap(
+ const PlainTableOptions& table_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ PlainTableOptions* new_table_options, bool input_strings_escaped = false,
+ bool ignore_unknown_options = false);
+
+// Take a string representation of option names and values, apply them into the
+// base_options, and return the new options as a result. The string has the
+// following format:
+// "write_buffer_size=1024;max_write_buffer_number=2"
+// Nested options config is also possible. For example, you can define
+// BlockBasedTableOptions as part of the string for block-based table factory:
+// "write_buffer_size=1024;block_based_table_factory={block_size=4k};"
+// "max_write_buffer_num=2"
+Status GetColumnFamilyOptionsFromString(const ColumnFamilyOptions& base_options,
+ const std::string& opts_str,
+ ColumnFamilyOptions* new_options);
+
+Status GetDBOptionsFromString(const DBOptions& base_options,
+ const std::string& opts_str,
+ DBOptions* new_options);
+
+Status GetStringFromDBOptions(std::string* opts_str,
+ const DBOptions& db_options,
+ const std::string& delimiter = "; ");
+
+Status GetStringFromColumnFamilyOptions(std::string* opts_str,
+ const ColumnFamilyOptions& cf_options,
+ const std::string& delimiter = "; ");
+
+Status GetStringFromCompressionType(std::string* compression_str,
+ CompressionType compression_type);
+
+std::vector<CompressionType> GetSupportedCompressions();
+
+Status GetBlockBasedTableOptionsFromString(
+ const BlockBasedTableOptions& table_options, const std::string& opts_str,
+ BlockBasedTableOptions* new_table_options);
+
+Status GetPlainTableOptionsFromString(const PlainTableOptions& table_options,
+ const std::string& opts_str,
+ PlainTableOptions* new_table_options);
+
+Status GetMemTableRepFactoryFromString(
+ const std::string& opts_str,
+ std::unique_ptr<MemTableRepFactory>* new_mem_factory);
+
+Status GetOptionsFromString(const Options& base_options,
+ const std::string& opts_str, Options* new_options);
+
+Status StringToMap(const std::string& opts_str,
+ std::unordered_map<std::string, std::string>* opts_map);
+
+// Request stopping background work, if wait is true wait until it's done
+void CancelAllBackgroundWork(DB* db, bool wait = false);
+
+// Delete files which are entirely in the given range
+// Could leave some keys in the range which are in files which are not
+// entirely in the range. Also leaves L0 files regardless of whether they're
+// in the range.
+// Snapshots before the delete might not see the data in the given range.
+Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end,
+ bool include_end = true);
+
+// 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.
+Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
+ const RangePtr* ranges, size_t n,
+ bool include_end = true);
+
+// Verify the checksum of file
+Status VerifySstFileChecksum(const Options& options,
+ const EnvOptions& env_options,
+ const std::string& file_path);
+
+// Verify the checksum of file
+Status VerifySstFileChecksum(const Options& options,
+ const EnvOptions& env_options,
+ const ReadOptions& read_options,
+ const std::string& file_path);
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/db.h b/storage/rocksdb/rocksdb/include/rocksdb/db.h
new file mode 100644
index 00000000..3108003f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/db.h
@@ -0,0 +1,1525 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <stdio.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "rocksdb/iterator.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/metadata.h"
+#include "rocksdb/options.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/sst_file_writer.h"
+#include "rocksdb/thread_status.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/types.h"
+#include "rocksdb/version.h"
+
+#ifdef _WIN32
+// Windows API macro interference
+#undef DeleteFile
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
+#elif _WIN32
+#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+struct Options;
+struct DBOptions;
+struct ColumnFamilyOptions;
+struct ReadOptions;
+struct WriteOptions;
+struct FlushOptions;
+struct CompactionOptions;
+struct CompactRangeOptions;
+struct TableProperties;
+struct ExternalSstFileInfo;
+class WriteBatch;
+class Env;
+class EventListener;
+class StatsHistoryIterator;
+class TraceWriter;
+#ifdef ROCKSDB_LITE
+class CompactionJobInfo;
+#endif
+class FileSystem;
+
+extern const std::string kDefaultColumnFamilyName;
+extern const std::string kPersistentStatsColumnFamilyName;
+struct ColumnFamilyDescriptor {
+ std::string name;
+ ColumnFamilyOptions options;
+ ColumnFamilyDescriptor()
+ : name(kDefaultColumnFamilyName), options(ColumnFamilyOptions()) {}
+ ColumnFamilyDescriptor(const std::string& _name,
+ const ColumnFamilyOptions& _options)
+ : name(_name), options(_options) {}
+};
+
+class ColumnFamilyHandle {
+ public:
+ virtual ~ColumnFamilyHandle() {}
+ // Returns the name of the column family associated with the current handle.
+ virtual const std::string& GetName() const = 0;
+ // Returns the ID of the column family associated with the current handle.
+ virtual uint32_t GetID() const = 0;
+ // Fills "*desc" with 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.
+ virtual Status GetDescriptor(ColumnFamilyDescriptor* desc) = 0;
+ // Returns the comparator of the column family associated with the
+ // current handle.
+ virtual const Comparator* GetComparator() const = 0;
+};
+
+static const int kMajorVersion = __ROCKSDB_MAJOR__;
+static const int kMinorVersion = __ROCKSDB_MINOR__;
+
+// A range of keys
+struct Range {
+ Slice start;
+ Slice limit;
+
+ Range() {}
+ Range(const Slice& s, const Slice& l) : start(s), limit(l) {}
+};
+
+struct RangePtr {
+ const Slice* start;
+ const Slice* limit;
+
+ RangePtr() : start(nullptr), limit(nullptr) {}
+ RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
+};
+
+struct IngestExternalFileArg {
+ ColumnFamilyHandle* column_family = nullptr;
+ std::vector<std::string> external_files;
+ IngestExternalFileOptions options;
+};
+
+struct GetMergeOperandsOptions {
+ int expected_max_number_of_operands = 0;
+};
+
+// A collections of table properties objects, where
+// key: is the table's file name.
+// value: the table properties object of the given table.
+typedef std::unordered_map<std::string, std::shared_ptr<const TableProperties>>
+ TablePropertiesCollection;
+
+// A DB is a persistent ordered map from keys to values.
+// A DB is safe for concurrent access from multiple threads without
+// any external synchronization.
+class DB {
+ public:
+ // Open the database with the specified "name".
+ // Stores a pointer to a heap-allocated database in *dbptr and returns
+ // OK on success.
+ // Stores nullptr in *dbptr and returns a non-OK status on error.
+ // Caller should delete *dbptr when it is no longer needed.
+ static Status Open(const Options& options, const std::string& name,
+ DB** dbptr);
+
+ // Open the database for read only. All DB interfaces
+ // that modify data, like put/delete, will return error.
+ // If the db is opened in read only mode, then no compactions
+ // will happen.
+ //
+ // Not supported in ROCKSDB_LITE, in which case the function will
+ // return Status::NotSupported.
+ static Status OpenForReadOnly(const Options& options, const std::string& name,
+ DB** dbptr,
+ bool error_if_log_file_exist = false);
+
+ // Open the database for read only with column families. When opening DB with
+ // read only, you can specify only a subset of column families in the
+ // database that should be opened. However, you always need to specify default
+ // column family. The default column family name is 'default' and it's stored
+ // in ROCKSDB_NAMESPACE::kDefaultColumnFamilyName
+ //
+ // Not supported in ROCKSDB_LITE, in which case the function will
+ // return Status::NotSupported.
+ static Status OpenForReadOnly(
+ const DBOptions& db_options, const std::string& name,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
+ bool error_if_log_file_exist = false);
+
+ // The following OpenAsSecondary functions create a secondary instance that
+ // can dynamically tail the MANIFEST of a primary that must have already been
+ // created. User can call 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.
+ //
+ // The options argument specifies the options to open the secondary instance.
+ // The name argument specifies the name of the primary db that you have used
+ // to open the primary instance.
+ // The secondary_path argument points to a directory where the secondary
+ // instance stores its info log.
+ // The dbptr is an out-arg corresponding to the opened secondary instance.
+ // The pointer points to a heap-allocated database, and the user should
+ // delete it after use.
+ // Open DB as secondary instance with only the default column family.
+ // Return OK on success, non-OK on failures.
+ static Status OpenAsSecondary(const Options& options, const std::string& name,
+ const std::string& secondary_path, DB** dbptr);
+
+ // Open DB as secondary instance with column families. You can open a subset
+ // of column families in secondary mode.
+ // The db_options specify the database specific options.
+ // The name argument specifies the name of the primary db that you have used
+ // to open the primary instance.
+ // The secondary_path argument points to a directory where the secondary
+ // instance stores its info log.
+ // The column_families argument specifieds a list of column families to open.
+ // If any of the column families does not exist, the function returns non-OK
+ // status.
+ // The handles is an out-arg corresponding to the opened database column
+ // familiy handles.
+ // The dbptr is an out-arg corresponding to the opened secondary instance.
+ // The pointer points to a heap-allocated database, and the caller should
+ // delete it after use. Before deleting the dbptr, the user should also
+ // delete the pointers stored in handles vector.
+ // Return OK on success, on-OK on failures.
+ static Status OpenAsSecondary(
+ const DBOptions& db_options, const std::string& name,
+ const std::string& secondary_path,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr);
+
+ // Open DB with column families.
+ // db_options specify database specific options
+ // column_families is the vector of all column families in the database,
+ // containing column family name and options. You need to open ALL column
+ // families in the database. To get the list of column families, you can use
+ // ListColumnFamilies(). Also, you can open only a subset of column families
+ // for read-only access.
+ // The default column family name is 'default' and it's stored
+ // in ROCKSDB_NAMESPACE::kDefaultColumnFamilyName.
+ // If everything is OK, handles will on return be the same size
+ // as column_families --- handles[i] will be a handle that you
+ // will use to operate on column family column_family[i].
+ // Before delete DB, you have to close All column families by calling
+ // DestroyColumnFamilyHandle() with all the handles.
+ static Status Open(const DBOptions& db_options, const std::string& name,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DB** dbptr);
+
+ virtual Status Resume() { return Status::NotSupported(); }
+
+ // Close the DB by releasing resources, closing files etc. This should be
+ // called before calling the destructor so that the caller can get back a
+ // status in case there are any errors. This will not fsync the WAL files.
+ // If syncing is required, the caller must first call SyncWAL(), or Write()
+ // using an empty write batch with WriteOptions.sync=true.
+ // Regardless of the return status, the DB must be freed.
+ // If the return status is Aborted(), closing fails because there is
+ // unreleased snapshot in the system. In this case, users can release
+ // the unreleased snapshots and try again and expect it to succeed. For
+ // other status, recalling Close() will be no-op.
+ // If the return status is NotSupported(), then the DB implementation does
+ // cleanup in the destructor
+ virtual Status Close() { return Status::NotSupported(); }
+
+ // ListColumnFamilies will open the DB specified by argument name
+ // and return the list of all column families in that DB
+ // through column_families argument. The ordering of
+ // column families in column_families is unspecified.
+ static Status ListColumnFamilies(const DBOptions& db_options,
+ const std::string& name,
+ std::vector<std::string>* column_families);
+
+ DB() {}
+ // No copying allowed
+ DB(const DB&) = delete;
+ void operator=(const DB&) = delete;
+
+ virtual ~DB();
+
+ // Create a column_family and return the handle of column family
+ // through the argument handle.
+ virtual Status CreateColumnFamily(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle);
+
+ // Bulk create column families with the same column family options.
+ // Return the handles of the column families through the argument handles.
+ // In case of error, the request may succeed partially, and handles will
+ // contain column family handles that it managed to create, and have size
+ // equal to the number of created column families.
+ virtual Status CreateColumnFamilies(
+ const ColumnFamilyOptions& options,
+ const std::vector<std::string>& column_family_names,
+ std::vector<ColumnFamilyHandle*>* handles);
+
+ // Bulk create column families.
+ // Return the handles of the column families through the argument handles.
+ // In case of error, the request may succeed partially, and handles will
+ // contain column family handles that it managed to create, and have size
+ // equal to the number of created column families.
+ virtual Status CreateColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles);
+
+ // Drop a column family specified by column_family handle. This call
+ // only records a drop record in the manifest and prevents the column
+ // family from flushing and compacting.
+ virtual Status DropColumnFamily(ColumnFamilyHandle* column_family);
+
+ // 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.
+ virtual Status DropColumnFamilies(
+ const std::vector<ColumnFamilyHandle*>& column_families);
+
+ // Close a column family specified by column_family handle and destroy
+ // the column family handle specified to avoid double deletion. This call
+ // deletes the column family handle by default. Use this method to
+ // close column family instead of deleting column family handle directly
+ virtual Status DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family);
+
+ // Set the database entry for "key" to "value".
+ // If "key" already exists, it will be overwritten.
+ // Returns OK on success, and a non-OK status on error.
+ // Note: consider setting options.sync = true.
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) = 0;
+ virtual Status Put(const WriteOptions& options, const Slice& key,
+ const Slice& value) {
+ return Put(options, DefaultColumnFamily(), key, value);
+ }
+
+ // Remove 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.
+ // Note: consider setting options.sync = true.
+ virtual Status Delete(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+ virtual Status Delete(const WriteOptions& options, const Slice& key) {
+ return Delete(options, DefaultColumnFamily(), key);
+ }
+
+ // Remove the database entry for "key". Requires that the key exists
+ // and was not overwritten. Returns OK on success, and a non-OK status
+ // on error. It is not an error if "key" did not exist in the database.
+ //
+ // If a key is overwritten (by calling Put() 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 options.sync = true.
+ virtual Status SingleDelete(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+ virtual Status SingleDelete(const WriteOptions& options, const Slice& key) {
+ return SingleDelete(options, DefaultColumnFamily(), key);
+ }
+
+ // Removes the database entries in the range ["begin_key", "end_key"), i.e.,
+ // including "begin_key" and excluding "end_key". Returns OK on success, and
+ // a non-OK status on error. It is not an error if no keys exist in the range
+ // ["begin_key", "end_key").
+ //
+ // This feature is now usable in production, with the following caveats:
+ // 1) Accumulating many range tombstones in the memtable will degrade read
+ // performance; this can be avoided by manually flushing occasionally.
+ // 2) Limiting the maximum number of open files in the presence of range
+ // tombstones can degrade read performance. To avoid this problem, set
+ // max_open_files to -1 whenever possible.
+ virtual Status DeleteRange(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& begin_key, const Slice& end_key);
+
+ // Merge the database entry for "key" with "value". Returns OK on success,
+ // and a non-OK status on error. The semantics of this operation is
+ // determined by the user provided merge_operator when opening DB.
+ // Note: consider setting options.sync = true.
+ virtual Status Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) = 0;
+ virtual Status Merge(const WriteOptions& options, const Slice& key,
+ const Slice& value) {
+ return Merge(options, DefaultColumnFamily(), key, value);
+ }
+
+ // Apply the specified updates to the database.
+ // If `updates` contains no update, WAL will still be synced if
+ // options.sync=true.
+ // Returns OK on success, non-OK on failure.
+ // Note: consider setting options.sync = true.
+ virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0;
+
+ // If the database contains an entry for "key" store the
+ // corresponding value in *value and return OK.
+ //
+ // If there is no entry for "key" leave *value unchanged and return
+ // a status for which Status::IsNotFound() returns true.
+ //
+ // May return some other Status on an error.
+ virtual inline Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value) {
+ assert(value != nullptr);
+ PinnableSlice pinnable_val(value);
+ assert(!pinnable_val.IsPinned());
+ auto s = Get(options, column_family, key, &pinnable_val);
+ if (s.ok() && pinnable_val.IsPinned()) {
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ } // else value is already assigned
+ return s;
+ }
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) = 0;
+ virtual Status Get(const ReadOptions& options, const Slice& key,
+ std::string* value) {
+ return Get(options, DefaultColumnFamily(), key, value);
+ }
+
+ // Returns all the merge operands corresponding to the key. If the
+ // number of merge operands in DB is greater than
+ // merge_operands_options.expected_max_number_of_operands
+ // no merge operands are returned and status is Incomplete. Merge operands
+ // returned are in the order of insertion.
+ // merge_operands- Points to an array of at-least
+ // merge_operands_options.expected_max_number_of_operands and the
+ // caller is responsible for allocating it. If the status
+ // returned is Incomplete then number_of_operands will contain
+ // the total number of merge operands found in DB for key.
+ virtual Status GetMergeOperands(
+ const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* merge_operands,
+ GetMergeOperandsOptions* get_merge_operands_options,
+ int* number_of_operands) = 0;
+
+ // If keys[i] does not exist in the database, then the i'th returned
+ // status will be one for which Status::IsNotFound() is true, and
+ // (*values)[i] will be set to some arbitrary value (often ""). Otherwise,
+ // the i'th returned status will have Status::ok() true, and (*values)[i]
+ // will store the value associated with keys[i].
+ //
+ // (*values) will always be resized to be the same size as (keys).
+ // Similarly, the number of returned statuses will be the number of keys.
+ // Note: keys will not be "de-duplicated". Duplicate keys will return
+ // duplicate values in order.
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) = 0;
+ virtual std::vector<Status> MultiGet(const ReadOptions& options,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) {
+ return MultiGet(
+ options,
+ std::vector<ColumnFamilyHandle*>(keys.size(), DefaultColumnFamily()),
+ keys, values);
+ }
+
+ // Overloaded MultiGet API that improves performance by batching operations
+ // in the read path for greater efficiency. Currently, only the block based
+ // table format with full filters are supported. Other table formats such
+ // as plain table, block based table with block based filters and
+ // partitioned indexes will still work, but will not get any performance
+ // benefits.
+ // Parameters -
+ // options - ReadOptions
+ // column_family - ColumnFamilyHandle* that the keys belong to. All the keys
+ // passed to the API are restricted to a single column family
+ // num_keys - Number of keys to lookup
+ // keys - Pointer to C style array of key Slices with num_keys elements
+ // values - Pointer to C style array of PinnableSlices with num_keys elements
+ // statuses - Pointer to C style array of Status with num_keys elements
+ // sorted_input - If true, it means the input keys are already sorted by key
+ // order, so the MultiGet() API doesn't have to sort them
+ // again. If false, the keys will be copied and sorted
+ // internally by the API - the input array will not be
+ // modified
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool /*sorted_input*/ = false) {
+ std::vector<ColumnFamilyHandle*> cf;
+ std::vector<Slice> user_keys;
+ std::vector<Status> status;
+ std::vector<std::string> vals;
+
+ for (size_t i = 0; i < num_keys; ++i) {
+ cf.emplace_back(column_family);
+ user_keys.emplace_back(keys[i]);
+ }
+ status = MultiGet(options, cf, user_keys, &vals);
+ std::copy(status.begin(), status.end(), statuses);
+ for (auto& value : vals) {
+ values->PinSelf(value);
+ values++;
+ }
+ }
+
+ // Overloaded MultiGet API that improves performance by batching operations
+ // in the read path for greater efficiency. Currently, only the block based
+ // table format with full filters are supported. Other table formats such
+ // as plain table, block based table with block based filters and
+ // partitioned indexes will still work, but will not get any performance
+ // benefits.
+ // Parameters -
+ // options - ReadOptions
+ // column_family - ColumnFamilyHandle* that the keys belong to. All the keys
+ // passed to the API are restricted to a single column family
+ // num_keys - Number of keys to lookup
+ // keys - Pointer to C style array of key Slices with num_keys elements
+ // values - Pointer to C style array of PinnableSlices with num_keys elements
+ // statuses - Pointer to C style array of Status with num_keys elements
+ // sorted_input - If true, it means the input keys are already sorted by key
+ // order, so the MultiGet() API doesn't have to sort them
+ // again. If false, the keys will be copied and sorted
+ // internally by the API - the input array will not be
+ // modified
+ virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
+ ColumnFamilyHandle** column_families, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool /*sorted_input*/ = false) {
+ std::vector<ColumnFamilyHandle*> cf;
+ std::vector<Slice> user_keys;
+ std::vector<Status> status;
+ std::vector<std::string> vals;
+
+ for (size_t i = 0; i < num_keys; ++i) {
+ cf.emplace_back(column_families[i]);
+ user_keys.emplace_back(keys[i]);
+ }
+ status = MultiGet(options, cf, user_keys, &vals);
+ std::copy(status.begin(), status.end(), statuses);
+ for (auto& value : vals) {
+ values->PinSelf(value);
+ values++;
+ }
+ }
+
+ // If the key definitely does not exist in the database, then this method
+ // returns false, else true. If the caller wants to obtain value when the key
+ // is found in memory, a bool for 'value_found' must be passed. 'value_found'
+ // will be true on return if value has been set properly.
+ // This check is potentially lighter-weight than invoking DB::Get(). One way
+ // to make this lighter weight is to avoid doing any IOs.
+ // Default implementation here returns true and sets 'value_found' to false
+ virtual bool KeyMayExist(const ReadOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, std::string* /*value*/,
+ bool* value_found = nullptr) {
+ if (value_found != nullptr) {
+ *value_found = false;
+ }
+ return true;
+ }
+ virtual bool KeyMayExist(const ReadOptions& options, const Slice& key,
+ std::string* value, bool* value_found = nullptr) {
+ return KeyMayExist(options, DefaultColumnFamily(), key, value, value_found);
+ }
+
+ // 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 delete the iterator when it is no longer needed.
+ // The returned iterator should be deleted before this db is deleted.
+ virtual Iterator* NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) = 0;
+ virtual Iterator* NewIterator(const ReadOptions& options) {
+ return NewIterator(options, DefaultColumnFamily());
+ }
+ // 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
+ virtual Status NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) = 0;
+
+ // Return 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.
+ virtual const Snapshot* GetSnapshot() = 0;
+
+ // Release a previously acquired snapshot. The caller must not
+ // use "snapshot" after this call.
+ virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0;
+
+#ifndef ROCKSDB_LITE
+ // Contains all valid property arguments for GetProperty().
+ //
+ // NOTE: Property names cannot end in numbers since those are interpreted as
+ // arguments, e.g., see kNumFilesAtLevelPrefix.
+ struct Properties {
+ // "rocksdb.num-files-at-level<N>" - returns string containing the number
+ // of files at level <N>, where <N> is an ASCII representation of a
+ // level number (e.g., "0").
+ static const std::string kNumFilesAtLevelPrefix;
+
+ // "rocksdb.compression-ratio-at-level<N>" - returns string containing the
+ // compression ratio of data at level <N>, where <N> is an ASCII
+ // representation of a level number (e.g., "0"). Here, compression
+ // ratio is defined as uncompressed data size / compressed file size.
+ // Returns "-1.0" if no open files at level <N>.
+ static const std::string kCompressionRatioAtLevelPrefix;
+
+ // "rocksdb.stats" - returns a multi-line string containing the data
+ // described by kCFStats followed by the data described by kDBStats.
+ static const std::string kStats;
+
+ // "rocksdb.sstables" - returns a multi-line string summarizing current
+ // SST files.
+ static const std::string kSSTables;
+
+ // "rocksdb.cfstats" - Both of "rocksdb.cfstats-no-file-histogram" and
+ // "rocksdb.cf-file-histogram" together. See below for description
+ // of the two.
+ static const std::string kCFStats;
+
+ // "rocksdb.cfstats-no-file-histogram" - returns a multi-line string with
+ // general columm family stats per-level over db's lifetime ("L<n>"),
+ // aggregated over db's lifetime ("Sum"), and aggregated over the
+ // interval since the last retrieval ("Int").
+ // It could also be used to return the stats in the format of the map.
+ // In this case there will a pair of string to array of double for
+ // each level as well as for "Sum". "Int" stats will not be affected
+ // when this form of stats are retrieved.
+ static const std::string kCFStatsNoFileHistogram;
+
+ // "rocksdb.cf-file-histogram" - print out how many file reads to every
+ // level, as well as the histogram of latency of single requests.
+ static const std::string kCFFileHistogram;
+
+ // "rocksdb.dbstats" - returns a multi-line string with general database
+ // stats, both cumulative (over the db's lifetime) and interval (since
+ // the last retrieval of kDBStats).
+ static const std::string kDBStats;
+
+ // "rocksdb.levelstats" - returns multi-line string containing the number
+ // of files per level and total size of each level (MB).
+ static const std::string kLevelStats;
+
+ // "rocksdb.num-immutable-mem-table" - returns number of immutable
+ // memtables that have not yet been flushed.
+ static const std::string kNumImmutableMemTable;
+
+ // "rocksdb.num-immutable-mem-table-flushed" - returns number of immutable
+ // memtables that have already been flushed.
+ static const std::string kNumImmutableMemTableFlushed;
+
+ // "rocksdb.mem-table-flush-pending" - returns 1 if a memtable flush is
+ // pending; otherwise, returns 0.
+ static const std::string kMemTableFlushPending;
+
+ // "rocksdb.num-running-flushes" - returns the number of currently running
+ // flushes.
+ static const std::string kNumRunningFlushes;
+
+ // "rocksdb.compaction-pending" - returns 1 if at least one compaction is
+ // pending; otherwise, returns 0.
+ static const std::string kCompactionPending;
+
+ // "rocksdb.num-running-compactions" - returns the number of currently
+ // running compactions.
+ static const std::string kNumRunningCompactions;
+
+ // "rocksdb.background-errors" - returns accumulated number of background
+ // errors.
+ static const std::string kBackgroundErrors;
+
+ // "rocksdb.cur-size-active-mem-table" - returns approximate size of active
+ // memtable (bytes).
+ static const std::string kCurSizeActiveMemTable;
+
+ // "rocksdb.cur-size-all-mem-tables" - returns approximate size of active
+ // and unflushed immutable memtables (bytes).
+ static const std::string kCurSizeAllMemTables;
+
+ // "rocksdb.size-all-mem-tables" - returns approximate size of active,
+ // unflushed immutable, and pinned immutable memtables (bytes).
+ static const std::string kSizeAllMemTables;
+
+ // "rocksdb.num-entries-active-mem-table" - returns total number of entries
+ // in the active memtable.
+ static const std::string kNumEntriesActiveMemTable;
+
+ // "rocksdb.num-entries-imm-mem-tables" - returns total number of entries
+ // in the unflushed immutable memtables.
+ static const std::string kNumEntriesImmMemTables;
+
+ // "rocksdb.num-deletes-active-mem-table" - returns total number of delete
+ // entries in the active memtable.
+ static const std::string kNumDeletesActiveMemTable;
+
+ // "rocksdb.num-deletes-imm-mem-tables" - returns total number of delete
+ // entries in the unflushed immutable memtables.
+ static const std::string kNumDeletesImmMemTables;
+
+ // "rocksdb.estimate-num-keys" - returns estimated number of total keys in
+ // the active and unflushed immutable memtables and storage.
+ static const std::string kEstimateNumKeys;
+
+ // "rocksdb.estimate-table-readers-mem" - returns estimated memory used for
+ // reading SST tables, excluding memory used in block cache (e.g.,
+ // filter and index blocks).
+ static const std::string kEstimateTableReadersMem;
+
+ // "rocksdb.is-file-deletions-enabled" - returns 0 if deletion of obsolete
+ // files is enabled; otherwise, returns a non-zero number.
+ static const std::string kIsFileDeletionsEnabled;
+
+ // "rocksdb.num-snapshots" - returns number of unreleased snapshots of the
+ // database.
+ static const std::string kNumSnapshots;
+
+ // "rocksdb.oldest-snapshot-time" - returns number representing unix
+ // timestamp of oldest unreleased snapshot.
+ static const std::string kOldestSnapshotTime;
+
+ // "rocksdb.oldest-snapshot-sequence" - returns number representing
+ // sequence number of oldest unreleased snapshot.
+ static const std::string kOldestSnapshotSequence;
+
+ // "rocksdb.num-live-versions" - returns number of live versions. `Version`
+ // is an internal data structure. See version_set.h for details. More
+ // live versions often mean more SST files are held from being deleted,
+ // by iterators or unfinished compactions.
+ static const std::string kNumLiveVersions;
+
+ // "rocksdb.current-super-version-number" - returns number of current LSM
+ // version. It is a uint64_t integer number, incremented after there is
+ // any change to the LSM tree. The number is not preserved after restarting
+ // the DB. After DB restart, it will start from 0 again.
+ static const std::string kCurrentSuperVersionNumber;
+
+ // "rocksdb.estimate-live-data-size" - returns an estimate of the amount of
+ // live data in bytes.
+ static const std::string kEstimateLiveDataSize;
+
+ // "rocksdb.min-log-number-to-keep" - return the minimum log number of the
+ // log files that should be kept.
+ static const std::string kMinLogNumberToKeep;
+
+ // "rocksdb.min-obsolete-sst-number-to-keep" - return the minimum file
+ // number for an obsolete SST to be kept. The max value of `uint64_t`
+ // will be returned if all obsolete files can be deleted.
+ static const std::string kMinObsoleteSstNumberToKeep;
+
+ // "rocksdb.total-sst-files-size" - returns total size (bytes) of all SST
+ // files.
+ // WARNING: may slow down online queries if there are too many files.
+ static const std::string kTotalSstFilesSize;
+
+ // "rocksdb.live-sst-files-size" - returns total size (bytes) of all SST
+ // files belong to the latest LSM tree.
+ static const std::string kLiveSstFilesSize;
+
+ // "rocksdb.base-level" - returns number of level to which L0 data will be
+ // compacted.
+ static const std::string kBaseLevel;
+
+ // "rocksdb.estimate-pending-compaction-bytes" - returns estimated total
+ // number of bytes compaction needs to rewrite to get all levels down
+ // to under target size. Not valid for other compactions than level-
+ // based.
+ static const std::string kEstimatePendingCompactionBytes;
+
+ // "rocksdb.aggregated-table-properties" - returns a string representation
+ // of the aggregated table properties of the target column family.
+ static const std::string kAggregatedTableProperties;
+
+ // "rocksdb.aggregated-table-properties-at-level<N>", same as the previous
+ // one but only returns the aggregated table properties of the
+ // specified level "N" at the target column family.
+ static const std::string kAggregatedTablePropertiesAtLevel;
+
+ // "rocksdb.actual-delayed-write-rate" - returns the current actual delayed
+ // write rate. 0 means no delay.
+ static const std::string kActualDelayedWriteRate;
+
+ // "rocksdb.is-write-stopped" - Return 1 if write has been stopped.
+ static const std::string kIsWriteStopped;
+
+ // "rocksdb.estimate-oldest-key-time" - returns an estimation of
+ // oldest key timestamp in the DB. Currently only available for
+ // FIFO compaction with
+ // compaction_options_fifo.allow_compaction = false.
+ static const std::string kEstimateOldestKeyTime;
+
+ // "rocksdb.block-cache-capacity" - returns block cache capacity.
+ static const std::string kBlockCacheCapacity;
+
+ // "rocksdb.block-cache-usage" - returns the memory size for the entries
+ // residing in block cache.
+ static const std::string kBlockCacheUsage;
+
+ // "rocksdb.block-cache-pinned-usage" - returns the memory size for the
+ // entries being pinned.
+ static const std::string kBlockCachePinnedUsage;
+
+ // "rocksdb.options-statistics" - returns multi-line string
+ // of options.statistics
+ static const std::string kOptionsStatistics;
+ };
+#endif /* ROCKSDB_LITE */
+
+ // DB implementations can export properties about their state via this method.
+ // If "property" is a valid property understood by this DB implementation (see
+ // Properties struct above for valid options), fills "*value" with its current
+ // value and returns true. Otherwise, returns false.
+ virtual bool GetProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, std::string* value) = 0;
+ virtual bool GetProperty(const Slice& property, std::string* value) {
+ return GetProperty(DefaultColumnFamily(), property, value);
+ }
+ virtual bool GetMapProperty(ColumnFamilyHandle* column_family,
+ const Slice& property,
+ std::map<std::string, std::string>* value) = 0;
+ virtual bool GetMapProperty(const Slice& property,
+ std::map<std::string, std::string>* value) {
+ return GetMapProperty(DefaultColumnFamily(), property, value);
+ }
+
+ // Similar to GetProperty(), but only works for a subset of properties whose
+ // return value is an integer. Return the value by integer. Supported
+ // properties:
+ // "rocksdb.num-immutable-mem-table"
+ // "rocksdb.mem-table-flush-pending"
+ // "rocksdb.compaction-pending"
+ // "rocksdb.background-errors"
+ // "rocksdb.cur-size-active-mem-table"
+ // "rocksdb.cur-size-all-mem-tables"
+ // "rocksdb.size-all-mem-tables"
+ // "rocksdb.num-entries-active-mem-table"
+ // "rocksdb.num-entries-imm-mem-tables"
+ // "rocksdb.num-deletes-active-mem-table"
+ // "rocksdb.num-deletes-imm-mem-tables"
+ // "rocksdb.estimate-num-keys"
+ // "rocksdb.estimate-table-readers-mem"
+ // "rocksdb.is-file-deletions-enabled"
+ // "rocksdb.num-snapshots"
+ // "rocksdb.oldest-snapshot-time"
+ // "rocksdb.num-live-versions"
+ // "rocksdb.current-super-version-number"
+ // "rocksdb.estimate-live-data-size"
+ // "rocksdb.min-log-number-to-keep"
+ // "rocksdb.min-obsolete-sst-number-to-keep"
+ // "rocksdb.total-sst-files-size"
+ // "rocksdb.live-sst-files-size"
+ // "rocksdb.base-level"
+ // "rocksdb.estimate-pending-compaction-bytes"
+ // "rocksdb.num-running-compactions"
+ // "rocksdb.num-running-flushes"
+ // "rocksdb.actual-delayed-write-rate"
+ // "rocksdb.is-write-stopped"
+ // "rocksdb.estimate-oldest-key-time"
+ // "rocksdb.block-cache-capacity"
+ // "rocksdb.block-cache-usage"
+ // "rocksdb.block-cache-pinned-usage"
+ virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, uint64_t* value) = 0;
+ virtual bool GetIntProperty(const Slice& property, uint64_t* value) {
+ return GetIntProperty(DefaultColumnFamily(), property, value);
+ }
+
+ // Reset internal stats for DB and all column families.
+ // Note this doesn't reset options.statistics as it is not owned by
+ // DB.
+ virtual Status ResetStats() {
+ return Status::NotSupported("Not implemented");
+ }
+
+ // Same as GetIntProperty(), but this one returns the aggregated int
+ // property from all column families.
+ virtual bool GetAggregatedIntProperty(const Slice& property,
+ uint64_t* value) = 0;
+
+ // Flags for DB::GetSizeApproximation that specify whether memtable
+ // stats should be included, or file stats approximation or both
+ enum SizeApproximationFlags : uint8_t {
+ NONE = 0,
+ INCLUDE_MEMTABLES = 1 << 0,
+ INCLUDE_FILES = 1 << 1
+ };
+
+ // For each i in [0,n-1], store in "sizes[i]", the approximate
+ // file system space used by keys in "[range[i].start .. range[i].limit)".
+ //
+ // 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.
+ virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Range* range, int n,
+ uint64_t* sizes) = 0;
+
+ // Simpler versions of the GetApproximateSizes() method above.
+ // The include_flags argumenbt must of type DB::SizeApproximationFlags
+ // and can not be NONE.
+ virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
+ const Range* range, int n, uint64_t* sizes,
+ uint8_t include_flags = INCLUDE_FILES) {
+ SizeApproximationOptions options;
+ options.include_memtabtles =
+ (include_flags & SizeApproximationFlags::INCLUDE_MEMTABLES) != 0;
+ options.include_files =
+ (include_flags & SizeApproximationFlags::INCLUDE_FILES) != 0;
+ GetApproximateSizes(options, column_family, range, n, sizes);
+ }
+ virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes,
+ uint8_t include_flags = INCLUDE_FILES) {
+ GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, include_flags);
+ }
+
+ // The method is similar to GetApproximateSizes, except it
+ // returns approximate number of records in memtables.
+ virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
+ const Range& range,
+ uint64_t* const count,
+ uint64_t* const size) = 0;
+ virtual void GetApproximateMemTableStats(const Range& range,
+ uint64_t* const count,
+ uint64_t* const size) {
+ GetApproximateMemTableStats(DefaultColumnFamily(), range, count, size);
+ }
+
+ // Deprecated versions of GetApproximateSizes
+ ROCKSDB_DEPRECATED_FUNC virtual void GetApproximateSizes(
+ const Range* range, int n, uint64_t* sizes, bool include_memtable) {
+ uint8_t include_flags = SizeApproximationFlags::INCLUDE_FILES;
+ if (include_memtable) {
+ include_flags |= SizeApproximationFlags::INCLUDE_MEMTABLES;
+ }
+ GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, include_flags);
+ }
+ ROCKSDB_DEPRECATED_FUNC virtual void GetApproximateSizes(
+ ColumnFamilyHandle* column_family, const Range* range, int n,
+ uint64_t* sizes, bool include_memtable) {
+ uint8_t include_flags = SizeApproximationFlags::INCLUDE_FILES;
+ if (include_memtable) {
+ include_flags |= SizeApproximationFlags::INCLUDE_MEMTABLES;
+ }
+ GetApproximateSizes(column_family, range, n, sizes, include_flags);
+ }
+
+ // Compact the underlying storage for the key range [*begin,*end].
+ // The actual compaction interval might be superset of [*begin, *end].
+ // In particular, deleted and overwritten versions are discarded,
+ // and the data is rearranged to reduce the cost of operations
+ // needed to access the data. This operation should typically only
+ // be invoked by users who understand the underlying implementation.
+ //
+ // begin==nullptr is treated as a key before all keys in the database.
+ // end==nullptr is treated as a key after all keys in the database.
+ // Therefore the following call will compact the entire database:
+ // db->CompactRange(options, nullptr, nullptr);
+ // Note that after the entire database is compacted, all data are pushed
+ // down to the last level containing any data. If the total data size after
+ // compaction is reduced, that level might not be appropriate for hosting all
+ // the files. In this case, client could set options.change_level to true, to
+ // move the files back to the minimum level capable of holding the data set
+ // or a given level (specified by non-negative options.target_level).
+ virtual Status CompactRange(const CompactRangeOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) = 0;
+ virtual Status CompactRange(const CompactRangeOptions& options,
+ const Slice* begin, const Slice* end) {
+ return CompactRange(options, DefaultColumnFamily(), begin, end);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status CompactRange(
+ ColumnFamilyHandle* column_family, const Slice* begin, const Slice* end,
+ bool change_level = false, int target_level = -1,
+ uint32_t target_path_id = 0) {
+ CompactRangeOptions options;
+ options.change_level = change_level;
+ options.target_level = target_level;
+ options.target_path_id = target_path_id;
+ return CompactRange(options, column_family, begin, end);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status CompactRange(
+ const Slice* begin, const Slice* end, bool change_level = false,
+ int target_level = -1, uint32_t target_path_id = 0) {
+ CompactRangeOptions options;
+ options.change_level = change_level;
+ options.target_level = target_level;
+ options.target_path_id = target_path_id;
+ return CompactRange(options, DefaultColumnFamily(), begin, end);
+ }
+
+ virtual Status SetOptions(
+ ColumnFamilyHandle* /*column_family*/,
+ const std::unordered_map<std::string, std::string>& /*new_options*/) {
+ return Status::NotSupported("Not implemented");
+ }
+ virtual Status SetOptions(
+ const std::unordered_map<std::string, std::string>& new_options) {
+ return SetOptions(DefaultColumnFamily(), new_options);
+ }
+
+ virtual Status SetDBOptions(
+ const std::unordered_map<std::string, std::string>& new_options) = 0;
+
+ // CompactFiles() inputs a list of files specified by file numbers and
+ // compacts them to the specified level. Note that the behavior is different
+ // from CompactRange() in that CompactFiles() performs the compaction job
+ // using the CURRENT thread.
+ //
+ // @see GetDataBaseMetaData
+ // @see GetColumnFamilyMetaData
+ virtual Status CompactFiles(
+ const CompactionOptions& compact_options,
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) = 0;
+
+ virtual Status CompactFiles(
+ const CompactionOptions& compact_options,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) {
+ return CompactFiles(compact_options, DefaultColumnFamily(),
+ input_file_names, output_level, output_path_id,
+ output_file_names, compaction_job_info);
+ }
+
+ // This function will wait until all currently running background processes
+ // finish. After it returns, no background process will be run until
+ // ContinueBackgroundWork is called
+ virtual Status PauseBackgroundWork() = 0;
+ virtual Status ContinueBackgroundWork() = 0;
+
+ // This function will enable automatic compactions for the given column
+ // families if they were previously disabled. The function will first set the
+ // disable_auto_compactions option for each column family to 'false', after
+ // which it will schedule a flush/compaction.
+ //
+ // NOTE: Setting disable_auto_compactions to 'false' through SetOptions() API
+ // does NOT schedule a flush/compaction afterwards, and only changes the
+ // parameter itself within the column family option.
+ //
+ virtual Status EnableAutoCompaction(
+ const std::vector<ColumnFamilyHandle*>& column_family_handles) = 0;
+
+ virtual void DisableManualCompaction() = 0;
+ virtual void EnableManualCompaction() = 0;
+
+ // Number of levels used for this DB.
+ virtual int NumberLevels(ColumnFamilyHandle* column_family) = 0;
+ virtual int NumberLevels() { return NumberLevels(DefaultColumnFamily()); }
+
+ // Maximum level to which a new compacted memtable is pushed if it
+ // does not create overlap.
+ virtual int MaxMemCompactionLevel(ColumnFamilyHandle* column_family) = 0;
+ virtual int MaxMemCompactionLevel() {
+ return MaxMemCompactionLevel(DefaultColumnFamily());
+ }
+
+ // Number of files in level-0 that would stop writes.
+ virtual int Level0StopWriteTrigger(ColumnFamilyHandle* column_family) = 0;
+ virtual int Level0StopWriteTrigger() {
+ return Level0StopWriteTrigger(DefaultColumnFamily());
+ }
+
+ // Get DB name -- the exact same name that was provided as an argument to
+ // DB::Open()
+ virtual const std::string& GetName() const = 0;
+
+ // Get Env object from the DB
+ virtual Env* GetEnv() const = 0;
+
+ virtual FileSystem* GetFileSystem() const;
+
+ // Get DB Options that we use. During the process of opening the
+ // column family, the options provided when calling DB::Open() or
+ // DB::CreateColumnFamily() will have been "sanitized" and transformed
+ // in an implementation-defined manner.
+ virtual Options GetOptions(ColumnFamilyHandle* column_family) const = 0;
+ virtual Options GetOptions() const {
+ return GetOptions(DefaultColumnFamily());
+ }
+
+ virtual DBOptions GetDBOptions() const = 0;
+
+ // Flush all mem-table data.
+ // Flush a single column family, even when atomic flush is enabled. To flush
+ // multiple column families, use Flush(options, column_families).
+ virtual Status Flush(const FlushOptions& options,
+ ColumnFamilyHandle* column_family) = 0;
+ virtual Status Flush(const FlushOptions& options) {
+ return Flush(options, DefaultColumnFamily());
+ }
+ // Flushes multiple column families.
+ // If atomic flush is not enabled, Flush(options, column_families) is
+ // equivalent to calling Flush(options, column_family) multiple times.
+ // If atomic flush is enabled, Flush(options, column_families) will flush all
+ // column families specified in 'column_families' up to the latest sequence
+ // number at the time when flush is requested.
+ // Note that RocksDB 5.15 and earlier may not be able to open later versions
+ // with atomic flush enabled.
+ virtual Status Flush(
+ const FlushOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families) = 0;
+
+ // Flush the WAL memory buffer to the file. If sync is true, it calls SyncWAL
+ // afterwards.
+ virtual Status FlushWAL(bool /*sync*/) {
+ return Status::NotSupported("FlushWAL not implemented");
+ }
+ // Sync the wal. Note that Write() followed by SyncWAL() is not exactly the
+ // same as Write() with sync=true: in the latter case the changes won't be
+ // visible until the sync is done.
+ // Currently only works if allow_mmap_writes = false in Options.
+ virtual Status SyncWAL() = 0;
+
+ // Lock the WAL. Also flushes the WAL after locking.
+ virtual Status LockWAL() {
+ return Status::NotSupported("LockWAL not implemented");
+ }
+
+ // Unlock the WAL.
+ virtual Status UnlockWAL() {
+ return Status::NotSupported("UnlockWAL not implemented");
+ }
+
+ // The sequence number of the most recent transaction.
+ virtual SequenceNumber GetLatestSequenceNumber() const = 0;
+
+ // Instructs DB to preserve deletes with sequence numbers >= passed seqnum.
+ // Has no effect if DBOptions.preserve_deletes is set to false.
+ // This function assumes that user calls this function with monotonically
+ // increasing seqnums (otherwise we can't guarantee that a particular delete
+ // hasn't been already processed); returns true if the value was successfully
+ // updated, false if user attempted to call if with seqnum <= current value.
+ virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) = 0;
+
+#ifndef ROCKSDB_LITE
+
+ // 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.
+ virtual Status DisableFileDeletions() = 0;
+
+ // 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()
+ virtual Status EnableFileDeletions(bool force = true) = 0;
+
+ // GetLiveFiles followed by GetSortedWalFiles can generate a lossless backup
+
+ // Retrieve the list of all files in the database. The files are
+ // relative to the dbname and are not absolute paths. Despite being relative
+ // paths, the file names begin with "/". The valid size of the manifest file
+ // is returned in manifest_file_size. The manifest file is an ever growing
+ // file, but only the portion specified by manifest_file_size is valid for
+ // this snapshot. Setting flush_memtable to true does Flush before recording
+ // the live files. Setting flush_memtable 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.
+ //
+ // In case you have multiple column families, even if flush_memtable is true,
+ // you still need to call GetSortedWalFiles after GetLiveFiles to compensate
+ // for new data that arrived to already-flushed column families while other
+ // column families were flushing
+ virtual Status GetLiveFiles(std::vector<std::string>&,
+ uint64_t* manifest_file_size,
+ bool flush_memtable = true) = 0;
+
+ // Retrieve the sorted list of all wal files with earliest file first
+ virtual Status GetSortedWalFiles(VectorLogPtr& files) = 0;
+
+ // Retrieve information about the current wal file
+ //
+ // Note that the log might have rolled after this call in which case
+ // the current_log_file would not point to the current log file.
+ //
+ // Additionally, for the sake of optimization current_log_file->StartSequence
+ // would always be set to 0
+ virtual Status GetCurrentWalFile(
+ std::unique_ptr<LogFile>* current_log_file) = 0;
+
+ // Retrieves the creation time of the oldest file in the DB.
+ // This API only works if max_open_files = -1, if it is not then
+ // Status returned is Status::NotSupported()
+ // The file creation time is set using the env provided to the DB.
+ // If the DB was created from a very old release then its possible that
+ // the SST files might not have file_creation_time property and even after
+ // moving to a newer release its possible that some files never got compacted
+ // and may not have file_creation_time property. In both the cases
+ // file_creation_time is considered 0 which means this API will return
+ // creation_time = 0 as there wouldn't be a timestamp lower than 0.
+ virtual Status GetCreationTimeOfOldestFile(uint64_t* creation_time) = 0;
+
+ // Note: this API is not yet consistent with WritePrepared transactions.
+ // Sets iter to 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
+ // Returns Status::OK if iterator is valid
+ // 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.
+ virtual Status GetUpdatesSince(
+ SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options =
+ TransactionLogIterator::ReadOptions()) = 0;
+
+// Windows API macro interference
+#undef DeleteFile
+ // 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
+ virtual Status DeleteFile(std::string name) = 0;
+
+ // Returns a list of all table files with their level, start key
+ // and end key
+ virtual void GetLiveFilesMetaData(
+ std::vector<LiveFileMetaData>* /*metadata*/) {}
+
+ // Obtains the meta data of the specified column family of the DB.
+ virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
+ ColumnFamilyMetaData* /*metadata*/) {}
+
+ // Get the metadata of the default column family.
+ void GetColumnFamilyMetaData(ColumnFamilyMetaData* metadata) {
+ GetColumnFamilyMetaData(DefaultColumnFamily(), metadata);
+ }
+
+ // IngestExternalFile() will load a list of external SST files (1) into the DB
+ // Two primary modes are supported:
+ // - Duplicate keys in the new files will overwrite exiting keys (default)
+ // - Duplicate keys will be skipped (set ingest_behind=true)
+ // In the first mode 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.
+ // In the second mode we will always ingest in the bottom most level (see
+ // docs to IngestExternalFileOptions::ingest_behind).
+ //
+ // (1) External SST files can be created using 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
+ // (3) If IngestExternalFileOptions->ingest_behind is set to true,
+ // we always ingest at the bottommost level, which should be reserved
+ // for this purpose (see DBOPtions::allow_ingest_behind flag).
+ virtual Status IngestExternalFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& external_files,
+ const IngestExternalFileOptions& options) = 0;
+
+ virtual Status IngestExternalFile(
+ const std::vector<std::string>& external_files,
+ const IngestExternalFileOptions& options) {
+ return IngestExternalFile(DefaultColumnFamily(), external_files, options);
+ }
+
+ // IngestExternalFiles() will ingest files for multiple column families, and
+ // record the result atomically to the MANIFEST.
+ // If this function returns OK, all column families' ingestion must succeed.
+ // If this function returns NOK, or the process crashes, then non-of the
+ // files will be ingested into the database after recovery.
+ // Note that it is possible for application to observe a mixed state during
+ // the execution of this function. If the user performs range scan over the
+ // column families with iterators, iterator on one column family may return
+ // ingested data, while iterator on other column family returns old data.
+ // Users can use snapshot for a consistent view of data.
+ // If your db ingests multiple SST files using this API, i.e. args.size()
+ // > 1, then RocksDB 5.15 and earlier will not be able to open it.
+ //
+ // REQUIRES: each arg corresponds to a different column family: namely, for
+ // 0 <= i < j < len(args), args[i].column_family != args[j].column_family.
+ virtual Status IngestExternalFiles(
+ const std::vector<IngestExternalFileArg>& args) = 0;
+
+ // CreateColumnFamilyWithImport() will create a new column family with
+ // column_family_name and import external SST files specified in metadata into
+ // this column family.
+ // (1) External SST files can be created using SstFileWriter.
+ // (2) External SST files can be exported from a particular column family in
+ // an existing DB.
+ // Option in import_options specifies whether the external files are copied or
+ // moved (default is copy). When option specifies copy, managing files at
+ // external_file_path is caller's responsibility. When option specifies a
+ // move, the call ensures that the specified files at external_file_path are
+ // deleted on successful return and files are not modified on any error
+ // return.
+ // On error return, column family handle returned will be nullptr.
+ // ColumnFamily will be present on successful return and will not be present
+ // on error return. ColumnFamily may be present on any crash during this call.
+ virtual Status CreateColumnFamilyWithImport(
+ const ColumnFamilyOptions& options, const std::string& column_family_name,
+ const ImportColumnFamilyOptions& import_options,
+ const ExportImportFilesMetaData& metadata,
+ ColumnFamilyHandle** handle) = 0;
+
+ virtual Status VerifyChecksum(const ReadOptions& read_options) = 0;
+
+ virtual Status VerifyChecksum() { return VerifyChecksum(ReadOptions()); }
+
+ // AddFile() is deprecated, please use IngestExternalFile()
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& file_path_list, bool move_file = false,
+ bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(column_family, file_path_list, ifo);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ const std::vector<std::string>& file_path_list, bool move_file = false,
+ bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(DefaultColumnFamily(), file_path_list, ifo);
+ }
+
+ // AddFile() is deprecated, please use IngestExternalFile()
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ ColumnFamilyHandle* column_family, const std::string& file_path,
+ bool move_file = false, bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(column_family, {file_path}, ifo);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ const std::string& file_path, bool move_file = false,
+ bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(DefaultColumnFamily(), {file_path}, ifo);
+ }
+
+ // Load table file with information "file_info" into "column_family"
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<ExternalSstFileInfo>& file_info_list,
+ bool move_file = false, bool skip_snapshot_check = false) {
+ std::vector<std::string> external_files;
+ for (const ExternalSstFileInfo& file_info : file_info_list) {
+ external_files.push_back(file_info.file_path);
+ }
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(column_family, external_files, ifo);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ const std::vector<ExternalSstFileInfo>& file_info_list,
+ bool move_file = false, bool skip_snapshot_check = false) {
+ std::vector<std::string> external_files;
+ for (const ExternalSstFileInfo& file_info : file_info_list) {
+ external_files.push_back(file_info.file_path);
+ }
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(DefaultColumnFamily(), external_files, ifo);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ ColumnFamilyHandle* column_family, const ExternalSstFileInfo* file_info,
+ bool move_file = false, bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(column_family, {file_info->file_path}, ifo);
+ }
+
+ ROCKSDB_DEPRECATED_FUNC virtual Status AddFile(
+ const ExternalSstFileInfo* file_info, bool move_file = false,
+ bool skip_snapshot_check = false) {
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_file;
+ ifo.snapshot_consistency = !skip_snapshot_check;
+ ifo.allow_global_seqno = false;
+ ifo.allow_blocking_flush = false;
+ return IngestExternalFile(DefaultColumnFamily(), {file_info->file_path},
+ ifo);
+ }
+
+#endif // ROCKSDB_LITE
+
+ // Returns the unique ID which is read from IDENTITY file during the opening
+ // of database by setting in the identity variable
+ // Returns Status::OK if identity could be set properly
+ virtual Status GetDbIdentity(std::string& identity) const = 0;
+
+ // Returns default column family handle
+ virtual ColumnFamilyHandle* DefaultColumnFamily() const = 0;
+
+#ifndef ROCKSDB_LITE
+ virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
+ TablePropertiesCollection* props) = 0;
+ virtual Status GetPropertiesOfAllTables(TablePropertiesCollection* props) {
+ return GetPropertiesOfAllTables(DefaultColumnFamily(), props);
+ }
+ virtual Status GetPropertiesOfTablesInRange(
+ ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
+ TablePropertiesCollection* props) = 0;
+
+ virtual Status SuggestCompactRange(ColumnFamilyHandle* /*column_family*/,
+ const Slice* /*begin*/,
+ const Slice* /*end*/) {
+ return Status::NotSupported("SuggestCompactRange() is not implemented.");
+ }
+
+ virtual Status PromoteL0(ColumnFamilyHandle* /*column_family*/,
+ int /*target_level*/) {
+ return Status::NotSupported("PromoteL0() is not implemented.");
+ }
+
+ // Trace DB operations. Use EndTrace() to stop tracing.
+ virtual Status StartTrace(const TraceOptions& /*options*/,
+ std::unique_ptr<TraceWriter>&& /*trace_writer*/) {
+ return Status::NotSupported("StartTrace() is not implemented.");
+ }
+
+ virtual Status EndTrace() {
+ return Status::NotSupported("EndTrace() is not implemented.");
+ }
+
+ // Trace block cache accesses. Use EndBlockCacheTrace() to stop tracing.
+ virtual Status StartBlockCacheTrace(
+ const TraceOptions& /*options*/,
+ std::unique_ptr<TraceWriter>&& /*trace_writer*/) {
+ return Status::NotSupported("StartBlockCacheTrace() is not implemented.");
+ }
+
+ virtual Status EndBlockCacheTrace() {
+ return Status::NotSupported("EndBlockCacheTrace() is not implemented.");
+ }
+#endif // ROCKSDB_LITE
+
+ // Needed for StackableDB
+ virtual DB* GetRootDB() { return this; }
+
+ // Given a window [start_time, end_time), setup a StatsHistoryIterator
+ // to access stats history. Note the start_time and end_time are epoch
+ // time measured in seconds, and end_time is an exclusive bound.
+ virtual Status GetStatsHistory(
+ uint64_t /*start_time*/, uint64_t /*end_time*/,
+ std::unique_ptr<StatsHistoryIterator>* /*stats_iterator*/) {
+ return Status::NotSupported("GetStatsHistory() is not implemented.");
+ }
+
+#ifndef ROCKSDB_LITE
+ // 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.
+ // TODO: we will support WAL tailing soon.
+ virtual Status TryCatchUpWithPrimary() {
+ return Status::NotSupported("Supported only by secondary instance");
+ }
+#endif // !ROCKSDB_LITE
+};
+
+// Destroy the contents of the specified database.
+// Be very careful using this method.
+Status DestroyDB(const std::string& name, const Options& options,
+ const std::vector<ColumnFamilyDescriptor>& column_families =
+ std::vector<ColumnFamilyDescriptor>());
+
+#ifndef ROCKSDB_LITE
+// If a DB cannot be opened, you may attempt to call this method to
+// resurrect as much of the contents of the database as possible.
+// Some data may be lost, so be careful when calling this function
+// on a database that contains important information.
+//
+// With this API, we will warn and skip data associated with column families not
+// specified in column_families.
+//
+// @param column_families Descriptors for known column families
+Status RepairDB(const std::string& dbname, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families);
+
+// @param unknown_cf_opts Options for column families encountered during the
+// repair that were not specified in column_families.
+Status RepairDB(const std::string& dbname, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ const ColumnFamilyOptions& unknown_cf_opts);
+
+// @param options These options will be used for the database and for ALL column
+// families encountered during the repair
+Status RepairDB(const std::string& dbname, const Options& options);
+
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/db_bench_tool.h b/storage/rocksdb/rocksdb/include/rocksdb/db_bench_tool.h
new file mode 100644
index 00000000..17f4e6bd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/db_bench_tool.h
@@ -0,0 +1,11 @@
+// Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+int db_bench_tool(int argc, char** argv);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/db_dump_tool.h b/storage/rocksdb/rocksdb/include/rocksdb/db_dump_tool.h
new file mode 100644
index 00000000..b7d4766a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/db_dump_tool.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <string>
+
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct DumpOptions {
+ // Database that will be dumped
+ std::string db_path;
+ // File location that will contain dump output
+ std::string dump_location;
+ // Don't include db information header in the dump
+ bool anonymous = false;
+};
+
+class DbDumpTool {
+ public:
+ bool Run(const DumpOptions& dump_options,
+ ROCKSDB_NAMESPACE::Options options = ROCKSDB_NAMESPACE::Options());
+};
+
+struct UndumpOptions {
+ // Database that we will load the dumped file into
+ std::string db_path;
+ // File location of the dumped file that will be loaded
+ std::string dump_location;
+ // Compact the db after loading the dumped file
+ bool compact_db = false;
+};
+
+class DbUndumpTool {
+ public:
+ bool Run(const UndumpOptions& undump_options,
+ ROCKSDB_NAMESPACE::Options options = ROCKSDB_NAMESPACE::Options());
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/db_stress_tool.h b/storage/rocksdb/rocksdb/include/rocksdb/db_stress_tool.h
new file mode 100644
index 00000000..7d3d42c9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/db_stress_tool.h
@@ -0,0 +1,11 @@
+// Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+int db_stress_tool(int argc, char** argv);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/env.h b/storage/rocksdb/rocksdb/include/rocksdb/env.h
new file mode 100644
index 00000000..056d8a1c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/env.h
@@ -0,0 +1,1589 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// An Env is an interface used by the rocksdb implementation to access
+// operating system functionality like the filesystem etc. Callers
+// may wish to provide a custom Env object when opening a database to
+// get fine gain control; e.g., to rate limit file system operations.
+//
+// All Env implementations are safe for concurrent access from
+// multiple threads without any external synchronization.
+
+#pragma once
+
+#include <stdint.h>
+#include <cstdarg>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+#include "rocksdb/status.h"
+#include "rocksdb/thread_status.h"
+
+#ifdef _WIN32
+// Windows API macro interference
+#undef DeleteFile
+#undef GetCurrentTime
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param) \
+ __attribute__((__format__(__printf__, format_param, dots_param)))
+#else
+#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param)
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class DynamicLibrary;
+class FileLock;
+class Logger;
+class RandomAccessFile;
+class SequentialFile;
+class Slice;
+class WritableFile;
+class RandomRWFile;
+class MemoryMappedFileBuffer;
+class Directory;
+struct DBOptions;
+struct ImmutableDBOptions;
+struct MutableDBOptions;
+class RateLimiter;
+class ThreadStatusUpdater;
+struct ThreadStatus;
+
+const size_t kDefaultPageSize = 4 * 1024;
+
+// Options while opening a file to read/write
+struct EnvOptions {
+ // Construct with default Options
+ EnvOptions();
+
+ // Construct from Options
+ explicit EnvOptions(const DBOptions& options);
+
+ // If true, then use mmap to read data
+ bool use_mmap_reads = false;
+
+ // If true, then use mmap to write data
+ bool use_mmap_writes = true;
+
+ // If true, then use O_DIRECT for reading data
+ bool use_direct_reads = false;
+
+ // If true, then use O_DIRECT for writing data
+ bool use_direct_writes = false;
+
+ // If false, fallocate() calls are bypassed
+ bool allow_fallocate = true;
+
+ // If true, set the FD_CLOEXEC on open fd.
+ bool set_fd_cloexec = true;
+
+ // Allows OS to incrementally sync files to disk while they are being
+ // written, in the background. Issue one request for every bytes_per_sync
+ // written. 0 turns it off.
+ // Default: 0
+ uint64_t bytes_per_sync = 0;
+
+ // When true, guarantees the file has at most `bytes_per_sync` bytes submitted
+ // for writeback at any given time.
+ //
+ // - 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
+ bool strict_bytes_per_sync = false;
+
+ // If true, we will preallocate the file with 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
+ bool fallocate_with_keep_size = true;
+
+ // See DBOptions doc
+ size_t compaction_readahead_size = 0;
+
+ // See DBOptions doc
+ size_t random_access_max_buffer_size = 0;
+
+ // See DBOptions doc
+ size_t writable_file_max_buffer_size = 1024 * 1024;
+
+ // If not nullptr, write rate limiting is enabled for flush and compaction
+ RateLimiter* rate_limiter = nullptr;
+};
+
+class Env {
+ public:
+ struct FileAttributes {
+ // File name
+ std::string name;
+
+ // Size of file in bytes
+ uint64_t size_bytes;
+ };
+
+ Env() : thread_status_updater_(nullptr) {}
+ // No copying allowed
+ Env(const Env&) = delete;
+ void operator=(const Env&) = delete;
+
+ virtual ~Env();
+
+ static const char* Type() { return "Environment"; }
+
+ // Loads the environment specified by the input value into the result
+ static Status LoadEnv(const std::string& value, Env** result);
+
+ // Loads the environment specified by the input value into the result
+ static Status LoadEnv(const std::string& value, Env** result,
+ std::shared_ptr<Env>* guard);
+
+ // Return a default environment suitable for the current operating
+ // system. Sophisticated users may wish to provide their own Env
+ // implementation instead of relying on this default environment.
+ //
+ // The result of Default() belongs to rocksdb and must never be deleted.
+ static Env* Default();
+
+ // Create a brand new sequentially-readable file with the specified name.
+ // On success, stores a pointer to the new file in *result and returns OK.
+ // On failure stores nullptr in *result and returns non-OK. If the file does
+ // not exist, returns a non-OK status.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) = 0;
+
+ // Create a brand new random access read-only file with the
+ // specified name. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK. If the file does not exist, returns a non-OK
+ // status.
+ //
+ // The returned file may be concurrently accessed by multiple threads.
+ virtual Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) = 0;
+ // These values match Linux definition
+ // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fcntl.h#n56
+ enum WriteLifeTimeHint {
+ WLTH_NOT_SET = 0, // No hint information set
+ WLTH_NONE, // No hints about write life time
+ WLTH_SHORT, // Data written has a short life time
+ WLTH_MEDIUM, // Data written has a medium life time
+ WLTH_LONG, // Data written has a long life time
+ WLTH_EXTREME, // Data written has an extremely long life time
+ };
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) = 0;
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual Status ReopenWritableFile(const std::string& /*fname*/,
+ std::unique_ptr<WritableFile>* /*result*/,
+ const EnvOptions& /*options*/) {
+ return Status::NotSupported();
+ }
+
+ // Reuse an existing file by renaming it and opening it as writable.
+ virtual Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options);
+
+ // Open `fname` for random read and write, if file doesn't exist the file
+ // will be created. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual Status NewRandomRWFile(const std::string& /*fname*/,
+ std::unique_ptr<RandomRWFile>* /*result*/,
+ const EnvOptions& /*options*/) {
+ return Status::NotSupported("RandomRWFile is not implemented in this Env");
+ }
+
+ // Opens `fname` as a memory-mapped file for read and write (in-place updates
+ // only, i.e., no appends). On success, stores a raw buffer covering the whole
+ // file in `*result`. The file must exist prior to this call.
+ virtual Status NewMemoryMappedFileBuffer(
+ const std::string& /*fname*/,
+ std::unique_ptr<MemoryMappedFileBuffer>* /*result*/) {
+ return Status::NotSupported(
+ "MemoryMappedFileBuffer is not implemented in this Env");
+ }
+
+ // Create an object that represents a directory. Will fail if directory
+ // doesn't exist. If the directory exists, it will open the directory
+ // and create a new Directory object.
+ //
+ // On success, stores a pointer to the new Directory in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ virtual Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) = 0;
+
+ // Returns OK if the named file exists.
+ // NotFound if the named file does not exist,
+ // the calling process does not have permission to determine
+ // whether this file exists, or if the path is invalid.
+ // IOError if an IO Error was encountered
+ virtual Status FileExists(const std::string& fname) = 0;
+
+ // Store in *result the names of the children of the specified directory.
+ // The names are relative to "dir".
+ // Original contents of *results are dropped.
+ // Returns OK if "dir" exists and "*result" contains its children.
+ // NotFound if "dir" does not exist, the calling process does not have
+ // permission to access "dir", or if "dir" is invalid.
+ // IOError if an IO Error was encountered
+ virtual Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) = 0;
+
+ // Store in *result the attributes of the children of the specified directory.
+ // In case the implementation lists the directory prior to iterating the files
+ // and files are concurrently deleted, the deleted files will be omitted from
+ // result.
+ // The name attributes are relative to "dir".
+ // Original contents of *results are dropped.
+ // Returns OK if "dir" exists and "*result" contains its children.
+ // NotFound if "dir" does not exist, the calling process does not have
+ // permission to access "dir", or if "dir" is invalid.
+ // IOError if an IO Error was encountered
+ virtual Status GetChildrenFileAttributes(const std::string& dir,
+ std::vector<FileAttributes>* result);
+
+ // Delete the named file.
+ virtual Status DeleteFile(const std::string& fname) = 0;
+
+ // Truncate the named file to the specified size.
+ virtual Status Truncate(const std::string& /*fname*/, size_t /*size*/) {
+ return Status::NotSupported("Truncate is not supported for this Env");
+ }
+
+ // Create the specified directory. Returns error if directory exists.
+ virtual Status CreateDir(const std::string& dirname) = 0;
+
+ // Creates directory if missing. Return Ok if it exists, or successful in
+ // Creating.
+ virtual Status CreateDirIfMissing(const std::string& dirname) = 0;
+
+ // Delete the specified directory.
+ virtual Status DeleteDir(const std::string& dirname) = 0;
+
+ // Store the size of fname in *file_size.
+ virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) = 0;
+
+ // Store the last modification time of fname in *file_mtime.
+ virtual Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) = 0;
+ // Rename file src to target.
+ virtual Status RenameFile(const std::string& src,
+ const std::string& target) = 0;
+
+ // Hard Link file src to target.
+ virtual Status LinkFile(const std::string& /*src*/,
+ const std::string& /*target*/) {
+ return Status::NotSupported("LinkFile is not supported for this Env");
+ }
+
+ virtual Status NumFileLinks(const std::string& /*fname*/,
+ uint64_t* /*count*/) {
+ return Status::NotSupported(
+ "Getting number of file links is not supported for this Env");
+ }
+
+ virtual Status AreFilesSame(const std::string& /*first*/,
+ const std::string& /*second*/, bool* /*res*/) {
+ return Status::NotSupported("AreFilesSame is not supported for this Env");
+ }
+
+ // Lock the specified file. Used to prevent concurrent access to
+ // the same db by multiple processes. On failure, stores nullptr in
+ // *lock and returns non-OK.
+ //
+ // On success, stores a pointer to the object that represents the
+ // acquired lock in *lock and returns OK. The caller should call
+ // UnlockFile(*lock) to release the lock. If the process exits,
+ // the lock will be automatically released.
+ //
+ // If somebody else already holds the lock, finishes immediately
+ // with a failure. I.e., this call does not wait for existing locks
+ // to go away.
+ //
+ // May create the named file if it does not already exist.
+ virtual Status LockFile(const std::string& fname, FileLock** lock) = 0;
+
+ // Release the lock acquired by a previous successful call to LockFile.
+ // REQUIRES: lock was returned by a successful LockFile() call
+ // REQUIRES: lock has not already been unlocked.
+ virtual Status UnlockFile(FileLock* lock) = 0;
+
+ // Opens `lib_name` as a dynamic library.
+ // If the 'search_path' is specified, breaks the path into its components
+ // based on the appropriate platform separator (";" or ";") and looks for the
+ // library in those directories. If 'search path is not specified, uses the
+ // default library path search mechanism (such as LD_LIBRARY_PATH). On
+ // success, stores a dynamic library in `*result`.
+ virtual Status LoadLibrary(const std::string& /*lib_name*/,
+ const std::string& /*search_path */,
+ std::shared_ptr<DynamicLibrary>* /*result*/) {
+ return Status::NotSupported("LoadLibrary is not implemented in this Env");
+ }
+
+ // Priority for scheduling job in thread pool
+ enum Priority { BOTTOM, LOW, HIGH, USER, TOTAL };
+
+ static std::string PriorityToString(Priority priority);
+
+ // Priority for requesting bytes in rate limiter scheduler
+ enum IOPriority { IO_LOW = 0, IO_HIGH = 1, IO_TOTAL = 2 };
+
+ // Arrange to run "(*function)(arg)" once in a background thread, in
+ // the thread pool specified by pri. By default, jobs go to the 'LOW'
+ // priority thread pool.
+
+ // "function" may run in an unspecified thread. Multiple functions
+ // added to the same Env may run concurrently in different threads.
+ // I.e., the caller may not assume that background work items are
+ // serialized.
+ // When the UnSchedule function is called, the unschedFunction
+ // registered at the time of Schedule is invoked with arg as a parameter.
+ virtual void Schedule(void (*function)(void* arg), void* arg,
+ Priority pri = LOW, void* tag = nullptr,
+ void (*unschedFunction)(void* arg) = nullptr) = 0;
+
+ // Arrange to remove jobs for given arg from the queue_ if they are not
+ // already scheduled. Caller is expected to have exclusive lock on arg.
+ virtual int UnSchedule(void* /*arg*/, Priority /*pri*/) { return 0; }
+
+ // Start a new thread, invoking "function(arg)" within the new thread.
+ // When "function(arg)" returns, the thread will be destroyed.
+ virtual void StartThread(void (*function)(void* arg), void* arg) = 0;
+
+ // Wait for all threads started by StartThread to terminate.
+ virtual void WaitForJoin() {}
+
+ // Get thread pool queue length for specific thread pool.
+ virtual unsigned int GetThreadPoolQueueLen(Priority /*pri*/ = LOW) const {
+ return 0;
+ }
+
+ // *path is set to a temporary directory that can be used for testing. It may
+ // or many not have just been created. The directory may or may not differ
+ // between runs of the same process, but subsequent calls will return the
+ // same directory.
+ virtual Status GetTestDirectory(std::string* path) = 0;
+
+ // Create and returns a default logger (an instance of EnvLogger) for storing
+ // informational messages. Derived classes can overide to provide custom
+ // logger.
+ virtual Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result);
+
+ // Returns the number of micro-seconds since some fixed point in time.
+ // It is often used as system time such as in GenericRateLimiter
+ // and other places so a port needs to return system time in order to work.
+ virtual uint64_t NowMicros() = 0;
+
+ // Returns the number of nano-seconds since some fixed point in time. Only
+ // useful for computing deltas of time in one run.
+ // Default implementation simply relies on NowMicros.
+ // In platform-specific implementations, NowNanos() should return time points
+ // that are MONOTONIC.
+ virtual uint64_t NowNanos() { return NowMicros() * 1000; }
+
+ // 0 indicates not supported.
+ virtual uint64_t NowCPUNanos() { return 0; }
+
+ // Sleep/delay the thread for the prescribed number of micro-seconds.
+ virtual void SleepForMicroseconds(int micros) = 0;
+
+ // Get the current host name.
+ virtual Status GetHostName(char* name, uint64_t len) = 0;
+
+ // Get the number of seconds since the Epoch, 1970-01-01 00:00:00 (UTC).
+ // Only overwrites *unix_time on success.
+ virtual Status GetCurrentTime(int64_t* unix_time) = 0;
+
+ // Get full directory name for this db.
+ virtual Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) = 0;
+
+ // The number of background worker threads of a specific thread pool
+ // for this environment. 'LOW' is the default pool.
+ // default number: 1
+ virtual void SetBackgroundThreads(int number, Priority pri = LOW) = 0;
+ virtual int GetBackgroundThreads(Priority pri = LOW) = 0;
+
+ virtual Status SetAllowNonOwnerAccess(bool /*allow_non_owner_access*/) {
+ return Status::NotSupported("Not supported.");
+ }
+
+ // 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.
+ virtual void IncBackgroundThreadsIfNeeded(int number, Priority pri) = 0;
+
+ // Lower IO priority for threads from the specified pool.
+ virtual void LowerThreadPoolIOPriority(Priority /*pool*/ = LOW) {}
+
+ // Lower CPU priority for threads from the specified pool.
+ virtual void LowerThreadPoolCPUPriority(Priority /*pool*/ = LOW) {}
+
+ // Converts seconds-since-Jan-01-1970 to a printable string
+ virtual std::string TimeToString(uint64_t time) = 0;
+
+ // Generates a unique id that can be used to identify a db
+ virtual std::string GenerateUniqueId();
+
+ // OptimizeForLogWrite will create a new EnvOptions object that is a copy of
+ // the EnvOptions in the parameters, but is optimized for reading log files.
+ virtual EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const;
+
+ // OptimizeForManifestRead will create a new EnvOptions object that is a copy
+ // of the EnvOptions in the parameters, but is optimized for reading manifest
+ // files.
+ virtual EnvOptions OptimizeForManifestRead(
+ const EnvOptions& env_options) const;
+
+ // OptimizeForLogWrite will create a new EnvOptions object that is a copy of
+ // the EnvOptions in the parameters, but is optimized for writing log files.
+ // Default implementation returns the copy of the same object.
+ virtual EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const;
+ // OptimizeForManifestWrite will create a new EnvOptions object that is a copy
+ // of the EnvOptions in the parameters, but is optimized for writing manifest
+ // files. Default implementation returns the copy of the same object.
+ virtual EnvOptions OptimizeForManifestWrite(
+ const EnvOptions& env_options) const;
+
+ // OptimizeForCompactionTableWrite will create a new EnvOptions object that is
+ // a copy of the EnvOptions in the parameters, but is optimized for writing
+ // table files.
+ virtual EnvOptions OptimizeForCompactionTableWrite(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& immutable_ops) const;
+
+ // OptimizeForCompactionTableWrite will create a new EnvOptions object that
+ // is a copy of the EnvOptions in the parameters, but is optimized for reading
+ // table files.
+ virtual EnvOptions OptimizeForCompactionTableRead(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& db_options) const;
+
+ // Returns the status of all threads that belong to the current Env.
+ virtual Status GetThreadList(std::vector<ThreadStatus>* /*thread_list*/) {
+ return Status::NotSupported("Not supported.");
+ }
+
+ // Returns the pointer to ThreadStatusUpdater. This function will be
+ // used in RocksDB internally to update thread status and supports
+ // GetThreadList().
+ virtual ThreadStatusUpdater* GetThreadStatusUpdater() const {
+ return thread_status_updater_;
+ }
+
+ // Returns the ID of the current thread.
+ virtual uint64_t GetThreadID() const;
+
+// This seems to clash with a macro on Windows, so #undef it here
+#undef GetFreeSpace
+
+ // Get the amount of free disk space
+ virtual Status GetFreeSpace(const std::string& /*path*/,
+ uint64_t* /*diskfree*/) {
+ return Status::NotSupported();
+ }
+
+ virtual void SanitizeEnvOptions(EnvOptions* /*env_opts*/) const {}
+
+ // If you're adding methods here, remember to add them to EnvWrapper too.
+
+ protected:
+ // The pointer to an internal structure that will update the
+ // status of each thread.
+ ThreadStatusUpdater* thread_status_updater_;
+};
+
+// The factory function to construct a ThreadStatusUpdater. Any Env
+// that supports GetThreadList() feature should call this function in its
+// constructor to initialize thread_status_updater_.
+ThreadStatusUpdater* CreateThreadStatusUpdater();
+
+// A file abstraction for reading sequentially through a file
+class SequentialFile {
+ public:
+ SequentialFile() {}
+ virtual ~SequentialFile();
+
+ // Read up to "n" bytes from the file. "scratch[0..n-1]" may be
+ // written by this routine. Sets "*result" to the data that was
+ // read (including if fewer than "n" bytes were successfully read).
+ // May set "*result" to point at data in "scratch[0..n-1]", so
+ // "scratch[0..n-1]" must be live when "*result" is used.
+ // If an error was encountered, returns a non-OK status.
+ //
+ // REQUIRES: External synchronization
+ virtual Status Read(size_t n, Slice* result, char* scratch) = 0;
+
+ // Skip "n" bytes from the file. This is guaranteed to be no
+ // slower that reading the same data, but may be faster.
+ //
+ // If end of file is reached, skipping will stop at the end of the
+ // file, and Skip will return OK.
+ //
+ // REQUIRES: External synchronization
+ virtual Status Skip(uint64_t n) = 0;
+
+ // Indicates the upper layers if the current SequentialFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return Status::NotSupported("InvalidateCache not supported.");
+ }
+
+ // Positioned Read for direct I/O
+ // If Direct I/O enabled, offset, n, and scratch should be properly aligned
+ virtual Status PositionedRead(uint64_t /*offset*/, size_t /*n*/,
+ Slice* /*result*/, char* /*scratch*/) {
+ return Status::NotSupported();
+ }
+
+ // If you're adding methods here, remember to add them to
+ // SequentialFileWrapper too.
+};
+
+// A read IO request structure for use in MultiRead
+struct ReadRequest {
+ // File offset in bytes
+ uint64_t offset;
+
+ // Length to read in bytes
+ size_t len;
+
+ // A buffer that MultiRead() can optionally place data in. It can
+ // ignore this and allocate its own buffer
+ char* scratch;
+
+ // Output parameter set by MultiRead() to point to the data buffer, and
+ // the number of valid bytes
+ Slice result;
+
+ // Status of read
+ Status status;
+};
+
+// A file abstraction for randomly reading the contents of a file.
+class RandomAccessFile {
+ public:
+ RandomAccessFile() {}
+ virtual ~RandomAccessFile();
+
+ // Read up to "n" bytes from the file starting at "offset".
+ // "scratch[0..n-1]" may be written by this routine. Sets "*result"
+ // to the data that was read (including if fewer than "n" bytes were
+ // successfully read). May set "*result" to point at data in
+ // "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
+ // "*result" is used. If an error was encountered, returns a non-OK
+ // status.
+ //
+ // Safe for concurrent use by multiple threads.
+ // If Direct I/O enabled, offset, n, and scratch should be aligned properly.
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const = 0;
+
+ // Readahead the file starting from offset by n bytes for caching.
+ virtual Status Prefetch(uint64_t /*offset*/, size_t /*n*/) {
+ return Status::OK();
+ }
+
+ // Read a bunch of blocks as described by reqs. The blocks can
+ // optionally be read in parallel. This is a synchronous call, i.e it
+ // should return after all reads have completed. The reads will be
+ // non-overlapping. If the function return Status is not ok, status of
+ // individual requests will be ignored and return status will be assumed
+ // for all read requests. The function return status is only meant for any
+ // any errors that occur before even processing specific read requests
+ virtual Status MultiRead(ReadRequest* reqs, size_t num_reqs) {
+ assert(reqs != nullptr);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ ReadRequest& req = reqs[i];
+ req.status = Read(req.offset, req.len, &req.result, req.scratch);
+ }
+ return Status::OK();
+ }
+
+ // Tries to get an unique ID for this file that will be the same each time
+ // the file is opened (and will stay the same while the file is open).
+ // Furthermore, it tries to make this ID at most "max_size" bytes. If such an
+ // ID can be created this function returns the length of the ID and places it
+ // in "id"; otherwise, this function returns 0, in which case "id"
+ // may not have been modified.
+ //
+ // This function guarantees, for IDs from a given environment, two unique ids
+ // cannot be made equal to each other by adding arbitrary bytes to one of
+ // them. That is, no unique ID is the prefix of another.
+ //
+ // This function guarantees that the returned ID will not be interpretable as
+ // a single varint.
+ //
+ // Note: these IDs are only valid for the duration of the process.
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0; // Default implementation to prevent issues with backwards
+ // compatibility.
+ }
+
+ enum AccessPattern { NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED };
+
+ virtual void Hint(AccessPattern /*pattern*/) {}
+
+ // Indicates the upper layers if the current RandomAccessFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return Status::NotSupported("InvalidateCache not supported.");
+ }
+
+ // If you're adding methods here, remember to add them to
+ // RandomAccessFileWrapper too.
+};
+
+// A file abstraction for sequential writing. The implementation
+// must provide buffering since callers may append small fragments
+// at a time to the file.
+class WritableFile {
+ public:
+ WritableFile()
+ : last_preallocated_block_(0),
+ preallocation_block_size_(0),
+ io_priority_(Env::IO_TOTAL),
+ write_hint_(Env::WLTH_NOT_SET),
+ strict_bytes_per_sync_(false) {}
+
+ explicit WritableFile(const EnvOptions& options)
+ : last_preallocated_block_(0),
+ preallocation_block_size_(0),
+ io_priority_(Env::IO_TOTAL),
+ write_hint_(Env::WLTH_NOT_SET),
+ strict_bytes_per_sync_(options.strict_bytes_per_sync) {}
+ // No copying allowed
+ WritableFile(const WritableFile&) = delete;
+ void operator=(const WritableFile&) = delete;
+
+ virtual ~WritableFile();
+
+ // Append data to the end of the file
+ // Note: A WriteabelFile object must support either Append or
+ // PositionedAppend, so the users cannot mix the two.
+ virtual Status Append(const Slice& data) = 0;
+
+ // PositionedAppend data to the specified offset. The new EOF after append
+ // must be larger than the previous EOF. This is to be used when writes are
+ // not backed by OS buffers and hence has to always start from the start of
+ // the sector. The implementation thus needs to also rewrite the last
+ // partial sector.
+ // Note: PositionAppend does not guarantee moving the file offset after the
+ // write. A WritableFile object must support either Append or
+ // PositionedAppend, so the users cannot mix the two.
+ //
+ // PositionedAppend() can only happen on the page/sector boundaries. For that
+ // reason, if the last write was an incomplete sector we still need to rewind
+ // back to the nearest sector/page and rewrite the portion of it with whatever
+ // we need to add. We need to keep where we stop writing.
+ //
+ // PositionedAppend() can only write whole sectors. For that reason we have to
+ // pad with zeros for the last write and trim the file when closing according
+ // to the position we keep in the previous step.
+ //
+ // PositionedAppend() requires aligned buffer to be passed in. The alignment
+ // required is queried via GetRequiredBufferAlignment()
+ virtual Status PositionedAppend(const Slice& /* data */,
+ uint64_t /* offset */) {
+ return Status::NotSupported();
+ }
+
+ // Truncate is necessary to trim the file to the correct size
+ // before closing. It is not always possible to keep track of the file
+ // size due to whole pages writes. The behavior is undefined if called
+ // with other writes to follow.
+ virtual Status Truncate(uint64_t /*size*/) { return Status::OK(); }
+ virtual Status Close() = 0;
+ virtual Status Flush() = 0;
+ virtual Status Sync() = 0; // sync data
+
+ /*
+ * Sync data and/or metadata as well.
+ * By default, sync only data.
+ * Override this method for environments where we need to sync
+ * metadata as well.
+ */
+ virtual Status Fsync() { return Sync(); }
+
+ // true if Sync() and Fsync() are safe to call concurrently with Append()
+ // and Flush().
+ virtual bool IsSyncThreadSafe() const { return false; }
+
+ // Indicates the upper layers if the current WritableFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+ /*
+ * Change the priority in rate limiter if rate limiting is enabled.
+ * If rate limiting is not enabled, this call has no effect.
+ */
+ virtual void SetIOPriority(Env::IOPriority pri) { io_priority_ = pri; }
+
+ virtual Env::IOPriority GetIOPriority() { return io_priority_; }
+
+ virtual void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) {
+ write_hint_ = hint;
+ }
+
+ virtual Env::WriteLifeTimeHint GetWriteLifeTimeHint() { return write_hint_; }
+ /*
+ * Get the size of valid data in the file.
+ */
+ virtual uint64_t GetFileSize() { return 0; }
+
+ /*
+ * Get and set the default pre-allocation block size for writes to
+ * this file. If non-zero, then Allocate will be used to extend the
+ * underlying storage of a file (generally via fallocate) if the Env
+ * instance supports it.
+ */
+ virtual void SetPreallocationBlockSize(size_t size) {
+ preallocation_block_size_ = size;
+ }
+
+ virtual void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) {
+ *last_allocated_block = last_preallocated_block_;
+ *block_size = preallocation_block_size_;
+ }
+
+ // For documentation, refer to RandomAccessFile::GetUniqueId()
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0; // Default implementation to prevent issues with backwards
+ }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ // This call has no effect on dirty pages in the cache.
+ virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return Status::NotSupported("InvalidateCache not supported.");
+ }
+
+ // Sync a file range with disk.
+ // offset is the starting byte of the file range to be synchronized.
+ // nbytes specifies the length of the range to be synchronized.
+ // This asks the OS to initiate flushing the cached data to disk,
+ // without waiting for completion.
+ // Default implementation does nothing.
+ virtual Status RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/) {
+ if (strict_bytes_per_sync_) {
+ return Sync();
+ }
+ return Status::OK();
+ }
+
+ // PrepareWrite performs any necessary preparation for a write
+ // before the write actually occurs. This allows for pre-allocation
+ // of space on devices where it can result in less file
+ // fragmentation and/or less waste from over-zealous filesystem
+ // pre-allocation.
+ virtual void PrepareWrite(size_t offset, size_t len) {
+ if (preallocation_block_size_ == 0) {
+ return;
+ }
+ // If this write would cross one or more preallocation blocks,
+ // determine what the last preallocation block necessary to
+ // cover this write would be and Allocate to that point.
+ const auto block_size = preallocation_block_size_;
+ size_t new_last_preallocated_block =
+ (offset + len + block_size - 1) / block_size;
+ if (new_last_preallocated_block > last_preallocated_block_) {
+ size_t num_spanned_blocks =
+ new_last_preallocated_block - last_preallocated_block_;
+ Allocate(block_size * last_preallocated_block_,
+ block_size * num_spanned_blocks);
+ last_preallocated_block_ = new_last_preallocated_block;
+ }
+ }
+
+ // Pre-allocates space for a file.
+ virtual Status Allocate(uint64_t /*offset*/, uint64_t /*len*/) {
+ return Status::OK();
+ }
+
+ // If you're adding methods here, remember to add them to
+ // WritableFileWrapper too.
+
+ protected:
+ size_t preallocation_block_size() { return preallocation_block_size_; }
+
+ private:
+ size_t last_preallocated_block_;
+ size_t preallocation_block_size_;
+
+ protected:
+ Env::IOPriority io_priority_;
+ Env::WriteLifeTimeHint write_hint_;
+ const bool strict_bytes_per_sync_;
+};
+
+// A file abstraction for random reading and writing.
+class RandomRWFile {
+ public:
+ RandomRWFile() {}
+ // No copying allowed
+ RandomRWFile(const RandomRWFile&) = delete;
+ RandomRWFile& operator=(const RandomRWFile&) = delete;
+
+ virtual ~RandomRWFile() {}
+
+ // Indicates if the class makes use of direct I/O
+ // If false you must pass aligned buffer to Write()
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
+ // Pass aligned buffer when use_direct_io() returns true.
+ virtual Status Write(uint64_t offset, const Slice& data) = 0;
+
+ // Read up to `n` bytes starting from offset `offset` and store them in
+ // result, provided `scratch` size should be at least `n`.
+ // Returns Status::OK() on success.
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const = 0;
+
+ virtual Status Flush() = 0;
+
+ virtual Status Sync() = 0;
+
+ virtual Status Fsync() { return Sync(); }
+
+ virtual Status Close() = 0;
+
+ // If you're adding methods here, remember to add them to
+ // RandomRWFileWrapper too.
+};
+
+// MemoryMappedFileBuffer object represents a memory-mapped file's raw buffer.
+// Subclasses should release the mapping upon destruction.
+class MemoryMappedFileBuffer {
+ public:
+ MemoryMappedFileBuffer(void* _base, size_t _length)
+ : base_(_base), length_(_length) {}
+
+ virtual ~MemoryMappedFileBuffer() = 0;
+
+ // We do not want to unmap this twice. We can make this class
+ // movable if desired, however, since
+ MemoryMappedFileBuffer(const MemoryMappedFileBuffer&) = delete;
+ MemoryMappedFileBuffer& operator=(const MemoryMappedFileBuffer&) = delete;
+
+ void* GetBase() const { return base_; }
+ size_t GetLen() const { return length_; }
+
+ protected:
+ void* base_;
+ const size_t length_;
+};
+
+// Directory object represents collection of files and implements
+// filesystem operations that can be executed on directories.
+class Directory {
+ public:
+ virtual ~Directory() {}
+ // Fsync directory. Can be called concurrently from multiple threads.
+ virtual Status Fsync() = 0;
+
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0;
+ }
+
+ // If you're adding methods here, remember to add them to
+ // DirectoryWrapper too.
+};
+
+enum InfoLogLevel : unsigned char {
+ DEBUG_LEVEL = 0,
+ INFO_LEVEL,
+ WARN_LEVEL,
+ ERROR_LEVEL,
+ FATAL_LEVEL,
+ HEADER_LEVEL,
+ NUM_INFO_LOG_LEVELS,
+};
+
+// An interface for writing log messages.
+class Logger {
+ public:
+ size_t kDoNotSupportGetLogFileSize = (std::numeric_limits<size_t>::max)();
+
+ explicit Logger(const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL)
+ : closed_(false), log_level_(log_level) {}
+ // No copying allowed
+ Logger(const Logger&) = delete;
+ void operator=(const Logger&) = delete;
+
+ virtual ~Logger();
+
+ // Close the log file. Must be called before destructor. If the return
+ // status is NotSupported(), it means the implementation does cleanup in
+ // the destructor
+ virtual Status Close();
+
+ // Write a header to the log file with the specified format
+ // It is recommended that you log all header information at the start of the
+ // application. But it is not enforced.
+ virtual void LogHeader(const char* format, va_list ap) {
+ // Default implementation does a simple INFO level log write.
+ // Please override as per the logger class requirement.
+ Logv(format, ap);
+ }
+
+ // Write an entry to the log file with the specified format.
+ virtual void Logv(const char* format, va_list ap) = 0;
+
+ // 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);
+
+ virtual size_t GetLogFileSize() const { return kDoNotSupportGetLogFileSize; }
+ // Flush to the OS buffers
+ virtual void Flush() {}
+ virtual InfoLogLevel GetInfoLogLevel() const { return log_level_; }
+ virtual void SetInfoLogLevel(const InfoLogLevel log_level) {
+ log_level_ = log_level;
+ }
+
+ // If you're adding methods here, remember to add them to LoggerWrapper too.
+
+ protected:
+ virtual Status CloseImpl();
+ bool closed_;
+
+ private:
+ InfoLogLevel log_level_;
+};
+
+// Identifies a locked file.
+class FileLock {
+ public:
+ FileLock() {}
+ virtual ~FileLock();
+
+ private:
+ // No copying allowed
+ FileLock(const FileLock&) = delete;
+ void operator=(const FileLock&) = delete;
+};
+
+class DynamicLibrary {
+ public:
+ virtual ~DynamicLibrary() {}
+
+ // Returns the name of the dynamic library.
+ virtual const char* Name() const = 0;
+
+ // Loads the symbol for sym_name from the library and updates the input
+ // function. Returns the loaded symbol.
+ template <typename T>
+ Status LoadFunction(const std::string& sym_name, std::function<T>* function) {
+ assert(nullptr != function);
+ void* ptr = nullptr;
+ Status s = LoadSymbol(sym_name, &ptr);
+ *function = reinterpret_cast<T*>(ptr);
+ return s;
+ }
+ // Loads and returns the symbol for sym_name from the library.
+ virtual Status LoadSymbol(const std::string& sym_name, void** func) = 0;
+};
+
+extern void LogFlush(const std::shared_ptr<Logger>& info_log);
+
+extern void Log(const InfoLogLevel log_level,
+ const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(3, 4);
+
+// a set of log functions with different log levels.
+extern void Header(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Debug(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Info(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Warn(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Error(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Fatal(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+
+// Log the specified data to *info_log if info_log is non-nullptr.
+// The default info log level is InfoLogLevel::INFO_LEVEL.
+extern void Log(const std::shared_ptr<Logger>& info_log, const char* format,
+ ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+
+extern void LogFlush(Logger* info_log);
+
+extern void Log(const InfoLogLevel log_level, Logger* info_log,
+ const char* format, ...) ROCKSDB_PRINTF_FORMAT_ATTR(3, 4);
+
+// The default info log level is InfoLogLevel::INFO_LEVEL.
+extern void Log(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+
+// a set of log functions with different log levels.
+extern void Header(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Debug(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Info(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Warn(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Error(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+extern void Fatal(Logger* info_log, const char* format, ...)
+ ROCKSDB_PRINTF_FORMAT_ATTR(2, 3);
+
+// A utility routine: write "data" to the named file.
+extern Status WriteStringToFile(Env* env, const Slice& data,
+ const std::string& fname,
+ bool should_sync = false);
+
+// A utility routine: read contents of named file into *data
+extern Status ReadFileToString(Env* env, const std::string& fname,
+ std::string* data);
+
+// Below are helpers for wrapping most of the classes in this file.
+// They forward all calls to another instance of the class.
+// Useful when wrapping the default implementations.
+// Typical usage is to inherit your wrapper from *Wrapper, e.g.:
+//
+// class MySequentialFileWrapper : public
+// ROCKSDB_NAMESPACE::SequentialFileWrapper {
+// public:
+// MySequentialFileWrapper(ROCKSDB_NAMESPACE::SequentialFile* target):
+// ROCKSDB_NAMESPACE::SequentialFileWrapper(target) {}
+// Status Read(size_t n, Slice* result, char* scratch) override {
+// cout << "Doing a read of size " << n << "!" << endl;
+// return ROCKSDB_NAMESPACE::SequentialFileWrapper::Read(n, result,
+// scratch);
+// }
+// // All other methods are forwarded to target_ automatically.
+// };
+//
+// This is often more convenient than inheriting the class directly because
+// (a) Don't have to override and forward all methods - the Wrapper will
+// forward everything you're not explicitly overriding.
+// (b) Don't need to update the wrapper when more methods are added to the
+// rocksdb class. Unless you actually want to override the behavior.
+// (And unless rocksdb people forgot to update the *Wrapper class.)
+
+// An implementation of Env that forwards all calls to another Env.
+// May be useful to clients who wish to override just part of the
+// functionality of another Env.
+class EnvWrapper : public Env {
+ public:
+ // Initialize an EnvWrapper that delegates all calls to *t
+ explicit EnvWrapper(Env* t) : target_(t) {}
+ ~EnvWrapper() override;
+
+ // Return the target to which this Env forwards all calls
+ Env* target() const { return target_; }
+
+ // The following text is boilerplate that forwards all methods to target()
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& options) override {
+ return target_->NewSequentialFile(f, r, options);
+ }
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& options) override {
+ return target_->NewRandomAccessFile(f, r, options);
+ }
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override {
+ return target_->NewWritableFile(f, r, options);
+ }
+ Status ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ return target_->ReopenWritableFile(fname, result, options);
+ }
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override {
+ return target_->ReuseWritableFile(fname, old_fname, r, options);
+ }
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override {
+ return target_->NewRandomRWFile(fname, result, options);
+ }
+ Status NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override {
+ return target_->NewMemoryMappedFileBuffer(fname, result);
+ }
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override {
+ return target_->NewDirectory(name, result);
+ }
+ Status FileExists(const std::string& f) override {
+ return target_->FileExists(f);
+ }
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* r) override {
+ return target_->GetChildren(dir, r);
+ }
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ return target_->GetChildrenFileAttributes(dir, result);
+ }
+ Status DeleteFile(const std::string& f) override {
+ return target_->DeleteFile(f);
+ }
+ Status Truncate(const std::string& fname, size_t size) override {
+ return target_->Truncate(fname, size);
+ }
+ Status CreateDir(const std::string& d) override {
+ return target_->CreateDir(d);
+ }
+ Status CreateDirIfMissing(const std::string& d) override {
+ return target_->CreateDirIfMissing(d);
+ }
+ Status DeleteDir(const std::string& d) override {
+ return target_->DeleteDir(d);
+ }
+ Status GetFileSize(const std::string& f, uint64_t* s) override {
+ return target_->GetFileSize(f, s);
+ }
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override {
+ return target_->GetFileModificationTime(fname, file_mtime);
+ }
+
+ Status RenameFile(const std::string& s, const std::string& t) override {
+ return target_->RenameFile(s, t);
+ }
+
+ Status LinkFile(const std::string& s, const std::string& t) override {
+ return target_->LinkFile(s, t);
+ }
+
+ Status NumFileLinks(const std::string& fname, uint64_t* count) override {
+ return target_->NumFileLinks(fname, count);
+ }
+
+ Status AreFilesSame(const std::string& first, const std::string& second,
+ bool* res) override {
+ return target_->AreFilesSame(first, second, res);
+ }
+
+ Status LockFile(const std::string& f, FileLock** l) override {
+ return target_->LockFile(f, l);
+ }
+
+ Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); }
+
+ Status LoadLibrary(const std::string& lib_name,
+ const std::string& search_path,
+ std::shared_ptr<DynamicLibrary>* result) override {
+ return target_->LoadLibrary(lib_name, search_path, result);
+ }
+
+ void Schedule(void (*f)(void* arg), void* a, Priority pri,
+ void* tag = nullptr, void (*u)(void* arg) = nullptr) override {
+ return target_->Schedule(f, a, pri, tag, u);
+ }
+
+ int UnSchedule(void* tag, Priority pri) override {
+ return target_->UnSchedule(tag, pri);
+ }
+
+ void StartThread(void (*f)(void*), void* a) override {
+ return target_->StartThread(f, a);
+ }
+ void WaitForJoin() override { return target_->WaitForJoin(); }
+ unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override {
+ return target_->GetThreadPoolQueueLen(pri);
+ }
+ Status GetTestDirectory(std::string* path) override {
+ return target_->GetTestDirectory(path);
+ }
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override {
+ return target_->NewLogger(fname, result);
+ }
+ uint64_t NowMicros() override { return target_->NowMicros(); }
+ uint64_t NowNanos() override { return target_->NowNanos(); }
+ uint64_t NowCPUNanos() override { return target_->NowCPUNanos(); }
+
+ void SleepForMicroseconds(int micros) override {
+ target_->SleepForMicroseconds(micros);
+ }
+ Status GetHostName(char* name, uint64_t len) override {
+ return target_->GetHostName(name, len);
+ }
+ Status GetCurrentTime(int64_t* unix_time) override {
+ return target_->GetCurrentTime(unix_time);
+ }
+ Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) override {
+ return target_->GetAbsolutePath(db_path, output_path);
+ }
+ void SetBackgroundThreads(int num, Priority pri) override {
+ return target_->SetBackgroundThreads(num, pri);
+ }
+ int GetBackgroundThreads(Priority pri) override {
+ return target_->GetBackgroundThreads(pri);
+ }
+
+ Status SetAllowNonOwnerAccess(bool allow_non_owner_access) override {
+ return target_->SetAllowNonOwnerAccess(allow_non_owner_access);
+ }
+
+ void IncBackgroundThreadsIfNeeded(int num, Priority pri) override {
+ return target_->IncBackgroundThreadsIfNeeded(num, pri);
+ }
+
+ void LowerThreadPoolIOPriority(Priority pool = LOW) override {
+ target_->LowerThreadPoolIOPriority(pool);
+ }
+
+ void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
+ target_->LowerThreadPoolCPUPriority(pool);
+ }
+
+ std::string TimeToString(uint64_t time) override {
+ return target_->TimeToString(time);
+ }
+
+ Status GetThreadList(std::vector<ThreadStatus>* thread_list) override {
+ return target_->GetThreadList(thread_list);
+ }
+
+ ThreadStatusUpdater* GetThreadStatusUpdater() const override {
+ return target_->GetThreadStatusUpdater();
+ }
+
+ uint64_t GetThreadID() const override { return target_->GetThreadID(); }
+
+ std::string GenerateUniqueId() override {
+ return target_->GenerateUniqueId();
+ }
+
+ EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const override {
+ return target_->OptimizeForLogRead(env_options);
+ }
+ EnvOptions OptimizeForManifestRead(
+ const EnvOptions& env_options) const override {
+ return target_->OptimizeForManifestRead(env_options);
+ }
+ EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const override {
+ return target_->OptimizeForLogWrite(env_options, db_options);
+ }
+ EnvOptions OptimizeForManifestWrite(
+ const EnvOptions& env_options) const override {
+ return target_->OptimizeForManifestWrite(env_options);
+ }
+ EnvOptions OptimizeForCompactionTableWrite(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& immutable_ops) const override {
+ return target_->OptimizeForCompactionTableWrite(env_options, immutable_ops);
+ }
+ EnvOptions OptimizeForCompactionTableRead(
+ const EnvOptions& env_options,
+ const ImmutableDBOptions& db_options) const override {
+ return target_->OptimizeForCompactionTableRead(env_options, db_options);
+ }
+ Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override {
+ return target_->GetFreeSpace(path, diskfree);
+ }
+ void SanitizeEnvOptions(EnvOptions* env_opts) const override {
+ target_->SanitizeEnvOptions(env_opts);
+ }
+
+ private:
+ Env* target_;
+};
+
+class SequentialFileWrapper : public SequentialFile {
+ public:
+ explicit SequentialFileWrapper(SequentialFile* target) : target_(target) {}
+
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ return target_->Read(n, result, scratch);
+ }
+ Status Skip(uint64_t n) override { return target_->Skip(n); }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+ Status PositionedRead(uint64_t offset, size_t n, Slice* result,
+ char* scratch) override {
+ return target_->PositionedRead(offset, n, result, scratch);
+ }
+
+ private:
+ SequentialFile* target_;
+};
+
+class RandomAccessFileWrapper : public RandomAccessFile {
+ public:
+ explicit RandomAccessFileWrapper(RandomAccessFile* target)
+ : target_(target) {}
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ return target_->Read(offset, n, result, scratch);
+ }
+ Status MultiRead(ReadRequest* reqs, size_t num_reqs) override {
+ return target_->MultiRead(reqs, num_reqs);
+ }
+ Status Prefetch(uint64_t offset, size_t n) override {
+ return target_->Prefetch(offset, n);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+ void Hint(AccessPattern pattern) override { target_->Hint(pattern); }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ private:
+ RandomAccessFile* target_;
+};
+
+class WritableFileWrapper : public WritableFile {
+ public:
+ explicit WritableFileWrapper(WritableFile* t) : target_(t) {}
+
+ Status Append(const Slice& data) override { return target_->Append(data); }
+ Status PositionedAppend(const Slice& data, uint64_t offset) override {
+ return target_->PositionedAppend(data, offset);
+ }
+ Status Truncate(uint64_t size) override { return target_->Truncate(size); }
+ Status Close() override { return target_->Close(); }
+ Status Flush() override { return target_->Flush(); }
+ Status Sync() override { return target_->Sync(); }
+ Status Fsync() override { return target_->Fsync(); }
+ bool IsSyncThreadSafe() const override { return target_->IsSyncThreadSafe(); }
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+
+ void SetIOPriority(Env::IOPriority pri) override {
+ target_->SetIOPriority(pri);
+ }
+
+ Env::IOPriority GetIOPriority() override { return target_->GetIOPriority(); }
+
+ void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override {
+ target_->SetWriteLifeTimeHint(hint);
+ }
+
+ Env::WriteLifeTimeHint GetWriteLifeTimeHint() override {
+ return target_->GetWriteLifeTimeHint();
+ }
+
+ uint64_t GetFileSize() override { return target_->GetFileSize(); }
+
+ void SetPreallocationBlockSize(size_t size) override {
+ target_->SetPreallocationBlockSize(size);
+ }
+
+ void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) override {
+ target_->GetPreallocationStatus(block_size, last_allocated_block);
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ Status InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ Status RangeSync(uint64_t offset, uint64_t nbytes) override {
+ return target_->RangeSync(offset, nbytes);
+ }
+
+ void PrepareWrite(size_t offset, size_t len) override {
+ target_->PrepareWrite(offset, len);
+ }
+
+ Status Allocate(uint64_t offset, uint64_t len) override {
+ return target_->Allocate(offset, len);
+ }
+
+ private:
+ WritableFile* target_;
+};
+
+class RandomRWFileWrapper : public RandomRWFile {
+ public:
+ explicit RandomRWFileWrapper(RandomRWFile* target) : target_(target) {}
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ Status Write(uint64_t offset, const Slice& data) override {
+ return target_->Write(offset, data);
+ }
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ return target_->Read(offset, n, result, scratch);
+ }
+ Status Flush() override { return target_->Flush(); }
+ Status Sync() override { return target_->Sync(); }
+ Status Fsync() override { return target_->Fsync(); }
+ Status Close() override { return target_->Close(); }
+
+ private:
+ RandomRWFile* target_;
+};
+
+class DirectoryWrapper : public Directory {
+ public:
+ explicit DirectoryWrapper(Directory* target) : target_(target) {}
+
+ Status Fsync() override { return target_->Fsync(); }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ private:
+ Directory* target_;
+};
+
+class LoggerWrapper : public Logger {
+ public:
+ explicit LoggerWrapper(Logger* target) : target_(target) {}
+
+ Status Close() override { return target_->Close(); }
+ void LogHeader(const char* format, va_list ap) override {
+ return target_->LogHeader(format, ap);
+ }
+ void Logv(const char* format, va_list ap) override {
+ return target_->Logv(format, ap);
+ }
+ void Logv(const InfoLogLevel log_level, const char* format,
+ va_list ap) override {
+ return target_->Logv(log_level, format, ap);
+ }
+ size_t GetLogFileSize() const override { return target_->GetLogFileSize(); }
+ void Flush() override { return target_->Flush(); }
+ InfoLogLevel GetInfoLogLevel() const override {
+ return target_->GetInfoLogLevel();
+ }
+ void SetInfoLogLevel(const InfoLogLevel log_level) override {
+ return target_->SetInfoLogLevel(log_level);
+ }
+
+ private:
+ Logger* target_;
+};
+
+// Returns a new environment that stores its data in memory and delegates
+// all non-file-storage tasks to base_env. The caller must delete the result
+// when it is no longer needed.
+// *base_env must remain live while the result is in use.
+Env* NewMemEnv(Env* base_env);
+
+// Returns a new environment that is used for HDFS environment.
+// This is a factory method for HdfsEnv declared in hdfs/env_hdfs.h
+Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname);
+
+// Returns a new environment that measures function call times for filesystem
+// operations, reporting results to variables in PerfContext.
+// This is a factory method for TimedEnv defined in utilities/env_timed.cc.
+Env* NewTimedEnv(Env* base_env);
+
+// Returns an instance of logger that can be used for storing informational
+// messages.
+// This is a factory method for EnvLogger declared in logging/env_logging.h
+Status NewEnvLogger(const std::string& fname, Env* env,
+ std::shared_ptr<Logger>* result);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/env_encryption.h b/storage/rocksdb/rocksdb/include/rocksdb/env_encryption.h
new file mode 100644
index 00000000..a4db10fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/env_encryption.h
@@ -0,0 +1,206 @@
+// 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).
+
+#pragma once
+
+#if !defined(ROCKSDB_LITE)
+
+#include <string>
+
+#include "env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class EncryptionProvider;
+
+// Returns an Env that encrypts data when stored on disk and decrypts data when
+// read from disk.
+Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider);
+
+// BlockAccessCipherStream is the base class for any cipher stream that
+// supports random access at block level (without requiring data from other
+// blocks). E.g. CTR (Counter operation mode) supports this requirement.
+class BlockAccessCipherStream {
+ public:
+ virtual ~BlockAccessCipherStream(){};
+
+ // BlockSize returns the size of each block supported by this cipher stream.
+ virtual size_t BlockSize() = 0;
+
+ // Encrypt one or more (partial) blocks of data at the file offset.
+ // Length of data is given in dataSize.
+ virtual Status Encrypt(uint64_t fileOffset, char* data, size_t dataSize);
+
+ // Decrypt one or more (partial) blocks of data at the file offset.
+ // Length of data is given in dataSize.
+ virtual Status Decrypt(uint64_t fileOffset, char* data, size_t dataSize);
+
+ protected:
+ // Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
+ virtual void AllocateScratch(std::string&) = 0;
+
+ // Encrypt a block of data at the given block index.
+ // Length of data is equal to BlockSize();
+ virtual Status EncryptBlock(uint64_t blockIndex, char* data,
+ char* scratch) = 0;
+
+ // Decrypt a block of data at the given block index.
+ // Length of data is equal to BlockSize();
+ virtual Status DecryptBlock(uint64_t blockIndex, char* data,
+ char* scratch) = 0;
+};
+
+// BlockCipher
+class BlockCipher {
+ public:
+ virtual ~BlockCipher(){};
+
+ // BlockSize returns the size of each block supported by this cipher stream.
+ virtual size_t BlockSize() = 0;
+
+ // Encrypt a block of data.
+ // Length of data is equal to BlockSize().
+ virtual Status Encrypt(char* data) = 0;
+
+ // Decrypt a block of data.
+ // Length of data is equal to BlockSize().
+ virtual Status Decrypt(char* data) = 0;
+};
+
+// Implements a BlockCipher using ROT13.
+//
+// Note: This is a sample implementation of BlockCipher,
+// it is NOT considered safe and should NOT be used in production.
+class ROT13BlockCipher : public BlockCipher {
+ private:
+ size_t blockSize_;
+
+ public:
+ ROT13BlockCipher(size_t blockSize) : blockSize_(blockSize) {}
+ virtual ~ROT13BlockCipher(){};
+
+ // BlockSize returns the size of each block supported by this cipher stream.
+ virtual size_t BlockSize() override { return blockSize_; }
+
+ // Encrypt a block of data.
+ // Length of data is equal to BlockSize().
+ virtual Status Encrypt(char* data) override;
+
+ // Decrypt a block of data.
+ // Length of data is equal to BlockSize().
+ virtual Status Decrypt(char* data) override;
+};
+
+// CTRCipherStream implements BlockAccessCipherStream using an
+// Counter operations mode.
+// See https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+//
+// Note: This is a possible implementation of BlockAccessCipherStream,
+// it is considered suitable for use.
+class CTRCipherStream final : public BlockAccessCipherStream {
+ private:
+ BlockCipher& cipher_;
+ std::string iv_;
+ uint64_t initialCounter_;
+
+ public:
+ CTRCipherStream(BlockCipher& c, const char* iv, uint64_t initialCounter)
+ : cipher_(c), iv_(iv, c.BlockSize()), initialCounter_(initialCounter){};
+ virtual ~CTRCipherStream(){};
+
+ // BlockSize returns the size of each block supported by this cipher stream.
+ virtual size_t BlockSize() override { return cipher_.BlockSize(); }
+
+ protected:
+ // Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
+ virtual void AllocateScratch(std::string&) override;
+
+ // Encrypt a block of data at the given block index.
+ // Length of data is equal to BlockSize();
+ virtual Status EncryptBlock(uint64_t blockIndex, char* data,
+ char* scratch) override;
+
+ // Decrypt a block of data at the given block index.
+ // Length of data is equal to BlockSize();
+ virtual Status DecryptBlock(uint64_t blockIndex, char* data,
+ char* scratch) override;
+};
+
+// The encryption provider is used to create a cipher stream for a specific
+// file. The returned cipher stream will be used for actual
+// encryption/decryption actions.
+class EncryptionProvider {
+ public:
+ virtual ~EncryptionProvider(){};
+
+ // GetPrefixLength returns the length of the prefix that is added to every
+ // file and used for storing encryption options. For optimal performance, the
+ // prefix length should be a multiple of the page size.
+ virtual size_t GetPrefixLength() = 0;
+
+ // CreateNewPrefix initialized an allocated block of prefix memory
+ // for a new file.
+ virtual Status CreateNewPrefix(const std::string& fname, char* prefix,
+ size_t prefixLength) = 0;
+
+ // CreateCipherStream creates a block access cipher stream for a file given
+ // given name and options.
+ virtual Status CreateCipherStream(
+ const std::string& fname, const EnvOptions& options, Slice& prefix,
+ std::unique_ptr<BlockAccessCipherStream>* result) = 0;
+};
+
+// This encryption provider uses a CTR cipher stream, with a given block cipher
+// and IV.
+//
+// Note: This is a possible implementation of EncryptionProvider,
+// it is considered suitable for use, provided a safe BlockCipher is used.
+class CTREncryptionProvider : public EncryptionProvider {
+ private:
+ BlockCipher& cipher_;
+
+ protected:
+ const static size_t defaultPrefixLength = 4096;
+
+ public:
+ CTREncryptionProvider(BlockCipher& c) : cipher_(c){};
+ virtual ~CTREncryptionProvider() {}
+
+ // GetPrefixLength returns the length of the prefix that is added to every
+ // file and used for storing encryption options. For optimal performance, the
+ // prefix length should be a multiple of the page size.
+ virtual size_t GetPrefixLength() override;
+
+ // CreateNewPrefix initialized an allocated block of prefix memory
+ // for a new file.
+ virtual Status CreateNewPrefix(const std::string& fname, char* prefix,
+ size_t prefixLength) override;
+
+ // CreateCipherStream creates a block access cipher stream for a file given
+ // given name and options.
+ virtual Status CreateCipherStream(
+ const std::string& fname, const EnvOptions& options, Slice& prefix,
+ std::unique_ptr<BlockAccessCipherStream>* result) override;
+
+ protected:
+ // PopulateSecretPrefixPart initializes the data into a new prefix block
+ // that will be encrypted. This function will store the data in plain text.
+ // It will be encrypted later (before written to disk).
+ // Returns the amount of space (starting from the start of the prefix)
+ // that has been initialized.
+ virtual size_t PopulateSecretPrefixPart(char* prefix, size_t prefixLength,
+ size_t blockSize);
+
+ // CreateCipherStreamFromPrefix creates a block access cipher stream for a
+ // file given given name and options. The given prefix is already decrypted.
+ virtual Status CreateCipherStreamFromPrefix(
+ const std::string& fname, const EnvOptions& options,
+ uint64_t initialCounter, const Slice& iv, const Slice& prefix,
+ std::unique_ptr<BlockAccessCipherStream>* result);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !defined(ROCKSDB_LITE)
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/experimental.h b/storage/rocksdb/rocksdb/include/rocksdb/experimental.h
new file mode 100644
index 00000000..f26d6371
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/experimental.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/db.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace experimental {
+
+// Supported only for Leveled compaction
+Status SuggestCompactRange(DB* db, ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end);
+Status SuggestCompactRange(DB* db, const Slice* begin, const Slice* end);
+
+// Move all L0 files to target_level skipping compaction.
+// This operation succeeds only if the files in L0 have disjoint ranges; this
+// is guaranteed to happen, for instance, if keys are inserted in sorted
+// order. Furthermore, all levels between 1 and target_level must be empty.
+// If any of the above condition is violated, InvalidArgument will be
+// returned.
+Status PromoteL0(DB* db, ColumnFamilyHandle* column_family,
+ int target_level = 1);
+
+} // namespace experimental
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/file_checksum.h b/storage/rocksdb/rocksdb/include/rocksdb/file_checksum.h
new file mode 100644
index 00000000..35f54f40
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/file_checksum.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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) 2013 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 <cassert>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// FileChecksumFunc is the function class to generates the checksum value
+// for each file when the file is written to the file system.
+class FileChecksumFunc {
+ public:
+ virtual ~FileChecksumFunc() {}
+ // Return the checksum of concat (A, data[0,n-1]) where init_checksum is the
+ // returned value of some string A. It is used to maintain the checksum of a
+ // stream of data
+ virtual std::string Extend(const std::string& init_checksum, const char* data,
+ size_t n) = 0;
+
+ // Return the checksum value of data[0,n-1]
+ virtual std::string Value(const char* data, size_t n) = 0;
+
+ // Return a processed value of the checksum for store in somewhere
+ virtual std::string ProcessChecksum(const std::string& checksum) = 0;
+
+ // Returns a name that identifies the current file checksum function.
+ virtual const char* Name() const = 0;
+};
+
+// FileChecksumList stores the checksum information of a list of files (e.g.,
+// SST files). The FileChecksumLIst can be used to store the checksum
+// information of all SST file getting from the MANIFEST, which are
+// the checksum information of all valid SST file of a DB instance. It can
+// also be used to store the checksum information of a list of SST files to
+// be ingested.
+class FileChecksumList {
+ public:
+ virtual ~FileChecksumList() {}
+
+ // Clean the previously stored file checksum information.
+ virtual void reset() = 0;
+
+ // Get the number of checksums in the checksum list
+ virtual size_t size() const = 0;
+
+ // Return all the file checksum information being stored in a unordered_map.
+ // File_number is the key, the first part of the value is checksum value,
+ // and the second part of the value is checksum function name.
+ virtual Status GetAllFileChecksums(
+ std::vector<uint64_t>* file_numbers, std::vector<std::string>* checksums,
+ std::vector<std::string>* checksum_func_names) = 0;
+
+ // Given the file_number, it searches if the file checksum information is
+ // stored.
+ virtual Status SearchOneFileChecksum(uint64_t file_number,
+ std::string* checksum,
+ std::string* checksum_func_name) = 0;
+
+ // Insert the checksum information of one file to the FileChecksumList.
+ virtual Status InsertOneFileChecksum(
+ uint64_t file_number, const std::string& checksum,
+ const std::string& checksum_func_name) = 0;
+
+ // Remove the checksum information of one SST file.
+ virtual Status RemoveOneFileChecksum(uint64_t file_number) = 0;
+};
+
+// Create a new file checksum list.
+extern FileChecksumList* NewFileChecksumList();
+
+// Create a Crc32c based file checksum function
+extern FileChecksumFunc* CreateFileChecksumFuncCrc32c();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/file_system.h b/storage/rocksdb/rocksdb/include/rocksdb/file_system.h
new file mode 100644
index 00000000..c1fd919f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/file_system.h
@@ -0,0 +1,1358 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// A FileSystem is an interface used by the rocksdb implementation to access
+// storage functionality like the filesystem etc. Callers
+// may wish to provide a custom FileSystem object when opening a database to
+// get fine gain control; e.g., to rate limit file system operations.
+//
+// All FileSystem implementations are safe for concurrent access from
+// multiple threads without any external synchronization.
+//
+// WARNING: Since this is a new interface, it is expected that there will be
+// some changes as storage systems are ported over.
+
+#pragma once
+
+#include <stdint.h>
+#include <chrono>
+#include <cstdarg>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+#include "rocksdb/env.h"
+#include "rocksdb/io_status.h"
+#include "rocksdb/options.h"
+#include "rocksdb/thread_status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class FileLock;
+class FSDirectory;
+class FSRandomAccessFile;
+class FSRandomRWFile;
+class FSSequentialFile;
+class FSWritableFile;
+class Logger;
+class Slice;
+struct ImmutableDBOptions;
+struct MutableDBOptions;
+class RateLimiter;
+
+using AccessPattern = RandomAccessFile::AccessPattern;
+using FileAttributes = Env::FileAttributes;
+
+// Priority of an IO request. This is a hint and does not guarantee any
+// particular QoS.
+// IO_LOW - Typically background reads/writes such as compaction/flush
+// IO_HIGH - Typically user reads/synchronous WAL writes
+enum class IOPriority : uint8_t {
+ kIOLow,
+ kIOHigh,
+ kIOTotal,
+};
+
+// Type of the data begin read/written. It can be passed down as a flag
+// for the FileSystem implementation to optionally handle different types in
+// different ways
+enum class IOType : uint8_t {
+ kData,
+ kFilter,
+ kIndex,
+ kMetadata,
+ kWAL,
+ kManifest,
+ kLog,
+ kUnknown,
+ kInvalid,
+};
+
+// Per-request options that can be passed down to the FileSystem
+// implementation. These are hints and are not necessarily guaranteed to be
+// honored. More hints can be added here in the future to indicate things like
+// storage media (HDD/SSD) to be used, replication level etc.
+struct IOOptions {
+ // Timeout for the operation in milliseconds
+ std::chrono::milliseconds timeout;
+
+ // Priority - high or low
+ IOPriority prio;
+
+ // Type of data being read/written
+ IOType type;
+};
+
+// File scope options that control how a file is opened/created and accessed
+// while its open. We may add more options here in the future such as
+// redundancy level, media to use etc.
+struct FileOptions : EnvOptions {
+ // Embedded IOOptions to control the parameters for any IOs that need
+ // to be issued for the file open/creation
+ IOOptions io_options;
+
+ FileOptions() : EnvOptions() {}
+
+ FileOptions(const DBOptions& opts)
+ : EnvOptions(opts) {}
+
+ FileOptions(const EnvOptions& opts)
+ : EnvOptions(opts) {}
+};
+
+// A structure to pass back some debugging information from the FileSystem
+// implementation to RocksDB in case of an IO error
+struct IODebugContext {
+ // file_path to be filled in by RocksDB in case of an error
+ std::string file_path;
+
+ // A map of counter names to values - set by the FileSystem implementation
+ std::map<std::string, uint64_t> counters;
+
+ // To be set by the FileSystem implementation
+ std::string msg;
+
+ IODebugContext() {}
+
+ void AddCounter(std::string& name, uint64_t value) {
+ counters.emplace(name, value);
+ }
+
+ std::string ToString() {
+ std::ostringstream ss;
+ ss << file_path << ", ";
+ for (auto counter : counters) {
+ ss << counter.first << " = " << counter.second << ",";
+ }
+ ss << msg;
+ return ss.str();
+ }
+};
+
+// The FileSystem, FSSequentialFile, FSRandomAccessFile, FSWritableFile,
+// FSRandomRWFileclass, and FSDIrectory classes define the interface between
+// RocksDB and storage systems, such as Posix filesystems,
+// remote filesystems etc.
+// The interface allows for fine grained control of individual IO operations,
+// such as setting a timeout, prioritization, hints on data placement,
+// different handling based on type of IO etc.
+// This is accomplished by passing an instance of IOOptions to every
+// API call that can potentially perform IO. Additionally, each such API is
+// passed a pointer to a IODebugContext structure that can be used by the
+// storage system to include troubleshooting information. The return values
+// of the APIs is of type IOStatus, which can indicate an error code/sub-code,
+// as well as metadata about the error such as its scope and whether its
+// retryable.
+class FileSystem {
+ public:
+ FileSystem();
+
+ // No copying allowed
+ FileSystem(const FileSystem&) = delete;
+
+ virtual ~FileSystem();
+
+ virtual const char* Name() const = 0;
+
+ static const char* Type() { return "FileSystem"; }
+
+ // Loads the FileSystem specified by the input value into the result
+ static Status Load(const std::string& value,
+ std::shared_ptr<FileSystem>* result);
+
+ // Return a default fie_system suitable for the current operating
+ // system. Sophisticated users may wish to provide their own Env
+ // implementation instead of relying on this default file_system
+ //
+ // The result of Default() belongs to rocksdb and must never be deleted.
+ static std::shared_ptr<FileSystem> Default();
+
+ // Create a brand new sequentially-readable file with the specified name.
+ // On success, stores a pointer to the new file in *result and returns OK.
+ // On failure stores nullptr in *result and returns non-OK. If the file does
+ // not exist, returns a non-OK status.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual IOStatus NewSequentialFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSSequentialFile>* result,
+ IODebugContext* dbg) = 0;
+
+ // Create a brand new random access read-only file with the
+ // specified name. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK. If the file does not exist, returns a non-OK
+ // status.
+ //
+ // The returned file may be concurrently accessed by multiple threads.
+ virtual IOStatus NewRandomAccessFile(
+ const std::string& fname, const FileOptions& file_opts,
+ std::unique_ptr<FSRandomAccessFile>* result,
+ IODebugContext* dbg) = 0;
+ // These values match Linux definition
+ // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fcntl.h#n56
+ enum WriteLifeTimeHint {
+ kWLTHNotSet = 0, // No hint information set
+ kWLTHNone, // No hints about write life time
+ kWLTHShort, // Data written has a short life time
+ kWLTHMedium, // Data written has a medium life time
+ kWLTHLong, // Data written has a long life time
+ kWLTHExtreme, // Data written has an extremely long life time
+ };
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual IOStatus NewWritableFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* dbg) = 0;
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual IOStatus ReopenWritableFile(
+ const std::string& /*fname*/, const FileOptions& /*options*/,
+ std::unique_ptr<FSWritableFile>* /*result*/, IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported();
+ }
+
+ // Reuse an existing file by renaming it and opening it as writable.
+ virtual IOStatus ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* dbg) = 0;
+
+ // Open `fname` for random read and write, if file doesn't exist the file
+ // will be created. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ virtual IOStatus NewRandomRWFile(const std::string& /*fname*/,
+ const FileOptions& /*options*/,
+ std::unique_ptr<FSRandomRWFile>* /*result*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported(
+ "RandomRWFile is not implemented in this FileSystem");
+ }
+
+ // Opens `fname` as a memory-mapped file for read and write (in-place updates
+ // only, i.e., no appends). On success, stores a raw buffer covering the whole
+ // file in `*result`. The file must exist prior to this call.
+ virtual IOStatus NewMemoryMappedFileBuffer(
+ const std::string& /*fname*/,
+ std::unique_ptr<MemoryMappedFileBuffer>* /*result*/) {
+ return IOStatus::NotSupported(
+ "MemoryMappedFileBuffer is not implemented in this FileSystem");
+ }
+
+ // Create an object that represents a directory. Will fail if directory
+ // doesn't exist. If the directory exists, it will open the directory
+ // and create a new Directory object.
+ //
+ // On success, stores a pointer to the new Directory in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ virtual IOStatus NewDirectory(const std::string& name,
+ const IOOptions& io_opts,
+ std::unique_ptr<FSDirectory>* result,
+ IODebugContext* dbg) = 0;
+
+ // Returns OK if the named file exists.
+ // NotFound if the named file does not exist,
+ // the calling process does not have permission to determine
+ // whether this file exists, or if the path is invalid.
+ // IOError if an IO Error was encountered
+ virtual IOStatus FileExists(const std::string& fname,
+ const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // Store in *result the names of the children of the specified directory.
+ // The names are relative to "dir".
+ // Original contents of *results are dropped.
+ // Returns OK if "dir" exists and "*result" contains its children.
+ // NotFound if "dir" does not exist, the calling process does not have
+ // permission to access "dir", or if "dir" is invalid.
+ // IOError if an IO Error was encountered
+ virtual IOStatus GetChildren(const std::string& dir, const IOOptions& options,
+ std::vector<std::string>* result,
+ IODebugContext* dbg) = 0;
+
+ // Store in *result the attributes of the children of the specified directory.
+ // In case the implementation lists the directory prior to iterating the files
+ // and files are concurrently deleted, the deleted files will be omitted from
+ // result.
+ // The name attributes are relative to "dir".
+ // Original contents of *results are dropped.
+ // Returns OK if "dir" exists and "*result" contains its children.
+ // NotFound if "dir" does not exist, the calling process does not have
+ // permission to access "dir", or if "dir" is invalid.
+ // IOError if an IO Error was encountered
+ virtual IOStatus GetChildrenFileAttributes(
+ const std::string& dir, const IOOptions& options,
+ std::vector<FileAttributes>* result, IODebugContext* dbg) {
+ assert(result != nullptr);
+ std::vector<std::string> child_fnames;
+ IOStatus s = GetChildren(dir, options, &child_fnames, dbg);
+ if (!s.ok()) {
+ return s;
+ }
+ result->resize(child_fnames.size());
+ size_t result_size = 0;
+ for (size_t i = 0; i < child_fnames.size(); ++i) {
+ const std::string path = dir + "/" + child_fnames[i];
+ if (!(s = GetFileSize(path, options, &(*result)[result_size].size_bytes,
+ dbg))
+ .ok()) {
+ if (FileExists(path, options, dbg).IsNotFound()) {
+ // The file may have been deleted since we listed the directory
+ continue;
+ }
+ return s;
+ }
+ (*result)[result_size].name = std::move(child_fnames[i]);
+ result_size++;
+ }
+ result->resize(result_size);
+ return IOStatus::OK();
+ }
+
+ // Delete the named file.
+ virtual IOStatus DeleteFile(const std::string& fname,
+ const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // Truncate the named file to the specified size.
+ virtual IOStatus Truncate(const std::string& /*fname*/, size_t /*size*/,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported("Truncate is not supported for this FileSystem");
+ }
+
+ // Create the specified directory. Returns error if directory exists.
+ virtual IOStatus CreateDir(const std::string& dirname,
+ const IOOptions& options, IODebugContext* dbg) = 0;
+
+ // Creates directory if missing. Return Ok if it exists, or successful in
+ // Creating.
+ virtual IOStatus CreateDirIfMissing(const std::string& dirname,
+ const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // Delete the specified directory.
+ virtual IOStatus DeleteDir(const std::string& dirname,
+ const IOOptions& options, IODebugContext* dbg) = 0;
+
+ // Store the size of fname in *file_size.
+ virtual IOStatus GetFileSize(const std::string& fname,
+ const IOOptions& options, uint64_t* file_size,
+ IODebugContext* dbg) = 0;
+
+ // Store the last modification time of fname in *file_mtime.
+ virtual IOStatus GetFileModificationTime(const std::string& fname,
+ const IOOptions& options,
+ uint64_t* file_mtime,
+ IODebugContext* dbg) = 0;
+ // Rename file src to target.
+ virtual IOStatus RenameFile(const std::string& src, const std::string& target,
+ const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // Hard Link file src to target.
+ virtual IOStatus LinkFile(const std::string& /*src*/,
+ const std::string& /*target*/,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported("LinkFile is not supported for this FileSystem");
+ }
+
+ virtual IOStatus NumFileLinks(const std::string& /*fname*/,
+ const IOOptions& /*options*/,
+ uint64_t* /*count*/, IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported(
+ "Getting number of file links is not supported for this FileSystem");
+ }
+
+ virtual IOStatus AreFilesSame(const std::string& /*first*/,
+ const std::string& /*second*/,
+ const IOOptions& /*options*/, bool* /*res*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported("AreFilesSame is not supported for this FileSystem");
+ }
+
+ // Lock the specified file. Used to prevent concurrent access to
+ // the same db by multiple processes. On failure, stores nullptr in
+ // *lock and returns non-OK.
+ //
+ // On success, stores a pointer to the object that represents the
+ // acquired lock in *lock and returns OK. The caller should call
+ // UnlockFile(*lock) to release the lock. If the process exits,
+ // the lock will be automatically released.
+ //
+ // If somebody else already holds the lock, finishes immediately
+ // with a failure. I.e., this call does not wait for existing locks
+ // to go away.
+ //
+ // May create the named file if it does not already exist.
+ virtual IOStatus LockFile(const std::string& fname, const IOOptions& options,
+ FileLock** lock, IODebugContext* dbg) = 0;
+
+ // Release the lock acquired by a previous successful call to LockFile.
+ // REQUIRES: lock was returned by a successful LockFile() call
+ // REQUIRES: lock has not already been unlocked.
+ virtual IOStatus UnlockFile(FileLock* lock, const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // *path is set to a temporary directory that can be used for testing. It may
+ // or many not have just been created. The directory may or may not differ
+ // between runs of the same process, but subsequent calls will return the
+ // same directory.
+ virtual IOStatus GetTestDirectory(const IOOptions& options, std::string* path,
+ IODebugContext* dbg) = 0;
+
+ // Create and returns a default logger (an instance of EnvLogger) for storing
+ // informational messages. Derived classes can overide to provide custom
+ // logger.
+ virtual IOStatus NewLogger(const std::string& fname, const IOOptions& io_opts,
+ std::shared_ptr<Logger>* result,
+ IODebugContext* dbg) = 0;
+
+ // Get full directory name for this db.
+ virtual IOStatus GetAbsolutePath(const std::string& db_path,
+ const IOOptions& options,
+ std::string* output_path,
+ IODebugContext* dbg) = 0;
+
+ // OptimizeForLogRead will create a new FileOptions object that is a copy of
+ // the FileOptions in the parameters, but is optimized for reading log files.
+ virtual FileOptions OptimizeForLogRead(const FileOptions& file_options) const;
+
+ // OptimizeForManifestRead will create a new FileOptions object that is a copy
+ // of the FileOptions in the parameters, but is optimized for reading manifest
+ // files.
+ virtual FileOptions OptimizeForManifestRead(
+ const FileOptions& file_options) const;
+
+ // OptimizeForLogWrite will create a new FileOptions object that is a copy of
+ // the FileOptions in the parameters, but is optimized for writing log files.
+ // Default implementation returns the copy of the same object.
+ virtual FileOptions OptimizeForLogWrite(const FileOptions& file_options,
+ const DBOptions& db_options) const;
+
+ // OptimizeForManifestWrite will create a new FileOptions object that is a
+ // copy of the FileOptions in the parameters, but is optimized for writing
+ // manifest files. Default implementation returns the copy of the same
+ // object.
+ virtual FileOptions OptimizeForManifestWrite(
+ const FileOptions& file_options) const;
+
+ // OptimizeForCompactionTableWrite will create a new FileOptions object that
+ // is a copy of the FileOptions in the parameters, but is optimized for
+ // writing table files.
+ virtual FileOptions OptimizeForCompactionTableWrite(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& immutable_ops) const;
+
+ // OptimizeForCompactionTableRead will create a new FileOptions object that
+ // is a copy of the FileOptions in the parameters, but is optimized for
+ // reading table files.
+ virtual FileOptions OptimizeForCompactionTableRead(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& db_options) const;
+
+// This seems to clash with a macro on Windows, so #undef it here
+#ifdef GetFreeSpace
+#undef GetFreeSpace
+#endif
+
+ // Get the amount of free disk space
+ virtual IOStatus GetFreeSpace(const std::string& /*path*/,
+ const IOOptions& /*options*/,
+ uint64_t* /*diskfree*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported();
+ }
+
+ // If you're adding methods here, remember to add them to EnvWrapper too.
+
+ private:
+ void operator=(const FileSystem&);
+};
+
+// A file abstraction for reading sequentially through a file
+class FSSequentialFile {
+ public:
+ FSSequentialFile() {}
+
+ virtual ~FSSequentialFile() {}
+
+ // Read up to "n" bytes from the file. "scratch[0..n-1]" may be
+ // written by this routine. Sets "*result" to the data that was
+ // read (including if fewer than "n" bytes were successfully read).
+ // May set "*result" to point at data in "scratch[0..n-1]", so
+ // "scratch[0..n-1]" must be live when "*result" is used.
+ // If an error was encountered, returns a non-OK status.
+ //
+ // REQUIRES: External synchronization
+ virtual IOStatus Read(size_t n, const IOOptions& options, Slice* result,
+ char* scratch, IODebugContext* dbg) = 0;
+
+ // Skip "n" bytes from the file. This is guaranteed to be no
+ // slower that reading the same data, but may be faster.
+ //
+ // If end of file is reached, skipping will stop at the end of the
+ // file, and Skip will return OK.
+ //
+ // REQUIRES: External synchronization
+ virtual IOStatus Skip(uint64_t n) = 0;
+
+ // Indicates the upper layers if the current SequentialFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ virtual IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return IOStatus::NotSupported("InvalidateCache not supported.");
+ }
+
+ // Positioned Read for direct I/O
+ // If Direct I/O enabled, offset, n, and scratch should be properly aligned
+ virtual IOStatus PositionedRead(uint64_t /*offset*/, size_t /*n*/,
+ const IOOptions& /*options*/,
+ Slice* /*result*/, char* /*scratch*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported();
+ }
+
+ // If you're adding methods here, remember to add them to
+ // SequentialFileWrapper too.
+};
+
+// A read IO request structure for use in MultiRead
+struct FSReadRequest {
+ // File offset in bytes
+ uint64_t offset;
+
+ // Length to read in bytes
+ size_t len;
+
+ // A buffer that MultiRead() can optionally place data in. It can
+ // ignore this and allocate its own buffer
+ char* scratch;
+
+ // Output parameter set by MultiRead() to point to the data buffer, and
+ // the number of valid bytes
+ Slice result;
+
+ // Status of read
+ IOStatus status;
+};
+
+// A file abstraction for randomly reading the contents of a file.
+class FSRandomAccessFile {
+ public:
+ FSRandomAccessFile() {}
+
+ virtual ~FSRandomAccessFile() {}
+
+ // Read up to "n" bytes from the file starting at "offset".
+ // "scratch[0..n-1]" may be written by this routine. Sets "*result"
+ // to the data that was read (including if fewer than "n" bytes were
+ // successfully read). May set "*result" to point at data in
+ // "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
+ // "*result" is used. If an error was encountered, returns a non-OK
+ // status.
+ //
+ // Safe for concurrent use by multiple threads.
+ // If Direct I/O enabled, offset, n, and scratch should be aligned properly.
+ virtual IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const = 0;
+
+ // Readahead the file starting from offset by n bytes for caching.
+ virtual IOStatus Prefetch(uint64_t /*offset*/, size_t /*n*/,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+ }
+
+ // Read a bunch of blocks as described by reqs. The blocks can
+ // optionally be read in parallel. This is a synchronous call, i.e it
+ // should return after all reads have completed. The reads will be
+ // non-overlapping. If the function return Status is not ok, status of
+ // individual requests will be ignored and return status will be assumed
+ // for all read requests. The function return status is only meant for any
+ // any errors that occur before even processing specific read requests
+ virtual IOStatus MultiRead(FSReadRequest* reqs, size_t num_reqs,
+ const IOOptions& options, IODebugContext* dbg) {
+ assert(reqs != nullptr);
+ for (size_t i = 0; i < num_reqs; ++i) {
+ FSReadRequest& req = reqs[i];
+ req.status =
+ Read(req.offset, req.len, options, &req.result, req.scratch, dbg);
+ }
+ return IOStatus::OK();
+ }
+
+ // Tries to get an unique ID for this file that will be the same each time
+ // the file is opened (and will stay the same while the file is open).
+ // Furthermore, it tries to make this ID at most "max_size" bytes. If such an
+ // ID can be created this function returns the length of the ID and places it
+ // in "id"; otherwise, this function returns 0, in which case "id"
+ // may not have been modified.
+ //
+ // This function guarantees, for IDs from a given environment, two unique ids
+ // cannot be made equal to each other by adding arbitrary bytes to one of
+ // them. That is, no unique ID is the prefix of another.
+ //
+ // This function guarantees that the returned ID will not be interpretable as
+ // a single varint.
+ //
+ // Note: these IDs are only valid for the duration of the process.
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0; // Default implementation to prevent issues with backwards
+ // compatibility.
+ };
+
+ enum AccessPattern { kNormal, kRandom, kSequential, kWillNeed, kWontNeed };
+
+ virtual void Hint(AccessPattern /*pattern*/) {}
+
+ // Indicates the upper layers if the current RandomAccessFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ virtual IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return IOStatus::NotSupported("InvalidateCache not supported.");
+ }
+
+ // If you're adding methods here, remember to add them to
+ // RandomAccessFileWrapper too.
+};
+
+// A file abstraction for sequential writing. The implementation
+// must provide buffering since callers may append small fragments
+// at a time to the file.
+class FSWritableFile {
+ public:
+ FSWritableFile()
+ : last_preallocated_block_(0),
+ preallocation_block_size_(0),
+ io_priority_(Env::IO_TOTAL),
+ write_hint_(Env::WLTH_NOT_SET),
+ strict_bytes_per_sync_(false) {}
+
+ explicit FSWritableFile(const FileOptions& options)
+ : last_preallocated_block_(0),
+ preallocation_block_size_(0),
+ io_priority_(Env::IO_TOTAL),
+ write_hint_(Env::WLTH_NOT_SET),
+ strict_bytes_per_sync_(options.strict_bytes_per_sync) {}
+
+ virtual ~FSWritableFile() {}
+
+ // Append data to the end of the file
+ // Note: A WriteabelFile object must support either Append or
+ // PositionedAppend, so the users cannot mix the two.
+ virtual IOStatus Append(const Slice& data, const IOOptions& options,
+ IODebugContext* dbg) = 0;
+
+ // PositionedAppend data to the specified offset. The new EOF after append
+ // must be larger than the previous EOF. This is to be used when writes are
+ // not backed by OS buffers and hence has to always start from the start of
+ // the sector. The implementation thus needs to also rewrite the last
+ // partial sector.
+ // Note: PositionAppend does not guarantee moving the file offset after the
+ // write. A WritableFile object must support either Append or
+ // PositionedAppend, so the users cannot mix the two.
+ //
+ // PositionedAppend() can only happen on the page/sector boundaries. For that
+ // reason, if the last write was an incomplete sector we still need to rewind
+ // back to the nearest sector/page and rewrite the portion of it with whatever
+ // we need to add. We need to keep where we stop writing.
+ //
+ // PositionedAppend() can only write whole sectors. For that reason we have to
+ // pad with zeros for the last write and trim the file when closing according
+ // to the position we keep in the previous step.
+ //
+ // PositionedAppend() requires aligned buffer to be passed in. The alignment
+ // required is queried via GetRequiredBufferAlignment()
+ virtual IOStatus PositionedAppend(const Slice& /* data */,
+ uint64_t /* offset */,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::NotSupported();
+ }
+
+ // Truncate is necessary to trim the file to the correct size
+ // before closing. It is not always possible to keep track of the file
+ // size due to whole pages writes. The behavior is undefined if called
+ // with other writes to follow.
+ virtual IOStatus Truncate(uint64_t /*size*/, const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+ }
+ virtual IOStatus Close(const IOOptions& options, IODebugContext* dbg) = 0;
+ virtual IOStatus Flush(const IOOptions& options, IODebugContext* dbg) = 0;
+ virtual IOStatus Sync(const IOOptions& options,
+ IODebugContext* dbg) = 0; // sync data
+
+ /*
+ * Sync data and/or metadata as well.
+ * By default, sync only data.
+ * Override this method for environments where we need to sync
+ * metadata as well.
+ */
+ virtual IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) {
+ return Sync(options, dbg);
+ }
+
+ // true if Sync() and Fsync() are safe to call concurrently with Append()
+ // and Flush().
+ virtual bool IsSyncThreadSafe() const { return false; }
+
+ // Indicates the upper layers if the current WritableFile implementation
+ // uses direct IO.
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ virtual void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) {
+ write_hint_ = hint;
+ }
+
+ virtual void SetIOPriority(Env::IOPriority pri) { io_priority_ = pri; }
+
+ virtual Env::IOPriority GetIOPriority() { return io_priority_; }
+
+ virtual Env::WriteLifeTimeHint GetWriteLifeTimeHint() { return write_hint_; }
+ /*
+ * Get the size of valid data in the file.
+ */
+ virtual uint64_t GetFileSize(const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return 0;
+ }
+
+ /*
+ * Get and set the default pre-allocation block size for writes to
+ * this file. If non-zero, then Allocate will be used to extend the
+ * underlying storage of a file (generally via fallocate) if the Env
+ * instance supports it.
+ */
+ virtual void SetPreallocationBlockSize(size_t size) {
+ preallocation_block_size_ = size;
+ }
+
+ virtual void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) {
+ *last_allocated_block = last_preallocated_block_;
+ *block_size = preallocation_block_size_;
+ }
+
+ // For documentation, refer to RandomAccessFile::GetUniqueId()
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0; // Default implementation to prevent issues with backwards
+ }
+
+ // Remove any kind of caching of data from the offset to offset+length
+ // of this file. If the length is 0, then it refers to the end of file.
+ // If the system is not caching the file contents, then this is a noop.
+ // This call has no effect on dirty pages in the cache.
+ virtual IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) {
+ return IOStatus::NotSupported("InvalidateCache not supported.");
+ }
+
+ // Sync a file range with disk.
+ // offset is the starting byte of the file range to be synchronized.
+ // nbytes specifies the length of the range to be synchronized.
+ // This asks the OS to initiate flushing the cached data to disk,
+ // without waiting for completion.
+ // Default implementation does nothing.
+ virtual IOStatus RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/,
+ const IOOptions& options, IODebugContext* dbg) {
+ if (strict_bytes_per_sync_) {
+ return Sync(options, dbg);
+ }
+ return IOStatus::OK();
+ }
+
+ // PrepareWrite performs any necessary preparation for a write
+ // before the write actually occurs. This allows for pre-allocation
+ // of space on devices where it can result in less file
+ // fragmentation and/or less waste from over-zealous filesystem
+ // pre-allocation.
+ virtual void PrepareWrite(size_t offset, size_t len, const IOOptions& options,
+ IODebugContext* dbg) {
+ if (preallocation_block_size_ == 0) {
+ return;
+ }
+ // If this write would cross one or more preallocation blocks,
+ // determine what the last preallocation block necessary to
+ // cover this write would be and Allocate to that point.
+ const auto block_size = preallocation_block_size_;
+ size_t new_last_preallocated_block =
+ (offset + len + block_size - 1) / block_size;
+ if (new_last_preallocated_block > last_preallocated_block_) {
+ size_t num_spanned_blocks =
+ new_last_preallocated_block - last_preallocated_block_;
+ Allocate(block_size * last_preallocated_block_,
+ block_size * num_spanned_blocks, options, dbg);
+ last_preallocated_block_ = new_last_preallocated_block;
+ }
+ }
+
+ // Pre-allocates space for a file.
+ virtual IOStatus Allocate(uint64_t /*offset*/, uint64_t /*len*/,
+ const IOOptions& /*options*/,
+ IODebugContext* /*dbg*/) {
+ return IOStatus::OK();
+ }
+
+ // If you're adding methods here, remember to add them to
+ // WritableFileWrapper too.
+
+ protected:
+ size_t preallocation_block_size() { return preallocation_block_size_; }
+
+ private:
+ size_t last_preallocated_block_;
+ size_t preallocation_block_size_;
+ // No copying allowed
+ FSWritableFile(const FSWritableFile&);
+ void operator=(const FSWritableFile&);
+
+ protected:
+ Env::IOPriority io_priority_;
+ Env::WriteLifeTimeHint write_hint_;
+ const bool strict_bytes_per_sync_;
+};
+
+// A file abstraction for random reading and writing.
+class FSRandomRWFile {
+ public:
+ FSRandomRWFile() {}
+
+ virtual ~FSRandomRWFile() {}
+
+ // Indicates if the class makes use of direct I/O
+ // If false you must pass aligned buffer to Write()
+ virtual bool use_direct_io() const { return false; }
+
+ // Use the returned alignment value to allocate
+ // aligned buffer for Direct I/O
+ virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
+
+ // Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
+ // Pass aligned buffer when use_direct_io() returns true.
+ virtual IOStatus Write(uint64_t offset, const Slice& data,
+ const IOOptions& options, IODebugContext* dbg) = 0;
+
+ // Read up to `n` bytes starting from offset `offset` and store them in
+ // result, provided `scratch` size should be at least `n`.
+ // Returns Status::OK() on success.
+ virtual IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const = 0;
+
+ virtual IOStatus Flush(const IOOptions& options, IODebugContext* dbg) = 0;
+
+ virtual IOStatus Sync(const IOOptions& options, IODebugContext* dbg) = 0;
+
+ virtual IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) {
+ return Sync(options, dbg);
+ }
+
+ virtual IOStatus Close(const IOOptions& options, IODebugContext* dbg) = 0;
+
+ // If you're adding methods here, remember to add them to
+ // RandomRWFileWrapper too.
+
+ // No copying allowed
+ FSRandomRWFile(const RandomRWFile&) = delete;
+ FSRandomRWFile& operator=(const RandomRWFile&) = delete;
+};
+
+// MemoryMappedFileBuffer object represents a memory-mapped file's raw buffer.
+// Subclasses should release the mapping upon destruction.
+class FSMemoryMappedFileBuffer {
+ public:
+ FSMemoryMappedFileBuffer(void* _base, size_t _length)
+ : base_(_base), length_(_length) {}
+
+ virtual ~FSMemoryMappedFileBuffer() = 0;
+
+ // We do not want to unmap this twice. We can make this class
+ // movable if desired, however, since
+ FSMemoryMappedFileBuffer(const FSMemoryMappedFileBuffer&) = delete;
+ FSMemoryMappedFileBuffer& operator=(const FSMemoryMappedFileBuffer&) = delete;
+
+ void* GetBase() const { return base_; }
+ size_t GetLen() const { return length_; }
+
+ protected:
+ void* base_;
+ const size_t length_;
+};
+
+// Directory object represents collection of files and implements
+// filesystem operations that can be executed on directories.
+class FSDirectory {
+ public:
+ virtual ~FSDirectory() {}
+ // Fsync directory. Can be called concurrently from multiple threads.
+ virtual IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) = 0;
+
+ virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
+ return 0;
+ }
+
+ // If you're adding methods here, remember to add them to
+ // DirectoryWrapper too.
+};
+
+// Below are helpers for wrapping most of the classes in this file.
+// They forward all calls to another instance of the class.
+// Useful when wrapping the default implementations.
+// Typical usage is to inherit your wrapper from *Wrapper, e.g.:
+//
+// class MySequentialFileWrapper : public
+// ROCKSDB_NAMESPACE::FSSequentialFileWrapper {
+// public:
+// MySequentialFileWrapper(ROCKSDB_NAMESPACE::FSSequentialFile* target):
+// ROCKSDB_NAMESPACE::FSSequentialFileWrapper(target) {}
+// Status Read(size_t n, FileSystem::IOOptions& options, Slice* result,
+// char* scratch, FileSystem::IODebugContext* dbg) override {
+// cout << "Doing a read of size " << n << "!" << endl;
+// return ROCKSDB_NAMESPACE::FSSequentialFileWrapper::Read(n, options,
+// result,
+// scratch, dbg);
+// }
+// // All other methods are forwarded to target_ automatically.
+// };
+//
+// This is often more convenient than inheriting the class directly because
+// (a) Don't have to override and forward all methods - the Wrapper will
+// forward everything you're not explicitly overriding.
+// (b) Don't need to update the wrapper when more methods are added to the
+// rocksdb class. Unless you actually want to override the behavior.
+// (And unless rocksdb people forgot to update the *Wrapper class.)
+
+// An implementation of Env that forwards all calls to another Env.
+// May be useful to clients who wish to override just part of the
+// functionality of another Env.
+class FileSystemWrapper : public FileSystem {
+ public:
+ // Initialize an EnvWrapper that delegates all calls to *t
+ explicit FileSystemWrapper(FileSystem* t) : target_(t) {}
+ ~FileSystemWrapper() override {}
+
+ // Return the target to which this Env forwards all calls
+ FileSystem* target() const { return target_; }
+
+ // The following text is boilerplate that forwards all methods to target()
+ IOStatus NewSequentialFile(const std::string& f,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSSequentialFile>* r,
+ IODebugContext* dbg) override {
+ return target_->NewSequentialFile(f, file_opts, r, dbg);
+ }
+ IOStatus NewRandomAccessFile(const std::string& f,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSRandomAccessFile>* r,
+ IODebugContext* dbg) override {
+ return target_->NewRandomAccessFile(f, file_opts, r, dbg);
+ }
+ IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* r,
+ IODebugContext* dbg) override {
+ return target_->NewWritableFile(f, file_opts, r, dbg);
+ }
+ IOStatus ReopenWritableFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* result,
+ IODebugContext* dbg) override {
+ return target_->ReopenWritableFile(fname, file_opts, result, dbg);
+ }
+ IOStatus ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSWritableFile>* r,
+ IODebugContext* dbg) override {
+ return target_->ReuseWritableFile(fname, old_fname, file_opts, r,
+ dbg);
+ }
+ IOStatus NewRandomRWFile(const std::string& fname,
+ const FileOptions& file_opts,
+ std::unique_ptr<FSRandomRWFile>* result,
+ IODebugContext* dbg) override {
+ return target_->NewRandomRWFile(fname, file_opts, result, dbg);
+ }
+ IOStatus NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override {
+ return target_->NewMemoryMappedFileBuffer(fname, result);
+ }
+ IOStatus NewDirectory(const std::string& name, const IOOptions& io_opts,
+ std::unique_ptr<FSDirectory>* result,
+ IODebugContext* dbg) override {
+ return target_->NewDirectory(name, io_opts, result, dbg);
+ }
+ IOStatus FileExists(const std::string& f, const IOOptions& io_opts,
+ IODebugContext* dbg) override {
+ return target_->FileExists(f, io_opts, dbg);
+ }
+ IOStatus GetChildren(const std::string& dir, const IOOptions& io_opts,
+ std::vector<std::string>* r,
+ IODebugContext* dbg) override {
+ return target_->GetChildren(dir, io_opts, r, dbg);
+ }
+ IOStatus GetChildrenFileAttributes(const std::string& dir,
+ const IOOptions& options,
+ std::vector<FileAttributes>* result,
+ IODebugContext* dbg) override {
+ return target_->GetChildrenFileAttributes(dir, options, result, dbg);
+ }
+ IOStatus DeleteFile(const std::string& f, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->DeleteFile(f, options, dbg);
+ }
+ IOStatus Truncate(const std::string& fname, size_t size,
+ const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Truncate(fname, size, options, dbg);
+ }
+ IOStatus CreateDir(const std::string& d, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->CreateDir(d, options, dbg);
+ }
+ IOStatus CreateDirIfMissing(const std::string& d, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->CreateDirIfMissing(d, options, dbg);
+ }
+ IOStatus DeleteDir(const std::string& d, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->DeleteDir(d, options, dbg);
+ }
+ IOStatus GetFileSize(const std::string& f, const IOOptions& options,
+ uint64_t* s, IODebugContext* dbg) override {
+ return target_->GetFileSize(f, options, s, dbg);
+ }
+
+ IOStatus GetFileModificationTime(const std::string& fname,
+ const IOOptions& options,
+ uint64_t* file_mtime,
+ IODebugContext* dbg) override {
+ return target_->GetFileModificationTime(fname, options, file_mtime, dbg);
+ }
+
+ IOStatus GetAbsolutePath(const std::string& db_path, const IOOptions& options,
+ std::string* output_path,
+ IODebugContext* dbg) override {
+ return target_->GetAbsolutePath(db_path, options, output_path, dbg);
+ }
+
+ IOStatus RenameFile(const std::string& s, const std::string& t,
+ const IOOptions& options, IODebugContext* dbg) override {
+ return target_->RenameFile(s, t, options, dbg);
+ }
+
+ IOStatus LinkFile(const std::string& s, const std::string& t,
+ const IOOptions& options, IODebugContext* dbg) override {
+ return target_->LinkFile(s, t, options, dbg);
+ }
+
+ IOStatus NumFileLinks(const std::string& fname, const IOOptions& options,
+ uint64_t* count, IODebugContext* dbg) override {
+ return target_->NumFileLinks(fname, options, count, dbg);
+ }
+
+ IOStatus AreFilesSame(const std::string& first, const std::string& second,
+ const IOOptions& options, bool* res,
+ IODebugContext* dbg) override {
+ return target_->AreFilesSame(first, second, options, res, dbg);
+ }
+
+ IOStatus LockFile(const std::string& f, const IOOptions& options,
+ FileLock** l, IODebugContext* dbg) override {
+ return target_->LockFile(f, options, l, dbg);
+ }
+
+ IOStatus UnlockFile(FileLock* l, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->UnlockFile(l, options, dbg);
+ }
+
+ IOStatus GetTestDirectory(const IOOptions& options, std::string* path,
+ IODebugContext* dbg) override {
+ return target_->GetTestDirectory(options, path, dbg);
+ }
+ IOStatus NewLogger(const std::string& fname, const IOOptions& options,
+ std::shared_ptr<Logger>* result,
+ IODebugContext* dbg) override {
+ return target_->NewLogger(fname, options, result, dbg);
+ }
+
+ FileOptions OptimizeForLogRead(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForLogRead(file_options);
+ }
+ FileOptions OptimizeForManifestRead(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForManifestRead(file_options);
+ }
+ FileOptions OptimizeForLogWrite(const FileOptions& file_options,
+ const DBOptions& db_options) const override {
+ return target_->OptimizeForLogWrite(file_options, db_options);
+ }
+ FileOptions OptimizeForManifestWrite(
+ const FileOptions& file_options) const override {
+ return target_->OptimizeForManifestWrite(file_options);
+ }
+ FileOptions OptimizeForCompactionTableWrite(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& immutable_ops) const override {
+ return target_->OptimizeForCompactionTableWrite(file_options,
+ immutable_ops);
+ }
+ FileOptions OptimizeForCompactionTableRead(
+ const FileOptions& file_options,
+ const ImmutableDBOptions& db_options) const override {
+ return target_->OptimizeForCompactionTableRead(file_options, db_options);
+ }
+ IOStatus GetFreeSpace(const std::string& path, const IOOptions& options,
+ uint64_t* diskfree, IODebugContext* dbg) override {
+ return target_->GetFreeSpace(path, options, diskfree, dbg);
+ }
+
+ private:
+ FileSystem* target_;
+};
+
+class FSSequentialFileWrapper : public FSSequentialFile {
+ public:
+ explicit FSSequentialFileWrapper(FSSequentialFile* target)
+ : target_(target) {}
+
+ IOStatus Read(size_t n, const IOOptions& options, Slice* result,
+ char* scratch, IODebugContext* dbg) override {
+ return target_->Read(n, options, result, scratch, dbg);
+ }
+ IOStatus Skip(uint64_t n) override { return target_->Skip(n); }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+ IOStatus PositionedRead(uint64_t offset, size_t n, const IOOptions& options,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) override {
+ return target_->PositionedRead(offset, n, options, result, scratch, dbg);
+ }
+
+ private:
+ FSSequentialFile* target_;
+};
+
+class FSRandomAccessFileWrapper : public FSRandomAccessFile {
+ public:
+ explicit FSRandomAccessFileWrapper(FSRandomAccessFile* target)
+ : target_(target) {}
+
+ IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const override {
+ return target_->Read(offset, n, options, result, scratch, dbg);
+ }
+ IOStatus MultiRead(FSReadRequest* reqs, size_t num_reqs,
+ const IOOptions& options, IODebugContext* dbg) override {
+ return target_->MultiRead(reqs, num_reqs, options, dbg);
+ }
+ IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->Prefetch(offset, n, options, dbg);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ };
+ void Hint(AccessPattern pattern) override { target_->Hint(pattern); }
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ private:
+ FSRandomAccessFile* target_;
+};
+
+class FSWritableFileWrapper : public FSWritableFile {
+ public:
+ explicit FSWritableFileWrapper(FSWritableFile* t) : target_(t) {}
+
+ IOStatus Append(const Slice& data, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->Append(data, options, dbg);
+ }
+ IOStatus PositionedAppend(const Slice& data, uint64_t offset,
+ const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->PositionedAppend(data, offset, options, dbg);
+ }
+ IOStatus Truncate(uint64_t size, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->Truncate(size, options, dbg);
+ }
+ IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Close(options, dbg);
+ }
+ IOStatus Flush(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Flush(options, dbg);
+ }
+ IOStatus Sync(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Sync(options, dbg);
+ }
+ IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Fsync(options, dbg);
+ }
+ bool IsSyncThreadSafe() const override { return target_->IsSyncThreadSafe(); }
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+
+ void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override {
+ target_->SetWriteLifeTimeHint(hint);
+ }
+
+ Env::WriteLifeTimeHint GetWriteLifeTimeHint() override {
+ return target_->GetWriteLifeTimeHint();
+ }
+
+ uint64_t GetFileSize(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->GetFileSize(options, dbg);
+ }
+
+ void SetPreallocationBlockSize(size_t size) override {
+ target_->SetPreallocationBlockSize(size);
+ }
+
+ void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) override {
+ target_->GetPreallocationStatus(block_size, last_allocated_block);
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ IOStatus InvalidateCache(size_t offset, size_t length) override {
+ return target_->InvalidateCache(offset, length);
+ }
+
+ IOStatus RangeSync(uint64_t offset, uint64_t nbytes, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->RangeSync(offset, nbytes, options, dbg);
+ }
+
+ void PrepareWrite(size_t offset, size_t len, const IOOptions& options,
+ IODebugContext* dbg) override {
+ target_->PrepareWrite(offset, len, options, dbg);
+ }
+
+ IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->Allocate(offset, len, options, dbg);
+ }
+
+ private:
+ FSWritableFile* target_;
+};
+
+class FSRandomRWFileWrapper : public FSRandomRWFile {
+ public:
+ explicit FSRandomRWFileWrapper(FSRandomRWFile* target) : target_(target) {}
+
+ bool use_direct_io() const override { return target_->use_direct_io(); }
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ IOStatus Write(uint64_t offset, const Slice& data, const IOOptions& options,
+ IODebugContext* dbg) override {
+ return target_->Write(offset, data, options, dbg);
+ }
+ IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
+ Slice* result, char* scratch,
+ IODebugContext* dbg) const override {
+ return target_->Read(offset, n, options, result, scratch, dbg);
+ }
+ IOStatus Flush(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Flush(options, dbg);
+ }
+ IOStatus Sync(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Sync(options, dbg);
+ }
+ IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Fsync(options, dbg);
+ }
+ IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Close(options, dbg);
+ }
+
+ private:
+ FSRandomRWFile* target_;
+};
+
+class FSDirectoryWrapper : public FSDirectory {
+ public:
+ explicit FSDirectoryWrapper(FSDirectory* target) : target_(target) {}
+
+ IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override {
+ return target_->Fsync(options, dbg);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ return target_->GetUniqueId(id, max_size);
+ }
+
+ private:
+ FSDirectory* target_;
+};
+
+// A utility routine: read contents of named file into *data
+extern Status ReadFileToString(FileSystem* fs, const std::string& fname,
+ std::string* data);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/filter_policy.h b/storage/rocksdb/rocksdb/include/rocksdb/filter_policy.h
new file mode 100644
index 00000000..03d6471c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/filter_policy.h
@@ -0,0 +1,200 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A database can be configured with a custom FilterPolicy object.
+// This object is responsible for creating a small filter from a set
+// of keys. These 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.
+//
+// Most people will want to use the builtin bloom filter support (see
+// NewBloomFilterPolicy() below).
+
+#pragma once
+
+#include <stdlib.h>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "rocksdb/advanced_options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+struct BlockBasedTableOptions;
+
+// A class that takes a bunch of keys, then generates filter
+class FilterBitsBuilder {
+ public:
+ virtual ~FilterBitsBuilder() {}
+
+ // Add Key to filter, you could use any way to store the key.
+ // Such as: storing hashes or original keys
+ // Keys are in sorted order and duplicated keys are possible.
+ virtual void AddKey(const Slice& key) = 0;
+
+ // Generate the filter using the keys that are added
+ // The return value of this function would be the filter bits,
+ // The ownership of actual data is set to buf
+ virtual Slice Finish(std::unique_ptr<const char[]>* buf) = 0;
+
+ // Calculate num of keys that can be added and generate a filter
+ // <= the specified number of bytes.
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4702) // unreachable code
+#endif
+ virtual int CalculateNumEntry(const uint32_t /*bytes*/) {
+#ifndef ROCKSDB_LITE
+ throw std::runtime_error("CalculateNumEntry not Implemented");
+#else
+ abort();
+#endif
+ return 0;
+ }
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+};
+
+// A class that checks if a key can be in filter
+// It should be initialized by Slice generated by BitsBuilder
+class FilterBitsReader {
+ public:
+ virtual ~FilterBitsReader() {}
+
+ // Check if the entry match the bits in filter
+ virtual bool MayMatch(const Slice& entry) = 0;
+
+ // Check if an array of entries match the bits in filter
+ virtual void MayMatch(int num_keys, Slice** keys, bool* may_match) {
+ for (int i = 0; i < num_keys; ++i) {
+ may_match[i] = MayMatch(*keys[i]);
+ }
+ }
+};
+
+// Contextual information passed to BloomFilterPolicy at filter building time.
+// Used in overriding FilterPolicy::GetBuilderWithContext(). References other
+// structs because this is expected to be a temporary, stack-allocated object.
+struct FilterBuildingContext {
+ // This constructor is for internal use only and subject to change.
+ FilterBuildingContext(const BlockBasedTableOptions& table_options);
+
+ // Options for the table being built
+ const BlockBasedTableOptions& table_options;
+
+ // Name of the column family for the table (or empty string if unknown)
+ std::string column_family_name;
+
+ // The compactions style in effect for the table
+ CompactionStyle compaction_style = kCompactionStyleLevel;
+
+ // The table level at time of constructing the SST file, or -1 if unknown.
+ // (The table file could later be used at a different level.)
+ int level_at_creation = -1;
+
+ // An optional logger for reporting errors, warnings, etc.
+ Logger* info_log = nullptr;
+};
+
+// We add a new format of filter block called full filter block
+// This new interface gives you more space of customization
+//
+// For the full filter block, you can plug in your version by implement
+// the FilterBitsBuilder and FilterBitsReader
+//
+// There are two sets of interface in FilterPolicy
+// Set 1: CreateFilter, KeyMayMatch: used for blockbased filter
+// Set 2: GetFilterBitsBuilder, GetFilterBitsReader, they are used for
+// full filter.
+// Set 1 MUST be implemented correctly, Set 2 is optional
+// RocksDB would first try using functions in Set 2. if they return nullptr,
+// it would use Set 1 instead.
+// You can choose filter type in NewBloomFilterPolicy
+class FilterPolicy {
+ public:
+ virtual ~FilterPolicy();
+
+ // Return the name of this policy. Note that if the filter encoding
+ // changes in an incompatible way, the name returned by this method
+ // must be changed. Otherwise, old incompatible filters may be
+ // passed to methods of this type.
+ virtual const char* Name() const = 0;
+
+ // keys[0,n-1] contains a list of keys (potentially with duplicates)
+ // that are ordered according to the user supplied comparator.
+ // Append a filter that summarizes keys[0,n-1] to *dst.
+ //
+ // Warning: do not change the initial contents of *dst. Instead,
+ // append the newly constructed filter to *dst.
+ virtual void CreateFilter(const Slice* keys, int n,
+ std::string* dst) const = 0;
+
+ // "filter" contains the data appended by a preceding call to
+ // CreateFilter() on this class. This method must return true if
+ // the key was in the list of keys passed to CreateFilter().
+ // This method may return true or false if the key was not on the
+ // list, but it should aim to return false with a high probability.
+ virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0;
+
+ // Return a new FilterBitsBuilder for full or partitioned filter blocks, or
+ // nullptr if using block-based filter.
+ // NOTE: This function is only called by GetBuilderWithContext() below for
+ // custom FilterPolicy implementations. Thus, it is not necessary to
+ // override this function if overriding GetBuilderWithContext().
+ virtual FilterBitsBuilder* GetFilterBitsBuilder() const { return nullptr; }
+
+ // A newer variant of GetFilterBitsBuilder that allows a FilterPolicy
+ // to customize the builder for contextual constraints and hints.
+ // (Name changed to avoid triggering -Werror=overloaded-virtual.)
+ // If overriding GetFilterBitsBuilder() suffices, it is not necessary to
+ // override this function.
+ virtual FilterBitsBuilder* GetBuilderWithContext(
+ const FilterBuildingContext&) const {
+ return GetFilterBitsBuilder();
+ }
+
+ // Return a new FilterBitsReader for full or partitioned filter blocks, or
+ // nullptr if using block-based filter.
+ // As here, the input slice should NOT be deleted by FilterPolicy.
+ virtual FilterBitsReader* GetFilterBitsReader(
+ const Slice& /*contents*/) const {
+ return nullptr;
+ }
+};
+
+// Return a new filter policy that uses a bloom filter with approximately
+// the specified number of bits per key.
+//
+// bits_per_key: average bits allocated per key in bloom filter. A good
+// choice is 9.9, which yields a filter with ~ 1% false positive rate.
+// When format_version < 5, the value will be rounded to the nearest
+// integer. Recommend using no more than three decimal digits after the
+// decimal point, as in 6.667.
+//
+// use_block_based_builder: use deprecated block based filter (true) rather
+// than full or partitioned filter (false).
+//
+// Callers must delete the result after any database that is using the
+// result has been closed.
+//
+// Note: if you are using a custom comparator that ignores some parts
+// of the keys being compared, you must not use NewBloomFilterPolicy()
+// 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 NewBloomFilterPolicy) that does not ignore
+// trailing spaces in keys.
+extern const FilterPolicy* NewBloomFilterPolicy(
+ double bits_per_key, bool use_block_based_builder = false);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/flush_block_policy.h b/storage/rocksdb/rocksdb/include/rocksdb/flush_block_policy.h
new file mode 100644
index 00000000..badc0808
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/flush_block_policy.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class BlockBuilder;
+struct Options;
+
+// FlushBlockPolicy provides a configurable way to determine when to flush a
+// block in the block based tables,
+class FlushBlockPolicy {
+ public:
+ // Keep track of the key/value sequences and return the boolean value to
+ // determine if table builder should flush current data block.
+ virtual bool Update(const Slice& key, const Slice& value) = 0;
+
+ virtual ~FlushBlockPolicy() {}
+};
+
+class FlushBlockPolicyFactory {
+ public:
+ // Return the name of the flush block policy.
+ virtual const char* Name() const = 0;
+
+ // Return a new block flush policy that flushes data blocks by data size.
+ // FlushBlockPolicy may need to access the metadata of the data block
+ // builder to determine when to flush the blocks.
+ //
+ // Callers must delete the result after any database that is using the
+ // result has been closed.
+ virtual FlushBlockPolicy* NewFlushBlockPolicy(
+ const BlockBasedTableOptions& table_options,
+ const BlockBuilder& data_block_builder) const = 0;
+
+ virtual ~FlushBlockPolicyFactory() {}
+};
+
+class FlushBlockBySizePolicyFactory : public FlushBlockPolicyFactory {
+ public:
+ FlushBlockBySizePolicyFactory() {}
+
+ const char* Name() const override { return "FlushBlockBySizePolicyFactory"; }
+
+ FlushBlockPolicy* NewFlushBlockPolicy(
+ const BlockBasedTableOptions& table_options,
+ const BlockBuilder& data_block_builder) const override;
+
+ static FlushBlockPolicy* NewFlushBlockPolicy(
+ const uint64_t size, const int deviation,
+ const BlockBuilder& data_block_builder);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/io_status.h b/storage/rocksdb/rocksdb/include/rocksdb/io_status.h
new file mode 100644
index 00000000..a1de859a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/io_status.h
@@ -0,0 +1,232 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 IOStatus encapsulates the result of an operation. It may indicate
+// success, or it may indicate an error with an associated error message.
+//
+// Multiple threads can invoke const methods on an IOStatus without
+// external synchronization, but if any of the threads may call a
+// non-const method, all threads accessing the same IOStatus must use
+// external synchronization.
+
+#pragma once
+
+#include <string>
+#include "rocksdb/slice.h"
+#ifdef OS_WIN
+#include <string.h>
+#endif
+#include <cstring>
+#include "status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class IOStatus : public Status {
+ public:
+ using Code = Status::Code;
+ using SubCode = Status::SubCode;
+
+ enum IOErrorScope {
+ kIOErrorScopeFileSystem,
+ kIOErrorScopeFile,
+ kIOErrorScopeRange,
+ kIOErrorScopeMax,
+ };
+
+ // Create a success status.
+ IOStatus() : IOStatus(kOk, kNone) {}
+ ~IOStatus() {}
+
+ // Copy the specified status.
+ IOStatus(const IOStatus& s);
+ IOStatus& operator=(const IOStatus& s);
+ IOStatus(IOStatus&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ ;
+ IOStatus& operator=(IOStatus&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ ;
+ bool operator==(const IOStatus& rhs) const;
+ bool operator!=(const IOStatus& rhs) const;
+
+ void SetRetryable(bool retryable) { retryable_ = retryable; }
+ void SetDataLoss(bool data_loss) { data_loss_ = data_loss; }
+ void SetScope(IOErrorScope scope) { scope_ = scope; }
+
+ bool GetRetryable() const { return retryable_; }
+ bool GetDataLoss() const { return data_loss_; }
+ IOErrorScope GetScope() const { return scope_; }
+
+ // Return a success status.
+ static IOStatus OK() { return IOStatus(); }
+
+ static IOStatus NotSupported(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kNotSupported, msg, msg2);
+ }
+ static IOStatus NotSupported(SubCode msg = kNone) {
+ return IOStatus(kNotSupported, msg);
+ }
+
+ // Return error status of an appropriate type.
+ static IOStatus NotFound(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kNotFound, msg, msg2);
+ }
+ // Fast path for not found without malloc;
+ static IOStatus NotFound(SubCode msg = kNone) {
+ return IOStatus(kNotFound, msg);
+ }
+
+ static IOStatus Corruption(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kCorruption, msg, msg2);
+ }
+ static IOStatus Corruption(SubCode msg = kNone) {
+ return IOStatus(kCorruption, msg);
+ }
+
+ static IOStatus InvalidArgument(const Slice& msg,
+ const Slice& msg2 = Slice()) {
+ return IOStatus(kInvalidArgument, msg, msg2);
+ }
+ static IOStatus InvalidArgument(SubCode msg = kNone) {
+ return IOStatus(kInvalidArgument, msg);
+ }
+
+ static IOStatus IOError(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kIOError, msg, msg2);
+ }
+ static IOStatus IOError(SubCode msg = kNone) {
+ return IOStatus(kIOError, msg);
+ }
+
+ static IOStatus Busy(SubCode msg = kNone) { return IOStatus(kBusy, msg); }
+ static IOStatus Busy(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kBusy, msg, msg2);
+ }
+
+ static IOStatus TimedOut(SubCode msg = kNone) {
+ return IOStatus(kTimedOut, msg);
+ }
+ static IOStatus TimedOut(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kTimedOut, msg, msg2);
+ }
+
+ static IOStatus NoSpace() { return IOStatus(kIOError, kNoSpace); }
+ static IOStatus NoSpace(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kIOError, kNoSpace, msg, msg2);
+ }
+
+ static IOStatus PathNotFound() { return IOStatus(kIOError, kPathNotFound); }
+ static IOStatus PathNotFound(const Slice& msg, const Slice& msg2 = Slice()) {
+ return IOStatus(kIOError, kPathNotFound, msg, msg2);
+ }
+
+ // Return a string representation of this status suitable for printing.
+ // Returns the string "OK" for success.
+ // std::string ToString() const;
+
+ private:
+ friend IOStatus status_to_io_status(Status&&);
+ bool retryable_;
+ bool data_loss_;
+ IOErrorScope scope_;
+
+ explicit IOStatus(Code _code, SubCode _subcode = kNone)
+ : Status(_code, _subcode),
+ retryable_(false),
+ data_loss_(false),
+ scope_(kIOErrorScopeFileSystem) {}
+
+ IOStatus(Code _code, SubCode _subcode, const Slice& msg, const Slice& msg2);
+ IOStatus(Code _code, const Slice& msg, const Slice& msg2)
+ : IOStatus(_code, kNone, msg, msg2) {}
+};
+
+inline IOStatus::IOStatus(Code _code, SubCode _subcode, const Slice& msg,
+ const Slice& msg2)
+ : Status(_code, _subcode),
+ retryable_(false),
+ data_loss_(false),
+ scope_(kIOErrorScopeFileSystem) {
+ assert(code_ != kOk);
+ assert(subcode_ != kMaxSubCode);
+ const size_t len1 = msg.size();
+ const size_t len2 = msg2.size();
+ const size_t size = len1 + (len2 ? (2 + len2) : 0);
+ char* const result = new char[size + 1]; // +1 for null terminator
+ memcpy(result, msg.data(), len1);
+ if (len2) {
+ result[len1] = ':';
+ result[len1 + 1] = ' ';
+ memcpy(result + len1 + 2, msg2.data(), len2);
+ }
+ result[size] = '\0'; // null terminator for C style string
+ state_ = result;
+}
+
+inline IOStatus::IOStatus(const IOStatus& s) : Status(s.code_, s.subcode_) {
+ retryable_ = s.retryable_;
+ data_loss_ = s.data_loss_;
+ scope_ = s.scope_;
+ state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
+}
+inline IOStatus& IOStatus::operator=(const IOStatus& s) {
+ // The following condition catches both aliasing (when this == &s),
+ // and the common case where both s and *this are ok.
+ if (this != &s) {
+ code_ = s.code_;
+ subcode_ = s.subcode_;
+ retryable_ = s.retryable_;
+ data_loss_ = s.data_loss_;
+ scope_ = s.scope_;
+ delete[] state_;
+ state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
+ }
+ return *this;
+}
+
+inline IOStatus::IOStatus(IOStatus&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ : IOStatus() {
+ *this = std::move(s);
+}
+
+inline IOStatus& IOStatus::operator=(IOStatus&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+{
+ if (this != &s) {
+ code_ = std::move(s.code_);
+ s.code_ = kOk;
+ subcode_ = std::move(s.subcode_);
+ s.subcode_ = kNone;
+ retryable_ = s.retryable_;
+ retryable_ = false;
+ data_loss_ = s.data_loss_;
+ data_loss_ = false;
+ scope_ = s.scope_;
+ scope_ = kIOErrorScopeFileSystem;
+ delete[] state_;
+ state_ = nullptr;
+ std::swap(state_, s.state_);
+ }
+ return *this;
+}
+
+inline bool IOStatus::operator==(const IOStatus& rhs) const {
+ return (code_ == rhs.code_);
+}
+
+inline bool IOStatus::operator!=(const IOStatus& rhs) const {
+ return !(*this == rhs);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/iostats_context.h b/storage/rocksdb/rocksdb/include/rocksdb/iostats_context.h
new file mode 100644
index 00000000..b31b6d70
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/iostats_context.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+
+#include "rocksdb/perf_level.h"
+
+// A thread local context for gathering io-stats efficiently and transparently.
+// Use SetPerfLevel(PerfLevel::kEnableTime) to enable time stats.
+
+namespace ROCKSDB_NAMESPACE {
+
+struct IOStatsContext {
+ // reset all io-stats counter to zero
+ void Reset();
+
+ std::string ToString(bool exclude_zero_counters = false) const;
+
+ // the thread pool id
+ uint64_t thread_pool_id;
+
+ // number of bytes that has been written.
+ uint64_t bytes_written;
+ // number of bytes that has been read.
+ uint64_t bytes_read;
+
+ // time spent in open() and fopen().
+ uint64_t open_nanos;
+ // time spent in fallocate().
+ uint64_t allocate_nanos;
+ // time spent in write() and pwrite().
+ uint64_t write_nanos;
+ // time spent in read() and pread()
+ uint64_t read_nanos;
+ // time spent in sync_file_range().
+ uint64_t range_sync_nanos;
+ // time spent in fsync
+ uint64_t fsync_nanos;
+ // time spent in preparing write (fallocate etc).
+ uint64_t prepare_write_nanos;
+ // time spent in Logger::Logv().
+ uint64_t logger_nanos;
+ // CPU time spent in write() and pwrite()
+ uint64_t cpu_write_nanos;
+ // CPU time spent in read() and pread()
+ uint64_t cpu_read_nanos;
+};
+
+// Get Thread-local IOStatsContext object pointer
+IOStatsContext* get_iostats_context();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/iterator.h b/storage/rocksdb/rocksdb/include/rocksdb/iterator.h
new file mode 100644
index 00000000..2f8f1e38
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/iterator.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// An iterator yields a sequence of key/value pairs from a source.
+// The following class defines the interface. 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 Iterator without
+// external synchronization, but if any of the threads may call a
+// non-const method, all threads accessing the same Iterator must use
+// external synchronization.
+
+#pragma once
+
+#include <string>
+#include "rocksdb/cleanable.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Iterator : public Cleanable {
+ public:
+ Iterator() {}
+ // No copying allowed
+ Iterator(const Iterator&) = delete;
+ void operator=(const Iterator&) = delete;
+
+ virtual ~Iterator() {}
+
+ // An iterator is either positioned at a key/value pair, or
+ // not valid. This method returns true iff the iterator is valid.
+ // Always returns false if !status().ok().
+ virtual bool Valid() const = 0;
+
+ // Position at the first key in the source. The iterator is Valid()
+ // after this call iff the source is not empty.
+ virtual void SeekToFirst() = 0;
+
+ // Position at the last key in the source. The iterator is
+ // Valid() after this call iff the source is not empty.
+ virtual void SeekToLast() = 0;
+
+ // Position at the first key in the source that at or past target.
+ // The iterator is Valid() after this call iff the source contains
+ // an entry that comes at or past target.
+ // All Seek*() methods clear any error status() that the iterator had prior to
+ // the call; after the seek, status() indicates only the error (if any) that
+ // happened during the seek, not any past errors.
+ virtual void Seek(const Slice& target) = 0;
+
+ // Position at the last key in the source that at or before target.
+ // The iterator is Valid() after this call iff the source contains
+ // an entry that comes at or before target.
+ virtual void SeekForPrev(const Slice& target) = 0;
+
+ // Moves to the next entry in the source. After this call, Valid() is
+ // true iff the iterator was not positioned at the last entry in the source.
+ // REQUIRES: Valid()
+ virtual void Next() = 0;
+
+ // Moves to the previous entry in the source. After this call, Valid() is
+ // true iff the iterator was not positioned at the first entry in source.
+ // REQUIRES: Valid()
+ virtual void Prev() = 0;
+
+ // 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: Valid()
+ virtual Slice key() const = 0;
+
+ // 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: Valid()
+ virtual Slice value() const = 0;
+
+ // 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().
+ virtual Status status() const = 0;
+
+ // If supported, renew the iterator to represent the latest state. The
+ // iterator will be invalidated after the call. Not supported if
+ // ReadOptions.snapshot is given when creating the iterator.
+ virtual Status Refresh() {
+ return Status::NotSupported("Refresh() is not supported");
+ }
+
+ // Property "rocksdb.iterator.is-key-pinned":
+ // If returning "1", this means that the Slice returned by key() is valid
+ // as long as the iterator is not deleted.
+ // It is guaranteed to always return "1" if
+ // - Iterator created with ReadOptions::pin_data = true
+ // - DB tables were created with
+ // BlockBasedTableOptions::use_delta_encoding = false.
+ // Property "rocksdb.iterator.super-version-number":
+ // LSM version used by the iterator. The same format as DB Property
+ // kCurrentSuperVersionNumber. See its comment for more information.
+ // Property "rocksdb.iterator.internal-key":
+ // Get the user-key portion of the internal key at which the iteration
+ // stopped.
+ virtual Status GetProperty(std::string prop_name, std::string* prop);
+};
+
+// Return an empty iterator (yields nothing).
+extern Iterator* NewEmptyIterator();
+
+// Return an empty iterator with the specified status.
+extern Iterator* NewErrorIterator(const Status& status);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/ldb_tool.h b/storage/rocksdb/rocksdb/include/rocksdb/ldb_tool.h
new file mode 100644
index 00000000..22ea7734
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/ldb_tool.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include <string>
+#include <vector>
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// An interface for converting a slice to a readable string
+class SliceFormatter {
+ public:
+ virtual ~SliceFormatter() {}
+ virtual std::string Format(const Slice& s) const = 0;
+};
+
+// Options for customizing ldb tool (beyond the DB Options)
+struct LDBOptions {
+ // Create LDBOptions with default values for all fields
+ LDBOptions();
+
+ // Key formatter that converts a slice to a readable string.
+ // Default: Slice::ToString()
+ std::shared_ptr<SliceFormatter> key_formatter;
+
+ std::string print_help_header = "ldb - RocksDB Tool";
+};
+
+class LDBTool {
+ public:
+ void Run(
+ int argc, char** argv, Options db_options = Options(),
+ const LDBOptions& ldb_options = LDBOptions(),
+ const std::vector<ColumnFamilyDescriptor>* column_families = nullptr);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/listener.h b/storage/rocksdb/rocksdb/include/rocksdb/listener.h
new file mode 100644
index 00000000..d1c953f0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/listener.h
@@ -0,0 +1,491 @@
+// Copyright (c) 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.
+//
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "rocksdb/compaction_job_stats.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table_properties.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+typedef std::unordered_map<std::string, std::shared_ptr<const TableProperties>>
+ TablePropertiesCollection;
+
+class DB;
+class ColumnFamilyHandle;
+class Status;
+struct CompactionJobStats;
+enum CompressionType : unsigned char;
+
+enum class TableFileCreationReason {
+ kFlush,
+ kCompaction,
+ kRecovery,
+ kMisc,
+};
+
+struct TableFileCreationBriefInfo {
+ // the name of the database where the file was created
+ std::string db_name;
+ // the name of the column family where the file was created.
+ std::string cf_name;
+ // the path to the created file.
+ std::string file_path;
+ // the id of the job (which could be flush or compaction) that
+ // created the file.
+ int job_id;
+ // reason of creating the table.
+ TableFileCreationReason reason;
+};
+
+struct TableFileCreationInfo : public TableFileCreationBriefInfo {
+ TableFileCreationInfo() = default;
+ explicit TableFileCreationInfo(TableProperties&& prop)
+ : table_properties(prop) {}
+ // the size of the file.
+ uint64_t file_size;
+ // Detailed properties of the created file.
+ TableProperties table_properties;
+ // The status indicating whether the creation was successful or not.
+ Status status;
+};
+
+enum class CompactionReason : int {
+ kUnknown = 0,
+ // [Level] number of L0 files > level0_file_num_compaction_trigger
+ kLevelL0FilesNum,
+ // [Level] total size of level > MaxBytesForLevel()
+ kLevelMaxLevelSize,
+ // [Universal] Compacting for size amplification
+ kUniversalSizeAmplification,
+ // [Universal] Compacting for size ratio
+ kUniversalSizeRatio,
+ // [Universal] number of sorted runs > level0_file_num_compaction_trigger
+ kUniversalSortedRunNum,
+ // [FIFO] total size > max_table_files_size
+ kFIFOMaxSize,
+ // [FIFO] reduce number of files.
+ kFIFOReduceNumFiles,
+ // [FIFO] files with creation time < (current_time - interval)
+ kFIFOTtl,
+ // Manual compaction
+ kManualCompaction,
+ // DB::SuggestCompactRange() marked files for compaction
+ kFilesMarkedForCompaction,
+ // [Level] Automatic compaction within bottommost level to cleanup duplicate
+ // versions of same user key, usually due to a released snapshot.
+ kBottommostFiles,
+ // Compaction based on TTL
+ kTtl,
+ // According to the comments in flush_job.cc, RocksDB treats flush as
+ // a level 0 compaction in internal stats.
+ kFlush,
+ // Compaction caused by external sst file ingestion
+ kExternalSstIngestion,
+ // Compaction due to SST file being too old
+ kPeriodicCompaction,
+ // total number of compaction reasons, new reasons must be added above this.
+ kNumOfReasons,
+};
+
+enum class FlushReason : int {
+ kOthers = 0x00,
+ kGetLiveFiles = 0x01,
+ kShutDown = 0x02,
+ kExternalFileIngestion = 0x03,
+ kManualCompaction = 0x04,
+ kWriteBufferManager = 0x05,
+ kWriteBufferFull = 0x06,
+ kTest = 0x07,
+ kDeleteFiles = 0x08,
+ kAutoCompaction = 0x09,
+ kManualFlush = 0x0a,
+ kErrorRecovery = 0xb,
+};
+
+enum class BackgroundErrorReason {
+ kFlush,
+ kCompaction,
+ kWriteCallback,
+ kMemTable,
+};
+
+enum class WriteStallCondition {
+ kNormal,
+ kDelayed,
+ kStopped,
+};
+
+struct WriteStallInfo {
+ // the name of the column family
+ std::string cf_name;
+ // state of the write controller
+ struct {
+ WriteStallCondition cur;
+ WriteStallCondition prev;
+ } condition;
+};
+
+#ifndef ROCKSDB_LITE
+
+struct TableFileDeletionInfo {
+ // The name of the database where the file was deleted.
+ std::string db_name;
+ // The path to the deleted file.
+ std::string file_path;
+ // The id of the job which deleted the file.
+ int job_id;
+ // The status indicating whether the deletion was successful or not.
+ Status status;
+};
+
+struct FileOperationInfo {
+ using TimePoint = std::chrono::time_point<std::chrono::system_clock,
+ std::chrono::nanoseconds>;
+
+ const std::string& path;
+ uint64_t offset;
+ size_t length;
+ const TimePoint& start_timestamp;
+ const TimePoint& finish_timestamp;
+ Status status;
+ FileOperationInfo(const std::string& _path, const TimePoint& start,
+ const TimePoint& finish)
+ : path(_path), start_timestamp(start), finish_timestamp(finish) {}
+};
+
+struct FlushJobInfo {
+ // the id of the column family
+ uint32_t cf_id;
+ // the name of the column family
+ std::string cf_name;
+ // the path to the newly created file
+ std::string file_path;
+ // the file number of the newly created file
+ uint64_t file_number;
+ // the oldest blob file referenced by the newly created file
+ uint64_t oldest_blob_file_number;
+ // the id of the thread that completed this flush job.
+ uint64_t thread_id;
+ // the job id, which is unique in the same thread.
+ int job_id;
+ // If true, then 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.
+ bool triggered_writes_slowdown;
+ // If true, then 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.
+ bool triggered_writes_stop;
+ // The smallest sequence number in the newly created file
+ SequenceNumber smallest_seqno;
+ // The largest sequence number in the newly created file
+ SequenceNumber largest_seqno;
+ // Table properties of the table being flushed
+ TableProperties table_properties;
+
+ FlushReason flush_reason;
+};
+
+struct CompactionFileInfo {
+ // The level of the file.
+ int level;
+
+ // The file number of the file.
+ uint64_t file_number;
+
+ // The file number of the oldest blob file this SST file references.
+ uint64_t oldest_blob_file_number;
+};
+
+struct CompactionJobInfo {
+ // the id of the column family where the compaction happened.
+ uint32_t cf_id;
+ // the name of the column family where the compaction happened.
+ std::string cf_name;
+ // the status indicating whether the compaction was successful or not.
+ Status status;
+ // the id of the thread that completed this compaction job.
+ uint64_t thread_id;
+ // the job id, which is unique in the same thread.
+ int job_id;
+ // the smallest input level of the compaction.
+ int base_input_level;
+ // the output level of the compaction.
+ int output_level;
+
+ // The following variables contain information about compaction inputs
+ // and outputs. A file may appear in both the input and output lists
+ // if it was simply moved to a different level. The order of elements
+ // is the same across input_files and input_file_infos; similarly, it is
+ // the same across output_files and output_file_infos.
+
+ // The names of the compaction input files.
+ std::vector<std::string> input_files;
+
+ // Additional information about the compaction input files.
+ std::vector<CompactionFileInfo> input_file_infos;
+
+ // The names of the compaction output files.
+ std::vector<std::string> output_files;
+
+ // Additional information about the compaction output files.
+ std::vector<CompactionFileInfo> output_file_infos;
+
+ // Table properties for input and output tables.
+ // The map is keyed by values from input_files and output_files.
+ TablePropertiesCollection table_properties;
+
+ // Reason to run the compaction
+ CompactionReason compaction_reason;
+
+ // Compression algorithm used for output files
+ CompressionType compression;
+
+ // If non-null, this variable stores detailed information
+ // about this compaction.
+ CompactionJobStats stats;
+};
+
+struct MemTableInfo {
+ // the name of the column family to which memtable belongs
+ std::string cf_name;
+ // Sequence number of the first element that was inserted
+ // into the memtable.
+ SequenceNumber first_seqno;
+ // 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.
+ SequenceNumber earliest_seqno;
+ // Total number of entries in memtable
+ uint64_t num_entries;
+ // Total number of deletes in memtable
+ uint64_t num_deletes;
+};
+
+struct ExternalFileIngestionInfo {
+ // the name of the column family
+ std::string cf_name;
+ // Path of the file outside the DB
+ std::string external_file_path;
+ // Path of the file inside the DB
+ std::string internal_file_path;
+ // The global sequence number assigned to keys in this file
+ SequenceNumber global_seqno;
+ // Table properties of the table being flushed
+ TableProperties table_properties;
+};
+
+// 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 DB::CompactFiles() (as it may
+// run for a long while) or issue many of DB::Put() (as Put may be blocked
+// in certain cases) in the same thread in the EventListener callback.
+// However, doing DB::CompactFiles() and DB::Put() 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 EventListener::OnFlushCompleted().
+//
+// [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.
+class EventListener {
+ public:
+ // A callback function to RocksDB which will be called whenever a
+ // registered RocksDB flushes a file. 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.
+ virtual void OnFlushCompleted(DB* /*db*/,
+ const FlushJobInfo& /*flush_job_info*/) {}
+
+ // A callback function to RocksDB which will be called before a
+ // RocksDB starts to flush memtables. 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.
+ virtual void OnFlushBegin(DB* /*db*/,
+ const FlushJobInfo& /*flush_job_info*/) {}
+
+ // A callback function for RocksDB which will be called whenever
+ // a SST file is deleted. 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.
+ //
+ // Note that if applications would like to use the passed reference
+ // outside this function call, they should make copies from the
+ // returned value.
+ virtual void OnTableFileDeleted(const TableFileDeletionInfo& /*info*/) {}
+
+ // 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.
+ virtual void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& /*ci*/) {}
+
+ // 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 ci a reference to a CompactionJobInfo struct. 'ci' is released
+ // after this function is returned, and must be copied if it is needed
+ // outside of this function.
+ virtual void OnCompactionCompleted(DB* /*db*/,
+ const CompactionJobInfo& /*ci*/) {}
+
+ // 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.
+ virtual void OnTableFileCreated(const TableFileCreationInfo& /*info*/) {}
+
+ // 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.
+ virtual void OnTableFileCreationStarted(
+ const TableFileCreationBriefInfo& /*info*/) {}
+
+ // 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.
+ virtual void OnMemTableSealed(const MemTableInfo& /*info*/) {}
+
+ // 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 handle is a pointer to the column family handle to be deleted
+ // which will become a dangling pointer after the deletion.
+ virtual void OnColumnFamilyHandleDeletionStarted(
+ ColumnFamilyHandle* /*handle*/) {}
+
+ // 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.
+ virtual void OnExternalFileIngested(
+ DB* /*db*/, const ExternalFileIngestionInfo& /*info*/) {}
+
+ // 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.
+ virtual void OnBackgroundError(BackgroundErrorReason /* reason */,
+ Status* /* bg_error */) {}
+
+ // 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.
+ virtual void OnStallConditionsChanged(const WriteStallInfo& /*info*/) {}
+
+ // A callback function for RocksDB which will be called whenever a file read
+ // operation finishes.
+ virtual void OnFileReadFinish(const FileOperationInfo& /* info */) {}
+
+ // A callback function for RocksDB which will be called whenever a file write
+ // operation finishes.
+ virtual void OnFileWriteFinish(const FileOperationInfo& /* info */) {}
+
+ // If true, the OnFileReadFinish and OnFileWriteFinish will be called. If
+ // false, then they won't be called.
+ virtual bool ShouldBeNotifiedOnFileIO() { return false; }
+
+ // 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 *auto_recovery to false. The database will then
+ // have to be transitioned out of read-only mode by calling DB::Resume()
+ virtual void OnErrorRecoveryBegin(BackgroundErrorReason /* reason */,
+ Status /* bg_error */,
+ bool* /* auto_recovery */) {}
+
+ // 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
+ virtual void OnErrorRecoveryCompleted(Status /* old_bg_error */) {}
+
+ virtual ~EventListener() {}
+};
+
+#else
+
+class EventListener {};
+struct FlushJobInfo {};
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/memory_allocator.h b/storage/rocksdb/rocksdb/include/rocksdb/memory_allocator.h
new file mode 100644
index 00000000..60256a97
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/memory_allocator.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+
+#include <memory>
+
+namespace ROCKSDB_NAMESPACE {
+
+// MemoryAllocator is an interface that a client can implement to supply custom
+// memory allocation and deallocation methods. See rocksdb/cache.h for more
+// information.
+// All methods should be thread-safe.
+class MemoryAllocator {
+ public:
+ virtual ~MemoryAllocator() = default;
+
+ // Name of the cache allocator, printed in the log
+ virtual const char* Name() const = 0;
+
+ // Allocate a block of at least size. Has to be thread-safe.
+ virtual void* Allocate(size_t size) = 0;
+
+ // Deallocate previously allocated block. Has to be thread-safe.
+ virtual void Deallocate(void* p) = 0;
+
+ // Returns the memory size of the block allocated at p. The default
+ // implementation that just returns the original allocation_size is fine.
+ virtual size_t UsableSize(void* /*p*/, size_t allocation_size) const {
+ // default implementation just returns the allocation size
+ return allocation_size;
+ }
+};
+
+struct JemallocAllocatorOptions {
+ // Jemalloc tcache cache allocations by size class. For each size class,
+ // it caches between 20 (for large size classes) to 200 (for small size
+ // classes). To reduce tcache memory usage in case the allocator is access
+ // by large number of threads, we can control whether to cache an allocation
+ // by its size.
+ bool limit_tcache_size = false;
+
+ // Lower bound of allocation size to use tcache, if limit_tcache_size=true.
+ // When used with block cache, it is recommneded to set it to block_size/4.
+ size_t tcache_size_lower_bound = 1024;
+
+ // Upper bound of allocation size to use tcache, if limit_tcache_size=true.
+ // When used with block cache, it is recommneded to set it to block_size.
+ size_t tcache_size_upper_bound = 16 * 1024;
+};
+
+// Generate memory allocators which allocates through Jemalloc and utilize
+// MADV_DONTDUMP through madvice to exclude cache items from core dump.
+// Applications can use the allocator with block cache to exclude block cache
+// usage from core dump.
+//
+// Implementation details:
+// The JemallocNodumpAllocator creates a delicated jemalloc arena, and all
+// allocations of the JemallocNodumpAllocator is through the same arena.
+// The memory allocator hooks memory allocation of the arena, and call
+// madvice() with MADV_DONTDUMP flag to exclude the piece of memory from
+// core dump. Side benefit of using single arena would be reduce of jemalloc
+// metadata for some workload.
+//
+// To mitigate mutex contention for using one single arena, jemalloc tcache
+// (thread-local cache) is enabled to cache unused allocations for future use.
+// The tcache normally incur 0.5M extra memory usage per-thread. The usage
+// can be reduce by limitting allocation sizes to cache.
+extern Status NewJemallocNodumpAllocator(
+ JemallocAllocatorOptions& options,
+ std::shared_ptr<MemoryAllocator>* memory_allocator);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/memtablerep.h b/storage/rocksdb/rocksdb/include/rocksdb/memtablerep.h
new file mode 100644
index 00000000..49723264
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/memtablerep.h
@@ -0,0 +1,385 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 contains the interface that must be implemented by any collection
+// to be used as the backing store for a MemTable. Such a collection must
+// satisfy the following properties:
+// (1) It does not store duplicate items.
+// (2) It uses MemTableRep::KeyComparator to compare items for iteration and
+// equality.
+// (3) It can be accessed concurrently by multiple readers and can support
+// during reads. However, it needn't support multiple concurrent writes.
+// (4) Items are never deleted.
+// The liberal use of assertions is encouraged to enforce (1).
+//
+// The factory will be passed an MemTableAllocator object when a new MemTableRep
+// is requested.
+//
+// Users can implement their own memtable representations. We include three
+// types built in:
+// - SkipListRep: This is the default; it is backed by a skip list.
+// - HashSkipListRep: The memtable rep that is best used for keys that are
+// structured like "prefix:suffix" where iteration within a prefix is
+// common and iteration across different prefixes is rare. It is backed by
+// a hash map where each bucket is a skip list.
+// - VectorRep: This is backed by an unordered std::vector. On iteration, the
+// vector is sorted. It is intelligent about sorting; once the MarkReadOnly()
+// has been called, the vector will only be sorted once. It is optimized for
+// random-write-heavy workloads.
+//
+// The last four implementations are designed for situations in which
+// iteration over the entire collection is rare since doing so requires all the
+// keys to be copied into a sorted data structure.
+
+#pragma once
+
+#include <rocksdb/slice.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <memory>
+#include <stdexcept>
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena;
+class Allocator;
+class LookupKey;
+class SliceTransform;
+class Logger;
+
+typedef void* KeyHandle;
+
+extern Slice GetLengthPrefixedSlice(const char* data);
+
+class MemTableRep {
+ public:
+ // KeyComparator provides a means to compare keys, which are internal keys
+ // concatenated with values.
+ class KeyComparator {
+ public:
+ typedef ROCKSDB_NAMESPACE::Slice DecodedType;
+
+ virtual DecodedType decode_key(const char* key) const {
+ // The format of key is frozen and can be terated as a part of the API
+ // contract. Refer to MemTable::Add for details.
+ return GetLengthPrefixedSlice(key);
+ }
+
+ // Compare a and b. Return a negative value if a is less than b, 0 if they
+ // are equal, and a positive value if a is greater than b
+ virtual int operator()(const char* prefix_len_key1,
+ const char* prefix_len_key2) const = 0;
+
+ virtual int operator()(const char* prefix_len_key,
+ const Slice& key) const = 0;
+
+ virtual ~KeyComparator() {}
+ };
+
+ explicit MemTableRep(Allocator* allocator) : allocator_(allocator) {}
+
+ // Allocate a buf of len size for storing key. The idea is that a
+ // specific memtable representation knows its underlying data structure
+ // better. By allowing it to allocate memory, it can possibly put
+ // correlated stuff in consecutive memory area to make processor
+ // prefetching more efficient.
+ virtual KeyHandle Allocate(const size_t len, char** buf);
+
+ // Insert key into the collection. (The caller will pack key and value into a
+ // single buffer and pass that in as the parameter to Insert).
+ // REQUIRES: nothing that compares equal to key is currently in the
+ // collection, and no concurrent modifications to the table in progress
+ virtual void Insert(KeyHandle handle) = 0;
+
+ // Same as ::Insert
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ virtual bool InsertKey(KeyHandle handle) {
+ Insert(handle);
+ return true;
+ }
+
+ // Same as Insert(), but in additional pass a hint to insert location for
+ // the key. If hint points to nullptr, a new hint will be populated.
+ // otherwise the hint will be updated to reflect the last insert location.
+ //
+ // Currently only skip-list based memtable implement the interface. Other
+ // implementations will fallback to Insert() by default.
+ virtual void InsertWithHint(KeyHandle handle, void** /*hint*/) {
+ // Ignore the hint by default.
+ Insert(handle);
+ }
+
+ // Same as ::InsertWithHint
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ virtual bool InsertKeyWithHint(KeyHandle handle, void** hint) {
+ InsertWithHint(handle, hint);
+ return true;
+ }
+
+ // Same as ::InsertWithHint, but allow concurrnet write
+ //
+ // If hint points to nullptr, a new hint will be allocated on heap, otherwise
+ // the hint will be updated to reflect the last insert location. The hint is
+ // owned by the caller and it is the caller's responsibility to delete the
+ // hint later.
+ //
+ // Currently only skip-list based memtable implement the interface. Other
+ // implementations will fallback to InsertConcurrently() by default.
+ virtual void InsertWithHintConcurrently(KeyHandle handle, void** /*hint*/) {
+ // Ignore the hint by default.
+ InsertConcurrently(handle);
+ }
+
+ // Same as ::InsertWithHintConcurrently
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ virtual bool InsertKeyWithHintConcurrently(KeyHandle handle, void** hint) {
+ InsertWithHintConcurrently(handle, hint);
+ return true;
+ }
+
+ // Like Insert(handle), but may be called concurrent with other calls
+ // to InsertConcurrently for other handles.
+ //
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ virtual void InsertConcurrently(KeyHandle handle);
+
+ // Same as ::InsertConcurrently
+ // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
+ // the <key, seq> already exists.
+ virtual bool InsertKeyConcurrently(KeyHandle handle) {
+ InsertConcurrently(handle);
+ return true;
+ }
+
+ // Returns true iff an entry that compares equal to key is in the collection.
+ virtual bool Contains(const char* key) const = 0;
+
+ // Notify this table rep that it will no longer be added to. By default,
+ // does nothing. After MarkReadOnly() is called, this table rep will
+ // not be written to (ie No more calls to Allocate(), Insert(),
+ // or any writes done directly to entries accessed through the iterator.)
+ virtual void MarkReadOnly() {}
+
+ // Notify this table rep that it has been flushed to stable storage.
+ // By default, does nothing.
+ //
+ // Invariant: MarkReadOnly() is called, before MarkFlushed().
+ // Note that this method if overridden, should not run for an extended period
+ // of time. Otherwise, RocksDB may be blocked.
+ virtual void MarkFlushed() {}
+
+ // Look up key from the mem table, since the first key in the mem table whose
+ // user_key matches the one given k, call the function callback_func(), with
+ // callback_args directly forwarded as the first parameter, and the mem table
+ // key as the second parameter. If the return value is false, then terminates.
+ // Otherwise, go through the next key.
+ //
+ // It's safe for Get() to terminate after having finished all the potential
+ // key for the k.user_key(), or not.
+ //
+ // Default:
+ // Get() function with a default value of dynamically construct an iterator,
+ // seek and call the call back function.
+ virtual void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry));
+
+ virtual uint64_t ApproximateNumEntries(const Slice& /*start_ikey*/,
+ const Slice& /*end_key*/) {
+ return 0;
+ }
+
+ // Report an approximation of how much memory has been used other than memory
+ // that was allocated through the allocator. Safe to call from any thread.
+ virtual size_t ApproximateMemoryUsage() = 0;
+
+ virtual ~MemTableRep() {}
+
+ // Iteration over the contents of a skip collection
+ class Iterator {
+ public:
+ // Initialize an iterator over the specified collection.
+ // The returned iterator is not valid.
+ // explicit Iterator(const MemTableRep* collection);
+ virtual ~Iterator() {}
+
+ // Returns true iff the iterator is positioned at a valid node.
+ virtual bool Valid() const = 0;
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ virtual const char* key() const = 0;
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ virtual void Next() = 0;
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ virtual void Prev() = 0;
+
+ // Advance to the first entry with a key >= target
+ virtual void Seek(const Slice& internal_key, const char* memtable_key) = 0;
+
+ // retreat to the first entry with a key <= target
+ virtual void SeekForPrev(const Slice& internal_key,
+ const char* memtable_key) = 0;
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ virtual void SeekToFirst() = 0;
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ virtual void SeekToLast() = 0;
+ };
+
+ // Return an iterator over the keys in this representation.
+ // arena: If not null, the arena needs to be used to allocate the Iterator.
+ // When destroying the iterator, the caller will not call "delete"
+ // but Iterator::~Iterator() directly. The destructor needs to destroy
+ // all the states but those allocated in arena.
+ virtual Iterator* GetIterator(Arena* arena = nullptr) = 0;
+
+ // Return an iterator that has a special Seek semantics. The result of
+ // a Seek might only include keys with the same prefix as the target key.
+ // arena: If not null, the arena is used to allocate the Iterator.
+ // When destroying the iterator, the caller will not call "delete"
+ // but Iterator::~Iterator() directly. The destructor needs to destroy
+ // all the states but those allocated in arena.
+ virtual Iterator* GetDynamicPrefixIterator(Arena* arena = nullptr) {
+ return GetIterator(arena);
+ }
+
+ // Return true if the current MemTableRep supports merge operator.
+ // Default: true
+ virtual bool IsMergeOperatorSupported() const { return true; }
+
+ // Return true if the current MemTableRep supports snapshot
+ // Default: true
+ virtual bool IsSnapshotSupported() const { return true; }
+
+ protected:
+ // When *key is an internal key concatenated with the value, returns the
+ // user key.
+ virtual Slice UserKey(const char* key) const;
+
+ Allocator* allocator_;
+};
+
+// This is the base class for all factories that are used by RocksDB to create
+// new MemTableRep objects
+class MemTableRepFactory {
+ public:
+ virtual ~MemTableRepFactory() {}
+
+ virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&,
+ Allocator*, const SliceTransform*,
+ Logger* logger) = 0;
+ virtual MemTableRep* CreateMemTableRep(
+ const MemTableRep::KeyComparator& key_cmp, Allocator* allocator,
+ const SliceTransform* slice_transform, Logger* logger,
+ uint32_t /* column_family_id */) {
+ return CreateMemTableRep(key_cmp, allocator, slice_transform, logger);
+ }
+
+ virtual const char* Name() const = 0;
+
+ // Return true if the current MemTableRep supports concurrent inserts
+ // Default: false
+ virtual bool IsInsertConcurrentlySupported() const { return false; }
+
+ // Return true if the current MemTableRep supports detecting duplicate
+ // <key,seq> at insertion time. If true, then MemTableRep::Insert* returns
+ // false when if the <key,seq> already exists.
+ // Default: false
+ virtual bool CanHandleDuplicatedKey() const { return false; }
+};
+
+// This uses a skip list to store keys. It is the default.
+//
+// Parameters:
+// 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.
+class SkipListFactory : public MemTableRepFactory {
+ public:
+ explicit SkipListFactory(size_t lookahead = 0) : lookahead_(lookahead) {}
+
+ using MemTableRepFactory::CreateMemTableRep;
+ virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&,
+ Allocator*, const SliceTransform*,
+ Logger* logger) override;
+ virtual const char* Name() const override { return "SkipListFactory"; }
+
+ bool IsInsertConcurrentlySupported() const override { return true; }
+
+ bool CanHandleDuplicatedKey() const override { return true; }
+
+ private:
+ const size_t lookahead_;
+};
+
+#ifndef ROCKSDB_LITE
+// This creates MemTableReps that are backed by an std::vector. On iteration,
+// the vector is sorted. This is useful for workloads where iteration is very
+// rare and writes are generally not issued after reads begin.
+//
+// Parameters:
+// count: Passed to the constructor of the underlying std::vector of each
+// VectorRep. On initialization, the underlying array will be at least count
+// bytes reserved for usage.
+class VectorRepFactory : public MemTableRepFactory {
+ const size_t count_;
+
+ public:
+ explicit VectorRepFactory(size_t count = 0) : count_(count) {}
+
+ using MemTableRepFactory::CreateMemTableRep;
+ virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&,
+ Allocator*, const SliceTransform*,
+ Logger* logger) override;
+
+ virtual const char* Name() const override { return "VectorRepFactory"; }
+};
+
+// This class contains a fixed array of buckets, each
+// pointing to a skiplist (null if the bucket is empty).
+// bucket_count: number of fixed array buckets
+// skiplist_height: the max height of the skiplist
+// skiplist_branching_factor: probabilistic size ratio between adjacent
+// link lists in the skiplist
+extern MemTableRepFactory* NewHashSkipListRepFactory(
+ size_t bucket_count = 1000000, int32_t skiplist_height = 4,
+ int32_t skiplist_branching_factor = 4);
+
+// The factory is to create memtables based on a hash table:
+// it contains a fixed array of buckets, each pointing to either a linked list
+// or a skip list if number of entries inside the bucket exceeds
+// threshold_use_skiplist.
+// @bucket_count: number of fixed array buckets
+// @huge_page_tlb_size: if <=0, allocate the hash table bytes from malloc.
+// Otherwise from huge page TLB. The user needs to reserve
+// huge pages for it to be allocated, like:
+// sysctl -w vm.nr_hugepages=20
+// See linux doc Documentation/vm/hugetlbpage.txt
+// @bucket_entries_logging_threshold: if number of entries in one bucket
+// exceeds this number, log about it.
+// @if_log_bucket_dist_when_flash: if true, log distribution of number of
+// entries when flushing.
+// @threshold_use_skiplist: a bucket switches to skip list if number of
+// entries exceed this parameter.
+extern MemTableRepFactory* NewHashLinkListRepFactory(
+ size_t bucket_count = 50000, size_t huge_page_tlb_size = 0,
+ int bucket_entries_logging_threshold = 4096,
+ bool if_log_bucket_dist_when_flash = true,
+ uint32_t threshold_use_skiplist = 256);
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/merge_operator.h b/storage/rocksdb/rocksdb/include/rocksdb/merge_operator.h
new file mode 100644
index 00000000..86f3d726
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/merge_operator.h
@@ -0,0 +1,257 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class Logger;
+
+// The Merge Operator
+//
+// Essentially, a MergeOperator specifies the SEMANTICS of a merge, which only
+// client knows. It could be numeric addition, list append, string
+// concatenation, edit data structure, ... , anything.
+// The library, on the other hand, is concerned with the exercise of this
+// interface, at the right time (during get, iteration, compaction...)
+//
+// To use merge, the client needs to provide an object implementing one of
+// the following interfaces:
+// a) AssociativeMergeOperator - for most simple semantics (always take
+// two values, and merge them into one value, which is then put back
+// into rocksdb); numeric addition and string concatenation are examples;
+//
+// b) MergeOperator - the generic class for all the more abstract / complex
+// operations; one method (FullMergeV2) to merge a Put/Delete value with a
+// merge operand; and another method (PartialMerge) that merges multiple
+// operands together. this is especially useful if your key values have
+// complex structures but you would still like to support client-specific
+// incremental updates.
+//
+// AssociativeMergeOperator is simpler to implement. MergeOperator is simply
+// more powerful.
+//
+// Refer to rocksdb-merge wiki for more details and example implementations.
+//
+class MergeOperator {
+ public:
+ virtual ~MergeOperator() {}
+ static const char* Type() { return "MergeOperator"; }
+
+ // Gives the client a way to express the read -> modify -> write semantics
+ // key: (IN) The key that's associated with this merge operation.
+ // Client could multiplex the merge operator based on it
+ // if the key space is partitioned and different subspaces
+ // refer to different types of data which have different
+ // merge operation semantics
+ // existing: (IN) null indicates that the key does not exist before this op
+ // operand_list:(IN) the sequence of merge operations to apply, front() first.
+ // new_value:(OUT) Client is responsible for filling the merge result here.
+ // The string that new_value is pointing to will be empty.
+ // logger: (IN) Client could use this to log errors during merge.
+ //
+ // Return true on success.
+ // All values passed in will be client-specific values. So if this method
+ // returns false, it is because client specified bad data or there was
+ // internal corruption. This will be treated as an error by the library.
+ //
+ // Also make use of the *logger for error messages.
+ virtual bool FullMerge(const Slice& /*key*/, const Slice* /*existing_value*/,
+ const std::deque<std::string>& /*operand_list*/,
+ std::string* /*new_value*/, Logger* /*logger*/) const {
+ // deprecated, please use FullMergeV2()
+ assert(false);
+ return false;
+ }
+
+ struct MergeOperationInput {
+ explicit MergeOperationInput(const Slice& _key,
+ const Slice* _existing_value,
+ const std::vector<Slice>& _operand_list,
+ Logger* _logger)
+ : key(_key),
+ existing_value(_existing_value),
+ operand_list(_operand_list),
+ logger(_logger) {}
+
+ // The key associated with the merge operation.
+ const Slice& key;
+ // The existing value of the current key, nullptr means that the
+ // value doesn't exist.
+ const Slice* existing_value;
+ // A list of operands to apply.
+ const std::vector<Slice>& operand_list;
+ // Logger could be used by client to log any errors that happen during
+ // the merge operation.
+ Logger* logger;
+ };
+
+ struct MergeOperationOutput {
+ explicit MergeOperationOutput(std::string& _new_value,
+ Slice& _existing_operand)
+ : new_value(_new_value), existing_operand(_existing_operand) {}
+
+ // Client is responsible for filling the merge result here.
+ std::string& new_value;
+ // If the merge result is one of the existing operands (or existing_value),
+ // client can set this field to the operand (or existing_value) instead of
+ // using new_value.
+ Slice& existing_operand;
+ };
+
+ // This function applies a stack of merge operands in chrionological order
+ // on top of an existing value. There are two ways in which this method is
+ // being used:
+ // a) During Get() operation, it used to calculate the final value of a key
+ // b) During compaction, in order to collapse some operands with the based
+ // value.
+ //
+ // Note: The name of the method is somewhat misleading, as both in the cases
+ // of Get() or compaction it may be called on a subset of operands:
+ // K: 0 +1 +2 +7 +4 +5 2 +1 +2
+ // ^
+ // |
+ // snapshot
+ // In the example above, Get(K) operation will call FullMerge with a base
+ // value of 2 and operands [+1, +2]. Compaction process might decide to
+ // collapse the beginning of the history up to the snapshot by performing
+ // full Merge with base value of 0 and operands [+1, +2, +7, +3].
+ virtual bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const;
+
+ // This function performs merge(left_op, right_op)
+ // when both the operands are themselves merge operation types
+ // that you would have passed to a DB::Merge() call in the same order
+ // (i.e.: DB::Merge(key,left_op), followed by DB::Merge(key,right_op)).
+ //
+ // PartialMerge should combine them into a single merge operation that is
+ // saved into *new_value, and then it should return true.
+ // *new_value should be constructed such that a call to
+ // DB::Merge(key, *new_value) would yield the same result as a call
+ // to DB::Merge(key, left_op) followed by DB::Merge(key, right_op).
+ //
+ // The string that new_value is pointing to will be empty.
+ //
+ // The default implementation of PartialMergeMulti will use this function
+ // as a helper, for backward compatibility. Any successor class of
+ // MergeOperator should either implement PartialMerge or PartialMergeMulti,
+ // although implementing PartialMergeMulti is suggested as it is in general
+ // more effective to merge multiple operands at a time instead of two
+ // operands at a time.
+ //
+ // If it is impossible or infeasible to combine the two operations,
+ // leave new_value unchanged and return false. The library will
+ // internally keep track of the operations, and apply them in the
+ // correct order once a base-value (a Put/Delete/End-of-Database) is seen.
+ //
+ // TODO: Presently there is no way to differentiate between error/corruption
+ // and simply "return false". For now, the client should simply return
+ // false in any case it cannot perform partial-merge, regardless of reason.
+ // If there is corruption in the data, handle it in the FullMergeV2() function
+ // and return false there. The default implementation of PartialMerge will
+ // always return false.
+ virtual bool PartialMerge(const Slice& /*key*/, const Slice& /*left_operand*/,
+ const Slice& /*right_operand*/,
+ std::string* /*new_value*/,
+ Logger* /*logger*/) const {
+ return false;
+ }
+
+ // This function performs merge when all the operands are themselves merge
+ // operation types that you would have passed to a DB::Merge() call in the
+ // same order (front() first)
+ // (i.e. DB::Merge(key, operand_list[0]), followed by
+ // DB::Merge(key, operand_list[1]), ...)
+ //
+ // PartialMergeMulti should combine them into a single merge operation that is
+ // saved into *new_value, and then it should return true. *new_value should
+ // be constructed such that a call to DB::Merge(key, *new_value) would yield
+ // the same result as subquential individual calls to DB::Merge(key, operand)
+ // for each operand in operand_list from front() to back().
+ //
+ // The string that new_value is pointing to will be empty.
+ //
+ // The PartialMergeMulti function will be called when there are at least two
+ // operands.
+ //
+ // In the default implementation, PartialMergeMulti will invoke PartialMerge
+ // multiple times, where each time it only merges two operands. Developers
+ // should either implement PartialMergeMulti, or implement PartialMerge which
+ // is served as the helper function of the default PartialMergeMulti.
+ virtual bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* logger) const;
+
+ // The name of the MergeOperator. Used to check for MergeOperator
+ // mismatches (i.e., a DB created with one MergeOperator is
+ // accessed using a different MergeOperator)
+ // TODO: the name is currently not stored persistently and thus
+ // no checking is enforced. Client is responsible for providing
+ // consistent MergeOperator between DB opens.
+ virtual const char* Name() const = 0;
+
+ // Determines whether the PartialMerge can be called with just a single
+ // merge operand.
+ // Override and return true for allowing a single operand. PartialMerge
+ // and PartialMergeMulti should be overridden and implemented
+ // correctly to properly handle a single operand.
+ virtual bool AllowSingleOperand() const { return false; }
+
+ // Allows to control when to invoke a full merge during Get.
+ // This could be used to limit the number of merge operands that are looked at
+ // during a point lookup, thereby helping in limiting the number of levels to
+ // read from.
+ // Doesn't help with iterators.
+ //
+ // Note: the merge operands are passed to this function in the reversed order
+ // relative to how they were merged (passed to FullMerge or FullMergeV2)
+ // for performance reasons, see also:
+ // https://github.com/facebook/rocksdb/issues/3865
+ virtual bool ShouldMerge(const std::vector<Slice>& /*operands*/) const {
+ return false;
+ }
+};
+
+// The simpler, associative merge operator.
+class AssociativeMergeOperator : public MergeOperator {
+ public:
+ ~AssociativeMergeOperator() override {}
+
+ // Gives the client a way to express the read -> modify -> write semantics
+ // key: (IN) The key that's associated with this merge operation.
+ // existing_value:(IN) null indicates the key does not exist before this op
+ // value: (IN) the value to update/merge the existing_value with
+ // new_value: (OUT) Client is responsible for filling the merge result
+ // here. The string that new_value is pointing to will be empty.
+ // logger: (IN) Client could use this to log errors during merge.
+ //
+ // Return true on success.
+ // All values passed in will be client-specific values. So if this method
+ // returns false, it is because client specified bad data or there was
+ // internal corruption. The client should assume that this will be treated
+ // as an error by the library.
+ virtual bool Merge(const Slice& key, const Slice* existing_value,
+ const Slice& value, std::string* new_value,
+ Logger* logger) const = 0;
+
+ private:
+ // Default implementations of the MergeOperator functions
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override;
+
+ bool PartialMerge(const Slice& key, const Slice& left_operand,
+ const Slice& right_operand, std::string* new_value,
+ Logger* logger) const override;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/metadata.h b/storage/rocksdb/rocksdb/include/rocksdb/metadata.h
new file mode 100644
index 00000000..f1a9ee60
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/metadata.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+struct ColumnFamilyMetaData;
+struct LevelMetaData;
+struct SstFileMetaData;
+
+// The metadata that describes a column family.
+struct ColumnFamilyMetaData {
+ ColumnFamilyMetaData() : size(0), file_count(0), name("") {}
+ ColumnFamilyMetaData(const std::string& _name, uint64_t _size,
+ const std::vector<LevelMetaData>&& _levels)
+ : size(_size), name(_name), levels(_levels) {}
+
+ // The size of this column family in bytes, which is equal to the sum of
+ // the file size of its "levels".
+ uint64_t size;
+ // The number of files in this column family.
+ size_t file_count;
+ // The name of the column family.
+ std::string name;
+ // The metadata of all levels in this column family.
+ std::vector<LevelMetaData> levels;
+};
+
+// The metadata that describes a level.
+struct LevelMetaData {
+ LevelMetaData(int _level, uint64_t _size,
+ const std::vector<SstFileMetaData>&& _files)
+ : level(_level), size(_size), files(_files) {}
+
+ // The level which this meta data describes.
+ const int level;
+ // The size of this level in bytes, which is equal to the sum of
+ // the file size of its "files".
+ const uint64_t size;
+ // The metadata of all sst files in this level.
+ const std::vector<SstFileMetaData> files;
+};
+
+// The metadata that describes a SST file.
+struct SstFileMetaData {
+ SstFileMetaData()
+ : size(0),
+ file_number(0),
+ smallest_seqno(0),
+ largest_seqno(0),
+ num_reads_sampled(0),
+ being_compacted(false),
+ num_entries(0),
+ num_deletions(0),
+ oldest_blob_file_number(0) {}
+
+ SstFileMetaData(const std::string& _file_name, uint64_t _file_number,
+ const std::string& _path, size_t _size,
+ SequenceNumber _smallest_seqno, SequenceNumber _largest_seqno,
+ const std::string& _smallestkey,
+ const std::string& _largestkey, uint64_t _num_reads_sampled,
+ bool _being_compacted, uint64_t _oldest_blob_file_number,
+ uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
+ std::string& _file_checksum,
+ std::string& _file_checksum_func_name)
+ : size(_size),
+ name(_file_name),
+ file_number(_file_number),
+ db_path(_path),
+ smallest_seqno(_smallest_seqno),
+ largest_seqno(_largest_seqno),
+ smallestkey(_smallestkey),
+ largestkey(_largestkey),
+ num_reads_sampled(_num_reads_sampled),
+ being_compacted(_being_compacted),
+ num_entries(0),
+ num_deletions(0),
+ oldest_blob_file_number(_oldest_blob_file_number),
+ oldest_ancester_time(_oldest_ancester_time),
+ file_creation_time(_file_creation_time),
+ file_checksum(_file_checksum),
+ file_checksum_func_name(_file_checksum_func_name) {}
+
+ // File size in bytes.
+ size_t size;
+ // The name of the file.
+ std::string name;
+ // The id of the file.
+ uint64_t file_number;
+ // The full path where the file locates.
+ std::string db_path;
+
+ SequenceNumber smallest_seqno; // Smallest sequence number in file.
+ SequenceNumber largest_seqno; // Largest sequence number in file.
+ std::string smallestkey; // Smallest user defined key in the file.
+ std::string largestkey; // Largest user defined key in the file.
+ uint64_t num_reads_sampled; // How many times the file is read.
+ bool being_compacted; // true if the file is currently being compacted.
+
+ uint64_t num_entries;
+ uint64_t num_deletions;
+
+ uint64_t oldest_blob_file_number; // The id of the oldest blob file
+ // referenced by the file.
+ // An SST file may be generated by compactions whose input files may
+ // in turn be generated by earlier compactions. The creation time of the
+ // oldest SST file that is the compaction ancester of this file.
+ // The timestamp is provided Env::GetCurrentTime().
+ // 0 if the information is not available.
+ uint64_t oldest_ancester_time;
+ // Timestamp when the SST file is created, provided by Env::GetCurrentTime().
+ // 0 if the information is not available.
+ uint64_t file_creation_time;
+
+ // The checksum of a SST file, the value is decided by the file content and
+ // the checksum algorithm used for this SST file. The checksum function is
+ // identified by the file_checksum_func_name. If the checksum function is
+ // not specified, file_checksum is "0" by default.
+ std::string file_checksum;
+
+ // The name of the checksum function used to generate the file checksum
+ // value. If file checksum is not enabled (e.g., sst_file_checksum_func is
+ // null), file_checksum_func_name is UnknownFileChecksumFuncName, which is
+ // "Unknown".
+ std::string file_checksum_func_name;
+};
+
+// The full set of metadata associated with each SST file.
+struct LiveFileMetaData : SstFileMetaData {
+ std::string column_family_name; // Name of the column family
+ int level; // Level at which this file resides.
+ LiveFileMetaData() : column_family_name(), level(0) {}
+};
+
+// Metadata returned as output from ExportColumnFamily() and used as input to
+// CreateColumnFamiliesWithImport().
+struct ExportImportFilesMetaData {
+ std::string db_comparator_name; // Used to safety check at import.
+ std::vector<LiveFileMetaData> files; // Vector of file metadata.
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/options.h b/storage/rocksdb/rocksdb/include/rocksdb/options.h
new file mode 100644
index 00000000..f5d44fb7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/options.h
@@ -0,0 +1,1587 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+#include <stdint.h>
+#include <limits>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "rocksdb/advanced_options.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_checksum.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/universal_compaction.h"
+#include "rocksdb/version.h"
+#include "rocksdb/write_buffer_manager.h"
+
+#ifdef max
+#undef max
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class Cache;
+class CompactionFilter;
+class CompactionFilterFactory;
+class Comparator;
+class ConcurrentTaskLimiter;
+class Env;
+enum InfoLogLevel : unsigned char;
+class SstFileManager;
+class FilterPolicy;
+class Logger;
+class MergeOperator;
+class Snapshot;
+class MemTableRepFactory;
+class RateLimiter;
+class Slice;
+class Statistics;
+class InternalKeyComparator;
+class WalFilter;
+class FileSystem;
+
+// 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.
+enum CompressionType : unsigned char {
+ // NOTE: do not change the values of existing entries, as these are
+ // part of the persistent format on disk.
+ kNoCompression = 0x0,
+ kSnappyCompression = 0x1,
+ kZlibCompression = 0x2,
+ kBZip2Compression = 0x3,
+ kLZ4Compression = 0x4,
+ kLZ4HCCompression = 0x5,
+ kXpressCompression = 0x6,
+ kZSTD = 0x7,
+
+ // Only use kZSTDNotFinalCompression if you have to use ZSTD lib older than
+ // 0.8.0 or consider a possibility of downgrading the service or copying
+ // the database files to another service running with an older version of
+ // RocksDB that doesn't have kZSTD. Otherwise, you should use kZSTD. We will
+ // eventually remove the option from the public API.
+ kZSTDNotFinalCompression = 0x40,
+
+ // kDisableCompressionOption is used to disable some compression options.
+ kDisableCompressionOption = 0xff,
+};
+
+struct Options;
+struct DbPath;
+
+struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
+ // The function recovers options to a previous version. Only 4.6 or later
+ // versions are supported.
+ ColumnFamilyOptions* OldDefaults(int rocksdb_major_version = 4,
+ int rocksdb_minor_version = 6);
+
+ // 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.
+ // An optional cache object is passed in to be used as the block cache
+ ColumnFamilyOptions* OptimizeForSmallDb(
+ std::shared_ptr<Cache>* cache = nullptr);
+
+ // 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
+ //
+ // Not supported in ROCKSDB_LITE
+ ColumnFamilyOptions* OptimizeForPointLookup(uint64_t block_cache_size_mb);
+
+ // 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 two functions:
+ // * OptimizeLevelStyleCompaction -- optimizes level style compaction
+ // * OptimizeUniversalStyleCompaction -- optimizes universal style compaction
+ // Universal style compaction is focused on reducing Write Amplification
+ // Factor for big data sets, but increases Space Amplification. You can learn
+ // more about the different styles here:
+ // https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide
+ // 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
+ //
+ // OptimizeUniversalStyleCompaction is not supported in ROCKSDB_LITE
+ ColumnFamilyOptions* OptimizeLevelStyleCompaction(
+ uint64_t memtable_memory_budget = 512 * 1024 * 1024);
+ ColumnFamilyOptions* OptimizeUniversalStyleCompaction(
+ uint64_t memtable_memory_budget = 512 * 1024 * 1024);
+
+ // -------------------
+ // Parameters that affect behavior
+
+ // Comparator used to define the order of keys in the table.
+ // Default: a comparator that uses lexicographic byte-wise ordering
+ //
+ // REQUIRES: The client must ensure that the comparator supplied
+ // here has the same name and orders keys *exactly* the same as the
+ // comparator provided to previous open calls on the same DB.
+ const Comparator* comparator = BytewiseComparator();
+
+ // REQUIRES: The client must provide a merge operator if Merge operation
+ // needs to be accessed. Calling Merge on a DB without a merge operator
+ // would result in Status::NotSupported. The client must ensure that the
+ // merge operator supplied here has the same name and *exactly* the same
+ // semantics as the merge operator provided to previous open calls on
+ // the same DB. The only exception is reserved for upgrade, where a DB
+ // previously without a merge operator is introduced to Merge operation
+ // for the first time. It's necessary to specify a merge operator when
+ // opening the DB in this case.
+ // Default: nullptr
+ std::shared_ptr<MergeOperator> merge_operator = nullptr;
+
+ // 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 compaction_filter_factory instead of this
+ // option. The client should specify only one of the two.
+ // compaction_filter takes precedence over compaction_filter_factory if
+ // 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.
+ //
+ // Default: nullptr
+ const CompactionFilter* compaction_filter = nullptr;
+
+ // This is a factory that provides compaction filter 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.
+ //
+ // Default: nullptr
+ std::shared_ptr<CompactionFilterFactory> compaction_filter_factory = nullptr;
+
+ // -------------------
+ // Parameters that affect performance
+
+ // 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 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.
+ //
+ // Note that write_buffer_size is enforced per column family.
+ // See db_write_buffer_size for sharing memory across column families.
+ //
+ // Default: 64MB
+ //
+ // Dynamically changeable through SetOptions() API
+ size_t write_buffer_size = 64 << 20;
+
+ // Compress blocks using the specified compression algorithm.
+ //
+ // Default: kSnappyCompression, if it's supported. If snappy is not linked
+ // with the library, the default is kNoCompression.
+ //
+ // Typical speeds of kSnappyCompression on an Intel(R) Core(TM)2 2.4GHz:
+ // ~200-500MB/s compression
+ // ~400-800MB/s decompression
+ //
+ // Note that these speeds are significantly faster than most
+ // persistent storage speeds, and therefore it is typically never
+ // worth switching to kNoCompression. Even if the input data is
+ // incompressible, the kSnappyCompression implementation will
+ // efficiently detect that and will switch to uncompressed mode.
+ //
+ // If you do not set `compression_opts.level`, or set it to
+ // `CompressionOptions::kDefaultCompressionLevel`, we will attempt to pick the
+ // default corresponding to `compression` as follows:
+ //
+ // - kZSTD: 3
+ // - kZlibCompression: Z_DEFAULT_COMPRESSION (currently -1)
+ // - kLZ4HCCompression: 0
+ // - For all others, we do not specify a compression level
+ //
+ // Dynamically changeable through SetOptions() API
+ CompressionType compression;
+
+ // Compression algorithm that will be used for the bottommost level that
+ // contain files.
+ //
+ // Default: kDisableCompressionOption (Disabled)
+ CompressionType bottommost_compression = kDisableCompressionOption;
+
+ // different options for compression algorithms used by bottommost_compression
+ // if it is enabled. To enable it, please see the definition of
+ // CompressionOptions.
+ CompressionOptions bottommost_compression_opts;
+
+ // different options for compression algorithms
+ CompressionOptions compression_opts;
+
+ // 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
+ //
+ // Dynamically changeable through SetOptions() API
+ int level0_file_num_compaction_trigger = 4;
+
+ // If non-nullptr, use the specified function to determine the
+ // prefixes for keys. These prefixes will be placed in the filter.
+ // Depending on the workload, this can reduce the number of read-IOP
+ // cost for scans when a prefix is passed via ReadOptions to
+ // db.NewIterator(). For prefix filtering to work properly,
+ // "prefix_extractor" and "comparator" must be such that the following
+ // properties hold:
+ //
+ // 1) key.starts_with(prefix(key))
+ // 2) Compare(prefix(key), key) <= 0.
+ // 3) If Compare(k1, k2) <= 0, then Compare(prefix(k1), prefix(k2)) <= 0
+ // 4) prefix(prefix(key)) == prefix(key)
+ //
+ // Default: nullptr
+ std::shared_ptr<const SliceTransform> prefix_extractor = nullptr;
+
+ // Control maximum total data size for a level.
+ // max_bytes_for_level_base is the max total for level-1.
+ // Maximum number of bytes for level L can be calculated as
+ // (max_bytes_for_level_base) * (max_bytes_for_level_multiplier ^ (L-1))
+ // For example, if max_bytes_for_level_base is 200MB, 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.
+ //
+ // Default: 256MB.
+ //
+ // Dynamically changeable through SetOptions() API
+ uint64_t max_bytes_for_level_base = 256 * 1048576;
+
+ // Deprecated.
+ uint64_t snap_refresh_nanos = 0;
+
+ // Disable automatic compactions. Manual compactions can still
+ // be issued on this column family
+ //
+ // Dynamically changeable through SetOptions() API
+ bool disable_auto_compactions = false;
+
+ // This is a factory that provides TableFactory objects.
+ // Default: a block-based table factory that provides a default
+ // implementation of TableBuilder and TableReader with default
+ // BlockBasedTableOptions.
+ std::shared_ptr<TableFactory> table_factory;
+
+ // 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
+ std::vector<DbPath> cf_paths;
+
+ // 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.
+ //
+ // Default: nullptr
+ std::shared_ptr<ConcurrentTaskLimiter> compaction_thread_limiter = nullptr;
+
+ // Create ColumnFamilyOptions with default values for all fields
+ ColumnFamilyOptions();
+ // Create ColumnFamilyOptions from Options
+ explicit ColumnFamilyOptions(const Options& options);
+
+ void Dump(Logger* log) const;
+};
+
+enum class WALRecoveryMode : char {
+ // Original levelDB recovery
+ // We tolerate incomplete record in trailing data on all logs
+ // Use case : This is legacy behavior
+ kTolerateCorruptedTailRecords = 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
+ kAbsoluteConsistency = 0x01,
+ // Recover to point-in-time consistency (default)
+ // 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
+ kPointInTimeRecovery = 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
+ kSkipAnyCorruptedRecords = 0x03,
+};
+
+struct DbPath {
+ std::string path;
+ uint64_t target_size; // Target size of total files under the path, in byte.
+
+ DbPath() : target_size(0) {}
+ DbPath(const std::string& p, uint64_t t) : path(p), target_size(t) {}
+};
+
+struct DBOptions {
+ // The function recovers options to the option as in version 4.6.
+ DBOptions* OldDefaults(int rocksdb_major_version = 4,
+ int rocksdb_minor_version = 6);
+
+ // 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.
+ // An optional cache object is passed in for the memory of the
+ // memtable to cost to
+ DBOptions* OptimizeForSmallDb(std::shared_ptr<Cache>* cache = nullptr);
+
+#ifndef ROCKSDB_LITE
+ // 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. Good value for `total_threads` is the number of
+ // cores. You almost definitely want to call this function if your system is
+ // bottlenecked by RocksDB.
+ DBOptions* IncreaseParallelism(int total_threads = 16);
+#endif // ROCKSDB_LITE
+
+ // If true, the database will be created if it is missing.
+ // Default: false
+ bool create_if_missing = false;
+
+ // If true, missing column families will be automatically created.
+ // Default: false
+ bool create_missing_column_families = false;
+
+ // If true, an error is raised if the database already exists.
+ // Default: false
+ bool error_if_exists = false;
+
+ // If true, RocksDB will aggressively check consistency of the data.
+ // Also, 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.
+ // In most cases you want this to be set to true.
+ // Default: true
+ bool paranoid_checks = true;
+
+ // Use the specified object to interact with the environment,
+ // e.g. to read/write files, schedule background work, etc. In the near
+ // future, support for doing storage operations such as read/write files
+ // through env will be deprecated in favor of file_system (see below)
+ // Default: Env::Default()
+ Env* env = Env::Default();
+
+ // Use the specified object to interact with the storage to
+ // read/write files. This is in addition to env. This option should be used
+ // if the desired storage subsystem provides a FileSystem implementation.
+ std::shared_ptr<FileSystem> file_system = nullptr;
+
+ // Use to control write rate of flush and compaction. Flush has higher
+ // priority than compaction. Rate limiting is disabled if nullptr.
+ // If rate limiter is enabled, bytes_per_sync is set to 1MB by default.
+ // Default: nullptr
+ std::shared_ptr<RateLimiter> rate_limiter = nullptr;
+
+ // 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).
+ //
+ // Default: nullptr
+ std::shared_ptr<SstFileManager> sst_file_manager = nullptr;
+
+ // Any internal progress/error information generated by the db will
+ // be written to info_log 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
+ std::shared_ptr<Logger> info_log = nullptr;
+
+#ifdef NDEBUG
+ InfoLogLevel info_log_level = INFO_LEVEL;
+#else
+ InfoLogLevel info_log_level = DEBUG_LEVEL;
+#endif // NDEBUG
+
+ // 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 target_file_size_base and target_file_size_multiplier for level-based
+ // compaction. For universal-style compaction, you can usually set it to -1.
+ //
+ // Default: -1
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ int max_open_files = -1;
+
+ // If max_open_files 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
+ int max_file_opening_threads = 16;
+
+ // 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
+ // (i.e. the ones that are causing all the space amplification). 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
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ uint64_t max_total_wal_size = 0;
+
+ // If non-null, then we should collect metrics about database operations
+ std::shared_ptr<Statistics> statistics = nullptr;
+
+ // By default, writes to stable storage use fdatasync (on platforms
+ // where this function is available). If this option is true,
+ // fsync is used instead.
+ //
+ // fsync and fdatasync are equally safe for our purposes and fdatasync is
+ // faster, so it is rarely necessary to set this option. It is provided
+ // as a workaround for kernel/filesystem bugs, such as one that affected
+ // fdatasync with ext4 in kernel versions prior to 3.7.
+ bool use_fsync = false;
+
+ // 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
+ std::vector<DbPath> db_paths;
+
+ // 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.
+ std::string db_log_dir = "";
+
+ // 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
+ std::string wal_dir = "";
+
+ // 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
+ //
+ // Default: 6 hours
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ uint64_t delete_obsolete_files_period_micros = 6ULL * 60 * 60 * 1000000;
+
+ // Maximum number of concurrent background jobs (compactions and flushes).
+ //
+ // Default: 2
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ int max_background_jobs = 2;
+
+ // NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the
+ // value of max_background_jobs. This option is ignored.
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ int base_background_compactions = -1;
+
+ // 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).
+ //
+ // 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
+ // Env::SetBackgroundThreads
+ //
+ // Default: -1
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ int max_background_compactions = -1;
+
+ // 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)
+ uint32_t max_subcompactions = 1;
+
+ // 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`.
+ //
+ // Maximum number of concurrent background memtable flush jobs, submitted by
+ // default to the HIGH priority thread pool. If the HIGH priority thread pool
+ // is configured to have zero threads, flush jobs will share the LOW priority
+ // thread pool with compaction jobs.
+ //
+ // It is important to use both thread pools when the same Env is shared by
+ // multiple db instances. Without a separate pool, long running compaction
+ // jobs could potentially block memtable flush jobs of other db instances,
+ // leading to unnecessary Put stalls.
+ //
+ // If you're increasing this, also consider increasing number of threads in
+ // HIGH priority thread pool. For more information, see
+ // Env::SetBackgroundThreads
+ // Default: -1
+ int max_background_flushes = -1;
+
+ // Specify the maximal size of the info log file. If the log file
+ // is larger than `max_log_file_size`, a new info log file will
+ // be created.
+ // If max_log_file_size == 0, all logs will be written to one
+ // log file.
+ size_t max_log_file_size = 0;
+
+ // Time 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)
+ // Not supported in ROCKSDB_LITE mode!
+ size_t log_file_time_to_roll = 0;
+
+ // Maximal info log files to be kept.
+ // Default: 1000
+ size_t keep_log_file_num = 1000;
+
+ // 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
+ size_t recycle_log_file_num = 0;
+
+ // 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.
+ uint64_t max_manifest_file_size = 1024 * 1024 * 1024;
+
+ // Number of shards used for table cache.
+ int table_cache_numshardbits = 6;
+
+ // NOT SUPPORTED ANYMORE
+ // int table_cache_remove_scan_count_limit;
+
+ // The following two fields 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. 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.
+ // 3. 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.
+ // 4. If both are not 0, WAL files will be checked every 10 min and both
+ // checks will be performed with ttl being first.
+ uint64_t WAL_ttl_seconds = 0;
+ uint64_t WAL_size_limit_MB = 0;
+
+ // 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).
+ size_t manifest_preallocation_size = 4 * 1024 * 1024;
+
+ // Allow the OS to mmap file for reading sst tables. Default: false
+ bool allow_mmap_reads = false;
+
+ // Allow the OS to mmap file for writing.
+ // DB::SyncWAL() only works if this is set to false.
+ // Default: false
+ bool allow_mmap_writes = false;
+
+ // Enable direct I/O mode for read/write
+ // they may or may not improve performance depending on the use case
+ //
+ // Files will be opened in "direct I/O" mode
+ // which means that data r/w from the disk will not be cached or
+ // buffered. The hardware buffer of the devices may however still
+ // be used. Memory mapped files are not impacted by these parameters.
+
+ // Use O_DIRECT for user and compaction reads.
+ // When true, we also force new_table_reader_for_compaction_inputs to true.
+ // Default: false
+ // Not supported in ROCKSDB_LITE mode!
+ bool use_direct_reads = false;
+
+ // Use O_DIRECT for writes in background flush and compactions.
+ // Default: false
+ // Not supported in ROCKSDB_LITE mode!
+ bool use_direct_io_for_flush_and_compaction = false;
+
+ // If false, fallocate() calls are bypassed
+ bool allow_fallocate = true;
+
+ // Disable child process inherit open files. Default: true
+ bool is_fd_close_on_exec = true;
+
+ // NOT SUPPORTED ANYMORE -- this options is no longer used
+ bool skip_log_error_on_recovery = false;
+
+ // if not zero, dump rocksdb.stats to LOG every stats_dump_period_sec
+ //
+ // Default: 600 (10 min)
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ unsigned int stats_dump_period_sec = 600;
+
+ // if not zero, dump rocksdb.stats to RocksDB every stats_persist_period_sec
+ // Default: 600
+ unsigned int stats_persist_period_sec = 600;
+
+ // 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
+ bool persist_stats_to_disk = false;
+
+ // if not zero, periodically take stats snapshots and store in memory, the
+ // memory size for stats snapshots is capped at stats_history_buffer_size
+ // Default: 1MB
+ size_t stats_history_buffer_size = 1024 * 1024;
+
+ // If set true, will hint the underlying file system that the file
+ // access pattern is random, when a sst file is opened.
+ // Default: true
+ bool advise_random_on_open = true;
+
+ // Amount of data to build up in memtables across all column
+ // families before writing to disk.
+ //
+ // This is distinct from write_buffer_size, 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)
+ size_t db_write_buffer_size = 0;
+
+ // The memory usage of memtable will report to this object. The same object
+ // can be passed into multiple DBs and it will track the sum of size of all
+ // the DBs. If the total size of all live memtables of all the DBs exceeds
+ // a limit, a flush will be triggered in the next DB to which the next write
+ // is issued.
+ //
+ // If the object is only passed to one DB, the behavior is the same as
+ // db_write_buffer_size. When write_buffer_manager is set, the value set will
+ // override db_write_buffer_size.
+ //
+ // This feature is disabled by default. Specify a non-zero value
+ // to enable it.
+ //
+ // Default: null
+ std::shared_ptr<WriteBufferManager> write_buffer_manager = nullptr;
+
+ // Specify the file access pattern once a compaction is started.
+ // It will be applied to all input files of a compaction.
+ // Default: NORMAL
+ enum AccessHint { NONE, NORMAL, SEQUENTIAL, WILLNEED };
+ AccessHint access_hint_on_compaction_start = NORMAL;
+
+ // If true, always create a new file descriptor and new table reader
+ // for compaction inputs. Turn this parameter on may introduce extra
+ // memory usage in the table reader, if it allocates extra memory
+ // for indexes. This will allow file descriptor prefetch options
+ // to be set for compaction input files and not to impact file
+ // descriptors for the same file used by user queries.
+ // Suggest to enable BlockBasedTableOptions.cache_index_and_filter_blocks
+ // for this mode if using block-based table.
+ //
+ // Default: false
+ // This flag has no affect on the behavior of compaction and plan to delete
+ // in the future.
+ bool new_table_reader_for_compaction_inputs = false;
+
+ // 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.
+ //
+ // When non-zero, we also force new_table_reader_for_compaction_inputs to
+ // true.
+ //
+ // Default: 0
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ size_t compaction_readahead_size = 0;
+
+ // 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 compaction_readahead_size
+ // 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.
+ size_t random_access_max_buffer_size = 1024 * 1024;
+
+ // 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 in buffered
+ // IO and fix the buffer size when using direct IO to ensure alignment of
+ // write requests if the logical sector size is unusual
+ //
+ // Default: 1024 * 1024 (1 MB)
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ size_t writable_file_max_buffer_size = 1024 * 1024;
+
+ // 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
+ bool use_adaptive_mutex = false;
+
+ // Create DBOptions with default values for all fields
+ DBOptions();
+ // Create DBOptions from Options
+ explicit DBOptions(const Options& options);
+
+ void Dump(Logger* log) const;
+
+ // Allows OS to incrementally sync files to disk while they are being
+ // written, asynchronously, in the background. This operation can be used
+ // to smooth out write I/Os over time. Users shouldn't rely on it for
+ // persistency guarantee.
+ // Issue one request for every bytes_per_sync written. 0 turns it off.
+ //
+ // You may consider using rate_limiter to regulate write rate to device.
+ // When rate limiter is enabled, it automatically enables bytes_per_sync
+ // to 1MB.
+ //
+ // This option applies to table files
+ //
+ // Default: 0, turned off
+ //
+ // Note: DOES NOT apply to WAL files. See wal_bytes_per_sync instead
+ // Dynamically changeable through SetDBOptions() API.
+ uint64_t bytes_per_sync = 0;
+
+ // Same as bytes_per_sync, but applies to WAL files
+ //
+ // Default: 0, turned off
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ uint64_t wal_bytes_per_sync = 0;
+
+ // When true, guarantees WAL files have at most `wal_bytes_per_sync`
+ // bytes submitted for writeback at any given time, and SST files have at most
+ // `bytes_per_sync` 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 `bytes_per_sync` / `wal_bytes_per_sync` 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
+ bool strict_bytes_per_sync = false;
+
+ // A vector of EventListeners whose callback functions will be called
+ // when specific RocksDB event happens.
+ std::vector<std::shared_ptr<EventListener>> listeners;
+
+ // 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 = false;
+
+ // The limited write rate to DB if soft_pending_compaction_bytes_limit or
+ // level0_slowdown_writes_trigger 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: byte per second.
+ //
+ // Default: 0
+ //
+ // Dynamically changeable through SetDBOptions() API.
+ uint64_t delayed_write_rate = 0;
+
+ // 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 enable_pipelined_write 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
+ bool enable_pipelined_write = false;
+
+ // Setting unordered_write 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
+ // two_write_queues=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.
+ //
+ // Default: false
+ bool unordered_write = false;
+
+ // If true, allow multi-writers to update mem tables in parallel.
+ // Only some memtable_factory-s 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 enable_write_thread_adaptive_yield
+ // if you are going to use this feature.
+ //
+ // Default: true
+ bool allow_concurrent_memtable_write = true;
+
+ // If true, threads synchronizing with the write batch group leader will
+ // wait for up to write_thread_max_yield_usec before blocking on a mutex.
+ // This can substantially improve throughput for concurrent workloads,
+ // regardless of whether allow_concurrent_memtable_write is enabled.
+ //
+ // Default: true
+ bool enable_write_thread_adaptive_yield = true;
+
+ // 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
+ uint64_t max_write_batch_group_size_bytes = 1 << 20;
+
+ // 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 write_thread_slow_yield_usec is
+ // set properly) increasing this value is likely to increase RocksDB
+ // throughput at the expense of increased CPU usage.
+ //
+ // Default: 100
+ uint64_t write_thread_max_yield_usec = 100;
+
+ // 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
+ uint64_t write_thread_slow_yield_usec = 3;
+
+ // 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
+ bool skip_stats_update_on_db_open = false;
+
+ // If true, then DB::Open() 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 paranoid_checks is false, this option is ignored, and sst files are
+ // not checked at all.
+ //
+ // Default: false
+ bool skip_checking_sst_file_sizes_on_db_open = false;
+
+ // Recovery mode to control the consistency while replaying WAL
+ // Default: kPointInTimeRecovery
+ WALRecoveryMode wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
+
+ // if set to false then recovery will fail when a prepared
+ // transaction is encountered in the WAL
+ bool allow_2pc = false;
+
+ // A global cache for table-level rows.
+ // Default: nullptr (disabled)
+ // Not supported in ROCKSDB_LITE mode!
+ std::shared_ptr<Cache> row_cache = nullptr;
+
+#ifndef ROCKSDB_LITE
+ // 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.
+ WalFilter* wal_filter = nullptr;
+#endif // ROCKSDB_LITE
+
+ // If true, then DB::Open / CreateColumnFamily / DropColumnFamily
+ // / SetOptions will fail if options file is not detected or properly
+ // persisted.
+ //
+ // DEFAULT: false
+ bool fail_if_options_file_error = false;
+
+ // If true, then print malloc stats together with rocksdb.stats
+ // when printing to LOG.
+ // DEFAULT: false
+ bool dump_malloc_stats = false;
+
+ // 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
+ bool avoid_flush_during_recovery = false;
+
+ // 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 SetDBOptions() API.
+ bool avoid_flush_during_shutdown = false;
+
+ // 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
+ // Immutable.
+ bool allow_ingest_behind = false;
+
+ // Needed to support differential snapshots.
+ // If set to true then DB will only process deletes with sequence number
+ // less than what was set by SetPreserveDeletesSequenceNumber(uint64_t ts).
+ // Clients are responsible to periodically call this method to advance
+ // the cutoff time. If this method is never called and preserve_deletes
+ // is set to true NO deletes will ever be processed.
+ // At the moment this only keeps normal deletes, SingleDeletes will
+ // not be preserved.
+ // DEFAULT: false
+ // Immutable (TODO: make it dynamically changeable)
+ bool preserve_deletes = false;
+
+ // 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.
+ bool two_write_queues = false;
+
+ // 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.
+ bool manual_wal_flush = false;
+
+ // 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 DB::Flush.
+ // 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.
+ bool atomic_flush = false;
+
+ // 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
+ // ReadOptions::background_purge_on_iterator_cleanup.
+ bool avoid_unnecessary_blocking_io = false;
+
+ // 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
+ bool write_dbid_to_manifest = false;
+
+ // 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
+ size_t log_readahead_size = 0;
+
+ // If user does NOT provide SST file checksum function, the SST file checksum
+ // will NOT be used. The single checksum instance are shared by options and
+ // file writers. Make sure the algorithm is thread safe.
+ //
+ // Default: nullptr
+ std::shared_ptr<FileChecksumFunc> sst_file_checksum_func = nullptr;
+};
+
+// Options to control the behavior of a database (passed to DB::Open)
+struct Options : public DBOptions, public ColumnFamilyOptions {
+ // Create an Options object with default values for all fields.
+ Options() : DBOptions(), ColumnFamilyOptions() {}
+
+ Options(const DBOptions& db_options,
+ const ColumnFamilyOptions& column_family_options)
+ : DBOptions(db_options), ColumnFamilyOptions(column_family_options) {}
+
+ // The function recovers options to the option as in version 4.6.
+ Options* OldDefaults(int rocksdb_major_version = 4,
+ int rocksdb_minor_version = 6);
+
+ void Dump(Logger* log) const;
+
+ void DumpCFOptions(Logger* log) const;
+
+ // Some functions that make it easier to optimize RocksDB
+
+ // 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.
+ Options* PrepareForBulkLoad();
+
+ // Use this if your DB is very small (like under 1GB) and you don't want to
+ // spend lots of memory for memtables.
+ Options* OptimizeForSmallDb();
+};
+
+//
+// An application can issue a read request (via Get/Iterators) and specify
+// if that read should process data that ALREADY resides on a specified cache
+// level. For example, if an application specifies kBlockCacheTier then the
+// Get call will process data that is already processed in the memtable or
+// the block cache. It will not page in data from the OS cache or data that
+// resides in storage.
+enum ReadTier {
+ kReadAllTier = 0x0, // data in memtable, block cache, OS cache or storage
+ kBlockCacheTier = 0x1, // data in memtable or block cache
+ kPersistedTier = 0x2, // persisted data. When WAL is disabled, this option
+ // will skip data in memtable.
+ // Note that this ReadTier currently only supports
+ // Get and MultiGet and does not support iterators.
+ kMemtableTier = 0x3 // data in memtable. used for memtable-only iterators.
+};
+
+// Options that control read operations
+struct ReadOptions {
+ // 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: nullptr
+ const Snapshot* snapshot;
+
+ // `iterate_lower_bound` defines the smallest key at which the backward
+ // iterator can return an entry. Once the bound is passed, Valid() will be
+ // false. `iterate_lower_bound` is inclusive ie 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: nullptr
+ const Slice* iterate_lower_bound;
+
+ // "iterate_upper_bound" defines the extent upto which the forward iterator
+ // can returns entries. Once the bound is reached, Valid() will be false.
+ // "iterate_upper_bound" is exclusive ie 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: nullptr
+ const Slice* iterate_upper_bound;
+
+ // RocksDB does auto-readahead for iterators on noticing more than two reads
+ // for a table file. The readahead starts at 8KB and doubles on every
+ // additional read upto 256KB.
+ // This option can help if most of the range scans are large, and if it is
+ // determined that a larger readahead than that enabled by auto-readahead is
+ // needed.
+ // Using a large readahead size (> 2MB) can typically improve the performance
+ // of forward iteration on spinning disks.
+ // Default: 0
+ size_t readahead_size;
+
+ // 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
+ uint64_t max_skippable_internal_keys;
+
+ // 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 Status::Incomplete is returned.
+ // Default: kReadAllTier
+ ReadTier read_tier;
+
+ // If true, all data read from underlying storage will be
+ // verified against corresponding checksums.
+ // Default: true
+ bool verify_checksums;
+
+ // Should the "data block"/"index block"" read for this iteration be placed in
+ // block cache?
+ // Callers may wish to set this field to false for bulk scans.
+ // This would help not to the change eviction order of existing items in the
+ // block cache. Default: true
+ bool fill_cache;
+
+ // 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
+ // Not supported in ROCKSDB_LITE mode!
+ bool tailing;
+
+ // This options is not used anymore. It was to turn on a functionality that
+ // has been removed.
+ bool managed;
+
+ // 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.
+ // If true when calling Get(), we also skip prefix bloom when reading from
+ // block based table. It provides a way to read existing data after
+ // changing implementation of prefix extractor.
+ bool total_order_seek;
+
+ // 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.
+ // Not suppported in ROCKSDB_LITE mode, in the way that even with value true
+ // prefix mode is not used.
+ bool auto_prefix_mode;
+
+ // 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 total_order_seek is false. Unlike
+ // iterate_upper_bound, prefix_same_as_start only works within a prefix
+ // but in both directions.
+ // Default: false
+ bool prefix_same_as_start;
+
+ // 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.
+ // Default: false
+ bool pin_data;
+
+ // 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
+ bool background_purge_on_iterator_cleanup;
+
+ // 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
+ bool ignore_range_deletions;
+
+ // 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: empty (every table will be scanned)
+ std::function<bool(const TableProperties&)> table_filter;
+
+ // Needed to support differential snapshots. Has 2 effects:
+ // 1) Iterator will skip all internal keys with seqnum < iter_start_seqnum
+ // 2) if this param > 0 iterator will return INTERNAL keys instead of
+ // user keys; e.g. return tombstones as well.
+ // Default: 0 (don't filter by seqnum, return user keys)
+ SequenceNumber iter_start_seqnum;
+
+ // 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.
+ // The user-specified timestamp feature is still under active development,
+ // and the API is subject to change.
+ const Slice* timestamp;
+
+ ReadOptions();
+ ReadOptions(bool cksum, bool cache);
+};
+
+// Options that control write operations
+struct WriteOptions {
+ // 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
+ bool sync;
+
+ // If true, writes will not first go to the write ahead log,
+ // and the write may get 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.
+ // Default: false
+ bool disableWAL;
+
+ // 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
+ bool ignore_missing_column_families;
+
+ // If true and we need to wait or sleep for the write request, fails
+ // immediately with Status::Incomplete().
+ // Default: false
+ bool no_slowdown;
+
+ // If true, this write request is of lower priority if compaction is
+ // behind. In this case, no_slowdown = true, the request will be cancelled
+ // immediately with Status::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
+ bool low_pri;
+
+ // 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 concurrent_memtable_writes is false) this
+ // option will be ignored.
+ //
+ // Default: false
+ bool memtable_insert_hint_per_batch;
+
+ // Timestamp of write operation, e.g. Put. All timestamps of the same
+ // database must share the same length and format. The user is also
+ // responsible for providing a customized compare function via Comparator to
+ // order <key, timestamp> tuples. If the user wants to enable timestamp, then
+ // all write operations must be associated with timestamp because RocksDB, as
+ // a single-node storage engine currently has no knowledge of global time,
+ // thus has to rely on the application.
+ // The user-specified timestamp feature is still under active development,
+ // and the API is subject to change.
+ const Slice* timestamp;
+
+ WriteOptions()
+ : sync(false),
+ disableWAL(false),
+ ignore_missing_column_families(false),
+ no_slowdown(false),
+ low_pri(false),
+ memtable_insert_hint_per_batch(false),
+ timestamp(nullptr) {}
+};
+
+// Options that control flush operations
+struct FlushOptions {
+ // If true, the flush will wait until the flush is done.
+ // Default: true
+ bool wait;
+ // If true, the flush would proceed immediately even it means writes will
+ // stall for the duration of the flush; if false the operation will wait
+ // until it's possible to do flush w/o causing stall or until required flush
+ // is performed by someone else (foreground call or background thread).
+ // Default: false
+ bool allow_write_stall;
+ FlushOptions() : wait(true), allow_write_stall(false) {}
+};
+
+// Create a Logger from provided DBOptions
+extern Status CreateLoggerFromOptions(const std::string& dbname,
+ const DBOptions& options,
+ std::shared_ptr<Logger>* logger);
+
+// CompactionOptions are used in CompactFiles() call.
+struct CompactionOptions {
+ // Compaction output compression type
+ // Default: snappy
+ // If set to `kDisableCompressionOption`, RocksDB will choose compression type
+ // according to the `ColumnFamilyOptions`, taking into account the output
+ // level if `compression_per_level` is specified.
+ CompressionType compression;
+ // Compaction will create files of size `output_file_size_limit`.
+ // Default: MAX, which means that compaction will create a single file
+ uint64_t output_file_size_limit;
+ // If > 0, it will replace the option in the DBOptions for this compaction.
+ uint32_t max_subcompactions;
+
+ CompactionOptions()
+ : compression(kSnappyCompression),
+ output_file_size_limit(std::numeric_limits<uint64_t>::max()),
+ max_subcompactions(0) {}
+};
+
+// For level based compaction, we can configure if we want to skip/force
+// bottommost level compaction.
+enum class BottommostLevelCompaction {
+ // Skip bottommost level compaction
+ kSkip,
+ // Only compact bottommost level if there is a compaction filter
+ // This is the default option
+ kIfHaveCompactionFilter,
+ // Always compact bottommost level
+ kForce,
+ // Always compact bottommost level but in bottommost level avoid
+ // double-compacting files created in the same compaction
+ kForceOptimized,
+};
+
+// CompactRangeOptions is used by CompactRange() call.
+struct CompactRangeOptions {
+ // If true, no other compaction will run at the same time as this
+ // manual compaction
+ bool exclusive_manual_compaction = true;
+ // If true, compacted files will be moved to the minimum level capable
+ // of holding the data or given level (specified non-negative target_level).
+ bool change_level = false;
+ // If change_level is true and target_level have non-negative value, compacted
+ // files will be moved to target_level.
+ int target_level = -1;
+ // Compaction outputs will be placed in options.db_paths[target_path_id].
+ // Behavior is undefined if target_path_id is out of range.
+ uint32_t target_path_id = 0;
+ // By default level based compaction will only compact the bottommost level
+ // if there is a compaction filter
+ BottommostLevelCompaction bottommost_level_compaction =
+ BottommostLevelCompaction::kIfHaveCompactionFilter;
+ // If true, 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.
+ bool allow_write_stall = false;
+ // If > 0, it will replace the option in the DBOptions for this compaction.
+ uint32_t max_subcompactions = 0;
+};
+
+// IngestExternalFileOptions is used by IngestExternalFile()
+struct IngestExternalFileOptions {
+ // Can be set to true to move the files instead of copying them.
+ bool move_files = false;
+ // If set to true, ingestion falls back to copy when move fails.
+ bool failed_move_fall_back_to_copy = true;
+ // If set to false, an ingested file keys could appear in existing snapshots
+ // that where created before the file was ingested.
+ bool snapshot_consistency = true;
+ // If set to false, IngestExternalFile() will fail if the file key range
+ // overlaps with existing keys or tombstones in the DB.
+ bool allow_global_seqno = true;
+ // If set to false and the file key range overlaps with the memtable key range
+ // (memtable flush required), IngestExternalFile will fail.
+ bool allow_blocking_flush = true;
+ // 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 allow_ingest_behind=true since the dawn of time.
+ // All files will be ingested at the bottommost level with seqno=0.
+ bool ingest_behind = false;
+ // Set to true if you would like to write global_seqno to a given offset in
+ // the external SST file for backward compatibility. Older versions of
+ // RocksDB writes a global_seqno to a given offset within 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 'write_global_seqno' 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.
+ bool write_global_seqno = true;
+ // Set to true if you would like to verify the checksums of each block of the
+ // external SST file before ingestion.
+ // Warning: setting this to true causes slowdown in file ingestion because
+ // the external SST file has to be read.
+ bool verify_checksums_before_ingest = false;
+ // When verify_checksums_before_ingest = true, RocksDB uses default
+ // readahead setting to scan the file while verifying checksums before
+ // ingestion.
+ // Users can override the default value using this option.
+ // Using a large readahead size (> 2MB) can typically improve the performance
+ // of forward iteration on spinning disks.
+ size_t verify_checksums_readahead_size = 0;
+};
+
+enum TraceFilterType : uint64_t {
+ // Trace all the operations
+ kTraceFilterNone = 0x0,
+ // Do not trace the get operations
+ kTraceFilterGet = 0x1 << 0,
+ // Do not trace the write operations
+ kTraceFilterWrite = 0x1 << 1
+};
+
+// TraceOptions is used for StartTrace
+struct TraceOptions {
+ // To avoid the trace file size grows large than the storage space,
+ // user can set the max trace file size in Bytes. Default is 64GB
+ uint64_t max_trace_file_size = uint64_t{64} * 1024 * 1024 * 1024;
+ // Specify trace sampling option, i.e. capture one per how many requests.
+ // Default to 1 (capture every request).
+ uint64_t sampling_frequency = 1;
+ // Note: The filtering happens before sampling.
+ uint64_t filter = kTraceFilterNone;
+};
+
+// ImportColumnFamilyOptions is used by ImportColumnFamily()
+struct ImportColumnFamilyOptions {
+ // Can be set to true to move the files instead of copying them.
+ bool move_files = false;
+};
+
+// Options used with DB::GetApproximateSizes()
+struct SizeApproximationOptions {
+ // Defines whether the returned size should include the recently written
+ // data in the mem-tables. If set to false, include_files must be true.
+ bool include_memtabtles = false;
+ // Defines whether the returned size should include data serialized to disk.
+ // If set to false, include_memtabtles must be true.
+ bool include_files = true;
+ // When approximating the files total size that is used to store a keys range
+ // using DB::GetApproximateSizes, allow approximation with an error margin of
+ // up to total_files_size * files_size_error_margin. This allows to take some
+ // shortcuts in files size approximation, resulting in better performance,
+ // while guaranteeing the resulting error is within a reasonable margin.
+ // E.g., if the value is 0.1, then the error margin of the returned files size
+ // approximation will be within 10%.
+ // If the value is non-positive - a more precise yet more CPU intensive
+ // estimation is performed.
+ double files_size_error_margin = -1.0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/perf_context.h b/storage/rocksdb/rocksdb/include/rocksdb/perf_context.h
new file mode 100644
index 00000000..123a21bc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/perf_context.h
@@ -0,0 +1,232 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <map>
+#include <string>
+
+#include "rocksdb/perf_level.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A thread local context for gathering performance counter efficiently
+// and transparently.
+// Use SetPerfLevel(PerfLevel::kEnableTime) to enable time stats.
+
+// Break down performance counters by level and store per-level perf context in
+// PerfContextByLevel
+struct PerfContextByLevel {
+ // # of times bloom filter has avoided file reads, i.e., negatives.
+ uint64_t bloom_filter_useful = 0;
+ // # of times bloom FullFilter has not avoided the reads.
+ uint64_t bloom_filter_full_positive = 0;
+ // # of times bloom FullFilter has not avoided the reads and data actually
+ // exist.
+ uint64_t bloom_filter_full_true_positive = 0;
+
+ // total number of user key returned (only include keys that are found, does
+ // not include keys that are deleted or merged without a final put
+ uint64_t user_key_return_count;
+
+ // total nanos spent on reading data from SST files
+ uint64_t get_from_table_nanos;
+
+ uint64_t block_cache_hit_count = 0; // total number of block cache hits
+ uint64_t block_cache_miss_count = 0; // total number of block cache misses
+
+ void Reset(); // reset all performance counters to zero
+};
+
+struct PerfContext {
+ ~PerfContext();
+
+ PerfContext() {}
+
+ PerfContext(const PerfContext&);
+ PerfContext& operator=(const PerfContext&);
+ PerfContext(PerfContext&&) noexcept;
+
+ void Reset(); // reset all performance counters to zero
+
+ std::string ToString(bool exclude_zero_counters = false) const;
+
+ // enable per level perf context and allocate storage for PerfContextByLevel
+ void EnablePerLevelPerfContext();
+
+ // temporarily disable per level perf contxt by setting the flag to false
+ void DisablePerLevelPerfContext();
+
+ // free the space for PerfContextByLevel, also disable per level perf context
+ void ClearPerLevelPerfContext();
+
+ uint64_t user_key_comparison_count; // total number of user key comparisons
+ uint64_t block_cache_hit_count; // total number of block cache hits
+ uint64_t block_read_count; // total number of block reads (with IO)
+ uint64_t block_read_byte; // total number of bytes from block reads
+ uint64_t block_read_time; // total nanos spent on block reads
+ uint64_t block_cache_index_hit_count; // total number of index block hits
+ uint64_t index_block_read_count; // total number of index block reads
+ uint64_t block_cache_filter_hit_count; // total number of filter block hits
+ uint64_t filter_block_read_count; // total number of filter block reads
+ uint64_t compression_dict_block_read_count; // total number of compression
+ // dictionary block reads
+ uint64_t block_checksum_time; // total nanos spent on block checksum
+ uint64_t block_decompress_time; // total nanos spent on block decompression
+
+ uint64_t get_read_bytes; // bytes for vals returned by Get
+ uint64_t multiget_read_bytes; // bytes for vals returned by MultiGet
+ uint64_t iter_read_bytes; // bytes for keys/vals decoded by iterator
+
+ // total number of internal keys skipped over during iteration.
+ // There are several reasons for it:
+ // 1. when calling Next(), the iterator is in the position of the previous
+ // key, so that we'll need to skip it. It means this counter will always
+ // be incremented in Next().
+ // 2. when calling Next(), we need to skip internal entries for the previous
+ // keys that are overwritten.
+ // 3. when calling Next(), Seek() or SeekToFirst(), after previous key
+ // before calling Next(), the seek key in Seek() or the beginning for
+ // SeekToFirst(), there may be one or more deleted keys before the next
+ // valid key that the operation should place the iterator to. We need
+ // to skip both of the tombstone and updates hidden by the tombstones. The
+ // tombstones are not included in this counter, while previous updates
+ // hidden by the tombstones will be included here.
+ // 4. symmetric cases for Prev() and SeekToLast()
+ // internal_recent_skipped_count is not included in this counter.
+ //
+ uint64_t internal_key_skipped_count;
+ // Total number of deletes and single deletes skipped over during iteration
+ // When calling Next(), Seek() or SeekToFirst(), after previous position
+ // before calling Next(), the seek key in Seek() or the beginning for
+ // SeekToFirst(), there may be one or more deleted keys before the next valid
+ // key. Every deleted key is counted once. We don't recount here if there are
+ // still older updates invalidated by the tombstones.
+ //
+ uint64_t internal_delete_skipped_count;
+ // How many times iterators skipped over internal keys that are more recent
+ // than the snapshot that iterator is using.
+ //
+ uint64_t internal_recent_skipped_count;
+ // How many values were fed into merge operator by iterators.
+ //
+ uint64_t internal_merge_count;
+
+ uint64_t get_snapshot_time; // total nanos spent on getting snapshot
+ uint64_t get_from_memtable_time; // total nanos spent on querying memtables
+ uint64_t get_from_memtable_count; // number of mem tables queried
+ // total nanos spent after Get() finds a key
+ uint64_t get_post_process_time;
+ uint64_t get_from_output_files_time; // total nanos reading from output files
+ // total nanos spent on seeking memtable
+ uint64_t seek_on_memtable_time;
+ // number of seeks issued on memtable
+ // (including SeekForPrev but not SeekToFirst and SeekToLast)
+ uint64_t seek_on_memtable_count;
+ // number of Next()s issued on memtable
+ uint64_t next_on_memtable_count;
+ // number of Prev()s issued on memtable
+ uint64_t prev_on_memtable_count;
+ // total nanos spent on seeking child iters
+ uint64_t seek_child_seek_time;
+ // number of seek issued in child iterators
+ uint64_t seek_child_seek_count;
+ uint64_t seek_min_heap_time; // total nanos spent on the merge min heap
+ uint64_t seek_max_heap_time; // total nanos spent on the merge max heap
+ // total nanos spent on seeking the internal entries
+ uint64_t seek_internal_seek_time;
+ // total nanos spent on iterating internal entries to find the next user entry
+ uint64_t find_next_user_entry_time;
+
+ // This group of stats provide a breakdown of time spent by Write().
+ // May be inaccurate when 2PC, two_write_queues or enable_pipelined_write
+ // are enabled.
+ //
+ // total nanos spent on writing to WAL
+ uint64_t write_wal_time;
+ // total nanos spent on writing to mem tables
+ uint64_t write_memtable_time;
+ // total nanos spent on delaying or throttling write
+ uint64_t write_delay_time;
+ // total nanos spent on switching memtable/wal and scheduling
+ // flushes/compactions.
+ uint64_t write_scheduling_flushes_compactions_time;
+ // total nanos spent on writing a record, excluding the above four things
+ uint64_t write_pre_and_post_process_time;
+
+ // time spent waiting for other threads of the batch group
+ uint64_t write_thread_wait_nanos;
+
+ // time spent on acquiring DB mutex.
+ uint64_t db_mutex_lock_nanos;
+ // Time spent on waiting with a condition variable created with DB mutex.
+ uint64_t db_condition_wait_nanos;
+ // Time spent on merge operator.
+ uint64_t merge_operator_time_nanos;
+
+ // Time spent on reading index block from block cache or SST file
+ uint64_t read_index_block_nanos;
+ // Time spent on reading filter block from block cache or SST file
+ uint64_t read_filter_block_nanos;
+ // Time spent on creating data block iterator
+ uint64_t new_table_block_iter_nanos;
+ // Time spent on creating a iterator of an SST file.
+ uint64_t new_table_iterator_nanos;
+ // Time spent on seeking a key in data/index blocks
+ uint64_t block_seek_nanos;
+ // Time spent on finding or creating a table reader
+ uint64_t find_table_nanos;
+ // total number of mem table bloom hits
+ uint64_t bloom_memtable_hit_count;
+ // total number of mem table bloom misses
+ uint64_t bloom_memtable_miss_count;
+ // total number of SST table bloom hits
+ uint64_t bloom_sst_hit_count;
+ // total number of SST table bloom misses
+ uint64_t bloom_sst_miss_count;
+
+ // Time spent waiting on key locks in transaction lock manager.
+ uint64_t key_lock_wait_time;
+ // number of times acquiring a lock was blocked by another transaction.
+ uint64_t key_lock_wait_count;
+
+ // Total time spent in Env filesystem operations. These are only populated
+ // when TimedEnv is used.
+ uint64_t env_new_sequential_file_nanos;
+ uint64_t env_new_random_access_file_nanos;
+ uint64_t env_new_writable_file_nanos;
+ uint64_t env_reuse_writable_file_nanos;
+ uint64_t env_new_random_rw_file_nanos;
+ uint64_t env_new_directory_nanos;
+ uint64_t env_file_exists_nanos;
+ uint64_t env_get_children_nanos;
+ uint64_t env_get_children_file_attributes_nanos;
+ uint64_t env_delete_file_nanos;
+ uint64_t env_create_dir_nanos;
+ uint64_t env_create_dir_if_missing_nanos;
+ uint64_t env_delete_dir_nanos;
+ uint64_t env_get_file_size_nanos;
+ uint64_t env_get_file_modification_time_nanos;
+ uint64_t env_rename_file_nanos;
+ uint64_t env_link_file_nanos;
+ uint64_t env_lock_file_nanos;
+ uint64_t env_unlock_file_nanos;
+ uint64_t env_new_logger_nanos;
+
+ uint64_t get_cpu_nanos;
+ uint64_t iter_next_cpu_nanos;
+ uint64_t iter_prev_cpu_nanos;
+ uint64_t iter_seek_cpu_nanos;
+
+ std::map<uint32_t, PerfContextByLevel>* level_to_perf_context = nullptr;
+ bool per_level_perf_context_enabled = false;
+};
+
+// Get Thread-local PerfContext object pointer
+// if defined(NPERF_CONTEXT), then the pointer is not thread-local
+PerfContext* get_perf_context();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/perf_level.h b/storage/rocksdb/rocksdb/include/rocksdb/perf_level.h
new file mode 100644
index 00000000..e6a76890
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/perf_level.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// How much perf stats to collect. Affects perf_context and iostats_context.
+enum PerfLevel : unsigned char {
+ kUninitialized = 0, // unknown setting
+ kDisable = 1, // disable perf stats
+ kEnableCount = 2, // enable only count stats
+ kEnableTimeExceptForMutex = 3, // Other than count stats, also enable time
+ // stats except for mutexes
+ // Other than time, also measure CPU time counters. Still don't measure
+ // time (neither wall time nor CPU time) for mutexes.
+ kEnableTimeAndCPUTimeExceptForMutex = 4,
+ kEnableTime = 5, // enable count and time stats
+ kOutOfBounds = 6 // N.B. Must always be the last value!
+};
+
+// set the perf stats level for current thread
+void SetPerfLevel(PerfLevel level);
+
+// get current perf stats level for current thread
+PerfLevel GetPerfLevel();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/persistent_cache.h b/storage/rocksdb/rocksdb/include/rocksdb/persistent_cache.h
new file mode 100644
index 00000000..9651812c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/persistent_cache.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <memory>
+#include <string>
+
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// PersistentCache
+//
+// Persistent cache interface for caching IO pages on a persistent medium. The
+// cache interface is specifically designed for persistent read cache.
+class PersistentCache {
+ public:
+ typedef std::vector<std::map<std::string, double>> StatsType;
+
+ virtual ~PersistentCache() {}
+
+ // Insert to page cache
+ //
+ // page_key Identifier to identify a page uniquely across restarts
+ // data Page data
+ // size Size of the page
+ virtual Status Insert(const Slice& key, const char* data,
+ const size_t size) = 0;
+
+ // Lookup page cache by page identifier
+ //
+ // page_key Page identifier
+ // buf Buffer where the data should be copied
+ // size Size of the page
+ virtual Status Lookup(const Slice& key, std::unique_ptr<char[]>* data,
+ size_t* size) = 0;
+
+ // Is cache storing uncompressed data ?
+ //
+ // True if the cache is configured to store uncompressed data else false
+ virtual bool IsCompressed() = 0;
+
+ // Return stats as map of {string, double} per-tier
+ //
+ // Persistent cache can be initialized as a tier of caches. The stats are per
+ // tire top-down
+ virtual StatsType Stats() = 0;
+
+ virtual std::string GetPrintableOptions() const = 0;
+};
+
+// Factor method to create a new persistent cache
+Status NewPersistentCache(Env* const env, const std::string& path,
+ const uint64_t size,
+ const std::shared_ptr<Logger>& log,
+ const bool optimized_for_nvm,
+ std::shared_ptr<PersistentCache>* cache);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/rate_limiter.h b/storage/rocksdb/rocksdb/include/rocksdb/rate_limiter.h
new file mode 100644
index 00000000..0ee89f5c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/rate_limiter.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RateLimiter {
+ public:
+ enum class OpType {
+ // Limitation: we currently only invoke Request() with OpType::kRead for
+ // compactions when DBOptions::new_table_reader_for_compaction_inputs is set
+ kRead,
+ kWrite,
+ };
+ enum class Mode {
+ kReadsOnly,
+ kWritesOnly,
+ kAllIo,
+ };
+
+ // For API compatibility, default to rate-limiting writes only.
+ explicit RateLimiter(Mode mode = Mode::kWritesOnly) : mode_(mode) {}
+
+ virtual ~RateLimiter() {}
+
+ // This API allows user to dynamically change rate limiter's bytes per second.
+ // REQUIRED: bytes_per_second > 0
+ virtual void SetBytesPerSecond(int64_t bytes_per_second) = 0;
+
+ // Deprecated. New RateLimiter derived classes should override
+ // Request(const int64_t, const Env::IOPriority, Statistics*) or
+ // Request(const int64_t, const Env::IOPriority, Statistics*, OpType)
+ // instead.
+ //
+ // Request for token for bytes. If this request can not be satisfied, the call
+ // is blocked. Caller is responsible to make sure
+ // bytes <= GetSingleBurstBytes()
+ virtual void Request(const int64_t /*bytes*/, const Env::IOPriority /*pri*/) {
+ assert(false);
+ }
+
+ // Request for token for bytes and potentially update statistics. If this
+ // request can not be satisfied, the call is blocked. Caller is responsible to
+ // make sure bytes <= GetSingleBurstBytes().
+ virtual void Request(const int64_t bytes, const Env::IOPriority pri,
+ Statistics* /* stats */) {
+ // For API compatibility, default implementation calls the older API in
+ // which statistics are unsupported.
+ Request(bytes, pri);
+ }
+
+ // Requests token to read or write bytes and potentially updates statistics.
+ //
+ // If this request can not be satisfied, the call is blocked. Caller is
+ // responsible to make sure bytes <= GetSingleBurstBytes().
+ virtual void Request(const int64_t bytes, const Env::IOPriority pri,
+ Statistics* stats, OpType op_type) {
+ if (IsRateLimited(op_type)) {
+ Request(bytes, pri, stats);
+ }
+ }
+
+ // Requests token to read or write bytes and potentially updates statistics.
+ // Takes into account GetSingleBurstBytes() and alignment (e.g., in case of
+ // direct I/O) to allocate an appropriate number of bytes, which may be less
+ // than the number of bytes requested.
+ virtual size_t RequestToken(size_t bytes, size_t alignment,
+ Env::IOPriority io_priority, Statistics* stats,
+ RateLimiter::OpType op_type);
+
+ // Max bytes can be granted in a single burst
+ virtual int64_t GetSingleBurstBytes() const = 0;
+
+ // Total bytes that go through rate limiter
+ virtual int64_t GetTotalBytesThrough(
+ const Env::IOPriority pri = Env::IO_TOTAL) const = 0;
+
+ // Total # of requests that go through rate limiter
+ virtual int64_t GetTotalRequests(
+ const Env::IOPriority pri = Env::IO_TOTAL) const = 0;
+
+ virtual int64_t GetBytesPerSecond() const = 0;
+
+ virtual bool IsRateLimited(OpType op_type) {
+ if ((mode_ == RateLimiter::Mode::kWritesOnly &&
+ op_type == RateLimiter::OpType::kRead) ||
+ (mode_ == RateLimiter::Mode::kReadsOnly &&
+ op_type == RateLimiter::OpType::kWrite)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected:
+ Mode GetMode() { return mode_; }
+
+ private:
+ const Mode mode_;
+};
+
+// Create a RateLimiter object, which can be shared among RocksDB instances to
+// control write rate of flush and compaction.
+// @rate_bytes_per_sec: 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.
+// @refill_period_us: 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 should work for most cases.
+// @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
+// 1/fairness chance even though high-pri requests exist to avoid starvation.
+// You should be good by leaving it at default 10.
+// @mode: Mode indicates which types of operations count against the limit.
+// @auto_tuned: Enables dynamic adjustment of rate limit within the range
+// `[rate_bytes_per_sec / 20, rate_bytes_per_sec]`, according to
+// the recent demand for background I/O.
+extern RateLimiter* NewGenericRateLimiter(
+ int64_t rate_bytes_per_sec, int64_t refill_period_us = 100 * 1000,
+ int32_t fairness = 10,
+ RateLimiter::Mode mode = RateLimiter::Mode::kWritesOnly,
+ bool auto_tuned = false);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/rocksdb_namespace.h b/storage/rocksdb/rocksdb/include/rocksdb/rocksdb_namespace.h
new file mode 100644
index 00000000..e9f8620d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/rocksdb_namespace.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_NAMESPACE
+#define ROCKSDB_NAMESPACE rocksdb
+#endif
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/slice.h b/storage/rocksdb/rocksdb/include/rocksdb/slice.h
new file mode 100644
index 00000000..c17b32c5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/slice.h
@@ -0,0 +1,269 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Slice is a simple structure containing a pointer into some external
+// storage and a size. The user of a Slice must ensure that the slice
+// is not used after the corresponding external storage has been
+// deallocated.
+//
+// Multiple threads can invoke const methods on a Slice without
+// external synchronization, but if any of the threads may call a
+// non-const method, all threads accessing the same Slice must use
+// external synchronization.
+
+#pragma once
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <cstdio>
+#include <string>
+
+#ifdef __cpp_lib_string_view
+#include <string_view>
+#endif
+
+#include "rocksdb/cleanable.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice {
+ public:
+ // Create an empty slice.
+ Slice() : data_(""), size_(0) {}
+
+ // Create a slice that refers to d[0,n-1].
+ Slice(const char* d, size_t n) : data_(d), size_(n) {}
+
+ // Create a slice that refers to the contents of "s"
+ /* implicit */
+ Slice(const std::string& s) : data_(s.data()), size_(s.size()) {}
+
+#ifdef __cpp_lib_string_view
+ // Create a slice that refers to the same contents as "sv"
+ /* implicit */
+ Slice(std::string_view sv) : data_(sv.data()), size_(sv.size()) {}
+#endif
+
+ // Create a slice that refers to s[0,strlen(s)-1]
+ /* implicit */
+ Slice(const char* s) : data_(s) { size_ = (s == nullptr) ? 0 : strlen(s); }
+
+ // Create a single slice from SliceParts using buf as storage.
+ // buf must exist as long as the returned Slice exists.
+ Slice(const struct SliceParts& parts, std::string* buf);
+
+ // Return a pointer to the beginning of the referenced data
+ const char* data() const { return data_; }
+
+ // Return the length (in bytes) of the referenced data
+ size_t size() const { return size_; }
+
+ // Return true iff the length of the referenced data is zero
+ bool empty() const { return size_ == 0; }
+
+ // Return the ith byte in the referenced data.
+ // REQUIRES: n < size()
+ char operator[](size_t n) const {
+ assert(n < size());
+ return data_[n];
+ }
+
+ // Change this slice to refer to an empty array
+ void clear() {
+ data_ = "";
+ size_ = 0;
+ }
+
+ // Drop the first "n" bytes from this slice.
+ void remove_prefix(size_t n) {
+ assert(n <= size());
+ data_ += n;
+ size_ -= n;
+ }
+
+ void remove_suffix(size_t n) {
+ assert(n <= size());
+ size_ -= n;
+ }
+
+ // Return a string that contains the copy of the referenced data.
+ // when hex is true, returns a string of twice the length hex encoded (0-9A-F)
+ std::string ToString(bool hex = false) const;
+
+#ifdef __cpp_lib_string_view
+ // Return a string_view that references the same data as this slice.
+ std::string_view ToStringView() const {
+ return std::string_view(data_, size_);
+ }
+#endif
+
+ // Decodes the current slice interpreted as an hexadecimal string into result,
+ // if successful returns true, if this isn't a valid hex string
+ // (e.g not coming from Slice::ToString(true)) DecodeHex returns false.
+ // This slice is expected to have an even number of 0-9A-F characters
+ // also accepts lowercase (a-f)
+ bool DecodeHex(std::string* result) const;
+
+ // Three-way comparison. Returns value:
+ // < 0 iff "*this" < "b",
+ // == 0 iff "*this" == "b",
+ // > 0 iff "*this" > "b"
+ int compare(const Slice& b) const;
+
+ // Return true iff "x" is a prefix of "*this"
+ bool starts_with(const Slice& x) const {
+ return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0));
+ }
+
+ bool ends_with(const Slice& x) const {
+ return ((size_ >= x.size_) &&
+ (memcmp(data_ + size_ - x.size_, x.data_, x.size_) == 0));
+ }
+
+ // Compare two slices and returns the first byte where they differ
+ size_t difference_offset(const Slice& b) const;
+
+ // private: make these public for rocksdbjni access
+ const char* data_;
+ size_t size_;
+
+ // Intentionally copyable
+};
+
+/**
+ * A Slice that can be pinned with some cleanup tasks, which will be run upon
+ * ::Reset() or object destruction, whichever is invoked first. This can be used
+ * to avoid memcpy by having the PinnableSlice object referring to the data
+ * that is locked in the memory and release them after the data is consumed.
+ */
+class PinnableSlice : public Slice, public Cleanable {
+ public:
+ PinnableSlice() { buf_ = &self_space_; }
+ explicit PinnableSlice(std::string* buf) { buf_ = buf; }
+
+ PinnableSlice(PinnableSlice&& other);
+ PinnableSlice& operator=(PinnableSlice&& other);
+
+ // No copy constructor and copy assignment allowed.
+ PinnableSlice(PinnableSlice&) = delete;
+ PinnableSlice& operator=(PinnableSlice&) = delete;
+
+ inline void PinSlice(const Slice& s, CleanupFunction f, void* arg1,
+ void* arg2) {
+ assert(!pinned_);
+ pinned_ = true;
+ data_ = s.data();
+ size_ = s.size();
+ RegisterCleanup(f, arg1, arg2);
+ assert(pinned_);
+ }
+
+ inline void PinSlice(const Slice& s, Cleanable* cleanable) {
+ assert(!pinned_);
+ pinned_ = true;
+ data_ = s.data();
+ size_ = s.size();
+ cleanable->DelegateCleanupsTo(this);
+ assert(pinned_);
+ }
+
+ inline void PinSelf(const Slice& slice) {
+ assert(!pinned_);
+ buf_->assign(slice.data(), slice.size());
+ data_ = buf_->data();
+ size_ = buf_->size();
+ assert(!pinned_);
+ }
+
+ inline void PinSelf() {
+ assert(!pinned_);
+ data_ = buf_->data();
+ size_ = buf_->size();
+ assert(!pinned_);
+ }
+
+ void remove_suffix(size_t n) {
+ assert(n <= size());
+ if (pinned_) {
+ size_ -= n;
+ } else {
+ buf_->erase(size() - n, n);
+ PinSelf();
+ }
+ }
+
+ void remove_prefix(size_t n) {
+ assert(n <= size());
+ if (pinned_) {
+ data_ += n;
+ size_ -= n;
+ } else {
+ buf_->erase(0, n);
+ PinSelf();
+ }
+ }
+
+ void Reset() {
+ Cleanable::Reset();
+ pinned_ = false;
+ size_ = 0;
+ }
+
+ inline std::string* GetSelf() { return buf_; }
+
+ inline bool IsPinned() const { return pinned_; }
+
+ private:
+ friend class PinnableSlice4Test;
+ std::string self_space_;
+ std::string* buf_;
+ bool pinned_ = false;
+};
+
+// A set of Slices that are virtually concatenated together. 'parts' points
+// to an array of Slices. The number of elements in the array is 'num_parts'.
+struct SliceParts {
+ SliceParts(const Slice* _parts, int _num_parts)
+ : parts(_parts), num_parts(_num_parts) {}
+ SliceParts() : parts(nullptr), num_parts(0) {}
+
+ const Slice* parts;
+ int num_parts;
+};
+
+inline bool operator==(const Slice& x, const Slice& y) {
+ return ((x.size() == y.size()) &&
+ (memcmp(x.data(), y.data(), x.size()) == 0));
+}
+
+inline bool operator!=(const Slice& x, const Slice& y) { return !(x == y); }
+
+inline int Slice::compare(const Slice& b) const {
+ assert(data_ != nullptr && b.data_ != nullptr);
+ const size_t min_len = (size_ < b.size_) ? size_ : b.size_;
+ int r = memcmp(data_, b.data_, min_len);
+ if (r == 0) {
+ if (size_ < b.size_)
+ r = -1;
+ else if (size_ > b.size_)
+ r = +1;
+ }
+ return r;
+}
+
+inline size_t Slice::difference_offset(const Slice& b) const {
+ size_t off = 0;
+ const size_t len = (size_ < b.size_) ? size_ : b.size_;
+ for (; off < len; off++) {
+ if (data_[off] != b.data_[off]) break;
+ }
+ return off;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/slice_transform.h b/storage/rocksdb/rocksdb/include/rocksdb/slice_transform.h
new file mode 100644
index 00000000..54f61f9d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/slice_transform.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Class for specifying user-defined functions which perform a
+// transformation on a slice. It is not required that every slice
+// belong to the domain and/or range of a function. Subclasses should
+// define InDomain and InRange to determine which slices are in either
+// of these sets respectively.
+
+#pragma once
+
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+
+/*
+ * A SliceTransform is a generic pluggable way of transforming one string
+ * to another. Its primary use-case is in configuring rocksdb
+ * to store prefix blooms by setting prefix_extractor in
+ * ColumnFamilyOptions.
+ */
+class SliceTransform {
+ public:
+ virtual ~SliceTransform(){};
+
+ // Return the name of this transformation.
+ virtual const char* Name() const = 0;
+
+ // Extract a prefix from a specified key. This method is called when
+ // a key is inserted into the db, and the returned slice is used to
+ // create a bloom filter.
+ virtual Slice Transform(const Slice& key) const = 0;
+
+ // Determine whether the specified key is compatible with the logic
+ // specified in the Transform method. This method is invoked for every
+ // key that is inserted into the db. If this method returns true,
+ // then Transform is called to translate the key to its prefix and
+ // that returned prefix is inserted into the bloom filter. If this
+ // method returns false, then the call to Transform is skipped and
+ // no prefix is inserted into the bloom filters.
+ //
+ // For example, if the Transform method operates on a fixed length
+ // prefix of size 4, then an invocation to InDomain("abc") returns
+ // false because the specified key length(3) is shorter than the
+ // prefix size of 4.
+ //
+ // Wiki documentation here:
+ // https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes
+ //
+ virtual bool InDomain(const Slice& key) const = 0;
+
+ // This is currently not used and remains here for backward compatibility.
+ virtual bool InRange(const Slice& /*dst*/) const { return false; }
+
+ // Some SliceTransform will have a full length which can be used to
+ // determine if two keys are consecuitive. Can be disabled by always
+ // returning 0
+ virtual bool FullLengthEnabled(size_t* /*len*/) const { return false; }
+
+ // Transform(s)=Transform(`prefix`) for any s with `prefix` as a prefix.
+ //
+ // This function is not used by RocksDB, but for users. If users pass
+ // Options by string to RocksDB, they might not know what prefix extractor
+ // they are using. This function is to help users can determine:
+ // if they want to iterate all keys prefixing `prefix`, whether it is
+ // safe to use prefix bloom filter and seek to key `prefix`.
+ // If this function returns true, this means a user can Seek() to a prefix
+ // using the bloom filter. Otherwise, user needs to skip the bloom filter
+ // by setting ReadOptions.total_order_seek = true.
+ //
+ // Here is an example: Suppose we implement a slice transform that returns
+ // the first part of the string after splitting it using delimiter ",":
+ // 1. SameResultWhenAppended("abc,") should return true. If applying prefix
+ // bloom filter using it, all slices matching "abc:.*" will be extracted
+ // to "abc,", so any SST file or memtable containing any of those key
+ // will not be filtered out.
+ // 2. SameResultWhenAppended("abc") should return false. A user will not
+ // guaranteed to see all the keys matching "abc.*" if a user seek to "abc"
+ // against a DB with the same setting. If one SST file only contains
+ // "abcd,e", the file can be filtered out and the key will be invisible.
+ //
+ // i.e., an implementation always returning false is safe.
+ virtual bool SameResultWhenAppended(const Slice& /*prefix*/) const {
+ return false;
+ }
+};
+
+extern const SliceTransform* NewFixedPrefixTransform(size_t prefix_len);
+
+extern const SliceTransform* NewCappedPrefixTransform(size_t cap_len);
+
+extern const SliceTransform* NewNoopTransform();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/snapshot.h b/storage/rocksdb/rocksdb/include/rocksdb/snapshot.h
new file mode 100644
index 00000000..6a7212d6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/snapshot.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DB;
+
+// Abstract handle to particular state of a DB.
+// A Snapshot is an immutable object and can therefore be safely
+// accessed from multiple threads without any external synchronization.
+//
+// To Create a Snapshot, call DB::GetSnapshot().
+// To Destroy a Snapshot, call DB::ReleaseSnapshot(snapshot).
+class Snapshot {
+ public:
+ // returns Snapshot's sequence number
+ virtual SequenceNumber GetSequenceNumber() const = 0;
+
+ protected:
+ virtual ~Snapshot();
+};
+
+// Simple RAII wrapper class for Snapshot.
+// Constructing this object will create a snapshot. Destructing will
+// release the snapshot.
+class ManagedSnapshot {
+ public:
+ explicit ManagedSnapshot(DB* db);
+
+ // Instead of creating a snapshot, take ownership of the input snapshot.
+ ManagedSnapshot(DB* db, const Snapshot* _snapshot);
+
+ ~ManagedSnapshot();
+
+ const Snapshot* snapshot();
+
+ private:
+ DB* db_;
+ const Snapshot* snapshot_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/sst_dump_tool.h b/storage/rocksdb/rocksdb/include/rocksdb/sst_dump_tool.h
new file mode 100644
index 00000000..ecb692e3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/sst_dump_tool.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#pragma once
+
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SSTDumpTool {
+ public:
+ int Run(int argc, char** argv, Options options = Options());
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/sst_file_manager.h b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_manager.h
new file mode 100644
index 00000000..92d0bbbf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_manager.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "rocksdb/file_system.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Logger;
+
+// 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.
+class SstFileManager {
+ public:
+ virtual ~SstFileManager() {}
+
+ // Update the maximum allowed space that should be used by RocksDB, if
+ // the total size of the SST files exceeds max_allowed_space, writes to
+ // RocksDB will fail.
+ //
+ // Setting max_allowed_space to 0 will disable this feature; maximum allowed
+ // space will be infinite (Default value).
+ //
+ // thread-safe.
+ virtual void SetMaxAllowedSpaceUsage(uint64_t max_allowed_space) = 0;
+
+ // 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 compaction_buffer_size available on the disk so that
+ // other background functions may continue, such as logging and flushing.
+ virtual void SetCompactionBufferSize(uint64_t compaction_buffer_size) = 0;
+
+ // Return true if the total size of SST files exceeded the maximum allowed
+ // space usage.
+ //
+ // thread-safe.
+ virtual bool IsMaxAllowedSpaceReached() = 0;
+
+ // Returns true if the total size of SST files as well as estimated size
+ // of ongoing compactions exceeds the maximums allowed space usage.
+ virtual bool IsMaxAllowedSpaceReachedIncludingCompactions() = 0;
+
+ // Return the total size of all tracked files.
+ // thread-safe
+ virtual uint64_t GetTotalSize() = 0;
+
+ // Return a map containing all tracked files and their corresponding sizes.
+ // thread-safe
+ virtual std::unordered_map<std::string, uint64_t> GetTrackedFiles() = 0;
+
+ // Return delete rate limit in bytes per second.
+ // thread-safe
+ virtual int64_t GetDeleteRateBytesPerSecond() = 0;
+
+ // Update the delete rate limit in bytes per second.
+ // zero means disable delete rate limiting and delete files immediately
+ // thread-safe
+ virtual void SetDeleteRateBytesPerSecond(int64_t delete_rate) = 0;
+
+ // Return trash/DB size ratio where new files will be deleted immediately
+ // thread-safe
+ virtual double GetMaxTrashDBRatio() = 0;
+
+ // Update trash/DB size ratio where new files will be deleted immediately
+ // thread-safe
+ virtual void SetMaxTrashDBRatio(double ratio) = 0;
+
+ // Return the total size of trash files
+ // thread-safe
+ virtual uint64_t GetTotalTrashSize() = 0;
+};
+
+// Create a new SstFileManager that can be shared among multiple RocksDB
+// instances to track SST file and control there deletion rate.
+// Even though SstFileManager don't track WAL files but it still control
+// there deletion rate.
+//
+// @param env: Pointer to Env object, please see "rocksdb/env.h".
+// @param fs: Pointer to FileSystem object (rocksdb/file_system.h"
+// @param info_log: If not nullptr, info_log will be used to log errors.
+//
+// == Deletion rate limiting specific arguments ==
+// @param trash_dir: Deprecated, this argument have no effect
+// @param rate_bytes_per_sec: 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.
+// This option also affect the delete rate of WAL files in the DB.
+// @param delete_existing_trash: Deprecated, this argument have no effect, but
+// if user provide trash_dir we will schedule deletes for files in the dir
+// @param status: If not nullptr, status will contain any errors that happened
+// during creating the missing trash_dir or deleting existing files in trash.
+// @param max_trash_db_ratio: 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 bytes_max_delete_chunk: if a file to delete 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. If the file has more
+// than one linked names, the file will be deleted as a whole. Either way,
+// `rate_bytes_per_sec` will be appreciated. NOTE that with this option,
+// files already renamed as a trash may be partial, so users should not
+// directly recover them without checking.
+extern SstFileManager* NewSstFileManager(
+ Env* env, std::shared_ptr<FileSystem> fs,
+ std::shared_ptr<Logger> info_log = nullptr,
+ const std::string& trash_dir = "", int64_t rate_bytes_per_sec = 0,
+ bool delete_existing_trash = true, Status* status = nullptr,
+ double max_trash_db_ratio = 0.25,
+ uint64_t bytes_max_delete_chunk = 64 * 1024 * 1024);
+
+// Same as above, but takes a pointer to a legacy Env object, instead of
+// Env and FileSystem objects
+extern SstFileManager* NewSstFileManager(
+ Env* env, std::shared_ptr<Logger> info_log = nullptr,
+ std::string trash_dir = "", int64_t rate_bytes_per_sec = 0,
+ bool delete_existing_trash = true, Status* status = nullptr,
+ double max_trash_db_ratio = 0.25,
+ uint64_t bytes_max_delete_chunk = 64 * 1024 * 1024);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/sst_file_reader.h b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_reader.h
new file mode 100644
index 00000000..4b864248
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_reader.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/table_properties.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// SstFileReader is used to read sst files that are generated by DB or
+// SstFileWriter.
+class SstFileReader {
+ public:
+ SstFileReader(const Options& options);
+
+ ~SstFileReader();
+
+ // Prepares to read from the file located at "file_path".
+ Status Open(const std::string& file_path);
+
+ // Returns a new iterator over the table contents.
+ // Most read options provide the same control as we read from DB.
+ // If "snapshot" is nullptr, the iterator returns only the latest keys.
+ Iterator* NewIterator(const ReadOptions& options);
+
+ std::shared_ptr<const TableProperties> GetTableProperties() const;
+
+ // Verifies whether there is corruption in this table.
+ Status VerifyChecksum(const ReadOptions& /*read_options*/);
+
+ Status VerifyChecksum() { return VerifyChecksum(ReadOptions()); }
+
+ private:
+ struct Rep;
+ std::unique_ptr<Rep> rep_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/sst_file_writer.h b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_writer.h
new file mode 100644
index 00000000..e83383fe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/sst_file_writer.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <memory>
+#include <string>
+
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/types.h"
+
+#if defined(__GNUC__) || defined(__clang__)
+#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
+#elif _WIN32
+#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class Comparator;
+
+// ExternalSstFileInfo include information about sst files created
+// using SstFileWriter.
+struct ExternalSstFileInfo {
+ ExternalSstFileInfo()
+ : file_path(""),
+ smallest_key(""),
+ largest_key(""),
+ smallest_range_del_key(""),
+ largest_range_del_key(""),
+ sequence_number(0),
+ file_size(0),
+ num_entries(0),
+ num_range_del_entries(0),
+ version(0) {}
+
+ ExternalSstFileInfo(const std::string& _file_path,
+ const std::string& _smallest_key,
+ const std::string& _largest_key,
+ SequenceNumber _sequence_number, uint64_t _file_size,
+ int32_t _num_entries, int32_t _version)
+ : file_path(_file_path),
+ smallest_key(_smallest_key),
+ largest_key(_largest_key),
+ smallest_range_del_key(""),
+ largest_range_del_key(""),
+ sequence_number(_sequence_number),
+ file_size(_file_size),
+ num_entries(_num_entries),
+ num_range_del_entries(0),
+ version(_version) {}
+
+ std::string file_path; // external sst file path
+ std::string smallest_key; // smallest user key in file
+ std::string largest_key; // largest user key in file
+ std::string
+ smallest_range_del_key; // smallest range deletion user key in file
+ std::string largest_range_del_key; // largest range deletion user key in file
+ SequenceNumber sequence_number; // sequence number of all keys in file
+ uint64_t file_size; // file size in bytes
+ uint64_t num_entries; // number of entries in file
+ uint64_t num_range_del_entries; // number of range deletion entries in file
+ int32_t version; // file version
+};
+
+// SstFileWriter is used to create sst files that can be added to database later
+// All keys in files generated by SstFileWriter will have sequence number = 0.
+class SstFileWriter {
+ public:
+ // User can pass `column_family` to specify that the generated file will
+ // be ingested into this column_family, note that passing nullptr means that
+ // the column_family is unknown.
+ // If invalidate_page_cache is set to true, SstFileWriter will give the OS a
+ // hint that this file pages is not needed every time we write 1MB to the
+ // file. To use the rate limiter an io_priority smaller than IO_TOTAL can be
+ // passed.
+ SstFileWriter(const EnvOptions& env_options, const Options& options,
+ ColumnFamilyHandle* column_family = nullptr,
+ bool invalidate_page_cache = true,
+ Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL,
+ bool skip_filters = false)
+ : SstFileWriter(env_options, options, options.comparator, column_family,
+ invalidate_page_cache, io_priority, skip_filters) {}
+
+ // Deprecated API
+ SstFileWriter(const EnvOptions& env_options, const Options& options,
+ const Comparator* user_comparator,
+ ColumnFamilyHandle* column_family = nullptr,
+ bool invalidate_page_cache = true,
+ Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL,
+ bool skip_filters = false);
+
+ ~SstFileWriter();
+
+ // Prepare SstFileWriter to write into file located at "file_path".
+ Status Open(const std::string& file_path);
+
+ // Add a Put key with value to currently opened file (deprecated)
+ // REQUIRES: key is after any previously added key according to comparator.
+ ROCKSDB_DEPRECATED_FUNC Status Add(const Slice& user_key, const Slice& value);
+
+ // Add a Put key with value to currently opened file
+ // REQUIRES: key is after any previously added key according to comparator.
+ Status Put(const Slice& user_key, const Slice& value);
+
+ // Add a Merge key with value to currently opened file
+ // REQUIRES: key is after any previously added key according to comparator.
+ Status Merge(const Slice& user_key, const Slice& value);
+
+ // Add a deletion key to currently opened file
+ // REQUIRES: key is after any previously added key according to comparator.
+ Status Delete(const Slice& user_key);
+
+ // Add a range deletion tombstone to currently opened file
+ Status DeleteRange(const Slice& begin_key, const Slice& end_key);
+
+ // Finalize writing to sst file and close file.
+ //
+ // An optional ExternalSstFileInfo pointer can be passed to the function
+ // which will be populated with information about the created sst file.
+ Status Finish(ExternalSstFileInfo* file_info = nullptr);
+
+ // Return the current file size.
+ uint64_t FileSize();
+
+ private:
+ void InvalidatePageCache(bool closing);
+ struct Rep;
+ std::unique_ptr<Rep> rep_;
+};
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/statistics.h b/storage/rocksdb/rocksdb/include/rocksdb/statistics.h
new file mode 100644
index 00000000..3bda6d71
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/statistics.h
@@ -0,0 +1,548 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+/**
+ * Keep adding ticker's here.
+ * 1. Any ticker should be added before TICKER_ENUM_MAX.
+ * 2. Add a readable string in TickersNameMap below for the newly added ticker.
+ * 3. Add a corresponding enum value to TickerType.java in the java API
+ * 4. Add the enum conversions from Java and C++ to portal.h's toJavaTickerType
+ * and toCppTickers
+ */
+enum Tickers : uint32_t {
+ // total block cache misses
+ // REQUIRES: BLOCK_CACHE_MISS == BLOCK_CACHE_INDEX_MISS +
+ // BLOCK_CACHE_FILTER_MISS +
+ // BLOCK_CACHE_DATA_MISS;
+ BLOCK_CACHE_MISS = 0,
+ // total block cache hit
+ // REQUIRES: BLOCK_CACHE_HIT == BLOCK_CACHE_INDEX_HIT +
+ // BLOCK_CACHE_FILTER_HIT +
+ // BLOCK_CACHE_DATA_HIT;
+ BLOCK_CACHE_HIT,
+ // # of blocks added to block cache.
+ BLOCK_CACHE_ADD,
+ // # of failures when adding blocks to block cache.
+ BLOCK_CACHE_ADD_FAILURES,
+ // # of times cache miss when accessing index block from block cache.
+ BLOCK_CACHE_INDEX_MISS,
+ // # of times cache hit when accessing index block from block cache.
+ BLOCK_CACHE_INDEX_HIT,
+ // # of index blocks added to block cache.
+ BLOCK_CACHE_INDEX_ADD,
+ // # of bytes of index blocks inserted into cache
+ BLOCK_CACHE_INDEX_BYTES_INSERT,
+ // # of bytes of index block erased from cache
+ BLOCK_CACHE_INDEX_BYTES_EVICT,
+ // # of times cache miss when accessing filter block from block cache.
+ BLOCK_CACHE_FILTER_MISS,
+ // # of times cache hit when accessing filter block from block cache.
+ BLOCK_CACHE_FILTER_HIT,
+ // # of filter blocks added to block cache.
+ BLOCK_CACHE_FILTER_ADD,
+ // # of bytes of bloom filter blocks inserted into cache
+ BLOCK_CACHE_FILTER_BYTES_INSERT,
+ // # of bytes of bloom filter block erased from cache
+ BLOCK_CACHE_FILTER_BYTES_EVICT,
+ // # of times cache miss when accessing data block from block cache.
+ BLOCK_CACHE_DATA_MISS,
+ // # of times cache hit when accessing data block from block cache.
+ BLOCK_CACHE_DATA_HIT,
+ // # of data blocks added to block cache.
+ BLOCK_CACHE_DATA_ADD,
+ // # of bytes of data blocks inserted into cache
+ BLOCK_CACHE_DATA_BYTES_INSERT,
+ // # of bytes read from cache.
+ BLOCK_CACHE_BYTES_READ,
+ // # of bytes written into cache.
+ BLOCK_CACHE_BYTES_WRITE,
+
+ // # of times bloom filter has avoided file reads, i.e., negatives.
+ BLOOM_FILTER_USEFUL,
+ // # of times bloom FullFilter has not avoided the reads.
+ BLOOM_FILTER_FULL_POSITIVE,
+ // # of times bloom FullFilter has not avoided the reads and data actually
+ // exist.
+ BLOOM_FILTER_FULL_TRUE_POSITIVE,
+
+ BLOOM_FILTER_MICROS,
+
+ // # persistent cache hit
+ PERSISTENT_CACHE_HIT,
+ // # persistent cache miss
+ PERSISTENT_CACHE_MISS,
+
+ // # total simulation block cache hits
+ SIM_BLOCK_CACHE_HIT,
+ // # total simulation block cache misses
+ SIM_BLOCK_CACHE_MISS,
+
+ // # of memtable hits.
+ MEMTABLE_HIT,
+ // # of memtable misses.
+ MEMTABLE_MISS,
+
+ // # of Get() queries served by L0
+ GET_HIT_L0,
+ // # of Get() queries served by L1
+ GET_HIT_L1,
+ // # of Get() queries served by L2 and up
+ GET_HIT_L2_AND_UP,
+
+ /**
+ * COMPACTION_KEY_DROP_* count the reasons for key drop during compaction
+ * There are 4 reasons currently.
+ */
+ COMPACTION_KEY_DROP_NEWER_ENTRY, // key was written with a newer value.
+ // Also includes keys dropped for range del.
+ COMPACTION_KEY_DROP_OBSOLETE, // The key is obsolete.
+ COMPACTION_KEY_DROP_RANGE_DEL, // key was covered by a range tombstone.
+ COMPACTION_KEY_DROP_USER, // user compaction function has dropped the key.
+ COMPACTION_RANGE_DEL_DROP_OBSOLETE, // all keys in range were deleted.
+ // Deletions obsoleted before bottom level due to file gap optimization.
+ COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
+ // If a compaction was cancelled in sfm to prevent ENOSPC
+ COMPACTION_CANCELLED,
+
+ // Number of keys written to the database via the Put and Write call's
+ NUMBER_KEYS_WRITTEN,
+ // Number of Keys read,
+ NUMBER_KEYS_READ,
+ // Number keys updated, if inplace update is enabled
+ NUMBER_KEYS_UPDATED,
+ // The number of uncompressed bytes issued by DB::Put(), DB::Delete(),
+ // DB::Merge(), and DB::Write().
+ BYTES_WRITTEN,
+ // 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 NUMBER_MULTIGET_BYTES_READ.
+ BYTES_READ,
+ // The number of calls to seek/next/prev
+ NUMBER_DB_SEEK,
+ NUMBER_DB_NEXT,
+ NUMBER_DB_PREV,
+ // The number of calls to seek/next/prev that returned data
+ NUMBER_DB_SEEK_FOUND,
+ NUMBER_DB_NEXT_FOUND,
+ NUMBER_DB_PREV_FOUND,
+ // The number of uncompressed bytes read from an iterator.
+ // Includes size of key and value.
+ ITER_BYTES_READ,
+ NO_FILE_CLOSES,
+ NO_FILE_OPENS,
+ NO_FILE_ERRORS,
+ // DEPRECATED Time system had to wait to do LO-L1 compactions
+ STALL_L0_SLOWDOWN_MICROS,
+ // DEPRECATED Time system had to wait to move memtable to L1.
+ STALL_MEMTABLE_COMPACTION_MICROS,
+ // DEPRECATED write throttle because of too many files in L0
+ STALL_L0_NUM_FILES_MICROS,
+ // Writer has to wait for compaction or flush to finish.
+ STALL_MICROS,
+ // The wait time for db mutex.
+ // Disabled by default. To enable it set stats level to kAll
+ DB_MUTEX_WAIT_MICROS,
+ RATE_LIMIT_DELAY_MILLIS,
+ // DEPRECATED number of iterators currently open
+ NO_ITERATORS,
+
+ // Number of MultiGet calls, keys read, and bytes read
+ NUMBER_MULTIGET_CALLS,
+ NUMBER_MULTIGET_KEYS_READ,
+ NUMBER_MULTIGET_BYTES_READ,
+
+ // Number of deletes records that were not required to be
+ // written to storage because key does not exist
+ NUMBER_FILTERED_DELETES,
+ NUMBER_MERGE_FAILURES,
+
+ // 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,
+ BLOOM_FILTER_PREFIX_USEFUL,
+
+ // 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,
+
+ // Record the number of calls to GetUpadtesSince. Useful to keep track of
+ // transaction log iterator refreshes
+ GET_UPDATES_SINCE_CALLS,
+ BLOCK_CACHE_COMPRESSED_MISS, // miss in the compressed block cache
+ BLOCK_CACHE_COMPRESSED_HIT, // hit in the compressed block cache
+ // Number of blocks added to compressed block cache
+ BLOCK_CACHE_COMPRESSED_ADD,
+ // Number of failures when adding blocks to compressed block cache
+ BLOCK_CACHE_COMPRESSED_ADD_FAILURES,
+ WAL_FILE_SYNCED, // Number of times WAL sync is done
+ WAL_FILE_BYTES, // Number of bytes written to WAL
+
+ // Writes can be processed by requesting thread or by the thread at the
+ // head of the writers queue.
+ WRITE_DONE_BY_SELF,
+ WRITE_DONE_BY_OTHER, // Equivalent to writes done for others
+ WRITE_TIMEDOUT, // Number of writes ending up with timed-out.
+ WRITE_WITH_WAL, // Number of Write calls that request WAL
+ COMPACT_READ_BYTES, // Bytes read during compaction
+ COMPACT_WRITE_BYTES, // Bytes written during compaction
+ FLUSH_WRITE_BYTES, // Bytes written during flush
+
+ // Number of table's properties loaded directly from file, without creating
+ // table reader object.
+ NUMBER_DIRECT_LOAD_TABLE_PROPERTIES,
+ NUMBER_SUPERVERSION_ACQUIRES,
+ NUMBER_SUPERVERSION_RELEASES,
+ NUMBER_SUPERVERSION_CLEANUPS,
+
+ // # of compressions/decompressions executed
+ NUMBER_BLOCK_COMPRESSED,
+ NUMBER_BLOCK_DECOMPRESSED,
+
+ NUMBER_BLOCK_NOT_COMPRESSED,
+ MERGE_OPERATION_TOTAL_TIME,
+ FILTER_OPERATION_TOTAL_TIME,
+
+ // Row cache.
+ ROW_CACHE_HIT,
+ ROW_CACHE_MISS,
+
+ // 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
+ READ_AMP_ESTIMATE_USEFUL_BYTES, // Estimate of total bytes actually used.
+ READ_AMP_TOTAL_READ_BYTES, // Total size of loaded data blocks.
+
+ // Number of refill intervals where rate limiter's bytes are fully consumed.
+ NUMBER_RATE_LIMITER_DRAINS,
+
+ // Number of internal keys skipped by Iterator
+ NUMBER_ITER_SKIP,
+
+ // BlobDB specific stats
+ // # of Put/PutTTL/PutUntil to BlobDB.
+ BLOB_DB_NUM_PUT,
+ // # of Write to BlobDB.
+ BLOB_DB_NUM_WRITE,
+ // # of Get to BlobDB.
+ BLOB_DB_NUM_GET,
+ // # of MultiGet to BlobDB.
+ BLOB_DB_NUM_MULTIGET,
+ // # of Seek/SeekToFirst/SeekToLast/SeekForPrev to BlobDB iterator.
+ BLOB_DB_NUM_SEEK,
+ // # of Next to BlobDB iterator.
+ BLOB_DB_NUM_NEXT,
+ // # of Prev to BlobDB iterator.
+ BLOB_DB_NUM_PREV,
+ // # of keys written to BlobDB.
+ BLOB_DB_NUM_KEYS_WRITTEN,
+ // # of keys read from BlobDB.
+ BLOB_DB_NUM_KEYS_READ,
+ // # of bytes (key + value) written to BlobDB.
+ BLOB_DB_BYTES_WRITTEN,
+ // # of bytes (keys + value) read from BlobDB.
+ BLOB_DB_BYTES_READ,
+ // # of keys written by BlobDB as non-TTL inlined value.
+ BLOB_DB_WRITE_INLINED,
+ // # of keys written by BlobDB as TTL inlined value.
+ BLOB_DB_WRITE_INLINED_TTL,
+ // # of keys written by BlobDB as non-TTL blob value.
+ BLOB_DB_WRITE_BLOB,
+ // # of keys written by BlobDB as TTL blob value.
+ BLOB_DB_WRITE_BLOB_TTL,
+ // # of bytes written to blob file.
+ BLOB_DB_BLOB_FILE_BYTES_WRITTEN,
+ // # of bytes read from blob file.
+ BLOB_DB_BLOB_FILE_BYTES_READ,
+ // # of times a blob files being synced.
+ BLOB_DB_BLOB_FILE_SYNCED,
+ // # of blob index evicted from base DB by BlobDB compaction filter because
+ // of expiration.
+ BLOB_DB_BLOB_INDEX_EXPIRED_COUNT,
+ // size of blob index evicted from base DB by BlobDB compaction filter
+ // because of expiration.
+ BLOB_DB_BLOB_INDEX_EXPIRED_SIZE,
+ // # of blob index evicted from base DB by BlobDB compaction filter because
+ // of corresponding file deleted.
+ BLOB_DB_BLOB_INDEX_EVICTED_COUNT,
+ // size of blob index evicted from base DB by BlobDB compaction filter
+ // because of corresponding file deleted.
+ BLOB_DB_BLOB_INDEX_EVICTED_SIZE,
+ // # of blob files that were obsoleted by garbage collection.
+ BLOB_DB_GC_NUM_FILES,
+ // # of blob files generated by garbage collection.
+ BLOB_DB_GC_NUM_NEW_FILES,
+ // # of BlobDB garbage collection failures.
+ BLOB_DB_GC_FAILURES,
+ // # of keys dropped by BlobDB garbage collection because they had been
+ // overwritten. DEPRECATED.
+ BLOB_DB_GC_NUM_KEYS_OVERWRITTEN,
+ // # of keys dropped by BlobDB garbage collection because of expiration.
+ // DEPRECATED.
+ BLOB_DB_GC_NUM_KEYS_EXPIRED,
+ // # of keys relocated to new blob file by garbage collection.
+ BLOB_DB_GC_NUM_KEYS_RELOCATED,
+ // # of bytes dropped by BlobDB garbage collection because they had been
+ // overwritten. DEPRECATED.
+ BLOB_DB_GC_BYTES_OVERWRITTEN,
+ // # of bytes dropped by BlobDB garbage collection because of expiration.
+ // DEPRECATED.
+ BLOB_DB_GC_BYTES_EXPIRED,
+ // # of bytes relocated to new blob file by garbage collection.
+ BLOB_DB_GC_BYTES_RELOCATED,
+ // # of blob files evicted because of BlobDB is full.
+ BLOB_DB_FIFO_NUM_FILES_EVICTED,
+ // # of keys in the blob files evicted because of BlobDB is full.
+ BLOB_DB_FIFO_NUM_KEYS_EVICTED,
+ // # of bytes in the blob files evicted because of BlobDB is full.
+ BLOB_DB_FIFO_BYTES_EVICTED,
+
+ // 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,
+ // # of times old_commit_map_mutex_ is acquired in the fast path.
+ TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD,
+ // # of times we checked a batch for duplicate keys.
+ TXN_DUPLICATE_KEY_OVERHEAD,
+ // # of times snapshot_mutex_ is acquired in the fast path.
+ TXN_SNAPSHOT_MUTEX_OVERHEAD,
+ // # of times ::Get returned TryAgain due to expired snapshot seq
+ TXN_GET_TRY_AGAIN,
+
+ // Number of keys actually found in MultiGet calls (vs number requested by
+ // caller)
+ // NUMBER_MULTIGET_KEYS_READ gives the number requested by caller
+ NUMBER_MULTIGET_KEYS_FOUND,
+
+ NO_ITERATOR_CREATED, // number of iterators created
+ NO_ITERATOR_DELETED, // number of iterators deleted
+
+ BLOCK_CACHE_COMPRESSION_DICT_MISS,
+ BLOCK_CACHE_COMPRESSION_DICT_HIT,
+ BLOCK_CACHE_COMPRESSION_DICT_ADD,
+ BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT,
+ BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT,
+ TICKER_ENUM_MAX
+};
+
+// The order of items listed in Tickers should be the same as
+// the order listed in TickersNameMap
+extern const std::vector<std::pair<Tickers, std::string>> TickersNameMap;
+
+/**
+ * Keep adding histogram's here.
+ * Any histogram should have value less than HISTOGRAM_ENUM_MAX
+ * Add a new Histogram by assigning it the current value of HISTOGRAM_ENUM_MAX
+ * Add a string representation in HistogramsNameMap below
+ * And increment HISTOGRAM_ENUM_MAX
+ * Add a corresponding enum value to HistogramType.java in the java API
+ */
+enum Histograms : uint32_t {
+ DB_GET = 0,
+ DB_WRITE,
+ COMPACTION_TIME,
+ COMPACTION_CPU_TIME,
+ SUBCOMPACTION_SETUP_TIME,
+ TABLE_SYNC_MICROS,
+ COMPACTION_OUTFILE_SYNC_MICROS,
+ WAL_FILE_SYNC_MICROS,
+ MANIFEST_FILE_SYNC_MICROS,
+ // TIME SPENT IN IO DURING TABLE OPEN
+ TABLE_OPEN_IO_MICROS,
+ DB_MULTIGET,
+ READ_BLOCK_COMPACTION_MICROS,
+ READ_BLOCK_GET_MICROS,
+ WRITE_RAW_BLOCK_MICROS,
+ STALL_L0_SLOWDOWN_COUNT,
+ STALL_MEMTABLE_COMPACTION_COUNT,
+ STALL_L0_NUM_FILES_COUNT,
+ HARD_RATE_LIMIT_DELAY_COUNT,
+ SOFT_RATE_LIMIT_DELAY_COUNT,
+ NUM_FILES_IN_SINGLE_COMPACTION,
+ DB_SEEK,
+ WRITE_STALL,
+ SST_READ_MICROS,
+ // The number of subcompactions actually scheduled during a compaction
+ NUM_SUBCOMPACTIONS_SCHEDULED,
+ // Value size distribution in each operation
+ BYTES_PER_READ,
+ BYTES_PER_WRITE,
+ BYTES_PER_MULTIGET,
+
+ // number of bytes compressed/decompressed
+ // number of bytes is when uncompressed; i.e. before/after respectively
+ BYTES_COMPRESSED,
+ BYTES_DECOMPRESSED,
+ COMPRESSION_TIMES_NANOS,
+ DECOMPRESSION_TIMES_NANOS,
+ // Number of merge operands passed to the merge operator in user read
+ // requests.
+ READ_NUM_MERGE_OPERANDS,
+
+ // BlobDB specific stats
+ // Size of keys written to BlobDB.
+ BLOB_DB_KEY_SIZE,
+ // Size of values written to BlobDB.
+ BLOB_DB_VALUE_SIZE,
+ // BlobDB Put/PutWithTTL/PutUntil/Write latency.
+ BLOB_DB_WRITE_MICROS,
+ // BlobDB Get lagency.
+ BLOB_DB_GET_MICROS,
+ // BlobDB MultiGet latency.
+ BLOB_DB_MULTIGET_MICROS,
+ // BlobDB Seek/SeekToFirst/SeekToLast/SeekForPrev latency.
+ BLOB_DB_SEEK_MICROS,
+ // BlobDB Next latency.
+ BLOB_DB_NEXT_MICROS,
+ // BlobDB Prev latency.
+ BLOB_DB_PREV_MICROS,
+ // Blob file write latency.
+ BLOB_DB_BLOB_FILE_WRITE_MICROS,
+ // Blob file read latency.
+ BLOB_DB_BLOB_FILE_READ_MICROS,
+ // Blob file sync latency.
+ BLOB_DB_BLOB_FILE_SYNC_MICROS,
+ // BlobDB garbage collection time. DEPRECATED.
+ BLOB_DB_GC_MICROS,
+ // BlobDB compression time.
+ BLOB_DB_COMPRESSION_MICROS,
+ // BlobDB decompression time.
+ BLOB_DB_DECOMPRESSION_MICROS,
+ // Time spent flushing memtable to disk
+ FLUSH_TIME,
+ SST_BATCH_SIZE,
+
+ HISTOGRAM_ENUM_MAX,
+};
+
+extern const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap;
+
+struct HistogramData {
+ double median;
+ double percentile95;
+ double percentile99;
+ double average;
+ double standard_deviation;
+ // zero-initialize new members since old Statistics::histogramData()
+ // implementations won't write them.
+ double max = 0.0;
+ uint64_t count = 0;
+ uint64_t sum = 0;
+ double min = 0.0;
+};
+
+// StatsLevel can be used to reduce statistics overhead by skipping certain
+// types of stats in the stats collection process.
+// Usage:
+// options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
+enum StatsLevel : uint8_t {
+ // Disable timer stats, and skip histogram stats
+ kExceptHistogramOrTimers,
+ // Skip timer stats
+ kExceptTimers,
+ // Collect all stats except time inside mutex lock AND time spent on
+ // compression.
+ kExceptDetailedTimers,
+ // Collect all stats except the counters requiring to get time inside the
+ // mutex lock.
+ kExceptTimeForMutex,
+ // 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.
+ kAll,
+};
+
+// Analyze the performance of a db by providing cumulative stats over time.
+// Usage:
+// Options options;
+// options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+// Status s = DB::Open(options, kDBPath, &db);
+// ...
+// options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
+// HistogramData hist;
+// options.statistics->histogramData(FLUSH_TIME, &hist);
+class Statistics {
+ public:
+ virtual ~Statistics() {}
+ static const char* Type() { return "Statistics"; }
+ virtual uint64_t getTickerCount(uint32_t tickerType) const = 0;
+ virtual void histogramData(uint32_t type,
+ HistogramData* const data) const = 0;
+ virtual std::string getHistogramString(uint32_t /*type*/) const { return ""; }
+ virtual void recordTick(uint32_t tickerType, uint64_t count = 0) = 0;
+ virtual void setTickerCount(uint32_t tickerType, uint64_t count) = 0;
+ virtual uint64_t getAndResetTickerCount(uint32_t tickerType) = 0;
+ virtual void reportTimeToHistogram(uint32_t histogramType, uint64_t time) {
+ if (get_stats_level() <= StatsLevel::kExceptTimers) {
+ return;
+ }
+ recordInHistogram(histogramType, time);
+ }
+ // The function is here only for backward compatibility reason.
+ // Users implementing their own Statistics class should override
+ // recordInHistogram() instead and leave measureTime() as it is.
+ virtual void measureTime(uint32_t /*histogramType*/, uint64_t /*time*/) {
+ // This is not supposed to be called.
+ assert(false);
+ }
+ virtual void recordInHistogram(uint32_t histogramType, uint64_t time) {
+ // measureTime() is the old and inaccurate function name.
+ // To keep backward compatible. If users implement their own
+ // statistics, which overrides measureTime() but doesn't override
+ // this function. We forward to measureTime().
+ measureTime(histogramType, time);
+ }
+
+ // Resets all ticker and histogram stats
+ virtual Status Reset() { return Status::NotSupported("Not implemented"); }
+
+ // String representation of the statistic object.
+ virtual std::string ToString() const {
+ // Do nothing by default
+ return std::string("ToString(): not implemented");
+ }
+
+ virtual bool getTickerMap(std::map<std::string, uint64_t>*) const {
+ // Do nothing by default
+ return false;
+ }
+
+ // Override this function to disable particular histogram collection
+ virtual bool HistEnabledForType(uint32_t type) const {
+ return type < HISTOGRAM_ENUM_MAX;
+ }
+ void set_stats_level(StatsLevel sl) {
+ stats_level_.store(sl, std::memory_order_relaxed);
+ }
+ StatsLevel get_stats_level() const {
+ return stats_level_.load(std::memory_order_relaxed);
+ }
+
+ private:
+ std::atomic<StatsLevel> stats_level_{kExceptDetailedTimers};
+};
+
+// Create a concrete DBStatistics object
+std::shared_ptr<Statistics> CreateDBStatistics();
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/stats_history.h b/storage/rocksdb/rocksdb/include/rocksdb/stats_history.h
new file mode 100644
index 00000000..4acaad26
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/stats_history.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+
+// StatsHistoryIterator is the main interface for users to programmatically
+// access statistics snapshots that was automatically stored by RocksDB.
+// Depending on options, the stats can be in memory or on disk.
+// The stats snapshots are indexed by time that they were recorded, and each
+// stats snapshot contains individual stat name and value at the time of
+// recording.
+// Example:
+// std::unique_ptr<StatsHistoryIterator> stats_iter;
+// Status s = db->GetStatsHistory(0 /* start_time */,
+// env->NowMicros() /* end_time*/,
+// &stats_iter);
+// if (s.ok) {
+// for (; stats_iter->Valid(); stats_iter->Next()) {
+// uint64_t stats_time = stats_iter->GetStatsTime();
+// const std::map<std::string, uint64_t>& stats_map =
+// stats_iter->GetStatsMap();
+// process(stats_time, stats_map);
+// }
+// }
+class StatsHistoryIterator {
+ public:
+ StatsHistoryIterator() {}
+ virtual ~StatsHistoryIterator() {}
+
+ virtual bool Valid() const = 0;
+
+ // Moves to the next stats history record. After this call, Valid() is
+ // true iff the iterator was not positioned at the last entry in the source.
+ // REQUIRES: Valid()
+ virtual void Next() = 0;
+
+ // Return the time stamp (in seconds) when stats history is recorded.
+ // REQUIRES: Valid()
+ virtual uint64_t GetStatsTime() const = 0;
+
+ virtual int GetFormatVersion() const { return -1; }
+
+ // Return the current stats history as an std::map which specifies the
+ // mapping from stats name to stats value . The underlying storage
+ // for the returned map is valid only until the next modification of
+ // the iterator.
+ // REQUIRES: Valid()
+ virtual const std::map<std::string, uint64_t>& GetStatsMap() const = 0;
+
+ // If an error has occurred, return it. Else return an ok status.
+ virtual Status status() const = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/status.h b/storage/rocksdb/rocksdb/include/rocksdb/status.h
new file mode 100644
index 00000000..cf62512c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/status.h
@@ -0,0 +1,386 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A Status encapsulates the result of an operation. It may indicate success,
+// or it may indicate an error with an associated error message.
+//
+// Multiple threads can invoke const methods on a Status without
+// external synchronization, but if any of the threads may call a
+// non-const method, all threads accessing the same Status must use
+// external synchronization.
+
+#pragma once
+
+#include <string>
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Status {
+ public:
+ // Create a success status.
+ Status() : code_(kOk), subcode_(kNone), sev_(kNoError), state_(nullptr) {}
+ ~Status() { delete[] state_; }
+
+ // Copy the specified status.
+ Status(const Status& s);
+ Status& operator=(const Status& s);
+ Status(Status&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ ;
+ Status& operator=(Status&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ ;
+ bool operator==(const Status& rhs) const;
+ bool operator!=(const Status& rhs) const;
+
+ enum Code : unsigned char {
+ kOk = 0,
+ kNotFound = 1,
+ kCorruption = 2,
+ kNotSupported = 3,
+ kInvalidArgument = 4,
+ kIOError = 5,
+ kMergeInProgress = 6,
+ kIncomplete = 7,
+ kShutdownInProgress = 8,
+ kTimedOut = 9,
+ kAborted = 10,
+ kBusy = 11,
+ kExpired = 12,
+ kTryAgain = 13,
+ kCompactionTooLarge = 14,
+ kColumnFamilyDropped = 15,
+ kMaxCode
+ };
+
+ Code code() const { return code_; }
+
+ enum SubCode : unsigned char {
+ kNone = 0,
+ kMutexTimeout = 1,
+ kLockTimeout = 2,
+ kLockLimit = 3,
+ kNoSpace = 4,
+ kDeadlock = 5,
+ kStaleFile = 6,
+ kMemoryLimit = 7,
+ kSpaceLimit = 8,
+ kPathNotFound = 9,
+ KMergeOperandsInsufficientCapacity = 10,
+ kManualCompactionPaused = 11,
+ kMaxSubCode
+ };
+
+ SubCode subcode() const { return subcode_; }
+
+ enum Severity : unsigned char {
+ kNoError = 0,
+ kSoftError = 1,
+ kHardError = 2,
+ kFatalError = 3,
+ kUnrecoverableError = 4,
+ kMaxSeverity
+ };
+
+ Status(const Status& s, Severity sev);
+ Severity severity() const { return sev_; }
+
+ // Returns a C style string indicating the message of the Status
+ const char* getState() const { return state_; }
+
+ // Return a success status.
+ static Status OK() { return Status(); }
+
+ // Return error status of an appropriate type.
+ static Status NotFound(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kNotFound, msg, msg2);
+ }
+ // Fast path for not found without malloc;
+ static Status NotFound(SubCode msg = kNone) { return Status(kNotFound, msg); }
+
+ static Status Corruption(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kCorruption, msg, msg2);
+ }
+ static Status Corruption(SubCode msg = kNone) {
+ return Status(kCorruption, msg);
+ }
+
+ static Status NotSupported(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kNotSupported, msg, msg2);
+ }
+ static Status NotSupported(SubCode msg = kNone) {
+ return Status(kNotSupported, msg);
+ }
+
+ static Status InvalidArgument(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kInvalidArgument, msg, msg2);
+ }
+ static Status InvalidArgument(SubCode msg = kNone) {
+ return Status(kInvalidArgument, msg);
+ }
+
+ static Status IOError(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kIOError, msg, msg2);
+ }
+ static Status IOError(SubCode msg = kNone) { return Status(kIOError, msg); }
+
+ static Status MergeInProgress(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kMergeInProgress, msg, msg2);
+ }
+ static Status MergeInProgress(SubCode msg = kNone) {
+ return Status(kMergeInProgress, msg);
+ }
+
+ static Status Incomplete(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kIncomplete, msg, msg2);
+ }
+ static Status Incomplete(SubCode msg = kNone) {
+ return Status(kIncomplete, msg);
+ }
+
+ static Status ShutdownInProgress(SubCode msg = kNone) {
+ return Status(kShutdownInProgress, msg);
+ }
+ static Status ShutdownInProgress(const Slice& msg,
+ const Slice& msg2 = Slice()) {
+ return Status(kShutdownInProgress, msg, msg2);
+ }
+ static Status Aborted(SubCode msg = kNone) { return Status(kAborted, msg); }
+ static Status Aborted(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kAborted, msg, msg2);
+ }
+
+ static Status Busy(SubCode msg = kNone) { return Status(kBusy, msg); }
+ static Status Busy(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kBusy, msg, msg2);
+ }
+
+ static Status TimedOut(SubCode msg = kNone) { return Status(kTimedOut, msg); }
+ static Status TimedOut(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kTimedOut, msg, msg2);
+ }
+
+ static Status Expired(SubCode msg = kNone) { return Status(kExpired, msg); }
+ static Status Expired(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kExpired, msg, msg2);
+ }
+
+ static Status TryAgain(SubCode msg = kNone) { return Status(kTryAgain, msg); }
+ static Status TryAgain(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kTryAgain, msg, msg2);
+ }
+
+ static Status CompactionTooLarge(SubCode msg = kNone) {
+ return Status(kCompactionTooLarge, msg);
+ }
+ static Status CompactionTooLarge(const Slice& msg,
+ const Slice& msg2 = Slice()) {
+ return Status(kCompactionTooLarge, msg, msg2);
+ }
+
+ static Status ColumnFamilyDropped(SubCode msg = kNone) {
+ return Status(kColumnFamilyDropped, msg);
+ }
+
+ static Status ColumnFamilyDropped(const Slice& msg,
+ const Slice& msg2 = Slice()) {
+ return Status(kColumnFamilyDropped, msg, msg2);
+ }
+
+ static Status NoSpace() { return Status(kIOError, kNoSpace); }
+ static Status NoSpace(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kIOError, kNoSpace, msg, msg2);
+ }
+
+ static Status MemoryLimit() { return Status(kAborted, kMemoryLimit); }
+ static Status MemoryLimit(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kAborted, kMemoryLimit, msg, msg2);
+ }
+
+ static Status SpaceLimit() { return Status(kIOError, kSpaceLimit); }
+ static Status SpaceLimit(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kIOError, kSpaceLimit, msg, msg2);
+ }
+
+ static Status PathNotFound() { return Status(kIOError, kPathNotFound); }
+ static Status PathNotFound(const Slice& msg, const Slice& msg2 = Slice()) {
+ return Status(kIOError, kPathNotFound, msg, msg2);
+ }
+
+ // Returns true iff the status indicates success.
+ bool ok() const { return code() == kOk; }
+
+ // Returns true iff the status indicates a NotFound error.
+ bool IsNotFound() const { return code() == kNotFound; }
+
+ // Returns true iff the status indicates a Corruption error.
+ bool IsCorruption() const { return code() == kCorruption; }
+
+ // Returns true iff the status indicates a NotSupported error.
+ bool IsNotSupported() const { return code() == kNotSupported; }
+
+ // Returns true iff the status indicates an InvalidArgument error.
+ bool IsInvalidArgument() const { return code() == kInvalidArgument; }
+
+ // Returns true iff the status indicates an IOError.
+ bool IsIOError() const { return code() == kIOError; }
+
+ // Returns true iff the status indicates an MergeInProgress.
+ bool IsMergeInProgress() const { return code() == kMergeInProgress; }
+
+ // Returns true iff the status indicates Incomplete
+ bool IsIncomplete() const { return code() == kIncomplete; }
+
+ // Returns true iff the status indicates Shutdown In progress
+ bool IsShutdownInProgress() const { return code() == kShutdownInProgress; }
+
+ bool IsTimedOut() const { return code() == kTimedOut; }
+
+ bool IsAborted() const { return code() == kAborted; }
+
+ bool IsLockLimit() const {
+ return code() == kAborted && subcode() == kLockLimit;
+ }
+
+ // Returns true iff the status indicates that a resource is Busy and
+ // temporarily could not be acquired.
+ bool IsBusy() const { return code() == kBusy; }
+
+ bool IsDeadlock() const { return code() == kBusy && subcode() == kDeadlock; }
+
+ // Returns true iff the status indicated that the operation has Expired.
+ bool IsExpired() const { return code() == kExpired; }
+
+ // Returns true iff the status indicates a TryAgain error.
+ // This usually means that the operation failed, but may succeed if
+ // re-attempted.
+ bool IsTryAgain() const { return code() == kTryAgain; }
+
+ // Returns true iff the status indicates the proposed compaction is too large
+ bool IsCompactionTooLarge() const { return code() == kCompactionTooLarge; }
+
+ // Returns true iff the status indicates Column Family Dropped
+ bool IsColumnFamilyDropped() const { return code() == kColumnFamilyDropped; }
+
+ // Returns true iff the status indicates a NoSpace error
+ // This is caused by an I/O error returning the specific "out of space"
+ // error condition. Stricto sensu, an NoSpace error is an I/O error
+ // with a specific subcode, enabling users to take the appropriate action
+ // if needed
+ bool IsNoSpace() const {
+ return (code() == kIOError) && (subcode() == kNoSpace);
+ }
+
+ // Returns true iff the status indicates a memory limit error. There may be
+ // cases where we limit the memory used in certain operations (eg. the size
+ // of a write batch) in order to avoid out of memory exceptions.
+ bool IsMemoryLimit() const {
+ return (code() == kAborted) && (subcode() == kMemoryLimit);
+ }
+
+ // Returns true iff the status indicates a PathNotFound error
+ // This is caused by an I/O error returning the specific "no such file or
+ // directory" error condition. A PathNotFound error is an I/O error with
+ // a specific subcode, enabling users to take appropriate action if necessary
+ bool IsPathNotFound() const {
+ return (code() == kIOError) && (subcode() == kPathNotFound);
+ }
+
+ // Returns true iff the status indicates manual compaction paused. This
+ // is caused by a call to PauseManualCompaction
+ bool IsManualCompactionPaused() const {
+ return (code() == kIncomplete) && (subcode() == kManualCompactionPaused);
+ }
+
+ // Return a string representation of this status suitable for printing.
+ // Returns the string "OK" for success.
+ std::string ToString() const;
+
+ protected:
+ // A nullptr state_ (which is always the case for OK) means the message
+ // is empty.
+ // of the following form:
+ // state_[0..3] == length of message
+ // state_[4..] == message
+ Code code_;
+ SubCode subcode_;
+ Severity sev_;
+ const char* state_;
+
+ explicit Status(Code _code, SubCode _subcode = kNone)
+ : code_(_code), subcode_(_subcode), sev_(kNoError), state_(nullptr) {}
+
+ Status(Code _code, SubCode _subcode, const Slice& msg, const Slice& msg2);
+ Status(Code _code, const Slice& msg, const Slice& msg2)
+ : Status(_code, kNone, msg, msg2) {}
+
+ static const char* CopyState(const char* s);
+};
+
+inline Status::Status(const Status& s)
+ : code_(s.code_), subcode_(s.subcode_), sev_(s.sev_) {
+ state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
+}
+inline Status::Status(const Status& s, Severity sev)
+ : code_(s.code_), subcode_(s.subcode_), sev_(sev) {
+ state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
+}
+inline Status& Status::operator=(const Status& s) {
+ // The following condition catches both aliasing (when this == &s),
+ // and the common case where both s and *this are ok.
+ if (this != &s) {
+ code_ = s.code_;
+ subcode_ = s.subcode_;
+ sev_ = s.sev_;
+ delete[] state_;
+ state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_);
+ }
+ return *this;
+}
+
+inline Status::Status(Status&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+ : Status() {
+ *this = std::move(s);
+}
+
+inline Status& Status::operator=(Status&& s)
+#if !(defined _MSC_VER) || ((defined _MSC_VER) && (_MSC_VER >= 1900))
+ noexcept
+#endif
+{
+ if (this != &s) {
+ code_ = std::move(s.code_);
+ s.code_ = kOk;
+ subcode_ = std::move(s.subcode_);
+ s.subcode_ = kNone;
+ sev_ = std::move(s.sev_);
+ s.sev_ = kNoError;
+ delete[] state_;
+ state_ = nullptr;
+ std::swap(state_, s.state_);
+ }
+ return *this;
+}
+
+inline bool Status::operator==(const Status& rhs) const {
+ return (code_ == rhs.code_);
+}
+
+inline bool Status::operator!=(const Status& rhs) const {
+ return !(*this == rhs);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/table.h b/storage/rocksdb/rocksdb/include/rocksdb/table.h
new file mode 100644
index 00000000..fb5d6711
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/table.h
@@ -0,0 +1,607 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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.
+//
+// Currently we support two types of tables: plain table and block-based table.
+// 1. Block-based table: this is the default table type that we inherited from
+// LevelDB, which was designed for storing data in hard disk or flash
+// device.
+// 2. Plain table: it is one of RocksDB's SST file format optimized
+// for low query latency on pure-memory or really low-latency media.
+//
+// A tutorial of rocksdb table formats is available here:
+// https://github.com/facebook/rocksdb/wiki/A-Tutorial-of-RocksDB-SST-formats
+//
+// Example code is also available
+// https://github.com/facebook/rocksdb/wiki/A-Tutorial-of-RocksDB-SST-formats#wiki-examples
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "rocksdb/cache.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// -- Block-based Table
+class FlushBlockPolicyFactory;
+class PersistentCache;
+class RandomAccessFile;
+struct TableReaderOptions;
+struct TableBuilderOptions;
+class TableBuilder;
+class TableReader;
+class WritableFileWriter;
+struct EnvOptions;
+struct Options;
+
+enum ChecksumType : char {
+ kNoChecksum = 0x0,
+ kCRC32c = 0x1,
+ kxxHash = 0x2,
+ kxxHash64 = 0x3,
+};
+
+// For advanced user only
+struct BlockBasedTableOptions {
+ // @flush_block_policy_factory creates the instances of flush block policy.
+ // which provides a configurable way to determine when to flush a block in
+ // the block based tables. If not set, table builder will use the default
+ // block flush policy, which cut blocks by block size (please refer to
+ // `FlushBlockBySizePolicy`).
+ std::shared_ptr<FlushBlockPolicyFactory> flush_block_policy_factory;
+
+ // TODO(kailiu) Temporarily disable this feature by making the default value
+ // to be false.
+ //
+ // TODO(ajkr) we need to update names of variables controlling meta-block
+ // caching as they should now apply to range tombstone and compression
+ // dictionary meta-blocks, in addition to index and filter meta-blocks.
+ //
+ // 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.
+ bool cache_index_and_filter_blocks = false;
+
+ // If cache_index_and_filter_blocks is enabled, 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.
+ bool cache_index_and_filter_blocks_with_high_priority = true;
+
+ // if cache_index_and_filter_blocks is true and the below is true, then
+ // 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.
+ bool pin_l0_filter_and_index_blocks_in_cache = false;
+
+ // If cache_index_and_filter_blocks 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.
+ bool pin_top_level_index_and_filter = true;
+
+ // The index type that will be used for this table.
+ enum IndexType : char {
+ // A space efficient index block that is optimized for
+ // binary-search-based index.
+ kBinarySearch = 0x00,
+
+ // The hash index, if enabled, will do the hash lookup when
+ // `Options.prefix_extractor` is provided.
+ kHashSearch = 0x01,
+
+ // A two-level index implementation. Both levels are binary search indexes.
+ kTwoLevelIndexSearch = 0x02,
+
+ // Like 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.
+ //
+ // IO errors are not handled correctly in this mode right now: if an error
+ // happens when lazily reading a block in value(), value() returns empty
+ // slice, and you need to call Valid()/status() afterwards.
+ // TODO(kolmike): Fix it.
+ kBinarySearchWithFirstKey = 0x03,
+ };
+
+ IndexType index_type = kBinarySearch;
+
+ // The index type that will be used for the data block.
+ enum DataBlockIndexType : char {
+ kDataBlockBinarySearch = 0, // traditional block type
+ kDataBlockBinaryAndHash = 1, // additional 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;
+
+ // This option is now deprecated. No matter what value it is set to,
+ // it will behave as if hash_index_allow_collision=true.
+ bool hash_index_allow_collision = true;
+
+ // Use the specified checksum type. Newly created table files will be
+ // protected with this checksum type. Old table files will still be readable,
+ // even though they have different checksum type.
+ ChecksumType checksum = kCRC32c;
+
+ // Disable block cache. If this is set to true,
+ // then no block cache should be used, and the block_cache should
+ // point to a nullptr object.
+ bool no_block_cache = false;
+
+ // If non-NULL use the specified cache for blocks.
+ // If NULL, rocksdb will automatically create and use an 8MB internal cache.
+ std::shared_ptr<Cache> block_cache = nullptr;
+
+ // If non-NULL use the specified cache for pages read from device
+ // IF NULL, no page cache is used
+ std::shared_ptr<PersistentCache> persistent_cache = nullptr;
+
+ // If non-NULL use the specified cache for compressed blocks.
+ // If NULL, rocksdb will not use a compressed block cache.
+ // Note: though it looks similar to `block_cache`, RocksDB doesn't put the
+ // same type of object there.
+ std::shared_ptr<Cache> block_cache_compressed = nullptr;
+
+ // 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.
+ size_t block_size = 4 * 1024;
+
+ // This is used to close a block before it reaches the configured
+ // 'block_size'. 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.
+ int block_size_deviation = 10;
+
+ // Number of keys between restart points for delta encoding of keys.
+ // This parameter can be changed dynamically. Most clients should
+ // leave this parameter alone. The minimum value allowed is 1. Any smaller
+ // value will be silently overwritten with 1.
+ int block_restart_interval = 16;
+
+ // Same as block_restart_interval but used for the index block.
+ int index_block_restart_interval = 1;
+
+ // Block size for partitioned metadata. Currently applied to indexes when
+ // kTwoLevelIndexSearch is used and to filters when partition_filters is used.
+ // Note: Since in the current implementation the filters and index partitions
+ // are aligned, an index/filter block is created when either index or filter
+ // block size reaches the specified limit.
+ // Note: this limit is currently applied to only index blocks; a filter
+ // partition is cut right after an index block is cut
+ // TODO(myabandeh): remove the note above when filter partitions are cut
+ // separately
+ uint64_t metadata_block_size = 4096;
+
+ // Note: currently this option requires kTwoLevelIndexSearch to be set as
+ // well.
+ // TODO(myabandeh): remove the note above once the limitation is lifted
+ // Use partitioned full filters for each SST file. This option is
+ // incompatible with block-based filters.
+ bool partition_filters = false;
+
+ // Use delta encoding to compress keys in blocks.
+ // ReadOptions::pin_data requires this option to be disabled.
+ //
+ // Default: true
+ bool use_delta_encoding = true;
+
+ // If non-nullptr, use the specified filter policy to reduce disk reads.
+ // Many applications will benefit from passing the result of
+ // NewBloomFilterPolicy() here.
+ std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
+
+ // If true, place whole keys in the filter (not just prefixes).
+ // This must generally be true for gets to be efficient.
+ bool whole_key_filtering = true;
+
+ // Verify that decompressing the compressed block gives back the input. This
+ // is a verification mode that we use to detect bugs in compression
+ // algorithms.
+ bool verify_compression = false;
+
+ // 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)
+ uint32_t read_amp_bytes_per_bit = 0;
+
+ // 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.
+ uint32_t format_version = 2;
+
+ // 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
+ bool enable_index_compression = true;
+
+ // Align data blocks on lesser of page size and block size
+ bool block_align = false;
+
+ // This enum allows trading off increased index size for improved iterator
+ // seek performance in some situations, particularly when block cache is
+ // disabled (ReadOptions::fill_cache = false) and direct IO is
+ // enabled (DBOptions::use_direct_reads = 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.
+ enum class IndexShorteningMode : char {
+ // Use full keys.
+ kNoShortening,
+ // Shorten index keys between blocks, but use full key for the last index
+ // key, which is the upper bound of the whole file.
+ kShortenSeparators,
+ // Shorten both keys between blocks and key after last block.
+ kShortenSeparatorsAndSuccessor,
+ };
+
+ IndexShorteningMode index_shortening =
+ IndexShorteningMode::kShortenSeparators;
+};
+
+// Table Properties that are specific to block-based table properties.
+struct BlockBasedTablePropertyNames {
+ // value of this properties is a fixed int32 number.
+ static const std::string kIndexType;
+ // value is "1" for true and "0" for false.
+ static const std::string kWholeKeyFiltering;
+ // value is "1" for true and "0" for false.
+ static const std::string kPrefixFiltering;
+};
+
+// Create default block based table factory.
+extern TableFactory* NewBlockBasedTableFactory(
+ const BlockBasedTableOptions& table_options = BlockBasedTableOptions());
+
+#ifndef ROCKSDB_LITE
+
+enum EncodingType : char {
+ // Always write full keys without any special encoding.
+ kPlain,
+ // 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,
+};
+
+// Table Properties that are specific to plain table properties.
+struct PlainTablePropertyNames {
+ static const std::string kEncodingType;
+ static const std::string kBloomVersion;
+ static const std::string kNumBloomBlocks;
+};
+
+const uint32_t kPlainTableVariableLength = 0;
+
+struct PlainTableOptions {
+ // @user_key_len: plain table has optimization for fix-sized keys, which can
+ // be specified via user_key_len. Alternatively, you can pass
+ // `kPlainTableVariableLength` if your keys have variable
+ // lengths.
+ uint32_t user_key_len = kPlainTableVariableLength;
+
+ // @bloom_bits_per_key: the number of bits used for bloom filer per prefix.
+ // You may disable it by passing a zero.
+ int bloom_bits_per_key = 10;
+
+ // @hash_table_ratio: the desired utilization of the hash table used for
+ // prefix hashing.
+ // hash_table_ratio = number of prefixes / #buckets in the
+ // hash table
+ double hash_table_ratio = 0.75;
+
+ // @index_sparseness: inside each prefix, need to build one index record for
+ // how many keys for binary search inside each hash bucket.
+ // For encoding type kPrefix, the value will be used when
+ // writing to determine an interval to rewrite the full
+ // key. It will also be used as a suggestion and satisfied
+ // when possible.
+ size_t index_sparseness = 16;
+
+ // @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:
+ // sysctl -w vm.nr_hugepages=20
+ // See linux doc Documentation/vm/hugetlbpage.txt
+ size_t huge_page_tlb_size = 0;
+
+ // @encoding_type: how to encode the keys. See enum EncodingType above 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.
+ EncodingType encoding_type = kPlain;
+
+ // @full_scan_mode: mode for reading the whole file one record by one without
+ // using the index.
+ bool full_scan_mode = false;
+
+ // @store_index_in_file: compute plain table index and bloom filter during
+ // file building and store it in file. When reading
+ // file, index will be mmaped instead of recomputation.
+ bool store_index_in_file = false;
+};
+
+// -- Plain Table with prefix-only seek
+// For this factory, you need to set Options.prefix_extractor properly to make
+// it work. Look-up will starts with prefix hash lookup for key prefix. Inside
+// the hash bucket found, a binary search is executed for hash conflicts.
+// Finally, a linear search is used.
+
+extern TableFactory* NewPlainTableFactory(
+ const PlainTableOptions& options = PlainTableOptions());
+
+struct CuckooTablePropertyNames {
+ // The key that is used to fill empty buckets.
+ static const std::string kEmptyKey;
+ // Fixed length of value.
+ static const std::string kValueLength;
+ // Number of hash functions used in Cuckoo Hash.
+ static const std::string kNumHashFunc;
+ // It denotes the number of buckets in a Cuckoo Block. Given a key and a
+ // particular hash function, a Cuckoo Block is a set of consecutive buckets,
+ // where starting bucket id is given by the hash function on the key. In case
+ // of a collision during inserting the key, the builder tries to insert the
+ // key in other locations of the cuckoo block before using the next hash
+ // function. This reduces cache miss during read operation in case of
+ // collision.
+ static const std::string kCuckooBlockSize;
+ // Size of the hash table. Use this number to compute the modulo of hash
+ // function. The actual number of buckets will be kMaxHashTableSize +
+ // kCuckooBlockSize - 1. The last kCuckooBlockSize-1 buckets are used to
+ // accommodate the Cuckoo Block from end of hash table, due to cache friendly
+ // implementation.
+ static const std::string kHashTableSize;
+ // Denotes if the key sorted in the file is Internal Key (if false)
+ // or User Key only (if true).
+ static const std::string kIsLastLevel;
+ // Indicate if using identity function for the first hash function.
+ static const std::string kIdentityAsFirstHash;
+ // Indicate if using module or bit and to calculate hash value
+ static const std::string kUseModuleHash;
+ // Fixed user key length
+ static const std::string kUserKeyLength;
+};
+
+struct CuckooTableOptions {
+ // Determines the utilization of hash tables. Smaller values
+ // result in larger hash tables with fewer collisions.
+ double hash_table_ratio = 0.9;
+ // A property used by builder to determine the depth to go to
+ // to search for a path to displace elements in case of
+ // collision. See Builder.MakeSpaceForKey method. Higher
+ // values result in more efficient hash tables with fewer
+ // lookups but take more time to build.
+ uint32_t max_search_depth = 100;
+ // In case of collision while inserting, the builder
+ // attempts to insert in the next cuckoo_block_size
+ // locations before skipping over to the next Cuckoo hash
+ // function. This makes lookups more cache friendly in case
+ // of collisions.
+ uint32_t cuckoo_block_size = 5;
+ // If this option is enabled, user key is treated as uint64_t and its value
+ // is used as hash value directly. This option changes builder's behavior.
+ // Reader ignore this option and behave according to what specified in table
+ // property.
+ bool identity_as_first_hash = false;
+ // If this option is set to true, module is used during hash calculation.
+ // This often yields better space efficiency at the cost of performance.
+ // If this option is set to false, # of entries in table is constrained to be
+ // power of two, and bit and is used to calculate hash, which is faster in
+ // general.
+ bool use_module_hash = true;
+};
+
+// Cuckoo Table Factory for SST table format using Cache Friendly Cuckoo Hashing
+extern TableFactory* NewCuckooTableFactory(
+ const CuckooTableOptions& table_options = CuckooTableOptions());
+
+#endif // ROCKSDB_LITE
+
+class RandomAccessFileReader;
+
+// A base class for table factories.
+class TableFactory {
+ public:
+ virtual ~TableFactory() {}
+
+ // The type of the table.
+ //
+ // The client of this package should switch to a new name whenever
+ // the table format implementation changes.
+ //
+ // Names starting with "rocksdb." are reserved and should not be used
+ // by any clients of this package.
+ virtual const char* Name() const = 0;
+
+ // Returns a Table object table that can fetch data from file specified
+ // in parameter file. It's the caller's responsibility to make sure
+ // file is in the correct format.
+ //
+ // NewTableReader() is called in three places:
+ // (1) TableCache::FindTable() calls the function when table cache miss
+ // and cache the table object returned.
+ // (2) SstFileDumper (for SST Dump) opens the table and dump the table
+ // contents using the iterator of the table.
+ // (3) DBImpl::IngestExternalFile() calls this function to read the contents
+ // of the sst file it's attempting to add
+ //
+ // table_reader_options is a TableReaderOptions which contain all the
+ // needed parameters and configuration to open the table.
+ // file is a file handler to handle the file for the table.
+ // file_size is the physical file size of the file.
+ // table_reader is the output table reader.
+ virtual Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ bool prefetch_index_and_filter_in_cache = true) const = 0;
+
+ // Return a table builder to write to a file for this table type.
+ //
+ // It is called in several places:
+ // (1) When flushing memtable to a level-0 output file, it creates a table
+ // builder (In DBImpl::WriteLevel0Table(), by calling BuildTable())
+ // (2) During compaction, it gets the builder for writing compaction output
+ // files in DBImpl::OpenCompactionOutputFile().
+ // (3) When recovering from transaction logs, it creates a table builder to
+ // write to a level-0 output file (In DBImpl::WriteLevel0TableForRecovery,
+ // by calling BuildTable())
+ // (4) When running Repairer, it creates a table builder to convert logs to
+ // SST files (In Repairer::ConvertLogToTable() by calling BuildTable())
+ //
+ // Multiple configured can be accessed from there, including and not limited
+ // to compression options. file is a handle of a writable file.
+ // It is the caller's responsibility to keep the file open and close the file
+ // after closing the table builder. compression_type is the compression type
+ // to use in this table.
+ virtual TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_family_id, WritableFileWriter* file) const = 0;
+
+ // Sanitizes the specified DB Options and ColumnFamilyOptions.
+ //
+ // If the function cannot find a way to sanitize the input DB Options,
+ // a non-ok Status will be returned.
+ virtual Status SanitizeOptions(const DBOptions& db_opts,
+ const ColumnFamilyOptions& cf_opts) const = 0;
+
+ // Return a string that contains printable format of table configurations.
+ // RocksDB prints configurations at DB Open().
+ virtual std::string GetPrintableTableOptions() const = 0;
+
+ virtual Status GetOptionString(std::string* /*opt_string*/,
+ const std::string& /*delimiter*/) const {
+ return Status::NotSupported(
+ "The table factory doesn't implement GetOptionString().");
+ }
+
+ // Returns the raw pointer of the table options that is used by this
+ // TableFactory, or nullptr if this function is not supported.
+ // Since the return value is a raw pointer, the TableFactory owns the
+ // pointer and the caller should not delete the pointer.
+ //
+ // In certain case, it is desirable to alter the underlying options when the
+ // TableFactory is not used by any open DB by casting the returned pointer
+ // to the right class. For instance, if BlockBasedTableFactory is used,
+ // then the pointer can be casted to BlockBasedTableOptions.
+ //
+ // Note that changing the underlying TableFactory options while the
+ // TableFactory is currently used by any open DB is undefined behavior.
+ // Developers should use DB::SetOption() instead to dynamically change
+ // options while the DB is open.
+ virtual void* GetOptions() { return nullptr; }
+
+ // Return is delete range supported
+ virtual bool IsDeleteRangeSupported() const { return false; }
+};
+
+#ifndef ROCKSDB_LITE
+// Create a special table factory that can open either of the supported
+// table formats, based on setting inside the SST files. It should be used to
+// convert a DB from one table format to another.
+// @table_factory_to_write: the table factory used when writing to new files.
+// @block_based_table_factory: block based table factory to use. If NULL, use
+// a default one.
+// @plain_table_factory: plain table factory to use. If NULL, use a default one.
+// @cuckoo_table_factory: cuckoo table factory to use. If NULL, use a default
+// one.
+extern TableFactory* NewAdaptiveTableFactory(
+ std::shared_ptr<TableFactory> table_factory_to_write = nullptr,
+ std::shared_ptr<TableFactory> block_based_table_factory = nullptr,
+ std::shared_ptr<TableFactory> plain_table_factory = nullptr,
+ std::shared_ptr<TableFactory> cuckoo_table_factory = nullptr);
+
+#endif // ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/table_properties.h b/storage/rocksdb/rocksdb/include/rocksdb/table_properties.h
new file mode 100644
index 00000000..d0ac0231
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/table_properties.h
@@ -0,0 +1,250 @@
+// 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.
+#pragma once
+
+#include <stdint.h>
+#include <map>
+#include <string>
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// -- Table Properties
+// Other than basic table properties, each table may also have the user
+// collected properties.
+// The value of the user-collected properties are encoded as raw bytes --
+// users have to interpret these values by themselves.
+// Note: To do prefix seek/scan in `UserCollectedProperties`, you can do
+// something similar to:
+//
+// UserCollectedProperties props = ...;
+// for (auto pos = props.lower_bound(prefix);
+// pos != props.end() && pos->first.compare(0, prefix.size(), prefix) == 0;
+// ++pos) {
+// ...
+// }
+typedef std::map<std::string, std::string> UserCollectedProperties;
+
+// table properties' human-readable names in the property block.
+struct TablePropertiesNames {
+ static const std::string kDataSize;
+ static const std::string kIndexSize;
+ static const std::string kIndexPartitions;
+ static const std::string kTopLevelIndexSize;
+ static const std::string kIndexKeyIsUserKey;
+ static const std::string kIndexValueIsDeltaEncoded;
+ static const std::string kFilterSize;
+ static const std::string kRawKeySize;
+ static const std::string kRawValueSize;
+ static const std::string kNumDataBlocks;
+ static const std::string kNumEntries;
+ static const std::string kDeletedKeys;
+ static const std::string kMergeOperands;
+ static const std::string kNumRangeDeletions;
+ static const std::string kFormatVersion;
+ static const std::string kFixedKeyLen;
+ static const std::string kFilterPolicy;
+ static const std::string kColumnFamilyName;
+ static const std::string kColumnFamilyId;
+ static const std::string kComparator;
+ static const std::string kMergeOperator;
+ static const std::string kPrefixExtractorName;
+ static const std::string kPropertyCollectors;
+ static const std::string kCompression;
+ static const std::string kCompressionOptions;
+ static const std::string kCreationTime;
+ static const std::string kOldestKeyTime;
+ static const std::string kFileCreationTime;
+};
+
+extern const std::string kPropertiesBlock;
+extern const std::string kCompressionDictBlock;
+extern const std::string kRangeDelBlock;
+
+// `TablePropertiesCollector` provides the mechanism for users to collect
+// their own properties that they are interested in. This class is essentially
+// a collection of callback functions that will be invoked during table
+// building. It is constructed with TablePropertiesCollectorFactory. The methods
+// don't need to be thread-safe, as we will create exactly one
+// TablePropertiesCollector object per table and then call it sequentially
+class TablePropertiesCollector {
+ public:
+ virtual ~TablePropertiesCollector() {}
+
+ // DEPRECATE User defined collector should implement AddUserKey(), though
+ // this old function still works for backward compatible reason.
+ // Add() will be called when a new key/value pair is inserted into the table.
+ // @params key the user key that is inserted into the table.
+ // @params value the value that is inserted into the table.
+ virtual Status Add(const Slice& /*key*/, const Slice& /*value*/) {
+ return Status::InvalidArgument(
+ "TablePropertiesCollector::Add() deprecated.");
+ }
+
+ // AddUserKey() will be called when a new key/value pair is inserted into the
+ // table.
+ // @params key the user key that is inserted into the table.
+ // @params value the value that is inserted into the table.
+ virtual Status AddUserKey(const Slice& key, const Slice& value,
+ EntryType /*type*/, SequenceNumber /*seq*/,
+ uint64_t /*file_size*/) {
+ // For backwards-compatibility.
+ return Add(key, value);
+ }
+
+ // Called after each new block is cut
+ virtual void BlockAdd(uint64_t /* blockRawBytes */,
+ uint64_t /* blockCompressedBytesFast */,
+ uint64_t /* blockCompressedBytesSlow */) {
+ // Nothing to do here. Callback registers can override.
+ return;
+ }
+
+ // Finish() will be called when a table has already been built and is ready
+ // for writing the properties block.
+ // @params properties User will add their collected statistics to
+ // `properties`.
+ virtual Status Finish(UserCollectedProperties* properties) = 0;
+
+ // Return the human-readable properties, where the key is property name and
+ // the value is the human-readable form of value.
+ virtual UserCollectedProperties GetReadableProperties() const = 0;
+
+ // The name of the properties collector can be used for debugging purpose.
+ virtual const char* Name() const = 0;
+
+ // EXPERIMENTAL Return whether the output file should be further compacted
+ virtual bool NeedCompact() const { return false; }
+};
+
+// Constructs TablePropertiesCollector. Internals create a new
+// TablePropertiesCollector for each new table
+class TablePropertiesCollectorFactory {
+ public:
+ struct Context {
+ uint32_t column_family_id;
+ static const uint32_t kUnknownColumnFamily;
+ };
+
+ virtual ~TablePropertiesCollectorFactory() {}
+ // has to be thread-safe
+ virtual TablePropertiesCollector* CreateTablePropertiesCollector(
+ TablePropertiesCollectorFactory::Context context) = 0;
+
+ // The name of the properties collector can be used for debugging purpose.
+ virtual const char* Name() const = 0;
+};
+
+// TableProperties contains a bunch of read-only properties of its associated
+// table.
+struct TableProperties {
+ public:
+ // the total size of all data blocks.
+ uint64_t data_size = 0;
+ // the size of index block.
+ uint64_t index_size = 0;
+ // Total number of index partitions if kTwoLevelIndexSearch is used
+ uint64_t index_partitions = 0;
+ // Size of the top-level index if kTwoLevelIndexSearch is used
+ uint64_t top_level_index_size = 0;
+ // Whether the index key is user key. Otherwise it includes 8 byte of sequence
+ // number added by internal key format.
+ uint64_t index_key_is_user_key = 0;
+ // Whether delta encoding is used to encode the index values.
+ uint64_t index_value_is_delta_encoded = 0;
+ // the size of filter block.
+ uint64_t filter_size = 0;
+ // total raw key size
+ uint64_t raw_key_size = 0;
+ // total raw value size
+ uint64_t raw_value_size = 0;
+ // the number of blocks in this table
+ uint64_t num_data_blocks = 0;
+ // the number of entries in this table
+ uint64_t num_entries = 0;
+ // the number of deletions in the table
+ uint64_t num_deletions = 0;
+ // the number of merge operands in the table
+ uint64_t num_merge_operands = 0;
+ // the number of range deletions in this table
+ uint64_t num_range_deletions = 0;
+ // format version, reserved for backward compatibility
+ uint64_t format_version = 0;
+ // If 0, key is variable length. Otherwise number of bytes for each key.
+ uint64_t fixed_key_len = 0;
+ // ID of column family for this SST file, corresponding to the CF identified
+ // by column_family_name.
+ uint64_t column_family_id = ROCKSDB_NAMESPACE::
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily;
+ // Timestamp of the latest key. 0 means unknown.
+ // TODO(sagar0): Should be changed to latest_key_time ... but don't know the
+ // full implications of backward compatibility. Hence retaining for now.
+ uint64_t creation_time = 0;
+ // Timestamp of the earliest key. 0 means unknown.
+ uint64_t oldest_key_time = 0;
+ // Actual SST file creation time. 0 means unknown.
+ uint64_t file_creation_time = 0;
+
+ // Name of the column family with which this SST file is associated.
+ // If column family is unknown, `column_family_name` will be an empty string.
+ std::string column_family_name;
+
+ // The name of the filter policy used in this table.
+ // If no filter policy is used, `filter_policy_name` will be an empty string.
+ std::string filter_policy_name;
+
+ // The name of the comparator used in this table.
+ std::string comparator_name;
+
+ // The name of the merge operator used in this table.
+ // If no merge operator is used, `merge_operator_name` will be "nullptr".
+ std::string merge_operator_name;
+
+ // The name of the prefix extractor used in this table
+ // If no prefix extractor is used, `prefix_extractor_name` will be "nullptr".
+ std::string prefix_extractor_name;
+
+ // The names of the property collectors factories used in this table
+ // separated by commas
+ // {collector_name[1]},{collector_name[2]},{collector_name[3]} ..
+ std::string property_collectors_names;
+
+ // The compression algo used to compress the SST files.
+ std::string compression_name;
+
+ // Compression options used to compress the SST files.
+ std::string compression_options;
+
+ // user collected properties
+ UserCollectedProperties user_collected_properties;
+ UserCollectedProperties readable_properties;
+
+ // The offset of the value of each property in the file.
+ std::map<std::string, uint64_t> properties_offsets;
+
+ // convert this object to a human readable form
+ // @prop_delim: delimiter for each property.
+ std::string ToString(const std::string& prop_delim = "; ",
+ const std::string& kv_delim = "=") const;
+
+ // Aggregate the numerical member variables of the specified
+ // TableProperties.
+ void Add(const TableProperties& tp);
+};
+
+// Extra properties
+// Below is a list of non-basic properties that are collected by database
+// itself. Especially some properties regarding to the internal keys (which
+// is unknown to `table`).
+//
+// DEPRECATED: these properties now belong as TableProperties members. Please
+// use TableProperties::num_deletions and TableProperties::num_merge_operands,
+// respectively.
+extern uint64_t GetDeletedKeys(const UserCollectedProperties& props);
+extern uint64_t GetMergeOperands(const UserCollectedProperties& props,
+ bool* property_present);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/thread_status.h b/storage/rocksdb/rocksdb/include/rocksdb/thread_status.h
new file mode 100644
index 00000000..6b2f5c88
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/thread_status.h
@@ -0,0 +1,188 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 defines the structures for exposing run-time status of any
+// rocksdb-related thread. Such run-time status can be obtained via
+// GetThreadList() API.
+//
+// Note that all thread-status features are still under-development, and
+// thus APIs and class definitions might subject to change at this point.
+// Will remove this comment once the APIs have been finalized.
+
+#pragma once
+
+#include <stdint.h>
+#include <cstddef>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#if !defined(ROCKSDB_LITE) && !defined(NROCKSDB_THREAD_STATUS) && \
+ defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
+#define ROCKSDB_USING_THREAD_STATUS
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+// TODO(yhchiang): remove this function once c++14 is available
+// as std::max will be able to cover this.
+// Current MS compiler does not support constexpr
+template <int A, int B>
+struct constexpr_max {
+ static const int result = (A > B) ? A : B;
+};
+
+// A structure that describes the current status of a thread.
+// The status of active threads can be fetched using
+// ROCKSDB_NAMESPACE::GetThreadList().
+struct ThreadStatus {
+ // The type of a thread.
+ enum ThreadType : int {
+ HIGH_PRIORITY = 0, // RocksDB BG thread in high-pri thread pool
+ LOW_PRIORITY, // RocksDB BG thread in low-pri thread pool
+ USER, // User thread (Non-RocksDB BG thread)
+ BOTTOM_PRIORITY, // RocksDB BG thread in bottom-pri thread pool
+ NUM_THREAD_TYPES
+ };
+
+ // The type used to refer to a thread operation.
+ // A thread operation describes high-level action of a thread.
+ // Examples include compaction and flush.
+ enum OperationType : int {
+ OP_UNKNOWN = 0,
+ OP_COMPACTION,
+ OP_FLUSH,
+ NUM_OP_TYPES
+ };
+
+ enum OperationStage : int {
+ STAGE_UNKNOWN = 0,
+ STAGE_FLUSH_RUN,
+ STAGE_FLUSH_WRITE_L0,
+ STAGE_COMPACTION_PREPARE,
+ STAGE_COMPACTION_RUN,
+ STAGE_COMPACTION_PROCESS_KV,
+ STAGE_COMPACTION_INSTALL,
+ STAGE_COMPACTION_SYNC_FILE,
+ STAGE_PICK_MEMTABLES_TO_FLUSH,
+ STAGE_MEMTABLE_ROLLBACK,
+ STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS,
+ NUM_OP_STAGES
+ };
+
+ enum CompactionPropertyType : int {
+ COMPACTION_JOB_ID = 0,
+ COMPACTION_INPUT_OUTPUT_LEVEL,
+ COMPACTION_PROP_FLAGS,
+ COMPACTION_TOTAL_INPUT_BYTES,
+ COMPACTION_BYTES_READ,
+ COMPACTION_BYTES_WRITTEN,
+ NUM_COMPACTION_PROPERTIES
+ };
+
+ enum FlushPropertyType : int {
+ FLUSH_JOB_ID = 0,
+ FLUSH_BYTES_MEMTABLES,
+ FLUSH_BYTES_WRITTEN,
+ NUM_FLUSH_PROPERTIES
+ };
+
+ // The maximum number of properties of an operation.
+ // This number should be set to the biggest NUM_XXX_PROPERTIES.
+ static const int kNumOperationProperties =
+ constexpr_max<NUM_COMPACTION_PROPERTIES, NUM_FLUSH_PROPERTIES>::result;
+
+ // 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.
+ enum StateType : int {
+ STATE_UNKNOWN = 0,
+ STATE_MUTEX_WAIT = 1,
+ NUM_STATE_TYPES
+ };
+
+ ThreadStatus(const uint64_t _id, const ThreadType _thread_type,
+ const std::string& _db_name, const std::string& _cf_name,
+ const OperationType _operation_type,
+ const uint64_t _op_elapsed_micros,
+ const OperationStage _operation_stage,
+ const uint64_t _op_props[], const StateType _state_type)
+ : thread_id(_id),
+ thread_type(_thread_type),
+ db_name(_db_name),
+ cf_name(_cf_name),
+ operation_type(_operation_type),
+ op_elapsed_micros(_op_elapsed_micros),
+ operation_stage(_operation_stage),
+ state_type(_state_type) {
+ for (int i = 0; i < kNumOperationProperties; ++i) {
+ op_properties[i] = _op_props[i];
+ }
+ }
+
+ // 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 of the current thread operation in microseconds.
+ 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;
+
+ // The followings are a set of utility functions for interpreting
+ // the information of ThreadStatus
+
+ static std::string GetThreadTypeName(ThreadType thread_type);
+
+ // Obtain the name of an operation given its type.
+ static const std::string& GetOperationName(OperationType op_type);
+
+ static const std::string MicrosToString(uint64_t op_elapsed_time);
+
+ // Obtain a human-readable string describing the specified operation stage.
+ static const std::string& GetOperationStageName(OperationStage stage);
+
+ // Obtain the name of the "i"th operation property of the
+ // specified operation.
+ static const std::string& GetOperationPropertyName(OperationType op_type,
+ int i);
+
+ // Translate the "i"th property of the specified operation given
+ // a property value.
+ static std::map<std::string, uint64_t> InterpretOperationProperties(
+ OperationType op_type, const uint64_t* op_properties);
+
+ // Obtain the name of a state given its type.
+ static const std::string& GetStateName(StateType state_type);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/threadpool.h b/storage/rocksdb/rocksdb/include/rocksdb/threadpool.h
new file mode 100644
index 00000000..b39321fe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/threadpool.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+/*
+ * ThreadPool is a component that will spawn N background threads that will
+ * be used to execute scheduled work, The number of background threads could
+ * be modified by calling SetBackgroundThreads().
+ * */
+class ThreadPool {
+ public:
+ virtual ~ThreadPool() {}
+
+ // Wait for all threads to finish.
+ // Discard those threads that did not start
+ // executing
+ virtual void JoinAllThreads() = 0;
+
+ // Set the number of background threads that will be executing the
+ // scheduled jobs.
+ virtual void SetBackgroundThreads(int num) = 0;
+ virtual int GetBackgroundThreads() = 0;
+
+ // Get the number of jobs scheduled in the ThreadPool queue.
+ virtual unsigned int GetQueueLen() const = 0;
+
+ // Waits for all jobs to complete those
+ // that already started running and those that did not
+ // start yet. This ensures that everything that was thrown
+ // on the TP runs even though
+ // we may not have specified enough threads for the amount
+ // of jobs
+ virtual void WaitForJobsAndJoinAllThreads() = 0;
+
+ // Submit a fire and forget jobs
+ // This allows to submit the same job multiple times
+ virtual void SubmitJob(const std::function<void()>&) = 0;
+ // This moves the function in for efficiency
+ virtual void SubmitJob(std::function<void()>&&) = 0;
+};
+
+// NewThreadPool() is a function that could be used to create a ThreadPool
+// with `num_threads` background threads.
+extern ThreadPool* NewThreadPool(int num_threads);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/trace_reader_writer.h b/storage/rocksdb/rocksdb/include/rocksdb/trace_reader_writer.h
new file mode 100644
index 00000000..d58ed47b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/trace_reader_writer.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Allow custom implementations of TraceWriter and TraceReader.
+// By default, RocksDB provides a way to capture the traces to a file using the
+// factory NewFileTraceWriter(). But users could also choose to export traces to
+// any other system by providing custom implementations of TraceWriter and
+// TraceReader.
+
+// TraceWriter allows exporting RocksDB traces to any system, one operation at
+// a time.
+class TraceWriter {
+ public:
+ TraceWriter() {}
+ virtual ~TraceWriter() {}
+
+ virtual Status Write(const Slice& data) = 0;
+ virtual Status Close() = 0;
+ virtual uint64_t GetFileSize() = 0;
+};
+
+// TraceReader allows reading RocksDB traces from any system, one operation at
+// a time. A RocksDB Replayer could depend on this to replay opertions.
+class TraceReader {
+ public:
+ TraceReader() {}
+ virtual ~TraceReader() {}
+
+ virtual Status Read(std::string* data) = 0;
+ virtual Status Close() = 0;
+};
+
+// Factory methods to read/write traces from/to a file.
+Status NewFileTraceWriter(Env* env, const EnvOptions& env_options,
+ const std::string& trace_filename,
+ std::unique_ptr<TraceWriter>* trace_writer);
+Status NewFileTraceReader(Env* env, const EnvOptions& env_options,
+ const std::string& trace_filename,
+ std::unique_ptr<TraceReader>* trace_reader);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/transaction_log.h b/storage/rocksdb/rocksdb/include/rocksdb/transaction_log.h
new file mode 100644
index 00000000..48d0e5c0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/transaction_log.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <vector>
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/write_batch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class LogFile;
+typedef std::vector<std::unique_ptr<LogFile>> VectorLogPtr;
+
+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 = 0,
+
+ /* Indicates that WAL file is live and resides in the main db directory */
+ kAliveLogFile = 1
+};
+
+class LogFile {
+ public:
+ LogFile() {}
+ virtual ~LogFile() {}
+
+ // 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
+ virtual std::string PathName() const = 0;
+
+ // Primary identifier for log file.
+ // This is directly proportional to creation time of the log file
+ virtual uint64_t LogNumber() const = 0;
+
+ // Log file can be either alive or archived
+ virtual WalFileType Type() const = 0;
+
+ // Starting sequence number of writebatch written in this log file
+ virtual SequenceNumber StartSequence() const = 0;
+
+ // Size of log file on disk in Bytes
+ virtual uint64_t SizeFileBytes() const = 0;
+};
+
+struct BatchResult {
+ SequenceNumber sequence = 0;
+ std::unique_ptr<WriteBatch> writeBatchPtr;
+
+ // Add empty __ctor and __dtor for the rule of five
+ // However, preserve the original semantics and prohibit copying
+ // as the std::unique_ptr member does not copy.
+ BatchResult() {}
+
+ ~BatchResult() {}
+
+ BatchResult(const BatchResult&) = delete;
+
+ BatchResult& operator=(const BatchResult&) = delete;
+
+ BatchResult(BatchResult&& bResult)
+ : sequence(std::move(bResult.sequence)),
+ writeBatchPtr(std::move(bResult.writeBatchPtr)) {}
+
+ BatchResult& operator=(BatchResult&& bResult) {
+ sequence = std::move(bResult.sequence);
+ writeBatchPtr = std::move(bResult.writeBatchPtr);
+ return *this;
+ }
+};
+
+// 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
+class TransactionLogIterator {
+ public:
+ TransactionLogIterator() {}
+ virtual ~TransactionLogIterator() {}
+
+ // 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.
+ virtual bool Valid() = 0;
+
+ // Moves the iterator to the next WriteBatch.
+ // REQUIRES: Valid() to be true.
+ virtual void Next() = 0;
+
+ // Returns ok if the iterator is valid.
+ // Returns the Error when something has gone wrong.
+ virtual Status status() = 0;
+
+ // If valid return's 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.
+ virtual BatchResult GetBatch() = 0;
+
+ // The read options for TransactionLogIterator.
+ struct ReadOptions {
+ // If true, all data read from underlying storage will be
+ // verified against corresponding checksums.
+ // Default: true
+ bool verify_checksums_;
+
+ ReadOptions() : verify_checksums_(true) {}
+
+ explicit ReadOptions(bool verify_checksums)
+ : verify_checksums_(verify_checksums) {}
+ };
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/types.h b/storage/rocksdb/rocksdb/include/rocksdb/types.h
new file mode 100644
index 00000000..4d004b69
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/types.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Define all public custom types here.
+
+// Represents a sequence number in a WAL file.
+typedef uint64_t SequenceNumber;
+
+const SequenceNumber kMinUnCommittedSeq = 1; // 0 is always committed
+
+// User-oriented representation of internal key types.
+enum EntryType {
+ kEntryPut,
+ kEntryDelete,
+ kEntrySingleDelete,
+ kEntryMerge,
+ kEntryRangeDeletion,
+ kEntryBlobIndex,
+ kEntryOther,
+};
+
+// <user key, sequence number, and entry type> tuple.
+struct FullKey {
+ Slice user_key;
+ SequenceNumber sequence;
+ EntryType type;
+
+ FullKey() : sequence(0) {} // Intentionally left uninitialized (for speed)
+ FullKey(const Slice& u, const SequenceNumber& seq, EntryType t)
+ : user_key(u), sequence(seq), type(t) {}
+ std::string DebugString(bool hex = false) const;
+
+ void clear() {
+ user_key.clear();
+ sequence = 0;
+ type = EntryType::kEntryPut;
+ }
+};
+
+// Parse slice representing internal key to FullKey
+// Parsed FullKey is valid for as long as the memory pointed to by
+// internal_key is alive.
+bool ParseFullKey(const Slice& internal_key, FullKey* result);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/universal_compaction.h b/storage/rocksdb/rocksdb/include/rocksdb/universal_compaction.h
new file mode 100644
index 00000000..e3aeee6c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/universal_compaction.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <climits>
+#include <vector>
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// Algorithm used to make a compaction request stop picking new files
+// into a single compaction run
+//
+enum CompactionStopStyle {
+ kCompactionStopStyleSimilarSize, // pick files of similar size
+ kCompactionStopStyleTotalSize // total size of picked files > next file
+};
+
+class CompactionOptionsUniversal {
+ public:
+ // 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
+ unsigned int size_ratio;
+
+ // The minimum number of files in a single compaction run. Default: 2
+ unsigned int min_merge_width;
+
+ // The maximum number of files in a single compaction run. Default: UINT_MAX
+ unsigned int max_merge_width;
+
+ // 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.
+ unsigned int max_size_amplification_percent;
+
+ // 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
+ int compression_size_percent;
+
+ // The algorithm used to stop picking files into a single compaction run
+ // Default: kCompactionStopStyleTotalSize
+ CompactionStopStyle stop_style;
+
+ // Option to optimize the universal multi level compaction by enabling
+ // trivial move for non overlapping files.
+ // Default: false
+ bool allow_trivial_move;
+
+ // Default set of parameters
+ CompactionOptionsUniversal()
+ : size_ratio(1),
+ min_merge_width(2),
+ max_merge_width(UINT_MAX),
+ max_size_amplification_percent(200),
+ compression_size_percent(-1),
+ stop_style(kCompactionStopStyleTotalSize),
+ allow_trivial_move(false) {}
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/backupable_db.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/backupable_db.h
new file mode 100644
index 00000000..f281ed13
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/backupable_db.h
@@ -0,0 +1,341 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <cinttypes>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "rocksdb/utilities/stackable_db.h"
+
+#include "rocksdb/env.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct BackupableDBOptions {
+ // Where to keep the backup files. Has to be different than dbname_
+ // Best to set this to dbname_ + "/backups"
+ // Required
+ std::string backup_dir;
+
+ // Backup Env object. It will be used for backup file I/O. If it's
+ // nullptr, backups will be written out using DBs Env. If it's
+ // non-nullptr, backup's I/O will be performed using this object.
+ // If you want to have backups on HDFS, use HDFS Env here!
+ // Default: nullptr
+ Env* backup_env;
+
+ // If 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 share_table_files == false, each backup will be on its own and will
+ // not share any data with other backups.
+ // default: true
+ bool share_table_files;
+
+ // Backup info and error messages will be written to info_log
+ // if non-nullptr.
+ // Default: nullptr
+ Logger* info_log;
+
+ // If sync == true, we can guarantee you'll get consistent backup even
+ // on a machine crash/reboot. Backup process is slower with sync enabled.
+ // If sync == false, we don't guarantee anything on machine reboot. However,
+ // chances are some of the backups are consistent.
+ // Default: true
+ bool sync;
+
+ // If true, it will delete whatever backups there are already
+ // Default: false
+ bool destroy_old_data;
+
+ // 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
+ bool backup_log_files;
+
+ // Max bytes that can be transferred in a second during backup.
+ // If 0, go as fast as you can
+ // Default: 0
+ uint64_t backup_rate_limit;
+
+ // Backup rate limiter. Used to control transfer speed for backup. If this is
+ // not null, backup_rate_limit is ignored.
+ // Default: nullptr
+ std::shared_ptr<RateLimiter> backup_rate_limiter{nullptr};
+
+ // Max bytes that can be transferred in a second during restore.
+ // If 0, go as fast as you can
+ // Default: 0
+ uint64_t restore_rate_limit;
+
+ // Restore rate limiter. Used to control transfer speed during restore. If
+ // this is not null, restore_rate_limit is ignored.
+ // Default: nullptr
+ std::shared_ptr<RateLimiter> restore_rate_limiter{nullptr};
+
+ // 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
+ // identifed by its name, but by the triple (file name, crc32, file length)
+ // Default: false
+ // 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*
+ bool share_files_with_checksum;
+
+ // Up to this many background threads will copy files for CreateNewBackup()
+ // and RestoreDBFromBackup()
+ // Default: 1
+ int max_background_operations;
+
+ // During backup user can get callback every time next
+ // callback_trigger_interval_size bytes being copied.
+ // Default: 4194304
+ uint64_t callback_trigger_interval_size;
+
+ // For BackupEngineReadOnly, Open() will open at most this many of the
+ // latest non-corrupted backups.
+ //
+ // Note: this setting is ignored (behaves like INT_MAX) for any kind of
+ // writable BackupEngine because it would inhibit accounting for shared
+ // files for proper backup deletion, including purging any incompletely
+ // created backups on creation of a new backup.
+ //
+ // Default: INT_MAX
+ int max_valid_backups_to_open;
+
+ void Dump(Logger* logger) const;
+
+ explicit BackupableDBOptions(
+ const std::string& _backup_dir, Env* _backup_env = nullptr,
+ bool _share_table_files = true, Logger* _info_log = nullptr,
+ bool _sync = true, bool _destroy_old_data = false,
+ bool _backup_log_files = true, uint64_t _backup_rate_limit = 0,
+ uint64_t _restore_rate_limit = 0, int _max_background_operations = 1,
+ uint64_t _callback_trigger_interval_size = 4 * 1024 * 1024,
+ int _max_valid_backups_to_open = INT_MAX)
+ : backup_dir(_backup_dir),
+ backup_env(_backup_env),
+ share_table_files(_share_table_files),
+ info_log(_info_log),
+ sync(_sync),
+ destroy_old_data(_destroy_old_data),
+ backup_log_files(_backup_log_files),
+ backup_rate_limit(_backup_rate_limit),
+ restore_rate_limit(_restore_rate_limit),
+ share_files_with_checksum(false),
+ max_background_operations(_max_background_operations),
+ callback_trigger_interval_size(_callback_trigger_interval_size),
+ max_valid_backups_to_open(_max_valid_backups_to_open) {
+ assert(share_table_files || !share_files_with_checksum);
+ }
+};
+
+struct RestoreOptions {
+ // 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 BackupableDBOptions::backup_log_files = false for
+ // persisting in-memory databases.
+ // Default: false
+ bool keep_log_files;
+
+ explicit RestoreOptions(bool _keep_log_files = false)
+ : keep_log_files(_keep_log_files) {}
+};
+
+typedef uint32_t BackupID;
+
+struct BackupInfo {
+ BackupID backup_id;
+ int64_t timestamp;
+ uint64_t size;
+
+ uint32_t number_files;
+ std::string app_metadata;
+
+ BackupInfo() {}
+
+ BackupInfo(BackupID _backup_id, int64_t _timestamp, uint64_t _size,
+ uint32_t _number_files, const std::string& _app_metadata)
+ : backup_id(_backup_id),
+ timestamp(_timestamp),
+ size(_size),
+ number_files(_number_files),
+ app_metadata(_app_metadata) {}
+};
+
+class BackupStatistics {
+ public:
+ BackupStatistics() {
+ number_success_backup = 0;
+ number_fail_backup = 0;
+ }
+
+ BackupStatistics(uint32_t _number_success_backup,
+ uint32_t _number_fail_backup)
+ : number_success_backup(_number_success_backup),
+ number_fail_backup(_number_fail_backup) {}
+
+ ~BackupStatistics() {}
+
+ void IncrementNumberSuccessBackup();
+ void IncrementNumberFailBackup();
+
+ uint32_t GetNumberSuccessBackup() const;
+ uint32_t GetNumberFailBackup() const;
+
+ std::string ToString() const;
+
+ private:
+ uint32_t number_success_backup;
+ uint32_t number_fail_backup;
+};
+
+// A backup engine for accessing information about backups and restoring from
+// them.
+class BackupEngineReadOnly {
+ public:
+ virtual ~BackupEngineReadOnly() {}
+
+ static Status Open(Env* db_env, const BackupableDBOptions& options,
+ BackupEngineReadOnly** backup_engine_ptr);
+
+ // Returns info about backups in backup_info
+ // You can GetBackupInfo safely, even with other BackupEngine performing
+ // backups on the same directory
+ virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
+
+ // Returns info about corrupt backups in corrupt_backups
+ virtual void GetCorruptedBackups(
+ std::vector<BackupID>* corrupt_backup_ids) = 0;
+
+ // Restoring DB from backup is NOT safe when there is another BackupEngine
+ // running that might call DeleteBackup() or PurgeOldBackups(). It is caller's
+ // responsibility to synchronize the operation, i.e. don't delete the backup
+ // when you're restoring from it
+ // See also the corresponding doc in BackupEngine
+ virtual Status RestoreDBFromBackup(
+ BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) = 0;
+
+ // See the corresponding doc in BackupEngine
+ virtual Status RestoreDBFromLatestBackup(
+ const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) = 0;
+
+ // checks that each file exists and that the size of the file matches our
+ // expectations. it does not check file checksum.
+ //
+ // If this BackupEngine created the backup, it compares the files' current
+ // sizes against the number of bytes written to them during creation.
+ // Otherwise, it compares the files' current sizes against their sizes when
+ // the BackupEngine was opened.
+ //
+ // Returns Status::OK() if all checks are good
+ virtual Status VerifyBackup(BackupID backup_id) = 0;
+};
+
+// A backup engine for creating new backups.
+class BackupEngine {
+ public:
+ virtual ~BackupEngine() {}
+
+ // BackupableDBOptions have to be the same as the ones used in previous
+ // BackupEngines for the same backup directory.
+ static Status Open(Env* db_env, const BackupableDBOptions& options,
+ BackupEngine** backup_engine_ptr);
+
+ // same as CreateNewBackup, but stores extra application metadata
+ // Flush will always trigger if 2PC is enabled.
+ // If write-ahead logs are disabled, set flush_before_backup=true to
+ // avoid losing unflushed key/value pairs from the memtable.
+ virtual Status CreateNewBackupWithMetadata(
+ DB* db, const std::string& app_metadata, bool flush_before_backup = false,
+ std::function<void()> progress_callback = []() {}) = 0;
+
+ // Captures the state of the database in the latest backup
+ // NOT a thread safe call
+ // Flush will always trigger if 2PC is enabled.
+ // If write-ahead logs are disabled, set flush_before_backup=true to
+ // avoid losing unflushed key/value pairs from the memtable.
+ virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false,
+ std::function<void()> progress_callback =
+ []() {}) {
+ return CreateNewBackupWithMetadata(db, "", flush_before_backup,
+ progress_callback);
+ }
+
+ // Deletes old backups, keeping latest num_backups_to_keep alive.
+ // See also DeleteBackup.
+ virtual Status PurgeOldBackups(uint32_t num_backups_to_keep) = 0;
+
+ // Deletes a specific backup. If this operation (or PurgeOldBackups)
+ // is not completed due to crash, power failure, etc. the state
+ // will be cleaned up the next time you call DeleteBackup,
+ // PurgeOldBackups, or GarbageCollect.
+ virtual Status DeleteBackup(BackupID backup_id) = 0;
+
+ // Call this from another thread if you want to stop the backup
+ // that is currently happening. It will return immediatelly, will
+ // not wait for the backup to stop.
+ // The backup will stop ASAP and the call to CreateNewBackup will
+ // return Status::Incomplete(). It will not clean up after itself, but
+ // the state will remain consistent. The state will be cleaned up the
+ // next time you call CreateNewBackup or GarbageCollect.
+ virtual void StopBackup() = 0;
+
+ // Returns info about backups in backup_info
+ virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
+
+ // Returns info about corrupt backups in corrupt_backups
+ virtual void GetCorruptedBackups(
+ std::vector<BackupID>* corrupt_backup_ids) = 0;
+
+ // restore from backup with backup_id
+ // IMPORTANT -- if options_.share_table_files == true,
+ // options_.share_files_with_checksum == false, you restore 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.
+ virtual Status RestoreDBFromBackup(
+ BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) = 0;
+
+ // restore from the latest backup
+ virtual Status RestoreDBFromLatestBackup(
+ const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) = 0;
+
+ // checks that each file exists and that the size of the file matches our
+ // expectations. it does not check file checksum.
+ // Returns Status::OK() if all checks are good
+ virtual Status VerifyBackup(BackupID backup_id) = 0;
+
+ // Will delete any files left over from incomplete creation or deletion of
+ // a backup. This is not normally needed as those operations also clean up
+ // after prior incomplete calls to the same kind of operation (create or
+ // delete).
+ // NOTE: This is not designed to delete arbitrary files added to the backup
+ // directory outside of BackupEngine, and clean-up is always subject to
+ // permissions on and availability of the underlying filesystem.
+ virtual Status GarbageCollect() = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/checkpoint.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/checkpoint.h
new file mode 100644
index 00000000..c7f93b4c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/checkpoint.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// A checkpoint is an openable snapshot of a database at a point in time.
+
+#pragma once
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DB;
+class ColumnFamilyHandle;
+struct LiveFileMetaData;
+struct ExportImportFilesMetaData;
+
+class Checkpoint {
+ public:
+ // Creates a Checkpoint object to be used for creating openable snapshots
+ static Status Create(DB* db, Checkpoint** checkpoint_ptr);
+
+ // Builds an openable 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
+ // SST files will be copied if output directory is on a different filesystem
+ // (2) a copied manifest files and other files
+ // The directory should not already exist and will be created by this API.
+ // The directory will be an absolute path
+ // log_size_for_flush: if the total log file size is equal or larger than
+ // this value, then a flush is triggered for all the column families. The
+ // default value is 0, which means flush is always triggered. If you move
+ // away from the default, the checkpoint may not contain up-to-date data
+ // if WAL writing is not always enabled.
+ // Flush will always trigger if it is 2PC.
+ virtual Status CreateCheckpoint(const std::string& checkpoint_dir,
+ uint64_t log_size_for_flush = 0);
+
+ // Exports all live SST files of a specified Column Family onto export_dir,
+ // returning SST files information in metadata.
+ // - SST files will be created as hard links when the directory specified
+ // is in the same partition as the db directory, copied otherwise.
+ // - export_dir should not already exist and will be created by this API.
+ // - Always triggers a flush.
+ virtual Status ExportColumnFamily(ColumnFamilyHandle* handle,
+ const std::string& export_dir,
+ ExportImportFilesMetaData** metadata);
+
+ virtual ~Checkpoint() {}
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/convenience.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/convenience.h
new file mode 100644
index 00000000..f61afd69
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/convenience.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 file was moved to rocksdb/convenience.h"
+
+#include "rocksdb/convenience.h"
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/db_ttl.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/db_ttl.h
new file mode 100644
index 00000000..dd83cb24
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/db_ttl.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/stackable_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Database with TTL support.
+//
+// USE-CASES:
+// 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
+
+class DBWithTTL : public StackableDB {
+ public:
+ virtual Status CreateColumnFamilyWithTtl(
+ const ColumnFamilyOptions& options, const std::string& column_family_name,
+ ColumnFamilyHandle** handle, int ttl) = 0;
+
+ static Status Open(const Options& options, const std::string& dbname,
+ DBWithTTL** dbptr, int32_t ttl = 0,
+ bool read_only = false);
+
+ static Status Open(const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ DBWithTTL** dbptr, std::vector<int32_t> ttls,
+ bool read_only = false);
+
+ virtual void SetTtl(int32_t ttl) = 0;
+
+ virtual void SetTtl(ColumnFamilyHandle* h, int32_t ttl) = 0;
+
+ protected:
+ explicit DBWithTTL(DB* db) : StackableDB(db) {}
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/debug.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/debug.h
new file mode 100644
index 00000000..a2b6adcb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/debug.h
@@ -0,0 +1,49 @@
+// 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).
+
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/db.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Data associated with a particular version of a key. A database may internally
+// store multiple versions of a same user key due to snapshots, compaction not
+// happening yet, etc.
+struct KeyVersion {
+ KeyVersion() : user_key(""), value(""), sequence(0), type(0) {}
+
+ KeyVersion(const std::string& _user_key, const std::string& _value,
+ SequenceNumber _sequence, int _type)
+ : user_key(_user_key), value(_value), sequence(_sequence), type(_type) {}
+
+ std::string user_key;
+ std::string value;
+ SequenceNumber sequence;
+ // TODO(ajkr): we should provide a helper function that converts the int to a
+ // string describing the type for easier debugging.
+ int type;
+};
+
+// Returns listing of all versions of keys in the provided user key range.
+// The range is inclusive-inclusive, i.e., [`begin_key`, `end_key`], or
+// `max_num_ikeys` has been reached. Since all those keys returned will be
+// copied to memory, if the range covers too many keys, the memory usage
+// may be huge. `max_num_ikeys` can be used to cap the memory usage.
+// The result is inserted into the provided vector, `key_versions`.
+Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key,
+ size_t max_num_ikeys,
+ std::vector<KeyVersion>* key_versions);
+
+Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, Slice begin_key,
+ Slice end_key, size_t max_num_ikeys,
+ std::vector<KeyVersion>* key_versions);
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_librados.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_librados.h
new file mode 100644
index 00000000..361217c6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_librados.h
@@ -0,0 +1,175 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <string>
+
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/env_mirror.h"
+
+#include <rados/librados.hpp>
+
+namespace ROCKSDB_NAMESPACE {
+class LibradosWritableFile;
+
+class EnvLibrados : public EnvWrapper {
+ public:
+ // Create a brand new sequentially-readable file with the specified name.
+ // On success, stores a pointer to the new file in *result and returns OK.
+ // On failure stores nullptr in *result and returns non-OK. If the file does
+ // not exist, returns a non-OK status.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override;
+
+ // Create a brand new random access read-only file with the
+ // specified name. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK. If the file does not exist, returns a non-OK
+ // status.
+ //
+ // The returned file may be concurrently accessed by multiple threads.
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override;
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ // Reuse an existing file by renaming it and opening it as writable.
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ // Create an object that represents a directory. Will fail if directory
+ // doesn't exist. If the directory exists, it will open the directory
+ // and create a new Directory object.
+ //
+ // On success, stores a pointer to the new Directory in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override;
+
+ // Returns OK if the named file exists.
+ // NotFound if the named file does not exist,
+ // the calling process does not have permission to determine
+ // whether this file exists, or if the path is invalid.
+ // IOError if an IO Error was encountered
+ Status FileExists(const std::string& fname) override;
+
+ // Store in *result the names of the children of the specified directory.
+ // The names are relative to "dir".
+ // Original contents of *results are dropped.
+ Status GetChildren(const std::string& dir, std::vector<std::string>* result);
+
+ // Delete the named file.
+ Status DeleteFile(const std::string& fname) override;
+
+ // Create the specified directory. Returns error if directory exists.
+ Status CreateDir(const std::string& dirname) override;
+
+ // Creates directory if missing. Return Ok if it exists, or successful in
+ // Creating.
+ Status CreateDirIfMissing(const std::string& dirname) override;
+
+ // Delete the specified directory.
+ Status DeleteDir(const std::string& dirname) override;
+
+ // Store the size of fname in *file_size.
+ Status GetFileSize(const std::string& fname, uint64_t* file_size) override;
+
+ // Store the last modification time of fname in *file_mtime.
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override;
+ // Rename file src to target.
+ Status RenameFile(const std::string& src, const std::string& target) override;
+ // Hard Link file src to target.
+ Status LinkFile(const std::string& src, const std::string& target) override;
+
+ // Lock the specified file. Used to prevent concurrent access to
+ // the same db by multiple processes. On failure, stores nullptr in
+ // *lock and returns non-OK.
+ //
+ // On success, stores a pointer to the object that represents the
+ // acquired lock in *lock and returns OK. The caller should call
+ // UnlockFile(*lock) to release the lock. If the process exits,
+ // the lock will be automatically released.
+ //
+ // If somebody else already holds the lock, finishes immediately
+ // with a failure. I.e., this call does not wait for existing locks
+ // to go away.
+ //
+ // May create the named file if it does not already exist.
+ Status LockFile(const std::string& fname, FileLock** lock);
+
+ // Release the lock acquired by a previous successful call to LockFile.
+ // REQUIRES: lock was returned by a successful LockFile() call
+ // REQUIRES: lock has not already been unlocked.
+ Status UnlockFile(FileLock* lock);
+
+ // Get full directory name for this db.
+ Status GetAbsolutePath(const std::string& db_path, std::string* output_path);
+
+ // Generate unique id
+ std::string GenerateUniqueId();
+
+ // Get default EnvLibrados
+ static EnvLibrados* Default();
+
+ explicit EnvLibrados(const std::string& db_name,
+ const std::string& config_path,
+ const std::string& db_pool);
+
+ explicit EnvLibrados(
+ const std::string& client_name, // first 3 parameters are
+ // for RADOS client init
+ const std::string& cluster_name, const uint64_t flags,
+ const std::string& db_name, const std::string& config_path,
+ const std::string& db_pool, const std::string& wal_dir,
+ const std::string& wal_pool, const uint64_t write_buffer_size);
+ ~EnvLibrados() { _rados.shutdown(); }
+
+ private:
+ std::string _client_name;
+ std::string _cluster_name;
+ uint64_t _flags;
+ std::string _db_name; // get from user, readable string; Also used as db_id
+ // for db metadata
+ std::string _config_path;
+ librados::Rados _rados; // RADOS client
+ std::string _db_pool_name;
+ librados::IoCtx _db_pool_ioctx; // IoCtx for connecting db_pool
+ std::string _wal_dir; // WAL dir path
+ std::string _wal_pool_name;
+ librados::IoCtx _wal_pool_ioctx; // IoCtx for connecting wal_pool
+ uint64_t _write_buffer_size; // WritableFile buffer max size
+
+ /* private function to communicate with rados */
+ std::string _CreateFid();
+ Status _GetFid(const std::string& fname, std::string& fid);
+ Status _GetFid(const std::string& fname, std::string& fid, int fid_len);
+ Status _RenameFid(const std::string& old_fname, const std::string& new_fname);
+ Status _AddFid(const std::string& fname, const std::string& fid);
+ Status _DelFid(const std::string& fname);
+ Status _GetSubFnames(const std::string& dirname,
+ std::vector<std::string>* result);
+ librados::IoCtx* _GetIoctx(const std::string& prefix);
+ friend class LibradosWritableFile;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_mirror.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_mirror.h
new file mode 100644
index 00000000..8e96ac41
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/env_mirror.h
@@ -0,0 +1,180 @@
+// 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).
+// 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.
+//
+// MirrorEnv is an Env implementation that mirrors all file-related
+// operations to two backing Env's (provided at construction time).
+// Writes are mirrored. For read operations, we do the read from both
+// backends and assert that the results match.
+//
+// This is useful when implementing a new Env and ensuring that the
+// semantics and behavior are correct (in that they match that of an
+// existing, stable Env, like the default POSIX one).
+
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include <algorithm>
+#include <iostream>
+#include <vector>
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SequentialFileMirror;
+class RandomAccessFileMirror;
+class WritableFileMirror;
+
+class EnvMirror : public EnvWrapper {
+ Env *a_, *b_;
+ bool free_a_, free_b_;
+
+ public:
+ EnvMirror(Env* a, Env* b, bool free_a = false, bool free_b = false)
+ : EnvWrapper(a), a_(a), b_(b), free_a_(free_a), free_b_(free_b) {}
+ ~EnvMirror() {
+ if (free_a_) delete a_;
+ if (free_b_) delete b_;
+ }
+
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& options) override;
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& options) override;
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override;
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override;
+ virtual Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override {
+ std::unique_ptr<Directory> br;
+ Status as = a_->NewDirectory(name, result);
+ Status bs = b_->NewDirectory(name, &br);
+ assert(as == bs);
+ return as;
+ }
+ Status FileExists(const std::string& f) override {
+ Status as = a_->FileExists(f);
+ Status bs = b_->FileExists(f);
+ assert(as == bs);
+ return as;
+ }
+#if defined(_MSC_VER)
+#pragma warning(push)
+// logical operation on address of string constant
+#pragma warning(disable : 4130)
+#endif
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* r) override {
+ std::vector<std::string> ar, br;
+ Status as = a_->GetChildren(dir, &ar);
+ Status bs = b_->GetChildren(dir, &br);
+ assert(as == bs);
+ std::sort(ar.begin(), ar.end());
+ std::sort(br.begin(), br.end());
+ if (!as.ok() || ar != br) {
+ assert(0 == "getchildren results don't match");
+ }
+ *r = ar;
+ return as;
+ }
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+ Status DeleteFile(const std::string& f) override {
+ Status as = a_->DeleteFile(f);
+ Status bs = b_->DeleteFile(f);
+ assert(as == bs);
+ return as;
+ }
+ Status CreateDir(const std::string& d) override {
+ Status as = a_->CreateDir(d);
+ Status bs = b_->CreateDir(d);
+ assert(as == bs);
+ return as;
+ }
+ Status CreateDirIfMissing(const std::string& d) override {
+ Status as = a_->CreateDirIfMissing(d);
+ Status bs = b_->CreateDirIfMissing(d);
+ assert(as == bs);
+ return as;
+ }
+ Status DeleteDir(const std::string& d) override {
+ Status as = a_->DeleteDir(d);
+ Status bs = b_->DeleteDir(d);
+ assert(as == bs);
+ return as;
+ }
+ Status GetFileSize(const std::string& f, uint64_t* s) override {
+ uint64_t asize, bsize;
+ Status as = a_->GetFileSize(f, &asize);
+ Status bs = b_->GetFileSize(f, &bsize);
+ assert(as == bs);
+ assert(!as.ok() || asize == bsize);
+ *s = asize;
+ return as;
+ }
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override {
+ uint64_t amtime, bmtime;
+ Status as = a_->GetFileModificationTime(fname, &amtime);
+ Status bs = b_->GetFileModificationTime(fname, &bmtime);
+ assert(as == bs);
+ assert(!as.ok() || amtime - bmtime < 10000 || bmtime - amtime < 10000);
+ *file_mtime = amtime;
+ return as;
+ }
+
+ Status RenameFile(const std::string& s, const std::string& t) override {
+ Status as = a_->RenameFile(s, t);
+ Status bs = b_->RenameFile(s, t);
+ assert(as == bs);
+ return as;
+ }
+
+ Status LinkFile(const std::string& s, const std::string& t) override {
+ Status as = a_->LinkFile(s, t);
+ Status bs = b_->LinkFile(s, t);
+ assert(as == bs);
+ return as;
+ }
+
+ class FileLockMirror : public FileLock {
+ public:
+ FileLock *a_, *b_;
+ FileLockMirror(FileLock* a, FileLock* b) : a_(a), b_(b) {}
+ };
+
+ Status LockFile(const std::string& f, FileLock** l) override {
+ FileLock *al, *bl;
+ Status as = a_->LockFile(f, &al);
+ Status bs = b_->LockFile(f, &bl);
+ assert(as == bs);
+ if (as.ok()) *l = new FileLockMirror(al, bl);
+ return as;
+ }
+
+ Status UnlockFile(FileLock* l) override {
+ FileLockMirror* ml = static_cast<FileLockMirror*>(l);
+ Status as = a_->UnlockFile(ml->a_);
+ Status bs = b_->UnlockFile(ml->b_);
+ assert(as == bs);
+ delete ml;
+ return as;
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/info_log_finder.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/info_log_finder.h
new file mode 100644
index 00000000..824f8a3d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/info_log_finder.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This function can be used to list the Information logs,
+// given the db pointer.
+Status GetInfoLogList(DB* db, std::vector<std::string>* info_log_list);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd.h
new file mode 100644
index 00000000..94548b53
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd.h
@@ -0,0 +1,277 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <functional>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/ldb_tool.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "rocksdb/utilities/ldb_cmd_execute_result.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class LDBCommand {
+ public:
+ // Command-line arguments
+ static const std::string ARG_ENV_URI;
+ static const std::string ARG_DB;
+ static const std::string ARG_PATH;
+ static const std::string ARG_SECONDARY_PATH;
+ static const std::string ARG_HEX;
+ static const std::string ARG_KEY_HEX;
+ static const std::string ARG_VALUE_HEX;
+ static const std::string ARG_CF_NAME;
+ static const std::string ARG_TTL;
+ static const std::string ARG_TTL_START;
+ static const std::string ARG_TTL_END;
+ static const std::string ARG_TIMESTAMP;
+ static const std::string ARG_TRY_LOAD_OPTIONS;
+ static const std::string ARG_IGNORE_UNKNOWN_OPTIONS;
+ static const std::string ARG_FROM;
+ static const std::string ARG_TO;
+ static const std::string ARG_MAX_KEYS;
+ static const std::string ARG_BLOOM_BITS;
+ static const std::string ARG_FIX_PREFIX_LEN;
+ static const std::string ARG_COMPRESSION_TYPE;
+ static const std::string ARG_COMPRESSION_MAX_DICT_BYTES;
+ static const std::string ARG_BLOCK_SIZE;
+ static const std::string ARG_AUTO_COMPACTION;
+ static const std::string ARG_DB_WRITE_BUFFER_SIZE;
+ static const std::string ARG_WRITE_BUFFER_SIZE;
+ static const std::string ARG_FILE_SIZE;
+ static const std::string ARG_CREATE_IF_MISSING;
+ static const std::string ARG_NO_VALUE;
+
+ struct ParsedParams {
+ std::string cmd;
+ std::vector<std::string> cmd_params;
+ std::map<std::string, std::string> option_map;
+ std::vector<std::string> flags;
+ };
+
+ static LDBCommand* SelectCommand(const ParsedParams& parsed_parms);
+
+ static LDBCommand* InitFromCmdLineArgs(
+ const std::vector<std::string>& args, const Options& options,
+ const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families,
+ const std::function<LDBCommand*(const ParsedParams&)>& selector =
+ SelectCommand);
+
+ static LDBCommand* InitFromCmdLineArgs(
+ int argc, char** argv, const Options& options,
+ const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families);
+
+ bool ValidateCmdLineOptions();
+
+ virtual Options PrepareOptionsForOpenDB();
+
+ virtual void SetDBOptions(Options options) { options_ = options; }
+
+ virtual void SetColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>* column_families) {
+ if (column_families != nullptr) {
+ column_families_ = *column_families;
+ } else {
+ column_families_.clear();
+ }
+ }
+
+ void SetLDBOptions(const LDBOptions& ldb_options) {
+ ldb_options_ = ldb_options;
+ }
+
+ const std::map<std::string, std::string>& TEST_GetOptionMap() {
+ return option_map_;
+ }
+
+ const std::vector<std::string>& TEST_GetFlags() { return flags_; }
+
+ virtual bool NoDBOpen() { return false; }
+
+ virtual ~LDBCommand() { CloseDB(); }
+
+ /* Run the command, and return the execute result. */
+ void Run();
+
+ virtual void DoCommand() = 0;
+
+ LDBCommandExecuteResult GetExecuteState() { return exec_state_; }
+
+ void ClearPreviousRunState() { exec_state_.Reset(); }
+
+ // Consider using Slice::DecodeHex directly instead if you don't need the
+ // 0x prefix
+ static std::string HexToString(const std::string& str);
+
+ // Consider using Slice::ToString(true) directly instead if
+ // you don't need the 0x prefix
+ static std::string StringToHex(const std::string& str);
+
+ static const char* DELIM;
+
+ protected:
+ LDBCommandExecuteResult exec_state_;
+ std::string env_uri_;
+ std::string db_path_;
+ // If empty, open DB as primary. If non-empty, open the DB as secondary
+ // with this secondary path. When running against a database opened by
+ // another process, ldb wll leave the source directory completely intact.
+ std::string secondary_path_;
+ std::string column_family_name_;
+ DB* db_;
+ DBWithTTL* db_ttl_;
+ std::map<std::string, ColumnFamilyHandle*> cf_handles_;
+
+ /**
+ * true implies that this command can work if the db is opened in read-only
+ * mode.
+ */
+ bool is_read_only_;
+
+ /** If true, the key is input/output as hex in get/put/scan/delete etc. */
+ bool is_key_hex_;
+
+ /** If true, the value is input/output as hex in get/put/scan/delete etc. */
+ bool is_value_hex_;
+
+ /** If true, the value is treated as timestamp suffixed */
+ bool is_db_ttl_;
+
+ // If true, the kvs are output with their insert/modify timestamp in a ttl db
+ bool timestamp_;
+
+ // If true, try to construct options from DB's option files.
+ bool try_load_options_;
+
+ bool ignore_unknown_options_;
+
+ bool create_if_missing_;
+
+ /**
+ * Map of options passed on the command-line.
+ */
+ const std::map<std::string, std::string> option_map_;
+
+ /**
+ * Flags passed on the command-line.
+ */
+ const std::vector<std::string> flags_;
+
+ /** List of command-line options valid for this command */
+ const std::vector<std::string> valid_cmd_line_options_;
+
+ /** Shared pointer to underlying environment if applicable **/
+ std::shared_ptr<Env> env_guard_;
+
+ bool ParseKeyValue(const std::string& line, std::string* key,
+ std::string* value, bool is_key_hex, bool is_value_hex);
+
+ LDBCommand(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags, bool is_read_only,
+ const std::vector<std::string>& valid_cmd_line_options);
+
+ void OpenDB();
+
+ void CloseDB();
+
+ ColumnFamilyHandle* GetCfHandle();
+
+ static std::string PrintKeyValue(const std::string& key,
+ const std::string& value, bool is_key_hex,
+ bool is_value_hex);
+
+ static std::string PrintKeyValue(const std::string& key,
+ const std::string& value, bool is_hex);
+
+ /**
+ * Return true if the specified flag is present in the specified flags vector
+ */
+ static bool IsFlagPresent(const std::vector<std::string>& flags,
+ const std::string& flag) {
+ return (std::find(flags.begin(), flags.end(), flag) != flags.end());
+ }
+
+ static std::string HelpRangeCmdArgs();
+
+ /**
+ * A helper function that returns a list of command line options
+ * used by this command. It includes the common options and the ones
+ * passed in.
+ */
+ static std::vector<std::string> BuildCmdLineOptions(
+ std::vector<std::string> options);
+
+ bool ParseIntOption(const std::map<std::string, std::string>& options,
+ const std::string& option, int& value,
+ LDBCommandExecuteResult& exec_state);
+
+ bool ParseStringOption(const std::map<std::string, std::string>& options,
+ const std::string& option, std::string* value);
+
+ /**
+ * Returns the value of the specified option as a boolean.
+ * default_val is used if the option is not found in options.
+ * Throws an exception if the value of the option is not
+ * "true" or "false" (case insensitive).
+ */
+ bool ParseBooleanOption(const std::map<std::string, std::string>& options,
+ const std::string& option, bool default_val);
+
+ Options options_;
+ std::vector<ColumnFamilyDescriptor> column_families_;
+ LDBOptions ldb_options_;
+
+ private:
+ /**
+ * Interpret command line options and flags to determine if the key
+ * should be input/output in hex.
+ */
+ bool IsKeyHex(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ /**
+ * Interpret command line options and flags to determine if the value
+ * should be input/output in hex.
+ */
+ bool IsValueHex(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ /**
+ * Converts val to a boolean.
+ * val must be either true or false (case insensitive).
+ * Otherwise an exception is thrown.
+ */
+ bool StringToBool(std::string val);
+};
+
+class LDBCommandRunner {
+ public:
+ static void PrintHelp(const LDBOptions& ldb_options, const char* exec_name);
+
+ // Returns the status code to return. 0 is no error.
+ static int RunCommand(
+ int argc, char** argv, Options options, const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h
new file mode 100644
index 00000000..c837b47f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifdef FAILED
+#undef FAILED
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class LDBCommandExecuteResult {
+ public:
+ enum State {
+ EXEC_NOT_STARTED = 0,
+ EXEC_SUCCEED = 1,
+ EXEC_FAILED = 2,
+ };
+
+ LDBCommandExecuteResult() : state_(EXEC_NOT_STARTED), message_("") {}
+
+ LDBCommandExecuteResult(State state, std::string& msg)
+ : state_(state), message_(msg) {}
+
+ std::string ToString() {
+ std::string ret;
+ switch (state_) {
+ case EXEC_SUCCEED:
+ break;
+ case EXEC_FAILED:
+ ret.append("Failed: ");
+ break;
+ case EXEC_NOT_STARTED:
+ ret.append("Not started: ");
+ }
+ if (!message_.empty()) {
+ ret.append(message_);
+ }
+ return ret;
+ }
+
+ void Reset() {
+ state_ = EXEC_NOT_STARTED;
+ message_ = "";
+ }
+
+ bool IsSucceed() { return state_ == EXEC_SUCCEED; }
+
+ bool IsNotStarted() { return state_ == EXEC_NOT_STARTED; }
+
+ bool IsFailed() { return state_ == EXEC_FAILED; }
+
+ static LDBCommandExecuteResult Succeed(std::string msg) {
+ return LDBCommandExecuteResult(EXEC_SUCCEED, msg);
+ }
+
+ static LDBCommandExecuteResult Failed(std::string msg) {
+ return LDBCommandExecuteResult(EXEC_FAILED, msg);
+ }
+
+ private:
+ State state_;
+ std::string message_;
+
+ bool operator==(const LDBCommandExecuteResult&);
+ bool operator!=(const LDBCommandExecuteResult&);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/leveldb_options.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/leveldb_options.h
new file mode 100644
index 00000000..e9fef960
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/leveldb_options.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Cache;
+class Comparator;
+class Env;
+class FilterPolicy;
+class Logger;
+struct Options;
+class Snapshot;
+
+enum CompressionType : unsigned char;
+
+// Options to control the behavior of a database (passed to
+// DB::Open). A LevelDBOptions object can be initialized as though
+// it were a LevelDB Options object, and then it can be converted into
+// a RocksDB Options object.
+struct LevelDBOptions {
+ // -------------------
+ // Parameters that affect behavior
+
+ // Comparator used to define the order of keys in the table.
+ // Default: a comparator that uses lexicographic byte-wise ordering
+ //
+ // REQUIRES: The client must ensure that the comparator supplied
+ // here has the same name and orders keys *exactly* the same as the
+ // comparator provided to previous open calls on the same DB.
+ const Comparator* comparator;
+
+ // If true, the database will be created if it is missing.
+ // Default: false
+ bool create_if_missing;
+
+ // If true, an error is raised if the database already exists.
+ // Default: false
+ bool error_if_exists;
+
+ // 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.
+ // Default: false
+ bool paranoid_checks;
+
+ // Use the specified object to interact with the environment,
+ // e.g. to read/write files, schedule background work, etc.
+ // Default: Env::Default()
+ Env* env;
+
+ // Any internal progress/error information generated by the db will
+ // be written to info_log if it is non-NULL, or to a file stored
+ // in the same directory as the DB contents if info_log is NULL.
+ // Default: NULL
+ Logger* info_log;
+
+ // -------------------
+ // Parameters that affect performance
+
+ // 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 two 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: 4MB
+ size_t write_buffer_size;
+
+ // 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 (budget
+ // one open file per 2MB of working set).
+ //
+ // Default: 1000
+ int max_open_files;
+
+ // Control over blocks (user data is stored in a set of blocks, and
+ // a block is the unit of reading from disk).
+
+ // If non-NULL, use the specified cache for blocks.
+ // If NULL, leveldb will automatically create and use an 8MB internal cache.
+ // Default: NULL
+ Cache* block_cache;
+
+ // 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
+ size_t block_size;
+
+ // Number of keys between restart points for delta encoding of keys.
+ // This parameter can be changed dynamically. Most clients should
+ // leave this parameter alone.
+ //
+ // Default: 16
+ int block_restart_interval;
+
+ // Compress blocks using the specified compression algorithm. This
+ // parameter can be changed dynamically.
+ //
+ // Default: kSnappyCompression, which gives lightweight but fast
+ // compression.
+ //
+ // Typical speeds of kSnappyCompression on an Intel(R) Core(TM)2 2.4GHz:
+ // ~200-500MB/s compression
+ // ~400-800MB/s decompression
+ // Note that these speeds are significantly faster than most
+ // persistent storage speeds, and therefore it is typically never
+ // worth switching to kNoCompression. Even if the input data is
+ // incompressible, the kSnappyCompression implementation will
+ // efficiently detect that and will switch to uncompressed mode.
+ CompressionType compression;
+
+ // If non-NULL, use the specified filter policy to reduce disk reads.
+ // Many applications will benefit from passing the result of
+ // NewBloomFilterPolicy() here.
+ //
+ // Default: NULL
+ const FilterPolicy* filter_policy;
+
+ // Create a LevelDBOptions object with default values for all fields.
+ LevelDBOptions();
+};
+
+// Converts a LevelDBOptions object into a RocksDB Options object.
+Options ConvertOptions(const LevelDBOptions& leveldb_options);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h
new file mode 100644
index 00000000..f617da02
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h
@@ -0,0 +1,43 @@
+// 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).
+
+#pragma once
+#ifdef LUA
+
+// lua headers
+extern "C" {
+#include <lauxlib.h>
+#include <lua.h>
+#include <lualib.h>
+}
+
+namespace ROCKSDB_NAMESPACE {
+namespace lua {
+// A class that used to define custom C Library that is callable
+// from Lua script
+class RocksLuaCustomLibrary {
+ public:
+ virtual ~RocksLuaCustomLibrary() {}
+ // The name of the C library. This name will also be used as the table
+ // (namespace) in Lua that contains the C library.
+ virtual const char* Name() const = 0;
+
+ // Returns a "static const struct luaL_Reg[]", which includes a list of
+ // C functions. Note that the last entry of this static array must be
+ // {nullptr, nullptr} as required by Lua.
+ //
+ // More details about how to implement Lua C libraries can be found
+ // in the official Lua document http://www.lua.org/pil/26.2.html
+ virtual const struct luaL_Reg* Lib() const = 0;
+
+ // A function that will be called right after the library has been created
+ // and pushed on the top of the lua_State. This custom setup function
+ // allows developers to put additional table or constant values inside
+ // the same table / namespace.
+ virtual void CustomSetup(lua_State* /*L*/) const {}
+};
+} // namespace lua
+} // namespace ROCKSDB_NAMESPACE
+#endif // LUA
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_util.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_util.h
new file mode 100644
index 00000000..3427b65e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/lua/rocks_lua_util.h
@@ -0,0 +1,55 @@
+// 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).
+
+#pragma once
+// lua headers
+extern "C" {
+#include <lauxlib.h>
+#include <lua.h>
+#include <lualib.h>
+}
+
+#ifdef LUA
+#include <string>
+#include <vector>
+
+#include "rocksdb/utilities/lua/rocks_lua_custom_library.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace lua {
+class LuaStateWrapper {
+ public:
+ explicit LuaStateWrapper(const std::string& lua_script) {
+ lua_state_ = luaL_newstate();
+ Init(lua_script, {});
+ }
+ LuaStateWrapper(
+ const std::string& lua_script,
+ const std::vector<std::shared_ptr<RocksLuaCustomLibrary>>& libraries) {
+ lua_state_ = luaL_newstate();
+ Init(lua_script, libraries);
+ }
+ lua_State* GetLuaState() const { return lua_state_; }
+ ~LuaStateWrapper() { lua_close(lua_state_); }
+
+ private:
+ void Init(
+ const std::string& lua_script,
+ const std::vector<std::shared_ptr<RocksLuaCustomLibrary>>& libraries) {
+ if (lua_state_) {
+ luaL_openlibs(lua_state_);
+ for (const auto& library : libraries) {
+ luaL_openlib(lua_state_, library->Name(), library->Lib(), 0);
+ library->CustomSetup(lua_state_);
+ }
+ luaL_dostring(lua_state_, lua_script.c_str());
+ }
+ }
+
+ lua_State* lua_state_;
+};
+} // namespace lua
+} // namespace ROCKSDB_NAMESPACE
+#endif // LUA
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/memory_util.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/memory_util.h
new file mode 100644
index 00000000..4f1606b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/memory_util.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Returns the current memory usage of the specified DB instances.
+class MemoryUtil {
+ public:
+ enum UsageType : int {
+ // Memory usage of all the mem-tables.
+ kMemTableTotal = 0,
+ // Memory usage of those un-flushed mem-tables.
+ kMemTableUnFlushed = 1,
+ // Memory usage of all the table readers.
+ kTableReadersTotal = 2,
+ // Memory usage by Cache.
+ kCacheTotal = 3,
+ kNumUsageTypes = 4
+ };
+
+ // Returns the approximate memory usage of different types in the input
+ // list of DBs and Cache set. For instance, in the output map
+ // usage_by_type, usage_by_type[kMemTableTotal] will store 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.
+ static Status GetApproximateMemoryUsageByType(
+ const std::vector<DB*>& dbs,
+ const std::unordered_set<const Cache*> cache_set,
+ std::map<MemoryUtil::UsageType, uint64_t>* usage_by_type);
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/object_registry.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/object_registry.h
new file mode 100644
index 00000000..74a49d40
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/object_registry.h
@@ -0,0 +1,205 @@
+// 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).
+
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+#include <memory>
+#include <regex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+class Logger;
+// Returns a new T when called with a string. Populates the std::unique_ptr
+// argument if granting ownership to caller.
+template <typename T>
+using FactoryFunc =
+ std::function<T*(const std::string&, std::unique_ptr<T>*, std::string*)>;
+
+class ObjectLibrary {
+ public:
+ // Base class for an Entry in the Registry.
+ class Entry {
+ public:
+ virtual ~Entry() {}
+ Entry(const std::string& name) : name_(std::move(name)) {}
+
+ // Checks to see if the target matches this entry
+ virtual bool matches(const std::string& target) const {
+ return name_ == target;
+ }
+ const std::string& Name() const { return name_; }
+
+ private:
+ const std::string name_; // The name of the Entry
+ }; // End class Entry
+
+ // An Entry containing a FactoryFunc for creating new Objects
+ template <typename T>
+ class FactoryEntry : public Entry {
+ public:
+ FactoryEntry(const std::string& name, FactoryFunc<T> f)
+ : Entry(name), pattern_(std::move(name)), factory_(std::move(f)) {}
+ ~FactoryEntry() override {}
+ bool matches(const std::string& target) const override {
+ return std::regex_match(target, pattern_);
+ }
+ // Creates a new T object.
+ T* NewFactoryObject(const std::string& target, std::unique_ptr<T>* guard,
+ std::string* msg) const {
+ return factory_(target, guard, msg);
+ }
+
+ private:
+ std::regex pattern_; // The pattern for this entry
+ FactoryFunc<T> factory_;
+ }; // End class FactoryEntry
+ public:
+ // Finds the entry matching the input name and type
+ const Entry* FindEntry(const std::string& type,
+ const std::string& name) const;
+ void Dump(Logger* logger) const;
+
+ // Registers the factory with the library for the pattern.
+ // If the pattern matches, the factory may be used to create a new object.
+ template <typename T>
+ const FactoryFunc<T>& Register(const std::string& pattern,
+ const FactoryFunc<T>& factory) {
+ std::unique_ptr<Entry> entry(new FactoryEntry<T>(pattern, factory));
+ AddEntry(T::Type(), entry);
+ return factory;
+ }
+ // Returns the default ObjectLibrary
+ static std::shared_ptr<ObjectLibrary>& Default();
+
+ private:
+ // Adds the input entry to the list for the given type
+ void AddEntry(const std::string& type, std::unique_ptr<Entry>& entry);
+
+ // ** FactoryFunctions for this loader, organized by type
+ std::unordered_map<std::string, std::vector<std::unique_ptr<Entry>>> entries_;
+};
+
+// The ObjectRegistry is used to register objects that can be created by a
+// name/pattern at run-time where the specific implementation of the object may
+// not be known in advance.
+class ObjectRegistry {
+ public:
+ static std::shared_ptr<ObjectRegistry> NewInstance();
+
+ ObjectRegistry();
+
+ void AddLibrary(const std::shared_ptr<ObjectLibrary>& library) {
+ libraries_.emplace_back(library);
+ }
+
+ // Creates a new T using the factory function that was registered with a
+ // pattern that matches the provided "target" string according to
+ // std::regex_match.
+ //
+ // If no registered functions match, returns nullptr. If multiple functions
+ // match, the factory function used is unspecified.
+ //
+ // Populates res_guard with result pointer if caller is granted ownership.
+ template <typename T>
+ T* NewObject(const std::string& target, std::unique_ptr<T>* guard,
+ std::string* errmsg) {
+ guard->reset();
+ const auto* basic = FindEntry(T::Type(), target);
+ if (basic != nullptr) {
+ const auto* factory =
+ static_cast<const ObjectLibrary::FactoryEntry<T>*>(basic);
+ return factory->NewFactoryObject(target, guard, errmsg);
+ } else {
+ *errmsg = std::string("Could not load ") + T::Type();
+ return nullptr;
+ }
+ }
+
+ // Creates a new unique T using the input factory functions.
+ // Returns OK if a new unique T was successfully created
+ // Returns NotFound if the type/target could not be created
+ // Returns InvalidArgument if the factory return an unguarded object
+ // (meaning it cannot be managed by a unique ptr)
+ template <typename T>
+ Status NewUniqueObject(const std::string& target,
+ std::unique_ptr<T>* result) {
+ std::string errmsg;
+ T* ptr = NewObject(target, result, &errmsg);
+ if (ptr == nullptr) {
+ return Status::NotFound(errmsg, target);
+ } else if (*result) {
+ return Status::OK();
+ } else {
+ return Status::InvalidArgument(std::string("Cannot make a unique ") +
+ T::Type() + " from unguarded one ",
+ target);
+ }
+ }
+
+ // Creates a new shared T using the input factory functions.
+ // Returns OK if a new shared T was successfully created
+ // Returns NotFound if the type/target could not be created
+ // Returns InvalidArgument if the factory return an unguarded object
+ // (meaning it cannot be managed by a shared ptr)
+ template <typename T>
+ Status NewSharedObject(const std::string& target,
+ std::shared_ptr<T>* result) {
+ std::string errmsg;
+ std::unique_ptr<T> guard;
+ T* ptr = NewObject(target, &guard, &errmsg);
+ if (ptr == nullptr) {
+ return Status::NotFound(errmsg, target);
+ } else if (guard) {
+ result->reset(guard.release());
+ return Status::OK();
+ } else {
+ return Status::InvalidArgument(std::string("Cannot make a shared ") +
+ T::Type() + " from unguarded one ",
+ target);
+ }
+ }
+
+ // Creates a new static T using the input factory functions.
+ // Returns OK if a new static T was successfully created
+ // Returns NotFound if the type/target could not be created
+ // Returns InvalidArgument if the factory return a guarded object
+ // (meaning it is managed by a unique ptr)
+ template <typename T>
+ Status NewStaticObject(const std::string& target, T** result) {
+ std::string errmsg;
+ std::unique_ptr<T> guard;
+ T* ptr = NewObject(target, &guard, &errmsg);
+ if (ptr == nullptr) {
+ return Status::NotFound(errmsg, target);
+ } else if (guard.get()) {
+ return Status::InvalidArgument(std::string("Cannot make a static ") +
+ T::Type() + " from a guarded one ",
+ target);
+ } else {
+ *result = ptr;
+ return Status::OK();
+ }
+ }
+
+ // Dump the contents of the registry to the logger
+ void Dump(Logger* logger) const;
+
+ private:
+ const ObjectLibrary::Entry* FindEntry(const std::string& type,
+ const std::string& name) const;
+
+ // The set of libraries to search for factories for this registry.
+ // The libraries are searched in reverse order (back to front) when
+ // searching for entries.
+ std::vector<std::shared_ptr<ObjectLibrary>> libraries_;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h
new file mode 100644
index 00000000..5356df71
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/stackable_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Transaction;
+
+// Database with Transaction support.
+//
+// See optimistic_transaction.h and examples/transaction_example.cc
+
+// Options to use when starting an Optimistic Transaction
+struct OptimisticTransactionOptions {
+ // Setting set_snapshot=true is the same as calling SetSnapshot().
+ bool set_snapshot = false;
+
+ // Should be set if the DB has a non-default comparator.
+ // See comment in WriteBatchWithIndex constructor.
+ const Comparator* cmp = BytewiseComparator();
+};
+
+enum class OccValidationPolicy {
+ // Validate serially at commit stage, AFTER entering the write-group.
+ // Isolation validation is processed single-threaded(since in the
+ // write-group).
+ // May suffer from high mutex contention, as per this link:
+ // https://github.com/facebook/rocksdb/issues/4402
+ kValidateSerial = 0,
+ // Validate parallelly before commit stage, BEFORE entering the write-group to
+ // reduce mutex contention. Each txn acquires locks for its write-set
+ // records in some well-defined order.
+ kValidateParallel = 1
+};
+
+struct OptimisticTransactionDBOptions {
+ OccValidationPolicy validate_policy = OccValidationPolicy::kValidateParallel;
+
+ // works only if validate_policy == OccValidationPolicy::kValidateParallel
+ uint32_t occ_lock_buckets = (1 << 20);
+};
+
+class OptimisticTransactionDB : public StackableDB {
+ public:
+ // Open an OptimisticTransactionDB similar to DB::Open().
+ static Status Open(const Options& options, const std::string& dbname,
+ OptimisticTransactionDB** dbptr);
+
+ static Status Open(const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ OptimisticTransactionDB** dbptr);
+
+ static Status Open(const DBOptions& db_options,
+ const OptimisticTransactionDBOptions& occ_options,
+ const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ OptimisticTransactionDB** dbptr);
+
+ virtual ~OptimisticTransactionDB() {}
+
+ // Starts a new Transaction.
+ //
+ // Caller is responsible for deleting the returned transaction when no
+ // longer needed.
+ //
+ // If old_txn is not null, BeginTransaction will reuse this Transaction
+ // handle instead of allocating a new one. This is an optimization to avoid
+ // extra allocations when repeatedly creating transactions.
+ virtual Transaction* BeginTransaction(
+ const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options =
+ OptimisticTransactionOptions(),
+ Transaction* old_txn = nullptr) = 0;
+
+ OptimisticTransactionDB(const OptimisticTransactionDB&) = delete;
+ void operator=(const OptimisticTransactionDB&) = delete;
+
+ protected:
+ // To Create an OptimisticTransactionDB, call Open()
+ explicit OptimisticTransactionDB(DB* db) : StackableDB(db) {}
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/option_change_migration.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/option_change_migration.h
new file mode 100644
index 00000000..cb1d0d11
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/option_change_migration.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Try to migrate DB created with old_opts to be use new_opts.
+// Multiple column families is not supported.
+// It is best-effort. No guarantee to succeed.
+// A full compaction may be executed.
+Status OptionChangeMigration(std::string dbname, const Options& old_opts,
+ const Options& new_opts);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/options_util.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/options_util.h
new file mode 100644
index 00000000..1a29464a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/options_util.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 contains utility functions for RocksDB Options.
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Constructs 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
+//
+// User can also choose to load customized comparator, env, and/or
+// merge_operator through object registry:
+// * comparator needs to be registered through Registrar<const Comparator>
+// * env needs to be registered through Registrar<Env>
+// * merge operator needs to be registered through
+// Registrar<std::shared_ptr<MergeOperator>>.
+//
+// For table_factory, this function further supports deserializing
+// BlockBasedTableFactory and its BlockBasedTableOptions except the
+// pointer options of BlockBasedTableOptions (flush_block_policy_factory,
+// block_cache, and block_cache_compressed), 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.
+//
+// ignore_unknown_options can be set to true if you want to ignore options
+// that are from a newer version of the db, esentially for forward
+// compatibility.
+//
+// examples/options_file_example.cc demonstrates how to use this function
+// to open a RocksDB instance.
+//
+// @return the function returns an OK status when it went successfully. If
+// the specified "dbpath" does not contain any option file, then a
+// Status::NotFound will be returned. A return value other than
+// Status::OK or Status::NotFound indicates there're some error related
+// to the options file itself.
+//
+// @see LoadOptionsFromFile
+Status LoadLatestOptions(const std::string& dbpath, Env* env,
+ DBOptions* db_options,
+ std::vector<ColumnFamilyDescriptor>* cf_descs,
+ bool ignore_unknown_options = false,
+ std::shared_ptr<Cache>* cache = {});
+
+// Similar to LoadLatestOptions, this function constructs the DBOptions
+// and ColumnFamilyDescriptors based on the specified RocksDB Options file.
+//
+// @see LoadLatestOptions
+Status LoadOptionsFromFile(const std::string& options_file_name, Env* env,
+ DBOptions* db_options,
+ std::vector<ColumnFamilyDescriptor>* cf_descs,
+ bool ignore_unknown_options = false,
+ std::shared_ptr<Cache>* cache = {});
+
+// Returns the latest options file name under the specified db path.
+Status GetLatestOptionsFileName(const std::string& dbpath, Env* env,
+ std::string* options_file_name);
+
+// Returns Status::OK if the input DBOptions and ColumnFamilyDescriptors
+// are compatible with the latest options stored in the specified DB path.
+//
+// If the return status is non-ok, it means the specified RocksDB instance
+// might not be correctly opened with the input set of options. Currently,
+// changing one of the following options will fail the compatibility check:
+//
+// * comparator
+// * prefix_extractor
+// * table_factory
+// * merge_operator
+Status CheckOptionsCompatibility(
+ const std::string& dbpath, Env* env, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& cf_descs,
+ bool ignore_unknown_options = false);
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/sim_cache.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/sim_cache.h
new file mode 100644
index 00000000..ba6f1d74
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/sim_cache.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <memory>
+#include <string>
+#include "rocksdb/cache.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SimCache;
+
+// For instrumentation purpose, use NewSimCache instead of NewLRUCache API
+// NewSimCache is a wrapper function returning a SimCache instance that can
+// have additional interface provided in Simcache class besides Cache interface
+// to predict block cache hit rate without actually allocating the memory. It
+// can help users tune their current block cache size, and determine how
+// efficient they are using the memory.
+//
+// Since GetSimCapacity() returns the capacity for simulutation, it differs from
+// actual memory usage, which can be estimated as:
+// sim_capacity * entry_size / (entry_size + block_size),
+// where 76 <= entry_size <= 104,
+// BlockBasedTableOptions.block_size = 4096 by default but is configurable,
+// Therefore, generally the actual memory overhead of SimCache is Less than
+// sim_capacity * 2%
+extern std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> cache,
+ size_t sim_capacity,
+ int num_shard_bits);
+
+extern std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> sim_cache,
+ std::shared_ptr<Cache> cache,
+ int num_shard_bits);
+
+class SimCache : public Cache {
+ public:
+ SimCache() {}
+
+ ~SimCache() override {}
+
+ const char* Name() const override { return "SimCache"; }
+
+ // returns the maximum configured capacity of the simcache for simulation
+ virtual size_t GetSimCapacity() const = 0;
+
+ // simcache doesn't provide internal handler reference to user, so always
+ // PinnedUsage = 0 and the behavior will be not exactly consistent the
+ // with real cache.
+ // returns the memory size for the entries residing in the simcache.
+ virtual size_t GetSimUsage() const = 0;
+
+ // sets the maximum configured capacity of the simcache. When the new
+ // capacity is less than the old capacity and the existing usage is
+ // greater than new capacity, the implementation will purge old entries
+ // to fit new capapicty.
+ virtual void SetSimCapacity(size_t capacity) = 0;
+
+ // returns the lookup times of simcache
+ virtual uint64_t get_miss_counter() const = 0;
+ // returns the hit times of simcache
+ virtual uint64_t get_hit_counter() const = 0;
+ // reset the lookup and hit counters
+ virtual void reset_counter() = 0;
+ // String representation of the statistics of the simcache
+ virtual std::string ToString() const = 0;
+
+ // Start storing logs of the cache activity (Add/Lookup) into
+ // a file located at activity_log_file, max_logging_size option can be used to
+ // stop logging to the file automatically after reaching a specific size in
+ // bytes, a values of 0 disable this feature
+ virtual Status StartActivityLogging(const std::string& activity_log_file,
+ Env* env,
+ uint64_t max_logging_size = 0) = 0;
+
+ // Stop cache activity logging if any
+ virtual void StopActivityLogging() = 0;
+
+ // Status of cache logging happening in background
+ virtual Status GetActivityLoggingStatus() = 0;
+
+ private:
+ SimCache(const SimCache&);
+ SimCache& operator=(const SimCache&);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h
new file mode 100644
index 00000000..9888fa22
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/stackable_db.h
@@ -0,0 +1,465 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 <map>
+#include <memory>
+#include <string>
+#include "rocksdb/db.h"
+
+#ifdef _WIN32
+// Windows API macro interference
+#undef DeleteFile
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+// This class contains APIs to stack rocksdb wrappers.Eg. Stack TTL over base d
+class StackableDB : public DB {
+ public:
+ // StackableDB take sole ownership of the underlying db.
+ explicit StackableDB(DB* db) : db_(db) {}
+
+ // StackableDB take shared ownership of the underlying db.
+ explicit StackableDB(std::shared_ptr<DB> db)
+ : db_(db.get()), shared_db_ptr_(db) {}
+
+ ~StackableDB() {
+ if (shared_db_ptr_ == nullptr) {
+ delete db_;
+ } else {
+ assert(shared_db_ptr_.get() == db_);
+ }
+ db_ = nullptr;
+ }
+
+ virtual Status Close() override { return db_->Close(); }
+
+ virtual DB* GetBaseDB() { return db_; }
+
+ virtual DB* GetRootDB() override { return db_->GetRootDB(); }
+
+ virtual Status CreateColumnFamily(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle) override {
+ return db_->CreateColumnFamily(options, column_family_name, handle);
+ }
+
+ virtual Status CreateColumnFamilies(
+ const ColumnFamilyOptions& options,
+ const std::vector<std::string>& column_family_names,
+ std::vector<ColumnFamilyHandle*>* handles) override {
+ return db_->CreateColumnFamilies(options, column_family_names, handles);
+ }
+
+ virtual Status CreateColumnFamilies(
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles) override {
+ return db_->CreateColumnFamilies(column_families, handles);
+ }
+
+ virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) override {
+ return db_->DropColumnFamily(column_family);
+ }
+
+ virtual Status DropColumnFamilies(
+ const std::vector<ColumnFamilyHandle*>& column_families) override {
+ return db_->DropColumnFamilies(column_families);
+ }
+
+ virtual Status DestroyColumnFamilyHandle(
+ ColumnFamilyHandle* column_family) override {
+ return db_->DestroyColumnFamilyHandle(column_family);
+ }
+
+ using DB::Put;
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& val) override {
+ return db_->Put(options, column_family, key, val);
+ }
+
+ using DB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override {
+ return db_->Get(options, column_family, key, value);
+ }
+
+ using DB::GetMergeOperands;
+ virtual Status GetMergeOperands(
+ const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* slice,
+ GetMergeOperandsOptions* get_merge_operands_options,
+ int* number_of_operands) override {
+ return db_->GetMergeOperands(options, column_family, key, slice,
+ get_merge_operands_options,
+ number_of_operands);
+ }
+
+ using DB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override {
+ return db_->MultiGet(options, column_family, keys, values);
+ }
+
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input = false) override {
+ return db_->MultiGet(options, column_family, num_keys, keys,
+ values, statuses, sorted_input);
+ }
+
+ using DB::IngestExternalFile;
+ virtual Status IngestExternalFile(
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& external_files,
+ const IngestExternalFileOptions& options) override {
+ return db_->IngestExternalFile(column_family, external_files, options);
+ }
+
+ using DB::IngestExternalFiles;
+ virtual Status IngestExternalFiles(
+ const std::vector<IngestExternalFileArg>& args) override {
+ return db_->IngestExternalFiles(args);
+ }
+
+ 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 db_->CreateColumnFamilyWithImport(options, column_family_name,
+ import_options, metadata, handle);
+ }
+
+ virtual Status VerifyChecksum() override { return db_->VerifyChecksum(); }
+
+ virtual Status VerifyChecksum(const ReadOptions& options) override {
+ return db_->VerifyChecksum(options);
+ }
+
+ using DB::KeyMayExist;
+ virtual bool KeyMayExist(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value,
+ bool* value_found = nullptr) override {
+ return db_->KeyMayExist(options, column_family, key, value, value_found);
+ }
+
+ using DB::Delete;
+ virtual Status Delete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override {
+ return db_->Delete(wopts, column_family, key);
+ }
+
+ using DB::SingleDelete;
+ virtual Status SingleDelete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override {
+ return db_->SingleDelete(wopts, column_family, key);
+ }
+
+ using DB::Merge;
+ virtual Status Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override {
+ return db_->Merge(options, column_family, key, value);
+ }
+
+ virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override {
+ return db_->Write(opts, updates);
+ }
+
+ using DB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& opts,
+ ColumnFamilyHandle* column_family) override {
+ return db_->NewIterator(opts, column_family);
+ }
+
+ virtual Status NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) override {
+ return db_->NewIterators(options, column_families, iterators);
+ }
+
+ virtual const Snapshot* GetSnapshot() override { return db_->GetSnapshot(); }
+
+ virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
+ return db_->ReleaseSnapshot(snapshot);
+ }
+
+ using DB::GetMapProperty;
+ using DB::GetProperty;
+ virtual bool GetProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, std::string* value) override {
+ return db_->GetProperty(column_family, property, value);
+ }
+ virtual bool GetMapProperty(
+ ColumnFamilyHandle* column_family, const Slice& property,
+ std::map<std::string, std::string>* value) override {
+ return db_->GetMapProperty(column_family, property, value);
+ }
+
+ using DB::GetIntProperty;
+ virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
+ const Slice& property, uint64_t* value) override {
+ return db_->GetIntProperty(column_family, property, value);
+ }
+
+ using DB::GetAggregatedIntProperty;
+ virtual bool GetAggregatedIntProperty(const Slice& property,
+ uint64_t* value) override {
+ return db_->GetAggregatedIntProperty(property, value);
+ }
+
+ using DB::GetApproximateSizes;
+ virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Range* r, int n,
+ uint64_t* sizes) override {
+ return db_->GetApproximateSizes(options, column_family, r, n, sizes);
+ }
+
+ using DB::GetApproximateMemTableStats;
+ virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
+ const Range& range,
+ uint64_t* const count,
+ uint64_t* const size) override {
+ return db_->GetApproximateMemTableStats(column_family, range, count, size);
+ }
+
+ using DB::CompactRange;
+ virtual Status CompactRange(const CompactRangeOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice* begin, const Slice* end) override {
+ return db_->CompactRange(options, column_family, begin, end);
+ }
+
+ using DB::CompactFiles;
+ virtual Status CompactFiles(
+ const CompactionOptions& compact_options,
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) override {
+ return db_->CompactFiles(compact_options, column_family, input_file_names,
+ output_level, output_path_id, output_file_names,
+ compaction_job_info);
+ }
+
+ virtual Status PauseBackgroundWork() override {
+ return db_->PauseBackgroundWork();
+ }
+ virtual Status ContinueBackgroundWork() override {
+ return db_->ContinueBackgroundWork();
+ }
+
+ virtual Status EnableAutoCompaction(
+ const std::vector<ColumnFamilyHandle*>& column_family_handles) override {
+ return db_->EnableAutoCompaction(column_family_handles);
+ }
+
+ virtual void EnableManualCompaction() override {
+ return db_->EnableManualCompaction();
+ }
+ virtual void DisableManualCompaction() override {
+ return db_->DisableManualCompaction();
+ }
+
+ using DB::NumberLevels;
+ virtual int NumberLevels(ColumnFamilyHandle* column_family) override {
+ return db_->NumberLevels(column_family);
+ }
+
+ using DB::MaxMemCompactionLevel;
+ virtual int MaxMemCompactionLevel(
+ ColumnFamilyHandle* column_family) override {
+ return db_->MaxMemCompactionLevel(column_family);
+ }
+
+ using DB::Level0StopWriteTrigger;
+ virtual int Level0StopWriteTrigger(
+ ColumnFamilyHandle* column_family) override {
+ return db_->Level0StopWriteTrigger(column_family);
+ }
+
+ virtual const std::string& GetName() const override { return db_->GetName(); }
+
+ virtual Env* GetEnv() const override { return db_->GetEnv(); }
+
+ virtual FileSystem* GetFileSystem() const override {
+ return db_->GetFileSystem();
+ }
+
+ using DB::GetOptions;
+ virtual Options GetOptions(ColumnFamilyHandle* column_family) const override {
+ return db_->GetOptions(column_family);
+ }
+
+ using DB::GetDBOptions;
+ virtual DBOptions GetDBOptions() const override {
+ return db_->GetDBOptions();
+ }
+
+ using DB::Flush;
+ virtual Status Flush(const FlushOptions& fopts,
+ ColumnFamilyHandle* column_family) override {
+ return db_->Flush(fopts, column_family);
+ }
+ virtual Status Flush(
+ const FlushOptions& fopts,
+ const std::vector<ColumnFamilyHandle*>& column_families) override {
+ return db_->Flush(fopts, column_families);
+ }
+
+ virtual Status SyncWAL() override { return db_->SyncWAL(); }
+
+ virtual Status FlushWAL(bool sync) override { return db_->FlushWAL(sync); }
+
+ virtual Status LockWAL() override { return db_->LockWAL(); }
+
+ virtual Status UnlockWAL() override { return db_->UnlockWAL(); }
+
+#ifndef ROCKSDB_LITE
+
+ virtual Status DisableFileDeletions() override {
+ return db_->DisableFileDeletions();
+ }
+
+ virtual Status EnableFileDeletions(bool force) override {
+ return db_->EnableFileDeletions(force);
+ }
+
+ virtual void GetLiveFilesMetaData(
+ std::vector<LiveFileMetaData>* metadata) override {
+ db_->GetLiveFilesMetaData(metadata);
+ }
+
+ virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
+ ColumnFamilyMetaData* cf_meta) override {
+ db_->GetColumnFamilyMetaData(column_family, cf_meta);
+ }
+
+ using DB::StartBlockCacheTrace;
+ Status StartBlockCacheTrace(
+ const TraceOptions& options,
+ std::unique_ptr<TraceWriter>&& trace_writer) override {
+ return db_->StartBlockCacheTrace(options, std::move(trace_writer));
+ }
+
+ using DB::EndBlockCacheTrace;
+ Status EndBlockCacheTrace() override { return db_->EndBlockCacheTrace(); }
+
+#endif // ROCKSDB_LITE
+
+ virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,
+ bool flush_memtable = true) override {
+ return db_->GetLiveFiles(vec, mfs, flush_memtable);
+ }
+
+ virtual SequenceNumber GetLatestSequenceNumber() const override {
+ return db_->GetLatestSequenceNumber();
+ }
+
+ virtual bool SetPreserveDeletesSequenceNumber(
+ SequenceNumber seqnum) override {
+ return db_->SetPreserveDeletesSequenceNumber(seqnum);
+ }
+
+ virtual Status GetSortedWalFiles(VectorLogPtr& files) override {
+ return db_->GetSortedWalFiles(files);
+ }
+
+ virtual Status GetCurrentWalFile(
+ std::unique_ptr<LogFile>* current_log_file) override {
+ return db_->GetCurrentWalFile(current_log_file);
+ }
+
+ virtual Status GetCreationTimeOfOldestFile(
+ uint64_t* creation_time) override {
+ return db_->GetCreationTimeOfOldestFile(creation_time);
+ }
+
+ virtual Status DeleteFile(std::string name) override {
+ return db_->DeleteFile(name);
+ }
+
+ virtual Status GetDbIdentity(std::string& identity) const override {
+ return db_->GetDbIdentity(identity);
+ }
+
+ using DB::SetOptions;
+ virtual Status SetOptions(ColumnFamilyHandle* column_family_handle,
+ const std::unordered_map<std::string, std::string>&
+ new_options) override {
+ return db_->SetOptions(column_family_handle, new_options);
+ }
+
+ virtual Status SetDBOptions(
+ const std::unordered_map<std::string, std::string>& new_options)
+ override {
+ return db_->SetDBOptions(new_options);
+ }
+
+ using DB::ResetStats;
+ virtual Status ResetStats() override { return db_->ResetStats(); }
+
+ using DB::GetPropertiesOfAllTables;
+ virtual Status GetPropertiesOfAllTables(
+ ColumnFamilyHandle* column_family,
+ TablePropertiesCollection* props) override {
+ return db_->GetPropertiesOfAllTables(column_family, props);
+ }
+
+ using DB::GetPropertiesOfTablesInRange;
+ virtual Status GetPropertiesOfTablesInRange(
+ ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
+ TablePropertiesCollection* props) override {
+ return db_->GetPropertiesOfTablesInRange(column_family, range, n, props);
+ }
+
+ virtual Status GetUpdatesSince(
+ SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
+ const TransactionLogIterator::ReadOptions& read_options) override {
+ return db_->GetUpdatesSince(seq_number, iter, read_options);
+ }
+
+ virtual Status SuggestCompactRange(ColumnFamilyHandle* column_family,
+ const Slice* begin,
+ const Slice* end) override {
+ return db_->SuggestCompactRange(column_family, begin, end);
+ }
+
+ virtual Status PromoteL0(ColumnFamilyHandle* column_family,
+ int target_level) override {
+ return db_->PromoteL0(column_family, target_level);
+ }
+
+ virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
+ return db_->DefaultColumnFamily();
+ }
+
+#ifndef ROCKSDB_LITE
+ Status TryCatchUpWithPrimary() override {
+ return db_->TryCatchUpWithPrimary();
+ }
+#endif // ROCKSDB_LITE
+
+ protected:
+ DB* db_;
+ std::shared_ptr<DB> shared_db_ptr_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/table_properties_collectors.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/table_properties_collectors.h
new file mode 100644
index 00000000..b7ee88bc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/table_properties_collectors.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include <atomic>
+#include <memory>
+
+#include "rocksdb/table_properties.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A factory of a table property collector that marks a SST
+// file as need-compaction when it observe at least "D" deletion
+// entries in any "N" consecutive entires.
+class CompactOnDeletionCollectorFactory
+ : public TablePropertiesCollectorFactory {
+ public:
+ virtual ~CompactOnDeletionCollectorFactory() {}
+
+ virtual TablePropertiesCollector* CreateTablePropertiesCollector(
+ TablePropertiesCollectorFactory::Context context) override;
+
+ // Change the value of sliding_window_size "N"
+ // Setting it to 0 disables the delete triggered compaction
+ void SetWindowSize(size_t sliding_window_size) {
+ sliding_window_size_.store(sliding_window_size);
+ }
+
+ // Change the value of deletion_trigger "D"
+ void SetDeletionTrigger(size_t deletion_trigger) {
+ deletion_trigger_.store(deletion_trigger);
+ }
+
+ virtual const char* Name() const override {
+ return "CompactOnDeletionCollector";
+ }
+
+ private:
+ friend std::shared_ptr<CompactOnDeletionCollectorFactory>
+ NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
+ size_t deletion_trigger);
+ // A factory of a table property collector that marks a SST
+ // file as need-compaction when it observe at least "D" deletion
+ // entries in any "N" consecutive entires.
+ //
+ // @param sliding_window_size "N"
+ // @param deletion_trigger "D"
+ CompactOnDeletionCollectorFactory(size_t sliding_window_size,
+ size_t deletion_trigger)
+ : sliding_window_size_(sliding_window_size),
+ deletion_trigger_(deletion_trigger) {}
+
+ std::atomic<size_t> sliding_window_size_;
+ std::atomic<size_t> deletion_trigger_;
+};
+
+// Creates a factory of a table property collector that marks a SST
+// file as need-compaction when it observe at least "D" deletion
+// entries in any "N" consecutive entires.
+//
+// @param sliding_window_size "N". Note that this number will be
+// round up to the smallest multiple of 128 that is no less
+// than the specified size.
+// @param deletion_trigger "D". Note that even when "N" is changed,
+// the specified number for "D" will not be changed.
+extern std::shared_ptr<CompactOnDeletionCollectorFactory>
+NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
+ size_t deletion_trigger);
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction.h
new file mode 100644
index 00000000..d6c6722c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction.h
@@ -0,0 +1,540 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Iterator;
+class TransactionDB;
+class WriteBatchWithIndex;
+
+using TransactionName = std::string;
+
+using TransactionID = uint64_t;
+
+// Provides notification to the caller of SetSnapshotOnNextOperation when
+// the actual snapshot gets created
+class TransactionNotifier {
+ public:
+ virtual ~TransactionNotifier() {}
+
+ // Implement this method to receive notification when a snapshot is
+ // requested via SetSnapshotOnNextOperation.
+ virtual void SnapshotCreated(const Snapshot* newSnapshot) = 0;
+};
+
+// Provides BEGIN/COMMIT/ROLLBACK transactions.
+//
+// To use transactions, you must first create either an OptimisticTransactionDB
+// or a TransactionDB. See examples/[optimistic_]transaction_example.cc for
+// more information.
+//
+// To create a transaction, use [Optimistic]TransactionDB::BeginTransaction().
+//
+// It is up to the caller to synchronize access to this object.
+//
+// See examples/transaction_example.cc for some simple examples.
+//
+// TODO(agiardullo): Not yet implemented
+// -PerfContext statistics
+// -Support for using Transactions with DBWithTTL
+class Transaction {
+ public:
+ // No copying allowed
+ Transaction(const Transaction&) = delete;
+ void operator=(const Transaction&) = delete;
+
+ virtual ~Transaction() {}
+
+ // If a transaction has a snapshot set, the transaction will ensure that
+ // any keys successfully written(or fetched via 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
+ // GetForUpdate()).
+ //
+ // Using SetSnapshot() will provide stricter isolation guarantees at the
+ // expense of potentially more transaction failures due to conflicts with
+ // other writes.
+ //
+ // Calling SetSnapshot() has no effect on keys written before this function
+ // has been called.
+ //
+ // SetSnapshot() may be called multiple times if you would like to change
+ // the snapshot used for different operations in this transaction.
+ //
+ // Calling SetSnapshot will not affect the version of Data returned by Get()
+ // methods. See Transaction::Get() for more details.
+ virtual void SetSnapshot() = 0;
+
+ // Similar to 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
+ // SetSnapshot() for you right before performing the next write/GetForUpdate.
+ //
+ // Calling SetSnapshotOnNextOperation() will not affect what snapshot is
+ // returned by 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 SetSnapshot() is called and the first
+ // write/GetForUpdate operation. Eg, this prevents the following
+ // race-condition:
+ //
+ // txn1->SetSnapshot();
+ // txn2->Put("A", ...);
+ // txn2->Commit();
+ // txn1->GetForUpdate(opts, "A", ...); // FAIL!
+ virtual void SetSnapshotOnNextOperation(
+ std::shared_ptr<TransactionNotifier> notifier = nullptr) = 0;
+
+ // Returns the Snapshot created by the last call to SetSnapshot().
+ //
+ // REQUIRED: The returned Snapshot is only valid up until the next time
+ // SetSnapshot()/SetSnapshotOnNextSavePoint() is called, ClearSnapshot()
+ // is called, or the Transaction is deleted.
+ virtual const Snapshot* GetSnapshot() const = 0;
+
+ // 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 (SetSnapshotOnNextOperation).
+ //
+ // Calling ClearSnapshot() has no effect on keys written before this function
+ // has been called.
+ //
+ // If a reference to a snapshot was retrieved via GetSnapshot(), it will no
+ // longer be valid and should be discarded after a call to ClearSnapshot().
+ virtual void ClearSnapshot() = 0;
+
+ // Prepare the current transaction for 2PC
+ virtual Status Prepare() = 0;
+
+ // 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 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_size_to_maintain).
+ //
+ // If this transaction was created by a TransactionDB(), Status::Expired()
+ // may be returned if this transaction has lived for longer than
+ // TransactionOptions.expiration.
+ virtual Status Commit() = 0;
+
+ // Discard all batched writes in this transaction.
+ virtual Status Rollback() = 0;
+
+ // Records the state of the transaction for future calls to
+ // RollbackToSavePoint(). May be called multiple times to set multiple save
+ // points.
+ virtual void SetSavePoint() = 0;
+
+ // Undo all operations in this transaction (Put, Merge, Delete, PutLogData)
+ // since the most recent call to SetSavePoint() and removes the most recent
+ // SetSavePoint().
+ // If there is no previous call to SetSavePoint(), returns Status::NotFound()
+ virtual Status RollbackToSavePoint() = 0;
+
+ // Pop the most recent save point.
+ // If there is no previous call to SetSavePoint(), Status::NotFound()
+ // will be returned.
+ // Otherwise returns Status::OK().
+ virtual Status PopSavePoint() = 0;
+
+ // This function is similar to DB::Get() 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 read_options.snapshot is not set, the current version of the key will
+ // be read. Calling SetSnapshot() does not affect the version of the data
+ // returned.
+ //
+ // Note that setting read_options.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).
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value) = 0;
+
+ // An overload of the above method that receives a PinnableSlice
+ // For backward compatibility a default implementation is provided
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* pinnable_val) {
+ assert(pinnable_val != nullptr);
+ auto s = Get(options, column_family, key, pinnable_val->GetSelf());
+ pinnable_val->PinSelf();
+ return s;
+ }
+
+ virtual Status Get(const ReadOptions& options, const Slice& key,
+ std::string* value) = 0;
+ virtual Status Get(const ReadOptions& options, const Slice& key,
+ PinnableSlice* pinnable_val) {
+ assert(pinnable_val != nullptr);
+ auto s = Get(options, key, pinnable_val->GetSelf());
+ pinnable_val->PinSelf();
+ return s;
+ }
+
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) = 0;
+
+ virtual std::vector<Status> MultiGet(const ReadOptions& options,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) = 0;
+
+ // Batched version of MultiGet - see DBImpl::MultiGet(). Sub-classes are
+ // expected to override this with an implementation that calls
+ // DBImpl::MultiGet()
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool /*sorted_input*/ = false) {
+ for (size_t i = 0; i < num_keys; ++i) {
+ statuses[i] = Get(options, column_family, keys[i], &values[i]);
+ }
+ }
+
+ // 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 and do_validate is true). If
+ // do_validate is false, ReadOptions::snapshot is expected to be nullptr so
+ // that GetForUpdate returns the latest committed value. 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 Transaction::Get().
+ // 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 OptimisticTransaction, GetForUpdate()
+ // could cause commit() to fail. Otherwise, it could return any error
+ // that could be returned by DB::Get().
+ //
+ // If this transaction was created by a TransactionDB, it can return
+ // Status::OK() on success,
+ // Status::Busy() if there is a write conflict,
+ // Status::TimedOut() if a lock could not be acquired,
+ // Status::TryAgain() if the memtable history size is not large enough
+ // (See max_write_buffer_size_to_maintain)
+ // Status::MergeInProgress() if merge operations cannot be resolved.
+ // or other errors if this key could not be read.
+ virtual Status GetForUpdate(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, std::string* value,
+ bool exclusive = true,
+ const bool do_validate = true) = 0;
+
+ // An overload of the above method that receives a PinnableSlice
+ // For backward compatibility a default implementation is provided
+ virtual Status GetForUpdate(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* pinnable_val,
+ bool exclusive = true,
+ const bool do_validate = true) {
+ if (pinnable_val == nullptr) {
+ std::string* null_str = nullptr;
+ return GetForUpdate(options, column_family, key, null_str, exclusive,
+ do_validate);
+ } else {
+ auto s = GetForUpdate(options, column_family, key,
+ pinnable_val->GetSelf(), exclusive, do_validate);
+ pinnable_val->PinSelf();
+ return s;
+ }
+ }
+
+ virtual Status GetForUpdate(const ReadOptions& options, const Slice& key,
+ std::string* value, bool exclusive = true,
+ const bool do_validate = true) = 0;
+
+ virtual std::vector<Status> MultiGetForUpdate(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) = 0;
+
+ virtual std::vector<Status> MultiGetForUpdate(
+ const ReadOptions& options, const std::vector<Slice>& keys,
+ std::vector<std::string>* values) = 0;
+
+ // 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 read_options.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 Commit(), Rollback(), or
+ // RollbackToSavePoint() is called.
+ virtual Iterator* GetIterator(const ReadOptions& read_options) = 0;
+
+ virtual Iterator* GetIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) = 0;
+
+ // Put, Merge, Delete, and SingleDelete behave similarly to the corresponding
+ // functions in WriteBatch, but will also do conflict checking on the
+ // keys being written.
+ //
+ // assume_tracked=true expects the key be 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. Returns error otherwise.
+ //
+ // If this Transaction was created on an OptimisticTransactionDB, these
+ // functions should always return Status::OK().
+ //
+ // If this Transaction was created on a TransactionDB, the status returned
+ // can be:
+ // Status::OK() on success,
+ // Status::Busy() if there is a write conflict,
+ // Status::TimedOut() if a lock could not be acquired,
+ // Status::TryAgain() if the memtable history size is not large enough
+ // (See max_write_buffer_size_to_maintain)
+ // or other errors on unexpected failures.
+ virtual Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value, const bool assume_tracked = false) = 0;
+ virtual Status Put(const Slice& key, const Slice& value) = 0;
+ virtual Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value,
+ const bool assume_tracked = false) = 0;
+ virtual Status Put(const SliceParts& key, const SliceParts& value) = 0;
+
+ virtual Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value,
+ const bool assume_tracked = false) = 0;
+ virtual Status Merge(const Slice& key, const Slice& value) = 0;
+
+ virtual Status Delete(ColumnFamilyHandle* column_family, const Slice& key,
+ const bool assume_tracked = false) = 0;
+ virtual Status Delete(const Slice& key) = 0;
+ virtual Status Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked = false) = 0;
+ virtual Status Delete(const SliceParts& key) = 0;
+
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const bool assume_tracked = false) = 0;
+ virtual Status SingleDelete(const Slice& key) = 0;
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked = false) = 0;
+ virtual Status SingleDelete(const SliceParts& key) = 0;
+
+ // PutUntracked() will write a Put to the batch of operations to be committed
+ // in this transaction. This write will only happen if this transaction
+ // gets committed successfully. But unlike Transaction::Put(),
+ // no conflict checking will be done for this key.
+ //
+ // If this Transaction was created on a PessimisticTransactionDB, this
+ // function will still acquire locks necessary to make sure this write doesn't
+ // cause conflicts in other transactions and may return Status::Busy().
+ virtual Status PutUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) = 0;
+ virtual Status PutUntracked(const Slice& key, const Slice& value) = 0;
+ virtual Status PutUntracked(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const SliceParts& value) = 0;
+ virtual Status PutUntracked(const SliceParts& key,
+ const SliceParts& value) = 0;
+
+ virtual Status MergeUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) = 0;
+ virtual Status MergeUntracked(const Slice& key, const Slice& value) = 0;
+
+ virtual Status DeleteUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+
+ virtual Status DeleteUntracked(const Slice& key) = 0;
+ virtual Status DeleteUntracked(ColumnFamilyHandle* column_family,
+ const SliceParts& key) = 0;
+ virtual Status DeleteUntracked(const SliceParts& key) = 0;
+ virtual Status SingleDeleteUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+
+ virtual Status SingleDeleteUntracked(const Slice& key) = 0;
+
+ // Similar to WriteBatch::PutLogData
+ virtual void PutLogData(const Slice& blob) = 0;
+
+ // 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 DisableIndexing() will turn off indexing for all future
+ // Put/Merge/Delete operations until EnableIndexing() is called.
+ //
+ // If a key is Put/Merge/Deleted after DisableIndexing is called and then
+ // is fetched via Get/GetForUpdate/GetIterator, the result of the fetch is
+ // undefined.
+ virtual void DisableIndexing() = 0;
+ virtual void EnableIndexing() = 0;
+
+ // Returns the number of distinct Keys being tracked by this transaction.
+ // If this transaction was created by a TransactionDB, this is the number of
+ // keys that are currently locked by this transaction.
+ // If this transaction was created by an OptimisticTransactionDB, this is the
+ // number of keys that need to be checked for conflicts at commit time.
+ virtual uint64_t GetNumKeys() const = 0;
+
+ // Returns the number of Puts/Deletes/Merges that have been applied to this
+ // transaction so far.
+ virtual uint64_t GetNumPuts() const = 0;
+ virtual uint64_t GetNumDeletes() const = 0;
+ virtual uint64_t GetNumMerges() const = 0;
+
+ // Returns the elapsed time in milliseconds since this Transaction began.
+ virtual uint64_t GetElapsedTime() const = 0;
+
+ // 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 Transaction class to
+ // write to this transaction.
+ virtual WriteBatchWithIndex* GetWriteBatch() = 0;
+
+ // Change the value of TransactionOptions.lock_timeout (in milliseconds) for
+ // this transaction.
+ // Has no effect on OptimisticTransactions.
+ virtual void SetLockTimeout(int64_t timeout) = 0;
+
+ // Return the WriteOptions that will be used during Commit()
+ virtual WriteOptions* GetWriteOptions() = 0;
+
+ // Reset the WriteOptions that will be used during Commit().
+ virtual void SetWriteOptions(const WriteOptions& write_options) = 0;
+
+ // If this key was previously fetched in this transaction using
+ // GetForUpdate/MultigetForUpdate(), calling UndoGetForUpdate 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 GetForUpdate/MultigetForUpdate(),
+ // then UndoGetForUpdate will only have an effect if it is also called N
+ // times. If this key has been written to in this transaction,
+ // UndoGetForUpdate() will have no effect.
+ //
+ // If SetSavePoint() has been called after the GetForUpdate(),
+ // UndoGetForUpdate() will not have any effect.
+ //
+ // If this Transaction was created by an OptimisticTransactionDB,
+ // calling UndoGetForUpdate can affect whether this key is conflict checked
+ // at commit time.
+ // If this Transaction was created by a TransactionDB,
+ // calling UndoGetForUpdate may release any held locks for this key.
+ virtual void UndoGetForUpdate(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+ virtual void UndoGetForUpdate(const Slice& key) = 0;
+
+ virtual Status RebuildFromWriteBatch(WriteBatch* src_batch) = 0;
+
+ virtual WriteBatch* GetCommitTimeWriteBatch() = 0;
+
+ virtual void SetLogNumber(uint64_t log) { log_number_ = log; }
+
+ virtual uint64_t GetLogNumber() const { return log_number_; }
+
+ virtual Status SetName(const TransactionName& name) = 0;
+
+ virtual TransactionName GetName() const { return name_; }
+
+ virtual TransactionID GetID() const { return 0; }
+
+ virtual bool IsDeadlockDetect() const { return false; }
+
+ virtual std::vector<TransactionID> GetWaitingTxns(
+ uint32_t* /*column_family_id*/, std::string* /*key*/) const {
+ assert(false);
+ return std::vector<TransactionID>();
+ }
+
+ enum TransactionState {
+ STARTED = 0,
+ AWAITING_PREPARE = 1,
+ PREPARED = 2,
+ AWAITING_COMMIT = 3,
+ COMMITED = 4,
+ AWAITING_ROLLBACK = 5,
+ ROLLEDBACK = 6,
+ LOCKS_STOLEN = 7,
+ };
+
+ TransactionState GetState() const { return txn_state_; }
+ void SetState(TransactionState state) { txn_state_ = state; }
+
+ // NOTE: Experimental feature
+ // 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.
+ uint64_t GetId() { return id_; }
+
+ protected:
+ explicit Transaction(const TransactionDB* /*db*/) {}
+ Transaction() : log_number_(0), txn_state_(STARTED) {}
+
+ // the log in which the prepared section for this txn resides
+ // (for two phase commit)
+ uint64_t log_number_;
+ TransactionName name_;
+
+ // Execution status of the transaction.
+ std::atomic<TransactionState> txn_state_;
+
+ uint64_t id_ = 0;
+ virtual void SetId(uint64_t id) {
+ assert(id_ == 0);
+ id_ = id;
+ }
+
+ virtual uint64_t GetLastLogNumber() const { return log_number_; }
+
+ private:
+ friend class PessimisticTransactionDB;
+ friend class WriteUnpreparedTxnDB;
+ friend class TransactionTest_TwoPhaseLogRollingTest_Test;
+ friend class TransactionTest_TwoPhaseLogRollingTest2_Test;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db.h
new file mode 100644
index 00000000..73b7416a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db.h
@@ -0,0 +1,309 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/stackable_db.h"
+#include "rocksdb/utilities/transaction.h"
+
+// Database with Transaction support.
+//
+// See transaction.h and examples/transaction_example.cc
+
+namespace ROCKSDB_NAMESPACE {
+
+class TransactionDBMutexFactory;
+
+enum TxnDBWritePolicy {
+ WRITE_COMMITTED = 0, // write only the committed data
+ WRITE_PREPARED, // write data after the prepare phase of 2pc
+ WRITE_UNPREPARED // write data before the prepare phase of 2pc
+};
+
+const uint32_t kInitialMaxDeadlocks = 5;
+
+struct TransactionDBOptions {
+ // 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 max_num_locks, transaction
+ // writes (or GetForUpdate) will return an error.
+ // If this value is not positive, no limit will be enforced.
+ int64_t max_num_locks = -1;
+
+ // Stores the number of latest deadlocks to track
+ uint32_t max_num_deadlocks = kInitialMaxDeadlocks;
+
+ // 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.
+ size_t num_stripes = 16;
+
+ // If positive, specifies the default wait timeout in milliseconds when
+ // a transaction attempts to lock a key if not specified by
+ // TransactionOptions::lock_timeout.
+ //
+ // 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.
+ int64_t transaction_lock_timeout = 1000; // 1 second
+
+ // If positive, specifies the wait timeout in milliseconds when writing a key
+ // OUTSIDE of a transaction (ie by calling DB::Put(),Merge(),Delete(),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.
+ int64_t default_lock_timeout = 1000; // 1 second
+
+ // If set, the TransactionDB will use this implementation of a mutex and
+ // condition variable for all transaction locking instead of the default
+ // mutex/condvar implementation.
+ std::shared_ptr<TransactionDBMutexFactory> custom_mutex_factory;
+
+ // The policy for when to write the data into the DB. The default policy is to
+ // write only the committed data (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.
+ TxnDBWritePolicy write_policy = TxnDBWritePolicy::WRITE_COMMITTED;
+
+ // TODO(myabandeh): remove this option
+ // Note: this is a temporary option as a hot fix in rollback of writeprepared
+ // txns in myrocks. MyRocks uses merge operands for autoinc column id without
+ // however obtaining locks. This breaks the assumption behind the rollback
+ // logic in myrocks. This hack of simply not rolling back merge operands works
+ // for the special way that myrocks uses this operands.
+ bool rollback_merge_operands = false;
+
+ // If true, the TransactionDB implementation might skip concurrency control
+ // unless it is overridden by TransactionOptions or
+ // TransactionDBWriteOptimizations. This can be used in conjuction with
+ // DBOptions::unordered_write when the TransactionDB is used solely for write
+ // ordering rather than concurrency control.
+ bool skip_concurrency_control = false;
+
+ // This option is only valid for write unprepared. If a write batch exceeds
+ // this threshold, then the transaction will implicitly flush the currently
+ // pending writes into the database. A value of 0 or less means no limit.
+ int64_t default_write_batch_flush_threshold = 0;
+
+ private:
+ // 128 entries
+ size_t wp_snapshot_cache_bits = static_cast<size_t>(7);
+ // 8m entry, 64MB size
+ size_t wp_commit_cache_bits = static_cast<size_t>(23);
+
+ // For testing, whether transaction name should be auto-generated or not. This
+ // is useful for write unprepared which requires named transactions.
+ bool autogenerate_name = false;
+
+ friend class WritePreparedTxnDB;
+ friend class WriteUnpreparedTxn;
+ friend class WritePreparedTransactionTestBase;
+ friend class TransactionTestBase;
+ friend class MySQLStyleTransactionTest;
+};
+
+struct TransactionOptions {
+ // Setting set_snapshot=true is the same as calling
+ // Transaction::SetSnapshot().
+ bool set_snapshot = false;
+
+ // 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
+ // Status::Busy. The user should retry their transaction.
+ bool deadlock_detect = false;
+
+ // If set, it states that the CommitTimeWriteBatch represents the latest state
+ // of the application, has only one sub-batch, i.e., no duplicate keys, and
+ // meant to be used later during recovery. It enables an optimization to
+ // postpone updating the memtable with CommitTimeWriteBatch to only
+ // SwitchMemtable or recovery.
+ bool use_only_the_last_commit_time_batch_for_recovery = false;
+
+ // TODO(agiardullo): TransactionDB does not yet support comparators that allow
+ // two non-equal keys to be equivalent. Ie, cmp->Compare(a,b) should only
+ // return 0 if
+ // a.compare(b) returns 0.
+
+ // 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, TransactionDBOptions::transaction_lock_timeout will be used.
+ int64_t lock_timeout = -1;
+
+ // 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.
+ int64_t expiration = -1;
+
+ // The number of traversals to make during deadlock detection.
+ int64_t deadlock_detect_depth = 50;
+
+ // The maximum number of bytes used for the write batch. 0 means no limit.
+ size_t max_write_batch_size = 0;
+
+ // Skip Concurrency Control. This could be as an optimization if the
+ // application knows that the transaction would not have any conflict with
+ // concurrent transactions. It could also be used during recovery if (i)
+ // application guarantees no conflict between prepared transactions in the WAL
+ // (ii) application guarantees that recovered transactions will be rolled
+ // back/commit before new transactions start.
+ // Default: false
+ bool skip_concurrency_control = false;
+
+ // See TransactionDBOptions::default_write_batch_flush_threshold for
+ // description. If a negative value is specified, then the default value from
+ // TransactionDBOptions is used.
+ int64_t write_batch_flush_threshold = -1;
+};
+
+// The per-write optimizations that do not involve transactions. TransactionDB
+// implementation might or might not make use of the specified optimizations.
+struct TransactionDBWriteOptimizations {
+ // If it is true it means that the application guarantees that the
+ // key-set in the write batch do not conflict with any concurrent transaction
+ // and hence the concurrency control mechanism could be skipped for this
+ // write.
+ bool skip_concurrency_control = false;
+ // If true, the application guarantees that there is no duplicate <column
+ // family, key> in the write batch and any employed mechanism to handle
+ // duplicate keys could be skipped.
+ bool skip_duplicate_key_check = false;
+};
+
+struct KeyLockInfo {
+ std::string key;
+ std::vector<TransactionID> ids;
+ bool exclusive;
+};
+
+struct DeadlockInfo {
+ TransactionID m_txn_id;
+ uint32_t m_cf_id;
+ bool m_exclusive;
+ std::string m_waiting_key;
+};
+
+struct DeadlockPath {
+ std::vector<DeadlockInfo> path;
+ bool limit_exceeded;
+ int64_t deadlock_time;
+
+ explicit DeadlockPath(std::vector<DeadlockInfo> path_entry,
+ const int64_t& dl_time)
+ : path(path_entry), limit_exceeded(false), deadlock_time(dl_time) {}
+
+ // empty path, limit exceeded constructor and default constructor
+ explicit DeadlockPath(const int64_t& dl_time = 0, bool limit = false)
+ : path(0), limit_exceeded(limit), deadlock_time(dl_time) {}
+
+ bool empty() { return path.empty() && !limit_exceeded; }
+};
+
+class TransactionDB : public StackableDB {
+ public:
+ // Optimized version of ::Write that receives more optimization request such
+ // as skip_concurrency_control.
+ using StackableDB::Write;
+ virtual Status Write(const WriteOptions& opts,
+ const TransactionDBWriteOptimizations&,
+ WriteBatch* updates) {
+ // The default implementation ignores TransactionDBWriteOptimizations and
+ // falls back to the un-optimized version of ::Write
+ return Write(opts, updates);
+ }
+ // Open a TransactionDB similar to DB::Open().
+ // Internally call PrepareWrap() and WrapDB()
+ // If the return status is not ok, then dbptr is set to nullptr.
+ static Status Open(const Options& options,
+ const TransactionDBOptions& txn_db_options,
+ const std::string& dbname, TransactionDB** dbptr);
+
+ static Status Open(const DBOptions& db_options,
+ const TransactionDBOptions& txn_db_options,
+ const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ TransactionDB** dbptr);
+ // Note: PrepareWrap() may change parameters, make copies before the
+ // invocation if needed.
+ static void PrepareWrap(DBOptions* db_options,
+ std::vector<ColumnFamilyDescriptor>* column_families,
+ std::vector<size_t>* compaction_enabled_cf_indices);
+ // If the return status is not ok, then dbptr will bet set to nullptr. The
+ // input db parameter might or might not be deleted as a result of the
+ // failure. If it is properly deleted it will be set to nullptr. If the return
+ // status is ok, the ownership of db is transferred to dbptr.
+ static Status WrapDB(DB* db, const TransactionDBOptions& txn_db_options,
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles,
+ TransactionDB** dbptr);
+ // If the return status is not ok, then dbptr will bet set to nullptr. The
+ // input db parameter might or might not be deleted as a result of the
+ // failure. If it is properly deleted it will be set to nullptr. If the return
+ // status is ok, the ownership of db is transferred to dbptr.
+ static Status WrapStackableDB(
+ StackableDB* db, const TransactionDBOptions& txn_db_options,
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles, TransactionDB** dbptr);
+ // Since the destructor in StackableDB is virtual, this destructor is virtual
+ // too. The root db will be deleted by the base's destructor.
+ ~TransactionDB() override {}
+
+ // Starts a new Transaction.
+ //
+ // Caller is responsible for deleting the returned transaction when no
+ // longer needed.
+ //
+ // If old_txn is not null, BeginTransaction will reuse this Transaction
+ // handle instead of allocating a new one. This is an optimization to avoid
+ // extra allocations when repeatedly creating transactions.
+ virtual Transaction* BeginTransaction(
+ const WriteOptions& write_options,
+ const TransactionOptions& txn_options = TransactionOptions(),
+ Transaction* old_txn = nullptr) = 0;
+
+ virtual Transaction* GetTransactionByName(const TransactionName& name) = 0;
+ virtual void GetAllPreparedTransactions(std::vector<Transaction*>* trans) = 0;
+
+ // Returns set of all locks held.
+ //
+ // The mapping is column family id -> KeyLockInfo
+ virtual std::unordered_multimap<uint32_t, KeyLockInfo>
+ GetLockStatusData() = 0;
+ virtual std::vector<DeadlockPath> GetDeadlockInfoBuffer() = 0;
+ virtual void SetDeadlockInfoBufferSize(uint32_t target_size) = 0;
+
+ protected:
+ // To Create an TransactionDB, call Open()
+ // The ownership of db is transferred to the base StackableDB
+ explicit TransactionDB(DB* db) : StackableDB(db) {}
+ // No copying allowed
+ TransactionDB(const TransactionDB&) = delete;
+ void operator=(const TransactionDB&) = delete;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db_mutex.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db_mutex.h
new file mode 100644
index 00000000..96a42adf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/transaction_db_mutex.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <memory>
+
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// TransactionDBMutex and TransactionDBCondVar APIs allows applications to
+// implement custom mutexes and condition variables to be used by a
+// TransactionDB when locking keys.
+//
+// To open a TransactionDB with a custom TransactionDBMutexFactory, set
+// TransactionDBOptions.custom_mutex_factory.
+
+class TransactionDBMutex {
+ public:
+ virtual ~TransactionDBMutex() {}
+
+ // Attempt to acquire lock. Return OK on success, or other Status on failure.
+ // If returned status is OK, TransactionDB will eventually call UnLock().
+ virtual Status Lock() = 0;
+
+ // Attempt to acquire lock. If timeout is non-negative, operation may be
+ // failed after this many microseconds.
+ // Returns OK on success,
+ // TimedOut if timed out,
+ // or other Status on failure.
+ // If returned status is OK, TransactionDB will eventually call UnLock().
+ virtual Status TryLockFor(int64_t timeout_time) = 0;
+
+ // Unlock Mutex that was successfully locked by Lock() or TryLockUntil()
+ virtual void UnLock() = 0;
+};
+
+class TransactionDBCondVar {
+ public:
+ virtual ~TransactionDBCondVar() {}
+
+ // Block current thread until condition variable is notified by a call to
+ // Notify() or NotifyAll(). Wait() will be called with mutex locked.
+ // Returns OK if notified.
+ // Returns non-OK if TransactionDB should stop waiting and fail the operation.
+ // May return OK spuriously even if not notified.
+ virtual Status Wait(std::shared_ptr<TransactionDBMutex> mutex) = 0;
+
+ // Block current thread until condition variable is notified by a call to
+ // Notify() or NotifyAll(), or if the timeout is reached.
+ // Wait() will be called with mutex locked.
+ //
+ // If timeout is non-negative, operation should be failed after this many
+ // microseconds.
+ // If implementing a custom version of this class, the implementation may
+ // choose to ignore the timeout.
+ //
+ // Returns OK if notified.
+ // Returns TimedOut if timeout is reached.
+ // Returns other status if TransactionDB should otherwis stop waiting and
+ // fail the operation.
+ // May return OK spuriously even if not notified.
+ virtual Status WaitFor(std::shared_ptr<TransactionDBMutex> mutex,
+ int64_t timeout_time) = 0;
+
+ // If any threads are waiting on *this, unblock at least one of the
+ // waiting threads.
+ virtual void Notify() = 0;
+
+ // Unblocks all threads waiting on *this.
+ virtual void NotifyAll() = 0;
+};
+
+// Factory class that can allocate mutexes and condition variables.
+class TransactionDBMutexFactory {
+ public:
+ // Create a TransactionDBMutex object.
+ virtual std::shared_ptr<TransactionDBMutex> AllocateMutex() = 0;
+
+ // Create a TransactionDBCondVar object.
+ virtual std::shared_ptr<TransactionDBCondVar> AllocateCondVar() = 0;
+
+ virtual ~TransactionDBMutexFactory() {}
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/utility_db.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/utility_db.h
new file mode 100644
index 00000000..cf2e5811
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/utility_db.h
@@ -0,0 +1,34 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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
+#ifndef ROCKSDB_LITE
+#include <string>
+#include <vector>
+
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "rocksdb/utilities/stackable_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Please don't use this class. It's deprecated
+class UtilityDB {
+ public:
+ // This function is here only for backwards compatibility. Please use the
+ // functions defined in DBWithTTl (rocksdb/utilities/db_ttl.h)
+ // (deprecated)
+#if defined(__GNUC__) || defined(__clang__)
+ __attribute__((deprecated))
+#elif _WIN32
+ __declspec(deprecated)
+#endif
+ static Status
+ OpenTtlDB(const Options& options, const std::string& name,
+ StackableDB** dbptr, int32_t ttl = 0, bool read_only = false);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/utilities/write_batch_with_index.h b/storage/rocksdb/rocksdb/include/rocksdb/utilities/write_batch_with_index.h
new file mode 100644
index 00000000..424aa158
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/utilities/write_batch_with_index.h
@@ -0,0 +1,278 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A WriteBatchWithIndex with a binary searchable index built for all the keys
+// inserted.
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/write_batch.h"
+#include "rocksdb/write_batch_base.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyHandle;
+class Comparator;
+class DB;
+class ReadCallback;
+struct ReadOptions;
+struct DBOptions;
+
+enum WriteType {
+ kPutRecord,
+ kMergeRecord,
+ kDeleteRecord,
+ kSingleDeleteRecord,
+ kDeleteRangeRecord,
+ kLogDataRecord,
+ kXIDRecord,
+};
+
+// an entry for Put, Merge, Delete, or SingleDelete entry for write batches.
+// Used in WBWIIterator.
+struct WriteEntry {
+ WriteType type;
+ Slice key;
+ Slice value;
+};
+
+// Iterator of one column family out of a WriteBatchWithIndex.
+class WBWIIterator {
+ public:
+ virtual ~WBWIIterator() {}
+
+ virtual bool Valid() const = 0;
+
+ virtual void SeekToFirst() = 0;
+
+ virtual void SeekToLast() = 0;
+
+ virtual void Seek(const Slice& key) = 0;
+
+ virtual void SeekForPrev(const Slice& key) = 0;
+
+ virtual void Next() = 0;
+
+ virtual void Prev() = 0;
+
+ // the return WriteEntry is only valid until the next mutation of
+ // WriteBatchWithIndex
+ virtual WriteEntry Entry() const = 0;
+
+ virtual Status status() const = 0;
+};
+
+// A WriteBatchWithIndex with a binary searchable index built for all the keys
+// inserted.
+// In Put(), Merge() Delete(), or SingleDelete(), the same function of the
+// wrapped will be called. At the same time, indexes will be built.
+// By calling GetWriteBatch(), a user will get the WriteBatch for the data
+// they inserted, which can be used for DB::Write().
+// A user can call NewIterator() to create an iterator.
+class WriteBatchWithIndex : public WriteBatchBase {
+ public:
+ // backup_index_comparator: the backup comparator used to compare keys
+ // within the same column family, if column family is not given in the
+ // interface, or we can't find a column family from the column family handle
+ // passed in, backup_index_comparator will be used for the column family.
+ // reserved_bytes: reserved bytes in underlying WriteBatch
+ // max_bytes: maximum size of underlying WriteBatch in bytes
+ // overwrite_key: if true, overwrite the key in the index when inserting
+ // the same key as previously, so iterator will never
+ // show two entries with the same key.
+ explicit WriteBatchWithIndex(
+ const Comparator* backup_index_comparator = BytewiseComparator(),
+ size_t reserved_bytes = 0, bool overwrite_key = false,
+ size_t max_bytes = 0);
+
+ ~WriteBatchWithIndex() override;
+ WriteBatchWithIndex(WriteBatchWithIndex&&);
+ WriteBatchWithIndex& operator=(WriteBatchWithIndex&&);
+
+ using WriteBatchBase::Put;
+ Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+
+ Status Put(const Slice& key, const Slice& value) override;
+
+ using WriteBatchBase::Merge;
+ Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+
+ Status Merge(const Slice& key, const Slice& value) override;
+
+ using WriteBatchBase::Delete;
+ Status Delete(ColumnFamilyHandle* column_family, const Slice& key) override;
+ Status Delete(const Slice& key) override;
+
+ using WriteBatchBase::SingleDelete;
+ Status SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ Status SingleDelete(const Slice& key) override;
+
+ using WriteBatchBase::DeleteRange;
+ Status DeleteRange(ColumnFamilyHandle* /* column_family */,
+ const Slice& /* begin_key */,
+ const Slice& /* end_key */) override {
+ return Status::NotSupported(
+ "DeleteRange unsupported in WriteBatchWithIndex");
+ }
+ Status DeleteRange(const Slice& /* begin_key */,
+ const Slice& /* end_key */) override {
+ return Status::NotSupported(
+ "DeleteRange unsupported in WriteBatchWithIndex");
+ }
+
+ using WriteBatchBase::PutLogData;
+ Status PutLogData(const Slice& blob) override;
+
+ using WriteBatchBase::Clear;
+ void Clear() override;
+
+ using WriteBatchBase::GetWriteBatch;
+ WriteBatch* GetWriteBatch() override;
+
+ // Create an iterator of a column family. User can call iterator.Seek() 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.
+ //
+ // The returned iterator should be deleted by the caller.
+ WBWIIterator* NewIterator(ColumnFamilyHandle* column_family);
+ // Create an iterator of the default column family.
+ WBWIIterator* NewIterator();
+
+ // Will create a new Iterator that will use WBWIIterator as a delta and
+ // base_iterator as base.
+ //
+ // This function is only supported if the WriteBatchWithIndex was
+ // constructed with overwrite_key=true.
+ //
+ // The returned iterator should be deleted by the caller.
+ // The base_iterator is now 'owned' by the returned iterator. Deleting the
+ // returned iterator will also delete the base_iterator.
+ //
+ // Updating write batch with the current key of the iterator is not safe.
+ // We strongly recommand 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.
+ Iterator* NewIteratorWithBase(ColumnFamilyHandle* column_family,
+ Iterator* base_iterator,
+ const ReadOptions* opts = nullptr);
+ // default column family
+ Iterator* NewIteratorWithBase(Iterator* base_iterator);
+
+ // Similar to DB::Get() but will only read the key from this batch.
+ // If the batch does not have enough data to resolve Merge operations,
+ // MergeInProgress status may be returned.
+ Status GetFromBatch(ColumnFamilyHandle* column_family,
+ const DBOptions& options, const Slice& key,
+ std::string* value);
+
+ // Similar to previous function but does not require a column_family.
+ // Note: An InvalidArgument status will be returned if there are any Merge
+ // operators for this key. Use previous method instead.
+ Status GetFromBatch(const DBOptions& options, const Slice& key,
+ std::string* value) {
+ return GetFromBatch(nullptr, options, key, value);
+ }
+
+ // Similar to DB::Get() 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 read_options.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).
+ Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ const Slice& key, std::string* value);
+
+ // An overload of the above method that receives a PinnableSlice
+ Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ const Slice& key, PinnableSlice* value);
+
+ Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value);
+
+ // An overload of the above method that receives a PinnableSlice
+ Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value);
+
+ void MultiGetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ bool sorted_input);
+
+ // Records the state of the batch for future calls to RollbackToSavePoint().
+ // May be called multiple times to set multiple save points.
+ void SetSavePoint() override;
+
+ // Remove all entries in this batch (Put, Merge, Delete, SingleDelete,
+ // PutLogData) since the most recent call to SetSavePoint() and removes the
+ // most recent save point.
+ // If there is no previous call to SetSavePoint(), behaves the same as
+ // Clear().
+ //
+ // Calling RollbackToSavePoint invalidates any open iterators on this batch.
+ //
+ // Returns Status::OK() on success,
+ // Status::NotFound() if no previous call to SetSavePoint(),
+ // or other Status on corruption.
+ Status RollbackToSavePoint() override;
+
+ // Pop the most recent save point.
+ // If there is no previous call to SetSavePoint(), Status::NotFound()
+ // will be returned.
+ // Otherwise returns Status::OK().
+ Status PopSavePoint() override;
+
+ void SetMaxBytes(size_t max_bytes) override;
+ size_t GetDataSize() const;
+
+ private:
+ friend class PessimisticTransactionDB;
+ friend class WritePreparedTxn;
+ friend class WriteUnpreparedTxn;
+ friend class WriteBatchWithIndex_SubBatchCnt_Test;
+ // Returns the number of sub-batches inside the write batch. A sub-batch
+ // starts right before inserting a key that is a duplicate of a key in the
+ // last sub-batch.
+ size_t SubBatchCnt();
+
+ Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value, ReadCallback* callback);
+ void MultiGetFromBatchAndDB(DB* db, const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ bool sorted_input, ReadCallback* callback);
+ struct Rep;
+ std::unique_ptr<Rep> rep;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/version.h b/storage/rocksdb/rocksdb/include/rocksdb/version.h
new file mode 100644
index 00000000..b8f1fe7c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/version.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_MAJOR 6
+#define ROCKSDB_MINOR 8
+#define ROCKSDB_PATCH 0
+
+// Do not use these. We made the mistake of declaring macros starting with
+// double underscore. Now we have to live with our choice. We'll deprecate these
+// at some point
+#define __ROCKSDB_MAJOR__ ROCKSDB_MAJOR
+#define __ROCKSDB_MINOR__ ROCKSDB_MINOR
+#define __ROCKSDB_PATCH__ ROCKSDB_PATCH
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/wal_filter.h b/storage/rocksdb/rocksdb/include/rocksdb/wal_filter.h
new file mode 100644
index 00000000..98eddc2e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/wal_filter.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteBatch;
+
+// WALFilter allows an application to inspect write-ahead-log (WAL)
+// records or modify their processing on recovery.
+// Please see the details below.
+class WalFilter {
+ public:
+ enum class WalProcessingOption {
+ // Continue processing as usual
+ kContinueProcessing = 0,
+ // Ignore the current record but continue processing of log(s)
+ kIgnoreCurrentRecord = 1,
+ // Stop replay of logs and discard logs
+ // Logs won't be replayed on subsequent recovery
+ kStopReplay = 2,
+ // Corrupted record detected by filter
+ kCorruptedRecord = 3,
+ // Marker for enum count
+ kWalProcessingOptionMax = 4
+ };
+
+ virtual ~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.
+ //
+ // @params cf_lognumber_map column_family_id to lognumber map
+ // @params cf_name_id_map column_family_name to column_family_id map
+
+ virtual void ColumnFamilyLogNumberMap(
+ const std::map<uint32_t, uint64_t>& /*cf_lognumber_map*/,
+ const std::map<std::string, uint32_t>& /*cf_name_id_map*/) {}
+
+ // 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.
+ //
+ // @params log_number log_number of the current log.
+ // Filter might use this to determine if the log
+ // record is applicable to a certain column family.
+ // @params log_file_name log file name - only for informational purposes
+ // @params batch batch encountered in the log during recovery
+ // @params new_batch 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.
+ // @params batch_changed Whether batch was changed by the filter.
+ // It must be set to true if new_batch was populated,
+ // else new_batch has no effect.
+ // @returns Processing option for the current record.
+ // Please see WalProcessingOption enum above for
+ // details.
+ virtual WalProcessingOption LogRecordFound(
+ unsigned long long /*log_number*/, const std::string& /*log_file_name*/,
+ const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed) {
+ // Default implementation falls back to older function for compatibility
+ return LogRecord(batch, new_batch, batch_changed);
+ }
+
+ // Please see the comments for LogRecord above. This function is for
+ // compatibility only and contains a subset of parameters.
+ // New code should use the function above.
+ virtual WalProcessingOption LogRecord(const WriteBatch& /*batch*/,
+ WriteBatch* /*new_batch*/,
+ bool* /*batch_changed*/) const {
+ return WalProcessingOption::kContinueProcessing;
+ }
+
+ // Returns a name that identifies this WAL filter.
+ // The name will be printed to LOG file on start up for diagnosis.
+ virtual const char* Name() const = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/write_batch.h b/storage/rocksdb/rocksdb/include/rocksdb/write_batch.h
new file mode 100644
index 00000000..51fd4d8a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/write_batch.h
@@ -0,0 +1,377 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// 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.Delete("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.
+
+#pragma once
+
+#include <stdint.h>
+#include <atomic>
+#include <memory>
+#include <string>
+#include <vector>
+#include "rocksdb/status.h"
+#include "rocksdb/write_batch_base.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class ColumnFamilyHandle;
+struct SavePoints;
+struct SliceParts;
+
+struct SavePoint {
+ size_t size; // size of rep_
+ int count; // count of elements in rep_
+ uint32_t content_flags;
+
+ SavePoint() : size(0), count(0), content_flags(0) {}
+
+ SavePoint(size_t _size, int _count, uint32_t _flags)
+ : size(_size), count(_count), content_flags(_flags) {}
+
+ void clear() {
+ size = 0;
+ count = 0;
+ content_flags = 0;
+ }
+
+ bool is_cleared() const { return (size | count | content_flags) == 0; }
+};
+
+class WriteBatch : public WriteBatchBase {
+ public:
+ explicit WriteBatch(size_t reserved_bytes = 0, size_t max_bytes = 0);
+ explicit WriteBatch(size_t reserved_bytes, size_t max_bytes, size_t ts_sz);
+ ~WriteBatch() override;
+
+ using WriteBatchBase::Put;
+ // Store the mapping "key->value" in the database.
+ Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ Status Put(const Slice& key, const Slice& value) override {
+ return Put(nullptr, key, value);
+ }
+
+ // Variant of Put() that gathers output like writev(2). The key and value
+ // that will be written to the database are concatenations of arrays of
+ // slices.
+ Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value) override;
+ Status Put(const SliceParts& key, const SliceParts& value) override {
+ return Put(nullptr, key, value);
+ }
+
+ using WriteBatchBase::Delete;
+ // If the database contains a mapping for "key", erase it. Else do nothing.
+ Status Delete(ColumnFamilyHandle* column_family, const Slice& key) override;
+ Status Delete(const Slice& key) override { return Delete(nullptr, key); }
+
+ // variant that takes SliceParts
+ Status Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) override;
+ Status Delete(const SliceParts& key) override { return Delete(nullptr, key); }
+
+ using WriteBatchBase::SingleDelete;
+ // WriteBatch implementation of DB::SingleDelete(). See db.h.
+ Status SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ Status SingleDelete(const Slice& key) override {
+ return SingleDelete(nullptr, key);
+ }
+
+ // variant that takes SliceParts
+ Status SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key) override;
+ Status SingleDelete(const SliceParts& key) override {
+ return SingleDelete(nullptr, key);
+ }
+
+ using WriteBatchBase::DeleteRange;
+ // WriteBatch implementation of DB::DeleteRange(). See db.h.
+ Status DeleteRange(ColumnFamilyHandle* column_family, const Slice& begin_key,
+ const Slice& end_key) override;
+ Status DeleteRange(const Slice& begin_key, const Slice& end_key) override {
+ return DeleteRange(nullptr, begin_key, end_key);
+ }
+
+ // variant that takes SliceParts
+ Status DeleteRange(ColumnFamilyHandle* column_family,
+ const SliceParts& begin_key,
+ const SliceParts& end_key) override;
+ Status DeleteRange(const SliceParts& begin_key,
+ const SliceParts& end_key) override {
+ return DeleteRange(nullptr, begin_key, end_key);
+ }
+
+ using WriteBatchBase::Merge;
+ // Merge "value" with the existing value of "key" in the database.
+ // "key->merge(existing, value)"
+ Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ Status Merge(const Slice& key, const Slice& value) override {
+ return Merge(nullptr, key, value);
+ }
+
+ // variant that takes SliceParts
+ Status Merge(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value) override;
+ Status Merge(const SliceParts& key, const SliceParts& value) override {
+ return Merge(nullptr, key, value);
+ }
+
+ using WriteBatchBase::PutLogData;
+ // 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 which 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.
+ Status PutLogData(const Slice& blob) override;
+
+ using WriteBatchBase::Clear;
+ // Clear all updates buffered in this batch.
+ void Clear() override;
+
+ // Records the state of the batch for future calls to RollbackToSavePoint().
+ // May be called multiple times to set multiple save points.
+ void SetSavePoint() override;
+
+ // Remove all entries in this batch (Put, Merge, Delete, PutLogData) since the
+ // most recent call to SetSavePoint() and removes the most recent save point.
+ // If there is no previous call to SetSavePoint(), Status::NotFound()
+ // will be returned.
+ // Otherwise returns Status::OK().
+ Status RollbackToSavePoint() override;
+
+ // Pop the most recent save point.
+ // If there is no previous call to SetSavePoint(), Status::NotFound()
+ // will be returned.
+ // Otherwise returns Status::OK().
+ Status PopSavePoint() override;
+
+ // Support for iterating over the contents of a batch.
+ class Handler {
+ public:
+ virtual ~Handler();
+ // All handler functions in this class provide default implementations so
+ // we won't break existing clients of Handler on a source code level when
+ // adding a new member function.
+
+ // default implementation will just call Put without column family for
+ // backwards compatibility. If the column family is not default,
+ // the function is noop
+ virtual Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) {
+ if (column_family_id == 0) {
+ // Put() historically doesn't return status. We didn't want to be
+ // backwards incompatible so we didn't change the return status
+ // (this is a public API). We do an ordinary get and return Status::OK()
+ Put(key, value);
+ return Status::OK();
+ }
+ return Status::InvalidArgument(
+ "non-default column family and PutCF not implemented");
+ }
+ virtual void Put(const Slice& /*key*/, const Slice& /*value*/) {}
+
+ virtual Status DeleteCF(uint32_t column_family_id, const Slice& key) {
+ if (column_family_id == 0) {
+ Delete(key);
+ return Status::OK();
+ }
+ return Status::InvalidArgument(
+ "non-default column family and DeleteCF not implemented");
+ }
+ virtual void Delete(const Slice& /*key*/) {}
+
+ virtual Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) {
+ if (column_family_id == 0) {
+ SingleDelete(key);
+ return Status::OK();
+ }
+ return Status::InvalidArgument(
+ "non-default column family and SingleDeleteCF not implemented");
+ }
+ virtual void SingleDelete(const Slice& /*key*/) {}
+
+ virtual Status DeleteRangeCF(uint32_t /*column_family_id*/,
+ const Slice& /*begin_key*/,
+ const Slice& /*end_key*/) {
+ return Status::InvalidArgument("DeleteRangeCF not implemented");
+ }
+
+ virtual Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) {
+ if (column_family_id == 0) {
+ Merge(key, value);
+ return Status::OK();
+ }
+ return Status::InvalidArgument(
+ "non-default column family and MergeCF not implemented");
+ }
+ virtual void Merge(const Slice& /*key*/, const Slice& /*value*/) {}
+
+ virtual Status PutBlobIndexCF(uint32_t /*column_family_id*/,
+ const Slice& /*key*/,
+ const Slice& /*value*/) {
+ return Status::InvalidArgument("PutBlobIndexCF not implemented");
+ }
+
+ // The default implementation of LogData does nothing.
+ virtual void LogData(const Slice& blob);
+
+ virtual Status MarkBeginPrepare(bool = false) {
+ return Status::InvalidArgument("MarkBeginPrepare() handler not defined.");
+ }
+
+ virtual Status MarkEndPrepare(const Slice& /*xid*/) {
+ return Status::InvalidArgument("MarkEndPrepare() handler not defined.");
+ }
+
+ virtual Status MarkNoop(bool /*empty_batch*/) {
+ return Status::InvalidArgument("MarkNoop() handler not defined.");
+ }
+
+ virtual Status MarkRollback(const Slice& /*xid*/) {
+ return Status::InvalidArgument(
+ "MarkRollbackPrepare() handler not defined.");
+ }
+
+ virtual Status MarkCommit(const Slice& /*xid*/) {
+ return Status::InvalidArgument("MarkCommit() handler not defined.");
+ }
+
+ // Continue is called by WriteBatch::Iterate. If it returns false,
+ // iteration is halted. Otherwise, it continues iterating. The default
+ // implementation always returns true.
+ virtual bool Continue();
+
+ protected:
+ friend class WriteBatchInternal;
+ virtual bool WriteAfterCommit() const { return true; }
+ virtual bool WriteBeforePrepare() const { return false; }
+ };
+ Status Iterate(Handler* handler) const;
+
+ // Retrieve the serialized version of this batch.
+ const std::string& Data() const { return rep_; }
+
+ // Retrieve data size of the batch.
+ size_t GetDataSize() const { return rep_.size(); }
+
+ // Returns the number of updates in the batch
+ uint32_t Count() const;
+
+ // Returns true if PutCF will be called during Iterate
+ bool HasPut() const;
+
+ // Returns true if DeleteCF will be called during Iterate
+ bool HasDelete() const;
+
+ // Returns true if SingleDeleteCF will be called during Iterate
+ bool HasSingleDelete() const;
+
+ // Returns true if DeleteRangeCF will be called during Iterate
+ bool HasDeleteRange() const;
+
+ // Returns true if MergeCF will be called during Iterate
+ bool HasMerge() const;
+
+ // Returns true if MarkBeginPrepare will be called during Iterate
+ bool HasBeginPrepare() const;
+
+ // Returns true if MarkEndPrepare will be called during Iterate
+ bool HasEndPrepare() const;
+
+ // Returns trie if MarkCommit will be called during Iterate
+ bool HasCommit() const;
+
+ // Returns trie if MarkRollback will be called during Iterate
+ bool HasRollback() const;
+
+ // Assign timestamp to write batch
+ Status AssignTimestamp(const Slice& ts);
+
+ // Assign timestamps to write batch
+ Status AssignTimestamps(const std::vector<Slice>& ts_list);
+
+ using WriteBatchBase::GetWriteBatch;
+ WriteBatch* GetWriteBatch() override { return this; }
+
+ // Constructor with a serialized string object
+ explicit WriteBatch(const std::string& rep);
+ explicit WriteBatch(std::string&& rep);
+
+ WriteBatch(const WriteBatch& src);
+ WriteBatch(WriteBatch&& src) noexcept;
+ WriteBatch& operator=(const WriteBatch& src);
+ WriteBatch& operator=(WriteBatch&& src);
+
+ // marks this point in the WriteBatch as the last record to
+ // be inserted into the WAL, provided the WAL is enabled
+ void MarkWalTerminationPoint();
+ const SavePoint& GetWalTerminationPoint() const { return wal_term_point_; }
+
+ void SetMaxBytes(size_t max_bytes) override { max_bytes_ = max_bytes; }
+
+ private:
+ friend class WriteBatchInternal;
+ friend class LocalSavePoint;
+ // TODO(myabandeh): this is needed for a hack to collapse the write batch and
+ // remove duplicate keys. Remove it when the hack is replaced with a proper
+ // solution.
+ friend class WriteBatchWithIndex;
+ std::unique_ptr<SavePoints> save_points_;
+
+ // When sending a WriteBatch through WriteImpl we might want to
+ // specify that only the first x records of the batch be written to
+ // the WAL.
+ SavePoint wal_term_point_;
+
+ // For HasXYZ. Mutable to allow lazy computation of results
+ mutable std::atomic<uint32_t> content_flags_;
+
+ // Performs deferred computation of content_flags if necessary
+ uint32_t ComputeContentFlags() const;
+
+ // Maximum size of rep_.
+ size_t max_bytes_;
+
+ // Is the content of the batch the application's latest state that meant only
+ // to be used for recovery? Refer to
+ // TransactionOptions::use_only_the_last_commit_time_batch_for_recovery for
+ // more details.
+ bool is_latest_persistent_state_ = false;
+
+ protected:
+ std::string rep_; // See comment in write_batch.cc for the format of rep_
+ const size_t timestamp_size_;
+
+ // Intentionally copyable
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/write_batch_base.h b/storage/rocksdb/rocksdb/include/rocksdb/write_batch_base.h
new file mode 100644
index 00000000..19ff877e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/write_batch_base.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstddef>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class Status;
+class ColumnFamilyHandle;
+class WriteBatch;
+struct SliceParts;
+
+// Abstract base class that defines the basic interface for a write batch.
+// See WriteBatch for a basic implementation and WrithBatchWithIndex for an
+// indexed implementation.
+class WriteBatchBase {
+ public:
+ virtual ~WriteBatchBase() {}
+
+ // Store the mapping "key->value" in the database.
+ virtual Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) = 0;
+ virtual Status Put(const Slice& key, const Slice& value) = 0;
+
+ // Variant of Put() that gathers output like writev(2). The key and value
+ // that will be written to the database are concatenations of arrays of
+ // slices.
+ virtual Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value);
+ virtual Status Put(const SliceParts& key, const SliceParts& value);
+
+ // Merge "value" with the existing value of "key" in the database.
+ // "key->merge(existing, value)"
+ virtual Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) = 0;
+ virtual Status Merge(const Slice& key, const Slice& value) = 0;
+
+ // variant that takes SliceParts
+ virtual Status Merge(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value);
+ virtual Status Merge(const SliceParts& key, const SliceParts& value);
+
+ // If the database contains a mapping for "key", erase it. Else do nothing.
+ virtual Status Delete(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+ virtual Status Delete(const Slice& key) = 0;
+
+ // variant that takes SliceParts
+ virtual Status Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key);
+ virtual Status Delete(const SliceParts& key);
+
+ // If the database contains a mapping for "key", erase it. Expects that the
+ // key was not overwritten. Else do nothing.
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+ virtual Status SingleDelete(const Slice& key) = 0;
+
+ // variant that takes SliceParts
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key);
+ virtual Status SingleDelete(const SliceParts& key);
+
+ // If the database contains mappings in the range ["begin_key", "end_key"),
+ // erase them. Else do nothing.
+ virtual Status DeleteRange(ColumnFamilyHandle* column_family,
+ const Slice& begin_key, const Slice& end_key) = 0;
+ virtual Status DeleteRange(const Slice& begin_key, const Slice& end_key) = 0;
+
+ // variant that takes SliceParts
+ virtual Status DeleteRange(ColumnFamilyHandle* column_family,
+ const SliceParts& begin_key,
+ const SliceParts& end_key);
+ virtual Status DeleteRange(const SliceParts& begin_key,
+ const SliceParts& end_key);
+
+ // 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 which 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.
+ virtual Status PutLogData(const Slice& blob) = 0;
+
+ // Clear all updates buffered in this batch.
+ virtual void Clear() = 0;
+
+ // Covert this batch into a WriteBatch. This is an abstracted way of
+ // converting any WriteBatchBase(eg WriteBatchWithIndex) into a basic
+ // WriteBatch.
+ virtual WriteBatch* GetWriteBatch() = 0;
+
+ // Records the state of the batch for future calls to RollbackToSavePoint().
+ // May be called multiple times to set multiple save points.
+ virtual void SetSavePoint() = 0;
+
+ // Remove all entries in this batch (Put, Merge, Delete, PutLogData) since the
+ // most recent call to SetSavePoint() and removes the most recent save point.
+ // If there is no previous call to SetSavePoint(), behaves the same as
+ // Clear().
+ virtual Status RollbackToSavePoint() = 0;
+
+ // Pop the most recent save point.
+ // If there is no previous call to SetSavePoint(), Status::NotFound()
+ // will be returned.
+ // Otherwise returns Status::OK().
+ virtual Status PopSavePoint() = 0;
+
+ // Sets the maximum size of the write batch in bytes. 0 means no limit.
+ virtual void SetMaxBytes(size_t max_bytes) = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/include/rocksdb/write_buffer_manager.h b/storage/rocksdb/rocksdb/include/rocksdb/write_buffer_manager.h
new file mode 100644
index 00000000..ae1c98ca
--- /dev/null
+++ b/storage/rocksdb/rocksdb/include/rocksdb/write_buffer_manager.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// WriteBufferManager is for managing memory allocation for one or more
+// MemTables.
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include "rocksdb/cache.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteBufferManager {
+ public:
+ // _buffer_size = 0 indicates no limit. Memory won't be capped.
+ // memory_usage() won't be valid and ShouldFlush() will always return true.
+ // if `cache` is provided, we'll put dummy entries in the cache and cost
+ // the memory allocated to the cache. It can be used even if _buffer_size = 0.
+ explicit WriteBufferManager(size_t _buffer_size,
+ std::shared_ptr<Cache> cache = {});
+ // No copying allowed
+ WriteBufferManager(const WriteBufferManager&) = delete;
+ WriteBufferManager& operator=(const WriteBufferManager&) = delete;
+
+ ~WriteBufferManager();
+
+ bool enabled() const { return buffer_size_ != 0; }
+
+ bool cost_to_cache() const { return cache_rep_ != nullptr; }
+
+ // Only valid if enabled()
+ size_t memory_usage() const {
+ return memory_used_.load(std::memory_order_relaxed);
+ }
+ size_t mutable_memtable_memory_usage() const {
+ return memory_active_.load(std::memory_order_relaxed);
+ }
+ size_t buffer_size() const { return buffer_size_; }
+
+ // Should only be called from write thread
+ bool ShouldFlush() const {
+ if (enabled()) {
+ if (mutable_memtable_memory_usage() > mutable_limit_) {
+ return true;
+ }
+ if (memory_usage() >= buffer_size_ &&
+ mutable_memtable_memory_usage() >= buffer_size_ / 2) {
+ // If the memory exceeds the buffer size, we trigger more aggressive
+ // flush. But if already more than half memory is being flushed,
+ // triggering more flush may not help. We will hold it instead.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void ReserveMem(size_t mem) {
+ if (cache_rep_ != nullptr) {
+ ReserveMemWithCache(mem);
+ } else if (enabled()) {
+ memory_used_.fetch_add(mem, std::memory_order_relaxed);
+ }
+ if (enabled()) {
+ memory_active_.fetch_add(mem, std::memory_order_relaxed);
+ }
+ }
+ // We are in the process of freeing `mem` bytes, so it is not considered
+ // when checking the soft limit.
+ void ScheduleFreeMem(size_t mem) {
+ if (enabled()) {
+ memory_active_.fetch_sub(mem, std::memory_order_relaxed);
+ }
+ }
+ void FreeMem(size_t mem) {
+ if (cache_rep_ != nullptr) {
+ FreeMemWithCache(mem);
+ } else if (enabled()) {
+ memory_used_.fetch_sub(mem, std::memory_order_relaxed);
+ }
+ }
+
+ private:
+ const size_t buffer_size_;
+ const size_t mutable_limit_;
+ std::atomic<size_t> memory_used_;
+ // Memory that hasn't been scheduled to free.
+ std::atomic<size_t> memory_active_;
+ struct CacheRep;
+ std::unique_ptr<CacheRep> cache_rep_;
+
+ void ReserveMemWithCache(size_t mem);
+ void FreeMemWithCache(size_t mem);
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/issue_template.md b/storage/rocksdb/rocksdb/issue_template.md
new file mode 100644
index 00000000..ca52f5ea
--- /dev/null
+++ b/storage/rocksdb/rocksdb/issue_template.md
@@ -0,0 +1,7 @@
+> Note: Please use Issues only for bug reports. For questions, discussions, feature requests, etc. post to dev group: https://groups.google.com/forum/#!forum/rocksdb or https://www.facebook.com/groups/rocksdb.dev
+
+### Expected behavior
+
+### Actual behavior
+
+### Steps to reproduce the behavior
diff --git a/storage/rocksdb/rocksdb/java/CMakeLists.txt b/storage/rocksdb/rocksdb/java/CMakeLists.txt
new file mode 100644
index 00000000..b9ab8177
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/CMakeLists.txt
@@ -0,0 +1,502 @@
+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(JNI_NATIVE_SOURCES
+ rocksjni/backupablejni.cc
+ rocksjni/backupenginejni.cc
+ rocksjni/cassandra_compactionfilterjni.cc
+ rocksjni/cassandra_value_operator.cc
+ rocksjni/checkpoint.cc
+ rocksjni/clock_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/env.cc
+ rocksjni/env_options.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/statistics.cc
+ rocksjni/statisticsjni.cc
+ rocksjni/table.cc
+ rocksjni/table_filter.cc
+ rocksjni/table_filter_jnicallback.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/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/BackupableDBOptions.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/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/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/Experimental.java
+ src/main/java/org/rocksdb/Filter.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/HdfsEnv.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/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/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/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/OptionsUtil.java
+ src/main/java/org/rocksdb/PersistentCache.java
+ src/main/java/org/rocksdb/PlainTableConfig.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/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/SstFileWriter.java
+ src/main/java/org/rocksdb/SstFileReader.java
+ src/main/java/org/rocksdb/SstFileReaderIterator.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/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/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
+)
+
+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_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" OR (${Java_VERSION_MINOR} STREQUAL "7" AND ${Java_VERSION_MAJOR} STREQUAL "1"))
+ # Old CMake or Java 1.7 prepare the JAR...
+ message("Preparing Jar for Java 7")
+ 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 ()
+ # This is a URL for artifacts from a "fake" release on pdillinger's fork,
+ # so as not to put binaries in git (ew). We should move to hosting these
+ # under the facebook account on github, or something else more reliable
+ # than maven.org, which has been failing frequently from Travis.
+ set(DEPS_URL "https://github.com/pdillinger/rocksdb/releases/download/v6.6.x-java-deps")
+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)
+ if(NOT error_code EQUAL 0)
+ message(FATAL_ERROR "Failed downloading ${JAVA_JUNIT_JAR}")
+ 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)
+ if(NOT error_code EQUAL 0)
+ message(FATAL_ERROR "Failed downloading ${JAVA_HAMCR_JAR}")
+ 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)
+ if(NOT error_code EQUAL 0)
+ message(FATAL_ERROR "Failed downloading ${JAVA_MOCKITO_JAR}")
+ 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)
+ if(NOT error_code EQUAL 0)
+ message(FATAL_ERROR "Failed downloading ${JAVA_CGLIB_JAR}")
+ 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)
+ if(NOT error_code EQUAL 0)
+ message(FATAL_ERROR "Failed downloading ${JAVA_ASSERTJ_JAR}")
+ endif()
+ file(RENAME ${JAVA_TMP_JAR} ${JAVA_ASSERTJ_JAR})
+endif()
+
+if(${CMAKE_VERSION} VERSION_LESS "3.11.4" OR (${Java_VERSION_MINOR} STREQUAL "7" AND ${Java_VERSION_MAJOR} STREQUAL "1"))
+ # Old CMake or Java 1.7 ONLY generate JNI headers, Java 1.8+ JNI is handled in add_jar step above
+ message("Preparing JNI headers for Java 7")
+ set(NATIVE_JAVA_CLASSES
+ org.rocksdb.AbstractCompactionFilter
+ org.rocksdb.AbstractCompactionFilterFactory
+ org.rocksdb.AbstractComparator
+ 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.BackupableDBOptions
+ org.rocksdb.BackupEngine
+ org.rocksdb.BlockBasedTableConfig
+ org.rocksdb.BloomFilter
+ org.rocksdb.CassandraCompactionFilter
+ org.rocksdb.CassandraValueMergeOperator
+ org.rocksdb.Checkpoint
+ org.rocksdb.ClockCache
+ 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.DBOptions
+ org.rocksdb.DirectSlice
+ org.rocksdb.Env
+ org.rocksdb.EnvOptions
+ org.rocksdb.Filter
+ org.rocksdb.FlushOptions
+ org.rocksdb.HashLinkedListMemTableConfig
+ org.rocksdb.HashSkipListMemTableConfig
+ org.rocksdb.HdfsEnv
+ 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.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
+ )
+
+ 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/storage/rocksdb/rocksdb/java/HISTORY-JAVA.md b/storage/rocksdb/rocksdb/java/HISTORY-JAVA.md
new file mode 100644
index 00000000..731886a6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/HISTORY-JAVA.md
@@ -0,0 +1,86 @@
+# 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/storage/rocksdb/rocksdb/java/Makefile b/storage/rocksdb/rocksdb/java/Makefile
new file mode 100644
index 00000000..c6acd416
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/Makefile
@@ -0,0 +1,321 @@
+NATIVE_JAVA_CLASSES = \
+ org.rocksdb.AbstractCompactionFilter\
+ org.rocksdb.AbstractCompactionFilterFactory\
+ org.rocksdb.AbstractComparator\
+ org.rocksdb.AbstractSlice\
+ org.rocksdb.AbstractTableFilter\
+ org.rocksdb.AbstractTraceWriter\
+ org.rocksdb.AbstractTransactionNotifier\
+ org.rocksdb.AbstractWalFilter\
+ org.rocksdb.BackupEngine\
+ org.rocksdb.BackupableDBOptions\
+ org.rocksdb.BlockBasedTableConfig\
+ org.rocksdb.BloomFilter\
+ org.rocksdb.Checkpoint\
+ org.rocksdb.ClockCache\
+ 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.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.HdfsEnv\
+ 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.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.NativeComparatorWrapperTest.NativeStringComparatorWrapper\
+ org.rocksdb.WriteBatchTest\
+ org.rocksdb.WriteBatchTestInternalHelper
+
+ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3)
+ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3)
+ROCKSDB_PATCH = $(shell egrep "ROCKSDB_PATCH.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3)
+
+NATIVE_INCLUDE = ./include
+ARCH := $(shell getconf LONG_BIT)
+ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux$(ARCH).jar
+ifeq ($(PLATFORM), OS_MACOSX)
+ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx.jar
+endif
+
+JAVA_TESTS = \
+ org.rocksdb.BackupableDBOptionsTest\
+ org.rocksdb.BackupEngineTest\
+ org.rocksdb.BlockBasedTableConfigTest\
+ org.rocksdb.BuiltinComparatorTest\
+ 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.HdfsEnvTest\
+ 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.LoggerTest\
+ org.rocksdb.LRUCacheTest\
+ org.rocksdb.MemoryUtilTest\
+ org.rocksdb.MemTableTest\
+ org.rocksdb.MergeTest\
+ org.rocksdb.MixedOptionsTest\
+ org.rocksdb.MutableColumnFamilyOptionsTest\
+ org.rocksdb.MutableDBOptionsTest\
+ 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.SliceTest\
+ org.rocksdb.SnapshotTest\
+ org.rocksdb.SstFileManagerTest\
+ org.rocksdb.SstFileWriterTest\
+ org.rocksdb.SstFileReaderTest\
+ 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.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_JAR = $(JAVA_TEST_LIBDIR)/junit-4.12.jar
+JAVA_HAMCR_JAR = $(JAVA_TEST_LIBDIR)/hamcrest-core-1.3.jar
+JAVA_MOCKITO_JAR = $(JAVA_TEST_LIBDIR)/mockito-all-1.10.19.jar
+JAVA_CGLIB_JAR = $(JAVA_TEST_LIBDIR)/cglib-2.2.2.jar
+JAVA_ASSERTJ_JAR = $(JAVA_TEST_LIBDIR)/assertj-core-1.7.1.jar
+JAVA_TESTCLASSPATH = $(JAVA_JUNIT_JAR):$(JAVA_HAMCR_JAR):$(JAVA_MOCKITO_JAR):$(JAVA_CGLIB_JAR):$(JAVA_ASSERTJ_JAR)
+
+MVN_LOCAL = ~/.m2/repository
+
+# Set the default JAVA_ARGS to "" for DEBUG_LEVEL=0
+JAVA_ARGS? =
+
+JAVAC_ARGS? =
+
+# 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
+
+# This is a URL for artifacts from a "fake" release on pdillinger's fork,
+# so as not to put binaries in git (ew). We should move to hosting these
+# under the facebook account on github, or something else more reliable
+# than maven.org, which has been failing frequently from Travis.
+DEPS_URL?=https://github.com/pdillinger/rocksdb/releases/download/v6.6.x-java-deps
+
+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 -d $(JAVADOC) -sourcepath $(MAIN_SRC) -subpackages org
+
+javalib: java java_test javadocs
+
+java:
+ $(AM_V_GEN)mkdir -p $(MAIN_CLASSES)
+ifeq ($(shell java -version 2>&1 | grep 1.7.0 > /dev/null; printf $$?), 0)
+ $(AM_V_at)javac $(JAVAC_ARGS) -d $(MAIN_CLASSES)\
+ $(MAIN_SRC)/org/rocksdb/util/*.java\
+ $(MAIN_SRC)/org/rocksdb/*.java
+else
+ $(AM_V_at)javac $(JAVAC_ARGS) -h $(NATIVE_INCLUDE) -d $(MAIN_CLASSES)\
+ $(MAIN_SRC)/org/rocksdb/util/*.java\
+ $(MAIN_SRC)/org/rocksdb/*.java
+endif
+ $(AM_V_at)@cp ../HISTORY.md ./HISTORY-CPP.md
+ $(AM_V_at)@rm -f ./HISTORY-CPP.md
+ifeq ($(shell java -version 2>&1 | grep 1.7.0 > /dev/null; printf $$?), 0)
+ $(AM_V_at)javah -cp $(MAIN_CLASSES) -d $(NATIVE_INCLUDE) -jni $(NATIVE_JAVA_CLASSES)
+endif
+
+sample: java
+ $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES)
+ $(AM_V_at)javac $(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 $(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 $(JAVAC_ARGS) -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/RocksDBColumnFamilySample.java
+ $(AM_V_at)@rm -rf /tmp/rocksdbjni
+ java $(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 -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/TransactionSample.java
+ $(AM_V_at)@rm -rf /tmp/rocksdbjni
+ java -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 -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/OptimisticTransactionSample.java
+ $(AM_V_at)@rm -rf /tmp/rocksdbjni
+ java -ea -Xcheck:jni -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) OptimisticTransactionSample /tmp/rocksdbjni
+ $(AM_V_at)@rm -rf /tmp/rocksdbjni
+
+resolve_test_deps:
+ test -d "$(JAVA_TEST_LIBDIR)" || mkdir -p "$(JAVA_TEST_LIBDIR)"
+ test -s "$(JAVA_JUNIT_JAR)" || cp $(MVN_LOCAL)/junit/junit/4.12/junit-4.12.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output $(JAVA_JUNIT_JAR) --location $(DEPS_URL)/junit-4.12.jar
+ test -s "$(JAVA_HAMCR_JAR)" || cp $(MVN_LOCAL)/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output $(JAVA_HAMCR_JAR) --location $(DEPS_URL)/hamcrest-core-1.3.jar
+ test -s "$(JAVA_MOCKITO_JAR)" || cp $(MVN_LOCAL)/org/mockito/mockito-all/1.10.19/mockito-all-1.10.19.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_MOCKITO_JAR)" --location $(DEPS_URL)/mockito-all-1.10.19.jar
+ test -s "$(JAVA_CGLIB_JAR)" || cp $(MVN_LOCAL)/cglib/cglib/2.2.2/cglib-2.2.2.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_CGLIB_JAR)" --location $(DEPS_URL)/cglib-2.2.2.jar
+ test -s "$(JAVA_ASSERTJ_JAR)" || cp $(MVN_LOCAL)/org/assertj/assertj-core/1.7.1/assertj-core-1.7.1.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_ASSERTJ_JAR)" --location $(DEPS_URL)/assertj-core-1.7.1.jar
+
+java_test: java resolve_test_deps
+ $(AM_V_GEN)mkdir -p $(TEST_CLASSES)
+ifeq ($(shell java -version 2>&1|grep 1.7.0 >/dev/null; printf $$?),0)
+ $(AM_V_at)javac $(JAVAC_ARGS) -cp $(MAIN_CLASSES):$(JAVA_TESTCLASSPATH) -d $(TEST_CLASSES)\
+ $(TEST_SRC)/org/rocksdb/test/*.java\
+ $(TEST_SRC)/org/rocksdb/util/*.java\
+ $(TEST_SRC)/org/rocksdb/*.java
+ $(AM_V_at)javah -cp $(MAIN_CLASSES):$(TEST_CLASSES) -d $(NATIVE_INCLUDE) -jni $(NATIVE_JAVA_TEST_CLASSES)
+else
+ $(AM_V_at)javac $(JAVAC_ARGS) -cp $(MAIN_CLASSES):$(JAVA_TESTCLASSPATH) -h $(NATIVE_INCLUDE) -d $(TEST_CLASSES)\
+ $(TEST_SRC)/org/rocksdb/test/*.java\
+ $(TEST_SRC)/org/rocksdb/util/*.java\
+ $(TEST_SRC)/org/rocksdb/*.java
+endif
+
+test: java java_test run_test
+
+run_test:
+ java $(JAVA_ARGS) -Djava.library.path=target -cp "$(MAIN_CLASSES):$(TEST_CLASSES):$(JAVA_TESTCLASSPATH):target/*" org.rocksdb.test.RocksJunitRunner $(JAVA_TESTS)
+
+db_bench: java
+ $(AM_V_GEN)mkdir -p $(BENCHMARK_MAIN_CLASSES)
+ $(AM_V_at)javac $(JAVAC_ARGS) -cp $(MAIN_CLASSES) -d $(BENCHMARK_MAIN_CLASSES) $(BENCHMARK_MAIN_SRC)/org/rocksdb/benchmark/*.java
diff --git a/storage/rocksdb/rocksdb/java/RELEASE.md b/storage/rocksdb/rocksdb/java/RELEASE.md
new file mode 100644
index 00000000..dda19455
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/RELEASE.md
@@ -0,0 +1,59 @@
+## 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:
+
+ <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+ <servers>
+ <server>
+ <id>sonatype-nexus-staging</id>
+ <username>your-sonatype-jira-username</username>
+ <password>your-sonatype-jira-password</password>
+ </server>
+ </servers>
+ </settings>
+
+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/storage/rocksdb/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java b/storage/rocksdb/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java
new file mode 100644
index 00000000..ff36c74a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java
@@ -0,0 +1,1653 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<Stats> {
+ // 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<Flag, Object> flags) throws Exception {
+ benchmarks_ = (List<String>) 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.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.setHdfs((String)flags_.get(Flag.hdfs)); // env
+ 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<Callable<Stats>> tasks = new ArrayList<Callable<Stats>>();
+ List<Callable<Stats>> bgTasks = new ArrayList<Callable<Stats>>();
+ 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<Future<Stats>> bgResults = new ArrayList<Future<Stats>>();
+ for (Callable bgTask : bgTasks) {
+ bgResults.add(bgExecutor.submit(bgTask));
+ }
+ start();
+ List<Future<Stats>> 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<Future<Stats>> 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<Stats> 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<Flag, Object> flags = new EnumMap<Flag, Object>(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<String>(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);
+ }
+ },
+ 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);
+ }
+ },
+ soft_rate_limit(0.0d,"") {
+ @Override public Object parseValue(String value) {
+ return Double.parseDouble(value);
+ }
+ },
+ hard_rate_limit(0.0d,"When not equal to 0 this make threads\n" +
+ "\tsleep at each stats reporting interval until the compaction\n" +
+ "\tscore for all levels is less than or equal to this value.") {
+ @Override public Object parseValue(String value) {
+ return Double.parseDouble(value);
+ }
+ },
+ rate_limit_delay_max_milliseconds(1000,
+ "When hard_rate_limit is set then this is the max time a put will\n" +
+ "\tbe stalled.") {
+ @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<AbstractComparator> clsComparator =
+ (Class<AbstractComparator>)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<String> 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<Flag, Object> 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/storage/rocksdb/rocksdb/java/crossbuild/Vagrantfile b/storage/rocksdb/rocksdb/java/crossbuild/Vagrantfile
new file mode 100644
index 00000000..0ee50de2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/Vagrantfile
@@ -0,0 +1,51 @@
+# -*- 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/storage/rocksdb/rocksdb/java/crossbuild/build-linux-alpine.sh b/storage/rocksdb/rocksdb/java/crossbuild/build-linux-alpine.sh
new file mode 100755
index 00000000..561d3414
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/build-linux-alpine.sh
@@ -0,0 +1,70 @@
+#!/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/storage/rocksdb/rocksdb/java/crossbuild/build-linux-centos.sh b/storage/rocksdb/rocksdb/java/crossbuild/build-linux-centos.sh
new file mode 100755
index 00000000..176e3456
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/build-linux-centos.sh
@@ -0,0 +1,38 @@
+#!/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/storage/rocksdb/rocksdb/java/crossbuild/build-linux.sh b/storage/rocksdb/rocksdb/java/crossbuild/build-linux.sh
new file mode 100755
index 00000000..74178adb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/build-linux.sh
@@ -0,0 +1,15 @@
+#!/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/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-alpine.sh b/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-alpine.sh
new file mode 100755
index 00000000..e605c771
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-alpine.sh
@@ -0,0 +1,18 @@
+#!/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 rocksdbjavastatic
+
+cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar /rocksdb-java-target
+
diff --git a/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-centos.sh b/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-centos.sh
new file mode 100755
index 00000000..c4217785
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/crossbuild/docker-build-linux-centos.sh
@@ -0,0 +1,34 @@
+#!/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-7'; then
+ # CentOS 7+
+ 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 /rocksdb-java-target
+
diff --git a/storage/rocksdb/rocksdb/java/jdb_bench.sh b/storage/rocksdb/rocksdb/java/jdb_bench.sh
new file mode 100755
index 00000000..5dfc385e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jdb_bench.sh
@@ -0,0 +1,13 @@
+# 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/storage/rocksdb/rocksdb/java/jmh/LICENSE-HEADER.txt b/storage/rocksdb/rocksdb/java/jmh/LICENSE-HEADER.txt
new file mode 100644
index 00000000..365ee653
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/LICENSE-HEADER.txt
@@ -0,0 +1,5 @@
+Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ This source code is licensed under both the GPLv2 (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/storage/rocksdb/rocksdb/java/jmh/README.md b/storage/rocksdb/rocksdb/java/jmh/README.md
new file mode 100644
index 00000000..f1ed0c68
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/README.md
@@ -0,0 +1,18 @@
+# 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 `<version>` 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.
+
+```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/storage/rocksdb/rocksdb/java/jmh/pom.xml b/storage/rocksdb/rocksdb/java/jmh/pom.xml
new file mode 100644
index 00000000..62671091
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/pom.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni-jmh</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <url>http://rocksdb.org/</url>
+
+ <name>rocksdbjni-jmh</name>
+ <description>JMH Benchmarks for RocksDB Java API</description>
+
+ <organization>
+ <name>Facebook, Inc.</name>
+ <url>https://www.facebook.com</url>
+ </organization>
+
+ <licenses>
+ <license>
+ <name>Apache License 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ <license>
+ <name>GNU General Public License, version 2</name>
+ <url>http://www.gnu.org/licenses/gpl-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <scm>
+ <connection>scm:git:git://github.com/facebook/rocksdb.git</connection>
+ <developerConnection>scm:git:git@github.com:facebook/rocksdb.git</developerConnection>
+ <url>http://github.com/facebook/rocksdb/</url>
+ </scm>
+
+ <properties>
+ <project.build.source>1.7</project.build.source>
+ <project.build.target>1.7</project.build.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <jmh.version>1.22</jmh.version>
+ <uberjar.name>benchmarks</uberjar.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni</artifactId>
+ <version>6.6.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <source>${project.build.source}</source>
+ <target>${project.build.target}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <version>3.0</version>
+ <inherited>true</inherited>
+ <configuration>
+ <header>LICENSE-HEADER.txt</header>
+ <failIfMissing>true</failIfMissing>
+ <aggregate>true</aggregate>
+ <strictCheck>true</strictCheck>
+ <excludes>
+ <exclude>pom.xml</exclude>
+ </excludes>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <finalName>${project.artifactId}-${project.version}-${uberjar.name}</finalName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.openjdk.jmh.Main</mainClass>
+ </transformer>
+ </transformers>
+ <filters>
+ <filter>
+ <!--
+ Shading signed JARs will fail without this.
+ http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
+ -->
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project> \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java
new file mode 100644
index 00000000..1973b548
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java
new file mode 100644
index 00000000..e34005c2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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 GetBenchmarks {
+
+ @Param({
+ "no_column_family",
+ "1_column_family",
+ "20_column_families",
+ "100_column_families"
+ })
+ String columnFamilyTestType;
+
+ @Param("100000")
+ int keyCount;
+
+ 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-get-benchmarks");
+
+ options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+
+ final List<ColumnFamilyDescriptor> 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<ColumnFamilyHandle> 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++) {
+ db.put(cfHandles[i], ba("key" + j), ba("value" + j));
+ }
+ }
+
+ 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];
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ @Benchmark
+ public byte[] get() throws RocksDBException {
+ final int keyIdx = next();
+ return db.get(getColumnFamily(), ba("key" + keyIdx));
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java
new file mode 100644
index 00000000..60a0de87
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java
@@ -0,0 +1,158 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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;
+import static org.rocksdb.util.KVUtils.keys;
+
+@State(Scope.Benchmark)
+public class MultiGetBenchmarks {
+
+ @Param({
+ "no_column_family",
+ "1_column_family",
+ "20_column_families",
+ "100_column_families"
+ })
+ String columnFamilyTestType;
+
+ @Param("100000")
+ int keyCount;
+
+ @Param({
+ "10",
+ "100",
+ "1000",
+ "10000",
+ })
+ int multiGetSize;
+
+ 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<ColumnFamilyDescriptor> 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<ColumnFamilyHandle> 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++) {
+ db.put(cfHandles[i], ba("key" + j), ba("value" + j));
+ }
+ }
+
+ 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;
+ }
+ }
+
+ @Benchmark
+ public List<byte[]> multiGet10() throws RocksDBException {
+ final int fromKeyIdx = next(multiGetSize, keyCount);
+ final List<byte[]> keys = keys(fromKeyIdx, fromKeyIdx + multiGetSize);
+ return db.multiGetAsList(keys);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java
new file mode 100644
index 00000000..5aae21cb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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<ColumnFamilyDescriptor> 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<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java
new file mode 100644
index 00000000..63744a14
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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<Path> 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<Path> {
+ @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/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java
new file mode 100644
index 00000000..848de5d8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * 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.util.ArrayList;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+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<byte[]> keys(final int from, final int to) {
+ final List<byte[]> keys = new ArrayList<>(to - from);
+ for (int i = from; i < to; i++) {
+ keys.add(ba("key" + i));
+ }
+ return keys;
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni.pom b/storage/rocksdb/rocksdb/java/rocksjni.pom
new file mode 100644
index 00000000..5defdca7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni.pom
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <name>RocksDB JNI</name>
+ <url>http://rocksdb.org/</url>
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni</artifactId>
+ <!-- Version will be automatically replaced -->
+ <version>-</version>
+ <description>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.
+ </description>
+ <licenses>
+ <license>
+ <name>Apache License 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ <license>
+ <name>GNU General Public License, version 2</name>
+ <url>http://www.gnu.org/licenses/gpl-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:git:git://github.com/dropwizard/metrics.git</connection>
+ <developerConnection>scm:git:git@github.com:dropwizard/metrics.git</developerConnection>
+ <url>http://github.com/dropwizard/metrics/</url>
+ <tag>HEAD</tag>
+ </scm>
+ <developers>
+ <developer>
+ <name>Facebook</name>
+ <email>help@facebook.com</email>
+ <timezone>America/New_York</timezone>
+ <roles>
+ <role>architect</role>
+ </roles>
+ </developer>
+ </developers>
+
+ <properties>
+ <project.build.source>1.7</project.build.source>
+ <project.build.target>1.7</project.build.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.2</version>
+ <configuration>
+ <source>${project.build.source}</source>
+ <target>${project.build.target}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.18.1</version>
+ <configuration>
+ <argLine>${argLine} -ea -Xcheck:jni -Djava.library.path=${project.build.directory}</argLine>
+ <useManifestOnlyJar>false</useManifestOnlyJar>
+ <useSystemClassLoader>false</useSystemClassLoader>
+ <additionalClasspathElements>
+ <additionalClasspathElement>${project.build.directory}/*</additionalClasspathElement>
+ </additionalClasspathElements>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.7.2.201409121644</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>report</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmaven</groupId>
+ <artifactId>groovy-maven-plugin</artifactId>
+ <version>2.0</version>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <defaults>
+ <name>Xenu</name>
+ </defaults>
+ <source>
+ 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
+ </source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>1.7.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/backupablejni.cc b/storage/rocksdb/rocksdb/java/rocksjni/backupablejni.cc
new file mode 100644
index 00000000..3e52305d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/backupablejni.cc
@@ -0,0 +1,363 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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::BackupableDBOptions methods from Java side.
+
+#include <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#include "include/org_rocksdb_BackupableDBOptions.h"
+#include "rocksdb/utilities/backupable_db.h"
+#include "rocksjni/portal.h"
+
+///////////////////////////////////////////////////////////////////////////
+// BackupDBOptions
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: newBackupableDBOptions
+ * Signature: (Ljava/lang/String;)J
+ */
+jlong Java_org_rocksdb_BackupableDBOptions_newBackupableDBOptions(
+ 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::BackupableDBOptions(cpath);
+ env->ReleaseStringUTFChars(jpath, cpath);
+ return reinterpret_cast<jlong>(bopt);
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: backupDir
+ * Signature: (J)Ljava/lang/String;
+ */
+jstring Java_org_rocksdb_BackupableDBOptions_backupDir(JNIEnv* env,
+ jobject /*jopt*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return env->NewStringUTF(bopt->backup_dir.c_str());
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setBackupEnv
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setBackupEnv(
+ JNIEnv* /*env*/, jobject /*jopt*/, jlong jhandle, jlong jrocks_env_handle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ auto* rocks_env =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(jrocks_env_handle);
+ bopt->backup_env = rocks_env;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setShareTableFiles
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setShareTableFiles(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle,
+ jboolean flag) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->share_table_files = flag;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: shareTableFiles
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_BackupableDBOptions_shareTableFiles(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->share_table_files;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setInfoLog
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setInfoLog(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle,
+ jlong /*jlogger_handle*/) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ auto* sptr_logger =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jhandle);
+ bopt->info_log = sptr_logger->get();
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setSync
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setSync(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle,
+ jboolean flag) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->sync = flag;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: sync
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_BackupableDBOptions_sync(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->sync;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setDestroyOldData
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setDestroyOldData(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle,
+ jboolean flag) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->destroy_old_data = flag;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: destroyOldData
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_BackupableDBOptions_destroyOldData(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->destroy_old_data;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setBackupLogFiles
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setBackupLogFiles(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle,
+ jboolean flag) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->backup_log_files = flag;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: backupLogFiles
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_BackupableDBOptions_backupLogFiles(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->backup_log_files;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setBackupRateLimit
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimit(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jlong jbackup_rate_limit) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->backup_rate_limit = jbackup_rate_limit;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: backupRateLimit
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_BackupableDBOptions_backupRateLimit(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->backup_rate_limit;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setBackupRateLimiter
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimiter(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jlong jrate_limiter_handle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ auto* sptr_rate_limiter =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ jrate_limiter_handle);
+ bopt->backup_rate_limiter = *sptr_rate_limiter;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setRestoreRateLimit
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimit(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jlong jrestore_rate_limit) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->restore_rate_limit = jrestore_rate_limit;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: restoreRateLimit
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_BackupableDBOptions_restoreRateLimit(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->restore_rate_limit;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setRestoreRateLimiter
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimiter(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jlong jrate_limiter_handle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ auto* sptr_rate_limiter =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ jrate_limiter_handle);
+ bopt->restore_rate_limiter = *sptr_rate_limiter;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setShareFilesWithChecksum
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setShareFilesWithChecksum(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean flag) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->share_files_with_checksum = flag;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: shareFilesWithChecksum
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_BackupableDBOptions_shareFilesWithChecksum(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return bopt->share_files_with_checksum;
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setMaxBackgroundOperations
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setMaxBackgroundOperations(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jint max_background_operations) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->max_background_operations = static_cast<int>(max_background_operations);
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: maxBackgroundOperations
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_BackupableDBOptions_maxBackgroundOperations(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return static_cast<jint>(bopt->max_background_operations);
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: setCallbackTriggerIntervalSize
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_setCallbackTriggerIntervalSize(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
+ jlong jcallback_trigger_interval_size) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ bopt->callback_trigger_interval_size =
+ static_cast<uint64_t>(jcallback_trigger_interval_size);
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: callbackTriggerIntervalSize
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_BackupableDBOptions_callbackTriggerIntervalSize(
+ JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ return static_cast<jlong>(bopt->callback_trigger_interval_size);
+}
+
+/*
+ * Class: org_rocksdb_BackupableDBOptions
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_BackupableDBOptions_disposeInternal(JNIEnv* /*env*/,
+ jobject /*jopt*/,
+ jlong jhandle) {
+ auto* bopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(jhandle);
+ assert(bopt != nullptr);
+ delete bopt;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/backupenginejni.cc b/storage/rocksdb/rocksdb/java/rocksjni/backupenginejni.cc
new file mode 100644
index 00000000..76889fa8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/backupenginejni.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <vector>
+
+#include "include/org_rocksdb_BackupEngine.h"
+#include "rocksdb/utilities/backupable_db.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 backupable_db_options_handle) {
+ auto* rocks_env = reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(env_handle);
+ auto* backupable_db_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupableDBOptions*>(
+ backupable_db_options_handle);
+ ROCKSDB_NAMESPACE::BackupEngine* backup_engine;
+ auto status = ROCKSDB_NAMESPACE::BackupEngine::Open(
+ rocks_env, *backupable_db_options, &backup_engine);
+
+ if (status.ok()) {
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto* backup_engine =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ auto status = backup_engine->CreateNewBackup(
+ db, static_cast<bool>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto* backup_engine =
+ reinterpret_cast<ROCKSDB_NAMESPACE::BackupEngine*>(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<bool>(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<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ std::vector<ROCKSDB_NAMESPACE::BackupInfo> 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<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ std::vector<ROCKSDB_NAMESPACE::BackupID> backup_ids;
+ backup_engine->GetCorruptedBackups(&backup_ids);
+ // store backupids in int array
+ std::vector<jint> 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<jsize>(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<ROCKSDB_NAMESPACE::BackupEngine*>(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<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ auto status = backup_engine->PurgeOldBackups(
+ static_cast<uint32_t>(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<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ auto status = backup_engine->DeleteBackup(
+ static_cast<ROCKSDB_NAMESPACE::BackupID>(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<ROCKSDB_NAMESPACE::BackupEngine*>(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<ROCKSDB_NAMESPACE::RestoreOptions*>(
+ jrestore_options_handle);
+ auto status = backup_engine->RestoreDBFromBackup(
+ static_cast<ROCKSDB_NAMESPACE::BackupID>(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<ROCKSDB_NAMESPACE::BackupEngine*>(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<ROCKSDB_NAMESPACE::RestoreOptions*>(
+ 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<ROCKSDB_NAMESPACE::BackupEngine*>(jbe_handle);
+ assert(be != nullptr);
+ delete be;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc
new file mode 100644
index 00000000..10d482ba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CassandraCompactionFilter.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 reinterpret_cast<jlong>(compaction_filter);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/cassandra_value_operator.cc b/storage/rocksdb/rocksdb/java/rocksjni/cassandra_value_operator.cc
new file mode 100644
index 00000000..9bd31b9f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/cassandra_value_operator.cc
@@ -0,0 +1,48 @@
+// 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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory>
+#include <string>
+
+#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/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<ROCKSDB_NAMESPACE::MergeOperator>(
+ new ROCKSDB_NAMESPACE::cassandra::CassandraValueMergeOperator(
+ gcGracePeriodInSeconds, operands_limit));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>*>(
+ jhandle);
+ delete op;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/checkpoint.cc b/storage/rocksdb/rocksdb/java/rocksjni/checkpoint.cc
new file mode 100644
index 00000000..b04846e8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/checkpoint.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "include/org_rocksdb_Checkpoint.h"
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/checkpoint.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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::Checkpoint* checkpoint;
+ ROCKSDB_NAMESPACE::Checkpoint::Create(db, &checkpoint);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Checkpoint*>(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<ROCKSDB_NAMESPACE::Checkpoint*>(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/storage/rocksdb/rocksdb/java/rocksjni/clock_cache.cc b/storage/rocksdb/rocksdb/java/rocksjni/clock_cache.cc
new file mode 100644
index 00000000..56ddcfce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/clock_cache.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "cache/clock_cache.h"
+#include "include/org_rocksdb_ClockCache.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::Cache>(
+ ROCKSDB_NAMESPACE::NewClockCache(
+ static_cast<size_t>(jcapacity), static_cast<int>(jnum_shard_bits),
+ static_cast<bool>(jstrict_capacity_limit)));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::Cache>*>(jhandle);
+ delete sptr_clock_cache; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/columnfamilyhandle.cc b/storage/rocksdb/rocksdb/java/rocksjni/columnfamilyhandle.cc
new file mode 100644
index 00000000..4140580f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/columnfamilyhandle.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jhandle);
+ const int32_t id = cfh->GetID();
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jhandle);
+ assert(cfh != nullptr);
+ delete cfh;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compact_range_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/compact_range_options.cc
new file mode 100644
index 00000000..af5a200e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compact_range_options.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactRangeOptions.h"
+#include "rocksdb/options.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->exclusive_manual_compaction = static_cast<bool>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->change_level = static_cast<bool>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->target_level = static_cast<int>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->target_path_id = static_cast<uint32_t>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->allow_write_stall = static_cast<bool>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ options->max_subcompactions = static_cast<uint32_t>(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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(jhandle);
+ delete options;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter.cc
new file mode 100644
index 00000000..c3a68cdf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_AbstractCompactionFilter.h"
+#include "rocksdb/compaction_filter.h"
+
+// <editor-fold desc="org.rocksdb.AbstractCompactionFilter">
+
+/*
+ * 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<ROCKSDB_NAMESPACE::CompactionFilter*>(handle);
+ assert(cf != nullptr);
+ delete cf;
+}
+// </editor-fold>
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory.cc
new file mode 100644
index 00000000..3b7c462c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+
+#include "include/org_rocksdb_AbstractCompactionFilterFactory.h"
+#include "rocksjni/compaction_filter_factory_jnicallback.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback>*>(
+ jhandle);
+ delete ptr_sptr_cff;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc
new file mode 100644
index 00000000..cacbf02c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<CompactionFilter> 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<jboolean>(context.is_full_compaction),
+ static_cast<jboolean>(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<CompactionFilter*>(addr_compaction_filter);
+
+ releaseJniEnv(attached_thread);
+
+ return std::unique_ptr<CompactionFilter>(cff);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h
new file mode 100644
index 00000000..eb2d5111
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+
+#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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context);
+ virtual const char* Name() const;
+
+ private:
+ std::unique_ptr<const char[]> m_name;
+ jmethodID m_jcreate_compaction_filter_methodid;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_info.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_info.cc
new file mode 100644
index 00000000..245ff7e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_info.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactionJobInfo.h"
+#include "rocksdb/listener.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ auto* map = &compact_job_info->table_properties;
+
+ jobject jhash_map = ROCKSDB_NAMESPACE::HashMapJni::construct(
+ env, static_cast<uint32_t>(map->size()));
+ if (jhash_map == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
+ const std::string,
+ std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties>, jobject,
+ jobject>
+ fn_map_kv =
+ [env](const std::pair<
+ const std::string,
+ std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties>>&
+ kv) {
+ jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.first), false);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(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<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(static_cast<jobject>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(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<ROCKSDB_NAMESPACE::CompactionJobInfo*>(jhandle);
+ auto* stats = new ROCKSDB_NAMESPACE::CompactionJobStats();
+ stats->Add(compact_job_info->stats);
+ return reinterpret_cast<jlong>(stats);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_stats.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_stats.cc
new file mode 100644
index 00000000..efaec69e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_job_stats.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactionJobStats.h"
+#include "rocksdb/compaction_job_stats.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ auto* other_compact_job_stats =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionJobStats*>(jhandle);
+ return static_cast<jlong>(
+ compact_job_stats->num_single_del_mismatch);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options.cc
new file mode 100644
index 00000000..e904d4ab
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactionOptions.h"
+#include "rocksdb/options.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionOptions*>(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<ROCKSDB_NAMESPACE::CompactionOptions*>(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<ROCKSDB_NAMESPACE::CompactionOptions*>(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<ROCKSDB_NAMESPACE::CompactionOptions*>(jhandle);
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::CompactionOptions*>(jhandle);
+ compact_opts->output_file_size_limit =
+ static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::CompactionOptions*>(jhandle);
+ return static_cast<jint>(
+ 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<ROCKSDB_NAMESPACE::CompactionOptions*>(jhandle);
+ compact_opts->max_subcompactions =
+ static_cast<uint32_t>(jmax_subcompactions);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_fifo.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_fifo.cc
new file mode 100644
index 00000000..08993524
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_fifo.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactionOptionsFIFO.h"
+#include "rocksdb/advanced_options.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(jhandle);
+ opt->max_table_files_size = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(jhandle);
+ opt->allow_compaction = static_cast<bool>(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<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_universal.cc b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_universal.cc
new file mode 100644
index 00000000..899ee6d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compaction_options_universal.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompactionOptionsUniversal.h"
+#include "rocksdb/advanced_options.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->size_ratio = static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->min_merge_width = static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->max_merge_width = static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->max_size_amplification_percent =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->compression_size_percent =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(jhandle);
+ opt->allow_trivial_move = static_cast<bool>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(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<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(
+ jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/comparator.cc b/storage/rocksdb/rocksdb/java/rocksjni/comparator.cc
new file mode 100644
index 00000000..485d18f0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/comparator.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <functional>
+#include <string>
+
+#include "include/org_rocksdb_AbstractComparator.h"
+#include "include/org_rocksdb_NativeComparatorWrapper.h"
+#include "rocksjni/comparatorjnicallback.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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ copt_handle);
+ auto* c =
+ new ROCKSDB_NAMESPACE::ComparatorJniCallback(env, jcomparator, copt);
+ return reinterpret_cast<jlong>(c);
+}
+
+/*
+ * Class: org_rocksdb_AbstractComparator
+ * Method: usingDirectBuffers
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_AbstractComparator_usingDirectBuffers(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* c =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ComparatorJniCallback*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Comparator*>(jcomparator_handle);
+ delete comparator;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.cc
new file mode 100644
index 00000000..248b15d3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.cc
@@ -0,0 +1,638 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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(options) {
+
+ // cache the AbstractComparatorJniBridge class as we will reuse it many times for each callback
+ m_abstract_comparator_jni_bridge_clazz =
+ static_cast<jclass>(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<jclass>(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<ThreadLocalBuf*>(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<char*>(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<port::Mutex>(new port::Mutex(adaptive));
+ mtx_shortest = std::unique_ptr<port::Mutex>(new port::Mutex(adaptive));
+ mtx_short = std::unique_ptr<port::Mutex>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<int64_t>(a.size()) <= m_options->max_reused_buffer_size;
+ const bool reuse_jbuf_b =
+ static_cast<int64_t>(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<int64_t>(start->length()) <= m_options->max_reused_buffer_size;
+ const bool reuse_jbuf_limit =
+ static_cast<int64_t>(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<size_t>(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<const char*>(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<std::string>(env, jarray, [start, jstart_len](const char* data, const size_t) {
+ return start->assign(data, static_cast<size_t>(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<int64_t>(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<size_t>(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<const char*>(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<std::string>(env, jarray, [key, jkey_len](const char* data, const size_t) {
+ return key->assign(data, static_cast<size_t>(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<port::Mutex>& 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<port::Mutex>& 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<ThreadLocalBuf*>(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<jsize>(src.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.h
new file mode 100644
index 00000000..2e27de00
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/comparatorjnicallback.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+#include <string>
+#include "rocksjni/jnicallback.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/slice.h"
+#include "port/port.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.
+ // Default: ADAPTIVE_MUTEX
+ ReusedSynchronisationType reused_synchronisation_type =
+ ReusedSynchronisationType::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[].
+ // Default: true
+ 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.
+ // Default: 64 bytes
+ 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 ComparatorJniCallbackOptions* 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<port::Mutex>& mutex,
+ const bool cond) const;
+ inline void MaybeUnlockForReuse(const std::unique_ptr<port::Mutex>& 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<port::Mutex> mtx_compare;
+ // used for synchronisation in findShortestSeparator method
+ std::unique_ptr<port::Mutex> mtx_shortest;
+ // used for synchronisation in findShortSuccessor method
+ std::unique_ptr<port::Mutex> mtx_short;
+ std::unique_ptr<const char[]> 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/storage/rocksdb/rocksdb/java/rocksjni/compression_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/compression_options.cc
new file mode 100644
index 00000000..4fed5ba5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/compression_options.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_CompressionOptions.h"
+#include "rocksdb/advanced_options.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ opt->window_bits = static_cast<int>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ opt->level = static_cast<int>(jlevel);
+}
+
+/*
+ * Class: org_rocksdb_CompressionOptions
+ * Method: level
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_CompressionOptions_level(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ opt->strategy = static_cast<int>(jstrategy);
+}
+
+/*
+ * Class: org_rocksdb_CompressionOptions
+ * Method: strategy
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_CompressionOptions_strategy(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ opt->max_dict_bytes = static_cast<uint32_t>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ opt->zstd_max_train_bytes = static_cast<uint32_t>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<jint>(opt->zstd_max_train_bytes);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::CompressionOptions*>(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<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+ return static_cast<bool>(opt->enabled);
+}
+/*
+ * Class: org_rocksdb_CompressionOptions
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_CompressionOptions_disposeInternal(
+ JNIEnv*, jobject, jlong jhandle) {
+ delete reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/env.cc b/storage/rocksdb/rocksdb/java/rocksjni/env.cc
new file mode 100644
index 00000000..f450c560
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/env.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <vector>
+
+#include "portal.h"
+#include "rocksdb/env.h"
+#include "include/org_rocksdb_Env.h"
+#include "include/org_rocksdb_HdfsEnv.h"
+#include "include/org_rocksdb_RocksEnv.h"
+#include "include/org_rocksdb_RocksMemEnv.h"
+#include "include/org_rocksdb_TimedEnv.h"
+
+/*
+ * Class: org_rocksdb_Env
+ * Method: getDefaultEnvInternal
+ * Signature: ()J
+ */
+jlong Java_org_rocksdb_Env_getDefaultEnvInternal(
+ JNIEnv*, jclass) {
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ rocks_env->SetBackgroundThreads(
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ const int num = rocks_env->GetBackgroundThreads(
+ ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value));
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ const int queue_len = rocks_env->GetThreadPoolQueueLen(
+ ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value));
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ rocks_env->IncBackgroundThreadsIfNeeded(
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ std::vector<ROCKSDB_NAMESPACE::ThreadStatus> 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<jsize>(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<ROCKSDB_NAMESPACE::Env*>(jbase_env_handle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ assert(e != nullptr);
+ delete e;
+}
+
+/*
+ * Class: org_rocksdb_HdfsEnv
+ * Method: createHdfsEnv
+ * Signature: (Ljava/lang/String;)J
+ */
+jlong Java_org_rocksdb_HdfsEnv_createHdfsEnv(
+ JNIEnv* env, jclass, jstring jfsname) {
+ jboolean has_exception = JNI_FALSE;
+ auto fsname =
+ ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jfsname, &has_exception);
+ if (has_exception == JNI_TRUE) {
+ // exception occurred
+ return 0;
+ }
+ ROCKSDB_NAMESPACE::Env* hdfs_env;
+ ROCKSDB_NAMESPACE::Status s =
+ ROCKSDB_NAMESPACE::NewHdfsEnv(&hdfs_env, fsname);
+ if (!s.ok()) {
+ // error occurred
+ ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
+ return 0;
+ }
+ return reinterpret_cast<jlong>(hdfs_env);
+}
+
+/*
+ * Class: org_rocksdb_HdfsEnv
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_HdfsEnv_disposeInternal(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* e = reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::Env*>(jbase_env_handle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Env*>(jhandle);
+ assert(e != nullptr);
+ delete e;
+}
+
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/env_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/env_options.cc
new file mode 100644
index 00000000..2a9c8aeb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/env_options.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_EnvOptions.h"
+#include "rocksdb/env.h"
+
+#define ENV_OPTIONS_SET_BOOL(_jhandle, _opt) \
+ reinterpret_cast<ROCKSDB_NAMESPACE::EnvOptions *>(_jhandle)->_opt = \
+ static_cast<bool>(_opt)
+
+#define ENV_OPTIONS_SET_SIZE_T(_jhandle, _opt) \
+ reinterpret_cast<ROCKSDB_NAMESPACE::EnvOptions *>(_jhandle)->_opt = \
+ static_cast<size_t>(_opt)
+
+#define ENV_OPTIONS_SET_UINT64_T(_jhandle, _opt) \
+ reinterpret_cast<ROCKSDB_NAMESPACE::EnvOptions *>(_jhandle)->_opt = \
+ static_cast<uint64_t>(_opt)
+
+#define ENV_OPTIONS_GET(_jhandle, _opt) \
+ reinterpret_cast<ROCKSDB_NAMESPACE::EnvOptions *>(_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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions *>(jdboptions_handle);
+ auto *env_opt = new ROCKSDB_NAMESPACE::EnvOptions(*db_options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::EnvOptions *>(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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter> *>(
+ rl_handle);
+ auto *env_opt = reinterpret_cast<ROCKSDB_NAMESPACE::EnvOptions *>(jhandle);
+ env_opt->rate_limiter = sptr_rate_limiter->get();
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/filter.cc b/storage/rocksdb/rocksdb/java/rocksjni/filter.cc
new file mode 100644
index 00000000..4143fc96
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/filter.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "include/org_rocksdb_BloomFilter.h"
+#include "include/org_rocksdb_Filter.h"
+#include "rocksdb/filter_policy.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,
+ jboolean use_block_base_builder) {
+ auto* sptr_filter =
+ new std::shared_ptr<const ROCKSDB_NAMESPACE::FilterPolicy>(
+ ROCKSDB_NAMESPACE::NewBloomFilterPolicy(bits_per_key,
+ use_block_base_builder));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<const ROCKSDB_NAMESPACE::FilterPolicy>*>(
+ jhandle);
+ delete handle; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/ingest_external_file_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/ingest_external_file_options.cc
new file mode 100644
index 00000000..ceaa6b17
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/ingest_external_file_options.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_IngestExternalFileOptions.h"
+#include "rocksdb/options.h"
+
+/*
+ * Class: org_rocksdb_IngestExternalFileOptions
+ * Method: newIngestExternalFileOptions
+ * Signature: ()J
+ */
+jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__(
+ JNIEnv*, jclass) {
+ auto* options = new ROCKSDB_NAMESPACE::IngestExternalFileOptions();
+ return reinterpret_cast<jlong>(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<bool>(jmove_files);
+ options->snapshot_consistency = static_cast<bool>(jsnapshot_consistency);
+ options->allow_global_seqno = static_cast<bool>(jallow_global_seqno);
+ options->allow_blocking_flush = static_cast<bool>(jallow_blocking_flush);
+ return reinterpret_cast<jlong>(options);
+}
+
+/*
+ * Class: org_rocksdb_IngestExternalFileOptions
+ * Method: moveFiles
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_IngestExternalFileOptions_moveFiles(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ options->move_files = static_cast<bool>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ options->snapshot_consistency = static_cast<bool>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ options->allow_global_seqno = static_cast<bool>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ options->allow_blocking_flush = static_cast<bool>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(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<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(jhandle);
+ delete options;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/iterator.cc b/storage/rocksdb/rocksdb/java/rocksjni/iterator.cc
new file mode 100644
index 00000000..2935adc5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/iterator.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+
+#include "include/org_rocksdb_RocksIterator.h"
+#include "rocksdb/iterator.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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle)->Prev();
+}
+
+/*
+ * 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) {
+ jbyte* target = env->GetByteArrayElements(jtarget, nullptr);
+ if (target == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast<char*>(target),
+ jtarget_len);
+
+ auto* it = reinterpret_cast<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ it->Seek(target_slice);
+
+ env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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) {
+ jbyte* target = env->GetByteArrayElements(jtarget, nullptr);
+ if (target == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast<char*>(target),
+ jtarget_len);
+
+ auto* it = reinterpret_cast<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ it->SeekForPrev(target_slice);
+
+ env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice key_slice = it->key();
+
+ jbyteArray jkey = env->NewByteArray(static_cast<jsize>(key_slice.size()));
+ if (jkey == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ env->SetByteArrayRegion(
+ jkey, 0, static_cast<jsize>(key_slice.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice key_slice = it->key();
+ return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, key_slice, jtarget,
+ jtarget_off, jtarget_len);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice value_slice = it->value();
+
+ jbyteArray jkeyValue =
+ env->NewByteArray(static_cast<jsize>(value_slice.size()));
+ if (jkeyValue == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ env->SetByteArrayRegion(
+ jkeyValue, 0, static_cast<jsize>(value_slice.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice value_slice = it->value();
+ return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, value_slice, jtarget,
+ jtarget_off, jtarget_len);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.cc
new file mode 100644
index 00000000..40fb4514
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include "rocksjni/jnicallback.h"
+#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);
+}
+// @lint-ignore TXT4 T25377293 Grandfathered in
+} // namespace ROCKSDB_NAMESPACE \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.h
new file mode 100644
index 00000000..54264ead
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/jnicallback.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+class JniCallback {
+ public:
+ JniCallback(JNIEnv* env, jobject jcallback_obj);
+ virtual ~JniCallback();
+
+ protected:
+ JavaVM* m_jvm;
+ jobject m_jcallback_obj;
+ JNIEnv* getJniEnv(jboolean* attached) const;
+ void releaseJniEnv(jboolean& attached) const;
+ };
+ } // namespace ROCKSDB_NAMESPACE
+
+// @lint-ignore TXT4 T25377293 Grandfathered in
+#endif // JAVA_ROCKSJNI_JNICALLBACK_H_
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.cc
new file mode 100644
index 00000000..e9a9ce68
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "include/org_rocksdb_Logger.h"
+
+#include <cstdarg>
+#include <cstdio>
+#include "rocksjni/loggerjnicallback.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<char[]> 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<char[]> 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<char[]> 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<ROCKSDB_NAMESPACE::LoggerJniCallback>(
+ new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj));
+
+ // set log level
+ auto* options = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(joptions);
+ sptr_logger->get()->SetInfoLogLevel(options->info_log_level);
+
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::LoggerJniCallback>(
+ new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj));
+
+ // set log level
+ auto* db_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdb_options);
+ sptr_logger->get()->SetInfoLogLevel(db_options->info_log_level);
+
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jhandle);
+ handle->get()->SetInfoLogLevel(
+ static_cast<ROCKSDB_NAMESPACE::InfoLogLevel>(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<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jhandle);
+ return static_cast<jbyte>(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<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jhandle);
+ delete handle; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.h
new file mode 100644
index 00000000..7bcba82e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/loggerjnicallback.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+#include <string>
+#include "rocksjni/jnicallback.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class LoggerJniCallback : public JniCallback, public Logger {
+ public:
+ LoggerJniCallback(JNIEnv* env, jobject jLogger);
+ ~LoggerJniCallback();
+
+ using Logger::SetInfoLogLevel;
+ using Logger::GetInfoLogLevel;
+ // 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<char[]> format_str(const char* format, va_list ap) const;
+ };
+ } // namespace ROCKSDB_NAMESPACE
+
+#endif // JAVA_ROCKSJNI_LOGGERJNICALLBACK_H_
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/lru_cache.cc b/storage/rocksdb/rocksdb/java/rocksjni/lru_cache.cc
new file mode 100644
index 00000000..cfdcb525
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/lru_cache.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "cache/lru_cache.h"
+#include "include/org_rocksdb_LRUCache.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) {
+ auto* sptr_lru_cache = new std::shared_ptr<ROCKSDB_NAMESPACE::Cache>(
+ ROCKSDB_NAMESPACE::NewLRUCache(
+ static_cast<size_t>(jcapacity), static_cast<int>(jnum_shard_bits),
+ static_cast<bool>(jstrict_capacity_limit),
+ static_cast<double>(jhigh_pri_pool_ratio)));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::Cache>*>(jhandle);
+ delete sptr_lru_cache; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/memory_util.cc b/storage/rocksdb/rocksdb/java/rocksjni/memory_util.cc
new file mode 100644
index 00000000..fac288c9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/memory_util.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <map>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "include/org_rocksdb_MemoryUtil.h"
+
+#include "rocksjni/portal.h"
+
+#include "rocksdb/utilities/memory_util.h"
+
+
+/*
+ * Class: org_rocksdb_MemoryUtil
+ * Method: getApproximateMemoryUsageByType
+ * Signature: ([J[J)Ljava/util/Map;
+ */
+jobject Java_org_rocksdb_MemoryUtil_getApproximateMemoryUsageByType(
+ JNIEnv *env, jclass /*jclazz*/, jlongArray jdb_handles, jlongArray jcache_handles) {
+ std::vector<ROCKSDB_NAMESPACE::DB *> dbs;
+ jsize db_handle_count = env->GetArrayLength(jdb_handles);
+ if(db_handle_count > 0) {
+ jlong *ptr_jdb_handles = env->GetLongArrayElements(jdb_handles, nullptr);
+ if (ptr_jdb_handles == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ for (jsize i = 0; i < db_handle_count; i++) {
+ dbs.push_back(
+ reinterpret_cast<ROCKSDB_NAMESPACE::DB *>(ptr_jdb_handles[i]));
+ }
+ env->ReleaseLongArrayElements(jdb_handles, ptr_jdb_handles, JNI_ABORT);
+ }
+
+ std::unordered_set<const ROCKSDB_NAMESPACE::Cache *> 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<std::shared_ptr<ROCKSDB_NAMESPACE::Cache> *>(
+ ptr_jcache_handles[i]);
+ cache_set.insert(cache_ptr->get());
+ }
+ env->ReleaseLongArrayElements(jcache_handles, ptr_jcache_handles, JNI_ABORT);
+ }
+
+ std::map<ROCKSDB_NAMESPACE::MemoryUtil::UsageType, uint64_t> 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<uint32_t>(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<ROCKSDB_NAMESPACE::MemoryUtil::UsageType,
+ uint64_t> &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<std::pair<jobject, jobject>>(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<std::pair<jobject, jobject>>(nullptr);
+ }
+ // Construct and return pointer to pair of jobjects
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(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/storage/rocksdb/rocksdb/java/rocksjni/memtablejni.cc b/storage/rocksdb/rocksdb/java/rocksjni/memtablejni.cc
new file mode 100644
index 00000000..1188c5e5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/memtablejni.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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 reinterpret_cast<jlong>(ROCKSDB_NAMESPACE::NewHashSkipListRepFactory(
+ static_cast<size_t>(jbucket_count), static_cast<int32_t>(jheight),
+ static_cast<int32_t>(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 reinterpret_cast<jlong>(ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(
+ static_cast<size_t>(jbucket_count),
+ static_cast<size_t>(jhuge_page_tlb_size),
+ static_cast<int32_t>(jbucket_entries_logging_threshold),
+ static_cast<bool>(jif_log_bucket_dist_when_flash),
+ static_cast<int32_t>(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 reinterpret_cast<jlong>(new ROCKSDB_NAMESPACE::VectorRepFactory(
+ static_cast<size_t>(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 reinterpret_cast<jlong>(new ROCKSDB_NAMESPACE::SkipListFactory(
+ static_cast<size_t>(jlookahead)));
+ }
+ ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s);
+ return 0;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/merge_operator.cc b/storage/rocksdb/rocksdb/java/rocksjni/merge_operator.cc
new file mode 100644
index 00000000..edc3e723
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/merge_operator.cc
@@ -0,0 +1,81 @@
+// 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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory>
+#include <string>
+
+#include "include/org_rocksdb_StringAppendOperator.h"
+#include "include/org_rocksdb_UInt64AddOperator.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/portal.h"
+#include "utilities/merge_operators.h"
+
+/*
+ * Class: org_rocksdb_StringAppendOperator
+ * Method: newSharedStringAppendOperator
+ * Signature: (C)J
+ */
+jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator(
+ JNIEnv* /*env*/, jclass /*jclazz*/, jchar jdelim) {
+ auto* sptr_string_append_op =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>(
+ ROCKSDB_NAMESPACE::MergeOperators::CreateStringAppendOperator(
+ (char)jdelim));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>*>(
+ 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::MergeOperator>(
+ ROCKSDB_NAMESPACE::MergeOperators::CreateUInt64AddOperator());
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>*>(
+ jhandle);
+ delete sptr_uint64_add_op; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc b/storage/rocksdb/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc
new file mode 100644
index 00000000..d2f5c1bd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <string>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/slice.h"
+
+#include "include/org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper.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 reinterpret_cast<jlong>(comparator);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_db.cc b/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_db.cc
new file mode 100644
index 00000000..4f966cdd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_db.cc
@@ -0,0 +1,284 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_OptimisticTransactionDB.h"
+
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/transaction.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<ROCKSDB_NAMESPACE::Options*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> column_families;
+ const jsize len_cols = env->GetArrayLength(jcolumn_names);
+ if (len_cols > 0) {
+ if (env->EnsureLocalCapacity(len_cols) != 0) {
+ // out of memory
+ env->ReleaseStringUTFChars(jdb_path, db_path);
+ return nullptr;
+ }
+
+ 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<jbyteArray>(jcn);
+ const jsize jcf_name_len = env->GetArrayLength(jcn_ba);
+ if (env->EnsureLocalCapacity(jcf_name_len) != 0) {
+ // out of memory
+ env->DeleteLocalRef(jcn);
+ env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT);
+ env->ReleaseStringUTFChars(jdb_path, db_path);
+ return nullptr;
+ }
+
+ 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<char*>(jcf_name),
+ jcf_name_len);
+ const ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jdb_options_handle);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<jlong[]> results =
+ std::unique_ptr<jlong[]>(new jlong[resultsLen]);
+ results[0] = reinterpret_cast<jlong>(otdb);
+ for (int i = 1; i <= len_cols; i++) {
+ results[i] = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ ROCKSDB_NAMESPACE::Transaction* txn =
+ optimistic_txn_db->BeginTransaction(*write_options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* optimistic_txn_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ joptimistic_txn_options_handle);
+ ROCKSDB_NAMESPACE::Transaction* txn = optimistic_txn_db->BeginTransaction(
+ *write_options, *optimistic_txn_options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* old_txn =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* optimistic_txn_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ joptimistic_txn_options_handle);
+ auto* old_txn =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionDB*>(jhandle);
+ return reinterpret_cast<jlong>(optimistic_txn_db->GetBaseDB());
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_options.cc
new file mode 100644
index 00000000..6bc80fdf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/optimistic_transaction_options.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_OptimisticTransactionOptions.h"
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/utilities/optimistic_transaction_db.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ 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<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ 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<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ jhandle);
+ opts->cmp =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Comparator*>(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<ROCKSDB_NAMESPACE::OptimisticTransactionOptions*>(
+ jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/options.cc b/storage/rocksdb/rocksdb/java/rocksjni/options.cc
new file mode 100644
index 00000000..c1361337
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/options.cc
@@ -0,0 +1,7240 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory>
+#include <vector>
+
+#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 "rocksjni/comparatorjnicallback.h"
+#include "rocksjni/portal.h"
+#include "rocksjni/statisticsjni.h"
+#include "rocksjni/table_filter_jnicallback.h"
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/db.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/table.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 reinterpret_cast<jlong>(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<const ROCKSDB_NAMESPACE::DBOptions*>(jdboptions);
+ auto* cfOpt = reinterpret_cast<const ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(
+ jcfoptions);
+ auto* op = new ROCKSDB_NAMESPACE::Options(*dbOpt, *cfOpt);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->IncreaseParallelism(
+ static_cast<int>(totalThreads));
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setCreateIfMissing
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_Options_setCreateIfMissing(
+ JNIEnv*, jobject, jlong jhandle, jboolean flag) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->comparator =
+ ROCKSDB_NAMESPACE::ReverseBytewiseComparator();
+ break;
+ default:
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::ComparatorJniCallback*>(
+ jcomparator_handle);
+ break;
+
+ // JAVA_NATIVE_COMPARATOR_WRAPPER
+ case 0x1:
+ comparator =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Comparator*>(jcomparator_handle);
+ break;
+ }
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->merge_operator =
+ *(reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jopt_handle)
+ ->compaction_filter =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionFilter*>(
+ 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<ROCKSDB_NAMESPACE::CompactionFilterFactory>*>(
+ jcompactionfilterfactory_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::WriteBufferManager>*>(
+ jwrite_buffer_manager_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* pSptr =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::StatisticsJni>*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> sptr = opt->statistics;
+ if (sptr == nullptr) {
+ return 0;
+ } else {
+ std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>* pSptr =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>(sptr);
+ return reinterpret_cast<jlong>(pSptr);
+ }
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: maxWriteBufferNumber
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_maxWriteBufferNumber(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->error_if_exists =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->paranoid_checks =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->env =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->max_total_wal_size =
+ static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->max_open_files =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_file_opening_threads = static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->use_fsync =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DbPath> 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<jstring>(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<jstring>(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<uint64_t>(jtarget_size)));
+ }
+
+ env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT);
+
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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) {
+ jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr);
+ if (ptr_jtarget_size == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<jint>(db_path.target_size);
+ }
+
+ env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_COMMIT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->delete_obsolete_files_period_micros = static_cast<int64_t>(micros);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setBaseBackgroundCompactions
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_Options_setBaseBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle, jint max) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->base_background_compactions = static_cast<int>(max);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: baseBackgroundCompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_baseBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->base_background_compactions;
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: maxBackgroundCompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_maxBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_background_compactions = static_cast<int>(max);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setMaxSubcompactions
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_Options_setMaxSubcompactions(
+ JNIEnv*, jobject, jlong jhandle, jint max) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)->max_subcompactions =
+ static_cast<int32_t>(max);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: maxSubcompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_maxSubcompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_background_flushes = static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->max_background_jobs =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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());
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_manifest_file_size = static_cast<int64_t>(max_manifest_file_size);
+}
+
+/*
+ * Method: setMemTableFactory
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_Options_setMemTableFactory(
+ JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->memtable_factory.reset(
+ reinterpret_cast<ROCKSDB_NAMESPACE::MemTableRepFactory*>(
+ 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<ROCKSDB_NAMESPACE::RateLimiter>* pRateLimiter =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ jrate_limiter_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ jsst_file_manager_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::LoggerJniCallback>* pLogger =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jlogger_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->info_log_level =
+ static_cast<ROCKSDB_NAMESPACE::InfoLogLevel>(jlog_level);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: infoLogLevel
+ * Signature: (J)B
+ */
+jbyte Java_org_rocksdb_Options_infoLogLevel(
+ JNIEnv*, jobject, jlong jhandle) {
+ return static_cast<jbyte>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->table_cache_numshardbits = static_cast<int>(table_cache_numshardbits);
+}
+
+/*
+ * Method: useFixedLengthPrefixExtractor
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_Options_useFixedLengthPrefixExtractor(
+ JNIEnv*, jobject, jlong jhandle, jint jprefix_length) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewFixedPrefixTransform(
+ static_cast<int>(jprefix_length)));
+}
+
+/*
+ * Method: useCappedPrefixExtractor
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_Options_useCappedPrefixExtractor(
+ JNIEnv*, jobject, jlong jhandle, jint jprefix_length) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewCappedPrefixTransform(
+ static_cast<int>(jprefix_length)));
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: walTtlSeconds
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_walTtlSeconds(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->WAL_ttl_seconds =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->WAL_size_limit_MB =
+ static_cast<int64_t>(WAL_size_limit_MB);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: manifestPreallocationSize
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_manifestPreallocationSize(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* table_factory =
+ reinterpret_cast<ROCKSDB_NAMESPACE::TableFactory*>(jtable_factory_handle);
+ options->table_factory.reset(table_factory);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: allowMmapReads
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_allowMmapReads(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->allow_mmap_reads =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->allow_mmap_writes =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->use_direct_reads =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->use_direct_io_for_flush_and_compaction =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->allow_fallocate =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->is_fd_close_on_exec =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->stats_dump_period_sec =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->stats_persist_period_sec =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->stats_history_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->advise_random_on_open = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->db_write_buffer_size = static_cast<size_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return ROCKSDB_NAMESPACE::AccessHintJni::toJavaAccessHint(
+ opt->access_hint_on_compaction_start);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setNewTableReaderForCompactionInputs
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_Options_setNewTableReaderForCompactionInputs(
+ JNIEnv*, jobject, jlong jhandle,
+ jboolean jnew_table_reader_for_compaction_inputs) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->new_table_reader_for_compaction_inputs =
+ static_cast<bool>(jnew_table_reader_for_compaction_inputs);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: newTableReaderForCompactionInputs
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_newTableReaderForCompactionInputs(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<bool>(opt->new_table_reader_for_compaction_inputs);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->compaction_readahead_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->random_access_max_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->writable_file_max_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->use_adaptive_mutex =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->bytes_per_sync =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->wal_bytes_per_sync =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(opt->strict_bytes_per_sync);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->enable_thread_tracking = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->delayed_write_rate = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->unordered_write =
+ static_cast<bool>(unordered_write);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: unorderedWrite
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_unorderedWrite(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->allow_concurrent_memtable_write = static_cast<bool>(allow);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: allowConcurrentMemtableWrite
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_allowConcurrentMemtableWrite(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->enable_write_thread_adaptive_yield = static_cast<bool>(yield);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: enableWriteThreadAdaptiveYield
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_enableWriteThreadAdaptiveYield(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->write_thread_max_yield_usec = static_cast<int64_t>(max);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: writeThreadMaxYieldUsec
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_writeThreadMaxYieldUsec(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->write_thread_slow_yield_usec = static_cast<int64_t>(slow);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: writeThreadSlowYieldUsec
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_writeThreadSlowYieldUsec(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->skip_stats_update_on_db_open =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(opt->skip_stats_update_on_db_open);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setSkipCheckingSstFileSizesOnDbOpen
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_Options_setSkipCheckingSstFileSizesOnDbOpen(
+ JNIEnv*, jobject, jlong jhandle,
+ jboolean jskip_checking_sst_file_sizes_on_db_open) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->skip_checking_sst_file_sizes_on_db_open =
+ static_cast<bool>(jskip_checking_sst_file_sizes_on_db_open);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: skipCheckingSstFileSizesOnDbOpen
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_skipCheckingSstFileSizesOnDbOpen(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->allow_2pc = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* row_cache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Cache>*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* wal_filter = reinterpret_cast<ROCKSDB_NAMESPACE::WalFilterJniCallback*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->fail_if_options_file_error =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->dump_malloc_stats = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->avoid_flush_during_recovery =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(opt->avoid_flush_during_recovery);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->avoid_flush_during_shutdown =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(opt->allow_ingest_behind);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: setPreserveDeletes
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_Options_setPreserveDeletes(
+ JNIEnv*, jobject, jlong jhandle, jboolean jpreserve_deletes) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opt->preserve_deletes = jpreserve_deletes == JNI_TRUE;
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: preserveDeletes
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_Options_preserveDeletes(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(opt->preserve_deletes);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->min_write_buffer_number_to_merge =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_write_buffer_number_to_maintain =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<std::vector<ROCKSDB_NAMESPACE::CompressionType>>
+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<std::vector<ROCKSDB_NAMESPACE::CompressionType>>();
+ }
+
+ auto* compression_levels =
+ new std::vector<ROCKSDB_NAMESPACE::CompressionType>();
+ std::unique_ptr<std::vector<ROCKSDB_NAMESPACE::CompressionType>>
+ uptr_compression_levels(compression_levels);
+
+ for (jsize i = 0; i < len; i++) {
+ jbyte jcl = jcompression_level[i];
+ compression_levels->push_back(
+ static_cast<ROCKSDB_NAMESPACE::CompressionType>(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<ROCKSDB_NAMESPACE::CompressionType> 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<jsize>(len));
+ if (jcompression_levels == nullptr) {
+ // exception thrown: OutOfMemoryError
+ delete[] jbuf;
+ return nullptr;
+ }
+ env->SetByteArrayRegion(jcompression_levels, 0, static_cast<jsize>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* bottommost_compression_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* compression_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->compaction_options_fifo.max_table_files_size =
+ static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->num_levels =
+ static_cast<int>(jnum_levels);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: levelZeroFileNumCompactionTrigger
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_levelZeroFileNumCompactionTrigger(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_file_num_compaction_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_slowdown_writes_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_stop_writes_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->target_file_size_base = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->target_file_size_multiplier =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_bytes_for_level_base =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_bytes_for_level_multiplier =
+ static_cast<double>(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<jlong>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->max_compaction_bytes =
+ static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->disable_auto_compactions = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->max_sequential_skip_in_iterations =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->inplace_update_support = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->memtable_prefix_bloom_size_ratio =
+ static_cast<double>(jmemtable_prefix_bloom_size_ratio);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: bloomLocality
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_Options_bloomLocality(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->bloom_locality =
+ static_cast<int32_t>(jbloom_locality);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: maxSuccessiveMerges
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_maxSuccessiveMerges(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->optimize_filters_for_hits =
+ static_cast<bool>(joptimize_filters_for_hits);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: optimizeForSmallDb
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_Options_optimizeForSmallDb(
+ JNIEnv*, jobject, jlong jhandle) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle)->OptimizeForSmallDb();
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->PrepareForBulkLoad();
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: memtableHugePageSize
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_memtableHugePageSize(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->soft_pending_compaction_bytes_limit =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->hard_pending_compaction_bytes_limit =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_file_num_compaction_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_slowdown_writes_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)
+ ->level0_stop_writes_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::Options*>(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<jint>(mbflma[i]);
+ }
+
+ jsize jlen = static_cast<jsize>(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<ROCKSDB_NAMESPACE::Options*>(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<int32_t>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle)->paranoid_file_checks =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opts->report_bg_io_stats = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opts->ttl = static_cast<uint64_t>(jttl);
+}
+
+/*
+ * Class: org_rocksdb_Options
+ * Method: ttl
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_Options_ttl(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opts = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<jlong>(opts->ttl);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* opts_uni =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ auto* opts_fifo = reinterpret_cast<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(
+ 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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ opts->force_consistency_checks = static_cast<bool>(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<ROCKSDB_NAMESPACE::Options*>(jhandle);
+ return static_cast<bool>(opts->force_consistency_checks);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(joptions_handle));
+ return reinterpret_cast<jlong>(new_opt);
+}
+
+/*
+ * Class: org_rocksdb_ColumnFamilyOptions
+ * Method: getColumnFamilyOptionsFromProps
+ * Signature: (Ljava/util/String;)J
+ */
+jlong Java_org_rocksdb_ColumnFamilyOptions_getColumnFamilyOptionsFromProps(
+ 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::Status status =
+ ROCKSDB_NAMESPACE::GetColumnFamilyOptionsFromString(
+ 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 = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(handle);
+ assert(cfo != nullptr);
+ delete cfo;
+}
+
+/*
+ * Class: org_rocksdb_ColumnFamilyOptions
+ * Method: optimizeForSmallDb
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_ColumnFamilyOptions_optimizeForSmallDb(
+ JNIEnv*, jobject, jlong jhandle) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->OptimizeForSmallDb();
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->comparator = ROCKSDB_NAMESPACE::ReverseBytewiseComparator();
+ break;
+ default:
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ComparatorJniCallback*>(
+ jcomparator_handle);
+ break;
+
+ // JAVA_NATIVE_COMPARATOR_WRAPPER
+ case 0x1:
+ comparator =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Comparator*>(jcomparator_handle);
+ break;
+ }
+ auto* opt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->merge_operator =
+ *(reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::MergeOperator>*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jopt_handle)
+ ->compaction_filter =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionFilter*>(
+ 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<ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback>*>(
+ jcompactionfilterfactory_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->memtable_factory.reset(
+ reinterpret_cast<ROCKSDB_NAMESPACE::MemTableRepFactory*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewFixedPrefixTransform(
+ static_cast<int>(jprefix_length)));
+}
+
+/*
+ * Method: useCappedPrefixExtractor
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_ColumnFamilyOptions_useCappedPrefixExtractor(
+ JNIEnv*, jobject, jlong jhandle, jint jprefix_length) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewCappedPrefixTransform(
+ static_cast<int>(jprefix_length)));
+}
+
+/*
+ * Method: setTableFactory
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_ColumnFamilyOptions_setTableFactory(
+ JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->table_factory.reset(
+ reinterpret_cast<ROCKSDB_NAMESPACE::TableFactory*>(jfactory_handle));
+}
+
+/*
+ * Method: tableFactoryName
+ * Signature: (J)Ljava/lang/String
+ */
+jstring Java_org_rocksdb_ColumnFamilyOptions_tableFactoryName(
+ JNIEnv* env, jobject, jlong jhandle) {
+ auto* opt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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: minWriteBufferNumberToMerge
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_ColumnFamilyOptions_minWriteBufferNumberToMerge(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->min_write_buffer_number_to_merge =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->max_write_buffer_number_to_maintain =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ auto* bottommost_compression_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ auto* compression_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompressionOptions*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->compaction_options_fifo.max_table_files_size =
+ static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->num_levels = static_cast<int>(jnum_levels);
+}
+
+/*
+ * Class: org_rocksdb_ColumnFamilyOptions
+ * Method: levelZeroFileNumCompactionTrigger
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroFileNumCompactionTrigger(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_file_num_compaction_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_slowdown_writes_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_stop_writes_trigger =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->target_file_size_base = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->target_file_size_multiplier =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->max_bytes_for_level_base =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->max_bytes_for_level_multiplier =
+ static_cast<double>(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<jlong>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->max_compaction_bytes = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->disable_auto_compactions = static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->max_sequential_skip_in_iterations =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->inplace_update_support = static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->memtable_prefix_bloom_size_ratio =
+ static_cast<double>(jmemtable_prefix_bloom_size_ratio);
+}
+
+/*
+ * Class: org_rocksdb_ColumnFamilyOptions
+ * Method: bloomLocality
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_ColumnFamilyOptions_bloomLocality(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->bloom_locality = static_cast<int32_t>(jbloom_locality);
+}
+
+/*
+ * Class: org_rocksdb_ColumnFamilyOptions
+ * Method: maxSuccessiveMerges
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_ColumnFamilyOptions_maxSuccessiveMerges(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->optimize_filters_for_hits =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->soft_pending_compaction_bytes_limit =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->hard_pending_compaction_bytes_limit =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_file_num_compaction_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_slowdown_writes_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->level0_stop_writes_trigger =
+ static_cast<int32_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<jint>(mbflma[i]);
+ }
+
+ jsize jlen = static_cast<jsize>(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, 0);
+ if (additionals == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ auto* cf_opt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<int32_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle)
+ ->paranoid_file_checks = static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ cf_opts->report_bg_io_stats = static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ return static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ cf_opts->ttl = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ return static_cast<jlong>(cf_opts->ttl);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ auto* opts_uni =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionOptionsUniversal*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ auto* opts_fifo = reinterpret_cast<ROCKSDB_NAMESPACE::CompactionOptionsFIFO*>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ cf_opts->force_consistency_checks =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jhandle);
+ return static_cast<bool>(cf_opts->force_consistency_checks);
+}
+
+/////////////////////////////////////////////////////////////////////
+// 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Options*>(joptions_handle));
+ return reinterpret_cast<jlong>(new_opt);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: getDBOptionsFromProps
+ * Signature: (Ljava/util/String;)J
+ */
+jlong Java_org_rocksdb_DBOptions_getDBOptionsFromProps(
+ 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* db_options = new ROCKSDB_NAMESPACE::DBOptions();
+ ROCKSDB_NAMESPACE::Status status = ROCKSDB_NAMESPACE::GetDBOptionsFromString(
+ 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 = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->env =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->IncreaseParallelism(
+ static_cast<int>(totalThreads));
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setCreateIfMissing
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_DBOptions_setCreateIfMissing(
+ JNIEnv*, jobject, jlong jhandle, jboolean flag) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->error_if_exists =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->paranoid_checks =
+ static_cast<bool>(paranoid_checks);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: paranoidChecks
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_paranoidChecks(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::RateLimiter>* pRateLimiter =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ jrate_limiter_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ jsst_file_manager_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::LoggerJniCallback>* pLogger =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jlogger_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->info_log_level =
+ static_cast<ROCKSDB_NAMESPACE::InfoLogLevel>(jlog_level);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: infoLogLevel
+ * Signature: (J)B
+ */
+jbyte Java_org_rocksdb_DBOptions_infoLogLevel(
+ JNIEnv*, jobject, jlong jhandle) {
+ return static_cast<jbyte>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->max_total_wal_size =
+ static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->max_open_files =
+ static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->max_file_opening_threads = static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ auto* pSptr =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::StatisticsJni>*>(
+ 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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> sptr = opt->statistics;
+ if (sptr == nullptr) {
+ return 0;
+ } else {
+ std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>* pSptr =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>(sptr);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->use_fsync =
+ static_cast<bool>(use_fsync);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: useFsync
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_useFsync(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DbPath> 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<jstring>(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<jstring>(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<uint64_t>(jtarget_size)));
+ }
+
+ env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT);
+
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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) {
+ jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr);
+ if (ptr_jtarget_size == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<jint>(db_path.target_size);
+ }
+
+ env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_COMMIT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->delete_obsolete_files_period_micros = static_cast<int64_t>(micros);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: deleteObsoleteFilesPeriodMicros
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_DBOptions_deleteObsoleteFilesPeriodMicros(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->delete_obsolete_files_period_micros;
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setBaseBackgroundCompactions
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_DBOptions_setBaseBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle, jint max) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->base_background_compactions = static_cast<int>(max);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: baseBackgroundCompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_DBOptions_baseBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->base_background_compactions;
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setMaxBackgroundCompactions
+ * Signature: (JI)V
+ */
+void Java_org_rocksdb_DBOptions_setMaxBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle, jint max) {
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->max_background_compactions = static_cast<int>(max);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: maxBackgroundCompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_DBOptions_maxBackgroundCompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->max_subcompactions =
+ static_cast<int32_t>(max);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: maxSubcompactions
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_DBOptions_maxSubcompactions(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->max_background_flushes = static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->max_background_jobs = static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->max_manifest_file_size = static_cast<int64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->table_cache_numshardbits = static_cast<int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->WAL_ttl_seconds =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->WAL_size_limit_MB =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->WAL_size_limit_MB;
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->use_direct_reads =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->use_direct_io_for_flush_and_compaction =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->allow_fallocate =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->allow_mmap_reads =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->allow_mmap_writes =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->is_fd_close_on_exec = static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->stats_dump_period_sec =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->stats_persist_period_sec =
+ static_cast<unsigned int>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->stats_history_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->advise_random_on_open = static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->db_write_buffer_size = static_cast<size_t>(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<std::shared_ptr<ROCKSDB_NAMESPACE::WriteBufferManager>*>(
+ jwrite_buffer_manager_handle);
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return ROCKSDB_NAMESPACE::AccessHintJni::toJavaAccessHint(
+ opt->access_hint_on_compaction_start);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setNewTableReaderForCompactionInputs
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_DBOptions_setNewTableReaderForCompactionInputs(
+ JNIEnv*, jobject, jlong jhandle,
+ jboolean jnew_table_reader_for_compaction_inputs) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->new_table_reader_for_compaction_inputs =
+ static_cast<bool>(jnew_table_reader_for_compaction_inputs);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: newTableReaderForCompactionInputs
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_newTableReaderForCompactionInputs(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<bool>(opt->new_table_reader_for_compaction_inputs);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->compaction_readahead_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->random_access_max_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->writable_file_max_buffer_size =
+ static_cast<size_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->use_adaptive_mutex =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->bytes_per_sync =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)->wal_bytes_per_sync =
+ static_cast<int64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<jboolean>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->strict_bytes_per_sync);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->delayed_write_rate = static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->allow_concurrent_memtable_write = static_cast<bool>(allow);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: allowConcurrentMemtableWrite
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_allowConcurrentMemtableWrite(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->enable_write_thread_adaptive_yield = static_cast<bool>(yield);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: enableWriteThreadAdaptiveYield
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_enableWriteThreadAdaptiveYield(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->write_thread_max_yield_usec = static_cast<int64_t>(max);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: writeThreadMaxYieldUsec
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_DBOptions_writeThreadMaxYieldUsec(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle)
+ ->write_thread_slow_yield_usec = static_cast<int64_t>(slow);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: writeThreadSlowYieldUsec
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_DBOptions_writeThreadSlowYieldUsec(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->skip_stats_update_on_db_open =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(opt->skip_stats_update_on_db_open);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setSkipCheckingSstFileSizesOnDbOpen
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_DBOptions_setSkipCheckingSstFileSizesOnDbOpen(
+ JNIEnv*, jobject, jlong jhandle,
+ jboolean jskip_checking_sst_file_sizes_on_db_open) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->skip_checking_sst_file_sizes_on_db_open =
+ static_cast<bool>(jskip_checking_sst_file_sizes_on_db_open);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: skipCheckingSstFileSizesOnDbOpen
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_skipCheckingSstFileSizesOnDbOpen(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->allow_2pc = static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ auto* row_cache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Cache>*>(
+ 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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ auto* wal_filter = reinterpret_cast<ROCKSDB_NAMESPACE::WalFilterJniCallback*>(
+ 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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->fail_if_options_file_error =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->dump_malloc_stats = static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->avoid_flush_during_recovery =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(opt->allow_ingest_behind);
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: setPreserveDeletes
+ * Signature: (JZ)V
+ */
+void Java_org_rocksdb_DBOptions_setPreserveDeletes(
+ JNIEnv*, jobject, jlong jhandle, jboolean jpreserve_deletes) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->preserve_deletes = jpreserve_deletes == JNI_TRUE;
+}
+
+/*
+ * Class: org_rocksdb_DBOptions
+ * Method: preserveDeletes
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_DBOptions_preserveDeletes(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(opt->preserve_deletes);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ opt->avoid_flush_during_shutdown =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::DBOptions*>(jhandle);
+ return static_cast<jboolean>(opt->avoid_flush_during_shutdown);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteOptions*>(jhandle)));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(jhandle)
+ ->ignore_missing_column_families =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(jhandle)->no_slowdown =
+ static_cast<bool>(jno_slowdown);
+}
+
+/*
+ * Class: org_rocksdb_WriteOptions
+ * Method: noSlowdown
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_WriteOptions_noSlowdown(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::WriteOptions*>(jhandle)->low_pri =
+ static_cast<bool>(jlow_pri);
+}
+
+/*
+ * Class: org_rocksdb_WriteOptions
+ * Method: lowPri
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_WriteOptions_lowPri(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jhandle)->low_pri;
+}
+
+/////////////////////////////////////////////////////////////////////
+// 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 reinterpret_cast<jlong>(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<bool>(jverify_checksums), static_cast<bool>(jfill_cache));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->verify_checksums =
+ static_cast<bool>(jverify_checksums);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: verifyChecksums
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_ReadOptions_verifyChecksums(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->fill_cache =
+ static_cast<bool>(jfill_cache);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: fillCache
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_ReadOptions_fillCache(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->tailing =
+ static_cast<bool>(jtailing);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: tailing
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_ReadOptions_tailing(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->tailing;
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: managed
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_ReadOptions_managed(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->managed =
+ static_cast<bool>(jmanaged);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: totalOrderSeek
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_ReadOptions_totalOrderSeek(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->total_order_seek =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)
+ ->prefix_same_as_start = static_cast<bool>(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<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->pin_data =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ opt->background_purge_on_iterator_cleanup =
+ static_cast<bool>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ opt->readahead_size = static_cast<size_t>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ opt->max_skippable_internal_keys =
+ static_cast<uint64_t>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ opt->ignore_range_deletions = static_cast<bool>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->snapshot =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Snapshot*>(jsnapshot);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: snapshot
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_ReadOptions_snapshot(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto& snapshot =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->snapshot;
+ return reinterpret_cast<jlong>(snapshot);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: readTier
+ * Signature: (J)B
+ */
+jbyte Java_org_rocksdb_ReadOptions_readTier(
+ JNIEnv*, jobject, jlong jhandle) {
+ return static_cast<jbyte>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)->read_tier =
+ static_cast<ROCKSDB_NAMESPACE::ReadTier>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)
+ ->iterate_upper_bound =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)
+ ->iterate_upper_bound;
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)
+ ->iterate_lower_bound =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle)
+ ->iterate_lower_bound;
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ auto* jni_table_filter =
+ reinterpret_cast<ROCKSDB_NAMESPACE::TableFilterJniCallback*>(
+ jjni_table_filter_handle);
+ opt->table_filter = jni_table_filter->GetTableFilterFunction();
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: setIterStartSeqnum
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_ReadOptions_setIterStartSeqnum(
+ JNIEnv*, jobject, jlong jhandle, jlong jiter_start_seqnum) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ opt->iter_start_seqnum = static_cast<uint64_t>(jiter_start_seqnum);
+}
+
+/*
+ * Class: org_rocksdb_ReadOptions
+ * Method: iterStartSeqnum
+ * Signature: (J)J
+ */
+jlong Java_org_rocksdb_ReadOptions_iterStartSeqnum(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
+ return static_cast<jlong>(opt->iter_start_seqnum);
+}
+
+/////////////////////////////////////////////////////////////////////
+// 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ 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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ 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<jboolean>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ 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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(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<jint>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ 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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(jhandle)
+ ->max_reused_buffer_size = static_cast<int32_t>(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<ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*>(
+ 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::FlushOptions*>(jhandle)->wait =
+ static_cast<bool>(jwait);
+}
+
+/*
+ * Class: org_rocksdb_FlushOptions
+ * Method: waitForFlush
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_FlushOptions_waitForFlush(
+ JNIEnv*, jobject, jlong jhandle) {
+ return reinterpret_cast<ROCKSDB_NAMESPACE::FlushOptions*>(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<ROCKSDB_NAMESPACE::FlushOptions*>(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<ROCKSDB_NAMESPACE::FlushOptions*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::FlushOptions*>(jhandle);
+ assert(flush_opt != nullptr);
+ delete flush_opt;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/options_util.cc b/storage/rocksdb/rocksdb/java/rocksjni/options_util.cc
new file mode 100644
index 00000000..e195adaf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/options_util.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <string>
+
+#include "include/org_rocksdb_OptionsUtil.h"
+
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/utilities/options_util.h"
+#include "rocksjni/portal.h"
+
+void build_column_family_descriptor_list(
+ JNIEnv* env, jobject jcfds,
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor>& 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: (Ljava/lang/String;JLjava/util/List;Z)V
+ */
+void Java_org_rocksdb_OptionsUtil_loadLatestOptions(
+ JNIEnv* env, jclass /*jcls*/, jstring jdbpath, jlong jenv_handle,
+ jlong jdb_opts_handle, jobject jcfds, jboolean ignore_unknown_options) {
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> cf_descs;
+ ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::LoadLatestOptions(
+ db_path, reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(jenv_handle),
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdb_opts_handle),
+ &cf_descs, ignore_unknown_options);
+ 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: (Ljava/lang/String;JJLjava/util/List;Z)V
+ */
+void Java_org_rocksdb_OptionsUtil_loadOptionsFromFile(
+ JNIEnv* env, jclass /*jcls*/, jstring jopts_file_name, jlong jenv_handle,
+ jlong jdb_opts_handle, jobject jcfds, jboolean ignore_unknown_options) {
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> cf_descs;
+ ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::LoadOptionsFromFile(
+ opts_file_name, reinterpret_cast<ROCKSDB_NAMESPACE::Env*>(jenv_handle),
+ reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdb_opts_handle),
+ &cf_descs, ignore_unknown_options);
+ 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<ROCKSDB_NAMESPACE::Env*>(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/storage/rocksdb/rocksdb/java/rocksjni/persistent_cache.cc b/storage/rocksdb/rocksdb/java/rocksjni/persistent_cache.cc
new file mode 100644
index 00000000..6776022e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/persistent_cache.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <string>
+
+#include "include/org_rocksdb_PersistentCache.h"
+#include "rocksdb/persistent_cache.h"
+#include "loggerjnicallback.h"
+#include "portal.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<ROCKSDB_NAMESPACE::Env*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*>(
+ jlogger_handle);
+ auto* cache =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::PersistentCache>(nullptr);
+ ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::NewPersistentCache(
+ rocks_env, path, static_cast<uint64_t>(jsz), *logger,
+ static_cast<bool>(joptimized_for_nvm), cache);
+ if (!s.ok()) {
+ ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
+ }
+ return reinterpret_cast<jlong>(cache);
+}
+
+/*
+ * Class: org_rocksdb_PersistentCache
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_PersistentCache_disposeInternal(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* cache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::PersistentCache>*>(
+ jhandle);
+ delete cache; // delete std::shared_ptr
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/portal.h b/storage/rocksdb/rocksdb/java/rocksjni/portal.h
new file mode 100644
index 00000000..deb88af4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/portal.h
@@ -0,0 +1,7534 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <iterator>
+#include <jni.h>
+#include <limits>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#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/backupable_db.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/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 PTR, class DERIVED> class RocksDBNativeClass : public JavaClass {
+};
+
+// Native class template for sub-classes of RocksMutableObject
+template<class PTR, class DERIVED> class NativeRocksMutableObject
+ : public RocksDBNativeClass<PTR, DERIVED> {
+ 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, reinterpret_cast<jlong>(ptr), java_owns_handle);
+ if(env->ExceptionCheck()) {
+ return true; // signal exception
+ }
+
+ return false;
+ }
+};
+
+// Java Exception template
+template<class DERIVED> 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<IllegalArgumentExceptionJni> {
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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<ROCKSDB_NAMESPACE::Status*, StatusJni> {
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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, "<init>", "(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;
+ }
+
+ // 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<ROCKSDB_NAMESPACE::Status> toCppStatus(
+ const jbyte jcode_value, const jbyte jsub_code_value) {
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> status;
+ switch (jcode_value) {
+ case 0x0:
+ //Ok
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
+ break;
+ case 0x1:
+ //NotFound
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::NotFound(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0x2:
+ //Corruption
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Corruption(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0x3:
+ //NotSupported
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(
+ ROCKSDB_NAMESPACE::Status::NotSupported(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
+ jsub_code_value))));
+ break;
+ case 0x4:
+ //InvalidArgument
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(
+ ROCKSDB_NAMESPACE::Status::InvalidArgument(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
+ jsub_code_value))));
+ break;
+ case 0x5:
+ //IOError
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::IOError(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0x6:
+ //MergeInProgress
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(
+ ROCKSDB_NAMESPACE::Status::MergeInProgress(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
+ jsub_code_value))));
+ break;
+ case 0x7:
+ //Incomplete
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Incomplete(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0x8:
+ //ShutdownInProgress
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(
+ ROCKSDB_NAMESPACE::Status::ShutdownInProgress(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
+ jsub_code_value))));
+ break;
+ case 0x9:
+ //TimedOut
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TimedOut(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0xA:
+ //Aborted
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Aborted(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0xB:
+ //Busy
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Busy(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0xC:
+ //Expired
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Expired(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0xD:
+ //TryAgain
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TryAgain(
+ ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
+ break;
+ case 0xE:
+ // ColumnFamilyDropped
+ status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ 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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::Status> 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<RocksDBExceptionJni> {
+ 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<Status>& 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, "<init>", "(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<jthrowable>(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, "<init>", "(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<jthrowable>(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 retieved
+ */
+ 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<ROCKSDB_NAMESPACE::Status> 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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, "<init>", "(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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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<char*>(buf), static_cast<jlong>(capacity));
+ if (jbuf == nullptr) {
+ // exception occurred
+ if (allocated) {
+ delete[] static_cast<const char*>(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<jint>(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<jbyte*>(
+ env->GetPrimitiveArrayCritical(jarray, &is_copy));
+ if (ja == nullptr) {
+ // exception occurred
+ env->DeleteLocalRef(jarray);
+ env->DeleteLocalRef(jbuf);
+ return nullptr;
+ }
+
+ memcpy(ja, const_cast<char*>(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<jbyteArray>(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 retieved
+ */
+ 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<uint64_t>(jvalue) > std::numeric_limits<size_t>::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<void**>(&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<void**>(&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<std::string>
+ *
+ * @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<std:string> containing copies of the Java strings
+ */
+ static std::vector<std::string> 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<std::string>
+ *
+ * @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<std:string> containing copies of the Java strings
+ */
+ static std::vector<std::string> copyStrings(JNIEnv* env,
+ jobjectArray jss, const jsize jss_len, jboolean* has_exception) {
+ std::vector<std::string> 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<jstring>(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<char[]> 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<char[]> 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 <typename T> static void byteStrings(JNIEnv* env,
+ jobjectArray jbyte_strings,
+ std::function<T(const char*, const size_t)> string_fn,
+ std::function<void(size_t, T)> 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<jbyteArray>(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 <typename T> static T byteString(JNIEnv* env,
+ jbyteArray jbyte_string_ary,
+ std::function<T(const char*, const size_t)> string_fn,
+ jboolean* has_exception) {
+ const jsize jbyte_string_len = env->GetArrayLength(jbyte_string_ary);
+ return byteString<T>(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 <typename T> static T byteString(JNIEnv* env,
+ jbyteArray jbyte_string_ary, const jsize jbyte_string_len,
+ std::function<T(const char*, const size_t)> 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<char *>(jbyte_string), jbyte_string_len);
+
+ env->ReleaseByteArrayElements(jbyte_string_ary, jbyte_string, JNI_ABORT);
+
+ *has_exception = JNI_FALSE;
+ return result;
+ }
+
+ /**
+ * Converts a std::vector<string> 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<std::string> strings) {
+ jclass jcls_ba = ByteJni::getArrayJClass(env);
+ if(jcls_ba == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const jsize len = static_cast<jsize>(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<jsize>(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<jbyte*>(reinterpret_cast<const jbyte*>(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<std::string> 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<std::string>* strings) {
+ jclass jcls_str = env->FindClass("java/lang/String");
+ if(jcls_str == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const jsize len = static_cast<jsize>(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<size_t>(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<jsize>(size);
+ jbyteArray jbytes = env->NewByteArray(jlen);
+ if(jbytes == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(jbytes, 0, jlen,
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(bytes)));
+ if(env->ExceptionCheck()) {
+ // exception thrown: ArrayIndexOutOfBoundsException
+ env->DeleteLocalRef(jbytes);
+ return nullptr;
+ }
+
+ return jbytes;
+ }
+
+ /**
+ * Copies bytes from a ROCKSDB_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<ROCKSDB_NAMESPACE::Status> kv_op(
+ std::function<ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Slice,
+ ROCKSDB_NAMESPACE::Slice)>
+ 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<char*>(key),
+ jkey_len);
+ ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Status>(
+ 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<ROCKSDB_NAMESPACE::Status> k_op(
+ std::function<ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Slice)> 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<char*>(key),
+ jkey_len);
+
+ auto status = op(key_slice);
+
+ if(key != nullptr) {
+ env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
+ }
+
+ return std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(status));
+ }
+
+ /*
+ * Helper for operations on a value
+ * for example WriteBatchWithIndex->GetFromBatch
+ */
+ static jbyteArray v_op(std::function<ROCKSDB_NAMESPACE::Status(
+ ROCKSDB_NAMESPACE::Slice, std::string*)>
+ 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<char*>(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<jsize>(value.size()));
+ if(jret_value == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(jret_value, 0, static_cast<jsize>(value.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<T*> 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<typename T> static std::vector<T*> fromJPointers(
+ JNIEnv* env, jlongArray jptrs, jboolean *has_exception) {
+ const jsize jptrs_len = env->GetArrayLength(jptrs);
+ std::vector<T*> 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<T*>(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<typename T> static jlongArray toJPointers(JNIEnv* env,
+ const std::vector<T*> &pointers,
+ jboolean *has_exception) {
+ const jsize len = static_cast<jsize>(pointers.size());
+ std::unique_ptr<jlong[]> results(new jlong[len]);
+ std::transform(pointers.begin(), pointers.end(), results.get(), [](T* pointer) -> jlong {
+ return reinterpret_cast<jlong>(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<void(ROCKSDB_NAMESPACE::Slice&,
+ ROCKSDB_NAMESPACE::Slice&)>
+ op,
+ JNIEnv* env, jobject jkey, jint jkey_off,
+ jint jkey_len, jobject jval, jint jval_off,
+ jint jval_len) {
+ char* key = reinterpret_cast<char*>(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<char*>(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<void(ROCKSDB_NAMESPACE::Slice&)> op,
+ JNIEnv* env, jobject jkey, jint jkey_off,
+ jint jkey_len) {
+ char* key = reinterpret_cast<char*>(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 <class T>
+ static jint copyToDirect(JNIEnv* env, T& source, jobject jtarget,
+ jint jtarget_off, jint jtarget_len) {
+ char* target =
+ reinterpret_cast<char*>(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<jint>(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 retieved
+ */
+ 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, "<init>", "(I)V");
+ if (mid == nullptr) {
+ // exception thrown: NoSuchMethodException or OutOfMemoryError
+ return nullptr;
+ }
+
+ jobject jhash_map = env->NewObject(jclazz, mid, static_cast<jint>(initial_capacity));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ return jhash_map;
+ }
+
+ /**
+ * A function which maps a std::pair<K,V> to a std::pair<JK, JV>
+ *
+ * @return Either a pointer to a std::pair<jobject, jobject>, or nullptr
+ * if an error occurs during the mapping
+ */
+ template <typename K, typename V, typename JK, typename JV>
+ using FnMapKV = std::function<std::unique_ptr<std::pair<JK, JV>> (const std::pair<K, V>&)>;
+
+ // template <class I, typename K, typename V, typename K1, typename V1, typename std::enable_if<std::is_same<typename std::iterator_traits<I>::value_type, std::pair<const K,V>>::value, int32_t>::type = 0>
+ // static void putAll(JNIEnv* env, const jobject jhash_map, I iterator, const FnMapKV<const K,V,K1,V1> &fn_map_kv) {
+ /**
+ * Returns true if it succeeds, false if an error occurs
+ */
+ template<class iterator_type, typename K, typename V>
+ static bool putAll(JNIEnv* env, const jobject jhash_map, iterator_type iterator, iterator_type end, const FnMapKV<K, V, jobject, jobject> &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<std::pair<jobject, jobject>> 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<String, String> from a std::map<std::string, std::string>
+ *
+ * @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<std::string, std::string>* map) {
+ if (map == nullptr) {
+ return nullptr;
+ }
+
+ jobject jhash_map = construct(env, static_cast<uint32_t>(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<const std::string, const std::string>& kv) {
+ jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.first), false);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ jstring jvalue = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.second), true);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ env->DeleteLocalRef(jkey);
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(
+ static_cast<jobject>(jkey),
+ static_cast<jobject>(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<String, Long> from a std::map<std::string, uint32_t>
+ *
+ * @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<std::string, uint32_t>* map) {
+ if (map == nullptr) {
+ return nullptr;
+ }
+
+ if (map == nullptr) {
+ return nullptr;
+ }
+
+ jobject jhash_map = construct(env, static_cast<uint32_t>(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<const std::string, const uint32_t>& kv) {
+ jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.first), false);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ jobject jvalue = ROCKSDB_NAMESPACE::IntegerJni::valueOf(
+ env, static_cast<jint>(kv.second));
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ env->DeleteLocalRef(jkey);
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(static_cast<jobject>(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<String, Long> from a std::map<std::string, uint64_t>
+ *
+ * @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<std::string, uint64_t>* map) {
+ if (map == nullptr) {
+ return nullptr;
+ }
+
+ jobject jhash_map = construct(env, static_cast<uint32_t>(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<const std::string, const uint64_t>& kv) {
+ jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.first), false);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf(
+ env, static_cast<jlong>(kv.second));
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ env->DeleteLocalRef(jkey);
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(static_cast<jobject>(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<String, Long> from a std::map<uint32_t, uint64_t>
+ *
+ * @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<uint32_t, uint64_t>* map) {
+ if (map == nullptr) {
+ return nullptr;
+ }
+
+ jobject jhash_map = construct(env, static_cast<uint32_t>(map->size()));
+ if (jhash_map == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<const uint32_t, const uint64_t,
+ jobject, jobject>
+ fn_map_kv = [env](const std::pair<const uint32_t, const uint64_t>& kv) {
+ jobject jkey = ROCKSDB_NAMESPACE::IntegerJni::valueOf(
+ env, static_cast<jint>(kv.first));
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf(
+ env, static_cast<jlong>(kv.second));
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ env->DeleteLocalRef(jkey);
+ return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(static_cast<jobject>(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<ROCKSDB_NAMESPACE::DB*, RocksDBJni> {
+ 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<ROCKSDB_NAMESPACE::Options*, OptionsJni> {
+ 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<ROCKSDB_NAMESPACE::DBOptions*, DBOptionsJni> {
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*,
+ ColumnFamilyOptionsJni> {
+ 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, "<init>", "(J)V");
+ if (mid == nullptr) {
+ // exception thrown: NoSuchMethodException or OutOfMemoryError
+ return nullptr;
+ }
+
+ jobject jcfd = env->NewObject(jclazz, mid, reinterpret_cast<jlong>(cfo));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ return jcfd;
+ }
+};
+
+// The portal class for org.rocksdb.WriteOptions
+class WriteOptionsJni
+ : public RocksDBNativeClass<ROCKSDB_NAMESPACE::WriteOptions*,
+ WriteOptionsJni> {
+ 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<ROCKSDB_NAMESPACE::ReadOptions*,
+ ReadOptionsJni> {
+ 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<ROCKSDB_NAMESPACE::WriteBatch*, WriteBatchJni> {
+ 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, "<init>", "(J)V");
+ if (mid == nullptr) {
+ // exception thrown: NoSuchMethodException or OutOfMemoryError
+ return nullptr;
+ }
+
+ jobject jwb = env->NewObject(jclazz, mid, reinterpret_cast<jlong>(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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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#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 retieved
+ */
+ 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 retieved
+ */
+ static jmethodID getConstructorMethodId(JNIEnv* env) {
+ jclass jclazz = getJClass(env);
+ if(jclazz == nullptr) {
+ // exception occurred accessing class
+ return nullptr;
+ }
+
+ static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(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<jlong>(save_point.size),
+ static_cast<jlong>(save_point.count),
+ static_cast<jlong>(save_point.content_flags));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ return jsave_point;
+ }
+};
+
+// The portal class for org.rocksdb.WriteBatchWithIndex
+class WriteBatchWithIndexJni
+ : public RocksDBNativeClass<ROCKSDB_NAMESPACE::WriteBatchWithIndex*,
+ WriteBatchWithIndexJni> {
+ 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 retieved
+ */
+ static jmethodID getConstructorMethodId(JNIEnv* env) {
+ jclass jclazz = getJClass(env);
+ if(jclazz == nullptr) {
+ // exception occurred accessing class
+ return nullptr;
+ }
+
+ static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(DDDDDDJJD)V");
+ assert(mid != nullptr);
+ return mid;
+ }
+};
+
+// The portal class for org.rocksdb.BackupableDBOptions
+class BackupableDBOptionsJni
+ : public RocksDBNativeClass<ROCKSDB_NAMESPACE::BackupableDBOptions*,
+ BackupableDBOptionsJni> {
+ public:
+ /**
+ * Get the Java Class org.rocksdb.BackupableDBOptions
+ *
+ * @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/BackupableDBOptions");
+ }
+};
+
+// The portal class for org.rocksdb.BackupEngine
+class BackupEngineJni
+ : public RocksDBNativeClass<ROCKSDB_NAMESPACE::BackupEngine*,
+ BackupEngineJni> {
+ public:
+ /**
+ * Get the Java Class org.rocksdb.BackupableEngine
+ *
+ * @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<ROCKSDB_NAMESPACE::Iterator*, IteratorJni> {
+ 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<ROCKSDB_NAMESPACE::FilterPolicy>*, 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ ColumnFamilyHandleJni> {
+ public:
+ /**
+ * 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<ROCKSDB_NAMESPACE::FlushOptions*,
+ FlushOptionsJni> {
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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<const ROCKSDB_NAMESPACE::ComparatorJniCallback*,
+ AbstractComparatorJni> {
+ 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 retieved
+ */
+ 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<const ROCKSDB_NAMESPACE::Slice*,
+ AbstractSliceJni> {
+ 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<const ROCKSDB_NAMESPACE::Slice*,
+ AbstractSliceJni> {
+ 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, "<init>", "()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<const ROCKSDB_NAMESPACE::Slice*,
+ AbstractSliceJni> {
+ 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, "<init>", "()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, "<init>", "(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<BackupInfo> object to
+ * a Java ArrayList<org.rocksdb.BackupInfo> 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<BackupInfo> 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 retieved
+ */
+ 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<ROCKSDB_NAMESPACE::LoggerJniCallback>*, 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 retieved
+ */
+ 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, "<init>", "(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;
+ 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;
+ 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_INDEX_BYTES_EVICT:
+ return 0x8;
+ 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_FILTER_BYTES_EVICT:
+ return 0xD;
+ 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_CLOSES:
+ return 0x2F;
+ case ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS:
+ return 0x30;
+ case ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS:
+ return 0x31;
+ case ROCKSDB_NAMESPACE::Tickers::STALL_L0_SLOWDOWN_MICROS:
+ return 0x32;
+ case ROCKSDB_NAMESPACE::Tickers::STALL_MEMTABLE_COMPACTION_MICROS:
+ return 0x33;
+ case ROCKSDB_NAMESPACE::Tickers::STALL_L0_NUM_FILES_MICROS:
+ return 0x34;
+ case ROCKSDB_NAMESPACE::Tickers::STALL_MICROS:
+ return 0x35;
+ case ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS:
+ return 0x36;
+ case ROCKSDB_NAMESPACE::Tickers::RATE_LIMIT_DELAY_MILLIS:
+ return 0x37;
+ case ROCKSDB_NAMESPACE::Tickers::NO_ITERATORS:
+ return 0x38;
+ 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_FILTERED_DELETES:
+ return 0x3C;
+ 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::BLOCK_CACHE_COMPRESSED_MISS:
+ return 0x42;
+ case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_HIT:
+ return 0x43;
+ case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD:
+ return 0x44;
+ case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES:
+ return 0x45;
+ 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_TIMEDOUT:
+ return 0x4A;
+ 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 to fixate the new value that incorrectly changed 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_OVERWRITTEN:
+ return 0x7E;
+ case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED:
+ return 0x7F;
+ case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED:
+ return -0x02;
+ case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN:
+ return -0x03;
+ case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_EXPIRED:
+ return -0x04;
+ 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::TICKER_ENUM_MAX:
+ // 0x5F for backwards compatibility on current minor version.
+ 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 0x8:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT;
+ 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 0xD:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT;
+ 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 0x2F:
+ return ROCKSDB_NAMESPACE::Tickers::NO_FILE_CLOSES;
+ case 0x30:
+ return ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS;
+ case 0x31:
+ return ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS;
+ case 0x32:
+ return ROCKSDB_NAMESPACE::Tickers::STALL_L0_SLOWDOWN_MICROS;
+ case 0x33:
+ return ROCKSDB_NAMESPACE::Tickers::STALL_MEMTABLE_COMPACTION_MICROS;
+ case 0x34:
+ return ROCKSDB_NAMESPACE::Tickers::STALL_L0_NUM_FILES_MICROS;
+ case 0x35:
+ return ROCKSDB_NAMESPACE::Tickers::STALL_MICROS;
+ case 0x36:
+ return ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS;
+ case 0x37:
+ return ROCKSDB_NAMESPACE::Tickers::RATE_LIMIT_DELAY_MILLIS;
+ case 0x38:
+ return ROCKSDB_NAMESPACE::Tickers::NO_ITERATORS;
+ 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 0x3C:
+ return ROCKSDB_NAMESPACE::Tickers::NUMBER_FILTERED_DELETES;
+ 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 0x42:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_MISS;
+ case 0x43:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_HIT;
+ case 0x44:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD;
+ case 0x45:
+ return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES;
+ 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 0x4A:
+ return ROCKSDB_NAMESPACE::Tickers::WRITE_TIMEDOUT;
+ 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 to fixate the new value that incorrectly changed 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 0x7E:
+ return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_OVERWRITTEN;
+ case 0x7F:
+ return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED;
+ case -0x02:
+ return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED;
+ case -0x03:
+ return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN;
+ case -0x04:
+ return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_EXPIRED;
+ 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 0x5F:
+ // 0x5F for backwards compatibility on current minor version.
+ 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::STALL_L0_SLOWDOWN_COUNT:
+ return 0xD;
+ case ROCKSDB_NAMESPACE::Histograms::STALL_MEMTABLE_COMPACTION_COUNT:
+ return 0xE;
+ case ROCKSDB_NAMESPACE::Histograms::STALL_L0_NUM_FILES_COUNT:
+ return 0xF;
+ case ROCKSDB_NAMESPACE::Histograms::HARD_RATE_LIMIT_DELAY_COUNT:
+ return 0x10;
+ case ROCKSDB_NAMESPACE::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT:
+ return 0x11;
+ 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_GC_MICROS:
+ return 0x2C;
+ case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS:
+ return 0x2D;
+ case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS:
+ return 0x2E;
+ 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 0xD:
+ return ROCKSDB_NAMESPACE::Histograms::STALL_L0_SLOWDOWN_COUNT;
+ case 0xE:
+ return ROCKSDB_NAMESPACE::Histograms::STALL_MEMTABLE_COMPACTION_COUNT;
+ case 0xF:
+ return ROCKSDB_NAMESPACE::Histograms::STALL_L0_NUM_FILES_COUNT;
+ case 0x10:
+ return ROCKSDB_NAMESPACE::Histograms::HARD_RATE_LIMIT_DELAY_COUNT;
+ case 0x11:
+ return ROCKSDB_NAMESPACE::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT;
+ 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 0x2C:
+ return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GC_MICROS;
+ case 0x2D:
+ return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS;
+ case 0x2E:
+ return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS;
+ 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<TransactionID> &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<jsize>(len));
+ if(jtransaction_ids == nullptr) {
+ // exception thrown: OutOfMemoryError
+ env->DeleteLocalRef(jkey);
+ return nullptr;
+ }
+
+ jlong *body = env->GetLongArrayElements(jtransaction_ids, nullptr);
+ 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<jlong>(transaction_ids[i]);
+ }
+ env->ReleaseLongArrayElements(jtransaction_ids, body, 0);
+
+ jobject jwaiting_transactions = env->CallObjectMethod(jtransaction,
+ mid, static_cast<jlong>(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<jlong>(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, "<init>", "(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<jsize>(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, "<init>", "([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 retieved
+ */
+ 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, "<init>", "(JJJJJJJJJJJJJJJJJJJ[BLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;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<String, String>
+ 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<String, String>
+ 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;
+ }
+
+ // Map<String, Long>
+ jobject jproperties_offsets = ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(
+ env, &table_properties.properties_offsets);
+ 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);
+ env->DeleteLocalRef(jreadable_properties);
+ return nullptr;
+ }
+
+ jobject jtable_properties = env->NewObject(jclazz, mid,
+ static_cast<jlong>(table_properties.data_size),
+ static_cast<jlong>(table_properties.index_size),
+ static_cast<jlong>(table_properties.index_partitions),
+ static_cast<jlong>(table_properties.top_level_index_size),
+ static_cast<jlong>(table_properties.index_key_is_user_key),
+ static_cast<jlong>(table_properties.index_value_is_delta_encoded),
+ static_cast<jlong>(table_properties.filter_size),
+ static_cast<jlong>(table_properties.raw_key_size),
+ static_cast<jlong>(table_properties.raw_value_size),
+ static_cast<jlong>(table_properties.num_data_blocks),
+ static_cast<jlong>(table_properties.num_entries),
+ static_cast<jlong>(table_properties.num_deletions),
+ static_cast<jlong>(table_properties.num_merge_operands),
+ static_cast<jlong>(table_properties.num_range_deletions),
+ static_cast<jlong>(table_properties.format_version),
+ static_cast<jlong>(table_properties.fixed_key_len),
+ static_cast<jlong>(table_properties.column_family_id),
+ static_cast<jlong>(table_properties.creation_time),
+ static_cast<jlong>(table_properties.oldest_key_time),
+ jcolumn_family_name,
+ jfilter_policy_name,
+ jcomparator_name,
+ jmerge_operator_name,
+ jprefix_extractor_name,
+ jproperty_collectors_names,
+ jcompression_name,
+ juser_collected_properties,
+ jreadable_properties,
+ jproperties_offsets
+ );
+
+ 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, "<init>",
+ "([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 retieved
+ */
+ 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 retieved
+ */
+ 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;
+ 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;
+ default:
+ // undefined/default
+ return ROCKSDB_NAMESPACE::ChecksumType::kCRC32c;
+ }
+ }
+};
+
+// 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, "<init>", "(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<jsize>(
+ 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;
+ }
+ jlong *body = env->GetLongArrayElements(joperation_properties, nullptr);
+ 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<jlong>(thread_status->op_properties[i]);
+ }
+ env->ReleaseLongArrayElements(joperation_properties, body, 0);
+
+ jobject jcfd = env->NewObject(jclazz, mid,
+ static_cast<jlong>(thread_status->thread_id),
+ ThreadTypeJni::toJavaThreadType(thread_status->thread_type),
+ jdb_name,
+ jcf_name,
+ OperationTypeJni::toJavaOperationType(thread_status->operation_type),
+ static_cast<jlong>(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;
+ 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;
+ 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, "<init>", "(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<jlong>(log_file->LogNumber()),
+ ROCKSDB_NAMESPACE::WalFileTypeJni::toJavaWalFileType(log_file->Type()),
+ static_cast<jlong>(log_file->StartSequence()),
+ static_cast<jlong>(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, "<init>", "([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<jint>(live_file_meta_data->level),
+ jfile_name,
+ jpath,
+ static_cast<jlong>(live_file_meta_data->size),
+ static_cast<jlong>(live_file_meta_data->smallest_seqno),
+ static_cast<jlong>(live_file_meta_data->largest_seqno),
+ jsmallest_key,
+ jlargest_key,
+ static_cast<jlong>(live_file_meta_data->num_reads_sampled),
+ static_cast<jboolean>(live_file_meta_data->being_compacted),
+ static_cast<jlong>(live_file_meta_data->num_entries),
+ static_cast<jlong>(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, "<init>", "(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<jlong>(sst_file_meta_data->size),
+ static_cast<jint>(sst_file_meta_data->smallest_seqno),
+ static_cast<jlong>(sst_file_meta_data->largest_seqno),
+ jsmallest_key,
+ jlargest_key,
+ static_cast<jlong>(sst_file_meta_data->num_reads_sampled),
+ static_cast<jboolean>(sst_file_meta_data->being_compacted),
+ static_cast<jlong>(sst_file_meta_data->num_entries),
+ static_cast<jlong>(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, "<init>", "(IJ[Lorg/rocksdb/SstFileMetaData;)V");
+ if (mid == nullptr) {
+ // exception thrown: NoSuchMethodException or OutOfMemoryError
+ return nullptr;
+ }
+
+ const jsize jlen =
+ static_cast<jsize>(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<jint>(level_meta_data->level),
+ static_cast<jlong>(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, "<init>", "(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<jsize>(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<jlong>(column_famly_meta_data->size),
+ static_cast<jlong>(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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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<const ROCKSDB_NAMESPACE::WalFilterJniCallback*,
+ AbstractWalFilterJni> {
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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 retieved
+ */
+ 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;
+ }
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // JAVA_ROCKSJNI_PORTAL_H_
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/ratelimiterjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/ratelimiterjni.cc
new file mode 100644
index 00000000..96b440dc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/ratelimiterjni.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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::RateLimiter>(
+ ROCKSDB_NAMESPACE::NewGenericRateLimiter(
+ static_cast<int64_t>(jrate_bytes_per_second),
+ static_cast<int64_t>(jrefill_period_micros),
+ static_cast<int32_t>(jfairness), rate_limiter_mode, jauto_tune));
+
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::RateLimiter>*>(
+ handle)
+ ->get()
+ ->GetTotalRequests();
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc
new file mode 100644
index 00000000..8f0037b3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_RemoveEmptyValueCompactionFilter.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 reinterpret_cast<jlong>(compaction_filter);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/restorejni.cc b/storage/rocksdb/rocksdb/java/rocksjni/restorejni.cc
new file mode 100644
index 00000000..9b605c70
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/restorejni.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "include/org_rocksdb_RestoreOptions.h"
+#include "rocksdb/utilities/backupable_db.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::RestoreOptions*>(jhandle);
+ assert(ropt);
+ delete ropt;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/rocks_callback_object.cc b/storage/rocksdb/rocksdb/java/rocksjni/rocks_callback_object.cc
new file mode 100644
index 00000000..73aa8613
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/rocks_callback_object.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#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<ROCKSDB_NAMESPACE::JniCallback*>(handle);
+ // @lint-ignore TXT4 T25377293 Grandfathered in
+} \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/rocksdb_exception_test.cc b/storage/rocksdb/rocksdb/java/rocksjni/rocksdb_exception_test.cc
new file mode 100644
index 00000000..d0fd834b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/rocksdb_exception_test.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#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/storage/rocksdb/rocksdb/java/rocksjni/rocksjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/rocksjni.cc
new file mode 100644
index 00000000..84175913
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/rocksjni.cc
@@ -0,0 +1,3406 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#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 "rocksjni/portal.h"
+
+#ifdef min
+#undef min
+#endif
+
+jlong rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, jstring jdb_path,
+ std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::Options&,
+ const std::string&, ROCKSDB_NAMESPACE::DB**)>
+ open_fn) {
+ const char* db_path = env->GetStringUTFChars(jdb_path, nullptr);
+ if (db_path == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return 0;
+ }
+
+ auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(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 reinterpret_cast<jlong>(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;)J
+ */
+jlong Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2(
+ JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path) {
+ return rocksdb_open_helper(
+ env, jopt_handle, jdb_path,
+ [](const ROCKSDB_NAMESPACE::Options& options, const std::string& db_path,
+ ROCKSDB_NAMESPACE::DB** db) {
+ return ROCKSDB_NAMESPACE::DB::OpenForReadOnly(options, db_path, db);
+ });
+}
+
+jlongArray rocksdb_open_helper(
+ JNIEnv* env, jlong jopt_handle, jstring jdb_path,
+ jobjectArray jcolumn_names, jlongArray jcolumn_options,
+ std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::DBOptions&, const std::string&,
+ const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor>&,
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>*,
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> column_families;
+ jboolean has_exception = JNI_FALSE;
+ ROCKSDB_NAMESPACE::JniUtil::byteStrings<std::string>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jopt_handle);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<jlong[]> results =
+ std::unique_ptr<jlong[]>(new jlong[resultsLen]);
+ results[0] = reinterpret_cast<jlong>(db);
+ for (int i = 1; i <= len_cols; i++) {
+ results[i] = reinterpret_cast<jlong>(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[J)[J
+ */
+jlongArray Java_org_rocksdb_RocksDB_openROnly__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,
+ [](const ROCKSDB_NAMESPACE::DBOptions& options,
+ const std::string& db_path,
+ const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor>&
+ column_families,
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>* handles,
+ ROCKSDB_NAMESPACE::DB** db) {
+ return ROCKSDB_NAMESPACE::DB::OpenForReadOnly(
+ options, db_path, column_families, handles, db);
+ });
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor>&,
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>*,
+ ROCKSDB_NAMESPACE::DB**)) &
+ ROCKSDB_NAMESPACE::DB::Open);
+}
+
+/*
+ * Class: org_rocksdb_RocksDB
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_RocksDB_disposeInternal(
+ JNIEnv*, jobject, jlong jhandle) {
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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<std::string> 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::DB*>(jhandle);
+ jboolean has_exception = JNI_FALSE;
+ const std::string cf_name =
+ ROCKSDB_NAMESPACE::JniUtil::byteString<std::string>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(
+ 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(jhandle);
+ auto* cf_options = reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(
+ jcf_options_handle);
+ jboolean has_exception = JNI_FALSE;
+ std::vector<std::string> cf_names;
+ ROCKSDB_NAMESPACE::JniUtil::byteStrings<std::string>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::DB*>(jhandle);
+ const jsize jlen = env->GetArrayLength(jcf_options_handles);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> cf_descriptors;
+ cf_descriptors.reserve(jlen);
+
+ jboolean jcf_options_handles_is_copy = JNI_FALSE;
+ jlong *jcf_options_handles_elems = env->GetLongArrayElements(jcf_options_handles, &jcf_options_handles_is_copy);
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(
+ jcf_options_handles_elems[i]);
+ jbyteArray jcf_name = static_cast<jbyteArray>(
+ 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<std::string>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<char*>(key), jkey_len);
+ ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
+ ROCKSDB_NAMESPACE::WriteOptions();
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<char*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
+ ROCKSDB_NAMESPACE::WriteOptions();
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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 = env->GetByteArrayElements(jkey, nullptr);
+ if (key == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return false;
+ }
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(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);
+ }
+
+ // trigger java unref on key and value.
+ // 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 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
+ ROCKSDB_NAMESPACE::WriteOptions();
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<char*>(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<char*>(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<ROCKSDB_NAMESPACE::DB*>(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<char*>(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<char*>(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);
+
+ // TODO(yhchiang): we might save one memory allocation here by adding
+ // a DB::Get() function which takes preallocated jbyte* as input.
+ std::string cvalue;
+ ROCKSDB_NAMESPACE::Status s;
+ if (column_family_handle != nullptr) {
+ s = db->Get(read_options, column_family_handle, key_slice, &cvalue);
+ } else {
+ // backwards compatibility
+ s = db->Get(read_options, key_slice, &cvalue);
+ }
+
+ 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 cvalue_len = static_cast<jint>(cvalue.size());
+ const jint length = std::min(jval_len, cvalue_len);
+
+ memcpy(value, cvalue.c_str(), length);
+
+ *has_exception = false;
+ return cvalue_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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
+ ROCKSDB_NAMESPACE::WriteOptions();
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* ro_opt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<char*>(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<char*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
+ ROCKSDB_NAMESPACE::WriteOptions();
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* wb = reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* wbwi =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<char*>(key), jkey_len);
+
+ std::string value;
+ ROCKSDB_NAMESPACE::Status s;
+ if (column_family_handle != nullptr) {
+ s = db->Get(read_opt, column_family_handle, key_slice, &value);
+ } else {
+ // backwards compatibility
+ s = db->Get(read_opt, key_slice, &value);
+ }
+
+ // cleanup
+ delete[] key;
+
+ if (s.IsNotFound()) {
+ return nullptr;
+ }
+
+ if (s.ok()) {
+ jbyteArray jret_value = ROCKSDB_NAMESPACE::JniUtil::copyBytes(env, value);
+ 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle),
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto& ro_opt =
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<char*>(key), jkey_len);
+
+ // TODO(yhchiang): we might save one memory allocation here by adding
+ // a DB::Get() function which takes preallocated jbyte* as input.
+ std::string cvalue;
+ ROCKSDB_NAMESPACE::Status s;
+ if (column_family_handle != nullptr) {
+ s = db->Get(read_options, column_family_handle, key_slice, &cvalue);
+ } else {
+ // backwards compatibility
+ s = db->Get(read_options, key_slice, &cvalue);
+ }
+
+ // 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 cvalue_len = static_cast<jint>(cvalue.size());
+ const jint length = std::min(jval_len, cvalue_len);
+
+ env->SetByteArrayRegion(
+ jval, jval_off, length,
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(cvalue.c_str())));
+ if (env->ExceptionCheck()) {
+ // exception thrown: OutOfMemoryError
+ *has_exception = true;
+ return kStatusError;
+ }
+
+ *has_exception = false;
+ return cvalue_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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle),
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto& ro_opt =
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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(
+ JNIEnv* env, std::vector<std::pair<jbyte*, jobject>>& keys_to_free) {
+ auto end = keys_to_free.end();
+ for (auto it = keys_to_free.begin(); it != end; ++it) {
+ delete[] it->first;
+ env->DeleteLocalRef(it->second);
+ }
+ keys_to_free.clear();
+}
+
+/**
+ * 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcfh[i]);
+ cf_handles.push_back(cf_handle);
+ }
+ env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT);
+ }
+
+ const jsize len_keys = env->GetArrayLength(jkeys);
+ if (env->EnsureLocalCapacity(len_keys) != 0) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ jint* jkey_off = env->GetIntArrayElements(jkey_offs, nullptr);
+ if (jkey_off == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ jint* jkey_len = env->GetIntArrayElements(jkey_lens, nullptr);
+ if (jkey_len == nullptr) {
+ // exception thrown: OutOfMemoryError
+ env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT);
+ return nullptr;
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::Slice> keys;
+ std::vector<std::pair<jbyte*, jobject>> keys_to_free;
+ 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(env, keys_to_free);
+ return nullptr;
+ }
+
+ jbyteArray jkey_ba = reinterpret_cast<jbyteArray>(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(env, keys_to_free);
+ return nullptr;
+ }
+
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key), len_key);
+ keys.push_back(key_slice);
+
+ keys_to_free.push_back(std::pair<jbyte*, jobject>(key, jkey));
+ }
+
+ // cleanup jkey_off and jken_len
+ env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT);
+ env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT);
+
+ std::vector<std::string> values;
+ std::vector<ROCKSDB_NAMESPACE::Status> 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(env, keys_to_free);
+
+ // prepare the results
+ jobjectArray jresults = ROCKSDB_NAMESPACE::ByteJni::new2dByteArray(
+ env, static_cast<jsize>(s.size()));
+ if (jresults == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ // TODO(AR) it is not clear to me why EnsureLocalCapacity is needed for the
+ // loop as we cleanup references with env->DeleteLocalRef(jentry_value);
+ if (env->EnsureLocalCapacity(static_cast<jint>(s.size())) != 0) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ // add to the jresults
+ for (std::vector<ROCKSDB_NAMESPACE::Status>::size_type i = 0; i != s.size();
+ i++) {
+ if (s[i].ok()) {
+ std::string* value = &values[i];
+ const jsize jvalue_len = static_cast<jsize>(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<jsize>(jvalue_len),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(value->c_str())));
+ if (env->ExceptionCheck()) {
+ // exception thrown: ArrayIndexOutOfBoundsException
+ env->DeleteLocalRef(jentry_value);
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(jresults, static_cast<jsize>(i), jentry_value);
+ if (env->ExceptionCheck()) {
+ // exception thrown: ArrayIndexOutOfBoundsException
+ env->DeleteLocalRef(jentry_value);
+ return nullptr;
+ }
+
+ env->DeleteLocalRef(jentry_value);
+ }
+ }
+
+ return jresults;
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle),
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle),
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle), jkeys,
+ jkey_offs, jkey_lens, jcolumn_family_handles);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ ROCKSDB_NAMESPACE::ReadOptions read_opts =
+ jread_opts_handle == 0
+ ? ROCKSDB_NAMESPACE::ReadOptions()
+ : *(reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(
+ 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<char*>(key), jkey_len);
+
+ const bool exists = db->KeyMayExist(
+ read_opts, cf_handle, key_slice, value, value_found);
+
+ // cleanup
+ delete[] key;
+
+ 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<jboolean>(exists);
+}
+
+/*
+ * 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<jsize>(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<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto& read_options =
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ auto& read_options =
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto& read_options =
+ *reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcfh[i]);
+ cf_handles.push_back(cf_handle);
+ }
+
+ env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT);
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::Iterator*> iterators;
+ ROCKSDB_NAMESPACE::Status s =
+ db->NewIterators(read_options, cf_handles, &iterators);
+ if (s.ok()) {
+ jlongArray jLongArray =
+ env->NewLongArray(static_cast<jsize>(iterators.size()));
+ if (jLongArray == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ for (std::vector<ROCKSDB_NAMESPACE::Iterator*>::size_type i = 0;
+ i < iterators.size(); i++) {
+ env->SetLongArrayRegion(
+ jLongArray, static_cast<jsize>(i), 1,
+ const_cast<jlong*>(reinterpret_cast<const jlong*>(&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<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ const ROCKSDB_NAMESPACE::Snapshot* snapshot = db->GetSnapshot();
+ return reinterpret_cast<jlong>(snapshot);
+}
+
+/*
+ * Method: releaseSnapshot
+ * Signature: (JJ)V
+ */
+void Java_org_rocksdb_RocksDB_releaseSnapshot(
+ JNIEnv*, jobject, jlong db_handle,
+ jlong snapshot_handle) {
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(db_handle);
+ auto* snapshot =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Snapshot*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+
+ std::map<std::string, std::string> 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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;
+
+ jboolean jranges_is_copy = JNI_FALSE;
+ jlong* jranges = env->GetLongArrayElements(jrange_slice_handles,
+ &jranges_is_copy);
+ if (jranges == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ auto ranges = std::unique_ptr<ROCKSDB_NAMESPACE::Range[]>(
+ new ROCKSDB_NAMESPACE::Range[range_count]);
+ for (jsize i = 0; i < jlen; ++i) {
+ auto* start = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jranges[i]);
+ auto* limit = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jranges[++i]);
+ ranges.get()[i] = ROCKSDB_NAMESPACE::Range(*start, *limit);
+ }
+
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+
+ auto sizes = std::unique_ptr<uint64_t[]>(new uint64_t[range_count]);
+ db->GetApproximateSizes(cf_handle, ranges.get(),
+ static_cast<int>(range_count), sizes.get(),
+ static_cast<uint8_t>(jinclude_flags));
+
+ // release LongArrayElements
+ env->ReleaseLongArrayElements(jrange_slice_handles, jranges, JNI_ABORT);
+
+ // prepare results
+ auto results = std::unique_ptr<jlong[]>(new jlong[range_count]);
+ for (size_t i = 0; i < range_count; ++i) {
+ results.get()[i] = static_cast<jlong>(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<ROCKSDB_NAMESPACE::Slice*>(jstartHandle);
+ auto* limit = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jlimitHandle);
+ const ROCKSDB_NAMESPACE::Range range(*start, *limit);
+
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+
+ uint64_t count = 0;
+ uint64_t sizes = 0;
+ db->GetApproximateMemTableStats(cf_handle, range, &count, &sizes);
+
+ // prepare results
+ jlong results[2] = {
+ static_cast<jlong>(count),
+ static_cast<jlong>(sizes)};
+
+ const jsize jcount = static_cast<jsize>(count);
+ jlongArray jsizes = env->NewLongArray(jcount);
+ if (jsizes == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetLongArrayRegion(jsizes, 0, jcount, 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<std::string>(
+ 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<std::string>(
+ 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<ROCKSDB_NAMESPACE::CompactRangeOptions*>(
+ jcompact_range_opts_handle);
+ }
+
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<std::string, std::string> 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<jstring>(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<jstring>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ 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<std::string, std::string> 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<jstring>(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<jstring>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto s = db->SetDBOptions(options_map);
+ if (!s.ok()) {
+ ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
+ }
+}
+
+/*
+ * 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<std::string> 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<ROCKSDB_NAMESPACE::CompactionOptions*>(
+ jcompaction_opts_handle);
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+
+ ROCKSDB_NAMESPACE::CompactionJobInfo* compaction_job_info = nullptr;
+ if (jcompaction_job_info_handle != 0) {
+ compaction_job_info =
+ reinterpret_cast<ROCKSDB_NAMESPACE::CompactionJobInfo*>(
+ jcompaction_job_info_handle);
+ }
+
+ std::vector<std::string> output_file_names;
+ auto s = db->CompactFiles(*compaction_opts, cf_handle, input_file_names,
+ static_cast<int>(joutput_level), static_cast<int>(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: pauseBackgroundWork
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_RocksDB_pauseBackgroundWork(
+ JNIEnv* env, jobject, jlong jdb_handle) {
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ jboolean has_exception = JNI_FALSE;
+ const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* flush_opts =
+ reinterpret_cast<ROCKSDB_NAMESPACE::FlushOptions*>(jflush_opts_handle);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ return db->GetLatestSequenceNumber();
+}
+
+/*
+ * Class: org_rocksdb_RocksDB
+ * Method: setPreserveDeletesSequenceNumber
+ * Signature: (JJ)Z
+ */
+jboolean JNICALL Java_org_rocksdb_RocksDB_setPreserveDeletesSequenceNumber(
+ JNIEnv*, jobject, jlong jdb_handle, jlong jseq_number) {
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ if (db->SetPreserveDeletesSequenceNumber(
+ static_cast<uint64_t>(jseq_number))) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ std::vector<std::string> 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ std::vector<std::unique_ptr<ROCKSDB_NAMESPACE::LogFile>> 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<jsize>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::SequenceNumber sequence_number =
+ static_cast<ROCKSDB_NAMESPACE::SequenceNumber>(jsequence_number);
+ std::unique_ptr<ROCKSDB_NAMESPACE::TransactionLogIterator> iter;
+ ROCKSDB_NAMESPACE::Status s = db->GetUpdatesSince(sequence_number, &iter);
+ if (s.ok()) {
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ std::vector<ROCKSDB_NAMESPACE::LiveFileMetaData> live_files_meta_data;
+ db->GetLiveFilesMetaData(&live_files_meta_data);
+
+ // convert to Java type
+ const jsize jlen = static_cast<jsize>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<std::string> 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* column_family =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ auto* ifo = reinterpret_cast<ROCKSDB_NAMESPACE::IngestExternalFileOptions*>(
+ 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<ROCKSDB_NAMESPACE::DB*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* cf_handle = db_handle->DefaultColumnFamily();
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<uint32_t>(table_properties_collection.size()));
+ if (jhash_map == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
+ const std::string,
+ const std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties>, jobject,
+ jobject>
+ fn_map_kv =
+ [env](const std::pair<const std::string,
+ const std::shared_ptr<
+ const ROCKSDB_NAMESPACE::TableProperties>>&
+ kv) {
+ jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
+ env, &(kv.first), false);
+ if (env->ExceptionCheck()) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(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<std::pair<jobject, jobject>>(nullptr);
+ }
+
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(
+ static_cast<jobject>(jkey),
+ static_cast<jobject>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ const jsize jlen = env->GetArrayLength(jrange_slice_handles);
+ jboolean jrange_slice_handles_is_copy = JNI_FALSE;
+ jlong *jrange_slice_handle = env->GetLongArrayElements(
+ jrange_slice_handles, &jrange_slice_handles_is_copy);
+ if (jrange_slice_handle == nullptr) {
+ // exception occurred
+ return nullptr;
+ }
+
+ const size_t ranges_len = static_cast<size_t>(jlen / 2);
+ auto ranges = std::unique_ptr<ROCKSDB_NAMESPACE::Range[]>(
+ new ROCKSDB_NAMESPACE::Range[ranges_len]);
+ for (jsize i = 0, j = 0; i < jlen; ++i) {
+ auto* start =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jrange_slice_handle[i]);
+ auto* limit =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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] = reinterpret_cast<jlong>(begin);
+ slice_handles[1] = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle;
+ if (jcf_handle == 0) {
+ cf_handle = db->DefaultColumnFamily();
+ } else {
+ cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ }
+ db->PromoteL0(cf_handle, static_cast<int>(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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ ROCKSDB_NAMESPACE::TraceOptions trace_options;
+ trace_options.max_trace_file_size =
+ static_cast<uint64_t>(jmax_trace_file_size);
+ // transfer ownership of trace writer from Java to C++
+ auto trace_writer =
+ std::unique_ptr<ROCKSDB_NAMESPACE::TraceWriterJniCallback>(
+ reinterpret_cast<ROCKSDB_NAMESPACE::TraceWriterJniCallback*>(
+ 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
+ */
+JNIEXPORT void JNICALL Java_org_rocksdb_RocksDB_endTrace(
+ JNIEnv* env, jobject, jlong jdb_handle) {
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto s = db->EndTrace();
+ 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<ROCKSDB_NAMESPACE::Options*>(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<ROCKSDB_NAMESPACE::Slice>& slice,
+ std::vector<std::unique_ptr<jbyte[]>>& 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<jbyteArray>(jArray);
+ jsize len_ba = env->GetArrayLength(jba);
+ ranges_to_free.push_back(std::unique_ptr<jbyte[]>(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<char*>(ranges_to_free.back().get()), len_ba));
+ return true;
+}
+/*
+ * Class: org_rocksdb_RocksDB
+ * Method: deleteFilesInRanges
+ * Signature: (JJLjava/util/List;Z)V
+ */
+JNIEXPORT void JNICALL 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<ROCKSDB_NAMESPACE::RangePtr> rangesVector;
+ std::vector<std::unique_ptr<ROCKSDB_NAMESPACE::Slice>> slices;
+ std::vector<std::unique_ptr<jbyte[]>> ranges_to_free;
+ for (jsize i = 0; (i + 1) < length; i += 2) {
+ slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
+ if (!get_slice_helper(env, ranges, i, slices.back(), ranges_to_free)) {
+ // exception thrown
+ return;
+ }
+
+ slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
+ 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<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* column_family =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/slice.cc b/storage/rocksdb/rocksdb/java/rocksjni/slice.cc
new file mode 100644
index 00000000..d9e58992
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/slice.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "include/org_rocksdb_AbstractSlice.h"
+#include "include/org_rocksdb_DirectSlice.h"
+#include "include/org_rocksdb_Slice.h"
+#include "rocksdb/slice.h"
+#include "rocksjni/portal.h"
+
+// <editor-fold desc="org.rocksdb.AbstractSlice>
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: createNewSliceFromString
+ * Signature: (Ljava/lang/String;)J
+ */
+jlong Java_org_rocksdb_AbstractSlice_createNewSliceFromString(JNIEnv* env,
+ jclass /*jcls*/,
+ jstring jstr) {
+ const auto* str = env->GetStringUTFChars(jstr, nullptr);
+ if (str == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return 0;
+ }
+
+ const size_t len = strlen(str);
+
+ // NOTE: buf will be deleted in the
+ // Java_org_rocksdb_Slice_disposeInternalBuf or
+ // or Java_org_rocksdb_DirectSlice_disposeInternalBuf methods
+ char* buf = new char[len + 1];
+ memcpy(buf, str, len);
+ buf[len] = 0;
+ env->ReleaseStringUTFChars(jstr, str);
+
+ const auto* slice = new ROCKSDB_NAMESPACE::Slice(buf);
+ return reinterpret_cast<jlong>(slice);
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: size0
+ * Signature: (J)I
+ */
+jint Java_org_rocksdb_AbstractSlice_size0(JNIEnv* /*env*/, jobject /*jobj*/,
+ jlong handle) {
+ const auto* slice = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+ return static_cast<jint>(slice->size());
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: empty0
+ * Signature: (J)Z
+ */
+jboolean Java_org_rocksdb_AbstractSlice_empty0(JNIEnv* /*env*/,
+ jobject /*jobj*/, jlong handle) {
+ const auto* slice = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+ return slice->empty();
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: toString0
+ * Signature: (JZ)Ljava/lang/String;
+ */
+jstring Java_org_rocksdb_AbstractSlice_toString0(JNIEnv* env, jobject /*jobj*/,
+ jlong handle, jboolean hex) {
+ const auto* slice = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const std::string s = slice->ToString(hex);
+ return env->NewStringUTF(s.c_str());
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: compare0
+ * Signature: (JJ)I;
+ */
+jint Java_org_rocksdb_AbstractSlice_compare0(JNIEnv* /*env*/, jobject /*jobj*/,
+ jlong handle, jlong otherHandle) {
+ const auto* slice = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const auto* otherSlice =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(otherHandle);
+ return slice->compare(*otherSlice);
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: startsWith0
+ * Signature: (JJ)Z;
+ */
+jboolean Java_org_rocksdb_AbstractSlice_startsWith0(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong handle,
+ jlong otherHandle) {
+ const auto* slice = reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const auto* otherSlice =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(otherHandle);
+ return slice->starts_with(*otherSlice);
+}
+
+/*
+ * Class: org_rocksdb_AbstractSlice
+ * Method: disposeInternal
+ * Signature: (J)V
+ */
+void Java_org_rocksdb_AbstractSlice_disposeInternal(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong handle) {
+ delete reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(handle);
+}
+
+// </editor-fold>
+
+// <editor-fold desc="org.rocksdb.Slice>
+
+/*
+ * 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 reinterpret_cast<jlong>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const jsize len = static_cast<jsize>(slice->size());
+ const jbyteArray data = env->NewByteArray(len);
+ if (data == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(
+ data, 0, len,
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::Slice*>(handle);
+ slice->remove_prefix(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<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const char* buf = slice->data_ - internalBufferOffset;
+ delete[] buf;
+}
+
+// </editor-fold>
+
+// <editor-fold desc="org.rocksdb.DirectSlice>
+
+/*
+ * Class: org_rocksdb_DirectSlice
+ * Method: createNewDirectSlice0
+ * Signature: (Ljava/nio/ByteBuffer;I)J
+ */
+jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice0(JNIEnv* env,
+ jclass /*jcls*/,
+ jobject data,
+ jint length) {
+ assert(data != nullptr);
+ 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<char*>(data_addr);
+ const auto* slice = new ROCKSDB_NAMESPACE::Slice(ptrData, length);
+ return reinterpret_cast<jlong>(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<char*>(data_addr);
+ const auto* slice = new ROCKSDB_NAMESPACE::Slice(ptrData);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Slice*>(handle);
+ return env->NewDirectByteBuffer(const_cast<char*>(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<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::Slice*>(handle);
+ const char* buf = slice->data_ - internalBufferOffset;
+ delete[] buf;
+}
+
+// </editor-fold>
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/snapshot.cc b/storage/rocksdb/rocksdb/java/rocksjni/snapshot.cc
new file mode 100644
index 00000000..2a1265a5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/snapshot.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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<ROCKSDB_NAMESPACE::Snapshot*>(jsnapshot_handle);
+ return snapshot->GetSequenceNumber();
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/sst_file_manager.cc b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_manager.cc
new file mode 100644
index 00000000..e7445d80
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_manager.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+
+#include "include/org_rocksdb_SstFileManager.h"
+#include "rocksdb/sst_file_manager.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<ROCKSDB_NAMESPACE::Env*>(jenv_handle);
+ ROCKSDB_NAMESPACE::Status s;
+ ROCKSDB_NAMESPACE::SstFileManager* sst_file_manager = nullptr;
+
+ if (jlogger_handle != 0) {
+ auto* sptr_logger =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Logger>*>(
+ 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<ROCKSDB_NAMESPACE::SstFileManager>(sst_file_manager);
+
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<uint32_t>(tracked_files.size()));
+ if (jtracked_files == 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<const std::string, const uint64_t>& pair) {
+ const jstring jtracked_file_path =
+ env->NewStringUTF(pair.first.c_str());
+ if (jtracked_file_path == nullptr) {
+ // an error occurred
+ return std::unique_ptr<std::pair<jobject, jobject>>(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<std::pair<jobject, jobject>>(nullptr);
+ }
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::SstFileManager>*>(
+ jhandle);
+ delete sptr_sst_file_manager;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/sst_file_reader_iterator.cc b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_reader_iterator.cc
new file mode 100644
index 00000000..29cf2c5d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_reader_iterator.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<char*>(target),
+ jtarget_len);
+
+ auto* it = reinterpret_cast<ROCKSDB_NAMESPACE::Iterator*>(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<char*>(target),
+ jtarget_len);
+
+ auto* it = reinterpret_cast<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice key_slice = it->key();
+
+ jbyteArray jkey = env->NewByteArray(static_cast<jsize>(key_slice.size()));
+ if (jkey == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ env->SetByteArrayRegion(
+ jkey, 0, static_cast<jsize>(key_slice.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice value_slice = it->value();
+
+ jbyteArray jkeyValue =
+ env->NewByteArray(static_cast<jsize>(value_slice.size()));
+ if (jkeyValue == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ env->SetByteArrayRegion(jkeyValue, 0, static_cast<jsize>(value_slice.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice key_slice = it->key();
+ return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, key_slice, jtarget,
+ jtarget_off, jtarget_len);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Iterator*>(handle);
+ ROCKSDB_NAMESPACE::Slice value_slice = it->value();
+ return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, value_slice, jtarget,
+ jtarget_off, jtarget_len);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::Iterator*>(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<ROCKSDB_NAMESPACE::Iterator*>(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);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/sst_file_readerjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_readerjni.cc
new file mode 100644
index 00000000..d79a2c09
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_readerjni.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <string>
+
+#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/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<const ROCKSDB_NAMESPACE::Options *>(joptions);
+ ROCKSDB_NAMESPACE::SstFileReader *sst_file_reader =
+ new ROCKSDB_NAMESPACE::SstFileReader(*options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::SstFileReader *>(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<ROCKSDB_NAMESPACE::SstFileReader *>(jhandle);
+ auto *read_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions *>(jread_options_handle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::SstFileReader *>(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<ROCKSDB_NAMESPACE::SstFileReader *>(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<ROCKSDB_NAMESPACE::SstFileReader *>(jhandle);
+ std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties> tp =
+ sst_file_reader->GetTableProperties();
+ jobject jtable_properties =
+ ROCKSDB_NAMESPACE::TablePropertiesJni::fromCppTableProperties(
+ env, *(tp.get()));
+ return jtable_properties;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/sst_file_writerjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_writerjni.cc
new file mode 100644
index 00000000..5ca8c530
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/sst_file_writerjni.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <string>
+
+#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/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<ROCKSDB_NAMESPACE::ComparatorJniCallback *>(
+ jcomparator_handle);
+ break;
+
+ // JAVA_NATIVE_COMPARATOR_WRAPPER
+ case 0x1:
+ comparator =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Comparator *>(jcomparator_handle);
+ break;
+ }
+ auto *env_options =
+ reinterpret_cast<const ROCKSDB_NAMESPACE::EnvOptions *>(jenvoptions);
+ auto *options =
+ reinterpret_cast<const ROCKSDB_NAMESPACE::Options *>(joptions);
+ ROCKSDB_NAMESPACE::SstFileWriter *sst_file_writer =
+ new ROCKSDB_NAMESPACE::SstFileWriter(*env_options, *options, comparator);
+ return reinterpret_cast<jlong>(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<const ROCKSDB_NAMESPACE::EnvOptions *>(jenvoptions);
+ auto *options =
+ reinterpret_cast<const ROCKSDB_NAMESPACE::Options *>(joptions);
+ ROCKSDB_NAMESPACE::SstFileWriter *sst_file_writer =
+ new ROCKSDB_NAMESPACE::SstFileWriter(*env_options, *options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::Slice *>(jkey_handle);
+ auto *value_slice =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice *>(jvalue_handle);
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<char *>(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<char *>(value),
+ env->GetArrayLength(jval));
+
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::SstFileWriter *>(jdb_handle);
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Slice *>(jkey_handle);
+ auto *value_slice =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice *>(jvalue_handle);
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<char *>(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<char *>(value),
+ env->GetArrayLength(jval));
+
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<char *>(key),
+ env->GetArrayLength(jkey));
+
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::Slice *>(jkey_handle);
+ ROCKSDB_NAMESPACE::Status s =
+ reinterpret_cast<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::SstFileWriter *>(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<ROCKSDB_NAMESPACE::SstFileWriter *>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/statistics.cc b/storage/rocksdb/rocksdb/java/rocksjni/statistics.cc
new file mode 100644
index 00000000..f59e79e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/statistics.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+#include <set>
+
+#include "include/org_rocksdb_Statistics.h"
+#include "rocksdb/statistics.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<ROCKSDB_NAMESPACE::Statistics>* pSptr_other_statistics =
+ nullptr;
+ if (jother_statistics_handle > 0) {
+ pSptr_other_statistics =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ jother_statistics_handle);
+ }
+
+ std::set<uint32_t> 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<ROCKSDB_NAMESPACE::Statistics> sptr_other_statistics =
+ nullptr;
+ if (pSptr_other_statistics != nullptr) {
+ sptr_other_statistics = *pSptr_other_statistics;
+ }
+
+ auto* pSptr_statistics =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::StatisticsJni>(
+ new ROCKSDB_NAMESPACE::StatisticsJni(sptr_other_statistics,
+ histograms));
+
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ jhandle);
+ assert(pSptr_statistics != nullptr);
+ auto ticker = ROCKSDB_NAMESPACE::TickerTypeJni::toCppTickers(jticker_type);
+ uint64_t count = pSptr_statistics->get()->getTickerCount(ticker);
+ return static_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<ROCKSDB_NAMESPACE::Histograms>(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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ 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<std::shared_ptr<ROCKSDB_NAMESPACE::Statistics>*>(
+ jhandle);
+ assert(pSptr_statistics != nullptr);
+ auto str = pSptr_statistics->get()->ToString();
+ return env->NewStringUTF(str.c_str());
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.cc b/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.cc
new file mode 100644
index 00000000..afb9d6cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<Statistics> stats)
+ : StatisticsImpl(stats), m_ignore_histograms() {}
+
+StatisticsJni::StatisticsJni(std::shared_ptr<Statistics> stats,
+ const std::set<uint32_t> 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;
+}
+// @lint-ignore TXT4 T25377293 Grandfathered in
+}; // namespace ROCKSDB_NAMESPACE \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.h b/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.h
new file mode 100644
index 00000000..51e27914
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/statisticsjni.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <set>
+#include <string>
+#include "rocksdb/statistics.h"
+#include "monitoring/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class StatisticsJni : public StatisticsImpl {
+ public:
+ StatisticsJni(std::shared_ptr<Statistics> stats);
+ StatisticsJni(std::shared_ptr<Statistics> stats,
+ const std::set<uint32_t> ignore_histograms);
+ virtual bool HistEnabledForType(uint32_t type) const override;
+
+ private:
+ const std::set<uint32_t> m_ignore_histograms;
+ };
+
+ } // namespace ROCKSDB_NAMESPACE
+
+// @lint-ignore TXT4 T25377293 Grandfathered in
+#endif // JAVA_ROCKSJNI_STATISTICSJNI_H_ \ No newline at end of file
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/table.cc b/storage/rocksdb/rocksdb/java/rocksjni/table.cc
new file mode 100644
index 00000000..9e3f4b66
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/table.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include "include/org_rocksdb_BlockBasedTableConfig.h"
+#include "include/org_rocksdb_PlainTableConfig.h"
+#include "portal.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/filter_policy.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<ROCKSDB_NAMESPACE::EncodingType>(jencoding_type);
+ options.full_scan_mode = jfull_scan_mode;
+ options.store_index_in_file = jstore_index_in_file;
+ return reinterpret_cast<jlong>(
+ ROCKSDB_NAMESPACE::NewPlainTableFactory(options));
+}
+
+/*
+ * Class: org_rocksdb_BlockBasedTableConfig
+ * Method: newTableFactoryHandle
+ * Signature: (ZZZZBBDBZJJJJIIIJZZJZZIIZZJIJI)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_cache_compressed_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 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, jlong jblock_cache_size,
+ jint jblock_cache_num_shard_bits, jlong jblock_cache_compressed_size,
+ jint jblock_cache_compressed_num_shard_bits) {
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions options;
+ options.cache_index_and_filter_blocks =
+ static_cast<bool>(jcache_index_and_filter_blocks);
+ options.cache_index_and_filter_blocks_with_high_priority =
+ static_cast<bool>(jcache_index_and_filter_blocks_with_high_priority);
+ options.pin_l0_filter_and_index_blocks_in_cache =
+ static_cast<bool>(jpin_l0_filter_and_index_blocks_in_cache);
+ options.pin_top_level_index_and_filter =
+ static_cast<bool>(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<double>(jdata_block_hash_table_util_ratio);
+ options.checksum = ROCKSDB_NAMESPACE::ChecksumTypeJni::toCppChecksumType(
+ jchecksum_type_value);
+ options.no_block_cache = static_cast<bool>(jno_block_cache);
+ if (options.no_block_cache) {
+ options.block_cache = nullptr;
+ } else {
+ if (jblock_cache_handle > 0) {
+ std::shared_ptr<ROCKSDB_NAMESPACE::Cache> *pCache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Cache> *>(
+ 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<size_t>(jblock_cache_size),
+ static_cast<int>(jblock_cache_num_shard_bits));
+ } else {
+ options.block_cache = ROCKSDB_NAMESPACE::NewLRUCache(
+ static_cast<size_t>(jblock_cache_size));
+ }
+ } else {
+ options.no_block_cache = true;
+ options.block_cache = nullptr;
+ }
+ }
+ if (jpersistent_cache_handle > 0) {
+ std::shared_ptr<ROCKSDB_NAMESPACE::PersistentCache> *pCache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::PersistentCache> *>(
+ jpersistent_cache_handle);
+ options.persistent_cache = *pCache;
+ }
+ if (jblock_cache_compressed_handle > 0) {
+ std::shared_ptr<ROCKSDB_NAMESPACE::Cache> *pCache =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Cache> *>(
+ jblock_cache_compressed_handle);
+ options.block_cache_compressed = *pCache;
+ } else if (jblock_cache_compressed_size > 0) {
+ if (jblock_cache_compressed_num_shard_bits > 0) {
+ options.block_cache_compressed = ROCKSDB_NAMESPACE::NewLRUCache(
+ static_cast<size_t>(jblock_cache_compressed_size),
+ static_cast<int>(jblock_cache_compressed_num_shard_bits));
+ } else {
+ options.block_cache_compressed = ROCKSDB_NAMESPACE::NewLRUCache(
+ static_cast<size_t>(jblock_cache_compressed_size));
+ }
+ }
+ options.block_size = static_cast<size_t>(jblock_size);
+ options.block_size_deviation = static_cast<int>(jblock_size_deviation);
+ options.block_restart_interval = static_cast<int>(jblock_restart_interval);
+ options.index_block_restart_interval = static_cast<int>(jindex_block_restart_interval);
+ options.metadata_block_size = static_cast<uint64_t>(jmetadata_block_size);
+ options.partition_filters = static_cast<bool>(jpartition_filters);
+ options.use_delta_encoding = static_cast<bool>(juse_delta_encoding);
+ if (jfilter_policy_handle > 0) {
+ std::shared_ptr<ROCKSDB_NAMESPACE::FilterPolicy> *pFilterPolicy =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::FilterPolicy> *>(
+ jfilter_policy_handle);
+ options.filter_policy = *pFilterPolicy;
+ }
+ options.whole_key_filtering = static_cast<bool>(jwhole_key_filtering);
+ options.verify_compression = static_cast<bool>(jverify_compression);
+ options.read_amp_bytes_per_bit = static_cast<uint32_t>(jread_amp_bytes_per_bit);
+ options.format_version = static_cast<uint32_t>(jformat_version);
+ options.enable_index_compression = static_cast<bool>(jenable_index_compression);
+ options.block_align = static_cast<bool>(jblock_align);
+
+ return reinterpret_cast<jlong>(
+ ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(options));
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/table_filter.cc b/storage/rocksdb/rocksdb/java/rocksjni/table_filter.cc
new file mode 100644
index 00000000..a9024ce8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/table_filter.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+
+#include "include/org_rocksdb_AbstractTableFilter.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 reinterpret_cast<jlong>(table_filter_jnicallback);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.cc
new file mode 100644
index 00000000..d1699548
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<bool>(result);
+ };
+}
+
+std::function<bool(const ROCKSDB_NAMESPACE::TableProperties&)>
+TableFilterJniCallback::GetTableFilterFunction() {
+ return m_table_filter_function;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.h
new file mode 100644
index 00000000..b5804737
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/table_filter_jnicallback.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <functional>
+#include <memory>
+
+#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<bool(const ROCKSDB_NAMESPACE::TableProperties&)>
+ GetTableFilterFunction();
+
+ private:
+ jmethodID m_jfilter_methodid;
+ std::function<bool(const ROCKSDB_NAMESPACE::TableProperties&)>
+ m_table_filter_function;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/thread_status.cc b/storage/rocksdb/rocksdb/java/rocksjni/thread_status.cc
new file mode 100644
index 00000000..a5811ec1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/thread_status.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "portal.h"
+#include "include/org_rocksdb_ThreadStatus.h"
+#include "rocksdb/thread_status.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<uint64_t>(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<int>(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<uint64_t[]> 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<uint64_t>(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/storage/rocksdb/rocksdb/java/rocksjni/trace_writer.cc b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer.cc
new file mode 100644
index 00000000..3beafd45
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_AbstractTraceWriter.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 reinterpret_cast<jlong>(trace_writer);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.cc
new file mode 100644
index 00000000..b6566d33
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<Status> 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<Status> 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<uint64_t>(jfile_size);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.h
new file mode 100644
index 00000000..eb2a8b0f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/trace_writer_jnicallback.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <memory>
+
+#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/storage/rocksdb/rocksdb/java/rocksjni/transaction.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction.cc
new file mode 100644
index 00000000..ae98b868
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction.cc
@@ -0,0 +1,1646 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <functional>
+
+#include "include/org_rocksdb_Transaction.h"
+
+#include "rocksdb/utilities/transaction.h"
+#include "rocksjni/portal.h"
+
+using namespace std::placeholders;
+
+#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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* txn_notifier = reinterpret_cast<
+ std::shared_ptr<ROCKSDB_NAMESPACE::TransactionNotifierJniCallback>*>(
+ 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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ const ROCKSDB_NAMESPACE::Snapshot* snapshot = txn->GetSnapshot();
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ ROCKSDB_NAMESPACE::Status s = txn->RollbackToSavePoint();
+ if (!s.ok()) {
+ ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
+ }
+}
+
+typedef std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::ReadOptions&, const ROCKSDB_NAMESPACE::Slice&,
+ std::string*)>
+ 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<char*>(key),
+ jkey_part_len);
+
+ auto* read_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<jsize>(value.size()));
+ if (jret_value == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+ env->SetByteArrayRegion(jret_value, 0, static_cast<jsize>(value.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnGet fn_get =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, std::string*)>(
+ &ROCKSDB_NAMESPACE::Transaction::Get, txn, _1, column_family_handle,
+ _2, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnGet fn_get =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const ROCKSDB_NAMESPACE::Slice&, std::string*)>(
+ &ROCKSDB_NAMESPACE::Transaction::Get, txn, _1, _2, _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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> txn_column_families_helper(
+ JNIEnv* env, jlongArray jcolumn_family_handles, bool* has_exception) {
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> cf_handles;
+ if (jcolumn_family_handles != nullptr) {
+ const jsize len_cols = env->GetArrayLength(jcolumn_family_handles);
+ if (len_cols > 0) {
+ if (env->EnsureLocalCapacity(len_cols) != 0) {
+ // out of memory
+ *has_exception = JNI_TRUE;
+ return std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>();
+ }
+
+ jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr);
+ if (jcfh == nullptr) {
+ // exception thrown: OutOfMemoryError
+ *has_exception = JNI_TRUE;
+ return std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>();
+ }
+ for (int i = 0; i < len_cols; i++) {
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcfh[i]);
+ cf_handles.push_back(cf_handle);
+ }
+ env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT);
+ }
+ }
+ return cf_handles;
+}
+
+typedef std::function<std::vector<ROCKSDB_NAMESPACE::Status>(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const std::vector<ROCKSDB_NAMESPACE::Slice>&, std::vector<std::string>*)>
+ FnMultiGet;
+
+void free_parts(
+ JNIEnv* env,
+ std::vector<std::tuple<jbyteArray, jbyte*, jobject>>& 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);
+ }
+}
+
+// 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);
+ if (env->EnsureLocalCapacity(len_key_parts) != 0) {
+ // out of memory
+ return nullptr;
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::Slice> key_parts;
+ std::vector<std::tuple<jbyteArray, jbyte*, jobject>> key_parts_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_parts(env, key_parts_to_free);
+ return nullptr;
+ }
+ jbyteArray jk_ba = reinterpret_cast<jbyteArray>(jk);
+ const jsize len_key = env->GetArrayLength(jk_ba);
+ if (env->EnsureLocalCapacity(len_key) != 0) {
+ // out of memory
+ env->DeleteLocalRef(jk);
+ free_parts(env, key_parts_to_free);
+ return nullptr;
+ }
+ jbyte* jk_val = env->GetByteArrayElements(jk_ba, nullptr);
+ if (jk_val == nullptr) {
+ // exception thrown: OutOfMemoryError
+ env->DeleteLocalRef(jk);
+ free_parts(env, key_parts_to_free);
+ return nullptr;
+ }
+
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(jk_val),
+ len_key);
+ key_parts.push_back(key_slice);
+
+ key_parts_to_free.push_back(std::make_tuple(jk_ba, jk_val, jk));
+ }
+
+ auto* read_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
+ std::vector<std::string> value_parts;
+ std::vector<ROCKSDB_NAMESPACE::Status> s =
+ fn_multi_get(*read_options, key_parts, &value_parts);
+
+ // free up allocated byte arrays
+ free_parts(env, key_parts_to_free);
+
+ // prepare the results
+ const jclass jcls_ba = env->FindClass("[B");
+ jobjectArray jresults =
+ env->NewObjectArray(static_cast<jsize>(s.size()), jcls_ba, nullptr);
+ if (jresults == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ // add to the jresults
+ for (std::vector<ROCKSDB_NAMESPACE::Status>::size_type i = 0; i != s.size();
+ i++) {
+ if (s[i].ok()) {
+ jbyteArray jentry_value =
+ env->NewByteArray(static_cast<jsize>(value_parts[i].size()));
+ if (jentry_value == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(
+ jentry_value, 0, static_cast<jsize>(value_parts[i].size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(value_parts[i].c_str())));
+ if (env->ExceptionCheck()) {
+ // exception thrown: ArrayIndexOutOfBoundsException
+ env->DeleteLocalRef(jentry_value);
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(jresults, static_cast<jsize>(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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>
+ 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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnMultiGet fn_multi_get = std::bind<std::vector<ROCKSDB_NAMESPACE::Status> (
+ ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>&,
+ const std::vector<ROCKSDB_NAMESPACE::Slice>&, std::vector<std::string>*)>(
+ &ROCKSDB_NAMESPACE::Transaction::MultiGet, txn, _1, column_family_handles,
+ _2, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnMultiGet fn_multi_get = std::bind<std::vector<ROCKSDB_NAMESPACE::Status> (
+ ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const std::vector<ROCKSDB_NAMESPACE::Slice>&, std::vector<std::string>*)>(
+ &ROCKSDB_NAMESPACE::Transaction::MultiGet, txn, _1, _2, _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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnGet fn_get_for_update =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, std::string*, bool, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn, _1,
+ column_family_handle, _2, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnGet fn_get_for_update =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const ROCKSDB_NAMESPACE::Slice&, std::string*, bool, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn, _1, _2, _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<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>
+ 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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnMultiGet fn_multi_get_for_update = std::bind<std::vector<
+ ROCKSDB_NAMESPACE::Status> (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>&,
+ const std::vector<ROCKSDB_NAMESPACE::Slice>&, std::vector<std::string>*)>(
+ &ROCKSDB_NAMESPACE::Transaction::MultiGetForUpdate, txn, _1,
+ column_family_handles, _2, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnMultiGet fn_multi_get_for_update = std::bind<std::vector<
+ ROCKSDB_NAMESPACE::Status> (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::ReadOptions&,
+ const std::vector<ROCKSDB_NAMESPACE::Slice>&, std::vector<std::string>*)>(
+ &ROCKSDB_NAMESPACE::Transaction::MultiGetForUpdate, txn, _1, _2, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* read_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* read_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ return reinterpret_cast<jlong>(
+ txn->GetIterator(*read_options, column_family_handle));
+}
+
+typedef std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>
+ 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<char*>(key),
+ jkey_part_len);
+ ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKV fn_put =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&,
+ bool)>(&ROCKSDB_NAMESPACE::Transaction::Put, txn,
+ column_family_handle, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKV fn_put =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::Put, txn, _1, _2);
+ txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len);
+}
+
+typedef std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::SliceParts&, const ROCKSDB_NAMESPACE::SliceParts&)>
+ 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<ROCKSDB_NAMESPACE::Slice>();
+ auto value_parts = std::vector<ROCKSDB_NAMESPACE::Slice>();
+ auto jparts_to_free = std::vector<std::tuple<jbyteArray, jbyte*, jobject>>();
+
+ // 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<jbyteArray>(jobj_key_part);
+ const jsize jkey_part_len = env->GetArrayLength(jba_key_part);
+ if (env->EnsureLocalCapacity(jkey_part_len) != 0) {
+ // out of memory
+ env->DeleteLocalRef(jobj_value_part);
+ env->DeleteLocalRef(jobj_key_part);
+ free_parts(env, jparts_to_free);
+ return;
+ }
+ 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<jbyteArray>(jobj_value_part);
+ const jsize jvalue_part_len = env->GetArrayLength(jba_value_part);
+ if (env->EnsureLocalCapacity(jvalue_part_len) != 0) {
+ // out of memory
+ env->DeleteLocalRef(jobj_value_part);
+ env->DeleteLocalRef(jobj_key_part);
+ free_parts(env, jparts_to_free);
+ return;
+ }
+ jbyte* jvalue_part = env->GetByteArrayElements(jba_value_part, nullptr);
+ if (jvalue_part == nullptr) {
+ // exception thrown: OutOfMemoryError
+ env->ReleaseByteArrayElements(jba_value_part, jvalue_part, JNI_ABORT);
+ env->DeleteLocalRef(jobj_value_part);
+ env->DeleteLocalRef(jobj_key_part);
+ 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<char*>(jkey_part), jkey_part_len));
+ value_parts.push_back(ROCKSDB_NAMESPACE::Slice(
+ reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKVParts fn_put_parts =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::SliceParts&,
+ const ROCKSDB_NAMESPACE::SliceParts&, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::Put, txn, column_family_handle, _1,
+ _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKVParts fn_put_parts = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::SliceParts&,
+ const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::Put, txn, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKV fn_merge =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&,
+ bool)>(&ROCKSDB_NAMESPACE::Transaction::Merge, txn,
+ column_family_handle, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKV fn_merge =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::Merge, txn, _1, _2);
+ txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len);
+}
+
+typedef std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::Slice&)>
+ 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<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteK fn_delete =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::Delete, txn, column_family_handle,
+ _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteK fn_delete = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::Delete, txn, _1);
+ txn_write_k_helper(env, fn_delete, jkey, jkey_part_len);
+}
+
+typedef std::function<ROCKSDB_NAMESPACE::Status(
+ const ROCKSDB_NAMESPACE::SliceParts&)>
+ 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<ROCKSDB_NAMESPACE::Slice> key_parts;
+ std::vector<std::tuple<jbyteArray, jbyte*, jobject>> 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<jbyteArray>(jobj_key_part);
+ const jsize jkey_part_len = env->GetArrayLength(jba_key_part);
+ if (env->EnsureLocalCapacity(jkey_part_len) != 0) {
+ // out of memory
+ env->DeleteLocalRef(jobj_key_part);
+ free_parts(env, jkey_parts_to_free);
+ return;
+ }
+ 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<jbyteArray, jbyte*, jobject>(
+ jba_key_part, jkey_part, jobj_key_part));
+
+ key_parts.push_back(ROCKSDB_NAMESPACE::Slice(
+ reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKParts fn_delete_parts =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::SliceParts&, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::Delete, txn, column_family_handle,
+ _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKParts fn_delete_parts = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::Delete, txn, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteK fn_single_delete =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn,
+ column_family_handle, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteK fn_single_delete = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKParts fn_single_delete_parts =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::SliceParts&, bool)>(
+ &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn,
+ column_family_handle, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKParts fn_single_delete_parts = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKV fn_put_untracked =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn,
+ column_family_handle, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKV fn_put_untracked =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKVParts fn_put_parts_untracked = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::SliceParts&,
+ const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, column_family_handle,
+ _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKVParts fn_put_parts_untracked = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::SliceParts&,
+ const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKV fn_merge_untracked =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn,
+ column_family_handle, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKV fn_merge_untracked =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn, _1, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteK fn_delete_untracked = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn,
+ column_family_handle, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteK fn_delete_untracked = std::bind<ROCKSDB_NAMESPACE::Status (
+ ROCKSDB_NAMESPACE::Transaction::*)(const ROCKSDB_NAMESPACE::Slice&)>(
+ &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ FnWriteKParts fn_delete_untracked_parts =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
+ const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn,
+ column_family_handle, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ FnWriteKParts fn_delete_untracked_parts =
+ std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
+ const ROCKSDB_NAMESPACE::SliceParts&)>(
+ &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, _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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+
+ jbyte* key = env->GetByteArrayElements(jkey, nullptr);
+ if (key == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* column_family_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
+ jcolumn_family_handle);
+ jbyte* key = env->GetByteArrayElements(jkey, nullptr);
+ if (key == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ jbyte* key = env->GetByteArrayElements(jkey, nullptr);
+ if (key == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ auto* write_batch =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ ROCKSDB_NAMESPACE::TransactionID id = txn->GetID();
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ return static_cast<jboolean>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ uint32_t column_family_id;
+ std::string key;
+ std::vector<ROCKSDB_NAMESPACE::TransactionID> 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<ROCKSDB_NAMESPACE::Transaction*>(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::COMMITED:
+ 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<jbyte>(-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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+ uint64_t id = txn->GetId();
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/transaction_db.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_db.cc
new file mode 100644
index 00000000..c6ec6464
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_db.cc
@@ -0,0 +1,463 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "include/org_rocksdb_TransactionDB.h"
+
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.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<ROCKSDB_NAMESPACE::Options*>(joptions_handle);
+ auto* txn_db_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDBOptions*>(
+ 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 reinterpret_cast<jlong>(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);
+ if (env->EnsureLocalCapacity(len_cols) != 0) {
+ // out of memory
+ env->ReleaseStringUTFChars(jdb_path, db_path);
+ return nullptr;
+ }
+
+ jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr);
+ if (jco == nullptr) {
+ // exception thrown: OutOfMemoryError
+ env->ReleaseStringUTFChars(jdb_path, db_path);
+ return nullptr;
+ }
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> 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<jbyteArray>(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);
+ if (env->EnsureLocalCapacity(jcf_name_len) != 0) {
+ // out of memory
+ env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT);
+ 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<char*>(jcf_name), jcf_name_len);
+ const ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<ROCKSDB_NAMESPACE::DBOptions*>(jdb_options_handle);
+ auto* txn_db_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDBOptions*>(
+ jtxn_db_options_handle);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<jlong[]> results =
+ std::unique_ptr<jlong[]>(new jlong[resultsLen]);
+ results[0] = reinterpret_cast<jlong>(tdb);
+ for (int i = 1; i <= len_cols; i++) {
+ results[i] = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(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<ROCKSDB_NAMESPACE::TransactionDB*>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ ROCKSDB_NAMESPACE::Transaction* txn =
+ txn_db->BeginTransaction(*write_options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* txn_options = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionOptions*>(
+ jtxn_options_handle);
+ ROCKSDB_NAMESPACE::Transaction* txn =
+ txn_db->BeginTransaction(*write_options, *txn_options);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* old_txn =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ auto* write_options =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
+ auto* txn_options = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionOptions*>(
+ jtxn_options_handle);
+ auto* old_txn =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ std::vector<ROCKSDB_NAMESPACE::Transaction*> 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<jsize>(size);
+ std::vector<jlong> tmp(len);
+ for (jsize i = 0; i < len; ++i) {
+ tmp[i] = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ const std::unordered_multimap<uint32_t, ROCKSDB_NAMESPACE::KeyLockInfo>
+ lock_status_data = txn_db->GetLockStatusData();
+ const jobject jlock_status_data = ROCKSDB_NAMESPACE::HashMapJni::construct(
+ env, static_cast<uint32_t>(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<const int32_t,
+ const ROCKSDB_NAMESPACE::KeyLockInfo>& 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<std::pair<jobject, jobject>>(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<std::pair<jobject, jobject>>(nullptr);
+ }
+ return std::unique_ptr<std::pair<jobject, jobject>>(
+ new std::pair<jobject, jobject>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ const std::vector<ROCKSDB_NAMESPACE::DeadlockPath> deadlock_info_buffer =
+ txn_db->GetDeadlockInfoBuffer();
+
+ const jsize deadlock_info_buffer_len =
+ static_cast<jsize>(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<ROCKSDB_NAMESPACE::DeadlockInfo> deadlock_infos =
+ deadlock_path.path;
+ const jsize deadlock_infos_len =
+ static_cast<jsize>(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<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
+ txn_db->SetDeadlockInfoBufferSize(jdeadlock_info_buffer_size);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/transaction_db_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_db_options.cc
new file mode 100644
index 00000000..c64ff945
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_db_options.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_TransactionDBOptions.h"
+
+#include "rocksdb/utilities/transaction_db.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(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<ROCKSDB_NAMESPACE::TransactionDBOptions*>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/transaction_log.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_log.cc
new file mode 100644
index 00000000..aa57211e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_log.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "include/org_rocksdb_TransactionLogIterator.h"
+#include "rocksdb/transaction_log.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<ROCKSDB_NAMESPACE::TransactionLogIterator*>(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<ROCKSDB_NAMESPACE::TransactionLogIterator*>(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<ROCKSDB_NAMESPACE::TransactionLogIterator*>(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<ROCKSDB_NAMESPACE::TransactionLogIterator*>(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<ROCKSDB_NAMESPACE::TransactionLogIterator*>(handle)
+ ->GetBatch();
+ return ROCKSDB_NAMESPACE::BatchResultJni::construct(env, batch_result);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier.cc
new file mode 100644
index 00000000..4e42af35
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_AbstractTransactionNotifier.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<ROCKSDB_NAMESPACE::TransactionNotifierJniCallback>(
+ transaction_notifier);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionNotifierJniCallback>* handle =
+ reinterpret_cast<
+ std::shared_ptr<ROCKSDB_NAMESPACE::TransactionNotifierJniCallback>*>(
+ jhandle);
+ delete handle;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc
new file mode 100644
index 00000000..06b62ad6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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, reinterpret_cast<jlong>(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/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h
new file mode 100644
index 00000000..b3155b5a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#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/storage/rocksdb/rocksdb/java/rocksjni/transaction_options.cc b/storage/rocksdb/rocksdb/java/rocksjni/transaction_options.cc
new file mode 100644
index 00000000..0544f255
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/transaction_options.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_TransactionOptions.h"
+
+#include "rocksdb/utilities/transaction_db.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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(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<ROCKSDB_NAMESPACE::TransactionOptions*>(jhandle);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/ttl.cc b/storage/rocksdb/rocksdb/java/rocksjni/ttl.cc
new file mode 100644
index 00000000..77d17c82
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/ttl.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "include/org_rocksdb_TtlDB.h"
+#include "rocksdb/utilities/db_ttl.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<ROCKSDB_NAMESPACE::Options*>(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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> column_families;
+ jboolean has_exception = JNI_FALSE;
+ ROCKSDB_NAMESPACE::JniUtil::byteStrings<std::string>(
+ 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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(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<int32_t> 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<ROCKSDB_NAMESPACE::DBOptions*>(jopt_handle);
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> 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<jlong[]> results =
+ std::unique_ptr<jlong[]>(new jlong[resultsLen]);
+ results[0] = reinterpret_cast<jlong>(db);
+ for (int i = 1; i <= len_cols; i++) {
+ results[i] = reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::DBWithTTL*>(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<ROCKSDB_NAMESPACE::DBWithTTL*>(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<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(
+ jcolumn_options);
+
+ auto* db_handle = reinterpret_cast<ROCKSDB_NAMESPACE::DBWithTTL*>(jdb_handle);
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* handle;
+ ROCKSDB_NAMESPACE::Status s = db_handle->CreateColumnFamilyWithTtl(
+ *cfOptions, std::string(reinterpret_cast<char*>(cfname), len), &handle,
+ jttl);
+
+ env->ReleaseByteArrayElements(jcolumn_name, cfname, 0);
+
+ if (s.ok()) {
+ return reinterpret_cast<jlong>(handle);
+ }
+ ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
+ return 0;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/wal_filter.cc b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter.cc
new file mode 100644
index 00000000..e4040b1f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_AbstractWalFilter.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 reinterpret_cast<jlong>(wal_filter);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.cc
new file mode 100644
index 00000000..48fe2d50
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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<uint32_t, uint64_t>& cf_lognumber_map,
+ const std::map<std::string, uint32_t>& 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<jlong>(log_number),
+ jlog_file_name,
+ reinterpret_cast<jlong>(&batch),
+ reinterpret_cast<jlong>(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/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.h
new file mode 100644
index 00000000..b575ddc6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/wal_filter_jnicallback.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+#include <map>
+#include <memory>
+#include <string>
+
+#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<uint32_t, uint64_t>& cf_lognumber_map,
+ const std::map<std::string, uint32_t>& 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<const char[]> 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/storage/rocksdb/rocksdb/java/rocksjni/write_batch.cc b/storage/rocksdb/rocksdb/java/rocksjni/write_batch.cc
new file mode 100644
index 00000000..4ef8035c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/write_batch.cc
@@ -0,0 +1,674 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#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_batch.h"
+#include "rocksdb/write_buffer_manager.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<size_t>(jreserved_bytes));
+ return reinterpret_cast<jlong>(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<std::string>(
+ 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 reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ wb->SetMaxBytes(static_cast<size_t>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto put = [&wb](ROCKSDB_NAMESPACE::Slice key,
+ ROCKSDB_NAMESPACE::Slice value) {
+ return wb->Put(key, value);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto merge = [&wb](ROCKSDB_NAMESPACE::Slice key,
+ ROCKSDB_NAMESPACE::Slice value) {
+ return wb->Merge(key, value);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto remove = [&wb](ROCKSDB_NAMESPACE::Slice key) { return wb->Delete(key); };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ assert(cf_handle != nullptr);
+ auto remove = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice key) {
+ return wb->Delete(cf_handle, key);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto single_delete = [&wb](ROCKSDB_NAMESPACE::Slice key) {
+ return wb->SingleDelete(key);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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: removeDirect
+ * Signature: (JLjava/nio/ByteBuffer;IIJ)V
+ */
+void Java_org_rocksdb_WriteBatch_removeDirect(JNIEnv* env, jobject /*jobj*/,
+ jlong jwb_handle, jobject jkey,
+ jint jkey_offset, jint jkey_len,
+ jlong jcf_handle) {
+ auto* wb = reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto deleteRange = [&wb](ROCKSDB_NAMESPACE::Slice beginKey,
+ ROCKSDB_NAMESPACE::Slice endKey) {
+ return wb->DeleteRange(beginKey, endKey);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto putLogData = [&wb](ROCKSDB_NAMESPACE::Slice blob) {
+ return wb->PutLogData(blob);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ ROCKSDB_NAMESPACE::Status s = wb->Iterate(
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatchHandlerJniCallback*>(
+ 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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ auto data_size = wb->GetDataSize();
+ return static_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(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 reinterpret_cast<jlong>(wbjnic);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/write_batch_test.cc b/storage/rocksdb/rocksdb/java/rocksjni/write_batch_test.cc
new file mode 100644
index 00000000..114eac42
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/write_batch_test.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#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_batch.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<ROCKSDB_NAMESPACE::WriteBatch*>(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::SkipListFactory>();
+ 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::ImmutableCFOptions(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();
+ bool parsed = ROCKSDB_NAMESPACE::ParseInternalKey(iter->key(), &ikey);
+ if (!parsed) {
+ assert(parsed);
+ }
+ 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(ROCKSDB_NAMESPACE::NumberToString(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<jsize>(state.size()));
+ if (jstate == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(
+ jstate, 0, static_cast<jsize>(state.size()),
+ const_cast<jbyte*>(reinterpret_cast<const jbyte*>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ ROCKSDB_NAMESPACE::WriteBatchInternal::SetSequence(
+ wb, static_cast<ROCKSDB_NAMESPACE::SequenceNumber>(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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+
+ return static_cast<jlong>(
+ 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle_1);
+ assert(wb1 != nullptr);
+ auto* wb2 = reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle_2);
+ assert(wb2 != nullptr);
+
+ ROCKSDB_NAMESPACE::WriteBatchInternal::Append(wb1, wb2);
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/write_batch_with_index.cc b/storage/rocksdb/rocksdb/java/rocksjni/write_batch_with_index.cc
new file mode 100644
index 00000000..da826452
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/write_batch_with_index.cc
@@ -0,0 +1,862 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/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 reinterpret_cast<jlong>(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<bool>(joverwrite_key));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::ComparatorJniCallback*>(
+ jfallback_index_comparator_handle);
+ break;
+
+ // JAVA_NATIVE_COMPARATOR_WRAPPER
+ case 0x1:
+ fallback_comparator = reinterpret_cast<ROCKSDB_NAMESPACE::Comparator*>(
+ jfallback_index_comparator_handle);
+ break;
+ }
+ auto* wbwi = new ROCKSDB_NAMESPACE::WriteBatchWithIndex(
+ fallback_comparator, static_cast<size_t>(jreserved_bytes),
+ static_cast<bool>(joverwrite_key));
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+
+ return static_cast<jint>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto put = [&wbwi](ROCKSDB_NAMESPACE::Slice key,
+ ROCKSDB_NAMESPACE::Slice value) {
+ return wbwi->Put(key, value);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto merge = [&wbwi](ROCKSDB_NAMESPACE::Slice key,
+ ROCKSDB_NAMESPACE::Slice value) {
+ return wbwi->Merge(key, value);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto remove = [&wbwi](ROCKSDB_NAMESPACE::Slice key) {
+ return wbwi->Delete(key);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ assert(cf_handle != nullptr);
+ auto remove = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice key) {
+ return wbwi->Delete(cf_handle, key);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto single_delete = [&wbwi](ROCKSDB_NAMESPACE::Slice key) {
+ return wbwi->SingleDelete(key);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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: removeDirect
+ * Signature: (JLjava/nio/ByteBuffer;IIJ)V
+ */
+void Java_org_rocksdb_WriteBatchWithIndex_removeDirect(
+ JNIEnv* env, jobject /*jobj*/, jlong jwb_handle, jobject jkey,
+ jint jkey_offset, jint jkey_len, jlong jcf_handle) {
+ auto* wb = reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatch*>(jwb_handle);
+ assert(wb != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto deleteRange = [&wbwi](ROCKSDB_NAMESPACE::Slice beginKey,
+ ROCKSDB_NAMESPACE::Slice endKey) {
+ return wbwi->DeleteRange(beginKey, endKey);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+ auto putLogData = [&wbwi](ROCKSDB_NAMESPACE::Slice blob) {
+ return wbwi->PutLogData(blob);
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ assert(wbwi != nullptr);
+
+ wbwi->SetMaxBytes(static_cast<size_t>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* wbwi_iterator = wbwi->NewIterator();
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ auto* wbwi_iterator = wbwi->NewIterator(cf_handle);
+ return reinterpret_cast<jlong>(wbwi_iterator);
+}
+
+/*
+ * Class: org_rocksdb_WriteBatchWithIndex
+ * Method: iteratorWithBase
+ * Signature: (JJJ)J
+ */
+jlong Java_org_rocksdb_WriteBatchWithIndex_iteratorWithBase(JNIEnv* /*env*/,
+ jobject /*jobj*/,
+ jlong jwbwi_handle,
+ jlong jcf_handle,
+ jlong jbi_handle) {
+ auto* wbwi =
+ reinterpret_cast<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
+ auto* base_iterator =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Iterator*>(jbi_handle);
+ auto* iterator = wbwi->NewIteratorWithBase(cf_handle, base_iterator);
+ return reinterpret_cast<jlong>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* dbopt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* dbopt = reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdbopt_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* readopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(jwbwi_handle);
+ auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+ auto* readopt =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jreadopt_handle);
+ auto* cf_handle =
+ reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(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<ROCKSDB_NAMESPACE::WriteBatchWithIndex*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(handle);
+ jbyte* target = env->GetByteArrayElements(jtarget, nullptr);
+ if (target == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast<char*>(target),
+ jtarget_len);
+
+ it->Seek(target_slice);
+
+ env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::WBWIIterator*>(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_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<ROCKSDB_NAMESPACE::WBWIIterator*>(handle);
+ jbyte* target = env->GetByteArrayElements(jtarget, nullptr);
+ if (target == nullptr) {
+ // exception thrown: OutOfMemoryError
+ return;
+ }
+
+ ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast<char*>(target),
+ jtarget_len);
+
+ it->SeekForPrev(target_slice);
+
+ env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT);
+}
+
+/*
+ * 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<ROCKSDB_NAMESPACE::WBWIIterator*>(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<ROCKSDB_NAMESPACE::WBWIIterator*>(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] = reinterpret_cast<jlong>(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] = reinterpret_cast<jlong>(value_slice);
+ }
+
+ jlongArray jresults = env->NewLongArray(3);
+ if (jresults == nullptr) {
+ // exception thrown: OutOfMemoryError
+ if (results[2] != 0) {
+ auto* value_slice =
+ reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(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<ROCKSDB_NAMESPACE::Slice*>(results[2]);
+ delete value_slice;
+ }
+ delete key_slice;
+ return nullptr;
+ }
+
+ return jresults;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/write_buffer_manager.cc b/storage/rocksdb/rocksdb/java/rocksjni/write_buffer_manager.cc
new file mode 100644
index 00000000..a52daed7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/write_buffer_manager.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <jni.h>
+
+#include "include/org_rocksdb_WriteBufferManager.h"
+
+#include "rocksdb/cache.h"
+#include "rocksdb/write_buffer_manager.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) {
+ auto* cache_ptr =
+ reinterpret_cast<std::shared_ptr<ROCKSDB_NAMESPACE::Cache>*>(
+ jcache_handle);
+ auto* write_buffer_manager =
+ new std::shared_ptr<ROCKSDB_NAMESPACE::WriteBufferManager>(
+ std::make_shared<ROCKSDB_NAMESPACE::WriteBufferManager>(jbuffer_size,
+ *cache_ptr));
+ return reinterpret_cast<jlong>(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<std::shared_ptr<ROCKSDB_NAMESPACE::WriteBufferManager>*>(
+ jhandle);
+ assert(write_buffer_manager != nullptr);
+ delete write_buffer_manager;
+}
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc b/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc
new file mode 100644
index 00000000..4ecb6b2d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc
@@ -0,0 +1,548 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_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<jint>(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<jint>(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<jint>(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<jint>(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<jint>(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<jint>(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<ROCKSDB_NAMESPACE::Status> 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<jboolean>(empty_batch));
+
+ // check for Exception, in-particular RocksDBException
+ if (m_env->ExceptionCheck()) {
+ // exception thrown
+ jthrowable exception = m_env->ExceptionOccurred();
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> 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);
+ }
+}
+
+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<bool>(jContinue == JNI_TRUE);
+}
+
+std::unique_ptr<ROCKSDB_NAMESPACE::Status> WriteBatchHandlerJniCallback::kv_op(
+ const Slice& key, const Slice& value,
+ std::function<void(jbyteArray, jbyteArray)> 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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
+}
+
+std::unique_ptr<ROCKSDB_NAMESPACE::Status> WriteBatchHandlerJniCallback::k_op(
+ const Slice& key, std::function<void(jbyteArray)> 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<ROCKSDB_NAMESPACE::Status> 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<ROCKSDB_NAMESPACE::Status>(
+ new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h b/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h
new file mode 100644
index 00000000..a4c61f8b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+#include <jni.h>
+#include <memory>
+#include "rocksjni/jnicallback.h"
+#include "rocksdb/write_batch.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);
+ 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_jContinueMethodId;
+ /**
+ * @return A pointer to a ROCKSDB_NAMESPACE::Status or nullptr if an
+ * unexpected exception occurred
+ */
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> kv_op(
+ const Slice& key, const Slice& value,
+ std::function<void(jbyteArray, jbyteArray)> kvFn);
+ /**
+ * @return A pointer to a ROCKSDB_NAMESPACE::Status or nullptr if an
+ * unexpected exception occurred
+ */
+ std::unique_ptr<ROCKSDB_NAMESPACE::Status> k_op(
+ const Slice& key, std::function<void(jbyteArray)> kFn);
+};
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_
diff --git a/storage/rocksdb/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java b/storage/rocksdb/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java
new file mode 100644
index 00000000..1633d1f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 == value1);
+
+ 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/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBColumnFamilySample.java b/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBColumnFamilySample.java
new file mode 100644
index 00000000..650b1b2f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBColumnFamilySample.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<ColumnFamilyDescriptor> 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<ColumnFamilyHandle> 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(0), 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.remove(columnFamilyHandles.get(0), "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/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBSample.java b/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBSample.java
new file mode 100644
index 00000000..f61995ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/samples/src/main/java/RocksDBSample.java
@@ -0,0 +1,303 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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)
+ .setMaxBackgroundCompactions(10)
+ .setCompressionType(CompressionType.SNAPPY_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.maxBackgroundCompactions() == 10);
+ assert (options.compressionType() == CompressionType.SNAPPY_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();
+ table_options.setBlockCacheSize(64 * SizeUnit.KB)
+ .setFilter(bloomFilter)
+ .setCacheNumShardBits(6)
+ .setBlockSizeDeviation(5)
+ .setBlockRestartInterval(10)
+ .setCacheIndexAndFilterBlocks(true)
+ .setHashIndexAllowCollision(false)
+ .setBlockCacheCompressedSize(64 * SizeUnit.KB)
+ .setBlockCacheCompressedNumShardBits(10);
+
+ assert (table_options.blockCacheSize() == 64 * SizeUnit.KB);
+ assert (table_options.cacheNumShardBits() == 6);
+ assert (table_options.blockSizeDeviation() == 5);
+ assert (table_options.blockRestartInterval() == 10);
+ assert (table_options.cacheIndexAndFilterBlocks() == true);
+ assert (table_options.hashIndexAllowCollision() == false);
+ assert (table_options.blockCacheCompressedSize() == 64 * SizeUnit.KB);
+ assert (table_options.blockCacheCompressedNumShardBits() == 10);
+
+ 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;'?><MNBVCXZQWERTYUIOP{+_)(*&^%$#@".getBytes();
+ db.put(testKey, testValue);
+ byte[] testResult = db.get(testKey);
+ assert (testResult != null);
+ assert (Arrays.equals(testValue, testResult));
+ assert (new String(testValue).equals(new String(testResult)));
+ testResult = db.get(readOptions, testKey);
+ assert (testResult != null);
+ assert (Arrays.equals(testValue, testResult));
+ assert (new String(testValue).equals(new String(testResult)));
+
+ final byte[] insufficientArray = new byte[10];
+ final byte[] enoughArray = new byte[50];
+ int len;
+ len = db.get(testKey, insufficientArray);
+ assert (len > 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.remove(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(true);
+ 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<byte[]> keys = new ArrayList<>();
+ try (final RocksIterator iterator = db.newIterator()) {
+ for (iterator.seekToLast(); iterator.isValid(); iterator.prev()) {
+ keys.add(iterator.key());
+ }
+ }
+
+ Map<byte[], byte[]> values = db.multiGet(keys);
+ assert (values.size() == keys.size());
+ for (final byte[] value1 : values.values()) {
+ assert (value1 != null);
+ }
+
+ values = db.multiGet(new ReadOptions(), keys);
+ assert (values.size() == keys.size());
+ for (final byte[] value1 : values.values()) {
+ assert (value1 != null);
+ }
+ } catch (final RocksDBException e) {
+ System.err.println(e);
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/samples/src/main/java/TransactionSample.java b/storage/rocksdb/rocksdb/java/samples/src/main/java/TransactionSample.java
new file mode 100644
index 00000000..b88a68f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/samples/src/main/java/TransactionSample.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java
new file mode 100644
index 00000000..2f0d4f3c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T extends AbstractSlice<?>>
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java
new file mode 100644
index 00000000..380b4461
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 <T> The concrete type of the compaction filter
+ */
+public abstract class AbstractCompactionFilterFactory<T extends AbstractCompactionFilter<?>>
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java
new file mode 100644
index 00000000..c08e9127
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 copt) {
+ super(copt.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
+ * <a href="https://en.wikipedia.org/wiki/Total_order">total order</a>
+ * 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) &lt; 0 if "a" &lt; "b"
+ * 2) == 0 if "a" == "b"
+ * 3) &gt; 0 if "a" &gt; "b"
+ */
+ public abstract int compare(final ByteBuffer a, final ByteBuffer b);
+
+ /**
+ * <p>Used to reduce the space requirements
+ * for internal data structures like index blocks.</p>
+ *
+ * <p>If start &lt; limit, you may modify start which is a
+ * shorter string in [start, limit).</p>
+ *
+ * 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.
+ *
+ * <p>Simple comparator implementations may return with start unchanged.
+ * i.e., an implementation of this method that does nothing is correct.</p>
+ *
+ * @param start the start
+ * @param limit the limit
+ */
+ public void findShortestSeparator(final ByteBuffer start,
+ final ByteBuffer limit) {
+ // no-op
+ }
+
+ /**
+ * <p>Used to reduce the space requirements
+ * for internal data structures like index blocks.</p>
+ *
+ * <p>You may change key to a shorter key (key1) where
+ * key1 &ge; key.</p>
+ *
+ * <p>Simple comparator implementations may return the key unchanged.
+ * i.e., an implementation of
+ * this method that does nothing is correct.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java
new file mode 100644
index 00000000..b732d249
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java
new file mode 100644
index 00000000..8532debf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java
@@ -0,0 +1,67 @@
+// 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
+ * <p>
+ * This will prevent the object from attempting to delete the underlying
+ * native object in its finalizer. This must be used when another object
+ * takes over ownership of the native object or both will attempt to delete
+ * the underlying object when garbage collected.
+ * <p>
+ * When {@code disOwnNativeHandle()} is called, {@code dispose()} will
+ * subsequently take no action. As a result, incorrect use of this function
+ * may cause a memory leak.
+ * </p>
+ *
+ * @see #dispose()
+ */
+ protected final void disOwnNativeHandle() {
+ owningHandle_.set(false);
+ }
+
+ @Override
+ public void close() {
+ if (owningHandle_.compareAndSet(true, false)) {
+ disposeInternal();
+ }
+ }
+
+ /**
+ * The helper function of {@link AbstractImmutableNativeReference#dispose()}
+ * which all subclasses of {@code AbstractImmutableNativeReference} must
+ * implement to release their underlying native C++ objects.
+ */
+ protected abstract void disposeInternal();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java
new file mode 100644
index 00000000..6180fba1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java
@@ -0,0 +1,256 @@
+// 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<T, U, K>,
+ K extends MutableOptionKey> {
+
+ private final Map<K, MutableOptionValue<?>> options = new LinkedHashMap<>();
+
+ protected abstract U self();
+
+ /**
+ * Get all of the possible keys
+ *
+ * @return A map of all keys, indexed by name.
+ */
+ protected abstract Map<String, K> 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<K, MutableOptionValue<?>> 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 <N extends Enum<N>> 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 extends Enum<N>> 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<N>) value).asObject();
+ }
+
+ public U fromString(
+ final String keyStr, final String valueStr)
+ throws IllegalArgumentException {
+ Objects.requireNonNull(keyStr);
+ Objects.requireNonNull(valueStr);
+
+ final K key = allKeys().get(keyStr);
+ switch(key.getValueType()) {
+ case DOUBLE:
+ return setDouble(key, Double.parseDouble(valueStr));
+
+ case LONG:
+ return setLong(key, Long.parseLong(valueStr));
+
+ case INT:
+ return setInt(key, Integer.parseInt(valueStr));
+
+ case BOOLEAN:
+ return setBoolean(key, Boolean.parseBoolean(valueStr));
+
+ case INT_ARRAY:
+ final String[] strInts = valueStr
+ .trim().split(INT_ARRAY_INT_SEPARATOR);
+ if(strInts == null || strInts.length == 0) {
+ throw new IllegalArgumentException(
+ "int array value is not correctly formatted");
+ }
+
+ final int value[] = new int[strInts.length];
+ int i = 0;
+ for(final String strInt : strInts) {
+ value[i++] = Integer.parseInt(strInt);
+ }
+ return setIntArray(key, value);
+ }
+
+ throw new IllegalStateException(
+ key + " has unknown value type: " + key.getValueType());
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractNativeReference.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractNativeReference.java
new file mode 100644
index 00000000..ffb0776e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractNativeReference.java
@@ -0,0 +1,76 @@
+// 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.
+ * <p>
+ * AbstractNativeReference has the {@link AbstractNativeReference#dispose()}
+ * method, which frees its associated C++ object.</p>
+ * <p>
+ * This function should be called manually, however, if required it will be
+ * called automatically during the regular Java GC process via
+ * {@link AbstractNativeReference#finalize()}.</p>
+ * <p>
+ * Note - Java can only see the long member variable (which is the C++ pointer
+ * value to the native object), as such it does not know the real size of the
+ * object and therefore may assign a low GC priority for it; So it is strongly
+ * suggested that you manually dispose of objects when you are finished with
+ * them.</p>
+ */
+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
+ * @see #dispose()
+ */
+ protected abstract boolean isOwningHandle();
+
+ /**
+ * Frees the underlying C++ object
+ * <p>
+ * It is strong recommended that the developer calls this after they
+ * have finished using the object.</p>
+ * <p>
+ * Note, that once an instance of {@link AbstractNativeReference} has been
+ * disposed, calling any of its functions will lead to undefined
+ * behavior.</p>
+ */
+ @Override
+ public abstract void close();
+
+ /**
+ * @deprecated Instead use {@link AbstractNativeReference#close()}
+ */
+ @Deprecated
+ public final void dispose() {
+ close();
+ }
+
+ /**
+ * Simply calls {@link AbstractNativeReference#dispose()} to free
+ * any underlying C++ object reference which has not yet been manually
+ * released.
+ *
+ * @deprecated You should not rely on GC of Rocks objects, and instead should
+ * either call {@link AbstractNativeReference#close()} manually or make
+ * use of some sort of ARM (Automatic Resource Management) such as
+ * Java 7's <a href="https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html">try-with-resources</a>
+ * statement
+ */
+ @Override
+ @Deprecated
+ protected void finalize() throws Throwable {
+ if(isOwningHandle()) {
+ //TODO(AR) log a warning message... developer should have called close()
+ }
+ dispose();
+ super.finalize();
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java
new file mode 100644
index 00000000..9e08f146
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ *
+ * <p>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.</p>
+ *
+ * @param <P> 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<P extends RocksObject>
+ 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(byte[] target) {
+ assert (isOwningHandle());
+ seek0(nativeHandle_, target, target.length);
+ }
+
+ @Override
+ public void seekForPrev(byte[] target) {
+ assert (isOwningHandle());
+ seekForPrev0(nativeHandle_, target, target.length);
+ }
+
+ @Override
+ public void seek(ByteBuffer target) {
+ assert (isOwningHandle() && target.isDirect());
+ seekDirect0(nativeHandle_, target, target.position(), target.remaining());
+ target.position(target.limit());
+ }
+
+ @Override
+ public void seekForPrev(ByteBuffer target) {
+ assert (isOwningHandle() && target.isDirect());
+ seekForPrevDirect0(nativeHandle_, target, 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 status() throws RocksDBException {
+ assert (isOwningHandle());
+ status0(nativeHandle_);
+ }
+
+ /**
+ * <p>Deletes underlying C++ iterator pointer.</p>
+ *
+ * <p>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.</p>
+ */
+ @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 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 status0(long handle) throws RocksDBException;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractSlice.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractSlice.java
new file mode 100644
index 00000000..5a22e295
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractSlice.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T> 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) &lt; 0 if this &lt; other
+ * 2) == 0 if this == other
+ * 3) &gt; 0 if this &gt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java
new file mode 100644
index 00000000..c696c3e1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java
@@ -0,0 +1,20 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java
new file mode 100644
index 00000000..806709b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java
new file mode 100644
index 00000000..cbb49836
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java
new file mode 100644
index 00000000..d525045c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java
new file mode 100644
index 00000000..1f81c99e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ @Deprecated
+ public void remove(byte[] key) throws RocksDBException {
+ delete(nativeHandle_, key, key.length);
+ }
+
+ @Override
+ @Deprecated
+ public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key)
+ throws RocksDBException {
+ delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_);
+ }
+
+ public void put(ByteBuffer key, 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, ByteBuffer key, 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 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_);
+ }
+
+ public void remove(ByteBuffer key) throws RocksDBException {
+ removeDirect(nativeHandle_, key, key.position(), key.remaining(), 0);
+ key.position(key.limit());
+ }
+
+ @Override
+ public void remove(ColumnFamilyHandle columnFamilyHandle, ByteBuffer key)
+ throws RocksDBException {
+ removeDirect(
+ nativeHandle_, key, key.position(), key.remaining(), columnFamilyHandle.nativeHandle_);
+ key.position(key.limit());
+ }
+
+ @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 removeDirect(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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AccessHint.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AccessHint.java
new file mode 100644
index 00000000..877c4ab3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AccessHint.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value;
+ }
+
+ /**
+ * <p>Get the AccessHint enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java
new file mode 100644
index 00000000..772a5900
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java
@@ -0,0 +1,465 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T>> {
+ /**
+ * 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) &le; 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) &le; 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();
+
+ /**
+ * <p>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'.</p>
+ *
+ * <strong>NOTICE</strong>
+ * <p>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.
+ * </p>
+ * <p>If {@code L0} files are merged to {@code level - n},
+ * then, for {@code i&gt;0}, {@code compression_per_level[i]}
+ * determines compaction type for level {@code n+i-1}.</p>
+ *
+ * <strong>Example</strong>
+ * <p>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]}.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p><strong>Default:</strong> empty</p>
+ *
+ * @param compressionLevels list of
+ * {@link org.rocksdb.CompressionType} instances.
+ *
+ * @return the reference to the current options.
+ */
+ T setCompressionPerLevel(
+ List<CompressionType> compressionLevels);
+
+ /**
+ * <p>Return the currently set {@link org.rocksdb.CompressionType}
+ * per instances.</p>
+ *
+ * <p>See: {@link #setCompressionPerLevel(java.util.List)}</p>
+ *
+ * @return list of {@link org.rocksdb.CompressionType}
+ * instances.
+ */
+ List<CompressionType> 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();
+
+ /**
+ * <p>If {@code true}, RocksDB will pick target size of each level
+ * dynamically. We will pick a base level b &gt;= 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</p>
+ *
+ * <ol>
+ * <li>target size is in the range of
+ * (max_bytes_for_level_base / max_bytes_for_level_multiplier,
+ * max_bytes_for_level_base]</li>
+ * <li>target size of the last level (level num_levels-1) equals to extra size
+ * of the level.</li>
+ * </ol>
+ *
+ * <p>At the same time max_bytes_for_level_multiplier and
+ * max_bytes_for_level_multiplier_additional are still satisfied.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p><b>Example</b></p>
+ *
+ * <p>For example, assume {@code max_bytes_for_level_multiplier=10},
+ * {@code num_levels=6}, and {@code max_bytes_for_level_base=10MB}.</p>
+ *
+ * <p>Target sizes of level 1 to 5 starts with:</p>
+ * {@code [- - - - 10MB]}
+ * <p>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:</p>
+ * {@code [- - - 1.1MB 11MB]}
+ * <p>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:</p>
+ * {@code [- - - 5MB 50MB]}
+ * <p>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:</p>
+ * {@code [- - 1.01MB 10.1MB 101MB]}
+ * <p>In the same way, while level 5 further grows, all levels' targets grow,
+ * like</p>
+ * {@code [- - 5MB 50MB 500MB]}
+ * <p>Until level 5 exceeds 1000MB and becomes 1001MB, we make level 2 the
+ * base level and make levels' target sizes like this:</p>
+ * {@code [- 1.001MB 10.01MB 100.1MB 1001MB]}
+ * <p>and go on...</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>{@code max_bytes_for_level_multiplier_additional} is ignored with
+ * this flag on.</p>
+ *
+ * <p>Turning this feature on or off for an existing DB can cause unexpected
+ * LSM tree structure so it's not recommended.</p>
+ *
+ * <p><strong>Caution</strong>: this option is experimental</p>
+ *
+ * <p>Default: false</p>
+ *
+ * @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);
+
+ /**
+ * <p>Return if {@code LevelCompactionDynamicLevelBytes} is enabled.
+ * </p>
+ *
+ * <p>For further information see
+ * {@link #setLevelCompactionDynamicLevelBytes(boolean)}</p>
+ *
+ * @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();
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p><strong>NOTE</strong>: 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.</p>
+ *
+ * <p>Default: false<p>
+ *
+ * @param optimizeFiltersForHits boolean value indicating if this flag is set.
+ * @return the reference to the current options.
+ */
+ T setOptimizeFiltersForHits(
+ boolean optimizeFiltersForHits);
+
+ /**
+ * <p>Returns the current state of the {@code optimize_filters_for_hits}
+ * setting.</p>
+ *
+ * @return boolean value indicating if the flag
+ * {@code optimize_filters_for_hits} was set.
+ */
+ boolean optimizeFiltersForHits();
+
+ /**
+ * In debug mode, RocksDB run consistency checks on the LSM every time the LSM
+ * change (Flush, Compaction, AddFile). These checks are disabled in release
+ * mode, use this option to enable them in release mode as well.
+ *
+ * Default: false
+ *
+ * @param forceConsistencyChecks true to force consistency checks
+ *
+ * @return the reference to the current options.
+ */
+ T setForceConsistencyChecks(
+ boolean forceConsistencyChecks);
+
+ /**
+ * In debug mode, RocksDB run consistency checks on the LSM every time the LSM
+ * change (Flush, Compaction, AddFile). These checks are disabled in release
+ * mode.
+ *
+ * @return true if consistency checks are enforced
+ */
+ boolean forceConsistencyChecks();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java
new file mode 100644
index 00000000..5b581ca1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java
@@ -0,0 +1,464 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T>> {
+ /**
+ * 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 (disable)
+ *
+ * @param memtablePrefixBloomSizeRatio The ratio
+ * @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 (disable)
+ *
+ * @return the ratio
+ */
+ double memtablePrefixBloomSizeRatio();
+
+ /**
+ * Page size for huge page TLB for bloom in memtable. If &le; 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 &le; 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 &le; 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 &lt;&lt; 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 &le; 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 &lt;&lt; 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 &lt; 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 &lt; 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-&gt;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-&gt;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();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java
new file mode 100644
index 00000000..a028edea
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java
@@ -0,0 +1,259 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 (like HDFS)
+ * 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 BackupableDBOptions 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<BackupInfo> getBackupInfo() {
+ assert (isOwningHandle());
+ return getBackupInfo(nativeHandle_);
+ }
+
+ /**
+ * <p>Returns a list of corrupted backup ids. If there
+ * is no corrupted backup the method will return an
+ * empty list.</p>
+ *
+ * @return array of backup ids as int ids.
+ */
+ public int[] getCorruptedBackups() {
+ assert(isOwningHandle());
+ return getCorruptedBackups(nativeHandle_);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @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 backupableDbOptions) 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<BackupInfo> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java
new file mode 100644
index 00000000..9244e4eb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupableDBOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupableDBOptions.java
new file mode 100644
index 00000000..8bb41433
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BackupableDBOptions.java
@@ -0,0 +1,465 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>BackupableDBOptions to control the behavior of a backupable database.
+ * It will be used during the creation of a {@link org.rocksdb.BackupEngine}.
+ * </p>
+ * <p>Note that dispose() must be called before an Options instance
+ * become out-of-scope to release the allocated memory in c++.</p>
+ *
+ * @see org.rocksdb.BackupEngine
+ */
+public class BackupableDBOptions extends RocksObject {
+
+ private Env backupEnv = null;
+ private Logger infoLog = null;
+ private RateLimiter backupRateLimiter = null;
+ private RateLimiter restoreRateLimiter = null;
+
+ /**
+ * <p>BackupableDBOptions constructor.</p>
+ *
+ * @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 BackupableDBOptions(final String path) {
+ super(newBackupableDBOptions(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;
+ }
+ }
+
+ /**
+ * <p>Returns the path to the BackupableDB directory.</p>
+ *
+ * @return the path to the BackupableDB 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.
+ *
+ * If you want to have backups on HDFS, use HDFS Env here!
+ *
+ * Default: null
+ *
+ * @param env The environment to use
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions 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.
+ *
+ * If you want to have backups on HDFS, use HDFS Env here!
+ *
+ * Default: null
+ *
+ * @return The environment in use
+ */
+ public Env backupEnv() {
+ return this.backupEnv;
+ }
+
+ /**
+ * <p>Share table files between backups.</p>
+ *
+ * @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.
+ *
+ * <p>Default: true</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setShareTableFiles(final boolean shareTableFiles) {
+ assert(isOwningHandle());
+ setShareTableFiles(nativeHandle_, shareTableFiles);
+ return this;
+ }
+
+ /**
+ * <p>Share table files between backups.</p>
+ *
+ * @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 BackupableDBOptions.
+ */
+ public BackupableDBOptions 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;
+ }
+
+ /**
+ * <p>Set synchronous backups.</p>
+ *
+ * @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.
+ *
+ * <p>Default: true</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setSync(final boolean sync) {
+ assert(isOwningHandle());
+ setSync(nativeHandle_, sync);
+ return this;
+ }
+
+ /**
+ * <p>Are synchronous backups activated.</p>
+ *
+ * @return boolean value if synchronous backups are configured.
+ */
+ public boolean sync() {
+ assert(isOwningHandle());
+ return sync(nativeHandle_);
+ }
+
+ /**
+ * <p>Set if old data will be destroyed.</p>
+ *
+ * @param destroyOldData If true, it will delete whatever backups there are
+ * already.
+ *
+ * <p>Default: false</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setDestroyOldData(final boolean destroyOldData) {
+ assert(isOwningHandle());
+ setDestroyOldData(nativeHandle_, destroyOldData);
+ return this;
+ }
+
+ /**
+ * <p>Returns if old data will be destroyed will performing new backups.</p>
+ *
+ * @return boolean value indicating if old data will be destroyed.
+ */
+ public boolean destroyOldData() {
+ assert(isOwningHandle());
+ return destroyOldData(nativeHandle_);
+ }
+
+ /**
+ * <p>Set if log files shall be persisted.</p>
+ *
+ * @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.
+ *
+ * <p>Default: true</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setBackupLogFiles(final boolean backupLogFiles) {
+ assert(isOwningHandle());
+ setBackupLogFiles(nativeHandle_, backupLogFiles);
+ return this;
+ }
+
+ /**
+ * <p>Return information if log files shall be persisted.</p>
+ *
+ * @return boolean value indicating if log files will be persisted.
+ */
+ public boolean backupLogFiles() {
+ assert(isOwningHandle());
+ return backupLogFiles(nativeHandle_);
+ }
+
+ /**
+ * <p>Set backup rate limit.</p>
+ *
+ * @param backupRateLimit Max bytes that can be transferred in a second during
+ * backup. If 0 or negative, then go as fast as you can.
+ *
+ * <p>Default: 0</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setBackupRateLimit(long backupRateLimit) {
+ assert(isOwningHandle());
+ backupRateLimit = (backupRateLimit <= 0) ? 0 : backupRateLimit;
+ setBackupRateLimit(nativeHandle_, backupRateLimit);
+ return this;
+ }
+
+ /**
+ * <p>Return backup rate limit which described the max bytes that can be
+ * transferred in a second during backup.</p>
+ *
+ * @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 BackupableDBOptions.
+ */
+ public BackupableDBOptions 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;
+ }
+
+ /**
+ * <p>Set restore rate limit.</p>
+ *
+ * @param restoreRateLimit Max bytes that can be transferred in a second
+ * during restore. If 0 or negative, then go as fast as you can.
+ *
+ * <p>Default: 0</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setRestoreRateLimit(long restoreRateLimit) {
+ assert(isOwningHandle());
+ restoreRateLimit = (restoreRateLimit <= 0) ? 0 : restoreRateLimit;
+ setRestoreRateLimit(nativeHandle_, restoreRateLimit);
+ return this;
+ }
+
+ /**
+ * <p>Return restore rate limit which described the max bytes that can be
+ * transferred in a second during restore.</p>
+ *
+ * @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 BackupableDBOptions.
+ */
+ public BackupableDBOptions 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;
+ }
+
+ /**
+ * <p>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)
+ * </p>
+ *
+ * @param shareFilesWithChecksum boolean value indicating if SST files are
+ * stored using the triple (file name, crc32, file length) and not its name.
+ *
+ * <p>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*</p>
+ *
+ * <p>Default: false</p>
+ *
+ * @return instance of current BackupableDBOptions.
+ */
+ public BackupableDBOptions setShareFilesWithChecksum(
+ final boolean shareFilesWithChecksum) {
+ assert(isOwningHandle());
+ setShareFilesWithChecksum(nativeHandle_, shareFilesWithChecksum);
+ return this;
+ }
+
+ /**
+ * <p>Return of share files with checksum is active.</p>
+ *
+ * @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 BackupableDBOptions.
+ */
+ public BackupableDBOptions 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 BackupableDBOptions.
+ */
+ public BackupableDBOptions 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 newBackupableDBOptions(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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java
new file mode 100644
index 00000000..5bc694af
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java
@@ -0,0 +1,987 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 = false;
+ pinL0FilterAndIndexBlocksInCache = false;
+ pinTopLevelIndexAndFilter = true;
+ indexType = IndexType.kBinarySearch;
+ dataBlockIndexType = DataBlockIndexType.kDataBlockBinarySearch;
+ dataBlockHashTableUtilRatio = 0.75;
+ checksumType = ChecksumType.kCRC32c;
+ noBlockCache = false;
+ blockCache = null;
+ persistentCache = null;
+ blockCacheCompressed = null;
+ blockSize = 4 * 1024;
+ blockSizeDeviation = 10;
+ blockRestartInterval = 16;
+ indexBlockRestartInterval = 1;
+ metadataBlockSize = 4096;
+ partitionFilters = false;
+ useDeltaEncoding = true;
+ filterPolicy = null;
+ wholeKeyFiltering = true;
+ verifyCompression = true;
+ readAmpBytesPerBit = 0;
+ formatVersion = 2;
+ enableIndexCompression = true;
+ blockAlign = false;
+
+ // NOTE: ONLY used if blockCache == null
+ blockCacheSize = 8 * 1024 * 1024;
+ blockCacheNumShardBits = 0;
+
+ // NOTE: ONLY used if blockCacheCompressed == null
+ blockCacheCompressedSize = 0;
+ blockCacheCompressedNumShardBits = 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 false.
+ *
+ * @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;
+ }
+
+ /**
+ * Use the specified cache for compressed blocks.
+ *
+ * If {@code null}, RocksDB will not use a compressed block cache.
+ *
+ * Note: though it looks similar to {@link #setBlockCache(Cache)}, RocksDB
+ * doesn't put the same type of object there.
+ *
+ * {@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 blockCacheCompressed {@link org.rocksdb.Cache} Cache java instance
+ * (e.g. LRUCache).
+ *
+ * @return the reference to the current config.
+ */
+ public BlockBasedTableConfig setBlockCacheCompressed(
+ final Cache blockCacheCompressed) {
+ this.blockCacheCompressed = blockCacheCompressed;
+ 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;
+ }
+
+ /**
+ * 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 disposed before options instances
+ * using this filter is disposed. If {@link Filter#dispose()} function is not
+ * called, then filter object will be GC'd automatically.
+ *
+ * {@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 =&gt; memory usage (percentage of loaded blocks memory)
+ * 1 =&gt; 12.50 %
+ * 2 =&gt; 06.25 %
+ * 4 =&gt; 03.12 %
+ * 8 =&gt; 01.56 %
+ * 16 =&gt; 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;
+ }
+
+ /**
+ * <p>We currently have five versions:</p>
+ *
+ * <ul>
+ * <li><strong>0</strong> - 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).</li>
+ * <li><strong>1</strong> - 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)</li>
+ * <li><strong>2</strong> - 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.</li>
+ * <li><strong>3</strong> - 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.</li>
+ * <li><strong>4</strong> - 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 &gt; 1, you should
+ * probably use this as it would reduce the index size.</li>
+ * </ul>
+ * <p> This option only affects newly written tables. When reading existing
+ * tables, the information about version is read from the footer.</p>
+ *
+ * @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 && formatVersion <= 4);
+ 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 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;
+ }
+
+ /**
+ * Size of compressed block cache. If 0, then block_cache_compressed is set
+ * to null.
+ *
+ * @return size of compressed block cache.
+ */
+ @Deprecated
+ public long blockCacheCompressedSize() {
+ return blockCacheCompressedSize;
+ }
+
+ /**
+ * Size of compressed block cache. If 0, then block_cache_compressed is set
+ * to null.
+ *
+ * @param blockCacheCompressedSize of compressed block cache.
+ * @return the reference to the current config.
+ *
+ * @deprecated Use {@link #setBlockCacheCompressed(Cache)}.
+ */
+ @Deprecated
+ public BlockBasedTableConfig setBlockCacheCompressedSize(
+ final long blockCacheCompressedSize) {
+ this.blockCacheCompressedSize = blockCacheCompressedSize;
+ return this;
+ }
+
+ /**
+ * Controls the number of shards for the block compressed cache.
+ * This is applied only if blockCompressedCacheSize is set to non-negative.
+ *
+ * @return numShardBits the number of shard bits. The resulting
+ * number of shards would be 2 ^ numShardBits. Any negative
+ * number means use default settings.
+ */
+ @Deprecated
+ public int blockCacheCompressedNumShardBits() {
+ return blockCacheCompressedNumShardBits;
+ }
+
+ /**
+ * Controls the number of shards for the block compressed cache.
+ * This is applied only if blockCompressedCacheSize is set to non-negative.
+ *
+ * @param blockCacheCompressedNumShardBits 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 #setBlockCacheCompressed(Cache)}.
+ */
+ @Deprecated
+ public BlockBasedTableConfig setBlockCacheCompressedNumShardBits(
+ final int blockCacheCompressedNumShardBits) {
+ this.blockCacheCompressedNumShardBits = blockCacheCompressedNumShardBits;
+ 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;
+ }
+
+ final long blockCacheCompressedHandle;
+ if (blockCacheCompressed != null) {
+ blockCacheCompressedHandle = blockCacheCompressed.nativeHandle_;
+ } else {
+ blockCacheCompressedHandle = 0;
+ }
+
+ return newTableFactoryHandle(cacheIndexAndFilterBlocks,
+ cacheIndexAndFilterBlocksWithHighPriority,
+ pinL0FilterAndIndexBlocksInCache, pinTopLevelIndexAndFilter,
+ indexType.getValue(), dataBlockIndexType.getValue(),
+ dataBlockHashTableUtilRatio, checksumType.getValue(), noBlockCache,
+ blockCacheHandle, persistentCacheHandle, blockCacheCompressedHandle,
+ blockSize, blockSizeDeviation, blockRestartInterval,
+ indexBlockRestartInterval, metadataBlockSize, partitionFilters,
+ useDeltaEncoding, filterPolicyHandle, wholeKeyFiltering,
+ verifyCompression, readAmpBytesPerBit, formatVersion,
+ enableIndexCompression, blockAlign,
+ blockCacheSize, blockCacheNumShardBits,
+ blockCacheCompressedSize, blockCacheCompressedNumShardBits);
+ }
+
+ 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 blockCacheCompressedHandle,
+ final long blockSize,
+ final int blockSizeDeviation,
+ final int blockRestartInterval,
+ final int indexBlockRestartInterval,
+ final long metadataBlockSize,
+ final boolean partitionFilters,
+ final boolean useDeltaEncoding,
+ final long filterPolicyHandle,
+ final boolean wholeKeyFiltering,
+ final boolean verifyCompression,
+ final int readAmpBytesPerBit,
+ final int formatVersion,
+ final boolean enableIndexCompression,
+ final boolean blockAlign,
+
+ @Deprecated final long blockCacheSize,
+ @Deprecated final int blockCacheNumShardBits,
+
+ @Deprecated final long blockCacheCompressedSize,
+ @Deprecated final int blockCacheCompressedNumShardBits
+ );
+
+ //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 Cache blockCacheCompressed;
+ private long blockSize;
+ private int blockSizeDeviation;
+ private int blockRestartInterval;
+ private int indexBlockRestartInterval;
+ private long metadataBlockSize;
+ private boolean partitionFilters;
+ private boolean useDeltaEncoding;
+ private Filter filterPolicy;
+ private boolean wholeKeyFiltering;
+ private boolean verifyCompression;
+ private int readAmpBytesPerBit;
+ private int formatVersion;
+ private boolean enableIndexCompression;
+ private boolean blockAlign;
+
+ // NOTE: ONLY used if blockCache == null
+ @Deprecated private long blockCacheSize;
+ @Deprecated private int blockCacheNumShardBits;
+
+ // NOTE: ONLY used if blockCacheCompressed == null
+ @Deprecated private long blockCacheCompressedSize;
+ @Deprecated private int blockCacheCompressedNumShardBits;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BloomFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BloomFilter.java
new file mode 100644
index 00000000..0a119878
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BloomFilter.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ *
+ * <p>
+ * 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.</p>
+ */
+public class BloomFilter extends Filter {
+
+ private static final double DEFAULT_BITS_PER_KEY = 10.0;
+ private static final boolean DEFAULT_MODE = true;
+
+ /**
+ * BloomFilter constructor
+ *
+ * <p>
+ * Callers must delete the result after any database that is using the
+ * result has been closed.</p>
+ */
+ public BloomFilter() {
+ this(DEFAULT_BITS_PER_KEY, DEFAULT_MODE);
+ }
+
+ /**
+ * BloomFilter constructor
+ *
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * Callers must delete the result after any database that is using the
+ * result has been closed.</p>
+ *
+ * @param bitsPerKey number of bits to use
+ */
+ public BloomFilter(final double bitsPerKey) {
+ this(bitsPerKey, DEFAULT_MODE);
+ }
+
+ /**
+ * BloomFilter constructor
+ *
+ * <p>
+ * 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.
+ * <p><strong>default bits_per_key</strong>: 10</p>
+ *
+ * <p>use_block_based_builder: use block based filter rather than full filter.
+ * If you want to builder full filter, it needs to be set to false.
+ * </p>
+ * <p><strong>default mode: block based filter</strong></p>
+ * <p>
+ * Callers must delete the result after any database that is using the
+ * result has been closed.</p>
+ *
+ * @param bitsPerKey number of bits to use
+ * @param useBlockBasedMode use block based mode or full filter mode
+ */
+ public BloomFilter(final double bitsPerKey, final boolean useBlockBasedMode) {
+ super(createNewBloomFilter(bitsPerKey, useBlockBasedMode));
+ }
+
+ private native static long createNewBloomFilter(final double bitsKeyKey,
+ final boolean useBlockBasedMode);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BuiltinComparator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BuiltinComparator.java
new file mode 100644
index 00000000..2c89bf21
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/BuiltinComparator.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ *
+ * <ol>
+ * <li>BYTEWISE_COMPARATOR - Sorts all keys in ascending bytewise
+ * order.</li>
+ * <li>REVERSE_BYTEWISE_COMPARATOR - Sorts all keys in descending bytewise
+ * order</li>
+ * </ol>
+ */
+public enum BuiltinComparator {
+ BYTEWISE_COMPARATOR, REVERSE_BYTEWISE_COMPARATOR
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Cache.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Cache.java
new file mode 100644
index 00000000..3952e1d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Cache.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java
new file mode 100644
index 00000000..6c87cc18
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java
@@ -0,0 +1,19 @@
+// 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<Slice> {
+ public CassandraCompactionFilter(boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds) {
+ super(createNewCassandraCompactionFilter0(purgeTtlOnExpiration, gcGracePeriodInSeconds));
+ }
+
+ private native static long createNewCassandraCompactionFilter0(
+ boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java
new file mode 100644
index 00000000..4b0c71ba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java
@@ -0,0 +1,25 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Checkpoint.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Checkpoint.java
new file mode 100644
index 00000000..00096993
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Checkpoint.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>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</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ChecksumType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ChecksumType.java
new file mode 100644
index 00000000..def9f2e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ChecksumType.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+
+ /**
+ * Returns the byte value of the enumerations value
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value_;
+ }
+
+ private ChecksumType(byte value) {
+ value_ = value;
+ }
+
+ private final byte value_;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ClockCache.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ClockCache.java
new file mode 100644
index 00000000..a66dc0e8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ClockCache.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java
new file mode 100644
index 00000000..8bb570e5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>Describes a column family with a
+ * name and respective Options.</p>
+ */
+public class ColumnFamilyDescriptor {
+
+ /**
+ * <p>Creates a new Column Family using a name and default
+ * options,</p>
+ *
+ * @param columnFamilyName name of column family.
+ * @since 3.10.0
+ */
+ public ColumnFamilyDescriptor(final byte[] columnFamilyName) {
+ this(columnFamilyName, new ColumnFamilyOptions());
+ }
+
+ /**
+ * <p>Creates a new Column Family using a name and custom
+ * options.</p>
+ *
+ * @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 name of column family.
+ *
+ * @return column family name.
+ * @since 3.10.0
+ *
+ * @deprecated Use {@link #getName()} instead.
+ */
+ @Deprecated
+ public byte[] columnFamilyName() {
+ return getName();
+ }
+
+ /**
+ * Retrieve assigned options instance.
+ *
+ * @return Options instance assigned to this instance.
+ */
+ public ColumnFamilyOptions getOptions() {
+ return columnFamilyOptions_;
+ }
+
+ /**
+ * Retrieve assigned options instance.
+ *
+ * @return Options instance assigned to this instance.
+ *
+ * @deprecated Use {@link #getOptions()} instead.
+ */
+ @Deprecated
+ public ColumnFamilyOptions columnFamilyOptions() {
+ return getOptions();
+ }
+
+ @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java
new file mode 100644
index 00000000..9cda136b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+ 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;
+ }
+
+ /**
+ * 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 {
+ return getName(nativeHandle_);
+ }
+
+ /**
+ * Gets the ID of the Column Family.
+ *
+ * @return the ID of the Column Family.
+ */
+ public int getID() {
+ 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());
+ 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 {
+ return Objects.hash(getName(), getID(), rocksDB_.nativeHandle_);
+ } catch (RocksDBException e) {
+ throw new RuntimeException("Cannot calculate hash code of column family handle", e);
+ }
+ }
+
+ /**
+ * <p>Deletes underlying C++ iterator pointer.</p>
+ *
+ * <p>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.</p>
+ */
+ @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java
new file mode 100644
index 00000000..19190401
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<LevelMetaData> levels() {
+ return Arrays.asList(levels);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java
new file mode 100644
index 00000000..07f07b3e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java
@@ -0,0 +1,1001 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.Properties;
+
+/**
+ * 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()).
+ *
+ * If {@link #dispose()} function is not called, then it will be GC'd
+ * automatically and native resources will be released as part of the process.
+ */
+public class ColumnFamilyOptions extends RocksObject
+ implements ColumnFamilyOptionsInterface<ColumnFamilyOptions>,
+ MutableColumnFamilyOptionsInterface<ColumnFamilyOptions> {
+ 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(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_;
+ }
+
+ /**
+ * Constructor from Options
+ *
+ * @param options The options.
+ */
+ public ColumnFamilyOptions(final Options options) {
+ super(newColumnFamilyOptionsFromOptions(options.nativeHandle_));
+ }
+
+ /**
+ * <p>Constructor to be used by
+ * {@link #getColumnFamilyOptionsFromProps(java.util.Properties)},
+ * {@link ColumnFamilyDescriptor#getOptions()}
+ * and also called via JNI.</p>
+ *
+ * @param handle native handle to ColumnFamilyOptions instance.
+ */
+ ColumnFamilyOptions(final long handle) {
+ super(handle);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p><strong>Note</strong>: 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}.</p>
+ *
+ * @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) {
+ if (properties == null || properties.size() == 0) {
+ throw new IllegalArgumentException(
+ "Properties value must contain at least one value.");
+ }
+ ColumnFamilyOptions columnFamilyOptions = null;
+ StringBuilder stringBuilder = new StringBuilder();
+ for (final String name : properties.stringPropertyNames()){
+ stringBuilder.append(name);
+ stringBuilder.append("=");
+ stringBuilder.append(properties.getProperty(name));
+ stringBuilder.append(";");
+ }
+ long handle = getColumnFamilyOptionsFromProps(
+ stringBuilder.toString());
+ if (handle != 0){
+ columnFamilyOptions = new ColumnFamilyOptions(handle);
+ }
+ return columnFamilyOptions;
+ }
+
+ @Override
+ public ColumnFamilyOptions optimizeForSmallDb() {
+ optimizeForSmallDb(nativeHandle_);
+ 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<? extends AbstractSlice<?>>
+ compactionFilter) {
+ setCompactionFilterHandle(nativeHandle_, compactionFilter.nativeHandle_);
+ compactionFilter_ = compactionFilter;
+ return this;
+ }
+
+ @Override
+ public AbstractCompactionFilter<? extends AbstractSlice<?>> compactionFilter() {
+ assert (isOwningHandle());
+ return compactionFilter_;
+ }
+
+ @Override
+ public ColumnFamilyOptions setCompactionFilterFactory(final AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>> compactionFilterFactory) {
+ assert (isOwningHandle());
+ setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_);
+ compactionFilterFactory_ = compactionFilterFactory;
+ return this;
+ }
+
+ @Override
+ public AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>> 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<CompressionType> 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<CompressionType> compressionPerLevel() {
+ final byte[] byteCompressionTypes =
+ compressionPerLevel(nativeHandle_);
+ final List<CompressionType> 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 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 setBloomLocality(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(
+ long memtableHugePageSize) {
+ setMemtableHugePageSize(nativeHandle_,
+ memtableHugePageSize);
+ return this;
+ }
+
+ @Override
+ public long memtableHugePageSize() {
+ return memtableHugePageSize(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setSoftPendingCompactionBytesLimit(long softPendingCompactionBytesLimit) {
+ setSoftPendingCompactionBytesLimit(nativeHandle_,
+ softPendingCompactionBytesLimit);
+ return this;
+ }
+
+ @Override
+ public long softPendingCompactionBytesLimit() {
+ return softPendingCompactionBytesLimit(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setHardPendingCompactionBytesLimit(long hardPendingCompactionBytesLimit) {
+ setHardPendingCompactionBytesLimit(nativeHandle_, hardPendingCompactionBytesLimit);
+ return this;
+ }
+
+ @Override
+ public long hardPendingCompactionBytesLimit() {
+ return hardPendingCompactionBytesLimit(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setLevel0FileNumCompactionTrigger(int level0FileNumCompactionTrigger) {
+ setLevel0FileNumCompactionTrigger(nativeHandle_, level0FileNumCompactionTrigger);
+ return this;
+ }
+
+ @Override
+ public int level0FileNumCompactionTrigger() {
+ return level0FileNumCompactionTrigger(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setLevel0SlowdownWritesTrigger(int level0SlowdownWritesTrigger) {
+ setLevel0SlowdownWritesTrigger(nativeHandle_, level0SlowdownWritesTrigger);
+ return this;
+ }
+
+ @Override
+ public int level0SlowdownWritesTrigger() {
+ return level0SlowdownWritesTrigger(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setLevel0StopWritesTrigger(int level0StopWritesTrigger) {
+ setLevel0StopWritesTrigger(nativeHandle_, level0StopWritesTrigger);
+ return this;
+ }
+
+ @Override
+ public int level0StopWritesTrigger() {
+ return level0StopWritesTrigger(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setMaxBytesForLevelMultiplierAdditional(int[] maxBytesForLevelMultiplierAdditional) {
+ setMaxBytesForLevelMultiplierAdditional(nativeHandle_, maxBytesForLevelMultiplierAdditional);
+ return this;
+ }
+
+ @Override
+ public int[] maxBytesForLevelMultiplierAdditional() {
+ return maxBytesForLevelMultiplierAdditional(nativeHandle_);
+ }
+
+ @Override
+ public ColumnFamilyOptions setParanoidFileChecks(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 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_);
+ }
+
+ private static native long getColumnFamilyOptionsFromProps(
+ 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 native void optimizeForSmallDb(final long handle);
+ 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 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 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 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);
+
+ // 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<? extends AbstractSlice<?>> compactionFilter_;
+ private AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>>
+ compactionFilterFactory_;
+ private CompactionOptionsUniversal compactionOptionsUniversal_;
+ private CompactionOptionsFIFO compactionOptionsFIFO_;
+ private CompressionOptions bottommostCompressionOptions_;
+ private CompressionOptions compressionOptions_;
+
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java
new file mode 100644
index 00000000..b02c6c23
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java
@@ -0,0 +1,449 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 ColumnFamilyOptionsInterface<T extends ColumnFamilyOptionsInterface<T>>
+ extends AdvancedColumnFamilyOptionsInterface<T> {
+ /**
+ * 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 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);
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Make sure to also call IncreaseParallelism(), which will provide the
+ * biggest performance gains.</p>
+ * <p>Note: we might use more memory than memtable_memory_budget during high
+ * write rate period</p>
+ *
+ * @return the instance of the current object.
+ */
+ T optimizeLevelStyleCompaction();
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Make sure to also call IncreaseParallelism(), which will provide the
+ * biggest performance gains.</p>
+ * <p>Note: we might use more memory than memtable_memory_budget during high
+ * write rate period</p>
+ *
+ * @param memtableMemoryBudget memory budget in bytes
+ * @return the instance of the current object.
+ */
+ T optimizeLevelStyleCompaction(
+ long memtableMemoryBudget);
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Universal style compaction is focused on reducing Write Amplification
+ * Factor for big data sets, but increases Space Amplification.</p>
+ *
+ * <p>Make sure to also call IncreaseParallelism(), which will provide the
+ * biggest performance gains.</p>
+ *
+ * <p>Note: we might use more memory than memtable_memory_budget during high
+ * write rate period</p>
+ *
+ * @return the instance of the current object.
+ */
+ T optimizeUniversalStyleCompaction();
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Universal style compaction is focused on reducing Write Amplification
+ * Factor for big data sets, but increases Space Amplification.</p>
+ *
+ * <p>Make sure to also call IncreaseParallelism(), which will provide the
+ * biggest performance gains.</p>
+ *
+ * <p>Note: we might use more memory than memtable_memory_budget during high
+ * write rate period</p>
+ *
+ * @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);
+
+ /**
+ * <p>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.</p>
+ *
+ * @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);
+
+ /**
+ * <p>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.</p>
+ *
+ * @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<? extends AbstractSlice<?>> compactionFilter);
+
+ /**
+ * Accessor for the CompactionFilter instance in use.
+ *
+ * @return Reference to the CompactionFilter, or null if one hasn't been set.
+ */
+ AbstractCompactionFilter<? extends AbstractSlice<?>> 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<? extends AbstractCompactionFilter<?>>
+ compactionFilterFactory);
+
+ /**
+ * Accessor for the CompactionFilterFactory instance in use.
+ *
+ * @return Reference to the CompactionFilterFactory, or null if one hasn't been set.
+ */
+ AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>> 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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();
+
+ /**
+ * 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();
+
+ /**
+ * Default memtable memory budget used with the following methods:
+ *
+ * <ol>
+ * <li>{@link #optimizeLevelStyleCompaction()}</li>
+ * <li>{@link #optimizeUniversalStyleCompaction()}</li>
+ * </ol>
+ */
+ long DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET = 512 * 1024 * 1024;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java
new file mode 100644
index 00000000..c07bd96a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java
@@ -0,0 +1,237 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+ // For level based compaction, we can configure if we want to skip/force bottommost level compaction.
+ // The order of this neum MUST follow the C++ layer. See BottommostLevelCompaction in db/options.h
+ public enum BottommostLevelCompaction {
+ /**
+ * Skip bottommost level compaction
+ */
+ kSkip((byte)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);
+
+ private final byte value;
+
+ BottommostLevelCompaction(final byte value) {
+ this.value = value;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @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;
+ 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 &gt; 0, it will replace the option in the DBOptions for this compaction
+ * @return number of subcompactions
+ */
+ public int maxSubcompactions() {
+ return maxSubcompactions(nativeHandle_);
+ }
+
+ /**
+ * If &gt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java
new file mode 100644
index 00000000..8b59edc9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+ }
+
+ /**
+ * 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<String> inputFiles() {
+ return Arrays.asList(inputFiles(nativeHandle_));
+ }
+
+ /**
+ * Get the names of the compaction output files.
+ *
+ * @return the names of the output files.
+ */
+ public List<String> 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<String, TableProperties> 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<String, TableProperties> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java
new file mode 100644
index 00000000..3d53b556
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java
@@ -0,0 +1,295 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java
new file mode 100644
index 00000000..2c7e391f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 &gt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java
new file mode 100644
index 00000000..4c8d6545
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java
new file mode 100644
index 00000000..d2dfa4ee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java
@@ -0,0 +1,273 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 &lt; 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 &lt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionPriority.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionPriority.java
new file mode 100644
index 00000000..a4f53cd6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionPriority.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java
new file mode 100644
index 00000000..f18c4812
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 &gt; level0_file_num_compaction_trigger
+ */
+ kLevelL0FilesNum((byte)0x1),
+
+ /**
+ * [Level] total size of level &gt; 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 &gt; level0_file_num_compaction_trigger
+ */
+ kUniversalSortedRunNum((byte)0x5),
+
+ /**
+ * [FIFO] total size &gt; max_table_files_size
+ */
+ kFIFOMaxSize((byte)0x6),
+
+ /**
+ * [FIFO] reduce number of files.
+ */
+ kFIFOReduceNumFiles((byte)0x7),
+
+ /**
+ * [FIFO] files with creation time &lt; (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);
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStopStyle.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStopStyle.java
new file mode 100644
index 00000000..f6e63209
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStopStyle.java
@@ -0,0 +1,55 @@
+// 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 &gt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java
new file mode 100644
index 00000000..b24bbf85
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ *
+ * <ol>
+ * <li><strong>LEVEL</strong> - Level based Compaction style</li>
+ * <li><strong>UNIVERSAL</strong> - Universal Compaction Style is a
+ * compaction style, targeting the use cases requiring lower write
+ * amplification, trading off read amplification and space
+ * amplification.</li>
+ * <li><strong>FIFO</strong> - 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.</li>
+ * <li><strong>NONE</strong> - Disable background compaction.
+ * Compaction jobs are submitted
+ * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, CompactionJobInfo)} ()}.</li>
+ * </ol>
+ *
+ * @see <a
+ * href="https://github.com/facebook/rocksdb/wiki/Universal-Compaction">
+ * Universal Compaction</a>
+ * @see <a
+ * href="https://github.com/facebook/rocksdb/wiki/FIFO-compaction-style">
+ * FIFO Compaction</a>
+ */
+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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorOptions.java
new file mode 100644
index 00000000..8c316285
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorOptions.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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()} &gt; 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()} &gt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java
new file mode 100644
index 00000000..199980b6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ byte getValue() {
+ return value;
+ }
+
+ /**
+ * <p>Get the ComparatorType enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java
new file mode 100644
index 00000000..a9072bbb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionType.java
new file mode 100644
index 00000000..2781537c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/CompressionType.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ *
+ * <p>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.</p>
+ */
+public enum CompressionType {
+
+ NO_COMPRESSION((byte) 0x0, null),
+ SNAPPY_COMPRESSION((byte) 0x1, "snappy"),
+ ZLIB_COMPRESSION((byte) 0x2, "z"),
+ BZLIB2_COMPRESSION((byte) 0x3, "bzip2"),
+ LZ4_COMPRESSION((byte) 0x4, "lz4"),
+ LZ4HC_COMPRESSION((byte) 0x5, "lz4hc"),
+ XPRESS_COMPRESSION((byte) 0x6, "xpress"),
+ ZSTD_COMPRESSION((byte)0x7, "zstd"),
+ DISABLE_COMPRESSION_OPTION((byte)0x7F, null);
+
+ /**
+ * <p>Get the CompressionType enumeration value by
+ * passing the library name to this method.</p>
+ *
+ * <p>If library cannot be found the enumeration
+ * value {@code NO_COMPRESSION} will be returned.</p>
+ *
+ * @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;
+ }
+
+ /**
+ * <p>Get the CompressionType enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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.");
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value_;
+ }
+
+ /**
+ * <p>Returns the library name of the compression type
+ * identified by the enumeration value.</p>
+ *
+ * @return library name
+ */
+ public String getLibraryName() {
+ return libraryName_;
+ }
+
+ CompressionType(final byte value, final String libraryName) {
+ value_ = value;
+ libraryName_ = libraryName;
+ }
+
+ private final byte value_;
+ private final String libraryName_;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java
new file mode 100644
index 00000000..36cc4abd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java
@@ -0,0 +1,1403 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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()).
+ *
+ * If {@link #dispose()} function is not called, then it will be GC'd
+ * automatically and native resources will be released as part of the process.
+ */
+public class DBOptions extends RocksObject
+ implements DBOptionsInterface<DBOptions>,
+ MutableDBOptionsInterface<DBOptions> {
+ 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;
+ }
+
+ /**
+ * 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_));
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p><strong>Note</strong>: 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}.</p>
+ *
+ * @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) {
+ if (properties == null || properties.size() == 0) {
+ throw new IllegalArgumentException(
+ "Properties value must contain at least one value.");
+ }
+ DBOptions dbOptions = null;
+ StringBuilder stringBuilder = new StringBuilder();
+ for (final String name : properties.stringPropertyNames()){
+ stringBuilder.append(name);
+ stringBuilder.append("=");
+ stringBuilder.append(properties.getProperty(name));
+ stringBuilder.append(";");
+ }
+ long handle = getDBOptionsFromProps(
+ stringBuilder.toString());
+ 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<DbPath> 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<DbPath> 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<DbPath> 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 void setBaseBackgroundCompactions(
+ final int baseBackgroundCompactions) {
+ assert(isOwningHandle());
+ setBaseBackgroundCompactions(nativeHandle_, baseBackgroundCompactions);
+ }
+
+ @Override
+ public int baseBackgroundCompactions() {
+ assert(isOwningHandle());
+ return baseBackgroundCompactions(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 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 setNewTableReaderForCompactionInputs(
+ final boolean newTableReaderForCompactionInputs) {
+ assert(isOwningHandle());
+ setNewTableReaderForCompactionInputs(nativeHandle_,
+ newTableReaderForCompactionInputs);
+ return this;
+ }
+
+ @Override
+ public boolean newTableReaderForCompactionInputs() {
+ assert(isOwningHandle());
+ return newTableReaderForCompactionInputs(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_);
+ }
+
+ //TODO(AR) NOW
+// @Override
+// public DBOptions setListeners(final List<EventListener> listeners) {
+// assert(isOwningHandle());
+// final long[] eventListenerHandlers = new long[listeners.size()];
+// for (int i = 0; i < eventListenerHandlers.length; i++) {
+// eventListenerHandlers[i] = listeners.get(i).nativeHandle_;
+// }
+// setEventListeners(nativeHandle_, eventListenerHandlers);
+// return this;
+// }
+//
+// @Override
+// public Collection<EventListener> listeners() {
+// assert(isOwningHandle());
+// final long[] eventListenerHandlers = listeners(nativeHandle_);
+// if (eventListenerHandlers == null || eventListenerHandlers.length == 0) {
+// return Collections.emptyList();
+// }
+//
+// final List<EventListener> eventListeners = new ArrayList<>();
+// for (final long eventListenerHandle : eventListenerHandlers) {
+// eventListeners.add(new EventListener(eventListenerHandle)); //TODO(AR) check ownership is set to false!
+// }
+// return eventListeners;
+// }
+
+ @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 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 setPreserveDeletes(final boolean preserveDeletes) {
+ assert(isOwningHandle());
+ setPreserveDeletes(nativeHandle_, preserveDeletes);
+ return this;
+ }
+
+ @Override
+ public boolean preserveDeletes() {
+ assert(isOwningHandle());
+ return preserveDeletes(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_);
+ }
+
+ static final int DEFAULT_NUM_SHARD_BITS = -1;
+
+
+
+
+ /**
+ * <p>Private constructor to be used by
+ * {@link #getDBOptionsFromProps(java.util.Properties)}</p>
+ *
+ * @param nativeHandle native handle to DBOptions instance.
+ */
+ private DBOptions(final long nativeHandle) {
+ super(nativeHandle);
+ }
+
+ 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 setBaseBackgroundCompactions(long handle,
+ int baseBackgroundCompactions);
+ private native int baseBackgroundCompactions(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 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 setNewTableReaderForCompactionInputs(final long handle,
+ final boolean newTableReaderForCompactionInputs);
+ private native boolean newTableReaderForCompactionInputs(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 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 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 setPreserveDeletes(final long handle,
+ final boolean preserveDeletes);
+ private native boolean preserveDeletes(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);
+
+ // 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java
new file mode 100644
index 00000000..99e24f3d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java
@@ -0,0 +1,1564 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T extends DBOptionsInterface<T>> {
+ /**
+ * 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();
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>You almost definitely want to call this function if your system is
+ * bottlenecked by RocksDB.</p>
+ *
+ * @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();
+
+ /**
+ * <p>If true, missing column families will be automatically created</p>
+ *
+ * <p>Default: false</p>
+ *
+ * @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);
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Default: nullptr</p>
+ *
+ * @param logger {@link Logger} instance.
+ * @return the instance of the current object.
+ */
+ T setLogger(Logger logger);
+
+ /**
+ * <p>Sets the RocksDB log level. Default level is INFO</p>
+ *
+ * @param infoLogLevel log level to set.
+ * @return the instance of the current object.
+ */
+ T setInfoLogLevel(InfoLogLevel infoLogLevel);
+
+ /**
+ * <p>Returns currently set log level.</p>
+ * @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();
+
+ /**
+ * <p>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.</p>
+ *
+ * @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);
+
+ /**
+ * <p>Returns statistics object.</p>
+ *
+ * @return the instance of the statistics object or null if there is no
+ * statistics object.
+ *
+ * @see #setStatistics(Statistics)
+ */
+ Statistics statistics();
+
+ /**
+ * <p>If true, then every store to stable storage will issue a fsync.</p>
+ * <p>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.</p>
+ * <p>Default: false</p>
+ *
+ * @param useFsync a boolean flag to specify whether to use fsync
+ * @return the instance of the current object.
+ */
+ T setUseFsync(boolean useFsync);
+
+ /**
+ * <p>If true, then every store to stable storage will issue a fsync.</p>
+ * <p>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.</p>
+ *
+ * @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<DbPath> 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<DbPath> 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.
+ * <ol>
+ * <li>If both set to 0, logs will be deleted asap and will not get into
+ * the archive.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If both are not 0, WAL files will be checked every 10 min and both
+ * checks will be performed with ttl being first.</li>
+ * </ol>
+ *
+ * @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.
+ * <ol>
+ * <li>If both set to 0, logs will be deleted asap and will not get into
+ * the archive.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If both are not 0, WAL files will be checked every 10 min and both
+ * checks will be performed with ttl being first.</li>
+ * </ol>
+ *
+ * @return the wal-ttl seconds
+ * @see #walSizeLimitMB()
+ */
+ long walTtlSeconds();
+
+ /**
+ * WalTtlSeconds() and walSizeLimitMB() affect how archived logs
+ * will be deleted.
+ * <ol>
+ * <li>If both set to 0, logs will be deleted asap and will not get into
+ * the archive.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If both are not 0, WAL files will be checked every 10 min and both
+ * checks will be performed with ttl being first.</li>
+ * </ol>
+ *
+ * @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.
+ * <ol>
+ * <li>If both set to 0, logs will be deleted asap and will not get into
+ * the archive.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If both are not 0, WAL files will be checked every 10 min and both
+ * checks will be performed with ttl being first.</li>
+ * </ol>
+ * @return size limit in mega-bytes.
+ * @see #walSizeLimitMB()
+ */
+ long walSizeLimitMB();
+
+ /**
+ * 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 <a href="https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager">
+ * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager</a>
+ * 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. <br>
+ *
+ * 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();
+
+ /**
+ * If true, always create a new file descriptor and new table reader
+ * for compaction inputs. Turn this parameter on may introduce extra
+ * memory usage in the table reader, if it allocates extra memory
+ * for indexes. This will allow file descriptor prefetch options
+ * to be set for compaction input files and not to impact file
+ * descriptors for the same file used by user queries.
+ * Suggest to enable {@link BlockBasedTableConfig#cacheIndexAndFilterBlocks()}
+ * for this mode if using block-based table.
+ *
+ * Default: false
+ *
+ * @param newTableReaderForCompactionInputs true if a new file descriptor and
+ * table reader should be created for compaction inputs
+ *
+ * @return the reference to the current options.
+ */
+ T setNewTableReaderForCompactionInputs(
+ boolean newTableReaderForCompactionInputs);
+
+ /**
+ * If true, always create a new file descriptor and new table reader
+ * for compaction inputs. Turn this parameter on may introduce extra
+ * memory usage in the table reader, if it allocates extra memory
+ * for indexes. This will allow file descriptor prefetch options
+ * to be set for compaction input files and not to impact file
+ * descriptors for the same file used by user queries.
+ * Suggest to enable {@link BlockBasedTableConfig#cacheIndexAndFilterBlocks()}
+ * for this mode if using block-based table.
+ *
+ * Default: false
+ *
+ * @return true if a new file descriptor and table reader are created for
+ * compaction inputs
+ */
+ boolean newTableReaderForCompactionInputs();
+
+ /**
+ * 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();
+
+ //TODO(AR) NOW
+// /**
+// * Sets the {@link EventListener}s whose callback functions
+// * will be called when specific RocksDB event happens.
+// *
+// * @param listeners the listeners who should be notified on various events.
+// *
+// * @return the instance of the current object.
+// */
+// T setListeners(final List<EventListener> listeners);
+//
+// /**
+// * Gets the {@link EventListener}s whose callback functions
+// * will be called when specific RocksDB event happens.
+// *
+// * @return a collection of Event listeners.
+// */
+// Collection<EventListener> 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();
+
+ /**
+ * 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 &gt;= 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();
+
+ /**
+ * Needed to support differential snapshots.
+ * If set to true then DB will only process deletes with sequence number
+ * less than what was set by SetPreserveDeletesSequenceNumber(uint64_t ts).
+ * Clients are responsible to periodically call this method to advance
+ * the cutoff time. If this method is never called and preserve_deletes
+ * is set to true NO deletes will ever be processed.
+ * At the moment this only keeps normal deletes, SingleDeletes will
+ * not be preserved.
+ *
+ * DEFAULT: false
+ *
+ * @param preserveDeletes true to preserve deletes.
+ *
+ * @return the reference to the current options.
+ */
+ T setPreserveDeletes(final boolean preserveDeletes);
+
+ /**
+ * Returns true if deletes are preserved.
+ * See {@link #setPreserveDeletes(boolean)}.
+ *
+ * @return true if deletes are preserved, false otherwise.
+ */
+ boolean preserveDeletes();
+
+ /**
+ * 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();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java
new file mode 100644
index 00000000..513e5b42
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DbPath.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DbPath.java
new file mode 100644
index 00000000..3f0b6755
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DbPath.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DirectSlice.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DirectSlice.java
new file mode 100644
index 00000000..b0d35c3c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/DirectSlice.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ByteBuffer> {
+ 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;
+ }
+
+ @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 disposeInternalBuf(final long handle,
+ long internalBufferOffset);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EncodingType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EncodingType.java
new file mode 100644
index 00000000..5ceeb54c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EncodingType.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ *
+ * <p>The value will determine how to encode keys
+ * when writing to a new SST file.</p>
+ *
+ * <p>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.</p>
+ */
+public enum EncodingType {
+ /**
+ * Always write full keys without any special encoding.
+ */
+ kPlain((byte) 0),
+ /**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ */
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Env.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Env.java
new file mode 100644
index 00000000..719296a1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Env.java
@@ -0,0 +1,167 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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();
+ }
+
+ /**
+ * <p>Returns the default environment suitable for the current operating
+ * system.</p>
+ *
+ * <p>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.</p>
+ *
+ * @return the default {@link org.rocksdb.RocksEnv} instance.
+ */
+ public static Env getDefault() {
+ return DEFAULT_ENV;
+ }
+
+ /**
+ * <p>Sets the number of background worker threads of the flush pool
+ * for this environment.</p>
+ * <p>Default number: 1</p>
+ *
+ * @param number the number of threads
+ *
+ * @return current {@link RocksEnv} instance.
+ */
+ public Env setBackgroundThreads(final int number) {
+ return setBackgroundThreads(number, Priority.LOW);
+ }
+
+ /**
+ * <p>Gets the number of background worker threads of the pool
+ * for this environment.</p>
+ *
+ * @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());
+ }
+
+ /**
+ * <p>Sets the number of background worker threads of the specified thread
+ * pool for this environment.</p>
+ *
+ * @param number the number of threads
+ * @param priority the priority id of a specified thread pool.
+ *
+ * <p>Default number: 1</p>
+ * @return current {@link RocksEnv} instance.
+ */
+ public Env setBackgroundThreads(final int number, final Priority priority) {
+ setBackgroundThreads(nativeHandle_, number, priority.getValue());
+ return this;
+ }
+
+ /**
+ * <p>Returns the length of the queue associated with the specified
+ * thread pool.</p>
+ *
+ * @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<ThreadStatus> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java
new file mode 100644
index 00000000..6baddb31
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java
@@ -0,0 +1,366 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Experimental.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Experimental.java
new file mode 100644
index 00000000..64b404d6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Experimental.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Filter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Filter.java
new file mode 100644
index 00000000..7f490cf5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Filter.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java
new file mode 100644
index 00000000..760b515f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java
new file mode 100644
index 00000000..b943cd99
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java
@@ -0,0 +1,174 @@
+// 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_;
+ }
+
+ /**
+ * <p>Set the size of huge tlb or allocate the hashtable bytes from
+ * malloc if {@code size <= 0}.</p>
+ *
+ * <p>The user needs to reserve huge pages for it to be allocated,
+ * like: {@code sysctl -w vm.nr_hugepages=20}</p>
+ *
+ * <p>See linux documentation/vm/hugetlbpage.txt</p>
+ *
+ * @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 distrubtion 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java
new file mode 100644
index 00000000..efc78b14
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java
@@ -0,0 +1,106 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java
new file mode 100644
index 00000000..4d8d3bff
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * HDFS environment.
+ */
+public class HdfsEnv extends Env {
+
+ /**
+ <p>Creates a new environment that is used for HDFS environment.</p>
+ *
+ * <p>The caller must delete the result when it is
+ * no longer needed.</p>
+ *
+ * @param fsName the HDFS as a string in the form "hdfs://hostname:port/"
+ */
+ public HdfsEnv(final String fsName) {
+ super(createHdfsEnv(fsName));
+ }
+
+ private static native long createHdfsEnv(final String fsName);
+ @Override protected final native void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java
new file mode 100644
index 00000000..81d89088
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java
new file mode 100644
index 00000000..696ee75f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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),
+
+ STALL_L0_SLOWDOWN_COUNT((byte) 0xD),
+
+ STALL_MEMTABLE_COMPACTION_COUNT((byte) 0xE),
+
+ STALL_L0_NUM_FILES_COUNT((byte) 0xF),
+
+ HARD_RATE_LIMIT_DELAY_COUNT((byte) 0x10),
+
+ SOFT_RATE_LIMIT_DELAY_COUNT((byte) 0x11),
+
+ 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 garbage collection time.
+ */
+ BLOB_DB_GC_MICROS((byte) 0x2C),
+
+ /**
+ * BlobDB compression time.
+ */
+ BLOB_DB_COMPRESSION_MICROS((byte) 0x2D),
+
+ /**
+ * BlobDB decompression time.
+ */
+ BLOB_DB_DECOMPRESSION_MICROS((byte) 0x2E),
+
+ // 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Holder.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Holder.java
new file mode 100644
index 00000000..716a0bda
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Holder.java
@@ -0,0 +1,46 @@
+// 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<T> {
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IndexType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IndexType.java
new file mode 100644
index 00000000..04e48146
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IndexType.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+
+ /**
+ * 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/InfoLogLevel.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/InfoLogLevel.java
new file mode 100644
index 00000000..b7c0f070
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/InfoLogLevel.java
@@ -0,0 +1,49 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java
new file mode 100644
index 00000000..a6a308da
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java
@@ -0,0 +1,227 @@
+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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LRUCache.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LRUCache.java
new file mode 100644
index 00000000..5e5bdeea
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LRUCache.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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));
+ }
+
+ private native static long newLRUCache(final long capacity,
+ final int numShardBits, final boolean strictCapacityLimit,
+ final double highPriPoolRatio);
+ @Override protected final native void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java
new file mode 100644
index 00000000..c5685098
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<SstFileMetaData> files() {
+ return Arrays.asList(files);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java
new file mode 100644
index 00000000..35d883e1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LogFile.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LogFile.java
new file mode 100644
index 00000000..ef24a642
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/LogFile.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Logger.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Logger.java
new file mode 100644
index 00000000..00a5d567
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Logger.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * <p>This class provides a custom logger functionality
+ * in Java which wraps {@code RocksDB} logging facilities.
+ * </p>
+ *
+ * <p>Using this class RocksDB can log with common
+ * Java logging APIs like Log4j or Slf4j without keeping
+ * database logs in the filesystem.</p>
+ *
+ * <strong>Performance</strong>
+ * <p>There are certain performance penalties using a Java
+ * {@code Logger} implementation within production code.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>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}.
+ * </p>
+ */
+public abstract class Logger extends RocksCallbackObject {
+
+ private final static long WITH_OPTIONS = 0;
+ private final static long WITH_DBOPTIONS = 1;
+
+ /**
+ * <p>AbstractLogger constructor.</p>
+ *
+ * <p><strong>Important:</strong> the log level set within
+ * the {@link org.rocksdb.Options} instance will be used as
+ * maximum log level of RocksDB.</p>
+ *
+ * @param options {@link org.rocksdb.Options} instance.
+ */
+ public Logger(final Options options) {
+ super(options.nativeHandle_, WITH_OPTIONS);
+
+ }
+
+ /**
+ * <p>AbstractLogger constructor.</p>
+ *
+ * <p><strong>Important:</strong> the log level set within
+ * the {@link org.rocksdb.DBOptions} instance will be used
+ * as maximum log level of RocksDB.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemTableConfig.java
new file mode 100644
index 00000000..83cee974
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemTableConfig.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java
new file mode 100644
index 00000000..6010ce7a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ *
+ * <p>The value will be used as a key to indicate the type of memory usage
+ * described</p>
+ */
+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_;
+ }
+
+ /**
+ * <p>Get the MemoryUsageType enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java
new file mode 100644
index 00000000..52b2175e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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<MemoryUsageType, Long> getApproximateMemoryUsageByType(final List<RocksDB> dbs, final Set<Cache> 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<RocksDB> 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<Byte, Long> byteOutput = getApproximateMemoryUsageByType(dbHandles, cacheHandles);
+ Map<MemoryUsageType, Long> output = new HashMap<>();
+ for(Map.Entry<Byte, Long> longEntry : byteOutput.entrySet()) {
+ output.put(MemoryUsageType.getMemoryUsageType(longEntry.getKey()), longEntry.getValue());
+ }
+ return output;
+ }
+
+ private native static Map<Byte, Long> getApproximateMemoryUsageByType(final long[] dbHandles,
+ final long[] cacheHandles);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MergeOperator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MergeOperator.java
new file mode 100644
index 00000000..c299f622
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MergeOperator.java
@@ -0,0 +1,18 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java
new file mode 100644
index 00000000..1d9ca081
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java
@@ -0,0 +1,469 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 comma, e.g.
+ *
+ * key1=value1;intArrayKey1=1,2,3
+ *
+ * @param str The string representation of the mutable column family options
+ *
+ * @return A builder for the mutable column family options
+ */
+ public static MutableColumnFamilyOptionsBuilder parse(final String str) {
+ Objects.requireNonNull(str);
+
+ final MutableColumnFamilyOptionsBuilder builder =
+ new MutableColumnFamilyOptionsBuilder();
+
+ final String[] options = str.trim().split(KEY_VALUE_PAIR_SEPARATOR);
+ for(final String option : options) {
+ final int equalsOffset = option.indexOf(KEY_VALUE_SEPARATOR);
+ if(equalsOffset <= 0) {
+ throw new IllegalArgumentException(
+ "options string has an invalid key=value pair");
+ }
+
+ final String key = option.substring(0, equalsOffset);
+ if(key.isEmpty()) {
+ throw new IllegalArgumentException("options string is invalid");
+ }
+
+ final String value = option.substring(equalsOffset + 1);
+ if(value.isEmpty()) {
+ throw new IllegalArgumentException("options string is invalid");
+ }
+
+ builder.fromString(key, value);
+ }
+
+ return builder;
+ }
+
+ 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),
+ @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);
+
+ 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),
+ @Deprecated soft_rate_limit(ValueType.DOUBLE),
+ soft_pending_compaction_bytes_limit(ValueType.LONG),
+ @Deprecated hard_rate_limit(ValueType.DOUBLE),
+ 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);
+
+ private final ValueType valueType;
+ CompactionOption(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_type(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<MutableColumnFamilyOptions, MutableColumnFamilyOptionsBuilder, MutableColumnFamilyOptionKey>
+ implements MutableColumnFamilyOptionsInterface<MutableColumnFamilyOptionsBuilder> {
+
+ private final static Map<String, MutableColumnFamilyOptionKey> 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);
+ }
+ }
+
+ private MutableColumnFamilyOptionsBuilder() {
+ super();
+ }
+
+ @Override
+ protected MutableColumnFamilyOptionsBuilder self() {
+ return this;
+ }
+
+ @Override
+ protected Map<String, MutableColumnFamilyOptionKey> 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 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 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_type, compressionType);
+ }
+
+ @Override
+ public CompressionType compressionType() {
+ return (CompressionType)getEnum(MiscOption.compression_type);
+ }
+
+ @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);
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java
new file mode 100644
index 00000000..5f51acb8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T>>
+ extends AdvancedMutableColumnFamilyOptionsInterface<T> {
+ /**
+ * 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.
+ */
+ MutableColumnFamilyOptionsInterface 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.
+ */
+ MutableColumnFamilyOptionsInterface 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 &lt; 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.
+ */
+ MutableColumnFamilyOptionsInterface setLevel0FileNumCompactionTrigger(
+ int level0FileNumCompactionTrigger);
+
+ /**
+ * Number of files to trigger level-0 compaction. A value &lt; 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()
+ */
+ MutableColumnFamilyOptionsInterface 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java
new file mode 100644
index 00000000..aca33b13
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java
@@ -0,0 +1,325 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.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
+ *
+ * @return A builder for the mutable db options
+ */
+ public static MutableDBOptionsBuilder parse(final String str) {
+ Objects.requireNonNull(str);
+
+ final MutableDBOptionsBuilder builder =
+ new MutableDBOptionsBuilder();
+
+ final String[] options = str.trim().split(KEY_VALUE_PAIR_SEPARATOR);
+ for(final String option : options) {
+ final int equalsOffset = option.indexOf(KEY_VALUE_SEPARATOR);
+ if(equalsOffset <= 0) {
+ throw new IllegalArgumentException(
+ "options string has an invalid key=value pair");
+ }
+
+ final String key = option.substring(0, equalsOffset);
+ if(key.isEmpty()) {
+ throw new IllegalArgumentException("options string is invalid");
+ }
+
+ final String value = option.substring(equalsOffset + 1);
+ if(value.isEmpty()) {
+ throw new IllegalArgumentException("options string is invalid");
+ }
+
+ builder.fromString(key, value);
+ }
+
+ return builder;
+ }
+
+ private interface MutableDBOptionKey extends MutableOptionKey {}
+
+ public enum DBOption implements MutableDBOptionKey {
+ max_background_jobs(ValueType.INT),
+ base_background_compactions(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<MutableDBOptions, MutableDBOptionsBuilder, MutableDBOptionKey>
+ implements MutableDBOptionsInterface<MutableDBOptionsBuilder> {
+
+ private final static Map<String, MutableDBOptionKey> 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<String, MutableDBOptionKey> 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 void setBaseBackgroundCompactions(
+ final int baseBackgroundCompactions) {
+ setInt(DBOption.base_background_compactions,
+ baseBackgroundCompactions);
+ }
+
+ @Override
+ public int baseBackgroundCompactions() {
+ return getInt(DBOption.base_background_compactions);
+ }
+
+ @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java
new file mode 100644
index 00000000..a3e23d4b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java
@@ -0,0 +1,443 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+package org.rocksdb;
+
+public interface MutableDBOptionsInterface<T extends MutableDBOptionsInterface<T>> {
+ /**
+ * 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. This option is ignored.
+ *
+ * Suggested number of concurrent background compaction jobs, submitted to
+ * the default LOW priority thread pool.
+ * Default: -1
+ *
+ * @param baseBackgroundCompactions Suggested number of background compaction
+ * jobs
+ *
+ * @deprecated Use {@link #setMaxBackgroundJobs(int)}
+ */
+ @Deprecated
+ void setBaseBackgroundCompactions(int baseBackgroundCompactions);
+
+ /**
+ * NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the
+ * value of max_background_jobs. This option is ignored.
+ *
+ * Suggested number of concurrent background compaction jobs, submitted to
+ * the default LOW priority thread pool.
+ * Default: -1
+ *
+ * @return Suggested number of background compaction jobs
+ */
+ int baseBackgroundCompactions();
+
+ /**
+ * 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();
+
+ /**
+ * <p>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 (i.e. the ones that are causing all the space amplification).
+ * </p>
+ * <p>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] * 2</p>
+ * <p>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.</p>
+ * <p>Default: 0</p>
+ *
+ * @param maxTotalWalSize max total wal size.
+ * @return the instance of the current object.
+ */
+ T setMaxTotalWalSize(long maxTotalWalSize);
+
+ /**
+ * <p>Returns the max total wal 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 (i.e. the ones that are causing all
+ * the space amplification).</p>
+ *
+ * <p>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] * 2
+ * </p>
+ *
+ * @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.
+ * When non-zero, we also force
+ * {@link DBOptionsInterface#newTableReaderForCompactionInputs()} to true.
+ *
+ * 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.
+ * When non-zero, we also force
+ * {@link DBOptionsInterface#newTableReaderForCompactionInputs()} to true.
+ *
+ * Default: 0
+ *
+ * @return The compaction read-ahead size
+ */
+ long compactionReadaheadSize();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java
new file mode 100644
index 00000000..ec1b9ff3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java
@@ -0,0 +1,16 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java
new file mode 100644
index 00000000..8ec63269
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java
@@ -0,0 +1,376 @@
+// 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<T> {
+
+ 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<T>
+ extends MutableOptionValue<T> {
+ protected final T value;
+
+ private MutableOptionValueObject(final T value) {
+ this.value = value;
+ }
+
+ @Override T asObject() {
+ return value;
+ }
+ }
+
+ static MutableOptionValue<String> fromString(final String s) {
+ return new MutableOptionStringValue(s);
+ }
+
+ static MutableOptionValue<Double> fromDouble(final double d) {
+ return new MutableOptionDoubleValue(d);
+ }
+
+ static MutableOptionValue<Long> fromLong(final long d) {
+ return new MutableOptionLongValue(d);
+ }
+
+ static MutableOptionValue<Integer> fromInt(final int i) {
+ return new MutableOptionIntValue(i);
+ }
+
+ static MutableOptionValue<Boolean> fromBoolean(final boolean b) {
+ return new MutableOptionBooleanValue(b);
+ }
+
+ static MutableOptionValue<int[]> fromIntArray(final int[] ix) {
+ return new MutableOptionIntArrayValue(ix);
+ }
+
+ static <N extends Enum<N>> MutableOptionValue<N> fromEnum(final N value) {
+ return new MutableOptionEnumValue<>(value);
+ }
+
+ static class MutableOptionStringValue
+ extends MutableOptionValueObject<String> {
+ 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<Double> {
+ 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<Long> {
+ private final long value;
+
+ MutableOptionLongValue(final long value) {
+ this.value = value;
+ }
+
+ @Override
+ double asDouble() {
+ if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) {
+ throw new NumberFormatException(
+ "long value lies outside the bounds of int");
+ }
+ 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<Integer> {
+ private final int value;
+
+ MutableOptionIntValue(final int value) {
+ this.value = value;
+ }
+
+ @Override
+ double asDouble() {
+ if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) {
+ throw new NumberFormatException("int value lies outside the bounds of int");
+ }
+ 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<Boolean> {
+ 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<int[]> {
+ 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(i);
+ if(i + 1 < value.length) {
+ builder.append(INT_ARRAY_INT_SEPARATOR);
+ }
+ }
+ return builder.toString();
+ }
+ }
+
+ static class MutableOptionEnumValue<T extends Enum<T>>
+ extends MutableOptionValueObject<T> {
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java
new file mode 100644
index 00000000..6acc146f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeLibraryLoader.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeLibraryLoader.java
new file mode 100644
index 00000000..6e37e8cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/NativeLibraryLoader.java
@@ -0,0 +1,125 @@
+// 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 String jniLibraryFileName = Environment.getJniLibraryFileName("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 <i>java.library.path</i>,
+ * 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 {
+ System.loadLibrary(sharedLibraryName);
+ } catch(final UnsatisfiedLinkError ule1) {
+ try {
+ System.loadLibrary(jniLibraryName);
+ } catch(final UnsatisfiedLinkError ule2) {
+ 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 {
+ final File temp;
+ if (tmpDir == null || tmpDir.isEmpty()) {
+ temp = File.createTempFile(tempFilePrefix, tempFileSuffix);
+ } else {
+ temp = new File(tmpDir, jniLibraryFileName);
+ 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();
+ }
+
+ // attempt to copy the library from the Jar file to the temp destination
+ try (final InputStream is = getClass().getClassLoader().
+ getResourceAsStream(jniLibraryFileName)) {
+ if (is == null) {
+ throw new RuntimeException(jniLibraryFileName + " was not found inside JAR.");
+ } else {
+ Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ return temp;
+ }
+
+ /**
+ * Private constructor to disallow instantiation
+ */
+ private NativeLibraryLoader() {
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java
new file mode 100644
index 00000000..6ac0a15a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationType.java
new file mode 100644
index 00000000..7cc9b65c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OperationType.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java
new file mode 100644
index 00000000..5a2e1f3e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java
@@ -0,0 +1,226 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<OptimisticTransactionOptions> {
+
+ /**
+ * 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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java
new file mode 100644
index 00000000..250edf80
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<OptimisticTransactionOptions> {
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Options.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Options.java
new file mode 100644
index 00000000..8a534f50
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Options.java
@@ -0,0 +1,2183 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 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()).
+ *
+ * If {@link #dispose()} function is not called, then it will be GC'd
+ * automatically and native resources will be released as part of the process.
+ */
+public class Options extends RocksObject
+ implements DBOptionsInterface<Options>,
+ MutableDBOptionsInterface<Options>,
+ ColumnFamilyOptionsInterface<Options>,
+ MutableColumnFamilyOptionsInterface<Options> {
+ static {
+ RocksDB.loadLibrary();
+ }
+
+ /**
+ * 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_ = 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_;
+ }
+
+ @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_;
+ }
+
+ /**
+ * <p>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.
+ * </p>
+ *
+ * <p>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.</p>
+ *
+ * @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 optimizeForSmallDb() {
+ optimizeForSmallDb(nativeHandle_);
+ 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<? extends AbstractSlice<?>>
+ compactionFilter) {
+ setCompactionFilterHandle(nativeHandle_, compactionFilter.nativeHandle_);
+ compactionFilter_ = compactionFilter;
+ return this;
+ }
+
+ @Override
+ public AbstractCompactionFilter<? extends AbstractSlice<?>> compactionFilter() {
+ assert (isOwningHandle());
+ return compactionFilter_;
+ }
+
+ @Override
+ public Options setCompactionFilterFactory(final AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>> compactionFilterFactory) {
+ assert (isOwningHandle());
+ setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_);
+ compactionFilterFactory_ = compactionFilterFactory;
+ return this;
+ }
+
+ @Override
+ public AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>> 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<DbPath> 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<DbPath> 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<DbPath> 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 void setBaseBackgroundCompactions(
+ final int baseBackgroundCompactions) {
+ assert(isOwningHandle());
+ setBaseBackgroundCompactions(nativeHandle_, baseBackgroundCompactions);
+ }
+
+ @Override
+ public int baseBackgroundCompactions() {
+ assert(isOwningHandle());
+ return baseBackgroundCompactions(nativeHandle_);
+ }
+
+ @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 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 setNewTableReaderForCompactionInputs(
+ final boolean newTableReaderForCompactionInputs) {
+ assert(isOwningHandle());
+ setNewTableReaderForCompactionInputs(nativeHandle_,
+ newTableReaderForCompactionInputs);
+ return this;
+ }
+
+ @Override
+ public boolean newTableReaderForCompactionInputs() {
+ assert(isOwningHandle());
+ return newTableReaderForCompactionInputs(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 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 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 setPreserveDeletes(final boolean preserveDeletes) {
+ assert(isOwningHandle());
+ setPreserveDeletes(nativeHandle_, preserveDeletes);
+ return this;
+ }
+
+ @Override
+ public boolean preserveDeletes() {
+ assert(isOwningHandle());
+ return preserveDeletes(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 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<CompressionType> 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<CompressionType> compressionPerLevel() {
+ final byte[] byteCompressionTypes =
+ compressionPerLevel(nativeHandle_);
+ final List<CompressionType> 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 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 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_);
+ }
+
+ 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 setBaseBackgroundCompactions(long handle,
+ int baseBackgroundCompactions);
+ private native int baseBackgroundCompactions(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 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 setNewTableReaderForCompactionInputs(final long handle,
+ final boolean newTableReaderForCompactionInputs);
+ private native boolean newTableReaderForCompactionInputs(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 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 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 setPreserveDeletes(final long handle,
+ final boolean preserveDeletes);
+ private native boolean preserveDeletes(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 native void optimizeForSmallDb(final long handle);
+ 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 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 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 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);
+
+ // 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<? extends AbstractSlice<?>> compactionFilter_;
+ private AbstractCompactionFilterFactory<? extends AbstractCompactionFilter<?>>
+ compactionFilterFactory_;
+ private CompactionOptionsUniversal compactionOptionsUniversal_;
+ private CompactionOptionsFIFO compactionOptionsFIFO_;
+ private CompressionOptions bottommostCompressionOptions_;
+ private CompressionOptions compressionOptions_;
+ private Cache rowCache_;
+ private WalFilter walFilter_;
+ private WriteBufferManager writeBufferManager_;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java
new file mode 100644
index 00000000..f153556b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+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,
+ * block_cache, and block_cache_compressed), 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 env {@link org.rocksdb.Env} 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(String dbPath, Env env, DBOptions dbOptions,
+ List<ColumnFamilyDescriptor> cfDescs) throws RocksDBException {
+ loadLatestOptions(dbPath, env, dbOptions, cfDescs, false);
+ }
+
+ /**
+ * @param dbPath the path to the RocksDB.
+ * @param env {@link org.rocksdb.Env} 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.
+ * @param ignoreUnknownOptions this flag can be set to true if you want to
+ * ignore options that are from a newer version of the db, esentially for
+ * forward compatibility.
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ */
+ public static void loadLatestOptions(String dbPath, Env env, DBOptions dbOptions,
+ List<ColumnFamilyDescriptor> cfDescs, boolean ignoreUnknownOptions) throws RocksDBException {
+ loadLatestOptions(
+ dbPath, env.nativeHandle_, dbOptions.nativeHandle_, cfDescs, ignoreUnknownOptions);
+ }
+
+ /**
+ * 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 env {@link org.rocksdb.Env} 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(String optionsFileName, Env env, DBOptions dbOptions,
+ List<ColumnFamilyDescriptor> cfDescs) throws RocksDBException {
+ loadOptionsFromFile(optionsFileName, env, dbOptions, cfDescs, false);
+ }
+
+ /**
+ * @param optionsFileName the RocksDB options file path.
+ * @param env {@link org.rocksdb.Env} 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.
+ * @param ignoreUnknownOptions this flag can be set to true if you want to
+ * ignore options that are from a newer version of the db, esentially for
+ * forward compatibility.
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ */
+ public static void loadOptionsFromFile(String optionsFileName, Env env, DBOptions dbOptions,
+ List<ColumnFamilyDescriptor> cfDescs, boolean ignoreUnknownOptions) throws RocksDBException {
+ loadOptionsFromFile(
+ optionsFileName, env.nativeHandle_, dbOptions.nativeHandle_, cfDescs, ignoreUnknownOptions);
+ }
+
+ /**
+ * 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(String dbPath, long envHandle, long dbOptionsHandle,
+ List<ColumnFamilyDescriptor> cfDescs, boolean ignoreUnknownOptions) throws RocksDBException;
+ private native static void loadOptionsFromFile(String optionsFileName, long envHandle,
+ long dbOptionsHandle, List<ColumnFamilyDescriptor> cfDescs, boolean ignoreUnknownOptions)
+ throws RocksDBException;
+ private native static String getLatestOptionsFileName(String dbPath, long envHandle)
+ throws RocksDBException;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java
new file mode 100644
index 00000000..aed56529
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PlainTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PlainTableConfig.java
new file mode 100644
index 00000000..c0999816
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/PlainTableConfig.java
@@ -0,0 +1,251 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ *
+ * <p>PlainTable is a RocksDB's SST file format optimized for low query
+ * latency on pure-memory or really low-latency media.</p>
+ *
+ * <p>It also support prefix hash feature.</p>
+ */
+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;
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>Otherwise,all the keys need to have the same length
+ * in byte.</p>
+ *
+ * <p>DEFAULT: VARIABLE_LENGTH</p>
+ *
+ * @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_;
+ }
+
+ /**
+ * <p>huge_page_tlb_size: if &le;0, allocate hash indexes and blooms
+ * from malloc otherwise from huge page TLB.</p>
+ *
+ * <p>The user needs to reserve huge pages for it to be allocated,
+ * like: {@code sysctl -w vm.nr_hugepages=20}</p>
+ *
+ * <p>See linux doc Documentation/vm/hugetlbpage.txt</p>
+ *
+ * @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.
+ *
+ * <p>This setting determines how to encode
+ * the keys. See enum {@link EncodingType} for
+ * the choices.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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_;
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Priority.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Priority.java
new file mode 100644
index 00000000..34a56edc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Priority.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Range.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Range.java
new file mode 100644
index 00000000..74c85e5f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Range.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java
new file mode 100644
index 00000000..c2b8a0fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java
@@ -0,0 +1,227 @@
+// 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));
+ }
+
+ /**
+ * <p>This API allows user to dynamically change rate limiter's bytes per second.
+ * REQUIRED: bytes_per_second &gt; 0</p>
+ *
+ * @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_);
+ }
+
+ /**
+ * <p>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 &lt; GetSingleBurstBytes()}.</p>
+ *
+ * @param bytes requested bytes.
+ */
+ public void request(final long bytes) {
+ assert(isOwningHandle());
+ request(nativeHandle_, bytes);
+ }
+
+ /**
+ * <p>Max bytes can be granted in a single burst.</p>
+ *
+ * @return max bytes can be granted in a single burst.
+ */
+ public long getSingleBurstBytes() {
+ assert(isOwningHandle());
+ return getSingleBurstBytes(nativeHandle_);
+ }
+
+ /**
+ * <p>Total bytes that go through rate limiter.</p>
+ *
+ * @return total bytes that go through rate limiter.
+ */
+ public long getTotalBytesThrough() {
+ assert(isOwningHandle());
+ return getTotalBytesThrough(nativeHandle_);
+ }
+
+ /**
+ * <p>Total # of requests that go through rate limiter.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java
new file mode 100644
index 00000000..4b029d81
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value;
+ }
+
+ /**
+ * <p>Get the RateLimiterMode enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java
new file mode 100644
index 00000000..1f151056
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java
@@ -0,0 +1,622 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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_;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * <p>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.</p>
+ * <p>Default: null</p>
+ *
+ * @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
+ *
+ * Not supported in {@code ROCKSDB_LITE} mode!
+ *
+ * @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
+ * Not supported in ROCKSDB_LITE mode!
+ *
+ * @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 (&gt; 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 (&gt; 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 upper bound
+ * @return the reference to the current ReadOptions.
+ */
+ public ReadOptions setIterateLowerBound(final Slice iterateLowerBound) {
+ assert(isOwningHandle());
+ if (iterateLowerBound != null) {
+ // Hold onto a reference so it doesn't get garbage collected out from under us.
+ iterateLowerBoundSlice_ = iterateLowerBound;
+ setIterateLowerBound(nativeHandle_, iterateLowerBoundSlice_.getNativeHandle());
+ }
+ 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 Slice iterateUpperBound) {
+ assert(isOwningHandle());
+ if (iterateUpperBound != null) {
+ // Hold onto a reference so it doesn't get garbage collected out from under us.
+ iterateUpperBoundSlice_ = iterateUpperBound;
+ setIterateUpperBound(nativeHandle_, iterateUpperBoundSlice_.getNativeHandle());
+ }
+ 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;
+ }
+
+ /**
+ * Needed to support differential snapshots. Has 2 effects:
+ * 1) Iterator will skip all internal keys with seqnum &lt; iter_start_seqnum
+ * 2) if this param &gt; 0 iterator will return INTERNAL keys instead of user
+ * keys; e.g. return tombstones as well.
+ *
+ * Default: 0 (don't filter by seqnum, return user keys)
+ *
+ * @param startSeqnum the starting sequence number.
+ *
+ * @return the reference to the current ReadOptions.
+ */
+ public ReadOptions setIterStartSeqnum(final long startSeqnum) {
+ assert(isOwningHandle());
+ setIterStartSeqnum(nativeHandle_, startSeqnum);
+ return this;
+ }
+
+ /**
+ * Returns the starting Sequence Number of any iterator.
+ * See {@link #setIterStartSeqnum(long)}.
+ *
+ * @return the starting sequence number of any iterator.
+ */
+ public long iterStartSeqnum() {
+ assert(isOwningHandle());
+ return iterStartSeqnum(nativeHandle_);
+ }
+
+ // 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 Slice iterateLowerBoundSlice_;
+ private Slice iterateUpperBoundSlice_;
+
+ 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 void setIterStartSeqnum(final long handle, final long seqNum);
+ private native long iterStartSeqnum(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java
new file mode 100644
index 00000000..78f83f6a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java
new file mode 100644
index 00000000..6ee81d85
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Slice> {
+ public RemoveEmptyValueCompactionFilter() {
+ super(createNewRemoveEmptyValueCompactionFilter0());
+ }
+
+ private native static long createNewRemoveEmptyValueCompactionFilter0();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RestoreOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RestoreOptions.java
new file mode 100644
index 00000000..94d93fc7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RestoreOptions.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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
+ * BackupableDBOptions::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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java
new file mode 100644
index 00000000..2709a5d5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java
new file mode 100644
index 00000000..a662f78f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * 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);
+ }
+
+ /**
+ * 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java
new file mode 100644
index 00000000..338324b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java
@@ -0,0 +1,4522 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+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();
+ public static final int NOT_FOUND = -1;
+
+ private enum LibraryState {
+ NOT_LOADED,
+ LOADING,
+ LOADED
+ }
+
+ private static AtomicReference<LibraryState> libraryLoaded
+ = new AtomicReference<>(LibraryState.NOT_LOADED);
+
+ static {
+ RocksDB.loadLibrary();
+ }
+
+ /**
+ * 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 (UnsatisfiedLinkError e) {
+ // since it may be optional, we ignore its loading failure here.
+ }
+ }
+ try {
+ NativeLibraryLoader.getInstance().loadLibrary(tmpDir);
+ } catch (IOException e) {
+ libraryLoaded.set(LibraryState.NOT_LOADED);
+ throw new RuntimeException("Unable to load the RocksDB shared library",
+ e);
+ }
+
+ 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<String> 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 (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 (UnsatisfiedLinkError e) {
+ err = e;
+ }
+ }
+ if (!success) {
+ libraryLoaded.set(LibraryState.NOT_LOADED);
+ throw err;
+ }
+
+ libraryLoaded.set(LibraryState.LOADED);
+ return;
+ }
+
+ while (libraryLoaded.get() == LibraryState.LOADING) {
+ try {
+ Thread.sleep(10);
+ } catch(final InterruptedException e) {
+ //ignore
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * <p>
+ * If opened in read write mode every existing column family name must be
+ * passed within the list to this method.</p>
+ * <p>
+ * If opened in read-only mode only a subset of existing column families must
+ * be passed to this method.</p>
+ * <p>
+ * 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</p>
+ * <p>
+ * ColumnFamily handles are disposed when the RocksDB instance is disposed.
+ * </p>
+ *
+ * @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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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.
+ *
+ * <p>
+ * 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.</p>
+ * <p>
+ * 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.</p>
+ *
+ * @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.
+ * <p>
+ * If opened in read write mode every existing column family name must be
+ * passed within the list to this method.</p>
+ * <p>
+ * If opened in read-only mode only a subset of existing column families must
+ * be passed to this method.</p>
+ * <p>
+ * 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.</p>
+ * <p>
+ * 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.</p>
+ * <p>
+ * ColumnFamily handles are disposed when the RocksDB instance is disposed.
+ * </p>
+ *
+ * @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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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++) {
+ columnFamilyHandles.add(new ColumnFamilyHandle(db, handles[i]));
+ }
+
+ 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.
+ 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 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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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);
+ }
+
+ /**
+ * 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 {
+ // 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));
+ 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 specified
+ * options and db path.
+ *
+ * <p>This open method allows to open RocksDB using a subset of available
+ * column families</p>
+ * <p>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.</p>
+ *
+ * @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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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 = openROnly(options.nativeHandle_, path, cfNames,
+ cfOptionHandles);
+ final RocksDB db = new RocksDB(handles[0]);
+ db.storeOptionsInstance(options);
+
+ for (int i = 1; i < handles.length; i++) {
+ columnFamilyHandles.add(new ColumnFamilyHandle(db, handles[i]));
+ }
+
+ 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 {
+ 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();
+ }
+ }
+ }
+
+ /**
+ * 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&lt;byte[]&gt; List containing the column family names
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ */
+ public static List<byte[]> 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 {
+ return new ColumnFamilyHandle(this, createColumnFamily(nativeHandle_,
+ columnFamilyDescriptor.getName(),
+ columnFamilyDescriptor.getName().length,
+ columnFamilyDescriptor.getOptions().nativeHandle_));
+ }
+
+ /**
+ * 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<ColumnFamilyHandle> createColumnFamilies(
+ final ColumnFamilyOptions columnFamilyOptions,
+ final List<byte[]> columnFamilyNames) throws RocksDBException {
+ final byte[][] cfNames = columnFamilyNames.toArray(
+ new byte[0][]);
+ final long[] cfHandles = createColumnFamilies(nativeHandle_,
+ columnFamilyOptions.nativeHandle_, cfNames);
+ final List<ColumnFamilyHandle> columnFamilyHandles =
+ new ArrayList<>(cfHandles.length);
+ for (int i = 0; i < cfHandles.length; i++) {
+ columnFamilyHandles.add(new ColumnFamilyHandle(this, cfHandles[i]));
+ }
+ 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<ColumnFamilyHandle> createColumnFamilies(
+ final List<ColumnFamilyDescriptor> 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<ColumnFamilyHandle> columnFamilyHandles =
+ new ArrayList<>(cfHandles.length);
+ for (int i = 0; i < cfHandles.length; i++) {
+ columnFamilyHandles.add(new ColumnFamilyHandle(this, cfHandles[i]));
+ }
+ 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<ColumnFamilyHandle> 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);
+ }
+
+ //TODO(AR) what about DestroyColumnFamilyHandle
+
+ /**
+ * 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_);
+ }
+
+ /**
+ * Remove 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.
+ *
+ * @deprecated Use {@link #delete(byte[])}
+ */
+ @Deprecated
+ public void remove(final byte[] key) throws RocksDBException {
+ delete(key);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Remove 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.
+ *
+ * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])}
+ */
+ @Deprecated
+ public void remove(final ColumnFamilyHandle columnFamilyHandle,
+ final byte[] key) throws RocksDBException {
+ delete(columnFamilyHandle, key);
+ }
+
+ /**
+ * 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_);
+ }
+
+ /**
+ * Remove 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.
+ *
+ * @deprecated Use {@link #delete(WriteOptions, byte[])}
+ */
+ @Deprecated
+ public void remove(final WriteOptions writeOpt, final byte[] key)
+ throws RocksDBException {
+ delete(writeOpt, key);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Remove 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.
+ *
+ * @deprecated Use {@link #delete(ColumnFamilyHandle, WriteOptions, byte[])}
+ */
+ @Deprecated
+ public void remove(final ColumnFamilyHandle columnFamilyHandle,
+ final WriteOptions writeOpt, final byte[] key) throws RocksDBException {
+ delete(columnFamilyHandle, writeOpt, key);
+ }
+
+ /**
+ * 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_);
+ }
+
+ /**
+ * Returns a map of keys for which values were found in DB.
+ *
+ * @param keys List of keys for which values need to be retrieved.
+ * @return Map where key of map is the key passed by user and value for map
+ * entry is the corresponding value in DB.
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ *
+ * @deprecated Consider {@link #multiGetAsList(List)} instead.
+ */
+ @Deprecated
+ public Map<byte[], byte[]> multiGet(final List<byte[]> keys)
+ throws RocksDBException {
+ assert(keys.size() != 0);
+
+ final byte[][] keysArray = keys.toArray(new byte[0][]);
+ 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;
+ }
+
+ final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets,
+ keyLengths);
+
+ final Map<byte[], byte[]> keyValueMap =
+ new HashMap<>(computeCapacityHint(values.length));
+ for(int i = 0; i < values.length; i++) {
+ if(values[i] == null) {
+ continue;
+ }
+
+ keyValueMap.put(keys.get(i), values[i]);
+ }
+
+ return keyValueMap;
+ }
+
+ /**
+ * Returns a map of keys for which values were found in DB.
+ * <p>
+ * Note: Every key needs to have a related column family name in
+ * {@code columnFamilyHandleList}.
+ * </p>
+ *
+ * @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 Map where key of map is the key passed by user and value for map
+ * entry is the corresponding value in DB.
+ *
+ * @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 Consider {@link #multiGetAsList(List, List)} instead.
+ */
+ @Deprecated
+ public Map<byte[], byte[]> multiGet(
+ final List<ColumnFamilyHandle> columnFamilyHandleList,
+ final List<byte[]> 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[0][]);
+ 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;
+ }
+
+ final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets,
+ keyLengths, cfHandles);
+
+ final Map<byte[], byte[]> keyValueMap =
+ new HashMap<>(computeCapacityHint(values.length));
+ for(int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ continue;
+ }
+ keyValueMap.put(keys.get(i), values[i]);
+ }
+ return keyValueMap;
+ }
+
+ /**
+ * Returns a map of keys for which values were found in DB.
+ *
+ * @param opt Read options.
+ * @param keys of keys for which values need to be retrieved.
+ * @return Map where key of map is the key passed by user and value for map
+ * entry is the corresponding value in DB.
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ *
+ * @deprecated Consider {@link #multiGetAsList(ReadOptions, List)} instead.
+ */
+ @Deprecated
+ public Map<byte[], byte[]> multiGet(final ReadOptions opt,
+ final List<byte[]> keys) throws RocksDBException {
+ assert(keys.size() != 0);
+
+ final byte[][] keysArray = keys.toArray(new byte[0][]);
+ 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;
+ }
+
+ final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_,
+ keysArray, keyOffsets, keyLengths);
+
+ final Map<byte[], byte[]> keyValueMap =
+ new HashMap<>(computeCapacityHint(values.length));
+ for(int i = 0; i < values.length; i++) {
+ if(values[i] == null) {
+ continue;
+ }
+
+ keyValueMap.put(keys.get(i), values[i]);
+ }
+
+ return keyValueMap;
+ }
+
+ /**
+ * Returns a map of keys for which values were found in DB.
+ * <p>
+ * Note: Every key needs to have a related column family name in
+ * {@code columnFamilyHandleList}.
+ * </p>
+ *
+ * @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 Map where key of map is the key passed by user and value for map
+ * entry is the corresponding value in DB.
+ *
+ * @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 Consider {@link #multiGetAsList(ReadOptions, List, List)}
+ * instead.
+ */
+ @Deprecated
+ public Map<byte[], byte[]> multiGet(final ReadOptions opt,
+ final List<ColumnFamilyHandle> columnFamilyHandleList,
+ final List<byte[]> 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[0][]);
+ 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;
+ }
+
+ final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_,
+ keysArray, keyOffsets, keyLengths, cfHandles);
+
+ final Map<byte[], byte[]> keyValueMap
+ = new HashMap<>(computeCapacityHint(values.length));
+ for(int i = 0; i < values.length; i++) {
+ if(values[i] == null) {
+ continue;
+ }
+ keyValueMap.put(keys.get(i), values[i]);
+ }
+
+ return keyValueMap;
+ }
+
+ /**
+ * 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<byte[]> multiGetAsList(final List<byte[]> 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.
+ * <p>
+ * Note: Every key needs to have a related column family name in
+ * {@code columnFamilyHandleList}.
+ * </p>
+ *
+ * @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<byte[]> multiGetAsList(
+ final List<ColumnFamilyHandle> columnFamilyHandleList,
+ final List<byte[]> 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<byte[]> multiGetAsList(final ReadOptions opt,
+ final List<byte[]> 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.
+ * <p>
+ * Note: Every key needs to have a related column family name in
+ * {@code columnFamilyHandleList}.
+ * </p>
+ *
+ * @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<byte[]> multiGetAsList(final ReadOptions opt,
+ final List<ColumnFamilyHandle> columnFamilyHandleList,
+ final List<byte[]> 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));
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(key, 0, key.length, valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist((ColumnFamilyHandle)null, key, offset, len, valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(columnFamilyHandle, key, 0, key.length,
+ valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(columnFamilyHandle, null, key, offset, len,
+ valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(readOptions, key, 0, key.length,
+ valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(null, readOptions,
+ key, offset, len, valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> valueHolder) {
+ return keyMayExist(columnFamilyHandle, readOptions,
+ key, 0, key.length, valueHolder);
+ }
+
+ /**
+ * If the key definitely does not exist in the database, then this method
+ * returns null, else it returns an instance of KeyMayExistResult
+ *
+ * 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<byte[]> 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;
+ }
+ }
+ }
+
+ /**
+ * <p>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).</p>
+ *
+ * <p>Caller should close the iterator when it is no longer needed.
+ * The returned iterator should be closed before this db is closed.
+ * </p>
+ *
+ * @return instance of iterator object.
+ */
+ public RocksIterator newIterator() {
+ return new RocksIterator(this, iterator(nativeHandle_));
+ }
+
+ /**
+ * <p>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).</p>
+ *
+ * <p>Caller should close the iterator when it is no longer needed.
+ * The returned iterator should be closed before this db is closed.
+ * </p>
+ *
+ * @param readOptions {@link ReadOptions} instance.
+ * @return instance of iterator object.
+ */
+ public RocksIterator newIterator(final ReadOptions readOptions) {
+ return new RocksIterator(this, iterator(nativeHandle_,
+ readOptions.nativeHandle_));
+ }
+
+ /**
+ * <p>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).</p>
+ *
+ * <p>Caller should close the iterator when it is no longer needed.
+ * The returned iterator should be closed before this db is closed.
+ * </p>
+ *
+ * @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_));
+ }
+
+ /**
+ * <p>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).</p>
+ *
+ * <p>Caller should close the iterator when it is no longer needed.
+ * The returned iterator should be closed before this db is closed.
+ * </p>
+ *
+ * @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<RocksIterator> newIterators(
+ final List<ColumnFamilyHandle> 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<RocksIterator> newIterators(
+ final List<ColumnFamilyHandle> 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<RocksIterator> iterators = new ArrayList<>(
+ columnFamilyHandleList.size());
+ for (int i=0; i<columnFamilyHandleList.size(); i++){
+ iterators.add(new RocksIterator(this, iteratorRefs[i]));
+ }
+ return iterators;
+ }
+
+
+ /**
+ * <p>Return 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.</p>
+ *
+ * <p>nullptr will be returned if the DB fails to take a snapshot or does
+ * not support snapshot.</p>
+ *
+ * @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>Valid property names include:
+ * <ul>
+ * <li>"rocksdb.num-files-at-level&lt;N&gt;" - return the number of files at
+ * level &lt;N&gt;, where &lt;N&gt; is an ASCII representation of a level
+ * number (e.g. "0").</li>
+ * <li>"rocksdb.stats" - returns a multi-line string that describes statistics
+ * about the internal operation of the DB.</li>
+ * <li>"rocksdb.sstables" - returns a multi-line string that describes all
+ * of the sstables that make up the db contents.</li>
+ * </ul>
+ *
+ * @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.
+ *
+ * <p>Valid property names include:
+ * <ul>
+ * <li>"rocksdb.num-files-at-level&lt;N&gt;" - return the number of files at
+ * level &lt;N&gt;, where &lt;N&gt; is an ASCII representation of a level
+ * number (e.g. "0").</li>
+ * <li>"rocksdb.stats" - returns a multi-line string that describes statistics
+ * about the internal operation of the DB.</li>
+ * <li>"rocksdb.sstables" - returns a multi-line string that describes all
+ * of the sstables that make up the db contents.</li>
+ *</ul>
+ *
+ * @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<String, String> 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<String, String> getMapProperty(
+ /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle,
+ final String property) throws RocksDBException {
+ return getMapProperty(nativeHandle_,
+ columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_,
+ property, property.length());
+ }
+
+ /**
+ * <p> Similar to GetProperty(), but only works for a subset of properties
+ * whose return value is a numerical value. Return the value as long.</p>
+ *
+ * <p><strong>Note</strong>: 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.</p>
+ *
+ * <p><strong>Java 7</strong>: 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.</p>
+ *
+ * <p><strong>Java 8</strong>: In Java 8 the value should be treated as
+ * unsigned long using provided methods of type {@link Long}.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * <p> Similar to GetProperty(), but only works for a subset of properties
+ * whose return value is a numerical value. Return the value as long.</p>
+ *
+ * <p><strong>Note</strong>: 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.</p>
+ *
+ * <p><strong>Java 7</strong>: 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.</p>
+ *
+ * <p><strong>Java 8</strong>: In Java 8 the value should be treated as
+ * unsigned long using provided methods of type {@link Long}.</p>
+ *
+ * @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_);
+ }
+
+ /**
+ * <p> Return sum of the getLongProperty of all the column families</p>
+ *
+ * <p><strong>Note</strong>: 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.</p>
+ *
+ * <p><strong>Java 7</strong>: 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.</p>
+ *
+ * <p><strong>Java 8</strong>: In Java 8 the value should be treated as
+ * unsigned long using provided methods of type {@link Long}.</p>
+ *
+ * @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<Range> 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<Range> 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);
+ }
+
+ /**
+ * <p>Range compaction of database.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange(boolean, int, int)}</li>
+ * <li>{@link #compactRange(byte[], byte[])}</li>
+ * <li>{@link #compactRange(byte[], byte[], boolean, int, int)}</li>
+ * </ul>
+ *
+ * @throws RocksDBException thrown if an error occurs within the native
+ * part of the library.
+ */
+ public void compactRange() throws RocksDBException {
+ compactRange(null);
+ }
+
+ /**
+ * <p>Range compaction of column family.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)}
+ * </li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])}
+ * </li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[],
+ * boolean, int, int)}
+ * </li>
+ * </ul>
+ *
+ * @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_);
+ }
+
+ /**
+ * <p>Range compaction of database.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange()}</li>
+ * <li>{@link #compactRange(boolean, int, int)}</li>
+ * <li>{@link #compactRange(byte[], byte[], boolean, int, int)}</li>
+ * </ul>
+ *
+ * @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);
+ }
+
+ /**
+ * <p>Range compaction of column family.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange(ColumnFamilyHandle)}</li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)}
+ * </li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[],
+ * boolean, int, int)}
+ * </li>
+ * </ul>
+ *
+ * @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_);
+ }
+
+ /**
+ * <p>Range compaction of database.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p>Compaction outputs should be placed in options.db_paths
+ * [target_path_id]. Behavior is undefined if target_path_id is
+ * out of range.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange()}</li>
+ * <li>{@link #compactRange(byte[], byte[])}</li>
+ * <li>{@link #compactRange(byte[], byte[], boolean, int, int)}</li>
+ * </ul>
+ *
+ * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead
+ *
+ * @param changeLevel reduce level after compaction
+ * @param targetLevel target level to compact to
+ * @param targetPathId the target path id of output path
+ *
+ * @throws RocksDBException thrown if an error occurs within the native
+ * part of the library.
+ */
+ @Deprecated
+ public void compactRange(final boolean changeLevel, final int targetLevel,
+ final int targetPathId) throws RocksDBException {
+ compactRange(null, changeLevel, targetLevel, targetPathId);
+ }
+
+ /**
+ * <p>Range compaction of column family.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p>Compaction outputs should be placed in options.db_paths
+ * [target_path_id]. Behavior is undefined if target_path_id is
+ * out of range.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange(ColumnFamilyHandle)}</li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])}
+ * </li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[],
+ * boolean, int, int)}
+ * </li>
+ * </ul>
+ *
+ * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead
+ *
+ * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle}
+ * instance, or null for the default column family.
+ * @param changeLevel reduce level after compaction
+ * @param targetLevel target level to compact to
+ * @param targetPathId the target path id of output path
+ *
+ * @throws RocksDBException thrown if an error occurs within the native
+ * part of the library.
+ */
+ @Deprecated
+ public void compactRange(
+ /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle,
+ final boolean changeLevel, final int targetLevel, final int targetPathId)
+ throws RocksDBException {
+ final CompactRangeOptions options = new CompactRangeOptions();
+ options.setChangeLevel(changeLevel);
+ options.setTargetLevel(targetLevel);
+ options.setTargetPathId(targetPathId);
+ compactRange(nativeHandle_,
+ null, -1,
+ null, -1,
+ options.nativeHandle_,
+ columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_);
+ }
+
+ /**
+ * <p>Range compaction of database.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p>Compaction outputs should be placed in options.db_paths
+ * [target_path_id]. Behavior is undefined if target_path_id is
+ * out of range.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange()}</li>
+ * <li>{@link #compactRange(boolean, int, int)}</li>
+ * <li>{@link #compactRange(byte[], byte[])}</li>
+ * </ul>
+ *
+ * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)}
+ * instead
+ *
+ * @param begin start of key range (included in range)
+ * @param end end of key range (excluded from range)
+ * @param changeLevel reduce level after compaction
+ * @param targetLevel target level to compact to
+ * @param targetPathId the target path id of output path
+ *
+ * @throws RocksDBException thrown if an error occurs within the native
+ * part of the library.
+ */
+ @Deprecated
+ public void compactRange(final byte[] begin, final byte[] end,
+ final boolean changeLevel, final int targetLevel,
+ final int targetPathId) throws RocksDBException {
+ compactRange(null, begin, end, changeLevel, targetLevel, targetPathId);
+ }
+
+ /**
+ * <p>Range compaction of column family.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * <p>Compaction outputs should be placed in options.db_paths
+ * [target_path_id]. Behavior is undefined if target_path_id is
+ * out of range.</p>
+ *
+ * <p><strong>See also</strong></p>
+ * <ul>
+ * <li>{@link #compactRange(ColumnFamilyHandle)}</li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)}
+ * </li>
+ * <li>
+ * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])}
+ * </li>
+ * </ul>
+ *
+ * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead
+ *
+ * @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 changeLevel reduce level after compaction
+ * @param targetLevel target level to compact to
+ * @param targetPathId the target path id of output path
+ *
+ * @throws RocksDBException thrown if an error occurs within the native
+ * part of the library.
+ */
+ @Deprecated
+ public void compactRange(
+ /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle,
+ final byte[] begin, final byte[] end, final boolean changeLevel,
+ final int targetLevel, final int targetPathId)
+ throws RocksDBException {
+ final CompactRangeOptions options = new CompactRangeOptions();
+ options.setChangeLevel(changeLevel);
+ options.setTargetLevel(targetLevel);
+ options.setTargetPathId(targetPathId);
+ compactRange(nativeHandle_,
+ begin, begin == null ? -1 : begin.length,
+ end, end == null ? -1 : end.length,
+ options.nativeHandle_,
+ columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_);
+ }
+
+ /**
+ * <p>Range compaction of column family.</p>
+ * <p><strong>Note</strong>: After the database has been compacted,
+ * all data will have been pushed down to the last level containing
+ * any data.</p>
+ *
+ * @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.nativeHandle_,
+ mutableColumnFamilyOptions.getKeys(),
+ mutableColumnFamilyOptions.getValues());
+ }
+
+ /**
+ * 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<String> compactFiles(
+ final CompactionOptions compactionOptions,
+ final List<String> 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<String> compactFiles(
+ final CompactionOptions compactionOptions,
+ /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle,
+ final List<String> 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 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<ColumnFamilyHandle> 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;
+ }
+ }
+
+ /**
+ * <p>Flush all memory table data.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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<ColumnFamilyHandle>) null);
+ }
+
+ /**
+ * <p>Flush all memory table data.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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<ColumnFamilyHandle> 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_);
+ }
+
+ /**
+ * <p>The sequence number of the most recent transaction.</p>
+ *
+ * @return sequence number of the most
+ * recent transaction.
+ */
+ public long getLatestSequenceNumber() {
+ return getLatestSequenceNumber(nativeHandle_);
+ }
+
+ /**
+ * Instructs DB to preserve deletes with sequence numbers &gt;= sequenceNumber.
+ *
+ * Has no effect if DBOptions#preserveDeletes() is set to false.
+ *
+ * This function assumes that user calls this function with monotonically
+ * increasing seqnums (otherwise we can't guarantee that a particular delete
+ * hasn't been already processed).
+ *
+ * @param sequenceNumber the minimum sequence number to preserve
+ *
+ * @return true if the value was successfully updated,
+ * false if user attempted to call if with
+ * sequenceNumber &lt;= current value.
+ */
+ public boolean setPreserveDeletesSequenceNumber(final long sequenceNumber) {
+ return setPreserveDeletesSequenceNumber(nativeHandle_, sequenceNumber);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @throws RocksDBException thrown if operation was not performed
+ * successfully.
+ */
+ public void disableFileDeletions() throws RocksDBException {
+ disableFileDeletions(nativeHandle_);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>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()</p>
+ *
+ * @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<String> files;
+
+ LiveFiles(final long manifestFileSize, final List<String> 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<LogFile> getSortedWalFiles() throws RocksDBException {
+ final LogFile[] logFiles = getSortedWalFiles(nativeHandle_);
+ return Arrays.asList(logFiles);
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @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<LiveFileMetaData> 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<String> 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<String> 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<String, TableProperties> 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<String, TableProperties> 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<String, TableProperties> getPropertiesOfTablesInRange(
+ /* @Nullable */final ColumnFamilyHandle columnFamilyHandle,
+ final List<Range> 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<String, TableProperties> getPropertiesOfTablesInRange(
+ final List<Range> 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_);
+ }
+
+ /**
+ * 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<byte[]> 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<? extends RocksObject> 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<Range> 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) 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
+ ) 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;
+ //TODO(AR) best way to express DestroyColumnFamilyHandle? ...maybe in ColumnFamilyHandle?
+ 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 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<String, String> 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 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 final 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 void setDBOptions(final long handle,
+ final String[] keys, final String[] values) throws RocksDBException;
+ 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 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 boolean setPreserveDeletesSequenceNumber(final long handle,
+ final long sequenceNumber);
+ 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<String, TableProperties> getPropertiesOfAllTables(
+ final long handle, final long columnFamilyHandle) throws RocksDBException;
+ private native Map<String, TableProperties> 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 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;
+
+ protected DBOptionsInterface options_;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDBException.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDBException.java
new file mode 100644
index 00000000..8b035f45
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksDBException.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java
new file mode 100644
index 00000000..b3681d77
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * <p>A RocksEnv is an interface used by the rocksdb implementation to access
+ * operating system functionality like the filesystem etc.</p>
+ *
+ * <p>All Env implementations are safe for concurrent access from
+ * multiple threads without any external synchronization.</p>
+ */
+public class RocksEnv extends Env {
+
+ /**
+ * <p>Package-private constructor that uses the specified native handle
+ * to construct a RocksEnv.</p>
+ *
+ * <p>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.</p>
+ */
+ RocksEnv(final long handle) {
+ super(handle);
+ }
+
+ @Override
+ protected native final void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java
new file mode 100644
index 00000000..94611cd7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @see org.rocksdb.RocksObject
+ */
+public class RocksIterator extends AbstractRocksIterator<RocksDB> {
+ protected RocksIterator(RocksDB rocksDB, long nativeHandle) {
+ super(rocksDB, nativeHandle);
+ }
+
+ /**
+ * <p>Return the key for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @return key for the current entry.
+ */
+ public byte[] key() {
+ assert(isOwningHandle());
+ return key0(nativeHandle_);
+ }
+
+ /**
+ * <p>Return the key for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @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(ByteBuffer key) {
+ assert (isOwningHandle() && key.isDirect());
+ int result = keyDirect0(nativeHandle_, key, key.position(), key.remaining());
+ key.limit(Math.min(key.position() + result, key.limit()));
+ return result;
+ }
+
+ /**
+ * <p>Return the value for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: !AtEnd() &amp;&amp; !AtStart()</p>
+ * @return value for the current entry.
+ */
+ public byte[] value() {
+ assert(isOwningHandle());
+ return value0(nativeHandle_);
+ }
+
+ /**
+ * <p>Return the value for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @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(ByteBuffer value) {
+ assert (isOwningHandle() && value.isDirect());
+ int result = valueDirect0(nativeHandle_, value, 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 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 seekForPrevDirect0(
+ long handle, ByteBuffer 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 valueDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java
new file mode 100644
index 00000000..ddd2593c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @see org.rocksdb.RocksObject
+ */
+public interface RocksIteratorInterface {
+
+ /**
+ * <p>An iterator is either positioned at an entry, or
+ * not valid. This method returns true if the iterator is valid.</p>
+ *
+ * @return true if iterator is valid.
+ */
+ boolean isValid();
+
+ /**
+ * <p>Position at the first entry in the source. The iterator is Valid()
+ * after this call if the source is not empty.</p>
+ */
+ void seekToFirst();
+
+ /**
+ * <p>Position at the last entry in the source. The iterator is
+ * valid after this call if the source is not empty.</p>
+ */
+ void seekToLast();
+
+ /**
+ * <p>Position at the first entry in the source whose key is at or
+ * past target.</p>
+ *
+ * <p>The iterator is valid after this call if the source contains
+ * a key that comes at or past target.</p>
+ *
+ * @param target byte array describing a key or a
+ * key prefix to seek for.
+ */
+ void seek(byte[] target);
+
+ /**
+ * <p>Position at the first entry in the source whose key is that or
+ * before target.</p>
+ *
+ * <p>The iterator is valid after this call if the source contains
+ * a key that comes at or before target.</p>
+ *
+ * @param target byte array describing a key or a
+ * key prefix to seek for.
+ */
+ void seekForPrev(byte[] target);
+
+ /**
+ * <p>Position at the first entry in the source whose key is that or
+ * past target.</p>
+ *
+ * <p>The iterator is valid after this call if the source contains
+ * a key that comes at or past target.</p>
+ *
+ * @param target byte array describing a key or a
+ * key prefix to seek for. Supports direct buffer only.
+ */
+ void seek(ByteBuffer target);
+
+ /**
+ * <p>Position at the last key that is less than or equal to the target key.</p>
+ *
+ * <p>The iterator is valid after this call if the source contains
+ * a key that comes at or past target.</p>
+ *
+ * @param target byte array describing a key or a
+ * key prefix to seek for. Supports direct buffer only.
+ */
+ void seekForPrev(ByteBuffer target);
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ */
+ void next();
+
+ /**
+ * <p>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.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ */
+ void prev();
+
+ /**
+ * <p>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().</p>
+ *
+ * @throws RocksDBException thrown if error happens in underlying
+ * native library.
+ */
+ void status() throws RocksDBException;
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java
new file mode 100644
index 00000000..0afa5f66
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+
+ /**
+ * <p>Creates a new environment that stores its data
+ * in memory and delegates all non-file-storage tasks to
+ * {@code baseEnv}.</p>
+ *
+ * <p>The caller must delete the result when it is
+ * no longer needed.</p>
+ *
+ * @param baseEnv the base environment,
+ * must remain live while the result is in use.
+ */
+ public RocksMemEnv(final Env baseEnv) {
+ super(createMemEnv(baseEnv.nativeHandle_));
+ }
+
+ /**
+ * @deprecated Use {@link #RocksMemEnv(Env)}.
+ */
+ @Deprecated
+ public RocksMemEnv() {
+ this(Env.getDefault());
+ }
+
+ private static native long createMemEnv(final long baseEnvHandle);
+ @Override protected final native void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMutableObject.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMutableObject.java
new file mode 100644
index 00000000..e92289dc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksMutableObject.java
@@ -0,0 +1,87 @@
+// 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.
+ *
+ * <p>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.</p>
+ */
+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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksObject.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksObject.java
new file mode 100644
index 00000000..545dd896
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/RocksObject.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ * <p>
+ * RocksObject is the base-class of almost all RocksDB classes that have a
+ * pointer to some underlying native C++ {@code rocksdb} object.</p>
+ * <p>
+ * The use of {@code RocksObject} should always be preferred over
+ * {@link RocksMutableObject}.</p>
+ */
+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);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java
new file mode 100644
index 00000000..fe3c2dd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java
@@ -0,0 +1,31 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java
new file mode 100644
index 00000000..e2c1b97d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java
@@ -0,0 +1,51 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Slice.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Slice.java
new file mode 100644
index 00000000..50d9f765
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Slice.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * <p>Base class for slices which will receive
+ * byte[] based access to the underlying data.</p>
+ *
+ * <p>byte[] backed slices typically perform better with
+ * small keys and values. When using larger keys and
+ * values consider using {@link org.rocksdb.DirectSlice}</p>
+ */
+public class Slice extends AbstractSlice<byte[]> {
+
+ /**
+ * Indicates whether we have to free the memory pointed to by the Slice
+ */
+ private volatile boolean cleared;
+ private volatile long internalBufferOffset = 0;
+
+ /**
+ * <p>Called from JNI to construct a new Java Slice
+ * without an underlying C++ object set
+ * at creation time.</p>
+ *
+ * <p>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.</p>
+ */
+ @SuppressWarnings("unused")
+ private Slice() {
+ super();
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @param nativeHandle address of native instance.
+ */
+ Slice(final long nativeHandle) {
+ this(nativeHandle, false);
+ }
+
+ /**
+ * <p>Package-private Slice constructor which is used to construct
+ * Slice instances using a handle. </p>
+ *
+ * @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);
+ }
+
+ /**
+ * <p>Constructs a slice where the data is taken from
+ * a String.</p>
+ *
+ * @param str String value.
+ */
+ public Slice(final String str) {
+ super(createNewSliceFromString(str));
+ }
+
+ /**
+ * <p>Constructs a slice where the data is a copy of
+ * the byte array from a specific offset.</p>
+ *
+ * @param data byte array.
+ * @param offset offset within the byte array.
+ */
+ public Slice(final byte[] data, final int offset) {
+ super(createNewSlice0(data, offset));
+ }
+
+ /**
+ * <p>Constructs a slice where the data is a copy of
+ * the byte array.</p>
+ *
+ * @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;
+ }
+
+ /**
+ * <p>Deletes underlying C++ slice pointer
+ * and any buffered data.</p>
+ *
+ * <p>
+ * Note that this function should be called only after all
+ * RocksDB instances referencing the slice are closed.
+ * Otherwise an undefined behavior will occur.</p>
+ */
+ @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java
new file mode 100644
index 00000000..39cdf0c2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java
new file mode 100644
index 00000000..8805410a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java
@@ -0,0 +1,251 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<String, Long> 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<String, Long> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java
new file mode 100644
index 00000000..a04d05cb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReader.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReader.java
new file mode 100644
index 00000000..bb1e94ee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReader.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReaderIterator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReaderIterator.java
new file mode 100644
index 00000000..8ba39ba0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileReaderIterator.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * @see RocksObject
+ */
+public class SstFileReaderIterator extends AbstractRocksIterator<SstFileReader> {
+ protected SstFileReaderIterator(SstFileReader reader, long nativeHandle) {
+ super(reader, nativeHandle);
+ }
+
+ /**
+ * <p>Return the key for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @return key for the current entry.
+ */
+ public byte[] key() {
+ assert (isOwningHandle());
+ return key0(nativeHandle_);
+ }
+
+ /**
+ * <p>Return the key for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @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(ByteBuffer key) {
+ assert (isOwningHandle() && key.isDirect());
+ int result = keyDirect0(nativeHandle_, key, key.position(), key.remaining());
+ key.limit(Math.min(key.position() + result, key.limit()));
+ return result;
+ }
+
+ /**
+ * <p>Return the value for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: !AtEnd() &amp;&amp; !AtStart()</p>
+ * @return value for the current entry.
+ */
+ public byte[] value() {
+ assert (isOwningHandle());
+ return value0(nativeHandle_);
+ }
+
+ /**
+ * <p>Return the value for the current entry. The underlying storage for
+ * the returned slice is valid only until the next modification of
+ * the iterator.</p>
+ *
+ * <p>REQUIRES: {@link #isValid()}</p>
+ *
+ * @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(ByteBuffer value) {
+ assert (isOwningHandle() && value.isDirect());
+ int result = valueDirect0(nativeHandle_, value, 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 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;
+
+ 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 valueDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen);
+
+ @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);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java
new file mode 100644
index 00000000..6d9c559b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java
@@ -0,0 +1,290 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ * @param comparator the comparator to specify the ordering of keys.
+ *
+ * @deprecated Use {@link #SstFileWriter(EnvOptions, Options)}.
+ * Passing an explicit comparator is deprecated in lieu of passing the
+ * comparator as part of options. Use the other constructor instead.
+ */
+ @Deprecated
+ public SstFileWriter(final EnvOptions envOptions, final Options options,
+ final AbstractComparator comparator) {
+ super(newSstFileWriter(
+ envOptions.nativeHandle_, options.nativeHandle_, comparator.nativeHandle_,
+ comparator.getComparatorType().getValue()));
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use {@link #put(Slice, Slice)}
+ */
+ @Deprecated
+ public void add(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.
+ *
+ * @deprecated Use {@link #put(DirectSlice, DirectSlice)}
+ */
+ @Deprecated
+ public void add(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 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.
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StateType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StateType.java
new file mode 100644
index 00000000..803456bb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StateType.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Statistics.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Statistics.java
new file mode 100644
index 00000000..0938a6d5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Statistics.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<HistogramType> ignoreHistograms) {
+ super(newStatistics(toArrayValues(ignoreHistograms)));
+ }
+
+ public Statistics(final EnumSet<HistogramType> 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<HistogramType> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java
new file mode 100644
index 00000000..fb3f5715
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>Helper class to collect DB statistics periodically at a period specified in
+ * constructor. Callback function (provided in constructor) is called with
+ * every statistics collection.</p>
+ *
+ * <p>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.</p>
+ */
+public class StatisticsCollector {
+ private final List<StatsCollectorInput> _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<StatsCollectorInput> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java
new file mode 100644
index 00000000..f3785b15
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsCollectorInput.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsCollectorInput.java
new file mode 100644
index 00000000..5bf43ade
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsCollectorInput.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java
new file mode 100644
index 00000000..58504b84
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Status.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Status.java
new file mode 100644
index 00000000..e633940c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Status.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+package org.rocksdb;
+
+/**
+ * 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;
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java
new file mode 100644
index 00000000..ae525d4d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java
@@ -0,0 +1,24 @@
+// 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));
+ }
+
+ private native static long newSharedStringAppendOperator(final char delim);
+ @Override protected final native void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java
new file mode 100644
index 00000000..a39a329f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java
@@ -0,0 +1,21 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFormatConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFormatConfig.java
new file mode 100644
index 00000000..dbe524c4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableFormatConfig.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+ /**
+ * <p>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.</p>
+ *
+ * @return native handle address to native table instance.
+ */
+ abstract protected long newTableFactoryHandle();
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java
new file mode 100644
index 00000000..8c0b7e37
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java
@@ -0,0 +1,366 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+package org.rocksdb;
+
+import java.util.Map;
+
+/**
+ * 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 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<String, String> userCollectedProperties;
+ private final Map<String, String> readableProperties;
+ private final Map<String, Long> propertiesOffsets;
+
+ /**
+ * Access is private as this will only be constructed from
+ * C++ via JNI.
+ */
+ private 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 byte[] columnFamilyName,
+ final String filterPolicyName, final String comparatorName,
+ final String mergeOperatorName, final String prefixExtractorName,
+ final String propertyCollectorsNames, final String compressionName,
+ final Map<String, String> userCollectedProperties,
+ final Map<String, String> readableProperties,
+ final Map<String, Long> propertiesOffsets) {
+ 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.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;
+ this.propertiesOffsets = propertiesOffsets;
+ }
+
+ /**
+ * 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 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<String, String> getUserCollectedProperties() {
+ return userCollectedProperties;
+ }
+
+ /**
+ * Get the readable properties.
+ *
+ * @return the readable properties.
+ */
+ public Map<String, String> getReadableProperties() {
+ return readableProperties;
+ }
+
+ /**
+ * The offset of the value of each property in the file.
+ *
+ * @return the offset of each property.
+ */
+ public Map<String, Long> getPropertiesOffsets() {
+ return propertiesOffsets;
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java
new file mode 100644
index 00000000..062df588
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<String, Long> 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 Map<String, Long>interpretOperationProperties(
+ final byte operationTypeValue, final long[] operationProperties);
+ private static native String getStateName(final byte stateTypeValue);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java
new file mode 100644
index 00000000..cc329f44
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TickerType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TickerType.java
new file mode 100644
index 00000000..67873351
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TickerType.java
@@ -0,0 +1,760 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 bytes of index block erased from cache
+ */
+ BLOCK_CACHE_INDEX_BYTES_EVICT((byte) 0x8),
+
+ /**
+ * # 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 bytes of bloom filter block erased from cache
+ */
+ BLOCK_CACHE_FILTER_BYTES_EVICT((byte) 0xD),
+
+ /**
+ * # 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_CLOSES((byte) 0x2F),
+
+ NO_FILE_OPENS((byte) 0x30),
+
+ NO_FILE_ERRORS((byte) 0x31),
+
+ /**
+ * Time system had to wait to do LO-L1 compactions.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ STALL_L0_SLOWDOWN_MICROS((byte) 0x32),
+
+ /**
+ * Time system had to wait to move memtable to L1.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ STALL_MEMTABLE_COMPACTION_MICROS((byte) 0x33),
+
+ /**
+ * write throttle because of too many files in L0.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ STALL_L0_NUM_FILES_MICROS((byte) 0x34),
+
+ /**
+ * 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),
+
+ RATE_LIMIT_DELAY_MILLIS((byte) 0x37),
+
+ /**
+ * Number of iterators created.
+ *
+ */
+ NO_ITERATORS((byte) 0x38),
+
+ /**
+ * 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 of deletes records that were not required to be
+ * written to storage because key does not exist.
+ */
+ NUMBER_FILTERED_DELETES((byte) 0x3C),
+ 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),
+
+ /**
+ * Miss in the compressed block cache.
+ */
+ BLOCK_CACHE_COMPRESSED_MISS((byte) 0x42),
+
+ /**
+ * Hit in the compressed block cache.
+ */
+ BLOCK_CACHE_COMPRESSED_HIT((byte) 0x43),
+
+ /**
+ * Number of blocks added to compressed block cache.
+ */
+ BLOCK_CACHE_COMPRESSED_ADD((byte) 0x44),
+
+ /**
+ * Number of failures when adding blocks to compressed block cache.
+ */
+ BLOCK_CACHE_COMPRESSED_ADD_FAILURES((byte) 0x45),
+
+ /**
+ * 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 writes ending up with timed-out.
+ */
+ WRITE_TIMEDOUT((byte) 0x4A),
+
+ /**
+ * 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 drop by BlobDB garbage collection because they had been
+ * overwritten.
+ */
+ BLOB_DB_GC_NUM_KEYS_OVERWRITTEN((byte) 0x7E),
+
+ /**
+ * # of keys drop by BlobDB garbage collection because of expiration.
+ */
+ BLOB_DB_GC_NUM_KEYS_EXPIRED((byte) 0x7F),
+
+ /**
+ * # of keys relocated to new blob file by garbage collection.
+ */
+ BLOB_DB_GC_NUM_KEYS_RELOCATED((byte) -0x02),
+
+ /**
+ * # of bytes drop by BlobDB garbage collection because they had been
+ * overwritten.
+ */
+ BLOB_DB_GC_BYTES_OVERWRITTEN((byte) -0x03),
+
+ /**
+ * # of bytes drop by BlobDB garbage collection because of expiration.
+ */
+ BLOB_DB_GC_BYTES_EXPIRED((byte) -0x04),
+
+ /**
+ * # 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),
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java
new file mode 100644
index 00000000..dc8b5d6e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+
+ /**
+ * <p>Creates a new environment that measures function call times for
+ * filesystem operations, reporting results to variables in PerfContext.</p>
+ *
+ *
+ * <p>The caller must delete the result when it is
+ * no longer needed.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java
new file mode 100644
index 00000000..657b263c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 = 64 * 1024 * 1024 * 1024; // 64 GB
+ }
+
+ public TraceOptions(final long maxTraceFileSize) {
+ this.maxTraceFileSize = maxTraceFileSize;
+ }
+
+ /**
+ * To avoid the trace file size grows large than the storage space,
+ * user can set the max trace file size in Bytes. Default is 64GB
+ *
+ * @return the max trace size
+ */
+ public long getMaxTraceFileSize() {
+ return maxTraceFileSize;
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java
new file mode 100644
index 00000000..cb0234e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Transaction.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Transaction.java
new file mode 100644
index 00000000..d59be4c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/Transaction.java
@@ -0,0 +1,2012 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * 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-&gt;setSnapshot();
+ * txn2-&gt;put("A", ...);
+ * txn2-&gt;commit();
+ * txn1-&gt;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-&gt;setSnapshot();
+ * txn2-&gt;put("A", ...);
+ * txn2-&gt;commit();
+ * txn1-&gt;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
+ */
+ 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#multiGet(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 byte[][] multiGet(final ReadOptions readOptions,
+ final List<ColumnFamilyHandle> 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#multiGet(ReadOptions, 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.=
+ * {@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 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);
+ }
+
+ /**
+ * 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.
+ */
+ public byte[][] multiGetForUpdate(final ReadOptions readOptions,
+ final List<ColumnFamilyHandle> 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, 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.
+ */
+ 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);
+ }
+
+ /**
+ * 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 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 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 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
+ */
+ @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 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
+ */
+ @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),
+ COMMITED((byte)4),
+ AWAITING_ROLLBACK((byte)5),
+ ROLLEDBACK((byte)6),
+ LOCKS_STOLEN((byte)7);
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java
new file mode 100644
index 00000000..7a625997
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java
@@ -0,0 +1,404 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Database with Transaction support
+ */
+public class TransactionDB extends RocksDB
+ implements TransactionalDB<TransactionOptions> {
+
+ 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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> 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<Transaction> getAllPreparedTransactions() {
+ final long[] jtxnHandles = getAllPreparedTransactions(nativeHandle_);
+
+ final List<Transaction> 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<Long, KeyLockInfo> 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<Long, KeyLockInfo> getLockStatusData(
+ final long handle);
+ private native DeadlockPath[] getDeadlockInfoBuffer(final long handle);
+ private native void setDeadlockInfoBufferSize(final long handle,
+ final int targetSize);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java
new file mode 100644
index 00000000..7f4296a7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java
@@ -0,0 +1,217 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionLogIterator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionLogIterator.java
new file mode 100644
index 00000000..5d9ec58d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionLogIterator.java
@@ -0,0 +1,112 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+package org.rocksdb;
+
+/**
+ * <p>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.</p>
+ */
+public class TransactionLogIterator extends RocksObject {
+
+ /**
+ * <p>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.</p>
+ *
+ * @return true if iterator position is valid.
+ */
+ public boolean isValid() {
+ return isValid(nativeHandle_);
+ }
+
+ /**
+ * <p>Moves the iterator to the next WriteBatch.
+ * <strong>REQUIRES</strong>: Valid() to be true.</p>
+ */
+ public void next() {
+ next(nativeHandle_);
+ }
+
+ /**
+ * <p>Throws RocksDBException if something went wrong.</p>
+ *
+ * @throws org.rocksdb.RocksDBException if something went
+ * wrong in the underlying C++ code.
+ */
+ public void status() throws RocksDBException {
+ status(nativeHandle_);
+ }
+
+ /**
+ * <p>If iterator position is valid, return the current
+ * write_batch and the sequence number of the earliest
+ * transaction contained in the batch.</p>
+ *
+ * <p>ONLY use if Valid() is true and status() is OK.</p>
+ *
+ * @return {@link org.rocksdb.TransactionLogIterator.BatchResult}
+ * instance.
+ */
+ public BatchResult getBatch() {
+ assert(isValid());
+ return getBatch(nativeHandle_);
+ }
+
+ /**
+ * <p>TransactionLogIterator constructor.</p>
+ *
+ * @param nativeHandle address to native address.
+ */
+ TransactionLogIterator(final long nativeHandle) {
+ super(nativeHandle);
+ }
+
+ /**
+ * <p>BatchResult represents a data structure returned
+ * by a TransactionLogIterator containing a sequence
+ * number and a {@link WriteBatch} instance.</p>
+ */
+ public static final class BatchResult {
+ /**
+ * <p>Constructor of BatchResult class.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * <p>Return sequence number related to this BatchResult.</p>
+ *
+ * @return Sequence number.
+ */
+ public long sequenceNumber() {
+ return sequenceNumber_;
+ }
+
+ /**
+ * <p>Return contained {@link org.rocksdb.WriteBatch}
+ * instance</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java
new file mode 100644
index 00000000..195fc85e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<TransactionOptions> {
+
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java
new file mode 100644
index 00000000..3f0eceda
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T extends TransactionalOptions>
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java
new file mode 100644
index 00000000..d55ee900
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<T extends TransactionalOptions<T>>
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java
new file mode 100644
index 00000000..c1e3bb47
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java
@@ -0,0 +1,245 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.
+ *
+ * <p><strong>Use case</strong></p>
+ * <p>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 &gt;= 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.
+ * </p>
+ *
+ * <p><strong>Behaviour</strong></p>
+ * <p>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&lt;time_now)
+ * Get/Iterator may return expired entries(compaction not run on them yet)
+ * Different TTL may be used during different Opens
+ * </p>
+ *
+ * <p><strong>Example</strong></p>
+ * <ul>
+ * <li>Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2</li>
+ * <li>Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t&gt;=5</li>
+ * </ul>
+ *
+ * <p>
+ * read_only=true opens in the usual read-only mode. Compactions will not be
+ * triggered(neither manual nor automatic), so no expired entries removed
+ * </p>
+ *
+ * <p><strong>Constraints</strong></p>
+ * <p>Not specifying/passing or non-positive TTL behaves
+ * like TTL = infinity</p>
+ *
+ * <p><strong>!!!WARNING!!!</strong></p>
+ * <p>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.</p>
+ */
+public class TtlDB extends RocksDB {
+
+ /**
+ * <p>Opens a TtlDB.</p>
+ *
+ * <p>Database is opened in read-write mode without default TTL.</p>
+ *
+ * @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);
+ }
+
+ /**
+ * <p>Opens a TtlDB.</p>
+ *
+ * @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));
+ }
+
+ /**
+ * <p>Opens a TtlDB.</p>
+ *
+ * @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<ColumnFamilyDescriptor> columnFamilyDescriptors,
+ final List<ColumnFamilyHandle> columnFamilyHandles,
+ final List<Integer> 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;
+ }
+
+ /**
+ * <p>Close the TtlDB instance and release resource.</p>
+ *
+ * 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();
+ }
+ }
+ }
+
+ /**
+ * <p>Close the TtlDB instance and release resource.</p>
+ *
+ *
+ * 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();
+ }
+ }
+ }
+
+ /**
+ * <p>Creates a new ttl based column family with a name defined
+ * in given ColumnFamilyDescriptor and allocates a
+ * ColumnFamilyHandle within an internal structure.</p>
+ *
+ * <p>The ColumnFamilyHandle is automatically disposed with DB
+ * disposal.</p>
+ *
+ * @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));
+ }
+
+ /**
+ * <p>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)}.
+ * </p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java
new file mode 100644
index 00000000..837ce615
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value;
+ }
+
+ /**
+ * <p>Get the TxnDBWritePolicy enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java
new file mode 100644
index 00000000..cce9b298
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/VectorMemTableConfig.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/VectorMemTableConfig.java
new file mode 100644
index 00000000..fb1e7a94
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/VectorMemTableConfig.java
@@ -0,0 +1,46 @@
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java
new file mode 100644
index 00000000..d8b9eece
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>Returns the byte value of the enumerations value.</p>
+ *
+ * @return byte representation
+ */
+ public byte getValue() {
+ return value;
+ }
+
+ /**
+ * <p>Get the WALRecoveryMode enumeration value by
+ * passing the byte identifier to this method.</p>
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java
new file mode 100644
index 00000000..60922ae4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<WriteBatchWithIndex> {
+ 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 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);
+
+ 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();
+ }
+ }
+
+ @Override
+ void seekForPrevDirect0(long handle, ByteBuffer target, int targetOffset, int targetLen) {
+ throw new IllegalAccessError("Not implemented");
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java
new file mode 100644
index 00000000..fed27ed1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java
new file mode 100644
index 00000000..37e36213
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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-&gt;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&gt;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<Integer, Long> cfLognumber,
+ final Map<String, Integer> 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java
new file mode 100644
index 00000000..889602ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java
new file mode 100644
index 00000000..01dbe5a5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java
@@ -0,0 +1,394 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @param nativeHandle address of native instance.
+ */
+ WriteBatch(final long nativeHandle) {
+ this(nativeHandle, false);
+ }
+
+ /**
+ * <p>Private WriteBatch constructor which is used to construct
+ * WriteBatch instances. </p>
+ *
+ * @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 removeDirect(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;
+
+ /**
+ * 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java
new file mode 100644
index 00000000..1f1ddc4a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java
@@ -0,0 +1,305 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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;
+
+/**
+ * <p>Defines the interface for a Write Batch which
+ * holds a collection of updates to apply atomically to a DB.</p>
+ */
+public interface WriteBatchInterface {
+
+ /**
+ * Returns the number of updates in the batch.
+ *
+ * @return number of items in WriteBatch
+ */
+ int count();
+
+ /**
+ * <p>Store the mapping "key-&gt;value" in the database.</p>
+ *
+ * @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;
+
+ /**
+ * <p>Store the mapping "key-&gt;value" within given column
+ * family.</p>
+ *
+ * @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;
+
+ /**
+ * <p>Store the mapping "key-&gt;value" within given column
+ * family.</p>
+ *
+ * @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
+ */
+ void put(ByteBuffer key, ByteBuffer value) throws RocksDBException;
+
+ /**
+ * <p>Store the mapping "key-&gt;value" within given column
+ * family.</p>
+ *
+ * @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
+ */
+ void put(ColumnFamilyHandle columnFamilyHandle, ByteBuffer key, ByteBuffer value)
+ throws RocksDBException;
+
+ /**
+ * <p>Merge "value" with the existing value of "key" in the database.
+ * "key-&gt;merge(existing, value)"</p>
+ *
+ * @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;
+
+ /**
+ * <p>Merge "value" with the existing value of "key" in given column family.
+ * "key-&gt;merge(existing, value)"</p>
+ *
+ * @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;
+
+ /**
+ * <p>If the database contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @param key Key to delete within database
+ *
+ * @deprecated Use {@link #delete(byte[])}
+ * @throws RocksDBException thrown if error happens in underlying native library.
+ */
+ @Deprecated
+ void remove(byte[] key) throws RocksDBException;
+
+ /**
+ * <p>If column family contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
+ * @param key Key to delete within database
+ *
+ * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])}
+ * @throws RocksDBException thrown if error happens in underlying native library.
+ */
+ @Deprecated
+ void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key)
+ throws RocksDBException;
+
+ /**
+ * <p>If the database contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @param key Key to delete within database
+ * @throws RocksDBException thrown if error happens in underlying native library.
+ */
+ void delete(byte[] key) throws RocksDBException;
+
+ /**
+ * <p>If column family contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @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;
+
+ /**
+ * 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;
+
+ /**
+ * <p>If column family contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @param key Key to delete within database. It is using position and limit.
+ * Supports direct buffer only.
+ * @throws RocksDBException
+ */
+ void remove(ByteBuffer key) throws RocksDBException;
+
+ /**
+ * <p>If column family contains a mapping for "key", erase it. Else do nothing.</p>
+ *
+ * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
+ * @param key Key to delete within database. It is using position and limit.
+ * Supports direct buffer only.
+ * @throws RocksDBException
+ */
+ void remove(ColumnFamilyHandle columnFamilyHandle, ByteBuffer 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java
new file mode 100644
index 00000000..3831f85b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java
@@ -0,0 +1,318 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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));
+ }
+
+ /**
+ * <p>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.</p>
+ *
+ * @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 recommand 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) {
+ RocksIterator iterator = new RocksIterator(baseIterator.parent_,
+ iteratorWithBase(
+ nativeHandle_, columnFamilyHandle.nativeHandle_, baseIterator.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);
+ }
+
+ /**
+ * 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(long, long)} 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(long, long)} 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 removeDirect(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 baseIteratorHandle, final long cfHandle);
+ 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java
new file mode 100644
index 00000000..b244aa95
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 <a href="https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager">
+ * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager</a>
+ * 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
+ */
+ public WriteBufferManager(final long bufferSizeBytes, final Cache cache){
+ super(newWriteBufferManager(bufferSizeBytes, cache.nativeHandle_));
+ }
+
+ private native static long newWriteBufferManager(final long bufferSizeBytes, final long cacheHandle);
+ @Override
+ protected native void disposeInternal(final long handle);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java
new file mode 100644
index 00000000..71789ed1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java
@@ -0,0 +1,219 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 this 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_);
+ }
+
+ 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);
+}
diff --git a/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ByteUtil.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ByteUtil.java
new file mode 100644
index 00000000..9014fcba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ByteUtil.java
@@ -0,0 +1,46 @@
+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 <a href="https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c">memcmp.c</a>.
+ *
+ * @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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/BytewiseComparator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/BytewiseComparator.java
new file mode 100644
index 00000000..9561b0a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/BytewiseComparator.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java
new file mode 100644
index 00000000..b5de34b7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java
@@ -0,0 +1,152 @@
+// 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 boolean MUSL_LIBC;
+
+ static {
+ try {
+ final Process p = new ProcessBuilder("/usr/bin/env", "sh", "-c", "ldd /usr/bin/env | grep -q musl").start();
+ MUSL_LIBC = p.waitFor() == 0;
+ } catch (final IOException | InterruptedException e) {
+ MUSL_LIBC = false;
+ }
+ }
+
+ 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");
+ }
+
+ public static boolean isMuslLibc() {
+ return MUSL_LIBC;
+ }
+
+ 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()) {
+ 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 String getJniLibraryFileName(final String name) {
+ return appendLibOsSuffix("lib" + getJniLibraryName(name), 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/IntComparator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/IntComparator.java
new file mode 100644
index 00000000..cc096cd1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/IntComparator.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 &lt; 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java
new file mode 100644
index 00000000..4c06f80a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/SizeUnit.java b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/SizeUnit.java
new file mode 100644
index 00000000..0f717e8d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/main/java/org/rocksdb/util/SizeUnit.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java
new file mode 100644
index 00000000..7cac3015
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java
@@ -0,0 +1,902 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyHandle> 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 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 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<ColumnFamilyHandle> 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);
+ }
+ }
+
+ @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<ColumnFamilyHandle> 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 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 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<Snapshot> createdSnapshots = new ArrayList<>();
+
+ @Override
+ public void snapshotCreated(final Snapshot newSnapshot) {
+ createdSnapshots.add(newSnapshot);
+ }
+
+ public List<Snapshot> getCreatedSnapshots() {
+ return createdSnapshots;
+ }
+ }
+
+ protected static abstract class DBContainer
+ implements AutoCloseable {
+ protected final WriteOptions writeOptions;
+ protected final List<ColumnFamilyHandle> columnFamilyHandles;
+ protected final ColumnFamilyOptions columnFamilyOptions;
+ protected final DBOptions options;
+
+ public DBContainer(final WriteOptions writeOptions,
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java
new file mode 100644
index 00000000..de8017a9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java
@@ -0,0 +1,261 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 BackupableDBOptions bopt = new BackupableDBOptions(
+ 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 BackupableDBOptions bopt = new BackupableDBOptions(
+ backupFolder.getRoot().getAbsolutePath());
+ final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
+ be.createNewBackup(db, false);
+ be.createNewBackup(db, true);
+ final List<BackupInfo> backupInfo =
+ verifyNumberOfValidBackups(be, 2);
+ // Delete the first backup
+ be.deleteBackup(backupInfo.get(0).backupId());
+ final List<BackupInfo> 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 BackupableDBOptions bopt = new BackupableDBOptions(
+ 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> backupInfo =
+ verifyNumberOfValidBackups(be, 4);
+ // Delete everything except the latest backup
+ be.purgeOldBackups(1);
+ final List<BackupInfo> 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 BackupableDBOptions bopt = new BackupableDBOptions(
+ 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 BackupableDBOptions bopt = new BackupableDBOptions(
+ 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> 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 BackupableDBOptions bopt =
+ new BackupableDBOptions(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<BackupInfo> 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<BackupInfo> verifyNumberOfValidBackups(final BackupEngine be,
+ final int expectedNumberOfBackups) throws RocksDBException {
+ // Verify that backups exist
+ assertThat(be.getCorruptedBackups().length).
+ isEqualTo(0);
+ be.garbageCollect();
+ final List<BackupInfo> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java
new file mode 100644
index 00000000..a216ba57
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java
@@ -0,0 +1,351 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 BackupableDBOptionsTest {
+
+ 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 BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ assertThat(backupableDBOptions.backupDir()).
+ isEqualTo(ARBITRARY_PATH);
+ }
+ }
+
+ @Test
+ public void env() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ assertThat(backupableDBOptions.backupEnv()).
+ isNull();
+
+ try(final Env env = new RocksMemEnv(Env.getDefault())) {
+ backupableDBOptions.setBackupEnv(env);
+ assertThat(backupableDBOptions.backupEnv())
+ .isEqualTo(env);
+ }
+ }
+ }
+
+ @Test
+ public void shareTableFiles() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final boolean value = rand.nextBoolean();
+ backupableDBOptions.setShareTableFiles(value);
+ assertThat(backupableDBOptions.shareTableFiles()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void infoLog() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ assertThat(backupableDBOptions.infoLog()).
+ isNull();
+
+ try(final Options options = new Options();
+ final Logger logger = new Logger(options){
+ @Override
+ protected void log(InfoLogLevel infoLogLevel, String logMsg) {
+
+ }
+ }) {
+ backupableDBOptions.setInfoLog(logger);
+ assertThat(backupableDBOptions.infoLog())
+ .isEqualTo(logger);
+ }
+ }
+ }
+
+ @Test
+ public void sync() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final boolean value = rand.nextBoolean();
+ backupableDBOptions.setSync(value);
+ assertThat(backupableDBOptions.sync()).isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void destroyOldData() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH);) {
+ final boolean value = rand.nextBoolean();
+ backupableDBOptions.setDestroyOldData(value);
+ assertThat(backupableDBOptions.destroyOldData()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void backupLogFiles() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final boolean value = rand.nextBoolean();
+ backupableDBOptions.setBackupLogFiles(value);
+ assertThat(backupableDBOptions.backupLogFiles()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void backupRateLimit() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final long value = Math.abs(rand.nextLong());
+ backupableDBOptions.setBackupRateLimit(value);
+ assertThat(backupableDBOptions.backupRateLimit()).
+ isEqualTo(value);
+ // negative will be mapped to 0
+ backupableDBOptions.setBackupRateLimit(-1);
+ assertThat(backupableDBOptions.backupRateLimit()).
+ isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void backupRateLimiter() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ assertThat(backupableDBOptions.backupEnv()).
+ isNull();
+
+ try(final RateLimiter backupRateLimiter =
+ new RateLimiter(999)) {
+ backupableDBOptions.setBackupRateLimiter(backupRateLimiter);
+ assertThat(backupableDBOptions.backupRateLimiter())
+ .isEqualTo(backupRateLimiter);
+ }
+ }
+ }
+
+ @Test
+ public void restoreRateLimit() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final long value = Math.abs(rand.nextLong());
+ backupableDBOptions.setRestoreRateLimit(value);
+ assertThat(backupableDBOptions.restoreRateLimit()).
+ isEqualTo(value);
+ // negative will be mapped to 0
+ backupableDBOptions.setRestoreRateLimit(-1);
+ assertThat(backupableDBOptions.restoreRateLimit()).
+ isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void restoreRateLimiter() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ assertThat(backupableDBOptions.backupEnv()).
+ isNull();
+
+ try(final RateLimiter restoreRateLimiter =
+ new RateLimiter(911)) {
+ backupableDBOptions.setRestoreRateLimiter(restoreRateLimiter);
+ assertThat(backupableDBOptions.restoreRateLimiter())
+ .isEqualTo(restoreRateLimiter);
+ }
+ }
+ }
+
+ @Test
+ public void shareFilesWithChecksum() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ boolean value = rand.nextBoolean();
+ backupableDBOptions.setShareFilesWithChecksum(value);
+ assertThat(backupableDBOptions.shareFilesWithChecksum()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void maxBackgroundOperations() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final int value = rand.nextInt();
+ backupableDBOptions.setMaxBackgroundOperations(value);
+ assertThat(backupableDBOptions.maxBackgroundOperations()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void callbackTriggerIntervalSize() {
+ try (final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH)) {
+ final long value = rand.nextLong();
+ backupableDBOptions.setCallbackTriggerIntervalSize(value);
+ assertThat(backupableDBOptions.callbackTriggerIntervalSize()).
+ isEqualTo(value);
+ }
+ }
+
+ @Test
+ public void failBackupDirIsNull() {
+ exception.expect(IllegalArgumentException.class);
+ try (final BackupableDBOptions opts = new BackupableDBOptions(null)) {
+ //no-op
+ }
+ }
+
+ @Test
+ public void failBackupDirIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.backupDir();
+ }
+ }
+
+ @Test
+ public void failSetShareTableFilesIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setShareTableFiles(true);
+ }
+ }
+
+ @Test
+ public void failShareTableFilesIfDisposed() {
+ try (BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.shareTableFiles();
+ }
+ }
+
+ @Test
+ public void failSetSyncIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setSync(true);
+ }
+ }
+
+ @Test
+ public void failSyncIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.sync();
+ }
+ }
+
+ @Test
+ public void failSetDestroyOldDataIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setDestroyOldData(true);
+ }
+ }
+
+ @Test
+ public void failDestroyOldDataIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.destroyOldData();
+ }
+ }
+
+ @Test
+ public void failSetBackupLogFilesIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setBackupLogFiles(true);
+ }
+ }
+
+ @Test
+ public void failBackupLogFilesIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.backupLogFiles();
+ }
+ }
+
+ @Test
+ public void failSetBackupRateLimitIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setBackupRateLimit(1);
+ }
+ }
+
+ @Test
+ public void failBackupRateLimitIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.backupRateLimit();
+ }
+ }
+
+ @Test
+ public void failSetRestoreRateLimitIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setRestoreRateLimit(1);
+ }
+ }
+
+ @Test
+ public void failRestoreRateLimitIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.restoreRateLimit();
+ }
+ }
+
+ @Test
+ public void failSetShareFilesWithChecksumIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.setShareFilesWithChecksum(true);
+ }
+ }
+
+ @Test
+ public void failShareFilesWithChecksumIfDisposed() {
+ try (final BackupableDBOptions options =
+ setupUninitializedBackupableDBOptions(exception)) {
+ options.shareFilesWithChecksum();
+ }
+ }
+
+ private BackupableDBOptions setupUninitializedBackupableDBOptions(
+ ExpectedException exception) {
+ final BackupableDBOptions backupableDBOptions =
+ new BackupableDBOptions(ARBITRARY_PATH);
+ backupableDBOptions.close();
+ exception.expect(AssertionError.class);
+ return backupableDBOptions;
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java
new file mode 100644
index 00000000..6fdd314c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java
@@ -0,0 +1,393 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.Ignore;
+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 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();
+ blockBasedTableConfig.setCacheIndexAndFilterBlocksWithHighPriority(true);
+ assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocksWithHighPriority()).
+ isTrue();
+ }
+
+ @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(3);
+ blockBasedTableConfig.setIndexType(IndexType.kHashSearch);
+ assertThat(blockBasedTableConfig.indexType().equals(
+ IndexType.kHashSearch));
+ assertThat(IndexType.valueOf("kBinarySearch")).isNotNull();
+ blockBasedTableConfig.setIndexType(IndexType.valueOf("kBinarySearch"));
+ assertThat(blockBasedTableConfig.indexType().equals(
+ IndexType.kBinarySearch));
+ }
+
+ @Test
+ public void dataBlockIndexType() {
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
+ blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash);
+ assertThat(blockBasedTableConfig.dataBlockIndexType().equals(
+ DataBlockIndexType.kDataBlockBinaryAndHash));
+ blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch);
+ assertThat(blockBasedTableConfig.dataBlockIndexType().equals(
+ DataBlockIndexType.kDataBlockBinarySearch));
+ }
+
+ @Test
+ public void checksumType() {
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
+ assertThat(ChecksumType.values().length).isEqualTo(3);
+ assertThat(ChecksumType.valueOf("kxxHash")).
+ isEqualTo(ChecksumType.kxxHash);
+ blockBasedTableConfig.setChecksumType(ChecksumType.kNoChecksum);
+ blockBasedTableConfig.setChecksumType(ChecksumType.kxxHash);
+ assertThat(blockBasedTableConfig.checksumType().equals(
+ ChecksumType.kxxHash));
+ }
+
+ @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 blockCacheCompressed() {
+ try (final Cache cache = new LRUCache(17 * 1024 * 1024);
+ final Options options = new Options().setTableFormatConfig(
+ new BlockBasedTableConfig().setBlockCacheCompressed(cache))) {
+ assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable");
+ }
+ }
+
+ @Ignore("See issue: https://github.com/facebook/rocksdb/issues/4822")
+ @Test
+ public void blockCacheCompressedIntegration() throws RocksDBException {
+ final byte[] key1 = "some-key1".getBytes(StandardCharsets.UTF_8);
+ final byte[] key2 = "some-key1".getBytes(StandardCharsets.UTF_8);
+ final byte[] key3 = "some-key1".getBytes(StandardCharsets.UTF_8);
+ final byte[] key4 = "some-key1".getBytes(StandardCharsets.UTF_8);
+ final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8);
+
+ try (final Cache compressedCache = new LRUCache(8 * 1024 * 1024);
+ final Statistics statistics = new Statistics()) {
+
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig()
+ .setNoBlockCache(true)
+ .setBlockCache(null)
+ .setBlockCacheCompressed(compressedCache)
+ .setFormatVersion(4);
+
+ try (final Options options = new Options()
+ .setCreateIfMissing(true)
+ .setStatistics(statistics)
+ .setTableFormatConfig(blockBasedTableConfig)) {
+
+ for (int shard = 0; shard < 8; shard++) {
+ try (final FlushOptions flushOptions = new FlushOptions();
+ final WriteOptions writeOptions = new WriteOptions();
+ final ReadOptions readOptions = new ReadOptions();
+ final RocksDB db =
+ RocksDB.open(options, dbFolder.getRoot().getAbsolutePath() + "/" + shard)) {
+
+ db.put(writeOptions, key1, value);
+ db.put(writeOptions, key2, value);
+ db.put(writeOptions, key3, value);
+ db.put(writeOptions, key4, value);
+ db.flush(flushOptions);
+
+ db.get(readOptions, key1);
+ db.get(readOptions, key2);
+ db.get(readOptions, key3);
+ db.get(readOptions, key4);
+
+ assertThat(statistics.getTickerCount(TickerType.BLOCK_CACHE_COMPRESSED_ADD)).isEqualTo(shard + 1);
+ }
+ }
+ }
+ }
+ }
+
+ @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 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();
+ 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 = AssertionError.class)
+ public void formatVersionFailIllegalVersion() {
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
+ blockBasedTableConfig.setFormatVersion(99);
+ }
+
+ @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();
+ }
+
+ @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);
+ }
+
+ @Deprecated
+ @Test
+ public void blockCacheCompressedSize() {
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
+ blockBasedTableConfig.setBlockCacheCompressedSize(40);
+ assertThat(blockBasedTableConfig.blockCacheCompressedSize()).
+ isEqualTo(40);
+ }
+
+ @Deprecated
+ @Test
+ public void blockCacheCompressedNumShardBits() {
+ final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig();
+ blockBasedTableConfig.setBlockCacheCompressedNumShardBits(4);
+ assertThat(blockBasedTableConfig.blockCacheCompressedNumShardBits()).
+ isEqualTo(4);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java
new file mode 100644
index 00000000..e238ae07
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CheckPointTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CheckPointTest.java
new file mode 100644
index 00000000..c2cc6fc6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CheckPointTest.java
@@ -0,0 +1,83 @@
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ClockCacheTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ClockCacheTest.java
new file mode 100644
index 00000000..d1241ac7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ClockCacheTest.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java
new file mode 100644
index 00000000..af67f466
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java
@@ -0,0 +1,625 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Random;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+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 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 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<CompressionType> 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<CompressionType> 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 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);
+ }
+ }
+
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java
new file mode 100644
index 00000000..cc8199ec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java
@@ -0,0 +1,734 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.*;
+
+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;
+import static org.assertj.core.api.Assertions.assertThat;
+
+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<byte[]> 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);
+
+ 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<byte[]> 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<ColumnFamilyDescriptor> cfNames = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes())
+ );
+
+ final List<ColumnFamilyHandle> 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)) {
+
+ try {
+ 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();
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void getWithOutValueAndCf() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
+ final List<ColumnFamilyHandle> 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)) {
+ try {
+ 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());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void createWriteDropColumnFamily() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> 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;
+ try {
+ tmpColumnFamilyHandle = db.createColumnFamily(
+ new ColumnFamilyDescriptor("tmpCF".getBytes(),
+ new ColumnFamilyOptions()));
+ db.put(tmpColumnFamilyHandle, "key".getBytes(), "value".getBytes());
+ db.dropColumnFamily(tmpColumnFamilyHandle);
+ assertThat(tmpColumnFamilyHandle.isOwningHandle()).isTrue();
+ } finally {
+ if (tmpColumnFamilyHandle != null) {
+ tmpColumnFamilyHandle.close();
+ }
+ for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void createWriteDropColumnFamilies() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> 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;
+ try {
+ 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();
+ } finally {
+ if (tmpColumnFamilyHandle != null) {
+ tmpColumnFamilyHandle.close();
+ }
+ if (tmpColumnFamilyHandle2 != null) {
+ tmpColumnFamilyHandle2.close();
+ }
+ for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void writeBatch() throws RocksDBException {
+ try (final StringAppendOperator stringAppendOperator = new StringAppendOperator();
+ final ColumnFamilyOptions defaultCfOptions = new ColumnFamilyOptions()
+ .setMergeOperator(stringAppendOperator)) {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY,
+ defaultCfOptions),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> 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()) {
+ try {
+ 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");
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void iteratorOnColumnFamily() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+
+ 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<String, String> 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);
+ }
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void multiGet() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ db.put(columnFamilyHandleList.get(0), "key".getBytes(),
+ "value".getBytes());
+ db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(),
+ "value".getBytes());
+
+ final List<byte[]> keys = Arrays.asList(new byte[][]{
+ "key".getBytes(), "newcfkey".getBytes()
+ });
+
+ List<byte[]> 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");
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void multiGetAsList() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ db.put(columnFamilyHandleList.get(0), "key".getBytes(),
+ "value".getBytes());
+ db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(),
+ "value".getBytes());
+
+ final List<byte[]> keys = Arrays.asList(new byte[][]{
+ "key".getBytes(), "newcfkey".getBytes()
+ });
+ List<byte[]> 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");
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void properties() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ 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);
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+
+ @Test
+ public void iterators() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> 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<RocksIterator> iterators = null;
+ try {
+ iterators = db.newIterators(columnFamilyHandleList);
+ assertThat(iterators.size()).isEqualTo(2);
+ RocksIterator iter = iterators.get(0);
+ iter.seekToFirst();
+ final Map<String, String> 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<String, String> 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();
+ }
+ }
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test(expected = RocksDBException.class)
+ public void failPutDisposedCF() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ db.dropColumnFamily(columnFamilyHandleList.get(1));
+ db.put(columnFamilyHandleList.get(1), "key".getBytes(),
+ "value".getBytes());
+ } finally {
+ for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test(expected = RocksDBException.class)
+ public void failRemoveDisposedCF() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ db.dropColumnFamily(columnFamilyHandleList.get(1));
+ db.delete(columnFamilyHandleList.get(1), "key".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test(expected = RocksDBException.class)
+ public void failGetDisposedCF() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ columnFamilyHandleList)) {
+ try {
+ db.dropColumnFamily(columnFamilyHandleList.get(1));
+ db.get(columnFamilyHandleList.get(1), "key".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test(expected = RocksDBException.class)
+ public void failMultiGetWithoutCorrectNumberOfCF() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes()));
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ columnFamilyHandleList)) {
+ try {
+ final List<byte[]> keys = new ArrayList<>();
+ keys.add("key".getBytes());
+ keys.add("newcfkey".getBytes());
+ final List<ColumnFamilyHandle> cfCustomList = new ArrayList<>();
+ db.multiGetAsList(cfCustomList, keys);
+
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @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};
+ ColumnFamilyHandle cf1 = null, cf2 = null, cf3 = null;
+ try {
+ cf1 = db.createColumnFamily(new ColumnFamilyDescriptor(b0));
+ cf2 = db.createColumnFamily(new ColumnFamilyDescriptor(b1));
+ final List<byte[]> families = RocksDB.listColumnFamilies(options,
+ dbFolder.getRoot().getAbsolutePath());
+ assertThat(families).contains("default".getBytes(), b0, b1);
+ cf3 = db.createColumnFamily(new ColumnFamilyDescriptor(b2));
+ } finally {
+ if (cf1 != null) {
+ cf1.close();
+ }
+ if (cf2 != null) {
+ cf2.close();
+ }
+ if (cf3 != null) {
+ cf3.close();
+ }
+ }
+ }
+ }
+
+ @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());
+ ) {
+ try {
+ 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<byte[]> families = RocksDB.listColumnFamilies(options,
+ dbFolder.getRoot().getAbsolutePath());
+ assertThat(families).contains("default".getBytes(), b0, b1);
+ } finally {
+ if (cf1 != null) {
+ cf1.close();
+ }
+ if (cf2 != null) {
+ cf2.close();
+ }
+ }
+ }
+ }
+
+ @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());
+ ) {
+ try {
+ final String simplifiedChinese = "\u7b80\u4f53\u5b57";
+ columnFamilyHandle = db.createColumnFamily(
+ new ColumnFamilyDescriptor(simplifiedChinese.getBytes()));
+
+ final List<byte[]> families = RocksDB.listColumnFamilies(options,
+ dbFolder.getRoot().getAbsolutePath());
+ assertThat(families).contains("default".getBytes(),
+ simplifiedChinese.getBytes());
+ } finally {
+ if (columnFamilyHandle != null) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java
new file mode 100644
index 00000000..18c187dd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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);
+ }
+
+ @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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java
new file mode 100644
index 00000000..e05f1eef
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyDescriptor> cfNames = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts));
+
+ final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
+
+ try (final RocksDB rocksDb = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(), cfNames, cfHandles);
+ ) {
+ try {
+ 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();
+ } finally {
+ for (final ColumnFamilyHandle cfHandle : cfHandles) {
+ cfHandle.close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java
new file mode 100644
index 00000000..c71b0da1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java
new file mode 100644
index 00000000..5c1eb2aa
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java
@@ -0,0 +1,196 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java
new file mode 100644
index 00000000..841615e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java
new file mode 100644
index 00000000..9b7d7969
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java
new file mode 100644
index 00000000..5e2d195b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionPriorityTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionPriorityTest.java
new file mode 100644
index 00000000..b078e132
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionPriorityTest.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java
new file mode 100644
index 00000000..4c8a2095
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java
new file mode 100644
index 00000000..3e90b9f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java
new file mode 100644
index 00000000..116552c3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionTypesTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionTypesTest.java
new file mode 100644
index 00000000..e26cc0ac
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/CompressionTypesTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java
new file mode 100644
index 00000000..71cada20
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java
@@ -0,0 +1,813 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.file.Paths;
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+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());
+ assertThat(origOpts.baseBackgroundCompactions()).isEqualTo(
+ copyOpts.baseBackgroundCompactions());
+ }
+
+ @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<DbPath> 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 baseBackgroundCompactions() {
+ try (final DBOptions opt = new DBOptions()) {
+ final int intValue = rand.nextInt();
+ opt.setBaseBackgroundCompactions(intValue);
+ assertThat(opt.baseBackgroundCompactions()).
+ isEqualTo(intValue);
+ }
+ }
+
+ @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 newTableReaderForCompactionInputs() {
+ try(final DBOptions opt = new DBOptions()) {
+ final boolean boolValue = rand.nextBoolean();
+ opt.setNewTableReaderForCompactionInputs(boolValue);
+ assertThat(opt.newTableReaderForCompactionInputs()).isEqualTo(boolValue);
+ }
+ }
+
+ @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<Integer, Long> cfLognumber,
+ final Map<String, Integer> 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 preserveDeletes() {
+ try (final DBOptions opt = new DBOptions()) {
+ assertThat(opt.preserveDeletes()).isFalse();
+ opt.setPreserveDeletes(true);
+ assertThat(opt.preserveDeletes()).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();
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java
new file mode 100644
index 00000000..3fb563ec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ThreadStatus> 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<ThreadStatus> threadList = env.getThreadList();
+ assertThat(threadList.size()).isGreaterThan(0);
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DirectSliceTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DirectSliceTest.java
new file mode 100644
index 00000000..67385345
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/DirectSliceTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java
new file mode 100644
index 00000000..75768e2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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();
+ envOptions.setCompactionReadaheadSize(intValue);
+ assertThat(envOptions.compactionReadaheadSize()).isEqualTo(intValue);
+ }
+ }
+
+ @Test
+ public void randomAccessMaxBufferSize() {
+ try (final EnvOptions envOptions = new EnvOptions()) {
+ final int intValue = rand.nextInt();
+ envOptions.setRandomAccessMaxBufferSize(intValue);
+ assertThat(envOptions.randomAccessMaxBufferSize()).isEqualTo(intValue);
+ }
+ }
+
+ @Test
+ public void writableFileMaxBufferSize() {
+ try (final EnvOptions envOptions = new EnvOptions()) {
+ final int intValue = rand.nextInt();
+ 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FilterTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FilterTest.java
new file mode 100644
index 00000000..dc5c19fb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FilterTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java
new file mode 100644
index 00000000..f90ae911
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushTest.java
new file mode 100644
index 00000000..1a354f4c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/FlushTest.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java
new file mode 100644
index 00000000..c1b064f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 HdfsEnvTest {
+
+ @ClassRule
+ public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
+ new RocksNativeLibraryResource();
+
+ @Rule
+ public TemporaryFolder dbFolder = new TemporaryFolder();
+
+ // expect org.rocksdb.RocksDBException: Not compiled with hdfs support
+ @Test(expected = RocksDBException.class)
+ public void construct() throws RocksDBException {
+ try (final Env env = new HdfsEnv("hdfs://localhost:5000")) {
+ // no-op
+ }
+ }
+
+ // expect org.rocksdb.RocksDBException: Not compiled with hdfs support
+ @Test(expected = RocksDBException.class)
+ public void construct_integration() throws RocksDBException {
+ try (final Env env = new HdfsEnv("hdfs://localhost:5000");
+ 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java
new file mode 100644
index 00000000..12ee537d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java
@@ -0,0 +1,109 @@
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java
new file mode 100644
index 00000000..ab7e2156
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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()).isTrue();
+ options.setWriteGlobalSeqno(false);
+ assertThat(options.writeGlobalSeqno()).isFalse();
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java
new file mode 100644
index 00000000..45b06be3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class KeyMayExistTest {
+
+ @ClassRule
+ public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
+ new RocksNativeLibraryResource();
+
+ @Rule
+ public TemporaryFolder dbFolder = new TemporaryFolder();
+
+ @Test
+ public void keyMayExist() throws RocksDBException {
+ final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes())
+ );
+
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try {
+ assertThat(columnFamilyHandleList.size()).
+ isEqualTo(2);
+ db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8));
+ // Test without column family
+ final Holder<byte[]> 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();
+
+ // Slice key
+ final StringBuilder builder = new StringBuilder("prefix");
+ final int offset = builder.toString().length();
+ builder.append("slice key 0");
+ final int len = builder.toString().length() - offset;
+ builder.append("suffix");
+
+ final byte[] sliceKey = builder.toString().getBytes(UTF_8);
+ final byte[] sliceValue = "slice value 0".getBytes(UTF_8);
+ db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length);
+
+ 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();
+
+ // Test without column family but with readOptions
+ try (final ReadOptions readOptions = new ReadOptions()) {
+ 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 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();
+
+ // Test slice sky with column family
+ 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 with column family and readOptions
+ try (final ReadOptions readOptions = new ReadOptions()) {
+ 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();
+ }
+
+ // 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();
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void keyMayExistNonUnicodeString() throws RocksDBException {
+ try (final Options options = new Options()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+ final RocksDB db = RocksDB.open(options,
+ dbFolder.getRoot().getAbsolutePath())) {
+ 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<byte[]> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LRUCacheTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LRUCacheTest.java
new file mode 100644
index 00000000..d2cd15b7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LRUCacheTest.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 LRUCacheTest {
+
+ static {
+ RocksDB.loadLibrary();
+ }
+
+ @Test
+ public void newLRUCache() {
+ final long capacity = 1000;
+ final int numShardBits = 16;
+ final boolean strictCapacityLimit = true;
+ final double highPriPoolRatio = 5;
+ try(final Cache lruCache = new LRUCache(capacity,
+ numShardBits, strictCapacityLimit, highPriPoolRatio)) {
+ //no op
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LoggerTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LoggerTest.java
new file mode 100644
index 00000000..5bc299f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/LoggerTest.java
@@ -0,0 +1,239 @@
+// 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<ColumnFamilyDescriptor> cfDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemTableTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemTableTest.java
new file mode 100644
index 00000000..73ac589a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemTableTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java
new file mode 100644
index 00000000..72d3ddc3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<RocksDB> dbs = new ArrayList<>(1);
+ dbs.add(db);
+ Set<Cache> caches = new HashSet<>(1);
+ caches.add(cache);
+ Map<MemoryUsageType, Long> 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));
+ assertThat(usage.get(MemoryUsageType.kCacheTotal)).isEqualTo(0);
+
+ 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<MemoryUsageType, Long> 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<RocksDB> dbs = new ArrayList<>(1);
+ dbs.add(db1);
+ dbs.add(db2);
+ Set<Cache> 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<MemoryUsageType, Long> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java
new file mode 100644
index 00000000..128d694b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java
@@ -0,0 +1,440 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+
+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 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);
+ buf.putLong(l);
+ return buf.array();
+ }
+
+ private long longFromByteArray(byte[] a) {
+ ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2)
+ );
+
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2)
+ );
+
+ final List<ColumnFamilyHandle> 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);
+ assertThat(longValue).isEqualTo(101);
+ } 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2)
+ );
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2)
+ );
+ final List<ColumnFamilyHandle> 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 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MixedOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MixedOptionsTest.java
new file mode 100644
index 00000000..10c92d49
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MixedOptionsTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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();
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java
new file mode 100644
index 00000000..f631905e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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)
+ .build()
+ .toString();
+
+ assertThat(str).isEqualTo("write_buffer_size=10;inplace_update_num_locks=5;"
+ + "disable_auto_compactions=true;paranoid_file_checks=true");
+ }
+
+ @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";
+
+ 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);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java
new file mode 100644
index 00000000..063a8de3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java
new file mode 100644
index 00000000..d1bdf0f8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 {
+
+ @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<String>() {
+ @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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java
new file mode 100644
index 00000000..ab60081a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java
new file mode 100644
index 00000000..519b70b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts));
+
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java
new file mode 100644
index 00000000..ef656b95
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java
new file mode 100644
index 00000000..f44816e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java
@@ -0,0 +1,350 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 OptimisticTransactionTest 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.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");
+ }
+ }
+
+ @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<ColumnFamilyHandle> 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 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 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);
+ } catch(final RocksDBException e) {
+ assertThat(e.getStatus().getCode() == Status.Code.InvalidArgument);
+ return;
+ }
+
+ fail("Optimistic transactions cannot be named.");
+ }
+ }
+
+ @Override
+ public OptimisticTransactionDBContainer startDb()
+ throws RocksDBException {
+ final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+
+ final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions();
+ final List<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY,
+ columnFamilyOptions));
+ final List<ColumnFamilyHandle> 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<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java
new file mode 100644
index 00000000..b249f95f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java
@@ -0,0 +1,1311 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.*;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+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 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<DbPath> 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 baseBackgroundCompactions() {
+ try (final Options opt = new Options()) {
+ final int intValue = rand.nextInt();
+ opt.setBaseBackgroundCompactions(intValue);
+ assertThat(opt.baseBackgroundCompactions()).
+ isEqualTo(intValue);
+ }
+ }
+
+ @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 accessHintOnCompactionStart() {
+ try (final Options opt = new Options()) {
+ final AccessHint accessHint = AccessHint.SEQUENTIAL;
+ opt.setAccessHintOnCompactionStart(accessHint);
+ assertThat(opt.accessHintOnCompactionStart()).isEqualTo(accessHint);
+ }
+ }
+
+ @Test
+ public void newTableReaderForCompactionInputs() {
+ try (final Options opt = new Options()) {
+ final boolean boolValue = rand.nextBoolean();
+ opt.setNewTableReaderForCompactionInputs(boolValue);
+ assertThat(opt.newTableReaderForCompactionInputs()).isEqualTo(boolValue);
+ }
+ }
+
+ @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<Integer, Long> cfLognumber,
+ final Map<String, Integer> 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 preserveDeletes() {
+ try (final Options opt = new Options()) {
+ assertThat(opt.preserveDeletes()).isFalse();
+ opt.setPreserveDeletes(true);
+ assertThat(opt.preserveDeletes()).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 compressionPerLevel() {
+ try (final Options options = new Options()) {
+ assertThat(options.compressionPerLevel()).isEmpty();
+ List<CompressionType> 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<CompressionType> 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 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);
+ }
+ }
+
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java
new file mode 100644
index 00000000..b84314ee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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();
+ final List<ColumnFamilyDescriptor> cfDescs = new ArrayList<>();
+ String path = dbPath;
+ if (apiType == TestAPI.LOAD_LATEST_OPTIONS) {
+ OptionsUtil.loadLatestOptions(path, Env.getDefault(), dbOptions, cfDescs, false);
+ } else if (apiType == TestAPI.LOAD_OPTIONS_FROM_FILE) {
+ path = dbPath + "/" + OptionsUtil.getLatestOptionsFileName(dbPath, Env.getDefault());
+ OptionsUtil.loadOptionsFromFile(path, Env.getDefault(), dbOptions, cfDescs, false);
+ }
+
+ 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlainTableConfigTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlainTableConfigTest.java
new file mode 100644
index 00000000..c813dbbb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlainTableConfigTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlatformRandomHelper.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlatformRandomHelper.java
new file mode 100644
index 00000000..80ea4d19
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/PlatformRandomHelper.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 &lt; 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java
new file mode 100644
index 00000000..e7d6e6c4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOnlyTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOnlyTest.java
new file mode 100644
index 00000000..6d5bc96f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOnlyTest.java
@@ -0,0 +1,305 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 db2 = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath())) {
+ assertThat("value").
+ isEqualTo(new String(db2.get("key".getBytes())));
+ }
+ }
+
+ try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) {
+ final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
+ cfDescriptors.add(new ColumnFamilyDescriptor(
+ RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts));
+
+ final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
+ try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(),
+ cfDescriptors, columnFamilyHandleList)) {
+ try (final ColumnFamilyOptions newCfOpts = new ColumnFamilyOptions();
+ final ColumnFamilyOptions newCf2Opts = new ColumnFamilyOptions()
+ ) {
+ columnFamilyHandleList.add(db.createColumnFamily(
+ new ColumnFamilyDescriptor("new_cf".getBytes(), newCfOpts)));
+ columnFamilyHandleList.add(db.createColumnFamily(
+ new ColumnFamilyDescriptor("new_cf2".getBytes(), newCf2Opts)));
+ db.put(columnFamilyHandleList.get(2), "key2".getBytes(),
+ "value2".getBytes());
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB db2 = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList)) {
+ try (final ColumnFamilyOptions newCfOpts2 =
+ new ColumnFamilyOptions();
+ final ColumnFamilyOptions newCf2Opts2 =
+ new ColumnFamilyOptions()
+ ) {
+ assertThat(db2.get("key2".getBytes())).isNull();
+ assertThat(db2.get(readOnlyColumnFamilyHandleList.get(0),
+ "key2".getBytes())).
+ isNull();
+ cfDescriptors.clear();
+ cfDescriptors.add(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY,
+ newCfOpts2));
+ cfDescriptors.add(new ColumnFamilyDescriptor("new_cf2".getBytes(),
+ newCf2Opts2));
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList2
+ = new ArrayList<>();
+ try (final RocksDB db3 = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList2)) {
+ try {
+ assertThat(new String(db3.get(
+ readOnlyColumnFamilyHandleList2.get(1),
+ "key2".getBytes()))).isEqualTo("value2");
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList2) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ columnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList)) {
+ try {
+ // test that put fails in readonly mode
+ rDb.put("key".getBytes(), "value".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList)) {
+ try {
+ rDb.put(readOnlyColumnFamilyHandleList.get(0),
+ "key".getBytes(), "value".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList)) {
+ try {
+ rDb.delete("key".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList)) {
+ try {
+ rDb.delete(readOnlyColumnFamilyHandleList.get(0),
+ "key".getBytes());
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList);
+ final WriteBatch wb = new WriteBatch();
+ final WriteOptions wOpts = new WriteOptions()) {
+ try {
+ wb.put("key".getBytes(), "value".getBytes());
+ rDb.write(wOpts, wb);
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)
+ );
+
+ final List<ColumnFamilyHandle> readOnlyColumnFamilyHandleList =
+ new ArrayList<>();
+ try (final RocksDB rDb = RocksDB.openReadOnly(
+ dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
+ readOnlyColumnFamilyHandleList);
+ final WriteBatch wb = new WriteBatch();
+ final WriteOptions wOpts = new WriteOptions()) {
+ try {
+ wb.put(readOnlyColumnFamilyHandleList.get(0), "key".getBytes(),
+ "value".getBytes());
+ rDb.write(wOpts, wb);
+ } finally {
+ for (final ColumnFamilyHandle columnFamilyHandle :
+ readOnlyColumnFamilyHandleList) {
+ columnFamilyHandle.close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java
new file mode 100644
index 00000000..675023ef
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java
@@ -0,0 +1,323 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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());
+ 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();
+ }
+ }
+ }
+
+ @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 long longValue = rand.nextLong();
+ opt.setReadaheadSize(longValue);
+ assertThat(opt.readaheadSize()).isEqualTo(longValue);
+ }
+ }
+
+ @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();
+ }
+ }
+
+ @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();
+ }
+ }
+
+ @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 iterStartSeqnum() {
+ try (final ReadOptions opt = new ReadOptions()) {
+ assertThat(opt.iterStartSeqnum()).isEqualTo(0);
+
+ opt.setIterStartSeqnum(10);
+ assertThat(opt.iterStartSeqnum()).isEqualTo(10);
+ }
+ }
+
+ @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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java
new file mode 100644
index 00000000..d3bd4ece
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java
new file mode 100644
index 00000000..b4d96ed4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java
@@ -0,0 +1,1669 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyHandle> 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<ColumnFamilyHandle> 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<ColumnFamilyHandle> 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);
+ }
+ }
+
+ @SuppressWarnings("deprecated")
+ @Test
+ public void multiGet() 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<byte[]> lookupKeys = new ArrayList<>();
+ lookupKeys.add("key1".getBytes());
+ lookupKeys.add("key2".getBytes());
+ Map<byte[], byte[]> results = db.multiGet(lookupKeys);
+ assertThat(results).isNotNull();
+ assertThat(results.values()).isNotNull();
+ assertThat(results.values()).
+ contains("value".getBytes(), "12345678".getBytes());
+ // test same method with ReadOptions
+ results = db.multiGet(rOpt, lookupKeys);
+ assertThat(results).isNotNull();
+ assertThat(results.values()).isNotNull();
+ assertThat(results.values()).
+ contains("value".getBytes(), "12345678".getBytes());
+
+ // remove existing key
+ lookupKeys.remove("key2".getBytes());
+ // add non existing key
+ lookupKeys.add("key3".getBytes());
+ results = db.multiGet(lookupKeys);
+ assertThat(results).isNotNull();
+ assertThat(results.values()).isNotNull();
+ assertThat(results.values()).
+ contains("value".getBytes());
+ // test same call with readOptions
+ results = db.multiGet(rOpt, lookupKeys);
+ assertThat(results).isNotNull();
+ assertThat(results.values()).isNotNull();
+ assertThat(results.values()).
+ contains("value".getBytes());
+ }
+ }
+
+ @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<byte[]> lookupKeys = new ArrayList<>();
+ lookupKeys.add("key1".getBytes());
+ lookupKeys.add("key2".getBytes());
+ List<byte[]> 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts));
+
+ // open database
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)
+ );
+
+ // open database
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)
+ );
+
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)
+ );
+
+ final List<ColumnFamilyHandle> 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 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts));
+
+ // open database
+ final List<ColumnFamilyHandle> 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);
+ }
+ }
+ }
+
+ @Ignore("This test crashes. Re-enable after fixing.")
+ @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(key2)),
+ 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);
+ }
+ }
+ }
+
+ @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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions),
+ new ColumnFamilyDescriptor(cfName, cfOptions)
+ );
+ final List<ColumnFamilyHandle> 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<String> 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<ColumnFamilyDescriptor> cfDescs = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)
+ );
+ final List<ColumnFamilyHandle> 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 setPreserveDeletesSequenceNumber() 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.setPreserveDeletesSequenceNumber(db.getLatestSequenceNumber()))
+ .isFalse();
+ }
+ }
+ }
+
+ @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(13);
+ assertThat(livefiles.files.size()).isEqualTo(3);
+ assertThat(livefiles.files.get(0)).isEqualTo("/CURRENT");
+ assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000001");
+ assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000005");
+ }
+ }
+ }
+
+ @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<LogFile> 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<LiveFileMetaData> liveFilesMetaData
+ = db.getLiveFilesMetaData();
+ assertThat(liveFilesMetaData).isEmpty();
+ }
+ }
+ }
+
+ @Test
+ public void getColumnFamilyMetaData() throws RocksDBException {
+ try (final DBOptions options = new DBOptions()
+ .setCreateIfMissing(true)) {
+ final List<ColumnFamilyDescriptor> cfDescs = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)
+ );
+ final List<ColumnFamilyHandle> 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<ColumnFamilyDescriptor> cfDescs = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)
+ );
+ final List<ColumnFamilyHandle> 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<String, TableProperties> 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<ColumnFamilyDescriptor> cfDescs = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)
+ );
+ final List<ColumnFamilyHandle> 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<String, TableProperties> 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<ColumnFamilyDescriptor> cfDescs = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)
+ );
+ final List<ColumnFamilyHandle> 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<byte[]> 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts));
+
+ // open database
+ final List<ColumnFamilyHandle> 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();
+ }
+ }
+ }
+ }
+ }
+
+ private static class InMemoryTraceWriter extends AbstractTraceWriter {
+ private final List<byte[]> 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<byte[]> getWrites() {
+ return writes;
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java
new file mode 100644
index 00000000..a8f773b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 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();
+
+ @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());
+
+ ByteBuffer key = ByteBuffer.allocateDirect(2);
+ ByteBuffer value = ByteBuffer.allocateDirect(2);
+ assertThat(iterator.key(key)).isEqualTo(4);
+ assertThat(iterator.value(value)).isEqualTo(6);
+
+ assertThat(key.position()).isEqualTo(0);
+ assertThat(key.limit()).isEqualTo(2);
+ assertThat(value.position()).isEqualTo(0);
+ assertThat(value.limit()).isEqualTo(2);
+
+ byte[] tmp = new byte[2];
+ key.get(tmp);
+ assertThat(tmp).isEqualTo("ke".getBytes());
+ value.get(tmp);
+ assertThat(tmp).isEqualTo("va".getBytes());
+
+ key = ByteBuffer.allocateDirect(12);
+ value = ByteBuffer.allocateDirect(12);
+ assertThat(iterator.key(key)).isEqualTo(4);
+ assertThat(iterator.value(value)).isEqualTo(6);
+ assertThat(key.position()).isEqualTo(0);
+ assertThat(key.limit()).isEqualTo(4);
+ assertThat(value.position()).isEqualTo(0);
+ assertThat(value.limit()).isEqualTo(6);
+
+ tmp = new byte[4];
+ key.get(tmp);
+ assertThat(tmp).isEqualTo("key1".getBytes());
+ tmp = new byte[6];
+ value.get(tmp);
+ assertThat(tmp).isEqualTo("value1".getBytes());
+
+ 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();
+
+ key.clear();
+ key.put("key1".getBytes());
+ key.flip();
+ iterator.seek(key);
+ assertThat(iterator.isValid()).isTrue();
+ assertThat(iterator.value()).isEqualTo("value1".getBytes());
+ assertThat(key.position()).isEqualTo(4);
+ assertThat(key.limit()).isEqualTo(4);
+
+ key.clear();
+ key.put("key2".getBytes());
+ key.flip();
+ iterator.seekForPrev(key);
+ assertThat(iterator.isValid()).isTrue();
+ assertThat(iterator.value()).isEqualTo("value2".getBytes());
+ assertThat(key.position()).isEqualTo(4);
+ assertThat(key.limit()).isEqualTo(4);
+ }
+
+ 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());
+ }
+ }
+ }
+
+ @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
+ ColumnFamilyDescriptor cfd1 = new ColumnFamilyDescriptor("cf1".getBytes());
+ 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
+ ColumnFamilyDescriptor cfd2 = new ColumnFamilyDescriptor("cf2".getBytes());
+ 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java
new file mode 100644
index 00000000..a03a0f0a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java
new file mode 100644
index 00000000..6116f2f9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SliceTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SliceTest.java
new file mode 100644
index 00000000..c65b0190
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SliceTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SnapshotTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SnapshotTest.java
new file mode 100644
index 00000000..11f0d560
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SnapshotTest.java
@@ -0,0 +1,169 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java
new file mode 100644
index 00000000..2e136e82
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileReaderTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileReaderTest.java
new file mode 100644
index 00000000..0b841f42
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileReaderTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.rocksdb.util.BytewiseComparator;
+
+public class SstFileReaderTest {
+ private static final String SST_FILE_NAME = "test.sst";
+
+ 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 String key;
+ private String value;
+ private OpType opType;
+ }
+
+ @Rule public TemporaryFolder parentFolder = new TemporaryFolder();
+
+ enum OpType { PUT, PUT_BYTES, MERGE, MERGE_BYTES, DELETE, DELETE_BYTES }
+
+ private File newSstFile(final List<KeyValueWithOp> keyValues)
+ throws IOException, RocksDBException {
+ final EnvOptions envOptions = new EnvOptions();
+ final StringAppendOperator stringAppendOperator = new StringAppendOperator();
+ final Options options = new Options().setMergeOperator(stringAppendOperator);
+ SstFileWriter sstFileWriter;
+ sstFileWriter = new SstFileWriter(envOptions, options);
+
+ final File sstFile = parentFolder.newFile(SST_FILE_NAME);
+ try {
+ sstFileWriter.open(sstFile.getAbsolutePath());
+ 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();
+ 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<KeyValueWithOp> keyValues = new ArrayList<>();
+ keyValues.add(new KeyValueWithOp("key1", "value1", 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(), 1);
+
+ // Check key and value
+ assertThat(iterator.key()).isEqualTo("key1".getBytes());
+ assertThat(iterator.value()).isEqualTo("value1".getBytes());
+
+ ByteBuffer direct = ByteBuffer.allocateDirect(128);
+ direct.put("key1".getBytes()).flip();
+ iterator.seek(direct);
+ assertThat(direct.position()).isEqualTo(4);
+ assertThat(direct.limit()).isEqualTo(4);
+
+ assertThat(iterator.isValid()).isTrue();
+ assertThat(iterator.key()).isEqualTo("key1".getBytes());
+ assertThat(iterator.value()).isEqualTo("value1".getBytes());
+
+ direct.clear();
+ assertThat(iterator.key(direct)).isEqualTo("key1".getBytes().length);
+ byte[] dst = new byte["key1".getBytes().length];
+ direct.get(dst);
+ assertThat(new String(dst)).isEqualTo("key1");
+
+ direct.clear();
+ assertThat(iterator.value(direct)).isEqualTo("value1".getBytes().length);
+ dst = new byte["value1".getBytes().length];
+ direct.get(dst);
+ assertThat(new String(dst)).isEqualTo("value1");
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileWriterTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileWriterTest.java
new file mode 100644
index 00000000..0a5506fc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/SstFileWriterTest.java
@@ -0,0 +1,241 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 }
+
+ 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 String key;
+ private String value;
+ private OpType opType;
+ };
+
+ private File newSstFile(final List<KeyValueWithOp> 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<KeyValueWithOp> 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<KeyValueWithOp> 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<KeyValueWithOp> 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<KeyValueWithOp> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java
new file mode 100644
index 00000000..36721c80
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java
new file mode 100644
index 00000000..de92102e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatsCallbackMock.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatsCallbackMock.java
new file mode 100644
index 00000000..af8db0ca
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/StatsCallbackMock.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java
new file mode 100644
index 00000000..2bd3b179
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java
@@ -0,0 +1,106 @@
+// 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)
+ );
+
+ final List<ColumnFamilyHandle> 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<byte[]> cfNames = new ArrayList<>();
+
+ @Override
+ public boolean filter(final TableProperties tableProperties) {
+ cfNames.add(tableProperties.getColumnFamilyName());
+ return true;
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java
new file mode 100644
index 00000000..c958f96b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java
new file mode 100644
index 00000000..7eaa6b16
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java
new file mode 100644
index 00000000..b0ea813f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java
@@ -0,0 +1,178 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts));
+
+ final List<ColumnFamilyHandle> 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<Long, TransactionDB.KeyLockInfo> lockStatus =
+ tdb.getLockStatusData();
+
+ assertThat(lockStatus.size()).isEqualTo(1);
+ final Set<Map.Entry<Long, TransactionDB.KeyLockInfo>> entrySet = lockStatus.entrySet();
+ final Map.Entry<Long, TransactionDB.KeyLockInfo> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java
new file mode 100644
index 00000000..3c4dff7b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java
@@ -0,0 +1,139 @@
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java
new file mode 100644
index 00000000..add0439e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java
new file mode 100644
index 00000000..57a05c9e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java
@@ -0,0 +1,308 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 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<ColumnFamilyHandle> 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 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 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.COMMITED);
+ }
+
+ 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<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY,
+ columnFamilyOptions));
+ final List<ColumnFamilyHandle> 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<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TtlDBTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TtlDBTest.java
new file mode 100644
index 00000000..ffa15e76
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/TtlDBTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyDescriptor> cfNames = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes())
+ );
+ final List<Integer> ttlValues = Arrays.asList(0, 1);
+
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/Types.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/Types.java
new file mode 100644
index 00000000..c3c1de83
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/Types.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java
new file mode 100644
index 00000000..2a0133f6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java
new file mode 100644
index 00000000..adeb959d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor(bytes("pikachu"))
+ );
+ final List<ColumnFamilyHandle> 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<Integer, Long> cfLognumber;
+ Map<String, Integer> cfNameId;
+ final List<Long> logNumbers = new ArrayList<>();
+ final List<String> 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<Integer, Long> cfLognumber,
+ final Map<String, Integer> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java
new file mode 100644
index 00000000..2826b128
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Event> 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<Event> actualEvents =
+ handler.getEvents();
+ assertThat(testEvents.size()).isSameAs(actualEvents.size());
+
+ assertThat(testEvents).isEqualTo(actualEvents);
+ }
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java
new file mode 100644
index 00000000..f915c7dc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java
@@ -0,0 +1,528 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.remove(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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java
new file mode 100644
index 00000000..c5090dbc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Integer> 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<Callable<Void>> callables = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final int offset = i * 100;
+ callables.add(new Callable<Void>() {
+ @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<Void> completionService =
+ new ExecutorCompletionService<>(executorService);
+ final Set<Future<Void>> futures = new HashSet<>();
+ for (final Callable<Void> callable : callables) {
+ futures.add(completionService.submit(callable));
+ }
+
+ while (futures.size() > 0) {
+ final Future<Void> future = completionService.take();
+ futures.remove(future);
+
+ try {
+ future.get();
+ } catch (final ExecutionException e) {
+ for (final Future<Void> f : futures) {
+ f.cancel(true);
+ }
+
+ throw e;
+ }
+ }
+ } finally {
+ executorService.shutdown();
+ executorService.awaitTermination(10, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java
new file mode 100644
index 00000000..01eb652f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java
@@ -0,0 +1,566 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.Arrays;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+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 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())) {
+ ByteBuffer k1 = ByteBuffer.allocateDirect(16);
+ ByteBuffer v1 = ByteBuffer.allocateDirect(16);
+ ByteBuffer k2 = ByteBuffer.allocateDirect(16);
+ 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 k6 = "key6";
+ final String k7 = "key7";
+ 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[] k6b = k6.getBytes(UTF_8);
+ final byte[] k7b = k7.getBytes(UTF_8);
+ final byte[] v8b = v8.getBytes(UTF_8);
+
+ // 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 (int i = 0; i < testOffsets.length; i++) {
+ final int testOffset = testOffsets[i];
+ 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]);
+
+ // Direct buffer seek
+ expected[testOffset].getKey().data().mark();
+ ByteBuffer db = expected[testOffset].getKey().data();
+ it.seek(db);
+ assertThat(db.position()).isEqualTo(key.length);
+ assertThat(it.isValid()).isTrue();
+ }
+
+ //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());
+ }
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java
new file mode 100644
index 00000000..1d5f3cc8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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 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();
+ }
+ }
+
+ @Test
+ public void copyConstructor() {
+ WriteOptions origOpts = new WriteOptions();
+ origOpts.setDisableWAL(rand.nextBoolean());
+ origOpts.setIgnoreMissingColumnFamilies(rand.nextBoolean());
+ origOpts.setSync(rand.nextBoolean());
+ WriteOptions copyOpts = new WriteOptions(origOpts);
+ assertThat(origOpts.disableWAL()).isEqualTo(copyOpts.disableWAL());
+ assertThat(origOpts.ignoreMissingColumnFamilies()).isEqualTo(
+ copyOpts.ignoreMissingColumnFamilies());
+ assertThat(origOpts.sync()).isEqualTo(copyOpts.sync());
+ }
+
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java
new file mode 100644
index 00000000..c4e4f25a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java
@@ -0,0 +1,21 @@
+// 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<RemoveEmptyValueCompactionFilter> {
+ @Override
+ public RemoveEmptyValueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) {
+ return new RemoveEmptyValueCompactionFilter();
+ }
+
+ @Override
+ public String name() {
+ return "RemoveEmptyValueCompactionFilterFactory";
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java
new file mode 100644
index 00000000..42d3148e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Class<?>> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java
new file mode 100644
index 00000000..fb7239c9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java
@@ -0,0 +1,267 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Object[]> 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(),
+ new ColumnFamilyOptions()
+ .setComparator(comparator))
+ );
+
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java
new file mode 100644
index 00000000..2e2ddc54
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java
@@ -0,0 +1,519 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<String> source_strings = Arrays.asList("b", "d", "f", "h", "j", "l");
+ private List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> jComparator = toJavaComparator(comparator2);
+ doRandomIterationTest(
+ db,
+ jComparator,
+ rnd,
+ 8, 100, 3
+ );
+ }
+ }
+ }
+ }
+
+ private void doRandomIterationTest(
+ final RocksDB db, final java.util.Comparator<String> javaComparator,
+ final Random rnd,
+ final int num_writes, final int num_iter_ops,
+ final int num_trigger_flush) throws RocksDBException {
+
+ final TreeMap<String, String> 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<String, String> 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(7);
+ 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;
+ default: {
+ assert (type == 6);
+ 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<String> toJavaComparator(
+ final AbstractComparator rocksComparator) {
+ return new java.util.Comparator<String>() {
+ @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<K, V> implements RocksIteratorInterface {
+
+ private final List<Map.Entry<K, V>> entries;
+ private final java.util.Comparator<? super K> comparator;
+ private int offset = -1;
+
+ private int lastPrefixMatchIdx = -1;
+ private int lastPrefixMatch = 0;
+
+ public KVIter(final TreeMap<K, V> 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 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java
new file mode 100644
index 00000000..89081947
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java
@@ -0,0 +1,172 @@
+// 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<Event> events = new ArrayList<>();
+
+ /**
+ * Returns a copy of the current events list
+ *
+ * @return a list of the events which have happened upto now
+ */
+ public List<Event> 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));
+ }
+
+ 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() {
+
+ return Objects.hash(action, columnFamilyId, key, value);
+ }
+ }
+
+ /**
+ * 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 }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/EnvironmentTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/EnvironmentTest.java
new file mode 100644
index 00000000..8b57066b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/EnvironmentTest.java
@@ -0,0 +1,259 @@
+// 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 org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EnvironmentTest {
+ private final static String ARCH_FIELD_NAME = "ARCH";
+ private final static String OS_FIELD_NAME = "OS";
+ private final static String MUSL_LIBC_FIELD_NAME = "MUSL_LIBC";
+
+ private static String INITIAL_OS;
+ private static String INITIAL_ARCH;
+ 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);
+ }
+
+ @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.getSharedLibraryFileName("rocksdb")).
+ isEqualTo("librocksdbjni.dylib");
+ }
+
+ @Test
+ public void mac64() {
+ setEnvironmentClassFields("mac", "64");
+ assertThat(Environment.isWindows()).isFalse();
+ assertThat(Environment.getJniLibraryExtension()).
+ isEqualTo(".jnilib");
+ assertThat(Environment.getJniLibraryFileName("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.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");
+ Environment.getJniLibraryFileName("rocksdb");
+ }
+
+ @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.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.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.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.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.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.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.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so");
+ setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false);
+ }
+
+ @Test
+ public void aarch64() {
+ 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.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.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so");
+ setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false);
+ }
+
+ 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_LIBC_FIELD_NAME, INITIAL_MUSL_LIBC);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/IntComparatorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/IntComparatorTest.java
new file mode 100644
index 00000000..dd328851
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/IntComparatorTest.java
@@ -0,0 +1,266 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Object[]> 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(),
+ new ColumnFamilyOptions()
+ .setComparator(comparator))
+ );
+
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java
new file mode 100644
index 00000000..6ffa2785
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java
@@ -0,0 +1,174 @@
+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<Object[]> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java
new file mode 100644
index 00000000..ca08d9de
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java
@@ -0,0 +1,270 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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<Object[]> 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<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
+ new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
+ new ColumnFamilyDescriptor("new_cf".getBytes(),
+ new ColumnFamilyOptions()
+ .setComparator(comparator))
+ );
+
+ final List<ColumnFamilyHandle> 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/SizeUnitTest.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/SizeUnitTest.java
new file mode 100644
index 00000000..990aa5f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/SizeUnitTest.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java
new file mode 100644
index 00000000..57347b08
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// 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.CompactionPriority;
+import org.rocksdb.Options;
+import org.rocksdb.WALRecoveryMode;
+
+import java.util.Random;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * 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;
+ }
+}
diff --git a/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java
new file mode 100644
index 00000000..646e8b8f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java
@@ -0,0 +1,134 @@
+// 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();
+ }
+}
diff --git a/storage/rocksdb/rocksdb/logging/auto_roll_logger.cc b/storage/rocksdb/rocksdb/logging/auto_roll_logger.cc
new file mode 100644
index 00000000..fc498521
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/auto_roll_logger.cc
@@ -0,0 +1,292 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include "file/filename.h"
+#include "logging/logging.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+// -- AutoRollLogger
+
+AutoRollLogger::AutoRollLogger(Env* env, const std::string& dbname,
+ const std::string& db_log_dir,
+ size_t log_max_size,
+ size_t log_file_time_to_roll,
+ size_t keep_log_file_num,
+ const InfoLogLevel log_level)
+ : Logger(log_level),
+ dbname_(dbname),
+ db_log_dir_(db_log_dir),
+ env_(env),
+ status_(Status::OK()),
+ kMaxLogFileSize(log_max_size),
+ kLogFileTimeToRoll(log_file_time_to_roll),
+ kKeepLogFileNum(keep_log_file_num),
+ cached_now(static_cast<uint64_t>(env_->NowMicros() * 1e-6)),
+ ctime_(cached_now),
+ cached_now_access_count(0),
+ call_NowMicros_every_N_records_(100),
+ mutex_() {
+ Status s = env->GetAbsolutePath(dbname, &db_absolute_path_);
+ if (s.IsNotSupported()) {
+ db_absolute_path_ = dbname;
+ } else {
+ status_ = s;
+ }
+ log_fname_ = InfoLogFileName(dbname_, db_absolute_path_, db_log_dir_);
+ if (env_->FileExists(log_fname_).ok()) {
+ RollLogFile();
+ }
+ GetExistingFiles();
+ ResetLogger();
+ if (status_.ok()) {
+ status_ = TrimOldLogFiles();
+ }
+}
+
+Status AutoRollLogger::ResetLogger() {
+ TEST_SYNC_POINT("AutoRollLogger::ResetLogger:BeforeNewLogger");
+ status_ = env_->NewLogger(log_fname_, &logger_);
+ TEST_SYNC_POINT("AutoRollLogger::ResetLogger:AfterNewLogger");
+
+ if (!status_.ok()) {
+ return status_;
+ }
+ assert(logger_);
+ logger_->SetInfoLogLevel(Logger::GetInfoLogLevel());
+
+ if (logger_->GetLogFileSize() == Logger::kDoNotSupportGetLogFileSize) {
+ status_ = Status::NotSupported(
+ "The underlying logger doesn't support GetLogFileSize()");
+ }
+ if (status_.ok()) {
+ cached_now = static_cast<uint64_t>(env_->NowMicros() * 1e-6);
+ ctime_ = cached_now;
+ cached_now_access_count = 0;
+ }
+
+ return status_;
+}
+
+void AutoRollLogger::RollLogFile() {
+ // This function is called when log is rotating. Two rotations
+ // can happen quickly (NowMicro returns same value). To not overwrite
+ // previous log file we increment by one micro second and try again.
+ uint64_t now = env_->NowMicros();
+ std::string old_fname;
+ do {
+ old_fname = OldInfoLogFileName(
+ dbname_, now, db_absolute_path_, db_log_dir_);
+ now++;
+ } while (env_->FileExists(old_fname).ok());
+ env_->RenameFile(log_fname_, old_fname);
+ old_log_files_.push(old_fname);
+}
+
+void AutoRollLogger::GetExistingFiles() {
+ {
+ // Empty the queue to avoid duplicated entries in the queue.
+ std::queue<std::string> empty;
+ std::swap(old_log_files_, empty);
+ }
+
+ std::string parent_dir;
+ std::vector<std::string> info_log_files;
+ Status s =
+ GetInfoLogFiles(env_, db_log_dir_, dbname_, &parent_dir, &info_log_files);
+ if (status_.ok()) {
+ status_ = s;
+ }
+ // We need to sort the file before enqueing it so that when we
+ // delete file from the front, it is the oldest file.
+ std::sort(info_log_files.begin(), info_log_files.end());
+
+ for (const std::string& f : info_log_files) {
+ old_log_files_.push(parent_dir + "/" + f);
+ }
+}
+
+Status AutoRollLogger::TrimOldLogFiles() {
+ // Here we directly list info files and delete them through Env.
+ // The deletion isn't going through DB, so there are shortcomes:
+ // 1. the deletion is not rate limited by SstFileManager
+ // 2. there is a chance that an I/O will be issued here
+ // Since it's going to be complicated to pass DB object down to
+ // here, we take a simple approach to keep the code easier to
+ // maintain.
+
+ // old_log_files_.empty() is helpful for the corner case that
+ // kKeepLogFileNum == 0. We can instead check kKeepLogFileNum != 0 but
+ // it's essentially the same thing, and checking empty before accessing
+ // the queue feels safer.
+ while (!old_log_files_.empty() && old_log_files_.size() >= kKeepLogFileNum) {
+ Status s = env_->DeleteFile(old_log_files_.front());
+ // Remove the file from the tracking anyway. It's possible that
+ // DB cleaned up the old log file, or people cleaned it up manually.
+ old_log_files_.pop();
+ // To make the file really go away, we should sync parent directory.
+ // Since there isn't any consistency issue involved here, skipping
+ // this part to avoid one I/O here.
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ return Status::OK();
+}
+
+std::string AutoRollLogger::ValistToString(const char* format,
+ va_list args) const {
+ // Any log messages longer than 1024 will get truncated.
+ // The user is responsible for chopping longer messages into multi line log
+ static const int MAXBUFFERSIZE = 1024;
+ char buffer[MAXBUFFERSIZE];
+
+ int count = vsnprintf(buffer, MAXBUFFERSIZE, format, args);
+ (void) count;
+ assert(count >= 0);
+
+ return buffer;
+}
+
+void AutoRollLogger::LogInternal(const char* format, ...) {
+ mutex_.AssertHeld();
+
+ if (!logger_) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, format);
+ logger_->Logv(format, args);
+ va_end(args);
+}
+
+void AutoRollLogger::Logv(const char* format, va_list ap) {
+ assert(GetStatus().ok());
+ if (!logger_) {
+ return;
+ }
+
+ std::shared_ptr<Logger> logger;
+ {
+ MutexLock l(&mutex_);
+ if ((kLogFileTimeToRoll > 0 && LogExpired()) ||
+ (kMaxLogFileSize > 0 && logger_->GetLogFileSize() >= kMaxLogFileSize)) {
+ RollLogFile();
+ Status s = ResetLogger();
+ Status s2 = TrimOldLogFiles();
+
+ if (!s.ok()) {
+ // can't really log the error if creating a new LOG file failed
+ return;
+ }
+
+ WriteHeaderInfo();
+
+ if (!s2.ok()) {
+ ROCKS_LOG_WARN(logger.get(), "Fail to trim old info log file: %s",
+ s2.ToString().c_str());
+ }
+ }
+
+ // pin down the current logger_ instance before releasing the mutex.
+ logger = logger_;
+ }
+
+ // Another thread could have put a new Logger instance into logger_ by now.
+ // However, since logger is still hanging on to the previous instance
+ // (reference count is not zero), we don't have to worry about it being
+ // deleted while we are accessing it.
+ // Note that logv itself is not mutex protected to allow maximum concurrency,
+ // as thread safety should have been handled by the underlying logger.
+ logger->Logv(format, ap);
+}
+
+void AutoRollLogger::WriteHeaderInfo() {
+ mutex_.AssertHeld();
+ for (auto& header : headers_) {
+ LogInternal("%s", header.c_str());
+ }
+}
+
+void AutoRollLogger::LogHeader(const char* format, va_list args) {
+ if (!logger_) {
+ return;
+ }
+
+ // header message are to be retained in memory. Since we cannot make any
+ // assumptions about the data contained in va_list, we will retain them as
+ // strings
+ va_list tmp;
+ va_copy(tmp, args);
+ std::string data = ValistToString(format, tmp);
+ va_end(tmp);
+
+ MutexLock l(&mutex_);
+ headers_.push_back(data);
+
+ // Log the original message to the current log
+ logger_->Logv(format, args);
+}
+
+bool AutoRollLogger::LogExpired() {
+ if (cached_now_access_count >= call_NowMicros_every_N_records_) {
+ cached_now = static_cast<uint64_t>(env_->NowMicros() * 1e-6);
+ cached_now_access_count = 0;
+ }
+
+ ++cached_now_access_count;
+ return cached_now >= ctime_ + kLogFileTimeToRoll;
+}
+#endif // !ROCKSDB_LITE
+
+Status CreateLoggerFromOptions(const std::string& dbname,
+ const DBOptions& options,
+ std::shared_ptr<Logger>* logger) {
+ if (options.info_log) {
+ *logger = options.info_log;
+ return Status::OK();
+ }
+
+ Env* env = options.env;
+ std::string db_absolute_path;
+ env->GetAbsolutePath(dbname, &db_absolute_path);
+ std::string fname =
+ InfoLogFileName(dbname, db_absolute_path, options.db_log_dir);
+
+ env->CreateDirIfMissing(dbname); // In case it does not exist
+ // Currently we only support roll by time-to-roll and log size
+#ifndef ROCKSDB_LITE
+ if (options.log_file_time_to_roll > 0 || options.max_log_file_size > 0) {
+ AutoRollLogger* result = new AutoRollLogger(
+ env, dbname, options.db_log_dir, options.max_log_file_size,
+ options.log_file_time_to_roll, options.keep_log_file_num,
+ options.info_log_level);
+ Status s = result->GetStatus();
+ if (!s.ok()) {
+ delete result;
+ } else {
+ logger->reset(result);
+ }
+ return s;
+ }
+#endif // !ROCKSDB_LITE
+ // Open a log file in the same directory as the db
+ env->RenameFile(fname,
+ OldInfoLogFileName(dbname, env->NowMicros(), db_absolute_path,
+ options.db_log_dir));
+ auto s = env->NewLogger(fname, logger);
+ if (logger->get() != nullptr) {
+ (*logger)->SetInfoLogLevel(options.info_log_level);
+ }
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/auto_roll_logger.h b/storage/rocksdb/rocksdb/logging/auto_roll_logger.h
new file mode 100644
index 00000000..5d8c4141
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/auto_roll_logger.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// Logger implementation that can be shared by all environments
+// where enough posix functionality is available.
+
+#pragma once
+#include <list>
+#include <queue>
+#include <string>
+
+#include "file/filename.h"
+#include "port/port.h"
+#include "port/util_logger.h"
+#include "test_util/sync_point.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+// Rolls the log file by size and/or time
+class AutoRollLogger : public Logger {
+ public:
+ AutoRollLogger(Env* env, const std::string& dbname,
+ const std::string& db_log_dir, size_t log_max_size,
+ size_t log_file_time_to_roll, size_t keep_log_file_num,
+ const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL);
+
+ using Logger::Logv;
+ void Logv(const char* format, va_list ap) override;
+
+ // Write a header entry to the log. All header information will be written
+ // again every time the log rolls over.
+ virtual void LogHeader(const char* format, va_list ap) override;
+
+ // check if the logger has encountered any problem.
+ Status GetStatus() {
+ return status_;
+ }
+
+ size_t GetLogFileSize() const override {
+ if (!logger_) {
+ return 0;
+ }
+
+ std::shared_ptr<Logger> logger;
+ {
+ MutexLock l(&mutex_);
+ // pin down the current logger_ instance before releasing the mutex.
+ logger = logger_;
+ }
+ return logger->GetLogFileSize();
+ }
+
+ void Flush() override {
+ std::shared_ptr<Logger> logger;
+ {
+ MutexLock l(&mutex_);
+ // pin down the current logger_ instance before releasing the mutex.
+ logger = logger_;
+ }
+ TEST_SYNC_POINT("AutoRollLogger::Flush:PinnedLogger");
+ if (logger) {
+ logger->Flush();
+ }
+ }
+
+ virtual ~AutoRollLogger() {
+ if (logger_ && !closed_) {
+ logger_->Close();
+ }
+ }
+
+ using Logger::GetInfoLogLevel;
+ InfoLogLevel GetInfoLogLevel() const override {
+ MutexLock l(&mutex_);
+ if (!logger_) {
+ return Logger::GetInfoLogLevel();
+ }
+ return logger_->GetInfoLogLevel();
+ }
+
+ using Logger::SetInfoLogLevel;
+ void SetInfoLogLevel(const InfoLogLevel log_level) override {
+ MutexLock lock(&mutex_);
+ Logger::SetInfoLogLevel(log_level);
+ if (logger_) {
+ logger_->SetInfoLogLevel(log_level);
+ }
+ }
+
+ void SetCallNowMicrosEveryNRecords(uint64_t call_NowMicros_every_N_records) {
+ call_NowMicros_every_N_records_ = call_NowMicros_every_N_records;
+ }
+
+ // Expose the log file path for testing purpose
+ std::string TEST_log_fname() const {
+ return log_fname_;
+ }
+
+ uint64_t TEST_ctime() const { return ctime_; }
+
+ Logger* TEST_inner_logger() const { return logger_.get(); }
+
+ protected:
+ // Implementation of Close()
+ virtual Status CloseImpl() override {
+ if (logger_) {
+ return logger_->Close();
+ } else {
+ return Status::OK();
+ }
+ }
+
+ private:
+ bool LogExpired();
+ Status ResetLogger();
+ void RollLogFile();
+ // Read all names of old log files into old_log_files_
+ // If there is any error, put the error code in status_
+ void GetExistingFiles();
+ // Delete old log files if it excceeds the limit.
+ Status TrimOldLogFiles();
+ // Log message to logger without rolling
+ void LogInternal(const char* format, ...);
+ // Serialize the va_list to a string
+ std::string ValistToString(const char* format, va_list args) const;
+ // Write the logs marked as headers to the new log file
+ void WriteHeaderInfo();
+ std::string log_fname_; // Current active info log's file name.
+ std::string dbname_;
+ std::string db_log_dir_;
+ std::string db_absolute_path_;
+ Env* env_;
+ std::shared_ptr<Logger> logger_;
+ // current status of the logger
+ Status status_;
+ const size_t kMaxLogFileSize;
+ const size_t kLogFileTimeToRoll;
+ const size_t kKeepLogFileNum;
+ // header information
+ std::list<std::string> headers_;
+ // List of all existing info log files. Used for enforcing number of
+ // info log files.
+ // Full path is stored here. It consumes signifianctly more memory
+ // than only storing file name. Can optimize if it causes a problem.
+ std::queue<std::string> old_log_files_;
+ // to avoid frequent env->NowMicros() calls, we cached the current time
+ uint64_t cached_now;
+ uint64_t ctime_;
+ uint64_t cached_now_access_count;
+ uint64_t call_NowMicros_every_N_records_;
+ mutable port::Mutex mutex_;
+};
+#endif // !ROCKSDB_LITE
+
+// Facade to craete logger automatically
+Status CreateLoggerFromOptions(const std::string& dbname,
+ const DBOptions& options,
+ std::shared_ptr<Logger>* logger);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/auto_roll_logger_test.cc b/storage/rocksdb/rocksdb/logging/auto_roll_logger_test.cc
new file mode 100644
index 00000000..6e72fb90
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/auto_roll_logger_test.cc
@@ -0,0 +1,685 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "logging/auto_roll_logger.h"
+#include <errno.h>
+#include <sys/stat.h>
+#include <algorithm>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <thread>
+#include <vector>
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+class NoSleepEnv : public EnvWrapper {
+ public:
+ NoSleepEnv(Env* base) : EnvWrapper(base) {}
+ void SleepForMicroseconds(int micros) override {
+ fake_time_ += static_cast<uint64_t>(micros);
+ }
+
+ uint64_t NowNanos() override { return fake_time_ * 1000; }
+
+ uint64_t NowMicros() override { return fake_time_; }
+
+ private:
+ uint64_t fake_time_ = 6666666666;
+};
+} // 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() {
+#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_TRUE(system(deleteCmd.c_str()) == 0);
+ Env::Default()->CreateDir(kTestDir);
+ }
+
+ void RollLogFileBySizeTest(AutoRollLogger* logger, size_t log_max_size,
+ const std::string& log_message);
+ void RollLogFileByTimeTest(Env*, AutoRollLogger* logger, size_t time,
+ const std::string& log_message);
+ // return list of files under kTestDir that contains "LOG"
+ std::vector<std::string> GetLogFiles() {
+ std::vector<std::string> ret;
+ std::vector<std::string> 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 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::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(Env* env, 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(env->GetFileSize(kLogFile, &total_log_size));
+ 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) {
+ env->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
+ env->SleepForMicroseconds(static_cast<int>(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(Env::Default(), kTestDir, "", log_max_size, 0,
+ keep_log_file_num);
+
+ RollLogFileBySizeTest(&logger, log_max_size,
+ kSampleMessage + ":RollLogFileBySize");
+}
+
+TEST_F(AutoRollLoggerTest, RollLogFileByTime) {
+ NoSleepEnv nse(Env::Default());
+
+ 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(&nse, kTestDir, "", log_size, time, keep_log_file_num);
+ ASSERT_OK(default_env->FileExists(kLogFile));
+
+ RollLogFileByTimeTest(&nse, &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> logger;
+ ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
+ auto* auto_roll_logger = dynamic_cast<AutoRollLogger*>(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(Env::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(Env::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();
+
+ NoSleepEnv nse(Env::Default());
+ AutoRollLogger logger(&nse, 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(&nse, &logger, time,
+ kSampleMessage + ":CompositeRollByTimeAndSizeLogger");
+}
+
+#ifndef OS_WIN
+// TODO: does not build for Windows because of PosixLogger use below. Need to
+// port
+TEST_F(AutoRollLoggerTest, CreateLoggerFromOptions) {
+ DBOptions options;
+ NoSleepEnv nse(Env::Default());
+ std::shared_ptr<Logger> logger;
+
+ // Normal logger
+ ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
+ ASSERT_TRUE(dynamic_cast<PosixLogger*>(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<AutoRollLogger*>(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;
+ 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<AutoRollLogger*>(logger.get());
+ RollLogFileByTimeTest(&nse, 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<AutoRollLogger*>(logger.get());
+ RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size,
+ kSampleMessage + ":CreateLoggerFromOptions - both");
+ RollLogFileByTimeTest(&nse, 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<AutoRollLogger*>(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<std::string> 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("/dummy/db/name", options, &logger));
+ auto_roll_logger = dynamic_cast<AutoRollLogger*>(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<std::string> files = GetLogFiles();
+ ASSERT_EQ(kFileNum, files.size());
+ for (const auto& f : files) {
+ ASSERT_TRUE(f.find("dummy") != 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(Env::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(Env::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(Env::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> logger;
+
+ InitTestDb();
+ options.max_log_file_size = 1024 * 5;
+ ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
+ AutoRollLogger* auto_roll_logger =
+ dynamic_cast<AutoRollLogger*>(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) Need to reset logger during PosixLogger::Flush() to exercise a race
+ // condition case, which is executing the flush with the pinned (old)
+ // logger after auto-roll logger has cut over to a new logger.
+ // (3) PosixLogger::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"},
+ {"PosixLogger::Flush:Begin1",
+ "AutoRollLogger::ResetLogger:BeforeNewLogger"},
+ {"AutoRollLogger::ResetLogger:AfterNewLogger",
+ "PosixLogger::Flush:Begin2"}},
+ {{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin1"},
+ {"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::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(Env::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<char>(inFile),
+ std::istreambuf_iterator<char>(), '\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(Env::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<char>(inFile),
+ std::istreambuf_iterator<char>(), '\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<std::string> GetOldFileNames(const std::string& path) {
+ std::vector<std::string> 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<std::string> children;
+ Env::Default()->GetChildren(dirname, &children);
+
+ // We know that the old log files are named [path]<something>
+ // 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(Env::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> logger;
+ ASSERT_NOK(CreateLoggerFromOptions("", options, &logger));
+ ASSERT_TRUE(!logger);
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as AutoRollLogger is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/logging/env_logger.h b/storage/rocksdb/rocksdb/logging/env_logger.h
new file mode 100644
index 00000000..9fecb50c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/env_logger.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Logger implementation that uses custom Env object for logging.
+
+#pragma once
+
+#include <time.h>
+#include <atomic>
+#include <memory>
+#include "port/sys_time.h"
+
+#include "file/writable_file_writer.h"
+#include "monitoring/iostats_context_imp.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "test_util/sync_point.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class EnvLogger : public Logger {
+ public:
+ EnvLogger(std::unique_ptr<FSWritableFile>&& writable_file,
+ const std::string& fname, const EnvOptions& options, Env* env,
+ InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL)
+ : Logger(log_level),
+ file_(std::move(writable_file), fname, options, env),
+ last_flush_micros_(0),
+ env_(env),
+ flush_pending_(false) {}
+
+ ~EnvLogger() {
+ if (!closed_) {
+ closed_ = true;
+ CloseHelper();
+ }
+ }
+
+ private:
+ void FlushLocked() {
+ mutex_.AssertHeld();
+ if (flush_pending_) {
+ flush_pending_ = false;
+ file_.Flush();
+ }
+ last_flush_micros_ = env_->NowMicros();
+ }
+
+ void Flush() override {
+ TEST_SYNC_POINT("EnvLogger::Flush:Begin1");
+ TEST_SYNC_POINT("EnvLogger::Flush:Begin2");
+
+ MutexLock l(&mutex_);
+ FlushLocked();
+ }
+
+ Status CloseImpl() override { return CloseHelper(); }
+
+ Status CloseHelper() {
+ mutex_.Lock();
+ const auto close_status = file_.Close();
+ mutex_.Unlock();
+
+ if (close_status.ok()) {
+ return close_status;
+ }
+ return Status::IOError("Close of log file failed with error:" +
+ (close_status.getState()
+ ? std::string(close_status.getState())
+ : std::string()));
+ }
+
+ using Logger::Logv;
+ void Logv(const char* format, va_list ap) override {
+ IOSTATS_TIMER_GUARD(logger_nanos);
+
+ const uint64_t thread_id = env_->GetThreadID();
+
+ // We try twice: the first time with a fixed-size stack allocated buffer,
+ // and the second time with a much larger dynamically allocated buffer.
+ char buffer[500];
+ for (int iter = 0; iter < 2; iter++) {
+ char* base;
+ int bufsize;
+ if (iter == 0) {
+ bufsize = sizeof(buffer);
+ base = buffer;
+ } else {
+ bufsize = 65536;
+ base = new char[bufsize];
+ }
+ char* p = base;
+ char* limit = base + bufsize;
+
+ struct timeval now_tv;
+ gettimeofday(&now_tv, nullptr);
+ const time_t seconds = now_tv.tv_sec;
+ struct tm t;
+ localtime_r(&seconds, &t);
+ p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
+ t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+ t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec),
+ static_cast<long long unsigned int>(thread_id));
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ p += vsnprintf(p, limit - p, format, backup_ap);
+ va_end(backup_ap);
+ }
+
+ // Truncate to available space if necessary
+ if (p >= limit) {
+ if (iter == 0) {
+ continue; // Try again with larger buffer
+ } else {
+ p = limit - 1;
+ }
+ }
+
+ // Add newline if necessary
+ if (p == base || p[-1] != '\n') {
+ *p++ = '\n';
+ }
+
+ assert(p <= limit);
+ mutex_.Lock();
+ // We will ignore any error returned by Append().
+ file_.Append(Slice(base, p - base));
+ flush_pending_ = true;
+ const uint64_t now_micros = env_->NowMicros();
+ if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
+ FlushLocked();
+ }
+ mutex_.Unlock();
+ if (base != buffer) {
+ delete[] base;
+ }
+ break;
+ }
+ }
+
+ size_t GetLogFileSize() const override {
+ MutexLock l(&mutex_);
+ return file_.GetFileSize();
+ }
+
+ private:
+ WritableFileWriter file_;
+ mutable port::Mutex mutex_; // Mutex to protect the shared variables below.
+ const static uint64_t flush_every_seconds_ = 5;
+ std::atomic_uint_fast64_t last_flush_micros_;
+ Env* env_;
+ std::atomic<bool> flush_pending_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/env_logger_test.cc b/storage/rocksdb/rocksdb/logging/env_logger_test.cc
new file mode 100644
index 00000000..375e2cf5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/env_logger_test.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "env/mock_env.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> 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> 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<Logger> CreateLogger() {
+ std::shared_ptr<Logger> 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<void()> cb = [&]() {
+ WriteLogs(logger, kSampleMessage, kNumIter);
+ logger->Flush();
+ };
+
+ // Write to the logs from multiple threads.
+ std::vector<port::Thread> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/logging/event_logger.cc b/storage/rocksdb/rocksdb/logging/event_logger.cc
new file mode 100644
index 00000000..f1747ad2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/event_logger.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cassert>
+#include <cinttypes>
+#include <sstream>
+#include <string>
+
+#include "logging/logging.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+EventLoggerStream::EventLoggerStream(Logger* logger)
+ : logger_(logger),
+ log_buffer_(nullptr),
+ max_log_size_(0),
+ json_writer_(nullptr) {}
+
+EventLoggerStream::EventLoggerStream(LogBuffer* log_buffer,
+ const size_t max_log_size)
+ : logger_(nullptr),
+ log_buffer_(log_buffer),
+ max_log_size_(max_log_size),
+ json_writer_(nullptr) {}
+
+EventLoggerStream::~EventLoggerStream() {
+ if (json_writer_) {
+ json_writer_->EndObject();
+#ifdef ROCKSDB_PRINT_EVENTS_TO_STDOUT
+ printf("%s\n", json_writer_->Get().c_str());
+#else
+ if (logger_) {
+ EventLogger::Log(logger_, *json_writer_);
+ } else if (log_buffer_) {
+ assert(max_log_size_);
+ EventLogger::LogToBuffer(log_buffer_, *json_writer_, max_log_size_);
+ }
+#endif
+ delete json_writer_;
+ }
+}
+
+void EventLogger::Log(const JSONWriter& jwriter) {
+ Log(logger_, jwriter);
+}
+
+void EventLogger::Log(Logger* logger, const JSONWriter& jwriter) {
+#ifdef ROCKSDB_PRINT_EVENTS_TO_STDOUT
+ printf("%s\n", jwriter.Get().c_str());
+#else
+ ROCKSDB_NAMESPACE::Log(logger, "%s %s", Prefix(), jwriter.Get().c_str());
+#endif
+}
+
+void EventLogger::LogToBuffer(LogBuffer* log_buffer, const JSONWriter& jwriter,
+ const size_t max_log_size) {
+#ifdef ROCKSDB_PRINT_EVENTS_TO_STDOUT
+ printf("%s\n", jwriter.Get().c_str());
+#else
+ assert(log_buffer);
+ ROCKSDB_NAMESPACE::LogToBuffer(log_buffer, max_log_size, "%s %s", Prefix(),
+ jwriter.Get().c_str());
+#endif
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/event_logger.h b/storage/rocksdb/rocksdb/logging/event_logger.h
new file mode 100644
index 00000000..596eb0f5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/event_logger.h
@@ -0,0 +1,203 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <sstream>
+#include <string>
+#include <chrono>
+
+#include "logging/log_buffer.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class JSONWriter {
+ public:
+ JSONWriter() : state_(kExpectKey), first_element_(true), in_array_(false) {
+ stream_ << "{";
+ }
+
+ void AddKey(const std::string& key) {
+ assert(state_ == kExpectKey);
+ if (!first_element_) {
+ stream_ << ", ";
+ }
+ stream_ << "\"" << key << "\": ";
+ state_ = kExpectValue;
+ first_element_ = false;
+ }
+
+ void AddValue(const char* value) {
+ assert(state_ == kExpectValue || state_ == kInArray);
+ if (state_ == kInArray && !first_element_) {
+ stream_ << ", ";
+ }
+ stream_ << "\"" << value << "\"";
+ if (state_ != kInArray) {
+ state_ = kExpectKey;
+ }
+ first_element_ = false;
+ }
+
+ template <typename T>
+ void AddValue(const T& value) {
+ assert(state_ == kExpectValue || state_ == kInArray);
+ if (state_ == kInArray && !first_element_) {
+ stream_ << ", ";
+ }
+ stream_ << value;
+ if (state_ != kInArray) {
+ state_ = kExpectKey;
+ }
+ first_element_ = false;
+ }
+
+ void StartArray() {
+ assert(state_ == kExpectValue);
+ state_ = kInArray;
+ in_array_ = true;
+ stream_ << "[";
+ first_element_ = true;
+ }
+
+ void EndArray() {
+ assert(state_ == kInArray);
+ state_ = kExpectKey;
+ in_array_ = false;
+ stream_ << "]";
+ first_element_ = false;
+ }
+
+ void StartObject() {
+ assert(state_ == kExpectValue);
+ state_ = kExpectKey;
+ stream_ << "{";
+ first_element_ = true;
+ }
+
+ void EndObject() {
+ assert(state_ == kExpectKey);
+ stream_ << "}";
+ first_element_ = false;
+ }
+
+ void StartArrayedObject() {
+ assert(state_ == kInArray && in_array_);
+ state_ = kExpectValue;
+ if (!first_element_) {
+ stream_ << ", ";
+ }
+ StartObject();
+ }
+
+ void EndArrayedObject() {
+ assert(in_array_);
+ EndObject();
+ state_ = kInArray;
+ }
+
+ std::string Get() const { return stream_.str(); }
+
+ JSONWriter& operator<<(const char* val) {
+ if (state_ == kExpectKey) {
+ AddKey(val);
+ } else {
+ AddValue(val);
+ }
+ return *this;
+ }
+
+ JSONWriter& operator<<(const std::string& val) {
+ return *this << val.c_str();
+ }
+
+ template <typename T>
+ JSONWriter& operator<<(const T& val) {
+ assert(state_ != kExpectKey);
+ AddValue(val);
+ return *this;
+ }
+
+ private:
+ enum JSONWriterState {
+ kExpectKey,
+ kExpectValue,
+ kInArray,
+ kInArrayedObject,
+ };
+ JSONWriterState state_;
+ bool first_element_;
+ bool in_array_;
+ std::ostringstream stream_;
+};
+
+class EventLoggerStream {
+ public:
+ template <typename T>
+ EventLoggerStream& operator<<(const T& val) {
+ MakeStream();
+ *json_writer_ << val;
+ return *this;
+ }
+
+ void StartArray() { json_writer_->StartArray(); }
+ void EndArray() { json_writer_->EndArray(); }
+ void StartObject() { json_writer_->StartObject(); }
+ void EndObject() { json_writer_->EndObject(); }
+
+ ~EventLoggerStream();
+
+ private:
+ void MakeStream() {
+ if (!json_writer_) {
+ json_writer_ = new JSONWriter();
+ *this << "time_micros"
+ << std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+ }
+ }
+ friend class EventLogger;
+ explicit EventLoggerStream(Logger* logger);
+ explicit EventLoggerStream(LogBuffer* log_buffer, const size_t max_log_size);
+ // exactly one is non-nullptr
+ Logger* const logger_;
+ LogBuffer* const log_buffer_;
+ const size_t max_log_size_; // used only for log_buffer_
+ // ownership
+ JSONWriter* json_writer_;
+};
+
+// here is an example of the output that will show up in the LOG:
+// 2015/01/15-14:13:25.788019 1105ef000 EVENT_LOG_v1 {"time_micros":
+// 1421360005788015, "event": "table_file_creation", "file_number": 12,
+// "file_size": 1909699}
+class EventLogger {
+ public:
+ static const char* Prefix() {
+ return "EVENT_LOG_v1";
+ }
+
+ explicit EventLogger(Logger* logger) : logger_(logger) {}
+ EventLoggerStream Log() { return EventLoggerStream(logger_); }
+ EventLoggerStream LogToBuffer(LogBuffer* log_buffer) {
+ return EventLoggerStream(log_buffer, LogBuffer::kDefaultMaxLogSize);
+ }
+ EventLoggerStream LogToBuffer(LogBuffer* log_buffer,
+ const size_t max_log_size) {
+ return EventLoggerStream(log_buffer, max_log_size);
+ }
+ void Log(const JSONWriter& jwriter);
+ static void Log(Logger* logger, const JSONWriter& jwriter);
+ static void LogToBuffer(
+ LogBuffer* log_buffer, const JSONWriter& jwriter,
+ const size_t max_log_size = LogBuffer::kDefaultMaxLogSize);
+
+ private:
+ Logger* logger_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/event_logger_test.cc b/storage/rocksdb/rocksdb/logging/event_logger_test.cc
new file mode 100644
index 00000000..656d73c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/event_logger_test.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "logging/event_logger.h"
+#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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/logging/log_buffer.cc b/storage/rocksdb/rocksdb/logging/log_buffer.cc
new file mode 100644
index 00000000..242d280b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/log_buffer.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/log_buffer.h"
+
+#include "port/sys_time.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+LogBuffer::LogBuffer(const InfoLogLevel log_level,
+ Logger*info_log)
+ : log_level_(log_level), info_log_(info_log) {}
+
+void LogBuffer::AddLogToBuffer(size_t max_log_size, const char* format,
+ va_list ap) {
+ if (!info_log_ || log_level_ < info_log_->GetInfoLogLevel()) {
+ // Skip the level because of its level.
+ return;
+ }
+
+ char* alloc_mem = arena_.AllocateAligned(max_log_size);
+ BufferedLog* buffered_log = new (alloc_mem) BufferedLog();
+ char* p = buffered_log->message;
+ char* limit = alloc_mem + max_log_size - 1;
+
+ // store the time
+ gettimeofday(&(buffered_log->now_tv), nullptr);
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ auto n = vsnprintf(p, limit - p, format, backup_ap);
+#ifndef OS_WIN
+ // MS reports -1 when the buffer is too short
+ assert(n >= 0);
+#endif
+ if (n > 0) {
+ p += n;
+ } else {
+ p = limit;
+ }
+ va_end(backup_ap);
+ }
+
+ if (p > limit) {
+ p = limit;
+ }
+
+ // Add '\0' to the end
+ *p = '\0';
+
+ logs_.push_back(buffered_log);
+}
+
+void LogBuffer::FlushBufferToLog() {
+ for (BufferedLog* log : logs_) {
+ const time_t seconds = log->now_tv.tv_sec;
+ struct tm t;
+ if (localtime_r(&seconds, &t) != nullptr) {
+ Log(log_level_, info_log_,
+ "(Original Log Time %04d/%02d/%02d-%02d:%02d:%02d.%06d) %s",
+ t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
+ t.tm_sec, static_cast<int>(log->now_tv.tv_usec), log->message);
+ }
+ }
+ logs_.clear();
+}
+
+void LogToBuffer(LogBuffer* log_buffer, size_t max_log_size, const char* format,
+ ...) {
+ if (log_buffer != nullptr) {
+ va_list ap;
+ va_start(ap, format);
+ log_buffer->AddLogToBuffer(max_log_size, format, ap);
+ va_end(ap);
+ }
+}
+
+void LogToBuffer(LogBuffer* log_buffer, const char* format, ...) {
+ if (log_buffer != nullptr) {
+ va_list ap;
+ va_start(ap, format);
+ log_buffer->AddLogToBuffer(LogBuffer::kDefaultMaxLogSize, format, ap);
+ va_end(ap);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/log_buffer.h b/storage/rocksdb/rocksdb/logging/log_buffer.h
new file mode 100644
index 00000000..285256e2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/log_buffer.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <ctime>
+#include "memory/arena.h"
+#include "port/sys_time.h"
+#include "rocksdb/env.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Logger;
+
+// A class to buffer info log entries and flush them in the end.
+class LogBuffer {
+ public:
+ // log_level: the log level for all the logs
+ // info_log: logger to write the logs to
+ LogBuffer(const InfoLogLevel log_level, Logger* info_log);
+
+ // Add a log entry to the buffer. Use default max_log_size.
+ // max_log_size indicates maximize log size, including some metadata.
+ void AddLogToBuffer(size_t max_log_size, const char* format, va_list ap);
+
+ size_t IsEmpty() const { return logs_.empty(); }
+
+ // Flush all buffered log to the info log.
+ void FlushBufferToLog();
+ static const size_t kDefaultMaxLogSize = 512;
+
+ private:
+ // One log entry with its timestamp
+ struct BufferedLog {
+ struct timeval now_tv; // Timestamp of the log
+ char message[1]; // Beginning of log message
+ };
+
+ const InfoLogLevel log_level_;
+ Logger* info_log_;
+ Arena arena_;
+ autovector<BufferedLog*> logs_;
+};
+
+// Add log to the LogBuffer for a delayed info logging. It can be used when
+// we want to add some logs inside a mutex.
+// max_log_size indicates maximize log size, including some metadata.
+extern void LogToBuffer(LogBuffer* log_buffer, size_t max_log_size,
+ const char* format, ...);
+// Same as previous function, but with default max log size.
+extern void LogToBuffer(LogBuffer* log_buffer, const char* format, ...);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/logging/logging.h b/storage/rocksdb/rocksdb/logging/logging.h
new file mode 100644
index 00000000..178addcc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/logging.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Must not be included from any .h files to avoid polluting the namespace
+// with macros.
+
+#pragma once
+
+// Helper macros that include information about file name and line number
+#define ROCKS_LOG_STRINGIFY(x) #x
+#define ROCKS_LOG_TOSTRING(x) ROCKS_LOG_STRINGIFY(x)
+#define ROCKS_LOG_PREPEND_FILE_LINE(FMT) ("[%s:" ROCKS_LOG_TOSTRING(__LINE__) "] " FMT)
+
+inline const char* RocksLogShorterFileName(const char* file)
+{
+ // 15 is the length of "logging/logging.h".
+ // If the name of this file changed, please change this number, too.
+ return file + (sizeof(__FILE__) > 15 ? sizeof(__FILE__) - 15 : 0);
+}
+
+// Don't inclide file/line info in HEADER level
+#define ROCKS_LOG_HEADER(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::HEADER_LEVEL, LGR, FMT, ##__VA_ARGS__)
+
+#define ROCKS_LOG_DEBUG(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::DEBUG_LEVEL, LGR, \
+ ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_INFO(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::INFO_LEVEL, LGR, \
+ ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_WARN(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::WARN_LEVEL, LGR, \
+ ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_ERROR(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::ERROR_LEVEL, LGR, \
+ ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_FATAL(LGR, FMT, ...) \
+ ROCKSDB_NAMESPACE::Log(InfoLogLevel::FATAL_LEVEL, LGR, \
+ ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_BUFFER(LOG_BUF, FMT, ...) \
+ ROCKSDB_NAMESPACE::LogToBuffer(LOG_BUF, ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), \
+ ##__VA_ARGS__)
+
+#define ROCKS_LOG_BUFFER_MAX_SZ(LOG_BUF, MAX_LOG_SIZE, FMT, ...) \
+ ROCKSDB_NAMESPACE::LogToBuffer( \
+ LOG_BUF, MAX_LOG_SIZE, ROCKS_LOG_PREPEND_FILE_LINE(FMT), \
+ RocksLogShorterFileName(__FILE__), ##__VA_ARGS__)
+
+#define ROCKS_LOG_DETAILS(LGR, FMT, ...) \
+ ; // due to overhead by default skip such lines
+// ROCKS_LOG_DEBUG(LGR, FMT, ##__VA_ARGS__)
diff --git a/storage/rocksdb/rocksdb/logging/posix_logger.h b/storage/rocksdb/rocksdb/logging/posix_logger.h
new file mode 100644
index 00000000..fbd297c6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/logging/posix_logger.h
@@ -0,0 +1,185 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Logger implementation that can be shared by all environments
+// where enough posix functionality is available.
+
+#pragma once
+#include <algorithm>
+#include <stdio.h>
+#include "port/sys_time.h"
+#include <time.h>
+#include <fcntl.h>
+
+#ifdef OS_LINUX
+#ifndef FALLOC_FL_KEEP_SIZE
+#include <linux/falloc.h>
+#endif
+#endif
+
+#include <atomic>
+#include "env/io_posix.h"
+#include "monitoring/iostats_context_imp.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PosixLogger : public Logger {
+ private:
+ Status PosixCloseHelper() {
+ int ret;
+
+ ret = fclose(file_);
+ if (ret) {
+ return IOError("Unable to close log file", "", ret);
+ }
+ return Status::OK();
+ }
+ FILE* file_;
+ uint64_t (*gettid_)(); // Return the thread id for the current thread
+ std::atomic_size_t log_size_;
+ int fd_;
+ const static uint64_t flush_every_seconds_ = 5;
+ std::atomic_uint_fast64_t last_flush_micros_;
+ Env* env_;
+ std::atomic<bool> flush_pending_;
+
+ protected:
+ virtual Status CloseImpl() override { return PosixCloseHelper(); }
+
+ public:
+ PosixLogger(FILE* f, uint64_t (*gettid)(), Env* env,
+ const InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL)
+ : Logger(log_level),
+ file_(f),
+ gettid_(gettid),
+ log_size_(0),
+ fd_(fileno(f)),
+ last_flush_micros_(0),
+ env_(env),
+ flush_pending_(false) {}
+ virtual ~PosixLogger() {
+ if (!closed_) {
+ closed_ = true;
+ PosixCloseHelper();
+ }
+ }
+ virtual void Flush() override {
+ TEST_SYNC_POINT("PosixLogger::Flush:Begin1");
+ TEST_SYNC_POINT("PosixLogger::Flush:Begin2");
+ if (flush_pending_) {
+ flush_pending_ = false;
+ fflush(file_);
+ }
+ last_flush_micros_ = env_->NowMicros();
+ }
+
+ using Logger::Logv;
+ virtual void Logv(const char* format, va_list ap) override {
+ IOSTATS_TIMER_GUARD(logger_nanos);
+
+ const uint64_t thread_id = (*gettid_)();
+
+ // We try twice: the first time with a fixed-size stack allocated buffer,
+ // and the second time with a much larger dynamically allocated buffer.
+ char buffer[500];
+ for (int iter = 0; iter < 2; iter++) {
+ char* base;
+ int bufsize;
+ if (iter == 0) {
+ bufsize = sizeof(buffer);
+ base = buffer;
+ } else {
+ bufsize = 65536;
+ base = new char[bufsize];
+ }
+ char* p = base;
+ char* limit = base + bufsize;
+
+ struct timeval now_tv;
+ gettimeofday(&now_tv, nullptr);
+ const time_t seconds = now_tv.tv_sec;
+ struct tm t;
+ localtime_r(&seconds, &t);
+ p += snprintf(p, limit - p,
+ "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
+ t.tm_year + 1900,
+ t.tm_mon + 1,
+ t.tm_mday,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec,
+ static_cast<int>(now_tv.tv_usec),
+ static_cast<long long unsigned int>(thread_id));
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ p += vsnprintf(p, limit - p, format, backup_ap);
+ va_end(backup_ap);
+ }
+
+ // Truncate to available space if necessary
+ if (p >= limit) {
+ if (iter == 0) {
+ continue; // Try again with larger buffer
+ } else {
+ p = limit - 1;
+ }
+ }
+
+ // Add newline if necessary
+ if (p == base || p[-1] != '\n') {
+ *p++ = '\n';
+ }
+
+ assert(p <= limit);
+ const size_t write_size = p - base;
+
+#ifdef ROCKSDB_FALLOCATE_PRESENT
+ const int kDebugLogChunkSize = 128 * 1024;
+
+ // If this write would cross a boundary of kDebugLogChunkSize
+ // space, pre-allocate more space to avoid overly large
+ // allocations from filesystem allocsize options.
+ const size_t log_size = log_size_;
+ const size_t last_allocation_chunk =
+ ((kDebugLogChunkSize - 1 + log_size) / kDebugLogChunkSize);
+ const size_t desired_allocation_chunk =
+ ((kDebugLogChunkSize - 1 + log_size + write_size) /
+ kDebugLogChunkSize);
+ if (last_allocation_chunk != desired_allocation_chunk) {
+ fallocate(
+ fd_, FALLOC_FL_KEEP_SIZE, 0,
+ static_cast<off_t>(desired_allocation_chunk * kDebugLogChunkSize));
+ }
+#endif
+
+ size_t sz = fwrite(base, 1, write_size, file_);
+ flush_pending_ = true;
+ if (sz > 0) {
+ log_size_ += write_size;
+ }
+ uint64_t now_micros = static_cast<uint64_t>(now_tv.tv_sec) * 1000000 +
+ now_tv.tv_usec;
+ if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
+ Flush();
+ }
+ if (base != buffer) {
+ delete[] base;
+ }
+ break;
+ }
+ }
+ size_t GetLogFileSize() const override { return log_size_; }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/allocator.h b/storage/rocksdb/rocksdb/memory/allocator.h
new file mode 100644
index 00000000..002ad5f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/allocator.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Abstract interface for allocating memory in blocks. This memory is freed
+// when the allocator object is destroyed. See the Arena class for more info.
+
+#pragma once
+#include <cerrno>
+#include <cstddef>
+#include "rocksdb/write_buffer_manager.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Logger;
+
+class Allocator {
+ public:
+ virtual ~Allocator() {}
+
+ virtual char* Allocate(size_t bytes) = 0;
+ virtual char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
+ Logger* logger = nullptr) = 0;
+
+ virtual size_t BlockSize() const = 0;
+};
+
+class AllocTracker {
+ public:
+ explicit AllocTracker(WriteBufferManager* write_buffer_manager);
+ // No copying allowed
+ AllocTracker(const AllocTracker&) = delete;
+ void operator=(const AllocTracker&) = delete;
+
+ ~AllocTracker();
+ void Allocate(size_t bytes);
+ // Call when we're finished allocating memory so we can free it from
+ // the write buffer's limit.
+ void DoneAllocating();
+
+ void FreeMem();
+
+ bool is_freed() const { return write_buffer_manager_ == nullptr || freed_; }
+
+ private:
+ WriteBufferManager* write_buffer_manager_;
+ std::atomic<size_t> bytes_allocated_;
+ bool done_allocating_;
+ bool freed_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/arena.cc b/storage/rocksdb/rocksdb/memory/arena.cc
new file mode 100644
index 00000000..ed46459d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/arena.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sys/mman.h>
+#endif
+#include <algorithm>
+#include "logging/logging.h"
+#include "port/malloc.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// MSVC complains that it is already defined since it is static in the header.
+#ifndef _MSC_VER
+const size_t Arena::kInlineSize;
+#endif
+
+const size_t Arena::kMinBlockSize = 4096;
+const size_t Arena::kMaxBlockSize = 2u << 30;
+static const int kAlignUnit = alignof(max_align_t);
+
+size_t OptimizeBlockSize(size_t block_size) {
+ // Make sure block_size is in optimal range
+ block_size = std::max(Arena::kMinBlockSize, block_size);
+ block_size = std::min(Arena::kMaxBlockSize, block_size);
+
+ // make sure block_size is the multiple of kAlignUnit
+ if (block_size % kAlignUnit != 0) {
+ block_size = (1 + block_size / kAlignUnit) * kAlignUnit;
+ }
+
+ return block_size;
+}
+
+Arena::Arena(size_t block_size, AllocTracker* tracker, size_t huge_page_size)
+ : kBlockSize(OptimizeBlockSize(block_size)), tracker_(tracker) {
+ assert(kBlockSize >= kMinBlockSize && kBlockSize <= kMaxBlockSize &&
+ kBlockSize % kAlignUnit == 0);
+ TEST_SYNC_POINT_CALLBACK("Arena::Arena:0", const_cast<size_t*>(&kBlockSize));
+ alloc_bytes_remaining_ = sizeof(inline_block_);
+ blocks_memory_ += alloc_bytes_remaining_;
+ aligned_alloc_ptr_ = inline_block_;
+ unaligned_alloc_ptr_ = inline_block_ + alloc_bytes_remaining_;
+#ifdef MAP_HUGETLB
+ hugetlb_size_ = huge_page_size;
+ if (hugetlb_size_ && kBlockSize > hugetlb_size_) {
+ hugetlb_size_ = ((kBlockSize - 1U) / hugetlb_size_ + 1U) * hugetlb_size_;
+ }
+#else
+ (void)huge_page_size;
+#endif
+ if (tracker_ != nullptr) {
+ tracker_->Allocate(kInlineSize);
+ }
+}
+
+Arena::~Arena() {
+ if (tracker_ != nullptr) {
+ assert(tracker_->is_freed());
+ tracker_->FreeMem();
+ }
+ for (const auto& block : blocks_) {
+ delete[] block;
+ }
+
+#ifdef MAP_HUGETLB
+ for (const auto& mmap_info : huge_blocks_) {
+ if (mmap_info.addr_ == nullptr) {
+ continue;
+ }
+ auto ret = munmap(mmap_info.addr_, mmap_info.length_);
+ if (ret != 0) {
+ // TODO(sdong): Better handling
+ }
+ }
+#endif
+}
+
+char* Arena::AllocateFallback(size_t bytes, bool aligned) {
+ if (bytes > kBlockSize / 4) {
+ ++irregular_block_num;
+ // Object is more than a quarter of our block size. Allocate it separately
+ // to avoid wasting too much space in leftover bytes.
+ return AllocateNewBlock(bytes);
+ }
+
+ // We waste the remaining space in the current block.
+ size_t size = 0;
+ char* block_head = nullptr;
+#ifdef MAP_HUGETLB
+ if (hugetlb_size_) {
+ size = hugetlb_size_;
+ block_head = AllocateFromHugePage(size);
+ }
+#endif
+ if (!block_head) {
+ size = kBlockSize;
+ block_head = AllocateNewBlock(size);
+ }
+ alloc_bytes_remaining_ = size - bytes;
+
+ if (aligned) {
+ aligned_alloc_ptr_ = block_head + bytes;
+ unaligned_alloc_ptr_ = block_head + size;
+ return block_head;
+ } else {
+ aligned_alloc_ptr_ = block_head;
+ unaligned_alloc_ptr_ = block_head + size - bytes;
+ return unaligned_alloc_ptr_;
+ }
+}
+
+char* Arena::AllocateFromHugePage(size_t bytes) {
+#ifdef MAP_HUGETLB
+ if (hugetlb_size_ == 0) {
+ return nullptr;
+ }
+ // Reserve space in `huge_blocks_` before calling `mmap`.
+ // Use `emplace_back()` instead of `reserve()` to let std::vector manage its
+ // own memory and do fewer reallocations.
+ //
+ // - If `emplace_back` throws, no memory leaks because we haven't called
+ // `mmap` yet.
+ // - If `mmap` throws, no memory leaks because the vector will be cleaned up
+ // via RAII.
+ huge_blocks_.emplace_back(nullptr /* addr */, 0 /* length */);
+
+ void* addr = mmap(nullptr, bytes, (PROT_READ | PROT_WRITE),
+ (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB), -1, 0);
+
+ if (addr == MAP_FAILED) {
+ return nullptr;
+ }
+ huge_blocks_.back() = MmapInfo(addr, bytes);
+ blocks_memory_ += bytes;
+ if (tracker_ != nullptr) {
+ tracker_->Allocate(bytes);
+ }
+ return reinterpret_cast<char*>(addr);
+#else
+ (void)bytes;
+ return nullptr;
+#endif
+}
+
+char* Arena::AllocateAligned(size_t bytes, size_t huge_page_size,
+ Logger* logger) {
+ assert((kAlignUnit & (kAlignUnit - 1)) ==
+ 0); // Pointer size should be a power of 2
+
+#ifdef MAP_HUGETLB
+ if (huge_page_size > 0 && bytes > 0) {
+ // Allocate from a huge page TBL table.
+ assert(logger != nullptr); // logger need to be passed in.
+ size_t reserved_size =
+ ((bytes - 1U) / huge_page_size + 1U) * huge_page_size;
+ assert(reserved_size >= bytes);
+
+ char* addr = AllocateFromHugePage(reserved_size);
+ if (addr == nullptr) {
+ ROCKS_LOG_WARN(logger,
+ "AllocateAligned fail to allocate huge TLB pages: %s",
+ strerror(errno));
+ // fail back to malloc
+ } else {
+ return addr;
+ }
+ }
+#else
+ (void)huge_page_size;
+ (void)logger;
+#endif
+
+ size_t current_mod =
+ reinterpret_cast<uintptr_t>(aligned_alloc_ptr_) & (kAlignUnit - 1);
+ size_t slop = (current_mod == 0 ? 0 : kAlignUnit - current_mod);
+ size_t needed = bytes + slop;
+ char* result;
+ if (needed <= alloc_bytes_remaining_) {
+ result = aligned_alloc_ptr_ + slop;
+ aligned_alloc_ptr_ += needed;
+ alloc_bytes_remaining_ -= needed;
+ } else {
+ // AllocateFallback always returns aligned memory
+ result = AllocateFallback(bytes, true /* aligned */);
+ }
+ assert((reinterpret_cast<uintptr_t>(result) & (kAlignUnit - 1)) == 0);
+ return result;
+}
+
+char* Arena::AllocateNewBlock(size_t block_bytes) {
+ // Reserve space in `blocks_` before allocating memory via new.
+ // Use `emplace_back()` instead of `reserve()` to let std::vector manage its
+ // own memory and do fewer reallocations.
+ //
+ // - If `emplace_back` throws, no memory leaks because we haven't called `new`
+ // yet.
+ // - If `new` throws, no memory leaks because the vector will be cleaned up
+ // via RAII.
+ blocks_.emplace_back(nullptr);
+
+ char* block = new char[block_bytes];
+ size_t allocated_size;
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ allocated_size = malloc_usable_size(block);
+#ifndef NDEBUG
+ // It's hard to predict what malloc_usable_size() returns.
+ // A callback can allow users to change the costed size.
+ std::pair<size_t*, size_t*> pair(&allocated_size, &block_bytes);
+ TEST_SYNC_POINT_CALLBACK("Arena::AllocateNewBlock:0", &pair);
+#endif // NDEBUG
+#else
+ allocated_size = block_bytes;
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ blocks_memory_ += allocated_size;
+ if (tracker_ != nullptr) {
+ tracker_->Allocate(allocated_size);
+ }
+ blocks_.back() = block;
+ return block;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/arena.h b/storage/rocksdb/rocksdb/memory/arena.h
new file mode 100644
index 00000000..a7ee4c6a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/arena.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+// Arena is an implementation of Allocator class. For a request of small size,
+// it allocates a block with pre-defined block size. For a request of big
+// size, it uses malloc to directly get the requested size.
+
+#pragma once
+#ifndef OS_WIN
+#include <sys/mman.h>
+#endif
+#include <assert.h>
+#include <stdint.h>
+#include <cerrno>
+#include <cstddef>
+#include <vector>
+#include "memory/allocator.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena : public Allocator {
+ public:
+ // No copying allowed
+ Arena(const Arena&) = delete;
+ void operator=(const Arena&) = delete;
+
+ static const size_t kInlineSize = 2048;
+ static const size_t kMinBlockSize;
+ static const size_t kMaxBlockSize;
+
+ // huge_page_size: if 0, don't use huge page TLB. If > 0 (should set to the
+ // supported hugepage size of the system), block allocation will try huge
+ // page TLB first. If allocation fails, will fall back to normal case.
+ explicit Arena(size_t block_size = kMinBlockSize,
+ AllocTracker* tracker = nullptr, size_t huge_page_size = 0);
+ ~Arena();
+
+ char* Allocate(size_t bytes) override;
+
+ // huge_page_size: if >0, will try to allocate from huage page TLB.
+ // The argument will be the size of the page size for huge page TLB. Bytes
+ // will be rounded up to multiple of the page size to allocate through mmap
+ // anonymous option with huge page on. The extra space allocated will be
+ // wasted. If allocation fails, will fall back to normal case. To enable it,
+ // need to reserve huge pages for it to be allocated, like:
+ // sysctl -w vm.nr_hugepages=20
+ // See linux doc Documentation/vm/hugetlbpage.txt for details.
+ // huge page allocation can fail. In this case it will fail back to
+ // normal cases. The messages will be logged to logger. So when calling with
+ // huge_page_tlb_size > 0, we highly recommend a logger is passed in.
+ // Otherwise, the error message will be printed out to stderr directly.
+ char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
+ Logger* logger = nullptr) override;
+
+ // Returns an estimate of the total memory usage of data allocated
+ // by the arena (exclude the space allocated but not yet used for future
+ // allocations).
+ size_t ApproximateMemoryUsage() const {
+ return blocks_memory_ + blocks_.capacity() * sizeof(char*) -
+ alloc_bytes_remaining_;
+ }
+
+ size_t MemoryAllocatedBytes() const { return blocks_memory_; }
+
+ size_t AllocatedAndUnused() const { return alloc_bytes_remaining_; }
+
+ // If an allocation is too big, we'll allocate an irregular block with the
+ // same size of that allocation.
+ size_t IrregularBlockNum() const { return irregular_block_num; }
+
+ size_t BlockSize() const override { return kBlockSize; }
+
+ bool IsInInlineBlock() const {
+ return blocks_.empty();
+ }
+
+ private:
+ char inline_block_[kInlineSize] __attribute__((__aligned__(alignof(max_align_t))));
+ // Number of bytes allocated in one block
+ const size_t kBlockSize;
+ // Array of new[] allocated memory blocks
+ typedef std::vector<char*> Blocks;
+ Blocks blocks_;
+
+ struct MmapInfo {
+ void* addr_;
+ size_t length_;
+
+ MmapInfo(void* addr, size_t length) : addr_(addr), length_(length) {}
+ };
+ std::vector<MmapInfo> huge_blocks_;
+ size_t irregular_block_num = 0;
+
+ // Stats for current active block.
+ // For each block, we allocate aligned memory chucks from one end and
+ // allocate unaligned memory chucks from the other end. Otherwise the
+ // memory waste for alignment will be higher if we allocate both types of
+ // memory from one direction.
+ char* unaligned_alloc_ptr_ = nullptr;
+ char* aligned_alloc_ptr_ = nullptr;
+ // How many bytes left in currently active block?
+ size_t alloc_bytes_remaining_ = 0;
+
+#ifdef MAP_HUGETLB
+ size_t hugetlb_size_ = 0;
+#endif // MAP_HUGETLB
+ char* AllocateFromHugePage(size_t bytes);
+ char* AllocateFallback(size_t bytes, bool aligned);
+ char* AllocateNewBlock(size_t block_bytes);
+
+ // Bytes of memory in blocks allocated so far
+ size_t blocks_memory_ = 0;
+ AllocTracker* tracker_;
+};
+
+inline char* Arena::Allocate(size_t bytes) {
+ // The semantics of what to return are a bit messy if we allow
+ // 0-byte allocations, so we disallow them here (we don't need
+ // them for our internal use).
+ assert(bytes > 0);
+ if (bytes <= alloc_bytes_remaining_) {
+ unaligned_alloc_ptr_ -= bytes;
+ alloc_bytes_remaining_ -= bytes;
+ return unaligned_alloc_ptr_;
+ }
+ return AllocateFallback(bytes, false /* unaligned */);
+}
+
+// check and adjust the block_size so that the return value is
+// 1. in the range of [kMinBlockSize, kMaxBlockSize].
+// 2. the multiple of align unit.
+extern size_t OptimizeBlockSize(size_t block_size);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/arena_test.cc b/storage/rocksdb/rocksdb/memory/arena_test.cc
new file mode 100644
index 00000000..3fa6483f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/arena_test.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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"
+#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<std::pair<size_t, char*>> 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);
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/memory/concurrent_arena.cc b/storage/rocksdb/rocksdb/memory/concurrent_arena.cc
new file mode 100644
index 00000000..3333f94d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/concurrent_arena.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/concurrent_arena.h"
+#include <thread>
+#include "port/port.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+__thread size_t ConcurrentArena::tls_cpuid = 0;
+#endif
+
+namespace {
+// If the shard block size is too large, in the worst case, every core
+// allocates a block without populate it. If the shared block size is
+// 1MB, 64 cores will quickly allocate 64MB, and may quickly trigger a
+// flush. Cap the size instead.
+const size_t kMaxShardBlockSize = size_t{128 * 1024};
+} // namespace
+
+ConcurrentArena::ConcurrentArena(size_t block_size, AllocTracker* tracker,
+ size_t huge_page_size)
+ : shard_block_size_(std::min(kMaxShardBlockSize, block_size / 8)),
+ shards_(),
+ arena_(block_size, tracker, huge_page_size) {
+ Fixup();
+}
+
+ConcurrentArena::Shard* ConcurrentArena::Repick() {
+ auto shard_and_index = shards_.AccessElementAndIndex();
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+ // even if we are cpu 0, use a non-zero tls_cpuid so we can tell we
+ // have repicked
+ tls_cpuid = shard_and_index.second | shards_.Size();
+#endif
+ return shard_and_index.first;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/concurrent_arena.h b/storage/rocksdb/rocksdb/memory/concurrent_arena.h
new file mode 100644
index 00000000..dad27a30
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/concurrent_arena.h
@@ -0,0 +1,215 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <memory>
+#include <utility>
+#include "memory/allocator.h"
+#include "memory/arena.h"
+#include "port/likely.h"
+#include "util/core_local.h"
+#include "util/mutexlock.h"
+#include "util/thread_local.h"
+
+// Only generate field unused warning for padding array, or build under
+// GCC 4.8.1 will fail.
+#ifdef __clang__
+#define ROCKSDB_FIELD_UNUSED __attribute__((__unused__))
+#else
+#define ROCKSDB_FIELD_UNUSED
+#endif // __clang__
+
+namespace ROCKSDB_NAMESPACE {
+
+class Logger;
+
+// ConcurrentArena wraps an Arena. It makes it thread safe using a fast
+// inlined spinlock, and adds small per-core allocation caches to avoid
+// contention for small allocations. To avoid any memory waste from the
+// per-core shards, they are kept small, they are lazily instantiated
+// only if ConcurrentArena actually notices concurrent use, and they
+// adjust their size so that there is no fragmentation waste when the
+// shard blocks are allocated from the underlying main arena.
+class ConcurrentArena : public Allocator {
+ public:
+ // block_size and huge_page_size are the same as for Arena (and are
+ // in fact just passed to the constructor of arena_. The core-local
+ // shards compute their shard_block_size as a fraction of block_size
+ // that varies according to the hardware concurrency level.
+ explicit ConcurrentArena(size_t block_size = Arena::kMinBlockSize,
+ AllocTracker* tracker = nullptr,
+ size_t huge_page_size = 0);
+
+ char* Allocate(size_t bytes) override {
+ return AllocateImpl(bytes, false /*force_arena*/,
+ [=]() { return arena_.Allocate(bytes); });
+ }
+
+ char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
+ Logger* logger = nullptr) override {
+ size_t rounded_up = ((bytes - 1) | (sizeof(void*) - 1)) + 1;
+ assert(rounded_up >= bytes && rounded_up < bytes + sizeof(void*) &&
+ (rounded_up % sizeof(void*)) == 0);
+
+ return AllocateImpl(rounded_up, huge_page_size != 0 /*force_arena*/, [=]() {
+ return arena_.AllocateAligned(rounded_up, huge_page_size, logger);
+ });
+ }
+
+ size_t ApproximateMemoryUsage() const {
+ std::unique_lock<SpinMutex> lock(arena_mutex_, std::defer_lock);
+ lock.lock();
+ return arena_.ApproximateMemoryUsage() - ShardAllocatedAndUnused();
+ }
+
+ size_t MemoryAllocatedBytes() const {
+ return memory_allocated_bytes_.load(std::memory_order_relaxed);
+ }
+
+ size_t AllocatedAndUnused() const {
+ return arena_allocated_and_unused_.load(std::memory_order_relaxed) +
+ ShardAllocatedAndUnused();
+ }
+
+ size_t IrregularBlockNum() const {
+ return irregular_block_num_.load(std::memory_order_relaxed);
+ }
+
+ size_t BlockSize() const override { return arena_.BlockSize(); }
+
+ private:
+ struct Shard {
+ char padding[40] ROCKSDB_FIELD_UNUSED;
+ mutable SpinMutex mutex;
+ char* free_begin_;
+ std::atomic<size_t> allocated_and_unused_;
+
+ Shard() : free_begin_(nullptr), allocated_and_unused_(0) {}
+ };
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+ static __thread size_t tls_cpuid;
+#else
+ enum ZeroFirstEnum : size_t { tls_cpuid = 0 };
+#endif
+
+ char padding0[56] ROCKSDB_FIELD_UNUSED;
+
+ size_t shard_block_size_;
+
+ CoreLocalArray<Shard> shards_;
+
+ Arena arena_;
+ mutable SpinMutex arena_mutex_;
+ std::atomic<size_t> arena_allocated_and_unused_;
+ std::atomic<size_t> memory_allocated_bytes_;
+ std::atomic<size_t> irregular_block_num_;
+
+ char padding1[56] ROCKSDB_FIELD_UNUSED;
+
+ Shard* Repick();
+
+ size_t ShardAllocatedAndUnused() const {
+ size_t total = 0;
+ for (size_t i = 0; i < shards_.Size(); ++i) {
+ total += shards_.AccessAtCore(i)->allocated_and_unused_.load(
+ std::memory_order_relaxed);
+ }
+ return total;
+ }
+
+ template <typename Func>
+ char* AllocateImpl(size_t bytes, bool force_arena, const Func& func) {
+ size_t cpu;
+
+ // Go directly to the arena if the allocation is too large, or if
+ // we've never needed to Repick() and the arena mutex is available
+ // with no waiting. This keeps the fragmentation penalty of
+ // concurrency zero unless it might actually confer an advantage.
+ std::unique_lock<SpinMutex> arena_lock(arena_mutex_, std::defer_lock);
+ if (bytes > shard_block_size_ / 4 || force_arena ||
+ ((cpu = tls_cpuid) == 0 &&
+ !shards_.AccessAtCore(0)->allocated_and_unused_.load(
+ std::memory_order_relaxed) &&
+ arena_lock.try_lock())) {
+ if (!arena_lock.owns_lock()) {
+ arena_lock.lock();
+ }
+ auto rv = func();
+ Fixup();
+ return rv;
+ }
+
+ // pick a shard from which to allocate
+ Shard* s = shards_.AccessAtCore(cpu & (shards_.Size() - 1));
+ if (!s->mutex.try_lock()) {
+ s = Repick();
+ s->mutex.lock();
+ }
+ std::unique_lock<SpinMutex> lock(s->mutex, std::adopt_lock);
+
+ size_t avail = s->allocated_and_unused_.load(std::memory_order_relaxed);
+ if (avail < bytes) {
+ // reload
+ std::lock_guard<SpinMutex> reload_lock(arena_mutex_);
+
+ // If the arena's current block is within a factor of 2 of the right
+ // size, we adjust our request to avoid arena waste.
+ auto exact = arena_allocated_and_unused_.load(std::memory_order_relaxed);
+ assert(exact == arena_.AllocatedAndUnused());
+
+ if (exact >= bytes && arena_.IsInInlineBlock()) {
+ // If we haven't exhausted arena's inline block yet, allocate from arena
+ // directly. This ensures that we'll do the first few small allocations
+ // without allocating any blocks.
+ // In particular this prevents empty memtables from using
+ // disproportionately large amount of memory: a memtable allocates on
+ // the order of 1 KB of memory when created; we wouldn't want to
+ // allocate a full arena block (typically a few megabytes) for that,
+ // especially if there are thousands of empty memtables.
+ auto rv = func();
+ Fixup();
+ return rv;
+ }
+
+ avail = exact >= shard_block_size_ / 2 && exact < shard_block_size_ * 2
+ ? exact
+ : shard_block_size_;
+ s->free_begin_ = arena_.AllocateAligned(avail);
+ Fixup();
+ }
+ s->allocated_and_unused_.store(avail - bytes, std::memory_order_relaxed);
+
+ char* rv;
+ if ((bytes % sizeof(void*)) == 0) {
+ // aligned allocation from the beginning
+ rv = s->free_begin_;
+ s->free_begin_ += bytes;
+ } else {
+ // unaligned from the end
+ rv = s->free_begin_ + avail - bytes;
+ }
+ return rv;
+ }
+
+ void Fixup() {
+ arena_allocated_and_unused_.store(arena_.AllocatedAndUnused(),
+ std::memory_order_relaxed);
+ memory_allocated_bytes_.store(arena_.MemoryAllocatedBytes(),
+ std::memory_order_relaxed);
+ irregular_block_num_.store(arena_.IrregularBlockNum(),
+ std::memory_order_relaxed);
+ }
+
+ ConcurrentArena(const ConcurrentArena&) = delete;
+ ConcurrentArena& operator=(const ConcurrentArena&) = delete;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.cc b/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.cc
new file mode 100644
index 00000000..980b08b9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "memory/jemalloc_nodump_allocator.h"
+
+#include <string>
+#include <thread>
+
+#include "port/likely.h"
+#include "port/port.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
+
+std::atomic<extent_alloc_t*> JemallocNodumpAllocator::original_alloc_{nullptr};
+
+JemallocNodumpAllocator::JemallocNodumpAllocator(
+ JemallocAllocatorOptions& options,
+ std::unique_ptr<extent_hooks_t>&& arena_hooks, unsigned arena_index)
+ : options_(options),
+ arena_hooks_(std::move(arena_hooks)),
+ arena_index_(arena_index),
+ tcache_(&JemallocNodumpAllocator::DestroyThreadSpecificCache) {}
+
+int JemallocNodumpAllocator::GetThreadSpecificCache(size_t size) {
+ // We always enable tcache. The only corner case is when there are a ton of
+ // threads accessing with low frequency, then it could consume a lot of
+ // memory (may reach # threads * ~1MB) without bringing too much benefit.
+ if (options_.limit_tcache_size && (size <= options_.tcache_size_lower_bound ||
+ size > options_.tcache_size_upper_bound)) {
+ return MALLOCX_TCACHE_NONE;
+ }
+ unsigned* tcache_index = reinterpret_cast<unsigned*>(tcache_.Get());
+ if (UNLIKELY(tcache_index == nullptr)) {
+ // Instantiate tcache.
+ tcache_index = new unsigned(0);
+ size_t tcache_index_size = sizeof(unsigned);
+ int ret =
+ mallctl("tcache.create", tcache_index, &tcache_index_size, nullptr, 0);
+ if (ret != 0) {
+ // No good way to expose the error. Silently disable tcache.
+ delete tcache_index;
+ return MALLOCX_TCACHE_NONE;
+ }
+ tcache_.Reset(static_cast<void*>(tcache_index));
+ }
+ return MALLOCX_TCACHE(*tcache_index);
+}
+
+void* JemallocNodumpAllocator::Allocate(size_t size) {
+ int tcache_flag = GetThreadSpecificCache(size);
+ return mallocx(size, MALLOCX_ARENA(arena_index_) | tcache_flag);
+}
+
+void JemallocNodumpAllocator::Deallocate(void* p) {
+ // Obtain tcache.
+ size_t size = 0;
+ if (options_.limit_tcache_size) {
+ size = malloc_usable_size(p);
+ }
+ int tcache_flag = GetThreadSpecificCache(size);
+ // No need to pass arena index to dallocx(). Jemalloc will find arena index
+ // from its own metadata.
+ dallocx(p, tcache_flag);
+}
+
+void* JemallocNodumpAllocator::Alloc(extent_hooks_t* extent, void* new_addr,
+ size_t size, size_t alignment, bool* zero,
+ bool* commit, unsigned arena_ind) {
+ extent_alloc_t* original_alloc =
+ original_alloc_.load(std::memory_order_relaxed);
+ assert(original_alloc != nullptr);
+ void* result = original_alloc(extent, new_addr, size, alignment, zero, commit,
+ arena_ind);
+ if (result != nullptr) {
+ int ret = madvise(result, size, MADV_DONTDUMP);
+ if (ret != 0) {
+ fprintf(
+ stderr,
+ "JemallocNodumpAllocator failed to set MADV_DONTDUMP, error code: %d",
+ ret);
+ assert(false);
+ }
+ }
+ return result;
+}
+
+Status JemallocNodumpAllocator::DestroyArena(unsigned arena_index) {
+ assert(arena_index != 0);
+ std::string key = "arena." + ToString(arena_index) + ".destroy";
+ int ret = mallctl(key.c_str(), nullptr, 0, nullptr, 0);
+ if (ret != 0) {
+ return Status::Incomplete("Failed to destroy jemalloc arena, error code: " +
+ ToString(ret));
+ }
+ return Status::OK();
+}
+
+void JemallocNodumpAllocator::DestroyThreadSpecificCache(void* ptr) {
+ assert(ptr != nullptr);
+ unsigned* tcache_index = static_cast<unsigned*>(ptr);
+ size_t tcache_index_size = sizeof(unsigned);
+ int ret __attribute__((__unused__)) =
+ mallctl("tcache.destroy", nullptr, 0, tcache_index, tcache_index_size);
+ // Silently ignore error.
+ assert(ret == 0);
+ delete tcache_index;
+}
+
+JemallocNodumpAllocator::~JemallocNodumpAllocator() {
+ // Destroy tcache before destroying arena.
+ autovector<void*> tcache_list;
+ tcache_.Scrape(&tcache_list, nullptr);
+ for (void* tcache_index : tcache_list) {
+ DestroyThreadSpecificCache(tcache_index);
+ }
+ // Destroy arena. Silently ignore error.
+ Status s __attribute__((__unused__)) = DestroyArena(arena_index_);
+ assert(s.ok());
+}
+
+size_t JemallocNodumpAllocator::UsableSize(void* p,
+ size_t /*allocation_size*/) const {
+ return malloc_usable_size(static_cast<void*>(p));
+}
+#endif // ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
+
+Status NewJemallocNodumpAllocator(
+ JemallocAllocatorOptions& options,
+ std::shared_ptr<MemoryAllocator>* memory_allocator) {
+ *memory_allocator = nullptr;
+ Status unsupported = Status::NotSupported(
+ "JemallocNodumpAllocator only available with jemalloc version >= 5 "
+ "and MADV_DONTDUMP is available.");
+#ifndef ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
+ (void)options;
+ return unsupported;
+#else
+ if (!HasJemalloc()) {
+ return unsupported;
+ }
+ if (memory_allocator == nullptr) {
+ return Status::InvalidArgument("memory_allocator must be non-null.");
+ }
+ if (options.limit_tcache_size &&
+ options.tcache_size_lower_bound >= options.tcache_size_upper_bound) {
+ return Status::InvalidArgument(
+ "tcache_size_lower_bound larger or equal to tcache_size_upper_bound.");
+ }
+
+ // Create arena.
+ unsigned arena_index = 0;
+ size_t arena_index_size = sizeof(arena_index);
+ int ret =
+ mallctl("arenas.create", &arena_index, &arena_index_size, nullptr, 0);
+ if (ret != 0) {
+ return Status::Incomplete("Failed to create jemalloc arena, error code: " +
+ ToString(ret));
+ }
+ assert(arena_index != 0);
+
+ // Read existing hooks.
+ std::string key = "arena." + ToString(arena_index) + ".extent_hooks";
+ extent_hooks_t* hooks;
+ size_t hooks_size = sizeof(hooks);
+ ret = mallctl(key.c_str(), &hooks, &hooks_size, nullptr, 0);
+ if (ret != 0) {
+ JemallocNodumpAllocator::DestroyArena(arena_index);
+ return Status::Incomplete("Failed to read existing hooks, error code: " +
+ ToString(ret));
+ }
+
+ // Store existing alloc.
+ extent_alloc_t* original_alloc = hooks->alloc;
+ extent_alloc_t* expected = nullptr;
+ bool success =
+ JemallocNodumpAllocator::original_alloc_.compare_exchange_strong(
+ expected, original_alloc);
+ if (!success && original_alloc != expected) {
+ JemallocNodumpAllocator::DestroyArena(arena_index);
+ return Status::Incomplete("Original alloc conflict.");
+ }
+
+ // Set the custom hook.
+ std::unique_ptr<extent_hooks_t> new_hooks(new extent_hooks_t(*hooks));
+ new_hooks->alloc = &JemallocNodumpAllocator::Alloc;
+ extent_hooks_t* hooks_ptr = new_hooks.get();
+ ret = mallctl(key.c_str(), nullptr, nullptr, &hooks_ptr, sizeof(hooks_ptr));
+ if (ret != 0) {
+ JemallocNodumpAllocator::DestroyArena(arena_index);
+ return Status::Incomplete("Failed to set custom hook, error code: " +
+ ToString(ret));
+ }
+
+ // Create cache allocator.
+ memory_allocator->reset(
+ new JemallocNodumpAllocator(options, std::move(new_hooks), arena_index));
+ return Status::OK();
+#endif // ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.h b/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.h
new file mode 100644
index 00000000..7bc25550
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/jemalloc_nodump_allocator.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <vector>
+
+#include "port/jemalloc_helper.h"
+#include "port/port.h"
+#include "rocksdb/memory_allocator.h"
+#include "util/thread_local.h"
+
+#if defined(ROCKSDB_JEMALLOC) && defined(ROCKSDB_PLATFORM_POSIX)
+
+#include <sys/mman.h>
+
+#if (JEMALLOC_VERSION_MAJOR >= 5) && defined(MADV_DONTDUMP)
+#define ROCKSDB_JEMALLOC_NODUMP_ALLOCATOR
+
+namespace ROCKSDB_NAMESPACE {
+
+class JemallocNodumpAllocator : public MemoryAllocator {
+ public:
+ JemallocNodumpAllocator(JemallocAllocatorOptions& options,
+ std::unique_ptr<extent_hooks_t>&& arena_hooks,
+ unsigned arena_index);
+ ~JemallocNodumpAllocator();
+
+ const char* Name() const override { return "JemallocNodumpAllocator"; }
+ void* Allocate(size_t size) override;
+ void Deallocate(void* p) override;
+ size_t UsableSize(void* p, size_t allocation_size) const override;
+
+ private:
+ friend Status NewJemallocNodumpAllocator(
+ JemallocAllocatorOptions& options,
+ std::shared_ptr<MemoryAllocator>* memory_allocator);
+
+ // Custom alloc hook to replace jemalloc default alloc.
+ static void* Alloc(extent_hooks_t* extent, void* new_addr, size_t size,
+ size_t alignment, bool* zero, bool* commit,
+ unsigned arena_ind);
+
+ // Destroy arena on destruction of the allocator, or on failure.
+ static Status DestroyArena(unsigned arena_index);
+
+ // Destroy tcache on destruction of the allocator, or thread exit.
+ static void DestroyThreadSpecificCache(void* ptr);
+
+ // Get or create tcache. Return flag suitable to use with `mallocx`:
+ // either MALLOCX_TCACHE_NONE or MALLOCX_TCACHE(tc).
+ int GetThreadSpecificCache(size_t size);
+
+ // A function pointer to jemalloc default alloc. Use atomic to make sure
+ // NewJemallocNodumpAllocator is thread-safe.
+ //
+ // Hack: original_alloc_ needs to be static for Alloc() to access it.
+ // alloc needs to be static to pass to jemalloc as function pointer.
+ static std::atomic<extent_alloc_t*> original_alloc_;
+
+ const JemallocAllocatorOptions options_;
+
+ // Custom hooks has to outlive corresponding arena.
+ const std::unique_ptr<extent_hooks_t> arena_hooks_;
+
+ // Arena index.
+ const unsigned arena_index_;
+
+ // Hold thread-local tcache index.
+ ThreadLocalPtr tcache_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // (JEMALLOC_VERSION_MAJOR >= 5) && MADV_DONTDUMP
+#endif // ROCKSDB_JEMALLOC && ROCKSDB_PLATFORM_POSIX
diff --git a/storage/rocksdb/rocksdb/memory/memory_allocator.h b/storage/rocksdb/rocksdb/memory/memory_allocator.h
new file mode 100644
index 00000000..f1a54865
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/memory_allocator.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/memory_allocator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct CustomDeleter {
+ CustomDeleter(MemoryAllocator* a = nullptr) : allocator(a) {}
+
+ void operator()(char* ptr) const {
+ if (allocator) {
+ allocator->Deallocate(reinterpret_cast<void*>(ptr));
+ } else {
+ delete[] ptr;
+ }
+ }
+
+ MemoryAllocator* allocator;
+};
+
+using CacheAllocationPtr = std::unique_ptr<char[], CustomDeleter>;
+
+inline CacheAllocationPtr AllocateBlock(size_t size,
+ MemoryAllocator* allocator) {
+ if (allocator) {
+ auto block = reinterpret_cast<char*>(allocator->Allocate(size));
+ return CacheAllocationPtr(block, allocator);
+ }
+ return CacheAllocationPtr(new char[size]);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memory/memory_usage.h b/storage/rocksdb/rocksdb/memory/memory_usage.h
new file mode 100644
index 00000000..15e8b87c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memory/memory_usage.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <unordered_map>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Helper methods to estimate memroy usage by std containers.
+
+template <class Key, class Value, class Hash>
+size_t ApproximateMemoryUsage(
+ const std::unordered_map<Key, Value, Hash>& umap) {
+ typedef std::unordered_map<Key, Value, Hash> Map;
+ return sizeof(umap) +
+ // Size of all items plus a next pointer for each item.
+ (sizeof(typename Map::value_type) + sizeof(void*)) * umap.size() +
+ // Size of hash buckets.
+ umap.bucket_count() * sizeof(void*);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/alloc_tracker.cc b/storage/rocksdb/rocksdb/memtable/alloc_tracker.cc
new file mode 100644
index 00000000..fe213434
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/alloc_tracker.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include "memory/allocator.h"
+#include "memory/arena.h"
+#include "rocksdb/write_buffer_manager.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+AllocTracker::AllocTracker(WriteBufferManager* write_buffer_manager)
+ : write_buffer_manager_(write_buffer_manager),
+ bytes_allocated_(0),
+ done_allocating_(false),
+ freed_(false) {}
+
+AllocTracker::~AllocTracker() { FreeMem(); }
+
+void AllocTracker::Allocate(size_t bytes) {
+ assert(write_buffer_manager_ != nullptr);
+ if (write_buffer_manager_->enabled() ||
+ write_buffer_manager_->cost_to_cache()) {
+ bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
+ write_buffer_manager_->ReserveMem(bytes);
+ }
+}
+
+void AllocTracker::DoneAllocating() {
+ if (write_buffer_manager_ != nullptr && !done_allocating_) {
+ if (write_buffer_manager_->enabled() ||
+ write_buffer_manager_->cost_to_cache()) {
+ write_buffer_manager_->ScheduleFreeMem(
+ bytes_allocated_.load(std::memory_order_relaxed));
+ } else {
+ assert(bytes_allocated_.load(std::memory_order_relaxed) == 0);
+ }
+ done_allocating_ = true;
+ }
+}
+
+void AllocTracker::FreeMem() {
+ if (!done_allocating_) {
+ DoneAllocating();
+ }
+ if (write_buffer_manager_ != nullptr && !freed_) {
+ if (write_buffer_manager_->enabled() ||
+ write_buffer_manager_->cost_to_cache()) {
+ write_buffer_manager_->FreeMem(
+ bytes_allocated_.load(std::memory_order_relaxed));
+ } else {
+ assert(bytes_allocated_.load(std::memory_order_relaxed) == 0);
+ }
+ freed_ = true;
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.cc b/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.cc
new file mode 100644
index 00000000..3698812c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.cc
@@ -0,0 +1,844 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "memtable/hash_linklist_rep.h"
+
+#include <algorithm>
+#include <atomic>
+#include "db/memtable.h"
+#include "memory/arena.h"
+#include "memtable/skiplist.h"
+#include "monitoring/histogram.h"
+#include "port/port.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+
+typedef const char* Key;
+typedef SkipList<Key, const MemTableRep::KeyComparator&> MemtableSkipList;
+typedef std::atomic<void*> Pointer;
+
+// A data structure used as the header of a link list of a hash bucket.
+struct BucketHeader {
+ Pointer next;
+ std::atomic<uint32_t> num_entries;
+
+ explicit BucketHeader(void* n, uint32_t count)
+ : next(n), num_entries(count) {}
+
+ bool IsSkipListBucket() {
+ return next.load(std::memory_order_relaxed) == this;
+ }
+
+ uint32_t GetNumEntries() const {
+ return num_entries.load(std::memory_order_relaxed);
+ }
+
+ // REQUIRES: called from single-threaded Insert()
+ void IncNumEntries() {
+ // Only one thread can do write at one time. No need to do atomic
+ // incremental. Update it with relaxed load and store.
+ num_entries.store(GetNumEntries() + 1, std::memory_order_relaxed);
+ }
+};
+
+// A data structure used as the header of a skip list of a hash bucket.
+struct SkipListBucketHeader {
+ BucketHeader Counting_header;
+ MemtableSkipList skip_list;
+
+ explicit SkipListBucketHeader(const MemTableRep::KeyComparator& cmp,
+ Allocator* allocator, uint32_t count)
+ : Counting_header(this, // Pointing to itself to indicate header type.
+ count),
+ skip_list(cmp, allocator) {}
+};
+
+struct Node {
+ // Accessors/mutators for links. Wrapped in methods so we can
+ // add the appropriate barriers as necessary.
+ Node* Next() {
+ // Use an 'acquire load' so that we observe a fully initialized
+ // version of the returned Node.
+ return next_.load(std::memory_order_acquire);
+ }
+ void SetNext(Node* x) {
+ // Use a 'release store' so that anybody who reads through this
+ // pointer observes a fully initialized version of the inserted node.
+ next_.store(x, std::memory_order_release);
+ }
+ // No-barrier variants that can be safely used in a few locations.
+ Node* NoBarrier_Next() {
+ return next_.load(std::memory_order_relaxed);
+ }
+
+ void NoBarrier_SetNext(Node* x) { next_.store(x, std::memory_order_relaxed); }
+
+ // Needed for placement new below which is fine
+ Node() {}
+
+ private:
+ std::atomic<Node*> next_;
+
+ // Prohibit copying due to the below
+ Node(const Node&) = delete;
+ Node& operator=(const Node&) = delete;
+
+ public:
+ char key[1];
+};
+
+// Memory structure of the mem table:
+// It is a hash table, each bucket points to one entry, a linked list or a
+// skip list. In order to track total number of records in a bucket to determine
+// whether should switch to skip list, a header is added just to indicate
+// number of entries in the bucket.
+//
+//
+// +-----> NULL Case 1. Empty bucket
+// |
+// |
+// | +---> +-------+
+// | | | Next +--> NULL
+// | | +-------+
+// +-----+ | | | | Case 2. One Entry in bucket.
+// | +-+ | | Data | next pointer points to
+// +-----+ | | | NULL. All other cases
+// | | | | | next pointer is not NULL.
+// +-----+ | +-------+
+// | +---+
+// +-----+ +-> +-------+ +> +-------+ +-> +-------+
+// | | | | Next +--+ | Next +--+ | Next +-->NULL
+// +-----+ | +-------+ +-------+ +-------+
+// | +-----+ | Count | | | | |
+// +-----+ +-------+ | Data | | Data |
+// | | | | | |
+// +-----+ Case 3. | | | |
+// | | A header +-------+ +-------+
+// +-----+ points to
+// | | a linked list. Count indicates total number
+// +-----+ of rows in this bucket.
+// | |
+// +-----+ +-> +-------+ <--+
+// | | | | Next +----+
+// +-----+ | +-------+ Case 4. A header points to a skip
+// | +----+ | Count | list and next pointer points to
+// +-----+ +-------+ itself, to distinguish case 3 or 4.
+// | | | | Count still is kept to indicates total
+// +-----+ | Skip +--> of entries in the bucket for debugging
+// | | | List | Data purpose.
+// | | | +-->
+// +-----+ | |
+// | | +-------+
+// +-----+
+//
+// We don't have data race when changing cases because:
+// (1) When changing from case 2->3, we create a new bucket header, put the
+// single node there first without changing the original node, and do a
+// release store when changing the bucket pointer. In that case, a reader
+// who sees a stale value of the bucket pointer will read this node, while
+// a reader sees the correct value because of the release store.
+// (2) When changing case 3->4, a new header is created with skip list points
+// to the data, before doing an acquire store to change the bucket pointer.
+// The old header and nodes are never changed, so any reader sees any
+// of those existing pointers will guarantee to be able to iterate to the
+// end of the linked list.
+// (3) Header's next pointer in case 3 might change, but they are never equal
+// to itself, so no matter a reader sees any stale or newer value, it will
+// be able to correctly distinguish case 3 and 4.
+//
+// The reason that we use case 2 is we want to make the format to be efficient
+// when the utilization of buckets is relatively low. If we use case 3 for
+// single entry bucket, we will need to waste 12 bytes for every entry,
+// which can be significant decrease of memory utilization.
+class HashLinkListRep : public MemTableRep {
+ public:
+ HashLinkListRep(const MemTableRep::KeyComparator& compare,
+ Allocator* allocator, const SliceTransform* transform,
+ size_t bucket_size, uint32_t threshold_use_skiplist,
+ size_t huge_page_tlb_size, Logger* logger,
+ int bucket_entries_logging_threshold,
+ bool if_log_bucket_dist_when_flash);
+
+ KeyHandle Allocate(const size_t len, char** buf) override;
+
+ void Insert(KeyHandle handle) override;
+
+ bool Contains(const char* key) const override;
+
+ size_t ApproximateMemoryUsage() override;
+
+ void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) override;
+
+ ~HashLinkListRep() override;
+
+ MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override;
+
+ MemTableRep::Iterator* GetDynamicPrefixIterator(
+ Arena* arena = nullptr) override;
+
+ private:
+ friend class DynamicIterator;
+
+ size_t bucket_size_;
+
+ // Maps slices (which are transformed user keys) to buckets of keys sharing
+ // the same transform.
+ Pointer* buckets_;
+
+ const uint32_t threshold_use_skiplist_;
+
+ // The user-supplied transform whose domain is the user keys.
+ const SliceTransform* transform_;
+
+ const MemTableRep::KeyComparator& compare_;
+
+ Logger* logger_;
+ int bucket_entries_logging_threshold_;
+ bool if_log_bucket_dist_when_flash_;
+
+ bool LinkListContains(Node* head, const Slice& key) const;
+
+ SkipListBucketHeader* GetSkipListBucketHeader(Pointer* first_next_pointer)
+ const;
+
+ Node* GetLinkListFirstNode(Pointer* first_next_pointer) const;
+
+ Slice GetPrefix(const Slice& internal_key) const {
+ return transform_->Transform(ExtractUserKey(internal_key));
+ }
+
+ size_t GetHash(const Slice& slice) const {
+ return fastrange64(GetSliceNPHash64(slice), bucket_size_);
+ }
+
+ Pointer* GetBucket(size_t i) const {
+ return static_cast<Pointer*>(buckets_[i].load(std::memory_order_acquire));
+ }
+
+ Pointer* GetBucket(const Slice& slice) const {
+ return GetBucket(GetHash(slice));
+ }
+
+ bool Equal(const Slice& a, const Key& b) const {
+ return (compare_(b, a) == 0);
+ }
+
+ bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
+
+ bool KeyIsAfterNode(const Slice& internal_key, const Node* n) const {
+ // nullptr n is considered infinite
+ return (n != nullptr) && (compare_(n->key, internal_key) < 0);
+ }
+
+ bool KeyIsAfterNode(const Key& key, const Node* n) const {
+ // nullptr n is considered infinite
+ return (n != nullptr) && (compare_(n->key, key) < 0);
+ }
+
+ bool KeyIsAfterOrAtNode(const Slice& internal_key, const Node* n) const {
+ // nullptr n is considered infinite
+ return (n != nullptr) && (compare_(n->key, internal_key) <= 0);
+ }
+
+ bool KeyIsAfterOrAtNode(const Key& key, const Node* n) const {
+ // nullptr n is considered infinite
+ return (n != nullptr) && (compare_(n->key, key) <= 0);
+ }
+
+ Node* FindGreaterOrEqualInBucket(Node* head, const Slice& key) const;
+ Node* FindLessOrEqualInBucket(Node* head, const Slice& key) const;
+
+ class FullListIterator : public MemTableRep::Iterator {
+ public:
+ explicit FullListIterator(MemtableSkipList* list, Allocator* allocator)
+ : iter_(list), full_list_(list), allocator_(allocator) {}
+
+ ~FullListIterator() override {}
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const override { return iter_.Valid(); }
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const override {
+ assert(Valid());
+ return iter_.key();
+ }
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next() override {
+ assert(Valid());
+ iter_.Next();
+ }
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev() override {
+ assert(Valid());
+ iter_.Prev();
+ }
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& internal_key, const char* memtable_key) override {
+ const char* encoded_key =
+ (memtable_key != nullptr) ?
+ memtable_key : EncodeKey(&tmp_, internal_key);
+ iter_.Seek(encoded_key);
+ }
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const Slice& internal_key,
+ const char* memtable_key) override {
+ const char* encoded_key = (memtable_key != nullptr)
+ ? memtable_key
+ : EncodeKey(&tmp_, internal_key);
+ iter_.SeekForPrev(encoded_key);
+ }
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToFirst() override { iter_.SeekToFirst(); }
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToLast() override { iter_.SeekToLast(); }
+
+ private:
+ MemtableSkipList::Iterator iter_;
+ // To destruct with the iterator.
+ std::unique_ptr<MemtableSkipList> full_list_;
+ std::unique_ptr<Allocator> allocator_;
+ std::string tmp_; // For passing to EncodeKey
+ };
+
+ class LinkListIterator : public MemTableRep::Iterator {
+ public:
+ explicit LinkListIterator(const HashLinkListRep* const hash_link_list_rep,
+ Node* head)
+ : hash_link_list_rep_(hash_link_list_rep),
+ head_(head),
+ node_(nullptr) {}
+
+ ~LinkListIterator() override {}
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const override { return node_ != nullptr; }
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const override {
+ assert(Valid());
+ return node_->key;
+ }
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next() override {
+ assert(Valid());
+ node_ = node_->Next();
+ }
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev() override {
+ // Prefix iterator does not support total order.
+ // We simply set the iterator to invalid state
+ Reset(nullptr);
+ }
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& internal_key,
+ const char* /*memtable_key*/) override {
+ node_ = hash_link_list_rep_->FindGreaterOrEqualInBucket(head_,
+ internal_key);
+ }
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const Slice& /*internal_key*/,
+ const char* /*memtable_key*/) override {
+ // Since we do not support Prev()
+ // We simply do not support SeekForPrev
+ Reset(nullptr);
+ }
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToFirst() override {
+ // Prefix iterator does not support total order.
+ // We simply set the iterator to invalid state
+ Reset(nullptr);
+ }
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToLast() override {
+ // Prefix iterator does not support total order.
+ // We simply set the iterator to invalid state
+ Reset(nullptr);
+ }
+
+ protected:
+ void Reset(Node* head) {
+ head_ = head;
+ node_ = nullptr;
+ }
+ private:
+ friend class HashLinkListRep;
+ const HashLinkListRep* const hash_link_list_rep_;
+ Node* head_;
+ Node* node_;
+
+ virtual void SeekToHead() {
+ node_ = head_;
+ }
+ };
+
+ class DynamicIterator : public HashLinkListRep::LinkListIterator {
+ public:
+ explicit DynamicIterator(HashLinkListRep& memtable_rep)
+ : HashLinkListRep::LinkListIterator(&memtable_rep, nullptr),
+ memtable_rep_(memtable_rep) {}
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& k, const char* memtable_key) override {
+ auto transformed = memtable_rep_.GetPrefix(k);
+ auto* bucket = memtable_rep_.GetBucket(transformed);
+
+ SkipListBucketHeader* skip_list_header =
+ memtable_rep_.GetSkipListBucketHeader(bucket);
+ if (skip_list_header != nullptr) {
+ // The bucket is organized as a skip list
+ if (!skip_list_iter_) {
+ skip_list_iter_.reset(
+ new MemtableSkipList::Iterator(&skip_list_header->skip_list));
+ } else {
+ skip_list_iter_->SetList(&skip_list_header->skip_list);
+ }
+ if (memtable_key != nullptr) {
+ skip_list_iter_->Seek(memtable_key);
+ } else {
+ IterKey encoded_key;
+ encoded_key.EncodeLengthPrefixedKey(k);
+ skip_list_iter_->Seek(encoded_key.GetUserKey().data());
+ }
+ } else {
+ // The bucket is organized as a linked list
+ skip_list_iter_.reset();
+ Reset(memtable_rep_.GetLinkListFirstNode(bucket));
+ HashLinkListRep::LinkListIterator::Seek(k, memtable_key);
+ }
+ }
+
+ bool Valid() const override {
+ if (skip_list_iter_) {
+ return skip_list_iter_->Valid();
+ }
+ return HashLinkListRep::LinkListIterator::Valid();
+ }
+
+ const char* key() const override {
+ if (skip_list_iter_) {
+ return skip_list_iter_->key();
+ }
+ return HashLinkListRep::LinkListIterator::key();
+ }
+
+ void Next() override {
+ if (skip_list_iter_) {
+ skip_list_iter_->Next();
+ } else {
+ HashLinkListRep::LinkListIterator::Next();
+ }
+ }
+
+ private:
+ // the underlying memtable
+ const HashLinkListRep& memtable_rep_;
+ std::unique_ptr<MemtableSkipList::Iterator> skip_list_iter_;
+ };
+
+ class EmptyIterator : public MemTableRep::Iterator {
+ // This is used when there wasn't a bucket. It is cheaper than
+ // instantiating an empty bucket over which to iterate.
+ public:
+ EmptyIterator() { }
+ bool Valid() const override { return false; }
+ const char* key() const override {
+ assert(false);
+ return nullptr;
+ }
+ void Next() override {}
+ void Prev() override {}
+ void Seek(const Slice& /*user_key*/,
+ const char* /*memtable_key*/) override {}
+ void SeekForPrev(const Slice& /*user_key*/,
+ const char* /*memtable_key*/) override {}
+ void SeekToFirst() override {}
+ void SeekToLast() override {}
+
+ private:
+ };
+};
+
+HashLinkListRep::HashLinkListRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, size_t bucket_size,
+ uint32_t threshold_use_skiplist, size_t huge_page_tlb_size, Logger* logger,
+ int bucket_entries_logging_threshold, bool if_log_bucket_dist_when_flash)
+ : MemTableRep(allocator),
+ bucket_size_(bucket_size),
+ // Threshold to use skip list doesn't make sense if less than 3, so we
+ // force it to be minimum of 3 to simplify implementation.
+ threshold_use_skiplist_(std::max(threshold_use_skiplist, 3U)),
+ transform_(transform),
+ compare_(compare),
+ logger_(logger),
+ bucket_entries_logging_threshold_(bucket_entries_logging_threshold),
+ if_log_bucket_dist_when_flash_(if_log_bucket_dist_when_flash) {
+ char* mem = allocator_->AllocateAligned(sizeof(Pointer) * bucket_size,
+ huge_page_tlb_size, logger);
+
+ buckets_ = new (mem) Pointer[bucket_size];
+
+ for (size_t i = 0; i < bucket_size_; ++i) {
+ buckets_[i].store(nullptr, std::memory_order_relaxed);
+ }
+}
+
+HashLinkListRep::~HashLinkListRep() {
+}
+
+KeyHandle HashLinkListRep::Allocate(const size_t len, char** buf) {
+ char* mem = allocator_->AllocateAligned(sizeof(Node) + len);
+ Node* x = new (mem) Node();
+ *buf = x->key;
+ return static_cast<void*>(x);
+}
+
+SkipListBucketHeader* HashLinkListRep::GetSkipListBucketHeader(
+ Pointer* first_next_pointer) const {
+ if (first_next_pointer == nullptr) {
+ return nullptr;
+ }
+ if (first_next_pointer->load(std::memory_order_relaxed) == nullptr) {
+ // Single entry bucket
+ return nullptr;
+ }
+ // Counting header
+ BucketHeader* header = reinterpret_cast<BucketHeader*>(first_next_pointer);
+ if (header->IsSkipListBucket()) {
+ assert(header->GetNumEntries() > threshold_use_skiplist_);
+ auto* skip_list_bucket_header =
+ reinterpret_cast<SkipListBucketHeader*>(header);
+ assert(skip_list_bucket_header->Counting_header.next.load(
+ std::memory_order_relaxed) == header);
+ return skip_list_bucket_header;
+ }
+ assert(header->GetNumEntries() <= threshold_use_skiplist_);
+ return nullptr;
+}
+
+Node* HashLinkListRep::GetLinkListFirstNode(Pointer* first_next_pointer) const {
+ if (first_next_pointer == nullptr) {
+ return nullptr;
+ }
+ if (first_next_pointer->load(std::memory_order_relaxed) == nullptr) {
+ // Single entry bucket
+ return reinterpret_cast<Node*>(first_next_pointer);
+ }
+ // Counting header
+ BucketHeader* header = reinterpret_cast<BucketHeader*>(first_next_pointer);
+ if (!header->IsSkipListBucket()) {
+ assert(header->GetNumEntries() <= threshold_use_skiplist_);
+ return reinterpret_cast<Node*>(
+ header->next.load(std::memory_order_acquire));
+ }
+ assert(header->GetNumEntries() > threshold_use_skiplist_);
+ return nullptr;
+}
+
+void HashLinkListRep::Insert(KeyHandle handle) {
+ Node* x = static_cast<Node*>(handle);
+ assert(!Contains(x->key));
+ Slice internal_key = GetLengthPrefixedSlice(x->key);
+ auto transformed = GetPrefix(internal_key);
+ auto& bucket = buckets_[GetHash(transformed)];
+ Pointer* first_next_pointer =
+ static_cast<Pointer*>(bucket.load(std::memory_order_relaxed));
+
+ if (first_next_pointer == nullptr) {
+ // Case 1. empty bucket
+ // NoBarrier_SetNext() suffices since we will add a barrier when
+ // we publish a pointer to "x" in prev[i].
+ x->NoBarrier_SetNext(nullptr);
+ bucket.store(x, std::memory_order_release);
+ return;
+ }
+
+ BucketHeader* header = nullptr;
+ if (first_next_pointer->load(std::memory_order_relaxed) == nullptr) {
+ // Case 2. only one entry in the bucket
+ // Need to convert to a Counting bucket and turn to case 4.
+ Node* first = reinterpret_cast<Node*>(first_next_pointer);
+ // Need to add a bucket header.
+ // We have to first convert it to a bucket with header before inserting
+ // the new node. Otherwise, we might need to change next pointer of first.
+ // In that case, a reader might sees the next pointer is NULL and wrongly
+ // think the node is a bucket header.
+ auto* mem = allocator_->AllocateAligned(sizeof(BucketHeader));
+ header = new (mem) BucketHeader(first, 1);
+ bucket.store(header, std::memory_order_release);
+ } else {
+ header = reinterpret_cast<BucketHeader*>(first_next_pointer);
+ if (header->IsSkipListBucket()) {
+ // Case 4. Bucket is already a skip list
+ assert(header->GetNumEntries() > threshold_use_skiplist_);
+ auto* skip_list_bucket_header =
+ reinterpret_cast<SkipListBucketHeader*>(header);
+ // Only one thread can execute Insert() at one time. No need to do atomic
+ // incremental.
+ skip_list_bucket_header->Counting_header.IncNumEntries();
+ skip_list_bucket_header->skip_list.Insert(x->key);
+ return;
+ }
+ }
+
+ if (bucket_entries_logging_threshold_ > 0 &&
+ header->GetNumEntries() ==
+ static_cast<uint32_t>(bucket_entries_logging_threshold_)) {
+ Info(logger_, "HashLinkedList bucket %" ROCKSDB_PRIszt
+ " has more than %d "
+ "entries. Key to insert: %s",
+ GetHash(transformed), header->GetNumEntries(),
+ GetLengthPrefixedSlice(x->key).ToString(true).c_str());
+ }
+
+ if (header->GetNumEntries() == threshold_use_skiplist_) {
+ // Case 3. number of entries reaches the threshold so need to convert to
+ // skip list.
+ LinkListIterator bucket_iter(
+ this, reinterpret_cast<Node*>(
+ first_next_pointer->load(std::memory_order_relaxed)));
+ auto mem = allocator_->AllocateAligned(sizeof(SkipListBucketHeader));
+ SkipListBucketHeader* new_skip_list_header = new (mem)
+ SkipListBucketHeader(compare_, allocator_, header->GetNumEntries() + 1);
+ auto& skip_list = new_skip_list_header->skip_list;
+
+ // Add all current entries to the skip list
+ for (bucket_iter.SeekToHead(); bucket_iter.Valid(); bucket_iter.Next()) {
+ skip_list.Insert(bucket_iter.key());
+ }
+
+ // insert the new entry
+ skip_list.Insert(x->key);
+ // Set the bucket
+ bucket.store(new_skip_list_header, std::memory_order_release);
+ } else {
+ // Case 5. Need to insert to the sorted linked list without changing the
+ // header.
+ Node* first =
+ reinterpret_cast<Node*>(header->next.load(std::memory_order_relaxed));
+ assert(first != nullptr);
+ // Advance counter unless the bucket needs to be advanced to skip list.
+ // In that case, we need to make sure the previous count never exceeds
+ // threshold_use_skiplist_ to avoid readers to cast to wrong format.
+ header->IncNumEntries();
+
+ Node* cur = first;
+ Node* prev = nullptr;
+ while (true) {
+ if (cur == nullptr) {
+ break;
+ }
+ Node* next = cur->Next();
+ // Make sure the lists are sorted.
+ // If x points to head_ or next points nullptr, it is trivially satisfied.
+ assert((cur == first) || (next == nullptr) ||
+ KeyIsAfterNode(next->key, cur));
+ if (KeyIsAfterNode(internal_key, cur)) {
+ // Keep searching in this list
+ prev = cur;
+ cur = next;
+ } else {
+ break;
+ }
+ }
+
+ // Our data structure does not allow duplicate insertion
+ assert(cur == nullptr || !Equal(x->key, cur->key));
+
+ // NoBarrier_SetNext() suffices since we will add a barrier when
+ // we publish a pointer to "x" in prev[i].
+ x->NoBarrier_SetNext(cur);
+
+ if (prev) {
+ prev->SetNext(x);
+ } else {
+ header->next.store(static_cast<void*>(x), std::memory_order_release);
+ }
+ }
+}
+
+bool HashLinkListRep::Contains(const char* key) const {
+ Slice internal_key = GetLengthPrefixedSlice(key);
+
+ auto transformed = GetPrefix(internal_key);
+ auto bucket = GetBucket(transformed);
+ if (bucket == nullptr) {
+ return false;
+ }
+
+ SkipListBucketHeader* skip_list_header = GetSkipListBucketHeader(bucket);
+ if (skip_list_header != nullptr) {
+ return skip_list_header->skip_list.Contains(key);
+ } else {
+ return LinkListContains(GetLinkListFirstNode(bucket), internal_key);
+ }
+}
+
+size_t HashLinkListRep::ApproximateMemoryUsage() {
+ // Memory is always allocated from the allocator.
+ return 0;
+}
+
+void HashLinkListRep::Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) {
+ auto transformed = transform_->Transform(k.user_key());
+ auto bucket = GetBucket(transformed);
+
+ auto* skip_list_header = GetSkipListBucketHeader(bucket);
+ if (skip_list_header != nullptr) {
+ // Is a skip list
+ MemtableSkipList::Iterator iter(&skip_list_header->skip_list);
+ for (iter.Seek(k.memtable_key().data());
+ iter.Valid() && callback_func(callback_args, iter.key());
+ iter.Next()) {
+ }
+ } else {
+ auto* link_list_head = GetLinkListFirstNode(bucket);
+ if (link_list_head != nullptr) {
+ LinkListIterator iter(this, link_list_head);
+ for (iter.Seek(k.internal_key(), nullptr);
+ iter.Valid() && callback_func(callback_args, iter.key());
+ iter.Next()) {
+ }
+ }
+ }
+}
+
+MemTableRep::Iterator* HashLinkListRep::GetIterator(Arena* alloc_arena) {
+ // allocate a new arena of similar size to the one currently in use
+ Arena* new_arena = new Arena(allocator_->BlockSize());
+ auto list = new MemtableSkipList(compare_, new_arena);
+ HistogramImpl keys_per_bucket_hist;
+
+ for (size_t i = 0; i < bucket_size_; ++i) {
+ int count = 0;
+ auto* bucket = GetBucket(i);
+ if (bucket != nullptr) {
+ auto* skip_list_header = GetSkipListBucketHeader(bucket);
+ if (skip_list_header != nullptr) {
+ // Is a skip list
+ MemtableSkipList::Iterator itr(&skip_list_header->skip_list);
+ for (itr.SeekToFirst(); itr.Valid(); itr.Next()) {
+ list->Insert(itr.key());
+ count++;
+ }
+ } else {
+ auto* link_list_head = GetLinkListFirstNode(bucket);
+ if (link_list_head != nullptr) {
+ LinkListIterator itr(this, link_list_head);
+ for (itr.SeekToHead(); itr.Valid(); itr.Next()) {
+ list->Insert(itr.key());
+ count++;
+ }
+ }
+ }
+ }
+ if (if_log_bucket_dist_when_flash_) {
+ keys_per_bucket_hist.Add(count);
+ }
+ }
+ if (if_log_bucket_dist_when_flash_ && logger_ != nullptr) {
+ Info(logger_, "hashLinkedList Entry distribution among buckets: %s",
+ keys_per_bucket_hist.ToString().c_str());
+ }
+
+ if (alloc_arena == nullptr) {
+ return new FullListIterator(list, new_arena);
+ } else {
+ auto mem = alloc_arena->AllocateAligned(sizeof(FullListIterator));
+ return new (mem) FullListIterator(list, new_arena);
+ }
+}
+
+MemTableRep::Iterator* HashLinkListRep::GetDynamicPrefixIterator(
+ Arena* alloc_arena) {
+ if (alloc_arena == nullptr) {
+ return new DynamicIterator(*this);
+ } else {
+ auto mem = alloc_arena->AllocateAligned(sizeof(DynamicIterator));
+ return new (mem) DynamicIterator(*this);
+ }
+}
+
+bool HashLinkListRep::LinkListContains(Node* head,
+ const Slice& user_key) const {
+ Node* x = FindGreaterOrEqualInBucket(head, user_key);
+ return (x != nullptr && Equal(user_key, x->key));
+}
+
+Node* HashLinkListRep::FindGreaterOrEqualInBucket(Node* head,
+ const Slice& key) const {
+ Node* x = head;
+ while (true) {
+ if (x == nullptr) {
+ return x;
+ }
+ Node* next = x->Next();
+ // Make sure the lists are sorted.
+ // If x points to head_ or next points nullptr, it is trivially satisfied.
+ assert((x == head) || (next == nullptr) || KeyIsAfterNode(next->key, x));
+ if (KeyIsAfterNode(key, x)) {
+ // Keep searching in this list
+ x = next;
+ } else {
+ break;
+ }
+ }
+ return x;
+}
+
+} // anon namespace
+
+MemTableRep* HashLinkListRepFactory::CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* logger) {
+ return new HashLinkListRep(compare, allocator, transform, bucket_count_,
+ threshold_use_skiplist_, huge_page_tlb_size_,
+ logger, bucket_entries_logging_threshold_,
+ if_log_bucket_dist_when_flash_);
+}
+
+MemTableRepFactory* NewHashLinkListRepFactory(
+ size_t bucket_count, size_t huge_page_tlb_size,
+ int bucket_entries_logging_threshold, bool if_log_bucket_dist_when_flash,
+ uint32_t threshold_use_skiplist) {
+ return new HashLinkListRepFactory(
+ bucket_count, threshold_use_skiplist, huge_page_tlb_size,
+ bucket_entries_logging_threshold, if_log_bucket_dist_when_flash);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.h b/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.h
new file mode 100644
index 00000000..7e719552
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/hash_linklist_rep.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/memtablerep.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class HashLinkListRepFactory : public MemTableRepFactory {
+ public:
+ explicit HashLinkListRepFactory(size_t bucket_count,
+ uint32_t threshold_use_skiplist,
+ size_t huge_page_tlb_size,
+ int bucket_entries_logging_threshold,
+ bool if_log_bucket_dist_when_flash)
+ : bucket_count_(bucket_count),
+ threshold_use_skiplist_(threshold_use_skiplist),
+ huge_page_tlb_size_(huge_page_tlb_size),
+ bucket_entries_logging_threshold_(bucket_entries_logging_threshold),
+ if_log_bucket_dist_when_flash_(if_log_bucket_dist_when_flash) {}
+
+ virtual ~HashLinkListRepFactory() {}
+
+ using MemTableRepFactory::CreateMemTableRep;
+ virtual MemTableRep* CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* logger) override;
+
+ virtual const char* Name() const override {
+ return "HashLinkListRepFactory";
+ }
+
+ private:
+ const size_t bucket_count_;
+ const uint32_t threshold_use_skiplist_;
+ const size_t huge_page_tlb_size_;
+ int bucket_entries_logging_threshold_;
+ bool if_log_bucket_dist_when_flash_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.cc b/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.cc
new file mode 100644
index 00000000..67a2a6c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.cc
@@ -0,0 +1,349 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "memtable/hash_skiplist_rep.h"
+
+#include <atomic>
+
+#include "db/memtable.h"
+#include "memory/arena.h"
+#include "memtable/skiplist.h"
+#include "port/port.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "util/murmurhash.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+
+class HashSkipListRep : public MemTableRep {
+ public:
+ HashSkipListRep(const MemTableRep::KeyComparator& compare,
+ Allocator* allocator, const SliceTransform* transform,
+ size_t bucket_size, int32_t skiplist_height,
+ int32_t skiplist_branching_factor);
+
+ void Insert(KeyHandle handle) override;
+
+ bool Contains(const char* key) const override;
+
+ size_t ApproximateMemoryUsage() override;
+
+ void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) override;
+
+ ~HashSkipListRep() override;
+
+ MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override;
+
+ MemTableRep::Iterator* GetDynamicPrefixIterator(
+ Arena* arena = nullptr) override;
+
+ private:
+ friend class DynamicIterator;
+ typedef SkipList<const char*, const MemTableRep::KeyComparator&> Bucket;
+
+ size_t bucket_size_;
+
+ const int32_t skiplist_height_;
+ const int32_t skiplist_branching_factor_;
+
+ // Maps slices (which are transformed user keys) to buckets of keys sharing
+ // the same transform.
+ std::atomic<Bucket*>* buckets_;
+
+ // The user-supplied transform whose domain is the user keys.
+ const SliceTransform* transform_;
+
+ const MemTableRep::KeyComparator& compare_;
+ // immutable after construction
+ Allocator* const allocator_;
+
+ inline size_t GetHash(const Slice& slice) const {
+ return MurmurHash(slice.data(), static_cast<int>(slice.size()), 0) %
+ bucket_size_;
+ }
+ inline Bucket* GetBucket(size_t i) const {
+ return buckets_[i].load(std::memory_order_acquire);
+ }
+ inline Bucket* GetBucket(const Slice& slice) const {
+ return GetBucket(GetHash(slice));
+ }
+ // Get a bucket from buckets_. If the bucket hasn't been initialized yet,
+ // initialize it before returning.
+ Bucket* GetInitializedBucket(const Slice& transformed);
+
+ class Iterator : public MemTableRep::Iterator {
+ public:
+ explicit Iterator(Bucket* list, bool own_list = true,
+ Arena* arena = nullptr)
+ : list_(list), iter_(list), own_list_(own_list), arena_(arena) {}
+
+ ~Iterator() override {
+ // if we own the list, we should also delete it
+ if (own_list_) {
+ assert(list_ != nullptr);
+ delete list_;
+ }
+ }
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const override { return list_ != nullptr && iter_.Valid(); }
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const override {
+ assert(Valid());
+ return iter_.key();
+ }
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next() override {
+ assert(Valid());
+ iter_.Next();
+ }
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev() override {
+ assert(Valid());
+ iter_.Prev();
+ }
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& internal_key, const char* memtable_key) override {
+ if (list_ != nullptr) {
+ const char* encoded_key =
+ (memtable_key != nullptr) ?
+ memtable_key : EncodeKey(&tmp_, internal_key);
+ iter_.Seek(encoded_key);
+ }
+ }
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const Slice& /*internal_key*/,
+ const char* /*memtable_key*/) override {
+ // not supported
+ assert(false);
+ }
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToFirst() override {
+ if (list_ != nullptr) {
+ iter_.SeekToFirst();
+ }
+ }
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToLast() override {
+ if (list_ != nullptr) {
+ iter_.SeekToLast();
+ }
+ }
+
+ protected:
+ void Reset(Bucket* list) {
+ if (own_list_) {
+ assert(list_ != nullptr);
+ delete list_;
+ }
+ list_ = list;
+ iter_.SetList(list);
+ own_list_ = false;
+ }
+ private:
+ // if list_ is nullptr, we should NEVER call any methods on iter_
+ // if list_ is nullptr, this Iterator is not Valid()
+ Bucket* list_;
+ Bucket::Iterator iter_;
+ // here we track if we own list_. If we own it, we are also
+ // responsible for it's cleaning. This is a poor man's std::shared_ptr
+ bool own_list_;
+ std::unique_ptr<Arena> arena_;
+ std::string tmp_; // For passing to EncodeKey
+ };
+
+ class DynamicIterator : public HashSkipListRep::Iterator {
+ public:
+ explicit DynamicIterator(const HashSkipListRep& memtable_rep)
+ : HashSkipListRep::Iterator(nullptr, false),
+ memtable_rep_(memtable_rep) {}
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& k, const char* memtable_key) override {
+ auto transformed = memtable_rep_.transform_->Transform(ExtractUserKey(k));
+ Reset(memtable_rep_.GetBucket(transformed));
+ HashSkipListRep::Iterator::Seek(k, memtable_key);
+ }
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToFirst() override {
+ // Prefix iterator does not support total order.
+ // We simply set the iterator to invalid state
+ Reset(nullptr);
+ }
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToLast() override {
+ // Prefix iterator does not support total order.
+ // We simply set the iterator to invalid state
+ Reset(nullptr);
+ }
+
+ private:
+ // the underlying memtable
+ const HashSkipListRep& memtable_rep_;
+ };
+
+ class EmptyIterator : public MemTableRep::Iterator {
+ // This is used when there wasn't a bucket. It is cheaper than
+ // instantiating an empty bucket over which to iterate.
+ public:
+ EmptyIterator() { }
+ bool Valid() const override { return false; }
+ const char* key() const override {
+ assert(false);
+ return nullptr;
+ }
+ void Next() override {}
+ void Prev() override {}
+ void Seek(const Slice& /*internal_key*/,
+ const char* /*memtable_key*/) override {}
+ void SeekForPrev(const Slice& /*internal_key*/,
+ const char* /*memtable_key*/) override {}
+ void SeekToFirst() override {}
+ void SeekToLast() override {}
+
+ private:
+ };
+};
+
+HashSkipListRep::HashSkipListRep(const MemTableRep::KeyComparator& compare,
+ Allocator* allocator,
+ const SliceTransform* transform,
+ size_t bucket_size, int32_t skiplist_height,
+ int32_t skiplist_branching_factor)
+ : MemTableRep(allocator),
+ bucket_size_(bucket_size),
+ skiplist_height_(skiplist_height),
+ skiplist_branching_factor_(skiplist_branching_factor),
+ transform_(transform),
+ compare_(compare),
+ allocator_(allocator) {
+ auto mem = allocator->AllocateAligned(
+ sizeof(std::atomic<void*>) * bucket_size);
+ buckets_ = new (mem) std::atomic<Bucket*>[bucket_size];
+
+ for (size_t i = 0; i < bucket_size_; ++i) {
+ buckets_[i].store(nullptr, std::memory_order_relaxed);
+ }
+}
+
+HashSkipListRep::~HashSkipListRep() {
+}
+
+HashSkipListRep::Bucket* HashSkipListRep::GetInitializedBucket(
+ const Slice& transformed) {
+ size_t hash = GetHash(transformed);
+ auto bucket = GetBucket(hash);
+ if (bucket == nullptr) {
+ auto addr = allocator_->AllocateAligned(sizeof(Bucket));
+ bucket = new (addr) Bucket(compare_, allocator_, skiplist_height_,
+ skiplist_branching_factor_);
+ buckets_[hash].store(bucket, std::memory_order_release);
+ }
+ return bucket;
+}
+
+void HashSkipListRep::Insert(KeyHandle handle) {
+ auto* key = static_cast<char*>(handle);
+ assert(!Contains(key));
+ auto transformed = transform_->Transform(UserKey(key));
+ auto bucket = GetInitializedBucket(transformed);
+ bucket->Insert(key);
+}
+
+bool HashSkipListRep::Contains(const char* key) const {
+ auto transformed = transform_->Transform(UserKey(key));
+ auto bucket = GetBucket(transformed);
+ if (bucket == nullptr) {
+ return false;
+ }
+ return bucket->Contains(key);
+}
+
+size_t HashSkipListRep::ApproximateMemoryUsage() {
+ return 0;
+}
+
+void HashSkipListRep::Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) {
+ auto transformed = transform_->Transform(k.user_key());
+ auto bucket = GetBucket(transformed);
+ if (bucket != nullptr) {
+ Bucket::Iterator iter(bucket);
+ for (iter.Seek(k.memtable_key().data());
+ iter.Valid() && callback_func(callback_args, iter.key());
+ iter.Next()) {
+ }
+ }
+}
+
+MemTableRep::Iterator* HashSkipListRep::GetIterator(Arena* arena) {
+ // allocate a new arena of similar size to the one currently in use
+ Arena* new_arena = new Arena(allocator_->BlockSize());
+ auto list = new Bucket(compare_, new_arena);
+ for (size_t i = 0; i < bucket_size_; ++i) {
+ auto bucket = GetBucket(i);
+ if (bucket != nullptr) {
+ Bucket::Iterator itr(bucket);
+ for (itr.SeekToFirst(); itr.Valid(); itr.Next()) {
+ list->Insert(itr.key());
+ }
+ }
+ }
+ if (arena == nullptr) {
+ return new Iterator(list, true, new_arena);
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(Iterator));
+ return new (mem) Iterator(list, true, new_arena);
+ }
+}
+
+MemTableRep::Iterator* HashSkipListRep::GetDynamicPrefixIterator(Arena* arena) {
+ if (arena == nullptr) {
+ return new DynamicIterator(*this);
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(DynamicIterator));
+ return new (mem) DynamicIterator(*this);
+ }
+}
+
+} // anon namespace
+
+MemTableRep* HashSkipListRepFactory::CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* /*logger*/) {
+ return new HashSkipListRep(compare, allocator, transform, bucket_count_,
+ skiplist_height_, skiplist_branching_factor_);
+}
+
+MemTableRepFactory* NewHashSkipListRepFactory(
+ size_t bucket_count, int32_t skiplist_height,
+ int32_t skiplist_branching_factor) {
+ return new HashSkipListRepFactory(bucket_count, skiplist_height,
+ skiplist_branching_factor);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.h b/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.h
new file mode 100644
index 00000000..6da5a4e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/hash_skiplist_rep.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/memtablerep.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class HashSkipListRepFactory : public MemTableRepFactory {
+ public:
+ explicit HashSkipListRepFactory(
+ size_t bucket_count,
+ int32_t skiplist_height,
+ int32_t skiplist_branching_factor)
+ : bucket_count_(bucket_count),
+ skiplist_height_(skiplist_height),
+ skiplist_branching_factor_(skiplist_branching_factor) { }
+
+ virtual ~HashSkipListRepFactory() {}
+
+ using MemTableRepFactory::CreateMemTableRep;
+ virtual MemTableRep* CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* logger) override;
+
+ virtual const char* Name() const override {
+ return "HashSkipListRepFactory";
+ }
+
+ private:
+ const size_t bucket_count_;
+ const int32_t skiplist_height_;
+ const int32_t skiplist_branching_factor_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/memtable/inlineskiplist.h b/storage/rocksdb/rocksdb/memtable/inlineskiplist.h
new file mode 100644
index 00000000..1782288f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/inlineskiplist.h
@@ -0,0 +1,997 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// InlineSkipList is derived from SkipList (skiplist.h), but it optimizes
+// the memory layout by requiring that the key storage be allocated through
+// the skip list instance. For the common case of SkipList<const char*,
+// Cmp> this saves 1 pointer per skip list node and gives better cache
+// locality, at the expense of wasted padding from using AllocateAligned
+// instead of Allocate for the keys. The unused padding will be from
+// 0 to sizeof(void*)-1 bytes, and the space savings are sizeof(void*)
+// bytes, so despite the padding the space used is always less than
+// SkipList<const char*, ..>.
+//
+// Thread safety -------------
+//
+// Writes via Insert require external synchronization, most likely a mutex.
+// InsertConcurrently can be safely called concurrently with reads and
+// with other concurrent inserts. Reads require a guarantee that the
+// InlineSkipList will not be destroyed while the read is in progress.
+// Apart from that, reads progress without any internal locking or
+// synchronization.
+//
+// Invariants:
+//
+// (1) Allocated nodes are never deleted until the InlineSkipList is
+// destroyed. This is trivially guaranteed by the code since we never
+// delete any skip list nodes.
+//
+// (2) The contents of a Node except for the next/prev pointers are
+// immutable after the Node has been linked into the InlineSkipList.
+// Only Insert() modifies the list, and it is careful to initialize a
+// node and use release-stores to publish the nodes in one or more lists.
+//
+// ... prev vs. next pointer ordering ...
+//
+
+#pragma once
+#include <assert.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <atomic>
+#include <type_traits>
+#include "memory/allocator.h"
+#include "port/likely.h"
+#include "port/port.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+template <class Comparator>
+class InlineSkipList {
+ private:
+ struct Node;
+ struct Splice;
+
+ public:
+ using DecodedKey = \
+ typename std::remove_reference<Comparator>::type::DecodedType;
+
+ static const uint16_t kMaxPossibleHeight = 32;
+
+ // Create a new InlineSkipList object that will use "cmp" for comparing
+ // keys, and will allocate memory using "*allocator". Objects allocated
+ // in the allocator must remain allocated for the lifetime of the
+ // skiplist object.
+ explicit InlineSkipList(Comparator cmp, Allocator* allocator,
+ int32_t max_height = 12,
+ int32_t branching_factor = 4);
+ // No copying allowed
+ InlineSkipList(const InlineSkipList&) = delete;
+ InlineSkipList& operator=(const InlineSkipList&) = delete;
+
+ // Allocates a key and a skip-list node, returning a pointer to the key
+ // portion of the node. This method is thread-safe if the allocator
+ // is thread-safe.
+ char* AllocateKey(size_t key_size);
+
+ // Allocate a splice using allocator.
+ Splice* AllocateSplice();
+
+ // Allocate a splice on heap.
+ Splice* AllocateSpliceOnHeap();
+
+ // Inserts a key allocated by AllocateKey, after the actual key value
+ // has been filled in.
+ //
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ // REQUIRES: no concurrent calls to any of inserts.
+ bool Insert(const char* key);
+
+ // Inserts a key allocated by AllocateKey with a hint of last insert
+ // position in the skip-list. If hint points to nullptr, a new hint will be
+ // populated, which can be used in subsequent calls.
+ //
+ // It can be used to optimize the workload where there are multiple groups
+ // of keys, and each key is likely to insert to a location close to the last
+ // inserted key in the same group. One example is sequential inserts.
+ //
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ // REQUIRES: no concurrent calls to any of inserts.
+ bool InsertWithHint(const char* key, void** hint);
+
+ // Like InsertConcurrently, but with a hint
+ //
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ // REQUIRES: no concurrent calls that use same hint
+ bool InsertWithHintConcurrently(const char* key, void** hint);
+
+ // Like Insert, but external synchronization is not required.
+ bool InsertConcurrently(const char* key);
+
+ // Inserts a node into the skip list. key must have been allocated by
+ // AllocateKey and then filled in by the caller. If UseCAS is true,
+ // then external synchronization is not required, otherwise this method
+ // may not be called concurrently with any other insertions.
+ //
+ // Regardless of whether UseCAS is true, the splice must be owned
+ // exclusively by the current thread. If allow_partial_splice_fix is
+ // true, then the cost of insertion is amortized O(log D), where D is
+ // the distance from the splice to the inserted key (measured as the
+ // number of intervening nodes). Note that this bound is very good for
+ // sequential insertions! If allow_partial_splice_fix is false then
+ // the existing splice will be ignored unless the current key is being
+ // inserted immediately after the splice. allow_partial_splice_fix ==
+ // false has worse running time for the non-sequential case O(log N),
+ // but a better constant factor.
+ template <bool UseCAS>
+ bool Insert(const char* key, Splice* splice, bool allow_partial_splice_fix);
+
+ // Returns true iff an entry that compares equal to key is in the list.
+ bool Contains(const char* key) const;
+
+ // Return estimated number of entries smaller than `key`.
+ uint64_t EstimateCount(const char* key) const;
+
+ // Validate correctness of the skip-list.
+ void TEST_Validate() const;
+
+ // Iteration over the contents of a skip list
+ class Iterator {
+ public:
+ // Initialize an iterator over the specified list.
+ // The returned iterator is not valid.
+ explicit Iterator(const InlineSkipList* list);
+
+ // Change the underlying skiplist used for this iterator
+ // This enables us not changing the iterator without deallocating
+ // an old one and then allocating a new one
+ void SetList(const InlineSkipList* list);
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const;
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const;
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next();
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev();
+
+ // Advance to the first entry with a key >= target
+ void Seek(const char* target);
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const char* target);
+
+ // Position at the first entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToFirst();
+
+ // Position at the last entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToLast();
+
+ private:
+ const InlineSkipList* list_;
+ Node* node_;
+ // Intentionally copyable
+ };
+
+ private:
+ const uint16_t kMaxHeight_;
+ const uint16_t kBranching_;
+ const uint32_t kScaledInverseBranching_;
+
+ Allocator* const allocator_; // Allocator used for allocations of nodes
+ // Immutable after construction
+ Comparator const compare_;
+ Node* const head_;
+
+ // Modified only by Insert(). Read racily by readers, but stale
+ // values are ok.
+ std::atomic<int> max_height_; // Height of the entire list
+
+ // seq_splice_ is a Splice used for insertions in the non-concurrent
+ // case. It caches the prev and next found during the most recent
+ // non-concurrent insertion.
+ Splice* seq_splice_;
+
+ inline int GetMaxHeight() const {
+ return max_height_.load(std::memory_order_relaxed);
+ }
+
+ int RandomHeight();
+
+ Node* AllocateNode(size_t key_size, int height);
+
+ bool Equal(const char* a, const char* b) const {
+ return (compare_(a, b) == 0);
+ }
+
+ bool LessThan(const char* a, const char* b) const {
+ return (compare_(a, b) < 0);
+ }
+
+ // Return true if key is greater than the data stored in "n". Null n
+ // is considered infinite. n should not be head_.
+ bool KeyIsAfterNode(const char* key, Node* n) const;
+ bool KeyIsAfterNode(const DecodedKey& key, Node* n) const;
+
+ // Returns the earliest node with a key >= key.
+ // Return nullptr if there is no such node.
+ Node* FindGreaterOrEqual(const char* key) const;
+
+ // Return the latest node with a key < key.
+ // Return head_ if there is no such node.
+ // Fills prev[level] with pointer to previous node at "level" for every
+ // level in [0..max_height_-1], if prev is non-null.
+ Node* FindLessThan(const char* key, Node** prev = nullptr) const;
+
+ // Return the latest node with a key < key on bottom_level. Start searching
+ // from root node on the level below top_level.
+ // Fills prev[level] with pointer to previous node at "level" for every
+ // level in [bottom_level..top_level-1], if prev is non-null.
+ Node* FindLessThan(const char* key, Node** prev, Node* root, int top_level,
+ int bottom_level) const;
+
+ // Return the last node in the list.
+ // Return head_ if list is empty.
+ Node* FindLast() const;
+
+ // Traverses a single level of the list, setting *out_prev to the last
+ // node before the key and *out_next to the first node after. Assumes
+ // that the key is not present in the skip list. On entry, before should
+ // point to a node that is before the key, and after should point to
+ // a node that is after the key. after should be nullptr if a good after
+ // node isn't conveniently available.
+ template<bool prefetch_before>
+ void FindSpliceForLevel(const DecodedKey& key, Node* before, Node* after, int level,
+ Node** out_prev, Node** out_next);
+
+ // Recomputes Splice levels from highest_level (inclusive) down to
+ // lowest_level (inclusive).
+ void RecomputeSpliceLevels(const DecodedKey& key, Splice* splice,
+ int recompute_level);
+};
+
+// Implementation details follow
+
+template <class Comparator>
+struct InlineSkipList<Comparator>::Splice {
+ // The invariant of a Splice is that prev_[i+1].key <= prev_[i].key <
+ // next_[i].key <= next_[i+1].key for all i. That means that if a
+ // key is bracketed by prev_[i] and next_[i] then it is bracketed by
+ // all higher levels. It is _not_ required that prev_[i]->Next(i) ==
+ // next_[i] (it probably did at some point in the past, but intervening
+ // or concurrent operations might have inserted nodes in between).
+ int height_ = 0;
+ Node** prev_;
+ Node** next_;
+};
+
+// The Node data type is more of a pointer into custom-managed memory than
+// a traditional C++ struct. The key is stored in the bytes immediately
+// after the struct, and the next_ pointers for nodes with height > 1 are
+// stored immediately _before_ the struct. This avoids the need to include
+// any pointer or sizing data, which reduces per-node memory overheads.
+template <class Comparator>
+struct InlineSkipList<Comparator>::Node {
+ // Stores the height of the node in the memory location normally used for
+ // next_[0]. This is used for passing data from AllocateKey to Insert.
+ void StashHeight(const int height) {
+ assert(sizeof(int) <= sizeof(next_[0]));
+ memcpy(static_cast<void*>(&next_[0]), &height, sizeof(int));
+ }
+
+ // Retrieves the value passed to StashHeight. Undefined after a call
+ // to SetNext or NoBarrier_SetNext.
+ int UnstashHeight() const {
+ int rv;
+ memcpy(&rv, &next_[0], sizeof(int));
+ return rv;
+ }
+
+ const char* Key() const { return reinterpret_cast<const char*>(&next_[1]); }
+
+ // Accessors/mutators for links. Wrapped in methods so we can add
+ // the appropriate barriers as necessary, and perform the necessary
+ // addressing trickery for storing links below the Node in memory.
+ Node* Next(int n) {
+ assert(n >= 0);
+ // Use an 'acquire load' so that we observe a fully initialized
+ // version of the returned Node.
+ return ((&next_[0] - n)->load(std::memory_order_acquire));
+ }
+
+ void SetNext(int n, Node* x) {
+ assert(n >= 0);
+ // Use a 'release store' so that anybody who reads through this
+ // pointer observes a fully initialized version of the inserted node.
+ (&next_[0] - n)->store(x, std::memory_order_release);
+ }
+
+ bool CASNext(int n, Node* expected, Node* x) {
+ assert(n >= 0);
+ return (&next_[0] - n)->compare_exchange_strong(expected, x);
+ }
+
+ // No-barrier variants that can be safely used in a few locations.
+ Node* NoBarrier_Next(int n) {
+ assert(n >= 0);
+ return (&next_[0] - n)->load(std::memory_order_relaxed);
+ }
+
+ void NoBarrier_SetNext(int n, Node* x) {
+ assert(n >= 0);
+ (&next_[0] - n)->store(x, std::memory_order_relaxed);
+ }
+
+ // Insert node after prev on specific level.
+ void InsertAfter(Node* prev, int level) {
+ // NoBarrier_SetNext() suffices since we will add a barrier when
+ // we publish a pointer to "this" in prev.
+ NoBarrier_SetNext(level, prev->NoBarrier_Next(level));
+ prev->SetNext(level, this);
+ }
+
+ private:
+ // next_[0] is the lowest level link (level 0). Higher levels are
+ // stored _earlier_, so level 1 is at next_[-1].
+ std::atomic<Node*> next_[1];
+};
+
+template <class Comparator>
+inline InlineSkipList<Comparator>::Iterator::Iterator(
+ const InlineSkipList* list) {
+ SetList(list);
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::SetList(
+ const InlineSkipList* list) {
+ list_ = list;
+ node_ = nullptr;
+}
+
+template <class Comparator>
+inline bool InlineSkipList<Comparator>::Iterator::Valid() const {
+ return node_ != nullptr;
+}
+
+template <class Comparator>
+inline const char* InlineSkipList<Comparator>::Iterator::key() const {
+ assert(Valid());
+ return node_->Key();
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::Next() {
+ assert(Valid());
+ node_ = node_->Next(0);
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::Prev() {
+ // Instead of using explicit "prev" links, we just search for the
+ // last node that falls before key.
+ assert(Valid());
+ node_ = list_->FindLessThan(node_->Key());
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::Seek(const char* target) {
+ node_ = list_->FindGreaterOrEqual(target);
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::SeekForPrev(
+ const char* target) {
+ Seek(target);
+ if (!Valid()) {
+ SeekToLast();
+ }
+ while (Valid() && list_->LessThan(target, key())) {
+ Prev();
+ }
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::SeekToFirst() {
+ node_ = list_->head_->Next(0);
+}
+
+template <class Comparator>
+inline void InlineSkipList<Comparator>::Iterator::SeekToLast() {
+ node_ = list_->FindLast();
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+}
+
+template <class Comparator>
+int InlineSkipList<Comparator>::RandomHeight() {
+ auto rnd = Random::GetTLSInstance();
+
+ // Increase height with probability 1 in kBranching
+ int height = 1;
+ while (height < kMaxHeight_ && height < kMaxPossibleHeight &&
+ rnd->Next() < kScaledInverseBranching_) {
+ height++;
+ }
+ assert(height > 0);
+ assert(height <= kMaxHeight_);
+ assert(height <= kMaxPossibleHeight);
+ return height;
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::KeyIsAfterNode(const char* key,
+ Node* n) const {
+ // nullptr n is considered infinite
+ assert(n != head_);
+ return (n != nullptr) && (compare_(n->Key(), key) < 0);
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::KeyIsAfterNode(const DecodedKey& key,
+ Node* n) const {
+ // nullptr n is considered infinite
+ assert(n != head_);
+ return (n != nullptr) && (compare_(n->Key(), key) < 0);
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Node*
+InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const {
+ // Note: It looks like we could reduce duplication by implementing
+ // this function as FindLessThan(key)->Next(0), but we wouldn't be able
+ // to exit early on equality and the result wouldn't even be correct.
+ // A concurrent insert might occur after FindLessThan(key) but before
+ // we get a chance to call Next(0).
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ Node* last_bigger = nullptr;
+ const DecodedKey key_decoded = compare_.decode_key(key);
+ while (true) {
+ Node* next = x->Next(level);
+ if (next != nullptr) {
+ PREFETCH(next->Next(level), 0, 1);
+ }
+ // Make sure the lists are sorted
+ assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x));
+ // Make sure we haven't overshot during our search
+ assert(x == head_ || KeyIsAfterNode(key_decoded, x));
+ int cmp = (next == nullptr || next == last_bigger)
+ ? 1
+ : compare_(next->Key(), key_decoded);
+ if (cmp == 0 || (cmp > 0 && level == 0)) {
+ return next;
+ } else if (cmp < 0) {
+ // Keep searching in this list
+ x = next;
+ } else {
+ // Switch to next list, reuse compare_() result
+ last_bigger = next;
+ level--;
+ }
+ }
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Node*
+InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev) const {
+ return FindLessThan(key, prev, head_, GetMaxHeight(), 0);
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Node*
+InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev,
+ Node* root, int top_level,
+ int bottom_level) const {
+ assert(top_level > bottom_level);
+ int level = top_level - 1;
+ Node* x = root;
+ // KeyIsAfter(key, last_not_after) is definitely false
+ Node* last_not_after = nullptr;
+ const DecodedKey key_decoded = compare_.decode_key(key);
+ while (true) {
+ assert(x != nullptr);
+ Node* next = x->Next(level);
+ if (next != nullptr) {
+ PREFETCH(next->Next(level), 0, 1);
+ }
+ assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x));
+ assert(x == head_ || KeyIsAfterNode(key_decoded, x));
+ if (next != last_not_after && KeyIsAfterNode(key_decoded, next)) {
+ // Keep searching in this list
+ assert(next != nullptr);
+ x = next;
+ } else {
+ if (prev != nullptr) {
+ prev[level] = x;
+ }
+ if (level == bottom_level) {
+ return x;
+ } else {
+ // Switch to next list, reuse KeyIsAfterNode() result
+ last_not_after = next;
+ level--;
+ }
+ }
+ }
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Node*
+InlineSkipList<Comparator>::FindLast() const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ Node* next = x->Next(level);
+ if (next == nullptr) {
+ if (level == 0) {
+ return x;
+ } else {
+ // Switch to next list
+ level--;
+ }
+ } else {
+ x = next;
+ }
+ }
+}
+
+template <class Comparator>
+uint64_t InlineSkipList<Comparator>::EstimateCount(const char* key) const {
+ uint64_t count = 0;
+
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ const DecodedKey key_decoded = compare_.decode_key(key);
+ while (true) {
+ assert(x == head_ || compare_(x->Key(), key_decoded) < 0);
+ Node* next = x->Next(level);
+ if (next != nullptr) {
+ PREFETCH(next->Next(level), 0, 1);
+ }
+ if (next == nullptr || compare_(next->Key(), key_decoded) >= 0) {
+ if (level == 0) {
+ return count;
+ } else {
+ // Switch to next list
+ count *= kBranching_;
+ level--;
+ }
+ } else {
+ x = next;
+ count++;
+ }
+ }
+}
+
+template <class Comparator>
+InlineSkipList<Comparator>::InlineSkipList(const Comparator cmp,
+ Allocator* allocator,
+ int32_t max_height,
+ int32_t branching_factor)
+ : kMaxHeight_(static_cast<uint16_t>(max_height)),
+ kBranching_(static_cast<uint16_t>(branching_factor)),
+ kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
+ allocator_(allocator),
+ compare_(cmp),
+ head_(AllocateNode(0, max_height)),
+ max_height_(1),
+ seq_splice_(AllocateSplice()) {
+ assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
+ assert(branching_factor > 1 &&
+ kBranching_ == static_cast<uint32_t>(branching_factor));
+ assert(kScaledInverseBranching_ > 0);
+
+ for (int i = 0; i < kMaxHeight_; ++i) {
+ head_->SetNext(i, nullptr);
+ }
+}
+
+template <class Comparator>
+char* InlineSkipList<Comparator>::AllocateKey(size_t key_size) {
+ return const_cast<char*>(AllocateNode(key_size, RandomHeight())->Key());
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Node*
+InlineSkipList<Comparator>::AllocateNode(size_t key_size, int height) {
+ auto prefix = sizeof(std::atomic<Node*>) * (height - 1);
+
+ // prefix is space for the height - 1 pointers that we store before
+ // the Node instance (next_[-(height - 1) .. -1]). Node starts at
+ // raw + prefix, and holds the bottom-mode (level 0) skip list pointer
+ // next_[0]. key_size is the bytes for the key, which comes just after
+ // the Node.
+ char* raw = allocator_->AllocateAligned(prefix + sizeof(Node) + key_size);
+ Node* x = reinterpret_cast<Node*>(raw + prefix);
+
+ // Once we've linked the node into the skip list we don't actually need
+ // to know its height, because we can implicitly use the fact that we
+ // traversed into a node at level h to known that h is a valid level
+ // for that node. We need to convey the height to the Insert step,
+ // however, so that it can perform the proper links. Since we're not
+ // using the pointers at the moment, StashHeight temporarily borrow
+ // storage from next_[0] for that purpose.
+ x->StashHeight(height);
+ return x;
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Splice*
+InlineSkipList<Comparator>::AllocateSplice() {
+ // size of prev_ and next_
+ size_t array_size = sizeof(Node*) * (kMaxHeight_ + 1);
+ char* raw = allocator_->AllocateAligned(sizeof(Splice) + array_size * 2);
+ Splice* splice = reinterpret_cast<Splice*>(raw);
+ splice->height_ = 0;
+ splice->prev_ = reinterpret_cast<Node**>(raw + sizeof(Splice));
+ splice->next_ = reinterpret_cast<Node**>(raw + sizeof(Splice) + array_size);
+ return splice;
+}
+
+template <class Comparator>
+typename InlineSkipList<Comparator>::Splice*
+InlineSkipList<Comparator>::AllocateSpliceOnHeap() {
+ size_t array_size = sizeof(Node*) * (kMaxHeight_ + 1);
+ char* raw = new char[sizeof(Splice) + array_size * 2];
+ Splice* splice = reinterpret_cast<Splice*>(raw);
+ splice->height_ = 0;
+ splice->prev_ = reinterpret_cast<Node**>(raw + sizeof(Splice));
+ splice->next_ = reinterpret_cast<Node**>(raw + sizeof(Splice) + array_size);
+ return splice;
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::Insert(const char* key) {
+ return Insert<false>(key, seq_splice_, false);
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::InsertConcurrently(const char* key) {
+ Node* prev[kMaxPossibleHeight];
+ Node* next[kMaxPossibleHeight];
+ Splice splice;
+ splice.prev_ = prev;
+ splice.next_ = next;
+ return Insert<true>(key, &splice, false);
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::InsertWithHint(const char* key, void** hint) {
+ assert(hint != nullptr);
+ Splice* splice = reinterpret_cast<Splice*>(*hint);
+ if (splice == nullptr) {
+ splice = AllocateSplice();
+ *hint = reinterpret_cast<void*>(splice);
+ }
+ return Insert<false>(key, splice, true);
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::InsertWithHintConcurrently(const char* key,
+ void** hint) {
+ assert(hint != nullptr);
+ Splice* splice = reinterpret_cast<Splice*>(*hint);
+ if (splice == nullptr) {
+ splice = AllocateSpliceOnHeap();
+ *hint = reinterpret_cast<void*>(splice);
+ }
+ return Insert<true>(key, splice, true);
+}
+
+template <class Comparator>
+template <bool prefetch_before>
+void InlineSkipList<Comparator>::FindSpliceForLevel(const DecodedKey& key,
+ Node* before, Node* after,
+ int level, Node** out_prev,
+ Node** out_next) {
+ while (true) {
+ Node* next = before->Next(level);
+ if (next != nullptr) {
+ PREFETCH(next->Next(level), 0, 1);
+ }
+ if (prefetch_before == true) {
+ if (next != nullptr && level>0) {
+ PREFETCH(next->Next(level-1), 0, 1);
+ }
+ }
+ assert(before == head_ || next == nullptr ||
+ KeyIsAfterNode(next->Key(), before));
+ assert(before == head_ || KeyIsAfterNode(key, before));
+ if (next == after || !KeyIsAfterNode(key, next)) {
+ // found it
+ *out_prev = before;
+ *out_next = next;
+ return;
+ }
+ before = next;
+ }
+}
+
+template <class Comparator>
+void InlineSkipList<Comparator>::RecomputeSpliceLevels(const DecodedKey& key,
+ Splice* splice,
+ int recompute_level) {
+ assert(recompute_level > 0);
+ assert(recompute_level <= splice->height_);
+ for (int i = recompute_level - 1; i >= 0; --i) {
+ FindSpliceForLevel<true>(key, splice->prev_[i + 1], splice->next_[i + 1], i,
+ &splice->prev_[i], &splice->next_[i]);
+ }
+}
+
+template <class Comparator>
+template <bool UseCAS>
+bool InlineSkipList<Comparator>::Insert(const char* key, Splice* splice,
+ bool allow_partial_splice_fix) {
+ Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1;
+ const DecodedKey key_decoded = compare_.decode_key(key);
+ int height = x->UnstashHeight();
+ assert(height >= 1 && height <= kMaxHeight_);
+
+ int max_height = max_height_.load(std::memory_order_relaxed);
+ while (height > max_height) {
+ if (max_height_.compare_exchange_weak(max_height, height)) {
+ // successfully updated it
+ max_height = height;
+ break;
+ }
+ // else retry, possibly exiting the loop because somebody else
+ // increased it
+ }
+ assert(max_height <= kMaxPossibleHeight);
+
+ int recompute_height = 0;
+ if (splice->height_ < max_height) {
+ // Either splice has never been used or max_height has grown since
+ // last use. We could potentially fix it in the latter case, but
+ // that is tricky.
+ splice->prev_[max_height] = head_;
+ splice->next_[max_height] = nullptr;
+ splice->height_ = max_height;
+ recompute_height = max_height;
+ } else {
+ // Splice is a valid proper-height splice that brackets some
+ // key, but does it bracket this one? We need to validate it and
+ // recompute a portion of the splice (levels 0..recompute_height-1)
+ // that is a superset of all levels that don't bracket the new key.
+ // Several choices are reasonable, because we have to balance the work
+ // saved against the extra comparisons required to validate the Splice.
+ //
+ // One strategy is just to recompute all of orig_splice_height if the
+ // bottom level isn't bracketing. This pessimistically assumes that
+ // we will either get a perfect Splice hit (increasing sequential
+ // inserts) or have no locality.
+ //
+ // Another strategy is to walk up the Splice's levels until we find
+ // a level that brackets the key. This strategy lets the Splice
+ // hint help for other cases: it turns insertion from O(log N) into
+ // O(log D), where D is the number of nodes in between the key that
+ // produced the Splice and the current insert (insertion is aided
+ // whether the new key is before or after the splice). If you have
+ // a way of using a prefix of the key to map directly to the closest
+ // Splice out of O(sqrt(N)) Splices and we make it so that splices
+ // can also be used as hints during read, then we end up with Oshman's
+ // and Shavit's SkipTrie, which has O(log log N) lookup and insertion
+ // (compare to O(log N) for skip list).
+ //
+ // We control the pessimistic strategy with allow_partial_splice_fix.
+ // A good strategy is probably to be pessimistic for seq_splice_,
+ // optimistic if the caller actually went to the work of providing
+ // a Splice.
+ while (recompute_height < max_height) {
+ if (splice->prev_[recompute_height]->Next(recompute_height) !=
+ splice->next_[recompute_height]) {
+ // splice isn't tight at this level, there must have been some inserts
+ // to this
+ // location that didn't update the splice. We might only be a little
+ // stale, but if
+ // the splice is very stale it would be O(N) to fix it. We haven't used
+ // up any of
+ // our budget of comparisons, so always move up even if we are
+ // pessimistic about
+ // our chances of success.
+ ++recompute_height;
+ } else if (splice->prev_[recompute_height] != head_ &&
+ !KeyIsAfterNode(key_decoded,
+ splice->prev_[recompute_height])) {
+ // key is from before splice
+ if (allow_partial_splice_fix) {
+ // skip all levels with the same node without more comparisons
+ Node* bad = splice->prev_[recompute_height];
+ while (splice->prev_[recompute_height] == bad) {
+ ++recompute_height;
+ }
+ } else {
+ // we're pessimistic, recompute everything
+ recompute_height = max_height;
+ }
+ } else if (KeyIsAfterNode(key_decoded,
+ splice->next_[recompute_height])) {
+ // key is from after splice
+ if (allow_partial_splice_fix) {
+ Node* bad = splice->next_[recompute_height];
+ while (splice->next_[recompute_height] == bad) {
+ ++recompute_height;
+ }
+ } else {
+ recompute_height = max_height;
+ }
+ } else {
+ // this level brackets the key, we won!
+ break;
+ }
+ }
+ }
+ assert(recompute_height <= max_height);
+ if (recompute_height > 0) {
+ RecomputeSpliceLevels(key_decoded, splice, recompute_height);
+ }
+
+ bool splice_is_valid = true;
+ if (UseCAS) {
+ for (int i = 0; i < height; ++i) {
+ while (true) {
+ // Checking for duplicate keys on the level 0 is sufficient
+ if (UNLIKELY(i == 0 && splice->next_[i] != nullptr &&
+ compare_(x->Key(), splice->next_[i]->Key()) >= 0)) {
+ // duplicate key
+ return false;
+ }
+ if (UNLIKELY(i == 0 && splice->prev_[i] != head_ &&
+ compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) {
+ // duplicate key
+ return false;
+ }
+ assert(splice->next_[i] == nullptr ||
+ compare_(x->Key(), splice->next_[i]->Key()) < 0);
+ assert(splice->prev_[i] == head_ ||
+ compare_(splice->prev_[i]->Key(), x->Key()) < 0);
+ x->NoBarrier_SetNext(i, splice->next_[i]);
+ if (splice->prev_[i]->CASNext(i, splice->next_[i], x)) {
+ // success
+ break;
+ }
+ // CAS failed, we need to recompute prev and next. It is unlikely
+ // to be helpful to try to use a different level as we redo the
+ // search, because it should be unlikely that lots of nodes have
+ // been inserted between prev[i] and next[i]. No point in using
+ // next[i] as the after hint, because we know it is stale.
+ FindSpliceForLevel<false>(key_decoded, splice->prev_[i], nullptr, i,
+ &splice->prev_[i], &splice->next_[i]);
+
+ // Since we've narrowed the bracket for level i, we might have
+ // violated the Splice constraint between i and i-1. Make sure
+ // we recompute the whole thing next time.
+ if (i > 0) {
+ splice_is_valid = false;
+ }
+ }
+ }
+ } else {
+ for (int i = 0; i < height; ++i) {
+ if (i >= recompute_height &&
+ splice->prev_[i]->Next(i) != splice->next_[i]) {
+ FindSpliceForLevel<false>(key_decoded, splice->prev_[i], nullptr, i,
+ &splice->prev_[i], &splice->next_[i]);
+ }
+ // Checking for duplicate keys on the level 0 is sufficient
+ if (UNLIKELY(i == 0 && splice->next_[i] != nullptr &&
+ compare_(x->Key(), splice->next_[i]->Key()) >= 0)) {
+ // duplicate key
+ return false;
+ }
+ if (UNLIKELY(i == 0 && splice->prev_[i] != head_ &&
+ compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) {
+ // duplicate key
+ return false;
+ }
+ assert(splice->next_[i] == nullptr ||
+ compare_(x->Key(), splice->next_[i]->Key()) < 0);
+ assert(splice->prev_[i] == head_ ||
+ compare_(splice->prev_[i]->Key(), x->Key()) < 0);
+ assert(splice->prev_[i]->Next(i) == splice->next_[i]);
+ x->NoBarrier_SetNext(i, splice->next_[i]);
+ splice->prev_[i]->SetNext(i, x);
+ }
+ }
+ if (splice_is_valid) {
+ for (int i = 0; i < height; ++i) {
+ splice->prev_[i] = x;
+ }
+ assert(splice->prev_[splice->height_] == head_);
+ assert(splice->next_[splice->height_] == nullptr);
+ for (int i = 0; i < splice->height_; ++i) {
+ assert(splice->next_[i] == nullptr ||
+ compare_(key, splice->next_[i]->Key()) < 0);
+ assert(splice->prev_[i] == head_ ||
+ compare_(splice->prev_[i]->Key(), key) <= 0);
+ assert(splice->prev_[i + 1] == splice->prev_[i] ||
+ splice->prev_[i + 1] == head_ ||
+ compare_(splice->prev_[i + 1]->Key(), splice->prev_[i]->Key()) <
+ 0);
+ assert(splice->next_[i + 1] == splice->next_[i] ||
+ splice->next_[i + 1] == nullptr ||
+ compare_(splice->next_[i]->Key(), splice->next_[i + 1]->Key()) <
+ 0);
+ }
+ } else {
+ splice->height_ = 0;
+ }
+ return true;
+}
+
+template <class Comparator>
+bool InlineSkipList<Comparator>::Contains(const char* key) const {
+ Node* x = FindGreaterOrEqual(key);
+ if (x != nullptr && Equal(key, x->Key())) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <class Comparator>
+void InlineSkipList<Comparator>::TEST_Validate() const {
+ // Interate over all levels at the same time, and verify nodes appear in
+ // the right order, and nodes appear in upper level also appear in lower
+ // levels.
+ Node* nodes[kMaxPossibleHeight];
+ int max_height = GetMaxHeight();
+ assert(max_height > 0);
+ for (int i = 0; i < max_height; i++) {
+ nodes[i] = head_;
+ }
+ while (nodes[0] != nullptr) {
+ Node* l0_next = nodes[0]->Next(0);
+ if (l0_next == nullptr) {
+ break;
+ }
+ assert(nodes[0] == head_ || compare_(nodes[0]->Key(), l0_next->Key()) < 0);
+ nodes[0] = l0_next;
+
+ int i = 1;
+ while (i < max_height) {
+ Node* next = nodes[i]->Next(i);
+ if (next == nullptr) {
+ break;
+ }
+ auto cmp = compare_(nodes[0]->Key(), next->Key());
+ assert(cmp <= 0);
+ if (cmp == 0) {
+ assert(next == nodes[0]);
+ nodes[i] = next;
+ } else {
+ break;
+ }
+ i++;
+ }
+ }
+ for (int i = 1; i < max_height; i++) {
+ assert(nodes[i] != nullptr && nodes[i]->Next(i) == nullptr);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/inlineskiplist_test.cc b/storage/rocksdb/rocksdb/memtable/inlineskiplist_test.cc
new file mode 100644
index 00000000..f0a70531
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/inlineskiplist_test.cc
@@ -0,0 +1,663 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <set>
+#include <unordered_set>
+#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
+typedef uint64_t Key;
+
+static const char* Encode(const uint64_t* key) {
+ return reinterpret_cast<const char*>(key);
+}
+
+static Key Decode(const char* key) {
+ Key rv;
+ memcpy(&rv, key, sizeof(Key));
+ return rv;
+}
+
+struct TestComparator {
+ typedef Key DecodedType;
+
+ 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;
+ }
+ }
+};
+
+typedef InlineSkipList<TestComparator> TestInlineSkipList;
+
+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<Key> keys_;
+};
+
+TEST_F(InlineSkipTest, Empty) {
+ Arena arena;
+ TestComparator cmp;
+ InlineSkipList<TestComparator> list(cmp, &arena);
+ Key key = 10;
+ ASSERT_TRUE(!list.Contains(Encode(&key)));
+
+ InlineSkipList<TestComparator>::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<Key> keys;
+ ConcurrentArena arena;
+ TestComparator cmp;
+ InlineSkipList<TestComparator> 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<TestComparator>::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<TestComparator>::Iterator iter(&list);
+ iter.Seek(Encode(&i));
+
+ // Compare against model iterator
+ std::set<Key>::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<TestComparator>::Iterator iter(&list);
+ iter.SeekForPrev(Encode(&i));
+
+ // Compare against model iterator
+ std::set<Key>::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<Key> 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);
+}
+
+#ifndef ROCKSDB_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:
+// <key,gen,hash>
+// 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<char*>(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<int> 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<TestComparator> 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<char*>(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<TestComparator>::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<uint64_t>(initial_state.Get(
+ static_cast<int>(key(pos))))))
+ << "key: " << key(pos) << "; gen: " << gen(pos)
+ << "; initgen: " << initial_state.Get(static_cast<int>(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<bool> quit_flag_;
+ std::atomic<uint32_t> 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<TestState*>(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<TestState*>(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 // ROCKSDB_VALGRIND_RUN
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/memtable/memtablerep_bench.cc b/storage/rocksdb/rocksdb/memtable/memtablerep_bench.cc
new file mode 100644
index 00000000..06074a3a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/memtablerep_bench.cc
@@ -0,0 +1,678 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include <atomic>
+#include <iostream>
+#include <memory>
+#include <thread>
+#include <type_traits>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/memtable.h"
+#include "memory/arena.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "test_util/testutil.h"
+#include "util/gflags_compat.h"
+#include "util/mutexlock.h"
+#include "util/stop_watch.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::RegisterFlagValidator;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_string(benchmarks, "fillrandom",
+ "Comma-separated list of benchmarks to run. Options:\n"
+ "\tfillrandom -- write N random values\n"
+ "\tfillseq -- write N values in sequential order\n"
+ "\treadrandom -- read N values in random order\n"
+ "\treadseq -- scan the DB\n"
+ "\treadwrite -- 1 thread writes while N - 1 threads "
+ "do random\n"
+ "\t reads\n"
+ "\tseqreadwrite -- 1 thread writes while N - 1 threads "
+ "do scans\n");
+
+DEFINE_string(memtablerep, "skiplist",
+ "Which implementation of memtablerep to use. See "
+ "include/memtablerep.h for\n"
+ " more details. Options:\n"
+ "\tskiplist -- backed by a skiplist\n"
+ "\tvector -- backed by an std::vector\n"
+ "\thashskiplist -- backed by a hash skip list\n"
+ "\thashlinklist -- backed by a hash linked list\n"
+ "\tcuckoo -- backed by a cuckoo hash table");
+
+DEFINE_int64(bucket_count, 1000000,
+ "bucket_count parameter to pass into NewHashSkiplistRepFactory or "
+ "NewHashLinkListRepFactory");
+
+DEFINE_int32(
+ hashskiplist_height, 4,
+ "skiplist_height parameter to pass into NewHashSkiplistRepFactory");
+
+DEFINE_int32(
+ hashskiplist_branching_factor, 4,
+ "branching_factor parameter to pass into NewHashSkiplistRepFactory");
+
+DEFINE_int32(
+ huge_page_tlb_size, 0,
+ "huge_page_tlb_size parameter to pass into NewHashLinkListRepFactory");
+
+DEFINE_int32(bucket_entries_logging_threshold, 4096,
+ "bucket_entries_logging_threshold parameter to pass into "
+ "NewHashLinkListRepFactory");
+
+DEFINE_bool(if_log_bucket_dist_when_flash, true,
+ "if_log_bucket_dist_when_flash parameter to pass into "
+ "NewHashLinkListRepFactory");
+
+DEFINE_int32(
+ threshold_use_skiplist, 256,
+ "threshold_use_skiplist parameter to pass into NewHashLinkListRepFactory");
+
+DEFINE_int64(write_buffer_size, 256,
+ "write_buffer_size parameter to pass into WriteBufferManager");
+
+DEFINE_int32(
+ num_threads, 1,
+ "Number of concurrent threads to run. If the benchmark includes writes,\n"
+ "then at most one thread will be a writer");
+
+DEFINE_int32(num_operations, 1000000,
+ "Number of operations to do for write and random read benchmarks");
+
+DEFINE_int32(num_scans, 10,
+ "Number of times for each thread to scan the memtablerep for "
+ "sequential read "
+ "benchmarks");
+
+DEFINE_int32(item_size, 100, "Number of bytes each item should be");
+
+DEFINE_int32(prefix_length, 8,
+ "Prefix length to pass into NewFixedPrefixTransform");
+
+/* VectorRep settings */
+DEFINE_int64(vectorrep_count, 0,
+ "Number of entries to reserve on VectorRep initialization");
+
+DEFINE_int64(seed, 0,
+ "Seed base for random number generators. "
+ "When 0 it is deterministic.");
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+struct CallbackVerifyArgs {
+ bool found;
+ LookupKey* key;
+ MemTableRep* table;
+ InternalKeyComparator* comparator;
+};
+} // namespace
+
+// Helper for quickly generating random data.
+class RandomGenerator {
+ private:
+ std::string data_;
+ unsigned int pos_;
+
+ public:
+ RandomGenerator() {
+ Random rnd(301);
+ auto size = (unsigned)std::max(1048576, FLAGS_item_size);
+ test::RandomString(&rnd, size, &data_);
+ pos_ = 0;
+ }
+
+ Slice Generate(unsigned int len) {
+ assert(len <= data_.size());
+ if (pos_ + len > data_.size()) {
+ pos_ = 0;
+ }
+ pos_ += len;
+ return Slice(data_.data() + pos_ - len, len);
+ }
+};
+
+enum WriteMode { SEQUENTIAL, RANDOM, UNIQUE_RANDOM };
+
+class KeyGenerator {
+ public:
+ KeyGenerator(Random64* rand, WriteMode mode, uint64_t num)
+ : rand_(rand), mode_(mode), num_(num), next_(0) {
+ if (mode_ == UNIQUE_RANDOM) {
+ // NOTE: if memory consumption of this approach becomes a concern,
+ // we can either break it into pieces and only random shuffle a section
+ // each time. Alternatively, use a bit map implementation
+ // (https://reviews.facebook.net/differential/diff/54627/)
+ values_.resize(num_);
+ for (uint64_t i = 0; i < num_; ++i) {
+ values_[i] = i;
+ }
+ std::shuffle(
+ values_.begin(), values_.end(),
+ std::default_random_engine(static_cast<unsigned int>(FLAGS_seed)));
+ }
+ }
+
+ uint64_t Next() {
+ switch (mode_) {
+ case SEQUENTIAL:
+ return next_++;
+ case RANDOM:
+ return rand_->Next() % num_;
+ case UNIQUE_RANDOM:
+ return values_[next_++];
+ }
+ assert(false);
+ return std::numeric_limits<uint64_t>::max();
+ }
+
+ private:
+ Random64* rand_;
+ WriteMode mode_;
+ const uint64_t num_;
+ uint64_t next_;
+ std::vector<uint64_t> values_;
+};
+
+class BenchmarkThread {
+ public:
+ explicit BenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops,
+ uint64_t* read_hits)
+ : table_(table),
+ key_gen_(key_gen),
+ bytes_written_(bytes_written),
+ bytes_read_(bytes_read),
+ sequence_(sequence),
+ num_ops_(num_ops),
+ read_hits_(read_hits) {}
+
+ virtual void operator()() = 0;
+ virtual ~BenchmarkThread() {}
+
+ protected:
+ MemTableRep* table_;
+ KeyGenerator* key_gen_;
+ uint64_t* bytes_written_;
+ uint64_t* bytes_read_;
+ uint64_t* sequence_;
+ uint64_t num_ops_;
+ uint64_t* read_hits_;
+ RandomGenerator generator_;
+};
+
+class FillBenchmarkThread : public BenchmarkThread {
+ public:
+ FillBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops, uint64_t* read_hits)
+ : BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence,
+ num_ops, read_hits) {}
+
+ void FillOne() {
+ char* buf = nullptr;
+ auto internal_key_size = 16;
+ auto encoded_len =
+ FLAGS_item_size + VarintLength(internal_key_size) + internal_key_size;
+ KeyHandle handle = table_->Allocate(encoded_len, &buf);
+ assert(buf != nullptr);
+ char* p = EncodeVarint32(buf, internal_key_size);
+ auto key = key_gen_->Next();
+ EncodeFixed64(p, key);
+ p += 8;
+ EncodeFixed64(p, ++(*sequence_));
+ p += 8;
+ Slice bytes = generator_.Generate(FLAGS_item_size);
+ memcpy(p, bytes.data(), FLAGS_item_size);
+ p += FLAGS_item_size;
+ assert(p == buf + encoded_len);
+ table_->Insert(handle);
+ *bytes_written_ += encoded_len;
+ }
+
+ void operator()() override {
+ for (unsigned int i = 0; i < num_ops_; ++i) {
+ FillOne();
+ }
+ }
+};
+
+class ConcurrentFillBenchmarkThread : public FillBenchmarkThread {
+ public:
+ ConcurrentFillBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops,
+ uint64_t* read_hits,
+ std::atomic_int* threads_done)
+ : FillBenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence,
+ num_ops, read_hits) {
+ threads_done_ = threads_done;
+ }
+
+ void operator()() override {
+ // # of read threads will be total threads - write threads (always 1). Loop
+ // while all reads complete.
+ while ((*threads_done_).load() < (FLAGS_num_threads - 1)) {
+ FillOne();
+ }
+ }
+
+ private:
+ std::atomic_int* threads_done_;
+};
+
+class ReadBenchmarkThread : public BenchmarkThread {
+ public:
+ ReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops, uint64_t* read_hits)
+ : BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence,
+ num_ops, read_hits) {}
+
+ static bool callback(void* arg, const char* entry) {
+ CallbackVerifyArgs* callback_args = static_cast<CallbackVerifyArgs*>(arg);
+ assert(callback_args != nullptr);
+ uint32_t key_length;
+ const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
+ if ((callback_args->comparator)
+ ->user_comparator()
+ ->Equal(Slice(key_ptr, key_length - 8),
+ callback_args->key->user_key())) {
+ callback_args->found = true;
+ }
+ return false;
+ }
+
+ void ReadOne() {
+ std::string user_key;
+ auto key = key_gen_->Next();
+ PutFixed64(&user_key, key);
+ LookupKey lookup_key(user_key, *sequence_);
+ InternalKeyComparator internal_key_comp(BytewiseComparator());
+ CallbackVerifyArgs verify_args;
+ verify_args.found = false;
+ verify_args.key = &lookup_key;
+ verify_args.table = table_;
+ verify_args.comparator = &internal_key_comp;
+ table_->Get(lookup_key, &verify_args, callback);
+ if (verify_args.found) {
+ *bytes_read_ += VarintLength(16) + 16 + FLAGS_item_size;
+ ++*read_hits_;
+ }
+ }
+ void operator()() override {
+ for (unsigned int i = 0; i < num_ops_; ++i) {
+ ReadOne();
+ }
+ }
+};
+
+class SeqReadBenchmarkThread : public BenchmarkThread {
+ public:
+ SeqReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops,
+ uint64_t* read_hits)
+ : BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence,
+ num_ops, read_hits) {}
+
+ void ReadOneSeq() {
+ std::unique_ptr<MemTableRep::Iterator> iter(table_->GetIterator());
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ // pretend to read the value
+ *bytes_read_ += VarintLength(16) + 16 + FLAGS_item_size;
+ }
+ ++*read_hits_;
+ }
+
+ void operator()() override {
+ for (unsigned int i = 0; i < num_ops_; ++i) {
+ { ReadOneSeq(); }
+ }
+ }
+};
+
+class ConcurrentReadBenchmarkThread : public ReadBenchmarkThread {
+ public:
+ ConcurrentReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ uint64_t* sequence, uint64_t num_ops,
+ uint64_t* read_hits,
+ std::atomic_int* threads_done)
+ : ReadBenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence,
+ num_ops, read_hits) {
+ threads_done_ = threads_done;
+ }
+
+ void operator()() override {
+ for (unsigned int i = 0; i < num_ops_; ++i) {
+ ReadOne();
+ }
+ ++*threads_done_;
+ }
+
+ private:
+ std::atomic_int* threads_done_;
+};
+
+class SeqConcurrentReadBenchmarkThread : public SeqReadBenchmarkThread {
+ public:
+ SeqConcurrentReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* bytes_written,
+ uint64_t* bytes_read, uint64_t* sequence,
+ uint64_t num_ops, uint64_t* read_hits,
+ std::atomic_int* threads_done)
+ : SeqReadBenchmarkThread(table, key_gen, bytes_written, bytes_read,
+ sequence, num_ops, read_hits) {
+ threads_done_ = threads_done;
+ }
+
+ void operator()() override {
+ for (unsigned int i = 0; i < num_ops_; ++i) {
+ ReadOneSeq();
+ }
+ ++*threads_done_;
+ }
+
+ private:
+ std::atomic_int* threads_done_;
+};
+
+class Benchmark {
+ public:
+ explicit Benchmark(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* sequence, uint32_t num_threads)
+ : table_(table),
+ key_gen_(key_gen),
+ sequence_(sequence),
+ num_threads_(num_threads) {}
+
+ virtual ~Benchmark() {}
+ virtual void Run() {
+ std::cout << "Number of threads: " << num_threads_ << std::endl;
+ std::vector<port::Thread> threads;
+ uint64_t bytes_written = 0;
+ uint64_t bytes_read = 0;
+ uint64_t read_hits = 0;
+ StopWatchNano timer(Env::Default(), true);
+ RunThreads(&threads, &bytes_written, &bytes_read, true, &read_hits);
+ auto elapsed_time = static_cast<double>(timer.ElapsedNanos() / 1000);
+ std::cout << "Elapsed time: " << static_cast<int>(elapsed_time) << " us"
+ << std::endl;
+
+ if (bytes_written > 0) {
+ auto MiB_written = static_cast<double>(bytes_written) / (1 << 20);
+ auto write_throughput = MiB_written / (elapsed_time / 1000000);
+ std::cout << "Total bytes written: " << MiB_written << " MiB"
+ << std::endl;
+ std::cout << "Write throughput: " << write_throughput << " MiB/s"
+ << std::endl;
+ auto us_per_op = elapsed_time / num_write_ops_per_thread_;
+ std::cout << "write us/op: " << us_per_op << std::endl;
+ }
+ if (bytes_read > 0) {
+ auto MiB_read = static_cast<double>(bytes_read) / (1 << 20);
+ auto read_throughput = MiB_read / (elapsed_time / 1000000);
+ std::cout << "Total bytes read: " << MiB_read << " MiB" << std::endl;
+ std::cout << "Read throughput: " << read_throughput << " MiB/s"
+ << std::endl;
+ auto us_per_op = elapsed_time / num_read_ops_per_thread_;
+ std::cout << "read us/op: " << us_per_op << std::endl;
+ }
+ }
+
+ virtual void RunThreads(std::vector<port::Thread>* threads,
+ uint64_t* bytes_written, uint64_t* bytes_read,
+ bool write, uint64_t* read_hits) = 0;
+
+ protected:
+ MemTableRep* table_;
+ KeyGenerator* key_gen_;
+ uint64_t* sequence_;
+ uint64_t num_write_ops_per_thread_;
+ uint64_t num_read_ops_per_thread_;
+ const uint32_t num_threads_;
+};
+
+class FillBenchmark : public Benchmark {
+ public:
+ explicit FillBenchmark(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* sequence)
+ : Benchmark(table, key_gen, sequence, 1) {
+ num_write_ops_per_thread_ = FLAGS_num_operations;
+ }
+
+ void RunThreads(std::vector<port::Thread>* /*threads*/, uint64_t* bytes_written,
+ uint64_t* bytes_read, bool /*write*/,
+ uint64_t* read_hits) override {
+ FillBenchmarkThread(table_, key_gen_, bytes_written, bytes_read, sequence_,
+ num_write_ops_per_thread_, read_hits)();
+ }
+};
+
+class ReadBenchmark : public Benchmark {
+ public:
+ explicit ReadBenchmark(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* sequence)
+ : Benchmark(table, key_gen, sequence, FLAGS_num_threads) {
+ num_read_ops_per_thread_ = FLAGS_num_operations / FLAGS_num_threads;
+ }
+
+ void RunThreads(std::vector<port::Thread>* threads, uint64_t* bytes_written,
+ uint64_t* bytes_read, bool /*write*/,
+ uint64_t* read_hits) override {
+ for (int i = 0; i < FLAGS_num_threads; ++i) {
+ threads->emplace_back(
+ ReadBenchmarkThread(table_, key_gen_, bytes_written, bytes_read,
+ sequence_, num_read_ops_per_thread_, read_hits));
+ }
+ for (auto& thread : *threads) {
+ thread.join();
+ }
+ std::cout << "read hit%: "
+ << (static_cast<double>(*read_hits) / FLAGS_num_operations) * 100
+ << std::endl;
+ }
+};
+
+class SeqReadBenchmark : public Benchmark {
+ public:
+ explicit SeqReadBenchmark(MemTableRep* table, uint64_t* sequence)
+ : Benchmark(table, nullptr, sequence, FLAGS_num_threads) {
+ num_read_ops_per_thread_ = FLAGS_num_scans;
+ }
+
+ void RunThreads(std::vector<port::Thread>* threads, uint64_t* bytes_written,
+ uint64_t* bytes_read, bool /*write*/,
+ uint64_t* read_hits) override {
+ for (int i = 0; i < FLAGS_num_threads; ++i) {
+ threads->emplace_back(SeqReadBenchmarkThread(
+ table_, key_gen_, bytes_written, bytes_read, sequence_,
+ num_read_ops_per_thread_, read_hits));
+ }
+ for (auto& thread : *threads) {
+ thread.join();
+ }
+ }
+};
+
+template <class ReadThreadType>
+class ReadWriteBenchmark : public Benchmark {
+ public:
+ explicit ReadWriteBenchmark(MemTableRep* table, KeyGenerator* key_gen,
+ uint64_t* sequence)
+ : Benchmark(table, key_gen, sequence, FLAGS_num_threads) {
+ num_read_ops_per_thread_ =
+ FLAGS_num_threads <= 1
+ ? 0
+ : (FLAGS_num_operations / (FLAGS_num_threads - 1));
+ num_write_ops_per_thread_ = FLAGS_num_operations;
+ }
+
+ void RunThreads(std::vector<port::Thread>* threads, uint64_t* bytes_written,
+ uint64_t* bytes_read, bool /*write*/,
+ uint64_t* read_hits) override {
+ std::atomic_int threads_done;
+ threads_done.store(0);
+ threads->emplace_back(ConcurrentFillBenchmarkThread(
+ table_, key_gen_, bytes_written, bytes_read, sequence_,
+ num_write_ops_per_thread_, read_hits, &threads_done));
+ for (int i = 1; i < FLAGS_num_threads; ++i) {
+ threads->emplace_back(
+ ReadThreadType(table_, key_gen_, bytes_written, bytes_read, sequence_,
+ num_read_ops_per_thread_, read_hits, &threads_done));
+ }
+ for (auto& thread : *threads) {
+ thread.join();
+ }
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+void PrintWarnings() {
+#if defined(__GNUC__) && !defined(__OPTIMIZE__)
+ fprintf(stdout,
+ "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n");
+#endif
+#ifndef NDEBUG
+ fprintf(stdout,
+ "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
+#endif
+}
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [OPTIONS]...");
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ PrintWarnings();
+
+ ROCKSDB_NAMESPACE::Options options;
+
+ std::unique_ptr<ROCKSDB_NAMESPACE::MemTableRepFactory> factory;
+ if (FLAGS_memtablerep == "skiplist") {
+ factory.reset(new ROCKSDB_NAMESPACE::SkipListFactory);
+#ifndef ROCKSDB_LITE
+ } else if (FLAGS_memtablerep == "vector") {
+ factory.reset(new ROCKSDB_NAMESPACE::VectorRepFactory);
+ } else if (FLAGS_memtablerep == "hashskiplist") {
+ factory.reset(ROCKSDB_NAMESPACE::NewHashSkipListRepFactory(
+ FLAGS_bucket_count, FLAGS_hashskiplist_height,
+ FLAGS_hashskiplist_branching_factor));
+ options.prefix_extractor.reset(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(FLAGS_prefix_length));
+ } else if (FLAGS_memtablerep == "hashlinklist") {
+ factory.reset(ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(
+ FLAGS_bucket_count, FLAGS_huge_page_tlb_size,
+ FLAGS_bucket_entries_logging_threshold,
+ FLAGS_if_log_bucket_dist_when_flash, FLAGS_threshold_use_skiplist));
+ options.prefix_extractor.reset(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(FLAGS_prefix_length));
+#endif // ROCKSDB_LITE
+ } else {
+ fprintf(stdout, "Unknown memtablerep: %s\n", FLAGS_memtablerep.c_str());
+ exit(1);
+ }
+
+ ROCKSDB_NAMESPACE::InternalKeyComparator internal_key_comp(
+ ROCKSDB_NAMESPACE::BytewiseComparator());
+ ROCKSDB_NAMESPACE::MemTable::KeyComparator key_comp(internal_key_comp);
+ ROCKSDB_NAMESPACE::Arena arena;
+ ROCKSDB_NAMESPACE::WriteBufferManager wb(FLAGS_write_buffer_size);
+ uint64_t sequence;
+ auto createMemtableRep = [&] {
+ sequence = 0;
+ return factory->CreateMemTableRep(key_comp, &arena,
+ options.prefix_extractor.get(),
+ options.info_log.get());
+ };
+ std::unique_ptr<ROCKSDB_NAMESPACE::MemTableRep> memtablerep;
+ ROCKSDB_NAMESPACE::Random64 rng(FLAGS_seed);
+ const char* benchmarks = FLAGS_benchmarks.c_str();
+ while (benchmarks != nullptr) {
+ std::unique_ptr<ROCKSDB_NAMESPACE::KeyGenerator> key_gen;
+ const char* sep = strchr(benchmarks, ',');
+ ROCKSDB_NAMESPACE::Slice name;
+ if (sep == nullptr) {
+ name = benchmarks;
+ benchmarks = nullptr;
+ } else {
+ name = ROCKSDB_NAMESPACE::Slice(benchmarks, sep - benchmarks);
+ benchmarks = sep + 1;
+ }
+ std::unique_ptr<ROCKSDB_NAMESPACE::Benchmark> benchmark;
+ if (name == ROCKSDB_NAMESPACE::Slice("fillseq")) {
+ memtablerep.reset(createMemtableRep());
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::SEQUENTIAL, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::FillBenchmark(
+ memtablerep.get(), key_gen.get(), &sequence));
+ } else if (name == ROCKSDB_NAMESPACE::Slice("fillrandom")) {
+ memtablerep.reset(createMemtableRep());
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::UNIQUE_RANDOM, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::FillBenchmark(
+ memtablerep.get(), key_gen.get(), &sequence));
+ } else if (name == ROCKSDB_NAMESPACE::Slice("readrandom")) {
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::RANDOM, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::ReadBenchmark(
+ memtablerep.get(), key_gen.get(), &sequence));
+ } else if (name == ROCKSDB_NAMESPACE::Slice("readseq")) {
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::SEQUENTIAL, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::SeqReadBenchmark(memtablerep.get(),
+ &sequence));
+ } else if (name == ROCKSDB_NAMESPACE::Slice("readwrite")) {
+ memtablerep.reset(createMemtableRep());
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::RANDOM, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::ReadWriteBenchmark<
+ ROCKSDB_NAMESPACE::ConcurrentReadBenchmarkThread>(
+ memtablerep.get(), key_gen.get(), &sequence));
+ } else if (name == ROCKSDB_NAMESPACE::Slice("seqreadwrite")) {
+ memtablerep.reset(createMemtableRep());
+ key_gen.reset(new ROCKSDB_NAMESPACE::KeyGenerator(
+ &rng, ROCKSDB_NAMESPACE::RANDOM, FLAGS_num_operations));
+ benchmark.reset(new ROCKSDB_NAMESPACE::ReadWriteBenchmark<
+ ROCKSDB_NAMESPACE::SeqConcurrentReadBenchmarkThread>(
+ memtablerep.get(), key_gen.get(), &sequence));
+ } else {
+ std::cout << "WARNING: skipping unknown benchmark '" << name.ToString()
+ << std::endl;
+ continue;
+ }
+ std::cout << "Running " << name.ToString() << std::endl;
+ benchmark->Run();
+ }
+
+ return 0;
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/memtable/skiplist.h b/storage/rocksdb/rocksdb/memtable/skiplist.h
new file mode 100644
index 00000000..52818e30
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/skiplist.h
@@ -0,0 +1,496 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Thread safety
+// -------------
+//
+// Writes require external synchronization, most likely a mutex.
+// Reads require a guarantee that the SkipList will not be destroyed
+// while the read is in progress. Apart from that, reads progress
+// without any internal locking or synchronization.
+//
+// Invariants:
+//
+// (1) Allocated nodes are never deleted until the SkipList is
+// destroyed. This is trivially guaranteed by the code since we
+// never delete any skip list nodes.
+//
+// (2) The contents of a Node except for the next/prev pointers are
+// immutable after the Node has been linked into the SkipList.
+// Only Insert() modifies the list, and it is careful to initialize
+// a node and use release-stores to publish the nodes in one or
+// more lists.
+//
+// ... prev vs. next pointer ordering ...
+//
+
+#pragma once
+#include <assert.h>
+#include <stdlib.h>
+#include <atomic>
+#include "memory/allocator.h"
+#include "port/port.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+template<typename Key, class Comparator>
+class SkipList {
+ private:
+ struct Node;
+
+ public:
+ // Create a new SkipList object that will use "cmp" for comparing keys,
+ // and will allocate memory using "*allocator". Objects allocated in the
+ // allocator must remain allocated for the lifetime of the skiplist object.
+ explicit SkipList(Comparator cmp, Allocator* allocator,
+ int32_t max_height = 12, int32_t branching_factor = 4);
+ // No copying allowed
+ SkipList(const SkipList&) = delete;
+ void operator=(const SkipList&) = delete;
+
+ // Insert key into the list.
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ void Insert(const Key& key);
+
+ // Returns true iff an entry that compares equal to key is in the list.
+ bool Contains(const Key& key) const;
+
+ // Return estimated number of entries smaller than `key`.
+ uint64_t EstimateCount(const Key& key) const;
+
+ // Iteration over the contents of a skip list
+ class Iterator {
+ public:
+ // Initialize an iterator over the specified list.
+ // The returned iterator is not valid.
+ explicit Iterator(const SkipList* list);
+
+ // Change the underlying skiplist used for this iterator
+ // This enables us not changing the iterator without deallocating
+ // an old one and then allocating a new one
+ void SetList(const SkipList* list);
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const;
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const Key& key() const;
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next();
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev();
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Key& target);
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const Key& target);
+
+ // Position at the first entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToFirst();
+
+ // Position at the last entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToLast();
+
+ private:
+ const SkipList* list_;
+ Node* node_;
+ // Intentionally copyable
+ };
+
+ private:
+ const uint16_t kMaxHeight_;
+ const uint16_t kBranching_;
+ const uint32_t kScaledInverseBranching_;
+
+ // Immutable after construction
+ Comparator const compare_;
+ Allocator* const allocator_; // Allocator used for allocations of nodes
+
+ Node* const head_;
+
+ // Modified only by Insert(). Read racily by readers, but stale
+ // values are ok.
+ std::atomic<int> max_height_; // Height of the entire list
+
+ // Used for optimizing sequential insert patterns. Tricky. prev_[i] for
+ // i up to max_height_ is the predecessor of prev_[0] and prev_height_
+ // is the height of prev_[0]. prev_[0] can only be equal to head before
+ // insertion, in which case max_height_ and prev_height_ are 1.
+ Node** prev_;
+ int32_t prev_height_;
+
+ inline int GetMaxHeight() const {
+ return max_height_.load(std::memory_order_relaxed);
+ }
+
+ Node* NewNode(const Key& key, int height);
+ int RandomHeight();
+ bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
+ bool LessThan(const Key& a, const Key& b) const {
+ return (compare_(a, b) < 0);
+ }
+
+ // Return true if key is greater than the data stored in "n"
+ bool KeyIsAfterNode(const Key& key, Node* n) const;
+
+ // Returns the earliest node with a key >= key.
+ // Return nullptr if there is no such node.
+ Node* FindGreaterOrEqual(const Key& key) const;
+
+ // Return the latest node with a key < key.
+ // Return head_ if there is no such node.
+ // Fills prev[level] with pointer to previous node at "level" for every
+ // level in [0..max_height_-1], if prev is non-null.
+ Node* FindLessThan(const Key& key, Node** prev = nullptr) const;
+
+ // Return the last node in the list.
+ // Return head_ if list is empty.
+ Node* FindLast() const;
+};
+
+// Implementation details follow
+template<typename Key, class Comparator>
+struct SkipList<Key, Comparator>::Node {
+ explicit Node(const Key& k) : key(k) { }
+
+ Key const key;
+
+ // Accessors/mutators for links. Wrapped in methods so we can
+ // add the appropriate barriers as necessary.
+ Node* Next(int n) {
+ assert(n >= 0);
+ // Use an 'acquire load' so that we observe a fully initialized
+ // version of the returned Node.
+ return (next_[n].load(std::memory_order_acquire));
+ }
+ void SetNext(int n, Node* x) {
+ assert(n >= 0);
+ // Use a 'release store' so that anybody who reads through this
+ // pointer observes a fully initialized version of the inserted node.
+ next_[n].store(x, std::memory_order_release);
+ }
+
+ // No-barrier variants that can be safely used in a few locations.
+ Node* NoBarrier_Next(int n) {
+ assert(n >= 0);
+ return next_[n].load(std::memory_order_relaxed);
+ }
+ void NoBarrier_SetNext(int n, Node* x) {
+ assert(n >= 0);
+ next_[n].store(x, std::memory_order_relaxed);
+ }
+
+ private:
+ // Array of length equal to the node height. next_[0] is lowest level link.
+ std::atomic<Node*> next_[1];
+};
+
+template<typename Key, class Comparator>
+typename SkipList<Key, Comparator>::Node*
+SkipList<Key, Comparator>::NewNode(const Key& key, int height) {
+ char* mem = allocator_->AllocateAligned(
+ sizeof(Node) + sizeof(std::atomic<Node*>) * (height - 1));
+ return new (mem) Node(key);
+}
+
+template<typename Key, class Comparator>
+inline SkipList<Key, Comparator>::Iterator::Iterator(const SkipList* list) {
+ SetList(list);
+}
+
+template<typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::SetList(const SkipList* list) {
+ list_ = list;
+ node_ = nullptr;
+}
+
+template<typename Key, class Comparator>
+inline bool SkipList<Key, Comparator>::Iterator::Valid() const {
+ return node_ != nullptr;
+}
+
+template<typename Key, class Comparator>
+inline const Key& SkipList<Key, Comparator>::Iterator::key() const {
+ assert(Valid());
+ return node_->key;
+}
+
+template<typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::Next() {
+ assert(Valid());
+ node_ = node_->Next(0);
+}
+
+template<typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::Prev() {
+ // Instead of using explicit "prev" links, we just search for the
+ // last node that falls before key.
+ assert(Valid());
+ node_ = list_->FindLessThan(node_->key);
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+}
+
+template<typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::Seek(const Key& target) {
+ node_ = list_->FindGreaterOrEqual(target);
+}
+
+template <typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::SeekForPrev(
+ const Key& target) {
+ Seek(target);
+ if (!Valid()) {
+ SeekToLast();
+ }
+ while (Valid() && list_->LessThan(target, key())) {
+ Prev();
+ }
+}
+
+template <typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::SeekToFirst() {
+ node_ = list_->head_->Next(0);
+}
+
+template<typename Key, class Comparator>
+inline void SkipList<Key, Comparator>::Iterator::SeekToLast() {
+ node_ = list_->FindLast();
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+}
+
+template<typename Key, class Comparator>
+int SkipList<Key, Comparator>::RandomHeight() {
+ auto rnd = Random::GetTLSInstance();
+
+ // Increase height with probability 1 in kBranching
+ int height = 1;
+ while (height < kMaxHeight_ && rnd->Next() < kScaledInverseBranching_) {
+ height++;
+ }
+ assert(height > 0);
+ assert(height <= kMaxHeight_);
+ return height;
+}
+
+template<typename Key, class Comparator>
+bool SkipList<Key, Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
+ // nullptr n is considered infinite
+ return (n != nullptr) && (compare_(n->key, key) < 0);
+}
+
+template<typename Key, class Comparator>
+typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::
+ FindGreaterOrEqual(const Key& key) const {
+ // Note: It looks like we could reduce duplication by implementing
+ // this function as FindLessThan(key)->Next(0), but we wouldn't be able
+ // to exit early on equality and the result wouldn't even be correct.
+ // A concurrent insert might occur after FindLessThan(key) but before
+ // we get a chance to call Next(0).
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ Node* last_bigger = nullptr;
+ while (true) {
+ assert(x != nullptr);
+ Node* next = x->Next(level);
+ // Make sure the lists are sorted
+ assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x));
+ // Make sure we haven't overshot during our search
+ assert(x == head_ || KeyIsAfterNode(key, x));
+ int cmp = (next == nullptr || next == last_bigger)
+ ? 1 : compare_(next->key, key);
+ if (cmp == 0 || (cmp > 0 && level == 0)) {
+ return next;
+ } else if (cmp < 0) {
+ // Keep searching in this list
+ x = next;
+ } else {
+ // Switch to next list, reuse compare_() result
+ last_bigger = next;
+ level--;
+ }
+ }
+}
+
+template<typename Key, class Comparator>
+typename SkipList<Key, Comparator>::Node*
+SkipList<Key, Comparator>::FindLessThan(const Key& key, Node** prev) const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ // KeyIsAfter(key, last_not_after) is definitely false
+ Node* last_not_after = nullptr;
+ while (true) {
+ assert(x != nullptr);
+ Node* next = x->Next(level);
+ assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x));
+ assert(x == head_ || KeyIsAfterNode(key, x));
+ if (next != last_not_after && KeyIsAfterNode(key, next)) {
+ // Keep searching in this list
+ x = next;
+ } else {
+ if (prev != nullptr) {
+ prev[level] = x;
+ }
+ if (level == 0) {
+ return x;
+ } else {
+ // Switch to next list, reuse KeyIUsAfterNode() result
+ last_not_after = next;
+ level--;
+ }
+ }
+ }
+}
+
+template<typename Key, class Comparator>
+typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::FindLast()
+ const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ Node* next = x->Next(level);
+ if (next == nullptr) {
+ if (level == 0) {
+ return x;
+ } else {
+ // Switch to next list
+ level--;
+ }
+ } else {
+ x = next;
+ }
+ }
+}
+
+template <typename Key, class Comparator>
+uint64_t SkipList<Key, Comparator>::EstimateCount(const Key& key) const {
+ uint64_t count = 0;
+
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ assert(x == head_ || compare_(x->key, key) < 0);
+ Node* next = x->Next(level);
+ if (next == nullptr || compare_(next->key, key) >= 0) {
+ if (level == 0) {
+ return count;
+ } else {
+ // Switch to next list
+ count *= kBranching_;
+ level--;
+ }
+ } else {
+ x = next;
+ count++;
+ }
+ }
+}
+
+template <typename Key, class Comparator>
+SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
+ int32_t max_height,
+ int32_t branching_factor)
+ : kMaxHeight_(static_cast<uint16_t>(max_height)),
+ kBranching_(static_cast<uint16_t>(branching_factor)),
+ kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
+ compare_(cmp),
+ allocator_(allocator),
+ head_(NewNode(0 /* any key will do */, max_height)),
+ max_height_(1),
+ prev_height_(1) {
+ assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
+ assert(branching_factor > 0 &&
+ kBranching_ == static_cast<uint32_t>(branching_factor));
+ assert(kScaledInverseBranching_ > 0);
+ // Allocate the prev_ Node* array, directly from the passed-in allocator.
+ // prev_ does not need to be freed, as its life cycle is tied up with
+ // the allocator as a whole.
+ prev_ = reinterpret_cast<Node**>(
+ allocator_->AllocateAligned(sizeof(Node*) * kMaxHeight_));
+ for (int i = 0; i < kMaxHeight_; i++) {
+ head_->SetNext(i, nullptr);
+ prev_[i] = head_;
+ }
+}
+
+template<typename Key, class Comparator>
+void SkipList<Key, Comparator>::Insert(const Key& key) {
+ // fast path for sequential insertion
+ if (!KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) &&
+ (prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) {
+ assert(prev_[0] != head_ || (prev_height_ == 1 && GetMaxHeight() == 1));
+
+ // Outside of this method prev_[1..max_height_] is the predecessor
+ // of prev_[0], and prev_height_ refers to prev_[0]. Inside Insert
+ // prev_[0..max_height - 1] is the predecessor of key. Switch from
+ // the external state to the internal
+ for (int i = 1; i < prev_height_; i++) {
+ prev_[i] = prev_[0];
+ }
+ } else {
+ // TODO(opt): we could use a NoBarrier predecessor search as an
+ // optimization for architectures where memory_order_acquire needs
+ // a synchronization instruction. Doesn't matter on x86
+ FindLessThan(key, prev_);
+ }
+
+ // Our data structure does not allow duplicate insertion
+ assert(prev_[0]->Next(0) == nullptr || !Equal(key, prev_[0]->Next(0)->key));
+
+ int height = RandomHeight();
+ if (height > GetMaxHeight()) {
+ for (int i = GetMaxHeight(); i < height; i++) {
+ prev_[i] = head_;
+ }
+ //fprintf(stderr, "Change height from %d to %d\n", max_height_, height);
+
+ // It is ok to mutate max_height_ without any synchronization
+ // with concurrent readers. A concurrent reader that observes
+ // the new value of max_height_ will see either the old value of
+ // new level pointers from head_ (nullptr), or a new value set in
+ // the loop below. In the former case the reader will
+ // immediately drop to the next level since nullptr sorts after all
+ // keys. In the latter case the reader will use the new node.
+ max_height_.store(height, std::memory_order_relaxed);
+ }
+
+ Node* x = NewNode(key, height);
+ for (int i = 0; i < height; i++) {
+ // NoBarrier_SetNext() suffices since we will add a barrier when
+ // we publish a pointer to "x" in prev[i].
+ x->NoBarrier_SetNext(i, prev_[i]->NoBarrier_Next(i));
+ prev_[i]->SetNext(i, x);
+ }
+ prev_[0] = x;
+ prev_height_ = height;
+}
+
+template<typename Key, class Comparator>
+bool SkipList<Key, Comparator>::Contains(const Key& key) const {
+ Node* x = FindGreaterOrEqual(key);
+ if (x != nullptr && Equal(key, x->key)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/skiplist_test.cc b/storage/rocksdb/rocksdb/memtable/skiplist_test.cc
new file mode 100644
index 00000000..18990eab
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/skiplist_test.cc
@@ -0,0 +1,388 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <set>
+#include "memory/arena.h"
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+#include "util/hash.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+typedef uint64_t Key;
+
+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<Key, TestComparator> list(cmp, &arena);
+ ASSERT_TRUE(!list.Contains(10));
+
+ SkipList<Key, TestComparator>::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<Key> keys;
+ Arena arena;
+ TestComparator cmp;
+ SkipList<Key, TestComparator> 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<Key, TestComparator>::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<Key, TestComparator>::Iterator iter(&list);
+ iter.Seek(i);
+
+ // Compare against model iterator
+ std::set<Key>::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<Key, TestComparator>::Iterator iter(&list);
+ iter.SeekForPrev(i);
+
+ // Compare against model iterator
+ std::set<Key>::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:
+// <key,gen,hash>
+// 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<char*>(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<int> 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<Key, TestComparator> 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<Key, TestComparator>::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<uint64_t>(initial_state.Get(
+ static_cast<int>(key(pos))))))
+ << "key: " << key(pos) << "; gen: " << gen(pos)
+ << "; initgen: " << initial_state.Get(static_cast<int>(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<bool> 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<TestState*>(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/memtable/skiplistrep.cc b/storage/rocksdb/rocksdb/memtable/skiplistrep.cc
new file mode 100644
index 00000000..eec15626
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/skiplistrep.cc
@@ -0,0 +1,280 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.h"
+#include "memory/arena.h"
+#include "memtable/inlineskiplist.h"
+#include "rocksdb/memtablerep.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+class SkipListRep : public MemTableRep {
+ InlineSkipList<const MemTableRep::KeyComparator&> skip_list_;
+ const MemTableRep::KeyComparator& cmp_;
+ const SliceTransform* transform_;
+ const size_t lookahead_;
+
+ friend class LookaheadIterator;
+public:
+ explicit SkipListRep(const MemTableRep::KeyComparator& compare,
+ Allocator* allocator, const SliceTransform* transform,
+ const size_t lookahead)
+ : MemTableRep(allocator),
+ skip_list_(compare, allocator),
+ cmp_(compare),
+ transform_(transform),
+ lookahead_(lookahead) {}
+
+ KeyHandle Allocate(const size_t len, char** buf) override {
+ *buf = skip_list_.AllocateKey(len);
+ return static_cast<KeyHandle>(*buf);
+ }
+
+ // Insert key into the list.
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ void Insert(KeyHandle handle) override {
+ skip_list_.Insert(static_cast<char*>(handle));
+ }
+
+ bool InsertKey(KeyHandle handle) override {
+ return skip_list_.Insert(static_cast<char*>(handle));
+ }
+
+ void InsertWithHint(KeyHandle handle, void** hint) override {
+ skip_list_.InsertWithHint(static_cast<char*>(handle), hint);
+ }
+
+ bool InsertKeyWithHint(KeyHandle handle, void** hint) override {
+ return skip_list_.InsertWithHint(static_cast<char*>(handle), hint);
+ }
+
+ void InsertWithHintConcurrently(KeyHandle handle, void** hint) override {
+ skip_list_.InsertWithHintConcurrently(static_cast<char*>(handle), hint);
+ }
+
+ bool InsertKeyWithHintConcurrently(KeyHandle handle, void** hint) override {
+ return skip_list_.InsertWithHintConcurrently(static_cast<char*>(handle),
+ hint);
+ }
+
+ void InsertConcurrently(KeyHandle handle) override {
+ skip_list_.InsertConcurrently(static_cast<char*>(handle));
+ }
+
+ bool InsertKeyConcurrently(KeyHandle handle) override {
+ return skip_list_.InsertConcurrently(static_cast<char*>(handle));
+ }
+
+ // Returns true iff an entry that compares equal to key is in the list.
+ bool Contains(const char* key) const override {
+ return skip_list_.Contains(key);
+ }
+
+ size_t ApproximateMemoryUsage() override {
+ // All memory is allocated through allocator; nothing to report here
+ return 0;
+ }
+
+ void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) override {
+ SkipListRep::Iterator iter(&skip_list_);
+ Slice dummy_slice;
+ for (iter.Seek(dummy_slice, k.memtable_key().data());
+ iter.Valid() && callback_func(callback_args, iter.key()); iter.Next()) {
+ }
+ }
+
+ uint64_t ApproximateNumEntries(const Slice& start_ikey,
+ const Slice& end_ikey) override {
+ std::string tmp;
+ uint64_t start_count =
+ skip_list_.EstimateCount(EncodeKey(&tmp, start_ikey));
+ uint64_t end_count = skip_list_.EstimateCount(EncodeKey(&tmp, end_ikey));
+ return (end_count >= start_count) ? (end_count - start_count) : 0;
+ }
+
+ ~SkipListRep() override {}
+
+ // Iteration over the contents of a skip list
+ class Iterator : public MemTableRep::Iterator {
+ InlineSkipList<const MemTableRep::KeyComparator&>::Iterator iter_;
+
+ public:
+ // Initialize an iterator over the specified list.
+ // The returned iterator is not valid.
+ explicit Iterator(
+ const InlineSkipList<const MemTableRep::KeyComparator&>* list)
+ : iter_(list) {}
+
+ ~Iterator() override {}
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const override { return iter_.Valid(); }
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const override { return iter_.key(); }
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next() override { iter_.Next(); }
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev() override { iter_.Prev(); }
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& user_key, const char* memtable_key) override {
+ if (memtable_key != nullptr) {
+ iter_.Seek(memtable_key);
+ } else {
+ iter_.Seek(EncodeKey(&tmp_, user_key));
+ }
+ }
+
+ // Retreat to the last entry with a key <= target
+ void SeekForPrev(const Slice& user_key, const char* memtable_key) override {
+ if (memtable_key != nullptr) {
+ iter_.SeekForPrev(memtable_key);
+ } else {
+ iter_.SeekForPrev(EncodeKey(&tmp_, user_key));
+ }
+ }
+
+ // Position at the first entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToFirst() override { iter_.SeekToFirst(); }
+
+ // Position at the last entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToLast() override { iter_.SeekToLast(); }
+
+ protected:
+ std::string tmp_; // For passing to EncodeKey
+ };
+
+ // Iterator over the contents of a skip list which also keeps track of the
+ // previously visited node. In Seek(), it examines a few nodes after it
+ // first, falling back to O(log n) search from the head of the list only if
+ // the target key hasn't been found.
+ class LookaheadIterator : public MemTableRep::Iterator {
+ public:
+ explicit LookaheadIterator(const SkipListRep& rep) :
+ rep_(rep), iter_(&rep_.skip_list_), prev_(iter_) {}
+
+ ~LookaheadIterator() override {}
+
+ bool Valid() const override { return iter_.Valid(); }
+
+ const char* key() const override {
+ assert(Valid());
+ return iter_.key();
+ }
+
+ void Next() override {
+ assert(Valid());
+
+ bool advance_prev = true;
+ if (prev_.Valid()) {
+ auto k1 = rep_.UserKey(prev_.key());
+ auto k2 = rep_.UserKey(iter_.key());
+
+ if (k1.compare(k2) == 0) {
+ // same user key, don't move prev_
+ advance_prev = false;
+ } else if (rep_.transform_) {
+ // only advance prev_ if it has the same prefix as iter_
+ auto t1 = rep_.transform_->Transform(k1);
+ auto t2 = rep_.transform_->Transform(k2);
+ advance_prev = t1.compare(t2) == 0;
+ }
+ }
+
+ if (advance_prev) {
+ prev_ = iter_;
+ }
+ iter_.Next();
+ }
+
+ void Prev() override {
+ assert(Valid());
+ iter_.Prev();
+ prev_ = iter_;
+ }
+
+ void Seek(const Slice& internal_key, const char* memtable_key) override {
+ const char *encoded_key =
+ (memtable_key != nullptr) ?
+ memtable_key : EncodeKey(&tmp_, internal_key);
+
+ if (prev_.Valid() && rep_.cmp_(encoded_key, prev_.key()) >= 0) {
+ // prev_.key() is smaller or equal to our target key; do a quick
+ // linear search (at most lookahead_ steps) starting from prev_
+ iter_ = prev_;
+
+ size_t cur = 0;
+ while (cur++ <= rep_.lookahead_ && iter_.Valid()) {
+ if (rep_.cmp_(encoded_key, iter_.key()) <= 0) {
+ return;
+ }
+ Next();
+ }
+ }
+
+ iter_.Seek(encoded_key);
+ prev_ = iter_;
+ }
+
+ void SeekForPrev(const Slice& internal_key,
+ const char* memtable_key) override {
+ const char* encoded_key = (memtable_key != nullptr)
+ ? memtable_key
+ : EncodeKey(&tmp_, internal_key);
+ iter_.SeekForPrev(encoded_key);
+ prev_ = iter_;
+ }
+
+ void SeekToFirst() override {
+ iter_.SeekToFirst();
+ prev_ = iter_;
+ }
+
+ void SeekToLast() override {
+ iter_.SeekToLast();
+ prev_ = iter_;
+ }
+
+ protected:
+ std::string tmp_; // For passing to EncodeKey
+
+ private:
+ const SkipListRep& rep_;
+ InlineSkipList<const MemTableRep::KeyComparator&>::Iterator iter_;
+ InlineSkipList<const MemTableRep::KeyComparator&>::Iterator prev_;
+ };
+
+ MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override {
+ if (lookahead_ > 0) {
+ void *mem =
+ arena ? arena->AllocateAligned(sizeof(SkipListRep::LookaheadIterator))
+ : operator new(sizeof(SkipListRep::LookaheadIterator));
+ return new (mem) SkipListRep::LookaheadIterator(*this);
+ } else {
+ void *mem =
+ arena ? arena->AllocateAligned(sizeof(SkipListRep::Iterator))
+ : operator new(sizeof(SkipListRep::Iterator));
+ return new (mem) SkipListRep::Iterator(&skip_list_);
+ }
+ }
+};
+}
+
+MemTableRep* SkipListFactory::CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform* transform, Logger* /*logger*/) {
+ return new SkipListRep(compare, allocator, transform, lookahead_);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/stl_wrappers.h b/storage/rocksdb/rocksdb/memtable/stl_wrappers.h
new file mode 100644
index 00000000..e9f8f214
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/stl_wrappers.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace stl_wrappers {
+
+class Base {
+ protected:
+ const MemTableRep::KeyComparator& compare_;
+ explicit Base(const MemTableRep::KeyComparator& compare)
+ : compare_(compare) {}
+};
+
+struct Compare : private Base {
+ explicit Compare(const MemTableRep::KeyComparator& compare) : Base(compare) {}
+ inline bool operator()(const char* a, const char* b) const {
+ return compare_(a, b) < 0;
+ }
+};
+
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/vectorrep.cc b/storage/rocksdb/rocksdb/memtable/vectorrep.cc
new file mode 100644
index 00000000..3797e46c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/vectorrep.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "rocksdb/memtablerep.h"
+
+#include <unordered_set>
+#include <set>
+#include <memory>
+#include <algorithm>
+#include <type_traits>
+
+#include "db/memtable.h"
+#include "memory/arena.h"
+#include "memtable/stl_wrappers.h"
+#include "port/port.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+
+using namespace stl_wrappers;
+
+class VectorRep : public MemTableRep {
+ public:
+ VectorRep(const KeyComparator& compare, Allocator* allocator, size_t count);
+
+ // Insert key into the collection. (The caller will pack key and value into a
+ // single buffer and pass that in as the parameter to Insert)
+ // REQUIRES: nothing that compares equal to key is currently in the
+ // collection.
+ void Insert(KeyHandle handle) override;
+
+ // Returns true iff an entry that compares equal to key is in the collection.
+ bool Contains(const char* key) const override;
+
+ void MarkReadOnly() override;
+
+ size_t ApproximateMemoryUsage() override;
+
+ void Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) override;
+
+ ~VectorRep() override {}
+
+ class Iterator : public MemTableRep::Iterator {
+ class VectorRep* vrep_;
+ std::shared_ptr<std::vector<const char*>> bucket_;
+ std::vector<const char*>::const_iterator mutable cit_;
+ const KeyComparator& compare_;
+ std::string tmp_; // For passing to EncodeKey
+ bool mutable sorted_;
+ void DoSort() const;
+ public:
+ explicit Iterator(class VectorRep* vrep,
+ std::shared_ptr<std::vector<const char*>> bucket,
+ const KeyComparator& compare);
+
+ // Initialize an iterator over the specified collection.
+ // The returned iterator is not valid.
+ // explicit Iterator(const MemTableRep* collection);
+ ~Iterator() override{};
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const override;
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const char* key() const override;
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next() override;
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev() override;
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Slice& user_key, const char* memtable_key) override;
+
+ // Advance to the first entry with a key <= target
+ void SeekForPrev(const Slice& user_key, const char* memtable_key) override;
+
+ // Position at the first entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToFirst() override;
+
+ // Position at the last entry in collection.
+ // Final state of iterator is Valid() iff collection is not empty.
+ void SeekToLast() override;
+ };
+
+ // Return an iterator over the keys in this representation.
+ MemTableRep::Iterator* GetIterator(Arena* arena) override;
+
+ private:
+ friend class Iterator;
+ typedef std::vector<const char*> Bucket;
+ std::shared_ptr<Bucket> bucket_;
+ mutable port::RWMutex rwlock_;
+ bool immutable_;
+ bool sorted_;
+ const KeyComparator& compare_;
+};
+
+void VectorRep::Insert(KeyHandle handle) {
+ auto* key = static_cast<char*>(handle);
+ WriteLock l(&rwlock_);
+ assert(!immutable_);
+ bucket_->push_back(key);
+}
+
+// Returns true iff an entry that compares equal to key is in the collection.
+bool VectorRep::Contains(const char* key) const {
+ ReadLock l(&rwlock_);
+ return std::find(bucket_->begin(), bucket_->end(), key) != bucket_->end();
+}
+
+void VectorRep::MarkReadOnly() {
+ WriteLock l(&rwlock_);
+ immutable_ = true;
+}
+
+size_t VectorRep::ApproximateMemoryUsage() {
+ return
+ sizeof(bucket_) + sizeof(*bucket_) +
+ bucket_->size() *
+ sizeof(
+ std::remove_reference<decltype(*bucket_)>::type::value_type
+ );
+}
+
+VectorRep::VectorRep(const KeyComparator& compare, Allocator* allocator,
+ size_t count)
+ : MemTableRep(allocator),
+ bucket_(new Bucket()),
+ immutable_(false),
+ sorted_(false),
+ compare_(compare) {
+ bucket_.get()->reserve(count);
+}
+
+VectorRep::Iterator::Iterator(class VectorRep* vrep,
+ std::shared_ptr<std::vector<const char*>> bucket,
+ const KeyComparator& compare)
+: vrep_(vrep),
+ bucket_(bucket),
+ cit_(bucket_->end()),
+ compare_(compare),
+ sorted_(false) { }
+
+void VectorRep::Iterator::DoSort() const {
+ // vrep is non-null means that we are working on an immutable memtable
+ if (!sorted_ && vrep_ != nullptr) {
+ WriteLock l(&vrep_->rwlock_);
+ if (!vrep_->sorted_) {
+ std::sort(bucket_->begin(), bucket_->end(), Compare(compare_));
+ cit_ = bucket_->begin();
+ vrep_->sorted_ = true;
+ }
+ sorted_ = true;
+ }
+ if (!sorted_) {
+ std::sort(bucket_->begin(), bucket_->end(), Compare(compare_));
+ cit_ = bucket_->begin();
+ sorted_ = true;
+ }
+ assert(sorted_);
+ assert(vrep_ == nullptr || vrep_->sorted_);
+}
+
+// Returns true iff the iterator is positioned at a valid node.
+bool VectorRep::Iterator::Valid() const {
+ DoSort();
+ return cit_ != bucket_->end();
+}
+
+// Returns the key at the current position.
+// REQUIRES: Valid()
+const char* VectorRep::Iterator::key() const {
+ assert(sorted_);
+ return *cit_;
+}
+
+// Advances to the next position.
+// REQUIRES: Valid()
+void VectorRep::Iterator::Next() {
+ assert(sorted_);
+ if (cit_ == bucket_->end()) {
+ return;
+ }
+ ++cit_;
+}
+
+// Advances to the previous position.
+// REQUIRES: Valid()
+void VectorRep::Iterator::Prev() {
+ assert(sorted_);
+ if (cit_ == bucket_->begin()) {
+ // If you try to go back from the first element, the iterator should be
+ // invalidated. So we set it to past-the-end. This means that you can
+ // treat the container circularly.
+ cit_ = bucket_->end();
+ } else {
+ --cit_;
+ }
+}
+
+// Advance to the first entry with a key >= target
+void VectorRep::Iterator::Seek(const Slice& user_key,
+ const char* memtable_key) {
+ DoSort();
+ // Do binary search to find first value not less than the target
+ const char* encoded_key =
+ (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, user_key);
+ cit_ = std::equal_range(bucket_->begin(),
+ bucket_->end(),
+ encoded_key,
+ [this] (const char* a, const char* b) {
+ return compare_(a, b) < 0;
+ }).first;
+}
+
+// Advance to the first entry with a key <= target
+void VectorRep::Iterator::SeekForPrev(const Slice& /*user_key*/,
+ const char* /*memtable_key*/) {
+ assert(false);
+}
+
+// Position at the first entry in collection.
+// Final state of iterator is Valid() iff collection is not empty.
+void VectorRep::Iterator::SeekToFirst() {
+ DoSort();
+ cit_ = bucket_->begin();
+}
+
+// Position at the last entry in collection.
+// Final state of iterator is Valid() iff collection is not empty.
+void VectorRep::Iterator::SeekToLast() {
+ DoSort();
+ cit_ = bucket_->end();
+ if (bucket_->size() != 0) {
+ --cit_;
+ }
+}
+
+void VectorRep::Get(const LookupKey& k, void* callback_args,
+ bool (*callback_func)(void* arg, const char* entry)) {
+ rwlock_.ReadLock();
+ VectorRep* vector_rep;
+ std::shared_ptr<Bucket> bucket;
+ if (immutable_) {
+ vector_rep = this;
+ } else {
+ vector_rep = nullptr;
+ bucket.reset(new Bucket(*bucket_)); // make a copy
+ }
+ VectorRep::Iterator iter(vector_rep, immutable_ ? bucket_ : bucket, compare_);
+ rwlock_.ReadUnlock();
+
+ for (iter.Seek(k.user_key(), k.memtable_key().data());
+ iter.Valid() && callback_func(callback_args, iter.key()); iter.Next()) {
+ }
+}
+
+MemTableRep::Iterator* VectorRep::GetIterator(Arena* arena) {
+ char* mem = nullptr;
+ if (arena != nullptr) {
+ mem = arena->AllocateAligned(sizeof(Iterator));
+ }
+ ReadLock l(&rwlock_);
+ // Do not sort here. The sorting would be done the first time
+ // a Seek is performed on the iterator.
+ if (immutable_) {
+ if (arena == nullptr) {
+ return new Iterator(this, bucket_, compare_);
+ } else {
+ return new (mem) Iterator(this, bucket_, compare_);
+ }
+ } else {
+ std::shared_ptr<Bucket> tmp;
+ tmp.reset(new Bucket(*bucket_)); // make a copy
+ if (arena == nullptr) {
+ return new Iterator(nullptr, tmp, compare_);
+ } else {
+ return new (mem) Iterator(nullptr, tmp, compare_);
+ }
+ }
+}
+} // anon namespace
+
+MemTableRep* VectorRepFactory::CreateMemTableRep(
+ const MemTableRep::KeyComparator& compare, Allocator* allocator,
+ const SliceTransform*, Logger* /*logger*/) {
+ return new VectorRep(compare, allocator, count_);
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/memtable/write_buffer_manager.cc b/storage/rocksdb/rocksdb/memtable/write_buffer_manager.cc
new file mode 100644
index 00000000..8a2dc3b8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/write_buffer_manager.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <mutex>
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+#ifndef ROCKSDB_LITE
+namespace {
+const size_t kSizeDummyEntry = 256 * 1024;
+// The key will be longer than keys for blocks in SST files so they won't
+// conflict.
+const size_t kCacheKeyPrefix = kMaxVarint64Length * 4 + 1;
+} // namespace
+
+struct WriteBufferManager::CacheRep {
+ std::shared_ptr<Cache> cache_;
+ std::mutex cache_mutex_;
+ std::atomic<size_t> cache_allocated_size_;
+ // The non-prefix part will be updated according to the ID to use.
+ char cache_key_[kCacheKeyPrefix + kMaxVarint64Length];
+ uint64_t next_cache_key_id_ = 0;
+ std::vector<Cache::Handle*> dummy_handles_;
+
+ explicit CacheRep(std::shared_ptr<Cache> cache)
+ : cache_(cache), cache_allocated_size_(0) {
+ memset(cache_key_, 0, kCacheKeyPrefix);
+ size_t pointer_size = sizeof(const void*);
+ assert(pointer_size <= kCacheKeyPrefix);
+ memcpy(cache_key_, static_cast<const void*>(this), pointer_size);
+ }
+
+ Slice GetNextCacheKey() {
+ memset(cache_key_ + kCacheKeyPrefix, 0, kMaxVarint64Length);
+ char* end =
+ EncodeVarint64(cache_key_ + kCacheKeyPrefix, next_cache_key_id_++);
+ return Slice(cache_key_, static_cast<size_t>(end - cache_key_));
+ }
+};
+#else
+struct WriteBufferManager::CacheRep {};
+#endif // ROCKSDB_LITE
+
+WriteBufferManager::WriteBufferManager(size_t _buffer_size,
+ std::shared_ptr<Cache> cache)
+ : buffer_size_(_buffer_size),
+ mutable_limit_(buffer_size_ * 7 / 8),
+ memory_used_(0),
+ memory_active_(0),
+ cache_rep_(nullptr) {
+#ifndef ROCKSDB_LITE
+ if (cache) {
+ // Construct the cache key using the pointer to this.
+ cache_rep_.reset(new CacheRep(cache));
+ }
+#else
+ (void)cache;
+#endif // ROCKSDB_LITE
+}
+
+WriteBufferManager::~WriteBufferManager() {
+#ifndef ROCKSDB_LITE
+ if (cache_rep_) {
+ for (auto* handle : cache_rep_->dummy_handles_) {
+ cache_rep_->cache_->Release(handle, true);
+ }
+ }
+#endif // ROCKSDB_LITE
+}
+
+// Should only be called from write thread
+void WriteBufferManager::ReserveMemWithCache(size_t mem) {
+#ifndef ROCKSDB_LITE
+ assert(cache_rep_ != nullptr);
+ // Use a mutex to protect various data structures. Can be optimized to a
+ // lock-free solution if it ends up with a performance bottleneck.
+ std::lock_guard<std::mutex> lock(cache_rep_->cache_mutex_);
+
+ size_t new_mem_used = memory_used_.load(std::memory_order_relaxed) + mem;
+ memory_used_.store(new_mem_used, std::memory_order_relaxed);
+ while (new_mem_used > cache_rep_->cache_allocated_size_) {
+ // Expand size by at least 256KB.
+ // Add a dummy record to the cache
+ Cache::Handle* handle;
+ cache_rep_->cache_->Insert(cache_rep_->GetNextCacheKey(), nullptr,
+ kSizeDummyEntry, nullptr, &handle);
+ cache_rep_->dummy_handles_.push_back(handle);
+ cache_rep_->cache_allocated_size_ += kSizeDummyEntry;
+ }
+#else
+ (void)mem;
+#endif // ROCKSDB_LITE
+}
+
+void WriteBufferManager::FreeMemWithCache(size_t mem) {
+#ifndef ROCKSDB_LITE
+ assert(cache_rep_ != nullptr);
+ // Use a mutex to protect various data structures. Can be optimized to a
+ // lock-free solution if it ends up with a performance bottleneck.
+ std::lock_guard<std::mutex> lock(cache_rep_->cache_mutex_);
+ size_t new_mem_used = memory_used_.load(std::memory_order_relaxed) - mem;
+ memory_used_.store(new_mem_used, std::memory_order_relaxed);
+ // Gradually shrink memory costed in the block cache if the actual
+ // usage is less than 3/4 of what we reserve from the block cache.
+ // We do this because:
+ // 1. we don't pay the cost of the block cache immediately a memtable is
+ // freed, as block cache insert is expensive;
+ // 2. eventually, if we walk away from a temporary memtable size increase,
+ // we make sure shrink the memory costed in block cache over time.
+ // In this way, we only shrink costed memory showly even there is enough
+ // margin.
+ if (new_mem_used < cache_rep_->cache_allocated_size_ / 4 * 3 &&
+ cache_rep_->cache_allocated_size_ - kSizeDummyEntry > new_mem_used) {
+ assert(!cache_rep_->dummy_handles_.empty());
+ cache_rep_->cache_->Release(cache_rep_->dummy_handles_.back(), true);
+ cache_rep_->dummy_handles_.pop_back();
+ cache_rep_->cache_allocated_size_ -= kSizeDummyEntry;
+ }
+#else
+ (void)mem;
+#endif // ROCKSDB_LITE
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/memtable/write_buffer_manager_test.cc b/storage/rocksdb/rocksdb/memtable/write_buffer_manager_test.cc
new file mode 100644
index 00000000..4ea52348
--- /dev/null
+++ b/storage/rocksdb/rocksdb/memtable/write_buffer_manager_test.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteBufferManagerTest : public testing::Test {};
+
+#ifndef ROCKSDB_LITE
+TEST_F(WriteBufferManagerTest, ShouldFlush) {
+ // A write buffer manager of size 10MB
+ std::unique_ptr<WriteBufferManager> 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);
+ // 9MB total, 8MB mutable.
+ ASSERT_FALSE(wbf->ShouldFlush());
+}
+
+TEST_F(WriteBufferManagerTest, CacheCost) {
+ LRUCacheOptions co;
+ // 1GB cache
+ co.capacity = 1024 * 1024 * 1024;
+ co.num_shard_bits = 4;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ std::shared_ptr<Cache> cache = NewLRUCache(co);
+ // A write buffer manager of size 50MB
+ std::unique_ptr<WriteBufferManager> wbf(
+ new WriteBufferManager(50 * 1024 * 1024, cache));
+
+ // Allocate 333KB will allocate 512KB
+ wbf->ReserveMem(333 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 2 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 2 * 256 * 1024 + 10000);
+
+ // Allocate another 512KB
+ wbf->ReserveMem(512 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 4 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 4 * 256 * 1024 + 10000);
+
+ // Allocate another 10MB
+ wbf->ReserveMem(10 * 1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 11 * 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 11 * 1024 * 1024 + 10000);
+
+ // Free 1MB will not cause any change in cache cost
+ wbf->FreeMem(1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 11 * 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 11 * 1024 * 1024 + 10000);
+
+ ASSERT_FALSE(wbf->ShouldFlush());
+
+ // Allocate another 41MB
+ wbf->ReserveMem(41 * 1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 + 10000);
+ ASSERT_TRUE(wbf->ShouldFlush());
+
+ ASSERT_TRUE(wbf->ShouldFlush());
+
+ wbf->ScheduleFreeMem(20 * 1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 + 10000);
+
+ // Still need flush as the hard limit hits
+ ASSERT_TRUE(wbf->ShouldFlush());
+
+ // Free 20MB will releae 256KB from cache
+ wbf->FreeMem(20 * 1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 256 * 1024 + 10000);
+
+ ASSERT_FALSE(wbf->ShouldFlush());
+
+ // Every free will release 256KB if still not hit 3/4
+ wbf->FreeMem(16 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 2 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 2 * 256 * 1024 + 10000);
+
+ wbf->FreeMem(16 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 3 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 3 * 256 * 1024 + 10000);
+
+ // Reserve 512KB will not cause any change in cache cost
+ wbf->ReserveMem(512 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 3 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 3 * 256 * 1024 + 10000);
+
+ wbf->FreeMem(16 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 4 * 256 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 51 * 1024 * 1024 - 4 * 256 * 1024 + 10000);
+
+ // Destory write buffer manger should free everything
+ wbf.reset();
+ ASSERT_LT(cache->GetPinnedUsage(), 1024 * 1024);
+}
+
+TEST_F(WriteBufferManagerTest, NoCapCacheCost) {
+ // 1GB cache
+ std::shared_ptr<Cache> cache = NewLRUCache(1024 * 1024 * 1024, 4);
+ // A write buffer manager of size 256MB
+ std::unique_ptr<WriteBufferManager> wbf(new WriteBufferManager(0, cache));
+ // Allocate 1.5MB will allocate 2MB
+ wbf->ReserveMem(10 * 1024 * 1024);
+ ASSERT_GE(cache->GetPinnedUsage(), 10 * 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 10 * 1024 * 1024 + 10000);
+ ASSERT_FALSE(wbf->ShouldFlush());
+
+ wbf->FreeMem(9 * 1024 * 1024);
+ for (int i = 0; i < 40; i++) {
+ wbf->FreeMem(4 * 1024);
+ }
+ ASSERT_GE(cache->GetPinnedUsage(), 1024 * 1024);
+ ASSERT_LT(cache->GetPinnedUsage(), 1024 * 1024 + 10000);
+}
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/monitoring/file_read_sample.h b/storage/rocksdb/rocksdb/monitoring/file_read_sample.h
new file mode 100644
index 00000000..82a933e0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/file_read_sample.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "db/version_edit.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+static const uint32_t kFileReadSampleRate = 1024;
+extern bool should_sample_file_read();
+extern void sample_file_read_inc(FileMetaData*);
+
+inline bool should_sample_file_read() {
+ return (Random::GetTLSInstance()->Next() % kFileReadSampleRate == 307);
+}
+
+inline void sample_file_read_inc(FileMetaData* meta) {
+ meta->stats.num_reads_sampled.fetch_add(kFileReadSampleRate,
+ std::memory_order_relaxed);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/histogram.cc b/storage/rocksdb/rocksdb/monitoring/histogram.cc
new file mode 100644
index 00000000..55339f88
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/histogram.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/histogram.h"
+
+#include <stdio.h>
+#include <cassert>
+#include <cinttypes>
+#include <cmath>
+
+#include "port/port.h"
+#include "util/cast_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+HistogramBucketMapper::HistogramBucketMapper() {
+ // If you change this, you also need to change
+ // size of array buckets_ in HistogramImpl
+ bucketValues_ = {1, 2};
+ valueIndexMap_ = {{1, 0}, {2, 1}};
+ double bucket_val = static_cast<double>(bucketValues_.back());
+ while ((bucket_val = 1.5 * bucket_val) <= static_cast<double>(port::kMaxUint64)) {
+ bucketValues_.push_back(static_cast<uint64_t>(bucket_val));
+ // Extracts two most significant digits to make histogram buckets more
+ // human-readable. E.g., 172 becomes 170.
+ uint64_t pow_of_ten = 1;
+ while (bucketValues_.back() / 10 > 10) {
+ bucketValues_.back() /= 10;
+ pow_of_ten *= 10;
+ }
+ bucketValues_.back() *= pow_of_ten;
+ valueIndexMap_[bucketValues_.back()] = bucketValues_.size() - 1;
+ }
+ maxBucketValue_ = bucketValues_.back();
+ minBucketValue_ = bucketValues_.front();
+}
+
+size_t HistogramBucketMapper::IndexForValue(const uint64_t value) const {
+ if (value >= maxBucketValue_) {
+ return bucketValues_.size() - 1;
+ } else if ( value >= minBucketValue_ ) {
+ std::map<uint64_t, uint64_t>::const_iterator lowerBound =
+ valueIndexMap_.lower_bound(value);
+ if (lowerBound != valueIndexMap_.end()) {
+ return static_cast<size_t>(lowerBound->second);
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+}
+
+namespace {
+ const HistogramBucketMapper bucketMapper;
+}
+
+HistogramStat::HistogramStat()
+ : num_buckets_(bucketMapper.BucketCount()) {
+ assert(num_buckets_ == sizeof(buckets_) / sizeof(*buckets_));
+ Clear();
+}
+
+void HistogramStat::Clear() {
+ min_.store(bucketMapper.LastValue(), std::memory_order_relaxed);
+ max_.store(0, std::memory_order_relaxed);
+ num_.store(0, std::memory_order_relaxed);
+ sum_.store(0, std::memory_order_relaxed);
+ sum_squares_.store(0, std::memory_order_relaxed);
+ for (unsigned int b = 0; b < num_buckets_; b++) {
+ buckets_[b].store(0, std::memory_order_relaxed);
+ }
+};
+
+bool HistogramStat::Empty() const { return num() == 0; }
+
+void HistogramStat::Add(uint64_t value) {
+ // This function is designed to be lock free, as it's in the critical path
+ // of any operation. Each individual value is atomic and the order of updates
+ // by concurrent threads is tolerable.
+ const size_t index = bucketMapper.IndexForValue(value);
+ assert(index < num_buckets_);
+ buckets_[index].store(buckets_[index].load(std::memory_order_relaxed) + 1,
+ std::memory_order_relaxed);
+
+ uint64_t old_min = min();
+ if (value < old_min) {
+ min_.store(value, std::memory_order_relaxed);
+ }
+
+ uint64_t old_max = max();
+ if (value > old_max) {
+ max_.store(value, std::memory_order_relaxed);
+ }
+
+ num_.store(num_.load(std::memory_order_relaxed) + 1,
+ std::memory_order_relaxed);
+ sum_.store(sum_.load(std::memory_order_relaxed) + value,
+ std::memory_order_relaxed);
+ sum_squares_.store(
+ sum_squares_.load(std::memory_order_relaxed) + value * value,
+ std::memory_order_relaxed);
+}
+
+void HistogramStat::Merge(const HistogramStat& other) {
+ // This function needs to be performned with the outer lock acquired
+ // However, atomic operation on every member is still need, since Add()
+ // requires no lock and value update can still happen concurrently
+ uint64_t old_min = min();
+ uint64_t other_min = other.min();
+ while (other_min < old_min &&
+ !min_.compare_exchange_weak(old_min, other_min)) {}
+
+ uint64_t old_max = max();
+ uint64_t other_max = other.max();
+ while (other_max > old_max &&
+ !max_.compare_exchange_weak(old_max, other_max)) {}
+
+ num_.fetch_add(other.num(), std::memory_order_relaxed);
+ sum_.fetch_add(other.sum(), std::memory_order_relaxed);
+ sum_squares_.fetch_add(other.sum_squares(), std::memory_order_relaxed);
+ for (unsigned int b = 0; b < num_buckets_; b++) {
+ buckets_[b].fetch_add(other.bucket_at(b), std::memory_order_relaxed);
+ }
+}
+
+double HistogramStat::Median() const {
+ return Percentile(50.0);
+}
+
+double HistogramStat::Percentile(double p) const {
+ double threshold = num() * (p / 100.0);
+ uint64_t cumulative_sum = 0;
+ for (unsigned int b = 0; b < num_buckets_; b++) {
+ uint64_t bucket_value = bucket_at(b);
+ cumulative_sum += bucket_value;
+ if (cumulative_sum >= threshold) {
+ // Scale linearly within this bucket
+ uint64_t left_point = (b == 0) ? 0 : bucketMapper.BucketLimit(b-1);
+ uint64_t right_point = bucketMapper.BucketLimit(b);
+ uint64_t left_sum = cumulative_sum - bucket_value;
+ uint64_t right_sum = cumulative_sum;
+ double pos = 0;
+ uint64_t right_left_diff = right_sum - left_sum;
+ if (right_left_diff != 0) {
+ pos = (threshold - left_sum) / right_left_diff;
+ }
+ double r = left_point + (right_point - left_point) * pos;
+ uint64_t cur_min = min();
+ uint64_t cur_max = max();
+ if (r < cur_min) r = static_cast<double>(cur_min);
+ if (r > cur_max) r = static_cast<double>(cur_max);
+ return r;
+ }
+ }
+ return static_cast<double>(max());
+}
+
+double HistogramStat::Average() const {
+ uint64_t cur_num = num();
+ uint64_t cur_sum = sum();
+ if (cur_num == 0) return 0;
+ return static_cast<double>(cur_sum) / static_cast<double>(cur_num);
+}
+
+double HistogramStat::StandardDeviation() const {
+ uint64_t cur_num = num();
+ uint64_t cur_sum = sum();
+ uint64_t cur_sum_squares = sum_squares();
+ if (cur_num == 0) return 0;
+ double variance =
+ static_cast<double>(cur_sum_squares * cur_num - cur_sum * cur_sum) /
+ static_cast<double>(cur_num * cur_num);
+ return std::sqrt(variance);
+}
+std::string HistogramStat::ToString() const {
+ uint64_t cur_num = num();
+ std::string r;
+ char buf[1650];
+ snprintf(buf, sizeof(buf),
+ "Count: %" PRIu64 " Average: %.4f StdDev: %.2f\n",
+ cur_num, Average(), StandardDeviation());
+ r.append(buf);
+ snprintf(buf, sizeof(buf),
+ "Min: %" PRIu64 " Median: %.4f Max: %" PRIu64 "\n",
+ (cur_num == 0 ? 0 : min()), Median(), (cur_num == 0 ? 0 : max()));
+ r.append(buf);
+ snprintf(buf, sizeof(buf),
+ "Percentiles: "
+ "P50: %.2f P75: %.2f P99: %.2f P99.9: %.2f P99.99: %.2f\n",
+ Percentile(50), Percentile(75), Percentile(99), Percentile(99.9),
+ Percentile(99.99));
+ r.append(buf);
+ r.append("------------------------------------------------------\n");
+ if (cur_num == 0) return r; // all buckets are empty
+ const double mult = 100.0 / cur_num;
+ uint64_t cumulative_sum = 0;
+ for (unsigned int b = 0; b < num_buckets_; b++) {
+ uint64_t bucket_value = bucket_at(b);
+ if (bucket_value <= 0.0) continue;
+ cumulative_sum += bucket_value;
+ snprintf(buf, sizeof(buf),
+ "%c %7" PRIu64 ", %7" PRIu64 " ] %8" PRIu64 " %7.3f%% %7.3f%% ",
+ (b == 0) ? '[' : '(',
+ (b == 0) ? 0 : bucketMapper.BucketLimit(b-1), // left
+ bucketMapper.BucketLimit(b), // right
+ bucket_value, // count
+ (mult * bucket_value), // percentage
+ (mult * cumulative_sum)); // cumulative percentage
+ r.append(buf);
+
+ // Add hash marks based on percentage; 20 marks for 100%.
+ size_t marks = static_cast<size_t>(mult * bucket_value / 5 + 0.5);
+ r.append(marks, '#');
+ r.push_back('\n');
+ }
+ return r;
+}
+
+void HistogramStat::Data(HistogramData * const data) const {
+ assert(data);
+ data->median = Median();
+ data->percentile95 = Percentile(95);
+ data->percentile99 = Percentile(99);
+ data->max = static_cast<double>(max());
+ data->average = Average();
+ data->standard_deviation = StandardDeviation();
+ data->count = num();
+ data->sum = sum();
+ data->min = static_cast<double>(min());
+}
+
+void HistogramImpl::Clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ stats_.Clear();
+}
+
+bool HistogramImpl::Empty() const {
+ return stats_.Empty();
+}
+
+void HistogramImpl::Add(uint64_t value) {
+ stats_.Add(value);
+}
+
+void HistogramImpl::Merge(const Histogram& other) {
+ if (strcmp(Name(), other.Name()) == 0) {
+ Merge(
+ *static_cast_with_check<const HistogramImpl, const Histogram>(&other));
+ }
+}
+
+void HistogramImpl::Merge(const HistogramImpl& other) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ stats_.Merge(other.stats_);
+}
+
+double HistogramImpl::Median() const {
+ return stats_.Median();
+}
+
+double HistogramImpl::Percentile(double p) const {
+ return stats_.Percentile(p);
+}
+
+double HistogramImpl::Average() const {
+ return stats_.Average();
+}
+
+double HistogramImpl::StandardDeviation() const {
+ return stats_.StandardDeviation();
+}
+
+std::string HistogramImpl::ToString() const {
+ return stats_.ToString();
+}
+
+void HistogramImpl::Data(HistogramData * const data) const {
+ stats_.Data(data);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/histogram.h b/storage/rocksdb/rocksdb/monitoring/histogram.h
new file mode 100644
index 00000000..a6b93e8f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/histogram.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/statistics.h"
+
+#include <cassert>
+#include <string>
+#include <vector>
+#include <map>
+#include <mutex>
+
+namespace ROCKSDB_NAMESPACE {
+
+class HistogramBucketMapper {
+ public:
+
+ HistogramBucketMapper();
+
+ // converts a value to the bucket index.
+ size_t IndexForValue(uint64_t value) const;
+ // number of buckets required.
+
+ size_t BucketCount() const {
+ return bucketValues_.size();
+ }
+
+ uint64_t LastValue() const {
+ return maxBucketValue_;
+ }
+
+ uint64_t FirstValue() const {
+ return minBucketValue_;
+ }
+
+ uint64_t BucketLimit(const size_t bucketNumber) const {
+ assert(bucketNumber < BucketCount());
+ return bucketValues_[bucketNumber];
+ }
+
+ private:
+ std::vector<uint64_t> bucketValues_;
+ uint64_t maxBucketValue_;
+ uint64_t minBucketValue_;
+ std::map<uint64_t, uint64_t> valueIndexMap_;
+};
+
+struct HistogramStat {
+ HistogramStat();
+ ~HistogramStat() {}
+
+ HistogramStat(const HistogramStat&) = delete;
+ HistogramStat& operator=(const HistogramStat&) = delete;
+
+ void Clear();
+ bool Empty() const;
+ void Add(uint64_t value);
+ void Merge(const HistogramStat& other);
+
+ inline uint64_t min() const { return min_.load(std::memory_order_relaxed); }
+ inline uint64_t max() const { return max_.load(std::memory_order_relaxed); }
+ inline uint64_t num() const { return num_.load(std::memory_order_relaxed); }
+ inline uint64_t sum() const { return sum_.load(std::memory_order_relaxed); }
+ inline uint64_t sum_squares() const {
+ return sum_squares_.load(std::memory_order_relaxed);
+ }
+ inline uint64_t bucket_at(size_t b) const {
+ return buckets_[b].load(std::memory_order_relaxed);
+ }
+
+ double Median() const;
+ double Percentile(double p) const;
+ double Average() const;
+ double StandardDeviation() const;
+ void Data(HistogramData* const data) const;
+ std::string ToString() const;
+
+ // To be able to use HistogramStat as thread local variable, it
+ // cannot have dynamic allocated member. That's why we're
+ // using manually values from BucketMapper
+ std::atomic_uint_fast64_t min_;
+ std::atomic_uint_fast64_t max_;
+ std::atomic_uint_fast64_t num_;
+ std::atomic_uint_fast64_t sum_;
+ std::atomic_uint_fast64_t sum_squares_;
+ std::atomic_uint_fast64_t buckets_[109]; // 109==BucketMapper::BucketCount()
+ const uint64_t num_buckets_;
+};
+
+class Histogram {
+public:
+ Histogram() {}
+ virtual ~Histogram() {};
+
+ virtual void Clear() = 0;
+ virtual bool Empty() const = 0;
+ virtual void Add(uint64_t value) = 0;
+ virtual void Merge(const Histogram&) = 0;
+
+ virtual std::string ToString() const = 0;
+ virtual const char* Name() const = 0;
+ virtual uint64_t min() const = 0;
+ virtual uint64_t max() const = 0;
+ virtual uint64_t num() const = 0;
+ virtual double Median() const = 0;
+ virtual double Percentile(double p) const = 0;
+ virtual double Average() const = 0;
+ virtual double StandardDeviation() const = 0;
+ virtual void Data(HistogramData* const data) const = 0;
+};
+
+class HistogramImpl : public Histogram {
+ public:
+ HistogramImpl() { Clear(); }
+
+ HistogramImpl(const HistogramImpl&) = delete;
+ HistogramImpl& operator=(const HistogramImpl&) = delete;
+
+ virtual void Clear() override;
+ virtual bool Empty() const override;
+ virtual void Add(uint64_t value) override;
+ virtual void Merge(const Histogram& other) override;
+ void Merge(const HistogramImpl& other);
+
+ virtual std::string ToString() const override;
+ virtual const char* Name() const override { return "HistogramImpl"; }
+ virtual uint64_t min() const override { return stats_.min(); }
+ virtual uint64_t max() const override { return stats_.max(); }
+ virtual uint64_t num() const override { return stats_.num(); }
+ virtual double Median() const override;
+ virtual double Percentile(double p) const override;
+ virtual double Average() const override;
+ virtual double StandardDeviation() const override;
+ virtual void Data(HistogramData* const data) const override;
+
+ virtual ~HistogramImpl() {}
+
+ private:
+ HistogramStat stats_;
+ std::mutex mutex_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/histogram_test.cc b/storage/rocksdb/rocksdb/monitoring/histogram_test.cc
new file mode 100644
index 00000000..36a7d715
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/histogram_test.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cmath>
+
+#include "monitoring/histogram.h"
+#include "monitoring/histogram_windowing.h"
+#include "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class HistogramTest : public testing::Test {};
+
+namespace {
+ const double kIota = 0.1;
+ const HistogramBucketMapper bucketMapper;
+ Env* env = Env::Default();
+}
+
+void PopulateHistogram(Histogram& histogram,
+ uint64_t low, uint64_t high, uint64_t loop = 1) {
+ for (; loop > 0; loop--) {
+ for (uint64_t i = low; i <= high; i++) {
+ histogram.Add(i);
+ }
+ }
+}
+
+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);
+
+ PopulateHistogram(histogramWindowing, 1, 1, 100);
+ env->SleepForMicroseconds(micros_per_window);
+ ASSERT_EQ(histogramWindowing.num(), 100);
+ ASSERT_EQ(histogramWindowing.min(), 1);
+ ASSERT_EQ(histogramWindowing.max(), 1);
+ ASSERT_EQ(histogramWindowing.Average(), 1);
+
+ PopulateHistogram(histogramWindowing, 2, 2, 100);
+ env->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);
+
+ PopulateHistogram(histogramWindowing, 3, 3, 100);
+ env->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);
+
+ // dropping oldest window with value 1, remaining 2 ~ 4
+ PopulateHistogram(histogramWindowing, 4, 4, 100);
+ env->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);
+
+ // dropping oldest window with value 2, remaining 3 ~ 5
+ PopulateHistogram(histogramWindowing, 5, 5, 100);
+ env->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);
+}
+
+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);
+
+ PopulateHistogram(histogramWindowing, 1, 1, 100);
+ PopulateHistogram(otherWindowing, 1, 1, 100);
+ env->SleepForMicroseconds(micros_per_window);
+
+ PopulateHistogram(histogramWindowing, 2, 2, 100);
+ PopulateHistogram(otherWindowing, 2, 2, 100);
+ env->SleepForMicroseconds(micros_per_window);
+
+ PopulateHistogram(histogramWindowing, 3, 3, 100);
+ PopulateHistogram(otherWindowing, 3, 3, 100);
+ env->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);
+ env->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);
+ env->SleepForMicroseconds(micros_per_window);
+ ASSERT_EQ(histogramWindowing.num(), 400);
+ ASSERT_EQ(histogramWindowing.min(), 3);
+ ASSERT_EQ(histogramWindowing.max(), 5);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/monitoring/histogram_windowing.cc b/storage/rocksdb/rocksdb/monitoring/histogram_windowing.cc
new file mode 100644
index 00000000..25351277
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/histogram_windowing.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/histogram_windowing.h"
+#include "monitoring/histogram.h"
+#include "util/cast_util.h"
+
+#include <algorithm>
+
+namespace ROCKSDB_NAMESPACE {
+
+HistogramWindowingImpl::HistogramWindowingImpl() {
+ env_ = Env::Default();
+ window_stats_.reset(new HistogramStat[static_cast<size_t>(num_windows_)]);
+ Clear();
+}
+
+HistogramWindowingImpl::HistogramWindowingImpl(
+ uint64_t num_windows,
+ uint64_t micros_per_window,
+ uint64_t min_num_per_window) :
+ num_windows_(num_windows),
+ micros_per_window_(micros_per_window),
+ min_num_per_window_(min_num_per_window) {
+ env_ = Env::Default();
+ window_stats_.reset(new HistogramStat[static_cast<size_t>(num_windows_)]);
+ Clear();
+}
+
+HistogramWindowingImpl::~HistogramWindowingImpl() {
+}
+
+void HistogramWindowingImpl::Clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+
+ stats_.Clear();
+ for (size_t i = 0; i < num_windows_; i++) {
+ window_stats_[i].Clear();
+ }
+ current_window_.store(0, std::memory_order_relaxed);
+ last_swap_time_.store(env_->NowMicros(), std::memory_order_relaxed);
+}
+
+bool HistogramWindowingImpl::Empty() const { return stats_.Empty(); }
+
+// This function is designed to be lock free, as it's in the critical path
+// of any operation.
+// Each individual value is atomic, it is just that some samples can go
+// in the older bucket which is tolerable.
+void HistogramWindowingImpl::Add(uint64_t value){
+ TimerTick();
+
+ // Parent (global) member update
+ stats_.Add(value);
+
+ // Current window update
+ window_stats_[static_cast<size_t>(current_window())].Add(value);
+}
+
+void HistogramWindowingImpl::Merge(const Histogram& other) {
+ if (strcmp(Name(), other.Name()) == 0) {
+ Merge(
+ *static_cast_with_check<const HistogramWindowingImpl, const Histogram>(
+ &other));
+ }
+}
+
+void HistogramWindowingImpl::Merge(const HistogramWindowingImpl& other) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ stats_.Merge(other.stats_);
+
+ if (stats_.num_buckets_ != other.stats_.num_buckets_ ||
+ micros_per_window_ != other.micros_per_window_) {
+ return;
+ }
+
+ uint64_t cur_window = current_window();
+ uint64_t other_cur_window = other.current_window();
+ // going backwards for alignment
+ for (unsigned int i = 0;
+ i < std::min(num_windows_, other.num_windows_); i++) {
+ uint64_t window_index =
+ (cur_window + num_windows_ - i) % num_windows_;
+ uint64_t other_window_index =
+ (other_cur_window + other.num_windows_ - i) % other.num_windows_;
+ size_t windex = static_cast<size_t>(window_index);
+ size_t other_windex = static_cast<size_t>(other_window_index);
+
+ window_stats_[windex].Merge(
+ other.window_stats_[other_windex]);
+ }
+}
+
+std::string HistogramWindowingImpl::ToString() const {
+ return stats_.ToString();
+}
+
+double HistogramWindowingImpl::Median() const {
+ return Percentile(50.0);
+}
+
+double HistogramWindowingImpl::Percentile(double p) const {
+ // Retry 3 times in total
+ for (int retry = 0; retry < 3; retry++) {
+ uint64_t start_num = stats_.num();
+ double result = stats_.Percentile(p);
+ // Detect if swap buckets or Clear() was called during calculation
+ if (stats_.num() >= start_num) {
+ return result;
+ }
+ }
+ return 0.0;
+}
+
+double HistogramWindowingImpl::Average() const {
+ return stats_.Average();
+}
+
+double HistogramWindowingImpl::StandardDeviation() const {
+ return stats_.StandardDeviation();
+}
+
+void HistogramWindowingImpl::Data(HistogramData * const data) const {
+ stats_.Data(data);
+}
+
+void HistogramWindowingImpl::TimerTick() {
+ uint64_t curr_time = env_->NowMicros();
+ size_t curr_window_ = static_cast<size_t>(current_window());
+ if (curr_time - last_swap_time() > micros_per_window_ &&
+ window_stats_[curr_window_].num() >= min_num_per_window_) {
+ SwapHistoryBucket();
+ }
+}
+
+void HistogramWindowingImpl::SwapHistoryBucket() {
+ // Threads executing Add() would be competing for this mutex, the first one
+ // who got the metex would take care of the bucket swap, other threads
+ // can skip this.
+ // If mutex is held by Merge() or Clear(), next Add() will take care of the
+ // swap, if needed.
+ if (mutex_.try_lock()) {
+ last_swap_time_.store(env_->NowMicros(), std::memory_order_relaxed);
+
+ uint64_t curr_window = current_window();
+ uint64_t next_window = (curr_window == num_windows_ - 1) ?
+ 0 : curr_window + 1;
+
+ // subtract next buckets from totals and swap to next buckets
+ HistogramStat& stats_to_drop =
+ window_stats_[static_cast<size_t>(next_window)];
+
+ if (!stats_to_drop.Empty()) {
+ for (size_t b = 0; b < stats_.num_buckets_; b++){
+ stats_.buckets_[b].fetch_sub(
+ stats_to_drop.bucket_at(b), std::memory_order_relaxed);
+ }
+
+ if (stats_.min() == stats_to_drop.min()) {
+ uint64_t new_min = std::numeric_limits<uint64_t>::max();
+ for (unsigned int i = 0; i < num_windows_; i++) {
+ if (i != next_window) {
+ uint64_t m = window_stats_[i].min();
+ if (m < new_min) new_min = m;
+ }
+ }
+ stats_.min_.store(new_min, std::memory_order_relaxed);
+ }
+
+ if (stats_.max() == stats_to_drop.max()) {
+ uint64_t new_max = 0;
+ for (unsigned int i = 0; i < num_windows_; i++) {
+ if (i != next_window) {
+ uint64_t m = window_stats_[i].max();
+ if (m > new_max) new_max = m;
+ }
+ }
+ stats_.max_.store(new_max, std::memory_order_relaxed);
+ }
+
+ stats_.num_.fetch_sub(stats_to_drop.num(), std::memory_order_relaxed);
+ stats_.sum_.fetch_sub(stats_to_drop.sum(), std::memory_order_relaxed);
+ stats_.sum_squares_.fetch_sub(
+ stats_to_drop.sum_squares(), std::memory_order_relaxed);
+
+ stats_to_drop.Clear();
+ }
+
+ // advance to next window bucket
+ current_window_.store(next_window, std::memory_order_relaxed);
+
+ mutex_.unlock();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/histogram_windowing.h b/storage/rocksdb/rocksdb/monitoring/histogram_windowing.h
new file mode 100644
index 00000000..72545b07
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/histogram_windowing.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/histogram.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class HistogramWindowingImpl : public Histogram
+{
+public:
+ HistogramWindowingImpl();
+ HistogramWindowingImpl(uint64_t num_windows,
+ uint64_t micros_per_window,
+ uint64_t min_num_per_window);
+
+ HistogramWindowingImpl(const HistogramWindowingImpl&) = delete;
+ HistogramWindowingImpl& operator=(const HistogramWindowingImpl&) = delete;
+
+ ~HistogramWindowingImpl();
+
+ virtual void Clear() override;
+ virtual bool Empty() const override;
+ virtual void Add(uint64_t value) override;
+ virtual void Merge(const Histogram& other) override;
+ void Merge(const HistogramWindowingImpl& other);
+
+ virtual std::string ToString() const override;
+ virtual const char* Name() const override { return "HistogramWindowingImpl"; }
+ virtual uint64_t min() const override { return stats_.min(); }
+ virtual uint64_t max() const override { return stats_.max(); }
+ virtual uint64_t num() const override { return stats_.num(); }
+ virtual double Median() const override;
+ virtual double Percentile(double p) const override;
+ virtual double Average() const override;
+ virtual double StandardDeviation() const override;
+ virtual void Data(HistogramData* const data) const override;
+
+private:
+ void TimerTick();
+ void SwapHistoryBucket();
+ inline uint64_t current_window() const {
+ return current_window_.load(std::memory_order_relaxed);
+ }
+ inline uint64_t last_swap_time() const{
+ return last_swap_time_.load(std::memory_order_relaxed);
+ }
+
+ Env* env_;
+ std::mutex mutex_;
+
+ // Aggregated stats over windows_stats_, all the computation is done
+ // upon aggregated values
+ HistogramStat stats_;
+
+ // This is a circular array representing the latest N time-windows.
+ // Each entry stores a time-window of data. Expiration is done
+ // on window-based.
+ std::unique_ptr<HistogramStat[]> window_stats_;
+
+ std::atomic_uint_fast64_t current_window_;
+ std::atomic_uint_fast64_t last_swap_time_;
+
+ // Following parameters are configuable
+ uint64_t num_windows_ = 5;
+ uint64_t micros_per_window_ = 60000000;
+ // By default, don't care about the number of values in current window
+ // when decide whether to swap windows or not.
+ uint64_t min_num_per_window_ = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.cc b/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.cc
new file mode 100644
index 00000000..dba791e2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/in_memory_stats_history.h"
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+InMemoryStatsHistoryIterator::~InMemoryStatsHistoryIterator() {}
+
+bool InMemoryStatsHistoryIterator::Valid() const { return valid_; }
+
+Status InMemoryStatsHistoryIterator::status() const { return status_; }
+
+// Because of garbage collection, the next stats snapshot may or may not be
+// right after the current one. When reading from DBImpl::stats_history_, this
+// call will be protected by DB Mutex so it will not return partial or
+// corrupted results.
+void InMemoryStatsHistoryIterator::Next() {
+ // increment start_time by 1 to avoid infinite loop
+ AdvanceIteratorByTime(GetStatsTime() + 1, end_time_);
+}
+
+uint64_t InMemoryStatsHistoryIterator::GetStatsTime() const { return time_; }
+
+const std::map<std::string, uint64_t>&
+InMemoryStatsHistoryIterator::GetStatsMap() const {
+ return stats_map_;
+}
+
+// advance the iterator to the next time between [start_time, end_time)
+// if success, update time_ and stats_map_ with new_time and stats_map
+void InMemoryStatsHistoryIterator::AdvanceIteratorByTime(uint64_t start_time,
+ uint64_t end_time) {
+ // try to find next entry in stats_history_ map
+ if (db_impl_ != nullptr) {
+ valid_ =
+ db_impl_->FindStatsByTime(start_time, end_time, &time_, &stats_map_);
+ } else {
+ valid_ = false;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.h b/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.h
new file mode 100644
index 00000000..3be864fe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/in_memory_stats_history.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/stats_history.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// InMemoryStatsHistoryIterator can be used to access stats history that was
+// stored by an in-memory two level std::map(DBImpl::stats_history_). It keeps
+// a copy of the stats snapshot (in stats_map_) that is currently being pointed
+// to, which allows the iterator to access the stats snapshot even when
+// the background garbage collecting thread purges it from the source of truth
+// (`DBImpl::stats_history_`). In that case, the iterator will continue to be
+// valid until a call to `Next()` returns no result and invalidates it. In
+// some extreme cases, the iterator may also return fragmented segments of
+// stats snapshots due to long gaps between `Next()` calls and interleaved
+// garbage collection.
+class InMemoryStatsHistoryIterator final : public StatsHistoryIterator {
+ public:
+ // Setup InMemoryStatsHistoryIterator to return stats snapshots between
+ // seconds timestamps [start_time, end_time)
+ InMemoryStatsHistoryIterator(uint64_t start_time, uint64_t end_time,
+ DBImpl* db_impl)
+ : start_time_(start_time),
+ end_time_(end_time),
+ valid_(true),
+ db_impl_(db_impl) {
+ AdvanceIteratorByTime(start_time_, end_time_);
+ }
+ // no copying allowed
+ InMemoryStatsHistoryIterator(const InMemoryStatsHistoryIterator&) = delete;
+ void operator=(const InMemoryStatsHistoryIterator&) = delete;
+ InMemoryStatsHistoryIterator(InMemoryStatsHistoryIterator&&) = delete;
+ InMemoryStatsHistoryIterator& operator=(InMemoryStatsHistoryIterator&&) =
+ delete;
+
+ ~InMemoryStatsHistoryIterator() override;
+ bool Valid() const override;
+ Status status() const override;
+
+ // Move to the next stats snapshot currently available
+ // This function may invalidate the iterator
+ // REQUIRES: Valid()
+ void Next() override;
+
+ // REQUIRES: Valid()
+ uint64_t GetStatsTime() const override;
+
+ // This function is idempotent
+ // REQUIRES: Valid()
+ const std::map<std::string, uint64_t>& GetStatsMap() const override;
+
+ private:
+ // advance the iterator to the next stats history record with timestamp
+ // between [start_time, end_time)
+ void AdvanceIteratorByTime(uint64_t start_time, uint64_t end_time);
+
+ uint64_t time_;
+ uint64_t start_time_;
+ uint64_t end_time_;
+ std::map<std::string, uint64_t> stats_map_;
+ Status status_;
+ bool valid_;
+ DBImpl* db_impl_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.cc b/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.cc
new file mode 100644
index 00000000..8b06c60f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/instrumented_mutex.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/thread_status_util.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+Statistics* stats_for_report(Env* env, Statistics* stats) {
+ if (env != nullptr && stats != nullptr &&
+ stats->get_stats_level() > kExceptTimeForMutex) {
+ return stats;
+ } else {
+ return nullptr;
+ }
+}
+} // namespace
+
+void InstrumentedMutex::Lock() {
+ PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(
+ db_mutex_lock_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS,
+ stats_for_report(env_, stats_), stats_code_);
+ LockInternal();
+}
+
+void InstrumentedMutex::LockInternal() {
+#ifndef NDEBUG
+ ThreadStatusUtil::TEST_StateDelay(ThreadStatus::STATE_MUTEX_WAIT);
+#endif
+ mutex_.Lock();
+}
+
+void InstrumentedCondVar::Wait() {
+ PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(
+ db_condition_wait_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS,
+ stats_for_report(env_, stats_), stats_code_);
+ WaitInternal();
+}
+
+void InstrumentedCondVar::WaitInternal() {
+#ifndef NDEBUG
+ ThreadStatusUtil::TEST_StateDelay(ThreadStatus::STATE_MUTEX_WAIT);
+#endif
+ cond_.Wait();
+}
+
+bool InstrumentedCondVar::TimedWait(uint64_t abs_time_us) {
+ PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(
+ db_condition_wait_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS,
+ stats_for_report(env_, stats_), stats_code_);
+ return TimedWaitInternal(abs_time_us);
+}
+
+bool InstrumentedCondVar::TimedWaitInternal(uint64_t abs_time_us) {
+#ifndef NDEBUG
+ ThreadStatusUtil::TEST_StateDelay(ThreadStatus::STATE_MUTEX_WAIT);
+#endif
+
+ TEST_SYNC_POINT_CALLBACK("InstrumentedCondVar::TimedWaitInternal",
+ &abs_time_us);
+
+ return cond_.TimedWait(abs_time_us);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.h b/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.h
new file mode 100644
index 00000000..50c1f29c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/instrumented_mutex.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/statistics.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/thread_status.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+class InstrumentedCondVar;
+
+// A wrapper class for port::Mutex that provides additional layer
+// for collecting stats and instrumentation.
+class InstrumentedMutex {
+ public:
+ explicit InstrumentedMutex(bool adaptive = false)
+ : mutex_(adaptive), stats_(nullptr), env_(nullptr),
+ stats_code_(0) {}
+
+ InstrumentedMutex(
+ Statistics* stats, Env* env,
+ int stats_code, bool adaptive = false)
+ : mutex_(adaptive), stats_(stats), env_(env),
+ stats_code_(stats_code) {}
+
+ void Lock();
+
+ void Unlock() {
+ mutex_.Unlock();
+ }
+
+ void AssertHeld() {
+ mutex_.AssertHeld();
+ }
+
+ private:
+ void LockInternal();
+ friend class InstrumentedCondVar;
+ port::Mutex mutex_;
+ Statistics* stats_;
+ Env* env_;
+ int stats_code_;
+};
+
+// A wrapper class for port::Mutex that provides additional layer
+// for collecting stats and instrumentation.
+class InstrumentedMutexLock {
+ public:
+ explicit InstrumentedMutexLock(InstrumentedMutex* mutex) : mutex_(mutex) {
+ mutex_->Lock();
+ }
+
+ ~InstrumentedMutexLock() {
+ mutex_->Unlock();
+ }
+
+ private:
+ InstrumentedMutex* const mutex_;
+ InstrumentedMutexLock(const InstrumentedMutexLock&) = delete;
+ void operator=(const InstrumentedMutexLock&) = delete;
+};
+
+class InstrumentedCondVar {
+ public:
+ explicit InstrumentedCondVar(InstrumentedMutex* instrumented_mutex)
+ : cond_(&(instrumented_mutex->mutex_)),
+ stats_(instrumented_mutex->stats_),
+ env_(instrumented_mutex->env_),
+ stats_code_(instrumented_mutex->stats_code_) {}
+
+ void Wait();
+
+ bool TimedWait(uint64_t abs_time_us);
+
+ void Signal() {
+ cond_.Signal();
+ }
+
+ void SignalAll() {
+ cond_.SignalAll();
+ }
+
+ private:
+ void WaitInternal();
+ bool TimedWaitInternal(uint64_t abs_time_us);
+ port::CondVar cond_;
+ Statistics* stats_;
+ Env* env_;
+ int stats_code_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/iostats_context.cc b/storage/rocksdb/rocksdb/monitoring/iostats_context.cc
new file mode 100644
index 00000000..2960f05e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/iostats_context.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sstream>
+#include "monitoring/iostats_context_imp.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+__thread IOStatsContext iostats_context;
+#endif
+
+IOStatsContext* get_iostats_context() {
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+ return &iostats_context;
+#else
+ return nullptr;
+#endif
+}
+
+void IOStatsContext::Reset() {
+ thread_pool_id = Env::Priority::TOTAL;
+ bytes_read = 0;
+ bytes_written = 0;
+ open_nanos = 0;
+ allocate_nanos = 0;
+ write_nanos = 0;
+ read_nanos = 0;
+ range_sync_nanos = 0;
+ prepare_write_nanos = 0;
+ fsync_nanos = 0;
+ logger_nanos = 0;
+}
+
+#define IOSTATS_CONTEXT_OUTPUT(counter) \
+ if (!exclude_zero_counters || counter > 0) { \
+ ss << #counter << " = " << counter << ", "; \
+ }
+
+std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
+ std::ostringstream ss;
+ IOSTATS_CONTEXT_OUTPUT(thread_pool_id);
+ IOSTATS_CONTEXT_OUTPUT(bytes_read);
+ IOSTATS_CONTEXT_OUTPUT(bytes_written);
+ IOSTATS_CONTEXT_OUTPUT(open_nanos);
+ IOSTATS_CONTEXT_OUTPUT(allocate_nanos);
+ IOSTATS_CONTEXT_OUTPUT(write_nanos);
+ IOSTATS_CONTEXT_OUTPUT(read_nanos);
+ IOSTATS_CONTEXT_OUTPUT(range_sync_nanos);
+ IOSTATS_CONTEXT_OUTPUT(fsync_nanos);
+ IOSTATS_CONTEXT_OUTPUT(prepare_write_nanos);
+ IOSTATS_CONTEXT_OUTPUT(logger_nanos);
+
+ std::string str = ss.str();
+ str.erase(str.find_last_not_of(", ") + 1);
+ return str;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/iostats_context_imp.h b/storage/rocksdb/rocksdb/monitoring/iostats_context_imp.h
new file mode 100644
index 00000000..a7f095d6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/iostats_context_imp.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/perf_step_timer.h"
+#include "rocksdb/iostats_context.h"
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+namespace ROCKSDB_NAMESPACE {
+extern __thread IOStatsContext iostats_context;
+} // namespace ROCKSDB_NAMESPACE
+
+// increment a specific counter by the specified value
+#define IOSTATS_ADD(metric, value) (iostats_context.metric += value)
+
+// Increase metric value only when it is positive
+#define IOSTATS_ADD_IF_POSITIVE(metric, value) \
+ if (value > 0) { IOSTATS_ADD(metric, value); }
+
+// reset a specific counter to zero
+#define IOSTATS_RESET(metric) (iostats_context.metric = 0)
+
+// reset all counters to zero
+#define IOSTATS_RESET_ALL() (iostats_context.Reset())
+
+#define IOSTATS_SET_THREAD_POOL_ID(value) \
+ (iostats_context.thread_pool_id = value)
+
+#define IOSTATS_THREAD_POOL_ID() (iostats_context.thread_pool_id)
+
+#define IOSTATS(metric) (iostats_context.metric)
+
+// Declare and set start time of the timer
+#define IOSTATS_TIMER_GUARD(metric) \
+ PerfStepTimer iostats_step_timer_##metric(&(iostats_context.metric)); \
+ iostats_step_timer_##metric.Start();
+
+// Declare and set start time of the timer
+#define IOSTATS_CPU_TIMER_GUARD(metric, env) \
+ PerfStepTimer iostats_step_timer_##metric( \
+ &(iostats_context.metric), env, true, \
+ PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); \
+ iostats_step_timer_##metric.Start();
+
+#else // ROCKSDB_SUPPORT_THREAD_LOCAL
+
+#define IOSTATS_ADD(metric, value)
+#define IOSTATS_ADD_IF_POSITIVE(metric, value)
+#define IOSTATS_RESET(metric)
+#define IOSTATS_RESET_ALL()
+#define IOSTATS_SET_THREAD_POOL_ID(value)
+#define IOSTATS_THREAD_POOL_ID()
+#define IOSTATS(metric) 0
+
+#define IOSTATS_TIMER_GUARD(metric)
+#define IOSTATS_CPU_TIMER_GUARD(metric, env) static_cast<void>(env)
+
+#endif // ROCKSDB_SUPPORT_THREAD_LOCAL
diff --git a/storage/rocksdb/rocksdb/monitoring/iostats_context_test.cc b/storage/rocksdb/rocksdb/monitoring/iostats_context_test.cc
new file mode 100644
index 00000000..49f6fc05
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/iostats_context_test.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/monitoring/perf_context.cc b/storage/rocksdb/rocksdb/monitoring/perf_context.cc
new file mode 100644
index 00000000..446a9b76
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/perf_context.cc
@@ -0,0 +1,559 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sstream>
+#include "monitoring/perf_context_imp.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
+PerfContext perf_context;
+#else
+#if defined(OS_SOLARIS)
+__thread PerfContext perf_context_;
+#else
+thread_local PerfContext perf_context;
+#endif
+#endif
+
+PerfContext* get_perf_context() {
+#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
+ return &perf_context;
+#else
+#if defined(OS_SOLARIS)
+ return &perf_context_;
+#else
+ return &perf_context;
+#endif
+#endif
+}
+
+PerfContext::~PerfContext() {
+#if !defined(NPERF_CONTEXT) && defined(ROCKSDB_SUPPORT_THREAD_LOCAL) && !defined(OS_SOLARIS)
+ ClearPerLevelPerfContext();
+#endif
+}
+
+PerfContext::PerfContext(const PerfContext& other) {
+#ifndef NPERF_CONTEXT
+ user_key_comparison_count = other.user_key_comparison_count;
+ block_cache_hit_count = other.block_cache_hit_count;
+ block_read_count = other.block_read_count;
+ block_read_byte = other.block_read_byte;
+ block_read_time = other.block_read_time;
+ block_cache_index_hit_count = other.block_cache_index_hit_count;
+ index_block_read_count = other.index_block_read_count;
+ block_cache_filter_hit_count = other.block_cache_filter_hit_count;
+ filter_block_read_count = other.filter_block_read_count;
+ compression_dict_block_read_count = other.compression_dict_block_read_count;
+ block_checksum_time = other.block_checksum_time;
+ block_decompress_time = other.block_decompress_time;
+ get_read_bytes = other.get_read_bytes;
+ multiget_read_bytes = other.multiget_read_bytes;
+ iter_read_bytes = other.iter_read_bytes;
+ internal_key_skipped_count = other.internal_key_skipped_count;
+ internal_delete_skipped_count = other.internal_delete_skipped_count;
+ internal_recent_skipped_count = other.internal_recent_skipped_count;
+ internal_merge_count = other.internal_merge_count;
+ write_wal_time = other.write_wal_time;
+ get_snapshot_time = other.get_snapshot_time;
+ get_from_memtable_time = other.get_from_memtable_time;
+ get_from_memtable_count = other.get_from_memtable_count;
+ get_post_process_time = other.get_post_process_time;
+ get_from_output_files_time = other.get_from_output_files_time;
+ seek_on_memtable_time = other.seek_on_memtable_time;
+ seek_on_memtable_count = other.seek_on_memtable_count;
+ next_on_memtable_count = other.next_on_memtable_count;
+ prev_on_memtable_count = other.prev_on_memtable_count;
+ seek_child_seek_time = other.seek_child_seek_time;
+ seek_child_seek_count = other.seek_child_seek_count;
+ seek_min_heap_time = other.seek_min_heap_time;
+ seek_internal_seek_time = other.seek_internal_seek_time;
+ find_next_user_entry_time = other.find_next_user_entry_time;
+ write_pre_and_post_process_time = other.write_pre_and_post_process_time;
+ write_memtable_time = other.write_memtable_time;
+ write_delay_time = other.write_delay_time;
+ write_thread_wait_nanos = other.write_thread_wait_nanos;
+ write_scheduling_flushes_compactions_time =
+ other.write_scheduling_flushes_compactions_time;
+ db_mutex_lock_nanos = other.db_mutex_lock_nanos;
+ db_condition_wait_nanos = other.db_condition_wait_nanos;
+ merge_operator_time_nanos = other.merge_operator_time_nanos;
+ read_index_block_nanos = other.read_index_block_nanos;
+ read_filter_block_nanos = other.read_filter_block_nanos;
+ new_table_block_iter_nanos = other.new_table_block_iter_nanos;
+ new_table_iterator_nanos = other.new_table_iterator_nanos;
+ block_seek_nanos = other.block_seek_nanos;
+ find_table_nanos = other.find_table_nanos;
+ bloom_memtable_hit_count = other.bloom_memtable_hit_count;
+ bloom_memtable_miss_count = other.bloom_memtable_miss_count;
+ bloom_sst_hit_count = other.bloom_sst_hit_count;
+ bloom_sst_miss_count = other.bloom_sst_miss_count;
+ key_lock_wait_time = other.key_lock_wait_time;
+ key_lock_wait_count = other.key_lock_wait_count;
+
+ env_new_sequential_file_nanos = other.env_new_sequential_file_nanos;
+ env_new_random_access_file_nanos = other.env_new_random_access_file_nanos;
+ env_new_writable_file_nanos = other.env_new_writable_file_nanos;
+ env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos;
+ env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos;
+ env_new_directory_nanos = other.env_new_directory_nanos;
+ env_file_exists_nanos = other.env_file_exists_nanos;
+ env_get_children_nanos = other.env_get_children_nanos;
+ env_get_children_file_attributes_nanos =
+ other.env_get_children_file_attributes_nanos;
+ env_delete_file_nanos = other.env_delete_file_nanos;
+ env_create_dir_nanos = other.env_create_dir_nanos;
+ env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos;
+ env_delete_dir_nanos = other.env_delete_dir_nanos;
+ env_get_file_size_nanos = other.env_get_file_size_nanos;
+ env_get_file_modification_time_nanos =
+ other.env_get_file_modification_time_nanos;
+ env_rename_file_nanos = other.env_rename_file_nanos;
+ env_link_file_nanos = other.env_link_file_nanos;
+ env_lock_file_nanos = other.env_lock_file_nanos;
+ env_unlock_file_nanos = other.env_unlock_file_nanos;
+ env_new_logger_nanos = other.env_new_logger_nanos;
+ get_cpu_nanos = other.get_cpu_nanos;
+ iter_next_cpu_nanos = other.iter_next_cpu_nanos;
+ iter_prev_cpu_nanos = other.iter_prev_cpu_nanos;
+ iter_seek_cpu_nanos = other.iter_seek_cpu_nanos;
+ if (per_level_perf_context_enabled && level_to_perf_context != nullptr) {
+ ClearPerLevelPerfContext();
+ }
+ if (other.level_to_perf_context != nullptr) {
+ level_to_perf_context = new std::map<uint32_t, PerfContextByLevel>();
+ *level_to_perf_context = *other.level_to_perf_context;
+ }
+ per_level_perf_context_enabled = other.per_level_perf_context_enabled;
+#endif
+}
+
+PerfContext::PerfContext(PerfContext&& other) noexcept {
+#ifndef NPERF_CONTEXT
+ user_key_comparison_count = other.user_key_comparison_count;
+ block_cache_hit_count = other.block_cache_hit_count;
+ block_read_count = other.block_read_count;
+ block_read_byte = other.block_read_byte;
+ block_read_time = other.block_read_time;
+ block_cache_index_hit_count = other.block_cache_index_hit_count;
+ index_block_read_count = other.index_block_read_count;
+ block_cache_filter_hit_count = other.block_cache_filter_hit_count;
+ filter_block_read_count = other.filter_block_read_count;
+ compression_dict_block_read_count = other.compression_dict_block_read_count;
+ block_checksum_time = other.block_checksum_time;
+ block_decompress_time = other.block_decompress_time;
+ get_read_bytes = other.get_read_bytes;
+ multiget_read_bytes = other.multiget_read_bytes;
+ iter_read_bytes = other.iter_read_bytes;
+ internal_key_skipped_count = other.internal_key_skipped_count;
+ internal_delete_skipped_count = other.internal_delete_skipped_count;
+ internal_recent_skipped_count = other.internal_recent_skipped_count;
+ internal_merge_count = other.internal_merge_count;
+ write_wal_time = other.write_wal_time;
+ get_snapshot_time = other.get_snapshot_time;
+ get_from_memtable_time = other.get_from_memtable_time;
+ get_from_memtable_count = other.get_from_memtable_count;
+ get_post_process_time = other.get_post_process_time;
+ get_from_output_files_time = other.get_from_output_files_time;
+ seek_on_memtable_time = other.seek_on_memtable_time;
+ seek_on_memtable_count = other.seek_on_memtable_count;
+ next_on_memtable_count = other.next_on_memtable_count;
+ prev_on_memtable_count = other.prev_on_memtable_count;
+ seek_child_seek_time = other.seek_child_seek_time;
+ seek_child_seek_count = other.seek_child_seek_count;
+ seek_min_heap_time = other.seek_min_heap_time;
+ seek_internal_seek_time = other.seek_internal_seek_time;
+ find_next_user_entry_time = other.find_next_user_entry_time;
+ write_pre_and_post_process_time = other.write_pre_and_post_process_time;
+ write_memtable_time = other.write_memtable_time;
+ write_delay_time = other.write_delay_time;
+ write_thread_wait_nanos = other.write_thread_wait_nanos;
+ write_scheduling_flushes_compactions_time =
+ other.write_scheduling_flushes_compactions_time;
+ db_mutex_lock_nanos = other.db_mutex_lock_nanos;
+ db_condition_wait_nanos = other.db_condition_wait_nanos;
+ merge_operator_time_nanos = other.merge_operator_time_nanos;
+ read_index_block_nanos = other.read_index_block_nanos;
+ read_filter_block_nanos = other.read_filter_block_nanos;
+ new_table_block_iter_nanos = other.new_table_block_iter_nanos;
+ new_table_iterator_nanos = other.new_table_iterator_nanos;
+ block_seek_nanos = other.block_seek_nanos;
+ find_table_nanos = other.find_table_nanos;
+ bloom_memtable_hit_count = other.bloom_memtable_hit_count;
+ bloom_memtable_miss_count = other.bloom_memtable_miss_count;
+ bloom_sst_hit_count = other.bloom_sst_hit_count;
+ bloom_sst_miss_count = other.bloom_sst_miss_count;
+ key_lock_wait_time = other.key_lock_wait_time;
+ key_lock_wait_count = other.key_lock_wait_count;
+
+ env_new_sequential_file_nanos = other.env_new_sequential_file_nanos;
+ env_new_random_access_file_nanos = other.env_new_random_access_file_nanos;
+ env_new_writable_file_nanos = other.env_new_writable_file_nanos;
+ env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos;
+ env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos;
+ env_new_directory_nanos = other.env_new_directory_nanos;
+ env_file_exists_nanos = other.env_file_exists_nanos;
+ env_get_children_nanos = other.env_get_children_nanos;
+ env_get_children_file_attributes_nanos =
+ other.env_get_children_file_attributes_nanos;
+ env_delete_file_nanos = other.env_delete_file_nanos;
+ env_create_dir_nanos = other.env_create_dir_nanos;
+ env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos;
+ env_delete_dir_nanos = other.env_delete_dir_nanos;
+ env_get_file_size_nanos = other.env_get_file_size_nanos;
+ env_get_file_modification_time_nanos =
+ other.env_get_file_modification_time_nanos;
+ env_rename_file_nanos = other.env_rename_file_nanos;
+ env_link_file_nanos = other.env_link_file_nanos;
+ env_lock_file_nanos = other.env_lock_file_nanos;
+ env_unlock_file_nanos = other.env_unlock_file_nanos;
+ env_new_logger_nanos = other.env_new_logger_nanos;
+ get_cpu_nanos = other.get_cpu_nanos;
+ iter_next_cpu_nanos = other.iter_next_cpu_nanos;
+ iter_prev_cpu_nanos = other.iter_prev_cpu_nanos;
+ iter_seek_cpu_nanos = other.iter_seek_cpu_nanos;
+ if (per_level_perf_context_enabled && level_to_perf_context != nullptr) {
+ ClearPerLevelPerfContext();
+ }
+ if (other.level_to_perf_context != nullptr) {
+ level_to_perf_context = other.level_to_perf_context;
+ other.level_to_perf_context = nullptr;
+ }
+ per_level_perf_context_enabled = other.per_level_perf_context_enabled;
+#endif
+}
+
+// TODO(Zhongyi): reduce code duplication between copy constructor and
+// assignment operator
+PerfContext& PerfContext::operator=(const PerfContext& other) {
+#ifndef NPERF_CONTEXT
+ user_key_comparison_count = other.user_key_comparison_count;
+ block_cache_hit_count = other.block_cache_hit_count;
+ block_read_count = other.block_read_count;
+ block_read_byte = other.block_read_byte;
+ block_read_time = other.block_read_time;
+ block_cache_index_hit_count = other.block_cache_index_hit_count;
+ index_block_read_count = other.index_block_read_count;
+ block_cache_filter_hit_count = other.block_cache_filter_hit_count;
+ filter_block_read_count = other.filter_block_read_count;
+ compression_dict_block_read_count = other.compression_dict_block_read_count;
+ block_checksum_time = other.block_checksum_time;
+ block_decompress_time = other.block_decompress_time;
+ get_read_bytes = other.get_read_bytes;
+ multiget_read_bytes = other.multiget_read_bytes;
+ iter_read_bytes = other.iter_read_bytes;
+ internal_key_skipped_count = other.internal_key_skipped_count;
+ internal_delete_skipped_count = other.internal_delete_skipped_count;
+ internal_recent_skipped_count = other.internal_recent_skipped_count;
+ internal_merge_count = other.internal_merge_count;
+ write_wal_time = other.write_wal_time;
+ get_snapshot_time = other.get_snapshot_time;
+ get_from_memtable_time = other.get_from_memtable_time;
+ get_from_memtable_count = other.get_from_memtable_count;
+ get_post_process_time = other.get_post_process_time;
+ get_from_output_files_time = other.get_from_output_files_time;
+ seek_on_memtable_time = other.seek_on_memtable_time;
+ seek_on_memtable_count = other.seek_on_memtable_count;
+ next_on_memtable_count = other.next_on_memtable_count;
+ prev_on_memtable_count = other.prev_on_memtable_count;
+ seek_child_seek_time = other.seek_child_seek_time;
+ seek_child_seek_count = other.seek_child_seek_count;
+ seek_min_heap_time = other.seek_min_heap_time;
+ seek_internal_seek_time = other.seek_internal_seek_time;
+ find_next_user_entry_time = other.find_next_user_entry_time;
+ write_pre_and_post_process_time = other.write_pre_and_post_process_time;
+ write_memtable_time = other.write_memtable_time;
+ write_delay_time = other.write_delay_time;
+ write_thread_wait_nanos = other.write_thread_wait_nanos;
+ write_scheduling_flushes_compactions_time =
+ other.write_scheduling_flushes_compactions_time;
+ db_mutex_lock_nanos = other.db_mutex_lock_nanos;
+ db_condition_wait_nanos = other.db_condition_wait_nanos;
+ merge_operator_time_nanos = other.merge_operator_time_nanos;
+ read_index_block_nanos = other.read_index_block_nanos;
+ read_filter_block_nanos = other.read_filter_block_nanos;
+ new_table_block_iter_nanos = other.new_table_block_iter_nanos;
+ new_table_iterator_nanos = other.new_table_iterator_nanos;
+ block_seek_nanos = other.block_seek_nanos;
+ find_table_nanos = other.find_table_nanos;
+ bloom_memtable_hit_count = other.bloom_memtable_hit_count;
+ bloom_memtable_miss_count = other.bloom_memtable_miss_count;
+ bloom_sst_hit_count = other.bloom_sst_hit_count;
+ bloom_sst_miss_count = other.bloom_sst_miss_count;
+ key_lock_wait_time = other.key_lock_wait_time;
+ key_lock_wait_count = other.key_lock_wait_count;
+
+ env_new_sequential_file_nanos = other.env_new_sequential_file_nanos;
+ env_new_random_access_file_nanos = other.env_new_random_access_file_nanos;
+ env_new_writable_file_nanos = other.env_new_writable_file_nanos;
+ env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos;
+ env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos;
+ env_new_directory_nanos = other.env_new_directory_nanos;
+ env_file_exists_nanos = other.env_file_exists_nanos;
+ env_get_children_nanos = other.env_get_children_nanos;
+ env_get_children_file_attributes_nanos =
+ other.env_get_children_file_attributes_nanos;
+ env_delete_file_nanos = other.env_delete_file_nanos;
+ env_create_dir_nanos = other.env_create_dir_nanos;
+ env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos;
+ env_delete_dir_nanos = other.env_delete_dir_nanos;
+ env_get_file_size_nanos = other.env_get_file_size_nanos;
+ env_get_file_modification_time_nanos =
+ other.env_get_file_modification_time_nanos;
+ env_rename_file_nanos = other.env_rename_file_nanos;
+ env_link_file_nanos = other.env_link_file_nanos;
+ env_lock_file_nanos = other.env_lock_file_nanos;
+ env_unlock_file_nanos = other.env_unlock_file_nanos;
+ env_new_logger_nanos = other.env_new_logger_nanos;
+ get_cpu_nanos = other.get_cpu_nanos;
+ iter_next_cpu_nanos = other.iter_next_cpu_nanos;
+ iter_prev_cpu_nanos = other.iter_prev_cpu_nanos;
+ iter_seek_cpu_nanos = other.iter_seek_cpu_nanos;
+ if (per_level_perf_context_enabled && level_to_perf_context != nullptr) {
+ ClearPerLevelPerfContext();
+ }
+ if (other.level_to_perf_context != nullptr) {
+ level_to_perf_context = new std::map<uint32_t, PerfContextByLevel>();
+ *level_to_perf_context = *other.level_to_perf_context;
+ }
+ per_level_perf_context_enabled = other.per_level_perf_context_enabled;
+#endif
+ return *this;
+}
+
+void PerfContext::Reset() {
+#ifndef NPERF_CONTEXT
+ user_key_comparison_count = 0;
+ block_cache_hit_count = 0;
+ block_read_count = 0;
+ block_read_byte = 0;
+ block_read_time = 0;
+ block_cache_index_hit_count = 0;
+ index_block_read_count = 0;
+ block_cache_filter_hit_count = 0;
+ filter_block_read_count = 0;
+ compression_dict_block_read_count = 0;
+ block_checksum_time = 0;
+ block_decompress_time = 0;
+ get_read_bytes = 0;
+ multiget_read_bytes = 0;
+ iter_read_bytes = 0;
+ internal_key_skipped_count = 0;
+ internal_delete_skipped_count = 0;
+ internal_recent_skipped_count = 0;
+ internal_merge_count = 0;
+ write_wal_time = 0;
+
+ get_snapshot_time = 0;
+ get_from_memtable_time = 0;
+ get_from_memtable_count = 0;
+ get_post_process_time = 0;
+ get_from_output_files_time = 0;
+ seek_on_memtable_time = 0;
+ seek_on_memtable_count = 0;
+ next_on_memtable_count = 0;
+ prev_on_memtable_count = 0;
+ seek_child_seek_time = 0;
+ seek_child_seek_count = 0;
+ seek_min_heap_time = 0;
+ seek_internal_seek_time = 0;
+ find_next_user_entry_time = 0;
+ write_pre_and_post_process_time = 0;
+ write_memtable_time = 0;
+ write_delay_time = 0;
+ write_thread_wait_nanos = 0;
+ write_scheduling_flushes_compactions_time = 0;
+ db_mutex_lock_nanos = 0;
+ db_condition_wait_nanos = 0;
+ merge_operator_time_nanos = 0;
+ read_index_block_nanos = 0;
+ read_filter_block_nanos = 0;
+ new_table_block_iter_nanos = 0;
+ new_table_iterator_nanos = 0;
+ block_seek_nanos = 0;
+ find_table_nanos = 0;
+ bloom_memtable_hit_count = 0;
+ bloom_memtable_miss_count = 0;
+ bloom_sst_hit_count = 0;
+ bloom_sst_miss_count = 0;
+ key_lock_wait_time = 0;
+ key_lock_wait_count = 0;
+
+ env_new_sequential_file_nanos = 0;
+ env_new_random_access_file_nanos = 0;
+ env_new_writable_file_nanos = 0;
+ env_reuse_writable_file_nanos = 0;
+ env_new_random_rw_file_nanos = 0;
+ env_new_directory_nanos = 0;
+ env_file_exists_nanos = 0;
+ env_get_children_nanos = 0;
+ env_get_children_file_attributes_nanos = 0;
+ env_delete_file_nanos = 0;
+ env_create_dir_nanos = 0;
+ env_create_dir_if_missing_nanos = 0;
+ env_delete_dir_nanos = 0;
+ env_get_file_size_nanos = 0;
+ env_get_file_modification_time_nanos = 0;
+ env_rename_file_nanos = 0;
+ env_link_file_nanos = 0;
+ env_lock_file_nanos = 0;
+ env_unlock_file_nanos = 0;
+ env_new_logger_nanos = 0;
+ get_cpu_nanos = 0;
+ iter_next_cpu_nanos = 0;
+ iter_prev_cpu_nanos = 0;
+ iter_seek_cpu_nanos = 0;
+ if (per_level_perf_context_enabled && level_to_perf_context) {
+ for (auto& kv : *level_to_perf_context) {
+ kv.second.Reset();
+ }
+ }
+#endif
+}
+
+#define PERF_CONTEXT_OUTPUT(counter) \
+ if (!exclude_zero_counters || (counter > 0)) { \
+ ss << #counter << " = " << counter << ", "; \
+ }
+
+#define PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(counter) \
+ if (per_level_perf_context_enabled && \
+ level_to_perf_context) { \
+ ss << #counter << " = "; \
+ for (auto& kv : *level_to_perf_context) { \
+ if (!exclude_zero_counters || (kv.second.counter > 0)) { \
+ ss << kv.second.counter << "@level" << kv.first << ", "; \
+ } \
+ } \
+ }
+
+void PerfContextByLevel::Reset() {
+#ifndef NPERF_CONTEXT
+ bloom_filter_useful = 0;
+ bloom_filter_full_positive = 0;
+ bloom_filter_full_true_positive = 0;
+ block_cache_hit_count = 0;
+ block_cache_miss_count = 0;
+#endif
+}
+
+std::string PerfContext::ToString(bool exclude_zero_counters) const {
+#ifdef NPERF_CONTEXT
+ return "";
+#else
+ std::ostringstream ss;
+ PERF_CONTEXT_OUTPUT(user_key_comparison_count);
+ PERF_CONTEXT_OUTPUT(block_cache_hit_count);
+ PERF_CONTEXT_OUTPUT(block_read_count);
+ PERF_CONTEXT_OUTPUT(block_read_byte);
+ PERF_CONTEXT_OUTPUT(block_read_time);
+ PERF_CONTEXT_OUTPUT(block_cache_index_hit_count);
+ PERF_CONTEXT_OUTPUT(index_block_read_count);
+ PERF_CONTEXT_OUTPUT(block_cache_filter_hit_count);
+ PERF_CONTEXT_OUTPUT(filter_block_read_count);
+ PERF_CONTEXT_OUTPUT(compression_dict_block_read_count);
+ PERF_CONTEXT_OUTPUT(block_checksum_time);
+ PERF_CONTEXT_OUTPUT(block_decompress_time);
+ PERF_CONTEXT_OUTPUT(get_read_bytes);
+ PERF_CONTEXT_OUTPUT(multiget_read_bytes);
+ PERF_CONTEXT_OUTPUT(iter_read_bytes);
+ PERF_CONTEXT_OUTPUT(internal_key_skipped_count);
+ PERF_CONTEXT_OUTPUT(internal_delete_skipped_count);
+ PERF_CONTEXT_OUTPUT(internal_recent_skipped_count);
+ PERF_CONTEXT_OUTPUT(internal_merge_count);
+ PERF_CONTEXT_OUTPUT(write_wal_time);
+ PERF_CONTEXT_OUTPUT(get_snapshot_time);
+ PERF_CONTEXT_OUTPUT(get_from_memtable_time);
+ PERF_CONTEXT_OUTPUT(get_from_memtable_count);
+ PERF_CONTEXT_OUTPUT(get_post_process_time);
+ PERF_CONTEXT_OUTPUT(get_from_output_files_time);
+ PERF_CONTEXT_OUTPUT(seek_on_memtable_time);
+ PERF_CONTEXT_OUTPUT(seek_on_memtable_count);
+ PERF_CONTEXT_OUTPUT(next_on_memtable_count);
+ PERF_CONTEXT_OUTPUT(prev_on_memtable_count);
+ PERF_CONTEXT_OUTPUT(seek_child_seek_time);
+ PERF_CONTEXT_OUTPUT(seek_child_seek_count);
+ PERF_CONTEXT_OUTPUT(seek_min_heap_time);
+ PERF_CONTEXT_OUTPUT(seek_internal_seek_time);
+ PERF_CONTEXT_OUTPUT(find_next_user_entry_time);
+ PERF_CONTEXT_OUTPUT(write_pre_and_post_process_time);
+ PERF_CONTEXT_OUTPUT(write_memtable_time);
+ PERF_CONTEXT_OUTPUT(write_thread_wait_nanos);
+ PERF_CONTEXT_OUTPUT(write_scheduling_flushes_compactions_time);
+ PERF_CONTEXT_OUTPUT(db_mutex_lock_nanos);
+ PERF_CONTEXT_OUTPUT(db_condition_wait_nanos);
+ PERF_CONTEXT_OUTPUT(merge_operator_time_nanos);
+ PERF_CONTEXT_OUTPUT(write_delay_time);
+ PERF_CONTEXT_OUTPUT(read_index_block_nanos);
+ PERF_CONTEXT_OUTPUT(read_filter_block_nanos);
+ PERF_CONTEXT_OUTPUT(new_table_block_iter_nanos);
+ PERF_CONTEXT_OUTPUT(new_table_iterator_nanos);
+ PERF_CONTEXT_OUTPUT(block_seek_nanos);
+ PERF_CONTEXT_OUTPUT(find_table_nanos);
+ PERF_CONTEXT_OUTPUT(bloom_memtable_hit_count);
+ PERF_CONTEXT_OUTPUT(bloom_memtable_miss_count);
+ PERF_CONTEXT_OUTPUT(bloom_sst_hit_count);
+ PERF_CONTEXT_OUTPUT(bloom_sst_miss_count);
+ PERF_CONTEXT_OUTPUT(key_lock_wait_time);
+ PERF_CONTEXT_OUTPUT(key_lock_wait_count);
+ PERF_CONTEXT_OUTPUT(env_new_sequential_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_new_random_access_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_new_writable_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_reuse_writable_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_new_random_rw_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_new_directory_nanos);
+ PERF_CONTEXT_OUTPUT(env_file_exists_nanos);
+ PERF_CONTEXT_OUTPUT(env_get_children_nanos);
+ PERF_CONTEXT_OUTPUT(env_get_children_file_attributes_nanos);
+ PERF_CONTEXT_OUTPUT(env_delete_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_create_dir_nanos);
+ PERF_CONTEXT_OUTPUT(env_create_dir_if_missing_nanos);
+ PERF_CONTEXT_OUTPUT(env_delete_dir_nanos);
+ PERF_CONTEXT_OUTPUT(env_get_file_size_nanos);
+ PERF_CONTEXT_OUTPUT(env_get_file_modification_time_nanos);
+ PERF_CONTEXT_OUTPUT(env_rename_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_link_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_lock_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_unlock_file_nanos);
+ PERF_CONTEXT_OUTPUT(env_new_logger_nanos);
+ PERF_CONTEXT_OUTPUT(get_cpu_nanos);
+ PERF_CONTEXT_OUTPUT(iter_next_cpu_nanos);
+ PERF_CONTEXT_OUTPUT(iter_prev_cpu_nanos);
+ PERF_CONTEXT_OUTPUT(iter_seek_cpu_nanos);
+ PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_useful);
+ PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_full_positive);
+ PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_full_true_positive);
+ PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(block_cache_hit_count);
+ PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(block_cache_miss_count);
+
+ std::string str = ss.str();
+ str.erase(str.find_last_not_of(", ") + 1);
+ return str;
+#endif
+}
+
+void PerfContext::EnablePerLevelPerfContext() {
+ if (level_to_perf_context == nullptr) {
+ level_to_perf_context = new std::map<uint32_t, PerfContextByLevel>();
+ }
+ per_level_perf_context_enabled = true;
+}
+
+void PerfContext::DisablePerLevelPerfContext(){
+ per_level_perf_context_enabled = false;
+}
+
+void PerfContext::ClearPerLevelPerfContext(){
+ if (level_to_perf_context != nullptr) {
+ level_to_perf_context->clear();
+ delete level_to_perf_context;
+ level_to_perf_context = nullptr;
+ }
+ per_level_perf_context_enabled = false;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/perf_context_imp.h b/storage/rocksdb/rocksdb/monitoring/perf_context_imp.h
new file mode 100644
index 00000000..cdca2762
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/perf_context_imp.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/perf_step_timer.h"
+#include "rocksdb/perf_context.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL)
+extern PerfContext perf_context;
+#else
+#if defined(OS_SOLARIS)
+extern __thread PerfContext perf_context_;
+#define perf_context (*get_perf_context())
+#else
+extern thread_local PerfContext perf_context;
+#endif
+#endif
+
+#if defined(NPERF_CONTEXT)
+
+#define PERF_TIMER_STOP(metric)
+#define PERF_TIMER_START(metric)
+#define PERF_TIMER_GUARD(metric)
+#define PERF_TIMER_GUARD_WITH_ENV(metric, env)
+#define PERF_CPU_TIMER_GUARD(metric, env)
+#define PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(metric, condition, stats, \
+ ticker_type)
+#define PERF_TIMER_MEASURE(metric)
+#define PERF_COUNTER_ADD(metric, value)
+#define PERF_COUNTER_BY_LEVEL_ADD(metric, value, level)
+
+#else
+
+// Stop the timer and update the metric
+#define PERF_TIMER_STOP(metric) perf_step_timer_##metric.Stop();
+
+#define PERF_TIMER_START(metric) perf_step_timer_##metric.Start();
+
+// Declare and set start time of the timer
+#define PERF_TIMER_GUARD(metric) \
+ PerfStepTimer perf_step_timer_##metric(&(perf_context.metric)); \
+ perf_step_timer_##metric.Start();
+
+// Declare and set start time of the timer
+#define PERF_TIMER_GUARD_WITH_ENV(metric, env) \
+ PerfStepTimer perf_step_timer_##metric(&(perf_context.metric), env); \
+ perf_step_timer_##metric.Start();
+
+// Declare and set start time of the timer
+#define PERF_CPU_TIMER_GUARD(metric, env) \
+ PerfStepTimer perf_step_timer_##metric( \
+ &(perf_context.metric), env, true, \
+ PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); \
+ perf_step_timer_##metric.Start();
+
+#define PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(metric, condition, stats, \
+ ticker_type) \
+ PerfStepTimer perf_step_timer_##metric(&(perf_context.metric), nullptr, \
+ false, PerfLevel::kEnableTime, stats, \
+ ticker_type); \
+ if (condition) { \
+ perf_step_timer_##metric.Start(); \
+ }
+
+// Update metric with time elapsed since last START. start time is reset
+// to current timestamp.
+#define PERF_TIMER_MEASURE(metric) perf_step_timer_##metric.Measure();
+
+// Increase metric value
+#define PERF_COUNTER_ADD(metric, value) \
+ if (perf_level >= PerfLevel::kEnableCount) { \
+ perf_context.metric += value; \
+ }
+
+// Increase metric value
+#define PERF_COUNTER_BY_LEVEL_ADD(metric, value, level) \
+ if (perf_level >= PerfLevel::kEnableCount && \
+ perf_context.per_level_perf_context_enabled && \
+ perf_context.level_to_perf_context) { \
+ if ((*(perf_context.level_to_perf_context)).find(level) != \
+ (*(perf_context.level_to_perf_context)).end()) { \
+ (*(perf_context.level_to_perf_context))[level].metric += value; \
+ } \
+ else { \
+ PerfContextByLevel empty_context; \
+ (*(perf_context.level_to_perf_context))[level] = empty_context; \
+ (*(perf_context.level_to_perf_context))[level].metric += value; \
+ } \
+ } \
+
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/perf_level.cc b/storage/rocksdb/rocksdb/monitoring/perf_level.cc
new file mode 100644
index 00000000..27bff0d2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/perf_level.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include "monitoring/perf_level_imp.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+__thread PerfLevel perf_level = kEnableCount;
+#else
+PerfLevel perf_level = kEnableCount;
+#endif
+
+void SetPerfLevel(PerfLevel level) {
+ assert(level > kUninitialized);
+ assert(level < kOutOfBounds);
+ perf_level = level;
+}
+
+PerfLevel GetPerfLevel() {
+ return perf_level;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/perf_level_imp.h b/storage/rocksdb/rocksdb/monitoring/perf_level_imp.h
new file mode 100644
index 00000000..01277af5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/perf_level_imp.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/perf_level.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+extern __thread PerfLevel perf_level;
+#else
+extern PerfLevel perf_level;
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/perf_step_timer.h b/storage/rocksdb/rocksdb/monitoring/perf_step_timer.h
new file mode 100644
index 00000000..f2d35d9d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/perf_step_timer.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/perf_level_imp.h"
+#include "rocksdb/env.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PerfStepTimer {
+ public:
+ explicit PerfStepTimer(
+ uint64_t* metric, Env* env = nullptr, bool use_cpu_time = false,
+ PerfLevel enable_level = PerfLevel::kEnableTimeExceptForMutex,
+ Statistics* statistics = nullptr, uint32_t ticker_type = 0)
+ : perf_counter_enabled_(perf_level >= enable_level),
+ use_cpu_time_(use_cpu_time),
+ env_((perf_counter_enabled_ || statistics != nullptr)
+ ? ((env != nullptr) ? env : Env::Default())
+ : nullptr),
+ start_(0),
+ metric_(metric),
+ statistics_(statistics),
+ ticker_type_(ticker_type) {}
+
+ ~PerfStepTimer() {
+ Stop();
+ }
+
+ void Start() {
+ if (perf_counter_enabled_ || statistics_ != nullptr) {
+ start_ = time_now();
+ }
+ }
+
+ uint64_t time_now() {
+ if (!use_cpu_time_) {
+ return env_->NowNanos();
+ } else {
+ return env_->NowCPUNanos();
+ }
+ }
+
+ void Measure() {
+ if (start_) {
+ uint64_t now = time_now();
+ *metric_ += now - start_;
+ start_ = now;
+ }
+ }
+
+ void Stop() {
+ if (start_) {
+ uint64_t duration = time_now() - start_;
+ if (perf_counter_enabled_) {
+ *metric_ += duration;
+ }
+
+ if (statistics_ != nullptr) {
+ RecordTick(statistics_, ticker_type_, duration);
+ }
+ start_ = 0;
+ }
+ }
+
+ private:
+ const bool perf_counter_enabled_;
+ const bool use_cpu_time_;
+ Env* const env_;
+ uint64_t start_;
+ uint64_t* metric_;
+ Statistics* statistics_;
+ uint32_t ticker_type_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.cc b/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.cc
new file mode 100644
index 00000000..7cc869cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/persistent_stats_history.h"
+
+#include <cstring>
+#include <string>
+#include <utility>
+#include "db/db_impl/db_impl.h"
+#include "port/likely.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+// 10 digit seconds timestamp => [Sep 9, 2001 ~ Nov 20, 2286]
+const int kNowSecondsStringLength = 10;
+const std::string kFormatVersionKeyString =
+ "__persistent_stats_format_version__";
+const std::string kCompatibleVersionKeyString =
+ "__persistent_stats_compatible_version__";
+// Every release maintains two versions numbers for persistents stats: Current
+// format version and compatible format version. Current format version
+// designates what type of encoding will be used when writing to stats CF;
+// compatible format version designates the minimum format version that
+// can decode the stats CF encoded using the current format version.
+const uint64_t kStatsCFCurrentFormatVersion = 1;
+const uint64_t kStatsCFCompatibleFormatVersion = 1;
+
+Status DecodePersistentStatsVersionNumber(DBImpl* db, StatsVersionKeyType type,
+ uint64_t* version_number) {
+ if (type >= StatsVersionKeyType::kKeyTypeMax) {
+ return Status::InvalidArgument("Invalid stats version key type provided");
+ }
+ std::string key;
+ if (type == StatsVersionKeyType::kFormatVersion) {
+ key = kFormatVersionKeyString;
+ } else if (type == StatsVersionKeyType::kCompatibleVersion) {
+ key = kCompatibleVersionKeyString;
+ }
+ ReadOptions options;
+ options.verify_checksums = true;
+ std::string result;
+ Status s = db->Get(options, db->PersistentStatsColumnFamily(), key, &result);
+ if (!s.ok() || result.empty()) {
+ return Status::NotFound("Persistent stats version key " + key +
+ " not found.");
+ }
+
+ // read version_number but do nothing in current version
+ *version_number = ParseUint64(result);
+ return Status::OK();
+}
+
+int EncodePersistentStatsKey(uint64_t now_seconds, const std::string& key,
+ int size, char* buf) {
+ char timestamp[kNowSecondsStringLength + 1];
+ // make time stamp string equal in length to allow sorting by time
+ snprintf(timestamp, sizeof(timestamp), "%010d",
+ static_cast<int>(now_seconds));
+ timestamp[kNowSecondsStringLength] = '\0';
+ return snprintf(buf, size, "%s#%s", timestamp, key.c_str());
+}
+
+void OptimizeForPersistentStats(ColumnFamilyOptions* cfo) {
+ cfo->write_buffer_size = 2 << 20;
+ cfo->target_file_size_base = 2 * 1048576;
+ cfo->max_bytes_for_level_base = 10 * 1048576;
+ cfo->soft_pending_compaction_bytes_limit = 256 * 1048576;
+ cfo->hard_pending_compaction_bytes_limit = 1073741824ul;
+ cfo->compression = kNoCompression;
+}
+
+PersistentStatsHistoryIterator::~PersistentStatsHistoryIterator() {}
+
+bool PersistentStatsHistoryIterator::Valid() const { return valid_; }
+
+Status PersistentStatsHistoryIterator::status() const { return status_; }
+
+void PersistentStatsHistoryIterator::Next() {
+ // increment start_time by 1 to avoid infinite loop
+ AdvanceIteratorByTime(GetStatsTime() + 1, end_time_);
+}
+
+uint64_t PersistentStatsHistoryIterator::GetStatsTime() const { return time_; }
+
+const std::map<std::string, uint64_t>&
+PersistentStatsHistoryIterator::GetStatsMap() const {
+ return stats_map_;
+}
+
+std::pair<uint64_t, std::string> parseKey(const Slice& key,
+ uint64_t start_time) {
+ std::pair<uint64_t, std::string> result;
+ std::string key_str = key.ToString();
+ std::string::size_type pos = key_str.find("#");
+ // TODO(Zhongyi): add counters to track parse failures?
+ if (pos == std::string::npos) {
+ result.first = port::kMaxUint64;
+ result.second.clear();
+ } else {
+ uint64_t parsed_time = ParseUint64(key_str.substr(0, pos));
+ // skip entries with timestamp smaller than start_time
+ if (parsed_time < start_time) {
+ result.first = port::kMaxUint64;
+ result.second = "";
+ } else {
+ result.first = parsed_time;
+ std::string key_resize = key_str.substr(pos + 1);
+ result.second = key_resize;
+ }
+ }
+ return result;
+}
+
+// advance the iterator to the next time between [start_time, end_time)
+// if success, update time_ and stats_map_ with new_time and stats_map
+void PersistentStatsHistoryIterator::AdvanceIteratorByTime(uint64_t start_time,
+ uint64_t end_time) {
+ // try to find next entry in stats_history_ map
+ if (db_impl_ != nullptr) {
+ ReadOptions ro;
+ Iterator* iter =
+ db_impl_->NewIterator(ro, db_impl_->PersistentStatsColumnFamily());
+
+ char timestamp[kNowSecondsStringLength + 1];
+ snprintf(timestamp, sizeof(timestamp), "%010d",
+ static_cast<int>(std::max(time_, start_time)));
+ timestamp[kNowSecondsStringLength] = '\0';
+
+ iter->Seek(timestamp);
+ // no more entries with timestamp >= start_time is found or version key
+ // is found to be incompatible
+ if (!iter->Valid()) {
+ valid_ = false;
+ delete iter;
+ return;
+ }
+ time_ = parseKey(iter->key(), start_time).first;
+ valid_ = true;
+ // check parsed time and invalid if it exceeds end_time
+ if (time_ > end_time) {
+ valid_ = false;
+ delete iter;
+ return;
+ }
+ // find all entries with timestamp equal to time_
+ std::map<std::string, uint64_t> new_stats_map;
+ std::pair<uint64_t, std::string> kv;
+ for (; iter->Valid(); iter->Next()) {
+ kv = parseKey(iter->key(), start_time);
+ if (kv.first != time_) {
+ break;
+ }
+ if (kv.second.compare(kFormatVersionKeyString) == 0) {
+ continue;
+ }
+ new_stats_map[kv.second] = ParseUint64(iter->value().ToString());
+ }
+ stats_map_.swap(new_stats_map);
+ delete iter;
+ } else {
+ valid_ = false;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.h b/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.h
new file mode 100644
index 00000000..7c711fe4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/persistent_stats_history.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/db_impl/db_impl.h"
+#include "rocksdb/stats_history.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const std::string kFormatVersionKeyString;
+extern const std::string kCompatibleVersionKeyString;
+extern const uint64_t kStatsCFCurrentFormatVersion;
+extern const uint64_t kStatsCFCompatibleFormatVersion;
+
+enum StatsVersionKeyType : uint32_t {
+ kFormatVersion = 1,
+ kCompatibleVersion = 2,
+ kKeyTypeMax = 3
+};
+
+// Read the version number from persitent stats cf depending on type provided
+// stores the version number in `*version_number`
+// returns Status::OK() on success, or other status code on failure
+Status DecodePersistentStatsVersionNumber(DBImpl* db, StatsVersionKeyType type,
+ uint64_t* version_number);
+
+// Encode timestamp and stats key into buf
+// Format: timestamp(10 digit) + '#' + key
+// Total length of encoded key will be capped at 100 bytes
+int EncodePersistentStatsKey(uint64_t timestamp, const std::string& key,
+ int size, char* buf);
+
+void OptimizeForPersistentStats(ColumnFamilyOptions* cfo);
+
+class PersistentStatsHistoryIterator final : public StatsHistoryIterator {
+ public:
+ PersistentStatsHistoryIterator(uint64_t start_time, uint64_t end_time,
+ DBImpl* db_impl)
+ : time_(0),
+ start_time_(start_time),
+ end_time_(end_time),
+ valid_(true),
+ db_impl_(db_impl) {
+ AdvanceIteratorByTime(start_time_, end_time_);
+ }
+ ~PersistentStatsHistoryIterator() override;
+ bool Valid() const override;
+ Status status() const override;
+
+ void Next() override;
+ uint64_t GetStatsTime() const override;
+
+ const std::map<std::string, uint64_t>& GetStatsMap() const override;
+
+ private:
+ // advance the iterator to the next stats history record with timestamp
+ // between [start_time, end_time)
+ void AdvanceIteratorByTime(uint64_t start_time, uint64_t end_time);
+
+ // No copying allowed
+ PersistentStatsHistoryIterator(const PersistentStatsHistoryIterator&) =
+ delete;
+ void operator=(const PersistentStatsHistoryIterator&) = delete;
+ PersistentStatsHistoryIterator(PersistentStatsHistoryIterator&&) = delete;
+ PersistentStatsHistoryIterator& operator=(PersistentStatsHistoryIterator&&) =
+ delete;
+
+ uint64_t time_;
+ uint64_t start_time_;
+ uint64_t end_time_;
+ std::map<std::string, uint64_t> stats_map_;
+ Status status_;
+ bool valid_;
+ DBImpl* db_impl_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/statistics.cc b/storage/rocksdb/rocksdb/monitoring/statistics.cc
new file mode 100644
index 00000000..ff610c8a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/statistics.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/statistics.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdio>
+#include "port/likely.h"
+#include "rocksdb/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// The order of items listed in Tickers should be the same as
+// the order listed in TickersNameMap
+const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
+ {BLOCK_CACHE_MISS, "rocksdb.block.cache.miss"},
+ {BLOCK_CACHE_HIT, "rocksdb.block.cache.hit"},
+ {BLOCK_CACHE_ADD, "rocksdb.block.cache.add"},
+ {BLOCK_CACHE_ADD_FAILURES, "rocksdb.block.cache.add.failures"},
+ {BLOCK_CACHE_INDEX_MISS, "rocksdb.block.cache.index.miss"},
+ {BLOCK_CACHE_INDEX_HIT, "rocksdb.block.cache.index.hit"},
+ {BLOCK_CACHE_INDEX_ADD, "rocksdb.block.cache.index.add"},
+ {BLOCK_CACHE_INDEX_BYTES_INSERT, "rocksdb.block.cache.index.bytes.insert"},
+ {BLOCK_CACHE_INDEX_BYTES_EVICT, "rocksdb.block.cache.index.bytes.evict"},
+ {BLOCK_CACHE_FILTER_MISS, "rocksdb.block.cache.filter.miss"},
+ {BLOCK_CACHE_FILTER_HIT, "rocksdb.block.cache.filter.hit"},
+ {BLOCK_CACHE_FILTER_ADD, "rocksdb.block.cache.filter.add"},
+ {BLOCK_CACHE_FILTER_BYTES_INSERT,
+ "rocksdb.block.cache.filter.bytes.insert"},
+ {BLOCK_CACHE_FILTER_BYTES_EVICT, "rocksdb.block.cache.filter.bytes.evict"},
+ {BLOCK_CACHE_DATA_MISS, "rocksdb.block.cache.data.miss"},
+ {BLOCK_CACHE_DATA_HIT, "rocksdb.block.cache.data.hit"},
+ {BLOCK_CACHE_DATA_ADD, "rocksdb.block.cache.data.add"},
+ {BLOCK_CACHE_DATA_BYTES_INSERT, "rocksdb.block.cache.data.bytes.insert"},
+ {BLOCK_CACHE_BYTES_READ, "rocksdb.block.cache.bytes.read"},
+ {BLOCK_CACHE_BYTES_WRITE, "rocksdb.block.cache.bytes.write"},
+ {BLOOM_FILTER_USEFUL, "rocksdb.bloom.filter.useful"},
+ {BLOOM_FILTER_FULL_POSITIVE, "rocksdb.bloom.filter.full.positive"},
+ {BLOOM_FILTER_FULL_TRUE_POSITIVE,
+ "rocksdb.bloom.filter.full.true.positive"},
+ {BLOOM_FILTER_MICROS, "rocksdb.bloom.filter.micros"},
+ {PERSISTENT_CACHE_HIT, "rocksdb.persistent.cache.hit"},
+ {PERSISTENT_CACHE_MISS, "rocksdb.persistent.cache.miss"},
+ {SIM_BLOCK_CACHE_HIT, "rocksdb.sim.block.cache.hit"},
+ {SIM_BLOCK_CACHE_MISS, "rocksdb.sim.block.cache.miss"},
+ {MEMTABLE_HIT, "rocksdb.memtable.hit"},
+ {MEMTABLE_MISS, "rocksdb.memtable.miss"},
+ {GET_HIT_L0, "rocksdb.l0.hit"},
+ {GET_HIT_L1, "rocksdb.l1.hit"},
+ {GET_HIT_L2_AND_UP, "rocksdb.l2andup.hit"},
+ {COMPACTION_KEY_DROP_NEWER_ENTRY, "rocksdb.compaction.key.drop.new"},
+ {COMPACTION_KEY_DROP_OBSOLETE, "rocksdb.compaction.key.drop.obsolete"},
+ {COMPACTION_KEY_DROP_RANGE_DEL, "rocksdb.compaction.key.drop.range_del"},
+ {COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user"},
+ {COMPACTION_RANGE_DEL_DROP_OBSOLETE,
+ "rocksdb.compaction.range_del.drop.obsolete"},
+ {COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
+ "rocksdb.compaction.optimized.del.drop.obsolete"},
+ {COMPACTION_CANCELLED, "rocksdb.compaction.cancelled"},
+ {NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written"},
+ {NUMBER_KEYS_READ, "rocksdb.number.keys.read"},
+ {NUMBER_KEYS_UPDATED, "rocksdb.number.keys.updated"},
+ {BYTES_WRITTEN, "rocksdb.bytes.written"},
+ {BYTES_READ, "rocksdb.bytes.read"},
+ {NUMBER_DB_SEEK, "rocksdb.number.db.seek"},
+ {NUMBER_DB_NEXT, "rocksdb.number.db.next"},
+ {NUMBER_DB_PREV, "rocksdb.number.db.prev"},
+ {NUMBER_DB_SEEK_FOUND, "rocksdb.number.db.seek.found"},
+ {NUMBER_DB_NEXT_FOUND, "rocksdb.number.db.next.found"},
+ {NUMBER_DB_PREV_FOUND, "rocksdb.number.db.prev.found"},
+ {ITER_BYTES_READ, "rocksdb.db.iter.bytes.read"},
+ {NO_FILE_CLOSES, "rocksdb.no.file.closes"},
+ {NO_FILE_OPENS, "rocksdb.no.file.opens"},
+ {NO_FILE_ERRORS, "rocksdb.no.file.errors"},
+ {STALL_L0_SLOWDOWN_MICROS, "rocksdb.l0.slowdown.micros"},
+ {STALL_MEMTABLE_COMPACTION_MICROS, "rocksdb.memtable.compaction.micros"},
+ {STALL_L0_NUM_FILES_MICROS, "rocksdb.l0.num.files.stall.micros"},
+ {STALL_MICROS, "rocksdb.stall.micros"},
+ {DB_MUTEX_WAIT_MICROS, "rocksdb.db.mutex.wait.micros"},
+ {RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.delay.millis"},
+ {NO_ITERATORS, "rocksdb.num.iterators"},
+ {NUMBER_MULTIGET_CALLS, "rocksdb.number.multiget.get"},
+ {NUMBER_MULTIGET_KEYS_READ, "rocksdb.number.multiget.keys.read"},
+ {NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read"},
+ {NUMBER_FILTERED_DELETES, "rocksdb.number.deletes.filtered"},
+ {NUMBER_MERGE_FAILURES, "rocksdb.number.merge.failures"},
+ {BLOOM_FILTER_PREFIX_CHECKED, "rocksdb.bloom.filter.prefix.checked"},
+ {BLOOM_FILTER_PREFIX_USEFUL, "rocksdb.bloom.filter.prefix.useful"},
+ {NUMBER_OF_RESEEKS_IN_ITERATION, "rocksdb.number.reseeks.iteration"},
+ {GET_UPDATES_SINCE_CALLS, "rocksdb.getupdatessince.calls"},
+ {BLOCK_CACHE_COMPRESSED_MISS, "rocksdb.block.cachecompressed.miss"},
+ {BLOCK_CACHE_COMPRESSED_HIT, "rocksdb.block.cachecompressed.hit"},
+ {BLOCK_CACHE_COMPRESSED_ADD, "rocksdb.block.cachecompressed.add"},
+ {BLOCK_CACHE_COMPRESSED_ADD_FAILURES,
+ "rocksdb.block.cachecompressed.add.failures"},
+ {WAL_FILE_SYNCED, "rocksdb.wal.synced"},
+ {WAL_FILE_BYTES, "rocksdb.wal.bytes"},
+ {WRITE_DONE_BY_SELF, "rocksdb.write.self"},
+ {WRITE_DONE_BY_OTHER, "rocksdb.write.other"},
+ {WRITE_TIMEDOUT, "rocksdb.write.timeout"},
+ {WRITE_WITH_WAL, "rocksdb.write.wal"},
+ {COMPACT_READ_BYTES, "rocksdb.compact.read.bytes"},
+ {COMPACT_WRITE_BYTES, "rocksdb.compact.write.bytes"},
+ {FLUSH_WRITE_BYTES, "rocksdb.flush.write.bytes"},
+ {NUMBER_DIRECT_LOAD_TABLE_PROPERTIES,
+ "rocksdb.number.direct.load.table.properties"},
+ {NUMBER_SUPERVERSION_ACQUIRES, "rocksdb.number.superversion_acquires"},
+ {NUMBER_SUPERVERSION_RELEASES, "rocksdb.number.superversion_releases"},
+ {NUMBER_SUPERVERSION_CLEANUPS, "rocksdb.number.superversion_cleanups"},
+ {NUMBER_BLOCK_COMPRESSED, "rocksdb.number.block.compressed"},
+ {NUMBER_BLOCK_DECOMPRESSED, "rocksdb.number.block.decompressed"},
+ {NUMBER_BLOCK_NOT_COMPRESSED, "rocksdb.number.block.not_compressed"},
+ {MERGE_OPERATION_TOTAL_TIME, "rocksdb.merge.operation.time.nanos"},
+ {FILTER_OPERATION_TOTAL_TIME, "rocksdb.filter.operation.time.nanos"},
+ {ROW_CACHE_HIT, "rocksdb.row.cache.hit"},
+ {ROW_CACHE_MISS, "rocksdb.row.cache.miss"},
+ {READ_AMP_ESTIMATE_USEFUL_BYTES, "rocksdb.read.amp.estimate.useful.bytes"},
+ {READ_AMP_TOTAL_READ_BYTES, "rocksdb.read.amp.total.read.bytes"},
+ {NUMBER_RATE_LIMITER_DRAINS, "rocksdb.number.rate_limiter.drains"},
+ {NUMBER_ITER_SKIP, "rocksdb.number.iter.skip"},
+ {BLOB_DB_NUM_PUT, "rocksdb.blobdb.num.put"},
+ {BLOB_DB_NUM_WRITE, "rocksdb.blobdb.num.write"},
+ {BLOB_DB_NUM_GET, "rocksdb.blobdb.num.get"},
+ {BLOB_DB_NUM_MULTIGET, "rocksdb.blobdb.num.multiget"},
+ {BLOB_DB_NUM_SEEK, "rocksdb.blobdb.num.seek"},
+ {BLOB_DB_NUM_NEXT, "rocksdb.blobdb.num.next"},
+ {BLOB_DB_NUM_PREV, "rocksdb.blobdb.num.prev"},
+ {BLOB_DB_NUM_KEYS_WRITTEN, "rocksdb.blobdb.num.keys.written"},
+ {BLOB_DB_NUM_KEYS_READ, "rocksdb.blobdb.num.keys.read"},
+ {BLOB_DB_BYTES_WRITTEN, "rocksdb.blobdb.bytes.written"},
+ {BLOB_DB_BYTES_READ, "rocksdb.blobdb.bytes.read"},
+ {BLOB_DB_WRITE_INLINED, "rocksdb.blobdb.write.inlined"},
+ {BLOB_DB_WRITE_INLINED_TTL, "rocksdb.blobdb.write.inlined.ttl"},
+ {BLOB_DB_WRITE_BLOB, "rocksdb.blobdb.write.blob"},
+ {BLOB_DB_WRITE_BLOB_TTL, "rocksdb.blobdb.write.blob.ttl"},
+ {BLOB_DB_BLOB_FILE_BYTES_WRITTEN, "rocksdb.blobdb.blob.file.bytes.written"},
+ {BLOB_DB_BLOB_FILE_BYTES_READ, "rocksdb.blobdb.blob.file.bytes.read"},
+ {BLOB_DB_BLOB_FILE_SYNCED, "rocksdb.blobdb.blob.file.synced"},
+ {BLOB_DB_BLOB_INDEX_EXPIRED_COUNT,
+ "rocksdb.blobdb.blob.index.expired.count"},
+ {BLOB_DB_BLOB_INDEX_EXPIRED_SIZE, "rocksdb.blobdb.blob.index.expired.size"},
+ {BLOB_DB_BLOB_INDEX_EVICTED_COUNT,
+ "rocksdb.blobdb.blob.index.evicted.count"},
+ {BLOB_DB_BLOB_INDEX_EVICTED_SIZE, "rocksdb.blobdb.blob.index.evicted.size"},
+ {BLOB_DB_GC_NUM_FILES, "rocksdb.blobdb.gc.num.files"},
+ {BLOB_DB_GC_NUM_NEW_FILES, "rocksdb.blobdb.gc.num.new.files"},
+ {BLOB_DB_GC_FAILURES, "rocksdb.blobdb.gc.failures"},
+ {BLOB_DB_GC_NUM_KEYS_OVERWRITTEN, "rocksdb.blobdb.gc.num.keys.overwritten"},
+ {BLOB_DB_GC_NUM_KEYS_EXPIRED, "rocksdb.blobdb.gc.num.keys.expired"},
+ {BLOB_DB_GC_NUM_KEYS_RELOCATED, "rocksdb.blobdb.gc.num.keys.relocated"},
+ {BLOB_DB_GC_BYTES_OVERWRITTEN, "rocksdb.blobdb.gc.bytes.overwritten"},
+ {BLOB_DB_GC_BYTES_EXPIRED, "rocksdb.blobdb.gc.bytes.expired"},
+ {BLOB_DB_GC_BYTES_RELOCATED, "rocksdb.blobdb.gc.bytes.relocated"},
+ {BLOB_DB_FIFO_NUM_FILES_EVICTED, "rocksdb.blobdb.fifo.num.files.evicted"},
+ {BLOB_DB_FIFO_NUM_KEYS_EVICTED, "rocksdb.blobdb.fifo.num.keys.evicted"},
+ {BLOB_DB_FIFO_BYTES_EVICTED, "rocksdb.blobdb.fifo.bytes.evicted"},
+ {TXN_PREPARE_MUTEX_OVERHEAD, "rocksdb.txn.overhead.mutex.prepare"},
+ {TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD,
+ "rocksdb.txn.overhead.mutex.old.commit.map"},
+ {TXN_DUPLICATE_KEY_OVERHEAD, "rocksdb.txn.overhead.duplicate.key"},
+ {TXN_SNAPSHOT_MUTEX_OVERHEAD, "rocksdb.txn.overhead.mutex.snapshot"},
+ {TXN_GET_TRY_AGAIN, "rocksdb.txn.get.tryagain"},
+ {NUMBER_MULTIGET_KEYS_FOUND, "rocksdb.number.multiget.keys.found"},
+ {NO_ITERATOR_CREATED, "rocksdb.num.iterator.created"},
+ {NO_ITERATOR_DELETED, "rocksdb.num.iterator.deleted"},
+ {BLOCK_CACHE_COMPRESSION_DICT_MISS,
+ "rocksdb.block.cache.compression.dict.miss"},
+ {BLOCK_CACHE_COMPRESSION_DICT_HIT,
+ "rocksdb.block.cache.compression.dict.hit"},
+ {BLOCK_CACHE_COMPRESSION_DICT_ADD,
+ "rocksdb.block.cache.compression.dict.add"},
+ {BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT,
+ "rocksdb.block.cache.compression.dict.bytes.insert"},
+ {BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT,
+ "rocksdb.block.cache.compression.dict.bytes.evict"},
+};
+
+const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
+ {DB_GET, "rocksdb.db.get.micros"},
+ {DB_WRITE, "rocksdb.db.write.micros"},
+ {COMPACTION_TIME, "rocksdb.compaction.times.micros"},
+ {COMPACTION_CPU_TIME, "rocksdb.compaction.times.cpu_micros"},
+ {SUBCOMPACTION_SETUP_TIME, "rocksdb.subcompaction.setup.times.micros"},
+ {TABLE_SYNC_MICROS, "rocksdb.table.sync.micros"},
+ {COMPACTION_OUTFILE_SYNC_MICROS, "rocksdb.compaction.outfile.sync.micros"},
+ {WAL_FILE_SYNC_MICROS, "rocksdb.wal.file.sync.micros"},
+ {MANIFEST_FILE_SYNC_MICROS, "rocksdb.manifest.file.sync.micros"},
+ {TABLE_OPEN_IO_MICROS, "rocksdb.table.open.io.micros"},
+ {DB_MULTIGET, "rocksdb.db.multiget.micros"},
+ {READ_BLOCK_COMPACTION_MICROS, "rocksdb.read.block.compaction.micros"},
+ {READ_BLOCK_GET_MICROS, "rocksdb.read.block.get.micros"},
+ {WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros"},
+ {STALL_L0_SLOWDOWN_COUNT, "rocksdb.l0.slowdown.count"},
+ {STALL_MEMTABLE_COMPACTION_COUNT, "rocksdb.memtable.compaction.count"},
+ {STALL_L0_NUM_FILES_COUNT, "rocksdb.num.files.stall.count"},
+ {HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"},
+ {SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"},
+ {NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction"},
+ {DB_SEEK, "rocksdb.db.seek.micros"},
+ {WRITE_STALL, "rocksdb.db.write.stall"},
+ {SST_READ_MICROS, "rocksdb.sst.read.micros"},
+ {NUM_SUBCOMPACTIONS_SCHEDULED, "rocksdb.num.subcompactions.scheduled"},
+ {BYTES_PER_READ, "rocksdb.bytes.per.read"},
+ {BYTES_PER_WRITE, "rocksdb.bytes.per.write"},
+ {BYTES_PER_MULTIGET, "rocksdb.bytes.per.multiget"},
+ {BYTES_COMPRESSED, "rocksdb.bytes.compressed"},
+ {BYTES_DECOMPRESSED, "rocksdb.bytes.decompressed"},
+ {COMPRESSION_TIMES_NANOS, "rocksdb.compression.times.nanos"},
+ {DECOMPRESSION_TIMES_NANOS, "rocksdb.decompression.times.nanos"},
+ {READ_NUM_MERGE_OPERANDS, "rocksdb.read.num.merge_operands"},
+ {BLOB_DB_KEY_SIZE, "rocksdb.blobdb.key.size"},
+ {BLOB_DB_VALUE_SIZE, "rocksdb.blobdb.value.size"},
+ {BLOB_DB_WRITE_MICROS, "rocksdb.blobdb.write.micros"},
+ {BLOB_DB_GET_MICROS, "rocksdb.blobdb.get.micros"},
+ {BLOB_DB_MULTIGET_MICROS, "rocksdb.blobdb.multiget.micros"},
+ {BLOB_DB_SEEK_MICROS, "rocksdb.blobdb.seek.micros"},
+ {BLOB_DB_NEXT_MICROS, "rocksdb.blobdb.next.micros"},
+ {BLOB_DB_PREV_MICROS, "rocksdb.blobdb.prev.micros"},
+ {BLOB_DB_BLOB_FILE_WRITE_MICROS, "rocksdb.blobdb.blob.file.write.micros"},
+ {BLOB_DB_BLOB_FILE_READ_MICROS, "rocksdb.blobdb.blob.file.read.micros"},
+ {BLOB_DB_BLOB_FILE_SYNC_MICROS, "rocksdb.blobdb.blob.file.sync.micros"},
+ {BLOB_DB_GC_MICROS, "rocksdb.blobdb.gc.micros"},
+ {BLOB_DB_COMPRESSION_MICROS, "rocksdb.blobdb.compression.micros"},
+ {BLOB_DB_DECOMPRESSION_MICROS, "rocksdb.blobdb.decompression.micros"},
+ {FLUSH_TIME, "rocksdb.db.flush.micros"},
+ {SST_BATCH_SIZE, "rocksdb.sst.batch.size"},
+};
+
+std::shared_ptr<Statistics> CreateDBStatistics() {
+ return std::make_shared<StatisticsImpl>(nullptr);
+}
+
+StatisticsImpl::StatisticsImpl(std::shared_ptr<Statistics> stats)
+ : stats_(std::move(stats)) {}
+
+StatisticsImpl::~StatisticsImpl() {}
+
+uint64_t StatisticsImpl::getTickerCount(uint32_t tickerType) const {
+ MutexLock lock(&aggregate_lock_);
+ return getTickerCountLocked(tickerType);
+}
+
+uint64_t StatisticsImpl::getTickerCountLocked(uint32_t tickerType) const {
+ assert(tickerType < TICKER_ENUM_MAX);
+ uint64_t res = 0;
+ for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) {
+ res += per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType];
+ }
+ return res;
+}
+
+void StatisticsImpl::histogramData(uint32_t histogramType,
+ HistogramData* const data) const {
+ MutexLock lock(&aggregate_lock_);
+ getHistogramImplLocked(histogramType)->Data(data);
+}
+
+std::unique_ptr<HistogramImpl> StatisticsImpl::getHistogramImplLocked(
+ uint32_t histogramType) const {
+ assert(histogramType < HISTOGRAM_ENUM_MAX);
+ std::unique_ptr<HistogramImpl> res_hist(new HistogramImpl());
+ for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) {
+ res_hist->Merge(
+ per_core_stats_.AccessAtCore(core_idx)->histograms_[histogramType]);
+ }
+ return res_hist;
+}
+
+std::string StatisticsImpl::getHistogramString(uint32_t histogramType) const {
+ MutexLock lock(&aggregate_lock_);
+ return getHistogramImplLocked(histogramType)->ToString();
+}
+
+void StatisticsImpl::setTickerCount(uint32_t tickerType, uint64_t count) {
+ {
+ MutexLock lock(&aggregate_lock_);
+ setTickerCountLocked(tickerType, count);
+ }
+ if (stats_ && tickerType < TICKER_ENUM_MAX) {
+ stats_->setTickerCount(tickerType, count);
+ }
+}
+
+void StatisticsImpl::setTickerCountLocked(uint32_t tickerType, uint64_t count) {
+ assert(tickerType < TICKER_ENUM_MAX);
+ for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) {
+ if (core_idx == 0) {
+ per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType] = count;
+ } else {
+ per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType] = 0;
+ }
+ }
+}
+
+uint64_t StatisticsImpl::getAndResetTickerCount(uint32_t tickerType) {
+ uint64_t sum = 0;
+ {
+ MutexLock lock(&aggregate_lock_);
+ assert(tickerType < TICKER_ENUM_MAX);
+ for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) {
+ sum +=
+ per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType].exchange(
+ 0, std::memory_order_relaxed);
+ }
+ }
+ if (stats_ && tickerType < TICKER_ENUM_MAX) {
+ stats_->setTickerCount(tickerType, 0);
+ }
+ return sum;
+}
+
+void StatisticsImpl::recordTick(uint32_t tickerType, uint64_t count) {
+ assert(tickerType < TICKER_ENUM_MAX);
+ per_core_stats_.Access()->tickers_[tickerType].fetch_add(
+ count, std::memory_order_relaxed);
+ if (stats_ && tickerType < TICKER_ENUM_MAX) {
+ stats_->recordTick(tickerType, count);
+ }
+}
+
+void StatisticsImpl::recordInHistogram(uint32_t histogramType, uint64_t value) {
+ assert(histogramType < HISTOGRAM_ENUM_MAX);
+ if (get_stats_level() <= StatsLevel::kExceptHistogramOrTimers) {
+ return;
+ }
+ per_core_stats_.Access()->histograms_[histogramType].Add(value);
+ if (stats_ && histogramType < HISTOGRAM_ENUM_MAX) {
+ stats_->recordInHistogram(histogramType, value);
+ }
+}
+
+Status StatisticsImpl::Reset() {
+ MutexLock lock(&aggregate_lock_);
+ for (uint32_t i = 0; i < TICKER_ENUM_MAX; ++i) {
+ setTickerCountLocked(i, 0);
+ }
+ for (uint32_t i = 0; i < HISTOGRAM_ENUM_MAX; ++i) {
+ for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) {
+ per_core_stats_.AccessAtCore(core_idx)->histograms_[i].Clear();
+ }
+ }
+ return Status::OK();
+}
+
+namespace {
+
+// a buffer size used for temp string buffers
+const int kTmpStrBufferSize = 200;
+
+} // namespace
+
+std::string StatisticsImpl::ToString() const {
+ MutexLock lock(&aggregate_lock_);
+ std::string res;
+ res.reserve(20000);
+ for (const auto& t : TickersNameMap) {
+ assert(t.first < TICKER_ENUM_MAX);
+ char buffer[kTmpStrBufferSize];
+ snprintf(buffer, kTmpStrBufferSize, "%s COUNT : %" PRIu64 "\n",
+ t.second.c_str(), getTickerCountLocked(t.first));
+ res.append(buffer);
+ }
+ for (const auto& h : HistogramsNameMap) {
+ assert(h.first < HISTOGRAM_ENUM_MAX);
+ char buffer[kTmpStrBufferSize];
+ HistogramData hData;
+ getHistogramImplLocked(h.first)->Data(&hData);
+ // don't handle failures - buffer should always be big enough and arguments
+ // should be provided correctly
+ int ret =
+ snprintf(buffer, kTmpStrBufferSize,
+ "%s P50 : %f P95 : %f P99 : %f P100 : %f COUNT : %" PRIu64
+ " SUM : %" PRIu64 "\n",
+ h.second.c_str(), hData.median, hData.percentile95,
+ hData.percentile99, hData.max, hData.count, hData.sum);
+ if (ret < 0 || ret >= kTmpStrBufferSize) {
+ assert(false);
+ continue;
+ }
+ res.append(buffer);
+ }
+ res.shrink_to_fit();
+ return res;
+}
+
+bool StatisticsImpl::getTickerMap(
+ std::map<std::string, uint64_t>* stats_map) const {
+ assert(stats_map);
+ if (!stats_map) return false;
+ stats_map->clear();
+ MutexLock lock(&aggregate_lock_);
+ for (const auto& t : TickersNameMap) {
+ assert(t.first < TICKER_ENUM_MAX);
+ (*stats_map)[t.second.c_str()] = getTickerCountLocked(t.first);
+ }
+ return true;
+}
+
+bool StatisticsImpl::HistEnabledForType(uint32_t type) const {
+ return type < HISTOGRAM_ENUM_MAX;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/statistics.h b/storage/rocksdb/rocksdb/monitoring/statistics.h
new file mode 100644
index 00000000..f633aa4e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/statistics.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/statistics.h"
+
+#include <atomic>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "monitoring/histogram.h"
+#include "port/likely.h"
+#include "port/port.h"
+#include "util/core_local.h"
+#include "util/mutexlock.h"
+
+#ifdef __clang__
+#define ROCKSDB_FIELD_UNUSED __attribute__((__unused__))
+#else
+#define ROCKSDB_FIELD_UNUSED
+#endif // __clang__
+
+#ifndef STRINGIFY
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+enum TickersInternal : uint32_t {
+ INTERNAL_TICKER_ENUM_START = TICKER_ENUM_MAX,
+ INTERNAL_TICKER_ENUM_MAX
+};
+
+enum HistogramsInternal : uint32_t {
+ INTERNAL_HISTOGRAM_START = HISTOGRAM_ENUM_MAX,
+ INTERNAL_HISTOGRAM_ENUM_MAX
+};
+
+class StatisticsImpl : public Statistics {
+ public:
+ StatisticsImpl(std::shared_ptr<Statistics> stats);
+ virtual ~StatisticsImpl();
+
+ virtual uint64_t getTickerCount(uint32_t ticker_type) const override;
+ virtual void histogramData(uint32_t histogram_type,
+ HistogramData* const data) const override;
+ std::string getHistogramString(uint32_t histogram_type) const override;
+
+ virtual void setTickerCount(uint32_t ticker_type, uint64_t count) override;
+ virtual uint64_t getAndResetTickerCount(uint32_t ticker_type) override;
+ virtual void recordTick(uint32_t ticker_type, uint64_t count) override;
+ // The function is implemented for now for backward compatibility reason.
+ // In case a user explictly calls it, for example, they may have a wrapped
+ // Statistics object, passing the call to recordTick() into here, nothing
+ // will break.
+ void measureTime(uint32_t histogramType, uint64_t time) override {
+ recordInHistogram(histogramType, time);
+ }
+ virtual void recordInHistogram(uint32_t histogram_type,
+ uint64_t value) override;
+
+ virtual Status Reset() override;
+ virtual std::string ToString() const override;
+ virtual bool getTickerMap(std::map<std::string, uint64_t>*) const override;
+ virtual bool HistEnabledForType(uint32_t type) const override;
+
+ private:
+ // If non-nullptr, forwards updates to the object pointed to by `stats_`.
+ std::shared_ptr<Statistics> stats_;
+ // Synchronizes anything that operates across other cores' local data,
+ // such that operations like Reset() can be performed atomically.
+ mutable port::Mutex aggregate_lock_;
+
+ // The ticker/histogram data are stored in this structure, which we will store
+ // per-core. It is cache-aligned, so tickers/histograms belonging to different
+ // cores can never share the same cache line.
+ //
+ // Alignment attributes expand to nothing depending on the platform
+ struct ALIGN_AS(CACHE_LINE_SIZE) StatisticsData {
+ std::atomic_uint_fast64_t tickers_[INTERNAL_TICKER_ENUM_MAX] = {{0}};
+ HistogramImpl histograms_[INTERNAL_HISTOGRAM_ENUM_MAX];
+#ifndef HAVE_ALIGNED_NEW
+ char
+ padding[(CACHE_LINE_SIZE -
+ (INTERNAL_TICKER_ENUM_MAX * sizeof(std::atomic_uint_fast64_t) +
+ INTERNAL_HISTOGRAM_ENUM_MAX * sizeof(HistogramImpl)) %
+ CACHE_LINE_SIZE)] ROCKSDB_FIELD_UNUSED;
+#endif
+ void *operator new(size_t s) { return port::cacheline_aligned_alloc(s); }
+ void *operator new[](size_t s) { return port::cacheline_aligned_alloc(s); }
+ void operator delete(void *p) { port::cacheline_aligned_free(p); }
+ void operator delete[](void *p) { port::cacheline_aligned_free(p); }
+ };
+
+ static_assert(sizeof(StatisticsData) % CACHE_LINE_SIZE == 0, "Expected " TOSTRING(CACHE_LINE_SIZE) "-byte aligned");
+
+ CoreLocalArray<StatisticsData> per_core_stats_;
+
+ uint64_t getTickerCountLocked(uint32_t ticker_type) const;
+ std::unique_ptr<HistogramImpl> getHistogramImplLocked(
+ uint32_t histogram_type) const;
+ void setTickerCountLocked(uint32_t ticker_type, uint64_t count);
+};
+
+// Utility functions
+inline void RecordInHistogram(Statistics* statistics, uint32_t histogram_type,
+ uint64_t value) {
+ if (statistics) {
+ statistics->recordInHistogram(histogram_type, value);
+ }
+}
+
+inline void RecordTimeToHistogram(Statistics* statistics,
+ uint32_t histogram_type, uint64_t value) {
+ if (statistics) {
+ statistics->reportTimeToHistogram(histogram_type, value);
+ }
+}
+
+inline void RecordTick(Statistics* statistics, uint32_t ticker_type,
+ uint64_t count = 1) {
+ if (statistics) {
+ statistics->recordTick(ticker_type, count);
+ }
+}
+
+inline void SetTickerCount(Statistics* statistics, uint32_t ticker_type,
+ uint64_t count) {
+ if (statistics) {
+ statistics->setTickerCount(ticker_type, count);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/statistics_test.cc b/storage/rocksdb/rocksdb/monitoring/statistics_test.cc
new file mode 100644
index 00000000..e497afcb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/statistics_test.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/stack_trace.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+#include "rocksdb/statistics.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<size_t>(Tickers::TICKER_ENUM_MAX),
+ TickersNameMap.size());
+
+ for (uint32_t t = 0; t < Tickers::TICKER_ENUM_MAX; t++) {
+ auto pair = TickersNameMap[static_cast<size_t>(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<size_t>(Histograms::HISTOGRAM_ENUM_MAX),
+ HistogramsNameMap.size());
+
+ for (uint32_t h = 0; h < Histograms::HISTOGRAM_ENUM_MAX; h++) {
+ auto pair = HistogramsNameMap[static_cast<size_t>(h)];
+ ASSERT_EQ(pair.first, h) << "Miss match at " << pair.second;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/monitoring/stats_history_test.cc b/storage/rocksdb/rocksdb/monitoring/stats_history_test.cc
new file mode 100644
index 00000000..56b8dc4f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/stats_history_test.cc
@@ -0,0 +1,653 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <limits>
+#include <string>
+#include <unordered_map>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/db_test_util.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 "rocksdb/stats_history.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") {}
+};
+#ifndef ROCKSDB_LITE
+
+TEST_F(StatsHistoryTest, RunStatsDumpPeriodSec) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_dump_period_sec = 5;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ int counter = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+#if defined(OS_MACOSX) && !defined(NDEBUG)
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
+ }
+ });
+#endif // OS_MACOSX && !NDEBUG
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::DumpStats:1", [&](void* /*arg*/) { counter++; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Reopen(options);
+ ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec);
+ dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(5); });
+ ASSERT_GE(counter, 1);
+
+ // Test cacel job through SetOptions
+ ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}}));
+ int old_val = counter;
+ for (int i = 6; i < 20; ++i) {
+ dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(i); });
+ }
+ ASSERT_EQ(counter, old_val);
+ Close();
+}
+
+// Test persistent stats background thread scheduling and cancelling
+TEST_F(StatsHistoryTest, StatsPersistScheduling) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 5;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+#if defined(OS_MACOSX) && !defined(NDEBUG)
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
+ }
+ });
+#endif // OS_MACOSX && !NDEBUG
+ int counter = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Reopen(options);
+ ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_persist_period_sec);
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ ASSERT_GE(counter, 1);
+
+ // Test cacel job through SetOptions
+ ASSERT_TRUE(dbfull()->TEST_IsPersistentStatsEnabled());
+ ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}}));
+ ASSERT_FALSE(dbfull()->TEST_IsPersistentStatsEnabled());
+ Close();
+}
+
+// Test enabling persistent stats for the first time
+TEST_F(StatsHistoryTest, PersistentStatsFreshInstall) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 0;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+#if defined(OS_MACOSX) && !defined(NDEBUG)
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
+ }
+ });
+#endif // OS_MACOSX && !NDEBUG
+ int counter = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Reopen(options);
+ ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "5"}}));
+ ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_persist_period_sec);
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ ASSERT_GE(counter, 1);
+ Close();
+}
+
+// TODO(Zhongyi): Move persistent stats related tests to a separate file
+TEST_F(StatsHistoryTest, GetStatsHistoryInMemory) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 5;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+#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) {
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+#endif // OS_MACOSX && !NDEBUG
+
+ CreateColumnFamilies({"pikachu"}, options);
+ ASSERT_OK(Put("foo", "bar"));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+
+ int mock_time = 1;
+ // Wait for stats persist to finish
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ std::unique_ptr<StatsHistoryIterator> stats_iter;
+ db_->GetStatsHistory(0 /*start_time*/, 6 /*end_time*/, &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(), 5);
+ stats_count += stats_map.size();
+ }
+ ASSERT_GT(stats_count, 0);
+ // Wait a bit and verify no more stats are found
+ for (mock_time = 6; mock_time < 20; ++mock_time) {
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(mock_time); });
+ }
+ db_->GetStatsHistory(0 /*start_time*/, 20 /*end_time*/, &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) {
+ Options options;
+ options.create_if_missing = true;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.stats_persist_period_sec = 1;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+#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) {
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+#endif // OS_MACOSX && !NDEBUG
+
+ 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"));
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ int mock_time = 1;
+ // Wait for stats persist to finish
+ for (; mock_time < 5; ++mock_time) {
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(mock_time); });
+ }
+
+ // 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());
+ db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ for (; mock_time < 10; ++mock_time) {
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(mock_time); });
+ }
+ std::unique_ptr<StatsHistoryIterator> stats_iter;
+ db_->GetStatsHistory(0 /*start_time*/, 10 /*end_time*/, &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, 9);
+ ASSERT_GE(stats_history_size, 12000);
+ // capping memory cost at 12000 bytes since one slice is around 10000~12000
+ ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "12000"}}));
+ ASSERT_EQ(12000, dbfull()->GetDBOptions().stats_history_buffer_size);
+ // Wait for stats persist to finish
+ for (; mock_time < 20; ++mock_time) {
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(mock_time); });
+ }
+ db_->GetStatsHistory(0 /*start_time*/, 20 /*end_time*/, &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 < 12000 &&
+ 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) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 5;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.persist_stats_to_disk = true;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ CreateColumnFamilies({"pikachu"}, options);
+ ASSERT_OK(Put("foo", "bar"));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ ASSERT_EQ(Get("foo"), "bar");
+
+ // Wait for stats persist to finish
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ auto iter =
+ db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily());
+ int key_count1 = countkeys(iter);
+ delete iter;
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(10); });
+ iter =
+ db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily());
+ int key_count2 = countkeys(iter);
+ delete iter;
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(15); });
+ 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<StatsHistoryIterator> stats_iter;
+ db_->GetStatsHistory(0 /*start_time*/, 16 /*end_time*/, &stats_iter);
+ ASSERT_TRUE(stats_iter != nullptr);
+ size_t stats_count = 0;
+ int slice_count = 0;
+ int non_zero_count = 0;
+ for (int i = 1; stats_iter->Valid(); stats_iter->Next(), i++) {
+ slice_count++;
+ auto stats_map = stats_iter->GetStatsMap();
+ ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i);
+ 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);
+ db_->GetStatsHistory(0 /*start_time*/, 16 /*end_time*/, &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) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 5;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.persist_stats_to_disk = true;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ std::map<std::string, uint64_t> stats_map_before;
+ ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_before));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ CreateColumnFamilies({"pikachu"}, options);
+ ASSERT_OK(Put("foo", "bar"));
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ ASSERT_EQ(Get("foo"), "bar");
+
+ // Wait for stats persist to finish
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ auto iter =
+ db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily());
+ countkeys(iter);
+ delete iter;
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(10); });
+ iter =
+ db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily());
+ countkeys(iter);
+ delete iter;
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(15); });
+ iter =
+ db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily());
+ countkeys(iter);
+ delete iter;
+ dbfull()->TEST_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(20); });
+
+ std::map<std::string, uint64_t> stats_map_after;
+ ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_after));
+ std::unique_ptr<StatsHistoryIterator> stats_iter;
+ db_->GetStatsHistory(0 /*start_time*/, 21 /*end_time*/, &stats_iter);
+ ASSERT_TRUE(stats_iter != nullptr);
+ std::string sample = "rocksdb.num.iterator.deleted";
+ uint64_t recovered_value = 0;
+ for (int i = 1; stats_iter->Valid(); stats_iter->Next(), ++i) {
+ auto stats_map = stats_iter->GetStatsMap();
+ ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i);
+ 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);
+ db_->GetStatsHistory(0 /*start_time*/, 21 /*end_time*/, &stats_iter);
+ ASSERT_TRUE(stats_iter != nullptr);
+ uint64_t new_recovered_value = 0;
+ for (int i = 1; stats_iter->Valid(); stats_iter->Next(), i++) {
+ auto stats_map = stats_iter->GetStatsMap();
+ ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i);
+ 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) {
+ Options options;
+ options.create_if_missing = true;
+ options.stats_persist_period_sec = 5;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.persist_stats_to_disk = true;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ 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");
+ dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ 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<StatsHistoryIterator> stats_iter;
+ db_->GetStatsHistory(0 /*start_time*/, 5 /*end_time*/, &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, 2);
+
+ 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
+ db_->GetStatsHistory(0 /*start_time*/, 5 /*end_time*/, &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, 2);
+
+ 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));
+ Flush();
+ Close();
+ // Now check keys in read only mode.
+ ASSERT_OK(ReadOnlyReopen(options));
+}
+
+TEST_F(StatsHistoryTest, ForceManualFlushStatsCF) {
+ Options options;
+ options.create_if_missing = true;
+ options.write_buffer_size = 1024 * 1024 * 10; // 10 Mb
+ options.stats_persist_period_sec = 5;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.persist_stats_to_disk = true;
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env;
+ mock_env.reset(new ROCKSDB_NAMESPACE::MockTimeEnv(env_));
+ mock_env->set_current_time(0); // in seconds
+ options.env = mock_env.get();
+ CreateColumnFamilies({"pikachu"}, options);
+ ReopenWithColumnFamilies({"default", "pikachu"}, options);
+ ColumnFamilyData* cfd_default =
+ static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily())
+ ->cfd();
+ ColumnFamilyData* cfd_stats = static_cast<ColumnFamilyHandleImpl*>(
+ dbfull()->PersistentStatsColumnFamily())
+ ->cfd();
+ ColumnFamilyData* cfd_test =
+ static_cast<ColumnFamilyHandleImpl*>(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_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
+ // writing to all three cf, flush default cf
+ // LogNumbers: default: 14, stats: 4, pikachu: 4
+ ASSERT_OK(Flush());
+ ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
+ ASSERT_LT(cfd_stats->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_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(10); });
+ // 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_WaitForPersistStatsRun(
+ [&] { mock_env->set_current_time(15); });
+ // 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();
+}
+
+#endif // !ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_impl.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_impl.cc
new file mode 100644
index 00000000..9619dfd8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_impl.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sstream>
+
+#include "rocksdb/env.h"
+#include "rocksdb/thread_status.h"
+#include "util/string_util.h"
+#include "util/thread_operation.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+std::string ThreadStatus::GetThreadTypeName(
+ ThreadStatus::ThreadType thread_type) {
+ switch (thread_type) {
+ case ThreadStatus::ThreadType::HIGH_PRIORITY:
+ return "High Pri";
+ case ThreadStatus::ThreadType::LOW_PRIORITY:
+ return "Low Pri";
+ case ThreadStatus::ThreadType::USER:
+ return "User";
+ case ThreadStatus::ThreadType::BOTTOM_PRIORITY:
+ return "Bottom Pri";
+ case ThreadStatus::ThreadType::NUM_THREAD_TYPES:
+ assert(false);
+ }
+ return "Unknown";
+}
+
+const std::string& ThreadStatus::GetOperationName(
+ ThreadStatus::OperationType op_type) {
+ if (op_type < 0 || op_type >= NUM_OP_TYPES) {
+ return global_operation_table[OP_UNKNOWN].name;
+ }
+ return global_operation_table[op_type].name;
+}
+
+const std::string& ThreadStatus::GetOperationStageName(
+ ThreadStatus::OperationStage stage) {
+ if (stage < 0 || stage >= NUM_OP_STAGES) {
+ return global_op_stage_table[STAGE_UNKNOWN].name;
+ }
+ return global_op_stage_table[stage].name;
+}
+
+const std::string& ThreadStatus::GetStateName(
+ ThreadStatus::StateType state_type) {
+ if (state_type < 0 || state_type >= NUM_STATE_TYPES) {
+ return global_state_table[STATE_UNKNOWN].name;
+ }
+ return global_state_table[state_type].name;
+}
+
+const std::string ThreadStatus::MicrosToString(uint64_t micros) {
+ if (micros == 0) {
+ return "";
+ }
+ const int kBufferLen = 100;
+ char buffer[kBufferLen];
+ AppendHumanMicros(micros, buffer, kBufferLen, false);
+ return std::string(buffer);
+}
+
+const std::string& ThreadStatus::GetOperationPropertyName(
+ ThreadStatus::OperationType op_type, int i) {
+ static const std::string empty_str = "";
+ switch (op_type) {
+ case ThreadStatus::OP_COMPACTION:
+ if (i >= NUM_COMPACTION_PROPERTIES) {
+ return empty_str;
+ }
+ return compaction_operation_properties[i].name;
+ case ThreadStatus::OP_FLUSH:
+ if (i >= NUM_FLUSH_PROPERTIES) {
+ return empty_str;
+ }
+ return flush_operation_properties[i].name;
+ default:
+ return empty_str;
+ }
+}
+
+std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
+ ThreadStatus::OperationType op_type, const uint64_t* op_properties) {
+ int num_properties;
+ switch (op_type) {
+ case OP_COMPACTION:
+ num_properties = NUM_COMPACTION_PROPERTIES;
+ break;
+ case OP_FLUSH:
+ num_properties = NUM_FLUSH_PROPERTIES;
+ break;
+ default:
+ num_properties = 0;
+ }
+
+ std::map<std::string, uint64_t> property_map;
+ for (int i = 0; i < num_properties; ++i) {
+ if (op_type == OP_COMPACTION && i == COMPACTION_INPUT_OUTPUT_LEVEL) {
+ property_map.insert({"BaseInputLevel", op_properties[i] >> 32});
+ property_map.insert(
+ {"OutputLevel", op_properties[i] % (uint64_t(1) << 32U)});
+ } else if (op_type == OP_COMPACTION && i == COMPACTION_PROP_FLAGS) {
+ property_map.insert({"IsManual", ((op_properties[i] & 2) >> 1)});
+ property_map.insert({"IsDeletion", ((op_properties[i] & 4) >> 2)});
+ property_map.insert({"IsTrivialMove", ((op_properties[i] & 8) >> 3)});
+ } else {
+ property_map.insert(
+ {GetOperationPropertyName(op_type, i), op_properties[i]});
+ }
+ }
+ return property_map;
+}
+
+#else
+
+std::string ThreadStatus::GetThreadTypeName(
+ ThreadStatus::ThreadType /*thread_type*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+const std::string& ThreadStatus::GetOperationName(
+ ThreadStatus::OperationType /*op_type*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+const std::string& ThreadStatus::GetOperationStageName(
+ ThreadStatus::OperationStage /*stage*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+const std::string& ThreadStatus::GetStateName(
+ ThreadStatus::StateType /*state_type*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+const std::string ThreadStatus::MicrosToString(uint64_t /*op_elapsed_time*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+const std::string& ThreadStatus::GetOperationPropertyName(
+ ThreadStatus::OperationType /*op_type*/, int /*i*/) {
+ static std::string dummy_str = "";
+ return dummy_str;
+}
+
+std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
+ ThreadStatus::OperationType /*op_type*/,
+ const uint64_t* /*op_properties*/) {
+ return std::map<std::string, uint64_t>();
+}
+
+#endif // ROCKSDB_USING_THREAD_STATUS
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc
new file mode 100644
index 00000000..7e4b299a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc
@@ -0,0 +1,314 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/thread_status_updater.h"
+#include <memory>
+#include "port/likely.h"
+#include "rocksdb/env.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+
+__thread ThreadStatusData* ThreadStatusUpdater::thread_status_data_ = nullptr;
+
+void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType ttype,
+ uint64_t thread_id) {
+ if (UNLIKELY(thread_status_data_ == nullptr)) {
+ thread_status_data_ = new ThreadStatusData();
+ thread_status_data_->thread_type = ttype;
+ thread_status_data_->thread_id = thread_id;
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+ thread_data_set_.insert(thread_status_data_);
+ }
+
+ ClearThreadOperationProperties();
+}
+
+void ThreadStatusUpdater::UnregisterThread() {
+ if (thread_status_data_ != nullptr) {
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+ thread_data_set_.erase(thread_status_data_);
+ delete thread_status_data_;
+ thread_status_data_ = nullptr;
+ }
+}
+
+void ThreadStatusUpdater::ResetThreadStatus() {
+ ClearThreadState();
+ ClearThreadOperation();
+ SetColumnFamilyInfoKey(nullptr);
+}
+
+void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* cf_key) {
+ auto* data = Get();
+ if (data == nullptr) {
+ return;
+ }
+ // set the tracking flag based on whether cf_key is non-null or not.
+ // If enable_thread_tracking is set to false, the input cf_key
+ // would be nullptr.
+ data->enable_tracking = (cf_key != nullptr);
+ data->cf_key.store(const_cast<void*>(cf_key), std::memory_order_relaxed);
+}
+
+const void* ThreadStatusUpdater::GetColumnFamilyInfoKey() {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return nullptr;
+ }
+ return data->cf_key.load(std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::SetThreadOperation(
+ const ThreadStatus::OperationType type) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ // NOTE: Our practice here is to set all the thread operation properties
+ // and stage before we set thread operation, and thread operation
+ // will be set in std::memory_order_release. This is to ensure
+ // whenever a thread operation is not OP_UNKNOWN, we will always
+ // have a consistent information on its properties.
+ data->operation_type.store(type, std::memory_order_release);
+ if (type == ThreadStatus::OP_UNKNOWN) {
+ data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN,
+ std::memory_order_relaxed);
+ ClearThreadOperationProperties();
+ }
+}
+
+void ThreadStatusUpdater::SetThreadOperationProperty(int i, uint64_t value) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->op_properties[i].store(value, std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::IncreaseThreadOperationProperty(int i,
+ uint64_t delta) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->op_properties[i].fetch_add(delta, std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::SetOperationStartTime(const uint64_t start_time) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->op_start_time.store(start_time, std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::ClearThreadOperation() {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN,
+ std::memory_order_relaxed);
+ data->operation_type.store(ThreadStatus::OP_UNKNOWN,
+ std::memory_order_relaxed);
+ ClearThreadOperationProperties();
+}
+
+void ThreadStatusUpdater::ClearThreadOperationProperties() {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ for (int i = 0; i < ThreadStatus::kNumOperationProperties; ++i) {
+ data->op_properties[i].store(0, std::memory_order_relaxed);
+ }
+}
+
+ThreadStatus::OperationStage ThreadStatusUpdater::SetThreadOperationStage(
+ ThreadStatus::OperationStage stage) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return ThreadStatus::STAGE_UNKNOWN;
+ }
+ return data->operation_stage.exchange(stage, std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::SetThreadState(const ThreadStatus::StateType type) {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->state_type.store(type, std::memory_order_relaxed);
+}
+
+void ThreadStatusUpdater::ClearThreadState() {
+ auto* data = GetLocalThreadStatus();
+ if (data == nullptr) {
+ return;
+ }
+ data->state_type.store(ThreadStatus::STATE_UNKNOWN,
+ std::memory_order_relaxed);
+}
+
+Status ThreadStatusUpdater::GetThreadList(
+ std::vector<ThreadStatus>* thread_list) {
+ thread_list->clear();
+ std::vector<std::shared_ptr<ThreadStatusData>> valid_list;
+ uint64_t now_micros = Env::Default()->NowMicros();
+
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+ for (auto* thread_data : thread_data_set_) {
+ assert(thread_data);
+ auto thread_id = thread_data->thread_id.load(std::memory_order_relaxed);
+ auto thread_type = thread_data->thread_type.load(std::memory_order_relaxed);
+ // Since any change to cf_info_map requires thread_list_mutex,
+ // which is currently held by GetThreadList(), here we can safely
+ // use "memory_order_relaxed" to load the cf_key.
+ auto cf_key = thread_data->cf_key.load(std::memory_order_relaxed);
+
+ ThreadStatus::OperationType op_type = ThreadStatus::OP_UNKNOWN;
+ ThreadStatus::OperationStage op_stage = ThreadStatus::STAGE_UNKNOWN;
+ ThreadStatus::StateType state_type = ThreadStatus::STATE_UNKNOWN;
+ uint64_t op_elapsed_micros = 0;
+ uint64_t op_props[ThreadStatus::kNumOperationProperties] = {0};
+
+ auto iter = cf_info_map_.find(cf_key);
+ if (iter != cf_info_map_.end()) {
+ op_type = thread_data->operation_type.load(std::memory_order_acquire);
+ // display lower-level info only when higher-level info is available.
+ if (op_type != ThreadStatus::OP_UNKNOWN) {
+ op_elapsed_micros = now_micros - thread_data->op_start_time.load(
+ std::memory_order_relaxed);
+ op_stage = thread_data->operation_stage.load(std::memory_order_relaxed);
+ state_type = thread_data->state_type.load(std::memory_order_relaxed);
+ for (int i = 0; i < ThreadStatus::kNumOperationProperties; ++i) {
+ op_props[i] =
+ thread_data->op_properties[i].load(std::memory_order_relaxed);
+ }
+ }
+ }
+
+ thread_list->emplace_back(
+ thread_id, thread_type,
+ iter != cf_info_map_.end() ? iter->second.db_name : "",
+ iter != cf_info_map_.end() ? iter->second.cf_name : "", op_type,
+ op_elapsed_micros, op_stage, op_props, state_type);
+ }
+
+ return Status::OK();
+}
+
+ThreadStatusData* ThreadStatusUpdater::GetLocalThreadStatus() {
+ if (thread_status_data_ == nullptr) {
+ return nullptr;
+ }
+ if (!thread_status_data_->enable_tracking) {
+ assert(thread_status_data_->cf_key.load(std::memory_order_relaxed) ==
+ nullptr);
+ return nullptr;
+ }
+ return thread_status_data_;
+}
+
+void ThreadStatusUpdater::NewColumnFamilyInfo(const void* db_key,
+ const std::string& db_name,
+ const void* cf_key,
+ const std::string& cf_name) {
+ // Acquiring same lock as GetThreadList() to guarantee
+ // a consistent view of global column family table (cf_info_map).
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+
+ cf_info_map_.emplace(std::piecewise_construct, std::make_tuple(cf_key),
+ std::make_tuple(db_key, db_name, cf_name));
+ db_key_map_[db_key].insert(cf_key);
+}
+
+void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* cf_key) {
+ // Acquiring same lock as GetThreadList() to guarantee
+ // a consistent view of global column family table (cf_info_map).
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+
+ auto cf_pair = cf_info_map_.find(cf_key);
+ if (cf_pair != cf_info_map_.end()) {
+ // Remove its entry from db_key_map_ by the following steps:
+ // 1. Obtain the entry in db_key_map_ whose set contains cf_key
+ // 2. Remove it from the set.
+ ConstantColumnFamilyInfo& cf_info = cf_pair->second;
+ auto db_pair = db_key_map_.find(cf_info.db_key);
+ assert(db_pair != db_key_map_.end());
+ size_t result __attribute__((__unused__));
+ result = db_pair->second.erase(cf_key);
+ assert(result);
+ cf_info_map_.erase(cf_pair);
+ }
+}
+
+void ThreadStatusUpdater::EraseDatabaseInfo(const void* db_key) {
+ // Acquiring same lock as GetThreadList() to guarantee
+ // a consistent view of global column family table (cf_info_map).
+ std::lock_guard<std::mutex> lck(thread_list_mutex_);
+ auto db_pair = db_key_map_.find(db_key);
+ if (UNLIKELY(db_pair == db_key_map_.end())) {
+ // In some occasional cases such as DB::Open fails, we won't
+ // register ColumnFamilyInfo for a db.
+ return;
+ }
+
+ for (auto cf_key : db_pair->second) {
+ auto cf_pair = cf_info_map_.find(cf_key);
+ if (cf_pair != cf_info_map_.end()) {
+ cf_info_map_.erase(cf_pair);
+ }
+ }
+ db_key_map_.erase(db_key);
+}
+
+#else
+
+void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType /*ttype*/,
+ uint64_t /*thread_id*/) {}
+
+void ThreadStatusUpdater::UnregisterThread() {}
+
+void ThreadStatusUpdater::ResetThreadStatus() {}
+
+void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* /*cf_key*/) {}
+
+void ThreadStatusUpdater::SetThreadOperation(
+ const ThreadStatus::OperationType /*type*/) {}
+
+void ThreadStatusUpdater::ClearThreadOperation() {}
+
+void ThreadStatusUpdater::SetThreadState(
+ const ThreadStatus::StateType /*type*/) {}
+
+void ThreadStatusUpdater::ClearThreadState() {}
+
+Status ThreadStatusUpdater::GetThreadList(
+ std::vector<ThreadStatus>* /*thread_list*/) {
+ return Status::NotSupported(
+ "GetThreadList is not supported in the current running environment.");
+}
+
+void ThreadStatusUpdater::NewColumnFamilyInfo(const void* /*db_key*/,
+ const std::string& /*db_name*/,
+ const void* /*cf_key*/,
+ const std::string& /*cf_name*/) {}
+
+void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* /*cf_key*/) {}
+
+void ThreadStatusUpdater::EraseDatabaseInfo(const void* /*db_key*/) {}
+
+void ThreadStatusUpdater::SetThreadOperationProperty(int /*i*/,
+ uint64_t /*value*/) {}
+
+void ThreadStatusUpdater::IncreaseThreadOperationProperty(int /*i*/,
+ uint64_t /*delta*/) {}
+
+#endif // ROCKSDB_USING_THREAD_STATUS
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h
new file mode 100644
index 00000000..ac8c0f35
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h
@@ -0,0 +1,233 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// The implementation of ThreadStatus.
+//
+// Note that we make get and set access to ThreadStatusData lockless.
+// As a result, ThreadStatusData as a whole is not atomic. However,
+// we guarantee consistent ThreadStatusData all the time whenever
+// user call GetThreadList(). This consistency guarantee is done
+// by having the following constraint in the internal implementation
+// of set and get order:
+//
+// 1. When reset any information in ThreadStatusData, always start from
+// clearing up the lower-level information first.
+// 2. When setting any information in ThreadStatusData, always start from
+// setting the higher-level information.
+// 3. When returning ThreadStatusData to the user, fields are fetched from
+// higher-level to lower-level. In addition, where there's a nullptr
+// in one field, then all fields that has lower-level than that field
+// should be ignored.
+//
+// The high to low level information would be:
+// thread_id > thread_type > db > cf > operation > state
+//
+// This means user might not always get full information, but whenever
+// returned by the GetThreadList() is guaranteed to be consistent.
+#pragma once
+#include <atomic>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "rocksdb/status.h"
+#include "rocksdb/thread_status.h"
+#include "port/port.h"
+#include "util/thread_operation.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyHandle;
+
+// The structure that keeps constant information about a column family.
+struct ConstantColumnFamilyInfo {
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ public:
+ ConstantColumnFamilyInfo(
+ const void* _db_key,
+ const std::string& _db_name,
+ const std::string& _cf_name) :
+ db_key(_db_key), db_name(_db_name), cf_name(_cf_name) {}
+ const void* db_key;
+ const std::string db_name;
+ const std::string cf_name;
+#endif // ROCKSDB_USING_THREAD_STATUS
+};
+
+// the internal data-structure that is used to reflect the current
+// status of a thread using a set of atomic pointers.
+struct ThreadStatusData {
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ explicit ThreadStatusData() : enable_tracking(false) {
+ thread_id.store(0);
+ thread_type.store(ThreadStatus::USER);
+ cf_key.store(nullptr);
+ operation_type.store(ThreadStatus::OP_UNKNOWN);
+ op_start_time.store(0);
+ state_type.store(ThreadStatus::STATE_UNKNOWN);
+ }
+
+ // A flag to indicate whether the thread tracking is enabled
+ // in the current thread. This value will be updated based on whether
+ // the associated Options::enable_thread_tracking is set to true
+ // in ThreadStatusUtil::SetColumnFamily().
+ //
+ // If set to false, then SetThreadOperation and SetThreadState
+ // will be no-op.
+ bool enable_tracking;
+
+ std::atomic<uint64_t> thread_id;
+ std::atomic<ThreadStatus::ThreadType> thread_type;
+ std::atomic<void*> cf_key;
+ std::atomic<ThreadStatus::OperationType> operation_type;
+ std::atomic<uint64_t> op_start_time;
+ std::atomic<ThreadStatus::OperationStage> operation_stage;
+ std::atomic<uint64_t> op_properties[ThreadStatus::kNumOperationProperties];
+ std::atomic<ThreadStatus::StateType> state_type;
+#endif // ROCKSDB_USING_THREAD_STATUS
+};
+
+// The class that stores and updates the status of the current thread
+// using a thread-local ThreadStatusData.
+//
+// In most of the case, you should use ThreadStatusUtil to update
+// the status of the current thread instead of using ThreadSatusUpdater
+// directly.
+//
+// @see ThreadStatusUtil
+class ThreadStatusUpdater {
+ public:
+ ThreadStatusUpdater() {}
+
+ // Releases all ThreadStatusData of all active threads.
+ virtual ~ThreadStatusUpdater() {}
+
+ // Unregister the current thread.
+ void UnregisterThread();
+
+ // Reset the status of the current thread. This includes resetting
+ // ColumnFamilyInfoKey, ThreadOperation, and ThreadState.
+ void ResetThreadStatus();
+
+ // Set the id of the current thread.
+ void SetThreadID(uint64_t thread_id);
+
+ // Register the current thread for tracking.
+ void RegisterThread(ThreadStatus::ThreadType ttype, uint64_t thread_id);
+
+ // Update the column-family info of the current thread by setting
+ // its thread-local pointer of ThreadStateInfo to the correct entry.
+ void SetColumnFamilyInfoKey(const void* cf_key);
+
+ // returns the column family info key.
+ const void* GetColumnFamilyInfoKey();
+
+ // Update the thread operation of the current thread.
+ void SetThreadOperation(const ThreadStatus::OperationType type);
+
+ // The start time of the current thread operation. It is in the format
+ // of micro-seconds since some fixed point in time.
+ void SetOperationStartTime(const uint64_t start_time);
+
+ // Set the "i"th property of the current operation.
+ //
+ // NOTE: Our practice here is to set all the thread operation properties
+ // and stage before we set thread operation, and thread operation
+ // will be set in std::memory_order_release. This is to ensure
+ // whenever a thread operation is not OP_UNKNOWN, we will always
+ // have a consistent information on its properties.
+ void SetThreadOperationProperty(
+ int i, uint64_t value);
+
+ // Increase the "i"th property of the current operation with
+ // the specified delta.
+ void IncreaseThreadOperationProperty(
+ int i, uint64_t delta);
+
+ // Update the thread operation stage of the current thread.
+ ThreadStatus::OperationStage SetThreadOperationStage(
+ const ThreadStatus::OperationStage stage);
+
+ // Clear thread operation of the current thread.
+ void ClearThreadOperation();
+
+ // Reset all thread-operation-properties to 0.
+ void ClearThreadOperationProperties();
+
+ // Update the thread state of the current thread.
+ void SetThreadState(const ThreadStatus::StateType type);
+
+ // Clear the thread state of the current thread.
+ void ClearThreadState();
+
+ // Obtain the status of all active registered threads.
+ Status GetThreadList(
+ std::vector<ThreadStatus>* thread_list);
+
+ // Create an entry in the global ColumnFamilyInfo table for the
+ // specified column family. This function should be called only
+ // when the current thread does not hold db_mutex.
+ void NewColumnFamilyInfo(
+ const void* db_key, const std::string& db_name,
+ const void* cf_key, const std::string& cf_name);
+
+ // Erase all ConstantColumnFamilyInfo that is associated with the
+ // specified db instance. This function should be called only when
+ // the current thread does not hold db_mutex.
+ void EraseDatabaseInfo(const void* db_key);
+
+ // Erase the ConstantColumnFamilyInfo that is associated with the
+ // specified ColumnFamilyData. This function should be called only
+ // when the current thread does not hold db_mutex.
+ void EraseColumnFamilyInfo(const void* cf_key);
+
+ // Verifies whether the input ColumnFamilyHandles matches
+ // the information stored in the current cf_info_map.
+ void TEST_VerifyColumnFamilyInfoMap(
+ const std::vector<ColumnFamilyHandle*>& handles,
+ bool check_exist);
+
+ protected:
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ // The thread-local variable for storing thread status.
+ static __thread ThreadStatusData* thread_status_data_;
+
+ // Returns the pointer to the thread status data only when the
+ // thread status data is non-null and has enable_tracking == true.
+ ThreadStatusData* GetLocalThreadStatus();
+
+ // Directly returns the pointer to thread_status_data_ without
+ // checking whether enabling_tracking is true of not.
+ ThreadStatusData* Get() {
+ return thread_status_data_;
+ }
+
+ // The mutex that protects cf_info_map and db_key_map.
+ std::mutex thread_list_mutex_;
+
+ // The current status data of all active threads.
+ std::unordered_set<ThreadStatusData*> thread_data_set_;
+
+ // A global map that keeps the column family information. It is stored
+ // globally instead of inside DB is to avoid the situation where DB is
+ // closing while GetThreadList function already get the pointer to its
+ // CopnstantColumnFamilyInfo.
+ std::unordered_map<const void*, ConstantColumnFamilyInfo> cf_info_map_;
+
+ // A db_key to cf_key map that allows erasing elements in cf_info_map
+ // associated to the same db_key faster.
+ std::unordered_map<
+ const void*, std::unordered_set<const void*>> db_key_map_;
+
+#else
+ static ThreadStatusData* thread_status_data_;
+#endif // ROCKSDB_USING_THREAD_STATUS
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_updater_debug.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_updater_debug.cc
new file mode 100644
index 00000000..5937d91f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_updater_debug.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <mutex>
+
+#include "db/column_family.h"
+#include "monitoring/thread_status_updater.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef NDEBUG
+#ifdef ROCKSDB_USING_THREAD_STATUS
+void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
+ const std::vector<ColumnFamilyHandle*>& handles, bool check_exist) {
+ std::unique_lock<std::mutex> lock(thread_list_mutex_);
+ if (check_exist) {
+ assert(cf_info_map_.size() == handles.size());
+ }
+ for (auto* handle : handles) {
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(handle)->cfd();
+ auto iter __attribute__((__unused__)) = cf_info_map_.find(cfd);
+ if (check_exist) {
+ assert(iter != cf_info_map_.end());
+ assert(iter->second.cf_name == cfd->GetName());
+ } else {
+ assert(iter == cf_info_map_.end());
+ }
+ }
+}
+
+#else
+
+void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
+ const std::vector<ColumnFamilyHandle*>& /*handles*/, bool /*check_exist*/) {
+}
+
+#endif // ROCKSDB_USING_THREAD_STATUS
+#endif // !NDEBUG
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_util.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_util.cc
new file mode 100644
index 00000000..13a79163
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_util.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/thread_status_util.h"
+
+#include "monitoring/thread_status_updater.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+__thread ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ =
+ nullptr;
+__thread bool ThreadStatusUtil::thread_updater_initialized_ = false;
+
+void ThreadStatusUtil::RegisterThread(const Env* env,
+ ThreadStatus::ThreadType thread_type) {
+ if (!MaybeInitThreadLocalUpdater(env)) {
+ return;
+ }
+ assert(thread_updater_local_cache_);
+ thread_updater_local_cache_->RegisterThread(thread_type, env->GetThreadID());
+}
+
+void ThreadStatusUtil::UnregisterThread() {
+ thread_updater_initialized_ = false;
+ if (thread_updater_local_cache_ != nullptr) {
+ thread_updater_local_cache_->UnregisterThread();
+ thread_updater_local_cache_ = nullptr;
+ }
+}
+
+void ThreadStatusUtil::SetColumnFamily(const ColumnFamilyData* cfd,
+ const Env* env,
+ bool enable_thread_tracking) {
+ if (!MaybeInitThreadLocalUpdater(env)) {
+ return;
+ }
+ assert(thread_updater_local_cache_);
+ if (cfd != nullptr && enable_thread_tracking) {
+ thread_updater_local_cache_->SetColumnFamilyInfoKey(cfd);
+ } else {
+ // When cfd == nullptr or enable_thread_tracking == false, we set
+ // ColumnFamilyInfoKey to nullptr, which makes SetThreadOperation
+ // and SetThreadState become no-op.
+ thread_updater_local_cache_->SetColumnFamilyInfoKey(nullptr);
+ }
+}
+
+void ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType op) {
+ if (thread_updater_local_cache_ == nullptr) {
+ // thread_updater_local_cache_ must be set in SetColumnFamily
+ // or other ThreadStatusUtil functions.
+ return;
+ }
+
+ if (op != ThreadStatus::OP_UNKNOWN) {
+ uint64_t current_time = Env::Default()->NowMicros();
+ thread_updater_local_cache_->SetOperationStartTime(current_time);
+ } else {
+ // TDOO(yhchiang): we could report the time when we set operation to
+ // OP_UNKNOWN once the whole instrumentation has been done.
+ thread_updater_local_cache_->SetOperationStartTime(0);
+ }
+ thread_updater_local_cache_->SetThreadOperation(op);
+}
+
+ThreadStatus::OperationStage ThreadStatusUtil::SetThreadOperationStage(
+ ThreadStatus::OperationStage stage) {
+ if (thread_updater_local_cache_ == nullptr) {
+ // thread_updater_local_cache_ must be set in SetColumnFamily
+ // or other ThreadStatusUtil functions.
+ return ThreadStatus::STAGE_UNKNOWN;
+ }
+
+ return thread_updater_local_cache_->SetThreadOperationStage(stage);
+}
+
+void ThreadStatusUtil::SetThreadOperationProperty(int code, uint64_t value) {
+ if (thread_updater_local_cache_ == nullptr) {
+ // thread_updater_local_cache_ must be set in SetColumnFamily
+ // or other ThreadStatusUtil functions.
+ return;
+ }
+
+ thread_updater_local_cache_->SetThreadOperationProperty(code, value);
+}
+
+void ThreadStatusUtil::IncreaseThreadOperationProperty(int code,
+ uint64_t delta) {
+ if (thread_updater_local_cache_ == nullptr) {
+ // thread_updater_local_cache_ must be set in SetColumnFamily
+ // or other ThreadStatusUtil functions.
+ return;
+ }
+
+ thread_updater_local_cache_->IncreaseThreadOperationProperty(code, delta);
+}
+
+void ThreadStatusUtil::SetThreadState(ThreadStatus::StateType state) {
+ if (thread_updater_local_cache_ == nullptr) {
+ // thread_updater_local_cache_ must be set in SetColumnFamily
+ // or other ThreadStatusUtil functions.
+ return;
+ }
+
+ thread_updater_local_cache_->SetThreadState(state);
+}
+
+void ThreadStatusUtil::ResetThreadStatus() {
+ if (thread_updater_local_cache_ == nullptr) {
+ return;
+ }
+ thread_updater_local_cache_->ResetThreadStatus();
+}
+
+void ThreadStatusUtil::NewColumnFamilyInfo(const DB* db,
+ const ColumnFamilyData* cfd,
+ const std::string& cf_name,
+ const Env* env) {
+ if (!MaybeInitThreadLocalUpdater(env)) {
+ return;
+ }
+ assert(thread_updater_local_cache_);
+ if (thread_updater_local_cache_) {
+ thread_updater_local_cache_->NewColumnFamilyInfo(db, db->GetName(), cfd,
+ cf_name);
+ }
+}
+
+void ThreadStatusUtil::EraseColumnFamilyInfo(const ColumnFamilyData* cfd) {
+ if (thread_updater_local_cache_ == nullptr) {
+ return;
+ }
+ thread_updater_local_cache_->EraseColumnFamilyInfo(cfd);
+}
+
+void ThreadStatusUtil::EraseDatabaseInfo(const DB* db) {
+ ThreadStatusUpdater* thread_updater = db->GetEnv()->GetThreadStatusUpdater();
+ if (thread_updater == nullptr) {
+ return;
+ }
+ thread_updater->EraseDatabaseInfo(db);
+}
+
+bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* env) {
+ if (!thread_updater_initialized_ && env != nullptr) {
+ thread_updater_initialized_ = true;
+ thread_updater_local_cache_ = env->GetThreadStatusUpdater();
+ }
+ return (thread_updater_local_cache_ != nullptr);
+}
+
+AutoThreadOperationStageUpdater::AutoThreadOperationStageUpdater(
+ ThreadStatus::OperationStage stage) {
+ prev_stage_ = ThreadStatusUtil::SetThreadOperationStage(stage);
+}
+
+AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {
+ ThreadStatusUtil::SetThreadOperationStage(prev_stage_);
+}
+
+#else
+
+ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ = nullptr;
+bool ThreadStatusUtil::thread_updater_initialized_ = false;
+
+bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) {
+ return false;
+}
+
+void ThreadStatusUtil::SetColumnFamily(const ColumnFamilyData* /*cfd*/,
+ const Env* /*env*/,
+ bool /*enable_thread_tracking*/) {}
+
+void ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType /*op*/) {}
+
+void ThreadStatusUtil::SetThreadOperationProperty(int /*code*/,
+ uint64_t /*value*/) {}
+
+void ThreadStatusUtil::IncreaseThreadOperationProperty(int /*code*/,
+ uint64_t /*delta*/) {}
+
+void ThreadStatusUtil::SetThreadState(ThreadStatus::StateType /*state*/) {}
+
+void ThreadStatusUtil::NewColumnFamilyInfo(const DB* /*db*/,
+ const ColumnFamilyData* /*cfd*/,
+ const std::string& /*cf_name*/,
+ const Env* /*env*/) {}
+
+void ThreadStatusUtil::EraseColumnFamilyInfo(const ColumnFamilyData* /*cfd*/) {}
+
+void ThreadStatusUtil::EraseDatabaseInfo(const DB* /*db*/) {}
+
+void ThreadStatusUtil::ResetThreadStatus() {}
+
+AutoThreadOperationStageUpdater::AutoThreadOperationStageUpdater(
+ ThreadStatus::OperationStage /*stage*/) {}
+
+AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {}
+
+#endif // ROCKSDB_USING_THREAD_STATUS
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_util.h b/storage/rocksdb/rocksdb/monitoring/thread_status_util.h
new file mode 100644
index 00000000..b4d97b0b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_util.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "monitoring/thread_status_updater.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/thread_status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyData;
+
+// The static utility class for updating thread-local status.
+//
+// The thread-local status is updated via the thread-local cached
+// pointer thread_updater_local_cache_. During each function call,
+// when ThreadStatusUtil finds thread_updater_local_cache_ is
+// left uninitialized (determined by thread_updater_initialized_),
+// it will tries to initialize it using the return value of
+// Env::GetThreadStatusUpdater(). When thread_updater_local_cache_
+// is initialized by a non-null pointer, each function call will
+// then update the status of the current thread. Otherwise,
+// all function calls to ThreadStatusUtil will be no-op.
+class ThreadStatusUtil {
+ public:
+ // Register the current thread for tracking.
+ static void RegisterThread(
+ const Env* env, ThreadStatus::ThreadType thread_type);
+
+ // Unregister the current thread.
+ static void UnregisterThread();
+
+ // Create an entry in the global ColumnFamilyInfo table for the
+ // specified column family. This function should be called only
+ // when the current thread does not hold db_mutex.
+ static void NewColumnFamilyInfo(const DB* db, const ColumnFamilyData* cfd,
+ const std::string& cf_name, const Env* env);
+
+ // Erase the ConstantColumnFamilyInfo that is associated with the
+ // specified ColumnFamilyData. This function should be called only
+ // when the current thread does not hold db_mutex.
+ static void EraseColumnFamilyInfo(const ColumnFamilyData* cfd);
+
+ // Erase all ConstantColumnFamilyInfo that is associated with the
+ // specified db instance. This function should be called only when
+ // the current thread does not hold db_mutex.
+ static void EraseDatabaseInfo(const DB* db);
+
+ // Update the thread status to indicate the current thread is doing
+ // something related to the specified column family.
+ static void SetColumnFamily(const ColumnFamilyData* cfd, const Env* env,
+ bool enable_thread_tracking);
+
+ static void SetThreadOperation(ThreadStatus::OperationType type);
+
+ static ThreadStatus::OperationStage SetThreadOperationStage(
+ ThreadStatus::OperationStage stage);
+
+ static void SetThreadOperationProperty(
+ int code, uint64_t value);
+
+ static void IncreaseThreadOperationProperty(
+ int code, uint64_t delta);
+
+ static void SetThreadState(ThreadStatus::StateType type);
+
+ static void ResetThreadStatus();
+
+#ifndef NDEBUG
+ static void TEST_SetStateDelay(
+ const ThreadStatus::StateType state, int micro);
+ static void TEST_StateDelay(const ThreadStatus::StateType state);
+#endif
+
+ protected:
+ // Initialize the thread-local ThreadStatusUpdater when it finds
+ // the cached value is nullptr. Returns true if it has cached
+ // a non-null pointer.
+ static bool MaybeInitThreadLocalUpdater(const Env* env);
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ // A boolean flag indicating whether thread_updater_local_cache_
+ // is initialized. It is set to true when an Env uses any
+ // ThreadStatusUtil functions using the current thread other
+ // than UnregisterThread(). It will be set to false when
+ // UnregisterThread() is called.
+ //
+ // When this variable is set to true, thread_updater_local_cache_
+ // will not be updated until this variable is again set to false
+ // in UnregisterThread().
+ static __thread bool thread_updater_initialized_;
+
+ // The thread-local cached ThreadStatusUpdater that caches the
+ // thread_status_updater_ of the first Env that uses any ThreadStatusUtil
+ // function other than UnregisterThread(). This variable will
+ // be cleared when UnregisterThread() is called.
+ //
+ // When this variable is set to a non-null pointer, then the status
+ // of the current thread will be updated when a function of
+ // ThreadStatusUtil is called. Otherwise, all functions of
+ // ThreadStatusUtil will be no-op.
+ //
+ // When thread_updater_initialized_ is set to true, this variable
+ // will not be updated until this thread_updater_initialized_ is
+ // again set to false in UnregisterThread().
+ static __thread ThreadStatusUpdater* thread_updater_local_cache_;
+#else
+ static bool thread_updater_initialized_;
+ static ThreadStatusUpdater* thread_updater_local_cache_;
+#endif
+};
+
+// A helper class for updating thread state. It will set the
+// thread state according to the input parameter in its constructor
+// and set the thread state to the previous state in its destructor.
+class AutoThreadOperationStageUpdater {
+ public:
+ explicit AutoThreadOperationStageUpdater(
+ ThreadStatus::OperationStage stage);
+ ~AutoThreadOperationStageUpdater();
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ private:
+ ThreadStatus::OperationStage prev_stage_;
+#endif
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_util_debug.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_util_debug.cc
new file mode 100644
index 00000000..375fe8c0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/monitoring/thread_status_util_debug.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+
+#include "monitoring/thread_status_updater.h"
+#include "monitoring/thread_status_util.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef NDEBUG
+// the delay for debugging purpose.
+static std::atomic<int> states_delay[ThreadStatus::NUM_STATE_TYPES];
+
+void ThreadStatusUtil::TEST_SetStateDelay(
+ const ThreadStatus::StateType state, int micro) {
+ states_delay[state].store(micro, std::memory_order_relaxed);
+}
+
+void ThreadStatusUtil::TEST_StateDelay(const ThreadStatus::StateType state) {
+ auto delay = states_delay[state].load(std::memory_order_relaxed);
+ if (delay > 0) {
+ Env::Default()->SleepForMicroseconds(delay);
+ }
+}
+
+#endif // !NDEBUG
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/cf_options.cc b/storage/rocksdb/rocksdb/options/cf_options.cc
new file mode 100644
index 00000000..bc9e301e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/cf_options.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "options/cf_options.h"
+
+#include <cassert>
+#include <cinttypes>
+#include <limits>
+#include <string>
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/concurrent_task_limiter.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ImmutableCFOptions::ImmutableCFOptions(const Options& options)
+ : ImmutableCFOptions(ImmutableDBOptions(options), options) {}
+
+ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
+ const ColumnFamilyOptions& cf_options)
+ : compaction_style(cf_options.compaction_style),
+ compaction_pri(cf_options.compaction_pri),
+ user_comparator(cf_options.comparator),
+ internal_comparator(InternalKeyComparator(cf_options.comparator)),
+ merge_operator(cf_options.merge_operator.get()),
+ compaction_filter(cf_options.compaction_filter),
+ compaction_filter_factory(cf_options.compaction_filter_factory.get()),
+ min_write_buffer_number_to_merge(
+ cf_options.min_write_buffer_number_to_merge),
+ max_write_buffer_number_to_maintain(
+ cf_options.max_write_buffer_number_to_maintain),
+ max_write_buffer_size_to_maintain(
+ cf_options.max_write_buffer_size_to_maintain),
+ inplace_update_support(cf_options.inplace_update_support),
+ inplace_callback(cf_options.inplace_callback),
+ info_log(db_options.info_log.get()),
+ statistics(db_options.statistics.get()),
+ rate_limiter(db_options.rate_limiter.get()),
+ info_log_level(db_options.info_log_level),
+ env(db_options.env),
+ fs(db_options.fs.get()),
+ allow_mmap_reads(db_options.allow_mmap_reads),
+ allow_mmap_writes(db_options.allow_mmap_writes),
+ db_paths(db_options.db_paths),
+ memtable_factory(cf_options.memtable_factory.get()),
+ table_factory(cf_options.table_factory.get()),
+ table_properties_collector_factories(
+ cf_options.table_properties_collector_factories),
+ advise_random_on_open(db_options.advise_random_on_open),
+ bloom_locality(cf_options.bloom_locality),
+ purge_redundant_kvs_while_flush(
+ cf_options.purge_redundant_kvs_while_flush),
+ use_fsync(db_options.use_fsync),
+ compression_per_level(cf_options.compression_per_level),
+ bottommost_compression(cf_options.bottommost_compression),
+ bottommost_compression_opts(cf_options.bottommost_compression_opts),
+ compression_opts(cf_options.compression_opts),
+ level_compaction_dynamic_level_bytes(
+ cf_options.level_compaction_dynamic_level_bytes),
+ access_hint_on_compaction_start(
+ db_options.access_hint_on_compaction_start),
+ new_table_reader_for_compaction_inputs(
+ db_options.new_table_reader_for_compaction_inputs),
+ num_levels(cf_options.num_levels),
+ optimize_filters_for_hits(cf_options.optimize_filters_for_hits),
+ force_consistency_checks(cf_options.force_consistency_checks),
+ allow_ingest_behind(db_options.allow_ingest_behind),
+ preserve_deletes(db_options.preserve_deletes),
+ listeners(db_options.listeners),
+ row_cache(db_options.row_cache),
+ max_subcompactions(db_options.max_subcompactions),
+ memtable_insert_with_hint_prefix_extractor(
+ cf_options.memtable_insert_with_hint_prefix_extractor.get()),
+ cf_paths(cf_options.cf_paths),
+ compaction_thread_limiter(cf_options.compaction_thread_limiter),
+ sst_file_checksum_func(db_options.sst_file_checksum_func.get()) {}
+
+// Multiple two operands. If they overflow, return op1.
+uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) {
+ if (op1 == 0 || op2 <= 0) {
+ return 0;
+ }
+ if (port::kMaxUint64 / op1 < op2) {
+ return op1;
+ }
+ return static_cast<uint64_t>(op1 * op2);
+}
+
+// when level_compaction_dynamic_level_bytes is true and leveled compaction
+// is used, the base level is not always L1, so precomupted max_file_size can
+// no longer be used. Recompute file_size_for_level from base level.
+uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options,
+ int level, CompactionStyle compaction_style, int base_level,
+ bool level_compaction_dynamic_level_bytes) {
+ if (!level_compaction_dynamic_level_bytes || level < base_level ||
+ compaction_style != kCompactionStyleLevel) {
+ assert(level >= 0);
+ assert(level < (int)cf_options.max_file_size.size());
+ return cf_options.max_file_size[level];
+ } else {
+ assert(level >= 0 && base_level >= 0);
+ assert(level - base_level < (int)cf_options.max_file_size.size());
+ return cf_options.max_file_size[level - base_level];
+ }
+}
+
+void MutableCFOptions::RefreshDerivedOptions(int num_levels,
+ CompactionStyle compaction_style) {
+ max_file_size.resize(num_levels);
+ for (int i = 0; i < num_levels; ++i) {
+ if (i == 0 && compaction_style == kCompactionStyleUniversal) {
+ max_file_size[i] = ULLONG_MAX;
+ } else if (i > 1) {
+ max_file_size[i] = MultiplyCheckOverflow(max_file_size[i - 1],
+ target_file_size_multiplier);
+ } else {
+ max_file_size[i] = target_file_size_base;
+ }
+ }
+}
+
+void MutableCFOptions::Dump(Logger* log) const {
+ // Memtable related options
+ ROCKS_LOG_INFO(log,
+ " write_buffer_size: %" ROCKSDB_PRIszt,
+ write_buffer_size);
+ ROCKS_LOG_INFO(log, " max_write_buffer_number: %d",
+ max_write_buffer_number);
+ ROCKS_LOG_INFO(log,
+ " arena_block_size: %" ROCKSDB_PRIszt,
+ arena_block_size);
+ ROCKS_LOG_INFO(log, " memtable_prefix_bloom_ratio: %f",
+ memtable_prefix_bloom_size_ratio);
+ ROCKS_LOG_INFO(log, " memtable_whole_key_filtering: %d",
+ memtable_whole_key_filtering);
+ ROCKS_LOG_INFO(log,
+ " memtable_huge_page_size: %" ROCKSDB_PRIszt,
+ memtable_huge_page_size);
+ ROCKS_LOG_INFO(log,
+ " max_successive_merges: %" ROCKSDB_PRIszt,
+ max_successive_merges);
+ ROCKS_LOG_INFO(log,
+ " inplace_update_num_locks: %" ROCKSDB_PRIszt,
+ inplace_update_num_locks);
+ ROCKS_LOG_INFO(
+ log, " prefix_extractor: %s",
+ prefix_extractor == nullptr ? "nullptr" : prefix_extractor->Name());
+ ROCKS_LOG_INFO(log, " disable_auto_compactions: %d",
+ disable_auto_compactions);
+ ROCKS_LOG_INFO(log, " soft_pending_compaction_bytes_limit: %" PRIu64,
+ soft_pending_compaction_bytes_limit);
+ ROCKS_LOG_INFO(log, " hard_pending_compaction_bytes_limit: %" PRIu64,
+ hard_pending_compaction_bytes_limit);
+ ROCKS_LOG_INFO(log, " level0_file_num_compaction_trigger: %d",
+ level0_file_num_compaction_trigger);
+ ROCKS_LOG_INFO(log, " level0_slowdown_writes_trigger: %d",
+ level0_slowdown_writes_trigger);
+ ROCKS_LOG_INFO(log, " level0_stop_writes_trigger: %d",
+ level0_stop_writes_trigger);
+ ROCKS_LOG_INFO(log, " max_compaction_bytes: %" PRIu64,
+ max_compaction_bytes);
+ ROCKS_LOG_INFO(log, " target_file_size_base: %" PRIu64,
+ target_file_size_base);
+ ROCKS_LOG_INFO(log, " target_file_size_multiplier: %d",
+ target_file_size_multiplier);
+ ROCKS_LOG_INFO(log, " max_bytes_for_level_base: %" PRIu64,
+ max_bytes_for_level_base);
+ ROCKS_LOG_INFO(log, " max_bytes_for_level_multiplier: %f",
+ max_bytes_for_level_multiplier);
+ ROCKS_LOG_INFO(log, " ttl: %" PRIu64,
+ ttl);
+ ROCKS_LOG_INFO(log, " periodic_compaction_seconds: %" PRIu64,
+ periodic_compaction_seconds);
+ std::string result;
+ char buf[10];
+ for (const auto m : max_bytes_for_level_multiplier_additional) {
+ snprintf(buf, sizeof(buf), "%d, ", m);
+ result += buf;
+ }
+ if (result.size() >= 2) {
+ result.resize(result.size() - 2);
+ } else {
+ result = "";
+ }
+
+ ROCKS_LOG_INFO(log, "max_bytes_for_level_multiplier_additional: %s",
+ result.c_str());
+ ROCKS_LOG_INFO(log, " max_sequential_skip_in_iterations: %" PRIu64,
+ max_sequential_skip_in_iterations);
+ ROCKS_LOG_INFO(log, " paranoid_file_checks: %d",
+ paranoid_file_checks);
+ ROCKS_LOG_INFO(log, " report_bg_io_stats: %d",
+ report_bg_io_stats);
+ ROCKS_LOG_INFO(log, " compression: %d",
+ static_cast<int>(compression));
+
+ // Universal Compaction Options
+ ROCKS_LOG_INFO(log, "compaction_options_universal.size_ratio : %d",
+ compaction_options_universal.size_ratio);
+ ROCKS_LOG_INFO(log, "compaction_options_universal.min_merge_width : %d",
+ compaction_options_universal.min_merge_width);
+ ROCKS_LOG_INFO(log, "compaction_options_universal.max_merge_width : %d",
+ compaction_options_universal.max_merge_width);
+ ROCKS_LOG_INFO(
+ log, "compaction_options_universal.max_size_amplification_percent : %d",
+ compaction_options_universal.max_size_amplification_percent);
+ ROCKS_LOG_INFO(log,
+ "compaction_options_universal.compression_size_percent : %d",
+ compaction_options_universal.compression_size_percent);
+ ROCKS_LOG_INFO(log, "compaction_options_universal.stop_style : %d",
+ compaction_options_universal.stop_style);
+ ROCKS_LOG_INFO(
+ log, "compaction_options_universal.allow_trivial_move : %d",
+ static_cast<int>(compaction_options_universal.allow_trivial_move));
+
+ // FIFO Compaction Options
+ ROCKS_LOG_INFO(log, "compaction_options_fifo.max_table_files_size : %" PRIu64,
+ compaction_options_fifo.max_table_files_size);
+ ROCKS_LOG_INFO(log, "compaction_options_fifo.allow_compaction : %d",
+ compaction_options_fifo.allow_compaction);
+}
+
+MutableCFOptions::MutableCFOptions(const Options& options)
+ : MutableCFOptions(ColumnFamilyOptions(options)) {}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/cf_options.h b/storage/rocksdb/rocksdb/options/cf_options.h
new file mode 100644
index 00000000..b53ed840
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/cf_options.h
@@ -0,0 +1,269 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "options/db_options.h"
+#include "rocksdb/options.h"
+#include "util/compression.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// ImmutableCFOptions is a data struct used by RocksDB internal. It contains a
+// subset of Options that should not be changed during the entire lifetime
+// of DB. Raw pointers defined in this struct do not have ownership to the data
+// they point to. Options contains std::shared_ptr to these data.
+struct ImmutableCFOptions {
+ explicit ImmutableCFOptions(const Options& options);
+
+ ImmutableCFOptions(const ImmutableDBOptions& db_options,
+ const ColumnFamilyOptions& cf_options);
+
+ CompactionStyle compaction_style;
+
+ CompactionPri compaction_pri;
+
+ const Comparator* user_comparator;
+ InternalKeyComparator internal_comparator;
+
+ MergeOperator* merge_operator;
+
+ const CompactionFilter* compaction_filter;
+
+ CompactionFilterFactory* compaction_filter_factory;
+
+ int min_write_buffer_number_to_merge;
+
+ int max_write_buffer_number_to_maintain;
+
+ int64_t max_write_buffer_size_to_maintain;
+
+ bool inplace_update_support;
+
+ UpdateStatus (*inplace_callback)(char* existing_value,
+ uint32_t* existing_value_size,
+ Slice delta_value,
+ std::string* merged_value);
+
+ Logger* info_log;
+
+ Statistics* statistics;
+
+ RateLimiter* rate_limiter;
+
+ InfoLogLevel info_log_level;
+
+ Env* env;
+
+ FileSystem* fs;
+
+ // Allow the OS to mmap file for reading sst tables. Default: false
+ bool allow_mmap_reads;
+
+ // Allow the OS to mmap file for writing. Default: false
+ bool allow_mmap_writes;
+
+ std::vector<DbPath> db_paths;
+
+ MemTableRepFactory* memtable_factory;
+
+ TableFactory* table_factory;
+
+ Options::TablePropertiesCollectorFactories
+ table_properties_collector_factories;
+
+ bool advise_random_on_open;
+
+ // This options is required by PlainTableReader. May need to move it
+ // to PlainTableOptions just like bloom_bits_per_key
+ uint32_t bloom_locality;
+
+ bool purge_redundant_kvs_while_flush;
+
+ bool use_fsync;
+
+ std::vector<CompressionType> compression_per_level;
+
+ CompressionType bottommost_compression;
+
+ CompressionOptions bottommost_compression_opts;
+
+ CompressionOptions compression_opts;
+
+ bool level_compaction_dynamic_level_bytes;
+
+ Options::AccessHint access_hint_on_compaction_start;
+
+ bool new_table_reader_for_compaction_inputs;
+
+ int num_levels;
+
+ bool optimize_filters_for_hits;
+
+ bool force_consistency_checks;
+
+ bool allow_ingest_behind;
+
+ bool preserve_deletes;
+
+ // A vector of EventListeners which callback functions will be called
+ // when specific RocksDB event happens.
+ std::vector<std::shared_ptr<EventListener>> listeners;
+
+ std::shared_ptr<Cache> row_cache;
+
+ uint32_t max_subcompactions;
+
+ const SliceTransform* memtable_insert_with_hint_prefix_extractor;
+
+ std::vector<DbPath> cf_paths;
+
+ std::shared_ptr<ConcurrentTaskLimiter> compaction_thread_limiter;
+
+ FileChecksumFunc* sst_file_checksum_func;
+};
+
+struct MutableCFOptions {
+ explicit MutableCFOptions(const ColumnFamilyOptions& options)
+ : write_buffer_size(options.write_buffer_size),
+ max_write_buffer_number(options.max_write_buffer_number),
+ arena_block_size(options.arena_block_size),
+ memtable_prefix_bloom_size_ratio(
+ options.memtable_prefix_bloom_size_ratio),
+ memtable_whole_key_filtering(options.memtable_whole_key_filtering),
+ memtable_huge_page_size(options.memtable_huge_page_size),
+ max_successive_merges(options.max_successive_merges),
+ inplace_update_num_locks(options.inplace_update_num_locks),
+ prefix_extractor(options.prefix_extractor),
+ disable_auto_compactions(options.disable_auto_compactions),
+ soft_pending_compaction_bytes_limit(
+ options.soft_pending_compaction_bytes_limit),
+ hard_pending_compaction_bytes_limit(
+ options.hard_pending_compaction_bytes_limit),
+ level0_file_num_compaction_trigger(
+ options.level0_file_num_compaction_trigger),
+ level0_slowdown_writes_trigger(options.level0_slowdown_writes_trigger),
+ level0_stop_writes_trigger(options.level0_stop_writes_trigger),
+ max_compaction_bytes(options.max_compaction_bytes),
+ target_file_size_base(options.target_file_size_base),
+ target_file_size_multiplier(options.target_file_size_multiplier),
+ max_bytes_for_level_base(options.max_bytes_for_level_base),
+ max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
+ ttl(options.ttl),
+ periodic_compaction_seconds(options.periodic_compaction_seconds),
+ max_bytes_for_level_multiplier_additional(
+ options.max_bytes_for_level_multiplier_additional),
+ compaction_options_fifo(options.compaction_options_fifo),
+ compaction_options_universal(options.compaction_options_universal),
+ max_sequential_skip_in_iterations(
+ options.max_sequential_skip_in_iterations),
+ paranoid_file_checks(options.paranoid_file_checks),
+ report_bg_io_stats(options.report_bg_io_stats),
+ compression(options.compression),
+ sample_for_compression(options.sample_for_compression) {
+ RefreshDerivedOptions(options.num_levels, options.compaction_style);
+ }
+
+ MutableCFOptions()
+ : write_buffer_size(0),
+ max_write_buffer_number(0),
+ arena_block_size(0),
+ memtable_prefix_bloom_size_ratio(0),
+ memtable_whole_key_filtering(false),
+ memtable_huge_page_size(0),
+ max_successive_merges(0),
+ inplace_update_num_locks(0),
+ prefix_extractor(nullptr),
+ disable_auto_compactions(false),
+ soft_pending_compaction_bytes_limit(0),
+ hard_pending_compaction_bytes_limit(0),
+ level0_file_num_compaction_trigger(0),
+ level0_slowdown_writes_trigger(0),
+ level0_stop_writes_trigger(0),
+ max_compaction_bytes(0),
+ target_file_size_base(0),
+ target_file_size_multiplier(0),
+ max_bytes_for_level_base(0),
+ max_bytes_for_level_multiplier(0),
+ ttl(0),
+ periodic_compaction_seconds(0),
+ compaction_options_fifo(),
+ max_sequential_skip_in_iterations(0),
+ paranoid_file_checks(false),
+ report_bg_io_stats(false),
+ compression(Snappy_Supported() ? kSnappyCompression : kNoCompression),
+ sample_for_compression(0) {}
+
+ explicit MutableCFOptions(const Options& options);
+
+ // Must be called after any change to MutableCFOptions
+ void RefreshDerivedOptions(int num_levels, CompactionStyle compaction_style);
+
+ void RefreshDerivedOptions(const ImmutableCFOptions& ioptions) {
+ RefreshDerivedOptions(ioptions.num_levels, ioptions.compaction_style);
+ }
+
+ int MaxBytesMultiplerAdditional(int level) const {
+ if (level >=
+ static_cast<int>(max_bytes_for_level_multiplier_additional.size())) {
+ return 1;
+ }
+ return max_bytes_for_level_multiplier_additional[level];
+ }
+
+ void Dump(Logger* log) const;
+
+ // Memtable related options
+ size_t write_buffer_size;
+ int max_write_buffer_number;
+ size_t arena_block_size;
+ double memtable_prefix_bloom_size_ratio;
+ bool memtable_whole_key_filtering;
+ size_t memtable_huge_page_size;
+ size_t max_successive_merges;
+ size_t inplace_update_num_locks;
+ std::shared_ptr<const SliceTransform> prefix_extractor;
+
+ // Compaction related options
+ bool disable_auto_compactions;
+ uint64_t soft_pending_compaction_bytes_limit;
+ uint64_t hard_pending_compaction_bytes_limit;
+ int level0_file_num_compaction_trigger;
+ int level0_slowdown_writes_trigger;
+ int level0_stop_writes_trigger;
+ uint64_t max_compaction_bytes;
+ uint64_t target_file_size_base;
+ int target_file_size_multiplier;
+ uint64_t max_bytes_for_level_base;
+ double max_bytes_for_level_multiplier;
+ uint64_t ttl;
+ uint64_t periodic_compaction_seconds;
+ std::vector<int> max_bytes_for_level_multiplier_additional;
+ CompactionOptionsFIFO compaction_options_fifo;
+ CompactionOptionsUniversal compaction_options_universal;
+
+ // Misc options
+ uint64_t max_sequential_skip_in_iterations;
+ bool paranoid_file_checks;
+ bool report_bg_io_stats;
+ CompressionType compression;
+ uint64_t sample_for_compression;
+
+ // Derived options
+ // Per-level target file size.
+ std::vector<uint64_t> max_file_size;
+};
+
+uint64_t MultiplyCheckOverflow(uint64_t op1, double op2);
+
+// Get the max file size in a given level.
+uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options,
+ int level, CompactionStyle compaction_style, int base_level = 1,
+ bool level_compaction_dynamic_level_bytes = false);
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/db_options.cc b/storage/rocksdb/rocksdb/options/db_options.cc
new file mode 100644
index 00000000..0dcc8ef7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/db_options.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "options/db_options.h"
+
+#include <cinttypes>
+
+#include "db/version_edit.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/sst_file_manager.h"
+#include "rocksdb/wal_filter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ImmutableDBOptions::ImmutableDBOptions() : ImmutableDBOptions(Options()) {}
+
+ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
+ : create_if_missing(options.create_if_missing),
+ create_missing_column_families(options.create_missing_column_families),
+ error_if_exists(options.error_if_exists),
+ paranoid_checks(options.paranoid_checks),
+ env(options.env),
+ fs(options.file_system),
+ rate_limiter(options.rate_limiter),
+ sst_file_manager(options.sst_file_manager),
+ info_log(options.info_log),
+ info_log_level(options.info_log_level),
+ max_file_opening_threads(options.max_file_opening_threads),
+ statistics(options.statistics),
+ use_fsync(options.use_fsync),
+ db_paths(options.db_paths),
+ db_log_dir(options.db_log_dir),
+ wal_dir(options.wal_dir),
+ max_subcompactions(options.max_subcompactions),
+ max_background_flushes(options.max_background_flushes),
+ max_log_file_size(options.max_log_file_size),
+ log_file_time_to_roll(options.log_file_time_to_roll),
+ keep_log_file_num(options.keep_log_file_num),
+ recycle_log_file_num(options.recycle_log_file_num),
+ max_manifest_file_size(options.max_manifest_file_size),
+ table_cache_numshardbits(options.table_cache_numshardbits),
+ wal_ttl_seconds(options.WAL_ttl_seconds),
+ wal_size_limit_mb(options.WAL_size_limit_MB),
+ max_write_batch_group_size_bytes(
+ options.max_write_batch_group_size_bytes),
+ manifest_preallocation_size(options.manifest_preallocation_size),
+ allow_mmap_reads(options.allow_mmap_reads),
+ allow_mmap_writes(options.allow_mmap_writes),
+ use_direct_reads(options.use_direct_reads),
+ use_direct_io_for_flush_and_compaction(
+ options.use_direct_io_for_flush_and_compaction),
+ allow_fallocate(options.allow_fallocate),
+ is_fd_close_on_exec(options.is_fd_close_on_exec),
+ advise_random_on_open(options.advise_random_on_open),
+ db_write_buffer_size(options.db_write_buffer_size),
+ write_buffer_manager(options.write_buffer_manager),
+ access_hint_on_compaction_start(options.access_hint_on_compaction_start),
+ new_table_reader_for_compaction_inputs(
+ options.new_table_reader_for_compaction_inputs),
+ random_access_max_buffer_size(options.random_access_max_buffer_size),
+ use_adaptive_mutex(options.use_adaptive_mutex),
+ listeners(options.listeners),
+ enable_thread_tracking(options.enable_thread_tracking),
+ enable_pipelined_write(options.enable_pipelined_write),
+ unordered_write(options.unordered_write),
+ allow_concurrent_memtable_write(options.allow_concurrent_memtable_write),
+ enable_write_thread_adaptive_yield(
+ options.enable_write_thread_adaptive_yield),
+ write_thread_max_yield_usec(options.write_thread_max_yield_usec),
+ write_thread_slow_yield_usec(options.write_thread_slow_yield_usec),
+ skip_stats_update_on_db_open(options.skip_stats_update_on_db_open),
+ skip_checking_sst_file_sizes_on_db_open(
+ options.skip_checking_sst_file_sizes_on_db_open),
+ wal_recovery_mode(options.wal_recovery_mode),
+ allow_2pc(options.allow_2pc),
+ row_cache(options.row_cache),
+#ifndef ROCKSDB_LITE
+ wal_filter(options.wal_filter),
+#endif // ROCKSDB_LITE
+ fail_if_options_file_error(options.fail_if_options_file_error),
+ dump_malloc_stats(options.dump_malloc_stats),
+ avoid_flush_during_recovery(options.avoid_flush_during_recovery),
+ allow_ingest_behind(options.allow_ingest_behind),
+ preserve_deletes(options.preserve_deletes),
+ two_write_queues(options.two_write_queues),
+ manual_wal_flush(options.manual_wal_flush),
+ atomic_flush(options.atomic_flush),
+ avoid_unnecessary_blocking_io(options.avoid_unnecessary_blocking_io),
+ persist_stats_to_disk(options.persist_stats_to_disk),
+ write_dbid_to_manifest(options.write_dbid_to_manifest),
+ log_readahead_size(options.log_readahead_size),
+ sst_file_checksum_func(options.sst_file_checksum_func) {
+}
+
+void ImmutableDBOptions::Dump(Logger* log) const {
+ ROCKS_LOG_HEADER(log, " Options.error_if_exists: %d",
+ error_if_exists);
+ ROCKS_LOG_HEADER(log, " Options.create_if_missing: %d",
+ create_if_missing);
+ ROCKS_LOG_HEADER(log, " Options.paranoid_checks: %d",
+ paranoid_checks);
+ ROCKS_LOG_HEADER(log, " Options.env: %p",
+ env);
+ ROCKS_LOG_HEADER(log, " Options.fs: %s",
+ fs->Name());
+ ROCKS_LOG_HEADER(log, " Options.info_log: %p",
+ info_log.get());
+ ROCKS_LOG_HEADER(log, " Options.max_file_opening_threads: %d",
+ max_file_opening_threads);
+ ROCKS_LOG_HEADER(log, " Options.statistics: %p",
+ statistics.get());
+ ROCKS_LOG_HEADER(log, " Options.use_fsync: %d",
+ use_fsync);
+ ROCKS_LOG_HEADER(
+ log, " Options.max_log_file_size: %" ROCKSDB_PRIszt,
+ max_log_file_size);
+ ROCKS_LOG_HEADER(log,
+ " Options.max_manifest_file_size: %" PRIu64,
+ max_manifest_file_size);
+ ROCKS_LOG_HEADER(
+ log, " Options.log_file_time_to_roll: %" ROCKSDB_PRIszt,
+ log_file_time_to_roll);
+ ROCKS_LOG_HEADER(
+ log, " Options.keep_log_file_num: %" ROCKSDB_PRIszt,
+ keep_log_file_num);
+ ROCKS_LOG_HEADER(
+ log, " Options.recycle_log_file_num: %" ROCKSDB_PRIszt,
+ recycle_log_file_num);
+ ROCKS_LOG_HEADER(log, " Options.allow_fallocate: %d",
+ allow_fallocate);
+ ROCKS_LOG_HEADER(log, " Options.allow_mmap_reads: %d",
+ allow_mmap_reads);
+ ROCKS_LOG_HEADER(log, " Options.allow_mmap_writes: %d",
+ allow_mmap_writes);
+ ROCKS_LOG_HEADER(log, " Options.use_direct_reads: %d",
+ use_direct_reads);
+ ROCKS_LOG_HEADER(log,
+ " "
+ "Options.use_direct_io_for_flush_and_compaction: %d",
+ use_direct_io_for_flush_and_compaction);
+ ROCKS_LOG_HEADER(log, " Options.create_missing_column_families: %d",
+ create_missing_column_families);
+ ROCKS_LOG_HEADER(log, " Options.db_log_dir: %s",
+ db_log_dir.c_str());
+ ROCKS_LOG_HEADER(log, " Options.wal_dir: %s",
+ wal_dir.c_str());
+ ROCKS_LOG_HEADER(log, " Options.table_cache_numshardbits: %d",
+ table_cache_numshardbits);
+ ROCKS_LOG_HEADER(log,
+ " Options.max_subcompactions: %" PRIu32,
+ max_subcompactions);
+ ROCKS_LOG_HEADER(log, " Options.max_background_flushes: %d",
+ max_background_flushes);
+ ROCKS_LOG_HEADER(log,
+ " Options.WAL_ttl_seconds: %" PRIu64,
+ wal_ttl_seconds);
+ ROCKS_LOG_HEADER(log,
+ " Options.WAL_size_limit_MB: %" PRIu64,
+ wal_size_limit_mb);
+ ROCKS_LOG_HEADER(log,
+ " "
+ "Options.max_write_batch_group_size_bytes: %" PRIu64,
+ max_write_batch_group_size_bytes);
+ ROCKS_LOG_HEADER(
+ log, " Options.manifest_preallocation_size: %" ROCKSDB_PRIszt,
+ manifest_preallocation_size);
+ ROCKS_LOG_HEADER(log, " Options.is_fd_close_on_exec: %d",
+ is_fd_close_on_exec);
+ ROCKS_LOG_HEADER(log, " Options.advise_random_on_open: %d",
+ advise_random_on_open);
+ ROCKS_LOG_HEADER(
+ log, " Options.db_write_buffer_size: %" ROCKSDB_PRIszt,
+ db_write_buffer_size);
+ ROCKS_LOG_HEADER(log, " Options.write_buffer_manager: %p",
+ write_buffer_manager.get());
+ ROCKS_LOG_HEADER(log, " Options.access_hint_on_compaction_start: %d",
+ static_cast<int>(access_hint_on_compaction_start));
+ ROCKS_LOG_HEADER(log, " Options.new_table_reader_for_compaction_inputs: %d",
+ new_table_reader_for_compaction_inputs);
+ ROCKS_LOG_HEADER(
+ log, " Options.random_access_max_buffer_size: %" ROCKSDB_PRIszt,
+ random_access_max_buffer_size);
+ ROCKS_LOG_HEADER(log, " Options.use_adaptive_mutex: %d",
+ use_adaptive_mutex);
+ ROCKS_LOG_HEADER(log, " Options.rate_limiter: %p",
+ rate_limiter.get());
+ Header(
+ log, " Options.sst_file_manager.rate_bytes_per_sec: %" PRIi64,
+ sst_file_manager ? sst_file_manager->GetDeleteRateBytesPerSecond() : 0);
+ ROCKS_LOG_HEADER(log, " Options.wal_recovery_mode: %d",
+ static_cast<int>(wal_recovery_mode));
+ ROCKS_LOG_HEADER(log, " Options.enable_thread_tracking: %d",
+ enable_thread_tracking);
+ ROCKS_LOG_HEADER(log, " Options.enable_pipelined_write: %d",
+ enable_pipelined_write);
+ ROCKS_LOG_HEADER(log, " Options.unordered_write: %d",
+ unordered_write);
+ ROCKS_LOG_HEADER(log, " Options.allow_concurrent_memtable_write: %d",
+ allow_concurrent_memtable_write);
+ ROCKS_LOG_HEADER(log, " Options.enable_write_thread_adaptive_yield: %d",
+ enable_write_thread_adaptive_yield);
+ ROCKS_LOG_HEADER(log,
+ " Options.write_thread_max_yield_usec: %" PRIu64,
+ write_thread_max_yield_usec);
+ ROCKS_LOG_HEADER(log,
+ " Options.write_thread_slow_yield_usec: %" PRIu64,
+ write_thread_slow_yield_usec);
+ if (row_cache) {
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.row_cache: %" ROCKSDB_PRIszt,
+ row_cache->GetCapacity());
+ } else {
+ ROCKS_LOG_HEADER(log,
+ " Options.row_cache: None");
+ }
+#ifndef ROCKSDB_LITE
+ ROCKS_LOG_HEADER(log, " Options.wal_filter: %s",
+ wal_filter ? wal_filter->Name() : "None");
+#endif // ROCKDB_LITE
+
+ ROCKS_LOG_HEADER(log, " Options.avoid_flush_during_recovery: %d",
+ avoid_flush_during_recovery);
+ ROCKS_LOG_HEADER(log, " Options.allow_ingest_behind: %d",
+ allow_ingest_behind);
+ ROCKS_LOG_HEADER(log, " Options.preserve_deletes: %d",
+ preserve_deletes);
+ ROCKS_LOG_HEADER(log, " Options.two_write_queues: %d",
+ two_write_queues);
+ ROCKS_LOG_HEADER(log, " Options.manual_wal_flush: %d",
+ manual_wal_flush);
+ ROCKS_LOG_HEADER(log, " Options.atomic_flush: %d", atomic_flush);
+ ROCKS_LOG_HEADER(log,
+ " Options.avoid_unnecessary_blocking_io: %d",
+ avoid_unnecessary_blocking_io);
+ ROCKS_LOG_HEADER(log, " Options.persist_stats_to_disk: %u",
+ persist_stats_to_disk);
+ ROCKS_LOG_HEADER(log, " Options.write_dbid_to_manifest: %d",
+ write_dbid_to_manifest);
+ ROCKS_LOG_HEADER(
+ log, " Options.log_readahead_size: %" ROCKSDB_PRIszt,
+ log_readahead_size);
+ ROCKS_LOG_HEADER(log, " Options.sst_file_checksum_func: %s",
+ sst_file_checksum_func
+ ? sst_file_checksum_func->Name()
+ : kUnknownFileChecksumFuncName.c_str());
+}
+
+MutableDBOptions::MutableDBOptions()
+ : max_background_jobs(2),
+ base_background_compactions(-1),
+ max_background_compactions(-1),
+ avoid_flush_during_shutdown(false),
+ writable_file_max_buffer_size(1024 * 1024),
+ delayed_write_rate(2 * 1024U * 1024U),
+ max_total_wal_size(0),
+ delete_obsolete_files_period_micros(6ULL * 60 * 60 * 1000000),
+ stats_dump_period_sec(600),
+ stats_persist_period_sec(600),
+ stats_history_buffer_size(1024 * 1024),
+ max_open_files(-1),
+ bytes_per_sync(0),
+ wal_bytes_per_sync(0),
+ strict_bytes_per_sync(false),
+ compaction_readahead_size(0) {}
+
+MutableDBOptions::MutableDBOptions(const DBOptions& options)
+ : max_background_jobs(options.max_background_jobs),
+ base_background_compactions(options.base_background_compactions),
+ max_background_compactions(options.max_background_compactions),
+ avoid_flush_during_shutdown(options.avoid_flush_during_shutdown),
+ writable_file_max_buffer_size(options.writable_file_max_buffer_size),
+ delayed_write_rate(options.delayed_write_rate),
+ max_total_wal_size(options.max_total_wal_size),
+ delete_obsolete_files_period_micros(
+ options.delete_obsolete_files_period_micros),
+ stats_dump_period_sec(options.stats_dump_period_sec),
+ stats_persist_period_sec(options.stats_persist_period_sec),
+ stats_history_buffer_size(options.stats_history_buffer_size),
+ max_open_files(options.max_open_files),
+ bytes_per_sync(options.bytes_per_sync),
+ wal_bytes_per_sync(options.wal_bytes_per_sync),
+ strict_bytes_per_sync(options.strict_bytes_per_sync),
+ compaction_readahead_size(options.compaction_readahead_size) {}
+
+void MutableDBOptions::Dump(Logger* log) const {
+ ROCKS_LOG_HEADER(log, " Options.max_background_jobs: %d",
+ max_background_jobs);
+ ROCKS_LOG_HEADER(log, " Options.max_background_compactions: %d",
+ max_background_compactions);
+ ROCKS_LOG_HEADER(log, " Options.avoid_flush_during_shutdown: %d",
+ avoid_flush_during_shutdown);
+ ROCKS_LOG_HEADER(
+ log, " Options.writable_file_max_buffer_size: %" ROCKSDB_PRIszt,
+ writable_file_max_buffer_size);
+ ROCKS_LOG_HEADER(log, " Options.delayed_write_rate : %" PRIu64,
+ delayed_write_rate);
+ ROCKS_LOG_HEADER(log, " Options.max_total_wal_size: %" PRIu64,
+ max_total_wal_size);
+ ROCKS_LOG_HEADER(
+ log, " Options.delete_obsolete_files_period_micros: %" PRIu64,
+ delete_obsolete_files_period_micros);
+ ROCKS_LOG_HEADER(log, " Options.stats_dump_period_sec: %u",
+ stats_dump_period_sec);
+ ROCKS_LOG_HEADER(log, " Options.stats_persist_period_sec: %d",
+ stats_persist_period_sec);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.stats_history_buffer_size: %" ROCKSDB_PRIszt,
+ stats_history_buffer_size);
+ ROCKS_LOG_HEADER(log, " Options.max_open_files: %d",
+ max_open_files);
+ ROCKS_LOG_HEADER(log,
+ " Options.bytes_per_sync: %" PRIu64,
+ bytes_per_sync);
+ ROCKS_LOG_HEADER(log,
+ " Options.wal_bytes_per_sync: %" PRIu64,
+ wal_bytes_per_sync);
+ ROCKS_LOG_HEADER(log,
+ " Options.strict_bytes_per_sync: %d",
+ strict_bytes_per_sync);
+ ROCKS_LOG_HEADER(log,
+ " Options.compaction_readahead_size: %" ROCKSDB_PRIszt,
+ compaction_readahead_size);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/db_options.h b/storage/rocksdb/rocksdb/options/db_options.h
new file mode 100644
index 00000000..cba298a2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/db_options.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct ImmutableDBOptions {
+ ImmutableDBOptions();
+ explicit ImmutableDBOptions(const DBOptions& options);
+
+ void Dump(Logger* log) const;
+
+ bool create_if_missing;
+ bool create_missing_column_families;
+ bool error_if_exists;
+ bool paranoid_checks;
+ Env* env;
+ std::shared_ptr<FileSystem> fs;
+ std::shared_ptr<RateLimiter> rate_limiter;
+ std::shared_ptr<SstFileManager> sst_file_manager;
+ std::shared_ptr<Logger> info_log;
+ InfoLogLevel info_log_level;
+ int max_file_opening_threads;
+ std::shared_ptr<Statistics> statistics;
+ bool use_fsync;
+ std::vector<DbPath> db_paths;
+ std::string db_log_dir;
+ std::string wal_dir;
+ uint32_t max_subcompactions;
+ int max_background_flushes;
+ size_t max_log_file_size;
+ size_t log_file_time_to_roll;
+ size_t keep_log_file_num;
+ size_t recycle_log_file_num;
+ uint64_t max_manifest_file_size;
+ int table_cache_numshardbits;
+ uint64_t wal_ttl_seconds;
+ uint64_t wal_size_limit_mb;
+ uint64_t max_write_batch_group_size_bytes;
+ size_t manifest_preallocation_size;
+ bool allow_mmap_reads;
+ bool allow_mmap_writes;
+ bool use_direct_reads;
+ bool use_direct_io_for_flush_and_compaction;
+ bool allow_fallocate;
+ bool is_fd_close_on_exec;
+ bool advise_random_on_open;
+ size_t db_write_buffer_size;
+ std::shared_ptr<WriteBufferManager> write_buffer_manager;
+ DBOptions::AccessHint access_hint_on_compaction_start;
+ bool new_table_reader_for_compaction_inputs;
+ size_t random_access_max_buffer_size;
+ bool use_adaptive_mutex;
+ std::vector<std::shared_ptr<EventListener>> listeners;
+ bool enable_thread_tracking;
+ bool enable_pipelined_write;
+ bool unordered_write;
+ bool allow_concurrent_memtable_write;
+ bool enable_write_thread_adaptive_yield;
+ uint64_t write_thread_max_yield_usec;
+ uint64_t write_thread_slow_yield_usec;
+ bool skip_stats_update_on_db_open;
+ bool skip_checking_sst_file_sizes_on_db_open;
+ WALRecoveryMode wal_recovery_mode;
+ bool allow_2pc;
+ std::shared_ptr<Cache> row_cache;
+#ifndef ROCKSDB_LITE
+ WalFilter* wal_filter;
+#endif // ROCKSDB_LITE
+ bool fail_if_options_file_error;
+ bool dump_malloc_stats;
+ bool avoid_flush_during_recovery;
+ bool allow_ingest_behind;
+ bool preserve_deletes;
+ bool two_write_queues;
+ bool manual_wal_flush;
+ bool atomic_flush;
+ bool avoid_unnecessary_blocking_io;
+ bool persist_stats_to_disk;
+ bool write_dbid_to_manifest;
+ size_t log_readahead_size;
+ std::shared_ptr<FileChecksumFunc> sst_file_checksum_func;
+};
+
+struct MutableDBOptions {
+ MutableDBOptions();
+ explicit MutableDBOptions(const MutableDBOptions& options) = default;
+ explicit MutableDBOptions(const DBOptions& options);
+
+ void Dump(Logger* log) const;
+
+ int max_background_jobs;
+ int base_background_compactions;
+ int max_background_compactions;
+ bool avoid_flush_during_shutdown;
+ size_t writable_file_max_buffer_size;
+ uint64_t delayed_write_rate;
+ uint64_t max_total_wal_size;
+ uint64_t delete_obsolete_files_period_micros;
+ unsigned int stats_dump_period_sec;
+ unsigned int stats_persist_period_sec;
+ size_t stats_history_buffer_size;
+ int max_open_files;
+ uint64_t bytes_per_sync;
+ uint64_t wal_bytes_per_sync;
+ bool strict_bytes_per_sync;
+ size_t compaction_readahead_size;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/options.cc b/storage/rocksdb/rocksdb/options/options.cc
new file mode 100644
index 00000000..fe676597
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options.cc
@@ -0,0 +1,623 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+#include <limits>
+
+#include "monitoring/statistics.h"
+#include "options/db_options.h"
+#include "options/options_helper.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/sst_file_manager.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/wal_filter.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "util/compression.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions() {
+ assert(memtable_factory.get() != nullptr);
+}
+
+AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
+ : max_write_buffer_number(options.max_write_buffer_number),
+ min_write_buffer_number_to_merge(
+ options.min_write_buffer_number_to_merge),
+ max_write_buffer_number_to_maintain(
+ options.max_write_buffer_number_to_maintain),
+ max_write_buffer_size_to_maintain(
+ options.max_write_buffer_size_to_maintain),
+ inplace_update_support(options.inplace_update_support),
+ inplace_update_num_locks(options.inplace_update_num_locks),
+ inplace_callback(options.inplace_callback),
+ memtable_prefix_bloom_size_ratio(
+ options.memtable_prefix_bloom_size_ratio),
+ memtable_whole_key_filtering(options.memtable_whole_key_filtering),
+ memtable_huge_page_size(options.memtable_huge_page_size),
+ memtable_insert_with_hint_prefix_extractor(
+ options.memtable_insert_with_hint_prefix_extractor),
+ bloom_locality(options.bloom_locality),
+ arena_block_size(options.arena_block_size),
+ compression_per_level(options.compression_per_level),
+ num_levels(options.num_levels),
+ level0_slowdown_writes_trigger(options.level0_slowdown_writes_trigger),
+ level0_stop_writes_trigger(options.level0_stop_writes_trigger),
+ target_file_size_base(options.target_file_size_base),
+ target_file_size_multiplier(options.target_file_size_multiplier),
+ level_compaction_dynamic_level_bytes(
+ options.level_compaction_dynamic_level_bytes),
+ max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
+ max_bytes_for_level_multiplier_additional(
+ options.max_bytes_for_level_multiplier_additional),
+ max_compaction_bytes(options.max_compaction_bytes),
+ soft_pending_compaction_bytes_limit(
+ options.soft_pending_compaction_bytes_limit),
+ hard_pending_compaction_bytes_limit(
+ options.hard_pending_compaction_bytes_limit),
+ compaction_style(options.compaction_style),
+ compaction_pri(options.compaction_pri),
+ compaction_options_universal(options.compaction_options_universal),
+ compaction_options_fifo(options.compaction_options_fifo),
+ max_sequential_skip_in_iterations(
+ options.max_sequential_skip_in_iterations),
+ memtable_factory(options.memtable_factory),
+ table_properties_collector_factories(
+ options.table_properties_collector_factories),
+ max_successive_merges(options.max_successive_merges),
+ optimize_filters_for_hits(options.optimize_filters_for_hits),
+ paranoid_file_checks(options.paranoid_file_checks),
+ force_consistency_checks(options.force_consistency_checks),
+ report_bg_io_stats(options.report_bg_io_stats),
+ ttl(options.ttl),
+ periodic_compaction_seconds(options.periodic_compaction_seconds),
+ sample_for_compression(options.sample_for_compression) {
+ assert(memtable_factory.get() != nullptr);
+ if (max_bytes_for_level_multiplier_additional.size() <
+ static_cast<unsigned int>(num_levels)) {
+ max_bytes_for_level_multiplier_additional.resize(num_levels, 1);
+ }
+}
+
+ColumnFamilyOptions::ColumnFamilyOptions()
+ : compression(Snappy_Supported() ? kSnappyCompression : kNoCompression),
+ table_factory(
+ std::shared_ptr<TableFactory>(new BlockBasedTableFactory())) {}
+
+ColumnFamilyOptions::ColumnFamilyOptions(const Options& options)
+ : ColumnFamilyOptions(*static_cast<const ColumnFamilyOptions*>(&options)) {}
+
+DBOptions::DBOptions() {}
+DBOptions::DBOptions(const Options& options)
+ : DBOptions(*static_cast<const DBOptions*>(&options)) {}
+
+void DBOptions::Dump(Logger* log) const {
+ ImmutableDBOptions(*this).Dump(log);
+ MutableDBOptions(*this).Dump(log);
+} // DBOptions::Dump
+
+void ColumnFamilyOptions::Dump(Logger* log) const {
+ ROCKS_LOG_HEADER(log, " Options.comparator: %s",
+ comparator->Name());
+ ROCKS_LOG_HEADER(log, " Options.merge_operator: %s",
+ merge_operator ? merge_operator->Name() : "None");
+ ROCKS_LOG_HEADER(log, " Options.compaction_filter: %s",
+ compaction_filter ? compaction_filter->Name() : "None");
+ ROCKS_LOG_HEADER(
+ log, " Options.compaction_filter_factory: %s",
+ compaction_filter_factory ? compaction_filter_factory->Name() : "None");
+ ROCKS_LOG_HEADER(log, " Options.memtable_factory: %s",
+ memtable_factory->Name());
+ ROCKS_LOG_HEADER(log, " Options.table_factory: %s",
+ table_factory->Name());
+ ROCKS_LOG_HEADER(log, " table_factory options: %s",
+ table_factory->GetPrintableTableOptions().c_str());
+ ROCKS_LOG_HEADER(log, " Options.write_buffer_size: %" ROCKSDB_PRIszt,
+ write_buffer_size);
+ ROCKS_LOG_HEADER(log, " Options.max_write_buffer_number: %d",
+ max_write_buffer_number);
+ if (!compression_per_level.empty()) {
+ for (unsigned int i = 0; i < compression_per_level.size(); i++) {
+ ROCKS_LOG_HEADER(
+ log, " Options.compression[%d]: %s", i,
+ CompressionTypeToString(compression_per_level[i]).c_str());
+ }
+ } else {
+ ROCKS_LOG_HEADER(log, " Options.compression: %s",
+ CompressionTypeToString(compression).c_str());
+ }
+ ROCKS_LOG_HEADER(
+ log, " Options.bottommost_compression: %s",
+ bottommost_compression == kDisableCompressionOption
+ ? "Disabled"
+ : CompressionTypeToString(bottommost_compression).c_str());
+ ROCKS_LOG_HEADER(
+ log, " Options.prefix_extractor: %s",
+ prefix_extractor == nullptr ? "nullptr" : prefix_extractor->Name());
+ ROCKS_LOG_HEADER(log,
+ " Options.memtable_insert_with_hint_prefix_extractor: %s",
+ memtable_insert_with_hint_prefix_extractor == nullptr
+ ? "nullptr"
+ : memtable_insert_with_hint_prefix_extractor->Name());
+ ROCKS_LOG_HEADER(log, " Options.num_levels: %d", num_levels);
+ ROCKS_LOG_HEADER(log, " Options.min_write_buffer_number_to_merge: %d",
+ min_write_buffer_number_to_merge);
+ ROCKS_LOG_HEADER(log, " Options.max_write_buffer_number_to_maintain: %d",
+ max_write_buffer_number_to_maintain);
+ ROCKS_LOG_HEADER(log,
+ " Options.max_write_buffer_size_to_maintain: %" PRIu64,
+ max_write_buffer_size_to_maintain);
+ ROCKS_LOG_HEADER(
+ log, " Options.bottommost_compression_opts.window_bits: %d",
+ bottommost_compression_opts.window_bits);
+ ROCKS_LOG_HEADER(
+ log, " Options.bottommost_compression_opts.level: %d",
+ bottommost_compression_opts.level);
+ ROCKS_LOG_HEADER(
+ log, " Options.bottommost_compression_opts.strategy: %d",
+ bottommost_compression_opts.strategy);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.bottommost_compression_opts.max_dict_bytes: "
+ "%" PRIu32,
+ bottommost_compression_opts.max_dict_bytes);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.bottommost_compression_opts.zstd_max_train_bytes: "
+ "%" PRIu32,
+ bottommost_compression_opts.zstd_max_train_bytes);
+ ROCKS_LOG_HEADER(
+ log, " Options.bottommost_compression_opts.enabled: %s",
+ bottommost_compression_opts.enabled ? "true" : "false");
+ ROCKS_LOG_HEADER(log, " Options.compression_opts.window_bits: %d",
+ compression_opts.window_bits);
+ ROCKS_LOG_HEADER(log, " Options.compression_opts.level: %d",
+ compression_opts.level);
+ ROCKS_LOG_HEADER(log, " Options.compression_opts.strategy: %d",
+ compression_opts.strategy);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.compression_opts.max_dict_bytes: %" PRIu32,
+ compression_opts.max_dict_bytes);
+ ROCKS_LOG_HEADER(log,
+ " Options.compression_opts.zstd_max_train_bytes: "
+ "%" PRIu32,
+ compression_opts.zstd_max_train_bytes);
+ ROCKS_LOG_HEADER(log,
+ " Options.compression_opts.enabled: %s",
+ compression_opts.enabled ? "true" : "false");
+ ROCKS_LOG_HEADER(log, " Options.level0_file_num_compaction_trigger: %d",
+ level0_file_num_compaction_trigger);
+ ROCKS_LOG_HEADER(log, " Options.level0_slowdown_writes_trigger: %d",
+ level0_slowdown_writes_trigger);
+ ROCKS_LOG_HEADER(log, " Options.level0_stop_writes_trigger: %d",
+ level0_stop_writes_trigger);
+ ROCKS_LOG_HEADER(
+ log, " Options.target_file_size_base: %" PRIu64,
+ target_file_size_base);
+ ROCKS_LOG_HEADER(log, " Options.target_file_size_multiplier: %d",
+ target_file_size_multiplier);
+ ROCKS_LOG_HEADER(
+ log, " Options.max_bytes_for_level_base: %" PRIu64,
+ max_bytes_for_level_base);
+ ROCKS_LOG_HEADER(log, "Options.level_compaction_dynamic_level_bytes: %d",
+ level_compaction_dynamic_level_bytes);
+ ROCKS_LOG_HEADER(log, " Options.max_bytes_for_level_multiplier: %f",
+ max_bytes_for_level_multiplier);
+ for (size_t i = 0; i < max_bytes_for_level_multiplier_additional.size();
+ i++) {
+ ROCKS_LOG_HEADER(
+ log, "Options.max_bytes_for_level_multiplier_addtl[%" ROCKSDB_PRIszt
+ "]: %d",
+ i, max_bytes_for_level_multiplier_additional[i]);
+ }
+ ROCKS_LOG_HEADER(
+ log, " Options.max_sequential_skip_in_iterations: %" PRIu64,
+ max_sequential_skip_in_iterations);
+ ROCKS_LOG_HEADER(
+ log, " Options.max_compaction_bytes: %" PRIu64,
+ max_compaction_bytes);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.arena_block_size: %" ROCKSDB_PRIszt,
+ arena_block_size);
+ ROCKS_LOG_HEADER(log,
+ " Options.soft_pending_compaction_bytes_limit: %" PRIu64,
+ soft_pending_compaction_bytes_limit);
+ ROCKS_LOG_HEADER(log,
+ " Options.hard_pending_compaction_bytes_limit: %" PRIu64,
+ hard_pending_compaction_bytes_limit);
+ ROCKS_LOG_HEADER(log, " Options.rate_limit_delay_max_milliseconds: %u",
+ rate_limit_delay_max_milliseconds);
+ ROCKS_LOG_HEADER(log, " Options.disable_auto_compactions: %d",
+ disable_auto_compactions);
+
+ const auto& it_compaction_style =
+ compaction_style_to_string.find(compaction_style);
+ std::string str_compaction_style;
+ if (it_compaction_style == compaction_style_to_string.end()) {
+ assert(false);
+ str_compaction_style = "unknown_" + std::to_string(compaction_style);
+ } else {
+ str_compaction_style = it_compaction_style->second;
+ }
+ ROCKS_LOG_HEADER(log,
+ " Options.compaction_style: %s",
+ str_compaction_style.c_str());
+
+ const auto& it_compaction_pri =
+ compaction_pri_to_string.find(compaction_pri);
+ std::string str_compaction_pri;
+ if (it_compaction_pri == compaction_pri_to_string.end()) {
+ assert(false);
+ str_compaction_pri = "unknown_" + std::to_string(compaction_pri);
+ } else {
+ str_compaction_pri = it_compaction_pri->second;
+ }
+ ROCKS_LOG_HEADER(log,
+ " Options.compaction_pri: %s",
+ str_compaction_pri.c_str());
+ ROCKS_LOG_HEADER(log,
+ "Options.compaction_options_universal.size_ratio: %u",
+ compaction_options_universal.size_ratio);
+ ROCKS_LOG_HEADER(log,
+ "Options.compaction_options_universal.min_merge_width: %u",
+ compaction_options_universal.min_merge_width);
+ ROCKS_LOG_HEADER(log,
+ "Options.compaction_options_universal.max_merge_width: %u",
+ compaction_options_universal.max_merge_width);
+ ROCKS_LOG_HEADER(
+ log,
+ "Options.compaction_options_universal."
+ "max_size_amplification_percent: %u",
+ compaction_options_universal.max_size_amplification_percent);
+ ROCKS_LOG_HEADER(
+ log,
+ "Options.compaction_options_universal.compression_size_percent: %d",
+ compaction_options_universal.compression_size_percent);
+ const auto& it_compaction_stop_style = compaction_stop_style_to_string.find(
+ compaction_options_universal.stop_style);
+ std::string str_compaction_stop_style;
+ if (it_compaction_stop_style == compaction_stop_style_to_string.end()) {
+ assert(false);
+ str_compaction_stop_style =
+ "unknown_" + std::to_string(compaction_options_universal.stop_style);
+ } else {
+ str_compaction_stop_style = it_compaction_stop_style->second;
+ }
+ ROCKS_LOG_HEADER(log,
+ "Options.compaction_options_universal.stop_style: %s",
+ str_compaction_stop_style.c_str());
+ ROCKS_LOG_HEADER(
+ log, "Options.compaction_options_fifo.max_table_files_size: %" PRIu64,
+ compaction_options_fifo.max_table_files_size);
+ ROCKS_LOG_HEADER(log,
+ "Options.compaction_options_fifo.allow_compaction: %d",
+ compaction_options_fifo.allow_compaction);
+ std::string collector_names;
+ for (const auto& collector_factory : table_properties_collector_factories) {
+ collector_names.append(collector_factory->Name());
+ collector_names.append("; ");
+ }
+ ROCKS_LOG_HEADER(
+ log, " Options.table_properties_collectors: %s",
+ collector_names.c_str());
+ ROCKS_LOG_HEADER(log,
+ " Options.inplace_update_support: %d",
+ inplace_update_support);
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.inplace_update_num_locks: %" ROCKSDB_PRIszt,
+ inplace_update_num_locks);
+ // TODO: easier config for bloom (maybe based on avg key/value size)
+ ROCKS_LOG_HEADER(
+ log, " Options.memtable_prefix_bloom_size_ratio: %f",
+ memtable_prefix_bloom_size_ratio);
+ ROCKS_LOG_HEADER(log,
+ " Options.memtable_whole_key_filtering: %d",
+ memtable_whole_key_filtering);
+
+ ROCKS_LOG_HEADER(log, " Options.memtable_huge_page_size: %" ROCKSDB_PRIszt,
+ memtable_huge_page_size);
+ ROCKS_LOG_HEADER(log,
+ " Options.bloom_locality: %d",
+ bloom_locality);
+
+ ROCKS_LOG_HEADER(
+ log,
+ " Options.max_successive_merges: %" ROCKSDB_PRIszt,
+ max_successive_merges);
+ ROCKS_LOG_HEADER(log,
+ " Options.optimize_filters_for_hits: %d",
+ optimize_filters_for_hits);
+ ROCKS_LOG_HEADER(log, " Options.paranoid_file_checks: %d",
+ paranoid_file_checks);
+ ROCKS_LOG_HEADER(log, " Options.force_consistency_checks: %d",
+ force_consistency_checks);
+ ROCKS_LOG_HEADER(log, " Options.report_bg_io_stats: %d",
+ report_bg_io_stats);
+ ROCKS_LOG_HEADER(log, " Options.ttl: %" PRIu64,
+ ttl);
+ ROCKS_LOG_HEADER(log,
+ " Options.periodic_compaction_seconds: %" PRIu64,
+ periodic_compaction_seconds);
+} // ColumnFamilyOptions::Dump
+
+void Options::Dump(Logger* log) const {
+ DBOptions::Dump(log);
+ ColumnFamilyOptions::Dump(log);
+} // Options::Dump
+
+void Options::DumpCFOptions(Logger* log) const {
+ ColumnFamilyOptions::Dump(log);
+} // Options::DumpCFOptions
+
+//
+// The goal of this method is to create a configuration that
+// allows an application to write all files into L0 and
+// then do a single compaction to output all files into L1.
+Options*
+Options::PrepareForBulkLoad()
+{
+ // never slowdown ingest.
+ level0_file_num_compaction_trigger = (1<<30);
+ level0_slowdown_writes_trigger = (1<<30);
+ level0_stop_writes_trigger = (1<<30);
+ soft_pending_compaction_bytes_limit = 0;
+ hard_pending_compaction_bytes_limit = 0;
+
+ // no auto compactions please. The application should issue a
+ // manual compaction after all data is loaded into L0.
+ disable_auto_compactions = true;
+ // A manual compaction run should pick all files in L0 in
+ // a single compaction run.
+ max_compaction_bytes = (static_cast<uint64_t>(1) << 60);
+
+ // It is better to have only 2 levels, otherwise a manual
+ // compaction would compact at every possible level, thereby
+ // increasing the total time needed for compactions.
+ num_levels = 2;
+
+ // Need to allow more write buffers to allow more parallism
+ // of flushes.
+ max_write_buffer_number = 6;
+ min_write_buffer_number_to_merge = 1;
+
+ // When compaction is disabled, more parallel flush threads can
+ // help with write throughput.
+ max_background_flushes = 4;
+
+ // Prevent a memtable flush to automatically promote files
+ // to L1. This is helpful so that all files that are
+ // input to the manual compaction are all at L0.
+ max_background_compactions = 2;
+
+ // The compaction would create large files in L1.
+ target_file_size_base = 256 * 1024 * 1024;
+ return this;
+}
+
+Options* Options::OptimizeForSmallDb() {
+ // 16MB block cache
+ std::shared_ptr<Cache> cache = NewLRUCache(16 << 20);
+
+ ColumnFamilyOptions::OptimizeForSmallDb(&cache);
+ DBOptions::OptimizeForSmallDb(&cache);
+ return this;
+}
+
+Options* Options::OldDefaults(int rocksdb_major_version,
+ int rocksdb_minor_version) {
+ ColumnFamilyOptions::OldDefaults(rocksdb_major_version,
+ rocksdb_minor_version);
+ DBOptions::OldDefaults(rocksdb_major_version, rocksdb_minor_version);
+ return this;
+}
+
+DBOptions* DBOptions::OldDefaults(int rocksdb_major_version,
+ int rocksdb_minor_version) {
+ if (rocksdb_major_version < 4 ||
+ (rocksdb_major_version == 4 && rocksdb_minor_version < 7)) {
+ max_file_opening_threads = 1;
+ table_cache_numshardbits = 4;
+ }
+ if (rocksdb_major_version < 5 ||
+ (rocksdb_major_version == 5 && rocksdb_minor_version < 2)) {
+ delayed_write_rate = 2 * 1024U * 1024U;
+ } else if (rocksdb_major_version < 5 ||
+ (rocksdb_major_version == 5 && rocksdb_minor_version < 6)) {
+ delayed_write_rate = 16 * 1024U * 1024U;
+ }
+ max_open_files = 5000;
+ wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
+ return this;
+}
+
+ColumnFamilyOptions* ColumnFamilyOptions::OldDefaults(
+ int rocksdb_major_version, int rocksdb_minor_version) {
+ if (rocksdb_major_version < 5 ||
+ (rocksdb_major_version == 5 && rocksdb_minor_version <= 18)) {
+ compaction_pri = CompactionPri::kByCompensatedSize;
+ }
+ if (rocksdb_major_version < 4 ||
+ (rocksdb_major_version == 4 && rocksdb_minor_version < 7)) {
+ write_buffer_size = 4 << 20;
+ target_file_size_base = 2 * 1048576;
+ max_bytes_for_level_base = 10 * 1048576;
+ soft_pending_compaction_bytes_limit = 0;
+ hard_pending_compaction_bytes_limit = 0;
+ }
+ if (rocksdb_major_version < 5) {
+ level0_stop_writes_trigger = 24;
+ } else if (rocksdb_major_version == 5 && rocksdb_minor_version < 2) {
+ level0_stop_writes_trigger = 30;
+ }
+
+ return this;
+}
+
+// Optimization functions
+DBOptions* DBOptions::OptimizeForSmallDb(std::shared_ptr<Cache>* cache) {
+ max_file_opening_threads = 1;
+ max_open_files = 5000;
+
+ // Cost memtable to block cache too.
+ std::shared_ptr<ROCKSDB_NAMESPACE::WriteBufferManager> wbm =
+ std::make_shared<ROCKSDB_NAMESPACE::WriteBufferManager>(
+ 0, (cache != nullptr) ? *cache : std::shared_ptr<Cache>());
+ write_buffer_manager = wbm;
+
+ return this;
+}
+
+ColumnFamilyOptions* ColumnFamilyOptions::OptimizeForSmallDb(
+ std::shared_ptr<Cache>* cache) {
+ write_buffer_size = 2 << 20;
+ target_file_size_base = 2 * 1048576;
+ max_bytes_for_level_base = 10 * 1048576;
+ soft_pending_compaction_bytes_limit = 256 * 1048576;
+ hard_pending_compaction_bytes_limit = 1073741824ul;
+
+ BlockBasedTableOptions table_options;
+ table_options.block_cache =
+ (cache != nullptr) ? *cache : std::shared_ptr<Cache>();
+ table_options.cache_index_and_filter_blocks = true;
+ // Two level iterator to avoid LRU cache imbalance
+ table_options.index_type =
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
+ table_factory.reset(new BlockBasedTableFactory(table_options));
+
+ return this;
+}
+
+#ifndef ROCKSDB_LITE
+ColumnFamilyOptions* ColumnFamilyOptions::OptimizeForPointLookup(
+ uint64_t block_cache_size_mb) {
+ BlockBasedTableOptions block_based_options;
+ block_based_options.data_block_index_type =
+ BlockBasedTableOptions::kDataBlockBinaryAndHash;
+ block_based_options.data_block_hash_table_util_ratio = 0.75;
+ block_based_options.filter_policy.reset(NewBloomFilterPolicy(10));
+ block_based_options.block_cache =
+ NewLRUCache(static_cast<size_t>(block_cache_size_mb * 1024 * 1024));
+ table_factory.reset(new BlockBasedTableFactory(block_based_options));
+ memtable_prefix_bloom_size_ratio = 0.02;
+ memtable_whole_key_filtering = true;
+ return this;
+}
+
+ColumnFamilyOptions* ColumnFamilyOptions::OptimizeLevelStyleCompaction(
+ uint64_t memtable_memory_budget) {
+ write_buffer_size = static_cast<size_t>(memtable_memory_budget / 4);
+ // merge two memtables when flushing to L0
+ min_write_buffer_number_to_merge = 2;
+ // this means we'll use 50% extra memory in the worst case, but will reduce
+ // write stalls.
+ max_write_buffer_number = 6;
+ // start flushing L0->L1 as soon as possible. each file on level0 is
+ // (memtable_memory_budget / 2). This will flush level 0 when it's bigger than
+ // memtable_memory_budget.
+ level0_file_num_compaction_trigger = 2;
+ // doesn't really matter much, but we don't want to create too many files
+ target_file_size_base = memtable_memory_budget / 8;
+ // make Level1 size equal to Level0 size, so that L0->L1 compactions are fast
+ max_bytes_for_level_base = memtable_memory_budget;
+
+ // level style compaction
+ compaction_style = kCompactionStyleLevel;
+
+ // only compress levels >= 2
+ compression_per_level.resize(num_levels);
+ for (int i = 0; i < num_levels; ++i) {
+ if (i < 2) {
+ compression_per_level[i] = kNoCompression;
+ } else {
+ compression_per_level[i] =
+ LZ4_Supported()
+ ? kLZ4Compression
+ : (Snappy_Supported() ? kSnappyCompression : kNoCompression);
+ }
+ }
+ return this;
+}
+
+ColumnFamilyOptions* ColumnFamilyOptions::OptimizeUniversalStyleCompaction(
+ uint64_t memtable_memory_budget) {
+ write_buffer_size = static_cast<size_t>(memtable_memory_budget / 4);
+ // merge two memtables when flushing to L0
+ min_write_buffer_number_to_merge = 2;
+ // this means we'll use 50% extra memory in the worst case, but will reduce
+ // write stalls.
+ max_write_buffer_number = 6;
+ // universal style compaction
+ compaction_style = kCompactionStyleUniversal;
+ compaction_options_universal.compression_size_percent = 80;
+ return this;
+}
+
+DBOptions* DBOptions::IncreaseParallelism(int total_threads) {
+ max_background_jobs = total_threads;
+ env->SetBackgroundThreads(total_threads, Env::LOW);
+ env->SetBackgroundThreads(1, Env::HIGH);
+ return this;
+}
+
+#endif // !ROCKSDB_LITE
+
+ReadOptions::ReadOptions()
+ : snapshot(nullptr),
+ iterate_lower_bound(nullptr),
+ iterate_upper_bound(nullptr),
+ readahead_size(0),
+ max_skippable_internal_keys(0),
+ read_tier(kReadAllTier),
+ verify_checksums(true),
+ fill_cache(true),
+ tailing(false),
+ managed(false),
+ total_order_seek(false),
+ auto_prefix_mode(false),
+ prefix_same_as_start(false),
+ pin_data(false),
+ background_purge_on_iterator_cleanup(false),
+ ignore_range_deletions(false),
+ iter_start_seqnum(0),
+ timestamp(nullptr) {}
+
+ReadOptions::ReadOptions(bool cksum, bool cache)
+ : snapshot(nullptr),
+ iterate_lower_bound(nullptr),
+ iterate_upper_bound(nullptr),
+ readahead_size(0),
+ max_skippable_internal_keys(0),
+ read_tier(kReadAllTier),
+ verify_checksums(cksum),
+ fill_cache(cache),
+ tailing(false),
+ managed(false),
+ total_order_seek(false),
+ auto_prefix_mode(false),
+ prefix_same_as_start(false),
+ pin_data(false),
+ background_purge_on_iterator_cleanup(false),
+ ignore_range_deletions(false),
+ iter_start_seqnum(0),
+ timestamp(nullptr) {}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/options_helper.cc b/storage/rocksdb/rocksdb/options/options_helper.cc
new file mode 100644
index 00000000..bc36dbcf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_helper.cc
@@ -0,0 +1,2124 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "options/options_helper.h"
+
+#include <cassert>
+#include <cctype>
+#include <cstdlib>
+#include <unordered_set>
+#include <vector>
+
+#include "rocksdb/cache.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/object_registry.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/plain/plain_table_factory.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
+ const MutableDBOptions& mutable_db_options) {
+ DBOptions options;
+
+ options.create_if_missing = immutable_db_options.create_if_missing;
+ options.create_missing_column_families =
+ immutable_db_options.create_missing_column_families;
+ options.error_if_exists = immutable_db_options.error_if_exists;
+ options.paranoid_checks = immutable_db_options.paranoid_checks;
+ options.env = immutable_db_options.env;
+ options.file_system = immutable_db_options.fs;
+ options.rate_limiter = immutable_db_options.rate_limiter;
+ options.sst_file_manager = immutable_db_options.sst_file_manager;
+ options.info_log = immutable_db_options.info_log;
+ options.info_log_level = immutable_db_options.info_log_level;
+ options.max_open_files = mutable_db_options.max_open_files;
+ options.max_file_opening_threads =
+ immutable_db_options.max_file_opening_threads;
+ options.max_total_wal_size = mutable_db_options.max_total_wal_size;
+ options.statistics = immutable_db_options.statistics;
+ options.use_fsync = immutable_db_options.use_fsync;
+ options.db_paths = immutable_db_options.db_paths;
+ options.db_log_dir = immutable_db_options.db_log_dir;
+ options.wal_dir = immutable_db_options.wal_dir;
+ options.delete_obsolete_files_period_micros =
+ mutable_db_options.delete_obsolete_files_period_micros;
+ options.max_background_jobs = mutable_db_options.max_background_jobs;
+ options.base_background_compactions =
+ mutable_db_options.base_background_compactions;
+ options.max_background_compactions =
+ mutable_db_options.max_background_compactions;
+ options.bytes_per_sync = mutable_db_options.bytes_per_sync;
+ options.wal_bytes_per_sync = mutable_db_options.wal_bytes_per_sync;
+ options.strict_bytes_per_sync = mutable_db_options.strict_bytes_per_sync;
+ options.max_subcompactions = immutable_db_options.max_subcompactions;
+ options.max_background_flushes = immutable_db_options.max_background_flushes;
+ options.max_log_file_size = immutable_db_options.max_log_file_size;
+ options.log_file_time_to_roll = immutable_db_options.log_file_time_to_roll;
+ options.keep_log_file_num = immutable_db_options.keep_log_file_num;
+ options.recycle_log_file_num = immutable_db_options.recycle_log_file_num;
+ options.max_manifest_file_size = immutable_db_options.max_manifest_file_size;
+ options.table_cache_numshardbits =
+ immutable_db_options.table_cache_numshardbits;
+ options.WAL_ttl_seconds = immutable_db_options.wal_ttl_seconds;
+ options.WAL_size_limit_MB = immutable_db_options.wal_size_limit_mb;
+ options.manifest_preallocation_size =
+ immutable_db_options.manifest_preallocation_size;
+ options.allow_mmap_reads = immutable_db_options.allow_mmap_reads;
+ options.allow_mmap_writes = immutable_db_options.allow_mmap_writes;
+ options.use_direct_reads = immutable_db_options.use_direct_reads;
+ options.use_direct_io_for_flush_and_compaction =
+ immutable_db_options.use_direct_io_for_flush_and_compaction;
+ options.allow_fallocate = immutable_db_options.allow_fallocate;
+ options.is_fd_close_on_exec = immutable_db_options.is_fd_close_on_exec;
+ options.stats_dump_period_sec = mutable_db_options.stats_dump_period_sec;
+ options.stats_persist_period_sec =
+ mutable_db_options.stats_persist_period_sec;
+ options.persist_stats_to_disk = immutable_db_options.persist_stats_to_disk;
+ options.stats_history_buffer_size =
+ mutable_db_options.stats_history_buffer_size;
+ options.advise_random_on_open = immutable_db_options.advise_random_on_open;
+ options.db_write_buffer_size = immutable_db_options.db_write_buffer_size;
+ options.write_buffer_manager = immutable_db_options.write_buffer_manager;
+ options.access_hint_on_compaction_start =
+ immutable_db_options.access_hint_on_compaction_start;
+ options.new_table_reader_for_compaction_inputs =
+ immutable_db_options.new_table_reader_for_compaction_inputs;
+ options.compaction_readahead_size =
+ mutable_db_options.compaction_readahead_size;
+ options.random_access_max_buffer_size =
+ immutable_db_options.random_access_max_buffer_size;
+ options.writable_file_max_buffer_size =
+ mutable_db_options.writable_file_max_buffer_size;
+ options.use_adaptive_mutex = immutable_db_options.use_adaptive_mutex;
+ options.listeners = immutable_db_options.listeners;
+ options.enable_thread_tracking = immutable_db_options.enable_thread_tracking;
+ options.delayed_write_rate = mutable_db_options.delayed_write_rate;
+ options.enable_pipelined_write = immutable_db_options.enable_pipelined_write;
+ options.unordered_write = immutable_db_options.unordered_write;
+ options.allow_concurrent_memtable_write =
+ immutable_db_options.allow_concurrent_memtable_write;
+ options.enable_write_thread_adaptive_yield =
+ immutable_db_options.enable_write_thread_adaptive_yield;
+ options.max_write_batch_group_size_bytes =
+ immutable_db_options.max_write_batch_group_size_bytes;
+ options.write_thread_max_yield_usec =
+ immutable_db_options.write_thread_max_yield_usec;
+ options.write_thread_slow_yield_usec =
+ immutable_db_options.write_thread_slow_yield_usec;
+ options.skip_stats_update_on_db_open =
+ immutable_db_options.skip_stats_update_on_db_open;
+ options.skip_checking_sst_file_sizes_on_db_open =
+ immutable_db_options.skip_checking_sst_file_sizes_on_db_open;
+ options.wal_recovery_mode = immutable_db_options.wal_recovery_mode;
+ options.allow_2pc = immutable_db_options.allow_2pc;
+ options.row_cache = immutable_db_options.row_cache;
+#ifndef ROCKSDB_LITE
+ options.wal_filter = immutable_db_options.wal_filter;
+#endif // ROCKSDB_LITE
+ options.fail_if_options_file_error =
+ immutable_db_options.fail_if_options_file_error;
+ options.dump_malloc_stats = immutable_db_options.dump_malloc_stats;
+ options.avoid_flush_during_recovery =
+ immutable_db_options.avoid_flush_during_recovery;
+ options.avoid_flush_during_shutdown =
+ mutable_db_options.avoid_flush_during_shutdown;
+ options.allow_ingest_behind =
+ immutable_db_options.allow_ingest_behind;
+ options.preserve_deletes =
+ immutable_db_options.preserve_deletes;
+ options.two_write_queues = immutable_db_options.two_write_queues;
+ options.manual_wal_flush = immutable_db_options.manual_wal_flush;
+ options.atomic_flush = immutable_db_options.atomic_flush;
+ options.avoid_unnecessary_blocking_io =
+ immutable_db_options.avoid_unnecessary_blocking_io;
+ options.log_readahead_size = immutable_db_options.log_readahead_size;
+ options.sst_file_checksum_func = immutable_db_options.sst_file_checksum_func;
+ return options;
+}
+
+ColumnFamilyOptions BuildColumnFamilyOptions(
+ const ColumnFamilyOptions& options,
+ const MutableCFOptions& mutable_cf_options) {
+ ColumnFamilyOptions cf_opts(options);
+
+ // Memtable related options
+ cf_opts.write_buffer_size = mutable_cf_options.write_buffer_size;
+ cf_opts.max_write_buffer_number = mutable_cf_options.max_write_buffer_number;
+ cf_opts.arena_block_size = mutable_cf_options.arena_block_size;
+ cf_opts.memtable_prefix_bloom_size_ratio =
+ mutable_cf_options.memtable_prefix_bloom_size_ratio;
+ cf_opts.memtable_whole_key_filtering =
+ mutable_cf_options.memtable_whole_key_filtering;
+ cf_opts.memtable_huge_page_size = mutable_cf_options.memtable_huge_page_size;
+ cf_opts.max_successive_merges = mutable_cf_options.max_successive_merges;
+ cf_opts.inplace_update_num_locks =
+ mutable_cf_options.inplace_update_num_locks;
+ cf_opts.prefix_extractor = mutable_cf_options.prefix_extractor;
+
+ // Compaction related options
+ cf_opts.disable_auto_compactions =
+ mutable_cf_options.disable_auto_compactions;
+ cf_opts.soft_pending_compaction_bytes_limit =
+ mutable_cf_options.soft_pending_compaction_bytes_limit;
+ cf_opts.hard_pending_compaction_bytes_limit =
+ mutable_cf_options.hard_pending_compaction_bytes_limit;
+ cf_opts.level0_file_num_compaction_trigger =
+ mutable_cf_options.level0_file_num_compaction_trigger;
+ cf_opts.level0_slowdown_writes_trigger =
+ mutable_cf_options.level0_slowdown_writes_trigger;
+ cf_opts.level0_stop_writes_trigger =
+ mutable_cf_options.level0_stop_writes_trigger;
+ cf_opts.max_compaction_bytes = mutable_cf_options.max_compaction_bytes;
+ cf_opts.target_file_size_base = mutable_cf_options.target_file_size_base;
+ cf_opts.target_file_size_multiplier =
+ mutable_cf_options.target_file_size_multiplier;
+ cf_opts.max_bytes_for_level_base =
+ mutable_cf_options.max_bytes_for_level_base;
+ cf_opts.max_bytes_for_level_multiplier =
+ mutable_cf_options.max_bytes_for_level_multiplier;
+ cf_opts.ttl = mutable_cf_options.ttl;
+ cf_opts.periodic_compaction_seconds =
+ mutable_cf_options.periodic_compaction_seconds;
+
+ cf_opts.max_bytes_for_level_multiplier_additional.clear();
+ for (auto value :
+ mutable_cf_options.max_bytes_for_level_multiplier_additional) {
+ cf_opts.max_bytes_for_level_multiplier_additional.emplace_back(value);
+ }
+
+ cf_opts.compaction_options_fifo = mutable_cf_options.compaction_options_fifo;
+ cf_opts.compaction_options_universal =
+ mutable_cf_options.compaction_options_universal;
+
+ // Misc options
+ cf_opts.max_sequential_skip_in_iterations =
+ mutable_cf_options.max_sequential_skip_in_iterations;
+ cf_opts.paranoid_file_checks = mutable_cf_options.paranoid_file_checks;
+ cf_opts.report_bg_io_stats = mutable_cf_options.report_bg_io_stats;
+ cf_opts.compression = mutable_cf_options.compression;
+ cf_opts.sample_for_compression = mutable_cf_options.sample_for_compression;
+
+ cf_opts.table_factory = options.table_factory;
+ // TODO(yhchiang): find some way to handle the following derived options
+ // * max_file_size
+
+ return cf_opts;
+}
+
+std::map<CompactionStyle, std::string>
+ OptionsHelper::compaction_style_to_string = {
+ {kCompactionStyleLevel, "kCompactionStyleLevel"},
+ {kCompactionStyleUniversal, "kCompactionStyleUniversal"},
+ {kCompactionStyleFIFO, "kCompactionStyleFIFO"},
+ {kCompactionStyleNone, "kCompactionStyleNone"}};
+
+std::map<CompactionPri, std::string> OptionsHelper::compaction_pri_to_string = {
+ {kByCompensatedSize, "kByCompensatedSize"},
+ {kOldestLargestSeqFirst, "kOldestLargestSeqFirst"},
+ {kOldestSmallestSeqFirst, "kOldestSmallestSeqFirst"},
+ {kMinOverlappingRatio, "kMinOverlappingRatio"}};
+
+std::map<CompactionStopStyle, std::string>
+ OptionsHelper::compaction_stop_style_to_string = {
+ {kCompactionStopStyleSimilarSize, "kCompactionStopStyleSimilarSize"},
+ {kCompactionStopStyleTotalSize, "kCompactionStopStyleTotalSize"}};
+
+std::unordered_map<std::string, ChecksumType>
+ OptionsHelper::checksum_type_string_map = {{"kNoChecksum", kNoChecksum},
+ {"kCRC32c", kCRC32c},
+ {"kxxHash", kxxHash},
+ {"kxxHash64", kxxHash64}};
+
+std::unordered_map<std::string, CompressionType>
+ OptionsHelper::compression_type_string_map = {
+ {"kNoCompression", kNoCompression},
+ {"kSnappyCompression", kSnappyCompression},
+ {"kZlibCompression", kZlibCompression},
+ {"kBZip2Compression", kBZip2Compression},
+ {"kLZ4Compression", kLZ4Compression},
+ {"kLZ4HCCompression", kLZ4HCCompression},
+ {"kXpressCompression", kXpressCompression},
+ {"kZSTD", kZSTD},
+ {"kZSTDNotFinalCompression", kZSTDNotFinalCompression},
+ {"kDisableCompressionOption", kDisableCompressionOption}};
+#ifndef ROCKSDB_LITE
+
+const std::string kNameComparator = "comparator";
+const std::string kNameEnv = "env";
+const std::string kNameMergeOperator = "merge_operator";
+
+template <typename T>
+Status GetStringFromStruct(
+ std::string* opt_string, const T& options,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info,
+ const std::string& delimiter);
+
+namespace {
+template <typename T>
+bool ParseEnum(const std::unordered_map<std::string, T>& type_map,
+ const std::string& type, T* value) {
+ auto iter = type_map.find(type);
+ if (iter != type_map.end()) {
+ *value = iter->second;
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+bool SerializeEnum(const std::unordered_map<std::string, T>& type_map,
+ const T& type, std::string* value) {
+ for (const auto& pair : type_map) {
+ if (pair.second == type) {
+ *value = pair.first;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SerializeVectorCompressionType(const std::vector<CompressionType>& types,
+ std::string* value) {
+ std::stringstream ss;
+ bool result;
+ for (size_t i = 0; i < types.size(); ++i) {
+ if (i > 0) {
+ ss << ':';
+ }
+ std::string string_type;
+ result = SerializeEnum<CompressionType>(compression_type_string_map,
+ types[i], &string_type);
+ if (result == false) {
+ return result;
+ }
+ ss << string_type;
+ }
+ *value = ss.str();
+ return true;
+}
+
+bool ParseVectorCompressionType(
+ const std::string& value,
+ std::vector<CompressionType>* compression_per_level) {
+ compression_per_level->clear();
+ size_t start = 0;
+ while (start < value.size()) {
+ size_t end = value.find(':', start);
+ bool is_ok;
+ CompressionType type;
+ if (end == std::string::npos) {
+ is_ok = ParseEnum<CompressionType>(compression_type_string_map,
+ value.substr(start), &type);
+ if (!is_ok) {
+ return false;
+ }
+ compression_per_level->emplace_back(type);
+ break;
+ } else {
+ is_ok = ParseEnum<CompressionType>(
+ compression_type_string_map, value.substr(start, end - start), &type);
+ if (!is_ok) {
+ return false;
+ }
+ compression_per_level->emplace_back(type);
+ start = end + 1;
+ }
+ }
+ return true;
+}
+
+// This is to handle backward compatibility, where compaction_options_fifo
+// could be assigned a single scalar value, say, like "23", which would be
+// assigned to max_table_files_size.
+bool FIFOCompactionOptionsSpecialCase(const std::string& opt_str,
+ CompactionOptionsFIFO* options) {
+ if (opt_str.find("=") != std::string::npos) {
+ // New format. Go do your new parsing using ParseStructOptions.
+ return false;
+ }
+
+ // Old format. Parse just a single uint64_t value.
+ options->max_table_files_size = ParseUint64(opt_str);
+ return true;
+}
+
+template <typename T>
+bool SerializeStruct(
+ const T& options, std::string* value,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info_map) {
+ std::string opt_str;
+ Status s = GetStringFromStruct(&opt_str, options, type_info_map, ";");
+ if (!s.ok()) {
+ return false;
+ }
+ *value = "{" + opt_str + "}";
+ return true;
+}
+
+template <typename T>
+bool ParseSingleStructOption(
+ const std::string& opt_val_str, T* options,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info_map) {
+ size_t end = opt_val_str.find('=');
+ std::string key = opt_val_str.substr(0, end);
+ std::string value = opt_val_str.substr(end + 1);
+ auto iter = type_info_map.find(key);
+ if (iter == type_info_map.end()) {
+ return false;
+ }
+ const auto& opt_info = iter->second;
+ if (opt_info.verification == OptionVerificationType::kDeprecated) {
+ // Should also skip deprecated sub-options such as
+ // fifo_compaction_options_type_info.ttl
+ return true;
+ }
+ return ParseOptionHelper(
+ reinterpret_cast<char*>(options) + opt_info.mutable_offset, opt_info.type,
+ value);
+}
+
+template <typename T>
+bool ParseStructOptions(
+ const std::string& opt_str, T* options,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info_map) {
+ assert(!opt_str.empty());
+
+ size_t start = 0;
+ if (opt_str[0] == '{') {
+ start++;
+ }
+ while ((start != std::string::npos) && (start < opt_str.size())) {
+ if (opt_str[start] == '}') {
+ break;
+ }
+ size_t end = opt_str.find(';', start);
+ size_t len = (end == std::string::npos) ? end : end - start;
+ if (!ParseSingleStructOption(opt_str.substr(start, len), options,
+ type_info_map)) {
+ return false;
+ }
+ start = (end == std::string::npos) ? end : end + 1;
+ }
+ return true;
+}
+} // anonymouse namespace
+
+bool ParseSliceTransformHelper(
+ const std::string& kFixedPrefixName, const std::string& kCappedPrefixName,
+ const std::string& value,
+ std::shared_ptr<const SliceTransform>* slice_transform) {
+ const char* no_op_name = "rocksdb.Noop";
+ size_t no_op_length = strlen(no_op_name);
+ auto& pe_value = value;
+ if (pe_value.size() > kFixedPrefixName.size() &&
+ pe_value.compare(0, kFixedPrefixName.size(), kFixedPrefixName) == 0) {
+ int prefix_length = ParseInt(trim(value.substr(kFixedPrefixName.size())));
+ slice_transform->reset(NewFixedPrefixTransform(prefix_length));
+ } else if (pe_value.size() > kCappedPrefixName.size() &&
+ pe_value.compare(0, kCappedPrefixName.size(), kCappedPrefixName) ==
+ 0) {
+ int prefix_length =
+ ParseInt(trim(pe_value.substr(kCappedPrefixName.size())));
+ slice_transform->reset(NewCappedPrefixTransform(prefix_length));
+ } else if (pe_value.size() == no_op_length &&
+ pe_value.compare(0, no_op_length, no_op_name) == 0) {
+ const SliceTransform* no_op_transform = NewNoopTransform();
+ slice_transform->reset(no_op_transform);
+ } else if (value == kNullptrString) {
+ slice_transform->reset();
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseSliceTransform(
+ const std::string& value,
+ std::shared_ptr<const SliceTransform>* slice_transform) {
+ // While we normally don't convert the string representation of a
+ // pointer-typed option into its instance, here we do so for backward
+ // compatibility as we allow this action in SetOption().
+
+ // TODO(yhchiang): A possible better place for these serialization /
+ // deserialization is inside the class definition of pointer-typed
+ // option itself, but this requires a bigger change of public API.
+ bool result =
+ ParseSliceTransformHelper("fixed:", "capped:", value, slice_transform);
+ if (result) {
+ return result;
+ }
+ result = ParseSliceTransformHelper(
+ "rocksdb.FixedPrefix.", "rocksdb.CappedPrefix.", value, slice_transform);
+ if (result) {
+ return result;
+ }
+ // TODO(yhchiang): we can further support other default
+ // SliceTransforms here.
+ return false;
+}
+
+bool ParseOptionHelper(char* opt_address, const OptionType& opt_type,
+ const std::string& value) {
+ switch (opt_type) {
+ case OptionType::kBoolean:
+ *reinterpret_cast<bool*>(opt_address) = ParseBoolean("", value);
+ break;
+ case OptionType::kInt:
+ *reinterpret_cast<int*>(opt_address) = ParseInt(value);
+ break;
+ case OptionType::kInt32T:
+ *reinterpret_cast<int32_t*>(opt_address) = ParseInt32(value);
+ break;
+ case OptionType::kInt64T:
+ PutUnaligned(reinterpret_cast<int64_t*>(opt_address), ParseInt64(value));
+ break;
+ case OptionType::kVectorInt:
+ *reinterpret_cast<std::vector<int>*>(opt_address) = ParseVectorInt(value);
+ break;
+ case OptionType::kUInt:
+ *reinterpret_cast<unsigned int*>(opt_address) = ParseUint32(value);
+ break;
+ case OptionType::kUInt32T:
+ *reinterpret_cast<uint32_t*>(opt_address) = ParseUint32(value);
+ break;
+ case OptionType::kUInt64T:
+ PutUnaligned(reinterpret_cast<uint64_t*>(opt_address), ParseUint64(value));
+ break;
+ case OptionType::kSizeT:
+ PutUnaligned(reinterpret_cast<size_t*>(opt_address), ParseSizeT(value));
+ break;
+ case OptionType::kString:
+ *reinterpret_cast<std::string*>(opt_address) = value;
+ break;
+ case OptionType::kDouble:
+ *reinterpret_cast<double*>(opt_address) = ParseDouble(value);
+ break;
+ case OptionType::kCompactionStyle:
+ return ParseEnum<CompactionStyle>(
+ compaction_style_string_map, value,
+ reinterpret_cast<CompactionStyle*>(opt_address));
+ case OptionType::kCompactionPri:
+ return ParseEnum<CompactionPri>(
+ compaction_pri_string_map, value,
+ reinterpret_cast<CompactionPri*>(opt_address));
+ case OptionType::kCompressionType:
+ return ParseEnum<CompressionType>(
+ compression_type_string_map, value,
+ reinterpret_cast<CompressionType*>(opt_address));
+ case OptionType::kVectorCompressionType:
+ return ParseVectorCompressionType(
+ value, reinterpret_cast<std::vector<CompressionType>*>(opt_address));
+ case OptionType::kSliceTransform:
+ return ParseSliceTransform(
+ value, reinterpret_cast<std::shared_ptr<const SliceTransform>*>(
+ opt_address));
+ case OptionType::kChecksumType:
+ return ParseEnum<ChecksumType>(
+ checksum_type_string_map, value,
+ reinterpret_cast<ChecksumType*>(opt_address));
+ case OptionType::kBlockBasedTableIndexType:
+ return ParseEnum<BlockBasedTableOptions::IndexType>(
+ block_base_table_index_type_string_map, value,
+ reinterpret_cast<BlockBasedTableOptions::IndexType*>(opt_address));
+ case OptionType::kBlockBasedTableDataBlockIndexType:
+ return ParseEnum<BlockBasedTableOptions::DataBlockIndexType>(
+ block_base_table_data_block_index_type_string_map, value,
+ reinterpret_cast<BlockBasedTableOptions::DataBlockIndexType*>(
+ opt_address));
+ case OptionType::kBlockBasedTableIndexShorteningMode:
+ return ParseEnum<BlockBasedTableOptions::IndexShorteningMode>(
+ block_base_table_index_shortening_mode_string_map, value,
+ reinterpret_cast<BlockBasedTableOptions::IndexShorteningMode*>(
+ opt_address));
+ case OptionType::kEncodingType:
+ return ParseEnum<EncodingType>(
+ encoding_type_string_map, value,
+ reinterpret_cast<EncodingType*>(opt_address));
+ case OptionType::kWALRecoveryMode:
+ return ParseEnum<WALRecoveryMode>(
+ wal_recovery_mode_string_map, value,
+ reinterpret_cast<WALRecoveryMode*>(opt_address));
+ case OptionType::kAccessHint:
+ return ParseEnum<DBOptions::AccessHint>(
+ access_hint_string_map, value,
+ reinterpret_cast<DBOptions::AccessHint*>(opt_address));
+ case OptionType::kInfoLogLevel:
+ return ParseEnum<InfoLogLevel>(
+ info_log_level_string_map, value,
+ reinterpret_cast<InfoLogLevel*>(opt_address));
+ case OptionType::kCompactionOptionsFIFO: {
+ if (!FIFOCompactionOptionsSpecialCase(
+ value, reinterpret_cast<CompactionOptionsFIFO*>(opt_address))) {
+ return ParseStructOptions<CompactionOptionsFIFO>(
+ value, reinterpret_cast<CompactionOptionsFIFO*>(opt_address),
+ fifo_compaction_options_type_info);
+ }
+ return true;
+ }
+ case OptionType::kLRUCacheOptions: {
+ return ParseStructOptions<LRUCacheOptions>(value,
+ reinterpret_cast<LRUCacheOptions*>(opt_address),
+ lru_cache_options_type_info);
+ }
+ case OptionType::kCompactionOptionsUniversal:
+ return ParseStructOptions<CompactionOptionsUniversal>(
+ value, reinterpret_cast<CompactionOptionsUniversal*>(opt_address),
+ universal_compaction_options_type_info);
+ case OptionType::kCompactionStopStyle:
+ return ParseEnum<CompactionStopStyle>(
+ compaction_stop_style_string_map, value,
+ reinterpret_cast<CompactionStopStyle*>(opt_address));
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool SerializeSingleOptionHelper(const char* opt_address,
+ const OptionType opt_type,
+ std::string* value) {
+
+ assert(value);
+ switch (opt_type) {
+ case OptionType::kBoolean:
+ *value = *(reinterpret_cast<const bool*>(opt_address)) ? "true" : "false";
+ break;
+ case OptionType::kInt:
+ *value = ToString(*(reinterpret_cast<const int*>(opt_address)));
+ break;
+ case OptionType::kInt32T:
+ *value = ToString(*(reinterpret_cast<const int32_t*>(opt_address)));
+ break;
+ case OptionType::kInt64T:
+ {
+ int64_t v;
+ GetUnaligned(reinterpret_cast<const int64_t*>(opt_address), &v);
+ *value = ToString(v);
+ }
+ break;
+ case OptionType::kVectorInt:
+ return SerializeIntVector(
+ *reinterpret_cast<const std::vector<int>*>(opt_address), value);
+ case OptionType::kUInt:
+ *value = ToString(*(reinterpret_cast<const unsigned int*>(opt_address)));
+ break;
+ case OptionType::kUInt32T:
+ *value = ToString(*(reinterpret_cast<const uint32_t*>(opt_address)));
+ break;
+ case OptionType::kUInt64T:
+ {
+ uint64_t v;
+ GetUnaligned(reinterpret_cast<const uint64_t*>(opt_address), &v);
+ *value = ToString(v);
+ }
+ break;
+ case OptionType::kSizeT:
+ {
+ size_t v;
+ GetUnaligned(reinterpret_cast<const size_t*>(opt_address), &v);
+ *value = ToString(v);
+ }
+ break;
+ case OptionType::kDouble:
+ *value = ToString(*(reinterpret_cast<const double*>(opt_address)));
+ break;
+ case OptionType::kString:
+ *value = EscapeOptionString(
+ *(reinterpret_cast<const std::string*>(opt_address)));
+ break;
+ case OptionType::kCompactionStyle:
+ return SerializeEnum<CompactionStyle>(
+ compaction_style_string_map,
+ *(reinterpret_cast<const CompactionStyle*>(opt_address)), value);
+ case OptionType::kCompactionPri:
+ return SerializeEnum<CompactionPri>(
+ compaction_pri_string_map,
+ *(reinterpret_cast<const CompactionPri*>(opt_address)), value);
+ case OptionType::kCompressionType:
+ return SerializeEnum<CompressionType>(
+ compression_type_string_map,
+ *(reinterpret_cast<const CompressionType*>(opt_address)), value);
+ case OptionType::kVectorCompressionType:
+ return SerializeVectorCompressionType(
+ *(reinterpret_cast<const std::vector<CompressionType>*>(opt_address)),
+ value);
+ break;
+ case OptionType::kSliceTransform: {
+ const auto* slice_transform_ptr =
+ reinterpret_cast<const std::shared_ptr<const SliceTransform>*>(
+ opt_address);
+ *value = slice_transform_ptr->get() ? slice_transform_ptr->get()->Name()
+ : kNullptrString;
+ break;
+ }
+ case OptionType::kTableFactory: {
+ const auto* table_factory_ptr =
+ reinterpret_cast<const std::shared_ptr<const TableFactory>*>(
+ opt_address);
+ *value = table_factory_ptr->get() ? table_factory_ptr->get()->Name()
+ : kNullptrString;
+ break;
+ }
+ case OptionType::kComparator: {
+ // it's a const pointer of const Comparator*
+ const auto* ptr = reinterpret_cast<const Comparator* const*>(opt_address);
+ // Since the user-specified comparator will be wrapped by
+ // InternalKeyComparator, we should persist the user-specified one
+ // instead of InternalKeyComparator.
+ if (*ptr == nullptr) {
+ *value = kNullptrString;
+ } else {
+ const Comparator* root_comp = (*ptr)->GetRootComparator();
+ if (root_comp == nullptr) {
+ root_comp = (*ptr);
+ }
+ *value = root_comp->Name();
+ }
+ break;
+ }
+ case OptionType::kCompactionFilter: {
+ // it's a const pointer of const CompactionFilter*
+ const auto* ptr =
+ reinterpret_cast<const CompactionFilter* const*>(opt_address);
+ *value = *ptr ? (*ptr)->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kCompactionFilterFactory: {
+ const auto* ptr =
+ reinterpret_cast<const std::shared_ptr<CompactionFilterFactory>*>(
+ opt_address);
+ *value = ptr->get() ? ptr->get()->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kMemTableRepFactory: {
+ const auto* ptr =
+ reinterpret_cast<const std::shared_ptr<MemTableRepFactory>*>(
+ opt_address);
+ *value = ptr->get() ? ptr->get()->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kMergeOperator: {
+ const auto* ptr =
+ reinterpret_cast<const std::shared_ptr<MergeOperator>*>(opt_address);
+ *value = ptr->get() ? ptr->get()->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kFilterPolicy: {
+ const auto* ptr =
+ reinterpret_cast<const std::shared_ptr<FilterPolicy>*>(opt_address);
+ *value = ptr->get() ? ptr->get()->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kChecksumType:
+ return SerializeEnum<ChecksumType>(
+ checksum_type_string_map,
+ *reinterpret_cast<const ChecksumType*>(opt_address), value);
+ case OptionType::kBlockBasedTableIndexType:
+ return SerializeEnum<BlockBasedTableOptions::IndexType>(
+ block_base_table_index_type_string_map,
+ *reinterpret_cast<const BlockBasedTableOptions::IndexType*>(
+ opt_address),
+ value);
+ case OptionType::kBlockBasedTableDataBlockIndexType:
+ return SerializeEnum<BlockBasedTableOptions::DataBlockIndexType>(
+ block_base_table_data_block_index_type_string_map,
+ *reinterpret_cast<const BlockBasedTableOptions::DataBlockIndexType*>(
+ opt_address),
+ value);
+ case OptionType::kBlockBasedTableIndexShorteningMode:
+ return SerializeEnum<BlockBasedTableOptions::IndexShorteningMode>(
+ block_base_table_index_shortening_mode_string_map,
+ *reinterpret_cast<const BlockBasedTableOptions::IndexShorteningMode*>(
+ opt_address),
+ value);
+ case OptionType::kFlushBlockPolicyFactory: {
+ const auto* ptr =
+ reinterpret_cast<const std::shared_ptr<FlushBlockPolicyFactory>*>(
+ opt_address);
+ *value = ptr->get() ? ptr->get()->Name() : kNullptrString;
+ break;
+ }
+ case OptionType::kEncodingType:
+ return SerializeEnum<EncodingType>(
+ encoding_type_string_map,
+ *reinterpret_cast<const EncodingType*>(opt_address), value);
+ case OptionType::kWALRecoveryMode:
+ return SerializeEnum<WALRecoveryMode>(
+ wal_recovery_mode_string_map,
+ *reinterpret_cast<const WALRecoveryMode*>(opt_address), value);
+ case OptionType::kAccessHint:
+ return SerializeEnum<DBOptions::AccessHint>(
+ access_hint_string_map,
+ *reinterpret_cast<const DBOptions::AccessHint*>(opt_address), value);
+ case OptionType::kInfoLogLevel:
+ return SerializeEnum<InfoLogLevel>(
+ info_log_level_string_map,
+ *reinterpret_cast<const InfoLogLevel*>(opt_address), value);
+ case OptionType::kCompactionOptionsFIFO:
+ return SerializeStruct<CompactionOptionsFIFO>(
+ *reinterpret_cast<const CompactionOptionsFIFO*>(opt_address), value,
+ fifo_compaction_options_type_info);
+ case OptionType::kCompactionOptionsUniversal:
+ return SerializeStruct<CompactionOptionsUniversal>(
+ *reinterpret_cast<const CompactionOptionsUniversal*>(opt_address),
+ value, universal_compaction_options_type_info);
+ case OptionType::kCompactionStopStyle:
+ return SerializeEnum<CompactionStopStyle>(
+ compaction_stop_style_string_map,
+ *reinterpret_cast<const CompactionStopStyle*>(opt_address), value);
+ default:
+ return false;
+ }
+ return true;
+}
+
+Status GetMutableOptionsFromStrings(
+ const MutableCFOptions& base_options,
+ const std::unordered_map<std::string, std::string>& options_map,
+ Logger* info_log, MutableCFOptions* new_options) {
+ assert(new_options);
+ *new_options = base_options;
+ for (const auto& o : options_map) {
+ try {
+ auto iter = cf_options_type_info.find(o.first);
+ if (iter == cf_options_type_info.end()) {
+ return Status::InvalidArgument("Unrecognized option: " + o.first);
+ }
+ const auto& opt_info = iter->second;
+ if (!opt_info.is_mutable) {
+ return Status::InvalidArgument("Option not changeable: " + o.first);
+ }
+ if (opt_info.verification == OptionVerificationType::kDeprecated) {
+ // log warning when user tries to set a deprecated option but don't fail
+ // the call for compatibility.
+ ROCKS_LOG_WARN(info_log, "%s is a deprecated option and cannot be set",
+ o.first.c_str());
+ continue;
+ }
+ bool is_ok = ParseOptionHelper(
+ reinterpret_cast<char*>(new_options) + opt_info.mutable_offset,
+ opt_info.type, o.second);
+ if (!is_ok) {
+ return Status::InvalidArgument("Error parsing " + o.first);
+ }
+ } catch (std::exception& e) {
+ return Status::InvalidArgument("Error parsing " + o.first + ":" +
+ std::string(e.what()));
+ }
+ }
+ return Status::OK();
+}
+
+Status GetMutableDBOptionsFromStrings(
+ const MutableDBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& options_map,
+ MutableDBOptions* new_options) {
+ assert(new_options);
+ *new_options = base_options;
+ for (const auto& o : options_map) {
+ try {
+ auto iter = db_options_type_info.find(o.first);
+ if (iter == db_options_type_info.end()) {
+ return Status::InvalidArgument("Unrecognized option: " + o.first);
+ }
+ const auto& opt_info = iter->second;
+ if (!opt_info.is_mutable) {
+ return Status::InvalidArgument("Option not changeable: " + o.first);
+ }
+ bool is_ok = ParseOptionHelper(
+ reinterpret_cast<char*>(new_options) + opt_info.mutable_offset,
+ opt_info.type, o.second);
+ if (!is_ok) {
+ return Status::InvalidArgument("Error parsing " + o.first);
+ }
+ } catch (std::exception& e) {
+ return Status::InvalidArgument("Error parsing " + o.first + ":" +
+ std::string(e.what()));
+ }
+ }
+ return Status::OK();
+}
+
+Status StringToMap(const std::string& opts_str,
+ std::unordered_map<std::string, std::string>* opts_map) {
+ assert(opts_map);
+ // Example:
+ // opts_str = "write_buffer_size=1024;max_write_buffer_number=2;"
+ // "nested_opt={opt1=1;opt2=2};max_bytes_for_level_base=100"
+ size_t pos = 0;
+ std::string opts = trim(opts_str);
+ while (pos < opts.size()) {
+ size_t eq_pos = opts.find('=', pos);
+ if (eq_pos == std::string::npos) {
+ return Status::InvalidArgument("Mismatched key value pair, '=' expected");
+ }
+ std::string key = trim(opts.substr(pos, eq_pos - pos));
+ if (key.empty()) {
+ return Status::InvalidArgument("Empty key found");
+ }
+
+ // skip space after '=' and look for '{' for possible nested options
+ pos = eq_pos + 1;
+ while (pos < opts.size() && isspace(opts[pos])) {
+ ++pos;
+ }
+ // Empty value at the end
+ if (pos >= opts.size()) {
+ (*opts_map)[key] = "";
+ break;
+ }
+ if (opts[pos] == '{') {
+ int count = 1;
+ size_t brace_pos = pos + 1;
+ while (brace_pos < opts.size()) {
+ if (opts[brace_pos] == '{') {
+ ++count;
+ } else if (opts[brace_pos] == '}') {
+ --count;
+ if (count == 0) {
+ break;
+ }
+ }
+ ++brace_pos;
+ }
+ // found the matching closing brace
+ if (count == 0) {
+ (*opts_map)[key] = trim(opts.substr(pos + 1, brace_pos - pos - 1));
+ // skip all whitespace and move to the next ';'
+ // brace_pos points to the next position after the matching '}'
+ pos = brace_pos + 1;
+ while (pos < opts.size() && isspace(opts[pos])) {
+ ++pos;
+ }
+ if (pos < opts.size() && opts[pos] != ';') {
+ return Status::InvalidArgument(
+ "Unexpected chars after nested options");
+ }
+ ++pos;
+ } else {
+ return Status::InvalidArgument(
+ "Mismatched curly braces for nested options");
+ }
+ } else {
+ size_t sc_pos = opts.find(';', pos);
+ if (sc_pos == std::string::npos) {
+ (*opts_map)[key] = trim(opts.substr(pos));
+ // It either ends with a trailing semi-colon or the last key-value pair
+ break;
+ } else {
+ (*opts_map)[key] = trim(opts.substr(pos, sc_pos - pos));
+ }
+ pos = sc_pos + 1;
+ }
+ }
+
+ return Status::OK();
+}
+
+Status ParseCompressionOptions(const std::string& value, const std::string& name,
+ CompressionOptions& compression_opts) {
+ size_t start = 0;
+ size_t end = value.find(':');
+ if (end == std::string::npos) {
+ return Status::InvalidArgument("unable to parse the specified CF option " +
+ name);
+ }
+ compression_opts.window_bits = ParseInt(value.substr(start, end - start));
+ start = end + 1;
+ end = value.find(':', start);
+ if (end == std::string::npos) {
+ return Status::InvalidArgument("unable to parse the specified CF option " +
+ name);
+ }
+ compression_opts.level = ParseInt(value.substr(start, end - start));
+ start = end + 1;
+ if (start >= value.size()) {
+ return Status::InvalidArgument("unable to parse the specified CF option " +
+ name);
+ }
+ end = value.find(':', start);
+ compression_opts.strategy =
+ ParseInt(value.substr(start, value.size() - start));
+ // max_dict_bytes is optional for backwards compatibility
+ if (end != std::string::npos) {
+ start = end + 1;
+ if (start >= value.size()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ compression_opts.max_dict_bytes =
+ ParseInt(value.substr(start, value.size() - start));
+ end = value.find(':', start);
+ }
+ // zstd_max_train_bytes is optional for backwards compatibility
+ if (end != std::string::npos) {
+ start = end + 1;
+ if (start >= value.size()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ compression_opts.zstd_max_train_bytes =
+ ParseInt(value.substr(start, value.size() - start));
+ end = value.find(':', start);
+ }
+ // enabled is optional for backwards compatibility
+ if (end != std::string::npos) {
+ start = end + 1;
+ if (start >= value.size()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ compression_opts.enabled =
+ ParseBoolean("", value.substr(start, value.size() - start));
+ }
+ return Status::OK();
+}
+
+Status ParseColumnFamilyOption(const std::string& name,
+ const std::string& org_value,
+ ColumnFamilyOptions* new_options,
+ bool input_strings_escaped = false) {
+ const std::string& value =
+ input_strings_escaped ? UnescapeOptionString(org_value) : org_value;
+ try {
+ if (name == "block_based_table_factory") {
+ // Nested options
+ BlockBasedTableOptions table_opt, base_table_options;
+ BlockBasedTableFactory* block_based_table_factory =
+ static_cast_with_check<BlockBasedTableFactory, TableFactory>(
+ new_options->table_factory.get());
+ if (block_based_table_factory != nullptr) {
+ base_table_options = block_based_table_factory->table_options();
+ }
+ Status table_opt_s = GetBlockBasedTableOptionsFromString(
+ base_table_options, value, &table_opt);
+ if (!table_opt_s.ok()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ new_options->table_factory.reset(NewBlockBasedTableFactory(table_opt));
+ } else if (name == "plain_table_factory") {
+ // Nested options
+ PlainTableOptions table_opt, base_table_options;
+ PlainTableFactory* plain_table_factory =
+ static_cast_with_check<PlainTableFactory, TableFactory>(
+ new_options->table_factory.get());
+ if (plain_table_factory != nullptr) {
+ base_table_options = plain_table_factory->table_options();
+ }
+ Status table_opt_s = GetPlainTableOptionsFromString(
+ base_table_options, value, &table_opt);
+ if (!table_opt_s.ok()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ new_options->table_factory.reset(NewPlainTableFactory(table_opt));
+ } else if (name == "memtable") {
+ std::unique_ptr<MemTableRepFactory> new_mem_factory;
+ Status mem_factory_s =
+ GetMemTableRepFactoryFromString(value, &new_mem_factory);
+ if (!mem_factory_s.ok()) {
+ return Status::InvalidArgument(
+ "unable to parse the specified CF option " + name);
+ }
+ new_options->memtable_factory.reset(new_mem_factory.release());
+ } else if (name == "bottommost_compression_opts") {
+ Status s = ParseCompressionOptions(
+ value, name, new_options->bottommost_compression_opts);
+ if (!s.ok()) {
+ return s;
+ }
+ } else if (name == "compression_opts") {
+ Status s =
+ ParseCompressionOptions(value, name, new_options->compression_opts);
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ if (name == kNameComparator) {
+ // Try to get comparator from object registry first.
+ // Only support static comparator for now.
+ Status status = ObjectRegistry::NewInstance()->NewStaticObject(
+ value, &new_options->comparator);
+ if (status.ok()) {
+ return status;
+ }
+ } else if (name == kNameMergeOperator) {
+ // Try to get merge operator from object registry first.
+ std::shared_ptr<MergeOperator> mo;
+ Status status =
+ ObjectRegistry::NewInstance()->NewSharedObject<MergeOperator>(
+ value, &new_options->merge_operator);
+ // Only support static comparator for now.
+ if (status.ok()) {
+ return status;
+ }
+ }
+
+ auto iter = cf_options_type_info.find(name);
+ if (iter == cf_options_type_info.end()) {
+ return Status::InvalidArgument(
+ "Unable to parse the specified CF option " + name);
+ }
+ const auto& opt_info = iter->second;
+ if (opt_info.verification != OptionVerificationType::kDeprecated &&
+ ParseOptionHelper(
+ reinterpret_cast<char*>(new_options) + opt_info.offset,
+ opt_info.type, value)) {
+ return Status::OK();
+ }
+ switch (opt_info.verification) {
+ case OptionVerificationType::kByName:
+ case OptionVerificationType::kByNameAllowNull:
+ case OptionVerificationType::kByNameAllowFromNull:
+ return Status::NotSupported(
+ "Deserializing the specified CF option " + name +
+ " is not supported");
+ case OptionVerificationType::kDeprecated:
+ return Status::OK();
+ default:
+ return Status::InvalidArgument(
+ "Unable to parse the specified CF option " + name);
+ }
+ }
+ } catch (const std::exception&) {
+ return Status::InvalidArgument(
+ "unable to parse the specified option " + name);
+ }
+ return Status::OK();
+}
+
+template <typename T>
+bool SerializeSingleStructOption(
+ std::string* opt_string, const T& options,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info,
+ const std::string& name, const std::string& delimiter) {
+ auto iter = type_info.find(name);
+ if (iter == type_info.end()) {
+ return false;
+ }
+ auto& opt_info = iter->second;
+ const char* opt_address =
+ reinterpret_cast<const char*>(&options) + opt_info.offset;
+ std::string value;
+ bool result = SerializeSingleOptionHelper(opt_address, opt_info.type, &value);
+ if (result) {
+ *opt_string = name + "=" + value + delimiter;
+ }
+ return result;
+}
+
+template <typename T>
+Status GetStringFromStruct(
+ std::string* opt_string, const T& options,
+ const std::unordered_map<std::string, OptionTypeInfo>& type_info,
+ const std::string& delimiter) {
+ assert(opt_string);
+ opt_string->clear();
+ for (auto iter = type_info.begin(); iter != type_info.end(); ++iter) {
+ if (iter->second.verification == OptionVerificationType::kDeprecated) {
+ // If the option is no longer used in rocksdb and marked as deprecated,
+ // we skip it in the serialization.
+ continue;
+ }
+ std::string single_output;
+ bool result = SerializeSingleStructOption<T>(
+ &single_output, options, type_info, iter->first, delimiter);
+ if (result) {
+ opt_string->append(single_output);
+ } else {
+ return Status::InvalidArgument("failed to serialize %s\n",
+ iter->first.c_str());
+ }
+ assert(result);
+ }
+ return Status::OK();
+}
+
+Status GetStringFromDBOptions(std::string* opt_string,
+ const DBOptions& db_options,
+ const std::string& delimiter) {
+ return GetStringFromStruct<DBOptions>(opt_string, db_options,
+ db_options_type_info, delimiter);
+}
+
+Status GetStringFromColumnFamilyOptions(std::string* opt_string,
+ const ColumnFamilyOptions& cf_options,
+ const std::string& delimiter) {
+ return GetStringFromStruct<ColumnFamilyOptions>(
+ opt_string, cf_options, cf_options_type_info, delimiter);
+}
+
+Status GetStringFromCompressionType(std::string* compression_str,
+ CompressionType compression_type) {
+ bool ok = SerializeEnum<CompressionType>(compression_type_string_map,
+ compression_type, compression_str);
+ if (ok) {
+ return Status::OK();
+ } else {
+ return Status::InvalidArgument("Invalid compression types");
+ }
+}
+
+std::vector<CompressionType> GetSupportedCompressions() {
+ std::vector<CompressionType> supported_compressions;
+ for (const auto& comp_to_name : compression_type_string_map) {
+ CompressionType t = comp_to_name.second;
+ if (t != kDisableCompressionOption && CompressionTypeSupported(t)) {
+ supported_compressions.push_back(t);
+ }
+ }
+ return supported_compressions;
+}
+
+Status ParseDBOption(const std::string& name,
+ const std::string& org_value,
+ DBOptions* new_options,
+ bool input_strings_escaped = false) {
+ const std::string& value =
+ input_strings_escaped ? UnescapeOptionString(org_value) : org_value;
+ try {
+ if (name == "rate_limiter_bytes_per_sec") {
+ new_options->rate_limiter.reset(
+ NewGenericRateLimiter(static_cast<int64_t>(ParseUint64(value))));
+ } else if (name == kNameEnv) {
+ // Currently `Env` can be deserialized from object registry only.
+ Env* env = new_options->env;
+ Status status = Env::LoadEnv(value, &env);
+ // Only support static env for now.
+ if (status.ok()) {
+ new_options->env = env;
+ }
+ } else {
+ auto iter = db_options_type_info.find(name);
+ if (iter == db_options_type_info.end()) {
+ return Status::InvalidArgument("Unrecognized option DBOptions:", name);
+ }
+ const auto& opt_info = iter->second;
+ if (opt_info.verification != OptionVerificationType::kDeprecated &&
+ ParseOptionHelper(
+ reinterpret_cast<char*>(new_options) + opt_info.offset,
+ opt_info.type, value)) {
+ return Status::OK();
+ }
+ switch (opt_info.verification) {
+ case OptionVerificationType::kByName:
+ case OptionVerificationType::kByNameAllowNull:
+ return Status::NotSupported(
+ "Deserializing the specified DB option " + name +
+ " is not supported");
+ case OptionVerificationType::kDeprecated:
+ return Status::OK();
+ default:
+ return Status::InvalidArgument(
+ "Unable to parse the specified DB option " + name);
+ }
+ }
+ } catch (const std::exception&) {
+ return Status::InvalidArgument("Unable to parse DBOptions:", name);
+ }
+ return Status::OK();
+}
+
+Status GetColumnFamilyOptionsFromMap(
+ const ColumnFamilyOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ ColumnFamilyOptions* new_options, bool input_strings_escaped,
+ bool ignore_unknown_options) {
+ return GetColumnFamilyOptionsFromMapInternal(
+ base_options, opts_map, new_options, input_strings_escaped, nullptr,
+ ignore_unknown_options);
+}
+
+Status GetColumnFamilyOptionsFromMapInternal(
+ const ColumnFamilyOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ ColumnFamilyOptions* new_options, bool input_strings_escaped,
+ std::vector<std::string>* unsupported_options_names,
+ bool ignore_unknown_options) {
+ assert(new_options);
+ *new_options = base_options;
+ if (unsupported_options_names) {
+ unsupported_options_names->clear();
+ }
+ for (const auto& o : opts_map) {
+ auto s = ParseColumnFamilyOption(o.first, o.second, new_options,
+ input_strings_escaped);
+ if (!s.ok()) {
+ if (s.IsNotSupported()) {
+ // If the deserialization of the specified option is not supported
+ // and an output vector of unsupported_options is provided, then
+ // we log the name of the unsupported option and proceed.
+ if (unsupported_options_names != nullptr) {
+ unsupported_options_names->push_back(o.first);
+ }
+ // Note that we still return Status::OK in such case to maintain
+ // the backward compatibility in the old public API defined in
+ // rocksdb/convenience.h
+ } else if (s.IsInvalidArgument() && ignore_unknown_options) {
+ continue;
+ } else {
+ // Restore "new_options" to the default "base_options".
+ *new_options = base_options;
+ return s;
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status GetColumnFamilyOptionsFromString(
+ const ColumnFamilyOptions& base_options,
+ const std::string& opts_str,
+ ColumnFamilyOptions* new_options) {
+ std::unordered_map<std::string, std::string> opts_map;
+ Status s = StringToMap(opts_str, &opts_map);
+ if (!s.ok()) {
+ *new_options = base_options;
+ return s;
+ }
+ return GetColumnFamilyOptionsFromMap(base_options, opts_map, new_options);
+}
+
+Status GetDBOptionsFromMap(
+ const DBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ DBOptions* new_options, bool input_strings_escaped,
+ bool ignore_unknown_options) {
+ return GetDBOptionsFromMapInternal(base_options, opts_map, new_options,
+ input_strings_escaped, nullptr,
+ ignore_unknown_options);
+}
+
+Status GetDBOptionsFromMapInternal(
+ const DBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ DBOptions* new_options, bool input_strings_escaped,
+ std::vector<std::string>* unsupported_options_names,
+ bool ignore_unknown_options) {
+ assert(new_options);
+ *new_options = base_options;
+ if (unsupported_options_names) {
+ unsupported_options_names->clear();
+ }
+ for (const auto& o : opts_map) {
+ auto s = ParseDBOption(o.first, o.second,
+ new_options, input_strings_escaped);
+ if (!s.ok()) {
+ if (s.IsNotSupported()) {
+ // If the deserialization of the specified option is not supported
+ // and an output vector of unsupported_options is provided, then
+ // we log the name of the unsupported option and proceed.
+ if (unsupported_options_names != nullptr) {
+ unsupported_options_names->push_back(o.first);
+ }
+ // Note that we still return Status::OK in such case to maintain
+ // the backward compatibility in the old public API defined in
+ // rocksdb/convenience.h
+ } else if (s.IsInvalidArgument() && ignore_unknown_options) {
+ continue;
+ } else {
+ // Restore "new_options" to the default "base_options".
+ *new_options = base_options;
+ return s;
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status GetDBOptionsFromString(
+ const DBOptions& base_options,
+ const std::string& opts_str,
+ DBOptions* new_options) {
+ std::unordered_map<std::string, std::string> opts_map;
+ Status s = StringToMap(opts_str, &opts_map);
+ if (!s.ok()) {
+ *new_options = base_options;
+ return s;
+ }
+ return GetDBOptionsFromMap(base_options, opts_map, new_options);
+}
+
+Status GetOptionsFromString(const Options& base_options,
+ const std::string& opts_str, Options* new_options) {
+ std::unordered_map<std::string, std::string> opts_map;
+ Status s = StringToMap(opts_str, &opts_map);
+ if (!s.ok()) {
+ return s;
+ }
+ DBOptions new_db_options(base_options);
+ ColumnFamilyOptions new_cf_options(base_options);
+ for (const auto& o : opts_map) {
+ if (ParseDBOption(o.first, o.second, &new_db_options).ok()) {
+ } else if (ParseColumnFamilyOption(
+ o.first, o.second, &new_cf_options).ok()) {
+ } else {
+ return Status::InvalidArgument("Can't parse option " + o.first);
+ }
+ }
+ *new_options = Options(new_db_options, new_cf_options);
+ return Status::OK();
+}
+
+Status GetTableFactoryFromMap(
+ const std::string& factory_name,
+ const std::unordered_map<std::string, std::string>& opt_map,
+ std::shared_ptr<TableFactory>* table_factory, bool ignore_unknown_options) {
+ Status s;
+ if (factory_name == BlockBasedTableFactory().Name()) {
+ BlockBasedTableOptions bbt_opt;
+ s = GetBlockBasedTableOptionsFromMap(BlockBasedTableOptions(), opt_map,
+ &bbt_opt,
+ true, /* input_strings_escaped */
+ ignore_unknown_options);
+ if (!s.ok()) {
+ return s;
+ }
+ table_factory->reset(new BlockBasedTableFactory(bbt_opt));
+ return Status::OK();
+ } else if (factory_name == PlainTableFactory().Name()) {
+ PlainTableOptions pt_opt;
+ s = GetPlainTableOptionsFromMap(PlainTableOptions(), opt_map, &pt_opt,
+ true, /* input_strings_escaped */
+ ignore_unknown_options);
+ if (!s.ok()) {
+ return s;
+ }
+ table_factory->reset(new PlainTableFactory(pt_opt));
+ return Status::OK();
+ }
+ // Return OK for not supported table factories as TableFactory
+ // Deserialization is optional.
+ table_factory->reset();
+ return Status::OK();
+}
+
+std::unordered_map<std::string, OptionTypeInfo>
+ OptionsHelper::db_options_type_info = {
+ /*
+ // not yet supported
+ std::shared_ptr<Cache> row_cache;
+ std::shared_ptr<DeleteScheduler> delete_scheduler;
+ std::shared_ptr<Logger> info_log;
+ std::shared_ptr<RateLimiter> rate_limiter;
+ std::shared_ptr<Statistics> statistics;
+ std::vector<DbPath> db_paths;
+ std::vector<std::shared_ptr<EventListener>> listeners;
+ */
+ {"advise_random_on_open",
+ {offsetof(struct DBOptions, advise_random_on_open),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"allow_mmap_reads",
+ {offsetof(struct DBOptions, allow_mmap_reads), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"allow_fallocate",
+ {offsetof(struct DBOptions, allow_fallocate), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"allow_mmap_writes",
+ {offsetof(struct DBOptions, allow_mmap_writes), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"use_direct_reads",
+ {offsetof(struct DBOptions, use_direct_reads), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"use_direct_writes",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"use_direct_io_for_flush_and_compaction",
+ {offsetof(struct DBOptions, use_direct_io_for_flush_and_compaction),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"allow_2pc",
+ {offsetof(struct DBOptions, allow_2pc), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"allow_os_buffer",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"create_if_missing",
+ {offsetof(struct DBOptions, create_if_missing), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"create_missing_column_families",
+ {offsetof(struct DBOptions, create_missing_column_families),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"disableDataSync",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"disable_data_sync", // for compatibility
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"enable_thread_tracking",
+ {offsetof(struct DBOptions, enable_thread_tracking),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"error_if_exists",
+ {offsetof(struct DBOptions, error_if_exists), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"is_fd_close_on_exec",
+ {offsetof(struct DBOptions, is_fd_close_on_exec), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"paranoid_checks",
+ {offsetof(struct DBOptions, paranoid_checks), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"skip_log_error_on_recovery",
+ {offsetof(struct DBOptions, skip_log_error_on_recovery),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"skip_stats_update_on_db_open",
+ {offsetof(struct DBOptions, skip_stats_update_on_db_open),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"skip_checking_sst_file_sizes_on_db_open",
+ {offsetof(struct DBOptions, skip_checking_sst_file_sizes_on_db_open),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"new_table_reader_for_compaction_inputs",
+ {offsetof(struct DBOptions, new_table_reader_for_compaction_inputs),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"compaction_readahead_size",
+ {offsetof(struct DBOptions, compaction_readahead_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, compaction_readahead_size)}},
+ {"random_access_max_buffer_size",
+ {offsetof(struct DBOptions, random_access_max_buffer_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
+ {"use_adaptive_mutex",
+ {offsetof(struct DBOptions, use_adaptive_mutex), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"use_fsync",
+ {offsetof(struct DBOptions, use_fsync), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"max_background_jobs",
+ {offsetof(struct DBOptions, max_background_jobs), OptionType::kInt,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, max_background_jobs)}},
+ {"max_background_compactions",
+ {offsetof(struct DBOptions, max_background_compactions),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, max_background_compactions)}},
+ {"base_background_compactions",
+ {offsetof(struct DBOptions, base_background_compactions),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, base_background_compactions)}},
+ {"max_background_flushes",
+ {offsetof(struct DBOptions, max_background_flushes), OptionType::kInt,
+ OptionVerificationType::kNormal, false, 0}},
+ {"max_file_opening_threads",
+ {offsetof(struct DBOptions, max_file_opening_threads),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"max_open_files",
+ {offsetof(struct DBOptions, max_open_files), OptionType::kInt,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, max_open_files)}},
+ {"table_cache_numshardbits",
+ {offsetof(struct DBOptions, table_cache_numshardbits),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"db_write_buffer_size",
+ {offsetof(struct DBOptions, db_write_buffer_size), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"keep_log_file_num",
+ {offsetof(struct DBOptions, keep_log_file_num), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"recycle_log_file_num",
+ {offsetof(struct DBOptions, recycle_log_file_num), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"log_file_time_to_roll",
+ {offsetof(struct DBOptions, log_file_time_to_roll), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"manifest_preallocation_size",
+ {offsetof(struct DBOptions, manifest_preallocation_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
+ {"max_log_file_size",
+ {offsetof(struct DBOptions, max_log_file_size), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"db_log_dir",
+ {offsetof(struct DBOptions, db_log_dir), OptionType::kString,
+ OptionVerificationType::kNormal, false, 0}},
+ {"wal_dir",
+ {offsetof(struct DBOptions, wal_dir), OptionType::kString,
+ OptionVerificationType::kNormal, false, 0}},
+ {"max_subcompactions",
+ {offsetof(struct DBOptions, max_subcompactions), OptionType::kUInt32T,
+ OptionVerificationType::kNormal, false, 0}},
+ {"WAL_size_limit_MB",
+ {offsetof(struct DBOptions, WAL_size_limit_MB), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, false, 0}},
+ {"WAL_ttl_seconds",
+ {offsetof(struct DBOptions, WAL_ttl_seconds), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, false, 0}},
+ {"bytes_per_sync",
+ {offsetof(struct DBOptions, bytes_per_sync), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, bytes_per_sync)}},
+ {"delayed_write_rate",
+ {offsetof(struct DBOptions, delayed_write_rate), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, delayed_write_rate)}},
+ {"delete_obsolete_files_period_micros",
+ {offsetof(struct DBOptions, delete_obsolete_files_period_micros),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions,
+ delete_obsolete_files_period_micros)}},
+ {"max_manifest_file_size",
+ {offsetof(struct DBOptions, max_manifest_file_size),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"max_total_wal_size",
+ {offsetof(struct DBOptions, max_total_wal_size), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, max_total_wal_size)}},
+ {"wal_bytes_per_sync",
+ {offsetof(struct DBOptions, wal_bytes_per_sync), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, wal_bytes_per_sync)}},
+ {"strict_bytes_per_sync",
+ {offsetof(struct DBOptions, strict_bytes_per_sync),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, strict_bytes_per_sync)}},
+ {"stats_dump_period_sec",
+ {offsetof(struct DBOptions, stats_dump_period_sec), OptionType::kUInt,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, stats_dump_period_sec)}},
+ {"stats_persist_period_sec",
+ {offsetof(struct DBOptions, stats_persist_period_sec),
+ OptionType::kUInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, stats_persist_period_sec)}},
+ {"persist_stats_to_disk",
+ {offsetof(struct DBOptions, persist_stats_to_disk),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, persist_stats_to_disk)}},
+ {"stats_history_buffer_size",
+ {offsetof(struct DBOptions, stats_history_buffer_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, stats_history_buffer_size)}},
+ {"fail_if_options_file_error",
+ {offsetof(struct DBOptions, fail_if_options_file_error),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"enable_pipelined_write",
+ {offsetof(struct DBOptions, enable_pipelined_write),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"unordered_write",
+ {offsetof(struct DBOptions, unordered_write), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"allow_concurrent_memtable_write",
+ {offsetof(struct DBOptions, allow_concurrent_memtable_write),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"wal_recovery_mode",
+ {offsetof(struct DBOptions, wal_recovery_mode),
+ OptionType::kWALRecoveryMode, OptionVerificationType::kNormal, false,
+ 0}},
+ {"enable_write_thread_adaptive_yield",
+ {offsetof(struct DBOptions, enable_write_thread_adaptive_yield),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"write_thread_slow_yield_usec",
+ {offsetof(struct DBOptions, write_thread_slow_yield_usec),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"max_write_batch_group_size_bytes",
+ {offsetof(struct DBOptions, max_write_batch_group_size_bytes),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"write_thread_max_yield_usec",
+ {offsetof(struct DBOptions, write_thread_max_yield_usec),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"access_hint_on_compaction_start",
+ {offsetof(struct DBOptions, access_hint_on_compaction_start),
+ OptionType::kAccessHint, OptionVerificationType::kNormal, false, 0}},
+ {"info_log_level",
+ {offsetof(struct DBOptions, info_log_level), OptionType::kInfoLogLevel,
+ OptionVerificationType::kNormal, false, 0}},
+ {"dump_malloc_stats",
+ {offsetof(struct DBOptions, dump_malloc_stats), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"avoid_flush_during_recovery",
+ {offsetof(struct DBOptions, avoid_flush_during_recovery),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"avoid_flush_during_shutdown",
+ {offsetof(struct DBOptions, avoid_flush_during_shutdown),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, avoid_flush_during_shutdown)}},
+ {"writable_file_max_buffer_size",
+ {offsetof(struct DBOptions, writable_file_max_buffer_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableDBOptions, writable_file_max_buffer_size)}},
+ {"allow_ingest_behind",
+ {offsetof(struct DBOptions, allow_ingest_behind), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, allow_ingest_behind)}},
+ {"preserve_deletes",
+ {offsetof(struct DBOptions, preserve_deletes), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, preserve_deletes)}},
+ {"concurrent_prepare", // Deprecated by two_write_queues
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"two_write_queues",
+ {offsetof(struct DBOptions, two_write_queues), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, two_write_queues)}},
+ {"manual_wal_flush",
+ {offsetof(struct DBOptions, manual_wal_flush), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, manual_wal_flush)}},
+ {"seq_per_batch",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"atomic_flush",
+ {offsetof(struct DBOptions, atomic_flush), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, atomic_flush)}},
+ {"avoid_unnecessary_blocking_io",
+ {offsetof(struct DBOptions, avoid_unnecessary_blocking_io),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false,
+ offsetof(struct ImmutableDBOptions, avoid_unnecessary_blocking_io)}},
+ {"write_dbid_to_manifest",
+ {offsetof(struct DBOptions, write_dbid_to_manifest),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"log_readahead_size",
+ {offsetof(struct DBOptions, log_readahead_size), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+};
+
+std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
+ OptionsHelper::block_base_table_index_type_string_map = {
+ {"kBinarySearch", BlockBasedTableOptions::IndexType::kBinarySearch},
+ {"kHashSearch", BlockBasedTableOptions::IndexType::kHashSearch},
+ {"kTwoLevelIndexSearch",
+ BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch},
+ {"kBinarySearchWithFirstKey",
+ BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey}};
+
+std::unordered_map<std::string, BlockBasedTableOptions::DataBlockIndexType>
+ OptionsHelper::block_base_table_data_block_index_type_string_map = {
+ {"kDataBlockBinarySearch",
+ BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinarySearch},
+ {"kDataBlockBinaryAndHash",
+ BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinaryAndHash}};
+
+std::unordered_map<std::string, BlockBasedTableOptions::IndexShorteningMode>
+ OptionsHelper::block_base_table_index_shortening_mode_string_map = {
+ {"kNoShortening",
+ BlockBasedTableOptions::IndexShorteningMode::kNoShortening},
+ {"kShortenSeparators",
+ BlockBasedTableOptions::IndexShorteningMode::kShortenSeparators},
+ {"kShortenSeparatorsAndSuccessor",
+ BlockBasedTableOptions::IndexShorteningMode::
+ kShortenSeparatorsAndSuccessor}};
+
+std::unordered_map<std::string, EncodingType>
+ OptionsHelper::encoding_type_string_map = {{"kPlain", kPlain},
+ {"kPrefix", kPrefix}};
+
+std::unordered_map<std::string, CompactionStyle>
+ OptionsHelper::compaction_style_string_map = {
+ {"kCompactionStyleLevel", kCompactionStyleLevel},
+ {"kCompactionStyleUniversal", kCompactionStyleUniversal},
+ {"kCompactionStyleFIFO", kCompactionStyleFIFO},
+ {"kCompactionStyleNone", kCompactionStyleNone}};
+
+std::unordered_map<std::string, CompactionPri>
+ OptionsHelper::compaction_pri_string_map = {
+ {"kByCompensatedSize", kByCompensatedSize},
+ {"kOldestLargestSeqFirst", kOldestLargestSeqFirst},
+ {"kOldestSmallestSeqFirst", kOldestSmallestSeqFirst},
+ {"kMinOverlappingRatio", kMinOverlappingRatio}};
+
+std::unordered_map<std::string, WALRecoveryMode>
+ OptionsHelper::wal_recovery_mode_string_map = {
+ {"kTolerateCorruptedTailRecords",
+ WALRecoveryMode::kTolerateCorruptedTailRecords},
+ {"kAbsoluteConsistency", WALRecoveryMode::kAbsoluteConsistency},
+ {"kPointInTimeRecovery", WALRecoveryMode::kPointInTimeRecovery},
+ {"kSkipAnyCorruptedRecords",
+ WALRecoveryMode::kSkipAnyCorruptedRecords}};
+
+std::unordered_map<std::string, DBOptions::AccessHint>
+ OptionsHelper::access_hint_string_map = {
+ {"NONE", DBOptions::AccessHint::NONE},
+ {"NORMAL", DBOptions::AccessHint::NORMAL},
+ {"SEQUENTIAL", DBOptions::AccessHint::SEQUENTIAL},
+ {"WILLNEED", DBOptions::AccessHint::WILLNEED}};
+
+std::unordered_map<std::string, InfoLogLevel>
+ OptionsHelper::info_log_level_string_map = {
+ {"DEBUG_LEVEL", InfoLogLevel::DEBUG_LEVEL},
+ {"INFO_LEVEL", InfoLogLevel::INFO_LEVEL},
+ {"WARN_LEVEL", InfoLogLevel::WARN_LEVEL},
+ {"ERROR_LEVEL", InfoLogLevel::ERROR_LEVEL},
+ {"FATAL_LEVEL", InfoLogLevel::FATAL_LEVEL},
+ {"HEADER_LEVEL", InfoLogLevel::HEADER_LEVEL}};
+
+ColumnFamilyOptions OptionsHelper::dummy_cf_options;
+CompactionOptionsFIFO OptionsHelper::dummy_comp_options;
+LRUCacheOptions OptionsHelper::dummy_lru_cache_options;
+CompactionOptionsUniversal OptionsHelper::dummy_comp_options_universal;
+
+// offset_of is used to get the offset of a class data member
+// ex: offset_of(&ColumnFamilyOptions::num_levels)
+// This call will return the offset of num_levels in ColumnFamilyOptions class
+//
+// This is the same as offsetof() but allow us to work with non standard-layout
+// classes and structures
+// refs:
+// http://en.cppreference.com/w/cpp/concept/StandardLayoutType
+// https://gist.github.com/graphitemaster/494f21190bb2c63c5516
+template <typename T1>
+int offset_of(T1 ColumnFamilyOptions::*member) {
+ return int(size_t(&(OptionsHelper::dummy_cf_options.*member)) -
+ size_t(&OptionsHelper::dummy_cf_options));
+}
+template <typename T1>
+int offset_of(T1 AdvancedColumnFamilyOptions::*member) {
+ return int(size_t(&(OptionsHelper::dummy_cf_options.*member)) -
+ size_t(&OptionsHelper::dummy_cf_options));
+}
+template <typename T1>
+int offset_of(T1 CompactionOptionsFIFO::*member) {
+ return int(size_t(&(OptionsHelper::dummy_comp_options.*member)) -
+ size_t(&OptionsHelper::dummy_comp_options));
+}
+template <typename T1>
+int offset_of(T1 LRUCacheOptions::*member) {
+ return int(size_t(&(OptionsHelper::dummy_lru_cache_options.*member)) -
+ size_t(&OptionsHelper::dummy_lru_cache_options));
+}
+template <typename T1>
+int offset_of(T1 CompactionOptionsUniversal::*member) {
+ return int(size_t(&(OptionsHelper::dummy_comp_options_universal.*member)) -
+ size_t(&OptionsHelper::dummy_comp_options_universal));
+}
+
+std::unordered_map<std::string, OptionTypeInfo>
+ OptionsHelper::cf_options_type_info = {
+ /* not yet supported
+ CompressionOptions compression_opts;
+ TablePropertiesCollectorFactories table_properties_collector_factories;
+ typedef std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
+ TablePropertiesCollectorFactories;
+ UpdateStatus (*inplace_callback)(char* existing_value,
+ uint34_t* existing_value_size,
+ Slice delta_value,
+ std::string* merged_value);
+ std::vector<DbPath> cf_paths;
+ */
+ {"report_bg_io_stats",
+ {offset_of(&ColumnFamilyOptions::report_bg_io_stats),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, report_bg_io_stats)}},
+ {"compaction_measure_io_stats",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"disable_auto_compactions",
+ {offset_of(&ColumnFamilyOptions::disable_auto_compactions),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, disable_auto_compactions)}},
+ {"filter_deletes",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"inplace_update_support",
+ {offset_of(&ColumnFamilyOptions::inplace_update_support),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"level_compaction_dynamic_level_bytes",
+ {offset_of(&ColumnFamilyOptions::level_compaction_dynamic_level_bytes),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"optimize_filters_for_hits",
+ {offset_of(&ColumnFamilyOptions::optimize_filters_for_hits),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"paranoid_file_checks",
+ {offset_of(&ColumnFamilyOptions::paranoid_file_checks),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, paranoid_file_checks)}},
+ {"force_consistency_checks",
+ {offset_of(&ColumnFamilyOptions::force_consistency_checks),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"purge_redundant_kvs_while_flush",
+ {offset_of(&ColumnFamilyOptions::purge_redundant_kvs_while_flush),
+ OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}},
+ {"verify_checksums_in_compaction",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"soft_pending_compaction_bytes_limit",
+ {offset_of(&ColumnFamilyOptions::soft_pending_compaction_bytes_limit),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions,
+ soft_pending_compaction_bytes_limit)}},
+ {"hard_pending_compaction_bytes_limit",
+ {offset_of(&ColumnFamilyOptions::hard_pending_compaction_bytes_limit),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions,
+ hard_pending_compaction_bytes_limit)}},
+ {"hard_rate_limit",
+ {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"soft_rate_limit",
+ {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"max_compaction_bytes",
+ {offset_of(&ColumnFamilyOptions::max_compaction_bytes),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, max_compaction_bytes)}},
+ {"expanded_compaction_factor",
+ {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}},
+ {"level0_file_num_compaction_trigger",
+ {offset_of(&ColumnFamilyOptions::level0_file_num_compaction_trigger),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions,
+ level0_file_num_compaction_trigger)}},
+ {"level0_slowdown_writes_trigger",
+ {offset_of(&ColumnFamilyOptions::level0_slowdown_writes_trigger),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, level0_slowdown_writes_trigger)}},
+ {"level0_stop_writes_trigger",
+ {offset_of(&ColumnFamilyOptions::level0_stop_writes_trigger),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, level0_stop_writes_trigger)}},
+ {"max_grandparent_overlap_factor",
+ {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}},
+ {"max_mem_compaction_level",
+ {0, OptionType::kInt, OptionVerificationType::kDeprecated, false, 0}},
+ {"max_write_buffer_number",
+ {offset_of(&ColumnFamilyOptions::max_write_buffer_number),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, max_write_buffer_number)}},
+ {"max_write_buffer_number_to_maintain",
+ {offset_of(&ColumnFamilyOptions::max_write_buffer_number_to_maintain),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"max_write_buffer_size_to_maintain",
+ {offset_of(&ColumnFamilyOptions::max_write_buffer_size_to_maintain),
+ OptionType::kInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"min_write_buffer_number_to_merge",
+ {offset_of(&ColumnFamilyOptions::min_write_buffer_number_to_merge),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"num_levels",
+ {offset_of(&ColumnFamilyOptions::num_levels), OptionType::kInt,
+ OptionVerificationType::kNormal, false, 0}},
+ {"source_compaction_factor",
+ {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}},
+ {"target_file_size_multiplier",
+ {offset_of(&ColumnFamilyOptions::target_file_size_multiplier),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, target_file_size_multiplier)}},
+ {"arena_block_size",
+ {offset_of(&ColumnFamilyOptions::arena_block_size), OptionType::kSizeT,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, arena_block_size)}},
+ {"inplace_update_num_locks",
+ {offset_of(&ColumnFamilyOptions::inplace_update_num_locks),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, inplace_update_num_locks)}},
+ {"max_successive_merges",
+ {offset_of(&ColumnFamilyOptions::max_successive_merges),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, max_successive_merges)}},
+ {"memtable_huge_page_size",
+ {offset_of(&ColumnFamilyOptions::memtable_huge_page_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, memtable_huge_page_size)}},
+ {"memtable_prefix_bloom_huge_page_tlb_size",
+ {0, OptionType::kSizeT, OptionVerificationType::kDeprecated, true, 0}},
+ {"write_buffer_size",
+ {offset_of(&ColumnFamilyOptions::write_buffer_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, write_buffer_size)}},
+ {"bloom_locality",
+ {offset_of(&ColumnFamilyOptions::bloom_locality), OptionType::kUInt32T,
+ OptionVerificationType::kNormal, false, 0}},
+ {"memtable_prefix_bloom_bits",
+ {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"memtable_prefix_bloom_size_ratio",
+ {offset_of(&ColumnFamilyOptions::memtable_prefix_bloom_size_ratio),
+ OptionType::kDouble, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, memtable_prefix_bloom_size_ratio)}},
+ {"memtable_prefix_bloom_probes",
+ {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"memtable_whole_key_filtering",
+ {offset_of(&ColumnFamilyOptions::memtable_whole_key_filtering),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, memtable_whole_key_filtering)}},
+ {"min_partial_merge_operands",
+ {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"max_bytes_for_level_base",
+ {offset_of(&ColumnFamilyOptions::max_bytes_for_level_base),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, max_bytes_for_level_base)}},
+ {"snap_refresh_nanos",
+ {0, OptionType::kUInt64T, OptionVerificationType::kDeprecated, true,
+ 0}},
+ {"max_bytes_for_level_multiplier",
+ {offset_of(&ColumnFamilyOptions::max_bytes_for_level_multiplier),
+ OptionType::kDouble, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, max_bytes_for_level_multiplier)}},
+ {"max_bytes_for_level_multiplier_additional",
+ {offset_of(
+ &ColumnFamilyOptions::max_bytes_for_level_multiplier_additional),
+ OptionType::kVectorInt, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions,
+ max_bytes_for_level_multiplier_additional)}},
+ {"max_sequential_skip_in_iterations",
+ {offset_of(&ColumnFamilyOptions::max_sequential_skip_in_iterations),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions,
+ max_sequential_skip_in_iterations)}},
+ {"target_file_size_base",
+ {offset_of(&ColumnFamilyOptions::target_file_size_base),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, target_file_size_base)}},
+ {"rate_limit_delay_max_milliseconds",
+ {0, OptionType::kUInt, OptionVerificationType::kDeprecated, false, 0}},
+ {"compression",
+ {offset_of(&ColumnFamilyOptions::compression),
+ OptionType::kCompressionType, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, compression)}},
+ {"compression_per_level",
+ {offset_of(&ColumnFamilyOptions::compression_per_level),
+ OptionType::kVectorCompressionType, OptionVerificationType::kNormal,
+ false, 0}},
+ {"bottommost_compression",
+ {offset_of(&ColumnFamilyOptions::bottommost_compression),
+ OptionType::kCompressionType, OptionVerificationType::kNormal, false,
+ 0}},
+ {kNameComparator,
+ {offset_of(&ColumnFamilyOptions::comparator), OptionType::kComparator,
+ OptionVerificationType::kByName, false, 0}},
+ {"prefix_extractor",
+ {offset_of(&ColumnFamilyOptions::prefix_extractor),
+ OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull,
+ true, offsetof(struct MutableCFOptions, prefix_extractor)}},
+ {"memtable_insert_with_hint_prefix_extractor",
+ {offset_of(
+ &ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor),
+ OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull,
+ false, 0}},
+ {"memtable_factory",
+ {offset_of(&ColumnFamilyOptions::memtable_factory),
+ OptionType::kMemTableRepFactory, OptionVerificationType::kByName,
+ false, 0}},
+ {"table_factory",
+ {offset_of(&ColumnFamilyOptions::table_factory),
+ OptionType::kTableFactory, OptionVerificationType::kByName, false,
+ 0}},
+ {"compaction_filter",
+ {offset_of(&ColumnFamilyOptions::compaction_filter),
+ OptionType::kCompactionFilter, OptionVerificationType::kByName, false,
+ 0}},
+ {"compaction_filter_factory",
+ {offset_of(&ColumnFamilyOptions::compaction_filter_factory),
+ OptionType::kCompactionFilterFactory, OptionVerificationType::kByName,
+ false, 0}},
+ {kNameMergeOperator,
+ {offset_of(&ColumnFamilyOptions::merge_operator),
+ OptionType::kMergeOperator,
+ OptionVerificationType::kByNameAllowFromNull, false, 0}},
+ {"compaction_style",
+ {offset_of(&ColumnFamilyOptions::compaction_style),
+ OptionType::kCompactionStyle, OptionVerificationType::kNormal, false,
+ 0}},
+ {"compaction_pri",
+ {offset_of(&ColumnFamilyOptions::compaction_pri),
+ OptionType::kCompactionPri, OptionVerificationType::kNormal, false,
+ 0}},
+ {"compaction_options_fifo",
+ {offset_of(&ColumnFamilyOptions::compaction_options_fifo),
+ OptionType::kCompactionOptionsFIFO, OptionVerificationType::kNormal,
+ true, offsetof(struct MutableCFOptions, compaction_options_fifo)}},
+ {"compaction_options_universal",
+ {offset_of(&ColumnFamilyOptions::compaction_options_universal),
+ OptionType::kCompactionOptionsUniversal,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, compaction_options_universal)}},
+ {"ttl",
+ {offset_of(&ColumnFamilyOptions::ttl), OptionType::kUInt64T,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, ttl)}},
+ {"periodic_compaction_seconds",
+ {offset_of(&ColumnFamilyOptions::periodic_compaction_seconds),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, periodic_compaction_seconds)}},
+ {"sample_for_compression",
+ {offset_of(&ColumnFamilyOptions::sample_for_compression),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct MutableCFOptions, sample_for_compression)}}};
+
+std::unordered_map<std::string, OptionTypeInfo>
+ OptionsHelper::fifo_compaction_options_type_info = {
+ {"max_table_files_size",
+ {offset_of(&CompactionOptionsFIFO::max_table_files_size),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, true,
+ offsetof(struct CompactionOptionsFIFO, max_table_files_size)}},
+ {"ttl",
+ {0, OptionType::kUInt64T,
+ OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"allow_compaction",
+ {offset_of(&CompactionOptionsFIFO::allow_compaction),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct CompactionOptionsFIFO, allow_compaction)}}};
+
+std::unordered_map<std::string, OptionTypeInfo>
+ OptionsHelper::universal_compaction_options_type_info = {
+ {"size_ratio",
+ {offset_of(&CompactionOptionsUniversal::size_ratio), OptionType::kUInt,
+ OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal, size_ratio)}},
+ {"min_merge_width",
+ {offset_of(&CompactionOptionsUniversal::min_merge_width),
+ OptionType::kUInt, OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal, min_merge_width)}},
+ {"max_merge_width",
+ {offset_of(&CompactionOptionsUniversal::max_merge_width),
+ OptionType::kUInt, OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal, max_merge_width)}},
+ {"max_size_amplification_percent",
+ {offset_of(
+ &CompactionOptionsUniversal::max_size_amplification_percent),
+ OptionType::kUInt, OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal,
+ max_size_amplification_percent)}},
+ {"compression_size_percent",
+ {offset_of(&CompactionOptionsUniversal::compression_size_percent),
+ OptionType::kInt, OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal,
+ compression_size_percent)}},
+ {"stop_style",
+ {offset_of(&CompactionOptionsUniversal::stop_style),
+ OptionType::kCompactionStopStyle, OptionVerificationType::kNormal,
+ true, offsetof(class CompactionOptionsUniversal, stop_style)}},
+ {"allow_trivial_move",
+ {offset_of(&CompactionOptionsUniversal::allow_trivial_move),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(class CompactionOptionsUniversal, allow_trivial_move)}}};
+
+std::unordered_map<std::string, CompactionStopStyle>
+ OptionsHelper::compaction_stop_style_string_map = {
+ {"kCompactionStopStyleSimilarSize", kCompactionStopStyleSimilarSize},
+ {"kCompactionStopStyleTotalSize", kCompactionStopStyleTotalSize}};
+
+std::unordered_map<std::string, OptionTypeInfo>
+ OptionsHelper::lru_cache_options_type_info = {
+ {"capacity",
+ {offset_of(&LRUCacheOptions::capacity), OptionType::kSizeT,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct LRUCacheOptions, capacity)}},
+ {"num_shard_bits",
+ {offset_of(&LRUCacheOptions::num_shard_bits), OptionType::kInt,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct LRUCacheOptions, num_shard_bits)}},
+ {"strict_capacity_limit",
+ {offset_of(&LRUCacheOptions::strict_capacity_limit),
+ OptionType::kBoolean, OptionVerificationType::kNormal, true,
+ offsetof(struct LRUCacheOptions, strict_capacity_limit)}},
+ {"high_pri_pool_ratio",
+ {offset_of(&LRUCacheOptions::high_pri_pool_ratio), OptionType::kDouble,
+ OptionVerificationType::kNormal, true,
+ offsetof(struct LRUCacheOptions, high_pri_pool_ratio)}}};
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/options_helper.h b/storage/rocksdb/rocksdb/options/options_helper.h
new file mode 100644
index 00000000..c75fdb1c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_helper.h
@@ -0,0 +1,233 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "options/cf_options.h"
+#include "options/db_options.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "rocksdb/universal_compaction.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
+ const MutableDBOptions& mutable_db_options);
+
+ColumnFamilyOptions BuildColumnFamilyOptions(
+ const ColumnFamilyOptions& ioptions,
+ const MutableCFOptions& mutable_cf_options);
+
+#ifndef ROCKSDB_LITE
+
+Status GetMutableOptionsFromStrings(
+ const MutableCFOptions& base_options,
+ const std::unordered_map<std::string, std::string>& options_map,
+ Logger* info_log, MutableCFOptions* new_options);
+
+Status GetMutableDBOptionsFromStrings(
+ const MutableDBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& options_map,
+ MutableDBOptions* new_options);
+
+Status GetTableFactoryFromMap(
+ const std::string& factory_name,
+ const std::unordered_map<std::string, std::string>& opt_map,
+ std::shared_ptr<TableFactory>* table_factory,
+ bool ignore_unknown_options = false);
+
+enum class OptionType {
+ kBoolean,
+ kInt,
+ kInt32T,
+ kInt64T,
+ kVectorInt,
+ kUInt,
+ kUInt32T,
+ kUInt64T,
+ kSizeT,
+ kString,
+ kDouble,
+ kCompactionStyle,
+ kCompactionPri,
+ kSliceTransform,
+ kCompressionType,
+ kVectorCompressionType,
+ kTableFactory,
+ kComparator,
+ kCompactionFilter,
+ kCompactionFilterFactory,
+ kCompactionOptionsFIFO,
+ kCompactionOptionsUniversal,
+ kCompactionStopStyle,
+ kMergeOperator,
+ kMemTableRepFactory,
+ kBlockBasedTableIndexType,
+ kBlockBasedTableDataBlockIndexType,
+ kBlockBasedTableIndexShorteningMode,
+ kFilterPolicy,
+ kFlushBlockPolicyFactory,
+ kChecksumType,
+ kEncodingType,
+ kWALRecoveryMode,
+ kAccessHint,
+ kInfoLogLevel,
+ kLRUCacheOptions,
+ kEnv,
+ kUnknown,
+};
+
+enum class OptionVerificationType {
+ kNormal,
+ kByName, // The option is pointer typed so we can only verify
+ // based on it's name.
+ kByNameAllowNull, // Same as kByName, but it also allows the case
+ // where one of them is a nullptr.
+ kByNameAllowFromNull, // Same as kByName, but it also allows the case
+ // where the old option is nullptr.
+ kDeprecated // The option is no longer used in rocksdb. The RocksDB
+ // OptionsParser will still accept this option if it
+ // happen to exists in some Options file. However,
+ // the parser will not include it in serialization
+ // and verification processes.
+};
+
+// A struct for storing constant option information such as option name,
+// option type, and offset.
+struct OptionTypeInfo {
+ int offset;
+ OptionType type;
+ OptionVerificationType verification;
+ bool is_mutable;
+ int mutable_offset;
+};
+
+// A helper function that converts "opt_address" to a std::string
+// based on the specified OptionType.
+bool SerializeSingleOptionHelper(const char* opt_address,
+ const OptionType opt_type, std::string* value);
+
+// In addition to its public version defined in rocksdb/convenience.h,
+// this further takes an optional output vector "unsupported_options_names",
+// which stores the name of all the unsupported options specified in "opts_map".
+Status GetDBOptionsFromMapInternal(
+ const DBOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ DBOptions* new_options, bool input_strings_escaped,
+ std::vector<std::string>* unsupported_options_names = nullptr,
+ bool ignore_unknown_options = false);
+
+// In addition to its public version defined in rocksdb/convenience.h,
+// this further takes an optional output vector "unsupported_options_names",
+// which stores the name of all the unsupported options specified in "opts_map".
+Status GetColumnFamilyOptionsFromMapInternal(
+ const ColumnFamilyOptions& base_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ ColumnFamilyOptions* new_options, bool input_strings_escaped,
+ std::vector<std::string>* unsupported_options_names = nullptr,
+ bool ignore_unknown_options = false);
+
+bool ParseSliceTransform(
+ const std::string& value,
+ std::shared_ptr<const SliceTransform>* slice_transform);
+
+extern Status StringToMap(
+ const std::string& opts_str,
+ std::unordered_map<std::string, std::string>* opts_map);
+
+extern bool ParseOptionHelper(char* opt_address, const OptionType& opt_type,
+ const std::string& value);
+#endif // !ROCKSDB_LITE
+
+struct OptionsHelper {
+ static std::map<CompactionStyle, std::string> compaction_style_to_string;
+ static std::map<CompactionPri, std::string> compaction_pri_to_string;
+ static std::map<CompactionStopStyle, std::string>
+ compaction_stop_style_to_string;
+ static std::unordered_map<std::string, ChecksumType> checksum_type_string_map;
+ static std::unordered_map<std::string, CompressionType>
+ compression_type_string_map;
+#ifndef ROCKSDB_LITE
+ static std::unordered_map<std::string, OptionTypeInfo> cf_options_type_info;
+ static std::unordered_map<std::string, OptionTypeInfo>
+ fifo_compaction_options_type_info;
+ static std::unordered_map<std::string, OptionTypeInfo>
+ universal_compaction_options_type_info;
+ static std::unordered_map<std::string, CompactionStopStyle>
+ compaction_stop_style_string_map;
+ static std::unordered_map<std::string, OptionTypeInfo> db_options_type_info;
+ static std::unordered_map<std::string, OptionTypeInfo>
+ lru_cache_options_type_info;
+ static std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
+ block_base_table_index_type_string_map;
+ static std::unordered_map<std::string,
+ BlockBasedTableOptions::DataBlockIndexType>
+ block_base_table_data_block_index_type_string_map;
+ static std::unordered_map<std::string,
+ BlockBasedTableOptions::IndexShorteningMode>
+ block_base_table_index_shortening_mode_string_map;
+ static std::unordered_map<std::string, EncodingType> encoding_type_string_map;
+ static std::unordered_map<std::string, CompactionStyle>
+ compaction_style_string_map;
+ static std::unordered_map<std::string, CompactionPri>
+ compaction_pri_string_map;
+ static std::unordered_map<std::string, WALRecoveryMode>
+ wal_recovery_mode_string_map;
+ static std::unordered_map<std::string, DBOptions::AccessHint>
+ access_hint_string_map;
+ static std::unordered_map<std::string, InfoLogLevel>
+ info_log_level_string_map;
+ static ColumnFamilyOptions dummy_cf_options;
+ static CompactionOptionsFIFO dummy_comp_options;
+ static LRUCacheOptions dummy_lru_cache_options;
+ static CompactionOptionsUniversal dummy_comp_options_universal;
+#endif // !ROCKSDB_LITE
+};
+
+// Some aliasing
+static auto& compaction_style_to_string =
+ OptionsHelper::compaction_style_to_string;
+static auto& compaction_pri_to_string = OptionsHelper::compaction_pri_to_string;
+static auto& compaction_stop_style_to_string =
+ OptionsHelper::compaction_stop_style_to_string;
+static auto& checksum_type_string_map = OptionsHelper::checksum_type_string_map;
+#ifndef ROCKSDB_LITE
+static auto& cf_options_type_info = OptionsHelper::cf_options_type_info;
+static auto& fifo_compaction_options_type_info =
+ OptionsHelper::fifo_compaction_options_type_info;
+static auto& universal_compaction_options_type_info =
+ OptionsHelper::universal_compaction_options_type_info;
+static auto& compaction_stop_style_string_map =
+ OptionsHelper::compaction_stop_style_string_map;
+static auto& db_options_type_info = OptionsHelper::db_options_type_info;
+static auto& lru_cache_options_type_info =
+ OptionsHelper::lru_cache_options_type_info;
+static auto& compression_type_string_map =
+ OptionsHelper::compression_type_string_map;
+static auto& block_base_table_index_type_string_map =
+ OptionsHelper::block_base_table_index_type_string_map;
+static auto& block_base_table_data_block_index_type_string_map =
+ OptionsHelper::block_base_table_data_block_index_type_string_map;
+static auto& block_base_table_index_shortening_mode_string_map =
+ OptionsHelper::block_base_table_index_shortening_mode_string_map;
+static auto& encoding_type_string_map = OptionsHelper::encoding_type_string_map;
+static auto& compaction_style_string_map =
+ OptionsHelper::compaction_style_string_map;
+static auto& compaction_pri_string_map =
+ OptionsHelper::compaction_pri_string_map;
+static auto& wal_recovery_mode_string_map =
+ OptionsHelper::wal_recovery_mode_string_map;
+static auto& access_hint_string_map = OptionsHelper::access_hint_string_map;
+static auto& info_log_level_string_map =
+ OptionsHelper::info_log_level_string_map;
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/options_parser.cc b/storage/rocksdb/rocksdb/options/options_parser.cc
new file mode 100644
index 00000000..e10f932c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_parser.cc
@@ -0,0 +1,839 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "options/options_parser.h"
+
+#include <cmath>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "file/read_write_util.h"
+#include "file/writable_file_writer.h"
+#include "options/options_helper.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/db.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static const std::string option_file_header =
+ "# This is a RocksDB option file.\n"
+ "#\n"
+ "# For detailed file format spec, please refer to the example file\n"
+ "# in examples/rocksdb_option_file_example.ini\n"
+ "#\n"
+ "\n";
+
+Status PersistRocksDBOptions(const DBOptions& db_opt,
+ const std::vector<std::string>& cf_names,
+ const std::vector<ColumnFamilyOptions>& cf_opts,
+ const std::string& file_name, FileSystem* fs) {
+ TEST_SYNC_POINT("PersistRocksDBOptions:start");
+ if (cf_names.size() != cf_opts.size()) {
+ return Status::InvalidArgument(
+ "cf_names.size() and cf_opts.size() must be the same");
+ }
+ std::unique_ptr<FSWritableFile> wf;
+
+ Status s =
+ fs->NewWritableFile(file_name, FileOptions(), &wf, nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+ std::unique_ptr<WritableFileWriter> writable;
+ writable.reset(new WritableFileWriter(std::move(wf), file_name, EnvOptions(),
+ nullptr /* statistics */));
+
+ std::string options_file_content;
+
+ writable->Append(option_file_header + "[" +
+ opt_section_titles[kOptionSectionVersion] +
+ "]\n"
+ " rocksdb_version=" +
+ ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR) +
+ "." + ToString(ROCKSDB_PATCH) + "\n");
+ writable->Append(" options_file_version=" +
+ ToString(ROCKSDB_OPTION_FILE_MAJOR) + "." +
+ ToString(ROCKSDB_OPTION_FILE_MINOR) + "\n");
+ writable->Append("\n[" + opt_section_titles[kOptionSectionDBOptions] +
+ "]\n ");
+
+ s = GetStringFromDBOptions(&options_file_content, db_opt, "\n ");
+ if (!s.ok()) {
+ writable->Close();
+ return s;
+ }
+ writable->Append(options_file_content + "\n");
+
+ for (size_t i = 0; i < cf_opts.size(); ++i) {
+ // CFOptions section
+ writable->Append("\n[" + opt_section_titles[kOptionSectionCFOptions] +
+ " \"" + EscapeOptionString(cf_names[i]) + "\"]\n ");
+ s = GetStringFromColumnFamilyOptions(&options_file_content, cf_opts[i],
+ "\n ");
+ if (!s.ok()) {
+ writable->Close();
+ return s;
+ }
+ writable->Append(options_file_content + "\n");
+ // TableOptions section
+ auto* tf = cf_opts[i].table_factory.get();
+ if (tf != nullptr) {
+ writable->Append("[" + opt_section_titles[kOptionSectionTableOptions] +
+ tf->Name() + " \"" + EscapeOptionString(cf_names[i]) +
+ "\"]\n ");
+ options_file_content.clear();
+ s = tf->GetOptionString(&options_file_content, "\n ");
+ if (!s.ok()) {
+ return s;
+ }
+ writable->Append(options_file_content + "\n");
+ }
+ }
+ writable->Sync(true /* use_fsync */);
+ writable->Close();
+
+ return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
+ db_opt, cf_names, cf_opts, file_name, fs);
+}
+
+RocksDBOptionsParser::RocksDBOptionsParser() { Reset(); }
+
+void RocksDBOptionsParser::Reset() {
+ db_opt_ = DBOptions();
+ db_opt_map_.clear();
+ cf_names_.clear();
+ cf_opts_.clear();
+ cf_opt_maps_.clear();
+ has_version_section_ = false;
+ has_db_options_ = false;
+ has_default_cf_options_ = false;
+ for (int i = 0; i < 3; ++i) {
+ db_version[i] = 0;
+ opt_file_version[i] = 0;
+ }
+}
+
+bool RocksDBOptionsParser::IsSection(const std::string& line) {
+ if (line.size() < 2) {
+ return false;
+ }
+ if (line[0] != '[' || line[line.size() - 1] != ']') {
+ return false;
+ }
+ return true;
+}
+
+Status RocksDBOptionsParser::ParseSection(OptionSection* section,
+ std::string* title,
+ std::string* argument,
+ const std::string& line,
+ const int line_num) {
+ *section = kOptionSectionUnknown;
+ // A section is of the form [<SectionName> "<SectionArg>"], where
+ // "<SectionArg>" is optional.
+ size_t arg_start_pos = line.find("\"");
+ size_t arg_end_pos = line.rfind("\"");
+ // The following if-then check tries to identify whether the input
+ // section has the optional section argument.
+ if (arg_start_pos != std::string::npos && arg_start_pos != arg_end_pos) {
+ *title = TrimAndRemoveComment(line.substr(1, arg_start_pos - 1), true);
+ *argument = UnescapeOptionString(
+ line.substr(arg_start_pos + 1, arg_end_pos - arg_start_pos - 1));
+ } else {
+ *title = TrimAndRemoveComment(line.substr(1, line.size() - 2), true);
+ *argument = "";
+ }
+ for (int i = 0; i < kOptionSectionUnknown; ++i) {
+ if (title->find(opt_section_titles[i]) == 0) {
+ if (i == kOptionSectionVersion || i == kOptionSectionDBOptions ||
+ i == kOptionSectionCFOptions) {
+ if (title->size() == opt_section_titles[i].size()) {
+ // if true, then it indicats equal
+ *section = static_cast<OptionSection>(i);
+ return CheckSection(*section, *argument, line_num);
+ }
+ } else if (i == kOptionSectionTableOptions) {
+ // This type of sections has a sufffix at the end of the
+ // section title
+ if (title->size() > opt_section_titles[i].size()) {
+ *section = static_cast<OptionSection>(i);
+ return CheckSection(*section, *argument, line_num);
+ }
+ }
+ }
+ }
+ return Status::InvalidArgument(std::string("Unknown section ") + line);
+}
+
+Status RocksDBOptionsParser::InvalidArgument(const int line_num,
+ const std::string& message) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionsParser Error] ",
+ message + " (at line " + ToString(line_num) + ")");
+}
+
+Status RocksDBOptionsParser::ParseStatement(std::string* name,
+ std::string* value,
+ const std::string& line,
+ const int line_num) {
+ size_t eq_pos = line.find("=");
+ if (eq_pos == std::string::npos) {
+ return InvalidArgument(line_num, "A valid statement must have a '='.");
+ }
+
+ *name = TrimAndRemoveComment(line.substr(0, eq_pos), true);
+ *value =
+ TrimAndRemoveComment(line.substr(eq_pos + 1, line.size() - eq_pos - 1));
+ if (name->empty()) {
+ return InvalidArgument(line_num,
+ "A valid statement must have a variable name.");
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::Parse(const std::string& file_name, FileSystem* fs,
+ bool ignore_unknown_options,
+ size_t file_readahead_size) {
+ Reset();
+
+ std::unique_ptr<FSSequentialFile> seq_file;
+ Status s = fs->NewSequentialFile(file_name, FileOptions(), &seq_file,
+ nullptr);
+ if (!s.ok()) {
+ return s;
+ }
+
+ SequentialFileReader sf_reader(std::move(seq_file), file_name,
+ file_readahead_size);
+
+ OptionSection section = kOptionSectionUnknown;
+ std::string title;
+ std::string argument;
+ std::unordered_map<std::string, std::string> opt_map;
+ std::istringstream iss;
+ std::string line;
+ bool has_data = true;
+ // we only support single-lined statement.
+ for (int line_num = 1; ReadOneLine(&iss, &sf_reader, &line, &has_data, &s);
+ ++line_num) {
+ if (!s.ok()) {
+ return s;
+ }
+ line = TrimAndRemoveComment(line);
+ if (line.empty()) {
+ continue;
+ }
+ if (IsSection(line)) {
+ s = EndSection(section, title, argument, opt_map, ignore_unknown_options);
+ opt_map.clear();
+ if (!s.ok()) {
+ return s;
+ }
+
+ // If the option file is not generated by a higher minor version,
+ // there shouldn't be any unknown option.
+ if (ignore_unknown_options && section == kOptionSectionVersion) {
+ if (db_version[0] < ROCKSDB_MAJOR || (db_version[0] == ROCKSDB_MAJOR &&
+ db_version[1] <= ROCKSDB_MINOR)) {
+ ignore_unknown_options = false;
+ }
+ }
+
+ s = ParseSection(&section, &title, &argument, line, line_num);
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ std::string name;
+ std::string value;
+ s = ParseStatement(&name, &value, line, line_num);
+ if (!s.ok()) {
+ return s;
+ }
+ opt_map.insert({name, value});
+ }
+ }
+
+ s = EndSection(section, title, argument, opt_map, ignore_unknown_options);
+ opt_map.clear();
+ if (!s.ok()) {
+ return s;
+ }
+ return ValidityCheck();
+}
+
+Status RocksDBOptionsParser::CheckSection(const OptionSection section,
+ const std::string& section_arg,
+ const int line_num) {
+ if (section == kOptionSectionDBOptions) {
+ if (has_db_options_) {
+ return InvalidArgument(
+ line_num,
+ "More than one DBOption section found in the option config file");
+ }
+ has_db_options_ = true;
+ } else if (section == kOptionSectionCFOptions) {
+ bool is_default_cf = (section_arg == kDefaultColumnFamilyName);
+ if (cf_opts_.size() == 0 && !is_default_cf) {
+ return InvalidArgument(
+ line_num,
+ "Default column family must be the first CFOptions section "
+ "in the option config file");
+ } else if (cf_opts_.size() != 0 && is_default_cf) {
+ return InvalidArgument(
+ line_num,
+ "Default column family must be the first CFOptions section "
+ "in the optio/n config file");
+ } else if (GetCFOptions(section_arg) != nullptr) {
+ return InvalidArgument(
+ line_num,
+ "Two identical column families found in option config file");
+ }
+ has_default_cf_options_ |= is_default_cf;
+ } else if (section == kOptionSectionTableOptions) {
+ if (GetCFOptions(section_arg) == nullptr) {
+ return InvalidArgument(
+ line_num, std::string(
+ "Does not find a matched column family name in "
+ "TableOptions section. Column Family Name:") +
+ section_arg);
+ }
+ } else if (section == kOptionSectionVersion) {
+ if (has_version_section_) {
+ return InvalidArgument(
+ line_num,
+ "More than one Version section found in the option config file.");
+ }
+ has_version_section_ = true;
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::ParseVersionNumber(const std::string& ver_name,
+ const std::string& ver_string,
+ const int max_count,
+ int* version) {
+ int version_index = 0;
+ int current_number = 0;
+ int current_digit_count = 0;
+ bool has_dot = false;
+ for (int i = 0; i < max_count; ++i) {
+ version[i] = 0;
+ }
+ constexpr int kBufferSize = 200;
+ char buffer[kBufferSize];
+ for (size_t i = 0; i < ver_string.size(); ++i) {
+ if (ver_string[i] == '.') {
+ if (version_index >= max_count - 1) {
+ snprintf(buffer, sizeof(buffer) - 1,
+ "A valid %s can only contains at most %d dots.",
+ ver_name.c_str(), max_count - 1);
+ return Status::InvalidArgument(buffer);
+ }
+ if (current_digit_count == 0) {
+ snprintf(buffer, sizeof(buffer) - 1,
+ "A valid %s must have at least one digit before each dot.",
+ ver_name.c_str());
+ return Status::InvalidArgument(buffer);
+ }
+ version[version_index++] = current_number;
+ current_number = 0;
+ current_digit_count = 0;
+ has_dot = true;
+ } else if (isdigit(ver_string[i])) {
+ current_number = current_number * 10 + (ver_string[i] - '0');
+ current_digit_count++;
+ } else {
+ snprintf(buffer, sizeof(buffer) - 1,
+ "A valid %s can only contains dots and numbers.",
+ ver_name.c_str());
+ return Status::InvalidArgument(buffer);
+ }
+ }
+ version[version_index] = current_number;
+ if (has_dot && current_digit_count == 0) {
+ snprintf(buffer, sizeof(buffer) - 1,
+ "A valid %s must have at least one digit after each dot.",
+ ver_name.c_str());
+ return Status::InvalidArgument(buffer);
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::EndSection(
+ const OptionSection section, const std::string& section_title,
+ const std::string& section_arg,
+ const std::unordered_map<std::string, std::string>& opt_map,
+ bool ignore_unknown_options) {
+ Status s;
+ if (section == kOptionSectionDBOptions) {
+ s = GetDBOptionsFromMap(DBOptions(), opt_map, &db_opt_, true,
+ ignore_unknown_options);
+ if (!s.ok()) {
+ return s;
+ }
+ db_opt_map_ = opt_map;
+ } else if (section == kOptionSectionCFOptions) {
+ // This condition should be ensured earlier in ParseSection
+ // so we make an assertion here.
+ assert(GetCFOptions(section_arg) == nullptr);
+ cf_names_.emplace_back(section_arg);
+ cf_opts_.emplace_back();
+ s = GetColumnFamilyOptionsFromMap(ColumnFamilyOptions(), opt_map,
+ &cf_opts_.back(), true,
+ ignore_unknown_options);
+ if (!s.ok()) {
+ return s;
+ }
+ // keep the parsed string.
+ cf_opt_maps_.emplace_back(opt_map);
+ } else if (section == kOptionSectionTableOptions) {
+ assert(GetCFOptions(section_arg) != nullptr);
+ auto* cf_opt = GetCFOptionsImpl(section_arg);
+ if (cf_opt == nullptr) {
+ return Status::InvalidArgument(
+ "The specified column family must be defined before the "
+ "TableOptions section:",
+ section_arg);
+ }
+ // Ignore error as table factory deserialization is optional
+ s = GetTableFactoryFromMap(
+ section_title.substr(
+ opt_section_titles[kOptionSectionTableOptions].size()),
+ opt_map, &(cf_opt->table_factory), ignore_unknown_options);
+ if (!s.ok()) {
+ return s;
+ }
+ } else if (section == kOptionSectionVersion) {
+ for (const auto pair : opt_map) {
+ if (pair.first == "rocksdb_version") {
+ s = ParseVersionNumber(pair.first, pair.second, 3, db_version);
+ if (!s.ok()) {
+ return s;
+ }
+ } else if (pair.first == "options_file_version") {
+ s = ParseVersionNumber(pair.first, pair.second, 2, opt_file_version);
+ if (!s.ok()) {
+ return s;
+ }
+ if (opt_file_version[0] < 1) {
+ return Status::InvalidArgument(
+ "A valid options_file_version must be at least 1.");
+ }
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::ValidityCheck() {
+ if (!has_db_options_) {
+ return Status::Corruption(
+ "A RocksDB Option file must have a single DBOptions section");
+ }
+ if (!has_default_cf_options_) {
+ return Status::Corruption(
+ "A RocksDB Option file must have a single CFOptions:default section");
+ }
+
+ return Status::OK();
+}
+
+std::string RocksDBOptionsParser::TrimAndRemoveComment(const std::string& line,
+ bool trim_only) {
+ size_t start = 0;
+ size_t end = line.size();
+
+ // we only support "#" style comment
+ if (!trim_only) {
+ size_t search_pos = 0;
+ while (search_pos < line.size()) {
+ size_t comment_pos = line.find('#', search_pos);
+ if (comment_pos == std::string::npos) {
+ break;
+ }
+ if (comment_pos == 0 || line[comment_pos - 1] != '\\') {
+ end = comment_pos;
+ break;
+ }
+ search_pos = comment_pos + 1;
+ }
+ }
+
+ while (start < end && isspace(line[start]) != 0) {
+ ++start;
+ }
+
+ // start < end implies end > 0.
+ while (start < end && isspace(line[end - 1]) != 0) {
+ --end;
+ }
+
+ if (start < end) {
+ return line.substr(start, end - start);
+ }
+
+ return "";
+}
+
+namespace {
+bool AreEqualDoubles(const double a, const double b) {
+ return (fabs(a - b) < 0.00001);
+}
+} // namespace
+
+bool AreEqualOptions(
+ const char* opt1, const char* opt2, const OptionTypeInfo& type_info,
+ const std::string& opt_name,
+ const std::unordered_map<std::string, std::string>* opt_map) {
+ const char* offset1 = opt1 + type_info.offset;
+ const char* offset2 = opt2 + type_info.offset;
+
+ switch (type_info.type) {
+ case OptionType::kBoolean:
+ return (*reinterpret_cast<const bool*>(offset1) ==
+ *reinterpret_cast<const bool*>(offset2));
+ case OptionType::kInt:
+ return (*reinterpret_cast<const int*>(offset1) ==
+ *reinterpret_cast<const int*>(offset2));
+ case OptionType::kInt32T:
+ return (*reinterpret_cast<const int32_t*>(offset1) ==
+ *reinterpret_cast<const int32_t*>(offset2));
+ case OptionType::kInt64T:
+ {
+ int64_t v1, v2;
+ GetUnaligned(reinterpret_cast<const int64_t*>(offset1), &v1);
+ GetUnaligned(reinterpret_cast<const int64_t*>(offset2), &v2);
+ return (v1 == v2);
+ }
+ case OptionType::kVectorInt:
+ return (*reinterpret_cast<const std::vector<int>*>(offset1) ==
+ *reinterpret_cast<const std::vector<int>*>(offset2));
+ case OptionType::kUInt:
+ return (*reinterpret_cast<const unsigned int*>(offset1) ==
+ *reinterpret_cast<const unsigned int*>(offset2));
+ case OptionType::kUInt32T:
+ return (*reinterpret_cast<const uint32_t*>(offset1) ==
+ *reinterpret_cast<const uint32_t*>(offset2));
+ case OptionType::kUInt64T:
+ {
+ uint64_t v1, v2;
+ GetUnaligned(reinterpret_cast<const uint64_t*>(offset1), &v1);
+ GetUnaligned(reinterpret_cast<const uint64_t*>(offset2), &v2);
+ return (v1 == v2);
+ }
+ case OptionType::kSizeT:
+ {
+ size_t v1, v2;
+ GetUnaligned(reinterpret_cast<const size_t*>(offset1), &v1);
+ GetUnaligned(reinterpret_cast<const size_t*>(offset2), &v2);
+ return (v1 == v2);
+ }
+ case OptionType::kString:
+ return (*reinterpret_cast<const std::string*>(offset1) ==
+ *reinterpret_cast<const std::string*>(offset2));
+ case OptionType::kDouble:
+ return AreEqualDoubles(*reinterpret_cast<const double*>(offset1),
+ *reinterpret_cast<const double*>(offset2));
+ case OptionType::kCompactionStyle:
+ return (*reinterpret_cast<const CompactionStyle*>(offset1) ==
+ *reinterpret_cast<const CompactionStyle*>(offset2));
+ case OptionType::kCompactionPri:
+ return (*reinterpret_cast<const CompactionPri*>(offset1) ==
+ *reinterpret_cast<const CompactionPri*>(offset2));
+ case OptionType::kCompressionType:
+ return (*reinterpret_cast<const CompressionType*>(offset1) ==
+ *reinterpret_cast<const CompressionType*>(offset2));
+ case OptionType::kVectorCompressionType: {
+ const auto* vec1 =
+ reinterpret_cast<const std::vector<CompressionType>*>(offset1);
+ const auto* vec2 =
+ reinterpret_cast<const std::vector<CompressionType>*>(offset2);
+ return (*vec1 == *vec2);
+ }
+ case OptionType::kChecksumType:
+ return (*reinterpret_cast<const ChecksumType*>(offset1) ==
+ *reinterpret_cast<const ChecksumType*>(offset2));
+ case OptionType::kBlockBasedTableIndexType:
+ return (
+ *reinterpret_cast<const BlockBasedTableOptions::IndexType*>(
+ offset1) ==
+ *reinterpret_cast<const BlockBasedTableOptions::IndexType*>(offset2));
+ case OptionType::kBlockBasedTableDataBlockIndexType:
+ return (
+ *reinterpret_cast<const BlockBasedTableOptions::DataBlockIndexType*>(
+ offset1) ==
+ *reinterpret_cast<const BlockBasedTableOptions::DataBlockIndexType*>(
+ offset2));
+ case OptionType::kBlockBasedTableIndexShorteningMode:
+ return (
+ *reinterpret_cast<const BlockBasedTableOptions::IndexShorteningMode*>(
+ offset1) ==
+ *reinterpret_cast<const BlockBasedTableOptions::IndexShorteningMode*>(
+ offset2));
+ case OptionType::kWALRecoveryMode:
+ return (*reinterpret_cast<const WALRecoveryMode*>(offset1) ==
+ *reinterpret_cast<const WALRecoveryMode*>(offset2));
+ case OptionType::kAccessHint:
+ return (*reinterpret_cast<const DBOptions::AccessHint*>(offset1) ==
+ *reinterpret_cast<const DBOptions::AccessHint*>(offset2));
+ case OptionType::kInfoLogLevel:
+ return (*reinterpret_cast<const InfoLogLevel*>(offset1) ==
+ *reinterpret_cast<const InfoLogLevel*>(offset2));
+ case OptionType::kCompactionOptionsFIFO: {
+ CompactionOptionsFIFO lhs =
+ *reinterpret_cast<const CompactionOptionsFIFO*>(offset1);
+ CompactionOptionsFIFO rhs =
+ *reinterpret_cast<const CompactionOptionsFIFO*>(offset2);
+ if (lhs.max_table_files_size == rhs.max_table_files_size &&
+ lhs.allow_compaction == rhs.allow_compaction) {
+ return true;
+ }
+ return false;
+ }
+ case OptionType::kCompactionOptionsUniversal: {
+ CompactionOptionsUniversal lhs =
+ *reinterpret_cast<const CompactionOptionsUniversal*>(offset1);
+ CompactionOptionsUniversal rhs =
+ *reinterpret_cast<const CompactionOptionsUniversal*>(offset2);
+ if (lhs.size_ratio == rhs.size_ratio &&
+ lhs.min_merge_width == rhs.min_merge_width &&
+ lhs.max_merge_width == rhs.max_merge_width &&
+ lhs.max_size_amplification_percent ==
+ rhs.max_size_amplification_percent &&
+ lhs.compression_size_percent == rhs.compression_size_percent &&
+ lhs.stop_style == rhs.stop_style &&
+ lhs.allow_trivial_move == rhs.allow_trivial_move) {
+ return true;
+ }
+ return false;
+ }
+ default:
+ if (type_info.verification == OptionVerificationType::kByName ||
+ type_info.verification ==
+ OptionVerificationType::kByNameAllowFromNull ||
+ type_info.verification == OptionVerificationType::kByNameAllowNull) {
+ std::string value1;
+ bool result =
+ SerializeSingleOptionHelper(offset1, type_info.type, &value1);
+ if (result == false) {
+ return false;
+ }
+ if (opt_map == nullptr) {
+ return true;
+ }
+ auto iter = opt_map->find(opt_name);
+ if (iter == opt_map->end()) {
+ return true;
+ } else {
+ if (type_info.verification ==
+ OptionVerificationType::kByNameAllowNull) {
+ if (iter->second == kNullptrString || value1 == kNullptrString) {
+ return true;
+ }
+ } else if (type_info.verification ==
+ OptionVerificationType::kByNameAllowFromNull) {
+ if (iter->second == kNullptrString) {
+ return true;
+ }
+ }
+ return (value1 == iter->second);
+ }
+ }
+ return false;
+ }
+}
+
+Status RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
+ const DBOptions& db_opt, const std::vector<std::string>& cf_names,
+ const std::vector<ColumnFamilyOptions>& cf_opts,
+ const std::string& file_name, FileSystem* fs,
+ OptionsSanityCheckLevel sanity_check_level, bool ignore_unknown_options) {
+ // We infer option file readhead size from log readahead size.
+ // If it is not given, use 512KB.
+ size_t file_readahead_size = db_opt.log_readahead_size;
+ if (file_readahead_size == 0) {
+ const size_t kDefaultOptionFileReadAheadSize = 512 * 1024;
+ file_readahead_size = kDefaultOptionFileReadAheadSize;
+ }
+
+ RocksDBOptionsParser parser;
+ Status s =
+ parser.Parse(file_name, fs, ignore_unknown_options, file_readahead_size);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Verify DBOptions
+ s = VerifyDBOptions(db_opt, *parser.db_opt(), parser.db_opt_map(),
+ sanity_check_level);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Verify ColumnFamily Name
+ if (cf_names.size() != parser.cf_names()->size()) {
+ if (sanity_check_level >= kSanityLevelLooselyCompatible) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionParser Error] The persisted options does not have "
+ "the same number of column family names as the db instance.");
+ } else if (cf_opts.size() > parser.cf_opts()->size()) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionsParser Error]",
+ "The persisted options file has less number of column family "
+ "names than that of the specified one.");
+ }
+ }
+ for (size_t i = 0; i < cf_names.size(); ++i) {
+ if (cf_names[i] != parser.cf_names()->at(i)) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionParser Error] The persisted options and the db"
+ "instance does not have the same name for column family ",
+ ToString(i));
+ }
+ }
+
+ // Verify Column Family Options
+ if (cf_opts.size() != parser.cf_opts()->size()) {
+ if (sanity_check_level >= kSanityLevelLooselyCompatible) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionsParser Error]",
+ "The persisted options does not have the same number of "
+ "column families as the db instance.");
+ } else if (cf_opts.size() > parser.cf_opts()->size()) {
+ return Status::InvalidArgument(
+ "[RocksDBOptionsParser Error]",
+ "The persisted options file has less number of column families "
+ "than that of the specified number.");
+ }
+ }
+ for (size_t i = 0; i < cf_opts.size(); ++i) {
+ s = VerifyCFOptions(cf_opts[i], parser.cf_opts()->at(i),
+ &(parser.cf_opt_maps()->at(i)), sanity_check_level);
+ if (!s.ok()) {
+ return s;
+ }
+ s = VerifyTableFactory(cf_opts[i].table_factory.get(),
+ parser.cf_opts()->at(i).table_factory.get(),
+ sanity_check_level);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::VerifyDBOptions(
+ const DBOptions& base_opt, const DBOptions& persisted_opt,
+ const std::unordered_map<std::string, std::string>* /*opt_map*/,
+ OptionsSanityCheckLevel sanity_check_level) {
+ for (auto pair : db_options_type_info) {
+ if (pair.second.verification == OptionVerificationType::kDeprecated) {
+ // We skip checking deprecated variables as they might
+ // contain random values since they might not be initialized
+ continue;
+ }
+ if (DBOptionSanityCheckLevel(pair.first) <= sanity_check_level) {
+ if (!AreEqualOptions(reinterpret_cast<const char*>(&base_opt),
+ reinterpret_cast<const char*>(&persisted_opt),
+ pair.second, pair.first, nullptr)) {
+ constexpr size_t kBufferSize = 2048;
+ char buffer[kBufferSize];
+ std::string base_value;
+ std::string persisted_value;
+ SerializeSingleOptionHelper(
+ reinterpret_cast<const char*>(&base_opt) + pair.second.offset,
+ pair.second.type, &base_value);
+ SerializeSingleOptionHelper(
+ reinterpret_cast<const char*>(&persisted_opt) + pair.second.offset,
+ pair.second.type, &persisted_value);
+ snprintf(buffer, sizeof(buffer),
+ "[RocksDBOptionsParser]: "
+ "failed the verification on DBOptions::%s --- "
+ "The specified one is %s while the persisted one is %s.\n",
+ pair.first.c_str(), base_value.c_str(),
+ persisted_value.c_str());
+ return Status::InvalidArgument(Slice(buffer, strlen(buffer)));
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::VerifyCFOptions(
+ const ColumnFamilyOptions& base_opt,
+ const ColumnFamilyOptions& persisted_opt,
+ const std::unordered_map<std::string, std::string>* persisted_opt_map,
+ OptionsSanityCheckLevel sanity_check_level) {
+ for (auto& pair : cf_options_type_info) {
+ if (pair.second.verification == OptionVerificationType::kDeprecated) {
+ // We skip checking deprecated variables as they might
+ // contain random values since they might not be initialized
+ continue;
+ }
+ if (CFOptionSanityCheckLevel(pair.first) <= sanity_check_level) {
+ if (!AreEqualOptions(reinterpret_cast<const char*>(&base_opt),
+ reinterpret_cast<const char*>(&persisted_opt),
+ pair.second, pair.first, persisted_opt_map)) {
+ constexpr size_t kBufferSize = 2048;
+ char buffer[kBufferSize];
+ std::string base_value;
+ std::string persisted_value;
+ SerializeSingleOptionHelper(
+ reinterpret_cast<const char*>(&base_opt) + pair.second.offset,
+ pair.second.type, &base_value);
+ SerializeSingleOptionHelper(
+ reinterpret_cast<const char*>(&persisted_opt) + pair.second.offset,
+ pair.second.type, &persisted_value);
+ snprintf(buffer, sizeof(buffer),
+ "[RocksDBOptionsParser]: "
+ "failed the verification on ColumnFamilyOptions::%s --- "
+ "The specified one is %s while the persisted one is %s.\n",
+ pair.first.c_str(), base_value.c_str(),
+ persisted_value.c_str());
+ return Status::InvalidArgument(Slice(buffer, sizeof(buffer)));
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status RocksDBOptionsParser::VerifyTableFactory(
+ const TableFactory* base_tf, const TableFactory* file_tf,
+ OptionsSanityCheckLevel sanity_check_level) {
+ if (base_tf && file_tf) {
+ if (sanity_check_level > kSanityLevelNone &&
+ std::string(base_tf->Name()) != std::string(file_tf->Name())) {
+ return Status::Corruption(
+ "[RocksDBOptionsParser]: "
+ "failed the verification on TableFactory->Name()");
+ }
+ if (base_tf->Name() == BlockBasedTableFactory::kName) {
+ return VerifyBlockBasedTableFactory(
+ static_cast_with_check<const BlockBasedTableFactory,
+ const TableFactory>(base_tf),
+ static_cast_with_check<const BlockBasedTableFactory,
+ const TableFactory>(file_tf),
+ sanity_check_level);
+ }
+ // TODO(yhchiang): add checks for other table factory types
+ } else {
+ // TODO(yhchiang): further support sanity check here
+ }
+ return Status::OK();
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/options/options_parser.h b/storage/rocksdb/rocksdb/options/options_parser.h
new file mode 100644
index 00000000..a88b806f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_parser.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+#include <vector>
+
+#include "options/options_sanity_check.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "table/block_based/block_based_table_factory.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+#define ROCKSDB_OPTION_FILE_MAJOR 1
+#define ROCKSDB_OPTION_FILE_MINOR 1
+
+enum OptionSection : char {
+ kOptionSectionVersion = 0,
+ kOptionSectionDBOptions,
+ kOptionSectionCFOptions,
+ kOptionSectionTableOptions,
+ kOptionSectionUnknown
+};
+
+static const std::string opt_section_titles[] = {
+ "Version", "DBOptions", "CFOptions", "TableOptions/", "Unknown"};
+
+Status PersistRocksDBOptions(const DBOptions& db_opt,
+ const std::vector<std::string>& cf_names,
+ const std::vector<ColumnFamilyOptions>& cf_opts,
+ const std::string& file_name, FileSystem* fs);
+
+extern bool AreEqualOptions(
+ const char* opt1, const char* opt2, const OptionTypeInfo& type_info,
+ const std::string& opt_name,
+ const std::unordered_map<std::string, std::string>* opt_map);
+
+class RocksDBOptionsParser {
+ public:
+ explicit RocksDBOptionsParser();
+ ~RocksDBOptionsParser() {}
+ void Reset();
+
+ // `file_readahead_size` is used for readahead for the option file.
+ // If 0 is given, a default value will be used.
+ Status Parse(const std::string& file_name, FileSystem* fs,
+ bool ignore_unknown_options, size_t file_readahead_size);
+ static std::string TrimAndRemoveComment(const std::string& line,
+ const bool trim_only = false);
+
+ const DBOptions* db_opt() const { return &db_opt_; }
+ const std::unordered_map<std::string, std::string>* db_opt_map() const {
+ return &db_opt_map_;
+ }
+ const std::vector<ColumnFamilyOptions>* cf_opts() const { return &cf_opts_; }
+ const std::vector<std::string>* cf_names() const { return &cf_names_; }
+ const std::vector<std::unordered_map<std::string, std::string>>* cf_opt_maps()
+ const {
+ return &cf_opt_maps_;
+ }
+
+ const ColumnFamilyOptions* GetCFOptions(const std::string& name) {
+ return GetCFOptionsImpl(name);
+ }
+ size_t NumColumnFamilies() { return cf_opts_.size(); }
+
+ static Status VerifyRocksDBOptionsFromFile(
+ const DBOptions& db_opt, const std::vector<std::string>& cf_names,
+ const std::vector<ColumnFamilyOptions>& cf_opts,
+ const std::string& file_name, FileSystem* fs,
+ OptionsSanityCheckLevel sanity_check_level = kSanityLevelExactMatch,
+ bool ignore_unknown_options = false);
+
+ static Status VerifyDBOptions(
+ const DBOptions& base_opt, const DBOptions& new_opt,
+ const std::unordered_map<std::string, std::string>* new_opt_map = nullptr,
+ OptionsSanityCheckLevel sanity_check_level = kSanityLevelExactMatch);
+
+ static Status VerifyCFOptions(
+ const ColumnFamilyOptions& base_opt, const ColumnFamilyOptions& new_opt,
+ const std::unordered_map<std::string, std::string>* new_opt_map = nullptr,
+ OptionsSanityCheckLevel sanity_check_level = kSanityLevelExactMatch);
+
+ static Status VerifyTableFactory(
+ const TableFactory* base_tf, const TableFactory* file_tf,
+ OptionsSanityCheckLevel sanity_check_level = kSanityLevelExactMatch);
+
+ static Status ExtraParserCheck(const RocksDBOptionsParser& input_parser);
+
+ protected:
+ bool IsSection(const std::string& line);
+ Status ParseSection(OptionSection* section, std::string* title,
+ std::string* argument, const std::string& line,
+ const int line_num);
+
+ Status CheckSection(const OptionSection section,
+ const std::string& section_arg, const int line_num);
+
+ Status ParseStatement(std::string* name, std::string* value,
+ const std::string& line, const int line_num);
+
+ Status EndSection(const OptionSection section, const std::string& title,
+ const std::string& section_arg,
+ const std::unordered_map<std::string, std::string>& opt_map,
+ bool ignore_unknown_options);
+
+ Status ValidityCheck();
+
+ Status InvalidArgument(const int line_num, const std::string& message);
+
+ Status ParseVersionNumber(const std::string& ver_name,
+ const std::string& ver_string, const int max_count,
+ int* version);
+
+ ColumnFamilyOptions* GetCFOptionsImpl(const std::string& name) {
+ assert(cf_names_.size() == cf_opts_.size());
+ for (size_t i = 0; i < cf_names_.size(); ++i) {
+ if (cf_names_[i] == name) {
+ return &cf_opts_[i];
+ }
+ }
+ return nullptr;
+ }
+
+ private:
+ DBOptions db_opt_;
+ std::unordered_map<std::string, std::string> db_opt_map_;
+ std::vector<std::string> cf_names_;
+ std::vector<ColumnFamilyOptions> cf_opts_;
+ std::vector<std::unordered_map<std::string, std::string>> cf_opt_maps_;
+ bool has_version_section_;
+ bool has_db_options_;
+ bool has_default_cf_options_;
+ int db_version[3];
+ int opt_file_version[3];
+};
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/options/options_sanity_check.cc b/storage/rocksdb/rocksdb/options/options_sanity_check.cc
new file mode 100644
index 00000000..47cff31c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_sanity_check.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "options/options_sanity_check.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+OptionsSanityCheckLevel SanityCheckLevelHelper(
+ const std::unordered_map<std::string, OptionsSanityCheckLevel>& smap,
+ const std::string& name) {
+ auto iter = smap.find(name);
+ return iter != smap.end() ? iter->second : kSanityLevelExactMatch;
+}
+}
+
+OptionsSanityCheckLevel DBOptionSanityCheckLevel(
+ const std::string& option_name) {
+ return SanityCheckLevelHelper(sanity_level_db_options, option_name);
+}
+
+OptionsSanityCheckLevel CFOptionSanityCheckLevel(
+ const std::string& option_name) {
+ return SanityCheckLevelHelper(sanity_level_cf_options, option_name);
+}
+
+OptionsSanityCheckLevel BBTOptionSanityCheckLevel(
+ const std::string& option_name) {
+ return SanityCheckLevelHelper(sanity_level_bbt_options, option_name);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/options/options_sanity_check.h b/storage/rocksdb/rocksdb/options/options_sanity_check.h
new file mode 100644
index 00000000..ab8ec6d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_sanity_check.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <unordered_map>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+#ifndef ROCKSDB_LITE
+namespace ROCKSDB_NAMESPACE {
+// This enum defines the RocksDB options sanity level.
+enum OptionsSanityCheckLevel : unsigned char {
+ // Performs no sanity check at all.
+ kSanityLevelNone = 0x00,
+ // Performs minimum check to ensure the RocksDB instance can be
+ // opened without corrupting / mis-interpreting the data.
+ kSanityLevelLooselyCompatible = 0x01,
+ // Perform exact match sanity check.
+ kSanityLevelExactMatch = 0xFF,
+};
+
+// The sanity check level for DB options
+static const std::unordered_map<std::string, OptionsSanityCheckLevel>
+ sanity_level_db_options {};
+
+// The sanity check level for column-family options
+static const std::unordered_map<std::string, OptionsSanityCheckLevel>
+ sanity_level_cf_options = {
+ {"comparator", kSanityLevelLooselyCompatible},
+ {"table_factory", kSanityLevelLooselyCompatible},
+ {"merge_operator", kSanityLevelLooselyCompatible}};
+
+// The sanity check level for block-based table options
+static const std::unordered_map<std::string, OptionsSanityCheckLevel>
+ sanity_level_bbt_options {};
+
+OptionsSanityCheckLevel DBOptionSanityCheckLevel(
+ const std::string& options_name);
+OptionsSanityCheckLevel CFOptionSanityCheckLevel(
+ const std::string& options_name);
+OptionsSanityCheckLevel BBTOptionSanityCheckLevel(
+ const std::string& options_name);
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/options/options_settable_test.cc b/storage/rocksdb/rocksdb/options/options_settable_test.cc
new file mode 100644
index 00000000..c5fee7b3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_settable_test.cc
@@ -0,0 +1,492 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstring>
+
+#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.
+#ifndef ROCKSDB_LITE
+#if defined OS_LINUX || defined OS_WIN
+#ifndef __clang__
+
+class OptionsSettableTest : public testing::Test {
+ public:
+ OptionsSettableTest() {}
+};
+
+const char kSpecialChar = 'z';
+typedef std::vector<std::pair<size_t, size_t>> OffsetGap;
+
+void FillWithSpecialChar(char* start_ptr, size_t total_size,
+ const OffsetGap& blacklist) {
+ size_t offset = 0;
+ for (auto& pair : blacklist) {
+ std::memset(start_ptr + offset, kSpecialChar, pair.first - offset);
+ offset = pair.first + pair.second;
+ }
+ std::memset(start_ptr + offset, kSpecialChar, total_size - offset);
+}
+
+int NumUnsetBytes(char* start_ptr, size_t total_size,
+ const OffsetGap& blacklist) {
+ int total_unset_bytes_base = 0;
+ size_t offset = 0;
+ for (auto& pair : blacklist) {
+ for (char* ptr = start_ptr + offset; ptr < start_ptr + pair.first; ptr++) {
+ if (*ptr == kSpecialChar) {
+ total_unset_bytes_base++;
+ }
+ }
+ offset = pair.first + pair.second;
+ }
+ for (char* ptr = start_ptr + offset; ptr < start_ptr + total_size; ptr++) {
+ if (*ptr == kSpecialChar) {
+ total_unset_bytes_base++;
+ }
+ }
+ return total_unset_bytes_base;
+}
+
+// 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
+// kBbtoBlacklist, and maybe add customized verification for it.
+TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
+ // Items in the form of <offset, size>. Need to be in ascending order
+ // and not overlapping. Need to updated if new pointer-option is added.
+ const OffsetGap kBbtoBlacklist = {
+ {offsetof(struct BlockBasedTableOptions, flush_block_policy_factory),
+ sizeof(std::shared_ptr<FlushBlockPolicyFactory>)},
+ {offsetof(struct BlockBasedTableOptions, block_cache),
+ sizeof(std::shared_ptr<Cache>)},
+ {offsetof(struct BlockBasedTableOptions, persistent_cache),
+ sizeof(std::shared_ptr<PersistentCache>)},
+ {offsetof(struct BlockBasedTableOptions, block_cache_compressed),
+ sizeof(std::shared_ptr<Cache>)},
+ {offsetof(struct BlockBasedTableOptions, filter_policy),
+ sizeof(std::shared_ptr<const FilterPolicy>)},
+ };
+
+ // 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), kBbtoBlacklist);
+ // 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), kBbtoBlacklist);
+ 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), kBbtoBlacklist);
+ // 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),
+ kBbtoBlacklist);
+
+ // Need to update the option string if a new option is added.
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(
+ *bbto,
+ "cache_index_and_filter_blocks=1;"
+ "cache_index_and_filter_blocks_with_high_priority=true;"
+ "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;hash_index_allow_collision=1;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;"
+ "index_block_restart_interval=4;"
+ "filter_policy=bloomfilter:4:true;whole_key_filtering=1;"
+ "format_version=1;"
+ "hash_index_allow_collision=false;"
+ "verify_compression=true;read_amp_bytes_per_bit=0;"
+ "enable_index_compression=false;"
+ "block_align=true",
+ new_bbto));
+
+ ASSERT_EQ(unset_bytes_base,
+ NumUnsetBytes(new_bbto_ptr, sizeof(BlockBasedTableOptions),
+ kBbtoBlacklist));
+
+ ASSERT_TRUE(new_bbto->block_cache.get() != nullptr);
+ ASSERT_TRUE(new_bbto->block_cache_compressed.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
+// kDBOptionsBlacklist, and maybe add customized verification for it.
+TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
+ const OffsetGap kDBOptionsBlacklist = {
+ {offsetof(struct DBOptions, env), sizeof(Env*)},
+ {offsetof(struct DBOptions, file_system),
+ sizeof(std::shared_ptr<FileSystem>)},
+ {offsetof(struct DBOptions, rate_limiter),
+ sizeof(std::shared_ptr<RateLimiter>)},
+ {offsetof(struct DBOptions, sst_file_manager),
+ sizeof(std::shared_ptr<SstFileManager>)},
+ {offsetof(struct DBOptions, info_log), sizeof(std::shared_ptr<Logger>)},
+ {offsetof(struct DBOptions, statistics),
+ sizeof(std::shared_ptr<Statistics>)},
+ {offsetof(struct DBOptions, db_paths), sizeof(std::vector<DbPath>)},
+ {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<WriteBufferManager>)},
+ {offsetof(struct DBOptions, listeners),
+ sizeof(std::vector<std::shared_ptr<EventListener>>)},
+ {offsetof(struct DBOptions, row_cache), sizeof(std::shared_ptr<Cache>)},
+ {offsetof(struct DBOptions, wal_filter), sizeof(const WalFilter*)},
+ {offsetof(struct DBOptions, sst_file_checksum_func),
+ sizeof(std::shared_ptr<FileChecksumFunc>)},
+ };
+
+ 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), kDBOptionsBlacklist);
+ // 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), kDBOptionsBlacklist);
+ ASSERT_GT(unset_bytes_base, 0);
+ options->~DBOptions();
+
+ options = new (options_ptr) DBOptions();
+ FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
+
+ char* new_options_ptr = new char[sizeof(DBOptions)];
+ DBOptions* new_options = new (new_options_ptr) DBOptions();
+ FillWithSpecialChar(new_options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
+
+ // Need to update the option string if a new option is added.
+ ASSERT_OK(
+ GetDBOptionsFromString(*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;"
+ "base_background_compactions=3;"
+ "max_background_compactions=33;"
+ "use_fsync=true;"
+ "use_adaptive_mutex=false;"
+ "max_total_wal_size=4295005604;"
+ "compaction_readahead_size=0;"
+ "new_table_reader_for_compaction_inputs=false;"
+ "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;"
+ "skip_log_error_on_recovery=true;"
+ "writable_file_max_buffer_size=1048576;"
+ "paranoid_checks=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;"
+ "preserve_deletes=false;"
+ "concurrent_prepare=false;"
+ "two_write_queues=false;"
+ "manual_wal_flush=false;"
+ "seq_per_batch=false;"
+ "atomic_flush=false;"
+ "avoid_unnecessary_blocking_io=false;"
+ "log_readahead_size=0;"
+ "write_dbid_to_manifest=false",
+ new_options));
+
+ ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions),
+ kDBOptionsBlacklist));
+
+ options->~DBOptions();
+ new_options->~DBOptions();
+
+ delete[] options_ptr;
+ delete[] new_options_ptr;
+}
+
+template <typename T1, typename T2>
+inline int offset_of(T1 T2::*member) {
+ static T2 obj;
+ return int(size_t(&(obj.*member)) - size_t(&obj));
+}
+
+// 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
+// kColumnFamilyOptionsBlacklist, and maybe add customized verification
+// for it.
+TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
+ // options in the blacklist need to appear in the same order as in
+ // ColumnFamilyOptions.
+ const OffsetGap kColumnFamilyOptionsBlacklist = {
+ {offset_of(&ColumnFamilyOptions::inplace_callback),
+ sizeof(UpdateStatus(*)(char*, uint32_t*, Slice, std::string*))},
+ {offset_of(
+ &ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor),
+ sizeof(std::shared_ptr<const SliceTransform>)},
+ {offset_of(&ColumnFamilyOptions::compression_per_level),
+ sizeof(std::vector<CompressionType>)},
+ {offset_of(
+ &ColumnFamilyOptions::max_bytes_for_level_multiplier_additional),
+ sizeof(std::vector<int>)},
+ {offset_of(&ColumnFamilyOptions::memtable_factory),
+ sizeof(std::shared_ptr<MemTableRepFactory>)},
+ {offset_of(&ColumnFamilyOptions::table_properties_collector_factories),
+ sizeof(ColumnFamilyOptions::TablePropertiesCollectorFactories)},
+ {offset_of(&ColumnFamilyOptions::comparator), sizeof(Comparator*)},
+ {offset_of(&ColumnFamilyOptions::merge_operator),
+ sizeof(std::shared_ptr<MergeOperator>)},
+ {offset_of(&ColumnFamilyOptions::compaction_filter),
+ sizeof(const CompactionFilter*)},
+ {offset_of(&ColumnFamilyOptions::compaction_filter_factory),
+ sizeof(std::shared_ptr<CompactionFilterFactory>)},
+ {offset_of(&ColumnFamilyOptions::prefix_extractor),
+ sizeof(std::shared_ptr<const SliceTransform>)},
+ {offset_of(&ColumnFamilyOptions::snap_refresh_nanos), sizeof(uint64_t)},
+ {offset_of(&ColumnFamilyOptions::table_factory),
+ sizeof(std::shared_ptr<TableFactory>)},
+ {offset_of(&ColumnFamilyOptions::cf_paths), sizeof(std::vector<DbPath>)},
+ {offset_of(&ColumnFamilyOptions::compaction_thread_limiter),
+ sizeof(std::shared_ptr<ConcurrentTaskLimiter>)},
+ };
+
+ 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.
+ ColumnFamilyOptions* options = new (options_ptr) ColumnFamilyOptions();
+ FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
+ kColumnFamilyOptionsBlacklist);
+ // 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 = ColumnFamilyOptions();
+
+ // Deprecatd option which is not initialized. Need to set it to avoid
+ // Valgrind error
+ options->max_mem_compaction_level = 0;
+
+ int unset_bytes_base = NumUnsetBytes(options_ptr, sizeof(ColumnFamilyOptions),
+ kColumnFamilyOptionsBlacklist);
+ ASSERT_GT(unset_bytes_base, 0);
+ options->~ColumnFamilyOptions();
+
+ options = new (options_ptr) ColumnFamilyOptions();
+ FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
+ kColumnFamilyOptionsBlacklist);
+
+ // Following options are not settable through
+ // GetColumnFamilyOptionsFromString():
+ options->rate_limit_delay_max_milliseconds = 33;
+ options->compaction_options_universal = CompactionOptionsUniversal();
+ options->compression_opts = CompressionOptions();
+ options->bottommost_compression_opts = CompressionOptions();
+ options->hard_rate_limit = 0;
+ options->soft_rate_limit = 0;
+ options->purge_redundant_kvs_while_flush = false;
+ options->max_mem_compaction_level = 0;
+ options->compaction_filter = nullptr;
+
+ char* new_options_ptr = new char[sizeof(ColumnFamilyOptions)];
+ ColumnFamilyOptions* new_options =
+ new (new_options_ptr) ColumnFamilyOptions();
+ FillWithSpecialChar(new_options_ptr, sizeof(ColumnFamilyOptions),
+ kColumnFamilyOptionsBlacklist);
+
+ // Need to update the option string if a new option is added.
+ ASSERT_OK(GetColumnFamilyOptionsFromString(
+ *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;"
+ "max_bytes_for_level_multiplier=60;"
+ "memtable_factory=SkipListFactory;"
+ "compression=kNoCompression;"
+ "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_rate_limit=530.615385;"
+ "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;"
+ "paranoid_file_checks=true;"
+ "force_consistency_checks=true;"
+ "inplace_update_num_locks=7429;"
+ "optimize_filters_for_hits=false;"
+ "level_compaction_dynamic_level_bytes=false;"
+ "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;"
+ "compaction_options_fifo={max_table_files_size=3;allow_"
+ "compaction=false;};",
+ new_options));
+
+ ASSERT_EQ(unset_bytes_base,
+ NumUnsetBytes(new_options_ptr, sizeof(ColumnFamilyOptions),
+ kColumnFamilyOptionsBlacklist));
+
+ options->~ColumnFamilyOptions();
+ new_options->~ColumnFamilyOptions();
+
+ delete[] options_ptr;
+ delete[] new_options_ptr;
+}
+#endif // !__clang__
+#endif // OS_LINUX || OS_WIN
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+#ifdef GFLAGS
+ ParseCommandLineFlags(&argc, &argv, true);
+#endif // GFLAGS
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/options/options_test.cc b/storage/rocksdb/rocksdb/options/options_test.cc
new file mode 100644
index 00000000..9ff99532
--- /dev/null
+++ b/storage/rocksdb/rocksdb/options/options_test.cc
@@ -0,0 +1,2004 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cctype>
+#include <cinttypes>
+#include <cstring>
+#include <unordered_map>
+
+#include "cache/lru_cache.h"
+#include "cache/sharded_cache.h"
+#include "options/options_helper.h"
+#include "options/options_parser.h"
+#include "options/options_sanity_check.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/utilities/leveldb_options.h"
+#include "rocksdb/utilities/object_registry.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"
+
+#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 {};
+
+#ifndef ROCKSDB_LITE // GetOptionsFromMap is not supported in ROCKSDB_LITE
+TEST_F(OptionsTest, GetOptionsFromMapTest) {
+ std::unordered_map<std::string, std::string> 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:true"},
+ {"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"},
+ {"soft_rate_limit", "1.1"},
+ {"hard_rate_limit", "2.1"},
+ {"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"},
+ {"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"},
+ {"optimize_filters_for_hits", "true"},
+ };
+
+ std::unordered_map<std::string, std::string> db_options_map = {
+ {"create_if_missing", "false"},
+ {"create_missing_column_families", "true"},
+ {"error_if_exists", "false"},
+ {"paranoid_checks", "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"},
+ {"new_table_reader_for_compaction_inputs", "true"},
+ {"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"},
+ };
+
+ ColumnFamilyOptions base_cf_opt;
+ ColumnFamilyOptions new_cf_opt;
+ ASSERT_OK(GetColumnFamilyOptionsFromMap(
+ 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.enabled, true);
+ 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.enabled, true);
+ 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<uint64_t>(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<uint64_t>(23));
+ ASSERT_EQ(new_cf_opt.max_sequential_skip_in_iterations,
+ static_cast<uint64_t>(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(std::string(new_cf_opt.prefix_extractor->Name()),
+ "rocksdb.FixedPrefix.31");
+
+ cf_options_map["write_buffer_size"] = "hello";
+ ASSERT_NOK(GetColumnFamilyOptionsFromMap(
+ base_cf_opt, cf_options_map, &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ cf_options_map["write_buffer_size"] = "1";
+ ASSERT_OK(GetColumnFamilyOptionsFromMap(
+ base_cf_opt, cf_options_map, &new_cf_opt));
+
+ cf_options_map["unknown_option"] = "1";
+ ASSERT_NOK(GetColumnFamilyOptionsFromMap(
+ base_cf_opt, cf_options_map, &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ ASSERT_OK(GetColumnFamilyOptionsFromMap(base_cf_opt, cf_options_map,
+ &new_cf_opt,
+ false, /* input_strings_escaped */
+ true /* ignore_unknown_options */));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
+ base_cf_opt, new_cf_opt, nullptr, /* new_opt_map */
+ kSanityLevelLooselyCompatible /* from CheckOptionsCompatibility*/));
+ ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions(
+ base_cf_opt, new_cf_opt, nullptr, /* new_opt_map */
+ kSanityLevelExactMatch /* default for VerifyCFOptions */));
+
+ DBOptions base_db_opt;
+ DBOptions new_db_opt;
+ ASSERT_OK(GetDBOptionsFromMap(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.max_open_files, 32);
+ ASSERT_EQ(new_db_opt.max_total_wal_size, static_cast<uint64_t>(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<uint64_t>(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<uint64_t>(40));
+ ASSERT_EQ(new_db_opt.table_cache_numshardbits, 41);
+ ASSERT_EQ(new_db_opt.WAL_ttl_seconds, static_cast<uint64_t>(43));
+ ASSERT_EQ(new_db_opt.WAL_size_limit_MB, static_cast<uint64_t>(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.skip_log_error_on_recovery, false);
+ 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.new_table_reader_for_compaction_inputs, true);
+ 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<uint64_t>(47));
+ ASSERT_EQ(new_db_opt.wal_bytes_per_sync, static_cast<uint64_t>(48));
+ ASSERT_EQ(new_db_opt.strict_bytes_per_sync, true);
+
+ db_options_map["max_open_files"] = "hello";
+ ASSERT_NOK(GetDBOptionsFromMap(base_db_opt, db_options_map, &new_db_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(base_db_opt, new_db_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(
+ base_db_opt, new_db_opt, nullptr, /* new_opt_map */
+ kSanityLevelLooselyCompatible /* from CheckOptionsCompatibility */));
+
+ // unknow options should fail parsing without ignore_unknown_options = true
+ db_options_map["unknown_db_option"] = "1";
+ ASSERT_NOK(GetDBOptionsFromMap(base_db_opt, db_options_map, &new_db_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(base_db_opt, new_db_opt));
+
+ ASSERT_OK(GetDBOptionsFromMap(base_db_opt, db_options_map, &new_db_opt,
+ false, /* input_strings_escaped */
+ true /* ignore_unknown_options */));
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(
+ base_db_opt, new_db_opt, nullptr, /* new_opt_map */
+ kSanityLevelLooselyCompatible /* from CheckOptionsCompatibility */));
+ ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(
+ base_db_opt, new_db_opt, nullptr, /* new_opt_mat */
+ kSanityLevelExactMatch /* default for VerifyDBOptions */));
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // GetColumnFamilyOptionsFromString is not supported in
+ // ROCKSDB_LITE
+TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) {
+ ColumnFamilyOptions base_cf_opt;
+ ColumnFamilyOptions new_cf_opt;
+ base_cf_opt.table_factory.reset();
+ ASSERT_OK(GetColumnFamilyOptionsFromString(base_cf_opt, "", &new_cf_opt));
+ ASSERT_OK(GetColumnFamilyOptionsFromString(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(base_cf_opt,
+ "write_buffer_size=6;", &new_cf_opt));
+ ASSERT_EQ(new_cf_opt.write_buffer_size, 6U);
+ ASSERT_OK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ " write_buffer_size = 7 ", &new_cf_opt));
+ ASSERT_EQ(new_cf_opt.write_buffer_size, 7U);
+ ASSERT_OK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ " write_buffer_size = 8 ; ", &new_cf_opt));
+ ASSERT_EQ(new_cf_opt.write_buffer_size, 8U);
+ ASSERT_OK(GetColumnFamilyOptionsFromString(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(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(base_cf_opt,
+ "write_buffer_size=13;max_write_buffer_number_=14;",
+ &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ // Comparator from object registry
+ std::string kCompName = "reverse_comp";
+ ObjectLibrary::Default()->Register<const Comparator>(
+ kCompName,
+ [](const std::string& /*name*/,
+ std::unique_ptr<const Comparator>* /*guard*/,
+ std::string* /* errmsg */) { return ReverseBytewiseComparator(); });
+
+ ASSERT_OK(GetColumnFamilyOptionsFromString(
+ base_cf_opt, "comparator=" + kCompName + ";", &new_cf_opt));
+ ASSERT_EQ(new_cf_opt.comparator, ReverseBytewiseComparator());
+
+ // MergeOperator from object registry
+ std::unique_ptr<BytesXOROperator> bxo(new BytesXOROperator());
+ std::string kMoName = bxo->Name();
+ ObjectLibrary::Default()->Register<MergeOperator>(
+ kMoName,
+ [](const std::string& /*name*/, std::unique_ptr<MergeOperator>* guard,
+ std::string* /* errmsg */) {
+ guard->reset(new BytesXOROperator());
+ return guard->get();
+ });
+
+ ASSERT_OK(GetColumnFamilyOptionsFromString(
+ 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(base_cf_opt,
+ "write_buffer_size=13;max_write_buffer_number;", &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ // Error Paring value
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ "write_buffer_size=13;max_write_buffer_number=;", &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ // Missing option name
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ "write_buffer_size=13; =100;", &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(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(
+ 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(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(
+ 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);
+ std::string prefix_name(new_cf_opt.prefix_extractor->Name());
+ ASSERT_EQ(prefix_name, "rocksdb.CappedPrefix.8");
+
+ // Units (t)
+ ASSERT_OK(GetColumnFamilyOptionsFromString(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(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(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(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(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(base_cf_opt, new_cf_opt));
+
+ // Unexpected chars after closing curly brace
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(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(base_cf_opt, new_cf_opt));
+
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(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(base_cf_opt, new_cf_opt));
+
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(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(base_cf_opt, new_cf_opt));
+
+ // Invalid block based table option
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(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(base_cf_opt, new_cf_opt));
+
+ ASSERT_OK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ "optimize_filters_for_hits=true",
+ &new_cf_opt));
+ ASSERT_OK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ "optimize_filters_for_hits=false",
+ &new_cf_opt));
+
+ ASSERT_NOK(GetColumnFamilyOptionsFromString(base_cf_opt,
+ "optimize_filters_for_hits=junk",
+ &new_cf_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt));
+
+ // Nested plain table options
+ // Empty
+ ASSERT_OK(GetColumnFamilyOptionsFromString(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(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(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");
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // GetBlockBasedTableOptionsFromString is not supported
+TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
+ BlockBasedTableOptions table_opt;
+ BlockBasedTableOptions new_opt;
+ // make sure default values are overwritten by something else
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(
+ table_opt,
+ "cache_index_and_filter_blocks=1;index_type=kHashSearch;"
+ "checksum=kxxHash;hash_index_allow_collision=1;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.hash_index_allow_collision);
+ ASSERT_TRUE(new_opt.no_block_cache);
+ ASSERT_TRUE(new_opt.block_cache != nullptr);
+ ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL);
+ ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
+ ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 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<const BloomFilterPolicy&>(*new_opt.filter_policy);
+ EXPECT_EQ(bfp.GetMillibitsPerKey(), 4567);
+ EXPECT_EQ(bfp.GetWholeBitsPerKey(), 5);
+
+ // unknown option
+ ASSERT_NOK(GetBlockBasedTableOptionsFromString(table_opt,
+ "cache_index_and_filter_blocks=1;index_type=kBinarySearch;"
+ "bad_option=1",
+ &new_opt));
+ ASSERT_EQ(static_cast<bool>(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(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(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(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);
+
+ // unrecognized filter policy config
+ ASSERT_NOK(GetBlockBasedTableOptionsFromString(table_opt,
+ "cache_index_and_filter_blocks=1;"
+ "filter_policy=bloomfilter:4",
+ &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);
+
+ // Check block cache options are overwritten when specified
+ // in new format as a struct.
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(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<ShardedCache>(
+ new_opt.block_cache)->GetNumShardBits(), 4);
+ ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
+ new_opt.block_cache)->GetHighPriPoolRatio(), 0.5);
+ ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
+ ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 1024UL*1024UL);
+ ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
+ new_opt.block_cache_compressed)->GetNumShardBits(), 4);
+ ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), true);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
+ new_opt.block_cache_compressed)->GetHighPriPoolRatio(),
+ 0.5);
+
+ // Set only block cache capacity. Check other values are
+ // reset to default values.
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(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<ShardedCache>(
+ 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<LRUCache>(new_opt.block_cache)
+ ->GetHighPriPoolRatio(),
+ 0.5);
+ ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
+ ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 2*1024UL*1024UL);
+ // Default values
+ ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
+ new_opt.block_cache_compressed)->GetNumShardBits(),
+ GetDefaultCacheShardBits(
+ new_opt.block_cache_compressed->GetCapacity()));
+ ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
+ ->GetHighPriPoolRatio(),
+ 0.5);
+
+ // Set couple of block cache options.
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(
+ 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<ShardedCache>(
+ new_opt.block_cache)->GetNumShardBits(), 5);
+ ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
+ new_opt.block_cache)->GetHighPriPoolRatio(), 0.5);
+ ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
+ ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 0);
+ ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
+ new_opt.block_cache_compressed)->GetNumShardBits(), 5);
+ ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
+ ->GetHighPriPoolRatio(),
+ 0.0);
+
+ // Set couple of block cache options.
+ ASSERT_OK(GetBlockBasedTableOptionsFromString(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<ShardedCache>(
+ new_opt.block_cache)->GetNumShardBits(), 4);
+ ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache)
+ ->GetHighPriPoolRatio(),
+ 0.5);
+ ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
+ ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 1024UL*1024UL);
+ ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
+ new_opt.block_cache_compressed)->GetNumShardBits(), 4);
+ ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), true);
+ ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
+ ->GetHighPriPoolRatio(),
+ 0.5);
+}
+#endif // !ROCKSDB_LITE
+
+
+#ifndef ROCKSDB_LITE // GetPlainTableOptionsFromString is not supported
+TEST_F(OptionsTest, GetPlainTableOptionsFromString) {
+ PlainTableOptions table_opt;
+ PlainTableOptions new_opt;
+ // make sure default values are overwritten by something else
+ ASSERT_OK(GetPlainTableOptionsFromString(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
+ ASSERT_NOK(GetPlainTableOptionsFromString(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(table_opt,
+ "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;"
+ "encoding_type=kPrefixXX",
+ &new_opt));
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // GetMemTableRepFactoryFromString is not supported
+TEST_F(OptionsTest, GetMemTableRepFactoryFromString) {
+ std::unique_ptr<MemTableRepFactory> new_mem_factory = nullptr;
+
+ ASSERT_OK(GetMemTableRepFactoryFromString("skip_list", &new_mem_factory));
+ ASSERT_OK(GetMemTableRepFactoryFromString("skip_list:16", &new_mem_factory));
+ ASSERT_EQ(std::string(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_EQ(std::string(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));
+}
+#endif // !ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // GetOptionsFromString is not supported in RocksDB Lite
+TEST_F(OptionsTest, 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.
+ const static char* kCustomEnvName = "CustomEnv";
+ class CustomEnv : public EnvWrapper {
+ public:
+ explicit CustomEnv(Env* _target) : EnvWrapper(_target) {}
+ };
+
+ ObjectLibrary::Default()->Register<Env>(
+ kCustomEnvName,
+ [](const std::string& /*name*/, std::unique_ptr<Env>* /*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=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.enabled, false);
+ 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.enabled, false);
+ ASSERT_EQ(new_options.write_buffer_size, 10U);
+ ASSERT_EQ(new_options.max_write_buffer_number, 16);
+ BlockBasedTableOptions new_block_based_table_options =
+ dynamic_cast<BlockBasedTableFactory*>(new_options.table_factory.get())
+ ->table_options();
+ 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::LoadEnv(kCustomEnvName, &newEnv));
+ ASSERT_EQ(newEnv, new_options.env);
+}
+
+TEST_F(OptionsTest, 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
+ ASSERT_OK(GetDBOptionsFromString(DBOptions(), base_options_file_content,
+ &new_options));
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(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;
+
+ 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(base_db_opts, new_db_opts));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opts, new_cf_opts));
+ delete new_cf_opts.compaction_filter;
+}
+
+TEST_F(OptionsTest, 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
+ ASSERT_OK(GetColumnFamilyOptionsFromString(
+ ColumnFamilyOptions(), base_options_file_content, &new_opt));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_opt, new_opt));
+ if (base_opt.compaction_filter) {
+ delete base_opt.compaction_filter;
+ }
+}
+
+#endif // !ROCKSDB_LITE
+
+Status StringToMap(
+ const std::string& opts_str,
+ std::unordered_map<std::string, std::string>* opts_map);
+
+#ifndef ROCKSDB_LITE // StringToMap is not supported in ROCKSDB_LITE
+TEST_F(OptionsTest, StringToMapTest) {
+ std::unordered_map<std::string, std::string> 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));
+}
+#endif // ROCKSDB_LITE
+
+#ifndef ROCKSDB_LITE // StringToMap is not supported in ROCKSDB_LITE
+TEST_F(OptionsTest, StringToMapRandomTest) {
+ std::unordered_map<std::string, std::string> opts_map;
+ // Make sure segfault is not hit by semi-random strings
+
+ std::vector<std::string> 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<size_t>(
+ rnd.Uniform(static_cast<int>(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<char> 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<size_t>(
+ rnd.Uniform(static_cast<int>(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<CompressionType>(-10)));
+}
+#endif // !ROCKSDB_LITE
+
+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<TableFactory> tb_guard = converted_opt.table_factory;
+ BlockBasedTableFactory* table_factory =
+ dynamic_cast<BlockBasedTableFactory*>(converted_opt.table_factory.get());
+
+ ASSERT_TRUE(table_factory != nullptr);
+
+ const BlockBasedTableOptions table_opt = table_factory->table_options();
+
+ 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);
+}
+
+#ifndef ROCKSDB_LITE
+class OptionsParserTest : public testing::Test {
+ public:
+ OptionsParserTest() {
+ env_.reset(new test::StringEnv(Env::Default()));
+ fs_.reset(new LegacyFileSystemWrapper(env_.get()));
+ }
+
+ protected:
+ std::unique_ptr<test::StringEnv> env_;
+ std::unique_ptr<LegacyFileSystemWrapper> 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";
+ env_->WriteToNewFile(kTestFileName, options_file_content);
+ RocksDBOptionsParser parser;
+ ASSERT_OK(
+ parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */));
+
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(*parser.db_opt(), db_opt));
+ ASSERT_EQ(parser.NumColumnFamilies(), 1U);
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
+ *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";
+ env_->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";
+ env_->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";
+ env_->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";
+ env_->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";
+ env_->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";
+ env_->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 =
+ ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR) + ".0";
+ } else if (case_id == 1) {
+ // higher minor version
+ should_ignore = true;
+ version_string =
+ ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR + 1) + ".0";
+ } else if (case_id == 2) {
+ // higher major version.
+ should_ignore = true;
+ version_string = ToString(ROCKSDB_MAJOR + 1) + ".0.0";
+ } else if (case_id == 3) {
+ // lower minor version
+#if ROCKSDB_MINOR == 0
+ continue;
+#else
+ version_string =
+ ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR - 1) + ".0";
+ should_ignore = false;
+#endif
+ } else {
+ // lower major version
+ should_ignore = false;
+ version_string =
+ ToString(ROCKSDB_MAJOR - 1) + "." + ToString(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";
+ env_->DeleteFile(kTestFileName);
+ env_->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<std::string> 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();
+ env_->WriteToNewFile(iv, buffer);
+ ASSERT_NOK(parser.Parse(iv, fs_.get(), false, 0 /* readahead_size */));
+ }
+
+ const std::vector<std::string> 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();
+ env_->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<std::string, std::string>* new_cf_opt_map) {
+ std::string name_buffer;
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(*base_cf_opt, *new_cf_opt,
+ new_cf_opt_map));
+
+ // change the name of merge operator back-and-forth
+ {
+ auto* merge_operator = dynamic_cast<test::ChanglingMergeOperator*>(
+ base_cf_opt->merge_operator.get());
+ 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(
+ *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(*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 =
+ dynamic_cast<test::ChanglingCompactionFilterFactory*>(
+ base_cf_opt->compaction_filter_factory.get());
+ 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(
+ *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(*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(
+ *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(*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(
+ *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(*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(
+ *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(*base_cf_opt, *new_cf_opt,
+ new_cf_opt_map));
+ }
+ }
+}
+
+TEST_F(OptionsParserTest, Readahead) {
+ DBOptions base_db_opt;
+ std::vector<ColumnFamilyOptions> 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<std::string> 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(env_->GetFileSize(kOptionsFileName, &file_size));
+ assert(file_size > 0);
+
+ RocksDBOptionsParser parser;
+
+ env_->num_seq_file_read_ = 0;
+ size_t readahead_size = 128 * 1024;
+
+ ASSERT_OK(parser.Parse(kOptionsFileName, fs_.get(), false, readahead_size));
+ ASSERT_EQ(env_->num_seq_file_read_.load(),
+ (file_size - 1) / readahead_size + 1);
+
+ env_->num_seq_file_read_.store(0);
+ readahead_size = 1024 * 1024;
+ ASSERT_OK(parser.Parse(kOptionsFileName, fs_.get(), false, readahead_size));
+ ASSERT_EQ(env_->num_seq_file_read_.load(),
+ (file_size - 1) / readahead_size + 1);
+
+ // Tiny readahead. 8 KB is read each time.
+ env_->num_seq_file_read_.store(0);
+ ASSERT_OK(
+ parser.Parse(kOptionsFileName, fs_.get(), false, 1 /* readahead_size */));
+ ASSERT_GE(env_->num_seq_file_read_.load(), file_size / (8 * 1024));
+ ASSERT_LT(env_->num_seq_file_read_.load(), file_size / (8 * 1024) * 2);
+
+ // Disable readahead means 512KB readahead.
+ env_->num_seq_file_read_.store(0);
+ ASSERT_OK(
+ parser.Parse(kOptionsFileName, fs_.get(), false, 0 /* readahead_size */));
+ ASSERT_GE(env_->num_seq_file_read_.load(),
+ (file_size - 1) / (512 * 1024) + 1);
+}
+
+TEST_F(OptionsParserTest, DumpAndParse) {
+ DBOptions base_db_opt;
+ std::vector<ColumnFamilyOptions> base_cf_opts;
+ std::vector<std::string> 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<int>(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));
+ }
+ base_cf_opts.emplace_back(cf_opt);
+ }
+
+ const std::string kOptionsFileName = "test-persisted-options.ini";
+ ASSERT_OK(PersistRocksDBOptions(base_db_opt, cf_names, base_cf_opts,
+ kOptionsFileName, fs_.get()));
+
+ RocksDBOptionsParser parser;
+ ASSERT_OK(
+ parser.Parse(kOptionsFileName, fs_.get(), false, 0 /* readahead_size */));
+
+ // Make sure block-based table factory options was deserialized correctly
+ std::shared_ptr<TableFactory> ttf = (*parser.cf_opts())[4].table_factory;
+ ASSERT_EQ(BlockBasedTableFactory::kName, std::string(ttf->Name()));
+ const BlockBasedTableOptions& parsed_bbto =
+ static_cast<BlockBasedTableFactory*>(ttf.get())->table_options();
+ 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(
+ base_db_opt, cf_names, base_cf_opts, kOptionsFileName, fs_.get()));
+
+ ASSERT_OK(
+ RocksDBOptionsParser::VerifyDBOptions(*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(
+ 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(
+ 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, 0 /* 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:
+ OptionsSanityCheckTest() {}
+
+ protected:
+ Status SanityCheckCFOptions(const ColumnFamilyOptions& cf_opts,
+ OptionsSanityCheckLevel level) {
+ return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
+ DBOptions(), {"default"}, {cf_opts}, kOptionsFileName, fs_.get(),
+ level);
+ }
+
+ Status PersistCFOptions(const ColumnFamilyOptions& cf_opts) {
+ Status s = env_->DeleteFile(kOptionsFileName);
+ if (!s.ok()) {
+ return s;
+ }
+ return PersistRocksDBOptions(DBOptions(), {"default"}, {cf_opts},
+ kOptionsFileName, fs_.get());
+ }
+
+ const std::string kOptionsFileName = "OPTIONS";
+};
+
+TEST_F(OptionsSanityCheckTest, SanityCheck) {
+ ColumnFamilyOptions opts;
+ Random rnd(301);
+
+ // default ColumnFamilyOptions
+ {
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, 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, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+
+ // use same prefix extractor but with different parameter
+ opts.prefix_extractor.reset(NewCappedPrefixTransform(15));
+ // expect pass only in kSanityLevelLooselyCompatible
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // repeat the test with FixedPrefixTransform
+ opts.prefix_extractor.reset(NewFixedPrefixTransform(10));
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change of prefix_extractor
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+
+ // use same prefix extractor but with different parameter
+ opts.prefix_extractor.reset(NewFixedPrefixTransform(15));
+ // expect pass only in kSanityLevelLooselyCompatible
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // 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, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+ }
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, 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, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ }
+ }
+
+ // merge_operator
+ {
+ // Test when going from nullptr -> merge operator
+ opts.merge_operator.reset(test::RandomMergeOperator(&rnd));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+
+ for (int test = 0; test < 5; ++test) {
+ // change the merge operator
+ opts.merge_operator.reset(test::RandomMergeOperator(&rnd));
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ }
+
+ // Test when going from merge operator -> nullptr
+ opts.merge_operator = nullptr;
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ }
+
+ // compaction_filter
+ {
+ for (int test = 0; test < 5; ++test) {
+ // change the compaction filter
+ opts.compaction_filter = test::RandomCompactionFilter(&rnd);
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ 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));
+ ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelExactMatch));
+ ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible));
+
+ // persist the change
+ ASSERT_OK(PersistCFOptions(opts));
+ ASSERT_OK(SanityCheckCFOptions(opts, 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"), port::kMinInt64);
+ ASSERT_EQ(ParseInt32("2147483647"), 2147483647);
+ ASSERT_EQ(ParseInt32("-2147483648"), port::kMinInt32);
+ 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,.?>"
+ "<MNBVCXZ\\\" should be okay to \\#\\\\\\:\\#\\#\\#\\ "
+ "be serialized and deserialized";
+
+ std::string escaped_string = EscapeOptionString(readible_chars);
+ ASSERT_TRUE(IsEscapedString(escaped_string));
+ // This two transformations should be canceled and should output
+ // the original input.
+ ASSERT_EQ(UnescapeOptionString(escaped_string), readible_chars);
+
+ std::string all_chars;
+ for (unsigned char c = 0;; ++c) {
+ all_chars += c;
+ if (c == 255) {
+ break;
+ }
+ }
+ escaped_string = EscapeOptionString(all_chars);
+ ASSERT_TRUE(IsEscapedString(escaped_string));
+ ASSERT_EQ(UnescapeOptionString(escaped_string), all_chars);
+
+ ASSERT_EQ(RocksDBOptionsParser::TrimAndRemoveComment(
+ " A simple statement with a comment. # like this :)"),
+ "A simple statement with a comment.");
+
+ ASSERT_EQ(RocksDBOptionsParser::TrimAndRemoveComment(
+ "Escape \\# and # comment together ."),
+ "Escape \\# and");
+}
+#endif // !ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+#ifdef GFLAGS
+ ParseCommandLineFlags(&argc, &argv, true);
+#endif // GFLAGS
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/port/README b/storage/rocksdb/rocksdb/port/README
new file mode 100644
index 00000000..422563e2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/README
@@ -0,0 +1,10 @@
+This directory contains interfaces and implementations that isolate the
+rest of the package from platform details.
+
+Code in the rest of the package includes "port.h" from this directory.
+"port.h" in turn includes a platform specific "port_<platform>.h" file
+that provides the platform specific implementation.
+
+See port_posix.h for an example of what must be provided in a platform
+specific header file.
+
diff --git a/storage/rocksdb/rocksdb/port/jemalloc_helper.h b/storage/rocksdb/rocksdb/port/jemalloc_helper.h
new file mode 100644
index 00000000..f6f72f8c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/jemalloc_helper.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#if defined(__clang__)
+// glibc's `posix_memalign()` declaration specifies `throw()` while clang's
+// declaration does not. There is a hack in clang to make its re-declaration
+// compatible with glibc's if they are declared consecutively. That hack breaks
+// if yet another `posix_memalign()` declaration comes between glibc's and
+// clang's declarations. Include "mm_malloc.h" here ensures glibc's and clang's
+// declarations both come before "jemalloc.h"'s `posix_memalign()` declaration.
+//
+// This problem could also be avoided if "jemalloc.h"'s `posix_memalign()`
+// declaration did not specify `throw()` when built with clang.
+#include <mm_malloc.h>
+#endif
+
+#ifdef ROCKSDB_JEMALLOC
+#ifdef __FreeBSD__
+#include <malloc_np.h>
+#else
+#define JEMALLOC_MANGLE
+#include <jemalloc/jemalloc.h>
+#endif
+
+#ifndef JEMALLOC_CXX_THROW
+#define JEMALLOC_CXX_THROW
+#endif
+
+#if defined(OS_WIN) && defined(_MSC_VER)
+
+// MSVC does not have weak symbol support. As long as ROCKSDB_JEMALLOC is
+// defined, Jemalloc memory allocator is used.
+static inline bool HasJemalloc() { return true; }
+
+#else
+
+// Declare non-standard jemalloc APIs as weak symbols. We can null-check these
+// symbols to detect whether jemalloc is linked with the binary.
+extern "C" void* mallocx(size_t, int) __attribute__((__weak__));
+extern "C" void* rallocx(void*, size_t, int) __attribute__((__weak__));
+extern "C" size_t xallocx(void*, size_t, size_t, int) __attribute__((__weak__));
+extern "C" size_t sallocx(const void*, int) __attribute__((__weak__));
+extern "C" void dallocx(void*, int) __attribute__((__weak__));
+extern "C" void sdallocx(void*, size_t, int) __attribute__((__weak__));
+extern "C" size_t nallocx(size_t, int) __attribute__((__weak__));
+extern "C" int mallctl(const char*, void*, size_t*, void*, size_t)
+ __attribute__((__weak__));
+extern "C" int mallctlnametomib(const char*, size_t*, size_t*)
+ __attribute__((__weak__));
+extern "C" int mallctlbymib(const size_t*, size_t, void*, size_t*, void*,
+ size_t) __attribute__((__weak__));
+extern "C" void malloc_stats_print(void (*)(void*, const char*), void*,
+ const char*) __attribute__((__weak__));
+extern "C" size_t malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void*)
+ JEMALLOC_CXX_THROW __attribute__((__weak__));
+
+// Check if Jemalloc is linked with the binary. Note the main program might be
+// using a different memory allocator even this method return true.
+// It is loosely based on folly::usingJEMalloc(), minus the check that actually
+// allocate memory and see if it is through jemalloc, to handle the dlopen()
+// case:
+// https://github.com/facebook/folly/blob/76cf8b5841fb33137cfbf8b224f0226437c855bc/folly/memory/Malloc.h#L147
+static inline bool HasJemalloc() {
+ return mallocx != nullptr && rallocx != nullptr && xallocx != nullptr &&
+ sallocx != nullptr && dallocx != nullptr && sdallocx != nullptr &&
+ nallocx != nullptr && mallctl != nullptr &&
+ mallctlnametomib != nullptr && mallctlbymib != nullptr &&
+ malloc_stats_print != nullptr && malloc_usable_size != nullptr;
+}
+
+#endif
+
+#endif // ROCKSDB_JEMALLOC
diff --git a/storage/rocksdb/rocksdb/port/likely.h b/storage/rocksdb/rocksdb/port/likely.h
new file mode 100644
index 00000000..397d7571
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/likely.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define LIKELY(x) (__builtin_expect((x), 1))
+#define UNLIKELY(x) (__builtin_expect((x), 0))
+#else
+#define LIKELY(x) (x)
+#define UNLIKELY(x) (x)
+#endif
diff --git a/storage/rocksdb/rocksdb/port/malloc.h b/storage/rocksdb/rocksdb/port/malloc.h
new file mode 100644
index 00000000..f973263e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/malloc.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+#ifdef OS_FREEBSD
+#include <malloc_np.h>
+#else
+#include <malloc.h>
+#endif // OS_FREEBSD
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
diff --git a/storage/rocksdb/rocksdb/port/port.h b/storage/rocksdb/rocksdb/port/port.h
new file mode 100644
index 00000000..13aa56d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/port.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+// Include the appropriate platform specific file below. If you are
+// porting to a new platform, see "port_example.h" for documentation
+// of what the new port_<platform>.h file must provide.
+#if defined(ROCKSDB_PLATFORM_POSIX)
+#include "port/port_posix.h"
+#elif defined(OS_WIN)
+#include "port/win/port_win.h"
+#endif
diff --git a/storage/rocksdb/rocksdb/port/port_dirent.h b/storage/rocksdb/rocksdb/port/port_dirent.h
new file mode 100644
index 00000000..192abec4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/port_dirent.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// See port_example.h for documentation for the following types/functions.
+
+#pragma once
+
+#ifdef ROCKSDB_PLATFORM_POSIX
+#include <dirent.h>
+#include <sys/types.h>
+#elif defined(OS_WIN)
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+struct dirent {
+ char d_name[_MAX_PATH]; /* filename */
+};
+
+struct DIR;
+
+DIR* opendir(const char* name);
+
+dirent* readdir(DIR* dirp);
+
+int closedir(DIR* dirp);
+
+} // namespace port
+
+using port::dirent;
+using port::DIR;
+using port::opendir;
+using port::readdir;
+using port::closedir;
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // OS_WIN
diff --git a/storage/rocksdb/rocksdb/port/port_example.h b/storage/rocksdb/rocksdb/port/port_example.h
new file mode 100644
index 00000000..b906bbec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/port_example.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// This file contains the specification, but not the implementations,
+// of the types/operations/etc. that should be defined by a platform
+// specific port_<platform>.h file. Use this file as a reference for
+// how to port this package to a new platform.
+
+#pragma once
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+// TODO(jorlow): Many of these belong more in the environment class rather than
+// here. We should try moving them and see if it affects perf.
+
+// The following boolean constant must be true on a little-endian machine
+// and false otherwise.
+static const bool kLittleEndian = true /* or some other expression */;
+
+// ------------------ Threading -------------------
+
+// A Mutex represents an exclusive lock.
+class Mutex {
+ public:
+ Mutex();
+ ~Mutex();
+
+ // Lock the mutex. Waits until other lockers have exited.
+ // Will deadlock if the mutex is already locked by this thread.
+ void Lock();
+
+ // Unlock the mutex.
+ // REQUIRES: This mutex was locked by this thread.
+ void Unlock();
+
+ // Optionally crash if this thread does not hold this mutex.
+ // The implementation must be fast, especially if NDEBUG is
+ // defined. The implementation is allowed to skip all checks.
+ void AssertHeld();
+};
+
+class CondVar {
+ public:
+ explicit CondVar(Mutex* mu);
+ ~CondVar();
+
+ // Atomically release *mu and block on this condition variable until
+ // either a call to SignalAll(), or a call to Signal() that picks
+ // this thread to wakeup.
+ // REQUIRES: this thread holds *mu
+ void Wait();
+
+ // If there are some threads waiting, wake up at least one of them.
+ void Signal();
+
+ // Wake up all waiting threads.
+ void SignallAll();
+};
+
+// Thread-safe initialization.
+// Used as follows:
+// static port::OnceType init_control = LEVELDB_ONCE_INIT;
+// static void Initializer() { ... do something ...; }
+// ...
+// port::InitOnce(&init_control, &Initializer);
+typedef intptr_t OnceType;
+#define LEVELDB_ONCE_INIT 0
+extern void InitOnce(port::OnceType*, void (*initializer)());
+
+// ------------------ Compression -------------------
+
+// Store the snappy compression of "input[0,input_length-1]" in *output.
+// Returns false if snappy is not supported by this port.
+extern bool Snappy_Compress(const char* input, size_t input_length,
+ std::string* output);
+
+// If input[0,input_length-1] looks like a valid snappy compressed
+// buffer, store the size of the uncompressed data in *result and
+// return true. Else return false.
+extern bool Snappy_GetUncompressedLength(const char* input, size_t length,
+ size_t* result);
+
+// Attempt to snappy uncompress input[0,input_length-1] into *output.
+// Returns true if successful, false if the input is invalid lightweight
+// compressed data.
+//
+// REQUIRES: at least the first "n" bytes of output[] must be writable
+// where "n" is the result of a successful call to
+// Snappy_GetUncompressedLength.
+extern bool Snappy_Uncompress(const char* input_data, size_t input_length,
+ char* output);
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/port_posix.cc b/storage/rocksdb/rocksdb/port/port_posix.cc
new file mode 100644
index 00000000..e3ea5ca6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/port_posix.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/port_posix.h"
+
+#include <assert.h>
+#if defined(__i386__) || defined(__x86_64__)
+#include <cpuid.h>
+#endif
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <cstdlib>
+#include "logging/logging.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// We want to give users opportunity to default all the mutexes to adaptive if
+// not specified otherwise. This enables a quick way to conduct various
+// performance related experiements.
+//
+// NB! Support for adaptive mutexes is turned on by definining
+// ROCKSDB_PTHREAD_ADAPTIVE_MUTEX during the compilation. If you use RocksDB
+// build environment then this happens automatically; otherwise it's up to the
+// consumer to define the identifier.
+#ifdef ROCKSDB_DEFAULT_TO_ADAPTIVE_MUTEX
+extern const bool kDefaultToAdaptiveMutex = true;
+#else
+extern const bool kDefaultToAdaptiveMutex = false;
+#endif
+
+namespace port {
+
+static int PthreadCall(const char* label, int result) {
+ if (result != 0 && result != ETIMEDOUT) {
+ fprintf(stderr, "pthread %s: %s\n", label, strerror(result));
+ abort();
+ }
+ return result;
+}
+
+Mutex::Mutex(bool adaptive) {
+ (void) adaptive;
+#ifdef ROCKSDB_PTHREAD_ADAPTIVE_MUTEX
+ if (!adaptive) {
+ PthreadCall("init mutex", pthread_mutex_init(&mu_, nullptr));
+ } else {
+ pthread_mutexattr_t mutex_attr;
+ PthreadCall("init mutex attr", pthread_mutexattr_init(&mutex_attr));
+ PthreadCall("set mutex attr",
+ pthread_mutexattr_settype(&mutex_attr,
+ PTHREAD_MUTEX_ADAPTIVE_NP));
+ PthreadCall("init mutex", pthread_mutex_init(&mu_, &mutex_attr));
+ PthreadCall("destroy mutex attr",
+ pthread_mutexattr_destroy(&mutex_attr));
+ }
+#else
+ PthreadCall("init mutex", pthread_mutex_init(&mu_, nullptr));
+#endif // ROCKSDB_PTHREAD_ADAPTIVE_MUTEX
+}
+
+Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }
+
+void Mutex::Lock() {
+ PthreadCall("lock", pthread_mutex_lock(&mu_));
+#ifndef NDEBUG
+ locked_ = true;
+#endif
+}
+
+void Mutex::Unlock() {
+#ifndef NDEBUG
+ locked_ = false;
+#endif
+ PthreadCall("unlock", pthread_mutex_unlock(&mu_));
+}
+
+void Mutex::AssertHeld() {
+#ifndef NDEBUG
+ assert(locked_);
+#endif
+}
+
+CondVar::CondVar(Mutex* mu)
+ : mu_(mu) {
+ PthreadCall("init cv", pthread_cond_init(&cv_, nullptr));
+}
+
+CondVar::~CondVar() { PthreadCall("destroy cv", pthread_cond_destroy(&cv_)); }
+
+void CondVar::Wait() {
+#ifndef NDEBUG
+ mu_->locked_ = false;
+#endif
+ PthreadCall("wait", pthread_cond_wait(&cv_, &mu_->mu_));
+#ifndef NDEBUG
+ mu_->locked_ = true;
+#endif
+}
+
+bool CondVar::TimedWait(uint64_t abs_time_us) {
+ struct timespec ts;
+ ts.tv_sec = static_cast<time_t>(abs_time_us / 1000000);
+ ts.tv_nsec = static_cast<suseconds_t>((abs_time_us % 1000000) * 1000);
+
+#ifndef NDEBUG
+ mu_->locked_ = false;
+#endif
+ int err = pthread_cond_timedwait(&cv_, &mu_->mu_, &ts);
+#ifndef NDEBUG
+ mu_->locked_ = true;
+#endif
+ if (err == ETIMEDOUT) {
+ return true;
+ }
+ if (err != 0) {
+ PthreadCall("timedwait", err);
+ }
+ return false;
+}
+
+void CondVar::Signal() {
+ PthreadCall("signal", pthread_cond_signal(&cv_));
+}
+
+void CondVar::SignalAll() {
+ PthreadCall("broadcast", pthread_cond_broadcast(&cv_));
+}
+
+RWMutex::RWMutex() {
+ PthreadCall("init mutex", pthread_rwlock_init(&mu_, nullptr));
+}
+
+RWMutex::~RWMutex() { PthreadCall("destroy mutex", pthread_rwlock_destroy(&mu_)); }
+
+void RWMutex::ReadLock() { PthreadCall("read lock", pthread_rwlock_rdlock(&mu_)); }
+
+void RWMutex::WriteLock() { PthreadCall("write lock", pthread_rwlock_wrlock(&mu_)); }
+
+void RWMutex::ReadUnlock() { PthreadCall("read unlock", pthread_rwlock_unlock(&mu_)); }
+
+void RWMutex::WriteUnlock() { PthreadCall("write unlock", pthread_rwlock_unlock(&mu_)); }
+
+int PhysicalCoreID() {
+#if defined(ROCKSDB_SCHED_GETCPU_PRESENT) && defined(__x86_64__) && \
+ (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 22))
+ // sched_getcpu uses VDSO getcpu() syscall since 2.22. I believe Linux offers VDSO
+ // support only on x86_64. This is the fastest/preferred method if available.
+ int cpuno = sched_getcpu();
+ if (cpuno < 0) {
+ return -1;
+ }
+ return cpuno;
+#elif defined(__x86_64__) || defined(__i386__)
+ // clang/gcc both provide cpuid.h, which defines __get_cpuid(), for x86_64 and i386.
+ unsigned eax, ebx = 0, ecx, edx;
+ if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
+ return -1;
+ }
+ return ebx >> 24;
+#else
+ // give up, the caller can generate a random number or something.
+ return -1;
+#endif
+}
+
+void InitOnce(OnceType* once, void (*initializer)()) {
+ PthreadCall("once", pthread_once(once, initializer));
+}
+
+void Crash(const std::string& srcfile, int srcline) {
+ fprintf(stdout, "Crashing at %s:%d\n", srcfile.c_str(), srcline);
+ fflush(stdout);
+ kill(getpid(), SIGTERM);
+}
+
+int GetMaxOpenFiles() {
+#if defined(RLIMIT_NOFILE)
+ struct rlimit no_files_limit;
+ if (getrlimit(RLIMIT_NOFILE, &no_files_limit) != 0) {
+ return -1;
+ }
+ // protect against overflow
+ if (static_cast<uintmax_t>(no_files_limit.rlim_cur) >=
+ static_cast<uintmax_t>(std::numeric_limits<int>::max())) {
+ return std::numeric_limits<int>::max();
+ }
+ return static_cast<int>(no_files_limit.rlim_cur);
+#endif
+ return -1;
+}
+
+void *cacheline_aligned_alloc(size_t size) {
+#if __GNUC__ < 5 && defined(__SANITIZE_ADDRESS__)
+ return malloc(size);
+#elif ( _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__))
+ void *m;
+ errno = posix_memalign(&m, CACHE_LINE_SIZE, size);
+ return errno ? nullptr : m;
+#else
+ return malloc(size);
+#endif
+}
+
+void cacheline_aligned_free(void *memblock) {
+ free(memblock);
+}
+
+static size_t GetPageSize() {
+#if defined(OS_LINUX) || defined(_SC_PAGESIZE)
+ long v = sysconf(_SC_PAGESIZE);
+ if (v >= 1024) {
+ return static_cast<size_t>(v);
+ }
+#endif
+ // Default assume 4KB
+ return 4U * 1024U;
+}
+
+const size_t kPageSize = GetPageSize();
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/port_posix.h b/storage/rocksdb/rocksdb/port/port_posix.h
new file mode 100644
index 00000000..0c9c6983
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/port_posix.h
@@ -0,0 +1,218 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// See port_example.h for documentation for the following types/functions.
+
+#pragma once
+
+#include <thread>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+// size_t printf formatting named in the manner of C99 standard formatting
+// strings such as PRIu64
+// in fact, we could use that one
+#define ROCKSDB_PRIszt "zu"
+
+#define __declspec(S)
+
+#define ROCKSDB_NOEXCEPT noexcept
+
+#undef PLATFORM_IS_LITTLE_ENDIAN
+#if defined(OS_MACOSX)
+ #include <machine/endian.h>
+ #if defined(__DARWIN_LITTLE_ENDIAN) && defined(__DARWIN_BYTE_ORDER)
+ #define PLATFORM_IS_LITTLE_ENDIAN \
+ (__DARWIN_BYTE_ORDER == __DARWIN_LITTLE_ENDIAN)
+ #endif
+#elif defined(OS_SOLARIS)
+ #include <sys/isa_defs.h>
+ #ifdef _LITTLE_ENDIAN
+ #define PLATFORM_IS_LITTLE_ENDIAN true
+ #else
+ #define PLATFORM_IS_LITTLE_ENDIAN false
+ #endif
+ #include <alloca.h>
+#elif defined(OS_AIX)
+ #include <sys/types.h>
+ #include <arpa/nameser_compat.h>
+ #define PLATFORM_IS_LITTLE_ENDIAN (BYTE_ORDER == LITTLE_ENDIAN)
+ #include <alloca.h>
+#elif defined(OS_FREEBSD) || defined(OS_OPENBSD) || defined(OS_NETBSD) || \
+ defined(OS_DRAGONFLYBSD) || defined(OS_ANDROID)
+ #include <sys/endian.h>
+ #include <sys/types.h>
+ #define PLATFORM_IS_LITTLE_ENDIAN (_BYTE_ORDER == _LITTLE_ENDIAN)
+#else
+ #include <endian.h>
+#endif
+#include <pthread.h>
+
+#include <stdint.h>
+#include <string.h>
+#include <limits>
+#include <string>
+
+#ifndef PLATFORM_IS_LITTLE_ENDIAN
+#define PLATFORM_IS_LITTLE_ENDIAN (__BYTE_ORDER == __LITTLE_ENDIAN)
+#endif
+
+#if defined(OS_MACOSX) || defined(OS_SOLARIS) || defined(OS_FREEBSD) ||\
+ defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_DRAGONFLYBSD) ||\
+ defined(OS_ANDROID) || defined(CYGWIN) || defined(OS_AIX)
+// Use fread/fwrite/fflush on platforms without _unlocked variants
+#define fread_unlocked fread
+#define fwrite_unlocked fwrite
+#define fflush_unlocked fflush
+#endif
+
+#if defined(OS_MACOSX) || defined(OS_FREEBSD) ||\
+ defined(OS_OPENBSD) || defined(OS_DRAGONFLYBSD)
+// Use fsync() on platforms without fdatasync()
+#define fdatasync fsync
+#endif
+
+#if defined(OS_ANDROID) && __ANDROID_API__ < 9
+// fdatasync() was only introduced in API level 9 on Android. Use fsync()
+// when targeting older platforms.
+#define fdatasync fsync
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const bool kDefaultToAdaptiveMutex;
+
+namespace port {
+
+// For use at db/file_indexer.h kLevelMaxIndex
+const uint32_t kMaxUint32 = std::numeric_limits<uint32_t>::max();
+const int kMaxInt32 = std::numeric_limits<int32_t>::max();
+const int kMinInt32 = std::numeric_limits<int32_t>::min();
+const uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
+const int64_t kMaxInt64 = std::numeric_limits<int64_t>::max();
+const int64_t kMinInt64 = std::numeric_limits<int64_t>::min();
+const size_t kMaxSizet = std::numeric_limits<size_t>::max();
+
+constexpr bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN;
+#undef PLATFORM_IS_LITTLE_ENDIAN
+
+class CondVar;
+
+class Mutex {
+ public:
+ explicit Mutex(bool adaptive = kDefaultToAdaptiveMutex);
+ // No copying
+ Mutex(const Mutex&) = delete;
+ void operator=(const Mutex&) = delete;
+
+ ~Mutex();
+
+ void Lock();
+ void Unlock();
+ // this will assert if the mutex is not locked
+ // it does NOT verify that mutex is held by a calling thread
+ void AssertHeld();
+
+ private:
+ friend class CondVar;
+ pthread_mutex_t mu_;
+#ifndef NDEBUG
+ bool locked_;
+#endif
+};
+
+class RWMutex {
+ public:
+ RWMutex();
+ // No copying allowed
+ RWMutex(const RWMutex&) = delete;
+ void operator=(const RWMutex&) = delete;
+
+ ~RWMutex();
+
+ void ReadLock();
+ void WriteLock();
+ void ReadUnlock();
+ void WriteUnlock();
+ void AssertHeld() { }
+
+ private:
+ pthread_rwlock_t mu_; // the underlying platform mutex
+};
+
+class CondVar {
+ public:
+ explicit CondVar(Mutex* mu);
+ ~CondVar();
+ void Wait();
+ // Timed condition wait. Returns true if timeout occurred.
+ bool TimedWait(uint64_t abs_time_us);
+ void Signal();
+ void SignalAll();
+ private:
+ pthread_cond_t cv_;
+ Mutex* mu_;
+};
+
+using Thread = std::thread;
+
+static inline void AsmVolatilePause() {
+#if defined(__i386__) || defined(__x86_64__)
+ asm volatile("pause");
+#elif defined(__aarch64__)
+ asm volatile("wfe");
+#elif defined(__powerpc64__)
+ asm volatile("or 27,27,27");
+#endif
+ // it's okay for other platforms to be no-ops
+}
+
+// Returns -1 if not available on this platform
+extern int PhysicalCoreID();
+
+typedef pthread_once_t OnceType;
+#define LEVELDB_ONCE_INIT PTHREAD_ONCE_INIT
+extern void InitOnce(OnceType* once, void (*initializer)());
+
+#ifndef CACHE_LINE_SIZE
+// To test behavior with non-native cache line size, e.g. for
+// Bloom filters, set TEST_CACHE_LINE_SIZE to the desired test size.
+// This disables ALIGN_AS to keep it from failing compilation.
+#ifdef TEST_CACHE_LINE_SIZE
+#define CACHE_LINE_SIZE TEST_CACHE_LINE_SIZE
+#define ALIGN_AS(n) /*empty*/
+#else
+#if defined(__s390__)
+#define CACHE_LINE_SIZE 256U
+#elif defined(__powerpc__) || defined(__aarch64__)
+#define CACHE_LINE_SIZE 128U
+#else
+#define CACHE_LINE_SIZE 64U
+#endif
+#define ALIGN_AS(n) alignas(n)
+#endif
+#endif
+
+static_assert((CACHE_LINE_SIZE & (CACHE_LINE_SIZE - 1)) == 0,
+ "Cache line size must be a power of 2 number of bytes");
+
+extern void *cacheline_aligned_alloc(size_t size);
+
+extern void cacheline_aligned_free(void *memblock);
+
+#define PREFETCH(addr, rw, locality) __builtin_prefetch(addr, rw, locality)
+
+extern void Crash(const std::string& srcfile, int srcline);
+
+extern int GetMaxOpenFiles();
+
+extern const size_t kPageSize;
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/stack_trace.cc b/storage/rocksdb/rocksdb/port/stack_trace.cc
new file mode 100644
index 00000000..32eb45bf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/stack_trace.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/stack_trace.h"
+
+#if defined(ROCKSDB_LITE) || !(defined(ROCKSDB_BACKTRACE) || defined(OS_MACOSX)) || \
+ defined(CYGWIN) || defined(OS_FREEBSD) || defined(OS_SOLARIS)
+
+// noop
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+void InstallStackTraceHandler() {}
+void PrintStack(int /*first_frames_to_skip*/) {}
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
+
+#else
+
+#include <execinfo.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <cxxabi.h>
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+namespace {
+
+#if defined(OS_LINUX) || defined(OS_FREEBSD)
+const char* GetExecutableName() {
+ static char name[1024];
+
+ char link[1024];
+ snprintf(link, sizeof(link), "/proc/%d/exe", getpid());
+ auto read = readlink(link, name, sizeof(name) - 1);
+ if (-1 == read) {
+ return nullptr;
+ } else {
+ name[read] = 0;
+ return name;
+ }
+}
+
+void PrintStackTraceLine(const char* symbol, void* frame) {
+ static const char* executable = GetExecutableName();
+ if (symbol) {
+ fprintf(stderr, "%s ", symbol);
+ }
+ if (executable) {
+ // out source to addr2line, for the address translation
+ const int kLineMax = 256;
+ char cmd[kLineMax];
+ snprintf(cmd, kLineMax, "addr2line %p -e %s -f -C 2>&1", frame, executable);
+ auto f = popen(cmd, "r");
+ if (f) {
+ char line[kLineMax];
+ while (fgets(line, sizeof(line), f)) {
+ line[strlen(line) - 1] = 0; // remove newline
+ fprintf(stderr, "%s\t", line);
+ }
+ pclose(f);
+ }
+ } else {
+ fprintf(stderr, " %p", frame);
+ }
+
+ fprintf(stderr, "\n");
+}
+#elif defined(OS_MACOSX)
+
+void PrintStackTraceLine(const char* symbol, void* frame) {
+ static int pid = getpid();
+ // out source to atos, for the address translation
+ const int kLineMax = 256;
+ char cmd[kLineMax];
+ snprintf(cmd, kLineMax, "xcrun atos %p -p %d 2>&1", frame, pid);
+ auto f = popen(cmd, "r");
+ if (f) {
+ char line[kLineMax];
+ while (fgets(line, sizeof(line), f)) {
+ line[strlen(line) - 1] = 0; // remove newline
+ fprintf(stderr, "%s\t", line);
+ }
+ pclose(f);
+ } else if (symbol) {
+ fprintf(stderr, "%s ", symbol);
+ }
+
+ fprintf(stderr, "\n");
+}
+
+#endif
+
+} // namespace
+
+void PrintStack(int first_frames_to_skip) {
+ const int kMaxFrames = 100;
+ void* frames[kMaxFrames];
+
+ auto num_frames = backtrace(frames, kMaxFrames);
+ auto symbols = backtrace_symbols(frames, num_frames);
+
+ for (int i = first_frames_to_skip; i < num_frames; ++i) {
+ fprintf(stderr, "#%-2d ", i - first_frames_to_skip);
+ PrintStackTraceLine((symbols != nullptr) ? symbols[i] : nullptr, frames[i]);
+ }
+ free(symbols);
+}
+
+static void StackTraceHandler(int sig) {
+ // reset to default handler
+ signal(sig, SIG_DFL);
+ fprintf(stderr, "Received signal %d (%s)\n", sig, strsignal(sig));
+ // skip the top three signal handler related frames
+ PrintStack(3);
+ // re-signal to default handler (so we still get core dump if needed...)
+ raise(sig);
+}
+
+void InstallStackTraceHandler() {
+ // just use the plain old signal as it's simple and sufficient
+ // for this use case
+ signal(SIGILL, StackTraceHandler);
+ signal(SIGSEGV, StackTraceHandler);
+ signal(SIGBUS, StackTraceHandler);
+ signal(SIGABRT, StackTraceHandler);
+}
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/port/stack_trace.h b/storage/rocksdb/rocksdb/port/stack_trace.h
new file mode 100644
index 00000000..4924f5b1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/stack_trace.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+// Install a signal handler to print callstack on the following signals:
+// SIGILL SIGSEGV SIGBUS SIGABRT
+// Currently supports linux only. No-op otherwise.
+void InstallStackTraceHandler();
+
+// Prints stack, skips skip_first_frames frames
+void PrintStack(int first_frames_to_skip = 0);
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/sys_time.h b/storage/rocksdb/rocksdb/port/sys_time.h
new file mode 100644
index 00000000..1c7d206a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/sys_time.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+// This file is a portable substitute for sys/time.h which does not exist on
+// Windows
+
+#pragma once
+
+#if defined(OS_WIN) && defined(_MSC_VER)
+
+#include <time.h>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace port {
+
+// Avoid including winsock2.h for this definition
+typedef struct timeval {
+ long tv_sec;
+ long tv_usec;
+} timeval;
+
+void gettimeofday(struct timeval* tv, struct timezone* tz);
+
+inline struct tm* localtime_r(const time_t* timep, struct tm* result) {
+ errno_t ret = localtime_s(result, timep);
+ return (ret == 0) ? result : NULL;
+}
+}
+
+using port::timeval;
+using port::gettimeofday;
+using port::localtime_r;
+} // namespace ROCKSDB_NAMESPACE
+
+#else
+#include <time.h>
+#include <sys/time.h>
+#endif
diff --git a/storage/rocksdb/rocksdb/port/util_logger.h b/storage/rocksdb/rocksdb/port/util_logger.h
new file mode 100644
index 00000000..d2d62a98
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/util_logger.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 the appropriate platform specific file below. If you are
+// porting to a new platform, see "port_example.h" for documentation
+// of what the new port_<platform>.h file must provide.
+
+#if defined(ROCKSDB_PLATFORM_POSIX)
+#include "logging/posix_logger.h"
+#elif defined(OS_WIN)
+#include "port/win/win_logger.h"
+#endif
diff --git a/storage/rocksdb/rocksdb/port/win/env_default.cc b/storage/rocksdb/rocksdb/port/win/env_default.cc
new file mode 100644
index 00000000..663779dd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/env_default.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <mutex>
+
+#include <rocksdb/env.h>
+#include "port/win/env_win.h"
+#include "test_util/sync_point.h"
+#include "util/compression_context_cache.h"
+#include "util/thread_local.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+// We choose not to destroy the env because joining the threads from the
+// system loader
+// which destroys the statics (same as from DLLMain) creates a system loader
+// dead-lock.
+// in this manner any remaining threads are terminated OK.
+namespace {
+ std::once_flag winenv_once_flag;
+ Env* envptr;
+};
+}
+
+Env* Env::Default() {
+ using namespace port;
+ ThreadLocalPtr::InitSingletons();
+ CompressionContextCache::InitSingleton();
+ INIT_SYNC_POINT_SINGLETONS();
+ std::call_once(winenv_once_flag, []() { envptr = new WinEnv(); });
+ return envptr;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/env_win.cc b/storage/rocksdb/rocksdb/port/win/env_win.cc
new file mode 100644
index 00000000..295bb2b7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/env_win.cc
@@ -0,0 +1,1540 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/win/env_win.h"
+#include "port/win/win_thread.h"
+#include <algorithm>
+#include <ctime>
+#include <thread>
+
+#include <errno.h>
+#include <process.h> // _getpid
+#include <io.h> // _access
+#include <direct.h> // _rmdir, _mkdir, _getcwd
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+
+#include "port/port.h"
+#include "port/port_dirent.h"
+#include "port/win/win_logger.h"
+#include "port/win/io_win.h"
+
+#include "monitoring/iostats_context_imp.h"
+
+#include "monitoring/thread_status_updater.h"
+#include "monitoring/thread_status_util.h"
+
+#include <rpc.h> // for uuid generation
+#include <windows.h>
+#include <shlwapi.h>
+#include "strsafe.h"
+
+#include <algorithm>
+
+namespace ROCKSDB_NAMESPACE {
+
+ThreadStatusUpdater* CreateThreadStatusUpdater() {
+ return new ThreadStatusUpdater();
+}
+
+namespace {
+
+// Sector size used when physical sector size cannot be obtained from device.
+static const size_t kSectorSize = 512;
+
+// RAII helpers for HANDLEs
+const auto CloseHandleFunc = [](HANDLE h) { ::CloseHandle(h); };
+typedef std::unique_ptr<void, decltype(CloseHandleFunc)> UniqueCloseHandlePtr;
+
+const auto FindCloseFunc = [](HANDLE h) { ::FindClose(h); };
+typedef std::unique_ptr<void, decltype(FindCloseFunc)> UniqueFindClosePtr;
+
+void WinthreadCall(const char* label, std::error_code result) {
+ if (0 != result.value()) {
+ fprintf(stderr, "pthread %s: %s\n", label, strerror(result.value()));
+ abort();
+ }
+}
+
+}
+
+namespace port {
+
+WinEnvIO::WinEnvIO(Env* hosted_env)
+ : hosted_env_(hosted_env),
+ page_size_(4 * 1024),
+ allocation_granularity_(page_size_),
+ perf_counter_frequency_(0),
+ nano_seconds_per_period_(0),
+ GetSystemTimePreciseAsFileTime_(NULL) {
+
+ SYSTEM_INFO sinfo;
+ GetSystemInfo(&sinfo);
+
+ page_size_ = sinfo.dwPageSize;
+ allocation_granularity_ = sinfo.dwAllocationGranularity;
+
+ {
+ LARGE_INTEGER qpf;
+ BOOL ret __attribute__((__unused__));
+ ret = QueryPerformanceFrequency(&qpf);
+ assert(ret == TRUE);
+ perf_counter_frequency_ = qpf.QuadPart;
+
+ if (std::nano::den % perf_counter_frequency_ == 0) {
+ nano_seconds_per_period_ = std::nano::den / perf_counter_frequency_;
+ }
+ }
+
+ HMODULE module = GetModuleHandle("kernel32.dll");
+ if (module != NULL) {
+ GetSystemTimePreciseAsFileTime_ =
+ (FnGetSystemTimePreciseAsFileTime)GetProcAddress(
+ module, "GetSystemTimePreciseAsFileTime");
+ }
+}
+
+WinEnvIO::~WinEnvIO() {
+}
+
+Status WinEnvIO::DeleteFile(const std::string& fname) {
+ Status result;
+
+ BOOL ret = RX_DeleteFile(RX_FN(fname).c_str());
+
+ if(!ret) {
+ auto lastError = GetLastError();
+ result = IOErrorFromWindowsError("Failed to delete: " + fname,
+ lastError);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::Truncate(const std::string& fname, size_t size) {
+ Status s;
+ int result = ROCKSDB_NAMESPACE::port::Truncate(fname, size);
+ if (result != 0) {
+ s = IOError("Failed to truncate: " + fname, errno);
+ }
+ return s;
+}
+
+Status WinEnvIO::GetCurrentTime(int64_t* unix_time) {
+ time_t time = std::time(nullptr);
+ if (time == (time_t)(-1)) {
+ return Status::NotSupported("Failed to get time");
+ }
+
+ *unix_time = time;
+ return Status::OK();
+}
+
+Status WinEnvIO::NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) {
+ Status s;
+
+ result->reset();
+
+ // Corruption test needs to rename and delete files of these kind
+ // while they are still open with another handle. For that reason we
+ // allow share_write and delete(allows rename).
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+
+ DWORD fileFlags = FILE_ATTRIBUTE_READONLY;
+
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+ fileFlags |= FILE_FLAG_NO_BUFFERING;
+ }
+
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(
+ RX_FN(fname).c_str(), GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, // Original fopen mode is "rb"
+ fileFlags, NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("Failed to open NewSequentialFile" + fname,
+ lastError);
+ } else {
+ result->reset(new WinSequentialFile(fname, hFile, options));
+ }
+ return s;
+}
+
+Status WinEnvIO::NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) {
+ result->reset();
+ Status s;
+
+ // Open the file for read-only random access
+ // Random access is to disable read-ahead as the system reads too much data
+ DWORD fileFlags = FILE_ATTRIBUTE_READONLY;
+
+ if (options.use_direct_reads && !options.use_mmap_reads) {
+ fileFlags |= FILE_FLAG_NO_BUFFERING;
+ } else {
+ fileFlags |= FILE_FLAG_RANDOM_ACCESS;
+ }
+
+ /// Shared access is necessary for corruption test to pass
+ // almost all tests would work with a possible exception of fault_injection
+ HANDLE hFile = 0;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(
+ RX_FN(fname).c_str(), GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, fileFlags, NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "NewRandomAccessFile failed to Create/Open: " + fname, lastError);
+ }
+
+ UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc);
+
+ // CAUTION! This will map the entire file into the process address space
+ if (options.use_mmap_reads && sizeof(void*) >= 8) {
+ // Use mmap when virtual address-space is plentiful.
+ uint64_t fileSize;
+
+ s = GetFileSize(fname, &fileSize);
+
+ if (s.ok()) {
+ // Will not map empty files
+ if (fileSize == 0) {
+ return IOError(
+ "NewRandomAccessFile failed to map empty file: " + fname, EINVAL);
+ }
+
+ HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READONLY,
+ 0, // At its present length
+ 0,
+ NULL); // Mapping name
+
+ if (!hMap) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to create file mapping for NewRandomAccessFile: " + fname,
+ lastError);
+ }
+
+ UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc);
+
+ const void* mapped_region =
+ MapViewOfFileEx(hMap, FILE_MAP_READ,
+ 0, // High DWORD of access start
+ 0, // Low DWORD
+ static_cast<SIZE_T>(fileSize),
+ NULL); // Let the OS choose the mapping
+
+ if (!mapped_region) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to MapViewOfFile for NewRandomAccessFile: " + fname,
+ lastError);
+ }
+
+ result->reset(new WinMmapReadableFile(fname, hFile, hMap, mapped_region,
+ static_cast<size_t>(fileSize)));
+
+ mapGuard.release();
+ fileGuard.release();
+ }
+ } else {
+ result->reset(new WinRandomAccessFile(fname, hFile,
+ std::max(GetSectorSize(fname),
+ page_size_),
+ options));
+ fileGuard.release();
+ }
+ return s;
+}
+
+Status WinEnvIO::OpenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options,
+ bool reopen) {
+
+ const size_t c_BufferCapacity = 64 * 1024;
+
+ EnvOptions local_options(options);
+
+ result->reset();
+ Status s;
+
+ DWORD fileFlags = FILE_ATTRIBUTE_NORMAL;
+
+ if (local_options.use_direct_writes && !local_options.use_mmap_writes) {
+ fileFlags = FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH;
+ }
+
+ // Desired access. We are want to write only here but if we want to memory
+ // map
+ // the file then there is no write only mode so we have to create it
+ // Read/Write
+ // However, MapViewOfFile specifies only Write only
+ DWORD desired_access = GENERIC_WRITE;
+ DWORD shared_mode = FILE_SHARE_READ;
+
+ if (local_options.use_mmap_writes) {
+ desired_access |= GENERIC_READ;
+ } else {
+ // Adding this solely for tests to pass (fault_injection_test,
+ // wal_manager_test).
+ shared_mode |= (FILE_SHARE_WRITE | FILE_SHARE_DELETE);
+ }
+
+ // This will always truncate the file
+ DWORD creation_disposition = CREATE_ALWAYS;
+ if (reopen) {
+ creation_disposition = OPEN_ALWAYS;
+ }
+
+ HANDLE hFile = 0;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(
+ RX_FN(fname).c_str(),
+ desired_access, // Access desired
+ shared_mode,
+ NULL, // Security attributes
+ // Posix env says (reopen) ? (O_CREATE | O_APPEND) : O_CREAT | O_TRUNC
+ creation_disposition,
+ fileFlags, // Flags
+ NULL); // Template File
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to create a NewWriteableFile: " + fname, lastError);
+ }
+
+ // We will start writing at the end, appending
+ if (reopen) {
+ LARGE_INTEGER zero_move;
+ zero_move.QuadPart = 0;
+ BOOL ret = SetFilePointerEx(hFile, zero_move, NULL, FILE_END);
+ if (!ret) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to create a ReopenWritableFile move to the end: " + fname,
+ lastError);
+ }
+ }
+
+ if (options.use_mmap_writes) {
+ // We usually do not use mmmapping on SSD and thus we pass memory
+ // page_size
+ result->reset(new WinMmapFile(fname, hFile, page_size_,
+ allocation_granularity_, local_options));
+ } else {
+ // Here we want the buffer allocation to be aligned by the SSD page size
+ // and to be a multiple of it
+ result->reset(new WinWritableFile(fname, hFile,
+ std::max(GetSectorSize(fname),
+ GetPageSize()),
+ c_BufferCapacity, local_options));
+ }
+ return s;
+}
+
+Status WinEnvIO::NewRandomRWFile(const std::string & fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions & options) {
+
+ Status s;
+
+ // Open the file for read-only random access
+ // Random access is to disable read-ahead as the system reads too much data
+ DWORD desired_access = GENERIC_READ | GENERIC_WRITE;
+ DWORD shared_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ DWORD creation_disposition = OPEN_EXISTING; // Fail if file does not exist
+ DWORD file_flags = FILE_FLAG_RANDOM_ACCESS;
+
+ if (options.use_direct_reads && options.use_direct_writes) {
+ file_flags |= FILE_FLAG_NO_BUFFERING;
+ }
+
+ /// Shared access is necessary for corruption test to pass
+ // almost all tests would work with a possible exception of fault_injection
+ HANDLE hFile = 0;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile =
+ RX_CreateFile(RX_FN(fname).c_str(),
+ desired_access,
+ shared_mode,
+ NULL, // Security attributes
+ creation_disposition,
+ file_flags,
+ NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "NewRandomRWFile failed to Create/Open: " + fname, lastError);
+ }
+
+ UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc);
+ result->reset(new WinRandomRWFile(fname, hFile,
+ std::max(GetSectorSize(fname),
+ GetPageSize()),
+ options));
+ fileGuard.release();
+
+ return s;
+}
+
+Status WinEnvIO::NewMemoryMappedFileBuffer(
+ const std::string & fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) {
+ Status s;
+ result->reset();
+
+ DWORD fileFlags = FILE_ATTRIBUTE_READONLY;
+
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(
+ RX_FN(fname).c_str(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING, // Open only if it exists
+ fileFlags,
+ NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "Failed to open NewMemoryMappedFileBuffer: " + fname, lastError);
+ return s;
+ }
+ UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc);
+
+ uint64_t fileSize = 0;
+ s = GetFileSize(fname, &fileSize);
+ if (!s.ok()) {
+ return s;
+ }
+ // Will not map empty files
+ if (fileSize == 0) {
+ return Status::NotSupported(
+ "NewMemoryMappedFileBuffer can not map zero length files: " + fname);
+ }
+
+ // size_t is 32-bit with 32-bit builds
+ if (fileSize > std::numeric_limits<size_t>::max()) {
+ return Status::NotSupported(
+ "The specified file size does not fit into 32-bit memory addressing: "
+ + fname);
+ }
+
+ HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READWRITE,
+ 0, // Whole file at its present length
+ 0,
+ NULL); // Mapping name
+
+ if (!hMap) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to create file mapping for: " + fname, lastError);
+ }
+ UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc);
+
+ void* base = MapViewOfFileEx(hMap, FILE_MAP_WRITE,
+ 0, // High DWORD of access start
+ 0, // Low DWORD
+ static_cast<SIZE_T>(fileSize),
+ NULL); // Let the OS choose the mapping
+
+ if (!base) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError(
+ "Failed to MapViewOfFile for NewMemoryMappedFileBuffer: " + fname,
+ lastError);
+ }
+
+ result->reset(new WinMemoryMappedBuffer(hFile, hMap, base,
+ static_cast<size_t>(fileSize)));
+
+ mapGuard.release();
+ fileGuard.release();
+
+ return s;
+}
+
+Status WinEnvIO::NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) {
+ Status s;
+ // Must be nullptr on failure
+ result->reset();
+
+ if (!DirExists(name)) {
+ s = IOErrorFromWindowsError(
+ "open folder: " + name, ERROR_DIRECTORY);
+ return s;
+ }
+
+ HANDLE handle = INVALID_HANDLE_VALUE;
+ // 0 - for access means read metadata
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ handle = RX_CreateFile(
+ RX_FN(name).c_str(), 0,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible
+ NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == handle) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("open folder: " + name, lastError);
+ return s;
+ }
+
+ result->reset(new WinDirectory(handle));
+
+ return s;
+}
+
+Status WinEnvIO::FileExists(const std::string& fname) {
+ Status s;
+ // TODO: This does not follow symbolic links at this point
+ // which is consistent with _access() impl on windows
+ // but can be added
+ WIN32_FILE_ATTRIBUTE_DATA attrs;
+ if (FALSE == RX_GetFileAttributesEx(RX_FN(fname).c_str(),
+ GetFileExInfoStandard, &attrs)) {
+ auto lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_ACCESS_DENIED:
+ case ERROR_NOT_FOUND:
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ s = Status::NotFound();
+ break;
+ default:
+ s = IOErrorFromWindowsError("Unexpected error for: " + fname,
+ lastError);
+ break;
+ }
+ }
+ return s;
+}
+
+Status WinEnvIO::GetChildren(const std::string& dir,
+ std::vector<std::string>* result) {
+
+ Status status;
+ result->clear();
+ std::vector<std::string> output;
+
+ RX_WIN32_FIND_DATA data;
+ memset(&data, 0, sizeof(data));
+ std::string pattern(dir);
+ pattern.append("\\").append("*");
+
+ HANDLE handle = RX_FindFirstFileEx(RX_FN(pattern).c_str(),
+ // Do not want alternative name
+ FindExInfoBasic,
+ &data,
+ FindExSearchNameMatch,
+ NULL, // lpSearchFilter
+ 0);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ auto lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_NOT_FOUND:
+ case ERROR_ACCESS_DENIED:
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ status = Status::NotFound();
+ break;
+ default:
+ status = IOErrorFromWindowsError(
+ "Failed to GetChhildren for: " + dir, lastError);
+ }
+ return status;
+ }
+
+ UniqueFindClosePtr fc(handle, FindCloseFunc);
+
+ if (result->capacity() > 0) {
+ output.reserve(result->capacity());
+ }
+
+ // For safety
+ data.cFileName[MAX_PATH - 1] = 0;
+
+ while (true) {
+ auto x = RX_FILESTRING(data.cFileName, RX_FNLEN(data.cFileName));
+ output.emplace_back(FN_TO_RX(x));
+ BOOL ret =- RX_FindNextFile(handle, &data);
+ // If the function fails the return value is zero
+ // and non-zero otherwise. Not TRUE or FALSE.
+ if (ret == FALSE) {
+ // Posix does not care why we stopped
+ break;
+ }
+ data.cFileName[MAX_PATH - 1] = 0;
+ }
+ output.swap(*result);
+ return status;
+}
+
+Status WinEnvIO::CreateDir(const std::string& name) {
+ Status result;
+ BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL);
+ if (!ret) {
+ auto lastError = GetLastError();
+ result = IOErrorFromWindowsError(
+ "Failed to create a directory: " + name, lastError);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::CreateDirIfMissing(const std::string& name) {
+ Status result;
+
+ if (DirExists(name)) {
+ return result;
+ }
+
+ BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL);
+ if (!ret) {
+ auto lastError = GetLastError();
+ if (lastError != ERROR_ALREADY_EXISTS) {
+ result = IOErrorFromWindowsError(
+ "Failed to create a directory: " + name, lastError);
+ } else {
+ result =
+ Status::IOError(name + ": exists but is not a directory");
+ }
+ }
+ return result;
+}
+
+Status WinEnvIO::DeleteDir(const std::string& name) {
+ Status result;
+ BOOL ret = RX_RemoveDirectory(RX_FN(name).c_str());
+ if (!ret) {
+ auto lastError = GetLastError();
+ result = IOErrorFromWindowsError("Failed to remove dir: " + name,
+ lastError);
+ }
+ return result;
+}
+
+Status WinEnvIO::GetFileSize(const std::string& fname,
+ uint64_t* size) {
+ Status s;
+
+ WIN32_FILE_ATTRIBUTE_DATA attrs;
+ if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard,
+ &attrs)) {
+ ULARGE_INTEGER file_size;
+ file_size.HighPart = attrs.nFileSizeHigh;
+ file_size.LowPart = attrs.nFileSizeLow;
+ *size = file_size.QuadPart;
+ } else {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("Can not get size for: " + fname, lastError);
+ }
+ return s;
+}
+
+uint64_t WinEnvIO::FileTimeToUnixTime(const FILETIME& ftTime) {
+ const uint64_t c_FileTimePerSecond = 10000000U;
+ // UNIX epoch starts on 1970-01-01T00:00:00Z
+ // Windows FILETIME starts on 1601-01-01T00:00:00Z
+ // Therefore, we need to subtract the below number of seconds from
+ // the seconds that we obtain from FILETIME with an obvious loss of
+ // precision
+ const uint64_t c_SecondBeforeUnixEpoch = 11644473600U;
+
+ ULARGE_INTEGER li;
+ li.HighPart = ftTime.dwHighDateTime;
+ li.LowPart = ftTime.dwLowDateTime;
+
+ uint64_t result =
+ (li.QuadPart / c_FileTimePerSecond) - c_SecondBeforeUnixEpoch;
+ return result;
+}
+
+Status WinEnvIO::GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) {
+ Status s;
+
+ WIN32_FILE_ATTRIBUTE_DATA attrs;
+ if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard,
+ &attrs)) {
+ *file_mtime = FileTimeToUnixTime(attrs.ftLastWriteTime);
+ } else {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "Can not get file modification time for: " + fname, lastError);
+ *file_mtime = 0;
+ }
+
+ return s;
+}
+
+Status WinEnvIO::RenameFile(const std::string& src,
+ const std::string& target) {
+ Status result;
+
+ // rename() is not capable of replacing the existing file as on Linux
+ // so use OS API directly
+ if (!RX_MoveFileEx(RX_FN(src).c_str(), RX_FN(target).c_str(),
+ MOVEFILE_REPLACE_EXISTING)) {
+ DWORD lastError = GetLastError();
+
+ std::string text("Failed to rename: ");
+ text.append(src).append(" to: ").append(target);
+
+ result = IOErrorFromWindowsError(text, lastError);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::LinkFile(const std::string& src,
+ const std::string& target) {
+ Status result;
+
+ if (!RX_CreateHardLink(RX_FN(target).c_str(), RX_FN(src).c_str(), NULL)) {
+ DWORD lastError = GetLastError();
+ if (lastError == ERROR_NOT_SAME_DEVICE) {
+ return Status::NotSupported("No cross FS links allowed");
+ }
+
+ std::string text("Failed to link: ");
+ text.append(src).append(" to: ").append(target);
+
+ result = IOErrorFromWindowsError(text, lastError);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::NumFileLinks(const std::string& fname, uint64_t* count) {
+ Status s;
+ HANDLE handle = RX_CreateFile(
+ RX_FN(fname).c_str(), 0,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (INVALID_HANDLE_VALUE == handle) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("NumFileLinks: " + fname, lastError);
+ return s;
+ }
+ UniqueCloseHandlePtr handle_guard(handle, CloseHandleFunc);
+ FILE_STANDARD_INFO standard_info;
+ if (0 != GetFileInformationByHandleEx(handle, FileStandardInfo,
+ &standard_info,
+ sizeof(standard_info))) {
+ *count = standard_info.NumberOfLinks;
+ } else {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("GetFileInformationByHandleEx: " + fname,
+ lastError);
+ }
+ return s;
+}
+
+Status WinEnvIO::AreFilesSame(const std::string& first,
+ const std::string& second, bool* res) {
+// For MinGW builds
+#if (_WIN32_WINNT == _WIN32_WINNT_VISTA)
+ Status s = Status::NotSupported();
+#else
+ assert(res != nullptr);
+ Status s;
+ if (res == nullptr) {
+ s = Status::InvalidArgument("res");
+ return s;
+ }
+
+ // 0 - for access means read metadata
+ HANDLE file_1 = RX_CreateFile(
+ RX_FN(first).c_str(), 0,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == file_1) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("open file: " + first, lastError);
+ return s;
+ }
+ UniqueCloseHandlePtr g_1(file_1, CloseHandleFunc);
+
+ HANDLE file_2 = RX_CreateFile(
+ RX_FN(second).c_str(), 0,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == file_2) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("open file: " + second, lastError);
+ return s;
+ }
+ UniqueCloseHandlePtr g_2(file_2, CloseHandleFunc);
+
+ FILE_ID_INFO FileInfo_1;
+ BOOL result = GetFileInformationByHandleEx(file_1, FileIdInfo, &FileInfo_1,
+ sizeof(FileInfo_1));
+
+ if (!result) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("stat file: " + first, lastError);
+ return s;
+ }
+
+ FILE_ID_INFO FileInfo_2;
+ result = GetFileInformationByHandleEx(file_2, FileIdInfo, &FileInfo_2,
+ sizeof(FileInfo_2));
+
+ if (!result) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("stat file: " + second, lastError);
+ return s;
+ }
+
+ if (FileInfo_1.VolumeSerialNumber == FileInfo_2.VolumeSerialNumber) {
+ *res = (0 == memcmp(FileInfo_1.FileId.Identifier,
+ FileInfo_2.FileId.Identifier,
+ sizeof(FileInfo_1.FileId.Identifier)));
+ } else {
+ *res = false;
+ }
+#endif
+ return s;
+}
+
+Status WinEnvIO::LockFile(const std::string& lockFname,
+ FileLock** lock) {
+ assert(lock != nullptr);
+
+ *lock = NULL;
+ Status result;
+
+ // No-sharing, this is a LOCK file
+ const DWORD ExclusiveAccessON = 0;
+
+ // Obtain exclusive access to the LOCK file
+ // Previously, instead of NORMAL attr we set DELETE on close and that worked
+ // well except with fault_injection test that insists on deleting it.
+ HANDLE hFile = 0;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(RX_FN(lockFname).c_str(),
+ (GENERIC_READ | GENERIC_WRITE),
+ ExclusiveAccessON, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ result = IOErrorFromWindowsError(
+ "Failed to create lock file: " + lockFname, lastError);
+ } else {
+ *lock = new WinFileLock(hFile);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::UnlockFile(FileLock* lock) {
+ Status result;
+
+ assert(lock != nullptr);
+
+ delete lock;
+
+ return result;
+}
+
+Status WinEnvIO::GetTestDirectory(std::string* result) {
+
+ std::string output;
+
+ const char* env = getenv("TEST_TMPDIR");
+ if (env && env[0] != '\0') {
+ output = env;
+ } else {
+ env = getenv("TMP");
+
+ if (env && env[0] != '\0') {
+ output = env;
+ } else {
+ output = "c:\\tmp";
+ }
+ }
+ CreateDir(output);
+
+ output.append("\\testrocksdb-");
+ output.append(std::to_string(_getpid()));
+
+ CreateDir(output);
+
+ output.swap(*result);
+
+ return Status::OK();
+}
+
+Status WinEnvIO::NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) {
+ Status s;
+
+ result->reset();
+
+ HANDLE hFile = 0;
+ {
+ IOSTATS_TIMER_GUARD(open_nanos);
+ hFile = RX_CreateFile(
+ RX_FN(fname).c_str(), GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, // In RocksDb log files are
+ // renamed and deleted before
+ // they are closed. This enables
+ // doing so.
+ NULL,
+ CREATE_ALWAYS, // Original fopen mode is "w"
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ }
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("Failed to open LogFile" + fname, lastError);
+ } else {
+ {
+ // With log files we want to set the true creation time as of now
+ // because the system
+ // for some reason caches the attributes of the previous file that just
+ // been renamed from
+ // this name so auto_roll_logger_test fails
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ // Set creation, last access and last write time to the same value
+ SetFileTime(hFile, &ft, &ft, &ft);
+ }
+ result->reset(new WinLogger(&WinEnvThreads::gettid, hosted_env_, hFile));
+ }
+ return s;
+}
+
+uint64_t WinEnvIO::NowMicros() {
+
+ if (GetSystemTimePreciseAsFileTime_ != NULL) {
+ // all std::chrono clocks on windows proved to return
+ // values that may repeat that is not good enough for some uses.
+ const int64_t c_UnixEpochStartTicks = 116444736000000000LL;
+ const int64_t c_FtToMicroSec = 10;
+
+ // This interface needs to return system time and not
+ // just any microseconds because it is often used as an argument
+ // to TimedWait() on condition variable
+ FILETIME ftSystemTime;
+ GetSystemTimePreciseAsFileTime_(&ftSystemTime);
+
+ LARGE_INTEGER li;
+ li.LowPart = ftSystemTime.dwLowDateTime;
+ li.HighPart = ftSystemTime.dwHighDateTime;
+ // Subtract unix epoch start
+ li.QuadPart -= c_UnixEpochStartTicks;
+ // Convert to microsecs
+ li.QuadPart /= c_FtToMicroSec;
+ return li.QuadPart;
+ }
+ using namespace std::chrono;
+ return duration_cast<microseconds>(system_clock::now().time_since_epoch())
+ .count();
+}
+
+uint64_t WinEnvIO::NowNanos() {
+ if (nano_seconds_per_period_ != 0) {
+ // all std::chrono clocks on windows have the same resolution that is only
+ // good enough for microseconds but not nanoseconds
+ // On Windows 8 and Windows 2012 Server
+ // GetSystemTimePreciseAsFileTime(&current_time) can be used
+ LARGE_INTEGER li;
+ QueryPerformanceCounter(&li);
+ // Convert performance counter to nanoseconds by precomputed ratio.
+ // Directly multiply nano::den with li.QuadPart causes overflow.
+ // Only do this when nano::den is divisible by perf_counter_frequency_,
+ // which most likely is the case in reality. If it's not, fall back to
+ // high_resolution_clock, which may be less precise under old compilers.
+ li.QuadPart *= nano_seconds_per_period_;
+ return li.QuadPart;
+ }
+ using namespace std::chrono;
+ return duration_cast<nanoseconds>(
+ high_resolution_clock::now().time_since_epoch()).count();
+}
+
+Status WinEnvIO::GetHostName(char* name, uint64_t len) {
+ Status s;
+ DWORD nSize = static_cast<DWORD>(
+ std::min<uint64_t>(len, std::numeric_limits<DWORD>::max()));
+
+ if (!::GetComputerNameA(name, &nSize)) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("GetHostName", lastError);
+ } else {
+ name[nSize] = 0;
+ }
+
+ return s;
+}
+
+Status WinEnvIO::GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) {
+ // Check if we already have an absolute path
+ // For test compatibility we will consider starting slash as an
+ // absolute path
+ if ((!db_path.empty() && (db_path[0] == '\\' || db_path[0] == '/')) ||
+ !RX_PathIsRelative(RX_FN(db_path).c_str())) {
+ *output_path = db_path;
+ return Status::OK();
+ }
+
+ RX_FILESTRING result;
+ result.resize(MAX_PATH);
+
+ // Hopefully no changes the current directory while we do this
+ // however _getcwd also suffers from the same limitation
+ DWORD len = RX_GetCurrentDirectory(MAX_PATH, &result[0]);
+ if (len == 0) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError("Failed to get current working directory",
+ lastError);
+ }
+
+ result.resize(len);
+ std::string res = FN_TO_RX(result);
+
+ res.swap(*output_path);
+ return Status::OK();
+}
+
+std::string WinEnvIO::TimeToString(uint64_t secondsSince1970) {
+ std::string result;
+
+ const time_t seconds = secondsSince1970;
+ const int maxsize = 64;
+
+ struct tm t;
+ errno_t ret = localtime_s(&t, &seconds);
+
+ if (ret) {
+ result = std::to_string(seconds);
+ } else {
+ result.resize(maxsize);
+ char* p = &result[0];
+
+ int len = snprintf(p, maxsize, "%04d/%02d/%02d-%02d:%02d:%02d ",
+ t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+ t.tm_min, t.tm_sec);
+ assert(len > 0);
+
+ result.resize(len);
+ }
+
+ return result;
+}
+
+Status WinEnvIO::GetFreeSpace(const std::string& path, uint64_t* diskfree) {
+ assert(diskfree != nullptr);
+ ULARGE_INTEGER freeBytes;
+ BOOL f = RX_GetDiskFreeSpaceEx(RX_FN(path).c_str(), &freeBytes, NULL, NULL);
+ if (f) {
+ *diskfree = freeBytes.QuadPart;
+ return Status::OK();
+ } else {
+ DWORD lastError = GetLastError();
+ return IOErrorFromWindowsError("Failed to get free space: " + path,
+ lastError);
+ }
+}
+
+EnvOptions WinEnvIO::OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const {
+ EnvOptions optimized(env_options);
+ // These two the same as default optimizations
+ optimized.bytes_per_sync = db_options.wal_bytes_per_sync;
+ optimized.writable_file_max_buffer_size =
+ db_options.writable_file_max_buffer_size;
+
+ // This adversely affects %999 on windows
+ optimized.use_mmap_writes = false;
+ // Direct writes will produce a huge perf impact on
+ // Windows. Pre-allocate space for WAL.
+ optimized.use_direct_writes = false;
+ return optimized;
+}
+
+EnvOptions WinEnvIO::OptimizeForManifestWrite(
+ const EnvOptions& env_options) const {
+ EnvOptions optimized(env_options);
+ optimized.use_mmap_writes = false;
+ optimized.use_direct_reads = false;
+ return optimized;
+}
+
+EnvOptions WinEnvIO::OptimizeForManifestRead(
+ const EnvOptions& env_options) const {
+ EnvOptions optimized(env_options);
+ optimized.use_mmap_writes = false;
+ optimized.use_direct_reads = false;
+ return optimized;
+}
+
+// Returns true iff the named directory exists and is a directory.
+bool WinEnvIO::DirExists(const std::string& dname) {
+ WIN32_FILE_ATTRIBUTE_DATA attrs;
+ if (RX_GetFileAttributesEx(RX_FN(dname).c_str(),
+ GetFileExInfoStandard, &attrs)) {
+ return 0 != (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+ }
+ return false;
+}
+
+size_t WinEnvIO::GetSectorSize(const std::string& fname) {
+ size_t sector_size = kSectorSize;
+
+ if (RX_PathIsRelative(RX_FN(fname).c_str())) {
+ return sector_size;
+ }
+
+ // obtain device handle
+ char devicename[7] = "\\\\.\\";
+ int erresult = strncat_s(devicename, sizeof(devicename), fname.c_str(), 2);
+
+ if (erresult) {
+ assert(false);
+ return sector_size;
+ }
+
+ HANDLE hDevice = CreateFile(devicename, 0, 0, nullptr, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ if (hDevice == INVALID_HANDLE_VALUE) {
+ return sector_size;
+ }
+
+ STORAGE_PROPERTY_QUERY spropertyquery;
+ spropertyquery.PropertyId = StorageAccessAlignmentProperty;
+ spropertyquery.QueryType = PropertyStandardQuery;
+
+ BYTE output_buffer[sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)];
+ DWORD output_bytes = 0;
+
+ BOOL ret = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &spropertyquery, sizeof(spropertyquery),
+ output_buffer,
+ sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR),
+ &output_bytes, nullptr);
+
+ if (ret) {
+ sector_size = ((STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR *)output_buffer)->BytesPerLogicalSector;
+ } else {
+ // many devices do not support StorageProcessAlignmentProperty. Any failure here and we
+ // fall back to logical alignment
+
+ DISK_GEOMETRY_EX geometry = { 0 };
+ ret = DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY,
+ nullptr, 0, &geometry, sizeof(geometry), &output_bytes, nullptr);
+ if (ret) {
+ sector_size = geometry.Geometry.BytesPerSector;
+ }
+ }
+
+ if (hDevice != INVALID_HANDLE_VALUE) {
+ CloseHandle(hDevice);
+ }
+
+ return sector_size;
+}
+
+////////////////////////////////////////////////////////////////////////
+// WinEnvThreads
+
+WinEnvThreads::WinEnvThreads(Env* hosted_env)
+ : hosted_env_(hosted_env), thread_pools_(Env::Priority::TOTAL) {
+
+ for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
+ thread_pools_[pool_id].SetThreadPriority(
+ static_cast<Env::Priority>(pool_id));
+ // This allows later initializing the thread-local-env of each thread.
+ thread_pools_[pool_id].SetHostEnv(hosted_env);
+ }
+}
+
+WinEnvThreads::~WinEnvThreads() {
+
+ WaitForJoin();
+
+ for (auto& thpool : thread_pools_) {
+ thpool.JoinAllThreads();
+ }
+}
+
+void WinEnvThreads::Schedule(void(*function)(void*), void* arg,
+ Env::Priority pri, void* tag,
+ void(*unschedFunction)(void* arg)) {
+ assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH);
+ thread_pools_[pri].Schedule(function, arg, tag, unschedFunction);
+}
+
+int WinEnvThreads::UnSchedule(void* arg, Env::Priority pri) {
+ return thread_pools_[pri].UnSchedule(arg);
+}
+
+namespace {
+
+ struct StartThreadState {
+ void(*user_function)(void*);
+ void* arg;
+ };
+
+ void* StartThreadWrapper(void* arg) {
+ std::unique_ptr<StartThreadState> state(
+ reinterpret_cast<StartThreadState*>(arg));
+ state->user_function(state->arg);
+ return nullptr;
+ }
+
+}
+
+void WinEnvThreads::StartThread(void(*function)(void* arg), void* arg) {
+ std::unique_ptr<StartThreadState> state(new StartThreadState);
+ state->user_function = function;
+ state->arg = arg;
+ try {
+ ROCKSDB_NAMESPACE::port::WindowsThread th(&StartThreadWrapper, state.get());
+ state.release();
+
+ std::lock_guard<std::mutex> lg(mu_);
+ threads_to_join_.push_back(std::move(th));
+
+ } catch (const std::system_error& ex) {
+ WinthreadCall("start thread", ex.code());
+ }
+}
+
+void WinEnvThreads::WaitForJoin() {
+ for (auto& th : threads_to_join_) {
+ th.join();
+ }
+ threads_to_join_.clear();
+}
+
+unsigned int WinEnvThreads::GetThreadPoolQueueLen(Env::Priority pri) const {
+ assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH);
+ return thread_pools_[pri].GetQueueLen();
+}
+
+uint64_t WinEnvThreads::gettid() {
+ uint64_t thread_id = GetCurrentThreadId();
+ return thread_id;
+}
+
+uint64_t WinEnvThreads::GetThreadID() const { return gettid(); }
+
+void WinEnvThreads::SleepForMicroseconds(int micros) {
+ std::this_thread::sleep_for(std::chrono::microseconds(micros));
+}
+
+void WinEnvThreads::SetBackgroundThreads(int num, Env::Priority pri) {
+ assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH);
+ thread_pools_[pri].SetBackgroundThreads(num);
+}
+
+int WinEnvThreads::GetBackgroundThreads(Env::Priority pri) {
+ assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH);
+ return thread_pools_[pri].GetBackgroundThreads();
+}
+
+void WinEnvThreads::IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) {
+ assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH);
+ thread_pools_[pri].IncBackgroundThreadsIfNeeded(num);
+}
+
+/////////////////////////////////////////////////////////////////////////
+// WinEnv
+
+WinEnv::WinEnv() : winenv_io_(this), winenv_threads_(this) {
+ // Protected member of the base class
+ thread_status_updater_ = CreateThreadStatusUpdater();
+}
+
+
+WinEnv::~WinEnv() {
+ // All threads must be joined before the deletion of
+ // thread_status_updater_.
+ delete thread_status_updater_;
+}
+
+Status WinEnv::GetThreadList(std::vector<ThreadStatus>* thread_list) {
+ assert(thread_status_updater_);
+ return thread_status_updater_->GetThreadList(thread_list);
+}
+
+Status WinEnv::DeleteFile(const std::string& fname) {
+ return winenv_io_.DeleteFile(fname);
+}
+
+Status WinEnv::Truncate(const std::string& fname, size_t size) {
+ return winenv_io_.Truncate(fname, size);
+}
+
+Status WinEnv::GetCurrentTime(int64_t* unix_time) {
+ return winenv_io_.GetCurrentTime(unix_time);
+}
+
+Status WinEnv::NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) {
+ return winenv_io_.NewSequentialFile(fname, result, options);
+}
+
+Status WinEnv::NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) {
+ return winenv_io_.NewRandomAccessFile(fname, result, options);
+}
+
+Status WinEnv::NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) {
+ return winenv_io_.OpenWritableFile(fname, result, options, false);
+}
+
+Status WinEnv::ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) {
+ return winenv_io_.OpenWritableFile(fname, result, options, true);
+}
+
+Status WinEnv::NewRandomRWFile(const std::string & fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions & options) {
+ return winenv_io_.NewRandomRWFile(fname, result, options);
+}
+
+Status WinEnv::NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) {
+ return winenv_io_.NewMemoryMappedFileBuffer(fname, result);
+}
+
+Status WinEnv::NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) {
+ return winenv_io_.NewDirectory(name, result);
+}
+
+Status WinEnv::FileExists(const std::string& fname) {
+ return winenv_io_.FileExists(fname);
+}
+
+Status WinEnv::GetChildren(const std::string& dir,
+ std::vector<std::string>* result) {
+ return winenv_io_.GetChildren(dir, result);
+}
+
+Status WinEnv::CreateDir(const std::string& name) {
+ return winenv_io_.CreateDir(name);
+}
+
+Status WinEnv::CreateDirIfMissing(const std::string& name) {
+ return winenv_io_.CreateDirIfMissing(name);
+}
+
+Status WinEnv::DeleteDir(const std::string& name) {
+ return winenv_io_.DeleteDir(name);
+}
+
+Status WinEnv::GetFileSize(const std::string& fname,
+ uint64_t* size) {
+ return winenv_io_.GetFileSize(fname, size);
+}
+
+Status WinEnv::GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) {
+ return winenv_io_.GetFileModificationTime(fname, file_mtime);
+}
+
+Status WinEnv::RenameFile(const std::string& src,
+ const std::string& target) {
+ return winenv_io_.RenameFile(src, target);
+}
+
+Status WinEnv::LinkFile(const std::string& src,
+ const std::string& target) {
+ return winenv_io_.LinkFile(src, target);
+}
+
+Status WinEnv::NumFileLinks(const std::string& fname, uint64_t* count) {
+ return winenv_io_.NumFileLinks(fname, count);
+}
+
+Status WinEnv::AreFilesSame(const std::string& first,
+ const std::string& second, bool* res) {
+ return winenv_io_.AreFilesSame(first, second, res);
+}
+
+Status WinEnv::LockFile(const std::string& lockFname,
+ FileLock** lock) {
+ return winenv_io_.LockFile(lockFname, lock);
+}
+
+Status WinEnv::UnlockFile(FileLock* lock) {
+ return winenv_io_.UnlockFile(lock);
+}
+
+Status WinEnv::GetTestDirectory(std::string* result) {
+ return winenv_io_.GetTestDirectory(result);
+}
+
+Status WinEnv::NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) {
+ return winenv_io_.NewLogger(fname, result);
+}
+
+uint64_t WinEnv::NowMicros() {
+ return winenv_io_.NowMicros();
+}
+
+uint64_t WinEnv::NowNanos() {
+ return winenv_io_.NowNanos();
+}
+
+Status WinEnv::GetHostName(char* name, uint64_t len) {
+ return winenv_io_.GetHostName(name, len);
+}
+
+Status WinEnv::GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) {
+ return winenv_io_.GetAbsolutePath(db_path, output_path);
+}
+
+std::string WinEnv::TimeToString(uint64_t secondsSince1970) {
+ return winenv_io_.TimeToString(secondsSince1970);
+}
+
+void WinEnv::Schedule(void(*function)(void*), void* arg, Env::Priority pri,
+ void* tag,
+ void(*unschedFunction)(void* arg)) {
+ return winenv_threads_.Schedule(function, arg, pri, tag, unschedFunction);
+}
+
+int WinEnv::UnSchedule(void* arg, Env::Priority pri) {
+ return winenv_threads_.UnSchedule(arg, pri);
+}
+
+void WinEnv::StartThread(void(*function)(void* arg), void* arg) {
+ return winenv_threads_.StartThread(function, arg);
+}
+
+void WinEnv::WaitForJoin() {
+ return winenv_threads_.WaitForJoin();
+}
+
+unsigned int WinEnv::GetThreadPoolQueueLen(Env::Priority pri) const {
+ return winenv_threads_.GetThreadPoolQueueLen(pri);
+}
+
+uint64_t WinEnv::GetThreadID() const {
+ return winenv_threads_.GetThreadID();
+}
+
+Status WinEnv::GetFreeSpace(const std::string& path, uint64_t* diskfree) {
+ return winenv_io_.GetFreeSpace(path, diskfree);
+}
+
+void WinEnv::SleepForMicroseconds(int micros) {
+ return winenv_threads_.SleepForMicroseconds(micros);
+}
+
+// Allow increasing the number of worker threads.
+void WinEnv::SetBackgroundThreads(int num, Env::Priority pri) {
+ return winenv_threads_.SetBackgroundThreads(num, pri);
+}
+
+int WinEnv::GetBackgroundThreads(Env::Priority pri) {
+ return winenv_threads_.GetBackgroundThreads(pri);
+}
+
+void WinEnv::IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) {
+ return winenv_threads_.IncBackgroundThreadsIfNeeded(num, pri);
+}
+
+EnvOptions WinEnv::OptimizeForManifestRead(
+ const EnvOptions& env_options) const {
+ return winenv_io_.OptimizeForManifestRead(env_options);
+}
+
+EnvOptions WinEnv::OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const {
+ return winenv_io_.OptimizeForLogWrite(env_options, db_options);
+}
+
+EnvOptions WinEnv::OptimizeForManifestWrite(
+ const EnvOptions& env_options) const {
+ return winenv_io_.OptimizeForManifestWrite(env_options);
+}
+
+} // namespace port
+
+std::string Env::GenerateUniqueId() {
+ std::string result;
+
+ UUID uuid;
+ UuidCreateSequential(&uuid);
+
+ RPC_CSTR rpc_str;
+ auto status = UuidToStringA(&uuid, &rpc_str);
+ (void)status;
+ assert(status == RPC_S_OK);
+
+ result = reinterpret_cast<char*>(rpc_str);
+
+ status = RpcStringFreeA(&rpc_str);
+ assert(status == RPC_S_OK);
+
+ return result;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/env_win.h b/storage/rocksdb/rocksdb/port/win/env_win.h
new file mode 100644
index 00000000..5dfca58b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/env_win.h
@@ -0,0 +1,347 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// An Env is an interface used by the rocksdb implementation to access
+// operating system functionality like the filesystem etc. Callers
+// may wish to provide a custom Env object when opening a database to
+// get fine gain control; e.g., to rate limit file system operations.
+//
+// All Env implementations are safe for concurrent access from
+// multiple threads without any external synchronization.
+
+#pragma once
+
+#include "port/win/win_thread.h"
+#include <rocksdb/env.h>
+#include "util/threadpool_imp.h"
+
+#include <stdint.h>
+#include <windows.h>
+
+#include <mutex>
+#include <vector>
+#include <string>
+
+
+#undef GetCurrentTime
+#undef DeleteFile
+#undef GetTickCount
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+// Currently not designed for inheritance but rather a replacement
+class WinEnvThreads {
+public:
+
+ explicit WinEnvThreads(Env* hosted_env);
+
+ ~WinEnvThreads();
+
+ WinEnvThreads(const WinEnvThreads&) = delete;
+ WinEnvThreads& operator=(const WinEnvThreads&) = delete;
+
+ void Schedule(void(*function)(void*), void* arg, Env::Priority pri,
+ void* tag, void(*unschedFunction)(void* arg));
+
+ int UnSchedule(void* arg, Env::Priority pri);
+
+ void StartThread(void(*function)(void* arg), void* arg);
+
+ void WaitForJoin();
+
+ unsigned int GetThreadPoolQueueLen(Env::Priority pri) const;
+
+ static uint64_t gettid();
+
+ uint64_t GetThreadID() const;
+
+ void SleepForMicroseconds(int micros);
+
+ // Allow increasing the number of worker threads.
+ void SetBackgroundThreads(int num, Env::Priority pri);
+ int GetBackgroundThreads(Env::Priority pri);
+
+ void IncBackgroundThreadsIfNeeded(int num, Env::Priority pri);
+
+private:
+
+ Env* hosted_env_;
+ mutable std::mutex mu_;
+ std::vector<ThreadPoolImpl> thread_pools_;
+ std::vector<WindowsThread> threads_to_join_;
+
+};
+
+// Designed for inheritance so can be re-used
+// but certain parts replaced
+class WinEnvIO {
+public:
+ explicit WinEnvIO(Env* hosted_env);
+
+ virtual ~WinEnvIO();
+
+ virtual Status DeleteFile(const std::string& fname);
+
+ Status Truncate(const std::string& fname, size_t size);
+
+ virtual Status GetCurrentTime(int64_t* unix_time);
+
+ virtual Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options);
+
+ // Helper for NewWritable and ReopenWritableFile
+ virtual Status OpenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options,
+ bool reopen);
+
+ virtual Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options);
+
+ // The returned file will only be accessed by one thread at a time.
+ virtual Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options);
+
+ virtual Status NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result);
+
+ virtual Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result);
+
+ virtual Status FileExists(const std::string& fname);
+
+ virtual Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result);
+
+ virtual Status CreateDir(const std::string& name);
+
+ virtual Status CreateDirIfMissing(const std::string& name);
+
+ virtual Status DeleteDir(const std::string& name);
+
+ virtual Status GetFileSize(const std::string& fname, uint64_t* size);
+
+ static uint64_t FileTimeToUnixTime(const FILETIME& ftTime);
+
+ virtual Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime);
+
+ virtual Status RenameFile(const std::string& src, const std::string& target);
+
+ virtual Status LinkFile(const std::string& src, const std::string& target);
+
+ virtual Status NumFileLinks(const std::string& /*fname*/,
+ uint64_t* /*count*/);
+
+ virtual Status AreFilesSame(const std::string& first,
+ const std::string& second, bool* res);
+
+ virtual Status LockFile(const std::string& lockFname, FileLock** lock);
+
+ virtual Status UnlockFile(FileLock* lock);
+
+ virtual Status GetTestDirectory(std::string* result);
+
+ virtual Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result);
+
+ virtual uint64_t NowMicros();
+
+ virtual uint64_t NowNanos();
+
+ virtual Status GetHostName(char* name, uint64_t len);
+
+ virtual Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path);
+
+ // This seems to clash with a macro on Windows, so #undef it here
+#undef GetFreeSpace
+
+ // Get the amount of free disk space
+ virtual Status GetFreeSpace(const std::string& path, uint64_t* diskfree);
+
+ virtual std::string TimeToString(uint64_t secondsSince1970);
+
+ virtual EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const;
+
+ virtual EnvOptions OptimizeForManifestWrite(
+ const EnvOptions& env_options) const;
+
+ virtual EnvOptions OptimizeForManifestRead(
+ const EnvOptions& env_options) const;
+
+ size_t GetPageSize() const { return page_size_; }
+
+ size_t GetAllocationGranularity() const { return allocation_granularity_; }
+
+ uint64_t GetPerfCounterFrequency() const { return perf_counter_frequency_; }
+
+ static size_t GetSectorSize(const std::string& fname);
+
+private:
+ // Returns true iff the named directory exists and is a directory.
+ virtual bool DirExists(const std::string& dname);
+
+ typedef VOID(WINAPI * FnGetSystemTimePreciseAsFileTime)(LPFILETIME);
+
+ Env* hosted_env_;
+ size_t page_size_;
+ size_t allocation_granularity_;
+ uint64_t perf_counter_frequency_;
+ uint64_t nano_seconds_per_period_;
+ FnGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTime_;
+};
+
+class WinEnv : public Env {
+public:
+ WinEnv();
+
+ ~WinEnv();
+
+ Status DeleteFile(const std::string& fname) override;
+
+ Status Truncate(const std::string& fname, size_t size) override;
+
+ Status GetCurrentTime(int64_t* unix_time) override;
+
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ // Create an object that writes to a new file with the specified
+ // name. Deletes any existing file with the same name and creates a
+ // new file. On success, stores a pointer to the new file in
+ // *result and returns OK. On failure stores nullptr in *result and
+ // returns non-OK.
+ //
+ // The returned file will only be accessed by one thread at a time.
+ Status ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override;
+
+ // The returned file will only be accessed by one thread at a time.
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override;
+
+ Status NewMemoryMappedFileBuffer(
+ const std::string& fname,
+ std::unique_ptr<MemoryMappedFileBuffer>* result) override;
+
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override;
+
+ Status FileExists(const std::string& fname) override;
+
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) override;
+
+ Status CreateDir(const std::string& name) override;
+
+ Status CreateDirIfMissing(const std::string& name) override;
+
+ Status DeleteDir(const std::string& name) override;
+
+ Status GetFileSize(const std::string& fname,
+ uint64_t* size) override;
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override;
+
+ Status RenameFile(const std::string& src,
+ const std::string& target) override;
+
+ Status LinkFile(const std::string& src,
+ const std::string& target) override;
+
+ Status NumFileLinks(const std::string& fname, uint64_t* count) override;
+
+ Status AreFilesSame(const std::string& first,
+ const std::string& second, bool* res) override;
+
+ Status LockFile(const std::string& lockFname, FileLock** lock) override;
+
+ Status UnlockFile(FileLock* lock) override;
+
+ Status GetTestDirectory(std::string* result) override;
+
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override;
+
+ uint64_t NowMicros() override;
+
+ uint64_t NowNanos() override;
+
+ Status GetHostName(char* name, uint64_t len) override;
+
+ Status GetAbsolutePath(const std::string& db_path,
+ std::string* output_path) override;
+
+ std::string TimeToString(uint64_t secondsSince1970) override;
+
+ Status GetThreadList(std::vector<ThreadStatus>* thread_list) override;
+
+ void Schedule(void(*function)(void*), void* arg, Env::Priority pri,
+ void* tag, void(*unschedFunction)(void* arg)) override;
+
+ int UnSchedule(void* arg, Env::Priority pri) override;
+
+ void StartThread(void(*function)(void* arg), void* arg) override;
+
+ void WaitForJoin();
+
+ unsigned int GetThreadPoolQueueLen(Env::Priority pri) const override;
+
+ uint64_t GetThreadID() const override;
+
+ // This seems to clash with a macro on Windows, so #undef it here
+#undef GetFreeSpace
+
+ // Get the amount of free disk space
+ Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override;
+
+ void SleepForMicroseconds(int micros) override;
+
+ // Allow increasing the number of worker threads.
+ void SetBackgroundThreads(int num, Env::Priority pri) override;
+ int GetBackgroundThreads(Env::Priority pri) override;
+
+ void IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) override;
+
+ EnvOptions OptimizeForManifestRead(
+ const EnvOptions& env_options) const override;
+
+ EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
+ const DBOptions& db_options) const override;
+
+ EnvOptions OptimizeForManifestWrite(
+ const EnvOptions& env_options) const override;
+
+
+private:
+
+ WinEnvIO winenv_io_;
+ WinEnvThreads winenv_threads_;
+};
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/io_win.cc b/storage/rocksdb/rocksdb/port/win/io_win.cc
new file mode 100644
index 00000000..e6c6890e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/io_win.cc
@@ -0,0 +1,1069 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/win/io_win.h"
+
+#include "monitoring/iostats_context_imp.h"
+#include "test_util/sync_point.h"
+#include "util/aligned_buffer.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+/*
+* DirectIOHelper
+*/
+namespace {
+
+const size_t kSectorSize = 512;
+
+inline
+bool IsPowerOfTwo(const size_t alignment) {
+ return ((alignment) & (alignment - 1)) == 0;
+}
+
+inline
+bool IsSectorAligned(const size_t off) {
+ return (off & (kSectorSize - 1)) == 0;
+}
+
+inline
+bool IsAligned(size_t alignment, const void* ptr) {
+ return ((uintptr_t(ptr)) & (alignment - 1)) == 0;
+}
+}
+
+
+std::string GetWindowsErrSz(DWORD err) {
+ LPSTR lpMsgBuf;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err,
+ 0, // Default language
+ reinterpret_cast<LPSTR>(&lpMsgBuf), 0, NULL);
+
+ std::string Err = lpMsgBuf;
+ LocalFree(lpMsgBuf);
+ return Err;
+}
+
+// We preserve the original name of this interface to denote the original idea
+// behind it.
+// All reads happen by a specified offset and pwrite interface does not change
+// the position of the file pointer. Judging from the man page and errno it does
+// execute
+// lseek atomically to return the position of the file back where it was.
+// WriteFile() does not
+// have this capability. Therefore, for both pread and pwrite the pointer is
+// advanced to the next position
+// which is fine for writes because they are (should be) sequential.
+// Because all the reads/writes happen by the specified offset, the caller in
+// theory should not
+// rely on the current file offset.
+Status pwrite(const WinFileData* file_data, const Slice& data,
+ uint64_t offset, size_t& bytes_written) {
+
+ Status s;
+ bytes_written = 0;
+
+ size_t num_bytes = data.size();
+ if (num_bytes > std::numeric_limits<DWORD>::max()) {
+ // May happen in 64-bit builds where size_t is 64-bits but
+ // long is still 32-bit, but that's the API here at the moment
+ return Status::InvalidArgument("num_bytes is too large for a single write: " +
+ file_data->GetName());
+ }
+
+ OVERLAPPED overlapped = { 0 };
+ ULARGE_INTEGER offsetUnion;
+ offsetUnion.QuadPart = offset;
+
+ overlapped.Offset = offsetUnion.LowPart;
+ overlapped.OffsetHigh = offsetUnion.HighPart;
+
+ DWORD bytesWritten = 0;
+
+ if (FALSE == WriteFile(file_data->GetFileHandle(), data.data(), static_cast<DWORD>(num_bytes),
+ &bytesWritten, &overlapped)) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("WriteFile failed: " + file_data->GetName(),
+ lastError);
+ } else {
+ bytes_written = bytesWritten;
+ }
+
+ return s;
+}
+
+// See comments for pwrite above
+Status pread(const WinFileData* file_data, char* src, size_t num_bytes,
+ uint64_t offset, size_t& bytes_read) {
+
+ Status s;
+ bytes_read = 0;
+
+ if (num_bytes > std::numeric_limits<DWORD>::max()) {
+ return Status::InvalidArgument("num_bytes is too large for a single read: " +
+ file_data->GetName());
+ }
+
+ OVERLAPPED overlapped = { 0 };
+ ULARGE_INTEGER offsetUnion;
+ offsetUnion.QuadPart = offset;
+
+ overlapped.Offset = offsetUnion.LowPart;
+ overlapped.OffsetHigh = offsetUnion.HighPart;
+
+ DWORD bytesRead = 0;
+
+ if (FALSE == ReadFile(file_data->GetFileHandle(), src, static_cast<DWORD>(num_bytes),
+ &bytesRead, &overlapped)) {
+ auto lastError = GetLastError();
+ // EOF is OK with zero bytes read
+ if (lastError != ERROR_HANDLE_EOF) {
+ s = IOErrorFromWindowsError("ReadFile failed: " + file_data->GetName(),
+ lastError);
+ }
+ } else {
+ bytes_read = bytesRead;
+ }
+
+ return s;
+}
+
+// SetFileInformationByHandle() is capable of fast pre-allocates.
+// However, this does not change the file end position unless the file is
+// truncated and the pre-allocated space is not considered filled with zeros.
+Status fallocate(const std::string& filename, HANDLE hFile,
+ uint64_t to_size) {
+ Status status;
+
+ FILE_ALLOCATION_INFO alloc_info;
+ alloc_info.AllocationSize.QuadPart = to_size;
+
+ if (!SetFileInformationByHandle(hFile, FileAllocationInfo, &alloc_info,
+ sizeof(FILE_ALLOCATION_INFO))) {
+ auto lastError = GetLastError();
+ status = IOErrorFromWindowsError(
+ "Failed to pre-allocate space: " + filename, lastError);
+ }
+
+ return status;
+}
+
+Status ftruncate(const std::string& filename, HANDLE hFile,
+ uint64_t toSize) {
+ Status status;
+
+ FILE_END_OF_FILE_INFO end_of_file;
+ end_of_file.EndOfFile.QuadPart = toSize;
+
+ if (!SetFileInformationByHandle(hFile, FileEndOfFileInfo, &end_of_file,
+ sizeof(FILE_END_OF_FILE_INFO))) {
+ auto lastError = GetLastError();
+ status = IOErrorFromWindowsError("Failed to Set end of file: " + filename,
+ lastError);
+ }
+
+ return status;
+}
+
+size_t GetUniqueIdFromFile(HANDLE /*hFile*/, char* /*id*/,
+ size_t /*max_size*/) {
+ // Returning 0 is safe as it causes the table reader to generate a unique ID.
+ // This is suboptimal for performance as it prevents multiple table readers
+ // for the same file from sharing cached blocks. For example, if users have
+ // a low value for `max_open_files`, there can be many table readers opened
+ // for the same file.
+ //
+ // TODO: this is a temporarily solution as it is safe but not optimal for
+ // performance. For more details see discussion in
+ // https://github.com/facebook/rocksdb/pull/5844.
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// WinMmapReadableFile
+
+WinMmapReadableFile::WinMmapReadableFile(const std::string& fileName,
+ HANDLE hFile, HANDLE hMap,
+ const void* mapped_region,
+ size_t length)
+ : WinFileData(fileName, hFile, false /* use_direct_io */),
+ hMap_(hMap),
+ mapped_region_(mapped_region),
+ length_(length) {}
+
+WinMmapReadableFile::~WinMmapReadableFile() {
+ BOOL ret __attribute__((__unused__));
+ ret = ::UnmapViewOfFile(mapped_region_);
+ assert(ret);
+
+ ret = ::CloseHandle(hMap_);
+ assert(ret);
+}
+
+Status WinMmapReadableFile::Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ Status s;
+
+ if (offset > length_) {
+ *result = Slice();
+ return IOError(filename_, EINVAL);
+ } else if (offset + n > length_) {
+ n = length_ - static_cast<size_t>(offset);
+ }
+ *result =
+ Slice(reinterpret_cast<const char*>(mapped_region_)+offset, n);
+ return s;
+}
+
+Status WinMmapReadableFile::InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+}
+
+size_t WinMmapReadableFile::GetUniqueId(char* id, size_t max_size) const {
+ return GetUniqueIdFromFile(hFile_, id, max_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// WinMmapFile
+
+
+// Can only truncate or reserve to a sector size aligned if
+// used on files that are opened with Unbuffered I/O
+Status WinMmapFile::TruncateFile(uint64_t toSize) {
+ return ftruncate(filename_, hFile_, toSize);
+}
+
+Status WinMmapFile::UnmapCurrentRegion() {
+ Status status;
+
+ if (mapped_begin_ != nullptr) {
+ if (!::UnmapViewOfFile(mapped_begin_)) {
+ status = IOErrorFromWindowsError(
+ "Failed to unmap file view: " + filename_, GetLastError());
+ }
+
+ // Move on to the next portion of the file
+ file_offset_ += view_size_;
+
+ // UnmapView automatically sends data to disk but not the metadata
+ // which is good and provides some equivalent of fdatasync() on Linux
+ // therefore, we donot need separate flag for metadata
+ mapped_begin_ = nullptr;
+ mapped_end_ = nullptr;
+ dst_ = nullptr;
+
+ last_sync_ = nullptr;
+ pending_sync_ = false;
+ }
+
+ return status;
+}
+
+Status WinMmapFile::MapNewRegion() {
+
+ Status status;
+
+ assert(mapped_begin_ == nullptr);
+
+ size_t minDiskSize = static_cast<size_t>(file_offset_) + view_size_;
+
+ if (minDiskSize > reserved_size_) {
+ status = Allocate(file_offset_, view_size_);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+
+ // Need to remap
+ if (hMap_ == NULL || reserved_size_ > mapping_size_) {
+
+ if (hMap_ != NULL) {
+ // Unmap the previous one
+ BOOL ret __attribute__((__unused__));
+ ret = ::CloseHandle(hMap_);
+ assert(ret);
+ hMap_ = NULL;
+ }
+
+ ULARGE_INTEGER mappingSize;
+ mappingSize.QuadPart = reserved_size_;
+
+ hMap_ = CreateFileMappingA(
+ hFile_,
+ NULL, // Security attributes
+ PAGE_READWRITE, // There is not a write only mode for mapping
+ mappingSize.HighPart, // Enable mapping the whole file but the actual
+ // amount mapped is determined by MapViewOfFile
+ mappingSize.LowPart,
+ NULL); // Mapping name
+
+ if (NULL == hMap_) {
+ return IOErrorFromWindowsError(
+ "WindowsMmapFile failed to create file mapping for: " + filename_,
+ GetLastError());
+ }
+
+ mapping_size_ = reserved_size_;
+ }
+
+ ULARGE_INTEGER offset;
+ offset.QuadPart = file_offset_;
+
+ // View must begin at the granularity aligned offset
+ mapped_begin_ = reinterpret_cast<char*>(
+ MapViewOfFileEx(hMap_, FILE_MAP_WRITE, offset.HighPart, offset.LowPart,
+ view_size_, NULL));
+
+ if (!mapped_begin_) {
+ status = IOErrorFromWindowsError(
+ "WindowsMmapFile failed to map file view: " + filename_,
+ GetLastError());
+ } else {
+ mapped_end_ = mapped_begin_ + view_size_;
+ dst_ = mapped_begin_;
+ last_sync_ = mapped_begin_;
+ pending_sync_ = false;
+ }
+ return status;
+}
+
+Status WinMmapFile::PreallocateInternal(uint64_t spaceToReserve) {
+ return fallocate(filename_, hFile_, spaceToReserve);
+}
+
+WinMmapFile::WinMmapFile(const std::string& fname, HANDLE hFile,
+ size_t page_size, size_t allocation_granularity,
+ const EnvOptions& options)
+ : WinFileData(fname, hFile, false),
+ WritableFile(options),
+ hMap_(NULL),
+ page_size_(page_size),
+ allocation_granularity_(allocation_granularity),
+ reserved_size_(0),
+ mapping_size_(0),
+ view_size_(0),
+ mapped_begin_(nullptr),
+ mapped_end_(nullptr),
+ dst_(nullptr),
+ last_sync_(nullptr),
+ file_offset_(0),
+ pending_sync_(false) {
+ // Allocation granularity must be obtained from GetSystemInfo() and must be
+ // a power of two.
+ assert(allocation_granularity > 0);
+ assert((allocation_granularity & (allocation_granularity - 1)) == 0);
+
+ assert(page_size > 0);
+ assert((page_size & (page_size - 1)) == 0);
+
+ // Only for memory mapped writes
+ assert(options.use_mmap_writes);
+
+ // View size must be both the multiple of allocation_granularity AND the
+ // page size and the granularity is usually a multiple of a page size.
+ const size_t viewSize = 32 * 1024; // 32Kb similar to the Windows File Cache in buffered mode
+ view_size_ = Roundup(viewSize, allocation_granularity_);
+}
+
+WinMmapFile::~WinMmapFile() {
+ if (hFile_) {
+ this->Close();
+ }
+}
+
+Status WinMmapFile::Append(const Slice& data) {
+ const char* src = data.data();
+ size_t left = data.size();
+
+ while (left > 0) {
+ assert(mapped_begin_ <= dst_);
+ size_t avail = mapped_end_ - dst_;
+
+ if (avail == 0) {
+ Status s = UnmapCurrentRegion();
+ if (s.ok()) {
+ s = MapNewRegion();
+ }
+
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ size_t n = std::min(left, avail);
+ memcpy(dst_, src, n);
+ dst_ += n;
+ src += n;
+ left -= n;
+ pending_sync_ = true;
+ }
+ }
+
+ // Now make sure that the last partial page is padded with zeros if needed
+ size_t bytesToPad = Roundup(size_t(dst_), page_size_) - size_t(dst_);
+ if (bytesToPad > 0) {
+ memset(dst_, 0, bytesToPad);
+ }
+
+ return Status::OK();
+}
+
+// Means Close() will properly take care of truncate
+// and it does not need any additional information
+Status WinMmapFile::Truncate(uint64_t size) {
+ return Status::OK();
+}
+
+Status WinMmapFile::Close() {
+ Status s;
+
+ assert(NULL != hFile_);
+
+ // We truncate to the precise size so no
+ // uninitialized data at the end. SetEndOfFile
+ // which we use does not write zeros and it is good.
+ uint64_t targetSize = GetFileSize();
+
+ if (mapped_begin_ != nullptr) {
+ // Sync before unmapping to make sure everything
+ // is on disk and there is not a lazy writing
+ // so we are deterministic with the tests
+ Sync();
+ s = UnmapCurrentRegion();
+ }
+
+ if (NULL != hMap_) {
+ BOOL ret = ::CloseHandle(hMap_);
+ if (!ret && s.ok()) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "Failed to Close mapping for file: " + filename_, lastError);
+ }
+
+ hMap_ = NULL;
+ }
+
+ if (hFile_ != NULL) {
+
+ TruncateFile(targetSize);
+
+ BOOL ret = ::CloseHandle(hFile_);
+ hFile_ = NULL;
+
+ if (!ret && s.ok()) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "Failed to close file map handle: " + filename_, lastError);
+ }
+ }
+
+ return s;
+}
+
+Status WinMmapFile::Flush() { return Status::OK(); }
+
+// Flush only data
+Status WinMmapFile::Sync() {
+ Status s;
+
+ // Some writes occurred since last sync
+ if (dst_ > last_sync_) {
+ assert(mapped_begin_);
+ assert(dst_);
+ assert(dst_ > mapped_begin_);
+ assert(dst_ < mapped_end_);
+
+ size_t page_begin =
+ TruncateToPageBoundary(page_size_, last_sync_ - mapped_begin_);
+ size_t page_end =
+ TruncateToPageBoundary(page_size_, dst_ - mapped_begin_ - 1);
+
+ // Flush only the amount of that is a multiple of pages
+ if (!::FlushViewOfFile(mapped_begin_ + page_begin,
+ (page_end - page_begin) + page_size_)) {
+ s = IOErrorFromWindowsError("Failed to FlushViewOfFile: " + filename_,
+ GetLastError());
+ } else {
+ last_sync_ = dst_;
+ }
+ }
+
+ return s;
+}
+
+/**
+* Flush data as well as metadata to stable storage.
+*/
+Status WinMmapFile::Fsync() {
+ Status s = Sync();
+
+ // Flush metadata
+ if (s.ok() && pending_sync_) {
+ if (!::FlushFileBuffers(hFile_)) {
+ s = IOErrorFromWindowsError("Failed to FlushFileBuffers: " + filename_,
+ GetLastError());
+ }
+ pending_sync_ = false;
+ }
+
+ return s;
+}
+
+/**
+* Get the size of valid data in the file. This will not match the
+* size that is returned from the filesystem because we use mmap
+* to extend file by map_size every time.
+*/
+uint64_t WinMmapFile::GetFileSize() {
+ size_t used = dst_ - mapped_begin_;
+ return file_offset_ + used;
+}
+
+Status WinMmapFile::InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+}
+
+Status WinMmapFile::Allocate(uint64_t offset, uint64_t len) {
+ Status status;
+ TEST_KILL_RANDOM("WinMmapFile::Allocate", rocksdb_kill_odds);
+
+ // Make sure that we reserve an aligned amount of space
+ // since the reservation block size is driven outside so we want
+ // to check if we are ok with reservation here
+ size_t spaceToReserve = Roundup(static_cast<size_t>(offset + len), view_size_);
+ // Nothing to do
+ if (spaceToReserve <= reserved_size_) {
+ return status;
+ }
+
+ IOSTATS_TIMER_GUARD(allocate_nanos);
+ status = PreallocateInternal(spaceToReserve);
+ if (status.ok()) {
+ reserved_size_ = spaceToReserve;
+ }
+ return status;
+}
+
+size_t WinMmapFile::GetUniqueId(char* id, size_t max_size) const {
+ return GetUniqueIdFromFile(hFile_, id, max_size);
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// WinSequentialFile
+
+WinSequentialFile::WinSequentialFile(const std::string& fname, HANDLE f,
+ const EnvOptions& options)
+ : WinFileData(fname, f, options.use_direct_reads) {}
+
+WinSequentialFile::~WinSequentialFile() {
+ assert(hFile_ != INVALID_HANDLE_VALUE);
+}
+
+Status WinSequentialFile::Read(size_t n, Slice* result, char* scratch) {
+ Status s;
+ size_t r = 0;
+
+ assert(result != nullptr);
+ if (WinFileData::use_direct_io()) {
+ return Status::NotSupported("Read() does not support direct_io");
+ }
+
+ // Windows ReadFile API accepts a DWORD.
+ // While it is possible to read in a loop if n is too big
+ // it is an unlikely case.
+ if (n > std::numeric_limits<DWORD>::max()) {
+ return Status::InvalidArgument("n is too big for a single ReadFile: "
+ + filename_);
+ }
+
+ DWORD bytesToRead = static_cast<DWORD>(n); //cast is safe due to the check above
+ DWORD bytesRead = 0;
+ BOOL ret = ReadFile(hFile_, scratch, bytesToRead, &bytesRead, NULL);
+ if (ret != FALSE) {
+ r = bytesRead;
+ } else {
+ auto lastError = GetLastError();
+ if (lastError != ERROR_HANDLE_EOF) {
+ s = IOErrorFromWindowsError("ReadFile failed: " + filename_,
+ lastError);
+ }
+ }
+
+ *result = Slice(scratch, r);
+ return s;
+}
+
+Status WinSequentialFile::PositionedReadInternal(char* src, size_t numBytes,
+ uint64_t offset, size_t& bytes_read) const {
+ return pread(this, src, numBytes, offset, bytes_read);
+}
+
+Status WinSequentialFile::PositionedRead(uint64_t offset, size_t n, Slice* result,
+ char* scratch) {
+
+ Status s;
+
+ if (!WinFileData::use_direct_io()) {
+ return Status::NotSupported("This function is only used for direct_io");
+ }
+
+ if (!IsSectorAligned(static_cast<size_t>(offset)) ||
+ !IsSectorAligned(n)) {
+ return Status::InvalidArgument(
+ "WinSequentialFile::PositionedRead: offset is not properly aligned");
+ }
+
+ size_t bytes_read = 0; // out param
+ s = PositionedReadInternal(scratch, static_cast<size_t>(n), offset, bytes_read);
+ *result = Slice(scratch, bytes_read);
+ return s;
+}
+
+
+Status WinSequentialFile::Skip(uint64_t n) {
+ // Can't handle more than signed max as SetFilePointerEx accepts a signed 64-bit
+ // integer. As such it is a highly unlikley case to have n so large.
+ if (n > static_cast<uint64_t>(std::numeric_limits<LONGLONG>::max())) {
+ return Status::InvalidArgument("n is too large for a single SetFilePointerEx() call" +
+ filename_);
+ }
+
+ LARGE_INTEGER li;
+ li.QuadPart = static_cast<LONGLONG>(n); //cast is safe due to the check above
+ BOOL ret = SetFilePointerEx(hFile_, li, NULL, FILE_CURRENT);
+ if (ret == FALSE) {
+ auto lastError = GetLastError();
+ return IOErrorFromWindowsError("Skip SetFilePointerEx():" + filename_,
+ lastError);
+ }
+ return Status::OK();
+}
+
+Status WinSequentialFile::InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+/// WinRandomAccessBase
+
+inline
+Status WinRandomAccessImpl::PositionedReadInternal(char* src,
+ size_t numBytes,
+ uint64_t offset,
+ size_t& bytes_read) const {
+ return pread(file_base_, src, numBytes, offset, bytes_read);
+}
+
+inline
+WinRandomAccessImpl::WinRandomAccessImpl(WinFileData* file_base,
+ size_t alignment,
+ const EnvOptions& options) :
+ file_base_(file_base),
+ alignment_(alignment) {
+
+ assert(!options.use_mmap_reads);
+}
+
+inline
+Status WinRandomAccessImpl::ReadImpl(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+
+ Status s;
+
+ // Check buffer alignment
+ if (file_base_->use_direct_io()) {
+ if (!IsSectorAligned(static_cast<size_t>(offset)) ||
+ !IsAligned(alignment_, scratch)) {
+ return Status::InvalidArgument(
+ "WinRandomAccessImpl::ReadImpl: offset or scratch is not properly aligned");
+ }
+ }
+
+ if (n == 0) {
+ *result = Slice(scratch, 0);
+ return s;
+ }
+
+ size_t bytes_read = 0;
+ s = PositionedReadInternal(scratch, n, offset, bytes_read);
+ *result = Slice(scratch, bytes_read);
+ return s;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/// WinRandomAccessFile
+
+WinRandomAccessFile::WinRandomAccessFile(const std::string& fname, HANDLE hFile,
+ size_t alignment,
+ const EnvOptions& options)
+ : WinFileData(fname, hFile, options.use_direct_reads),
+ WinRandomAccessImpl(this, alignment, options) {}
+
+WinRandomAccessFile::~WinRandomAccessFile() {
+}
+
+Status WinRandomAccessFile::Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ return ReadImpl(offset, n, result, scratch);
+}
+
+Status WinRandomAccessFile::InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+}
+
+size_t WinRandomAccessFile::GetUniqueId(char* id, size_t max_size) const {
+ return GetUniqueIdFromFile(GetFileHandle(), id, max_size);
+}
+
+size_t WinRandomAccessFile::GetRequiredBufferAlignment() const {
+ return GetAlignment();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// WinWritableImpl
+//
+
+inline
+Status WinWritableImpl::PreallocateInternal(uint64_t spaceToReserve) {
+ return fallocate(file_data_->GetName(), file_data_->GetFileHandle(), spaceToReserve);
+}
+
+inline
+WinWritableImpl::WinWritableImpl(WinFileData* file_data, size_t alignment)
+ : file_data_(file_data),
+ alignment_(alignment),
+ next_write_offset_(0),
+ reservedsize_(0) {
+
+ // Query current position in case ReopenWritableFile is called
+ // This position is only important for buffered writes
+ // for unbuffered writes we explicitely specify the position.
+ LARGE_INTEGER zero_move;
+ zero_move.QuadPart = 0; // Do not move
+ LARGE_INTEGER pos;
+ pos.QuadPart = 0;
+ BOOL ret = SetFilePointerEx(file_data_->GetFileHandle(), zero_move, &pos,
+ FILE_CURRENT);
+ // Querying no supped to fail
+ if (ret != 0) {
+ next_write_offset_ = pos.QuadPart;
+ } else {
+ assert(false);
+ }
+}
+
+inline
+Status WinWritableImpl::AppendImpl(const Slice& data) {
+
+ Status s;
+
+ if (data.size() > std::numeric_limits<DWORD>::max()) {
+ return Status::InvalidArgument("data is too long for a single write" +
+ file_data_->GetName());
+ }
+
+ size_t bytes_written = 0; // out param
+
+ if (file_data_->use_direct_io()) {
+ // With no offset specified we are appending
+ // to the end of the file
+ assert(IsSectorAligned(next_write_offset_));
+ if (!IsSectorAligned(data.size()) ||
+ !IsAligned(static_cast<size_t>(GetAlignement()), data.data())) {
+ s = Status::InvalidArgument(
+ "WriteData must be page aligned, size must be sector aligned");
+ } else {
+ s = pwrite(file_data_, data, next_write_offset_, bytes_written);
+ }
+ } else {
+
+ DWORD bytesWritten = 0;
+ if (!WriteFile(file_data_->GetFileHandle(), data.data(),
+ static_cast<DWORD>(data.size()), &bytesWritten, NULL)) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "Failed to WriteFile: " + file_data_->GetName(),
+ lastError);
+ } else {
+ bytes_written = bytesWritten;
+ }
+ }
+
+ if(s.ok()) {
+ if (bytes_written == data.size()) {
+ // This matters for direct_io cases where
+ // we rely on the fact that next_write_offset_
+ // is sector aligned
+ next_write_offset_ += bytes_written;
+ } else {
+ s = Status::IOError("Failed to write all bytes: " +
+ file_data_->GetName());
+ }
+ }
+
+ return s;
+}
+
+inline
+Status WinWritableImpl::PositionedAppendImpl(const Slice& data, uint64_t offset) {
+
+ if(file_data_->use_direct_io()) {
+ if (!IsSectorAligned(static_cast<size_t>(offset)) ||
+ !IsSectorAligned(data.size()) ||
+ !IsAligned(static_cast<size_t>(GetAlignement()), data.data())) {
+ return Status::InvalidArgument(
+ "Data and offset must be page aligned, size must be sector aligned");
+ }
+ }
+
+ size_t bytes_written = 0;
+ Status s = pwrite(file_data_, data, offset, bytes_written);
+
+ if(s.ok()) {
+ if (bytes_written == data.size()) {
+ // For sequential write this would be simple
+ // size extension by data.size()
+ uint64_t write_end = offset + bytes_written;
+ if (write_end >= next_write_offset_) {
+ next_write_offset_ = write_end;
+ }
+ } else {
+ s = Status::IOError("Failed to write all of the requested data: " +
+ file_data_->GetName());
+ }
+ }
+ return s;
+}
+
+inline
+Status WinWritableImpl::TruncateImpl(uint64_t size) {
+
+ // It is tempting to check for the size for sector alignment
+ // but truncation may come at the end and there is not a requirement
+ // for this to be sector aligned so long as we do not attempt to write
+ // after that. The interface docs state that the behavior is undefined
+ // in that case.
+ Status s = ftruncate(file_data_->GetName(), file_data_->GetFileHandle(),
+ size);
+
+ if (s.ok()) {
+ next_write_offset_ = size;
+ }
+ return s;
+}
+
+inline
+Status WinWritableImpl::CloseImpl() {
+
+ Status s;
+
+ auto hFile = file_data_->GetFileHandle();
+ assert(INVALID_HANDLE_VALUE != hFile);
+
+ if (!::FlushFileBuffers(hFile)) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("FlushFileBuffers failed at Close() for: " +
+ file_data_->GetName(),
+ lastError);
+ }
+
+ if(!file_data_->CloseFile() && s.ok()) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("CloseHandle failed for: " + file_data_->GetName(),
+ lastError);
+ }
+ return s;
+}
+
+inline
+Status WinWritableImpl::SyncImpl() {
+ Status s;
+ if (!::FlushFileBuffers (file_data_->GetFileHandle())) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError(
+ "FlushFileBuffers failed at Sync() for: " + file_data_->GetName(), lastError);
+ }
+ return s;
+}
+
+
+inline
+Status WinWritableImpl::AllocateImpl(uint64_t offset, uint64_t len) {
+ Status status;
+ TEST_KILL_RANDOM("WinWritableFile::Allocate", rocksdb_kill_odds);
+
+ // Make sure that we reserve an aligned amount of space
+ // since the reservation block size is driven outside so we want
+ // to check if we are ok with reservation here
+ size_t spaceToReserve = Roundup(static_cast<size_t>(offset + len), static_cast<size_t>(alignment_));
+ // Nothing to do
+ if (spaceToReserve <= reservedsize_) {
+ return status;
+ }
+
+ IOSTATS_TIMER_GUARD(allocate_nanos);
+ status = PreallocateInternal(spaceToReserve);
+ if (status.ok()) {
+ reservedsize_ = spaceToReserve;
+ }
+ return status;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// WinWritableFile
+
+WinWritableFile::WinWritableFile(const std::string& fname, HANDLE hFile,
+ size_t alignment, size_t /* capacity */,
+ const EnvOptions& options)
+ : WinFileData(fname, hFile, options.use_direct_writes),
+ WinWritableImpl(this, alignment),
+ WritableFile(options) {
+ assert(!options.use_mmap_writes);
+}
+
+WinWritableFile::~WinWritableFile() {
+}
+
+// Indicates if the class makes use of direct I/O
+bool WinWritableFile::use_direct_io() const { return WinFileData::use_direct_io(); }
+
+size_t WinWritableFile::GetRequiredBufferAlignment() const {
+ return static_cast<size_t>(GetAlignement());
+}
+
+Status WinWritableFile::Append(const Slice& data) {
+ return AppendImpl(data);
+}
+
+Status WinWritableFile::PositionedAppend(const Slice& data, uint64_t offset) {
+ return PositionedAppendImpl(data, offset);
+}
+
+// Need to implement this so the file is truncated correctly
+// when buffered and unbuffered mode
+Status WinWritableFile::Truncate(uint64_t size) {
+ return TruncateImpl(size);
+}
+
+Status WinWritableFile::Close() {
+ return CloseImpl();
+}
+
+ // write out the cached data to the OS cache
+ // This is now taken care of the WritableFileWriter
+Status WinWritableFile::Flush() {
+ return Status::OK();
+}
+
+Status WinWritableFile::Sync() {
+ return SyncImpl();
+}
+
+Status WinWritableFile::Fsync() { return SyncImpl(); }
+
+bool WinWritableFile::IsSyncThreadSafe() const { return true; }
+
+uint64_t WinWritableFile::GetFileSize() {
+ return GetFileNextWriteOffset();
+}
+
+Status WinWritableFile::Allocate(uint64_t offset, uint64_t len) {
+ return AllocateImpl(offset, len);
+}
+
+size_t WinWritableFile::GetUniqueId(char* id, size_t max_size) const {
+ return GetUniqueIdFromFile(GetFileHandle(), id, max_size);
+}
+
+/////////////////////////////////////////////////////////////////////////
+/// WinRandomRWFile
+
+WinRandomRWFile::WinRandomRWFile(const std::string& fname, HANDLE hFile,
+ size_t alignment, const EnvOptions& options)
+ : WinFileData(fname, hFile,
+ options.use_direct_reads && options.use_direct_writes),
+ WinRandomAccessImpl(this, alignment, options),
+ WinWritableImpl(this, alignment) {}
+
+bool WinRandomRWFile::use_direct_io() const { return WinFileData::use_direct_io(); }
+
+size_t WinRandomRWFile::GetRequiredBufferAlignment() const {
+ return static_cast<size_t>(GetAlignement());
+}
+
+Status WinRandomRWFile::Write(uint64_t offset, const Slice & data) {
+ return PositionedAppendImpl(data, offset);
+}
+
+Status WinRandomRWFile::Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ return ReadImpl(offset, n, result, scratch);
+}
+
+Status WinRandomRWFile::Flush() {
+ return Status::OK();
+}
+
+Status WinRandomRWFile::Sync() {
+ return SyncImpl();
+}
+
+Status WinRandomRWFile::Close() {
+ return CloseImpl();
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// WinMemoryMappedBufer
+WinMemoryMappedBuffer::~WinMemoryMappedBuffer() {
+ BOOL ret
+#if defined(_MSC_VER)
+ = FALSE;
+#else
+ __attribute__((__unused__));
+#endif
+ if (base_ != nullptr) {
+ ret = ::UnmapViewOfFile(base_);
+ assert(ret);
+ base_ = nullptr;
+ }
+ if (map_handle_ != NULL && map_handle_ != INVALID_HANDLE_VALUE) {
+ ret = ::CloseHandle(map_handle_);
+ assert(ret);
+ map_handle_ = NULL;
+ }
+ if (file_handle_ != NULL && file_handle_ != INVALID_HANDLE_VALUE) {
+ ret = ::CloseHandle(file_handle_);
+ assert(ret);
+ file_handle_ = NULL;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// WinDirectory
+
+Status WinDirectory::Fsync() { return Status::OK(); }
+
+size_t WinDirectory::GetUniqueId(char* id, size_t max_size) const {
+ return GetUniqueIdFromFile(handle_, id, max_size);
+}
+//////////////////////////////////////////////////////////////////////////
+/// WinFileLock
+
+WinFileLock::~WinFileLock() {
+ BOOL ret __attribute__((__unused__));
+ ret = ::CloseHandle(hFile_);
+ assert(ret);
+}
+
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/io_win.h b/storage/rocksdb/rocksdb/port/win/io_win.h
new file mode 100644
index 00000000..4fc2ba49
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/io_win.h
@@ -0,0 +1,456 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <mutex>
+#include <string>
+
+#include "rocksdb/status.h"
+#include "rocksdb/env.h"
+#include "util/aligned_buffer.h"
+
+#include <windows.h>
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+std::string GetWindowsErrSz(DWORD err);
+
+inline Status IOErrorFromWindowsError(const std::string& context, DWORD err) {
+ return ((err == ERROR_HANDLE_DISK_FULL) || (err == ERROR_DISK_FULL))
+ ? Status::NoSpace(context, GetWindowsErrSz(err))
+ : ((err == ERROR_FILE_NOT_FOUND) || (err == ERROR_PATH_NOT_FOUND))
+ ? Status::PathNotFound(context, GetWindowsErrSz(err))
+ : Status::IOError(context, GetWindowsErrSz(err));
+}
+
+inline Status IOErrorFromLastWindowsError(const std::string& context) {
+ return IOErrorFromWindowsError(context, GetLastError());
+}
+
+inline Status IOError(const std::string& context, int err_number) {
+ return (err_number == ENOSPC)
+ ? Status::NoSpace(context, strerror(err_number))
+ : (err_number == ENOENT)
+ ? Status::PathNotFound(context, strerror(err_number))
+ : Status::IOError(context, strerror(err_number));
+}
+
+class WinFileData;
+
+Status pwrite(const WinFileData* file_data, const Slice& data,
+ uint64_t offset, size_t& bytes_written);
+
+Status pread(const WinFileData* file_data, char* src, size_t num_bytes,
+ uint64_t offset, size_t& bytes_read);
+
+Status fallocate(const std::string& filename, HANDLE hFile, uint64_t to_size);
+
+Status ftruncate(const std::string& filename, HANDLE hFile, uint64_t toSize);
+
+size_t GetUniqueIdFromFile(HANDLE hFile, char* id, size_t max_size);
+
+class WinFileData {
+ protected:
+ const std::string filename_;
+ HANDLE hFile_;
+ // If true, the I/O issued would be direct I/O which the buffer
+ // will need to be aligned (not sure there is a guarantee that the buffer
+ // passed in is aligned).
+ const bool use_direct_io_;
+
+ public:
+ // We want this class be usable both for inheritance (prive
+ // or protected) and for containment so __ctor and __dtor public
+ WinFileData(const std::string& filename, HANDLE hFile, bool direct_io)
+ : filename_(filename), hFile_(hFile), use_direct_io_(direct_io) {}
+
+ virtual ~WinFileData() { this->CloseFile(); }
+
+ bool CloseFile() {
+ bool result = true;
+
+ if (hFile_ != NULL && hFile_ != INVALID_HANDLE_VALUE) {
+ result = ::CloseHandle(hFile_);
+ assert(result);
+ hFile_ = NULL;
+ }
+ return result;
+ }
+
+ const std::string& GetName() const { return filename_; }
+
+ HANDLE GetFileHandle() const { return hFile_; }
+
+ bool use_direct_io() const { return use_direct_io_; }
+
+ WinFileData(const WinFileData&) = delete;
+ WinFileData& operator=(const WinFileData&) = delete;
+};
+
+class WinSequentialFile : protected WinFileData, public SequentialFile {
+
+ // Override for behavior change when creating a custom env
+ virtual Status PositionedReadInternal(char* src, size_t numBytes,
+ uint64_t offset, size_t& bytes_read) const;
+
+public:
+ WinSequentialFile(const std::string& fname, HANDLE f,
+ const EnvOptions& options);
+
+ ~WinSequentialFile();
+
+ WinSequentialFile(const WinSequentialFile&) = delete;
+ WinSequentialFile& operator=(const WinSequentialFile&) = delete;
+
+ virtual Status Read(size_t n, Slice* result, char* scratch) override;
+ virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result,
+ char* scratch) override;
+
+ virtual Status Skip(uint64_t n) override;
+
+ virtual Status InvalidateCache(size_t offset, size_t length) override;
+
+ virtual bool use_direct_io() const override { return WinFileData::use_direct_io(); }
+};
+
+// mmap() based random-access
+class WinMmapReadableFile : private WinFileData, public RandomAccessFile {
+ HANDLE hMap_;
+
+ const void* mapped_region_;
+ const size_t length_;
+
+ public:
+ // mapped_region_[0,length-1] contains the mmapped contents of the file.
+ WinMmapReadableFile(const std::string& fileName, HANDLE hFile, HANDLE hMap,
+ const void* mapped_region, size_t length);
+
+ ~WinMmapReadableFile();
+
+ WinMmapReadableFile(const WinMmapReadableFile&) = delete;
+ WinMmapReadableFile& operator=(const WinMmapReadableFile&) = delete;
+
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override;
+
+ virtual Status InvalidateCache(size_t offset, size_t length) override;
+
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+};
+
+// We preallocate and use memcpy to append new
+// data to the file. This is safe since we either properly close the
+// file before reading from it, or for log files, the reading code
+// knows enough to skip zero suffixes.
+class WinMmapFile : private WinFileData, public WritableFile {
+ private:
+ HANDLE hMap_;
+
+ const size_t page_size_; // We flush the mapping view in page_size
+ // increments. We may decide if this is a memory
+ // page size or SSD page size
+ const size_t
+ allocation_granularity_; // View must start at such a granularity
+
+ size_t reserved_size_; // Preallocated size
+
+ size_t mapping_size_; // The max size of the mapping object
+ // we want to guess the final file size to minimize the remapping
+ size_t view_size_; // How much memory to map into a view at a time
+
+ char* mapped_begin_; // Must begin at the file offset that is aligned with
+ // allocation_granularity_
+ char* mapped_end_;
+ char* dst_; // Where to write next (in range [mapped_begin_,mapped_end_])
+ char* last_sync_; // Where have we synced up to
+
+ uint64_t file_offset_; // Offset of mapped_begin_ in file
+
+ // Do we have unsynced writes?
+ bool pending_sync_;
+
+ // Can only truncate or reserve to a sector size aligned if
+ // used on files that are opened with Unbuffered I/O
+ Status TruncateFile(uint64_t toSize);
+
+ Status UnmapCurrentRegion();
+
+ Status MapNewRegion();
+
+ virtual Status PreallocateInternal(uint64_t spaceToReserve);
+
+ public:
+ WinMmapFile(const std::string& fname, HANDLE hFile, size_t page_size,
+ size_t allocation_granularity, const EnvOptions& options);
+
+ ~WinMmapFile();
+
+ WinMmapFile(const WinMmapFile&) = delete;
+ WinMmapFile& operator=(const WinMmapFile&) = delete;
+
+ virtual Status Append(const Slice& data) override;
+
+ // Means Close() will properly take care of truncate
+ // and it does not need any additional information
+ virtual Status Truncate(uint64_t size) override;
+
+ virtual Status Close() override;
+
+ virtual Status Flush() override;
+
+ // Flush only data
+ virtual Status Sync() override;
+
+ /**
+ * Flush data as well as metadata to stable storage.
+ */
+ virtual Status Fsync() override;
+
+ /**
+ * Get the size of valid data in the file. This will not match the
+ * size that is returned from the filesystem because we use mmap
+ * to extend file by map_size every time.
+ */
+ virtual uint64_t GetFileSize() override;
+
+ virtual Status InvalidateCache(size_t offset, size_t length) override;
+
+ virtual Status Allocate(uint64_t offset, uint64_t len) override;
+
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+};
+
+class WinRandomAccessImpl {
+ protected:
+ WinFileData* file_base_;
+ size_t alignment_;
+
+ // Override for behavior change when creating a custom env
+ virtual Status PositionedReadInternal(char* src, size_t numBytes,
+ uint64_t offset, size_t& bytes_read) const;
+
+ WinRandomAccessImpl(WinFileData* file_base, size_t alignment,
+ const EnvOptions& options);
+
+ virtual ~WinRandomAccessImpl() {}
+
+ Status ReadImpl(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const;
+
+ size_t GetAlignment() const { return alignment_; }
+
+ public:
+
+ WinRandomAccessImpl(const WinRandomAccessImpl&) = delete;
+ WinRandomAccessImpl& operator=(const WinRandomAccessImpl&) = delete;
+};
+
+// pread() based random-access
+class WinRandomAccessFile
+ : private WinFileData,
+ protected WinRandomAccessImpl, // Want to be able to override
+ // PositionedReadInternal
+ public RandomAccessFile {
+ public:
+ WinRandomAccessFile(const std::string& fname, HANDLE hFile, size_t alignment,
+ const EnvOptions& options);
+
+ ~WinRandomAccessFile();
+
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override;
+
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+
+ virtual bool use_direct_io() const override { return WinFileData::use_direct_io(); }
+
+ virtual Status InvalidateCache(size_t offset, size_t length) override;
+
+ virtual size_t GetRequiredBufferAlignment() const override;
+};
+
+// This is a sequential write class. It has been mimicked (as others) after
+// the original Posix class. We add support for unbuffered I/O on windows as
+// well
+// we utilize the original buffer as an alignment buffer to write directly to
+// file with no buffering.
+// No buffering requires that the provided buffer is aligned to the physical
+// sector size (SSD page size) and
+// that all SetFilePointer() operations to occur with such an alignment.
+// We thus always write in sector/page size increments to the drive and leave
+// the tail for the next write OR for Close() at which point we pad with zeros.
+// No padding is required for
+// buffered access.
+class WinWritableImpl {
+ protected:
+ WinFileData* file_data_;
+ const uint64_t alignment_;
+ uint64_t next_write_offset_; // Needed because Windows does not support O_APPEND
+ uint64_t reservedsize_; // how far we have reserved space
+
+ virtual Status PreallocateInternal(uint64_t spaceToReserve);
+
+ WinWritableImpl(WinFileData* file_data, size_t alignment);
+
+ ~WinWritableImpl() {}
+
+ uint64_t GetAlignement() const { return alignment_; }
+
+ Status AppendImpl(const Slice& data);
+
+ // Requires that the data is aligned as specified by
+ // GetRequiredBufferAlignment()
+ Status PositionedAppendImpl(const Slice& data, uint64_t offset);
+
+ Status TruncateImpl(uint64_t size);
+
+ Status CloseImpl();
+
+ Status SyncImpl();
+
+ uint64_t GetFileNextWriteOffset() {
+ // Double accounting now here with WritableFileWriter
+ // and this size will be wrong when unbuffered access is used
+ // but tests implement their own writable files and do not use
+ // WritableFileWrapper
+ // so we need to squeeze a square peg through
+ // a round hole here.
+ return next_write_offset_;
+ }
+
+ Status AllocateImpl(uint64_t offset, uint64_t len);
+
+ public:
+ WinWritableImpl(const WinWritableImpl&) = delete;
+ WinWritableImpl& operator=(const WinWritableImpl&) = delete;
+};
+
+class WinWritableFile : private WinFileData,
+ protected WinWritableImpl,
+ public WritableFile {
+ public:
+ WinWritableFile(const std::string& fname, HANDLE hFile, size_t alignment,
+ size_t capacity, const EnvOptions& options);
+
+ ~WinWritableFile();
+
+ virtual Status Append(const Slice& data) override;
+
+ // Requires that the data is aligned as specified by
+ // GetRequiredBufferAlignment()
+ virtual Status PositionedAppend(const Slice& data, uint64_t offset) override;
+
+ // Need to implement this so the file is truncated correctly
+ // when buffered and unbuffered mode
+ virtual Status Truncate(uint64_t size) override;
+
+ virtual Status Close() override;
+
+ // write out the cached data to the OS cache
+ // This is now taken care of the WritableFileWriter
+ virtual Status Flush() override;
+
+ virtual Status Sync() override;
+
+ virtual Status Fsync() override;
+
+ virtual bool IsSyncThreadSafe() const override;
+
+ // Indicates if the class makes use of direct I/O
+ // Use PositionedAppend
+ virtual bool use_direct_io() const override;
+
+ virtual size_t GetRequiredBufferAlignment() const override;
+
+ virtual uint64_t GetFileSize() override;
+
+ virtual Status Allocate(uint64_t offset, uint64_t len) override;
+
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override;
+};
+
+class WinRandomRWFile : private WinFileData,
+ protected WinRandomAccessImpl,
+ protected WinWritableImpl,
+ public RandomRWFile {
+ public:
+ WinRandomRWFile(const std::string& fname, HANDLE hFile, size_t alignment,
+ const EnvOptions& options);
+
+ ~WinRandomRWFile() {}
+
+ // Indicates if the class makes use of direct I/O
+ // If false you must pass aligned buffer to Write()
+ virtual bool use_direct_io() const override;
+
+ // Use the returned alignment value to allocate aligned
+ // buffer for Write() when use_direct_io() returns true
+ virtual size_t GetRequiredBufferAlignment() const override;
+
+ // Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
+ // Pass aligned buffer when use_direct_io() returns true.
+ virtual Status Write(uint64_t offset, const Slice& data) override;
+
+ // Read up to `n` bytes starting from offset `offset` and store them in
+ // result, provided `scratch` size should be at least `n`.
+ // Returns Status::OK() on success.
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override;
+
+ virtual Status Flush() override;
+
+ virtual Status Sync() override;
+
+ virtual Status Fsync() { return Sync(); }
+
+ virtual Status Close() override;
+};
+
+class WinMemoryMappedBuffer : public MemoryMappedFileBuffer {
+private:
+ HANDLE file_handle_;
+ HANDLE map_handle_;
+public:
+ WinMemoryMappedBuffer(HANDLE file_handle, HANDLE map_handle, void* base, size_t size) :
+ MemoryMappedFileBuffer(base, size),
+ file_handle_(file_handle),
+ map_handle_(map_handle) {}
+ ~WinMemoryMappedBuffer() override;
+};
+
+class WinDirectory : public Directory {
+ HANDLE handle_;
+ public:
+ explicit WinDirectory(HANDLE h) noexcept : handle_(h) {
+ assert(handle_ != INVALID_HANDLE_VALUE);
+ }
+ ~WinDirectory() {
+ ::CloseHandle(handle_);
+ }
+ virtual Status Fsync() override;
+
+ size_t GetUniqueId(char* id, size_t max_size) const override;
+};
+
+class WinFileLock : public FileLock {
+ public:
+ explicit WinFileLock(HANDLE hFile) : hFile_(hFile) {
+ assert(hFile != NULL);
+ assert(hFile != INVALID_HANDLE_VALUE);
+ }
+
+ ~WinFileLock();
+
+ private:
+ HANDLE hFile_;
+};
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/port_win.cc b/storage/rocksdb/rocksdb/port/win/port_win.cc
new file mode 100644
index 00000000..6e43b1b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/port_win.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+#if !defined(OS_WIN) && !defined(WIN32) && !defined(_WIN32)
+#error Windows Specific Code
+#endif
+
+#include "port/win/port_win.h"
+
+#include <io.h>
+#include "port/port_dirent.h"
+#include "port/sys_time.h"
+
+#include <cstdlib>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <memory>
+#include <exception>
+#include <chrono>
+
+#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES
+// utf8 <-> utf16
+#include <string>
+#include <locale>
+#include <codecvt>
+#endif
+
+#include "logging/logging.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const bool kDefaultToAdaptiveMutex = false;
+
+namespace port {
+
+#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES
+std::string utf16_to_utf8(const std::wstring& utf16) {
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> convert;
+ return convert.to_bytes(utf16);
+}
+
+std::wstring utf8_to_utf16(const std::string& utf8) {
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+ return converter.from_bytes(utf8);
+}
+#endif
+
+void gettimeofday(struct timeval* tv, struct timezone* /* tz */) {
+ using namespace std::chrono;
+
+ microseconds usNow(
+ duration_cast<microseconds>(system_clock::now().time_since_epoch()));
+
+ seconds secNow(duration_cast<seconds>(usNow));
+
+ tv->tv_sec = static_cast<long>(secNow.count());
+ tv->tv_usec = static_cast<long>(usNow.count() -
+ duration_cast<microseconds>(secNow).count());
+}
+
+Mutex::~Mutex() {}
+
+CondVar::~CondVar() {}
+
+void CondVar::Wait() {
+ // Caller must ensure that mutex is held prior to calling this method
+ std::unique_lock<std::mutex> lk(mu_->getLock(), std::adopt_lock);
+#ifndef NDEBUG
+ mu_->locked_ = false;
+#endif
+ cv_.wait(lk);
+#ifndef NDEBUG
+ mu_->locked_ = true;
+#endif
+ // Release ownership of the lock as we don't want it to be unlocked when
+ // it goes out of scope (as we adopted the lock and didn't lock it ourselves)
+ lk.release();
+}
+
+bool CondVar::TimedWait(uint64_t abs_time_us) {
+
+ using namespace std::chrono;
+
+ // MSVC++ library implements wait_until in terms of wait_for so
+ // we need to convert absolute wait into relative wait.
+ microseconds usAbsTime(abs_time_us);
+
+ microseconds usNow(
+ duration_cast<microseconds>(system_clock::now().time_since_epoch()));
+ microseconds relTimeUs =
+ (usAbsTime > usNow) ? (usAbsTime - usNow) : microseconds::zero();
+
+ // Caller must ensure that mutex is held prior to calling this method
+ std::unique_lock<std::mutex> lk(mu_->getLock(), std::adopt_lock);
+#ifndef NDEBUG
+ mu_->locked_ = false;
+#endif
+ std::cv_status cvStatus = cv_.wait_for(lk, relTimeUs);
+#ifndef NDEBUG
+ mu_->locked_ = true;
+#endif
+ // Release ownership of the lock as we don't want it to be unlocked when
+ // it goes out of scope (as we adopted the lock and didn't lock it ourselves)
+ lk.release();
+
+ if (cvStatus == std::cv_status::timeout) {
+ return true;
+ }
+
+ return false;
+}
+
+void CondVar::Signal() { cv_.notify_one(); }
+
+void CondVar::SignalAll() { cv_.notify_all(); }
+
+int PhysicalCoreID() { return GetCurrentProcessorNumber(); }
+
+void InitOnce(OnceType* once, void (*initializer)()) {
+ std::call_once(once->flag_, initializer);
+}
+
+// Private structure, exposed only by pointer
+struct DIR {
+ HANDLE handle_;
+ bool firstread_;
+ RX_WIN32_FIND_DATA data_;
+ dirent entry_;
+
+ DIR() : handle_(INVALID_HANDLE_VALUE),
+ firstread_(true) {}
+
+ DIR(const DIR&) = delete;
+ DIR& operator=(const DIR&) = delete;
+
+ ~DIR() {
+ if (INVALID_HANDLE_VALUE != handle_) {
+ ::FindClose(handle_);
+ }
+ }
+};
+
+DIR* opendir(const char* name) {
+ if (!name || *name == 0) {
+ errno = ENOENT;
+ return nullptr;
+ }
+
+ std::string pattern(name);
+ pattern.append("\\").append("*");
+
+ std::unique_ptr<DIR> dir(new DIR);
+
+ dir->handle_ = RX_FindFirstFileEx(RX_FN(pattern).c_str(),
+ FindExInfoBasic, // Do not want alternative name
+ &dir->data_,
+ FindExSearchNameMatch,
+ NULL, // lpSearchFilter
+ 0);
+
+ if (dir->handle_ == INVALID_HANDLE_VALUE) {
+ return nullptr;
+ }
+
+ RX_FILESTRING x(dir->data_.cFileName, RX_FNLEN(dir->data_.cFileName));
+ strcpy_s(dir->entry_.d_name, sizeof(dir->entry_.d_name),
+ FN_TO_RX(x).c_str());
+
+ return dir.release();
+}
+
+struct dirent* readdir(DIR* dirp) {
+ if (!dirp || dirp->handle_ == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ return nullptr;
+ }
+
+ if (dirp->firstread_) {
+ dirp->firstread_ = false;
+ return &dirp->entry_;
+ }
+
+ auto ret = RX_FindNextFile(dirp->handle_, &dirp->data_);
+
+ if (ret == 0) {
+ return nullptr;
+ }
+
+ RX_FILESTRING x(dirp->data_.cFileName, RX_FNLEN(dirp->data_.cFileName));
+ strcpy_s(dirp->entry_.d_name, sizeof(dirp->entry_.d_name),
+ FN_TO_RX(x).c_str());
+
+ return &dirp->entry_;
+}
+
+int closedir(DIR* dirp) {
+ delete dirp;
+ return 0;
+}
+
+int truncate(const char* path, int64_t length) {
+ if (path == nullptr) {
+ errno = EFAULT;
+ return -1;
+ }
+ return ROCKSDB_NAMESPACE::port::Truncate(path, length);
+}
+
+int Truncate(std::string path, int64_t len) {
+
+ if (len < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ HANDLE hFile =
+ RX_CreateFile(RX_FN(path).c_str(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, // Security attrs
+ OPEN_EXISTING, // Truncate existing file only
+ FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (INVALID_HANDLE_VALUE == hFile) {
+ auto lastError = GetLastError();
+ if (lastError == ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ } else if (lastError == ERROR_ACCESS_DENIED) {
+ errno = EACCES;
+ } else {
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ int result = 0;
+ FILE_END_OF_FILE_INFO end_of_file;
+ end_of_file.EndOfFile.QuadPart = len;
+
+ if (!SetFileInformationByHandle(hFile, FileEndOfFileInfo, &end_of_file,
+ sizeof(FILE_END_OF_FILE_INFO))) {
+ errno = EIO;
+ result = -1;
+ }
+
+ CloseHandle(hFile);
+ return result;
+}
+
+void Crash(const std::string& srcfile, int srcline) {
+ fprintf(stdout, "Crashing at %s:%d\n", srcfile.c_str(), srcline);
+ fflush(stdout);
+ abort();
+}
+
+int GetMaxOpenFiles() { return -1; }
+
+// Assume 4KB page size
+const size_t kPageSize = 4U * 1024U;
+
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/port_win.h b/storage/rocksdb/rocksdb/port/win/port_win.h
new file mode 100644
index 00000000..76fc8cf3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/port_win.h
@@ -0,0 +1,398 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// See port_example.h for documentation for the following types/functions.
+
+#pragma once
+
+// Always want minimum headers
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+#include <string>
+#include <string.h>
+#include <mutex>
+#include <limits>
+#include <condition_variable>
+#include <malloc.h>
+#include <intrin.h>
+
+#include <stdint.h>
+
+#include "port/win/win_thread.h"
+
+#include "rocksdb/options.h"
+
+#undef min
+#undef max
+#undef DeleteFile
+#undef GetCurrentTime
+
+
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif
+
+#undef GetCurrentTime
+#undef DeleteFile
+
+#ifndef _SSIZE_T_DEFINED
+typedef SSIZE_T ssize_t;
+#endif
+
+// size_t printf formatting named in the manner of C99 standard formatting
+// strings such as PRIu64
+// in fact, we could use that one
+#ifndef ROCKSDB_PRIszt
+#define ROCKSDB_PRIszt "Iu"
+#endif
+
+#ifdef _MSC_VER
+#define __attribute__(A)
+
+// Thread local storage on Linux
+// There is thread_local in C++11
+#ifndef __thread
+#define __thread __declspec(thread)
+#endif
+
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+#define PREFETCH(addr, rw, locality)
+
+extern const bool kDefaultToAdaptiveMutex;
+
+namespace port {
+
+// VS < 2015
+#if defined(_MSC_VER) && (_MSC_VER < 1900)
+
+// VS 15 has snprintf
+#define snprintf _snprintf
+
+#define ROCKSDB_NOEXCEPT
+// std::numeric_limits<size_t>::max() is not constexpr just yet
+// therefore, use the same limits
+
+// For use at db/file_indexer.h kLevelMaxIndex
+const uint32_t kMaxUint32 = UINT32_MAX;
+const int kMaxInt32 = INT32_MAX;
+const int kMinInt32 = INT32_MIN;
+const int64_t kMaxInt64 = INT64_MAX;
+const int64_t kMinInt64 = INT64_MIN;
+const uint64_t kMaxUint64 = UINT64_MAX;
+
+#ifdef _WIN64
+const size_t kMaxSizet = UINT64_MAX;
+#else
+const size_t kMaxSizet = UINT_MAX;
+#endif
+
+#else // VS >= 2015 or MinGW
+
+#define ROCKSDB_NOEXCEPT noexcept
+
+// For use at db/file_indexer.h kLevelMaxIndex
+const uint32_t kMaxUint32 = std::numeric_limits<uint32_t>::max();
+const int kMaxInt32 = std::numeric_limits<int>::max();
+const int kMinInt32 = std::numeric_limits<int>::min();
+const uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
+const int64_t kMaxInt64 = std::numeric_limits<int64_t>::max();
+const int64_t kMinInt64 = std::numeric_limits<int64_t>::min();
+
+const size_t kMaxSizet = std::numeric_limits<size_t>::max();
+
+#endif //_MSC_VER
+
+// "Windows is designed to run on little-endian computer architectures."
+// https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
+constexpr bool kLittleEndian = true;
+#undef PLATFORM_IS_LITTLE_ENDIAN
+
+class CondVar;
+
+class Mutex {
+ public:
+
+ /* implicit */ Mutex(bool adaptive = kDefaultToAdaptiveMutex)
+#ifndef NDEBUG
+ : locked_(false)
+#endif
+ { }
+
+ ~Mutex();
+
+ void Lock() {
+ mutex_.lock();
+#ifndef NDEBUG
+ locked_ = true;
+#endif
+ }
+
+ void Unlock() {
+#ifndef NDEBUG
+ locked_ = false;
+#endif
+ mutex_.unlock();
+ }
+
+ // this will assert if the mutex is not locked
+ // it does NOT verify that mutex is held by a calling thread
+ void AssertHeld() {
+#ifndef NDEBUG
+ assert(locked_);
+#endif
+ }
+
+ // Mutex is move only with lock ownership transfer
+ Mutex(const Mutex&) = delete;
+ void operator=(const Mutex&) = delete;
+
+ private:
+
+ friend class CondVar;
+
+ std::mutex& getLock() {
+ return mutex_;
+ }
+
+ std::mutex mutex_;
+#ifndef NDEBUG
+ bool locked_;
+#endif
+};
+
+class RWMutex {
+ public:
+ RWMutex() { InitializeSRWLock(&srwLock_); }
+ // No copying allowed
+ RWMutex(const RWMutex&) = delete;
+ void operator=(const RWMutex&) = delete;
+
+ void ReadLock() { AcquireSRWLockShared(&srwLock_); }
+
+ void WriteLock() { AcquireSRWLockExclusive(&srwLock_); }
+
+ void ReadUnlock() { ReleaseSRWLockShared(&srwLock_); }
+
+ void WriteUnlock() { ReleaseSRWLockExclusive(&srwLock_); }
+
+ // Empty as in POSIX
+ void AssertHeld() {}
+
+ private:
+ SRWLOCK srwLock_;
+};
+
+class CondVar {
+ public:
+ explicit CondVar(Mutex* mu) : mu_(mu) {
+ }
+
+ ~CondVar();
+ void Wait();
+ bool TimedWait(uint64_t expiration_time);
+ void Signal();
+ void SignalAll();
+
+ // Condition var is not copy/move constructible
+ CondVar(const CondVar&) = delete;
+ CondVar& operator=(const CondVar&) = delete;
+
+ CondVar(CondVar&&) = delete;
+ CondVar& operator=(CondVar&&) = delete;
+
+ private:
+ std::condition_variable cv_;
+ Mutex* mu_;
+};
+
+// Wrapper around the platform efficient
+// or otherwise preferrable implementation
+using Thread = WindowsThread;
+
+// OnceInit type helps emulate
+// Posix semantics with initialization
+// adopted in the project
+struct OnceType {
+
+ struct Init {};
+
+ OnceType() {}
+ OnceType(const Init&) {}
+ OnceType(const OnceType&) = delete;
+ OnceType& operator=(const OnceType&) = delete;
+
+ std::once_flag flag_;
+};
+
+#define LEVELDB_ONCE_INIT port::OnceType::Init()
+extern void InitOnce(OnceType* once, void (*initializer)());
+
+#ifndef CACHE_LINE_SIZE
+#define CACHE_LINE_SIZE 64U
+#endif
+
+#ifdef ROCKSDB_JEMALLOC
+// Separate inlines so they can be replaced if needed
+void* jemalloc_aligned_alloc(size_t size, size_t alignment) ROCKSDB_NOEXCEPT;
+void jemalloc_aligned_free(void* p) ROCKSDB_NOEXCEPT;
+#endif
+
+inline void *cacheline_aligned_alloc(size_t size) {
+#ifdef ROCKSDB_JEMALLOC
+ return jemalloc_aligned_alloc(size, CACHE_LINE_SIZE);
+#else
+ return _aligned_malloc(size, CACHE_LINE_SIZE);
+#endif
+}
+
+inline void cacheline_aligned_free(void *memblock) {
+#ifdef ROCKSDB_JEMALLOC
+ jemalloc_aligned_free(memblock);
+#else
+ _aligned_free(memblock);
+#endif
+}
+
+extern const size_t kPageSize;
+
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 for MINGW32
+// could not be worked around with by -mno-ms-bitfields
+#ifndef __MINGW32__
+#define ALIGN_AS(n) __declspec(align(n))
+#else
+#define ALIGN_AS(n)
+#endif
+
+static inline void AsmVolatilePause() {
+#if defined(_M_IX86) || defined(_M_X64)
+ YieldProcessor();
+#endif
+ // it would be nice to get "wfe" on ARM here
+}
+
+extern int PhysicalCoreID();
+
+// For Thread Local Storage abstraction
+typedef DWORD pthread_key_t;
+
+inline int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) {
+ // Not used
+ (void)destructor;
+
+ pthread_key_t k = TlsAlloc();
+ if (TLS_OUT_OF_INDEXES == k) {
+ return ENOMEM;
+ }
+
+ *key = k;
+ return 0;
+}
+
+inline int pthread_key_delete(pthread_key_t key) {
+ if (!TlsFree(key)) {
+ return EINVAL;
+ }
+ return 0;
+}
+
+inline int pthread_setspecific(pthread_key_t key, const void* value) {
+ if (!TlsSetValue(key, const_cast<void*>(value))) {
+ return ENOMEM;
+ }
+ return 0;
+}
+
+inline void* pthread_getspecific(pthread_key_t key) {
+ void* result = TlsGetValue(key);
+ if (!result) {
+ if (GetLastError() != ERROR_SUCCESS) {
+ errno = EINVAL;
+ } else {
+ errno = NOERROR;
+ }
+ }
+ return result;
+}
+
+// UNIX equiv although errno numbers will be off
+// using C-runtime to implement. Note, this does not
+// feel space with zeros in case the file is extended.
+int truncate(const char* path, int64_t length);
+int Truncate(std::string path, int64_t length);
+void Crash(const std::string& srcfile, int srcline);
+extern int GetMaxOpenFiles();
+std::string utf16_to_utf8(const std::wstring& utf16);
+std::wstring utf8_to_utf16(const std::string& utf8);
+
+} // namespace port
+
+
+#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES
+
+#define RX_FILESTRING std::wstring
+#define RX_FN(a) ROCKSDB_NAMESPACE::port::utf8_to_utf16(a)
+#define FN_TO_RX(a) ROCKSDB_NAMESPACE::port::utf16_to_utf8(a)
+#define RX_FNLEN(a) ::wcslen(a)
+
+#define RX_DeleteFile DeleteFileW
+#define RX_CreateFile CreateFileW
+#define RX_CreateFileMapping CreateFileMappingW
+#define RX_GetFileAttributesEx GetFileAttributesExW
+#define RX_FindFirstFileEx FindFirstFileExW
+#define RX_FindNextFile FindNextFileW
+#define RX_WIN32_FIND_DATA WIN32_FIND_DATAW
+#define RX_CreateDirectory CreateDirectoryW
+#define RX_RemoveDirectory RemoveDirectoryW
+#define RX_GetFileAttributesEx GetFileAttributesExW
+#define RX_MoveFileEx MoveFileExW
+#define RX_CreateHardLink CreateHardLinkW
+#define RX_PathIsRelative PathIsRelativeW
+#define RX_GetCurrentDirectory GetCurrentDirectoryW
+#define RX_GetDiskFreeSpaceEx GetDiskFreeSpaceExW
+
+#else
+
+#define RX_FILESTRING std::string
+#define RX_FN(a) a
+#define FN_TO_RX(a) a
+#define RX_FNLEN(a) strlen(a)
+
+#define RX_DeleteFile DeleteFileA
+#define RX_CreateFile CreateFileA
+#define RX_CreateFileMapping CreateFileMappingA
+#define RX_GetFileAttributesEx GetFileAttributesExA
+#define RX_FindFirstFileEx FindFirstFileExA
+#define RX_CreateDirectory CreateDirectoryA
+#define RX_FindNextFile FindNextFileA
+#define RX_WIN32_FIND_DATA WIN32_FIND_DATA
+#define RX_CreateDirectory CreateDirectoryA
+#define RX_RemoveDirectory RemoveDirectoryA
+#define RX_GetFileAttributesEx GetFileAttributesExA
+#define RX_MoveFileEx MoveFileExA
+#define RX_CreateHardLink CreateHardLinkA
+#define RX_PathIsRelative PathIsRelativeA
+#define RX_GetCurrentDirectory GetCurrentDirectoryA
+#define RX_GetDiskFreeSpaceEx GetDiskFreeSpaceExA
+
+#endif
+
+using port::pthread_key_t;
+using port::pthread_key_create;
+using port::pthread_key_delete;
+using port::pthread_setspecific;
+using port::pthread_getspecific;
+using port::truncate;
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/win_jemalloc.cc b/storage/rocksdb/rocksdb/port/win/win_jemalloc.cc
new file mode 100644
index 00000000..a29232f8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/win_jemalloc.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_JEMALLOC
+# error This file can only be part of jemalloc aware build
+#endif
+
+#include <stdexcept>
+#include "jemalloc/jemalloc.h"
+#include "port/win/port_win.h"
+
+#if defined(ZSTD) && defined(ZSTD_STATIC_LINKING_ONLY)
+#include <zstd.h>
+#if (ZSTD_VERSION_NUMBER >= 500)
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+void* JemallocAllocateForZSTD(void* /* opaque */, size_t size) {
+ return je_malloc(size);
+}
+void JemallocDeallocateForZSTD(void* /* opaque */, void* address) {
+ je_free(address);
+}
+ZSTD_customMem GetJeZstdAllocationOverrides() {
+ return {JemallocAllocateForZSTD, JemallocDeallocateForZSTD, nullptr};
+}
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
+#endif // (ZSTD_VERSION_NUMBER >= 500)
+#endif // defined(ZSTD) defined(ZSTD_STATIC_LINKING_ONLY)
+
+// Global operators to be replaced by a linker when this file is
+// a part of the build
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+void* jemalloc_aligned_alloc(size_t size, size_t alignment) ROCKSDB_NOEXCEPT {
+ return je_aligned_alloc(alignment, size);
+}
+void jemalloc_aligned_free(void* p) ROCKSDB_NOEXCEPT { je_free(p); }
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
+
+void* operator new(size_t size) {
+ void* p = je_malloc(size);
+ if (!p) {
+ throw std::bad_alloc();
+ }
+ return p;
+}
+
+void* operator new[](size_t size) {
+ void* p = je_malloc(size);
+ if (!p) {
+ throw std::bad_alloc();
+ }
+ return p;
+}
+
+void operator delete(void* p) {
+ if (p) {
+ je_free(p);
+ }
+}
+
+void operator delete[](void* p) {
+ if (p) {
+ je_free(p);
+ }
+}
diff --git a/storage/rocksdb/rocksdb/port/win/win_logger.cc b/storage/rocksdb/rocksdb/port/win/win_logger.cc
new file mode 100644
index 00000000..d429a3f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/win_logger.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Logger implementation that can be shared by all environments
+// where enough posix functionality is available.
+
+#include "port/win/win_logger.h"
+#include "port/win/io_win.h"
+
+#include <algorithm>
+#include <stdio.h>
+#include <time.h>
+#include <fcntl.h>
+#include <atomic>
+
+#include "rocksdb/env.h"
+
+#include "monitoring/iostats_context_imp.h"
+#include "port/sys_time.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace port {
+
+WinLogger::WinLogger(uint64_t (*gettid)(), Env* env, HANDLE file,
+ const InfoLogLevel log_level)
+ : Logger(log_level),
+ file_(file),
+ gettid_(gettid),
+ log_size_(0),
+ last_flush_micros_(0),
+ env_(env),
+ flush_pending_(false) {
+ assert(file_ != NULL);
+ assert(file_ != INVALID_HANDLE_VALUE);
+}
+
+void WinLogger::DebugWriter(const char* str, int len) {
+ assert(file_ != INVALID_HANDLE_VALUE);
+ DWORD bytesWritten = 0;
+ BOOL ret = WriteFile(file_, str, len, &bytesWritten, NULL);
+ if (ret == FALSE) {
+ std::string errSz = GetWindowsErrSz(GetLastError());
+ fprintf(stderr, errSz.c_str());
+ }
+}
+
+WinLogger::~WinLogger() {
+ CloseInternal();
+}
+
+Status WinLogger::CloseImpl() {
+ return CloseInternal();
+}
+
+Status WinLogger::CloseInternal() {
+ Status s;
+ if (INVALID_HANDLE_VALUE != file_) {
+ BOOL ret = FlushFileBuffers(file_);
+ if (ret == 0) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("Failed to flush LOG on Close() ",
+ lastError);
+ }
+ ret = CloseHandle(file_);
+ // On error the return value is zero
+ if (ret == 0 && s.ok()) {
+ auto lastError = GetLastError();
+ s = IOErrorFromWindowsError("Failed to flush LOG on Close() ",
+ lastError);
+ }
+ file_ = INVALID_HANDLE_VALUE;
+ closed_ = true;
+ }
+ return s;
+}
+
+void WinLogger::Flush() {
+ assert(file_ != INVALID_HANDLE_VALUE);
+ if (flush_pending_) {
+ flush_pending_ = false;
+ // With Windows API writes go to OS buffers directly so no fflush needed
+ // unlike with C runtime API. We don't flush all the way to disk
+ // for perf reasons.
+ }
+
+ last_flush_micros_ = env_->NowMicros();
+}
+
+void WinLogger::Logv(const char* format, va_list ap) {
+ IOSTATS_TIMER_GUARD(logger_nanos);
+ assert(file_ != INVALID_HANDLE_VALUE);
+
+ const uint64_t thread_id = (*gettid_)();
+
+ // We try twice: the first time with a fixed-size stack allocated buffer,
+ // and the second time with a much larger dynamically allocated buffer.
+ char buffer[500];
+ std::unique_ptr<char[]> largeBuffer;
+ for (int iter = 0; iter < 2; ++iter) {
+ char* base;
+ int bufsize;
+ if (iter == 0) {
+ bufsize = sizeof(buffer);
+ base = buffer;
+ } else {
+ bufsize = 30000;
+ largeBuffer.reset(new char[bufsize]);
+ base = largeBuffer.get();
+ }
+
+ char* p = base;
+ char* limit = base + bufsize;
+
+ struct timeval now_tv;
+ gettimeofday(&now_tv, nullptr);
+ const time_t seconds = now_tv.tv_sec;
+ struct tm t;
+ localtime_s(&t, &seconds);
+ p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
+ t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+ t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec),
+ static_cast<long long unsigned int>(thread_id));
+
+ // Print the message
+ if (p < limit) {
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ int done = vsnprintf(p, limit - p, format, backup_ap);
+ if (done > 0) {
+ p += done;
+ } else {
+ continue;
+ }
+ va_end(backup_ap);
+ }
+
+ // Truncate to available space if necessary
+ if (p >= limit) {
+ if (iter == 0) {
+ continue; // Try again with larger buffer
+ } else {
+ p = limit - 1;
+ }
+ }
+
+ // Add newline if necessary
+ if (p == base || p[-1] != '\n') {
+ *p++ = '\n';
+ }
+
+ assert(p <= limit);
+ const size_t write_size = p - base;
+
+ DWORD bytesWritten = 0;
+ BOOL ret = WriteFile(file_, base, static_cast<DWORD>(write_size),
+ &bytesWritten, NULL);
+ if (ret == FALSE) {
+ std::string errSz = GetWindowsErrSz(GetLastError());
+ fprintf(stderr, errSz.c_str());
+ }
+
+ flush_pending_ = true;
+ assert((bytesWritten == write_size) || (ret == FALSE));
+ if (bytesWritten > 0) {
+ log_size_ += write_size;
+ }
+
+ uint64_t now_micros =
+ static_cast<uint64_t>(now_tv.tv_sec) * 1000000 + now_tv.tv_usec;
+ if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
+ flush_pending_ = false;
+ // With Windows API writes go to OS buffers directly so no fflush needed
+ // unlike with C runtime API. We don't flush all the way to disk
+ // for perf reasons.
+ last_flush_micros_ = now_micros;
+ }
+ break;
+ }
+}
+
+size_t WinLogger::GetLogFileSize() const { return log_size_; }
+
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/win_logger.h b/storage/rocksdb/rocksdb/port/win/win_logger.h
new file mode 100644
index 00000000..116e7898
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/win_logger.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Logger implementation that can be shared by all environments
+// where enough posix functionality is available.
+
+#pragma once
+
+#include <atomic>
+
+#include "rocksdb/env.h"
+
+#include <stdint.h>
+#include <windows.h>
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+
+namespace port {
+
+class WinLogger : public ROCKSDB_NAMESPACE::Logger {
+ public:
+ WinLogger(uint64_t (*gettid)(), Env* env, HANDLE file,
+ const InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL);
+
+ virtual ~WinLogger();
+
+ WinLogger(const WinLogger&) = delete;
+
+ WinLogger& operator=(const WinLogger&) = delete;
+
+ void Flush() override;
+
+ using ROCKSDB_NAMESPACE::Logger::Logv;
+ void Logv(const char* format, va_list ap) override;
+
+ size_t GetLogFileSize() const override;
+
+ void DebugWriter(const char* str, int len);
+
+protected:
+
+ Status CloseImpl() override;
+
+ private:
+ HANDLE file_;
+ uint64_t (*gettid_)(); // Return the thread id for the current thread
+ std::atomic_size_t log_size_;
+ std::atomic_uint_fast64_t last_flush_micros_;
+ Env* env_;
+ bool flush_pending_;
+
+ Status CloseInternal();
+
+ const static uint64_t flush_every_seconds_ = 5;
+};
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/win_thread.cc b/storage/rocksdb/rocksdb/port/win/win_thread.cc
new file mode 100644
index 00000000..74c667b5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/win_thread.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/win/win_thread.h"
+
+#include <assert.h>
+#include <process.h> // __beginthreadex
+#include <windows.h>
+
+#include <stdexcept>
+#include <system_error>
+#include <thread>
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+struct WindowsThread::Data {
+
+ std::function<void()> func_;
+ uintptr_t handle_;
+
+ Data(std::function<void()>&& func) :
+ func_(std::move(func)),
+ handle_(0) {
+ }
+
+ Data(const Data&) = delete;
+ Data& operator=(const Data&) = delete;
+
+ static unsigned int __stdcall ThreadProc(void* arg);
+};
+
+
+void WindowsThread::Init(std::function<void()>&& func) {
+
+ data_ = std::make_shared<Data>(std::move(func));
+ // We create another instance of std::shared_ptr to get an additional ref
+ // since we may detach and destroy this instance before the threadproc
+ // may start to run. We choose to allocate this additional ref on the heap
+ // so we do not need to synchronize and allow this thread to proceed
+ std::unique_ptr<std::shared_ptr<Data>> th_data(new std::shared_ptr<Data>(data_));
+
+ data_->handle_ = _beginthreadex(NULL,
+ 0, // stack size
+ &Data::ThreadProc,
+ th_data.get(),
+ 0, // init flag
+ &th_id_);
+
+ if (data_->handle_ == 0) {
+ throw std::system_error(std::make_error_code(
+ std::errc::resource_unavailable_try_again),
+ "Unable to create a thread");
+ }
+ th_data.release();
+}
+
+WindowsThread::WindowsThread() :
+ data_(nullptr),
+ th_id_(0)
+{}
+
+
+WindowsThread::~WindowsThread() {
+ // Must be joined or detached
+ // before destruction.
+ // This is the same as std::thread
+ if (data_) {
+ if (joinable()) {
+ assert(false);
+ std::terminate();
+ }
+ data_.reset();
+ }
+}
+
+WindowsThread::WindowsThread(WindowsThread&& o) noexcept :
+ WindowsThread() {
+ *this = std::move(o);
+}
+
+WindowsThread& WindowsThread::operator=(WindowsThread&& o) noexcept {
+
+ if (joinable()) {
+ assert(false);
+ std::terminate();
+ }
+
+ data_ = std::move(o.data_);
+
+ // Per spec both instances will have the same id
+ th_id_ = o.th_id_;
+
+ return *this;
+}
+
+bool WindowsThread::joinable() const {
+ return (data_ && data_->handle_ != 0);
+}
+
+WindowsThread::native_handle_type WindowsThread::native_handle() const {
+ return reinterpret_cast<native_handle_type>(data_->handle_);
+}
+
+unsigned WindowsThread::hardware_concurrency() {
+ return std::thread::hardware_concurrency();
+}
+
+void WindowsThread::join() {
+
+ if (!joinable()) {
+ assert(false);
+ throw std::system_error(
+ std::make_error_code(std::errc::invalid_argument),
+ "Thread is no longer joinable");
+ }
+
+ if (GetThreadId(GetCurrentThread()) == th_id_) {
+ assert(false);
+ throw std::system_error(
+ std::make_error_code(std::errc::resource_deadlock_would_occur),
+ "Can not join itself");
+ }
+
+ auto ret = WaitForSingleObject(reinterpret_cast<HANDLE>(data_->handle_),
+ INFINITE);
+ if (ret != WAIT_OBJECT_0) {
+ auto lastError = GetLastError();
+ assert(false);
+ throw std::system_error(static_cast<int>(lastError),
+ std::system_category(),
+ "WaitForSingleObjectFailed: thread join");
+ }
+
+ BOOL rc
+#if defined(_MSC_VER)
+ = FALSE;
+#else
+ __attribute__((__unused__));
+#endif
+ rc = CloseHandle(reinterpret_cast<HANDLE>(data_->handle_));
+ assert(rc != 0);
+ data_->handle_ = 0;
+}
+
+bool WindowsThread::detach() {
+
+ if (!joinable()) {
+ assert(false);
+ throw std::system_error(
+ std::make_error_code(std::errc::invalid_argument),
+ "Thread is no longer available");
+ }
+
+ BOOL ret = CloseHandle(reinterpret_cast<HANDLE>(data_->handle_));
+ data_->handle_ = 0;
+
+ return (ret != 0);
+}
+
+void WindowsThread::swap(WindowsThread& o) {
+ data_.swap(o.data_);
+ std::swap(th_id_, o.th_id_);
+}
+
+unsigned int __stdcall WindowsThread::Data::ThreadProc(void* arg) {
+ auto ptr = reinterpret_cast<std::shared_ptr<Data>*>(arg);
+ std::unique_ptr<std::shared_ptr<Data>> data(ptr);
+ (*data)->func_();
+ return 0;
+}
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/win/win_thread.h b/storage/rocksdb/rocksdb/port/win/win_thread.h
new file mode 100644
index 00000000..472b2995
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/win_thread.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <functional>
+#include <type_traits>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+
+// This class is a replacement for std::thread
+// 2 reasons we do not like std::thread:
+// -- is that it dynamically allocates its internals that are automatically
+// freed when the thread terminates and not on the destruction of the
+// object. This makes it difficult to control the source of memory
+// allocation
+// - This implements Pimpl so we can easily replace the guts of the
+// object in our private version if necessary.
+class WindowsThread {
+
+ struct Data;
+
+ std::shared_ptr<Data> data_;
+ unsigned int th_id_;
+
+ void Init(std::function<void()>&&);
+
+public:
+
+ typedef void* native_handle_type;
+
+ // Construct with no thread
+ WindowsThread();
+
+ // Template constructor
+ //
+ // This templated constructor accomplishes several things
+ //
+ // - Allows the class as whole to be not a template
+ //
+ // - take "universal" references to support both _lvalues and _rvalues
+ //
+ // - because this constructor is a catchall case in many respects it
+ // may prevent us from using both the default __ctor, the move __ctor.
+ // Also it may circumvent copy __ctor deletion. To work around this
+ // we make sure this one has at least one argument and eliminate
+ // it from the overload selection when WindowsThread is the first
+ // argument.
+ //
+ // - construct with Fx(Ax...) with a variable number of types/arguments.
+ //
+ // - Gathers together the callable object with its arguments and constructs
+ // a single callable entity
+ //
+ // - Makes use of std::function to convert it to a specification-template
+ // dependent type that both checks the signature conformance to ensure
+ // that all of the necessary arguments are provided and allows pimpl
+ // implementation.
+ template<class Fn,
+ class... Args,
+ class = typename std::enable_if<
+ !std::is_same<typename std::decay<Fn>::type,
+ WindowsThread>::value>::type>
+ explicit WindowsThread(Fn&& fx, Args&&... ax) :
+ WindowsThread() {
+
+ // Use binder to create a single callable entity
+ auto binder = std::bind(std::forward<Fn>(fx),
+ std::forward<Args>(ax)...);
+ // Use std::function to take advantage of the type erasure
+ // so we can still hide implementation within pimpl
+ // This also makes sure that the binder signature is compliant
+ std::function<void()> target = binder;
+
+ Init(std::move(target));
+ }
+
+
+ ~WindowsThread();
+
+ WindowsThread(const WindowsThread&) = delete;
+
+ WindowsThread& operator=(const WindowsThread&) = delete;
+
+ WindowsThread(WindowsThread&&) noexcept;
+
+ WindowsThread& operator=(WindowsThread&&) noexcept;
+
+ bool joinable() const;
+
+ unsigned int get_id() const { return th_id_; }
+
+ native_handle_type native_handle() const;
+
+ static unsigned hardware_concurrency();
+
+ void join();
+
+ bool detach();
+
+ void swap(WindowsThread&);
+};
+} // namespace port
+} // namespace ROCKSDB_NAMESPACE
+
+namespace std {
+inline void swap(ROCKSDB_NAMESPACE::port::WindowsThread& th1,
+ ROCKSDB_NAMESPACE::port::WindowsThread& th2) {
+ th1.swap(th2);
+}
+} // namespace std
+
diff --git a/storage/rocksdb/rocksdb/port/win/xpress_win.cc b/storage/rocksdb/rocksdb/port/win/xpress_win.cc
new file mode 100644
index 00000000..bcc10653
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/xpress_win.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/win/xpress_win.h"
+#include <windows.h>
+
+#include <cassert>
+#include <memory>
+#include <limits>
+#include <iostream>
+
+#ifdef XPRESS
+
+// Put this under ifdef so windows systems w/o this
+// can still build
+#include <compressapi.h>
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+namespace xpress {
+
+// Helpers
+namespace {
+
+auto CloseCompressorFun = [](void* h) {
+ if (NULL != h) {
+ ::CloseCompressor(reinterpret_cast<COMPRESSOR_HANDLE>(h));
+ }
+};
+
+auto CloseDecompressorFun = [](void* h) {
+ if (NULL != h) {
+ ::CloseDecompressor(reinterpret_cast<DECOMPRESSOR_HANDLE>(h));
+ }
+};
+}
+
+bool Compress(const char* input, size_t length, std::string* output) {
+
+ assert(input != nullptr);
+ assert(output != nullptr);
+
+ if (length == 0) {
+ output->clear();
+ return true;
+ }
+
+ COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
+
+ COMPRESSOR_HANDLE compressor = NULL;
+
+ BOOL success = CreateCompressor(
+ COMPRESS_ALGORITHM_XPRESS, // Compression Algorithm
+ allocRoutinesPtr, // Optional allocation routine
+ &compressor); // Handle
+
+ if (!success) {
+#ifdef _DEBUG
+ std::cerr << "XPRESS: Failed to create Compressor LastError: " <<
+ GetLastError() << std::endl;
+#endif
+ return false;
+ }
+
+ std::unique_ptr<void, decltype(CloseCompressorFun)>
+ compressorGuard(compressor, CloseCompressorFun);
+
+ SIZE_T compressedBufferSize = 0;
+
+ // Query compressed buffer size.
+ success = ::Compress(
+ compressor, // Compressor Handle
+ const_cast<char*>(input), // Input buffer
+ length, // Uncompressed data size
+ NULL, // Compressed Buffer
+ 0, // Compressed Buffer size
+ &compressedBufferSize); // Compressed Data size
+
+ if (!success) {
+
+ auto lastError = GetLastError();
+
+ if (lastError != ERROR_INSUFFICIENT_BUFFER) {
+#ifdef _DEBUG
+ std::cerr <<
+ "XPRESS: Failed to estimate compressed buffer size LastError " <<
+ lastError << std::endl;
+#endif
+ return false;
+ }
+ }
+
+ assert(compressedBufferSize > 0);
+
+ std::string result;
+ result.resize(compressedBufferSize);
+
+ SIZE_T compressedDataSize = 0;
+
+ // Compress
+ success = ::Compress(
+ compressor, // Compressor Handle
+ const_cast<char*>(input), // Input buffer
+ length, // Uncompressed data size
+ &result[0], // Compressed Buffer
+ compressedBufferSize, // Compressed Buffer size
+ &compressedDataSize); // Compressed Data size
+
+ if (!success) {
+#ifdef _DEBUG
+ std::cerr << "XPRESS: Failed to compress LastError " <<
+ GetLastError() << std::endl;
+#endif
+ return false;
+ }
+
+ result.resize(compressedDataSize);
+ output->swap(result);
+
+ return true;
+}
+
+char* Decompress(const char* input_data, size_t input_length,
+ int* decompress_size) {
+
+ assert(input_data != nullptr);
+ assert(decompress_size != nullptr);
+
+ if (input_length == 0) {
+ return nullptr;
+ }
+
+ COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
+
+ DECOMPRESSOR_HANDLE decompressor = NULL;
+
+ BOOL success = CreateDecompressor(
+ COMPRESS_ALGORITHM_XPRESS, // Compression Algorithm
+ allocRoutinesPtr, // Optional allocation routine
+ &decompressor); // Handle
+
+
+ if (!success) {
+#ifdef _DEBUG
+ std::cerr << "XPRESS: Failed to create Decompressor LastError "
+ << GetLastError() << std::endl;
+#endif
+ return nullptr;
+ }
+
+ std::unique_ptr<void, decltype(CloseDecompressorFun)>
+ compressorGuard(decompressor, CloseDecompressorFun);
+
+ SIZE_T decompressedBufferSize = 0;
+
+ success = ::Decompress(
+ decompressor, // Compressor Handle
+ const_cast<char*>(input_data), // Compressed data
+ input_length, // Compressed data size
+ NULL, // Buffer set to NULL
+ 0, // Buffer size set to 0
+ &decompressedBufferSize); // Decompressed Data size
+
+ if (!success) {
+
+ auto lastError = GetLastError();
+
+ if (lastError != ERROR_INSUFFICIENT_BUFFER) {
+#ifdef _DEBUG
+ std::cerr
+ << "XPRESS: Failed to estimate decompressed buffer size LastError "
+ << lastError << std::endl;
+#endif
+ return nullptr;
+ }
+ }
+
+ assert(decompressedBufferSize > 0);
+
+ // On Windows we are limited to a 32-bit int for the
+ // output data size argument
+ // so we hopefully never get here
+ if (decompressedBufferSize > std::numeric_limits<int>::max()) {
+ assert(false);
+ return nullptr;
+ }
+
+ // The callers are deallocating using delete[]
+ // thus we must allocate with new[]
+ std::unique_ptr<char[]> outputBuffer(new char[decompressedBufferSize]);
+
+ SIZE_T decompressedDataSize = 0;
+
+ success = ::Decompress(
+ decompressor,
+ const_cast<char*>(input_data),
+ input_length,
+ outputBuffer.get(),
+ decompressedBufferSize,
+ &decompressedDataSize);
+
+ if (!success) {
+#ifdef _DEBUG
+ std::cerr <<
+ "XPRESS: Failed to decompress LastError " <<
+ GetLastError() << std::endl;
+#endif
+ return nullptr;
+ }
+
+ *decompress_size = static_cast<int>(decompressedDataSize);
+
+ // Return the raw buffer to the caller supporting the tradition
+ return outputBuffer.release();
+}
+}
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/port/win/xpress_win.h b/storage/rocksdb/rocksdb/port/win/xpress_win.h
new file mode 100644
index 00000000..1214bb7b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/win/xpress_win.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace port {
+namespace xpress {
+
+bool Compress(const char* input, size_t length, std::string* output);
+
+char* Decompress(const char* input_data, size_t input_length,
+ int* decompress_size);
+
+}
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/port/xpress.h b/storage/rocksdb/rocksdb/port/xpress.h
new file mode 100644
index 00000000..457025f6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/port/xpress.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+// Xpress on Windows is implemeted using Win API
+#if defined(ROCKSDB_PLATFORM_POSIX)
+#error "Xpress compression not implemented"
+#elif defined(OS_WIN)
+#include "port/win/xpress_win.h"
+#endif
diff --git a/storage/rocksdb/rocksdb/src.mk b/storage/rocksdb/rocksdb/src.mk
new file mode 100644
index 00000000..5db947a2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/src.mk
@@ -0,0 +1,527 @@
+# These are the sources from which librocksdb.a is built:
+LIB_SOURCES = \
+ cache/clock_cache.cc \
+ cache/lru_cache.cc \
+ cache/sharded_cache.cc \
+ db/arena_wrapped_db_iter.cc \
+ db/builder.cc \
+ db/c.cc \
+ db/column_family.cc \
+ db/compacted_db_impl.cc \
+ db/compaction/compaction.cc \
+ db/compaction/compaction_iterator.cc \
+ db/compaction/compaction_job.cc \
+ db/compaction/compaction_picker.cc \
+ db/compaction/compaction_picker_fifo.cc \
+ db/compaction/compaction_picker_level.cc \
+ db/compaction/compaction_picker_universal.cc \
+ db/convenience.cc \
+ db/db_filesnapshot.cc \
+ db/db_impl/db_impl.cc \
+ db/db_impl/db_impl_compaction_flush.cc \
+ db/db_impl/db_impl_debug.cc \
+ db/db_impl/db_impl_experimental.cc \
+ db/db_impl/db_impl_files.cc \
+ db/db_impl/db_impl_open.cc \
+ db/db_impl/db_impl_readonly.cc \
+ db/db_impl/db_impl_secondary.cc \
+ db/db_impl/db_impl_write.cc \
+ db/db_info_dumper.cc \
+ db/db_iter.cc \
+ db/dbformat.cc \
+ db/error_handler.cc \
+ db/event_helpers.cc \
+ db/experimental.cc \
+ db/external_sst_file_ingestion_job.cc \
+ db/file_indexer.cc \
+ db/flush_job.cc \
+ db/flush_scheduler.cc \
+ db/forward_iterator.cc \
+ db/import_column_family_job.cc \
+ db/internal_stats.cc \
+ db/logs_with_prep_tracker.cc \
+ db/log_reader.cc \
+ db/log_writer.cc \
+ db/malloc_stats.cc \
+ db/memtable.cc \
+ db/memtable_list.cc \
+ db/merge_helper.cc \
+ db/merge_operator.cc \
+ db/range_del_aggregator.cc \
+ db/range_tombstone_fragmenter.cc \
+ db/repair.cc \
+ db/snapshot_impl.cc \
+ db/table_cache.cc \
+ db/table_properties_collector.cc \
+ db/transaction_log_impl.cc \
+ db/trim_history_scheduler.cc \
+ db/version_builder.cc \
+ db/version_edit.cc \
+ db/version_set.cc \
+ db/wal_manager.cc \
+ db/write_batch.cc \
+ db/write_batch_base.cc \
+ db/write_controller.cc \
+ db/write_thread.cc \
+ env/env.cc \
+ env/env_chroot.cc \
+ env/env_encryption.cc \
+ env/env_hdfs.cc \
+ env/env_posix.cc \
+ env/file_system.cc \
+ env/fs_posix.cc \
+ env/io_posix.cc \
+ env/mock_env.cc \
+ file/delete_scheduler.cc \
+ file/file_prefetch_buffer.cc \
+ file/file_util.cc \
+ file/filename.cc \
+ file/random_access_file_reader.cc \
+ file/read_write_util.cc \
+ file/readahead_raf.cc \
+ file/sequence_file_reader.cc \
+ file/sst_file_manager_impl.cc \
+ file/writable_file_writer.cc \
+ logging/auto_roll_logger.cc \
+ logging/event_logger.cc \
+ logging/log_buffer.cc \
+ memory/arena.cc \
+ memory/concurrent_arena.cc \
+ memory/jemalloc_nodump_allocator.cc \
+ memtable/alloc_tracker.cc \
+ memtable/hash_linklist_rep.cc \
+ memtable/hash_skiplist_rep.cc \
+ memtable/skiplistrep.cc \
+ memtable/vectorrep.cc \
+ memtable/write_buffer_manager.cc \
+ monitoring/histogram.cc \
+ monitoring/histogram_windowing.cc \
+ monitoring/in_memory_stats_history.cc \
+ monitoring/instrumented_mutex.cc \
+ monitoring/iostats_context.cc \
+ monitoring/perf_context.cc \
+ monitoring/perf_level.cc \
+ monitoring/persistent_stats_history.cc \
+ monitoring/statistics.cc \
+ monitoring/thread_status_impl.cc \
+ monitoring/thread_status_updater.cc \
+ monitoring/thread_status_updater_debug.cc \
+ monitoring/thread_status_util.cc \
+ monitoring/thread_status_util_debug.cc \
+ options/cf_options.cc \
+ options/db_options.cc \
+ options/options.cc \
+ options/options_helper.cc \
+ options/options_parser.cc \
+ options/options_sanity_check.cc \
+ port/port_posix.cc \
+ port/stack_trace.cc \
+ table/adaptive/adaptive_table_factory.cc \
+ table/block_based/block.cc \
+ table/block_based/block_based_filter_block.cc \
+ table/block_based/block_based_table_builder.cc \
+ table/block_based/block_based_table_factory.cc \
+ table/block_based/block_based_table_reader.cc \
+ table/block_based/block_builder.cc \
+ table/block_based/block_prefix_index.cc \
+ table/block_based/data_block_hash_index.cc \
+ table/block_based/data_block_footer.cc \
+ table/block_based/filter_block_reader_common.cc \
+ table/block_based/filter_policy.cc \
+ table/block_based/flush_block_policy.cc \
+ table/block_based/full_filter_block.cc \
+ table/block_based/index_builder.cc \
+ table/block_based/parsed_full_filter_block.cc \
+ table/block_based/partitioned_filter_block.cc \
+ table/block_based/uncompression_dict_reader.cc \
+ table/block_fetcher.cc \
+ table/cuckoo/cuckoo_table_builder.cc \
+ table/cuckoo/cuckoo_table_factory.cc \
+ table/cuckoo/cuckoo_table_reader.cc \
+ table/format.cc \
+ table/get_context.cc \
+ table/iterator.cc \
+ table/merging_iterator.cc \
+ table/meta_blocks.cc \
+ table/persistent_cache_helper.cc \
+ table/plain/plain_table_bloom.cc \
+ table/plain/plain_table_builder.cc \
+ table/plain/plain_table_factory.cc \
+ table/plain/plain_table_index.cc \
+ table/plain/plain_table_key_coding.cc \
+ table/plain/plain_table_reader.cc \
+ table/sst_file_reader.cc \
+ table/sst_file_writer.cc \
+ table/table_properties.cc \
+ table/two_level_iterator.cc \
+ test_util/sync_point.cc \
+ test_util/sync_point_impl.cc \
+ test_util/transaction_test_util.cc \
+ tools/dump/db_dump_tool.cc \
+ trace_replay/trace_replay.cc \
+ trace_replay/block_cache_tracer.cc \
+ util/build_version.cc \
+ util/coding.cc \
+ util/compaction_job_stats_impl.cc \
+ util/comparator.cc \
+ util/compression_context_cache.cc \
+ util/concurrent_task_limiter_impl.cc \
+ util/crc32c.cc \
+ util/dynamic_bloom.cc \
+ util/hash.cc \
+ util/murmurhash.cc \
+ util/random.cc \
+ util/rate_limiter.cc \
+ util/slice.cc \
+ util/file_checksum_helper.cc \
+ util/status.cc \
+ util/string_util.cc \
+ util/thread_local.cc \
+ util/threadpool_imp.cc \
+ util/xxhash.cc \
+ utilities/backupable/backupable_db.cc \
+ utilities/blob_db/blob_compaction_filter.cc \
+ utilities/blob_db/blob_db.cc \
+ utilities/blob_db/blob_db_impl.cc \
+ utilities/blob_db/blob_db_impl_filesnapshot.cc \
+ utilities/blob_db/blob_file.cc \
+ utilities/blob_db/blob_log_format.cc \
+ utilities/blob_db/blob_log_reader.cc \
+ utilities/blob_db/blob_log_writer.cc \
+ utilities/cassandra/cassandra_compaction_filter.cc \
+ utilities/cassandra/format.cc \
+ utilities/cassandra/merge_operator.cc \
+ utilities/checkpoint/checkpoint_impl.cc \
+ utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc \
+ utilities/convenience/info_log_finder.cc \
+ utilities/debug.cc \
+ utilities/env_mirror.cc \
+ utilities/env_timed.cc \
+ utilities/leveldb_options/leveldb_options.cc \
+ utilities/memory/memory_util.cc \
+ utilities/merge_operators/max.cc \
+ utilities/merge_operators/put.cc \
+ utilities/merge_operators/sortlist.cc \
+ utilities/merge_operators/string_append/stringappend.cc \
+ utilities/merge_operators/string_append/stringappend2.cc \
+ utilities/merge_operators/uint64add.cc \
+ utilities/merge_operators/bytesxor.cc \
+ utilities/object_registry.cc \
+ utilities/option_change_migration/option_change_migration.cc \
+ utilities/options/options_util.cc \
+ utilities/persistent_cache/block_cache_tier.cc \
+ utilities/persistent_cache/block_cache_tier_file.cc \
+ utilities/persistent_cache/block_cache_tier_metadata.cc \
+ utilities/persistent_cache/persistent_cache_tier.cc \
+ utilities/persistent_cache/volatile_tier_impl.cc \
+ utilities/simulator_cache/cache_simulator.cc \
+ utilities/simulator_cache/sim_cache.cc \
+ utilities/table_properties_collectors/compact_on_deletion_collector.cc \
+ utilities/trace/file_trace_reader_writer.cc \
+ utilities/transactions/optimistic_transaction.cc \
+ utilities/transactions/optimistic_transaction_db_impl.cc \
+ utilities/transactions/pessimistic_transaction.cc \
+ utilities/transactions/pessimistic_transaction_db.cc \
+ utilities/transactions/snapshot_checker.cc \
+ utilities/transactions/transaction_base.cc \
+ utilities/transactions/transaction_db_mutex_impl.cc \
+ utilities/transactions/transaction_lock_mgr.cc \
+ utilities/transactions/transaction_util.cc \
+ utilities/transactions/write_prepared_txn.cc \
+ utilities/transactions/write_prepared_txn_db.cc \
+ utilities/transactions/write_unprepared_txn.cc \
+ utilities/transactions/write_unprepared_txn_db.cc \
+ utilities/ttl/db_ttl_impl.cc \
+ utilities/write_batch_with_index/write_batch_with_index.cc \
+ utilities/write_batch_with_index/write_batch_with_index_internal.cc \
+
+ifeq ($(ARMCRC_SOURCE),1)
+LIB_SOURCES +=\
+ util/crc32c_arm64.cc
+endif
+
+ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1))
+LIB_SOURCES_ASM =\
+ util/crc32c_ppc_asm.S
+LIB_SOURCES_C = \
+ util/crc32c_ppc.c
+else
+LIB_SOURCES_ASM =
+LIB_SOURCES_C =
+endif
+
+TOOL_LIB_SOURCES = \
+ tools/ldb_cmd.cc \
+ tools/ldb_tool.cc \
+ tools/sst_dump_tool.cc \
+ utilities/blob_db/blob_dump_tool.cc \
+
+ANALYZER_LIB_SOURCES = \
+ tools/block_cache_analyzer/block_cache_trace_analyzer.cc \
+ tools/trace_analyzer_tool.cc \
+
+MOCK_LIB_SOURCES = \
+ table/mock_table.cc \
+ test_util/fault_injection_test_env.cc
+
+BENCH_LIB_SOURCES = \
+ tools/db_bench_tool.cc \
+
+STRESS_LIB_SOURCES = \
+ db_stress_tool/batched_ops_stress.cc \
+ db_stress_tool/cf_consistency_stress.cc \
+ db_stress_tool/db_stress_common.cc \
+ db_stress_tool/db_stress_driver.cc \
+ db_stress_tool/db_stress_test_base.cc \
+ db_stress_tool/db_stress_gflags.cc \
+ db_stress_tool/db_stress_shared_state.cc \
+ db_stress_tool/db_stress_tool.cc \
+ db_stress_tool/no_batched_ops_stress.cc \
+
+TEST_LIB_SOURCES = \
+ db/db_test_util.cc \
+ test_util/testharness.cc \
+ test_util/testutil.cc \
+ utilities/cassandra/test_utils.cc \
+
+FOLLY_SOURCES = \
+ third-party/folly/folly/detail/Futex.cpp \
+ third-party/folly/folly/synchronization/AtomicNotification.cpp \
+ third-party/folly/folly/synchronization/DistributedMutex.cpp \
+ third-party/folly/folly/synchronization/ParkingLot.cpp \
+ third-party/folly/folly/synchronization/WaitOptions.cpp \
+
+MAIN_SOURCES = \
+ cache/cache_bench.cc \
+ cache/cache_test.cc \
+ db_stress_tool/db_stress.cc \
+ db/column_family_test.cc \
+ db/compact_files_test.cc \
+ db/compaction/compaction_iterator_test.cc \
+ db/compaction/compaction_job_test.cc \
+ db/compaction/compaction_job_stats_test.cc \
+ db/compaction/compaction_picker_test.cc \
+ db/comparator_db_test.cc \
+ db/corruption_test.cc \
+ db/cuckoo_table_db_test.cc \
+ db/db_basic_test.cc \
+ db/db_blob_index_test.cc \
+ db/db_block_cache_test.cc \
+ db/db_bloom_filter_test.cc \
+ db/db_compaction_filter_test.cc \
+ db/db_compaction_test.cc \
+ db/db_dynamic_level_test.cc \
+ db/db_encryption_test.cc \
+ db/db_flush_test.cc \
+ db/db_inplace_update_test.cc \
+ db/db_io_failure_test.cc \
+ db/db_iter_test.cc \
+ db/db_iter_stress_test.cc \
+ db/db_iterator_test.cc \
+ db/db_log_iter_test.cc \
+ db/db_memtable_test.cc \
+ db/db_merge_operator_test.cc \
+ db/db_merge_operand_test.cc \
+ db/db_options_test.cc \
+ db/db_properties_test.cc \
+ db/db_range_del_test.cc \
+ db/db_impl/db_secondary_test.cc \
+ db/db_sst_test.cc \
+ db/db_statistics_test.cc \
+ db/db_table_properties_test.cc \
+ db/db_tailing_iter_test.cc \
+ db/db_test.cc \
+ db/db_test2.cc \
+ db/db_universal_compaction_test.cc \
+ db/db_wal_test.cc \
+ db/db_write_test.cc \
+ db/dbformat_test.cc \
+ db/deletefile_test.cc \
+ db/env_timed_test.cc \
+ db/error_handler_test.cc \
+ db/external_sst_file_basic_test.cc \
+ db/external_sst_file_test.cc \
+ db/fault_injection_test.cc \
+ db/file_indexer_test.cc \
+ db/file_reader_writer_test.cc \
+ db/filename_test.cc \
+ db/flush_job_test.cc \
+ db/hash_table_test.cc \
+ db/hash_test.cc \
+ db/heap_test.cc \
+ db/listener_test.cc \
+ db/log_test.cc \
+ db/lru_cache_test.cc \
+ db/manual_compaction_test.cc \
+ db/memtable_list_test.cc \
+ db/merge_helper_test.cc \
+ db/merge_test.cc \
+ db/obsolete_files_test.cc \
+ db/options_settable_test.cc \
+ db/options_file_test.cc \
+ db/perf_context_test.cc \
+ db/persistent_cache_test.cc \
+ db/plain_table_db_test.cc \
+ db/prefix_test.cc \
+ db/repair_test.cc \
+ db/range_del_aggregator_test.cc \
+ db/range_del_aggregator_bench.cc \
+ db/range_tombstone_fragmenter_test.cc \
+ db/table_properties_collector_test.cc \
+ db/util_merge_operators_test.cc \
+ db/version_builder_test.cc \
+ db/version_edit_test.cc \
+ db/version_set_test.cc \
+ db/wal_manager_test.cc \
+ db/write_batch_test.cc \
+ db/write_callback_test.cc \
+ db/write_controller_test.cc \
+ env/env_basic_test.cc \
+ env/env_test.cc \
+ env/mock_env_test.cc \
+ logging/auto_roll_logger_test.cc \
+ logging/env_logger_test.cc \
+ logging/event_logger_test.cc \
+ memory/arena_test.cc \
+ memtable/inlineskiplist_test.cc \
+ memtable/memtablerep_bench.cc \
+ memtable/skiplist_test.cc \
+ memtable/write_buffer_manager_test.cc \
+ monitoring/histogram_test.cc \
+ monitoring/iostats_context_test.cc \
+ monitoring/statistics_test.cc \
+ monitoring/stats_history_test.cc \
+ options/options_test.cc \
+ table/block_based/block_based_filter_block_test.cc \
+ table/block_based/block_test.cc \
+ table/block_based/data_block_hash_index_test.cc \
+ table/block_based/full_filter_block_test.cc \
+ table/block_based/partitioned_filter_block_test.cc \
+ table/cleanable_test.cc \
+ table/cuckoo/cuckoo_table_builder_test.cc \
+ table/cuckoo/cuckoo_table_reader_test.cc \
+ table/merger_test.cc \
+ table/sst_file_reader_test.cc \
+ table/table_reader_bench.cc \
+ table/table_test.cc \
+ third-party/gtest-1.7.0/fused-src/gtest/gtest-all.cc \
+ tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc \
+ tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc \
+ tools/db_bench.cc \
+ tools/db_bench_tool_test.cc \
+ tools/db_sanity_test.cc \
+ tools/ldb_cmd_test.cc \
+ tools/reduce_levels_test.cc \
+ tools/sst_dump_test.cc \
+ tools/trace_analyzer_test.cc \
+ trace_replay/block_cache_tracer_test.cc \
+ util/autovector_test.cc \
+ util/bloom_test.cc \
+ util/coding_test.cc \
+ util/crc32c_test.cc \
+ util/defer_test.cc \
+ util/dynamic_bloom_test.cc \
+ util/filelock_test.cc \
+ util/log_write_bench.cc \
+ util/rate_limiter_test.cc \
+ util/random_test.cc \
+ util/repeatable_thread_test.cc \
+ util/slice_test.cc \
+ util/slice_transform_test.cc \
+ util/timer_queue_test.cc \
+ util/thread_list_test.cc \
+ util/thread_local_test.cc \
+ utilities/backupable/backupable_db_test.cc \
+ utilities/blob_db/blob_db_test.cc \
+ utilities/cassandra/cassandra_format_test.cc \
+ utilities/cassandra/cassandra_functional_test.cc \
+ utilities/cassandra/cassandra_row_merge_test.cc \
+ utilities/cassandra/cassandra_serialize_test.cc \
+ utilities/checkpoint/checkpoint_test.cc \
+ utilities/memory/memory_test.cc \
+ utilities/merge_operators/string_append/stringappend_test.cc \
+ utilities/object_registry_test.cc \
+ utilities/option_change_migration/option_change_migration_test.cc \
+ utilities/options/options_util_test.cc \
+ utilities/simulator_cache/cache_simulator_test.cc \
+ utilities/simulator_cache/sim_cache_test.cc \
+ utilities/table_properties_collectors/compact_on_deletion_collector_test.cc \
+ utilities/transactions/optimistic_transaction_test.cc \
+ utilities/transactions/transaction_test.cc \
+ utilities/transactions/write_prepared_transaction_test.cc \
+ utilities/transactions/write_unprepared_transaction_test.cc \
+ utilities/ttl/ttl_test.cc \
+ utilities/write_batch_with_index/write_batch_with_index_test.cc \
+
+JNI_NATIVE_SOURCES = \
+ java/rocksjni/backupenginejni.cc \
+ java/rocksjni/backupablejni.cc \
+ java/rocksjni/checkpoint.cc \
+ java/rocksjni/clock_cache.cc \
+ java/rocksjni/columnfamilyhandle.cc \
+ java/rocksjni/compact_range_options.cc \
+ java/rocksjni/compaction_filter.cc \
+ java/rocksjni/compaction_filter_factory.cc \
+ java/rocksjni/compaction_filter_factory_jnicallback.cc \
+ java/rocksjni/compaction_job_info.cc \
+ java/rocksjni/compaction_job_stats.cc \
+ java/rocksjni/compaction_options.cc \
+ java/rocksjni/compaction_options_fifo.cc \
+ java/rocksjni/compaction_options_universal.cc \
+ java/rocksjni/comparator.cc \
+ java/rocksjni/comparatorjnicallback.cc \
+ java/rocksjni/compression_options.cc \
+ java/rocksjni/env.cc \
+ java/rocksjni/env_options.cc \
+ java/rocksjni/ingest_external_file_options.cc \
+ java/rocksjni/filter.cc \
+ java/rocksjni/iterator.cc \
+ java/rocksjni/jnicallback.cc \
+ java/rocksjni/loggerjnicallback.cc \
+ java/rocksjni/lru_cache.cc \
+ java/rocksjni/memtablejni.cc \
+ java/rocksjni/memory_util.cc \
+ java/rocksjni/merge_operator.cc \
+ java/rocksjni/native_comparator_wrapper_test.cc \
+ java/rocksjni/optimistic_transaction_db.cc \
+ java/rocksjni/optimistic_transaction_options.cc \
+ java/rocksjni/options.cc \
+ java/rocksjni/options_util.cc \
+ java/rocksjni/persistent_cache.cc \
+ java/rocksjni/ratelimiterjni.cc \
+ java/rocksjni/remove_emptyvalue_compactionfilterjni.cc \
+ java/rocksjni/cassandra_compactionfilterjni.cc \
+ java/rocksjni/cassandra_value_operator.cc \
+ java/rocksjni/restorejni.cc \
+ java/rocksjni/rocks_callback_object.cc \
+ java/rocksjni/rocksjni.cc \
+ java/rocksjni/rocksdb_exception_test.cc \
+ java/rocksjni/slice.cc \
+ java/rocksjni/snapshot.cc \
+ java/rocksjni/sst_file_manager.cc \
+ java/rocksjni/sst_file_writerjni.cc \
+ java/rocksjni/sst_file_readerjni.cc \
+ java/rocksjni/sst_file_reader_iterator.cc \
+ java/rocksjni/statistics.cc \
+ java/rocksjni/statisticsjni.cc \
+ java/rocksjni/table.cc \
+ java/rocksjni/table_filter.cc \
+ java/rocksjni/table_filter_jnicallback.cc \
+ java/rocksjni/thread_status.cc \
+ java/rocksjni/trace_writer.cc \
+ java/rocksjni/trace_writer_jnicallback.cc \
+ java/rocksjni/transaction.cc \
+ java/rocksjni/transaction_db.cc \
+ java/rocksjni/transaction_options.cc \
+ java/rocksjni/transaction_db_options.cc \
+ java/rocksjni/transaction_log.cc \
+ java/rocksjni/transaction_notifier.cc \
+ java/rocksjni/transaction_notifier_jnicallback.cc \
+ java/rocksjni/ttl.cc \
+ java/rocksjni/wal_filter.cc \
+ java/rocksjni/wal_filter_jnicallback.cc \
+ java/rocksjni/write_batch.cc \
+ java/rocksjni/writebatchhandlerjnicallback.cc \
+ java/rocksjni/write_batch_test.cc \
+ java/rocksjni/write_batch_with_index.cc \
+ java/rocksjni/write_buffer_manager.cc
diff --git a/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.cc b/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.cc
new file mode 100644
index 00000000..fa94e7bc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.cc
@@ -0,0 +1,124 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 ROCKSDB_LITE
+#include "table/adaptive/adaptive_table_factory.h"
+
+#include "table/table_builder.h"
+#include "table/format.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+AdaptiveTableFactory::AdaptiveTableFactory(
+ std::shared_ptr<TableFactory> table_factory_to_write,
+ std::shared_ptr<TableFactory> block_based_table_factory,
+ std::shared_ptr<TableFactory> plain_table_factory,
+ std::shared_ptr<TableFactory> cuckoo_table_factory)
+ : table_factory_to_write_(table_factory_to_write),
+ block_based_table_factory_(block_based_table_factory),
+ plain_table_factory_(plain_table_factory),
+ cuckoo_table_factory_(cuckoo_table_factory) {
+ if (!plain_table_factory_) {
+ plain_table_factory_.reset(NewPlainTableFactory());
+ }
+ if (!block_based_table_factory_) {
+ block_based_table_factory_.reset(NewBlockBasedTableFactory());
+ }
+ if (!cuckoo_table_factory_) {
+ cuckoo_table_factory_.reset(NewCuckooTableFactory());
+ }
+ if (!table_factory_to_write_) {
+ table_factory_to_write_ = block_based_table_factory_;
+ }
+}
+
+extern const uint64_t kPlainTableMagicNumber;
+extern const uint64_t kLegacyPlainTableMagicNumber;
+extern const uint64_t kBlockBasedTableMagicNumber;
+extern const uint64_t kLegacyBlockBasedTableMagicNumber;
+extern const uint64_t kCuckooTableMagicNumber;
+
+Status AdaptiveTableFactory::NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool /*prefetch_index_and_filter_in_cache*/) const {
+ Footer footer;
+ auto s = ReadFooterFromFile(file.get(), nullptr /* prefetch_buffer */,
+ file_size, &footer);
+ if (!s.ok()) {
+ return s;
+ }
+ if (footer.table_magic_number() == kPlainTableMagicNumber ||
+ footer.table_magic_number() == kLegacyPlainTableMagicNumber) {
+ return plain_table_factory_->NewTableReader(
+ table_reader_options, std::move(file), file_size, table);
+ } else if (footer.table_magic_number() == kBlockBasedTableMagicNumber ||
+ footer.table_magic_number() == kLegacyBlockBasedTableMagicNumber) {
+ return block_based_table_factory_->NewTableReader(
+ table_reader_options, std::move(file), file_size, table);
+ } else if (footer.table_magic_number() == kCuckooTableMagicNumber) {
+ return cuckoo_table_factory_->NewTableReader(
+ table_reader_options, std::move(file), file_size, table);
+ } else {
+ return Status::NotSupported("Unidentified table format");
+ }
+}
+
+TableBuilder* AdaptiveTableFactory::NewTableBuilder(
+ const TableBuilderOptions& table_builder_options, uint32_t column_family_id,
+ WritableFileWriter* file) const {
+ return table_factory_to_write_->NewTableBuilder(table_builder_options,
+ column_family_id, file);
+}
+
+std::string AdaptiveTableFactory::GetPrintableTableOptions() const {
+ std::string ret;
+ ret.reserve(20000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+
+ if (table_factory_to_write_) {
+ snprintf(buffer, kBufferSize, " write factory (%s) options:\n%s\n",
+ (table_factory_to_write_->Name() ? table_factory_to_write_->Name()
+ : ""),
+ table_factory_to_write_->GetPrintableTableOptions().c_str());
+ ret.append(buffer);
+ }
+ if (plain_table_factory_) {
+ snprintf(buffer, kBufferSize, " %s options:\n%s\n",
+ plain_table_factory_->Name() ? plain_table_factory_->Name() : "",
+ plain_table_factory_->GetPrintableTableOptions().c_str());
+ ret.append(buffer);
+ }
+ if (block_based_table_factory_) {
+ snprintf(
+ buffer, kBufferSize, " %s options:\n%s\n",
+ (block_based_table_factory_->Name() ? block_based_table_factory_->Name()
+ : ""),
+ block_based_table_factory_->GetPrintableTableOptions().c_str());
+ ret.append(buffer);
+ }
+ if (cuckoo_table_factory_) {
+ snprintf(buffer, kBufferSize, " %s options:\n%s\n",
+ cuckoo_table_factory_->Name() ? cuckoo_table_factory_->Name() : "",
+ cuckoo_table_factory_->GetPrintableTableOptions().c_str());
+ ret.append(buffer);
+ }
+ return ret;
+}
+
+extern TableFactory* NewAdaptiveTableFactory(
+ std::shared_ptr<TableFactory> table_factory_to_write,
+ std::shared_ptr<TableFactory> block_based_table_factory,
+ std::shared_ptr<TableFactory> plain_table_factory,
+ std::shared_ptr<TableFactory> cuckoo_table_factory) {
+ return new AdaptiveTableFactory(table_factory_to_write,
+ block_based_table_factory, plain_table_factory, cuckoo_table_factory);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.h b/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.h
new file mode 100644
index 00000000..fcc4c682
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/adaptive/adaptive_table_factory.h
@@ -0,0 +1,63 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct EnvOptions;
+
+class Status;
+class RandomAccessFile;
+class WritableFile;
+class Table;
+class TableBuilder;
+
+class AdaptiveTableFactory : public TableFactory {
+ public:
+ ~AdaptiveTableFactory() {}
+
+ explicit AdaptiveTableFactory(
+ std::shared_ptr<TableFactory> table_factory_to_write,
+ std::shared_ptr<TableFactory> block_based_table_factory,
+ std::shared_ptr<TableFactory> plain_table_factory,
+ std::shared_ptr<TableFactory> cuckoo_table_factory);
+
+ const char* Name() const override { return "AdaptiveTableFactory"; }
+
+ Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool prefetch_index_and_filter_in_cache = true) const override;
+
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_family_id, WritableFileWriter* file) const override;
+
+ // Sanitizes the specified DB Options.
+ Status SanitizeOptions(
+ const DBOptions& /*db_opts*/,
+ const ColumnFamilyOptions& /*cf_opts*/) const override {
+ return Status::OK();
+ }
+
+ std::string GetPrintableTableOptions() const override;
+
+ private:
+ std::shared_ptr<TableFactory> table_factory_to_write_;
+ std::shared_ptr<TableFactory> block_based_table_factory_;
+ std::shared_ptr<TableFactory> plain_table_factory_;
+ std::shared_ptr<TableFactory> cuckoo_table_factory_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block.cc b/storage/rocksdb/rocksdb/table/block_based/block.cc
new file mode 100644
index 00000000..a04dd8ac
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block.cc
@@ -0,0 +1,1004 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Decodes the blocks generated by block_builder.cc.
+
+#include "table/block_based/block.h"
+#include <algorithm>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "logging/logging.h"
+#include "monitoring/perf_context_imp.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/comparator.h"
+#include "table/block_based/block_prefix_index.h"
+#include "table/block_based/data_block_footer.h"
+#include "table/format.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Helper routine: decode the next block entry starting at "p",
+// storing the number of shared key bytes, non_shared key bytes,
+// and the length of the value in "*shared", "*non_shared", and
+// "*value_length", respectively. Will not derefence past "limit".
+//
+// If any errors are detected, returns nullptr. Otherwise, returns a
+// pointer to the key delta (just past the three decoded values).
+struct DecodeEntry {
+ inline const char* operator()(const char* p, const char* limit,
+ uint32_t* shared, uint32_t* non_shared,
+ uint32_t* value_length) {
+ // We need 2 bytes for shared and non_shared size. We also need one more
+ // byte either for value size or the actual value in case of value delta
+ // encoding.
+ assert(limit - p >= 3);
+ *shared = reinterpret_cast<const unsigned char*>(p)[0];
+ *non_shared = reinterpret_cast<const unsigned char*>(p)[1];
+ *value_length = reinterpret_cast<const unsigned char*>(p)[2];
+ if ((*shared | *non_shared | *value_length) < 128) {
+ // Fast path: all three values are encoded in one byte each
+ p += 3;
+ } else {
+ if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr;
+ if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr;
+ if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) {
+ return nullptr;
+ }
+ }
+
+ // Using an assert in place of "return null" since we should not pay the
+ // cost of checking for corruption on every single key decoding
+ assert(!(static_cast<uint32_t>(limit - p) < (*non_shared + *value_length)));
+ return p;
+ }
+};
+
+// Helper routine: similar to DecodeEntry but does not have assertions.
+// Instead, returns nullptr so that caller can detect and report failure.
+struct CheckAndDecodeEntry {
+ inline const char* operator()(const char* p, const char* limit,
+ uint32_t* shared, uint32_t* non_shared,
+ uint32_t* value_length) {
+ // We need 2 bytes for shared and non_shared size. We also need one more
+ // byte either for value size or the actual value in case of value delta
+ // encoding.
+ if (limit - p < 3) {
+ return nullptr;
+ }
+ *shared = reinterpret_cast<const unsigned char*>(p)[0];
+ *non_shared = reinterpret_cast<const unsigned char*>(p)[1];
+ *value_length = reinterpret_cast<const unsigned char*>(p)[2];
+ if ((*shared | *non_shared | *value_length) < 128) {
+ // Fast path: all three values are encoded in one byte each
+ p += 3;
+ } else {
+ if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr;
+ if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr;
+ if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) {
+ return nullptr;
+ }
+ }
+
+ if (static_cast<uint32_t>(limit - p) < (*non_shared + *value_length)) {
+ return nullptr;
+ }
+ return p;
+ }
+};
+
+struct DecodeKey {
+ inline const char* operator()(const char* p, const char* limit,
+ uint32_t* shared, uint32_t* non_shared) {
+ uint32_t value_length;
+ return DecodeEntry()(p, limit, shared, non_shared, &value_length);
+ }
+};
+
+// In format_version 4, which is used by index blocks, the value size is not
+// encoded before the entry, as the value is known to be the handle with the
+// known size.
+struct DecodeKeyV4 {
+ inline const char* operator()(const char* p, const char* limit,
+ uint32_t* shared, uint32_t* non_shared) {
+ // We need 2 bytes for shared and non_shared size. We also need one more
+ // byte either for value size or the actual value in case of value delta
+ // encoding.
+ if (limit - p < 3) return nullptr;
+ *shared = reinterpret_cast<const unsigned char*>(p)[0];
+ *non_shared = reinterpret_cast<const unsigned char*>(p)[1];
+ if ((*shared | *non_shared) < 128) {
+ // Fast path: all three values are encoded in one byte each
+ p += 2;
+ } else {
+ if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr;
+ if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr;
+ }
+ return p;
+ }
+};
+
+void DataBlockIter::Next() {
+ assert(Valid());
+ ParseNextDataKey<DecodeEntry>();
+}
+
+void DataBlockIter::NextOrReport() {
+ assert(Valid());
+ ParseNextDataKey<CheckAndDecodeEntry>();
+}
+
+void IndexBlockIter::Next() {
+ assert(Valid());
+ ParseNextIndexKey();
+}
+
+void IndexBlockIter::Prev() {
+ assert(Valid());
+ // Scan backwards to a restart point before current_
+ const uint32_t original = current_;
+ while (GetRestartPoint(restart_index_) >= original) {
+ if (restart_index_ == 0) {
+ // No more entries
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ return;
+ }
+ restart_index_--;
+ }
+ SeekToRestartPoint(restart_index_);
+ // Loop until end of current entry hits the start of original entry
+ while (ParseNextIndexKey() && NextEntryOffset() < original) {
+ }
+}
+
+// Similar to IndexBlockIter::Prev but also caches the prev entries
+void DataBlockIter::Prev() {
+ assert(Valid());
+
+ assert(prev_entries_idx_ == -1 ||
+ static_cast<size_t>(prev_entries_idx_) < prev_entries_.size());
+ // Check if we can use cached prev_entries_
+ if (prev_entries_idx_ > 0 &&
+ prev_entries_[prev_entries_idx_].offset == current_) {
+ // Read cached CachedPrevEntry
+ prev_entries_idx_--;
+ const CachedPrevEntry& current_prev_entry =
+ prev_entries_[prev_entries_idx_];
+
+ const char* key_ptr = nullptr;
+ if (current_prev_entry.key_ptr != nullptr) {
+ // The key is not delta encoded and stored in the data block
+ key_ptr = current_prev_entry.key_ptr;
+ key_pinned_ = true;
+ } else {
+ // The key is delta encoded and stored in prev_entries_keys_buff_
+ key_ptr = prev_entries_keys_buff_.data() + current_prev_entry.key_offset;
+ key_pinned_ = false;
+ }
+ const Slice current_key(key_ptr, current_prev_entry.key_size);
+
+ current_ = current_prev_entry.offset;
+ key_.SetKey(current_key, false /* copy */);
+ value_ = current_prev_entry.value;
+
+ return;
+ }
+
+ // Clear prev entries cache
+ prev_entries_idx_ = -1;
+ prev_entries_.clear();
+ prev_entries_keys_buff_.clear();
+
+ // Scan backwards to a restart point before current_
+ const uint32_t original = current_;
+ while (GetRestartPoint(restart_index_) >= original) {
+ if (restart_index_ == 0) {
+ // No more entries
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ return;
+ }
+ restart_index_--;
+ }
+
+ SeekToRestartPoint(restart_index_);
+
+ do {
+ if (!ParseNextDataKey<DecodeEntry>()) {
+ break;
+ }
+ Slice current_key = key();
+
+ if (key_.IsKeyPinned()) {
+ // The key is not delta encoded
+ prev_entries_.emplace_back(current_, current_key.data(), 0,
+ current_key.size(), value());
+ } else {
+ // The key is delta encoded, cache decoded key in buffer
+ size_t new_key_offset = prev_entries_keys_buff_.size();
+ prev_entries_keys_buff_.append(current_key.data(), current_key.size());
+
+ prev_entries_.emplace_back(current_, nullptr, new_key_offset,
+ current_key.size(), value());
+ }
+ // Loop until end of current entry hits the start of original entry
+ } while (NextEntryOffset() < original);
+ prev_entries_idx_ = static_cast<int32_t>(prev_entries_.size()) - 1;
+}
+
+void DataBlockIter::Seek(const Slice& target) {
+ Slice seek_key = target;
+ PERF_TIMER_GUARD(block_seek_nanos);
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ uint32_t index = 0;
+ bool ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
+ comparator_);
+
+ if (!ok) {
+ return;
+ }
+ SeekToRestartPoint(index);
+
+ // Linear search (within restart block) for first key >= target
+ while (ParseNextDataKey<DecodeEntry>() && Compare(key_, seek_key) < 0) {
+ }
+}
+
+// Optimized Seek for point lookup for an internal key `target`
+// target = "seek_user_key @ type | seqno".
+//
+// For any type other than kTypeValue, kTypeDeletion, kTypeSingleDeletion,
+// or kTypeBlobIndex, this function behaves identically as Seek().
+//
+// For any type in kTypeValue, kTypeDeletion, kTypeSingleDeletion,
+// or kTypeBlobIndex:
+//
+// If the return value is FALSE, iter location is undefined, and it means:
+// 1) there is no key in this block falling into the range:
+// ["seek_user_key @ type | seqno", "seek_user_key @ kTypeDeletion | 0"],
+// inclusive; AND
+// 2) the last key of this block has a greater user_key from seek_user_key
+//
+// If the return value is TRUE, iter location has two possibilies:
+// 1) If iter is valid, it is set to a location as if set by BinarySeek. In
+// this case, it points to the first key_ with a larger user_key or a
+// matching user_key with a seqno no greater than the seeking seqno.
+// 2) If the iter is invalid, it means that either all the user_key is less
+// than the seek_user_key, or the block ends with a matching user_key but
+// with a smaller [ type | seqno ] (i.e. a larger seqno, or the same seqno
+// but larger type).
+bool DataBlockIter::SeekForGetImpl(const Slice& target) {
+ Slice target_user_key = ExtractUserKey(target);
+ uint32_t map_offset = restarts_ + num_restarts_ * sizeof(uint32_t);
+ uint8_t entry =
+ data_block_hash_index_->Lookup(data_, map_offset, target_user_key);
+
+ if (entry == kCollision) {
+ // HashSeek not effective, falling back
+ Seek(target);
+ return true;
+ }
+
+ if (entry == kNoEntry) {
+ // Even if we cannot find the user_key in this block, the result may
+ // exist in the next block. Consider this exmpale:
+ //
+ // Block N: [aab@100, ... , app@120]
+ // bounary key: axy@50 (we make minimal assumption about a boundary key)
+ // Block N+1: [axy@10, ... ]
+ //
+ // If seek_key = axy@60, the search will starts from Block N.
+ // Even if the user_key is not found in the hash map, the caller still
+ // have to conntinue searching the next block.
+ //
+ // In this case, we pretend the key is the the last restart interval.
+ // The while-loop below will search the last restart interval for the
+ // key. It will stop at the first key that is larger than the seek_key,
+ // or to the end of the block if no one is larger.
+ entry = static_cast<uint8_t>(num_restarts_ - 1);
+ }
+
+ uint32_t restart_index = entry;
+
+ // check if the key is in the restart_interval
+ assert(restart_index < num_restarts_);
+ SeekToRestartPoint(restart_index);
+
+ const char* limit = nullptr;
+ if (restart_index_ + 1 < num_restarts_) {
+ limit = data_ + GetRestartPoint(restart_index_ + 1);
+ } else {
+ limit = data_ + restarts_;
+ }
+
+ while (true) {
+ // Here we only linear seek the target key inside the restart interval.
+ // If a key does not exist inside a restart interval, we avoid
+ // further searching the block content accross restart interval boundary.
+ //
+ // TODO(fwu): check the left and write boundary of the restart interval
+ // to avoid linear seek a target key that is out of range.
+ if (!ParseNextDataKey<DecodeEntry>(limit) || Compare(key_, target) >= 0) {
+ // we stop at the first potential matching user key.
+ break;
+ }
+ }
+
+ if (current_ == restarts_) {
+ // Search reaches to the end of the block. There are three possibilites:
+ // 1) there is only one user_key match in the block (otherwise collsion).
+ // the matching user_key resides in the last restart interval, and it
+ // is the last key of the restart interval and of the block as well.
+ // ParseNextDataKey() skiped it as its [ type | seqno ] is smaller.
+ //
+ // 2) The seek_key is not found in the HashIndex Lookup(), i.e. kNoEntry,
+ // AND all existing user_keys in the restart interval are smaller than
+ // seek_user_key.
+ //
+ // 3) The seek_key is a false positive and happens to be hashed to the
+ // last restart interval, AND all existing user_keys in the restart
+ // interval are smaller than seek_user_key.
+ //
+ // The result may exist in the next block each case, so we return true.
+ return true;
+ }
+
+ if (user_comparator_->Compare(key_.GetUserKey(), target_user_key) != 0) {
+ // the key is not in this block and cannot be at the next block either.
+ return false;
+ }
+
+ // Here we are conservative and only support a limited set of cases
+ ValueType value_type = ExtractValueType(key_.GetKey());
+ if (value_type != ValueType::kTypeValue &&
+ value_type != ValueType::kTypeDeletion &&
+ value_type != ValueType::kTypeSingleDeletion &&
+ value_type != ValueType::kTypeBlobIndex) {
+ Seek(target);
+ return true;
+ }
+
+ // Result found, and the iter is correctly set.
+ return true;
+}
+
+void IndexBlockIter::Seek(const Slice& target) {
+ TEST_SYNC_POINT("IndexBlockIter::Seek:0");
+ Slice seek_key = target;
+ if (!key_includes_seq_) {
+ seek_key = ExtractUserKey(target);
+ }
+ PERF_TIMER_GUARD(block_seek_nanos);
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ status_ = Status::OK();
+ uint32_t index = 0;
+ bool ok = false;
+ if (prefix_index_) {
+ bool prefix_may_exist = true;
+ ok = PrefixSeek(target, &index, &prefix_may_exist);
+ if (!prefix_may_exist) {
+ // This is to let the caller to distinguish between non-existing prefix,
+ // and when key is larger than the last key, which both set Valid() to
+ // false.
+ current_ = restarts_;
+ status_ = Status::NotFound();
+ }
+ } else if (value_delta_encoded_) {
+ ok = BinarySeek<DecodeKeyV4>(seek_key, 0, num_restarts_ - 1, &index,
+ comparator_);
+ } else {
+ ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
+ comparator_);
+ }
+
+ if (!ok) {
+ return;
+ }
+ SeekToRestartPoint(index);
+
+ // Linear search (within restart block) for first key >= target
+ while (ParseNextIndexKey() && Compare(key_, seek_key) < 0) {
+ }
+}
+
+void DataBlockIter::SeekForPrev(const Slice& target) {
+ PERF_TIMER_GUARD(block_seek_nanos);
+ Slice seek_key = target;
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ uint32_t index = 0;
+ bool ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
+ comparator_);
+
+ if (!ok) {
+ return;
+ }
+ SeekToRestartPoint(index);
+
+ // Linear search (within restart block) for first key >= seek_key
+ while (ParseNextDataKey<DecodeEntry>() && Compare(key_, seek_key) < 0) {
+ }
+ if (!Valid()) {
+ SeekToLast();
+ } else {
+ while (Valid() && Compare(key_, seek_key) > 0) {
+ Prev();
+ }
+ }
+}
+
+void DataBlockIter::SeekToFirst() {
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ SeekToRestartPoint(0);
+ ParseNextDataKey<DecodeEntry>();
+}
+
+void DataBlockIter::SeekToFirstOrReport() {
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ SeekToRestartPoint(0);
+ ParseNextDataKey<CheckAndDecodeEntry>();
+}
+
+void IndexBlockIter::SeekToFirst() {
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ status_ = Status::OK();
+ SeekToRestartPoint(0);
+ ParseNextIndexKey();
+}
+
+void DataBlockIter::SeekToLast() {
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ SeekToRestartPoint(num_restarts_ - 1);
+ while (ParseNextDataKey<DecodeEntry>() && NextEntryOffset() < restarts_) {
+ // Keep skipping
+ }
+}
+
+void IndexBlockIter::SeekToLast() {
+ if (data_ == nullptr) { // Not init yet
+ return;
+ }
+ status_ = Status::OK();
+ SeekToRestartPoint(num_restarts_ - 1);
+ while (ParseNextIndexKey() && NextEntryOffset() < restarts_) {
+ // Keep skipping
+ }
+}
+
+template <class TValue>
+void BlockIter<TValue>::CorruptionError() {
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ status_ = Status::Corruption("bad entry in block");
+ key_.Clear();
+ value_.clear();
+}
+
+template <typename DecodeEntryFunc>
+bool DataBlockIter::ParseNextDataKey(const char* limit) {
+ current_ = NextEntryOffset();
+ const char* p = data_ + current_;
+ if (!limit) {
+ limit = data_ + restarts_; // Restarts come right after data
+ }
+
+ if (p >= limit) {
+ // No more entries to return. Mark as invalid.
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ return false;
+ }
+
+ // Decode next entry
+ uint32_t shared, non_shared, value_length;
+ p = DecodeEntryFunc()(p, limit, &shared, &non_shared, &value_length);
+ if (p == nullptr || key_.Size() < shared) {
+ CorruptionError();
+ return false;
+ } else {
+ if (shared == 0) {
+ // If this key dont share any bytes with prev key then we dont need
+ // to decode it and can use it's address in the block directly.
+ key_.SetKey(Slice(p, non_shared), false /* copy */);
+ key_pinned_ = true;
+ } else {
+ // This key share `shared` bytes with prev key, we need to decode it
+ key_.TrimAppend(shared, p, non_shared);
+ key_pinned_ = false;
+ }
+
+ if (global_seqno_ != kDisableGlobalSequenceNumber) {
+ // If we are reading a file with a global sequence number we should
+ // expect that all encoded sequence numbers are zeros and any value
+ // type is kTypeValue, kTypeMerge, kTypeDeletion, or kTypeRangeDeletion.
+ assert(GetInternalKeySeqno(key_.GetInternalKey()) == 0);
+
+ ValueType value_type = ExtractValueType(key_.GetKey());
+ assert(value_type == ValueType::kTypeValue ||
+ value_type == ValueType::kTypeMerge ||
+ value_type == ValueType::kTypeDeletion ||
+ value_type == ValueType::kTypeRangeDeletion);
+
+ if (key_pinned_) {
+ // TODO(tec): Investigate updating the seqno in the loaded block
+ // directly instead of doing a copy and update.
+
+ // We cannot use the key address in the block directly because
+ // we have a global_seqno_ that will overwrite the encoded one.
+ key_.OwnKey();
+ key_pinned_ = false;
+ }
+
+ key_.UpdateInternalKey(global_seqno_, value_type);
+ }
+
+ value_ = Slice(p + non_shared, value_length);
+ if (shared == 0) {
+ while (restart_index_ + 1 < num_restarts_ &&
+ GetRestartPoint(restart_index_ + 1) < current_) {
+ ++restart_index_;
+ }
+ }
+ // else we are in the middle of a restart interval and the restart_index_
+ // thus has not changed
+ return true;
+ }
+}
+
+bool IndexBlockIter::ParseNextIndexKey() {
+ current_ = NextEntryOffset();
+ const char* p = data_ + current_;
+ const char* limit = data_ + restarts_; // Restarts come right after data
+ if (p >= limit) {
+ // No more entries to return. Mark as invalid.
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ return false;
+ }
+
+ // Decode next entry
+ uint32_t shared, non_shared, value_length;
+ if (value_delta_encoded_) {
+ p = DecodeKeyV4()(p, limit, &shared, &non_shared);
+ value_length = 0;
+ } else {
+ p = DecodeEntry()(p, limit, &shared, &non_shared, &value_length);
+ }
+ if (p == nullptr || key_.Size() < shared) {
+ CorruptionError();
+ return false;
+ }
+ if (shared == 0) {
+ // If this key dont share any bytes with prev key then we dont need
+ // to decode it and can use it's address in the block directly.
+ key_.SetKey(Slice(p, non_shared), false /* copy */);
+ key_pinned_ = true;
+ } else {
+ // This key share `shared` bytes with prev key, we need to decode it
+ key_.TrimAppend(shared, p, non_shared);
+ key_pinned_ = false;
+ }
+ value_ = Slice(p + non_shared, value_length);
+ if (shared == 0) {
+ while (restart_index_ + 1 < num_restarts_ &&
+ GetRestartPoint(restart_index_ + 1) < current_) {
+ ++restart_index_;
+ }
+ }
+ // else we are in the middle of a restart interval and the restart_index_
+ // thus has not changed
+ if (value_delta_encoded_ || global_seqno_state_ != nullptr) {
+ DecodeCurrentValue(shared);
+ }
+ return true;
+}
+
+// The format:
+// 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.
+// The format of each key is (shared_size, non_shared_size, shared, non_shared)
+// The format of each value, i.e., block hanlde, 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.
+void IndexBlockIter::DecodeCurrentValue(uint32_t shared) {
+ Slice v(value_.data(), data_ + restarts_ - value_.data());
+ // Delta encoding is used if `shared` != 0.
+ Status decode_s __attribute__((__unused__)) = decoded_value_.DecodeFrom(
+ &v, have_first_key_,
+ (value_delta_encoded_ && shared) ? &decoded_value_.handle : nullptr);
+ assert(decode_s.ok());
+ value_ = Slice(value_.data(), v.data() - value_.data());
+
+ if (global_seqno_state_ != nullptr) {
+ // Overwrite sequence number the same way as in DataBlockIter.
+
+ IterKey& first_internal_key = global_seqno_state_->first_internal_key;
+ first_internal_key.SetInternalKey(decoded_value_.first_internal_key,
+ /* copy */ true);
+
+ assert(GetInternalKeySeqno(first_internal_key.GetInternalKey()) == 0);
+
+ ValueType value_type = ExtractValueType(first_internal_key.GetKey());
+ assert(value_type == ValueType::kTypeValue ||
+ value_type == ValueType::kTypeMerge ||
+ value_type == ValueType::kTypeDeletion ||
+ value_type == ValueType::kTypeRangeDeletion);
+
+ first_internal_key.UpdateInternalKey(global_seqno_state_->global_seqno,
+ value_type);
+ decoded_value_.first_internal_key = first_internal_key.GetKey();
+ }
+}
+
+// Binary search in restart array to find the first restart point that
+// is either the last restart point with a key less than target,
+// which means the key of next restart point is larger than target, or
+// the first restart point with a key = target
+template <class TValue>
+template <typename DecodeKeyFunc>
+bool BlockIter<TValue>::BinarySeek(const Slice& target, uint32_t left,
+ uint32_t right, uint32_t* index,
+ const Comparator* comp) {
+ assert(left <= right);
+
+ while (left < right) {
+ uint32_t mid = (left + right + 1) / 2;
+ uint32_t region_offset = GetRestartPoint(mid);
+ uint32_t shared, non_shared;
+ const char* key_ptr = DecodeKeyFunc()(
+ data_ + region_offset, data_ + restarts_, &shared, &non_shared);
+ if (key_ptr == nullptr || (shared != 0)) {
+ CorruptionError();
+ return false;
+ }
+ Slice mid_key(key_ptr, non_shared);
+ int cmp = comp->Compare(mid_key, target);
+ if (cmp < 0) {
+ // Key at "mid" is smaller than "target". Therefore all
+ // blocks before "mid" are uninteresting.
+ left = mid;
+ } else if (cmp > 0) {
+ // Key at "mid" is >= "target". Therefore all blocks at or
+ // after "mid" are uninteresting.
+ right = mid - 1;
+ } else {
+ left = right = mid;
+ }
+ }
+
+ *index = left;
+ return true;
+}
+
+// Compare target key and the block key of the block of `block_index`.
+// Return -1 if error.
+int IndexBlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) {
+ uint32_t region_offset = GetRestartPoint(block_index);
+ uint32_t shared, non_shared;
+ const char* key_ptr =
+ value_delta_encoded_
+ ? DecodeKeyV4()(data_ + region_offset, data_ + restarts_, &shared,
+ &non_shared)
+ : DecodeKey()(data_ + region_offset, data_ + restarts_, &shared,
+ &non_shared);
+ if (key_ptr == nullptr || (shared != 0)) {
+ CorruptionError();
+ return 1; // Return target is smaller
+ }
+ Slice block_key(key_ptr, non_shared);
+ return Compare(block_key, target);
+}
+
+// Binary search in block_ids to find the first block
+// with a key >= target
+bool IndexBlockIter::BinaryBlockIndexSeek(const Slice& target,
+ uint32_t* block_ids, uint32_t left,
+ uint32_t right, uint32_t* index,
+ bool* prefix_may_exist) {
+ assert(left <= right);
+ assert(index);
+ assert(prefix_may_exist);
+ *prefix_may_exist = true;
+ uint32_t left_bound = left;
+
+ while (left <= right) {
+ uint32_t mid = (right + left) / 2;
+
+ int cmp = CompareBlockKey(block_ids[mid], target);
+ if (!status_.ok()) {
+ return false;
+ }
+ if (cmp < 0) {
+ // Key at "target" is larger than "mid". Therefore all
+ // blocks before or at "mid" are uninteresting.
+ left = mid + 1;
+ } else {
+ // Key at "target" is <= "mid". Therefore all blocks
+ // after "mid" are uninteresting.
+ // If there is only one block left, we found it.
+ if (left == right) break;
+ right = mid;
+ }
+ }
+
+ if (left == right) {
+ // In one of the two following cases:
+ // (1) left is the first one of block_ids
+ // (2) there is a gap of blocks between block of `left` and `left-1`.
+ // we can further distinguish the case of key in the block or key not
+ // existing, by comparing the target key and the key of the previous
+ // block to the left of the block found.
+ if (block_ids[left] > 0 &&
+ (left == left_bound || block_ids[left - 1] != block_ids[left] - 1) &&
+ CompareBlockKey(block_ids[left] - 1, target) > 0) {
+ current_ = restarts_;
+ *prefix_may_exist = false;
+ return false;
+ }
+
+ *index = block_ids[left];
+ return true;
+ } else {
+ assert(left > right);
+
+ // If the next block key is larger than seek key, it is possible that
+ // no key shares the prefix with `target`, or all keys with the same
+ // prefix as `target` are smaller than prefix. In the latter case,
+ // we are mandated to set the position the same as the total order.
+ // In the latter case, either:
+ // (1) `target` falls into the range of the next block. In this case,
+ // we can place the iterator to the next block, or
+ // (2) `target` is larger than all block keys. In this case we can
+ // keep the iterator invalidate without setting `prefix_may_exist`
+ // to false.
+ // We might sometimes end up with setting the total order position
+ // while there is no key sharing the prefix as `target`, but it
+ // still follows the contract.
+ uint32_t right_index = block_ids[right];
+ assert(right_index + 1 <= num_restarts_);
+ if (right_index + 1 < num_restarts_) {
+ if (CompareBlockKey(right_index + 1, target) >= 0) {
+ *index = right_index + 1;
+ return true;
+ } else {
+ // We have to set the flag here because we are not positioning
+ // the iterator to the total order position.
+ *prefix_may_exist = false;
+ }
+ }
+
+ // Mark iterator invalid
+ current_ = restarts_;
+ return false;
+ }
+}
+
+bool IndexBlockIter::PrefixSeek(const Slice& target, uint32_t* index,
+ bool* prefix_may_exist) {
+ assert(index);
+ assert(prefix_may_exist);
+ assert(prefix_index_);
+ *prefix_may_exist = true;
+ Slice seek_key = target;
+ if (!key_includes_seq_) {
+ seek_key = ExtractUserKey(target);
+ }
+ uint32_t* block_ids = nullptr;
+ uint32_t num_blocks = prefix_index_->GetBlocks(target, &block_ids);
+
+ if (num_blocks == 0) {
+ current_ = restarts_;
+ *prefix_may_exist = false;
+ return false;
+ } else {
+ assert(block_ids);
+ return BinaryBlockIndexSeek(seek_key, block_ids, 0, num_blocks - 1, index,
+ prefix_may_exist);
+ }
+}
+
+uint32_t Block::NumRestarts() const {
+ assert(size_ >= 2 * sizeof(uint32_t));
+ uint32_t block_footer = DecodeFixed32(data_ + size_ - sizeof(uint32_t));
+ uint32_t num_restarts = block_footer;
+ if (size_ > kMaxBlockSizeSupportedByHashIndex) {
+ // In BlockBuilder, we have ensured a block with HashIndex is less than
+ // kMaxBlockSizeSupportedByHashIndex (64KiB).
+ //
+ // Therefore, if we encounter a block with a size > 64KiB, the block
+ // cannot have HashIndex. So the footer will directly interpreted as
+ // num_restarts.
+ //
+ // Such check is for backward compatibility. We can ensure legacy block
+ // with a vary large num_restarts i.e. >= 0x80000000 can be interpreted
+ // correctly as no HashIndex even if the MSB of num_restarts is set.
+ return num_restarts;
+ }
+ BlockBasedTableOptions::DataBlockIndexType index_type;
+ UnPackIndexTypeAndNumRestarts(block_footer, &index_type, &num_restarts);
+ return num_restarts;
+}
+
+BlockBasedTableOptions::DataBlockIndexType Block::IndexType() const {
+ assert(size_ >= 2 * sizeof(uint32_t));
+ if (size_ > kMaxBlockSizeSupportedByHashIndex) {
+ // The check is for the same reason as that in NumRestarts()
+ return BlockBasedTableOptions::kDataBlockBinarySearch;
+ }
+ uint32_t block_footer = DecodeFixed32(data_ + size_ - sizeof(uint32_t));
+ uint32_t num_restarts = block_footer;
+ BlockBasedTableOptions::DataBlockIndexType index_type;
+ UnPackIndexTypeAndNumRestarts(block_footer, &index_type, &num_restarts);
+ return index_type;
+}
+
+Block::~Block() {
+ // This sync point can be re-enabled if RocksDB can control the
+ // initialization order of any/all static options created by the user.
+ // TEST_SYNC_POINT("Block::~Block");
+}
+
+Block::Block(BlockContents&& contents, SequenceNumber _global_seqno,
+ size_t read_amp_bytes_per_bit, Statistics* statistics)
+ : contents_(std::move(contents)),
+ data_(contents_.data.data()),
+ size_(contents_.data.size()),
+ restart_offset_(0),
+ num_restarts_(0),
+ global_seqno_(_global_seqno) {
+ TEST_SYNC_POINT("Block::Block:0");
+ if (size_ < sizeof(uint32_t)) {
+ size_ = 0; // Error marker
+ } else {
+ // Should only decode restart points for uncompressed blocks
+ num_restarts_ = NumRestarts();
+ switch (IndexType()) {
+ case BlockBasedTableOptions::kDataBlockBinarySearch:
+ restart_offset_ = static_cast<uint32_t>(size_) -
+ (1 + num_restarts_) * sizeof(uint32_t);
+ if (restart_offset_ > size_ - sizeof(uint32_t)) {
+ // The size is too small for NumRestarts() and therefore
+ // restart_offset_ wrapped around.
+ size_ = 0;
+ }
+ break;
+ case BlockBasedTableOptions::kDataBlockBinaryAndHash:
+ if (size_ < sizeof(uint32_t) /* block footer */ +
+ sizeof(uint16_t) /* NUM_BUCK */) {
+ size_ = 0;
+ break;
+ }
+
+ uint16_t map_offset;
+ data_block_hash_index_.Initialize(
+ contents.data.data(),
+ static_cast<uint16_t>(contents.data.size() -
+ sizeof(uint32_t)), /*chop off
+ NUM_RESTARTS*/
+ &map_offset);
+
+ restart_offset_ = map_offset - num_restarts_ * sizeof(uint32_t);
+
+ if (restart_offset_ > map_offset) {
+ // map_offset is too small for NumRestarts() and
+ // therefore restart_offset_ wrapped around.
+ size_ = 0;
+ break;
+ }
+ break;
+ default:
+ size_ = 0; // Error marker
+ }
+ }
+ if (read_amp_bytes_per_bit != 0 && statistics && size_ != 0) {
+ read_amp_bitmap_.reset(new BlockReadAmpBitmap(
+ restart_offset_, read_amp_bytes_per_bit, statistics));
+ }
+}
+
+DataBlockIter* Block::NewDataIterator(const Comparator* cmp,
+ const Comparator* ucmp,
+ DataBlockIter* iter, Statistics* stats,
+ bool block_contents_pinned) {
+ DataBlockIter* ret_iter;
+ if (iter != nullptr) {
+ ret_iter = iter;
+ } else {
+ ret_iter = new DataBlockIter;
+ }
+ if (size_ < 2 * sizeof(uint32_t)) {
+ ret_iter->Invalidate(Status::Corruption("bad block contents"));
+ return ret_iter;
+ }
+ if (num_restarts_ == 0) {
+ // Empty block.
+ ret_iter->Invalidate(Status::OK());
+ return ret_iter;
+ } else {
+ ret_iter->Initialize(
+ cmp, ucmp, data_, restart_offset_, num_restarts_, global_seqno_,
+ read_amp_bitmap_.get(), block_contents_pinned,
+ data_block_hash_index_.Valid() ? &data_block_hash_index_ : nullptr);
+ if (read_amp_bitmap_) {
+ if (read_amp_bitmap_->GetStatistics() != stats) {
+ // DB changed the Statistics pointer, we need to notify read_amp_bitmap_
+ read_amp_bitmap_->SetStatistics(stats);
+ }
+ }
+ }
+
+ return ret_iter;
+}
+
+IndexBlockIter* Block::NewIndexIterator(
+ const Comparator* cmp, const Comparator* ucmp, IndexBlockIter* iter,
+ Statistics* /*stats*/, bool total_order_seek, bool have_first_key,
+ bool key_includes_seq, bool value_is_full, bool block_contents_pinned,
+ BlockPrefixIndex* prefix_index) {
+ IndexBlockIter* ret_iter;
+ if (iter != nullptr) {
+ ret_iter = iter;
+ } else {
+ ret_iter = new IndexBlockIter;
+ }
+ if (size_ < 2 * sizeof(uint32_t)) {
+ ret_iter->Invalidate(Status::Corruption("bad block contents"));
+ return ret_iter;
+ }
+ if (num_restarts_ == 0) {
+ // Empty block.
+ ret_iter->Invalidate(Status::OK());
+ return ret_iter;
+ } else {
+ BlockPrefixIndex* prefix_index_ptr =
+ total_order_seek ? nullptr : prefix_index;
+ ret_iter->Initialize(cmp, ucmp, data_, restart_offset_, num_restarts_,
+ global_seqno_, prefix_index_ptr, have_first_key,
+ key_includes_seq, value_is_full,
+ block_contents_pinned);
+ }
+
+ return ret_iter;
+}
+
+size_t Block::ApproximateMemoryUsage() const {
+ size_t usage = usable_size();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size((void*)this);
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ if (read_amp_bitmap_) {
+ usage += read_amp_bitmap_->ApproximateMemoryUsage();
+ }
+ return usage;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block.h b/storage/rocksdb/rocksdb/table/block_based/block.h
new file mode 100644
index 00000000..e82a1b2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block.h
@@ -0,0 +1,631 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+#include "port/malloc.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_prefix_index.h"
+#include "table/block_based/data_block_hash_index.h"
+#include "table/format.h"
+#include "table/internal_iterator.h"
+#include "test_util/sync_point.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct BlockContents;
+class Comparator;
+template <class TValue>
+class BlockIter;
+class DataBlockIter;
+class IndexBlockIter;
+class BlockPrefixIndex;
+
+// BlockReadAmpBitmap is a bitmap that map the ROCKSDB_NAMESPACE::Block data
+// bytes to a bitmap with ratio bytes_per_bit. Whenever we access a range of
+// bytes in the Block we update the bitmap and increment
+// READ_AMP_ESTIMATE_USEFUL_BYTES.
+class BlockReadAmpBitmap {
+ public:
+ explicit BlockReadAmpBitmap(size_t block_size, size_t bytes_per_bit,
+ Statistics* statistics)
+ : bitmap_(nullptr),
+ bytes_per_bit_pow_(0),
+ statistics_(statistics),
+ rnd_(Random::GetTLSInstance()->Uniform(
+ static_cast<int>(bytes_per_bit))) {
+ TEST_SYNC_POINT_CALLBACK("BlockReadAmpBitmap:rnd", &rnd_);
+ assert(block_size > 0 && bytes_per_bit > 0);
+
+ // convert bytes_per_bit to be a power of 2
+ while (bytes_per_bit >>= 1) {
+ bytes_per_bit_pow_++;
+ }
+
+ // num_bits_needed = ceil(block_size / bytes_per_bit)
+ size_t num_bits_needed = ((block_size - 1) >> bytes_per_bit_pow_) + 1;
+ assert(num_bits_needed > 0);
+
+ // bitmap_size = ceil(num_bits_needed / kBitsPerEntry)
+ size_t bitmap_size = (num_bits_needed - 1) / kBitsPerEntry + 1;
+
+ // Create bitmap and set all the bits to 0
+ bitmap_ = new std::atomic<uint32_t>[bitmap_size]();
+
+ RecordTick(GetStatistics(), READ_AMP_TOTAL_READ_BYTES, block_size);
+ }
+
+ ~BlockReadAmpBitmap() { delete[] bitmap_; }
+
+ void Mark(uint32_t start_offset, uint32_t end_offset) {
+ assert(end_offset >= start_offset);
+ // Index of first bit in mask
+ uint32_t start_bit =
+ (start_offset + (1 << bytes_per_bit_pow_) - rnd_ - 1) >>
+ bytes_per_bit_pow_;
+ // Index of last bit in mask + 1
+ uint32_t exclusive_end_bit =
+ (end_offset + (1 << bytes_per_bit_pow_) - rnd_) >> bytes_per_bit_pow_;
+ if (start_bit >= exclusive_end_bit) {
+ return;
+ }
+ assert(exclusive_end_bit > 0);
+
+ if (GetAndSet(start_bit) == 0) {
+ uint32_t new_useful_bytes = (exclusive_end_bit - start_bit)
+ << bytes_per_bit_pow_;
+ RecordTick(GetStatistics(), READ_AMP_ESTIMATE_USEFUL_BYTES,
+ new_useful_bytes);
+ }
+ }
+
+ Statistics* GetStatistics() {
+ return statistics_.load(std::memory_order_relaxed);
+ }
+
+ void SetStatistics(Statistics* stats) { statistics_.store(stats); }
+
+ uint32_t GetBytesPerBit() { return 1 << bytes_per_bit_pow_; }
+
+ size_t ApproximateMemoryUsage() const {
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ return malloc_usable_size((void*)this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return sizeof(*this);
+ }
+
+ private:
+ // Get the current value of bit at `bit_idx` and set it to 1
+ inline bool GetAndSet(uint32_t bit_idx) {
+ const uint32_t byte_idx = bit_idx / kBitsPerEntry;
+ const uint32_t bit_mask = 1 << (bit_idx % kBitsPerEntry);
+
+ return bitmap_[byte_idx].fetch_or(bit_mask, std::memory_order_relaxed) &
+ bit_mask;
+ }
+
+ const uint32_t kBytesPersEntry = sizeof(uint32_t); // 4 bytes
+ const uint32_t kBitsPerEntry = kBytesPersEntry * 8; // 32 bits
+
+ // Bitmap used to record the bytes that we read, use atomic to protect
+ // against multiple threads updating the same bit
+ std::atomic<uint32_t>* bitmap_;
+ // (1 << bytes_per_bit_pow_) is bytes_per_bit. Use power of 2 to optimize
+ // muliplication and division
+ uint8_t bytes_per_bit_pow_;
+ // Pointer to DB Statistics object, Since this bitmap may outlive the DB
+ // this pointer maybe invalid, but the DB will update it to a valid pointer
+ // by using SetStatistics() before calling Mark()
+ std::atomic<Statistics*> statistics_;
+ uint32_t rnd_;
+};
+
+// This Block class is not for any old block: it is designed to hold only
+// uncompressed blocks containing sorted key-value pairs. It is thus
+// suitable for storing uncompressed data blocks, index blocks (including
+// partitions), range deletion blocks, properties blocks, metaindex blocks,
+// as well as the top level of the partitioned filter structure (which is
+// actually an index of the filter partitions). It is NOT suitable for
+// compressed blocks in general, filter blocks/partitions, or compression
+// dictionaries (since the latter do not contain sorted key-value pairs).
+// Use BlockContents directly for those.
+//
+// See https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format
+// for details of the format and the various block types.
+class Block {
+ public:
+ // Initialize the block with the specified contents.
+ explicit Block(BlockContents&& contents, SequenceNumber _global_seqno,
+ size_t read_amp_bytes_per_bit = 0,
+ Statistics* statistics = nullptr);
+ // No copying allowed
+ Block(const Block&) = delete;
+ void operator=(const Block&) = delete;
+
+ ~Block();
+
+ size_t size() const { return size_; }
+ const char* data() const { return data_; }
+ // The additional memory space taken by the block data.
+ size_t usable_size() const { return contents_.usable_size(); }
+ uint32_t NumRestarts() const;
+ bool own_bytes() const { return contents_.own_bytes(); }
+
+ BlockBasedTableOptions::DataBlockIndexType IndexType() const;
+
+ // If comparator is InternalKeyComparator, user_comparator is its user
+ // comparator; they are equal otherwise.
+ //
+ // If iter is null, return new Iterator
+ // If iter is not null, update this one and return it as Iterator*
+ //
+ // Updates read_amp_bitmap_ if it is not nullptr.
+ //
+ // If `block_contents_pinned` is true, the caller will guarantee that when
+ // the cleanup functions are transferred from the iterator to other
+ // classes, e.g. PinnableSlice, the pointer to the bytes will still be
+ // valid. Either the iterator holds cache handle or ownership of some resource
+ // and release them in a release function, or caller is sure that the data
+ // will not go away (for example, it's from mmapped file which will not be
+ // closed).
+ //
+ // NOTE: for the hash based lookup, if a key prefix doesn't match any key,
+ // the iterator will simply be set as "invalid", rather than returning
+ // the key that is just pass the target key.
+ DataBlockIter* NewDataIterator(const Comparator* comparator,
+ const Comparator* user_comparator,
+ DataBlockIter* iter = nullptr,
+ Statistics* stats = nullptr,
+ bool block_contents_pinned = false);
+
+ // key_includes_seq, default true, means that the keys are in internal key
+ // format.
+ // value_is_full, default true, means that no delta encoding is
+ // applied to values.
+ //
+ // If `prefix_index` is not nullptr this block will do hash lookup for the key
+ // prefix. If total_order_seek is true, prefix_index_ is ignored.
+ //
+ // `have_first_key` controls whether IndexValue will contain
+ // first_internal_key. It affects data serialization format, so the same value
+ // have_first_key must be used when writing and reading index.
+ // It is determined by IndexType property of the table.
+ IndexBlockIter* NewIndexIterator(const Comparator* comparator,
+ const Comparator* user_comparator,
+ IndexBlockIter* iter, Statistics* stats,
+ bool total_order_seek, bool have_first_key,
+ bool key_includes_seq, bool value_is_full,
+ bool block_contents_pinned = false,
+ BlockPrefixIndex* prefix_index = nullptr);
+
+ // Report an approximation of how much memory has been used.
+ size_t ApproximateMemoryUsage() const;
+
+ SequenceNumber global_seqno() const { return global_seqno_; }
+
+ private:
+ BlockContents contents_;
+ const char* data_; // contents_.data.data()
+ size_t size_; // contents_.data.size()
+ uint32_t restart_offset_; // Offset in data_ of restart array
+ uint32_t num_restarts_;
+ std::unique_ptr<BlockReadAmpBitmap> read_amp_bitmap_;
+ // All keys in the block will have seqno = global_seqno_, regardless of
+ // the encoded value (kDisableGlobalSequenceNumber means disabled)
+ const SequenceNumber global_seqno_;
+
+ DataBlockHashIndex data_block_hash_index_;
+};
+
+template <class TValue>
+class BlockIter : public InternalIteratorBase<TValue> {
+ public:
+ void InitializeBase(const Comparator* comparator, const char* data,
+ uint32_t restarts, uint32_t num_restarts,
+ SequenceNumber global_seqno, bool block_contents_pinned) {
+ assert(data_ == nullptr); // Ensure it is called only once
+ assert(num_restarts > 0); // Ensure the param is valid
+
+ comparator_ = comparator;
+ data_ = data;
+ restarts_ = restarts;
+ num_restarts_ = num_restarts;
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ global_seqno_ = global_seqno;
+ block_contents_pinned_ = block_contents_pinned;
+ cache_handle_ = nullptr;
+ }
+
+ // Makes Valid() return false, status() return `s`, and Seek()/Prev()/etc do
+ // nothing. Calls cleanup functions.
+ void InvalidateBase(Status s) {
+ // Assert that the BlockIter is never deleted while Pinning is Enabled.
+ assert(!pinned_iters_mgr_ ||
+ (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
+
+ data_ = nullptr;
+ current_ = restarts_;
+ status_ = s;
+
+ // Call cleanup callbacks.
+ Cleanable::Reset();
+ }
+
+ bool Valid() const override { return current_ < restarts_; }
+ Status status() const override { return status_; }
+ Slice key() const override {
+ assert(Valid());
+ return key_.GetKey();
+ }
+
+#ifndef NDEBUG
+ ~BlockIter() override {
+ // Assert that the BlockIter is never deleted while Pinning is Enabled.
+ assert(!pinned_iters_mgr_ ||
+ (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
+ }
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ }
+ PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
+#endif
+
+ bool IsKeyPinned() const override {
+ return block_contents_pinned_ && key_pinned_;
+ }
+
+ bool IsValuePinned() const override { return block_contents_pinned_; }
+
+ size_t TEST_CurrentEntrySize() { return NextEntryOffset() - current_; }
+
+ uint32_t ValueOffset() const {
+ return static_cast<uint32_t>(value_.data() - data_);
+ }
+
+ void SetCacheHandle(Cache::Handle* handle) { cache_handle_ = handle; }
+
+ Cache::Handle* cache_handle() { return cache_handle_; }
+
+ protected:
+ // Note: The type could be changed to InternalKeyComparator but we see a weird
+ // performance drop by that.
+ const Comparator* comparator_;
+ const char* data_; // underlying block contents
+ uint32_t num_restarts_; // Number of uint32_t entries in restart array
+
+ // Index of restart block in which current_ or current_-1 falls
+ uint32_t restart_index_;
+ uint32_t restarts_; // Offset of restart array (list of fixed32)
+ // current_ is offset in data_ of current entry. >= restarts_ if !Valid
+ uint32_t current_;
+ IterKey key_;
+ Slice value_;
+ Status status_;
+ bool key_pinned_;
+ // Whether the block data is guaranteed to outlive this iterator, and
+ // as long as the cleanup functions are transferred to another class,
+ // e.g. PinnableSlice, the pointer to the bytes will still be valid.
+ bool block_contents_pinned_;
+ SequenceNumber global_seqno_;
+
+ private:
+ // Store the cache handle, if the block is cached. We need this since the
+ // only other place the handle is stored is as an argument to the Cleanable
+ // function callback, which is hard to retrieve. When multiple value
+ // PinnableSlices reference the block, they need the cache handle in order
+ // to bump up the ref count
+ Cache::Handle* cache_handle_;
+
+ public:
+ // Return the offset in data_ just past the end of the current entry.
+ inline uint32_t NextEntryOffset() const {
+ // NOTE: We don't support blocks bigger than 2GB
+ return static_cast<uint32_t>((value_.data() + value_.size()) - data_);
+ }
+
+ uint32_t GetRestartPoint(uint32_t index) {
+ assert(index < num_restarts_);
+ return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));
+ }
+
+ void SeekToRestartPoint(uint32_t index) {
+ key_.Clear();
+ restart_index_ = index;
+ // current_ will be fixed by ParseNextKey();
+
+ // ParseNextKey() starts at the end of value_, so set value_ accordingly
+ uint32_t offset = GetRestartPoint(index);
+ value_ = Slice(data_ + offset, 0);
+ }
+
+ void CorruptionError();
+
+ template <typename DecodeKeyFunc>
+ inline bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
+ uint32_t* index, const Comparator* comp);
+};
+
+class DataBlockIter final : public BlockIter<Slice> {
+ public:
+ DataBlockIter()
+ : BlockIter(), read_amp_bitmap_(nullptr), last_bitmap_offset_(0) {}
+ DataBlockIter(const Comparator* comparator, const Comparator* user_comparator,
+ const char* data, uint32_t restarts, uint32_t num_restarts,
+ SequenceNumber global_seqno,
+ BlockReadAmpBitmap* read_amp_bitmap, bool block_contents_pinned,
+ DataBlockHashIndex* data_block_hash_index)
+ : DataBlockIter() {
+ Initialize(comparator, user_comparator, data, restarts, num_restarts,
+ global_seqno, read_amp_bitmap, block_contents_pinned,
+ data_block_hash_index);
+ }
+ void Initialize(const Comparator* comparator,
+ const Comparator* user_comparator, const char* data,
+ uint32_t restarts, uint32_t num_restarts,
+ SequenceNumber global_seqno,
+ BlockReadAmpBitmap* read_amp_bitmap,
+ bool block_contents_pinned,
+ DataBlockHashIndex* data_block_hash_index) {
+ InitializeBase(comparator, data, restarts, num_restarts, global_seqno,
+ block_contents_pinned);
+ user_comparator_ = user_comparator;
+ key_.SetIsUserKey(false);
+ read_amp_bitmap_ = read_amp_bitmap;
+ last_bitmap_offset_ = current_ + 1;
+ data_block_hash_index_ = data_block_hash_index;
+ }
+
+ Slice value() const override {
+ assert(Valid());
+ if (read_amp_bitmap_ && current_ < restarts_ &&
+ current_ != last_bitmap_offset_) {
+ read_amp_bitmap_->Mark(current_ /* current entry offset */,
+ NextEntryOffset() - 1);
+ last_bitmap_offset_ = current_;
+ }
+ return value_;
+ }
+
+ void Seek(const Slice& target) override;
+
+ inline bool SeekForGet(const Slice& target) {
+ if (!data_block_hash_index_) {
+ Seek(target);
+ return true;
+ }
+
+ return SeekForGetImpl(target);
+ }
+
+ void SeekForPrev(const Slice& target) override;
+
+ void Prev() override;
+
+ void Next() final override;
+
+ // Try to advance to the next entry in the block. If there is data corruption
+ // or error, report it to the caller instead of aborting the process. May
+ // incur higher CPU overhead because we need to perform check on every entry.
+ void NextOrReport();
+
+ void SeekToFirst() override;
+
+ // Try to seek to the first entry in the block. If there is data corruption
+ // or error, report it to caller instead of aborting the process. May incur
+ // higher CPU overhead because we need to perform check on every entry.
+ void SeekToFirstOrReport();
+
+ void SeekToLast() override;
+
+ void Invalidate(Status s) {
+ InvalidateBase(s);
+ // Clear prev entries cache.
+ prev_entries_keys_buff_.clear();
+ prev_entries_.clear();
+ prev_entries_idx_ = -1;
+ }
+
+ private:
+ // read-amp bitmap
+ BlockReadAmpBitmap* read_amp_bitmap_;
+ // last `current_` value we report to read-amp bitmp
+ mutable uint32_t last_bitmap_offset_;
+ struct CachedPrevEntry {
+ explicit CachedPrevEntry(uint32_t _offset, const char* _key_ptr,
+ size_t _key_offset, size_t _key_size, Slice _value)
+ : offset(_offset),
+ key_ptr(_key_ptr),
+ key_offset(_key_offset),
+ key_size(_key_size),
+ value(_value) {}
+
+ // offset of entry in block
+ uint32_t offset;
+ // Pointer to key data in block (nullptr if key is delta-encoded)
+ const char* key_ptr;
+ // offset of key in prev_entries_keys_buff_ (0 if key_ptr is not nullptr)
+ size_t key_offset;
+ // size of key
+ size_t key_size;
+ // value slice pointing to data in block
+ Slice value;
+ };
+ std::string prev_entries_keys_buff_;
+ std::vector<CachedPrevEntry> prev_entries_;
+ int32_t prev_entries_idx_ = -1;
+
+ DataBlockHashIndex* data_block_hash_index_;
+ const Comparator* user_comparator_;
+
+ template <typename DecodeEntryFunc>
+ inline bool ParseNextDataKey(const char* limit = nullptr);
+
+ inline int Compare(const IterKey& ikey, const Slice& b) const {
+ return comparator_->Compare(ikey.GetInternalKey(), b);
+ }
+
+ bool SeekForGetImpl(const Slice& target);
+};
+
+class IndexBlockIter final : public BlockIter<IndexValue> {
+ public:
+ IndexBlockIter() : BlockIter(), prefix_index_(nullptr) {}
+
+ Slice key() const override {
+ assert(Valid());
+ return key_.GetKey();
+ }
+ // key_includes_seq, default true, means that the keys are in internal key
+ // format.
+ // value_is_full, default true, means that no delta encoding is
+ // applied to values.
+ void Initialize(const Comparator* comparator,
+ const Comparator* user_comparator, const char* data,
+ uint32_t restarts, uint32_t num_restarts,
+ SequenceNumber global_seqno, BlockPrefixIndex* prefix_index,
+ bool have_first_key, bool key_includes_seq,
+ bool value_is_full, bool block_contents_pinned) {
+ InitializeBase(key_includes_seq ? comparator : user_comparator, data,
+ restarts, num_restarts, kDisableGlobalSequenceNumber,
+ block_contents_pinned);
+ key_includes_seq_ = key_includes_seq;
+ key_.SetIsUserKey(!key_includes_seq_);
+ prefix_index_ = prefix_index;
+ value_delta_encoded_ = !value_is_full;
+ have_first_key_ = have_first_key;
+ if (have_first_key_ && global_seqno != kDisableGlobalSequenceNumber) {
+ global_seqno_state_.reset(new GlobalSeqnoState(global_seqno));
+ } else {
+ global_seqno_state_.reset();
+ }
+ }
+
+ Slice user_key() const override {
+ if (key_includes_seq_) {
+ return ExtractUserKey(key());
+ }
+ return key();
+ }
+
+ IndexValue value() const override {
+ assert(Valid());
+ if (value_delta_encoded_ || global_seqno_state_ != nullptr) {
+ return decoded_value_;
+ } else {
+ IndexValue entry;
+ Slice v = value_;
+ Status decode_s __attribute__((__unused__)) =
+ entry.DecodeFrom(&v, have_first_key_, nullptr);
+ assert(decode_s.ok());
+ return entry;
+ }
+ }
+
+ // IndexBlockIter follows a different contract for prefix iterator
+ // from data iterators.
+ // If prefix of the seek key `target` exists in the file, it must
+ // return the same result as total order seek.
+ // If the prefix of `target` doesn't exist in the file, it can either
+ // return the result of total order seek, or set both of Valid() = false
+ // and status() = NotFound().
+ void Seek(const Slice& target) override;
+
+ void SeekForPrev(const Slice&) override {
+ assert(false);
+ current_ = restarts_;
+ restart_index_ = num_restarts_;
+ status_ = Status::InvalidArgument(
+ "RocksDB internal error: should never call SeekForPrev() on index "
+ "blocks");
+ key_.Clear();
+ value_.clear();
+ }
+
+ void Prev() override;
+
+ void Next() override;
+
+ void SeekToFirst() override;
+
+ void SeekToLast() override;
+
+ void Invalidate(Status s) { InvalidateBase(s); }
+
+ bool IsValuePinned() const override {
+ return global_seqno_state_ != nullptr ? false : BlockIter::IsValuePinned();
+ }
+
+ private:
+ // Key is in InternalKey format
+ bool key_includes_seq_;
+ bool value_delta_encoded_;
+ bool have_first_key_; // value includes first_internal_key
+ BlockPrefixIndex* prefix_index_;
+ // Whether the value is delta encoded. In that case the value is assumed to be
+ // BlockHandle. The first value in each restart interval is the full encoded
+ // BlockHandle; the restart of encoded size part of the BlockHandle. The
+ // offset of delta encoded BlockHandles is computed by adding the size of
+ // previous delta encoded values in the same restart interval to the offset of
+ // the first value in that restart interval.
+ IndexValue decoded_value_;
+
+ // When sequence number overwriting is enabled, this struct contains the seqno
+ // to overwrite with, and current first_internal_key with overwritten seqno.
+ // This is rarely used, so we put it behind a pointer and only allocate when
+ // needed.
+ struct GlobalSeqnoState {
+ // First internal key according to current index entry, but with sequence
+ // number overwritten to global_seqno.
+ IterKey first_internal_key;
+ SequenceNumber global_seqno;
+
+ explicit GlobalSeqnoState(SequenceNumber seqno) : global_seqno(seqno) {}
+ };
+
+ std::unique_ptr<GlobalSeqnoState> global_seqno_state_;
+
+ // Set *prefix_may_exist to false if no key possibly share the same prefix
+ // as `target`. If not set, the result position should be the same as total
+ // order Seek.
+ bool PrefixSeek(const Slice& target, uint32_t* index, bool* prefix_may_exist);
+ // Set *prefix_may_exist to false if no key can possibly share the same
+ // prefix as `target`. If not set, the result position should be the same
+ // as total order seek.
+ bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
+ uint32_t left, uint32_t right, uint32_t* index,
+ bool* prefix_may_exist);
+ inline int CompareBlockKey(uint32_t block_index, const Slice& target);
+
+ inline int Compare(const Slice& a, const Slice& b) const {
+ return comparator_->Compare(a, b);
+ }
+
+ inline int Compare(const IterKey& ikey, const Slice& b) const {
+ return comparator_->Compare(ikey.GetKey(), b);
+ }
+
+ inline bool ParseNextIndexKey();
+
+ // When value_delta_encoded_ is enabled it decodes the value which is assumed
+ // to be BlockHandle and put it to decoded_value_
+ inline void DecodeCurrentValue(uint32_t shared);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.cc b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.cc
new file mode 100644
index 00000000..de3f5cb1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/block_based_filter_block.h"
+#include <algorithm>
+
+#include "db/dbformat.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/filter_policy.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+void AppendItem(std::string* props, const std::string& key,
+ const std::string& value) {
+ char cspace = ' ';
+ std::string value_str("");
+ size_t i = 0;
+ const size_t dataLength = 64;
+ const size_t tabLength = 2;
+ const size_t offLength = 16;
+
+ value_str.append(&value[i], std::min(size_t(dataLength), value.size()));
+ i += dataLength;
+ while (i < value.size()) {
+ value_str.append("\n");
+ value_str.append(offLength, cspace);
+ value_str.append(&value[i], std::min(size_t(dataLength), value.size() - i));
+ i += dataLength;
+ }
+
+ std::string result("");
+ if (key.size() < (offLength - tabLength))
+ result.append(size_t((offLength - tabLength)) - key.size(), cspace);
+ result.append(key);
+
+ props->append(result + ": " + value_str + "\n");
+}
+
+template <class TKey>
+void AppendItem(std::string* props, const TKey& key, const std::string& value) {
+ std::string key_str = ROCKSDB_NAMESPACE::ToString(key);
+ AppendItem(props, key_str, value);
+}
+} // namespace
+
+// See doc/table_format.txt for an explanation of the filter block format.
+
+// Generate new filter every 2KB of data
+static const size_t kFilterBaseLg = 11;
+static const size_t kFilterBase = 1 << kFilterBaseLg;
+
+BlockBasedFilterBlockBuilder::BlockBasedFilterBlockBuilder(
+ const SliceTransform* prefix_extractor,
+ const BlockBasedTableOptions& table_opt)
+ : policy_(table_opt.filter_policy.get()),
+ prefix_extractor_(prefix_extractor),
+ whole_key_filtering_(table_opt.whole_key_filtering),
+ prev_prefix_start_(0),
+ prev_prefix_size_(0),
+ num_added_(0) {
+ assert(policy_);
+}
+
+void BlockBasedFilterBlockBuilder::StartBlock(uint64_t block_offset) {
+ uint64_t filter_index = (block_offset / kFilterBase);
+ assert(filter_index >= filter_offsets_.size());
+ while (filter_index > filter_offsets_.size()) {
+ GenerateFilter();
+ }
+}
+
+void BlockBasedFilterBlockBuilder::Add(const Slice& key) {
+ if (prefix_extractor_ && prefix_extractor_->InDomain(key)) {
+ AddPrefix(key);
+ }
+
+ if (whole_key_filtering_) {
+ AddKey(key);
+ }
+}
+
+// Add key to filter if needed
+inline void BlockBasedFilterBlockBuilder::AddKey(const Slice& key) {
+ num_added_++;
+ start_.push_back(entries_.size());
+ entries_.append(key.data(), key.size());
+}
+
+// Add prefix to filter if needed
+inline void BlockBasedFilterBlockBuilder::AddPrefix(const Slice& key) {
+ // get slice for most recently added entry
+ Slice prev;
+ if (prev_prefix_size_ > 0) {
+ prev = Slice(entries_.data() + prev_prefix_start_, prev_prefix_size_);
+ }
+
+ Slice prefix = prefix_extractor_->Transform(key);
+ // insert prefix only when it's different from the previous prefix.
+ if (prev.size() == 0 || prefix != prev) {
+ prev_prefix_start_ = entries_.size();
+ prev_prefix_size_ = prefix.size();
+ AddKey(prefix);
+ }
+}
+
+Slice BlockBasedFilterBlockBuilder::Finish(const BlockHandle& /*tmp*/,
+ Status* status) {
+ // In this impl we ignore BlockHandle
+ *status = Status::OK();
+ if (!start_.empty()) {
+ GenerateFilter();
+ }
+
+ // Append array of per-filter offsets
+ const uint32_t array_offset = static_cast<uint32_t>(result_.size());
+ for (size_t i = 0; i < filter_offsets_.size(); i++) {
+ PutFixed32(&result_, filter_offsets_[i]);
+ }
+
+ PutFixed32(&result_, array_offset);
+ result_.push_back(kFilterBaseLg); // Save encoding parameter in result
+ return Slice(result_);
+}
+
+void BlockBasedFilterBlockBuilder::GenerateFilter() {
+ const size_t num_entries = start_.size();
+ if (num_entries == 0) {
+ // Fast path if there are no keys for this filter
+ filter_offsets_.push_back(static_cast<uint32_t>(result_.size()));
+ return;
+ }
+
+ // Make list of keys from flattened key structure
+ start_.push_back(entries_.size()); // Simplify length computation
+ tmp_entries_.resize(num_entries);
+ for (size_t i = 0; i < num_entries; i++) {
+ const char* base = entries_.data() + start_[i];
+ size_t length = start_[i + 1] - start_[i];
+ tmp_entries_[i] = Slice(base, length);
+ }
+
+ // Generate filter for current set of keys and append to result_.
+ filter_offsets_.push_back(static_cast<uint32_t>(result_.size()));
+ policy_->CreateFilter(&tmp_entries_[0], static_cast<int>(num_entries),
+ &result_);
+
+ tmp_entries_.clear();
+ entries_.clear();
+ start_.clear();
+ prev_prefix_start_ = 0;
+ prev_prefix_size_ = 0;
+}
+
+BlockBasedFilterBlockReader::BlockBasedFilterBlockReader(
+ const BlockBasedTable* t, CachableEntry<BlockContents>&& filter_block)
+ : FilterBlockReaderCommon(t, std::move(filter_block)) {
+ assert(table());
+ assert(table()->get_rep());
+ assert(table()->get_rep()->filter_policy);
+}
+
+std::unique_ptr<FilterBlockReader> BlockBasedFilterBlockReader::Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context) {
+ assert(table);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+
+ CachableEntry<BlockContents> filter_block;
+ if (prefetch || !use_cache) {
+ const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
+ use_cache, nullptr /* get_context */,
+ lookup_context, &filter_block);
+ if (!s.ok()) {
+ return std::unique_ptr<FilterBlockReader>();
+ }
+
+ if (use_cache && !pin) {
+ filter_block.Reset();
+ }
+ }
+
+ return std::unique_ptr<FilterBlockReader>(
+ new BlockBasedFilterBlockReader(table, std::move(filter_block)));
+}
+
+bool BlockBasedFilterBlockReader::KeyMayMatch(
+ const Slice& key, const SliceTransform* /* prefix_extractor */,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) {
+ assert(block_offset != kNotValid);
+ if (!whole_key_filtering()) {
+ return true;
+ }
+ return MayMatch(key, block_offset, no_io, get_context, lookup_context);
+}
+
+bool BlockBasedFilterBlockReader::PrefixMayMatch(
+ const Slice& prefix, const SliceTransform* /* prefix_extractor */,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) {
+ assert(block_offset != kNotValid);
+ return MayMatch(prefix, block_offset, no_io, get_context, lookup_context);
+}
+
+bool BlockBasedFilterBlockReader::ParseFieldsFromBlock(
+ const BlockContents& contents, const char** data, const char** offset,
+ size_t* num, size_t* base_lg) {
+ assert(data);
+ assert(offset);
+ assert(num);
+ assert(base_lg);
+
+ const size_t n = contents.data.size();
+ if (n < 5) { // 1 byte for base_lg and 4 for start of offset array
+ return false;
+ }
+
+ const uint32_t last_word = DecodeFixed32(contents.data.data() + n - 5);
+ if (last_word > n - 5) {
+ return false;
+ }
+
+ *data = contents.data.data();
+ *offset = (*data) + last_word;
+ *num = (n - 5 - last_word) / 4;
+ *base_lg = contents.data[n - 1];
+
+ return true;
+}
+
+bool BlockBasedFilterBlockReader::MayMatch(
+ const Slice& entry, uint64_t block_offset, bool no_io,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context) const {
+ CachableEntry<BlockContents> filter_block;
+
+ const Status s =
+ GetOrReadFilterBlock(no_io, get_context, lookup_context, &filter_block);
+ if (!s.ok()) {
+ return true;
+ }
+
+ assert(filter_block.GetValue());
+
+ const char* data = nullptr;
+ const char* offset = nullptr;
+ size_t num = 0;
+ size_t base_lg = 0;
+ if (!ParseFieldsFromBlock(*filter_block.GetValue(), &data, &offset, &num,
+ &base_lg)) {
+ return true; // Errors are treated as potential matches
+ }
+
+ const uint64_t index = block_offset >> base_lg;
+ if (index < num) {
+ const uint32_t start = DecodeFixed32(offset + index * 4);
+ const uint32_t limit = DecodeFixed32(offset + index * 4 + 4);
+ if (start <= limit && limit <= (uint32_t)(offset - data)) {
+ const Slice filter = Slice(data + start, limit - start);
+
+ assert(table());
+ assert(table()->get_rep());
+ const FilterPolicy* const policy = table()->get_rep()->filter_policy;
+
+ const bool may_match = policy->KeyMayMatch(entry, filter);
+ if (may_match) {
+ PERF_COUNTER_ADD(bloom_sst_hit_count, 1);
+ return true;
+ } else {
+ PERF_COUNTER_ADD(bloom_sst_miss_count, 1);
+ return false;
+ }
+ } else if (start == limit) {
+ // Empty filters do not match any entries
+ return false;
+ }
+ }
+ return true; // Errors are treated as potential matches
+}
+
+size_t BlockBasedFilterBlockReader::ApproximateMemoryUsage() const {
+ size_t usage = ApproximateFilterBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<BlockBasedFilterBlockReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return usage;
+}
+
+std::string BlockBasedFilterBlockReader::ToString() const {
+ CachableEntry<BlockContents> filter_block;
+
+ const Status s =
+ GetOrReadFilterBlock(false /* no_io */, nullptr /* get_context */,
+ nullptr /* lookup_context */, &filter_block);
+ if (!s.ok()) {
+ return std::string("Unable to retrieve filter block");
+ }
+
+ assert(filter_block.GetValue());
+
+ const char* data = nullptr;
+ const char* offset = nullptr;
+ size_t num = 0;
+ size_t base_lg = 0;
+ if (!ParseFieldsFromBlock(*filter_block.GetValue(), &data, &offset, &num,
+ &base_lg)) {
+ return std::string("Error parsing filter block");
+ }
+
+ std::string result;
+ result.reserve(1024);
+
+ std::string s_bo("Block offset"), s_hd("Hex dump"), s_fb("# filter blocks");
+ AppendItem(&result, s_fb, ROCKSDB_NAMESPACE::ToString(num));
+ AppendItem(&result, s_bo, s_hd);
+
+ for (size_t index = 0; index < num; index++) {
+ uint32_t start = DecodeFixed32(offset + index * 4);
+ uint32_t limit = DecodeFixed32(offset + index * 4 + 4);
+
+ if (start != limit) {
+ result.append(" filter block # " +
+ ROCKSDB_NAMESPACE::ToString(index + 1) + "\n");
+ Slice filter = Slice(data + start, limit - start);
+ AppendItem(&result, start, filter.ToString(true));
+ }
+ }
+ return result;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.h b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.h
new file mode 100644
index 00000000..01c98a70
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A filter block is stored near the end of a Table file. It contains
+// filters (e.g., bloom filters) for all data blocks in the table combined
+// into a single filter block.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "table/block_based/filter_block_reader_common.h"
+#include "table/format.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A BlockBasedFilterBlockBuilder is used to construct all of the filters for a
+// particular Table. It generates a single string which is stored as
+// a special block in the Table.
+//
+// The sequence of calls to BlockBasedFilterBlockBuilder must match the regexp:
+// (StartBlock Add*)* Finish
+class BlockBasedFilterBlockBuilder : public FilterBlockBuilder {
+ public:
+ BlockBasedFilterBlockBuilder(const SliceTransform* prefix_extractor,
+ const BlockBasedTableOptions& table_opt);
+ // No copying allowed
+ BlockBasedFilterBlockBuilder(const BlockBasedFilterBlockBuilder&) = delete;
+ void operator=(const BlockBasedFilterBlockBuilder&) = delete;
+
+ virtual bool IsBlockBased() override { return true; }
+ virtual void StartBlock(uint64_t block_offset) override;
+ virtual void Add(const Slice& key) override;
+ virtual size_t NumAdded() const override { return num_added_; }
+ virtual Slice Finish(const BlockHandle& tmp, Status* status) override;
+ using FilterBlockBuilder::Finish;
+
+ private:
+ void AddKey(const Slice& key);
+ void AddPrefix(const Slice& key);
+ void GenerateFilter();
+
+ // important: all of these might point to invalid addresses
+ // at the time of destruction of this filter block. destructor
+ // should NOT dereference them.
+ const FilterPolicy* policy_;
+ const SliceTransform* prefix_extractor_;
+ bool whole_key_filtering_;
+
+ size_t prev_prefix_start_; // the position of the last appended prefix
+ // to "entries_".
+ size_t prev_prefix_size_; // the length of the last appended prefix to
+ // "entries_".
+ std::string entries_; // Flattened entry contents
+ std::vector<size_t> start_; // Starting index in entries_ of each entry
+ std::string result_; // Filter data computed so far
+ std::vector<Slice> tmp_entries_; // policy_->CreateFilter() argument
+ std::vector<uint32_t> filter_offsets_;
+ size_t num_added_; // Number of keys added
+};
+
+// A FilterBlockReader is used to parse filter from SST table.
+// KeyMayMatch and PrefixMayMatch would trigger filter checking
+class BlockBasedFilterBlockReader
+ : public FilterBlockReaderCommon<BlockContents> {
+ public:
+ BlockBasedFilterBlockReader(const BlockBasedTable* t,
+ CachableEntry<BlockContents>&& filter_block);
+ // No copying allowed
+ BlockBasedFilterBlockReader(const BlockBasedFilterBlockReader&) = delete;
+ void operator=(const BlockBasedFilterBlockReader&) = delete;
+
+ static std::unique_ptr<FilterBlockReader> Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context);
+
+ bool IsBlockBased() override { return true; }
+
+ bool KeyMayMatch(const Slice& key, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+ bool PrefixMayMatch(const Slice& prefix,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+ size_t ApproximateMemoryUsage() const override;
+
+ // convert this object to a human readable form
+ std::string ToString() const override;
+
+ private:
+ static bool ParseFieldsFromBlock(const BlockContents& contents,
+ const char** data, const char** offset,
+ size_t* num, size_t* base_lg);
+
+ bool MayMatch(const Slice& entry, uint64_t block_offset, bool no_io,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block_test.cc b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block_test.cc
new file mode 100644
index 00000000..283d6a9a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_filter_block_test.cc
@@ -0,0 +1,434 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/block_based_filter_block.h"
+#include "rocksdb/filter_policy.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/mock_block_based_table.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/coding.h"
+#include "util/hash.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// For testing: emit an array with one hash value per key
+class TestHashFilter : public FilterPolicy {
+ public:
+ const char* Name() const override { return "TestHashFilter"; }
+
+ void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
+ for (int i = 0; i < n; i++) {
+ uint32_t h = Hash(keys[i].data(), keys[i].size(), 1);
+ PutFixed32(dst, h);
+ }
+ }
+
+ bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
+ uint32_t h = Hash(key.data(), key.size(), 1);
+ for (unsigned int i = 0; i + 4 <= filter.size(); i += 4) {
+ if (h == DecodeFixed32(filter.data() + i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+class MockBlockBasedTable : public BlockBasedTable {
+ public:
+ explicit MockBlockBasedTable(Rep* rep)
+ : BlockBasedTable(rep, nullptr /* block_cache_tracer */) {}
+};
+
+class FilterBlockTest : public mock::MockBlockBasedTableTester,
+ public testing::Test {
+ public:
+ FilterBlockTest() : mock::MockBlockBasedTableTester(new TestHashFilter) {}
+};
+
+TEST_F(FilterBlockTest, EmptyBuilder) {
+ BlockBasedFilterBlockBuilder builder(nullptr, table_options_);
+ Slice slice(builder.Finish());
+ ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(slice));
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader.KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+TEST_F(FilterBlockTest, SingleChunk) {
+ BlockBasedFilterBlockBuilder builder(nullptr, table_options_);
+ ASSERT_EQ(0, builder.NumAdded());
+ builder.StartBlock(100);
+ builder.Add("foo");
+ builder.Add("bar");
+ builder.Add("box");
+ builder.StartBlock(200);
+ builder.Add("box");
+ builder.StartBlock(300);
+ builder.Add("hello");
+ ASSERT_EQ(5, builder.NumAdded());
+ Slice slice(builder.Finish());
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "missing", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "other", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+TEST_F(FilterBlockTest, MultiChunk) {
+ BlockBasedFilterBlockBuilder builder(nullptr, table_options_);
+
+ // First filter
+ builder.StartBlock(0);
+ builder.Add("foo");
+ builder.StartBlock(2000);
+ builder.Add("bar");
+
+ // Second filter
+ builder.StartBlock(3100);
+ builder.Add("box");
+
+ // Third filter is empty
+
+ // Last filter
+ builder.StartBlock(9000);
+ builder.Add("box");
+ builder.Add("hello");
+
+ Slice slice(builder.Finish());
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
+
+ // Check first filter
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/2000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check second filter
+ ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check third filter (empty)
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check last filter
+ ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+// Test for block based filter block
+// use new interface in FilterPolicy to create filter builder/reader
+class BlockBasedFilterBlockTest : public mock::MockBlockBasedTableTester,
+ public testing::Test {
+ public:
+ BlockBasedFilterBlockTest()
+ : mock::MockBlockBasedTableTester(NewBloomFilterPolicy(10, true)) {}
+};
+
+TEST_F(BlockBasedFilterBlockTest, BlockBasedEmptyBuilder) {
+ FilterBlockBuilder* builder =
+ new BlockBasedFilterBlockBuilder(nullptr, table_options_);
+ Slice slice(builder->Finish());
+ ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(slice));
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ FilterBlockReader* reader =
+ new BlockBasedFilterBlockReader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/10000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ delete builder;
+ delete reader;
+}
+
+TEST_F(BlockBasedFilterBlockTest, BlockBasedSingleChunk) {
+ FilterBlockBuilder* builder =
+ new BlockBasedFilterBlockBuilder(nullptr, table_options_);
+ builder->StartBlock(100);
+ builder->Add("foo");
+ builder->Add("bar");
+ builder->Add("box");
+ builder->StartBlock(200);
+ builder->Add("box");
+ builder->StartBlock(300);
+ builder->Add("hello");
+ Slice slice(builder->Finish());
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ FilterBlockReader* reader =
+ new BlockBasedFilterBlockReader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "missing", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "other", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ delete builder;
+ delete reader;
+}
+
+TEST_F(BlockBasedFilterBlockTest, BlockBasedMultiChunk) {
+ FilterBlockBuilder* builder =
+ new BlockBasedFilterBlockBuilder(nullptr, table_options_);
+
+ // First filter
+ builder->StartBlock(0);
+ builder->Add("foo");
+ builder->StartBlock(2000);
+ builder->Add("bar");
+
+ // Second filter
+ builder->StartBlock(3100);
+ builder->Add("box");
+
+ // Third filter is empty
+
+ // Last filter
+ builder->StartBlock(9000);
+ builder->Add("box");
+ builder->Add("hello");
+
+ Slice slice(builder->Finish());
+
+ CachableEntry<BlockContents> block(
+ new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+
+ FilterBlockReader* reader =
+ new BlockBasedFilterBlockReader(table_.get(), std::move(block));
+
+ // Check first filter
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/2000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check second filter
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check third filter (empty)
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ // Check last filter
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "box", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader->KeyMayMatch(
+ "hello", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "foo", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader->KeyMayMatch(
+ "bar", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+
+ delete builder;
+ delete reader;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc b/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc
new file mode 100644
index 00000000..2003008f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc
@@ -0,0 +1,1217 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/block_based_table_builder.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "db/dbformat.h"
+#include "index_builder.h"
+
+#include "rocksdb/cache.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/flush_block_policy.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/table.h"
+
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_filter_block.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_block.h"
+#include "table/block_based/filter_policy_internal.h"
+#include "table/block_based/full_filter_block.h"
+#include "table/block_based/partitioned_filter_block.h"
+#include "table/format.h"
+#include "table/table_builder.h"
+
+#include "memory/memory_allocator.h"
+#include "util/coding.h"
+#include "util/compression.h"
+#include "util/crc32c.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+#include "util/xxhash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const std::string kHashIndexPrefixesBlock;
+extern const std::string kHashIndexPrefixesMetadataBlock;
+
+typedef BlockBasedTableOptions::IndexType IndexType;
+
+// Without anonymous namespace here, we fail the warning -Wmissing-prototypes
+namespace {
+
+// Create a filter block builder based on its type.
+FilterBlockBuilder* CreateFilterBlockBuilder(
+ const ImmutableCFOptions& /*opt*/, const MutableCFOptions& mopt,
+ const FilterBuildingContext& context,
+ const bool use_delta_encoding_for_index_values,
+ PartitionedIndexBuilder* const p_index_builder) {
+ const BlockBasedTableOptions& table_opt = context.table_options;
+ if (table_opt.filter_policy == nullptr) return nullptr;
+
+ FilterBitsBuilder* filter_bits_builder =
+ BloomFilterPolicy::GetBuilderFromContext(context);
+ if (filter_bits_builder == nullptr) {
+ return new BlockBasedFilterBlockBuilder(mopt.prefix_extractor.get(),
+ table_opt);
+ } else {
+ if (table_opt.partition_filters) {
+ assert(p_index_builder != nullptr);
+ // Since after partition cut request from filter builder it takes time
+ // until index builder actully cuts the partition, we take the lower bound
+ // as partition size.
+ assert(table_opt.block_size_deviation <= 100);
+ auto partition_size =
+ static_cast<uint32_t>(((table_opt.metadata_block_size *
+ (100 - table_opt.block_size_deviation)) +
+ 99) /
+ 100);
+ partition_size = std::max(partition_size, static_cast<uint32_t>(1));
+ return new PartitionedFilterBlockBuilder(
+ mopt.prefix_extractor.get(), table_opt.whole_key_filtering,
+ filter_bits_builder, table_opt.index_block_restart_interval,
+ use_delta_encoding_for_index_values, p_index_builder, partition_size);
+ } else {
+ return new FullFilterBlockBuilder(mopt.prefix_extractor.get(),
+ table_opt.whole_key_filtering,
+ filter_bits_builder);
+ }
+ }
+}
+
+bool GoodCompressionRatio(size_t compressed_size, size_t raw_size) {
+ // Check to see if compressed less than 12.5%
+ return compressed_size < raw_size - (raw_size / 8u);
+}
+
+bool CompressBlockInternal(const Slice& raw,
+ const CompressionInfo& compression_info,
+ uint32_t format_version,
+ std::string* compressed_output) {
+ // Will return compressed block contents if (1) the compression method is
+ // supported in this platform and (2) the compression rate is "good enough".
+ switch (compression_info.type()) {
+ case kSnappyCompression:
+ return Snappy_Compress(compression_info, raw.data(), raw.size(),
+ compressed_output);
+ case kZlibCompression:
+ return Zlib_Compress(
+ compression_info,
+ GetCompressFormatForVersion(kZlibCompression, format_version),
+ raw.data(), raw.size(), compressed_output);
+ case kBZip2Compression:
+ return BZip2_Compress(
+ compression_info,
+ GetCompressFormatForVersion(kBZip2Compression, format_version),
+ raw.data(), raw.size(), compressed_output);
+ case kLZ4Compression:
+ return LZ4_Compress(
+ compression_info,
+ GetCompressFormatForVersion(kLZ4Compression, format_version),
+ raw.data(), raw.size(), compressed_output);
+ case kLZ4HCCompression:
+ return LZ4HC_Compress(
+ compression_info,
+ GetCompressFormatForVersion(kLZ4HCCompression, format_version),
+ raw.data(), raw.size(), compressed_output);
+ case kXpressCompression:
+ return XPRESS_Compress(raw.data(), raw.size(), compressed_output);
+ case kZSTD:
+ case kZSTDNotFinalCompression:
+ return ZSTD_Compress(compression_info, raw.data(), raw.size(),
+ compressed_output);
+ default:
+ // Do not recognize this compression type
+ return false;
+ }
+}
+
+} // namespace
+
+// format_version is the block format as defined in include/rocksdb/table.h
+Slice CompressBlock(const Slice& raw, const CompressionInfo& info,
+ CompressionType* type, uint32_t format_version,
+ bool do_sample, std::string* compressed_output,
+ std::string* sampled_output_fast,
+ std::string* sampled_output_slow) {
+ *type = info.type();
+
+ if (info.type() == kNoCompression && !info.SampleForCompression()) {
+ return raw;
+ }
+
+ // If requested, we sample one in every N block with a
+ // fast and slow compression algorithm and report the stats.
+ // The users can use these stats to decide if it is worthwhile
+ // enabling compression and they also get a hint about which
+ // compression algorithm wil be beneficial.
+ if (do_sample && info.SampleForCompression() &&
+ Random::GetTLSInstance()->OneIn((int)info.SampleForCompression()) &&
+ sampled_output_fast && sampled_output_slow) {
+ // Sampling with a fast compression algorithm
+ if (LZ4_Supported() || Snappy_Supported()) {
+ CompressionType c =
+ LZ4_Supported() ? kLZ4Compression : kSnappyCompression;
+ CompressionContext context(c);
+ CompressionOptions options;
+ CompressionInfo info_tmp(options, context,
+ CompressionDict::GetEmptyDict(), c,
+ info.SampleForCompression());
+
+ CompressBlockInternal(raw, info_tmp, format_version, sampled_output_fast);
+ }
+
+ // Sampling with a slow but high-compression algorithm
+ if (ZSTD_Supported() || Zlib_Supported()) {
+ CompressionType c = ZSTD_Supported() ? kZSTD : kZlibCompression;
+ CompressionContext context(c);
+ CompressionOptions options;
+ CompressionInfo info_tmp(options, context,
+ CompressionDict::GetEmptyDict(), c,
+ info.SampleForCompression());
+ CompressBlockInternal(raw, info_tmp, format_version, sampled_output_slow);
+ }
+ }
+
+ // Actually compress the data
+ if (*type != kNoCompression) {
+ if (CompressBlockInternal(raw, info, format_version, compressed_output) &&
+ GoodCompressionRatio(compressed_output->size(), raw.size())) {
+ return *compressed_output;
+ }
+ }
+
+ // Compression method is not supported, or not good
+ // compression ratio, so just fall back to uncompressed form.
+ *type = kNoCompression;
+ return raw;
+}
+
+// kBlockBasedTableMagicNumber was picked by running
+// echo rocksdb.table.block_based | sha1sum
+// and taking the leading 64 bits.
+// Please note that kBlockBasedTableMagicNumber may also be accessed by other
+// .cc files
+// for that reason we declare it extern in the header but to get the space
+// allocated
+// it must be not extern in one place.
+const uint64_t kBlockBasedTableMagicNumber = 0x88e241b785f4cff7ull;
+// We also support reading and writing legacy block based table format (for
+// backwards compatibility)
+const uint64_t kLegacyBlockBasedTableMagicNumber = 0xdb4775248b80fb57ull;
+
+// A collector that collects properties of interest to block-based table.
+// For now this class looks heavy-weight since we only write one additional
+// property.
+// But in the foreseeable future, we will add more and more properties that are
+// specific to block-based table.
+class BlockBasedTableBuilder::BlockBasedTablePropertiesCollector
+ : public IntTblPropCollector {
+ public:
+ explicit BlockBasedTablePropertiesCollector(
+ BlockBasedTableOptions::IndexType index_type, bool whole_key_filtering,
+ bool prefix_filtering)
+ : index_type_(index_type),
+ whole_key_filtering_(whole_key_filtering),
+ prefix_filtering_(prefix_filtering) {}
+
+ Status InternalAdd(const Slice& /*key*/, const Slice& /*value*/,
+ uint64_t /*file_size*/) override {
+ // Intentionally left blank. Have no interest in collecting stats for
+ // individual key/value pairs.
+ return Status::OK();
+ }
+
+ virtual void BlockAdd(uint64_t /* blockRawBytes */,
+ uint64_t /* blockCompressedBytesFast */,
+ uint64_t /* blockCompressedBytesSlow */) override {
+ // Intentionally left blank. No interest in collecting stats for
+ // blocks.
+ return;
+ }
+
+ Status Finish(UserCollectedProperties* properties) override {
+ std::string val;
+ PutFixed32(&val, static_cast<uint32_t>(index_type_));
+ properties->insert({BlockBasedTablePropertyNames::kIndexType, val});
+ properties->insert({BlockBasedTablePropertyNames::kWholeKeyFiltering,
+ whole_key_filtering_ ? kPropTrue : kPropFalse});
+ properties->insert({BlockBasedTablePropertyNames::kPrefixFiltering,
+ prefix_filtering_ ? kPropTrue : kPropFalse});
+ return Status::OK();
+ }
+
+ // The name of the properties collector can be used for debugging purpose.
+ const char* Name() const override {
+ return "BlockBasedTablePropertiesCollector";
+ }
+
+ UserCollectedProperties GetReadableProperties() const override {
+ // Intentionally left blank.
+ return UserCollectedProperties();
+ }
+
+ private:
+ BlockBasedTableOptions::IndexType index_type_;
+ bool whole_key_filtering_;
+ bool prefix_filtering_;
+};
+
+struct BlockBasedTableBuilder::Rep {
+ const ImmutableCFOptions ioptions;
+ const MutableCFOptions moptions;
+ const BlockBasedTableOptions table_options;
+ const InternalKeyComparator& internal_comparator;
+ WritableFileWriter* file;
+ uint64_t offset = 0;
+ Status status;
+ size_t alignment;
+ BlockBuilder data_block;
+ // Buffers uncompressed data blocks and keys to replay later. Needed when
+ // compression dictionary is enabled so we can finalize the dictionary before
+ // compressing any data blocks.
+ // TODO(ajkr): ideally we don't buffer all keys and all uncompressed data
+ // blocks as it's redundant, but it's easier to implement for now.
+ std::vector<std::pair<std::string, std::vector<std::string>>>
+ data_block_and_keys_buffers;
+ BlockBuilder range_del_block;
+
+ InternalKeySliceTransform internal_prefix_transform;
+ std::unique_ptr<IndexBuilder> index_builder;
+ PartitionedIndexBuilder* p_index_builder_ = nullptr;
+
+ std::string last_key;
+ CompressionType compression_type;
+ uint64_t sample_for_compression;
+ CompressionOptions compression_opts;
+ std::unique_ptr<CompressionDict> compression_dict;
+ CompressionContext compression_ctx;
+ std::unique_ptr<UncompressionContext> verify_ctx;
+ std::unique_ptr<UncompressionDict> verify_dict;
+
+ size_t data_begin_offset = 0;
+
+ TableProperties props;
+
+ // States of the builder.
+ //
+ // - `kBuffered`: This is the initial state where zero or more data blocks are
+ // accumulated uncompressed in-memory. From this state, call
+ // `EnterUnbuffered()` to finalize the compression dictionary if enabled,
+ // compress/write out any buffered blocks, and proceed to the `kUnbuffered`
+ // state.
+ //
+ // - `kUnbuffered`: This is the state when compression dictionary is finalized
+ // either because it wasn't enabled in the first place or it's been created
+ // from sampling previously buffered data. In this state, blocks are simply
+ // compressed/written out as they fill up. From this state, call `Finish()`
+ // to complete the file (write meta-blocks, etc.), or `Abandon()` to delete
+ // the partially created file.
+ //
+ // - `kClosed`: This indicates either `Finish()` or `Abandon()` has been
+ // called, so the table builder is no longer usable. We must be in this
+ // state by the time the destructor runs.
+ enum class State {
+ kBuffered,
+ kUnbuffered,
+ kClosed,
+ };
+ State state;
+
+ const bool use_delta_encoding_for_index_values;
+ std::unique_ptr<FilterBlockBuilder> filter_builder;
+ char compressed_cache_key_prefix[BlockBasedTable::kMaxCacheKeyPrefixSize];
+ size_t compressed_cache_key_prefix_size;
+
+ BlockHandle pending_handle; // Handle to add to index block
+
+ std::string compressed_output;
+ std::unique_ptr<FlushBlockPolicy> flush_block_policy;
+ int level_at_creation;
+ uint32_t column_family_id;
+ const std::string& column_family_name;
+ uint64_t creation_time = 0;
+ uint64_t oldest_key_time = 0;
+ const uint64_t target_file_size;
+ uint64_t file_creation_time = 0;
+
+ std::vector<std::unique_ptr<IntTblPropCollector>> table_properties_collectors;
+
+ Rep(const ImmutableCFOptions& _ioptions, const MutableCFOptions& _moptions,
+ const BlockBasedTableOptions& table_opt,
+ const InternalKeyComparator& icomparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t _column_family_id, WritableFileWriter* f,
+ const CompressionType _compression_type,
+ const uint64_t _sample_for_compression,
+ const CompressionOptions& _compression_opts, const bool skip_filters,
+ const int _level_at_creation, const std::string& _column_family_name,
+ const uint64_t _creation_time, const uint64_t _oldest_key_time,
+ const uint64_t _target_file_size, const uint64_t _file_creation_time)
+ : ioptions(_ioptions),
+ moptions(_moptions),
+ table_options(table_opt),
+ internal_comparator(icomparator),
+ file(f),
+ alignment(table_options.block_align
+ ? std::min(table_options.block_size, kDefaultPageSize)
+ : 0),
+ data_block(table_options.block_restart_interval,
+ table_options.use_delta_encoding,
+ false /* use_value_delta_encoding */,
+ icomparator.user_comparator()
+ ->CanKeysWithDifferentByteContentsBeEqual()
+ ? BlockBasedTableOptions::kDataBlockBinarySearch
+ : table_options.data_block_index_type,
+ table_options.data_block_hash_table_util_ratio),
+ range_del_block(1 /* block_restart_interval */),
+ internal_prefix_transform(_moptions.prefix_extractor.get()),
+ compression_type(_compression_type),
+ sample_for_compression(_sample_for_compression),
+ compression_opts(_compression_opts),
+ compression_dict(),
+ compression_ctx(_compression_type),
+ verify_dict(),
+ state((_compression_opts.max_dict_bytes > 0) ? State::kBuffered
+ : State::kUnbuffered),
+ use_delta_encoding_for_index_values(table_opt.format_version >= 4 &&
+ !table_opt.block_align),
+ compressed_cache_key_prefix_size(0),
+ flush_block_policy(
+ table_options.flush_block_policy_factory->NewFlushBlockPolicy(
+ table_options, data_block)),
+ level_at_creation(_level_at_creation),
+ column_family_id(_column_family_id),
+ column_family_name(_column_family_name),
+ creation_time(_creation_time),
+ oldest_key_time(_oldest_key_time),
+ target_file_size(_target_file_size),
+ file_creation_time(_file_creation_time) {
+ if (table_options.index_type ==
+ BlockBasedTableOptions::kTwoLevelIndexSearch) {
+ p_index_builder_ = PartitionedIndexBuilder::CreateIndexBuilder(
+ &internal_comparator, use_delta_encoding_for_index_values,
+ table_options);
+ index_builder.reset(p_index_builder_);
+ } else {
+ index_builder.reset(IndexBuilder::CreateIndexBuilder(
+ table_options.index_type, &internal_comparator,
+ &this->internal_prefix_transform, use_delta_encoding_for_index_values,
+ table_options));
+ }
+ if (skip_filters) {
+ filter_builder = nullptr;
+ } else {
+ FilterBuildingContext context(table_options);
+ context.column_family_name = column_family_name;
+ context.compaction_style = ioptions.compaction_style;
+ context.level_at_creation = level_at_creation;
+ context.info_log = ioptions.info_log;
+ filter_builder.reset(CreateFilterBlockBuilder(
+ ioptions, moptions, context, use_delta_encoding_for_index_values,
+ p_index_builder_));
+ }
+
+ for (auto& collector_factories : *int_tbl_prop_collector_factories) {
+ table_properties_collectors.emplace_back(
+ collector_factories->CreateIntTblPropCollector(column_family_id));
+ }
+ table_properties_collectors.emplace_back(
+ new BlockBasedTablePropertiesCollector(
+ table_options.index_type, table_options.whole_key_filtering,
+ _moptions.prefix_extractor != nullptr));
+ if (table_options.verify_compression) {
+ verify_ctx.reset(new UncompressionContext(UncompressionContext::NoCache(),
+ compression_type));
+ }
+ }
+
+ Rep(const Rep&) = delete;
+ Rep& operator=(const Rep&) = delete;
+
+ ~Rep() {}
+};
+
+BlockBasedTableBuilder::BlockBasedTableBuilder(
+ const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
+ const BlockBasedTableOptions& table_options,
+ const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, WritableFileWriter* file,
+ const CompressionType compression_type,
+ const uint64_t sample_for_compression,
+ const CompressionOptions& compression_opts, const bool skip_filters,
+ const std::string& column_family_name, const int level_at_creation,
+ const uint64_t creation_time, const uint64_t oldest_key_time,
+ const uint64_t target_file_size, const uint64_t file_creation_time) {
+ BlockBasedTableOptions sanitized_table_options(table_options);
+ if (sanitized_table_options.format_version == 0 &&
+ sanitized_table_options.checksum != kCRC32c) {
+ ROCKS_LOG_WARN(
+ ioptions.info_log,
+ "Silently converting format_version to 1 because checksum is "
+ "non-default");
+ // silently convert format_version to 1 to keep consistent with current
+ // behavior
+ sanitized_table_options.format_version = 1;
+ }
+
+ rep_ = new Rep(ioptions, moptions, sanitized_table_options,
+ internal_comparator, int_tbl_prop_collector_factories,
+ column_family_id, file, compression_type,
+ sample_for_compression, compression_opts, skip_filters,
+ level_at_creation, column_family_name, creation_time,
+ oldest_key_time, target_file_size, file_creation_time);
+
+ if (rep_->filter_builder != nullptr) {
+ rep_->filter_builder->StartBlock(0);
+ }
+ if (table_options.block_cache_compressed.get() != nullptr) {
+ BlockBasedTable::GenerateCachePrefix(
+ table_options.block_cache_compressed.get(), file->writable_file(),
+ &rep_->compressed_cache_key_prefix[0],
+ &rep_->compressed_cache_key_prefix_size);
+ }
+}
+
+BlockBasedTableBuilder::~BlockBasedTableBuilder() {
+ // Catch errors where caller forgot to call Finish()
+ assert(rep_->state == Rep::State::kClosed);
+ delete rep_;
+}
+
+void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) {
+ Rep* r = rep_;
+ assert(rep_->state != Rep::State::kClosed);
+ if (!ok()) return;
+ ValueType value_type = ExtractValueType(key);
+ if (IsValueType(value_type)) {
+#ifndef NDEBUG
+ if (r->props.num_entries > r->props.num_range_deletions) {
+ assert(r->internal_comparator.Compare(key, Slice(r->last_key)) > 0);
+ }
+#endif // NDEBUG
+
+ auto should_flush = r->flush_block_policy->Update(key, value);
+ if (should_flush) {
+ assert(!r->data_block.empty());
+ Flush();
+
+ if (r->state == Rep::State::kBuffered &&
+ r->data_begin_offset > r->target_file_size) {
+ EnterUnbuffered();
+ }
+
+ // Add item to index block.
+ // We do not emit the index entry for a block until we have seen the
+ // first key for the next data block. This allows us to use shorter
+ // keys in the index block. For example, consider a block boundary
+ // between the keys "the quick brown fox" and "the who". We can use
+ // "the r" as the key for the index block entry since it is >= all
+ // entries in the first block and < all entries in subsequent
+ // blocks.
+ if (ok() && r->state == Rep::State::kUnbuffered) {
+ r->index_builder->AddIndexEntry(&r->last_key, &key, r->pending_handle);
+ }
+ }
+
+ // Note: PartitionedFilterBlockBuilder requires key being added to filter
+ // builder after being added to index builder.
+ if (r->state == Rep::State::kUnbuffered && r->filter_builder != nullptr) {
+ size_t ts_sz = r->internal_comparator.user_comparator()->timestamp_size();
+ r->filter_builder->Add(ExtractUserKeyAndStripTimestamp(key, ts_sz));
+ }
+
+ r->last_key.assign(key.data(), key.size());
+ r->data_block.Add(key, value);
+ if (r->state == Rep::State::kBuffered) {
+ // Buffer keys to be replayed during `Finish()` once compression
+ // dictionary has been finalized.
+ if (r->data_block_and_keys_buffers.empty() || should_flush) {
+ r->data_block_and_keys_buffers.emplace_back();
+ }
+ r->data_block_and_keys_buffers.back().second.emplace_back(key.ToString());
+ } else {
+ r->index_builder->OnKeyAdded(key);
+ }
+ NotifyCollectTableCollectorsOnAdd(key, value, r->offset,
+ r->table_properties_collectors,
+ r->ioptions.info_log);
+
+ } else if (value_type == kTypeRangeDeletion) {
+ r->range_del_block.Add(key, value);
+ NotifyCollectTableCollectorsOnAdd(key, value, r->offset,
+ r->table_properties_collectors,
+ r->ioptions.info_log);
+ } else {
+ assert(false);
+ }
+
+ r->props.num_entries++;
+ r->props.raw_key_size += key.size();
+ r->props.raw_value_size += value.size();
+ if (value_type == kTypeDeletion || value_type == kTypeSingleDeletion) {
+ r->props.num_deletions++;
+ } else if (value_type == kTypeRangeDeletion) {
+ r->props.num_deletions++;
+ r->props.num_range_deletions++;
+ } else if (value_type == kTypeMerge) {
+ r->props.num_merge_operands++;
+ }
+}
+
+void BlockBasedTableBuilder::Flush() {
+ Rep* r = rep_;
+ assert(rep_->state != Rep::State::kClosed);
+ if (!ok()) return;
+ if (r->data_block.empty()) return;
+ WriteBlock(&r->data_block, &r->pending_handle, true /* is_data_block */);
+}
+
+void BlockBasedTableBuilder::WriteBlock(BlockBuilder* block,
+ BlockHandle* handle,
+ bool is_data_block) {
+ WriteBlock(block->Finish(), handle, is_data_block);
+ block->Reset();
+}
+
+void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents,
+ BlockHandle* handle,
+ bool is_data_block) {
+ // File format contains a sequence of blocks where each block has:
+ // block_data: uint8[n]
+ // type: uint8
+ // crc: uint32
+ assert(ok());
+ Rep* r = rep_;
+
+ auto type = r->compression_type;
+ uint64_t sample_for_compression = r->sample_for_compression;
+ Slice block_contents;
+ bool abort_compression = false;
+
+ StopWatchNano timer(
+ r->ioptions.env,
+ ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics));
+
+ if (r->state == Rep::State::kBuffered) {
+ assert(is_data_block);
+ assert(!r->data_block_and_keys_buffers.empty());
+ r->data_block_and_keys_buffers.back().first = raw_block_contents.ToString();
+ r->data_begin_offset += r->data_block_and_keys_buffers.back().first.size();
+ return;
+ }
+
+ if (raw_block_contents.size() < kCompressionSizeLimit) {
+ const CompressionDict* compression_dict;
+ if (!is_data_block || r->compression_dict == nullptr) {
+ compression_dict = &CompressionDict::GetEmptyDict();
+ } else {
+ compression_dict = r->compression_dict.get();
+ }
+ assert(compression_dict != nullptr);
+ CompressionInfo compression_info(r->compression_opts, r->compression_ctx,
+ *compression_dict, type,
+ sample_for_compression);
+
+ std::string sampled_output_fast;
+ std::string sampled_output_slow;
+ block_contents = CompressBlock(
+ raw_block_contents, compression_info, &type,
+ r->table_options.format_version, is_data_block /* do_sample */,
+ &r->compressed_output, &sampled_output_fast, &sampled_output_slow);
+
+ // notify collectors on block add
+ NotifyCollectTableCollectorsOnBlockAdd(
+ r->table_properties_collectors, raw_block_contents.size(),
+ sampled_output_fast.size(), sampled_output_slow.size());
+
+ // Some of the compression algorithms are known to be unreliable. If
+ // the verify_compression flag is set then try to de-compress the
+ // compressed data and compare to the input.
+ if (type != kNoCompression && r->table_options.verify_compression) {
+ // Retrieve the uncompressed contents into a new buffer
+ const UncompressionDict* verify_dict;
+ if (!is_data_block || r->verify_dict == nullptr) {
+ verify_dict = &UncompressionDict::GetEmptyDict();
+ } else {
+ verify_dict = r->verify_dict.get();
+ }
+ assert(verify_dict != nullptr);
+ BlockContents contents;
+ UncompressionInfo uncompression_info(*r->verify_ctx, *verify_dict,
+ r->compression_type);
+ Status stat = UncompressBlockContentsForCompressionType(
+ uncompression_info, block_contents.data(), block_contents.size(),
+ &contents, r->table_options.format_version, r->ioptions);
+
+ if (stat.ok()) {
+ bool compressed_ok = contents.data.compare(raw_block_contents) == 0;
+ if (!compressed_ok) {
+ // The result of the compression was invalid. abort.
+ abort_compression = true;
+ ROCKS_LOG_ERROR(r->ioptions.info_log,
+ "Decompressed block did not match raw block");
+ r->status =
+ Status::Corruption("Decompressed block did not match raw block");
+ }
+ } else {
+ // Decompression reported an error. abort.
+ r->status = Status::Corruption("Could not decompress");
+ abort_compression = true;
+ }
+ }
+ } else {
+ // Block is too big to be compressed.
+ abort_compression = true;
+ }
+
+ // Abort compression if the block is too big, or did not pass
+ // verification.
+ if (abort_compression) {
+ RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED);
+ type = kNoCompression;
+ block_contents = raw_block_contents;
+ } else if (type != kNoCompression) {
+ if (ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics)) {
+ RecordTimeToHistogram(r->ioptions.statistics, COMPRESSION_TIMES_NANOS,
+ timer.ElapsedNanos());
+ }
+ RecordInHistogram(r->ioptions.statistics, BYTES_COMPRESSED,
+ raw_block_contents.size());
+ RecordTick(r->ioptions.statistics, NUMBER_BLOCK_COMPRESSED);
+ } else if (type != r->compression_type) {
+ RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED);
+ }
+
+ WriteRawBlock(block_contents, type, handle, is_data_block);
+ r->compressed_output.clear();
+ if (is_data_block) {
+ if (r->filter_builder != nullptr) {
+ r->filter_builder->StartBlock(r->offset);
+ }
+ r->props.data_size = r->offset;
+ ++r->props.num_data_blocks;
+ }
+}
+
+void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents,
+ CompressionType type,
+ BlockHandle* handle,
+ bool is_data_block) {
+ Rep* r = rep_;
+ StopWatch sw(r->ioptions.env, r->ioptions.statistics, WRITE_RAW_BLOCK_MICROS);
+ handle->set_offset(r->offset);
+ handle->set_size(block_contents.size());
+ assert(r->status.ok());
+ r->status = r->file->Append(block_contents);
+ if (r->status.ok()) {
+ char trailer[kBlockTrailerSize];
+ trailer[0] = type;
+ char* trailer_without_type = trailer + 1;
+ switch (r->table_options.checksum) {
+ case kNoChecksum:
+ EncodeFixed32(trailer_without_type, 0);
+ break;
+ case kCRC32c: {
+ auto crc = crc32c::Value(block_contents.data(), block_contents.size());
+ crc = crc32c::Extend(crc, trailer, 1); // Extend to cover block type
+ EncodeFixed32(trailer_without_type, crc32c::Mask(crc));
+ break;
+ }
+ case kxxHash: {
+ XXH32_state_t* const state = XXH32_createState();
+ XXH32_reset(state, 0);
+ XXH32_update(state, block_contents.data(),
+ static_cast<uint32_t>(block_contents.size()));
+ XXH32_update(state, trailer, 1); // Extend to cover block type
+ EncodeFixed32(trailer_without_type, XXH32_digest(state));
+ XXH32_freeState(state);
+ break;
+ }
+ case kxxHash64: {
+ XXH64_state_t* const state = XXH64_createState();
+ XXH64_reset(state, 0);
+ XXH64_update(state, block_contents.data(),
+ static_cast<uint32_t>(block_contents.size()));
+ XXH64_update(state, trailer, 1); // Extend to cover block type
+ EncodeFixed32(
+ trailer_without_type,
+ static_cast<uint32_t>(XXH64_digest(state) & // lower 32 bits
+ uint64_t{0xffffffff}));
+ XXH64_freeState(state);
+ break;
+ }
+ }
+
+ assert(r->status.ok());
+ TEST_SYNC_POINT_CALLBACK(
+ "BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum",
+ static_cast<char*>(trailer));
+ r->status = r->file->Append(Slice(trailer, kBlockTrailerSize));
+ if (r->status.ok()) {
+ r->status = InsertBlockInCache(block_contents, type, handle);
+ }
+ if (r->status.ok()) {
+ r->offset += block_contents.size() + kBlockTrailerSize;
+ if (r->table_options.block_align && is_data_block) {
+ size_t pad_bytes =
+ (r->alignment - ((block_contents.size() + kBlockTrailerSize) &
+ (r->alignment - 1))) &
+ (r->alignment - 1);
+ r->status = r->file->Pad(pad_bytes);
+ if (r->status.ok()) {
+ r->offset += pad_bytes;
+ }
+ }
+ }
+ }
+}
+
+Status BlockBasedTableBuilder::status() const { return rep_->status; }
+
+static void DeleteCachedBlockContents(const Slice& /*key*/, void* value) {
+ BlockContents* bc = reinterpret_cast<BlockContents*>(value);
+ delete bc;
+}
+
+//
+// Make a copy of the block contents and insert into compressed block cache
+//
+Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents,
+ const CompressionType type,
+ const BlockHandle* handle) {
+ Rep* r = rep_;
+ Cache* block_cache_compressed = r->table_options.block_cache_compressed.get();
+
+ if (type != kNoCompression && block_cache_compressed != nullptr) {
+ size_t size = block_contents.size();
+
+ auto ubuf =
+ AllocateBlock(size + 1, block_cache_compressed->memory_allocator());
+ memcpy(ubuf.get(), block_contents.data(), size);
+ ubuf[size] = type;
+
+ BlockContents* block_contents_to_cache =
+ new BlockContents(std::move(ubuf), size);
+#ifndef NDEBUG
+ block_contents_to_cache->is_raw_block = true;
+#endif // NDEBUG
+
+ // make cache key by appending the file offset to the cache prefix id
+ char* end = EncodeVarint64(
+ r->compressed_cache_key_prefix + r->compressed_cache_key_prefix_size,
+ handle->offset());
+ Slice key(r->compressed_cache_key_prefix,
+ static_cast<size_t>(end - r->compressed_cache_key_prefix));
+
+ // Insert into compressed block cache.
+ block_cache_compressed->Insert(
+ key, block_contents_to_cache,
+ block_contents_to_cache->ApproximateMemoryUsage(),
+ &DeleteCachedBlockContents);
+
+ // Invalidate OS cache.
+ r->file->InvalidateCache(static_cast<size_t>(r->offset), size);
+ }
+ return Status::OK();
+}
+
+void BlockBasedTableBuilder::WriteFilterBlock(
+ MetaIndexBuilder* meta_index_builder) {
+ BlockHandle filter_block_handle;
+ bool empty_filter_block = (rep_->filter_builder == nullptr ||
+ rep_->filter_builder->NumAdded() == 0);
+ if (ok() && !empty_filter_block) {
+ Status s = Status::Incomplete();
+ while (ok() && s.IsIncomplete()) {
+ Slice filter_content =
+ rep_->filter_builder->Finish(filter_block_handle, &s);
+ assert(s.ok() || s.IsIncomplete());
+ rep_->props.filter_size += filter_content.size();
+ WriteRawBlock(filter_content, kNoCompression, &filter_block_handle);
+ }
+ }
+ if (ok() && !empty_filter_block) {
+ // Add mapping from "<filter_block_prefix>.Name" to location
+ // of filter data.
+ std::string key;
+ if (rep_->filter_builder->IsBlockBased()) {
+ key = BlockBasedTable::kFilterBlockPrefix;
+ } else {
+ key = rep_->table_options.partition_filters
+ ? BlockBasedTable::kPartitionedFilterBlockPrefix
+ : BlockBasedTable::kFullFilterBlockPrefix;
+ }
+ key.append(rep_->table_options.filter_policy->Name());
+ meta_index_builder->Add(key, filter_block_handle);
+ }
+}
+
+void BlockBasedTableBuilder::WriteIndexBlock(
+ MetaIndexBuilder* meta_index_builder, BlockHandle* index_block_handle) {
+ IndexBuilder::IndexBlocks index_blocks;
+ auto index_builder_status = rep_->index_builder->Finish(&index_blocks);
+ if (index_builder_status.IsIncomplete()) {
+ // We we have more than one index partition then meta_blocks are not
+ // supported for the index. Currently meta_blocks are used only by
+ // HashIndexBuilder which is not multi-partition.
+ assert(index_blocks.meta_blocks.empty());
+ } else if (ok() && !index_builder_status.ok()) {
+ rep_->status = index_builder_status;
+ }
+ if (ok()) {
+ for (const auto& item : index_blocks.meta_blocks) {
+ BlockHandle block_handle;
+ WriteBlock(item.second, &block_handle, false /* is_data_block */);
+ if (!ok()) {
+ break;
+ }
+ meta_index_builder->Add(item.first, block_handle);
+ }
+ }
+ if (ok()) {
+ if (rep_->table_options.enable_index_compression) {
+ WriteBlock(index_blocks.index_block_contents, index_block_handle, false);
+ } else {
+ WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
+ index_block_handle);
+ }
+ }
+ // If there are more index partitions, finish them and write them out
+ Status s = index_builder_status;
+ while (ok() && s.IsIncomplete()) {
+ s = rep_->index_builder->Finish(&index_blocks, *index_block_handle);
+ if (!s.ok() && !s.IsIncomplete()) {
+ rep_->status = s;
+ return;
+ }
+ if (rep_->table_options.enable_index_compression) {
+ WriteBlock(index_blocks.index_block_contents, index_block_handle, false);
+ } else {
+ WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
+ index_block_handle);
+ }
+ // The last index_block_handle will be for the partition index block
+ }
+}
+
+void BlockBasedTableBuilder::WritePropertiesBlock(
+ MetaIndexBuilder* meta_index_builder) {
+ BlockHandle properties_block_handle;
+ if (ok()) {
+ PropertyBlockBuilder property_block_builder;
+ rep_->props.column_family_id = rep_->column_family_id;
+ rep_->props.column_family_name = rep_->column_family_name;
+ rep_->props.filter_policy_name =
+ rep_->table_options.filter_policy != nullptr
+ ? rep_->table_options.filter_policy->Name()
+ : "";
+ rep_->props.index_size =
+ rep_->index_builder->IndexSize() + kBlockTrailerSize;
+ rep_->props.comparator_name = rep_->ioptions.user_comparator != nullptr
+ ? rep_->ioptions.user_comparator->Name()
+ : "nullptr";
+ rep_->props.merge_operator_name =
+ rep_->ioptions.merge_operator != nullptr
+ ? rep_->ioptions.merge_operator->Name()
+ : "nullptr";
+ rep_->props.compression_name =
+ CompressionTypeToString(rep_->compression_type);
+ rep_->props.compression_options =
+ CompressionOptionsToString(rep_->compression_opts);
+ rep_->props.prefix_extractor_name =
+ rep_->moptions.prefix_extractor != nullptr
+ ? rep_->moptions.prefix_extractor->Name()
+ : "nullptr";
+
+ std::string property_collectors_names = "[";
+ for (size_t i = 0;
+ i < rep_->ioptions.table_properties_collector_factories.size(); ++i) {
+ if (i != 0) {
+ property_collectors_names += ",";
+ }
+ property_collectors_names +=
+ rep_->ioptions.table_properties_collector_factories[i]->Name();
+ }
+ property_collectors_names += "]";
+ rep_->props.property_collectors_names = property_collectors_names;
+ if (rep_->table_options.index_type ==
+ BlockBasedTableOptions::kTwoLevelIndexSearch) {
+ assert(rep_->p_index_builder_ != nullptr);
+ rep_->props.index_partitions = rep_->p_index_builder_->NumPartitions();
+ rep_->props.top_level_index_size =
+ rep_->p_index_builder_->TopLevelIndexSize(rep_->offset);
+ }
+ rep_->props.index_key_is_user_key =
+ !rep_->index_builder->seperator_is_key_plus_seq();
+ rep_->props.index_value_is_delta_encoded =
+ rep_->use_delta_encoding_for_index_values;
+ rep_->props.creation_time = rep_->creation_time;
+ rep_->props.oldest_key_time = rep_->oldest_key_time;
+ rep_->props.file_creation_time = rep_->file_creation_time;
+
+ // Add basic properties
+ property_block_builder.AddTableProperty(rep_->props);
+
+ // Add use collected properties
+ NotifyCollectTableCollectorsOnFinish(rep_->table_properties_collectors,
+ rep_->ioptions.info_log,
+ &property_block_builder);
+
+ WriteRawBlock(property_block_builder.Finish(), kNoCompression,
+ &properties_block_handle);
+ }
+ if (ok()) {
+#ifndef NDEBUG
+ {
+ uint64_t props_block_offset = properties_block_handle.offset();
+ uint64_t props_block_size = properties_block_handle.size();
+ TEST_SYNC_POINT_CALLBACK(
+ "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset",
+ &props_block_offset);
+ TEST_SYNC_POINT_CALLBACK(
+ "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize",
+ &props_block_size);
+ }
+#endif // !NDEBUG
+ meta_index_builder->Add(kPropertiesBlock, properties_block_handle);
+ }
+}
+
+void BlockBasedTableBuilder::WriteCompressionDictBlock(
+ MetaIndexBuilder* meta_index_builder) {
+ if (rep_->compression_dict != nullptr &&
+ rep_->compression_dict->GetRawDict().size()) {
+ BlockHandle compression_dict_block_handle;
+ if (ok()) {
+ WriteRawBlock(rep_->compression_dict->GetRawDict(), kNoCompression,
+ &compression_dict_block_handle);
+#ifndef NDEBUG
+ Slice compression_dict = rep_->compression_dict->GetRawDict();
+ TEST_SYNC_POINT_CALLBACK(
+ "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict",
+ &compression_dict);
+#endif // NDEBUG
+ }
+ if (ok()) {
+ meta_index_builder->Add(kCompressionDictBlock,
+ compression_dict_block_handle);
+ }
+ }
+}
+
+void BlockBasedTableBuilder::WriteRangeDelBlock(
+ MetaIndexBuilder* meta_index_builder) {
+ if (ok() && !rep_->range_del_block.empty()) {
+ BlockHandle range_del_block_handle;
+ WriteRawBlock(rep_->range_del_block.Finish(), kNoCompression,
+ &range_del_block_handle);
+ meta_index_builder->Add(kRangeDelBlock, range_del_block_handle);
+ }
+}
+
+void BlockBasedTableBuilder::WriteFooter(BlockHandle& metaindex_block_handle,
+ BlockHandle& index_block_handle) {
+ Rep* r = rep_;
+ // No need to write out new footer if we're using default checksum.
+ // We're writing legacy magic number because we want old versions of RocksDB
+ // be able to read files generated with new release (just in case if
+ // somebody wants to roll back after an upgrade)
+ // TODO(icanadi) at some point in the future, when we're absolutely sure
+ // nobody will roll back to RocksDB 2.x versions, retire the legacy magic
+ // number and always write new table files with new magic number
+ bool legacy = (r->table_options.format_version == 0);
+ // this is guaranteed by BlockBasedTableBuilder's constructor
+ assert(r->table_options.checksum == kCRC32c ||
+ r->table_options.format_version != 0);
+ Footer footer(
+ legacy ? kLegacyBlockBasedTableMagicNumber : kBlockBasedTableMagicNumber,
+ r->table_options.format_version);
+ footer.set_metaindex_handle(metaindex_block_handle);
+ footer.set_index_handle(index_block_handle);
+ footer.set_checksum(r->table_options.checksum);
+ std::string footer_encoding;
+ footer.EncodeTo(&footer_encoding);
+ assert(r->status.ok());
+ r->status = r->file->Append(footer_encoding);
+ if (r->status.ok()) {
+ r->offset += footer_encoding.size();
+ }
+}
+
+void BlockBasedTableBuilder::EnterUnbuffered() {
+ Rep* r = rep_;
+ assert(r->state == Rep::State::kBuffered);
+ r->state = Rep::State::kUnbuffered;
+ const size_t kSampleBytes = r->compression_opts.zstd_max_train_bytes > 0
+ ? r->compression_opts.zstd_max_train_bytes
+ : r->compression_opts.max_dict_bytes;
+ Random64 generator{r->creation_time};
+ std::string compression_dict_samples;
+ std::vector<size_t> compression_dict_sample_lens;
+ if (!r->data_block_and_keys_buffers.empty()) {
+ while (compression_dict_samples.size() < kSampleBytes) {
+ size_t rand_idx =
+ static_cast<size_t>(
+ generator.Uniform(r->data_block_and_keys_buffers.size()));
+ size_t copy_len =
+ std::min(kSampleBytes - compression_dict_samples.size(),
+ r->data_block_and_keys_buffers[rand_idx].first.size());
+ compression_dict_samples.append(
+ r->data_block_and_keys_buffers[rand_idx].first, 0, copy_len);
+ compression_dict_sample_lens.emplace_back(copy_len);
+ }
+ }
+
+ // final data block flushed, now we can generate dictionary from the samples.
+ // OK if compression_dict_samples is empty, we'll just get empty dictionary.
+ std::string dict;
+ if (r->compression_opts.zstd_max_train_bytes > 0) {
+ dict = ZSTD_TrainDictionary(compression_dict_samples,
+ compression_dict_sample_lens,
+ r->compression_opts.max_dict_bytes);
+ } else {
+ dict = std::move(compression_dict_samples);
+ }
+ r->compression_dict.reset(new CompressionDict(dict, r->compression_type,
+ r->compression_opts.level));
+ r->verify_dict.reset(new UncompressionDict(
+ dict, r->compression_type == kZSTD ||
+ r->compression_type == kZSTDNotFinalCompression));
+
+ for (size_t i = 0; ok() && i < r->data_block_and_keys_buffers.size(); ++i) {
+ const auto& data_block = r->data_block_and_keys_buffers[i].first;
+ auto& keys = r->data_block_and_keys_buffers[i].second;
+ assert(!data_block.empty());
+ assert(!keys.empty());
+
+ for (const auto& key : keys) {
+ if (r->filter_builder != nullptr) {
+ size_t ts_sz =
+ r->internal_comparator.user_comparator()->timestamp_size();
+ r->filter_builder->Add(ExtractUserKeyAndStripTimestamp(key, ts_sz));
+ }
+ r->index_builder->OnKeyAdded(key);
+ }
+ WriteBlock(Slice(data_block), &r->pending_handle, true /* is_data_block */);
+ if (ok() && i + 1 < r->data_block_and_keys_buffers.size()) {
+ Slice first_key_in_next_block =
+ r->data_block_and_keys_buffers[i + 1].second.front();
+ Slice* first_key_in_next_block_ptr = &first_key_in_next_block;
+ r->index_builder->AddIndexEntry(&keys.back(), first_key_in_next_block_ptr,
+ r->pending_handle);
+ }
+ }
+ r->data_block_and_keys_buffers.clear();
+}
+
+Status BlockBasedTableBuilder::Finish() {
+ Rep* r = rep_;
+ assert(r->state != Rep::State::kClosed);
+ bool empty_data_block = r->data_block.empty();
+ Flush();
+ if (r->state == Rep::State::kBuffered) {
+ EnterUnbuffered();
+ }
+ // To make sure properties block is able to keep the accurate size of index
+ // block, we will finish writing all index entries first.
+ if (ok() && !empty_data_block) {
+ r->index_builder->AddIndexEntry(
+ &r->last_key, nullptr /* no next data block */, r->pending_handle);
+ }
+
+ // Write meta blocks, metaindex block and footer in the following order.
+ // 1. [meta block: filter]
+ // 2. [meta block: index]
+ // 3. [meta block: compression dictionary]
+ // 4. [meta block: range deletion tombstone]
+ // 5. [meta block: properties]
+ // 6. [metaindex block]
+ // 7. Footer
+ BlockHandle metaindex_block_handle, index_block_handle;
+ MetaIndexBuilder meta_index_builder;
+ WriteFilterBlock(&meta_index_builder);
+ WriteIndexBlock(&meta_index_builder, &index_block_handle);
+ WriteCompressionDictBlock(&meta_index_builder);
+ WriteRangeDelBlock(&meta_index_builder);
+ WritePropertiesBlock(&meta_index_builder);
+ if (ok()) {
+ // flush the meta index block
+ WriteRawBlock(meta_index_builder.Finish(), kNoCompression,
+ &metaindex_block_handle);
+ }
+ if (ok()) {
+ WriteFooter(metaindex_block_handle, index_block_handle);
+ }
+ if (r->file != nullptr) {
+ file_checksum_ = r->file->GetFileChecksum();
+ }
+ r->state = Rep::State::kClosed;
+ return r->status;
+}
+
+void BlockBasedTableBuilder::Abandon() {
+ assert(rep_->state != Rep::State::kClosed);
+ rep_->state = Rep::State::kClosed;
+}
+
+uint64_t BlockBasedTableBuilder::NumEntries() const {
+ return rep_->props.num_entries;
+}
+
+uint64_t BlockBasedTableBuilder::FileSize() const { return rep_->offset; }
+
+bool BlockBasedTableBuilder::NeedCompact() const {
+ for (const auto& collector : rep_->table_properties_collectors) {
+ if (collector->NeedCompact()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+TableProperties BlockBasedTableBuilder::GetTableProperties() const {
+ TableProperties ret = rep_->props;
+ for (const auto& collector : rep_->table_properties_collectors) {
+ for (const auto& prop : collector->GetReadableProperties()) {
+ ret.readable_properties.insert(prop);
+ }
+ collector->Finish(&ret.user_collected_properties);
+ }
+ return ret;
+}
+
+const char* BlockBasedTableBuilder::GetFileChecksumFuncName() const {
+ if (rep_->file != nullptr) {
+ return rep_->file->GetFileChecksumFuncName();
+ } else {
+ return kUnknownFileChecksumFuncName.c_str();
+ }
+}
+
+const std::string BlockBasedTable::kFilterBlockPrefix = "filter.";
+const std::string BlockBasedTable::kFullFilterBlockPrefix = "fullfilter.";
+const std::string BlockBasedTable::kPartitionedFilterBlockPrefix =
+ "partitionedfilter.";
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.h b/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.h
new file mode 100644
index 00000000..97c9bc65
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_builder.h
@@ -0,0 +1,157 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/version_edit.h"
+#include "rocksdb/flush_block_policy.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+#include "table/meta_blocks.h"
+#include "table/table_builder.h"
+#include "util/compression.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBuilder;
+class BlockHandle;
+class WritableFile;
+struct BlockBasedTableOptions;
+
+extern const uint64_t kBlockBasedTableMagicNumber;
+extern const uint64_t kLegacyBlockBasedTableMagicNumber;
+
+class BlockBasedTableBuilder : public TableBuilder {
+ public:
+ // Create a builder that will store the contents of the table it is
+ // building in *file. Does not close the file. It is up to the
+ // caller to close the file after calling Finish().
+ BlockBasedTableBuilder(
+ const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
+ const BlockBasedTableOptions& table_options,
+ const InternalKeyComparator& internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, WritableFileWriter* file,
+ const CompressionType compression_type,
+ const uint64_t sample_for_compression,
+ const CompressionOptions& compression_opts, const bool skip_filters,
+ const std::string& column_family_name, const int level_at_creation,
+ const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
+ const uint64_t target_file_size = 0,
+ const uint64_t file_creation_time = 0);
+
+ // No copying allowed
+ BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete;
+ BlockBasedTableBuilder& operator=(const BlockBasedTableBuilder&) = delete;
+
+ // REQUIRES: Either Finish() or Abandon() has been called.
+ ~BlockBasedTableBuilder();
+
+ // Add key,value to the table being constructed.
+ // REQUIRES: key is after any previously added key according to comparator.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Add(const Slice& key, const Slice& value) override;
+
+ // Return non-ok iff some error has been detected.
+ Status status() const override;
+
+ // Finish building the table. Stops using the file passed to the
+ // constructor after this function returns.
+ // REQUIRES: Finish(), Abandon() have not been called
+ Status Finish() override;
+
+ // Indicate that the contents of this builder should be abandoned. Stops
+ // using the file passed to the constructor after this function returns.
+ // If the caller is not going to call Finish(), it must call Abandon()
+ // before destroying this builder.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Abandon() override;
+
+ // Number of calls to Add() so far.
+ uint64_t NumEntries() const override;
+
+ // Size of the file generated so far. If invoked after a successful
+ // Finish() call, returns the size of the final generated file.
+ uint64_t FileSize() const override;
+
+ bool NeedCompact() const override;
+
+ // Get table properties
+ TableProperties GetTableProperties() const override;
+
+ // Get file checksum
+ const std::string& GetFileChecksum() const override { return file_checksum_; }
+
+ // Get file checksum function name
+ const char* GetFileChecksumFuncName() const override;
+
+ private:
+ bool ok() const { return status().ok(); }
+
+ // Transition state from buffered to unbuffered. See `Rep::State` API comment
+ // for details of the states.
+ // REQUIRES: `rep_->state == kBuffered`
+ void EnterUnbuffered();
+
+ // Call block's Finish() method
+ // and then write the compressed block contents to file.
+ void WriteBlock(BlockBuilder* block, BlockHandle* handle, bool is_data_block);
+
+ // Compress and write block content to the file.
+ void WriteBlock(const Slice& block_contents, BlockHandle* handle,
+ bool is_data_block);
+ // Directly write data to the file.
+ void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle,
+ bool is_data_block = false);
+ Status InsertBlockInCache(const Slice& block_contents,
+ const CompressionType type,
+ const BlockHandle* handle);
+
+ void WriteFilterBlock(MetaIndexBuilder* meta_index_builder);
+ void WriteIndexBlock(MetaIndexBuilder* meta_index_builder,
+ BlockHandle* index_block_handle);
+ void WritePropertiesBlock(MetaIndexBuilder* meta_index_builder);
+ void WriteCompressionDictBlock(MetaIndexBuilder* meta_index_builder);
+ void WriteRangeDelBlock(MetaIndexBuilder* meta_index_builder);
+ void WriteFooter(BlockHandle& metaindex_block_handle,
+ BlockHandle& index_block_handle);
+
+ struct Rep;
+ class BlockBasedTablePropertiesCollectorFactory;
+ class BlockBasedTablePropertiesCollector;
+ Rep* rep_;
+
+ // Advanced operation: flush any buffered key/value pairs to file.
+ // Can be used to ensure that two adjacent entries never live in
+ // the same data block. Most clients should not need to use this method.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Flush();
+
+ // Some compression libraries fail when the raw size is bigger than int. If
+ // uncompressed size is bigger than kCompressionSizeLimit, don't compress it
+ const uint64_t kCompressionSizeLimit = std::numeric_limits<int>::max();
+
+ // Store file checksum. If checksum is disabled, its value is "0".
+ std::string file_checksum_ = kUnknownFileChecksum;
+};
+
+Slice CompressBlock(const Slice& raw, const CompressionInfo& info,
+ CompressionType* type, uint32_t format_version,
+ bool do_sample, std::string* compressed_output,
+ std::string* sampled_output_fast,
+ std::string* sampled_output_slow);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.cc b/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.cc
new file mode 100644
index 00000000..70a6f38d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.cc
@@ -0,0 +1,649 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <cinttypes>
+
+#include <memory>
+#include <string>
+
+#include "options/options_helper.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/flush_block_policy.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 "util/mutexlock.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void TailPrefetchStats::RecordEffectiveSize(size_t len) {
+ MutexLock l(&mutex_);
+ if (num_records_ < kNumTracked) {
+ num_records_++;
+ }
+ records_[next_++] = len;
+ if (next_ == kNumTracked) {
+ next_ = 0;
+ }
+}
+
+size_t TailPrefetchStats::GetSuggestedPrefetchSize() {
+ std::vector<size_t> sorted;
+ {
+ MutexLock l(&mutex_);
+
+ if (num_records_ == 0) {
+ return 0;
+ }
+ sorted.assign(records_, records_ + num_records_);
+ }
+
+ // Of the historic size, we find the maximum one that satisifis the condtiion
+ // that if prefetching all, less than 1/8 will be wasted.
+ std::sort(sorted.begin(), sorted.end());
+
+ // Assuming we have 5 data points, and after sorting it looks like this:
+ //
+ // +---+
+ // +---+ | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // +---+ | | | |
+ // | | | | | |
+ // +---+ | | | | | |
+ // | | | | | | | |
+ // +---+ | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // +---+ +---+ +---+ +---+ +---+
+ //
+ // and we use every of the value as a candidate, and estimate how much we
+ // wasted, compared to read. For example, when we use the 3rd record
+ // as candiate. This area is what we read:
+ // +---+
+ // +---+ | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // *** *** *** ***+ *** *** *** *** **
+ // * | | | | | |
+ // +---+ | | | | | *
+ // * | | | | | | | |
+ // +---+ | | | | | | | *
+ // * | | | | X | | | | |
+ // | | | | | | | | | *
+ // * | | | | | | | | |
+ // | | | | | | | | | *
+ // * | | | | | | | | |
+ // *** *** ***-*** ***--*** ***--*** +****
+ // which is (size of the record) X (number of records).
+ //
+ // While wasted is this area:
+ // +---+
+ // +---+ | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // *** *** *** ****---+ | | | |
+ // * * | | | | |
+ // * *-*** *** | | | | |
+ // * * | | | | | | |
+ // *--** *** | | | | | | |
+ // | | | | | X | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // +---+ +---+ +---+ +---+ +---+
+ //
+ // Which can be calculated iteratively.
+ // The difference between wasted using 4st and 3rd record, will
+ // be following area:
+ // +---+
+ // +--+ +-+ ++ +-+ +-+ +---+ | |
+ // + xxxxxxxxxxxxxxxxxxxxxxxx | | | |
+ // xxxxxxxxxxxxxxxxxxxxxxxx | | | |
+ // + xxxxxxxxxxxxxxxxxxxxxxxx | | | |
+ // | xxxxxxxxxxxxxxxxxxxxxxxx | | | |
+ // +-+ +-+ +-+ ++ +---+ +--+ | | |
+ // | | | | | | |
+ // +---+ ++ | | | | | |
+ // | | | | | | X | | |
+ // +---+ ++ | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // | | | | | | | | | |
+ // +---+ +---+ +---+ +---+ +---+
+ //
+ // which will be the size difference between 4st and 3rd record,
+ // times 3, which is number of records before the 4st.
+ // Here we assume that all data within the prefetch range will be useful. In
+ // reality, it may not be the case when a partial block is inside the range,
+ // or there are data in the middle that is not read. We ignore those cases
+ // for simplicity.
+ assert(!sorted.empty());
+ size_t prev_size = sorted[0];
+ size_t max_qualified_size = sorted[0];
+ size_t wasted = 0;
+ for (size_t i = 1; i < sorted.size(); i++) {
+ size_t read = sorted[i] * sorted.size();
+ wasted += (sorted[i] - prev_size) * i;
+ if (wasted <= read / 8) {
+ max_qualified_size = sorted[i];
+ }
+ prev_size = sorted[i];
+ }
+ const size_t kMaxPrefetchSize = 512 * 1024; // Never exceed 512KB
+ return std::min(kMaxPrefetchSize, max_qualified_size);
+}
+
+// TODO(myabandeh): We should return an error instead of silently changing the
+// options
+BlockBasedTableFactory::BlockBasedTableFactory(
+ const BlockBasedTableOptions& _table_options)
+ : table_options_(_table_options) {
+ if (table_options_.flush_block_policy_factory == nullptr) {
+ table_options_.flush_block_policy_factory.reset(
+ new FlushBlockBySizePolicyFactory());
+ }
+ if (table_options_.no_block_cache) {
+ table_options_.block_cache.reset();
+ } else if (table_options_.block_cache == nullptr) {
+ LRUCacheOptions co;
+ co.capacity = 8 << 20;
+ // It makes little sense to pay overhead for mid-point insertion while the
+ // block size is only 8MB.
+ co.high_pri_pool_ratio = 0.0;
+ table_options_.block_cache = NewLRUCache(co);
+ }
+ if (table_options_.block_size_deviation < 0 ||
+ table_options_.block_size_deviation > 100) {
+ table_options_.block_size_deviation = 0;
+ }
+ if (table_options_.block_restart_interval < 1) {
+ table_options_.block_restart_interval = 1;
+ }
+ if (table_options_.index_block_restart_interval < 1) {
+ table_options_.index_block_restart_interval = 1;
+ }
+ if (table_options_.index_type == BlockBasedTableOptions::kHashSearch &&
+ table_options_.index_block_restart_interval != 1) {
+ // Currently kHashSearch is incompatible with index_block_restart_interval > 1
+ table_options_.index_block_restart_interval = 1;
+ }
+ if (table_options_.partition_filters &&
+ table_options_.index_type !=
+ BlockBasedTableOptions::kTwoLevelIndexSearch) {
+ // We do not support partitioned filters without partitioning indexes
+ table_options_.partition_filters = false;
+ }
+}
+
+Status BlockBasedTableFactory::NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ bool prefetch_index_and_filter_in_cache) const {
+ return BlockBasedTable::Open(
+ table_reader_options.ioptions, table_reader_options.env_options,
+ table_options_, table_reader_options.internal_comparator, std::move(file),
+ file_size, table_reader, table_reader_options.prefix_extractor,
+ prefetch_index_and_filter_in_cache, table_reader_options.skip_filters,
+ table_reader_options.level, table_reader_options.immortal,
+ table_reader_options.largest_seqno, &tail_prefetch_stats_,
+ table_reader_options.block_cache_tracer);
+}
+
+TableBuilder* BlockBasedTableFactory::NewTableBuilder(
+ const TableBuilderOptions& table_builder_options, uint32_t column_family_id,
+ WritableFileWriter* file) const {
+ auto table_builder = new BlockBasedTableBuilder(
+ table_builder_options.ioptions, table_builder_options.moptions,
+ table_options_, table_builder_options.internal_comparator,
+ table_builder_options.int_tbl_prop_collector_factories, column_family_id,
+ file, table_builder_options.compression_type,
+ table_builder_options.sample_for_compression,
+ table_builder_options.compression_opts,
+ table_builder_options.skip_filters,
+ table_builder_options.column_family_name, table_builder_options.level,
+ table_builder_options.creation_time,
+ table_builder_options.oldest_key_time,
+ table_builder_options.target_file_size,
+ table_builder_options.file_creation_time);
+
+ return table_builder;
+}
+
+Status BlockBasedTableFactory::SanitizeOptions(
+ const DBOptions& db_opts, const ColumnFamilyOptions& cf_opts) const {
+ if (table_options_.index_type == BlockBasedTableOptions::kHashSearch &&
+ cf_opts.prefix_extractor == nullptr) {
+ return Status::InvalidArgument(
+ "Hash index is specified for block-based "
+ "table, but prefix_extractor is not given");
+ }
+ if (table_options_.cache_index_and_filter_blocks &&
+ table_options_.no_block_cache) {
+ return Status::InvalidArgument(
+ "Enable cache_index_and_filter_blocks, "
+ ", but block cache is disabled");
+ }
+ if (table_options_.pin_l0_filter_and_index_blocks_in_cache &&
+ table_options_.no_block_cache) {
+ return Status::InvalidArgument(
+ "Enable pin_l0_filter_and_index_blocks_in_cache, "
+ ", but block cache is disabled");
+ }
+ if (!BlockBasedTableSupportedVersion(table_options_.format_version)) {
+ return Status::InvalidArgument(
+ "Unsupported BlockBasedTable format_version. Please check "
+ "include/rocksdb/table.h for more info");
+ }
+ if (table_options_.block_align && (cf_opts.compression != kNoCompression)) {
+ return Status::InvalidArgument(
+ "Enable block_align, but compression "
+ "enabled");
+ }
+ if (table_options_.block_align &&
+ (table_options_.block_size & (table_options_.block_size - 1))) {
+ return Status::InvalidArgument(
+ "Block alignment requested but block size is not a power of 2");
+ }
+ if (table_options_.block_size > port::kMaxUint32) {
+ return Status::InvalidArgument(
+ "block size exceeds maximum number (4GiB) allowed");
+ }
+ if (table_options_.data_block_index_type ==
+ BlockBasedTableOptions::kDataBlockBinaryAndHash &&
+ table_options_.data_block_hash_table_util_ratio <= 0) {
+ return Status::InvalidArgument(
+ "data_block_hash_table_util_ratio should be greater than 0 when "
+ "data_block_index_type is set to kDataBlockBinaryAndHash");
+ }
+ if (db_opts.unordered_write && cf_opts.max_successive_merges > 0) {
+ // TODO(myabandeh): support it
+ return Status::InvalidArgument(
+ "max_successive_merges larger than 0 is currently inconsistent with "
+ "unordered_write");
+ }
+ return Status::OK();
+}
+
+std::string BlockBasedTableFactory::GetPrintableTableOptions() const {
+ std::string ret;
+ ret.reserve(20000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+
+ snprintf(buffer, kBufferSize, " flush_block_policy_factory: %s (%p)\n",
+ table_options_.flush_block_policy_factory->Name(),
+ static_cast<void*>(table_options_.flush_block_policy_factory.get()));
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " cache_index_and_filter_blocks: %d\n",
+ table_options_.cache_index_and_filter_blocks);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize,
+ " cache_index_and_filter_blocks_with_high_priority: %d\n",
+ table_options_.cache_index_and_filter_blocks_with_high_priority);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize,
+ " pin_l0_filter_and_index_blocks_in_cache: %d\n",
+ table_options_.pin_l0_filter_and_index_blocks_in_cache);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " pin_top_level_index_and_filter: %d\n",
+ table_options_.pin_top_level_index_and_filter);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " index_type: %d\n",
+ table_options_.index_type);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " data_block_index_type: %d\n",
+ table_options_.data_block_index_type);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " index_shortening: %d\n",
+ static_cast<int>(table_options_.index_shortening));
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " data_block_hash_table_util_ratio: %lf\n",
+ table_options_.data_block_hash_table_util_ratio);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " hash_index_allow_collision: %d\n",
+ table_options_.hash_index_allow_collision);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " checksum: %d\n", table_options_.checksum);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " no_block_cache: %d\n",
+ table_options_.no_block_cache);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " block_cache: %p\n",
+ static_cast<void*>(table_options_.block_cache.get()));
+ ret.append(buffer);
+ if (table_options_.block_cache) {
+ const char* block_cache_name = table_options_.block_cache->Name();
+ if (block_cache_name != nullptr) {
+ snprintf(buffer, kBufferSize, " block_cache_name: %s\n",
+ block_cache_name);
+ ret.append(buffer);
+ }
+ ret.append(" block_cache_options:\n");
+ ret.append(table_options_.block_cache->GetPrintableOptions());
+ }
+ snprintf(buffer, kBufferSize, " block_cache_compressed: %p\n",
+ static_cast<void*>(table_options_.block_cache_compressed.get()));
+ ret.append(buffer);
+ if (table_options_.block_cache_compressed) {
+ const char* block_cache_compressed_name =
+ table_options_.block_cache_compressed->Name();
+ if (block_cache_compressed_name != nullptr) {
+ snprintf(buffer, kBufferSize, " block_cache_name: %s\n",
+ block_cache_compressed_name);
+ ret.append(buffer);
+ }
+ ret.append(" block_cache_compressed_options:\n");
+ ret.append(table_options_.block_cache_compressed->GetPrintableOptions());
+ }
+ snprintf(buffer, kBufferSize, " persistent_cache: %p\n",
+ static_cast<void*>(table_options_.persistent_cache.get()));
+ ret.append(buffer);
+ if (table_options_.persistent_cache) {
+ snprintf(buffer, kBufferSize, " persistent_cache_options:\n");
+ ret.append(buffer);
+ ret.append(table_options_.persistent_cache->GetPrintableOptions());
+ }
+ snprintf(buffer, kBufferSize, " block_size: %" ROCKSDB_PRIszt "\n",
+ table_options_.block_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " block_size_deviation: %d\n",
+ table_options_.block_size_deviation);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " block_restart_interval: %d\n",
+ table_options_.block_restart_interval);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " index_block_restart_interval: %d\n",
+ table_options_.index_block_restart_interval);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " metadata_block_size: %" PRIu64 "\n",
+ table_options_.metadata_block_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " partition_filters: %d\n",
+ table_options_.partition_filters);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " use_delta_encoding: %d\n",
+ table_options_.use_delta_encoding);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " filter_policy: %s\n",
+ table_options_.filter_policy == nullptr
+ ? "nullptr"
+ : table_options_.filter_policy->Name());
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " whole_key_filtering: %d\n",
+ table_options_.whole_key_filtering);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " verify_compression: %d\n",
+ table_options_.verify_compression);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " read_amp_bytes_per_bit: %d\n",
+ table_options_.read_amp_bytes_per_bit);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " format_version: %d\n",
+ table_options_.format_version);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " enable_index_compression: %d\n",
+ table_options_.enable_index_compression);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " block_align: %d\n",
+ table_options_.block_align);
+ ret.append(buffer);
+ return ret;
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+bool SerializeSingleBlockBasedTableOption(
+ std::string* opt_string, const BlockBasedTableOptions& bbt_options,
+ const std::string& name, const std::string& delimiter) {
+ auto iter = block_based_table_type_info.find(name);
+ if (iter == block_based_table_type_info.end()) {
+ return false;
+ }
+ auto& opt_info = iter->second;
+ const char* opt_address =
+ reinterpret_cast<const char*>(&bbt_options) + opt_info.offset;
+ std::string value;
+ bool result = SerializeSingleOptionHelper(opt_address, opt_info.type, &value);
+ if (result) {
+ *opt_string = name + "=" + value + delimiter;
+ }
+ return result;
+}
+} // namespace
+
+Status BlockBasedTableFactory::GetOptionString(
+ std::string* opt_string, const std::string& delimiter) const {
+ assert(opt_string);
+ opt_string->clear();
+ for (auto iter = block_based_table_type_info.begin();
+ iter != block_based_table_type_info.end(); ++iter) {
+ if (iter->second.verification == OptionVerificationType::kDeprecated) {
+ // If the option is no longer used in rocksdb and marked as deprecated,
+ // we skip it in the serialization.
+ continue;
+ }
+ std::string single_output;
+ bool result = SerializeSingleBlockBasedTableOption(
+ &single_output, table_options_, iter->first, delimiter);
+ assert(result);
+ if (result) {
+ opt_string->append(single_output);
+ }
+ }
+ return Status::OK();
+}
+#else
+Status BlockBasedTableFactory::GetOptionString(
+ std::string* /*opt_string*/, const std::string& /*delimiter*/) const {
+ return Status::OK();
+}
+#endif // !ROCKSDB_LITE
+
+const BlockBasedTableOptions& BlockBasedTableFactory::table_options() const {
+ return table_options_;
+}
+
+#ifndef ROCKSDB_LITE
+namespace {
+std::string ParseBlockBasedTableOption(const std::string& name,
+ const std::string& org_value,
+ BlockBasedTableOptions* new_options,
+ bool input_strings_escaped = false,
+ bool ignore_unknown_options = false) {
+ const std::string& value =
+ input_strings_escaped ? UnescapeOptionString(org_value) : org_value;
+ if (!input_strings_escaped) {
+ // if the input string is not escaped, it means this function is
+ // invoked from SetOptions, which takes the old format.
+ if (name == "block_cache" || name == "block_cache_compressed") {
+ // cache options can be specified in the following format
+ // "block_cache={capacity=1M;num_shard_bits=4;
+ // strict_capacity_limit=true;high_pri_pool_ratio=0.5;}"
+ // To support backward compatibility, the following format
+ // is also supported.
+ // "block_cache=1M"
+ std::shared_ptr<Cache> cache;
+ // block_cache is specified in format block_cache=<cache_size>.
+ if (value.find('=') == std::string::npos) {
+ cache = NewLRUCache(ParseSizeT(value));
+ } else {
+ LRUCacheOptions cache_opts;
+ if (!ParseOptionHelper(reinterpret_cast<char*>(&cache_opts),
+ OptionType::kLRUCacheOptions, value)) {
+ return "Invalid cache options";
+ }
+ cache = NewLRUCache(cache_opts);
+ }
+
+ if (name == "block_cache") {
+ new_options->block_cache = cache;
+ } else {
+ new_options->block_cache_compressed = cache;
+ }
+ return "";
+ } else if (name == "filter_policy") {
+ // Expect the following format
+ // bloomfilter:int:bool
+ const std::string kName = "bloomfilter:";
+ if (value.compare(0, kName.size(), kName) != 0) {
+ return "Invalid filter policy name";
+ }
+ size_t pos = value.find(':', kName.size());
+ if (pos == std::string::npos) {
+ return "Invalid filter policy config, missing bits_per_key";
+ }
+ double bits_per_key =
+ ParseDouble(trim(value.substr(kName.size(), pos - kName.size())));
+ bool use_block_based_builder =
+ ParseBoolean("use_block_based_builder", trim(value.substr(pos + 1)));
+ new_options->filter_policy.reset(
+ NewBloomFilterPolicy(bits_per_key, use_block_based_builder));
+ return "";
+ }
+ }
+ const auto iter = block_based_table_type_info.find(name);
+ if (iter == block_based_table_type_info.end()) {
+ if (ignore_unknown_options) {
+ return "";
+ } else {
+ return "Unrecognized option";
+ }
+ }
+ const auto& opt_info = iter->second;
+ if (opt_info.verification != OptionVerificationType::kDeprecated &&
+ !ParseOptionHelper(reinterpret_cast<char*>(new_options) + opt_info.offset,
+ opt_info.type, value)) {
+ return "Invalid value";
+ }
+ return "";
+}
+} // namespace
+
+Status GetBlockBasedTableOptionsFromString(
+ const BlockBasedTableOptions& table_options, const std::string& opts_str,
+ BlockBasedTableOptions* new_table_options) {
+ std::unordered_map<std::string, std::string> opts_map;
+ Status s = StringToMap(opts_str, &opts_map);
+ if (!s.ok()) {
+ return s;
+ }
+
+ return GetBlockBasedTableOptionsFromMap(table_options, opts_map,
+ new_table_options);
+}
+
+Status GetBlockBasedTableOptionsFromMap(
+ const BlockBasedTableOptions& table_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ BlockBasedTableOptions* new_table_options, bool input_strings_escaped,
+ bool ignore_unknown_options) {
+ assert(new_table_options);
+ *new_table_options = table_options;
+ for (const auto& o : opts_map) {
+ auto error_message = ParseBlockBasedTableOption(
+ o.first, o.second, new_table_options, input_strings_escaped,
+ ignore_unknown_options);
+ if (error_message != "") {
+ const auto iter = block_based_table_type_info.find(o.first);
+ if (iter == block_based_table_type_info.end() ||
+ !input_strings_escaped || // !input_strings_escaped indicates
+ // the old API, where everything is
+ // parsable.
+ (iter->second.verification != OptionVerificationType::kByName &&
+ iter->second.verification !=
+ OptionVerificationType::kByNameAllowNull &&
+ iter->second.verification !=
+ OptionVerificationType::kByNameAllowFromNull &&
+ iter->second.verification != OptionVerificationType::kDeprecated)) {
+ // Restore "new_options" to the default "base_options".
+ *new_table_options = table_options;
+ return Status::InvalidArgument("Can't parse BlockBasedTableOptions:",
+ o.first + " " + error_message);
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status VerifyBlockBasedTableFactory(
+ const BlockBasedTableFactory* base_tf,
+ const BlockBasedTableFactory* file_tf,
+ OptionsSanityCheckLevel sanity_check_level) {
+ if ((base_tf != nullptr) != (file_tf != nullptr) &&
+ sanity_check_level > kSanityLevelNone) {
+ return Status::Corruption(
+ "[RocksDBOptionsParser]: Inconsistent TableFactory class type");
+ }
+ if (base_tf == nullptr) {
+ return Status::OK();
+ }
+ assert(file_tf != nullptr);
+
+ const auto& base_opt = base_tf->table_options();
+ const auto& file_opt = file_tf->table_options();
+
+ for (auto& pair : block_based_table_type_info) {
+ if (pair.second.verification == OptionVerificationType::kDeprecated) {
+ // We skip checking deprecated variables as they might
+ // contain random values since they might not be initialized
+ continue;
+ }
+ if (BBTOptionSanityCheckLevel(pair.first) <= sanity_check_level) {
+ if (!AreEqualOptions(reinterpret_cast<const char*>(&base_opt),
+ reinterpret_cast<const char*>(&file_opt),
+ pair.second, pair.first, nullptr)) {
+ return Status::Corruption(
+ "[RocksDBOptionsParser]: "
+ "failed the verification on BlockBasedTableOptions::",
+ pair.first);
+ }
+ }
+ }
+ return Status::OK();
+}
+#endif // !ROCKSDB_LITE
+
+TableFactory* NewBlockBasedTableFactory(
+ const BlockBasedTableOptions& _table_options) {
+ return new BlockBasedTableFactory(_table_options);
+}
+
+const std::string BlockBasedTableFactory::kName = "BlockBasedTable";
+const std::string BlockBasedTablePropertyNames::kIndexType =
+ "rocksdb.block.based.table.index.type";
+const std::string BlockBasedTablePropertyNames::kWholeKeyFiltering =
+ "rocksdb.block.based.table.whole.key.filtering";
+const std::string BlockBasedTablePropertyNames::kPrefixFiltering =
+ "rocksdb.block.based.table.prefix.filtering";
+const std::string kHashIndexPrefixesBlock = "rocksdb.hashindex.prefixes";
+const std::string kHashIndexPrefixesMetadataBlock =
+ "rocksdb.hashindex.metadata";
+const std::string kPropTrue = "1";
+const std::string kPropFalse = "0";
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.h b/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.h
new file mode 100644
index 00000000..7c8633c0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_factory.h
@@ -0,0 +1,195 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "db/dbformat.h"
+#include "options/options_helper.h"
+#include "options/options_parser.h"
+#include "rocksdb/flush_block_policy.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct EnvOptions;
+
+class BlockBasedTableBuilder;
+
+// A class used to track actual bytes written from the tail in the recent SST
+// file opens, and provide a suggestion for following open.
+class TailPrefetchStats {
+ public:
+ void RecordEffectiveSize(size_t len);
+ // 0 indicates no information to determine.
+ size_t GetSuggestedPrefetchSize();
+
+ private:
+ const static size_t kNumTracked = 32;
+ size_t records_[kNumTracked];
+ port::Mutex mutex_;
+ size_t next_ = 0;
+ size_t num_records_ = 0;
+};
+
+class BlockBasedTableFactory : public TableFactory {
+ public:
+ explicit BlockBasedTableFactory(
+ const BlockBasedTableOptions& table_options = BlockBasedTableOptions());
+
+ ~BlockBasedTableFactory() {}
+
+ const char* Name() const override { return kName.c_str(); }
+
+ Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ bool prefetch_index_and_filter_in_cache = true) const override;
+
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_family_id, WritableFileWriter* file) const override;
+
+ // Sanitizes the specified DB Options.
+ Status SanitizeOptions(const DBOptions& db_opts,
+ const ColumnFamilyOptions& cf_opts) const override;
+
+ std::string GetPrintableTableOptions() const override;
+
+ Status GetOptionString(std::string* opt_string,
+ const std::string& delimiter) const override;
+
+ const BlockBasedTableOptions& table_options() const;
+
+ void* GetOptions() override { return &table_options_; }
+
+ bool IsDeleteRangeSupported() const override { return true; }
+
+ static const std::string kName;
+
+ private:
+ BlockBasedTableOptions table_options_;
+ mutable TailPrefetchStats tail_prefetch_stats_;
+};
+
+extern const std::string kHashIndexPrefixesBlock;
+extern const std::string kHashIndexPrefixesMetadataBlock;
+extern const std::string kPropTrue;
+extern const std::string kPropFalse;
+
+#ifndef ROCKSDB_LITE
+extern Status VerifyBlockBasedTableFactory(
+ const BlockBasedTableFactory* base_tf,
+ const BlockBasedTableFactory* file_tf,
+ OptionsSanityCheckLevel sanity_check_level);
+
+static std::unordered_map<std::string, OptionTypeInfo>
+ block_based_table_type_info = {
+ /* currently not supported
+ std::shared_ptr<Cache> block_cache = nullptr;
+ std::shared_ptr<Cache> block_cache_compressed = nullptr;
+ */
+ {"flush_block_policy_factory",
+ {offsetof(struct BlockBasedTableOptions, flush_block_policy_factory),
+ OptionType::kFlushBlockPolicyFactory, OptionVerificationType::kByName,
+ false, 0}},
+ {"cache_index_and_filter_blocks",
+ {offsetof(struct BlockBasedTableOptions,
+ cache_index_and_filter_blocks),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"cache_index_and_filter_blocks_with_high_priority",
+ {offsetof(struct BlockBasedTableOptions,
+ cache_index_and_filter_blocks_with_high_priority),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"pin_l0_filter_and_index_blocks_in_cache",
+ {offsetof(struct BlockBasedTableOptions,
+ pin_l0_filter_and_index_blocks_in_cache),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"index_type",
+ {offsetof(struct BlockBasedTableOptions, index_type),
+ OptionType::kBlockBasedTableIndexType,
+ OptionVerificationType::kNormal, false, 0}},
+ {"hash_index_allow_collision",
+ {offsetof(struct BlockBasedTableOptions, hash_index_allow_collision),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"data_block_index_type",
+ {offsetof(struct BlockBasedTableOptions, data_block_index_type),
+ OptionType::kBlockBasedTableDataBlockIndexType,
+ OptionVerificationType::kNormal, false, 0}},
+ {"index_shortening",
+ {offsetof(struct BlockBasedTableOptions, index_shortening),
+ OptionType::kBlockBasedTableIndexShorteningMode,
+ OptionVerificationType::kNormal, false, 0}},
+ {"data_block_hash_table_util_ratio",
+ {offsetof(struct BlockBasedTableOptions,
+ data_block_hash_table_util_ratio),
+ OptionType::kDouble, OptionVerificationType::kNormal, false, 0}},
+ {"checksum",
+ {offsetof(struct BlockBasedTableOptions, checksum),
+ OptionType::kChecksumType, OptionVerificationType::kNormal, false,
+ 0}},
+ {"no_block_cache",
+ {offsetof(struct BlockBasedTableOptions, no_block_cache),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"block_size",
+ {offsetof(struct BlockBasedTableOptions, block_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
+ {"block_size_deviation",
+ {offsetof(struct BlockBasedTableOptions, block_size_deviation),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"block_restart_interval",
+ {offsetof(struct BlockBasedTableOptions, block_restart_interval),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"index_block_restart_interval",
+ {offsetof(struct BlockBasedTableOptions, index_block_restart_interval),
+ OptionType::kInt, OptionVerificationType::kNormal, false, 0}},
+ {"index_per_partition",
+ {0, OptionType::kUInt64T, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"metadata_block_size",
+ {offsetof(struct BlockBasedTableOptions, metadata_block_size),
+ OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}},
+ {"partition_filters",
+ {offsetof(struct BlockBasedTableOptions, partition_filters),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"filter_policy",
+ {offsetof(struct BlockBasedTableOptions, filter_policy),
+ OptionType::kFilterPolicy, OptionVerificationType::kByName, false,
+ 0}},
+ {"whole_key_filtering",
+ {offsetof(struct BlockBasedTableOptions, whole_key_filtering),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"skip_table_builder_flush",
+ {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
+ 0}},
+ {"format_version",
+ {offsetof(struct BlockBasedTableOptions, format_version),
+ OptionType::kUInt32T, OptionVerificationType::kNormal, false, 0}},
+ {"verify_compression",
+ {offsetof(struct BlockBasedTableOptions, verify_compression),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"read_amp_bytes_per_bit",
+ {offsetof(struct BlockBasedTableOptions, read_amp_bytes_per_bit),
+ OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
+ {"enable_index_compression",
+ {offsetof(struct BlockBasedTableOptions, enable_index_compression),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"block_align",
+ {offsetof(struct BlockBasedTableOptions, block_align),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
+ {"pin_top_level_index_and_filter",
+ {offsetof(struct BlockBasedTableOptions,
+ pin_top_level_index_and_filter),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}};
+#endif // !ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc b/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc
new file mode 100644
index 00000000..9b37b431
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc
@@ -0,0 +1,4531 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/block_based_table_reader.h"
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+
+#include "file/file_prefetch_buffer.h"
+#include "file/random_access_file_reader.h"
+
+#include "rocksdb/cache.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_filter_block.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/block_based/block_prefix_index.h"
+#include "table/block_based/filter_block.h"
+#include "table/block_based/full_filter_block.h"
+#include "table/block_based/partitioned_filter_block.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/multiget_context.h"
+#include "table/persistent_cache_helper.h"
+#include "table/sst_file_writer_collectors.h"
+#include "table/two_level_iterator.h"
+
+#include "monitoring/perf_context_imp.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+#include "util/util.h"
+#include "util/xxhash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const uint64_t kBlockBasedTableMagicNumber;
+extern const std::string kHashIndexPrefixesBlock;
+extern const std::string kHashIndexPrefixesMetadataBlock;
+
+typedef BlockBasedTable::IndexReader IndexReader;
+
+// Found that 256 KB readahead size provides the best performance, based on
+// experiments, for auto readahead. Experiment data is in PR #3282.
+const size_t BlockBasedTable::kMaxAutoReadaheadSize = 256 * 1024;
+
+BlockBasedTable::~BlockBasedTable() {
+ delete rep_;
+}
+
+std::atomic<uint64_t> BlockBasedTable::next_cache_key_id_(0);
+
+template <typename TBlocklike>
+class BlocklikeTraits;
+
+template <>
+class BlocklikeTraits<BlockContents> {
+ public:
+ static BlockContents* Create(BlockContents&& contents,
+ SequenceNumber /* global_seqno */,
+ size_t /* read_amp_bytes_per_bit */,
+ Statistics* /* statistics */,
+ bool /* using_zstd */,
+ const FilterPolicy* /* filter_policy */) {
+ return new BlockContents(std::move(contents));
+ }
+
+ static uint32_t GetNumRestarts(const BlockContents& /* contents */) {
+ return 0;
+ }
+};
+
+template <>
+class BlocklikeTraits<ParsedFullFilterBlock> {
+ public:
+ static ParsedFullFilterBlock* Create(BlockContents&& contents,
+ SequenceNumber /* global_seqno */,
+ size_t /* read_amp_bytes_per_bit */,
+ Statistics* /* statistics */,
+ bool /* using_zstd */,
+ const FilterPolicy* filter_policy) {
+ return new ParsedFullFilterBlock(filter_policy, std::move(contents));
+ }
+
+ static uint32_t GetNumRestarts(const ParsedFullFilterBlock& /* block */) {
+ return 0;
+ }
+};
+
+template <>
+class BlocklikeTraits<Block> {
+ public:
+ static Block* Create(BlockContents&& contents, SequenceNumber global_seqno,
+ size_t read_amp_bytes_per_bit, Statistics* statistics,
+ bool /* using_zstd */,
+ const FilterPolicy* /* filter_policy */) {
+ return new Block(std::move(contents), global_seqno, read_amp_bytes_per_bit,
+ statistics);
+ }
+
+ static uint32_t GetNumRestarts(const Block& block) {
+ return block.NumRestarts();
+ }
+};
+
+template <>
+class BlocklikeTraits<UncompressionDict> {
+ public:
+ static UncompressionDict* Create(BlockContents&& contents,
+ SequenceNumber /* global_seqno */,
+ size_t /* read_amp_bytes_per_bit */,
+ Statistics* /* statistics */,
+ bool using_zstd,
+ const FilterPolicy* /* filter_policy */) {
+ return new UncompressionDict(contents.data, std::move(contents.allocation),
+ using_zstd);
+ }
+
+ static uint32_t GetNumRestarts(const UncompressionDict& /* dict */) {
+ return 0;
+ }
+};
+
+namespace {
+// Read the block identified by "handle" from "file".
+// The only relevant option is options.verify_checksums for now.
+// On failure return non-OK.
+// On success fill *result and return OK - caller owns *result
+// @param uncompression_dict Data for presetting the compression library's
+// dictionary.
+template <typename TBlocklike>
+Status ReadBlockFromFile(
+ RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer,
+ const Footer& footer, const ReadOptions& options, const BlockHandle& handle,
+ std::unique_ptr<TBlocklike>* result, const ImmutableCFOptions& ioptions,
+ bool do_uncompress, bool maybe_compressed, BlockType block_type,
+ const UncompressionDict& uncompression_dict,
+ const PersistentCacheOptions& cache_options, SequenceNumber global_seqno,
+ size_t read_amp_bytes_per_bit, MemoryAllocator* memory_allocator,
+ bool for_compaction, bool using_zstd, const FilterPolicy* filter_policy) {
+ assert(result);
+
+ BlockContents contents;
+ BlockFetcher block_fetcher(
+ file, prefetch_buffer, footer, options, handle, &contents, ioptions,
+ do_uncompress, maybe_compressed, block_type, uncompression_dict,
+ cache_options, memory_allocator, nullptr, for_compaction);
+ Status s = block_fetcher.ReadBlockContents();
+ if (s.ok()) {
+ result->reset(BlocklikeTraits<TBlocklike>::Create(
+ std::move(contents), global_seqno, read_amp_bytes_per_bit,
+ ioptions.statistics, using_zstd, filter_policy));
+ }
+
+ return s;
+}
+
+inline MemoryAllocator* GetMemoryAllocator(
+ const BlockBasedTableOptions& table_options) {
+ return table_options.block_cache.get()
+ ? table_options.block_cache->memory_allocator()
+ : nullptr;
+}
+
+inline MemoryAllocator* GetMemoryAllocatorForCompressedBlock(
+ const BlockBasedTableOptions& table_options) {
+ return table_options.block_cache_compressed.get()
+ ? table_options.block_cache_compressed->memory_allocator()
+ : nullptr;
+}
+
+// Delete the entry resided in the cache.
+template <class Entry>
+void DeleteCachedEntry(const Slice& /*key*/, void* value) {
+ auto entry = reinterpret_cast<Entry*>(value);
+ delete entry;
+}
+
+// Release the cached entry and decrement its ref count.
+void ForceReleaseCachedEntry(void* arg, void* h) {
+ Cache* cache = reinterpret_cast<Cache*>(arg);
+ Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(h);
+ cache->Release(handle, true /* force_erase */);
+}
+
+// Release the cached entry and decrement its ref count.
+// Do not force erase
+void ReleaseCachedEntry(void* arg, void* h) {
+ Cache* cache = reinterpret_cast<Cache*>(arg);
+ Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(h);
+ cache->Release(handle, false /* force_erase */);
+}
+
+// For hash based index, return true if prefix_extractor and
+// prefix_extractor_block mismatch, false otherwise. This flag will be used
+// as total_order_seek via NewIndexIterator
+bool PrefixExtractorChanged(const TableProperties* table_properties,
+ const SliceTransform* prefix_extractor) {
+ // BlockBasedTableOptions::kHashSearch requires prefix_extractor to be set.
+ // Turn off hash index in prefix_extractor is not set; if prefix_extractor
+ // is set but prefix_extractor_block is not set, also disable hash index
+ if (prefix_extractor == nullptr || table_properties == nullptr ||
+ table_properties->prefix_extractor_name.empty()) {
+ return true;
+ }
+
+ // prefix_extractor and prefix_extractor_block are both non-empty
+ if (table_properties->prefix_extractor_name.compare(
+ prefix_extractor->Name()) != 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+CacheAllocationPtr CopyBufferToHeap(MemoryAllocator* allocator, Slice& buf) {
+ CacheAllocationPtr heap_buf;
+ heap_buf = AllocateBlock(buf.size(), allocator);
+ memcpy(heap_buf.get(), buf.data(), buf.size());
+ return heap_buf;
+}
+
+} // namespace
+
+// Encapsulates common functionality for the various index reader
+// implementations. Provides access to the index block regardless of whether
+// it is owned by the reader or stored in the cache, or whether it is pinned
+// in the cache or not.
+class BlockBasedTable::IndexReaderCommon : public BlockBasedTable::IndexReader {
+ public:
+ IndexReaderCommon(const BlockBasedTable* t,
+ CachableEntry<Block>&& index_block)
+ : table_(t), index_block_(std::move(index_block)) {
+ assert(table_ != nullptr);
+ }
+
+ protected:
+ static Status ReadIndexBlock(const BlockBasedTable* table,
+ FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<Block>* index_block);
+
+ const BlockBasedTable* table() const { return table_; }
+
+ const InternalKeyComparator* internal_comparator() const {
+ assert(table_ != nullptr);
+ assert(table_->get_rep() != nullptr);
+
+ return &table_->get_rep()->internal_comparator;
+ }
+
+ bool index_has_first_key() const {
+ assert(table_ != nullptr);
+ assert(table_->get_rep() != nullptr);
+ return table_->get_rep()->index_has_first_key;
+ }
+
+ bool index_key_includes_seq() const {
+ assert(table_ != nullptr);
+ assert(table_->get_rep() != nullptr);
+ return table_->get_rep()->index_key_includes_seq;
+ }
+
+ bool index_value_is_full() const {
+ assert(table_ != nullptr);
+ assert(table_->get_rep() != nullptr);
+ return table_->get_rep()->index_value_is_full;
+ }
+
+ bool cache_index_blocks() const {
+ assert(table_ != nullptr);
+ assert(table_->get_rep() != nullptr);
+ return table_->get_rep()->table_options.cache_index_and_filter_blocks;
+ }
+
+ Status GetOrReadIndexBlock(bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<Block>* index_block) const;
+
+ size_t ApproximateIndexBlockMemoryUsage() const {
+ assert(!index_block_.GetOwnValue() || index_block_.GetValue() != nullptr);
+ return index_block_.GetOwnValue()
+ ? index_block_.GetValue()->ApproximateMemoryUsage()
+ : 0;
+ }
+
+ private:
+ const BlockBasedTable* table_;
+ CachableEntry<Block> index_block_;
+};
+
+Status BlockBasedTable::IndexReaderCommon::ReadIndexBlock(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<Block>* index_block) {
+ PERF_TIMER_GUARD(read_index_block_nanos);
+
+ assert(table != nullptr);
+ assert(index_block != nullptr);
+ assert(index_block->IsEmpty());
+
+ const Rep* const rep = table->get_rep();
+ assert(rep != nullptr);
+
+ const Status s = table->RetrieveBlock(
+ prefetch_buffer, read_options, rep->footer.index_handle(),
+ UncompressionDict::GetEmptyDict(), index_block, BlockType::kIndex,
+ get_context, lookup_context, /* for_compaction */ false, use_cache);
+
+ return s;
+}
+
+Status BlockBasedTable::IndexReaderCommon::GetOrReadIndexBlock(
+ bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<Block>* index_block) const {
+ assert(index_block != nullptr);
+
+ if (!index_block_.IsEmpty()) {
+ index_block->SetUnownedValue(index_block_.GetValue());
+ return Status::OK();
+ }
+
+ ReadOptions read_options;
+ if (no_io) {
+ read_options.read_tier = kBlockCacheTier;
+ }
+
+ return ReadIndexBlock(table_, /*prefetch_buffer=*/nullptr, read_options,
+ cache_index_blocks(), get_context, lookup_context,
+ index_block);
+}
+
+// Index that allows binary search lookup in a two-level index structure.
+class PartitionIndexReader : public BlockBasedTable::IndexReaderCommon {
+ public:
+ // Read the partition index from the file and create an instance for
+ // `PartitionIndexReader`.
+ // On success, index_reader will be populated; otherwise it will remain
+ // unmodified.
+ static Status Create(const BlockBasedTable* table,
+ FilePrefetchBuffer* prefetch_buffer, bool use_cache,
+ bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<IndexReader>* index_reader) {
+ assert(table != nullptr);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+ assert(index_reader != nullptr);
+
+ CachableEntry<Block> index_block;
+ if (prefetch || !use_cache) {
+ const Status s =
+ ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
+ /*get_context=*/nullptr, lookup_context, &index_block);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (use_cache && !pin) {
+ index_block.Reset();
+ }
+ }
+
+ index_reader->reset(
+ new PartitionIndexReader(table, std::move(index_block)));
+
+ return Status::OK();
+ }
+
+ // return a two-level iterator: first level is on the partition index
+ InternalIteratorBase<IndexValue>* NewIterator(
+ const ReadOptions& read_options, bool /* disable_prefix_seek */,
+ IndexBlockIter* iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override {
+ const bool no_io = (read_options.read_tier == kBlockCacheTier);
+ CachableEntry<Block> index_block;
+ const Status s =
+ GetOrReadIndexBlock(no_io, get_context, lookup_context, &index_block);
+ if (!s.ok()) {
+ if (iter != nullptr) {
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ return NewErrorInternalIterator<IndexValue>(s);
+ }
+
+ InternalIteratorBase<IndexValue>* it = nullptr;
+
+ Statistics* kNullStats = nullptr;
+ // Filters are already checked before seeking the index
+ if (!partition_map_.empty()) {
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ it = NewTwoLevelIterator(
+ new BlockBasedTable::PartitionedIndexIteratorState(table(),
+ &partition_map_),
+ index_block.GetValue()->NewIndexIterator(
+ internal_comparator(), internal_comparator()->user_comparator(),
+ nullptr, kNullStats, true, index_has_first_key(),
+ index_key_includes_seq(), index_value_is_full()));
+ } else {
+ ReadOptions ro;
+ ro.fill_cache = read_options.fill_cache;
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ it = new BlockBasedTableIterator<IndexBlockIter, IndexValue>(
+ table(), ro, *internal_comparator(),
+ index_block.GetValue()->NewIndexIterator(
+ internal_comparator(), internal_comparator()->user_comparator(),
+ nullptr, kNullStats, true, index_has_first_key(),
+ index_key_includes_seq(), index_value_is_full()),
+ false, true, /* prefix_extractor */ nullptr, BlockType::kIndex,
+ lookup_context ? lookup_context->caller
+ : TableReaderCaller::kUncategorized);
+ }
+
+ assert(it != nullptr);
+ index_block.TransferTo(it);
+
+ return it;
+
+ // TODO(myabandeh): Update TwoLevelIterator to be able to make use of
+ // on-stack BlockIter while the state is on heap. Currentlly it assumes
+ // the first level iter is always on heap and will attempt to delete it
+ // in its destructor.
+ }
+
+ void CacheDependencies(bool pin) override {
+ // Before read partitions, prefetch them to avoid lots of IOs
+ BlockCacheLookupContext lookup_context{TableReaderCaller::kPrefetch};
+ const BlockBasedTable::Rep* rep = table()->rep_;
+ IndexBlockIter biter;
+ BlockHandle handle;
+ Statistics* kNullStats = nullptr;
+
+ CachableEntry<Block> index_block;
+ Status s = GetOrReadIndexBlock(false /* no_io */, nullptr /* get_context */,
+ &lookup_context, &index_block);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(rep->ioptions.info_log,
+ "Error retrieving top-level index block while trying to "
+ "cache index partitions: %s",
+ s.ToString().c_str());
+ return;
+ }
+
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ index_block.GetValue()->NewIndexIterator(
+ internal_comparator(), internal_comparator()->user_comparator(), &biter,
+ kNullStats, true, index_has_first_key(), index_key_includes_seq(),
+ index_value_is_full());
+ // Index partitions are assumed to be consecuitive. Prefetch them all.
+ // Read the first block offset
+ biter.SeekToFirst();
+ if (!biter.Valid()) {
+ // Empty index.
+ return;
+ }
+ handle = biter.value().handle;
+ uint64_t prefetch_off = handle.offset();
+
+ // Read the last block's offset
+ biter.SeekToLast();
+ if (!biter.Valid()) {
+ // Empty index.
+ return;
+ }
+ handle = biter.value().handle;
+ uint64_t last_off = handle.offset() + block_size(handle);
+ uint64_t prefetch_len = last_off - prefetch_off;
+ std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
+ rep->CreateFilePrefetchBuffer(0, 0, &prefetch_buffer);
+ s = prefetch_buffer->Prefetch(rep->file.get(), prefetch_off,
+ static_cast<size_t>(prefetch_len));
+
+ // After prefetch, read the partitions one by one
+ biter.SeekToFirst();
+ auto ro = ReadOptions();
+ for (; biter.Valid(); biter.Next()) {
+ handle = biter.value().handle;
+ CachableEntry<Block> block;
+ // TODO: Support counter batch update for partitioned index and
+ // filter blocks
+ s = table()->MaybeReadBlockAndLoadToCache(
+ prefetch_buffer.get(), ro, handle, UncompressionDict::GetEmptyDict(),
+ &block, BlockType::kIndex, /*get_context=*/nullptr, &lookup_context,
+ /*contents=*/nullptr);
+
+ assert(s.ok() || block.GetValue() == nullptr);
+ if (s.ok() && block.GetValue() != nullptr) {
+ if (block.IsCached()) {
+ if (pin) {
+ partition_map_[handle.offset()] = std::move(block);
+ }
+ }
+ }
+ }
+ }
+
+ size_t ApproximateMemoryUsage() const override {
+ size_t usage = ApproximateIndexBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<PartitionIndexReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ // TODO(myabandeh): more accurate estimate of partition_map_ mem usage
+ return usage;
+ }
+
+ private:
+ PartitionIndexReader(const BlockBasedTable* t,
+ CachableEntry<Block>&& index_block)
+ : IndexReaderCommon(t, std::move(index_block)) {}
+
+ std::unordered_map<uint64_t, CachableEntry<Block>> partition_map_;
+};
+
+// Index that allows binary search lookup for the first key of each block.
+// This class can be viewed as a thin wrapper for `Block` class which already
+// supports binary search.
+class BinarySearchIndexReader : public BlockBasedTable::IndexReaderCommon {
+ public:
+ // Read index from the file and create an intance for
+ // `BinarySearchIndexReader`.
+ // On success, index_reader will be populated; otherwise it will remain
+ // unmodified.
+ static Status Create(const BlockBasedTable* table,
+ FilePrefetchBuffer* prefetch_buffer, bool use_cache,
+ bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<IndexReader>* index_reader) {
+ assert(table != nullptr);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+ assert(index_reader != nullptr);
+
+ CachableEntry<Block> index_block;
+ if (prefetch || !use_cache) {
+ const Status s =
+ ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
+ /*get_context=*/nullptr, lookup_context, &index_block);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (use_cache && !pin) {
+ index_block.Reset();
+ }
+ }
+
+ index_reader->reset(
+ new BinarySearchIndexReader(table, std::move(index_block)));
+
+ return Status::OK();
+ }
+
+ InternalIteratorBase<IndexValue>* NewIterator(
+ const ReadOptions& read_options, bool /* disable_prefix_seek */,
+ IndexBlockIter* iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override {
+ const bool no_io = (read_options.read_tier == kBlockCacheTier);
+ CachableEntry<Block> index_block;
+ const Status s =
+ GetOrReadIndexBlock(no_io, get_context, lookup_context, &index_block);
+ if (!s.ok()) {
+ if (iter != nullptr) {
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ return NewErrorInternalIterator<IndexValue>(s);
+ }
+
+ Statistics* kNullStats = nullptr;
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ auto it = index_block.GetValue()->NewIndexIterator(
+ internal_comparator(), internal_comparator()->user_comparator(), iter,
+ kNullStats, true, index_has_first_key(), index_key_includes_seq(),
+ index_value_is_full());
+
+ assert(it != nullptr);
+ index_block.TransferTo(it);
+
+ return it;
+ }
+
+ size_t ApproximateMemoryUsage() const override {
+ size_t usage = ApproximateIndexBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<BinarySearchIndexReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return usage;
+ }
+
+ private:
+ BinarySearchIndexReader(const BlockBasedTable* t,
+ CachableEntry<Block>&& index_block)
+ : IndexReaderCommon(t, std::move(index_block)) {}
+};
+
+// Index that leverages an internal hash table to quicken the lookup for a given
+// key.
+class HashIndexReader : public BlockBasedTable::IndexReaderCommon {
+ public:
+ static Status Create(const BlockBasedTable* table,
+ FilePrefetchBuffer* prefetch_buffer,
+ InternalIterator* meta_index_iter, bool use_cache,
+ bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<IndexReader>* index_reader) {
+ assert(table != nullptr);
+ assert(index_reader != nullptr);
+ assert(!pin || prefetch);
+
+ const BlockBasedTable::Rep* rep = table->get_rep();
+ assert(rep != nullptr);
+
+ CachableEntry<Block> index_block;
+ if (prefetch || !use_cache) {
+ const Status s =
+ ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
+ /*get_context=*/nullptr, lookup_context, &index_block);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (use_cache && !pin) {
+ index_block.Reset();
+ }
+ }
+
+ // Note, failure to create prefix hash index does not need to be a
+ // hard error. We can still fall back to the original binary search index.
+ // So, Create will succeed regardless, from this point on.
+
+ index_reader->reset(new HashIndexReader(table, std::move(index_block)));
+
+ // Get prefixes block
+ BlockHandle prefixes_handle;
+ Status s = FindMetaBlock(meta_index_iter, kHashIndexPrefixesBlock,
+ &prefixes_handle);
+ if (!s.ok()) {
+ // TODO: log error
+ return Status::OK();
+ }
+
+ // Get index metadata block
+ BlockHandle prefixes_meta_handle;
+ s = FindMetaBlock(meta_index_iter, kHashIndexPrefixesMetadataBlock,
+ &prefixes_meta_handle);
+ if (!s.ok()) {
+ // TODO: log error
+ return Status::OK();
+ }
+
+ RandomAccessFileReader* const file = rep->file.get();
+ const Footer& footer = rep->footer;
+ const ImmutableCFOptions& ioptions = rep->ioptions;
+ const PersistentCacheOptions& cache_options = rep->persistent_cache_options;
+ MemoryAllocator* const memory_allocator =
+ GetMemoryAllocator(rep->table_options);
+
+ // Read contents for the blocks
+ BlockContents prefixes_contents;
+ BlockFetcher prefixes_block_fetcher(
+ file, prefetch_buffer, footer, ReadOptions(), prefixes_handle,
+ &prefixes_contents, ioptions, true /*decompress*/,
+ true /*maybe_compressed*/, BlockType::kHashIndexPrefixes,
+ UncompressionDict::GetEmptyDict(), cache_options, memory_allocator);
+ s = prefixes_block_fetcher.ReadBlockContents();
+ if (!s.ok()) {
+ return s;
+ }
+ BlockContents prefixes_meta_contents;
+ BlockFetcher prefixes_meta_block_fetcher(
+ file, prefetch_buffer, footer, ReadOptions(), prefixes_meta_handle,
+ &prefixes_meta_contents, ioptions, true /*decompress*/,
+ true /*maybe_compressed*/, BlockType::kHashIndexMetadata,
+ UncompressionDict::GetEmptyDict(), cache_options, memory_allocator);
+ s = prefixes_meta_block_fetcher.ReadBlockContents();
+ if (!s.ok()) {
+ // TODO: log error
+ return Status::OK();
+ }
+
+ BlockPrefixIndex* prefix_index = nullptr;
+ assert(rep->internal_prefix_transform.get() != nullptr);
+ s = BlockPrefixIndex::Create(rep->internal_prefix_transform.get(),
+ prefixes_contents.data,
+ prefixes_meta_contents.data, &prefix_index);
+ // TODO: log error
+ if (s.ok()) {
+ HashIndexReader* const hash_index_reader =
+ static_cast<HashIndexReader*>(index_reader->get());
+ hash_index_reader->prefix_index_.reset(prefix_index);
+ }
+
+ return Status::OK();
+ }
+
+ InternalIteratorBase<IndexValue>* NewIterator(
+ const ReadOptions& read_options, bool disable_prefix_seek,
+ IndexBlockIter* iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override {
+ const bool no_io = (read_options.read_tier == kBlockCacheTier);
+ CachableEntry<Block> index_block;
+ const Status s =
+ GetOrReadIndexBlock(no_io, get_context, lookup_context, &index_block);
+ if (!s.ok()) {
+ if (iter != nullptr) {
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ return NewErrorInternalIterator<IndexValue>(s);
+ }
+
+ Statistics* kNullStats = nullptr;
+ const bool total_order_seek =
+ read_options.total_order_seek || disable_prefix_seek;
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ auto it = index_block.GetValue()->NewIndexIterator(
+ internal_comparator(), internal_comparator()->user_comparator(), iter,
+ kNullStats, total_order_seek, index_has_first_key(),
+ index_key_includes_seq(), index_value_is_full(),
+ false /* block_contents_pinned */, prefix_index_.get());
+
+ assert(it != nullptr);
+ index_block.TransferTo(it);
+
+ return it;
+ }
+
+ size_t ApproximateMemoryUsage() const override {
+ size_t usage = ApproximateIndexBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<HashIndexReader*>(this));
+#else
+ if (prefix_index_) {
+ usage += prefix_index_->ApproximateMemoryUsage();
+ }
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return usage;
+ }
+
+ private:
+ HashIndexReader(const BlockBasedTable* t, CachableEntry<Block>&& index_block)
+ : IndexReaderCommon(t, std::move(index_block)) {}
+
+ std::unique_ptr<BlockPrefixIndex> prefix_index_;
+};
+
+void BlockBasedTable::UpdateCacheHitMetrics(BlockType block_type,
+ GetContext* get_context,
+ size_t usage) const {
+ Statistics* const statistics = rep_->ioptions.statistics;
+
+ PERF_COUNTER_ADD(block_cache_hit_count, 1);
+ PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1,
+ static_cast<uint32_t>(rep_->level));
+
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_hit;
+ get_context->get_context_stats_.num_cache_bytes_read += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_HIT);
+ RecordTick(statistics, BLOCK_CACHE_BYTES_READ, usage);
+ }
+
+ switch (block_type) {
+ case BlockType::kFilter:
+ PERF_COUNTER_ADD(block_cache_filter_hit_count, 1);
+
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_filter_hit;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_FILTER_HIT);
+ }
+ break;
+
+ case BlockType::kCompressionDictionary:
+ // TODO: introduce perf counter for compression dictionary hit count
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_compression_dict_hit;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSION_DICT_HIT);
+ }
+ break;
+
+ case BlockType::kIndex:
+ PERF_COUNTER_ADD(block_cache_index_hit_count, 1);
+
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_index_hit;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_INDEX_HIT);
+ }
+ break;
+
+ default:
+ // TODO: introduce dedicated tickers/statistics/counters
+ // for range tombstones
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_data_hit;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_DATA_HIT);
+ }
+ break;
+ }
+}
+
+void BlockBasedTable::UpdateCacheMissMetrics(BlockType block_type,
+ GetContext* get_context) const {
+ Statistics* const statistics = rep_->ioptions.statistics;
+
+ // TODO: introduce aggregate (not per-level) block cache miss count
+ PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 1,
+ static_cast<uint32_t>(rep_->level));
+
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_miss;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_MISS);
+ }
+
+ // TODO: introduce perf counters for misses per block type
+ switch (block_type) {
+ case BlockType::kFilter:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_filter_miss;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_FILTER_MISS);
+ }
+ break;
+
+ case BlockType::kCompressionDictionary:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_compression_dict_miss;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSION_DICT_MISS);
+ }
+ break;
+
+ case BlockType::kIndex:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_index_miss;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_INDEX_MISS);
+ }
+ break;
+
+ default:
+ // TODO: introduce dedicated tickers/statistics/counters
+ // for range tombstones
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_data_miss;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_DATA_MISS);
+ }
+ break;
+ }
+}
+
+void BlockBasedTable::UpdateCacheInsertionMetrics(BlockType block_type,
+ GetContext* get_context,
+ size_t usage) const {
+ Statistics* const statistics = rep_->ioptions.statistics;
+
+ // TODO: introduce perf counters for block cache insertions
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_add;
+ get_context->get_context_stats_.num_cache_bytes_write += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_ADD);
+ RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, usage);
+ }
+
+ switch (block_type) {
+ case BlockType::kFilter:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_filter_add;
+ get_context->get_context_stats_.num_cache_filter_bytes_insert += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_FILTER_ADD);
+ RecordTick(statistics, BLOCK_CACHE_FILTER_BYTES_INSERT, usage);
+ }
+ break;
+
+ case BlockType::kCompressionDictionary:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_compression_dict_add;
+ get_context->get_context_stats_
+ .num_cache_compression_dict_bytes_insert += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSION_DICT_ADD);
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT,
+ usage);
+ }
+ break;
+
+ case BlockType::kIndex:
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_index_add;
+ get_context->get_context_stats_.num_cache_index_bytes_insert += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
+ RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, usage);
+ }
+ break;
+
+ default:
+ // TODO: introduce dedicated tickers/statistics/counters
+ // for range tombstones
+ if (get_context) {
+ ++get_context->get_context_stats_.num_cache_data_add;
+ get_context->get_context_stats_.num_cache_data_bytes_insert += usage;
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_DATA_ADD);
+ RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT, usage);
+ }
+ break;
+ }
+}
+
+Cache::Handle* BlockBasedTable::GetEntryFromCache(
+ Cache* block_cache, const Slice& key, BlockType block_type,
+ GetContext* get_context) const {
+ auto cache_handle = block_cache->Lookup(key, rep_->ioptions.statistics);
+
+ if (cache_handle != nullptr) {
+ UpdateCacheHitMetrics(block_type, get_context,
+ block_cache->GetUsage(cache_handle));
+ } else {
+ UpdateCacheMissMetrics(block_type, get_context);
+ }
+
+ return cache_handle;
+}
+
+// Helper function to setup the cache key's prefix for the Table.
+void BlockBasedTable::SetupCacheKeyPrefix(Rep* rep) {
+ assert(kMaxCacheKeyPrefixSize >= 10);
+ rep->cache_key_prefix_size = 0;
+ rep->compressed_cache_key_prefix_size = 0;
+ if (rep->table_options.block_cache != nullptr) {
+ GenerateCachePrefix(rep->table_options.block_cache.get(), rep->file->file(),
+ &rep->cache_key_prefix[0], &rep->cache_key_prefix_size);
+ }
+ if (rep->table_options.persistent_cache != nullptr) {
+ GenerateCachePrefix(/*cache=*/nullptr, rep->file->file(),
+ &rep->persistent_cache_key_prefix[0],
+ &rep->persistent_cache_key_prefix_size);
+ }
+ if (rep->table_options.block_cache_compressed != nullptr) {
+ GenerateCachePrefix(rep->table_options.block_cache_compressed.get(),
+ rep->file->file(), &rep->compressed_cache_key_prefix[0],
+ &rep->compressed_cache_key_prefix_size);
+ }
+}
+
+void BlockBasedTable::GenerateCachePrefix(Cache* cc, FSRandomAccessFile* file,
+ char* buffer, size_t* size) {
+ // generate an id from the file
+ *size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
+
+ // If the prefix wasn't generated or was too long,
+ // create one from the cache.
+ if (cc != nullptr && *size == 0) {
+ char* end = EncodeVarint64(buffer, cc->NewId());
+ *size = static_cast<size_t>(end - buffer);
+ }
+}
+
+void BlockBasedTable::GenerateCachePrefix(Cache* cc, FSWritableFile* file,
+ char* buffer, size_t* size) {
+ // generate an id from the file
+ *size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
+
+ // If the prefix wasn't generated or was too long,
+ // create one from the cache.
+ if (cc != nullptr && *size == 0) {
+ char* end = EncodeVarint64(buffer, cc->NewId());
+ *size = static_cast<size_t>(end - buffer);
+ }
+}
+
+namespace {
+// Return True if table_properties has `user_prop_name` has a `true` value
+// or it doesn't contain this property (for backward compatible).
+bool IsFeatureSupported(const TableProperties& table_properties,
+ const std::string& user_prop_name, Logger* info_log) {
+ auto& props = table_properties.user_collected_properties;
+ auto pos = props.find(user_prop_name);
+ // Older version doesn't have this value set. Skip this check.
+ if (pos != props.end()) {
+ if (pos->second == kPropFalse) {
+ return false;
+ } else if (pos->second != kPropTrue) {
+ ROCKS_LOG_WARN(info_log, "Property %s has invalidate value %s",
+ user_prop_name.c_str(), pos->second.c_str());
+ }
+ }
+ return true;
+}
+
+// Caller has to ensure seqno is not nullptr.
+Status GetGlobalSequenceNumber(const TableProperties& table_properties,
+ SequenceNumber largest_seqno,
+ SequenceNumber* seqno) {
+ const auto& props = table_properties.user_collected_properties;
+ const auto version_pos = props.find(ExternalSstFilePropertyNames::kVersion);
+ const auto seqno_pos = props.find(ExternalSstFilePropertyNames::kGlobalSeqno);
+
+ *seqno = kDisableGlobalSequenceNumber;
+ if (version_pos == props.end()) {
+ if (seqno_pos != props.end()) {
+ std::array<char, 200> msg_buf;
+ // This is not an external sst file, global_seqno is not supported.
+ snprintf(
+ msg_buf.data(), msg_buf.max_size(),
+ "A non-external sst file have global seqno property with value %s",
+ seqno_pos->second.c_str());
+ return Status::Corruption(msg_buf.data());
+ }
+ return Status::OK();
+ }
+
+ uint32_t version = DecodeFixed32(version_pos->second.c_str());
+ if (version < 2) {
+ if (seqno_pos != props.end() || version != 1) {
+ std::array<char, 200> msg_buf;
+ // This is a v1 external sst file, global_seqno is not supported.
+ snprintf(msg_buf.data(), msg_buf.max_size(),
+ "An external sst file with version %u have global seqno "
+ "property with value %s",
+ version, seqno_pos->second.c_str());
+ return Status::Corruption(msg_buf.data());
+ }
+ return Status::OK();
+ }
+
+ // Since we have a plan to deprecate global_seqno, we do not return failure
+ // if seqno_pos == props.end(). We rely on version_pos to detect whether the
+ // SST is external.
+ SequenceNumber global_seqno(0);
+ if (seqno_pos != props.end()) {
+ global_seqno = DecodeFixed64(seqno_pos->second.c_str());
+ }
+ // SstTableReader open table reader with kMaxSequenceNumber as largest_seqno
+ // to denote it is unknown.
+ if (largest_seqno < kMaxSequenceNumber) {
+ if (global_seqno == 0) {
+ global_seqno = largest_seqno;
+ }
+ if (global_seqno != largest_seqno) {
+ std::array<char, 200> msg_buf;
+ snprintf(
+ msg_buf.data(), msg_buf.max_size(),
+ "An external sst file with version %u have global seqno property "
+ "with value %s, while largest seqno in the file is %llu",
+ version, seqno_pos->second.c_str(),
+ static_cast<unsigned long long>(largest_seqno));
+ return Status::Corruption(msg_buf.data());
+ }
+ }
+ *seqno = global_seqno;
+
+ if (global_seqno > kMaxSequenceNumber) {
+ std::array<char, 200> msg_buf;
+ snprintf(msg_buf.data(), msg_buf.max_size(),
+ "An external sst file with version %u have global seqno property "
+ "with value %llu, which is greater than kMaxSequenceNumber",
+ version, static_cast<unsigned long long>(global_seqno));
+ return Status::Corruption(msg_buf.data());
+ }
+
+ return Status::OK();
+}
+} // namespace
+
+Slice BlockBasedTable::GetCacheKey(const char* cache_key_prefix,
+ size_t cache_key_prefix_size,
+ const BlockHandle& handle, char* cache_key) {
+ assert(cache_key != nullptr);
+ assert(cache_key_prefix_size != 0);
+ assert(cache_key_prefix_size <= kMaxCacheKeyPrefixSize);
+ memcpy(cache_key, cache_key_prefix, cache_key_prefix_size);
+ char* end =
+ EncodeVarint64(cache_key + cache_key_prefix_size, handle.offset());
+ return Slice(cache_key, static_cast<size_t>(end - cache_key));
+}
+
+Status BlockBasedTable::Open(
+ const ImmutableCFOptions& ioptions, const EnvOptions& env_options,
+ const BlockBasedTableOptions& table_options,
+ const InternalKeyComparator& internal_comparator,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ const SliceTransform* prefix_extractor,
+ const bool prefetch_index_and_filter_in_cache, const bool skip_filters,
+ const int level, const bool immortal_table,
+ const SequenceNumber largest_seqno, TailPrefetchStats* tail_prefetch_stats,
+ BlockCacheTracer* const block_cache_tracer) {
+ table_reader->reset();
+
+ Status s;
+ Footer footer;
+ std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
+
+ // prefetch both index and filters, down to all partitions
+ const bool prefetch_all = prefetch_index_and_filter_in_cache || level == 0;
+ const bool preload_all = !table_options.cache_index_and_filter_blocks;
+
+ if (!ioptions.allow_mmap_reads) {
+ s = PrefetchTail(file.get(), file_size, tail_prefetch_stats, prefetch_all,
+ preload_all, &prefetch_buffer);
+ } else {
+ // Should not prefetch for mmap mode.
+ prefetch_buffer.reset(new FilePrefetchBuffer(
+ nullptr, 0, 0, false /* enable */, true /* track_min_offset */));
+ }
+
+ // Read in the following order:
+ // 1. Footer
+ // 2. [metaindex block]
+ // 3. [meta block: properties]
+ // 4. [meta block: range deletion tombstone]
+ // 5. [meta block: compression dictionary]
+ // 6. [meta block: index]
+ // 7. [meta block: filter]
+ s = ReadFooterFromFile(file.get(), prefetch_buffer.get(), file_size, &footer,
+ kBlockBasedTableMagicNumber);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!BlockBasedTableSupportedVersion(footer.version())) {
+ return Status::Corruption(
+ "Unknown Footer version. Maybe this file was created with newer "
+ "version of RocksDB?");
+ }
+
+ // We've successfully read the footer. We are ready to serve requests.
+ // Better not mutate rep_ after the creation. eg. internal_prefix_transform
+ // raw pointer will be used to create HashIndexReader, whose reset may
+ // access a dangling pointer.
+ BlockCacheLookupContext lookup_context{TableReaderCaller::kPrefetch};
+ Rep* rep = new BlockBasedTable::Rep(ioptions, env_options, table_options,
+ internal_comparator, skip_filters, level,
+ immortal_table);
+ rep->file = std::move(file);
+ rep->footer = footer;
+ rep->hash_index_allow_collision = table_options.hash_index_allow_collision;
+ // We need to wrap data with internal_prefix_transform to make sure it can
+ // handle prefix correctly.
+ if (prefix_extractor != nullptr) {
+ rep->internal_prefix_transform.reset(
+ new InternalKeySliceTransform(prefix_extractor));
+ }
+ SetupCacheKeyPrefix(rep);
+ std::unique_ptr<BlockBasedTable> new_table(
+ new BlockBasedTable(rep, block_cache_tracer));
+
+ // page cache options
+ rep->persistent_cache_options =
+ PersistentCacheOptions(rep->table_options.persistent_cache,
+ std::string(rep->persistent_cache_key_prefix,
+ rep->persistent_cache_key_prefix_size),
+ rep->ioptions.statistics);
+
+ // Meta-blocks are not dictionary compressed. Explicitly set the dictionary
+ // handle to null, otherwise it may be seen as uninitialized during the below
+ // meta-block reads.
+ rep->compression_dict_handle = BlockHandle::NullBlockHandle();
+
+ // Read metaindex
+ std::unique_ptr<Block> metaindex;
+ std::unique_ptr<InternalIterator> metaindex_iter;
+ s = new_table->ReadMetaIndexBlock(prefetch_buffer.get(), &metaindex,
+ &metaindex_iter);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Populates table_properties and some fields that depend on it,
+ // such as index_type.
+ s = new_table->ReadPropertiesBlock(prefetch_buffer.get(),
+ metaindex_iter.get(), largest_seqno);
+ if (!s.ok()) {
+ return s;
+ }
+ s = new_table->ReadRangeDelBlock(prefetch_buffer.get(), metaindex_iter.get(),
+ internal_comparator, &lookup_context);
+ if (!s.ok()) {
+ return s;
+ }
+ s = new_table->PrefetchIndexAndFilterBlocks(
+ prefetch_buffer.get(), metaindex_iter.get(), new_table.get(),
+ prefetch_all, table_options, level, &lookup_context);
+
+ if (s.ok()) {
+ // Update tail prefetch stats
+ assert(prefetch_buffer.get() != nullptr);
+ if (tail_prefetch_stats != nullptr) {
+ assert(prefetch_buffer->min_offset_read() < file_size);
+ tail_prefetch_stats->RecordEffectiveSize(
+ static_cast<size_t>(file_size) - prefetch_buffer->min_offset_read());
+ }
+
+ *table_reader = std::move(new_table);
+ }
+
+ return s;
+}
+
+Status BlockBasedTable::PrefetchTail(
+ RandomAccessFileReader* file, uint64_t file_size,
+ TailPrefetchStats* tail_prefetch_stats, const bool prefetch_all,
+ const bool preload_all,
+ std::unique_ptr<FilePrefetchBuffer>* prefetch_buffer) {
+ size_t tail_prefetch_size = 0;
+ if (tail_prefetch_stats != nullptr) {
+ // Multiple threads may get a 0 (no history) when running in parallel,
+ // but it will get cleared after the first of them finishes.
+ tail_prefetch_size = tail_prefetch_stats->GetSuggestedPrefetchSize();
+ }
+ if (tail_prefetch_size == 0) {
+ // Before read footer, readahead backwards to prefetch data. Do more
+ // readahead if we're going to read index/filter.
+ // TODO: This may incorrectly select small readahead in case partitioned
+ // index/filter is enabled and top-level partition pinning is enabled.
+ // That's because we need to issue readahead before we read the properties,
+ // at which point we don't yet know the index type.
+ tail_prefetch_size = prefetch_all || preload_all ? 512 * 1024 : 4 * 1024;
+ }
+ size_t prefetch_off;
+ size_t prefetch_len;
+ if (file_size < tail_prefetch_size) {
+ prefetch_off = 0;
+ prefetch_len = static_cast<size_t>(file_size);
+ } else {
+ prefetch_off = static_cast<size_t>(file_size - tail_prefetch_size);
+ prefetch_len = tail_prefetch_size;
+ }
+ TEST_SYNC_POINT_CALLBACK("BlockBasedTable::Open::TailPrefetchLen",
+ &tail_prefetch_size);
+ Status s;
+ // TODO should not have this special logic in the future.
+ if (!file->use_direct_io()) {
+ prefetch_buffer->reset(new FilePrefetchBuffer(
+ nullptr, 0, 0, false /* enable */, true /* track_min_offset */));
+ s = file->Prefetch(prefetch_off, prefetch_len);
+ } else {
+ prefetch_buffer->reset(new FilePrefetchBuffer(
+ nullptr, 0, 0, true /* enable */, true /* track_min_offset */));
+ s = (*prefetch_buffer)->Prefetch(file, prefetch_off, prefetch_len);
+ }
+ return s;
+}
+
+Status VerifyChecksum(const ChecksumType type, const char* buf, size_t len,
+ uint32_t expected) {
+ Status s;
+ uint32_t actual = 0;
+ switch (type) {
+ case kNoChecksum:
+ break;
+ case kCRC32c:
+ expected = crc32c::Unmask(expected);
+ actual = crc32c::Value(buf, len);
+ break;
+ case kxxHash:
+ actual = XXH32(buf, static_cast<int>(len), 0);
+ break;
+ case kxxHash64:
+ actual = static_cast<uint32_t>(XXH64(buf, static_cast<int>(len), 0) &
+ uint64_t{0xffffffff});
+ break;
+ default:
+ s = Status::Corruption("unknown checksum type");
+ }
+ if (s.ok() && actual != expected) {
+ s = Status::Corruption("properties block checksum mismatched");
+ }
+ return s;
+}
+
+Status BlockBasedTable::TryReadPropertiesWithGlobalSeqno(
+ FilePrefetchBuffer* prefetch_buffer, const Slice& handle_value,
+ TableProperties** table_properties) {
+ assert(table_properties != nullptr);
+ // If this is an external SST file ingested with write_global_seqno set to
+ // true, then we expect the checksum mismatch because checksum was written
+ // by SstFileWriter, but its global seqno in the properties block may have
+ // been changed during ingestion. In this case, we read the properties
+ // block, copy it to a memory buffer, change the global seqno to its
+ // original value, i.e. 0, and verify the checksum again.
+ BlockHandle props_block_handle;
+ CacheAllocationPtr tmp_buf;
+ Status s = ReadProperties(handle_value, rep_->file.get(), prefetch_buffer,
+ rep_->footer, rep_->ioptions, table_properties,
+ false /* verify_checksum */, &props_block_handle,
+ &tmp_buf, false /* compression_type_missing */,
+ nullptr /* memory_allocator */);
+ if (s.ok() && tmp_buf) {
+ const auto seqno_pos_iter =
+ (*table_properties)
+ ->properties_offsets.find(
+ ExternalSstFilePropertyNames::kGlobalSeqno);
+ size_t block_size = static_cast<size_t>(props_block_handle.size());
+ if (seqno_pos_iter != (*table_properties)->properties_offsets.end()) {
+ uint64_t global_seqno_offset = seqno_pos_iter->second;
+ EncodeFixed64(
+ tmp_buf.get() + global_seqno_offset - props_block_handle.offset(), 0);
+ }
+ uint32_t value = DecodeFixed32(tmp_buf.get() + block_size + 1);
+ s = ROCKSDB_NAMESPACE::VerifyChecksum(rep_->footer.checksum(),
+ tmp_buf.get(), block_size + 1, value);
+ }
+ return s;
+}
+
+Status BlockBasedTable::ReadPropertiesBlock(
+ FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
+ const SequenceNumber largest_seqno) {
+ bool found_properties_block = true;
+ Status s;
+ s = SeekToPropertiesBlock(meta_iter, &found_properties_block);
+
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(rep_->ioptions.info_log,
+ "Error when seeking to properties block from file: %s",
+ s.ToString().c_str());
+ } else if (found_properties_block) {
+ s = meta_iter->status();
+ TableProperties* table_properties = nullptr;
+ if (s.ok()) {
+ s = ReadProperties(
+ meta_iter->value(), rep_->file.get(), prefetch_buffer, rep_->footer,
+ rep_->ioptions, &table_properties, true /* verify_checksum */,
+ nullptr /* ret_block_handle */, nullptr /* ret_block_contents */,
+ false /* compression_type_missing */, nullptr /* memory_allocator */);
+ }
+
+ if (s.IsCorruption()) {
+ s = TryReadPropertiesWithGlobalSeqno(prefetch_buffer, meta_iter->value(),
+ &table_properties);
+ }
+ std::unique_ptr<TableProperties> props_guard;
+ if (table_properties != nullptr) {
+ props_guard.reset(table_properties);
+ }
+
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(rep_->ioptions.info_log,
+ "Encountered error while reading data from properties "
+ "block %s",
+ s.ToString().c_str());
+ } else {
+ assert(table_properties != nullptr);
+ rep_->table_properties.reset(props_guard.release());
+ rep_->blocks_maybe_compressed =
+ rep_->table_properties->compression_name !=
+ CompressionTypeToString(kNoCompression);
+ rep_->blocks_definitely_zstd_compressed =
+ (rep_->table_properties->compression_name ==
+ CompressionTypeToString(kZSTD) ||
+ rep_->table_properties->compression_name ==
+ CompressionTypeToString(kZSTDNotFinalCompression));
+ }
+ } else {
+ ROCKS_LOG_ERROR(rep_->ioptions.info_log,
+ "Cannot find Properties block from file.");
+ }
+#ifndef ROCKSDB_LITE
+ if (rep_->table_properties) {
+ ParseSliceTransform(rep_->table_properties->prefix_extractor_name,
+ &(rep_->table_prefix_extractor));
+ }
+#endif // ROCKSDB_LITE
+
+ // Read the table properties, if provided.
+ if (rep_->table_properties) {
+ rep_->whole_key_filtering &=
+ IsFeatureSupported(*(rep_->table_properties),
+ BlockBasedTablePropertyNames::kWholeKeyFiltering,
+ rep_->ioptions.info_log);
+ rep_->prefix_filtering &=
+ IsFeatureSupported(*(rep_->table_properties),
+ BlockBasedTablePropertyNames::kPrefixFiltering,
+ rep_->ioptions.info_log);
+
+ rep_->index_key_includes_seq =
+ rep_->table_properties->index_key_is_user_key == 0;
+ rep_->index_value_is_full =
+ rep_->table_properties->index_value_is_delta_encoded == 0;
+
+ // Update index_type with the true type.
+ // If table properties don't contain index type, we assume that the table
+ // is in very old format and has kBinarySearch index type.
+ auto& props = rep_->table_properties->user_collected_properties;
+ auto pos = props.find(BlockBasedTablePropertyNames::kIndexType);
+ if (pos != props.end()) {
+ rep_->index_type = static_cast<BlockBasedTableOptions::IndexType>(
+ DecodeFixed32(pos->second.c_str()));
+ }
+
+ rep_->index_has_first_key =
+ rep_->index_type == BlockBasedTableOptions::kBinarySearchWithFirstKey;
+
+ s = GetGlobalSequenceNumber(*(rep_->table_properties), largest_seqno,
+ &(rep_->global_seqno));
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(rep_->ioptions.info_log, "%s", s.ToString().c_str());
+ }
+ }
+ return s;
+}
+
+Status BlockBasedTable::ReadRangeDelBlock(
+ FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
+ const InternalKeyComparator& internal_comparator,
+ BlockCacheLookupContext* lookup_context) {
+ Status s;
+ bool found_range_del_block;
+ BlockHandle range_del_handle;
+ s = SeekToRangeDelBlock(meta_iter, &found_range_del_block, &range_del_handle);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ rep_->ioptions.info_log,
+ "Error when seeking to range delete tombstones block from file: %s",
+ s.ToString().c_str());
+ } else if (found_range_del_block && !range_del_handle.IsNull()) {
+ ReadOptions read_options;
+ std::unique_ptr<InternalIterator> iter(NewDataBlockIterator<DataBlockIter>(
+ read_options, range_del_handle,
+ /*input_iter=*/nullptr, BlockType::kRangeDeletion,
+ /*get_context=*/nullptr, lookup_context, Status(), prefetch_buffer));
+ assert(iter != nullptr);
+ s = iter->status();
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ rep_->ioptions.info_log,
+ "Encountered error while reading data from range del block %s",
+ s.ToString().c_str());
+ } else {
+ rep_->fragmented_range_dels =
+ std::make_shared<FragmentedRangeTombstoneList>(std::move(iter),
+ internal_comparator);
+ }
+ }
+ return s;
+}
+
+Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
+ FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
+ BlockBasedTable* new_table, bool prefetch_all,
+ const BlockBasedTableOptions& table_options, const int level,
+ BlockCacheLookupContext* lookup_context) {
+ Status s;
+
+ // Find filter handle and filter type
+ if (rep_->filter_policy) {
+ for (auto filter_type :
+ {Rep::FilterType::kFullFilter, Rep::FilterType::kPartitionedFilter,
+ Rep::FilterType::kBlockFilter}) {
+ std::string prefix;
+ switch (filter_type) {
+ case Rep::FilterType::kFullFilter:
+ prefix = kFullFilterBlockPrefix;
+ break;
+ case Rep::FilterType::kPartitionedFilter:
+ prefix = kPartitionedFilterBlockPrefix;
+ break;
+ case Rep::FilterType::kBlockFilter:
+ prefix = kFilterBlockPrefix;
+ break;
+ default:
+ assert(0);
+ }
+ std::string filter_block_key = prefix;
+ filter_block_key.append(rep_->filter_policy->Name());
+ if (FindMetaBlock(meta_iter, filter_block_key, &rep_->filter_handle)
+ .ok()) {
+ rep_->filter_type = filter_type;
+ break;
+ }
+ }
+ }
+
+ // Find compression dictionary handle
+ bool found_compression_dict = false;
+ s = SeekToCompressionDictBlock(meta_iter, &found_compression_dict,
+ &rep_->compression_dict_handle);
+ if (!s.ok()) {
+ return s;
+ }
+
+ BlockBasedTableOptions::IndexType index_type = rep_->index_type;
+
+ const bool use_cache = table_options.cache_index_and_filter_blocks;
+
+ // pin both index and filters, down to all partitions
+ const bool pin_all =
+ rep_->table_options.pin_l0_filter_and_index_blocks_in_cache && level == 0;
+
+ // prefetch the first level of index
+ const bool prefetch_index =
+ prefetch_all ||
+ (table_options.pin_top_level_index_and_filter &&
+ index_type == BlockBasedTableOptions::kTwoLevelIndexSearch);
+ // pin the first level of index
+ const bool pin_index =
+ pin_all || (table_options.pin_top_level_index_and_filter &&
+ index_type == BlockBasedTableOptions::kTwoLevelIndexSearch);
+
+ std::unique_ptr<IndexReader> index_reader;
+ s = new_table->CreateIndexReader(prefetch_buffer, meta_iter, use_cache,
+ prefetch_index, pin_index, lookup_context,
+ &index_reader);
+ if (!s.ok()) {
+ return s;
+ }
+
+ rep_->index_reader = std::move(index_reader);
+
+ // The partitions of partitioned index are always stored in cache. They
+ // are hence follow the configuration for pin and prefetch regardless of
+ // the value of cache_index_and_filter_blocks
+ if (prefetch_all) {
+ rep_->index_reader->CacheDependencies(pin_all);
+ }
+
+ // prefetch the first level of filter
+ const bool prefetch_filter =
+ prefetch_all ||
+ (table_options.pin_top_level_index_and_filter &&
+ rep_->filter_type == Rep::FilterType::kPartitionedFilter);
+ // Partition fitlers cannot be enabled without partition indexes
+ assert(!prefetch_filter || prefetch_index);
+ // pin the first level of filter
+ const bool pin_filter =
+ pin_all || (table_options.pin_top_level_index_and_filter &&
+ rep_->filter_type == Rep::FilterType::kPartitionedFilter);
+
+ if (rep_->filter_policy) {
+ auto filter = new_table->CreateFilterBlockReader(
+ prefetch_buffer, use_cache, prefetch_filter, pin_filter,
+ lookup_context);
+ if (filter) {
+ // Refer to the comment above about paritioned indexes always being cached
+ if (prefetch_all) {
+ filter->CacheDependencies(pin_all);
+ }
+
+ rep_->filter = std::move(filter);
+ }
+ }
+
+ if (!rep_->compression_dict_handle.IsNull()) {
+ std::unique_ptr<UncompressionDictReader> uncompression_dict_reader;
+ s = UncompressionDictReader::Create(this, prefetch_buffer, use_cache,
+ prefetch_all, pin_all, lookup_context,
+ &uncompression_dict_reader);
+ if (!s.ok()) {
+ return s;
+ }
+
+ rep_->uncompression_dict_reader = std::move(uncompression_dict_reader);
+ }
+
+ assert(s.ok());
+ return s;
+}
+
+void BlockBasedTable::SetupForCompaction() {
+ switch (rep_->ioptions.access_hint_on_compaction_start) {
+ case Options::NONE:
+ break;
+ case Options::NORMAL:
+ rep_->file->file()->Hint(FSRandomAccessFile::kNormal);
+ break;
+ case Options::SEQUENTIAL:
+ rep_->file->file()->Hint(FSRandomAccessFile::kSequential);
+ break;
+ case Options::WILLNEED:
+ rep_->file->file()->Hint(FSRandomAccessFile::kWillNeed);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+std::shared_ptr<const TableProperties> BlockBasedTable::GetTableProperties()
+ const {
+ return rep_->table_properties;
+}
+
+size_t BlockBasedTable::ApproximateMemoryUsage() const {
+ size_t usage = 0;
+ if (rep_->filter) {
+ usage += rep_->filter->ApproximateMemoryUsage();
+ }
+ if (rep_->index_reader) {
+ usage += rep_->index_reader->ApproximateMemoryUsage();
+ }
+ if (rep_->uncompression_dict_reader) {
+ usage += rep_->uncompression_dict_reader->ApproximateMemoryUsage();
+ }
+ return usage;
+}
+
+// Load the meta-index-block from the file. On success, return the loaded
+// metaindex
+// block and its iterator.
+Status BlockBasedTable::ReadMetaIndexBlock(
+ FilePrefetchBuffer* prefetch_buffer,
+ std::unique_ptr<Block>* metaindex_block,
+ std::unique_ptr<InternalIterator>* iter) {
+ // TODO(sanjay): Skip this if footer.metaindex_handle() size indicates
+ // it is an empty block.
+ std::unique_ptr<Block> metaindex;
+ Status s = ReadBlockFromFile(
+ rep_->file.get(), prefetch_buffer, rep_->footer, ReadOptions(),
+ rep_->footer.metaindex_handle(), &metaindex, rep_->ioptions,
+ true /* decompress */, true /*maybe_compressed*/, BlockType::kMetaIndex,
+ UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options,
+ kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */,
+ GetMemoryAllocator(rep_->table_options), false /* for_compaction */,
+ rep_->blocks_definitely_zstd_compressed, nullptr /* filter_policy */);
+
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(rep_->ioptions.info_log,
+ "Encountered error while reading data from properties"
+ " block %s",
+ s.ToString().c_str());
+ return s;
+ }
+
+ *metaindex_block = std::move(metaindex);
+ // meta block uses bytewise comparator.
+ iter->reset(metaindex_block->get()->NewDataIterator(BytewiseComparator(),
+ BytewiseComparator()));
+ return Status::OK();
+}
+
+template <typename TBlocklike>
+Status BlockBasedTable::GetDataBlockFromCache(
+ const Slice& block_cache_key, const Slice& compressed_block_cache_key,
+ Cache* block_cache, Cache* block_cache_compressed,
+ const ReadOptions& read_options, CachableEntry<TBlocklike>* block,
+ const UncompressionDict& uncompression_dict, BlockType block_type,
+ GetContext* get_context) const {
+ const size_t read_amp_bytes_per_bit =
+ block_type == BlockType::kData
+ ? rep_->table_options.read_amp_bytes_per_bit
+ : 0;
+ assert(block);
+ assert(block->IsEmpty());
+
+ Status s;
+ BlockContents* compressed_block = nullptr;
+ Cache::Handle* block_cache_compressed_handle = nullptr;
+
+ // Lookup uncompressed cache first
+ if (block_cache != nullptr) {
+ auto cache_handle = GetEntryFromCache(block_cache, block_cache_key,
+ block_type, get_context);
+ if (cache_handle != nullptr) {
+ block->SetCachedValue(
+ reinterpret_cast<TBlocklike*>(block_cache->Value(cache_handle)),
+ block_cache, cache_handle);
+ return s;
+ }
+ }
+
+ // If not found, search from the compressed block cache.
+ assert(block->IsEmpty());
+
+ if (block_cache_compressed == nullptr) {
+ return s;
+ }
+
+ assert(!compressed_block_cache_key.empty());
+ block_cache_compressed_handle =
+ block_cache_compressed->Lookup(compressed_block_cache_key);
+
+ Statistics* statistics = rep_->ioptions.statistics;
+
+ // if we found in the compressed cache, then uncompress and insert into
+ // uncompressed cache
+ if (block_cache_compressed_handle == nullptr) {
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSED_MISS);
+ return s;
+ }
+
+ // found compressed block
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSED_HIT);
+ compressed_block = reinterpret_cast<BlockContents*>(
+ block_cache_compressed->Value(block_cache_compressed_handle));
+ CompressionType compression_type = compressed_block->get_compression_type();
+ assert(compression_type != kNoCompression);
+
+ // Retrieve the uncompressed contents into a new buffer
+ BlockContents contents;
+ UncompressionContext context(compression_type);
+ UncompressionInfo info(context, uncompression_dict, compression_type);
+ s = UncompressBlockContents(
+ info, compressed_block->data.data(), compressed_block->data.size(),
+ &contents, rep_->table_options.format_version, rep_->ioptions,
+ GetMemoryAllocator(rep_->table_options));
+
+ // Insert uncompressed block into block cache
+ if (s.ok()) {
+ std::unique_ptr<TBlocklike> block_holder(
+ BlocklikeTraits<TBlocklike>::Create(
+ std::move(contents), rep_->get_global_seqno(block_type),
+ read_amp_bytes_per_bit, statistics,
+ rep_->blocks_definitely_zstd_compressed,
+ rep_->table_options.filter_policy.get())); // uncompressed block
+
+ if (block_cache != nullptr && block_holder->own_bytes() &&
+ read_options.fill_cache) {
+ size_t charge = block_holder->ApproximateMemoryUsage();
+ Cache::Handle* cache_handle = nullptr;
+ s = block_cache->Insert(block_cache_key, block_holder.get(), charge,
+ &DeleteCachedEntry<TBlocklike>, &cache_handle);
+ if (s.ok()) {
+ assert(cache_handle != nullptr);
+ block->SetCachedValue(block_holder.release(), block_cache,
+ cache_handle);
+
+ UpdateCacheInsertionMetrics(block_type, get_context, charge);
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES);
+ }
+ } else {
+ block->SetOwnedValue(block_holder.release());
+ }
+ }
+
+ // Release hold on compressed cache entry
+ block_cache_compressed->Release(block_cache_compressed_handle);
+ return s;
+}
+
+template <typename TBlocklike>
+Status BlockBasedTable::PutDataBlockToCache(
+ const Slice& block_cache_key, const Slice& compressed_block_cache_key,
+ Cache* block_cache, Cache* block_cache_compressed,
+ CachableEntry<TBlocklike>* cached_block, BlockContents* raw_block_contents,
+ CompressionType raw_block_comp_type,
+ const UncompressionDict& uncompression_dict, SequenceNumber seq_no,
+ MemoryAllocator* memory_allocator, BlockType block_type,
+ GetContext* get_context) const {
+ const ImmutableCFOptions& ioptions = rep_->ioptions;
+ const uint32_t format_version = rep_->table_options.format_version;
+ const size_t read_amp_bytes_per_bit =
+ block_type == BlockType::kData
+ ? rep_->table_options.read_amp_bytes_per_bit
+ : 0;
+ const Cache::Priority priority =
+ rep_->table_options.cache_index_and_filter_blocks_with_high_priority &&
+ (block_type == BlockType::kFilter ||
+ block_type == BlockType::kCompressionDictionary ||
+ block_type == BlockType::kIndex)
+ ? Cache::Priority::HIGH
+ : Cache::Priority::LOW;
+ assert(cached_block);
+ assert(cached_block->IsEmpty());
+
+ Status s;
+ Statistics* statistics = ioptions.statistics;
+
+ std::unique_ptr<TBlocklike> block_holder;
+ if (raw_block_comp_type != kNoCompression) {
+ // Retrieve the uncompressed contents into a new buffer
+ BlockContents uncompressed_block_contents;
+ UncompressionContext context(raw_block_comp_type);
+ UncompressionInfo info(context, uncompression_dict, raw_block_comp_type);
+ s = UncompressBlockContents(info, raw_block_contents->data.data(),
+ raw_block_contents->data.size(),
+ &uncompressed_block_contents, format_version,
+ ioptions, memory_allocator);
+ if (!s.ok()) {
+ return s;
+ }
+
+ block_holder.reset(BlocklikeTraits<TBlocklike>::Create(
+ std::move(uncompressed_block_contents), seq_no, read_amp_bytes_per_bit,
+ statistics, rep_->blocks_definitely_zstd_compressed,
+ rep_->table_options.filter_policy.get()));
+ } else {
+ block_holder.reset(BlocklikeTraits<TBlocklike>::Create(
+ std::move(*raw_block_contents), seq_no, read_amp_bytes_per_bit,
+ statistics, rep_->blocks_definitely_zstd_compressed,
+ rep_->table_options.filter_policy.get()));
+ }
+
+ // Insert compressed block into compressed block cache.
+ // Release the hold on the compressed cache entry immediately.
+ if (block_cache_compressed != nullptr &&
+ raw_block_comp_type != kNoCompression && raw_block_contents != nullptr &&
+ raw_block_contents->own_bytes()) {
+#ifndef NDEBUG
+ assert(raw_block_contents->is_raw_block);
+#endif // NDEBUG
+
+ // We cannot directly put raw_block_contents because this could point to
+ // an object in the stack.
+ BlockContents* block_cont_for_comp_cache =
+ new BlockContents(std::move(*raw_block_contents));
+ s = block_cache_compressed->Insert(
+ compressed_block_cache_key, block_cont_for_comp_cache,
+ block_cont_for_comp_cache->ApproximateMemoryUsage(),
+ &DeleteCachedEntry<BlockContents>);
+ if (s.ok()) {
+ // Avoid the following code to delete this cached block.
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSED_ADD);
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_COMPRESSED_ADD_FAILURES);
+ delete block_cont_for_comp_cache;
+ }
+ }
+
+ // insert into uncompressed block cache
+ if (block_cache != nullptr && block_holder->own_bytes()) {
+ size_t charge = block_holder->ApproximateMemoryUsage();
+ Cache::Handle* cache_handle = nullptr;
+ s = block_cache->Insert(block_cache_key, block_holder.get(), charge,
+ &DeleteCachedEntry<TBlocklike>, &cache_handle,
+ priority);
+ if (s.ok()) {
+ assert(cache_handle != nullptr);
+ cached_block->SetCachedValue(block_holder.release(), block_cache,
+ cache_handle);
+
+ UpdateCacheInsertionMetrics(block_type, get_context, charge);
+ } else {
+ RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES);
+ }
+ } else {
+ cached_block->SetOwnedValue(block_holder.release());
+ }
+
+ return s;
+}
+
+std::unique_ptr<FilterBlockReader> BlockBasedTable::CreateFilterBlockReader(
+ FilePrefetchBuffer* prefetch_buffer, bool use_cache, bool prefetch,
+ bool pin, BlockCacheLookupContext* lookup_context) {
+ auto& rep = rep_;
+ auto filter_type = rep->filter_type;
+ if (filter_type == Rep::FilterType::kNoFilter) {
+ return std::unique_ptr<FilterBlockReader>();
+ }
+
+ assert(rep->filter_policy);
+
+ switch (filter_type) {
+ case Rep::FilterType::kPartitionedFilter:
+ return PartitionedFilterBlockReader::Create(
+ this, prefetch_buffer, use_cache, prefetch, pin, lookup_context);
+
+ case Rep::FilterType::kBlockFilter:
+ return BlockBasedFilterBlockReader::Create(
+ this, prefetch_buffer, use_cache, prefetch, pin, lookup_context);
+
+ case Rep::FilterType::kFullFilter:
+ return FullFilterBlockReader::Create(this, prefetch_buffer, use_cache,
+ prefetch, pin, lookup_context);
+
+ default:
+ // filter_type is either kNoFilter (exited the function at the first if),
+ // or it must be covered in this switch block
+ assert(false);
+ return std::unique_ptr<FilterBlockReader>();
+ }
+}
+
+// disable_prefix_seek should be set to true when prefix_extractor found in SST
+// differs from the one in mutable_cf_options and index type is HashBasedIndex
+InternalIteratorBase<IndexValue>* BlockBasedTable::NewIndexIterator(
+ const ReadOptions& read_options, bool disable_prefix_seek,
+ IndexBlockIter* input_iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const {
+ assert(rep_ != nullptr);
+ assert(rep_->index_reader != nullptr);
+
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ return rep_->index_reader->NewIterator(read_options, disable_prefix_seek,
+ input_iter, get_context,
+ lookup_context);
+}
+
+// Convert an index iterator value (i.e., an encoded BlockHandle)
+// into an iterator over the contents of the corresponding block.
+// If input_iter is null, new a iterator
+// If input_iter is not null, update this iter and return it
+template <typename TBlockIter>
+TBlockIter* BlockBasedTable::NewDataBlockIterator(
+ const ReadOptions& ro, const BlockHandle& handle, TBlockIter* input_iter,
+ BlockType block_type, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context, Status s,
+ FilePrefetchBuffer* prefetch_buffer, bool for_compaction) const {
+ PERF_TIMER_GUARD(new_table_block_iter_nanos);
+
+ TBlockIter* iter = input_iter != nullptr ? input_iter : new TBlockIter;
+ if (!s.ok()) {
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ CachableEntry<UncompressionDict> uncompression_dict;
+ if (rep_->uncompression_dict_reader) {
+ const bool no_io = (ro.read_tier == kBlockCacheTier);
+ s = rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
+ prefetch_buffer, no_io, get_context, lookup_context,
+ &uncompression_dict);
+ if (!s.ok()) {
+ iter->Invalidate(s);
+ return iter;
+ }
+ }
+
+ const UncompressionDict& dict = uncompression_dict.GetValue()
+ ? *uncompression_dict.GetValue()
+ : UncompressionDict::GetEmptyDict();
+
+ CachableEntry<Block> block;
+ s = RetrieveBlock(prefetch_buffer, ro, handle, dict, &block, block_type,
+ get_context, lookup_context, for_compaction,
+ /* use_cache */ true);
+
+ if (!s.ok()) {
+ assert(block.IsEmpty());
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ assert(block.GetValue() != nullptr);
+
+ // Block contents are pinned and it is still pinned after the iterator
+ // is destroyed as long as cleanup functions are moved to another object,
+ // when:
+ // 1. block cache handle is set to be released in cleanup function, or
+ // 2. it's pointing to immortal source. If own_bytes is true then we are
+ // not reading data from the original source, whether immortal or not.
+ // Otherwise, the block is pinned iff the source is immortal.
+ const bool block_contents_pinned =
+ block.IsCached() ||
+ (!block.GetValue()->own_bytes() && rep_->immortal_table);
+ iter = InitBlockIterator<TBlockIter>(rep_, block.GetValue(), iter,
+ block_contents_pinned);
+
+ if (!block.IsCached()) {
+ if (!ro.fill_cache && rep_->cache_key_prefix_size != 0) {
+ // insert a dummy record to block cache to track the memory usage
+ Cache* const block_cache = rep_->table_options.block_cache.get();
+ Cache::Handle* cache_handle = nullptr;
+ // There are two other types of cache keys: 1) SST cache key added in
+ // `MaybeReadBlockAndLoadToCache` 2) dummy cache key added in
+ // `write_buffer_manager`. Use longer prefix (41 bytes) to differentiate
+ // from SST cache key(31 bytes), and use non-zero prefix to
+ // differentiate from `write_buffer_manager`
+ const size_t kExtraCacheKeyPrefix = kMaxVarint64Length * 4 + 1;
+ char cache_key[kExtraCacheKeyPrefix + kMaxVarint64Length];
+ // Prefix: use rep_->cache_key_prefix padded by 0s
+ memset(cache_key, 0, kExtraCacheKeyPrefix + kMaxVarint64Length);
+ assert(rep_->cache_key_prefix_size != 0);
+ assert(rep_->cache_key_prefix_size <= kExtraCacheKeyPrefix);
+ memcpy(cache_key, rep_->cache_key_prefix, rep_->cache_key_prefix_size);
+ char* end = EncodeVarint64(cache_key + kExtraCacheKeyPrefix,
+ next_cache_key_id_++);
+ assert(end - cache_key <=
+ static_cast<int>(kExtraCacheKeyPrefix + kMaxVarint64Length));
+ const Slice unique_key(cache_key, static_cast<size_t>(end - cache_key));
+ s = block_cache->Insert(unique_key, nullptr,
+ block.GetValue()->ApproximateMemoryUsage(),
+ nullptr, &cache_handle);
+
+ if (s.ok()) {
+ assert(cache_handle != nullptr);
+ iter->RegisterCleanup(&ForceReleaseCachedEntry, block_cache,
+ cache_handle);
+ }
+ }
+ } else {
+ iter->SetCacheHandle(block.GetCacheHandle());
+ }
+
+ block.TransferTo(iter);
+
+ return iter;
+}
+
+template <>
+DataBlockIter* BlockBasedTable::InitBlockIterator<DataBlockIter>(
+ const Rep* rep, Block* block, DataBlockIter* input_iter,
+ bool block_contents_pinned) {
+ return block->NewDataIterator(
+ &rep->internal_comparator, rep->internal_comparator.user_comparator(),
+ input_iter, rep->ioptions.statistics, block_contents_pinned);
+}
+
+template <>
+IndexBlockIter* BlockBasedTable::InitBlockIterator<IndexBlockIter>(
+ const Rep* rep, Block* block, IndexBlockIter* input_iter,
+ bool block_contents_pinned) {
+ return block->NewIndexIterator(
+ &rep->internal_comparator, rep->internal_comparator.user_comparator(),
+ input_iter, rep->ioptions.statistics, /* total_order_seek */ true,
+ rep->index_has_first_key, rep->index_key_includes_seq,
+ rep->index_value_is_full, block_contents_pinned);
+}
+
+// Convert an uncompressed data block (i.e CachableEntry<Block>)
+// into an iterator over the contents of the corresponding block.
+// If input_iter is null, new a iterator
+// If input_iter is not null, update this iter and return it
+template <typename TBlockIter>
+TBlockIter* BlockBasedTable::NewDataBlockIterator(const ReadOptions& ro,
+ CachableEntry<Block>& block,
+ TBlockIter* input_iter,
+ Status s) const {
+ PERF_TIMER_GUARD(new_table_block_iter_nanos);
+
+ TBlockIter* iter = input_iter != nullptr ? input_iter : new TBlockIter;
+ if (!s.ok()) {
+ iter->Invalidate(s);
+ return iter;
+ }
+
+ assert(block.GetValue() != nullptr);
+ // Block contents are pinned and it is still pinned after the iterator
+ // is destroyed as long as cleanup functions are moved to another object,
+ // when:
+ // 1. block cache handle is set to be released in cleanup function, or
+ // 2. it's pointing to immortal source. If own_bytes is true then we are
+ // not reading data from the original source, whether immortal or not.
+ // Otherwise, the block is pinned iff the source is immortal.
+ const bool block_contents_pinned =
+ block.IsCached() ||
+ (!block.GetValue()->own_bytes() && rep_->immortal_table);
+ iter = InitBlockIterator<TBlockIter>(rep_, block.GetValue(), iter,
+ block_contents_pinned);
+
+ if (!block.IsCached()) {
+ if (!ro.fill_cache && rep_->cache_key_prefix_size != 0) {
+ // insert a dummy record to block cache to track the memory usage
+ Cache* const block_cache = rep_->table_options.block_cache.get();
+ Cache::Handle* cache_handle = nullptr;
+ // There are two other types of cache keys: 1) SST cache key added in
+ // `MaybeReadBlockAndLoadToCache` 2) dummy cache key added in
+ // `write_buffer_manager`. Use longer prefix (41 bytes) to differentiate
+ // from SST cache key(31 bytes), and use non-zero prefix to
+ // differentiate from `write_buffer_manager`
+ const size_t kExtraCacheKeyPrefix = kMaxVarint64Length * 4 + 1;
+ char cache_key[kExtraCacheKeyPrefix + kMaxVarint64Length];
+ // Prefix: use rep_->cache_key_prefix padded by 0s
+ memset(cache_key, 0, kExtraCacheKeyPrefix + kMaxVarint64Length);
+ assert(rep_->cache_key_prefix_size != 0);
+ assert(rep_->cache_key_prefix_size <= kExtraCacheKeyPrefix);
+ memcpy(cache_key, rep_->cache_key_prefix, rep_->cache_key_prefix_size);
+ char* end = EncodeVarint64(cache_key + kExtraCacheKeyPrefix,
+ next_cache_key_id_++);
+ assert(end - cache_key <=
+ static_cast<int>(kExtraCacheKeyPrefix + kMaxVarint64Length));
+ const Slice unique_key(cache_key, static_cast<size_t>(end - cache_key));
+ s = block_cache->Insert(unique_key, nullptr,
+ block.GetValue()->ApproximateMemoryUsage(),
+ nullptr, &cache_handle);
+ if (s.ok()) {
+ assert(cache_handle != nullptr);
+ iter->RegisterCleanup(&ForceReleaseCachedEntry, block_cache,
+ cache_handle);
+ }
+ }
+ } else {
+ iter->SetCacheHandle(block.GetCacheHandle());
+ }
+
+ block.TransferTo(iter);
+ return iter;
+}
+
+// If contents is nullptr, this function looks up the block caches for the
+// data block referenced by handle, and read the block from disk if necessary.
+// If contents is non-null, it skips the cache lookup and disk read, since
+// the caller has already read it. In both cases, if ro.fill_cache is true,
+// it inserts the block into the block cache.
+template <typename TBlocklike>
+Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<TBlocklike>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ BlockContents* contents) const {
+ assert(block_entry != nullptr);
+ const bool no_io = (ro.read_tier == kBlockCacheTier);
+ Cache* block_cache = rep_->table_options.block_cache.get();
+ // No point to cache compressed blocks if it never goes away
+ Cache* block_cache_compressed =
+ rep_->immortal_table ? nullptr
+ : rep_->table_options.block_cache_compressed.get();
+
+ // First, try to get the block from the cache
+ //
+ // If either block cache is enabled, we'll try to read from it.
+ Status s;
+ char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ char compressed_cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ Slice key /* key to the block cache */;
+ Slice ckey /* key to the compressed block cache */;
+ bool is_cache_hit = false;
+ if (block_cache != nullptr || block_cache_compressed != nullptr) {
+ // create key for block cache
+ if (block_cache != nullptr) {
+ key = GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size,
+ handle, cache_key);
+ }
+
+ if (block_cache_compressed != nullptr) {
+ ckey = GetCacheKey(rep_->compressed_cache_key_prefix,
+ rep_->compressed_cache_key_prefix_size, handle,
+ compressed_cache_key);
+ }
+
+ if (!contents) {
+ s = GetDataBlockFromCache(key, ckey, block_cache, block_cache_compressed,
+ ro, block_entry, uncompression_dict, block_type,
+ get_context);
+ if (block_entry->GetValue()) {
+ // TODO(haoyu): Differentiate cache hit on uncompressed block cache and
+ // compressed block cache.
+ is_cache_hit = true;
+ }
+ }
+
+ // Can't find the block from the cache. If I/O is allowed, read from the
+ // file.
+ if (block_entry->GetValue() == nullptr && !no_io && ro.fill_cache) {
+ Statistics* statistics = rep_->ioptions.statistics;
+ const bool maybe_compressed =
+ block_type != BlockType::kFilter &&
+ block_type != BlockType::kCompressionDictionary &&
+ rep_->blocks_maybe_compressed;
+ const bool do_uncompress = maybe_compressed && !block_cache_compressed;
+ CompressionType raw_block_comp_type;
+ BlockContents raw_block_contents;
+ if (!contents) {
+ StopWatch sw(rep_->ioptions.env, statistics, READ_BLOCK_GET_MICROS);
+ BlockFetcher block_fetcher(
+ rep_->file.get(), prefetch_buffer, rep_->footer, ro, handle,
+ &raw_block_contents, rep_->ioptions, do_uncompress,
+ maybe_compressed, block_type, uncompression_dict,
+ rep_->persistent_cache_options,
+ GetMemoryAllocator(rep_->table_options),
+ GetMemoryAllocatorForCompressedBlock(rep_->table_options));
+ s = block_fetcher.ReadBlockContents();
+ raw_block_comp_type = block_fetcher.get_compression_type();
+ contents = &raw_block_contents;
+ } else {
+ raw_block_comp_type = contents->get_compression_type();
+ }
+
+ if (s.ok()) {
+ SequenceNumber seq_no = rep_->get_global_seqno(block_type);
+ // If filling cache is allowed and a cache is configured, try to put the
+ // block to the cache.
+ s = PutDataBlockToCache(
+ key, ckey, block_cache, block_cache_compressed, block_entry,
+ contents, raw_block_comp_type, uncompression_dict, seq_no,
+ GetMemoryAllocator(rep_->table_options), block_type, get_context);
+ }
+ }
+ }
+
+ // Fill lookup_context.
+ if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled() &&
+ lookup_context) {
+ size_t usage = 0;
+ uint64_t nkeys = 0;
+ if (block_entry->GetValue()) {
+ // Approximate the number of keys in the block using restarts.
+ nkeys =
+ rep_->table_options.block_restart_interval *
+ BlocklikeTraits<TBlocklike>::GetNumRestarts(*block_entry->GetValue());
+ usage = block_entry->GetValue()->ApproximateMemoryUsage();
+ }
+ TraceType trace_block_type = TraceType::kTraceMax;
+ switch (block_type) {
+ case BlockType::kData:
+ trace_block_type = TraceType::kBlockTraceDataBlock;
+ break;
+ case BlockType::kFilter:
+ trace_block_type = TraceType::kBlockTraceFilterBlock;
+ break;
+ case BlockType::kCompressionDictionary:
+ trace_block_type = TraceType::kBlockTraceUncompressionDictBlock;
+ break;
+ case BlockType::kRangeDeletion:
+ trace_block_type = TraceType::kBlockTraceRangeDeletionBlock;
+ break;
+ case BlockType::kIndex:
+ trace_block_type = TraceType::kBlockTraceIndexBlock;
+ break;
+ default:
+ // This cannot happen.
+ assert(false);
+ break;
+ }
+ bool no_insert = no_io || !ro.fill_cache;
+ if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(
+ trace_block_type, lookup_context->caller)) {
+ // Defer logging the access to Get() and MultiGet() to trace additional
+ // information, e.g., referenced_key_exist_in_block.
+
+ // Make a copy of the block key here since it will be logged later.
+ lookup_context->FillLookupContext(
+ is_cache_hit, no_insert, trace_block_type,
+ /*block_size=*/usage, /*block_key=*/key.ToString(), nkeys);
+ } else {
+ // Avoid making copy of block_key and cf_name when constructing the access
+ // record.
+ BlockCacheTraceRecord access_record(
+ rep_->ioptions.env->NowMicros(),
+ /*block_key=*/"", trace_block_type,
+ /*block_size=*/usage, rep_->cf_id_for_tracing(),
+ /*cf_name=*/"", rep_->level_for_tracing(),
+ rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit,
+ no_insert, lookup_context->get_id,
+ lookup_context->get_from_user_specified_snapshot,
+ /*referenced_key=*/"");
+ block_cache_tracer_->WriteBlockAccess(access_record, key,
+ rep_->cf_name_for_tracing(),
+ lookup_context->referenced_key);
+ }
+ }
+
+ assert(s.ok() || block_entry->GetValue() == nullptr);
+ return s;
+}
+
+// This function reads multiple data blocks from disk using Env::MultiRead()
+// and optionally inserts them into the block cache. It uses the scratch
+// buffer provided by the caller, which is contiguous. If scratch is a nullptr
+// it allocates a separate buffer for each block. Typically, if the blocks
+// need to be uncompressed and there is no compressed block cache, callers
+// can allocate a temporary scratch buffer in order to minimize memory
+// allocations.
+// If options.fill_cache is true, it inserts the blocks into cache. If its
+// false and scratch is non-null and the blocks are uncompressed, it copies
+// the buffers to heap. In any case, the CachableEntry<Block> returned will
+// own the data bytes.
+// If compression is enabled and also there is no compressed block cache,
+// the adjacent blocks are read out in one IO (combined read)
+// batch - A MultiGetRange with only those keys with unique data blocks not
+// found in cache
+// handles - A vector of block handles. Some of them me be NULL handles
+// scratch - An optional contiguous buffer to read compressed blocks into
+void BlockBasedTable::RetrieveMultipleBlocks(
+ const ReadOptions& options, const MultiGetRange* batch,
+ const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
+ autovector<Status, MultiGetContext::MAX_BATCH_SIZE>* statuses,
+ autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>* results,
+ char* scratch, const UncompressionDict& uncompression_dict) const {
+ RandomAccessFileReader* file = rep_->file.get();
+ const Footer& footer = rep_->footer;
+ const ImmutableCFOptions& ioptions = rep_->ioptions;
+ SequenceNumber global_seqno = rep_->get_global_seqno(BlockType::kData);
+ size_t read_amp_bytes_per_bit = rep_->table_options.read_amp_bytes_per_bit;
+ MemoryAllocator* memory_allocator = GetMemoryAllocator(rep_->table_options);
+
+ if (file->use_direct_io() || ioptions.allow_mmap_reads) {
+ size_t idx_in_batch = 0;
+ for (auto mget_iter = batch->begin(); mget_iter != batch->end();
+ ++mget_iter, ++idx_in_batch) {
+ BlockCacheLookupContext lookup_data_block_context(
+ TableReaderCaller::kUserMultiGet);
+ const BlockHandle& handle = (*handles)[idx_in_batch];
+ if (handle.IsNull()) {
+ continue;
+ }
+
+ (*statuses)[idx_in_batch] =
+ RetrieveBlock(nullptr, options, handle, uncompression_dict,
+ &(*results)[idx_in_batch], BlockType::kData,
+ mget_iter->get_context, &lookup_data_block_context,
+ /* for_compaction */ false, /* use_cache */ true);
+ }
+ return;
+ }
+
+ autovector<FSReadRequest, MultiGetContext::MAX_BATCH_SIZE> read_reqs;
+ size_t buf_offset = 0;
+ size_t idx_in_batch = 0;
+
+ uint64_t prev_offset = 0;
+ size_t prev_len = 0;
+ autovector<size_t, MultiGetContext::MAX_BATCH_SIZE> req_idx_for_block;
+ autovector<size_t, MultiGetContext::MAX_BATCH_SIZE> req_offset_for_block;
+ for (auto mget_iter = batch->begin(); mget_iter != batch->end();
+ ++mget_iter, ++idx_in_batch) {
+ const BlockHandle& handle = (*handles)[idx_in_batch];
+ if (handle.IsNull()) {
+ continue;
+ }
+
+ size_t prev_end = static_cast<size_t>(prev_offset) + prev_len;
+
+ // If current block is adjacent to the previous one, at the same time,
+ // compression is enabled and there is no compressed cache, we combine
+ // the two block read as one.
+ if (scratch != nullptr && prev_end == handle.offset()) {
+ req_offset_for_block.emplace_back(prev_len);
+ prev_len += block_size(handle);
+ } else {
+ // No compression or current block and previous one is not adjacent:
+ // Step 1, create a new request for previous blocks
+ if (prev_len != 0) {
+ FSReadRequest req;
+ req.offset = prev_offset;
+ req.len = prev_len;
+ if (scratch == nullptr) {
+ req.scratch = new char[req.len];
+ } else {
+ req.scratch = scratch + buf_offset;
+ buf_offset += req.len;
+ }
+ req.status = IOStatus::OK();
+ read_reqs.emplace_back(req);
+ }
+
+ // Step 2, remeber the previous block info
+ prev_offset = handle.offset();
+ prev_len = block_size(handle);
+ req_offset_for_block.emplace_back(0);
+ }
+ req_idx_for_block.emplace_back(read_reqs.size());
+ }
+ // Handle the last block and process the pending last request
+ if (prev_len != 0) {
+ FSReadRequest req;
+ req.offset = prev_offset;
+ req.len = prev_len;
+ if (scratch == nullptr) {
+ req.scratch = new char[req.len];
+ } else {
+ req.scratch = scratch + buf_offset;
+ }
+ req.status = IOStatus::OK();
+ read_reqs.emplace_back(req);
+ }
+
+ file->MultiRead(&read_reqs[0], read_reqs.size());
+
+ idx_in_batch = 0;
+ size_t valid_batch_idx = 0;
+ for (auto mget_iter = batch->begin(); mget_iter != batch->end();
+ ++mget_iter, ++idx_in_batch) {
+ const BlockHandle& handle = (*handles)[idx_in_batch];
+
+ if (handle.IsNull()) {
+ continue;
+ }
+
+ assert(valid_batch_idx < req_idx_for_block.size());
+ assert(valid_batch_idx < req_offset_for_block.size());
+ assert(req_idx_for_block[valid_batch_idx] < read_reqs.size());
+ size_t& req_idx = req_idx_for_block[valid_batch_idx];
+ size_t& req_offset = req_offset_for_block[valid_batch_idx];
+ valid_batch_idx++;
+ FSReadRequest& req = read_reqs[req_idx];
+ Status s = req.status;
+ if (s.ok()) {
+ if (req.result.size() != req.len) {
+ s = Status::Corruption(
+ "truncated block read from " + rep_->file->file_name() +
+ " offset " + ToString(handle.offset()) + ", expected " +
+ ToString(req.len) + " bytes, got " + ToString(req.result.size()));
+ }
+ }
+
+ BlockContents raw_block_contents;
+ size_t cur_read_end = req_offset + block_size(handle);
+ if (cur_read_end > req.result.size()) {
+ s = Status::Corruption(
+ "truncated block read from " + rep_->file->file_name() + " offset " +
+ ToString(handle.offset()) + ", expected " + ToString(req.len) +
+ " bytes, got " + ToString(req.result.size()));
+ }
+
+ bool blocks_share_read_buffer = (req.result.size() != block_size(handle));
+ if (s.ok()) {
+ if (scratch == nullptr && !blocks_share_read_buffer) {
+ // We allocated a buffer for this block. Give ownership of it to
+ // BlockContents so it can free the memory
+ assert(req.result.data() == req.scratch);
+ std::unique_ptr<char[]> raw_block(req.scratch + req_offset);
+ raw_block_contents = BlockContents(std::move(raw_block), handle.size());
+ } else {
+ // We used the scratch buffer which are shared by the blocks.
+ // raw_block_contents does not have the ownership.
+ raw_block_contents =
+ BlockContents(Slice(req.scratch + req_offset, handle.size()));
+ }
+
+#ifndef NDEBUG
+ raw_block_contents.is_raw_block = true;
+#endif
+ if (options.verify_checksums) {
+ PERF_TIMER_GUARD(block_checksum_time);
+ const char* data = req.result.data();
+ uint32_t expected =
+ DecodeFixed32(data + req_offset + handle.size() + 1);
+ // Since the scratch might be shared. the offset of the data block in
+ // the buffer might not be 0. req.result.data() only point to the
+ // begin address of each read request, we need to add the offset
+ // in each read request. Checksum is stored in the block trailer,
+ // which is handle.size() + 1.
+ s = ROCKSDB_NAMESPACE::VerifyChecksum(footer.checksum(),
+ req.result.data() + req_offset,
+ handle.size() + 1, expected);
+ TEST_SYNC_POINT_CALLBACK("RetrieveMultipleBlocks:VerifyChecksum", &s);
+ }
+ }
+
+ if (s.ok()) {
+ // It handles a rare case: compression is set and these is no compressed
+ // cache (enable combined read). In this case, the scratch != nullptr.
+ // At the same time, some blocks are actually not compressed,
+ // since its compression space saving is smaller than the threshold. In
+ // this case, if the block shares the scratch memory, we need to copy it
+ // to the heap such that it can be added to the regular block cache.
+ CompressionType compression_type =
+ raw_block_contents.get_compression_type();
+ if (scratch != nullptr && compression_type == kNoCompression) {
+ Slice raw = Slice(req.scratch + req_offset, block_size(handle));
+ raw_block_contents = BlockContents(
+ CopyBufferToHeap(GetMemoryAllocator(rep_->table_options), raw),
+ handle.size());
+#ifndef NDEBUG
+ raw_block_contents.is_raw_block = true;
+#endif
+ }
+ }
+
+ if (s.ok()) {
+ if (options.fill_cache) {
+ BlockCacheLookupContext lookup_data_block_context(
+ TableReaderCaller::kUserMultiGet);
+ CachableEntry<Block>* block_entry = &(*results)[idx_in_batch];
+ // MaybeReadBlockAndLoadToCache will insert into the block caches if
+ // necessary. Since we're passing the raw block contents, it will
+ // avoid looking up the block cache
+ s = MaybeReadBlockAndLoadToCache(
+ nullptr, options, handle, uncompression_dict, block_entry,
+ BlockType::kData, mget_iter->get_context,
+ &lookup_data_block_context, &raw_block_contents);
+
+ // block_entry value could be null if no block cache is present, i.e
+ // BlockBasedTableOptions::no_block_cache is true and no compressed
+ // block cache is configured. In that case, fall
+ // through and set up the block explicitly
+ if (block_entry->GetValue() != nullptr) {
+ continue;
+ }
+ }
+
+ CompressionType compression_type =
+ raw_block_contents.get_compression_type();
+ BlockContents contents;
+ if (compression_type != kNoCompression) {
+ UncompressionContext context(compression_type);
+ UncompressionInfo info(context, uncompression_dict, compression_type);
+ s = UncompressBlockContents(info, req.result.data() + req_offset,
+ handle.size(), &contents, footer.version(),
+ rep_->ioptions, memory_allocator);
+ } else {
+ // There are two cases here: 1) caller uses the scratch buffer; 2) we
+ // use the requst buffer. If scratch buffer is used, we ensure that
+ // all raw blocks are copyed to the heap as single blocks. If scratch
+ // buffer is not used, we also have no combined read, so the raw
+ // block can be used directly.
+ contents = std::move(raw_block_contents);
+ }
+ if (s.ok()) {
+ (*results)[idx_in_batch].SetOwnedValue(
+ new Block(std::move(contents), global_seqno, read_amp_bytes_per_bit,
+ ioptions.statistics));
+ }
+ }
+ (*statuses)[idx_in_batch] = s;
+ }
+}
+
+template <typename TBlocklike>
+Status BlockBasedTable::RetrieveBlock(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<TBlocklike>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const {
+ assert(block_entry);
+ assert(block_entry->IsEmpty());
+
+ Status s;
+ if (use_cache) {
+ s = MaybeReadBlockAndLoadToCache(prefetch_buffer, ro, handle,
+ uncompression_dict, block_entry,
+ block_type, get_context, lookup_context,
+ /*contents=*/nullptr);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (block_entry->GetValue() != nullptr) {
+ assert(s.ok());
+ return s;
+ }
+ }
+
+ assert(block_entry->IsEmpty());
+
+ const bool no_io = ro.read_tier == kBlockCacheTier;
+ if (no_io) {
+ return Status::Incomplete("no blocking io");
+ }
+
+ const bool maybe_compressed =
+ block_type != BlockType::kFilter &&
+ block_type != BlockType::kCompressionDictionary &&
+ rep_->blocks_maybe_compressed;
+ const bool do_uncompress = maybe_compressed;
+ std::unique_ptr<TBlocklike> block;
+
+ {
+ StopWatch sw(rep_->ioptions.env, rep_->ioptions.statistics,
+ READ_BLOCK_GET_MICROS);
+ s = ReadBlockFromFile(
+ rep_->file.get(), prefetch_buffer, rep_->footer, ro, handle, &block,
+ rep_->ioptions, do_uncompress, maybe_compressed, block_type,
+ uncompression_dict, rep_->persistent_cache_options,
+ rep_->get_global_seqno(block_type),
+ block_type == BlockType::kData
+ ? rep_->table_options.read_amp_bytes_per_bit
+ : 0,
+ GetMemoryAllocator(rep_->table_options), for_compaction,
+ rep_->blocks_definitely_zstd_compressed,
+ rep_->table_options.filter_policy.get());
+ }
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ block_entry->SetOwnedValue(block.release());
+
+ assert(s.ok());
+ return s;
+}
+
+// Explicitly instantiate templates for both "blocklike" types we use.
+// This makes it possible to keep the template definitions in the .cc file.
+template Status BlockBasedTable::RetrieveBlock<BlockContents>(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<BlockContents>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const;
+
+template Status BlockBasedTable::RetrieveBlock<ParsedFullFilterBlock>(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<ParsedFullFilterBlock>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const;
+
+template Status BlockBasedTable::RetrieveBlock<Block>(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<Block>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const;
+
+template Status BlockBasedTable::RetrieveBlock<UncompressionDict>(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<UncompressionDict>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const;
+
+BlockBasedTable::PartitionedIndexIteratorState::PartitionedIndexIteratorState(
+ const BlockBasedTable* table,
+ std::unordered_map<uint64_t, CachableEntry<Block>>* block_map)
+ : table_(table), block_map_(block_map) {}
+
+InternalIteratorBase<IndexValue>*
+BlockBasedTable::PartitionedIndexIteratorState::NewSecondaryIterator(
+ const BlockHandle& handle) {
+ // Return a block iterator on the index partition
+ auto block = block_map_->find(handle.offset());
+ // This is a possible scenario since block cache might not have had space
+ // for the partition
+ if (block != block_map_->end()) {
+ const Rep* rep = table_->get_rep();
+ assert(rep);
+
+ Statistics* kNullStats = nullptr;
+ // We don't return pinned data from index blocks, so no need
+ // to set `block_contents_pinned`.
+ return block->second.GetValue()->NewIndexIterator(
+ &rep->internal_comparator, rep->internal_comparator.user_comparator(),
+ nullptr, kNullStats, true, rep->index_has_first_key,
+ rep->index_key_includes_seq, rep->index_value_is_full);
+ }
+ // Create an empty iterator
+ return new IndexBlockIter();
+}
+
+// This will be broken if the user specifies an unusual implementation
+// of Options.comparator, or if the user specifies an unusual
+// definition of prefixes in BlockBasedTableOptions.filter_policy.
+// In particular, we require the following three properties:
+//
+// 1) key.starts_with(prefix(key))
+// 2) Compare(prefix(key), key) <= 0.
+// 3) If Compare(key1, key2) <= 0, then Compare(prefix(key1), prefix(key2)) <= 0
+//
+// Otherwise, this method guarantees no I/O will be incurred.
+//
+// REQUIRES: this method shouldn't be called while the DB lock is held.
+bool BlockBasedTable::PrefixMayMatch(
+ const Slice& internal_key, const ReadOptions& read_options,
+ const SliceTransform* options_prefix_extractor,
+ const bool need_upper_bound_check,
+ BlockCacheLookupContext* lookup_context) const {
+ if (!rep_->filter_policy) {
+ return true;
+ }
+
+ const SliceTransform* prefix_extractor;
+
+ if (rep_->table_prefix_extractor == nullptr) {
+ if (need_upper_bound_check) {
+ return true;
+ }
+ prefix_extractor = options_prefix_extractor;
+ } else {
+ prefix_extractor = rep_->table_prefix_extractor.get();
+ }
+ auto user_key = ExtractUserKey(internal_key);
+ if (!prefix_extractor->InDomain(user_key)) {
+ return true;
+ }
+
+ bool may_match = true;
+ Status s;
+
+ // First, try check with full filter
+ FilterBlockReader* const filter = rep_->filter.get();
+ bool filter_checked = true;
+ if (filter != nullptr) {
+ if (!filter->IsBlockBased()) {
+ const Slice* const const_ikey_ptr = &internal_key;
+ may_match = filter->RangeMayExist(
+ read_options.iterate_upper_bound, user_key, prefix_extractor,
+ rep_->internal_comparator.user_comparator(), const_ikey_ptr,
+ &filter_checked, need_upper_bound_check, lookup_context);
+ } else {
+ // if prefix_extractor changed for block based filter, skip filter
+ if (need_upper_bound_check) {
+ return true;
+ }
+ auto prefix = prefix_extractor->Transform(user_key);
+ InternalKey internal_key_prefix(prefix, kMaxSequenceNumber, kTypeValue);
+ auto internal_prefix = internal_key_prefix.Encode();
+
+ // To prevent any io operation in this method, we set `read_tier` to make
+ // sure we always read index or filter only when they have already been
+ // loaded to memory.
+ ReadOptions no_io_read_options;
+ no_io_read_options.read_tier = kBlockCacheTier;
+
+ // Then, try find it within each block
+ // we already know prefix_extractor and prefix_extractor_name must match
+ // because `CheckPrefixMayMatch` first checks `check_filter_ == true`
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter(NewIndexIterator(
+ no_io_read_options,
+ /*need_upper_bound_check=*/false, /*input_iter=*/nullptr,
+ /*get_context=*/nullptr, lookup_context));
+ iiter->Seek(internal_prefix);
+
+ if (!iiter->Valid()) {
+ // we're past end of file
+ // if it's incomplete, it means that we avoided I/O
+ // and we're not really sure that we're past the end
+ // of the file
+ may_match = iiter->status().IsIncomplete();
+ } else if ((rep_->index_key_includes_seq ? ExtractUserKey(iiter->key())
+ : iiter->key())
+ .starts_with(ExtractUserKey(internal_prefix))) {
+ // we need to check for this subtle case because our only
+ // guarantee is that "the key is a string >= last key in that data
+ // block" according to the doc/table_format.txt spec.
+ //
+ // Suppose iiter->key() starts with the desired prefix; it is not
+ // necessarily the case that the corresponding data block will
+ // contain the prefix, since iiter->key() need not be in the
+ // block. However, the next data block may contain the prefix, so
+ // we return true to play it safe.
+ may_match = true;
+ } else if (filter->IsBlockBased()) {
+ // iiter->key() does NOT start with the desired prefix. Because
+ // Seek() finds the first key that is >= the seek target, this
+ // means that iiter->key() > prefix. Thus, any data blocks coming
+ // after the data block corresponding to iiter->key() cannot
+ // possibly contain the key. Thus, the corresponding data block
+ // is the only on could potentially contain the prefix.
+ BlockHandle handle = iiter->value().handle;
+ may_match = filter->PrefixMayMatch(
+ prefix, prefix_extractor, handle.offset(), /*no_io=*/false,
+ /*const_key_ptr=*/nullptr, /*get_context=*/nullptr, lookup_context);
+ }
+ }
+ }
+
+ if (filter_checked) {
+ Statistics* statistics = rep_->ioptions.statistics;
+ RecordTick(statistics, BLOOM_FILTER_PREFIX_CHECKED);
+ if (!may_match) {
+ RecordTick(statistics, BLOOM_FILTER_PREFIX_USEFUL);
+ }
+ }
+
+ return may_match;
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::Seek(const Slice& target) {
+ SeekImpl(&target);
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::SeekToFirst() {
+ SeekImpl(nullptr);
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::SeekImpl(
+ const Slice* target) {
+ is_out_of_bound_ = false;
+ is_at_first_key_from_index_ = false;
+ if (target && !CheckPrefixMayMatch(*target, IterDirection::kForward)) {
+ ResetDataIter();
+ return;
+ }
+
+ bool need_seek_index = true;
+ if (block_iter_points_to_real_block_ && block_iter_.Valid()) {
+ // Reseek.
+ prev_block_offset_ = index_iter_->value().handle.offset();
+
+ if (target) {
+ // We can avoid an index seek if:
+ // 1. The new seek key is larger than the current key
+ // 2. The new seek key is within the upper bound of the block
+ // Since we don't necessarily know the internal key for either
+ // the current key or the upper bound, we check user keys and
+ // exclude the equality case. Considering internal keys can
+ // improve for the boundary cases, but it would complicate the
+ // code.
+ if (user_comparator_.Compare(ExtractUserKey(*target),
+ block_iter_.user_key()) > 0 &&
+ user_comparator_.Compare(ExtractUserKey(*target),
+ index_iter_->user_key()) < 0) {
+ need_seek_index = false;
+ }
+ }
+ }
+
+ if (need_seek_index) {
+ if (target) {
+ index_iter_->Seek(*target);
+ } else {
+ index_iter_->SeekToFirst();
+ }
+
+ if (!index_iter_->Valid()) {
+ ResetDataIter();
+ return;
+ }
+ }
+
+ IndexValue v = index_iter_->value();
+ const bool same_block = block_iter_points_to_real_block_ &&
+ v.handle.offset() == prev_block_offset_;
+
+ // TODO(kolmike): Remove the != kBlockCacheTier condition.
+ if (!v.first_internal_key.empty() && !same_block &&
+ (!target || icomp_.Compare(*target, v.first_internal_key) <= 0) &&
+ read_options_.read_tier != kBlockCacheTier) {
+ // Index contains the first key of the block, and it's >= target.
+ // We can defer reading the block.
+ is_at_first_key_from_index_ = true;
+ // ResetDataIter() will invalidate block_iter_. Thus, there is no need to
+ // call CheckDataBlockWithinUpperBound() to check for iterate_upper_bound
+ // as that will be done later when the data block is actually read.
+ ResetDataIter();
+ } else {
+ // Need to use the data block.
+ if (!same_block) {
+ InitDataBlock();
+ } else {
+ // When the user does a reseek, the iterate_upper_bound might have
+ // changed. CheckDataBlockWithinUpperBound() needs to be called
+ // explicitly if the reseek ends up in the same data block.
+ // If the reseek ends up in a different block, InitDataBlock() will do
+ // the iterator upper bound check.
+ CheckDataBlockWithinUpperBound();
+ }
+
+ if (target) {
+ block_iter_.Seek(*target);
+ } else {
+ block_iter_.SeekToFirst();
+ }
+ FindKeyForward();
+ }
+
+ CheckOutOfBound();
+
+ if (target) {
+ assert(!Valid() || ((block_type_ == BlockType::kIndex &&
+ !table_->get_rep()->index_key_includes_seq)
+ ? (user_comparator_.Compare(ExtractUserKey(*target),
+ key()) <= 0)
+ : (icomp_.Compare(*target, key()) <= 0)));
+ }
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::SeekForPrev(
+ const Slice& target) {
+ is_out_of_bound_ = false;
+ is_at_first_key_from_index_ = false;
+ // For now totally disable prefix seek in auto prefix mode because we don't
+ // have logic
+ if (!CheckPrefixMayMatch(target, IterDirection::kBackward)) {
+ ResetDataIter();
+ return;
+ }
+
+ SavePrevIndexValue();
+
+ // Call Seek() rather than SeekForPrev() in the index block, because the
+ // target data block will likely to contain the position for `target`, the
+ // same as Seek(), rather than than before.
+ // For example, if we have three data blocks, each containing two keys:
+ // [2, 4] [6, 8] [10, 12]
+ // (the keys in the index block would be [4, 8, 12])
+ // and the user calls SeekForPrev(7), we need to go to the second block,
+ // just like if they call Seek(7).
+ // The only case where the block is difference is when they seek to a position
+ // in the boundary. For example, if they SeekForPrev(5), we should go to the
+ // first block, rather than the second. However, we don't have the information
+ // to distinguish the two unless we read the second block. In this case, we'll
+ // end up with reading two blocks.
+ index_iter_->Seek(target);
+
+ if (!index_iter_->Valid()) {
+ auto seek_status = index_iter_->status();
+ // Check for IO error
+ if (!seek_status.IsNotFound() && !seek_status.ok()) {
+ ResetDataIter();
+ return;
+ }
+
+ // With prefix index, Seek() returns NotFound if the prefix doesn't exist
+ if (seek_status.IsNotFound()) {
+ // Any key less than the target is fine for prefix seek
+ ResetDataIter();
+ return;
+ } else {
+ index_iter_->SeekToLast();
+ }
+ // Check for IO error
+ if (!index_iter_->Valid()) {
+ ResetDataIter();
+ return;
+ }
+ }
+
+ InitDataBlock();
+
+ block_iter_.SeekForPrev(target);
+
+ FindKeyBackward();
+ CheckDataBlockWithinUpperBound();
+ assert(!block_iter_.Valid() ||
+ icomp_.Compare(target, block_iter_.key()) >= 0);
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::SeekToLast() {
+ is_out_of_bound_ = false;
+ is_at_first_key_from_index_ = false;
+ SavePrevIndexValue();
+ index_iter_->SeekToLast();
+ if (!index_iter_->Valid()) {
+ ResetDataIter();
+ return;
+ }
+ InitDataBlock();
+ block_iter_.SeekToLast();
+ FindKeyBackward();
+ CheckDataBlockWithinUpperBound();
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::Next() {
+ if (is_at_first_key_from_index_ && !MaterializeCurrentBlock()) {
+ return;
+ }
+ assert(block_iter_points_to_real_block_);
+ block_iter_.Next();
+ FindKeyForward();
+ CheckOutOfBound();
+}
+
+template <class TBlockIter, typename TValue>
+bool BlockBasedTableIterator<TBlockIter, TValue>::NextAndGetResult(
+ IterateResult* result) {
+ Next();
+ bool is_valid = Valid();
+ if (is_valid) {
+ result->key = key();
+ result->may_be_out_of_upper_bound = MayBeOutOfUpperBound();
+ }
+ return is_valid;
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::Prev() {
+ if (is_at_first_key_from_index_) {
+ is_at_first_key_from_index_ = false;
+
+ index_iter_->Prev();
+ if (!index_iter_->Valid()) {
+ return;
+ }
+
+ InitDataBlock();
+ block_iter_.SeekToLast();
+ } else {
+ assert(block_iter_points_to_real_block_);
+ block_iter_.Prev();
+ }
+
+ FindKeyBackward();
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::InitDataBlock() {
+ BlockHandle data_block_handle = index_iter_->value().handle;
+ if (!block_iter_points_to_real_block_ ||
+ data_block_handle.offset() != prev_block_offset_ ||
+ // if previous attempt of reading the block missed cache, try again
+ block_iter_.status().IsIncomplete()) {
+ if (block_iter_points_to_real_block_) {
+ ResetDataIter();
+ }
+ auto* rep = table_->get_rep();
+
+ // Prefetch additional data for range scans (iterators). Enabled only for
+ // user reads.
+ // Implicit auto readahead:
+ // Enabled after 2 sequential IOs when ReadOptions.readahead_size == 0.
+ // Explicit user requested readahead:
+ // Enabled from the very first IO when ReadOptions.readahead_size is set.
+ if (lookup_context_.caller != TableReaderCaller::kCompaction) {
+ if (read_options_.readahead_size == 0) {
+ // Implicit auto readahead
+ num_file_reads_++;
+ if (num_file_reads_ >
+ BlockBasedTable::kMinNumFileReadsToStartAutoReadahead) {
+ if (!rep->file->use_direct_io() &&
+ (data_block_handle.offset() +
+ static_cast<size_t>(block_size(data_block_handle)) >
+ readahead_limit_)) {
+ // Buffered I/O
+ // Discarding the return status of Prefetch calls intentionally, as
+ // we can fallback to reading from disk if Prefetch fails.
+ rep->file->Prefetch(data_block_handle.offset(), readahead_size_);
+ readahead_limit_ = static_cast<size_t>(data_block_handle.offset() +
+ readahead_size_);
+ // Keep exponentially increasing readahead size until
+ // kMaxAutoReadaheadSize.
+ readahead_size_ = std::min(BlockBasedTable::kMaxAutoReadaheadSize,
+ readahead_size_ * 2);
+ } else if (rep->file->use_direct_io() && !prefetch_buffer_) {
+ // Direct I/O
+ // Let FilePrefetchBuffer take care of the readahead.
+ rep->CreateFilePrefetchBuffer(
+ BlockBasedTable::kInitAutoReadaheadSize,
+ BlockBasedTable::kMaxAutoReadaheadSize, &prefetch_buffer_);
+ }
+ }
+ } else if (!prefetch_buffer_) {
+ // Explicit user requested readahead
+ // The actual condition is:
+ // if (read_options_.readahead_size != 0 && !prefetch_buffer_)
+ rep->CreateFilePrefetchBuffer(read_options_.readahead_size,
+ read_options_.readahead_size,
+ &prefetch_buffer_);
+ }
+ } else if (!prefetch_buffer_) {
+ rep->CreateFilePrefetchBuffer(compaction_readahead_size_,
+ compaction_readahead_size_,
+ &prefetch_buffer_);
+ }
+
+ Status s;
+ table_->NewDataBlockIterator<TBlockIter>(
+ read_options_, data_block_handle, &block_iter_, block_type_,
+ /*get_context=*/nullptr, &lookup_context_, s, prefetch_buffer_.get(),
+ /*for_compaction=*/lookup_context_.caller ==
+ TableReaderCaller::kCompaction);
+ block_iter_points_to_real_block_ = true;
+ CheckDataBlockWithinUpperBound();
+ }
+}
+
+template <class TBlockIter, typename TValue>
+bool BlockBasedTableIterator<TBlockIter, TValue>::MaterializeCurrentBlock() {
+ assert(is_at_first_key_from_index_);
+ assert(!block_iter_points_to_real_block_);
+ assert(index_iter_->Valid());
+
+ is_at_first_key_from_index_ = false;
+ InitDataBlock();
+ assert(block_iter_points_to_real_block_);
+ block_iter_.SeekToFirst();
+
+ if (!block_iter_.Valid() ||
+ icomp_.Compare(block_iter_.key(),
+ index_iter_->value().first_internal_key) != 0) {
+ // Uh oh.
+ block_iter_.Invalidate(Status::Corruption(
+ "first key in index doesn't match first key in block"));
+ return false;
+ }
+
+ return true;
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::FindKeyForward() {
+ // This method's code is kept short to make it likely to be inlined.
+
+ assert(!is_out_of_bound_);
+ assert(block_iter_points_to_real_block_);
+
+ if (!block_iter_.Valid()) {
+ // This is the only call site of FindBlockForward(), but it's extracted into
+ // a separate method to keep FindKeyForward() short and likely to be
+ // inlined. When transitioning to a different block, we call
+ // FindBlockForward(), which is much longer and is probably not inlined.
+ FindBlockForward();
+ } else {
+ // This is the fast path that avoids a function call.
+ }
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::FindBlockForward() {
+ // TODO the while loop inherits from two-level-iterator. We don't know
+ // whether a block can be empty so it can be replaced by an "if".
+ do {
+ if (!block_iter_.status().ok()) {
+ return;
+ }
+ // Whether next data block is out of upper bound, if there is one.
+ const bool next_block_is_out_of_bound =
+ read_options_.iterate_upper_bound != nullptr &&
+ block_iter_points_to_real_block_ && !data_block_within_upper_bound_;
+ assert(!next_block_is_out_of_bound ||
+ user_comparator_.Compare(*read_options_.iterate_upper_bound,
+ index_iter_->user_key()) <= 0);
+ ResetDataIter();
+ index_iter_->Next();
+ if (next_block_is_out_of_bound) {
+ // The next block is out of bound. No need to read it.
+ TEST_SYNC_POINT_CALLBACK("BlockBasedTableIterator:out_of_bound", nullptr);
+ // We need to make sure this is not the last data block before setting
+ // is_out_of_bound_, since the index key for the last data block can be
+ // larger than smallest key of the next file on the same level.
+ if (index_iter_->Valid()) {
+ is_out_of_bound_ = true;
+ }
+ return;
+ }
+
+ if (!index_iter_->Valid()) {
+ return;
+ }
+
+ IndexValue v = index_iter_->value();
+
+ // TODO(kolmike): Remove the != kBlockCacheTier condition.
+ if (!v.first_internal_key.empty() &&
+ read_options_.read_tier != kBlockCacheTier) {
+ // Index contains the first key of the block. Defer reading the block.
+ is_at_first_key_from_index_ = true;
+ return;
+ }
+
+ InitDataBlock();
+ block_iter_.SeekToFirst();
+ } while (!block_iter_.Valid());
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::FindKeyBackward() {
+ while (!block_iter_.Valid()) {
+ if (!block_iter_.status().ok()) {
+ return;
+ }
+
+ ResetDataIter();
+ index_iter_->Prev();
+
+ if (index_iter_->Valid()) {
+ InitDataBlock();
+ block_iter_.SeekToLast();
+ } else {
+ return;
+ }
+ }
+
+ // We could have check lower bound here too, but we opt not to do it for
+ // code simplicity.
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter, TValue>::CheckOutOfBound() {
+ if (read_options_.iterate_upper_bound != nullptr && Valid()) {
+ is_out_of_bound_ = user_comparator_.Compare(
+ *read_options_.iterate_upper_bound, user_key()) <= 0;
+ }
+}
+
+template <class TBlockIter, typename TValue>
+void BlockBasedTableIterator<TBlockIter,
+ TValue>::CheckDataBlockWithinUpperBound() {
+ if (read_options_.iterate_upper_bound != nullptr &&
+ block_iter_points_to_real_block_) {
+ data_block_within_upper_bound_ =
+ (user_comparator_.Compare(*read_options_.iterate_upper_bound,
+ index_iter_->user_key()) > 0);
+ }
+}
+
+InternalIterator* BlockBasedTable::NewIterator(
+ const ReadOptions& read_options, const SliceTransform* prefix_extractor,
+ Arena* arena, bool skip_filters, TableReaderCaller caller,
+ size_t compaction_readahead_size) {
+ BlockCacheLookupContext lookup_context{caller};
+ bool need_upper_bound_check =
+ read_options.auto_prefix_mode ||
+ PrefixExtractorChanged(rep_->table_properties.get(), prefix_extractor);
+ if (arena == nullptr) {
+ return new BlockBasedTableIterator<DataBlockIter>(
+ this, read_options, rep_->internal_comparator,
+ NewIndexIterator(
+ read_options,
+ need_upper_bound_check &&
+ rep_->index_type == BlockBasedTableOptions::kHashSearch,
+ /*input_iter=*/nullptr, /*get_context=*/nullptr, &lookup_context),
+ !skip_filters && !read_options.total_order_seek &&
+ prefix_extractor != nullptr,
+ need_upper_bound_check, prefix_extractor, BlockType::kData, caller,
+ compaction_readahead_size);
+ } else {
+ auto* mem =
+ arena->AllocateAligned(sizeof(BlockBasedTableIterator<DataBlockIter>));
+ return new (mem) BlockBasedTableIterator<DataBlockIter>(
+ this, read_options, rep_->internal_comparator,
+ NewIndexIterator(
+ read_options,
+ need_upper_bound_check &&
+ rep_->index_type == BlockBasedTableOptions::kHashSearch,
+ /*input_iter=*/nullptr, /*get_context=*/nullptr, &lookup_context),
+ !skip_filters && !read_options.total_order_seek &&
+ prefix_extractor != nullptr,
+ need_upper_bound_check, prefix_extractor, BlockType::kData, caller,
+ compaction_readahead_size);
+ }
+}
+
+FragmentedRangeTombstoneIterator* BlockBasedTable::NewRangeTombstoneIterator(
+ const ReadOptions& read_options) {
+ if (rep_->fragmented_range_dels == nullptr) {
+ return nullptr;
+ }
+ SequenceNumber snapshot = kMaxSequenceNumber;
+ if (read_options.snapshot != nullptr) {
+ snapshot = read_options.snapshot->GetSequenceNumber();
+ }
+ return new FragmentedRangeTombstoneIterator(
+ rep_->fragmented_range_dels, rep_->internal_comparator, snapshot);
+}
+
+bool BlockBasedTable::FullFilterKeyMayMatch(
+ const ReadOptions& read_options, FilterBlockReader* filter,
+ const Slice& internal_key, const bool no_io,
+ const SliceTransform* prefix_extractor, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const {
+ if (filter == nullptr || filter->IsBlockBased()) {
+ return true;
+ }
+ Slice user_key = ExtractUserKey(internal_key);
+ const Slice* const const_ikey_ptr = &internal_key;
+ bool may_match = true;
+ if (rep_->whole_key_filtering) {
+ size_t ts_sz =
+ rep_->internal_comparator.user_comparator()->timestamp_size();
+ Slice user_key_without_ts = StripTimestampFromUserKey(user_key, ts_sz);
+ may_match =
+ filter->KeyMayMatch(user_key_without_ts, prefix_extractor, kNotValid,
+ no_io, const_ikey_ptr, get_context, lookup_context);
+ } else if (!read_options.total_order_seek && prefix_extractor &&
+ rep_->table_properties->prefix_extractor_name.compare(
+ prefix_extractor->Name()) == 0 &&
+ prefix_extractor->InDomain(user_key) &&
+ !filter->PrefixMayMatch(prefix_extractor->Transform(user_key),
+ prefix_extractor, kNotValid, no_io,
+ const_ikey_ptr, get_context,
+ lookup_context)) {
+ may_match = false;
+ }
+ if (may_match) {
+ RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_FULL_POSITIVE);
+ PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, rep_->level);
+ }
+ return may_match;
+}
+
+void BlockBasedTable::FullFilterKeysMayMatch(
+ const ReadOptions& read_options, FilterBlockReader* filter,
+ MultiGetRange* range, const bool no_io,
+ const SliceTransform* prefix_extractor,
+ BlockCacheLookupContext* lookup_context) const {
+ if (filter == nullptr || filter->IsBlockBased()) {
+ return;
+ }
+ if (rep_->whole_key_filtering) {
+ filter->KeysMayMatch(range, prefix_extractor, kNotValid, no_io,
+ lookup_context);
+ } else if (!read_options.total_order_seek && prefix_extractor &&
+ rep_->table_properties->prefix_extractor_name.compare(
+ prefix_extractor->Name()) == 0) {
+ filter->PrefixesMayMatch(range, prefix_extractor, kNotValid, false,
+ lookup_context);
+ }
+}
+
+Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
+ GetContext* get_context,
+ const SliceTransform* prefix_extractor,
+ bool skip_filters) {
+ assert(key.size() >= 8); // key must be internal key
+ assert(get_context != nullptr);
+ Status s;
+ const bool no_io = read_options.read_tier == kBlockCacheTier;
+
+ FilterBlockReader* const filter =
+ !skip_filters ? rep_->filter.get() : nullptr;
+
+ // First check the full filter
+ // If full filter not useful, Then go into each block
+ uint64_t tracing_get_id = get_context->get_tracing_get_id();
+ BlockCacheLookupContext lookup_context{
+ TableReaderCaller::kUserGet, tracing_get_id,
+ /*get_from_user_specified_snapshot=*/read_options.snapshot != nullptr};
+ if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled()) {
+ // Trace the key since it contains both user key and sequence number.
+ lookup_context.referenced_key = key.ToString();
+ lookup_context.get_from_user_specified_snapshot =
+ read_options.snapshot != nullptr;
+ }
+ const bool may_match =
+ FullFilterKeyMayMatch(read_options, filter, key, no_io, prefix_extractor,
+ get_context, &lookup_context);
+ if (!may_match) {
+ RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL);
+ PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level);
+ } else {
+ IndexBlockIter iiter_on_stack;
+ // if prefix_extractor found in block differs from options, disable
+ // BlockPrefixIndex. Only do this check when index_type is kHashSearch.
+ bool need_upper_bound_check = false;
+ if (rep_->index_type == BlockBasedTableOptions::kHashSearch) {
+ need_upper_bound_check = PrefixExtractorChanged(
+ rep_->table_properties.get(), prefix_extractor);
+ }
+ auto iiter =
+ NewIndexIterator(read_options, need_upper_bound_check, &iiter_on_stack,
+ get_context, &lookup_context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (iiter != &iiter_on_stack) {
+ iiter_unique_ptr.reset(iiter);
+ }
+
+ size_t ts_sz =
+ rep_->internal_comparator.user_comparator()->timestamp_size();
+ bool matched = false; // if such user key mathced a key in SST
+ bool done = false;
+ for (iiter->Seek(key); iiter->Valid() && !done; iiter->Next()) {
+ IndexValue v = iiter->value();
+
+ bool not_exist_in_filter =
+ filter != nullptr && filter->IsBlockBased() == true &&
+ !filter->KeyMayMatch(ExtractUserKeyAndStripTimestamp(key, ts_sz),
+ prefix_extractor, v.handle.offset(), no_io,
+ /*const_ikey_ptr=*/nullptr, get_context,
+ &lookup_context);
+
+ if (not_exist_in_filter) {
+ // Not found
+ // TODO: think about interaction with Merge. If a user key cannot
+ // cross one data block, we should be fine.
+ RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL);
+ PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level);
+ break;
+ }
+
+ if (!v.first_internal_key.empty() && !skip_filters &&
+ UserComparatorWrapper(rep_->internal_comparator.user_comparator())
+ .Compare(ExtractUserKey(key),
+ ExtractUserKey(v.first_internal_key)) < 0) {
+ // The requested key falls between highest key in previous block and
+ // lowest key in current block.
+ break;
+ }
+
+ BlockCacheLookupContext lookup_data_block_context{
+ TableReaderCaller::kUserGet, tracing_get_id,
+ /*get_from_user_specified_snapshot=*/read_options.snapshot !=
+ nullptr};
+ bool does_referenced_key_exist = false;
+ DataBlockIter biter;
+ uint64_t referenced_data_size = 0;
+ NewDataBlockIterator<DataBlockIter>(
+ read_options, v.handle, &biter, BlockType::kData, get_context,
+ &lookup_data_block_context,
+ /*s=*/Status(), /*prefetch_buffer*/ nullptr);
+
+ if (no_io && biter.status().IsIncomplete()) {
+ // couldn't get block from block_cache
+ // Update Saver.state to Found because we are only looking for
+ // whether we can guarantee the key is not there when "no_io" is set
+ get_context->MarkKeyMayExist();
+ break;
+ }
+ if (!biter.status().ok()) {
+ s = biter.status();
+ break;
+ }
+
+ bool may_exist = biter.SeekForGet(key);
+ // If user-specified timestamp is supported, we cannot end the search
+ // just because hash index lookup indicates the key+ts does not exist.
+ if (!may_exist && ts_sz == 0) {
+ // HashSeek cannot find the key this block and the the iter is not
+ // the end of the block, i.e. cannot be in the following blocks
+ // either. In this case, the seek_key cannot be found, so we break
+ // from the top level for-loop.
+ done = true;
+ } else {
+ // Call the *saver function on each entry/block until it returns false
+ for (; biter.Valid(); biter.Next()) {
+ ParsedInternalKey parsed_key;
+ if (!ParseInternalKey(biter.key(), &parsed_key)) {
+ s = Status::Corruption(Slice());
+ }
+
+ if (!get_context->SaveValue(
+ parsed_key, biter.value(), &matched,
+ biter.IsValuePinned() ? &biter : nullptr)) {
+ if (get_context->State() == GetContext::GetState::kFound) {
+ does_referenced_key_exist = true;
+ referenced_data_size = biter.key().size() + biter.value().size();
+ }
+ done = true;
+ break;
+ }
+ }
+ s = biter.status();
+ }
+ // Write the block cache access record.
+ if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled()) {
+ // Avoid making copy of block_key, cf_name, and referenced_key when
+ // constructing the access record.
+ Slice referenced_key;
+ if (does_referenced_key_exist) {
+ referenced_key = biter.key();
+ } else {
+ referenced_key = key;
+ }
+ BlockCacheTraceRecord access_record(
+ rep_->ioptions.env->NowMicros(),
+ /*block_key=*/"", lookup_data_block_context.block_type,
+ lookup_data_block_context.block_size, rep_->cf_id_for_tracing(),
+ /*cf_name=*/"", rep_->level_for_tracing(),
+ rep_->sst_number_for_tracing(), lookup_data_block_context.caller,
+ lookup_data_block_context.is_cache_hit,
+ lookup_data_block_context.no_insert,
+ lookup_data_block_context.get_id,
+ lookup_data_block_context.get_from_user_specified_snapshot,
+ /*referenced_key=*/"", referenced_data_size,
+ lookup_data_block_context.num_keys_in_block,
+ does_referenced_key_exist);
+ block_cache_tracer_->WriteBlockAccess(
+ access_record, lookup_data_block_context.block_key,
+ rep_->cf_name_for_tracing(), referenced_key);
+ }
+
+ if (done) {
+ // Avoid the extra Next which is expensive in two-level indexes
+ break;
+ }
+ }
+ if (matched && filter != nullptr && !filter->IsBlockBased()) {
+ RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_FULL_TRUE_POSITIVE);
+ PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1,
+ rep_->level);
+ }
+ if (s.ok() && !iiter->status().IsNotFound()) {
+ s = iiter->status();
+ }
+ }
+
+ return s;
+}
+
+using MultiGetRange = MultiGetContext::Range;
+void BlockBasedTable::MultiGet(const ReadOptions& read_options,
+ const MultiGetRange* mget_range,
+ const SliceTransform* prefix_extractor,
+ bool skip_filters) {
+ FilterBlockReader* const filter =
+ !skip_filters ? rep_->filter.get() : nullptr;
+ MultiGetRange sst_file_range(*mget_range, mget_range->begin(),
+ mget_range->end());
+
+ // First check the full filter
+ // If full filter not useful, Then go into each block
+ const bool no_io = read_options.read_tier == kBlockCacheTier;
+ uint64_t tracing_mget_id = BlockCacheTraceHelper::kReservedGetId;
+ if (!sst_file_range.empty() && sst_file_range.begin()->get_context) {
+ tracing_mget_id = sst_file_range.begin()->get_context->get_tracing_get_id();
+ }
+ BlockCacheLookupContext lookup_context{
+ TableReaderCaller::kUserMultiGet, tracing_mget_id,
+ /*get_from_user_specified_snapshot=*/read_options.snapshot != nullptr};
+ FullFilterKeysMayMatch(read_options, filter, &sst_file_range, no_io,
+ prefix_extractor, &lookup_context);
+
+ if (skip_filters || !sst_file_range.empty()) {
+ IndexBlockIter iiter_on_stack;
+ // if prefix_extractor found in block differs from options, disable
+ // BlockPrefixIndex. Only do this check when index_type is kHashSearch.
+ bool need_upper_bound_check = false;
+ if (rep_->index_type == BlockBasedTableOptions::kHashSearch) {
+ need_upper_bound_check = PrefixExtractorChanged(
+ rep_->table_properties.get(), prefix_extractor);
+ }
+ auto iiter =
+ NewIndexIterator(read_options, need_upper_bound_check, &iiter_on_stack,
+ sst_file_range.begin()->get_context, &lookup_context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (iiter != &iiter_on_stack) {
+ iiter_unique_ptr.reset(iiter);
+ }
+
+ uint64_t offset = std::numeric_limits<uint64_t>::max();
+ autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE> block_handles;
+ autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE> results;
+ autovector<Status, MultiGetContext::MAX_BATCH_SIZE> statuses;
+ char stack_buf[kMultiGetReadStackBufSize];
+ std::unique_ptr<char[]> block_buf;
+ {
+ MultiGetRange data_block_range(sst_file_range, sst_file_range.begin(),
+ sst_file_range.end());
+
+ CachableEntry<UncompressionDict> uncompression_dict;
+ Status uncompression_dict_status;
+ if (rep_->uncompression_dict_reader) {
+ uncompression_dict_status =
+ rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
+ nullptr /* prefetch_buffer */, no_io,
+ sst_file_range.begin()->get_context, &lookup_context,
+ &uncompression_dict);
+ }
+
+ const UncompressionDict& dict = uncompression_dict.GetValue()
+ ? *uncompression_dict.GetValue()
+ : UncompressionDict::GetEmptyDict();
+
+ size_t total_len = 0;
+ ReadOptions ro = read_options;
+ ro.read_tier = kBlockCacheTier;
+
+ for (auto miter = data_block_range.begin();
+ miter != data_block_range.end(); ++miter) {
+ const Slice& key = miter->ikey;
+ iiter->Seek(miter->ikey);
+
+ IndexValue v;
+ if (iiter->Valid()) {
+ v = iiter->value();
+ }
+ if (!iiter->Valid() ||
+ (!v.first_internal_key.empty() && !skip_filters &&
+ UserComparatorWrapper(rep_->internal_comparator.user_comparator())
+ .Compare(ExtractUserKey(key),
+ ExtractUserKey(v.first_internal_key)) < 0)) {
+ // The requested key falls between highest key in previous block and
+ // lowest key in current block.
+ *(miter->s) = iiter->status();
+ data_block_range.SkipKey(miter);
+ sst_file_range.SkipKey(miter);
+ continue;
+ }
+
+ if (!uncompression_dict_status.ok()) {
+ *(miter->s) = uncompression_dict_status;
+ data_block_range.SkipKey(miter);
+ sst_file_range.SkipKey(miter);
+ continue;
+ }
+
+ statuses.emplace_back();
+ results.emplace_back();
+ if (v.handle.offset() == offset) {
+ // We're going to reuse the block for this key later on. No need to
+ // look it up now. Place a null handle
+ block_handles.emplace_back(BlockHandle::NullBlockHandle());
+ continue;
+ }
+ // Lookup the cache for the given data block referenced by an index
+ // iterator value (i.e BlockHandle). If it exists in the cache,
+ // initialize block to the contents of the data block.
+ offset = v.handle.offset();
+ BlockHandle handle = v.handle;
+ BlockCacheLookupContext lookup_data_block_context(
+ TableReaderCaller::kUserMultiGet);
+ Status s = RetrieveBlock(
+ nullptr, ro, handle, dict, &(results.back()), BlockType::kData,
+ miter->get_context, &lookup_data_block_context,
+ /* for_compaction */ false, /* use_cache */ true);
+ if (s.IsIncomplete()) {
+ s = Status::OK();
+ }
+ if (s.ok() && !results.back().IsEmpty()) {
+ // Found it in the cache. Add NULL handle to indicate there is
+ // nothing to read from disk
+ block_handles.emplace_back(BlockHandle::NullBlockHandle());
+ } else {
+ block_handles.emplace_back(handle);
+ total_len += block_size(handle);
+ }
+ }
+
+ if (total_len) {
+ char* scratch = nullptr;
+ // If the blocks need to be uncompressed and we don't need the
+ // compressed blocks, then we can use a contiguous block of
+ // memory to read in all the blocks as it will be temporary
+ // storage
+ // 1. If blocks are compressed and compressed block cache is there,
+ // alloc heap bufs
+ // 2. If blocks are uncompressed, alloc heap bufs
+ // 3. If blocks are compressed and no compressed block cache, use
+ // stack buf
+ if (rep_->table_options.block_cache_compressed == nullptr &&
+ rep_->blocks_maybe_compressed) {
+ if (total_len <= kMultiGetReadStackBufSize) {
+ scratch = stack_buf;
+ } else {
+ scratch = new char[total_len];
+ block_buf.reset(scratch);
+ }
+ }
+ RetrieveMultipleBlocks(read_options, &data_block_range, &block_handles,
+ &statuses, &results, scratch, dict);
+ }
+ }
+
+ DataBlockIter first_biter;
+ DataBlockIter next_biter;
+ size_t idx_in_batch = 0;
+ for (auto miter = sst_file_range.begin(); miter != sst_file_range.end();
+ ++miter) {
+ Status s;
+ GetContext* get_context = miter->get_context;
+ const Slice& key = miter->ikey;
+ bool matched = false; // if such user key matched a key in SST
+ bool done = false;
+ bool first_block = true;
+ do {
+ DataBlockIter* biter = nullptr;
+ bool reusing_block = true;
+ uint64_t referenced_data_size = 0;
+ bool does_referenced_key_exist = false;
+ BlockCacheLookupContext lookup_data_block_context(
+ TableReaderCaller::kUserMultiGet, tracing_mget_id,
+ /*get_from_user_specified_snapshot=*/read_options.snapshot !=
+ nullptr);
+ if (first_block) {
+ if (!block_handles[idx_in_batch].IsNull() ||
+ !results[idx_in_batch].IsEmpty()) {
+ first_biter.Invalidate(Status::OK());
+ NewDataBlockIterator<DataBlockIter>(
+ read_options, results[idx_in_batch], &first_biter,
+ statuses[idx_in_batch]);
+ reusing_block = false;
+ }
+ biter = &first_biter;
+ idx_in_batch++;
+ } else {
+ IndexValue v = iiter->value();
+ if (!v.first_internal_key.empty() && !skip_filters &&
+ UserComparatorWrapper(rep_->internal_comparator.user_comparator())
+ .Compare(ExtractUserKey(key),
+ ExtractUserKey(v.first_internal_key)) < 0) {
+ // The requested key falls between highest key in previous block and
+ // lowest key in current block.
+ break;
+ }
+
+ next_biter.Invalidate(Status::OK());
+ NewDataBlockIterator<DataBlockIter>(
+ read_options, iiter->value().handle, &next_biter,
+ BlockType::kData, get_context, &lookup_data_block_context,
+ Status(), nullptr);
+ biter = &next_biter;
+ reusing_block = false;
+ }
+
+ if (read_options.read_tier == kBlockCacheTier &&
+ biter->status().IsIncomplete()) {
+ // couldn't get block from block_cache
+ // Update Saver.state to Found because we are only looking for
+ // whether we can guarantee the key is not there when "no_io" is set
+ get_context->MarkKeyMayExist();
+ break;
+ }
+ if (!biter->status().ok()) {
+ s = biter->status();
+ break;
+ }
+
+ bool may_exist = biter->SeekForGet(key);
+ if (!may_exist) {
+ // HashSeek cannot find the key this block and the the iter is not
+ // the end of the block, i.e. cannot be in the following blocks
+ // either. In this case, the seek_key cannot be found, so we break
+ // from the top level for-loop.
+ break;
+ }
+
+ // Call the *saver function on each entry/block until it returns false
+ for (; biter->Valid(); biter->Next()) {
+ ParsedInternalKey parsed_key;
+ Cleanable dummy;
+ Cleanable* value_pinner = nullptr;
+ if (!ParseInternalKey(biter->key(), &parsed_key)) {
+ s = Status::Corruption(Slice());
+ }
+ if (biter->IsValuePinned()) {
+ if (reusing_block) {
+ Cache* block_cache = rep_->table_options.block_cache.get();
+ assert(biter->cache_handle() != nullptr);
+ block_cache->Ref(biter->cache_handle());
+ dummy.RegisterCleanup(&ReleaseCachedEntry, block_cache,
+ biter->cache_handle());
+ value_pinner = &dummy;
+ } else {
+ value_pinner = biter;
+ }
+ }
+ if (!get_context->SaveValue(parsed_key, biter->value(), &matched,
+ value_pinner)) {
+ if (get_context->State() == GetContext::GetState::kFound) {
+ does_referenced_key_exist = true;
+ referenced_data_size =
+ biter->key().size() + biter->value().size();
+ }
+ done = true;
+ break;
+ }
+ s = biter->status();
+ }
+ // Write the block cache access.
+ if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled()) {
+ // Avoid making copy of block_key, cf_name, and referenced_key when
+ // constructing the access record.
+ Slice referenced_key;
+ if (does_referenced_key_exist) {
+ referenced_key = biter->key();
+ } else {
+ referenced_key = key;
+ }
+ BlockCacheTraceRecord access_record(
+ rep_->ioptions.env->NowMicros(),
+ /*block_key=*/"", lookup_data_block_context.block_type,
+ lookup_data_block_context.block_size, rep_->cf_id_for_tracing(),
+ /*cf_name=*/"", rep_->level_for_tracing(),
+ rep_->sst_number_for_tracing(), lookup_data_block_context.caller,
+ lookup_data_block_context.is_cache_hit,
+ lookup_data_block_context.no_insert,
+ lookup_data_block_context.get_id,
+ lookup_data_block_context.get_from_user_specified_snapshot,
+ /*referenced_key=*/"", referenced_data_size,
+ lookup_data_block_context.num_keys_in_block,
+ does_referenced_key_exist);
+ block_cache_tracer_->WriteBlockAccess(
+ access_record, lookup_data_block_context.block_key,
+ rep_->cf_name_for_tracing(), referenced_key);
+ }
+ s = biter->status();
+ if (done) {
+ // Avoid the extra Next which is expensive in two-level indexes
+ break;
+ }
+ if (first_block) {
+ iiter->Seek(key);
+ }
+ first_block = false;
+ iiter->Next();
+ } while (iiter->Valid());
+
+ if (matched && filter != nullptr && !filter->IsBlockBased()) {
+ RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_FULL_TRUE_POSITIVE);
+ PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1,
+ rep_->level);
+ }
+ if (s.ok()) {
+ s = iiter->status();
+ }
+ *(miter->s) = s;
+ }
+ }
+}
+
+Status BlockBasedTable::Prefetch(const Slice* const begin,
+ const Slice* const end) {
+ auto& comparator = rep_->internal_comparator;
+ UserComparatorWrapper user_comparator(comparator.user_comparator());
+ // pre-condition
+ if (begin && end && comparator.Compare(*begin, *end) > 0) {
+ return Status::InvalidArgument(*begin, *end);
+ }
+ BlockCacheLookupContext lookup_context{TableReaderCaller::kPrefetch};
+ IndexBlockIter iiter_on_stack;
+ auto iiter = NewIndexIterator(ReadOptions(), /*need_upper_bound_check=*/false,
+ &iiter_on_stack, /*get_context=*/nullptr,
+ &lookup_context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (iiter != &iiter_on_stack) {
+ iiter_unique_ptr = std::unique_ptr<InternalIteratorBase<IndexValue>>(iiter);
+ }
+
+ if (!iiter->status().ok()) {
+ // error opening index iterator
+ return iiter->status();
+ }
+
+ // indicates if we are on the last page that need to be pre-fetched
+ bool prefetching_boundary_page = false;
+
+ for (begin ? iiter->Seek(*begin) : iiter->SeekToFirst(); iiter->Valid();
+ iiter->Next()) {
+ BlockHandle block_handle = iiter->value().handle;
+ const bool is_user_key = !rep_->index_key_includes_seq;
+ if (end &&
+ ((!is_user_key && comparator.Compare(iiter->key(), *end) >= 0) ||
+ (is_user_key &&
+ user_comparator.Compare(iiter->key(), ExtractUserKey(*end)) >= 0))) {
+ if (prefetching_boundary_page) {
+ break;
+ }
+
+ // The index entry represents the last key in the data block.
+ // We should load this page into memory as well, but no more
+ prefetching_boundary_page = true;
+ }
+
+ // Load the block specified by the block_handle into the block cache
+ DataBlockIter biter;
+
+ NewDataBlockIterator<DataBlockIter>(
+ ReadOptions(), block_handle, &biter, /*type=*/BlockType::kData,
+ /*get_context=*/nullptr, &lookup_context, Status(),
+ /*prefetch_buffer=*/nullptr);
+
+ if (!biter.status().ok()) {
+ // there was an unexpected error while pre-fetching
+ return biter.status();
+ }
+ }
+
+ return Status::OK();
+}
+
+Status BlockBasedTable::VerifyChecksum(const ReadOptions& read_options,
+ TableReaderCaller caller) {
+ Status s;
+ // Check Meta blocks
+ std::unique_ptr<Block> metaindex;
+ std::unique_ptr<InternalIterator> metaindex_iter;
+ s = ReadMetaIndexBlock(nullptr /* prefetch buffer */, &metaindex,
+ &metaindex_iter);
+ if (s.ok()) {
+ s = VerifyChecksumInMetaBlocks(metaindex_iter.get());
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ return s;
+ }
+ // Check Data blocks
+ IndexBlockIter iiter_on_stack;
+ BlockCacheLookupContext context{caller};
+ InternalIteratorBase<IndexValue>* iiter = NewIndexIterator(
+ read_options, /*disable_prefix_seek=*/false, &iiter_on_stack,
+ /*get_context=*/nullptr, &context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (iiter != &iiter_on_stack) {
+ iiter_unique_ptr = std::unique_ptr<InternalIteratorBase<IndexValue>>(iiter);
+ }
+ if (!iiter->status().ok()) {
+ // error opening index iterator
+ return iiter->status();
+ }
+ s = VerifyChecksumInBlocks(read_options, iiter);
+ return s;
+}
+
+Status BlockBasedTable::VerifyChecksumInBlocks(
+ const ReadOptions& read_options,
+ InternalIteratorBase<IndexValue>* index_iter) {
+ Status s;
+ // We are scanning the whole file, so no need to do exponential
+ // increasing of the buffer size.
+ size_t readahead_size = (read_options.readahead_size != 0)
+ ? read_options.readahead_size
+ : kMaxAutoReadaheadSize;
+ // FilePrefetchBuffer doesn't work in mmap mode and readahead is not
+ // needed there.
+ FilePrefetchBuffer prefetch_buffer(
+ rep_->file.get(), readahead_size /* readadhead_size */,
+ readahead_size /* max_readahead_size */,
+ !rep_->ioptions.allow_mmap_reads /* enable */);
+
+ for (index_iter->SeekToFirst(); index_iter->Valid(); index_iter->Next()) {
+ s = index_iter->status();
+ if (!s.ok()) {
+ break;
+ }
+ BlockHandle handle = index_iter->value().handle;
+ BlockContents contents;
+ BlockFetcher block_fetcher(
+ rep_->file.get(), &prefetch_buffer, rep_->footer, ReadOptions(), handle,
+ &contents, rep_->ioptions, false /* decompress */,
+ false /*maybe_compressed*/, BlockType::kData,
+ UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options);
+ s = block_fetcher.ReadBlockContents();
+ if (!s.ok()) {
+ break;
+ }
+ }
+ return s;
+}
+
+BlockType BlockBasedTable::GetBlockTypeForMetaBlockByName(
+ const Slice& meta_block_name) {
+ if (meta_block_name.starts_with(kFilterBlockPrefix) ||
+ meta_block_name.starts_with(kFullFilterBlockPrefix) ||
+ meta_block_name.starts_with(kPartitionedFilterBlockPrefix)) {
+ return BlockType::kFilter;
+ }
+
+ if (meta_block_name == kPropertiesBlock) {
+ return BlockType::kProperties;
+ }
+
+ if (meta_block_name == kCompressionDictBlock) {
+ return BlockType::kCompressionDictionary;
+ }
+
+ if (meta_block_name == kRangeDelBlock) {
+ return BlockType::kRangeDeletion;
+ }
+
+ if (meta_block_name == kHashIndexPrefixesBlock) {
+ return BlockType::kHashIndexPrefixes;
+ }
+
+ if (meta_block_name == kHashIndexPrefixesMetadataBlock) {
+ return BlockType::kHashIndexMetadata;
+ }
+
+ assert(false);
+ return BlockType::kInvalid;
+}
+
+Status BlockBasedTable::VerifyChecksumInMetaBlocks(
+ InternalIteratorBase<Slice>* index_iter) {
+ Status s;
+ for (index_iter->SeekToFirst(); index_iter->Valid(); index_iter->Next()) {
+ s = index_iter->status();
+ if (!s.ok()) {
+ break;
+ }
+ BlockHandle handle;
+ Slice input = index_iter->value();
+ s = handle.DecodeFrom(&input);
+ BlockContents contents;
+ const Slice meta_block_name = index_iter->key();
+ BlockFetcher block_fetcher(
+ rep_->file.get(), nullptr /* prefetch buffer */, rep_->footer,
+ ReadOptions(), handle, &contents, rep_->ioptions,
+ false /* decompress */, false /*maybe_compressed*/,
+ GetBlockTypeForMetaBlockByName(meta_block_name),
+ UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options);
+ s = block_fetcher.ReadBlockContents();
+ if (s.IsCorruption() && meta_block_name == kPropertiesBlock) {
+ TableProperties* table_properties;
+ s = TryReadPropertiesWithGlobalSeqno(nullptr /* prefetch_buffer */,
+ index_iter->value(),
+ &table_properties);
+ delete table_properties;
+ }
+ if (!s.ok()) {
+ break;
+ }
+ }
+ return s;
+}
+
+bool BlockBasedTable::TEST_BlockInCache(const BlockHandle& handle) const {
+ assert(rep_ != nullptr);
+
+ Cache* const cache = rep_->table_options.block_cache.get();
+ if (cache == nullptr) {
+ return false;
+ }
+
+ char cache_key_storage[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ Slice cache_key =
+ GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size, handle,
+ cache_key_storage);
+
+ Cache::Handle* const cache_handle = cache->Lookup(cache_key);
+ if (cache_handle == nullptr) {
+ return false;
+ }
+
+ cache->Release(cache_handle);
+
+ return true;
+}
+
+bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
+ const Slice& key) {
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter(NewIndexIterator(
+ options, /*need_upper_bound_check=*/false, /*input_iter=*/nullptr,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr));
+ iiter->Seek(key);
+ assert(iiter->Valid());
+
+ return TEST_BlockInCache(iiter->value().handle);
+}
+
+// REQUIRES: The following fields of rep_ should have already been populated:
+// 1. file
+// 2. index_handle,
+// 3. options
+// 4. internal_comparator
+// 5. index_type
+Status BlockBasedTable::CreateIndexReader(
+ FilePrefetchBuffer* prefetch_buffer,
+ InternalIterator* preloaded_meta_index_iter, bool use_cache, bool prefetch,
+ bool pin, BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<IndexReader>* index_reader) {
+ // kHashSearch requires non-empty prefix_extractor but bypass checking
+ // prefix_extractor here since we have no access to MutableCFOptions.
+ // Add need_upper_bound_check flag in BlockBasedTable::NewIndexIterator.
+ // If prefix_extractor does not match prefix_extractor_name from table
+ // properties, turn off Hash Index by setting total_order_seek to true
+
+ switch (rep_->index_type) {
+ case BlockBasedTableOptions::kTwoLevelIndexSearch: {
+ return PartitionIndexReader::Create(this, prefetch_buffer, use_cache,
+ prefetch, pin, lookup_context,
+ index_reader);
+ }
+ case BlockBasedTableOptions::kBinarySearch:
+ FALLTHROUGH_INTENDED;
+ case BlockBasedTableOptions::kBinarySearchWithFirstKey: {
+ return BinarySearchIndexReader::Create(this, prefetch_buffer, use_cache,
+ prefetch, pin, lookup_context,
+ index_reader);
+ }
+ case BlockBasedTableOptions::kHashSearch: {
+ std::unique_ptr<Block> metaindex_guard;
+ std::unique_ptr<InternalIterator> metaindex_iter_guard;
+ auto meta_index_iter = preloaded_meta_index_iter;
+ bool should_fallback = false;
+ if (rep_->internal_prefix_transform.get() == nullptr) {
+ ROCKS_LOG_WARN(rep_->ioptions.info_log,
+ "No prefix extractor passed in. Fall back to binary"
+ " search index.");
+ should_fallback = true;
+ } else if (meta_index_iter == nullptr) {
+ auto s = ReadMetaIndexBlock(prefetch_buffer, &metaindex_guard,
+ &metaindex_iter_guard);
+ if (!s.ok()) {
+ // we simply fall back to binary search in case there is any
+ // problem with prefix hash index loading.
+ ROCKS_LOG_WARN(rep_->ioptions.info_log,
+ "Unable to read the metaindex block."
+ " Fall back to binary search index.");
+ should_fallback = true;
+ }
+ meta_index_iter = metaindex_iter_guard.get();
+ }
+
+ if (should_fallback) {
+ return BinarySearchIndexReader::Create(this, prefetch_buffer, use_cache,
+ prefetch, pin, lookup_context,
+ index_reader);
+ } else {
+ return HashIndexReader::Create(this, prefetch_buffer, meta_index_iter,
+ use_cache, prefetch, pin, lookup_context,
+ index_reader);
+ }
+ }
+ default: {
+ std::string error_message =
+ "Unrecognized index type: " + ToString(rep_->index_type);
+ return Status::InvalidArgument(error_message.c_str());
+ }
+ }
+}
+
+uint64_t BlockBasedTable::ApproximateOffsetOf(
+ const InternalIteratorBase<IndexValue>& index_iter) const {
+ uint64_t result = 0;
+ if (index_iter.Valid()) {
+ BlockHandle handle = index_iter.value().handle;
+ result = handle.offset();
+ } else {
+ // The iterator is past the last key in the file. If table_properties is not
+ // available, approximate the offset by returning the offset of the
+ // metaindex block (which is right near the end of the file).
+ if (rep_->table_properties) {
+ result = rep_->table_properties->data_size;
+ }
+ // table_properties is not present in the table.
+ if (result == 0) {
+ result = rep_->footer.metaindex_handle().offset();
+ }
+ }
+
+ return result;
+}
+
+uint64_t BlockBasedTable::ApproximateOffsetOf(const Slice& key,
+ TableReaderCaller caller) {
+ BlockCacheLookupContext context(caller);
+ IndexBlockIter iiter_on_stack;
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ auto index_iter =
+ NewIndexIterator(ro, /*disable_prefix_seek=*/true,
+ /*input_iter=*/&iiter_on_stack, /*get_context=*/nullptr,
+ /*lookup_context=*/&context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (index_iter != &iiter_on_stack) {
+ iiter_unique_ptr.reset(index_iter);
+ }
+
+ index_iter->Seek(key);
+ return ApproximateOffsetOf(*index_iter);
+}
+
+uint64_t BlockBasedTable::ApproximateSize(const Slice& start, const Slice& end,
+ TableReaderCaller caller) {
+ assert(rep_->internal_comparator.Compare(start, end) <= 0);
+
+ BlockCacheLookupContext context(caller);
+ IndexBlockIter iiter_on_stack;
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ auto index_iter =
+ NewIndexIterator(ro, /*disable_prefix_seek=*/true,
+ /*input_iter=*/&iiter_on_stack, /*get_context=*/nullptr,
+ /*lookup_context=*/&context);
+ std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
+ if (index_iter != &iiter_on_stack) {
+ iiter_unique_ptr.reset(index_iter);
+ }
+
+ index_iter->Seek(start);
+ uint64_t start_offset = ApproximateOffsetOf(*index_iter);
+ index_iter->Seek(end);
+ uint64_t end_offset = ApproximateOffsetOf(*index_iter);
+
+ assert(end_offset >= start_offset);
+ return end_offset - start_offset;
+}
+
+bool BlockBasedTable::TEST_FilterBlockInCache() const {
+ assert(rep_ != nullptr);
+ return TEST_BlockInCache(rep_->filter_handle);
+}
+
+bool BlockBasedTable::TEST_IndexBlockInCache() const {
+ assert(rep_ != nullptr);
+
+ return TEST_BlockInCache(rep_->footer.index_handle());
+}
+
+Status BlockBasedTable::GetKVPairsFromDataBlocks(
+ std::vector<KVPairBlock>* kv_pair_blocks) {
+ std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
+ NewIndexIterator(ReadOptions(), /*need_upper_bound_check=*/false,
+ /*input_iter=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_contex=*/nullptr));
+
+ Status s = blockhandles_iter->status();
+ if (!s.ok()) {
+ // Cannot read Index Block
+ return s;
+ }
+
+ for (blockhandles_iter->SeekToFirst(); blockhandles_iter->Valid();
+ blockhandles_iter->Next()) {
+ s = blockhandles_iter->status();
+
+ if (!s.ok()) {
+ break;
+ }
+
+ std::unique_ptr<InternalIterator> datablock_iter;
+ datablock_iter.reset(NewDataBlockIterator<DataBlockIter>(
+ ReadOptions(), blockhandles_iter->value().handle,
+ /*input_iter=*/nullptr, /*type=*/BlockType::kData,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr, Status(),
+ /*prefetch_buffer=*/nullptr));
+ s = datablock_iter->status();
+
+ if (!s.ok()) {
+ // Error reading the block - Skipped
+ continue;
+ }
+
+ KVPairBlock kv_pair_block;
+ for (datablock_iter->SeekToFirst(); datablock_iter->Valid();
+ datablock_iter->Next()) {
+ s = datablock_iter->status();
+ if (!s.ok()) {
+ // Error reading the block - Skipped
+ break;
+ }
+ const Slice& key = datablock_iter->key();
+ const Slice& value = datablock_iter->value();
+ std::string key_copy = std::string(key.data(), key.size());
+ std::string value_copy = std::string(value.data(), value.size());
+
+ kv_pair_block.push_back(
+ std::make_pair(std::move(key_copy), std::move(value_copy)));
+ }
+ kv_pair_blocks->push_back(std::move(kv_pair_block));
+ }
+ return Status::OK();
+}
+
+Status BlockBasedTable::DumpTable(WritableFile* out_file) {
+ // Output Footer
+ out_file->Append(
+ "Footer Details:\n"
+ "--------------------------------------\n"
+ " ");
+ out_file->Append(rep_->footer.ToString().c_str());
+ out_file->Append("\n");
+
+ // Output MetaIndex
+ out_file->Append(
+ "Metaindex Details:\n"
+ "--------------------------------------\n");
+ std::unique_ptr<Block> metaindex;
+ std::unique_ptr<InternalIterator> metaindex_iter;
+ Status s = ReadMetaIndexBlock(nullptr /* prefetch_buffer */, &metaindex,
+ &metaindex_iter);
+ if (s.ok()) {
+ for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid();
+ metaindex_iter->Next()) {
+ s = metaindex_iter->status();
+ if (!s.ok()) {
+ return s;
+ }
+ if (metaindex_iter->key() == ROCKSDB_NAMESPACE::kPropertiesBlock) {
+ out_file->Append(" Properties block handle: ");
+ out_file->Append(metaindex_iter->value().ToString(true).c_str());
+ out_file->Append("\n");
+ } else if (metaindex_iter->key() ==
+ ROCKSDB_NAMESPACE::kCompressionDictBlock) {
+ out_file->Append(" Compression dictionary block handle: ");
+ out_file->Append(metaindex_iter->value().ToString(true).c_str());
+ out_file->Append("\n");
+ } else if (strstr(metaindex_iter->key().ToString().c_str(),
+ "filter.rocksdb.") != nullptr) {
+ out_file->Append(" Filter block handle: ");
+ out_file->Append(metaindex_iter->value().ToString(true).c_str());
+ out_file->Append("\n");
+ } else if (metaindex_iter->key() == ROCKSDB_NAMESPACE::kRangeDelBlock) {
+ out_file->Append(" Range deletion block handle: ");
+ out_file->Append(metaindex_iter->value().ToString(true).c_str());
+ out_file->Append("\n");
+ }
+ }
+ out_file->Append("\n");
+ } else {
+ return s;
+ }
+
+ // Output TableProperties
+ const ROCKSDB_NAMESPACE::TableProperties* table_properties;
+ table_properties = rep_->table_properties.get();
+
+ if (table_properties != nullptr) {
+ out_file->Append(
+ "Table Properties:\n"
+ "--------------------------------------\n"
+ " ");
+ out_file->Append(table_properties->ToString("\n ", ": ").c_str());
+ out_file->Append("\n");
+ }
+
+ if (rep_->filter) {
+ out_file->Append(
+ "Filter Details:\n"
+ "--------------------------------------\n"
+ " ");
+ out_file->Append(rep_->filter->ToString().c_str());
+ out_file->Append("\n");
+ }
+
+ // Output Index block
+ s = DumpIndexBlock(out_file);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Output compression dictionary
+ if (rep_->uncompression_dict_reader) {
+ CachableEntry<UncompressionDict> uncompression_dict;
+ s = rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
+ nullptr /* prefetch_buffer */, false /* no_io */,
+ nullptr /* get_context */, nullptr /* lookup_context */,
+ &uncompression_dict);
+ if (!s.ok()) {
+ return s;
+ }
+
+ assert(uncompression_dict.GetValue());
+
+ const Slice& raw_dict = uncompression_dict.GetValue()->GetRawDict();
+ out_file->Append(
+ "Compression Dictionary:\n"
+ "--------------------------------------\n");
+ out_file->Append(" size (bytes): ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(raw_dict.size()));
+ out_file->Append("\n\n");
+ out_file->Append(" HEX ");
+ out_file->Append(raw_dict.ToString(true).c_str());
+ out_file->Append("\n\n");
+ }
+
+ // Output range deletions block
+ auto* range_del_iter = NewRangeTombstoneIterator(ReadOptions());
+ if (range_del_iter != nullptr) {
+ range_del_iter->SeekToFirst();
+ if (range_del_iter->Valid()) {
+ out_file->Append(
+ "Range deletions:\n"
+ "--------------------------------------\n"
+ " ");
+ for (; range_del_iter->Valid(); range_del_iter->Next()) {
+ DumpKeyValue(range_del_iter->key(), range_del_iter->value(), out_file);
+ }
+ out_file->Append("\n");
+ }
+ delete range_del_iter;
+ }
+ // Output Data blocks
+ s = DumpDataBlocks(out_file);
+
+ return s;
+}
+
+Status BlockBasedTable::DumpIndexBlock(WritableFile* out_file) {
+ out_file->Append(
+ "Index Details:\n"
+ "--------------------------------------\n");
+ std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
+ NewIndexIterator(ReadOptions(), /*need_upper_bound_check=*/false,
+ /*input_iter=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_contex=*/nullptr));
+ Status s = blockhandles_iter->status();
+ if (!s.ok()) {
+ out_file->Append("Can not read Index Block \n\n");
+ return s;
+ }
+
+ out_file->Append(" Block key hex dump: Data block handle\n");
+ out_file->Append(" Block key ascii\n\n");
+ for (blockhandles_iter->SeekToFirst(); blockhandles_iter->Valid();
+ blockhandles_iter->Next()) {
+ s = blockhandles_iter->status();
+ if (!s.ok()) {
+ break;
+ }
+ Slice key = blockhandles_iter->key();
+ Slice user_key;
+ InternalKey ikey;
+ if (!rep_->index_key_includes_seq) {
+ user_key = key;
+ } else {
+ ikey.DecodeFrom(key);
+ user_key = ikey.user_key();
+ }
+
+ out_file->Append(" HEX ");
+ out_file->Append(user_key.ToString(true).c_str());
+ out_file->Append(": ");
+ out_file->Append(blockhandles_iter->value()
+ .ToString(true, rep_->index_has_first_key)
+ .c_str());
+ out_file->Append("\n");
+
+ std::string str_key = user_key.ToString();
+ std::string res_key("");
+ char cspace = ' ';
+ for (size_t i = 0; i < str_key.size(); i++) {
+ res_key.append(&str_key[i], 1);
+ res_key.append(1, cspace);
+ }
+ out_file->Append(" ASCII ");
+ out_file->Append(res_key.c_str());
+ out_file->Append("\n ------\n");
+ }
+ out_file->Append("\n");
+ return Status::OK();
+}
+
+Status BlockBasedTable::DumpDataBlocks(WritableFile* out_file) {
+ std::unique_ptr<InternalIteratorBase<IndexValue>> blockhandles_iter(
+ NewIndexIterator(ReadOptions(), /*need_upper_bound_check=*/false,
+ /*input_iter=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_contex=*/nullptr));
+ Status s = blockhandles_iter->status();
+ if (!s.ok()) {
+ out_file->Append("Can not read Index Block \n\n");
+ return s;
+ }
+
+ uint64_t datablock_size_min = std::numeric_limits<uint64_t>::max();
+ uint64_t datablock_size_max = 0;
+ uint64_t datablock_size_sum = 0;
+
+ size_t block_id = 1;
+ for (blockhandles_iter->SeekToFirst(); blockhandles_iter->Valid();
+ block_id++, blockhandles_iter->Next()) {
+ s = blockhandles_iter->status();
+ if (!s.ok()) {
+ break;
+ }
+
+ BlockHandle bh = blockhandles_iter->value().handle;
+ uint64_t datablock_size = bh.size();
+ datablock_size_min = std::min(datablock_size_min, datablock_size);
+ datablock_size_max = std::max(datablock_size_max, datablock_size);
+ datablock_size_sum += datablock_size;
+
+ out_file->Append("Data Block # ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(block_id));
+ out_file->Append(" @ ");
+ out_file->Append(blockhandles_iter->value().handle.ToString(true).c_str());
+ out_file->Append("\n");
+ out_file->Append("--------------------------------------\n");
+
+ std::unique_ptr<InternalIterator> datablock_iter;
+ datablock_iter.reset(NewDataBlockIterator<DataBlockIter>(
+ ReadOptions(), blockhandles_iter->value().handle,
+ /*input_iter=*/nullptr, /*type=*/BlockType::kData,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr, Status(),
+ /*prefetch_buffer=*/nullptr));
+ s = datablock_iter->status();
+
+ if (!s.ok()) {
+ out_file->Append("Error reading the block - Skipped \n\n");
+ continue;
+ }
+
+ for (datablock_iter->SeekToFirst(); datablock_iter->Valid();
+ datablock_iter->Next()) {
+ s = datablock_iter->status();
+ if (!s.ok()) {
+ out_file->Append("Error reading the block - Skipped \n");
+ break;
+ }
+ DumpKeyValue(datablock_iter->key(), datablock_iter->value(), out_file);
+ }
+ out_file->Append("\n");
+ }
+
+ uint64_t num_datablocks = block_id - 1;
+ if (num_datablocks) {
+ double datablock_size_avg =
+ static_cast<double>(datablock_size_sum) / num_datablocks;
+ out_file->Append("Data Block Summary:\n");
+ out_file->Append("--------------------------------------");
+ out_file->Append("\n # data blocks: ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(num_datablocks));
+ out_file->Append("\n min data block size: ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(datablock_size_min));
+ out_file->Append("\n max data block size: ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(datablock_size_max));
+ out_file->Append("\n avg data block size: ");
+ out_file->Append(ROCKSDB_NAMESPACE::ToString(datablock_size_avg));
+ out_file->Append("\n");
+ }
+
+ return Status::OK();
+}
+
+void BlockBasedTable::DumpKeyValue(const Slice& key, const Slice& value,
+ WritableFile* out_file) {
+ InternalKey ikey;
+ ikey.DecodeFrom(key);
+
+ out_file->Append(" HEX ");
+ out_file->Append(ikey.user_key().ToString(true).c_str());
+ out_file->Append(": ");
+ out_file->Append(value.ToString(true).c_str());
+ out_file->Append("\n");
+
+ std::string str_key = ikey.user_key().ToString();
+ std::string str_value = value.ToString();
+ std::string res_key(""), res_value("");
+ char cspace = ' ';
+ for (size_t i = 0; i < str_key.size(); i++) {
+ if (str_key[i] == '\0') {
+ res_key.append("\\0", 2);
+ } else {
+ res_key.append(&str_key[i], 1);
+ }
+ res_key.append(1, cspace);
+ }
+ for (size_t i = 0; i < str_value.size(); i++) {
+ if (str_value[i] == '\0') {
+ res_value.append("\\0", 2);
+ } else {
+ res_value.append(&str_value[i], 1);
+ }
+ res_value.append(1, cspace);
+ }
+
+ out_file->Append(" ASCII ");
+ out_file->Append(res_key.c_str());
+ out_file->Append(": ");
+ out_file->Append(res_value.c_str());
+ out_file->Append("\n ------\n");
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.h b/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.h
new file mode 100644
index 00000000..28a37898
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_based_table_reader.h
@@ -0,0 +1,824 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "db/range_tombstone_fragmenter.h"
+#include "file/filename.h"
+#include "file/random_access_file_reader.h"
+#include "options/cf_options.h"
+#include "rocksdb/options.h"
+#include "rocksdb/persistent_cache.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/block_based/block_type.h"
+#include "table/block_based/cachable_entry.h"
+#include "table/block_based/filter_block.h"
+#include "table/block_based/uncompression_dict_reader.h"
+#include "table/format.h"
+#include "table/get_context.h"
+#include "table/multiget_context.h"
+#include "table/persistent_cache_helper.h"
+#include "table/table_properties_internal.h"
+#include "table/table_reader.h"
+#include "table/two_level_iterator.h"
+#include "trace_replay/block_cache_tracer.h"
+#include "util/coding.h"
+#include "util/user_comparator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Cache;
+class FilterBlockReader;
+class BlockBasedFilterBlockReader;
+class FullFilterBlockReader;
+class Footer;
+class InternalKeyComparator;
+class Iterator;
+class FSRandomAccessFile;
+class TableCache;
+class TableReader;
+class WritableFile;
+struct BlockBasedTableOptions;
+struct EnvOptions;
+struct ReadOptions;
+class GetContext;
+
+typedef std::vector<std::pair<std::string, std::string>> KVPairBlock;
+
+// Reader class for BlockBasedTable format.
+// For the format of BlockBasedTable refer to
+// https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format.
+// This is the default table type. Data is chucked into fixed size blocks and
+// each block in-turn stores entries. When storing data, we can compress and/or
+// encode data efficiently within a block, which often results in a much smaller
+// data size compared with the raw data size. As for the record retrieval, we'll
+// first locate the block where target record may reside, then read the block to
+// memory, and finally search that record within the block. Of course, to avoid
+// frequent reads of the same block, we introduced the block cache to keep the
+// loaded blocks in the memory.
+class BlockBasedTable : public TableReader {
+ public:
+ static const std::string kFilterBlockPrefix;
+ static const std::string kFullFilterBlockPrefix;
+ static const std::string kPartitionedFilterBlockPrefix;
+ // The longest prefix of the cache key used to identify blocks.
+ // For Posix files the unique ID is three varints.
+ static const size_t kMaxCacheKeyPrefixSize = kMaxVarint64Length * 3 + 1;
+
+ // All the below fields control iterator readahead
+ static const size_t kInitAutoReadaheadSize = 8 * 1024;
+ // Found that 256 KB readahead size provides the best performance, based on
+ // experiments, for auto readahead. Experiment data is in PR #3282.
+ static const size_t kMaxAutoReadaheadSize;
+ static const int kMinNumFileReadsToStartAutoReadahead = 2;
+
+ // Attempt to open the table that is stored in bytes [0..file_size)
+ // of "file", and read the metadata entries necessary to allow
+ // retrieving data from the table.
+ //
+ // If successful, returns ok and sets "*table_reader" to the newly opened
+ // table. The client should delete "*table_reader" when no longer needed.
+ // If there was an error while initializing the table, sets "*table_reader"
+ // to nullptr and returns a non-ok status.
+ //
+ // @param file must remain live while this Table is in use.
+ // @param prefetch_index_and_filter_in_cache can be used to disable
+ // prefetching of
+ // index and filter blocks into block cache at startup
+ // @param skip_filters Disables loading/accessing the filter block. Overrides
+ // prefetch_index_and_filter_in_cache, so filter will be skipped if both
+ // are set.
+ static Status Open(const ImmutableCFOptions& ioptions,
+ const EnvOptions& env_options,
+ const BlockBasedTableOptions& table_options,
+ const InternalKeyComparator& internal_key_comparator,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ const SliceTransform* prefix_extractor = nullptr,
+ bool prefetch_index_and_filter_in_cache = true,
+ bool skip_filters = false, int level = -1,
+ const bool immortal_table = false,
+ const SequenceNumber largest_seqno = 0,
+ TailPrefetchStats* tail_prefetch_stats = nullptr,
+ BlockCacheTracer* const block_cache_tracer = nullptr);
+
+ bool PrefixMayMatch(const Slice& internal_key,
+ const ReadOptions& read_options,
+ const SliceTransform* options_prefix_extractor,
+ const bool need_upper_bound_check,
+ BlockCacheLookupContext* lookup_context) const;
+
+ // Returns a new iterator over the table contents.
+ // The result of NewIterator() is initially invalid (caller must
+ // call one of the Seek methods on the iterator before using it).
+ // @param skip_filters Disables loading/accessing the filter block
+ // compaction_readahead_size: its value will only be used if caller =
+ // kCompaction.
+ InternalIterator* NewIterator(const ReadOptions&,
+ const SliceTransform* prefix_extractor,
+ Arena* arena, bool skip_filters,
+ TableReaderCaller caller,
+ size_t compaction_readahead_size = 0) override;
+
+ FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
+ const ReadOptions& read_options) override;
+
+ // @param skip_filters Disables loading/accessing the filter block
+ Status Get(const ReadOptions& readOptions, const Slice& key,
+ GetContext* get_context, const SliceTransform* prefix_extractor,
+ bool skip_filters = false) override;
+
+ void MultiGet(const ReadOptions& readOptions,
+ const MultiGetContext::Range* mget_range,
+ const SliceTransform* prefix_extractor,
+ bool skip_filters = false) override;
+
+ // Pre-fetch the disk blocks that correspond to the key range specified by
+ // (kbegin, kend). The call will return error status in the event of
+ // IO or iteration error.
+ Status Prefetch(const Slice* begin, const Slice* end) override;
+
+ // Given a key, return an approximate byte offset in the file where
+ // the data for that key begins (or would begin if the key were
+ // present in the file). The returned value is in terms of file
+ // bytes, and so includes effects like compression of the underlying data.
+ // E.g., the approximate offset of the last key in the table will
+ // be close to the file length.
+ uint64_t ApproximateOffsetOf(const Slice& key,
+ TableReaderCaller caller) override;
+
+ // Given start and end keys, return the approximate data size in the file
+ // between the keys. The returned value is in terms of file bytes, and so
+ // includes effects like compression of the underlying data.
+ // The start key must not be greater than the end key.
+ uint64_t ApproximateSize(const Slice& start, const Slice& end,
+ TableReaderCaller caller) override;
+
+ bool TEST_BlockInCache(const BlockHandle& handle) const;
+
+ // Returns true if the block for the specified key is in cache.
+ // REQUIRES: key is in this table && block cache enabled
+ bool TEST_KeyInCache(const ReadOptions& options, const Slice& key);
+
+ // Set up the table for Compaction. Might change some parameters with
+ // posix_fadvise
+ void SetupForCompaction() override;
+
+ std::shared_ptr<const TableProperties> GetTableProperties() const override;
+
+ size_t ApproximateMemoryUsage() const override;
+
+ // convert SST file to a human readable form
+ Status DumpTable(WritableFile* out_file) override;
+
+ Status VerifyChecksum(const ReadOptions& readOptions,
+ TableReaderCaller caller) override;
+
+ ~BlockBasedTable();
+
+ bool TEST_FilterBlockInCache() const;
+ bool TEST_IndexBlockInCache() const;
+
+ // IndexReader is the interface that provides the functionality for index
+ // access.
+ class IndexReader {
+ public:
+ virtual ~IndexReader() = default;
+
+ // Create an iterator for index access. If iter is null, then a new object
+ // is created on the heap, and the callee will have the ownership.
+ // If a non-null iter is passed in, it will be used, and the returned value
+ // is either the same as iter or a new on-heap object that
+ // wraps the passed iter. In the latter case the return value points
+ // to a different object then iter, and the callee has the ownership of the
+ // returned object.
+ virtual InternalIteratorBase<IndexValue>* NewIterator(
+ const ReadOptions& read_options, bool disable_prefix_seek,
+ IndexBlockIter* iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) = 0;
+
+ // Report an approximation of how much memory has been used other than
+ // memory that was allocated in block cache.
+ virtual size_t ApproximateMemoryUsage() const = 0;
+ // Cache the dependencies of the index reader (e.g. the partitions
+ // of a partitioned index).
+ virtual void CacheDependencies(bool /* pin */) {}
+ };
+
+ class IndexReaderCommon;
+
+ static Slice GetCacheKey(const char* cache_key_prefix,
+ size_t cache_key_prefix_size,
+ const BlockHandle& handle, char* cache_key);
+
+ // Retrieve all key value pairs from data blocks in the table.
+ // The key retrieved are internal keys.
+ Status GetKVPairsFromDataBlocks(std::vector<KVPairBlock>* kv_pair_blocks);
+
+ struct Rep;
+
+ Rep* get_rep() { return rep_; }
+ const Rep* get_rep() const { return rep_; }
+
+ // input_iter: if it is not null, update this one and return it as Iterator
+ template <typename TBlockIter>
+ TBlockIter* NewDataBlockIterator(
+ const ReadOptions& ro, const BlockHandle& block_handle,
+ TBlockIter* input_iter, BlockType block_type, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context, Status s,
+ FilePrefetchBuffer* prefetch_buffer, bool for_compaction = false) const;
+
+ // input_iter: if it is not null, update this one and return it as Iterator
+ template <typename TBlockIter>
+ TBlockIter* NewDataBlockIterator(const ReadOptions& ro,
+ CachableEntry<Block>& block,
+ TBlockIter* input_iter, Status s) const;
+
+ class PartitionedIndexIteratorState;
+
+ template <typename TBlocklike>
+ friend class FilterBlockReaderCommon;
+
+ friend class PartitionIndexReader;
+
+ friend class UncompressionDictReader;
+
+ protected:
+ Rep* rep_;
+ explicit BlockBasedTable(Rep* rep, BlockCacheTracer* const block_cache_tracer)
+ : rep_(rep), block_cache_tracer_(block_cache_tracer) {}
+ // No copying allowed
+ explicit BlockBasedTable(const TableReader&) = delete;
+ void operator=(const TableReader&) = delete;
+
+ private:
+ friend class MockedBlockBasedTable;
+ static std::atomic<uint64_t> next_cache_key_id_;
+ BlockCacheTracer* const block_cache_tracer_;
+
+ void UpdateCacheHitMetrics(BlockType block_type, GetContext* get_context,
+ size_t usage) const;
+ void UpdateCacheMissMetrics(BlockType block_type,
+ GetContext* get_context) const;
+ void UpdateCacheInsertionMetrics(BlockType block_type,
+ GetContext* get_context, size_t usage) const;
+ Cache::Handle* GetEntryFromCache(Cache* block_cache, const Slice& key,
+ BlockType block_type,
+ GetContext* get_context) const;
+
+ // Either Block::NewDataIterator() or Block::NewIndexIterator().
+ template <typename TBlockIter>
+ static TBlockIter* InitBlockIterator(const Rep* rep, Block* block,
+ TBlockIter* input_iter,
+ bool block_contents_pinned);
+
+ // If block cache enabled (compressed or uncompressed), looks for the block
+ // identified by handle in (1) uncompressed cache, (2) compressed cache, and
+ // then (3) file. If found, inserts into the cache(s) that were searched
+ // unsuccessfully (e.g., if found in file, will add to both uncompressed and
+ // compressed caches if they're enabled).
+ //
+ // @param block_entry value is set to the uncompressed block if found. If
+ // in uncompressed block cache, also sets cache_handle to reference that
+ // block.
+ template <typename TBlocklike>
+ Status MaybeReadBlockAndLoadToCache(
+ FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
+ const BlockHandle& handle, const UncompressionDict& uncompression_dict,
+ CachableEntry<TBlocklike>* block_entry, BlockType block_type,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ BlockContents* contents) const;
+
+ // Similar to the above, with one crucial difference: it will retrieve the
+ // block from the file even if there are no caches configured (assuming the
+ // read options allow I/O).
+ template <typename TBlocklike>
+ Status RetrieveBlock(FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& ro, const BlockHandle& handle,
+ const UncompressionDict& uncompression_dict,
+ CachableEntry<TBlocklike>* block_entry,
+ BlockType block_type, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ bool for_compaction, bool use_cache) const;
+
+ void RetrieveMultipleBlocks(
+ const ReadOptions& options, const MultiGetRange* batch,
+ const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
+ autovector<Status, MultiGetContext::MAX_BATCH_SIZE>* statuses,
+ autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>*
+ results,
+ char* scratch, const UncompressionDict& uncompression_dict) const;
+
+ // Get the iterator from the index reader.
+ //
+ // If input_iter is not set, return a new Iterator.
+ // If input_iter is set, try to update it and return it as Iterator.
+ // However note that in some cases the returned iterator may be different
+ // from input_iter. In such case the returned iterator should be freed.
+ //
+ // Note: ErrorIterator with Status::Incomplete shall be returned if all the
+ // following conditions are met:
+ // 1. We enabled table_options.cache_index_and_filter_blocks.
+ // 2. index is not present in block cache.
+ // 3. We disallowed any io to be performed, that is, read_options ==
+ // kBlockCacheTier
+ InternalIteratorBase<IndexValue>* NewIndexIterator(
+ const ReadOptions& read_options, bool need_upper_bound_check,
+ IndexBlockIter* input_iter, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const;
+
+ // Read block cache from block caches (if set): block_cache and
+ // block_cache_compressed.
+ // On success, Status::OK with be returned and @block will be populated with
+ // pointer to the block as well as its block handle.
+ // @param uncompression_dict Data for presetting the compression library's
+ // dictionary.
+ template <typename TBlocklike>
+ Status GetDataBlockFromCache(
+ const Slice& block_cache_key, const Slice& compressed_block_cache_key,
+ Cache* block_cache, Cache* block_cache_compressed,
+ const ReadOptions& read_options, CachableEntry<TBlocklike>* block,
+ const UncompressionDict& uncompression_dict, BlockType block_type,
+ GetContext* get_context) const;
+
+ // Put a raw block (maybe compressed) to the corresponding block caches.
+ // This method will perform decompression against raw_block if needed and then
+ // populate the block caches.
+ // On success, Status::OK will be returned; also @block will be populated with
+ // uncompressed block and its cache handle.
+ //
+ // Allocated memory managed by raw_block_contents will be transferred to
+ // PutDataBlockToCache(). After the call, the object will be invalid.
+ // @param uncompression_dict Data for presetting the compression library's
+ // dictionary.
+ template <typename TBlocklike>
+ Status PutDataBlockToCache(
+ const Slice& block_cache_key, const Slice& compressed_block_cache_key,
+ Cache* block_cache, Cache* block_cache_compressed,
+ CachableEntry<TBlocklike>* cached_block,
+ BlockContents* raw_block_contents, CompressionType raw_block_comp_type,
+ const UncompressionDict& uncompression_dict, SequenceNumber seq_no,
+ MemoryAllocator* memory_allocator, BlockType block_type,
+ GetContext* get_context) const;
+
+ // Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found
+ // after a call to Seek(key), until handle_result returns false.
+ // May not make such a call if filter policy says that key is not present.
+ friend class TableCache;
+ friend class BlockBasedTableBuilder;
+
+ // Create a index reader based on the index type stored in the table.
+ // Optionally, user can pass a preloaded meta_index_iter for the index that
+ // need to access extra meta blocks for index construction. This parameter
+ // helps avoid re-reading meta index block if caller already created one.
+ Status CreateIndexReader(FilePrefetchBuffer* prefetch_buffer,
+ InternalIterator* preloaded_meta_index_iter,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<IndexReader>* index_reader);
+
+ bool FullFilterKeyMayMatch(const ReadOptions& read_options,
+ FilterBlockReader* filter, const Slice& user_key,
+ const bool no_io,
+ const SliceTransform* prefix_extractor,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const;
+
+ void FullFilterKeysMayMatch(const ReadOptions& read_options,
+ FilterBlockReader* filter, MultiGetRange* range,
+ const bool no_io,
+ const SliceTransform* prefix_extractor,
+ BlockCacheLookupContext* lookup_context) const;
+
+ static Status PrefetchTail(
+ RandomAccessFileReader* file, uint64_t file_size,
+ TailPrefetchStats* tail_prefetch_stats, const bool prefetch_all,
+ const bool preload_all,
+ std::unique_ptr<FilePrefetchBuffer>* prefetch_buffer);
+ Status ReadMetaIndexBlock(FilePrefetchBuffer* prefetch_buffer,
+ std::unique_ptr<Block>* metaindex_block,
+ std::unique_ptr<InternalIterator>* iter);
+ Status TryReadPropertiesWithGlobalSeqno(FilePrefetchBuffer* prefetch_buffer,
+ const Slice& handle_value,
+ TableProperties** table_properties);
+ Status ReadPropertiesBlock(FilePrefetchBuffer* prefetch_buffer,
+ InternalIterator* meta_iter,
+ const SequenceNumber largest_seqno);
+ Status ReadRangeDelBlock(FilePrefetchBuffer* prefetch_buffer,
+ InternalIterator* meta_iter,
+ const InternalKeyComparator& internal_comparator,
+ BlockCacheLookupContext* lookup_context);
+ Status PrefetchIndexAndFilterBlocks(
+ FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
+ BlockBasedTable* new_table, bool prefetch_all,
+ const BlockBasedTableOptions& table_options, const int level,
+ BlockCacheLookupContext* lookup_context);
+
+ static BlockType GetBlockTypeForMetaBlockByName(const Slice& meta_block_name);
+
+ Status VerifyChecksumInMetaBlocks(InternalIteratorBase<Slice>* index_iter);
+ Status VerifyChecksumInBlocks(const ReadOptions& read_options,
+ InternalIteratorBase<IndexValue>* index_iter);
+
+ // Create the filter from the filter block.
+ std::unique_ptr<FilterBlockReader> CreateFilterBlockReader(
+ FilePrefetchBuffer* prefetch_buffer, bool use_cache, bool prefetch,
+ bool pin, BlockCacheLookupContext* lookup_context);
+
+ static void SetupCacheKeyPrefix(Rep* rep);
+
+ // Generate a cache key prefix from the file
+ static void GenerateCachePrefix(Cache* cc, FSRandomAccessFile* file,
+ char* buffer, size_t* size);
+ static void GenerateCachePrefix(Cache* cc, FSWritableFile* file, char* buffer,
+ size_t* size);
+
+ // Given an iterator return its offset in file.
+ uint64_t ApproximateOffsetOf(
+ const InternalIteratorBase<IndexValue>& index_iter) const;
+
+ // Helper functions for DumpTable()
+ Status DumpIndexBlock(WritableFile* out_file);
+ Status DumpDataBlocks(WritableFile* out_file);
+ void DumpKeyValue(const Slice& key, const Slice& value,
+ WritableFile* out_file);
+
+ // A cumulative data block file read in MultiGet lower than this size will
+ // use a stack buffer
+ static constexpr size_t kMultiGetReadStackBufSize = 8192;
+
+ friend class PartitionedFilterBlockReader;
+ friend class PartitionedFilterBlockTest;
+ friend class DBBasicTest_MultiGetIOBufferOverrun_Test;
+};
+
+// Maitaning state of a two-level iteration on a partitioned index structure.
+class BlockBasedTable::PartitionedIndexIteratorState
+ : public TwoLevelIteratorState {
+ public:
+ PartitionedIndexIteratorState(
+ const BlockBasedTable* table,
+ std::unordered_map<uint64_t, CachableEntry<Block>>* block_map);
+ InternalIteratorBase<IndexValue>* NewSecondaryIterator(
+ const BlockHandle& index_value) override;
+
+ private:
+ // Don't own table_
+ const BlockBasedTable* table_;
+ std::unordered_map<uint64_t, CachableEntry<Block>>* block_map_;
+};
+
+// Stores all the properties associated with a BlockBasedTable.
+// These are immutable.
+struct BlockBasedTable::Rep {
+ Rep(const ImmutableCFOptions& _ioptions, const EnvOptions& _env_options,
+ const BlockBasedTableOptions& _table_opt,
+ const InternalKeyComparator& _internal_comparator, bool skip_filters,
+ int _level, const bool _immortal_table)
+ : ioptions(_ioptions),
+ env_options(_env_options),
+ table_options(_table_opt),
+ filter_policy(skip_filters ? nullptr : _table_opt.filter_policy.get()),
+ internal_comparator(_internal_comparator),
+ filter_type(FilterType::kNoFilter),
+ index_type(BlockBasedTableOptions::IndexType::kBinarySearch),
+ hash_index_allow_collision(false),
+ whole_key_filtering(_table_opt.whole_key_filtering),
+ prefix_filtering(true),
+ global_seqno(kDisableGlobalSequenceNumber),
+ level(_level),
+ immortal_table(_immortal_table) {}
+
+ const ImmutableCFOptions& ioptions;
+ const EnvOptions& env_options;
+ const BlockBasedTableOptions table_options;
+ const FilterPolicy* const filter_policy;
+ const InternalKeyComparator& internal_comparator;
+ Status status;
+ std::unique_ptr<RandomAccessFileReader> file;
+ char cache_key_prefix[kMaxCacheKeyPrefixSize];
+ size_t cache_key_prefix_size = 0;
+ char persistent_cache_key_prefix[kMaxCacheKeyPrefixSize];
+ size_t persistent_cache_key_prefix_size = 0;
+ char compressed_cache_key_prefix[kMaxCacheKeyPrefixSize];
+ size_t compressed_cache_key_prefix_size = 0;
+ PersistentCacheOptions persistent_cache_options;
+
+ // Footer contains the fixed table information
+ Footer footer;
+
+ std::unique_ptr<IndexReader> index_reader;
+ std::unique_ptr<FilterBlockReader> filter;
+ std::unique_ptr<UncompressionDictReader> uncompression_dict_reader;
+
+ enum class FilterType {
+ kNoFilter,
+ kFullFilter,
+ kBlockFilter,
+ kPartitionedFilter,
+ };
+ FilterType filter_type;
+ BlockHandle filter_handle;
+ BlockHandle compression_dict_handle;
+
+ std::shared_ptr<const TableProperties> table_properties;
+ BlockBasedTableOptions::IndexType index_type;
+ bool hash_index_allow_collision;
+ bool whole_key_filtering;
+ bool prefix_filtering;
+ // TODO(kailiu) It is very ugly to use internal key in table, since table
+ // module should not be relying on db module. However to make things easier
+ // and compatible with existing code, we introduce a wrapper that allows
+ // block to extract prefix without knowing if a key is internal or not.
+ // null if no prefix_extractor is passed in when opening the table reader.
+ std::unique_ptr<SliceTransform> internal_prefix_transform;
+ std::shared_ptr<const SliceTransform> table_prefix_extractor;
+
+ std::shared_ptr<const FragmentedRangeTombstoneList> fragmented_range_dels;
+
+ // If global_seqno is used, all Keys in this file will have the same
+ // seqno with value `global_seqno`.
+ //
+ // A value of kDisableGlobalSequenceNumber means that this feature is disabled
+ // and every key have it's own seqno.
+ SequenceNumber global_seqno;
+
+ // the level when the table is opened, could potentially change when trivial
+ // move is involved
+ int level;
+
+ // If false, blocks in this file are definitely all uncompressed. Knowing this
+ // before reading individual blocks enables certain optimizations.
+ bool blocks_maybe_compressed = true;
+
+ // If true, data blocks in this file are definitely ZSTD compressed. If false
+ // they might not be. When false we skip creating a ZSTD digested
+ // uncompression dictionary. Even if we get a false negative, things should
+ // still work, just not as quickly.
+ bool blocks_definitely_zstd_compressed = false;
+
+ // These describe how index is encoded.
+ bool index_has_first_key = false;
+ bool index_key_includes_seq = true;
+ bool index_value_is_full = true;
+
+ const bool immortal_table;
+
+ SequenceNumber get_global_seqno(BlockType block_type) const {
+ return (block_type == BlockType::kFilter ||
+ block_type == BlockType::kCompressionDictionary)
+ ? kDisableGlobalSequenceNumber
+ : global_seqno;
+ }
+
+ uint64_t cf_id_for_tracing() const {
+ return table_properties
+ ? table_properties->column_family_id
+ : ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory::Context::
+ kUnknownColumnFamily;
+ }
+
+ Slice cf_name_for_tracing() const {
+ return table_properties ? table_properties->column_family_name
+ : BlockCacheTraceHelper::kUnknownColumnFamilyName;
+ }
+
+ uint32_t level_for_tracing() const { return level >= 0 ? level : UINT32_MAX; }
+
+ uint64_t sst_number_for_tracing() const {
+ return file ? TableFileNameToNumber(file->file_name()) : UINT64_MAX;
+ }
+ void CreateFilePrefetchBuffer(
+ size_t readahead_size, size_t max_readahead_size,
+ std::unique_ptr<FilePrefetchBuffer>* fpb) const {
+ fpb->reset(new FilePrefetchBuffer(file.get(), readahead_size,
+ max_readahead_size,
+ !ioptions.allow_mmap_reads /* enable */));
+ }
+};
+
+// Iterates over the contents of BlockBasedTable.
+template <class TBlockIter, typename TValue = Slice>
+class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
+ // compaction_readahead_size: its value will only be used if for_compaction =
+ // true
+ public:
+ BlockBasedTableIterator(const BlockBasedTable* table,
+ const ReadOptions& read_options,
+ const InternalKeyComparator& icomp,
+ InternalIteratorBase<IndexValue>* index_iter,
+ bool check_filter, bool need_upper_bound_check,
+ const SliceTransform* prefix_extractor,
+ BlockType block_type, TableReaderCaller caller,
+ size_t compaction_readahead_size = 0)
+ : table_(table),
+ read_options_(read_options),
+ icomp_(icomp),
+ user_comparator_(icomp.user_comparator()),
+ index_iter_(index_iter),
+ pinned_iters_mgr_(nullptr),
+ block_iter_points_to_real_block_(false),
+ check_filter_(check_filter),
+ need_upper_bound_check_(need_upper_bound_check),
+ prefix_extractor_(prefix_extractor),
+ block_type_(block_type),
+ lookup_context_(caller),
+ compaction_readahead_size_(compaction_readahead_size) {}
+
+ ~BlockBasedTableIterator() { delete index_iter_; }
+
+ void Seek(const Slice& target) override;
+ void SeekForPrev(const Slice& target) override;
+ void SeekToFirst() override;
+ void SeekToLast() override;
+ void Next() final override;
+ bool NextAndGetResult(IterateResult* result) override;
+ void Prev() override;
+ bool Valid() const override {
+ return !is_out_of_bound_ &&
+ (is_at_first_key_from_index_ ||
+ (block_iter_points_to_real_block_ && block_iter_.Valid()));
+ }
+ Slice key() const override {
+ assert(Valid());
+ if (is_at_first_key_from_index_) {
+ return index_iter_->value().first_internal_key;
+ } else {
+ return block_iter_.key();
+ }
+ }
+ Slice user_key() const override {
+ assert(Valid());
+ if (is_at_first_key_from_index_) {
+ return ExtractUserKey(index_iter_->value().first_internal_key);
+ } else {
+ return block_iter_.user_key();
+ }
+ }
+ TValue value() const override {
+ assert(Valid());
+
+ // Load current block if not loaded.
+ if (is_at_first_key_from_index_ &&
+ !const_cast<BlockBasedTableIterator*>(this)
+ ->MaterializeCurrentBlock()) {
+ // Oops, index is not consistent with block contents, but we have
+ // no good way to report error at this point. Let's return empty value.
+ return TValue();
+ }
+
+ return block_iter_.value();
+ }
+ Status status() const override {
+ // Prefix index set status to NotFound when the prefix does not exist
+ if (!index_iter_->status().ok() && !index_iter_->status().IsNotFound()) {
+ return index_iter_->status();
+ } else if (block_iter_points_to_real_block_) {
+ return block_iter_.status();
+ } else {
+ return Status::OK();
+ }
+ }
+
+ // Whether iterator invalidated for being out of bound.
+ bool IsOutOfBound() override { return is_out_of_bound_; }
+
+ inline bool MayBeOutOfUpperBound() override {
+ assert(Valid());
+ return !data_block_within_upper_bound_;
+ }
+
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ }
+ bool IsKeyPinned() const override {
+ // Our key comes either from block_iter_'s current key
+ // or index_iter_'s current *value*.
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ ((is_at_first_key_from_index_ && index_iter_->IsValuePinned()) ||
+ (block_iter_points_to_real_block_ && block_iter_.IsKeyPinned()));
+ }
+ bool IsValuePinned() const override {
+ // Load current block if not loaded.
+ if (is_at_first_key_from_index_) {
+ const_cast<BlockBasedTableIterator*>(this)->MaterializeCurrentBlock();
+ }
+ // BlockIter::IsValuePinned() is always true. No need to check
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ block_iter_points_to_real_block_;
+ }
+
+ void ResetDataIter() {
+ if (block_iter_points_to_real_block_) {
+ if (pinned_iters_mgr_ != nullptr && pinned_iters_mgr_->PinningEnabled()) {
+ block_iter_.DelegateCleanupsTo(pinned_iters_mgr_);
+ }
+ block_iter_.Invalidate(Status::OK());
+ block_iter_points_to_real_block_ = false;
+ }
+ }
+
+ void SavePrevIndexValue() {
+ if (block_iter_points_to_real_block_) {
+ // Reseek. If they end up with the same data block, we shouldn't re-fetch
+ // the same data block.
+ prev_block_offset_ = index_iter_->value().handle.offset();
+ }
+ }
+
+ private:
+ enum class IterDirection {
+ kForward,
+ kBackward,
+ };
+
+ const BlockBasedTable* table_;
+ const ReadOptions read_options_;
+ const InternalKeyComparator& icomp_;
+ UserComparatorWrapper user_comparator_;
+ InternalIteratorBase<IndexValue>* index_iter_;
+ PinnedIteratorsManager* pinned_iters_mgr_;
+ TBlockIter block_iter_;
+
+ // True if block_iter_ is initialized and points to the same block
+ // as index iterator.
+ bool block_iter_points_to_real_block_;
+ // See InternalIteratorBase::IsOutOfBound().
+ bool is_out_of_bound_ = false;
+ // Whether current data block being fully within iterate upper bound.
+ bool data_block_within_upper_bound_ = false;
+ // True if we're standing at the first key of a block, and we haven't loaded
+ // that block yet. A call to value() will trigger loading the block.
+ bool is_at_first_key_from_index_ = false;
+ bool check_filter_;
+ // TODO(Zhongyi): pick a better name
+ bool need_upper_bound_check_;
+ const SliceTransform* prefix_extractor_;
+ BlockType block_type_;
+ uint64_t prev_block_offset_ = std::numeric_limits<uint64_t>::max();
+ BlockCacheLookupContext lookup_context_;
+ // Readahead size used in compaction, its value is used only if
+ // lookup_context_.caller = kCompaction.
+ size_t compaction_readahead_size_;
+
+ size_t readahead_size_ = BlockBasedTable::kInitAutoReadaheadSize;
+ size_t readahead_limit_ = 0;
+ int64_t num_file_reads_ = 0;
+ std::unique_ptr<FilePrefetchBuffer> prefetch_buffer_;
+
+ // If `target` is null, seek to first.
+ void SeekImpl(const Slice* target);
+
+ void InitDataBlock();
+ bool MaterializeCurrentBlock();
+ void FindKeyForward();
+ void FindBlockForward();
+ void FindKeyBackward();
+ void CheckOutOfBound();
+
+ // Check if data block is fully within iterate_upper_bound.
+ //
+ // Note MyRocks may update iterate bounds between seek. To workaround it,
+ // we need to check and update data_block_within_upper_bound_ accordingly.
+ void CheckDataBlockWithinUpperBound();
+
+ bool CheckPrefixMayMatch(const Slice& ikey, IterDirection direction) {
+ if (need_upper_bound_check_ && direction == IterDirection::kBackward) {
+ // Upper bound check isn't sufficnet for backward direction to
+ // guarantee the same result as total order, so disable prefix
+ // check.
+ return true;
+ }
+ if (check_filter_ &&
+ !table_->PrefixMayMatch(ikey, read_options_, prefix_extractor_,
+ need_upper_bound_check_, &lookup_context_)) {
+ // TODO remember the iterator is invalidated because of prefix
+ // match. This can avoid the upper level file iterator to falsely
+ // believe the position is the end of the SST file and move to
+ // the first key of the next file.
+ ResetDataIter();
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_builder.cc b/storage/rocksdb/rocksdb/table/block_based/block_builder.cc
new file mode 100644
index 00000000..6f77ef97
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_builder.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// BlockBuilder generates blocks where keys are prefix-compressed:
+//
+// When we store a key, we drop the prefix shared with the previous
+// string. This helps reduce the space requirement significantly.
+// Furthermore, once every K keys, we do not apply the prefix
+// compression and store the entire key. We call this a "restart
+// point". The tail end of the block stores the offsets of all of the
+// restart points, and can be used to do a binary search when looking
+// for a particular key. Values are stored as-is (without compression)
+// immediately following the corresponding key.
+//
+// An entry for a particular key-value pair has the form:
+// shared_bytes: varint32
+// unshared_bytes: varint32
+// value_length: varint32
+// key_delta: char[unshared_bytes]
+// value: char[value_length]
+// shared_bytes == 0 for restart points.
+//
+// The trailer of the block has the form:
+// restarts: uint32[num_restarts]
+// num_restarts: uint32
+// restarts[i] contains the offset within the block of the ith restart point.
+
+#include "table/block_based/block_builder.h"
+
+#include <assert.h>
+#include <algorithm>
+#include "db/dbformat.h"
+#include "rocksdb/comparator.h"
+#include "table/block_based/data_block_footer.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+BlockBuilder::BlockBuilder(
+ int block_restart_interval, bool use_delta_encoding,
+ bool use_value_delta_encoding,
+ BlockBasedTableOptions::DataBlockIndexType index_type,
+ double data_block_hash_table_util_ratio)
+ : block_restart_interval_(block_restart_interval),
+ use_delta_encoding_(use_delta_encoding),
+ use_value_delta_encoding_(use_value_delta_encoding),
+ restarts_(),
+ counter_(0),
+ finished_(false) {
+ switch (index_type) {
+ case BlockBasedTableOptions::kDataBlockBinarySearch:
+ break;
+ case BlockBasedTableOptions::kDataBlockBinaryAndHash:
+ data_block_hash_index_builder_.Initialize(
+ data_block_hash_table_util_ratio);
+ break;
+ default:
+ assert(0);
+ }
+ assert(block_restart_interval_ >= 1);
+ restarts_.push_back(0); // First restart point is at offset 0
+ estimate_ = sizeof(uint32_t) + sizeof(uint32_t);
+}
+
+void BlockBuilder::Reset() {
+ buffer_.clear();
+ restarts_.clear();
+ restarts_.push_back(0); // First restart point is at offset 0
+ estimate_ = sizeof(uint32_t) + sizeof(uint32_t);
+ counter_ = 0;
+ finished_ = false;
+ last_key_.clear();
+ if (data_block_hash_index_builder_.Valid()) {
+ data_block_hash_index_builder_.Reset();
+ }
+}
+
+size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key,
+ const Slice& value) const {
+ size_t estimate = CurrentSizeEstimate();
+ // Note: this is an imprecise estimate as it accounts for the whole key size
+ // instead of non-shared key size.
+ estimate += key.size();
+ // In value delta encoding we estimate the value delta size as half the full
+ // value size since only the size field of block handle is encoded.
+ estimate +=
+ !use_value_delta_encoding_ || (counter_ >= block_restart_interval_)
+ ? value.size()
+ : value.size() / 2;
+
+ if (counter_ >= block_restart_interval_) {
+ estimate += sizeof(uint32_t); // a new restart entry.
+ }
+
+ estimate += sizeof(int32_t); // varint for shared prefix length.
+ // Note: this is an imprecise estimate as we will have to encoded size, one
+ // for shared key and one for non-shared key.
+ estimate += VarintLength(key.size()); // varint for key length.
+ if (!use_value_delta_encoding_ || (counter_ >= block_restart_interval_)) {
+ estimate += VarintLength(value.size()); // varint for value length.
+ }
+
+ return estimate;
+}
+
+Slice BlockBuilder::Finish() {
+ // Append restart array
+ for (size_t i = 0; i < restarts_.size(); i++) {
+ PutFixed32(&buffer_, restarts_[i]);
+ }
+
+ uint32_t num_restarts = static_cast<uint32_t>(restarts_.size());
+ BlockBasedTableOptions::DataBlockIndexType index_type =
+ BlockBasedTableOptions::kDataBlockBinarySearch;
+ if (data_block_hash_index_builder_.Valid() &&
+ CurrentSizeEstimate() <= kMaxBlockSizeSupportedByHashIndex) {
+ data_block_hash_index_builder_.Finish(buffer_);
+ index_type = BlockBasedTableOptions::kDataBlockBinaryAndHash;
+ }
+
+ // footer is a packed format of data_block_index_type and num_restarts
+ uint32_t block_footer = PackIndexTypeAndNumRestarts(index_type, num_restarts);
+
+ PutFixed32(&buffer_, block_footer);
+ finished_ = true;
+ return Slice(buffer_);
+}
+
+void BlockBuilder::Add(const Slice& key, const Slice& value,
+ const Slice* const delta_value) {
+ assert(!finished_);
+ assert(counter_ <= block_restart_interval_);
+ assert(!use_value_delta_encoding_ || delta_value);
+ size_t shared = 0; // number of bytes shared with prev key
+ if (counter_ >= block_restart_interval_) {
+ // Restart compression
+ restarts_.push_back(static_cast<uint32_t>(buffer_.size()));
+ estimate_ += sizeof(uint32_t);
+ counter_ = 0;
+
+ if (use_delta_encoding_) {
+ // Update state
+ last_key_.assign(key.data(), key.size());
+ }
+ } else if (use_delta_encoding_) {
+ Slice last_key_piece(last_key_);
+ // See how much sharing to do with previous string
+ shared = key.difference_offset(last_key_piece);
+
+ // Update state
+ // We used to just copy the changed data here, but it appears to be
+ // faster to just copy the whole thing.
+ last_key_.assign(key.data(), key.size());
+ }
+
+ const size_t non_shared = key.size() - shared;
+ const size_t curr_size = buffer_.size();
+
+ if (use_value_delta_encoding_) {
+ // Add "<shared><non_shared>" to buffer_
+ PutVarint32Varint32(&buffer_, static_cast<uint32_t>(shared),
+ static_cast<uint32_t>(non_shared));
+ } else {
+ // Add "<shared><non_shared><value_size>" to buffer_
+ PutVarint32Varint32Varint32(&buffer_, static_cast<uint32_t>(shared),
+ static_cast<uint32_t>(non_shared),
+ static_cast<uint32_t>(value.size()));
+ }
+
+ // Add string delta to buffer_ followed by value
+ buffer_.append(key.data() + shared, non_shared);
+ // Use value delta encoding only when the key has shared bytes. This would
+ // simplify the decoding, where it can figure which decoding to use simply by
+ // looking at the shared bytes size.
+ if (shared != 0 && use_value_delta_encoding_) {
+ buffer_.append(delta_value->data(), delta_value->size());
+ } else {
+ buffer_.append(value.data(), value.size());
+ }
+
+ if (data_block_hash_index_builder_.Valid()) {
+ data_block_hash_index_builder_.Add(ExtractUserKey(key),
+ restarts_.size() - 1);
+ }
+
+ counter_++;
+ estimate_ += buffer_.size() - curr_size;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_builder.h b/storage/rocksdb/rocksdb/table/block_based/block_builder.h
new file mode 100644
index 00000000..42c996e5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_builder.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <vector>
+
+#include <stdint.h>
+#include "rocksdb/slice.h"
+#include "rocksdb/table.h"
+#include "table/block_based/data_block_hash_index.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBuilder {
+ public:
+ BlockBuilder(const BlockBuilder&) = delete;
+ void operator=(const BlockBuilder&) = delete;
+
+ explicit BlockBuilder(int block_restart_interval,
+ bool use_delta_encoding = true,
+ bool use_value_delta_encoding = false,
+ BlockBasedTableOptions::DataBlockIndexType index_type =
+ BlockBasedTableOptions::kDataBlockBinarySearch,
+ double data_block_hash_table_util_ratio = 0.75);
+
+ // Reset the contents as if the BlockBuilder was just constructed.
+ void Reset();
+
+ // REQUIRES: Finish() has not been called since the last call to Reset().
+ // REQUIRES: key is larger than any previously added key
+ void Add(const Slice& key, const Slice& value,
+ const Slice* const delta_value = nullptr);
+
+ // Finish building the block and return a slice that refers to the
+ // block contents. The returned slice will remain valid for the
+ // lifetime of this builder or until Reset() is called.
+ Slice Finish();
+
+ // Returns an estimate of the current (uncompressed) size of the block
+ // we are building.
+ inline size_t CurrentSizeEstimate() const {
+ return estimate_ + (data_block_hash_index_builder_.Valid()
+ ? data_block_hash_index_builder_.EstimateSize()
+ : 0);
+ }
+
+ // Returns an estimated block size after appending key and value.
+ size_t EstimateSizeAfterKV(const Slice& key, const Slice& value) const;
+
+ // Return true iff no entries have been added since the last Reset()
+ bool empty() const { return buffer_.empty(); }
+
+ private:
+ const int block_restart_interval_;
+ // TODO(myabandeh): put it into a separate IndexBlockBuilder
+ const bool use_delta_encoding_;
+ // Refer to BlockIter::DecodeCurrentValue for format of delta encoded values
+ const bool use_value_delta_encoding_;
+
+ std::string buffer_; // Destination buffer
+ std::vector<uint32_t> restarts_; // Restart points
+ size_t estimate_;
+ int counter_; // Number of entries emitted since restart
+ bool finished_; // Has Finish() been called?
+ std::string last_key_;
+ DataBlockHashIndexBuilder data_block_hash_index_builder_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.cc b/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.cc
new file mode 100644
index 00000000..f9d92c74
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/block_prefix_index.h"
+
+#include <vector>
+
+#include "memory/arena.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+inline uint32_t Hash(const Slice& s) {
+ return ROCKSDB_NAMESPACE::Hash(s.data(), s.size(), 0);
+}
+
+inline uint32_t PrefixToBucket(const Slice& prefix, uint32_t num_buckets) {
+ return Hash(prefix) % num_buckets;
+}
+
+// The prefix block index is simply a bucket array, with each entry pointing to
+// the blocks that span the prefixes hashed to this bucket.
+//
+// To reduce memory footprint, if there is only one block per bucket, the entry
+// stores the block id directly. If there are more than one blocks per bucket,
+// because of hash collision or a single prefix spanning multiple blocks,
+// the entry points to an array of block ids. The block array is an array of
+// uint32_t's. The first uint32_t indicates the total number of blocks, followed
+// by the block ids.
+//
+// To differentiate the two cases, the high order bit of the entry indicates
+// whether it is a 'pointer' into a separate block array.
+// 0x7FFFFFFF is reserved for empty bucket.
+
+const uint32_t kNoneBlock = 0x7FFFFFFF;
+const uint32_t kBlockArrayMask = 0x80000000;
+
+inline bool IsNone(uint32_t block_id) { return block_id == kNoneBlock; }
+
+inline bool IsBlockId(uint32_t block_id) {
+ return (block_id & kBlockArrayMask) == 0;
+}
+
+inline uint32_t DecodeIndex(uint32_t block_id) {
+ uint32_t index = block_id ^ kBlockArrayMask;
+ assert(index < kBlockArrayMask);
+ return index;
+}
+
+inline uint32_t EncodeIndex(uint32_t index) {
+ assert(index < kBlockArrayMask);
+ return index | kBlockArrayMask;
+}
+
+// temporary storage for prefix information during index building
+struct PrefixRecord {
+ Slice prefix;
+ uint32_t start_block;
+ uint32_t end_block;
+ uint32_t num_blocks;
+ PrefixRecord* next;
+};
+
+class BlockPrefixIndex::Builder {
+ public:
+ explicit Builder(const SliceTransform* internal_prefix_extractor)
+ : internal_prefix_extractor_(internal_prefix_extractor) {}
+
+ void Add(const Slice& key_prefix, uint32_t start_block, uint32_t num_blocks) {
+ PrefixRecord* record = reinterpret_cast<PrefixRecord*>(
+ arena_.AllocateAligned(sizeof(PrefixRecord)));
+ record->prefix = key_prefix;
+ record->start_block = start_block;
+ record->end_block = start_block + num_blocks - 1;
+ record->num_blocks = num_blocks;
+ prefixes_.push_back(record);
+ }
+
+ BlockPrefixIndex* Finish() {
+ // For now, use roughly 1:1 prefix to bucket ratio.
+ uint32_t num_buckets = static_cast<uint32_t>(prefixes_.size()) + 1;
+
+ // Collect prefix records that hash to the same bucket, into a single
+ // linklist.
+ std::vector<PrefixRecord*> prefixes_per_bucket(num_buckets, nullptr);
+ std::vector<uint32_t> num_blocks_per_bucket(num_buckets, 0);
+ for (PrefixRecord* current : prefixes_) {
+ uint32_t bucket = PrefixToBucket(current->prefix, num_buckets);
+ // merge the prefix block span if the first block of this prefix is
+ // connected to the last block of the previous prefix.
+ PrefixRecord* prev = prefixes_per_bucket[bucket];
+ if (prev) {
+ assert(current->start_block >= prev->end_block);
+ auto distance = current->start_block - prev->end_block;
+ if (distance <= 1) {
+ prev->end_block = current->end_block;
+ prev->num_blocks = prev->end_block - prev->start_block + 1;
+ num_blocks_per_bucket[bucket] += (current->num_blocks + distance - 1);
+ continue;
+ }
+ }
+ current->next = prev;
+ prefixes_per_bucket[bucket] = current;
+ num_blocks_per_bucket[bucket] += current->num_blocks;
+ }
+
+ // Calculate the block array buffer size
+ uint32_t total_block_array_entries = 0;
+ for (uint32_t i = 0; i < num_buckets; i++) {
+ uint32_t num_blocks = num_blocks_per_bucket[i];
+ if (num_blocks > 1) {
+ total_block_array_entries += (num_blocks + 1);
+ }
+ }
+
+ // Populate the final prefix block index
+ uint32_t* block_array_buffer = new uint32_t[total_block_array_entries];
+ uint32_t* buckets = new uint32_t[num_buckets];
+ uint32_t offset = 0;
+ for (uint32_t i = 0; i < num_buckets; i++) {
+ uint32_t num_blocks = num_blocks_per_bucket[i];
+ if (num_blocks == 0) {
+ assert(prefixes_per_bucket[i] == nullptr);
+ buckets[i] = kNoneBlock;
+ } else if (num_blocks == 1) {
+ assert(prefixes_per_bucket[i] != nullptr);
+ assert(prefixes_per_bucket[i]->next == nullptr);
+ buckets[i] = prefixes_per_bucket[i]->start_block;
+ } else {
+ assert(total_block_array_entries > 0);
+ assert(prefixes_per_bucket[i] != nullptr);
+ buckets[i] = EncodeIndex(offset);
+ block_array_buffer[offset] = num_blocks;
+ uint32_t* last_block = &block_array_buffer[offset + num_blocks];
+ auto current = prefixes_per_bucket[i];
+ // populate block ids from largest to smallest
+ while (current != nullptr) {
+ for (uint32_t iter = 0; iter < current->num_blocks; iter++) {
+ *last_block = current->end_block - iter;
+ last_block--;
+ }
+ current = current->next;
+ }
+ assert(last_block == &block_array_buffer[offset]);
+ offset += (num_blocks + 1);
+ }
+ }
+
+ assert(offset == total_block_array_entries);
+
+ return new BlockPrefixIndex(internal_prefix_extractor_, num_buckets,
+ buckets, total_block_array_entries,
+ block_array_buffer);
+ }
+
+ private:
+ const SliceTransform* internal_prefix_extractor_;
+
+ std::vector<PrefixRecord*> prefixes_;
+ Arena arena_;
+};
+
+Status BlockPrefixIndex::Create(const SliceTransform* internal_prefix_extractor,
+ const Slice& prefixes, const Slice& prefix_meta,
+ BlockPrefixIndex** prefix_index) {
+ uint64_t pos = 0;
+ auto meta_pos = prefix_meta;
+ Status s;
+ Builder builder(internal_prefix_extractor);
+
+ while (!meta_pos.empty()) {
+ uint32_t prefix_size = 0;
+ uint32_t entry_index = 0;
+ uint32_t num_blocks = 0;
+ if (!GetVarint32(&meta_pos, &prefix_size) ||
+ !GetVarint32(&meta_pos, &entry_index) ||
+ !GetVarint32(&meta_pos, &num_blocks)) {
+ s = Status::Corruption(
+ "Corrupted prefix meta block: unable to read from it.");
+ break;
+ }
+ if (pos + prefix_size > prefixes.size()) {
+ s = Status::Corruption(
+ "Corrupted prefix meta block: size inconsistency.");
+ break;
+ }
+ Slice prefix(prefixes.data() + pos, prefix_size);
+ builder.Add(prefix, entry_index, num_blocks);
+
+ pos += prefix_size;
+ }
+
+ if (s.ok() && pos != prefixes.size()) {
+ s = Status::Corruption("Corrupted prefix meta block");
+ }
+
+ if (s.ok()) {
+ *prefix_index = builder.Finish();
+ }
+
+ return s;
+}
+
+uint32_t BlockPrefixIndex::GetBlocks(const Slice& key, uint32_t** blocks) {
+ Slice prefix = internal_prefix_extractor_->Transform(key);
+
+ uint32_t bucket = PrefixToBucket(prefix, num_buckets_);
+ uint32_t block_id = buckets_[bucket];
+
+ if (IsNone(block_id)) {
+ return 0;
+ } else if (IsBlockId(block_id)) {
+ *blocks = &buckets_[bucket];
+ return 1;
+ } else {
+ uint32_t index = DecodeIndex(block_id);
+ assert(index < num_block_array_buffer_entries_);
+ *blocks = &block_array_buffer_[index + 1];
+ uint32_t num_blocks = block_array_buffer_[index];
+ assert(num_blocks > 1);
+ assert(index + num_blocks < num_block_array_buffer_entries_);
+ return num_blocks;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.h b/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.h
new file mode 100644
index 00000000..04121320
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_prefix_index.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Comparator;
+class Iterator;
+class Slice;
+class SliceTransform;
+
+// Build a hash-based index to speed up the lookup for "index block".
+// BlockHashIndex accepts a key and, if found, returns its restart index within
+// that index block.
+class BlockPrefixIndex {
+ public:
+ // Maps a key to a list of data blocks that could potentially contain
+ // the key, based on the prefix.
+ // Returns the total number of relevant blocks, 0 means the key does
+ // not exist.
+ uint32_t GetBlocks(const Slice& key, uint32_t** blocks);
+
+ size_t ApproximateMemoryUsage() const {
+ return sizeof(BlockPrefixIndex) +
+ (num_block_array_buffer_entries_ + num_buckets_) * sizeof(uint32_t);
+ }
+
+ // Create hash index by reading from the metadata blocks.
+ // @params prefixes: a sequence of prefixes.
+ // @params prefix_meta: contains the "metadata" to of the prefixes.
+ static Status Create(const SliceTransform* hash_key_extractor,
+ const Slice& prefixes, const Slice& prefix_meta,
+ BlockPrefixIndex** prefix_index);
+
+ ~BlockPrefixIndex() {
+ delete[] buckets_;
+ delete[] block_array_buffer_;
+ }
+
+ private:
+ class Builder;
+ friend Builder;
+
+ BlockPrefixIndex(const SliceTransform* internal_prefix_extractor,
+ uint32_t num_buckets, uint32_t* buckets,
+ uint32_t num_block_array_buffer_entries,
+ uint32_t* block_array_buffer)
+ : internal_prefix_extractor_(internal_prefix_extractor),
+ num_buckets_(num_buckets),
+ num_block_array_buffer_entries_(num_block_array_buffer_entries),
+ buckets_(buckets),
+ block_array_buffer_(block_array_buffer) {}
+
+ const SliceTransform* internal_prefix_extractor_;
+ uint32_t num_buckets_;
+ uint32_t num_block_array_buffer_entries_;
+ uint32_t* buckets_;
+ uint32_t* block_array_buffer_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_test.cc b/storage/rocksdb/rocksdb/table/block_based/block_test.cc
new file mode 100644
index 00000000..efa5b3ae
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_test.cc
@@ -0,0 +1,627 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <algorithm>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "db/memtable.h"
+#include "db/write_batch_internal.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_builder.h"
+#include "table/format.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static std::string RandomString(Random *rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+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 += RandomString(rnd, padding_size);
+ }
+
+ return k;
+}
+
+// Generate random key value pairs.
+// The generated key will be sorted. You can tune the parameters to generated
+// different kinds of test key/value pairs for different scenario.
+void GenerateRandomKVs(std::vector<std::string> *keys,
+ std::vector<std::string> *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 shares 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(RandomString(&rnd, 100));
+ }
+ }
+}
+
+class BlockTest : public testing::Test {};
+
+// block test
+TEST_F(BlockTest, SimpleTest) {
+ Random rnd(301);
+ Options options = Options();
+
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+ BlockBuilder builder(16);
+ int num_records = 100000;
+
+ GenerateRandomKVs(&keys, &values, 0, num_records);
+ // add a bunch of records to a block
+ for (int i = 0; i < num_records; i++) {
+ builder.Add(keys[i], values[i]);
+ }
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ // read contents of block sequentially
+ int count = 0;
+ InternalIterator *iter =
+ reader.NewDataIterator(options.comparator, options.comparator);
+ for (iter->SeekToFirst(); iter->Valid(); count++, iter->Next()) {
+ // read kv from block
+ Slice k = iter->key();
+ Slice v = iter->value();
+
+ // compare with lookaside array
+ ASSERT_EQ(k.ToString().compare(keys[count]), 0);
+ ASSERT_EQ(v.ToString().compare(values[count]), 0);
+ }
+ delete iter;
+
+ // read block contents randomly
+ iter = reader.NewDataIterator(options.comparator, options.comparator);
+ for (int i = 0; i < num_records; i++) {
+ // find a random key in the lookaside array
+ int index = rnd.Uniform(num_records);
+ Slice k(keys[index]);
+
+ // search in block for this key
+ iter->Seek(k);
+ ASSERT_TRUE(iter->Valid());
+ Slice v = iter->value();
+ ASSERT_EQ(v.ToString().compare(values[index]), 0);
+ }
+ delete iter;
+}
+
+// return the block contents
+BlockContents GetBlockContents(std::unique_ptr<BlockBuilder> *builder,
+ const std::vector<std::string> &keys,
+ const std::vector<std::string> &values,
+ const int /*prefix_group_size*/ = 1) {
+ builder->reset(new BlockBuilder(1 /* restart interval */));
+
+ // Add only half of the keys
+ for (size_t i = 0; i < keys.size(); ++i) {
+ (*builder)->Add(keys[i], values[i]);
+ }
+ Slice rawblock = (*builder)->Finish();
+
+ BlockContents contents;
+ contents.data = rawblock;
+
+ return contents;
+}
+
+void CheckBlockContents(BlockContents contents, const int max_key,
+ const std::vector<std::string> &keys,
+ const std::vector<std::string> &values) {
+ const size_t prefix_size = 6;
+ // create block reader
+ BlockContents contents_ref(contents.data);
+ Block reader1(std::move(contents), kDisableGlobalSequenceNumber);
+ Block reader2(std::move(contents_ref), kDisableGlobalSequenceNumber);
+
+ std::unique_ptr<const SliceTransform> prefix_extractor(
+ NewFixedPrefixTransform(prefix_size));
+
+ std::unique_ptr<InternalIterator> regular_iter(
+ reader2.NewDataIterator(BytewiseComparator(), BytewiseComparator()));
+
+ // Seek existent keys
+ for (size_t i = 0; i < keys.size(); i++) {
+ regular_iter->Seek(keys[i]);
+ ASSERT_OK(regular_iter->status());
+ ASSERT_TRUE(regular_iter->Valid());
+
+ Slice v = regular_iter->value();
+ ASSERT_EQ(v.ToString().compare(values[i]), 0);
+ }
+
+ // Seek non-existent keys.
+ // For hash index, if no key with a given prefix is not found, iterator will
+ // simply be set as invalid; whereas the binary search based iterator will
+ // return the one that is closest.
+ for (int i = 1; i < max_key - 1; i += 2) {
+ auto key = GenerateKey(i, 0, 0, nullptr);
+ regular_iter->Seek(key);
+ ASSERT_TRUE(regular_iter->Valid());
+ }
+}
+
+// In this test case, no two key share same prefix.
+TEST_F(BlockTest, SimpleIndexHash) {
+ const int kMaxKey = 100000;
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+ GenerateRandomKVs(&keys, &values, 0 /* first key id */,
+ kMaxKey /* last key id */, 2 /* step */,
+ 8 /* padding size (8 bytes randomly generated suffix) */);
+
+ std::unique_ptr<BlockBuilder> builder;
+ auto contents = GetBlockContents(&builder, keys, values);
+
+ CheckBlockContents(std::move(contents), kMaxKey, keys, values);
+}
+
+TEST_F(BlockTest, IndexHashWithSharedPrefix) {
+ const int kMaxKey = 100000;
+ // for each prefix, there will be 5 keys starts with it.
+ const int kPrefixGroup = 5;
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+ // Generate keys with same prefix.
+ GenerateRandomKVs(&keys, &values, 0, // first key id
+ kMaxKey, // last key id
+ 2, // step
+ 10, // padding size,
+ kPrefixGroup);
+
+ std::unique_ptr<BlockBuilder> builder;
+ auto contents = GetBlockContents(&builder, keys, values, kPrefixGroup);
+
+ CheckBlockContents(std::move(contents), kMaxKey, keys, values);
+}
+
+// A slow and accurate version of BlockReadAmpBitmap that simply store
+// all the marked ranges in a set.
+class BlockReadAmpBitmapSlowAndAccurate {
+ public:
+ void Mark(size_t start_offset, size_t end_offset) {
+ assert(end_offset >= start_offset);
+ marked_ranges_.emplace(end_offset, start_offset);
+ }
+
+ void ResetCheckSequence() { iter_valid_ = false; }
+
+ // Return true if any byte in this range was Marked
+ // This does linear search from the previous position. When calling
+ // multiple times, `offset` needs to be incremental to get correct results.
+ // Call ResetCheckSequence() to reset it.
+ bool IsPinMarked(size_t offset) {
+ if (iter_valid_) {
+ // Has existing iterator, try linear search from
+ // the iterator.
+ for (int i = 0; i < 64; i++) {
+ if (offset < iter_->second) {
+ return false;
+ }
+ if (offset <= iter_->first) {
+ return true;
+ }
+
+ iter_++;
+ if (iter_ == marked_ranges_.end()) {
+ iter_valid_ = false;
+ return false;
+ }
+ }
+ }
+ // Initial call or have linear searched too many times.
+ // Do binary search.
+ iter_ = marked_ranges_.lower_bound(
+ std::make_pair(offset, static_cast<size_t>(0)));
+ if (iter_ == marked_ranges_.end()) {
+ iter_valid_ = false;
+ return false;
+ }
+ iter_valid_ = true;
+ return offset <= iter_->first && offset >= iter_->second;
+ }
+
+ private:
+ std::set<std::pair<size_t, size_t>> marked_ranges_;
+ std::set<std::pair<size_t, size_t>>::iterator iter_;
+ bool iter_valid_ = false;
+};
+
+TEST_F(BlockTest, BlockReadAmpBitmap) {
+ uint32_t pin_offset = 0;
+ SyncPoint::GetInstance()->SetCallBack(
+ "BlockReadAmpBitmap:rnd", [&pin_offset](void *arg) {
+ pin_offset = *(static_cast<uint32_t *>(arg));
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ std::vector<size_t> block_sizes = {
+ 1, // 1 byte
+ 32, // 32 bytes
+ 61, // 61 bytes
+ 64, // 64 bytes
+ 512, // 0.5 KB
+ 1024, // 1 KB
+ 1024 * 4, // 4 KB
+ 1024 * 10, // 10 KB
+ 1024 * 50, // 50 KB
+ 1024 * 1024 * 4, // 5 MB
+ 777,
+ 124653,
+ };
+ const size_t kBytesPerBit = 64;
+
+ Random rnd(301);
+ for (size_t block_size : block_sizes) {
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ BlockReadAmpBitmap read_amp_bitmap(block_size, kBytesPerBit, stats.get());
+ BlockReadAmpBitmapSlowAndAccurate read_amp_slow_and_accurate;
+
+ size_t needed_bits = (block_size / kBytesPerBit);
+ if (block_size % kBytesPerBit != 0) {
+ needed_bits++;
+ }
+
+ ASSERT_EQ(stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES), block_size);
+
+ // Generate some random entries
+ std::vector<size_t> random_entry_offsets;
+ for (int i = 0; i < 1000; i++) {
+ random_entry_offsets.push_back(rnd.Next() % block_size);
+ }
+ std::sort(random_entry_offsets.begin(), random_entry_offsets.end());
+ auto it =
+ std::unique(random_entry_offsets.begin(), random_entry_offsets.end());
+ random_entry_offsets.resize(
+ std::distance(random_entry_offsets.begin(), it));
+
+ std::vector<std::pair<size_t, size_t>> random_entries;
+ for (size_t i = 0; i < random_entry_offsets.size(); i++) {
+ size_t entry_start = random_entry_offsets[i];
+ size_t entry_end;
+ if (i + 1 < random_entry_offsets.size()) {
+ entry_end = random_entry_offsets[i + 1] - 1;
+ } else {
+ entry_end = block_size - 1;
+ }
+ random_entries.emplace_back(entry_start, entry_end);
+ }
+
+ for (size_t i = 0; i < random_entries.size(); i++) {
+ read_amp_slow_and_accurate.ResetCheckSequence();
+ auto &current_entry = random_entries[rnd.Next() % random_entries.size()];
+
+ read_amp_bitmap.Mark(static_cast<uint32_t>(current_entry.first),
+ static_cast<uint32_t>(current_entry.second));
+ read_amp_slow_and_accurate.Mark(current_entry.first,
+ current_entry.second);
+
+ size_t total_bits = 0;
+ for (size_t bit_idx = 0; bit_idx < needed_bits; bit_idx++) {
+ total_bits += read_amp_slow_and_accurate.IsPinMarked(
+ bit_idx * kBytesPerBit + pin_offset);
+ }
+ size_t expected_estimate_useful = total_bits * kBytesPerBit;
+ size_t got_estimate_useful =
+ stats->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES);
+ ASSERT_EQ(expected_estimate_useful, got_estimate_useful);
+ }
+ }
+ SyncPoint::GetInstance()->DisableProcessing();
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+TEST_F(BlockTest, BlockWithReadAmpBitmap) {
+ Random rnd(301);
+ Options options = Options();
+
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+ BlockBuilder builder(16);
+ int num_records = 10000;
+
+ GenerateRandomKVs(&keys, &values, 0, num_records, 1);
+ // add a bunch of records to a block
+ for (int i = 0; i < num_records; i++) {
+ builder.Add(keys[i], values[i]);
+ }
+
+ Slice rawblock = builder.Finish();
+ const size_t kBytesPerBit = 8;
+
+ // Read the block sequentially using Next()
+ {
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber,
+ kBytesPerBit, stats.get());
+
+ // read contents of block sequentially
+ size_t read_bytes = 0;
+ DataBlockIter *iter = reader.NewDataIterator(
+ options.comparator, options.comparator, nullptr, stats.get());
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ iter->value();
+ read_bytes += iter->TEST_CurrentEntrySize();
+
+ double semi_acc_read_amp =
+ static_cast<double>(read_bytes) / rawblock.size();
+ double read_amp = static_cast<double>(stats->getTickerCount(
+ READ_AMP_ESTIMATE_USEFUL_BYTES)) /
+ stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ // Error in read amplification will be less than 1% if we are reading
+ // sequentially
+ double error_pct = fabs(semi_acc_read_amp - read_amp) * 100;
+ EXPECT_LT(error_pct, 1);
+ }
+
+ delete iter;
+ }
+
+ // Read the block sequentially using Seek()
+ {
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber,
+ kBytesPerBit, stats.get());
+
+ size_t read_bytes = 0;
+ DataBlockIter *iter = reader.NewDataIterator(
+ options.comparator, options.comparator, nullptr, stats.get());
+ for (int i = 0; i < num_records; i++) {
+ Slice k(keys[i]);
+
+ // search in block for this key
+ iter->Seek(k);
+ iter->value();
+ read_bytes += iter->TEST_CurrentEntrySize();
+
+ double semi_acc_read_amp =
+ static_cast<double>(read_bytes) / rawblock.size();
+ double read_amp = static_cast<double>(stats->getTickerCount(
+ READ_AMP_ESTIMATE_USEFUL_BYTES)) /
+ stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ // Error in read amplification will be less than 1% if we are reading
+ // sequentially
+ double error_pct = fabs(semi_acc_read_amp - read_amp) * 100;
+ EXPECT_LT(error_pct, 1);
+ }
+ delete iter;
+ }
+
+ // Read the block randomly
+ {
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber,
+ kBytesPerBit, stats.get());
+
+ size_t read_bytes = 0;
+ DataBlockIter *iter = reader.NewDataIterator(
+ options.comparator, options.comparator, nullptr, stats.get());
+ std::unordered_set<int> read_keys;
+ for (int i = 0; i < num_records; i++) {
+ int index = rnd.Uniform(num_records);
+ Slice k(keys[index]);
+
+ iter->Seek(k);
+ iter->value();
+ if (read_keys.find(index) == read_keys.end()) {
+ read_keys.insert(index);
+ read_bytes += iter->TEST_CurrentEntrySize();
+ }
+
+ double semi_acc_read_amp =
+ static_cast<double>(read_bytes) / rawblock.size();
+ double read_amp = static_cast<double>(stats->getTickerCount(
+ READ_AMP_ESTIMATE_USEFUL_BYTES)) /
+ stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES);
+
+ double error_pct = fabs(semi_acc_read_amp - read_amp) * 100;
+ // Error in read amplification will be less than 2% if we are reading
+ // randomly
+ EXPECT_LT(error_pct, 2);
+ }
+ delete iter;
+ }
+}
+
+TEST_F(BlockTest, ReadAmpBitmapPow2) {
+ std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ ASSERT_EQ(BlockReadAmpBitmap(100, 1, stats.get()).GetBytesPerBit(), 1u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 2, stats.get()).GetBytesPerBit(), 2u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 4, stats.get()).GetBytesPerBit(), 4u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 8, stats.get()).GetBytesPerBit(), 8u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 16, stats.get()).GetBytesPerBit(), 16u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 32, stats.get()).GetBytesPerBit(), 32u);
+
+ ASSERT_EQ(BlockReadAmpBitmap(100, 3, stats.get()).GetBytesPerBit(), 2u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 7, stats.get()).GetBytesPerBit(), 4u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 11, stats.get()).GetBytesPerBit(), 8u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 17, stats.get()).GetBytesPerBit(), 16u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 33, stats.get()).GetBytesPerBit(), 32u);
+ ASSERT_EQ(BlockReadAmpBitmap(100, 35, stats.get()).GetBytesPerBit(), 32u);
+}
+
+class IndexBlockTest
+ : public testing::Test,
+ public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+ IndexBlockTest() = default;
+
+ bool useValueDeltaEncoding() const { return std::get<0>(GetParam()); }
+ bool includeFirstKey() const { return std::get<1>(GetParam()); }
+};
+
+// Similar to GenerateRandomKVs but for index block contents.
+void GenerateRandomIndexEntries(std::vector<std::string> *separators,
+ std::vector<BlockHandle> *block_handles,
+ std::vector<std::string> *first_keys,
+ const int len) {
+ Random rnd(42);
+
+ // For each of `len` blocks, we need to generate a first and last key.
+ // Let's generate n*2 random keys, sort them, group into consecutive pairs.
+ std::set<std::string> keys;
+ while ((int)keys.size() < len * 2) {
+ // Keys need to be at least 8 bytes long to look like internal keys.
+ keys.insert(test::RandomKey(&rnd, 12));
+ }
+
+ uint64_t offset = 0;
+ for (auto it = keys.begin(); it != keys.end();) {
+ first_keys->emplace_back(*it++);
+ separators->emplace_back(*it++);
+ uint64_t size = rnd.Uniform(1024 * 16);
+ BlockHandle handle(offset, size);
+ offset += size + kBlockTrailerSize;
+ block_handles->emplace_back(handle);
+ }
+}
+
+TEST_P(IndexBlockTest, IndexValueEncodingTest) {
+ Random rnd(301);
+ Options options = Options();
+
+ std::vector<std::string> separators;
+ std::vector<BlockHandle> block_handles;
+ std::vector<std::string> first_keys;
+ const bool kUseDeltaEncoding = true;
+ BlockBuilder builder(16, kUseDeltaEncoding, useValueDeltaEncoding());
+ int num_records = 100;
+
+ GenerateRandomIndexEntries(&separators, &block_handles, &first_keys,
+ num_records);
+ BlockHandle last_encoded_handle;
+ for (int i = 0; i < num_records; i++) {
+ IndexValue entry(block_handles[i], first_keys[i]);
+ std::string encoded_entry;
+ std::string delta_encoded_entry;
+ entry.EncodeTo(&encoded_entry, includeFirstKey(), nullptr);
+ if (useValueDeltaEncoding() && i > 0) {
+ entry.EncodeTo(&delta_encoded_entry, includeFirstKey(),
+ &last_encoded_handle);
+ }
+ last_encoded_handle = entry.handle;
+ const Slice delta_encoded_entry_slice(delta_encoded_entry);
+ builder.Add(separators[i], encoded_entry, &delta_encoded_entry_slice);
+ }
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ const bool kTotalOrderSeek = true;
+ const bool kIncludesSeq = true;
+ const bool kValueIsFull = !useValueDeltaEncoding();
+ IndexBlockIter *kNullIter = nullptr;
+ Statistics *kNullStats = nullptr;
+ // read contents of block sequentially
+ InternalIteratorBase<IndexValue> *iter = reader.NewIndexIterator(
+ options.comparator, options.comparator, kNullIter, kNullStats,
+ kTotalOrderSeek, includeFirstKey(), kIncludesSeq, kValueIsFull);
+ iter->SeekToFirst();
+ for (int index = 0; index < num_records; ++index) {
+ ASSERT_TRUE(iter->Valid());
+
+ Slice k = iter->key();
+ IndexValue v = iter->value();
+
+ EXPECT_EQ(separators[index], k.ToString());
+ EXPECT_EQ(block_handles[index].offset(), v.handle.offset());
+ EXPECT_EQ(block_handles[index].size(), v.handle.size());
+ EXPECT_EQ(includeFirstKey() ? first_keys[index] : "",
+ v.first_internal_key.ToString());
+
+ iter->Next();
+ }
+ delete iter;
+
+ // read block contents randomly
+ iter = reader.NewIndexIterator(options.comparator, options.comparator,
+ kNullIter, kNullStats, kTotalOrderSeek,
+ includeFirstKey(), kIncludesSeq, kValueIsFull);
+ for (int i = 0; i < num_records * 2; i++) {
+ // find a random key in the lookaside array
+ int index = rnd.Uniform(num_records);
+ Slice k(separators[index]);
+
+ // search in block for this key
+ iter->Seek(k);
+ ASSERT_TRUE(iter->Valid());
+ IndexValue v = iter->value();
+ EXPECT_EQ(separators[index], iter->key().ToString());
+ EXPECT_EQ(block_handles[index].offset(), v.handle.offset());
+ EXPECT_EQ(block_handles[index].size(), v.handle.size());
+ EXPECT_EQ(includeFirstKey() ? first_keys[index] : "",
+ v.first_internal_key.ToString());
+ }
+ delete iter;
+}
+
+INSTANTIATE_TEST_CASE_P(P, IndexBlockTest,
+ ::testing::Values(std::make_tuple(false, false),
+ std::make_tuple(false, true),
+ std::make_tuple(true, false),
+ std::make_tuple(true, true)));
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/block_based/block_type.h b/storage/rocksdb/rocksdb/table/block_based/block_type.h
new file mode 100644
index 00000000..b2a91374
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/block_type.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdint>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Represents the types of blocks used in the block based table format.
+// See https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format
+// for details.
+
+enum class BlockType : uint8_t {
+ kData,
+ kFilter,
+ kProperties,
+ kCompressionDictionary,
+ kRangeDeletion,
+ kHashIndexPrefixes,
+ kHashIndexMetadata,
+ kMetaIndex,
+ kIndex,
+ // Note: keep kInvalid the last value when adding new enum values.
+ kInvalid
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/cachable_entry.h b/storage/rocksdb/rocksdb/table/block_based/cachable_entry.h
new file mode 100644
index 00000000..598f1ef5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/cachable_entry.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+#pragma once
+
+#include <cassert>
+#include "port/likely.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/cleanable.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// CachableEntry is a handle to an object that may or may not be in the block
+// cache. It is used in a variety of ways:
+//
+// 1) It may refer to an object in the block cache. In this case, cache_ and
+// cache_handle_ are not nullptr, and the cache handle has to be released when
+// the CachableEntry is destroyed (the lifecycle of the cached object, on the
+// other hand, is managed by the cache itself).
+// 2) It may uniquely own the (non-cached) object it refers to (examples include
+// a block read directly from file, or uncompressed blocks when there is a
+// compressed block cache but no uncompressed block cache). In such cases, the
+// object has to be destroyed when the CachableEntry is destroyed.
+// 3) It may point to an object (cached or not) without owning it. In this case,
+// no action is needed when the CachableEntry is destroyed.
+// 4) Sometimes, management of a cached or owned object (see #1 and #2 above)
+// is transferred to some other object. This is used for instance with iterators
+// (where cleanup is performed using a chain of cleanup functions,
+// see Cleanable).
+//
+// Because of #1 and #2 above, copying a CachableEntry is not safe (and thus not
+// allowed); hence, this is a move-only type, where a move transfers the
+// management responsibilities, and leaves the source object in an empty state.
+
+template <class T>
+class CachableEntry {
+public:
+ CachableEntry() = default;
+
+ CachableEntry(T* value, Cache* cache, Cache::Handle* cache_handle,
+ bool own_value)
+ : value_(value)
+ , cache_(cache)
+ , cache_handle_(cache_handle)
+ , own_value_(own_value)
+ {
+ assert(value_ != nullptr ||
+ (cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
+ assert(!!cache_ == !!cache_handle_);
+ assert(!cache_handle_ || !own_value_);
+ }
+
+ CachableEntry(const CachableEntry&) = delete;
+ CachableEntry& operator=(const CachableEntry&) = delete;
+
+ CachableEntry(CachableEntry&& rhs)
+ : value_(rhs.value_)
+ , cache_(rhs.cache_)
+ , cache_handle_(rhs.cache_handle_)
+ , own_value_(rhs.own_value_)
+ {
+ assert(value_ != nullptr ||
+ (cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
+ assert(!!cache_ == !!cache_handle_);
+ assert(!cache_handle_ || !own_value_);
+
+ rhs.ResetFields();
+ }
+
+ CachableEntry& operator=(CachableEntry&& rhs) {
+ if (UNLIKELY(this == &rhs)) {
+ return *this;
+ }
+
+ ReleaseResource();
+
+ value_ = rhs.value_;
+ cache_ = rhs.cache_;
+ cache_handle_ = rhs.cache_handle_;
+ own_value_ = rhs.own_value_;
+
+ assert(value_ != nullptr ||
+ (cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
+ assert(!!cache_ == !!cache_handle_);
+ assert(!cache_handle_ || !own_value_);
+
+ rhs.ResetFields();
+
+ return *this;
+ }
+
+ ~CachableEntry() {
+ ReleaseResource();
+ }
+
+ bool IsEmpty() const {
+ return value_ == nullptr && cache_ == nullptr && cache_handle_ == nullptr &&
+ !own_value_;
+ }
+
+ bool IsCached() const {
+ assert(!!cache_ == !!cache_handle_);
+
+ return cache_handle_ != nullptr;
+ }
+
+ T* GetValue() const { return value_; }
+ Cache* GetCache() const { return cache_; }
+ Cache::Handle* GetCacheHandle() const { return cache_handle_; }
+ bool GetOwnValue() const { return own_value_; }
+
+ void Reset() {
+ ReleaseResource();
+ ResetFields();
+ }
+
+ void TransferTo(Cleanable* cleanable) {
+ if (cleanable) {
+ if (cache_handle_ != nullptr) {
+ assert(cache_ != nullptr);
+ cleanable->RegisterCleanup(&ReleaseCacheHandle, cache_, cache_handle_);
+ } else if (own_value_) {
+ cleanable->RegisterCleanup(&DeleteValue, value_, nullptr);
+ }
+ }
+
+ ResetFields();
+ }
+
+ void SetOwnedValue(T* value) {
+ assert(value != nullptr);
+
+ if (UNLIKELY(value_ == value && own_value_)) {
+ assert(cache_ == nullptr && cache_handle_ == nullptr);
+ return;
+ }
+
+ Reset();
+
+ value_ = value;
+ own_value_ = true;
+ }
+
+ void SetUnownedValue(T* value) {
+ assert(value != nullptr);
+
+ if (UNLIKELY(value_ == value && cache_ == nullptr &&
+ cache_handle_ == nullptr && !own_value_)) {
+ return;
+ }
+
+ Reset();
+
+ value_ = value;
+ assert(!own_value_);
+ }
+
+ void SetCachedValue(T* value, Cache* cache, Cache::Handle* cache_handle) {
+ assert(value != nullptr);
+ assert(cache != nullptr);
+ assert(cache_handle != nullptr);
+
+ if (UNLIKELY(value_ == value && cache_ == cache &&
+ cache_handle_ == cache_handle && !own_value_)) {
+ return;
+ }
+
+ Reset();
+
+ value_ = value;
+ cache_ = cache;
+ cache_handle_ = cache_handle;
+ assert(!own_value_);
+ }
+
+private:
+ void ReleaseResource() {
+ if (LIKELY(cache_handle_ != nullptr)) {
+ assert(cache_ != nullptr);
+ cache_->Release(cache_handle_);
+ } else if (own_value_) {
+ delete value_;
+ }
+ }
+
+ void ResetFields() {
+ value_ = nullptr;
+ cache_ = nullptr;
+ cache_handle_ = nullptr;
+ own_value_ = false;
+ }
+
+ static void ReleaseCacheHandle(void* arg1, void* arg2) {
+ Cache* const cache = static_cast<Cache*>(arg1);
+ assert(cache);
+
+ Cache::Handle* const cache_handle = static_cast<Cache::Handle*>(arg2);
+ assert(cache_handle);
+
+ cache->Release(cache_handle);
+ }
+
+ static void DeleteValue(void* arg1, void* /* arg2 */) {
+ delete static_cast<T*>(arg1);
+ }
+
+private:
+ T* value_ = nullptr;
+ Cache* cache_ = nullptr;
+ Cache::Handle* cache_handle_ = nullptr;
+ bool own_value_ = false;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/data_block_footer.cc b/storage/rocksdb/rocksdb/table/block_based/data_block_footer.cc
new file mode 100644
index 00000000..5d5d8ed5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/data_block_footer.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/data_block_footer.h"
+
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const int kDataBlockIndexTypeBitShift = 31;
+
+// 0x7FFFFFFF
+const uint32_t kMaxNumRestarts = (1u << kDataBlockIndexTypeBitShift) - 1u;
+
+// 0x7FFFFFFF
+const uint32_t kNumRestartsMask = (1u << kDataBlockIndexTypeBitShift) - 1u;
+
+uint32_t PackIndexTypeAndNumRestarts(
+ BlockBasedTableOptions::DataBlockIndexType index_type,
+ uint32_t num_restarts) {
+ if (num_restarts > kMaxNumRestarts) {
+ assert(0); // mute travis "unused" warning
+ }
+
+ uint32_t block_footer = num_restarts;
+ if (index_type == BlockBasedTableOptions::kDataBlockBinaryAndHash) {
+ block_footer |= 1u << kDataBlockIndexTypeBitShift;
+ } else if (index_type != BlockBasedTableOptions::kDataBlockBinarySearch) {
+ assert(0);
+ }
+
+ return block_footer;
+}
+
+void UnPackIndexTypeAndNumRestarts(
+ uint32_t block_footer,
+ BlockBasedTableOptions::DataBlockIndexType* index_type,
+ uint32_t* num_restarts) {
+ if (index_type) {
+ if (block_footer & 1u << kDataBlockIndexTypeBitShift) {
+ *index_type = BlockBasedTableOptions::kDataBlockBinaryAndHash;
+ } else {
+ *index_type = BlockBasedTableOptions::kDataBlockBinarySearch;
+ }
+ }
+
+ if (num_restarts) {
+ *num_restarts = block_footer & kNumRestartsMask;
+ assert(*num_restarts <= kMaxNumRestarts);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/data_block_footer.h b/storage/rocksdb/rocksdb/table/block_based/data_block_footer.h
new file mode 100644
index 00000000..c1cfd473
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/data_block_footer.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+uint32_t PackIndexTypeAndNumRestarts(
+ BlockBasedTableOptions::DataBlockIndexType index_type,
+ uint32_t num_restarts);
+
+void UnPackIndexTypeAndNumRestarts(
+ uint32_t block_footer,
+ BlockBasedTableOptions::DataBlockIndexType* index_type,
+ uint32_t* num_restarts);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.cc b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.cc
new file mode 100644
index 00000000..22247583
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "rocksdb/slice.h"
+#include "table/block_based/data_block_hash_index.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void DataBlockHashIndexBuilder::Add(const Slice& key,
+ const size_t restart_index) {
+ assert(Valid());
+ if (restart_index > kMaxRestartSupportedByHashIndex) {
+ valid_ = false;
+ return;
+ }
+
+ uint32_t hash_value = GetSliceHash(key);
+ hash_and_restart_pairs_.emplace_back(hash_value,
+ static_cast<uint8_t>(restart_index));
+ estimated_num_buckets_ += bucket_per_key_;
+}
+
+void DataBlockHashIndexBuilder::Finish(std::string& buffer) {
+ assert(Valid());
+ uint16_t num_buckets = static_cast<uint16_t>(estimated_num_buckets_);
+
+ if (num_buckets == 0) {
+ num_buckets = 1; // sanity check
+ }
+
+ // The build-in hash cannot well distribute strings when into different
+ // buckets when num_buckets is power of two, resulting in high hash
+ // collision.
+ // We made the num_buckets to be odd to avoid this issue.
+ num_buckets |= 1;
+
+ std::vector<uint8_t> buckets(num_buckets, kNoEntry);
+ // write the restart_index array
+ for (auto& entry : hash_and_restart_pairs_) {
+ uint32_t hash_value = entry.first;
+ uint8_t restart_index = entry.second;
+ uint16_t buck_idx = static_cast<uint16_t>(hash_value % num_buckets);
+ if (buckets[buck_idx] == kNoEntry) {
+ buckets[buck_idx] = restart_index;
+ } else if (buckets[buck_idx] != restart_index) {
+ // same bucket cannot store two different restart_index, mark collision
+ buckets[buck_idx] = kCollision;
+ }
+ }
+
+ for (uint8_t restart_index : buckets) {
+ buffer.append(
+ const_cast<const char*>(reinterpret_cast<char*>(&restart_index)),
+ sizeof(restart_index));
+ }
+
+ // write NUM_BUCK
+ PutFixed16(&buffer, num_buckets);
+
+ assert(buffer.size() <= kMaxBlockSizeSupportedByHashIndex);
+}
+
+void DataBlockHashIndexBuilder::Reset() {
+ estimated_num_buckets_ = 0;
+ valid_ = true;
+ hash_and_restart_pairs_.clear();
+}
+
+void DataBlockHashIndex::Initialize(const char* data, uint16_t size,
+ uint16_t* map_offset) {
+ assert(size >= sizeof(uint16_t)); // NUM_BUCKETS
+ num_buckets_ = DecodeFixed16(data + size - sizeof(uint16_t));
+ assert(num_buckets_ > 0);
+ assert(size > num_buckets_ * sizeof(uint8_t));
+ *map_offset = static_cast<uint16_t>(size - sizeof(uint16_t) -
+ num_buckets_ * sizeof(uint8_t));
+}
+
+uint8_t DataBlockHashIndex::Lookup(const char* data, uint32_t map_offset,
+ const Slice& key) const {
+ uint32_t hash_value = GetSliceHash(key);
+ uint16_t idx = static_cast<uint16_t>(hash_value % num_buckets_);
+ const char* bucket_table = data + map_offset;
+ return static_cast<uint8_t>(*(bucket_table + idx * sizeof(uint8_t)));
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.h b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.h
new file mode 100644
index 00000000..f356395f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+// This is an experimental feature aiming to reduce the CPU utilization of
+// point-lookup within a data-block. It is only used in data blocks, and not
+// in meta-data blocks or per-table index blocks.
+//
+// It only used to support BlockBasedTable::Get().
+//
+// A serialized hash index is appended to the data-block. The new block data
+// format is as follows:
+//
+// DATA_BLOCK: [RI RI RI ... RI RI_IDX HASH_IDX FOOTER]
+//
+// RI: Restart Interval (the same as the default data-block format)
+// RI_IDX: Restart Interval index (the same as the default data-block format)
+// HASH_IDX: The new data-block hash index feature.
+// FOOTER: A 32bit block footer, which is the NUM_RESTARTS with the MSB as
+// the flag indicating if this hash index is in use. Note that
+// given a data block < 32KB, the MSB is never used. So we can
+// borrow the MSB as the hash index flag. Therefore, this format is
+// compatible with the legacy data-blocks with num_restarts < 32768,
+// as the MSB is 0.
+//
+// The format of the data-block hash index is as follows:
+//
+// HASH_IDX: [B B B ... B NUM_BUCK]
+//
+// B: bucket, an array of restart index. Each buckets is uint8_t.
+// NUM_BUCK: Number of buckets, which is the length of the bucket array.
+//
+// We reserve two special flag:
+// kNoEntry=255,
+// kCollision=254.
+//
+// Therefore, the max number of restarts this hash index can supoport is 253.
+//
+// Buckets are initialized to be kNoEntry.
+//
+// When storing a key in the hash index, the key is first hashed to a bucket.
+// If there the bucket is empty (kNoEntry), the restart index is stored in
+// the bucket. If there is already a restart index there, we will update the
+// existing restart index to a collision marker (kCollision). If the
+// the bucket is already marked as collision, we do not store the restart
+// index either.
+//
+// During query process, a key is first hashed to a bucket. Then we examine if
+// the buckets store nothing (kNoEntry) or the bucket had a collision
+// (kCollision). If either of those happens, we get the restart index of
+// the key and will directly go to the restart interval to search the key.
+//
+// Note that we only support blocks with #restart_interval < 254. If a block
+// has more restart interval than that, hash index will not be create for it.
+
+const uint8_t kNoEntry = 255;
+const uint8_t kCollision = 254;
+const uint8_t kMaxRestartSupportedByHashIndex = 253;
+
+// Because we use uint16_t address, we only support block no more than 64KB
+const size_t kMaxBlockSizeSupportedByHashIndex = 1u << 16;
+const double kDefaultUtilRatio = 0.75;
+
+class DataBlockHashIndexBuilder {
+ public:
+ DataBlockHashIndexBuilder()
+ : bucket_per_key_(-1 /*uninitialized marker*/),
+ estimated_num_buckets_(0),
+ valid_(false) {}
+
+ void Initialize(double util_ratio) {
+ if (util_ratio <= 0) {
+ util_ratio = kDefaultUtilRatio; // sanity check
+ }
+ bucket_per_key_ = 1 / util_ratio;
+ valid_ = true;
+ }
+
+ inline bool Valid() const { return valid_ && bucket_per_key_ > 0; }
+ void Add(const Slice& key, const size_t restart_index);
+ void Finish(std::string& buffer);
+ void Reset();
+ inline size_t EstimateSize() const {
+ uint16_t estimated_num_buckets =
+ static_cast<uint16_t>(estimated_num_buckets_);
+
+ // Maching the num_buckets number in DataBlockHashIndexBuilder::Finish.
+ estimated_num_buckets |= 1;
+
+ return sizeof(uint16_t) +
+ static_cast<size_t>(estimated_num_buckets * sizeof(uint8_t));
+ }
+
+ private:
+ double bucket_per_key_; // is the multiplicative inverse of util_ratio_
+ double estimated_num_buckets_;
+
+ // Now the only usage for `valid_` is to mark false when the inserted
+ // restart_index is larger than supported. In this case HashIndex is not
+ // appended to the block content.
+ bool valid_;
+
+ std::vector<std::pair<uint32_t, uint8_t>> hash_and_restart_pairs_;
+ friend class DataBlockHashIndex_DataBlockHashTestSmall_Test;
+};
+
+class DataBlockHashIndex {
+ public:
+ DataBlockHashIndex() : num_buckets_(0) {}
+
+ void Initialize(const char* data, uint16_t size, uint16_t* map_offset);
+
+ uint8_t Lookup(const char* data, uint32_t map_offset, const Slice& key) const;
+
+ inline bool Valid() { return num_buckets_ != 0; }
+
+ private:
+ // To make the serialized hash index compact and to save the space overhead,
+ // here all the data fields persisted in the block are in uint16 format.
+ // We find that a uint16 is large enough to index every offset of a 64KiB
+ // block.
+ // So in other words, DataBlockHashIndex does not support block size equal
+ // or greater then 64KiB.
+ uint16_t num_buckets_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index_test.cc b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index_test.cc
new file mode 100644
index 00000000..8548c850
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/data_block_hash_index_test.cc
@@ -0,0 +1,719 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdlib>
+#include <string>
+#include <unordered_map>
+
+#include "db/table_properties_collector.h"
+#include "rocksdb/slice.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/block_builder.h"
+#include "table/block_based/data_block_hash_index.h"
+#include "table/get_context.h"
+#include "table/table_builder.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool SearchForOffset(DataBlockHashIndex& index, const char* data,
+ uint16_t map_offset, const Slice& key,
+ uint8_t& restart_point) {
+ uint8_t entry = index.Lookup(data, map_offset, key);
+ if (entry == kCollision) {
+ return true;
+ }
+
+ if (entry == kNoEntry) {
+ return false;
+ }
+
+ return entry == restart_point;
+}
+
+// Random KV generator similer to block_test
+static std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+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 += RandomString(rnd, padding_size);
+ }
+
+ return k;
+}
+
+// Generate random key value pairs.
+// The generated key will be sorted. You can tune the parameters to generated
+// different kinds of test key/value pairs for different scenario.
+void GenerateRandomKVs(std::vector<std::string>* keys,
+ std::vector<std::string>* 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 shares 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(RandomString(&rnd, 100));
+ }
+ }
+}
+
+TEST(DataBlockHashIndex, DataBlockHashTestSmall) {
+ DataBlockHashIndexBuilder builder;
+ builder.Initialize(0.75 /*util_ratio*/);
+ for (int j = 0; j < 5; j++) {
+ for (uint8_t i = 0; i < 2 + j; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ }
+
+ size_t estimated_size = builder.EstimateSize();
+
+ std::string buffer("fake"), buffer2;
+ size_t original_size = buffer.size();
+ estimated_size += original_size;
+ builder.Finish(buffer);
+
+ ASSERT_EQ(buffer.size(), estimated_size);
+
+ buffer2 = buffer; // test for the correctness of relative offset
+
+ Slice s(buffer2);
+ DataBlockHashIndex index;
+ uint16_t map_offset;
+ index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
+
+ // the additional hash map should start at the end of the buffer
+ ASSERT_EQ(original_size, map_offset);
+ for (uint8_t i = 0; i < 2; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ ASSERT_TRUE(
+ SearchForOffset(index, s.data(), map_offset, key, restart_point));
+ }
+ builder.Reset();
+ }
+}
+
+TEST(DataBlockHashIndex, DataBlockHashTest) {
+ // bucket_num = 200, #keys = 100. 50% utilization
+ DataBlockHashIndexBuilder builder;
+ builder.Initialize(0.75 /*util_ratio*/);
+
+ for (uint8_t i = 0; i < 100; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ }
+
+ size_t estimated_size = builder.EstimateSize();
+
+ std::string buffer("fake content"), buffer2;
+ size_t original_size = buffer.size();
+ estimated_size += original_size;
+ builder.Finish(buffer);
+
+ ASSERT_EQ(buffer.size(), estimated_size);
+
+ buffer2 = buffer; // test for the correctness of relative offset
+
+ Slice s(buffer2);
+ DataBlockHashIndex index;
+ uint16_t map_offset;
+ index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
+
+ // the additional hash map should start at the end of the buffer
+ ASSERT_EQ(original_size, map_offset);
+ for (uint8_t i = 0; i < 100; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ ASSERT_TRUE(
+ SearchForOffset(index, s.data(), map_offset, key, restart_point));
+ }
+}
+
+TEST(DataBlockHashIndex, DataBlockHashTestCollision) {
+ // bucket_num = 2. There will be intense hash collisions
+ DataBlockHashIndexBuilder builder;
+ builder.Initialize(0.75 /*util_ratio*/);
+
+ for (uint8_t i = 0; i < 100; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ }
+
+ size_t estimated_size = builder.EstimateSize();
+
+ std::string buffer("some other fake content to take up space"), buffer2;
+ size_t original_size = buffer.size();
+ estimated_size += original_size;
+ builder.Finish(buffer);
+
+ ASSERT_EQ(buffer.size(), estimated_size);
+
+ buffer2 = buffer; // test for the correctness of relative offset
+
+ Slice s(buffer2);
+ DataBlockHashIndex index;
+ uint16_t map_offset;
+ index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
+
+ // the additional hash map should start at the end of the buffer
+ ASSERT_EQ(original_size, map_offset);
+ for (uint8_t i = 0; i < 100; i++) {
+ std::string key("key" + std::to_string(i));
+ uint8_t restart_point = i;
+ ASSERT_TRUE(
+ SearchForOffset(index, s.data(), map_offset, key, restart_point));
+ }
+}
+
+TEST(DataBlockHashIndex, DataBlockHashTestLarge) {
+ DataBlockHashIndexBuilder builder;
+ builder.Initialize(0.75 /*util_ratio*/);
+ std::unordered_map<std::string, uint8_t> m;
+
+ for (uint8_t i = 0; i < 100; i++) {
+ if (i % 2) {
+ continue; // leave half of the keys out
+ }
+ std::string key = "key" + std::to_string(i);
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ m[key] = restart_point;
+ }
+
+ size_t estimated_size = builder.EstimateSize();
+
+ std::string buffer("filling stuff"), buffer2;
+ size_t original_size = buffer.size();
+ estimated_size += original_size;
+ builder.Finish(buffer);
+
+ ASSERT_EQ(buffer.size(), estimated_size);
+
+ buffer2 = buffer; // test for the correctness of relative offset
+
+ Slice s(buffer2);
+ DataBlockHashIndex index;
+ uint16_t map_offset;
+ index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
+
+ // the additional hash map should start at the end of the buffer
+ ASSERT_EQ(original_size, map_offset);
+ for (uint8_t i = 0; i < 100; i++) {
+ std::string key = "key" + std::to_string(i);
+ uint8_t restart_point = i;
+ if (m.count(key)) {
+ ASSERT_TRUE(m[key] == restart_point);
+ ASSERT_TRUE(
+ SearchForOffset(index, s.data(), map_offset, key, restart_point));
+ } else {
+ // we allow false positve, so don't test the nonexisting keys.
+ // when false positive happens, the search will continue to the
+ // restart intervals to see if the key really exist.
+ }
+ }
+}
+
+TEST(DataBlockHashIndex, RestartIndexExceedMax) {
+ DataBlockHashIndexBuilder builder;
+ builder.Initialize(0.75 /*util_ratio*/);
+ std::unordered_map<std::string, uint8_t> m;
+
+ for (uint8_t i = 0; i <= 253; i++) {
+ std::string key = "key" + std::to_string(i);
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ }
+ ASSERT_TRUE(builder.Valid());
+
+ builder.Reset();
+
+ for (uint8_t i = 0; i <= 254; i++) {
+ std::string key = "key" + std::to_string(i);
+ uint8_t restart_point = i;
+ builder.Add(key, restart_point);
+ }
+
+ ASSERT_FALSE(builder.Valid());
+
+ builder.Reset();
+ ASSERT_TRUE(builder.Valid());
+}
+
+TEST(DataBlockHashIndex, BlockRestartIndexExceedMax) {
+ Options options = Options();
+
+ BlockBuilder builder(1 /* block_restart_interval */,
+ true /* use_delta_encoding */,
+ false /* use_value_delta_encoding */,
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+
+ // #restarts <= 253. HashIndex is valid
+ for (int i = 0; i <= 253; i++) {
+ std::string ukey = "key" + std::to_string(i);
+ InternalKey ikey(ukey, 0, kTypeValue);
+ builder.Add(ikey.Encode().ToString(), "value");
+ }
+
+ {
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ ASSERT_EQ(reader.IndexType(),
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+ }
+
+ builder.Reset();
+
+ // #restarts > 253. HashIndex is not used
+ for (int i = 0; i <= 254; i++) {
+ std::string ukey = "key" + std::to_string(i);
+ InternalKey ikey(ukey, 0, kTypeValue);
+ builder.Add(ikey.Encode().ToString(), "value");
+ }
+
+ {
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ ASSERT_EQ(reader.IndexType(),
+ BlockBasedTableOptions::kDataBlockBinarySearch);
+ }
+}
+
+TEST(DataBlockHashIndex, BlockSizeExceedMax) {
+ Options options = Options();
+ std::string ukey(10, 'k');
+ InternalKey ikey(ukey, 0, kTypeValue);
+
+ BlockBuilder builder(1 /* block_restart_interval */,
+ false /* use_delta_encoding */,
+ false /* use_value_delta_encoding */,
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+
+ {
+ // insert a large value. The block size plus HashIndex is 65536.
+ std::string value(65502, 'v');
+
+ builder.Add(ikey.Encode().ToString(), value);
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+ ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex);
+ std::cerr << "block size: " << rawblock.size() << std::endl;
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ ASSERT_EQ(reader.IndexType(),
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+ }
+
+ builder.Reset();
+
+ {
+ // insert a large value. The block size plus HashIndex would be 65537.
+ // This excceed the max block size supported by HashIndex (65536).
+ // So when build finishes HashIndex will not be created for the block.
+ std::string value(65503, 'v');
+
+ builder.Add(ikey.Encode().ToString(), value);
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+ ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex);
+ std::cerr << "block size: " << rawblock.size() << std::endl;
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ // the index type have fallen back to binary when build finish.
+ ASSERT_EQ(reader.IndexType(),
+ BlockBasedTableOptions::kDataBlockBinarySearch);
+ }
+}
+
+TEST(DataBlockHashIndex, BlockTestSingleKey) {
+ Options options = Options();
+
+ BlockBuilder builder(16 /* block_restart_interval */,
+ true /* use_delta_encoding */,
+ false /* use_value_delta_encoding */,
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+
+ std::string ukey("gopher");
+ std::string value("gold");
+ InternalKey ikey(ukey, 10, kTypeValue);
+ builder.Add(ikey.Encode().ToString(), value /*value*/);
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+
+ const InternalKeyComparator icmp(BytewiseComparator());
+ auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator());
+ bool may_exist;
+ // search in block for the key just inserted
+ {
+ InternalKey seek_ikey(ukey, 10, kValueTypeForSeek);
+ may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
+ ASSERT_TRUE(may_exist);
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(
+ options.comparator->Compare(iter->key(), ikey.Encode().ToString()), 0);
+ ASSERT_EQ(iter->value(), value);
+ }
+
+ // search in block for the existing ukey, but with higher seqno
+ {
+ InternalKey seek_ikey(ukey, 20, kValueTypeForSeek);
+
+ // HashIndex should be able to set the iter correctly
+ may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
+ ASSERT_TRUE(may_exist);
+ ASSERT_TRUE(iter->Valid());
+
+ // user key should match
+ ASSERT_EQ(options.comparator->Compare(ExtractUserKey(iter->key()), ukey),
+ 0);
+
+ // seek_key seqno number should be greater than that of iter result
+ ASSERT_GT(GetInternalKeySeqno(seek_ikey.Encode()),
+ GetInternalKeySeqno(iter->key()));
+
+ ASSERT_EQ(iter->value(), value);
+ }
+
+ // Search in block for the existing ukey, but with lower seqno
+ // in this case, hash can find the only occurrence of the user_key, but
+ // ParseNextDataKey() will skip it as it does not have a older seqno.
+ // In this case, GetForSeek() is effective to locate the user_key, and
+ // iter->Valid() == false indicates that we've reached to the end of
+ // the block and the caller should continue searching the next block.
+ {
+ InternalKey seek_ikey(ukey, 5, kValueTypeForSeek);
+ may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
+ ASSERT_TRUE(may_exist);
+ ASSERT_FALSE(iter->Valid()); // should have reached to the end of block
+ }
+
+ delete iter;
+}
+
+TEST(DataBlockHashIndex, BlockTestLarge) {
+ Random rnd(1019);
+ Options options = Options();
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+
+ BlockBuilder builder(16 /* block_restart_interval */,
+ true /* use_delta_encoding */,
+ false /* use_value_delta_encoding */,
+ BlockBasedTableOptions::kDataBlockBinaryAndHash);
+ int num_records = 500;
+
+ GenerateRandomKVs(&keys, &values, 0, num_records);
+
+ // Generate keys. Adding a trailing "1" to indicate existent keys.
+ // Later will Seeking for keys with a trailing "0" to test seeking
+ // non-existent keys.
+ for (int i = 0; i < num_records; i++) {
+ std::string ukey(keys[i] + "1" /* existing key marker */);
+ InternalKey ikey(ukey, 0, kTypeValue);
+ builder.Add(ikey.Encode().ToString(), values[i]);
+ }
+
+ // read serialized contents of the block
+ Slice rawblock = builder.Finish();
+
+ // create block reader
+ BlockContents contents;
+ contents.data = rawblock;
+ Block reader(std::move(contents), kDisableGlobalSequenceNumber);
+ const InternalKeyComparator icmp(BytewiseComparator());
+
+ // random seek existent keys
+ for (int i = 0; i < num_records; i++) {
+ auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator());
+ // find a random key in the lookaside array
+ int index = rnd.Uniform(num_records);
+ std::string ukey(keys[index] + "1" /* existing key marker */);
+ InternalKey ikey(ukey, 0, kTypeValue);
+
+ // search in block for this key
+ bool may_exist = iter->SeekForGet(ikey.Encode().ToString());
+ ASSERT_TRUE(may_exist);
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(values[index], iter->value());
+
+ delete iter;
+ }
+
+ // random seek non-existent user keys
+ // In this case A), the user_key cannot be found in HashIndex. The key may
+ // exist in the next block. So the iter is set invalidated to tell the
+ // caller to search the next block. This test case belongs to this case A).
+ //
+ // Note that for non-existent keys, there is possibility of false positive,
+ // i.e. the key is still hashed into some restart interval.
+ // Two additional possible outcome:
+ // B) linear seek the restart interval and not found, the iter stops at the
+ // starting of the next restart interval. The key does not exist
+ // anywhere.
+ // C) linear seek the restart interval and not found, the iter stops at the
+ // the end of the block, i.e. restarts_. The key may exist in the next
+ // block.
+ // So these combinations are possible when searching non-existent user_key:
+ //
+ // case# may_exist iter->Valid()
+ // A true false
+ // B false true
+ // C true false
+
+ for (int i = 0; i < num_records; i++) {
+ auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator());
+ // find a random key in the lookaside array
+ int index = rnd.Uniform(num_records);
+ std::string ukey(keys[index] + "0" /* non-existing key marker */);
+ InternalKey ikey(ukey, 0, kTypeValue);
+
+ // search in block for this key
+ bool may_exist = iter->SeekForGet(ikey.Encode().ToString());
+ if (!may_exist) {
+ ASSERT_TRUE(iter->Valid());
+ }
+ if (!iter->Valid()) {
+ ASSERT_TRUE(may_exist);
+ }
+
+ delete iter;
+ }
+}
+
+// helper routine for DataBlockHashIndex.BlockBoundary
+void TestBoundary(InternalKey& ik1, std::string& v1, InternalKey& ik2,
+ std::string& v2, InternalKey& seek_ikey,
+ GetContext& get_context, Options& options) {
+ std::unique_ptr<WritableFileWriter> file_writer;
+ std::unique_ptr<RandomAccessFileReader> file_reader;
+ std::unique_ptr<TableReader> table_reader;
+ int level_ = -1;
+
+ std::vector<std::string> keys;
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ const InternalKeyComparator internal_comparator(options.comparator);
+
+ EnvOptions soptions;
+
+ soptions.use_mmap_reads = ioptions.allow_mmap_reads;
+ file_writer.reset(
+ test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */));
+ std::unique_ptr<TableBuilder> builder;
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+ builder.reset(ioptions.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, internal_comparator,
+ &int_tbl_prop_collector_factories,
+ options.compression, options.sample_for_compression,
+ CompressionOptions(), false /* skip_filters */,
+ column_family_name, level_),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ file_writer.get()));
+
+ builder->Add(ik1.Encode().ToString(), v1);
+ builder->Add(ik2.Encode().ToString(), v2);
+ EXPECT_TRUE(builder->status().ok());
+
+ Status s = builder->Finish();
+ file_writer->Flush();
+ EXPECT_TRUE(s.ok()) << s.ToString();
+
+ EXPECT_EQ(
+ test::GetStringSinkFromLegacyWriter(file_writer.get())->contents().size(),
+ builder->FileSize());
+
+ // Open the table
+ file_reader.reset(test::GetRandomAccessFileReader(new test::StringSource(
+ test::GetStringSinkFromLegacyWriter(file_writer.get())->contents(),
+ 0 /*uniq_id*/, ioptions.allow_mmap_reads)));
+ const bool kSkipFilters = true;
+ const bool kImmortal = true;
+ ioptions.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
+ internal_comparator, !kSkipFilters, !kImmortal,
+ level_),
+ std::move(file_reader),
+ test::GetStringSinkFromLegacyWriter(file_writer.get())->contents().size(),
+ &table_reader);
+ // Search using Get()
+ ReadOptions ro;
+
+ ASSERT_OK(table_reader->Get(ro, seek_ikey.Encode().ToString(), &get_context,
+ moptions.prefix_extractor.get()));
+}
+
+TEST(DataBlockHashIndex, BlockBoundary) {
+ BlockBasedTableOptions table_options;
+ table_options.data_block_index_type =
+ BlockBasedTableOptions::kDataBlockBinaryAndHash;
+ table_options.block_restart_interval = 1;
+ table_options.block_size = 4096;
+
+ Options options;
+ options.comparator = BytewiseComparator();
+
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ // insert two large k/v pair. Given that the block_size is 4096, one k/v
+ // pair will take up one block.
+ // [ k1/v1 ][ k2/v2 ]
+ // [ Block N ][ Block N+1 ]
+
+ {
+ // [ "aab"@100 ][ "axy"@10 ]
+ // | Block N ][ Block N+1 ]
+ // seek for "axy"@60
+ std::string uk1("aab");
+ InternalKey ik1(uk1, 100, kTypeValue);
+ std::string v1(4100, '1'); // large value
+
+ std::string uk2("axy");
+ InternalKey ik2(uk2, 10, kTypeValue);
+ std::string v2(4100, '2'); // large value
+
+ PinnableSlice value;
+ std::string seek_ukey("axy");
+ InternalKey seek_ikey(seek_ukey, 60, kTypeValue);
+ GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, seek_ukey, &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+
+ TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
+ ASSERT_EQ(get_context.State(), GetContext::kFound);
+ ASSERT_EQ(value, v2);
+ value.Reset();
+ }
+
+ {
+ // [ "axy"@100 ][ "axy"@10 ]
+ // | Block N ][ Block N+1 ]
+ // seek for "axy"@60
+ std::string uk1("axy");
+ InternalKey ik1(uk1, 100, kTypeValue);
+ std::string v1(4100, '1'); // large value
+
+ std::string uk2("axy");
+ InternalKey ik2(uk2, 10, kTypeValue);
+ std::string v2(4100, '2'); // large value
+
+ PinnableSlice value;
+ std::string seek_ukey("axy");
+ InternalKey seek_ikey(seek_ukey, 60, kTypeValue);
+ GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, seek_ukey, &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+
+ TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
+ ASSERT_EQ(get_context.State(), GetContext::kFound);
+ ASSERT_EQ(value, v2);
+ value.Reset();
+ }
+
+ {
+ // [ "axy"@100 ][ "axy"@10 ]
+ // | Block N ][ Block N+1 ]
+ // seek for "axy"@120
+ std::string uk1("axy");
+ InternalKey ik1(uk1, 100, kTypeValue);
+ std::string v1(4100, '1'); // large value
+
+ std::string uk2("axy");
+ InternalKey ik2(uk2, 10, kTypeValue);
+ std::string v2(4100, '2'); // large value
+
+ PinnableSlice value;
+ std::string seek_ukey("axy");
+ InternalKey seek_ikey(seek_ukey, 120, kTypeValue);
+ GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, seek_ukey, &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+
+ TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
+ ASSERT_EQ(get_context.State(), GetContext::kFound);
+ ASSERT_EQ(value, v1);
+ value.Reset();
+ }
+
+ {
+ // [ "axy"@100 ][ "axy"@10 ]
+ // | Block N ][ Block N+1 ]
+ // seek for "axy"@5
+ std::string uk1("axy");
+ InternalKey ik1(uk1, 100, kTypeValue);
+ std::string v1(4100, '1'); // large value
+
+ std::string uk2("axy");
+ InternalKey ik2(uk2, 10, kTypeValue);
+ std::string v2(4100, '2'); // large value
+
+ PinnableSlice value;
+ std::string seek_ukey("axy");
+ InternalKey seek_ikey(seek_ukey, 5, kTypeValue);
+ GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, seek_ukey, &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+
+ TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
+ ASSERT_EQ(get_context.State(), GetContext::kNotFound);
+ value.Reset();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/block_based/filter_block.h b/storage/rocksdb/rocksdb/table/block_based/filter_block.h
new file mode 100644
index 00000000..1ad8d3f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/filter_block.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A filter block is stored near the end of a Table file. It contains
+// filters (e.g., bloom filters) for all data blocks in the table combined
+// into a single filter block.
+//
+// It is a base class for BlockBasedFilter and FullFilter.
+// These two are both used in BlockBasedTable. The first one contain filter
+// For a part of keys in sst file, the second contain filter for all keys
+// in sst file.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+#include "db/dbformat.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "table/format.h"
+#include "table/multiget_context.h"
+#include "trace_replay/block_cache_tracer.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const uint64_t kNotValid = ULLONG_MAX;
+class FilterPolicy;
+
+class GetContext;
+using MultiGetRange = MultiGetContext::Range;
+
+// A FilterBlockBuilder is used to construct all of the filters for a
+// particular Table. It generates a single string which is stored as
+// a special block in the Table.
+//
+// The sequence of calls to FilterBlockBuilder must match the regexp:
+// (StartBlock Add*)* Finish
+//
+// BlockBased/Full FilterBlock would be called in the same way.
+class FilterBlockBuilder {
+ public:
+ explicit FilterBlockBuilder() {}
+ // No copying allowed
+ FilterBlockBuilder(const FilterBlockBuilder&) = delete;
+ void operator=(const FilterBlockBuilder&) = delete;
+
+ virtual ~FilterBlockBuilder() {}
+
+ virtual bool IsBlockBased() = 0; // If is blockbased filter
+ virtual void StartBlock(uint64_t block_offset) = 0; // Start new block filter
+ virtual void Add(const Slice& key) = 0; // Add a key to current filter
+ virtual size_t NumAdded() const = 0; // Number of keys added
+ Slice Finish() { // Generate Filter
+ const BlockHandle empty_handle;
+ Status dont_care_status;
+ auto ret = Finish(empty_handle, &dont_care_status);
+ assert(dont_care_status.ok());
+ return ret;
+ }
+ virtual Slice Finish(const BlockHandle& tmp, Status* status) = 0;
+};
+
+// A FilterBlockReader is used to parse filter from SST table.
+// KeyMayMatch and PrefixMayMatch would trigger filter checking
+//
+// BlockBased/Full FilterBlock would be called in the same way.
+class FilterBlockReader {
+ public:
+ FilterBlockReader() = default;
+ virtual ~FilterBlockReader() = default;
+
+ FilterBlockReader(const FilterBlockReader&) = delete;
+ FilterBlockReader& operator=(const FilterBlockReader&) = delete;
+
+ virtual bool IsBlockBased() = 0; // If is blockbased filter
+
+ /**
+ * If no_io is set, then it returns true if it cannot answer the query without
+ * reading data from disk. This is used in PartitionedFilterBlockReader to
+ * avoid reading partitions that are not in block cache already
+ *
+ * Normally filters are built on only the user keys and the InternalKey is not
+ * needed for a query. The index in PartitionedFilterBlockReader however is
+ * built upon InternalKey and must be provided via const_ikey_ptr when running
+ * queries.
+ */
+ virtual bool KeyMayMatch(const Slice& key,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) = 0;
+
+ virtual void KeysMayMatch(MultiGetRange* range,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) {
+ for (auto iter = range->begin(); iter != range->end(); ++iter) {
+ const Slice ukey = iter->ukey;
+ const Slice ikey = iter->ikey;
+ GetContext* const get_context = iter->get_context;
+ if (!KeyMayMatch(ukey, prefix_extractor, block_offset, no_io, &ikey,
+ get_context, lookup_context)) {
+ range->SkipKey(iter);
+ }
+ }
+ }
+
+ /**
+ * no_io and const_ikey_ptr here means the same as in KeyMayMatch
+ */
+ virtual bool PrefixMayMatch(const Slice& prefix,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) = 0;
+
+ virtual void PrefixesMayMatch(MultiGetRange* range,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) {
+ for (auto iter = range->begin(); iter != range->end(); ++iter) {
+ const Slice ukey = iter->ukey;
+ const Slice ikey = iter->ikey;
+ GetContext* const get_context = iter->get_context;
+ if (prefix_extractor->InDomain(ukey) &&
+ !PrefixMayMatch(prefix_extractor->Transform(ukey), prefix_extractor,
+ block_offset, no_io, &ikey, get_context,
+ lookup_context)) {
+ range->SkipKey(iter);
+ }
+ }
+ }
+
+ virtual size_t ApproximateMemoryUsage() const = 0;
+
+ // convert this object to a human readable form
+ virtual std::string ToString() const {
+ std::string error_msg("Unsupported filter \n");
+ return error_msg;
+ }
+
+ virtual void CacheDependencies(bool /*pin*/) {}
+
+ virtual bool RangeMayExist(const Slice* /*iterate_upper_bound*/,
+ const Slice& user_key,
+ const SliceTransform* prefix_extractor,
+ const Comparator* /*comparator*/,
+ const Slice* const const_ikey_ptr,
+ bool* filter_checked, bool need_upper_bound_check,
+ BlockCacheLookupContext* lookup_context) {
+ if (need_upper_bound_check) {
+ return true;
+ }
+ *filter_checked = true;
+ Slice prefix = prefix_extractor->Transform(user_key);
+ return PrefixMayMatch(prefix, prefix_extractor, kNotValid, false,
+ const_ikey_ptr, /* get_context */ nullptr,
+ lookup_context);
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.cc b/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.cc
new file mode 100644
index 00000000..fa080266
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/filter_block_reader_common.h"
+#include "monitoring/perf_context_imp.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/parsed_full_filter_block.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+template <typename TBlocklike>
+Status FilterBlockReaderCommon<TBlocklike>::ReadFilterBlock(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<TBlocklike>* filter_block) {
+ PERF_TIMER_GUARD(read_filter_block_nanos);
+
+ assert(table);
+ assert(filter_block);
+ assert(filter_block->IsEmpty());
+
+ const BlockBasedTable::Rep* const rep = table->get_rep();
+ assert(rep);
+
+ const Status s =
+ table->RetrieveBlock(prefetch_buffer, read_options, rep->filter_handle,
+ UncompressionDict::GetEmptyDict(), filter_block,
+ BlockType::kFilter, get_context, lookup_context,
+ /* for_compaction */ false, use_cache);
+
+ return s;
+}
+
+template <typename TBlocklike>
+const SliceTransform*
+FilterBlockReaderCommon<TBlocklike>::table_prefix_extractor() const {
+ assert(table_);
+
+ const BlockBasedTable::Rep* const rep = table_->get_rep();
+ assert(rep);
+
+ return rep->prefix_filtering ? rep->table_prefix_extractor.get() : nullptr;
+}
+
+template <typename TBlocklike>
+bool FilterBlockReaderCommon<TBlocklike>::whole_key_filtering() const {
+ assert(table_);
+ assert(table_->get_rep());
+
+ return table_->get_rep()->whole_key_filtering;
+}
+
+template <typename TBlocklike>
+bool FilterBlockReaderCommon<TBlocklike>::cache_filter_blocks() const {
+ assert(table_);
+ assert(table_->get_rep());
+
+ return table_->get_rep()->table_options.cache_index_and_filter_blocks;
+}
+
+template <typename TBlocklike>
+Status FilterBlockReaderCommon<TBlocklike>::GetOrReadFilterBlock(
+ bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<TBlocklike>* filter_block) const {
+ assert(filter_block);
+
+ if (!filter_block_.IsEmpty()) {
+ filter_block->SetUnownedValue(filter_block_.GetValue());
+ return Status::OK();
+ }
+
+ ReadOptions read_options;
+ if (no_io) {
+ read_options.read_tier = kBlockCacheTier;
+ }
+
+ return ReadFilterBlock(table_, nullptr /* prefetch_buffer */, read_options,
+ cache_filter_blocks(), get_context, lookup_context,
+ filter_block);
+}
+
+template <typename TBlocklike>
+size_t FilterBlockReaderCommon<TBlocklike>::ApproximateFilterBlockMemoryUsage()
+ const {
+ assert(!filter_block_.GetOwnValue() || filter_block_.GetValue() != nullptr);
+ return filter_block_.GetOwnValue()
+ ? filter_block_.GetValue()->ApproximateMemoryUsage()
+ : 0;
+}
+
+// Explicitly instantiate templates for both "blocklike" types we use.
+// This makes it possible to keep the template definitions in the .cc file.
+template class FilterBlockReaderCommon<BlockContents>;
+template class FilterBlockReaderCommon<Block>;
+template class FilterBlockReaderCommon<ParsedFullFilterBlock>;
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.h b/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.h
new file mode 100644
index 00000000..a18bc544
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/filter_block_reader_common.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cassert>
+#include "table/block_based/cachable_entry.h"
+#include "table/block_based/filter_block.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBasedTable;
+class FilePrefetchBuffer;
+
+// Encapsulates common functionality for the various filter block reader
+// implementations. Provides access to the filter block regardless of whether
+// it is owned by the reader or stored in the cache, or whether it is pinned
+// in the cache or not.
+template <typename TBlocklike>
+class FilterBlockReaderCommon : public FilterBlockReader {
+ public:
+ FilterBlockReaderCommon(const BlockBasedTable* t,
+ CachableEntry<TBlocklike>&& filter_block)
+ : table_(t), filter_block_(std::move(filter_block)) {
+ assert(table_);
+ }
+
+ protected:
+ static Status ReadFilterBlock(const BlockBasedTable* table,
+ FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<TBlocklike>* filter_block);
+
+ const BlockBasedTable* table() const { return table_; }
+ const SliceTransform* table_prefix_extractor() const;
+ bool whole_key_filtering() const;
+ bool cache_filter_blocks() const;
+
+ Status GetOrReadFilterBlock(bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<TBlocklike>* filter_block) const;
+
+ size_t ApproximateFilterBlockMemoryUsage() const;
+
+ private:
+ const BlockBasedTable* table_;
+ CachableEntry<TBlocklike> filter_block_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/filter_policy.cc b/storage/rocksdb/rocksdb/table/block_based/filter_policy.cc
new file mode 100644
index 00000000..c8f23ee3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/filter_policy.cc
@@ -0,0 +1,759 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <array>
+#include <deque>
+
+#include "rocksdb/filter_policy.h"
+
+#include "rocksdb/slice.h"
+#include "table/block_based/block_based_filter_block.h"
+#include "table/block_based/full_filter_block.h"
+#include "table/block_based/filter_policy_internal.h"
+#include "third-party/folly/folly/ConstexprMath.h"
+#include "util/bloom_impl.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// See description in FastLocalBloomImpl
+class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
+ public:
+ explicit FastLocalBloomBitsBuilder(const int millibits_per_key)
+ : millibits_per_key_(millibits_per_key),
+ num_probes_(FastLocalBloomImpl::ChooseNumProbes(millibits_per_key_)) {
+ assert(millibits_per_key >= 1000);
+ }
+
+ // No Copy allowed
+ FastLocalBloomBitsBuilder(const FastLocalBloomBitsBuilder&) = delete;
+ void operator=(const FastLocalBloomBitsBuilder&) = delete;
+
+ ~FastLocalBloomBitsBuilder() override {}
+
+ virtual void AddKey(const Slice& key) override {
+ uint64_t hash = GetSliceHash64(key);
+ if (hash_entries_.empty() || hash != hash_entries_.back()) {
+ hash_entries_.push_back(hash);
+ }
+ }
+
+ virtual Slice Finish(std::unique_ptr<const char[]>* buf) override {
+ uint32_t len_with_metadata =
+ CalculateSpace(static_cast<uint32_t>(hash_entries_.size()));
+ char* data = new char[len_with_metadata];
+ memset(data, 0, len_with_metadata);
+
+ assert(data);
+ assert(len_with_metadata >= 5);
+
+ uint32_t len = len_with_metadata - 5;
+ if (len > 0) {
+ AddAllEntries(data, len);
+ }
+
+ // See BloomFilterPolicy::GetBloomBitsReader re: metadata
+ // -1 = Marker for newer Bloom implementations
+ data[len] = static_cast<char>(-1);
+ // 0 = Marker for this sub-implementation
+ data[len + 1] = static_cast<char>(0);
+ // num_probes (and 0 in upper bits for 64-byte block size)
+ data[len + 2] = static_cast<char>(num_probes_);
+ // rest of metadata stays zero
+
+ const char* const_data = data;
+ buf->reset(const_data);
+ assert(hash_entries_.empty());
+
+ return Slice(data, len_with_metadata);
+ }
+
+ int CalculateNumEntry(const uint32_t bytes) override {
+ uint32_t bytes_no_meta = bytes >= 5u ? bytes - 5u : 0;
+ return static_cast<int>(uint64_t{8000} * bytes_no_meta /
+ millibits_per_key_);
+ }
+
+ uint32_t CalculateSpace(const int num_entry) override {
+ uint32_t num_cache_lines = 0;
+ if (millibits_per_key_ > 0 && num_entry > 0) {
+ num_cache_lines = static_cast<uint32_t>(
+ (int64_t{num_entry} * millibits_per_key_ + 511999) / 512000);
+ }
+ return num_cache_lines * 64 + /*metadata*/ 5;
+ }
+
+ double EstimatedFpRate(size_t keys, size_t bytes) override {
+ return FastLocalBloomImpl::EstimatedFpRate(keys, bytes - /*metadata*/ 5,
+ num_probes_, /*hash bits*/ 64);
+ }
+
+ private:
+ void AddAllEntries(char* data, uint32_t len) {
+ // Simple version without prefetching:
+ //
+ // for (auto h : hash_entries_) {
+ // FastLocalBloomImpl::AddHash(Lower32of64(h), Upper32of64(h), len,
+ // num_probes_, data);
+ // }
+
+ const size_t num_entries = hash_entries_.size();
+ constexpr size_t kBufferMask = 7;
+ static_assert(((kBufferMask + 1) & kBufferMask) == 0,
+ "Must be power of 2 minus 1");
+
+ std::array<uint32_t, kBufferMask + 1> hashes;
+ std::array<uint32_t, kBufferMask + 1> byte_offsets;
+
+ // Prime the buffer
+ size_t i = 0;
+ for (; i <= kBufferMask && i < num_entries; ++i) {
+ uint64_t h = hash_entries_.front();
+ hash_entries_.pop_front();
+ FastLocalBloomImpl::PrepareHash(Lower32of64(h), len, data,
+ /*out*/ &byte_offsets[i]);
+ hashes[i] = Upper32of64(h);
+ }
+
+ // Process and buffer
+ for (; i < num_entries; ++i) {
+ uint32_t& hash_ref = hashes[i & kBufferMask];
+ uint32_t& byte_offset_ref = byte_offsets[i & kBufferMask];
+ // Process (add)
+ FastLocalBloomImpl::AddHashPrepared(hash_ref, num_probes_,
+ data + byte_offset_ref);
+ // And buffer
+ uint64_t h = hash_entries_.front();
+ hash_entries_.pop_front();
+ FastLocalBloomImpl::PrepareHash(Lower32of64(h), len, data,
+ /*out*/ &byte_offset_ref);
+ hash_ref = Upper32of64(h);
+ }
+
+ // Finish processing
+ for (i = 0; i <= kBufferMask && i < num_entries; ++i) {
+ FastLocalBloomImpl::AddHashPrepared(hashes[i], num_probes_,
+ data + byte_offsets[i]);
+ }
+ }
+
+ int millibits_per_key_;
+ int num_probes_;
+ // A deque avoids unnecessary copying of already-saved values
+ // and has near-minimal peak memory use.
+ std::deque<uint64_t> hash_entries_;
+};
+
+// See description in FastLocalBloomImpl
+class FastLocalBloomBitsReader : public FilterBitsReader {
+ public:
+ FastLocalBloomBitsReader(const char* data, int num_probes, uint32_t len_bytes)
+ : data_(data), num_probes_(num_probes), len_bytes_(len_bytes) {}
+
+ // No Copy allowed
+ FastLocalBloomBitsReader(const FastLocalBloomBitsReader&) = delete;
+ void operator=(const FastLocalBloomBitsReader&) = delete;
+
+ ~FastLocalBloomBitsReader() override {}
+
+ bool MayMatch(const Slice& key) override {
+ uint64_t h = GetSliceHash64(key);
+ uint32_t byte_offset;
+ FastLocalBloomImpl::PrepareHash(Lower32of64(h), len_bytes_, data_,
+ /*out*/ &byte_offset);
+ return FastLocalBloomImpl::HashMayMatchPrepared(Upper32of64(h), num_probes_,
+ data_ + byte_offset);
+ }
+
+ virtual void MayMatch(int num_keys, Slice** keys, bool* may_match) override {
+ std::array<uint32_t, MultiGetContext::MAX_BATCH_SIZE> hashes;
+ std::array<uint32_t, MultiGetContext::MAX_BATCH_SIZE> byte_offsets;
+ for (int i = 0; i < num_keys; ++i) {
+ uint64_t h = GetSliceHash64(*keys[i]);
+ FastLocalBloomImpl::PrepareHash(Lower32of64(h), len_bytes_, data_,
+ /*out*/ &byte_offsets[i]);
+ hashes[i] = Upper32of64(h);
+ }
+ for (int i = 0; i < num_keys; ++i) {
+ may_match[i] = FastLocalBloomImpl::HashMayMatchPrepared(
+ hashes[i], num_probes_, data_ + byte_offsets[i]);
+ }
+ }
+
+ private:
+ const char* data_;
+ const int num_probes_;
+ const uint32_t len_bytes_;
+};
+
+using LegacyBloomImpl = LegacyLocalityBloomImpl</*ExtraRotates*/ false>;
+
+class LegacyBloomBitsBuilder : public BuiltinFilterBitsBuilder {
+ public:
+ explicit LegacyBloomBitsBuilder(const int bits_per_key, Logger* info_log);
+
+ // No Copy allowed
+ LegacyBloomBitsBuilder(const LegacyBloomBitsBuilder&) = delete;
+ void operator=(const LegacyBloomBitsBuilder&) = delete;
+
+ ~LegacyBloomBitsBuilder() override;
+
+ void AddKey(const Slice& key) override;
+
+ Slice Finish(std::unique_ptr<const char[]>* buf) override;
+
+ int CalculateNumEntry(const uint32_t bytes) override;
+
+ uint32_t CalculateSpace(const int num_entry) override {
+ uint32_t dont_care1;
+ uint32_t dont_care2;
+ return CalculateSpace(num_entry, &dont_care1, &dont_care2);
+ }
+
+ double EstimatedFpRate(size_t keys, size_t bytes) override {
+ return LegacyBloomImpl::EstimatedFpRate(keys, bytes - /*metadata*/ 5,
+ num_probes_);
+ }
+
+ private:
+ int bits_per_key_;
+ int num_probes_;
+ std::vector<uint32_t> hash_entries_;
+ Logger* info_log_;
+
+ // Get totalbits that optimized for cpu cache line
+ uint32_t GetTotalBitsForLocality(uint32_t total_bits);
+
+ // Reserve space for new filter
+ char* ReserveSpace(const int num_entry, uint32_t* total_bits,
+ uint32_t* num_lines);
+
+ // Implementation-specific variant of public CalculateSpace
+ uint32_t CalculateSpace(const int num_entry, uint32_t* total_bits,
+ uint32_t* num_lines);
+
+ // Assuming single threaded access to this function.
+ void AddHash(uint32_t h, char* data, uint32_t num_lines, uint32_t total_bits);
+};
+
+LegacyBloomBitsBuilder::LegacyBloomBitsBuilder(const int bits_per_key,
+ Logger* info_log)
+ : bits_per_key_(bits_per_key),
+ num_probes_(LegacyNoLocalityBloomImpl::ChooseNumProbes(bits_per_key_)),
+ info_log_(info_log) {
+ assert(bits_per_key_);
+}
+
+LegacyBloomBitsBuilder::~LegacyBloomBitsBuilder() {}
+
+void LegacyBloomBitsBuilder::AddKey(const Slice& key) {
+ uint32_t hash = BloomHash(key);
+ if (hash_entries_.size() == 0 || hash != hash_entries_.back()) {
+ hash_entries_.push_back(hash);
+ }
+}
+
+Slice LegacyBloomBitsBuilder::Finish(std::unique_ptr<const char[]>* buf) {
+ uint32_t total_bits, num_lines;
+ size_t num_entries = hash_entries_.size();
+ char* data =
+ ReserveSpace(static_cast<int>(num_entries), &total_bits, &num_lines);
+ assert(data);
+
+ if (total_bits != 0 && num_lines != 0) {
+ for (auto h : hash_entries_) {
+ AddHash(h, data, num_lines, total_bits);
+ }
+
+ // Check for excessive entries for 32-bit hash function
+ if (num_entries >= /* minimum of 3 million */ 3000000U) {
+ // More specifically, we can detect that the 32-bit hash function
+ // is causing significant increase in FP rate by comparing current
+ // estimated FP rate to what we would get with a normal number of
+ // keys at same memory ratio.
+ double est_fp_rate = LegacyBloomImpl::EstimatedFpRate(
+ num_entries, total_bits / 8, num_probes_);
+ double vs_fp_rate = LegacyBloomImpl::EstimatedFpRate(
+ 1U << 16, (1U << 16) * bits_per_key_ / 8, num_probes_);
+
+ if (est_fp_rate >= 1.50 * vs_fp_rate) {
+ // For more details, see
+ // https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter
+ ROCKS_LOG_WARN(
+ info_log_,
+ "Using legacy SST/BBT Bloom filter with excessive key count "
+ "(%.1fM @ %dbpk), causing estimated %.1fx higher filter FP rate. "
+ "Consider using new Bloom with format_version>=5, smaller SST "
+ "file size, or partitioned filters.",
+ num_entries / 1000000.0, bits_per_key_, est_fp_rate / vs_fp_rate);
+ }
+ }
+ }
+ // See BloomFilterPolicy::GetFilterBitsReader for metadata
+ data[total_bits / 8] = static_cast<char>(num_probes_);
+ EncodeFixed32(data + total_bits / 8 + 1, static_cast<uint32_t>(num_lines));
+
+ const char* const_data = data;
+ buf->reset(const_data);
+ hash_entries_.clear();
+
+ return Slice(data, total_bits / 8 + 5);
+}
+
+uint32_t LegacyBloomBitsBuilder::GetTotalBitsForLocality(uint32_t total_bits) {
+ uint32_t num_lines =
+ (total_bits + CACHE_LINE_SIZE * 8 - 1) / (CACHE_LINE_SIZE * 8);
+
+ // Make num_lines an odd number to make sure more bits are involved
+ // when determining which block.
+ if (num_lines % 2 == 0) {
+ num_lines++;
+ }
+ return num_lines * (CACHE_LINE_SIZE * 8);
+}
+
+uint32_t LegacyBloomBitsBuilder::CalculateSpace(const int num_entry,
+ uint32_t* total_bits,
+ uint32_t* num_lines) {
+ assert(bits_per_key_);
+ if (num_entry != 0) {
+ uint32_t total_bits_tmp = static_cast<uint32_t>(num_entry * bits_per_key_);
+
+ *total_bits = GetTotalBitsForLocality(total_bits_tmp);
+ *num_lines = *total_bits / (CACHE_LINE_SIZE * 8);
+ assert(*total_bits > 0 && *total_bits % 8 == 0);
+ } else {
+ // filter is empty, just leave space for metadata
+ *total_bits = 0;
+ *num_lines = 0;
+ }
+
+ // Reserve space for Filter
+ uint32_t sz = *total_bits / 8;
+ sz += 5; // 4 bytes for num_lines, 1 byte for num_probes
+ return sz;
+}
+
+char* LegacyBloomBitsBuilder::ReserveSpace(const int num_entry,
+ uint32_t* total_bits,
+ uint32_t* num_lines) {
+ uint32_t sz = CalculateSpace(num_entry, total_bits, num_lines);
+ char* data = new char[sz];
+ memset(data, 0, sz);
+ return data;
+}
+
+int LegacyBloomBitsBuilder::CalculateNumEntry(const uint32_t bytes) {
+ assert(bits_per_key_);
+ assert(bytes > 0);
+ int high = static_cast<int>(bytes * 8 / bits_per_key_ + 1);
+ int low = 1;
+ int n = high;
+ for (; n >= low; n--) {
+ if (CalculateSpace(n) <= bytes) {
+ break;
+ }
+ }
+ assert(n < high); // High should be an overestimation
+ return n;
+}
+
+inline void LegacyBloomBitsBuilder::AddHash(uint32_t h, char* data,
+ uint32_t num_lines,
+ uint32_t total_bits) {
+#ifdef NDEBUG
+ static_cast<void>(total_bits);
+#endif
+ assert(num_lines > 0 && total_bits > 0);
+
+ LegacyBloomImpl::AddHash(h, num_lines, num_probes_, data,
+ folly::constexpr_log2(CACHE_LINE_SIZE));
+}
+
+class LegacyBloomBitsReader : public FilterBitsReader {
+ public:
+ LegacyBloomBitsReader(const char* data, int num_probes, uint32_t num_lines,
+ uint32_t log2_cache_line_size)
+ : data_(data),
+ num_probes_(num_probes),
+ num_lines_(num_lines),
+ log2_cache_line_size_(log2_cache_line_size) {}
+
+ // No Copy allowed
+ LegacyBloomBitsReader(const LegacyBloomBitsReader&) = delete;
+ void operator=(const LegacyBloomBitsReader&) = delete;
+
+ ~LegacyBloomBitsReader() override {}
+
+ // "contents" contains the data built by a preceding call to
+ // FilterBitsBuilder::Finish. MayMatch must return true if the key was
+ // passed to FilterBitsBuilder::AddKey. This method may return true or false
+ // if the key was not on the list, but it should aim to return false with a
+ // high probability.
+ bool MayMatch(const Slice& key) override {
+ uint32_t hash = BloomHash(key);
+ uint32_t byte_offset;
+ LegacyBloomImpl::PrepareHashMayMatch(
+ hash, num_lines_, data_, /*out*/ &byte_offset, log2_cache_line_size_);
+ return LegacyBloomImpl::HashMayMatchPrepared(
+ hash, num_probes_, data_ + byte_offset, log2_cache_line_size_);
+ }
+
+ virtual void MayMatch(int num_keys, Slice** keys, bool* may_match) override {
+ std::array<uint32_t, MultiGetContext::MAX_BATCH_SIZE> hashes;
+ std::array<uint32_t, MultiGetContext::MAX_BATCH_SIZE> byte_offsets;
+ for (int i = 0; i < num_keys; ++i) {
+ hashes[i] = BloomHash(*keys[i]);
+ LegacyBloomImpl::PrepareHashMayMatch(hashes[i], num_lines_, data_,
+ /*out*/ &byte_offsets[i],
+ log2_cache_line_size_);
+ }
+ for (int i = 0; i < num_keys; ++i) {
+ may_match[i] = LegacyBloomImpl::HashMayMatchPrepared(
+ hashes[i], num_probes_, data_ + byte_offsets[i],
+ log2_cache_line_size_);
+ }
+ }
+
+ private:
+ const char* data_;
+ const int num_probes_;
+ const uint32_t num_lines_;
+ const uint32_t log2_cache_line_size_;
+};
+
+class AlwaysTrueFilter : public FilterBitsReader {
+ public:
+ bool MayMatch(const Slice&) override { return true; }
+ using FilterBitsReader::MayMatch; // inherit overload
+};
+
+class AlwaysFalseFilter : public FilterBitsReader {
+ public:
+ bool MayMatch(const Slice&) override { return false; }
+ using FilterBitsReader::MayMatch; // inherit overload
+};
+
+} // namespace
+
+const std::vector<BloomFilterPolicy::Mode> BloomFilterPolicy::kAllFixedImpls = {
+ kLegacyBloom,
+ kDeprecatedBlock,
+ kFastLocalBloom,
+};
+
+const std::vector<BloomFilterPolicy::Mode> BloomFilterPolicy::kAllUserModes = {
+ kDeprecatedBlock,
+ kAuto,
+};
+
+BloomFilterPolicy::BloomFilterPolicy(double bits_per_key, Mode mode)
+ : mode_(mode), warned_(false) {
+ // Sanitize bits_per_key
+ if (bits_per_key < 1.0) {
+ bits_per_key = 1.0;
+ } else if (!(bits_per_key < 100.0)) { // including NaN
+ bits_per_key = 100.0;
+ }
+
+ // Includes a nudge toward rounding up, to ensure on all platforms
+ // that doubles specified with three decimal digits after the decimal
+ // point are interpreted accurately.
+ millibits_per_key_ = static_cast<int>(bits_per_key * 1000.0 + 0.500001);
+
+ // For better or worse, this is a rounding up of a nudged rounding up,
+ // e.g. 7.4999999999999 will round up to 8, but that provides more
+ // predictability against small arithmetic errors in floating point.
+ whole_bits_per_key_ = (millibits_per_key_ + 500) / 1000;
+}
+
+BloomFilterPolicy::~BloomFilterPolicy() {}
+
+const char* BloomFilterPolicy::Name() const {
+ return "rocksdb.BuiltinBloomFilter";
+}
+
+void BloomFilterPolicy::CreateFilter(const Slice* keys, int n,
+ std::string* dst) const {
+ // We should ideally only be using this deprecated interface for
+ // appropriately constructed BloomFilterPolicy
+ assert(mode_ == kDeprecatedBlock);
+
+ // Compute bloom filter size (in both bits and bytes)
+ uint32_t bits = static_cast<uint32_t>(n * whole_bits_per_key_);
+
+ // For small n, we can see a very high false positive rate. Fix it
+ // by enforcing a minimum bloom filter length.
+ if (bits < 64) bits = 64;
+
+ uint32_t bytes = (bits + 7) / 8;
+ bits = bytes * 8;
+
+ int num_probes =
+ LegacyNoLocalityBloomImpl::ChooseNumProbes(whole_bits_per_key_);
+
+ const size_t init_size = dst->size();
+ dst->resize(init_size + bytes, 0);
+ dst->push_back(static_cast<char>(num_probes)); // Remember # of probes
+ char* array = &(*dst)[init_size];
+ for (int i = 0; i < n; i++) {
+ LegacyNoLocalityBloomImpl::AddHash(BloomHash(keys[i]), bits, num_probes,
+ array);
+ }
+}
+
+bool BloomFilterPolicy::KeyMayMatch(const Slice& key,
+ const Slice& bloom_filter) const {
+ const size_t len = bloom_filter.size();
+ if (len < 2 || len > 0xffffffffU) {
+ return false;
+ }
+
+ const char* array = bloom_filter.data();
+ const uint32_t bits = static_cast<uint32_t>(len - 1) * 8;
+
+ // Use the encoded k so that we can read filters generated by
+ // bloom filters created using different parameters.
+ const int k = static_cast<uint8_t>(array[len - 1]);
+ if (k > 30) {
+ // Reserved for potentially new encodings for short bloom filters.
+ // Consider it a match.
+ return true;
+ }
+ // NB: using stored k not num_probes for whole_bits_per_key_
+ return LegacyNoLocalityBloomImpl::HashMayMatch(BloomHash(key), bits, k,
+ array);
+}
+
+FilterBitsBuilder* BloomFilterPolicy::GetFilterBitsBuilder() const {
+ // This code path should no longer be used, for the built-in
+ // BloomFilterPolicy. Internal to RocksDB and outside
+ // BloomFilterPolicy, only get a FilterBitsBuilder with
+ // BloomFilterPolicy::GetBuilderFromContext(), which will call
+ // BloomFilterPolicy::GetBuilderWithContext(). RocksDB users have
+ // been warned (HISTORY.md) that they can no longer call this on
+ // the built-in BloomFilterPolicy (unlikely).
+ assert(false);
+ return GetBuilderWithContext(FilterBuildingContext(BlockBasedTableOptions()));
+}
+
+FilterBitsBuilder* BloomFilterPolicy::GetBuilderWithContext(
+ const FilterBuildingContext& context) const {
+ Mode cur = mode_;
+ // Unusual code construction so that we can have just
+ // one exhaustive switch without (risky) recursion
+ for (int i = 0; i < 2; ++i) {
+ switch (cur) {
+ case kAuto:
+ if (context.table_options.format_version < 5) {
+ cur = kLegacyBloom;
+ } else {
+ cur = kFastLocalBloom;
+ }
+ break;
+ case kDeprecatedBlock:
+ return nullptr;
+ case kFastLocalBloom:
+ return new FastLocalBloomBitsBuilder(millibits_per_key_);
+ case kLegacyBloom:
+ if (whole_bits_per_key_ >= 14 && context.info_log &&
+ !warned_.load(std::memory_order_relaxed)) {
+ warned_ = true;
+ const char* adjective;
+ if (whole_bits_per_key_ >= 20) {
+ adjective = "Dramatic";
+ } else {
+ adjective = "Significant";
+ }
+ // For more details, see
+ // https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter
+ ROCKS_LOG_WARN(
+ context.info_log,
+ "Using legacy Bloom filter with high (%d) bits/key. "
+ "%s filter space and/or accuracy improvement is available "
+ "with format_version>=5.",
+ whole_bits_per_key_, adjective);
+ }
+ return new LegacyBloomBitsBuilder(whole_bits_per_key_,
+ context.info_log);
+ }
+ }
+ assert(false);
+ return nullptr; // something legal
+}
+
+FilterBitsBuilder* BloomFilterPolicy::GetBuilderFromContext(
+ const FilterBuildingContext& context) {
+ if (context.table_options.filter_policy) {
+ return context.table_options.filter_policy->GetBuilderWithContext(context);
+ } else {
+ return nullptr;
+ }
+}
+
+// Read metadata to determine what kind of FilterBitsReader is needed
+// and return a new one.
+FilterBitsReader* BloomFilterPolicy::GetFilterBitsReader(
+ const Slice& contents) const {
+ uint32_t len_with_meta = static_cast<uint32_t>(contents.size());
+ if (len_with_meta <= 5) {
+ // filter is empty or broken. Treat like zero keys added.
+ return new AlwaysFalseFilter();
+ }
+
+ // Legacy Bloom filter data:
+ // 0 +-----------------------------------+
+ // | Raw Bloom filter data |
+ // | ... |
+ // len +-----------------------------------+
+ // | byte for num_probes or |
+ // | marker for new implementations |
+ // len+1 +-----------------------------------+
+ // | four bytes for number of cache |
+ // | lines |
+ // len_with_meta +-----------------------------------+
+
+ int8_t raw_num_probes =
+ static_cast<int8_t>(contents.data()[len_with_meta - 5]);
+ // NB: *num_probes > 30 and < 128 probably have not been used, because of
+ // BloomFilterPolicy::initialize, unless directly calling
+ // LegacyBloomBitsBuilder as an API, but we are leaving those cases in
+ // limbo with LegacyBloomBitsReader for now.
+
+ if (raw_num_probes < 1) {
+ // Note: < 0 (or unsigned > 127) indicate special new implementations
+ // (or reserved for future use)
+ if (raw_num_probes == -1) {
+ // Marker for newer Bloom implementations
+ return GetBloomBitsReader(contents);
+ }
+ // otherwise
+ // Treat as zero probes (always FP) for now.
+ return new AlwaysTrueFilter();
+ }
+ // else attempt decode for LegacyBloomBitsReader
+
+ int num_probes = raw_num_probes;
+ assert(num_probes >= 1);
+ assert(num_probes <= 127);
+
+ uint32_t len = len_with_meta - 5;
+ assert(len > 0);
+
+ uint32_t num_lines = DecodeFixed32(contents.data() + len_with_meta - 4);
+ uint32_t log2_cache_line_size;
+
+ if (num_lines * CACHE_LINE_SIZE == len) {
+ // Common case
+ log2_cache_line_size = folly::constexpr_log2(CACHE_LINE_SIZE);
+ } else if (num_lines == 0 || len % num_lines != 0) {
+ // Invalid (no solution to num_lines * x == len)
+ // Treat as zero probes (always FP) for now.
+ return new AlwaysTrueFilter();
+ } else {
+ // Determine the non-native cache line size (from another system)
+ log2_cache_line_size = 0;
+ while ((num_lines << log2_cache_line_size) < len) {
+ ++log2_cache_line_size;
+ }
+ if ((num_lines << log2_cache_line_size) != len) {
+ // Invalid (block size not a power of two)
+ // Treat as zero probes (always FP) for now.
+ return new AlwaysTrueFilter();
+ }
+ }
+ // if not early return
+ return new LegacyBloomBitsReader(contents.data(), num_probes, num_lines,
+ log2_cache_line_size);
+}
+
+// For newer Bloom filter implementations
+FilterBitsReader* BloomFilterPolicy::GetBloomBitsReader(
+ const Slice& contents) const {
+ uint32_t len_with_meta = static_cast<uint32_t>(contents.size());
+ uint32_t len = len_with_meta - 5;
+
+ assert(len > 0); // precondition
+
+ // New Bloom filter data:
+ // 0 +-----------------------------------+
+ // | Raw Bloom filter data |
+ // | ... |
+ // len +-----------------------------------+
+ // | char{-1} byte -> new Bloom filter |
+ // len+1 +-----------------------------------+
+ // | byte for subimplementation |
+ // | 0: FastLocalBloom |
+ // | other: reserved |
+ // len+2 +-----------------------------------+
+ // | byte for block_and_probes |
+ // | 0 in top 3 bits -> 6 -> 64-byte |
+ // | reserved: |
+ // | 1 in top 3 bits -> 7 -> 128-byte|
+ // | 2 in top 3 bits -> 8 -> 256-byte|
+ // | ... |
+ // | num_probes in bottom 5 bits, |
+ // | except 0 and 31 reserved |
+ // len+3 +-----------------------------------+
+ // | two bytes reserved |
+ // | possibly for hash seed |
+ // len_with_meta +-----------------------------------+
+
+ // Read more metadata (see above)
+ char sub_impl_val = contents.data()[len_with_meta - 4];
+ char block_and_probes = contents.data()[len_with_meta - 3];
+ int log2_block_bytes = ((block_and_probes >> 5) & 7) + 6;
+
+ int num_probes = (block_and_probes & 31);
+ if (num_probes < 1 || num_probes > 30) {
+ // Reserved / future safe
+ return new AlwaysTrueFilter();
+ }
+
+ uint16_t rest = DecodeFixed16(contents.data() + len_with_meta - 2);
+ if (rest != 0) {
+ // Reserved, possibly for hash seed
+ // Future safe
+ return new AlwaysTrueFilter();
+ }
+
+ if (sub_impl_val == 0) { // FastLocalBloom
+ if (log2_block_bytes == 6) { // Only block size supported for now
+ return new FastLocalBloomBitsReader(contents.data(), num_probes, len);
+ }
+ }
+ // otherwise
+ // Reserved / future safe
+ return new AlwaysTrueFilter();
+}
+
+const FilterPolicy* NewBloomFilterPolicy(double bits_per_key,
+ bool use_block_based_builder) {
+ BloomFilterPolicy::Mode m;
+ if (use_block_based_builder) {
+ m = BloomFilterPolicy::kDeprecatedBlock;
+ } else {
+ m = BloomFilterPolicy::kAuto;
+ }
+ assert(std::find(BloomFilterPolicy::kAllUserModes.begin(),
+ BloomFilterPolicy::kAllUserModes.end(),
+ m) != BloomFilterPolicy::kAllUserModes.end());
+ return new BloomFilterPolicy(bits_per_key, m);
+}
+
+FilterBuildingContext::FilterBuildingContext(
+ const BlockBasedTableOptions& _table_options)
+ : table_options(_table_options) {}
+
+FilterPolicy::~FilterPolicy() { }
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/filter_policy_internal.h b/storage/rocksdb/rocksdb/table/block_based/filter_policy_internal.h
new file mode 100644
index 00000000..2ca9dc85
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/filter_policy_internal.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+
+// Exposes any extra information needed for testing built-in
+// FilterBitsBuilders
+class BuiltinFilterBitsBuilder : public FilterBitsBuilder {
+ public:
+ // Calculate number of bytes needed for a new filter, including
+ // metadata. Passing the result to CalculateNumEntry should
+ // return >= the num_entry passed in.
+ virtual uint32_t CalculateSpace(const int num_entry) = 0;
+
+ // Returns an estimate of the FP rate of the returned filter if
+ // `keys` keys are added and the filter returned by Finish is `bytes`
+ // bytes.
+ virtual double EstimatedFpRate(size_t keys, size_t bytes) = 0;
+};
+
+// RocksDB built-in filter policy for Bloom or Bloom-like filters.
+// This class is considered internal API and subject to change.
+// See NewBloomFilterPolicy.
+class BloomFilterPolicy : public FilterPolicy {
+ public:
+ // An internal marker for operating modes of BloomFilterPolicy, in terms
+ // of selecting an implementation. This makes it easier for tests to track
+ // or to walk over the built-in set of Bloom filter implementations. The
+ // only variance in BloomFilterPolicy by mode/implementation is in
+ // GetFilterBitsBuilder(), so an enum is practical here vs. subclasses.
+ //
+ // This enum is essentially the union of all the different kinds of return
+ // value from GetFilterBitsBuilder, or "underlying implementation", and
+ // higher-level modes that choose an underlying implementation based on
+ // context information.
+ enum Mode {
+ // Legacy implementation of Bloom filter for full and partitioned filters.
+ // Set to 0 in case of value confusion with bool use_block_based_builder
+ // NOTE: TESTING ONLY as this mode does not use best compatible
+ // implementation
+ kLegacyBloom = 0,
+ // Deprecated block-based Bloom filter implementation.
+ // Set to 1 in case of value confusion with bool use_block_based_builder
+ // NOTE: DEPRECATED but user exposed
+ kDeprecatedBlock = 1,
+ // A fast, cache-local Bloom filter implementation. See description in
+ // FastLocalBloomImpl.
+ // NOTE: TESTING ONLY as this mode does not check format_version
+ kFastLocalBloom = 2,
+ // Automatically choose from the above (except kDeprecatedBlock) based on
+ // context at build time, including compatibility with format_version.
+ // NOTE: This is currently the only recommended mode that is user exposed.
+ kAuto = 100,
+ };
+ // All the different underlying implementations that a BloomFilterPolicy
+ // might use, as a mode that says "always use this implementation."
+ // Only appropriate for unit tests.
+ static const std::vector<Mode> kAllFixedImpls;
+
+ // All the different modes of BloomFilterPolicy that are exposed from
+ // user APIs. Only appropriate for higher-level unit tests. Integration
+ // tests should prefer using NewBloomFilterPolicy (user-exposed).
+ static const std::vector<Mode> kAllUserModes;
+
+ explicit BloomFilterPolicy(double bits_per_key, Mode mode);
+
+ ~BloomFilterPolicy() override;
+
+ const char* Name() const override;
+
+ // Deprecated block-based filter only
+ void CreateFilter(const Slice* keys, int n, std::string* dst) const override;
+
+ // Deprecated block-based filter only
+ bool KeyMayMatch(const Slice& key, const Slice& bloom_filter) const override;
+
+ FilterBitsBuilder* GetFilterBitsBuilder() const override;
+
+ // To use this function, call GetBuilderFromContext().
+ //
+ // Neither the context nor any objects therein should be saved beyond
+ // the call to this function, unless it's shared_ptr.
+ FilterBitsBuilder* GetBuilderWithContext(
+ const FilterBuildingContext&) const override;
+
+ // Returns a new FilterBitsBuilder from the filter_policy in
+ // table_options of a context, or nullptr if not applicable.
+ // (An internal convenience function to save boilerplate.)
+ static FilterBitsBuilder* GetBuilderFromContext(const FilterBuildingContext&);
+
+ // Read metadata to determine what kind of FilterBitsReader is needed
+ // and return a new one. This must successfully process any filter data
+ // generated by a built-in FilterBitsBuilder, regardless of the impl
+ // chosen for this BloomFilterPolicy. Not compatible with CreateFilter.
+ FilterBitsReader* GetFilterBitsReader(const Slice& contents) const override;
+
+ // Essentially for testing only: configured millibits/key
+ int GetMillibitsPerKey() const { return millibits_per_key_; }
+ // Essentially for testing only: legacy whole bits/key
+ int GetWholeBitsPerKey() const { return whole_bits_per_key_; }
+
+ private:
+ // Newer filters support fractional bits per key. For predictable behavior
+ // of 0.001-precision values across floating point implementations, we
+ // round to thousandths of a bit (on average) per key.
+ int millibits_per_key_;
+
+ // Older filters round to whole number bits per key. (There *should* be no
+ // compatibility issue with fractional bits per key, but preserving old
+ // behavior with format_version < 5 just in case.)
+ int whole_bits_per_key_;
+
+ // Selected mode (a specific implementation or way of selecting an
+ // implementation) for building new SST filters.
+ Mode mode_;
+
+ // Whether relevant warnings have been logged already. (Remember so we
+ // only report once per BloomFilterPolicy instance, to keep the noise down.)
+ mutable std::atomic<bool> warned_;
+
+ // For newer Bloom filter implementation(s)
+ FilterBitsReader* GetBloomBitsReader(const Slice& contents) const;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.cc b/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.cc
new file mode 100644
index 00000000..f5cb2d22
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/flush_block_policy.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "table/block_based/block_builder.h"
+#include "table/format.h"
+
+#include <cassert>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Flush block by size
+class FlushBlockBySizePolicy : public FlushBlockPolicy {
+ public:
+ // @params block_size: Approximate size of user data packed per
+ // block.
+ // @params block_size_deviation: This is used to close a block before it
+ // reaches the configured
+ FlushBlockBySizePolicy(const uint64_t block_size,
+ const uint64_t block_size_deviation,
+ const bool align,
+ const BlockBuilder& data_block_builder)
+ : block_size_(block_size),
+ block_size_deviation_limit_(
+ ((block_size * (100 - block_size_deviation)) + 99) / 100),
+ align_(align),
+ data_block_builder_(data_block_builder) {}
+
+ bool Update(const Slice& key, const Slice& value) override {
+ // it makes no sense to flush when the data block is empty
+ if (data_block_builder_.empty()) {
+ return false;
+ }
+
+ auto curr_size = data_block_builder_.CurrentSizeEstimate();
+
+ // Do flush if one of the below two conditions is true:
+ // 1) if the current estimated size already exceeds the block size,
+ // 2) block_size_deviation is set and the estimated size after appending
+ // the kv will exceed the block size and the current size is under the
+ // the deviation.
+ return curr_size >= block_size_ || BlockAlmostFull(key, value);
+ }
+
+ private:
+ bool BlockAlmostFull(const Slice& key, const Slice& value) const {
+ if (block_size_deviation_limit_ == 0) {
+ return false;
+ }
+
+ const auto curr_size = data_block_builder_.CurrentSizeEstimate();
+ auto estimated_size_after =
+ data_block_builder_.EstimateSizeAfterKV(key, value);
+
+ if (align_) {
+ estimated_size_after += kBlockTrailerSize;
+ return estimated_size_after > block_size_;
+ }
+
+ return estimated_size_after > block_size_ &&
+ curr_size > block_size_deviation_limit_;
+ }
+
+ const uint64_t block_size_;
+ const uint64_t block_size_deviation_limit_;
+ const bool align_;
+ const BlockBuilder& data_block_builder_;
+};
+
+FlushBlockPolicy* FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
+ const BlockBasedTableOptions& table_options,
+ const BlockBuilder& data_block_builder) const {
+ return new FlushBlockBySizePolicy(
+ table_options.block_size, table_options.block_size_deviation,
+ table_options.block_align, data_block_builder);
+}
+
+FlushBlockPolicy* FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
+ const uint64_t size, const int deviation,
+ const BlockBuilder& data_block_builder) {
+ return new FlushBlockBySizePolicy(size, deviation, false, data_block_builder);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.h b/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.h
new file mode 100644
index 00000000..68c60c16
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/flush_block_policy.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/flush_block_policy.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// FlushBlockEveryKeyPolicy currently used only in tests.
+
+class FlushBlockEveryKeyPolicy : public FlushBlockPolicy {
+ public:
+ bool Update(const Slice& /*key*/, const Slice& /*value*/) override {
+ if (!start_) {
+ start_ = true;
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ bool start_ = false;
+};
+
+class FlushBlockEveryKeyPolicyFactory : public FlushBlockPolicyFactory {
+ public:
+ explicit FlushBlockEveryKeyPolicyFactory() {}
+
+ const char* Name() const override {
+ return "FlushBlockEveryKeyPolicyFactory";
+ }
+
+ FlushBlockPolicy* NewFlushBlockPolicy(
+ const BlockBasedTableOptions& /*table_options*/,
+ const BlockBuilder& /*data_block_builder*/) const override {
+ return new FlushBlockEveryKeyPolicy;
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/full_filter_block.cc b/storage/rocksdb/rocksdb/table/block_based/full_filter_block.cc
new file mode 100644
index 00000000..e2f7f476
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/full_filter_block.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/full_filter_block.h"
+#include <array>
+
+#include "monitoring/perf_context_imp.h"
+#include "port/malloc.h"
+#include "port/port.h"
+#include "rocksdb/filter_policy.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+FullFilterBlockBuilder::FullFilterBlockBuilder(
+ const SliceTransform* _prefix_extractor, bool whole_key_filtering,
+ FilterBitsBuilder* filter_bits_builder)
+ : prefix_extractor_(_prefix_extractor),
+ whole_key_filtering_(whole_key_filtering),
+ last_whole_key_recorded_(false),
+ last_prefix_recorded_(false),
+ num_added_(0) {
+ assert(filter_bits_builder != nullptr);
+ filter_bits_builder_.reset(filter_bits_builder);
+}
+
+void FullFilterBlockBuilder::Add(const Slice& key) {
+ const bool add_prefix = prefix_extractor_ && prefix_extractor_->InDomain(key);
+ if (whole_key_filtering_) {
+ if (!add_prefix) {
+ AddKey(key);
+ } else {
+ // if both whole_key and prefix are added to bloom then we will have whole
+ // key and prefix addition being interleaved and thus cannot rely on the
+ // bits builder to properly detect the duplicates by comparing with the
+ // last item.
+ Slice last_whole_key = Slice(last_whole_key_str_);
+ if (!last_whole_key_recorded_ || last_whole_key.compare(key) != 0) {
+ AddKey(key);
+ last_whole_key_recorded_ = true;
+ last_whole_key_str_.assign(key.data(), key.size());
+ }
+ }
+ }
+ if (add_prefix) {
+ AddPrefix(key);
+ }
+}
+
+// Add key to filter if needed
+inline void FullFilterBlockBuilder::AddKey(const Slice& key) {
+ filter_bits_builder_->AddKey(key);
+ num_added_++;
+}
+
+// Add prefix to filter if needed
+void FullFilterBlockBuilder::AddPrefix(const Slice& key) {
+ Slice prefix = prefix_extractor_->Transform(key);
+ if (whole_key_filtering_) {
+ // if both whole_key and prefix are added to bloom then we will have whole
+ // key and prefix addition being interleaved and thus cannot rely on the
+ // bits builder to properly detect the duplicates by comparing with the last
+ // item.
+ Slice last_prefix = Slice(last_prefix_str_);
+ if (!last_prefix_recorded_ || last_prefix.compare(prefix) != 0) {
+ AddKey(prefix);
+ last_prefix_recorded_ = true;
+ last_prefix_str_.assign(prefix.data(), prefix.size());
+ }
+ } else {
+ AddKey(prefix);
+ }
+}
+
+void FullFilterBlockBuilder::Reset() {
+ last_whole_key_recorded_ = false;
+ last_prefix_recorded_ = false;
+}
+
+Slice FullFilterBlockBuilder::Finish(const BlockHandle& /*tmp*/,
+ Status* status) {
+ Reset();
+ // In this impl we ignore BlockHandle
+ *status = Status::OK();
+ if (num_added_ != 0) {
+ num_added_ = 0;
+ return filter_bits_builder_->Finish(&filter_data_);
+ }
+ return Slice();
+}
+
+FullFilterBlockReader::FullFilterBlockReader(
+ const BlockBasedTable* t,
+ CachableEntry<ParsedFullFilterBlock>&& filter_block)
+ : FilterBlockReaderCommon(t, std::move(filter_block)) {
+ const SliceTransform* const prefix_extractor = table_prefix_extractor();
+ if (prefix_extractor) {
+ full_length_enabled_ =
+ prefix_extractor->FullLengthEnabled(&prefix_extractor_full_length_);
+ }
+}
+
+bool FullFilterBlockReader::KeyMayMatch(
+ const Slice& key, const SliceTransform* /*prefix_extractor*/,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) {
+#ifdef NDEBUG
+ (void)block_offset;
+#endif
+ assert(block_offset == kNotValid);
+ if (!whole_key_filtering()) {
+ return true;
+ }
+ return MayMatch(key, no_io, get_context, lookup_context);
+}
+
+std::unique_ptr<FilterBlockReader> FullFilterBlockReader::Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context) {
+ assert(table);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+
+ CachableEntry<ParsedFullFilterBlock> filter_block;
+ if (prefetch || !use_cache) {
+ const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
+ use_cache, nullptr /* get_context */,
+ lookup_context, &filter_block);
+ if (!s.ok()) {
+ return std::unique_ptr<FilterBlockReader>();
+ }
+
+ if (use_cache && !pin) {
+ filter_block.Reset();
+ }
+ }
+
+ return std::unique_ptr<FilterBlockReader>(
+ new FullFilterBlockReader(table, std::move(filter_block)));
+}
+
+bool FullFilterBlockReader::PrefixMayMatch(
+ const Slice& prefix, const SliceTransform* /* prefix_extractor */,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) {
+#ifdef NDEBUG
+ (void)block_offset;
+#endif
+ assert(block_offset == kNotValid);
+ return MayMatch(prefix, no_io, get_context, lookup_context);
+}
+
+bool FullFilterBlockReader::MayMatch(
+ const Slice& entry, bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const {
+ CachableEntry<ParsedFullFilterBlock> filter_block;
+
+ const Status s =
+ GetOrReadFilterBlock(no_io, get_context, lookup_context, &filter_block);
+ if (!s.ok()) {
+ return true;
+ }
+
+ assert(filter_block.GetValue());
+
+ FilterBitsReader* const filter_bits_reader =
+ filter_block.GetValue()->filter_bits_reader();
+
+ if (filter_bits_reader) {
+ if (filter_bits_reader->MayMatch(entry)) {
+ PERF_COUNTER_ADD(bloom_sst_hit_count, 1);
+ return true;
+ } else {
+ PERF_COUNTER_ADD(bloom_sst_miss_count, 1);
+ return false;
+ }
+ }
+ return true; // remain the same with block_based filter
+}
+
+void FullFilterBlockReader::KeysMayMatch(
+ MultiGetRange* range, const SliceTransform* /*prefix_extractor*/,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) {
+#ifdef NDEBUG
+ (void)range;
+ (void)block_offset;
+#endif
+ assert(block_offset == kNotValid);
+ if (!whole_key_filtering()) {
+ // Simply return. Don't skip any key - consider all keys as likely to be
+ // present
+ return;
+ }
+ MayMatch(range, no_io, nullptr, lookup_context);
+}
+
+void FullFilterBlockReader::PrefixesMayMatch(
+ MultiGetRange* range, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) {
+#ifdef NDEBUG
+ (void)range;
+ (void)block_offset;
+#endif
+ assert(block_offset == kNotValid);
+ MayMatch(range, no_io, prefix_extractor, lookup_context);
+}
+
+void FullFilterBlockReader::MayMatch(
+ MultiGetRange* range, bool no_io, const SliceTransform* prefix_extractor,
+ BlockCacheLookupContext* lookup_context) const {
+ CachableEntry<ParsedFullFilterBlock> filter_block;
+
+ const Status s = GetOrReadFilterBlock(no_io, range->begin()->get_context,
+ lookup_context, &filter_block);
+ if (!s.ok()) {
+ return;
+ }
+
+ assert(filter_block.GetValue());
+
+ FilterBitsReader* const filter_bits_reader =
+ filter_block.GetValue()->filter_bits_reader();
+
+ if (!filter_bits_reader) {
+ return;
+ }
+
+ // We need to use an array instead of autovector for may_match since
+ // &may_match[0] doesn't work for autovector<bool> (compiler error). So
+ // declare both keys and may_match as arrays, which is also slightly less
+ // expensive compared to autovector
+ std::array<Slice*, MultiGetContext::MAX_BATCH_SIZE> keys;
+ std::array<bool, MultiGetContext::MAX_BATCH_SIZE> may_match = {{true}};
+ autovector<Slice, MultiGetContext::MAX_BATCH_SIZE> prefixes;
+ int num_keys = 0;
+ MultiGetRange filter_range(*range, range->begin(), range->end());
+ for (auto iter = filter_range.begin(); iter != filter_range.end(); ++iter) {
+ if (!prefix_extractor) {
+ keys[num_keys++] = &iter->ukey;
+ } else if (prefix_extractor->InDomain(iter->ukey)) {
+ prefixes.emplace_back(prefix_extractor->Transform(iter->ukey));
+ keys[num_keys++] = &prefixes.back();
+ } else {
+ filter_range.SkipKey(iter);
+ }
+ }
+
+ filter_bits_reader->MayMatch(num_keys, &keys[0], &may_match[0]);
+
+ int i = 0;
+ for (auto iter = filter_range.begin(); iter != filter_range.end(); ++iter) {
+ if (!may_match[i]) {
+ // Update original MultiGet range to skip this key. The filter_range
+ // was temporarily used just to skip keys not in prefix_extractor domain
+ range->SkipKey(iter);
+ PERF_COUNTER_ADD(bloom_sst_miss_count, 1);
+ } else {
+ // PERF_COUNTER_ADD(bloom_sst_hit_count, 1);
+ PerfContext* perf_ctx = get_perf_context();
+ perf_ctx->bloom_sst_hit_count++;
+ }
+ ++i;
+ }
+}
+
+size_t FullFilterBlockReader::ApproximateMemoryUsage() const {
+ size_t usage = ApproximateFilterBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<FullFilterBlockReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return usage;
+}
+
+bool FullFilterBlockReader::RangeMayExist(
+ const Slice* iterate_upper_bound, const Slice& user_key,
+ const SliceTransform* prefix_extractor, const Comparator* comparator,
+ const Slice* const const_ikey_ptr, bool* filter_checked,
+ bool need_upper_bound_check, BlockCacheLookupContext* lookup_context) {
+ if (!prefix_extractor || !prefix_extractor->InDomain(user_key)) {
+ *filter_checked = false;
+ return true;
+ }
+ Slice prefix = prefix_extractor->Transform(user_key);
+ if (need_upper_bound_check &&
+ !IsFilterCompatible(iterate_upper_bound, prefix, comparator)) {
+ *filter_checked = false;
+ return true;
+ } else {
+ *filter_checked = true;
+ return PrefixMayMatch(prefix, prefix_extractor, kNotValid, false,
+ const_ikey_ptr, /* get_context */ nullptr,
+ lookup_context);
+ }
+}
+
+bool FullFilterBlockReader::IsFilterCompatible(
+ const Slice* iterate_upper_bound, const Slice& prefix,
+ const Comparator* comparator) const {
+ // Try to reuse the bloom filter in the SST table if prefix_extractor in
+ // mutable_cf_options has changed. If range [user_key, upper_bound) all
+ // share the same prefix then we may still be able to use the bloom filter.
+ const SliceTransform* const prefix_extractor = table_prefix_extractor();
+ if (iterate_upper_bound != nullptr && prefix_extractor) {
+ if (!prefix_extractor->InDomain(*iterate_upper_bound)) {
+ return false;
+ }
+ Slice upper_bound_xform = prefix_extractor->Transform(*iterate_upper_bound);
+ // first check if user_key and upper_bound all share the same prefix
+ if (!comparator->Equal(prefix, upper_bound_xform)) {
+ // second check if user_key's prefix is the immediate predecessor of
+ // upper_bound and have the same length. If so, we know for sure all
+ // keys in the range [user_key, upper_bound) share the same prefix.
+ // Also need to make sure upper_bound are full length to ensure
+ // correctness
+ if (!full_length_enabled_ ||
+ iterate_upper_bound->size() != prefix_extractor_full_length_ ||
+ !comparator->IsSameLengthImmediateSuccessor(prefix,
+ *iterate_upper_bound)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/full_filter_block.h b/storage/rocksdb/rocksdb/table/block_based/full_filter_block.h
new file mode 100644
index 00000000..c72a5802
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/full_filter_block.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "table/block_based/filter_block_reader_common.h"
+#include "table/block_based/parsed_full_filter_block.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class FilterPolicy;
+class FilterBitsBuilder;
+class FilterBitsReader;
+
+// A FullFilterBlockBuilder is used to construct a full filter for a
+// particular Table. It generates a single string which is stored as
+// a special block in the Table.
+// The format of full filter block is:
+// +----------------------------------------------------------------+
+// | full filter for all keys in sst file |
+// +----------------------------------------------------------------+
+// The full filter can be very large. At the end of it, we put
+// num_probes: how many hash functions are used in bloom filter
+//
+class FullFilterBlockBuilder : public FilterBlockBuilder {
+ public:
+ explicit FullFilterBlockBuilder(const SliceTransform* prefix_extractor,
+ bool whole_key_filtering,
+ FilterBitsBuilder* filter_bits_builder);
+ // No copying allowed
+ FullFilterBlockBuilder(const FullFilterBlockBuilder&) = delete;
+ void operator=(const FullFilterBlockBuilder&) = delete;
+
+ // bits_builder is created in filter_policy, it should be passed in here
+ // directly. and be deleted here
+ ~FullFilterBlockBuilder() {}
+
+ virtual bool IsBlockBased() override { return false; }
+ virtual void StartBlock(uint64_t /*block_offset*/) override {}
+ virtual void Add(const Slice& key) override;
+ virtual size_t NumAdded() const override { return num_added_; }
+ virtual Slice Finish(const BlockHandle& tmp, Status* status) override;
+ using FilterBlockBuilder::Finish;
+
+ protected:
+ virtual void AddKey(const Slice& key);
+ std::unique_ptr<FilterBitsBuilder> filter_bits_builder_;
+ virtual void Reset();
+ void AddPrefix(const Slice& key);
+ const SliceTransform* prefix_extractor() { return prefix_extractor_; }
+
+ private:
+ // important: all of these might point to invalid addresses
+ // at the time of destruction of this filter block. destructor
+ // should NOT dereference them.
+ const SliceTransform* prefix_extractor_;
+ bool whole_key_filtering_;
+ bool last_whole_key_recorded_;
+ std::string last_whole_key_str_;
+ bool last_prefix_recorded_;
+ std::string last_prefix_str_;
+
+ uint32_t num_added_;
+ std::unique_ptr<const char[]> filter_data_;
+
+};
+
+// A FilterBlockReader is used to parse filter from SST table.
+// KeyMayMatch and PrefixMayMatch would trigger filter checking
+class FullFilterBlockReader
+ : public FilterBlockReaderCommon<ParsedFullFilterBlock> {
+ public:
+ FullFilterBlockReader(const BlockBasedTable* t,
+ CachableEntry<ParsedFullFilterBlock>&& filter_block);
+
+ static std::unique_ptr<FilterBlockReader> Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context);
+
+ bool IsBlockBased() override { return false; }
+
+ bool KeyMayMatch(const Slice& key, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+
+ bool PrefixMayMatch(const Slice& prefix,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+
+ void KeysMayMatch(MultiGetRange* range,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) override;
+
+ void PrefixesMayMatch(MultiGetRange* range,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ BlockCacheLookupContext* lookup_context) override;
+ size_t ApproximateMemoryUsage() const override;
+ bool RangeMayExist(const Slice* iterate_upper_bound, const Slice& user_key,
+ const SliceTransform* prefix_extractor,
+ const Comparator* comparator,
+ const Slice* const const_ikey_ptr, bool* filter_checked,
+ bool need_upper_bound_check,
+ BlockCacheLookupContext* lookup_context) override;
+
+ private:
+ bool MayMatch(const Slice& entry, bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) const;
+ void MayMatch(MultiGetRange* range, bool no_io,
+ const SliceTransform* prefix_extractor,
+ BlockCacheLookupContext* lookup_context) const;
+ bool IsFilterCompatible(const Slice* iterate_upper_bound, const Slice& prefix,
+ const Comparator* comparator) const;
+
+ private:
+ bool full_length_enabled_;
+ size_t prefix_extractor_full_length_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/full_filter_block_test.cc b/storage/rocksdb/rocksdb/table/block_based/full_filter_block_test.cc
new file mode 100644
index 00000000..496b149a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/full_filter_block_test.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <set>
+
+#include "table/block_based/full_filter_block.h"
+#include "rocksdb/filter_policy.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/mock_block_based_table.h"
+#include "table/block_based/filter_policy_internal.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/coding.h"
+#include "util/hash.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TestFilterBitsBuilder : public FilterBitsBuilder {
+ public:
+ explicit TestFilterBitsBuilder() {}
+
+ // Add Key to filter
+ void AddKey(const Slice& key) override {
+ hash_entries_.push_back(Hash(key.data(), key.size(), 1));
+ }
+
+ // Generate the filter using the keys that are added
+ Slice Finish(std::unique_ptr<const char[]>* buf) override {
+ uint32_t len = static_cast<uint32_t>(hash_entries_.size()) * 4;
+ char* data = new char[len];
+ for (size_t i = 0; i < hash_entries_.size(); i++) {
+ EncodeFixed32(data + i * 4, hash_entries_[i]);
+ }
+ const char* const_data = data;
+ buf->reset(const_data);
+ return Slice(data, len);
+ }
+
+ private:
+ std::vector<uint32_t> hash_entries_;
+};
+
+class MockBlockBasedTable : public BlockBasedTable {
+ public:
+ explicit MockBlockBasedTable(Rep* rep)
+ : BlockBasedTable(rep, nullptr /* block_cache_tracer */) {}
+};
+
+class TestFilterBitsReader : public FilterBitsReader {
+ public:
+ explicit TestFilterBitsReader(const Slice& contents)
+ : data_(contents.data()), len_(static_cast<uint32_t>(contents.size())) {}
+
+ // Silence compiler warning about overloaded virtual
+ using FilterBitsReader::MayMatch;
+ bool MayMatch(const Slice& entry) override {
+ uint32_t h = Hash(entry.data(), entry.size(), 1);
+ for (size_t i = 0; i + 4 <= len_; i += 4) {
+ if (h == DecodeFixed32(data_ + i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private:
+ const char* data_;
+ uint32_t len_;
+};
+
+
+class TestHashFilter : public FilterPolicy {
+ public:
+ const char* Name() const override { return "TestHashFilter"; }
+
+ void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
+ for (int i = 0; i < n; i++) {
+ uint32_t h = Hash(keys[i].data(), keys[i].size(), 1);
+ PutFixed32(dst, h);
+ }
+ }
+
+ bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
+ uint32_t h = Hash(key.data(), key.size(), 1);
+ for (unsigned int i = 0; i + 4 <= filter.size(); i += 4) {
+ if (h == DecodeFixed32(filter.data() + i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ FilterBitsBuilder* GetFilterBitsBuilder() const override {
+ return new TestFilterBitsBuilder();
+ }
+
+ FilterBitsReader* GetFilterBitsReader(const Slice& contents) const override {
+ return new TestFilterBitsReader(contents);
+ }
+};
+
+class PluginFullFilterBlockTest : public mock::MockBlockBasedTableTester,
+ public testing::Test {
+ public:
+ PluginFullFilterBlockTest()
+ : mock::MockBlockBasedTableTester(new TestHashFilter) {}
+};
+
+TEST_F(PluginFullFilterBlockTest, PluginEmptyBuilder) {
+ FullFilterBlockBuilder builder(nullptr, true, GetBuilder());
+ Slice slice = builder.Finish();
+ ASSERT_EQ("", EscapeString(slice));
+
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(table_options_.filter_policy.get(),
+ BlockContents(slice)),
+ nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */);
+
+ FullFilterBlockReader reader(table_.get(), std::move(block));
+ // Remain same symantic with blockbased filter
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+TEST_F(PluginFullFilterBlockTest, PluginSingleChunk) {
+ FullFilterBlockBuilder builder(nullptr, true, GetBuilder());
+ builder.Add("foo");
+ builder.Add("bar");
+ builder.Add("box");
+ builder.Add("box");
+ builder.Add("hello");
+ Slice slice = builder.Finish();
+
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(table_options_.filter_policy.get(),
+ BlockContents(slice)),
+ nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */);
+
+ FullFilterBlockReader reader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "missing", /*prefix_extractor=*/nullptr, /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "other", /*prefix_extractor=*/nullptr, /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+class FullFilterBlockTest : public mock::MockBlockBasedTableTester,
+ public testing::Test {
+ public:
+ FullFilterBlockTest()
+ : mock::MockBlockBasedTableTester(NewBloomFilterPolicy(10, false)) {}
+};
+
+TEST_F(FullFilterBlockTest, EmptyBuilder) {
+ FullFilterBlockBuilder builder(nullptr, true, GetBuilder());
+ Slice slice = builder.Finish();
+ ASSERT_EQ("", EscapeString(slice));
+
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(table_options_.filter_policy.get(),
+ BlockContents(slice)),
+ nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */);
+
+ FullFilterBlockReader reader(table_.get(), std::move(block));
+ // Remain same symantic with blockbased filter
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+class CountUniqueFilterBitsBuilderWrapper : public FilterBitsBuilder {
+ std::unique_ptr<FilterBitsBuilder> b_;
+ std::set<std::string> uniq_;
+
+ public:
+ explicit CountUniqueFilterBitsBuilderWrapper(FilterBitsBuilder* b) : b_(b) {}
+
+ ~CountUniqueFilterBitsBuilderWrapper() override {}
+
+ void AddKey(const Slice& key) override {
+ b_->AddKey(key);
+ uniq_.insert(key.ToString());
+ }
+
+ Slice Finish(std::unique_ptr<const char[]>* buf) override {
+ Slice rv = b_->Finish(buf);
+ uniq_.clear();
+ return rv;
+ }
+
+ int CalculateNumEntry(const uint32_t bytes) override {
+ return b_->CalculateNumEntry(bytes);
+ }
+
+ size_t CountUnique() { return uniq_.size(); }
+};
+
+TEST_F(FullFilterBlockTest, DuplicateEntries) {
+ { // empty prefixes
+ std::unique_ptr<const SliceTransform> prefix_extractor(
+ NewFixedPrefixTransform(0));
+ auto bits_builder = new CountUniqueFilterBitsBuilderWrapper(GetBuilder());
+ const bool WHOLE_KEY = true;
+ FullFilterBlockBuilder builder(prefix_extractor.get(), WHOLE_KEY,
+ bits_builder);
+ ASSERT_EQ(0, builder.NumAdded());
+ ASSERT_EQ(0, bits_builder->CountUnique());
+ // adds key and empty prefix; both abstractions count them
+ builder.Add("key1");
+ ASSERT_EQ(2, builder.NumAdded());
+ ASSERT_EQ(2, bits_builder->CountUnique());
+ // Add different key (unique) and also empty prefix (not unique).
+ // From here in this test, it's immaterial whether the block builder
+ // can count unique keys.
+ builder.Add("key2");
+ ASSERT_EQ(3, bits_builder->CountUnique());
+ // Empty key -> nothing unique
+ builder.Add("");
+ ASSERT_EQ(3, bits_builder->CountUnique());
+ }
+
+ // mix of empty and non-empty
+ std::unique_ptr<const SliceTransform> prefix_extractor(
+ NewFixedPrefixTransform(7));
+ auto bits_builder = new CountUniqueFilterBitsBuilderWrapper(GetBuilder());
+ const bool WHOLE_KEY = true;
+ FullFilterBlockBuilder builder(prefix_extractor.get(), WHOLE_KEY,
+ bits_builder);
+ ASSERT_EQ(0, builder.NumAdded());
+ builder.Add(""); // test with empty key too
+ builder.Add("prefix1key1");
+ builder.Add("prefix1key1");
+ builder.Add("prefix1key2");
+ builder.Add("prefix1key3");
+ builder.Add("prefix2key4");
+ // 1 empty, 2 non-empty prefixes, and 4 non-empty keys
+ ASSERT_EQ(1 + 2 + 4, bits_builder->CountUnique());
+}
+
+TEST_F(FullFilterBlockTest, SingleChunk) {
+ FullFilterBlockBuilder builder(nullptr, true, GetBuilder());
+ ASSERT_EQ(0, builder.NumAdded());
+ builder.Add("foo");
+ builder.Add("bar");
+ builder.Add("box");
+ builder.Add("box");
+ builder.Add("hello");
+ ASSERT_EQ(5, builder.NumAdded());
+ Slice slice = builder.Finish();
+
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(table_options_.filter_policy.get(),
+ BlockContents(slice)),
+ nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */);
+
+ FullFilterBlockReader reader(table_.get(), std::move(block));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
+ /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "missing", /*prefix_extractor=*/nullptr, /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ ASSERT_TRUE(!reader.KeyMayMatch(
+ "other", /*prefix_extractor=*/nullptr, /*block_offset=*/kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/block_based/index_builder.cc b/storage/rocksdb/rocksdb/table/block_based/index_builder.cc
new file mode 100644
index 00000000..277bec61
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/index_builder.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_based/index_builder.h"
+
+#include <assert.h>
+#include <cinttypes>
+
+#include <list>
+#include <string>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/flush_block_policy.h"
+#include "table/block_based/partitioned_filter_block.h"
+#include "table/format.h"
+
+// Without anonymous namespace here, we fail the warning -Wmissing-prototypes
+namespace ROCKSDB_NAMESPACE {
+// using namespace rocksdb;
+// Create a index builder based on its type.
+IndexBuilder* IndexBuilder::CreateIndexBuilder(
+ BlockBasedTableOptions::IndexType index_type,
+ const InternalKeyComparator* comparator,
+ const InternalKeySliceTransform* int_key_slice_transform,
+ const bool use_value_delta_encoding,
+ const BlockBasedTableOptions& table_opt) {
+ IndexBuilder* result = nullptr;
+ switch (index_type) {
+ case BlockBasedTableOptions::kBinarySearch: {
+ result = new ShortenedIndexBuilder(
+ comparator, table_opt.index_block_restart_interval,
+ table_opt.format_version, use_value_delta_encoding,
+ table_opt.index_shortening, /* include_first_key */ false);
+ } break;
+ case BlockBasedTableOptions::kHashSearch: {
+ // Currently kHashSearch is incompatible with index_block_restart_interval
+ // > 1
+ assert(table_opt.index_block_restart_interval == 1);
+ result = new HashIndexBuilder(
+ comparator, int_key_slice_transform,
+ table_opt.index_block_restart_interval, table_opt.format_version,
+ use_value_delta_encoding, table_opt.index_shortening);
+ } break;
+ case BlockBasedTableOptions::kTwoLevelIndexSearch: {
+ result = PartitionedIndexBuilder::CreateIndexBuilder(
+ comparator, use_value_delta_encoding, table_opt);
+ } break;
+ case BlockBasedTableOptions::kBinarySearchWithFirstKey: {
+ result = new ShortenedIndexBuilder(
+ comparator, table_opt.index_block_restart_interval,
+ table_opt.format_version, use_value_delta_encoding,
+ table_opt.index_shortening, /* include_first_key */ true);
+ } break;
+ default: {
+ assert(!"Do not recognize the index type ");
+ } break;
+ }
+ return result;
+}
+
+PartitionedIndexBuilder* PartitionedIndexBuilder::CreateIndexBuilder(
+ const InternalKeyComparator* comparator,
+ const bool use_value_delta_encoding,
+ const BlockBasedTableOptions& table_opt) {
+ return new PartitionedIndexBuilder(comparator, table_opt,
+ use_value_delta_encoding);
+}
+
+PartitionedIndexBuilder::PartitionedIndexBuilder(
+ const InternalKeyComparator* comparator,
+ const BlockBasedTableOptions& table_opt,
+ const bool use_value_delta_encoding)
+ : IndexBuilder(comparator),
+ index_block_builder_(table_opt.index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ index_block_builder_without_seq_(table_opt.index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ sub_index_builder_(nullptr),
+ table_opt_(table_opt),
+ // We start by false. After each partition we revise the value based on
+ // what the sub_index_builder has decided. If the feature is disabled
+ // entirely, this will be set to true after switching the first
+ // sub_index_builder. Otherwise, it could be set to true even one of the
+ // sub_index_builders could not safely exclude seq from the keys, then it
+ // wil be enforced on all sub_index_builders on ::Finish.
+ seperator_is_key_plus_seq_(false),
+ use_value_delta_encoding_(use_value_delta_encoding) {}
+
+PartitionedIndexBuilder::~PartitionedIndexBuilder() {
+ delete sub_index_builder_;
+}
+
+void PartitionedIndexBuilder::MakeNewSubIndexBuilder() {
+ assert(sub_index_builder_ == nullptr);
+ sub_index_builder_ = new ShortenedIndexBuilder(
+ comparator_, table_opt_.index_block_restart_interval,
+ table_opt_.format_version, use_value_delta_encoding_,
+ table_opt_.index_shortening, /* include_first_key */ false);
+ flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
+ table_opt_.metadata_block_size, table_opt_.block_size_deviation,
+ // Note: this is sub-optimal since sub_index_builder_ could later reset
+ // seperator_is_key_plus_seq_ but the probability of that is low.
+ sub_index_builder_->seperator_is_key_plus_seq_
+ ? sub_index_builder_->index_block_builder_
+ : sub_index_builder_->index_block_builder_without_seq_));
+ partition_cut_requested_ = false;
+}
+
+void PartitionedIndexBuilder::RequestPartitionCut() {
+ partition_cut_requested_ = true;
+}
+
+void PartitionedIndexBuilder::AddIndexEntry(
+ std::string* last_key_in_current_block,
+ const Slice* first_key_in_next_block, const BlockHandle& block_handle) {
+ // Note: to avoid two consecuitive flush in the same method call, we do not
+ // check flush policy when adding the last key
+ if (UNLIKELY(first_key_in_next_block == nullptr)) { // no more keys
+ if (sub_index_builder_ == nullptr) {
+ MakeNewSubIndexBuilder();
+ }
+ sub_index_builder_->AddIndexEntry(last_key_in_current_block,
+ first_key_in_next_block, block_handle);
+ if (sub_index_builder_->seperator_is_key_plus_seq_) {
+ // then we need to apply it to all sub-index builders
+ seperator_is_key_plus_seq_ = true;
+ }
+ sub_index_last_key_ = std::string(*last_key_in_current_block);
+ entries_.push_back(
+ {sub_index_last_key_,
+ std::unique_ptr<ShortenedIndexBuilder>(sub_index_builder_)});
+ sub_index_builder_ = nullptr;
+ cut_filter_block = true;
+ } else {
+ // apply flush policy only to non-empty sub_index_builder_
+ if (sub_index_builder_ != nullptr) {
+ std::string handle_encoding;
+ block_handle.EncodeTo(&handle_encoding);
+ bool do_flush =
+ partition_cut_requested_ ||
+ flush_policy_->Update(*last_key_in_current_block, handle_encoding);
+ if (do_flush) {
+ entries_.push_back(
+ {sub_index_last_key_,
+ std::unique_ptr<ShortenedIndexBuilder>(sub_index_builder_)});
+ cut_filter_block = true;
+ sub_index_builder_ = nullptr;
+ }
+ }
+ if (sub_index_builder_ == nullptr) {
+ MakeNewSubIndexBuilder();
+ }
+ sub_index_builder_->AddIndexEntry(last_key_in_current_block,
+ first_key_in_next_block, block_handle);
+ sub_index_last_key_ = std::string(*last_key_in_current_block);
+ if (sub_index_builder_->seperator_is_key_plus_seq_) {
+ // then we need to apply it to all sub-index builders
+ seperator_is_key_plus_seq_ = true;
+ }
+ }
+}
+
+Status PartitionedIndexBuilder::Finish(
+ IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) {
+ if (partition_cnt_ == 0) {
+ partition_cnt_ = entries_.size();
+ }
+ // It must be set to null after last key is added
+ assert(sub_index_builder_ == nullptr);
+ if (finishing_indexes == true) {
+ Entry& last_entry = entries_.front();
+ std::string handle_encoding;
+ last_partition_block_handle.EncodeTo(&handle_encoding);
+ std::string handle_delta_encoding;
+ PutVarsignedint64(
+ &handle_delta_encoding,
+ last_partition_block_handle.size() - last_encoded_handle_.size());
+ last_encoded_handle_ = last_partition_block_handle;
+ const Slice handle_delta_encoding_slice(handle_delta_encoding);
+ index_block_builder_.Add(last_entry.key, handle_encoding,
+ &handle_delta_encoding_slice);
+ if (!seperator_is_key_plus_seq_) {
+ index_block_builder_without_seq_.Add(ExtractUserKey(last_entry.key),
+ handle_encoding,
+ &handle_delta_encoding_slice);
+ }
+ entries_.pop_front();
+ }
+ // If there is no sub_index left, then return the 2nd level index.
+ if (UNLIKELY(entries_.empty())) {
+ if (seperator_is_key_plus_seq_) {
+ index_blocks->index_block_contents = index_block_builder_.Finish();
+ } else {
+ index_blocks->index_block_contents =
+ index_block_builder_without_seq_.Finish();
+ }
+ top_level_index_size_ = index_blocks->index_block_contents.size();
+ index_size_ += top_level_index_size_;
+ return Status::OK();
+ } else {
+ // Finish the next partition index in line and Incomplete() to indicate we
+ // expect more calls to Finish
+ Entry& entry = entries_.front();
+ // Apply the policy to all sub-indexes
+ entry.value->seperator_is_key_plus_seq_ = seperator_is_key_plus_seq_;
+ auto s = entry.value->Finish(index_blocks);
+ index_size_ += index_blocks->index_block_contents.size();
+ finishing_indexes = true;
+ return s.ok() ? Status::Incomplete() : s;
+ }
+}
+
+size_t PartitionedIndexBuilder::NumPartitions() const { return partition_cnt_; }
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/index_builder.h b/storage/rocksdb/rocksdb/table/block_based/index_builder.h
new file mode 100644
index 00000000..bfffc599
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/index_builder.h
@@ -0,0 +1,443 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <cinttypes>
+
+#include <list>
+#include <string>
+#include <unordered_map>
+
+#include "rocksdb/comparator.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/block_based/block_builder.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+// The interface for building index.
+// Instruction for adding a new concrete IndexBuilder:
+// 1. Create a subclass instantiated from IndexBuilder.
+// 2. Add a new entry associated with that subclass in TableOptions::IndexType.
+// 3. Add a create function for the new subclass in CreateIndexBuilder.
+// Note: we can devise more advanced design to simplify the process for adding
+// new subclass, which will, on the other hand, increase the code complexity and
+// catch unwanted attention from readers. Given that we won't add/change
+// indexes frequently, it makes sense to just embrace a more straightforward
+// design that just works.
+class IndexBuilder {
+ public:
+ static IndexBuilder* CreateIndexBuilder(
+ BlockBasedTableOptions::IndexType index_type,
+ const ROCKSDB_NAMESPACE::InternalKeyComparator* comparator,
+ const InternalKeySliceTransform* int_key_slice_transform,
+ const bool use_value_delta_encoding,
+ const BlockBasedTableOptions& table_opt);
+
+ // Index builder will construct a set of blocks which contain:
+ // 1. One primary index block.
+ // 2. (Optional) a set of metablocks that contains the metadata of the
+ // primary index.
+ struct IndexBlocks {
+ Slice index_block_contents;
+ std::unordered_map<std::string, Slice> meta_blocks;
+ };
+ explicit IndexBuilder(const InternalKeyComparator* comparator)
+ : comparator_(comparator) {}
+
+ virtual ~IndexBuilder() {}
+
+ // Add a new index entry to index block.
+ // To allow further optimization, we provide `last_key_in_current_block` and
+ // `first_key_in_next_block`, based on which the specific implementation can
+ // determine the best index key to be used for the index block.
+ // Called before the OnKeyAdded() call for first_key_in_next_block.
+ // @last_key_in_current_block: this parameter maybe overridden with the value
+ // "substitute key".
+ // @first_key_in_next_block: it will be nullptr if the entry being added is
+ // the last one in the table
+ //
+ // REQUIRES: Finish() has not yet been called.
+ virtual void AddIndexEntry(std::string* last_key_in_current_block,
+ const Slice* first_key_in_next_block,
+ const BlockHandle& block_handle) = 0;
+
+ // This method will be called whenever a key is added. The subclasses may
+ // override OnKeyAdded() if they need to collect additional information.
+ virtual void OnKeyAdded(const Slice& /*key*/) {}
+
+ // Inform the index builder that all entries has been written. Block builder
+ // may therefore perform any operation required for block finalization.
+ //
+ // REQUIRES: Finish() has not yet been called.
+ inline Status Finish(IndexBlocks* index_blocks) {
+ // Throw away the changes to last_partition_block_handle. It has no effect
+ // on the first call to Finish anyway.
+ BlockHandle last_partition_block_handle;
+ return Finish(index_blocks, last_partition_block_handle);
+ }
+
+ // This override of Finish can be utilized to build the 2nd level index in
+ // PartitionIndexBuilder.
+ //
+ // index_blocks will be filled with the resulting index data. If the return
+ // value is Status::InComplete() then it means that the index is partitioned
+ // and the callee should keep calling Finish until Status::OK() is returned.
+ // In that case, last_partition_block_handle is pointer to the block written
+ // with the result of the last call to Finish. This can be utilized to build
+ // the second level index pointing to each block of partitioned indexes. The
+ // last call to Finish() that returns Status::OK() populates index_blocks with
+ // the 2nd level index content.
+ virtual Status Finish(IndexBlocks* index_blocks,
+ const BlockHandle& last_partition_block_handle) = 0;
+
+ // Get the size for index block. Must be called after ::Finish.
+ virtual size_t IndexSize() const = 0;
+
+ virtual bool seperator_is_key_plus_seq() { return true; }
+
+ protected:
+ const InternalKeyComparator* comparator_;
+ // Set after ::Finish is called
+ size_t index_size_ = 0;
+};
+
+// This index builder builds space-efficient index block.
+//
+// Optimizations:
+// 1. Made block's `block_restart_interval` to be 1, which will avoid linear
+// search when doing index lookup (can be disabled by setting
+// index_block_restart_interval).
+// 2. Shorten the key length for index block. Other than honestly using the
+// last key in the data block as the index key, we instead find a shortest
+// substitute key that serves the same function.
+class ShortenedIndexBuilder : public IndexBuilder {
+ public:
+ explicit ShortenedIndexBuilder(
+ const InternalKeyComparator* comparator,
+ const int index_block_restart_interval, const uint32_t format_version,
+ const bool use_value_delta_encoding,
+ BlockBasedTableOptions::IndexShorteningMode shortening_mode,
+ bool include_first_key)
+ : IndexBuilder(comparator),
+ index_block_builder_(index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ index_block_builder_without_seq_(index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ use_value_delta_encoding_(use_value_delta_encoding),
+ include_first_key_(include_first_key),
+ shortening_mode_(shortening_mode) {
+ // Making the default true will disable the feature for old versions
+ seperator_is_key_plus_seq_ = (format_version <= 2);
+ }
+
+ virtual void OnKeyAdded(const Slice& key) override {
+ if (include_first_key_ && current_block_first_internal_key_.empty()) {
+ current_block_first_internal_key_.assign(key.data(), key.size());
+ }
+ }
+
+ virtual void AddIndexEntry(std::string* last_key_in_current_block,
+ const Slice* first_key_in_next_block,
+ const BlockHandle& block_handle) override {
+ if (first_key_in_next_block != nullptr) {
+ if (shortening_mode_ !=
+ BlockBasedTableOptions::IndexShorteningMode::kNoShortening) {
+ comparator_->FindShortestSeparator(last_key_in_current_block,
+ *first_key_in_next_block);
+ }
+ if (!seperator_is_key_plus_seq_ &&
+ comparator_->user_comparator()->Compare(
+ ExtractUserKey(*last_key_in_current_block),
+ ExtractUserKey(*first_key_in_next_block)) == 0) {
+ seperator_is_key_plus_seq_ = true;
+ }
+ } else {
+ if (shortening_mode_ == BlockBasedTableOptions::IndexShorteningMode::
+ kShortenSeparatorsAndSuccessor) {
+ comparator_->FindShortSuccessor(last_key_in_current_block);
+ }
+ }
+ auto sep = Slice(*last_key_in_current_block);
+
+ assert(!include_first_key_ || !current_block_first_internal_key_.empty());
+ IndexValue entry(block_handle, current_block_first_internal_key_);
+ std::string encoded_entry;
+ std::string delta_encoded_entry;
+ entry.EncodeTo(&encoded_entry, include_first_key_, nullptr);
+ if (use_value_delta_encoding_ && !last_encoded_handle_.IsNull()) {
+ entry.EncodeTo(&delta_encoded_entry, include_first_key_,
+ &last_encoded_handle_);
+ } else {
+ // If it's the first block, or delta encoding is disabled,
+ // BlockBuilder::Add() below won't use delta-encoded slice.
+ }
+ last_encoded_handle_ = block_handle;
+ const Slice delta_encoded_entry_slice(delta_encoded_entry);
+ index_block_builder_.Add(sep, encoded_entry, &delta_encoded_entry_slice);
+ if (!seperator_is_key_plus_seq_) {
+ index_block_builder_without_seq_.Add(ExtractUserKey(sep), encoded_entry,
+ &delta_encoded_entry_slice);
+ }
+
+ current_block_first_internal_key_.clear();
+ }
+
+ using IndexBuilder::Finish;
+ virtual Status Finish(
+ IndexBlocks* index_blocks,
+ const BlockHandle& /*last_partition_block_handle*/) override {
+ if (seperator_is_key_plus_seq_) {
+ index_blocks->index_block_contents = index_block_builder_.Finish();
+ } else {
+ index_blocks->index_block_contents =
+ index_block_builder_without_seq_.Finish();
+ }
+ index_size_ = index_blocks->index_block_contents.size();
+ return Status::OK();
+ }
+
+ virtual size_t IndexSize() const override { return index_size_; }
+
+ virtual bool seperator_is_key_plus_seq() override {
+ return seperator_is_key_plus_seq_;
+ }
+
+ friend class PartitionedIndexBuilder;
+
+ private:
+ BlockBuilder index_block_builder_;
+ BlockBuilder index_block_builder_without_seq_;
+ const bool use_value_delta_encoding_;
+ bool seperator_is_key_plus_seq_;
+ const bool include_first_key_;
+ BlockBasedTableOptions::IndexShorteningMode shortening_mode_;
+ BlockHandle last_encoded_handle_ = BlockHandle::NullBlockHandle();
+ std::string current_block_first_internal_key_;
+};
+
+// HashIndexBuilder contains a binary-searchable primary index and the
+// metadata for secondary hash index construction.
+// The metadata for hash index consists two parts:
+// - a metablock that compactly contains a sequence of prefixes. All prefixes
+// are stored consectively without any metadata (like, prefix sizes) being
+// stored, which is kept in the other metablock.
+// - a metablock contains the metadata of the prefixes, including prefix size,
+// restart index and number of block it spans. The format looks like:
+//
+// +-----------------+---------------------------+---------------------+
+// <=prefix 1
+// | length: 4 bytes | restart interval: 4 bytes | num-blocks: 4 bytes |
+// +-----------------+---------------------------+---------------------+
+// <=prefix 2
+// | length: 4 bytes | restart interval: 4 bytes | num-blocks: 4 bytes |
+// +-----------------+---------------------------+---------------------+
+// | |
+// | .... |
+// | |
+// +-----------------+---------------------------+---------------------+
+// <=prefix n
+// | length: 4 bytes | restart interval: 4 bytes | num-blocks: 4 bytes |
+// +-----------------+---------------------------+---------------------+
+//
+// The reason of separating these two metablocks is to enable the efficiently
+// reuse the first metablock during hash index construction without unnecessary
+// data copy or small heap allocations for prefixes.
+class HashIndexBuilder : public IndexBuilder {
+ public:
+ explicit HashIndexBuilder(
+ const InternalKeyComparator* comparator,
+ const SliceTransform* hash_key_extractor,
+ int index_block_restart_interval, int format_version,
+ bool use_value_delta_encoding,
+ BlockBasedTableOptions::IndexShorteningMode shortening_mode)
+ : IndexBuilder(comparator),
+ primary_index_builder_(comparator, index_block_restart_interval,
+ format_version, use_value_delta_encoding,
+ shortening_mode, /* include_first_key */ false),
+ hash_key_extractor_(hash_key_extractor) {}
+
+ virtual void AddIndexEntry(std::string* last_key_in_current_block,
+ const Slice* first_key_in_next_block,
+ const BlockHandle& block_handle) override {
+ ++current_restart_index_;
+ primary_index_builder_.AddIndexEntry(last_key_in_current_block,
+ first_key_in_next_block, block_handle);
+ }
+
+ virtual void OnKeyAdded(const Slice& key) override {
+ auto key_prefix = hash_key_extractor_->Transform(key);
+ bool is_first_entry = pending_block_num_ == 0;
+
+ // Keys may share the prefix
+ if (is_first_entry || pending_entry_prefix_ != key_prefix) {
+ if (!is_first_entry) {
+ FlushPendingPrefix();
+ }
+
+ // need a hard copy otherwise the underlying data changes all the time.
+ // TODO(kailiu) ToString() is expensive. We may speed up can avoid data
+ // copy.
+ pending_entry_prefix_ = key_prefix.ToString();
+ pending_block_num_ = 1;
+ pending_entry_index_ = static_cast<uint32_t>(current_restart_index_);
+ } else {
+ // entry number increments when keys share the prefix reside in
+ // different data blocks.
+ auto last_restart_index = pending_entry_index_ + pending_block_num_ - 1;
+ assert(last_restart_index <= current_restart_index_);
+ if (last_restart_index != current_restart_index_) {
+ ++pending_block_num_;
+ }
+ }
+ }
+
+ virtual Status Finish(
+ IndexBlocks* index_blocks,
+ const BlockHandle& last_partition_block_handle) override {
+ if (pending_block_num_ != 0) {
+ FlushPendingPrefix();
+ }
+ primary_index_builder_.Finish(index_blocks, last_partition_block_handle);
+ index_blocks->meta_blocks.insert(
+ {kHashIndexPrefixesBlock.c_str(), prefix_block_});
+ index_blocks->meta_blocks.insert(
+ {kHashIndexPrefixesMetadataBlock.c_str(), prefix_meta_block_});
+ return Status::OK();
+ }
+
+ virtual size_t IndexSize() const override {
+ return primary_index_builder_.IndexSize() + prefix_block_.size() +
+ prefix_meta_block_.size();
+ }
+
+ virtual bool seperator_is_key_plus_seq() override {
+ return primary_index_builder_.seperator_is_key_plus_seq();
+ }
+
+ private:
+ void FlushPendingPrefix() {
+ prefix_block_.append(pending_entry_prefix_.data(),
+ pending_entry_prefix_.size());
+ PutVarint32Varint32Varint32(
+ &prefix_meta_block_,
+ static_cast<uint32_t>(pending_entry_prefix_.size()),
+ pending_entry_index_, pending_block_num_);
+ }
+
+ ShortenedIndexBuilder primary_index_builder_;
+ const SliceTransform* hash_key_extractor_;
+
+ // stores a sequence of prefixes
+ std::string prefix_block_;
+ // stores the metadata of prefixes
+ std::string prefix_meta_block_;
+
+ // The following 3 variables keeps unflushed prefix and its metadata.
+ // The details of block_num and entry_index can be found in
+ // "block_hash_index.{h,cc}"
+ uint32_t pending_block_num_ = 0;
+ uint32_t pending_entry_index_ = 0;
+ std::string pending_entry_prefix_;
+
+ uint64_t current_restart_index_ = 0;
+};
+
+/**
+ * IndexBuilder for two-level indexing. Internally it creates a new index for
+ * each partition and Finish then in order when Finish is called on it
+ * continiously until Status::OK() is returned.
+ *
+ * The format on the disk would be I I I I I I IP where I is block containing a
+ * partition of indexes built using ShortenedIndexBuilder and IP is a block
+ * containing a secondary index on the partitions, built using
+ * ShortenedIndexBuilder.
+ */
+class PartitionedIndexBuilder : public IndexBuilder {
+ public:
+ static PartitionedIndexBuilder* CreateIndexBuilder(
+ const ROCKSDB_NAMESPACE::InternalKeyComparator* comparator,
+ const bool use_value_delta_encoding,
+ const BlockBasedTableOptions& table_opt);
+
+ explicit PartitionedIndexBuilder(const InternalKeyComparator* comparator,
+ const BlockBasedTableOptions& table_opt,
+ const bool use_value_delta_encoding);
+
+ virtual ~PartitionedIndexBuilder();
+
+ virtual void AddIndexEntry(std::string* last_key_in_current_block,
+ const Slice* first_key_in_next_block,
+ const BlockHandle& block_handle) override;
+
+ virtual Status Finish(
+ IndexBlocks* index_blocks,
+ const BlockHandle& last_partition_block_handle) override;
+
+ virtual size_t IndexSize() const override { return index_size_; }
+ size_t TopLevelIndexSize(uint64_t) const { return top_level_index_size_; }
+ size_t NumPartitions() const;
+
+ inline bool ShouldCutFilterBlock() {
+ // Current policy is to align the partitions of index and filters
+ if (cut_filter_block) {
+ cut_filter_block = false;
+ return true;
+ }
+ return false;
+ }
+
+ std::string& GetPartitionKey() { return sub_index_last_key_; }
+
+ // Called when an external entity (such as filter partition builder) request
+ // cutting the next partition
+ void RequestPartitionCut();
+
+ virtual bool seperator_is_key_plus_seq() override {
+ return seperator_is_key_plus_seq_;
+ }
+
+ bool get_use_value_delta_encoding() { return use_value_delta_encoding_; }
+
+ private:
+ // Set after ::Finish is called
+ size_t top_level_index_size_ = 0;
+ // Set after ::Finish is called
+ size_t partition_cnt_ = 0;
+
+ void MakeNewSubIndexBuilder();
+
+ struct Entry {
+ std::string key;
+ std::unique_ptr<ShortenedIndexBuilder> value;
+ };
+ std::list<Entry> entries_; // list of partitioned indexes and their keys
+ BlockBuilder index_block_builder_; // top-level index builder
+ BlockBuilder index_block_builder_without_seq_; // same for user keys
+ // the active partition index builder
+ ShortenedIndexBuilder* sub_index_builder_;
+ // the last key in the active partition index builder
+ std::string sub_index_last_key_;
+ std::unique_ptr<FlushBlockPolicy> flush_policy_;
+ // true if Finish is called once but not complete yet.
+ bool finishing_indexes = false;
+ const BlockBasedTableOptions& table_opt_;
+ bool seperator_is_key_plus_seq_;
+ bool use_value_delta_encoding_;
+ // true if an external entity (such as filter partition builder) request
+ // cutting the next partition
+ bool partition_cut_requested_ = true;
+ // true if it should cut the next filter partition block
+ bool cut_filter_block = false;
+ BlockHandle last_encoded_handle_;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/mock_block_based_table.h b/storage/rocksdb/rocksdb/table/block_based/mock_block_based_table.h
new file mode 100644
index 00000000..54817bd6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/mock_block_based_table.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/filter_policy.h"
+#include "table/block_based/block_based_filter_block.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/filter_policy_internal.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace mock {
+
+class MockBlockBasedTable : public BlockBasedTable {
+ public:
+ explicit MockBlockBasedTable(Rep* rep)
+ : BlockBasedTable(rep, nullptr /* block_cache_tracer */) {}
+};
+
+class MockBlockBasedTableTester {
+ static constexpr int kMockLevel = 0;
+
+ public:
+ Options options_;
+ ImmutableCFOptions ioptions_;
+ EnvOptions env_options_;
+ BlockBasedTableOptions table_options_;
+ InternalKeyComparator icomp_;
+ std::unique_ptr<BlockBasedTable> table_;
+
+ MockBlockBasedTableTester(const FilterPolicy *filter_policy)
+ : ioptions_(options_),
+ env_options_(options_),
+ icomp_(options_.comparator) {
+ table_options_.filter_policy.reset(filter_policy);
+
+ constexpr bool skip_filters = false;
+ constexpr bool immortal_table = false;
+ table_.reset(new MockBlockBasedTable(new BlockBasedTable::Rep(
+ ioptions_, env_options_, table_options_, icomp_, skip_filters,
+ kMockLevel, immortal_table)));
+ }
+
+ FilterBitsBuilder* GetBuilder() const {
+ FilterBuildingContext context(table_options_);
+ context.column_family_name = "mock_cf";
+ context.compaction_style = ioptions_.compaction_style;
+ context.level_at_creation = kMockLevel;
+ context.info_log = ioptions_.info_log;
+ return BloomFilterPolicy::GetBuilderFromContext(context);
+ }
+};
+
+} // namespace mock
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.cc b/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.cc
new file mode 100644
index 00000000..3e555387
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/parsed_full_filter_block.h"
+#include "rocksdb/filter_policy.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ParsedFullFilterBlock::ParsedFullFilterBlock(const FilterPolicy* filter_policy,
+ BlockContents&& contents)
+ : block_contents_(std::move(contents)),
+ filter_bits_reader_(
+ !block_contents_.data.empty()
+ ? filter_policy->GetFilterBitsReader(block_contents_.data)
+ : nullptr) {}
+
+ParsedFullFilterBlock::~ParsedFullFilterBlock() = default;
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.h b/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.h
new file mode 100644
index 00000000..36c61992
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/parsed_full_filter_block.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class FilterBitsReader;
+class FilterPolicy;
+
+// The sharable/cachable part of the full filter.
+class ParsedFullFilterBlock {
+ public:
+ ParsedFullFilterBlock(const FilterPolicy* filter_policy,
+ BlockContents&& contents);
+ ~ParsedFullFilterBlock();
+
+ FilterBitsReader* filter_bits_reader() const {
+ return filter_bits_reader_.get();
+ }
+
+ // TODO: consider memory usage of the FilterBitsReader
+ size_t ApproximateMemoryUsage() const {
+ return block_contents_.ApproximateMemoryUsage();
+ }
+
+ bool own_bytes() const { return block_contents_.own_bytes(); }
+
+ private:
+ BlockContents block_contents_;
+ std::unique_ptr<FilterBitsReader> filter_bits_reader_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc
new file mode 100644
index 00000000..2138d96d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.cc
@@ -0,0 +1,388 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/partitioned_filter_block.h"
+
+#include <utility>
+
+#include "monitoring/perf_context_imp.h"
+#include "port/malloc.h"
+#include "port/port.h"
+#include "rocksdb/filter_policy.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+PartitionedFilterBlockBuilder::PartitionedFilterBlockBuilder(
+ const SliceTransform* _prefix_extractor, bool whole_key_filtering,
+ FilterBitsBuilder* filter_bits_builder, int index_block_restart_interval,
+ const bool use_value_delta_encoding,
+ PartitionedIndexBuilder* const p_index_builder,
+ const uint32_t partition_size)
+ : FullFilterBlockBuilder(_prefix_extractor, whole_key_filtering,
+ filter_bits_builder),
+ index_on_filter_block_builder_(index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ index_on_filter_block_builder_without_seq_(index_block_restart_interval,
+ true /*use_delta_encoding*/,
+ use_value_delta_encoding),
+ p_index_builder_(p_index_builder),
+ keys_added_to_partition_(0) {
+ keys_per_partition_ =
+ filter_bits_builder_->CalculateNumEntry(partition_size);
+}
+
+PartitionedFilterBlockBuilder::~PartitionedFilterBlockBuilder() {}
+
+void PartitionedFilterBlockBuilder::MaybeCutAFilterBlock(
+ const Slice* next_key) {
+ // Use == to send the request only once
+ if (keys_added_to_partition_ == keys_per_partition_) {
+ // Currently only index builder is in charge of cutting a partition. We keep
+ // requesting until it is granted.
+ p_index_builder_->RequestPartitionCut();
+ }
+ if (!p_index_builder_->ShouldCutFilterBlock()) {
+ return;
+ }
+ filter_gc.push_back(std::unique_ptr<const char[]>(nullptr));
+
+ // Add the prefix of the next key before finishing the partition. This hack,
+ // fixes a bug with format_verison=3 where seeking for the prefix would lead
+ // us to the previous partition.
+ const bool add_prefix =
+ next_key && prefix_extractor() && prefix_extractor()->InDomain(*next_key);
+ if (add_prefix) {
+ FullFilterBlockBuilder::AddPrefix(*next_key);
+ }
+
+ Slice filter = filter_bits_builder_->Finish(&filter_gc.back());
+ std::string& index_key = p_index_builder_->GetPartitionKey();
+ filters.push_back({index_key, filter});
+ keys_added_to_partition_ = 0;
+ Reset();
+}
+
+void PartitionedFilterBlockBuilder::Add(const Slice& key) {
+ MaybeCutAFilterBlock(&key);
+ FullFilterBlockBuilder::Add(key);
+}
+
+void PartitionedFilterBlockBuilder::AddKey(const Slice& key) {
+ FullFilterBlockBuilder::AddKey(key);
+ keys_added_to_partition_++;
+}
+
+Slice PartitionedFilterBlockBuilder::Finish(
+ const BlockHandle& last_partition_block_handle, Status* status) {
+ if (finishing_filters == true) {
+ // Record the handle of the last written filter block in the index
+ FilterEntry& last_entry = filters.front();
+ std::string handle_encoding;
+ last_partition_block_handle.EncodeTo(&handle_encoding);
+ std::string handle_delta_encoding;
+ PutVarsignedint64(
+ &handle_delta_encoding,
+ last_partition_block_handle.size() - last_encoded_handle_.size());
+ last_encoded_handle_ = last_partition_block_handle;
+ const Slice handle_delta_encoding_slice(handle_delta_encoding);
+ index_on_filter_block_builder_.Add(last_entry.key, handle_encoding,
+ &handle_delta_encoding_slice);
+ if (!p_index_builder_->seperator_is_key_plus_seq()) {
+ index_on_filter_block_builder_without_seq_.Add(
+ ExtractUserKey(last_entry.key), handle_encoding,
+ &handle_delta_encoding_slice);
+ }
+ filters.pop_front();
+ } else {
+ MaybeCutAFilterBlock(nullptr);
+ }
+ // If there is no filter partition left, then return the index on filter
+ // partitions
+ if (UNLIKELY(filters.empty())) {
+ *status = Status::OK();
+ if (finishing_filters) {
+ if (p_index_builder_->seperator_is_key_plus_seq()) {
+ return index_on_filter_block_builder_.Finish();
+ } else {
+ return index_on_filter_block_builder_without_seq_.Finish();
+ }
+ } else {
+ // This is the rare case where no key was added to the filter
+ return Slice();
+ }
+ } else {
+ // Return the next filter partition in line and set Incomplete() status to
+ // indicate we expect more calls to Finish
+ *status = Status::Incomplete();
+ finishing_filters = true;
+ return filters.front().filter;
+ }
+}
+
+PartitionedFilterBlockReader::PartitionedFilterBlockReader(
+ const BlockBasedTable* t, CachableEntry<Block>&& filter_block)
+ : FilterBlockReaderCommon(t, std::move(filter_block)) {}
+
+std::unique_ptr<FilterBlockReader> PartitionedFilterBlockReader::Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context) {
+ assert(table);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+
+ CachableEntry<Block> filter_block;
+ if (prefetch || !use_cache) {
+ const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
+ use_cache, nullptr /* get_context */,
+ lookup_context, &filter_block);
+ if (!s.ok()) {
+ return std::unique_ptr<FilterBlockReader>();
+ }
+
+ if (use_cache && !pin) {
+ filter_block.Reset();
+ }
+ }
+
+ return std::unique_ptr<FilterBlockReader>(
+ new PartitionedFilterBlockReader(table, std::move(filter_block)));
+}
+
+bool PartitionedFilterBlockReader::KeyMayMatch(
+ const Slice& key, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io, const Slice* const const_ikey_ptr,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context) {
+ assert(const_ikey_ptr != nullptr);
+ assert(block_offset == kNotValid);
+ if (!whole_key_filtering()) {
+ return true;
+ }
+
+ return MayMatch(key, prefix_extractor, block_offset, no_io, const_ikey_ptr,
+ get_context, lookup_context,
+ &FullFilterBlockReader::KeyMayMatch);
+}
+
+bool PartitionedFilterBlockReader::PrefixMayMatch(
+ const Slice& prefix, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io, const Slice* const const_ikey_ptr,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context) {
+#ifdef NDEBUG
+ (void)block_offset;
+#endif
+ assert(const_ikey_ptr != nullptr);
+ assert(block_offset == kNotValid);
+ if (!table_prefix_extractor() && !prefix_extractor) {
+ return true;
+ }
+
+ return MayMatch(prefix, prefix_extractor, block_offset, no_io, const_ikey_ptr,
+ get_context, lookup_context,
+ &FullFilterBlockReader::PrefixMayMatch);
+}
+
+BlockHandle PartitionedFilterBlockReader::GetFilterPartitionHandle(
+ const CachableEntry<Block>& filter_block, const Slice& entry) const {
+ IndexBlockIter iter;
+ const InternalKeyComparator* const comparator = internal_comparator();
+ Statistics* kNullStats = nullptr;
+ filter_block.GetValue()->NewIndexIterator(
+ comparator, comparator->user_comparator(), &iter, kNullStats,
+ true /* total_order_seek */, false /* have_first_key */,
+ index_key_includes_seq(), index_value_is_full());
+ iter.Seek(entry);
+ if (UNLIKELY(!iter.Valid())) {
+ // entry is larger than all the keys. However its prefix might still be
+ // present in the last partition. If this is called by PrefixMayMatch this
+ // is necessary for correct behavior. Otherwise it is unnecessary but safe.
+ // Assuming this is an unlikely case for full key search, the performance
+ // overhead should be negligible.
+ iter.SeekToLast();
+ }
+ assert(iter.Valid());
+ BlockHandle fltr_blk_handle = iter.value().handle;
+ return fltr_blk_handle;
+}
+
+Status PartitionedFilterBlockReader::GetFilterPartitionBlock(
+ FilePrefetchBuffer* prefetch_buffer, const BlockHandle& fltr_blk_handle,
+ bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<ParsedFullFilterBlock>* filter_block) const {
+ assert(table());
+ assert(filter_block);
+ assert(filter_block->IsEmpty());
+
+ if (!filter_map_.empty()) {
+ auto iter = filter_map_.find(fltr_blk_handle.offset());
+ // This is a possible scenario since block cache might not have had space
+ // for the partition
+ if (iter != filter_map_.end()) {
+ filter_block->SetUnownedValue(iter->second.GetValue());
+ return Status::OK();
+ }
+ }
+
+ ReadOptions read_options;
+ if (no_io) {
+ read_options.read_tier = kBlockCacheTier;
+ }
+
+ const Status s =
+ table()->RetrieveBlock(prefetch_buffer, read_options, fltr_blk_handle,
+ UncompressionDict::GetEmptyDict(), filter_block,
+ BlockType::kFilter, get_context, lookup_context,
+ /* for_compaction */ false, /* use_cache */ true);
+
+ return s;
+}
+
+bool PartitionedFilterBlockReader::MayMatch(
+ const Slice& slice, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, bool no_io, const Slice* const_ikey_ptr,
+ GetContext* get_context, BlockCacheLookupContext* lookup_context,
+ FilterFunction filter_function) const {
+ CachableEntry<Block> filter_block;
+ Status s =
+ GetOrReadFilterBlock(no_io, get_context, lookup_context, &filter_block);
+ if (UNLIKELY(!s.ok())) {
+ return true;
+ }
+
+ if (UNLIKELY(filter_block.GetValue()->size() == 0)) {
+ return true;
+ }
+
+ auto filter_handle = GetFilterPartitionHandle(filter_block, *const_ikey_ptr);
+ if (UNLIKELY(filter_handle.size() == 0)) { // key is out of range
+ return false;
+ }
+
+ CachableEntry<ParsedFullFilterBlock> filter_partition_block;
+ s = GetFilterPartitionBlock(nullptr /* prefetch_buffer */, filter_handle,
+ no_io, get_context, lookup_context,
+ &filter_partition_block);
+ if (UNLIKELY(!s.ok())) {
+ return true;
+ }
+
+ FullFilterBlockReader filter_partition(table(),
+ std::move(filter_partition_block));
+ return (filter_partition.*filter_function)(
+ slice, prefix_extractor, block_offset, no_io, const_ikey_ptr, get_context,
+ lookup_context);
+}
+
+size_t PartitionedFilterBlockReader::ApproximateMemoryUsage() const {
+ size_t usage = ApproximateFilterBlockMemoryUsage();
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<PartitionedFilterBlockReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ return usage;
+ // TODO(myabandeh): better estimation for filter_map_ size
+}
+
+// TODO(myabandeh): merge this with the same function in IndexReader
+void PartitionedFilterBlockReader::CacheDependencies(bool pin) {
+ assert(table());
+
+ const BlockBasedTable::Rep* const rep = table()->get_rep();
+ assert(rep);
+
+ BlockCacheLookupContext lookup_context{TableReaderCaller::kPrefetch};
+
+ CachableEntry<Block> filter_block;
+
+ Status s = GetOrReadFilterBlock(false /* no_io */, nullptr /* get_context */,
+ &lookup_context, &filter_block);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(rep->ioptions.info_log,
+ "Error retrieving top-level filter block while trying to "
+ "cache filter partitions: %s",
+ s.ToString().c_str());
+ return;
+ }
+
+ // Before read partitions, prefetch them to avoid lots of IOs
+ assert(filter_block.GetValue());
+
+ IndexBlockIter biter;
+ const InternalKeyComparator* const comparator = internal_comparator();
+ Statistics* kNullStats = nullptr;
+ filter_block.GetValue()->NewIndexIterator(
+ comparator, comparator->user_comparator(), &biter, kNullStats,
+ true /* total_order_seek */, false /* have_first_key */,
+ index_key_includes_seq(), index_value_is_full());
+ // Index partitions are assumed to be consecuitive. Prefetch them all.
+ // Read the first block offset
+ biter.SeekToFirst();
+ BlockHandle handle = biter.value().handle;
+ uint64_t prefetch_off = handle.offset();
+
+ // Read the last block's offset
+ biter.SeekToLast();
+ handle = biter.value().handle;
+ uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize;
+ uint64_t prefetch_len = last_off - prefetch_off;
+ std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
+
+ prefetch_buffer.reset(new FilePrefetchBuffer());
+ s = prefetch_buffer->Prefetch(rep->file.get(), prefetch_off,
+ static_cast<size_t>(prefetch_len));
+
+ // After prefetch, read the partitions one by one
+ ReadOptions read_options;
+ for (biter.SeekToFirst(); biter.Valid(); biter.Next()) {
+ handle = biter.value().handle;
+
+ CachableEntry<ParsedFullFilterBlock> block;
+ // TODO: Support counter batch update for partitioned index and
+ // filter blocks
+ s = table()->MaybeReadBlockAndLoadToCache(
+ prefetch_buffer.get(), read_options, handle,
+ UncompressionDict::GetEmptyDict(), &block, BlockType::kFilter,
+ nullptr /* get_context */, &lookup_context, nullptr /* contents */);
+
+ assert(s.ok() || block.GetValue() == nullptr);
+ if (s.ok() && block.GetValue() != nullptr) {
+ if (block.IsCached()) {
+ if (pin) {
+ filter_map_[handle.offset()] = std::move(block);
+ }
+ }
+ }
+ }
+}
+
+const InternalKeyComparator* PartitionedFilterBlockReader::internal_comparator()
+ const {
+ assert(table());
+ assert(table()->get_rep());
+
+ return &table()->get_rep()->internal_comparator;
+}
+
+bool PartitionedFilterBlockReader::index_key_includes_seq() const {
+ assert(table());
+ assert(table()->get_rep());
+
+ return table()->get_rep()->index_key_includes_seq;
+}
+
+bool PartitionedFilterBlockReader::index_value_is_full() const {
+ assert(table());
+ assert(table()->get_rep());
+
+ return table()->get_rep()->index_value_is_full;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.h b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.h
new file mode 100644
index 00000000..314297ca
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <list>
+#include <string>
+#include <unordered_map>
+#include "db/dbformat.h"
+#include "index_builder.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "table/block_based/block.h"
+#include "table/block_based/filter_block_reader_common.h"
+#include "table/block_based/full_filter_block.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder {
+ public:
+ explicit PartitionedFilterBlockBuilder(
+ const SliceTransform* prefix_extractor, bool whole_key_filtering,
+ FilterBitsBuilder* filter_bits_builder, int index_block_restart_interval,
+ const bool use_value_delta_encoding,
+ PartitionedIndexBuilder* const p_index_builder,
+ const uint32_t partition_size);
+
+ virtual ~PartitionedFilterBlockBuilder();
+
+ void AddKey(const Slice& key) override;
+ void Add(const Slice& key) override;
+
+ virtual Slice Finish(const BlockHandle& last_partition_block_handle,
+ Status* status) override;
+
+ private:
+ // Filter data
+ BlockBuilder index_on_filter_block_builder_; // top-level index builder
+ BlockBuilder
+ index_on_filter_block_builder_without_seq_; // same for user keys
+ struct FilterEntry {
+ std::string key;
+ Slice filter;
+ };
+ std::list<FilterEntry> filters; // list of partitioned indexes and their keys
+ std::unique_ptr<IndexBuilder> value;
+ std::vector<std::unique_ptr<const char[]>> filter_gc;
+ bool finishing_filters =
+ false; // true if Finish is called once but not complete yet.
+ // The policy of when cut a filter block and Finish it
+ void MaybeCutAFilterBlock(const Slice* next_key);
+ // Currently we keep the same number of partitions for filters and indexes.
+ // This would allow for some potentioal optimizations in future. If such
+ // optimizations did not realize we can use different number of partitions and
+ // eliminate p_index_builder_
+ PartitionedIndexBuilder* const p_index_builder_;
+ // The desired number of keys per partition
+ uint32_t keys_per_partition_;
+ // The number of keys added to the last partition so far
+ uint32_t keys_added_to_partition_;
+ BlockHandle last_encoded_handle_;
+};
+
+class PartitionedFilterBlockReader : public FilterBlockReaderCommon<Block> {
+ public:
+ PartitionedFilterBlockReader(const BlockBasedTable* t,
+ CachableEntry<Block>&& filter_block);
+
+ static std::unique_ptr<FilterBlockReader> Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context);
+
+ bool IsBlockBased() override { return false; }
+ bool KeyMayMatch(const Slice& key, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+ bool PrefixMayMatch(const Slice& prefix,
+ const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context) override;
+
+ size_t ApproximateMemoryUsage() const override;
+
+ private:
+ BlockHandle GetFilterPartitionHandle(const CachableEntry<Block>& filter_block,
+ const Slice& entry) const;
+ Status GetFilterPartitionBlock(
+ FilePrefetchBuffer* prefetch_buffer, const BlockHandle& handle,
+ bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<ParsedFullFilterBlock>* filter_block) const;
+
+ using FilterFunction = bool (FullFilterBlockReader::*)(
+ const Slice& slice, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, const bool no_io,
+ const Slice* const const_ikey_ptr, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context);
+ bool MayMatch(const Slice& slice, const SliceTransform* prefix_extractor,
+ uint64_t block_offset, bool no_io, const Slice* const_ikey_ptr,
+ GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ FilterFunction filter_function) const;
+ void CacheDependencies(bool pin) override;
+
+ const InternalKeyComparator* internal_comparator() const;
+ bool index_key_includes_seq() const;
+ bool index_value_is_full() const;
+
+ protected:
+ std::unordered_map<uint64_t, CachableEntry<ParsedFullFilterBlock>>
+ filter_map_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block_test.cc b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block_test.cc
new file mode 100644
index 00000000..071bad9c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/partitioned_filter_block_test.cc
@@ -0,0 +1,424 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+
+#include "rocksdb/filter_policy.h"
+
+#include "table/block_based/block_based_table_reader.h"
+#include "table/block_based/partitioned_filter_block.h"
+#include "table/block_based/filter_policy_internal.h"
+
+#include "index_builder.h"
+#include "logging/logging.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+std::map<uint64_t, std::string> blooms;
+
+class MockedBlockBasedTable : public BlockBasedTable {
+ public:
+ MockedBlockBasedTable(Rep* rep, PartitionedIndexBuilder* pib)
+ : BlockBasedTable(rep, /*block_cache_tracer=*/nullptr) {
+ // Initialize what Open normally does as much as necessary for the test
+ rep->index_key_includes_seq = pib->seperator_is_key_plus_seq();
+ rep->index_value_is_full = !pib->get_use_value_delta_encoding();
+ }
+};
+
+class MyPartitionedFilterBlockReader : public PartitionedFilterBlockReader {
+ public:
+ MyPartitionedFilterBlockReader(BlockBasedTable* t,
+ CachableEntry<Block>&& filter_block)
+ : PartitionedFilterBlockReader(t, std::move(filter_block)) {
+ for (const auto& pair : blooms) {
+ const uint64_t offset = pair.first;
+ const std::string& bloom = pair.second;
+
+ assert(t);
+ assert(t->get_rep());
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(
+ t->get_rep()->table_options.filter_policy.get(),
+ BlockContents(Slice(bloom))),
+ nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+ filter_map_[offset] = std::move(block);
+ }
+ }
+};
+
+class PartitionedFilterBlockTest
+ : public testing::Test,
+ virtual public ::testing::WithParamInterface<uint32_t> {
+ public:
+ Options options_;
+ ImmutableCFOptions ioptions_;
+ EnvOptions env_options_;
+ BlockBasedTableOptions table_options_;
+ InternalKeyComparator icomp_;
+ std::unique_ptr<BlockBasedTable> table_;
+ std::shared_ptr<Cache> cache_;
+ int bits_per_key_;
+
+ PartitionedFilterBlockTest()
+ : ioptions_(options_),
+ env_options_(options_),
+ icomp_(options_.comparator),
+ bits_per_key_(10) {
+ table_options_.filter_policy.reset(
+ NewBloomFilterPolicy(bits_per_key_, false));
+ table_options_.format_version = GetParam();
+ table_options_.index_block_restart_interval = 3;
+ }
+
+ ~PartitionedFilterBlockTest() override {}
+
+ const std::string keys[4] = {"afoo", "bar", "box", "hello"};
+ const std::string missing_keys[2] = {"missing", "other"};
+
+ uint64_t MaxIndexSize() {
+ int num_keys = sizeof(keys) / sizeof(*keys);
+ uint64_t max_key_size = 0;
+ for (int i = 1; i < num_keys; i++) {
+ max_key_size = std::max(max_key_size, static_cast<uint64_t>(keys[i].size()));
+ }
+ uint64_t max_index_size = num_keys * (max_key_size + 8 /*handle*/);
+ return max_index_size;
+ }
+
+ uint64_t MaxFilterSize() {
+ int num_keys = sizeof(keys) / sizeof(*keys);
+ // General, rough over-approximation
+ return num_keys * bits_per_key_ + (CACHE_LINE_SIZE * 8 + /*metadata*/ 5);
+ }
+
+ uint64_t last_offset = 10;
+ BlockHandle Write(const Slice& slice) {
+ BlockHandle bh(last_offset + 1, slice.size());
+ blooms[bh.offset()] = slice.ToString();
+ last_offset += bh.size();
+ return bh;
+ }
+
+ PartitionedIndexBuilder* NewIndexBuilder() {
+ const bool kValueDeltaEncoded = true;
+ return PartitionedIndexBuilder::CreateIndexBuilder(
+ &icomp_, !kValueDeltaEncoded, table_options_);
+ }
+
+ PartitionedFilterBlockBuilder* NewBuilder(
+ PartitionedIndexBuilder* const p_index_builder,
+ const SliceTransform* prefix_extractor = nullptr) {
+ assert(table_options_.block_size_deviation <= 100);
+ auto partition_size = static_cast<uint32_t>(
+ ((table_options_.metadata_block_size *
+ (100 - table_options_.block_size_deviation)) +
+ 99) /
+ 100);
+ partition_size = std::max(partition_size, static_cast<uint32_t>(1));
+ const bool kValueDeltaEncoded = true;
+ return new PartitionedFilterBlockBuilder(
+ prefix_extractor, table_options_.whole_key_filtering,
+ BloomFilterPolicy::GetBuilderFromContext(
+ FilterBuildingContext(table_options_)),
+ table_options_.index_block_restart_interval, !kValueDeltaEncoded,
+ p_index_builder, partition_size);
+ }
+
+ PartitionedFilterBlockReader* NewReader(
+ PartitionedFilterBlockBuilder* builder, PartitionedIndexBuilder* pib) {
+ BlockHandle bh;
+ Status status;
+ Slice slice;
+ do {
+ slice = builder->Finish(bh, &status);
+ bh = Write(slice);
+ } while (status.IsIncomplete());
+
+ constexpr bool skip_filters = false;
+ constexpr int level = 0;
+ constexpr bool immortal_table = false;
+ table_.reset(new MockedBlockBasedTable(
+ new BlockBasedTable::Rep(ioptions_, env_options_, table_options_,
+ icomp_, skip_filters, level, immortal_table),
+ pib));
+ BlockContents contents(slice);
+ CachableEntry<Block> block(
+ new Block(std::move(contents), kDisableGlobalSequenceNumber,
+ 0 /* read_amp_bytes_per_bit */, nullptr),
+ nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */);
+ auto reader =
+ new MyPartitionedFilterBlockReader(table_.get(), std::move(block));
+ return reader;
+ }
+
+ void VerifyReader(PartitionedFilterBlockBuilder* builder,
+ PartitionedIndexBuilder* pib, bool empty = false,
+ const SliceTransform* prefix_extractor = nullptr) {
+ std::unique_ptr<PartitionedFilterBlockReader> reader(
+ NewReader(builder, pib));
+ // Querying added keys
+ const bool no_io = true;
+ for (auto key : keys) {
+ auto ikey = InternalKey(key, 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ ASSERT_TRUE(reader->KeyMayMatch(key, prefix_extractor, kNotValid, !no_io,
+ &ikey_slice, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ }
+ {
+ // querying a key twice
+ auto ikey = InternalKey(keys[0], 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ ASSERT_TRUE(reader->KeyMayMatch(
+ keys[0], prefix_extractor, kNotValid, !no_io, &ikey_slice,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr));
+ }
+ // querying missing keys
+ for (auto key : missing_keys) {
+ auto ikey = InternalKey(key, 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ if (empty) {
+ ASSERT_TRUE(reader->KeyMayMatch(
+ key, prefix_extractor, kNotValid, !no_io, &ikey_slice,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr));
+ } else {
+ // assuming a good hash function
+ ASSERT_FALSE(reader->KeyMayMatch(
+ key, prefix_extractor, kNotValid, !no_io, &ikey_slice,
+ /*get_context=*/nullptr, /*lookup_context=*/nullptr));
+ }
+ }
+ }
+
+ int TestBlockPerKey() {
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(
+ NewBuilder(pib.get()));
+ int i = 0;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i], keys[i + 1]);
+ i++;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i], keys[i + 1]);
+ i++;
+ builder->Add(keys[i]);
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i], keys[i + 1]);
+ i++;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i]);
+
+ VerifyReader(builder.get(), pib.get());
+ return CountNumOfIndexPartitions(pib.get());
+ }
+
+ void TestBlockPerTwoKeys(const SliceTransform* prefix_extractor = nullptr) {
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(
+ NewBuilder(pib.get(), prefix_extractor));
+ int i = 0;
+ builder->Add(keys[i]);
+ i++;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i], keys[i + 1]);
+ i++;
+ builder->Add(keys[i]);
+ builder->Add(keys[i]);
+ i++;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i]);
+
+ VerifyReader(builder.get(), pib.get(), prefix_extractor);
+ }
+
+ void TestBlockPerAllKeys() {
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(
+ NewBuilder(pib.get()));
+ int i = 0;
+ builder->Add(keys[i]);
+ i++;
+ builder->Add(keys[i]);
+ i++;
+ builder->Add(keys[i]);
+ builder->Add(keys[i]);
+ i++;
+ builder->Add(keys[i]);
+ CutABlock(pib.get(), keys[i]);
+
+ VerifyReader(builder.get(), pib.get());
+ }
+
+ void CutABlock(PartitionedIndexBuilder* builder,
+ const std::string& user_key) {
+ // Assuming a block is cut, add an entry to the index
+ std::string key =
+ std::string(*InternalKey(user_key, 0, ValueType::kTypeValue).rep());
+ BlockHandle dont_care_block_handle(1, 1);
+ builder->AddIndexEntry(&key, nullptr, dont_care_block_handle);
+ }
+
+ void CutABlock(PartitionedIndexBuilder* builder, const std::string& user_key,
+ const std::string& next_user_key) {
+ // Assuming a block is cut, add an entry to the index
+ std::string key =
+ std::string(*InternalKey(user_key, 0, ValueType::kTypeValue).rep());
+ std::string next_key = std::string(
+ *InternalKey(next_user_key, 0, ValueType::kTypeValue).rep());
+ BlockHandle dont_care_block_handle(1, 1);
+ Slice slice = Slice(next_key.data(), next_key.size());
+ builder->AddIndexEntry(&key, &slice, dont_care_block_handle);
+ }
+
+ int CountNumOfIndexPartitions(PartitionedIndexBuilder* builder) {
+ IndexBuilder::IndexBlocks dont_care_ib;
+ BlockHandle dont_care_bh(10, 10);
+ Status s;
+ int cnt = 0;
+ do {
+ s = builder->Finish(&dont_care_ib, dont_care_bh);
+ cnt++;
+ } while (s.IsIncomplete());
+ return cnt - 1; // 1 is 2nd level index
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(FormatDef, PartitionedFilterBlockTest,
+ testing::Values(test::kDefaultFormatVersion));
+INSTANTIATE_TEST_CASE_P(FormatLatest, PartitionedFilterBlockTest,
+ testing::Values(test::kLatestFormatVersion));
+
+TEST_P(PartitionedFilterBlockTest, EmptyBuilder) {
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(NewBuilder(pib.get()));
+ const bool empty = true;
+ VerifyReader(builder.get(), pib.get(), empty);
+}
+
+TEST_P(PartitionedFilterBlockTest, OneBlock) {
+ uint64_t max_index_size = MaxIndexSize();
+ for (uint64_t i = 1; i < max_index_size + 1; i++) {
+ table_options_.metadata_block_size = i;
+ TestBlockPerAllKeys();
+ }
+}
+
+TEST_P(PartitionedFilterBlockTest, TwoBlocksPerKey) {
+ uint64_t max_index_size = MaxIndexSize();
+ for (uint64_t i = 1; i < max_index_size + 1; i++) {
+ table_options_.metadata_block_size = i;
+ TestBlockPerTwoKeys();
+ }
+}
+
+// This reproduces the bug that a prefix is the same among multiple consecutive
+// blocks but the bug would add it only to the first block.
+TEST_P(PartitionedFilterBlockTest, SamePrefixInMultipleBlocks) {
+ // some small number to cause partition cuts
+ table_options_.metadata_block_size = 1;
+ std::unique_ptr<const SliceTransform> prefix_extractor(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(1));
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(
+ NewBuilder(pib.get(), prefix_extractor.get()));
+ const std::string pkeys[3] = {"p-key10", "p-key20", "p-key30"};
+ builder->Add(pkeys[0]);
+ CutABlock(pib.get(), pkeys[0], pkeys[1]);
+ builder->Add(pkeys[1]);
+ CutABlock(pib.get(), pkeys[1], pkeys[2]);
+ builder->Add(pkeys[2]);
+ CutABlock(pib.get(), pkeys[2]);
+ std::unique_ptr<PartitionedFilterBlockReader> reader(
+ NewReader(builder.get(), pib.get()));
+ for (auto key : pkeys) {
+ auto ikey = InternalKey(key, 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ ASSERT_TRUE(reader->PrefixMayMatch(
+ prefix_extractor->Transform(key), prefix_extractor.get(), kNotValid,
+ /*no_io=*/false, &ikey_slice, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ }
+ // Non-existent keys but with the same prefix
+ const std::string pnonkeys[4] = {"p-key9", "p-key11", "p-key21", "p-key31"};
+ for (auto key : pnonkeys) {
+ auto ikey = InternalKey(key, 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ ASSERT_TRUE(reader->PrefixMayMatch(
+ prefix_extractor->Transform(key), prefix_extractor.get(), kNotValid,
+ /*no_io=*/false, &ikey_slice, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ }
+}
+
+// This reproduces the bug in format_version=3 that the seeking the prefix will
+// lead us to the partition before the one that has filter for the prefix.
+TEST_P(PartitionedFilterBlockTest, PrefixInWrongPartitionBug) {
+ // some small number to cause partition cuts
+ table_options_.metadata_block_size = 1;
+ std::unique_ptr<const SliceTransform> prefix_extractor(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(2));
+ std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
+ std::unique_ptr<PartitionedFilterBlockBuilder> builder(
+ NewBuilder(pib.get(), prefix_extractor.get()));
+ // In the bug, searching for prefix "p3" on an index with format version 3,
+ // will give the key "p3" and the partition of the keys that are <= p3, i.e.,
+ // p2-keys, where the filter for prefix "p3" does not exist.
+ const std::string pkeys[] = {"p1-key1", "p2-key2", "p3-key3", "p4-key3",
+ "p5-key3"};
+ builder->Add(pkeys[0]);
+ CutABlock(pib.get(), pkeys[0], pkeys[1]);
+ builder->Add(pkeys[1]);
+ CutABlock(pib.get(), pkeys[1], pkeys[2]);
+ builder->Add(pkeys[2]);
+ CutABlock(pib.get(), pkeys[2], pkeys[3]);
+ builder->Add(pkeys[3]);
+ CutABlock(pib.get(), pkeys[3], pkeys[4]);
+ builder->Add(pkeys[4]);
+ CutABlock(pib.get(), pkeys[4]);
+ std::unique_ptr<PartitionedFilterBlockReader> reader(
+ NewReader(builder.get(), pib.get()));
+ for (auto key : pkeys) {
+ auto prefix = prefix_extractor->Transform(key);
+ auto ikey = InternalKey(prefix, 0, ValueType::kTypeValue);
+ const Slice ikey_slice = Slice(*ikey.rep());
+ ASSERT_TRUE(reader->PrefixMayMatch(
+ prefix, prefix_extractor.get(), kNotValid,
+ /*no_io=*/false, &ikey_slice, /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr));
+ }
+}
+
+TEST_P(PartitionedFilterBlockTest, OneBlockPerKey) {
+ uint64_t max_index_size = MaxIndexSize();
+ for (uint64_t i = 1; i < max_index_size + 1; i++) {
+ table_options_.metadata_block_size = i;
+ TestBlockPerKey();
+ }
+}
+
+TEST_P(PartitionedFilterBlockTest, PartitionCount) {
+ int num_keys = sizeof(keys) / sizeof(*keys);
+ table_options_.metadata_block_size =
+ std::max(MaxIndexSize(), MaxFilterSize());
+ int partitions = TestBlockPerKey();
+ ASSERT_EQ(partitions, 1);
+ // A low number ensures cutting a block after each key
+ table_options_.metadata_block_size = 1;
+ partitions = TestBlockPerKey();
+ ASSERT_EQ(partitions, num_keys - 1 /* last two keys make one flush */);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.cc b/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.cc
new file mode 100644
index 00000000..78e2b93c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_based/uncompression_dict_reader.h"
+#include "monitoring/perf_context_imp.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "util/compression.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status UncompressionDictReader::Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<UncompressionDictReader>* uncompression_dict_reader) {
+ assert(table);
+ assert(table->get_rep());
+ assert(!pin || prefetch);
+ assert(uncompression_dict_reader);
+
+ CachableEntry<UncompressionDict> uncompression_dict;
+ if (prefetch || !use_cache) {
+ const Status s = ReadUncompressionDictionary(
+ table, prefetch_buffer, ReadOptions(), use_cache,
+ nullptr /* get_context */, lookup_context, &uncompression_dict);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (use_cache && !pin) {
+ uncompression_dict.Reset();
+ }
+ }
+
+ uncompression_dict_reader->reset(
+ new UncompressionDictReader(table, std::move(uncompression_dict)));
+
+ return Status::OK();
+}
+
+Status UncompressionDictReader::ReadUncompressionDictionary(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<UncompressionDict>* uncompression_dict) {
+ // TODO: add perf counter for compression dictionary read time
+
+ assert(table);
+ assert(uncompression_dict);
+ assert(uncompression_dict->IsEmpty());
+
+ const BlockBasedTable::Rep* const rep = table->get_rep();
+ assert(rep);
+ assert(!rep->compression_dict_handle.IsNull());
+
+ const Status s = table->RetrieveBlock(
+ prefetch_buffer, read_options, rep->compression_dict_handle,
+ UncompressionDict::GetEmptyDict(), uncompression_dict,
+ BlockType::kCompressionDictionary, get_context, lookup_context,
+ /* for_compaction */ false, use_cache);
+
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(
+ rep->ioptions.info_log,
+ "Encountered error while reading data from compression dictionary "
+ "block %s",
+ s.ToString().c_str());
+ }
+
+ return s;
+}
+
+Status UncompressionDictReader::GetOrReadUncompressionDictionary(
+ FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<UncompressionDict>* uncompression_dict) const {
+ assert(uncompression_dict);
+
+ if (!uncompression_dict_.IsEmpty()) {
+ uncompression_dict->SetUnownedValue(uncompression_dict_.GetValue());
+ return Status::OK();
+ }
+
+ ReadOptions read_options;
+ if (no_io) {
+ read_options.read_tier = kBlockCacheTier;
+ }
+
+ return ReadUncompressionDictionary(table_, prefetch_buffer, read_options,
+ cache_dictionary_blocks(), get_context,
+ lookup_context, uncompression_dict);
+}
+
+size_t UncompressionDictReader::ApproximateMemoryUsage() const {
+ assert(!uncompression_dict_.GetOwnValue() ||
+ uncompression_dict_.GetValue() != nullptr);
+ size_t usage = uncompression_dict_.GetOwnValue()
+ ? uncompression_dict_.GetValue()->ApproximateMemoryUsage()
+ : 0;
+
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ usage += malloc_usable_size(const_cast<UncompressionDictReader*>(this));
+#else
+ usage += sizeof(*this);
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+
+ return usage;
+}
+
+bool UncompressionDictReader::cache_dictionary_blocks() const {
+ assert(table_);
+ assert(table_->get_rep());
+
+ return table_->get_rep()->table_options.cache_index_and_filter_blocks;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.h b/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.h
new file mode 100644
index 00000000..3e782617
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_based/uncompression_dict_reader.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cassert>
+#include "table/block_based/cachable_entry.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBasedTable;
+struct BlockCacheLookupContext;
+class FilePrefetchBuffer;
+class GetContext;
+struct ReadOptions;
+struct UncompressionDict;
+
+// Provides access to the uncompression dictionary regardless of whether
+// it is owned by the reader or stored in the cache, or whether it is pinned
+// in the cache or not.
+class UncompressionDictReader {
+ public:
+ static Status Create(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ bool use_cache, bool prefetch, bool pin,
+ BlockCacheLookupContext* lookup_context,
+ std::unique_ptr<UncompressionDictReader>* uncompression_dict_reader);
+
+ Status GetOrReadUncompressionDictionary(
+ FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<UncompressionDict>* uncompression_dict) const;
+
+ size_t ApproximateMemoryUsage() const;
+
+ private:
+ UncompressionDictReader(const BlockBasedTable* t,
+ CachableEntry<UncompressionDict>&& uncompression_dict)
+ : table_(t), uncompression_dict_(std::move(uncompression_dict)) {
+ assert(table_);
+ }
+
+ bool cache_dictionary_blocks() const;
+
+ static Status ReadUncompressionDictionary(
+ const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
+ const ReadOptions& read_options, bool use_cache, GetContext* get_context,
+ BlockCacheLookupContext* lookup_context,
+ CachableEntry<UncompressionDict>* uncompression_dict);
+
+ const BlockBasedTable* table_;
+ CachableEntry<UncompressionDict> uncompression_dict_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_fetcher.cc b/storage/rocksdb/rocksdb/table/block_fetcher.cc
new file mode 100644
index 00000000..9ddee72c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_fetcher.cc
@@ -0,0 +1,284 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/block_fetcher.h"
+
+#include <cinttypes>
+#include <string>
+
+#include "logging/logging.h"
+#include "memory/memory_allocator.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/env.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/format.h"
+#include "table/persistent_cache_helper.h"
+#include "util/coding.h"
+#include "util/compression.h"
+#include "util/crc32c.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+#include "util/xxhash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+inline void BlockFetcher::CheckBlockChecksum() {
+ // Check the crc of the type and the block contents
+ if (read_options_.verify_checksums) {
+ const char* data = slice_.data(); // Pointer to where Read put the data
+ PERF_TIMER_GUARD(block_checksum_time);
+ uint32_t value = DecodeFixed32(data + block_size_ + 1);
+ uint32_t actual = 0;
+ switch (footer_.checksum()) {
+ case kNoChecksum:
+ break;
+ case kCRC32c:
+ value = crc32c::Unmask(value);
+ actual = crc32c::Value(data, block_size_ + 1);
+ break;
+ case kxxHash:
+ actual = XXH32(data, static_cast<int>(block_size_) + 1, 0);
+ break;
+ case kxxHash64:
+ actual = static_cast<uint32_t>(
+ XXH64(data, static_cast<int>(block_size_) + 1, 0) &
+ uint64_t{0xffffffff});
+ break;
+ default:
+ status_ = Status::Corruption(
+ "unknown checksum type " + ToString(footer_.checksum()) + " in " +
+ file_->file_name() + " offset " + ToString(handle_.offset()) +
+ " size " + ToString(block_size_));
+ }
+ if (status_.ok() && actual != value) {
+ status_ = Status::Corruption(
+ "block checksum mismatch: expected " + ToString(actual) + ", got " +
+ ToString(value) + " in " + file_->file_name() + " offset " +
+ ToString(handle_.offset()) + " size " + ToString(block_size_));
+ }
+ }
+}
+
+inline bool BlockFetcher::TryGetUncompressBlockFromPersistentCache() {
+ if (cache_options_.persistent_cache &&
+ !cache_options_.persistent_cache->IsCompressed()) {
+ Status status = PersistentCacheHelper::LookupUncompressedPage(
+ cache_options_, handle_, contents_);
+ if (status.ok()) {
+ // uncompressed page is found for the block handle
+ return true;
+ } else {
+ // uncompressed page is not found
+ if (ioptions_.info_log && !status.IsNotFound()) {
+ assert(!status.ok());
+ ROCKS_LOG_INFO(ioptions_.info_log,
+ "Error reading from persistent cache. %s",
+ status.ToString().c_str());
+ }
+ }
+ }
+ return false;
+}
+
+inline bool BlockFetcher::TryGetFromPrefetchBuffer() {
+ if (prefetch_buffer_ != nullptr &&
+ prefetch_buffer_->TryReadFromCache(
+ handle_.offset(),
+ static_cast<size_t>(handle_.size()) + kBlockTrailerSize, &slice_,
+ for_compaction_)) {
+ block_size_ = static_cast<size_t>(handle_.size());
+ CheckBlockChecksum();
+ if (!status_.ok()) {
+ return true;
+ }
+ got_from_prefetch_buffer_ = true;
+ used_buf_ = const_cast<char*>(slice_.data());
+ }
+ return got_from_prefetch_buffer_;
+}
+
+inline bool BlockFetcher::TryGetCompressedBlockFromPersistentCache() {
+ if (cache_options_.persistent_cache &&
+ cache_options_.persistent_cache->IsCompressed()) {
+ // lookup uncompressed cache mode p-cache
+ std::unique_ptr<char[]> raw_data;
+ status_ = PersistentCacheHelper::LookupRawPage(
+ cache_options_, handle_, &raw_data, block_size_ + kBlockTrailerSize);
+ if (status_.ok()) {
+ heap_buf_ = CacheAllocationPtr(raw_data.release());
+ used_buf_ = heap_buf_.get();
+ slice_ = Slice(heap_buf_.get(), block_size_);
+ return true;
+ } else if (!status_.IsNotFound() && ioptions_.info_log) {
+ assert(!status_.ok());
+ ROCKS_LOG_INFO(ioptions_.info_log,
+ "Error reading from persistent cache. %s",
+ status_.ToString().c_str());
+ }
+ }
+ return false;
+}
+
+inline void BlockFetcher::PrepareBufferForBlockFromFile() {
+ // cache miss read from device
+ if (do_uncompress_ &&
+ block_size_ + kBlockTrailerSize < kDefaultStackBufferSize) {
+ // If we've got a small enough hunk of data, read it in to the
+ // trivially allocated stack buffer instead of needing a full malloc()
+ used_buf_ = &stack_buf_[0];
+ } else if (maybe_compressed_ && !do_uncompress_) {
+ compressed_buf_ = AllocateBlock(block_size_ + kBlockTrailerSize,
+ memory_allocator_compressed_);
+ used_buf_ = compressed_buf_.get();
+ } else {
+ heap_buf_ =
+ AllocateBlock(block_size_ + kBlockTrailerSize, memory_allocator_);
+ used_buf_ = heap_buf_.get();
+ }
+}
+
+inline void BlockFetcher::InsertCompressedBlockToPersistentCacheIfNeeded() {
+ if (status_.ok() && read_options_.fill_cache &&
+ cache_options_.persistent_cache &&
+ cache_options_.persistent_cache->IsCompressed()) {
+ // insert to raw cache
+ PersistentCacheHelper::InsertRawPage(cache_options_, handle_, used_buf_,
+ block_size_ + kBlockTrailerSize);
+ }
+}
+
+inline void BlockFetcher::InsertUncompressedBlockToPersistentCacheIfNeeded() {
+ if (status_.ok() && !got_from_prefetch_buffer_ && read_options_.fill_cache &&
+ cache_options_.persistent_cache &&
+ !cache_options_.persistent_cache->IsCompressed()) {
+ // insert to uncompressed cache
+ PersistentCacheHelper::InsertUncompressedPage(cache_options_, handle_,
+ *contents_);
+ }
+}
+
+inline void BlockFetcher::CopyBufferToHeap() {
+ assert(used_buf_ != heap_buf_.get());
+ heap_buf_ = AllocateBlock(block_size_ + kBlockTrailerSize, memory_allocator_);
+ memcpy(heap_buf_.get(), used_buf_, block_size_ + kBlockTrailerSize);
+}
+
+inline void BlockFetcher::GetBlockContents() {
+ if (slice_.data() != used_buf_) {
+ // the slice content is not the buffer provided
+ *contents_ = BlockContents(Slice(slice_.data(), block_size_));
+ } else {
+ // page can be either uncompressed or compressed, the buffer either stack
+ // or heap provided. Refer to https://github.com/facebook/rocksdb/pull/4096
+ if (got_from_prefetch_buffer_ || used_buf_ == &stack_buf_[0]) {
+ CopyBufferToHeap();
+ } else if (used_buf_ == compressed_buf_.get()) {
+ if (compression_type_ == kNoCompression &&
+ memory_allocator_ != memory_allocator_compressed_) {
+ CopyBufferToHeap();
+ } else {
+ heap_buf_ = std::move(compressed_buf_);
+ }
+ }
+ *contents_ = BlockContents(std::move(heap_buf_), block_size_);
+ }
+#ifndef NDEBUG
+ contents_->is_raw_block = true;
+#endif
+}
+
+Status BlockFetcher::ReadBlockContents() {
+ block_size_ = static_cast<size_t>(handle_.size());
+
+ if (TryGetUncompressBlockFromPersistentCache()) {
+ compression_type_ = kNoCompression;
+#ifndef NDEBUG
+ contents_->is_raw_block = true;
+#endif // NDEBUG
+ return Status::OK();
+ }
+ if (TryGetFromPrefetchBuffer()) {
+ if (!status_.ok()) {
+ return status_;
+ }
+ } else if (!TryGetCompressedBlockFromPersistentCache()) {
+ PrepareBufferForBlockFromFile();
+ Status s;
+
+ {
+ PERF_TIMER_GUARD(block_read_time);
+ // Actual file read
+ status_ = file_->Read(handle_.offset(), block_size_ + kBlockTrailerSize,
+ &slice_, used_buf_, for_compaction_);
+ }
+ PERF_COUNTER_ADD(block_read_count, 1);
+
+ // TODO: introduce dedicated perf counter for range tombstones
+ switch (block_type_) {
+ case BlockType::kFilter:
+ PERF_COUNTER_ADD(filter_block_read_count, 1);
+ break;
+
+ case BlockType::kCompressionDictionary:
+ PERF_COUNTER_ADD(compression_dict_block_read_count, 1);
+ break;
+
+ case BlockType::kIndex:
+ PERF_COUNTER_ADD(index_block_read_count, 1);
+ break;
+
+ // Nothing to do here as we don't have counters for the other types.
+ default:
+ break;
+ }
+
+ PERF_COUNTER_ADD(block_read_byte, block_size_ + kBlockTrailerSize);
+ if (!status_.ok()) {
+ return status_;
+ }
+
+ if (slice_.size() != block_size_ + kBlockTrailerSize) {
+ return Status::Corruption("truncated block read from " +
+ file_->file_name() + " offset " +
+ ToString(handle_.offset()) + ", expected " +
+ ToString(block_size_ + kBlockTrailerSize) +
+ " bytes, got " + ToString(slice_.size()));
+ }
+
+ CheckBlockChecksum();
+ if (status_.ok()) {
+ InsertCompressedBlockToPersistentCacheIfNeeded();
+ } else {
+ return status_;
+ }
+ }
+
+ PERF_TIMER_GUARD(block_decompress_time);
+
+ compression_type_ = get_block_compression_type(slice_.data(), block_size_);
+
+ if (do_uncompress_ && compression_type_ != kNoCompression) {
+ // compressed page, uncompress, update cache
+ UncompressionContext context(compression_type_);
+ UncompressionInfo info(context, uncompression_dict_, compression_type_);
+ status_ = UncompressBlockContents(info, slice_.data(), block_size_,
+ contents_, footer_.version(), ioptions_,
+ memory_allocator_);
+ compression_type_ = kNoCompression;
+ } else {
+ GetBlockContents();
+ }
+
+ InsertUncompressedBlockToPersistentCacheIfNeeded();
+
+ return status_;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/block_fetcher.h b/storage/rocksdb/rocksdb/table/block_fetcher.h
new file mode 100644
index 00000000..1b003df1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/block_fetcher.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "memory/memory_allocator.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_type.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Retrieves a single block of a given file. Utilizes the prefetch buffer and/or
+// persistent cache provided (if any) to try to avoid reading from the file
+// directly. Note that both the prefetch buffer and the persistent cache are
+// optional; also, note that the persistent cache may be configured to store either
+// compressed or uncompressed blocks.
+//
+// If the retrieved block is compressed and the do_uncompress flag is set,
+// BlockFetcher uncompresses the block (using the uncompression dictionary,
+// if provided, to prime the compression algorithm), and returns the resulting
+// uncompressed block data. Otherwise, it returns the original block.
+//
+// Two read options affect the behavior of BlockFetcher: if verify_checksums is
+// true, the checksum of the (original) block is checked; if fill_cache is true,
+// the block is added to the persistent cache if needed.
+//
+// Memory for uncompressed and compressed blocks is allocated as needed
+// using memory_allocator and memory_allocator_compressed, respectively
+// (if provided; otherwise, the default allocator is used).
+
+class BlockFetcher {
+ public:
+ BlockFetcher(RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer, const Footer& footer,
+ const ReadOptions& read_options, const BlockHandle& handle,
+ BlockContents* contents, const ImmutableCFOptions& ioptions,
+ bool do_uncompress, bool maybe_compressed, BlockType block_type,
+ const UncompressionDict& uncompression_dict,
+ const PersistentCacheOptions& cache_options,
+ MemoryAllocator* memory_allocator = nullptr,
+ MemoryAllocator* memory_allocator_compressed = nullptr,
+ bool for_compaction = false)
+ : file_(file),
+ prefetch_buffer_(prefetch_buffer),
+ footer_(footer),
+ read_options_(read_options),
+ handle_(handle),
+ contents_(contents),
+ ioptions_(ioptions),
+ do_uncompress_(do_uncompress),
+ maybe_compressed_(maybe_compressed),
+ block_type_(block_type),
+ uncompression_dict_(uncompression_dict),
+ cache_options_(cache_options),
+ memory_allocator_(memory_allocator),
+ memory_allocator_compressed_(memory_allocator_compressed),
+ for_compaction_(for_compaction) {}
+
+ Status ReadBlockContents();
+ CompressionType get_compression_type() const { return compression_type_; }
+
+ private:
+ static const uint32_t kDefaultStackBufferSize = 5000;
+
+ RandomAccessFileReader* file_;
+ FilePrefetchBuffer* prefetch_buffer_;
+ const Footer& footer_;
+ const ReadOptions read_options_;
+ const BlockHandle& handle_;
+ BlockContents* contents_;
+ const ImmutableCFOptions& ioptions_;
+ bool do_uncompress_;
+ bool maybe_compressed_;
+ BlockType block_type_;
+ const UncompressionDict& uncompression_dict_;
+ const PersistentCacheOptions& cache_options_;
+ MemoryAllocator* memory_allocator_;
+ MemoryAllocator* memory_allocator_compressed_;
+ Status status_;
+ Slice slice_;
+ char* used_buf_ = nullptr;
+ size_t block_size_;
+ CacheAllocationPtr heap_buf_;
+ CacheAllocationPtr compressed_buf_;
+ char stack_buf_[kDefaultStackBufferSize];
+ bool got_from_prefetch_buffer_ = false;
+ ROCKSDB_NAMESPACE::CompressionType compression_type_;
+ bool for_compaction_ = false;
+
+ // return true if found
+ bool TryGetUncompressBlockFromPersistentCache();
+ // return true if found
+ bool TryGetFromPrefetchBuffer();
+ bool TryGetCompressedBlockFromPersistentCache();
+ void PrepareBufferForBlockFromFile();
+ // Copy content from used_buf_ to new heap buffer.
+ void CopyBufferToHeap();
+ void GetBlockContents();
+ void InsertCompressedBlockToPersistentCacheIfNeeded();
+ void InsertUncompressedBlockToPersistentCacheIfNeeded();
+ void CheckBlockChecksum();
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/cleanable_test.cc b/storage/rocksdb/rocksdb/table/cleanable_test.cc
new file mode 100644
index 00000000..f7d80a39
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cleanable_test.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+
+#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<int*>(arg1);
+ int* num = reinterpret_cast<int*>(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<const std::string*>(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);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.cc b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.cc
new file mode 100644
index 00000000..3ddce0b6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.cc
@@ -0,0 +1,528 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "table/cuckoo/cuckoo_table_builder.h"
+
+#include <assert.h>
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/env.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_builder.h"
+#include "table/cuckoo/cuckoo_table_factory.h"
+#include "table/format.h"
+#include "table/meta_blocks.h"
+#include "util/autovector.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+const std::string CuckooTablePropertyNames::kEmptyKey =
+ "rocksdb.cuckoo.bucket.empty.key";
+const std::string CuckooTablePropertyNames::kNumHashFunc =
+ "rocksdb.cuckoo.hash.num";
+const std::string CuckooTablePropertyNames::kHashTableSize =
+ "rocksdb.cuckoo.hash.size";
+const std::string CuckooTablePropertyNames::kValueLength =
+ "rocksdb.cuckoo.value.length";
+const std::string CuckooTablePropertyNames::kIsLastLevel =
+ "rocksdb.cuckoo.file.islastlevel";
+const std::string CuckooTablePropertyNames::kCuckooBlockSize =
+ "rocksdb.cuckoo.hash.cuckooblocksize";
+const std::string CuckooTablePropertyNames::kIdentityAsFirstHash =
+ "rocksdb.cuckoo.hash.identityfirst";
+const std::string CuckooTablePropertyNames::kUseModuleHash =
+ "rocksdb.cuckoo.hash.usemodule";
+const std::string CuckooTablePropertyNames::kUserKeyLength =
+ "rocksdb.cuckoo.hash.userkeylength";
+
+// Obtained by running echo rocksdb.table.cuckoo | sha1sum
+extern const uint64_t kCuckooTableMagicNumber = 0x926789d0c5f17873ull;
+
+CuckooTableBuilder::CuckooTableBuilder(
+ WritableFileWriter* file, double max_hash_table_ratio,
+ uint32_t max_num_hash_table, uint32_t max_search_depth,
+ const Comparator* user_comparator, uint32_t cuckoo_block_size,
+ bool use_module_hash, bool identity_as_first_hash,
+ uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t),
+ uint32_t column_family_id, const std::string& column_family_name)
+ : num_hash_func_(2),
+ file_(file),
+ max_hash_table_ratio_(max_hash_table_ratio),
+ max_num_hash_func_(max_num_hash_table),
+ max_search_depth_(max_search_depth),
+ cuckoo_block_size_(std::max(1U, cuckoo_block_size)),
+ hash_table_size_(use_module_hash ? 0 : 2),
+ is_last_level_file_(false),
+ has_seen_first_key_(false),
+ has_seen_first_value_(false),
+ key_size_(0),
+ value_size_(0),
+ num_entries_(0),
+ num_values_(0),
+ ucomp_(user_comparator),
+ use_module_hash_(use_module_hash),
+ identity_as_first_hash_(identity_as_first_hash),
+ get_slice_hash_(get_slice_hash),
+ closed_(false) {
+ // Data is in a huge block.
+ properties_.num_data_blocks = 1;
+ properties_.index_size = 0;
+ properties_.filter_size = 0;
+ properties_.column_family_id = column_family_id;
+ properties_.column_family_name = column_family_name;
+}
+
+void CuckooTableBuilder::Add(const Slice& key, const Slice& value) {
+ if (num_entries_ >= kMaxVectorIdx - 1) {
+ status_ = Status::NotSupported("Number of keys in a file must be < 2^32-1");
+ return;
+ }
+ ParsedInternalKey ikey;
+ if (!ParseInternalKey(key, &ikey)) {
+ status_ = Status::Corruption("Unable to parse key into inernal key.");
+ return;
+ }
+ if (ikey.type != kTypeDeletion && ikey.type != kTypeValue) {
+ status_ = Status::NotSupported("Unsupported key type " +
+ ToString(ikey.type));
+ return;
+ }
+
+ // Determine if we can ignore the sequence number and value type from
+ // internal keys by looking at sequence number from first key. We assume
+ // that if first key has a zero sequence number, then all the remaining
+ // keys will have zero seq. no.
+ if (!has_seen_first_key_) {
+ is_last_level_file_ = ikey.sequence == 0;
+ has_seen_first_key_ = true;
+ smallest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
+ largest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
+ key_size_ = is_last_level_file_ ? ikey.user_key.size() : key.size();
+ }
+ if (key_size_ != (is_last_level_file_ ? ikey.user_key.size() : key.size())) {
+ status_ = Status::NotSupported("all keys have to be the same size");
+ return;
+ }
+
+ if (ikey.type == kTypeValue) {
+ if (!has_seen_first_value_) {
+ has_seen_first_value_ = true;
+ value_size_ = value.size();
+ }
+ if (value_size_ != value.size()) {
+ status_ = Status::NotSupported("all values have to be the same size");
+ return;
+ }
+
+ if (is_last_level_file_) {
+ kvs_.append(ikey.user_key.data(), ikey.user_key.size());
+ } else {
+ kvs_.append(key.data(), key.size());
+ }
+ kvs_.append(value.data(), value.size());
+ ++num_values_;
+ } else {
+ if (is_last_level_file_) {
+ deleted_keys_.append(ikey.user_key.data(), ikey.user_key.size());
+ } else {
+ deleted_keys_.append(key.data(), key.size());
+ }
+ }
+ ++num_entries_;
+
+ // In order to fill the empty buckets in the hash table, we identify a
+ // key which is not used so far (unused_user_key). We determine this by
+ // maintaining smallest and largest keys inserted so far in bytewise order
+ // and use them to find a key outside this range in Finish() operation.
+ // Note that this strategy is independent of user comparator used here.
+ if (ikey.user_key.compare(smallest_user_key_) < 0) {
+ smallest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
+ } else if (ikey.user_key.compare(largest_user_key_) > 0) {
+ largest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
+ }
+ if (!use_module_hash_) {
+ if (hash_table_size_ < num_entries_ / max_hash_table_ratio_) {
+ hash_table_size_ *= 2;
+ }
+ }
+}
+
+bool CuckooTableBuilder::IsDeletedKey(uint64_t idx) const {
+ assert(closed_);
+ return idx >= num_values_;
+}
+
+Slice CuckooTableBuilder::GetKey(uint64_t idx) const {
+ assert(closed_);
+ if (IsDeletedKey(idx)) {
+ return Slice(&deleted_keys_[static_cast<size_t>((idx - num_values_) * key_size_)], static_cast<size_t>(key_size_));
+ }
+ return Slice(&kvs_[static_cast<size_t>(idx * (key_size_ + value_size_))], static_cast<size_t>(key_size_));
+}
+
+Slice CuckooTableBuilder::GetUserKey(uint64_t idx) const {
+ assert(closed_);
+ return is_last_level_file_ ? GetKey(idx) : ExtractUserKey(GetKey(idx));
+}
+
+Slice CuckooTableBuilder::GetValue(uint64_t idx) const {
+ assert(closed_);
+ if (IsDeletedKey(idx)) {
+ static std::string empty_value(static_cast<unsigned int>(value_size_), 'a');
+ return Slice(empty_value);
+ }
+ return Slice(&kvs_[static_cast<size_t>(idx * (key_size_ + value_size_) + key_size_)], static_cast<size_t>(value_size_));
+}
+
+Status CuckooTableBuilder::MakeHashTable(std::vector<CuckooBucket>* buckets) {
+ buckets->resize(static_cast<size_t>(hash_table_size_ + cuckoo_block_size_ - 1));
+ uint32_t make_space_for_key_call_id = 0;
+ for (uint32_t vector_idx = 0; vector_idx < num_entries_; vector_idx++) {
+ uint64_t bucket_id = 0;
+ bool bucket_found = false;
+ autovector<uint64_t> hash_vals;
+ Slice user_key = GetUserKey(vector_idx);
+ for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_ && !bucket_found;
+ ++hash_cnt) {
+ uint64_t hash_val = CuckooHash(user_key, hash_cnt, use_module_hash_,
+ hash_table_size_, identity_as_first_hash_, get_slice_hash_);
+ // If there is a collision, check next cuckoo_block_size_ locations for
+ // empty locations. While checking, if we reach end of the hash table,
+ // stop searching and proceed for next hash function.
+ for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
+ ++block_idx, ++hash_val) {
+ if ((*buckets)[static_cast<size_t>(hash_val)].vector_idx == kMaxVectorIdx) {
+ bucket_id = hash_val;
+ bucket_found = true;
+ break;
+ } else {
+ if (ucomp_->Compare(user_key,
+ GetUserKey((*buckets)[static_cast<size_t>(hash_val)].vector_idx)) == 0) {
+ return Status::NotSupported("Same key is being inserted again.");
+ }
+ hash_vals.push_back(hash_val);
+ }
+ }
+ }
+ while (!bucket_found && !MakeSpaceForKey(hash_vals,
+ ++make_space_for_key_call_id, buckets, &bucket_id)) {
+ // Rehash by increashing number of hash tables.
+ if (num_hash_func_ >= max_num_hash_func_) {
+ return Status::NotSupported("Too many collisions. Unable to hash.");
+ }
+ // We don't really need to rehash the entire table because old hashes are
+ // still valid and we only increased the number of hash functions.
+ uint64_t hash_val = CuckooHash(user_key, num_hash_func_, use_module_hash_,
+ hash_table_size_, identity_as_first_hash_, get_slice_hash_);
+ ++num_hash_func_;
+ for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
+ ++block_idx, ++hash_val) {
+ if ((*buckets)[static_cast<size_t>(hash_val)].vector_idx == kMaxVectorIdx) {
+ bucket_found = true;
+ bucket_id = hash_val;
+ break;
+ } else {
+ hash_vals.push_back(hash_val);
+ }
+ }
+ }
+ (*buckets)[static_cast<size_t>(bucket_id)].vector_idx = vector_idx;
+ }
+ return Status::OK();
+}
+
+Status CuckooTableBuilder::Finish() {
+ assert(!closed_);
+ closed_ = true;
+ std::vector<CuckooBucket> buckets;
+ Status s;
+ std::string unused_bucket;
+ if (num_entries_ > 0) {
+ // Calculate the real hash size if module hash is enabled.
+ if (use_module_hash_) {
+ hash_table_size_ =
+ static_cast<uint64_t>(num_entries_ / max_hash_table_ratio_);
+ }
+ s = MakeHashTable(&buckets);
+ if (!s.ok()) {
+ return s;
+ }
+ // Determine unused_user_key to fill empty buckets.
+ std::string unused_user_key = smallest_user_key_;
+ int curr_pos = static_cast<int>(unused_user_key.size()) - 1;
+ while (curr_pos >= 0) {
+ --unused_user_key[curr_pos];
+ if (Slice(unused_user_key).compare(smallest_user_key_) < 0) {
+ break;
+ }
+ --curr_pos;
+ }
+ if (curr_pos < 0) {
+ // Try using the largest key to identify an unused key.
+ unused_user_key = largest_user_key_;
+ curr_pos = static_cast<int>(unused_user_key.size()) - 1;
+ while (curr_pos >= 0) {
+ ++unused_user_key[curr_pos];
+ if (Slice(unused_user_key).compare(largest_user_key_) > 0) {
+ break;
+ }
+ --curr_pos;
+ }
+ }
+ if (curr_pos < 0) {
+ return Status::Corruption("Unable to find unused key");
+ }
+ if (is_last_level_file_) {
+ unused_bucket = unused_user_key;
+ } else {
+ ParsedInternalKey ikey(unused_user_key, 0, kTypeValue);
+ AppendInternalKey(&unused_bucket, ikey);
+ }
+ }
+ properties_.num_entries = num_entries_;
+ properties_.num_deletions = num_entries_ - num_values_;
+ properties_.fixed_key_len = key_size_;
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kValueLength].assign(
+ reinterpret_cast<const char*>(&value_size_), sizeof(value_size_));
+
+ uint64_t bucket_size = key_size_ + value_size_;
+ unused_bucket.resize(static_cast<size_t>(bucket_size), 'a');
+ // Write the table.
+ uint32_t num_added = 0;
+ for (auto& bucket : buckets) {
+ if (bucket.vector_idx == kMaxVectorIdx) {
+ s = file_->Append(Slice(unused_bucket));
+ } else {
+ ++num_added;
+ s = file_->Append(GetKey(bucket.vector_idx));
+ if (s.ok()) {
+ if (value_size_ > 0) {
+ s = file_->Append(GetValue(bucket.vector_idx));
+ }
+ }
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ assert(num_added == NumEntries());
+ properties_.raw_key_size = num_added * properties_.fixed_key_len;
+ properties_.raw_value_size = num_added * value_size_;
+
+ uint64_t offset = buckets.size() * bucket_size;
+ properties_.data_size = offset;
+ unused_bucket.resize(static_cast<size_t>(properties_.fixed_key_len));
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kEmptyKey] = unused_bucket;
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kNumHashFunc].assign(
+ reinterpret_cast<char*>(&num_hash_func_), sizeof(num_hash_func_));
+
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kHashTableSize].assign(
+ reinterpret_cast<const char*>(&hash_table_size_),
+ sizeof(hash_table_size_));
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kIsLastLevel].assign(
+ reinterpret_cast<const char*>(&is_last_level_file_),
+ sizeof(is_last_level_file_));
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kCuckooBlockSize].assign(
+ reinterpret_cast<const char*>(&cuckoo_block_size_),
+ sizeof(cuckoo_block_size_));
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kIdentityAsFirstHash].assign(
+ reinterpret_cast<const char*>(&identity_as_first_hash_),
+ sizeof(identity_as_first_hash_));
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kUseModuleHash].assign(
+ reinterpret_cast<const char*>(&use_module_hash_),
+ sizeof(use_module_hash_));
+ uint32_t user_key_len = static_cast<uint32_t>(smallest_user_key_.size());
+ properties_.user_collected_properties[
+ CuckooTablePropertyNames::kUserKeyLength].assign(
+ reinterpret_cast<const char*>(&user_key_len),
+ sizeof(user_key_len));
+
+ // Write meta blocks.
+ MetaIndexBuilder meta_index_builder;
+ PropertyBlockBuilder property_block_builder;
+
+ property_block_builder.AddTableProperty(properties_);
+ property_block_builder.Add(properties_.user_collected_properties);
+ Slice property_block = property_block_builder.Finish();
+ BlockHandle property_block_handle;
+ property_block_handle.set_offset(offset);
+ property_block_handle.set_size(property_block.size());
+ s = file_->Append(property_block);
+ offset += property_block.size();
+ if (!s.ok()) {
+ return s;
+ }
+
+ meta_index_builder.Add(kPropertiesBlock, property_block_handle);
+ Slice meta_index_block = meta_index_builder.Finish();
+
+ BlockHandle meta_index_block_handle;
+ meta_index_block_handle.set_offset(offset);
+ meta_index_block_handle.set_size(meta_index_block.size());
+ s = file_->Append(meta_index_block);
+ if (!s.ok()) {
+ return s;
+ }
+
+ Footer footer(kCuckooTableMagicNumber, 1);
+ footer.set_metaindex_handle(meta_index_block_handle);
+ footer.set_index_handle(BlockHandle::NullBlockHandle());
+ std::string footer_encoding;
+ footer.EncodeTo(&footer_encoding);
+ s = file_->Append(footer_encoding);
+
+ if (file_ != nullptr) {
+ file_checksum_ = file_->GetFileChecksum();
+ }
+ return s;
+}
+
+void CuckooTableBuilder::Abandon() {
+ assert(!closed_);
+ closed_ = true;
+}
+
+uint64_t CuckooTableBuilder::NumEntries() const {
+ return num_entries_;
+}
+
+uint64_t CuckooTableBuilder::FileSize() const {
+ if (closed_) {
+ return file_->GetFileSize();
+ } else if (num_entries_ == 0) {
+ return 0;
+ }
+
+ if (use_module_hash_) {
+ return static_cast<uint64_t>((key_size_ + value_size_) *
+ num_entries_ / max_hash_table_ratio_);
+ } else {
+ // Account for buckets being a power of two.
+ // As elements are added, file size remains constant for a while and
+ // doubles its size. Since compaction algorithm stops adding elements
+ // only after it exceeds the file limit, we account for the extra element
+ // being added here.
+ uint64_t expected_hash_table_size = hash_table_size_;
+ if (expected_hash_table_size < (num_entries_ + 1) / max_hash_table_ratio_) {
+ expected_hash_table_size *= 2;
+ }
+ return (key_size_ + value_size_) * expected_hash_table_size - 1;
+ }
+}
+
+// This method is invoked when there is no place to insert the target key.
+// It searches for a set of elements that can be moved to accommodate target
+// key. The search is a BFS graph traversal with first level (hash_vals)
+// being all the buckets target key could go to.
+// Then, from each node (curr_node), we find all the buckets that curr_node
+// could go to. They form the children of curr_node in the tree.
+// We continue the traversal until we find an empty bucket, in which case, we
+// move all elements along the path from first level to this empty bucket, to
+// make space for target key which is inserted at first level (*bucket_id).
+// If tree depth exceedes max depth, we return false indicating failure.
+bool CuckooTableBuilder::MakeSpaceForKey(
+ const autovector<uint64_t>& hash_vals,
+ const uint32_t make_space_for_key_call_id,
+ std::vector<CuckooBucket>* buckets, uint64_t* bucket_id) {
+ struct CuckooNode {
+ uint64_t bucket_id;
+ uint32_t depth;
+ uint32_t parent_pos;
+ CuckooNode(uint64_t _bucket_id, uint32_t _depth, int _parent_pos)
+ : bucket_id(_bucket_id), depth(_depth), parent_pos(_parent_pos) {}
+ };
+ // This is BFS search tree that is stored simply as a vector.
+ // Each node stores the index of parent node in the vector.
+ std::vector<CuckooNode> tree;
+ // We want to identify already visited buckets in the current method call so
+ // that we don't add same buckets again for exploration in the tree.
+ // We do this by maintaining a count of current method call in
+ // make_space_for_key_call_id, which acts as a unique id for this invocation
+ // of the method. We store this number into the nodes that we explore in
+ // current method call.
+ // It is unlikely for the increment operation to overflow because the maximum
+ // no. of times this will be called is <= max_num_hash_func_ + num_entries_.
+ for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) {
+ uint64_t bid = hash_vals[hash_cnt];
+ (*buckets)[static_cast<size_t>(bid)].make_space_for_key_call_id = make_space_for_key_call_id;
+ tree.push_back(CuckooNode(bid, 0, 0));
+ }
+ bool null_found = false;
+ uint32_t curr_pos = 0;
+ while (!null_found && curr_pos < tree.size()) {
+ CuckooNode& curr_node = tree[curr_pos];
+ uint32_t curr_depth = curr_node.depth;
+ if (curr_depth >= max_search_depth_) {
+ break;
+ }
+ CuckooBucket& curr_bucket = (*buckets)[static_cast<size_t>(curr_node.bucket_id)];
+ for (uint32_t hash_cnt = 0;
+ hash_cnt < num_hash_func_ && !null_found; ++hash_cnt) {
+ uint64_t child_bucket_id = CuckooHash(GetUserKey(curr_bucket.vector_idx),
+ hash_cnt, use_module_hash_, hash_table_size_, identity_as_first_hash_,
+ get_slice_hash_);
+ // Iterate inside Cuckoo Block.
+ for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
+ ++block_idx, ++child_bucket_id) {
+ if ((*buckets)[static_cast<size_t>(child_bucket_id)].make_space_for_key_call_id ==
+ make_space_for_key_call_id) {
+ continue;
+ }
+ (*buckets)[static_cast<size_t>(child_bucket_id)].make_space_for_key_call_id =
+ make_space_for_key_call_id;
+ tree.push_back(CuckooNode(child_bucket_id, curr_depth + 1,
+ curr_pos));
+ if ((*buckets)[static_cast<size_t>(child_bucket_id)].vector_idx == kMaxVectorIdx) {
+ null_found = true;
+ break;
+ }
+ }
+ }
+ ++curr_pos;
+ }
+
+ if (null_found) {
+ // There is an empty node in tree.back(). Now, traverse the path from this
+ // empty node to top of the tree and at every node in the path, replace
+ // child with the parent. Stop when first level is reached in the tree
+ // (happens when 0 <= bucket_to_replace_pos < num_hash_func_) and return
+ // this location in first level for target key to be inserted.
+ uint32_t bucket_to_replace_pos = static_cast<uint32_t>(tree.size()) - 1;
+ while (bucket_to_replace_pos >= num_hash_func_) {
+ CuckooNode& curr_node = tree[bucket_to_replace_pos];
+ (*buckets)[static_cast<size_t>(curr_node.bucket_id)] =
+ (*buckets)[static_cast<size_t>(tree[curr_node.parent_pos].bucket_id)];
+ bucket_to_replace_pos = curr_node.parent_pos;
+ }
+ *bucket_id = tree[bucket_to_replace_pos].bucket_id;
+ }
+ return null_found;
+}
+
+const char* CuckooTableBuilder::GetFileChecksumFuncName() const {
+ if (file_ != nullptr) {
+ return file_->GetFileChecksumFuncName();
+ } else {
+ return kUnknownFileChecksumFuncName.c_str();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.h b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.h
new file mode 100644
index 00000000..d41dfed7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include <stdint.h>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+#include "db/version_edit.h"
+#include "port/port.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "table/table_builder.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class CuckooTableBuilder: public TableBuilder {
+ public:
+ CuckooTableBuilder(WritableFileWriter* file, double max_hash_table_ratio,
+ uint32_t max_num_hash_func, uint32_t max_search_depth,
+ const Comparator* user_comparator,
+ uint32_t cuckoo_block_size, bool use_module_hash,
+ bool identity_as_first_hash,
+ uint64_t (*get_slice_hash)(const Slice&, uint32_t,
+ uint64_t),
+ uint32_t column_family_id,
+ const std::string& column_family_name);
+ // No copying allowed
+ CuckooTableBuilder(const CuckooTableBuilder&) = delete;
+ void operator=(const CuckooTableBuilder&) = delete;
+
+ // REQUIRES: Either Finish() or Abandon() has been called.
+ ~CuckooTableBuilder() {}
+
+ // Add key,value to the table being constructed.
+ // REQUIRES: key is after any previously added key according to comparator.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Add(const Slice& key, const Slice& value) override;
+
+ // Return non-ok iff some error has been detected.
+ Status status() const override { return status_; }
+
+ // Finish building the table. Stops using the file passed to the
+ // constructor after this function returns.
+ // REQUIRES: Finish(), Abandon() have not been called
+ Status Finish() override;
+
+ // Indicate that the contents of this builder should be abandoned. Stops
+ // using the file passed to the constructor after this function returns.
+ // If the caller is not going to call Finish(), it must call Abandon()
+ // before destroying this builder.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Abandon() override;
+
+ // Number of calls to Add() so far.
+ uint64_t NumEntries() const override;
+
+ // Size of the file generated so far. If invoked after a successful
+ // Finish() call, returns the size of the final generated file.
+ uint64_t FileSize() const override;
+
+ TableProperties GetTableProperties() const override { return properties_; }
+
+ // Get file checksum
+ const std::string& GetFileChecksum() const override { return file_checksum_; }
+
+ // Get file checksum function name
+ const char* GetFileChecksumFuncName() const override;
+
+ private:
+ struct CuckooBucket {
+ CuckooBucket()
+ : vector_idx(kMaxVectorIdx), make_space_for_key_call_id(0) {}
+ uint32_t vector_idx;
+ // This number will not exceed kvs_.size() + max_num_hash_func_.
+ // We assume number of items is <= 2^32.
+ uint32_t make_space_for_key_call_id;
+ };
+ static const uint32_t kMaxVectorIdx = port::kMaxInt32;
+
+ bool MakeSpaceForKey(const autovector<uint64_t>& hash_vals,
+ const uint32_t call_id,
+ std::vector<CuckooBucket>* buckets, uint64_t* bucket_id);
+ Status MakeHashTable(std::vector<CuckooBucket>* buckets);
+
+ inline bool IsDeletedKey(uint64_t idx) const;
+ inline Slice GetKey(uint64_t idx) const;
+ inline Slice GetUserKey(uint64_t idx) const;
+ inline Slice GetValue(uint64_t idx) const;
+
+ uint32_t num_hash_func_;
+ WritableFileWriter* file_;
+ const double max_hash_table_ratio_;
+ const uint32_t max_num_hash_func_;
+ const uint32_t max_search_depth_;
+ const uint32_t cuckoo_block_size_;
+ uint64_t hash_table_size_;
+ bool is_last_level_file_;
+ bool has_seen_first_key_;
+ bool has_seen_first_value_;
+ uint64_t key_size_;
+ uint64_t value_size_;
+ // A list of fixed-size key-value pairs concatenating into a string.
+ // Use GetKey(), GetUserKey(), and GetValue() to retrieve a specific
+ // key / value given an index
+ std::string kvs_;
+ std::string deleted_keys_;
+ // Number of key-value pairs stored in kvs_ + number of deleted keys
+ uint64_t num_entries_;
+ // Number of keys that contain value (non-deletion op)
+ uint64_t num_values_;
+ Status status_;
+ TableProperties properties_;
+ const Comparator* ucomp_;
+ bool use_module_hash_;
+ bool identity_as_first_hash_;
+ uint64_t (*get_slice_hash_)(const Slice& s, uint32_t index,
+ uint64_t max_num_buckets);
+ std::string largest_user_key_ = "";
+ std::string smallest_user_key_ = "";
+
+ bool closed_; // Either Finish() or Abandon() has been called.
+
+ // Store file checksum. If checksum is disabled, its value is "0"
+ std::string file_checksum_ = kUnknownFileChecksum;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder_test.cc b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder_test.cc
new file mode 100644
index 00000000..005ce717
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_builder_test.cc
@@ -0,0 +1,662 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <vector>
+#include <string>
+#include <map>
+#include <utility>
+
+#include "file/random_access_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "table/cuckoo/cuckoo_table_builder.h"
+#include "table/meta_blocks.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+extern const uint64_t kCuckooTableMagicNumber;
+
+namespace {
+std::unordered_map<std::string, std::vector<uint64_t>> hash_map;
+
+uint64_t GetSliceHash(const Slice& s, uint32_t index,
+ uint64_t /*max_num_buckets*/) {
+ return hash_map[s.ToString()][index];
+}
+} // namespace
+
+class CuckooBuilderTest : public testing::Test {
+ public:
+ CuckooBuilderTest() {
+ env_ = Env::Default();
+ Options options;
+ options.allow_mmap_reads = true;
+ env_options_ = EnvOptions(options);
+ }
+
+ void CheckFileContents(const std::vector<std::string>& keys,
+ const std::vector<std::string>& values,
+ const std::vector<uint64_t>& expected_locations,
+ std::string expected_unused_bucket, uint64_t expected_table_size,
+ uint32_t expected_num_hash_func, bool expected_is_last_level,
+ uint32_t expected_cuckoo_block_size = 1) {
+ uint64_t num_deletions = 0;
+ for (const auto& key : keys) {
+ ParsedInternalKey parsed;
+ if (ParseInternalKey(key, &parsed) && parsed.type == kTypeDeletion) {
+ num_deletions++;
+ }
+ }
+ // Read file
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env_->NewRandomAccessFile(fname, &read_file, env_options_));
+ uint64_t read_file_size;
+ ASSERT_OK(env_->GetFileSize(fname, &read_file_size));
+
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ Options options;
+ options.allow_mmap_reads = true;
+ ImmutableCFOptions ioptions(options);
+
+ // Assert Table Properties.
+ TableProperties* props = nullptr;
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+ ASSERT_OK(ReadTableProperties(file_reader.get(), read_file_size,
+ kCuckooTableMagicNumber, ioptions,
+ &props, true /* compression_type_missing */));
+ // Check unused bucket.
+ std::string unused_key = props->user_collected_properties[
+ CuckooTablePropertyNames::kEmptyKey];
+ ASSERT_EQ(expected_unused_bucket.substr(0,
+ props->fixed_key_len), unused_key);
+
+ uint64_t value_len_found =
+ *reinterpret_cast<const uint64_t*>(props->user_collected_properties[
+ CuckooTablePropertyNames::kValueLength].data());
+ ASSERT_EQ(values.empty() ? 0 : values[0].size(), value_len_found);
+ ASSERT_EQ(props->raw_value_size, values.size()*value_len_found);
+ const uint64_t table_size =
+ *reinterpret_cast<const uint64_t*>(props->user_collected_properties[
+ CuckooTablePropertyNames::kHashTableSize].data());
+ ASSERT_EQ(expected_table_size, table_size);
+ const uint32_t num_hash_func_found =
+ *reinterpret_cast<const uint32_t*>(props->user_collected_properties[
+ CuckooTablePropertyNames::kNumHashFunc].data());
+ ASSERT_EQ(expected_num_hash_func, num_hash_func_found);
+ const uint32_t cuckoo_block_size =
+ *reinterpret_cast<const uint32_t*>(props->user_collected_properties[
+ CuckooTablePropertyNames::kCuckooBlockSize].data());
+ ASSERT_EQ(expected_cuckoo_block_size, cuckoo_block_size);
+ const bool is_last_level_found =
+ *reinterpret_cast<const bool*>(props->user_collected_properties[
+ CuckooTablePropertyNames::kIsLastLevel].data());
+ ASSERT_EQ(expected_is_last_level, is_last_level_found);
+
+ ASSERT_EQ(props->num_entries, keys.size());
+ ASSERT_EQ(props->num_deletions, num_deletions);
+ ASSERT_EQ(props->fixed_key_len, keys.empty() ? 0 : keys[0].size());
+ ASSERT_EQ(props->data_size, expected_unused_bucket.size() *
+ (expected_table_size + expected_cuckoo_block_size - 1));
+ ASSERT_EQ(props->raw_key_size, keys.size()*props->fixed_key_len);
+ ASSERT_EQ(props->column_family_id, 0);
+ ASSERT_EQ(props->column_family_name, kDefaultColumnFamilyName);
+ delete props;
+
+ // Check contents of the bucket.
+ std::vector<bool> keys_found(keys.size(), false);
+ size_t bucket_size = expected_unused_bucket.size();
+ for (uint32_t i = 0; i < table_size + cuckoo_block_size - 1; ++i) {
+ Slice read_slice;
+ ASSERT_OK(file_reader->Read(i * bucket_size, bucket_size, &read_slice,
+ nullptr));
+ size_t key_idx =
+ std::find(expected_locations.begin(), expected_locations.end(), i) -
+ expected_locations.begin();
+ if (key_idx == keys.size()) {
+ // i is not one of the expected locations. Empty bucket.
+ if (read_slice.data() == nullptr) {
+ ASSERT_EQ(0, expected_unused_bucket.size());
+ } else {
+ ASSERT_EQ(read_slice.compare(expected_unused_bucket), 0);
+ }
+ } else {
+ keys_found[key_idx] = true;
+ ASSERT_EQ(read_slice.compare(keys[key_idx] + values[key_idx]), 0);
+ }
+ }
+ for (auto key_found : keys_found) {
+ // Check that all keys wereReader found.
+ ASSERT_TRUE(key_found);
+ }
+ }
+
+ std::string GetInternalKey(Slice user_key, bool zero_seqno,
+ ValueType type = kTypeValue) {
+ IterKey ikey;
+ ikey.SetInternalKey(user_key, zero_seqno ? 0 : 1000, type);
+ return ikey.GetInternalKey().ToString();
+ }
+
+ uint64_t NextPowOf2(uint64_t num) {
+ uint64_t n = 2;
+ while (n <= num) {
+ n *= 2;
+ }
+ return n;
+ }
+
+ uint64_t GetExpectedTableSize(uint64_t num) {
+ return NextPowOf2(static_cast<uint64_t>(num / kHashTableRatio));
+ }
+
+
+ Env* env_;
+ EnvOptions env_options_;
+ std::string fname;
+ const double kHashTableRatio = 0.9;
+};
+
+TEST_F(CuckooBuilderTest, SuccessWithEmptyFile) {
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("EmptyFile");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, 4, 100,
+ BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ ASSERT_EQ(0UL, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ CheckFileContents({}, {}, {}, "", 2, 2, false);
+}
+
+TEST_F(CuckooBuilderTest, WriteSuccessNoCollisionFullKey) {
+ for (auto type : {kTypeValue, kTypeDeletion}) {
+ uint32_t num_hash_fun = 4;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
+ std::vector<std::string> values;
+ if (type == kTypeValue) {
+ values = {"v01", "v02", "v03", "v04"};
+ } else {
+ values = {"", "", "", ""};
+ }
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1, 2, 3}},
+ {user_keys[1], {1, 2, 3, 4}},
+ {user_keys[2], {2, 3, 4, 5}},
+ {user_keys[3], {3, 4, 5, 6}}};
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
+ std::vector<std::string> keys;
+ for (auto& user_key : user_keys) {
+ keys.push_back(GetInternalKey(user_key, false, type));
+ }
+ uint64_t expected_table_size = GetExpectedTableSize(keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("NoCollisionFullKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(keys[i]), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = GetInternalKey("key00", true);
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(keys, values, expected_locations, expected_unused_bucket,
+ expected_table_size, 2, false);
+ }
+}
+
+TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionFullKey) {
+ uint32_t num_hash_fun = 4;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1, 2, 3}},
+ {user_keys[1], {0, 1, 2, 3}},
+ {user_keys[2], {0, 1, 2, 3}},
+ {user_keys[3], {0, 1, 2, 3}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
+ std::vector<std::string> keys;
+ for (auto& user_key : user_keys) {
+ keys.push_back(GetInternalKey(user_key, false));
+ }
+ uint64_t expected_table_size = GetExpectedTableSize(keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionFullKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(keys[i]), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = GetInternalKey("key00", true);
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 4, false);
+}
+
+TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionAndCuckooBlock) {
+ uint32_t num_hash_fun = 4;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1, 2, 3}},
+ {user_keys[1], {0, 1, 2, 3}},
+ {user_keys[2], {0, 1, 2, 3}},
+ {user_keys[3], {0, 1, 2, 3}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
+ std::vector<std::string> keys;
+ for (auto& user_key : user_keys) {
+ keys.push_back(GetInternalKey(user_key, false));
+ }
+ uint64_t expected_table_size = GetExpectedTableSize(keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ uint32_t cuckoo_block_size = 2;
+ fname = test::PerThreadDBPath("WithCollisionFullKey2");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(
+ file_writer.get(), kHashTableRatio, num_hash_fun, 100,
+ BytewiseComparator(), cuckoo_block_size, false, false, GetSliceHash,
+ 0 /* column_family_id */, kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(keys[i]), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = GetInternalKey("key00", true);
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 3, false, cuckoo_block_size);
+}
+
+TEST_F(CuckooBuilderTest, WithCollisionPathFullKey) {
+ // Have two hash functions. Insert elements with overlapping hashes.
+ // Finally insert an element with hash value somewhere in the middle
+ // so that it displaces all the elements after that.
+ uint32_t num_hash_fun = 2;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03",
+ "key04", "key05"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1}},
+ {user_keys[1], {1, 2}},
+ {user_keys[2], {2, 3}},
+ {user_keys[3], {3, 4}},
+ {user_keys[4], {0, 2}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 3, 4, 2};
+ std::vector<std::string> keys;
+ for (auto& user_key : user_keys) {
+ keys.push_back(GetInternalKey(user_key, false));
+ }
+ uint64_t expected_table_size = GetExpectedTableSize(keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionPathFullKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(keys[i]), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = GetInternalKey("key00", true);
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 2, false);
+}
+
+TEST_F(CuckooBuilderTest, WithCollisionPathFullKeyAndCuckooBlock) {
+ uint32_t num_hash_fun = 2;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03",
+ "key04", "key05"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1}},
+ {user_keys[1], {1, 2}},
+ {user_keys[2], {3, 4}},
+ {user_keys[3], {4, 5}},
+ {user_keys[4], {0, 3}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {2, 1, 3, 4, 0};
+ std::vector<std::string> keys;
+ for (auto& user_key : user_keys) {
+ keys.push_back(GetInternalKey(user_key, false));
+ }
+ uint64_t expected_table_size = GetExpectedTableSize(keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionPathFullKeyAndCuckooBlock");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 2, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(keys[i]), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = GetInternalKey("key00", true);
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 2, false, 2);
+}
+
+TEST_F(CuckooBuilderTest, WriteSuccessNoCollisionUserKey) {
+ uint32_t num_hash_fun = 4;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1, 2, 3}},
+ {user_keys[1], {1, 2, 3, 4}},
+ {user_keys[2], {2, 3, 4, 5}},
+ {user_keys[3], {3, 4, 5, 6}}};
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
+ uint64_t expected_table_size = GetExpectedTableSize(user_keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("NoCollisionUserKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = user_keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = "key00";
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(user_keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 2, true);
+}
+
+TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionUserKey) {
+ uint32_t num_hash_fun = 4;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1, 2, 3}},
+ {user_keys[1], {0, 1, 2, 3}},
+ {user_keys[2], {0, 1, 2, 3}},
+ {user_keys[3], {0, 1, 2, 3}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
+ uint64_t expected_table_size = GetExpectedTableSize(user_keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionUserKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = user_keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = "key00";
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(user_keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 4, true);
+}
+
+TEST_F(CuckooBuilderTest, WithCollisionPathUserKey) {
+ uint32_t num_hash_fun = 2;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03",
+ "key04", "key05"};
+ std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1}},
+ {user_keys[1], {1, 2}},
+ {user_keys[2], {2, 3}},
+ {user_keys[3], {3, 4}},
+ {user_keys[4], {0, 2}},
+ };
+ hash_map = std::move(hm);
+
+ std::vector<uint64_t> expected_locations = {0, 1, 3, 4, 2};
+ uint64_t expected_table_size = GetExpectedTableSize(user_keys.size());
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionPathUserKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 2, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ size_t bucket_size = user_keys[0].size() + values[0].size();
+ ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize());
+ ASSERT_OK(builder.Finish());
+ ASSERT_OK(file_writer->Close());
+ ASSERT_LE(expected_table_size * bucket_size, builder.FileSize());
+
+ std::string expected_unused_bucket = "key00";
+ expected_unused_bucket += std::string(values[0].size(), 'a');
+ CheckFileContents(user_keys, values, expected_locations,
+ expected_unused_bucket, expected_table_size, 2, true);
+}
+
+TEST_F(CuckooBuilderTest, FailWhenCollisionPathTooLong) {
+ // Have two hash functions. Insert elements with overlapping hashes.
+ // Finally try inserting an element with hash value somewhere in the middle
+ // and it should fail because the no. of elements to displace is too high.
+ uint32_t num_hash_fun = 2;
+ std::vector<std::string> user_keys = {"key01", "key02", "key03",
+ "key04", "key05"};
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {user_keys[0], {0, 1}},
+ {user_keys[1], {1, 2}},
+ {user_keys[2], {2, 3}},
+ {user_keys[3], {3, 4}},
+ {user_keys[4], {0, 1}},
+ };
+ hash_map = std::move(hm);
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("WithCollisionPathUserKey");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 2, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t i = 0; i < user_keys.size(); i++) {
+ builder.Add(Slice(GetInternalKey(user_keys[i], false)), Slice("value"));
+ ASSERT_EQ(builder.NumEntries(), i + 1);
+ ASSERT_OK(builder.status());
+ }
+ ASSERT_TRUE(builder.Finish().IsNotSupported());
+ ASSERT_OK(file_writer->Close());
+}
+
+TEST_F(CuckooBuilderTest, FailWhenSameKeyInserted) {
+ // Need to have a temporary variable here as VS compiler does not currently
+ // support operator= with initializer_list as a parameter
+ std::unordered_map<std::string, std::vector<uint64_t>> hm = {
+ {"repeatedkey", {0, 1, 2, 3}}};
+ hash_map = std::move(hm);
+ uint32_t num_hash_fun = 4;
+ std::string user_key = "repeatedkey";
+
+ std::unique_ptr<WritableFile> writable_file;
+ fname = test::PerThreadDBPath("FailWhenSameKeyInserted");
+ ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ EnvOptions()));
+ CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun,
+ 100, BytewiseComparator(), 1, false, false,
+ GetSliceHash, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+
+ builder.Add(Slice(GetInternalKey(user_key, false)), Slice("value1"));
+ ASSERT_EQ(builder.NumEntries(), 1u);
+ ASSERT_OK(builder.status());
+ builder.Add(Slice(GetInternalKey(user_key, true)), Slice("value2"));
+ ASSERT_EQ(builder.NumEntries(), 2u);
+ ASSERT_OK(builder.status());
+
+ ASSERT_TRUE(builder.Finish().IsNotSupported());
+ ASSERT_OK(file_writer->Close());
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.cc b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.cc
new file mode 100644
index 00000000..5ba48f09
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "table/cuckoo/cuckoo_table_factory.h"
+
+#include "db/dbformat.h"
+#include "table/cuckoo/cuckoo_table_builder.h"
+#include "table/cuckoo/cuckoo_table_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status CuckooTableFactory::NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool /*prefetch_index_and_filter_in_cache*/) const {
+ std::unique_ptr<CuckooTableReader> new_reader(new CuckooTableReader(
+ table_reader_options.ioptions, std::move(file), file_size,
+ table_reader_options.internal_comparator.user_comparator(), nullptr));
+ Status s = new_reader->status();
+ if (s.ok()) {
+ *table = std::move(new_reader);
+ }
+ return s;
+}
+
+TableBuilder* CuckooTableFactory::NewTableBuilder(
+ const TableBuilderOptions& table_builder_options, uint32_t column_family_id,
+ WritableFileWriter* file) const {
+ // Ignore the skipFIlters flag. Does not apply to this file format
+ //
+
+ // TODO: change builder to take the option struct
+ return new CuckooTableBuilder(
+ file, table_options_.hash_table_ratio, 64,
+ table_options_.max_search_depth,
+ table_builder_options.internal_comparator.user_comparator(),
+ table_options_.cuckoo_block_size, table_options_.use_module_hash,
+ table_options_.identity_as_first_hash, nullptr /* get_slice_hash */,
+ column_family_id, table_builder_options.column_family_name);
+}
+
+std::string CuckooTableFactory::GetPrintableTableOptions() const {
+ std::string ret;
+ ret.reserve(2000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+
+ snprintf(buffer, kBufferSize, " hash_table_ratio: %lf\n",
+ table_options_.hash_table_ratio);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " max_search_depth: %u\n",
+ table_options_.max_search_depth);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " cuckoo_block_size: %u\n",
+ table_options_.cuckoo_block_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " identity_as_first_hash: %d\n",
+ table_options_.identity_as_first_hash);
+ ret.append(buffer);
+ return ret;
+}
+
+TableFactory* NewCuckooTableFactory(const CuckooTableOptions& table_options) {
+ return new CuckooTableFactory(table_options);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.h b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.h
new file mode 100644
index 00000000..1b5b6c20
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_factory.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include "rocksdb/table.h"
+#include "util/murmurhash.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const uint32_t kCuckooMurmurSeedMultiplier = 816922183;
+static inline uint64_t CuckooHash(
+ const Slice& user_key, uint32_t hash_cnt, bool use_module_hash,
+ uint64_t table_size_, bool identity_as_first_hash,
+ uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t)) {
+#if !defined NDEBUG || defined OS_WIN
+ // This part is used only in unit tests but we have to keep it for Windows
+ // build as we run test in both debug and release modes under Windows.
+ if (get_slice_hash != nullptr) {
+ return get_slice_hash(user_key, hash_cnt, table_size_);
+ }
+#else
+ (void)get_slice_hash;
+#endif
+
+ uint64_t value = 0;
+ if (hash_cnt == 0 && identity_as_first_hash) {
+ value = (*reinterpret_cast<const int64_t*>(user_key.data()));
+ } else {
+ value = MurmurHash(user_key.data(), static_cast<int>(user_key.size()),
+ kCuckooMurmurSeedMultiplier * hash_cnt);
+ }
+ if (use_module_hash) {
+ return value % table_size_;
+ } else {
+ return value & (table_size_ - 1);
+ }
+}
+
+// Cuckoo Table is designed for applications that require fast point lookups
+// but not fast range scans.
+//
+// Some assumptions:
+// - Key length and Value length are fixed.
+// - Does not support Snapshot.
+// - Does not support Merge operations.
+// - Does not support prefix bloom filters.
+class CuckooTableFactory : public TableFactory {
+ public:
+ explicit CuckooTableFactory(const CuckooTableOptions& table_options)
+ : table_options_(table_options) {}
+ ~CuckooTableFactory() {}
+
+ const char* Name() const override { return "CuckooTable"; }
+
+ Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool prefetch_index_and_filter_in_cache = true) const override;
+
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_family_id, WritableFileWriter* file) const override;
+
+ // Sanitizes the specified DB Options.
+ Status SanitizeOptions(
+ const DBOptions& /*db_opts*/,
+ const ColumnFamilyOptions& /*cf_opts*/) const override {
+ return Status::OK();
+ }
+
+ std::string GetPrintableTableOptions() const override;
+
+ void* GetOptions() override { return &table_options_; }
+
+ Status GetOptionString(std::string* /*opt_string*/,
+ const std::string& /*delimiter*/) const override {
+ return Status::OK();
+ }
+
+ private:
+ CuckooTableOptions table_options_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.cc b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.cc
new file mode 100644
index 00000000..cd460bd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "table/cuckoo/cuckoo_table_reader.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+#include "memory/arena.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/table.h"
+#include "table/cuckoo/cuckoo_table_factory.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.h"
+#include "table/meta_blocks.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+const uint64_t CACHE_LINE_MASK = ~((uint64_t)CACHE_LINE_SIZE - 1);
+const uint32_t kInvalidIndex = std::numeric_limits<uint32_t>::max();
+}
+
+extern const uint64_t kCuckooTableMagicNumber;
+
+CuckooTableReader::CuckooTableReader(
+ const ImmutableCFOptions& ioptions,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ const Comparator* comparator,
+ uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t))
+ : file_(std::move(file)),
+ is_last_level_(false),
+ identity_as_first_hash_(false),
+ use_module_hash_(false),
+ num_hash_func_(0),
+ unused_key_(""),
+ key_length_(0),
+ user_key_length_(0),
+ value_length_(0),
+ bucket_length_(0),
+ cuckoo_block_size_(0),
+ cuckoo_block_bytes_minus_one_(0),
+ table_size_(0),
+ ucomp_(comparator),
+ get_slice_hash_(get_slice_hash) {
+ if (!ioptions.allow_mmap_reads) {
+ status_ = Status::InvalidArgument("File is not mmaped");
+ }
+ TableProperties* props = nullptr;
+ status_ = ReadTableProperties(file_.get(), file_size, kCuckooTableMagicNumber,
+ ioptions, &props, true /* compression_type_missing */);
+ if (!status_.ok()) {
+ return;
+ }
+ table_props_.reset(props);
+ auto& user_props = props->user_collected_properties;
+ auto hash_funs = user_props.find(CuckooTablePropertyNames::kNumHashFunc);
+ if (hash_funs == user_props.end()) {
+ status_ = Status::Corruption("Number of hash functions not found");
+ return;
+ }
+ num_hash_func_ = *reinterpret_cast<const uint32_t*>(hash_funs->second.data());
+ auto unused_key = user_props.find(CuckooTablePropertyNames::kEmptyKey);
+ if (unused_key == user_props.end()) {
+ status_ = Status::Corruption("Empty bucket value not found");
+ return;
+ }
+ unused_key_ = unused_key->second;
+
+ key_length_ = static_cast<uint32_t>(props->fixed_key_len);
+ auto user_key_len = user_props.find(CuckooTablePropertyNames::kUserKeyLength);
+ if (user_key_len == user_props.end()) {
+ status_ = Status::Corruption("User key length not found");
+ return;
+ }
+ user_key_length_ = *reinterpret_cast<const uint32_t*>(
+ user_key_len->second.data());
+
+ auto value_length = user_props.find(CuckooTablePropertyNames::kValueLength);
+ if (value_length == user_props.end()) {
+ status_ = Status::Corruption("Value length not found");
+ return;
+ }
+ value_length_ = *reinterpret_cast<const uint32_t*>(
+ value_length->second.data());
+ bucket_length_ = key_length_ + value_length_;
+
+ auto hash_table_size = user_props.find(
+ CuckooTablePropertyNames::kHashTableSize);
+ if (hash_table_size == user_props.end()) {
+ status_ = Status::Corruption("Hash table size not found");
+ return;
+ }
+ table_size_ = *reinterpret_cast<const uint64_t*>(
+ hash_table_size->second.data());
+
+ auto is_last_level = user_props.find(CuckooTablePropertyNames::kIsLastLevel);
+ if (is_last_level == user_props.end()) {
+ status_ = Status::Corruption("Is last level not found");
+ return;
+ }
+ is_last_level_ = *reinterpret_cast<const bool*>(is_last_level->second.data());
+
+ auto identity_as_first_hash = user_props.find(
+ CuckooTablePropertyNames::kIdentityAsFirstHash);
+ if (identity_as_first_hash == user_props.end()) {
+ status_ = Status::Corruption("identity as first hash not found");
+ return;
+ }
+ identity_as_first_hash_ = *reinterpret_cast<const bool*>(
+ identity_as_first_hash->second.data());
+
+ auto use_module_hash = user_props.find(
+ CuckooTablePropertyNames::kUseModuleHash);
+ if (use_module_hash == user_props.end()) {
+ status_ = Status::Corruption("hash type is not found");
+ return;
+ }
+ use_module_hash_ = *reinterpret_cast<const bool*>(
+ use_module_hash->second.data());
+ auto cuckoo_block_size = user_props.find(
+ CuckooTablePropertyNames::kCuckooBlockSize);
+ if (cuckoo_block_size == user_props.end()) {
+ status_ = Status::Corruption("Cuckoo block size not found");
+ return;
+ }
+ cuckoo_block_size_ = *reinterpret_cast<const uint32_t*>(
+ cuckoo_block_size->second.data());
+ cuckoo_block_bytes_minus_one_ = cuckoo_block_size_ * bucket_length_ - 1;
+ status_ = file_->Read(0, static_cast<size_t>(file_size), &file_data_, nullptr);
+}
+
+Status CuckooTableReader::Get(const ReadOptions& /*readOptions*/,
+ const Slice& key, GetContext* get_context,
+ const SliceTransform* /* prefix_extractor */,
+ bool /*skip_filters*/) {
+ assert(key.size() == key_length_ + (is_last_level_ ? 8 : 0));
+ Slice user_key = ExtractUserKey(key);
+ for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) {
+ uint64_t offset = bucket_length_ * CuckooHash(
+ user_key, hash_cnt, use_module_hash_, table_size_,
+ identity_as_first_hash_, get_slice_hash_);
+ const char* bucket = &file_data_.data()[offset];
+ for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
+ ++block_idx, bucket += bucket_length_) {
+ if (ucomp_->Equal(Slice(unused_key_.data(), user_key.size()),
+ Slice(bucket, user_key.size()))) {
+ return Status::OK();
+ }
+ // Here, we compare only the user key part as we support only one entry
+ // per user key and we don't support snapshot.
+ if (ucomp_->Equal(user_key, Slice(bucket, user_key.size()))) {
+ Slice value(bucket + key_length_, value_length_);
+ if (is_last_level_) {
+ // Sequence number is not stored at the last level, so we will use
+ // kMaxSequenceNumber since it is unknown. This could cause some
+ // transactions to fail to lock a key due to known sequence number.
+ // However, it is expected for anyone to use a CuckooTable in a
+ // TransactionDB.
+ get_context->SaveValue(value, kMaxSequenceNumber);
+ } else {
+ Slice full_key(bucket, key_length_);
+ ParsedInternalKey found_ikey;
+ ParseInternalKey(full_key, &found_ikey);
+ bool dont_care __attribute__((__unused__));
+ get_context->SaveValue(found_ikey, value, &dont_care);
+ }
+ // We don't support merge operations. So, we return here.
+ return Status::OK();
+ }
+ }
+ }
+ return Status::OK();
+}
+
+void CuckooTableReader::Prepare(const Slice& key) {
+ // Prefetch the first Cuckoo Block.
+ Slice user_key = ExtractUserKey(key);
+ uint64_t addr = reinterpret_cast<uint64_t>(file_data_.data()) +
+ bucket_length_ * CuckooHash(user_key, 0, use_module_hash_, table_size_,
+ identity_as_first_hash_, nullptr);
+ uint64_t end_addr = addr + cuckoo_block_bytes_minus_one_;
+ for (addr &= CACHE_LINE_MASK; addr < end_addr; addr += CACHE_LINE_SIZE) {
+ PREFETCH(reinterpret_cast<const char*>(addr), 0, 3);
+ }
+}
+
+class CuckooTableIterator : public InternalIterator {
+ public:
+ explicit CuckooTableIterator(CuckooTableReader* reader);
+ // No copying allowed
+ CuckooTableIterator(const CuckooTableIterator&) = delete;
+ void operator=(const Iterator&) = delete;
+ ~CuckooTableIterator() override {}
+ bool Valid() const override;
+ void SeekToFirst() override;
+ void SeekToLast() override;
+ void Seek(const Slice& target) override;
+ void SeekForPrev(const Slice& target) override;
+ void Next() override;
+ void Prev() override;
+ Slice key() const override;
+ Slice value() const override;
+ Status status() const override { return Status::OK(); }
+ void InitIfNeeded();
+
+ private:
+ struct BucketComparator {
+ BucketComparator(const Slice& file_data, const Comparator* ucomp,
+ uint32_t bucket_len, uint32_t user_key_len,
+ const Slice& target = Slice())
+ : file_data_(file_data),
+ ucomp_(ucomp),
+ bucket_len_(bucket_len),
+ user_key_len_(user_key_len),
+ target_(target) {}
+ bool operator()(const uint32_t first, const uint32_t second) const {
+ const char* first_bucket =
+ (first == kInvalidIndex) ? target_.data() :
+ &file_data_.data()[first * bucket_len_];
+ const char* second_bucket =
+ (second == kInvalidIndex) ? target_.data() :
+ &file_data_.data()[second * bucket_len_];
+ return ucomp_->Compare(Slice(first_bucket, user_key_len_),
+ Slice(second_bucket, user_key_len_)) < 0;
+ }
+ private:
+ const Slice file_data_;
+ const Comparator* ucomp_;
+ const uint32_t bucket_len_;
+ const uint32_t user_key_len_;
+ const Slice target_;
+ };
+
+ const BucketComparator bucket_comparator_;
+ void PrepareKVAtCurrIdx();
+ CuckooTableReader* reader_;
+ bool initialized_;
+ // Contains a map of keys to bucket_id sorted in key order.
+ std::vector<uint32_t> sorted_bucket_ids_;
+ // We assume that the number of items can be stored in uint32 (4 Billion).
+ uint32_t curr_key_idx_;
+ Slice curr_value_;
+ IterKey curr_key_;
+};
+
+CuckooTableIterator::CuckooTableIterator(CuckooTableReader* reader)
+ : bucket_comparator_(reader->file_data_, reader->ucomp_,
+ reader->bucket_length_, reader->user_key_length_),
+ reader_(reader),
+ initialized_(false),
+ curr_key_idx_(kInvalidIndex) {
+ sorted_bucket_ids_.clear();
+ curr_value_.clear();
+ curr_key_.Clear();
+}
+
+void CuckooTableIterator::InitIfNeeded() {
+ if (initialized_) {
+ return;
+ }
+ sorted_bucket_ids_.reserve(static_cast<size_t>(reader_->GetTableProperties()->num_entries));
+ uint64_t num_buckets = reader_->table_size_ + reader_->cuckoo_block_size_ - 1;
+ assert(num_buckets < kInvalidIndex);
+ const char* bucket = reader_->file_data_.data();
+ for (uint32_t bucket_id = 0; bucket_id < num_buckets; ++bucket_id) {
+ if (Slice(bucket, reader_->key_length_) != Slice(reader_->unused_key_)) {
+ sorted_bucket_ids_.push_back(bucket_id);
+ }
+ bucket += reader_->bucket_length_;
+ }
+ assert(sorted_bucket_ids_.size() ==
+ reader_->GetTableProperties()->num_entries);
+ std::sort(sorted_bucket_ids_.begin(), sorted_bucket_ids_.end(),
+ bucket_comparator_);
+ curr_key_idx_ = kInvalidIndex;
+ initialized_ = true;
+}
+
+void CuckooTableIterator::SeekToFirst() {
+ InitIfNeeded();
+ curr_key_idx_ = 0;
+ PrepareKVAtCurrIdx();
+}
+
+void CuckooTableIterator::SeekToLast() {
+ InitIfNeeded();
+ curr_key_idx_ = static_cast<uint32_t>(sorted_bucket_ids_.size()) - 1;
+ PrepareKVAtCurrIdx();
+}
+
+void CuckooTableIterator::Seek(const Slice& target) {
+ InitIfNeeded();
+ const BucketComparator seek_comparator(
+ reader_->file_data_, reader_->ucomp_,
+ reader_->bucket_length_, reader_->user_key_length_,
+ ExtractUserKey(target));
+ auto seek_it = std::lower_bound(sorted_bucket_ids_.begin(),
+ sorted_bucket_ids_.end(),
+ kInvalidIndex,
+ seek_comparator);
+ curr_key_idx_ =
+ static_cast<uint32_t>(std::distance(sorted_bucket_ids_.begin(), seek_it));
+ PrepareKVAtCurrIdx();
+}
+
+void CuckooTableIterator::SeekForPrev(const Slice& /*target*/) {
+ // Not supported
+ assert(false);
+}
+
+bool CuckooTableIterator::Valid() const {
+ return curr_key_idx_ < sorted_bucket_ids_.size();
+}
+
+void CuckooTableIterator::PrepareKVAtCurrIdx() {
+ if (!Valid()) {
+ curr_value_.clear();
+ curr_key_.Clear();
+ return;
+ }
+ uint32_t id = sorted_bucket_ids_[curr_key_idx_];
+ const char* offset = reader_->file_data_.data() +
+ id * reader_->bucket_length_;
+ if (reader_->is_last_level_) {
+ // Always return internal key.
+ curr_key_.SetInternalKey(Slice(offset, reader_->user_key_length_),
+ 0, kTypeValue);
+ } else {
+ curr_key_.SetInternalKey(Slice(offset, reader_->key_length_));
+ }
+ curr_value_ = Slice(offset + reader_->key_length_, reader_->value_length_);
+}
+
+void CuckooTableIterator::Next() {
+ if (!Valid()) {
+ curr_value_.clear();
+ curr_key_.Clear();
+ return;
+ }
+ ++curr_key_idx_;
+ PrepareKVAtCurrIdx();
+}
+
+void CuckooTableIterator::Prev() {
+ if (curr_key_idx_ == 0) {
+ curr_key_idx_ = static_cast<uint32_t>(sorted_bucket_ids_.size());
+ }
+ if (!Valid()) {
+ curr_value_.clear();
+ curr_key_.Clear();
+ return;
+ }
+ --curr_key_idx_;
+ PrepareKVAtCurrIdx();
+}
+
+Slice CuckooTableIterator::key() const {
+ assert(Valid());
+ return curr_key_.GetInternalKey();
+}
+
+Slice CuckooTableIterator::value() const {
+ assert(Valid());
+ return curr_value_;
+}
+
+InternalIterator* CuckooTableReader::NewIterator(
+ const ReadOptions& /*read_options*/,
+ const SliceTransform* /* prefix_extractor */, Arena* arena,
+ bool /*skip_filters*/, TableReaderCaller /*caller*/,
+ size_t /*compaction_readahead_size*/) {
+ if (!status().ok()) {
+ return NewErrorInternalIterator<Slice>(
+ Status::Corruption("CuckooTableReader status is not okay."), arena);
+ }
+ CuckooTableIterator* iter;
+ if (arena == nullptr) {
+ iter = new CuckooTableIterator(this);
+ } else {
+ auto iter_mem = arena->AllocateAligned(sizeof(CuckooTableIterator));
+ iter = new (iter_mem) CuckooTableIterator(this);
+ }
+ return iter;
+}
+
+size_t CuckooTableReader::ApproximateMemoryUsage() const { return 0; }
+
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.h b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.h
new file mode 100644
index 00000000..65bd13e1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+#include <string>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "file/random_access_file_reader.h"
+#include "options/cf_options.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "table/table_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Arena;
+class TableReader;
+
+class CuckooTableReader: public TableReader {
+ public:
+ CuckooTableReader(const ImmutableCFOptions& ioptions,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ uint64_t file_size, const Comparator* user_comparator,
+ uint64_t (*get_slice_hash)(const Slice&, uint32_t,
+ uint64_t));
+ ~CuckooTableReader() {}
+
+ std::shared_ptr<const TableProperties> GetTableProperties() const override {
+ return table_props_;
+ }
+
+ Status status() const { return status_; }
+
+ Status Get(const ReadOptions& readOptions, const Slice& key,
+ GetContext* get_context, const SliceTransform* prefix_extractor,
+ bool skip_filters = false) override;
+
+ // Returns a new iterator over table contents
+ // compaction_readahead_size: its value will only be used if for_compaction =
+ // true
+ InternalIterator* NewIterator(const ReadOptions&,
+ const SliceTransform* prefix_extractor,
+ Arena* arena, bool skip_filters,
+ TableReaderCaller caller,
+ size_t compaction_readahead_size = 0) override;
+ void Prepare(const Slice& target) override;
+
+ // Report an approximation of how much memory has been used.
+ size_t ApproximateMemoryUsage() const override;
+
+ // Following methods are not implemented for Cuckoo Table Reader
+ uint64_t ApproximateOffsetOf(const Slice& /*key*/,
+ TableReaderCaller /*caller*/) override {
+ return 0;
+ }
+
+ uint64_t ApproximateSize(const Slice& /*start*/, const Slice& /*end*/,
+ TableReaderCaller /*caller*/) override {
+ return 0;
+ }
+
+ void SetupForCompaction() override {}
+ // End of methods not implemented.
+
+ private:
+ friend class CuckooTableIterator;
+ void LoadAllKeys(std::vector<std::pair<Slice, uint32_t>>* key_to_bucket_id);
+ std::unique_ptr<RandomAccessFileReader> file_;
+ Slice file_data_;
+ bool is_last_level_;
+ bool identity_as_first_hash_;
+ bool use_module_hash_;
+ std::shared_ptr<const TableProperties> table_props_;
+ Status status_;
+ uint32_t num_hash_func_;
+ std::string unused_key_;
+ uint32_t key_length_;
+ uint32_t user_key_length_;
+ uint32_t value_length_;
+ uint32_t bucket_length_;
+ uint32_t cuckoo_block_size_;
+ uint32_t cuckoo_block_bytes_minus_one_;
+ uint64_t table_size_;
+ const Comparator* ucomp_;
+ uint64_t (*get_slice_hash_)(const Slice& s, uint32_t index,
+ uint64_t max_num_buckets);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader_test.cc b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader_test.cc
new file mode 100644
index 00000000..cbca768a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/cuckoo/cuckoo_table_reader_test.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run this test... Skipping...\n");
+ return 0;
+}
+#else
+
+#include <cinttypes>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "memory/arena.h"
+#include "table/cuckoo/cuckoo_table_builder.h"
+#include "table/cuckoo/cuckoo_table_factory.h"
+#include "table/cuckoo/cuckoo_table_reader.h"
+#include "table/get_context.h"
+#include "table/meta_blocks.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/gflags_compat.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_string(file_dir, "", "Directory where the files will be created"
+ " for benchmark. Added for using tmpfs.");
+DEFINE_bool(enable_perf, false, "Run Benchmark Tests too.");
+DEFINE_bool(write, false,
+ "Should write new values to file in performance tests?");
+DEFINE_bool(identity_as_first_hash, true, "use identity as first hash");
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+const uint32_t kNumHashFunc = 10;
+// Methods, variables related to Hash functions.
+std::unordered_map<std::string, std::vector<uint64_t>> hash_map;
+
+void AddHashLookups(const std::string& s, uint64_t bucket_id,
+ uint32_t num_hash_fun) {
+ std::vector<uint64_t> v;
+ for (uint32_t i = 0; i < num_hash_fun; i++) {
+ v.push_back(bucket_id + i);
+ }
+ hash_map[s] = v;
+}
+
+uint64_t GetSliceHash(const Slice& s, uint32_t index,
+ uint64_t /*max_num_buckets*/) {
+ return hash_map[s.ToString()][index];
+}
+} // namespace
+
+class CuckooReaderTest : public testing::Test {
+ public:
+ using testing::Test::SetUp;
+
+ CuckooReaderTest() {
+ options.allow_mmap_reads = true;
+ env = options.env;
+ env_options = EnvOptions(options);
+ }
+
+ void SetUp(int num) {
+ num_items = num;
+ hash_map.clear();
+ keys.clear();
+ keys.resize(num_items);
+ user_keys.clear();
+ user_keys.resize(num_items);
+ values.clear();
+ values.resize(num_items);
+ }
+
+ std::string NumToStr(int64_t i) {
+ return std::string(reinterpret_cast<char*>(&i), sizeof(i));
+ }
+
+ void CreateCuckooFileAndCheckReader(
+ const Comparator* ucomp = BytewiseComparator()) {
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ env_options));
+
+ CuckooTableBuilder builder(
+ file_writer.get(), 0.9, kNumHashFunc, 100, ucomp, 2, false, false,
+ GetSliceHash, 0 /* column_family_id */, kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint32_t key_idx = 0; key_idx < num_items; ++key_idx) {
+ builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
+ ASSERT_OK(builder.status());
+ ASSERT_EQ(builder.NumEntries(), key_idx + 1);
+ }
+ ASSERT_OK(builder.Finish());
+ ASSERT_EQ(num_items, builder.NumEntries());
+ file_size = builder.FileSize();
+ ASSERT_OK(file_writer->Close());
+
+ // Check reader now.
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+ const ImmutableCFOptions ioptions(options);
+ CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucomp,
+ GetSliceHash);
+ ASSERT_OK(reader.status());
+ // Assume no merge/deletion
+ for (uint32_t i = 0; i < num_items; ++i) {
+ PinnableSlice value;
+ GetContext get_context(ucomp, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, Slice(user_keys[i]), &value,
+ nullptr, nullptr, true, nullptr, nullptr);
+ ASSERT_OK(
+ reader.Get(ReadOptions(), Slice(keys[i]), &get_context, nullptr));
+ ASSERT_STREQ(values[i].c_str(), value.data());
+ }
+ }
+ void UpdateKeys(bool with_zero_seqno) {
+ for (uint32_t i = 0; i < num_items; i++) {
+ ParsedInternalKey ikey(user_keys[i],
+ with_zero_seqno ? 0 : i + 1000, kTypeValue);
+ keys[i].clear();
+ AppendInternalKey(&keys[i], ikey);
+ }
+ }
+
+ void CheckIterator(const Comparator* ucomp = BytewiseComparator()) {
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+ const ImmutableCFOptions ioptions(options);
+ CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucomp,
+ GetSliceHash);
+ ASSERT_OK(reader.status());
+ InternalIterator* it = reader.NewIterator(
+ ReadOptions(), /*prefix_extractor=*/nullptr, /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kUncategorized);
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(!it->Valid());
+ it->SeekToFirst();
+ int cnt = 0;
+ while (it->Valid()) {
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(Slice(keys[cnt]) == it->key());
+ ASSERT_TRUE(Slice(values[cnt]) == it->value());
+ ++cnt;
+ it->Next();
+ }
+ ASSERT_EQ(static_cast<uint32_t>(cnt), num_items);
+
+ it->SeekToLast();
+ cnt = static_cast<int>(num_items) - 1;
+ ASSERT_TRUE(it->Valid());
+ while (it->Valid()) {
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(Slice(keys[cnt]) == it->key());
+ ASSERT_TRUE(Slice(values[cnt]) == it->value());
+ --cnt;
+ it->Prev();
+ }
+ ASSERT_EQ(cnt, -1);
+
+ cnt = static_cast<int>(num_items) / 2;
+ it->Seek(keys[cnt]);
+ while (it->Valid()) {
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(Slice(keys[cnt]) == it->key());
+ ASSERT_TRUE(Slice(values[cnt]) == it->value());
+ ++cnt;
+ it->Next();
+ }
+ ASSERT_EQ(static_cast<uint32_t>(cnt), num_items);
+ delete it;
+
+ Arena arena;
+ it = reader.NewIterator(ReadOptions(), /*prefix_extractor=*/nullptr, &arena,
+ /*skip_filters=*/false,
+ TableReaderCaller::kUncategorized);
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(!it->Valid());
+ it->Seek(keys[num_items/2]);
+ ASSERT_TRUE(it->Valid());
+ ASSERT_OK(it->status());
+ ASSERT_TRUE(keys[num_items/2] == it->key());
+ ASSERT_TRUE(values[num_items/2] == it->value());
+ ASSERT_OK(it->status());
+ it->~InternalIterator();
+ }
+
+ std::vector<std::string> keys;
+ std::vector<std::string> user_keys;
+ std::vector<std::string> values;
+ uint64_t num_items;
+ std::string fname;
+ uint64_t file_size;
+ Options options;
+ Env* env;
+ EnvOptions env_options;
+};
+
+TEST_F(CuckooReaderTest, WhenKeyExists) {
+ SetUp(kNumHashFunc);
+ fname = test::PerThreadDBPath("CuckooReader_WhenKeyExists");
+ for (uint64_t i = 0; i < num_items; i++) {
+ user_keys[i] = "key" + NumToStr(i);
+ ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
+ AppendInternalKey(&keys[i], ikey);
+ values[i] = "value" + NumToStr(i);
+ // Give disjoint hash values.
+ AddHashLookups(user_keys[i], i, kNumHashFunc);
+ }
+ CreateCuckooFileAndCheckReader();
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader();
+ // Test with collision. Make all hash values collide.
+ hash_map.clear();
+ for (uint32_t i = 0; i < num_items; i++) {
+ AddHashLookups(user_keys[i], 0, kNumHashFunc);
+ }
+ UpdateKeys(false);
+ CreateCuckooFileAndCheckReader();
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader();
+}
+
+TEST_F(CuckooReaderTest, WhenKeyExistsWithUint64Comparator) {
+ SetUp(kNumHashFunc);
+ fname = test::PerThreadDBPath("CuckooReaderUint64_WhenKeyExists");
+ for (uint64_t i = 0; i < num_items; i++) {
+ user_keys[i].resize(8);
+ memcpy(&user_keys[i][0], static_cast<void*>(&i), 8);
+ ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
+ AppendInternalKey(&keys[i], ikey);
+ values[i] = "value" + NumToStr(i);
+ // Give disjoint hash values.
+ AddHashLookups(user_keys[i], i, kNumHashFunc);
+ }
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+ // Test with collision. Make all hash values collide.
+ hash_map.clear();
+ for (uint32_t i = 0; i < num_items; i++) {
+ AddHashLookups(user_keys[i], 0, kNumHashFunc);
+ }
+ UpdateKeys(false);
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+}
+
+TEST_F(CuckooReaderTest, CheckIterator) {
+ SetUp(2*kNumHashFunc);
+ fname = test::PerThreadDBPath("CuckooReader_CheckIterator");
+ for (uint64_t i = 0; i < num_items; i++) {
+ user_keys[i] = "key" + NumToStr(i);
+ ParsedInternalKey ikey(user_keys[i], 1000, kTypeValue);
+ AppendInternalKey(&keys[i], ikey);
+ values[i] = "value" + NumToStr(i);
+ // Give disjoint hash values, in reverse order.
+ AddHashLookups(user_keys[i], num_items-i-1, kNumHashFunc);
+ }
+ CreateCuckooFileAndCheckReader();
+ CheckIterator();
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader();
+ CheckIterator();
+}
+
+TEST_F(CuckooReaderTest, CheckIteratorUint64) {
+ SetUp(2*kNumHashFunc);
+ fname = test::PerThreadDBPath("CuckooReader_CheckIterator");
+ for (uint64_t i = 0; i < num_items; i++) {
+ user_keys[i].resize(8);
+ memcpy(&user_keys[i][0], static_cast<void*>(&i), 8);
+ ParsedInternalKey ikey(user_keys[i], 1000, kTypeValue);
+ AppendInternalKey(&keys[i], ikey);
+ values[i] = "value" + NumToStr(i);
+ // Give disjoint hash values, in reverse order.
+ AddHashLookups(user_keys[i], num_items-i-1, kNumHashFunc);
+ }
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+ CheckIterator(test::Uint64Comparator());
+ // Last level file.
+ UpdateKeys(true);
+ CreateCuckooFileAndCheckReader(test::Uint64Comparator());
+ CheckIterator(test::Uint64Comparator());
+}
+
+TEST_F(CuckooReaderTest, WhenKeyNotFound) {
+ // Add keys with colliding hash values.
+ SetUp(kNumHashFunc);
+ fname = test::PerThreadDBPath("CuckooReader_WhenKeyNotFound");
+ for (uint64_t i = 0; i < num_items; i++) {
+ user_keys[i] = "key" + NumToStr(i);
+ ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
+ AppendInternalKey(&keys[i], ikey);
+ values[i] = "value" + NumToStr(i);
+ // Make all hash values collide.
+ AddHashLookups(user_keys[i], 0, kNumHashFunc);
+ }
+ auto* ucmp = BytewiseComparator();
+ CreateCuckooFileAndCheckReader();
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+ const ImmutableCFOptions ioptions(options);
+ CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucmp,
+ GetSliceHash);
+ ASSERT_OK(reader.status());
+ // Search for a key with colliding hash values.
+ std::string not_found_user_key = "key" + NumToStr(num_items);
+ std::string not_found_key;
+ AddHashLookups(not_found_user_key, 0, kNumHashFunc);
+ ParsedInternalKey ikey(not_found_user_key, 1000, kTypeValue);
+ AppendInternalKey(&not_found_key, ikey);
+ PinnableSlice value;
+ GetContext get_context(ucmp, nullptr, nullptr, nullptr, GetContext::kNotFound,
+ Slice(not_found_key), &value, nullptr, nullptr, true,
+ nullptr, nullptr);
+ ASSERT_OK(
+ reader.Get(ReadOptions(), Slice(not_found_key), &get_context, nullptr));
+ ASSERT_TRUE(value.empty());
+ ASSERT_OK(reader.status());
+ // Search for a key with an independent hash value.
+ std::string not_found_user_key2 = "key" + NumToStr(num_items + 1);
+ AddHashLookups(not_found_user_key2, kNumHashFunc, kNumHashFunc);
+ ParsedInternalKey ikey2(not_found_user_key2, 1000, kTypeValue);
+ std::string not_found_key2;
+ AppendInternalKey(&not_found_key2, ikey2);
+ value.Reset();
+ GetContext get_context2(ucmp, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, Slice(not_found_key2), &value,
+ nullptr, nullptr, true, nullptr, nullptr);
+ ASSERT_OK(
+ reader.Get(ReadOptions(), Slice(not_found_key2), &get_context2, nullptr));
+ ASSERT_TRUE(value.empty());
+ ASSERT_OK(reader.status());
+
+ // Test read when key is unused key.
+ std::string unused_key =
+ reader.GetTableProperties()->user_collected_properties.at(
+ CuckooTablePropertyNames::kEmptyKey);
+ // Add hash values that map to empty buckets.
+ AddHashLookups(ExtractUserKey(unused_key).ToString(),
+ kNumHashFunc, kNumHashFunc);
+ value.Reset();
+ GetContext get_context3(ucmp, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, Slice(unused_key), &value,
+ nullptr, nullptr, true, nullptr, nullptr);
+ ASSERT_OK(
+ reader.Get(ReadOptions(), Slice(unused_key), &get_context3, nullptr));
+ ASSERT_TRUE(value.empty());
+ ASSERT_OK(reader.status());
+}
+
+// Performance tests
+namespace {
+void GetKeys(uint64_t num, std::vector<std::string>* keys) {
+ keys->clear();
+ IterKey k;
+ k.SetInternalKey("", 0, kTypeValue);
+ std::string internal_key_suffix = k.GetInternalKey().ToString();
+ ASSERT_EQ(static_cast<size_t>(8), internal_key_suffix.size());
+ for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
+ uint64_t value = 2 * key_idx;
+ std::string new_key(reinterpret_cast<char*>(&value), sizeof(value));
+ new_key += internal_key_suffix;
+ keys->push_back(new_key);
+ }
+}
+
+std::string GetFileName(uint64_t num) {
+ if (FLAGS_file_dir.empty()) {
+ FLAGS_file_dir = test::TmpDir();
+ }
+ return test::PerThreadDBPath(FLAGS_file_dir, "cuckoo_read_benchmark") +
+ ToString(num / 1000000) + "Mkeys";
+}
+
+// Create last level file as we are interested in measuring performance of
+// last level file only.
+void WriteFile(const std::vector<std::string>& keys,
+ const uint64_t num, double hash_ratio) {
+ Options options;
+ options.allow_mmap_reads = true;
+ Env* env = options.env;
+ EnvOptions env_options = EnvOptions(options);
+ std::string fname = GetFileName(num);
+
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(writable_file)), fname,
+ env_options));
+ CuckooTableBuilder builder(
+ file_writer.get(), hash_ratio, 64, 1000, test::Uint64Comparator(), 5,
+ false, FLAGS_identity_as_first_hash, nullptr, 0 /* column_family_id */,
+ kDefaultColumnFamilyName);
+ ASSERT_OK(builder.status());
+ for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
+ // Value is just a part of key.
+ builder.Add(Slice(keys[key_idx]), Slice(&keys[key_idx][0], 4));
+ ASSERT_EQ(builder.NumEntries(), key_idx + 1);
+ ASSERT_OK(builder.status());
+ }
+ ASSERT_OK(builder.Finish());
+ ASSERT_EQ(num, builder.NumEntries());
+ ASSERT_OK(file_writer->Close());
+
+ uint64_t file_size;
+ env->GetFileSize(fname, &file_size);
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+
+ const ImmutableCFOptions ioptions(options);
+ CuckooTableReader reader(ioptions, std::move(file_reader), file_size,
+ test::Uint64Comparator(), nullptr);
+ ASSERT_OK(reader.status());
+ ReadOptions r_options;
+ PinnableSlice value;
+ // Assume only the fast path is triggered
+ GetContext get_context(nullptr, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, Slice(), &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+ for (uint64_t i = 0; i < num; ++i) {
+ value.Reset();
+ value.clear();
+ ASSERT_OK(reader.Get(r_options, Slice(keys[i]), &get_context, nullptr));
+ ASSERT_TRUE(Slice(keys[i]) == Slice(&keys[i][0], 4));
+ }
+}
+
+void ReadKeys(uint64_t num, uint32_t batch_size) {
+ Options options;
+ options.allow_mmap_reads = true;
+ Env* env = options.env;
+ EnvOptions env_options = EnvOptions(options);
+ std::string fname = GetFileName(num);
+
+ uint64_t file_size;
+ env->GetFileSize(fname, &file_size);
+ std::unique_ptr<RandomAccessFile> read_file;
+ ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(read_file),
+ fname));
+
+ const ImmutableCFOptions ioptions(options);
+ CuckooTableReader reader(ioptions, std::move(file_reader), file_size,
+ test::Uint64Comparator(), nullptr);
+ ASSERT_OK(reader.status());
+ const UserCollectedProperties user_props =
+ reader.GetTableProperties()->user_collected_properties;
+ const uint32_t num_hash_fun = *reinterpret_cast<const uint32_t*>(
+ user_props.at(CuckooTablePropertyNames::kNumHashFunc).data());
+ const uint64_t table_size = *reinterpret_cast<const uint64_t*>(
+ user_props.at(CuckooTablePropertyNames::kHashTableSize).data());
+ fprintf(stderr, "With %" PRIu64 " items, utilization is %.2f%%, number of"
+ " hash functions: %u.\n", num, num * 100.0 / (table_size), num_hash_fun);
+ ReadOptions r_options;
+
+ std::vector<uint64_t> keys;
+ keys.reserve(num);
+ for (uint64_t i = 0; i < num; ++i) {
+ keys.push_back(2 * i);
+ }
+ std::random_shuffle(keys.begin(), keys.end());
+
+ PinnableSlice value;
+ // Assume only the fast path is triggered
+ GetContext get_context(nullptr, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, Slice(), &value, nullptr,
+ nullptr, true, nullptr, nullptr);
+ uint64_t start_time = env->NowMicros();
+ if (batch_size > 0) {
+ for (uint64_t i = 0; i < num; i += batch_size) {
+ for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
+ reader.Prepare(Slice(reinterpret_cast<char*>(&keys[j]), 16));
+ }
+ for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
+ reader.Get(r_options, Slice(reinterpret_cast<char*>(&keys[j]), 16),
+ &get_context, nullptr);
+ }
+ }
+ } else {
+ for (uint64_t i = 0; i < num; i++) {
+ reader.Get(r_options, Slice(reinterpret_cast<char*>(&keys[i]), 16),
+ &get_context, nullptr);
+ }
+ }
+ float time_per_op = (env->NowMicros() - start_time) * 1.0f / num;
+ fprintf(stderr,
+ "Time taken per op is %.3fus (%.1f Mqps) with batch size of %u\n",
+ time_per_op, 1.0 / time_per_op, batch_size);
+}
+} // namespace.
+
+TEST_F(CuckooReaderTest, TestReadPerformance) {
+ if (!FLAGS_enable_perf) {
+ return;
+ }
+ double hash_ratio = 0.95;
+ // These numbers are chosen to have a hash utilization % close to
+ // 0.9, 0.75, 0.6 and 0.5 respectively.
+ // They all create 128 M buckets.
+ std::vector<uint64_t> nums = {120*1024*1024, 100*1024*1024, 80*1024*1024,
+ 70*1024*1024};
+#ifndef NDEBUG
+ fprintf(stdout,
+ "WARNING: Not compiled with DNDEBUG. Performance tests may be slow.\n");
+#endif
+ for (uint64_t num : nums) {
+ if (FLAGS_write ||
+ Env::Default()->FileExists(GetFileName(num)).IsNotFound()) {
+ std::vector<std::string> all_keys;
+ GetKeys(num, &all_keys);
+ WriteFile(all_keys, num, hash_ratio);
+ }
+ ReadKeys(num, 0);
+ ReadKeys(num, 10);
+ ReadKeys(num, 25);
+ ReadKeys(num, 50);
+ ReadKeys(num, 100);
+ fprintf(stderr, "\n");
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ if (ROCKSDB_NAMESPACE::port::kLittleEndian) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ParseCommandLineFlags(&argc, &argv, true);
+ return RUN_ALL_TESTS();
+ } else {
+ fprintf(stderr, "SKIPPED as Cuckoo table doesn't support Big Endian\n");
+ return 0;
+ }
+}
+
+#endif // GFLAGS.
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/format.cc b/storage/rocksdb/rocksdb/table/format.cc
new file mode 100644
index 00000000..ee3766eb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/format.cc
@@ -0,0 +1,465 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/format.h"
+
+#include <cinttypes>
+#include <string>
+
+#include "block_fetcher.h"
+#include "file/random_access_file_reader.h"
+#include "logging/logging.h"
+#include "memory/memory_allocator.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/env.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/persistent_cache_helper.h"
+#include "util/coding.h"
+#include "util/compression.h"
+#include "util/crc32c.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const uint64_t kLegacyBlockBasedTableMagicNumber;
+extern const uint64_t kBlockBasedTableMagicNumber;
+
+#ifndef ROCKSDB_LITE
+extern const uint64_t kLegacyPlainTableMagicNumber;
+extern const uint64_t kPlainTableMagicNumber;
+#else
+// ROCKSDB_LITE doesn't have plain table
+const uint64_t kLegacyPlainTableMagicNumber = 0;
+const uint64_t kPlainTableMagicNumber = 0;
+#endif
+
+bool ShouldReportDetailedTime(Env* env, Statistics* stats) {
+ return env != nullptr && stats != nullptr &&
+ stats->get_stats_level() > kExceptDetailedTimers;
+}
+
+void BlockHandle::EncodeTo(std::string* dst) const {
+ // Sanity check that all fields have been set
+ assert(offset_ != ~static_cast<uint64_t>(0));
+ assert(size_ != ~static_cast<uint64_t>(0));
+ PutVarint64Varint64(dst, offset_, size_);
+}
+
+Status BlockHandle::DecodeFrom(Slice* input) {
+ if (GetVarint64(input, &offset_) && GetVarint64(input, &size_)) {
+ return Status::OK();
+ } else {
+ // reset in case failure after partially decoding
+ offset_ = 0;
+ size_ = 0;
+ return Status::Corruption("bad block handle");
+ }
+}
+
+Status BlockHandle::DecodeSizeFrom(uint64_t _offset, Slice* input) {
+ if (GetVarint64(input, &size_)) {
+ offset_ = _offset;
+ return Status::OK();
+ } else {
+ // reset in case failure after partially decoding
+ offset_ = 0;
+ size_ = 0;
+ return Status::Corruption("bad block handle");
+ }
+}
+
+// Return a string that contains the copy of handle.
+std::string BlockHandle::ToString(bool hex) const {
+ std::string handle_str;
+ EncodeTo(&handle_str);
+ if (hex) {
+ return Slice(handle_str).ToString(true);
+ } else {
+ return handle_str;
+ }
+}
+
+const BlockHandle BlockHandle::kNullBlockHandle(0, 0);
+
+void IndexValue::EncodeTo(std::string* dst, bool have_first_key,
+ const BlockHandle* previous_handle) const {
+ if (previous_handle) {
+ assert(handle.offset() == previous_handle->offset() +
+ previous_handle->size() + kBlockTrailerSize);
+ PutVarsignedint64(dst, handle.size() - previous_handle->size());
+ } else {
+ handle.EncodeTo(dst);
+ }
+ assert(dst->size() != 0);
+
+ if (have_first_key) {
+ PutLengthPrefixedSlice(dst, first_internal_key);
+ }
+}
+
+Status IndexValue::DecodeFrom(Slice* input, bool have_first_key,
+ const BlockHandle* previous_handle) {
+ if (previous_handle) {
+ int64_t delta;
+ if (!GetVarsignedint64(input, &delta)) {
+ return Status::Corruption("bad delta-encoded index value");
+ }
+ handle = BlockHandle(
+ previous_handle->offset() + previous_handle->size() + kBlockTrailerSize,
+ previous_handle->size() + delta);
+ } else {
+ Status s = handle.DecodeFrom(input);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ if (!have_first_key) {
+ first_internal_key = Slice();
+ } else if (!GetLengthPrefixedSlice(input, &first_internal_key)) {
+ return Status::Corruption("bad first key in block info");
+ }
+
+ return Status::OK();
+}
+
+std::string IndexValue::ToString(bool hex, bool have_first_key) const {
+ std::string s;
+ EncodeTo(&s, have_first_key, nullptr);
+ if (hex) {
+ return Slice(s).ToString(true);
+ } else {
+ return s;
+ }
+}
+
+namespace {
+inline bool IsLegacyFooterFormat(uint64_t magic_number) {
+ return magic_number == kLegacyBlockBasedTableMagicNumber ||
+ magic_number == kLegacyPlainTableMagicNumber;
+}
+inline uint64_t UpconvertLegacyFooterFormat(uint64_t magic_number) {
+ if (magic_number == kLegacyBlockBasedTableMagicNumber) {
+ return kBlockBasedTableMagicNumber;
+ }
+ if (magic_number == kLegacyPlainTableMagicNumber) {
+ return kPlainTableMagicNumber;
+ }
+ assert(false);
+ return 0;
+}
+} // namespace
+
+// legacy footer format:
+// metaindex handle (varint64 offset, varint64 size)
+// index handle (varint64 offset, varint64 size)
+// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength
+// table_magic_number (8 bytes)
+// new footer format:
+// checksum type (char, 1 byte)
+// metaindex handle (varint64 offset, varint64 size)
+// index handle (varint64 offset, varint64 size)
+// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength + 1
+// footer version (4 bytes)
+// table_magic_number (8 bytes)
+void Footer::EncodeTo(std::string* dst) const {
+ assert(HasInitializedTableMagicNumber());
+ if (IsLegacyFooterFormat(table_magic_number())) {
+ // has to be default checksum with legacy footer
+ assert(checksum_ == kCRC32c);
+ const size_t original_size = dst->size();
+ metaindex_handle_.EncodeTo(dst);
+ index_handle_.EncodeTo(dst);
+ dst->resize(original_size + 2 * BlockHandle::kMaxEncodedLength); // Padding
+ PutFixed32(dst, static_cast<uint32_t>(table_magic_number() & 0xffffffffu));
+ PutFixed32(dst, static_cast<uint32_t>(table_magic_number() >> 32));
+ assert(dst->size() == original_size + kVersion0EncodedLength);
+ } else {
+ const size_t original_size = dst->size();
+ dst->push_back(static_cast<char>(checksum_));
+ metaindex_handle_.EncodeTo(dst);
+ index_handle_.EncodeTo(dst);
+ dst->resize(original_size + kNewVersionsEncodedLength - 12); // Padding
+ PutFixed32(dst, version());
+ PutFixed32(dst, static_cast<uint32_t>(table_magic_number() & 0xffffffffu));
+ PutFixed32(dst, static_cast<uint32_t>(table_magic_number() >> 32));
+ assert(dst->size() == original_size + kNewVersionsEncodedLength);
+ }
+}
+
+Footer::Footer(uint64_t _table_magic_number, uint32_t _version)
+ : version_(_version),
+ checksum_(kCRC32c),
+ table_magic_number_(_table_magic_number) {
+ // This should be guaranteed by constructor callers
+ assert(!IsLegacyFooterFormat(_table_magic_number) || version_ == 0);
+}
+
+Status Footer::DecodeFrom(Slice* input) {
+ assert(!HasInitializedTableMagicNumber());
+ assert(input != nullptr);
+ assert(input->size() >= kMinEncodedLength);
+
+ const char* magic_ptr =
+ input->data() + input->size() - kMagicNumberLengthByte;
+ const uint32_t magic_lo = DecodeFixed32(magic_ptr);
+ const uint32_t magic_hi = DecodeFixed32(magic_ptr + 4);
+ uint64_t magic = ((static_cast<uint64_t>(magic_hi) << 32) |
+ (static_cast<uint64_t>(magic_lo)));
+
+ // We check for legacy formats here and silently upconvert them
+ bool legacy = IsLegacyFooterFormat(magic);
+ if (legacy) {
+ magic = UpconvertLegacyFooterFormat(magic);
+ }
+ set_table_magic_number(magic);
+
+ if (legacy) {
+ // The size is already asserted to be at least kMinEncodedLength
+ // at the beginning of the function
+ input->remove_prefix(input->size() - kVersion0EncodedLength);
+ version_ = 0 /* legacy */;
+ checksum_ = kCRC32c;
+ } else {
+ version_ = DecodeFixed32(magic_ptr - 4);
+ // Footer version 1 and higher will always occupy exactly this many bytes.
+ // It consists of the checksum type, two block handles, padding,
+ // a version number, and a magic number
+ if (input->size() < kNewVersionsEncodedLength) {
+ return Status::Corruption("input is too short to be an sstable");
+ } else {
+ input->remove_prefix(input->size() - kNewVersionsEncodedLength);
+ }
+ uint32_t chksum;
+ if (!GetVarint32(input, &chksum)) {
+ return Status::Corruption("bad checksum type");
+ }
+ checksum_ = static_cast<ChecksumType>(chksum);
+ }
+
+ Status result = metaindex_handle_.DecodeFrom(input);
+ if (result.ok()) {
+ result = index_handle_.DecodeFrom(input);
+ }
+ if (result.ok()) {
+ // We skip over any leftover data (just padding for now) in "input"
+ const char* end = magic_ptr + kMagicNumberLengthByte;
+ *input = Slice(end, input->data() + input->size() - end);
+ }
+ return result;
+}
+
+std::string Footer::ToString() const {
+ std::string result;
+ result.reserve(1024);
+
+ bool legacy = IsLegacyFooterFormat(table_magic_number_);
+ if (legacy) {
+ result.append("metaindex handle: " + metaindex_handle_.ToString() + "\n ");
+ result.append("index handle: " + index_handle_.ToString() + "\n ");
+ result.append("table_magic_number: " +
+ ROCKSDB_NAMESPACE::ToString(table_magic_number_) + "\n ");
+ } else {
+ result.append("checksum: " + ROCKSDB_NAMESPACE::ToString(checksum_) +
+ "\n ");
+ result.append("metaindex handle: " + metaindex_handle_.ToString() + "\n ");
+ result.append("index handle: " + index_handle_.ToString() + "\n ");
+ result.append("footer version: " + ROCKSDB_NAMESPACE::ToString(version_) +
+ "\n ");
+ result.append("table_magic_number: " +
+ ROCKSDB_NAMESPACE::ToString(table_magic_number_) + "\n ");
+ }
+ return result;
+}
+
+Status ReadFooterFromFile(RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer,
+ uint64_t file_size, Footer* footer,
+ uint64_t enforce_table_magic_number) {
+ if (file_size < Footer::kMinEncodedLength) {
+ return Status::Corruption("file is too short (" + ToString(file_size) +
+ " bytes) to be an "
+ "sstable: " +
+ file->file_name());
+ }
+
+ char footer_space[Footer::kMaxEncodedLength];
+ Slice footer_input;
+ size_t read_offset =
+ (file_size > Footer::kMaxEncodedLength)
+ ? static_cast<size_t>(file_size - Footer::kMaxEncodedLength)
+ : 0;
+ Status s;
+ if (prefetch_buffer == nullptr ||
+ !prefetch_buffer->TryReadFromCache(read_offset, Footer::kMaxEncodedLength,
+ &footer_input)) {
+ s = file->Read(read_offset, Footer::kMaxEncodedLength, &footer_input,
+ footer_space);
+ if (!s.ok()) return s;
+ }
+
+ // Check that we actually read the whole footer from the file. It may be
+ // that size isn't correct.
+ if (footer_input.size() < Footer::kMinEncodedLength) {
+ return Status::Corruption("file is too short (" + ToString(file_size) +
+ " bytes) to be an "
+ "sstable" +
+ file->file_name());
+ }
+
+ s = footer->DecodeFrom(&footer_input);
+ if (!s.ok()) {
+ return s;
+ }
+ if (enforce_table_magic_number != 0 &&
+ enforce_table_magic_number != footer->table_magic_number()) {
+ return Status::Corruption(
+ "Bad table magic number: expected " +
+ ToString(enforce_table_magic_number) + ", found " +
+ ToString(footer->table_magic_number()) + " in " + file->file_name());
+ }
+ return Status::OK();
+}
+
+Status UncompressBlockContentsForCompressionType(
+ const UncompressionInfo& uncompression_info, const char* data, size_t n,
+ BlockContents* contents, uint32_t format_version,
+ const ImmutableCFOptions& ioptions, MemoryAllocator* allocator) {
+ CacheAllocationPtr ubuf;
+
+ assert(uncompression_info.type() != kNoCompression &&
+ "Invalid compression type");
+
+ StopWatchNano timer(ioptions.env, ShouldReportDetailedTime(
+ ioptions.env, ioptions.statistics));
+ int decompress_size = 0;
+ switch (uncompression_info.type()) {
+ case kSnappyCompression: {
+ size_t ulength = 0;
+ static char snappy_corrupt_msg[] =
+ "Snappy not supported or corrupted Snappy compressed block contents";
+ if (!Snappy_GetUncompressedLength(data, n, &ulength)) {
+ return Status::Corruption(snappy_corrupt_msg);
+ }
+ ubuf = AllocateBlock(ulength, allocator);
+ if (!Snappy_Uncompress(data, n, ubuf.get())) {
+ return Status::Corruption(snappy_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), ulength);
+ break;
+ }
+ case kZlibCompression:
+ ubuf = Zlib_Uncompress(
+ uncompression_info, data, n, &decompress_size,
+ GetCompressFormatForVersion(kZlibCompression, format_version),
+ allocator);
+ if (!ubuf) {
+ static char zlib_corrupt_msg[] =
+ "Zlib not supported or corrupted Zlib compressed block contents";
+ return Status::Corruption(zlib_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ case kBZip2Compression:
+ ubuf = BZip2_Uncompress(
+ data, n, &decompress_size,
+ GetCompressFormatForVersion(kBZip2Compression, format_version),
+ allocator);
+ if (!ubuf) {
+ static char bzip2_corrupt_msg[] =
+ "Bzip2 not supported or corrupted Bzip2 compressed block contents";
+ return Status::Corruption(bzip2_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ case kLZ4Compression:
+ ubuf = LZ4_Uncompress(
+ uncompression_info, data, n, &decompress_size,
+ GetCompressFormatForVersion(kLZ4Compression, format_version),
+ allocator);
+ if (!ubuf) {
+ static char lz4_corrupt_msg[] =
+ "LZ4 not supported or corrupted LZ4 compressed block contents";
+ return Status::Corruption(lz4_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ case kLZ4HCCompression:
+ ubuf = LZ4_Uncompress(
+ uncompression_info, data, n, &decompress_size,
+ GetCompressFormatForVersion(kLZ4HCCompression, format_version),
+ allocator);
+ if (!ubuf) {
+ static char lz4hc_corrupt_msg[] =
+ "LZ4HC not supported or corrupted LZ4HC compressed block contents";
+ return Status::Corruption(lz4hc_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ case kXpressCompression:
+ // XPRESS allocates memory internally, thus no support for custom
+ // allocator.
+ ubuf.reset(XPRESS_Uncompress(data, n, &decompress_size));
+ if (!ubuf) {
+ static char xpress_corrupt_msg[] =
+ "XPRESS not supported or corrupted XPRESS compressed block "
+ "contents";
+ return Status::Corruption(xpress_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ case kZSTD:
+ case kZSTDNotFinalCompression:
+ ubuf = ZSTD_Uncompress(uncompression_info, data, n, &decompress_size,
+ allocator);
+ if (!ubuf) {
+ static char zstd_corrupt_msg[] =
+ "ZSTD not supported or corrupted ZSTD compressed block contents";
+ return Status::Corruption(zstd_corrupt_msg);
+ }
+ *contents = BlockContents(std::move(ubuf), decompress_size);
+ break;
+ default:
+ return Status::Corruption("bad block type");
+ }
+
+ if (ShouldReportDetailedTime(ioptions.env, ioptions.statistics)) {
+ RecordTimeToHistogram(ioptions.statistics, DECOMPRESSION_TIMES_NANOS,
+ timer.ElapsedNanos());
+ }
+ RecordTimeToHistogram(ioptions.statistics, BYTES_DECOMPRESSED,
+ contents->data.size());
+ RecordTick(ioptions.statistics, NUMBER_BLOCK_DECOMPRESSED);
+
+ return Status::OK();
+}
+
+//
+// The 'data' points to the raw block contents that was read in from file.
+// This method allocates a new heap buffer and the raw block
+// contents are uncompresed into this buffer. This
+// buffer is returned via 'result' and it is upto the caller to
+// free this buffer.
+// format_version is the block format as defined in include/rocksdb/table.h
+Status UncompressBlockContents(const UncompressionInfo& uncompression_info,
+ const char* data, size_t n,
+ BlockContents* contents, uint32_t format_version,
+ const ImmutableCFOptions& ioptions,
+ MemoryAllocator* allocator) {
+ assert(data[n] != kNoCompression);
+ assert(data[n] == uncompression_info.type());
+ return UncompressBlockContentsForCompressionType(uncompression_info, data, n,
+ contents, format_version,
+ ioptions, allocator);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/format.h b/storage/rocksdb/rocksdb/table/format.h
new file mode 100644
index 00000000..ad65fdbf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/format.h
@@ -0,0 +1,344 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+#include "file/file_prefetch_buffer.h"
+#include "file/random_access_file_reader.h"
+
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+
+#include "memory/memory_allocator.h"
+#include "options/cf_options.h"
+#include "port/malloc.h"
+#include "port/port.h" // noexcept
+#include "table/persistent_cache_options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RandomAccessFile;
+struct ReadOptions;
+
+extern bool ShouldReportDetailedTime(Env* env, Statistics* stats);
+
+// the length of the magic number in bytes.
+const int kMagicNumberLengthByte = 8;
+
+// BlockHandle is a pointer to the extent of a file that stores a data
+// block or a meta block.
+class BlockHandle {
+ public:
+ BlockHandle();
+ BlockHandle(uint64_t offset, uint64_t size);
+
+ // The offset of the block in the file.
+ uint64_t offset() const { return offset_; }
+ void set_offset(uint64_t _offset) { offset_ = _offset; }
+
+ // The size of the stored block
+ uint64_t size() const { return size_; }
+ void set_size(uint64_t _size) { size_ = _size; }
+
+ void EncodeTo(std::string* dst) const;
+ Status DecodeFrom(Slice* input);
+ Status DecodeSizeFrom(uint64_t offset, Slice* input);
+
+ // Return a string that contains the copy of handle.
+ std::string ToString(bool hex = true) const;
+
+ // if the block handle's offset and size are both "0", we will view it
+ // as a null block handle that points to no where.
+ bool IsNull() const { return offset_ == 0 && size_ == 0; }
+
+ static const BlockHandle& NullBlockHandle() { return kNullBlockHandle; }
+
+ // Maximum encoding length of a BlockHandle
+ enum { kMaxEncodedLength = 10 + 10 };
+
+ private:
+ uint64_t offset_;
+ uint64_t size_;
+
+ static const BlockHandle kNullBlockHandle;
+};
+
+// Value in block-based table file index.
+//
+// The index entry for block n is: y -> h, [x],
+// where: y is some key between the last key of block n (inclusive) and the
+// first key of block n+1 (exclusive); h is BlockHandle pointing to block n;
+// x, if present, is the first key of block n (unshortened).
+// This struct represents the "h, [x]" part.
+struct IndexValue {
+ BlockHandle handle;
+ // Empty means unknown.
+ Slice first_internal_key;
+
+ IndexValue() = default;
+ IndexValue(BlockHandle _handle, Slice _first_internal_key)
+ : handle(_handle), first_internal_key(_first_internal_key) {}
+
+ // have_first_key indicates whether the `first_internal_key` is used.
+ // If previous_handle is not null, delta encoding is used;
+ // in this case, the two handles must point to consecutive blocks:
+ // handle.offset() ==
+ // previous_handle->offset() + previous_handle->size() + kBlockTrailerSize
+ void EncodeTo(std::string* dst, bool have_first_key,
+ const BlockHandle* previous_handle) const;
+ Status DecodeFrom(Slice* input, bool have_first_key,
+ const BlockHandle* previous_handle);
+
+ std::string ToString(bool hex, bool have_first_key) const;
+};
+
+inline uint32_t GetCompressFormatForVersion(CompressionType compression_type,
+ uint32_t version) {
+#ifdef NDEBUG
+ (void)compression_type;
+#endif
+ // snappy is not versioned
+ assert(compression_type != kSnappyCompression &&
+ compression_type != kXpressCompression &&
+ compression_type != kNoCompression);
+ // As of version 2, we encode compressed block with
+ // compress_format_version == 2. Before that, the version is 1.
+ // DO NOT CHANGE THIS FUNCTION, it affects disk format
+ return version >= 2 ? 2 : 1;
+}
+
+inline bool BlockBasedTableSupportedVersion(uint32_t version) {
+ return version <= 5;
+}
+
+// Footer encapsulates the fixed information stored at the tail
+// end of every table file.
+class Footer {
+ public:
+ // Constructs a footer without specifying its table magic number.
+ // In such case, the table magic number of such footer should be
+ // initialized via @ReadFooterFromFile().
+ // Use this when you plan to load Footer with DecodeFrom(). Never use this
+ // when you plan to EncodeTo.
+ Footer() : Footer(kInvalidTableMagicNumber, 0) {}
+
+ // Use this constructor when you plan to write out the footer using
+ // EncodeTo(). Never use this constructor with DecodeFrom().
+ Footer(uint64_t table_magic_number, uint32_t version);
+
+ // The version of the footer in this file
+ uint32_t version() const { return version_; }
+
+ // The checksum type used in this file
+ ChecksumType checksum() const { return checksum_; }
+ void set_checksum(const ChecksumType c) { checksum_ = c; }
+
+ // The block handle for the metaindex block of the table
+ const BlockHandle& metaindex_handle() const { return metaindex_handle_; }
+ void set_metaindex_handle(const BlockHandle& h) { metaindex_handle_ = h; }
+
+ // The block handle for the index block of the table
+ const BlockHandle& index_handle() const { return index_handle_; }
+
+ void set_index_handle(const BlockHandle& h) { index_handle_ = h; }
+
+ uint64_t table_magic_number() const { return table_magic_number_; }
+
+ void EncodeTo(std::string* dst) const;
+
+ // Set the current footer based on the input slice.
+ //
+ // REQUIRES: table_magic_number_ is not set (i.e.,
+ // HasInitializedTableMagicNumber() is true). The function will initialize the
+ // magic number
+ Status DecodeFrom(Slice* input);
+
+ // Encoded length of a Footer. Note that the serialization of a Footer will
+ // always occupy at least kMinEncodedLength bytes. If fields are changed
+ // the version number should be incremented and kMaxEncodedLength should be
+ // increased accordingly.
+ enum {
+ // Footer version 0 (legacy) will always occupy exactly this many bytes.
+ // It consists of two block handles, padding, and a magic number.
+ kVersion0EncodedLength = 2 * BlockHandle::kMaxEncodedLength + 8,
+ // Footer of versions 1 and higher will always occupy exactly this many
+ // bytes. It consists of the checksum type, two block handles, padding,
+ // a version number (bigger than 1), and a magic number
+ kNewVersionsEncodedLength = 1 + 2 * BlockHandle::kMaxEncodedLength + 4 + 8,
+ kMinEncodedLength = kVersion0EncodedLength,
+ kMaxEncodedLength = kNewVersionsEncodedLength,
+ };
+
+ static const uint64_t kInvalidTableMagicNumber = 0;
+
+ // convert this object to a human readable form
+ std::string ToString() const;
+
+ private:
+ // REQUIRES: magic number wasn't initialized.
+ void set_table_magic_number(uint64_t magic_number) {
+ assert(!HasInitializedTableMagicNumber());
+ table_magic_number_ = magic_number;
+ }
+
+ // return true if @table_magic_number_ is set to a value different
+ // from @kInvalidTableMagicNumber.
+ bool HasInitializedTableMagicNumber() const {
+ return (table_magic_number_ != kInvalidTableMagicNumber);
+ }
+
+ uint32_t version_;
+ ChecksumType checksum_;
+ BlockHandle metaindex_handle_;
+ BlockHandle index_handle_;
+ uint64_t table_magic_number_ = 0;
+};
+
+// Read the footer from file
+// If enforce_table_magic_number != 0, ReadFooterFromFile() will return
+// corruption if table_magic number is not equal to enforce_table_magic_number
+Status ReadFooterFromFile(RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer,
+ uint64_t file_size, Footer* footer,
+ uint64_t enforce_table_magic_number = 0);
+
+// 1-byte type + 32-bit crc
+static const size_t kBlockTrailerSize = 5;
+
+// Make block size calculation for IO less error prone
+inline uint64_t block_size(const BlockHandle& handle) {
+ return handle.size() + kBlockTrailerSize;
+}
+
+inline CompressionType get_block_compression_type(const char* block_data,
+ size_t block_size) {
+ return static_cast<CompressionType>(block_data[block_size]);
+}
+
+// Represents the contents of a block read from an SST file. Depending on how
+// it's created, it may or may not own the actual block bytes. As an example,
+// BlockContents objects representing data read from mmapped files only point
+// into the mmapped region.
+struct BlockContents {
+ Slice data; // Actual contents of data
+ CacheAllocationPtr allocation;
+
+#ifndef NDEBUG
+ // Whether the block is a raw block, which contains compression type
+ // byte. It is only used for assertion.
+ bool is_raw_block = false;
+#endif // NDEBUG
+
+ BlockContents() {}
+
+ // Does not take ownership of the underlying data bytes.
+ BlockContents(const Slice& _data) : data(_data) {}
+
+ // Takes ownership of the underlying data bytes.
+ BlockContents(CacheAllocationPtr&& _data, size_t _size)
+ : data(_data.get(), _size), allocation(std::move(_data)) {}
+
+ // Takes ownership of the underlying data bytes.
+ BlockContents(std::unique_ptr<char[]>&& _data, size_t _size)
+ : data(_data.get(), _size) {
+ allocation.reset(_data.release());
+ }
+
+ // Returns whether the object has ownership of the underlying data bytes.
+ bool own_bytes() const { return allocation.get() != nullptr; }
+
+ // It's the caller's responsibility to make sure that this is
+ // for raw block contents, which contains the compression
+ // byte in the end.
+ CompressionType get_compression_type() const {
+ assert(is_raw_block);
+ return get_block_compression_type(data.data(), data.size());
+ }
+
+ // The additional memory space taken by the block data.
+ size_t usable_size() const {
+ if (allocation.get() != nullptr) {
+ auto allocator = allocation.get_deleter().allocator;
+ if (allocator) {
+ return allocator->UsableSize(allocation.get(), data.size());
+ }
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+ return malloc_usable_size(allocation.get());
+#else
+ return data.size();
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+ } else {
+ return 0; // no extra memory is occupied by the data
+ }
+ }
+
+ size_t ApproximateMemoryUsage() const {
+ return usable_size() + sizeof(*this);
+ }
+
+ BlockContents(BlockContents&& other) ROCKSDB_NOEXCEPT {
+ *this = std::move(other);
+ }
+
+ BlockContents& operator=(BlockContents&& other) {
+ data = std::move(other.data);
+ allocation = std::move(other.allocation);
+#ifndef NDEBUG
+ is_raw_block = other.is_raw_block;
+#endif // NDEBUG
+ return *this;
+ }
+};
+
+// Read the block identified by "handle" from "file". On failure
+// return non-OK. On success fill *result and return OK.
+extern Status ReadBlockContents(
+ RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer,
+ const Footer& footer, const ReadOptions& options, const BlockHandle& handle,
+ BlockContents* contents, const ImmutableCFOptions& ioptions,
+ bool do_uncompress = true, const Slice& compression_dict = Slice(),
+ const PersistentCacheOptions& cache_options = PersistentCacheOptions());
+
+// The 'data' points to the raw block contents read in from file.
+// This method allocates a new heap buffer and the raw block
+// contents are uncompresed into this buffer. This buffer is
+// returned via 'result' and it is upto the caller to
+// free this buffer.
+// For description of compress_format_version and possible values, see
+// util/compression.h
+extern Status UncompressBlockContents(const UncompressionInfo& info,
+ const char* data, size_t n,
+ BlockContents* contents,
+ uint32_t compress_format_version,
+ const ImmutableCFOptions& ioptions,
+ MemoryAllocator* allocator = nullptr);
+
+// This is an extension to UncompressBlockContents that accepts
+// a specific compression type. This is used by un-wrapped blocks
+// with no compression header.
+extern Status UncompressBlockContentsForCompressionType(
+ const UncompressionInfo& info, const char* data, size_t n,
+ BlockContents* contents, uint32_t compress_format_version,
+ const ImmutableCFOptions& ioptions, MemoryAllocator* allocator = nullptr);
+
+// Implementation details follow. Clients should ignore,
+
+// TODO(andrewkr): we should prefer one way of representing a null/uninitialized
+// BlockHandle. Currently we use zeros for null and use negation-of-zeros for
+// uninitialized.
+inline BlockHandle::BlockHandle()
+ : BlockHandle(~static_cast<uint64_t>(0), ~static_cast<uint64_t>(0)) {}
+
+inline BlockHandle::BlockHandle(uint64_t _offset, uint64_t _size)
+ : offset_(_offset), size_(_size) {}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/get_context.cc b/storage/rocksdb/rocksdb/table/get_context.cc
new file mode 100644
index 00000000..0e7ac059
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/get_context.cc
@@ -0,0 +1,366 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/get_context.h"
+#include "db/merge_helper.h"
+#include "db/pinned_iterators_manager.h"
+#include "db/read_callback.h"
+#include "monitoring/file_read_sample.h"
+#include "monitoring/perf_context_imp.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/statistics.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+void appendToReplayLog(std::string* replay_log, ValueType type, Slice value) {
+#ifndef ROCKSDB_LITE
+ if (replay_log) {
+ if (replay_log->empty()) {
+ // Optimization: in the common case of only one operation in the
+ // log, we allocate the exact amount of space needed.
+ replay_log->reserve(1 + VarintLength(value.size()) + value.size());
+ }
+ replay_log->push_back(type);
+ PutLengthPrefixedSlice(replay_log, value);
+ }
+#else
+ (void)replay_log;
+ (void)type;
+ (void)value;
+#endif // ROCKSDB_LITE
+}
+
+} // namespace
+
+GetContext::GetContext(
+ const Comparator* ucmp, const MergeOperator* merge_operator, Logger* logger,
+ Statistics* statistics, GetState init_state, const Slice& user_key,
+ PinnableSlice* pinnable_val, bool* value_found, MergeContext* merge_context,
+ bool do_merge, SequenceNumber* _max_covering_tombstone_seq, Env* env,
+ SequenceNumber* seq, PinnedIteratorsManager* _pinned_iters_mgr,
+ ReadCallback* callback, bool* is_blob_index, uint64_t tracing_get_id)
+ : ucmp_(ucmp),
+ merge_operator_(merge_operator),
+ logger_(logger),
+ statistics_(statistics),
+ state_(init_state),
+ user_key_(user_key),
+ pinnable_val_(pinnable_val),
+ value_found_(value_found),
+ merge_context_(merge_context),
+ max_covering_tombstone_seq_(_max_covering_tombstone_seq),
+ env_(env),
+ seq_(seq),
+ replay_log_(nullptr),
+ pinned_iters_mgr_(_pinned_iters_mgr),
+ callback_(callback),
+ do_merge_(do_merge),
+ is_blob_index_(is_blob_index),
+ tracing_get_id_(tracing_get_id) {
+ if (seq_) {
+ *seq_ = kMaxSequenceNumber;
+ }
+ sample_ = should_sample_file_read();
+}
+
+// Called from TableCache::Get and Table::Get when file/block in which
+// key may exist are not there in TableCache/BlockCache respectively. In this
+// case we can't guarantee that key does not exist and are not permitted to do
+// IO to be certain.Set the status=kFound and value_found=false to let the
+// caller know that key may exist but is not there in memory
+void GetContext::MarkKeyMayExist() {
+ state_ = kFound;
+ if (value_found_ != nullptr) {
+ *value_found_ = false;
+ }
+}
+
+void GetContext::SaveValue(const Slice& value, SequenceNumber /*seq*/) {
+ assert(state_ == kNotFound);
+ appendToReplayLog(replay_log_, kTypeValue, value);
+
+ state_ = kFound;
+ if (LIKELY(pinnable_val_ != nullptr)) {
+ pinnable_val_->PinSelf(value);
+ }
+}
+
+void GetContext::ReportCounters() {
+ if (get_context_stats_.num_cache_hit > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_HIT, get_context_stats_.num_cache_hit);
+ }
+ if (get_context_stats_.num_cache_index_hit > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_INDEX_HIT,
+ get_context_stats_.num_cache_index_hit);
+ }
+ if (get_context_stats_.num_cache_data_hit > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_DATA_HIT,
+ get_context_stats_.num_cache_data_hit);
+ }
+ if (get_context_stats_.num_cache_filter_hit > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_FILTER_HIT,
+ get_context_stats_.num_cache_filter_hit);
+ }
+ if (get_context_stats_.num_cache_compression_dict_hit > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_HIT,
+ get_context_stats_.num_cache_compression_dict_hit);
+ }
+ if (get_context_stats_.num_cache_index_miss > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_INDEX_MISS,
+ get_context_stats_.num_cache_index_miss);
+ }
+ if (get_context_stats_.num_cache_filter_miss > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_FILTER_MISS,
+ get_context_stats_.num_cache_filter_miss);
+ }
+ if (get_context_stats_.num_cache_data_miss > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_DATA_MISS,
+ get_context_stats_.num_cache_data_miss);
+ }
+ if (get_context_stats_.num_cache_compression_dict_miss > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_MISS,
+ get_context_stats_.num_cache_compression_dict_miss);
+ }
+ if (get_context_stats_.num_cache_bytes_read > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_BYTES_READ,
+ get_context_stats_.num_cache_bytes_read);
+ }
+ if (get_context_stats_.num_cache_miss > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_MISS,
+ get_context_stats_.num_cache_miss);
+ }
+ if (get_context_stats_.num_cache_add > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_ADD, get_context_stats_.num_cache_add);
+ }
+ if (get_context_stats_.num_cache_bytes_write > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_BYTES_WRITE,
+ get_context_stats_.num_cache_bytes_write);
+ }
+ if (get_context_stats_.num_cache_index_add > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_INDEX_ADD,
+ get_context_stats_.num_cache_index_add);
+ }
+ if (get_context_stats_.num_cache_index_bytes_insert > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_INDEX_BYTES_INSERT,
+ get_context_stats_.num_cache_index_bytes_insert);
+ }
+ if (get_context_stats_.num_cache_data_add > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_DATA_ADD,
+ get_context_stats_.num_cache_data_add);
+ }
+ if (get_context_stats_.num_cache_data_bytes_insert > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_DATA_BYTES_INSERT,
+ get_context_stats_.num_cache_data_bytes_insert);
+ }
+ if (get_context_stats_.num_cache_filter_add > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_FILTER_ADD,
+ get_context_stats_.num_cache_filter_add);
+ }
+ if (get_context_stats_.num_cache_filter_bytes_insert > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_FILTER_BYTES_INSERT,
+ get_context_stats_.num_cache_filter_bytes_insert);
+ }
+ if (get_context_stats_.num_cache_compression_dict_add > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_ADD,
+ get_context_stats_.num_cache_compression_dict_add);
+ }
+ if (get_context_stats_.num_cache_compression_dict_bytes_insert > 0) {
+ RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT,
+ get_context_stats_.num_cache_compression_dict_bytes_insert);
+ }
+}
+
+bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
+ const Slice& value, bool* matched,
+ Cleanable* value_pinner) {
+ assert(matched);
+ assert((state_ != kMerge && parsed_key.type != kTypeMerge) ||
+ merge_context_ != nullptr);
+ if (ucmp_->CompareWithoutTimestamp(parsed_key.user_key, user_key_) == 0) {
+ *matched = true;
+ // If the value is not in the snapshot, skip it
+ if (!CheckCallback(parsed_key.sequence)) {
+ return true; // to continue to the next seq
+ }
+
+ appendToReplayLog(replay_log_, parsed_key.type, value);
+
+ if (seq_ != nullptr) {
+ // Set the sequence number if it is uninitialized
+ if (*seq_ == kMaxSequenceNumber) {
+ *seq_ = parsed_key.sequence;
+ }
+ }
+
+ auto type = parsed_key.type;
+ // Key matches. Process it
+ if ((type == kTypeValue || type == kTypeMerge || type == kTypeBlobIndex) &&
+ max_covering_tombstone_seq_ != nullptr &&
+ *max_covering_tombstone_seq_ > parsed_key.sequence) {
+ type = kTypeRangeDeletion;
+ }
+ switch (type) {
+ case kTypeValue:
+ case kTypeBlobIndex:
+ assert(state_ == kNotFound || state_ == kMerge);
+ if (type == kTypeBlobIndex && is_blob_index_ == nullptr) {
+ // Blob value not supported. Stop.
+ state_ = kBlobIndex;
+ return false;
+ }
+ if (kNotFound == state_) {
+ state_ = kFound;
+ if (do_merge_) {
+ if (LIKELY(pinnable_val_ != nullptr)) {
+ if (LIKELY(value_pinner != nullptr)) {
+ // If the backing resources for the value are provided, pin them
+ pinnable_val_->PinSlice(value, value_pinner);
+ } else {
+ TEST_SYNC_POINT_CALLBACK("GetContext::SaveValue::PinSelf",
+ this);
+
+ // Otherwise copy the value
+ pinnable_val_->PinSelf(value);
+ }
+ }
+ } else {
+ // It means this function is called as part of DB GetMergeOperands
+ // API and the current value should be part of
+ // merge_context_->operand_list
+ push_operand(value, value_pinner);
+ }
+ } else if (kMerge == state_) {
+ assert(merge_operator_ != nullptr);
+ state_ = kFound;
+ if (do_merge_) {
+ if (LIKELY(pinnable_val_ != nullptr)) {
+ Status merge_status = MergeHelper::TimedFullMerge(
+ merge_operator_, user_key_, &value,
+ merge_context_->GetOperands(), pinnable_val_->GetSelf(),
+ logger_, statistics_, env_);
+ pinnable_val_->PinSelf();
+ if (!merge_status.ok()) {
+ state_ = kCorrupt;
+ }
+ }
+ } else {
+ // It means this function is called as part of DB GetMergeOperands
+ // API and the current value should be part of
+ // merge_context_->operand_list
+ push_operand(value, value_pinner);
+ }
+ }
+ if (is_blob_index_ != nullptr) {
+ *is_blob_index_ = (type == kTypeBlobIndex);
+ }
+ return false;
+
+ case kTypeDeletion:
+ case kTypeSingleDeletion:
+ case kTypeRangeDeletion:
+ // TODO(noetzli): Verify correctness once merge of single-deletes
+ // is supported
+ assert(state_ == kNotFound || state_ == kMerge);
+ if (kNotFound == state_) {
+ state_ = kDeleted;
+ } else if (kMerge == state_) {
+ state_ = kFound;
+ if (LIKELY(pinnable_val_ != nullptr)) {
+ if (do_merge_) {
+ Status merge_status = MergeHelper::TimedFullMerge(
+ merge_operator_, user_key_, nullptr,
+ merge_context_->GetOperands(), pinnable_val_->GetSelf(),
+ logger_, statistics_, env_);
+ pinnable_val_->PinSelf();
+ if (!merge_status.ok()) {
+ state_ = kCorrupt;
+ }
+ }
+ // If do_merge_ = false then the current value shouldn't be part of
+ // merge_context_->operand_list
+ }
+ }
+ return false;
+
+ case kTypeMerge:
+ assert(state_ == kNotFound || state_ == kMerge);
+ state_ = kMerge;
+ // value_pinner is not set from plain_table_reader.cc for example.
+ push_operand(value, value_pinner);
+ if (do_merge_ && merge_operator_ != nullptr &&
+ merge_operator_->ShouldMerge(
+ merge_context_->GetOperandsDirectionBackward())) {
+ state_ = kFound;
+ if (LIKELY(pinnable_val_ != nullptr)) {
+ // do_merge_ = true this is the case where this function is called
+ // as part of DB Get API hence merge operators should be merged.
+ if (do_merge_) {
+ Status merge_status = MergeHelper::TimedFullMerge(
+ merge_operator_, user_key_, nullptr,
+ merge_context_->GetOperands(), pinnable_val_->GetSelf(),
+ logger_, statistics_, env_);
+ pinnable_val_->PinSelf();
+ if (!merge_status.ok()) {
+ state_ = kCorrupt;
+ }
+ }
+ }
+ return false;
+ }
+ return true;
+
+ default:
+ assert(false);
+ break;
+ }
+ }
+
+ // state_ could be Corrupt, merge or notfound
+ return false;
+}
+
+void GetContext::push_operand(const Slice& value, Cleanable* value_pinner) {
+ if (pinned_iters_mgr() && pinned_iters_mgr()->PinningEnabled() &&
+ value_pinner != nullptr) {
+ value_pinner->DelegateCleanupsTo(pinned_iters_mgr());
+ merge_context_->PushOperand(value, true /*value_pinned*/);
+ } else {
+ merge_context_->PushOperand(value, false);
+ }
+}
+
+void replayGetContextLog(const Slice& replay_log, const Slice& user_key,
+ GetContext* get_context, Cleanable* value_pinner) {
+#ifndef ROCKSDB_LITE
+ Slice s = replay_log;
+ while (s.size()) {
+ auto type = static_cast<ValueType>(*s.data());
+ s.remove_prefix(1);
+ Slice value;
+ bool ret = GetLengthPrefixedSlice(&s, &value);
+ assert(ret);
+ (void)ret;
+
+ bool dont_care __attribute__((__unused__));
+ // Since SequenceNumber is not stored and unknown, we will use
+ // kMaxSequenceNumber.
+ get_context->SaveValue(
+ ParsedInternalKey(user_key, kMaxSequenceNumber, type), value,
+ &dont_care, value_pinner);
+ }
+#else // ROCKSDB_LITE
+ (void)replay_log;
+ (void)user_key;
+ (void)get_context;
+ (void)value_pinner;
+ assert(false);
+#endif // ROCKSDB_LITE
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/get_context.h b/storage/rocksdb/rocksdb/table/get_context.h
new file mode 100644
index 00000000..79bae221
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/get_context.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "db/dbformat.h"
+#include "db/merge_context.h"
+#include "db/read_callback.h"
+#include "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/types.h"
+#include "table/block_based/block.h"
+
+namespace ROCKSDB_NAMESPACE {
+class MergeContext;
+class PinnedIteratorsManager;
+
+// Data structure for accumulating statistics during a point lookup. At the
+// end of the point lookup, the corresponding ticker stats are updated. This
+// avoids the overhead of frequent ticker stats updates
+struct GetContextStats {
+ uint64_t num_cache_hit = 0;
+ uint64_t num_cache_index_hit = 0;
+ uint64_t num_cache_data_hit = 0;
+ uint64_t num_cache_filter_hit = 0;
+ uint64_t num_cache_compression_dict_hit = 0;
+ uint64_t num_cache_index_miss = 0;
+ uint64_t num_cache_filter_miss = 0;
+ uint64_t num_cache_data_miss = 0;
+ uint64_t num_cache_compression_dict_miss = 0;
+ uint64_t num_cache_bytes_read = 0;
+ uint64_t num_cache_miss = 0;
+ uint64_t num_cache_add = 0;
+ uint64_t num_cache_bytes_write = 0;
+ uint64_t num_cache_index_add = 0;
+ uint64_t num_cache_index_bytes_insert = 0;
+ uint64_t num_cache_data_add = 0;
+ uint64_t num_cache_data_bytes_insert = 0;
+ uint64_t num_cache_filter_add = 0;
+ uint64_t num_cache_filter_bytes_insert = 0;
+ uint64_t num_cache_compression_dict_add = 0;
+ uint64_t num_cache_compression_dict_bytes_insert = 0;
+};
+
+// A class to hold context about a point lookup, such as pointer to value
+// slice, key, merge context etc, as well as the current state of the
+// lookup. Any user using GetContext to track the lookup result must call
+// SaveValue() whenever the internal key is found. This can happen
+// repeatedly in case of merge operands. In case the key may exist with
+// high probability, but IO is required to confirm and the user doesn't allow
+// it, MarkKeyMayExist() must be called instead of SaveValue().
+class GetContext {
+ public:
+ // Current state of the point lookup. All except kNotFound and kMerge are
+ // terminal states
+ enum GetState {
+ kNotFound,
+ kFound,
+ kDeleted,
+ kCorrupt,
+ kMerge, // saver contains the current merge result (the operands)
+ kBlobIndex,
+ };
+ GetContextStats get_context_stats_;
+
+ // Constructor
+ // @param value Holds the value corresponding to user_key. If its nullptr
+ // then return all merge operands corresponding to user_key
+ // via merge_context
+ // @param value_found If non-nullptr, set to false if key may be present
+ // but we can't be certain because we cannot do IO
+ // @param max_covering_tombstone_seq Pointer to highest sequence number of
+ // range deletion covering the key. When an internal key
+ // is found with smaller sequence number, the lookup
+ // terminates
+ // @param seq If non-nullptr, the sequence number of the found key will be
+ // saved here
+ // @param callback Pointer to ReadCallback to perform additional checks
+ // for visibility of a key
+ // @param is_blob_index If non-nullptr, will be used to indicate if a found
+ // key is of type blob index
+ // @param do_merge True if value associated with user_key has to be returned
+ // and false if all the merge operands associated with user_key has to be
+ // returned. Id do_merge=false then all the merge operands are stored in
+ // merge_context and they are never merged. The value pointer is untouched.
+ GetContext(const Comparator* ucmp, const MergeOperator* merge_operator,
+ Logger* logger, Statistics* statistics, GetState init_state,
+ const Slice& user_key, PinnableSlice* value, bool* value_found,
+ MergeContext* merge_context, bool do_merge,
+ SequenceNumber* max_covering_tombstone_seq, Env* env,
+ SequenceNumber* seq = nullptr,
+ PinnedIteratorsManager* _pinned_iters_mgr = nullptr,
+ ReadCallback* callback = nullptr, bool* is_blob_index = nullptr,
+ uint64_t tracing_get_id = 0);
+
+ GetContext() = delete;
+
+ // This can be called to indicate that a key may be present, but cannot be
+ // confirmed due to IO not allowed
+ void MarkKeyMayExist();
+
+ // Records this key, value, and any meta-data (such as sequence number and
+ // state) into this GetContext.
+ //
+ // If the parsed_key matches the user key that we are looking for, sets
+ // matched to true.
+ //
+ // Returns True if more keys need to be read (due to merges) or
+ // False if the complete value has been found.
+ bool SaveValue(const ParsedInternalKey& parsed_key, const Slice& value,
+ bool* matched, Cleanable* value_pinner = nullptr);
+
+ // Simplified version of the previous function. Should only be used when we
+ // know that the operation is a Put.
+ void SaveValue(const Slice& value, SequenceNumber seq);
+
+ GetState State() const { return state_; }
+
+ SequenceNumber* max_covering_tombstone_seq() {
+ return max_covering_tombstone_seq_;
+ }
+
+ PinnedIteratorsManager* pinned_iters_mgr() { return pinned_iters_mgr_; }
+
+ // If a non-null string is passed, all the SaveValue calls will be
+ // logged into the string. The operations can then be replayed on
+ // another GetContext with replayGetContextLog.
+ void SetReplayLog(std::string* replay_log) { replay_log_ = replay_log; }
+
+ // Do we need to fetch the SequenceNumber for this key?
+ bool NeedToReadSequence() const { return (seq_ != nullptr); }
+
+ bool sample() const { return sample_; }
+
+ bool CheckCallback(SequenceNumber seq) {
+ if (callback_) {
+ return callback_->IsVisible(seq);
+ }
+ return true;
+ }
+
+ void ReportCounters();
+
+ bool has_callback() const { return callback_ != nullptr; }
+
+ uint64_t get_tracing_get_id() const { return tracing_get_id_; }
+
+ void push_operand(const Slice& value, Cleanable* value_pinner);
+
+ private:
+ const Comparator* ucmp_;
+ const MergeOperator* merge_operator_;
+ // the merge operations encountered;
+ Logger* logger_;
+ Statistics* statistics_;
+
+ GetState state_;
+ Slice user_key_;
+ PinnableSlice* pinnable_val_;
+ bool* value_found_; // Is value set correctly? Used by KeyMayExist
+ MergeContext* merge_context_;
+ SequenceNumber* max_covering_tombstone_seq_;
+ Env* env_;
+ // If a key is found, seq_ will be set to the SequenceNumber of most recent
+ // write to the key or kMaxSequenceNumber if unknown
+ SequenceNumber* seq_;
+ std::string* replay_log_;
+ // Used to temporarily pin blocks when state_ == GetContext::kMerge
+ PinnedIteratorsManager* pinned_iters_mgr_;
+ ReadCallback* callback_;
+ bool sample_;
+ // Value is true if it's called as part of DB Get API and false if it's
+ // called as part of DB GetMergeOperands API. When it's false merge operators
+ // are never merged.
+ bool do_merge_;
+ bool* is_blob_index_;
+ // Used for block cache tracing only. A tracing get id uniquely identifies a
+ // Get or a MultiGet.
+ const uint64_t tracing_get_id_;
+};
+
+// Call this to replay a log and bring the get_context up to date. The replay
+// log must have been created by another GetContext object, whose replay log
+// must have been set by calling GetContext::SetReplayLog().
+void replayGetContextLog(const Slice& replay_log, const Slice& user_key,
+ GetContext* get_context,
+ Cleanable* value_pinner = nullptr);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/internal_iterator.h b/storage/rocksdb/rocksdb/table/internal_iterator.h
new file mode 100644
index 00000000..780db64b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/internal_iterator.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "db/dbformat.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/status.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PinnedIteratorsManager;
+
+struct IterateResult {
+ Slice key;
+ bool may_be_out_of_upper_bound;
+};
+
+template <class TValue>
+class InternalIteratorBase : public Cleanable {
+ public:
+ InternalIteratorBase() {}
+
+ // No copying allowed
+ InternalIteratorBase(const InternalIteratorBase&) = delete;
+ InternalIteratorBase& operator=(const InternalIteratorBase&) = delete;
+
+ virtual ~InternalIteratorBase() {}
+
+ // An iterator is either positioned at a key/value pair, or
+ // not valid. This method returns true iff the iterator is valid.
+ // Always returns false if !status().ok().
+ virtual bool Valid() const = 0;
+
+ // Position at the first key in the source. The iterator is Valid()
+ // after this call iff the source is not empty.
+ virtual void SeekToFirst() = 0;
+
+ // Position at the last key in the source. The iterator is
+ // Valid() after this call iff the source is not empty.
+ virtual void SeekToLast() = 0;
+
+ // Position at the first key in the source that at or past target
+ // The iterator is Valid() after this call iff the source contains
+ // an entry that comes at or past target.
+ // All Seek*() methods clear any error status() that the iterator had prior to
+ // the call; after the seek, status() indicates only the error (if any) that
+ // happened during the seek, not any past errors.
+ virtual void Seek(const Slice& target) = 0;
+
+ // Position at the first key in the source that at or before target
+ // The iterator is Valid() after this call iff the source contains
+ // an entry that comes at or before target.
+ virtual void SeekForPrev(const Slice& target) = 0;
+
+ // Moves to the next entry in the source. After this call, Valid() is
+ // true iff the iterator was not positioned at the last entry in the source.
+ // REQUIRES: Valid()
+ virtual void Next() = 0;
+
+ // Moves to the next entry in the source, and return result. Iterator
+ // implementation should override this method to help methods inline better,
+ // or when MayBeOutOfUpperBound() is non-trivial.
+ // REQUIRES: Valid()
+ virtual bool NextAndGetResult(IterateResult* result) {
+ Next();
+ bool is_valid = Valid();
+ if (is_valid) {
+ result->key = key();
+ // Default may_be_out_of_upper_bound to true to avoid unnecessary virtual
+ // call. If an implementation has non-trivial MayBeOutOfUpperBound(),
+ // it should also override NextAndGetResult().
+ result->may_be_out_of_upper_bound = true;
+ assert(MayBeOutOfUpperBound());
+ }
+ return is_valid;
+ }
+
+ // Moves to the previous entry in the source. After this call, Valid() is
+ // true iff the iterator was not positioned at the first entry in source.
+ // REQUIRES: Valid()
+ virtual void Prev() = 0;
+
+ // 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: Valid()
+ virtual Slice key() const = 0;
+
+ // Return user key for the current entry.
+ // REQUIRES: Valid()
+ virtual Slice user_key() const { return ExtractUserKey(key()); }
+
+ // 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: Valid()
+ virtual TValue value() const = 0;
+
+ // 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().
+ virtual Status status() const = 0;
+
+ // True if the iterator is invalidated because it reached a key that is above
+ // the iterator upper bound. Used by LevelIterator to decide whether it should
+ // stop or move on to the next file.
+ // Important: if iterator reached the end of the file without encountering any
+ // keys above the upper bound, IsOutOfBound() must return false.
+ virtual bool IsOutOfBound() { return false; }
+
+ // Keys return from this iterator can be smaller than iterate_lower_bound.
+ virtual bool MayBeOutOfLowerBound() { return true; }
+
+ // Keys return from this iterator can be larger or equal to
+ // iterate_upper_bound.
+ virtual bool MayBeOutOfUpperBound() { return true; }
+
+ // Pass the PinnedIteratorsManager to the Iterator, most Iterators dont
+ // communicate with PinnedIteratorsManager so default implementation is no-op
+ // but for Iterators that need to communicate with PinnedIteratorsManager
+ // they will implement this function and use the passed pointer to communicate
+ // with PinnedIteratorsManager.
+ virtual void SetPinnedItersMgr(PinnedIteratorsManager* /*pinned_iters_mgr*/) {
+ }
+
+ // If true, this means that the Slice returned by key() is valid as long as
+ // PinnedIteratorsManager::ReleasePinnedData is not called and the
+ // Iterator is not deleted.
+ //
+ // IsKeyPinned() is guaranteed to always return true if
+ // - Iterator is created with ReadOptions::pin_data = true
+ // - DB tables were created with BlockBasedTableOptions::use_delta_encoding
+ // set to false.
+ virtual bool IsKeyPinned() const { return false; }
+
+ // If true, this means that the Slice returned by value() is valid as long as
+ // PinnedIteratorsManager::ReleasePinnedData is not called and the
+ // Iterator is not deleted.
+ virtual bool IsValuePinned() const { return false; }
+
+ virtual Status GetProperty(std::string /*prop_name*/, std::string* /*prop*/) {
+ return Status::NotSupported("");
+ }
+
+ protected:
+ void SeekForPrevImpl(const Slice& target, const Comparator* cmp) {
+ Seek(target);
+ if (!Valid()) {
+ SeekToLast();
+ }
+ while (Valid() && cmp->Compare(target, key()) < 0) {
+ Prev();
+ }
+ }
+
+ bool is_mutable_;
+};
+
+using InternalIterator = InternalIteratorBase<Slice>;
+
+// Return an empty iterator (yields nothing).
+template <class TValue = Slice>
+extern InternalIteratorBase<TValue>* NewEmptyInternalIterator();
+
+// Return an empty iterator with the specified status.
+template <class TValue = Slice>
+extern InternalIteratorBase<TValue>* NewErrorInternalIterator(
+ const Status& status);
+
+// Return an empty iterator with the specified status, allocated arena.
+template <class TValue = Slice>
+extern InternalIteratorBase<TValue>* NewErrorInternalIterator(
+ const Status& status, Arena* arena);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/iter_heap.h b/storage/rocksdb/rocksdb/table/iter_heap.h
new file mode 100644
index 00000000..f6812fa0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/iter_heap.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "db/dbformat.h"
+#include "table/iterator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// When used with std::priority_queue, this comparison functor puts the
+// iterator with the max/largest key on top.
+class MaxIteratorComparator {
+ public:
+ MaxIteratorComparator(const InternalKeyComparator* comparator)
+ : comparator_(comparator) {}
+
+ bool operator()(IteratorWrapper* a, IteratorWrapper* b) const {
+ return comparator_->Compare(a->key(), b->key()) < 0;
+ }
+ private:
+ const InternalKeyComparator* comparator_;
+};
+
+// When used with std::priority_queue, this comparison functor puts the
+// iterator with the min/smallest key on top.
+class MinIteratorComparator {
+ public:
+ MinIteratorComparator(const InternalKeyComparator* comparator)
+ : comparator_(comparator) {}
+
+ bool operator()(IteratorWrapper* a, IteratorWrapper* b) const {
+ return comparator_->Compare(a->key(), b->key()) > 0;
+ }
+ private:
+ const InternalKeyComparator* comparator_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/iterator.cc b/storage/rocksdb/rocksdb/table/iterator.cc
new file mode 100644
index 00000000..4ecfc007
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/iterator.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/iterator.h"
+#include "memory/arena.h"
+#include "table/internal_iterator.h"
+#include "table/iterator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Cleanable::Cleanable() {
+ cleanup_.function = nullptr;
+ cleanup_.next = nullptr;
+}
+
+Cleanable::~Cleanable() { DoCleanup(); }
+
+Cleanable::Cleanable(Cleanable&& other) {
+ *this = std::move(other);
+}
+
+Cleanable& Cleanable::operator=(Cleanable&& other) {
+ if (this != &other) {
+ cleanup_ = other.cleanup_;
+ other.cleanup_.function = nullptr;
+ other.cleanup_.next = nullptr;
+ }
+ return *this;
+}
+
+// If the entire linked list was on heap we could have simply add attach one
+// link list to another. However the head is an embeded object to avoid the cost
+// of creating objects for most of the use cases when the Cleanable has only one
+// Cleanup to do. We could put evernything on heap if benchmarks show no
+// negative impact on performance.
+// Also we need to iterate on the linked list since there is no pointer to the
+// tail. We can add the tail pointer but maintainin it might negatively impact
+// the perforamnce for the common case of one cleanup where tail pointer is not
+// needed. Again benchmarks could clarify that.
+// Even without a tail pointer we could iterate on the list, find the tail, and
+// have only that node updated without the need to insert the Cleanups one by
+// one. This however would be redundant when the source Cleanable has one or a
+// few Cleanups which is the case most of the time.
+// TODO(myabandeh): if the list is too long we should maintain a tail pointer
+// and have the entire list (minus the head that has to be inserted separately)
+// merged with the target linked list at once.
+void Cleanable::DelegateCleanupsTo(Cleanable* other) {
+ assert(other != nullptr);
+ if (cleanup_.function == nullptr) {
+ return;
+ }
+ Cleanup* c = &cleanup_;
+ other->RegisterCleanup(c->function, c->arg1, c->arg2);
+ c = c->next;
+ while (c != nullptr) {
+ Cleanup* next = c->next;
+ other->RegisterCleanup(c);
+ c = next;
+ }
+ cleanup_.function = nullptr;
+ cleanup_.next = nullptr;
+}
+
+void Cleanable::RegisterCleanup(Cleanable::Cleanup* c) {
+ assert(c != nullptr);
+ if (cleanup_.function == nullptr) {
+ cleanup_.function = c->function;
+ cleanup_.arg1 = c->arg1;
+ cleanup_.arg2 = c->arg2;
+ delete c;
+ } else {
+ c->next = cleanup_.next;
+ cleanup_.next = c;
+ }
+}
+
+void Cleanable::RegisterCleanup(CleanupFunction func, void* arg1, void* arg2) {
+ assert(func != nullptr);
+ Cleanup* c;
+ if (cleanup_.function == nullptr) {
+ c = &cleanup_;
+ } else {
+ c = new Cleanup;
+ c->next = cleanup_.next;
+ cleanup_.next = c;
+ }
+ c->function = func;
+ c->arg1 = arg1;
+ c->arg2 = arg2;
+}
+
+Status Iterator::GetProperty(std::string prop_name, std::string* prop) {
+ if (prop == nullptr) {
+ return Status::InvalidArgument("prop is nullptr");
+ }
+ if (prop_name == "rocksdb.iterator.is-key-pinned") {
+ *prop = "0";
+ return Status::OK();
+ }
+ return Status::InvalidArgument("Unidentified property.");
+}
+
+namespace {
+class EmptyIterator : public Iterator {
+ public:
+ explicit EmptyIterator(const Status& s) : status_(s) { }
+ bool Valid() const override { return false; }
+ void Seek(const Slice& /*target*/) override {}
+ void SeekForPrev(const Slice& /*target*/) override {}
+ void SeekToFirst() override {}
+ void SeekToLast() override {}
+ void Next() override { assert(false); }
+ void Prev() override { assert(false); }
+ Slice key() const override {
+ assert(false);
+ return Slice();
+ }
+ Slice value() const override {
+ assert(false);
+ return Slice();
+ }
+ Status status() const override { return status_; }
+
+ private:
+ Status status_;
+};
+
+template <class TValue = Slice>
+class EmptyInternalIterator : public InternalIteratorBase<TValue> {
+ public:
+ explicit EmptyInternalIterator(const Status& s) : status_(s) {}
+ bool Valid() const override { return false; }
+ void Seek(const Slice& /*target*/) override {}
+ void SeekForPrev(const Slice& /*target*/) override {}
+ void SeekToFirst() override {}
+ void SeekToLast() override {}
+ void Next() override { assert(false); }
+ void Prev() override { assert(false); }
+ Slice key() const override {
+ assert(false);
+ return Slice();
+ }
+ TValue value() const override {
+ assert(false);
+ return TValue();
+ }
+ Status status() const override { return status_; }
+
+ private:
+ Status status_;
+};
+} // namespace
+
+Iterator* NewEmptyIterator() { return new EmptyIterator(Status::OK()); }
+
+Iterator* NewErrorIterator(const Status& status) {
+ return new EmptyIterator(status);
+}
+
+template <class TValue>
+InternalIteratorBase<TValue>* NewErrorInternalIterator(const Status& status) {
+ return new EmptyInternalIterator<TValue>(status);
+}
+template InternalIteratorBase<IndexValue>* NewErrorInternalIterator(
+ const Status& status);
+template InternalIteratorBase<Slice>* NewErrorInternalIterator(
+ const Status& status);
+
+template <class TValue>
+InternalIteratorBase<TValue>* NewErrorInternalIterator(const Status& status,
+ Arena* arena) {
+ if (arena == nullptr) {
+ return NewErrorInternalIterator<TValue>(status);
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(EmptyInternalIterator<TValue>));
+ return new (mem) EmptyInternalIterator<TValue>(status);
+ }
+}
+template InternalIteratorBase<IndexValue>* NewErrorInternalIterator(
+ const Status& status, Arena* arena);
+template InternalIteratorBase<Slice>* NewErrorInternalIterator(
+ const Status& status, Arena* arena);
+
+template <class TValue>
+InternalIteratorBase<TValue>* NewEmptyInternalIterator() {
+ return new EmptyInternalIterator<TValue>(Status::OK());
+}
+template InternalIteratorBase<IndexValue>* NewEmptyInternalIterator();
+template InternalIteratorBase<Slice>* NewEmptyInternalIterator();
+
+template <class TValue>
+InternalIteratorBase<TValue>* NewEmptyInternalIterator(Arena* arena) {
+ if (arena == nullptr) {
+ return NewEmptyInternalIterator<TValue>();
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(EmptyInternalIterator<TValue>));
+ return new (mem) EmptyInternalIterator<TValue>(Status::OK());
+ }
+}
+template InternalIteratorBase<IndexValue>* NewEmptyInternalIterator(
+ Arena* arena);
+template InternalIteratorBase<Slice>* NewEmptyInternalIterator(Arena* arena);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/iterator_wrapper.h b/storage/rocksdb/rocksdb/table/iterator_wrapper.h
new file mode 100644
index 00000000..c13359e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/iterator_wrapper.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <set>
+
+#include "table/internal_iterator.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A internal wrapper class with an interface similar to Iterator that caches
+// the valid() and key() results for an underlying iterator.
+// This can help avoid virtual function calls and also gives better
+// cache locality.
+template <class TValue = Slice>
+class IteratorWrapperBase {
+ public:
+ IteratorWrapperBase() : iter_(nullptr), valid_(false) {}
+ explicit IteratorWrapperBase(InternalIteratorBase<TValue>* _iter)
+ : iter_(nullptr) {
+ Set(_iter);
+ }
+ ~IteratorWrapperBase() {}
+ InternalIteratorBase<TValue>* iter() const { return iter_; }
+
+ // Set the underlying Iterator to _iter and return
+ // previous underlying Iterator.
+ InternalIteratorBase<TValue>* Set(InternalIteratorBase<TValue>* _iter) {
+ InternalIteratorBase<TValue>* old_iter = iter_;
+
+ iter_ = _iter;
+ if (iter_ == nullptr) {
+ valid_ = false;
+ } else {
+ Update();
+ }
+ return old_iter;
+ }
+
+ void DeleteIter(bool is_arena_mode) {
+ if (iter_) {
+ if (!is_arena_mode) {
+ delete iter_;
+ } else {
+ iter_->~InternalIteratorBase<TValue>();
+ }
+ }
+ }
+
+ // Iterator interface methods
+ bool Valid() const { return valid_; }
+ Slice key() const {
+ assert(Valid());
+ return result_.key;
+ }
+ TValue value() const {
+ assert(Valid());
+ return iter_->value();
+ }
+ // Methods below require iter() != nullptr
+ Status status() const {
+ assert(iter_);
+ return iter_->status();
+ }
+ void Next() {
+ assert(iter_);
+ valid_ = iter_->NextAndGetResult(&result_);
+ assert(!valid_ || iter_->status().ok());
+ }
+ void Prev() {
+ assert(iter_);
+ iter_->Prev();
+ Update();
+ }
+ void Seek(const Slice& k) {
+ assert(iter_);
+ iter_->Seek(k);
+ Update();
+ }
+ void SeekForPrev(const Slice& k) {
+ assert(iter_);
+ iter_->SeekForPrev(k);
+ Update();
+ }
+ void SeekToFirst() {
+ assert(iter_);
+ iter_->SeekToFirst();
+ Update();
+ }
+ void SeekToLast() {
+ assert(iter_);
+ iter_->SeekToLast();
+ Update();
+ }
+
+ bool MayBeOutOfLowerBound() {
+ assert(Valid());
+ return iter_->MayBeOutOfLowerBound();
+ }
+
+ bool MayBeOutOfUpperBound() {
+ assert(Valid());
+ return result_.may_be_out_of_upper_bound;
+ }
+
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) {
+ assert(iter_);
+ iter_->SetPinnedItersMgr(pinned_iters_mgr);
+ }
+ bool IsKeyPinned() const {
+ assert(Valid());
+ return iter_->IsKeyPinned();
+ }
+ bool IsValuePinned() const {
+ assert(Valid());
+ return iter_->IsValuePinned();
+ }
+
+ private:
+ void Update() {
+ valid_ = iter_->Valid();
+ if (valid_) {
+ assert(iter_->status().ok());
+ result_.key = iter_->key();
+ result_.may_be_out_of_upper_bound = true;
+ }
+ }
+
+ InternalIteratorBase<TValue>* iter_;
+ IterateResult result_;
+ bool valid_;
+};
+
+using IteratorWrapper = IteratorWrapperBase<Slice>;
+
+class Arena;
+// Return an empty iterator (yields nothing) allocated from arena.
+template <class TValue = Slice>
+extern InternalIteratorBase<TValue>* NewEmptyInternalIterator(Arena* arena);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/merger_test.cc b/storage/rocksdb/rocksdb/table/merger_test.cc
new file mode 100644
index 00000000..466e0eb4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/merger_test.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <vector>
+#include <string>
+
+#include "table/merging_iterator.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.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<std::string> GenerateStrings(size_t len, int string_len) {
+ std::vector<std::string> ret;
+
+ for (size_t i = 0; i < len; ++i) {
+ InternalKey ik(test::RandomHumanReadableString(&rnd_, 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(test::RandomHumanReadableString(&rnd_, 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<InternalIterator*> 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 test::VectorIterator(strings));
+ all_keys_.insert(all_keys_.end(), strings.begin(), strings.end());
+ }
+
+ merging_iterator_.reset(
+ NewMergingIterator(&icomp_, &small_iterators[0],
+ static_cast<int>(small_iterators.size())));
+ single_iterator_.reset(new test::VectorIterator(all_keys_));
+ }
+
+ InternalKeyComparator icomp_;
+ Random rnd_;
+ std::unique_ptr<InternalIterator> merging_iterator_;
+ std::unique_ptr<InternalIterator> single_iterator_;
+ std::vector<std::string> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/merging_iterator.cc b/storage/rocksdb/rocksdb/table/merging_iterator.cc
new file mode 100644
index 00000000..47fa048f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/merging_iterator.cc
@@ -0,0 +1,468 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/merging_iterator.h"
+#include <string>
+#include <vector>
+#include "db/dbformat.h"
+#include "db/pinned_iterators_manager.h"
+#include "memory/arena.h"
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/options.h"
+#include "table/internal_iterator.h"
+#include "table/iter_heap.h"
+#include "table/iterator_wrapper.h"
+#include "test_util/sync_point.h"
+#include "util/autovector.h"
+#include "util/heap.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Without anonymous namespace here, we fail the warning -Wmissing-prototypes
+namespace {
+typedef BinaryHeap<IteratorWrapper*, MaxIteratorComparator> MergerMaxIterHeap;
+typedef BinaryHeap<IteratorWrapper*, MinIteratorComparator> MergerMinIterHeap;
+} // namespace
+
+const size_t kNumIterReserve = 4;
+
+class MergingIterator : public InternalIterator {
+ public:
+ MergingIterator(const InternalKeyComparator* comparator,
+ InternalIterator** children, int n, bool is_arena_mode,
+ bool prefix_seek_mode)
+ : is_arena_mode_(is_arena_mode),
+ comparator_(comparator),
+ current_(nullptr),
+ direction_(kForward),
+ minHeap_(comparator_),
+ prefix_seek_mode_(prefix_seek_mode),
+ pinned_iters_mgr_(nullptr) {
+ children_.resize(n);
+ for (int i = 0; i < n; i++) {
+ children_[i].Set(children[i]);
+ }
+ for (auto& child : children_) {
+ AddToMinHeapOrCheckStatus(&child);
+ }
+ current_ = CurrentForward();
+ }
+
+ void considerStatus(Status s) {
+ if (!s.ok() && status_.ok()) {
+ status_ = s;
+ }
+ }
+
+ virtual void AddIterator(InternalIterator* iter) {
+ assert(direction_ == kForward);
+ children_.emplace_back(iter);
+ if (pinned_iters_mgr_) {
+ iter->SetPinnedItersMgr(pinned_iters_mgr_);
+ }
+ auto new_wrapper = children_.back();
+ AddToMinHeapOrCheckStatus(&new_wrapper);
+ if (new_wrapper.Valid()) {
+ current_ = CurrentForward();
+ }
+ }
+
+ ~MergingIterator() override {
+ for (auto& child : children_) {
+ child.DeleteIter(is_arena_mode_);
+ }
+ }
+
+ bool Valid() const override { return current_ != nullptr && status_.ok(); }
+
+ Status status() const override { return status_; }
+
+ void SeekToFirst() override {
+ ClearHeaps();
+ status_ = Status::OK();
+ for (auto& child : children_) {
+ child.SeekToFirst();
+ AddToMinHeapOrCheckStatus(&child);
+ }
+ direction_ = kForward;
+ current_ = CurrentForward();
+ }
+
+ void SeekToLast() override {
+ ClearHeaps();
+ InitMaxHeap();
+ status_ = Status::OK();
+ for (auto& child : children_) {
+ child.SeekToLast();
+ AddToMaxHeapOrCheckStatus(&child);
+ }
+ direction_ = kReverse;
+ current_ = CurrentReverse();
+ }
+
+ void Seek(const Slice& target) override {
+ ClearHeaps();
+ status_ = Status::OK();
+ for (auto& child : children_) {
+ {
+ PERF_TIMER_GUARD(seek_child_seek_time);
+ child.Seek(target);
+ }
+
+ PERF_COUNTER_ADD(seek_child_seek_count, 1);
+ {
+ // Strictly, we timed slightly more than min heap operation,
+ // but these operations are very cheap.
+ PERF_TIMER_GUARD(seek_min_heap_time);
+ AddToMinHeapOrCheckStatus(&child);
+ }
+ }
+ direction_ = kForward;
+ {
+ PERF_TIMER_GUARD(seek_min_heap_time);
+ current_ = CurrentForward();
+ }
+ }
+
+ void SeekForPrev(const Slice& target) override {
+ ClearHeaps();
+ InitMaxHeap();
+ status_ = Status::OK();
+
+ for (auto& child : children_) {
+ {
+ PERF_TIMER_GUARD(seek_child_seek_time);
+ child.SeekForPrev(target);
+ }
+ PERF_COUNTER_ADD(seek_child_seek_count, 1);
+
+ {
+ PERF_TIMER_GUARD(seek_max_heap_time);
+ AddToMaxHeapOrCheckStatus(&child);
+ }
+ }
+ direction_ = kReverse;
+ {
+ PERF_TIMER_GUARD(seek_max_heap_time);
+ current_ = CurrentReverse();
+ }
+ }
+
+ void Next() override {
+ assert(Valid());
+
+ // Ensure that all children are positioned after key().
+ // If we are moving in the forward direction, it is already
+ // true for all of the non-current children since current_ is
+ // the smallest child and key() == current_->key().
+ if (direction_ != kForward) {
+ SwitchToForward();
+ // The loop advanced all non-current children to be > key() so current_
+ // should still be strictly the smallest key.
+ }
+
+ // For the heap modifications below to be correct, current_ must be the
+ // current top of the heap.
+ assert(current_ == CurrentForward());
+
+ // as the current points to the current record. move the iterator forward.
+ current_->Next();
+ if (current_->Valid()) {
+ // current is still valid after the Next() call above. Call
+ // replace_top() to restore the heap property. When the same child
+ // iterator yields a sequence of keys, this is cheap.
+ assert(current_->status().ok());
+ minHeap_.replace_top(current_);
+ } else {
+ // current stopped being valid, remove it from the heap.
+ considerStatus(current_->status());
+ minHeap_.pop();
+ }
+ current_ = CurrentForward();
+ }
+
+ bool NextAndGetResult(IterateResult* result) override {
+ Next();
+ bool is_valid = Valid();
+ if (is_valid) {
+ result->key = key();
+ result->may_be_out_of_upper_bound = MayBeOutOfUpperBound();
+ }
+ return is_valid;
+ }
+
+ void Prev() override {
+ assert(Valid());
+ // Ensure that all children are positioned before key().
+ // If we are moving in the reverse direction, it is already
+ // true for all of the non-current children since current_ is
+ // the largest child and key() == current_->key().
+ if (direction_ != kReverse) {
+ // Otherwise, retreat the non-current children. We retreat current_
+ // just after the if-block.
+ SwitchToBackward();
+ }
+
+ // For the heap modifications below to be correct, current_ must be the
+ // current top of the heap.
+ assert(current_ == CurrentReverse());
+
+ current_->Prev();
+ if (current_->Valid()) {
+ // current is still valid after the Prev() call above. Call
+ // replace_top() to restore the heap property. When the same child
+ // iterator yields a sequence of keys, this is cheap.
+ assert(current_->status().ok());
+ maxHeap_->replace_top(current_);
+ } else {
+ // current stopped being valid, remove it from the heap.
+ considerStatus(current_->status());
+ maxHeap_->pop();
+ }
+ current_ = CurrentReverse();
+ }
+
+ Slice key() const override {
+ assert(Valid());
+ return current_->key();
+ }
+
+ Slice value() const override {
+ assert(Valid());
+ return current_->value();
+ }
+
+ // Here we simply relay MayBeOutOfLowerBound/MayBeOutOfUpperBound result
+ // from current child iterator. Potentially as long as one of child iterator
+ // report out of bound is not possible, we know current key is within bound.
+
+ bool MayBeOutOfLowerBound() override {
+ assert(Valid());
+ return current_->MayBeOutOfLowerBound();
+ }
+
+ bool MayBeOutOfUpperBound() override {
+ assert(Valid());
+ return current_->MayBeOutOfUpperBound();
+ }
+
+ void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
+ pinned_iters_mgr_ = pinned_iters_mgr;
+ for (auto& child : children_) {
+ child.SetPinnedItersMgr(pinned_iters_mgr);
+ }
+ }
+
+ bool IsKeyPinned() const override {
+ assert(Valid());
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ current_->IsKeyPinned();
+ }
+
+ bool IsValuePinned() const override {
+ assert(Valid());
+ return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
+ current_->IsValuePinned();
+ }
+
+ private:
+ // Clears heaps for both directions, used when changing direction or seeking
+ void ClearHeaps();
+ // Ensures that maxHeap_ is initialized when starting to go in the reverse
+ // direction
+ void InitMaxHeap();
+
+ bool is_arena_mode_;
+ const InternalKeyComparator* comparator_;
+ autovector<IteratorWrapper, kNumIterReserve> children_;
+
+ // Cached pointer to child iterator with the current key, or nullptr if no
+ // child iterators are valid. This is the top of minHeap_ or maxHeap_
+ // depending on the direction.
+ IteratorWrapper* current_;
+ // If any of the children have non-ok status, this is one of them.
+ Status status_;
+ // Which direction is the iterator moving?
+ enum Direction {
+ kForward,
+ kReverse
+ };
+ Direction direction_;
+ MergerMinIterHeap minHeap_;
+ bool prefix_seek_mode_;
+
+ // Max heap is used for reverse iteration, which is way less common than
+ // forward. Lazily initialize it to save memory.
+ std::unique_ptr<MergerMaxIterHeap> maxHeap_;
+ PinnedIteratorsManager* pinned_iters_mgr_;
+
+ // In forward direction, process a child that is not in the min heap.
+ // If valid, add to the min heap. Otherwise, check status.
+ void AddToMinHeapOrCheckStatus(IteratorWrapper*);
+
+ // In backward direction, process a child that is not in the max heap.
+ // If valid, add to the min heap. Otherwise, check status.
+ void AddToMaxHeapOrCheckStatus(IteratorWrapper*);
+
+ void SwitchToForward();
+
+ // Switch the direction from forward to backward without changing the
+ // position. Iterator should still be valid.
+ void SwitchToBackward();
+
+ IteratorWrapper* CurrentForward() const {
+ assert(direction_ == kForward);
+ return !minHeap_.empty() ? minHeap_.top() : nullptr;
+ }
+
+ IteratorWrapper* CurrentReverse() const {
+ assert(direction_ == kReverse);
+ assert(maxHeap_);
+ return !maxHeap_->empty() ? maxHeap_->top() : nullptr;
+ }
+};
+
+void MergingIterator::AddToMinHeapOrCheckStatus(IteratorWrapper* child) {
+ if (child->Valid()) {
+ assert(child->status().ok());
+ minHeap_.push(child);
+ } else {
+ considerStatus(child->status());
+ }
+}
+
+void MergingIterator::AddToMaxHeapOrCheckStatus(IteratorWrapper* child) {
+ if (child->Valid()) {
+ assert(child->status().ok());
+ maxHeap_->push(child);
+ } else {
+ considerStatus(child->status());
+ }
+}
+
+void MergingIterator::SwitchToForward() {
+ // Otherwise, advance the non-current children. We advance current_
+ // just after the if-block.
+ ClearHeaps();
+ Slice target = key();
+ for (auto& child : children_) {
+ if (&child != current_) {
+ child.Seek(target);
+ if (child.Valid() && comparator_->Equal(target, child.key())) {
+ assert(child.status().ok());
+ child.Next();
+ }
+ }
+ AddToMinHeapOrCheckStatus(&child);
+ }
+ direction_ = kForward;
+}
+
+void MergingIterator::SwitchToBackward() {
+ ClearHeaps();
+ InitMaxHeap();
+ Slice target = key();
+ for (auto& child : children_) {
+ if (&child != current_) {
+ child.SeekForPrev(target);
+ TEST_SYNC_POINT_CALLBACK("MergeIterator::Prev:BeforePrev", &child);
+ if (child.Valid() && comparator_->Equal(target, child.key())) {
+ assert(child.status().ok());
+ child.Prev();
+ }
+ }
+ AddToMaxHeapOrCheckStatus(&child);
+ }
+ direction_ = kReverse;
+ if (!prefix_seek_mode_) {
+ // Note that we don't do assert(current_ == CurrentReverse()) here
+ // because it is possible to have some keys larger than the seek-key
+ // inserted between Seek() and SeekToLast(), which makes current_ not
+ // equal to CurrentReverse().
+ current_ = CurrentReverse();
+ }
+ assert(current_ == CurrentReverse());
+}
+
+void MergingIterator::ClearHeaps() {
+ minHeap_.clear();
+ if (maxHeap_) {
+ maxHeap_->clear();
+ }
+}
+
+void MergingIterator::InitMaxHeap() {
+ if (!maxHeap_) {
+ maxHeap_.reset(new MergerMaxIterHeap(comparator_));
+ }
+}
+
+InternalIterator* NewMergingIterator(const InternalKeyComparator* cmp,
+ InternalIterator** list, int n,
+ Arena* arena, bool prefix_seek_mode) {
+ assert(n >= 0);
+ if (n == 0) {
+ return NewEmptyInternalIterator<Slice>(arena);
+ } else if (n == 1) {
+ return list[0];
+ } else {
+ if (arena == nullptr) {
+ return new MergingIterator(cmp, list, n, false, prefix_seek_mode);
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(MergingIterator));
+ return new (mem) MergingIterator(cmp, list, n, true, prefix_seek_mode);
+ }
+ }
+}
+
+MergeIteratorBuilder::MergeIteratorBuilder(
+ const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode)
+ : first_iter(nullptr), use_merging_iter(false), arena(a) {
+ auto mem = arena->AllocateAligned(sizeof(MergingIterator));
+ merge_iter =
+ new (mem) MergingIterator(comparator, nullptr, 0, true, prefix_seek_mode);
+}
+
+MergeIteratorBuilder::~MergeIteratorBuilder() {
+ if (first_iter != nullptr) {
+ first_iter->~InternalIterator();
+ }
+ if (merge_iter != nullptr) {
+ merge_iter->~MergingIterator();
+ }
+}
+
+void MergeIteratorBuilder::AddIterator(InternalIterator* iter) {
+ if (!use_merging_iter && first_iter != nullptr) {
+ merge_iter->AddIterator(first_iter);
+ use_merging_iter = true;
+ first_iter = nullptr;
+ }
+ if (use_merging_iter) {
+ merge_iter->AddIterator(iter);
+ } else {
+ first_iter = iter;
+ }
+}
+
+InternalIterator* MergeIteratorBuilder::Finish() {
+ InternalIterator* ret = nullptr;
+ if (!use_merging_iter) {
+ ret = first_iter;
+ first_iter = nullptr;
+ } else {
+ ret = merge_iter;
+ merge_iter = nullptr;
+ }
+ return ret;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/merging_iterator.h b/storage/rocksdb/rocksdb/table/merging_iterator.h
new file mode 100644
index 00000000..c4429bf5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/merging_iterator.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/dbformat.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Comparator;
+class Env;
+class Arena;
+template <class TValue>
+class InternalIteratorBase;
+using InternalIterator = InternalIteratorBase<Slice>;
+
+// Return an iterator that provided the union of the data in
+// children[0,n-1]. Takes ownership of the child iterators and
+// will delete them when the result iterator is deleted.
+//
+// The result does no duplicate suppression. I.e., if a particular
+// key is present in K child iterators, it will be yielded K times.
+//
+// REQUIRES: n >= 0
+extern InternalIterator* NewMergingIterator(
+ const InternalKeyComparator* comparator, InternalIterator** children, int n,
+ Arena* arena = nullptr, bool prefix_seek_mode = false);
+
+class MergingIterator;
+
+// A builder class to build a merging iterator by adding iterators one by one.
+class MergeIteratorBuilder {
+ public:
+ // comparator: the comparator used in merging comparator
+ // arena: where the merging iterator needs to be allocated from.
+ explicit MergeIteratorBuilder(const InternalKeyComparator* comparator,
+ Arena* arena, bool prefix_seek_mode = false);
+ ~MergeIteratorBuilder();
+
+ // Add iter to the merging iterator.
+ void AddIterator(InternalIterator* iter);
+
+ // Get arena used to build the merging iterator. It is called one a child
+ // iterator needs to be allocated.
+ Arena* GetArena() { return arena; }
+
+ // Return the result merging iterator.
+ InternalIterator* Finish();
+
+ private:
+ MergingIterator* merge_iter;
+ InternalIterator* first_iter;
+ bool use_merging_iter;
+ Arena* arena;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/meta_blocks.cc b/storage/rocksdb/rocksdb/table/meta_blocks.cc
new file mode 100644
index 00000000..6920bb14
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/meta_blocks.cc
@@ -0,0 +1,525 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/meta_blocks.h"
+
+#include <map>
+#include <string>
+
+#include "block_fetcher.h"
+#include "db/table_properties_collector.h"
+#include "file/random_access_file_reader.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "table/block_based/block.h"
+#include "table/format.h"
+#include "table/internal_iterator.h"
+#include "table/persistent_cache_helper.h"
+#include "table/table_properties_internal.h"
+#include "test_util/sync_point.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+MetaIndexBuilder::MetaIndexBuilder()
+ : meta_index_block_(new BlockBuilder(1 /* restart interval */)) {}
+
+void MetaIndexBuilder::Add(const std::string& key,
+ const BlockHandle& handle) {
+ std::string handle_encoding;
+ handle.EncodeTo(&handle_encoding);
+ meta_block_handles_.insert({key, handle_encoding});
+}
+
+Slice MetaIndexBuilder::Finish() {
+ for (const auto& metablock : meta_block_handles_) {
+ meta_index_block_->Add(metablock.first, metablock.second);
+ }
+ return meta_index_block_->Finish();
+}
+
+// Property block will be read sequentially and cached in a heap located
+// object, so there's no need for restart points. Thus we set the restart
+// interval to infinity to save space.
+PropertyBlockBuilder::PropertyBlockBuilder()
+ : properties_block_(
+ new BlockBuilder(port::kMaxInt32 /* restart interval */)) {}
+
+void PropertyBlockBuilder::Add(const std::string& name,
+ const std::string& val) {
+ props_.insert({name, val});
+}
+
+void PropertyBlockBuilder::Add(const std::string& name, uint64_t val) {
+ assert(props_.find(name) == props_.end());
+
+ std::string dst;
+ PutVarint64(&dst, val);
+
+ Add(name, dst);
+}
+
+void PropertyBlockBuilder::Add(
+ const UserCollectedProperties& user_collected_properties) {
+ for (const auto& prop : user_collected_properties) {
+ Add(prop.first, prop.second);
+ }
+}
+
+void PropertyBlockBuilder::AddTableProperty(const TableProperties& props) {
+ TEST_SYNC_POINT_CALLBACK("PropertyBlockBuilder::AddTableProperty:Start",
+ const_cast<TableProperties*>(&props));
+
+ Add(TablePropertiesNames::kRawKeySize, props.raw_key_size);
+ Add(TablePropertiesNames::kRawValueSize, props.raw_value_size);
+ Add(TablePropertiesNames::kDataSize, props.data_size);
+ Add(TablePropertiesNames::kIndexSize, props.index_size);
+ if (props.index_partitions != 0) {
+ Add(TablePropertiesNames::kIndexPartitions, props.index_partitions);
+ Add(TablePropertiesNames::kTopLevelIndexSize, props.top_level_index_size);
+ }
+ Add(TablePropertiesNames::kIndexKeyIsUserKey, props.index_key_is_user_key);
+ Add(TablePropertiesNames::kIndexValueIsDeltaEncoded,
+ props.index_value_is_delta_encoded);
+ Add(TablePropertiesNames::kNumEntries, props.num_entries);
+ Add(TablePropertiesNames::kDeletedKeys, props.num_deletions);
+ Add(TablePropertiesNames::kMergeOperands, props.num_merge_operands);
+ Add(TablePropertiesNames::kNumRangeDeletions, props.num_range_deletions);
+ Add(TablePropertiesNames::kNumDataBlocks, props.num_data_blocks);
+ Add(TablePropertiesNames::kFilterSize, props.filter_size);
+ Add(TablePropertiesNames::kFormatVersion, props.format_version);
+ Add(TablePropertiesNames::kFixedKeyLen, props.fixed_key_len);
+ Add(TablePropertiesNames::kColumnFamilyId, props.column_family_id);
+ Add(TablePropertiesNames::kCreationTime, props.creation_time);
+ Add(TablePropertiesNames::kOldestKeyTime, props.oldest_key_time);
+ if (props.file_creation_time > 0) {
+ Add(TablePropertiesNames::kFileCreationTime, props.file_creation_time);
+ }
+
+ if (!props.filter_policy_name.empty()) {
+ Add(TablePropertiesNames::kFilterPolicy, props.filter_policy_name);
+ }
+ if (!props.comparator_name.empty()) {
+ Add(TablePropertiesNames::kComparator, props.comparator_name);
+ }
+
+ if (!props.merge_operator_name.empty()) {
+ Add(TablePropertiesNames::kMergeOperator, props.merge_operator_name);
+ }
+ if (!props.prefix_extractor_name.empty()) {
+ Add(TablePropertiesNames::kPrefixExtractorName,
+ props.prefix_extractor_name);
+ }
+ if (!props.property_collectors_names.empty()) {
+ Add(TablePropertiesNames::kPropertyCollectors,
+ props.property_collectors_names);
+ }
+ if (!props.column_family_name.empty()) {
+ Add(TablePropertiesNames::kColumnFamilyName, props.column_family_name);
+ }
+
+ if (!props.compression_name.empty()) {
+ Add(TablePropertiesNames::kCompression, props.compression_name);
+ }
+ if (!props.compression_options.empty()) {
+ Add(TablePropertiesNames::kCompressionOptions, props.compression_options);
+ }
+}
+
+Slice PropertyBlockBuilder::Finish() {
+ for (const auto& prop : props_) {
+ properties_block_->Add(prop.first, prop.second);
+ }
+
+ return properties_block_->Finish();
+}
+
+void LogPropertiesCollectionError(
+ Logger* info_log, const std::string& method, const std::string& name) {
+ assert(method == "Add" || method == "Finish");
+
+ std::string msg =
+ "Encountered error when calling TablePropertiesCollector::" +
+ method + "() with collector name: " + name;
+ ROCKS_LOG_ERROR(info_log, "%s", msg.c_str());
+}
+
+bool NotifyCollectTableCollectorsOnAdd(
+ const Slice& key, const Slice& value, uint64_t file_size,
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ Logger* info_log) {
+ bool all_succeeded = true;
+ for (auto& collector : collectors) {
+ Status s = collector->InternalAdd(key, value, file_size);
+ all_succeeded = all_succeeded && s.ok();
+ if (!s.ok()) {
+ LogPropertiesCollectionError(info_log, "Add" /* method */,
+ collector->Name());
+ }
+ }
+ return all_succeeded;
+}
+
+void NotifyCollectTableCollectorsOnBlockAdd(
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ const uint64_t blockRawBytes, const uint64_t blockCompressedBytesFast,
+ const uint64_t blockCompressedBytesSlow) {
+ for (auto& collector : collectors) {
+ collector->BlockAdd(blockRawBytes, blockCompressedBytesFast,
+ blockCompressedBytesSlow);
+ }
+}
+
+bool NotifyCollectTableCollectorsOnFinish(
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ Logger* info_log, PropertyBlockBuilder* builder) {
+ bool all_succeeded = true;
+ for (auto& collector : collectors) {
+ UserCollectedProperties user_collected_properties;
+ Status s = collector->Finish(&user_collected_properties);
+
+ all_succeeded = all_succeeded && s.ok();
+ if (!s.ok()) {
+ LogPropertiesCollectionError(info_log, "Finish" /* method */,
+ collector->Name());
+ } else {
+ builder->Add(user_collected_properties);
+ }
+ }
+
+ return all_succeeded;
+}
+
+Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer, const Footer& footer,
+ const ImmutableCFOptions& ioptions,
+ TableProperties** table_properties, bool verify_checksum,
+ BlockHandle* ret_block_handle,
+ CacheAllocationPtr* verification_buf,
+ bool /*compression_type_missing*/,
+ MemoryAllocator* memory_allocator) {
+ assert(table_properties);
+
+ Slice v = handle_value;
+ BlockHandle handle;
+ if (!handle.DecodeFrom(&v).ok()) {
+ return Status::InvalidArgument("Failed to decode properties block handle");
+ }
+
+ BlockContents block_contents;
+ ReadOptions read_options;
+ read_options.verify_checksums = verify_checksum;
+ Status s;
+ PersistentCacheOptions cache_options;
+
+ BlockFetcher block_fetcher(
+ file, prefetch_buffer, footer, read_options, handle, &block_contents,
+ ioptions, false /* decompress */, false /*maybe_compressed*/,
+ BlockType::kProperties, UncompressionDict::GetEmptyDict(), cache_options,
+ memory_allocator);
+ s = block_fetcher.ReadBlockContents();
+ // property block is never compressed. Need to add uncompress logic if we are
+ // to compress it..
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ Block properties_block(std::move(block_contents),
+ kDisableGlobalSequenceNumber);
+ DataBlockIter iter;
+ properties_block.NewDataIterator(BytewiseComparator(), BytewiseComparator(),
+ &iter);
+
+ auto new_table_properties = new TableProperties();
+ // All pre-defined properties of type uint64_t
+ std::unordered_map<std::string, uint64_t*> predefined_uint64_properties = {
+ {TablePropertiesNames::kDataSize, &new_table_properties->data_size},
+ {TablePropertiesNames::kIndexSize, &new_table_properties->index_size},
+ {TablePropertiesNames::kIndexPartitions,
+ &new_table_properties->index_partitions},
+ {TablePropertiesNames::kTopLevelIndexSize,
+ &new_table_properties->top_level_index_size},
+ {TablePropertiesNames::kIndexKeyIsUserKey,
+ &new_table_properties->index_key_is_user_key},
+ {TablePropertiesNames::kIndexValueIsDeltaEncoded,
+ &new_table_properties->index_value_is_delta_encoded},
+ {TablePropertiesNames::kFilterSize, &new_table_properties->filter_size},
+ {TablePropertiesNames::kRawKeySize, &new_table_properties->raw_key_size},
+ {TablePropertiesNames::kRawValueSize,
+ &new_table_properties->raw_value_size},
+ {TablePropertiesNames::kNumDataBlocks,
+ &new_table_properties->num_data_blocks},
+ {TablePropertiesNames::kNumEntries, &new_table_properties->num_entries},
+ {TablePropertiesNames::kDeletedKeys,
+ &new_table_properties->num_deletions},
+ {TablePropertiesNames::kMergeOperands,
+ &new_table_properties->num_merge_operands},
+ {TablePropertiesNames::kNumRangeDeletions,
+ &new_table_properties->num_range_deletions},
+ {TablePropertiesNames::kFormatVersion,
+ &new_table_properties->format_version},
+ {TablePropertiesNames::kFixedKeyLen,
+ &new_table_properties->fixed_key_len},
+ {TablePropertiesNames::kColumnFamilyId,
+ &new_table_properties->column_family_id},
+ {TablePropertiesNames::kCreationTime,
+ &new_table_properties->creation_time},
+ {TablePropertiesNames::kOldestKeyTime,
+ &new_table_properties->oldest_key_time},
+ {TablePropertiesNames::kFileCreationTime,
+ &new_table_properties->file_creation_time},
+ };
+
+ std::string last_key;
+ for (iter.SeekToFirstOrReport(); iter.Valid(); iter.NextOrReport()) {
+ s = iter.status();
+ if (!s.ok()) {
+ break;
+ }
+
+ auto key = iter.key().ToString();
+ // properties block should be strictly sorted with no duplicate key.
+ if (!last_key.empty() &&
+ BytewiseComparator()->Compare(key, last_key) <= 0) {
+ s = Status::Corruption("properties unsorted");
+ break;
+ }
+ last_key = key;
+
+ auto raw_val = iter.value();
+ auto pos = predefined_uint64_properties.find(key);
+
+ new_table_properties->properties_offsets.insert(
+ {key, handle.offset() + iter.ValueOffset()});
+
+ if (pos != predefined_uint64_properties.end()) {
+ if (key == TablePropertiesNames::kDeletedKeys ||
+ key == TablePropertiesNames::kMergeOperands) {
+ // Insert in user-collected properties for API backwards compatibility
+ new_table_properties->user_collected_properties.insert(
+ {key, raw_val.ToString()});
+ }
+ // handle predefined rocksdb properties
+ uint64_t val;
+ if (!GetVarint64(&raw_val, &val)) {
+ // skip malformed value
+ auto error_msg =
+ "Detect malformed value in properties meta-block:"
+ "\tkey: " + key + "\tval: " + raw_val.ToString();
+ ROCKS_LOG_ERROR(ioptions.info_log, "%s", error_msg.c_str());
+ continue;
+ }
+ *(pos->second) = val;
+ } else if (key == TablePropertiesNames::kFilterPolicy) {
+ new_table_properties->filter_policy_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kColumnFamilyName) {
+ new_table_properties->column_family_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kComparator) {
+ new_table_properties->comparator_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kMergeOperator) {
+ new_table_properties->merge_operator_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kPrefixExtractorName) {
+ new_table_properties->prefix_extractor_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kPropertyCollectors) {
+ new_table_properties->property_collectors_names = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kCompression) {
+ new_table_properties->compression_name = raw_val.ToString();
+ } else if (key == TablePropertiesNames::kCompressionOptions) {
+ new_table_properties->compression_options = raw_val.ToString();
+ } else {
+ // handle user-collected properties
+ new_table_properties->user_collected_properties.insert(
+ {key, raw_val.ToString()});
+ }
+ }
+ if (s.ok()) {
+ *table_properties = new_table_properties;
+ if (ret_block_handle != nullptr) {
+ *ret_block_handle = handle;
+ }
+ if (verification_buf != nullptr) {
+ size_t len = static_cast<size_t>(handle.size() + kBlockTrailerSize);
+ *verification_buf =
+ ROCKSDB_NAMESPACE::AllocateBlock(len, memory_allocator);
+ if (verification_buf->get() != nullptr) {
+ memcpy(verification_buf->get(), block_contents.data.data(), len);
+ }
+ }
+ } else {
+ delete new_table_properties;
+ }
+
+ return s;
+}
+
+Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ TableProperties** properties,
+ bool compression_type_missing,
+ MemoryAllocator* memory_allocator) {
+ // -- Read metaindex block
+ Footer footer;
+ auto s = ReadFooterFromFile(file, nullptr /* prefetch_buffer */, file_size,
+ &footer, table_magic_number);
+ if (!s.ok()) {
+ return s;
+ }
+
+ auto metaindex_handle = footer.metaindex_handle();
+ BlockContents metaindex_contents;
+ ReadOptions read_options;
+ read_options.verify_checksums = false;
+ PersistentCacheOptions cache_options;
+
+ BlockFetcher block_fetcher(
+ file, nullptr /* prefetch_buffer */, footer, read_options,
+ metaindex_handle, &metaindex_contents, ioptions, false /* decompress */,
+ false /*maybe_compressed*/, BlockType::kMetaIndex,
+ UncompressionDict::GetEmptyDict(), cache_options, memory_allocator);
+ s = block_fetcher.ReadBlockContents();
+ if (!s.ok()) {
+ return s;
+ }
+ // property blocks are never compressed. Need to add uncompress logic if we
+ // are to compress it.
+ Block metaindex_block(std::move(metaindex_contents),
+ kDisableGlobalSequenceNumber);
+ std::unique_ptr<InternalIterator> meta_iter(metaindex_block.NewDataIterator(
+ BytewiseComparator(), BytewiseComparator()));
+
+ // -- Read property block
+ bool found_properties_block = true;
+ s = SeekToPropertiesBlock(meta_iter.get(), &found_properties_block);
+ if (!s.ok()) {
+ return s;
+ }
+
+ TableProperties table_properties;
+ if (found_properties_block == true) {
+ s = ReadProperties(
+ meta_iter->value(), file, nullptr /* prefetch_buffer */, footer,
+ ioptions, properties, false /* verify_checksum */,
+ nullptr /* ret_block_hanel */, nullptr /* ret_block_contents */,
+ compression_type_missing, memory_allocator);
+ } else {
+ s = Status::NotFound();
+ }
+
+ return s;
+}
+
+Status FindMetaBlock(InternalIterator* meta_index_iter,
+ const std::string& meta_block_name,
+ BlockHandle* block_handle) {
+ meta_index_iter->Seek(meta_block_name);
+ if (meta_index_iter->status().ok() && meta_index_iter->Valid() &&
+ meta_index_iter->key() == meta_block_name) {
+ Slice v = meta_index_iter->value();
+ return block_handle->DecodeFrom(&v);
+ } else {
+ return Status::Corruption("Cannot find the meta block", meta_block_name);
+ }
+}
+
+Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ const std::string& meta_block_name,
+ BlockHandle* block_handle,
+ bool /*compression_type_missing*/,
+ MemoryAllocator* memory_allocator) {
+ Footer footer;
+ auto s = ReadFooterFromFile(file, nullptr /* prefetch_buffer */, file_size,
+ &footer, table_magic_number);
+ if (!s.ok()) {
+ return s;
+ }
+
+ auto metaindex_handle = footer.metaindex_handle();
+ BlockContents metaindex_contents;
+ ReadOptions read_options;
+ read_options.verify_checksums = false;
+ PersistentCacheOptions cache_options;
+ BlockFetcher block_fetcher(
+ file, nullptr /* prefetch_buffer */, footer, read_options,
+ metaindex_handle, &metaindex_contents, ioptions,
+ false /* do decompression */, false /*maybe_compressed*/,
+ BlockType::kMetaIndex, UncompressionDict::GetEmptyDict(), cache_options,
+ memory_allocator);
+ s = block_fetcher.ReadBlockContents();
+ if (!s.ok()) {
+ return s;
+ }
+ // meta blocks are never compressed. Need to add uncompress logic if we are to
+ // compress it.
+ Block metaindex_block(std::move(metaindex_contents),
+ kDisableGlobalSequenceNumber);
+
+ std::unique_ptr<InternalIterator> meta_iter;
+ meta_iter.reset(metaindex_block.NewDataIterator(BytewiseComparator(),
+ BytewiseComparator()));
+
+ return FindMetaBlock(meta_iter.get(), meta_block_name, block_handle);
+}
+
+Status ReadMetaBlock(RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ const std::string& meta_block_name, BlockType block_type,
+ BlockContents* contents, bool /*compression_type_missing*/,
+ MemoryAllocator* memory_allocator) {
+ Status status;
+ Footer footer;
+ status = ReadFooterFromFile(file, prefetch_buffer, file_size, &footer,
+ table_magic_number);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Reading metaindex block
+ auto metaindex_handle = footer.metaindex_handle();
+ BlockContents metaindex_contents;
+ ReadOptions read_options;
+ read_options.verify_checksums = false;
+ PersistentCacheOptions cache_options;
+
+ BlockFetcher block_fetcher(
+ file, prefetch_buffer, footer, read_options, metaindex_handle,
+ &metaindex_contents, ioptions, false /* decompress */,
+ false /*maybe_compressed*/, BlockType::kMetaIndex,
+ UncompressionDict::GetEmptyDict(), cache_options, memory_allocator);
+ status = block_fetcher.ReadBlockContents();
+ if (!status.ok()) {
+ return status;
+ }
+ // meta block is never compressed. Need to add uncompress logic if we are to
+ // compress it.
+
+ // Finding metablock
+ Block metaindex_block(std::move(metaindex_contents),
+ kDisableGlobalSequenceNumber);
+
+ std::unique_ptr<InternalIterator> meta_iter;
+ meta_iter.reset(metaindex_block.NewDataIterator(BytewiseComparator(),
+ BytewiseComparator()));
+
+ BlockHandle block_handle;
+ status = FindMetaBlock(meta_iter.get(), meta_block_name, &block_handle);
+
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Reading metablock
+ BlockFetcher block_fetcher2(
+ file, prefetch_buffer, footer, read_options, block_handle, contents,
+ ioptions, false /* decompress */, false /*maybe_compressed*/, block_type,
+ UncompressionDict::GetEmptyDict(), cache_options, memory_allocator);
+ return block_fetcher2.ReadBlockContents();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/meta_blocks.h b/storage/rocksdb/rocksdb/table/meta_blocks.h
new file mode 100644
index 00000000..0fd140cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/meta_blocks.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "db/builder.h"
+#include "db/table_properties_collector.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/memory_allocator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "table/block_based/block_builder.h"
+#include "table/block_based/block_type.h"
+#include "table/format.h"
+#include "util/kv_map.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBuilder;
+class BlockHandle;
+class Env;
+class Footer;
+class Logger;
+class RandomAccessFile;
+struct TableProperties;
+
+class MetaIndexBuilder {
+ public:
+ MetaIndexBuilder(const MetaIndexBuilder&) = delete;
+ MetaIndexBuilder& operator=(const MetaIndexBuilder&) = delete;
+
+ MetaIndexBuilder();
+ void Add(const std::string& key, const BlockHandle& handle);
+
+ // Write all the added key/value pairs to the block and return the contents
+ // of the block.
+ Slice Finish();
+
+ private:
+ // store the sorted key/handle of the metablocks.
+ stl_wrappers::KVMap meta_block_handles_;
+ std::unique_ptr<BlockBuilder> meta_index_block_;
+};
+
+class PropertyBlockBuilder {
+ public:
+ PropertyBlockBuilder(const PropertyBlockBuilder&) = delete;
+ PropertyBlockBuilder& operator=(const PropertyBlockBuilder&) = delete;
+
+ PropertyBlockBuilder();
+
+ void AddTableProperty(const TableProperties& props);
+ void Add(const std::string& key, uint64_t value);
+ void Add(const std::string& key, const std::string& value);
+ void Add(const UserCollectedProperties& user_collected_properties);
+
+ // Write all the added entries to the block and return the block contents
+ Slice Finish();
+
+ private:
+ std::unique_ptr<BlockBuilder> properties_block_;
+ stl_wrappers::KVMap props_;
+};
+
+// Were we encounter any error occurs during user-defined statistics collection,
+// we'll write the warning message to info log.
+void LogPropertiesCollectionError(
+ Logger* info_log, const std::string& method, const std::string& name);
+
+// Utility functions help table builder to trigger batch events for user
+// defined property collectors.
+// Return value indicates if there is any error occurred; if error occurred,
+// the warning message will be logged.
+// NotifyCollectTableCollectorsOnAdd() triggers the `Add` event for all
+// property collectors.
+bool NotifyCollectTableCollectorsOnAdd(
+ const Slice& key, const Slice& value, uint64_t file_size,
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ Logger* info_log);
+
+void NotifyCollectTableCollectorsOnBlockAdd(
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ uint64_t blockRawBytes, uint64_t blockCompressedBytesFast,
+ uint64_t blockCompressedBytesSlow);
+
+// NotifyCollectTableCollectorsOnFinish() triggers the `Finish` event for all
+// property collectors. The collected properties will be added to `builder`.
+bool NotifyCollectTableCollectorsOnFinish(
+ const std::vector<std::unique_ptr<IntTblPropCollector>>& collectors,
+ Logger* info_log, PropertyBlockBuilder* builder);
+
+// Read the properties from the table.
+// @returns a status to indicate if the operation succeeded. On success,
+// *table_properties will point to a heap-allocated TableProperties
+// object, otherwise value of `table_properties` will not be modified.
+Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer, const Footer& footer,
+ const ImmutableCFOptions& ioptions,
+ TableProperties** table_properties, bool verify_checksum,
+ BlockHandle* block_handle,
+ CacheAllocationPtr* verification_buf,
+ bool compression_type_missing = false,
+ MemoryAllocator* memory_allocator = nullptr);
+
+// Directly read the properties from the properties block of a plain table.
+// @returns a status to indicate if the operation succeeded. On success,
+// *table_properties will point to a heap-allocated TableProperties
+// object, otherwise value of `table_properties` will not be modified.
+// certain tables do not have compression_type byte setup properly for
+// uncompressed blocks, caller can request to reset compression type by
+// passing compression_type_missing = true, the same applies to
+// `ReadProperties`, `FindMetaBlock`, and `ReadMetaBlock`
+Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ TableProperties** properties,
+ bool compression_type_missing = false,
+ MemoryAllocator* memory_allocator = nullptr);
+
+// Find the meta block from the meta index block.
+Status FindMetaBlock(InternalIterator* meta_index_iter,
+ const std::string& meta_block_name,
+ BlockHandle* block_handle);
+
+// Find the meta block
+Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ const std::string& meta_block_name,
+ BlockHandle* block_handle,
+ bool compression_type_missing = false,
+ MemoryAllocator* memory_allocator = nullptr);
+
+// Read the specified meta block with name meta_block_name
+// from `file` and initialize `contents` with contents of this block.
+// Return Status::OK in case of success.
+Status ReadMetaBlock(RandomAccessFileReader* file,
+ FilePrefetchBuffer* prefetch_buffer, uint64_t file_size,
+ uint64_t table_magic_number,
+ const ImmutableCFOptions& ioptions,
+ const std::string& meta_block_name, BlockType block_type,
+ BlockContents* contents,
+ bool compression_type_missing = false,
+ MemoryAllocator* memory_allocator = nullptr);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/mock_table.cc b/storage/rocksdb/rocksdb/table/mock_table.cc
new file mode 100644
index 00000000..9ef44628
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/mock_table.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/mock_table.h"
+
+#include "db/dbformat.h"
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "port/port.h"
+#include "rocksdb/table_properties.h"
+#include "table/get_context.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace mock {
+
+namespace {
+
+const InternalKeyComparator icmp_(BytewiseComparator());
+
+} // namespace
+
+stl_wrappers::KVMap MakeMockFile(
+ std::initializer_list<std::pair<const std::string, std::string>> l) {
+ return stl_wrappers::KVMap(l, stl_wrappers::LessOfComparator(&icmp_));
+}
+
+InternalIterator* MockTableReader::NewIterator(
+ const ReadOptions&, const SliceTransform* /* prefix_extractor */,
+ Arena* /*arena*/, bool /*skip_filters*/, TableReaderCaller /*caller*/,
+ size_t /*compaction_readahead_size*/) {
+ return new MockTableIterator(table_);
+}
+
+Status MockTableReader::Get(const ReadOptions&, const Slice& key,
+ GetContext* get_context,
+ const SliceTransform* /*prefix_extractor*/,
+ bool /*skip_filters*/) {
+ std::unique_ptr<MockTableIterator> iter(new MockTableIterator(table_));
+ for (iter->Seek(key); iter->Valid(); iter->Next()) {
+ ParsedInternalKey parsed_key;
+ if (!ParseInternalKey(iter->key(), &parsed_key)) {
+ return Status::Corruption(Slice());
+ }
+
+ bool dont_care __attribute__((__unused__));
+ if (!get_context->SaveValue(parsed_key, iter->value(), &dont_care)) {
+ break;
+ }
+ }
+ return Status::OK();
+}
+
+std::shared_ptr<const TableProperties> MockTableReader::GetTableProperties()
+ const {
+ return std::shared_ptr<const TableProperties>(new TableProperties());
+}
+
+MockTableFactory::MockTableFactory() : next_id_(1) {}
+
+Status MockTableFactory::NewTableReader(
+ const TableReaderOptions& /*table_reader_options*/,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t /*file_size*/,
+ std::unique_ptr<TableReader>* table_reader,
+ bool /*prefetch_index_and_filter_in_cache*/) const {
+ uint32_t id = GetIDFromFile(file.get());
+
+ MutexLock lock_guard(&file_system_.mutex);
+
+ auto it = file_system_.files.find(id);
+ if (it == file_system_.files.end()) {
+ return Status::IOError("Mock file not found");
+ }
+
+ table_reader->reset(new MockTableReader(it->second));
+
+ return Status::OK();
+}
+
+TableBuilder* MockTableFactory::NewTableBuilder(
+ const TableBuilderOptions& /*table_builder_options*/,
+ uint32_t /*column_family_id*/, WritableFileWriter* file) const {
+ uint32_t id = GetAndWriteNextID(file);
+
+ return new MockTableBuilder(id, &file_system_);
+}
+
+Status MockTableFactory::CreateMockTable(Env* env, const std::string& fname,
+ stl_wrappers::KVMap file_contents) {
+ std::unique_ptr<WritableFile> file;
+ auto s = env->NewWritableFile(fname, &file, EnvOptions());
+ if (!s.ok()) {
+ return s;
+ }
+
+ WritableFileWriter file_writer(NewLegacyWritableFileWrapper(std::move(file)),
+ fname, EnvOptions());
+
+ uint32_t id = GetAndWriteNextID(&file_writer);
+ file_system_.files.insert({id, std::move(file_contents)});
+ return Status::OK();
+}
+
+uint32_t MockTableFactory::GetAndWriteNextID(WritableFileWriter* file) const {
+ uint32_t next_id = next_id_.fetch_add(1);
+ char buf[4];
+ EncodeFixed32(buf, next_id);
+ file->Append(Slice(buf, 4));
+ return next_id;
+}
+
+uint32_t MockTableFactory::GetIDFromFile(RandomAccessFileReader* file) const {
+ char buf[4];
+ Slice result;
+ file->Read(0, 4, &result, buf);
+ assert(result.size() == 4);
+ return DecodeFixed32(buf);
+}
+
+void MockTableFactory::AssertSingleFile(
+ const stl_wrappers::KVMap& file_contents) {
+ ASSERT_EQ(file_system_.files.size(), 1U);
+ ASSERT_EQ(file_contents, file_system_.files.begin()->second);
+}
+
+void MockTableFactory::AssertLatestFile(
+ const stl_wrappers::KVMap& file_contents) {
+ ASSERT_GE(file_system_.files.size(), 1U);
+ auto latest = file_system_.files.end();
+ --latest;
+
+ if (file_contents != latest->second) {
+ std::cout << "Wrong content! Content of latest file:" << std::endl;
+ for (const auto& kv : latest->second) {
+ ParsedInternalKey ikey;
+ std::string key, value;
+ std::tie(key, value) = kv;
+ ParseInternalKey(Slice(key), &ikey);
+ std::cout << ikey.DebugString(false) << " -> " << value << std::endl;
+ }
+ FAIL();
+ }
+}
+
+} // namespace mock
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/mock_table.h b/storage/rocksdb/rocksdb/table/mock_table.h
new file mode 100644
index 00000000..9e80c8d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/mock_table.h
@@ -0,0 +1,214 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <atomic>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "db/version_edit.h"
+#include "port/port.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/table.h"
+#include "table/internal_iterator.h"
+#include "table/table_builder.h"
+#include "table/table_reader.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/kv_map.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace mock {
+
+stl_wrappers::KVMap MakeMockFile(
+ std::initializer_list<std::pair<const std::string, std::string>> l = {});
+
+struct MockTableFileSystem {
+ port::Mutex mutex;
+ std::map<uint32_t, stl_wrappers::KVMap> files;
+};
+
+class MockTableReader : public TableReader {
+ public:
+ explicit MockTableReader(const stl_wrappers::KVMap& table) : table_(table) {}
+
+ InternalIterator* NewIterator(const ReadOptions&,
+ const SliceTransform* prefix_extractor,
+ Arena* arena, bool skip_filters,
+ TableReaderCaller caller,
+ size_t compaction_readahead_size = 0) override;
+
+ Status Get(const ReadOptions& readOptions, const Slice& key,
+ GetContext* get_context, const SliceTransform* prefix_extractor,
+ bool skip_filters = false) override;
+
+ uint64_t ApproximateOffsetOf(const Slice& /*key*/,
+ TableReaderCaller /*caller*/) override {
+ return 0;
+ }
+
+ uint64_t ApproximateSize(const Slice& /*start*/, const Slice& /*end*/,
+ TableReaderCaller /*caller*/) override {
+ return 0;
+ }
+
+ size_t ApproximateMemoryUsage() const override { return 0; }
+
+ void SetupForCompaction() override {}
+
+ std::shared_ptr<const TableProperties> GetTableProperties() const override;
+
+ ~MockTableReader() {}
+
+ private:
+ const stl_wrappers::KVMap& table_;
+};
+
+class MockTableIterator : public InternalIterator {
+ public:
+ explicit MockTableIterator(const stl_wrappers::KVMap& table) : table_(table) {
+ itr_ = table_.end();
+ }
+
+ bool Valid() const override { return itr_ != table_.end(); }
+
+ void SeekToFirst() override { itr_ = table_.begin(); }
+
+ void SeekToLast() override {
+ itr_ = table_.end();
+ --itr_;
+ }
+
+ void Seek(const Slice& target) override {
+ std::string str_target(target.data(), target.size());
+ itr_ = table_.lower_bound(str_target);
+ }
+
+ void SeekForPrev(const Slice& target) override {
+ std::string str_target(target.data(), target.size());
+ itr_ = table_.upper_bound(str_target);
+ Prev();
+ }
+
+ void Next() override { ++itr_; }
+
+ void Prev() override {
+ if (itr_ == table_.begin()) {
+ itr_ = table_.end();
+ } else {
+ --itr_;
+ }
+ }
+
+ Slice key() const override { return Slice(itr_->first); }
+
+ Slice value() const override { return Slice(itr_->second); }
+
+ Status status() const override { return Status::OK(); }
+
+ private:
+ const stl_wrappers::KVMap& table_;
+ stl_wrappers::KVMap::const_iterator itr_;
+};
+
+class MockTableBuilder : public TableBuilder {
+ public:
+ MockTableBuilder(uint32_t id, MockTableFileSystem* file_system)
+ : id_(id), file_system_(file_system) {
+ table_ = MakeMockFile({});
+ }
+
+ // REQUIRES: Either Finish() or Abandon() has been called.
+ ~MockTableBuilder() {}
+
+ // Add key,value to the table being constructed.
+ // REQUIRES: key is after any previously added key according to comparator.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Add(const Slice& key, const Slice& value) override {
+ table_.insert({key.ToString(), value.ToString()});
+ }
+
+ // Return non-ok iff some error has been detected.
+ Status status() const override { return Status::OK(); }
+
+ Status Finish() override {
+ MutexLock lock_guard(&file_system_->mutex);
+ file_system_->files.insert({id_, table_});
+ return Status::OK();
+ }
+
+ void Abandon() override {}
+
+ uint64_t NumEntries() const override { return table_.size(); }
+
+ uint64_t FileSize() const override { return table_.size(); }
+
+ TableProperties GetTableProperties() const override {
+ return TableProperties();
+ }
+
+ // Get file checksum
+ const std::string& GetFileChecksum() const override { return file_checksum_; }
+ // Get file checksum function name
+ const char* GetFileChecksumFuncName() const override {
+ return kUnknownFileChecksumFuncName.c_str();
+ }
+
+ private:
+ uint32_t id_;
+ MockTableFileSystem* file_system_;
+ stl_wrappers::KVMap table_;
+ std::string file_checksum_ = kUnknownFileChecksum;
+};
+
+class MockTableFactory : public TableFactory {
+ public:
+ MockTableFactory();
+ const char* Name() const override { return "MockTable"; }
+ Status NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader,
+ bool prefetch_index_and_filter_in_cache = true) const override;
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_familly_id, WritableFileWriter* file) const override;
+
+ // This function will directly create mock table instead of going through
+ // MockTableBuilder. file_contents has to have a format of <internal_key,
+ // value>. Those key-value pairs will then be inserted into the mock table.
+ Status CreateMockTable(Env* env, const std::string& fname,
+ stl_wrappers::KVMap file_contents);
+
+ virtual Status SanitizeOptions(
+ const DBOptions& /*db_opts*/,
+ const ColumnFamilyOptions& /*cf_opts*/) const override {
+ return Status::OK();
+ }
+
+ virtual std::string GetPrintableTableOptions() const override {
+ return std::string();
+ }
+
+ // This function will assert that only a single file exists and that the
+ // contents are equal to file_contents
+ void AssertSingleFile(const stl_wrappers::KVMap& file_contents);
+ void AssertLatestFile(const stl_wrappers::KVMap& file_contents);
+
+ private:
+ uint32_t GetAndWriteNextID(WritableFileWriter* file) const;
+ uint32_t GetIDFromFile(RandomAccessFileReader* file) const;
+
+ mutable MockTableFileSystem file_system_;
+ mutable std::atomic<uint32_t> next_id_;
+};
+
+} // namespace mock
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/multiget_context.h b/storage/rocksdb/rocksdb/table/multiget_context.h
new file mode 100644
index 00000000..0c5848c8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/multiget_context.h
@@ -0,0 +1,259 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <array>
+#include <string>
+#include "db/lookup_key.h"
+#include "db/merge_context.h"
+#include "rocksdb/env.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/types.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+class GetContext;
+
+struct KeyContext {
+ const Slice* key;
+ LookupKey* lkey;
+ Slice ukey;
+ Slice ikey;
+ ColumnFamilyHandle* column_family;
+ Status* s;
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq;
+ bool key_exists;
+ void* cb_arg;
+ PinnableSlice* value;
+ GetContext* get_context;
+
+ KeyContext(ColumnFamilyHandle* col_family, const Slice& user_key,
+ PinnableSlice* val, Status* stat)
+ : key(&user_key),
+ lkey(nullptr),
+ column_family(col_family),
+ s(stat),
+ max_covering_tombstone_seq(0),
+ key_exists(false),
+ cb_arg(nullptr),
+ value(val),
+ get_context(nullptr) {}
+
+ KeyContext() = default;
+};
+
+// The MultiGetContext class is a container for the sorted list of keys that
+// we need to lookup in a batch. Its main purpose is to make batch execution
+// easier by allowing various stages of the MultiGet lookups to operate on
+// subsets of keys, potentially non-contiguous. In order to accomplish this,
+// it defines the following classes -
+//
+// MultiGetContext::Range
+// MultiGetContext::Range::Iterator
+// MultiGetContext::Range::IteratorWrapper
+//
+// Here is an example of how this can be used -
+//
+// {
+// MultiGetContext ctx(...);
+// MultiGetContext::Range range = ctx.GetMultiGetRange();
+//
+// // Iterate to determine some subset of the keys
+// MultiGetContext::Range::Iterator start = range.begin();
+// MultiGetContext::Range::Iterator end = ...;
+//
+// // Make a new range with a subset of keys
+// MultiGetContext::Range subrange(range, start, end);
+//
+// // Define an auxillary vector, if needed, to hold additional data for
+// // each key
+// std::array<Foo, MultiGetContext::MAX_BATCH_SIZE> aux;
+//
+// // Iterate over the subrange and the auxillary vector simultaneously
+// MultiGetContext::Range::Iterator iter = subrange.begin();
+// for (; iter != subrange.end(); ++iter) {
+// KeyContext& key = *iter;
+// Foo& aux_key = aux_iter[iter.index()];
+// ...
+// }
+// }
+class MultiGetContext {
+ public:
+ // Limit the number of keys in a batch to this number. Benchmarks show that
+ // there is negligible benefit for batches exceeding this. Keeping this < 64
+ // simplifies iteration, as well as reduces the amount of stack allocations
+ // htat need to be performed
+ static const int MAX_BATCH_SIZE = 32;
+
+ MultiGetContext(autovector<KeyContext*, MAX_BATCH_SIZE>* sorted_keys,
+ size_t begin, size_t num_keys, SequenceNumber snapshot)
+ : num_keys_(num_keys),
+ value_mask_(0),
+ lookup_key_ptr_(reinterpret_cast<LookupKey*>(lookup_key_stack_buf)) {
+ if (num_keys > MAX_LOOKUP_KEYS_ON_STACK) {
+ lookup_key_heap_buf.reset(new char[sizeof(LookupKey) * num_keys]);
+ lookup_key_ptr_ = reinterpret_cast<LookupKey*>(
+ lookup_key_heap_buf.get());
+ }
+
+ for (size_t iter = 0; iter != num_keys_; ++iter) {
+ // autovector may not be contiguous storage, so make a copy
+ sorted_keys_[iter] = (*sorted_keys)[begin + iter];
+ sorted_keys_[iter]->lkey = new (&lookup_key_ptr_[iter])
+ LookupKey(*sorted_keys_[iter]->key, snapshot);
+ sorted_keys_[iter]->ukey = sorted_keys_[iter]->lkey->user_key();
+ sorted_keys_[iter]->ikey = sorted_keys_[iter]->lkey->internal_key();
+ }
+ }
+
+ ~MultiGetContext() {
+ for (size_t i = 0; i < num_keys_; ++i) {
+ lookup_key_ptr_[i].~LookupKey();
+ }
+ }
+
+ private:
+ static const int MAX_LOOKUP_KEYS_ON_STACK = 16;
+ alignas(alignof(LookupKey))
+ char lookup_key_stack_buf[sizeof(LookupKey) * MAX_LOOKUP_KEYS_ON_STACK];
+ std::array<KeyContext*, MAX_BATCH_SIZE> sorted_keys_;
+ size_t num_keys_;
+ uint64_t value_mask_;
+ std::unique_ptr<char[]> lookup_key_heap_buf;
+ LookupKey* lookup_key_ptr_;
+
+ public:
+ // MultiGetContext::Range - Specifies a range of keys, by start and end index,
+ // from the parent MultiGetContext. Each range contains a bit vector that
+ // indicates whether the corresponding keys need to be processed or skipped.
+ // A Range object can be copy constructed, and the new object inherits the
+ // original Range's bit vector. This is useful for progressively skipping
+ // keys as the lookup goes through various stages. For example, when looking
+ // up keys in the same SST file, a Range is created excluding keys not
+ // belonging to that file. A new Range is then copy constructed and individual
+ // keys are skipped based on bloom filter lookup.
+ class Range {
+ public:
+ // MultiGetContext::Range::Iterator - A forward iterator that iterates over
+ // non-skippable keys in a Range, as well as keys whose final value has been
+ // found. The latter is tracked by MultiGetContext::value_mask_
+ class Iterator {
+ public:
+ // -- iterator traits
+ typedef Iterator self_type;
+ typedef KeyContext value_type;
+ typedef KeyContext& reference;
+ typedef KeyContext* pointer;
+ typedef int difference_type;
+ typedef std::forward_iterator_tag iterator_category;
+
+ Iterator(const Range* range, size_t idx)
+ : range_(range), ctx_(range->ctx_), index_(idx) {
+ while (index_ < range_->end_ &&
+ (1ull << index_) &
+ (range_->ctx_->value_mask_ | range_->skip_mask_))
+ index_++;
+ }
+
+ Iterator(const Iterator&) = default;
+ Iterator& operator=(const Iterator&) = default;
+
+ Iterator& operator++() {
+ while (++index_ < range_->end_ &&
+ (1ull << index_) &
+ (range_->ctx_->value_mask_ | range_->skip_mask_))
+ ;
+ return *this;
+ }
+
+ bool operator==(Iterator other) const {
+ assert(range_->ctx_ == other.range_->ctx_);
+ return index_ == other.index_;
+ }
+
+ bool operator!=(Iterator other) const {
+ assert(range_->ctx_ == other.range_->ctx_);
+ return index_ != other.index_;
+ }
+
+ KeyContext& operator*() {
+ assert(index_ < range_->end_ && index_ >= range_->start_);
+ return *(ctx_->sorted_keys_[index_]);
+ }
+
+ KeyContext* operator->() {
+ assert(index_ < range_->end_ && index_ >= range_->start_);
+ return ctx_->sorted_keys_[index_];
+ }
+
+ size_t index() { return index_; }
+
+ private:
+ friend Range;
+ const Range* range_;
+ const MultiGetContext* ctx_;
+ size_t index_;
+ };
+
+ Range(const Range& mget_range,
+ const Iterator& first,
+ const Iterator& last) {
+ ctx_ = mget_range.ctx_;
+ start_ = first.index_;
+ end_ = last.index_;
+ skip_mask_ = mget_range.skip_mask_;
+ }
+
+ Range() = default;
+
+ Iterator begin() const { return Iterator(this, start_); }
+
+ Iterator end() const { return Iterator(this, end_); }
+
+ bool empty() {
+ return (((1ull << end_) - 1) & ~((1ull << start_) - 1) &
+ ~(ctx_->value_mask_ | skip_mask_)) == 0;
+ }
+
+ void SkipKey(const Iterator& iter) { skip_mask_ |= 1ull << iter.index_; }
+
+ // Update the value_mask_ in MultiGetContext so its
+ // immediately reflected in all the Range Iterators
+ void MarkKeyDone(Iterator& iter) {
+ ctx_->value_mask_ |= (1ull << iter.index_);
+ }
+
+ bool CheckKeyDone(Iterator& iter) {
+ return ctx_->value_mask_ & (1ull << iter.index_);
+ }
+
+ uint64_t KeysLeft() {
+ uint64_t new_val = skip_mask_ | ctx_->value_mask_;
+ uint64_t count = 0;
+ while (new_val) {
+ new_val = new_val & (new_val - 1);
+ count++;
+ }
+ return end_ - count;
+ }
+
+ private:
+ friend MultiGetContext;
+ MultiGetContext* ctx_;
+ size_t start_;
+ size_t end_;
+ uint64_t skip_mask_;
+
+ Range(MultiGetContext* ctx, size_t num_keys)
+ : ctx_(ctx), start_(0), end_(num_keys), skip_mask_(0) {}
+ };
+
+ // Return the initial range that encompasses all the keys in the batch
+ Range GetMultiGetRange() { return Range(this, num_keys_); }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/persistent_cache_helper.cc b/storage/rocksdb/rocksdb/table/persistent_cache_helper.cc
new file mode 100644
index 00000000..8797c9b9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/persistent_cache_helper.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/persistent_cache_helper.h"
+#include "table/block_based/block_based_table_reader.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void PersistentCacheHelper::InsertRawPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ const char* data, const size_t size) {
+ assert(cache_options.persistent_cache);
+ assert(cache_options.persistent_cache->IsCompressed());
+
+ // construct the page key
+ char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ auto key = BlockBasedTable::GetCacheKey(cache_options.key_prefix.c_str(),
+ cache_options.key_prefix.size(),
+ handle, cache_key);
+ // insert content to cache
+ cache_options.persistent_cache->Insert(key, data, size);
+}
+
+void PersistentCacheHelper::InsertUncompressedPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ const BlockContents& contents) {
+ assert(cache_options.persistent_cache);
+ assert(!cache_options.persistent_cache->IsCompressed());
+ // Precondition:
+ // (1) content is cacheable
+ // (2) content is not compressed
+
+ // construct the page key
+ char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ auto key = BlockBasedTable::GetCacheKey(cache_options.key_prefix.c_str(),
+ cache_options.key_prefix.size(),
+ handle, cache_key);
+ // insert block contents to page cache
+ cache_options.persistent_cache->Insert(key, contents.data.data(),
+ contents.data.size());
+}
+
+Status PersistentCacheHelper::LookupRawPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ std::unique_ptr<char[]>* raw_data, const size_t raw_data_size) {
+#ifdef NDEBUG
+ (void)raw_data_size;
+#endif
+ assert(cache_options.persistent_cache);
+ assert(cache_options.persistent_cache->IsCompressed());
+
+ // construct the page key
+ char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ auto key = BlockBasedTable::GetCacheKey(cache_options.key_prefix.c_str(),
+ cache_options.key_prefix.size(),
+ handle, cache_key);
+ // Lookup page
+ size_t size;
+ Status s = cache_options.persistent_cache->Lookup(key, raw_data, &size);
+ if (!s.ok()) {
+ // cache miss
+ RecordTick(cache_options.statistics, PERSISTENT_CACHE_MISS);
+ return s;
+ }
+
+ // cache hit
+ assert(raw_data_size == handle.size() + kBlockTrailerSize);
+ assert(size == raw_data_size);
+ RecordTick(cache_options.statistics, PERSISTENT_CACHE_HIT);
+ return Status::OK();
+}
+
+Status PersistentCacheHelper::LookupUncompressedPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ BlockContents* contents) {
+ assert(cache_options.persistent_cache);
+ assert(!cache_options.persistent_cache->IsCompressed());
+ if (!contents) {
+ // We shouldn't lookup in the cache. Either
+ // (1) Nowhere to store
+ return Status::NotFound();
+ }
+
+ // construct the page key
+ char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length];
+ auto key = BlockBasedTable::GetCacheKey(cache_options.key_prefix.c_str(),
+ cache_options.key_prefix.size(),
+ handle, cache_key);
+ // Lookup page
+ std::unique_ptr<char[]> data;
+ size_t size;
+ Status s = cache_options.persistent_cache->Lookup(key, &data, &size);
+ if (!s.ok()) {
+ // cache miss
+ RecordTick(cache_options.statistics, PERSISTENT_CACHE_MISS);
+ return s;
+ }
+
+ // please note we are potentially comparing compressed data size with
+ // uncompressed data size
+ assert(handle.size() <= size);
+
+ // update stats
+ RecordTick(cache_options.statistics, PERSISTENT_CACHE_HIT);
+ // construct result and return
+ *contents = BlockContents(std::move(data), size);
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/persistent_cache_helper.h b/storage/rocksdb/rocksdb/table/persistent_cache_helper.h
new file mode 100644
index 00000000..1db85572
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/persistent_cache_helper.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "monitoring/statistics.h"
+#include "table/format.h"
+#include "table/persistent_cache_options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct BlockContents;
+
+// PersistentCacheHelper
+//
+// Encapsulates some of the helper logic for read and writing from the cache
+class PersistentCacheHelper {
+ public:
+ // insert block into raw page cache
+ static void InsertRawPage(const PersistentCacheOptions& cache_options,
+ const BlockHandle& handle, const char* data,
+ const size_t size);
+
+ // insert block into uncompressed cache
+ static void InsertUncompressedPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ const BlockContents& contents);
+
+ // lookup block from raw page cacge
+ static Status LookupRawPage(const PersistentCacheOptions& cache_options,
+ const BlockHandle& handle,
+ std::unique_ptr<char[]>* raw_data,
+ const size_t raw_data_size);
+
+ // lookup block from uncompressed cache
+ static Status LookupUncompressedPage(
+ const PersistentCacheOptions& cache_options, const BlockHandle& handle,
+ BlockContents* contents);
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/persistent_cache_options.h b/storage/rocksdb/rocksdb/table/persistent_cache_options.h
new file mode 100644
index 00000000..7c65a041
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/persistent_cache_options.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+
+#include "monitoring/statistics.h"
+#include "rocksdb/persistent_cache.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// PersistentCacheOptions
+//
+// This describe the caching behavior for page cache
+// This is used to pass the context for caching and the cache handle
+struct PersistentCacheOptions {
+ PersistentCacheOptions() {}
+ explicit PersistentCacheOptions(
+ const std::shared_ptr<PersistentCache>& _persistent_cache,
+ const std::string _key_prefix, Statistics* const _statistics)
+ : persistent_cache(_persistent_cache),
+ key_prefix(_key_prefix),
+ statistics(_statistics) {}
+
+ virtual ~PersistentCacheOptions() {}
+
+ std::shared_ptr<PersistentCache> persistent_cache;
+ std::string key_prefix;
+ Statistics* statistics = nullptr;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.cc
new file mode 100644
index 00000000..7b683352
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/plain/plain_table_bloom.h"
+
+#include <algorithm>
+#include <string>
+#include "util/dynamic_bloom.h"
+
+#include "memory/allocator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+uint32_t GetTotalBitsForLocality(uint32_t total_bits) {
+ uint32_t num_blocks =
+ (total_bits + CACHE_LINE_SIZE * 8 - 1) / (CACHE_LINE_SIZE * 8);
+
+ // Make num_blocks an odd number to make sure more bits are involved
+ // when determining which block.
+ if (num_blocks % 2 == 0) {
+ num_blocks++;
+ }
+
+ return num_blocks * (CACHE_LINE_SIZE * 8);
+}
+} // namespace
+
+PlainTableBloomV1::PlainTableBloomV1(uint32_t num_probes)
+ : kTotalBits(0), kNumBlocks(0), kNumProbes(num_probes), data_(nullptr) {}
+
+void PlainTableBloomV1::SetRawData(char* raw_data, uint32_t total_bits,
+ uint32_t num_blocks) {
+ data_ = raw_data;
+ kTotalBits = total_bits;
+ kNumBlocks = num_blocks;
+}
+
+void PlainTableBloomV1::SetTotalBits(Allocator* allocator, uint32_t total_bits,
+ uint32_t locality,
+ size_t huge_page_tlb_size,
+ Logger* logger) {
+ kTotalBits = (locality > 0) ? GetTotalBitsForLocality(total_bits)
+ : (total_bits + 7) / 8 * 8;
+ kNumBlocks = (locality > 0) ? (kTotalBits / (CACHE_LINE_SIZE * 8)) : 0;
+
+ assert(kNumBlocks > 0 || kTotalBits > 0);
+ assert(kNumProbes > 0);
+
+ uint32_t sz = kTotalBits / 8;
+ if (kNumBlocks > 0) {
+ sz += CACHE_LINE_SIZE - 1;
+ }
+ assert(allocator);
+
+ char* raw = allocator->AllocateAligned(sz, huge_page_tlb_size, logger);
+ memset(raw, 0, sz);
+ auto cache_line_offset = reinterpret_cast<uintptr_t>(raw) % CACHE_LINE_SIZE;
+ if (kNumBlocks > 0 && cache_line_offset > 0) {
+ raw += CACHE_LINE_SIZE - cache_line_offset;
+ }
+ data_ = raw;
+}
+
+void BloomBlockBuilder::AddKeysHashes(
+ const std::vector<uint32_t>& keys_hashes) {
+ for (auto hash : keys_hashes) {
+ bloom_.AddHash(hash);
+ }
+}
+
+Slice BloomBlockBuilder::Finish() { return bloom_.GetRawData(); }
+
+const std::string BloomBlockBuilder::kBloomBlock = "kBloomBlock";
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.h b/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.h
new file mode 100644
index 00000000..fdacdb0d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_bloom.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include <vector>
+
+#include "rocksdb/slice.h"
+
+#include "port/port.h"
+#include "util/bloom_impl.h"
+#include "util/hash.h"
+
+#include "third-party/folly/folly/ConstexprMath.h"
+
+#include <memory>
+
+namespace ROCKSDB_NAMESPACE {
+class Slice;
+class Allocator;
+class Logger;
+
+// A legacy Bloom filter implementation used by Plain Table db format, for
+// schema backward compatibility. Not for use in new filter applications.
+class PlainTableBloomV1 {
+ public:
+ // allocator: pass allocator to bloom filter, hence trace the usage of memory
+ // total_bits: fixed total bits for the bloom
+ // num_probes: number of hash probes for a single key
+ // locality: If positive, optimize for cache line locality, 0 otherwise.
+ // hash_func: customized hash function
+ // huge_page_tlb_size: if >0, try to allocate bloom bytes from huge page TLB
+ // within this page size. Need to reserve huge pages for
+ // it to be allocated, like:
+ // sysctl -w vm.nr_hugepages=20
+ // See linux doc Documentation/vm/hugetlbpage.txt
+ explicit PlainTableBloomV1(uint32_t num_probes = 6);
+ void SetTotalBits(Allocator* allocator, uint32_t total_bits,
+ uint32_t locality, size_t huge_page_tlb_size,
+ Logger* logger);
+
+ ~PlainTableBloomV1() {}
+
+ // Assuming single threaded access to this function.
+ void AddHash(uint32_t hash);
+
+ // Multithreaded access to this function is OK
+ bool MayContainHash(uint32_t hash) const;
+
+ void Prefetch(uint32_t hash);
+
+ uint32_t GetNumBlocks() const { return kNumBlocks; }
+
+ Slice GetRawData() const { return Slice(data_, GetTotalBits() / 8); }
+
+ void SetRawData(char* raw_data, uint32_t total_bits, uint32_t num_blocks = 0);
+
+ uint32_t GetTotalBits() const { return kTotalBits; }
+
+ bool IsInitialized() const { return kNumBlocks > 0 || kTotalBits > 0; }
+
+ private:
+ uint32_t kTotalBits;
+ uint32_t kNumBlocks;
+ const uint32_t kNumProbes;
+
+ char* data_;
+
+ static constexpr int LOG2_CACHE_LINE_SIZE =
+ folly::constexpr_log2(CACHE_LINE_SIZE);
+};
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+// local variable is initialized but not referenced
+#pragma warning(disable : 4189)
+#endif
+inline void PlainTableBloomV1::Prefetch(uint32_t h) {
+ if (kNumBlocks != 0) {
+ uint32_t ignored;
+ LegacyLocalityBloomImpl</*ExtraRotates*/ true>::PrepareHashMayMatch(
+ h, kNumBlocks, data_, &ignored, LOG2_CACHE_LINE_SIZE);
+ }
+}
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+inline bool PlainTableBloomV1::MayContainHash(uint32_t h) const {
+ assert(IsInitialized());
+ if (kNumBlocks != 0) {
+ return LegacyLocalityBloomImpl<true>::HashMayMatch(
+ h, kNumBlocks, kNumProbes, data_, LOG2_CACHE_LINE_SIZE);
+ } else {
+ return LegacyNoLocalityBloomImpl::HashMayMatch(h, kTotalBits, kNumProbes,
+ data_);
+ }
+}
+
+inline void PlainTableBloomV1::AddHash(uint32_t h) {
+ assert(IsInitialized());
+ if (kNumBlocks != 0) {
+ LegacyLocalityBloomImpl<true>::AddHash(h, kNumBlocks, kNumProbes, data_,
+ LOG2_CACHE_LINE_SIZE);
+ } else {
+ LegacyNoLocalityBloomImpl::AddHash(h, kTotalBits, kNumProbes, data_);
+ }
+}
+
+class BloomBlockBuilder {
+ public:
+ static const std::string kBloomBlock;
+
+ explicit BloomBlockBuilder(uint32_t num_probes = 6) : bloom_(num_probes) {}
+
+ void SetTotalBits(Allocator* allocator, uint32_t total_bits,
+ uint32_t locality, size_t huge_page_tlb_size,
+ Logger* logger) {
+ bloom_.SetTotalBits(allocator, total_bits, locality, huge_page_tlb_size,
+ logger);
+ }
+
+ uint32_t GetNumBlocks() const { return bloom_.GetNumBlocks(); }
+
+ void AddKeysHashes(const std::vector<uint32_t>& keys_hashes);
+
+ Slice Finish();
+
+ private:
+ PlainTableBloomV1 bloom_;
+};
+
+}; // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_builder.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_builder.cc
new file mode 100644
index 00000000..147e46db
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_builder.cc
@@ -0,0 +1,314 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "table/plain/plain_table_builder.h"
+
+#include <assert.h>
+
+#include <string>
+#include <limits>
+#include <map>
+
+#include "db/dbformat.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_builder.h"
+#include "table/format.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_index.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// a utility that helps writing block content to the file
+// @offset will advance if @block_contents was successfully written.
+// @block_handle the block handle this particular block.
+Status WriteBlock(const Slice& block_contents, WritableFileWriter* file,
+ uint64_t* offset, BlockHandle* block_handle) {
+ block_handle->set_offset(*offset);
+ block_handle->set_size(block_contents.size());
+ Status s = file->Append(block_contents);
+
+ if (s.ok()) {
+ *offset += block_contents.size();
+ }
+ return s;
+}
+
+} // namespace
+
+// kPlainTableMagicNumber was picked by running
+// echo rocksdb.table.plain | sha1sum
+// and taking the leading 64 bits.
+extern const uint64_t kPlainTableMagicNumber = 0x8242229663bf9564ull;
+extern const uint64_t kLegacyPlainTableMagicNumber = 0x4f3418eb7a8f13b8ull;
+
+PlainTableBuilder::PlainTableBuilder(
+ const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, WritableFileWriter* file, uint32_t user_key_len,
+ EncodingType encoding_type, size_t index_sparseness,
+ uint32_t bloom_bits_per_key, const std::string& column_family_name,
+ uint32_t num_probes, size_t huge_page_tlb_size, double hash_table_ratio,
+ bool store_index_in_file)
+ : ioptions_(ioptions),
+ moptions_(moptions),
+ bloom_block_(num_probes),
+ file_(file),
+ bloom_bits_per_key_(bloom_bits_per_key),
+ huge_page_tlb_size_(huge_page_tlb_size),
+ encoder_(encoding_type, user_key_len, moptions.prefix_extractor.get(),
+ index_sparseness),
+ store_index_in_file_(store_index_in_file),
+ prefix_extractor_(moptions.prefix_extractor.get()) {
+ // Build index block and save it in the file if hash_table_ratio > 0
+ if (store_index_in_file_) {
+ assert(hash_table_ratio > 0 || IsTotalOrderMode());
+ index_builder_.reset(new PlainTableIndexBuilder(
+ &arena_, ioptions, moptions.prefix_extractor.get(), index_sparseness,
+ hash_table_ratio, huge_page_tlb_size_));
+ properties_.user_collected_properties
+ [PlainTablePropertyNames::kBloomVersion] = "1"; // For future use
+ }
+
+ properties_.fixed_key_len = user_key_len;
+
+ // for plain table, we put all the data in a big chuck.
+ properties_.num_data_blocks = 1;
+ // Fill it later if store_index_in_file_ == true
+ properties_.index_size = 0;
+ properties_.filter_size = 0;
+ // To support roll-back to previous version, now still use version 0 for
+ // plain encoding.
+ properties_.format_version = (encoding_type == kPlain) ? 0 : 1;
+ properties_.column_family_id = column_family_id;
+ properties_.column_family_name = column_family_name;
+ properties_.prefix_extractor_name = moptions_.prefix_extractor != nullptr
+ ? moptions_.prefix_extractor->Name()
+ : "nullptr";
+
+ std::string val;
+ PutFixed32(&val, static_cast<uint32_t>(encoder_.GetEncodingType()));
+ properties_.user_collected_properties
+ [PlainTablePropertyNames::kEncodingType] = val;
+
+ for (auto& collector_factories : *int_tbl_prop_collector_factories) {
+ table_properties_collectors_.emplace_back(
+ collector_factories->CreateIntTblPropCollector(column_family_id));
+ }
+}
+
+PlainTableBuilder::~PlainTableBuilder() {
+}
+
+void PlainTableBuilder::Add(const Slice& key, const Slice& value) {
+ // temp buffer for metadata bytes between key and value.
+ char meta_bytes_buf[6];
+ size_t meta_bytes_buf_size = 0;
+
+ ParsedInternalKey internal_key;
+ if (!ParseInternalKey(key, &internal_key)) {
+ assert(false);
+ return;
+ }
+ if (internal_key.type == kTypeRangeDeletion) {
+ status_ = Status::NotSupported("Range deletion unsupported");
+ return;
+ }
+
+ // Store key hash
+ if (store_index_in_file_) {
+ if (moptions_.prefix_extractor == nullptr) {
+ keys_or_prefixes_hashes_.push_back(GetSliceHash(internal_key.user_key));
+ } else {
+ Slice prefix =
+ moptions_.prefix_extractor->Transform(internal_key.user_key);
+ keys_or_prefixes_hashes_.push_back(GetSliceHash(prefix));
+ }
+ }
+
+ // Write value
+ assert(offset_ <= std::numeric_limits<uint32_t>::max());
+ auto prev_offset = static_cast<uint32_t>(offset_);
+ // Write out the key
+ encoder_.AppendKey(key, file_, &offset_, meta_bytes_buf,
+ &meta_bytes_buf_size);
+ if (SaveIndexInFile()) {
+ index_builder_->AddKeyPrefix(GetPrefix(internal_key), prev_offset);
+ }
+
+ // Write value length
+ uint32_t value_size = static_cast<uint32_t>(value.size());
+ char* end_ptr =
+ EncodeVarint32(meta_bytes_buf + meta_bytes_buf_size, value_size);
+ assert(end_ptr <= meta_bytes_buf + sizeof(meta_bytes_buf));
+ meta_bytes_buf_size = end_ptr - meta_bytes_buf;
+ file_->Append(Slice(meta_bytes_buf, meta_bytes_buf_size));
+
+ // Write value
+ file_->Append(value);
+ offset_ += value_size + meta_bytes_buf_size;
+
+ properties_.num_entries++;
+ properties_.raw_key_size += key.size();
+ properties_.raw_value_size += value.size();
+ if (internal_key.type == kTypeDeletion ||
+ internal_key.type == kTypeSingleDeletion) {
+ properties_.num_deletions++;
+ } else if (internal_key.type == kTypeMerge) {
+ properties_.num_merge_operands++;
+ }
+
+ // notify property collectors
+ NotifyCollectTableCollectorsOnAdd(
+ key, value, offset_, table_properties_collectors_, ioptions_.info_log);
+}
+
+Status PlainTableBuilder::status() const { return status_; }
+
+Status PlainTableBuilder::Finish() {
+ assert(!closed_);
+ closed_ = true;
+
+ properties_.data_size = offset_;
+
+ // Write the following blocks
+ // 1. [meta block: bloom] - optional
+ // 2. [meta block: index] - optional
+ // 3. [meta block: properties]
+ // 4. [metaindex block]
+ // 5. [footer]
+
+ MetaIndexBuilder meta_index_builer;
+
+ if (store_index_in_file_ && (properties_.num_entries > 0)) {
+ assert(properties_.num_entries <= std::numeric_limits<uint32_t>::max());
+ Status s;
+ BlockHandle bloom_block_handle;
+ if (bloom_bits_per_key_ > 0) {
+ bloom_block_.SetTotalBits(
+ &arena_,
+ static_cast<uint32_t>(properties_.num_entries) * bloom_bits_per_key_,
+ ioptions_.bloom_locality, huge_page_tlb_size_, ioptions_.info_log);
+
+ PutVarint32(&properties_.user_collected_properties
+ [PlainTablePropertyNames::kNumBloomBlocks],
+ bloom_block_.GetNumBlocks());
+
+ bloom_block_.AddKeysHashes(keys_or_prefixes_hashes_);
+
+ Slice bloom_finish_result = bloom_block_.Finish();
+
+ properties_.filter_size = bloom_finish_result.size();
+ s = WriteBlock(bloom_finish_result, file_, &offset_, &bloom_block_handle);
+
+ if (!s.ok()) {
+ return s;
+ }
+ meta_index_builer.Add(BloomBlockBuilder::kBloomBlock, bloom_block_handle);
+ }
+ BlockHandle index_block_handle;
+ Slice index_finish_result = index_builder_->Finish();
+
+ properties_.index_size = index_finish_result.size();
+ s = WriteBlock(index_finish_result, file_, &offset_, &index_block_handle);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ meta_index_builer.Add(PlainTableIndexBuilder::kPlainTableIndexBlock,
+ index_block_handle);
+ }
+
+ // Calculate bloom block size and index block size
+ PropertyBlockBuilder property_block_builder;
+ // -- Add basic properties
+ property_block_builder.AddTableProperty(properties_);
+
+ property_block_builder.Add(properties_.user_collected_properties);
+
+ // -- Add user collected properties
+ NotifyCollectTableCollectorsOnFinish(table_properties_collectors_,
+ ioptions_.info_log,
+ &property_block_builder);
+
+ // -- Write property block
+ BlockHandle property_block_handle;
+ auto s = WriteBlock(
+ property_block_builder.Finish(),
+ file_,
+ &offset_,
+ &property_block_handle
+ );
+ if (!s.ok()) {
+ return s;
+ }
+ meta_index_builer.Add(kPropertiesBlock, property_block_handle);
+
+ // -- write metaindex block
+ BlockHandle metaindex_block_handle;
+ s = WriteBlock(
+ meta_index_builer.Finish(),
+ file_,
+ &offset_,
+ &metaindex_block_handle
+ );
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Write Footer
+ // no need to write out new footer if we're using default checksum
+ Footer footer(kLegacyPlainTableMagicNumber, 0);
+ footer.set_metaindex_handle(metaindex_block_handle);
+ footer.set_index_handle(BlockHandle::NullBlockHandle());
+ std::string footer_encoding;
+ footer.EncodeTo(&footer_encoding);
+ s = file_->Append(footer_encoding);
+ if (s.ok()) {
+ offset_ += footer_encoding.size();
+ }
+
+ if (file_ != nullptr) {
+ file_checksum_ = file_->GetFileChecksum();
+ }
+ return s;
+}
+
+void PlainTableBuilder::Abandon() {
+ closed_ = true;
+}
+
+uint64_t PlainTableBuilder::NumEntries() const {
+ return properties_.num_entries;
+}
+
+uint64_t PlainTableBuilder::FileSize() const {
+ return offset_;
+}
+
+const char* PlainTableBuilder::GetFileChecksumFuncName() const {
+ if (file_ != nullptr) {
+ return file_->GetFileChecksumFuncName();
+ } else {
+ return kUnknownFileChecksumFuncName.c_str();
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_builder.h b/storage/rocksdb/rocksdb/table/plain/plain_table_builder.h
new file mode 100644
index 00000000..fe2bf3cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_builder.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include "db/version_edit.h"
+#include "rocksdb/options.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "table/plain/plain_table_bloom.h"
+#include "table/plain/plain_table_index.h"
+#include "table/plain/plain_table_key_coding.h"
+#include "table/table_builder.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockBuilder;
+class BlockHandle;
+class WritableFile;
+class TableBuilder;
+
+// The builder class of PlainTable. For description of PlainTable format
+// See comments of class PlainTableFactory, where instances of
+// PlainTableReader are created.
+class PlainTableBuilder: public TableBuilder {
+ public:
+ // Create a builder that will store the contents of the table it is
+ // building in *file. Does not close the file. It is up to the
+ // caller to close the file after calling Finish(). The output file
+ // will be part of level specified by 'level'. A value of -1 means
+ // that the caller does not know which level the output file will reside.
+ PlainTableBuilder(
+ const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories,
+ uint32_t column_family_id, WritableFileWriter* file,
+ uint32_t user_key_size, EncodingType encoding_type,
+ size_t index_sparseness, uint32_t bloom_bits_per_key,
+ const std::string& column_family_name, uint32_t num_probes = 6,
+ size_t huge_page_tlb_size = 0, double hash_table_ratio = 0,
+ bool store_index_in_file = false);
+ // No copying allowed
+ PlainTableBuilder(const PlainTableBuilder&) = delete;
+ void operator=(const PlainTableBuilder&) = delete;
+
+ // REQUIRES: Either Finish() or Abandon() has been called.
+ ~PlainTableBuilder();
+
+ // Add key,value to the table being constructed.
+ // REQUIRES: key is after any previously added key according to comparator.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Add(const Slice& key, const Slice& value) override;
+
+ // Return non-ok iff some error has been detected.
+ Status status() const override;
+
+ // Finish building the table. Stops using the file passed to the
+ // constructor after this function returns.
+ // REQUIRES: Finish(), Abandon() have not been called
+ Status Finish() override;
+
+ // Indicate that the contents of this builder should be abandoned. Stops
+ // using the file passed to the constructor after this function returns.
+ // If the caller is not going to call Finish(), it must call Abandon()
+ // before destroying this builder.
+ // REQUIRES: Finish(), Abandon() have not been called
+ void Abandon() override;
+
+ // Number of calls to Add() so far.
+ uint64_t NumEntries() const override;
+
+ // Size of the file generated so far. If invoked after a successful
+ // Finish() call, returns the size of the final generated file.
+ uint64_t FileSize() const override;
+
+ TableProperties GetTableProperties() const override { return properties_; }
+
+ bool SaveIndexInFile() const { return store_index_in_file_; }
+
+ // Get file checksum
+ const std::string& GetFileChecksum() const override { return file_checksum_; }
+
+ // Get file checksum function name
+ const char* GetFileChecksumFuncName() const override;
+
+ private:
+ Arena arena_;
+ const ImmutableCFOptions& ioptions_;
+ const MutableCFOptions& moptions_;
+ std::vector<std::unique_ptr<IntTblPropCollector>>
+ table_properties_collectors_;
+
+ BloomBlockBuilder bloom_block_;
+ std::unique_ptr<PlainTableIndexBuilder> index_builder_;
+
+ WritableFileWriter* file_;
+ uint64_t offset_ = 0;
+ uint32_t bloom_bits_per_key_;
+ size_t huge_page_tlb_size_;
+ Status status_;
+ TableProperties properties_;
+ PlainTableKeyEncoder encoder_;
+
+ bool store_index_in_file_;
+
+ std::vector<uint32_t> keys_or_prefixes_hashes_;
+ bool closed_ = false; // Either Finish() or Abandon() has been called.
+
+ const SliceTransform* prefix_extractor_;
+
+ // Store file checksum. If checksum is disabled, its value is "0".
+ std::string file_checksum_ = kUnknownFileChecksum;
+
+ Slice GetPrefix(const Slice& target) const {
+ assert(target.size() >= 8); // target is internal key
+ return GetPrefixFromUserKey(GetUserKey(target));
+ }
+
+ Slice GetPrefix(const ParsedInternalKey& target) const {
+ return GetPrefixFromUserKey(target.user_key);
+ }
+
+ Slice GetUserKey(const Slice& key) const {
+ return Slice(key.data(), key.size() - 8);
+ }
+
+ Slice GetPrefixFromUserKey(const Slice& user_key) const {
+ if (!IsTotalOrderMode()) {
+ return prefix_extractor_->Transform(user_key);
+ } else {
+ // Use empty slice as prefix if prefix_extractor is not set.
+ // In that case,
+ // it falls back to pure binary search and
+ // total iterator seek is supported.
+ return Slice();
+ }
+ }
+
+ bool IsTotalOrderMode() const { return (prefix_extractor_ == nullptr); }
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_factory.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_factory.cc
new file mode 100644
index 00000000..c2db8f39
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_factory.cc
@@ -0,0 +1,235 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 ROCKSDB_LITE
+#include "table/plain/plain_table_factory.h"
+
+#include <stdint.h>
+#include <memory>
+#include "db/dbformat.h"
+#include "options/options_helper.h"
+#include "port/port.h"
+#include "rocksdb/convenience.h"
+#include "table/plain/plain_table_builder.h"
+#include "table/plain/plain_table_reader.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status PlainTableFactory::NewTableReader(
+ const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table,
+ bool /*prefetch_index_and_filter_in_cache*/) const {
+ return PlainTableReader::Open(
+ table_reader_options.ioptions, table_reader_options.env_options,
+ table_reader_options.internal_comparator, std::move(file), file_size,
+ table, table_options_.bloom_bits_per_key, table_options_.hash_table_ratio,
+ table_options_.index_sparseness, table_options_.huge_page_tlb_size,
+ table_options_.full_scan_mode, table_reader_options.immortal,
+ table_reader_options.prefix_extractor);
+}
+
+TableBuilder* PlainTableFactory::NewTableBuilder(
+ const TableBuilderOptions& table_builder_options, uint32_t column_family_id,
+ WritableFileWriter* file) const {
+ // Ignore the skip_filters flag. PlainTable format is optimized for small
+ // in-memory dbs. The skip_filters optimization is not useful for plain
+ // tables
+ //
+ return new PlainTableBuilder(
+ table_builder_options.ioptions, table_builder_options.moptions,
+ table_builder_options.int_tbl_prop_collector_factories, column_family_id,
+ file, table_options_.user_key_len, table_options_.encoding_type,
+ table_options_.index_sparseness, table_options_.bloom_bits_per_key,
+ table_builder_options.column_family_name, 6,
+ table_options_.huge_page_tlb_size, table_options_.hash_table_ratio,
+ table_options_.store_index_in_file);
+}
+
+std::string PlainTableFactory::GetPrintableTableOptions() const {
+ std::string ret;
+ ret.reserve(20000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+
+ snprintf(buffer, kBufferSize, " user_key_len: %u\n",
+ table_options_.user_key_len);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " bloom_bits_per_key: %d\n",
+ table_options_.bloom_bits_per_key);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " hash_table_ratio: %lf\n",
+ table_options_.hash_table_ratio);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " index_sparseness: %" ROCKSDB_PRIszt "\n",
+ table_options_.index_sparseness);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " huge_page_tlb_size: %" ROCKSDB_PRIszt "\n",
+ table_options_.huge_page_tlb_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " encoding_type: %d\n",
+ table_options_.encoding_type);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " full_scan_mode: %d\n",
+ table_options_.full_scan_mode);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " store_index_in_file: %d\n",
+ table_options_.store_index_in_file);
+ ret.append(buffer);
+ return ret;
+}
+
+const PlainTableOptions& PlainTableFactory::table_options() const {
+ return table_options_;
+}
+
+Status GetPlainTableOptionsFromString(const PlainTableOptions& table_options,
+ const std::string& opts_str,
+ PlainTableOptions* new_table_options) {
+ std::unordered_map<std::string, std::string> opts_map;
+ Status s = StringToMap(opts_str, &opts_map);
+ if (!s.ok()) {
+ return s;
+ }
+ return GetPlainTableOptionsFromMap(table_options, opts_map,
+ new_table_options);
+}
+
+Status GetMemTableRepFactoryFromString(
+ const std::string& opts_str,
+ std::unique_ptr<MemTableRepFactory>* new_mem_factory) {
+ std::vector<std::string> opts_list = StringSplit(opts_str, ':');
+ size_t len = opts_list.size();
+
+ if (opts_list.empty() || opts_list.size() > 2) {
+ return Status::InvalidArgument("Can't parse memtable_factory option ",
+ opts_str);
+ }
+
+ MemTableRepFactory* mem_factory = nullptr;
+
+ if (opts_list[0] == "skip_list") {
+ // Expecting format
+ // skip_list:<lookahead>
+ if (2 == len) {
+ size_t lookahead = ParseSizeT(opts_list[1]);
+ mem_factory = new SkipListFactory(lookahead);
+ } else if (1 == len) {
+ mem_factory = new SkipListFactory();
+ }
+ } else if (opts_list[0] == "prefix_hash") {
+ // Expecting format
+ // prfix_hash:<hash_bucket_count>
+ if (2 == len) {
+ size_t hash_bucket_count = ParseSizeT(opts_list[1]);
+ mem_factory = NewHashSkipListRepFactory(hash_bucket_count);
+ } else if (1 == len) {
+ mem_factory = NewHashSkipListRepFactory();
+ }
+ } else if (opts_list[0] == "hash_linkedlist") {
+ // Expecting format
+ // hash_linkedlist:<hash_bucket_count>
+ if (2 == len) {
+ size_t hash_bucket_count = ParseSizeT(opts_list[1]);
+ mem_factory = NewHashLinkListRepFactory(hash_bucket_count);
+ } else if (1 == len) {
+ mem_factory = NewHashLinkListRepFactory();
+ }
+ } else if (opts_list[0] == "vector") {
+ // Expecting format
+ // vector:<count>
+ if (2 == len) {
+ size_t count = ParseSizeT(opts_list[1]);
+ mem_factory = new VectorRepFactory(count);
+ } else if (1 == len) {
+ mem_factory = new VectorRepFactory();
+ }
+ } else if (opts_list[0] == "cuckoo") {
+ return Status::NotSupported(
+ "cuckoo hash memtable is not supported anymore.");
+ } else {
+ return Status::InvalidArgument("Unrecognized memtable_factory option ",
+ opts_str);
+ }
+
+ if (mem_factory != nullptr) {
+ new_mem_factory->reset(mem_factory);
+ }
+
+ return Status::OK();
+}
+
+std::string ParsePlainTableOptions(const std::string& name,
+ const std::string& org_value,
+ PlainTableOptions* new_options,
+ bool input_strings_escaped = false,
+ bool ignore_unknown_options = false) {
+ const std::string& value =
+ input_strings_escaped ? UnescapeOptionString(org_value) : org_value;
+ const auto iter = plain_table_type_info.find(name);
+ if (iter == plain_table_type_info.end()) {
+ if (ignore_unknown_options) {
+ return "";
+ } else {
+ return "Unrecognized option";
+ }
+ }
+ const auto& opt_info = iter->second;
+ if (opt_info.verification != OptionVerificationType::kDeprecated &&
+ !ParseOptionHelper(reinterpret_cast<char*>(new_options) + opt_info.offset,
+ opt_info.type, value)) {
+ return "Invalid value";
+ }
+ return "";
+}
+
+Status GetPlainTableOptionsFromMap(
+ const PlainTableOptions& table_options,
+ const std::unordered_map<std::string, std::string>& opts_map,
+ PlainTableOptions* new_table_options, bool input_strings_escaped,
+ bool /*ignore_unknown_options*/) {
+ assert(new_table_options);
+ *new_table_options = table_options;
+ for (const auto& o : opts_map) {
+ auto error_message = ParsePlainTableOptions(
+ o.first, o.second, new_table_options, input_strings_escaped);
+ if (error_message != "") {
+ const auto iter = plain_table_type_info.find(o.first);
+ if (iter == plain_table_type_info.end() ||
+ !input_strings_escaped || // !input_strings_escaped indicates
+ // the old API, where everything is
+ // parsable.
+ (iter->second.verification != OptionVerificationType::kByName &&
+ iter->second.verification !=
+ OptionVerificationType::kByNameAllowNull &&
+ iter->second.verification !=
+ OptionVerificationType::kByNameAllowFromNull &&
+ iter->second.verification != OptionVerificationType::kDeprecated)) {
+ // Restore "new_options" to the default "base_options".
+ *new_table_options = table_options;
+ return Status::InvalidArgument("Can't parse PlainTableOptions:",
+ o.first + " " + error_message);
+ }
+ }
+ }
+ return Status::OK();
+}
+
+extern TableFactory* NewPlainTableFactory(const PlainTableOptions& options) {
+ return new PlainTableFactory(options);
+}
+
+const std::string PlainTablePropertyNames::kEncodingType =
+ "rocksdb.plain.table.encoding.type";
+
+const std::string PlainTablePropertyNames::kBloomVersion =
+ "rocksdb.plain.table.bloom.version";
+
+const std::string PlainTablePropertyNames::kNumBloomBlocks =
+ "rocksdb.plain.table.bloom.numblocks";
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_factory.h b/storage/rocksdb/rocksdb/table/plain/plain_table_factory.h
new file mode 100644
index 00000000..64dd171c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_factory.h
@@ -0,0 +1,223 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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
+
+#ifndef ROCKSDB_LITE
+#include <memory>
+#include <string>
+#include <stdint.h>
+
+#include "options/options_helper.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct EnvOptions;
+
+class Status;
+class RandomAccessFile;
+class WritableFile;
+class Table;
+class TableBuilder;
+
+// PlainTableFactory is the entrance function to the PlainTable format of
+// SST files. It returns instances PlainTableBuilder as the builder
+// class and PlainTableReader as the reader class, where the format is
+// actually implemented.
+//
+// The PlainTable is designed for memory-mapped file systems, e.g. tmpfs.
+// Data is not organized in blocks, which allows fast access. Because of
+// following downsides
+// 1. Data compression is not supported.
+// 2. Data is not checksumed.
+// it is not recommended to use this format on other type of file systems.
+//
+// PlainTable requires fixed length key, configured as a constructor
+// parameter of the factory class. Output file format:
+// +-------------+-----------------+
+// | version | user_key_length |
+// +------------++------------+-----------------+ <= key1 offset
+// | encoded key1 | value_size | |
+// +------------+-------------+-------------+ |
+// | value1 |
+// | |
+// +--------------------------+-------------+---+ <= key2 offset
+// | encoded key2 | value_size | |
+// +------------+-------------+-------------+ |
+// | value2 |
+// | |
+// | ...... |
+// +-----------------+--------------------------+
+//
+// When the key encoding type is kPlain. Key part is encoded as:
+// +------------+--------------------+
+// | [key_size] | internal key |
+// +------------+--------------------+
+// for the case of user_key_len = kPlainTableVariableLength case,
+// and simply:
+// +----------------------+
+// | internal key |
+// +----------------------+
+// for user_key_len != kPlainTableVariableLength case.
+//
+// If key encoding type is kPrefix. Keys are encoding in this format.
+// There are three ways to encode a key:
+// (1) Full Key
+// +---------------+---------------+-------------------+
+// | Full Key Flag | Full Key Size | Full Internal Key |
+// +---------------+---------------+-------------------+
+// which simply encodes a full key
+//
+// (2) A key shared the same prefix as the previous key, which is encoded as
+// format of (1).
+// +-------------+-------------+-------------+-------------+------------+
+// | Prefix Flag | Prefix Size | Suffix Flag | Suffix Size | Key Suffix |
+// +-------------+-------------+-------------+-------------+------------+
+// where key is the suffix part of the key, including the internal bytes.
+// the actual key will be constructed by concatenating prefix part of the
+// previous key, with the suffix part of the key here, with sizes given here.
+//
+// (3) A key shared the same prefix as the previous key, which is encoded as
+// the format of (2).
+// +-----------------+-----------------+------------------------+
+// | Key Suffix Flag | Key Suffix Size | Suffix of Internal Key |
+// +-----------------+-----------------+------------------------+
+// The key will be constructed by concatenating previous key's prefix (which is
+// also a prefix which the last key encoded in the format of (1)) and the
+// key given here.
+//
+// For example, we for following keys (prefix and suffix are separated by
+// spaces):
+// 0000 0001
+// 0000 00021
+// 0000 0002
+// 00011 00
+// 0002 0001
+// Will be encoded like this:
+// FK 8 00000001
+// PF 4 SF 5 00021
+// SF 4 0002
+// FK 7 0001100
+// FK 8 00020001
+// (where FK means full key flag, PF means prefix flag and SF means suffix flag)
+//
+// All those "key flag + key size" shown above are in this format:
+// The 8 bits of the first byte:
+// +----+----+----+----+----+----+----+----+
+// | Type | Size |
+// +----+----+----+----+----+----+----+----+
+// Type indicates: full key, prefix, or suffix.
+// The last 6 bits are for size. If the size bits are not all 1, it means the
+// size of the key. Otherwise, varint32 is read after this byte. This varint
+// value + 0x3F (the value of all 1) will be the key size.
+//
+// For example, full key with length 16 will be encoded as (binary):
+// 00 010000
+// (00 means full key)
+// and a prefix with 100 bytes will be encoded as:
+// 01 111111 00100101
+// (63) (37)
+// (01 means key suffix)
+//
+// All the internal keys above (including kPlain and kPrefix) are encoded in
+// this format:
+// There are two types:
+// (1) normal internal key format
+// +----------- ...... -------------+----+---+---+---+---+---+---+---+
+// | user key |type| sequence ID |
+// +----------- ..... --------------+----+---+---+---+---+---+---+---+
+// (2) Special case for keys whose sequence ID is 0 and is value type
+// +----------- ...... -------------+----+
+// | user key |0x80|
+// +----------- ..... --------------+----+
+// To save 7 bytes for the special case where sequence ID = 0.
+//
+//
+class PlainTableFactory : public TableFactory {
+ public:
+ ~PlainTableFactory() {}
+ // user_key_len is the length of the user key. If it is set to be
+ // kPlainTableVariableLength, then it means variable length. Otherwise, all
+ // the keys need to have the fix length of this value. bloom_bits_per_key is
+ // number of bits used for bloom filer per key. hash_table_ratio is
+ // the desired utilization of the hash table used for prefix hashing.
+ // hash_table_ratio = number of prefixes / #buckets in the hash table
+ // hash_table_ratio = 0 means skip hash table but only replying on binary
+ // search.
+ // index_sparseness determines index interval for keys
+ // inside the same prefix. It will be the maximum number of linear search
+ // required after hash and binary search.
+ // index_sparseness = 0 means index for every key.
+ // huge_page_tlb_size determines whether to allocate hash indexes from huge
+ // page TLB and the page size if allocating from there. See comments of
+ // Arena::AllocateAligned() for details.
+ explicit PlainTableFactory(
+ const PlainTableOptions& _table_options = PlainTableOptions())
+ : table_options_(_table_options) {}
+
+ const char* Name() const override { return "PlainTable"; }
+ Status NewTableReader(const TableReaderOptions& table_reader_options,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ uint64_t file_size, std::unique_ptr<TableReader>* table,
+ bool prefetch_index_and_filter_in_cache) const override;
+
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& table_builder_options,
+ uint32_t column_family_id, WritableFileWriter* file) const override;
+
+ std::string GetPrintableTableOptions() const override;
+
+ const PlainTableOptions& table_options() const;
+
+ static const char kValueTypeSeqId0 = char(~0);
+
+ // Sanitizes the specified DB Options.
+ Status SanitizeOptions(
+ const DBOptions& /*db_opts*/,
+ const ColumnFamilyOptions& /*cf_opts*/) const override {
+ return Status::OK();
+ }
+
+ void* GetOptions() override { return &table_options_; }
+
+ Status GetOptionString(std::string* /*opt_string*/,
+ const std::string& /*delimiter*/) const override {
+ return Status::OK();
+ }
+
+ private:
+ PlainTableOptions table_options_;
+};
+
+static std::unordered_map<std::string, OptionTypeInfo> plain_table_type_info = {
+ {"user_key_len",
+ {offsetof(struct PlainTableOptions, user_key_len), OptionType::kUInt32T,
+ OptionVerificationType::kNormal, false, 0}},
+ {"bloom_bits_per_key",
+ {offsetof(struct PlainTableOptions, bloom_bits_per_key), OptionType::kInt,
+ OptionVerificationType::kNormal, false, 0}},
+ {"hash_table_ratio",
+ {offsetof(struct PlainTableOptions, hash_table_ratio), OptionType::kDouble,
+ OptionVerificationType::kNormal, false, 0}},
+ {"index_sparseness",
+ {offsetof(struct PlainTableOptions, index_sparseness), OptionType::kSizeT,
+ OptionVerificationType::kNormal, false, 0}},
+ {"huge_page_tlb_size",
+ {offsetof(struct PlainTableOptions, huge_page_tlb_size),
+ OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
+ {"encoding_type",
+ {offsetof(struct PlainTableOptions, encoding_type),
+ OptionType::kEncodingType, OptionVerificationType::kByName, false, 0}},
+ {"full_scan_mode",
+ {offsetof(struct PlainTableOptions, full_scan_mode), OptionType::kBoolean,
+ OptionVerificationType::kNormal, false, 0}},
+ {"store_index_in_file",
+ {offsetof(struct PlainTableOptions, store_index_in_file),
+ OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_index.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_index.cc
new file mode 100644
index 00000000..1099dfa6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_index.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <cinttypes>
+
+#include "table/plain/plain_table_index.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+inline uint32_t GetBucketIdFromHash(uint32_t hash, uint32_t num_buckets) {
+ assert(num_buckets > 0);
+ return hash % num_buckets;
+}
+}
+
+Status PlainTableIndex::InitFromRawData(Slice data) {
+ if (!GetVarint32(&data, &index_size_)) {
+ return Status::Corruption("Couldn't read the index size!");
+ }
+ assert(index_size_ > 0);
+ if (!GetVarint32(&data, &num_prefixes_)) {
+ return Status::Corruption("Couldn't read the index size!");
+ }
+ sub_index_size_ =
+ static_cast<uint32_t>(data.size()) - index_size_ * kOffsetLen;
+
+ char* index_data_begin = const_cast<char*>(data.data());
+ index_ = reinterpret_cast<uint32_t*>(index_data_begin);
+ sub_index_ = reinterpret_cast<char*>(index_ + index_size_);
+ return Status::OK();
+}
+
+PlainTableIndex::IndexSearchResult PlainTableIndex::GetOffset(
+ uint32_t prefix_hash, uint32_t* bucket_value) const {
+ int bucket = GetBucketIdFromHash(prefix_hash, index_size_);
+ GetUnaligned(index_ + bucket, bucket_value);
+ if ((*bucket_value & kSubIndexMask) == kSubIndexMask) {
+ *bucket_value ^= kSubIndexMask;
+ return kSubindex;
+ }
+ if (*bucket_value >= kMaxFileSize) {
+ return kNoPrefixForBucket;
+ } else {
+ // point directly to the file
+ return kDirectToFile;
+ }
+}
+
+void PlainTableIndexBuilder::IndexRecordList::AddRecord(uint32_t hash,
+ uint32_t offset) {
+ if (num_records_in_current_group_ == kNumRecordsPerGroup) {
+ current_group_ = AllocateNewGroup();
+ num_records_in_current_group_ = 0;
+ }
+ auto& new_record = current_group_[num_records_in_current_group_++];
+ new_record.hash = hash;
+ new_record.offset = offset;
+ new_record.next = nullptr;
+}
+
+void PlainTableIndexBuilder::AddKeyPrefix(Slice key_prefix_slice,
+ uint32_t key_offset) {
+ if (is_first_record_ || prev_key_prefix_ != key_prefix_slice.ToString()) {
+ ++num_prefixes_;
+ if (!is_first_record_) {
+ keys_per_prefix_hist_.Add(num_keys_per_prefix_);
+ }
+ num_keys_per_prefix_ = 0;
+ prev_key_prefix_ = key_prefix_slice.ToString();
+ prev_key_prefix_hash_ = GetSliceHash(key_prefix_slice);
+ due_index_ = true;
+ }
+
+ if (due_index_) {
+ // Add an index key for every kIndexIntervalForSamePrefixKeys keys
+ record_list_.AddRecord(prev_key_prefix_hash_, key_offset);
+ due_index_ = false;
+ }
+
+ num_keys_per_prefix_++;
+ if (index_sparseness_ == 0 || num_keys_per_prefix_ % index_sparseness_ == 0) {
+ due_index_ = true;
+ }
+ is_first_record_ = false;
+}
+
+Slice PlainTableIndexBuilder::Finish() {
+ AllocateIndex();
+ std::vector<IndexRecord*> hash_to_offsets(index_size_, nullptr);
+ std::vector<uint32_t> entries_per_bucket(index_size_, 0);
+ BucketizeIndexes(&hash_to_offsets, &entries_per_bucket);
+
+ keys_per_prefix_hist_.Add(num_keys_per_prefix_);
+ ROCKS_LOG_INFO(ioptions_.info_log, "Number of Keys per prefix Histogram: %s",
+ keys_per_prefix_hist_.ToString().c_str());
+
+ // From the temp data structure, populate indexes.
+ return FillIndexes(hash_to_offsets, entries_per_bucket);
+}
+
+void PlainTableIndexBuilder::AllocateIndex() {
+ if (prefix_extractor_ == nullptr || hash_table_ratio_ <= 0) {
+ // Fall back to pure binary search if the user fails to specify a prefix
+ // extractor.
+ index_size_ = 1;
+ } else {
+ double hash_table_size_multipier = 1.0 / hash_table_ratio_;
+ index_size_ =
+ static_cast<uint32_t>(num_prefixes_ * hash_table_size_multipier) + 1;
+ assert(index_size_ > 0);
+ }
+}
+
+void PlainTableIndexBuilder::BucketizeIndexes(
+ std::vector<IndexRecord*>* hash_to_offsets,
+ std::vector<uint32_t>* entries_per_bucket) {
+ bool first = true;
+ uint32_t prev_hash = 0;
+ size_t num_records = record_list_.GetNumRecords();
+ for (size_t i = 0; i < num_records; i++) {
+ IndexRecord* index_record = record_list_.At(i);
+ uint32_t cur_hash = index_record->hash;
+ if (first || prev_hash != cur_hash) {
+ prev_hash = cur_hash;
+ first = false;
+ }
+ uint32_t bucket = GetBucketIdFromHash(cur_hash, index_size_);
+ IndexRecord* prev_bucket_head = (*hash_to_offsets)[bucket];
+ index_record->next = prev_bucket_head;
+ (*hash_to_offsets)[bucket] = index_record;
+ (*entries_per_bucket)[bucket]++;
+ }
+
+ sub_index_size_ = 0;
+ for (auto entry_count : *entries_per_bucket) {
+ if (entry_count <= 1) {
+ continue;
+ }
+ // Only buckets with more than 1 entry will have subindex.
+ sub_index_size_ += VarintLength(entry_count);
+ // total bytes needed to store these entries' in-file offsets.
+ sub_index_size_ += entry_count * PlainTableIndex::kOffsetLen;
+ }
+}
+
+Slice PlainTableIndexBuilder::FillIndexes(
+ const std::vector<IndexRecord*>& hash_to_offsets,
+ const std::vector<uint32_t>& entries_per_bucket) {
+ ROCKS_LOG_DEBUG(ioptions_.info_log,
+ "Reserving %" PRIu32 " bytes for plain table's sub_index",
+ sub_index_size_);
+ auto total_allocate_size = GetTotalSize();
+ char* allocated = arena_->AllocateAligned(
+ total_allocate_size, huge_page_tlb_size_, ioptions_.info_log);
+
+ auto temp_ptr = EncodeVarint32(allocated, index_size_);
+ uint32_t* index =
+ reinterpret_cast<uint32_t*>(EncodeVarint32(temp_ptr, num_prefixes_));
+ char* sub_index = reinterpret_cast<char*>(index + index_size_);
+
+ uint32_t sub_index_offset = 0;
+ for (uint32_t i = 0; i < index_size_; i++) {
+ uint32_t num_keys_for_bucket = entries_per_bucket[i];
+ switch (num_keys_for_bucket) {
+ case 0:
+ // No key for bucket
+ PutUnaligned(index + i, (uint32_t)PlainTableIndex::kMaxFileSize);
+ break;
+ case 1:
+ // point directly to the file offset
+ PutUnaligned(index + i, hash_to_offsets[i]->offset);
+ break;
+ default:
+ // point to second level indexes.
+ PutUnaligned(index + i, sub_index_offset | PlainTableIndex::kSubIndexMask);
+ char* prev_ptr = &sub_index[sub_index_offset];
+ char* cur_ptr = EncodeVarint32(prev_ptr, num_keys_for_bucket);
+ sub_index_offset += static_cast<uint32_t>(cur_ptr - prev_ptr);
+ char* sub_index_pos = &sub_index[sub_index_offset];
+ IndexRecord* record = hash_to_offsets[i];
+ int j;
+ for (j = num_keys_for_bucket - 1; j >= 0 && record;
+ j--, record = record->next) {
+ EncodeFixed32(sub_index_pos + j * sizeof(uint32_t), record->offset);
+ }
+ assert(j == -1 && record == nullptr);
+ sub_index_offset += PlainTableIndex::kOffsetLen * num_keys_for_bucket;
+ assert(sub_index_offset <= sub_index_size_);
+ break;
+ }
+ }
+ assert(sub_index_offset == sub_index_size_);
+
+ ROCKS_LOG_DEBUG(ioptions_.info_log,
+ "hash table size: %" PRIu32 ", suffix_map length %" PRIu32,
+ index_size_, sub_index_size_);
+ return Slice(allocated, GetTotalSize());
+}
+
+const std::string PlainTableIndexBuilder::kPlainTableIndexBlock =
+ "PlainTableIndexBlock";
+}; // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_index.h b/storage/rocksdb/rocksdb/table/plain/plain_table_index.h
new file mode 100644
index 00000000..86385b90
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_index.h
@@ -0,0 +1,249 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "memory/arena.h"
+#include "monitoring/histogram.h"
+#include "options/cf_options.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// The file contains two classes PlainTableIndex and PlainTableIndexBuilder
+// The two classes implement the index format of PlainTable.
+// For descripton of PlainTable format, see comments of class
+// PlainTableFactory
+//
+//
+// PlainTableIndex contains buckets size of index_size_, each is a
+// 32-bit integer. The lower 31 bits contain an offset value (explained below)
+// and the first bit of the integer indicates type of the offset.
+//
+// +--------------+------------------------------------------------------+
+// | Flag (1 bit) | Offset to binary search buffer or file (31 bits) +
+// +--------------+------------------------------------------------------+
+//
+// Explanation for the "flag bit":
+//
+// 0 indicates that the bucket contains only one prefix (no conflict when
+// hashing this prefix), whose first row starts from this offset of the
+// file.
+// 1 indicates that the bucket contains more than one prefixes, or there
+// are too many rows for one prefix so we need a binary search for it. In
+// this case, the offset indicates the offset of sub_index_ holding the
+// binary search indexes of keys for those rows. Those binary search indexes
+// are organized in this way:
+//
+// The first 4 bytes, indicate how many indexes (N) are stored after it. After
+// it, there are N 32-bit integers, each points of an offset of the file,
+// which
+// points to starting of a row. Those offsets need to be guaranteed to be in
+// ascending order so the keys they are pointing to are also in ascending
+// order
+// to make sure we can use them to do binary searches. Below is visual
+// presentation of a bucket.
+//
+// <begin>
+// number_of_records: varint32
+// record 1 file offset: fixedint32
+// record 2 file offset: fixedint32
+// ....
+// record N file offset: fixedint32
+// <end>
+
+// The class loads the index block from a PlainTable SST file, and executes
+// the index lookup.
+// The class is used by PlainTableReader class.
+class PlainTableIndex {
+ public:
+ enum IndexSearchResult {
+ kNoPrefixForBucket = 0,
+ kDirectToFile = 1,
+ kSubindex = 2
+ };
+
+ explicit PlainTableIndex(Slice data) { InitFromRawData(data); }
+
+ PlainTableIndex()
+ : index_size_(0),
+ sub_index_size_(0),
+ num_prefixes_(0),
+ index_(nullptr),
+ sub_index_(nullptr) {}
+
+ // The function that executes the lookup the hash table.
+ // The hash key is `prefix_hash`. The function fills the hash bucket
+ // content in `bucket_value`, which is up to the caller to interpret.
+ IndexSearchResult GetOffset(uint32_t prefix_hash,
+ uint32_t* bucket_value) const;
+
+ // Initialize data from `index_data`, which points to raw data for
+ // index stored in the SST file.
+ Status InitFromRawData(Slice index_data);
+
+ // Decode the sub index for specific hash bucket.
+ // The `offset` is the value returned as `bucket_value` by GetOffset()
+ // and is only valid when the return value is `kSubindex`.
+ // The return value is the pointer to the starting address of the
+ // sub-index. `upper_bound` is filled with the value indicating how many
+ // entries the sub-index has.
+ const char* GetSubIndexBasePtrAndUpperBound(uint32_t offset,
+ uint32_t* upper_bound) const {
+ const char* index_ptr = &sub_index_[offset];
+ return GetVarint32Ptr(index_ptr, index_ptr + 4, upper_bound);
+ }
+
+ uint32_t GetIndexSize() const { return index_size_; }
+
+ uint32_t GetSubIndexSize() const { return sub_index_size_; }
+
+ uint32_t GetNumPrefixes() const { return num_prefixes_; }
+
+ static const uint64_t kMaxFileSize = (1u << 31) - 1;
+ static const uint32_t kSubIndexMask = 0x80000000;
+ static const size_t kOffsetLen = sizeof(uint32_t);
+
+ private:
+ uint32_t index_size_;
+ uint32_t sub_index_size_;
+ uint32_t num_prefixes_;
+
+ uint32_t* index_;
+ char* sub_index_;
+};
+
+// PlainTableIndexBuilder is used to create plain table index.
+// After calling Finish(), it returns Slice, which is usually
+// used either to initialize PlainTableIndex or
+// to save index to sst file.
+// For more details about the index, please refer to:
+// https://github.com/facebook/rocksdb/wiki/PlainTable-Format
+// #wiki-in-memory-index-format
+// The class is used by PlainTableBuilder class.
+class PlainTableIndexBuilder {
+ public:
+ PlainTableIndexBuilder(Arena* arena, const ImmutableCFOptions& ioptions,
+ const SliceTransform* prefix_extractor,
+ size_t index_sparseness, double hash_table_ratio,
+ size_t huge_page_tlb_size)
+ : arena_(arena),
+ ioptions_(ioptions),
+ record_list_(kRecordsPerGroup),
+ is_first_record_(true),
+ due_index_(false),
+ num_prefixes_(0),
+ num_keys_per_prefix_(0),
+ prev_key_prefix_hash_(0),
+ index_sparseness_(index_sparseness),
+ index_size_(0),
+ sub_index_size_(0),
+ prefix_extractor_(prefix_extractor),
+ hash_table_ratio_(hash_table_ratio),
+ huge_page_tlb_size_(huge_page_tlb_size) {}
+
+ void AddKeyPrefix(Slice key_prefix_slice, uint32_t key_offset);
+
+ Slice Finish();
+
+ uint32_t GetTotalSize() const {
+ return VarintLength(index_size_) + VarintLength(num_prefixes_) +
+ PlainTableIndex::kOffsetLen * index_size_ + sub_index_size_;
+ }
+
+ static const std::string kPlainTableIndexBlock;
+
+ private:
+ struct IndexRecord {
+ uint32_t hash; // hash of the prefix
+ uint32_t offset; // offset of a row
+ IndexRecord* next;
+ };
+
+ // Helper class to track all the index records
+ class IndexRecordList {
+ public:
+ explicit IndexRecordList(size_t num_records_per_group)
+ : kNumRecordsPerGroup(num_records_per_group),
+ current_group_(nullptr),
+ num_records_in_current_group_(num_records_per_group) {}
+
+ ~IndexRecordList() {
+ for (size_t i = 0; i < groups_.size(); i++) {
+ delete[] groups_[i];
+ }
+ }
+
+ void AddRecord(uint32_t hash, uint32_t offset);
+
+ size_t GetNumRecords() const {
+ return (groups_.size() - 1) * kNumRecordsPerGroup +
+ num_records_in_current_group_;
+ }
+ IndexRecord* At(size_t index) {
+ return &(groups_[index / kNumRecordsPerGroup]
+ [index % kNumRecordsPerGroup]);
+ }
+
+ private:
+ IndexRecord* AllocateNewGroup() {
+ IndexRecord* result = new IndexRecord[kNumRecordsPerGroup];
+ groups_.push_back(result);
+ return result;
+ }
+
+ // Each group in `groups_` contains fix-sized records (determined by
+ // kNumRecordsPerGroup). Which can help us minimize the cost if resizing
+ // occurs.
+ const size_t kNumRecordsPerGroup;
+ IndexRecord* current_group_;
+ // List of arrays allocated
+ std::vector<IndexRecord*> groups_;
+ size_t num_records_in_current_group_;
+ };
+
+ void AllocateIndex();
+
+ // Internal helper function to bucket index record list to hash buckets.
+ void BucketizeIndexes(std::vector<IndexRecord*>* hash_to_offsets,
+ std::vector<uint32_t>* entries_per_bucket);
+
+ // Internal helper class to fill the indexes and bloom filters to internal
+ // data structures.
+ Slice FillIndexes(const std::vector<IndexRecord*>& hash_to_offsets,
+ const std::vector<uint32_t>& entries_per_bucket);
+
+ Arena* arena_;
+ const ImmutableCFOptions ioptions_;
+ HistogramImpl keys_per_prefix_hist_;
+ IndexRecordList record_list_;
+ bool is_first_record_;
+ bool due_index_;
+ uint32_t num_prefixes_;
+ uint32_t num_keys_per_prefix_;
+
+ uint32_t prev_key_prefix_hash_;
+ size_t index_sparseness_;
+ uint32_t index_size_;
+ uint32_t sub_index_size_;
+
+ const SliceTransform* prefix_extractor_;
+ double hash_table_ratio_;
+ size_t huge_page_tlb_size_;
+
+ std::string prev_key_prefix_;
+
+ static const size_t kRecordsPerGroup = 256;
+};
+
+}; // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.cc
new file mode 100644
index 00000000..d82b969b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.cc
@@ -0,0 +1,498 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "table/plain/plain_table_key_coding.h"
+
+#include <algorithm>
+#include <string>
+#include "db/dbformat.h"
+#include "file/writable_file_writer.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/plain/plain_table_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+enum PlainTableEntryType : unsigned char {
+ kFullKey = 0,
+ kPrefixFromPreviousKey = 1,
+ kKeySuffix = 2,
+};
+
+namespace {
+
+// Control byte:
+// First two bits indicate type of entry
+// Other bytes are inlined sizes. If all bits are 1 (0x03F), overflow bytes
+// are used. key_size-0x3F will be encoded as a variint32 after this bytes.
+
+const unsigned char kSizeInlineLimit = 0x3F;
+
+// Return 0 for error
+size_t EncodeSize(PlainTableEntryType type, uint32_t key_size,
+ char* out_buffer) {
+ out_buffer[0] = type << 6;
+
+ if (key_size < static_cast<uint32_t>(kSizeInlineLimit)) {
+ // size inlined
+ out_buffer[0] |= static_cast<char>(key_size);
+ return 1;
+ } else {
+ out_buffer[0] |= kSizeInlineLimit;
+ char* ptr = EncodeVarint32(out_buffer + 1, key_size - kSizeInlineLimit);
+ return ptr - out_buffer;
+ }
+}
+} // namespace
+
+// Fill bytes_read with number of bytes read.
+inline Status PlainTableKeyDecoder::DecodeSize(uint32_t start_offset,
+ PlainTableEntryType* entry_type,
+ uint32_t* key_size,
+ uint32_t* bytes_read) {
+ Slice next_byte_slice;
+ bool success = file_reader_.Read(start_offset, 1, &next_byte_slice);
+ if (!success) {
+ return file_reader_.status();
+ }
+ *entry_type = static_cast<PlainTableEntryType>(
+ (static_cast<unsigned char>(next_byte_slice[0]) & ~kSizeInlineLimit) >>
+ 6);
+ char inline_key_size = next_byte_slice[0] & kSizeInlineLimit;
+ if (inline_key_size < kSizeInlineLimit) {
+ *key_size = inline_key_size;
+ *bytes_read = 1;
+ return Status::OK();
+ } else {
+ uint32_t extra_size;
+ uint32_t tmp_bytes_read;
+ success = file_reader_.ReadVarint32(start_offset + 1, &extra_size,
+ &tmp_bytes_read);
+ if (!success) {
+ return file_reader_.status();
+ }
+ assert(tmp_bytes_read > 0);
+ *key_size = kSizeInlineLimit + extra_size;
+ *bytes_read = tmp_bytes_read + 1;
+ return Status::OK();
+ }
+}
+
+Status PlainTableKeyEncoder::AppendKey(const Slice& key,
+ WritableFileWriter* file,
+ uint64_t* offset, char* meta_bytes_buf,
+ size_t* meta_bytes_buf_size) {
+ ParsedInternalKey parsed_key;
+ if (!ParseInternalKey(key, &parsed_key)) {
+ return Status::Corruption(Slice());
+ }
+
+ Slice key_to_write = key; // Portion of internal key to write out.
+
+ uint32_t user_key_size = static_cast<uint32_t>(key.size() - 8);
+ if (encoding_type_ == kPlain) {
+ if (fixed_user_key_len_ == kPlainTableVariableLength) {
+ // Write key length
+ char key_size_buf[5]; // tmp buffer for key size as varint32
+ char* ptr = EncodeVarint32(key_size_buf, user_key_size);
+ assert(ptr <= key_size_buf + sizeof(key_size_buf));
+ auto len = ptr - key_size_buf;
+ Status s = file->Append(Slice(key_size_buf, len));
+ if (!s.ok()) {
+ return s;
+ }
+ *offset += len;
+ }
+ } else {
+ assert(encoding_type_ == kPrefix);
+ char size_bytes[12];
+ size_t size_bytes_pos = 0;
+
+ Slice prefix =
+ prefix_extractor_->Transform(Slice(key.data(), user_key_size));
+ if (key_count_for_prefix_ == 0 || prefix != pre_prefix_.GetUserKey() ||
+ key_count_for_prefix_ % index_sparseness_ == 0) {
+ key_count_for_prefix_ = 1;
+ pre_prefix_.SetUserKey(prefix);
+ size_bytes_pos += EncodeSize(kFullKey, user_key_size, size_bytes);
+ Status s = file->Append(Slice(size_bytes, size_bytes_pos));
+ if (!s.ok()) {
+ return s;
+ }
+ *offset += size_bytes_pos;
+ } else {
+ key_count_for_prefix_++;
+ if (key_count_for_prefix_ == 2) {
+ // For second key within a prefix, need to encode prefix length
+ size_bytes_pos +=
+ EncodeSize(kPrefixFromPreviousKey,
+ static_cast<uint32_t>(pre_prefix_.GetUserKey().size()),
+ size_bytes + size_bytes_pos);
+ }
+ uint32_t prefix_len =
+ static_cast<uint32_t>(pre_prefix_.GetUserKey().size());
+ size_bytes_pos += EncodeSize(kKeySuffix, user_key_size - prefix_len,
+ size_bytes + size_bytes_pos);
+ Status s = file->Append(Slice(size_bytes, size_bytes_pos));
+ if (!s.ok()) {
+ return s;
+ }
+ *offset += size_bytes_pos;
+ key_to_write = Slice(key.data() + prefix_len, key.size() - prefix_len);
+ }
+ }
+
+ // Encode full key
+ // For value size as varint32 (up to 5 bytes).
+ // If the row is of value type with seqId 0, flush the special flag together
+ // in this buffer to safe one file append call, which takes 1 byte.
+ if (parsed_key.sequence == 0 && parsed_key.type == kTypeValue) {
+ Status s =
+ file->Append(Slice(key_to_write.data(), key_to_write.size() - 8));
+ if (!s.ok()) {
+ return s;
+ }
+ *offset += key_to_write.size() - 8;
+ meta_bytes_buf[*meta_bytes_buf_size] = PlainTableFactory::kValueTypeSeqId0;
+ *meta_bytes_buf_size += 1;
+ } else {
+ file->Append(key_to_write);
+ *offset += key_to_write.size();
+ }
+
+ return Status::OK();
+}
+
+Slice PlainTableFileReader::GetFromBuffer(Buffer* buffer, uint32_t file_offset,
+ uint32_t len) {
+ assert(file_offset + len <= file_info_->data_end_offset);
+ return Slice(buffer->buf.get() + (file_offset - buffer->buf_start_offset),
+ len);
+}
+
+bool PlainTableFileReader::ReadNonMmap(uint32_t file_offset, uint32_t len,
+ Slice* out) {
+ const uint32_t kPrefetchSize = 256u;
+
+ // Try to read from buffers.
+ for (uint32_t i = 0; i < num_buf_; i++) {
+ Buffer* buffer = buffers_[num_buf_ - 1 - i].get();
+ if (file_offset >= buffer->buf_start_offset &&
+ file_offset + len <= buffer->buf_start_offset + buffer->buf_len) {
+ *out = GetFromBuffer(buffer, file_offset, len);
+ return true;
+ }
+ }
+
+ Buffer* new_buffer;
+ // Data needed is not in any of the buffer. Allocate a new buffer.
+ if (num_buf_ < buffers_.size()) {
+ // Add a new buffer
+ new_buffer = new Buffer();
+ buffers_[num_buf_++].reset(new_buffer);
+ } else {
+ // Now simply replace the last buffer. Can improve the placement policy
+ // if needed.
+ new_buffer = buffers_[num_buf_ - 1].get();
+ }
+
+ assert(file_offset + len <= file_info_->data_end_offset);
+ uint32_t size_to_read = std::min(file_info_->data_end_offset - file_offset,
+ std::max(kPrefetchSize, len));
+ if (size_to_read > new_buffer->buf_capacity) {
+ new_buffer->buf.reset(new char[size_to_read]);
+ new_buffer->buf_capacity = size_to_read;
+ new_buffer->buf_len = 0;
+ }
+ Slice read_result;
+ Status s = file_info_->file->Read(file_offset, size_to_read, &read_result,
+ new_buffer->buf.get());
+ if (!s.ok()) {
+ status_ = s;
+ return false;
+ }
+ new_buffer->buf_start_offset = file_offset;
+ new_buffer->buf_len = size_to_read;
+ *out = GetFromBuffer(new_buffer, file_offset, len);
+ return true;
+}
+
+inline bool PlainTableFileReader::ReadVarint32(uint32_t offset, uint32_t* out,
+ uint32_t* bytes_read) {
+ if (file_info_->is_mmap_mode) {
+ const char* start = file_info_->file_data.data() + offset;
+ const char* limit =
+ file_info_->file_data.data() + file_info_->data_end_offset;
+ const char* key_ptr = GetVarint32Ptr(start, limit, out);
+ assert(key_ptr != nullptr);
+ *bytes_read = static_cast<uint32_t>(key_ptr - start);
+ return true;
+ } else {
+ return ReadVarint32NonMmap(offset, out, bytes_read);
+ }
+}
+
+bool PlainTableFileReader::ReadVarint32NonMmap(uint32_t offset, uint32_t* out,
+ uint32_t* bytes_read) {
+ const char* start;
+ const char* limit;
+ const uint32_t kMaxVarInt32Size = 6u;
+ uint32_t bytes_to_read =
+ std::min(file_info_->data_end_offset - offset, kMaxVarInt32Size);
+ Slice bytes;
+ if (!Read(offset, bytes_to_read, &bytes)) {
+ return false;
+ }
+ start = bytes.data();
+ limit = bytes.data() + bytes.size();
+
+ const char* key_ptr = GetVarint32Ptr(start, limit, out);
+ *bytes_read =
+ (key_ptr != nullptr) ? static_cast<uint32_t>(key_ptr - start) : 0;
+ return true;
+}
+
+Status PlainTableKeyDecoder::ReadInternalKey(
+ uint32_t file_offset, uint32_t user_key_size, ParsedInternalKey* parsed_key,
+ uint32_t* bytes_read, bool* internal_key_valid, Slice* internal_key) {
+ Slice tmp_slice;
+ bool success = file_reader_.Read(file_offset, user_key_size + 1, &tmp_slice);
+ if (!success) {
+ return file_reader_.status();
+ }
+ if (tmp_slice[user_key_size] == PlainTableFactory::kValueTypeSeqId0) {
+ // Special encoding for the row with seqID=0
+ parsed_key->user_key = Slice(tmp_slice.data(), user_key_size);
+ parsed_key->sequence = 0;
+ parsed_key->type = kTypeValue;
+ *bytes_read += user_key_size + 1;
+ *internal_key_valid = false;
+ } else {
+ success = file_reader_.Read(file_offset, user_key_size + 8, internal_key);
+ if (!success) {
+ return file_reader_.status();
+ }
+ *internal_key_valid = true;
+ if (!ParseInternalKey(*internal_key, parsed_key)) {
+ return Status::Corruption(
+ Slice("Incorrect value type found when reading the next key"));
+ }
+ *bytes_read += user_key_size + 8;
+ }
+ return Status::OK();
+}
+
+Status PlainTableKeyDecoder::NextPlainEncodingKey(uint32_t start_offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key,
+ uint32_t* bytes_read,
+ bool* /*seekable*/) {
+ uint32_t user_key_size = 0;
+ Status s;
+ if (fixed_user_key_len_ != kPlainTableVariableLength) {
+ user_key_size = fixed_user_key_len_;
+ } else {
+ uint32_t tmp_size = 0;
+ uint32_t tmp_read;
+ bool success =
+ file_reader_.ReadVarint32(start_offset, &tmp_size, &tmp_read);
+ if (!success) {
+ return file_reader_.status();
+ }
+ assert(tmp_read > 0);
+ user_key_size = tmp_size;
+ *bytes_read = tmp_read;
+ }
+ // dummy initial value to avoid compiler complain
+ bool decoded_internal_key_valid = true;
+ Slice decoded_internal_key;
+ s = ReadInternalKey(start_offset + *bytes_read, user_key_size, parsed_key,
+ bytes_read, &decoded_internal_key_valid,
+ &decoded_internal_key);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!file_reader_.file_info()->is_mmap_mode) {
+ cur_key_.SetInternalKey(*parsed_key);
+ parsed_key->user_key =
+ Slice(cur_key_.GetInternalKey().data(), user_key_size);
+ if (internal_key != nullptr) {
+ *internal_key = cur_key_.GetInternalKey();
+ }
+ } else if (internal_key != nullptr) {
+ if (decoded_internal_key_valid) {
+ *internal_key = decoded_internal_key;
+ } else {
+ // Need to copy out the internal key
+ cur_key_.SetInternalKey(*parsed_key);
+ *internal_key = cur_key_.GetInternalKey();
+ }
+ }
+ return Status::OK();
+}
+
+Status PlainTableKeyDecoder::NextPrefixEncodingKey(
+ uint32_t start_offset, ParsedInternalKey* parsed_key, Slice* internal_key,
+ uint32_t* bytes_read, bool* seekable) {
+ PlainTableEntryType entry_type;
+
+ bool expect_suffix = false;
+ Status s;
+ do {
+ uint32_t size = 0;
+ // dummy initial value to avoid compiler complain
+ bool decoded_internal_key_valid = true;
+ uint32_t my_bytes_read = 0;
+ s = DecodeSize(start_offset + *bytes_read, &entry_type, &size,
+ &my_bytes_read);
+ if (!s.ok()) {
+ return s;
+ }
+ if (my_bytes_read == 0) {
+ return Status::Corruption("Unexpected EOF when reading size of the key");
+ }
+ *bytes_read += my_bytes_read;
+
+ switch (entry_type) {
+ case kFullKey: {
+ expect_suffix = false;
+ Slice decoded_internal_key;
+ s = ReadInternalKey(start_offset + *bytes_read, size, parsed_key,
+ bytes_read, &decoded_internal_key_valid,
+ &decoded_internal_key);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!file_reader_.file_info()->is_mmap_mode ||
+ (internal_key != nullptr && !decoded_internal_key_valid)) {
+ // In non-mmap mode, always need to make a copy of keys returned to
+ // users, because after reading value for the key, the key might
+ // be invalid.
+ cur_key_.SetInternalKey(*parsed_key);
+ saved_user_key_ = cur_key_.GetUserKey();
+ if (!file_reader_.file_info()->is_mmap_mode) {
+ parsed_key->user_key =
+ Slice(cur_key_.GetInternalKey().data(), size);
+ }
+ if (internal_key != nullptr) {
+ *internal_key = cur_key_.GetInternalKey();
+ }
+ } else {
+ if (internal_key != nullptr) {
+ *internal_key = decoded_internal_key;
+ }
+ saved_user_key_ = parsed_key->user_key;
+ }
+ break;
+ }
+ case kPrefixFromPreviousKey: {
+ if (seekable != nullptr) {
+ *seekable = false;
+ }
+ prefix_len_ = size;
+ assert(prefix_extractor_ == nullptr ||
+ prefix_extractor_->Transform(saved_user_key_).size() ==
+ prefix_len_);
+ // Need read another size flag for suffix
+ expect_suffix = true;
+ break;
+ }
+ case kKeySuffix: {
+ expect_suffix = false;
+ if (seekable != nullptr) {
+ *seekable = false;
+ }
+
+ Slice tmp_slice;
+ s = ReadInternalKey(start_offset + *bytes_read, size, parsed_key,
+ bytes_read, &decoded_internal_key_valid,
+ &tmp_slice);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!file_reader_.file_info()->is_mmap_mode) {
+ // In non-mmap mode, we need to make a copy of keys returned to
+ // users, because after reading value for the key, the key might
+ // be invalid.
+ // saved_user_key_ points to cur_key_. We are making a copy of
+ // the prefix part to another string, and construct the current
+ // key from the prefix part and the suffix part back to cur_key_.
+ std::string tmp =
+ Slice(saved_user_key_.data(), prefix_len_).ToString();
+ cur_key_.Reserve(prefix_len_ + size);
+ cur_key_.SetInternalKey(tmp, *parsed_key);
+ parsed_key->user_key =
+ Slice(cur_key_.GetInternalKey().data(), prefix_len_ + size);
+ saved_user_key_ = cur_key_.GetUserKey();
+ } else {
+ cur_key_.Reserve(prefix_len_ + size);
+ cur_key_.SetInternalKey(Slice(saved_user_key_.data(), prefix_len_),
+ *parsed_key);
+ }
+ parsed_key->user_key = cur_key_.GetUserKey();
+ if (internal_key != nullptr) {
+ *internal_key = cur_key_.GetInternalKey();
+ }
+ break;
+ }
+ default:
+ return Status::Corruption("Un-identified size flag.");
+ }
+ } while (expect_suffix); // Another round if suffix is expected.
+ return Status::OK();
+}
+
+Status PlainTableKeyDecoder::NextKey(uint32_t start_offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key, Slice* value,
+ uint32_t* bytes_read, bool* seekable) {
+ assert(value != nullptr);
+ Status s = NextKeyNoValue(start_offset, parsed_key, internal_key, bytes_read,
+ seekable);
+ if (s.ok()) {
+ assert(bytes_read != nullptr);
+ uint32_t value_size;
+ uint32_t value_size_bytes;
+ bool success = file_reader_.ReadVarint32(start_offset + *bytes_read,
+ &value_size, &value_size_bytes);
+ if (!success) {
+ return file_reader_.status();
+ }
+ if (value_size_bytes == 0) {
+ return Status::Corruption(
+ "Unexpected EOF when reading the next value's size.");
+ }
+ *bytes_read += value_size_bytes;
+ success = file_reader_.Read(start_offset + *bytes_read, value_size, value);
+ if (!success) {
+ return file_reader_.status();
+ }
+ *bytes_read += value_size;
+ }
+ return s;
+}
+
+Status PlainTableKeyDecoder::NextKeyNoValue(uint32_t start_offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key,
+ uint32_t* bytes_read,
+ bool* seekable) {
+ *bytes_read = 0;
+ if (seekable != nullptr) {
+ *seekable = true;
+ }
+ Status s;
+ if (encoding_type_ == kPlain) {
+ return NextPlainEncodingKey(start_offset, parsed_key, internal_key,
+ bytes_read, seekable);
+ } else {
+ assert(encoding_type_ == kPrefix);
+ return NextPrefixEncodingKey(start_offset, parsed_key, internal_key,
+ bytes_read, seekable);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LIT
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.h b/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.h
new file mode 100644
index 00000000..d1460837
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_key_coding.h
@@ -0,0 +1,193 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <array>
+#include "db/dbformat.h"
+#include "rocksdb/slice.h"
+#include "table/plain/plain_table_reader.h"
+
+// The file contains three helper classes of PlainTable format,
+// PlainTableKeyEncoder, PlainTableKeyDecoder and PlainTableFileReader.
+// These classes issue the lowest level of operations of PlainTable.
+// Actual data format of the key is documented in comments of class
+// PlainTableFactory.
+namespace ROCKSDB_NAMESPACE {
+
+class WritableFile;
+struct ParsedInternalKey;
+struct PlainTableReaderFileInfo;
+enum PlainTableEntryType : unsigned char;
+
+// Helper class for PlainTable format to write out a key to an output file
+// The class is used in PlainTableBuilder.
+class PlainTableKeyEncoder {
+ public:
+ explicit PlainTableKeyEncoder(EncodingType encoding_type,
+ uint32_t user_key_len,
+ const SliceTransform* prefix_extractor,
+ size_t index_sparseness)
+ : encoding_type_((prefix_extractor != nullptr) ? encoding_type : kPlain),
+ fixed_user_key_len_(user_key_len),
+ prefix_extractor_(prefix_extractor),
+ index_sparseness_((index_sparseness > 1) ? index_sparseness : 1),
+ key_count_for_prefix_(0) {}
+ // key: the key to write out, in the format of internal key.
+ // file: the output file to write out
+ // offset: offset in the file. Needs to be updated after appending bytes
+ // for the key
+ // meta_bytes_buf: buffer for extra meta bytes
+ // meta_bytes_buf_size: offset to append extra meta bytes. Will be updated
+ // if meta_bytes_buf is updated.
+ Status AppendKey(const Slice& key, WritableFileWriter* file, uint64_t* offset,
+ char* meta_bytes_buf, size_t* meta_bytes_buf_size);
+
+ // Return actual encoding type to be picked
+ EncodingType GetEncodingType() { return encoding_type_; }
+
+ private:
+ EncodingType encoding_type_;
+ uint32_t fixed_user_key_len_;
+ const SliceTransform* prefix_extractor_;
+ const size_t index_sparseness_;
+ size_t key_count_for_prefix_;
+ IterKey pre_prefix_;
+};
+
+// The class does raw file reads for PlainTableReader.
+// It hides whether it is a mmap-read, or a non-mmap read.
+// The class is implemented in a way to favor the performance of mmap case.
+// The class is used by PlainTableReader.
+class PlainTableFileReader {
+ public:
+ explicit PlainTableFileReader(const PlainTableReaderFileInfo* _file_info)
+ : file_info_(_file_info), num_buf_(0) {}
+ // In mmaped mode, the results point to mmaped area of the file, which
+ // means it is always valid before closing the file.
+ // In non-mmap mode, the results point to an internal buffer. If the caller
+ // makes another read call, the results may not be valid. So callers should
+ // make a copy when needed.
+ // In order to save read calls to files, we keep two internal buffers:
+ // the first read and the most recent read. This is efficient because it
+ // columns these two common use cases:
+ // (1) hash index only identify one location, we read the key to verify
+ // the location, and read key and value if it is the right location.
+ // (2) after hash index checking, we identify two locations (because of
+ // hash bucket conflicts), we binary search the two location to see
+ // which one is what we need and start to read from the location.
+ // These two most common use cases will be covered by the two buffers
+ // so that we don't need to re-read the same location.
+ // Currently we keep a fixed size buffer. If a read doesn't exactly fit
+ // the buffer, we replace the second buffer with the location user reads.
+ //
+ // If return false, status code is stored in status_.
+ bool Read(uint32_t file_offset, uint32_t len, Slice* out) {
+ if (file_info_->is_mmap_mode) {
+ assert(file_offset + len <= file_info_->data_end_offset);
+ *out = Slice(file_info_->file_data.data() + file_offset, len);
+ return true;
+ } else {
+ return ReadNonMmap(file_offset, len, out);
+ }
+ }
+
+ // If return false, status code is stored in status_.
+ bool ReadNonMmap(uint32_t file_offset, uint32_t len, Slice* output);
+
+ // *bytes_read = 0 means eof. false means failure and status is saved
+ // in status_. Not directly returning Status to save copying status
+ // object to map previous performance of mmap mode.
+ inline bool ReadVarint32(uint32_t offset, uint32_t* output,
+ uint32_t* bytes_read);
+
+ bool ReadVarint32NonMmap(uint32_t offset, uint32_t* output,
+ uint32_t* bytes_read);
+
+ Status status() const { return status_; }
+
+ const PlainTableReaderFileInfo* file_info() { return file_info_; }
+
+ private:
+ const PlainTableReaderFileInfo* file_info_;
+
+ struct Buffer {
+ Buffer() : buf_start_offset(0), buf_len(0), buf_capacity(0) {}
+ std::unique_ptr<char[]> buf;
+ uint32_t buf_start_offset;
+ uint32_t buf_len;
+ uint32_t buf_capacity;
+ };
+
+ // Keep buffers for two recent reads.
+ std::array<std::unique_ptr<Buffer>, 2> buffers_;
+ uint32_t num_buf_;
+ Status status_;
+
+ Slice GetFromBuffer(Buffer* buf, uint32_t file_offset, uint32_t len);
+};
+
+// A helper class to decode keys from input buffer
+// The class is used by PlainTableBuilder.
+class PlainTableKeyDecoder {
+ public:
+ explicit PlainTableKeyDecoder(const PlainTableReaderFileInfo* file_info,
+ EncodingType encoding_type,
+ uint32_t user_key_len,
+ const SliceTransform* prefix_extractor)
+ : file_reader_(file_info),
+ encoding_type_(encoding_type),
+ prefix_len_(0),
+ fixed_user_key_len_(user_key_len),
+ prefix_extractor_(prefix_extractor),
+ in_prefix_(false) {}
+ // Find the next key.
+ // start: char array where the key starts.
+ // limit: boundary of the char array
+ // parsed_key: the output of the result key
+ // internal_key: if not null, fill with the output of the result key in
+ // un-parsed format
+ // bytes_read: how many bytes read from start. Output
+ // seekable: whether key can be read from this place. Used when building
+ // indexes. Output.
+ Status NextKey(uint32_t start_offset, ParsedInternalKey* parsed_key,
+ Slice* internal_key, Slice* value, uint32_t* bytes_read,
+ bool* seekable = nullptr);
+
+ Status NextKeyNoValue(uint32_t start_offset, ParsedInternalKey* parsed_key,
+ Slice* internal_key, uint32_t* bytes_read,
+ bool* seekable = nullptr);
+
+ PlainTableFileReader file_reader_;
+ EncodingType encoding_type_;
+ uint32_t prefix_len_;
+ uint32_t fixed_user_key_len_;
+ Slice saved_user_key_;
+ IterKey cur_key_;
+ const SliceTransform* prefix_extractor_;
+ bool in_prefix_;
+
+ private:
+ Status NextPlainEncodingKey(uint32_t start_offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key, uint32_t* bytes_read,
+ bool* seekable = nullptr);
+ Status NextPrefixEncodingKey(uint32_t start_offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key, uint32_t* bytes_read,
+ bool* seekable = nullptr);
+ Status ReadInternalKey(uint32_t file_offset, uint32_t user_key_size,
+ ParsedInternalKey* parsed_key, uint32_t* bytes_read,
+ bool* internal_key_valid, Slice* internal_key);
+ inline Status DecodeSize(uint32_t start_offset,
+ PlainTableEntryType* entry_type, uint32_t* key_size,
+ uint32_t* bytes_read);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_reader.cc b/storage/rocksdb/rocksdb/table/plain/plain_table_reader.cc
new file mode 100644
index 00000000..55756d9c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_reader.cc
@@ -0,0 +1,775 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 ROCKSDB_LITE
+
+#include "table/plain/plain_table_reader.h"
+
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+
+#include "rocksdb/cache.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/options.h"
+#include "rocksdb/statistics.h"
+
+#include "table/block_based/block.h"
+#include "table/block_based/filter_block.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_bloom.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/plain/plain_table_key_coding.h"
+#include "table/two_level_iterator.h"
+
+#include "memory/arena.h"
+#include "monitoring/histogram.h"
+#include "monitoring/perf_context_imp.h"
+#include "util/coding.h"
+#include "util/dynamic_bloom.h"
+#include "util/hash.h"
+#include "util/stop_watch.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+// Safely getting a uint32_t element from a char array, where, starting from
+// `base`, every 4 bytes are considered as an fixed 32 bit integer.
+inline uint32_t GetFixed32Element(const char* base, size_t offset) {
+ return DecodeFixed32(base + offset * sizeof(uint32_t));
+}
+} // namespace
+
+// Iterator to iterate IndexedTable
+class PlainTableIterator : public InternalIterator {
+ public:
+ explicit PlainTableIterator(PlainTableReader* table, bool use_prefix_seek);
+ // No copying allowed
+ PlainTableIterator(const PlainTableIterator&) = delete;
+ void operator=(const Iterator&) = delete;
+
+ ~PlainTableIterator() override;
+
+ bool Valid() const override;
+
+ void SeekToFirst() override;
+
+ void SeekToLast() override;
+
+ void Seek(const Slice& target) override;
+
+ void SeekForPrev(const Slice& target) override;
+
+ void Next() override;
+
+ void Prev() override;
+
+ Slice key() const override;
+
+ Slice value() const override;
+
+ Status status() const override;
+
+ private:
+ PlainTableReader* table_;
+ PlainTableKeyDecoder decoder_;
+ bool use_prefix_seek_;
+ uint32_t offset_;
+ uint32_t next_offset_;
+ Slice key_;
+ Slice value_;
+ Status status_;
+};
+
+extern const uint64_t kPlainTableMagicNumber;
+PlainTableReader::PlainTableReader(
+ const ImmutableCFOptions& ioptions,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ const EnvOptions& storage_options, const InternalKeyComparator& icomparator,
+ EncodingType encoding_type, uint64_t file_size,
+ const TableProperties* table_properties,
+ const SliceTransform* prefix_extractor)
+ : internal_comparator_(icomparator),
+ encoding_type_(encoding_type),
+ full_scan_mode_(false),
+ user_key_len_(static_cast<uint32_t>(table_properties->fixed_key_len)),
+ prefix_extractor_(prefix_extractor),
+ enable_bloom_(false),
+ bloom_(6),
+ file_info_(std::move(file), storage_options,
+ static_cast<uint32_t>(table_properties->data_size)),
+ ioptions_(ioptions),
+ file_size_(file_size),
+ table_properties_(nullptr) {}
+
+PlainTableReader::~PlainTableReader() {
+}
+
+Status PlainTableReader::Open(
+ const ImmutableCFOptions& ioptions, const EnvOptions& env_options,
+ const InternalKeyComparator& internal_comparator,
+ std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader, const int bloom_bits_per_key,
+ double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size,
+ bool full_scan_mode, const bool immortal_table,
+ const SliceTransform* prefix_extractor) {
+ if (file_size > PlainTableIndex::kMaxFileSize) {
+ return Status::NotSupported("File is too large for PlainTableReader!");
+ }
+
+ TableProperties* props_ptr = nullptr;
+ auto s = ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber,
+ ioptions, &props_ptr,
+ true /* compression_type_missing */);
+ std::shared_ptr<TableProperties> props(props_ptr);
+ if (!s.ok()) {
+ return s;
+ }
+
+ assert(hash_table_ratio >= 0.0);
+ auto& user_props = props->user_collected_properties;
+ auto prefix_extractor_in_file = props->prefix_extractor_name;
+
+ if (!full_scan_mode &&
+ !prefix_extractor_in_file.empty() /* old version sst file*/
+ && prefix_extractor_in_file != "nullptr") {
+ if (!prefix_extractor) {
+ return Status::InvalidArgument(
+ "Prefix extractor is missing when opening a PlainTable built "
+ "using a prefix extractor");
+ } else if (prefix_extractor_in_file.compare(prefix_extractor->Name()) !=
+ 0) {
+ return Status::InvalidArgument(
+ "Prefix extractor given doesn't match the one used to build "
+ "PlainTable");
+ }
+ }
+
+ EncodingType encoding_type = kPlain;
+ auto encoding_type_prop =
+ user_props.find(PlainTablePropertyNames::kEncodingType);
+ if (encoding_type_prop != user_props.end()) {
+ encoding_type = static_cast<EncodingType>(
+ DecodeFixed32(encoding_type_prop->second.c_str()));
+ }
+
+ std::unique_ptr<PlainTableReader> new_reader(new PlainTableReader(
+ ioptions, std::move(file), env_options, internal_comparator,
+ encoding_type, file_size, props.get(), prefix_extractor));
+
+ s = new_reader->MmapDataIfNeeded();
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (!full_scan_mode) {
+ s = new_reader->PopulateIndex(props.get(), bloom_bits_per_key,
+ hash_table_ratio, index_sparseness,
+ huge_page_tlb_size);
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ // Flag to indicate it is a full scan mode so that none of the indexes
+ // can be used.
+ new_reader->full_scan_mode_ = true;
+ }
+ // PopulateIndex can add to the props, so don't store them until now
+ new_reader->table_properties_ = props;
+
+ if (immortal_table && new_reader->file_info_.is_mmap_mode) {
+ new_reader->dummy_cleanable_.reset(new Cleanable());
+ }
+
+ *table_reader = std::move(new_reader);
+ return s;
+}
+
+void PlainTableReader::SetupForCompaction() {
+}
+
+InternalIterator* PlainTableReader::NewIterator(
+ const ReadOptions& options, const SliceTransform* /* prefix_extractor */,
+ Arena* arena, bool /*skip_filters*/, TableReaderCaller /*caller*/,
+ size_t /*compaction_readahead_size*/) {
+ // Not necessarily used here, but make sure this has been initialized
+ assert(table_properties_);
+
+ // Auto prefix mode is not implemented in PlainTable.
+ bool use_prefix_seek = !IsTotalOrderMode() && !options.total_order_seek &&
+ !options.auto_prefix_mode;
+ if (arena == nullptr) {
+ return new PlainTableIterator(this, use_prefix_seek);
+ } else {
+ auto mem = arena->AllocateAligned(sizeof(PlainTableIterator));
+ return new (mem) PlainTableIterator(this, use_prefix_seek);
+ }
+}
+
+Status PlainTableReader::PopulateIndexRecordList(
+ PlainTableIndexBuilder* index_builder,
+ std::vector<uint32_t>* prefix_hashes) {
+ Slice prev_key_prefix_slice;
+ std::string prev_key_prefix_buf;
+ uint32_t pos = data_start_offset_;
+
+ bool is_first_record = true;
+ Slice key_prefix_slice;
+ PlainTableKeyDecoder decoder(&file_info_, encoding_type_, user_key_len_,
+ prefix_extractor_);
+ while (pos < file_info_.data_end_offset) {
+ uint32_t key_offset = pos;
+ ParsedInternalKey key;
+ Slice value_slice;
+ bool seekable = false;
+ Status s = Next(&decoder, &pos, &key, nullptr, &value_slice, &seekable);
+ if (!s.ok()) {
+ return s;
+ }
+
+ key_prefix_slice = GetPrefix(key);
+ if (enable_bloom_) {
+ bloom_.AddHash(GetSliceHash(key.user_key));
+ } else {
+ if (is_first_record || prev_key_prefix_slice != key_prefix_slice) {
+ if (!is_first_record) {
+ prefix_hashes->push_back(GetSliceHash(prev_key_prefix_slice));
+ }
+ if (file_info_.is_mmap_mode) {
+ prev_key_prefix_slice = key_prefix_slice;
+ } else {
+ prev_key_prefix_buf = key_prefix_slice.ToString();
+ prev_key_prefix_slice = prev_key_prefix_buf;
+ }
+ }
+ }
+
+ index_builder->AddKeyPrefix(GetPrefix(key), key_offset);
+
+ if (!seekable && is_first_record) {
+ return Status::Corruption("Key for a prefix is not seekable");
+ }
+
+ is_first_record = false;
+ }
+
+ prefix_hashes->push_back(GetSliceHash(key_prefix_slice));
+ auto s = index_.InitFromRawData(index_builder->Finish());
+ return s;
+}
+
+void PlainTableReader::AllocateBloom(int bloom_bits_per_key, int num_keys,
+ size_t huge_page_tlb_size) {
+ uint32_t bloom_total_bits = num_keys * bloom_bits_per_key;
+ if (bloom_total_bits > 0) {
+ enable_bloom_ = true;
+ bloom_.SetTotalBits(&arena_, bloom_total_bits, ioptions_.bloom_locality,
+ huge_page_tlb_size, ioptions_.info_log);
+ }
+}
+
+void PlainTableReader::FillBloom(const std::vector<uint32_t>& prefix_hashes) {
+ assert(bloom_.IsInitialized());
+ for (const auto prefix_hash : prefix_hashes) {
+ bloom_.AddHash(prefix_hash);
+ }
+}
+
+Status PlainTableReader::MmapDataIfNeeded() {
+ if (file_info_.is_mmap_mode) {
+ // Get mmapped memory.
+ return file_info_.file->Read(0, static_cast<size_t>(file_size_), &file_info_.file_data, nullptr);
+ }
+ return Status::OK();
+}
+
+Status PlainTableReader::PopulateIndex(TableProperties* props,
+ int bloom_bits_per_key,
+ double hash_table_ratio,
+ size_t index_sparseness,
+ size_t huge_page_tlb_size) {
+ assert(props != nullptr);
+
+ BlockContents index_block_contents;
+ Status s = ReadMetaBlock(file_info_.file.get(), nullptr /* prefetch_buffer */,
+ file_size_, kPlainTableMagicNumber, ioptions_,
+ PlainTableIndexBuilder::kPlainTableIndexBlock,
+ BlockType::kIndex, &index_block_contents,
+ true /* compression_type_missing */);
+
+ bool index_in_file = s.ok();
+
+ BlockContents bloom_block_contents;
+ bool bloom_in_file = false;
+ // We only need to read the bloom block if index block is in file.
+ if (index_in_file) {
+ s = ReadMetaBlock(file_info_.file.get(), nullptr /* prefetch_buffer */,
+ file_size_, kPlainTableMagicNumber, ioptions_,
+ BloomBlockBuilder::kBloomBlock, BlockType::kFilter,
+ &bloom_block_contents,
+ true /* compression_type_missing */);
+ bloom_in_file = s.ok() && bloom_block_contents.data.size() > 0;
+ }
+
+ Slice* bloom_block;
+ if (bloom_in_file) {
+ // If bloom_block_contents.allocation is not empty (which will be the case
+ // for non-mmap mode), it holds the alloated memory for the bloom block.
+ // It needs to be kept alive to keep `bloom_block` valid.
+ bloom_block_alloc_ = std::move(bloom_block_contents.allocation);
+ bloom_block = &bloom_block_contents.data;
+ } else {
+ bloom_block = nullptr;
+ }
+
+ Slice* index_block;
+ if (index_in_file) {
+ // If index_block_contents.allocation is not empty (which will be the case
+ // for non-mmap mode), it holds the alloated memory for the index block.
+ // It needs to be kept alive to keep `index_block` valid.
+ index_block_alloc_ = std::move(index_block_contents.allocation);
+ index_block = &index_block_contents.data;
+ } else {
+ index_block = nullptr;
+ }
+
+ if ((prefix_extractor_ == nullptr) && (hash_table_ratio != 0)) {
+ // moptions.prefix_extractor is requried for a hash-based look-up.
+ return Status::NotSupported(
+ "PlainTable requires a prefix extractor enable prefix hash mode.");
+ }
+
+ // First, read the whole file, for every kIndexIntervalForSamePrefixKeys rows
+ // for a prefix (starting from the first one), generate a record of (hash,
+ // offset) and append it to IndexRecordList, which is a data structure created
+ // to store them.
+
+ if (!index_in_file) {
+ // Allocate bloom filter here for total order mode.
+ if (IsTotalOrderMode()) {
+ AllocateBloom(bloom_bits_per_key,
+ static_cast<uint32_t>(props->num_entries),
+ huge_page_tlb_size);
+ }
+ } else if (bloom_in_file) {
+ enable_bloom_ = true;
+ auto num_blocks_property = props->user_collected_properties.find(
+ PlainTablePropertyNames::kNumBloomBlocks);
+
+ uint32_t num_blocks = 0;
+ if (num_blocks_property != props->user_collected_properties.end()) {
+ Slice temp_slice(num_blocks_property->second);
+ if (!GetVarint32(&temp_slice, &num_blocks)) {
+ num_blocks = 0;
+ }
+ }
+ // cast away const qualifier, because bloom_ won't be changed
+ bloom_.SetRawData(const_cast<char*>(bloom_block->data()),
+ static_cast<uint32_t>(bloom_block->size()) * 8,
+ num_blocks);
+ } else {
+ // Index in file but no bloom in file. Disable bloom filter in this case.
+ enable_bloom_ = false;
+ bloom_bits_per_key = 0;
+ }
+
+ PlainTableIndexBuilder index_builder(&arena_, ioptions_, prefix_extractor_,
+ index_sparseness, hash_table_ratio,
+ huge_page_tlb_size);
+
+ std::vector<uint32_t> prefix_hashes;
+ if (!index_in_file) {
+ // Populates _bloom if enabled (total order mode)
+ s = PopulateIndexRecordList(&index_builder, &prefix_hashes);
+ if (!s.ok()) {
+ return s;
+ }
+ } else {
+ s = index_.InitFromRawData(*index_block);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ if (!index_in_file) {
+ if (!IsTotalOrderMode()) {
+ // Calculated bloom filter size and allocate memory for
+ // bloom filter based on the number of prefixes, then fill it.
+ AllocateBloom(bloom_bits_per_key, index_.GetNumPrefixes(),
+ huge_page_tlb_size);
+ if (enable_bloom_) {
+ FillBloom(prefix_hashes);
+ }
+ }
+ }
+
+ // Fill two table properties.
+ if (!index_in_file) {
+ props->user_collected_properties["plain_table_hash_table_size"] =
+ ToString(index_.GetIndexSize() * PlainTableIndex::kOffsetLen);
+ props->user_collected_properties["plain_table_sub_index_size"] =
+ ToString(index_.GetSubIndexSize());
+ } else {
+ props->user_collected_properties["plain_table_hash_table_size"] =
+ ToString(0);
+ props->user_collected_properties["plain_table_sub_index_size"] =
+ ToString(0);
+ }
+
+ return Status::OK();
+}
+
+Status PlainTableReader::GetOffset(PlainTableKeyDecoder* decoder,
+ const Slice& target, const Slice& prefix,
+ uint32_t prefix_hash, bool& prefix_matched,
+ uint32_t* offset) const {
+ prefix_matched = false;
+ uint32_t prefix_index_offset;
+ auto res = index_.GetOffset(prefix_hash, &prefix_index_offset);
+ if (res == PlainTableIndex::kNoPrefixForBucket) {
+ *offset = file_info_.data_end_offset;
+ return Status::OK();
+ } else if (res == PlainTableIndex::kDirectToFile) {
+ *offset = prefix_index_offset;
+ return Status::OK();
+ }
+
+ // point to sub-index, need to do a binary search
+ uint32_t upper_bound;
+ const char* base_ptr =
+ index_.GetSubIndexBasePtrAndUpperBound(prefix_index_offset, &upper_bound);
+ uint32_t low = 0;
+ uint32_t high = upper_bound;
+ ParsedInternalKey mid_key;
+ ParsedInternalKey parsed_target;
+ if (!ParseInternalKey(target, &parsed_target)) {
+ return Status::Corruption(Slice());
+ }
+
+ // The key is between [low, high). Do a binary search between it.
+ while (high - low > 1) {
+ uint32_t mid = (high + low) / 2;
+ uint32_t file_offset = GetFixed32Element(base_ptr, mid);
+ uint32_t tmp;
+ Status s = decoder->NextKeyNoValue(file_offset, &mid_key, nullptr, &tmp);
+ if (!s.ok()) {
+ return s;
+ }
+ int cmp_result = internal_comparator_.Compare(mid_key, parsed_target);
+ if (cmp_result < 0) {
+ low = mid;
+ } else {
+ if (cmp_result == 0) {
+ // Happen to have found the exact key or target is smaller than the
+ // first key after base_offset.
+ prefix_matched = true;
+ *offset = file_offset;
+ return Status::OK();
+ } else {
+ high = mid;
+ }
+ }
+ }
+ // Both of the key at the position low or low+1 could share the same
+ // prefix as target. We need to rule out one of them to avoid to go
+ // to the wrong prefix.
+ ParsedInternalKey low_key;
+ uint32_t tmp;
+ uint32_t low_key_offset = GetFixed32Element(base_ptr, low);
+ Status s = decoder->NextKeyNoValue(low_key_offset, &low_key, nullptr, &tmp);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (GetPrefix(low_key) == prefix) {
+ prefix_matched = true;
+ *offset = low_key_offset;
+ } else if (low + 1 < upper_bound) {
+ // There is possible a next prefix, return it
+ prefix_matched = false;
+ *offset = GetFixed32Element(base_ptr, low + 1);
+ } else {
+ // target is larger than a key of the last prefix in this bucket
+ // but with a different prefix. Key does not exist.
+ *offset = file_info_.data_end_offset;
+ }
+ return Status::OK();
+}
+
+bool PlainTableReader::MatchBloom(uint32_t hash) const {
+ if (!enable_bloom_) {
+ return true;
+ }
+
+ if (bloom_.MayContainHash(hash)) {
+ PERF_COUNTER_ADD(bloom_sst_hit_count, 1);
+ return true;
+ } else {
+ PERF_COUNTER_ADD(bloom_sst_miss_count, 1);
+ return false;
+ }
+}
+
+Status PlainTableReader::Next(PlainTableKeyDecoder* decoder, uint32_t* offset,
+ ParsedInternalKey* parsed_key,
+ Slice* internal_key, Slice* value,
+ bool* seekable) const {
+ if (*offset == file_info_.data_end_offset) {
+ *offset = file_info_.data_end_offset;
+ return Status::OK();
+ }
+
+ if (*offset > file_info_.data_end_offset) {
+ return Status::Corruption("Offset is out of file size");
+ }
+
+ uint32_t bytes_read;
+ Status s = decoder->NextKey(*offset, parsed_key, internal_key, value,
+ &bytes_read, seekable);
+ if (!s.ok()) {
+ return s;
+ }
+ *offset = *offset + bytes_read;
+ return Status::OK();
+}
+
+void PlainTableReader::Prepare(const Slice& target) {
+ if (enable_bloom_) {
+ uint32_t prefix_hash = GetSliceHash(GetPrefix(target));
+ bloom_.Prefetch(prefix_hash);
+ }
+}
+
+Status PlainTableReader::Get(const ReadOptions& /*ro*/, const Slice& target,
+ GetContext* get_context,
+ const SliceTransform* /* prefix_extractor */,
+ bool /*skip_filters*/) {
+ // Check bloom filter first.
+ Slice prefix_slice;
+ uint32_t prefix_hash;
+ if (IsTotalOrderMode()) {
+ if (full_scan_mode_) {
+ status_ =
+ Status::InvalidArgument("Get() is not allowed in full scan mode.");
+ }
+ // Match whole user key for bloom filter check.
+ if (!MatchBloom(GetSliceHash(GetUserKey(target)))) {
+ return Status::OK();
+ }
+ // in total order mode, there is only one bucket 0, and we always use empty
+ // prefix.
+ prefix_slice = Slice();
+ prefix_hash = 0;
+ } else {
+ prefix_slice = GetPrefix(target);
+ prefix_hash = GetSliceHash(prefix_slice);
+ if (!MatchBloom(prefix_hash)) {
+ return Status::OK();
+ }
+ }
+ uint32_t offset;
+ bool prefix_match;
+ PlainTableKeyDecoder decoder(&file_info_, encoding_type_, user_key_len_,
+ prefix_extractor_);
+ Status s = GetOffset(&decoder, target, prefix_slice, prefix_hash,
+ prefix_match, &offset);
+
+ if (!s.ok()) {
+ return s;
+ }
+ ParsedInternalKey found_key;
+ ParsedInternalKey parsed_target;
+ if (!ParseInternalKey(target, &parsed_target)) {
+ return Status::Corruption(Slice());
+ }
+ Slice found_value;
+ while (offset < file_info_.data_end_offset) {
+ s = Next(&decoder, &offset, &found_key, nullptr, &found_value);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!prefix_match) {
+ // Need to verify prefix for the first key found if it is not yet
+ // checked.
+ if (GetPrefix(found_key) != prefix_slice) {
+ return Status::OK();
+ }
+ prefix_match = true;
+ }
+ // TODO(ljin): since we know the key comparison result here,
+ // can we enable the fast path?
+ if (internal_comparator_.Compare(found_key, parsed_target) >= 0) {
+ bool dont_care __attribute__((__unused__));
+ if (!get_context->SaveValue(found_key, found_value, &dont_care,
+ dummy_cleanable_.get())) {
+ break;
+ }
+ }
+ }
+ return Status::OK();
+}
+
+uint64_t PlainTableReader::ApproximateOffsetOf(const Slice& /*key*/,
+ TableReaderCaller /*caller*/) {
+ return 0;
+}
+
+uint64_t PlainTableReader::ApproximateSize(const Slice& /*start*/,
+ const Slice& /*end*/,
+ TableReaderCaller /*caller*/) {
+ return 0;
+}
+
+PlainTableIterator::PlainTableIterator(PlainTableReader* table,
+ bool use_prefix_seek)
+ : table_(table),
+ decoder_(&table_->file_info_, table_->encoding_type_,
+ table_->user_key_len_, table_->prefix_extractor_),
+ use_prefix_seek_(use_prefix_seek) {
+ next_offset_ = offset_ = table_->file_info_.data_end_offset;
+}
+
+PlainTableIterator::~PlainTableIterator() {
+}
+
+bool PlainTableIterator::Valid() const {
+ return offset_ < table_->file_info_.data_end_offset &&
+ offset_ >= table_->data_start_offset_;
+}
+
+void PlainTableIterator::SeekToFirst() {
+ status_ = Status::OK();
+ next_offset_ = table_->data_start_offset_;
+ if (next_offset_ >= table_->file_info_.data_end_offset) {
+ next_offset_ = offset_ = table_->file_info_.data_end_offset;
+ } else {
+ Next();
+ }
+}
+
+void PlainTableIterator::SeekToLast() {
+ assert(false);
+ status_ = Status::NotSupported("SeekToLast() is not supported in PlainTable");
+ next_offset_ = offset_ = table_->file_info_.data_end_offset;
+}
+
+void PlainTableIterator::Seek(const Slice& target) {
+ if (use_prefix_seek_ != !table_->IsTotalOrderMode()) {
+ // This check is done here instead of NewIterator() to permit creating an
+ // iterator with total_order_seek = true even if we won't be able to Seek()
+ // it. This is needed for compaction: it creates iterator with
+ // total_order_seek = true but usually never does Seek() on it,
+ // only SeekToFirst().
+ status_ =
+ Status::InvalidArgument(
+ "total_order_seek not implemented for PlainTable.");
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ return;
+ }
+
+ // If the user doesn't set prefix seek option and we are not able to do a
+ // total Seek(). assert failure.
+ if (table_->IsTotalOrderMode()) {
+ if (table_->full_scan_mode_) {
+ status_ =
+ Status::InvalidArgument("Seek() is not allowed in full scan mode.");
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ return;
+ } else if (table_->GetIndexSize() > 1) {
+ assert(false);
+ status_ = Status::NotSupported(
+ "PlainTable cannot issue non-prefix seek unless in total order "
+ "mode.");
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ return;
+ }
+ }
+
+ Slice prefix_slice = table_->GetPrefix(target);
+ uint32_t prefix_hash = 0;
+ // Bloom filter is ignored in total-order mode.
+ if (!table_->IsTotalOrderMode()) {
+ prefix_hash = GetSliceHash(prefix_slice);
+ if (!table_->MatchBloom(prefix_hash)) {
+ status_ = Status::OK();
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ return;
+ }
+ }
+ bool prefix_match;
+ status_ = table_->GetOffset(&decoder_, target, prefix_slice, prefix_hash,
+ prefix_match, &next_offset_);
+ if (!status_.ok()) {
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ return;
+ }
+
+ if (next_offset_ < table_->file_info_.data_end_offset) {
+ for (Next(); status_.ok() && Valid(); Next()) {
+ if (!prefix_match) {
+ // Need to verify the first key's prefix
+ if (table_->GetPrefix(key()) != prefix_slice) {
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ break;
+ }
+ prefix_match = true;
+ }
+ if (table_->internal_comparator_.Compare(key(), target) >= 0) {
+ break;
+ }
+ }
+ } else {
+ offset_ = table_->file_info_.data_end_offset;
+ }
+}
+
+void PlainTableIterator::SeekForPrev(const Slice& /*target*/) {
+ assert(false);
+ status_ =
+ Status::NotSupported("SeekForPrev() is not supported in PlainTable");
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+}
+
+void PlainTableIterator::Next() {
+ offset_ = next_offset_;
+ if (offset_ < table_->file_info_.data_end_offset) {
+ Slice tmp_slice;
+ ParsedInternalKey parsed_key;
+ status_ =
+ table_->Next(&decoder_, &next_offset_, &parsed_key, &key_, &value_);
+ if (!status_.ok()) {
+ offset_ = next_offset_ = table_->file_info_.data_end_offset;
+ }
+ }
+}
+
+void PlainTableIterator::Prev() {
+ assert(false);
+}
+
+Slice PlainTableIterator::key() const {
+ assert(Valid());
+ return key_;
+}
+
+Slice PlainTableIterator::value() const {
+ assert(Valid());
+ return value_;
+}
+
+Status PlainTableIterator::status() const {
+ return status_;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/plain/plain_table_reader.h b/storage/rocksdb/rocksdb/table/plain/plain_table_reader.h
new file mode 100644
index 00000000..db7b0626
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/plain/plain_table_reader.h
@@ -0,0 +1,246 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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
+
+#ifndef ROCKSDB_LITE
+#include <unordered_map>
+#include <memory>
+#include <vector>
+#include <string>
+#include <stdint.h>
+
+#include "db/dbformat.h"
+#include "file/random_access_file_reader.h"
+#include "memory/arena.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "table/plain/plain_table_bloom.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/plain/plain_table_index.h"
+#include "table/table_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Block;
+struct BlockContents;
+class BlockHandle;
+class Footer;
+struct Options;
+class RandomAccessFile;
+struct ReadOptions;
+class TableCache;
+class TableReader;
+class InternalKeyComparator;
+class PlainTableKeyDecoder;
+class GetContext;
+
+extern const uint32_t kPlainTableVariableLength;
+
+struct PlainTableReaderFileInfo {
+ bool is_mmap_mode;
+ Slice file_data;
+ uint32_t data_end_offset;
+ std::unique_ptr<RandomAccessFileReader> file;
+
+ PlainTableReaderFileInfo(std::unique_ptr<RandomAccessFileReader>&& _file,
+ const EnvOptions& storage_options,
+ uint32_t _data_size_offset)
+ : is_mmap_mode(storage_options.use_mmap_reads),
+ data_end_offset(_data_size_offset),
+ file(std::move(_file)) {}
+};
+
+// The reader class of PlainTable. For description of PlainTable format
+// See comments of class PlainTableFactory, where instances of
+// PlainTableReader are created.
+class PlainTableReader: public TableReader {
+ public:
+// Based on following output file format shown in plain_table_factory.h
+// When opening the output file, PlainTableReader creates a hash table
+// from key prefixes to offset of the output file. PlainTable will decide
+// whether it points to the data offset of the first key with the key prefix
+// or the offset of it. If there are too many keys share this prefix, it will
+// create a binary search-able index from the suffix to offset on disk.
+ static Status Open(const ImmutableCFOptions& ioptions,
+ const EnvOptions& env_options,
+ const InternalKeyComparator& internal_comparator,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ uint64_t file_size, std::unique_ptr<TableReader>* table,
+ const int bloom_bits_per_key, double hash_table_ratio,
+ size_t index_sparseness, size_t huge_page_tlb_size,
+ bool full_scan_mode, const bool immortal_table = false,
+ const SliceTransform* prefix_extractor = nullptr);
+
+ // Returns new iterator over table contents
+ // compaction_readahead_size: its value will only be used if for_compaction =
+ // true
+ InternalIterator* NewIterator(const ReadOptions&,
+ const SliceTransform* prefix_extractor,
+ Arena* arena, bool skip_filters,
+ TableReaderCaller caller,
+ size_t compaction_readahead_size = 0) override;
+
+ void Prepare(const Slice& target) override;
+
+ Status Get(const ReadOptions& readOptions, const Slice& key,
+ GetContext* get_context, const SliceTransform* prefix_extractor,
+ bool skip_filters = false) override;
+
+ uint64_t ApproximateOffsetOf(const Slice& key,
+ TableReaderCaller caller) override;
+
+ uint64_t ApproximateSize(const Slice& start, const Slice& end,
+ TableReaderCaller caller) override;
+
+ uint32_t GetIndexSize() const { return index_.GetIndexSize(); }
+ void SetupForCompaction() override;
+
+ std::shared_ptr<const TableProperties> GetTableProperties() const override {
+ return table_properties_;
+ }
+
+ virtual size_t ApproximateMemoryUsage() const override {
+ return arena_.MemoryAllocatedBytes();
+ }
+
+ PlainTableReader(const ImmutableCFOptions& ioptions,
+ std::unique_ptr<RandomAccessFileReader>&& file,
+ const EnvOptions& env_options,
+ const InternalKeyComparator& internal_comparator,
+ EncodingType encoding_type, uint64_t file_size,
+ const TableProperties* table_properties,
+ const SliceTransform* prefix_extractor);
+ virtual ~PlainTableReader();
+
+ protected:
+ // Check bloom filter to see whether it might contain this prefix.
+ // The hash of the prefix is given, since it can be reused for index lookup
+ // too.
+ virtual bool MatchBloom(uint32_t hash) const;
+
+ // PopulateIndex() builds index of keys. It must be called before any query
+ // to the table.
+ //
+ // props: the table properties object that need to be stored. Ownership of
+ // the object will be passed.
+ //
+
+ Status PopulateIndex(TableProperties* props, int bloom_bits_per_key,
+ double hash_table_ratio, size_t index_sparseness,
+ size_t huge_page_tlb_size);
+
+ Status MmapDataIfNeeded();
+
+ private:
+ const InternalKeyComparator internal_comparator_;
+ EncodingType encoding_type_;
+ // represents plain table's current status.
+ Status status_;
+
+ PlainTableIndex index_;
+ bool full_scan_mode_;
+
+ // data_start_offset_ and data_end_offset_ defines the range of the
+ // sst file that stores data.
+ const uint32_t data_start_offset_ = 0;
+ const uint32_t user_key_len_;
+ const SliceTransform* prefix_extractor_;
+
+ static const size_t kNumInternalBytes = 8;
+
+ // Bloom filter is used to rule out non-existent key
+ bool enable_bloom_;
+ PlainTableBloomV1 bloom_;
+ PlainTableReaderFileInfo file_info_;
+ Arena arena_;
+ CacheAllocationPtr index_block_alloc_;
+ CacheAllocationPtr bloom_block_alloc_;
+
+ const ImmutableCFOptions& ioptions_;
+ std::unique_ptr<Cleanable> dummy_cleanable_;
+ uint64_t file_size_;
+ protected: // for testing
+ std::shared_ptr<const TableProperties> table_properties_;
+ private:
+
+ bool IsFixedLength() const {
+ return user_key_len_ != kPlainTableVariableLength;
+ }
+
+ size_t GetFixedInternalKeyLength() const {
+ return user_key_len_ + kNumInternalBytes;
+ }
+
+ Slice GetPrefix(const Slice& target) const {
+ assert(target.size() >= 8); // target is internal key
+ return GetPrefixFromUserKey(GetUserKey(target));
+ }
+
+ Slice GetPrefix(const ParsedInternalKey& target) const {
+ return GetPrefixFromUserKey(target.user_key);
+ }
+
+ Slice GetUserKey(const Slice& key) const {
+ return Slice(key.data(), key.size() - 8);
+ }
+
+ Slice GetPrefixFromUserKey(const Slice& user_key) const {
+ if (!IsTotalOrderMode()) {
+ return prefix_extractor_->Transform(user_key);
+ } else {
+ // Use empty slice as prefix if prefix_extractor is not set.
+ // In that case,
+ // it falls back to pure binary search and
+ // total iterator seek is supported.
+ return Slice();
+ }
+ }
+
+ friend class TableCache;
+ friend class PlainTableIterator;
+
+ // Internal helper function to generate an IndexRecordList object from all
+ // the rows, which contains index records as a list.
+ // If bloom_ is not null, all the keys' full-key hash will be added to the
+ // bloom filter.
+ Status PopulateIndexRecordList(PlainTableIndexBuilder* index_builder,
+ std::vector<uint32_t>* prefix_hashes);
+
+ // Internal helper function to allocate memory for bloom filter
+ void AllocateBloom(int bloom_bits_per_key, int num_prefixes,
+ size_t huge_page_tlb_size);
+
+ void FillBloom(const std::vector<uint32_t>& prefix_hashes);
+
+ // Read the key and value at `offset` to parameters for keys, the and
+ // `seekable`.
+ // On success, `offset` will be updated as the offset for the next key.
+ // `parsed_key` will be key in parsed format.
+ // if `internal_key` is not empty, it will be filled with key with slice
+ // format.
+ // if `seekable` is not null, it will return whether we can directly read
+ // data using this offset.
+ Status Next(PlainTableKeyDecoder* decoder, uint32_t* offset,
+ ParsedInternalKey* parsed_key, Slice* internal_key, Slice* value,
+ bool* seekable = nullptr) const;
+ // Get file offset for key target.
+ // return value prefix_matched is set to true if the offset is confirmed
+ // for a key with the same prefix as target.
+ Status GetOffset(PlainTableKeyDecoder* decoder, const Slice& target,
+ const Slice& prefix, uint32_t prefix_hash,
+ bool& prefix_matched, uint32_t* offset) const;
+
+ bool IsTotalOrderMode() const { return (prefix_extractor_ == nullptr); }
+
+ // No copying allowed
+ explicit PlainTableReader(const TableReader&) = delete;
+ void operator=(const TableReader&) = delete;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/scoped_arena_iterator.h b/storage/rocksdb/rocksdb/table/scoped_arena_iterator.h
new file mode 100644
index 00000000..8d73d12e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/scoped_arena_iterator.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/internal_iterator.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+class ScopedArenaIterator {
+
+ void reset(InternalIterator* iter) ROCKSDB_NOEXCEPT {
+ if (iter_ != nullptr) {
+ iter_->~InternalIterator();
+ }
+ iter_ = iter;
+ }
+
+ public:
+
+ explicit ScopedArenaIterator(InternalIterator* iter = nullptr)
+ : iter_(iter) {}
+
+ ScopedArenaIterator(const ScopedArenaIterator&) = delete;
+ ScopedArenaIterator& operator=(const ScopedArenaIterator&) = delete;
+
+ ScopedArenaIterator(ScopedArenaIterator&& o) ROCKSDB_NOEXCEPT {
+ iter_ = o.iter_;
+ o.iter_ = nullptr;
+ }
+
+ ScopedArenaIterator& operator=(ScopedArenaIterator&& o) ROCKSDB_NOEXCEPT {
+ reset(o.iter_);
+ o.iter_ = nullptr;
+ return *this;
+ }
+
+ InternalIterator* operator->() { return iter_; }
+ InternalIterator* get() { return iter_; }
+
+ void set(InternalIterator* iter) { reset(iter); }
+
+ InternalIterator* release() {
+ assert(iter_ != nullptr);
+ auto* res = iter_;
+ iter_ = nullptr;
+ return res;
+ }
+
+ ~ScopedArenaIterator() {
+ reset(nullptr);
+ }
+
+ private:
+ InternalIterator* iter_;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/sst_file_reader.cc b/storage/rocksdb/rocksdb/table/sst_file_reader.cc
new file mode 100644
index 00000000..9e3ba6ea
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/sst_file_reader.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/sst_file_reader.h"
+
+#include "db/db_iter.h"
+#include "db/dbformat.h"
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "options/cf_options.h"
+#include "table/get_context.h"
+#include "table/table_builder.h"
+#include "table/table_reader.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct SstFileReader::Rep {
+ Options options;
+ EnvOptions soptions;
+ ImmutableCFOptions ioptions;
+ MutableCFOptions moptions;
+
+ std::unique_ptr<TableReader> table_reader;
+
+ Rep(const Options& opts)
+ : options(opts),
+ soptions(options),
+ ioptions(options),
+ moptions(ColumnFamilyOptions(options)) {}
+};
+
+SstFileReader::SstFileReader(const Options& options) : rep_(new Rep(options)) {}
+
+SstFileReader::~SstFileReader() {}
+
+Status SstFileReader::Open(const std::string& file_path) {
+ auto r = rep_.get();
+ Status s;
+ uint64_t file_size = 0;
+ std::unique_ptr<RandomAccessFile> file;
+ std::unique_ptr<RandomAccessFileReader> file_reader;
+ s = r->options.env->GetFileSize(file_path, &file_size);
+ if (s.ok()) {
+ s = r->options.env->NewRandomAccessFile(file_path, &file, r->soptions);
+ }
+ if (s.ok()) {
+ file_reader.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(file), file_path));
+ }
+ if (s.ok()) {
+ TableReaderOptions t_opt(r->ioptions, r->moptions.prefix_extractor.get(),
+ r->soptions, r->ioptions.internal_comparator);
+ // Allow open file with global sequence number for backward compatibility.
+ t_opt.largest_seqno = kMaxSequenceNumber;
+ s = r->options.table_factory->NewTableReader(t_opt, std::move(file_reader),
+ file_size, &r->table_reader);
+ }
+ return s;
+}
+
+Iterator* SstFileReader::NewIterator(const ReadOptions& options) {
+ auto r = rep_.get();
+ auto sequence = options.snapshot != nullptr
+ ? options.snapshot->GetSequenceNumber()
+ : kMaxSequenceNumber;
+ auto internal_iter = r->table_reader->NewIterator(
+ options, r->moptions.prefix_extractor.get(), /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kSSTFileReader);
+ return NewDBIterator(r->options.env, options, r->ioptions, r->moptions,
+ r->ioptions.user_comparator, internal_iter, sequence,
+ r->moptions.max_sequential_skip_in_iterations,
+ nullptr /* read_callback */);
+}
+
+std::shared_ptr<const TableProperties> SstFileReader::GetTableProperties()
+ const {
+ return rep_->table_reader->GetTableProperties();
+}
+
+Status SstFileReader::VerifyChecksum(const ReadOptions& read_options) {
+ return rep_->table_reader->VerifyChecksum(read_options,
+ TableReaderCaller::kSSTFileReader);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/sst_file_reader_test.cc b/storage/rocksdb/rocksdb/table/sst_file_reader_test.cc
new file mode 100644
index 00000000..ac020a3e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/sst_file_reader_test.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <cinttypes>
+
+#include "rocksdb/db.h"
+#include "rocksdb/sst_file_reader.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");
+ }
+
+ ~SstFileReaderTest() {
+ Status s = Env::Default()->DeleteFile(sst_name_);
+ assert(s.ok());
+ }
+
+ void CreateFile(const std::string& file_name,
+ const std::vector<std::string>& 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<std::string>& keys,
+ bool check_global_seqno = false) {
+ ReadOptions ropts;
+ SstFileReader reader(options_);
+ ASSERT_OK(reader.Open(file_name));
+ ASSERT_OK(reader.VerifyChecksum());
+ std::unique_ptr<Iterator> 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);
+ auto& user_properties = properties->user_collected_properties;
+ ASSERT_TRUE(
+ user_properties.count(ExternalSstFilePropertyNames::kGlobalSeqno));
+ }
+ }
+
+ void CreateFileAndCheck(const std::vector<std::string>& keys) {
+ CreateFile(sst_name_, keys);
+ CheckFile(sst_name_, keys);
+ }
+
+ protected:
+ Options options_;
+ EnvOptions soptions_;
+ std::string sst_name_;
+};
+
+const uint64_t kNumKeys = 100;
+
+TEST_F(SstFileReaderTest, Basic) {
+ std::vector<std::string> 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<std::string> keys;
+ for (uint64_t i = 0; i < kNumKeys; i++) {
+ keys.emplace_back(EncodeAsUint64(i));
+ }
+ CreateFileAndCheck(keys);
+}
+
+TEST_F(SstFileReaderTest, ReadFileWithGlobalSeqno) {
+ std::vector<std::string> 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<std::string> 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));
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as SstFileReader is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/table/sst_file_writer.cc b/storage/rocksdb/rocksdb/table/sst_file_writer.cc
new file mode 100644
index 00000000..a5d08ea7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/sst_file_writer.cc
@@ -0,0 +1,319 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_writer.h"
+
+#include <vector>
+
+#include "db/dbformat.h"
+#include "env/composite_env_wrapper.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_based_table_builder.h"
+#include "table/sst_file_writer_collectors.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const std::string ExternalSstFilePropertyNames::kVersion =
+ "rocksdb.external_sst_file.version";
+const std::string ExternalSstFilePropertyNames::kGlobalSeqno =
+ "rocksdb.external_sst_file.global_seqno";
+
+#ifndef ROCKSDB_LITE
+
+const size_t kFadviseTrigger = 1024 * 1024; // 1MB
+
+struct SstFileWriter::Rep {
+ Rep(const EnvOptions& _env_options, const Options& options,
+ Env::IOPriority _io_priority, const Comparator* _user_comparator,
+ ColumnFamilyHandle* _cfh, bool _invalidate_page_cache, bool _skip_filters)
+ : env_options(_env_options),
+ ioptions(options),
+ mutable_cf_options(options),
+ io_priority(_io_priority),
+ internal_comparator(_user_comparator),
+ cfh(_cfh),
+ invalidate_page_cache(_invalidate_page_cache),
+ last_fadvise_size(0),
+ skip_filters(_skip_filters) {}
+
+ std::unique_ptr<WritableFileWriter> file_writer;
+ std::unique_ptr<TableBuilder> builder;
+ EnvOptions env_options;
+ ImmutableCFOptions ioptions;
+ MutableCFOptions mutable_cf_options;
+ Env::IOPriority io_priority;
+ InternalKeyComparator internal_comparator;
+ ExternalSstFileInfo file_info;
+ InternalKey ikey;
+ std::string column_family_name;
+ ColumnFamilyHandle* cfh;
+ // If true, We will give the OS a hint that this file pages is not needed
+ // every time we write 1MB to the file.
+ bool invalidate_page_cache;
+ // The size of the file during the last time we called Fadvise to remove
+ // cached pages from page cache.
+ uint64_t last_fadvise_size;
+ bool skip_filters;
+ Status Add(const Slice& user_key, const Slice& value,
+ const ValueType value_type) {
+ if (!builder) {
+ return Status::InvalidArgument("File is not opened");
+ }
+
+ if (file_info.num_entries == 0) {
+ file_info.smallest_key.assign(user_key.data(), user_key.size());
+ } else {
+ if (internal_comparator.user_comparator()->Compare(
+ user_key, file_info.largest_key) <= 0) {
+ // Make sure that keys are added in order
+ return Status::InvalidArgument(
+ "Keys must be added in strict ascending order.");
+ }
+ }
+
+ // TODO(tec) : For external SST files we could omit the seqno and type.
+ switch (value_type) {
+ case ValueType::kTypeValue:
+ ikey.Set(user_key, 0 /* Sequence Number */,
+ ValueType::kTypeValue /* Put */);
+ break;
+ case ValueType::kTypeMerge:
+ ikey.Set(user_key, 0 /* Sequence Number */,
+ ValueType::kTypeMerge /* Merge */);
+ break;
+ case ValueType::kTypeDeletion:
+ ikey.Set(user_key, 0 /* Sequence Number */,
+ ValueType::kTypeDeletion /* Delete */);
+ break;
+ default:
+ return Status::InvalidArgument("Value type is not supported");
+ }
+ builder->Add(ikey.Encode(), value);
+
+ // update file info
+ file_info.num_entries++;
+ file_info.largest_key.assign(user_key.data(), user_key.size());
+ file_info.file_size = builder->FileSize();
+
+ InvalidatePageCache(false /* closing */);
+
+ return Status::OK();
+ }
+
+ Status DeleteRange(const Slice& begin_key, const Slice& end_key) {
+ if (!builder) {
+ return Status::InvalidArgument("File is not opened");
+ }
+
+ RangeTombstone tombstone(begin_key, end_key, 0 /* Sequence Number */);
+ if (file_info.num_range_del_entries == 0) {
+ file_info.smallest_range_del_key.assign(tombstone.start_key_.data(),
+ tombstone.start_key_.size());
+ file_info.largest_range_del_key.assign(tombstone.end_key_.data(),
+ tombstone.end_key_.size());
+ } else {
+ if (internal_comparator.user_comparator()->Compare(
+ tombstone.start_key_, file_info.smallest_range_del_key) < 0) {
+ file_info.smallest_range_del_key.assign(tombstone.start_key_.data(),
+ tombstone.start_key_.size());
+ }
+ if (internal_comparator.user_comparator()->Compare(
+ tombstone.end_key_, file_info.largest_range_del_key) > 0) {
+ file_info.largest_range_del_key.assign(tombstone.end_key_.data(),
+ tombstone.end_key_.size());
+ }
+ }
+
+ auto ikey_and_end_key = tombstone.Serialize();
+ builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second);
+
+ // update file info
+ file_info.num_range_del_entries++;
+ file_info.file_size = builder->FileSize();
+
+ InvalidatePageCache(false /* closing */);
+
+ return Status::OK();
+ }
+
+ void InvalidatePageCache(bool closing) {
+ if (invalidate_page_cache == false) {
+ // Fadvise disabled
+ return;
+ }
+ uint64_t bytes_since_last_fadvise =
+ builder->FileSize() - last_fadvise_size;
+ if (bytes_since_last_fadvise > kFadviseTrigger || closing) {
+ TEST_SYNC_POINT_CALLBACK("SstFileWriter::Rep::InvalidatePageCache",
+ &(bytes_since_last_fadvise));
+ // Tell the OS that we dont need this file in page cache
+ file_writer->InvalidateCache(0, 0);
+ last_fadvise_size = builder->FileSize();
+ }
+ }
+
+};
+
+SstFileWriter::SstFileWriter(const EnvOptions& env_options,
+ const Options& options,
+ const Comparator* user_comparator,
+ ColumnFamilyHandle* column_family,
+ bool invalidate_page_cache,
+ Env::IOPriority io_priority, bool skip_filters)
+ : rep_(new Rep(env_options, options, io_priority, user_comparator,
+ column_family, invalidate_page_cache, skip_filters)) {
+ rep_->file_info.file_size = 0;
+}
+
+SstFileWriter::~SstFileWriter() {
+ if (rep_->builder) {
+ // User did not call Finish() or Finish() failed, we need to
+ // abandon the builder.
+ rep_->builder->Abandon();
+ }
+}
+
+Status SstFileWriter::Open(const std::string& file_path) {
+ Rep* r = rep_.get();
+ Status s;
+ std::unique_ptr<WritableFile> sst_file;
+ s = r->ioptions.env->NewWritableFile(file_path, &sst_file, r->env_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ sst_file->SetIOPriority(r->io_priority);
+
+ CompressionType compression_type;
+ CompressionOptions compression_opts;
+ if (r->ioptions.bottommost_compression != kDisableCompressionOption) {
+ compression_type = r->ioptions.bottommost_compression;
+ if (r->ioptions.bottommost_compression_opts.enabled) {
+ compression_opts = r->ioptions.bottommost_compression_opts;
+ } else {
+ compression_opts = r->ioptions.compression_opts;
+ }
+ } else if (!r->ioptions.compression_per_level.empty()) {
+ // Use the compression of the last level if we have per level compression
+ compression_type = *(r->ioptions.compression_per_level.rbegin());
+ compression_opts = r->ioptions.compression_opts;
+ } else {
+ compression_type = r->mutable_cf_options.compression;
+ compression_opts = r->ioptions.compression_opts;
+ }
+ uint64_t sample_for_compression =
+ r->mutable_cf_options.sample_for_compression;
+
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+
+ // SstFileWriter properties collector to add SstFileWriter version.
+ int_tbl_prop_collector_factories.emplace_back(
+ new SstFileWriterPropertiesCollectorFactory(2 /* version */,
+ 0 /* global_seqno*/));
+
+ // User collector factories
+ auto user_collector_factories =
+ r->ioptions.table_properties_collector_factories;
+ for (size_t i = 0; i < user_collector_factories.size(); i++) {
+ int_tbl_prop_collector_factories.emplace_back(
+ new UserKeyTablePropertiesCollectorFactory(
+ user_collector_factories[i]));
+ }
+ int unknown_level = -1;
+ uint32_t cf_id;
+
+ if (r->cfh != nullptr) {
+ // user explicitly specified that this file will be ingested into cfh,
+ // we can persist this information in the file.
+ cf_id = r->cfh->GetID();
+ r->column_family_name = r->cfh->GetName();
+ } else {
+ r->column_family_name = "";
+ cf_id = TablePropertiesCollectorFactory::Context::kUnknownColumnFamily;
+ }
+
+ TableBuilderOptions table_builder_options(
+ r->ioptions, r->mutable_cf_options, r->internal_comparator,
+ &int_tbl_prop_collector_factories, compression_type,
+ sample_for_compression, compression_opts, r->skip_filters,
+ r->column_family_name, unknown_level);
+ r->file_writer.reset(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(sst_file)),
+ file_path, r->env_options, r->ioptions.env,
+ nullptr /* stats */, r->ioptions.listeners));
+
+ // TODO(tec) : If table_factory is using compressed block cache, we will
+ // be adding the external sst file blocks into it, which is wasteful.
+ r->builder.reset(r->ioptions.table_factory->NewTableBuilder(
+ table_builder_options, cf_id, r->file_writer.get()));
+
+ r->file_info = ExternalSstFileInfo();
+ r->file_info.file_path = file_path;
+ r->file_info.version = 2;
+ return s;
+}
+
+Status SstFileWriter::Add(const Slice& user_key, const Slice& value) {
+ return rep_->Add(user_key, value, ValueType::kTypeValue);
+}
+
+Status SstFileWriter::Put(const Slice& user_key, const Slice& value) {
+ return rep_->Add(user_key, value, ValueType::kTypeValue);
+}
+
+Status SstFileWriter::Merge(const Slice& user_key, const Slice& value) {
+ return rep_->Add(user_key, value, ValueType::kTypeMerge);
+}
+
+Status SstFileWriter::Delete(const Slice& user_key) {
+ return rep_->Add(user_key, Slice(), ValueType::kTypeDeletion);
+}
+
+Status SstFileWriter::DeleteRange(const Slice& begin_key,
+ const Slice& end_key) {
+ return rep_->DeleteRange(begin_key, end_key);
+}
+
+Status SstFileWriter::Finish(ExternalSstFileInfo* file_info) {
+ Rep* r = rep_.get();
+ if (!r->builder) {
+ return Status::InvalidArgument("File is not opened");
+ }
+ if (r->file_info.num_entries == 0 &&
+ r->file_info.num_range_del_entries == 0) {
+ return Status::InvalidArgument("Cannot create sst file with no entries");
+ }
+
+ Status s = r->builder->Finish();
+ r->file_info.file_size = r->builder->FileSize();
+
+ if (s.ok()) {
+ s = r->file_writer->Sync(r->ioptions.use_fsync);
+ r->InvalidatePageCache(true /* closing */);
+ if (s.ok()) {
+ s = r->file_writer->Close();
+ }
+ }
+ if (!s.ok()) {
+ r->ioptions.env->DeleteFile(r->file_info.file_path);
+ }
+
+ if (file_info != nullptr) {
+ *file_info = r->file_info;
+ }
+
+ r->builder.reset();
+ return s;
+}
+
+uint64_t SstFileWriter::FileSize() {
+ return rep_->file_info.file_size;
+}
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/sst_file_writer_collectors.h b/storage/rocksdb/rocksdb/table/sst_file_writer_collectors.h
new file mode 100644
index 00000000..01ecec97
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/sst_file_writer_collectors.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "db/dbformat.h"
+#include "db/table_properties_collector.h"
+#include "rocksdb/types.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Table Properties that are specific to tables created by SstFileWriter.
+struct ExternalSstFilePropertyNames {
+ // value of this property is a fixed uint32 number.
+ static const std::string kVersion;
+ // value of this property is a fixed uint64 number.
+ static const std::string kGlobalSeqno;
+};
+
+// PropertiesCollector used to add properties specific to tables
+// generated by SstFileWriter
+class SstFileWriterPropertiesCollector : public IntTblPropCollector {
+ public:
+ explicit SstFileWriterPropertiesCollector(int32_t version,
+ SequenceNumber global_seqno)
+ : version_(version), global_seqno_(global_seqno) {}
+
+ virtual Status InternalAdd(const Slice& /*key*/, const Slice& /*value*/,
+ uint64_t /*file_size*/) override {
+ // Intentionally left blank. Have no interest in collecting stats for
+ // individual key/value pairs.
+ return Status::OK();
+ }
+
+ virtual void BlockAdd(uint64_t /* blockRawBytes */,
+ uint64_t /* blockCompressedBytesFast */,
+ uint64_t /* blockCompressedBytesSlow */) override {
+ // Intentionally left blank. No interest in collecting stats for
+ // blocks.
+ return;
+ }
+
+ virtual Status Finish(UserCollectedProperties* properties) override {
+ // File version
+ std::string version_val;
+ PutFixed32(&version_val, static_cast<uint32_t>(version_));
+ properties->insert({ExternalSstFilePropertyNames::kVersion, version_val});
+
+ // Global Sequence number
+ std::string seqno_val;
+ PutFixed64(&seqno_val, static_cast<uint64_t>(global_seqno_));
+ properties->insert({ExternalSstFilePropertyNames::kGlobalSeqno, seqno_val});
+
+ return Status::OK();
+ }
+
+ virtual const char* Name() const override {
+ return "SstFileWriterPropertiesCollector";
+ }
+
+ virtual UserCollectedProperties GetReadableProperties() const override {
+ return {{ExternalSstFilePropertyNames::kVersion, ToString(version_)}};
+ }
+
+ private:
+ int32_t version_;
+ SequenceNumber global_seqno_;
+};
+
+class SstFileWriterPropertiesCollectorFactory
+ : public IntTblPropCollectorFactory {
+ public:
+ explicit SstFileWriterPropertiesCollectorFactory(int32_t version,
+ SequenceNumber global_seqno)
+ : version_(version), global_seqno_(global_seqno) {}
+
+ virtual IntTblPropCollector* CreateIntTblPropCollector(
+ uint32_t /*column_family_id*/) override {
+ return new SstFileWriterPropertiesCollector(version_, global_seqno_);
+ }
+
+ virtual const char* Name() const override {
+ return "SstFileWriterPropertiesCollector";
+ }
+
+ private:
+ int32_t version_;
+ SequenceNumber global_seqno_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_builder.h b/storage/rocksdb/rocksdb/table/table_builder.h
new file mode 100644
index 00000000..54125107
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_builder.h
@@ -0,0 +1,170 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+#include "db/dbformat.h"
+#include "db/table_properties_collector.h"
+#include "file/writable_file_writer.h"
+#include "options/cf_options.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table_properties.h"
+#include "trace_replay/block_cache_tracer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class Status;
+
+struct TableReaderOptions {
+ // @param skip_filters Disables loading/accessing the filter block
+ TableReaderOptions(const ImmutableCFOptions& _ioptions,
+ const SliceTransform* _prefix_extractor,
+ const EnvOptions& _env_options,
+ const InternalKeyComparator& _internal_comparator,
+ bool _skip_filters = false, bool _immortal = false,
+ int _level = -1,
+ BlockCacheTracer* const _block_cache_tracer = nullptr)
+ : TableReaderOptions(_ioptions, _prefix_extractor, _env_options,
+ _internal_comparator, _skip_filters, _immortal,
+ _level, 0 /* _largest_seqno */,
+ _block_cache_tracer) {}
+
+ // @param skip_filters Disables loading/accessing the filter block
+ TableReaderOptions(const ImmutableCFOptions& _ioptions,
+ const SliceTransform* _prefix_extractor,
+ const EnvOptions& _env_options,
+ const InternalKeyComparator& _internal_comparator,
+ bool _skip_filters, bool _immortal, int _level,
+ SequenceNumber _largest_seqno,
+ BlockCacheTracer* const _block_cache_tracer)
+ : ioptions(_ioptions),
+ prefix_extractor(_prefix_extractor),
+ env_options(_env_options),
+ internal_comparator(_internal_comparator),
+ skip_filters(_skip_filters),
+ immortal(_immortal),
+ level(_level),
+ largest_seqno(_largest_seqno),
+ block_cache_tracer(_block_cache_tracer) {}
+
+ const ImmutableCFOptions& ioptions;
+ const SliceTransform* prefix_extractor;
+ const EnvOptions& env_options;
+ const InternalKeyComparator& internal_comparator;
+ // This is only used for BlockBasedTable (reader)
+ bool skip_filters;
+ // Whether the table will be valid as long as the DB is open
+ bool immortal;
+ // what level this table/file is on, -1 for "not set, don't know"
+ int level;
+ // largest seqno in the table
+ SequenceNumber largest_seqno;
+ BlockCacheTracer* const block_cache_tracer;
+};
+
+struct TableBuilderOptions {
+ TableBuilderOptions(
+ const ImmutableCFOptions& _ioptions, const MutableCFOptions& _moptions,
+ const InternalKeyComparator& _internal_comparator,
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ _int_tbl_prop_collector_factories,
+ CompressionType _compression_type, uint64_t _sample_for_compression,
+ const CompressionOptions& _compression_opts, bool _skip_filters,
+ const std::string& _column_family_name, int _level,
+ const uint64_t _creation_time = 0, const int64_t _oldest_key_time = 0,
+ const uint64_t _target_file_size = 0,
+ const uint64_t _file_creation_time = 0)
+ : ioptions(_ioptions),
+ moptions(_moptions),
+ internal_comparator(_internal_comparator),
+ int_tbl_prop_collector_factories(_int_tbl_prop_collector_factories),
+ compression_type(_compression_type),
+ sample_for_compression(_sample_for_compression),
+ compression_opts(_compression_opts),
+ skip_filters(_skip_filters),
+ column_family_name(_column_family_name),
+ level(_level),
+ creation_time(_creation_time),
+ oldest_key_time(_oldest_key_time),
+ target_file_size(_target_file_size),
+ file_creation_time(_file_creation_time) {}
+ const ImmutableCFOptions& ioptions;
+ const MutableCFOptions& moptions;
+ const InternalKeyComparator& internal_comparator;
+ const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
+ int_tbl_prop_collector_factories;
+ CompressionType compression_type;
+ uint64_t sample_for_compression;
+ const CompressionOptions& compression_opts;
+ bool skip_filters; // only used by BlockBasedTableBuilder
+ const std::string& column_family_name;
+ int level; // what level this table/file is on, -1 for "not set, don't know"
+ const uint64_t creation_time;
+ const int64_t oldest_key_time;
+ const uint64_t target_file_size;
+ const uint64_t file_creation_time;
+};
+
+// TableBuilder provides the interface used to build a Table
+// (an immutable and sorted map from keys to values).
+//
+// Multiple threads can invoke const methods on a TableBuilder without
+// external synchronization, but if any of the threads may call a
+// non-const method, all threads accessing the same TableBuilder must use
+// external synchronization.
+class TableBuilder {
+ public:
+ // REQUIRES: Either Finish() or Abandon() has been called.
+ virtual ~TableBuilder() {}
+
+ // Add key,value to the table being constructed.
+ // REQUIRES: key is after any previously added key according to comparator.
+ // REQUIRES: Finish(), Abandon() have not been called
+ virtual void Add(const Slice& key, const Slice& value) = 0;
+
+ // Return non-ok iff some error has been detected.
+ virtual Status status() const = 0;
+
+ // Finish building the table.
+ // REQUIRES: Finish(), Abandon() have not been called
+ virtual Status Finish() = 0;
+
+ // Indicate that the contents of this builder should be abandoned.
+ // If the caller is not going to call Finish(), it must call Abandon()
+ // before destroying this builder.
+ // REQUIRES: Finish(), Abandon() have not been called
+ virtual void Abandon() = 0;
+
+ // Number of calls to Add() so far.
+ virtual uint64_t NumEntries() const = 0;
+
+ // Size of the file generated so far. If invoked after a successful
+ // Finish() call, returns the size of the final generated file.
+ virtual uint64_t FileSize() const = 0;
+
+ // If the user defined table properties collector suggest the file to
+ // be further compacted.
+ virtual bool NeedCompact() const { return false; }
+
+ // Returns table properties
+ virtual TableProperties GetTableProperties() const = 0;
+
+ // Return file checksum
+ virtual const std::string& GetFileChecksum() const = 0;
+
+ // Return file checksum function name
+ virtual const char* GetFileChecksumFuncName() const = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_properties.cc b/storage/rocksdb/rocksdb/table/table_properties.cc
new file mode 100644
index 00000000..d1dacd1a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_properties.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/table_properties.h"
+
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "table/block_based/block.h"
+#include "table/internal_iterator.h"
+#include "table/table_properties_internal.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const uint32_t TablePropertiesCollectorFactory::Context::kUnknownColumnFamily =
+ port::kMaxInt32;
+
+namespace {
+ void AppendProperty(
+ std::string& props,
+ const std::string& key,
+ const std::string& value,
+ const std::string& prop_delim,
+ const std::string& kv_delim) {
+ props.append(key);
+ props.append(kv_delim);
+ props.append(value);
+ props.append(prop_delim);
+ }
+
+ template <class TValue>
+ void AppendProperty(
+ std::string& props,
+ const std::string& key,
+ const TValue& value,
+ const std::string& prop_delim,
+ const std::string& kv_delim) {
+ AppendProperty(
+ props, key, ToString(value), prop_delim, kv_delim
+ );
+ }
+
+ // Seek to the specified meta block.
+ // Return true if it successfully seeks to that block.
+ Status SeekToMetaBlock(InternalIterator* meta_iter,
+ const std::string& block_name, bool* is_found,
+ BlockHandle* block_handle = nullptr) {
+ if (block_handle != nullptr) {
+ *block_handle = BlockHandle::NullBlockHandle();
+ }
+ *is_found = true;
+ meta_iter->Seek(block_name);
+ if (meta_iter->status().ok()) {
+ if (meta_iter->Valid() && meta_iter->key() == block_name) {
+ *is_found = true;
+ if (block_handle) {
+ Slice v = meta_iter->value();
+ return block_handle->DecodeFrom(&v);
+ }
+ } else {
+ *is_found = false;
+ return Status::OK();
+ }
+ }
+ return meta_iter->status();
+ }
+}
+
+std::string TableProperties::ToString(
+ const std::string& prop_delim,
+ const std::string& kv_delim) const {
+ std::string result;
+ result.reserve(1024);
+
+ // Basic Info
+ AppendProperty(result, "# data blocks", num_data_blocks, prop_delim,
+ kv_delim);
+ AppendProperty(result, "# entries", num_entries, prop_delim, kv_delim);
+ AppendProperty(result, "# deletions", num_deletions, prop_delim, kv_delim);
+ AppendProperty(result, "# merge operands", num_merge_operands, prop_delim,
+ kv_delim);
+ AppendProperty(result, "# range deletions", num_range_deletions, prop_delim,
+ kv_delim);
+
+ AppendProperty(result, "raw key size", raw_key_size, prop_delim, kv_delim);
+ AppendProperty(result, "raw average key size",
+ num_entries != 0 ? 1.0 * raw_key_size / num_entries : 0.0,
+ prop_delim, kv_delim);
+ AppendProperty(result, "raw value size", raw_value_size, prop_delim,
+ kv_delim);
+ AppendProperty(result, "raw average value size",
+ num_entries != 0 ? 1.0 * raw_value_size / num_entries : 0.0,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "data block size", data_size, prop_delim, kv_delim);
+ char index_block_size_str[80];
+ snprintf(index_block_size_str, sizeof(index_block_size_str),
+ "index block size (user-key? %d, delta-value? %d)",
+ static_cast<int>(index_key_is_user_key),
+ static_cast<int>(index_value_is_delta_encoded));
+ AppendProperty(result, index_block_size_str, index_size, prop_delim,
+ kv_delim);
+ if (index_partitions != 0) {
+ AppendProperty(result, "# index partitions", index_partitions, prop_delim,
+ kv_delim);
+ AppendProperty(result, "top-level index size", top_level_index_size, prop_delim,
+ kv_delim);
+ }
+ AppendProperty(result, "filter block size", filter_size, prop_delim,
+ kv_delim);
+ AppendProperty(result, "(estimated) table size",
+ data_size + index_size + filter_size, prop_delim, kv_delim);
+
+ AppendProperty(
+ result, "filter policy name",
+ filter_policy_name.empty() ? std::string("N/A") : filter_policy_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "prefix extractor name",
+ prefix_extractor_name.empty() ? std::string("N/A")
+ : prefix_extractor_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "column family ID",
+ column_family_id ==
+ ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory::
+ Context::kUnknownColumnFamily
+ ? std::string("N/A")
+ : ROCKSDB_NAMESPACE::ToString(column_family_id),
+ prop_delim, kv_delim);
+ AppendProperty(
+ result, "column family name",
+ column_family_name.empty() ? std::string("N/A") : column_family_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "comparator name",
+ comparator_name.empty() ? std::string("N/A") : comparator_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(
+ result, "merge operator name",
+ merge_operator_name.empty() ? std::string("N/A") : merge_operator_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "property collectors names",
+ property_collectors_names.empty() ? std::string("N/A")
+ : property_collectors_names,
+ prop_delim, kv_delim);
+
+ AppendProperty(
+ result, "SST file compression algo",
+ compression_name.empty() ? std::string("N/A") : compression_name,
+ prop_delim, kv_delim);
+
+ AppendProperty(
+ result, "SST file compression options",
+ compression_options.empty() ? std::string("N/A") : compression_options,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "creation time", creation_time, prop_delim, kv_delim);
+
+ AppendProperty(result, "time stamp of earliest key", oldest_key_time,
+ prop_delim, kv_delim);
+
+ AppendProperty(result, "file creation time", file_creation_time, prop_delim,
+ kv_delim);
+
+ return result;
+}
+
+void TableProperties::Add(const TableProperties& tp) {
+ data_size += tp.data_size;
+ index_size += tp.index_size;
+ index_partitions += tp.index_partitions;
+ top_level_index_size += tp.top_level_index_size;
+ index_key_is_user_key += tp.index_key_is_user_key;
+ index_value_is_delta_encoded += tp.index_value_is_delta_encoded;
+ filter_size += tp.filter_size;
+ raw_key_size += tp.raw_key_size;
+ raw_value_size += tp.raw_value_size;
+ num_data_blocks += tp.num_data_blocks;
+ num_entries += tp.num_entries;
+ num_deletions += tp.num_deletions;
+ num_merge_operands += tp.num_merge_operands;
+ num_range_deletions += tp.num_range_deletions;
+}
+
+const std::string TablePropertiesNames::kDataSize =
+ "rocksdb.data.size";
+const std::string TablePropertiesNames::kIndexSize =
+ "rocksdb.index.size";
+const std::string TablePropertiesNames::kIndexPartitions =
+ "rocksdb.index.partitions";
+const std::string TablePropertiesNames::kTopLevelIndexSize =
+ "rocksdb.top-level.index.size";
+const std::string TablePropertiesNames::kIndexKeyIsUserKey =
+ "rocksdb.index.key.is.user.key";
+const std::string TablePropertiesNames::kIndexValueIsDeltaEncoded =
+ "rocksdb.index.value.is.delta.encoded";
+const std::string TablePropertiesNames::kFilterSize =
+ "rocksdb.filter.size";
+const std::string TablePropertiesNames::kRawKeySize =
+ "rocksdb.raw.key.size";
+const std::string TablePropertiesNames::kRawValueSize =
+ "rocksdb.raw.value.size";
+const std::string TablePropertiesNames::kNumDataBlocks =
+ "rocksdb.num.data.blocks";
+const std::string TablePropertiesNames::kNumEntries =
+ "rocksdb.num.entries";
+const std::string TablePropertiesNames::kDeletedKeys = "rocksdb.deleted.keys";
+const std::string TablePropertiesNames::kMergeOperands =
+ "rocksdb.merge.operands";
+const std::string TablePropertiesNames::kNumRangeDeletions =
+ "rocksdb.num.range-deletions";
+const std::string TablePropertiesNames::kFilterPolicy =
+ "rocksdb.filter.policy";
+const std::string TablePropertiesNames::kFormatVersion =
+ "rocksdb.format.version";
+const std::string TablePropertiesNames::kFixedKeyLen =
+ "rocksdb.fixed.key.length";
+const std::string TablePropertiesNames::kColumnFamilyId =
+ "rocksdb.column.family.id";
+const std::string TablePropertiesNames::kColumnFamilyName =
+ "rocksdb.column.family.name";
+const std::string TablePropertiesNames::kComparator = "rocksdb.comparator";
+const std::string TablePropertiesNames::kMergeOperator =
+ "rocksdb.merge.operator";
+const std::string TablePropertiesNames::kPrefixExtractorName =
+ "rocksdb.prefix.extractor.name";
+const std::string TablePropertiesNames::kPropertyCollectors =
+ "rocksdb.property.collectors";
+const std::string TablePropertiesNames::kCompression = "rocksdb.compression";
+const std::string TablePropertiesNames::kCompressionOptions =
+ "rocksdb.compression_options";
+const std::string TablePropertiesNames::kCreationTime = "rocksdb.creation.time";
+const std::string TablePropertiesNames::kOldestKeyTime =
+ "rocksdb.oldest.key.time";
+const std::string TablePropertiesNames::kFileCreationTime =
+ "rocksdb.file.creation.time";
+
+extern const std::string kPropertiesBlock = "rocksdb.properties";
+// Old property block name for backward compatibility
+extern const std::string kPropertiesBlockOldName = "rocksdb.stats";
+extern const std::string kCompressionDictBlock = "rocksdb.compression_dict";
+extern const std::string kRangeDelBlock = "rocksdb.range_del";
+
+// Seek to the properties block.
+// Return true if it successfully seeks to the properties block.
+Status SeekToPropertiesBlock(InternalIterator* meta_iter, bool* is_found) {
+ Status status = SeekToMetaBlock(meta_iter, kPropertiesBlock, is_found);
+ if (!*is_found && status.ok()) {
+ status = SeekToMetaBlock(meta_iter, kPropertiesBlockOldName, is_found);
+ }
+ return status;
+}
+
+// Seek to the compression dictionary block.
+// Return true if it successfully seeks to that block.
+Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found,
+ BlockHandle* block_handle) {
+ return SeekToMetaBlock(meta_iter, kCompressionDictBlock, is_found, block_handle);
+}
+
+Status SeekToRangeDelBlock(InternalIterator* meta_iter, bool* is_found,
+ BlockHandle* block_handle = nullptr) {
+ return SeekToMetaBlock(meta_iter, kRangeDelBlock, is_found, block_handle);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_properties_internal.h b/storage/rocksdb/rocksdb/table/table_properties_internal.h
new file mode 100644
index 00000000..a7a92e3e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_properties_internal.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+#include "rocksdb/iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class BlockHandle;
+
+// Seek to the properties block.
+// If it successfully seeks to the properties block, "is_found" will be
+// set to true.
+Status SeekToPropertiesBlock(InternalIterator* meta_iter, bool* is_found);
+
+// Seek to the compression dictionary block.
+// If it successfully seeks to the properties block, "is_found" will be
+// set to true.
+Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found,
+ BlockHandle* block_handle);
+
+// TODO(andrewkr) should not put all meta block in table_properties.h/cc
+Status SeekToRangeDelBlock(InternalIterator* meta_iter, bool* is_found,
+ BlockHandle* block_handle);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_reader.h b/storage/rocksdb/rocksdb/table/table_reader.h
new file mode 100644
index 00000000..4a08e388
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_reader.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include "db/range_tombstone_fragmenter.h"
+#include "rocksdb/slice_transform.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.h"
+#include "table/multiget_context.h"
+#include "table/table_reader_caller.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Iterator;
+struct ParsedInternalKey;
+class Slice;
+class Arena;
+struct ReadOptions;
+struct TableProperties;
+class GetContext;
+class MultiGetContext;
+
+// A Table (also referred to as SST) is a sorted map from strings to strings.
+// Tables are immutable and persistent. A Table may be safely accessed from
+// multiple threads without external synchronization. Table readers are used
+// for reading various types of table formats supported by rocksdb including
+// BlockBasedTable, PlainTable and CuckooTable format.
+class TableReader {
+ public:
+ virtual ~TableReader() {}
+
+ // Returns a new iterator over the table contents.
+ // The result of NewIterator() is initially invalid (caller must
+ // call one of the Seek methods on the iterator before using it).
+ // arena: If not null, the arena needs to be used to allocate the Iterator.
+ // When destroying the iterator, the caller will not call "delete"
+ // but Iterator::~Iterator() directly. The destructor needs to destroy
+ // all the states but those allocated in arena.
+ // skip_filters: disables checking the bloom filters even if they exist. This
+ // option is effective only for block-based table format.
+ // compaction_readahead_size: its value will only be used if caller =
+ // kCompaction
+ virtual InternalIterator* NewIterator(
+ const ReadOptions&, const SliceTransform* prefix_extractor, Arena* arena,
+ bool skip_filters, TableReaderCaller caller,
+ size_t compaction_readahead_size = 0) = 0;
+
+ virtual FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
+ const ReadOptions& /*read_options*/) {
+ return nullptr;
+ }
+
+ // Given a key, return an approximate byte offset in the file where
+ // the data for that key begins (or would begin if the key were
+ // present in the file). The returned value is in terms of file
+ // bytes, and so includes effects like compression of the underlying data.
+ // E.g., the approximate offset of the last key in the table will
+ // be close to the file length.
+ virtual uint64_t ApproximateOffsetOf(const Slice& key,
+ TableReaderCaller caller) = 0;
+
+ // Given start and end keys, return the approximate data size in the file
+ // between the keys. The returned value is in terms of file bytes, and so
+ // includes effects like compression of the underlying data.
+ virtual uint64_t ApproximateSize(const Slice& start, const Slice& end,
+ TableReaderCaller caller) = 0;
+
+ // Set up the table for Compaction. Might change some parameters with
+ // posix_fadvise
+ virtual void SetupForCompaction() = 0;
+
+ virtual std::shared_ptr<const TableProperties> GetTableProperties() const = 0;
+
+ // Prepare work that can be done before the real Get()
+ virtual void Prepare(const Slice& /*target*/) {}
+
+ // Report an approximation of how much memory has been used.
+ virtual size_t ApproximateMemoryUsage() const = 0;
+
+ // Calls get_context->SaveValue() repeatedly, starting with
+ // the entry found after a call to Seek(key), until it returns false.
+ // May not make such a call if filter policy says that key is not present.
+ //
+ // get_context->MarkKeyMayExist needs to be called when it is configured to be
+ // memory only and the key is not found in the block cache.
+ //
+ // readOptions is the options for the read
+ // key is the key to search for
+ // skip_filters: disables checking the bloom filters even if they exist. This
+ // option is effective only for block-based table format.
+ virtual Status Get(const ReadOptions& readOptions, const Slice& key,
+ GetContext* get_context,
+ const SliceTransform* prefix_extractor,
+ bool skip_filters = false) = 0;
+
+ virtual void MultiGet(const ReadOptions& readOptions,
+ const MultiGetContext::Range* mget_range,
+ const SliceTransform* prefix_extractor,
+ bool skip_filters = false) {
+ for (auto iter = mget_range->begin(); iter != mget_range->end(); ++iter) {
+ *iter->s = Get(readOptions, iter->ikey, iter->get_context,
+ prefix_extractor, skip_filters);
+ }
+ }
+
+ // Prefetch data corresponding to a give range of keys
+ // Typically this functionality is required for table implementations that
+ // persists the data on a non volatile storage medium like disk/SSD
+ virtual Status Prefetch(const Slice* begin = nullptr,
+ const Slice* end = nullptr) {
+ (void) begin;
+ (void) end;
+ // Default implementation is NOOP.
+ // The child class should implement functionality when applicable
+ return Status::OK();
+ }
+
+ // convert db file to a human readable form
+ virtual Status DumpTable(WritableFile* /*out_file*/) {
+ return Status::NotSupported("DumpTable() not supported");
+ }
+
+ // check whether there is corruption in this db file
+ virtual Status VerifyChecksum(const ReadOptions& /*read_options*/,
+ TableReaderCaller /*caller*/) {
+ return Status::NotSupported("VerifyChecksum() not supported");
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_reader_bench.cc b/storage/rocksdb/rocksdb/table/table_reader_bench.cc
new file mode 100644
index 00000000..f1fd605a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_reader_bench.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "monitoring/histogram.h"
+#include "rocksdb/db.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.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/gflags_compat.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+// Make a key that i determines the first 4 characters and j determines the
+// last 4 characters.
+static std::string MakeKey(int i, int j, bool through_db) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%04d__key___%04d", i, j);
+ if (through_db) {
+ return std::string(buf);
+ }
+ // If we directly query table, which operates on internal keys
+ // instead of user keys, we need to add 8 bytes of internal
+ // information (row type etc) to user key to make an internal
+ // key.
+ InternalKey key(std::string(buf), 0, ValueType::kTypeValue);
+ return key.Encode().ToString();
+}
+
+uint64_t Now(Env* env, bool measured_by_nanosecond) {
+ return measured_by_nanosecond ? env->NowNanos() : env->NowMicros();
+}
+} // namespace
+
+// A very simple benchmark that.
+// Create a table with roughly numKey1 * numKey2 keys,
+// where there are numKey1 prefixes of the key, each has numKey2 number of
+// distinguished key, differing in the suffix part.
+// If if_query_empty_keys = false, query the existing keys numKey1 * numKey2
+// times randomly.
+// If if_query_empty_keys = true, query numKey1 * numKey2 random empty keys.
+// Print out the total time.
+// If through_db=true, a full DB will be created and queries will be against
+// it. Otherwise, operations will be directly through table level.
+//
+// If for_terator=true, instead of just query one key each time, it queries
+// a range sharing the same prefix.
+namespace {
+void TableReaderBenchmark(Options& opts, EnvOptions& env_options,
+ ReadOptions& read_options, int num_keys1,
+ int num_keys2, int num_iter, int /*prefix_len*/,
+ bool if_query_empty_keys, bool for_iterator,
+ bool through_db, bool measured_by_nanosecond) {
+ ROCKSDB_NAMESPACE::InternalKeyComparator ikc(opts.comparator);
+
+ std::string file_name =
+ test::PerThreadDBPath("rocksdb_table_reader_benchmark");
+ std::string dbname = test::PerThreadDBPath("rocksdb_table_reader_bench_db");
+ WriteOptions wo;
+ Env* env = Env::Default();
+ TableBuilder* tb = nullptr;
+ DB* db = nullptr;
+ Status s;
+ const ImmutableCFOptions ioptions(opts);
+ const ColumnFamilyOptions cfo(opts);
+ const MutableCFOptions moptions(cfo);
+ std::unique_ptr<WritableFileWriter> file_writer;
+ if (!through_db) {
+ std::unique_ptr<WritableFile> file;
+ env->NewWritableFile(file_name, &file, env_options);
+
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory> >
+ int_tbl_prop_collector_factories;
+
+ file_writer.reset(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), file_name, env_options));
+ int unknown_level = -1;
+ tb = opts.table_factory->NewTableBuilder(
+ TableBuilderOptions(
+ ioptions, moptions, ikc, &int_tbl_prop_collector_factories,
+ CompressionType::kNoCompression, 0 /* sample_for_compression */,
+ CompressionOptions(), false /* skip_filters */,
+ kDefaultColumnFamilyName, unknown_level),
+ 0 /* column_family_id */, file_writer.get());
+ } else {
+ s = DB::Open(opts, dbname, &db);
+ ASSERT_OK(s);
+ ASSERT_TRUE(db != nullptr);
+ }
+ // Populate slightly more than 1M keys
+ for (int i = 0; i < num_keys1; i++) {
+ for (int j = 0; j < num_keys2; j++) {
+ std::string key = MakeKey(i * 2, j, through_db);
+ if (!through_db) {
+ tb->Add(key, key);
+ } else {
+ db->Put(wo, key, key);
+ }
+ }
+ }
+ if (!through_db) {
+ tb->Finish();
+ file_writer->Close();
+ } else {
+ db->Flush(FlushOptions());
+ }
+
+ std::unique_ptr<TableReader> table_reader;
+ if (!through_db) {
+ std::unique_ptr<RandomAccessFile> raf;
+ s = env->NewRandomAccessFile(file_name, &raf, env_options);
+ if (!s.ok()) {
+ fprintf(stderr, "Create File Error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ uint64_t file_size;
+ env->GetFileSize(file_name, &file_size);
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(raf),
+ file_name));
+ s = opts.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, moptions.prefix_extractor.get(),
+ env_options, ikc),
+ std::move(file_reader), file_size, &table_reader);
+ if (!s.ok()) {
+ fprintf(stderr, "Open Table Error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ }
+
+ Random rnd(301);
+ std::string result;
+ HistogramImpl hist;
+
+ for (int it = 0; it < num_iter; it++) {
+ for (int i = 0; i < num_keys1; i++) {
+ for (int j = 0; j < num_keys2; j++) {
+ int r1 = rnd.Uniform(num_keys1) * 2;
+ int r2 = rnd.Uniform(num_keys2);
+ if (if_query_empty_keys) {
+ r1++;
+ r2 = num_keys2 * 2 - r2;
+ }
+
+ if (!for_iterator) {
+ // Query one existing key;
+ std::string key = MakeKey(r1, r2, through_db);
+ uint64_t start_time = Now(env, measured_by_nanosecond);
+ if (!through_db) {
+ PinnableSlice value;
+ MergeContext merge_context;
+ SequenceNumber max_covering_tombstone_seq = 0;
+ GetContext get_context(ioptions.user_comparator,
+ ioptions.merge_operator, ioptions.info_log,
+ ioptions.statistics, GetContext::kNotFound,
+ Slice(key), &value, nullptr, &merge_context,
+ true, &max_covering_tombstone_seq, env);
+ s = table_reader->Get(read_options, key, &get_context, nullptr);
+ } else {
+ s = db->Get(read_options, key, &result);
+ }
+ hist.Add(Now(env, measured_by_nanosecond) - start_time);
+ } else {
+ int r2_len;
+ if (if_query_empty_keys) {
+ r2_len = 0;
+ } else {
+ r2_len = rnd.Uniform(num_keys2) + 1;
+ if (r2_len + r2 > num_keys2) {
+ r2_len = num_keys2 - r2;
+ }
+ }
+ std::string start_key = MakeKey(r1, r2, through_db);
+ std::string end_key = MakeKey(r1, r2 + r2_len, through_db);
+ uint64_t total_time = 0;
+ uint64_t start_time = Now(env, measured_by_nanosecond);
+ Iterator* iter = nullptr;
+ InternalIterator* iiter = nullptr;
+ if (!through_db) {
+ iiter = table_reader->NewIterator(
+ read_options, /*prefix_extractor=*/nullptr, /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kUncategorized);
+ } else {
+ iter = db->NewIterator(read_options);
+ }
+ int count = 0;
+ for (through_db ? iter->Seek(start_key) : iiter->Seek(start_key);
+ through_db ? iter->Valid() : iiter->Valid();
+ through_db ? iter->Next() : iiter->Next()) {
+ if (if_query_empty_keys) {
+ break;
+ }
+ // verify key;
+ total_time += Now(env, measured_by_nanosecond) - start_time;
+ assert(Slice(MakeKey(r1, r2 + count, through_db)) ==
+ (through_db ? iter->key() : iiter->key()));
+ start_time = Now(env, measured_by_nanosecond);
+ if (++count >= r2_len) {
+ break;
+ }
+ }
+ if (count != r2_len) {
+ fprintf(
+ stderr, "Iterator cannot iterate expected number of entries. "
+ "Expected %d but got %d\n", r2_len, count);
+ assert(false);
+ }
+ delete iter;
+ total_time += Now(env, measured_by_nanosecond) - start_time;
+ hist.Add(total_time);
+ }
+ }
+ }
+ }
+
+ fprintf(
+ stderr,
+ "==================================================="
+ "====================================================\n"
+ "InMemoryTableSimpleBenchmark: %20s num_key1: %5d "
+ "num_key2: %5d %10s\n"
+ "==================================================="
+ "===================================================="
+ "\nHistogram (unit: %s): \n%s",
+ opts.table_factory->Name(), num_keys1, num_keys2,
+ for_iterator ? "iterator" : (if_query_empty_keys ? "empty" : "non_empty"),
+ measured_by_nanosecond ? "nanosecond" : "microsecond",
+ hist.ToString().c_str());
+ if (!through_db) {
+ env->DeleteFile(file_name);
+ } else {
+ delete db;
+ db = nullptr;
+ DestroyDB(dbname, opts);
+ }
+}
+} // namespace
+} // namespace ROCKSDB_NAMESPACE
+
+DEFINE_bool(query_empty, false, "query non-existing keys instead of existing "
+ "ones.");
+DEFINE_int32(num_keys1, 4096, "number of distinguish prefix of keys");
+DEFINE_int32(num_keys2, 512, "number of distinguish keys for each prefix");
+DEFINE_int32(iter, 3, "query non-existing keys instead of existing ones");
+DEFINE_int32(prefix_len, 16, "Prefix length used for iterators and indexes");
+DEFINE_bool(iterator, false, "For test iterator");
+DEFINE_bool(through_db, false, "If enable, a DB instance will be created and "
+ "the query will be against DB. Otherwise, will be directly against "
+ "a table reader.");
+DEFINE_bool(mmap_read, true, "Whether use mmap read");
+DEFINE_string(table_factory, "block_based",
+ "Table factory to use: `block_based` (default), `plain_table` or "
+ "`cuckoo_hash`.");
+DEFINE_string(time_unit, "microsecond",
+ "The time unit used for measuring performance. User can specify "
+ "`microsecond` (default) or `nanosecond`");
+
+int main(int argc, char** argv) {
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [OPTIONS]...");
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ std::shared_ptr<ROCKSDB_NAMESPACE::TableFactory> tf;
+ ROCKSDB_NAMESPACE::Options options;
+ if (FLAGS_prefix_len < 16) {
+ options.prefix_extractor.reset(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(FLAGS_prefix_len));
+ }
+ ROCKSDB_NAMESPACE::ReadOptions ro;
+ ROCKSDB_NAMESPACE::EnvOptions env_options;
+ options.create_if_missing = true;
+ options.compression = ROCKSDB_NAMESPACE::CompressionType::kNoCompression;
+
+ if (FLAGS_table_factory == "cuckoo_hash") {
+#ifndef ROCKSDB_LITE
+ options.allow_mmap_reads = FLAGS_mmap_read;
+ env_options.use_mmap_reads = FLAGS_mmap_read;
+ ROCKSDB_NAMESPACE::CuckooTableOptions table_options;
+ table_options.hash_table_ratio = 0.75;
+ tf.reset(ROCKSDB_NAMESPACE::NewCuckooTableFactory(table_options));
+#else
+ fprintf(stderr, "Plain table is not supported in lite mode\n");
+ exit(1);
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_table_factory == "plain_table") {
+#ifndef ROCKSDB_LITE
+ options.allow_mmap_reads = FLAGS_mmap_read;
+ env_options.use_mmap_reads = FLAGS_mmap_read;
+
+ ROCKSDB_NAMESPACE::PlainTableOptions plain_table_options;
+ plain_table_options.user_key_len = 16;
+ plain_table_options.bloom_bits_per_key = (FLAGS_prefix_len == 16) ? 0 : 8;
+ plain_table_options.hash_table_ratio = 0.75;
+
+ tf.reset(new ROCKSDB_NAMESPACE::PlainTableFactory(plain_table_options));
+ options.prefix_extractor.reset(
+ ROCKSDB_NAMESPACE::NewFixedPrefixTransform(FLAGS_prefix_len));
+#else
+ fprintf(stderr, "Cuckoo table is not supported in lite mode\n");
+ exit(1);
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_table_factory == "block_based") {
+ tf.reset(new ROCKSDB_NAMESPACE::BlockBasedTableFactory());
+ } else {
+ fprintf(stderr, "Invalid table type %s\n", FLAGS_table_factory.c_str());
+ }
+
+ if (tf) {
+ // if user provides invalid options, just fall back to microsecond.
+ bool measured_by_nanosecond = FLAGS_time_unit == "nanosecond";
+
+ options.table_factory = tf;
+ ROCKSDB_NAMESPACE::TableReaderBenchmark(
+ options, env_options, ro, FLAGS_num_keys1, FLAGS_num_keys2, FLAGS_iter,
+ FLAGS_prefix_len, FLAGS_query_empty, FLAGS_iterator, FLAGS_through_db,
+ measured_by_nanosecond);
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/table/table_reader_caller.h b/storage/rocksdb/rocksdb/table/table_reader_caller.h
new file mode 100644
index 00000000..7a57b5e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_reader_caller.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+namespace ROCKSDB_NAMESPACE {
+// A list of callers for a table reader. It is used to trace the caller that
+// accesses on a block. This is only used for block cache tracing and analysis.
+// A user may use kUncategorized if the caller is not interesting for analysis
+// or the table reader is called in the test environment, e.g., unit test, table
+// reader benchmark, etc.
+enum TableReaderCaller : char {
+ kUserGet = 1,
+ kUserMultiGet = 2,
+ kUserIterator = 3,
+ kUserApproximateSize = 4,
+ kUserVerifyChecksum = 5,
+ kSSTDumpTool = 6,
+ kExternalSSTIngestion = 7,
+ kRepair = 8,
+ kPrefetch = 9,
+ kCompaction = 10,
+ // A compaction job may refill the block cache with blocks in the new SST
+ // files if paranoid_file_checks is true.
+ kCompactionRefill = 11,
+ // After building a table, it may load all its blocks into the block cache if
+ // paranoid_file_checks is true.
+ kFlush = 12,
+ // sst_file_reader.
+ kSSTFileReader = 13,
+ // A list of callers that are either not interesting for analysis or are
+ // calling from a test environment, e.g., unit test, benchmark, etc.
+ kUncategorized = 14,
+ // All callers should be added before kMaxBlockCacheLookupCaller.
+ kMaxBlockCacheLookupCaller
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/table_test.cc b/storage/rocksdb/rocksdb/table/table_test.cc
new file mode 100644
index 00000000..2a24c99e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/table_test.cc
@@ -0,0 +1,4651 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "block_fetcher.h"
+#include "cache/lru_cache.h"
+#include "db/dbformat.h"
+#include "db/memtable.h"
+#include "db/write_batch_internal.h"
+#include "memtable/stl_wrappers.h"
+#include "meta_blocks.h"
+#include "monitoring/statistics.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/file_checksum.h"
+#include "rocksdb/file_system.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/statistics.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/flush_block_policy.h"
+#include "table/format.h"
+#include "table/get_context.h"
+#include "table/internal_iterator.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/scoped_arena_iterator.h"
+#include "table/sst_file_writer_collectors.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/compression.h"
+#include "util/file_checksum_helper.h"
+#include "util/random.h"
+#include "util/string_util.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 ""; }
+
+ 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 "DummyPropertiesCollector1"; }
+};
+
+class DummyPropertiesCollectorFactory2
+ : public TablePropertiesCollectorFactory {
+ public:
+ TablePropertiesCollector* CreateTablePropertiesCollector(
+ TablePropertiesCollectorFactory::Context /*context*/) override {
+ return new DummyPropertiesCollector();
+ }
+ const char* Name() const override { return "DummyPropertiesCollector2"; }
+};
+
+// 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);
+ }
+}
+
+} // 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 ImmutableCFOptions& ioptions,
+ const MutableCFOptions& moptions,
+ const BlockBasedTableOptions& table_options,
+ const InternalKeyComparator& internal_comparator,
+ std::vector<std::string>* keys, stl_wrappers::KVMap* kvmap) {
+ last_internal_key_ = &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 ImmutableCFOptions& 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_key_;
+
+ private:
+ stl_wrappers::KVMap data_;
+};
+
+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 ImmutableCFOptions& /*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) {
+ builder.Add(kv.first, kv.second);
+ }
+ // Open the block
+ data_ = builder.Finish().ToString();
+ BlockContents contents;
+ contents.data = data_;
+ block_ = new Block(std::move(contents), kDisableGlobalSequenceNumber);
+ return Status::OK();
+ }
+ InternalIterator* NewIterator(
+ const SliceTransform* /*prefix_extractor*/) const override {
+ return block_->NewDataIterator(comparator_, comparator_);
+ }
+
+ private:
+ const Comparator* comparator_;
+ std::string data_;
+ Block* block_;
+
+ BlockConstructor();
+};
+
+// 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(); }
+ bool IsOutOfBound() override { return iter_->IsOutOfBound(); }
+
+ Slice key() const override {
+ assert(Valid());
+ ParsedInternalKey parsed_key;
+ if (!ParseInternalKey(iter_->key(), &parsed_key)) {
+ status_ = Status::Corruption("malformed internal key");
+ return Slice("corrupted key");
+ }
+ 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&);
+};
+
+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 ImmutableCFOptions& 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;
+ file_writer_.reset(test::GetWritableFileWriter(new test::StringSink(),
+ "" /* don't care */));
+ std::unique_ptr<TableBuilder> builder;
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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.sample_for_compression,
+ options.compression_opts, false /* skip_filters */,
+ column_family_name, level_),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ 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_TRUE(builder->status().ok());
+ }
+ Status s = builder->Finish();
+ file_writer_->Flush();
+ EXPECT_TRUE(s.ok()) << s.ToString();
+
+ EXPECT_EQ(TEST_GetSink()->contents().size(), builder->FileSize());
+
+ // Open the table
+ uniq_id_ = cur_uniq_id_++;
+ file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource(
+ TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
+ const bool kSkipFilters = true;
+ const bool kImmortal = true;
+ return ioptions.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
+ internal_comparator, !kSkipFilters, !kImmortal,
+ level_, largest_seqno_, &block_cache_tracer_),
+ std::move(file_reader_), TEST_GetSink()->contents().size(),
+ &table_reader_);
+ }
+
+ InternalIterator* NewIterator(
+ const SliceTransform* prefix_extractor) const override {
+ ReadOptions ro;
+ InternalIterator* iter = table_reader_->NewIterator(
+ ro, 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 ImmutableCFOptions& ioptions,
+ const MutableCFOptions& moptions) {
+ file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource(
+ TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
+ return ioptions.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
+ *last_internal_key_),
+ 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 ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter(
+ file_writer_.get());
+ }
+
+ BlockCacheTracer block_cache_tracer_;
+
+ private:
+ void Reset() {
+ uniq_id_ = 0;
+ table_reader_.reset();
+ file_writer_.reset();
+ file_reader_.reset();
+ }
+
+ uint64_t uniq_id_;
+ std::unique_ptr<WritableFileWriter> file_writer_;
+ std::unique_ptr<RandomAccessFileReader> file_reader_;
+ std::unique_ptr<TableReader> table_reader_;
+ SequenceNumber largest_seqno_;
+ bool convert_to_internal_key_;
+ int level_;
+
+ TableConstructor();
+
+ static uint64_t cur_uniq_id_;
+ EnvOptions soptions;
+ Env* env_;
+};
+uint64_t TableConstructor::cur_uniq_id_ = 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_;
+ ImmutableCFOptions 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 ImmutableCFOptions& ioptions,
+ const MutableCFOptions& /*moptions*/,
+ const BlockBasedTableOptions& /*table_options*/,
+ const InternalKeyComparator& /*internal_comparator*/,
+ const stl_wrappers::KVMap& kv_map) override {
+ delete memtable_->Unref();
+ ImmutableCFOptions 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) {
+ memtable_->Add(seq, kTypeValue, kv.first, kv.second);
+ 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<SkipListFactory> 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<Iterator> 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 ImmutableCFOptions& /*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;
+ 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,
+#ifndef ROCKSDB_LITE
+ PLAIN_TABLE_SEMI_FIXED_PREFIX,
+ PLAIN_TABLE_FULL_STR_PREFIX,
+ PLAIN_TABLE_TOTAL_ORDER,
+#endif // !ROCKSDB_LITE
+ BLOCK_TEST,
+ MEMTABLE_TEST,
+ DB_TEST
+};
+
+struct TestArgs {
+ TestType type;
+ bool reverse_compare;
+ int restart_interval;
+ CompressionType compression;
+ uint32_t format_version;
+ bool use_mmap;
+};
+
+static std::vector<TestArgs> GenerateArgList() {
+ std::vector<TestArgs> test_args;
+ std::vector<TestType> test_types = {
+ BLOCK_BASED_TABLE_TEST,
+#ifndef ROCKSDB_LITE
+ PLAIN_TABLE_SEMI_FIXED_PREFIX,
+ PLAIN_TABLE_FULL_STR_PREFIX,
+ PLAIN_TABLE_TOTAL_ORDER,
+#endif // !ROCKSDB_LITE
+ BLOCK_TEST,
+ MEMTABLE_TEST, DB_TEST};
+ std::vector<bool> reverse_compare_types = {false, true};
+ std::vector<int> restart_intervals = {16, 1, 1024};
+
+ // Only add compression if it is supported
+ std::vector<std::pair<CompressionType, bool>> 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) {
+#ifndef ROCKSDB_LITE
+ 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.use_mmap = true;
+ test_args.push_back(one_arg);
+ one_arg.use_mmap = false;
+ test_args.push_back(one_arg);
+ continue;
+ }
+#endif // !ROCKSDB_LITE
+
+ for (auto restart_interval : restart_intervals) {
+ for (auto compression_type : compression_types) {
+ 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.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:
+ HarnessTest()
+ : ioptions_(options_),
+ moptions_(options_),
+ constructor_(nullptr),
+ write_buffer_(options_.db_write_buffer_size) {}
+
+ void Init(const TestArgs& args) {
+ delete constructor_;
+ constructor_ = nullptr;
+ options_ = Options();
+ options_.compression = args.compression;
+ // 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));
+
+ support_prev_ = true;
+ only_support_prefix_seek_ = false;
+ 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_ = new TableConstructor(
+ options_.comparator, true /* convert_to_internal_key_ */);
+ internal_comparator_.reset(
+ new InternalKeyComparator(options_.comparator));
+ break;
+// Plain table is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+ 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_ = 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_ = 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_ = new TableConstructor(
+ options_.comparator, true /* convert_to_internal_key_ */);
+ internal_comparator_.reset(
+ new InternalKeyComparator(options_.comparator));
+ break;
+#endif // !ROCKSDB_LITE
+ case BLOCK_TEST:
+ table_options_.block_size = 256;
+ options_.table_factory.reset(
+ new BlockBasedTableFactory(table_options_));
+ constructor_ = new BlockConstructor(options_.comparator);
+ break;
+ case MEMTABLE_TEST:
+ table_options_.block_size = 256;
+ options_.table_factory.reset(
+ new BlockBasedTableFactory(table_options_));
+ constructor_ = new MemTableConstructor(options_.comparator,
+ &write_buffer_);
+ break;
+ case DB_TEST:
+ table_options_.block_size = 256;
+ options_.table_factory.reset(
+ new BlockBasedTableFactory(table_options_));
+ constructor_ = new DBConstructor(options_.comparator);
+ break;
+ }
+ ioptions_ = ImmutableCFOptions(options_);
+ moptions_ = MutableCFOptions(options_);
+ }
+
+ ~HarnessTest() override { delete constructor_; }
+
+ void Add(const std::string& key, const std::string& value) {
+ constructor_->Add(key, value);
+ }
+
+ void Test(Random* rnd) {
+ std::vector<std::string> 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<std::string>& /*keys*/,
+ const stl_wrappers::KVMap& data) {
+ InternalIterator* iter = constructor_->NewIterator();
+ ASSERT_TRUE(!iter->Valid());
+ iter->SeekToFirst();
+ 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_TRUE(!iter->Valid());
+ if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) {
+ iter->~InternalIterator();
+ } else {
+ delete iter;
+ }
+ }
+
+ void TestBackwardScan(const std::vector<std::string>& /*keys*/,
+ const stl_wrappers::KVMap& data) {
+ InternalIterator* iter = constructor_->NewIterator();
+ ASSERT_TRUE(!iter->Valid());
+ iter->SeekToLast();
+ 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_TRUE(!iter->Valid());
+ if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) {
+ iter->~InternalIterator();
+ } else {
+ delete iter;
+ }
+ }
+
+ void TestRandomAccess(Random* rnd, const std::vector<std::string>& 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();
+ ++model_iter;
+ ASSERT_EQ(ToString(data, model_iter), ToString(iter));
+ }
+ break;
+ }
+
+ case 1: {
+ if (kVerbose) fprintf(stderr, "SeekToFirst\n");
+ iter->SeekToFirst();
+ 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_EQ(ToString(data, model_iter), ToString(iter));
+ break;
+ }
+
+ case 3: {
+ if (iter->Valid()) {
+ if (kVerbose) fprintf(stderr, "Prev\n");
+ iter->Prev();
+ 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();
+ 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<std::string>& keys) {
+ if (keys.empty()) {
+ return "foo";
+ } else {
+ const int index = rnd->Uniform(static_cast<int>(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(); }
+
+ void RandomizedHarnessTest(size_t part, size_t total) {
+ std::vector<TestArgs> args = GenerateArgList();
+ assert(part);
+ assert(part <= total);
+ for (size_t i = 0; i < args.size(); i++) {
+ if ((i % total) + 1 != part) {
+ continue;
+ }
+ Init(args[i]);
+ 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++) {
+ std::string v;
+ Add(test::RandomKey(&rnd, rnd.Skewed(4)),
+ test::RandomString(&rnd, rnd.Skewed(5), &v).ToString());
+ }
+ Test(&rnd);
+ }
+ }
+ }
+
+ private:
+ Options options_ = Options();
+ ImmutableCFOptions ioptions_;
+ MutableCFOptions moptions_;
+ BlockBasedTableOptions table_options_ = BlockBasedTableOptions();
+ Constructor* constructor_;
+ WriteBufferManager write_buffer_;
+ bool support_prev_;
+ bool only_support_prefix_seek_;
+ std::shared_ptr<InternalKeyComparator> internal_comparator_;
+};
+
+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<InternalKeyComparator> plain_internal_comparator;
+};
+
+class GeneralTableTest : public TableTest {};
+class BlockBasedTableTest
+ : public TableTest,
+ virtual public ::testing::WithParamInterface<uint32_t> {
+ 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";
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ EXPECT_OK(NewFileTraceWriter(env_, EnvOptions(), trace_file_path_,
+ &trace_writer));
+ c->block_cache_tracer_.StartTrace(env_, trace_opt, std::move(trace_writer));
+ {
+ 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<BlockCacheTraceRecord>& expected_records) {
+ c->block_cache_tracer_.EndTrace();
+
+ std::unique_ptr<TraceReader> 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_TRUE(access.get_from_user_specified_snapshot == Boolean::kFalse);
+ EXPECT_EQ(access.referenced_data_size, 0);
+ EXPECT_EQ(access.num_keys_in_block, 0);
+ EXPECT_TRUE(access.referenced_key_exist_in_block == Boolean::kFalse);
+ }
+ 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) {
+ sink_ = new test::StringSink();
+ }
+ ~FileChecksumTestHelper() {}
+
+ void CreateWriteableFile() {
+ file_writer_.reset(test::GetWritableFileWriter(sink_, "" /* don't care */));
+ }
+
+ void SetFileChecksumFunc(FileChecksumFunc* checksum_func) {
+ if (file_writer_ != nullptr) {
+ file_writer_->TEST_SetFileChecksumFunc(checksum_func);
+ }
+ }
+
+ WritableFileWriter* GetFileWriter() { return file_writer_.get(); }
+
+ Status ResetTableBuilder(std::unique_ptr<TableBuilder>&& 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;
+ test::RandomString(&rnd, 100, &v);
+ 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();
+ file_writer_->Flush();
+ EXPECT_TRUE(s.ok());
+
+ EXPECT_EQ(sink_->contents().size(), table_builder_->FileSize());
+ return s;
+ }
+
+ std::string GetFileChecksum() { return table_builder_->GetFileChecksum(); }
+
+ const char* GetFileChecksumFuncName() {
+ return table_builder_->GetFileChecksumFuncName();
+ }
+
+ Status CalculateFileChecksum(FileChecksumFunc* file_checksum_func,
+ std::string* checksum) {
+ assert(file_checksum_func != nullptr);
+ cur_uniq_id_ = checksum_uniq_id_++;
+ test::StringSink* ss_rw =
+ ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter(
+ file_writer_.get());
+ file_reader_.reset(test::GetRandomAccessFileReader(
+ new test::StringSource(ss_rw->contents())));
+ std::unique_ptr<char[]> scratch(new char[2048]);
+ Slice result;
+ uint64_t offset = 0;
+ std::string tmp_checksum;
+ bool first_read = true;
+ Status s;
+ s = file_reader_->Read(offset, 2048, &result, scratch.get(), false);
+ if (!s.ok()) {
+ return s;
+ }
+ while (result.size() != 0) {
+ if (first_read) {
+ first_read = false;
+ tmp_checksum = file_checksum_func->Value(scratch.get(), result.size());
+ } else {
+ tmp_checksum = file_checksum_func->Extend(tmp_checksum, scratch.get(),
+ result.size());
+ }
+ offset += static_cast<uint64_t>(result.size());
+ s = file_reader_->Read(offset, 2048, &result, scratch.get(), false);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ EXPECT_EQ(offset, static_cast<uint64_t>(table_builder_->FileSize()));
+ *checksum = tmp_checksum;
+ return Status::OK();
+ }
+
+ private:
+ bool convert_to_internal_key_;
+ uint64_t cur_uniq_id_;
+ std::unique_ptr<WritableFileWriter> file_writer_;
+ std::unique_ptr<RandomAccessFileReader> file_reader_;
+ std::unique_ptr<TableBuilder> table_builder_;
+ stl_wrappers::KVMap kv_map_;
+ test::StringSink* sink_;
+
+ static uint64_t checksum_uniq_id_;
+};
+
+uint64_t FileChecksumTestHelper::checksum_uniq_id_ = 1;
+
+INSTANTIATE_TEST_CASE_P(FormatDef, BlockBasedTableTest,
+ testing::Values(test::kDefaultFormatVersion));
+INSTANTIATE_TEST_CASE_P(FormatLatest, BlockBasedTableTest,
+ testing::Values(test::kLatestFormatVersion));
+
+// 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 + "." + ToString(num);
+ ASSERT_EQ(key, pos->first);
+ ASSERT_EQ(ToString(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);
+ }
+}
+
+// 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<std::string> 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));
+
+ ImmutableCFOptions ioptions(options);
+ MutableCFOptions moptions(options);
+ ioptions.statistics = options.statistics.get();
+ 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() + 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" + ToString(k), "val" + ToString(k));
+ }
+
+ std::vector<std::string> 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));
+
+ ImmutableCFOptions ioptions(options);
+ MutableCFOptions moptions(options);
+ ioptions.statistics = options.statistics.get();
+ 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+
+ {
+ Options options;
+ options.compression = CompressionType::kNoCompression;
+ BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ const ImmutableCFOptions 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 ImmutableCFOptions 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("[DummyPropertiesCollector1,DummyPropertiesCollector2]",
+ 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<std::string> keys = {"1pika", "2chu"};
+ std::vector<std::string> vals = {"p", "c"};
+
+ std::vector<RangeTombstone> 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<InternalKey, Slice> p = t.Serialize();
+ c.Add(p.first.Encode().ToString(), p.second);
+ }
+
+ std::vector<std::string> 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 ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ std::unique_ptr<InternalKeyComparator> 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<InternalIterator> 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_TRUE(ParseInternalKey(iter->key(), &parsed_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<std::string> 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 ImmutableCFOptions 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.BuiltinBloomFilter", props.filter_policy_name);
+ c.ResetTableReader();
+}
+
+//
+// BlockBasedTableTest::PrefetchTest
+//
+void AssertKeysInCache(BlockBasedTable* table_reader,
+ const std::vector<std::string>& keys_in_cache,
+ const std::vector<std::string>& 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<std::string>& keys_in_cache,
+ const std::vector<std::string>& 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 ImmutableCFOptions ioptions2(*opt);
+ const MutableCFOptions moptions(*opt);
+ ASSERT_OK(c->Reopen(ioptions2, moptions));
+
+ // prefetch
+ auto* table_reader = dynamic_cast<BlockBasedTable*>(c->GetTableReader());
+ Status s;
+ std::unique_ptr<Slice> begin, end;
+ std::unique_ptr<InternalKey> 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<InternalKeyComparator> 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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 <= 5; ++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 hash_index_allow_collision
+ table_options.index_type = BlockBasedTableOptions::kHashSearch;
+ table_options.hash_index_allow_collision = true;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ options.prefix_extractor.reset(NewFixedPrefixTransform(4));
+ break;
+ case 3:
+ // 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 4:
+ // Two-level index
+ table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ break;
+ case 5:
+ // 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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<InternalIterator> 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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<InternalIterator> 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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 ImmutableCFOptions new_ioptions(options);
+ const MutableCFOptions new_moptions(options);
+ c.Reopen(new_ioptions, new_moptions);
+ auto reader = c.GetTableReader();
+ std::unique_ptr<InternalIterator> db_iter(reader->NewIterator(
+ ReadOptions(), 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);
+ }
+}
+
+static std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+
+void AddInternalKey(TableConstructor* c, const std::string& prefix,
+ std::string value = "v", int /*suffix_len*/ = 800) {
+ static Random rnd(1023);
+ InternalKey k(prefix + RandomString(&rnd, 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<std::string> 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<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ const ImmutableCFOptions 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
+ std::unique_ptr<InternalIterator> index_iter(reader->NewIterator(
+ ReadOptions(), moptions.prefix_extractor.get(), /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kUncategorized));
+
+ // -- Find keys do not exist, but have common prefix.
+ std::vector<std::string> prefixes = {"001", "003", "005", "007", "009"};
+ std::vector<std::string> 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<std::string> 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<std::string> 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);
+ }
+ }
+ 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<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
+ Options options;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+
+ TableConstructor c(BytewiseComparator());
+ AddInternalKey(&c, "pika");
+
+ std::vector<std::string> 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<InternalIterator> 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<int> keys_per_block)
+ : keys_per_block_(keys_per_block) {}
+
+ const char* Name() const override { return "table_test"; }
+ 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<int> 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<CustomFlushBlockPolicy>(std::vector<int>{2, 1, 3, 2});
+ Options options;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.statistics = CreateDBStatistics();
+ Statistics* stats = options.statistics.get();
+ std::unique_ptr<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ const ImmutableCFOptions 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<std::string> 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);
+ std::unique_ptr<InternalIterator> iter(reader->NewIterator(
+ ReadOptions(), /*prefix_extractor=*/nullptr, /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kUncategorized));
+
+ // 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));
+ 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));
+ 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));
+ 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));
+ 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));
+ 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));
+ 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));
+ 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));
+ 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<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ const ImmutableCFOptions 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<std::string> 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);
+ std::unique_ptr<InternalIterator> iter(reader->NewIterator(
+ ReadOptions(), /*prefix_extractor=*/nullptr, /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kUncategorized));
+
+ 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));
+
+ 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<std::string> keys;
+
+ for (int i = 0; i < 100; ++i) {
+ keys.push_back(RandomString(&rnd, 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<std::string> 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 ImmutableCFOptions 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(RandomString(&rnd, 900), "val");
+ }
+
+ std::vector<std::string> ks;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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, true));
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ SetupTracingTest(&c);
+ std::vector<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ ImmutableCFOptions 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, 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<BlockCacheTraceRecord> 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 = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ 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::kBlockTraceIndexBlock;
+ record.caller = TableReaderCaller::kUserGet;
+ record.get_from_user_specified_snapshot = Boolean::kFalse;
+ record.referenced_key = encoded_key;
+ record.referenced_key_exist_in_block = Boolean::kTrue;
+ record.is_cache_hit = Boolean::kTrue;
+ expected_records.push_back(record);
+ record.block_type = TraceType::kBlockTraceFilterBlock;
+ expected_records.push_back(record);
+ record.is_cache_hit = Boolean::kFalse;
+ record.block_type = TraceType::kBlockTraceDataBlock;
+ expected_records.push_back(record);
+ // The second get should all observe cache hits.
+ record.is_cache_hit = Boolean::kTrue;
+ record.get_id = 2;
+ record.block_type = TraceType::kBlockTraceIndexBlock;
+ record.caller = TableReaderCaller::kUserGet;
+ record.get_from_user_specified_snapshot = Boolean::kFalse;
+ record.referenced_key = encoded_key;
+ expected_records.push_back(record);
+ record.block_type = TraceType::kBlockTraceFilterBlock;
+ 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ ImmutableCFOptions 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<BlockCacheTraceRecord> 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 = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ 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 = Boolean::kTrue;
+ 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ ImmutableCFOptions 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::unique_ptr<InternalIterator> iter(c.GetTableReader()->NewIterator(
+ ReadOptions(), 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<BlockCacheTraceRecord> 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 = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ 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 = Boolean::kTrue;
+ expected_records.push_back(record);
+ record.block_type = TraceType::kBlockTraceDataBlock;
+ record.is_cache_hit = Boolean::kFalse;
+ 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 = Boolean::kTrue;
+ 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+
+ TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */);
+ c.Add("key", "value");
+ const ImmutableCFOptions 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<BlockBasedTable*>(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, true, nullptr, nullptr);
+ // a hack that just to trigger BlockBasedTable::GetFilter.
+ 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+
+ TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */);
+ c.Add("key", "value");
+ const ImmutableCFOptions 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<BlockBasedTable*>(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<InternalIterator> 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<int64_t>(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
+ // <last_value> + <added_value> 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<int64_t>(table_options.block_cache->GetUsage()));
+ last_cache_bytes_read = props.GetCacheBytesRead();
+ }
+
+ // Only data block will be accessed
+ {
+ iter->SeekToFirst();
+ 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<int64_t>(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();
+ 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<int64_t>(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 ImmutableCFOptions ioptions2(options);
+ const MutableCFOptions moptions2(options);
+ 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();
+ 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");
+ ImmutableCFOptions 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();
+ ImmutableCFOptions ioptions4(options);
+ MutableCFOptions moptions4(options);
+ ASSERT_OK(c3.Reopen(ioptions4, moptions4));
+ reader = dynamic_cast<BlockBasedTable*>(c3.GetTableReader());
+ ASSERT_FALSE(reader->TEST_FilterBlockInCache());
+ PinnableSlice value;
+ GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
+ GetContext::kNotFound, user_key, &value, 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 =
+ (const BlockBasedTableOptions*)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 =
+ (const BlockBasedTableOptions*)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 = 0 -- block-based filter
+ // bloom_filter_type = 0 -- full filter
+ for (int bloom_filter_type = 0; 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 == 0));
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ std::vector<std::string> 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");
+ ImmutableCFOptions 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, 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, 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<InternalKeyComparator> 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions ioptions(opt);
+ const MutableCFOptions moptions(opt);
+ c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap);
+
+ std::unique_ptr<InternalIterator> 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 ImmutableCFOptions ioptions1(opt);
+ const MutableCFOptions moptions1(opt);
+ ASSERT_OK(c.Reopen(ioptions1, moptions1));
+ auto table_reader = dynamic_cast<BlockBasedTable*>(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 ImmutableCFOptions ioptions2(opt);
+ const MutableCFOptions moptions2(opt);
+ ASSERT_OK(c.Reopen(ioptions2, moptions2));
+ table_reader = dynamic_cast<BlockBasedTable*>(c.GetTableReader());
+ for (const std::string& key : keys) {
+ InternalKey ikey(key, kMaxSequenceNumber, kTypeValue);
+ ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode()));
+ }
+ c.ResetTableReader();
+}
+
+namespace {
+class CustomMemoryAllocator : public MemoryAllocator {
+ public:
+ const char* Name() const override { return "CustomMemoryAllocator"; }
+
+ void* Allocate(size_t size) override {
+ ++numAllocations;
+ auto ptr = new char[size + 16];
+ memcpy(ptr, "memory_allocator_", 16); // mangle first 16 bytes
+ return reinterpret_cast<void*>(ptr + 16);
+ }
+ void Deallocate(void* p) override {
+ ++numDeallocations;
+ char* ptr = reinterpret_cast<char*>(p) - 16;
+ delete[] ptr;
+ }
+
+ std::atomic<int> numAllocations;
+ std::atomic<int> numDeallocations;
+};
+} // namespace
+
+TEST_P(BlockBasedTableTest, MemoryAllocator) {
+ auto custom_memory_allocator = std::make_shared<CustomMemoryAllocator>();
+ {
+ Options opt;
+ std::unique_ptr<InternalKeyComparator> 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions ioptions(opt);
+ const MutableCFOptions moptions(opt);
+ c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap);
+
+ std::unique_ptr<InternalIterator> 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->numAllocations.load(),
+ custom_memory_allocator->numDeallocations.load());
+ // make sure that allocations actually happened through the cache allocator
+ EXPECT_GT(custom_memory_allocator->numAllocations.load(), 0);
+}
+
+// Test the file checksum of block based table
+TEST_P(BlockBasedTableTest, NoFileChecksum) {
+ Options options;
+ ImmutableCFOptions ioptions(options);
+ MutableCFOptions moptions(options);
+ BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
+ std::unique_ptr<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ SequenceNumber largest_seqno = 0;
+ int level = 0;
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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;
+
+ FileChecksumTestHelper f(true);
+ f.CreateWriteableFile();
+ std::unique_ptr<TableBuilder> builder;
+ builder.reset(ioptions.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, *comparator,
+ &int_tbl_prop_collector_factories,
+ options.compression, options.sample_for_compression,
+ options.compression_opts, false /* skip_filters */,
+ column_family_name, level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ f.GetFileWriter()));
+ f.ResetTableBuilder(std::move(builder));
+ f.AddKVtoKVMap(1000);
+ f.WriteKVAndFlushTable();
+ ASSERT_STREQ(f.GetFileChecksumFuncName(),
+ kUnknownFileChecksumFuncName.c_str());
+ ASSERT_STREQ(f.GetFileChecksum().c_str(), kUnknownFileChecksum.c_str());
+}
+
+TEST_P(BlockBasedTableTest, Crc32FileChecksum) {
+ Options options;
+ options.sst_file_checksum_func =
+ std::shared_ptr<FileChecksumFunc>(CreateFileChecksumFuncCrc32c());
+ ImmutableCFOptions ioptions(options);
+ MutableCFOptions moptions(options);
+ BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
+ std::unique_ptr<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ SequenceNumber largest_seqno = 0;
+ int level = 0;
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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;
+
+ FileChecksumTestHelper f(true);
+ f.CreateWriteableFile();
+ f.SetFileChecksumFunc(options.sst_file_checksum_func.get());
+ std::unique_ptr<TableBuilder> builder;
+ builder.reset(ioptions.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, *comparator,
+ &int_tbl_prop_collector_factories,
+ options.compression, options.sample_for_compression,
+ options.compression_opts, false /* skip_filters */,
+ column_family_name, level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ f.GetFileWriter()));
+ f.ResetTableBuilder(std::move(builder));
+ f.AddKVtoKVMap(1000);
+ f.WriteKVAndFlushTable();
+ ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c");
+ std::string checksum;
+ ASSERT_OK(
+ f.CalculateFileChecksum(options.sst_file_checksum_func.get(), &checksum));
+ ASSERT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str());
+}
+
+// Plain table is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+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);
+ test::StringSink sink;
+ std::unique_ptr<WritableFileWriter> file_writer(
+ test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */));
+ Options options;
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+ int unknown_level = -1;
+ std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder(
+ TableBuilderOptions(
+ ioptions, moptions, ikc, &int_tbl_prop_collector_factories,
+ kNoCompression, 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, unknown_level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ 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());
+ file_writer->Flush();
+
+ test::StringSink* ss =
+ ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter(file_writer.get());
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(ss->contents(), 72242, true)));
+
+ TableProperties* props = nullptr;
+ auto s = ReadTableProperties(file_reader.get(), ss->contents().size(),
+ kPlainTableMagicNumber, ioptions,
+ &props, true /* compression_type_missing */);
+ std::unique_ptr<TableProperties> props_guard(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 ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+ int unknown_level = -1;
+ FileChecksumTestHelper f(true);
+ f.CreateWriteableFile();
+
+ std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder(
+ TableBuilderOptions(
+ ioptions, moptions, ikc, &int_tbl_prop_collector_factories,
+ kNoCompression, 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, unknown_level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ f.GetFileWriter()));
+ f.ResetTableBuilder(std::move(builder));
+ f.AddKVtoKVMap(1000);
+ f.WriteKVAndFlushTable();
+ ASSERT_STREQ(f.GetFileChecksumFuncName(),
+ kUnknownFileChecksumFuncName.c_str());
+ EXPECT_EQ(f.GetFileChecksum(), kUnknownFileChecksum.c_str());
+}
+
+TEST_F(PlainTableTest, Crc32FileChecksum) {
+ 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;
+ options.sst_file_checksum_func =
+ std::shared_ptr<FileChecksumFunc>(CreateFileChecksumFuncCrc32c());
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+ int unknown_level = -1;
+ FileChecksumTestHelper f(true);
+ f.CreateWriteableFile();
+ f.SetFileChecksumFunc(options.sst_file_checksum_func.get());
+
+ std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder(
+ TableBuilderOptions(
+ ioptions, moptions, ikc, &int_tbl_prop_collector_factories,
+ kNoCompression, 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, unknown_level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ f.GetFileWriter()));
+ f.ResetTableBuilder(std::move(builder));
+ f.AddKVtoKVMap(1000);
+ f.WriteKVAndFlushTable();
+ ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c");
+ std::string checksum;
+ ASSERT_OK(
+ f.CalculateFileChecksum(options.sst_file_checksum_func.get(), &checksum));
+ EXPECT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str());
+}
+
+#endif // !ROCKSDB_LITE
+
+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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ Options options;
+ test::PlainInternalKeyComparator internal_comparator(options.comparator);
+ options.compression = kNoCompression;
+ BlockBasedTableOptions table_options;
+ table_options.block_size = 1024;
+ const ImmutableCFOptions 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ Options options;
+ test::PlainInternalKeyComparator ikc(options.comparator);
+ options.compression = comp;
+ BlockBasedTableOptions table_options;
+ table_options.block_size = 1024;
+ const ImmutableCFOptions 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, 3500));
+ ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3500));
+ ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 6500));
+ c.ResetTableReader();
+}
+
+TEST_F(GeneralTableTest, ApproximateOffsetOfCompressed) {
+ std::vector<CompressionType> 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);
+ }
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+// RandomizedHarnessTest is very slow for certain combination of arguments
+// Split into 8 pieces to reduce the time individual tests take.
+TEST_F(HarnessTest, Randomized1) {
+ // part 1 out of 8
+ const size_t part = 1;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized2) {
+ // part 2 out of 8
+ const size_t part = 2;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized3) {
+ // part 3 out of 8
+ const size_t part = 3;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized4) {
+ // part 4 out of 8
+ const size_t part = 4;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized5) {
+ // part 5 out of 8
+ const size_t part = 5;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized6) {
+ // part 6 out of 8
+ const size_t part = 6;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized7) {
+ // part 7 out of 8
+ const size_t part = 7;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+TEST_F(HarnessTest, Randomized8) {
+ // part 8 out of 8
+ const size_t part = 8;
+ const size_t total = 8;
+ RandomizedHarnessTest(part, total);
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(HarnessTest, RandomizedLongDB) {
+ Random rnd(test::RandomSeed());
+ TestArgs args = {DB_TEST, false, 16, kNoCompression, 0, false};
+ Init(args);
+ int num_entries = 100000;
+ for (int e = 0; e < num_entries; e++) {
+ std::string v;
+ Add(test::RandomKey(&rnd, rnd.Skewed(4)),
+ test::RandomString(&rnd, rnd.Skewed(5), &v).ToString());
+ }
+ 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 // ROCKSDB_LITE
+#endif // ROCKSDB_VALGRIND_RUN
+
+class MemTableTest : public testing::Test {};
+
+TEST_F(MemTableTest, Simple) {
+ InternalKeyComparator cmp(BytewiseComparator());
+ auto table_factory = std::make_shared<SkipListFactory>();
+ Options options;
+ options.memtable_factory = table_factory;
+ ImmutableCFOptions ioptions(options);
+ WriteBufferManager wb(options.db_write_buffer_size);
+ MemTable* memtable =
+ new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
+ kMaxSequenceNumber, 0 /* column_family_id */);
+ memtable->Ref();
+ WriteBatch batch;
+ WriteBatchInternal::SetSequence(&batch, 100);
+ batch.Put(std::string("k1"), std::string("v1"));
+ batch.Put(std::string("k2"), std::string("v2"));
+ batch.Put(std::string("k3"), std::string("v3"));
+ batch.Put(std::string("largekey"), std::string("vlarge"));
+ batch.DeleteRange(std::string("chi"), std::string("xigua"));
+ batch.DeleteRange(std::string("begin"), std::string("end"));
+ ColumnFamilyMemTablesDefault cf_mems_default(memtable);
+ 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<InternalIterator> iter_guard;
+ InternalIterator* iter;
+ if (i == 0) {
+ iter = memtable->NewIterator(ReadOptions(), &arena);
+ arena_iter_guard.set(iter);
+ } else {
+ iter = memtable->NewRangeTombstoneIterator(
+ ReadOptions(), kMaxSequenceNumber /* read_seq */);
+ 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();
+ }
+ }
+
+ delete memtable->Unref();
+}
+
+// Test the empty key
+TEST_F(HarnessTest, SimpleEmptyKey) {
+ auto args = GenerateArgList();
+ for (const auto& arg : args) {
+ Init(arg);
+ Random rnd(test::RandomSeed() + 1);
+ Add("", "v");
+ Test(&rnd);
+ }
+}
+
+TEST_F(HarnessTest, SimpleSingle) {
+ auto args = GenerateArgList();
+ for (const auto& arg : args) {
+ Init(arg);
+ Random rnd(test::RandomSeed() + 2);
+ Add("abc", "v");
+ Test(&rnd);
+ }
+}
+
+TEST_F(HarnessTest, SimpleMulti) {
+ auto args = GenerateArgList();
+ for (const auto& arg : args) {
+ Init(arg);
+ Random rnd(test::RandomSeed() + 3);
+ Add("abc", "v");
+ Add("abcd", "v");
+ Add("ac", "v2");
+ Test(&rnd);
+ }
+}
+
+TEST_F(HarnessTest, SimpleSpecialKey) {
+ auto args = GenerateArgList();
+ for (const auto& arg : args) {
+ Init(arg);
+ Random rnd(test::RandomSeed() + 4);
+ Add("\xff\xff", "v3");
+ Test(&rnd);
+ }
+}
+
+TEST_F(HarnessTest, FooterTests) {
+ {
+ // upconvert legacy block based
+ std::string encoded;
+ Footer footer(kLegacyBlockBasedTableMagicNumber, 0);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), 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.version(), 0U);
+ }
+ {
+ // xxhash block based
+ std::string encoded;
+ Footer footer(kBlockBasedTableMagicNumber, 1);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.set_checksum(kxxHash);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), 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(), index.offset());
+ ASSERT_EQ(decoded_footer.index_handle().size(), index.size());
+ ASSERT_EQ(decoded_footer.version(), 1U);
+ }
+ {
+ // xxhash64 block based
+ std::string encoded;
+ Footer footer(kBlockBasedTableMagicNumber, 1);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.set_checksum(kxxHash64);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), kxxHash64);
+ 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.version(), 1U);
+ }
+// Plain table is not supported in ROCKSDB_LITE
+#ifndef ROCKSDB_LITE
+ {
+ // upconvert legacy plain table
+ std::string encoded;
+ Footer footer(kLegacyPlainTableMagicNumber, 0);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), 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.version(), 0U);
+ }
+ {
+ // xxhash block based
+ std::string encoded;
+ Footer footer(kPlainTableMagicNumber, 1);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.set_checksum(kxxHash);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), 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(), index.offset());
+ ASSERT_EQ(decoded_footer.index_handle().size(), index.size());
+ ASSERT_EQ(decoded_footer.version(), 1U);
+ }
+#endif // !ROCKSDB_LITE
+ {
+ // version == 2
+ std::string encoded;
+ Footer footer(kBlockBasedTableMagicNumber, 2);
+ BlockHandle meta_index(10, 5), index(20, 15);
+ footer.set_metaindex_handle(meta_index);
+ footer.set_index_handle(index);
+ footer.EncodeTo(&encoded);
+ Footer decoded_footer;
+ Slice encoded_slice(encoded);
+ decoded_footer.DecodeFrom(&encoded_slice);
+ ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber);
+ ASSERT_EQ(decoded_footer.checksum(), 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.version(), 2U);
+ }
+}
+
+class IndexBlockRestartIntervalTest
+ : public TableTest,
+ public ::testing::WithParamInterface<std::pair<int, bool>> {
+ public:
+ static std::vector<std::pair<int, bool>> 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;
+ }
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+
+ TableConstructor c(BytewiseComparator());
+ static Random rnd(301);
+ for (int i = 0; i < kKeysInTable; i++) {
+ InternalKey k(RandomString(&rnd, kKeySize), 0, kTypeValue);
+ c.Add(k.Encode().ToString(), RandomString(&rnd, kValSize));
+ }
+
+ std::vector<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ std::unique_ptr<InternalKeyComparator> comparator(
+ new InternalKeyComparator(BytewiseComparator()));
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ c.Finish(options, ioptions, moptions, table_options, *comparator, &keys,
+ &kvmap);
+ auto reader = c.GetTableReader();
+
+ std::unique_ptr<InternalIterator> db_iter(reader->NewIterator(
+ ReadOptions(), 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 {
+ assert(IsValid(src));
+ return true;
+ }
+
+ 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<TestPrefixExtractor>();
+ 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));
+ 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);
+ db->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, "1");
+ }
+ }
+
+ // Trigger compaction.
+ 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<WritableFileWriter> file_writer(
+ test::GetWritableFileWriter(sink, "" /* don't care */));
+ Options options;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ 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<TableBuilder> builder(options.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, ikc,
+ &int_tbl_prop_collector_factories, kNoCompression,
+ 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, -1),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ 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());
+ 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<void()> GetVersionAndGlobalSeqno = [&]() {
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(ss_rw.contents(), 73342, true)));
+
+ TableProperties* props = nullptr;
+ ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(),
+ kBlockBasedTableMagicNumber, ioptions,
+ &props, true /* compression_type_missing */));
+
+ 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->properties_offsets[ExternalSstFilePropertyNames::kGlobalSeqno];
+
+ delete props;
+ };
+
+ // Helper function to update the value of the global seqno in the file
+ std::function<void(uint64_t)> 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));
+ };
+
+ // Helper function to get the contents of the table InternalIterator
+ std::unique_ptr<TableReader> table_reader;
+ std::function<InternalIterator*()> GetTableInternalIter = [&]() {
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(ss_rw.contents(), 73342, true)));
+
+ options.table_factory->NewTableReader(
+ TableReaderOptions(ioptions, moptions.prefix_extractor.get(),
+ EnvOptions(), ikc),
+ std::move(file_reader), ss_rw.contents().size(), &table_reader);
+
+ return table_reader->NewIterator(
+ ReadOptions(), 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_TRUE(ParseInternalKey(iter->key(), &pik));
+
+ 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_TRUE(ParseInternalKey(iter->key(), &pik));
+
+ 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_TRUE(ParseInternalKey(iter->key(), &pik));
+
+ 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_TRUE(ParseInternalKey(iter->key(), &pik));
+
+ 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_TRUE(ParseInternalKey(iter->key(), &pik));
+
+ 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<WritableFileWriter> file_writer(
+ test::GetWritableFileWriter(sink, "" /* don't care */));
+ Options options;
+ options.compression = kNoCompression;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+ std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, ikc,
+ &int_tbl_prop_collector_factories, kNoCompression,
+ 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, -1),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ 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());
+ file_writer->Flush();
+
+ test::RandomRWStringSink ss_rw(sink);
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(ss_rw.contents(), 73342, true)));
+
+ // Helper function to get version, global_seqno, global_seqno_offset
+ std::function<void()> VerifyBlockAlignment = [&]() {
+ TableProperties* props = nullptr;
+ ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(),
+ kBlockBasedTableMagicNumber, ioptions,
+ &props, true /* compression_type_missing */));
+
+ 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);
+ delete props;
+ };
+
+ 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<TableReader> table_reader;
+ bbto.block_align = false;
+ Options options2;
+ options2.table_factory.reset(NewBlockBasedTableFactory(bbto));
+ ImmutableCFOptions ioptions2(options2);
+ const MutableCFOptions moptions2(options2);
+
+ ASSERT_OK(ioptions.table_factory->NewTableReader(
+ TableReaderOptions(ioptions2, moptions2.prefix_extractor.get(),
+ EnvOptions(),
+ GetPlainInternalComparator(options2.comparator)),
+ std::move(file_reader), ss_rw.contents().size(), &table_reader));
+
+ std::unique_ptr<InternalIterator> db_iter(table_reader->NewIterator(
+ ReadOptions(), 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<WritableFileWriter> file_writer(
+ test::GetWritableFileWriter(sink, "" /* don't care */));
+
+ Options options;
+ options.compression = kNoCompression;
+ options.table_factory.reset(NewBlockBasedTableFactory(bbto));
+
+ const ImmutableCFOptions ioptions(options);
+ const MutableCFOptions moptions(options);
+ InternalKeyComparator ikc(options.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
+ int_tbl_prop_collector_factories;
+ std::string column_family_name;
+
+ std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder(
+ TableBuilderOptions(ioptions, moptions, ikc,
+ &int_tbl_prop_collector_factories, kNoCompression,
+ 0 /* sample_for_compression */, CompressionOptions(),
+ false /* skip_filters */, column_family_name, -1),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ 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());
+ file_writer->Flush();
+
+ test::RandomRWStringSink ss_rw(sink);
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ test::GetRandomAccessFileReader(
+ new test::StringSource(ss_rw.contents(), 73342, true)));
+
+ {
+ RandomAccessFileReader* file = file_reader.get();
+ uint64_t file_size = ss_rw.contents().size();
+
+ Footer footer;
+ ASSERT_OK(ReadFooterFromFile(file, 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),
+ kDisableGlobalSequenceNumber);
+
+ std::unique_ptr<InternalIterator> meta_iter(metaindex_block.NewDataIterator(
+ BytewiseComparator(), BytewiseComparator()));
+ bool found_properties_block = true;
+ ASSERT_OK(SeekToPropertiesBlock(meta_iter.get(), &found_properties_block));
+ ASSERT_TRUE(found_properties_block);
+
+ // -- Read properties block
+ Slice v = meta_iter->value();
+ BlockHandle properties_handle;
+ ASSERT_OK(properties_handle.DecodeFrom(&v));
+ BlockContents properties_contents;
+
+ BlockFetchHelper(properties_handle, BlockType::kProperties,
+ &properties_contents);
+ Block properties_block(std::move(properties_contents),
+ kDisableGlobalSequenceNumber);
+
+ 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));
+ ImmutableCFOptions ioptions(options);
+ MutableCFOptions moptions(options);
+ std::vector<std::string> 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<RandomAccessFileReader> table_reader{
+ test::GetRandomAccessFileReader(
+ new test::StringSource(table_sink->contents(), 0 /* unique_id */,
+ false /* allow_mmap_reads */))};
+ size_t table_size = table_sink->contents().size();
+
+ // read footer
+ Footer footer;
+ ASSERT_OK(ReadFooterFromFile(table_reader.get(),
+ 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),
+ kDisableGlobalSequenceNumber);
+
+ // verify properties block comes last
+ std::unique_ptr<InternalIterator> metaindex_iter{
+ metaindex_block.NewDataIterator(options.comparator, options.comparator)};
+ 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(kPropertiesBlock, 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, 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));
+ 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(nullptr, 0, 0, false, true);
+ buffer.TryReadFromCache(500, 10, nullptr);
+ buffer.TryReadFromCache(480, 10, nullptr);
+ buffer.TryReadFromCache(490, 10, nullptr);
+ 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(RandomString(&rnd, kKeySize - 1) + "1");
+ InternalKey k(random_key, 0, kTypeValue);
+ c.Add(k.Encode().ToString(), RandomString(&rnd, kValSize));
+ }
+
+ std::vector<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ const ImmutableCFOptions 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<InternalIterator> seek_iter;
+ seek_iter.reset(reader->NewIterator(
+ ReadOptions(), 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, 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, 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ Options options;
+ BlockBasedTableOptions table_opt(GetBlockBasedTableOptions());
+ options.table_factory.reset(NewBlockBasedTableFactory(table_opt));
+ const ImmutableCFOptions 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<InternalIterator> 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_TRUE(iter->IsOutOfBound());
+ 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_TRUE(iter->IsOutOfBound());
+}
+
+// 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<std::string> keys;
+ stl_wrappers::KVMap kvmap;
+ Options options;
+ BlockBasedTableOptions table_opt(GetBlockBasedTableOptions());
+ table_opt.flush_block_policy_factory =
+ std::make_shared<FlushBlockEveryKeyPolicyFactory>();
+ options.table_factory.reset(NewBlockBasedTableFactory(table_opt));
+ const ImmutableCFOptions 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<InternalIterator> 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->IsOutOfBound());
+ 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->IsOutOfBound());
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/table/two_level_iterator.cc b/storage/rocksdb/rocksdb/table/two_level_iterator.cc
new file mode 100644
index 00000000..a17d56df
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/two_level_iterator.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "table/two_level_iterator.h"
+#include "db/pinned_iterators_manager.h"
+#include "memory/arena.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block.h"
+#include "table/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+class TwoLevelIndexIterator : public InternalIteratorBase<IndexValue> {
+ public:
+ explicit TwoLevelIndexIterator(
+ TwoLevelIteratorState* state,
+ InternalIteratorBase<IndexValue>* first_level_iter);
+
+ ~TwoLevelIndexIterator() override {
+ first_level_iter_.DeleteIter(false /* is_arena_mode */);
+ second_level_iter_.DeleteIter(false /* is_arena_mode */);
+ delete state_;
+ }
+
+ void Seek(const Slice& target) override;
+ void SeekForPrev(const Slice& target) override;
+ void SeekToFirst() override;
+ void SeekToLast() override;
+ void Next() override;
+ void Prev() override;
+
+ bool Valid() const override { return second_level_iter_.Valid(); }
+ Slice key() const override {
+ assert(Valid());
+ return second_level_iter_.key();
+ }
+ IndexValue value() const override {
+ assert(Valid());
+ return second_level_iter_.value();
+ }
+ Status status() const override {
+ if (!first_level_iter_.status().ok()) {
+ assert(second_level_iter_.iter() == nullptr);
+ return first_level_iter_.status();
+ } else if (second_level_iter_.iter() != nullptr &&
+ !second_level_iter_.status().ok()) {
+ return second_level_iter_.status();
+ } else {
+ return status_;
+ }
+ }
+ void SetPinnedItersMgr(
+ PinnedIteratorsManager* /*pinned_iters_mgr*/) override {}
+ bool IsKeyPinned() const override { return false; }
+ bool IsValuePinned() const override { return false; }
+
+ private:
+ void SaveError(const Status& s) {
+ if (status_.ok() && !s.ok()) status_ = s;
+ }
+ void SkipEmptyDataBlocksForward();
+ void SkipEmptyDataBlocksBackward();
+ void SetSecondLevelIterator(InternalIteratorBase<IndexValue>* iter);
+ void InitDataBlock();
+
+ TwoLevelIteratorState* state_;
+ IteratorWrapperBase<IndexValue> first_level_iter_;
+ IteratorWrapperBase<IndexValue> second_level_iter_; // May be nullptr
+ Status status_;
+ // If second_level_iter is non-nullptr, then "data_block_handle_" holds the
+ // "index_value" passed to block_function_ to create the second_level_iter.
+ BlockHandle data_block_handle_;
+};
+
+TwoLevelIndexIterator::TwoLevelIndexIterator(
+ TwoLevelIteratorState* state,
+ InternalIteratorBase<IndexValue>* first_level_iter)
+ : state_(state), first_level_iter_(first_level_iter) {}
+
+void TwoLevelIndexIterator::Seek(const Slice& target) {
+ first_level_iter_.Seek(target);
+
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.Seek(target);
+ }
+ SkipEmptyDataBlocksForward();
+}
+
+void TwoLevelIndexIterator::SeekForPrev(const Slice& target) {
+ first_level_iter_.Seek(target);
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekForPrev(target);
+ }
+ if (!Valid()) {
+ if (!first_level_iter_.Valid() && first_level_iter_.status().ok()) {
+ first_level_iter_.SeekToLast();
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekForPrev(target);
+ }
+ }
+ SkipEmptyDataBlocksBackward();
+ }
+}
+
+void TwoLevelIndexIterator::SeekToFirst() {
+ first_level_iter_.SeekToFirst();
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekToFirst();
+ }
+ SkipEmptyDataBlocksForward();
+}
+
+void TwoLevelIndexIterator::SeekToLast() {
+ first_level_iter_.SeekToLast();
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekToLast();
+ }
+ SkipEmptyDataBlocksBackward();
+}
+
+void TwoLevelIndexIterator::Next() {
+ assert(Valid());
+ second_level_iter_.Next();
+ SkipEmptyDataBlocksForward();
+}
+
+void TwoLevelIndexIterator::Prev() {
+ assert(Valid());
+ second_level_iter_.Prev();
+ SkipEmptyDataBlocksBackward();
+}
+
+void TwoLevelIndexIterator::SkipEmptyDataBlocksForward() {
+ while (second_level_iter_.iter() == nullptr ||
+ (!second_level_iter_.Valid() && second_level_iter_.status().ok())) {
+ // Move to next block
+ if (!first_level_iter_.Valid()) {
+ SetSecondLevelIterator(nullptr);
+ return;
+ }
+ first_level_iter_.Next();
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekToFirst();
+ }
+ }
+}
+
+void TwoLevelIndexIterator::SkipEmptyDataBlocksBackward() {
+ while (second_level_iter_.iter() == nullptr ||
+ (!second_level_iter_.Valid() && second_level_iter_.status().ok())) {
+ // Move to next block
+ if (!first_level_iter_.Valid()) {
+ SetSecondLevelIterator(nullptr);
+ return;
+ }
+ first_level_iter_.Prev();
+ InitDataBlock();
+ if (second_level_iter_.iter() != nullptr) {
+ second_level_iter_.SeekToLast();
+ }
+ }
+}
+
+void TwoLevelIndexIterator::SetSecondLevelIterator(
+ InternalIteratorBase<IndexValue>* iter) {
+ InternalIteratorBase<IndexValue>* old_iter = second_level_iter_.Set(iter);
+ delete old_iter;
+}
+
+void TwoLevelIndexIterator::InitDataBlock() {
+ if (!first_level_iter_.Valid()) {
+ SetSecondLevelIterator(nullptr);
+ } else {
+ BlockHandle handle = first_level_iter_.value().handle;
+ if (second_level_iter_.iter() != nullptr &&
+ !second_level_iter_.status().IsIncomplete() &&
+ handle.offset() == data_block_handle_.offset()) {
+ // second_level_iter is already constructed with this iterator, so
+ // no need to change anything
+ } else {
+ InternalIteratorBase<IndexValue>* iter =
+ state_->NewSecondaryIterator(handle);
+ data_block_handle_ = handle;
+ SetSecondLevelIterator(iter);
+ }
+ }
+}
+
+} // namespace
+
+InternalIteratorBase<IndexValue>* NewTwoLevelIterator(
+ TwoLevelIteratorState* state,
+ InternalIteratorBase<IndexValue>* first_level_iter) {
+ return new TwoLevelIndexIterator(state, first_level_iter);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/table/two_level_iterator.h b/storage/rocksdb/rocksdb/table/two_level_iterator.h
new file mode 100644
index 00000000..885dff84
--- /dev/null
+++ b/storage/rocksdb/rocksdb/table/two_level_iterator.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/iterator.h"
+#include "rocksdb/env.h"
+#include "table/iterator_wrapper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct ReadOptions;
+class InternalKeyComparator;
+
+// TwoLevelIteratorState expects iterators are not created using the arena
+struct TwoLevelIteratorState {
+ TwoLevelIteratorState() {}
+
+ virtual ~TwoLevelIteratorState() {}
+ virtual InternalIteratorBase<IndexValue>* NewSecondaryIterator(
+ const BlockHandle& handle) = 0;
+};
+
+// Return a new two level iterator. A two-level iterator contains an
+// index iterator whose values point to a sequence of blocks where
+// each block is itself a sequence of key,value pairs. The returned
+// two-level iterator yields the concatenation of all key/value pairs
+// in the sequence of blocks. Takes ownership of "index_iter" and
+// will delete it when no longer needed.
+//
+// Uses a supplied function to convert an index_iter value into
+// an iterator over the contents of the corresponding block.
+// Note: this function expects first_level_iter was not created using the arena
+extern InternalIteratorBase<IndexValue>* NewTwoLevelIterator(
+ TwoLevelIteratorState* state,
+ InternalIteratorBase<IndexValue>* first_level_iter);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.cc b/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.cc
new file mode 100644
index 00000000..8bbd2692
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.cc
@@ -0,0 +1,437 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "test_util/fault_injection_test_env.h"
+#include <functional>
+#include <utility>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Assume a filename, and not a directory name like "/foo/bar/"
+std::string GetDirName(const std::string filename) {
+ size_t found = filename.find_last_of("/\\");
+ if (found == std::string::npos) {
+ return "";
+ } else {
+ return filename.substr(0, found);
+ }
+}
+
+// A basic file truncation function suitable for this test.
+Status Truncate(Env* env, const std::string& filename, uint64_t length) {
+ std::unique_ptr<SequentialFile> orig_file;
+ const EnvOptions options;
+ Status s = env->NewSequentialFile(filename, &orig_file, options);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot open file %s for truncation: %s\n",
+ filename.c_str(), s.ToString().c_str());
+ return s;
+ }
+
+ std::unique_ptr<char[]> scratch(new char[length]);
+ ROCKSDB_NAMESPACE::Slice result;
+ s = orig_file->Read(length, &result, scratch.get());
+#ifdef OS_WIN
+ orig_file.reset();
+#endif
+ if (s.ok()) {
+ std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
+ std::unique_ptr<WritableFile> tmp_file;
+ s = env->NewWritableFile(tmp_name, &tmp_file, options);
+ if (s.ok()) {
+ s = tmp_file->Append(result);
+ if (s.ok()) {
+ s = env->RenameFile(tmp_name, filename);
+ } else {
+ fprintf(stderr, "Cannot rename file %s to %s: %s\n", tmp_name.c_str(),
+ filename.c_str(), s.ToString().c_str());
+ env->DeleteFile(tmp_name);
+ }
+ }
+ }
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
+ s.ToString().c_str());
+ }
+
+ return s;
+}
+
+// Trim the tailing "/" in the end of `str`
+std::string TrimDirname(const std::string& str) {
+ size_t found = str.find_last_not_of("/");
+ if (found == std::string::npos) {
+ return str;
+ }
+ return str.substr(0, found + 1);
+}
+
+// Return pair <parent directory name, file name> of a full path.
+std::pair<std::string, std::string> GetDirAndName(const std::string& name) {
+ std::string dirname = GetDirName(name);
+ std::string fname = name.substr(dirname.size() + 1);
+ return std::make_pair(dirname, fname);
+}
+
+Status FileState::DropUnsyncedData(Env* env) const {
+ ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
+ return Truncate(env, filename_, sync_pos);
+}
+
+Status FileState::DropRandomUnsyncedData(Env* env, Random* rand) const {
+ ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
+ assert(pos_ >= sync_pos);
+ int range = static_cast<int>(pos_ - sync_pos);
+ uint64_t truncated_size =
+ static_cast<uint64_t>(sync_pos) + rand->Uniform(range);
+ return Truncate(env, filename_, truncated_size);
+}
+
+Status TestDirectory::Fsync() {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ env_->SyncDir(dirname_);
+ return dir_->Fsync();
+}
+
+TestWritableFile::TestWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>&& f,
+ FaultInjectionTestEnv* env)
+ : state_(fname),
+ target_(std::move(f)),
+ writable_file_opened_(true),
+ env_(env) {
+ assert(target_ != nullptr);
+ state_.pos_ = 0;
+}
+
+TestWritableFile::~TestWritableFile() {
+ if (writable_file_opened_) {
+ Close();
+ }
+}
+
+Status TestWritableFile::Append(const Slice& data) {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ Status s = target_->Append(data);
+ if (s.ok()) {
+ state_.pos_ += data.size();
+ env_->WritableFileAppended(state_);
+ }
+ return s;
+}
+
+Status TestWritableFile::Close() {
+ writable_file_opened_ = false;
+ Status s = target_->Close();
+ if (s.ok()) {
+ env_->WritableFileClosed(state_);
+ }
+ return s;
+}
+
+Status TestWritableFile::Flush() {
+ Status s = target_->Flush();
+ if (s.ok() && env_->IsFilesystemActive()) {
+ state_.pos_at_last_flush_ = state_.pos_;
+ }
+ return s;
+}
+
+Status TestWritableFile::Sync() {
+ if (!env_->IsFilesystemActive()) {
+ return Status::IOError("FaultInjectionTestEnv: not active");
+ }
+ // No need to actual sync.
+ state_.pos_at_last_sync_ = state_.pos_;
+ env_->WritableFileSynced(state_);
+ return Status::OK();
+}
+
+TestRandomRWFile::TestRandomRWFile(const std::string& /*fname*/,
+ std::unique_ptr<RandomRWFile>&& f,
+ FaultInjectionTestEnv* env)
+ : target_(std::move(f)), file_opened_(true), env_(env) {
+ assert(target_ != nullptr);
+}
+
+TestRandomRWFile::~TestRandomRWFile() {
+ if (file_opened_) {
+ Close();
+ }
+}
+
+Status TestRandomRWFile::Write(uint64_t offset, const Slice& data) {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ return target_->Write(offset, data);
+}
+
+Status TestRandomRWFile::Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ return target_->Read(offset, n, result, scratch);
+}
+
+Status TestRandomRWFile::Close() {
+ file_opened_ = false;
+ return target_->Close();
+}
+
+Status TestRandomRWFile::Flush() {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ return target_->Flush();
+}
+
+Status TestRandomRWFile::Sync() {
+ if (!env_->IsFilesystemActive()) {
+ return env_->GetError();
+ }
+ return target_->Sync();
+}
+
+Status FaultInjectionTestEnv::NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) {
+ std::unique_ptr<Directory> r;
+ Status s = target()->NewDirectory(name, &r);
+ assert(s.ok());
+ if (!s.ok()) {
+ return s;
+ }
+ result->reset(new TestDirectory(this, TrimDirname(name), r.release()));
+ return Status::OK();
+}
+
+Status FaultInjectionTestEnv::NewWritableFile(
+ const std::string& fname, std::unique_ptr<WritableFile>* result,
+ const EnvOptions& soptions) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ // Not allow overwriting files
+ Status s = target()->FileExists(fname);
+ if (s.ok()) {
+ return Status::Corruption("File already exists.");
+ } else if (!s.IsNotFound()) {
+ assert(s.IsIOError());
+ return s;
+ }
+ s = target()->NewWritableFile(fname, result, soptions);
+ if (s.ok()) {
+ result->reset(new TestWritableFile(fname, std::move(*result), this));
+ // WritableFileWriter* file is opened
+ // again then it will be truncated - so forget our saved state.
+ UntrackFile(fname);
+ MutexLock l(&mutex_);
+ open_files_.insert(fname);
+ auto dir_and_name = GetDirAndName(fname);
+ auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
+ list.insert(dir_and_name.second);
+ }
+ return s;
+}
+
+Status FaultInjectionTestEnv::ReopenWritableFile(
+ const std::string& fname, std::unique_ptr<WritableFile>* result,
+ const EnvOptions& soptions) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ Status s = target()->ReopenWritableFile(fname, result, soptions);
+ if (s.ok()) {
+ result->reset(new TestWritableFile(fname, std::move(*result), this));
+ // WritableFileWriter* file is opened
+ // again then it will be truncated - so forget our saved state.
+ UntrackFile(fname);
+ MutexLock l(&mutex_);
+ open_files_.insert(fname);
+ auto dir_and_name = GetDirAndName(fname);
+ auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
+ list.insert(dir_and_name.second);
+ }
+ return s;
+}
+
+Status FaultInjectionTestEnv::NewRandomRWFile(
+ const std::string& fname, std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& soptions) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ Status s = target()->NewRandomRWFile(fname, result, soptions);
+ if (s.ok()) {
+ result->reset(new TestRandomRWFile(fname, std::move(*result), this));
+ // WritableFileWriter* file is opened
+ // again then it will be truncated - so forget our saved state.
+ UntrackFile(fname);
+ MutexLock l(&mutex_);
+ open_files_.insert(fname);
+ auto dir_and_name = GetDirAndName(fname);
+ auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
+ list.insert(dir_and_name.second);
+ }
+ return s;
+}
+
+Status FaultInjectionTestEnv::NewRandomAccessFile(
+ const std::string& fname, std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& soptions) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ return target()->NewRandomAccessFile(fname, result, soptions);
+}
+
+Status FaultInjectionTestEnv::DeleteFile(const std::string& f) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ Status s = EnvWrapper::DeleteFile(f);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
+ s.ToString().c_str());
+ }
+ if (s.ok()) {
+ UntrackFile(f);
+ }
+ return s;
+}
+
+Status FaultInjectionTestEnv::RenameFile(const std::string& s,
+ const std::string& t) {
+ if (!IsFilesystemActive()) {
+ return GetError();
+ }
+ Status ret = EnvWrapper::RenameFile(s, t);
+
+ if (ret.ok()) {
+ MutexLock l(&mutex_);
+ if (db_file_state_.find(s) != db_file_state_.end()) {
+ db_file_state_[t] = db_file_state_[s];
+ db_file_state_.erase(s);
+ }
+
+ auto sdn = GetDirAndName(s);
+ auto tdn = GetDirAndName(t);
+ if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
+ auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
+ assert(tlist.find(tdn.second) == tlist.end());
+ tlist.insert(tdn.second);
+ }
+ }
+
+ return ret;
+}
+
+void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
+ MutexLock l(&mutex_);
+ if (open_files_.find(state.filename_) != open_files_.end()) {
+ db_file_state_[state.filename_] = state;
+ open_files_.erase(state.filename_);
+ }
+}
+
+void FaultInjectionTestEnv::WritableFileSynced(const FileState& state) {
+ MutexLock l(&mutex_);
+ if (open_files_.find(state.filename_) != open_files_.end()) {
+ if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
+ db_file_state_.insert(std::make_pair(state.filename_, state));
+ } else {
+ db_file_state_[state.filename_] = state;
+ }
+ }
+}
+
+void FaultInjectionTestEnv::WritableFileAppended(const FileState& state) {
+ MutexLock l(&mutex_);
+ if (open_files_.find(state.filename_) != open_files_.end()) {
+ if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
+ db_file_state_.insert(std::make_pair(state.filename_, state));
+ } else {
+ db_file_state_[state.filename_] = state;
+ }
+ }
+}
+
+// For every file that is not fully synced, make a call to `func` with
+// FileState of the file as the parameter.
+Status FaultInjectionTestEnv::DropFileData(
+ std::function<Status(Env*, FileState)> func) {
+ Status s;
+ MutexLock l(&mutex_);
+ for (std::map<std::string, FileState>::const_iterator it =
+ db_file_state_.begin();
+ s.ok() && it != db_file_state_.end(); ++it) {
+ const FileState& state = it->second;
+ if (!state.IsFullySynced()) {
+ s = func(target(), state);
+ }
+ }
+ return s;
+}
+
+Status FaultInjectionTestEnv::DropUnsyncedFileData() {
+ return DropFileData([&](Env* env, const FileState& state) {
+ return state.DropUnsyncedData(env);
+ });
+}
+
+Status FaultInjectionTestEnv::DropRandomUnsyncedFileData(Random* rnd) {
+ return DropFileData([&](Env* env, const FileState& state) {
+ return state.DropRandomUnsyncedData(env, rnd);
+ });
+}
+
+Status FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
+ // Because DeleteFile access this container make a copy to avoid deadlock
+ std::map<std::string, std::set<std::string>> map_copy;
+ {
+ MutexLock l(&mutex_);
+ map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
+ dir_to_new_files_since_last_sync_.end());
+ }
+
+ for (auto& pair : map_copy) {
+ for (std::string name : pair.second) {
+ Status s = DeleteFile(pair.first + "/" + name);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+ return Status::OK();
+}
+void FaultInjectionTestEnv::ResetState() {
+ MutexLock l(&mutex_);
+ db_file_state_.clear();
+ dir_to_new_files_since_last_sync_.clear();
+ SetFilesystemActiveNoLock(true);
+}
+
+void FaultInjectionTestEnv::UntrackFile(const std::string& f) {
+ MutexLock l(&mutex_);
+ auto dir_and_name = GetDirAndName(f);
+ dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
+ dir_and_name.second);
+ db_file_state_.erase(f);
+ open_files_.erase(f);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.h b/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.h
new file mode 100644
index 00000000..9cc33a8d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/fault_injection_test_env.h
@@ -0,0 +1,225 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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".
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "db/version_set.h"
+#include "env/mock_env.h"
+#include "file/filename.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TestWritableFile;
+class FaultInjectionTestEnv;
+
+struct FileState {
+ std::string filename_;
+ ssize_t pos_;
+ ssize_t pos_at_last_sync_;
+ ssize_t pos_at_last_flush_;
+
+ explicit FileState(const std::string& filename)
+ : filename_(filename),
+ pos_(-1),
+ pos_at_last_sync_(-1),
+ pos_at_last_flush_(-1) {}
+
+ FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
+
+ bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
+
+ Status DropUnsyncedData(Env* env) const;
+
+ Status DropRandomUnsyncedData(Env* env, Random* rand) const;
+};
+
+// A wrapper around WritableFileWriter* file
+// is written to or sync'ed.
+class TestWritableFile : public WritableFile {
+ public:
+ explicit TestWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>&& f,
+ FaultInjectionTestEnv* env);
+ virtual ~TestWritableFile();
+ virtual Status Append(const Slice& data) override;
+ virtual Status Truncate(uint64_t size) override {
+ return target_->Truncate(size);
+ }
+ virtual Status Close() override;
+ virtual Status Flush() override;
+ virtual Status Sync() override;
+ virtual bool IsSyncThreadSafe() const override { return true; }
+ virtual Status PositionedAppend(const Slice& data,
+ uint64_t offset) override {
+ return target_->PositionedAppend(data, offset);
+ }
+ virtual bool use_direct_io() const override {
+ return target_->use_direct_io();
+ };
+
+ private:
+ FileState state_;
+ std::unique_ptr<WritableFile> target_;
+ bool writable_file_opened_;
+ FaultInjectionTestEnv* env_;
+};
+
+// A wrapper around WritableFileWriter* file
+// is written to or sync'ed.
+class TestRandomRWFile : public RandomRWFile {
+ public:
+ explicit TestRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>&& f,
+ FaultInjectionTestEnv* env);
+ virtual ~TestRandomRWFile();
+ Status Write(uint64_t offset, const Slice& data) override;
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override;
+ Status Close() override;
+ Status Flush() override;
+ Status Sync() override;
+ size_t GetRequiredBufferAlignment() const override {
+ return target_->GetRequiredBufferAlignment();
+ }
+ bool use_direct_io() const override { return target_->use_direct_io(); };
+
+ private:
+ std::unique_ptr<RandomRWFile> target_;
+ bool file_opened_;
+ FaultInjectionTestEnv* env_;
+};
+
+class TestDirectory : public Directory {
+ public:
+ explicit TestDirectory(FaultInjectionTestEnv* env, std::string dirname,
+ Directory* dir)
+ : env_(env), dirname_(dirname), dir_(dir) {}
+ ~TestDirectory() {}
+
+ virtual Status Fsync() override;
+
+ private:
+ FaultInjectionTestEnv* env_;
+ std::string dirname_;
+ std::unique_ptr<Directory> dir_;
+};
+
+class FaultInjectionTestEnv : public EnvWrapper {
+ public:
+ explicit FaultInjectionTestEnv(Env* base)
+ : EnvWrapper(base), filesystem_active_(true) {}
+ virtual ~FaultInjectionTestEnv() {}
+
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override;
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& soptions) override;
+
+ Status ReopenWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& soptions) override;
+
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& soptions) override;
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& soptions) override;
+
+ virtual Status DeleteFile(const std::string& f) override;
+
+ virtual Status RenameFile(const std::string& s,
+ const std::string& t) override;
+
+// Undef to eliminate clash on Windows
+#undef GetFreeSpace
+ virtual Status GetFreeSpace(const std::string& path,
+ uint64_t* disk_free) override {
+ if (!IsFilesystemActive() && error_ == Status::NoSpace()) {
+ *disk_free = 0;
+ return Status::OK();
+ } else {
+ return target()->GetFreeSpace(path, disk_free);
+ }
+ }
+
+ void WritableFileClosed(const FileState& state);
+
+ void WritableFileSynced(const FileState& state);
+
+ void WritableFileAppended(const FileState& state);
+
+ // For every file that is not fully synced, make a call to `func` with
+ // FileState of the file as the parameter.
+ Status DropFileData(std::function<Status(Env*, FileState)> func);
+
+ Status DropUnsyncedFileData();
+
+ Status DropRandomUnsyncedFileData(Random* rnd);
+
+ Status DeleteFilesCreatedAfterLastDirSync();
+
+ void ResetState();
+
+ void UntrackFile(const std::string& f);
+
+ void SyncDir(const std::string& dirname) {
+ MutexLock l(&mutex_);
+ dir_to_new_files_since_last_sync_.erase(dirname);
+ }
+
+ // Setting the filesystem to inactive is the test equivalent to simulating a
+ // system reset. Setting to inactive will freeze our saved filesystem state so
+ // that it will stop being recorded. It can then be reset back to the state at
+ // the time of the reset.
+ bool IsFilesystemActive() {
+ MutexLock l(&mutex_);
+ return filesystem_active_;
+ }
+ void SetFilesystemActiveNoLock(bool active,
+ Status error = Status::Corruption("Not active")) {
+ filesystem_active_ = active;
+ if (!active) {
+ error_ = error;
+ }
+ }
+ void SetFilesystemActive(bool active,
+ Status error = Status::Corruption("Not active")) {
+ MutexLock l(&mutex_);
+ SetFilesystemActiveNoLock(active, error);
+ }
+ void AssertNoOpenFile() { assert(open_files_.empty()); }
+ Status GetError() { return error_; }
+
+ private:
+ port::Mutex mutex_;
+ std::map<std::string, FileState> db_file_state_;
+ std::set<std::string> open_files_;
+ std::unordered_map<std::string, std::set<std::string>>
+ dir_to_new_files_since_last_sync_;
+ bool filesystem_active_; // Record flushes, syncs, writes
+ Status error_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/mock_time_env.h b/storage/rocksdb/rocksdb/test_util/mock_time_env.h
new file mode 100644
index 00000000..32b40f79
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/mock_time_env.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MockTimeEnv : public EnvWrapper {
+ public:
+ explicit MockTimeEnv(Env* base) : EnvWrapper(base) {}
+
+ virtual Status GetCurrentTime(int64_t* time) override {
+ assert(time != nullptr);
+ assert(current_time_ <=
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
+ *time = static_cast<int64_t>(current_time_);
+ return Status::OK();
+ }
+
+ virtual uint64_t NowMicros() override {
+ assert(current_time_ <= std::numeric_limits<uint64_t>::max() / 1000000);
+ return current_time_ * 1000000;
+ }
+
+ virtual uint64_t NowNanos() override {
+ assert(current_time_ <= std::numeric_limits<uint64_t>::max() / 1000000000);
+ return current_time_ * 1000000000;
+ }
+
+ uint64_t RealNowMicros() { return target()->NowMicros(); }
+
+ void set_current_time(uint64_t time) {
+ assert(time >= current_time_);
+ current_time_ = time;
+ }
+
+ private:
+ std::atomic<uint64_t> current_time_{0};
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/sync_point.cc b/storage/rocksdb/rocksdb/test_util/sync_point.cc
new file mode 100644
index 00000000..d969ae3c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/sync_point.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sync_point.h"
+#include "test_util/sync_point_impl.h"
+
+int rocksdb_kill_odds = 0;
+std::vector<std::string> rocksdb_kill_prefix_blacklist;
+
+#ifndef NDEBUG
+namespace ROCKSDB_NAMESPACE {
+
+SyncPoint* SyncPoint::GetInstance() {
+ static SyncPoint sync_point;
+ return &sync_point;
+}
+
+SyncPoint::SyncPoint() : impl_(new Data) {}
+
+SyncPoint:: ~SyncPoint() {
+ delete impl_;
+}
+
+void SyncPoint::LoadDependency(const std::vector<SyncPointPair>& dependencies) {
+ impl_->LoadDependency(dependencies);
+}
+
+void SyncPoint::LoadDependencyAndMarkers(
+ const std::vector<SyncPointPair>& dependencies,
+ const std::vector<SyncPointPair>& markers) {
+ impl_->LoadDependencyAndMarkers(dependencies, markers);
+}
+
+void SyncPoint::SetCallBack(const std::string& point,
+ const std::function<void(void*)>& callback) {
+ impl_->SetCallBack(point, callback);
+}
+
+void SyncPoint::ClearCallBack(const std::string& point) {
+ impl_->ClearCallBack(point);
+}
+
+void SyncPoint::ClearAllCallBacks() {
+ impl_->ClearAllCallBacks();
+}
+
+void SyncPoint::EnableProcessing() {
+ impl_->EnableProcessing();
+}
+
+void SyncPoint::DisableProcessing() {
+ impl_->DisableProcessing();
+}
+
+void SyncPoint::ClearTrace() {
+ impl_->ClearTrace();
+}
+
+void SyncPoint::Process(const std::string& point, void* cb_arg) {
+ impl_->Process(point, cb_arg);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // NDEBUG
diff --git a/storage/rocksdb/rocksdb/test_util/sync_point.h b/storage/rocksdb/rocksdb/test_util/sync_point.h
new file mode 100644
index 00000000..46bfd50d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/sync_point.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+// This is only set from db_stress.cc and for testing only.
+// If non-zero, kill at various points in source code with probability 1/this
+extern int rocksdb_kill_odds;
+// If kill point has a prefix on this list, will skip killing.
+extern std::vector<std::string> rocksdb_kill_prefix_blacklist;
+
+#ifdef NDEBUG
+// empty in release build
+#define TEST_KILL_RANDOM(kill_point, rocksdb_kill_odds)
+#else
+
+namespace ROCKSDB_NAMESPACE {
+// Kill the process with probability 1/odds for testing.
+extern void TestKillRandom(std::string kill_point, int odds,
+ const std::string& srcfile, int srcline);
+
+// To avoid crashing always at some frequently executed codepaths (during
+// kill random test), use this factor to reduce odds
+#define REDUCE_ODDS 2
+#define REDUCE_ODDS2 4
+
+#define TEST_KILL_RANDOM(kill_point, rocksdb_kill_odds) \
+ { \
+ if (rocksdb_kill_odds > 0) { \
+ TestKillRandom(kill_point, rocksdb_kill_odds, __FILE__, __LINE__); \
+ } \
+ }
+} // namespace ROCKSDB_NAMESPACE
+#endif
+
+#ifdef NDEBUG
+#define TEST_SYNC_POINT(x)
+#define TEST_IDX_SYNC_POINT(x, index)
+#define TEST_SYNC_POINT_CALLBACK(x, y)
+#define INIT_SYNC_POINT_SINGLETONS()
+#else
+
+namespace ROCKSDB_NAMESPACE {
+
+// This class provides facility to reproduce race conditions deterministically
+// in unit tests.
+// Developer could specify sync points in the codebase via TEST_SYNC_POINT.
+// Each sync point represents a position in the execution stream of a thread.
+// In the unit test, 'Happens After' relationship among sync points could be
+// setup via SyncPoint::LoadDependency, to reproduce a desired interleave of
+// threads execution.
+// Refer to (DBTest,TransactionLogIteratorRace), for an example use case.
+
+class SyncPoint {
+ public:
+ static SyncPoint* GetInstance();
+
+ SyncPoint(const SyncPoint&) = delete;
+ SyncPoint& operator=(const SyncPoint&) = delete;
+ ~SyncPoint();
+
+ struct SyncPointPair {
+ std::string predecessor;
+ std::string successor;
+ };
+
+ // call once at the beginning of a test to setup the dependency between
+ // sync points
+ void LoadDependency(const std::vector<SyncPointPair>& dependencies);
+
+ // call once at the beginning of a test to setup the dependency between
+ // sync points and setup markers indicating the successor is only enabled
+ // when it is processed on the same thread as the predecessor.
+ // When adding a marker, it implicitly adds a dependency for the marker pair.
+ void LoadDependencyAndMarkers(const std::vector<SyncPointPair>& dependencies,
+ const std::vector<SyncPointPair>& markers);
+
+ // The argument to the callback is passed through from
+ // TEST_SYNC_POINT_CALLBACK(); nullptr if TEST_SYNC_POINT or
+ // TEST_IDX_SYNC_POINT was used.
+ void SetCallBack(const std::string& point,
+ const std::function<void(void*)>& callback);
+
+ // Clear callback function by point
+ void ClearCallBack(const std::string& point);
+
+ // Clear all call back functions.
+ void ClearAllCallBacks();
+
+ // enable sync point processing (disabled on startup)
+ void EnableProcessing();
+
+ // disable sync point processing
+ void DisableProcessing();
+
+ // remove the execution trace of all sync points
+ void ClearTrace();
+
+ // triggered by TEST_SYNC_POINT, blocking execution until all predecessors
+ // are executed.
+ // And/or call registered callback function, with argument `cb_arg`
+ void Process(const std::string& point, void* cb_arg = nullptr);
+
+ // TODO: it might be useful to provide a function that blocks until all
+ // sync points are cleared.
+
+ // We want this to be public so we can
+ // subclass the implementation
+ struct Data;
+
+ private:
+ // Singleton
+ SyncPoint();
+ Data* impl_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+// Use TEST_SYNC_POINT to specify sync points inside code base.
+// Sync points can have happens-after depedency on other sync points,
+// configured at runtime via SyncPoint::LoadDependency. This could be
+// utilized to re-produce race conditions between threads.
+// See TransactionLogIteratorRace in db_test.cc for an example use case.
+// TEST_SYNC_POINT is no op in release build.
+#define TEST_SYNC_POINT(x) \
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->Process(x)
+#define TEST_IDX_SYNC_POINT(x, index) \
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->Process(x + \
+ std::to_string(index))
+#define TEST_SYNC_POINT_CALLBACK(x, y) \
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->Process(x, y)
+#define INIT_SYNC_POINT_SINGLETONS() \
+ (void)ROCKSDB_NAMESPACE::SyncPoint::GetInstance();
+#endif // NDEBUG
diff --git a/storage/rocksdb/rocksdb/test_util/sync_point_impl.cc b/storage/rocksdb/rocksdb/test_util/sync_point_impl.cc
new file mode 100644
index 00000000..7b939c5f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/sync_point_impl.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sync_point_impl.h"
+
+#ifndef NDEBUG
+namespace ROCKSDB_NAMESPACE {
+
+void TestKillRandom(std::string kill_point, int odds,
+ const std::string& srcfile, int srcline) {
+ for (auto& p : rocksdb_kill_prefix_blacklist) {
+ if (kill_point.substr(0, p.length()) == p) {
+ return;
+ }
+ }
+
+ assert(odds > 0);
+ if (odds % 7 == 0) {
+ // class Random uses multiplier 16807, which is 7^5. If odds are
+ // multiplier of 7, there might be limited values generated.
+ odds++;
+ }
+ auto* r = Random::GetTLSInstance();
+ bool crash = r->OneIn(odds);
+ if (crash) {
+ port::Crash(srcfile, srcline);
+ }
+}
+
+
+void SyncPoint::Data::LoadDependency(const std::vector<SyncPointPair>& dependencies) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ successors_.clear();
+ predecessors_.clear();
+ cleared_points_.clear();
+ for (const auto& dependency : dependencies) {
+ successors_[dependency.predecessor].push_back(dependency.successor);
+ predecessors_[dependency.successor].push_back(dependency.predecessor);
+ }
+ cv_.notify_all();
+}
+
+void SyncPoint::Data::LoadDependencyAndMarkers(
+ const std::vector<SyncPointPair>& dependencies,
+ const std::vector<SyncPointPair>& markers) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ successors_.clear();
+ predecessors_.clear();
+ cleared_points_.clear();
+ markers_.clear();
+ marked_thread_id_.clear();
+ for (const auto& dependency : dependencies) {
+ successors_[dependency.predecessor].push_back(dependency.successor);
+ predecessors_[dependency.successor].push_back(dependency.predecessor);
+ }
+ for (const auto& marker : markers) {
+ successors_[marker.predecessor].push_back(marker.successor);
+ predecessors_[marker.successor].push_back(marker.predecessor);
+ markers_[marker.predecessor].push_back(marker.successor);
+ }
+ cv_.notify_all();
+}
+
+bool SyncPoint::Data::PredecessorsAllCleared(const std::string& point) {
+ for (const auto& pred : predecessors_[point]) {
+ if (cleared_points_.count(pred) == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SyncPoint::Data::ClearCallBack(const std::string& point) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (num_callbacks_running_ > 0) {
+ cv_.wait(lock);
+ }
+ callbacks_.erase(point);
+}
+
+void SyncPoint::Data::ClearAllCallBacks() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (num_callbacks_running_ > 0) {
+ cv_.wait(lock);
+ }
+ callbacks_.clear();
+}
+
+void SyncPoint::Data::Process(const std::string& point, void* cb_arg) {
+ if (!enabled_) {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(mutex_);
+ auto thread_id = std::this_thread::get_id();
+
+ auto marker_iter = markers_.find(point);
+ if (marker_iter != markers_.end()) {
+ for (auto& marked_point : marker_iter->second) {
+ marked_thread_id_.emplace(marked_point, thread_id);
+ }
+ }
+
+ if (DisabledByMarker(point, thread_id)) {
+ return;
+ }
+
+ while (!PredecessorsAllCleared(point)) {
+ cv_.wait(lock);
+ if (DisabledByMarker(point, thread_id)) {
+ return;
+ }
+ }
+
+ auto callback_pair = callbacks_.find(point);
+ if (callback_pair != callbacks_.end()) {
+ num_callbacks_running_++;
+ mutex_.unlock();
+ callback_pair->second(cb_arg);
+ mutex_.lock();
+ num_callbacks_running_--;
+ }
+ cleared_points_.insert(point);
+ cv_.notify_all();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/test_util/sync_point_impl.h b/storage/rocksdb/rocksdb/test_util/sync_point_impl.h
new file mode 100644
index 00000000..b246c019
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/sync_point_impl.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sync_point.h"
+
+#include <assert.h>
+#include <atomic>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "port/port.h"
+#include "util/random.h"
+
+#pragma once
+
+#ifndef NDEBUG
+namespace ROCKSDB_NAMESPACE {
+struct SyncPoint::Data {
+ Data() : enabled_(false) {}
+ // Enable proper deletion by subclasses
+ virtual ~Data() {}
+ // successor/predecessor map loaded from LoadDependency
+ std::unordered_map<std::string, std::vector<std::string>> successors_;
+ std::unordered_map<std::string, std::vector<std::string>> predecessors_;
+ std::unordered_map<std::string, std::function<void(void*)> > callbacks_;
+ std::unordered_map<std::string, std::vector<std::string> > markers_;
+ std::unordered_map<std::string, std::thread::id> marked_thread_id_;
+
+ std::mutex mutex_;
+ std::condition_variable cv_;
+ // sync points that have been passed through
+ std::unordered_set<std::string> cleared_points_;
+ std::atomic<bool> enabled_;
+ int num_callbacks_running_ = 0;
+
+ void LoadDependency(const std::vector<SyncPointPair>& dependencies);
+ void LoadDependencyAndMarkers(const std::vector<SyncPointPair>& dependencies,
+ const std::vector<SyncPointPair>& markers);
+ bool PredecessorsAllCleared(const std::string& point);
+ void SetCallBack(const std::string& point,
+ const std::function<void(void*)>& callback) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ callbacks_[point] = callback;
+}
+
+ void ClearCallBack(const std::string& point);
+ void ClearAllCallBacks();
+ void EnableProcessing() {
+ enabled_ = true;
+ }
+ void DisableProcessing() {
+ enabled_ = false;
+ }
+ void ClearTrace() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ cleared_points_.clear();
+ }
+ bool DisabledByMarker(const std::string& point,
+ std::thread::id thread_id) {
+ auto marked_point_iter = marked_thread_id_.find(point);
+ return marked_point_iter != marked_thread_id_.end() &&
+ thread_id != marked_point_iter->second;
+ }
+ void Process(const std::string& point, void* cb_arg);
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // NDEBUG
diff --git a/storage/rocksdb/rocksdb/test_util/testharness.cc b/storage/rocksdb/rocksdb/test_util/testharness.cc
new file mode 100644
index 00000000..d38d4308
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/testharness.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "test_util/testharness.h"
+#include <string>
+#include <thread>
+
+namespace ROCKSDB_NAMESPACE {
+namespace test {
+
+::testing::AssertionResult AssertStatus(const char* s_expr, const Status& s) {
+ if (s.ok()) {
+ return ::testing::AssertionSuccess();
+ } else {
+ return ::testing::AssertionFailure() << s_expr << std::endl
+ << s.ToString();
+ }
+}
+
+std::string TmpDir(Env* env) {
+ std::string dir;
+ Status s = env->GetTestDirectory(&dir);
+ EXPECT_TRUE(s.ok()) << s.ToString();
+ return dir;
+}
+
+std::string PerThreadDBPath(std::string dir, std::string name) {
+ size_t tid = std::hash<std::thread::id>()(std::this_thread::get_id());
+ return dir + "/" + name + "_" + std::to_string(tid);
+}
+
+std::string PerThreadDBPath(std::string name) {
+ return PerThreadDBPath(test::TmpDir(), name);
+}
+
+std::string PerThreadDBPath(Env* env, std::string name) {
+ return PerThreadDBPath(test::TmpDir(env), name);
+}
+
+int RandomSeed() {
+ const char* env = getenv("TEST_RANDOM_SEED");
+ int result = (env != nullptr ? atoi(env) : 301);
+ if (result <= 0) {
+ result = 301;
+ }
+ return result;
+}
+
+} // namespace test
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/testharness.h b/storage/rocksdb/rocksdb/test_util/testharness.h
new file mode 100644
index 00000000..11f40ff8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/testharness.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifdef OS_AIX
+#include "gtest/gtest.h"
+#else
+#include <gtest/gtest.h>
+#endif
+
+#include <string>
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace test {
+
+// Return the directory to use for temporary storage.
+std::string TmpDir(Env* env = Env::Default());
+
+// A path unique within the thread
+std::string PerThreadDBPath(std::string name);
+std::string PerThreadDBPath(Env* env, std::string name);
+std::string PerThreadDBPath(std::string dir, std::string name);
+
+// Return a randomization seed for this run. Typically returns the
+// same number on repeated invocations of this binary, but automated
+// runs may be able to vary the seed.
+int RandomSeed();
+
+::testing::AssertionResult AssertStatus(const char* s_expr, const Status& s);
+
+#define ASSERT_OK(s) \
+ ASSERT_PRED_FORMAT1(ROCKSDB_NAMESPACE::test::AssertStatus, s)
+#define ASSERT_NOK(s) ASSERT_FALSE((s).ok())
+#define EXPECT_OK(s) \
+ EXPECT_PRED_FORMAT1(ROCKSDB_NAMESPACE::test::AssertStatus, s)
+#define EXPECT_NOK(s) EXPECT_FALSE((s).ok())
+
+} // namespace test
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/testutil.cc b/storage/rocksdb/rocksdb/test_util/testutil.cc
new file mode 100644
index 00000000..2969a140
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/testutil.cc
@@ -0,0 +1,454 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "test_util/testutil.h"
+
+#include <array>
+#include <cctype>
+#include <fstream>
+#include <sstream>
+
+#include "db/memtable_list.h"
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "file/sequence_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace test {
+
+const uint32_t kDefaultFormatVersion = BlockBasedTableOptions().format_version;
+const uint32_t kLatestFormatVersion = 5u;
+
+Slice RandomString(Random* rnd, int len, std::string* dst) {
+ dst->resize(len);
+ for (int i = 0; i < len; i++) {
+ (*dst)[i] = static_cast<char>(' ' + rnd->Uniform(95)); // ' ' .. '~'
+ }
+ return Slice(*dst);
+}
+
+extern std::string RandomHumanReadableString(Random* rnd, int len) {
+ std::string ret;
+ ret.resize(len);
+ for (int i = 0; i < len; ++i) {
+ ret[i] = static_cast<char>('a' + rnd->Uniform(26));
+ }
+ return ret;
+}
+
+std::string RandomKey(Random* rnd, int len, RandomKeyType type) {
+ // Make sure to generate a wide variety of characters so we
+ // test the boundary conditions for short-key optimizations.
+ static const char kTestChars[] = {'\0', '\1', 'a', 'b', 'c',
+ 'd', 'e', '\xfd', '\xfe', '\xff'};
+ std::string result;
+ for (int i = 0; i < len; i++) {
+ std::size_t indx = 0;
+ switch (type) {
+ case RandomKeyType::RANDOM:
+ indx = rnd->Uniform(sizeof(kTestChars));
+ break;
+ case RandomKeyType::LARGEST:
+ indx = sizeof(kTestChars) - 1;
+ break;
+ case RandomKeyType::MIDDLE:
+ indx = sizeof(kTestChars) / 2;
+ break;
+ case RandomKeyType::SMALLEST:
+ indx = 0;
+ break;
+ }
+ result += kTestChars[indx];
+ }
+ return result;
+}
+
+extern Slice CompressibleString(Random* rnd, double compressed_fraction,
+ int len, std::string* dst) {
+ int raw = static_cast<int>(len * compressed_fraction);
+ if (raw < 1) raw = 1;
+ std::string raw_data;
+ RandomString(rnd, raw, &raw_data);
+
+ // Duplicate the random data until we have filled "len" bytes
+ dst->clear();
+ while (dst->size() < (unsigned int)len) {
+ dst->append(raw_data);
+ }
+ dst->resize(len);
+ return Slice(*dst);
+}
+
+namespace {
+class Uint64ComparatorImpl : public Comparator {
+ public:
+ Uint64ComparatorImpl() {}
+
+ const char* Name() const override { return "rocksdb.Uint64Comparator"; }
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ assert(a.size() == sizeof(uint64_t) && b.size() == sizeof(uint64_t));
+ const uint64_t* left = reinterpret_cast<const uint64_t*>(a.data());
+ const uint64_t* right = reinterpret_cast<const uint64_t*>(b.data());
+ uint64_t leftValue;
+ uint64_t rightValue;
+ GetUnaligned(left, &leftValue);
+ GetUnaligned(right, &rightValue);
+ if (leftValue == rightValue) {
+ return 0;
+ } else if (leftValue < rightValue) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ void FindShortestSeparator(std::string* /*start*/,
+ const Slice& /*limit*/) const override {
+ return;
+ }
+
+ void FindShortSuccessor(std::string* /*key*/) const override { return; }
+};
+} // namespace
+
+const Comparator* Uint64Comparator() {
+ static Uint64ComparatorImpl uint64comp;
+ return &uint64comp;
+}
+
+WritableFileWriter* GetWritableFileWriter(WritableFile* wf,
+ const std::string& fname) {
+ std::unique_ptr<WritableFile> file(wf);
+ return new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(file)),
+ fname, EnvOptions());
+}
+
+RandomAccessFileReader* GetRandomAccessFileReader(RandomAccessFile* raf) {
+ std::unique_ptr<RandomAccessFile> file(raf);
+ return new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
+ "[test RandomAccessFileReader]");
+}
+
+SequentialFileReader* GetSequentialFileReader(SequentialFile* se,
+ const std::string& fname) {
+ std::unique_ptr<SequentialFile> file(se);
+ return new SequentialFileReader(NewLegacySequentialFileWrapper(file), fname);
+}
+
+void CorruptKeyType(InternalKey* ikey) {
+ std::string keystr = ikey->Encode().ToString();
+ keystr[keystr.size() - 8] = kTypeLogData;
+ ikey->DecodeFrom(Slice(keystr.data(), keystr.size()));
+}
+
+std::string KeyStr(const std::string& user_key, const SequenceNumber& seq,
+ const ValueType& t, bool corrupt) {
+ InternalKey k(user_key, seq, t);
+ if (corrupt) {
+ CorruptKeyType(&k);
+ }
+ return k.Encode().ToString();
+}
+
+std::string RandomName(Random* rnd, const size_t len) {
+ std::stringstream ss;
+ for (size_t i = 0; i < len; ++i) {
+ ss << static_cast<char>(rnd->Uniform(26) + 'a');
+ }
+ return ss.str();
+}
+
+CompressionType RandomCompressionType(Random* rnd) {
+ auto ret = static_cast<CompressionType>(rnd->Uniform(6));
+ while (!CompressionTypeSupported(ret)) {
+ ret = static_cast<CompressionType>((static_cast<int>(ret) + 1) % 6);
+ }
+ return ret;
+}
+
+void RandomCompressionTypeVector(const size_t count,
+ std::vector<CompressionType>* types,
+ Random* rnd) {
+ types->clear();
+ for (size_t i = 0; i < count; ++i) {
+ types->emplace_back(RandomCompressionType(rnd));
+ }
+}
+
+const SliceTransform* RandomSliceTransform(Random* rnd, int pre_defined) {
+ int random_num = pre_defined >= 0 ? pre_defined : rnd->Uniform(4);
+ switch (random_num) {
+ case 0:
+ return NewFixedPrefixTransform(rnd->Uniform(20) + 1);
+ case 1:
+ return NewCappedPrefixTransform(rnd->Uniform(20) + 1);
+ case 2:
+ return NewNoopTransform();
+ default:
+ return nullptr;
+ }
+}
+
+BlockBasedTableOptions RandomBlockBasedTableOptions(Random* rnd) {
+ BlockBasedTableOptions opt;
+ opt.cache_index_and_filter_blocks = rnd->Uniform(2);
+ opt.pin_l0_filter_and_index_blocks_in_cache = rnd->Uniform(2);
+ opt.pin_top_level_index_and_filter = rnd->Uniform(2);
+ using IndexType = BlockBasedTableOptions::IndexType;
+ const std::array<IndexType, 4> index_types = {
+ {IndexType::kBinarySearch, IndexType::kHashSearch,
+ IndexType::kTwoLevelIndexSearch, IndexType::kBinarySearchWithFirstKey}};
+ opt.index_type =
+ index_types[rnd->Uniform(static_cast<int>(index_types.size()))];
+ opt.hash_index_allow_collision = rnd->Uniform(2);
+ opt.checksum = static_cast<ChecksumType>(rnd->Uniform(3));
+ opt.block_size = rnd->Uniform(10000000);
+ opt.block_size_deviation = rnd->Uniform(100);
+ opt.block_restart_interval = rnd->Uniform(100);
+ opt.index_block_restart_interval = rnd->Uniform(100);
+ opt.whole_key_filtering = rnd->Uniform(2);
+
+ return opt;
+}
+
+TableFactory* RandomTableFactory(Random* rnd, int pre_defined) {
+#ifndef ROCKSDB_LITE
+ int random_num = pre_defined >= 0 ? pre_defined : rnd->Uniform(4);
+ switch (random_num) {
+ case 0:
+ return NewPlainTableFactory();
+ case 1:
+ return NewCuckooTableFactory();
+ default:
+ return NewBlockBasedTableFactory();
+ }
+#else
+ (void)rnd;
+ (void)pre_defined;
+ return NewBlockBasedTableFactory();
+#endif // !ROCKSDB_LITE
+}
+
+MergeOperator* RandomMergeOperator(Random* rnd) {
+ return new ChanglingMergeOperator(RandomName(rnd, 10));
+}
+
+CompactionFilter* RandomCompactionFilter(Random* rnd) {
+ return new ChanglingCompactionFilter(RandomName(rnd, 10));
+}
+
+CompactionFilterFactory* RandomCompactionFilterFactory(Random* rnd) {
+ return new ChanglingCompactionFilterFactory(RandomName(rnd, 10));
+}
+
+void RandomInitDBOptions(DBOptions* db_opt, Random* rnd) {
+ // boolean options
+ db_opt->advise_random_on_open = rnd->Uniform(2);
+ db_opt->allow_mmap_reads = rnd->Uniform(2);
+ db_opt->allow_mmap_writes = rnd->Uniform(2);
+ db_opt->use_direct_reads = rnd->Uniform(2);
+ db_opt->use_direct_io_for_flush_and_compaction = rnd->Uniform(2);
+ db_opt->create_if_missing = rnd->Uniform(2);
+ db_opt->create_missing_column_families = rnd->Uniform(2);
+ db_opt->enable_thread_tracking = rnd->Uniform(2);
+ db_opt->error_if_exists = rnd->Uniform(2);
+ db_opt->is_fd_close_on_exec = rnd->Uniform(2);
+ db_opt->paranoid_checks = rnd->Uniform(2);
+ db_opt->skip_log_error_on_recovery = rnd->Uniform(2);
+ db_opt->skip_stats_update_on_db_open = rnd->Uniform(2);
+ db_opt->skip_checking_sst_file_sizes_on_db_open = rnd->Uniform(2);
+ db_opt->use_adaptive_mutex = rnd->Uniform(2);
+ db_opt->use_fsync = rnd->Uniform(2);
+ db_opt->recycle_log_file_num = rnd->Uniform(2);
+ db_opt->avoid_flush_during_recovery = rnd->Uniform(2);
+ db_opt->avoid_flush_during_shutdown = rnd->Uniform(2);
+
+ // int options
+ db_opt->max_background_compactions = rnd->Uniform(100);
+ db_opt->max_background_flushes = rnd->Uniform(100);
+ db_opt->max_file_opening_threads = rnd->Uniform(100);
+ db_opt->max_open_files = rnd->Uniform(100);
+ db_opt->table_cache_numshardbits = rnd->Uniform(100);
+
+ // size_t options
+ db_opt->db_write_buffer_size = rnd->Uniform(10000);
+ db_opt->keep_log_file_num = rnd->Uniform(10000);
+ db_opt->log_file_time_to_roll = rnd->Uniform(10000);
+ db_opt->manifest_preallocation_size = rnd->Uniform(10000);
+ db_opt->max_log_file_size = rnd->Uniform(10000);
+
+ // std::string options
+ db_opt->db_log_dir = "path/to/db_log_dir";
+ db_opt->wal_dir = "path/to/wal_dir";
+
+ // uint32_t options
+ db_opt->max_subcompactions = rnd->Uniform(100000);
+
+ // uint64_t options
+ static const uint64_t uint_max = static_cast<uint64_t>(UINT_MAX);
+ db_opt->WAL_size_limit_MB = uint_max + rnd->Uniform(100000);
+ db_opt->WAL_ttl_seconds = uint_max + rnd->Uniform(100000);
+ db_opt->bytes_per_sync = uint_max + rnd->Uniform(100000);
+ db_opt->delayed_write_rate = uint_max + rnd->Uniform(100000);
+ db_opt->delete_obsolete_files_period_micros = uint_max + rnd->Uniform(100000);
+ db_opt->max_manifest_file_size = uint_max + rnd->Uniform(100000);
+ db_opt->max_total_wal_size = uint_max + rnd->Uniform(100000);
+ db_opt->wal_bytes_per_sync = uint_max + rnd->Uniform(100000);
+
+ // unsigned int options
+ db_opt->stats_dump_period_sec = rnd->Uniform(100000);
+}
+
+void RandomInitCFOptions(ColumnFamilyOptions* cf_opt, DBOptions& db_options,
+ Random* rnd) {
+ cf_opt->compaction_style = (CompactionStyle)(rnd->Uniform(4));
+
+ // boolean options
+ cf_opt->report_bg_io_stats = rnd->Uniform(2);
+ cf_opt->disable_auto_compactions = rnd->Uniform(2);
+ cf_opt->inplace_update_support = rnd->Uniform(2);
+ cf_opt->level_compaction_dynamic_level_bytes = rnd->Uniform(2);
+ cf_opt->optimize_filters_for_hits = rnd->Uniform(2);
+ cf_opt->paranoid_file_checks = rnd->Uniform(2);
+ cf_opt->purge_redundant_kvs_while_flush = rnd->Uniform(2);
+ cf_opt->force_consistency_checks = rnd->Uniform(2);
+ cf_opt->compaction_options_fifo.allow_compaction = rnd->Uniform(2);
+ cf_opt->memtable_whole_key_filtering = rnd->Uniform(2);
+
+ // double options
+ cf_opt->hard_rate_limit = static_cast<double>(rnd->Uniform(10000)) / 13;
+ cf_opt->soft_rate_limit = static_cast<double>(rnd->Uniform(10000)) / 13;
+ cf_opt->memtable_prefix_bloom_size_ratio =
+ static_cast<double>(rnd->Uniform(10000)) / 20000.0;
+
+ // int options
+ cf_opt->level0_file_num_compaction_trigger = rnd->Uniform(100);
+ cf_opt->level0_slowdown_writes_trigger = rnd->Uniform(100);
+ cf_opt->level0_stop_writes_trigger = rnd->Uniform(100);
+ cf_opt->max_bytes_for_level_multiplier = rnd->Uniform(100);
+ cf_opt->max_mem_compaction_level = rnd->Uniform(100);
+ cf_opt->max_write_buffer_number = rnd->Uniform(100);
+ cf_opt->max_write_buffer_number_to_maintain = rnd->Uniform(100);
+ cf_opt->max_write_buffer_size_to_maintain = rnd->Uniform(10000);
+ cf_opt->min_write_buffer_number_to_merge = rnd->Uniform(100);
+ cf_opt->num_levels = rnd->Uniform(100);
+ cf_opt->target_file_size_multiplier = rnd->Uniform(100);
+
+ // vector int options
+ cf_opt->max_bytes_for_level_multiplier_additional.resize(cf_opt->num_levels);
+ for (int i = 0; i < cf_opt->num_levels; i++) {
+ cf_opt->max_bytes_for_level_multiplier_additional[i] = rnd->Uniform(100);
+ }
+
+ // size_t options
+ cf_opt->arena_block_size = rnd->Uniform(10000);
+ cf_opt->inplace_update_num_locks = rnd->Uniform(10000);
+ cf_opt->max_successive_merges = rnd->Uniform(10000);
+ cf_opt->memtable_huge_page_size = rnd->Uniform(10000);
+ cf_opt->write_buffer_size = rnd->Uniform(10000);
+
+ // uint32_t options
+ cf_opt->bloom_locality = rnd->Uniform(10000);
+ cf_opt->max_bytes_for_level_base = rnd->Uniform(10000);
+
+ // uint64_t options
+ static const uint64_t uint_max = static_cast<uint64_t>(UINT_MAX);
+ cf_opt->ttl =
+ db_options.max_open_files == -1 ? uint_max + rnd->Uniform(10000) : 0;
+ cf_opt->periodic_compaction_seconds =
+ db_options.max_open_files == -1 ? uint_max + rnd->Uniform(10000) : 0;
+ cf_opt->max_sequential_skip_in_iterations = uint_max + rnd->Uniform(10000);
+ cf_opt->target_file_size_base = uint_max + rnd->Uniform(10000);
+ cf_opt->max_compaction_bytes =
+ cf_opt->target_file_size_base * rnd->Uniform(100);
+ cf_opt->compaction_options_fifo.max_table_files_size =
+ uint_max + rnd->Uniform(10000);
+
+ // unsigned int options
+ cf_opt->rate_limit_delay_max_milliseconds = rnd->Uniform(10000);
+
+ // pointer typed options
+ cf_opt->prefix_extractor.reset(RandomSliceTransform(rnd));
+ cf_opt->table_factory.reset(RandomTableFactory(rnd));
+ cf_opt->merge_operator.reset(RandomMergeOperator(rnd));
+ if (cf_opt->compaction_filter) {
+ delete cf_opt->compaction_filter;
+ }
+ cf_opt->compaction_filter = RandomCompactionFilter(rnd);
+ cf_opt->compaction_filter_factory.reset(RandomCompactionFilterFactory(rnd));
+
+ // custom typed options
+ cf_opt->compression = RandomCompressionType(rnd);
+ RandomCompressionTypeVector(cf_opt->num_levels,
+ &cf_opt->compression_per_level, rnd);
+}
+
+Status DestroyDir(Env* env, const std::string& dir) {
+ Status s;
+ if (env->FileExists(dir).IsNotFound()) {
+ return s;
+ }
+ std::vector<std::string> files_in_dir;
+ s = env->GetChildren(dir, &files_in_dir);
+ if (s.ok()) {
+ for (auto& file_in_dir : files_in_dir) {
+ if (file_in_dir == "." || file_in_dir == "..") {
+ continue;
+ }
+ s = env->DeleteFile(dir + "/" + file_in_dir);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+
+ if (s.ok()) {
+ s = env->DeleteDir(dir);
+ }
+ return s;
+}
+
+bool IsDirectIOSupported(Env* env, const std::string& dir) {
+ EnvOptions env_options;
+ env_options.use_mmap_writes = false;
+ env_options.use_direct_writes = true;
+ std::string tmp = TempFileName(dir, 999);
+ Status s;
+ {
+ std::unique_ptr<WritableFile> file;
+ s = env->NewWritableFile(tmp, &file, env_options);
+ }
+ if (s.ok()) {
+ s = env->DeleteFile(tmp);
+ }
+ return s.ok();
+}
+
+size_t GetLinesCount(const std::string& fname, const std::string& pattern) {
+ std::stringstream ssbuf;
+ std::string line;
+ size_t count = 0;
+
+ std::ifstream inFile(fname.c_str());
+ ssbuf << inFile.rdbuf();
+
+ while (getline(ssbuf, line)) {
+ if (line.find(pattern) != std::string::npos) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+} // namespace test
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/testutil.h b/storage/rocksdb/rocksdb/test_util/testutil.h
new file mode 100644
index 00000000..3f6b4792
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/testutil.h
@@ -0,0 +1,802 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "env/composite_env_wrapper.h"
+#include "file/writable_file_writer.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/table.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "table/internal_iterator.h"
+#include "table/plain/plain_table_factory.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+class SequentialFile;
+class SequentialFileReader;
+
+namespace test {
+
+extern const uint32_t kDefaultFormatVersion;
+extern const uint32_t kLatestFormatVersion;
+
+// Store in *dst a random string of length "len" and return a Slice that
+// references the generated data.
+extern Slice RandomString(Random* rnd, int len, std::string* dst);
+
+extern std::string RandomHumanReadableString(Random* rnd, int len);
+
+// Return a random key with the specified length that may contain interesting
+// characters (e.g. \x00, \xff, etc.).
+enum RandomKeyType : char { RANDOM, LARGEST, SMALLEST, MIDDLE };
+extern std::string RandomKey(Random* rnd, int len,
+ RandomKeyType type = RandomKeyType::RANDOM);
+
+// Store in *dst a string of length "len" that will compress to
+// "N*compressed_fraction" bytes and return a Slice that references
+// the generated data.
+extern Slice CompressibleString(Random* rnd, double compressed_fraction,
+ int len, std::string* dst);
+
+// A wrapper that allows injection of errors.
+class ErrorEnv : public EnvWrapper {
+ public:
+ bool writable_file_error_;
+ int num_writable_file_errors_;
+
+ ErrorEnv() : EnvWrapper(Env::Default()),
+ writable_file_error_(false),
+ num_writable_file_errors_(0) { }
+
+ virtual Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& soptions) override {
+ result->reset();
+ if (writable_file_error_) {
+ ++num_writable_file_errors_;
+ return Status::IOError(fname, "fake error");
+ }
+ return target()->NewWritableFile(fname, result, soptions);
+ }
+};
+
+#ifndef NDEBUG
+// An internal comparator that just forward comparing results from the
+// user comparator in it. Can be used to test entities that have no dependency
+// on internal key structure but consumes InternalKeyComparator, like
+// BlockBasedTable.
+class PlainInternalKeyComparator : public InternalKeyComparator {
+ public:
+ explicit PlainInternalKeyComparator(const Comparator* c)
+ : InternalKeyComparator(c) {}
+
+ virtual ~PlainInternalKeyComparator() {}
+
+ virtual int Compare(const Slice& a, const Slice& b) const override {
+ return user_comparator()->Compare(a, b);
+ }
+};
+#endif
+
+// A test comparator which compare two strings in this way:
+// (1) first compare prefix of 8 bytes in alphabet order,
+// (2) if two strings share the same prefix, sort the other part of the string
+// in the reverse alphabet order.
+// This helps simulate the case of compounded key of [entity][timestamp] and
+// latest timestamp first.
+class SimpleSuffixReverseComparator : public Comparator {
+ public:
+ SimpleSuffixReverseComparator() {}
+
+ virtual const char* Name() const override {
+ return "SimpleSuffixReverseComparator";
+ }
+
+ virtual int Compare(const Slice& a, const Slice& b) const override {
+ Slice prefix_a = Slice(a.data(), 8);
+ Slice prefix_b = Slice(b.data(), 8);
+ int prefix_comp = prefix_a.compare(prefix_b);
+ if (prefix_comp != 0) {
+ return prefix_comp;
+ } else {
+ Slice suffix_a = Slice(a.data() + 8, a.size() - 8);
+ Slice suffix_b = Slice(b.data() + 8, b.size() - 8);
+ return -(suffix_a.compare(suffix_b));
+ }
+ }
+ virtual void FindShortestSeparator(std::string* /*start*/,
+ const Slice& /*limit*/) const override {}
+
+ virtual void FindShortSuccessor(std::string* /*key*/) const override {}
+};
+
+// Returns a user key comparator that can be used for comparing two uint64_t
+// slices. Instead of comparing slices byte-wise, it compares all the 8 bytes
+// at once. Assumes same endian-ness is used though the database's lifetime.
+// Symantics of comparison would differ from Bytewise comparator in little
+// endian machines.
+extern const Comparator* Uint64Comparator();
+
+// Iterator over a vector of keys/values
+class VectorIterator : public InternalIterator {
+ public:
+ explicit VectorIterator(const std::vector<std::string>& keys)
+ : keys_(keys), current_(keys.size()) {
+ std::sort(keys_.begin(), keys_.end());
+ values_.resize(keys.size());
+ }
+
+ VectorIterator(const std::vector<std::string>& keys,
+ const std::vector<std::string>& values)
+ : keys_(keys), values_(values), current_(keys.size()) {
+ assert(keys_.size() == values_.size());
+ }
+
+ virtual bool Valid() const override { return current_ < keys_.size(); }
+
+ virtual void SeekToFirst() override { current_ = 0; }
+ virtual void SeekToLast() override { current_ = keys_.size() - 1; }
+
+ virtual void Seek(const Slice& target) override {
+ current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) -
+ keys_.begin();
+ }
+
+ virtual void SeekForPrev(const Slice& target) override {
+ current_ = std::upper_bound(keys_.begin(), keys_.end(), target.ToString()) -
+ keys_.begin();
+ if (!Valid()) {
+ SeekToLast();
+ } else {
+ Prev();
+ }
+ }
+
+ virtual void Next() override { current_++; }
+ virtual void Prev() override { current_--; }
+
+ virtual Slice key() const override { return Slice(keys_[current_]); }
+ virtual Slice value() const override { return Slice(values_[current_]); }
+
+ virtual Status status() const override { return Status::OK(); }
+
+ virtual bool IsKeyPinned() const override { return true; }
+ virtual bool IsValuePinned() const override { return true; }
+
+ private:
+ std::vector<std::string> keys_;
+ std::vector<std::string> values_;
+ size_t current_;
+};
+extern WritableFileWriter* GetWritableFileWriter(WritableFile* wf,
+ const std::string& fname);
+
+extern RandomAccessFileReader* GetRandomAccessFileReader(RandomAccessFile* raf);
+
+extern SequentialFileReader* GetSequentialFileReader(SequentialFile* se,
+ const std::string& fname);
+
+class StringSink: public WritableFile {
+ public:
+ std::string contents_;
+
+ explicit StringSink(Slice* reader_contents = nullptr) :
+ WritableFile(),
+ contents_(""),
+ reader_contents_(reader_contents),
+ last_flush_(0) {
+ if (reader_contents_ != nullptr) {
+ *reader_contents_ = Slice(contents_.data(), 0);
+ }
+ }
+
+ const std::string& contents() const { return contents_; }
+
+ virtual Status Truncate(uint64_t size) override {
+ contents_.resize(static_cast<size_t>(size));
+ return Status::OK();
+ }
+ virtual Status Close() override { return Status::OK(); }
+ virtual Status Flush() override {
+ if (reader_contents_ != nullptr) {
+ assert(reader_contents_->size() <= last_flush_);
+ size_t offset = last_flush_ - reader_contents_->size();
+ *reader_contents_ = Slice(
+ contents_.data() + offset,
+ contents_.size() - offset);
+ last_flush_ = contents_.size();
+ }
+
+ return Status::OK();
+ }
+ virtual Status Sync() override { return Status::OK(); }
+ virtual Status Append(const Slice& slice) override {
+ contents_.append(slice.data(), slice.size());
+ return Status::OK();
+ }
+ void Drop(size_t bytes) {
+ if (reader_contents_ != nullptr) {
+ contents_.resize(contents_.size() - bytes);
+ *reader_contents_ = Slice(
+ reader_contents_->data(), reader_contents_->size() - bytes);
+ last_flush_ = contents_.size();
+ }
+ }
+
+ private:
+ Slice* reader_contents_;
+ size_t last_flush_;
+};
+
+// A wrapper around a StringSink to give it a RandomRWFile interface
+class RandomRWStringSink : public RandomRWFile {
+ public:
+ explicit RandomRWStringSink(StringSink* ss) : ss_(ss) {}
+
+ Status Write(uint64_t offset, const Slice& data) override {
+ if (offset + data.size() > ss_->contents_.size()) {
+ ss_->contents_.resize(static_cast<size_t>(offset) + data.size(), '\0');
+ }
+
+ char* pos = const_cast<char*>(ss_->contents_.data() + offset);
+ memcpy(pos, data.data(), data.size());
+ return Status::OK();
+ }
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* /*scratch*/) const override {
+ *result = Slice(nullptr, 0);
+ if (offset < ss_->contents_.size()) {
+ size_t str_res_sz =
+ std::min(static_cast<size_t>(ss_->contents_.size() - offset), n);
+ *result = Slice(ss_->contents_.data() + offset, str_res_sz);
+ }
+ return Status::OK();
+ }
+
+ Status Flush() override { return Status::OK(); }
+
+ Status Sync() override { return Status::OK(); }
+
+ Status Close() override { return Status::OK(); }
+
+ const std::string& contents() const { return ss_->contents(); }
+
+ private:
+ StringSink* ss_;
+};
+
+// Like StringSink, this writes into a string. Unlink StringSink, it
+// has some initial content and overwrites it, just like a recycled
+// log file.
+class OverwritingStringSink : public WritableFile {
+ public:
+ explicit OverwritingStringSink(Slice* reader_contents)
+ : WritableFile(),
+ contents_(""),
+ reader_contents_(reader_contents),
+ last_flush_(0) {}
+
+ const std::string& contents() const { return contents_; }
+
+ virtual Status Truncate(uint64_t size) override {
+ contents_.resize(static_cast<size_t>(size));
+ return Status::OK();
+ }
+ virtual Status Close() override { return Status::OK(); }
+ virtual Status Flush() override {
+ if (last_flush_ < contents_.size()) {
+ assert(reader_contents_->size() >= contents_.size());
+ memcpy((char*)reader_contents_->data() + last_flush_,
+ contents_.data() + last_flush_, contents_.size() - last_flush_);
+ last_flush_ = contents_.size();
+ }
+ return Status::OK();
+ }
+ virtual Status Sync() override { return Status::OK(); }
+ virtual Status Append(const Slice& slice) override {
+ contents_.append(slice.data(), slice.size());
+ return Status::OK();
+ }
+ void Drop(size_t bytes) {
+ contents_.resize(contents_.size() - bytes);
+ if (last_flush_ > contents_.size()) last_flush_ = contents_.size();
+ }
+
+ private:
+ std::string contents_;
+ Slice* reader_contents_;
+ size_t last_flush_;
+};
+
+class StringSource: public RandomAccessFile {
+ public:
+ explicit StringSource(const Slice& contents, uint64_t uniq_id = 0,
+ bool mmap = false)
+ : contents_(contents.data(), contents.size()),
+ uniq_id_(uniq_id),
+ mmap_(mmap),
+ total_reads_(0) {}
+
+ virtual ~StringSource() { }
+
+ uint64_t Size() const { return contents_.size(); }
+
+ virtual Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ total_reads_++;
+ if (offset > contents_.size()) {
+ return Status::InvalidArgument("invalid Read offset");
+ }
+ if (offset + n > contents_.size()) {
+ n = contents_.size() - static_cast<size_t>(offset);
+ }
+ if (!mmap_) {
+ memcpy(scratch, &contents_[static_cast<size_t>(offset)], n);
+ *result = Slice(scratch, n);
+ } else {
+ *result = Slice(&contents_[static_cast<size_t>(offset)], n);
+ }
+ return Status::OK();
+ }
+
+ virtual size_t GetUniqueId(char* id, size_t max_size) const override {
+ if (max_size < 20) {
+ return 0;
+ }
+
+ char* rid = id;
+ rid = EncodeVarint64(rid, uniq_id_);
+ rid = EncodeVarint64(rid, 0);
+ return static_cast<size_t>(rid-id);
+ }
+
+ int total_reads() const { return total_reads_; }
+
+ void set_total_reads(int tr) { total_reads_ = tr; }
+
+ private:
+ std::string contents_;
+ uint64_t uniq_id_;
+ bool mmap_;
+ mutable int total_reads_;
+};
+
+inline StringSink* GetStringSinkFromLegacyWriter(
+ const WritableFileWriter* writer) {
+ LegacyWritableFileWrapper* file =
+ static_cast<LegacyWritableFileWrapper*>(writer->writable_file());
+ return static_cast<StringSink*>(file->target());
+}
+
+class NullLogger : public Logger {
+ public:
+ using Logger::Logv;
+ virtual void Logv(const char* /*format*/, va_list /*ap*/) override {}
+ virtual size_t GetLogFileSize() const override { return 0; }
+};
+
+// Corrupts key by changing the type
+extern void CorruptKeyType(InternalKey* ikey);
+
+extern std::string KeyStr(const std::string& user_key,
+ const SequenceNumber& seq, const ValueType& t,
+ bool corrupt = false);
+
+class SleepingBackgroundTask {
+ public:
+ SleepingBackgroundTask()
+ : bg_cv_(&mutex_),
+ should_sleep_(true),
+ done_with_sleep_(false),
+ sleeping_(false) {}
+
+ bool IsSleeping() {
+ MutexLock l(&mutex_);
+ return sleeping_;
+ }
+ void DoSleep() {
+ MutexLock l(&mutex_);
+ sleeping_ = true;
+ bg_cv_.SignalAll();
+ while (should_sleep_) {
+ bg_cv_.Wait();
+ }
+ sleeping_ = false;
+ done_with_sleep_ = true;
+ bg_cv_.SignalAll();
+ }
+ void WaitUntilSleeping() {
+ MutexLock l(&mutex_);
+ while (!sleeping_ || !should_sleep_) {
+ bg_cv_.Wait();
+ }
+ }
+ // Waits for the status to change to sleeping,
+ // otherwise times out.
+ // wait_time is in microseconds.
+ // Returns true when times out, false otherwise.
+ bool TimedWaitUntilSleeping(uint64_t wait_time) {
+ auto abs_time = Env::Default()->NowMicros() + wait_time;
+ MutexLock l(&mutex_);
+ while (!sleeping_ || !should_sleep_) {
+ if (bg_cv_.TimedWait(abs_time)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ void WakeUp() {
+ MutexLock l(&mutex_);
+ should_sleep_ = false;
+ bg_cv_.SignalAll();
+ }
+ void WaitUntilDone() {
+ MutexLock l(&mutex_);
+ while (!done_with_sleep_) {
+ bg_cv_.Wait();
+ }
+ }
+ // Similar to TimedWaitUntilSleeping.
+ // Waits until the task is done.
+ bool TimedWaitUntilDone(uint64_t wait_time) {
+ auto abs_time = Env::Default()->NowMicros() + wait_time;
+ MutexLock l(&mutex_);
+ while (!done_with_sleep_) {
+ if (bg_cv_.TimedWait(abs_time)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ bool WokenUp() {
+ MutexLock l(&mutex_);
+ return should_sleep_ == false;
+ }
+
+ void Reset() {
+ MutexLock l(&mutex_);
+ should_sleep_ = true;
+ done_with_sleep_ = false;
+ }
+
+ static void DoSleepTask(void* arg) {
+ reinterpret_cast<SleepingBackgroundTask*>(arg)->DoSleep();
+ }
+
+ private:
+ port::Mutex mutex_;
+ port::CondVar bg_cv_; // Signalled when background work finishes
+ bool should_sleep_;
+ bool done_with_sleep_;
+ bool sleeping_;
+};
+
+// Filters merge operands and values that are equal to `num`.
+class FilterNumber : public CompactionFilter {
+ public:
+ explicit FilterNumber(uint64_t num) : num_(num) {}
+
+ std::string last_merge_operand_key() { return last_merge_operand_key_; }
+
+ bool Filter(int /*level*/, const ROCKSDB_NAMESPACE::Slice& /*key*/,
+ const ROCKSDB_NAMESPACE::Slice& value, std::string* /*new_value*/,
+ bool* /*value_changed*/) const override {
+ if (value.size() == sizeof(uint64_t)) {
+ return num_ == DecodeFixed64(value.data());
+ }
+ return true;
+ }
+
+ bool FilterMergeOperand(
+ int /*level*/, const ROCKSDB_NAMESPACE::Slice& key,
+ const ROCKSDB_NAMESPACE::Slice& value) const override {
+ last_merge_operand_key_ = key.ToString();
+ if (value.size() == sizeof(uint64_t)) {
+ return num_ == DecodeFixed64(value.data());
+ }
+ return true;
+ }
+
+ const char* Name() const override { return "FilterBadMergeOperand"; }
+
+ private:
+ mutable std::string last_merge_operand_key_;
+ uint64_t num_;
+};
+
+inline std::string EncodeInt(uint64_t x) {
+ std::string result;
+ PutFixed64(&result, x);
+ return result;
+}
+
+ class SeqStringSource : public SequentialFile {
+ public:
+ SeqStringSource(const std::string& data, std::atomic<int>* read_count)
+ : data_(data), offset_(0), read_count_(read_count) {}
+ ~SeqStringSource() override {}
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ std::string output;
+ if (offset_ < data_.size()) {
+ n = std::min(data_.size() - offset_, n);
+ memcpy(scratch, data_.data() + offset_, n);
+ offset_ += n;
+ *result = Slice(scratch, n);
+ } else {
+ return Status::InvalidArgument(
+ "Attemp to read when it already reached eof.");
+ }
+ (*read_count_)++;
+ return Status::OK();
+ }
+ Status Skip(uint64_t n) override {
+ if (offset_ >= data_.size()) {
+ return Status::InvalidArgument(
+ "Attemp to read when it already reached eof.");
+ }
+ // TODO(yhchiang): Currently doesn't handle the overflow case.
+ offset_ += static_cast<size_t>(n);
+ return Status::OK();
+ }
+
+ private:
+ std::string data_;
+ size_t offset_;
+ std::atomic<int>* read_count_;
+ };
+
+ class StringEnv : public EnvWrapper {
+ public:
+ class StringSink : public WritableFile {
+ public:
+ explicit StringSink(std::string* contents)
+ : WritableFile(), contents_(contents) {}
+ virtual Status Truncate(uint64_t size) override {
+ contents_->resize(static_cast<size_t>(size));
+ return Status::OK();
+ }
+ virtual Status Close() override { return Status::OK(); }
+ virtual Status Flush() override { return Status::OK(); }
+ virtual Status Sync() override { return Status::OK(); }
+ virtual Status Append(const Slice& slice) override {
+ contents_->append(slice.data(), slice.size());
+ return Status::OK();
+ }
+
+ private:
+ std::string* contents_;
+ };
+
+ explicit StringEnv(Env* t) : EnvWrapper(t) {}
+ ~StringEnv() override {}
+
+ const std::string& GetContent(const std::string& f) { return files_[f]; }
+
+ const Status WriteToNewFile(const std::string& file_name,
+ const std::string& content) {
+ std::unique_ptr<WritableFile> r;
+ auto s = NewWritableFile(file_name, &r, EnvOptions());
+ if (!s.ok()) {
+ return s;
+ }
+ r->Append(content);
+ r->Flush();
+ r->Close();
+ assert(files_[file_name] == content);
+ return Status::OK();
+ }
+
+ // The following text is boilerplate that forwards all methods to target()
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& /*options*/) override {
+ auto iter = files_.find(f);
+ if (iter == files_.end()) {
+ return Status::NotFound("The specified file does not exist", f);
+ }
+ r->reset(new SeqStringSource(iter->second, &num_seq_file_read_));
+ return Status::OK();
+ }
+ Status NewRandomAccessFile(const std::string& /*f*/,
+ std::unique_ptr<RandomAccessFile>* /*r*/,
+ const EnvOptions& /*options*/) override {
+ return Status::NotSupported();
+ }
+ Status NewWritableFile(const std::string& f,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& /*options*/) override {
+ auto iter = files_.find(f);
+ if (iter != files_.end()) {
+ return Status::IOError("The specified file already exists", f);
+ }
+ r->reset(new StringSink(&files_[f]));
+ return Status::OK();
+ }
+ virtual Status NewDirectory(
+ const std::string& /*name*/,
+ std::unique_ptr<Directory>* /*result*/) override {
+ return Status::NotSupported();
+ }
+ Status FileExists(const std::string& f) override {
+ if (files_.find(f) == files_.end()) {
+ return Status::NotFound();
+ }
+ return Status::OK();
+ }
+ Status GetChildren(const std::string& /*dir*/,
+ std::vector<std::string>* /*r*/) override {
+ return Status::NotSupported();
+ }
+ Status DeleteFile(const std::string& f) override {
+ files_.erase(f);
+ return Status::OK();
+ }
+ Status CreateDir(const std::string& /*d*/) override {
+ return Status::NotSupported();
+ }
+ Status CreateDirIfMissing(const std::string& /*d*/) override {
+ return Status::NotSupported();
+ }
+ Status DeleteDir(const std::string& /*d*/) override {
+ return Status::NotSupported();
+ }
+ Status GetFileSize(const std::string& f, uint64_t* s) override {
+ auto iter = files_.find(f);
+ if (iter == files_.end()) {
+ return Status::NotFound("The specified file does not exist:", f);
+ }
+ *s = iter->second.size();
+ return Status::OK();
+ }
+
+ Status GetFileModificationTime(const std::string& /*fname*/,
+ uint64_t* /*file_mtime*/) override {
+ return Status::NotSupported();
+ }
+
+ Status RenameFile(const std::string& /*s*/,
+ const std::string& /*t*/) override {
+ return Status::NotSupported();
+ }
+
+ Status LinkFile(const std::string& /*s*/,
+ const std::string& /*t*/) override {
+ return Status::NotSupported();
+ }
+
+ Status LockFile(const std::string& /*f*/, FileLock** /*l*/) override {
+ return Status::NotSupported();
+ }
+
+ Status UnlockFile(FileLock* /*l*/) override {
+ return Status::NotSupported();
+ }
+
+ std::atomic<int> num_seq_file_read_;
+
+ protected:
+ std::unordered_map<std::string, std::string> files_;
+ };
+
+// Randomly initialize the given DBOptions
+void RandomInitDBOptions(DBOptions* db_opt, Random* rnd);
+
+// Randomly initialize the given ColumnFamilyOptions
+// Note that the caller is responsible for releasing non-null
+// cf_opt->compaction_filter.
+void RandomInitCFOptions(ColumnFamilyOptions* cf_opt, DBOptions&, Random* rnd);
+
+// A dummy merge operator which can change its name
+class ChanglingMergeOperator : public MergeOperator {
+ public:
+ explicit ChanglingMergeOperator(const std::string& name)
+ : name_(name + "MergeOperator") {}
+ ~ChanglingMergeOperator() {}
+
+ void SetName(const std::string& name) { name_ = name; }
+
+ virtual bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
+ MergeOperationOutput* /*merge_out*/) const override {
+ return false;
+ }
+ virtual bool PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& /*operand_list*/,
+ std::string* /*new_value*/,
+ Logger* /*logger*/) const override {
+ return false;
+ }
+ virtual const char* Name() const override { return name_.c_str(); }
+
+ protected:
+ std::string name_;
+};
+
+// Returns a dummy merge operator with random name.
+MergeOperator* RandomMergeOperator(Random* rnd);
+
+// A dummy compaction filter which can change its name
+class ChanglingCompactionFilter : public CompactionFilter {
+ public:
+ explicit ChanglingCompactionFilter(const std::string& name)
+ : name_(name + "CompactionFilter") {}
+ ~ChanglingCompactionFilter() {}
+
+ void SetName(const std::string& name) { name_ = name; }
+
+ bool Filter(int /*level*/, const Slice& /*key*/,
+ const Slice& /*existing_value*/, std::string* /*new_value*/,
+ bool* /*value_changed*/) const override {
+ return false;
+ }
+
+ const char* Name() const override { return name_.c_str(); }
+
+ private:
+ std::string name_;
+};
+
+// Returns a dummy compaction filter with a random name.
+CompactionFilter* RandomCompactionFilter(Random* rnd);
+
+// A dummy compaction filter factory which can change its name
+class ChanglingCompactionFilterFactory : public CompactionFilterFactory {
+ public:
+ explicit ChanglingCompactionFilterFactory(const std::string& name)
+ : name_(name + "CompactionFilterFactory") {}
+ ~ChanglingCompactionFilterFactory() {}
+
+ void SetName(const std::string& name) { name_ = name; }
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>();
+ }
+
+ // Returns a name that identifies this compaction filter factory.
+ const char* Name() const override { return name_.c_str(); }
+
+ protected:
+ std::string name_;
+};
+
+CompressionType RandomCompressionType(Random* rnd);
+
+void RandomCompressionTypeVector(const size_t count,
+ std::vector<CompressionType>* types,
+ Random* rnd);
+
+CompactionFilterFactory* RandomCompactionFilterFactory(Random* rnd);
+
+const SliceTransform* RandomSliceTransform(Random* rnd, int pre_defined = -1);
+
+TableFactory* RandomTableFactory(Random* rnd, int pre_defined = -1);
+
+std::string RandomName(Random* rnd, const size_t len);
+
+Status DestroyDir(Env* env, const std::string& dir);
+
+bool IsDirectIOSupported(Env* env, const std::string& dir);
+
+// Return the number of lines where a given pattern was found in a file.
+size_t GetLinesCount(const std::string& fname, const std::string& pattern);
+
+} // namespace test
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/test_util/transaction_test_util.cc b/storage/rocksdb/rocksdb/test_util/transaction_test_util.cc
new file mode 100644
index 00000000..73670759
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/transaction_test_util.cc
@@ -0,0 +1,387 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "test_util/transaction_test_util.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <numeric>
+#include <random>
+#include <string>
+#include <thread>
+
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+
+#include "db/dbformat.h"
+#include "db/snapshot_impl.h"
+#include "logging/logging.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+RandomTransactionInserter::RandomTransactionInserter(
+ Random64* rand, const WriteOptions& write_options,
+ const ReadOptions& read_options, uint64_t num_keys, uint16_t num_sets,
+ const uint64_t cmt_delay_ms, const uint64_t first_id)
+ : rand_(rand),
+ write_options_(write_options),
+ read_options_(read_options),
+ num_keys_(num_keys),
+ num_sets_(num_sets),
+ txn_id_(first_id),
+ cmt_delay_ms_(cmt_delay_ms) {}
+
+RandomTransactionInserter::~RandomTransactionInserter() {
+ if (txn_ != nullptr) {
+ delete txn_;
+ }
+ if (optimistic_txn_ != nullptr) {
+ delete optimistic_txn_;
+ }
+}
+
+bool RandomTransactionInserter::TransactionDBInsert(
+ TransactionDB* db, const TransactionOptions& txn_options) {
+ txn_ = db->BeginTransaction(write_options_, txn_options, txn_);
+
+ std::hash<std::thread::id> hasher;
+ char name[64];
+ snprintf(name, 64, "txn%" ROCKSDB_PRIszt "-%" PRIu64,
+ hasher(std::this_thread::get_id()), txn_id_++);
+ assert(strlen(name) < 64 - 1);
+ assert(txn_->SetName(name).ok());
+
+ // Take a snapshot if set_snapshot was not set or with 50% change otherwise
+ bool take_snapshot = txn_->GetSnapshot() == nullptr || rand_->OneIn(2);
+ if (take_snapshot) {
+ txn_->SetSnapshot();
+ read_options_.snapshot = txn_->GetSnapshot();
+ }
+ auto res = DoInsert(db, txn_, false);
+ if (take_snapshot) {
+ read_options_.snapshot = nullptr;
+ }
+ return res;
+}
+
+bool RandomTransactionInserter::OptimisticTransactionDBInsert(
+ OptimisticTransactionDB* db,
+ const OptimisticTransactionOptions& txn_options) {
+ optimistic_txn_ =
+ db->BeginTransaction(write_options_, txn_options, optimistic_txn_);
+
+ return DoInsert(db, optimistic_txn_, true);
+}
+
+bool RandomTransactionInserter::DBInsert(DB* db) {
+ return DoInsert(db, nullptr, false);
+}
+
+Status RandomTransactionInserter::DBGet(
+ DB* db, Transaction* txn, ReadOptions& read_options, uint16_t set_i,
+ uint64_t ikey, bool get_for_update, uint64_t* int_value,
+ std::string* full_key, bool* unexpected_error) {
+ Status s;
+ // Five digits (since the largest uint16_t is 65535) plus the NUL
+ // end char.
+ char prefix_buf[6];
+ // Pad prefix appropriately so we can iterate over each set
+ assert(set_i + 1 <= 9999);
+ snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", set_i + 1);
+ // key format: [SET#][random#]
+ std::string skey = ToString(ikey);
+ Slice base_key(skey);
+ *full_key = std::string(prefix_buf) + base_key.ToString();
+ Slice key(*full_key);
+
+ std::string value;
+ if (txn != nullptr) {
+ if (get_for_update) {
+ s = txn->GetForUpdate(read_options, key, &value);
+ } else {
+ s = txn->Get(read_options, key, &value);
+ }
+ } else {
+ s = db->Get(read_options, key, &value);
+ }
+
+ if (s.ok()) {
+ // Found key, parse its value
+ *int_value = std::stoull(value);
+ if (*int_value == 0 || *int_value == ULONG_MAX) {
+ *unexpected_error = true;
+ fprintf(stderr, "Get returned unexpected value: %s\n", value.c_str());
+ s = Status::Corruption();
+ }
+ } else if (s.IsNotFound()) {
+ // Have not yet written to this key, so assume its value is 0
+ *int_value = 0;
+ s = Status::OK();
+ }
+ return s;
+}
+
+bool RandomTransactionInserter::DoInsert(DB* db, Transaction* txn,
+ bool is_optimistic) {
+ Status s;
+ WriteBatch batch;
+
+ // pick a random number to use to increment a key in each set
+ uint64_t incr = (rand_->Next() % 100) + 1;
+ bool unexpected_error = false;
+
+ std::vector<uint16_t> set_vec(num_sets_);
+ std::iota(set_vec.begin(), set_vec.end(), static_cast<uint16_t>(0));
+ std::shuffle(set_vec.begin(), set_vec.end(), std::random_device{});
+
+ // For each set, pick a key at random and increment it
+ for (uint16_t set_i : set_vec) {
+ uint64_t int_value = 0;
+ std::string full_key;
+ uint64_t rand_key = rand_->Next() % num_keys_;
+ const bool get_for_update = txn ? rand_->OneIn(2) : false;
+ s = DBGet(db, txn, read_options_, set_i, rand_key, get_for_update,
+ &int_value, &full_key, &unexpected_error);
+ Slice key(full_key);
+ if (!s.ok()) {
+ // Optimistic transactions should never return non-ok status here.
+ // Non-optimistic transactions may return write-coflict/timeout errors.
+ if (is_optimistic || !(s.IsBusy() || s.IsTimedOut() || s.IsTryAgain())) {
+ fprintf(stderr, "Get returned an unexpected error: %s\n",
+ s.ToString().c_str());
+ unexpected_error = true;
+ }
+ break;
+ }
+
+ if (s.ok()) {
+ // Increment key
+ std::string sum = ToString(int_value + incr);
+ if (txn != nullptr) {
+ s = txn->Put(key, sum);
+ if (!get_for_update && (s.IsBusy() || s.IsTimedOut())) {
+ // If the initial get was not for update, then the key is not locked
+ // before put and put could fail due to concurrent writes.
+ break;
+ } else if (!s.ok()) {
+ // Since we did a GetForUpdate, Put should not fail.
+ fprintf(stderr, "Put returned an unexpected error: %s\n",
+ s.ToString().c_str());
+ unexpected_error = true;
+ }
+ } else {
+ batch.Put(key, sum);
+ }
+ bytes_inserted_ += key.size() + sum.size();
+ }
+ if (txn != nullptr) {
+ ROCKS_LOG_DEBUG(db->GetDBOptions().info_log,
+ "Insert (%s) %s snap: %" PRIu64 " key:%s value: %" PRIu64
+ "+%" PRIu64 "=%" PRIu64,
+ txn->GetName().c_str(), s.ToString().c_str(),
+ txn->GetSnapshot()->GetSequenceNumber(), full_key.c_str(),
+ int_value, incr, int_value + incr);
+ }
+ }
+
+ if (s.ok()) {
+ if (txn != nullptr) {
+ bool with_prepare = !is_optimistic && !rand_->OneIn(10);
+ if (with_prepare) {
+ // Also try commit without prepare
+ s = txn->Prepare();
+ assert(s.ok());
+ ROCKS_LOG_DEBUG(db->GetDBOptions().info_log,
+ "Prepare of %" PRIu64 " %s (%s)", txn->GetId(),
+ s.ToString().c_str(), txn->GetName().c_str());
+ if (rand_->OneIn(20)) {
+ // This currently only tests the mechanics of writing commit time
+ // write batch so the exact values would not matter.
+ s = txn_->GetCommitTimeWriteBatch()->Put("cat", "dog");
+ assert(s.ok());
+ }
+ db->GetDBOptions().env->SleepForMicroseconds(
+ static_cast<int>(cmt_delay_ms_ * 1000));
+ }
+ if (!rand_->OneIn(20)) {
+ s = txn->Commit();
+ assert(!with_prepare || s.ok());
+ ROCKS_LOG_DEBUG(db->GetDBOptions().info_log,
+ "Commit of %" PRIu64 " %s (%s)", txn->GetId(),
+ s.ToString().c_str(), txn->GetName().c_str());
+ } else {
+ // Also try 5% rollback
+ s = txn->Rollback();
+ ROCKS_LOG_DEBUG(db->GetDBOptions().info_log,
+ "Rollback %" PRIu64 " %s %s", txn->GetId(),
+ txn->GetName().c_str(), s.ToString().c_str());
+ assert(s.ok());
+ }
+ assert(is_optimistic || s.ok());
+
+ if (!s.ok()) {
+ if (is_optimistic) {
+ // Optimistic transactions can have write-conflict errors on commit.
+ // Any other error is unexpected.
+ if (!(s.IsBusy() || s.IsTimedOut() || s.IsTryAgain())) {
+ unexpected_error = true;
+ }
+ } else {
+ // Non-optimistic transactions should only fail due to expiration
+ // or write failures. For testing purproses, we do not expect any
+ // write failures.
+ if (!s.IsExpired()) {
+ unexpected_error = true;
+ }
+ }
+
+ if (unexpected_error) {
+ fprintf(stderr, "Commit returned an unexpected error: %s\n",
+ s.ToString().c_str());
+ }
+ }
+ } else {
+ s = db->Write(write_options_, &batch);
+ if (!s.ok()) {
+ unexpected_error = true;
+ fprintf(stderr, "Write returned an unexpected error: %s\n",
+ s.ToString().c_str());
+ }
+ }
+ } else {
+ if (txn != nullptr) {
+ assert(txn->Rollback().ok());
+ ROCKS_LOG_DEBUG(db->GetDBOptions().info_log, "Error %s for txn %s",
+ s.ToString().c_str(), txn->GetName().c_str());
+ }
+ }
+
+ if (s.ok()) {
+ success_count_++;
+ } else {
+ failure_count_++;
+ }
+
+ last_status_ = s;
+
+ // return success if we didn't get any unexpected errors
+ return !unexpected_error;
+}
+
+// Verify that the sum of the keys in each set are equal
+Status RandomTransactionInserter::Verify(DB* db, uint16_t num_sets,
+ uint64_t num_keys_per_set,
+ bool take_snapshot, Random64* rand,
+ uint64_t delay_ms) {
+ // delay_ms is the delay between taking a snapshot and doing the reads. It
+ // emulates reads from a long-running backup job.
+ assert(delay_ms == 0 || take_snapshot);
+ uint64_t prev_total = 0;
+ uint32_t prev_i = 0;
+ bool prev_assigned = false;
+
+ ReadOptions roptions;
+ if (take_snapshot) {
+ roptions.snapshot = db->GetSnapshot();
+ db->GetDBOptions().env->SleepForMicroseconds(
+ static_cast<int>(delay_ms * 1000));
+ }
+
+ std::vector<uint16_t> set_vec(num_sets);
+ std::iota(set_vec.begin(), set_vec.end(), static_cast<uint16_t>(0));
+ std::shuffle(set_vec.begin(), set_vec.end(), std::random_device{});
+
+ // For each set of keys with the same prefix, sum all the values
+ for (uint16_t set_i : set_vec) {
+ // Five digits (since the largest uint16_t is 65535) plus the NUL
+ // end char.
+ char prefix_buf[6];
+ assert(set_i + 1 <= 9999);
+ snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", set_i + 1);
+ uint64_t total = 0;
+
+ // Use either point lookup or iterator. Point lookups are slower so we use
+ // it less often.
+ const bool use_point_lookup =
+ num_keys_per_set != 0 && rand && rand->OneIn(10);
+ if (use_point_lookup) {
+ ReadOptions read_options;
+ for (uint64_t k = 0; k < num_keys_per_set; k++) {
+ std::string dont_care;
+ uint64_t int_value = 0;
+ bool unexpected_error = false;
+ const bool FOR_UPDATE = false;
+ Status s = DBGet(db, nullptr, roptions, set_i, k, FOR_UPDATE,
+ &int_value, &dont_care, &unexpected_error);
+ assert(s.ok());
+ assert(!unexpected_error);
+ total += int_value;
+ }
+ } else { // user iterators
+ Iterator* iter = db->NewIterator(roptions);
+ for (iter->Seek(Slice(prefix_buf, 4)); iter->Valid(); iter->Next()) {
+ Slice key = iter->key();
+ // stop when we reach a different prefix
+ if (key.ToString().compare(0, 4, prefix_buf) != 0) {
+ break;
+ }
+ Slice value = iter->value();
+ uint64_t int_value = std::stoull(value.ToString());
+ if (int_value == 0 || int_value == ULONG_MAX) {
+ fprintf(stderr, "Iter returned unexpected value: %s\n",
+ value.ToString().c_str());
+ return Status::Corruption();
+ }
+ ROCKS_LOG_DEBUG(
+ db->GetDBOptions().info_log,
+ "VerifyRead at %" PRIu64 " (%" PRIu64 "): %.*s value: %" PRIu64,
+ roptions.snapshot ? roptions.snapshot->GetSequenceNumber() : 0ul,
+ roptions.snapshot
+ ? ((SnapshotImpl*)roptions.snapshot)->min_uncommitted_
+ : 0ul,
+ static_cast<int>(key.size()), key.data(), int_value);
+ total += int_value;
+ }
+ delete iter;
+ }
+
+ if (prev_assigned && total != prev_total) {
+ db->GetDBOptions().info_log->Flush();
+ fprintf(stdout,
+ "RandomTransactionVerify found inconsistent totals using "
+ "pointlookup? %d "
+ "Set[%" PRIu32 "]: %" PRIu64 ", Set[%" PRIu32 "]: %" PRIu64
+ " at snapshot %" PRIu64 "\n",
+ use_point_lookup, prev_i, prev_total, set_i, total,
+ roptions.snapshot ? roptions.snapshot->GetSequenceNumber() : 0ul);
+ fflush(stdout);
+ return Status::Corruption();
+ } else {
+ ROCKS_LOG_DEBUG(
+ db->GetDBOptions().info_log,
+ "RandomTransactionVerify pass pointlookup? %d total: %" PRIu64
+ " snap: %" PRIu64,
+ use_point_lookup, total,
+ roptions.snapshot ? roptions.snapshot->GetSequenceNumber() : 0ul);
+ }
+ prev_total = total;
+ prev_i = set_i;
+ prev_assigned = true;
+ }
+ if (take_snapshot) {
+ db->ReleaseSnapshot(roptions.snapshot);
+ }
+
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/test_util/transaction_test_util.h b/storage/rocksdb/rocksdb/test_util/transaction_test_util.h
new file mode 100644
index 00000000..086b0ea6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/test_util/transaction_test_util.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/options.h"
+#include "port/port.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/transaction_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DB;
+class Random64;
+
+// Utility class for stress testing transactions. Can be used to write many
+// transactions in parallel and then validate that the data written is logically
+// consistent. This class assumes the input DB is initially empty.
+//
+// Each call to TransactionDBInsert()/OptimisticTransactionDBInsert() will
+// increment the value of a key in #num_sets sets of keys. Regardless of
+// whether the transaction succeeds, the total sum of values of keys in each
+// set is an invariant that should remain equal.
+//
+// After calling TransactionDBInsert()/OptimisticTransactionDBInsert() many
+// times, Verify() can be called to validate that the invariant holds.
+//
+// To test writing Transaction in parallel, multiple threads can create a
+// RandomTransactionInserter with similar arguments using the same DB.
+class RandomTransactionInserter {
+ public:
+ // num_keys is the number of keys in each set.
+ // num_sets is the number of sets of keys.
+ // cmt_delay_ms is the delay between prepare (if there is any) and commit
+ // first_id is the id of the first transaction
+ explicit RandomTransactionInserter(
+ Random64* rand, const WriteOptions& write_options = WriteOptions(),
+ const ReadOptions& read_options = ReadOptions(), uint64_t num_keys = 1000,
+ uint16_t num_sets = 3, const uint64_t cmt_delay_ms = 0,
+ const uint64_t first_id = 0);
+
+ ~RandomTransactionInserter();
+
+ // Increment a key in each set using a Transaction on a TransactionDB.
+ //
+ // Returns true if the transaction succeeded OR if any error encountered was
+ // expected (eg a write-conflict). Error status may be obtained by calling
+ // GetLastStatus();
+ bool TransactionDBInsert(
+ TransactionDB* db,
+ const TransactionOptions& txn_options = TransactionOptions());
+
+ // Increment a key in each set using a Transaction on an
+ // OptimisticTransactionDB
+ //
+ // Returns true if the transaction succeeded OR if any error encountered was
+ // expected (eg a write-conflict). Error status may be obtained by calling
+ // GetLastStatus();
+ bool OptimisticTransactionDBInsert(
+ OptimisticTransactionDB* db,
+ const OptimisticTransactionOptions& txn_options =
+ OptimisticTransactionOptions());
+ // Increment a key in each set without using a transaction. If this function
+ // is called in parallel, then Verify() may fail.
+ //
+ // Returns true if the write succeeds.
+ // Error status may be obtained by calling GetLastStatus().
+ bool DBInsert(DB* db);
+
+ // Get the ikey'th key from set set_i
+ static Status DBGet(DB* db, Transaction* txn, ReadOptions& read_options,
+ uint16_t set_i, uint64_t ikey, bool get_for_update,
+ uint64_t* int_value, std::string* full_key,
+ bool* unexpected_error);
+
+ // Returns OK if Invariant is true.
+ static Status Verify(DB* db, uint16_t num_sets, uint64_t num_keys_per_set = 0,
+ bool take_snapshot = false, Random64* rand = nullptr,
+ uint64_t delay_ms = 0);
+
+ // Returns the status of the previous Insert operation
+ Status GetLastStatus() { return last_status_; }
+
+ // Returns the number of successfully written calls to
+ // TransactionDBInsert/OptimisticTransactionDBInsert/DBInsert
+ uint64_t GetSuccessCount() { return success_count_; }
+
+ // Returns the number of calls to
+ // TransactionDBInsert/OptimisticTransactionDBInsert/DBInsert that did not
+ // write any data.
+ uint64_t GetFailureCount() { return failure_count_; }
+
+ // Returns the sum of user keys/values Put() to the DB.
+ size_t GetBytesInserted() { return bytes_inserted_; }
+
+ private:
+ // Input options
+ Random64* rand_;
+ const WriteOptions write_options_;
+ ReadOptions read_options_;
+ const uint64_t num_keys_;
+ const uint16_t num_sets_;
+
+ // Number of successful insert batches performed
+ uint64_t success_count_ = 0;
+
+ // Number of failed insert batches attempted
+ uint64_t failure_count_ = 0;
+
+ size_t bytes_inserted_ = 0;
+
+ // Status returned by most recent insert operation
+ Status last_status_;
+
+ // optimization: re-use allocated transaction objects.
+ Transaction* txn_ = nullptr;
+ Transaction* optimistic_txn_ = nullptr;
+
+ uint64_t txn_id_;
+ // The delay between ::Prepare and ::Commit
+ const uint64_t cmt_delay_ms_;
+
+ bool DoInsert(DB* db, Transaction* txn, bool is_optimistic);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/CPortability.h b/storage/rocksdb/rocksdb/third-party/folly/folly/CPortability.h
new file mode 100644
index 00000000..56cb6b1a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/CPortability.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+/**
+ * Macro for marking functions as having public visibility.
+ */
+#if defined(__GNUC__)
+#define FOLLY_EXPORT __attribute__((__visibility__("default")))
+#else
+#define FOLLY_EXPORT
+#endif
+
+#if defined(__has_feature)
+#define FOLLY_HAS_FEATURE(...) __has_feature(__VA_ARGS__)
+#else
+#define FOLLY_HAS_FEATURE(...) 0
+#endif
+
+#if FOLLY_HAS_FEATURE(thread_sanitizer) || __SANITIZE_THREAD__
+#ifndef FOLLY_SANITIZE_THREAD
+#define FOLLY_SANITIZE_THREAD 1
+#endif
+#endif
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/ConstexprMath.h b/storage/rocksdb/rocksdb/third-party/folly/folly/ConstexprMath.h
new file mode 100644
index 00000000..f09167e0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/ConstexprMath.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+namespace folly {
+template <typename T>
+constexpr T constexpr_max(T a) {
+ return a;
+}
+template <typename T, typename... Ts>
+constexpr T constexpr_max(T a, T b, Ts... ts) {
+ return b < a ? constexpr_max(a, ts...) : constexpr_max(b, ts...);
+}
+
+namespace detail {
+template <typename T>
+constexpr T constexpr_log2_(T a, T e) {
+ return e == T(1) ? a : constexpr_log2_(a + T(1), e / T(2));
+}
+
+template <typename T>
+constexpr T constexpr_log2_ceil_(T l2, T t) {
+ return l2 + T(T(1) << l2 < t ? 1 : 0);
+}
+
+template <typename T>
+constexpr T constexpr_square_(T t) {
+ return t * t;
+}
+} // namespace detail
+
+template <typename T>
+constexpr T constexpr_log2(T t) {
+ return detail::constexpr_log2_(T(0), t);
+}
+
+template <typename T>
+constexpr T constexpr_log2_ceil(T t) {
+ return detail::constexpr_log2_ceil_(constexpr_log2(t), t);
+}
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Indestructible.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Indestructible.h
new file mode 100644
index 00000000..68249d86
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Indestructible.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cassert>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+
+namespace folly {
+
+/***
+ * Indestructible
+ *
+ * When you need a Meyers singleton that will not get destructed, even at
+ * shutdown, and you also want the object stored inline.
+ *
+ * Use like:
+ *
+ * void doSomethingWithExpensiveData();
+ *
+ * void doSomethingWithExpensiveData() {
+ * static const Indestructible<map<string, int>> data{
+ * map<string, int>{{"key1", 17}, {"key2", 19}, {"key3", 23}},
+ * };
+ * callSomethingTakingAMapByRef(*data);
+ * }
+ *
+ * This should be used only for Meyers singletons, and, even then, only when
+ * the instance does not need to be destructed ever.
+ *
+ * This should not be used more generally, e.g., as member fields, etc.
+ *
+ * This is designed as an alternative, but with one fewer allocation at
+ * construction time and one fewer pointer dereference at access time, to the
+ * Meyers singleton pattern of:
+ *
+ * void doSomethingWithExpensiveData() {
+ * static const auto data = // never `delete`d
+ * new map<string, int>{{"key1", 17}, {"key2", 19}, {"key3", 23}};
+ * callSomethingTakingAMapByRef(*data);
+ * }
+ */
+
+template <typename T>
+class Indestructible final {
+ public:
+ template <typename S = T, typename = decltype(S())>
+ constexpr Indestructible() noexcept(noexcept(T())) {}
+
+ /**
+ * Constructor accepting a single argument by forwarding reference, this
+ * allows using list initialzation without the overhead of things like
+ * in_place, etc and also works with std::initializer_list constructors
+ * which can't be deduced, the default parameter helps there.
+ *
+ * auto i = folly::Indestructible<std::map<int, int>>{{{1, 2}}};
+ *
+ * This provides convenience
+ *
+ * There are two versions of this constructor - one for when the element is
+ * implicitly constructible from the given argument and one for when the
+ * type is explicitly but not implicitly constructible from the given
+ * argument.
+ */
+ template <
+ typename U = T,
+ _t<std::enable_if<std::is_constructible<T, U&&>::value>>* = nullptr,
+ _t<std::enable_if<
+ !std::is_same<Indestructible<T>, remove_cvref_t<U>>::value>>* =
+ nullptr,
+ _t<std::enable_if<!std::is_convertible<U&&, T>::value>>* = nullptr>
+ explicit constexpr Indestructible(U&& u) noexcept(
+ noexcept(T(std::declval<U>())))
+ : storage_(std::forward<U>(u)) {}
+ template <
+ typename U = T,
+ _t<std::enable_if<std::is_constructible<T, U&&>::value>>* = nullptr,
+ _t<std::enable_if<
+ !std::is_same<Indestructible<T>, remove_cvref_t<U>>::value>>* =
+ nullptr,
+ _t<std::enable_if<std::is_convertible<U&&, T>::value>>* = nullptr>
+ /* implicit */ constexpr Indestructible(U&& u) noexcept(
+ noexcept(T(std::declval<U>())))
+ : storage_(std::forward<U>(u)) {}
+
+ template <typename... Args, typename = decltype(T(std::declval<Args>()...))>
+ explicit constexpr Indestructible(Args&&... args) noexcept(
+ noexcept(T(std::declval<Args>()...)))
+ : storage_(std::forward<Args>(args)...) {}
+ template <
+ typename U,
+ typename... Args,
+ typename = decltype(
+ T(std::declval<std::initializer_list<U>&>(),
+ std::declval<Args>()...))>
+ explicit constexpr Indestructible(std::initializer_list<U> il, Args... args) noexcept(
+ noexcept(
+ T(std::declval<std::initializer_list<U>&>(),
+ std::declval<Args>()...)))
+ : storage_(il, std::forward<Args>(args)...) {}
+
+ ~Indestructible() = default;
+
+ Indestructible(Indestructible const&) = delete;
+ Indestructible& operator=(Indestructible const&) = delete;
+
+ Indestructible(Indestructible&& other) noexcept(
+ noexcept(T(std::declval<T>())))
+ : storage_(std::move(other.storage_.value)) {
+ other.erased_ = true;
+ }
+ Indestructible& operator=(Indestructible&& other) noexcept(
+ noexcept(T(std::declval<T>()))) {
+ storage_.value = std::move(other.storage_.value);
+ other.erased_ = true;
+ }
+
+ T* get() noexcept {
+ check();
+ return &storage_.value;
+ }
+ T const* get() const noexcept {
+ check();
+ return &storage_.value;
+ }
+ T& operator*() noexcept {
+ return *get();
+ }
+ T const& operator*() const noexcept {
+ return *get();
+ }
+ T* operator->() noexcept {
+ return get();
+ }
+ T const* operator->() const noexcept {
+ return get();
+ }
+
+ private:
+ void check() const noexcept {
+ assert(!erased_);
+ }
+
+ union Storage {
+ T value;
+
+ template <typename S = T, typename = decltype(S())>
+ constexpr Storage() noexcept(noexcept(T())) : value() {}
+
+ template <typename... Args, typename = decltype(T(std::declval<Args>()...))>
+ explicit constexpr Storage(Args&&... args) noexcept(
+ noexcept(T(std::declval<Args>()...)))
+ : value(std::forward<Args>(args)...) {}
+
+ ~Storage() {}
+ };
+
+ Storage storage_{};
+ bool erased_{false};
+};
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Optional.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Optional.h
new file mode 100644
index 00000000..ee12467d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Optional.h
@@ -0,0 +1,570 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+/*
+ * Optional - For conditional initialization of values, like boost::optional,
+ * but with support for move semantics and emplacement. Reference type support
+ * has not been included due to limited use cases and potential confusion with
+ * semantics of assignment: Assigning to an optional reference could quite
+ * reasonably copy its value or redirect the reference.
+ *
+ * Optional can be useful when a variable might or might not be needed:
+ *
+ * Optional<Logger> maybeLogger = ...;
+ * if (maybeLogger) {
+ * maybeLogger->log("hello");
+ * }
+ *
+ * Optional enables a 'null' value for types which do not otherwise have
+ * nullability, especially useful for parameter passing:
+ *
+ * void testIterator(const unique_ptr<Iterator>& it,
+ * initializer_list<int> idsExpected,
+ * Optional<initializer_list<int>> ranksExpected = none) {
+ * for (int i = 0; it->next(); ++i) {
+ * EXPECT_EQ(it->doc().id(), idsExpected[i]);
+ * if (ranksExpected) {
+ * EXPECT_EQ(it->doc().rank(), (*ranksExpected)[i]);
+ * }
+ * }
+ * }
+ *
+ * Optional models OptionalPointee, so calling 'get_pointer(opt)' will return a
+ * pointer to nullptr if the 'opt' is empty, and a pointer to the value if it is
+ * not:
+ *
+ * Optional<int> maybeInt = ...;
+ * if (int* v = get_pointer(maybeInt)) {
+ * cout << *v << endl;
+ * }
+ */
+
+#include <cstddef>
+#include <functional>
+#include <new>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#include <folly/CPortability.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+template <class Value>
+class Optional;
+
+namespace detail {
+template <class Value>
+struct OptionalPromiseReturn;
+} // namespace detail
+
+struct None {
+ enum class _secret { _token };
+
+ /**
+ * No default constructor to support both `op = {}` and `op = none`
+ * as syntax for clearing an Optional, just like std::nullopt_t.
+ */
+ constexpr explicit None(_secret) {}
+};
+constexpr None none{None::_secret::_token};
+
+class FOLLY_EXPORT OptionalEmptyException : public std::runtime_error {
+ public:
+ OptionalEmptyException()
+ : std::runtime_error("Empty Optional cannot be unwrapped") {}
+};
+
+template <class Value>
+class Optional {
+ public:
+ typedef Value value_type;
+
+ static_assert(
+ !std::is_reference<Value>::value,
+ "Optional may not be used with reference types");
+ static_assert(
+ !std::is_abstract<Value>::value,
+ "Optional may not be used with abstract types");
+
+ Optional() noexcept {}
+
+ Optional(const Optional& src) noexcept(
+ std::is_nothrow_copy_constructible<Value>::value) {
+ if (src.hasValue()) {
+ construct(src.value());
+ }
+ }
+
+ Optional(Optional&& src) noexcept(
+ std::is_nothrow_move_constructible<Value>::value) {
+ if (src.hasValue()) {
+ construct(std::move(src.value()));
+ src.clear();
+ }
+ }
+
+ /* implicit */ Optional(const None&) noexcept {}
+
+ /* implicit */ Optional(Value&& newValue) noexcept(
+ std::is_nothrow_move_constructible<Value>::value) {
+ construct(std::move(newValue));
+ }
+
+ /* implicit */ Optional(const Value& newValue) noexcept(
+ std::is_nothrow_copy_constructible<Value>::value) {
+ construct(newValue);
+ }
+
+ template <typename... Args>
+ explicit Optional(in_place_t, Args&&... args) noexcept(
+ std::is_nothrow_constructible<Value, Args...>::value)
+ : Optional{PrivateConstructor{}, std::forward<Args>(args)...} {}
+
+ template <typename U, typename... Args>
+ explicit Optional(
+ in_place_t,
+ std::initializer_list<U> il,
+ Args&&... args) noexcept(std::
+ is_nothrow_constructible<
+ Value,
+ std::initializer_list<U>,
+ Args...>::value)
+ : Optional{PrivateConstructor{}, il, std::forward<Args>(args)...} {}
+
+ // Used only when an Optional is used with coroutines on MSVC
+ /* implicit */ Optional(const detail::OptionalPromiseReturn<Value>& p)
+ : Optional{} {
+ p.promise_->value_ = this;
+ }
+
+ void assign(const None&) {
+ clear();
+ }
+
+ void assign(Optional&& src) {
+ if (this != &src) {
+ if (src.hasValue()) {
+ assign(std::move(src.value()));
+ src.clear();
+ } else {
+ clear();
+ }
+ }
+ }
+
+ void assign(const Optional& src) {
+ if (src.hasValue()) {
+ assign(src.value());
+ } else {
+ clear();
+ }
+ }
+
+ void assign(Value&& newValue) {
+ if (hasValue()) {
+ storage_.value = std::move(newValue);
+ } else {
+ construct(std::move(newValue));
+ }
+ }
+
+ void assign(const Value& newValue) {
+ if (hasValue()) {
+ storage_.value = newValue;
+ } else {
+ construct(newValue);
+ }
+ }
+
+ Optional& operator=(None) noexcept {
+ reset();
+ return *this;
+ }
+
+ template <class Arg>
+ Optional& operator=(Arg&& arg) {
+ assign(std::forward<Arg>(arg));
+ return *this;
+ }
+
+ Optional& operator=(Optional&& other) noexcept(
+ std::is_nothrow_move_assignable<Value>::value) {
+ assign(std::move(other));
+ return *this;
+ }
+
+ Optional& operator=(const Optional& other) noexcept(
+ std::is_nothrow_copy_assignable<Value>::value) {
+ assign(other);
+ return *this;
+ }
+
+ template <class... Args>
+ Value& emplace(Args&&... args) {
+ clear();
+ construct(std::forward<Args>(args)...);
+ return value();
+ }
+
+ template <class U, class... Args>
+ typename std::enable_if<
+ std::is_constructible<Value, std::initializer_list<U>&, Args&&...>::value,
+ Value&>::type
+ emplace(std::initializer_list<U> ilist, Args&&... args) {
+ clear();
+ construct(ilist, std::forward<Args>(args)...);
+ return value();
+ }
+
+ void reset() noexcept {
+ storage_.clear();
+ }
+
+ void clear() noexcept {
+ reset();
+ }
+
+ void swap(Optional& that) noexcept(IsNothrowSwappable<Value>::value) {
+ if (hasValue() && that.hasValue()) {
+ using std::swap;
+ swap(value(), that.value());
+ } else if (hasValue()) {
+ that.emplace(std::move(value()));
+ reset();
+ } else if (that.hasValue()) {
+ emplace(std::move(that.value()));
+ that.reset();
+ }
+ }
+
+ const Value& value() const& {
+ require_value();
+ return storage_.value;
+ }
+
+ Value& value() & {
+ require_value();
+ return storage_.value;
+ }
+
+ Value&& value() && {
+ require_value();
+ return std::move(storage_.value);
+ }
+
+ const Value&& value() const&& {
+ require_value();
+ return std::move(storage_.value);
+ }
+
+ const Value* get_pointer() const& {
+ return storage_.hasValue ? &storage_.value : nullptr;
+ }
+ Value* get_pointer() & {
+ return storage_.hasValue ? &storage_.value : nullptr;
+ }
+ Value* get_pointer() && = delete;
+
+ bool has_value() const noexcept {
+ return storage_.hasValue;
+ }
+
+ bool hasValue() const noexcept {
+ return has_value();
+ }
+
+ explicit operator bool() const noexcept {
+ return has_value();
+ }
+
+ const Value& operator*() const& {
+ return value();
+ }
+ Value& operator*() & {
+ return value();
+ }
+ const Value&& operator*() const&& {
+ return std::move(value());
+ }
+ Value&& operator*() && {
+ return std::move(value());
+ }
+
+ const Value* operator->() const {
+ return &value();
+ }
+ Value* operator->() {
+ return &value();
+ }
+
+ // Return a copy of the value if set, or a given default if not.
+ template <class U>
+ Value value_or(U&& dflt) const& {
+ if (storage_.hasValue) {
+ return storage_.value;
+ }
+
+ return std::forward<U>(dflt);
+ }
+
+ template <class U>
+ Value value_or(U&& dflt) && {
+ if (storage_.hasValue) {
+ return std::move(storage_.value);
+ }
+
+ return std::forward<U>(dflt);
+ }
+
+ private:
+ template <class T>
+ friend Optional<_t<std::decay<T>>> make_optional(T&&);
+ template <class T, class... Args>
+ friend Optional<T> make_optional(Args&&... args);
+ template <class T, class U, class... As>
+ friend Optional<T> make_optional(std::initializer_list<U>, As&&...);
+
+ /**
+ * Construct the optional in place, this is duplicated as a non-explicit
+ * constructor to allow returning values that are non-movable from
+ * make_optional using list initialization.
+ *
+ * Until C++17, at which point this will become unnecessary because of
+ * specified prvalue elision.
+ */
+ struct PrivateConstructor {
+ explicit PrivateConstructor() = default;
+ };
+ template <typename... Args>
+ Optional(PrivateConstructor, Args&&... args) noexcept(
+ std::is_constructible<Value, Args&&...>::value) {
+ construct(std::forward<Args>(args)...);
+ }
+
+ void require_value() const {
+ if (!storage_.hasValue) {
+ throw OptionalEmptyException{};
+ }
+ }
+
+ template <class... Args>
+ void construct(Args&&... args) {
+ const void* ptr = &storage_.value;
+ // For supporting const types.
+ new (const_cast<void*>(ptr)) Value(std::forward<Args>(args)...);
+ storage_.hasValue = true;
+ }
+
+ struct StorageTriviallyDestructible {
+ union {
+ char emptyState;
+ Value value;
+ };
+ bool hasValue;
+
+ StorageTriviallyDestructible()
+ : emptyState('\0'), hasValue{false} {}
+ void clear() {
+ hasValue = false;
+ }
+ };
+
+ struct StorageNonTriviallyDestructible {
+ union {
+ char emptyState;
+ Value value;
+ };
+ bool hasValue;
+
+ StorageNonTriviallyDestructible() : hasValue{false} {}
+ ~StorageNonTriviallyDestructible() {
+ clear();
+ }
+
+ void clear() {
+ if (hasValue) {
+ hasValue = false;
+ value.~Value();
+ }
+ }
+ };
+
+ using Storage = typename std::conditional<
+ std::is_trivially_destructible<Value>::value,
+ StorageTriviallyDestructible,
+ StorageNonTriviallyDestructible>::type;
+
+ Storage storage_;
+};
+
+template <class T>
+const T* get_pointer(const Optional<T>& opt) {
+ return opt.get_pointer();
+}
+
+template <class T>
+T* get_pointer(Optional<T>& opt) {
+ return opt.get_pointer();
+}
+
+template <class T>
+void swap(Optional<T>& a, Optional<T>& b) noexcept(noexcept(a.swap(b))) {
+ a.swap(b);
+}
+
+template <class T>
+Optional<_t<std::decay<T>>> make_optional(T&& v) {
+ using PrivateConstructor =
+ typename folly::Optional<_t<std::decay<T>>>::PrivateConstructor;
+ return {PrivateConstructor{}, std::forward<T>(v)};
+}
+
+template <class T, class... Args>
+folly::Optional<T> make_optional(Args&&... args) {
+ using PrivateConstructor = typename folly::Optional<T>::PrivateConstructor;
+ return {PrivateConstructor{}, std::forward<Args>(args)...};
+}
+
+template <class T, class U, class... Args>
+folly::Optional<T> make_optional(
+ std::initializer_list<U> il,
+ Args&&... args) {
+ using PrivateConstructor = typename folly::Optional<T>::PrivateConstructor;
+ return {PrivateConstructor{}, il, std::forward<Args>(args)...};
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Comparisons.
+
+template <class U, class V>
+bool operator==(const Optional<U>& a, const V& b) {
+ return a.hasValue() && a.value() == b;
+}
+
+template <class U, class V>
+bool operator!=(const Optional<U>& a, const V& b) {
+ return !(a == b);
+}
+
+template <class U, class V>
+bool operator==(const U& a, const Optional<V>& b) {
+ return b.hasValue() && b.value() == a;
+}
+
+template <class U, class V>
+bool operator!=(const U& a, const Optional<V>& b) {
+ return !(a == b);
+}
+
+template <class U, class V>
+bool operator==(const Optional<U>& a, const Optional<V>& b) {
+ if (a.hasValue() != b.hasValue()) {
+ return false;
+ }
+ if (a.hasValue()) {
+ return a.value() == b.value();
+ }
+ return true;
+}
+
+template <class U, class V>
+bool operator!=(const Optional<U>& a, const Optional<V>& b) {
+ return !(a == b);
+}
+
+template <class U, class V>
+bool operator<(const Optional<U>& a, const Optional<V>& b) {
+ if (a.hasValue() != b.hasValue()) {
+ return a.hasValue() < b.hasValue();
+ }
+ if (a.hasValue()) {
+ return a.value() < b.value();
+ }
+ return false;
+}
+
+template <class U, class V>
+bool operator>(const Optional<U>& a, const Optional<V>& b) {
+ return b < a;
+}
+
+template <class U, class V>
+bool operator<=(const Optional<U>& a, const Optional<V>& b) {
+ return !(b < a);
+}
+
+template <class U, class V>
+bool operator>=(const Optional<U>& a, const Optional<V>& b) {
+ return !(a < b);
+}
+
+// Suppress comparability of Optional<T> with T, despite implicit conversion.
+template <class V>
+bool operator<(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator<=(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator>=(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator>(const Optional<V>&, const V& other) = delete;
+template <class V>
+bool operator<(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator<=(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator>=(const V& other, const Optional<V>&) = delete;
+template <class V>
+bool operator>(const V& other, const Optional<V>&) = delete;
+
+// Comparisons with none
+template <class V>
+bool operator==(const Optional<V>& a, None) noexcept {
+ return !a.hasValue();
+}
+template <class V>
+bool operator==(None, const Optional<V>& a) noexcept {
+ return !a.hasValue();
+}
+template <class V>
+bool operator<(const Optional<V>&, None) noexcept {
+ return false;
+}
+template <class V>
+bool operator<(None, const Optional<V>& a) noexcept {
+ return a.hasValue();
+}
+template <class V>
+bool operator>(const Optional<V>& a, None) noexcept {
+ return a.hasValue();
+}
+template <class V>
+bool operator>(None, const Optional<V>&) noexcept {
+ return false;
+}
+template <class V>
+bool operator<=(None, const Optional<V>&) noexcept {
+ return true;
+}
+template <class V>
+bool operator<=(const Optional<V>& a, None) noexcept {
+ return !a.hasValue();
+}
+template <class V>
+bool operator>=(const Optional<V>&, None) noexcept {
+ return true;
+}
+template <class V>
+bool operator>=(None, const Optional<V>& a) noexcept {
+ return !a.hasValue();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Portability.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Portability.h
new file mode 100644
index 00000000..61c05ff2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Portability.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/CPortability.h>
+
+#if defined(__arm__)
+#define FOLLY_ARM 1
+#else
+#define FOLLY_ARM 0
+#endif
+
+#if defined(__x86_64__) || defined(_M_X64)
+#define FOLLY_X64 1
+#else
+#define FOLLY_X64 0
+#endif
+
+#if defined(__aarch64__)
+#define FOLLY_AARCH64 1
+#else
+#define FOLLY_AARCH64 0
+#endif
+
+#if defined(__powerpc64__)
+#define FOLLY_PPC64 1
+#else
+#define FOLLY_PPC64 0
+#endif
+
+#if defined(__has_builtin)
+#define FOLLY_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__)
+#else
+#define FOLLY_HAS_BUILTIN(...) 0
+#endif
+
+#if defined(__has_cpp_attribute)
+#if __has_cpp_attribute(nodiscard)
+#define FOLLY_NODISCARD [[nodiscard]]
+#endif
+#endif
+#if !defined FOLLY_NODISCARD
+#if defined(_MSC_VER) && (_MSC_VER >= 1700)
+#define FOLLY_NODISCARD _Check_return_
+#elif defined(__GNUC__)
+#define FOLLY_NODISCARD __attribute__((__warn_unused_result__))
+#else
+#define FOLLY_NODISCARD
+#endif
+#endif
+
+namespace folly {
+constexpr bool kIsArchArm = FOLLY_ARM == 1;
+constexpr bool kIsArchAmd64 = FOLLY_X64 == 1;
+constexpr bool kIsArchAArch64 = FOLLY_AARCH64 == 1;
+constexpr bool kIsArchPPC64 = FOLLY_PPC64 == 1;
+} // namespace folly
+
+namespace folly {
+#ifdef NDEBUG
+constexpr auto kIsDebug = false;
+#else
+constexpr auto kIsDebug = true;
+#endif
+} // namespace folly
+
+namespace folly {
+#if defined(_MSC_VER)
+constexpr bool kIsMsvc = true;
+#else
+constexpr bool kIsMsvc = false;
+#endif
+} // namespace folly
+
+namespace folly {
+#if FOLLY_SANITIZE_THREAD
+constexpr bool kIsSanitizeThread = true;
+#else
+constexpr bool kIsSanitizeThread = false;
+#endif
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/ScopeGuard.h b/storage/rocksdb/rocksdb/third-party/folly/folly/ScopeGuard.h
new file mode 100644
index 00000000..71134406
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/ScopeGuard.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Traits.h>
+
+#include <utility>
+#include <type_traits>
+
+namespace folly {
+namespace scope_guard_detail {
+template <typename F>
+class ScopeGuardImpl {
+ public:
+ explicit ScopeGuardImpl(F&& f) : f_{std::forward<F>(f)} {}
+ ~ScopeGuardImpl() {
+ f_();
+ }
+
+ private:
+ F f_;
+};
+
+enum class ScopeGuardEnum {};
+template <typename Func, typename DecayedFunc = _t<std::decay<Func>>>
+ScopeGuardImpl<DecayedFunc> operator+(ScopeGuardEnum, Func&& func) {
+ return ScopeGuardImpl<DecayedFunc>{std::forward<Func>(func)};
+}
+} // namespace scope_guard_detail
+} // namespace folly
+
+/**
+ * FB_ANONYMOUS_VARIABLE(str) introduces an identifier starting with
+ * str and ending with a number that varies with the line.
+ */
+#ifndef FB_ANONYMOUS_VARIABLE
+#define FB_CONCATENATE_IMPL(s1, s2) s1##s2
+#define FB_CONCATENATE(s1, s2) FB_CONCATENATE_IMPL(s1, s2)
+#ifdef __COUNTER__
+#define FB_ANONYMOUS_VARIABLE(str) \
+ FB_CONCATENATE(FB_CONCATENATE(FB_CONCATENATE(str, __COUNTER__), _), __LINE__)
+#else
+#define FB_ANONYMOUS_VARIABLE(str) FB_CONCATENATE(str, __LINE__)
+#endif
+#endif
+
+#ifndef SCOPE_EXIT
+#define SCOPE_EXIT \
+ auto FB_ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) = \
+ ::folly::scope_guard_detail::ScopeGuardEnum{} + [&]() noexcept
+#endif
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Traits.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Traits.h
new file mode 100644
index 00000000..ea7e1eb1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Traits.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <type_traits>
+#include <utility>
+
+namespace folly {
+
+#if !defined(_MSC_VER)
+template <class T>
+struct is_trivially_copyable
+ : std::integral_constant<bool, __has_trivial_copy(T)> {};
+#else
+template <class T>
+using is_trivially_copyable = std::is_trivially_copyable<T>;
+#endif
+
+/***
+ * _t
+ *
+ * Instead of:
+ *
+ * using decayed = typename std::decay<T>::type;
+ *
+ * With the C++14 standard trait aliases, we could use:
+ *
+ * using decayed = std::decay_t<T>;
+ *
+ * Without them, we could use:
+ *
+ * using decayed = _t<std::decay<T>>;
+ *
+ * Also useful for any other library with template types having dependent
+ * member types named `type`, like the standard trait types.
+ */
+template <typename T>
+using _t = typename T::type;
+
+/**
+ * type_t
+ *
+ * A type alias for the first template type argument. `type_t` is useful for
+ * controlling class-template and function-template partial specialization.
+ *
+ * Example:
+ *
+ * template <typename Value>
+ * class Container {
+ * public:
+ * template <typename... Args>
+ * Container(
+ * type_t<in_place_t, decltype(Value(std::declval<Args>()...))>,
+ * Args&&...);
+ * };
+ *
+ * void_t
+ *
+ * A type alias for `void`. `void_t` is useful for controling class-template
+ * and function-template partial specialization.
+ *
+ * Example:
+ *
+ * // has_value_type<T>::value is true if T has a nested type `value_type`
+ * template <class T, class = void>
+ * struct has_value_type
+ * : std::false_type {};
+ *
+ * template <class T>
+ * struct has_value_type<T, folly::void_t<typename T::value_type>>
+ * : std::true_type {};
+ */
+
+/**
+ * There is a bug in libstdc++, libc++, and MSVC's STL that causes it to
+ * ignore unused template parameter arguments in template aliases and does not
+ * cause substitution failures. This defect has been recorded here:
+ * http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558.
+ *
+ * This causes the implementation of std::void_t to be buggy, as it is likely
+ * defined as something like the following:
+ *
+ * template <typename...>
+ * using void_t = void;
+ *
+ * This causes the compiler to ignore all the template arguments and does not
+ * help when one wants to cause substitution failures. Rather declarations
+ * which have void_t in orthogonal specializations are treated as the same.
+ * For example, assuming the possible `T` types are only allowed to have
+ * either the alias `one` or `two` and never both or none:
+ *
+ * template <typename T,
+ * typename std::void_t<std::decay_t<T>::one>* = nullptr>
+ * void foo(T&&) {}
+ * template <typename T,
+ * typename std::void_t<std::decay_t<T>::two>* = nullptr>
+ * void foo(T&&) {}
+ *
+ * The second foo() will be a redefinition because it conflicts with the first
+ * one; void_t does not cause substitution failures - the template types are
+ * just ignored.
+ */
+
+namespace traits_detail {
+template <class T, class...>
+struct type_t_ {
+ using type = T;
+};
+} // namespace traits_detail
+
+template <class T, class... Ts>
+using type_t = typename traits_detail::type_t_<T, Ts...>::type;
+template <class... Ts>
+using void_t = type_t<void, Ts...>;
+
+/**
+ * A type trait to remove all const volatile and reference qualifiers on a
+ * type T
+ */
+template <typename T>
+struct remove_cvref {
+ using type =
+ typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+};
+template <typename T>
+using remove_cvref_t = typename remove_cvref<T>::type;
+
+template <class T>
+struct IsNothrowSwappable
+ : std::integral_constant<
+ bool,
+ std::is_nothrow_move_constructible<T>::value&& noexcept(
+ std::swap(std::declval<T&>(), std::declval<T&>()))> {};
+
+template <typename...>
+struct Conjunction : std::true_type {};
+template <typename T>
+struct Conjunction<T> : T {};
+template <typename T, typename... TList>
+struct Conjunction<T, TList...>
+ : std::conditional<T::value, Conjunction<TList...>, T>::type {};
+
+template <typename T>
+struct Negation : std::integral_constant<bool, !T::value> {};
+
+template <std::size_t I>
+using index_constant = std::integral_constant<std::size_t, I>;
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Unit.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Unit.h
new file mode 100644
index 00000000..c8cb77e2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Unit.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <type_traits>
+
+namespace folly {
+
+/// In functional programming, the degenerate case is often called "unit". In
+/// C++, "void" is often the best analogue. However, because of the syntactic
+/// special-casing required for void, it is frequently a liability for template
+/// metaprogramming. So, instead of writing specializations to handle cases like
+/// SomeContainer<void>, a library author may instead rule that out and simply
+/// have library users use SomeContainer<Unit>. Contained values may be ignored.
+/// Much easier.
+///
+/// "void" is the type that admits of no values at all. It is not possible to
+/// construct a value of this type.
+/// "unit" is the type that admits of precisely one unique value. It is
+/// possible to construct a value of this type, but it is always the same value
+/// every time, so it is uninteresting.
+struct Unit {
+ constexpr bool operator==(const Unit& /*other*/) const {
+ return true;
+ }
+ constexpr bool operator!=(const Unit& /*other*/) const {
+ return false;
+ }
+};
+
+constexpr Unit unit{};
+
+template <typename T>
+struct lift_unit {
+ using type = T;
+};
+template <>
+struct lift_unit<void> {
+ using type = Unit;
+};
+template <typename T>
+using lift_unit_t = typename lift_unit<T>::type;
+
+template <typename T>
+struct drop_unit {
+ using type = T;
+};
+template <>
+struct drop_unit<Unit> {
+ using type = void;
+};
+template <typename T>
+using drop_unit_t = typename drop_unit<T>::type;
+
+} // namespace folly
+
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/Utility.h b/storage/rocksdb/rocksdb/third-party/folly/folly/Utility.h
new file mode 100644
index 00000000..7e43bdc2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/Utility.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <utility>
+#include <type_traits>
+
+namespace folly {
+
+/**
+ * Backports from C++17 of:
+ * std::in_place_t
+ * std::in_place_type_t
+ * std::in_place_index_t
+ * std::in_place
+ * std::in_place_type
+ * std::in_place_index
+ */
+
+struct in_place_tag {};
+template <class>
+struct in_place_type_tag {};
+template <std::size_t>
+struct in_place_index_tag {};
+
+using in_place_t = in_place_tag (&)(in_place_tag);
+template <class T>
+using in_place_type_t = in_place_type_tag<T> (&)(in_place_type_tag<T>);
+template <std::size_t I>
+using in_place_index_t = in_place_index_tag<I> (&)(in_place_index_tag<I>);
+
+inline in_place_tag in_place(in_place_tag = {}) {
+ return {};
+}
+template <class T>
+inline in_place_type_tag<T> in_place_type(in_place_type_tag<T> = {}) {
+ return {};
+}
+template <std::size_t I>
+inline in_place_index_tag<I> in_place_index(in_place_index_tag<I> = {}) {
+ return {};
+}
+
+template <class T, class U = T>
+T exchange(T& obj, U&& new_value) {
+ T old_value = std::move(obj);
+ obj = std::forward<U>(new_value);
+ return old_value;
+}
+
+namespace utility_detail {
+template <typename...>
+struct make_seq_cat;
+template <
+ template <typename T, T...> class S,
+ typename T,
+ T... Ta,
+ T... Tb,
+ T... Tc>
+struct make_seq_cat<S<T, Ta...>, S<T, Tb...>, S<T, Tc...>> {
+ using type =
+ S<T,
+ Ta...,
+ (sizeof...(Ta) + Tb)...,
+ (sizeof...(Ta) + sizeof...(Tb) + Tc)...>;
+};
+
+// Not parameterizing by `template <typename T, T...> class, typename` because
+// clang precisely v4.0 fails to compile that. Note that clang v3.9 and v5.0
+// handle that code correctly.
+//
+// For this to work, `S0` is required to be `Sequence<T>` and `S1` is required
+// to be `Sequence<T, 0>`.
+
+template <std::size_t Size>
+struct make_seq {
+ template <typename S0, typename S1>
+ using apply = typename make_seq_cat<
+ typename make_seq<Size / 2>::template apply<S0, S1>,
+ typename make_seq<Size / 2>::template apply<S0, S1>,
+ typename make_seq<Size % 2>::template apply<S0, S1>>::type;
+};
+template <>
+struct make_seq<1> {
+ template <typename S0, typename S1>
+ using apply = S1;
+};
+template <>
+struct make_seq<0> {
+ template <typename S0, typename S1>
+ using apply = S0;
+};
+} // namespace utility_detail
+
+// TODO: Remove after upgrading to C++14 baseline
+
+template <class T, T... Ints>
+struct integer_sequence {
+ using value_type = T;
+
+ static constexpr std::size_t size() noexcept {
+ return sizeof...(Ints);
+ }
+};
+
+template <std::size_t... Ints>
+using index_sequence = integer_sequence<std::size_t, Ints...>;
+
+template <typename T, std::size_t Size>
+using make_integer_sequence = typename utility_detail::make_seq<
+ Size>::template apply<integer_sequence<T>, integer_sequence<T, 0>>;
+
+template <std::size_t Size>
+using make_index_sequence = make_integer_sequence<std::size_t, Size>;
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+/**
+ * A simple helper for getting a constant reference to an object.
+ *
+ * Example:
+ *
+ * std::vector<int> v{1,2,3};
+ * // The following two lines are equivalent:
+ * auto a = const_cast<const std::vector<int>&>(v).begin();
+ * auto b = folly::as_const(v).begin();
+ *
+ * Like C++17's std::as_const. See http://wg21.link/p0007
+ */
+template <class T>
+T const& as_const(T& t) noexcept {
+ return t;
+}
+
+template <class T>
+void as_const(T const&&) = delete;
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/chrono/Hardware.h b/storage/rocksdb/rocksdb/third-party/folly/folly/chrono/Hardware.h
new file mode 100644
index 00000000..ec7be82e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/chrono/Hardware.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Portability.h>
+
+#include <chrono>
+#include <cstdint>
+
+#if _MSC_VER
+extern "C" std::uint64_t __rdtsc();
+#pragma intrinsic(__rdtsc)
+#endif
+
+namespace folly {
+
+inline std::uint64_t hardware_timestamp() {
+#if _MSC_VER
+ return __rdtsc();
+#elif __GNUC__ && (__i386__ || FOLLY_X64)
+ return __builtin_ia32_rdtsc();
+#else
+ // use steady_clock::now() as an approximation for the timestamp counter on
+ // non-x86 systems
+ return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+} // namespace folly
+
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/container/Array.h b/storage/rocksdb/rocksdb/third-party/folly/folly/container/Array.h
new file mode 100644
index 00000000..bb3167b9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/container/Array.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <array>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+namespace array_detail {
+template <typename>
+struct is_ref_wrapper : std::false_type {};
+template <typename T>
+struct is_ref_wrapper<std::reference_wrapper<T>> : std::true_type {};
+
+template <typename T>
+using not_ref_wrapper =
+ folly::Negation<is_ref_wrapper<typename std::decay<T>::type>>;
+
+template <typename D, typename...>
+struct return_type_helper {
+ using type = D;
+};
+template <typename... TList>
+struct return_type_helper<void, TList...> {
+ static_assert(
+ folly::Conjunction<not_ref_wrapper<TList>...>::value,
+ "TList cannot contain reference_wrappers when D is void");
+ using type = typename std::common_type<TList...>::type;
+};
+
+template <typename D, typename... TList>
+using return_type = std::
+ array<typename return_type_helper<D, TList...>::type, sizeof...(TList)>;
+} // namespace array_detail
+
+template <typename D = void, typename... TList>
+constexpr array_detail::return_type<D, TList...> make_array(TList&&... t) {
+ using value_type =
+ typename array_detail::return_type_helper<D, TList...>::type;
+ return {{static_cast<value_type>(std::forward<TList>(t))...}};
+}
+
+namespace array_detail {
+template <typename MakeItem, std::size_t... Index>
+inline constexpr auto make_array_with(
+ MakeItem const& make,
+ folly::index_sequence<Index...>)
+ -> std::array<decltype(make(0)), sizeof...(Index)> {
+ return std::array<decltype(make(0)), sizeof...(Index)>{{make(Index)...}};
+}
+} // namespace array_detail
+
+// make_array_with
+//
+// Constructs a std::array<..., Size> with elements m(i) for i in [0, Size).
+template <std::size_t Size, typename MakeItem>
+constexpr auto make_array_with(MakeItem const& make)
+ -> decltype(array_detail::make_array_with(
+ make,
+ folly::make_index_sequence<Size>{})) {
+ return array_detail::make_array_with(
+ make,
+ folly::make_index_sequence<Size>{});
+}
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex-inl.h b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex-inl.h
new file mode 100644
index 00000000..3b2a412b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex-inl.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/detail/Futex.h>
+#include <folly/synchronization/ParkingLot.h>
+
+namespace folly {
+namespace detail {
+
+/** Optimal when TargetClock is the same type as Clock.
+ *
+ * Otherwise, both Clock::now() and TargetClock::now() must be invoked. */
+template <typename TargetClock, typename Clock, typename Duration>
+typename TargetClock::time_point time_point_conv(
+ std::chrono::time_point<Clock, Duration> const& time) {
+ using std::chrono::duration_cast;
+ using TimePoint = std::chrono::time_point<Clock, Duration>;
+ using TargetDuration = typename TargetClock::duration;
+ using TargetTimePoint = typename TargetClock::time_point;
+ if (time == TimePoint::max()) {
+ return TargetTimePoint::max();
+ } else if (std::is_same<Clock, TargetClock>::value) {
+ // in place of time_point_cast, which cannot compile without if-constexpr
+ auto const delta = time.time_since_epoch();
+ return TargetTimePoint(duration_cast<TargetDuration>(delta));
+ } else {
+ // different clocks with different epochs, so non-optimal case
+ auto const delta = time - Clock::now();
+ return TargetClock::now() + duration_cast<TargetDuration>(delta);
+ }
+}
+
+/**
+ * Available overloads, with definitions elsewhere
+ *
+ * These functions are treated as ADL-extension points, the templates above
+ * call these functions without them having being pre-declared. This works
+ * because ADL lookup finds the definitions of these functions when you pass
+ * the relevant arguments
+ */
+int futexWakeImpl(
+ const Futex<std::atomic>* futex,
+ int count,
+ uint32_t wakeMask);
+FutexResult futexWaitImpl(
+ const Futex<std::atomic>* futex,
+ uint32_t expected,
+ std::chrono::system_clock::time_point const* absSystemTime,
+ std::chrono::steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask);
+
+int futexWakeImpl(
+ const Futex<EmulatedFutexAtomic>* futex,
+ int count,
+ uint32_t wakeMask);
+FutexResult futexWaitImpl(
+ const Futex<EmulatedFutexAtomic>* futex,
+ uint32_t expected,
+ std::chrono::system_clock::time_point const* absSystemTime,
+ std::chrono::steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask);
+
+template <typename Futex, typename Deadline>
+typename std::enable_if<Deadline::clock::is_steady, FutexResult>::type
+futexWaitImpl(
+ Futex* futex,
+ uint32_t expected,
+ Deadline const& deadline,
+ uint32_t waitMask) {
+ return futexWaitImpl(futex, expected, nullptr, &deadline, waitMask);
+}
+
+template <typename Futex, typename Deadline>
+typename std::enable_if<!Deadline::clock::is_steady, FutexResult>::type
+futexWaitImpl(
+ Futex* futex,
+ uint32_t expected,
+ Deadline const& deadline,
+ uint32_t waitMask) {
+ return futexWaitImpl(futex, expected, &deadline, nullptr, waitMask);
+}
+
+template <typename Futex>
+FutexResult
+futexWait(const Futex* futex, uint32_t expected, uint32_t waitMask) {
+ auto rv = futexWaitImpl(futex, expected, nullptr, nullptr, waitMask);
+ assert(rv != FutexResult::TIMEDOUT);
+ return rv;
+}
+
+template <typename Futex>
+int futexWake(const Futex* futex, int count, uint32_t wakeMask) {
+ return futexWakeImpl(futex, count, wakeMask);
+}
+
+template <typename Futex, class Clock, class Duration>
+FutexResult futexWaitUntil(
+ const Futex* futex,
+ uint32_t expected,
+ std::chrono::time_point<Clock, Duration> const& deadline,
+ uint32_t waitMask) {
+ using Target = typename std::conditional<
+ Clock::is_steady,
+ std::chrono::steady_clock,
+ std::chrono::system_clock>::type;
+ auto const converted = time_point_conv<Target>(deadline);
+ return converted == Target::time_point::max()
+ ? futexWaitImpl(futex, expected, nullptr, nullptr, waitMask)
+ : futexWaitImpl(futex, expected, converted, waitMask);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.cpp
new file mode 100644
index 00000000..62d6ea2b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.cpp
@@ -0,0 +1,263 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/detail/Futex.h>
+#include <folly/portability/SysSyscall.h>
+#include <stdint.h>
+#include <string.h>
+#include <array>
+#include <cerrno>
+
+#include <folly/synchronization/ParkingLot.h>
+
+#ifdef __linux__
+#include <linux/futex.h>
+#endif
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+using namespace std::chrono;
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+////////////////////////////////////////////////////
+// native implementation using the futex() syscall
+
+#ifdef __linux__
+
+/// Certain toolchains (like Android's) don't include the full futex API in
+/// their headers even though they support it. Make sure we have our constants
+/// even if the headers don't have them.
+#ifndef FUTEX_WAIT_BITSET
+#define FUTEX_WAIT_BITSET 9
+#endif
+#ifndef FUTEX_WAKE_BITSET
+#define FUTEX_WAKE_BITSET 10
+#endif
+#ifndef FUTEX_PRIVATE_FLAG
+#define FUTEX_PRIVATE_FLAG 128
+#endif
+#ifndef FUTEX_CLOCK_REALTIME
+#define FUTEX_CLOCK_REALTIME 256
+#endif
+
+int nativeFutexWake(const void* addr, int count, uint32_t wakeMask) {
+ long rv = syscall(
+ __NR_futex,
+ addr, /* addr1 */
+ FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, /* op */
+ count, /* val */
+ nullptr, /* timeout */
+ nullptr, /* addr2 */
+ wakeMask); /* val3 */
+
+ /* NOTE: we ignore errors on wake for the case of a futex
+ guarding its own destruction, similar to this
+ glibc bug with sem_post/sem_wait:
+ https://sourceware.org/bugzilla/show_bug.cgi?id=12674 */
+ if (rv < 0) {
+ return 0;
+ }
+ return static_cast<int>(rv);
+}
+
+template <class Clock>
+struct timespec timeSpecFromTimePoint(time_point<Clock> absTime) {
+ auto epoch = absTime.time_since_epoch();
+ if (epoch.count() < 0) {
+ // kernel timespec_valid requires non-negative seconds and nanos in [0,1G)
+ epoch = Clock::duration::zero();
+ }
+
+ // timespec-safe seconds and nanoseconds;
+ // chrono::{nano,}seconds are `long long int`
+ // whereas timespec uses smaller types
+ using time_t_seconds = duration<std::time_t, seconds::period>;
+ using long_nanos = duration<long int, nanoseconds::period>;
+
+ auto secs = duration_cast<time_t_seconds>(epoch);
+ auto nanos = duration_cast<long_nanos>(epoch - secs);
+ struct timespec result = {secs.count(), nanos.count()};
+ return result;
+}
+
+FutexResult nativeFutexWaitImpl(
+ const void* addr,
+ uint32_t expected,
+ system_clock::time_point const* absSystemTime,
+ steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask) {
+ assert(absSystemTime == nullptr || absSteadyTime == nullptr);
+
+ int op = FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG;
+ struct timespec ts;
+ struct timespec* timeout = nullptr;
+
+ if (absSystemTime != nullptr) {
+ op |= FUTEX_CLOCK_REALTIME;
+ ts = timeSpecFromTimePoint(*absSystemTime);
+ timeout = &ts;
+ } else if (absSteadyTime != nullptr) {
+ ts = timeSpecFromTimePoint(*absSteadyTime);
+ timeout = &ts;
+ }
+
+ // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET requires an absolute timeout
+ // value - http://locklessinc.com/articles/futex_cheat_sheet/
+ long rv = syscall(
+ __NR_futex,
+ addr, /* addr1 */
+ op, /* op */
+ expected, /* val */
+ timeout, /* timeout */
+ nullptr, /* addr2 */
+ waitMask); /* val3 */
+
+ if (rv == 0) {
+ return FutexResult::AWOKEN;
+ } else {
+ switch (errno) {
+ case ETIMEDOUT:
+ assert(timeout != nullptr);
+ return FutexResult::TIMEDOUT;
+ case EINTR:
+ return FutexResult::INTERRUPTED;
+ case EWOULDBLOCK:
+ return FutexResult::VALUE_CHANGED;
+ default:
+ assert(false);
+ // EINVAL, EACCESS, or EFAULT. EINVAL means there was an invalid
+ // op (should be impossible) or an invalid timeout (should have
+ // been sanitized by timeSpecFromTimePoint). EACCESS or EFAULT
+ // means *addr points to invalid memory, which is unlikely because
+ // the caller should have segfaulted already. We can either
+ // crash, or return a value that lets the process continue for
+ // a bit. We choose the latter. VALUE_CHANGED probably turns the
+ // caller into a spin lock.
+ return FutexResult::VALUE_CHANGED;
+ }
+ }
+}
+
+#endif // __linux__
+
+///////////////////////////////////////////////////////
+// compatibility implementation using standard C++ API
+
+using Lot = ParkingLot<uint32_t>;
+Lot parkingLot;
+
+int emulatedFutexWake(const void* addr, int count, uint32_t waitMask) {
+ int woken = 0;
+ parkingLot.unpark(addr, [&](const uint32_t& mask) {
+ if ((mask & waitMask) == 0) {
+ return UnparkControl::RetainContinue;
+ }
+ assert(count > 0);
+ count--;
+ woken++;
+ return count > 0 ? UnparkControl::RemoveContinue
+ : UnparkControl::RemoveBreak;
+ });
+ return woken;
+}
+
+template <typename F>
+FutexResult emulatedFutexWaitImpl(
+ F* futex,
+ uint32_t expected,
+ system_clock::time_point const* absSystemTime,
+ steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask) {
+ static_assert(
+ std::is_same<F, const Futex<std::atomic>>::value ||
+ std::is_same<F, const Futex<EmulatedFutexAtomic>>::value,
+ "Type F must be either Futex<std::atomic> or Futex<EmulatedFutexAtomic>");
+ ParkResult res;
+ if (absSystemTime) {
+ res = parkingLot.park_until(
+ futex,
+ waitMask,
+ [&] { return *futex == expected; },
+ [] {},
+ *absSystemTime);
+ } else if (absSteadyTime) {
+ res = parkingLot.park_until(
+ futex,
+ waitMask,
+ [&] { return *futex == expected; },
+ [] {},
+ *absSteadyTime);
+ } else {
+ res = parkingLot.park(
+ futex, waitMask, [&] { return *futex == expected; }, [] {});
+ }
+ switch (res) {
+ case ParkResult::Skip:
+ return FutexResult::VALUE_CHANGED;
+ case ParkResult::Unpark:
+ return FutexResult::AWOKEN;
+ case ParkResult::Timeout:
+ return FutexResult::TIMEDOUT;
+ }
+
+ return FutexResult::INTERRUPTED;
+}
+
+} // namespace
+
+/////////////////////////////////
+// Futex<> overloads
+
+int futexWakeImpl(
+ const Futex<std::atomic>* futex,
+ int count,
+ uint32_t wakeMask) {
+#ifdef __linux__
+ return nativeFutexWake(futex, count, wakeMask);
+#else
+ return emulatedFutexWake(futex, count, wakeMask);
+#endif
+}
+
+int futexWakeImpl(
+ const Futex<EmulatedFutexAtomic>* futex,
+ int count,
+ uint32_t wakeMask) {
+ return emulatedFutexWake(futex, count, wakeMask);
+}
+
+FutexResult futexWaitImpl(
+ const Futex<std::atomic>* futex,
+ uint32_t expected,
+ system_clock::time_point const* absSystemTime,
+ steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask) {
+#ifdef __linux__
+ return nativeFutexWaitImpl(
+ futex, expected, absSystemTime, absSteadyTime, waitMask);
+#else
+ return emulatedFutexWaitImpl(
+ futex, expected, absSystemTime, absSteadyTime, waitMask);
+#endif
+}
+
+FutexResult futexWaitImpl(
+ const Futex<EmulatedFutexAtomic>* futex,
+ uint32_t expected,
+ system_clock::time_point const* absSystemTime,
+ steady_clock::time_point const* absSteadyTime,
+ uint32_t waitMask) {
+ return emulatedFutexWaitImpl(
+ futex, expected, absSystemTime, absSteadyTime, waitMask);
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.h b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.h
new file mode 100644
index 00000000..987a1b89
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/detail/Futex.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cassert>
+#include <chrono>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+namespace folly {
+namespace detail {
+
+enum class FutexResult {
+ VALUE_CHANGED, /* futex value didn't match expected */
+ AWOKEN, /* wakeup by matching futex wake, or spurious wakeup */
+ INTERRUPTED, /* wakeup by interrupting signal */
+ TIMEDOUT, /* wakeup by expiring deadline */
+};
+
+/**
+ * Futex is an atomic 32 bit unsigned integer that provides access to the
+ * futex() syscall on that value. It is templated in such a way that it
+ * can interact properly with DeterministicSchedule testing.
+ *
+ * If you don't know how to use futex(), you probably shouldn't be using
+ * this class. Even if you do know how, you should have a good reason
+ * (and benchmarks to back you up).
+ *
+ * Because of the semantics of the futex syscall, the futex family of
+ * functions are available as free functions rather than member functions
+ */
+template <template <typename> class Atom = std::atomic>
+using Futex = Atom<std::uint32_t>;
+
+/**
+ * Puts the thread to sleep if this->load() == expected. Returns true when
+ * it is returning because it has consumed a wake() event, false for any
+ * other return (signal, this->load() != expected, or spurious wakeup).
+ */
+template <typename Futex>
+FutexResult
+futexWait(const Futex* futex, uint32_t expected, uint32_t waitMask = -1);
+
+/**
+ * Similar to futexWait but also accepts a deadline until when the wait call
+ * may block.
+ *
+ * Optimal clock types: std::chrono::system_clock, std::chrono::steady_clock.
+ * NOTE: On some systems steady_clock is just an alias for system_clock,
+ * and is not actually steady.
+ *
+ * For any other clock type, now() will be invoked twice.
+ */
+template <typename Futex, class Clock, class Duration>
+FutexResult futexWaitUntil(
+ const Futex* futex,
+ uint32_t expected,
+ std::chrono::time_point<Clock, Duration> const& deadline,
+ uint32_t waitMask = -1);
+
+/**
+ * Wakes up to count waiters where (waitMask & wakeMask) != 0, returning the
+ * number of awoken threads, or -1 if an error occurred. Note that when
+ * constructing a concurrency primitive that can guard its own destruction, it
+ * is likely that you will want to ignore EINVAL here (as well as making sure
+ * that you never touch the object after performing the memory store that is
+ * the linearization point for unlock or control handoff). See
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=13690
+ */
+template <typename Futex>
+int futexWake(
+ const Futex* futex,
+ int count = std::numeric_limits<int>::max(),
+ uint32_t wakeMask = -1);
+
+/** A std::atomic subclass that can be used to force Futex to emulate
+ * the underlying futex() syscall. This is primarily useful to test or
+ * benchmark the emulated implementation on systems that don't need it. */
+template <typename T>
+struct EmulatedFutexAtomic : public std::atomic<T> {
+ EmulatedFutexAtomic() noexcept = default;
+ constexpr /* implicit */ EmulatedFutexAtomic(T init) noexcept
+ : std::atomic<T>(init) {}
+ // It doesn't copy or move
+ EmulatedFutexAtomic(EmulatedFutexAtomic&& rhs) = delete;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/detail/Futex-inl.h>
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/functional/Invoke.h b/storage/rocksdb/rocksdb/third-party/folly/folly/functional/Invoke.h
new file mode 100644
index 00000000..67c55284
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/functional/Invoke.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Traits.h>
+
+#include <functional>
+#include <type_traits>
+
+namespace folly {
+namespace invoke_detail {
+template <typename F, typename... Args>
+using invoke_result_ = decltype(std::declval<F>()(std::declval<Args>()...));
+
+template <typename Void, typename F, typename... Args>
+struct is_invocable : std::false_type {};
+
+template <typename F, typename... Args>
+struct is_invocable<void_t<invoke_result_<F, Args...>>, F, Args...>
+ : std::true_type {};
+
+template <typename Void, typename R, typename F, typename... Args>
+struct is_invocable_r : std::false_type {};
+
+template <typename R, typename F, typename... Args>
+struct is_invocable_r<void_t<invoke_result_<F, Args...>>, R, F, Args...>
+ : std::is_convertible<invoke_result_<F, Args...>, R> {};
+} // namespace invoke_detail
+
+// mimic: std::is_invocable, C++17
+template <typename F, typename... Args>
+struct is_invocable : invoke_detail::is_invocable<void, F, Args...> {};
+
+// mimic: std::is_invocable_r, C++17
+template <typename R, typename F, typename... Args>
+struct is_invocable_r : invoke_detail::is_invocable_r<void, R, F, Args...> {};
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/hash/Hash.h b/storage/rocksdb/rocksdb/third-party/folly/folly/hash/Hash.h
new file mode 100644
index 00000000..ca221e5c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/hash/Hash.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdint>
+
+namespace folly {
+namespace hash {
+
+/*
+ * Thomas Wang 64 bit mix hash function
+ */
+
+inline uint64_t twang_mix64(uint64_t key) noexcept {
+ key = (~key) + (key << 21); // key *= (1 << 21) - 1; key -= 1;
+ key = key ^ (key >> 24);
+ key = key + (key << 3) + (key << 8); // key *= 1 + (1 << 3) + (1 << 8)
+ key = key ^ (key >> 14);
+ key = key + (key << 2) + (key << 4); // key *= 1 + (1 << 2) + (1 << 4)
+ key = key ^ (key >> 28);
+ key = key + (key << 31); // key *= 1 + (1 << 31)
+ return key;
+}
+
+} // namespace hash
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Align.h b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Align.h
new file mode 100644
index 00000000..5257e2f6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Align.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdint>
+
+namespace folly {
+
+// Memory locations within the same cache line are subject to destructive
+// interference, also known as false sharing, which is when concurrent
+// accesses to these different memory locations from different cores, where at
+// least one of the concurrent accesses is or involves a store operation,
+// induce contention and harm performance.
+//
+// Microbenchmarks indicate that pairs of cache lines also see destructive
+// interference under heavy use of atomic operations, as observed for atomic
+// increment on Sandy Bridge.
+//
+// We assume a cache line size of 64, so we use a cache line pair size of 128
+// to avoid destructive interference.
+//
+// mimic: std::hardware_destructive_interference_size, C++17
+constexpr std::size_t hardware_destructive_interference_size = 128;
+
+// Memory locations within the same cache line are subject to constructive
+// interference, also known as true sharing, which is when accesses to some
+// memory locations induce all memory locations within the same cache line to
+// be cached, benefiting subsequent accesses to different memory locations
+// within the same cache line and heping performance.
+//
+// mimic: std::hardware_constructive_interference_size, C++17
+constexpr std::size_t hardware_constructive_interference_size = 64;
+
+} // namespace folly
+
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Bits.h b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Bits.h
new file mode 100644
index 00000000..f3abeffc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Bits.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Traits.h>
+
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace folly {
+
+template <
+ typename To,
+ typename From,
+ _t<std::enable_if<
+ sizeof(From) == sizeof(To) && std::is_trivial<To>::value &&
+ is_trivially_copyable<From>::value,
+ int>> = 0>
+To bit_cast(const From& src) noexcept {
+ To to;
+ std::memcpy(&to, &src, sizeof(From));
+ return to;
+}
+
+} // namespace folly
+
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Launder.h b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Launder.h
new file mode 100644
index 00000000..9247e3e3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/lang/Launder.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <new>
+
+#include <folly/Portability.h>
+
+/***
+ * include or backport:
+ * * std::launder
+ */
+
+namespace folly {
+
+/**
+ * Approximate backport from C++17 of std::launder. It should be `constexpr`
+ * but that can't be done without specific support from the compiler.
+ */
+template <typename T>
+FOLLY_NODISCARD inline T* launder(T* in) noexcept {
+#if FOLLY_HAS_BUILTIN(__builtin_launder) || __GNUC__ >= 7
+ // The builtin has no unwanted side-effects.
+ return __builtin_launder(in);
+#elif __GNUC__
+ // This inline assembler block declares that `in` is an input and an output,
+ // so the compiler has to assume that it has been changed inside the block.
+ __asm__("" : "+r"(in));
+ return in;
+#elif defined(_WIN32)
+ // MSVC does not currently have optimizations around const members of structs.
+ // _ReadWriteBarrier() will prevent compiler reordering memory accesses.
+ _ReadWriteBarrier();
+ return in;
+#else
+ static_assert(
+ false, "folly::launder is not implemented for this environment");
+#endif
+}
+
+/* The standard explicitly forbids laundering these */
+void launder(void*) = delete;
+void launder(void const*) = delete;
+void launder(void volatile*) = delete;
+void launder(void const volatile*) = delete;
+template <typename T, typename... Args>
+void launder(T (*)(Args...)) = delete;
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/portability/Asm.h b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/Asm.h
new file mode 100644
index 00000000..cca16858
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/Asm.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Portability.h>
+
+#include <cstdint>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+namespace folly {
+inline void asm_volatile_pause() {
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+ ::_mm_pause();
+#elif defined(__i386__) || FOLLY_X64
+ asm volatile("pause");
+#elif FOLLY_AARCH64 || defined(__arm__)
+ asm volatile("yield");
+#elif FOLLY_PPC64
+ asm volatile("or 27,27,27");
+#endif
+}
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysSyscall.h b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysSyscall.h
new file mode 100644
index 00000000..fa969deb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysSyscall.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef _WIN32
+#include <sys/syscall.h>
+#endif
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysTypes.h b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysTypes.h
new file mode 100644
index 00000000..7beb68cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/portability/SysTypes.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sys/types.h>
+
+#ifdef _WIN32
+#include <basetsd.h> // @manual
+
+#define HAVE_MODE_T 1
+
+// This is a massive pain to have be an `int` due to the pthread implementation
+// we support, but it's far more compatible with the rest of the windows world
+// as an `int` than it would be as a `void*`
+using pid_t = int;
+// This isn't actually supposed to be defined here, but it's the most
+// appropriate place without defining a portability header for stdint.h
+// with just this single typedef.
+using ssize_t = SSIZE_T;
+// The Windows headers don't define this anywhere, nor do any of the libs
+// that Folly depends on, so define it here.
+using mode_t = unsigned short;
+#endif
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification-inl.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification-inl.h
new file mode 100644
index 00000000..c0b143d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification-inl.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/detail/Futex.h>
+#include <folly/synchronization/ParkingLot.h>
+
+#include <condition_variable>
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+namespace atomic_notification {
+/**
+ * We use Futex<std::atomic> as the alias that has the lowest performance
+ * overhead with respect to atomic notifications. Assert that
+ * atomic_uint_fast_wait_t is the same as Futex<std::atomic>
+ */
+static_assert(std::is_same<atomic_uint_fast_wait_t, Futex<std::atomic>>{}, "");
+
+/**
+ * Implementation and specializations for the atomic_wait() family of
+ * functions
+ */
+inline std::cv_status toCvStatus(FutexResult result) {
+ return (result == FutexResult::TIMEDOUT) ? std::cv_status::timeout
+ : std::cv_status::no_timeout;
+}
+inline std::cv_status toCvStatus(ParkResult result) {
+ return (result == ParkResult::Timeout) ? std::cv_status::timeout
+ : std::cv_status::no_timeout;
+}
+
+// ParkingLot instantiation for futex management
+extern ParkingLot<std::uint32_t> parkingLot;
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_wait_impl(
+ const Atom<std::uint32_t, Args...>* atomic,
+ std::uint32_t expected) {
+ futexWait(atomic, expected);
+ return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_wait_impl(const Atom<Integer, Args...>* atomic, Integer expected) {
+ static_assert(!std::is_same<Integer, std::uint32_t>{}, "");
+ parkingLot.park(
+ atomic, -1, [&] { return atomic->load() == expected; }, [] {});
+}
+
+template <
+ template <typename...> class Atom,
+ typename... Args,
+ typename Clock,
+ typename Duration>
+std::cv_status atomic_wait_until_impl(
+ const Atom<std::uint32_t, Args...>* atomic,
+ std::uint32_t expected,
+ const std::chrono::time_point<Clock, Duration>& deadline) {
+ return toCvStatus(futexWaitUntil(atomic, expected, deadline));
+}
+
+template <
+ template <typename...> class Atom,
+ typename Integer,
+ typename... Args,
+ typename Clock,
+ typename Duration>
+std::cv_status atomic_wait_until_impl(
+ const Atom<Integer, Args...>* atomic,
+ Integer expected,
+ const std::chrono::time_point<Clock, Duration>& deadline) {
+ static_assert(!std::is_same<Integer, std::uint32_t>{}, "");
+ return toCvStatus(parkingLot.park_until(
+ atomic, -1, [&] { return atomic->load() == expected; }, [] {}, deadline));
+}
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_notify_one_impl(const Atom<std::uint32_t, Args...>* atomic) {
+ futexWake(atomic, 1);
+ return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_notify_one_impl(const Atom<Integer, Args...>* atomic) {
+ static_assert(!std::is_same<Integer, std::uint32_t>{}, "");
+ parkingLot.unpark(atomic, [&](std::uint32_t data) {
+ assert(data == std::numeric_limits<std::uint32_t>::max());
+ return UnparkControl::RemoveBreak;
+ });
+}
+
+template <template <typename...> class Atom, typename... Args>
+void atomic_notify_all_impl(const Atom<std::uint32_t, Args...>* atomic) {
+ futexWake(atomic);
+ return;
+}
+
+template <template <typename...> class Atom, typename Integer, typename... Args>
+void atomic_notify_all_impl(const Atom<Integer, Args...>* atomic) {
+ static_assert(!std::is_same<Integer, std::uint32_t>{}, "");
+ parkingLot.unpark(atomic, [&](std::uint32_t data) {
+ assert(data == std::numeric_limits<std::uint32_t>::max());
+ return UnparkControl::RemoveContinue;
+ });
+}
+} // namespace atomic_notification
+} // namespace detail
+
+template <typename Integer>
+void atomic_wait(const std::atomic<Integer>* atomic, Integer expected) {
+ detail::atomic_notification::atomic_wait_impl(atomic, expected);
+}
+
+template <typename Integer, typename Clock, typename Duration>
+std::cv_status atomic_wait_until(
+ const std::atomic<Integer>* atomic,
+ Integer expected,
+ const std::chrono::time_point<Clock, Duration>& deadline) {
+ return detail::atomic_notification::atomic_wait_until_impl(
+ atomic, expected, deadline);
+}
+
+template <typename Integer>
+void atomic_notify_one(const std::atomic<Integer>* atomic) {
+ detail::atomic_notification::atomic_notify_one_impl(atomic);
+}
+
+template <typename Integer>
+void atomic_notify_all(const std::atomic<Integer>* atomic) {
+ detail::atomic_notification::atomic_notify_all_impl(atomic);
+}
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.cpp
new file mode 100644
index 00000000..b50875cd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/AtomicNotification.h>
+
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+namespace atomic_notification {
+
+// ParkingLot instance used for the atomic_wait() family of functions
+//
+// This has been defined as a static object (as opposed to allocated to avoid
+// destruction order problems) because of possible uses coming from
+// allocation-sensitive contexts.
+ParkingLot<std::uint32_t> parkingLot;
+
+} // namespace atomic_notification
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.h
new file mode 100644
index 00000000..af87852c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicNotification.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <condition_variable>
+
+namespace folly {
+
+/**
+ * The behavior of the atomic_wait() family of functions is semantically
+ * identical to futex(). Correspondingly, calling atomic_notify_one(),
+ * atomic_notify_all() is identical to futexWake() with 1 and
+ * std::numeric_limits<int>::max() respectively
+ *
+ * The difference here compared to the futex API above is that it works with
+ * all types of atomic widths. When a 32 bit atomic integer is used, the
+ * implementation falls back to using futex() if possible, and the
+ * compatibility implementation for non-linux systems otherwise. For all
+ * other integer widths, the compatibility implementation is used
+ *
+ * The templating of this API is changed from the standard in the following
+ * ways
+ *
+ * - At the time of writing, libstdc++'s implementation of std::atomic<> does
+ * not include the value_type alias. So we rely on the atomic type being a
+ * template class such that the first type is the underlying value type
+ * - The Atom parameter allows this API to be compatible with
+ * DeterministicSchedule testing.
+ * - atomic_wait_until() does not exist in the linked paper, the version here
+ * is identical to futexWaitUntil() and returns std::cv_status
+ */
+// mimic: std::atomic_wait, p1135r0
+template <typename Integer>
+void atomic_wait(const std::atomic<Integer>* atomic, Integer expected);
+template <typename Integer, typename Clock, typename Duration>
+std::cv_status atomic_wait_until(
+ const std::atomic<Integer>* atomic,
+ Integer expected,
+ const std::chrono::time_point<Clock, Duration>& deadline);
+
+// mimic: std::atomic_notify_one, p1135r0
+template <typename Integer>
+void atomic_notify_one(const std::atomic<Integer>* atomic);
+// mimic: std::atomic_notify_all, p1135r0
+template <typename Integer>
+void atomic_notify_all(const std::atomic<Integer>* atomic);
+
+// mimic: std::atomic_uint_fast_wait_t, p1135r0
+using atomic_uint_fast_wait_t = std::atomic<std::uint32_t>;
+
+} // namespace folly
+
+#include <folly/synchronization/AtomicNotification-inl.h>
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil-inl.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil-inl.h
new file mode 100644
index 00000000..4c10d845
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil-inl.h
@@ -0,0 +1,260 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Portability.h>
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <tuple>
+#include <type_traits>
+
+#if _WIN32
+#include <intrin.h>
+#endif
+
+namespace folly {
+namespace detail {
+
+// TODO: Remove the non-default implementations when both gcc and clang
+// can recognize single bit set/reset patterns and compile them down to locked
+// bts and btr instructions.
+//
+// Currently, at the time of writing it seems like gcc7 and greater can make
+// this optimization and clang cannot - https://gcc.godbolt.org/z/Q83rxX
+
+template <typename Atomic>
+bool atomic_fetch_set_default(
+ Atomic& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ using Integer = decltype(atomic.load());
+ auto mask = Integer{0b1} << static_cast<Integer>(bit);
+ return (atomic.fetch_or(mask, order) & mask);
+}
+
+template <typename Atomic>
+bool atomic_fetch_reset_default(
+ Atomic& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ using Integer = decltype(atomic.load());
+ auto mask = Integer{0b1} << static_cast<Integer>(bit);
+ return (atomic.fetch_and(~mask, order) & mask);
+}
+
+/**
+ * A simple trait to determine if the given type is an instantiation of
+ * std::atomic
+ */
+template <typename T>
+struct is_atomic : std::false_type {};
+template <typename Integer>
+struct is_atomic<std::atomic<Integer>> : std::true_type {};
+
+#if FOLLY_X64
+
+#if _MSC_VER
+
+template <typename Integer>
+inline bool atomic_fetch_set_x86(
+ std::atomic<Integer>& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ static_assert(alignof(std::atomic<Integer>) == alignof(Integer), "");
+ static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer), "");
+ assert(atomic.is_lock_free());
+
+ if /* constexpr */ (sizeof(Integer) == 4) {
+ return _interlockedbittestandset(
+ reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
+ } else if /* constexpr */ (sizeof(Integer) == 8) {
+ return _interlockedbittestandset64(
+ reinterpret_cast<volatile long long*>(&atomic),
+ static_cast<long long>(bit));
+ } else {
+ assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
+ return atomic_fetch_set_default(atomic, bit, order);
+ }
+}
+
+template <typename Atomic>
+inline bool
+atomic_fetch_set_x86(Atomic& atomic, std::size_t bit, std::memory_order order) {
+ static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{}, "");
+ static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{}, "");
+ return atomic_fetch_set_default(atomic, bit, order);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_reset_x86(
+ std::atomic<Integer>& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ static_assert(alignof(std::atomic<Integer>) == alignof(Integer), "");
+ static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer), "");
+ assert(atomic.is_lock_free());
+
+ if /* constexpr */ (sizeof(Integer) == 4) {
+ return _interlockedbittestandreset(
+ reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
+ } else if /* constexpr */ (sizeof(Integer) == 8) {
+ return _interlockedbittestandreset64(
+ reinterpret_cast<volatile long long*>(&atomic),
+ static_cast<long long>(bit));
+ } else {
+ assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
+ return atomic_fetch_reset_default(atomic, bit, order);
+ }
+}
+
+template <typename Atomic>
+inline bool
+atomic_fetch_reset_x86(Atomic& atomic, std::size_t bit, std::memory_order mo) {
+ static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{}, "");
+ static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{}, "");
+ return atomic_fetch_reset_default(atomic, bit, mo);
+}
+
+#else
+
+template <typename Integer>
+inline bool atomic_fetch_set_x86(
+ std::atomic<Integer>& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ auto previous = false;
+
+ if /* constexpr */ (sizeof(Integer) == 2) {
+ auto pointer = reinterpret_cast<std::uint16_t*>(&atomic);
+ asm volatile("lock; btsw %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint16_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else if /* constexpr */ (sizeof(Integer) == 4) {
+ auto pointer = reinterpret_cast<std::uint32_t*>(&atomic);
+ asm volatile("lock; btsl %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint32_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else if /* constexpr */ (sizeof(Integer) == 8) {
+ auto pointer = reinterpret_cast<std::uint64_t*>(&atomic);
+ asm volatile("lock; btsq %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint64_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else {
+ assert(sizeof(Integer) == 1);
+ return atomic_fetch_set_default(atomic, bit, order);
+ }
+
+ return previous;
+}
+
+template <typename Atomic>
+inline bool
+atomic_fetch_set_x86(Atomic& atomic, std::size_t bit, std::memory_order order) {
+ static_assert(!is_atomic<Atomic>::value, "");
+ return atomic_fetch_set_default(atomic, bit, order);
+}
+
+template <typename Integer>
+inline bool atomic_fetch_reset_x86(
+ std::atomic<Integer>& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ auto previous = false;
+
+ if /* constexpr */ (sizeof(Integer) == 2) {
+ auto pointer = reinterpret_cast<std::uint16_t*>(&atomic);
+ asm volatile("lock; btrw %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint16_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else if /* constexpr */ (sizeof(Integer) == 4) {
+ auto pointer = reinterpret_cast<std::uint32_t*>(&atomic);
+ asm volatile("lock; btrl %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint32_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else if /* constexpr */ (sizeof(Integer) == 8) {
+ auto pointer = reinterpret_cast<std::uint64_t*>(&atomic);
+ asm volatile("lock; btrq %1, (%2); setc %0"
+ : "=r"(previous)
+ : "ri"(static_cast<std::uint64_t>(bit)), "r"(pointer)
+ : "memory", "flags");
+ } else {
+ assert(sizeof(Integer) == 1);
+ return atomic_fetch_reset_default(atomic, bit, order);
+ }
+
+ return previous;
+}
+
+template <typename Atomic>
+bool atomic_fetch_reset_x86(
+ Atomic& atomic,
+ std::size_t bit,
+ std::memory_order order) {
+ static_assert(!is_atomic<Atomic>::value, "");
+ return atomic_fetch_reset_default(atomic, bit, order);
+}
+
+#endif
+
+#else
+
+template <typename Atomic>
+bool atomic_fetch_set_x86(Atomic&, std::size_t, std::memory_order) noexcept {
+ // This should never be called on non x86_64 platforms.
+ std::terminate();
+}
+template <typename Atomic>
+bool atomic_fetch_reset_x86(Atomic&, std::size_t, std::memory_order) noexcept {
+ // This should never be called on non x86_64 platforms.
+ std::terminate();
+}
+
+#endif
+
+} // namespace detail
+
+template <typename Atomic>
+bool atomic_fetch_set(Atomic& atomic, std::size_t bit, std::memory_order mo) {
+ using Integer = decltype(atomic.load());
+ static_assert(std::is_unsigned<Integer>{}, "");
+ static_assert(!std::is_const<Atomic>{}, "");
+ assert(bit < (sizeof(Integer) * 8));
+
+ // do the optimized thing on x86 builds. Also, some versions of TSAN do not
+ // properly instrument the inline assembly, so avoid it when TSAN is enabled
+ if (folly::kIsArchAmd64 && !folly::kIsSanitizeThread) {
+ return detail::atomic_fetch_set_x86(atomic, bit, mo);
+ } else {
+ // otherwise default to the default implementation using fetch_or()
+ return detail::atomic_fetch_set_default(atomic, bit, mo);
+ }
+}
+
+template <typename Atomic>
+bool atomic_fetch_reset(Atomic& atomic, std::size_t bit, std::memory_order mo) {
+ using Integer = decltype(atomic.load());
+ static_assert(std::is_unsigned<Integer>{}, "");
+ static_assert(!std::is_const<Atomic>{}, "");
+ assert(bit < (sizeof(Integer) * 8));
+
+ // do the optimized thing on x86 builds. Also, some versions of TSAN do not
+ // properly instrument the inline assembly, so avoid it when TSAN is enabled
+ if (folly::kIsArchAmd64 && !folly::kIsSanitizeThread) {
+ return detail::atomic_fetch_reset_x86(atomic, bit, mo);
+ } else {
+ // otherwise default to the default implementation using fetch_and()
+ return detail::atomic_fetch_reset_default(atomic, bit, mo);
+ }
+}
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil.h
new file mode 100644
index 00000000..95bcf73c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/AtomicUtil.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <cstdint>
+
+namespace folly {
+
+/**
+ * Sets a bit at the given index in the binary representation of the integer
+ * to 1. Returns the previous value of the bit, so true if the bit was not
+ * changed, false otherwise
+ *
+ * On some architectures, using this is more efficient than the corresponding
+ * std::atomic::fetch_or() with a mask. For example to set the first (least
+ * significant) bit of an integer, you could do atomic.fetch_or(0b1)
+ *
+ * The efficiency win is only visible in x86 (yet) and comes from the
+ * implementation using the x86 bts instruction when possible.
+ *
+ * When something other than std::atomic is passed, the implementation assumed
+ * incompatibility with this interface and calls Atomic::fetch_or()
+ */
+template <typename Atomic>
+bool atomic_fetch_set(
+ Atomic& atomic,
+ std::size_t bit,
+ std::memory_order order = std::memory_order_seq_cst);
+
+/**
+ * Resets a bit at the given index in the binary representation of the integer
+ * to 0. Returns the previous value of the bit, so true if the bit was
+ * changed, false otherwise
+ *
+ * This follows the same underlying principle and implementation as
+ * fetch_set(). Using the optimized implementation when possible and falling
+ * back to std::atomic::fetch_and() when in debug mode or in an architecture
+ * where an optimization is not possible
+ */
+template <typename Atomic>
+bool atomic_fetch_reset(
+ Atomic& atomic,
+ std::size_t bit,
+ std::memory_order order = std::memory_order_seq_cst);
+
+} // namespace folly
+
+#include <folly/synchronization/AtomicUtil-inl.h>
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/Baton.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/Baton.h
new file mode 100644
index 00000000..6a6403de
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/Baton.h
@@ -0,0 +1,327 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <atomic>
+#include <thread>
+
+#include <folly/detail/Futex.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/WaitOptions.h>
+#include <folly/synchronization/detail/Spin.h>
+
+namespace folly {
+
+/// A Baton allows a thread to block once and be awoken. Captures a
+/// single handoff, and during its lifecycle (from construction/reset
+/// to destruction/reset) a baton must either be post()ed and wait()ed
+/// exactly once each, or not at all.
+///
+/// Baton includes no internal padding, and is only 4 bytes in size.
+/// Any alignment or padding to avoid false sharing is up to the user.
+///
+/// This is basically a stripped-down semaphore that supports only a
+/// single call to sem_post and a single call to sem_wait.
+///
+/// The non-blocking version (MayBlock == false) provides more speed
+/// by using only load acquire and store release operations in the
+/// critical path, at the cost of disallowing blocking.
+///
+/// The current posix semaphore sem_t isn't too bad, but this provides
+/// more a bit more speed, inlining, smaller size, a guarantee that
+/// the implementation won't change, and compatibility with
+/// DeterministicSchedule. By having a much more restrictive
+/// lifecycle we can also add a bunch of assertions that can help to
+/// catch race conditions ahead of time.
+template <bool MayBlock = true, template <typename> class Atom = std::atomic>
+class Baton {
+ public:
+ static constexpr WaitOptions wait_options() {
+ return {};
+ }
+
+ constexpr Baton() noexcept : state_(INIT) {}
+
+ Baton(Baton const&) = delete;
+ Baton& operator=(Baton const&) = delete;
+
+ /// It is an error to destroy a Baton on which a thread is currently
+ /// wait()ing. In practice this means that the waiter usually takes
+ /// responsibility for destroying the Baton.
+ ~Baton() noexcept {
+ // The docblock for this function says that it can't be called when
+ // there is a concurrent waiter. We assume a strong version of this
+ // requirement in which the caller must _know_ that this is true, they
+ // are not allowed to be merely lucky. If two threads are involved,
+ // the destroying thread must actually have synchronized with the
+ // waiting thread after wait() returned. To convey causality the the
+ // waiting thread must have used release semantics and the destroying
+ // thread must have used acquire semantics for that communication,
+ // so we are guaranteed to see the post-wait() value of state_,
+ // which cannot be WAITING.
+ //
+ // Note that since we only care about a single memory location,
+ // the only two plausible memory orders here are relaxed and seq_cst.
+ assert(state_.load(std::memory_order_relaxed) != WAITING);
+ }
+
+ bool ready() const noexcept {
+ auto s = state_.load(std::memory_order_acquire);
+ assert(s == INIT || s == EARLY_DELIVERY);
+ return (s == EARLY_DELIVERY);
+ }
+
+ /// Equivalent to destroying the Baton and creating a new one. It is
+ /// a bug to call this while there is a waiting thread, so in practice
+ /// the waiter will be the one that resets the baton.
+ void reset() noexcept {
+ // See ~Baton for a discussion about why relaxed is okay here
+ assert(state_.load(std::memory_order_relaxed) != WAITING);
+
+ // We use a similar argument to justify the use of a relaxed store
+ // here. Since both wait() and post() are required to be called
+ // only once per lifetime, no thread can actually call those methods
+ // correctly after a reset() unless it synchronizes with the thread
+ // that performed the reset(). If a post() or wait() on another thread
+ // didn't synchronize, then regardless of what operation we performed
+ // here there would be a race on proper use of the Baton's spec
+ // (although not on any particular load and store). Put another way,
+ // we don't need to synchronize here because anybody that might rely
+ // on such synchronization is required by the baton rules to perform
+ // an additional synchronization that has the desired effect anyway.
+ //
+ // There is actually a similar argument to be made about the
+ // constructor, in which the fenceless constructor initialization
+ // of state_ is piggybacked on whatever synchronization mechanism
+ // distributes knowledge of the Baton's existence
+ state_.store(INIT, std::memory_order_relaxed);
+ }
+
+ /// Causes wait() to wake up. For each lifetime of a Baton (where a
+ /// lifetime starts at construction or reset() and ends at
+ /// destruction or reset()) there can be at most one call to post(),
+ /// in the single poster version. Any thread may call post().
+ void post() noexcept {
+ if (!MayBlock) {
+ /// Spin-only version
+ ///
+ assert(
+ ((1 << state_.load(std::memory_order_relaxed)) &
+ ((1 << INIT) | (1 << EARLY_DELIVERY))) != 0);
+ state_.store(EARLY_DELIVERY, std::memory_order_release);
+ return;
+ }
+
+ /// May-block versions
+ ///
+ uint32_t before = state_.load(std::memory_order_acquire);
+
+ assert(before == INIT || before == WAITING || before == TIMED_OUT);
+
+ if (before == INIT &&
+ state_.compare_exchange_strong(
+ before,
+ EARLY_DELIVERY,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
+ return;
+ }
+
+ assert(before == WAITING || before == TIMED_OUT);
+
+ if (before == TIMED_OUT) {
+ return;
+ }
+
+ assert(before == WAITING);
+ state_.store(LATE_DELIVERY, std::memory_order_release);
+ detail::futexWake(&state_, 1);
+ }
+
+ /// Waits until post() has been called in the current Baton lifetime.
+ /// May be called at most once during a Baton lifetime (construction
+ /// |reset until destruction|reset). If post is called before wait in
+ /// the current lifetime then this method returns immediately.
+ ///
+ /// The restriction that there can be at most one wait() per lifetime
+ /// could be relaxed somewhat without any perf or size regressions,
+ /// but by making this condition very restrictive we can provide better
+ /// checking in debug builds.
+ void wait(const WaitOptions& opt = wait_options()) noexcept {
+ if (try_wait()) {
+ return;
+ }
+
+ auto const deadline = std::chrono::steady_clock::time_point::max();
+ tryWaitSlow(deadline, opt);
+ }
+
+ /// Similar to wait, but doesn't block the thread if it hasn't been posted.
+ ///
+ /// try_wait has the following semantics:
+ /// - It is ok to call try_wait any number times on the same baton until
+ /// try_wait reports that the baton has been posted.
+ /// - It is ok to call timed_wait or wait on the same baton if try_wait
+ /// reports that baton hasn't been posted.
+ /// - If try_wait indicates that the baton has been posted, it is invalid to
+ /// call wait, try_wait or timed_wait on the same baton without resetting
+ ///
+ /// @return true if baton has been posted, false othewise
+ bool try_wait() const noexcept {
+ return ready();
+ }
+
+ /// Similar to wait, but with a timeout. The thread is unblocked if the
+ /// timeout expires.
+ /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
+ /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
+ /// words, after try_wait_for the caller can't invoke
+ /// wait/try_wait/try_wait_for/try_wait_until
+ /// again on the same baton without resetting it.
+ ///
+ /// @param timeout Time until which the thread can block
+ /// @return true if the baton was posted to before timeout,
+ /// false otherwise
+ template <typename Rep, typename Period>
+ bool try_wait_for(
+ const std::chrono::duration<Rep, Period>& timeout,
+ const WaitOptions& opt = wait_options()) noexcept {
+ if (try_wait()) {
+ return true;
+ }
+
+ auto const deadline = std::chrono::steady_clock::now() + timeout;
+ return tryWaitSlow(deadline, opt);
+ }
+
+ /// Similar to wait, but with a deadline. The thread is unblocked if the
+ /// deadline expires.
+ /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
+ /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
+ /// words, after try_wait_until the caller can't invoke
+ /// wait/try_wait/try_wait_for/try_wait_until
+ /// again on the same baton without resetting it.
+ ///
+ /// @param deadline Time until which the thread can block
+ /// @return true if the baton was posted to before deadline,
+ /// false otherwise
+ template <typename Clock, typename Duration>
+ bool try_wait_until(
+ const std::chrono::time_point<Clock, Duration>& deadline,
+ const WaitOptions& opt = wait_options()) noexcept {
+ if (try_wait()) {
+ return true;
+ }
+
+ return tryWaitSlow(deadline, opt);
+ }
+
+ /// Alias to try_wait_for. Deprecated.
+ template <typename Rep, typename Period>
+ bool timed_wait(
+ const std::chrono::duration<Rep, Period>& timeout) noexcept {
+ return try_wait_for(timeout);
+ }
+
+ /// Alias to try_wait_until. Deprecated.
+ template <typename Clock, typename Duration>
+ bool timed_wait(
+ const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+ return try_wait_until(deadline);
+ }
+
+ private:
+ enum State : uint32_t {
+ INIT = 0,
+ EARLY_DELIVERY = 1,
+ WAITING = 2,
+ LATE_DELIVERY = 3,
+ TIMED_OUT = 4,
+ };
+
+ template <typename Clock, typename Duration>
+ bool tryWaitSlow(
+ const std::chrono::time_point<Clock, Duration>& deadline,
+ const WaitOptions& opt) noexcept {
+ switch (detail::spin_pause_until(deadline, opt, [=] { return ready(); })) {
+ case detail::spin_result::success:
+ return true;
+ case detail::spin_result::timeout:
+ return false;
+ case detail::spin_result::advance:
+ break;
+ }
+
+ if (!MayBlock) {
+ switch (detail::spin_yield_until(deadline, [=] { return ready(); })) {
+ case detail::spin_result::success:
+ return true;
+ case detail::spin_result::timeout:
+ return false;
+ case detail::spin_result::advance:
+ break;
+ }
+ }
+
+ // guess we have to block :(
+ uint32_t expected = INIT;
+ if (!state_.compare_exchange_strong(
+ expected,
+ WAITING,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed)) {
+ // CAS failed, last minute reprieve
+ assert(expected == EARLY_DELIVERY);
+ // TODO: move the acquire to the compare_exchange failure load after C++17
+ std::atomic_thread_fence(std::memory_order_acquire);
+ return true;
+ }
+
+ while (true) {
+ auto rv = detail::futexWaitUntil(&state_, WAITING, deadline);
+
+ // Awoken by the deadline passing.
+ if (rv == detail::FutexResult::TIMEDOUT) {
+ assert(deadline != (std::chrono::time_point<Clock, Duration>::max()));
+ state_.store(TIMED_OUT, std::memory_order_release);
+ return false;
+ }
+
+ // Probably awoken by a matching wake event, but could also by awoken
+ // by an asynchronous signal or by a spurious wakeup.
+ //
+ // state_ is the truth even if FUTEX_WAIT reported a matching
+ // FUTEX_WAKE, since we aren't using type-stable storage and we
+ // don't guarantee reuse. The scenario goes like this: thread
+ // A's last touch of a Baton is a call to wake(), which stores
+ // LATE_DELIVERY and gets an unlucky context switch before delivering
+ // the corresponding futexWake. Thread B sees LATE_DELIVERY
+ // without consuming a futex event, because it calls futexWait
+ // with an expected value of WAITING and hence doesn't go to sleep.
+ // B returns, so the Baton's memory is reused and becomes another
+ // Baton (or a reuse of this one). B calls futexWait on the new
+ // Baton lifetime, then A wakes up and delivers a spurious futexWake
+ // to the same memory location. B's futexWait will then report a
+ // consumed wake event even though state_ is still WAITING.
+ //
+ // It would be possible to add an extra state_ dance to communicate
+ // that the futexWake has been sent so that we can be sure to consume
+ // it before returning, but that would be a perf and complexity hit.
+ uint32_t s = state_.load(std::memory_order_acquire);
+ assert(s == WAITING || s == LATE_DELIVERY);
+ if (s == LATE_DELIVERY) {
+ return true;
+ }
+ }
+ }
+
+ detail::Futex<Atom> state_;
+};
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex-inl.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex-inl.h
new file mode 100644
index 00000000..8eedb9cd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex-inl.h
@@ -0,0 +1,1703 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/DistributedMutex.h>
+
+#include <folly/ConstexprMath.h>
+#include <folly/Portability.h>
+#include <folly/ScopeGuard.h>
+#include <folly/Utility.h>
+#include <folly/chrono/Hardware.h>
+#include <folly/detail/Futex.h>
+#include <folly/lang/Align.h>
+#include <folly/lang/Bits.h>
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/AtomicNotification.h>
+#include <folly/synchronization/AtomicUtil.h>
+#include <folly/synchronization/detail/InlineFunctionRef.h>
+#include <folly/synchronization/detail/Sleeper.h>
+
+#include <array>
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <stdexcept>
+#include <thread>
+#include <utility>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+// kUnlocked is used to show unlocked state
+//
+// When locking threads encounter kUnlocked in the underlying storage, they
+// can just acquire the lock without any further effort
+constexpr auto kUnlocked = std::uintptr_t{0b0};
+// kLocked is used to show that the mutex is currently locked, and future
+// attempts to lock the mutex should enqueue on the central storage
+//
+// Locking threads find this on central storage only when there is a
+// contention chain that is undergoing wakeups, in every other case, a locker
+// will either find kUnlocked or an arbitrary address with the kLocked bit set
+constexpr auto kLocked = std::uintptr_t{0b1};
+// kTimedWaiter is set when there is at least one timed waiter on the mutex
+//
+// Timed waiters do not follow the sleeping strategy employed by regular,
+// non-timed threads. They sleep on the central mutex atomic through an
+// extended futex() interface that allows sleeping with the same semantics for
+// non-standard integer widths
+//
+// When a regular non-timed thread unlocks or enqueues on the mutex, and sees
+// a timed waiter, it takes ownership of all the timed waiters. The thread
+// that has taken ownership of the timed waiter releases the timed waiters
+// when it gets a chance at the critical section. At which point it issues a
+// wakeup to single timed waiter, timed waiters always issue wake() calls to
+// other timed waiters
+constexpr auto kTimedWaiter = std::uintptr_t{0b10};
+
+// kUninitialized means that the thread has just enqueued, and has not yet
+// gotten to initializing itself with the address of its successor
+//
+// this becomes significant for threads that are trying to wake up the
+// uninitialized thread, if they see that the thread is not yet initialized,
+// they can do nothing but spin, and wait for the thread to get initialized
+//
+// This also plays a role in the functioning of flat combining as implemented
+// in DistributedMutex. When a thread owning the lock goes through the
+// contention chain to either unlock the mutex or combine critical sections
+// from the other end. The presence of kUninitialized means that the
+// combining thread is not able to make progress after this point. So we
+// transfer the lock.
+constexpr auto kUninitialized = std::uint32_t{0b0};
+// kWaiting will be set in the waiter's futex structs while they are spinning
+// while waiting for the mutex
+constexpr auto kWaiting = std::uint32_t{0b1};
+// kWake will be set by threads that are waking up waiters that have enqueued
+constexpr auto kWake = std::uint32_t{0b10};
+// kSkipped will be set by a waker when they see that a waiter has been
+// preempted away by the kernel, in this case the thread that got skipped will
+// have to wake up and put itself back on the queue
+constexpr auto kSkipped = std::uint32_t{0b11};
+// kAboutToWait will be set by a waiter that enqueues itself with the purpose
+// of waiting on a futex
+constexpr auto kAboutToWait = std::uint32_t{0b100};
+// kSleeping will be set by a waiter right before enqueueing on a futex. When
+// a thread wants to wake up a waiter that has enqueued on a futex, it should
+// set the futex to contain kWake
+//
+// a thread that is unlocking and wants to skip over a sleeping thread also
+// calls futex_.exchange(kSleeping) on the sleeping thread's futex word. It
+// does this to 1. detect whether the sleeping thread had actually gone to
+// sleeping on the futex word so it can skip it, and 2. to synchronize with
+// other non atomic writes in the sleeping thread's context (such as the write
+// to track the next waiting thread).
+//
+// We reuse kSleeping instead of say using another constant kEarlyDelivery to
+// avoid situations where a thread has to enter kernel mode due to calling
+// futexWait() twice because of the presence of a waking thread. This
+// situation can arise when an unlocking thread goes to skip over a sleeping
+// thread, sees that the thread has slept and move on, but the sleeping thread
+// had not yet entered futex(). This interleaving causes the thread calling
+// futex() to return spuriously, as the futex word is not what it should be
+constexpr auto kSleeping = std::uint32_t{0b101};
+// kCombined is set by the lock holder to let the waiter thread know that its
+// combine request was successfully completed by the lock holder. A
+// successful combine means that the thread requesting the combine operation
+// does not need to unlock the mutex; in fact, doing so would be an error.
+constexpr auto kCombined = std::uint32_t{0b111};
+// kCombineUninitialized is like kUninitialized but is set by a thread when it
+// enqueues in hopes of getting its critical section combined with the lock
+// holder
+constexpr auto kCombineUninitialized = std::uint32_t{0b1000};
+// kCombineWaiting is set by a thread when it is ready to have its combine
+// record fulfilled by the lock holder. In particular, this signals to the
+// lock holder that the thread has set its next_ pointer in the contention
+// chain
+constexpr auto kCombineWaiting = std::uint32_t{0b1001};
+// kExceptionOccurred is set on the waiter futex when the remote task throws
+// an exception. It is the caller's responsibility to retrieve the exception
+// and rethrow it in their own context. Note that when the caller uses a
+// noexcept function as their critical section, they can avoid checking for
+// this value
+//
+// This allows us to avoid all cost of exceptions in the memory layout of the
+// fast path (no errors) as exceptions are stored as an std::exception_ptr in
+// the same union that stores the return value of the critical section. We
+// also avoid all CPU overhead because the combiner uses a try-catch block
+// without any additional branching to handle exceptions
+constexpr auto kExceptionOccurred = std::uint32_t{0b1010};
+
+// The number of spins that we are allowed to do before we resort to marking a
+// thread as having slept
+//
+// This is just a magic number from benchmarks
+constexpr auto kScheduledAwaySpinThreshold = std::chrono::nanoseconds{200};
+// The maximum number of spins before a thread starts yielding its processor
+// in hopes of getting skipped
+constexpr auto kMaxSpins = 4000;
+// The maximum number of contention chains we can resolve with flat combining.
+// After this number of contention chains, the mutex falls back to regular
+// two-phased mutual exclusion to ensure that we don't starve the combiner
+// thread
+constexpr auto kMaxCombineIterations = 2;
+
+/**
+ * Write only data that is available to the thread that is waking up another.
+ * Only the waking thread is allowed to write to this, the thread to be woken
+ * is allowed to read from this after a wakeup has been issued
+ */
+template <template <typename> class Atomic>
+class WakerMetadata {
+ public:
+ explicit WakerMetadata(
+ std::uintptr_t waker = 0,
+ std::uintptr_t waiters = 0,
+ std::uint32_t sleeper = kUninitialized)
+ : waker_{waker}, waiters_{waiters}, sleeper_{sleeper} {}
+
+ // This is the thread that initiated wakeups for the contention chain.
+ // There can only ever be one thread that initiates the wakeup for a
+ // chain in the spin only version of this mutex. When a thread that just
+ // woke up sees this as the next thread to wake up, it knows that it is the
+ // terminal node in the contention chain. This means that it was the one
+ // that took off the thread that had acquired the mutex off the centralized
+ // state. Therefore, the current thread is the last in its contention
+ // chain. It will fall back to centralized storage to pick up the next
+ // waiter or release the mutex
+ //
+ // When we move to a full sleeping implementation, this might need to change
+ // to a small_vector<> to account for failed wakeups, or we can put threads
+ // to sleep on the central futex, which is an easier implementation
+ // strategy. Although, since this is allocated on the stack, we can set a
+ // prohitively large threshold to avoid heap allocations, this strategy
+ // however, might cause increased cache misses on wakeup signalling
+ std::uintptr_t waker_{0};
+ // the list of threads that the waker had previously seen to be sleeping on
+ // a futex(),
+ //
+ // this is given to the current thread as a means to pass on
+ // information. When the current thread goes to unlock the mutex and does
+ // not see contention, it should go and wake up the head of this list. If
+ // the current thread sees a contention chain on the mutex, it should pass
+ // on this list to the next thread that gets woken up
+ std::uintptr_t waiters_{0};
+ // The futex that this waiter will sleep on
+ //
+ // how can we reuse futex_ from above for futex management?
+ Futex<Atomic> sleeper_{kUninitialized};
+};
+
+/**
+ * Type of the type-erased callable that is used for combining from the lock
+ * holder's end. This has 48 bytes of inline storage that can be used to
+ * minimize cache misses when combining
+ */
+using CombineFunction = detail::InlineFunctionRef<void(), 48>;
+
+/**
+ * Waiter encapsulates the state required for waiting on the mutex, this
+ * contains potentially heavy state and is intended to be allocated on the
+ * stack as part of a lock() function call
+ *
+ * To ensure that synchronization does not cause unintended side effects on
+ * the rest of the thread stack (eg. metadata in lockImplementation(), or any
+ * other data in the user's thread), we aggresively pad this struct and use
+ * custom alignment internally to ensure that the relevant data fits within a
+ * single cacheline. The added alignment here also gives us some room to
+ * wiggle in the bottom few bits of the mutex, where we store extra metadata
+ */
+template <template <typename> class Atomic>
+class Waiter {
+ public:
+ Waiter() {}
+ Waiter(Waiter&&) = delete;
+ Waiter(const Waiter&) = delete;
+ Waiter& operator=(Waiter&&) = delete;
+ Waiter& operator=(const Waiter&) = delete;
+
+ void initialize(std::uint64_t futex, CombineFunction task) {
+ // we only initialize the function if we were actually given a non-null
+ // task, otherwise
+ if (task) {
+ assert(futex == kCombineUninitialized);
+ new (&function_) CombineFunction(task);
+ } else {
+ assert((futex == kUninitialized) || (futex == kAboutToWait));
+ new (&metadata_) WakerMetadata<Atomic>{};
+ }
+
+ // this pedantic store is needed to ensure that the waking thread
+ // synchronizes with the state in the waiter struct when it loads the
+ // value of the futex word
+ //
+ // on x86, this gets optimized away to just a regular store, it might be
+ // needed on platforms where explicit acquire-release barriers are
+ // required for synchronization
+ //
+ // note that we release here at the end of the constructor because
+ // construction is complete here, any thread that acquires this release
+ // will see a well constructed wait node
+ futex_.store(futex, std::memory_order_release);
+ }
+
+ std::array<std::uint8_t, hardware_destructive_interference_size> padding1;
+ // the atomic that this thread will spin on while waiting for the mutex to
+ // be unlocked
+ alignas(hardware_destructive_interference_size) Atomic<std::uint64_t> futex_{
+ kUninitialized};
+ // The successor of this node. This will be the thread that had its address
+ // on the mutex previously
+ //
+ // We can do without making this atomic since the remote thread synchronizes
+ // on the futex variable above. If this were not atomic, the remote thread
+ // would only be allowed to read from it after the waiter has moved into the
+ // waiting state to avoid risk of a load racing with a write. However, it
+ // helps to make this atomic because we can use an unconditional load and make
+ // full use of the load buffer to coalesce both reads into a single clock
+ // cycle after the line arrives in the combiner core. This is a heavily
+ // contended line, so an RFO from the enqueueing thread is highly likely and
+ // has the potential to cause an immediate invalidation; blocking the combiner
+ // thread from making progress until the line is pulled back to read this
+ // value
+ //
+ // Further, making this atomic prevents the compiler from making an incorrect
+ // optimization where it does not load the value as written in the code, but
+ // rather dereferences it through a pointer whenever needed (since the value
+ // of the pointer to this is readily available on the stack). Doing this
+ // causes multiple invalidation requests from the enqueueing thread, blocking
+ // remote progress
+ //
+ // Note that we use relaxed loads and stores, so this should not have any
+ // additional overhead compared to a regular load on most architectures
+ std::atomic<std::uintptr_t> next_{0};
+ // We use an anonymous union for the combined critical section request and
+ // the metadata that will be filled in from the leader's end. Only one is
+ // active at a time - if a leader decides to combine the requested critical
+ // section into its execution, it will not touch the metadata field. If a
+ // leader decides to migrate the lock to the waiter, it will not touch the
+ // function
+ //
+ // this allows us to transfer more state when combining a critical section
+ // and reduce the cache misses originating from executing an arbitrary
+ // lambda
+ //
+ // note that this is an anonymous union, not an unnamed union, the members
+ // leak into the surrounding scope
+ union {
+ // metadata for the waker
+ WakerMetadata<Atomic> metadata_;
+ // The critical section that can potentially be combined into the critical
+ // section of the locking thread
+ //
+ // This is kept as a FunctionRef because the original function is preserved
+ // until the lock_combine() function returns. A consequence of using
+ // FunctionRef here is that we don't need to do any allocations and can
+ // allow users to capture unbounded state into the critical section. Flat
+ // combining means that the user does not have access to the thread
+ // executing the critical section, so assumptions about thread local
+ // references can be invalidated. Being able to capture arbitrary state
+ // allows the user to do thread local accesses right before the critical
+ // section and pass them as state to the callable being referenced here
+ CombineFunction function_;
+ // The user is allowed to use a combined critical section that returns a
+ // value. This buffer is used to implement the value transfer to the
+ // waiting thread. We reuse the same union because this helps us combine
+ // one synchronization operation with a material value transfer.
+ //
+ // The waker thread needs to synchronize on this cacheline to issue a
+ // wakeup to the waiter, meaning that the entire line needs to be pulled
+ // into the remote core in exclusive mode. So we reuse the coherence
+ // operation to transfer the return value in addition to the
+ // synchronization signal. In the case that the user's data item is
+ // small, the data is transferred all inline as part of the same line,
+ // which pretty much arrives into the CPU cache in the same clock cycle or
+ // two after a read-for-ownership request. This gives us a high chance of
+ // coalescing the entire transitive store buffer together into one cache
+ // coherence operation from the waker's end. This allows us to make use
+ // of the CPU bus bandwidth which would have otherwise gone to waste.
+ // Benchmarks prove this theory under a wide range of contention, value
+ // sizes, NUMA interactions and processor models
+ //
+ // The current version of the Intel optimization manual confirms this
+ // theory somewhat as well in section 2.3.5.1 (Load and Store Operation
+ // Overview)
+ //
+ // When an instruction writes data to a memory location [...], the
+ // processor ensures that it has the line containing this memory location
+ // is in its L1d cache [...]. If the cache line is not there, it fetches
+ // from the next levels using a RFO request [...] RFO and storing the
+ // data happens after instruction retirement. Therefore, the store
+ // latency usually does not affect the store instruction itself
+ //
+ // This gives the user the ability to input up to 48 bytes into the
+ // combined critical section through an InlineFunctionRef and output 48
+ // bytes from it basically without any cost. The type of the entity
+ // stored in the buffer has to be matched by the type erased callable that
+ // the caller has used. At this point, the caller is still in the
+ // template instantiation leading to the combine request, so it has
+ // knowledge of the return type and can apply the appropriate
+ // reinterpret_cast and launder operation to safely retrieve the data from
+ // this buffer
+ _t<std::aligned_storage<48, 8>> storage_;
+ };
+ std::array<std::uint8_t, hardware_destructive_interference_size> padding2;
+};
+
+/**
+ * A template that helps us differentiate between the different ways to return
+ * a value from a combined critical section. A return value of type void
+ * cannot be stored anywhere, so we use specializations and pick the right one
+ * switched through std::conditional_t
+ *
+ * This is then used by CoalescedTask and its family of functions to implement
+ * efficient return value transfers to the waiting threads
+ */
+template <typename Func>
+class RequestWithReturn {
+ public:
+ using F = Func;
+ using ReturnType = decltype(std::declval<const Func&>()());
+ explicit RequestWithReturn(Func func) : func_{std::move(func)} {}
+
+ /**
+ * We need to define the destructor here because C++ requires (with good
+ * reason) that a union with non-default destructor be explicitly destroyed
+ * from the surrounding class, as neither the runtime nor compiler have the
+ * knowledge of what to do with a union at the time of destruction
+ *
+ * Each request that has a valid return value set will have the value
+ * retrieved from the get() method, where the value is destroyed. So we
+ * don't need to destroy it here
+ */
+ ~RequestWithReturn() {}
+
+ /**
+ * This method can be used to return a value from the request. This returns
+ * the underlying value because return type of the function we were
+ * instantiated with is not void
+ */
+ ReturnType get() && {
+ // when the return value has been processed, we destroy the value
+ // contained in this request. Using a scope_exit means that we don't have
+ // to worry about storing the value somewhere and causing potentially an
+ // extra move
+ //
+ // note that the invariant here is that this function is only called if the
+ // requesting thread had it's critical section combined, and the value_
+ // member constructed through detach()
+ SCOPE_EXIT {
+ value_.~ReturnType();
+ };
+ return std::move(value_);
+ }
+
+ // this contains a copy of the function the waiter had requested to be
+ // executed as a combined critical section
+ Func func_;
+ // this stores the return value used in the request, we use a union here to
+ // avoid laundering and allow return types that are not default
+ // constructible to be propagated through the execution of the critical
+ // section
+ //
+ // note that this is an anonymous union, the member leaks into the
+ // surrounding scope as a member variable
+ union {
+ ReturnType value_;
+ };
+};
+
+template <typename Func>
+class RequestWithoutReturn {
+ public:
+ using F = Func;
+ using ReturnType = void;
+ explicit RequestWithoutReturn(Func func) : func_{std::move(func)} {}
+
+ /**
+ * In this version of the request class, get() returns nothing as there is
+ * no stored value
+ */
+ void get() && {}
+
+ // this contains a copy of the function the waiter had requested to be
+ // executed as a combined critical section
+ Func func_;
+};
+
+// we need to use std::integral_constant::value here as opposed to
+// std::integral_constant::operator T() because MSVC errors out with the
+// implicit conversion
+template <typename Func>
+using Request = _t<std::conditional<
+ std::is_void<decltype(std::declval<const Func&>()())>::value,
+ RequestWithoutReturn<Func>,
+ RequestWithReturn<Func>>>;
+
+/**
+ * A template that helps us to transform a callable returning a value to one
+ * that returns void so it can be type erased and passed on to the waker. If
+ * the return value is small enough, it gets coalesced into the wait struct
+ * for optimal data transfer. When it's not small enough to fit in the waiter
+ * storage buffer, we place it on it's own cacheline with isolation to prevent
+ * false-sharing with the on-stack metadata of the waiter thread
+ *
+ * This helps a combined critical section feel more normal in the case where
+ * the user wants to return a value, for example
+ *
+ * auto value = mutex_.lock_combine([&]() {
+ * return data_.value();
+ * });
+ *
+ * Without this, the user would typically create a dummy object that they
+ * would then assign to from within the lambda. With return value chaining,
+ * this pattern feels more natural
+ *
+ * Note that it is important to copy the entire callble into this class.
+ * Storing something like a reference instead is not desirable because it does
+ * not allow InlineFunctionRef to use inline storage to represent the user's
+ * callable without extra indirections
+ *
+ * We use std::conditional_t and switch to the right type of task with the
+ * CoalescedTask type alias
+ */
+template <typename Func, typename Waiter>
+class TaskWithCoalesce {
+ public:
+ using ReturnType = decltype(std::declval<const Func&>()());
+ using StorageType = folly::Unit;
+ explicit TaskWithCoalesce(Func func, Waiter& waiter)
+ : func_{std::move(func)}, waiter_(waiter) {}
+
+ void operator()() const {
+ auto value = func_();
+ new (&waiter_.storage_) ReturnType(std::move(value));
+ }
+
+ private:
+ Func func_;
+ Waiter& waiter_;
+
+ static_assert(!std::is_void<ReturnType>{}, "");
+ static_assert(alignof(decltype(waiter_.storage_)) >= alignof(ReturnType), "");
+ static_assert(sizeof(decltype(waiter_.storage_)) >= sizeof(ReturnType), "");
+};
+
+template <typename Func, typename Waiter>
+class TaskWithoutCoalesce {
+ public:
+ using ReturnType = void;
+ using StorageType = folly::Unit;
+ explicit TaskWithoutCoalesce(Func func, Waiter&) : func_{std::move(func)} {}
+
+ void operator()() const {
+ func_();
+ }
+
+ private:
+ Func func_;
+};
+
+template <typename Func, typename Waiter>
+class TaskWithBigReturnValue {
+ public:
+ // Using storage that is aligned on the cacheline boundary helps us avoid a
+ // situation where the data ends up being allocated on two separate
+ // cachelines. This would require the remote thread to pull in both lines
+ // to issue a write.
+ //
+ // We also isolate the storage by appending some padding to the end to
+ // ensure we avoid false-sharing with the metadata used while the waiter
+ // waits
+ using ReturnType = decltype(std::declval<const Func&>()());
+ static const auto kReturnValueAlignment = folly::kIsMsvc
+ ? 8
+ : folly::constexpr_max(
+ alignof(ReturnType),
+ folly::hardware_destructive_interference_size);
+ using StorageType = _t<std::aligned_storage<
+ sizeof(
+ _t<std::aligned_storage<sizeof(ReturnType), kReturnValueAlignment>>),
+ kReturnValueAlignment>>;
+
+ explicit TaskWithBigReturnValue(Func func, Waiter&)
+ : func_{std::move(func)} {}
+
+ void operator()() const {
+ assert(storage_);
+ auto value = func_();
+ new (storage_) ReturnType(std::move(value));
+ }
+
+ void attach(StorageType* storage) {
+ assert(!storage_);
+ storage_ = storage;
+ }
+
+ private:
+ Func func_;
+ StorageType* storage_{nullptr};
+
+ static_assert(!std::is_void<ReturnType>{}, "");
+ static_assert(sizeof(Waiter::storage_) < sizeof(ReturnType), "");
+};
+
+template <typename T, bool>
+struct Sizeof_;
+template <typename T>
+struct Sizeof_<T, false> : std::integral_constant<std::size_t, sizeof(T)> {};
+template <typename T>
+struct Sizeof_<T, true> : std::integral_constant<std::size_t, 0> {};
+template <typename T>
+struct Sizeof : Sizeof_<T, std::is_void<T>::value> {};
+
+// we need to use std::integral_constant::value here as opposed to
+// std::integral_constant::operator T() because MSVC errors out with the
+// implicit conversion
+template <typename Func, typename Waiter>
+using CoalescedTask = _t<std::conditional<
+ std::is_void<decltype(std::declval<const Func&>()())>::value,
+ TaskWithoutCoalesce<Func, Waiter>,
+ _t<std::conditional<
+ Sizeof<decltype(std::declval<const Func&>()())>::value <=
+ sizeof(Waiter::storage_),
+ TaskWithCoalesce<Func, Waiter>,
+ TaskWithBigReturnValue<Func, Waiter>>>>>;
+
+/**
+ * Given a request and a wait node, coalesce them into a CoalescedTask that
+ * coalesces the return value into the wait node when invoked from a remote
+ * thread
+ *
+ * When given a null request through nullptr_t, coalesce() returns null as well
+ */
+template <typename Waiter>
+std::nullptr_t coalesce(std::nullptr_t&, Waiter&) {
+ return nullptr;
+}
+
+template <
+ typename Request,
+ typename Waiter,
+ typename Func = typename Request::F>
+CoalescedTask<Func, Waiter> coalesce(Request& request, Waiter& waiter) {
+ static_assert(!std::is_same<Request, std::nullptr_t>{}, "");
+ return CoalescedTask<Func, Waiter>{request.func_, waiter};
+}
+
+/**
+ * Given a task, create storage for the return value. When we get a type
+ * of CoalescedTask, this returns an instance of CoalescedTask::StorageType.
+ * std::nullptr_t otherwise
+ */
+inline std::nullptr_t makeReturnValueStorageFor(std::nullptr_t&) {
+ return {};
+}
+
+template <
+ typename CoalescedTask,
+ typename StorageType = typename CoalescedTask::StorageType>
+StorageType makeReturnValueStorageFor(CoalescedTask&) {
+ return {};
+}
+
+/**
+ * Given a task and storage, attach them together if needed. This only helps
+ * when we have a task that returns a value bigger than can be coalesced. In
+ * that case, we need to attach the storage with the task so the return value
+ * can be transferred to this thread from the remote thread
+ */
+template <typename Task, typename Storage>
+void attach(Task&, Storage&) {
+ static_assert(
+ std::is_same<Storage, std::nullptr_t>{} ||
+ std::is_same<Storage, folly::Unit>{},
+ "");
+}
+
+template <
+ typename R,
+ typename W,
+ typename StorageType = typename TaskWithBigReturnValue<R, W>::StorageType>
+void attach(TaskWithBigReturnValue<R, W>& task, StorageType& storage) {
+ task.attach(&storage);
+}
+
+template <typename Request, typename Waiter>
+void throwIfExceptionOccurred(Request&, Waiter& waiter, bool exception) {
+ using Storage = decltype(waiter.storage_);
+ using F = typename Request::F;
+ static_assert(sizeof(Storage) >= sizeof(std::exception_ptr), "");
+ static_assert(alignof(Storage) >= alignof(std::exception_ptr), "");
+
+ // we only need to check for an exception in the waiter struct if the passed
+ // callable is not noexcept
+ //
+ // we need to make another instance of the exception with automatic storage
+ // duration and destroy the exception held in the storage *before throwing* to
+ // avoid leaks. If we don't destroy the exception_ptr in storage, the
+ // refcount for the internal exception will never hit zero, thereby leaking
+ // memory
+ if ((!noexcept(std::declval<const F&>()()) && exception)) {
+ auto storage = &waiter.storage_;
+ auto exc = folly::launder(reinterpret_cast<std::exception_ptr*>(storage));
+ auto copy = std::move(*exc);
+ exc->std::exception_ptr::~exception_ptr();
+ std::rethrow_exception(std::move(copy));
+ }
+}
+
+/**
+ * Given a CoalescedTask, a wait node and a request. Detach the return value
+ * into the request from the wait node and task.
+ */
+template <typename Waiter>
+void detach(std::nullptr_t&, Waiter&, bool exception, std::nullptr_t&) {
+ assert(!exception);
+}
+
+template <typename Waiter, typename F>
+void detach(
+ RequestWithoutReturn<F>& request,
+ Waiter& waiter,
+ bool exception,
+ folly::Unit&) {
+ throwIfExceptionOccurred(request, waiter, exception);
+}
+
+template <typename Waiter, typename F>
+void detach(
+ RequestWithReturn<F>& request,
+ Waiter& waiter,
+ bool exception,
+ folly::Unit&) {
+ throwIfExceptionOccurred(request, waiter, exception);
+
+ using ReturnType = typename RequestWithReturn<F>::ReturnType;
+ static_assert(!std::is_same<ReturnType, void>{}, "");
+ static_assert(sizeof(waiter.storage_) >= sizeof(ReturnType), "");
+
+ auto& val = *folly::launder(reinterpret_cast<ReturnType*>(&waiter.storage_));
+ new (&request.value_) ReturnType(std::move(val));
+ val.~ReturnType();
+}
+
+template <typename Waiter, typename F, typename Storage>
+void detach(
+ RequestWithReturn<F>& request,
+ Waiter& waiter,
+ bool exception,
+ Storage& storage) {
+ throwIfExceptionOccurred(request, waiter, exception);
+
+ using ReturnType = typename RequestWithReturn<F>::ReturnType;
+ static_assert(!std::is_same<ReturnType, void>{}, "");
+ static_assert(sizeof(storage) >= sizeof(ReturnType), "");
+
+ auto& val = *folly::launder(reinterpret_cast<ReturnType*>(&storage));
+ new (&request.value_) ReturnType(std::move(val));
+ val.~ReturnType();
+}
+
+/**
+ * Get the time since epoch in nanoseconds
+ *
+ * This is faster than std::chrono::steady_clock because it avoids a VDSO
+ * access to get the timestamp counter
+ *
+ * Note that the hardware timestamp counter on x86, like std::steady_clock is
+ * guaranteed to be monotonically increasing -
+ * https://c9x.me/x86/html/file_module_x86_id_278.html
+ */
+inline std::chrono::nanoseconds time() {
+ return std::chrono::nanoseconds{hardware_timestamp()};
+}
+
+/**
+ * Zero out the other bits used by the implementation and return just an
+ * address from a uintptr_t
+ */
+template <typename Type>
+Type* extractPtr(std::uintptr_t from) {
+ // shift one bit off the end, to get all 1s followed by a single 0
+ auto mask = std::numeric_limits<std::uintptr_t>::max();
+ mask >>= 1;
+ mask <<= 1;
+ assert(!(mask & 0b1));
+
+ return folly::bit_cast<Type*>(from & mask);
+}
+
+/**
+ * Strips the given nanoseconds into only the least significant 56 bits by
+ * moving the least significant 56 bits over by 8 zeroing out the bottom 8
+ * bits to be used as a medium of information transfer for the thread wait
+ * nodes
+ */
+inline std::uint64_t strip(std::chrono::nanoseconds t) {
+ auto time = t.count();
+ return static_cast<std::uint64_t>(time) << 8;
+}
+
+/**
+ * Recover the timestamp value from an integer that has the timestamp encoded
+ * in it
+ */
+inline std::uint64_t recover(std::uint64_t from) {
+ return from >> 8;
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+class DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy {
+ public:
+ // DistributedMutexStateProxy is move constructible and assignable for
+ // convenience
+ DistributedMutexStateProxy(DistributedMutexStateProxy&& other) {
+ *this = std::move(other);
+ }
+
+ DistributedMutexStateProxy& operator=(DistributedMutexStateProxy&& other) {
+ assert(!(*this));
+
+ next_ = folly::exchange(other.next_, nullptr);
+ expected_ = folly::exchange(other.expected_, 0);
+ timedWaiters_ = folly::exchange(other.timedWaiters_, false);
+ combined_ = folly::exchange(other.combined_, false);
+ waker_ = folly::exchange(other.waker_, 0);
+ waiters_ = folly::exchange(other.waiters_, nullptr);
+ ready_ = folly::exchange(other.ready_, nullptr);
+
+ return *this;
+ }
+
+ // The proxy is valid when a mutex acquisition attempt was successful,
+ // lock() is guaranteed to return a valid proxy, try_lock() is not
+ explicit operator bool() const {
+ return expected_;
+ }
+
+ // private:
+ // friend the mutex class, since that will be accessing state private to
+ // this class
+ friend class DistributedMutex<Atomic, TimePublishing>;
+
+ DistributedMutexStateProxy(
+ Waiter<Atomic>* next,
+ std::uintptr_t expected,
+ bool timedWaiter = false,
+ bool combined = false,
+ std::uintptr_t waker = 0,
+ Waiter<Atomic>* waiters = nullptr,
+ Waiter<Atomic>* ready = nullptr)
+ : next_{next},
+ expected_{expected},
+ timedWaiters_{timedWaiter},
+ combined_{combined},
+ waker_{waker},
+ waiters_{waiters},
+ ready_{ready} {}
+
+ // the next thread that is to be woken up, this being null at the time of
+ // unlock() shows that the current thread acquired the mutex without
+ // contention or it was the terminal thread in the queue of threads waking up
+ Waiter<Atomic>* next_{nullptr};
+ // this is the value that the current thread should expect to find on
+ // unlock, and if this value is not there on unlock, the current thread
+ // should assume that other threads are enqueued waiting for the mutex
+ //
+ // note that if the mutex has the same state set at unlock time, and this is
+ // set to an address (and not say kLocked in the case of a terminal waker)
+ // then it must have been the case that no other thread had enqueued itself,
+ // since threads in the domain of this mutex do not share stack space
+ //
+ // if we want to support stack sharing, we can solve the problem by looping
+ // at lock time, and setting a variable that says whether we have acquired
+ // the lock or not perhaps
+ std::uintptr_t expected_{0};
+ // a boolean that will be set when the mutex has timed waiters that the
+ // current thread is responsible for waking, in such a case, the current
+ // thread will issue an atomic_notify_one() call after unlocking the mutex
+ //
+ // note that a timed waiter will itself always have this flag set. This is
+ // done so we can avoid having to issue a atomic_notify_all() call (and
+ // subsequently a thundering herd) when waking up timed-wait threads
+ bool timedWaiters_{false};
+ // a boolean that contains true if the state proxy is not meant to be passed
+ // to the unlock() function. This is set only when there is contention and
+ // a thread had asked for its critical section to be combined
+ bool combined_{false};
+ // metadata passed along from the thread that woke this thread up
+ std::uintptr_t waker_{0};
+ // the list of threads that are waiting on a futex
+ //
+ // the current threads is meant to wake up this list of waiters if it is
+ // able to commit an unlock() on the mutex without seeing a contention chain
+ Waiter<Atomic>* waiters_{nullptr};
+ // after a thread has woken up from a futex() call, it will have the rest of
+ // the threads that it were waiting behind it in this list, a thread that
+ // unlocks has to wake up threads from this list if it has any, before it
+ // goes to sleep to prevent pathological unfairness
+ Waiter<Atomic>* ready_{nullptr};
+};
+
+template <template <typename> class Atomic, bool TimePublishing>
+DistributedMutex<Atomic, TimePublishing>::DistributedMutex()
+ : state_{kUnlocked} {}
+
+template <typename Waiter>
+std::uint64_t publish(
+ std::uint64_t spins,
+ bool& shouldPublish,
+ std::chrono::nanoseconds& previous,
+ Waiter& waiter,
+ std::uint32_t waitMode) {
+ // time publishing has some overhead because it executes an atomic exchange on
+ // the futex word. If this line is in a remote thread (eg. the combiner),
+ // then each time we publish a timestamp, this thread has to submit an RFO to
+ // the remote core for the cacheline, blocking progress for both threads.
+ //
+ // the remote core uses a store in the fast path - why then does an RFO make a
+ // difference? The only educated guess we have here is that the added
+ // roundtrip delays draining of the store buffer, which essentially exerts
+ // backpressure on future stores, preventing parallelization
+ //
+ // if we have requested a combine, time publishing is less important as it
+ // only comes into play when the combiner has exhausted their max combine
+ // passes. So we defer time publishing to the point when the current thread
+ // gets preempted
+ auto current = time();
+ if ((current - previous) >= kScheduledAwaySpinThreshold) {
+ shouldPublish = true;
+ }
+ previous = current;
+
+ // if we have requested a combine, and this is the first iteration of the
+ // wait-loop, we publish a max timestamp to optimistically convey that we have
+ // not yet been preempted (the remote knows the meaning of max timestamps)
+ //
+ // then if we are under the maximum number of spins allowed before sleeping,
+ // we publish the exact timestamp, otherwise we publish the minimum possible
+ // timestamp to force the waking thread to skip us
+ auto now = ((waitMode == kCombineWaiting) && !spins)
+ ? decltype(time())::max()
+ : (spins < kMaxSpins) ? previous : decltype(time())::zero();
+
+ // the wait mode information is published in the bottom 8 bits of the futex
+ // word, the rest contains time information as computed above. Overflows are
+ // not really a correctness concern because time publishing is only a
+ // heuristic. This leaves us 56 bits of nanoseconds (2 years) before we hit
+ // two consecutive wraparounds, so the lack of bits to respresent time is
+ // neither a performance nor correctness concern
+ auto data = strip(now) | waitMode;
+ auto signal = (shouldPublish || !spins || (waitMode != kCombineWaiting))
+ ? waiter.futex_.exchange(data, std::memory_order_acq_rel)
+ : waiter.futex_.load(std::memory_order_acquire);
+ return signal & std::numeric_limits<std::uint8_t>::max();
+}
+
+template <typename Waiter>
+bool spin(Waiter& waiter, std::uint32_t& sig, std::uint32_t mode) {
+ auto spins = std::uint64_t{0};
+ auto waitMode = (mode == kCombineUninitialized) ? kCombineWaiting : kWaiting;
+ auto previous = time();
+ auto shouldPublish = false;
+ while (true) {
+ auto signal = publish(spins++, shouldPublish, previous, waiter, waitMode);
+
+ // if we got skipped, make a note of it and return if we got a skipped
+ // signal or a signal to wake up
+ auto skipped = (signal == kSkipped);
+ auto combined = (signal == kCombined);
+ auto exceptionOccurred = (signal == kExceptionOccurred);
+ auto woken = (signal == kWake);
+ if (skipped || woken || combined || exceptionOccurred) {
+ sig = static_cast<std::uint32_t>(signal);
+ return !skipped;
+ }
+
+ // if we are under the spin threshold, pause to allow the other
+ // hyperthread to run. If not, then sleep
+ if (spins < kMaxSpins) {
+ asm_volatile_pause();
+ } else {
+ Sleeper::sleep();
+ }
+ }
+}
+
+template <typename Waiter>
+void doFutexWake(Waiter* waiter) {
+ if (waiter) {
+ // We can use a simple store operation here and not worry about checking
+ // to see if the thread had actually started waiting on the futex, that is
+ // already done in tryWake() when a sleeping thread is collected
+ //
+ // We now do not know whether the waiter had already enqueued on the futex
+ // or whether it had just stored kSleeping in its futex and was about to
+ // call futexWait(). We treat both these scenarios the same
+ //
+ // the below can theoretically cause a problem if we set the
+ // wake signal and the waiter was in between setting kSleeping in its
+ // futex and enqueueing on the futex. In this case the waiter will just
+ // return from futexWait() immediately. This leaves the address that the
+ // waiter was using for futexWait() possibly dangling, and the thread that
+ // we woke in the exchange above might have used that address for some
+ // other object
+ //
+ // however, even if the thread had indeed woken up simply becasue of the
+ // above exchange(), the futexWake() below is not incorrect. It is not
+ // incorrect because futexWake() does not actually change the memory of
+ // the futex word. It just uses the address to do a lookup in the kernel
+ // futex table. And even if we call futexWake() on some other address,
+ // and that address was being used to wait on futex() that thread will
+ // protect itself from spurious wakeups, check the value in the futex word
+ // and enqueue itself back on the futex
+ //
+ // this dangilng pointer possibility is why we use a pointer to the futex
+ // word, and avoid dereferencing after the store() operation
+ auto sleeper = &waiter->metadata_.sleeper_;
+ sleeper->store(kWake, std::memory_order_release);
+ futexWake(sleeper, 1);
+ }
+}
+
+template <typename Waiter>
+bool doFutexWait(Waiter* waiter, Waiter*& next) {
+ // first we get ready to sleep by calling exchange() on the futex with a
+ // kSleeping value
+ assert(waiter->futex_.load(std::memory_order_relaxed) == kAboutToWait);
+
+ // note the semantics of using a futex here, when we exchange the sleeper_
+ // with kSleeping, we are getting ready to sleep, but before sleeping we get
+ // ready to sleep, and we return from futexWait() when the value of
+ // sleeper_ might have changed. We can also wake up because of a spurious
+ // wakeup, so we always check against the value in sleeper_ after returning
+ // from futexWait(), if the value is not kWake, then we continue
+ auto pre =
+ waiter->metadata_.sleeper_.exchange(kSleeping, std::memory_order_acq_rel);
+
+ // Seeing a kSleeping on a futex word before we set it ourselves means only
+ // one thing - an unlocking thread caught us before we went to futex(), and
+ // we now have the lock, so we abort
+ //
+ // if we were given an early delivery, we can return from this function with
+ // a true, meaning that we now have the lock
+ if (pre == kSleeping) {
+ return true;
+ }
+
+ // if we reach here then were were not given an early delivery, and any
+ // thread that goes to wake us up will see a consistent view of the rest of
+ // the contention chain (since the next_ variable is set before the
+ // kSleeping exchange above)
+ while (pre != kWake) {
+ // before enqueueing on the futex, we wake any waiters that we were
+ // possibly responsible for
+ doFutexWake(folly::exchange(next, nullptr));
+
+ // then we wait on the futex
+ //
+ // note that we have to protect ourselves against spurious wakeups here.
+ // Because the corresponding futexWake() above does not synchronize
+ // wakeups around the futex word. Because doing so would become
+ // inefficient
+ futexWait(&waiter->metadata_.sleeper_, kSleeping);
+ pre = waiter->metadata_.sleeper_.load(std::memory_order_acquire);
+ assert((pre == kSleeping) || (pre == kWake));
+ }
+
+ // when coming out of a futex, we might have some other sleeping threads
+ // that we were supposed to wake up, assign that to the next pointer
+ assert(next == nullptr);
+ next = extractPtr<Waiter>(waiter->next_.load(std::memory_order_relaxed));
+ return false;
+}
+
+template <typename Waiter>
+bool wait(Waiter* waiter, std::uint32_t mode, Waiter*& next, uint32_t& signal) {
+ if (mode == kAboutToWait) {
+ return doFutexWait(waiter, next);
+ }
+
+ return spin(*waiter, signal, mode);
+}
+
+inline void recordTimedWaiterAndClearTimedBit(
+ bool& timedWaiter,
+ std::uintptr_t& previous) {
+ // the previous value in the mutex can never be kTimedWaiter, timed waiters
+ // always set (kTimedWaiter | kLocked) in the mutex word when they try and
+ // acquire the mutex
+ assert(previous != kTimedWaiter);
+
+ if ((previous & kTimedWaiter)) {
+ // record whether there was a timed waiter in the previous mutex state, and
+ // clear the timed bit from the previous state
+ timedWaiter = true;
+ previous = previous & (~kTimedWaiter);
+ }
+}
+
+template <typename Atomic>
+void wakeTimedWaiters(Atomic* state, bool timedWaiters) {
+ if ((timedWaiters)) {
+ atomic_notify_one(state);
+ }
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Func>
+auto DistributedMutex<Atomic, TimePublishing>::lock_combine(Func func)
+ -> decltype(std::declval<const Func&>()()) {
+ // invoke the lock implementation function and check whether we came out of
+ // it with our task executed as a combined critical section. This usually
+ // happens when the mutex is contended.
+ //
+ // In the absence of contention, we just return from the try_lock() function
+ // with the lock acquired. So we need to invoke the task and unlock
+ // the mutex before returning
+ auto&& task = Request<Func>{func};
+ auto&& state = lockImplementation(*this, state_, task);
+ if (!state.combined_) {
+ // to avoid having to play a return-value dance when the combinable
+ // returns void, we use a scope exit to perform the unlock after the
+ // function return has been processed
+ SCOPE_EXIT {
+ unlock(std::move(state));
+ };
+ return func();
+ }
+
+ // if we are here, that means we were able to get our request combined, we
+ // can return the value that was transferred to us
+ //
+ // each thread that enqueues as a part of a contention chain takes up the
+ // responsibility of any timed waiter that had come immediately before it,
+ // so we wake up timed waiters before exiting the lock function. Another
+ // strategy might be to add the timed waiter information to the metadata and
+ // let a single leader wake up a timed waiter for better concurrency. But
+ // this has proven not to be useful in benchmarks beyond a small 5% delta,
+ // so we avoid taking the complexity hit and branch to wake up timed waiters
+ // from each thread
+ wakeTimedWaiters(&state_, state.timedWaiters_);
+ return std::move(task).get();
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::lock() {
+ auto null = nullptr;
+ return lockImplementation(*this, state_, null);
+}
+
+template <typename Atomic, template <typename> class A, bool T>
+auto tryLockNoLoad(Atomic& atomic, DistributedMutex<A, T>&)
+ -> typename DistributedMutex<A, T>::DistributedMutexStateProxy {
+ // Try and set the least significant bit of the centralized lock state to 1,
+ // if this succeeds, it must have been the case that we had a kUnlocked (or
+ // 0) in the central storage before, since that is the only case where a 0
+ // can be found in the least significant bit
+ //
+ // If this fails, then it is a no-op
+ using Proxy = typename DistributedMutex<A, T>::DistributedMutexStateProxy;
+ auto previous = atomic_fetch_set(atomic, 0, std::memory_order_acquire);
+ if (!previous) {
+ return Proxy{nullptr, kLocked};
+ }
+
+ return Proxy{nullptr, 0};
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock() {
+ // The lock attempt below requires an expensive atomic fetch-and-mutate or
+ // an even more expensive atomic compare-and-swap loop depending on the
+ // platform. These operations require pulling the lock cacheline into the
+ // current core in exclusive mode and are therefore hard to parallelize
+ //
+ // This probabilistically avoids the expense by first checking whether the
+ // mutex is currently locked
+ if (state_.load(std::memory_order_relaxed) != kUnlocked) {
+ return DistributedMutexStateProxy{nullptr, 0};
+ }
+
+ return tryLockNoLoad(state_, *this);
+}
+
+template <
+ template <typename> class Atomic,
+ bool TimePublishing,
+ typename State,
+ typename Request>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+lockImplementation(
+ DistributedMutex<Atomic, TimePublishing>& mutex,
+ State& atomic,
+ Request& request) {
+ // first try and acquire the lock as a fast path, the underlying
+ // implementation is slightly faster than using std::atomic::exchange() as
+ // is used in this function. So we get a small perf boost in the
+ // uncontended case
+ //
+ // We only go through this fast path for the lock/unlock usage and avoid this
+ // for combined critical sections. This check adds unnecessary overhead in
+ // that case as it causes an extra cacheline bounce
+ constexpr auto combineRequested = !std::is_same<Request, std::nullptr_t>{};
+ if (!combineRequested) {
+ if (auto state = tryLockNoLoad(atomic, mutex)) {
+ return state;
+ }
+ }
+
+ auto previous = std::uintptr_t{0};
+ auto waitMode = combineRequested ? kCombineUninitialized : kUninitialized;
+ auto nextWaitMode = kAboutToWait;
+ auto timedWaiter = false;
+ Waiter<Atomic>* nextSleeper = nullptr;
+ while (true) {
+ // construct the state needed to wait
+ //
+ // We can't use auto here because MSVC errors out due to a missing copy
+ // constructor
+ Waiter<Atomic> state{};
+ auto&& task = coalesce(request, state);
+ auto&& storage = makeReturnValueStorageFor(task);
+ auto&& address = folly::bit_cast<std::uintptr_t>(&state);
+ attach(task, storage);
+ state.initialize(waitMode, std::move(task));
+ assert(!(address & 0b1));
+
+ // set the locked bit in the address we will be persisting in the mutex
+ address |= kLocked;
+
+ // attempt to acquire the mutex, mutex acquisition is successful if the
+ // previous value is zeroed out
+ //
+ // we use memory_order_acq_rel here because we want the read-modify-write
+ // operation to be both acquire and release. Acquire becasue if this is a
+ // successful lock acquisition, we want to acquire state any other thread
+ // has released from a prior unlock. We want release semantics becasue
+ // other threads that read the address of this value should see the full
+ // well-initialized node we are going to wait on if the mutex acquisition
+ // was unsuccessful
+ previous = atomic.exchange(address, std::memory_order_acq_rel);
+ recordTimedWaiterAndClearTimedBit(timedWaiter, previous);
+ state.next_.store(previous, std::memory_order_relaxed);
+ if (previous == kUnlocked) {
+ return {/* next */ nullptr,
+ /* expected */ address,
+ /* timedWaiter */ timedWaiter,
+ /* combined */ false,
+ /* waker */ 0,
+ /* waiters */ nullptr,
+ /* ready */ nextSleeper};
+ }
+ assert(previous & kLocked);
+
+ // wait until we get a signal from another thread, if this returns false,
+ // we got skipped and had probably been scheduled out, so try again
+ auto signal = kUninitialized;
+ if (!wait(&state, waitMode, nextSleeper, signal)) {
+ std::swap(waitMode, nextWaitMode);
+ continue;
+ }
+
+ // at this point it is safe to access the other fields in the waiter state,
+ // since the thread that woke us up is gone and nobody will be touching this
+ // state again, note that this requires memory ordering, and this is why we
+ // use memory_order_acquire (among other reasons) in the above wait
+ //
+ // first we see if the value we took off the mutex state was the thread that
+ // initated the wakeups, if so, we are the terminal node of the current
+ // contention chain. If we are the terminal node, then we should expect to
+ // see a kLocked in the mutex state when we unlock, if we see that, we can
+ // commit the unlock to the centralized mutex state. If not, we need to
+ // continue wakeups
+ //
+ // a nice consequence of passing kLocked as the current address if we are
+ // the terminal node is that it naturally just works with the algorithm. If
+ // we get a contention chain when coming out of a contention chain, the tail
+ // of the new contention chain will have kLocked set as the previous, which,
+ // as it happens "just works", since we have now established a recursive
+ // relationship until broken
+ auto next = previous;
+ auto expected = address;
+ if (previous == state.metadata_.waker_) {
+ next = 0;
+ expected = kLocked;
+ }
+
+ // if we were given a combine signal, detach the return value from the
+ // wait struct into the request, so the current thread can access it
+ // outside this function
+ auto combined = (signal == kCombined);
+ auto exceptionOccurred = (signal == kExceptionOccurred);
+ if (combined || exceptionOccurred) {
+ detach(request, state, exceptionOccurred, storage);
+ }
+
+ // if we are just coming out of a futex call, then it means that the next
+ // waiter we are responsible for is also a waiter waiting on a futex, so
+ // we return that list in the list of ready threads. We wlil be waking up
+ // the ready threads on unlock no matter what
+ return {/* next */ extractPtr<Waiter<Atomic>>(next),
+ /* expected */ expected,
+ /* timedWaiter */ timedWaiter,
+ /* combined */ combineRequested && (combined || exceptionOccurred),
+ /* waker */ state.metadata_.waker_,
+ /* waiters */ extractPtr<Waiter<Atomic>>(state.metadata_.waiters_),
+ /* ready */ nextSleeper};
+ }
+}
+
+inline bool preempted(std::uint64_t value, std::chrono::nanoseconds now) {
+ auto currentTime = recover(strip(now));
+ auto nodeTime = recover(value);
+ auto preempted =
+ (currentTime > nodeTime + kScheduledAwaySpinThreshold.count()) &&
+ (nodeTime != recover(strip(std::chrono::nanoseconds::max())));
+
+ // we say that the thread has been preempted if its timestamp says so, and
+ // also if it is neither uninitialized nor skipped
+ assert(value != kSkipped);
+ return (preempted) && (value != kUninitialized) &&
+ (value != kCombineUninitialized);
+}
+
+inline bool isSleeper(std::uintptr_t value) {
+ return (value == kAboutToWait);
+}
+
+inline bool isInitialized(std::uintptr_t value) {
+ return (value != kUninitialized) && (value != kCombineUninitialized);
+}
+
+inline bool isCombiner(std::uintptr_t value) {
+ auto mode = (value & 0xff);
+ return (mode == kCombineWaiting) || (mode == kCombineUninitialized);
+}
+
+inline bool isWaitingCombiner(std::uintptr_t value) {
+ return (value & 0xff) == kCombineWaiting;
+}
+
+template <typename Waiter>
+CombineFunction loadTask(Waiter* current, std::uintptr_t value) {
+ // if we know that the waiter is a combiner of some sort, it is safe to read
+ // and copy the value of the function in the waiter struct, since we know
+ // that a waiter would have set it before enqueueing
+ if (isCombiner(value)) {
+ return current->function_;
+ }
+
+ return nullptr;
+}
+
+template <typename Waiter>
+void transferCurrentException(Waiter* waiter) {
+ assert(std::current_exception());
+ new (&waiter->storage_) std::exception_ptr(std::current_exception());
+ waiter->futex_.store(kExceptionOccurred, std::memory_order_release);
+}
+
+template <template <typename> class Atomic>
+inline std::uintptr_t tryCombine(
+ Waiter<Atomic>* waiter,
+ std::uintptr_t value,
+ std::uintptr_t next,
+ std::uint64_t iteration,
+ std::chrono::nanoseconds now,
+ CombineFunction task) {
+#ifndef ROCKSDB_LITE
+ // if the waiter has asked for a combine operation, we should combine its
+ // critical section and move on to the next waiter
+ //
+ // the waiter is combinable if the following conditions are satisfied
+ //
+ // 1) the state in the futex word is not uninitialized (kUninitialized)
+ // 2) it has a valid combine function
+ // 3) we are not past the limit of the number of combines we can perform
+ // or the waiter thread been preempted. If the waiter gets preempted,
+ // its better to just execute their critical section before moving on.
+ // As they will have to re-queue themselves after preemption anyway,
+ // leading to further delays in critical section completion
+ //
+ // if all the above are satisfied, then we can combine the critical section.
+ // Note that if the waiter is in a combineable state, that means that it had
+ // finished its writes to both the task and the next_ value. And observing
+ // a waiting state also means that we have acquired the writes to the other
+ // members of the waiter struct, so it's fine to use those values here
+ if (isWaitingCombiner(value) &&
+ (iteration <= kMaxCombineIterations || preempted(value, now))) {
+ try {
+ task();
+ waiter->futex_.store(kCombined, std::memory_order_release);
+ } catch (...) {
+ transferCurrentException(waiter);
+ }
+ return next;
+ }
+#endif // ROCKSDB_LITE
+ return 0;
+}
+
+template <typename Waiter>
+inline std::uintptr_t tryWake(
+ bool publishing,
+ Waiter* waiter,
+ std::uintptr_t value,
+ std::uintptr_t next,
+ std::uintptr_t waker,
+ Waiter*& sleepers,
+ std::uint64_t iteration,
+ CombineFunction task) {
+ // try and combine the waiter's request first, if that succeeds that means
+ // we have successfully executed their critical section and can move on to
+ // the rest of the chain
+ auto now = time();
+ if (tryCombine(waiter, value, next, iteration, now, task)) {
+ return next;
+ }
+
+ // first we see if we can wake the current thread that is spinning
+ if ((!publishing || !preempted(value, now)) && !isSleeper(value)) {
+ // the Metadata class should be trivially destructible as we use placement
+ // new to set the relevant metadata without calling any destructor. We
+ // need to use placement new because the class contains a futex, which is
+ // non-movable and non-copyable
+ using Metadata = _t<std::decay<decltype(waiter->metadata_)>>;
+ static_assert(std::is_trivially_destructible<Metadata>{}, "");
+
+ // we need release here because of the write to waker_ and also because we
+ // are unlocking the mutex, the thread we do the handoff to here should
+ // see the modified data
+ new (&waiter->metadata_) Metadata(waker, bit_cast<uintptr_t>(sleepers));
+ waiter->futex_.store(kWake, std::memory_order_release);
+ return 0;
+ }
+
+ // if the thread is not a sleeper, and we were not able to catch it before
+ // preemption, we can just return a false, it is safe to read next_ because
+ // the thread was preempted. Preemption signals can only come after the
+ // thread has set the next_ pointer, since the timestamp writes only start
+ // occurring after that point
+ //
+ // if a thread was preempted it must have stored next_ in the waiter struct,
+ // as the store to futex_ that resets the value from kUninitialized happens
+ // after the write to next
+ assert(publishing);
+ if (!isSleeper(value)) {
+ // go on to the next one
+ //
+ // Also, we need a memory_order_release here to prevent missed wakeups. A
+ // missed wakeup here can happen when we see that a thread had been
+ // preempted and skip it. Then go on to release the lock, and then when
+ // the thread which got skipped does an exchange on the central storage,
+ // still sees the locked bit, and never gets woken up
+ //
+ // Can we relax this?
+ assert(preempted(value, now));
+ assert(!isCombiner(value));
+ next = waiter->next_.load(std::memory_order_relaxed);
+ waiter->futex_.store(kSkipped, std::memory_order_release);
+ return next;
+ }
+
+ // if we are here the thread is a sleeper
+ //
+ // we attempt to catch the thread before it goes to futex(). If we are able
+ // to catch the thread before it sleeps on a futex, we are done, and don't
+ // need to go any further
+ //
+ // if we are not able to catch the thread before it goes to futex, we
+ // collect the current thread in the list of sleeping threads represented by
+ // sleepers, and return the next thread in the list and return false along
+ // with the previous next value
+ //
+ // it is safe to read the next_ pointer in the waiter struct if we were
+ // unable to catch the thread before it went to futex() because we use
+ // acquire-release ordering for the exchange operation below. And if we see
+ // that the thread was already sleeping, we have synchronized with the write
+ // to next_ in the context of the sleeping thread
+ //
+ // Also we need to set the value of waiters_ and waker_ in the thread before
+ // doing the exchange because we need to pass on the list of sleepers in the
+ // event that we were able to catch the thread before it went to futex().
+ // If we were unable to catch the thread before it slept, these fields will
+ // be ignored when the thread wakes up anyway
+ assert(isSleeper(value));
+ waiter->metadata_.waker_ = waker;
+ waiter->metadata_.waiters_ = folly::bit_cast<std::uintptr_t>(sleepers);
+ auto pre =
+ waiter->metadata_.sleeper_.exchange(kSleeping, std::memory_order_acq_rel);
+
+ // we were able to catch the thread before it went to sleep, return true
+ if (pre != kSleeping) {
+ return 0;
+ }
+
+ // otherwise return false, with the value of next_, it is safe to read next
+ // because of the same logic as when a thread was preempted
+ //
+ // we also need to collect this sleeper in the list of sleepers being built
+ // up
+ next = waiter->next_.load(std::memory_order_relaxed);
+ auto head = folly::bit_cast<std::uintptr_t>(sleepers);
+ waiter->next_.store(head, std::memory_order_relaxed);
+ sleepers = waiter;
+ return next;
+}
+
+template <typename Waiter>
+bool wake(
+ bool publishing,
+ Waiter& waiter,
+ std::uintptr_t waker,
+ Waiter*& sleepers,
+ std::uint64_t iter) {
+ // loop till we find a node that is either at the end of the list (as
+ // specified by waker) or we find a node that is active (as specified by
+ // the last published timestamp of the node)
+ auto current = &waiter;
+ while (current) {
+ // it is important that we load the value of function and next_ after the
+ // initial acquire load. This is required because we need to synchronize
+ // with the construction of the waiter struct before reading from it
+ //
+ // the load from the next_ variable is an optimistic load that assumes
+ // that the waiting thread has probably gone to the waiting state. If the
+ // waiitng thread is in the waiting state (as revealed by the acquire load
+ // from the futex word), we will see a well formed next_ value because it
+ // happens-before the release store to the futex word. The atomic load from
+ // next_ is an optimization to avoid branching before loading and prevent
+ // the compiler from eliding the load altogether (and using a pointer
+ // dereference when needed)
+ auto value = current->futex_.load(std::memory_order_acquire);
+ auto next = current->next_.load(std::memory_order_relaxed);
+ auto task = loadTask(current, value);
+ next =
+ tryWake(publishing, current, value, next, waker, sleepers, iter, task);
+
+ // if there is no next node, we have managed to wake someone up and have
+ // successfully migrated the lock to another thread
+ if (!next) {
+ return true;
+ }
+
+ // we need to read the value of the next node in the list before skipping
+ // it, this is because after we skip it the node might wake up and enqueue
+ // itself, and thereby gain a new next node
+ assert(publishing);
+ current = (next == waker) ? nullptr : extractPtr<Waiter>(next);
+ }
+
+ return false;
+}
+
+template <typename Atomic, typename Proxy, typename Sleepers>
+bool tryUnlockClean(Atomic& state, Proxy& proxy, Sleepers sleepers) {
+ auto expected = proxy.expected_;
+ while (true) {
+ if (state.compare_exchange_strong(
+ expected,
+ kUnlocked,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
+ // if we were able to commit an unlocked, we need to wake up the futex
+ // waiters, if any
+ doFutexWake(sleepers);
+ return true;
+ }
+
+ // if we failed the compare_exchange_strong() above, we check to see if
+ // the failure was because of the presence of a timed waiter. If that
+ // was the case then we try one more time with the kTimedWaiter bit set
+ if (expected == (proxy.expected_ | kTimedWaiter)) {
+ proxy.timedWaiters_ = true;
+ continue;
+ }
+
+ // otherwise break, we have a contention chain
+ return false;
+ }
+}
+
+template <template <typename> class Atomic, bool Publish>
+void DistributedMutex<Atomic, Publish>::unlock(
+ DistributedMutex::DistributedMutexStateProxy proxy) {
+ // we always wake up ready threads and timed waiters if we saw either
+ assert(proxy);
+ assert(!proxy.combined_);
+ SCOPE_EXIT {
+ doFutexWake(proxy.ready_);
+ wakeTimedWaiters(&state_, proxy.timedWaiters_);
+ };
+
+ // if there is a wait queue we are responsible for, try and start wakeups,
+ // don't bother with the mutex state
+ auto sleepers = proxy.waiters_;
+ if (proxy.next_) {
+ if (wake(Publish, *proxy.next_, proxy.waker_, sleepers, 0)) {
+ return;
+ }
+
+ // At this point, if are in the if statement, we were not the terminal
+ // node of the wakeup chain. Terminal nodes have the next_ pointer set to
+ // null in lock()
+ //
+ // So we need to pretend we were the end of the contention chain. Coming
+ // out of a contention chain always has the kLocked state set in the
+ // mutex. Unless there is another contention chain lined up, which does
+ // not matter since we are the terminal node anyway
+ proxy.expected_ = kLocked;
+ }
+
+ for (std::uint64_t i = 0; true; ++i) {
+ // otherwise, since we don't have anyone we need to wake up, we try and
+ // release the mutex just as is
+ //
+ // if this is successful, we can return, the unlock was successful, we have
+ // committed a nice kUnlocked to the central storage, yay
+ if (tryUnlockClean(state_, proxy, sleepers)) {
+ return;
+ }
+
+ // here we have a contention chain built up on the mutex. We grab the
+ // wait queue and start executing wakeups. We leave a locked bit on the
+ // centralized storage and handoff control to the head of the queue
+ //
+ // we use memory_order_acq_rel here because we want to see the
+ // full well-initialized node that the other thread is waiting on
+ //
+ // If we are unable to wake the contention chain, it is possible that when
+ // we come back to looping here, a new contention chain will form. In
+ // that case we need to use kLocked as the waker_ value because the
+ // terminal node of the new chain will see kLocked in the central storage
+ auto head = state_.exchange(kLocked, std::memory_order_acq_rel);
+ recordTimedWaiterAndClearTimedBit(proxy.timedWaiters_, head);
+ auto next = extractPtr<Waiter<Atomic>>(head);
+ auto expected = folly::exchange(proxy.expected_, kLocked);
+ assert((head & kLocked) && (head != kLocked));
+ if (wake(Publish, *next, expected, sleepers, i)) {
+ break;
+ }
+ }
+}
+
+template <typename Atomic, typename Deadline, typename MakeProxy>
+auto timedLock(Atomic& state, Deadline deadline, MakeProxy proxy)
+ -> decltype(std::declval<MakeProxy&>()(nullptr, kLocked, true)) {
+ while (true) {
+ // we put a bit on the central state to show that there is a timed waiter
+ // and go to sleep on the central state
+ //
+ // when this thread goes to unlock the mutex, it will expect a 0b1 in the
+ // mutex state (0b1, not 0b11), but then it will see that the value in the
+ // mutex state is 0b11 and not 0b1, meaning that there might have been
+ // another timed waiter. Even though there might not have been another
+ // timed waiter in the time being. This sort of missed wakeup is
+ // desirable for timed waiters; it helps avoid thundering herds of timed
+ // waiters. Because the mutex is packed in 8 bytes, and we need an
+ // address to be stored in those 8 bytes, we don't have much room to play
+ // with. The only other solution is to issue a futexWake(INT_MAX) to wake
+ // up all waiters when a clean unlock is committed, when a thread saw a
+ // timed waiter in the mutex previously.
+ //
+ // putting a 0b11 here works for a set of reasons that is a superset of
+ // the set of reasons that make it okay to put a kLocked (0b1) in the
+ // mutex state. Now that the thread has put (kTimedWaiter | kLocked)
+ // (0b11) in the mutex state and it expects a kLocked (0b1), there are two
+ // scenarios possible. The first being when there is no contention chain
+ // formation in the mutex from the time a timed waiter got a lock to
+ // unlock. In this case, the unlocker sees a 0b11 in the mutex state,
+ // adjusts to the presence of a timed waiter and cleanly unlocks with a
+ // kUnlocked (0b0). The second is when there is a contention chain.
+ // When a thread puts its address in the mutex and sees the timed bit, it
+ // records the presence of a timed waiter, and then pretends as if it
+ // hadn't seen the timed bit. So future contention chain releases, will
+ // terminate with a kLocked (0b1) and not a (kLocked | kTimedWaiter)
+ // (0b11). This just works naturally with the rest of the algorithm
+ // without incurring a perf hit for the regular non-timed case
+ //
+ // this strategy does however mean, that when threads try to acquire the
+ // mutex and all time out, there will be a wasteful syscall to issue wakeups
+ // to waiting threads. We don't do anything to try and minimize this
+ //
+ // we need to use a fetch_or() here because we need to convey two bits of
+ // information - 1, whether the mutex is locked or not, and 2, whether
+ // there is a timed waiter. The alternative here is to use the second bit
+ // to convey information only, we can use a fetch_set() on the second bit
+ // to make this faster, but that comes at the expense of requiring regular
+ // fast path lock attempts. Which use a single bit read-modify-write for
+ // better performance
+ auto data = kTimedWaiter | kLocked;
+ auto previous = state.fetch_or(data, std::memory_order_acquire);
+ if (!(previous & 0b1)) {
+ assert(!previous);
+ return proxy(nullptr, kLocked, true);
+ }
+
+ // wait on the futex until signalled, if we get a timeout, the try_lock
+ // fails
+ auto result = atomic_wait_until(&state, previous | data, deadline);
+ if (result == std::cv_status::timeout) {
+ return proxy(nullptr, std::uintptr_t{0}, false);
+ }
+ }
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Clock, typename Duration>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock_until(
+ const std::chrono::time_point<Clock, Duration>& deadline) {
+ // fast path for the uncontended case
+ //
+ // we get the time after trying to acquire the mutex because in the
+ // uncontended case, the price of getting the time is about 1/3 of the
+ // actual mutex acquisition. So we only pay the price of that extra bit of
+ // latency when needed
+ //
+ // this is even higher when VDSO is involved on architectures that do not
+ // offer a direct interface to the timestamp counter
+ if (auto state = try_lock()) {
+ return state;
+ }
+
+ // fall back to the timed locking algorithm
+ using Proxy = DistributedMutexStateProxy;
+ return timedLock(
+ state_,
+ deadline,
+ [](Waiter<Atomic>* next, std::uintptr_t expected, bool timedWaiter) {
+ return Proxy{next, expected, timedWaiter};
+ });
+}
+
+template <template <typename> class Atomic, bool TimePublishing>
+template <typename Rep, typename Period>
+typename DistributedMutex<Atomic, TimePublishing>::DistributedMutexStateProxy
+DistributedMutex<Atomic, TimePublishing>::try_lock_for(
+ const std::chrono::duration<Rep, Period>& duration) {
+ // fast path for the uncontended case. Reasoning for doing this here is the
+ // same as in try_lock_until()
+ if (auto state = try_lock()) {
+ return state;
+ }
+
+ // fall back to the timed locking algorithm
+ using Proxy = DistributedMutexStateProxy;
+ auto deadline = std::chrono::steady_clock::now() + duration;
+ return timedLock(
+ state_,
+ deadline,
+ [](Waiter<Atomic>* next, std::uintptr_t expected, bool timedWaiter) {
+ return Proxy{next, expected, timedWaiter};
+ });
+}
+} // namespace distributed_mutex
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.cpp
new file mode 100644
index 00000000..28684ff2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.cpp
@@ -0,0 +1,16 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/DistributedMutex.h>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+
+template class DistributedMutex<std::atomic, true>;
+
+} // namespace distributed_mutex
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.h
new file mode 100644
index 00000000..7acf04f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutex.h
@@ -0,0 +1,304 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <chrono>
+#include <cstdint>
+
+namespace folly {
+namespace detail {
+namespace distributed_mutex {
+
+/**
+ * DistributedMutex is a small, exclusive-only mutex that distributes the
+ * bookkeeping required for mutual exclusion in the stacks of threads that are
+ * contending for it. It has a mode that can combine critical sections when
+ * the mutex experiences contention; this allows the implementation to elide
+ * several expensive coherence and synchronization operations to boost
+ * throughput, surpassing even atomic instructions in some cases. It has a
+ * smaller memory footprint than std::mutex, a similar level of fairness
+ * (better in some cases) and no dependencies on heap allocation. It is the
+ * same width as a single pointer (8 bytes on most platforms), where on the
+ * other hand, std::mutex and pthread_mutex_t are both 40 bytes. It is larger
+ * than some of the other smaller locks, but the wide majority of cases using
+ * the small locks are wasting the difference in alignment padding anyway
+ *
+ * Benchmark results are good - at the time of writing, in the contended case,
+ * for lock/unlock based critical sections, it is about 4-5x faster than the
+ * smaller locks and about ~2x faster than std::mutex. When used in
+ * combinable mode, it is much faster than the alternatives, going more than
+ * 10x faster than the small locks, about 6x faster than std::mutex, 2-3x
+ * faster than flat combining and even faster than std::atomic<> in some
+ * cases, allowing more work with higher throughput. In the uncontended case,
+ * it is a few cycles faster than folly::MicroLock but a bit slower than
+ * std::mutex. DistributedMutex is also resistent to tail latency pathalogies
+ * unlike many of the other mutexes in use, which sleep for large time
+ * quantums to reduce spin churn, this causes elevated latencies for threads
+ * that enter the sleep cycle. The tail latency of lock acquisition can go up
+ * to 10x lower because of a more deterministic scheduling algorithm that is
+ * managed almost entirely in userspace. Detailed results comparing the
+ * throughput and latencies of different mutex implementations and atomics are
+ * at the bottom of folly/synchronization/test/SmallLocksBenchmark.cpp
+ *
+ * Theoretically, write locks promote concurrency when the critical sections
+ * are small as most of the work is done outside the lock. And indeed,
+ * performant concurrent applications go through several pains to limit the
+ * amount of work they do while holding a lock. However, most times, the
+ * synchronization and scheduling overhead of a write lock in the critical
+ * path is so high, that after a certain point, making critical sections
+ * smaller does not actually increase the concurrency of the application and
+ * throughput plateaus. DistributedMutex moves this breaking point to the
+ * level of hardware atomic instructions, so applications keep getting
+ * concurrency even under very high contention. It does this by reducing
+ * cache misses and contention in userspace and in the kernel by making each
+ * thread wait on a thread local node and futex. When combined critical
+ * sections are used DistributedMutex leverages template metaprogramming to
+ * allow the mutex to make better synchronization decisions based on the
+ * layout of the input and output data. This allows threads to keep working
+ * only on their own cache lines without requiring cache coherence operations
+ * when a mutex experiences heavy contention
+ *
+ * Non-timed mutex acquisitions are scheduled through intrusive LIFO
+ * contention chains. Each thread starts by spinning for a short quantum and
+ * falls back to two phased sleeping. Enqueue operations are lock free and
+ * are piggybacked off mutex acquisition attempts. The LIFO behavior of a
+ * contention chain is good in the case where the mutex is held for a short
+ * amount of time, as the head of the chain is likely to not have slept on
+ * futex() after exhausting its spin quantum. This allow us to avoid
+ * unnecessary traversal and syscalls in the fast path with a higher
+ * probability. Even though the contention chains are LIFO, the mutex itself
+ * does not adhere to that scheduling policy globally. During contention,
+ * threads that fail to lock the mutex form a LIFO chain on the central mutex
+ * state, this chain is broken when a wakeup is scheduled, and future enqueue
+ * operations form a new chain. This makes the chains themselves LIFO, but
+ * preserves global fairness through a constant factor which is limited to the
+ * number of concurrent failed mutex acquisition attempts. This binds the
+ * last in first out behavior to the number of contending threads and helps
+ * prevent starvation and latency outliers
+ *
+ * This strategy of waking up wakers one by one in a queue does not scale well
+ * when the number of threads goes past the number of cores. At which point
+ * preemption causes elevated lock acquisition latencies. DistributedMutex
+ * implements a hardware timestamp publishing heuristic to detect and adapt to
+ * preemption.
+ *
+ * DistributedMutex does not have the typical mutex API - it does not satisfy
+ * the Lockable concept. It requires the user to maintain ephemeral bookkeeping
+ * and pass that bookkeeping around to unlock() calls. The API overhead,
+ * however, comes for free when you wrap this mutex for usage with
+ * std::unique_lock, which is the recommended usage (std::lock_guard, in
+ * optimized mode, has no performance benefit over std::unique_lock, so has been
+ * omitted). A benefit of this API is that it disallows incorrect usage where a
+ * thread unlocks a mutex that it does not own, thinking a mutex is functionally
+ * identical to a binary semaphore, which, unlike a mutex, is a suitable
+ * primitive for that usage
+ *
+ * Combined critical sections allow the implementation to elide several
+ * expensive operations during the lifetime of a critical section that cause
+ * slowdowns with regular lock/unlock based usage. DistributedMutex resolves
+ * contention through combining up to a constant factor of 2 contention chains
+ * to prevent issues with fairness and latency outliers, so we retain the
+ * fairness benefits of the lock/unlock implementation with no noticeable
+ * regression when switching between the lock methods. Despite the efficiency
+ * benefits, combined critical sections can only be used when the critical
+ * section does not depend on thread local state and does not introduce new
+ * dependencies between threads when the critical section gets combined. For
+ * example, locking or unlocking an unrelated mutex in a combined critical
+ * section might lead to unexpected results or even undefined behavior. This
+ * can happen if, for example, a different thread unlocks a mutex locked by
+ * the calling thread, leading to undefined behavior as the mutex might not
+ * allow locking and unlocking from unrelated threads (the posix and C++
+ * standard disallow this usage for their mutexes)
+ *
+ * Timed locking through DistributedMutex is implemented through a centralized
+ * algorithm. The underlying contention-chains framework used in
+ * DistributedMutex is not abortable so we build abortability on the side.
+ * All waiters wait on the central mutex state, by setting and resetting bits
+ * within the pointer-length word. Since pointer length atomic integers are
+ * incompatible with futex(FUTEX_WAIT) on most systems, a non-standard
+ * implementation of futex() is used, where wait queues are managed in
+ * user-space (see p1135r0 and folly::ParkingLot for more)
+ */
+template <
+ template <typename> class Atomic = std::atomic,
+ bool TimePublishing = true>
+class DistributedMutex {
+ public:
+ class DistributedMutexStateProxy;
+
+ /**
+ * DistributedMutex is only default constructible, it can neither be moved
+ * nor copied
+ */
+ DistributedMutex();
+ DistributedMutex(DistributedMutex&&) = delete;
+ DistributedMutex(const DistributedMutex&) = delete;
+ DistributedMutex& operator=(DistributedMutex&&) = delete;
+ DistributedMutex& operator=(const DistributedMutex&) = delete;
+
+ /**
+ * Acquires the mutex in exclusive mode
+ *
+ * This returns an ephemeral proxy that contains internal mutex state. This
+ * must be kept around for the duration of the critical section and passed
+ * subsequently to unlock() as an rvalue
+ *
+ * The proxy has no public API and is intended to be for internal usage only
+ *
+ * There are three notable cases where this method causes undefined
+ * behavior:
+ *
+ * - This is not a recursive mutex. Trying to acquire the mutex twice from
+ * the same thread without unlocking it results in undefined behavior
+ * - Thread, coroutine or fiber migrations from within a critical section
+ * are disallowed. This is because the implementation requires owning the
+ * stack frame through the execution of the critical section for both
+ * lock/unlock or combined critical sections. This also means that you
+ * cannot allow another thread, fiber or coroutine to unlock the mutex
+ * - This mutex cannot be used in a program compiled with segmented stacks,
+ * there is currently no way to detect the presence of segmented stacks
+ * at compile time or runtime, so we have no checks against this
+ */
+ DistributedMutexStateProxy lock();
+
+ /**
+ * Unlocks the mutex
+ *
+ * The proxy returned by lock must be passed to unlock as an rvalue. No
+ * other option is possible here, since the proxy is only movable and not
+ * copyable
+ *
+ * It is undefined behavior to unlock from a thread that did not lock the
+ * mutex
+ */
+ void unlock(DistributedMutexStateProxy);
+
+ /**
+ * Try to acquire the mutex
+ *
+ * A non blocking version of the lock() function. The returned object is
+ * contextually convertible to bool. And has the value true when the mutex
+ * was successfully acquired, false otherwise
+ *
+ * This is allowed to return false spuriously, i.e. this is not guaranteed
+ * to return true even when the mutex is currently unlocked. In the event
+ * of a failed acquisition, this does not impose any memory ordering
+ * constraints for other threads
+ */
+ DistributedMutexStateProxy try_lock();
+
+ /**
+ * Try to acquire the mutex, blocking for the given time
+ *
+ * Like try_lock(), this is allowed to fail spuriously and is not guaranteed
+ * to return false even when the mutex is currently unlocked. But only
+ * after the given time has elapsed
+ *
+ * try_lock_for() accepts a duration to block for, and try_lock_until()
+ * accepts an absolute wall clock time point
+ */
+ template <typename Rep, typename Period>
+ DistributedMutexStateProxy try_lock_for(
+ const std::chrono::duration<Rep, Period>& duration);
+
+ /**
+ * Try to acquire the lock, blocking until the given deadline
+ *
+ * Other than the difference in the meaning of the second argument, the
+ * semantics of this function are identical to try_lock_for()
+ */
+ template <typename Clock, typename Duration>
+ DistributedMutexStateProxy try_lock_until(
+ const std::chrono::time_point<Clock, Duration>& deadline);
+
+ /**
+ * Execute a task as a combined critical section
+ *
+ * Unlike traditional lock and unlock methods, lock_combine() enqueues the
+ * passed task for execution on any arbitrary thread. This allows the
+ * implementation to prevent cache line invalidations originating from
+ * expensive synchronization operations. The thread holding the lock is
+ * allowed to execute the task before unlocking, thereby forming a "combined
+ * critical section".
+ *
+ * This idea is inspired by Flat Combining. Flat Combining was introduced
+ * in the SPAA 2010 paper titled "Flat Combining and the
+ * Synchronization-Parallelism Tradeoff", by Danny Hendler, Itai Incze, Nir
+ * Shavit, and Moran Tzafrir -
+ * https://www.cs.bgu.ac.il/~hendlerd/papers/flat-combining.pdf. The
+ * implementation used here is significantly different from that described
+ * in the paper. The high-level goal of reducing the overhead of
+ * synchronization, however, is the same.
+ *
+ * Combined critical sections work best when kept simple. Since the
+ * critical section might be executed on any arbitrary thread, relying on
+ * things like thread local state or mutex locking and unlocking might cause
+ * incorrectness. Associativity is important. For example
+ *
+ * auto one = std::unique_lock{one_};
+ * two_.lock_combine([&]() {
+ * if (bar()) {
+ * one.unlock();
+ * }
+ * });
+ *
+ * This has the potential to cause undefined behavior because mutexes are
+ * only meant to be acquired and released from the owning thread. Similar
+ * errors can arise from a combined critical section introducing implicit
+ * dependencies based on the state of the combining thread. For example
+ *
+ * // thread 1
+ * auto one = std::unique_lock{one_};
+ * auto two = std::unique_lock{two_};
+ *
+ * // thread 2
+ * two_.lock_combine([&]() {
+ * auto three = std::unique_lock{three_};
+ * });
+ *
+ * Here, because we used a combined critical section, we have introduced a
+ * dependency from one -> three that might not obvious to the reader
+ *
+ * This function is exception-safe. If the passed task throws an exception,
+ * it will be propagated to the caller, even if the task is running on
+ * another thread
+ *
+ * There are three notable cases where this method causes undefined
+ * behavior:
+ *
+ * - This is not a recursive mutex. Trying to acquire the mutex twice from
+ * the same thread without unlocking it results in undefined behavior
+ * - Thread, coroutine or fiber migrations from within a critical section
+ * are disallowed. This is because the implementation requires owning the
+ * stack frame through the execution of the critical section for both
+ * lock/unlock or combined critical sections. This also means that you
+ * cannot allow another thread, fiber or coroutine to unlock the mutex
+ * - This mutex cannot be used in a program compiled with segmented stacks,
+ * there is currently no way to detect the presence of segmented stacks
+ * at compile time or runtime, so we have no checks against this
+ */
+ template <typename Task>
+ auto lock_combine(Task task) -> decltype(std::declval<const Task&>()());
+
+ private:
+ Atomic<std::uintptr_t> state_{0};
+};
+
+} // namespace distributed_mutex
+} // namespace detail
+
+/**
+ * Bring the default instantiation of DistributedMutex into the folly
+ * namespace without requiring any template arguments for public usage
+ */
+extern template class detail::distributed_mutex::DistributedMutex<>;
+using DistributedMutex = detail::distributed_mutex::DistributedMutex<>;
+
+} // namespace folly
+
+#include <folly/synchronization/DistributedMutex-inl.h>
+#include <folly/synchronization/DistributedMutexSpecializations.h>
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutexSpecializations.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutexSpecializations.h
new file mode 100644
index 00000000..451aa69b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/DistributedMutexSpecializations.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/DistributedMutex.h>
+#include <folly/synchronization/detail/ProxyLockable.h>
+
+/**
+ * Specializations for DistributedMutex allow us to use it like a normal
+ * mutex. Even though it has a non-usual interface
+ */
+namespace std {
+template <template <typename> class Atom, bool TimePublishing>
+class unique_lock<
+ ::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
+ : public ::folly::detail::ProxyLockableUniqueLock<
+ ::folly::detail::distributed_mutex::
+ DistributedMutex<Atom, TimePublishing>> {
+ public:
+ using ::folly::detail::ProxyLockableUniqueLock<
+ ::folly::detail::distributed_mutex::
+ DistributedMutex<Atom, TimePublishing>>::ProxyLockableUniqueLock;
+};
+
+template <template <typename> class Atom, bool TimePublishing>
+class lock_guard<
+ ::folly::detail::distributed_mutex::DistributedMutex<Atom, TimePublishing>>
+ : public ::folly::detail::ProxyLockableLockGuard<
+ ::folly::detail::distributed_mutex::
+ DistributedMutex<Atom, TimePublishing>> {
+ public:
+ using ::folly::detail::ProxyLockableLockGuard<
+ ::folly::detail::distributed_mutex::
+ DistributedMutex<Atom, TimePublishing>>::ProxyLockableLockGuard;
+};
+} // namespace std
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.cpp
new file mode 100644
index 00000000..74fba8e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/ParkingLot.h>
+
+#include <array>
+
+namespace folly {
+namespace parking_lot_detail {
+
+Bucket& Bucket::bucketFor(uint64_t key) {
+ constexpr size_t const kNumBuckets = 4096;
+
+ // Statically allocating this lets us use this in allocation-sensitive
+ // contexts. This relies on the assumption that std::mutex won't dynamically
+ // allocate memory, which we assume to be the case on Linux and iOS.
+ static Indestructible<std::array<Bucket, kNumBuckets>> gBuckets;
+ return (*gBuckets)[key % kNumBuckets];
+}
+
+std::atomic<uint64_t> idallocator{0};
+
+} // namespace parking_lot_detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.h
new file mode 100644
index 00000000..bb324fb0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/ParkingLot.h
@@ -0,0 +1,318 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <condition_variable>
+#include <mutex>
+
+#include <folly/hash/Hash.h>
+#include <folly/Indestructible.h>
+#include <folly/Unit.h>
+
+namespace folly {
+
+namespace parking_lot_detail {
+
+struct WaitNodeBase {
+ const uint64_t key_;
+ const uint64_t lotid_;
+ WaitNodeBase* next_{nullptr};
+ WaitNodeBase* prev_{nullptr};
+
+ // tricky: hold both bucket and node mutex to write, either to read
+ bool signaled_;
+ std::mutex mutex_;
+ std::condition_variable cond_;
+
+ WaitNodeBase(uint64_t key, uint64_t lotid)
+ : key_(key), lotid_(lotid), signaled_(false) {}
+
+ template <typename Clock, typename Duration>
+ std::cv_status wait(std::chrono::time_point<Clock, Duration> deadline) {
+ std::cv_status status = std::cv_status::no_timeout;
+ std::unique_lock<std::mutex> nodeLock(mutex_);
+ while (!signaled_ && status != std::cv_status::timeout) {
+ if (deadline != std::chrono::time_point<Clock, Duration>::max()) {
+ status = cond_.wait_until(nodeLock, deadline);
+ } else {
+ cond_.wait(nodeLock);
+ }
+ }
+ return status;
+ }
+
+ void wake() {
+ std::lock_guard<std::mutex> nodeLock(mutex_);
+ signaled_ = true;
+ cond_.notify_one();
+ }
+
+ bool signaled() {
+ return signaled_;
+ }
+};
+
+extern std::atomic<uint64_t> idallocator;
+
+// Our emulated futex uses 4096 lists of wait nodes. There are two levels
+// of locking: a per-list mutex that controls access to the list and a
+// per-node mutex, condvar, and bool that are used for the actual wakeups.
+// The per-node mutex allows us to do precise wakeups without thundering
+// herds.
+struct Bucket {
+ std::mutex mutex_;
+ WaitNodeBase* head_;
+ WaitNodeBase* tail_;
+ std::atomic<uint64_t> count_;
+
+ static Bucket& bucketFor(uint64_t key);
+
+ void push_back(WaitNodeBase* node) {
+ if (tail_) {
+ assert(head_);
+ node->prev_ = tail_;
+ tail_->next_ = node;
+ tail_ = node;
+ } else {
+ tail_ = node;
+ head_ = node;
+ }
+ }
+
+ void erase(WaitNodeBase* node) {
+ assert(count_.load(std::memory_order_relaxed) >= 1);
+ if (head_ == node && tail_ == node) {
+ assert(node->prev_ == nullptr);
+ assert(node->next_ == nullptr);
+ head_ = nullptr;
+ tail_ = nullptr;
+ } else if (head_ == node) {
+ assert(node->prev_ == nullptr);
+ assert(node->next_);
+ head_ = node->next_;
+ head_->prev_ = nullptr;
+ } else if (tail_ == node) {
+ assert(node->next_ == nullptr);
+ assert(node->prev_);
+ tail_ = node->prev_;
+ tail_->next_ = nullptr;
+ } else {
+ assert(node->next_);
+ assert(node->prev_);
+ node->next_->prev_ = node->prev_;
+ node->prev_->next_ = node->next_;
+ }
+ count_.fetch_sub(1, std::memory_order_relaxed);
+ }
+};
+
+} // namespace parking_lot_detail
+
+enum class UnparkControl {
+ RetainContinue,
+ RemoveContinue,
+ RetainBreak,
+ RemoveBreak,
+};
+
+enum class ParkResult {
+ Skip,
+ Unpark,
+ Timeout,
+};
+
+/*
+ * ParkingLot provides an interface that is similar to Linux's futex
+ * system call, but with additional functionality. It is implemented
+ * in a portable way on top of std::mutex and std::condition_variable.
+ *
+ * Additional reading:
+ * https://webkit.org/blog/6161/locking-in-webkit/
+ * https://github.com/WebKit/webkit/blob/master/Source/WTF/wtf/ParkingLot.h
+ * https://locklessinc.com/articles/futex_cheat_sheet/
+ *
+ * The main difference from futex is that park/unpark take lambdas,
+ * such that nearly anything can be done while holding the bucket
+ * lock. Unpark() lambda can also be used to wake up any number of
+ * waiters.
+ *
+ * ParkingLot is templated on the data type, however, all ParkingLot
+ * implementations are backed by a single static array of buckets to
+ * avoid large memory overhead. Lambdas will only ever be called on
+ * the specific ParkingLot's nodes.
+ */
+template <typename Data = Unit>
+class ParkingLot {
+ const uint64_t lotid_;
+ ParkingLot(const ParkingLot&) = delete;
+
+ struct WaitNode : public parking_lot_detail::WaitNodeBase {
+ const Data data_;
+
+ template <typename D>
+ WaitNode(uint64_t key, uint64_t lotid, D&& data)
+ : WaitNodeBase(key, lotid), data_(std::forward<D>(data)) {}
+ };
+
+ public:
+ ParkingLot() : lotid_(parking_lot_detail::idallocator++) {}
+
+ /* Park API
+ *
+ * Key is almost always the address of a variable.
+ *
+ * ToPark runs while holding the bucket lock: usually this
+ * is a check to see if we can sleep, by checking waiter bits.
+ *
+ * PreWait is usually used to implement condition variable like
+ * things, such that you can unlock the condition variable's lock at
+ * the appropriate time.
+ */
+ template <typename Key, typename D, typename ToPark, typename PreWait>
+ ParkResult park(const Key key, D&& data, ToPark&& toPark, PreWait&& preWait) {
+ return park_until(
+ key,
+ std::forward<D>(data),
+ std::forward<ToPark>(toPark),
+ std::forward<PreWait>(preWait),
+ std::chrono::steady_clock::time_point::max());
+ }
+
+ template <
+ typename Key,
+ typename D,
+ typename ToPark,
+ typename PreWait,
+ typename Clock,
+ typename Duration>
+ ParkResult park_until(
+ const Key key,
+ D&& data,
+ ToPark&& toPark,
+ PreWait&& preWait,
+ std::chrono::time_point<Clock, Duration> deadline);
+
+ template <
+ typename Key,
+ typename D,
+ typename ToPark,
+ typename PreWait,
+ typename Rep,
+ typename Period>
+ ParkResult park_for(
+ const Key key,
+ D&& data,
+ ToPark&& toPark,
+ PreWait&& preWait,
+ std::chrono::duration<Rep, Period>& timeout) {
+ return park_until(
+ key,
+ std::forward<D>(data),
+ std::forward<ToPark>(toPark),
+ std::forward<PreWait>(preWait),
+ timeout + std::chrono::steady_clock::now());
+ }
+
+ /*
+ * Unpark API
+ *
+ * Key is the same uniqueaddress used in park(), and is used as a
+ * hash key for lookup of waiters.
+ *
+ * Unparker is a function that is given the Data parameter, and
+ * returns an UnparkControl. The Remove* results will remove and
+ * wake the waiter, the Ignore/Stop results will not, while stopping
+ * or continuing iteration of the waiter list.
+ */
+ template <typename Key, typename Unparker>
+ void unpark(const Key key, Unparker&& func);
+};
+
+template <typename Data>
+template <
+ typename Key,
+ typename D,
+ typename ToPark,
+ typename PreWait,
+ typename Clock,
+ typename Duration>
+ParkResult ParkingLot<Data>::park_until(
+ const Key bits,
+ D&& data,
+ ToPark&& toPark,
+ PreWait&& preWait,
+ std::chrono::time_point<Clock, Duration> deadline) {
+ auto key = hash::twang_mix64(uint64_t(bits));
+ auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
+ WaitNode node(key, lotid_, std::forward<D>(data));
+
+ {
+ // A: Must be seq_cst. Matches B.
+ bucket.count_.fetch_add(1, std::memory_order_seq_cst);
+
+ std::unique_lock<std::mutex> bucketLock(bucket.mutex_);
+
+ if (!std::forward<ToPark>(toPark)()) {
+ bucketLock.unlock();
+ bucket.count_.fetch_sub(1, std::memory_order_relaxed);
+ return ParkResult::Skip;
+ }
+
+ bucket.push_back(&node);
+ } // bucketLock scope
+
+ std::forward<PreWait>(preWait)();
+
+ auto status = node.wait(deadline);
+
+ if (status == std::cv_status::timeout) {
+ // it's not really a timeout until we unlink the unsignaled node
+ std::lock_guard<std::mutex> bucketLock(bucket.mutex_);
+ if (!node.signaled()) {
+ bucket.erase(&node);
+ return ParkResult::Timeout;
+ }
+ }
+
+ return ParkResult::Unpark;
+}
+
+template <typename Data>
+template <typename Key, typename Func>
+void ParkingLot<Data>::unpark(const Key bits, Func&& func) {
+ auto key = hash::twang_mix64(uint64_t(bits));
+ auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
+ // B: Must be seq_cst. Matches A. If true, A *must* see in seq_cst
+ // order any atomic updates in toPark() (and matching updates that
+ // happen before unpark is called)
+ if (bucket.count_.load(std::memory_order_seq_cst) == 0) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> bucketLock(bucket.mutex_);
+
+ for (auto iter = bucket.head_; iter != nullptr;) {
+ auto node = static_cast<WaitNode*>(iter);
+ iter = iter->next_;
+ if (node->key_ == key && node->lotid_ == lotid_) {
+ auto result = std::forward<Func>(func)(node->data_);
+ if (result == UnparkControl::RemoveBreak ||
+ result == UnparkControl::RemoveContinue) {
+ // we unlink, but waiter destroys the node
+ bucket.erase(node);
+
+ node->wake();
+ }
+ if (result == UnparkControl::RemoveBreak ||
+ result == UnparkControl::RetainBreak) {
+ return;
+ }
+ }
+ }
+}
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.cpp
new file mode 100644
index 00000000..0c1fe2b9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.cpp
@@ -0,0 +1,12 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/WaitOptions.h>
+
+namespace folly {
+
+constexpr std::chrono::nanoseconds WaitOptions::Defaults::spin_max;
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.h
new file mode 100644
index 00000000..b28deb54
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/WaitOptions.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <chrono>
+
+namespace folly {
+
+/// WaitOptions
+///
+/// Various synchronization primitives as well as various concurrent data
+/// structures built using them have operations which might wait. This type
+/// represents a set of options for controlling such waiting.
+class WaitOptions {
+ public:
+ struct Defaults {
+ /// spin_max
+ ///
+ /// If multiple threads are actively using a synchronization primitive,
+ /// whether indirectly via a higher-level concurrent data structure or
+ /// directly, where the synchronization primitive has an operation which
+ /// waits and another operation which wakes the waiter, it is common for
+ /// wait and wake events to happen almost at the same time. In this state,
+ /// we lose big 50% of the time if the wait blocks immediately.
+ ///
+ /// We can improve our chances of being waked immediately, before blocking,
+ /// by spinning for a short duration, although we have to balance this
+ /// against the extra cpu utilization, latency reduction, power consumption,
+ /// and priority inversion effect if we end up blocking anyway.
+ ///
+ /// We use a default maximum of 2 usec of spinning. As partial consolation,
+ /// since spinning as implemented in folly uses the pause instruction where
+ /// available, we give a small speed boost to the colocated hyperthread.
+ ///
+ /// On circa-2013 devbox hardware, it costs about 7 usec to FUTEX_WAIT and
+ /// then be awoken. Spins on this hw take about 7 nsec, where all but 0.5
+ /// nsec is the pause instruction.
+ static constexpr std::chrono::nanoseconds spin_max =
+ std::chrono::microseconds(2);
+ };
+
+ std::chrono::nanoseconds spin_max() const {
+ return spin_max_;
+ }
+ WaitOptions& spin_max(std::chrono::nanoseconds dur) {
+ spin_max_ = dur;
+ return *this;
+ }
+
+ private:
+ std::chrono::nanoseconds spin_max_ = Defaults::spin_max;
+};
+
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/InlineFunctionRef.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/InlineFunctionRef.h
new file mode 100644
index 00000000..6782c792
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/InlineFunctionRef.h
@@ -0,0 +1,219 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdint>
+#include <type_traits>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+#include <folly/functional/Invoke.h>
+#include <folly/lang/Launder.h>
+
+namespace folly {
+namespace detail {
+
+/**
+ * InlineFunctionRef is similar to folly::FunctionRef but has the additional
+ * benefit of being able to store the function it was instantiated with inline
+ * in a buffer of the given capacity. Inline storage is only used if the
+ * function object and a pointer (for type-erasure) are small enough to fit in
+ * the templated size. If there is not enough in-situ capacity for the
+ * callable, this just stores a reference to the function object like
+ * FunctionRef.
+ *
+ * This helps give a perf boost in the case where the data gets separated from
+ * the point of invocation. If, for example, at the point of invocation, the
+ * InlineFunctionRef object is not cached, a remote memory/cache read might be
+ * required to invoke the original callable. Customizable inline storage
+ * helps tune storage so we can store a type-erased callable with better
+ * performance and locality. A real-life example of this might be a
+ * folly::FunctionRef with a function pointer. The folly::FunctionRef would
+ * point to the function pointer object in a remote location. This causes a
+ * double-indirection at the point of invocation, and if that memory is dirty,
+ * or not cached, it would cause additional cache misses. On the other hand
+ * with InlineFunctionRef, inline storage would store the value of the
+ * function pointer, avoiding the need to do a remote lookup to fetch the
+ * value of the function pointer.
+ *
+ * To prevent misuse, InlineFunctionRef disallows construction from an lvalue
+ * callable. This is to prevent usage where a user relies on the callable's
+ * state after invocation through InlineFunctionRef. This has the potential
+ * to copy the callable into inline storage when the callable is small, so we
+ * might not use the same function when invoking, but rather a copy of it.
+ *
+ * Also note that InlineFunctionRef will always invoke the const qualified
+ * version of the call operator for any callable that is passed. Regardless
+ * of whether it has a non-const version. This is done to enforce the logical
+ * constraint of function state being immutable.
+ *
+ * This class is always trivially-copyable (and therefore
+ * trivially-destructible), making it suitable for use in a union without
+ * requiring manual destruction.
+ */
+template <typename FunctionType, std::size_t Size>
+class InlineFunctionRef;
+
+template <typename ReturnType, typename... Args, std::size_t Size>
+class InlineFunctionRef<ReturnType(Args...), Size> {
+ using Storage =
+ _t<std::aligned_storage<Size - sizeof(uintptr_t), sizeof(uintptr_t)>>;
+ using Call = ReturnType (*)(const Storage&, Args&&...);
+
+ struct InSituTag {};
+ struct RefTag {};
+
+ static_assert(
+ (Size % sizeof(uintptr_t)) == 0,
+ "Size has to be a multiple of sizeof(uintptr_t)");
+ static_assert(Size >= 2 * sizeof(uintptr_t), "This doesn't work");
+ static_assert(alignof(Call) == alignof(Storage), "Mismatching alignments");
+
+ // This defines a mode tag that is used in the construction of
+ // InlineFunctionRef to determine the storage and indirection method for the
+ // passed callable.
+ //
+ // This requires that the we pass in a type that is not ref-qualified.
+ template <typename Func>
+ using ConstructMode = _t<std::conditional<
+ folly::is_trivially_copyable<Func>{} &&
+ (sizeof(Func) <= sizeof(Storage)) &&
+ (alignof(Func) <= alignof(Storage)),
+ InSituTag,
+ RefTag>>;
+
+ public:
+ /**
+ * InlineFunctionRef can be constructed from a nullptr, callable or another
+ * InlineFunctionRef with the same size. These are the constructors that
+ * don't take a callable.
+ *
+ * InlineFunctionRef is meant to be trivially copyable so we default the
+ * constructors and assignment operators.
+ */
+ InlineFunctionRef(std::nullptr_t) : call_{nullptr} {}
+ InlineFunctionRef() : call_{nullptr} {}
+ InlineFunctionRef(const InlineFunctionRef& other) = default;
+ InlineFunctionRef(InlineFunctionRef&&) = default;
+ InlineFunctionRef& operator=(const InlineFunctionRef&) = default;
+ InlineFunctionRef& operator=(InlineFunctionRef&&) = default;
+
+ /**
+ * Constructors from callables.
+ *
+ * If all of the following conditions are satisfied, then we store the
+ * callable in the inline storage:
+ *
+ * 1) The function has been passed as an rvalue, meaning that there is no
+ * use of the original in the user's code after it has been passed to
+ * us.
+ * 2) Size of the callable is less than the size of the inline storage
+ * buffer.
+ * 3) The callable is trivially constructible and destructible.
+ *
+ * If any one of the above conditions is not satisfied, we fall back to
+ * reference semantics and store the function as a pointer, and add a level
+ * of indirection through type erasure.
+ */
+ template <
+ typename Func,
+ _t<std::enable_if<
+ !std::is_same<_t<std::decay<Func>>, InlineFunctionRef>{} &&
+ !std::is_reference<Func>{} &&
+ std::is_convertible<
+ decltype(std::declval<Func&&>()(std::declval<Args&&>()...)),
+ ReturnType>{}>>* = nullptr>
+ InlineFunctionRef(Func&& func) {
+ // We disallow construction from lvalues, so assert that this is not a
+ // reference type. When invoked with an lvalue, Func is a lvalue
+ // reference type, when invoked with an rvalue, Func is not ref-qualified.
+ static_assert(
+ !std::is_reference<Func>{},
+ "InlineFunctionRef cannot be used with lvalues");
+ static_assert(std::is_rvalue_reference<Func&&>{}, "");
+ construct(ConstructMode<Func>{}, folly::as_const(func));
+ }
+
+ /**
+ * The call operator uses the function pointer and a reference to the
+ * storage to do the dispatch. The function pointer takes care of the
+ * appropriate casting.
+ */
+ ReturnType operator()(Args... args) const {
+ return call_(storage_, static_cast<Args&&>(args)...);
+ }
+
+ /**
+ * We have a function engaged if the call function points to anything other
+ * than null.
+ */
+ operator bool() const noexcept {
+ return call_;
+ }
+
+ private:
+ friend class InlineFunctionRefTest;
+
+ /**
+ * Inline storage constructor implementation.
+ */
+ template <typename Func>
+ void construct(InSituTag, Func& func) {
+ using Value = _t<std::remove_reference<Func>>;
+
+ // Assert that the following two assumptions are valid
+ // 1) fit in the storage space we have and match alignments, and
+ // 2) be invocable in a const context, it does not make sense to copy a
+ // callable into inline storage if it makes state local
+ // modifications.
+ static_assert(alignof(Value) <= alignof(Storage), "");
+ static_assert(is_invocable<const _t<std::decay<Func>>, Args&&...>{}, "");
+ static_assert(folly::is_trivially_copyable<Value>{}, "");
+
+ new (&storage_) Value{func};
+ call_ = &callInline<Value>;
+ }
+
+ /**
+ * Ref storage constructor implementation. This is identical to
+ * folly::FunctionRef.
+ */
+ template <typename Func>
+ void construct(RefTag, Func& func) {
+ // store a pointer to the function
+ using Pointer = _t<std::add_pointer<_t<std::remove_reference<Func>>>>;
+ new (&storage_) Pointer{&func};
+ call_ = &callPointer<Pointer>;
+ }
+
+ template <typename Func>
+ static ReturnType callInline(const Storage& object, Args&&... args) {
+ // The only type of pointer allowed is a function pointer, no other
+ // pointer types are invocable.
+ static_assert(
+ !std::is_pointer<Func>::value ||
+ std::is_function<_t<std::remove_pointer<Func>>>::value,
+ "");
+ return (*folly::launder(reinterpret_cast<const Func*>(&object)))(
+ static_cast<Args&&>(args)...);
+ }
+
+ template <typename Func>
+ static ReturnType callPointer(const Storage& object, Args&&... args) {
+ // When the function we were instantiated with was not trivial, the given
+ // pointer points to a pointer, which pointers to the callable. So we
+ // cast to a pointer and then to the pointee.
+ static_assert(std::is_pointer<Func>::value, "");
+ return (**folly::launder(reinterpret_cast<const Func*>(&object)))(
+ static_cast<Args&&>(args)...);
+ }
+
+ Call call_;
+ Storage storage_;
+};
+
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable-inl.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable-inl.h
new file mode 100644
index 00000000..573330ce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable-inl.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Optional.h>
+#include <folly/Portability.h>
+#include <folly/Utility.h>
+
+#include <cassert>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <utility>
+
+namespace folly {
+namespace detail {
+namespace proxylockable_detail {
+template <typename Bool>
+void throwIfAlreadyLocked(Bool&& locked) {
+ if (kIsDebug && locked) {
+ throw std::system_error{
+ std::make_error_code(std::errc::resource_deadlock_would_occur)};
+ }
+}
+
+template <typename Bool>
+void throwIfNotLocked(Bool&& locked) {
+ if (kIsDebug && !locked) {
+ throw std::system_error{
+ std::make_error_code(std::errc::operation_not_permitted)};
+ }
+}
+
+template <typename Bool>
+void throwIfNoMutex(Bool&& mutex) {
+ if (kIsDebug && !mutex) {
+ throw std::system_error{
+ std::make_error_code(std::errc::operation_not_permitted)};
+ }
+}
+} // namespace proxylockable_detail
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::~ProxyLockableUniqueLock() {
+ if (owns_lock()) {
+ unlock();
+ }
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ mutex_type& mtx) noexcept {
+ proxy_.emplace(mtx.lock());
+ mutex_ = std::addressof(mtx);
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ ProxyLockableUniqueLock&& a) noexcept {
+ *this = std::move(a);
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>& ProxyLockableUniqueLock<Mutex>::operator=(
+ ProxyLockableUniqueLock&& other) noexcept {
+ proxy_ = std::move(other.proxy_);
+ mutex_ = folly::exchange(other.mutex_, nullptr);
+ return *this;
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ std::defer_lock_t) noexcept {
+ mutex_ = std::addressof(mtx);
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ std::try_to_lock_t) {
+ mutex_ = std::addressof(mtx);
+ if (auto state = mtx.try_lock()) {
+ proxy_.emplace(std::move(state));
+ }
+}
+
+template <typename Mutex>
+template <typename Rep, typename Period>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ const std::chrono::duration<Rep, Period>& duration) {
+ mutex_ = std::addressof(mtx);
+ if (auto state = mtx.try_lock_for(duration)) {
+ proxy_.emplace(std::move(state));
+ }
+}
+
+template <typename Mutex>
+template <typename Clock, typename Duration>
+ProxyLockableUniqueLock<Mutex>::ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ const std::chrono::time_point<Clock, Duration>& time) {
+ mutex_ = std::addressof(mtx);
+ if (auto state = mtx.try_lock_until(time)) {
+ proxy_.emplace(std::move(state));
+ }
+}
+
+template <typename Mutex>
+void ProxyLockableUniqueLock<Mutex>::lock() {
+ proxylockable_detail::throwIfAlreadyLocked(proxy_);
+ proxylockable_detail::throwIfNoMutex(mutex_);
+
+ proxy_.emplace(mutex_->lock());
+}
+
+template <typename Mutex>
+void ProxyLockableUniqueLock<Mutex>::unlock() {
+ proxylockable_detail::throwIfNoMutex(mutex_);
+ proxylockable_detail::throwIfNotLocked(proxy_);
+
+ mutex_->unlock(std::move(*proxy_));
+ proxy_.reset();
+}
+
+template <typename Mutex>
+bool ProxyLockableUniqueLock<Mutex>::try_lock() {
+ proxylockable_detail::throwIfNoMutex(mutex_);
+ proxylockable_detail::throwIfAlreadyLocked(proxy_);
+
+ if (auto state = mutex_->try_lock()) {
+ proxy_.emplace(std::move(state));
+ return true;
+ }
+
+ return false;
+}
+
+template <typename Mutex>
+template <typename Rep, typename Period>
+bool ProxyLockableUniqueLock<Mutex>::try_lock_for(
+ const std::chrono::duration<Rep, Period>& duration) {
+ proxylockable_detail::throwIfNoMutex(mutex_);
+ proxylockable_detail::throwIfAlreadyLocked(proxy_);
+
+ if (auto state = mutex_->try_lock_for(duration)) {
+ proxy_.emplace(std::move(state));
+ return true;
+ }
+
+ return false;
+}
+
+template <typename Mutex>
+template <typename Clock, typename Duration>
+bool ProxyLockableUniqueLock<Mutex>::try_lock_until(
+ const std::chrono::time_point<Clock, Duration>& time) {
+ proxylockable_detail::throwIfNoMutex(mutex_);
+ proxylockable_detail::throwIfAlreadyLocked(proxy_);
+
+ if (auto state = mutex_->try_lock_until(time)) {
+ proxy_.emplace(std::move(state));
+ return true;
+ }
+
+ return false;
+}
+
+template <typename Mutex>
+void ProxyLockableUniqueLock<Mutex>::swap(
+ ProxyLockableUniqueLock& other) noexcept {
+ std::swap(mutex_, other.mutex_);
+ std::swap(proxy_, other.proxy_);
+}
+
+template <typename Mutex>
+typename ProxyLockableUniqueLock<Mutex>::mutex_type*
+ProxyLockableUniqueLock<Mutex>::mutex() const noexcept {
+ return mutex_;
+}
+
+template <typename Mutex>
+typename ProxyLockableUniqueLock<Mutex>::proxy_type*
+ProxyLockableUniqueLock<Mutex>::proxy() const noexcept {
+ return proxy_ ? std::addressof(proxy_.value()) : nullptr;
+}
+
+template <typename Mutex>
+bool ProxyLockableUniqueLock<Mutex>::owns_lock() const noexcept {
+ return proxy_.has_value();
+}
+
+template <typename Mutex>
+ProxyLockableUniqueLock<Mutex>::operator bool() const noexcept {
+ return owns_lock();
+}
+
+template <typename Mutex>
+ProxyLockableLockGuard<Mutex>::ProxyLockableLockGuard(mutex_type& mtx)
+ : ProxyLockableUniqueLock<Mutex>{mtx} {}
+
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable.h
new file mode 100644
index 00000000..af922daf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/ProxyLockable.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/Optional.h>
+
+#include <mutex>
+
+namespace folly {
+namespace detail {
+
+/**
+ * ProxyLockable is a "concept" that is used usually for mutexes that don't
+ * return void, but rather a proxy object that contains data that should be
+ * passed to the unlock function.
+ *
+ * This is in contrast with the normal Lockable concept that imposes no
+ * requirement on the return type of lock(), and requires an unlock() with no
+ * parameters. Here we require that lock() returns non-void and that unlock()
+ * accepts the return type of lock() by value, rvalue-reference or
+ * const-reference
+ *
+ * Here we define two classes, that can be used by the top level to implement
+ * specializations for std::unique_lock and std::lock_guard. Both
+ * ProxyLockableUniqueLock and ProxyLockableLockGuard implement the entire
+ * interface of std::unique_lock and std::lock_guard respectively
+ */
+template <typename Mutex>
+class ProxyLockableUniqueLock {
+ public:
+ using mutex_type = Mutex;
+ using proxy_type =
+ _t<std::decay<decltype(std::declval<mutex_type>().lock())>>;
+
+ /**
+ * Default constructor initializes the unique_lock to an empty state
+ */
+ ProxyLockableUniqueLock() = default;
+
+ /**
+ * Destructor releases the mutex if it is locked
+ */
+ ~ProxyLockableUniqueLock();
+
+ /**
+ * Move constructor and move assignment operators take state from the other
+ * lock
+ */
+ ProxyLockableUniqueLock(ProxyLockableUniqueLock&& other) noexcept;
+ ProxyLockableUniqueLock& operator=(ProxyLockableUniqueLock&&) noexcept;
+
+ /**
+ * Locks the mutex, blocks until the mutex can be acquired.
+ *
+ * The mutex is guaranteed to be acquired after this function returns.
+ */
+ ProxyLockableUniqueLock(mutex_type&) noexcept;
+
+ /**
+ * Explicit locking constructors to control how the lock() method is called
+ *
+ * std::defer_lock_t causes the mutex to get tracked, but not locked
+ * std::try_to_lock_t causes try_lock() to be called. The current object is
+ * converts to true if the lock was successful
+ */
+ ProxyLockableUniqueLock(mutex_type& mtx, std::defer_lock_t) noexcept;
+ ProxyLockableUniqueLock(mutex_type& mtx, std::try_to_lock_t);
+
+ /**
+ * Timed locking constructors
+ */
+ template <typename Rep, typename Period>
+ ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ const std::chrono::duration<Rep, Period>& duration);
+ template <typename Clock, typename Duration>
+ ProxyLockableUniqueLock(
+ mutex_type& mtx,
+ const std::chrono::time_point<Clock, Duration>& time);
+
+ /**
+ * Lock and unlock methods
+ *
+ * lock() and try_lock() throw if the mutex is already locked, or there is
+ * no mutex. unlock() throws if there is no mutex or if the mutex was not
+ * locked
+ */
+ void lock();
+ void unlock();
+ bool try_lock();
+
+ /**
+ * Timed locking methods
+ *
+ * These throw if there was no mutex, or if the mutex was already locked
+ */
+ template <typename Rep, typename Period>
+ bool try_lock_for(const std::chrono::duration<Rep, Period>& duration);
+ template <typename Clock, typename Duration>
+ bool try_lock_until(const std::chrono::time_point<Clock, Duration>& time);
+
+ /**
+ * Swap this unique lock with the other one
+ */
+ void swap(ProxyLockableUniqueLock& other) noexcept;
+
+ /**
+ * Returns true if the unique lock contains a lock and also has acquired an
+ * exclusive lock successfully
+ */
+ bool owns_lock() const noexcept;
+ explicit operator bool() const noexcept;
+
+ /**
+ * mutex() return a pointer to the mutex if there is a contained mutex and
+ * proxy() returns a pointer to the contained proxy if the mutex is locked
+ *
+ * If the unique lock was not constructed with a mutex, then mutex() returns
+ * nullptr. If the mutex is not locked, then proxy() returns nullptr
+ */
+ mutex_type* mutex() const noexcept;
+ proxy_type* proxy() const noexcept;
+
+ private:
+ friend class ProxyLockableTest;
+
+ /**
+ * If the optional has a value, the mutex is locked, if it is empty, it is
+ * not
+ */
+ mutable folly::Optional<proxy_type> proxy_{};
+ mutex_type* mutex_{nullptr};
+};
+
+template <typename Mutex>
+class ProxyLockableLockGuard : private ProxyLockableUniqueLock<Mutex> {
+ public:
+ using mutex_type = Mutex;
+
+ /**
+ * Constructor locks the mutex, and destructor unlocks
+ */
+ ProxyLockableLockGuard(mutex_type& mtx);
+ ~ProxyLockableLockGuard() = default;
+
+ /**
+ * This class is not movable or assignable
+ *
+ * For more complicated usecases, consider the UniqueLock variant, which
+ * provides more options
+ */
+ ProxyLockableLockGuard(const ProxyLockableLockGuard&) = delete;
+ ProxyLockableLockGuard(ProxyLockableLockGuard&&) = delete;
+ ProxyLockableLockGuard& operator=(ProxyLockableLockGuard&&) = delete;
+ ProxyLockableLockGuard& operator=(const ProxyLockableLockGuard&) = delete;
+};
+
+} // namespace detail
+} // namespace folly
+
+#include <folly/synchronization/detail/ProxyLockable-inl.h>
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Sleeper.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Sleeper.h
new file mode 100644
index 00000000..5bc98b33
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Sleeper.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+/*
+ * @author Keith Adams <kma@fb.com>
+ * @author Jordan DeLong <delong.j@fb.com>
+ */
+
+#include <cstdint>
+#include <thread>
+
+#include <folly/portability/Asm.h>
+
+namespace folly {
+
+//////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+/*
+ * A helper object for the contended case. Starts off with eager
+ * spinning, and falls back to sleeping for small quantums.
+ */
+class Sleeper {
+ static const uint32_t kMaxActiveSpin = 4000;
+
+ uint32_t spinCount;
+
+ public:
+ Sleeper() noexcept : spinCount(0) {}
+
+ static void sleep() noexcept {
+ /*
+ * Always sleep 0.5ms, assuming this will make the kernel put
+ * us down for whatever its minimum timer resolution is (in
+ * linux this varies by kernel version from 1ms to 10ms).
+ */
+ std::this_thread::sleep_for(std::chrono::microseconds{500});
+ }
+
+ void wait() noexcept {
+ if (spinCount < kMaxActiveSpin) {
+ ++spinCount;
+ asm_volatile_pause();
+ } else {
+ sleep();
+ }
+ }
+};
+
+} // namespace detail
+} // namespace folly
+
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Spin.h b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Spin.h
new file mode 100644
index 00000000..6eabc334
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/detail/Spin.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <chrono>
+#include <thread>
+
+#include <folly/portability/Asm.h>
+#include <folly/synchronization/WaitOptions.h>
+
+namespace folly {
+namespace detail {
+
+enum class spin_result {
+ success, // condition passed
+ timeout, // exceeded deadline
+ advance, // exceeded current wait-options component timeout
+};
+
+template <typename Clock, typename Duration, typename F>
+spin_result spin_pause_until(
+ std::chrono::time_point<Clock, Duration> const& deadline,
+ WaitOptions const& opt,
+ F f) {
+ if (opt.spin_max() <= opt.spin_max().zero()) {
+ return spin_result::advance;
+ }
+
+ auto tbegin = Clock::now();
+ while (true) {
+ if (f()) {
+ return spin_result::success;
+ }
+
+ auto const tnow = Clock::now();
+ if (tnow >= deadline) {
+ return spin_result::timeout;
+ }
+
+ // Backward time discontinuity in Clock? revise pre_block starting point
+ tbegin = std::min(tbegin, tnow);
+ if (tnow >= tbegin + opt.spin_max()) {
+ return spin_result::advance;
+ }
+
+ // The pause instruction is the polite way to spin, but it doesn't
+ // actually affect correctness to omit it if we don't have it. Pausing
+ // donates the full capabilities of the current core to its other
+ // hyperthreads for a dozen cycles or so.
+ asm_volatile_pause();
+ }
+}
+
+template <typename Clock, typename Duration, typename F>
+spin_result spin_yield_until(
+ std::chrono::time_point<Clock, Duration> const& deadline,
+ F f) {
+ while (true) {
+ if (f()) {
+ return spin_result::success;
+ }
+
+ auto const max = std::chrono::time_point<Clock, Duration>::max();
+ if (deadline != max && Clock::now() >= deadline) {
+ return spin_result::timeout;
+ }
+
+ std::this_thread::yield();
+ }
+}
+
+} // namespace detail
+} // namespace folly
diff --git a/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp
new file mode 100644
index 00000000..5a9f58e3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp
@@ -0,0 +1,1142 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <folly/synchronization/DistributedMutex.h>
+#include <folly/container/Array.h>
+#include <folly/synchronization/Baton.h>
+
+#ifdef OS_AIX
+#include "gtest/gtest.h"
+#else
+#include <gtest/gtest.h>
+#endif
+
+#if !defined(ROCKSDB_LITE) && !defined(__ARM_ARCH)
+
+#include <chrono>
+#include <cmath>
+#include <thread>
+
+namespace folly {
+namespace test {
+template <template <typename> class Atomic>
+using TestDistributedMutex =
+ folly::detail::distributed_mutex::DistributedMutex<Atomic, false>;
+} // namespace test
+
+namespace {
+constexpr auto kStressFactor = 1000;
+constexpr auto kStressTestSeconds = 2;
+constexpr auto kForever = std::chrono::hours{100};
+
+int sum(int n) {
+ return (n * (n + 1)) / 2;
+}
+
+template <template <typename> class Atom = std::atomic>
+void basicNThreads(int numThreads, int iterations = kStressFactor) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& result = std::vector<int>{};
+
+ auto&& function = [&](int id) {
+ return [&, id] {
+ for (auto j = 0; j < iterations; ++j) {
+ auto lck = std::unique_lock<_t<std::decay<decltype(mutex)>>>{mutex};
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ result.push_back(id);
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ };
+ };
+
+ for (auto i = 1; i <= numThreads; ++i) {
+ threads.push_back(std::thread(function(i)));
+ }
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ auto total = 0;
+ for (auto value : result) {
+ total += value;
+ }
+ EXPECT_EQ(total, sum(numThreads) * iterations);
+}
+
+template <template <typename> class Atom = std::atomic>
+void lockWithTryAndTimedNThreads(
+ int numThreads,
+ std::chrono::seconds duration) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+
+ auto&& lockUnlockFunction = [&]() {
+ while (!stop.load()) {
+ auto lck = std::unique_lock<_t<std::decay<decltype(mutex)>>>{mutex};
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ };
+
+ auto tryLockFunction = [&]() {
+ while (!stop.load()) {
+ using Mutex = _t<std::decay<decltype(mutex)>>;
+ auto lck = std::unique_lock<Mutex>{mutex, std::defer_lock};
+ if (lck.try_lock()) {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ }
+ };
+
+ auto timedLockFunction = [&]() {
+ while (!stop.load()) {
+ using Mutex = _t<std::decay<decltype(mutex)>>;
+ auto lck = std::unique_lock<Mutex>{mutex, std::defer_lock};
+ if (lck.try_lock_for(kForever)) {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ }
+ };
+
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(lockUnlockFunction));
+ }
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(tryLockFunction));
+ }
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(timedLockFunction));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+
+template <template <typename> class Atom = std::atomic>
+void combineNThreads(int numThreads, std::chrono::seconds duration) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+
+ auto&& function = [&]() {
+ return [&] {
+ auto&& expected = std::uint64_t{0};
+ auto&& local = std::atomic<std::uint64_t>{0};
+ auto&& result = std::atomic<std::uint64_t>{0};
+ while (!stop.load()) {
+ ++expected;
+ auto current = mutex.lock_combine([&]() {
+ result.fetch_add(1);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+ return local.fetch_add(1);
+ });
+ EXPECT_EQ(current, expected - 1);
+ }
+
+ EXPECT_EQ(expected, result.load());
+ };
+ };
+
+ for (auto i = 1; i <= numThreads; ++i) {
+ threads.push_back(std::thread(function()));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+
+template <template <typename> class Atom = std::atomic>
+void combineWithLockNThreads(int numThreads, std::chrono::seconds duration) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+
+ auto&& lockUnlockFunction = [&]() {
+ while (!stop.load()) {
+ auto lck = std::unique_lock<_t<std::decay<decltype(mutex)>>>{mutex};
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ };
+
+ auto&& combineFunction = [&]() {
+ auto&& expected = std::uint64_t{0};
+ auto&& total = std::atomic<std::uint64_t>{0};
+
+ while (!stop.load()) {
+ ++expected;
+ auto current = mutex.lock_combine([&]() {
+ auto iteration = total.fetch_add(1);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+ return iteration;
+ });
+
+ EXPECT_EQ(expected, current + 1);
+ }
+
+ EXPECT_EQ(expected, total.load());
+ };
+
+ for (auto i = 1; i < (numThreads / 2); ++i) {
+ threads.push_back(std::thread(combineFunction));
+ }
+ for (auto i = 0; i < (numThreads / 2); ++i) {
+ threads.push_back(std::thread(lockUnlockFunction));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+
+template <template <typename> class Atom = std::atomic>
+void combineWithTryLockNThreads(int numThreads, std::chrono::seconds duration) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+
+ auto&& lockUnlockFunction = [&]() {
+ while (!stop.load()) {
+ auto lck = std::unique_lock<_t<std::decay<decltype(mutex)>>>{mutex};
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ };
+
+ auto&& combineFunction = [&]() {
+ auto&& expected = std::uint64_t{0};
+ auto&& total = std::atomic<std::uint64_t>{0};
+
+ while (!stop.load()) {
+ ++expected;
+ auto current = mutex.lock_combine([&]() {
+ auto iteration = total.fetch_add(1);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+ return iteration;
+ });
+
+ EXPECT_EQ(expected, current + 1);
+ }
+
+ EXPECT_EQ(expected, total.load());
+ };
+
+ auto tryLockFunction = [&]() {
+ while (!stop.load()) {
+ using Mutex = _t<std::decay<decltype(mutex)>>;
+ auto lck = std::unique_lock<Mutex>{mutex, std::defer_lock};
+ if (lck.try_lock()) {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ }
+ };
+
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(lockUnlockFunction));
+ }
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(combineFunction));
+ }
+ for (auto i = 0; i < (numThreads / 3); ++i) {
+ threads.push_back(std::thread(tryLockFunction));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+
+template <template <typename> class Atom = std::atomic>
+void combineWithLockTryAndTimedNThreads(
+ int numThreads,
+ std::chrono::seconds duration) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& barrier = std::atomic<int>{0};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+
+ auto&& lockUnlockFunction = [&]() {
+ while (!stop.load()) {
+ auto lck = std::unique_lock<_t<std::decay<decltype(mutex)>>>{mutex};
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ };
+
+ auto&& combineFunction = [&]() {
+ auto&& expected = std::uint64_t{0};
+ auto&& total = std::atomic<std::uint64_t>{0};
+
+ while (!stop.load()) {
+ ++expected;
+ auto current = mutex.lock_combine([&]() {
+ auto iteration = total.fetch_add(1);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+
+ // return a non-trivially-copyable object that occupies all the
+ // storage we use to coalesce returns to test that codepath
+ return folly::make_array(
+ iteration,
+ iteration + 1,
+ iteration + 2,
+ iteration + 3,
+ iteration + 4,
+ iteration + 5);
+ });
+
+ EXPECT_EQ(expected, current[0] + 1);
+ EXPECT_EQ(expected, current[1]);
+ EXPECT_EQ(expected, current[2] - 1);
+ EXPECT_EQ(expected, current[3] - 2);
+ EXPECT_EQ(expected, current[4] - 3);
+ EXPECT_EQ(expected, current[5] - 4);
+ }
+
+ EXPECT_EQ(expected, total.load());
+ };
+
+ auto tryLockFunction = [&]() {
+ while (!stop.load()) {
+ using Mutex = _t<std::decay<decltype(mutex)>>;
+ auto lck = std::unique_lock<Mutex>{mutex, std::defer_lock};
+ if (lck.try_lock()) {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ }
+ };
+
+ auto timedLockFunction = [&]() {
+ while (!stop.load()) {
+ using Mutex = _t<std::decay<decltype(mutex)>>;
+ auto lck = std::unique_lock<Mutex>{mutex, std::defer_lock};
+ if (lck.try_lock_for(kForever)) {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ std::this_thread::yield();
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ }
+ }
+ };
+
+ for (auto i = 0; i < (numThreads / 4); ++i) {
+ threads.push_back(std::thread(lockUnlockFunction));
+ }
+ for (auto i = 0; i < (numThreads / 4); ++i) {
+ threads.push_back(std::thread(combineFunction));
+ }
+ for (auto i = 0; i < (numThreads / 4); ++i) {
+ threads.push_back(std::thread(tryLockFunction));
+ }
+ for (auto i = 0; i < (numThreads / 4); ++i) {
+ threads.push_back(std::thread(timedLockFunction));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, InternalDetailTestOne) {
+ auto value = 0;
+ auto ptr = reinterpret_cast<std::uintptr_t>(&value);
+ EXPECT_EQ(folly::detail::distributed_mutex::extractPtr<int>(ptr), &value);
+ ptr = ptr | 0b1;
+ EXPECT_EQ(folly::detail::distributed_mutex::extractPtr<int>(ptr), &value);
+}
+
+TEST(DistributedMutex, Basic) {
+ auto&& mutex = folly::DistributedMutex{};
+ auto state = mutex.lock();
+ mutex.unlock(std::move(state));
+}
+
+TEST(DistributedMutex, BasicTryLock) {
+ auto&& mutex = folly::DistributedMutex{};
+
+ while (true) {
+ auto state = mutex.try_lock();
+ if (state) {
+ mutex.unlock(std::move(state));
+ break;
+ }
+ }
+}
+
+TEST(DistributedMutex, StressTwoThreads) {
+ basicNThreads(2);
+}
+TEST(DistributedMutex, StressThreeThreads) {
+ basicNThreads(3);
+}
+TEST(DistributedMutex, StressFourThreads) {
+ basicNThreads(4);
+}
+TEST(DistributedMutex, StressFiveThreads) {
+ basicNThreads(5);
+}
+TEST(DistributedMutex, StressSixThreads) {
+ basicNThreads(6);
+}
+TEST(DistributedMutex, StressSevenThreads) {
+ basicNThreads(7);
+}
+TEST(DistributedMutex, StressEightThreads) {
+ basicNThreads(8);
+}
+TEST(DistributedMutex, StressSixteenThreads) {
+ basicNThreads(16);
+}
+TEST(DistributedMutex, StressThirtyTwoThreads) {
+ basicNThreads(32);
+}
+TEST(DistributedMutex, StressSixtyFourThreads) {
+ basicNThreads(64);
+}
+TEST(DistributedMutex, StressHundredThreads) {
+ basicNThreads(100);
+}
+TEST(DistributedMutex, StressHardwareConcurrencyThreads) {
+ basicNThreads(std::thread::hardware_concurrency());
+}
+
+TEST(DistributedMutex, StressThreeThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(3, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(6, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwelveThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(12, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwentyFourThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(24, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFourtyEightThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(48, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixtyFourThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(64, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHwConcThreadsLockTryAndTimed) {
+ lockWithTryAndTimedNThreads(
+ std::thread::hardware_concurrency(),
+ std::chrono::seconds{kStressTestSeconds});
+}
+
+TEST(DistributedMutex, StressTwoThreadsCombine) {
+ combineNThreads(2, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressThreeThreadsCombine) {
+ combineNThreads(3, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFourThreadsCombine) {
+ combineNThreads(4, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFiveThreadsCombine) {
+ combineNThreads(5, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixThreadsCombine) {
+ combineNThreads(6, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSevenThreadsCombine) {
+ combineNThreads(7, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressEightThreadsCombine) {
+ combineNThreads(8, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixteenThreadsCombine) {
+ combineNThreads(16, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressThirtyTwoThreadsCombine) {
+ combineNThreads(32, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixtyFourThreadsCombine) {
+ combineNThreads(64, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHundredThreadsCombine) {
+ combineNThreads(100, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHardwareConcurrencyThreadsCombine) {
+ combineNThreads(
+ std::thread::hardware_concurrency(),
+ std::chrono::seconds{kStressTestSeconds});
+}
+
+TEST(DistributedMutex, StressTwoThreadsCombineAndLock) {
+ combineWithLockNThreads(2, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFourThreadsCombineAndLock) {
+ combineWithLockNThreads(4, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressEightThreadsCombineAndLock) {
+ combineWithLockNThreads(8, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixteenThreadsCombineAndLock) {
+ combineWithLockNThreads(16, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressThirtyTwoThreadsCombineAndLock) {
+ combineWithLockNThreads(32, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixtyFourThreadsCombineAndLock) {
+ combineWithLockNThreads(64, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHardwareConcurrencyThreadsCombineAndLock) {
+ combineWithLockNThreads(
+ std::thread::hardware_concurrency(),
+ std::chrono::seconds{kStressTestSeconds});
+}
+
+TEST(DistributedMutex, StressThreeThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(3, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(6, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwelveThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(12, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwentyFourThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(24, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFourtyEightThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(48, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixtyFourThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(64, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHardwareConcurrencyThreadsCombineTryLockAndLock) {
+ combineWithTryLockNThreads(
+ std::thread::hardware_concurrency(),
+ std::chrono::seconds{kStressTestSeconds});
+}
+
+TEST(DistributedMutex, StressThreeThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 3, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 6, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwelveThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 12, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressTwentyFourThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 24, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressFourtyEightThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 48, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressSixtyFourThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ 64, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressHwConcurrencyThreadsCombineTryLockLockAndTimed) {
+ combineWithLockTryAndTimedNThreads(
+ std::thread::hardware_concurrency(),
+ std::chrono::seconds{kStressTestSeconds});
+}
+
+TEST(DistributedMutex, StressTryLock) {
+ auto&& mutex = folly::DistributedMutex{};
+
+ for (auto i = 0; i < kStressFactor; ++i) {
+ while (true) {
+ auto state = mutex.try_lock();
+ if (state) {
+ mutex.unlock(std::move(state));
+ break;
+ }
+ }
+ }
+}
+
+TEST(DistributedMutex, TimedLockTimeout) {
+ auto&& mutex = folly::DistributedMutex{};
+ auto&& start = folly::Baton<>{};
+ auto&& done = folly::Baton<>{};
+
+ auto thread = std::thread{[&]() {
+ auto state = mutex.lock();
+ start.post();
+ done.wait();
+ mutex.unlock(std::move(state));
+ }};
+
+ start.wait();
+ auto result = mutex.try_lock_for(std::chrono::milliseconds{10});
+ EXPECT_FALSE(result);
+ done.post();
+ thread.join();
+}
+
+TEST(DistributedMutex, TimedLockAcquireAfterUnlock) {
+ auto&& mutex = folly::DistributedMutex{};
+ auto&& start = folly::Baton<>{};
+
+ auto thread = std::thread{[&]() {
+ auto state = mutex.lock();
+ start.post();
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds{10});
+ mutex.unlock(std::move(state));
+ }};
+
+ start.wait();
+ auto result = mutex.try_lock_for(kForever);
+ EXPECT_TRUE(result);
+ thread.join();
+}
+
+namespace {
+template <template <typename> class Atom = std::atomic>
+void stressTryLockWithConcurrentLocks(
+ int numThreads,
+ int iterations = kStressFactor) {
+ auto&& threads = std::vector<std::thread>{};
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& atomic = std::atomic<std::uint64_t>{0};
+
+ for (auto i = 0; i < numThreads; ++i) {
+ threads.push_back(std::thread([&] {
+ for (auto j = 0; j < iterations; ++j) {
+ auto state = mutex.lock();
+ EXPECT_EQ(atomic.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(atomic.fetch_sub(1, std::memory_order_relaxed), 1);
+ mutex.unlock(std::move(state));
+ }
+ }));
+ }
+
+ for (auto i = 0; i < iterations; ++i) {
+ if (auto state = mutex.try_lock()) {
+ EXPECT_EQ(atomic.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(atomic.fetch_sub(1, std::memory_order_relaxed), 1);
+ mutex.unlock(std::move(state));
+ }
+ }
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksTwoThreads) {
+ stressTryLockWithConcurrentLocks(2);
+}
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksFourThreads) {
+ stressTryLockWithConcurrentLocks(4);
+}
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksEightThreads) {
+ stressTryLockWithConcurrentLocks(8);
+}
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksSixteenThreads) {
+ stressTryLockWithConcurrentLocks(16);
+}
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksThirtyTwoThreads) {
+ stressTryLockWithConcurrentLocks(32);
+}
+TEST(DistributedMutex, StressTryLockWithConcurrentLocksSixtyFourThreads) {
+ stressTryLockWithConcurrentLocks(64);
+}
+
+namespace {
+template <template <typename> class Atom = std::atomic>
+void concurrentTryLocks(int numThreads, int iterations = kStressFactor) {
+ auto&& threads = std::vector<std::thread>{};
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& atomic = std::atomic<std::uint64_t>{0};
+
+ for (auto i = 0; i < numThreads; ++i) {
+ threads.push_back(std::thread([&] {
+ for (auto j = 0; j < iterations; ++j) {
+ if (auto state = mutex.try_lock()) {
+ EXPECT_EQ(atomic.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(atomic.fetch_sub(1, std::memory_order_relaxed), 1);
+ mutex.unlock(std::move(state));
+ }
+ }
+ }));
+ }
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, StressTryLockWithTwoThreads) {
+ concurrentTryLocks(2);
+}
+TEST(DistributedMutex, StressTryLockFourThreads) {
+ concurrentTryLocks(4);
+}
+TEST(DistributedMutex, StressTryLockEightThreads) {
+ concurrentTryLocks(8);
+}
+TEST(DistributedMutex, StressTryLockSixteenThreads) {
+ concurrentTryLocks(16);
+}
+TEST(DistributedMutex, StressTryLockThirtyTwoThreads) {
+ concurrentTryLocks(32);
+}
+TEST(DistributedMutex, StressTryLockSixtyFourThreads) {
+ concurrentTryLocks(64);
+}
+
+namespace {
+class TestConstruction {
+ public:
+ TestConstruction() = delete;
+ explicit TestConstruction(int) {
+ defaultConstructs().fetch_add(1, std::memory_order_relaxed);
+ }
+ TestConstruction(TestConstruction&&) noexcept {
+ moveConstructs().fetch_add(1, std::memory_order_relaxed);
+ }
+ TestConstruction(const TestConstruction&) {
+ copyConstructs().fetch_add(1, std::memory_order_relaxed);
+ }
+ TestConstruction& operator=(const TestConstruction&) {
+ copyAssigns().fetch_add(1, std::memory_order_relaxed);
+ return *this;
+ }
+ TestConstruction& operator=(TestConstruction&&) {
+ moveAssigns().fetch_add(1, std::memory_order_relaxed);
+ return *this;
+ }
+ ~TestConstruction() {
+ destructs().fetch_add(1, std::memory_order_relaxed);
+ }
+
+ static std::atomic<std::uint64_t>& defaultConstructs() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+ static std::atomic<std::uint64_t>& moveConstructs() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+ static std::atomic<std::uint64_t>& copyConstructs() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+ static std::atomic<std::uint64_t>& moveAssigns() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+ static std::atomic<std::uint64_t>& copyAssigns() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+ static std::atomic<std::uint64_t>& destructs() {
+ static auto&& atomic = std::atomic<std::uint64_t>{0};
+ return atomic;
+ }
+
+ static void reset() {
+ defaultConstructs().store(0);
+ moveConstructs().store(0);
+ copyConstructs().store(0);
+ copyAssigns().store(0);
+ destructs().store(0);
+ }
+};
+} // namespace
+
+TEST(DistributedMutex, TestAppropriateDestructionAndConstructionWithCombine) {
+ auto&& mutex = folly::DistributedMutex{};
+ auto&& stop = std::atomic<bool>{false};
+
+ // test the simple return path to make sure that in the absence of
+ // contention, we get the right number of constructs and destructs
+ mutex.lock_combine([]() { return TestConstruction{1}; });
+ auto moves = TestConstruction::moveConstructs().load();
+ auto defaults = TestConstruction::defaultConstructs().load();
+ EXPECT_EQ(TestConstruction::defaultConstructs().load(), 1);
+ EXPECT_TRUE(moves == 0 || moves == 1);
+ EXPECT_EQ(TestConstruction::destructs().load(), moves + defaults);
+
+ // loop and make sure we were able to test the path where the critical
+ // section of the thread gets combined, and assert that we see the expected
+ // number of constructions and destructions
+ //
+ // this implements a timed backoff to test the combined path, so we use the
+ // smallest possible delay in tests
+ auto thread = std::thread{[&]() {
+ auto&& duration = std::chrono::milliseconds{10};
+ while (!stop.load()) {
+ TestConstruction::reset();
+ auto&& ready = folly::Baton<>{};
+ auto&& release = folly::Baton<>{};
+
+ // make one thread start it's critical section, signal and wait for
+ // another thread to enqueue, to test the
+ auto innerThread = std::thread{[&]() {
+ mutex.lock_combine([&]() {
+ ready.post();
+ release.wait();
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ });
+ }};
+
+ // wait for the thread to get in its critical section, then tell it to go
+ ready.wait();
+ release.post();
+ mutex.lock_combine([&]() { return TestConstruction{1}; });
+
+ innerThread.join();
+
+ // at this point we should have only one default construct, either 3
+ // or 4 move constructs the same number of destructions as
+ // constructions
+ auto innerDefaults = TestConstruction::defaultConstructs().load();
+ auto innerMoves = TestConstruction::moveConstructs().load();
+ auto destructs = TestConstruction::destructs().load();
+ EXPECT_EQ(innerDefaults, 1);
+ EXPECT_TRUE(innerMoves == 3 || innerMoves == 4 || innerMoves == 1);
+ EXPECT_EQ(destructs, innerMoves + innerDefaults);
+ EXPECT_EQ(TestConstruction::moveAssigns().load(), 0);
+ EXPECT_EQ(TestConstruction::copyAssigns().load(), 0);
+
+ // increase duration by 100ms each iteration
+ duration = duration + std::chrono::milliseconds{100};
+ }
+ }};
+
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::seconds{kStressTestSeconds});
+ stop.store(true);
+ thread.join();
+}
+
+namespace {
+template <template <typename> class Atom = std::atomic>
+void concurrentLocksManyMutexes(int numThreads, std::chrono::seconds duration) {
+ using DMutex = folly::detail::distributed_mutex::DistributedMutex<Atom>;
+ const auto&& kNumMutexes = 10;
+ auto&& threads = std::vector<std::thread>{};
+ auto&& mutexes = std::vector<DMutex>(kNumMutexes);
+ auto&& barriers = std::vector<std::atomic<std::uint64_t>>(kNumMutexes);
+ auto&& stop = std::atomic<bool>{false};
+
+ for (auto i = 0; i < numThreads; ++i) {
+ threads.push_back(std::thread([&] {
+ auto&& total = std::atomic<std::uint64_t>{0};
+ auto&& expected = std::uint64_t{0};
+
+ for (auto j = 0; !stop.load(std::memory_order_relaxed); ++j) {
+ auto& mutex = mutexes[j % kNumMutexes];
+ auto& barrier = barriers[j % kNumMutexes];
+
+ ++expected;
+ auto result = mutex.lock_combine([&]() {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+ return total.fetch_add(1, std::memory_order_relaxed);
+ });
+ EXPECT_EQ(result, expected - 1);
+ }
+
+ EXPECT_EQ(total.load(), expected);
+ }));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(duration);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, StressWithManyMutexesAlternatingTwoThreads) {
+ concurrentLocksManyMutexes(2, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressWithManyMutexesAlternatingFourThreads) {
+ concurrentLocksManyMutexes(4, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressWithManyMutexesAlternatingEightThreads) {
+ concurrentLocksManyMutexes(8, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressWithManyMutexesAlternatingSixteenThreads) {
+ concurrentLocksManyMutexes(16, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressWithManyMutexesAlternatingThirtyTwoThreads) {
+ concurrentLocksManyMutexes(32, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressWithManyMutexesAlternatingSixtyFourThreads) {
+ concurrentLocksManyMutexes(64, std::chrono::seconds{kStressTestSeconds});
+}
+
+namespace {
+class ExceptionWithConstructionTrack : public std::exception {
+ public:
+ explicit ExceptionWithConstructionTrack(int id)
+ : id_{std::to_string(id)}, constructionTrack_{id} {}
+
+ const char* what() const noexcept override {
+ return id_.c_str();
+ }
+
+ private:
+ std::string id_;
+ TestConstruction constructionTrack_;
+};
+} // namespace
+
+TEST(DistributedMutex, TestExceptionPropagationUncontended) {
+ TestConstruction::reset();
+ auto&& mutex = folly::DistributedMutex{};
+ auto&& thread = std::thread{[&]() {
+ try {
+ mutex.lock_combine([&]() { throw ExceptionWithConstructionTrack{46}; });
+ } catch (std::exception& exc) {
+ auto integer = std::stoi(exc.what());
+ EXPECT_EQ(integer, 46);
+ EXPECT_GT(TestConstruction::defaultConstructs(), 0);
+ }
+ EXPECT_EQ(
+ TestConstruction::defaultConstructs(), TestConstruction::destructs());
+ }};
+ thread.join();
+}
+
+namespace {
+template <template <typename> class Atom = std::atomic>
+void concurrentExceptionPropagationStress(
+ int numThreads,
+ std::chrono::milliseconds t) {
+ // this test fails under with a false negative under older versions of TSAN
+ // for some reason so disable it when TSAN is enabled
+ if (folly::kIsSanitizeThread) {
+ return;
+ }
+
+ TestConstruction::reset();
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+ auto&& barrier = std::atomic<std::uint64_t>{0};
+
+ for (auto i = 0; i < numThreads; ++i) {
+ threads.push_back(std::thread([&]() {
+ for (auto j = 0; !stop.load(); ++j) {
+ auto value = int{0};
+ try {
+ value = mutex.lock_combine([&]() {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+
+ // we only throw an exception once every 3 times
+ if (!(j % 3)) {
+ throw ExceptionWithConstructionTrack{j};
+ }
+
+ return j;
+ });
+ } catch (std::exception& exc) {
+ value = std::stoi(exc.what());
+ }
+
+ EXPECT_EQ(value, j);
+ }
+ }));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(t);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, TestExceptionPropagationStressTwoThreads) {
+ concurrentExceptionPropagationStress(
+ 2, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, TestExceptionPropagationStressFourThreads) {
+ concurrentExceptionPropagationStress(
+ 4, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, TestExceptionPropagationStressEightThreads) {
+ concurrentExceptionPropagationStress(
+ 8, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, TestExceptionPropagationStressSixteenThreads) {
+ concurrentExceptionPropagationStress(
+ 16, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, TestExceptionPropagationStressThirtyTwoThreads) {
+ concurrentExceptionPropagationStress(
+ 32, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, TestExceptionPropagationStressSixtyFourThreads) {
+ concurrentExceptionPropagationStress(
+ 64, std::chrono::seconds{kStressTestSeconds});
+}
+
+namespace {
+std::array<std::uint64_t, 8> makeMonotonicArray(int start) {
+ auto array = std::array<std::uint64_t, 8>{};
+ for (auto& element : array) { element = start++; }
+ return array;
+}
+
+template <template <typename> class Atom = std::atomic>
+void concurrentBigValueReturnStress(
+ int numThreads,
+ std::chrono::milliseconds t) {
+ auto&& mutex = folly::detail::distributed_mutex::DistributedMutex<Atom>{};
+ auto&& threads = std::vector<std::thread>{};
+ auto&& stop = std::atomic<bool>{false};
+ auto&& barrier = std::atomic<std::uint64_t>{0};
+
+ for (auto i = 0; i < numThreads; ++i) {
+ threads.push_back(std::thread([&]() {
+ auto&& value = std::atomic<std::uint64_t>{0};
+
+ for (auto j = 0; !stop.load(); ++j) {
+ auto returned = mutex.lock_combine([&]() {
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 0);
+ EXPECT_EQ(barrier.fetch_add(1, std::memory_order_relaxed), 1);
+ std::this_thread::yield();
+ // return an entire cacheline worth of data
+ auto current = value.fetch_add(1, std::memory_order_relaxed);
+ SCOPE_EXIT {
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 1);
+ };
+ EXPECT_EQ(barrier.fetch_sub(1, std::memory_order_relaxed), 2);
+ return makeMonotonicArray(static_cast<int>(current));
+ });
+
+ auto expected = value.load() - 1;
+ for (auto& element : returned) {
+ EXPECT_EQ(element, expected++);
+ }
+ }
+ }));
+ }
+
+ /* sleep override */
+ std::this_thread::sleep_for(t);
+ stop.store(true);
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+} // namespace
+
+TEST(DistributedMutex, StressBigValueReturnTwoThreads) {
+ concurrentBigValueReturnStress(2, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressBigValueReturnFourThreads) {
+ concurrentBigValueReturnStress(4, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressBigValueReturnEightThreads) {
+ concurrentBigValueReturnStress(8, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressBigValueReturnSixteenThreads) {
+ concurrentBigValueReturnStress(16, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressBigValueReturnThirtyTwoThreads) {
+ concurrentBigValueReturnStress(32, std::chrono::seconds{kStressTestSeconds});
+}
+TEST(DistributedMutex, StressBigValueReturnSixtyFourThreads) {
+ concurrentBigValueReturnStress(64, std::chrono::seconds{kStressTestSeconds});
+}
+
+} // namespace folly
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+int main(int /*argc*/, char** /*argv*/) {
+ printf("DistributedMutex is not supported in ROCKSDB_LITE or on ARM\n");
+ return 0;
+}
+#endif // !ROCKSDB_LITE && !__ARM_ARCH
diff --git a/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/CMakeLists.txt b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/CMakeLists.txt
new file mode 100644
index 00000000..90cff088
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/CMakeLists.txt
@@ -0,0 +1 @@
+add_library(gtest gtest-all.cc)
diff --git a/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc
new file mode 100644
index 00000000..9f2b3d56
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc
@@ -0,0 +1,11394 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Google C++ Testing and Mocking Framework (Google Test)
+//
+// Sometimes it's desirable to build Google Test by compiling a single file.
+// This file serves this purpose.
+
+// This line ensures that gtest.h can be compiled on its own, even
+// when it's fused.
+#include "gtest/gtest.h"
+
+// The following lines pull in the real gtest *.cc files.
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Utilities for testing Google Test itself and code that uses Google Test
+// (e.g. frameworks built on top of Google Test).
+
+// GOOGLETEST_CM0004 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_
+#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// This helper class can be used to mock out Google Test failure reporting
+// so that we can test Google Test or code that builds on Google Test.
+//
+// An object of this class appends a TestPartResult object to the
+// TestPartResultArray object given in the constructor whenever a Google Test
+// failure is reported. It can either intercept only failures that are
+// generated in the same thread that created this object or it can intercept
+// all generated failures. The scope of this mock object can be controlled with
+// the second argument to the two arguments constructor.
+class GTEST_API_ ScopedFakeTestPartResultReporter
+ : public TestPartResultReporterInterface {
+ public:
+ // The two possible mocking modes of this object.
+ enum InterceptMode {
+ INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures.
+ INTERCEPT_ALL_THREADS // Intercepts all failures.
+ };
+
+ // The c'tor sets this object as the test part result reporter used
+ // by Google Test. The 'result' parameter specifies where to report the
+ // results. This reporter will only catch failures generated in the current
+ // thread. DEPRECATED
+ explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
+
+ // Same as above, but you can choose the interception scope of this object.
+ ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
+ TestPartResultArray* result);
+
+ // The d'tor restores the previous test part result reporter.
+ virtual ~ScopedFakeTestPartResultReporter();
+
+ // Appends the TestPartResult object to the TestPartResultArray
+ // received in the constructor.
+ //
+ // This method is from the TestPartResultReporterInterface
+ // interface.
+ virtual void ReportTestPartResult(const TestPartResult& result);
+ private:
+ void Init();
+
+ const InterceptMode intercept_mode_;
+ TestPartResultReporterInterface* old_reporter_;
+ TestPartResultArray* const result_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter);
+};
+
+namespace internal {
+
+// A helper class for implementing EXPECT_FATAL_FAILURE() and
+// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given
+// TestPartResultArray contains exactly one failure that has the given
+// type and contains the given substring. If that's not the case, a
+// non-fatal failure will be generated.
+class GTEST_API_ SingleFailureChecker {
+ public:
+ // The constructor remembers the arguments.
+ SingleFailureChecker(const TestPartResultArray* results,
+ TestPartResult::Type type, const std::string& substr);
+ ~SingleFailureChecker();
+ private:
+ const TestPartResultArray* const results_;
+ const TestPartResult::Type type_;
+ const std::string substr_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
+};
+
+} // namespace internal
+
+} // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+// A set of macros for testing Google Test assertions or code that's expected
+// to generate Google Test fatal failures. It verifies that the given
+// statement will cause exactly one fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+// - 'statement' cannot reference local non-static variables or
+// non-static members of the current object.
+// - 'statement' cannot return a value.
+// - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works. The AcceptsMacroThatExpandsToUnprotectedComma test in
+// gtest_unittest.cc will fail to compile if we do that.
+#define EXPECT_FATAL_FAILURE(statement, substr) \
+ do { \
+ class GTestExpectFatalFailureHelper {\
+ public:\
+ static void Execute() { statement; }\
+ };\
+ ::testing::TestPartResultArray gtest_failures;\
+ ::testing::internal::SingleFailureChecker gtest_checker(\
+ &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+ {\
+ ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+ ::testing::ScopedFakeTestPartResultReporter:: \
+ INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+ GTestExpectFatalFailureHelper::Execute();\
+ }\
+ } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+ do { \
+ class GTestExpectFatalFailureHelper {\
+ public:\
+ static void Execute() { statement; }\
+ };\
+ ::testing::TestPartResultArray gtest_failures;\
+ ::testing::internal::SingleFailureChecker gtest_checker(\
+ &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+ {\
+ ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+ ::testing::ScopedFakeTestPartResultReporter:: \
+ INTERCEPT_ALL_THREADS, &gtest_failures);\
+ GTestExpectFatalFailureHelper::Execute();\
+ }\
+ } while (::testing::internal::AlwaysFalse())
+
+// A macro for testing Google Test assertions or code that's expected to
+// generate Google Test non-fatal failures. It asserts that the given
+// statement will cause exactly one non-fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// 'statement' is allowed to reference local variables and members of
+// the current object.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+// - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works. If we do that, the code won't compile when the user gives
+// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
+// expands to code containing an unprotected comma. The
+// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
+// catches that.
+//
+// For the same reason, we have to write
+// if (::testing::internal::AlwaysTrue()) { statement; }
+// instead of
+// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
+// to avoid an MSVC warning on unreachable code.
+#define EXPECT_NONFATAL_FAILURE(statement, substr) \
+ do {\
+ ::testing::TestPartResultArray gtest_failures;\
+ ::testing::internal::SingleFailureChecker gtest_checker(\
+ &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+ (substr));\
+ {\
+ ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+ ::testing::ScopedFakeTestPartResultReporter:: \
+ INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+ if (::testing::internal::AlwaysTrue()) { statement; }\
+ }\
+ } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+ do {\
+ ::testing::TestPartResultArray gtest_failures;\
+ ::testing::internal::SingleFailureChecker gtest_checker(\
+ &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+ (substr));\
+ {\
+ ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+ ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
+ &gtest_failures);\
+ if (::testing::internal::AlwaysTrue()) { statement; }\
+ }\
+ } while (::testing::internal::AlwaysFalse())
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_
+
+#include <ctype.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <limits>
+#include <list>
+#include <map>
+#include <ostream> // NOLINT
+#include <sstream>
+#include <vector>
+
+#if GTEST_OS_LINUX
+
+// FIXME: Use autoconf to detect availability of
+// gettimeofday().
+# define GTEST_HAS_GETTIMEOFDAY_ 1
+
+# include <fcntl.h> // NOLINT
+# include <limits.h> // NOLINT
+# include <sched.h> // NOLINT
+// Declares vsnprintf(). This header is not available on Windows.
+# include <strings.h> // NOLINT
+# include <sys/mman.h> // NOLINT
+# include <sys/time.h> // NOLINT
+# include <unistd.h> // NOLINT
+# include <string>
+
+#elif GTEST_OS_SYMBIAN
+# define GTEST_HAS_GETTIMEOFDAY_ 1
+# include <sys/time.h> // NOLINT
+
+#elif GTEST_OS_ZOS
+# define GTEST_HAS_GETTIMEOFDAY_ 1
+# include <sys/time.h> // NOLINT
+
+// On z/OS we additionally need strings.h for strcasecmp.
+# include <strings.h> // NOLINT
+
+#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE.
+
+# include <windows.h> // NOLINT
+# undef min
+
+#elif GTEST_OS_WINDOWS // We are on Windows proper.
+
+# include <io.h> // NOLINT
+# include <sys/timeb.h> // NOLINT
+# include <sys/types.h> // NOLINT
+# include <sys/stat.h> // NOLINT
+
+# if GTEST_OS_WINDOWS_MINGW
+// MinGW has gettimeofday() but not _ftime64().
+// FIXME: Use autoconf to detect availability of
+// gettimeofday().
+// FIXME: There are other ways to get the time on
+// Windows, like GetTickCount() or GetSystemTimeAsFileTime(). MinGW
+// supports these. consider using them instead.
+# define GTEST_HAS_GETTIMEOFDAY_ 1
+# include <sys/time.h> // NOLINT
+# endif // GTEST_OS_WINDOWS_MINGW
+
+// cpplint thinks that the header is already included, so we want to
+// silence it.
+# include <windows.h> // NOLINT
+# undef min
+
+#else
+
+// Assume other platforms have gettimeofday().
+// FIXME: Use autoconf to detect availability of
+// gettimeofday().
+# define GTEST_HAS_GETTIMEOFDAY_ 1
+
+// cpplint thinks that the header is already included, so we want to
+// silence it.
+# include <sys/time.h> // NOLINT
+# include <unistd.h> // NOLINT
+
+#endif // GTEST_OS_LINUX
+
+#if GTEST_HAS_EXCEPTIONS
+# include <stdexcept>
+#endif
+
+#if GTEST_CAN_STREAM_RESULTS_
+# include <arpa/inet.h> // NOLINT
+# include <netdb.h> // NOLINT
+# include <sys/socket.h> // NOLINT
+# include <sys/types.h> // NOLINT
+#endif
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Utility functions and classes used by the Google C++ testing framework.//
+// This file contains purely Google Test's internal implementation. Please
+// DO NOT #INCLUDE IT IN A USER PROGRAM.
+
+#ifndef GTEST_SRC_GTEST_INTERNAL_INL_H_
+#define GTEST_SRC_GTEST_INTERNAL_INL_H_
+
+#ifndef _WIN32_WCE
+# include <errno.h>
+#endif // !_WIN32_WCE
+#include <stddef.h>
+#include <stdlib.h> // For strtoll/_strtoul64/malloc/free.
+#include <string.h> // For memmove.
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+
+#if GTEST_CAN_STREAM_RESULTS_
+# include <arpa/inet.h> // NOLINT
+# include <netdb.h> // NOLINT
+#endif
+
+#if GTEST_OS_WINDOWS
+# include <windows.h> // NOLINT
+#endif // GTEST_OS_WINDOWS
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// Declares the flags.
+//
+// We don't want the users to modify this flag in the code, but want
+// Google Test's own unit tests to be able to access it. Therefore we
+// declare it here as opposed to in gtest.h.
+GTEST_DECLARE_bool_(death_test_use_fork);
+
+namespace internal {
+
+// The value of GetTestTypeId() as seen from within the Google Test
+// library. This is solely for testing GetTestTypeId().
+GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest;
+
+// Names of the flags (needed for parsing Google Test flags).
+const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests";
+const char kBreakOnFailureFlag[] = "break_on_failure";
+const char kCatchExceptionsFlag[] = "catch_exceptions";
+const char kColorFlag[] = "color";
+const char kFilterFlag[] = "filter";
+const char kListTestsFlag[] = "list_tests";
+const char kOutputFlag[] = "output";
+const char kPrintTimeFlag[] = "print_time";
+const char kPrintUTF8Flag[] = "print_utf8";
+const char kRandomSeedFlag[] = "random_seed";
+const char kRepeatFlag[] = "repeat";
+const char kShuffleFlag[] = "shuffle";
+const char kStackTraceDepthFlag[] = "stack_trace_depth";
+const char kStreamResultToFlag[] = "stream_result_to";
+const char kThrowOnFailureFlag[] = "throw_on_failure";
+const char kFlagfileFlag[] = "flagfile";
+
+// A valid random seed must be in [1, kMaxRandomSeed].
+const int kMaxRandomSeed = 99999;
+
+// g_help_flag is true iff the --help flag or an equivalent form is
+// specified on the command line.
+GTEST_API_ extern bool g_help_flag;
+
+// Returns the current time in milliseconds.
+GTEST_API_ TimeInMillis GetTimeInMillis();
+
+// Returns true iff Google Test should use colors in the output.
+GTEST_API_ bool ShouldUseColor(bool stdout_is_tty);
+
+// Formats the given time in milliseconds as seconds.
+GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms);
+
+// Converts the given time in milliseconds to a date string in the ISO 8601
+// format, without the timezone information. N.B.: due to the use the
+// non-reentrant localtime() function, this function is not thread safe. Do
+// not use it in any code that can be called from multiple threads.
+GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms);
+
+// Parses a string for an Int32 flag, in the form of "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true. On failure, returns false without changing *value.
+GTEST_API_ bool ParseInt32Flag(
+ const char* str, const char* flag, Int32* value);
+
+// Returns a random seed in range [1, kMaxRandomSeed] based on the
+// given --gtest_random_seed flag value.
+inline int GetRandomSeedFromFlag(Int32 random_seed_flag) {
+ const unsigned int raw_seed = (random_seed_flag == 0) ?
+ static_cast<unsigned int>(GetTimeInMillis()) :
+ static_cast<unsigned int>(random_seed_flag);
+
+ // Normalizes the actual seed to range [1, kMaxRandomSeed] such that
+ // it's easy to type.
+ const int normalized_seed =
+ static_cast<int>((raw_seed - 1U) %
+ static_cast<unsigned int>(kMaxRandomSeed)) + 1;
+ return normalized_seed;
+}
+
+// Returns the first valid random seed after 'seed'. The behavior is
+// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is
+// considered to be 1.
+inline int GetNextRandomSeed(int seed) {
+ GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed)
+ << "Invalid random seed " << seed << " - must be in [1, "
+ << kMaxRandomSeed << "].";
+ const int next_seed = seed + 1;
+ return (next_seed > kMaxRandomSeed) ? 1 : next_seed;
+}
+
+// This class saves the values of all Google Test flags in its c'tor, and
+// restores them in its d'tor.
+class GTestFlagSaver {
+ public:
+ // The c'tor.
+ GTestFlagSaver() {
+ also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests);
+ break_on_failure_ = GTEST_FLAG(break_on_failure);
+ catch_exceptions_ = GTEST_FLAG(catch_exceptions);
+ color_ = GTEST_FLAG(color);
+ death_test_style_ = GTEST_FLAG(death_test_style);
+ death_test_use_fork_ = GTEST_FLAG(death_test_use_fork);
+ filter_ = GTEST_FLAG(filter);
+ internal_run_death_test_ = GTEST_FLAG(internal_run_death_test);
+ list_tests_ = GTEST_FLAG(list_tests);
+ output_ = GTEST_FLAG(output);
+ print_time_ = GTEST_FLAG(print_time);
+ print_utf8_ = GTEST_FLAG(print_utf8);
+ random_seed_ = GTEST_FLAG(random_seed);
+ repeat_ = GTEST_FLAG(repeat);
+ shuffle_ = GTEST_FLAG(shuffle);
+ stack_trace_depth_ = GTEST_FLAG(stack_trace_depth);
+ stream_result_to_ = GTEST_FLAG(stream_result_to);
+ throw_on_failure_ = GTEST_FLAG(throw_on_failure);
+ }
+
+ // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS.
+ ~GTestFlagSaver() {
+ GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_;
+ GTEST_FLAG(break_on_failure) = break_on_failure_;
+ GTEST_FLAG(catch_exceptions) = catch_exceptions_;
+ GTEST_FLAG(color) = color_;
+ GTEST_FLAG(death_test_style) = death_test_style_;
+ GTEST_FLAG(death_test_use_fork) = death_test_use_fork_;
+ GTEST_FLAG(filter) = filter_;
+ GTEST_FLAG(internal_run_death_test) = internal_run_death_test_;
+ GTEST_FLAG(list_tests) = list_tests_;
+ GTEST_FLAG(output) = output_;
+ GTEST_FLAG(print_time) = print_time_;
+ GTEST_FLAG(print_utf8) = print_utf8_;
+ GTEST_FLAG(random_seed) = random_seed_;
+ GTEST_FLAG(repeat) = repeat_;
+ GTEST_FLAG(shuffle) = shuffle_;
+ GTEST_FLAG(stack_trace_depth) = stack_trace_depth_;
+ GTEST_FLAG(stream_result_to) = stream_result_to_;
+ GTEST_FLAG(throw_on_failure) = throw_on_failure_;
+ }
+
+ private:
+ // Fields for saving the original values of flags.
+ bool also_run_disabled_tests_;
+ bool break_on_failure_;
+ bool catch_exceptions_;
+ std::string color_;
+ std::string death_test_style_;
+ bool death_test_use_fork_;
+ std::string filter_;
+ std::string internal_run_death_test_;
+ bool list_tests_;
+ std::string output_;
+ bool print_time_;
+ bool print_utf8_;
+ internal::Int32 random_seed_;
+ internal::Int32 repeat_;
+ bool shuffle_;
+ internal::Int32 stack_trace_depth_;
+ std::string stream_result_to_;
+ bool throw_on_failure_;
+} GTEST_ATTRIBUTE_UNUSED_;
+
+// Converts a Unicode code point to a narrow string in UTF-8 encoding.
+// code_point parameter is of type UInt32 because wchar_t may not be
+// wide enough to contain a code point.
+// If the code_point is not a valid Unicode code point
+// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
+// to "(Invalid Unicode 0xXXXXXXXX)".
+GTEST_API_ std::string CodePointToUtf8(UInt32 code_point);
+
+// Converts a wide string to a narrow string in UTF-8 encoding.
+// The wide string is assumed to have the following encoding:
+// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS)
+// UTF-32 if sizeof(wchar_t) == 4 (on Linux)
+// Parameter str points to a null-terminated wide string.
+// Parameter num_chars may additionally limit the number
+// of wchar_t characters processed. -1 is used when the entire string
+// should be processed.
+// If the string contains code points that are not valid Unicode code points
+// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output
+// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding
+// and contains invalid UTF-16 surrogate pairs, values in those pairs
+// will be encoded as individual Unicode characters from Basic Normal Plane.
+GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars);
+
+// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file
+// if the variable is present. If a file already exists at this location, this
+// function will write over it. If the variable is present, but the file cannot
+// be created, prints an error and exits.
+void WriteToShardStatusFileIfNeeded();
+
+// Checks whether sharding is enabled by examining the relevant
+// environment variable values. If the variables are present,
+// but inconsistent (e.g., shard_index >= total_shards), prints
+// an error and exits. If in_subprocess_for_death_test, sharding is
+// disabled because it must only be applied to the original test
+// process. Otherwise, we could filter out death tests we intended to execute.
+GTEST_API_ bool ShouldShard(const char* total_shards_str,
+ const char* shard_index_str,
+ bool in_subprocess_for_death_test);
+
+// Parses the environment variable var as an Int32. If it is unset,
+// returns default_val. If it is not an Int32, prints an error and
+// and aborts.
+GTEST_API_ Int32 Int32FromEnvOrDie(const char* env_var, Int32 default_val);
+
+// Given the total number of shards, the shard index, and the test id,
+// returns true iff the test should be run on this shard. The test id is
+// some arbitrary but unique non-negative integer assigned to each test
+// method. Assumes that 0 <= shard_index < total_shards.
+GTEST_API_ bool ShouldRunTestOnShard(
+ int total_shards, int shard_index, int test_id);
+
+// STL container utilities.
+
+// Returns the number of elements in the given container that satisfy
+// the given predicate.
+template <class Container, typename Predicate>
+inline int CountIf(const Container& c, Predicate predicate) {
+ // Implemented as an explicit loop since std::count_if() in libCstd on
+ // Solaris has a non-standard signature.
+ int count = 0;
+ for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) {
+ if (predicate(*it))
+ ++count;
+ }
+ return count;
+}
+
+// Applies a function/functor to each element in the container.
+template <class Container, typename Functor>
+void ForEach(const Container& c, Functor functor) {
+ std::for_each(c.begin(), c.end(), functor);
+}
+
+// Returns the i-th element of the vector, or default_value if i is not
+// in range [0, v.size()).
+template <typename E>
+inline E GetElementOr(const std::vector<E>& v, int i, E default_value) {
+ return (i < 0 || i >= static_cast<int>(v.size())) ? default_value : v[i];
+}
+
+// Performs an in-place shuffle of a range of the vector's elements.
+// 'begin' and 'end' are element indices as an STL-style range;
+// i.e. [begin, end) are shuffled, where 'end' == size() means to
+// shuffle to the end of the vector.
+template <typename E>
+void ShuffleRange(internal::Random* random, int begin, int end,
+ std::vector<E>* v) {
+ const int size = static_cast<int>(v->size());
+ GTEST_CHECK_(0 <= begin && begin <= size)
+ << "Invalid shuffle range start " << begin << ": must be in range [0, "
+ << size << "].";
+ GTEST_CHECK_(begin <= end && end <= size)
+ << "Invalid shuffle range finish " << end << ": must be in range ["
+ << begin << ", " << size << "].";
+
+ // Fisher-Yates shuffle, from
+ // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
+ for (int range_width = end - begin; range_width >= 2; range_width--) {
+ const int last_in_range = begin + range_width - 1;
+ const int selected = begin + random->Generate(range_width);
+ std::swap((*v)[selected], (*v)[last_in_range]);
+ }
+}
+
+// Performs an in-place shuffle of the vector's elements.
+template <typename E>
+inline void Shuffle(internal::Random* random, std::vector<E>* v) {
+ ShuffleRange(random, 0, static_cast<int>(v->size()), v);
+}
+
+// A function for deleting an object. Handy for being used as a
+// functor.
+template <typename T>
+static void Delete(T* x) {
+ delete x;
+}
+
+// A predicate that checks the key of a TestProperty against a known key.
+//
+// TestPropertyKeyIs is copyable.
+class TestPropertyKeyIs {
+ public:
+ // Constructor.
+ //
+ // TestPropertyKeyIs has NO default constructor.
+ explicit TestPropertyKeyIs(const std::string& key) : key_(key) {}
+
+ // Returns true iff the test name of test property matches on key_.
+ bool operator()(const TestProperty& test_property) const {
+ return test_property.key() == key_;
+ }
+
+ private:
+ std::string key_;
+};
+
+// Class UnitTestOptions.
+//
+// This class contains functions for processing options the user
+// specifies when running the tests. It has only static members.
+//
+// In most cases, the user can specify an option using either an
+// environment variable or a command line flag. E.g. you can set the
+// test filter using either GTEST_FILTER or --gtest_filter. If both
+// the variable and the flag are present, the latter overrides the
+// former.
+class GTEST_API_ UnitTestOptions {
+ public:
+ // Functions for processing the gtest_output flag.
+
+ // Returns the output format, or "" for normal printed output.
+ static std::string GetOutputFormat();
+
+ // Returns the absolute path of the requested output file, or the
+ // default (test_detail.xml in the original working directory) if
+ // none was explicitly specified.
+ static std::string GetAbsolutePathToOutputFile();
+
+ // Functions for processing the gtest_filter flag.
+
+ // Returns true iff the wildcard pattern matches the string. The
+ // first ':' or '\0' character in pattern marks the end of it.
+ //
+ // This recursive algorithm isn't very efficient, but is clear and
+ // works well enough for matching test names, which are short.
+ static bool PatternMatchesString(const char *pattern, const char *str);
+
+ // Returns true iff the user-specified filter matches the test case
+ // name and the test name.
+ static bool FilterMatchesTest(const std::string &test_case_name,
+ const std::string &test_name);
+
+#if GTEST_OS_WINDOWS
+ // Function for supporting the gtest_catch_exception flag.
+
+ // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the
+ // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise.
+ // This function is useful as an __except condition.
+ static int GTestShouldProcessSEH(DWORD exception_code);
+#endif // GTEST_OS_WINDOWS
+
+ // Returns true if "name" matches the ':' separated list of glob-style
+ // filters in "filter".
+ static bool MatchesFilter(const std::string& name, const char* filter);
+};
+
+// Returns the current application's name, removing directory path if that
+// is present. Used by UnitTestOptions::GetOutputFile.
+GTEST_API_ FilePath GetCurrentExecutableName();
+
+// The role interface for getting the OS stack trace as a string.
+class OsStackTraceGetterInterface {
+ public:
+ OsStackTraceGetterInterface() {}
+ virtual ~OsStackTraceGetterInterface() {}
+
+ // Returns the current OS stack trace as an std::string. Parameters:
+ //
+ // max_depth - the maximum number of stack frames to be included
+ // in the trace.
+ // skip_count - the number of top frames to be skipped; doesn't count
+ // against max_depth.
+ virtual std::string CurrentStackTrace(int max_depth, int skip_count) = 0;
+
+ // UponLeavingGTest() should be called immediately before Google Test calls
+ // user code. It saves some information about the current stack that
+ // CurrentStackTrace() will use to find and hide Google Test stack frames.
+ virtual void UponLeavingGTest() = 0;
+
+ // This string is inserted in place of stack frames that are part of
+ // Google Test's implementation.
+ static const char* const kElidedFramesMarker;
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface);
+};
+
+// A working implementation of the OsStackTraceGetterInterface interface.
+class OsStackTraceGetter : public OsStackTraceGetterInterface {
+ public:
+ OsStackTraceGetter() {}
+
+ virtual std::string CurrentStackTrace(int max_depth, int skip_count);
+ virtual void UponLeavingGTest();
+
+ private:
+#if GTEST_HAS_ABSL
+ Mutex mutex_; // Protects all internal state.
+
+ // We save the stack frame below the frame that calls user code.
+ // We do this because the address of the frame immediately below
+ // the user code changes between the call to UponLeavingGTest()
+ // and any calls to the stack trace code from within the user code.
+ void* caller_frame_ = nullptr;
+#endif // GTEST_HAS_ABSL
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter);
+};
+
+// Information about a Google Test trace point.
+struct TraceInfo {
+ const char* file;
+ int line;
+ std::string message;
+};
+
+// This is the default global test part result reporter used in UnitTestImpl.
+// This class should only be used by UnitTestImpl.
+class DefaultGlobalTestPartResultReporter
+ : public TestPartResultReporterInterface {
+ public:
+ explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test);
+ // Implements the TestPartResultReporterInterface. Reports the test part
+ // result in the current test.
+ virtual void ReportTestPartResult(const TestPartResult& result);
+
+ private:
+ UnitTestImpl* const unit_test_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter);
+};
+
+// This is the default per thread test part result reporter used in
+// UnitTestImpl. This class should only be used by UnitTestImpl.
+class DefaultPerThreadTestPartResultReporter
+ : public TestPartResultReporterInterface {
+ public:
+ explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test);
+ // Implements the TestPartResultReporterInterface. The implementation just
+ // delegates to the current global test part result reporter of *unit_test_.
+ virtual void ReportTestPartResult(const TestPartResult& result);
+
+ private:
+ UnitTestImpl* const unit_test_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter);
+};
+
+// The private implementation of the UnitTest class. We don't protect
+// the methods under a mutex, as this class is not accessible by a
+// user and the UnitTest class that delegates work to this class does
+// proper locking.
+class GTEST_API_ UnitTestImpl {
+ public:
+ explicit UnitTestImpl(UnitTest* parent);
+ virtual ~UnitTestImpl();
+
+ // There are two different ways to register your own TestPartResultReporter.
+ // You can register your own repoter to listen either only for test results
+ // from the current thread or for results from all threads.
+ // By default, each per-thread test result repoter just passes a new
+ // TestPartResult to the global test result reporter, which registers the
+ // test part result for the currently running test.
+
+ // Returns the global test part result reporter.
+ TestPartResultReporterInterface* GetGlobalTestPartResultReporter();
+
+ // Sets the global test part result reporter.
+ void SetGlobalTestPartResultReporter(
+ TestPartResultReporterInterface* reporter);
+
+ // Returns the test part result reporter for the current thread.
+ TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread();
+
+ // Sets the test part result reporter for the current thread.
+ void SetTestPartResultReporterForCurrentThread(
+ TestPartResultReporterInterface* reporter);
+
+ // Gets the number of successful test cases.
+ int successful_test_case_count() const;
+
+ // Gets the number of failed test cases.
+ int failed_test_case_count() const;
+
+ // Gets the number of all test cases.
+ int total_test_case_count() const;
+
+ // Gets the number of all test cases that contain at least one test
+ // that should run.
+ int test_case_to_run_count() const;
+
+ // Gets the number of successful tests.
+ int successful_test_count() const;
+
+ // Gets the number of failed tests.
+ int failed_test_count() const;
+
+ // Gets the number of disabled tests that will be reported in the XML report.
+ int reportable_disabled_test_count() const;
+
+ // Gets the number of disabled tests.
+ int disabled_test_count() const;
+
+ // Gets the number of tests to be printed in the XML report.
+ int reportable_test_count() const;
+
+ // Gets the number of all tests.
+ int total_test_count() const;
+
+ // Gets the number of tests that should run.
+ int test_to_run_count() const;
+
+ // Gets the time of the test program start, in ms from the start of the
+ // UNIX epoch.
+ TimeInMillis start_timestamp() const { return start_timestamp_; }
+
+ // Gets the elapsed time, in milliseconds.
+ TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+ // Returns true iff the unit test passed (i.e. all test cases passed).
+ bool Passed() const { return !Failed(); }
+
+ // Returns true iff the unit test failed (i.e. some test case failed
+ // or something outside of all tests failed).
+ bool Failed() const {
+ return failed_test_case_count() > 0 || ad_hoc_test_result()->Failed();
+ }
+
+ // Gets the i-th test case among all the test cases. i can range from 0 to
+ // total_test_case_count() - 1. If i is not in that range, returns NULL.
+ const TestCase* GetTestCase(int i) const {
+ const int index = GetElementOr(test_case_indices_, i, -1);
+ return index < 0 ? NULL : test_cases_[i];
+ }
+
+ // Gets the i-th test case among all the test cases. i can range from 0 to
+ // total_test_case_count() - 1. If i is not in that range, returns NULL.
+ TestCase* GetMutableTestCase(int i) {
+ const int index = GetElementOr(test_case_indices_, i, -1);
+ return index < 0 ? NULL : test_cases_[index];
+ }
+
+ // Provides access to the event listener list.
+ TestEventListeners* listeners() { return &listeners_; }
+
+ // Returns the TestResult for the test that's currently running, or
+ // the TestResult for the ad hoc test if no test is running.
+ TestResult* current_test_result();
+
+ // Returns the TestResult for the ad hoc test.
+ const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; }
+
+ // Sets the OS stack trace getter.
+ //
+ // Does nothing if the input and the current OS stack trace getter
+ // are the same; otherwise, deletes the old getter and makes the
+ // input the current getter.
+ void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter);
+
+ // Returns the current OS stack trace getter if it is not NULL;
+ // otherwise, creates an OsStackTraceGetter, makes it the current
+ // getter, and returns it.
+ OsStackTraceGetterInterface* os_stack_trace_getter();
+
+ // Returns the current OS stack trace as an std::string.
+ //
+ // The maximum number of stack frames to be included is specified by
+ // the gtest_stack_trace_depth flag. The skip_count parameter
+ // specifies the number of top frames to be skipped, which doesn't
+ // count against the number of frames to be included.
+ //
+ // For example, if Foo() calls Bar(), which in turn calls
+ // CurrentOsStackTraceExceptTop(1), Foo() will be included in the
+ // trace but Bar() and CurrentOsStackTraceExceptTop() won't.
+ std::string CurrentOsStackTraceExceptTop(int skip_count) GTEST_NO_INLINE_;
+
+ // Finds and returns a TestCase with the given name. If one doesn't
+ // exist, creates one and returns it.
+ //
+ // Arguments:
+ //
+ // test_case_name: name of the test case
+ // type_param: the name of the test's type parameter, or NULL if
+ // this is not a typed or a type-parameterized test.
+ // set_up_tc: pointer to the function that sets up the test case
+ // tear_down_tc: pointer to the function that tears down the test case
+ TestCase* GetTestCase(const char* test_case_name,
+ const char* type_param,
+ Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc);
+
+ // Adds a TestInfo to the unit test.
+ //
+ // Arguments:
+ //
+ // set_up_tc: pointer to the function that sets up the test case
+ // tear_down_tc: pointer to the function that tears down the test case
+ // test_info: the TestInfo object
+ void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc,
+ TestInfo* test_info) {
+ // In order to support thread-safe death tests, we need to
+ // remember the original working directory when the test program
+ // was first invoked. We cannot do this in RUN_ALL_TESTS(), as
+ // the user may have changed the current directory before calling
+ // RUN_ALL_TESTS(). Therefore we capture the current directory in
+ // AddTestInfo(), which is called to register a TEST or TEST_F
+ // before main() is reached.
+ if (original_working_dir_.IsEmpty()) {
+ original_working_dir_.Set(FilePath::GetCurrentDir());
+ GTEST_CHECK_(!original_working_dir_.IsEmpty())
+ << "Failed to get the current working directory.";
+ }
+
+ GetTestCase(test_info->test_case_name(),
+ test_info->type_param(),
+ set_up_tc,
+ tear_down_tc)->AddTestInfo(test_info);
+ }
+
+ // Returns ParameterizedTestCaseRegistry object used to keep track of
+ // value-parameterized tests and instantiate and register them.
+ internal::ParameterizedTestCaseRegistry& parameterized_test_registry() {
+ return parameterized_test_registry_;
+ }
+
+ // Sets the TestCase object for the test that's currently running.
+ void set_current_test_case(TestCase* a_current_test_case) {
+ current_test_case_ = a_current_test_case;
+ }
+
+ // Sets the TestInfo object for the test that's currently running. If
+ // current_test_info is NULL, the assertion results will be stored in
+ // ad_hoc_test_result_.
+ void set_current_test_info(TestInfo* a_current_test_info) {
+ current_test_info_ = a_current_test_info;
+ }
+
+ // Registers all parameterized tests defined using TEST_P and
+ // INSTANTIATE_TEST_CASE_P, creating regular tests for each test/parameter
+ // combination. This method can be called more then once; it has guards
+ // protecting from registering the tests more then once. If
+ // value-parameterized tests are disabled, RegisterParameterizedTests is
+ // present but does nothing.
+ void RegisterParameterizedTests();
+
+ // Runs all tests in this UnitTest object, prints the result, and
+ // returns true if all tests are successful. If any exception is
+ // thrown during a test, this test is considered to be failed, but
+ // the rest of the tests will still be run.
+ bool RunAllTests();
+
+ // Clears the results of all tests, except the ad hoc tests.
+ void ClearNonAdHocTestResult() {
+ ForEach(test_cases_, TestCase::ClearTestCaseResult);
+ }
+
+ // Clears the results of ad-hoc test assertions.
+ void ClearAdHocTestResult() {
+ ad_hoc_test_result_.Clear();
+ }
+
+ // Adds a TestProperty to the current TestResult object when invoked in a
+ // context of a test or a test case, or to the global property set. If the
+ // result already contains a property with the same key, the value will be
+ // updated.
+ void RecordProperty(const TestProperty& test_property);
+
+ enum ReactionToSharding {
+ HONOR_SHARDING_PROTOCOL,
+ IGNORE_SHARDING_PROTOCOL
+ };
+
+ // Matches the full name of each test against the user-specified
+ // filter to decide whether the test should run, then records the
+ // result in each TestCase and TestInfo object.
+ // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests
+ // based on sharding variables in the environment.
+ // Returns the number of tests that should run.
+ int FilterTests(ReactionToSharding shard_tests);
+
+ // Prints the names of the tests matching the user-specified filter flag.
+ void ListTestsMatchingFilter();
+
+ const TestCase* current_test_case() const { return current_test_case_; }
+ TestInfo* current_test_info() { return current_test_info_; }
+ const TestInfo* current_test_info() const { return current_test_info_; }
+
+ // Returns the vector of environments that need to be set-up/torn-down
+ // before/after the tests are run.
+ std::vector<Environment*>& environments() { return environments_; }
+
+ // Getters for the per-thread Google Test trace stack.
+ std::vector<TraceInfo>& gtest_trace_stack() {
+ return *(gtest_trace_stack_.pointer());
+ }
+ const std::vector<TraceInfo>& gtest_trace_stack() const {
+ return gtest_trace_stack_.get();
+ }
+
+#if GTEST_HAS_DEATH_TEST
+ void InitDeathTestSubprocessControlInfo() {
+ internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag());
+ }
+ // Returns a pointer to the parsed --gtest_internal_run_death_test
+ // flag, or NULL if that flag was not specified.
+ // This information is useful only in a death test child process.
+ // Must not be called before a call to InitGoogleTest.
+ const InternalRunDeathTestFlag* internal_run_death_test_flag() const {
+ return internal_run_death_test_flag_.get();
+ }
+
+ // Returns a pointer to the current death test factory.
+ internal::DeathTestFactory* death_test_factory() {
+ return death_test_factory_.get();
+ }
+
+ void SuppressTestEventsIfInSubprocess();
+
+ friend class ReplaceDeathTestFactory;
+#endif // GTEST_HAS_DEATH_TEST
+
+ // Initializes the event listener performing XML output as specified by
+ // UnitTestOptions. Must not be called before InitGoogleTest.
+ void ConfigureXmlOutput();
+
+#if GTEST_CAN_STREAM_RESULTS_
+ // Initializes the event listener for streaming test results to a socket.
+ // Must not be called before InitGoogleTest.
+ void ConfigureStreamingOutput();
+#endif
+
+ // Performs initialization dependent upon flag values obtained in
+ // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to
+ // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest
+ // this function is also called from RunAllTests. Since this function can be
+ // called more than once, it has to be idempotent.
+ void PostFlagParsingInit();
+
+ // Gets the random seed used at the start of the current test iteration.
+ int random_seed() const { return random_seed_; }
+
+ // Gets the random number generator.
+ internal::Random* random() { return &random_; }
+
+ // Shuffles all test cases, and the tests within each test case,
+ // making sure that death tests are still run first.
+ void ShuffleTests();
+
+ // Restores the test cases and tests to their order before the first shuffle.
+ void UnshuffleTests();
+
+ // Returns the value of GTEST_FLAG(catch_exceptions) at the moment
+ // UnitTest::Run() starts.
+ bool catch_exceptions() const { return catch_exceptions_; }
+
+ private:
+ friend class ::testing::UnitTest;
+
+ // Used by UnitTest::Run() to capture the state of
+ // GTEST_FLAG(catch_exceptions) at the moment it starts.
+ void set_catch_exceptions(bool value) { catch_exceptions_ = value; }
+
+ // The UnitTest object that owns this implementation object.
+ UnitTest* const parent_;
+
+ // The working directory when the first TEST() or TEST_F() was
+ // executed.
+ internal::FilePath original_working_dir_;
+
+ // The default test part result reporters.
+ DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_;
+ DefaultPerThreadTestPartResultReporter
+ default_per_thread_test_part_result_reporter_;
+
+ // Points to (but doesn't own) the global test part result reporter.
+ TestPartResultReporterInterface* global_test_part_result_repoter_;
+
+ // Protects read and write access to global_test_part_result_reporter_.
+ internal::Mutex global_test_part_result_reporter_mutex_;
+
+ // Points to (but doesn't own) the per-thread test part result reporter.
+ internal::ThreadLocal<TestPartResultReporterInterface*>
+ per_thread_test_part_result_reporter_;
+
+ // The vector of environments that need to be set-up/torn-down
+ // before/after the tests are run.
+ std::vector<Environment*> environments_;
+
+ // The vector of TestCases in their original order. It owns the
+ // elements in the vector.
+ std::vector<TestCase*> test_cases_;
+
+ // Provides a level of indirection for the test case list to allow
+ // easy shuffling and restoring the test case order. The i-th
+ // element of this vector is the index of the i-th test case in the
+ // shuffled order.
+ std::vector<int> test_case_indices_;
+
+ // ParameterizedTestRegistry object used to register value-parameterized
+ // tests.
+ internal::ParameterizedTestCaseRegistry parameterized_test_registry_;
+
+ // Indicates whether RegisterParameterizedTests() has been called already.
+ bool parameterized_tests_registered_;
+
+ // Index of the last death test case registered. Initially -1.
+ int last_death_test_case_;
+
+ // This points to the TestCase for the currently running test. It
+ // changes as Google Test goes through one test case after another.
+ // When no test is running, this is set to NULL and Google Test
+ // stores assertion results in ad_hoc_test_result_. Initially NULL.
+ TestCase* current_test_case_;
+
+ // This points to the TestInfo for the currently running test. It
+ // changes as Google Test goes through one test after another. When
+ // no test is running, this is set to NULL and Google Test stores
+ // assertion results in ad_hoc_test_result_. Initially NULL.
+ TestInfo* current_test_info_;
+
+ // Normally, a user only writes assertions inside a TEST or TEST_F,
+ // or inside a function called by a TEST or TEST_F. Since Google
+ // Test keeps track of which test is current running, it can
+ // associate such an assertion with the test it belongs to.
+ //
+ // If an assertion is encountered when no TEST or TEST_F is running,
+ // Google Test attributes the assertion result to an imaginary "ad hoc"
+ // test, and records the result in ad_hoc_test_result_.
+ TestResult ad_hoc_test_result_;
+
+ // The list of event listeners that can be used to track events inside
+ // Google Test.
+ TestEventListeners listeners_;
+
+ // The OS stack trace getter. Will be deleted when the UnitTest
+ // object is destructed. By default, an OsStackTraceGetter is used,
+ // but the user can set this field to use a custom getter if that is
+ // desired.
+ OsStackTraceGetterInterface* os_stack_trace_getter_;
+
+ // True iff PostFlagParsingInit() has been called.
+ bool post_flag_parse_init_performed_;
+
+ // The random number seed used at the beginning of the test run.
+ int random_seed_;
+
+ // Our random number generator.
+ internal::Random random_;
+
+ // The time of the test program start, in ms from the start of the
+ // UNIX epoch.
+ TimeInMillis start_timestamp_;
+
+ // How long the test took to run, in milliseconds.
+ TimeInMillis elapsed_time_;
+
+#if GTEST_HAS_DEATH_TEST
+ // The decomposed components of the gtest_internal_run_death_test flag,
+ // parsed when RUN_ALL_TESTS is called.
+ internal::scoped_ptr<InternalRunDeathTestFlag> internal_run_death_test_flag_;
+ internal::scoped_ptr<internal::DeathTestFactory> death_test_factory_;
+#endif // GTEST_HAS_DEATH_TEST
+
+ // A per-thread stack of traces created by the SCOPED_TRACE() macro.
+ internal::ThreadLocal<std::vector<TraceInfo> > gtest_trace_stack_;
+
+ // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests()
+ // starts.
+ bool catch_exceptions_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl);
+}; // class UnitTestImpl
+
+// Convenience function for accessing the global UnitTest
+// implementation object.
+inline UnitTestImpl* GetUnitTestImpl() {
+ return UnitTest::GetInstance()->impl();
+}
+
+#if GTEST_USES_SIMPLE_RE
+
+// Internal helper functions for implementing the simple regular
+// expression matcher.
+GTEST_API_ bool IsInSet(char ch, const char* str);
+GTEST_API_ bool IsAsciiDigit(char ch);
+GTEST_API_ bool IsAsciiPunct(char ch);
+GTEST_API_ bool IsRepeat(char ch);
+GTEST_API_ bool IsAsciiWhiteSpace(char ch);
+GTEST_API_ bool IsAsciiWordChar(char ch);
+GTEST_API_ bool IsValidEscape(char ch);
+GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch);
+GTEST_API_ bool ValidateRegex(const char* regex);
+GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str);
+GTEST_API_ bool MatchRepetitionAndRegexAtHead(
+ bool escaped, char ch, char repeat, const char* regex, const char* str);
+GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str);
+
+#endif // GTEST_USES_SIMPLE_RE
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test.
+GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv);
+GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv);
+
+#if GTEST_HAS_DEATH_TEST
+
+// Returns the message describing the last system error, regardless of the
+// platform.
+GTEST_API_ std::string GetLastErrnoDescription();
+
+// Attempts to parse a string into a positive integer pointed to by the
+// number parameter. Returns true if that is possible.
+// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use
+// it here.
+template <typename Integer>
+bool ParseNaturalNumber(const ::std::string& str, Integer* number) {
+ // Fail fast if the given string does not begin with a digit;
+ // this bypasses strtoXXX's "optional leading whitespace and plus
+ // or minus sign" semantics, which are undesirable here.
+ if (str.empty() || !IsDigit(str[0])) {
+ return false;
+ }
+ errno = 0;
+
+ char* end;
+ // BiggestConvertible is the largest integer type that system-provided
+ // string-to-number conversion routines can return.
+
+# if GTEST_OS_WINDOWS && !defined(__GNUC__)
+
+ // MSVC and C++ Builder define __int64 instead of the standard long long.
+ typedef unsigned __int64 BiggestConvertible;
+ const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10);
+
+# else
+
+ typedef unsigned long long BiggestConvertible; // NOLINT
+ const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10);
+
+# endif // GTEST_OS_WINDOWS && !defined(__GNUC__)
+
+ const bool parse_success = *end == '\0' && errno == 0;
+
+ // FIXME: Convert this to compile time assertion when it is
+ // available.
+ GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed));
+
+ const Integer result = static_cast<Integer>(parsed);
+ if (parse_success && static_cast<BiggestConvertible>(result) == parsed) {
+ *number = result;
+ return true;
+ }
+ return false;
+}
+#endif // GTEST_HAS_DEATH_TEST
+
+// TestResult contains some private methods that should be hidden from
+// Google Test user but are required for testing. This class allow our tests
+// to access them.
+//
+// This class is supplied only for the purpose of testing Google Test's own
+// constructs. Do not use it in user tests, either directly or indirectly.
+class TestResultAccessor {
+ public:
+ static void RecordProperty(TestResult* test_result,
+ const std::string& xml_element,
+ const TestProperty& property) {
+ test_result->RecordProperty(xml_element, property);
+ }
+
+ static void ClearTestPartResults(TestResult* test_result) {
+ test_result->ClearTestPartResults();
+ }
+
+ static const std::vector<testing::TestPartResult>& test_part_results(
+ const TestResult& test_result) {
+ return test_result.test_part_results();
+ }
+};
+
+#if GTEST_CAN_STREAM_RESULTS_
+
+// Streams test results to the given port on the given host machine.
+class StreamingListener : public EmptyTestEventListener {
+ public:
+ // Abstract base class for writing strings to a socket.
+ class AbstractSocketWriter {
+ public:
+ virtual ~AbstractSocketWriter() {}
+
+ // Sends a string to the socket.
+ virtual void Send(const std::string& message) = 0;
+
+ // Closes the socket.
+ virtual void CloseConnection() {}
+
+ // Sends a string and a newline to the socket.
+ void SendLn(const std::string& message) { Send(message + "\n"); }
+ };
+
+ // Concrete class for actually writing strings to a socket.
+ class SocketWriter : public AbstractSocketWriter {
+ public:
+ SocketWriter(const std::string& host, const std::string& port)
+ : sockfd_(-1), host_name_(host), port_num_(port) {
+ MakeConnection();
+ }
+
+ virtual ~SocketWriter() {
+ if (sockfd_ != -1)
+ CloseConnection();
+ }
+
+ // Sends a string to the socket.
+ virtual void Send(const std::string& message) {
+ GTEST_CHECK_(sockfd_ != -1)
+ << "Send() can be called only when there is a connection.";
+
+ const int len = static_cast<int>(message.length());
+ if (write(sockfd_, message.c_str(), len) != len) {
+ GTEST_LOG_(WARNING)
+ << "stream_result_to: failed to stream to "
+ << host_name_ << ":" << port_num_;
+ }
+ }
+
+ private:
+ // Creates a client socket and connects to the server.
+ void MakeConnection();
+
+ // Closes the socket.
+ void CloseConnection() {
+ GTEST_CHECK_(sockfd_ != -1)
+ << "CloseConnection() can be called only when there is a connection.";
+
+ close(sockfd_);
+ sockfd_ = -1;
+ }
+
+ int sockfd_; // socket file descriptor
+ const std::string host_name_;
+ const std::string port_num_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(SocketWriter);
+ }; // class SocketWriter
+
+ // Escapes '=', '&', '%', and '\n' characters in str as "%xx".
+ static std::string UrlEncode(const char* str);
+
+ StreamingListener(const std::string& host, const std::string& port)
+ : socket_writer_(new SocketWriter(host, port)) {
+ Start();
+ }
+
+ explicit StreamingListener(AbstractSocketWriter* socket_writer)
+ : socket_writer_(socket_writer) { Start(); }
+
+ void OnTestProgramStart(const UnitTest& /* unit_test */) {
+ SendLn("event=TestProgramStart");
+ }
+
+ void OnTestProgramEnd(const UnitTest& unit_test) {
+ // Note that Google Test current only report elapsed time for each
+ // test iteration, not for the entire test program.
+ SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed()));
+
+ // Notify the streaming server to stop.
+ socket_writer_->CloseConnection();
+ }
+
+ void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) {
+ SendLn("event=TestIterationStart&iteration=" +
+ StreamableToString(iteration));
+ }
+
+ void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) {
+ SendLn("event=TestIterationEnd&passed=" +
+ FormatBool(unit_test.Passed()) + "&elapsed_time=" +
+ StreamableToString(unit_test.elapsed_time()) + "ms");
+ }
+
+ void OnTestCaseStart(const TestCase& test_case) {
+ SendLn(std::string("event=TestCaseStart&name=") + test_case.name());
+ }
+
+ void OnTestCaseEnd(const TestCase& test_case) {
+ SendLn("event=TestCaseEnd&passed=" + FormatBool(test_case.Passed())
+ + "&elapsed_time=" + StreamableToString(test_case.elapsed_time())
+ + "ms");
+ }
+
+ void OnTestStart(const TestInfo& test_info) {
+ SendLn(std::string("event=TestStart&name=") + test_info.name());
+ }
+
+ void OnTestEnd(const TestInfo& test_info) {
+ SendLn("event=TestEnd&passed=" +
+ FormatBool((test_info.result())->Passed()) +
+ "&elapsed_time=" +
+ StreamableToString((test_info.result())->elapsed_time()) + "ms");
+ }
+
+ void OnTestPartResult(const TestPartResult& test_part_result) {
+ const char* file_name = test_part_result.file_name();
+ if (file_name == NULL)
+ file_name = "";
+ SendLn("event=TestPartResult&file=" + UrlEncode(file_name) +
+ "&line=" + StreamableToString(test_part_result.line_number()) +
+ "&message=" + UrlEncode(test_part_result.message()));
+ }
+
+ private:
+ // Sends the given message and a newline to the socket.
+ void SendLn(const std::string& message) { socket_writer_->SendLn(message); }
+
+ // Called at the start of streaming to notify the receiver what
+ // protocol we are using.
+ void Start() { SendLn("gtest_streaming_protocol_version=1.0"); }
+
+ std::string FormatBool(bool value) { return value ? "1" : "0"; }
+
+ const scoped_ptr<AbstractSocketWriter> socket_writer_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener);
+}; // class StreamingListener
+
+#endif // GTEST_CAN_STREAM_RESULTS_
+
+} // namespace internal
+} // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+#endif // GTEST_SRC_GTEST_INTERNAL_INL_H_
+
+#if GTEST_OS_WINDOWS
+# define vsnprintf _vsnprintf
+#endif // GTEST_OS_WINDOWS
+
+#if GTEST_OS_MAC
+#ifndef GTEST_OS_IOS
+#include <crt_externs.h>
+#endif
+#endif
+
+#if GTEST_HAS_ABSL
+#include "absl/debugging/failure_signal_handler.h"
+#include "absl/debugging/stacktrace.h"
+#include "absl/debugging/symbolize.h"
+#include "absl/strings/str_cat.h"
+#endif // GTEST_HAS_ABSL
+
+namespace testing {
+
+using internal::CountIf;
+using internal::ForEach;
+using internal::GetElementOr;
+using internal::Shuffle;
+
+// Constants.
+
+// A test whose test case name or test name matches this filter is
+// disabled and not run.
+static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*";
+
+// A test case whose name matches this filter is considered a death
+// test case and will be run before test cases whose name doesn't
+// match this filter.
+static const char kDeathTestCaseFilter[] = "*DeathTest:*DeathTest/*";
+
+// A test filter that matches everything.
+static const char kUniversalFilter[] = "*";
+
+// The default output format.
+static const char kDefaultOutputFormat[] = "xml";
+// The default output file.
+static const char kDefaultOutputFile[] = "test_detail";
+
+// The environment variable name for the test shard index.
+static const char kTestShardIndex[] = "GTEST_SHARD_INDEX";
+// The environment variable name for the total number of test shards.
+static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS";
+// The environment variable name for the test shard status file.
+static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE";
+
+namespace internal {
+
+// The text used in failure messages to indicate the start of the
+// stack trace.
+const char kStackTraceMarker[] = "\nStack trace:\n";
+
+// g_help_flag is true iff the --help flag or an equivalent form is
+// specified on the command line.
+bool g_help_flag = false;
+
+// Utilty function to Open File for Writing
+static FILE* OpenFileForWriting(const std::string& output_file) {
+ FILE* fileout = NULL;
+ FilePath output_file_path(output_file);
+ FilePath output_dir(output_file_path.RemoveFileName());
+
+ if (output_dir.CreateDirectoriesRecursively()) {
+ fileout = posix::FOpen(output_file.c_str(), "w");
+ }
+ if (fileout == NULL) {
+ GTEST_LOG_(FATAL) << "Unable to open file \"" << output_file << "\"";
+ }
+ return fileout;
+}
+
+} // namespace internal
+
+// Bazel passes in the argument to '--test_filter' via the TESTBRIDGE_TEST_ONLY
+// environment variable.
+static const char* GetDefaultFilter() {
+ const char* const testbridge_test_only =
+ internal::posix::GetEnv("TESTBRIDGE_TEST_ONLY");
+ if (testbridge_test_only != NULL) {
+ return testbridge_test_only;
+ }
+ return kUniversalFilter;
+}
+
+GTEST_DEFINE_bool_(
+ also_run_disabled_tests,
+ internal::BoolFromGTestEnv("also_run_disabled_tests", false),
+ "Run disabled tests too, in addition to the tests normally being run.");
+
+GTEST_DEFINE_bool_(
+ break_on_failure,
+ internal::BoolFromGTestEnv("break_on_failure", false),
+ "True iff a failed assertion should be a debugger break-point.");
+
+GTEST_DEFINE_bool_(
+ catch_exceptions,
+ internal::BoolFromGTestEnv("catch_exceptions", true),
+ "True iff " GTEST_NAME_
+ " should catch exceptions and treat them as test failures.");
+
+GTEST_DEFINE_string_(
+ color,
+ internal::StringFromGTestEnv("color", "auto"),
+ "Whether to use colors in the output. Valid values: yes, no, "
+ "and auto. 'auto' means to use colors if the output is "
+ "being sent to a terminal and the TERM environment variable "
+ "is set to a terminal type that supports colors.");
+
+GTEST_DEFINE_string_(
+ filter,
+ internal::StringFromGTestEnv("filter", GetDefaultFilter()),
+ "A colon-separated list of glob (not regex) patterns "
+ "for filtering the tests to run, optionally followed by a "
+ "'-' and a : separated list of negative patterns (tests to "
+ "exclude). A test is run if it matches one of the positive "
+ "patterns and does not match any of the negative patterns.");
+
+GTEST_DEFINE_bool_(
+ install_failure_signal_handler,
+ internal::BoolFromGTestEnv("install_failure_signal_handler", false),
+ "If true and supported on the current platform, " GTEST_NAME_ " should "
+ "install a signal handler that dumps debugging information when fatal "
+ "signals are raised.");
+
+GTEST_DEFINE_bool_(list_tests, false,
+ "List all tests without running them.");
+
+// The net priority order after flag processing is thus:
+// --gtest_output command line flag
+// GTEST_OUTPUT environment variable
+// XML_OUTPUT_FILE environment variable
+// ''
+GTEST_DEFINE_string_(
+ output,
+ internal::StringFromGTestEnv("output",
+ internal::OutputFlagAlsoCheckEnvVar().c_str()),
+ "A format (defaults to \"xml\" but can be specified to be \"json\"), "
+ "optionally followed by a colon and an output file name or directory. "
+ "A directory is indicated by a trailing pathname separator. "
+ "Examples: \"xml:filename.xml\", \"xml::directoryname/\". "
+ "If a directory is specified, output files will be created "
+ "within that directory, with file-names based on the test "
+ "executable's name and, if necessary, made unique by adding "
+ "digits.");
+
+GTEST_DEFINE_bool_(
+ print_time,
+ internal::BoolFromGTestEnv("print_time", true),
+ "True iff " GTEST_NAME_
+ " should display elapsed time in text output.");
+
+GTEST_DEFINE_bool_(
+ print_utf8,
+ internal::BoolFromGTestEnv("print_utf8", true),
+ "True iff " GTEST_NAME_
+ " prints UTF8 characters as text.");
+
+GTEST_DEFINE_int32_(
+ random_seed,
+ internal::Int32FromGTestEnv("random_seed", 0),
+ "Random number seed to use when shuffling test orders. Must be in range "
+ "[1, 99999], or 0 to use a seed based on the current time.");
+
+GTEST_DEFINE_int32_(
+ repeat,
+ internal::Int32FromGTestEnv("repeat", 1),
+ "How many times to repeat each test. Specify a negative number "
+ "for repeating forever. Useful for shaking out flaky tests.");
+
+GTEST_DEFINE_bool_(
+ show_internal_stack_frames, false,
+ "True iff " GTEST_NAME_ " should include internal stack frames when "
+ "printing test failure stack traces.");
+
+GTEST_DEFINE_bool_(
+ shuffle,
+ internal::BoolFromGTestEnv("shuffle", false),
+ "True iff " GTEST_NAME_
+ " should randomize tests' order on every run.");
+
+GTEST_DEFINE_int32_(
+ stack_trace_depth,
+ internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth),
+ "The maximum number of stack frames to print when an "
+ "assertion fails. The valid range is 0 through 100, inclusive.");
+
+GTEST_DEFINE_string_(
+ stream_result_to,
+ internal::StringFromGTestEnv("stream_result_to", ""),
+ "This flag specifies the host name and the port number on which to stream "
+ "test results. Example: \"localhost:555\". The flag is effective only on "
+ "Linux.");
+
+GTEST_DEFINE_bool_(
+ throw_on_failure,
+ internal::BoolFromGTestEnv("throw_on_failure", false),
+ "When this flag is specified, a failed assertion will throw an exception "
+ "if exceptions are enabled or exit the program with a non-zero code "
+ "otherwise. For use with an external test framework.");
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+GTEST_DEFINE_string_(
+ flagfile,
+ internal::StringFromGTestEnv("flagfile", ""),
+ "This flag specifies the flagfile to read command-line flags from.");
+#endif // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+namespace internal {
+
+// Generates a random number from [0, range), using a Linear
+// Congruential Generator (LCG). Crashes if 'range' is 0 or greater
+// than kMaxRange.
+UInt32 Random::Generate(UInt32 range) {
+ // These constants are the same as are used in glibc's rand(3).
+ // Use wider types than necessary to prevent unsigned overflow diagnostics.
+ state_ = static_cast<UInt32>(1103515245ULL*state_ + 12345U) % kMaxRange;
+
+ GTEST_CHECK_(range > 0)
+ << "Cannot generate a number in the range [0, 0).";
+ GTEST_CHECK_(range <= kMaxRange)
+ << "Generation of a number in [0, " << range << ") was requested, "
+ << "but this can only generate numbers in [0, " << kMaxRange << ").";
+
+ // Converting via modulus introduces a bit of downward bias, but
+ // it's simple, and a linear congruential generator isn't too good
+ // to begin with.
+ return state_ % range;
+}
+
+// GTestIsInitialized() returns true iff the user has initialized
+// Google Test. Useful for catching the user mistake of not initializing
+// Google Test before calling RUN_ALL_TESTS().
+static bool GTestIsInitialized() { return GetArgvs().size() > 0; }
+
+// Iterates over a vector of TestCases, keeping a running sum of the
+// results of calling a given int-returning method on each.
+// Returns the sum.
+static int SumOverTestCaseList(const std::vector<TestCase*>& case_list,
+ int (TestCase::*method)() const) {
+ int sum = 0;
+ for (size_t i = 0; i < case_list.size(); i++) {
+ sum += (case_list[i]->*method)();
+ }
+ return sum;
+}
+
+// Returns true iff the test case passed.
+static bool TestCasePassed(const TestCase* test_case) {
+ return test_case->should_run() && test_case->Passed();
+}
+
+// Returns true iff the test case failed.
+static bool TestCaseFailed(const TestCase* test_case) {
+ return test_case->should_run() && test_case->Failed();
+}
+
+// Returns true iff test_case contains at least one test that should
+// run.
+static bool ShouldRunTestCase(const TestCase* test_case) {
+ return test_case->should_run();
+}
+
+// AssertHelper constructor.
+AssertHelper::AssertHelper(TestPartResult::Type type,
+ const char* file,
+ int line,
+ const char* message)
+ : data_(new AssertHelperData(type, file, line, message)) {
+}
+
+AssertHelper::~AssertHelper() {
+ delete data_;
+}
+
+// Message assignment, for assertion streaming support.
+void AssertHelper::operator=(const Message& message) const {
+ UnitTest::GetInstance()->
+ AddTestPartResult(data_->type, data_->file, data_->line,
+ AppendUserMessage(data_->message, message),
+ UnitTest::GetInstance()->impl()
+ ->CurrentOsStackTraceExceptTop(1)
+ // Skips the stack frame for this function itself.
+ ); // NOLINT
+}
+
+// Mutex for linked pointers.
+GTEST_API_ GTEST_DEFINE_STATIC_MUTEX_(g_linked_ptr_mutex);
+
+// A copy of all command line arguments. Set by InitGoogleTest().
+static ::std::vector<std::string> g_argvs;
+
+::std::vector<std::string> GetArgvs() {
+#if defined(GTEST_CUSTOM_GET_ARGVS_)
+ // GTEST_CUSTOM_GET_ARGVS_() may return a container of std::string or
+ // ::string. This code converts it to the appropriate type.
+ const auto& custom = GTEST_CUSTOM_GET_ARGVS_();
+ return ::std::vector<std::string>(custom.begin(), custom.end());
+#else // defined(GTEST_CUSTOM_GET_ARGVS_)
+ return g_argvs;
+#endif // defined(GTEST_CUSTOM_GET_ARGVS_)
+}
+
+// Returns the current application's name, removing directory path if that
+// is present.
+FilePath GetCurrentExecutableName() {
+ FilePath result;
+
+#if GTEST_OS_WINDOWS
+ result.Set(FilePath(GetArgvs()[0]).RemoveExtension("exe"));
+#else
+ result.Set(FilePath(GetArgvs()[0]));
+#endif // GTEST_OS_WINDOWS
+
+ return result.RemoveDirectoryName();
+}
+
+// Functions for processing the gtest_output flag.
+
+// Returns the output format, or "" for normal printed output.
+std::string UnitTestOptions::GetOutputFormat() {
+ const char* const gtest_output_flag = GTEST_FLAG(output).c_str();
+ const char* const colon = strchr(gtest_output_flag, ':');
+ return (colon == NULL) ?
+ std::string(gtest_output_flag) :
+ std::string(gtest_output_flag, colon - gtest_output_flag);
+}
+
+// Returns the name of the requested output file, or the default if none
+// was explicitly specified.
+std::string UnitTestOptions::GetAbsolutePathToOutputFile() {
+ const char* const gtest_output_flag = GTEST_FLAG(output).c_str();
+
+ std::string format = GetOutputFormat();
+ if (format.empty())
+ format = std::string(kDefaultOutputFormat);
+
+ const char* const colon = strchr(gtest_output_flag, ':');
+ if (colon == NULL)
+ return internal::FilePath::MakeFileName(
+ internal::FilePath(
+ UnitTest::GetInstance()->original_working_dir()),
+ internal::FilePath(kDefaultOutputFile), 0,
+ format.c_str()).string();
+
+ internal::FilePath output_name(colon + 1);
+ if (!output_name.IsAbsolutePath())
+ // FIXME: on Windows \some\path is not an absolute
+ // path (as its meaning depends on the current drive), yet the
+ // following logic for turning it into an absolute path is wrong.
+ // Fix it.
+ output_name = internal::FilePath::ConcatPaths(
+ internal::FilePath(UnitTest::GetInstance()->original_working_dir()),
+ internal::FilePath(colon + 1));
+
+ if (!output_name.IsDirectory())
+ return output_name.string();
+
+ internal::FilePath result(internal::FilePath::GenerateUniqueFileName(
+ output_name, internal::GetCurrentExecutableName(),
+ GetOutputFormat().c_str()));
+ return result.string();
+}
+
+// Returns true iff the wildcard pattern matches the string. The
+// first ':' or '\0' character in pattern marks the end of it.
+//
+// This recursive algorithm isn't very efficient, but is clear and
+// works well enough for matching test names, which are short.
+bool UnitTestOptions::PatternMatchesString(const char *pattern,
+ const char *str) {
+ switch (*pattern) {
+ case '\0':
+ case ':': // Either ':' or '\0' marks the end of the pattern.
+ return *str == '\0';
+ case '?': // Matches any single character.
+ return *str != '\0' && PatternMatchesString(pattern + 1, str + 1);
+ case '*': // Matches any string (possibly empty) of characters.
+ return (*str != '\0' && PatternMatchesString(pattern, str + 1)) ||
+ PatternMatchesString(pattern + 1, str);
+ default: // Non-special character. Matches itself.
+ return *pattern == *str &&
+ PatternMatchesString(pattern + 1, str + 1);
+ }
+}
+
+bool UnitTestOptions::MatchesFilter(
+ const std::string& name, const char* filter) {
+ const char *cur_pattern = filter;
+ for (;;) {
+ if (PatternMatchesString(cur_pattern, name.c_str())) {
+ return true;
+ }
+
+ // Finds the next pattern in the filter.
+ cur_pattern = strchr(cur_pattern, ':');
+
+ // Returns if no more pattern can be found.
+ if (cur_pattern == NULL) {
+ return false;
+ }
+
+ // Skips the pattern separater (the ':' character).
+ cur_pattern++;
+ }
+}
+
+// Returns true iff the user-specified filter matches the test case
+// name and the test name.
+bool UnitTestOptions::FilterMatchesTest(const std::string &test_case_name,
+ const std::string &test_name) {
+ const std::string& full_name = test_case_name + "." + test_name.c_str();
+
+ // Split --gtest_filter at '-', if there is one, to separate into
+ // positive filter and negative filter portions
+ const char* const p = GTEST_FLAG(filter).c_str();
+ const char* const dash = strchr(p, '-');
+ std::string positive;
+ std::string negative;
+ if (dash == NULL) {
+ positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter
+ negative = "";
+ } else {
+ positive = std::string(p, dash); // Everything up to the dash
+ negative = std::string(dash + 1); // Everything after the dash
+ if (positive.empty()) {
+ // Treat '-test1' as the same as '*-test1'
+ positive = kUniversalFilter;
+ }
+ }
+
+ // A filter is a colon-separated list of patterns. It matches a
+ // test if any pattern in it matches the test.
+ return (MatchesFilter(full_name, positive.c_str()) &&
+ !MatchesFilter(full_name, negative.c_str()));
+}
+
+#if GTEST_HAS_SEH
+// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the
+// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise.
+// This function is useful as an __except condition.
+int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) {
+ // Google Test should handle a SEH exception if:
+ // 1. the user wants it to, AND
+ // 2. this is not a breakpoint exception, AND
+ // 3. this is not a C++ exception (VC++ implements them via SEH,
+ // apparently).
+ //
+ // SEH exception code for C++ exceptions.
+ // (see http://support.microsoft.com/kb/185294 for more information).
+ const DWORD kCxxExceptionCode = 0xe06d7363;
+
+ bool should_handle = true;
+
+ if (!GTEST_FLAG(catch_exceptions))
+ should_handle = false;
+ else if (exception_code == EXCEPTION_BREAKPOINT)
+ should_handle = false;
+ else if (exception_code == kCxxExceptionCode)
+ should_handle = false;
+
+ return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
+}
+#endif // GTEST_HAS_SEH
+
+} // namespace internal
+
+// The c'tor sets this object as the test part result reporter used by
+// Google Test. The 'result' parameter specifies where to report the
+// results. Intercepts only failures from the current thread.
+ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter(
+ TestPartResultArray* result)
+ : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD),
+ result_(result) {
+ Init();
+}
+
+// The c'tor sets this object as the test part result reporter used by
+// Google Test. The 'result' parameter specifies where to report the
+// results.
+ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter(
+ InterceptMode intercept_mode, TestPartResultArray* result)
+ : intercept_mode_(intercept_mode),
+ result_(result) {
+ Init();
+}
+
+void ScopedFakeTestPartResultReporter::Init() {
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ if (intercept_mode_ == INTERCEPT_ALL_THREADS) {
+ old_reporter_ = impl->GetGlobalTestPartResultReporter();
+ impl->SetGlobalTestPartResultReporter(this);
+ } else {
+ old_reporter_ = impl->GetTestPartResultReporterForCurrentThread();
+ impl->SetTestPartResultReporterForCurrentThread(this);
+ }
+}
+
+// The d'tor restores the test part result reporter used by Google Test
+// before.
+ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() {
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ if (intercept_mode_ == INTERCEPT_ALL_THREADS) {
+ impl->SetGlobalTestPartResultReporter(old_reporter_);
+ } else {
+ impl->SetTestPartResultReporterForCurrentThread(old_reporter_);
+ }
+}
+
+// Increments the test part result count and remembers the result.
+// This method is from the TestPartResultReporterInterface interface.
+void ScopedFakeTestPartResultReporter::ReportTestPartResult(
+ const TestPartResult& result) {
+ result_->Append(result);
+}
+
+namespace internal {
+
+// Returns the type ID of ::testing::Test. We should always call this
+// instead of GetTypeId< ::testing::Test>() to get the type ID of
+// testing::Test. This is to work around a suspected linker bug when
+// using Google Test as a framework on Mac OS X. The bug causes
+// GetTypeId< ::testing::Test>() to return different values depending
+// on whether the call is from the Google Test framework itself or
+// from user test code. GetTestTypeId() is guaranteed to always
+// return the same value, as it always calls GetTypeId<>() from the
+// gtest.cc, which is within the Google Test framework.
+TypeId GetTestTypeId() {
+ return GetTypeId<Test>();
+}
+
+// The value of GetTestTypeId() as seen from within the Google Test
+// library. This is solely for testing GetTestTypeId().
+extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId();
+
+// This predicate-formatter checks that 'results' contains a test part
+// failure of the given type and that the failure message contains the
+// given substring.
+static AssertionResult HasOneFailure(const char* /* results_expr */,
+ const char* /* type_expr */,
+ const char* /* substr_expr */,
+ const TestPartResultArray& results,
+ TestPartResult::Type type,
+ const std::string& substr) {
+ const std::string expected(type == TestPartResult::kFatalFailure ?
+ "1 fatal failure" :
+ "1 non-fatal failure");
+ Message msg;
+ if (results.size() != 1) {
+ msg << "Expected: " << expected << "\n"
+ << " Actual: " << results.size() << " failures";
+ for (int i = 0; i < results.size(); i++) {
+ msg << "\n" << results.GetTestPartResult(i);
+ }
+ return AssertionFailure() << msg;
+ }
+
+ const TestPartResult& r = results.GetTestPartResult(0);
+ if (r.type() != type) {
+ return AssertionFailure() << "Expected: " << expected << "\n"
+ << " Actual:\n"
+ << r;
+ }
+
+ if (strstr(r.message(), substr.c_str()) == NULL) {
+ return AssertionFailure() << "Expected: " << expected << " containing \""
+ << substr << "\"\n"
+ << " Actual:\n"
+ << r;
+ }
+
+ return AssertionSuccess();
+}
+
+// The constructor of SingleFailureChecker remembers where to look up
+// test part results, what type of failure we expect, and what
+// substring the failure message should contain.
+SingleFailureChecker::SingleFailureChecker(const TestPartResultArray* results,
+ TestPartResult::Type type,
+ const std::string& substr)
+ : results_(results), type_(type), substr_(substr) {}
+
+// The destructor of SingleFailureChecker verifies that the given
+// TestPartResultArray contains exactly one failure that has the given
+// type and contains the given substring. If that's not the case, a
+// non-fatal failure will be generated.
+SingleFailureChecker::~SingleFailureChecker() {
+ EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_);
+}
+
+DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter(
+ UnitTestImpl* unit_test) : unit_test_(unit_test) {}
+
+void DefaultGlobalTestPartResultReporter::ReportTestPartResult(
+ const TestPartResult& result) {
+ unit_test_->current_test_result()->AddTestPartResult(result);
+ unit_test_->listeners()->repeater()->OnTestPartResult(result);
+}
+
+DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter(
+ UnitTestImpl* unit_test) : unit_test_(unit_test) {}
+
+void DefaultPerThreadTestPartResultReporter::ReportTestPartResult(
+ const TestPartResult& result) {
+ unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result);
+}
+
+// Returns the global test part result reporter.
+TestPartResultReporterInterface*
+UnitTestImpl::GetGlobalTestPartResultReporter() {
+ internal::MutexLock lock(&global_test_part_result_reporter_mutex_);
+ return global_test_part_result_repoter_;
+}
+
+// Sets the global test part result reporter.
+void UnitTestImpl::SetGlobalTestPartResultReporter(
+ TestPartResultReporterInterface* reporter) {
+ internal::MutexLock lock(&global_test_part_result_reporter_mutex_);
+ global_test_part_result_repoter_ = reporter;
+}
+
+// Returns the test part result reporter for the current thread.
+TestPartResultReporterInterface*
+UnitTestImpl::GetTestPartResultReporterForCurrentThread() {
+ return per_thread_test_part_result_reporter_.get();
+}
+
+// Sets the test part result reporter for the current thread.
+void UnitTestImpl::SetTestPartResultReporterForCurrentThread(
+ TestPartResultReporterInterface* reporter) {
+ per_thread_test_part_result_reporter_.set(reporter);
+}
+
+// Gets the number of successful test cases.
+int UnitTestImpl::successful_test_case_count() const {
+ return CountIf(test_cases_, TestCasePassed);
+}
+
+// Gets the number of failed test cases.
+int UnitTestImpl::failed_test_case_count() const {
+ return CountIf(test_cases_, TestCaseFailed);
+}
+
+// Gets the number of all test cases.
+int UnitTestImpl::total_test_case_count() const {
+ return static_cast<int>(test_cases_.size());
+}
+
+// Gets the number of all test cases that contain at least one test
+// that should run.
+int UnitTestImpl::test_case_to_run_count() const {
+ return CountIf(test_cases_, ShouldRunTestCase);
+}
+
+// Gets the number of successful tests.
+int UnitTestImpl::successful_test_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count);
+}
+
+// Gets the number of failed tests.
+int UnitTestImpl::failed_test_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count);
+}
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int UnitTestImpl::reportable_disabled_test_count() const {
+ return SumOverTestCaseList(test_cases_,
+ &TestCase::reportable_disabled_test_count);
+}
+
+// Gets the number of disabled tests.
+int UnitTestImpl::disabled_test_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::disabled_test_count);
+}
+
+// Gets the number of tests to be printed in the XML report.
+int UnitTestImpl::reportable_test_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::reportable_test_count);
+}
+
+// Gets the number of all tests.
+int UnitTestImpl::total_test_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::total_test_count);
+}
+
+// Gets the number of tests that should run.
+int UnitTestImpl::test_to_run_count() const {
+ return SumOverTestCaseList(test_cases_, &TestCase::test_to_run_count);
+}
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag. The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// CurrentOsStackTraceExceptTop(1), Foo() will be included in the
+// trace but Bar() and CurrentOsStackTraceExceptTop() won't.
+std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) {
+ return os_stack_trace_getter()->CurrentStackTrace(
+ static_cast<int>(GTEST_FLAG(stack_trace_depth)),
+ skip_count + 1
+ // Skips the user-specified number of frames plus this function
+ // itself.
+ ); // NOLINT
+}
+
+// Returns the current time in milliseconds.
+TimeInMillis GetTimeInMillis() {
+#if GTEST_OS_WINDOWS_MOBILE || defined(__BORLANDC__)
+ // Difference between 1970-01-01 and 1601-01-01 in milliseconds.
+ // http://analogous.blogspot.com/2005/04/epoch.html
+ const TimeInMillis kJavaEpochToWinFileTimeDelta =
+ static_cast<TimeInMillis>(116444736UL) * 100000UL;
+ const DWORD kTenthMicrosInMilliSecond = 10000;
+
+ SYSTEMTIME now_systime;
+ FILETIME now_filetime;
+ ULARGE_INTEGER now_int64;
+ // FIXME: Shouldn't this just use
+ // GetSystemTimeAsFileTime()?
+ GetSystemTime(&now_systime);
+ if (SystemTimeToFileTime(&now_systime, &now_filetime)) {
+ now_int64.LowPart = now_filetime.dwLowDateTime;
+ now_int64.HighPart = now_filetime.dwHighDateTime;
+ now_int64.QuadPart = (now_int64.QuadPart / kTenthMicrosInMilliSecond) -
+ kJavaEpochToWinFileTimeDelta;
+ return now_int64.QuadPart;
+ }
+ return 0;
+#elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_
+ __timeb64 now;
+
+ // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996
+ // (deprecated function) there.
+ // FIXME: Use GetTickCount()? Or use
+ // SystemTimeToFileTime()
+ GTEST_DISABLE_MSC_DEPRECATED_PUSH_()
+ _ftime64(&now);
+ GTEST_DISABLE_MSC_DEPRECATED_POP_()
+
+ return static_cast<TimeInMillis>(now.time) * 1000 + now.millitm;
+#elif GTEST_HAS_GETTIMEOFDAY_
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return static_cast<TimeInMillis>(now.tv_sec) * 1000 + now.tv_usec / 1000;
+#else
+# error "Don't know how to get the current time on your system."
+#endif
+}
+
+// Utilities
+
+// class String.
+
+#if GTEST_OS_WINDOWS_MOBILE
+// Creates a UTF-16 wide string from the given ANSI string, allocating
+// memory using new. The caller is responsible for deleting the return
+// value using delete[]. Returns the wide string, or NULL if the
+// input is NULL.
+LPCWSTR String::AnsiToUtf16(const char* ansi) {
+ if (!ansi) return NULL;
+ const int length = strlen(ansi);
+ const int unicode_length =
+ MultiByteToWideChar(CP_ACP, 0, ansi, length,
+ NULL, 0);
+ WCHAR* unicode = new WCHAR[unicode_length + 1];
+ MultiByteToWideChar(CP_ACP, 0, ansi, length,
+ unicode, unicode_length);
+ unicode[unicode_length] = 0;
+ return unicode;
+}
+
+// Creates an ANSI string from the given wide string, allocating
+// memory using new. The caller is responsible for deleting the return
+// value using delete[]. Returns the ANSI string, or NULL if the
+// input is NULL.
+const char* String::Utf16ToAnsi(LPCWSTR utf16_str) {
+ if (!utf16_str) return NULL;
+ const int ansi_length =
+ WideCharToMultiByte(CP_ACP, 0, utf16_str, -1,
+ NULL, 0, NULL, NULL);
+ char* ansi = new char[ansi_length + 1];
+ WideCharToMultiByte(CP_ACP, 0, utf16_str, -1,
+ ansi, ansi_length, NULL, NULL);
+ ansi[ansi_length] = 0;
+ return ansi;
+}
+
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+// Compares two C strings. Returns true iff they have the same content.
+//
+// Unlike strcmp(), this function can handle NULL argument(s). A NULL
+// C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::CStringEquals(const char * lhs, const char * rhs) {
+ if ( lhs == NULL ) return rhs == NULL;
+
+ if ( rhs == NULL ) return false;
+
+ return strcmp(lhs, rhs) == 0;
+}
+
+#if GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING
+
+// Converts an array of wide chars to a narrow string using the UTF-8
+// encoding, and streams the result to the given Message object.
+static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length,
+ Message* msg) {
+ for (size_t i = 0; i != length; ) { // NOLINT
+ if (wstr[i] != L'\0') {
+ *msg << WideStringToUtf8(wstr + i, static_cast<int>(length - i));
+ while (i != length && wstr[i] != L'\0')
+ i++;
+ } else {
+ *msg << '\0';
+ i++;
+ }
+ }
+}
+
+#endif // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING
+
+void SplitString(const ::std::string& str, char delimiter,
+ ::std::vector< ::std::string>* dest) {
+ ::std::vector< ::std::string> parsed;
+ ::std::string::size_type pos = 0;
+ while (::testing::internal::AlwaysTrue()) {
+ const ::std::string::size_type colon = str.find(delimiter, pos);
+ if (colon == ::std::string::npos) {
+ parsed.push_back(str.substr(pos));
+ break;
+ } else {
+ parsed.push_back(str.substr(pos, colon - pos));
+ pos = colon + 1;
+ }
+ }
+ dest->swap(parsed);
+}
+
+} // namespace internal
+
+// Constructs an empty Message.
+// We allocate the stringstream separately because otherwise each use of
+// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's
+// stack frame leading to huge stack frames in some cases; gcc does not reuse
+// the stack space.
+Message::Message() : ss_(new ::std::stringstream) {
+ // By default, we want there to be enough precision when printing
+ // a double to a Message.
+ *ss_ << std::setprecision(std::numeric_limits<double>::digits10 + 2);
+}
+
+// These two overloads allow streaming a wide C string to a Message
+// using the UTF-8 encoding.
+Message& Message::operator <<(const wchar_t* wide_c_str) {
+ return *this << internal::String::ShowWideCString(wide_c_str);
+}
+Message& Message::operator <<(wchar_t* wide_c_str) {
+ return *this << internal::String::ShowWideCString(wide_c_str);
+}
+
+#if GTEST_HAS_STD_WSTRING
+// Converts the given wide string to a narrow string using the UTF-8
+// encoding, and streams the result to this Message object.
+Message& Message::operator <<(const ::std::wstring& wstr) {
+ internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this);
+ return *this;
+}
+#endif // GTEST_HAS_STD_WSTRING
+
+#if GTEST_HAS_GLOBAL_WSTRING
+// Converts the given wide string to a narrow string using the UTF-8
+// encoding, and streams the result to this Message object.
+Message& Message::operator <<(const ::wstring& wstr) {
+ internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this);
+ return *this;
+}
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+// Gets the text streamed to this object so far as an std::string.
+// Each '\0' character in the buffer is replaced with "\\0".
+std::string Message::GetString() const {
+ return internal::StringStreamToString(ss_.get());
+}
+
+// AssertionResult constructors.
+// Used in EXPECT_TRUE/FALSE(assertion_result).
+AssertionResult::AssertionResult(const AssertionResult& other)
+ : success_(other.success_),
+ message_(other.message_.get() != NULL ?
+ new ::std::string(*other.message_) :
+ static_cast< ::std::string*>(NULL)) {
+}
+
+// Swaps two AssertionResults.
+void AssertionResult::swap(AssertionResult& other) {
+ using std::swap;
+ swap(success_, other.success_);
+ swap(message_, other.message_);
+}
+
+// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
+AssertionResult AssertionResult::operator!() const {
+ AssertionResult negation(!success_);
+ if (message_.get() != NULL)
+ negation << *message_;
+ return negation;
+}
+
+// Makes a successful assertion result.
+AssertionResult AssertionSuccess() {
+ return AssertionResult(true);
+}
+
+// Makes a failed assertion result.
+AssertionResult AssertionFailure() {
+ return AssertionResult(false);
+}
+
+// Makes a failed assertion result with the given failure message.
+// Deprecated; use AssertionFailure() << message.
+AssertionResult AssertionFailure(const Message& message) {
+ return AssertionFailure() << message;
+}
+
+namespace internal {
+
+namespace edit_distance {
+std::vector<EditType> CalculateOptimalEdits(const std::vector<size_t>& left,
+ const std::vector<size_t>& right) {
+ std::vector<std::vector<double> > costs(
+ left.size() + 1, std::vector<double>(right.size() + 1));
+ std::vector<std::vector<EditType> > best_move(
+ left.size() + 1, std::vector<EditType>(right.size() + 1));
+
+ // Populate for empty right.
+ for (size_t l_i = 0; l_i < costs.size(); ++l_i) {
+ costs[l_i][0] = static_cast<double>(l_i);
+ best_move[l_i][0] = kRemove;
+ }
+ // Populate for empty left.
+ for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) {
+ costs[0][r_i] = static_cast<double>(r_i);
+ best_move[0][r_i] = kAdd;
+ }
+
+ for (size_t l_i = 0; l_i < left.size(); ++l_i) {
+ for (size_t r_i = 0; r_i < right.size(); ++r_i) {
+ if (left[l_i] == right[r_i]) {
+ // Found a match. Consume it.
+ costs[l_i + 1][r_i + 1] = costs[l_i][r_i];
+ best_move[l_i + 1][r_i + 1] = kMatch;
+ continue;
+ }
+
+ const double add = costs[l_i + 1][r_i];
+ const double remove = costs[l_i][r_i + 1];
+ const double replace = costs[l_i][r_i];
+ if (add < remove && add < replace) {
+ costs[l_i + 1][r_i + 1] = add + 1;
+ best_move[l_i + 1][r_i + 1] = kAdd;
+ } else if (remove < add && remove < replace) {
+ costs[l_i + 1][r_i + 1] = remove + 1;
+ best_move[l_i + 1][r_i + 1] = kRemove;
+ } else {
+ // We make replace a little more expensive than add/remove to lower
+ // their priority.
+ costs[l_i + 1][r_i + 1] = replace + 1.00001;
+ best_move[l_i + 1][r_i + 1] = kReplace;
+ }
+ }
+ }
+
+ // Reconstruct the best path. We do it in reverse order.
+ std::vector<EditType> best_path;
+ for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) {
+ EditType move = best_move[l_i][r_i];
+ best_path.push_back(move);
+ l_i -= move != kAdd;
+ r_i -= move != kRemove;
+ }
+ std::reverse(best_path.begin(), best_path.end());
+ return best_path;
+}
+
+namespace {
+
+// Helper class to convert string into ids with deduplication.
+class InternalStrings {
+ public:
+ size_t GetId(const std::string& str) {
+ IdMap::iterator it = ids_.find(str);
+ if (it != ids_.end()) return it->second;
+ size_t id = ids_.size();
+ return ids_[str] = id;
+ }
+
+ private:
+ typedef std::map<std::string, size_t> IdMap;
+ IdMap ids_;
+};
+
+} // namespace
+
+std::vector<EditType> CalculateOptimalEdits(
+ const std::vector<std::string>& left,
+ const std::vector<std::string>& right) {
+ std::vector<size_t> left_ids, right_ids;
+ {
+ InternalStrings intern_table;
+ for (size_t i = 0; i < left.size(); ++i) {
+ left_ids.push_back(intern_table.GetId(left[i]));
+ }
+ for (size_t i = 0; i < right.size(); ++i) {
+ right_ids.push_back(intern_table.GetId(right[i]));
+ }
+ }
+ return CalculateOptimalEdits(left_ids, right_ids);
+}
+
+namespace {
+
+// Helper class that holds the state for one hunk and prints it out to the
+// stream.
+// It reorders adds/removes when possible to group all removes before all
+// adds. It also adds the hunk header before printint into the stream.
+class Hunk {
+ public:
+ Hunk(size_t left_start, size_t right_start)
+ : left_start_(left_start),
+ right_start_(right_start),
+ adds_(),
+ removes_(),
+ common_() {}
+
+ void PushLine(char edit, const char* line) {
+ switch (edit) {
+ case ' ':
+ ++common_;
+ FlushEdits();
+ hunk_.push_back(std::make_pair(' ', line));
+ break;
+ case '-':
+ ++removes_;
+ hunk_removes_.push_back(std::make_pair('-', line));
+ break;
+ case '+':
+ ++adds_;
+ hunk_adds_.push_back(std::make_pair('+', line));
+ break;
+ }
+ }
+
+ void PrintTo(std::ostream* os) {
+ PrintHeader(os);
+ FlushEdits();
+ for (std::list<std::pair<char, const char*> >::const_iterator it =
+ hunk_.begin();
+ it != hunk_.end(); ++it) {
+ *os << it->first << it->second << "\n";
+ }
+ }
+
+ bool has_edits() const { return adds_ || removes_; }
+
+ private:
+ void FlushEdits() {
+ hunk_.splice(hunk_.end(), hunk_removes_);
+ hunk_.splice(hunk_.end(), hunk_adds_);
+ }
+
+ // Print a unified diff header for one hunk.
+ // The format is
+ // "@@ -<left_start>,<left_length> +<right_start>,<right_length> @@"
+ // where the left/right parts are omitted if unnecessary.
+ void PrintHeader(std::ostream* ss) const {
+ *ss << "@@ ";
+ if (removes_) {
+ *ss << "-" << left_start_ << "," << (removes_ + common_);
+ }
+ if (removes_ && adds_) {
+ *ss << " ";
+ }
+ if (adds_) {
+ *ss << "+" << right_start_ << "," << (adds_ + common_);
+ }
+ *ss << " @@\n";
+ }
+
+ size_t left_start_, right_start_;
+ size_t adds_, removes_, common_;
+ std::list<std::pair<char, const char*> > hunk_, hunk_adds_, hunk_removes_;
+};
+
+} // namespace
+
+// Create a list of diff hunks in Unified diff format.
+// Each hunk has a header generated by PrintHeader above plus a body with
+// lines prefixed with ' ' for no change, '-' for deletion and '+' for
+// addition.
+// 'context' represents the desired unchanged prefix/suffix around the diff.
+// If two hunks are close enough that their contexts overlap, then they are
+// joined into one hunk.
+std::string CreateUnifiedDiff(const std::vector<std::string>& left,
+ const std::vector<std::string>& right,
+ size_t context) {
+ const std::vector<EditType> edits = CalculateOptimalEdits(left, right);
+
+ size_t l_i = 0, r_i = 0, edit_i = 0;
+ std::stringstream ss;
+ while (edit_i < edits.size()) {
+ // Find first edit.
+ while (edit_i < edits.size() && edits[edit_i] == kMatch) {
+ ++l_i;
+ ++r_i;
+ ++edit_i;
+ }
+
+ // Find the first line to include in the hunk.
+ const size_t prefix_context = std::min(l_i, context);
+ Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1);
+ for (size_t i = prefix_context; i > 0; --i) {
+ hunk.PushLine(' ', left[l_i - i].c_str());
+ }
+
+ // Iterate the edits until we found enough suffix for the hunk or the input
+ // is over.
+ size_t n_suffix = 0;
+ for (; edit_i < edits.size(); ++edit_i) {
+ if (n_suffix >= context) {
+ // Continue only if the next hunk is very close.
+ std::vector<EditType>::const_iterator it = edits.begin() + edit_i;
+ while (it != edits.end() && *it == kMatch) ++it;
+ if (it == edits.end() || (it - edits.begin()) - edit_i >= context) {
+ // There is no next edit or it is too far away.
+ break;
+ }
+ }
+
+ EditType edit = edits[edit_i];
+ // Reset count when a non match is found.
+ n_suffix = edit == kMatch ? n_suffix + 1 : 0;
+
+ if (edit == kMatch || edit == kRemove || edit == kReplace) {
+ hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str());
+ }
+ if (edit == kAdd || edit == kReplace) {
+ hunk.PushLine('+', right[r_i].c_str());
+ }
+
+ // Advance indices, depending on edit type.
+ l_i += edit != kAdd;
+ r_i += edit != kRemove;
+ }
+
+ if (!hunk.has_edits()) {
+ // We are done. We don't want this hunk.
+ break;
+ }
+
+ hunk.PrintTo(&ss);
+ }
+ return ss.str();
+}
+
+} // namespace edit_distance
+
+namespace {
+
+// The string representation of the values received in EqFailure() are already
+// escaped. Split them on escaped '\n' boundaries. Leave all other escaped
+// characters the same.
+std::vector<std::string> SplitEscapedString(const std::string& str) {
+ std::vector<std::string> lines;
+ size_t start = 0, end = str.size();
+ if (end > 2 && str[0] == '"' && str[end - 1] == '"') {
+ ++start;
+ --end;
+ }
+ bool escaped = false;
+ for (size_t i = start; i + 1 < end; ++i) {
+ if (escaped) {
+ escaped = false;
+ if (str[i] == 'n') {
+ lines.push_back(str.substr(start, i - start - 1));
+ start = i + 1;
+ }
+ } else {
+ escaped = str[i] == '\\';
+ }
+ }
+ lines.push_back(str.substr(start, end - start));
+ return lines;
+}
+
+} // namespace
+
+// Constructs and returns the message for an equality assertion
+// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure.
+//
+// The first four parameters are the expressions used in the assertion
+// and their values, as strings. For example, for ASSERT_EQ(foo, bar)
+// where foo is 5 and bar is 6, we have:
+//
+// lhs_expression: "foo"
+// rhs_expression: "bar"
+// lhs_value: "5"
+// rhs_value: "6"
+//
+// The ignoring_case parameter is true iff the assertion is a
+// *_STRCASEEQ*. When it's true, the string "Ignoring case" will
+// be inserted into the message.
+AssertionResult EqFailure(const char* lhs_expression,
+ const char* rhs_expression,
+ const std::string& lhs_value,
+ const std::string& rhs_value,
+ bool ignoring_case) {
+ Message msg;
+ msg << "Expected equality of these values:";
+ msg << "\n " << lhs_expression;
+ if (lhs_value != lhs_expression) {
+ msg << "\n Which is: " << lhs_value;
+ }
+ msg << "\n " << rhs_expression;
+ if (rhs_value != rhs_expression) {
+ msg << "\n Which is: " << rhs_value;
+ }
+
+ if (ignoring_case) {
+ msg << "\nIgnoring case";
+ }
+
+ if (!lhs_value.empty() && !rhs_value.empty()) {
+ const std::vector<std::string> lhs_lines =
+ SplitEscapedString(lhs_value);
+ const std::vector<std::string> rhs_lines =
+ SplitEscapedString(rhs_value);
+ if (lhs_lines.size() > 1 || rhs_lines.size() > 1) {
+ msg << "\nWith diff:\n"
+ << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines);
+ }
+ }
+
+ return AssertionFailure() << msg;
+}
+
+// Constructs a failure message for Boolean assertions such as EXPECT_TRUE.
+std::string GetBoolAssertionFailureMessage(
+ const AssertionResult& assertion_result,
+ const char* expression_text,
+ const char* actual_predicate_value,
+ const char* expected_predicate_value) {
+ const char* actual_message = assertion_result.message();
+ Message msg;
+ msg << "Value of: " << expression_text
+ << "\n Actual: " << actual_predicate_value;
+ if (actual_message[0] != '\0')
+ msg << " (" << actual_message << ")";
+ msg << "\nExpected: " << expected_predicate_value;
+ return msg.GetString();
+}
+
+// Helper function for implementing ASSERT_NEAR.
+AssertionResult DoubleNearPredFormat(const char* expr1,
+ const char* expr2,
+ const char* abs_error_expr,
+ double val1,
+ double val2,
+ double abs_error) {
+ const double diff = fabs(val1 - val2);
+ if (diff <= abs_error) return AssertionSuccess();
+
+ // FIXME: do not print the value of an expression if it's
+ // already a literal.
+ return AssertionFailure()
+ << "The difference between " << expr1 << " and " << expr2
+ << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n"
+ << expr1 << " evaluates to " << val1 << ",\n"
+ << expr2 << " evaluates to " << val2 << ", and\n"
+ << abs_error_expr << " evaluates to " << abs_error << ".";
+}
+
+
+// Helper template for implementing FloatLE() and DoubleLE().
+template <typename RawType>
+AssertionResult FloatingPointLE(const char* expr1,
+ const char* expr2,
+ RawType val1,
+ RawType val2) {
+ // Returns success if val1 is less than val2,
+ if (val1 < val2) {
+ return AssertionSuccess();
+ }
+
+ // or if val1 is almost equal to val2.
+ const FloatingPoint<RawType> lhs(val1), rhs(val2);
+ if (lhs.AlmostEquals(rhs)) {
+ return AssertionSuccess();
+ }
+
+ // Note that the above two checks will both fail if either val1 or
+ // val2 is NaN, as the IEEE floating-point standard requires that
+ // any predicate involving a NaN must return false.
+
+ ::std::stringstream val1_ss;
+ val1_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+ << val1;
+
+ ::std::stringstream val2_ss;
+ val2_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+ << val2;
+
+ return AssertionFailure()
+ << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n"
+ << " Actual: " << StringStreamToString(&val1_ss) << " vs "
+ << StringStreamToString(&val2_ss);
+}
+
+} // namespace internal
+
+// Asserts that val1 is less than, or almost equal to, val2. Fails
+// otherwise. In particular, it fails if either val1 or val2 is NaN.
+AssertionResult FloatLE(const char* expr1, const char* expr2,
+ float val1, float val2) {
+ return internal::FloatingPointLE<float>(expr1, expr2, val1, val2);
+}
+
+// Asserts that val1 is less than, or almost equal to, val2. Fails
+// otherwise. In particular, it fails if either val1 or val2 is NaN.
+AssertionResult DoubleLE(const char* expr1, const char* expr2,
+ double val1, double val2) {
+ return internal::FloatingPointLE<double>(expr1, expr2, val1, val2);
+}
+
+namespace internal {
+
+// The helper function for {ASSERT|EXPECT}_EQ with int or enum
+// arguments.
+AssertionResult CmpHelperEQ(const char* lhs_expression,
+ const char* rhs_expression,
+ BiggestInt lhs,
+ BiggestInt rhs) {
+ if (lhs == rhs) {
+ return AssertionSuccess();
+ }
+
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ FormatForComparisonFailureMessage(lhs, rhs),
+ FormatForComparisonFailureMessage(rhs, lhs),
+ false);
+}
+
+// A macro for implementing the helper functions needed to implement
+// ASSERT_?? and EXPECT_?? with integer or enum arguments. It is here
+// just to avoid copy-and-paste of similar code.
+#define GTEST_IMPL_CMP_HELPER_(op_name, op)\
+AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \
+ BiggestInt val1, BiggestInt val2) {\
+ if (val1 op val2) {\
+ return AssertionSuccess();\
+ } else {\
+ return AssertionFailure() \
+ << "Expected: (" << expr1 << ") " #op " (" << expr2\
+ << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\
+ << " vs " << FormatForComparisonFailureMessage(val2, val1);\
+ }\
+}
+
+// Implements the helper function for {ASSERT|EXPECT}_NE with int or
+// enum arguments.
+GTEST_IMPL_CMP_HELPER_(NE, !=)
+// Implements the helper function for {ASSERT|EXPECT}_LE with int or
+// enum arguments.
+GTEST_IMPL_CMP_HELPER_(LE, <=)
+// Implements the helper function for {ASSERT|EXPECT}_LT with int or
+// enum arguments.
+GTEST_IMPL_CMP_HELPER_(LT, < )
+// Implements the helper function for {ASSERT|EXPECT}_GE with int or
+// enum arguments.
+GTEST_IMPL_CMP_HELPER_(GE, >=)
+// Implements the helper function for {ASSERT|EXPECT}_GT with int or
+// enum arguments.
+GTEST_IMPL_CMP_HELPER_(GT, > )
+
+#undef GTEST_IMPL_CMP_HELPER_
+
+// The helper function for {ASSERT|EXPECT}_STREQ.
+AssertionResult CmpHelperSTREQ(const char* lhs_expression,
+ const char* rhs_expression,
+ const char* lhs,
+ const char* rhs) {
+ if (String::CStringEquals(lhs, rhs)) {
+ return AssertionSuccess();
+ }
+
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ PrintToString(lhs),
+ PrintToString(rhs),
+ false);
+}
+
+// The helper function for {ASSERT|EXPECT}_STRCASEEQ.
+AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression,
+ const char* rhs_expression,
+ const char* lhs,
+ const char* rhs) {
+ if (String::CaseInsensitiveCStringEquals(lhs, rhs)) {
+ return AssertionSuccess();
+ }
+
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ PrintToString(lhs),
+ PrintToString(rhs),
+ true);
+}
+
+// The helper function for {ASSERT|EXPECT}_STRNE.
+AssertionResult CmpHelperSTRNE(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2) {
+ if (!String::CStringEquals(s1, s2)) {
+ return AssertionSuccess();
+ } else {
+ return AssertionFailure() << "Expected: (" << s1_expression << ") != ("
+ << s2_expression << "), actual: \""
+ << s1 << "\" vs \"" << s2 << "\"";
+ }
+}
+
+// The helper function for {ASSERT|EXPECT}_STRCASENE.
+AssertionResult CmpHelperSTRCASENE(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2) {
+ if (!String::CaseInsensitiveCStringEquals(s1, s2)) {
+ return AssertionSuccess();
+ } else {
+ return AssertionFailure()
+ << "Expected: (" << s1_expression << ") != ("
+ << s2_expression << ") (ignoring case), actual: \""
+ << s1 << "\" vs \"" << s2 << "\"";
+ }
+}
+
+} // namespace internal
+
+namespace {
+
+// Helper functions for implementing IsSubString() and IsNotSubstring().
+
+// This group of overloaded functions return true iff needle is a
+// substring of haystack. NULL is considered a substring of itself
+// only.
+
+bool IsSubstringPred(const char* needle, const char* haystack) {
+ if (needle == NULL || haystack == NULL)
+ return needle == haystack;
+
+ return strstr(haystack, needle) != NULL;
+}
+
+bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) {
+ if (needle == NULL || haystack == NULL)
+ return needle == haystack;
+
+ return wcsstr(haystack, needle) != NULL;
+}
+
+// StringType here can be either ::std::string or ::std::wstring.
+template <typename StringType>
+bool IsSubstringPred(const StringType& needle,
+ const StringType& haystack) {
+ return haystack.find(needle) != StringType::npos;
+}
+
+// This function implements either IsSubstring() or IsNotSubstring(),
+// depending on the value of the expected_to_be_substring parameter.
+// StringType here can be const char*, const wchar_t*, ::std::string,
+// or ::std::wstring.
+template <typename StringType>
+AssertionResult IsSubstringImpl(
+ bool expected_to_be_substring,
+ const char* needle_expr, const char* haystack_expr,
+ const StringType& needle, const StringType& haystack) {
+ if (IsSubstringPred(needle, haystack) == expected_to_be_substring)
+ return AssertionSuccess();
+
+ const bool is_wide_string = sizeof(needle[0]) > 1;
+ const char* const begin_string_quote = is_wide_string ? "L\"" : "\"";
+ return AssertionFailure()
+ << "Value of: " << needle_expr << "\n"
+ << " Actual: " << begin_string_quote << needle << "\"\n"
+ << "Expected: " << (expected_to_be_substring ? "" : "not ")
+ << "a substring of " << haystack_expr << "\n"
+ << "Which is: " << begin_string_quote << haystack << "\"";
+}
+
+} // namespace
+
+// IsSubstring() and IsNotSubstring() check whether needle is a
+// substring of haystack (NULL is considered a substring of itself
+// only), and return an appropriate error message when they fail.
+
+AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const char* needle, const char* haystack) {
+ return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const wchar_t* needle, const wchar_t* haystack) {
+ return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const char* needle, const char* haystack) {
+ return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const wchar_t* needle, const wchar_t* haystack) {
+ return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::string& needle, const ::std::string& haystack) {
+ return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::string& needle, const ::std::string& haystack) {
+ return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+#if GTEST_HAS_STD_WSTRING
+AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::wstring& needle, const ::std::wstring& haystack) {
+ return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::wstring& needle, const ::std::wstring& haystack) {
+ return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+#endif // GTEST_HAS_STD_WSTRING
+
+namespace internal {
+
+#if GTEST_OS_WINDOWS
+
+namespace {
+
+// Helper function for IsHRESULT{SuccessFailure} predicates
+AssertionResult HRESULTFailureHelper(const char* expr,
+ const char* expected,
+ long hr) { // NOLINT
+# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_TV_TITLE
+
+ // Windows CE doesn't support FormatMessage.
+ const char error_text[] = "";
+
+# else
+
+ // Looks up the human-readable system message for the HRESULT code
+ // and since we're not passing any params to FormatMessage, we don't
+ // want inserts expanded.
+ const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS;
+ const DWORD kBufSize = 4096;
+ // Gets the system's human readable message string for this HRESULT.
+ char error_text[kBufSize] = { '\0' };
+ DWORD message_length = ::FormatMessageA(kFlags,
+ 0, // no source, we're asking system
+ hr, // the error
+ 0, // no line width restrictions
+ error_text, // output buffer
+ kBufSize, // buf size
+ NULL); // no arguments for inserts
+ // Trims tailing white space (FormatMessage leaves a trailing CR-LF)
+ for (; message_length && IsSpace(error_text[message_length - 1]);
+ --message_length) {
+ error_text[message_length - 1] = '\0';
+ }
+
+# endif // GTEST_OS_WINDOWS_MOBILE
+
+ const std::string error_hex("0x" + String::FormatHexInt(hr));
+ return ::testing::AssertionFailure()
+ << "Expected: " << expr << " " << expected << ".\n"
+ << " Actual: " << error_hex << " " << error_text << "\n";
+}
+
+} // namespace
+
+AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT
+ if (SUCCEEDED(hr)) {
+ return AssertionSuccess();
+ }
+ return HRESULTFailureHelper(expr, "succeeds", hr);
+}
+
+AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT
+ if (FAILED(hr)) {
+ return AssertionSuccess();
+ }
+ return HRESULTFailureHelper(expr, "fails", hr);
+}
+
+#endif // GTEST_OS_WINDOWS
+
+// Utility functions for encoding Unicode text (wide strings) in
+// UTF-8.
+
+// A Unicode code-point can have up to 21 bits, and is encoded in UTF-8
+// like this:
+//
+// Code-point length Encoding
+// 0 - 7 bits 0xxxxxxx
+// 8 - 11 bits 110xxxxx 10xxxxxx
+// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx
+// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+
+// The maximum code-point a one-byte UTF-8 sequence can represent.
+const UInt32 kMaxCodePoint1 = (static_cast<UInt32>(1) << 7) - 1;
+
+// The maximum code-point a two-byte UTF-8 sequence can represent.
+const UInt32 kMaxCodePoint2 = (static_cast<UInt32>(1) << (5 + 6)) - 1;
+
+// The maximum code-point a three-byte UTF-8 sequence can represent.
+const UInt32 kMaxCodePoint3 = (static_cast<UInt32>(1) << (4 + 2*6)) - 1;
+
+// The maximum code-point a four-byte UTF-8 sequence can represent.
+const UInt32 kMaxCodePoint4 = (static_cast<UInt32>(1) << (3 + 3*6)) - 1;
+
+// Chops off the n lowest bits from a bit pattern. Returns the n
+// lowest bits. As a side effect, the original bit pattern will be
+// shifted to the right by n bits.
+inline UInt32 ChopLowBits(UInt32* bits, int n) {
+ const UInt32 low_bits = *bits & ((static_cast<UInt32>(1) << n) - 1);
+ *bits >>= n;
+ return low_bits;
+}
+
+// Converts a Unicode code point to a narrow string in UTF-8 encoding.
+// code_point parameter is of type UInt32 because wchar_t may not be
+// wide enough to contain a code point.
+// If the code_point is not a valid Unicode code point
+// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
+// to "(Invalid Unicode 0xXXXXXXXX)".
+std::string CodePointToUtf8(UInt32 code_point) {
+ if (code_point > kMaxCodePoint4) {
+ return "(Invalid Unicode 0x" + String::FormatHexInt(code_point) + ")";
+ }
+
+ char str[5]; // Big enough for the largest valid code point.
+ if (code_point <= kMaxCodePoint1) {
+ str[1] = '\0';
+ str[0] = static_cast<char>(code_point); // 0xxxxxxx
+ } else if (code_point <= kMaxCodePoint2) {
+ str[2] = '\0';
+ str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[0] = static_cast<char>(0xC0 | code_point); // 110xxxxx
+ } else if (code_point <= kMaxCodePoint3) {
+ str[3] = '\0';
+ str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[0] = static_cast<char>(0xE0 | code_point); // 1110xxxx
+ } else { // code_point <= kMaxCodePoint4
+ str[4] = '\0';
+ str[3] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
+ str[0] = static_cast<char>(0xF0 | code_point); // 11110xxx
+ }
+ return str;
+}
+
+// The following two functions only make sense if the system
+// uses UTF-16 for wide string encoding. All supported systems
+// with 16 bit wchar_t (Windows, Cygwin, Symbian OS) do use UTF-16.
+
+// Determines if the arguments constitute UTF-16 surrogate pair
+// and thus should be combined into a single Unicode code point
+// using CreateCodePointFromUtf16SurrogatePair.
+inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) {
+ return sizeof(wchar_t) == 2 &&
+ (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00;
+}
+
+// Creates a Unicode code point from UTF16 surrogate pair.
+inline UInt32 CreateCodePointFromUtf16SurrogatePair(wchar_t first,
+ wchar_t second) {
+ const UInt32 mask = (1 << 10) - 1;
+ return (sizeof(wchar_t) == 2) ?
+ (((first & mask) << 10) | (second & mask)) + 0x10000 :
+ // This function should not be called when the condition is
+ // false, but we provide a sensible default in case it is.
+ static_cast<UInt32>(first);
+}
+
+// Converts a wide string to a narrow string in UTF-8 encoding.
+// The wide string is assumed to have the following encoding:
+// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS)
+// UTF-32 if sizeof(wchar_t) == 4 (on Linux)
+// Parameter str points to a null-terminated wide string.
+// Parameter num_chars may additionally limit the number
+// of wchar_t characters processed. -1 is used when the entire string
+// should be processed.
+// If the string contains code points that are not valid Unicode code points
+// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output
+// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding
+// and contains invalid UTF-16 surrogate pairs, values in those pairs
+// will be encoded as individual Unicode characters from Basic Normal Plane.
+std::string WideStringToUtf8(const wchar_t* str, int num_chars) {
+ if (num_chars == -1)
+ num_chars = static_cast<int>(wcslen(str));
+
+ ::std::stringstream stream;
+ for (int i = 0; i < num_chars; ++i) {
+ UInt32 unicode_code_point;
+
+ if (str[i] == L'\0') {
+ break;
+ } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) {
+ unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i],
+ str[i + 1]);
+ i++;
+ } else {
+ unicode_code_point = static_cast<UInt32>(str[i]);
+ }
+
+ stream << CodePointToUtf8(unicode_code_point);
+ }
+ return StringStreamToString(&stream);
+}
+
+// Converts a wide C string to an std::string using the UTF-8 encoding.
+// NULL will be converted to "(null)".
+std::string String::ShowWideCString(const wchar_t * wide_c_str) {
+ if (wide_c_str == NULL) return "(null)";
+
+ return internal::WideStringToUtf8(wide_c_str, -1);
+}
+
+// Compares two wide C strings. Returns true iff they have the same
+// content.
+//
+// Unlike wcscmp(), this function can handle NULL argument(s). A NULL
+// C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) {
+ if (lhs == NULL) return rhs == NULL;
+
+ if (rhs == NULL) return false;
+
+ return wcscmp(lhs, rhs) == 0;
+}
+
+// Helper function for *_STREQ on wide strings.
+AssertionResult CmpHelperSTREQ(const char* lhs_expression,
+ const char* rhs_expression,
+ const wchar_t* lhs,
+ const wchar_t* rhs) {
+ if (String::WideCStringEquals(lhs, rhs)) {
+ return AssertionSuccess();
+ }
+
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ PrintToString(lhs),
+ PrintToString(rhs),
+ false);
+}
+
+// Helper function for *_STRNE on wide strings.
+AssertionResult CmpHelperSTRNE(const char* s1_expression,
+ const char* s2_expression,
+ const wchar_t* s1,
+ const wchar_t* s2) {
+ if (!String::WideCStringEquals(s1, s2)) {
+ return AssertionSuccess();
+ }
+
+ return AssertionFailure() << "Expected: (" << s1_expression << ") != ("
+ << s2_expression << "), actual: "
+ << PrintToString(s1)
+ << " vs " << PrintToString(s2);
+}
+
+// Compares two C strings, ignoring case. Returns true iff they have
+// the same content.
+//
+// Unlike strcasecmp(), this function can handle NULL argument(s). A
+// NULL C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) {
+ if (lhs == NULL)
+ return rhs == NULL;
+ if (rhs == NULL)
+ return false;
+ return posix::StrCaseCmp(lhs, rhs) == 0;
+}
+
+ // Compares two wide C strings, ignoring case. Returns true iff they
+ // have the same content.
+ //
+ // Unlike wcscasecmp(), this function can handle NULL argument(s).
+ // A NULL C string is considered different to any non-NULL wide C string,
+ // including the empty string.
+ // NB: The implementations on different platforms slightly differ.
+ // On windows, this method uses _wcsicmp which compares according to LC_CTYPE
+ // environment variable. On GNU platform this method uses wcscasecmp
+ // which compares according to LC_CTYPE category of the current locale.
+ // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the
+ // current locale.
+bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs,
+ const wchar_t* rhs) {
+ if (lhs == NULL) return rhs == NULL;
+
+ if (rhs == NULL) return false;
+
+#if GTEST_OS_WINDOWS
+ return _wcsicmp(lhs, rhs) == 0;
+#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID
+ return wcscasecmp(lhs, rhs) == 0;
+#else
+ // Android, Mac OS X and Cygwin don't define wcscasecmp.
+ // Other unknown OSes may not define it either.
+ wint_t left, right;
+ do {
+ left = towlower(*lhs++);
+ right = towlower(*rhs++);
+ } while (left && left == right);
+ return left == right;
+#endif // OS selector
+}
+
+// Returns true iff str ends with the given suffix, ignoring case.
+// Any string is considered to end with an empty suffix.
+bool String::EndsWithCaseInsensitive(
+ const std::string& str, const std::string& suffix) {
+ const size_t str_len = str.length();
+ const size_t suffix_len = suffix.length();
+ return (str_len >= suffix_len) &&
+ CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len,
+ suffix.c_str());
+}
+
+// Formats an int value as "%02d".
+std::string String::FormatIntWidth2(int value) {
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(2) << value;
+ return ss.str();
+}
+
+// Formats an int value as "%X".
+std::string String::FormatHexInt(int value) {
+ std::stringstream ss;
+ ss << std::hex << std::uppercase << value;
+ return ss.str();
+}
+
+// Formats a byte as "%02X".
+std::string String::FormatByte(unsigned char value) {
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase
+ << static_cast<unsigned int>(value);
+ return ss.str();
+}
+
+// Converts the buffer in a stringstream to an std::string, converting NUL
+// bytes to "\\0" along the way.
+std::string StringStreamToString(::std::stringstream* ss) {
+ const ::std::string& str = ss->str();
+ const char* const start = str.c_str();
+ const char* const end = start + str.length();
+
+ std::string result;
+ result.reserve(2 * (end - start));
+ for (const char* ch = start; ch != end; ++ch) {
+ if (*ch == '\0') {
+ result += "\\0"; // Replaces NUL with "\\0";
+ } else {
+ result += *ch;
+ }
+ }
+
+ return result;
+}
+
+// Appends the user-supplied message to the Google-Test-generated message.
+std::string AppendUserMessage(const std::string& gtest_msg,
+ const Message& user_msg) {
+ // Appends the user message if it's non-empty.
+ const std::string user_msg_string = user_msg.GetString();
+ if (user_msg_string.empty()) {
+ return gtest_msg;
+ }
+
+ return gtest_msg + "\n" + user_msg_string;
+}
+
+} // namespace internal
+
+// class TestResult
+
+// Creates an empty TestResult.
+TestResult::TestResult()
+ : death_test_count_(0),
+ elapsed_time_(0) {
+}
+
+// D'tor.
+TestResult::~TestResult() {
+}
+
+// Returns the i-th test part result among all the results. i can
+// range from 0 to total_part_count() - 1. If i is not in that range,
+// aborts the program.
+const TestPartResult& TestResult::GetTestPartResult(int i) const {
+ if (i < 0 || i >= total_part_count())
+ internal::posix::Abort();
+ return test_part_results_.at(i);
+}
+
+// Returns the i-th test property. i can range from 0 to
+// test_property_count() - 1. If i is not in that range, aborts the
+// program.
+const TestProperty& TestResult::GetTestProperty(int i) const {
+ if (i < 0 || i >= test_property_count())
+ internal::posix::Abort();
+ return test_properties_.at(i);
+}
+
+// Clears the test part results.
+void TestResult::ClearTestPartResults() {
+ test_part_results_.clear();
+}
+
+// Adds a test part result to the list.
+void TestResult::AddTestPartResult(const TestPartResult& test_part_result) {
+ test_part_results_.push_back(test_part_result);
+}
+
+// Adds a test property to the list. If a property with the same key as the
+// supplied property is already represented, the value of this test_property
+// replaces the old value for that key.
+void TestResult::RecordProperty(const std::string& xml_element,
+ const TestProperty& test_property) {
+ if (!ValidateTestProperty(xml_element, test_property)) {
+ return;
+ }
+ internal::MutexLock lock(&test_properites_mutex_);
+ const std::vector<TestProperty>::iterator property_with_matching_key =
+ std::find_if(test_properties_.begin(), test_properties_.end(),
+ internal::TestPropertyKeyIs(test_property.key()));
+ if (property_with_matching_key == test_properties_.end()) {
+ test_properties_.push_back(test_property);
+ return;
+ }
+ property_with_matching_key->SetValue(test_property.value());
+}
+
+// The list of reserved attributes used in the <testsuites> element of XML
+// output.
+static const char* const kReservedTestSuitesAttributes[] = {
+ "disabled",
+ "errors",
+ "failures",
+ "name",
+ "random_seed",
+ "tests",
+ "time",
+ "timestamp"
+};
+
+// The list of reserved attributes used in the <testsuite> element of XML
+// output.
+static const char* const kReservedTestSuiteAttributes[] = {
+ "disabled",
+ "errors",
+ "failures",
+ "name",
+ "tests",
+ "time"
+};
+
+// The list of reserved attributes used in the <testcase> element of XML output.
+static const char* const kReservedTestCaseAttributes[] = {
+ "classname", "name", "status", "time",
+ "type_param", "value_param", "file", "line"};
+
+template <int kSize>
+std::vector<std::string> ArrayAsVector(const char* const (&array)[kSize]) {
+ return std::vector<std::string>(array, array + kSize);
+}
+
+static std::vector<std::string> GetReservedAttributesForElement(
+ const std::string& xml_element) {
+ if (xml_element == "testsuites") {
+ return ArrayAsVector(kReservedTestSuitesAttributes);
+ } else if (xml_element == "testsuite") {
+ return ArrayAsVector(kReservedTestSuiteAttributes);
+ } else if (xml_element == "testcase") {
+ return ArrayAsVector(kReservedTestCaseAttributes);
+ } else {
+ GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element;
+ }
+ // This code is unreachable but some compilers may not realizes that.
+ return std::vector<std::string>();
+}
+
+static std::string FormatWordList(const std::vector<std::string>& words) {
+ Message word_list;
+ for (size_t i = 0; i < words.size(); ++i) {
+ if (i > 0 && words.size() > 2) {
+ word_list << ", ";
+ }
+ if (i == words.size() - 1) {
+ word_list << "and ";
+ }
+ word_list << "'" << words[i] << "'";
+ }
+ return word_list.GetString();
+}
+
+static bool ValidateTestPropertyName(
+ const std::string& property_name,
+ const std::vector<std::string>& reserved_names) {
+ if (std::find(reserved_names.begin(), reserved_names.end(), property_name) !=
+ reserved_names.end()) {
+ ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name
+ << " (" << FormatWordList(reserved_names)
+ << " are reserved by " << GTEST_NAME_ << ")";
+ return false;
+ }
+ return true;
+}
+
+// Adds a failure if the key is a reserved attribute of the element named
+// xml_element. Returns true if the property is valid.
+bool TestResult::ValidateTestProperty(const std::string& xml_element,
+ const TestProperty& test_property) {
+ return ValidateTestPropertyName(test_property.key(),
+ GetReservedAttributesForElement(xml_element));
+}
+
+// Clears the object.
+void TestResult::Clear() {
+ test_part_results_.clear();
+ test_properties_.clear();
+ death_test_count_ = 0;
+ elapsed_time_ = 0;
+}
+
+// Returns true iff the test failed.
+bool TestResult::Failed() const {
+ for (int i = 0; i < total_part_count(); ++i) {
+ if (GetTestPartResult(i).failed())
+ return true;
+ }
+ return false;
+}
+
+// Returns true iff the test part fatally failed.
+static bool TestPartFatallyFailed(const TestPartResult& result) {
+ return result.fatally_failed();
+}
+
+// Returns true iff the test fatally failed.
+bool TestResult::HasFatalFailure() const {
+ return CountIf(test_part_results_, TestPartFatallyFailed) > 0;
+}
+
+// Returns true iff the test part non-fatally failed.
+static bool TestPartNonfatallyFailed(const TestPartResult& result) {
+ return result.nonfatally_failed();
+}
+
+// Returns true iff the test has a non-fatal failure.
+bool TestResult::HasNonfatalFailure() const {
+ return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0;
+}
+
+// Gets the number of all test parts. This is the sum of the number
+// of successful test parts and the number of failed test parts.
+int TestResult::total_part_count() const {
+ return static_cast<int>(test_part_results_.size());
+}
+
+// Returns the number of the test properties.
+int TestResult::test_property_count() const {
+ return static_cast<int>(test_properties_.size());
+}
+
+// class Test
+
+// Creates a Test object.
+
+// The c'tor saves the states of all flags.
+Test::Test()
+ : gtest_flag_saver_(new GTEST_FLAG_SAVER_) {
+}
+
+// The d'tor restores the states of all flags. The actual work is
+// done by the d'tor of the gtest_flag_saver_ field, and thus not
+// visible here.
+Test::~Test() {
+}
+
+// Sets up the test fixture.
+//
+// A sub-class may override this.
+void Test::SetUp() {
+}
+
+// Tears down the test fixture.
+//
+// A sub-class may override this.
+void Test::TearDown() {
+}
+
+// Allows user supplied key value pairs to be recorded for later output.
+void Test::RecordProperty(const std::string& key, const std::string& value) {
+ UnitTest::GetInstance()->RecordProperty(key, value);
+}
+
+// Allows user supplied key value pairs to be recorded for later output.
+void Test::RecordProperty(const std::string& key, int value) {
+ Message value_message;
+ value_message << value;
+ RecordProperty(key, value_message.GetString().c_str());
+}
+
+namespace internal {
+
+void ReportFailureInUnknownLocation(TestPartResult::Type result_type,
+ const std::string& message) {
+ // This function is a friend of UnitTest and as such has access to
+ // AddTestPartResult.
+ UnitTest::GetInstance()->AddTestPartResult(
+ result_type,
+ NULL, // No info about the source file where the exception occurred.
+ -1, // We have no info on which line caused the exception.
+ message,
+ ""); // No stack trace, either.
+}
+
+} // namespace internal
+
+// Google Test requires all tests in the same test case to use the same test
+// fixture class. This function checks if the current test has the
+// same fixture class as the first test in the current test case. If
+// yes, it returns true; otherwise it generates a Google Test failure and
+// returns false.
+bool Test::HasSameFixtureClass() {
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ const TestCase* const test_case = impl->current_test_case();
+
+ // Info about the first test in the current test case.
+ const TestInfo* const first_test_info = test_case->test_info_list()[0];
+ const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_;
+ const char* const first_test_name = first_test_info->name();
+
+ // Info about the current test.
+ const TestInfo* const this_test_info = impl->current_test_info();
+ const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_;
+ const char* const this_test_name = this_test_info->name();
+
+ if (this_fixture_id != first_fixture_id) {
+ // Is the first test defined using TEST?
+ const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId();
+ // Is this test defined using TEST?
+ const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId();
+
+ if (first_is_TEST || this_is_TEST) {
+ // Both TEST and TEST_F appear in same test case, which is incorrect.
+ // Tell the user how to fix this.
+
+ // Gets the name of the TEST and the name of the TEST_F. Note
+ // that first_is_TEST and this_is_TEST cannot both be true, as
+ // the fixture IDs are different for the two tests.
+ const char* const TEST_name =
+ first_is_TEST ? first_test_name : this_test_name;
+ const char* const TEST_F_name =
+ first_is_TEST ? this_test_name : first_test_name;
+
+ ADD_FAILURE()
+ << "All tests in the same test case must use the same test fixture\n"
+ << "class, so mixing TEST_F and TEST in the same test case is\n"
+ << "illegal. In test case " << this_test_info->test_case_name()
+ << ",\n"
+ << "test " << TEST_F_name << " is defined using TEST_F but\n"
+ << "test " << TEST_name << " is defined using TEST. You probably\n"
+ << "want to change the TEST to TEST_F or move it to another test\n"
+ << "case.";
+ } else {
+ // Two fixture classes with the same name appear in two different
+ // namespaces, which is not allowed. Tell the user how to fix this.
+ ADD_FAILURE()
+ << "All tests in the same test case must use the same test fixture\n"
+ << "class. However, in test case "
+ << this_test_info->test_case_name() << ",\n"
+ << "you defined test " << first_test_name
+ << " and test " << this_test_name << "\n"
+ << "using two different test fixture classes. This can happen if\n"
+ << "the two classes are from different namespaces or translation\n"
+ << "units and have the same name. You should probably rename one\n"
+ << "of the classes to put the tests into different test cases.";
+ }
+ return false;
+ }
+
+ return true;
+}
+
+#if GTEST_HAS_SEH
+
+// Adds an "exception thrown" fatal failure to the current test. This
+// function returns its result via an output parameter pointer because VC++
+// prohibits creation of objects with destructors on stack in functions
+// using __try (see error C2712).
+static std::string* FormatSehExceptionMessage(DWORD exception_code,
+ const char* location) {
+ Message message;
+ message << "SEH exception with code 0x" << std::setbase(16) <<
+ exception_code << std::setbase(10) << " thrown in " << location << ".";
+
+ return new std::string(message.GetString());
+}
+
+#endif // GTEST_HAS_SEH
+
+namespace internal {
+
+#if GTEST_HAS_EXCEPTIONS
+
+// Adds an "exception thrown" fatal failure to the current test.
+static std::string FormatCxxExceptionMessage(const char* description,
+ const char* location) {
+ Message message;
+ if (description != NULL) {
+ message << "C++ exception with description \"" << description << "\"";
+ } else {
+ message << "Unknown C++ exception";
+ }
+ message << " thrown in " << location << ".";
+
+ return message.GetString();
+}
+
+static std::string PrintTestPartResultToString(
+ const TestPartResult& test_part_result);
+
+GoogleTestFailureException::GoogleTestFailureException(
+ const TestPartResult& failure)
+ : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {}
+
+#endif // GTEST_HAS_EXCEPTIONS
+
+// We put these helper functions in the internal namespace as IBM's xlC
+// compiler rejects the code if they were declared static.
+
+// Runs the given method and handles SEH exceptions it throws, when
+// SEH is supported; returns the 0-value for type Result in case of an
+// SEH exception. (Microsoft compilers cannot handle SEH and C++
+// exceptions in the same function. Therefore, we provide a separate
+// wrapper function for handling SEH exceptions.)
+template <class T, typename Result>
+Result HandleSehExceptionsInMethodIfSupported(
+ T* object, Result (T::*method)(), const char* location) {
+#if GTEST_HAS_SEH
+ __try {
+ return (object->*method)();
+ } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT
+ GetExceptionCode())) {
+ // We create the exception message on the heap because VC++ prohibits
+ // creation of objects with destructors on stack in functions using __try
+ // (see error C2712).
+ std::string* exception_message = FormatSehExceptionMessage(
+ GetExceptionCode(), location);
+ internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure,
+ *exception_message);
+ delete exception_message;
+ return static_cast<Result>(0);
+ }
+#else
+ (void)location;
+ return (object->*method)();
+#endif // GTEST_HAS_SEH
+}
+
+// Runs the given method and catches and reports C++ and/or SEH-style
+// exceptions, if they are supported; returns the 0-value for type
+// Result in case of an SEH exception.
+template <class T, typename Result>
+Result HandleExceptionsInMethodIfSupported(
+ T* object, Result (T::*method)(), const char* location) {
+ // NOTE: The user code can affect the way in which Google Test handles
+ // exceptions by setting GTEST_FLAG(catch_exceptions), but only before
+ // RUN_ALL_TESTS() starts. It is technically possible to check the flag
+ // after the exception is caught and either report or re-throw the
+ // exception based on the flag's value:
+ //
+ // try {
+ // // Perform the test method.
+ // } catch (...) {
+ // if (GTEST_FLAG(catch_exceptions))
+ // // Report the exception as failure.
+ // else
+ // throw; // Re-throws the original exception.
+ // }
+ //
+ // However, the purpose of this flag is to allow the program to drop into
+ // the debugger when the exception is thrown. On most platforms, once the
+ // control enters the catch block, the exception origin information is
+ // lost and the debugger will stop the program at the point of the
+ // re-throw in this function -- instead of at the point of the original
+ // throw statement in the code under test. For this reason, we perform
+ // the check early, sacrificing the ability to affect Google Test's
+ // exception handling in the method where the exception is thrown.
+ if (internal::GetUnitTestImpl()->catch_exceptions()) {
+#if GTEST_HAS_EXCEPTIONS
+ try {
+ return HandleSehExceptionsInMethodIfSupported(object, method, location);
+ } catch (const AssertionException&) { // NOLINT
+ // This failure was reported already.
+ } catch (const internal::GoogleTestFailureException&) { // NOLINT
+ // This exception type can only be thrown by a failed Google
+ // Test assertion with the intention of letting another testing
+ // framework catch it. Therefore we just re-throw it.
+ throw;
+ } catch (const std::exception& e) { // NOLINT
+ internal::ReportFailureInUnknownLocation(
+ TestPartResult::kFatalFailure,
+ FormatCxxExceptionMessage(e.what(), location));
+ } catch (...) { // NOLINT
+ internal::ReportFailureInUnknownLocation(
+ TestPartResult::kFatalFailure,
+ FormatCxxExceptionMessage(NULL, location));
+ }
+ return static_cast<Result>(0);
+#else
+ return HandleSehExceptionsInMethodIfSupported(object, method, location);
+#endif // GTEST_HAS_EXCEPTIONS
+ } else {
+ return (object->*method)();
+ }
+}
+
+} // namespace internal
+
+// Runs the test and updates the test result.
+void Test::Run() {
+ if (!HasSameFixtureClass()) return;
+
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()");
+ // We will run the test only if SetUp() was successful.
+ if (!HasFatalFailure()) {
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(
+ this, &Test::TestBody, "the test body");
+ }
+
+ // However, we want to clean up as much as possible. Hence we will
+ // always call TearDown(), even if SetUp() or the test body has
+ // failed.
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(
+ this, &Test::TearDown, "TearDown()");
+}
+
+// Returns true iff the current test has a fatal failure.
+bool Test::HasFatalFailure() {
+ return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure();
+}
+
+// Returns true iff the current test has a non-fatal failure.
+bool Test::HasNonfatalFailure() {
+ return internal::GetUnitTestImpl()->current_test_result()->
+ HasNonfatalFailure();
+}
+
+// class TestInfo
+
+// Constructs a TestInfo object. It assumes ownership of the test factory
+// object.
+TestInfo::TestInfo(const std::string& a_test_case_name,
+ const std::string& a_name,
+ const char* a_type_param,
+ const char* a_value_param,
+ internal::CodeLocation a_code_location,
+ internal::TypeId fixture_class_id,
+ internal::TestFactoryBase* factory)
+ : test_case_name_(a_test_case_name),
+ name_(a_name),
+ type_param_(a_type_param ? new std::string(a_type_param) : NULL),
+ value_param_(a_value_param ? new std::string(a_value_param) : NULL),
+ location_(a_code_location),
+ fixture_class_id_(fixture_class_id),
+ should_run_(false),
+ is_disabled_(false),
+ matches_filter_(false),
+ factory_(factory),
+ result_() {}
+
+// Destructs a TestInfo object.
+TestInfo::~TestInfo() { delete factory_; }
+
+namespace internal {
+
+// Creates a new TestInfo object and registers it with Google Test;
+// returns the created object.
+//
+// Arguments:
+//
+// test_case_name: name of the test case
+// name: name of the test
+// type_param: the name of the test's type parameter, or NULL if
+// this is not a typed or a type-parameterized test.
+// value_param: text representation of the test's value parameter,
+// or NULL if this is not a value-parameterized test.
+// code_location: code location where the test is defined
+// fixture_class_id: ID of the test fixture class
+// set_up_tc: pointer to the function that sets up the test case
+// tear_down_tc: pointer to the function that tears down the test case
+// factory: pointer to the factory that creates a test object.
+// The newly created TestInfo instance will assume
+// ownership of the factory object.
+TestInfo* MakeAndRegisterTestInfo(
+ const char* test_case_name,
+ const char* name,
+ const char* type_param,
+ const char* value_param,
+ CodeLocation code_location,
+ TypeId fixture_class_id,
+ SetUpTestCaseFunc set_up_tc,
+ TearDownTestCaseFunc tear_down_tc,
+ TestFactoryBase* factory) {
+ TestInfo* const test_info =
+ new TestInfo(test_case_name, name, type_param, value_param,
+ code_location, fixture_class_id, factory);
+ GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
+ return test_info;
+}
+
+void ReportInvalidTestCaseType(const char* test_case_name,
+ CodeLocation code_location) {
+ Message errors;
+ errors
+ << "Attempted redefinition of test case " << test_case_name << ".\n"
+ << "All tests in the same test case must use the same test fixture\n"
+ << "class. However, in test case " << test_case_name << ", you tried\n"
+ << "to define a test using a fixture class different from the one\n"
+ << "used earlier. This can happen if the two fixture classes are\n"
+ << "from different namespaces and have the same name. You should\n"
+ << "probably rename one of the classes to put the tests into different\n"
+ << "test cases.";
+
+ GTEST_LOG_(ERROR) << FormatFileLocation(code_location.file.c_str(),
+ code_location.line)
+ << " " << errors.GetString();
+}
+} // namespace internal
+
+namespace {
+
+// A predicate that checks the test name of a TestInfo against a known
+// value.
+//
+// This is used for implementation of the TestCase class only. We put
+// it in the anonymous namespace to prevent polluting the outer
+// namespace.
+//
+// TestNameIs is copyable.
+class TestNameIs {
+ public:
+ // Constructor.
+ //
+ // TestNameIs has NO default constructor.
+ explicit TestNameIs(const char* name)
+ : name_(name) {}
+
+ // Returns true iff the test name of test_info matches name_.
+ bool operator()(const TestInfo * test_info) const {
+ return test_info && test_info->name() == name_;
+ }
+
+ private:
+ std::string name_;
+};
+
+} // namespace
+
+namespace internal {
+
+// This method expands all parameterized tests registered with macros TEST_P
+// and INSTANTIATE_TEST_CASE_P into regular tests and registers those.
+// This will be done just once during the program runtime.
+void UnitTestImpl::RegisterParameterizedTests() {
+ if (!parameterized_tests_registered_) {
+ parameterized_test_registry_.RegisterTests();
+ parameterized_tests_registered_ = true;
+ }
+}
+
+} // namespace internal
+
+// Creates the test object, runs it, records its result, and then
+// deletes it.
+void TestInfo::Run() {
+ if (!should_run_) return;
+
+ // Tells UnitTest where to store test result.
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ impl->set_current_test_info(this);
+
+ TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+ // Notifies the unit test event listeners that a test is about to start.
+ repeater->OnTestStart(*this);
+
+ const TimeInMillis start = internal::GetTimeInMillis();
+
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+
+ // Creates the test object.
+ Test* const test = internal::HandleExceptionsInMethodIfSupported(
+ factory_, &internal::TestFactoryBase::CreateTest,
+ "the test fixture's constructor");
+
+ // Runs the test if the constructor didn't generate a fatal failure.
+ // Note that the object will not be null
+ if (!Test::HasFatalFailure()) {
+ // This doesn't throw as all user code that can throw are wrapped into
+ // exception handling code.
+ test->Run();
+ }
+
+ // Deletes the test object.
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(
+ test, &Test::DeleteSelf_, "the test fixture's destructor");
+
+ result_.set_elapsed_time(internal::GetTimeInMillis() - start);
+
+ // Notifies the unit test event listener that a test has just finished.
+ repeater->OnTestEnd(*this);
+
+ // Tells UnitTest to stop associating assertion results to this
+ // test.
+ impl->set_current_test_info(NULL);
+}
+
+// class TestCase
+
+// Gets the number of successful tests in this test case.
+int TestCase::successful_test_count() const {
+ return CountIf(test_info_list_, TestPassed);
+}
+
+// Gets the number of failed tests in this test case.
+int TestCase::failed_test_count() const {
+ return CountIf(test_info_list_, TestFailed);
+}
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int TestCase::reportable_disabled_test_count() const {
+ return CountIf(test_info_list_, TestReportableDisabled);
+}
+
+// Gets the number of disabled tests in this test case.
+int TestCase::disabled_test_count() const {
+ return CountIf(test_info_list_, TestDisabled);
+}
+
+// Gets the number of tests to be printed in the XML report.
+int TestCase::reportable_test_count() const {
+ return CountIf(test_info_list_, TestReportable);
+}
+
+// Get the number of tests in this test case that should run.
+int TestCase::test_to_run_count() const {
+ return CountIf(test_info_list_, ShouldRunTest);
+}
+
+// Gets the number of all tests.
+int TestCase::total_test_count() const {
+ return static_cast<int>(test_info_list_.size());
+}
+
+// Creates a TestCase with the given name.
+//
+// Arguments:
+//
+// name: name of the test case
+// a_type_param: the name of the test case's type parameter, or NULL if
+// this is not a typed or a type-parameterized test case.
+// set_up_tc: pointer to the function that sets up the test case
+// tear_down_tc: pointer to the function that tears down the test case
+TestCase::TestCase(const char* a_name, const char* a_type_param,
+ Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc)
+ : name_(a_name),
+ type_param_(a_type_param ? new std::string(a_type_param) : NULL),
+ set_up_tc_(set_up_tc),
+ tear_down_tc_(tear_down_tc),
+ should_run_(false),
+ elapsed_time_(0) {
+}
+
+// Destructor of TestCase.
+TestCase::~TestCase() {
+ // Deletes every Test in the collection.
+ ForEach(test_info_list_, internal::Delete<TestInfo>);
+}
+
+// Returns the i-th test among all the tests. i can range from 0 to
+// total_test_count() - 1. If i is not in that range, returns NULL.
+const TestInfo* TestCase::GetTestInfo(int i) const {
+ const int index = GetElementOr(test_indices_, i, -1);
+ return index < 0 ? NULL : test_info_list_[index];
+}
+
+// Returns the i-th test among all the tests. i can range from 0 to
+// total_test_count() - 1. If i is not in that range, returns NULL.
+TestInfo* TestCase::GetMutableTestInfo(int i) {
+ const int index = GetElementOr(test_indices_, i, -1);
+ return index < 0 ? NULL : test_info_list_[index];
+}
+
+// Adds a test to this test case. Will delete the test upon
+// destruction of the TestCase object.
+void TestCase::AddTestInfo(TestInfo * test_info) {
+ test_info_list_.push_back(test_info);
+ test_indices_.push_back(static_cast<int>(test_indices_.size()));
+}
+
+// Runs every test in this TestCase.
+void TestCase::Run() {
+ if (!should_run_) return;
+
+ internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+ impl->set_current_test_case(this);
+
+ TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+ repeater->OnTestCaseStart(*this);
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(
+ this, &TestCase::RunSetUpTestCase, "SetUpTestCase()");
+
+ const internal::TimeInMillis start = internal::GetTimeInMillis();
+ for (int i = 0; i < total_test_count(); i++) {
+ GetMutableTestInfo(i)->Run();
+ }
+ elapsed_time_ = internal::GetTimeInMillis() - start;
+
+ impl->os_stack_trace_getter()->UponLeavingGTest();
+ internal::HandleExceptionsInMethodIfSupported(
+ this, &TestCase::RunTearDownTestCase, "TearDownTestCase()");
+
+ repeater->OnTestCaseEnd(*this);
+ impl->set_current_test_case(NULL);
+}
+
+// Clears the results of all tests in this test case.
+void TestCase::ClearResult() {
+ ad_hoc_test_result_.Clear();
+ ForEach(test_info_list_, TestInfo::ClearTestResult);
+}
+
+// Shuffles the tests in this test case.
+void TestCase::ShuffleTests(internal::Random* random) {
+ Shuffle(random, &test_indices_);
+}
+
+// Restores the test order to before the first shuffle.
+void TestCase::UnshuffleTests() {
+ for (size_t i = 0; i < test_indices_.size(); i++) {
+ test_indices_[i] = static_cast<int>(i);
+ }
+}
+
+// Formats a countable noun. Depending on its quantity, either the
+// singular form or the plural form is used. e.g.
+//
+// FormatCountableNoun(1, "formula", "formuli") returns "1 formula".
+// FormatCountableNoun(5, "book", "books") returns "5 books".
+static std::string FormatCountableNoun(int count,
+ const char * singular_form,
+ const char * plural_form) {
+ return internal::StreamableToString(count) + " " +
+ (count == 1 ? singular_form : plural_form);
+}
+
+// Formats the count of tests.
+static std::string FormatTestCount(int test_count) {
+ return FormatCountableNoun(test_count, "test", "tests");
+}
+
+// Formats the count of test cases.
+static std::string FormatTestCaseCount(int test_case_count) {
+ return FormatCountableNoun(test_case_count, "test case", "test cases");
+}
+
+// Converts a TestPartResult::Type enum to human-friendly string
+// representation. Both kNonFatalFailure and kFatalFailure are translated
+// to "Failure", as the user usually doesn't care about the difference
+// between the two when viewing the test result.
+static const char * TestPartResultTypeToString(TestPartResult::Type type) {
+ switch (type) {
+ case TestPartResult::kSuccess:
+ return "Success";
+
+ case TestPartResult::kNonFatalFailure:
+ case TestPartResult::kFatalFailure:
+#ifdef _MSC_VER
+ return "error: ";
+#else
+ return "Failure\n";
+#endif
+ default:
+ return "Unknown result type";
+ }
+}
+
+namespace internal {
+
+// Prints a TestPartResult to an std::string.
+static std::string PrintTestPartResultToString(
+ const TestPartResult& test_part_result) {
+ return (Message()
+ << internal::FormatFileLocation(test_part_result.file_name(),
+ test_part_result.line_number())
+ << " " << TestPartResultTypeToString(test_part_result.type())
+ << test_part_result.message()).GetString();
+}
+
+// Prints a TestPartResult.
+static void PrintTestPartResult(const TestPartResult& test_part_result) {
+ const std::string& result =
+ PrintTestPartResultToString(test_part_result);
+ printf("%s\n", result.c_str());
+ fflush(stdout);
+ // If the test program runs in Visual Studio or a debugger, the
+ // following statements add the test part result message to the Output
+ // window such that the user can double-click on it to jump to the
+ // corresponding source code location; otherwise they do nothing.
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+ // We don't call OutputDebugString*() on Windows Mobile, as printing
+ // to stdout is done by OutputDebugString() there already - we don't
+ // want the same message printed twice.
+ ::OutputDebugStringA(result.c_str());
+ ::OutputDebugStringA("\n");
+#endif
+}
+
+// class PrettyUnitTestResultPrinter
+
+enum GTestColor {
+ COLOR_DEFAULT,
+ COLOR_RED,
+ COLOR_GREEN,
+ COLOR_YELLOW
+};
+
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \
+ !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW
+
+// Returns the character attribute for the given color.
+static WORD GetColorAttribute(GTestColor color) {
+ switch (color) {
+ case COLOR_RED: return FOREGROUND_RED;
+ case COLOR_GREEN: return FOREGROUND_GREEN;
+ case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN;
+ default: return 0;
+ }
+}
+
+static int GetBitOffset(WORD color_mask) {
+ if (color_mask == 0) return 0;
+
+ int bitOffset = 0;
+ while ((color_mask & 1) == 0) {
+ color_mask >>= 1;
+ ++bitOffset;
+ }
+ return bitOffset;
+}
+
+static WORD GetNewColor(GTestColor color, WORD old_color_attrs) {
+ // Let's reuse the BG
+ static const WORD background_mask = BACKGROUND_BLUE | BACKGROUND_GREEN |
+ BACKGROUND_RED | BACKGROUND_INTENSITY;
+ static const WORD foreground_mask = FOREGROUND_BLUE | FOREGROUND_GREEN |
+ FOREGROUND_RED | FOREGROUND_INTENSITY;
+ const WORD existing_bg = old_color_attrs & background_mask;
+
+ WORD new_color =
+ GetColorAttribute(color) | existing_bg | FOREGROUND_INTENSITY;
+ static const int bg_bitOffset = GetBitOffset(background_mask);
+ static const int fg_bitOffset = GetBitOffset(foreground_mask);
+
+ if (((new_color & background_mask) >> bg_bitOffset) ==
+ ((new_color & foreground_mask) >> fg_bitOffset)) {
+ new_color ^= FOREGROUND_INTENSITY; // invert intensity
+ }
+ return new_color;
+}
+
+#else
+
+// Returns the ANSI color code for the given color. COLOR_DEFAULT is
+// an invalid input.
+static const char* GetAnsiColorCode(GTestColor color) {
+ switch (color) {
+ case COLOR_RED: return "1";
+ case COLOR_GREEN: return "2";
+ case COLOR_YELLOW: return "3";
+ default: return NULL;
+ };
+}
+
+#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+
+// Returns true iff Google Test should use colors in the output.
+bool ShouldUseColor(bool stdout_is_tty) {
+ const char* const gtest_color = GTEST_FLAG(color).c_str();
+
+ if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) {
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW
+ // On Windows the TERM variable is usually not set, but the
+ // console there does support colors.
+ return stdout_is_tty;
+#else
+ // On non-Windows platforms, we rely on the TERM variable.
+ const char* const term = posix::GetEnv("TERM");
+ const bool term_supports_color =
+ String::CStringEquals(term, "xterm") ||
+ String::CStringEquals(term, "xterm-color") ||
+ String::CStringEquals(term, "xterm-256color") ||
+ String::CStringEquals(term, "screen") ||
+ String::CStringEquals(term, "screen-256color") ||
+ String::CStringEquals(term, "tmux") ||
+ String::CStringEquals(term, "tmux-256color") ||
+ String::CStringEquals(term, "rxvt-unicode") ||
+ String::CStringEquals(term, "rxvt-unicode-256color") ||
+ String::CStringEquals(term, "linux") ||
+ String::CStringEquals(term, "cygwin");
+ return stdout_is_tty && term_supports_color;
+#endif // GTEST_OS_WINDOWS
+ }
+
+ return String::CaseInsensitiveCStringEquals(gtest_color, "yes") ||
+ String::CaseInsensitiveCStringEquals(gtest_color, "true") ||
+ String::CaseInsensitiveCStringEquals(gtest_color, "t") ||
+ String::CStringEquals(gtest_color, "1");
+ // We take "yes", "true", "t", and "1" as meaning "yes". If the
+ // value is neither one of these nor "auto", we treat it as "no" to
+ // be conservative.
+}
+
+// Helpers for printing colored strings to stdout. Note that on Windows, we
+// cannot simply emit special characters and have the terminal change colors.
+// This routine must actually emit the characters rather than return a string
+// that would be colored when printed, as can be done on Linux.
+static void ColoredPrintf(GTestColor color, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS || \
+ GTEST_OS_IOS || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT
+ const bool use_color = AlwaysFalse();
+#else
+ static const bool in_color_mode =
+ ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0);
+ const bool use_color = in_color_mode && (color != COLOR_DEFAULT);
+#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS
+ // The '!= 0' comparison is necessary to satisfy MSVC 7.1.
+
+ if (!use_color) {
+ vprintf(fmt, args);
+ va_end(args);
+ return;
+ }
+
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \
+ !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW
+ const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ // Gets the current text color.
+ CONSOLE_SCREEN_BUFFER_INFO buffer_info;
+ GetConsoleScreenBufferInfo(stdout_handle, &buffer_info);
+ const WORD old_color_attrs = buffer_info.wAttributes;
+ const WORD new_color = GetNewColor(color, old_color_attrs);
+
+ // We need to flush the stream buffers into the console before each
+ // SetConsoleTextAttribute call lest it affect the text that is already
+ // printed but has not yet reached the console.
+ fflush(stdout);
+ SetConsoleTextAttribute(stdout_handle, new_color);
+
+ vprintf(fmt, args);
+
+ fflush(stdout);
+ // Restores the text color.
+ SetConsoleTextAttribute(stdout_handle, old_color_attrs);
+#else
+ printf("\033[0;3%sm", GetAnsiColorCode(color));
+ vprintf(fmt, args);
+ printf("\033[m"); // Resets the terminal to default.
+#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+ va_end(args);
+}
+
+// Text printed in Google Test's text output and --gtest_list_tests
+// output to label the type parameter and value parameter for a test.
+static const char kTypeParamLabel[] = "TypeParam";
+static const char kValueParamLabel[] = "GetParam()";
+
+static void PrintFullTestCommentIfPresent(const TestInfo& test_info) {
+ const char* const type_param = test_info.type_param();
+ const char* const value_param = test_info.value_param();
+
+ if (type_param != NULL || value_param != NULL) {
+ printf(", where ");
+ if (type_param != NULL) {
+ printf("%s = %s", kTypeParamLabel, type_param);
+ if (value_param != NULL)
+ printf(" and ");
+ }
+ if (value_param != NULL) {
+ printf("%s = %s", kValueParamLabel, value_param);
+ }
+ }
+}
+
+// This class implements the TestEventListener interface.
+//
+// Class PrettyUnitTestResultPrinter is copyable.
+class PrettyUnitTestResultPrinter : public TestEventListener {
+ public:
+ PrettyUnitTestResultPrinter() {}
+ static void PrintTestName(const char * test_case, const char * test) {
+ printf("%s.%s", test_case, test);
+ }
+
+ // The following methods override what's in the TestEventListener class.
+ virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
+ virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
+ virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestCaseStart(const TestCase& test_case);
+ virtual void OnTestStart(const TestInfo& test_info);
+ virtual void OnTestPartResult(const TestPartResult& result);
+ virtual void OnTestEnd(const TestInfo& test_info);
+ virtual void OnTestCaseEnd(const TestCase& test_case);
+ virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
+ virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
+ virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {}
+
+ private:
+ static void PrintFailedTests(const UnitTest& unit_test);
+};
+
+ // Fired before each iteration of tests starts.
+void PrettyUnitTestResultPrinter::OnTestIterationStart(
+ const UnitTest& unit_test, int iteration) {
+ if (GTEST_FLAG(repeat) != 1)
+ printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1);
+
+ const char* const filter = GTEST_FLAG(filter).c_str();
+
+ // Prints the filter if it's not *. This reminds the user that some
+ // tests may be skipped.
+ if (!String::CStringEquals(filter, kUniversalFilter)) {
+ ColoredPrintf(COLOR_YELLOW,
+ "Note: %s filter = %s\n", GTEST_NAME_, filter);
+ }
+
+ if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) {
+ const Int32 shard_index = Int32FromEnvOrDie(kTestShardIndex, -1);
+ ColoredPrintf(COLOR_YELLOW,
+ "Note: This is test shard %d of %s.\n",
+ static_cast<int>(shard_index) + 1,
+ internal::posix::GetEnv(kTestTotalShards));
+ }
+
+ if (GTEST_FLAG(shuffle)) {
+ ColoredPrintf(COLOR_YELLOW,
+ "Note: Randomizing tests' orders with a seed of %d .\n",
+ unit_test.random_seed());
+ }
+
+ ColoredPrintf(COLOR_GREEN, "[==========] ");
+ printf("Running %s from %s.\n",
+ FormatTestCount(unit_test.test_to_run_count()).c_str(),
+ FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str());
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart(
+ const UnitTest& /*unit_test*/) {
+ ColoredPrintf(COLOR_GREEN, "[----------] ");
+ printf("Global test environment set-up.\n");
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) {
+ const std::string counts =
+ FormatCountableNoun(test_case.test_to_run_count(), "test", "tests");
+ ColoredPrintf(COLOR_GREEN, "[----------] ");
+ printf("%s from %s", counts.c_str(), test_case.name());
+ if (test_case.type_param() == NULL) {
+ printf("\n");
+ } else {
+ printf(", where %s = %s\n", kTypeParamLabel, test_case.type_param());
+ }
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) {
+ ColoredPrintf(COLOR_GREEN, "[ RUN ] ");
+ PrintTestName(test_info.test_case_name(), test_info.name());
+ printf("\n");
+ fflush(stdout);
+}
+
+// Called after an assertion failure.
+void PrettyUnitTestResultPrinter::OnTestPartResult(
+ const TestPartResult& result) {
+ // If the test part succeeded, we don't need to do anything.
+ if (result.type() == TestPartResult::kSuccess)
+ return;
+
+ // Print failure message from the assertion (e.g. expected this and got that).
+ PrintTestPartResult(result);
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) {
+ if (test_info.result()->Passed()) {
+ ColoredPrintf(COLOR_GREEN, "[ OK ] ");
+ } else {
+ ColoredPrintf(COLOR_RED, "[ FAILED ] ");
+ }
+ PrintTestName(test_info.test_case_name(), test_info.name());
+ if (test_info.result()->Failed())
+ PrintFullTestCommentIfPresent(test_info);
+
+ if (GTEST_FLAG(print_time)) {
+ printf(" (%s ms)\n", internal::StreamableToString(
+ test_info.result()->elapsed_time()).c_str());
+ } else {
+ printf("\n");
+ }
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) {
+ if (!GTEST_FLAG(print_time)) return;
+
+ const std::string counts =
+ FormatCountableNoun(test_case.test_to_run_count(), "test", "tests");
+ ColoredPrintf(COLOR_GREEN, "[----------] ");
+ printf("%s from %s (%s ms total)\n\n",
+ counts.c_str(), test_case.name(),
+ internal::StreamableToString(test_case.elapsed_time()).c_str());
+ fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart(
+ const UnitTest& /*unit_test*/) {
+ ColoredPrintf(COLOR_GREEN, "[----------] ");
+ printf("Global test environment tear-down\n");
+ fflush(stdout);
+}
+
+// Internal helper for printing the list of failed tests.
+void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) {
+ const int failed_test_count = unit_test.failed_test_count();
+ if (failed_test_count == 0) {
+ return;
+ }
+
+ for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
+ const TestCase& test_case = *unit_test.GetTestCase(i);
+ if (!test_case.should_run() || (test_case.failed_test_count() == 0)) {
+ continue;
+ }
+ for (int j = 0; j < test_case.total_test_count(); ++j) {
+ const TestInfo& test_info = *test_case.GetTestInfo(j);
+ if (!test_info.should_run() || test_info.result()->Passed()) {
+ continue;
+ }
+ ColoredPrintf(COLOR_RED, "[ FAILED ] ");
+ printf("%s.%s", test_case.name(), test_info.name());
+ PrintFullTestCommentIfPresent(test_info);
+ printf("\n");
+ }
+ }
+}
+
+void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+ int /*iteration*/) {
+ ColoredPrintf(COLOR_GREEN, "[==========] ");
+ printf("%s from %s ran.",
+ FormatTestCount(unit_test.test_to_run_count()).c_str(),
+ FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str());
+ if (GTEST_FLAG(print_time)) {
+ printf(" (%s ms total)",
+ internal::StreamableToString(unit_test.elapsed_time()).c_str());
+ }
+ printf("\n");
+ ColoredPrintf(COLOR_GREEN, "[ PASSED ] ");
+ printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str());
+
+ int num_failures = unit_test.failed_test_count();
+ if (!unit_test.Passed()) {
+ const int failed_test_count = unit_test.failed_test_count();
+ ColoredPrintf(COLOR_RED, "[ FAILED ] ");
+ printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str());
+ PrintFailedTests(unit_test);
+ printf("\n%2d FAILED %s\n", num_failures,
+ num_failures == 1 ? "TEST" : "TESTS");
+ }
+
+ int num_disabled = unit_test.reportable_disabled_test_count();
+ if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) {
+ if (!num_failures) {
+ printf("\n"); // Add a spacer if no FAILURE banner is displayed.
+ }
+ ColoredPrintf(COLOR_YELLOW,
+ " YOU HAVE %d DISABLED %s\n\n",
+ num_disabled,
+ num_disabled == 1 ? "TEST" : "TESTS");
+ }
+ // Ensure that Google Test output is printed before, e.g., heapchecker output.
+ fflush(stdout);
+}
+
+// End PrettyUnitTestResultPrinter
+
+// class TestEventRepeater
+//
+// This class forwards events to other event listeners.
+class TestEventRepeater : public TestEventListener {
+ public:
+ TestEventRepeater() : forwarding_enabled_(true) {}
+ virtual ~TestEventRepeater();
+ void Append(TestEventListener *listener);
+ TestEventListener* Release(TestEventListener* listener);
+
+ // Controls whether events will be forwarded to listeners_. Set to false
+ // in death test child processes.
+ bool forwarding_enabled() const { return forwarding_enabled_; }
+ void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; }
+
+ virtual void OnTestProgramStart(const UnitTest& unit_test);
+ virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
+ virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
+ virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);
+ virtual void OnTestCaseStart(const TestCase& test_case);
+ virtual void OnTestStart(const TestInfo& test_info);
+ virtual void OnTestPartResult(const TestPartResult& result);
+ virtual void OnTestEnd(const TestInfo& test_info);
+ virtual void OnTestCaseEnd(const TestCase& test_case);
+ virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
+ virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);
+ virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
+ virtual void OnTestProgramEnd(const UnitTest& unit_test);
+
+ private:
+ // Controls whether events will be forwarded to listeners_. Set to false
+ // in death test child processes.
+ bool forwarding_enabled_;
+ // The list of listeners that receive events.
+ std::vector<TestEventListener*> listeners_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater);
+};
+
+TestEventRepeater::~TestEventRepeater() {
+ ForEach(listeners_, Delete<TestEventListener>);
+}
+
+void TestEventRepeater::Append(TestEventListener *listener) {
+ listeners_.push_back(listener);
+}
+
+// FIXME: Factor the search functionality into Vector::Find.
+TestEventListener* TestEventRepeater::Release(TestEventListener *listener) {
+ for (size_t i = 0; i < listeners_.size(); ++i) {
+ if (listeners_[i] == listener) {
+ listeners_.erase(listeners_.begin() + i);
+ return listener;
+ }
+ }
+
+ return NULL;
+}
+
+// Since most methods are very similar, use macros to reduce boilerplate.
+// This defines a member that forwards the call to all listeners.
+#define GTEST_REPEATER_METHOD_(Name, Type) \
+void TestEventRepeater::Name(const Type& parameter) { \
+ if (forwarding_enabled_) { \
+ for (size_t i = 0; i < listeners_.size(); i++) { \
+ listeners_[i]->Name(parameter); \
+ } \
+ } \
+}
+// This defines a member that forwards the call to all listeners in reverse
+// order.
+#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \
+void TestEventRepeater::Name(const Type& parameter) { \
+ if (forwarding_enabled_) { \
+ for (int i = static_cast<int>(listeners_.size()) - 1; i >= 0; i--) { \
+ listeners_[i]->Name(parameter); \
+ } \
+ } \
+}
+
+GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest)
+GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest)
+GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase)
+GTEST_REPEATER_METHOD_(OnTestStart, TestInfo)
+GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult)
+GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo)
+GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase)
+GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest)
+
+#undef GTEST_REPEATER_METHOD_
+#undef GTEST_REVERSE_REPEATER_METHOD_
+
+void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test,
+ int iteration) {
+ if (forwarding_enabled_) {
+ for (size_t i = 0; i < listeners_.size(); i++) {
+ listeners_[i]->OnTestIterationStart(unit_test, iteration);
+ }
+ }
+}
+
+void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test,
+ int iteration) {
+ if (forwarding_enabled_) {
+ for (int i = static_cast<int>(listeners_.size()) - 1; i >= 0; i--) {
+ listeners_[i]->OnTestIterationEnd(unit_test, iteration);
+ }
+ }
+}
+
+// End TestEventRepeater
+
+// This class generates an XML output file.
+class XmlUnitTestResultPrinter : public EmptyTestEventListener {
+ public:
+ explicit XmlUnitTestResultPrinter(const char* output_file);
+
+ virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
+ void ListTestsMatchingFilter(const std::vector<TestCase*>& test_cases);
+
+ // Prints an XML summary of all unit tests.
+ static void PrintXmlTestsList(std::ostream* stream,
+ const std::vector<TestCase*>& test_cases);
+
+ private:
+ // Is c a whitespace character that is normalized to a space character
+ // when it appears in an XML attribute value?
+ static bool IsNormalizableWhitespace(char c) {
+ return c == 0x9 || c == 0xA || c == 0xD;
+ }
+
+ // May c appear in a well-formed XML document?
+ static bool IsValidXmlCharacter(char c) {
+ return IsNormalizableWhitespace(c) || c >= 0x20;
+ }
+
+ // Returns an XML-escaped copy of the input string str. If
+ // is_attribute is true, the text is meant to appear as an attribute
+ // value, and normalizable whitespace is preserved by replacing it
+ // with character references.
+ static std::string EscapeXml(const std::string& str, bool is_attribute);
+
+ // Returns the given string with all characters invalid in XML removed.
+ static std::string RemoveInvalidXmlCharacters(const std::string& str);
+
+ // Convenience wrapper around EscapeXml when str is an attribute value.
+ static std::string EscapeXmlAttribute(const std::string& str) {
+ return EscapeXml(str, true);
+ }
+
+ // Convenience wrapper around EscapeXml when str is not an attribute value.
+ static std::string EscapeXmlText(const char* str) {
+ return EscapeXml(str, false);
+ }
+
+ // Verifies that the given attribute belongs to the given element and
+ // streams the attribute as XML.
+ static void OutputXmlAttribute(std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ const std::string& value);
+
+ // Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
+ static void OutputXmlCDataSection(::std::ostream* stream, const char* data);
+
+ // Streams an XML representation of a TestInfo object.
+ static void OutputXmlTestInfo(::std::ostream* stream,
+ const char* test_case_name,
+ const TestInfo& test_info);
+
+ // Prints an XML representation of a TestCase object
+ static void PrintXmlTestCase(::std::ostream* stream,
+ const TestCase& test_case);
+
+ // Prints an XML summary of unit_test to output stream out.
+ static void PrintXmlUnitTest(::std::ostream* stream,
+ const UnitTest& unit_test);
+
+ // Produces a string representing the test properties in a result as space
+ // delimited XML attributes based on the property key="value" pairs.
+ // When the std::string is not empty, it includes a space at the beginning,
+ // to delimit this attribute from prior attributes.
+ static std::string TestPropertiesAsXmlAttributes(const TestResult& result);
+
+ // Streams an XML representation of the test properties of a TestResult
+ // object.
+ static void OutputXmlTestProperties(std::ostream* stream,
+ const TestResult& result);
+
+ // The output file.
+ const std::string output_file_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter);
+};
+
+// Creates a new XmlUnitTestResultPrinter.
+XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file)
+ : output_file_(output_file) {
+ if (output_file_.empty()) {
+ GTEST_LOG_(FATAL) << "XML output file may not be null";
+ }
+}
+
+// Called after the unit test ends.
+void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+ int /*iteration*/) {
+ FILE* xmlout = OpenFileForWriting(output_file_);
+ std::stringstream stream;
+ PrintXmlUnitTest(&stream, unit_test);
+ fprintf(xmlout, "%s", StringStreamToString(&stream).c_str());
+ fclose(xmlout);
+}
+
+void XmlUnitTestResultPrinter::ListTestsMatchingFilter(
+ const std::vector<TestCase*>& test_cases) {
+ FILE* xmlout = OpenFileForWriting(output_file_);
+ std::stringstream stream;
+ PrintXmlTestsList(&stream, test_cases);
+ fprintf(xmlout, "%s", StringStreamToString(&stream).c_str());
+ fclose(xmlout);
+}
+
+// Returns an XML-escaped copy of the input string str. If is_attribute
+// is true, the text is meant to appear as an attribute value, and
+// normalizable whitespace is preserved by replacing it with character
+// references.
+//
+// Invalid XML characters in str, if any, are stripped from the output.
+// It is expected that most, if not all, of the text processed by this
+// module will consist of ordinary English text.
+// If this module is ever modified to produce version 1.1 XML output,
+// most invalid characters can be retained using character references.
+// FIXME: It might be nice to have a minimally invasive, human-readable
+// escaping scheme for invalid characters, rather than dropping them.
+std::string XmlUnitTestResultPrinter::EscapeXml(
+ const std::string& str, bool is_attribute) {
+ Message m;
+
+ for (size_t i = 0; i < str.size(); ++i) {
+ const char ch = str[i];
+ switch (ch) {
+ case '<':
+ m << "&lt;";
+ break;
+ case '>':
+ m << "&gt;";
+ break;
+ case '&':
+ m << "&amp;";
+ break;
+ case '\'':
+ if (is_attribute)
+ m << "&apos;";
+ else
+ m << '\'';
+ break;
+ case '"':
+ if (is_attribute)
+ m << "&quot;";
+ else
+ m << '"';
+ break;
+ default:
+ if (IsValidXmlCharacter(ch)) {
+ if (is_attribute && IsNormalizableWhitespace(ch))
+ m << "&#x" << String::FormatByte(static_cast<unsigned char>(ch))
+ << ";";
+ else
+ m << ch;
+ }
+ break;
+ }
+ }
+
+ return m.GetString();
+}
+
+// Returns the given string with all characters invalid in XML removed.
+// Currently invalid characters are dropped from the string. An
+// alternative is to replace them with certain characters such as . or ?.
+std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(
+ const std::string& str) {
+ std::string output;
+ output.reserve(str.size());
+ for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
+ if (IsValidXmlCharacter(*it))
+ output.push_back(*it);
+
+ return output;
+}
+
+// The following routines generate an XML representation of a UnitTest
+// object.
+// GOOGLETEST_CM0009 DO NOT DELETE
+//
+// This is how Google Test concepts map to the DTD:
+//
+// <testsuites name="AllTests"> <-- corresponds to a UnitTest object
+// <testsuite name="testcase-name"> <-- corresponds to a TestCase object
+// <testcase name="test-name"> <-- corresponds to a TestInfo object
+// <failure message="...">...</failure>
+// <failure message="...">...</failure>
+// <failure message="...">...</failure>
+// <-- individual assertion failures
+// </testcase>
+// </testsuite>
+// </testsuites>
+
+// Formats the given time in milliseconds as seconds.
+std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) {
+ ::std::stringstream ss;
+ ss << (static_cast<double>(ms) * 1e-3);
+ return ss.str();
+}
+
+static bool PortableLocaltime(time_t seconds, struct tm* out) {
+#if defined(_MSC_VER)
+ return localtime_s(out, &seconds) == 0;
+#elif defined(__MINGW32__) || defined(__MINGW64__)
+ // MINGW <time.h> provides neither localtime_r nor localtime_s, but uses
+ // Windows' localtime(), which has a thread-local tm buffer.
+ struct tm* tm_ptr = localtime(&seconds); // NOLINT
+ if (tm_ptr == NULL)
+ return false;
+ *out = *tm_ptr;
+ return true;
+#else
+ return localtime_r(&seconds, out) != NULL;
+#endif
+}
+
+// Converts the given epoch time in milliseconds to a date string in the ISO
+// 8601 format, without the timezone information.
+std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) {
+ struct tm time_struct;
+ if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct))
+ return "";
+ // YYYY-MM-DDThh:mm:ss
+ return StreamableToString(time_struct.tm_year + 1900) + "-" +
+ String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" +
+ String::FormatIntWidth2(time_struct.tm_mday) + "T" +
+ String::FormatIntWidth2(time_struct.tm_hour) + ":" +
+ String::FormatIntWidth2(time_struct.tm_min) + ":" +
+ String::FormatIntWidth2(time_struct.tm_sec);
+}
+
+// Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
+void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream,
+ const char* data) {
+ const char* segment = data;
+ *stream << "<![CDATA[";
+ for (;;) {
+ const char* const next_segment = strstr(segment, "]]>");
+ if (next_segment != NULL) {
+ stream->write(
+ segment, static_cast<std::streamsize>(next_segment - segment));
+ *stream << "]]>]]&gt;<![CDATA[";
+ segment = next_segment + strlen("]]>");
+ } else {
+ *stream << segment;
+ break;
+ }
+ }
+ *stream << "]]>";
+}
+
+void XmlUnitTestResultPrinter::OutputXmlAttribute(
+ std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ const std::string& value) {
+ const std::vector<std::string>& allowed_names =
+ GetReservedAttributesForElement(element_name);
+
+ GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+ allowed_names.end())
+ << "Attribute " << name << " is not allowed for element <" << element_name
+ << ">.";
+
+ *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\"";
+}
+
+// Prints an XML representation of a TestInfo object.
+// FIXME: There is also value in printing properties with the plain printer.
+void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream,
+ const char* test_case_name,
+ const TestInfo& test_info) {
+ const TestResult& result = *test_info.result();
+ const std::string kTestcase = "testcase";
+
+ if (test_info.is_in_another_shard()) {
+ return;
+ }
+
+ *stream << " <testcase";
+ OutputXmlAttribute(stream, kTestcase, "name", test_info.name());
+
+ if (test_info.value_param() != NULL) {
+ OutputXmlAttribute(stream, kTestcase, "value_param",
+ test_info.value_param());
+ }
+ if (test_info.type_param() != NULL) {
+ OutputXmlAttribute(stream, kTestcase, "type_param", test_info.type_param());
+ }
+ if (GTEST_FLAG(list_tests)) {
+ OutputXmlAttribute(stream, kTestcase, "file", test_info.file());
+ OutputXmlAttribute(stream, kTestcase, "line",
+ StreamableToString(test_info.line()));
+ *stream << " />\n";
+ return;
+ }
+
+ OutputXmlAttribute(stream, kTestcase, "status",
+ test_info.should_run() ? "run" : "notrun");
+ OutputXmlAttribute(stream, kTestcase, "time",
+ FormatTimeInMillisAsSeconds(result.elapsed_time()));
+ OutputXmlAttribute(stream, kTestcase, "classname", test_case_name);
+
+ int failures = 0;
+ for (int i = 0; i < result.total_part_count(); ++i) {
+ const TestPartResult& part = result.GetTestPartResult(i);
+ if (part.failed()) {
+ if (++failures == 1) {
+ *stream << ">\n";
+ }
+ const std::string location =
+ internal::FormatCompilerIndependentFileLocation(part.file_name(),
+ part.line_number());
+ const std::string summary = location + "\n" + part.summary();
+ *stream << " <failure message=\""
+ << EscapeXmlAttribute(summary.c_str())
+ << "\" type=\"\">";
+ const std::string detail = location + "\n" + part.message();
+ OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str());
+ *stream << "</failure>\n";
+ }
+ }
+
+ if (failures == 0 && result.test_property_count() == 0) {
+ *stream << " />\n";
+ } else {
+ if (failures == 0) {
+ *stream << ">\n";
+ }
+ OutputXmlTestProperties(stream, result);
+ *stream << " </testcase>\n";
+ }
+}
+
+// Prints an XML representation of a TestCase object
+void XmlUnitTestResultPrinter::PrintXmlTestCase(std::ostream* stream,
+ const TestCase& test_case) {
+ const std::string kTestsuite = "testsuite";
+ *stream << " <" << kTestsuite;
+ OutputXmlAttribute(stream, kTestsuite, "name", test_case.name());
+ OutputXmlAttribute(stream, kTestsuite, "tests",
+ StreamableToString(test_case.reportable_test_count()));
+ if (!GTEST_FLAG(list_tests)) {
+ OutputXmlAttribute(stream, kTestsuite, "failures",
+ StreamableToString(test_case.failed_test_count()));
+ OutputXmlAttribute(
+ stream, kTestsuite, "disabled",
+ StreamableToString(test_case.reportable_disabled_test_count()));
+ OutputXmlAttribute(stream, kTestsuite, "errors", "0");
+ OutputXmlAttribute(stream, kTestsuite, "time",
+ FormatTimeInMillisAsSeconds(test_case.elapsed_time()));
+ *stream << TestPropertiesAsXmlAttributes(test_case.ad_hoc_test_result());
+ }
+ *stream << ">\n";
+ for (int i = 0; i < test_case.total_test_count(); ++i) {
+ if (test_case.GetTestInfo(i)->is_reportable())
+ OutputXmlTestInfo(stream, test_case.name(), *test_case.GetTestInfo(i));
+ }
+ *stream << " </" << kTestsuite << ">\n";
+}
+
+// Prints an XML summary of unit_test to output stream out.
+void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream,
+ const UnitTest& unit_test) {
+ const std::string kTestsuites = "testsuites";
+
+ *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ *stream << "<" << kTestsuites;
+
+ OutputXmlAttribute(stream, kTestsuites, "tests",
+ StreamableToString(unit_test.reportable_test_count()));
+ OutputXmlAttribute(stream, kTestsuites, "failures",
+ StreamableToString(unit_test.failed_test_count()));
+ OutputXmlAttribute(
+ stream, kTestsuites, "disabled",
+ StreamableToString(unit_test.reportable_disabled_test_count()));
+ OutputXmlAttribute(stream, kTestsuites, "errors", "0");
+ OutputXmlAttribute(
+ stream, kTestsuites, "timestamp",
+ FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp()));
+ OutputXmlAttribute(stream, kTestsuites, "time",
+ FormatTimeInMillisAsSeconds(unit_test.elapsed_time()));
+
+ if (GTEST_FLAG(shuffle)) {
+ OutputXmlAttribute(stream, kTestsuites, "random_seed",
+ StreamableToString(unit_test.random_seed()));
+ }
+ *stream << TestPropertiesAsXmlAttributes(unit_test.ad_hoc_test_result());
+
+ OutputXmlAttribute(stream, kTestsuites, "name", "AllTests");
+ *stream << ">\n";
+
+ for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
+ if (unit_test.GetTestCase(i)->reportable_test_count() > 0)
+ PrintXmlTestCase(stream, *unit_test.GetTestCase(i));
+ }
+ *stream << "</" << kTestsuites << ">\n";
+}
+
+void XmlUnitTestResultPrinter::PrintXmlTestsList(
+ std::ostream* stream, const std::vector<TestCase*>& test_cases) {
+ const std::string kTestsuites = "testsuites";
+
+ *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ *stream << "<" << kTestsuites;
+
+ int total_tests = 0;
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ total_tests += test_cases[i]->total_test_count();
+ }
+ OutputXmlAttribute(stream, kTestsuites, "tests",
+ StreamableToString(total_tests));
+ OutputXmlAttribute(stream, kTestsuites, "name", "AllTests");
+ *stream << ">\n";
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ PrintXmlTestCase(stream, *test_cases[i]);
+ }
+ *stream << "</" << kTestsuites << ">\n";
+}
+
+// Produces a string representing the test properties in a result as space
+// delimited XML attributes based on the property key="value" pairs.
+std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes(
+ const TestResult& result) {
+ Message attributes;
+ for (int i = 0; i < result.test_property_count(); ++i) {
+ const TestProperty& property = result.GetTestProperty(i);
+ attributes << " " << property.key() << "="
+ << "\"" << EscapeXmlAttribute(property.value()) << "\"";
+ }
+ return attributes.GetString();
+}
+
+void XmlUnitTestResultPrinter::OutputXmlTestProperties(
+ std::ostream* stream, const TestResult& result) {
+ const std::string kProperties = "properties";
+ const std::string kProperty = "property";
+
+ if (result.test_property_count() <= 0) {
+ return;
+ }
+
+ *stream << "<" << kProperties << ">\n";
+ for (int i = 0; i < result.test_property_count(); ++i) {
+ const TestProperty& property = result.GetTestProperty(i);
+ *stream << "<" << kProperty;
+ *stream << " name=\"" << EscapeXmlAttribute(property.key()) << "\"";
+ *stream << " value=\"" << EscapeXmlAttribute(property.value()) << "\"";
+ *stream << "/>\n";
+ }
+ *stream << "</" << kProperties << ">\n";
+}
+
+// End XmlUnitTestResultPrinter
+
+// This class generates an JSON output file.
+class JsonUnitTestResultPrinter : public EmptyTestEventListener {
+ public:
+ explicit JsonUnitTestResultPrinter(const char* output_file);
+
+ virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
+
+ // Prints an JSON summary of all unit tests.
+ static void PrintJsonTestList(::std::ostream* stream,
+ const std::vector<TestCase*>& test_cases);
+
+ private:
+ // Returns an JSON-escaped copy of the input string str.
+ static std::string EscapeJson(const std::string& str);
+
+ //// Verifies that the given attribute belongs to the given element and
+ //// streams the attribute as JSON.
+ static void OutputJsonKey(std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ const std::string& value,
+ const std::string& indent,
+ bool comma = true);
+ static void OutputJsonKey(std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ int value,
+ const std::string& indent,
+ bool comma = true);
+
+ // Streams a JSON representation of a TestInfo object.
+ static void OutputJsonTestInfo(::std::ostream* stream,
+ const char* test_case_name,
+ const TestInfo& test_info);
+
+ // Prints a JSON representation of a TestCase object
+ static void PrintJsonTestCase(::std::ostream* stream,
+ const TestCase& test_case);
+
+ // Prints a JSON summary of unit_test to output stream out.
+ static void PrintJsonUnitTest(::std::ostream* stream,
+ const UnitTest& unit_test);
+
+ // Produces a string representing the test properties in a result as
+ // a JSON dictionary.
+ static std::string TestPropertiesAsJson(const TestResult& result,
+ const std::string& indent);
+
+ // The output file.
+ const std::string output_file_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(JsonUnitTestResultPrinter);
+};
+
+// Creates a new JsonUnitTestResultPrinter.
+JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file)
+ : output_file_(output_file) {
+ if (output_file_.empty()) {
+ GTEST_LOG_(FATAL) << "JSON output file may not be null";
+ }
+}
+
+void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+ int /*iteration*/) {
+ FILE* jsonout = OpenFileForWriting(output_file_);
+ std::stringstream stream;
+ PrintJsonUnitTest(&stream, unit_test);
+ fprintf(jsonout, "%s", StringStreamToString(&stream).c_str());
+ fclose(jsonout);
+}
+
+// Returns an JSON-escaped copy of the input string str.
+std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) {
+ Message m;
+
+ for (size_t i = 0; i < str.size(); ++i) {
+ const char ch = str[i];
+ switch (ch) {
+ case '\\':
+ case '"':
+ case '/':
+ m << '\\' << ch;
+ break;
+ case '\b':
+ m << "\\b";
+ break;
+ case '\t':
+ m << "\\t";
+ break;
+ case '\n':
+ m << "\\n";
+ break;
+ case '\f':
+ m << "\\f";
+ break;
+ case '\r':
+ m << "\\r";
+ break;
+ default:
+ if (ch < ' ') {
+ m << "\\u00" << String::FormatByte(static_cast<unsigned char>(ch));
+ } else {
+ m << ch;
+ }
+ break;
+ }
+ }
+
+ return m.GetString();
+}
+
+// The following routines generate an JSON representation of a UnitTest
+// object.
+
+// Formats the given time in milliseconds as seconds.
+static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) {
+ ::std::stringstream ss;
+ ss << (static_cast<double>(ms) * 1e-3) << "s";
+ return ss.str();
+}
+
+// Converts the given epoch time in milliseconds to a date string in the
+// RFC3339 format, without the timezone information.
+static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) {
+ struct tm time_struct;
+ if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct))
+ return "";
+ // YYYY-MM-DDThh:mm:ss
+ return StreamableToString(time_struct.tm_year + 1900) + "-" +
+ String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" +
+ String::FormatIntWidth2(time_struct.tm_mday) + "T" +
+ String::FormatIntWidth2(time_struct.tm_hour) + ":" +
+ String::FormatIntWidth2(time_struct.tm_min) + ":" +
+ String::FormatIntWidth2(time_struct.tm_sec) + "Z";
+}
+
+static inline std::string Indent(int width) {
+ return std::string(width, ' ');
+}
+
+void JsonUnitTestResultPrinter::OutputJsonKey(
+ std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ const std::string& value,
+ const std::string& indent,
+ bool comma) {
+ const std::vector<std::string>& allowed_names =
+ GetReservedAttributesForElement(element_name);
+
+ GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+ allowed_names.end())
+ << "Key \"" << name << "\" is not allowed for value \"" << element_name
+ << "\".";
+
+ *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\"";
+ if (comma)
+ *stream << ",\n";
+}
+
+void JsonUnitTestResultPrinter::OutputJsonKey(
+ std::ostream* stream,
+ const std::string& element_name,
+ const std::string& name,
+ int value,
+ const std::string& indent,
+ bool comma) {
+ const std::vector<std::string>& allowed_names =
+ GetReservedAttributesForElement(element_name);
+
+ GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+ allowed_names.end())
+ << "Key \"" << name << "\" is not allowed for value \"" << element_name
+ << "\".";
+
+ *stream << indent << "\"" << name << "\": " << StreamableToString(value);
+ if (comma)
+ *stream << ",\n";
+}
+
+// Prints a JSON representation of a TestInfo object.
+void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream,
+ const char* test_case_name,
+ const TestInfo& test_info) {
+ const TestResult& result = *test_info.result();
+ const std::string kTestcase = "testcase";
+ const std::string kIndent = Indent(10);
+
+ *stream << Indent(8) << "{\n";
+ OutputJsonKey(stream, kTestcase, "name", test_info.name(), kIndent);
+
+ if (test_info.value_param() != NULL) {
+ OutputJsonKey(stream, kTestcase, "value_param",
+ test_info.value_param(), kIndent);
+ }
+ if (test_info.type_param() != NULL) {
+ OutputJsonKey(stream, kTestcase, "type_param", test_info.type_param(),
+ kIndent);
+ }
+ if (GTEST_FLAG(list_tests)) {
+ OutputJsonKey(stream, kTestcase, "file", test_info.file(), kIndent);
+ OutputJsonKey(stream, kTestcase, "line", test_info.line(), kIndent, false);
+ *stream << "\n" << Indent(8) << "}";
+ return;
+ }
+
+ OutputJsonKey(stream, kTestcase, "status",
+ test_info.should_run() ? "RUN" : "NOTRUN", kIndent);
+ OutputJsonKey(stream, kTestcase, "time",
+ FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent);
+ OutputJsonKey(stream, kTestcase, "classname", test_case_name, kIndent, false);
+ *stream << TestPropertiesAsJson(result, kIndent);
+
+ int failures = 0;
+ for (int i = 0; i < result.total_part_count(); ++i) {
+ const TestPartResult& part = result.GetTestPartResult(i);
+ if (part.failed()) {
+ *stream << ",\n";
+ if (++failures == 1) {
+ *stream << kIndent << "\"" << "failures" << "\": [\n";
+ }
+ const std::string location =
+ internal::FormatCompilerIndependentFileLocation(part.file_name(),
+ part.line_number());
+ const std::string message = EscapeJson(location + "\n" + part.message());
+ *stream << kIndent << " {\n"
+ << kIndent << " \"failure\": \"" << message << "\",\n"
+ << kIndent << " \"type\": \"\"\n"
+ << kIndent << " }";
+ }
+ }
+
+ if (failures > 0)
+ *stream << "\n" << kIndent << "]";
+ *stream << "\n" << Indent(8) << "}";
+}
+
+// Prints an JSON representation of a TestCase object
+void JsonUnitTestResultPrinter::PrintJsonTestCase(std::ostream* stream,
+ const TestCase& test_case) {
+ const std::string kTestsuite = "testsuite";
+ const std::string kIndent = Indent(6);
+
+ *stream << Indent(4) << "{\n";
+ OutputJsonKey(stream, kTestsuite, "name", test_case.name(), kIndent);
+ OutputJsonKey(stream, kTestsuite, "tests", test_case.reportable_test_count(),
+ kIndent);
+ if (!GTEST_FLAG(list_tests)) {
+ OutputJsonKey(stream, kTestsuite, "failures", test_case.failed_test_count(),
+ kIndent);
+ OutputJsonKey(stream, kTestsuite, "disabled",
+ test_case.reportable_disabled_test_count(), kIndent);
+ OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent);
+ OutputJsonKey(stream, kTestsuite, "time",
+ FormatTimeInMillisAsDuration(test_case.elapsed_time()),
+ kIndent, false);
+ *stream << TestPropertiesAsJson(test_case.ad_hoc_test_result(), kIndent)
+ << ",\n";
+ }
+
+ *stream << kIndent << "\"" << kTestsuite << "\": [\n";
+
+ bool comma = false;
+ for (int i = 0; i < test_case.total_test_count(); ++i) {
+ if (test_case.GetTestInfo(i)->is_reportable()) {
+ if (comma) {
+ *stream << ",\n";
+ } else {
+ comma = true;
+ }
+ OutputJsonTestInfo(stream, test_case.name(), *test_case.GetTestInfo(i));
+ }
+ }
+ *stream << "\n" << kIndent << "]\n" << Indent(4) << "}";
+}
+
+// Prints a JSON summary of unit_test to output stream out.
+void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream,
+ const UnitTest& unit_test) {
+ const std::string kTestsuites = "testsuites";
+ const std::string kIndent = Indent(2);
+ *stream << "{\n";
+
+ OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(),
+ kIndent);
+ OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(),
+ kIndent);
+ OutputJsonKey(stream, kTestsuites, "disabled",
+ unit_test.reportable_disabled_test_count(), kIndent);
+ OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent);
+ if (GTEST_FLAG(shuffle)) {
+ OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(),
+ kIndent);
+ }
+ OutputJsonKey(stream, kTestsuites, "timestamp",
+ FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()),
+ kIndent);
+ OutputJsonKey(stream, kTestsuites, "time",
+ FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent,
+ false);
+
+ *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent)
+ << ",\n";
+
+ OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent);
+ *stream << kIndent << "\"" << kTestsuites << "\": [\n";
+
+ bool comma = false;
+ for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
+ if (unit_test.GetTestCase(i)->reportable_test_count() > 0) {
+ if (comma) {
+ *stream << ",\n";
+ } else {
+ comma = true;
+ }
+ PrintJsonTestCase(stream, *unit_test.GetTestCase(i));
+ }
+ }
+
+ *stream << "\n" << kIndent << "]\n" << "}\n";
+}
+
+void JsonUnitTestResultPrinter::PrintJsonTestList(
+ std::ostream* stream, const std::vector<TestCase*>& test_cases) {
+ const std::string kTestsuites = "testsuites";
+ const std::string kIndent = Indent(2);
+ *stream << "{\n";
+ int total_tests = 0;
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ total_tests += test_cases[i]->total_test_count();
+ }
+ OutputJsonKey(stream, kTestsuites, "tests", total_tests, kIndent);
+
+ OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent);
+ *stream << kIndent << "\"" << kTestsuites << "\": [\n";
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ if (i != 0) {
+ *stream << ",\n";
+ }
+ PrintJsonTestCase(stream, *test_cases[i]);
+ }
+
+ *stream << "\n"
+ << kIndent << "]\n"
+ << "}\n";
+}
+// Produces a string representing the test properties in a result as
+// a JSON dictionary.
+std::string JsonUnitTestResultPrinter::TestPropertiesAsJson(
+ const TestResult& result, const std::string& indent) {
+ Message attributes;
+ for (int i = 0; i < result.test_property_count(); ++i) {
+ const TestProperty& property = result.GetTestProperty(i);
+ attributes << ",\n" << indent << "\"" << property.key() << "\": "
+ << "\"" << EscapeJson(property.value()) << "\"";
+ }
+ return attributes.GetString();
+}
+
+// End JsonUnitTestResultPrinter
+
+#if GTEST_CAN_STREAM_RESULTS_
+
+// Checks if str contains '=', '&', '%' or '\n' characters. If yes,
+// replaces them by "%xx" where xx is their hexadecimal value. For
+// example, replaces "=" with "%3D". This algorithm is O(strlen(str))
+// in both time and space -- important as the input str may contain an
+// arbitrarily long test failure message and stack trace.
+std::string StreamingListener::UrlEncode(const char* str) {
+ std::string result;
+ result.reserve(strlen(str) + 1);
+ for (char ch = *str; ch != '\0'; ch = *++str) {
+ switch (ch) {
+ case '%':
+ case '=':
+ case '&':
+ case '\n':
+ result.append("%" + String::FormatByte(static_cast<unsigned char>(ch)));
+ break;
+ default:
+ result.push_back(ch);
+ break;
+ }
+ }
+ return result;
+}
+
+void StreamingListener::SocketWriter::MakeConnection() {
+ GTEST_CHECK_(sockfd_ == -1)
+ << "MakeConnection() can't be called when there is already a connection.";
+
+ addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses.
+ hints.ai_socktype = SOCK_STREAM;
+ addrinfo* servinfo = NULL;
+
+ // Use the getaddrinfo() to get a linked list of IP addresses for
+ // the given host name.
+ const int error_num = getaddrinfo(
+ host_name_.c_str(), port_num_.c_str(), &hints, &servinfo);
+ if (error_num != 0) {
+ GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: "
+ << gai_strerror(error_num);
+ }
+
+ // Loop through all the results and connect to the first we can.
+ for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != NULL;
+ cur_addr = cur_addr->ai_next) {
+ sockfd_ = socket(
+ cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol);
+ if (sockfd_ != -1) {
+ // Connect the client socket to the server socket.
+ if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) {
+ close(sockfd_);
+ sockfd_ = -1;
+ }
+ }
+ }
+
+ freeaddrinfo(servinfo); // all done with this structure
+
+ if (sockfd_ == -1) {
+ GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to "
+ << host_name_ << ":" << port_num_;
+ }
+}
+
+// End of class Streaming Listener
+#endif // GTEST_CAN_STREAM_RESULTS__
+
+// class OsStackTraceGetter
+
+const char* const OsStackTraceGetterInterface::kElidedFramesMarker =
+ "... " GTEST_NAME_ " internal frames ...";
+
+std::string OsStackTraceGetter::CurrentStackTrace(int max_depth, int skip_count)
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+#if GTEST_HAS_ABSL
+ std::string result;
+
+ if (max_depth <= 0) {
+ return result;
+ }
+
+ max_depth = std::min(max_depth, kMaxStackTraceDepth);
+
+ std::vector<void*> raw_stack(max_depth);
+ // Skips the frames requested by the caller, plus this function.
+ const int raw_stack_size =
+ absl::GetStackTrace(&raw_stack[0], max_depth, skip_count + 1);
+
+ void* caller_frame = nullptr;
+ {
+ MutexLock lock(&mutex_);
+ caller_frame = caller_frame_;
+ }
+
+ for (int i = 0; i < raw_stack_size; ++i) {
+ if (raw_stack[i] == caller_frame &&
+ !GTEST_FLAG(show_internal_stack_frames)) {
+ // Add a marker to the trace and stop adding frames.
+ absl::StrAppend(&result, kElidedFramesMarker, "\n");
+ break;
+ }
+
+ char tmp[1024];
+ const char* symbol = "(unknown)";
+ if (absl::Symbolize(raw_stack[i], tmp, sizeof(tmp))) {
+ symbol = tmp;
+ }
+
+ char line[1024];
+ snprintf(line, sizeof(line), " %p: %s\n", raw_stack[i], symbol);
+ result += line;
+ }
+
+ return result;
+
+#else // !GTEST_HAS_ABSL
+ static_cast<void>(max_depth);
+ static_cast<void>(skip_count);
+ return "";
+#endif // GTEST_HAS_ABSL
+}
+
+void OsStackTraceGetter::UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_) {
+#if GTEST_HAS_ABSL
+ void* caller_frame = nullptr;
+ if (absl::GetStackTrace(&caller_frame, 1, 3) <= 0) {
+ caller_frame = nullptr;
+ }
+
+ MutexLock lock(&mutex_);
+ caller_frame_ = caller_frame;
+#endif // GTEST_HAS_ABSL
+}
+
+// A helper class that creates the premature-exit file in its
+// constructor and deletes the file in its destructor.
+class ScopedPrematureExitFile {
+ public:
+ explicit ScopedPrematureExitFile(const char* premature_exit_filepath)
+ : premature_exit_filepath_(premature_exit_filepath ?
+ premature_exit_filepath : "") {
+ // If a path to the premature-exit file is specified...
+ if (!premature_exit_filepath_.empty()) {
+ // create the file with a single "0" character in it. I/O
+ // errors are ignored as there's nothing better we can do and we
+ // don't want to fail the test because of this.
+ FILE* pfile = posix::FOpen(premature_exit_filepath, "w");
+ fwrite("0", 1, 1, pfile);
+ fclose(pfile);
+ }
+ }
+
+ ~ScopedPrematureExitFile() {
+ if (!premature_exit_filepath_.empty()) {
+ int retval = remove(premature_exit_filepath_.c_str());
+ if (retval) {
+ GTEST_LOG_(ERROR) << "Failed to remove premature exit filepath \""
+ << premature_exit_filepath_ << "\" with error "
+ << retval;
+ }
+ }
+ }
+
+ private:
+ const std::string premature_exit_filepath_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile);
+};
+
+} // namespace internal
+
+// class TestEventListeners
+
+TestEventListeners::TestEventListeners()
+ : repeater_(new internal::TestEventRepeater()),
+ default_result_printer_(NULL),
+ default_xml_generator_(NULL) {
+}
+
+TestEventListeners::~TestEventListeners() { delete repeater_; }
+
+// Returns the standard listener responsible for the default console
+// output. Can be removed from the listeners list to shut down default
+// console output. Note that removing this object from the listener list
+// with Release transfers its ownership to the user.
+void TestEventListeners::Append(TestEventListener* listener) {
+ repeater_->Append(listener);
+}
+
+// Removes the given event listener from the list and returns it. It then
+// becomes the caller's responsibility to delete the listener. Returns
+// NULL if the listener is not found in the list.
+TestEventListener* TestEventListeners::Release(TestEventListener* listener) {
+ if (listener == default_result_printer_)
+ default_result_printer_ = NULL;
+ else if (listener == default_xml_generator_)
+ default_xml_generator_ = NULL;
+ return repeater_->Release(listener);
+}
+
+// Returns repeater that broadcasts the TestEventListener events to all
+// subscribers.
+TestEventListener* TestEventListeners::repeater() { return repeater_; }
+
+// Sets the default_result_printer attribute to the provided listener.
+// The listener is also added to the listener list and previous
+// default_result_printer is removed from it and deleted. The listener can
+// also be NULL in which case it will not be added to the list. Does
+// nothing if the previous and the current listener objects are the same.
+void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) {
+ if (default_result_printer_ != listener) {
+ // It is an error to pass this method a listener that is already in the
+ // list.
+ delete Release(default_result_printer_);
+ default_result_printer_ = listener;
+ if (listener != NULL)
+ Append(listener);
+ }
+}
+
+// Sets the default_xml_generator attribute to the provided listener. The
+// listener is also added to the listener list and previous
+// default_xml_generator is removed from it and deleted. The listener can
+// also be NULL in which case it will not be added to the list. Does
+// nothing if the previous and the current listener objects are the same.
+void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) {
+ if (default_xml_generator_ != listener) {
+ // It is an error to pass this method a listener that is already in the
+ // list.
+ delete Release(default_xml_generator_);
+ default_xml_generator_ = listener;
+ if (listener != NULL)
+ Append(listener);
+ }
+}
+
+// Controls whether events will be forwarded by the repeater to the
+// listeners in the list.
+bool TestEventListeners::EventForwardingEnabled() const {
+ return repeater_->forwarding_enabled();
+}
+
+void TestEventListeners::SuppressEventForwarding() {
+ repeater_->set_forwarding_enabled(false);
+}
+
+// class UnitTest
+
+// Gets the singleton UnitTest object. The first time this method is
+// called, a UnitTest object is constructed and returned. Consecutive
+// calls will return the same object.
+//
+// We don't protect this under mutex_ as a user is not supposed to
+// call this before main() starts, from which point on the return
+// value will never change.
+UnitTest* UnitTest::GetInstance() {
+ // When compiled with MSVC 7.1 in optimized mode, destroying the
+ // UnitTest object upon exiting the program messes up the exit code,
+ // causing successful tests to appear failed. We have to use a
+ // different implementation in this case to bypass the compiler bug.
+ // This implementation makes the compiler happy, at the cost of
+ // leaking the UnitTest object.
+
+ // CodeGear C++Builder insists on a public destructor for the
+ // default implementation. Use this implementation to keep good OO
+ // design with private destructor.
+
+#if (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__)
+ static UnitTest* const instance = new UnitTest;
+ return instance;
+#else
+ static UnitTest instance;
+ return &instance;
+#endif // (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__)
+}
+
+// Gets the number of successful test cases.
+int UnitTest::successful_test_case_count() const {
+ return impl()->successful_test_case_count();
+}
+
+// Gets the number of failed test cases.
+int UnitTest::failed_test_case_count() const {
+ return impl()->failed_test_case_count();
+}
+
+// Gets the number of all test cases.
+int UnitTest::total_test_case_count() const {
+ return impl()->total_test_case_count();
+}
+
+// Gets the number of all test cases that contain at least one test
+// that should run.
+int UnitTest::test_case_to_run_count() const {
+ return impl()->test_case_to_run_count();
+}
+
+// Gets the number of successful tests.
+int UnitTest::successful_test_count() const {
+ return impl()->successful_test_count();
+}
+
+// Gets the number of failed tests.
+int UnitTest::failed_test_count() const { return impl()->failed_test_count(); }
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int UnitTest::reportable_disabled_test_count() const {
+ return impl()->reportable_disabled_test_count();
+}
+
+// Gets the number of disabled tests.
+int UnitTest::disabled_test_count() const {
+ return impl()->disabled_test_count();
+}
+
+// Gets the number of tests to be printed in the XML report.
+int UnitTest::reportable_test_count() const {
+ return impl()->reportable_test_count();
+}
+
+// Gets the number of all tests.
+int UnitTest::total_test_count() const { return impl()->total_test_count(); }
+
+// Gets the number of tests that should run.
+int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); }
+
+// Gets the time of the test program start, in ms from the start of the
+// UNIX epoch.
+internal::TimeInMillis UnitTest::start_timestamp() const {
+ return impl()->start_timestamp();
+}
+
+// Gets the elapsed time, in milliseconds.
+internal::TimeInMillis UnitTest::elapsed_time() const {
+ return impl()->elapsed_time();
+}
+
+// Returns true iff the unit test passed (i.e. all test cases passed).
+bool UnitTest::Passed() const { return impl()->Passed(); }
+
+// Returns true iff the unit test failed (i.e. some test case failed
+// or something outside of all tests failed).
+bool UnitTest::Failed() const { return impl()->Failed(); }
+
+// Gets the i-th test case among all the test cases. i can range from 0 to
+// total_test_case_count() - 1. If i is not in that range, returns NULL.
+const TestCase* UnitTest::GetTestCase(int i) const {
+ return impl()->GetTestCase(i);
+}
+
+// Returns the TestResult containing information on test failures and
+// properties logged outside of individual test cases.
+const TestResult& UnitTest::ad_hoc_test_result() const {
+ return *impl()->ad_hoc_test_result();
+}
+
+// Gets the i-th test case among all the test cases. i can range from 0 to
+// total_test_case_count() - 1. If i is not in that range, returns NULL.
+TestCase* UnitTest::GetMutableTestCase(int i) {
+ return impl()->GetMutableTestCase(i);
+}
+
+// Returns the list of event listeners that can be used to track events
+// inside Google Test.
+TestEventListeners& UnitTest::listeners() {
+ return *impl()->listeners();
+}
+
+// Registers and returns a global test environment. When a test
+// program is run, all global test environments will be set-up in the
+// order they were registered. After all tests in the program have
+// finished, all global test environments will be torn-down in the
+// *reverse* order they were registered.
+//
+// The UnitTest object takes ownership of the given environment.
+//
+// We don't protect this under mutex_, as we only support calling it
+// from the main thread.
+Environment* UnitTest::AddEnvironment(Environment* env) {
+ if (env == NULL) {
+ return NULL;
+ }
+
+ impl_->environments().push_back(env);
+ return env;
+}
+
+// Adds a TestPartResult to the current TestResult object. All Google Test
+// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call
+// this to report their results. The user code should use the
+// assertion macros instead of calling this directly.
+void UnitTest::AddTestPartResult(
+ TestPartResult::Type result_type,
+ const char* file_name,
+ int line_number,
+ const std::string& message,
+ const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) {
+ Message msg;
+ msg << message;
+
+ internal::MutexLock lock(&mutex_);
+ if (impl_->gtest_trace_stack().size() > 0) {
+ msg << "\n" << GTEST_NAME_ << " trace:";
+
+ for (int i = static_cast<int>(impl_->gtest_trace_stack().size());
+ i > 0; --i) {
+ const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1];
+ msg << "\n" << internal::FormatFileLocation(trace.file, trace.line)
+ << " " << trace.message;
+ }
+ }
+
+ if (os_stack_trace.c_str() != NULL && !os_stack_trace.empty()) {
+ msg << internal::kStackTraceMarker << os_stack_trace;
+ }
+
+ const TestPartResult result =
+ TestPartResult(result_type, file_name, line_number,
+ msg.GetString().c_str());
+ impl_->GetTestPartResultReporterForCurrentThread()->
+ ReportTestPartResult(result);
+
+ if (result_type != TestPartResult::kSuccess) {
+ // gtest_break_on_failure takes precedence over
+ // gtest_throw_on_failure. This allows a user to set the latter
+ // in the code (perhaps in order to use Google Test assertions
+ // with another testing framework) and specify the former on the
+ // command line for debugging.
+ if (GTEST_FLAG(break_on_failure)) {
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+ // Using DebugBreak on Windows allows gtest to still break into a debugger
+ // when a failure happens and both the --gtest_break_on_failure and
+ // the --gtest_catch_exceptions flags are specified.
+ DebugBreak();
+#elif (!defined(__native_client__)) && \
+ ((defined(__clang__) || defined(__GNUC__)) && \
+ (defined(__x86_64__) || defined(__i386__)))
+ // with clang/gcc we can achieve the same effect on x86 by invoking int3
+ asm("int3");
+#else
+ // Dereference NULL through a volatile pointer to prevent the compiler
+ // from removing. We use this rather than abort() or __builtin_trap() for
+ // portability: Symbian doesn't implement abort() well, and some debuggers
+ // don't correctly trap abort().
+ *static_cast<volatile int*>(NULL) = 1;
+#endif // GTEST_OS_WINDOWS
+ } else if (GTEST_FLAG(throw_on_failure)) {
+#if GTEST_HAS_EXCEPTIONS
+ throw internal::GoogleTestFailureException(result);
+#else
+ // We cannot call abort() as it generates a pop-up in debug mode
+ // that cannot be suppressed in VC 7.1 or below.
+ exit(1);
+#endif
+ }
+ }
+}
+
+// Adds a TestProperty to the current TestResult object when invoked from
+// inside a test, to current TestCase's ad_hoc_test_result_ when invoked
+// from SetUpTestCase or TearDownTestCase, or to the global property set
+// when invoked elsewhere. If the result already contains a property with
+// the same key, the value will be updated.
+void UnitTest::RecordProperty(const std::string& key,
+ const std::string& value) {
+ impl_->RecordProperty(TestProperty(key, value));
+}
+
+// Runs all tests in this UnitTest object and prints the result.
+// Returns 0 if successful, or 1 otherwise.
+//
+// We don't protect this under mutex_, as we only support calling it
+// from the main thread.
+int UnitTest::Run() {
+ const bool in_death_test_child_process =
+ internal::GTEST_FLAG(internal_run_death_test).length() > 0;
+
+ // Google Test implements this protocol for catching that a test
+ // program exits before returning control to Google Test:
+ //
+ // 1. Upon start, Google Test creates a file whose absolute path
+ // is specified by the environment variable
+ // TEST_PREMATURE_EXIT_FILE.
+ // 2. When Google Test has finished its work, it deletes the file.
+ //
+ // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before
+ // running a Google-Test-based test program and check the existence
+ // of the file at the end of the test execution to see if it has
+ // exited prematurely.
+
+ // If we are in the child process of a death test, don't
+ // create/delete the premature exit file, as doing so is unnecessary
+ // and will confuse the parent process. Otherwise, create/delete
+ // the file upon entering/leaving this function. If the program
+ // somehow exits before this function has a chance to return, the
+ // premature-exit file will be left undeleted, causing a test runner
+ // that understands the premature-exit-file protocol to report the
+ // test as having failed.
+ const internal::ScopedPrematureExitFile premature_exit_file(
+ in_death_test_child_process ?
+ NULL : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE"));
+
+ // Captures the value of GTEST_FLAG(catch_exceptions). This value will be
+ // used for the duration of the program.
+ impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions));
+
+#if GTEST_OS_WINDOWS
+ // Either the user wants Google Test to catch exceptions thrown by the
+ // tests or this is executing in the context of death test child
+ // process. In either case the user does not want to see pop-up dialogs
+ // about crashes - they are expected.
+ if (impl()->catch_exceptions() || in_death_test_child_process) {
+# if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+ // SetErrorMode doesn't exist on CE.
+ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT |
+ SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+# endif // !GTEST_OS_WINDOWS_MOBILE
+
+# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE
+ // Death test children can be terminated with _abort(). On Windows,
+ // _abort() can show a dialog with a warning message. This forces the
+ // abort message to go to stderr instead.
+ _set_error_mode(_OUT_TO_STDERR);
+# endif
+
+# if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE
+ // In the debug version, Visual Studio pops up a separate dialog
+ // offering a choice to debug the aborted program. We need to suppress
+ // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement
+ // executed. Google Test will notify the user of any unexpected
+ // failure via stderr.
+ //
+ // VC++ doesn't define _set_abort_behavior() prior to the version 8.0.
+ // Users of prior VC versions shall suffer the agony and pain of
+ // clicking through the countless debug dialogs.
+ // FIXME: find a way to suppress the abort dialog() in the
+ // debug mode when compiled with VC 7.1 or lower.
+ if (!GTEST_FLAG(break_on_failure))
+ _set_abort_behavior(
+ 0x0, // Clear the following flags:
+ _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump.
+# endif
+ }
+#endif // GTEST_OS_WINDOWS
+
+ return internal::HandleExceptionsInMethodIfSupported(
+ impl(),
+ &internal::UnitTestImpl::RunAllTests,
+ "auxiliary test code (environments or event listeners)") ? 0 : 1;
+}
+
+// Returns the working directory when the first TEST() or TEST_F() was
+// executed.
+const char* UnitTest::original_working_dir() const {
+ return impl_->original_working_dir_.c_str();
+}
+
+// Returns the TestCase object for the test that's currently running,
+// or NULL if no test is running.
+const TestCase* UnitTest::current_test_case() const
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+ internal::MutexLock lock(&mutex_);
+ return impl_->current_test_case();
+}
+
+// Returns the TestInfo object for the test that's currently running,
+// or NULL if no test is running.
+const TestInfo* UnitTest::current_test_info() const
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+ internal::MutexLock lock(&mutex_);
+ return impl_->current_test_info();
+}
+
+// Returns the random seed used at the start of the current test run.
+int UnitTest::random_seed() const { return impl_->random_seed(); }
+
+// Returns ParameterizedTestCaseRegistry object used to keep track of
+// value-parameterized tests and instantiate and register them.
+internal::ParameterizedTestCaseRegistry&
+ UnitTest::parameterized_test_registry()
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+ return impl_->parameterized_test_registry();
+}
+
+// Creates an empty UnitTest.
+UnitTest::UnitTest() {
+ impl_ = new internal::UnitTestImpl(this);
+}
+
+// Destructor of UnitTest.
+UnitTest::~UnitTest() {
+ delete impl_;
+}
+
+// Pushes a trace defined by SCOPED_TRACE() on to the per-thread
+// Google Test trace stack.
+void UnitTest::PushGTestTrace(const internal::TraceInfo& trace)
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+ internal::MutexLock lock(&mutex_);
+ impl_->gtest_trace_stack().push_back(trace);
+}
+
+// Pops a trace from the per-thread Google Test trace stack.
+void UnitTest::PopGTestTrace()
+ GTEST_LOCK_EXCLUDED_(mutex_) {
+ internal::MutexLock lock(&mutex_);
+ impl_->gtest_trace_stack().pop_back();
+}
+
+namespace internal {
+
+UnitTestImpl::UnitTestImpl(UnitTest* parent)
+ : parent_(parent),
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */)
+ default_global_test_part_result_reporter_(this),
+ default_per_thread_test_part_result_reporter_(this),
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+ global_test_part_result_repoter_(
+ &default_global_test_part_result_reporter_),
+ per_thread_test_part_result_reporter_(
+ &default_per_thread_test_part_result_reporter_),
+ parameterized_test_registry_(),
+ parameterized_tests_registered_(false),
+ last_death_test_case_(-1),
+ current_test_case_(NULL),
+ current_test_info_(NULL),
+ ad_hoc_test_result_(),
+ os_stack_trace_getter_(NULL),
+ post_flag_parse_init_performed_(false),
+ random_seed_(0), // Will be overridden by the flag before first use.
+ random_(0), // Will be reseeded before first use.
+ start_timestamp_(0),
+ elapsed_time_(0),
+#if GTEST_HAS_DEATH_TEST
+ death_test_factory_(new DefaultDeathTestFactory),
+#endif
+ // Will be overridden by the flag before first use.
+ catch_exceptions_(false) {
+ listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
+}
+
+UnitTestImpl::~UnitTestImpl() {
+ // Deletes every TestCase.
+ ForEach(test_cases_, internal::Delete<TestCase>);
+
+ // Deletes every Environment.
+ ForEach(environments_, internal::Delete<Environment>);
+
+ delete os_stack_trace_getter_;
+}
+
+// Adds a TestProperty to the current TestResult object when invoked in a
+// context of a test, to current test case's ad_hoc_test_result when invoke
+// from SetUpTestCase/TearDownTestCase, or to the global property set
+// otherwise. If the result already contains a property with the same key,
+// the value will be updated.
+void UnitTestImpl::RecordProperty(const TestProperty& test_property) {
+ std::string xml_element;
+ TestResult* test_result; // TestResult appropriate for property recording.
+
+ if (current_test_info_ != NULL) {
+ xml_element = "testcase";
+ test_result = &(current_test_info_->result_);
+ } else if (current_test_case_ != NULL) {
+ xml_element = "testsuite";
+ test_result = &(current_test_case_->ad_hoc_test_result_);
+ } else {
+ xml_element = "testsuites";
+ test_result = &ad_hoc_test_result_;
+ }
+ test_result->RecordProperty(xml_element, test_property);
+}
+
+#if GTEST_HAS_DEATH_TEST
+// Disables event forwarding if the control is currently in a death test
+// subprocess. Must not be called before InitGoogleTest.
+void UnitTestImpl::SuppressTestEventsIfInSubprocess() {
+ if (internal_run_death_test_flag_.get() != NULL)
+ listeners()->SuppressEventForwarding();
+}
+#endif // GTEST_HAS_DEATH_TEST
+
+// Initializes event listeners performing XML output as specified by
+// UnitTestOptions. Must not be called before InitGoogleTest.
+void UnitTestImpl::ConfigureXmlOutput() {
+ const std::string& output_format = UnitTestOptions::GetOutputFormat();
+ if (output_format == "xml") {
+ listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter(
+ UnitTestOptions::GetAbsolutePathToOutputFile().c_str()));
+ } else if (output_format == "json") {
+ listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter(
+ UnitTestOptions::GetAbsolutePathToOutputFile().c_str()));
+ } else if (output_format != "") {
+ GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \""
+ << output_format << "\" ignored.";
+ }
+}
+
+#if GTEST_CAN_STREAM_RESULTS_
+// Initializes event listeners for streaming test results in string form.
+// Must not be called before InitGoogleTest.
+void UnitTestImpl::ConfigureStreamingOutput() {
+ const std::string& target = GTEST_FLAG(stream_result_to);
+ if (!target.empty()) {
+ const size_t pos = target.find(':');
+ if (pos != std::string::npos) {
+ listeners()->Append(new StreamingListener(target.substr(0, pos),
+ target.substr(pos+1)));
+ } else {
+ GTEST_LOG_(WARNING) << "unrecognized streaming target \"" << target
+ << "\" ignored.";
+ }
+ }
+}
+#endif // GTEST_CAN_STREAM_RESULTS_
+
+// Performs initialization dependent upon flag values obtained in
+// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to
+// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest
+// this function is also called from RunAllTests. Since this function can be
+// called more than once, it has to be idempotent.
+void UnitTestImpl::PostFlagParsingInit() {
+ // Ensures that this function does not execute more than once.
+ if (!post_flag_parse_init_performed_) {
+ post_flag_parse_init_performed_ = true;
+
+#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_)
+ // Register to send notifications about key process state changes.
+ listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_());
+#endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_)
+
+#if GTEST_HAS_DEATH_TEST
+ InitDeathTestSubprocessControlInfo();
+ SuppressTestEventsIfInSubprocess();
+#endif // GTEST_HAS_DEATH_TEST
+
+ // Registers parameterized tests. This makes parameterized tests
+ // available to the UnitTest reflection API without running
+ // RUN_ALL_TESTS.
+ RegisterParameterizedTests();
+
+ // Configures listeners for XML output. This makes it possible for users
+ // to shut down the default XML output before invoking RUN_ALL_TESTS.
+ ConfigureXmlOutput();
+
+#if GTEST_CAN_STREAM_RESULTS_
+ // Configures listeners for streaming test results to the specified server.
+ ConfigureStreamingOutput();
+#endif // GTEST_CAN_STREAM_RESULTS_
+
+#if GTEST_HAS_ABSL
+ if (GTEST_FLAG(install_failure_signal_handler)) {
+ absl::FailureSignalHandlerOptions options;
+ absl::InstallFailureSignalHandler(options);
+ }
+#endif // GTEST_HAS_ABSL
+ }
+}
+
+// A predicate that checks the name of a TestCase against a known
+// value.
+//
+// This is used for implementation of the UnitTest class only. We put
+// it in the anonymous namespace to prevent polluting the outer
+// namespace.
+//
+// TestCaseNameIs is copyable.
+class TestCaseNameIs {
+ public:
+ // Constructor.
+ explicit TestCaseNameIs(const std::string& name)
+ : name_(name) {}
+
+ // Returns true iff the name of test_case matches name_.
+ bool operator()(const TestCase* test_case) const {
+ return test_case != NULL && strcmp(test_case->name(), name_.c_str()) == 0;
+ }
+
+ private:
+ std::string name_;
+};
+
+// Finds and returns a TestCase with the given name. If one doesn't
+// exist, creates one and returns it. It's the CALLER'S
+// RESPONSIBILITY to ensure that this function is only called WHEN THE
+// TESTS ARE NOT SHUFFLED.
+//
+// Arguments:
+//
+// test_case_name: name of the test case
+// type_param: the name of the test case's type parameter, or NULL if
+// this is not a typed or a type-parameterized test case.
+// set_up_tc: pointer to the function that sets up the test case
+// tear_down_tc: pointer to the function that tears down the test case
+TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,
+ const char* type_param,
+ Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc) {
+ // Can we find a TestCase with the given name?
+ const std::vector<TestCase*>::const_reverse_iterator test_case =
+ std::find_if(test_cases_.rbegin(), test_cases_.rend(),
+ TestCaseNameIs(test_case_name));
+
+ if (test_case != test_cases_.rend())
+ return *test_case;
+
+ // No. Let's create one.
+ TestCase* const new_test_case =
+ new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);
+
+ // Is this a death test case?
+ if (internal::UnitTestOptions::MatchesFilter(test_case_name,
+ kDeathTestCaseFilter)) {
+ // Yes. Inserts the test case after the last death test case
+ // defined so far. This only works when the test cases haven't
+ // been shuffled. Otherwise we may end up running a death test
+ // after a non-death test.
+ ++last_death_test_case_;
+ test_cases_.insert(test_cases_.begin() + last_death_test_case_,
+ new_test_case);
+ } else {
+ // No. Appends to the end of the list.
+ test_cases_.push_back(new_test_case);
+ }
+
+ test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));
+ return new_test_case;
+}
+
+// Helpers for setting up / tearing down the given environment. They
+// are for use in the ForEach() function.
+static void SetUpEnvironment(Environment* env) { env->SetUp(); }
+static void TearDownEnvironment(Environment* env) { env->TearDown(); }
+
+// Runs all tests in this UnitTest object, prints the result, and
+// returns true if all tests are successful. If any exception is
+// thrown during a test, the test is considered to be failed, but the
+// rest of the tests will still be run.
+//
+// When parameterized tests are enabled, it expands and registers
+// parameterized tests first in RegisterParameterizedTests().
+// All other functions called from RunAllTests() may safely assume that
+// parameterized tests are ready to be counted and run.
+bool UnitTestImpl::RunAllTests() {
+ // True iff Google Test is initialized before RUN_ALL_TESTS() is called.
+ const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized();
+
+ // Do not run any test if the --help flag was specified.
+ if (g_help_flag)
+ return true;
+
+ // Repeats the call to the post-flag parsing initialization in case the
+ // user didn't call InitGoogleTest.
+ PostFlagParsingInit();
+
+ // Even if sharding is not on, test runners may want to use the
+ // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding
+ // protocol.
+ internal::WriteToShardStatusFileIfNeeded();
+
+ // True iff we are in a subprocess for running a thread-safe-style
+ // death test.
+ bool in_subprocess_for_death_test = false;
+
+#if GTEST_HAS_DEATH_TEST
+ in_subprocess_for_death_test = (internal_run_death_test_flag_.get() != NULL);
+# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_)
+ if (in_subprocess_for_death_test) {
+ GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_();
+ }
+# endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_)
+#endif // GTEST_HAS_DEATH_TEST
+
+ const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex,
+ in_subprocess_for_death_test);
+
+ // Compares the full test names with the filter to decide which
+ // tests to run.
+ const bool has_tests_to_run = FilterTests(should_shard
+ ? HONOR_SHARDING_PROTOCOL
+ : IGNORE_SHARDING_PROTOCOL) > 0;
+
+ // Lists the tests and exits if the --gtest_list_tests flag was specified.
+ if (GTEST_FLAG(list_tests)) {
+ // This must be called *after* FilterTests() has been called.
+ ListTestsMatchingFilter();
+ return true;
+ }
+
+ random_seed_ = GTEST_FLAG(shuffle) ?
+ GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0;
+
+ // True iff at least one test has failed.
+ bool failed = false;
+
+ TestEventListener* repeater = listeners()->repeater();
+
+ start_timestamp_ = GetTimeInMillis();
+ repeater->OnTestProgramStart(*parent_);
+
+ // How many times to repeat the tests? We don't want to repeat them
+ // when we are inside the subprocess of a death test.
+ const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat);
+ // Repeats forever if the repeat count is negative.
+ const bool forever = repeat < 0;
+ for (int i = 0; forever || i != repeat; i++) {
+ // We want to preserve failures generated by ad-hoc test
+ // assertions executed before RUN_ALL_TESTS().
+ ClearNonAdHocTestResult();
+
+ const TimeInMillis start = GetTimeInMillis();
+
+ // Shuffles test cases and tests if requested.
+ if (has_tests_to_run && GTEST_FLAG(shuffle)) {
+ random()->Reseed(random_seed_);
+ // This should be done before calling OnTestIterationStart(),
+ // such that a test event listener can see the actual test order
+ // in the event.
+ ShuffleTests();
+ }
+
+ // Tells the unit test event listeners that the tests are about to start.
+ repeater->OnTestIterationStart(*parent_, i);
+
+ // Runs each test case if there is at least one test to run.
+ if (has_tests_to_run) {
+ // Sets up all environments beforehand.
+ repeater->OnEnvironmentsSetUpStart(*parent_);
+ ForEach(environments_, SetUpEnvironment);
+ repeater->OnEnvironmentsSetUpEnd(*parent_);
+
+ // Runs the tests only if there was no fatal failure during global
+ // set-up.
+ if (!Test::HasFatalFailure()) {
+ for (int test_index = 0; test_index < total_test_case_count();
+ test_index++) {
+ GetMutableTestCase(test_index)->Run();
+ }
+ }
+
+ // Tears down all environments in reverse order afterwards.
+ repeater->OnEnvironmentsTearDownStart(*parent_);
+ std::for_each(environments_.rbegin(), environments_.rend(),
+ TearDownEnvironment);
+ repeater->OnEnvironmentsTearDownEnd(*parent_);
+ }
+
+ elapsed_time_ = GetTimeInMillis() - start;
+
+ // Tells the unit test event listener that the tests have just finished.
+ repeater->OnTestIterationEnd(*parent_, i);
+
+ // Gets the result and clears it.
+ if (!Passed()) {
+ failed = true;
+ }
+
+ // Restores the original test order after the iteration. This
+ // allows the user to quickly repro a failure that happens in the
+ // N-th iteration without repeating the first (N - 1) iterations.
+ // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in
+ // case the user somehow changes the value of the flag somewhere
+ // (it's always safe to unshuffle the tests).
+ UnshuffleTests();
+
+ if (GTEST_FLAG(shuffle)) {
+ // Picks a new random seed for each iteration.
+ random_seed_ = GetNextRandomSeed(random_seed_);
+ }
+ }
+
+ repeater->OnTestProgramEnd(*parent_);
+
+ if (!gtest_is_initialized_before_run_all_tests) {
+ ColoredPrintf(
+ COLOR_RED,
+ "\nIMPORTANT NOTICE - DO NOT IGNORE:\n"
+ "This test program did NOT call " GTEST_INIT_GOOGLE_TEST_NAME_
+ "() before calling RUN_ALL_TESTS(). This is INVALID. Soon " GTEST_NAME_
+ " will start to enforce the valid usage. "
+ "Please fix it ASAP, or IT WILL START TO FAIL.\n"); // NOLINT
+#if GTEST_FOR_GOOGLE_
+ ColoredPrintf(COLOR_RED,
+ "For more details, see http://wiki/Main/ValidGUnitMain.\n");
+#endif // GTEST_FOR_GOOGLE_
+ }
+
+ return !failed;
+}
+
+// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file
+// if the variable is present. If a file already exists at this location, this
+// function will write over it. If the variable is present, but the file cannot
+// be created, prints an error and exits.
+void WriteToShardStatusFileIfNeeded() {
+ const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile);
+ if (test_shard_file != NULL) {
+ FILE* const file = posix::FOpen(test_shard_file, "w");
+ if (file == NULL) {
+ ColoredPrintf(COLOR_RED,
+ "Could not write to the test shard status file \"%s\" "
+ "specified by the %s environment variable.\n",
+ test_shard_file, kTestShardStatusFile);
+ fflush(stdout);
+ exit(EXIT_FAILURE);
+ }
+ fclose(file);
+ }
+}
+
+// Checks whether sharding is enabled by examining the relevant
+// environment variable values. If the variables are present,
+// but inconsistent (i.e., shard_index >= total_shards), prints
+// an error and exits. If in_subprocess_for_death_test, sharding is
+// disabled because it must only be applied to the original test
+// process. Otherwise, we could filter out death tests we intended to execute.
+bool ShouldShard(const char* total_shards_env,
+ const char* shard_index_env,
+ bool in_subprocess_for_death_test) {
+ if (in_subprocess_for_death_test) {
+ return false;
+ }
+
+ const Int32 total_shards = Int32FromEnvOrDie(total_shards_env, -1);
+ const Int32 shard_index = Int32FromEnvOrDie(shard_index_env, -1);
+
+ if (total_shards == -1 && shard_index == -1) {
+ return false;
+ } else if (total_shards == -1 && shard_index != -1) {
+ const Message msg = Message()
+ << "Invalid environment variables: you have "
+ << kTestShardIndex << " = " << shard_index
+ << ", but have left " << kTestTotalShards << " unset.\n";
+ ColoredPrintf(COLOR_RED, msg.GetString().c_str());
+ fflush(stdout);
+ exit(EXIT_FAILURE);
+ } else if (total_shards != -1 && shard_index == -1) {
+ const Message msg = Message()
+ << "Invalid environment variables: you have "
+ << kTestTotalShards << " = " << total_shards
+ << ", but have left " << kTestShardIndex << " unset.\n";
+ ColoredPrintf(COLOR_RED, msg.GetString().c_str());
+ fflush(stdout);
+ exit(EXIT_FAILURE);
+ } else if (shard_index < 0 || shard_index >= total_shards) {
+ const Message msg = Message()
+ << "Invalid environment variables: we require 0 <= "
+ << kTestShardIndex << " < " << kTestTotalShards
+ << ", but you have " << kTestShardIndex << "=" << shard_index
+ << ", " << kTestTotalShards << "=" << total_shards << ".\n";
+ ColoredPrintf(COLOR_RED, msg.GetString().c_str());
+ fflush(stdout);
+ exit(EXIT_FAILURE);
+ }
+
+ return total_shards > 1;
+}
+
+// Parses the environment variable var as an Int32. If it is unset,
+// returns default_val. If it is not an Int32, prints an error
+// and aborts.
+Int32 Int32FromEnvOrDie(const char* var, Int32 default_val) {
+ const char* str_val = posix::GetEnv(var);
+ if (str_val == NULL) {
+ return default_val;
+ }
+
+ Int32 result;
+ if (!ParseInt32(Message() << "The value of environment variable " << var,
+ str_val, &result)) {
+ exit(EXIT_FAILURE);
+ }
+ return result;
+}
+
+// Given the total number of shards, the shard index, and the test id,
+// returns true iff the test should be run on this shard. The test id is
+// some arbitrary but unique non-negative integer assigned to each test
+// method. Assumes that 0 <= shard_index < total_shards.
+bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) {
+ return (test_id % total_shards) == shard_index;
+}
+
+// Compares the name of each test with the user-specified filter to
+// decide whether the test should be run, then records the result in
+// each TestCase and TestInfo object.
+// If shard_tests == true, further filters tests based on sharding
+// variables in the environment - see
+// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md
+// . Returns the number of tests that should run.
+int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) {
+ const Int32 total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ?
+ Int32FromEnvOrDie(kTestTotalShards, -1) : -1;
+ const Int32 shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ?
+ Int32FromEnvOrDie(kTestShardIndex, -1) : -1;
+
+ // num_runnable_tests are the number of tests that will
+ // run across all shards (i.e., match filter and are not disabled).
+ // num_selected_tests are the number of tests to be run on
+ // this shard.
+ int num_runnable_tests = 0;
+ int num_selected_tests = 0;
+ for (size_t i = 0; i < test_cases_.size(); i++) {
+ TestCase* const test_case = test_cases_[i];
+ const std::string &test_case_name = test_case->name();
+ test_case->set_should_run(false);
+
+ for (size_t j = 0; j < test_case->test_info_list().size(); j++) {
+ TestInfo* const test_info = test_case->test_info_list()[j];
+ const std::string test_name(test_info->name());
+ // A test is disabled if test case name or test name matches
+ // kDisableTestFilter.
+ const bool is_disabled =
+ internal::UnitTestOptions::MatchesFilter(test_case_name,
+ kDisableTestFilter) ||
+ internal::UnitTestOptions::MatchesFilter(test_name,
+ kDisableTestFilter);
+ test_info->is_disabled_ = is_disabled;
+
+ const bool matches_filter =
+ internal::UnitTestOptions::FilterMatchesTest(test_case_name,
+ test_name);
+ test_info->matches_filter_ = matches_filter;
+
+ const bool is_runnable =
+ (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) &&
+ matches_filter;
+
+ const bool is_in_another_shard =
+ shard_tests != IGNORE_SHARDING_PROTOCOL &&
+ !ShouldRunTestOnShard(total_shards, shard_index, num_runnable_tests);
+ test_info->is_in_another_shard_ = is_in_another_shard;
+ const bool is_selected = is_runnable && !is_in_another_shard;
+
+ num_runnable_tests += is_runnable;
+ num_selected_tests += is_selected;
+
+ test_info->should_run_ = is_selected;
+ test_case->set_should_run(test_case->should_run() || is_selected);
+ }
+ }
+ return num_selected_tests;
+}
+
+// Prints the given C-string on a single line by replacing all '\n'
+// characters with string "\\n". If the output takes more than
+// max_length characters, only prints the first max_length characters
+// and "...".
+static void PrintOnOneLine(const char* str, int max_length) {
+ if (str != NULL) {
+ for (int i = 0; *str != '\0'; ++str) {
+ if (i >= max_length) {
+ printf("...");
+ break;
+ }
+ if (*str == '\n') {
+ printf("\\n");
+ i += 2;
+ } else {
+ printf("%c", *str);
+ ++i;
+ }
+ }
+ }
+}
+
+// Prints the names of the tests matching the user-specified filter flag.
+void UnitTestImpl::ListTestsMatchingFilter() {
+ // Print at most this many characters for each type/value parameter.
+ const int kMaxParamLength = 250;
+
+ for (size_t i = 0; i < test_cases_.size(); i++) {
+ const TestCase* const test_case = test_cases_[i];
+ bool printed_test_case_name = false;
+
+ for (size_t j = 0; j < test_case->test_info_list().size(); j++) {
+ const TestInfo* const test_info =
+ test_case->test_info_list()[j];
+ if (test_info->matches_filter_) {
+ if (!printed_test_case_name) {
+ printed_test_case_name = true;
+ printf("%s.", test_case->name());
+ if (test_case->type_param() != NULL) {
+ printf(" # %s = ", kTypeParamLabel);
+ // We print the type parameter on a single line to make
+ // the output easy to parse by a program.
+ PrintOnOneLine(test_case->type_param(), kMaxParamLength);
+ }
+ printf("\n");
+ }
+ printf(" %s", test_info->name());
+ if (test_info->value_param() != NULL) {
+ printf(" # %s = ", kValueParamLabel);
+ // We print the value parameter on a single line to make the
+ // output easy to parse by a program.
+ PrintOnOneLine(test_info->value_param(), kMaxParamLength);
+ }
+ printf("\n");
+ }
+ }
+ }
+ fflush(stdout);
+ const std::string& output_format = UnitTestOptions::GetOutputFormat();
+ if (output_format == "xml" || output_format == "json") {
+ FILE* fileout = OpenFileForWriting(
+ UnitTestOptions::GetAbsolutePathToOutputFile().c_str());
+ std::stringstream stream;
+ if (output_format == "xml") {
+ XmlUnitTestResultPrinter(
+ UnitTestOptions::GetAbsolutePathToOutputFile().c_str())
+ .PrintXmlTestsList(&stream, test_cases_);
+ } else if (output_format == "json") {
+ JsonUnitTestResultPrinter(
+ UnitTestOptions::GetAbsolutePathToOutputFile().c_str())
+ .PrintJsonTestList(&stream, test_cases_);
+ }
+ fprintf(fileout, "%s", StringStreamToString(&stream).c_str());
+ fclose(fileout);
+ }
+}
+
+// Sets the OS stack trace getter.
+//
+// Does nothing if the input and the current OS stack trace getter are
+// the same; otherwise, deletes the old getter and makes the input the
+// current getter.
+void UnitTestImpl::set_os_stack_trace_getter(
+ OsStackTraceGetterInterface* getter) {
+ if (os_stack_trace_getter_ != getter) {
+ delete os_stack_trace_getter_;
+ os_stack_trace_getter_ = getter;
+ }
+}
+
+// Returns the current OS stack trace getter if it is not NULL;
+// otherwise, creates an OsStackTraceGetter, makes it the current
+// getter, and returns it.
+OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() {
+ if (os_stack_trace_getter_ == NULL) {
+#ifdef GTEST_OS_STACK_TRACE_GETTER_
+ os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_;
+#else
+ os_stack_trace_getter_ = new OsStackTraceGetter;
+#endif // GTEST_OS_STACK_TRACE_GETTER_
+ }
+
+ return os_stack_trace_getter_;
+}
+
+// Returns the most specific TestResult currently running.
+TestResult* UnitTestImpl::current_test_result() {
+ if (current_test_info_ != NULL) {
+ return &current_test_info_->result_;
+ }
+ if (current_test_case_ != NULL) {
+ return &current_test_case_->ad_hoc_test_result_;
+ }
+ return &ad_hoc_test_result_;
+}
+
+// Shuffles all test cases, and the tests within each test case,
+// making sure that death tests are still run first.
+void UnitTestImpl::ShuffleTests() {
+ // Shuffles the death test cases.
+ ShuffleRange(random(), 0, last_death_test_case_ + 1, &test_case_indices_);
+
+ // Shuffles the non-death test cases.
+ ShuffleRange(random(), last_death_test_case_ + 1,
+ static_cast<int>(test_cases_.size()), &test_case_indices_);
+
+ // Shuffles the tests inside each test case.
+ for (size_t i = 0; i < test_cases_.size(); i++) {
+ test_cases_[i]->ShuffleTests(random());
+ }
+}
+
+// Restores the test cases and tests to their order before the first shuffle.
+void UnitTestImpl::UnshuffleTests() {
+ for (size_t i = 0; i < test_cases_.size(); i++) {
+ // Unshuffles the tests in each test case.
+ test_cases_[i]->UnshuffleTests();
+ // Resets the index of each test case.
+ test_case_indices_[i] = static_cast<int>(i);
+ }
+}
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag. The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in
+// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't.
+std::string GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/,
+ int skip_count) {
+ // We pass skip_count + 1 to skip this wrapper function in addition
+ // to what the user really wants to skip.
+ return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1);
+}
+
+// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to
+// suppress unreachable code warnings.
+namespace {
+class ClassUniqueToAlwaysTrue {};
+}
+
+bool IsTrue(bool condition) { return condition; }
+
+bool AlwaysTrue() {
+#if GTEST_HAS_EXCEPTIONS
+ // This condition is always false so AlwaysTrue() never actually throws,
+ // but it makes the compiler think that it may throw.
+ if (IsTrue(false))
+ throw ClassUniqueToAlwaysTrue();
+#endif // GTEST_HAS_EXCEPTIONS
+ return true;
+}
+
+// If *pstr starts with the given prefix, modifies *pstr to be right
+// past the prefix and returns true; otherwise leaves *pstr unchanged
+// and returns false. None of pstr, *pstr, and prefix can be NULL.
+bool SkipPrefix(const char* prefix, const char** pstr) {
+ const size_t prefix_len = strlen(prefix);
+ if (strncmp(*pstr, prefix, prefix_len) == 0) {
+ *pstr += prefix_len;
+ return true;
+ }
+ return false;
+}
+
+// Parses a string as a command line flag. The string should have
+// the format "--flag=value". When def_optional is true, the "=value"
+// part can be omitted.
+//
+// Returns the value of the flag, or NULL if the parsing failed.
+static const char* ParseFlagValue(const char* str, const char* flag,
+ bool def_optional) {
+ // str and flag must not be NULL.
+ if (str == NULL || flag == NULL) return NULL;
+
+ // The flag must start with "--" followed by GTEST_FLAG_PREFIX_.
+ const std::string flag_str = std::string("--") + GTEST_FLAG_PREFIX_ + flag;
+ const size_t flag_len = flag_str.length();
+ if (strncmp(str, flag_str.c_str(), flag_len) != 0) return NULL;
+
+ // Skips the flag name.
+ const char* flag_end = str + flag_len;
+
+ // When def_optional is true, it's OK to not have a "=value" part.
+ if (def_optional && (flag_end[0] == '\0')) {
+ return flag_end;
+ }
+
+ // If def_optional is true and there are more characters after the
+ // flag name, or if def_optional is false, there must be a '=' after
+ // the flag name.
+ if (flag_end[0] != '=') return NULL;
+
+ // Returns the string after "=".
+ return flag_end + 1;
+}
+
+// Parses a string for a bool flag, in the form of either
+// "--flag=value" or "--flag".
+//
+// In the former case, the value is taken as true as long as it does
+// not start with '0', 'f', or 'F'.
+//
+// In the latter case, the value is taken as true.
+//
+// On success, stores the value of the flag in *value, and returns
+// true. On failure, returns false without changing *value.
+static bool ParseBoolFlag(const char* str, const char* flag, bool* value) {
+ // Gets the value of the flag as a string.
+ const char* const value_str = ParseFlagValue(str, flag, true);
+
+ // Aborts if the parsing failed.
+ if (value_str == NULL) return false;
+
+ // Converts the string value to a bool.
+ *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F');
+ return true;
+}
+
+// Parses a string for an Int32 flag, in the form of
+// "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true. On failure, returns false without changing *value.
+bool ParseInt32Flag(const char* str, const char* flag, Int32* value) {
+ // Gets the value of the flag as a string.
+ const char* const value_str = ParseFlagValue(str, flag, false);
+
+ // Aborts if the parsing failed.
+ if (value_str == NULL) return false;
+
+ // Sets *value to the value of the flag.
+ return ParseInt32(Message() << "The value of flag --" << flag,
+ value_str, value);
+}
+
+// Parses a string for a string flag, in the form of
+// "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true. On failure, returns false without changing *value.
+template <typename String>
+static bool ParseStringFlag(const char* str, const char* flag, String* value) {
+ // Gets the value of the flag as a string.
+ const char* const value_str = ParseFlagValue(str, flag, false);
+
+ // Aborts if the parsing failed.
+ if (value_str == NULL) return false;
+
+ // Sets *value to the value of the flag.
+ *value = value_str;
+ return true;
+}
+
+// Determines whether a string has a prefix that Google Test uses for its
+// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_.
+// If Google Test detects that a command line flag has its prefix but is not
+// recognized, it will print its help message. Flags starting with
+// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test
+// internal flags and do not trigger the help message.
+static bool HasGoogleTestFlagPrefix(const char* str) {
+ return (SkipPrefix("--", &str) ||
+ SkipPrefix("-", &str) ||
+ SkipPrefix("/", &str)) &&
+ !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) &&
+ (SkipPrefix(GTEST_FLAG_PREFIX_, &str) ||
+ SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str));
+}
+
+// Prints a string containing code-encoded text. The following escape
+// sequences can be used in the string to control the text color:
+//
+// @@ prints a single '@' character.
+// @R changes the color to red.
+// @G changes the color to green.
+// @Y changes the color to yellow.
+// @D changes to the default terminal text color.
+//
+// FIXME: Write tests for this once we add stdout
+// capturing to Google Test.
+static void PrintColorEncoded(const char* str) {
+ GTestColor color = COLOR_DEFAULT; // The current color.
+
+ // Conceptually, we split the string into segments divided by escape
+ // sequences. Then we print one segment at a time. At the end of
+ // each iteration, the str pointer advances to the beginning of the
+ // next segment.
+ for (;;) {
+ const char* p = strchr(str, '@');
+ if (p == NULL) {
+ ColoredPrintf(color, "%s", str);
+ return;
+ }
+
+ ColoredPrintf(color, "%s", std::string(str, p).c_str());
+
+ const char ch = p[1];
+ str = p + 2;
+ if (ch == '@') {
+ ColoredPrintf(color, "@");
+ } else if (ch == 'D') {
+ color = COLOR_DEFAULT;
+ } else if (ch == 'R') {
+ color = COLOR_RED;
+ } else if (ch == 'G') {
+ color = COLOR_GREEN;
+ } else if (ch == 'Y') {
+ color = COLOR_YELLOW;
+ } else {
+ --str;
+ }
+ }
+}
+
+static const char kColorEncodedHelpMessage[] =
+"This program contains tests written using " GTEST_NAME_ ". You can use the\n"
+"following command line flags to control its behavior:\n"
+"\n"
+"Test Selection:\n"
+" @G--" GTEST_FLAG_PREFIX_ "list_tests@D\n"
+" List the names of all tests instead of running them. The name of\n"
+" TEST(Foo, Bar) is \"Foo.Bar\".\n"
+" @G--" GTEST_FLAG_PREFIX_ "filter=@YPOSTIVE_PATTERNS"
+ "[@G-@YNEGATIVE_PATTERNS]@D\n"
+" Run only the tests whose name matches one of the positive patterns but\n"
+" none of the negative patterns. '?' matches any single character; '*'\n"
+" matches any substring; ':' separates two patterns.\n"
+" @G--" GTEST_FLAG_PREFIX_ "also_run_disabled_tests@D\n"
+" Run all disabled tests too.\n"
+"\n"
+"Test Execution:\n"
+" @G--" GTEST_FLAG_PREFIX_ "repeat=@Y[COUNT]@D\n"
+" Run the tests repeatedly; use a negative count to repeat forever.\n"
+" @G--" GTEST_FLAG_PREFIX_ "shuffle@D\n"
+" Randomize tests' orders on every iteration.\n"
+" @G--" GTEST_FLAG_PREFIX_ "random_seed=@Y[NUMBER]@D\n"
+" Random number seed to use for shuffling test orders (between 1 and\n"
+" 99999, or 0 to use a seed based on the current time).\n"
+"\n"
+"Test Output:\n"
+" @G--" GTEST_FLAG_PREFIX_ "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n"
+" Enable/disable colored output. The default is @Gauto@D.\n"
+" -@G-" GTEST_FLAG_PREFIX_ "print_time=0@D\n"
+" Don't print the elapsed time of each test.\n"
+" @G--" GTEST_FLAG_PREFIX_ "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G"
+ GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n"
+" Generate a JSON or XML report in the given directory or with the given\n"
+" file name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n"
+# if GTEST_CAN_STREAM_RESULTS_
+" @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n"
+" Stream test results to the given server.\n"
+# endif // GTEST_CAN_STREAM_RESULTS_
+"\n"
+"Assertion Behavior:\n"
+# if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS
+" @G--" GTEST_FLAG_PREFIX_ "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n"
+" Set the default death test style.\n"
+# endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS
+" @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n"
+" Turn assertion failures into debugger break-points.\n"
+" @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n"
+" Turn assertion failures into C++ exceptions for use by an external\n"
+" test framework.\n"
+" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n"
+" Do not report exceptions as test failures. Instead, allow them\n"
+" to crash the program or throw a pop-up (on Windows).\n"
+"\n"
+"Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set "
+ "the corresponding\n"
+"environment variable of a flag (all letters in upper-case). For example, to\n"
+"disable colored text output, you can either specify @G--" GTEST_FLAG_PREFIX_
+ "color=no@D or set\n"
+"the @G" GTEST_FLAG_PREFIX_UPPER_ "COLOR@D environment variable to @Gno@D.\n"
+"\n"
+"For more information, please read the " GTEST_NAME_ " documentation at\n"
+"@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ "\n"
+"(not one in your own code or tests), please report it to\n"
+"@G<" GTEST_DEV_EMAIL_ ">@D.\n";
+
+static bool ParseGoogleTestFlag(const char* const arg) {
+ return ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag,
+ &GTEST_FLAG(also_run_disabled_tests)) ||
+ ParseBoolFlag(arg, kBreakOnFailureFlag,
+ &GTEST_FLAG(break_on_failure)) ||
+ ParseBoolFlag(arg, kCatchExceptionsFlag,
+ &GTEST_FLAG(catch_exceptions)) ||
+ ParseStringFlag(arg, kColorFlag, &GTEST_FLAG(color)) ||
+ ParseStringFlag(arg, kDeathTestStyleFlag,
+ &GTEST_FLAG(death_test_style)) ||
+ ParseBoolFlag(arg, kDeathTestUseFork,
+ &GTEST_FLAG(death_test_use_fork)) ||
+ ParseStringFlag(arg, kFilterFlag, &GTEST_FLAG(filter)) ||
+ ParseStringFlag(arg, kInternalRunDeathTestFlag,
+ &GTEST_FLAG(internal_run_death_test)) ||
+ ParseBoolFlag(arg, kListTestsFlag, &GTEST_FLAG(list_tests)) ||
+ ParseStringFlag(arg, kOutputFlag, &GTEST_FLAG(output)) ||
+ ParseBoolFlag(arg, kPrintTimeFlag, &GTEST_FLAG(print_time)) ||
+ ParseBoolFlag(arg, kPrintUTF8Flag, &GTEST_FLAG(print_utf8)) ||
+ ParseInt32Flag(arg, kRandomSeedFlag, &GTEST_FLAG(random_seed)) ||
+ ParseInt32Flag(arg, kRepeatFlag, &GTEST_FLAG(repeat)) ||
+ ParseBoolFlag(arg, kShuffleFlag, &GTEST_FLAG(shuffle)) ||
+ ParseInt32Flag(arg, kStackTraceDepthFlag,
+ &GTEST_FLAG(stack_trace_depth)) ||
+ ParseStringFlag(arg, kStreamResultToFlag,
+ &GTEST_FLAG(stream_result_to)) ||
+ ParseBoolFlag(arg, kThrowOnFailureFlag,
+ &GTEST_FLAG(throw_on_failure));
+}
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+static void LoadFlagsFromFile(const std::string& path) {
+ FILE* flagfile = posix::FOpen(path.c_str(), "r");
+ if (!flagfile) {
+ GTEST_LOG_(FATAL) << "Unable to open file \"" << GTEST_FLAG(flagfile)
+ << "\"";
+ }
+ std::string contents(ReadEntireFile(flagfile));
+ posix::FClose(flagfile);
+ std::vector<std::string> lines;
+ SplitString(contents, '\n', &lines);
+ for (size_t i = 0; i < lines.size(); ++i) {
+ if (lines[i].empty())
+ continue;
+ if (!ParseGoogleTestFlag(lines[i].c_str()))
+ g_help_flag = true;
+ }
+}
+#endif // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test. The type parameter CharType can be
+// instantiated to either char or wchar_t.
+template <typename CharType>
+void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) {
+ for (int i = 1; i < *argc; i++) {
+ const std::string arg_string = StreamableToString(argv[i]);
+ const char* const arg = arg_string.c_str();
+
+ using internal::ParseBoolFlag;
+ using internal::ParseInt32Flag;
+ using internal::ParseStringFlag;
+
+ bool remove_flag = false;
+ if (ParseGoogleTestFlag(arg)) {
+ remove_flag = true;
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+ } else if (ParseStringFlag(arg, kFlagfileFlag, &GTEST_FLAG(flagfile))) {
+ LoadFlagsFromFile(GTEST_FLAG(flagfile));
+ remove_flag = true;
+#endif // GTEST_USE_OWN_FLAGFILE_FLAG_
+ } else if (arg_string == "--help" || arg_string == "-h" ||
+ arg_string == "-?" || arg_string == "/?" ||
+ HasGoogleTestFlagPrefix(arg)) {
+ // Both help flag and unrecognized Google Test flags (excluding
+ // internal ones) trigger help display.
+ g_help_flag = true;
+ }
+
+ if (remove_flag) {
+ // Shift the remainder of the argv list left by one. Note
+ // that argv has (*argc + 1) elements, the last one always being
+ // NULL. The following loop moves the trailing NULL element as
+ // well.
+ for (int j = i; j != *argc; j++) {
+ argv[j] = argv[j + 1];
+ }
+
+ // Decrements the argument count.
+ (*argc)--;
+
+ // We also need to decrement the iterator as we just removed
+ // an element.
+ i--;
+ }
+ }
+
+ if (g_help_flag) {
+ // We print the help here instead of in RUN_ALL_TESTS(), as the
+ // latter may not be called at all if the user is using Google
+ // Test with another testing framework.
+ PrintColorEncoded(kColorEncodedHelpMessage);
+ }
+}
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test.
+void ParseGoogleTestFlagsOnly(int* argc, char** argv) {
+ ParseGoogleTestFlagsOnlyImpl(argc, argv);
+
+ // Fix the value of *_NSGetArgc() on macOS, but iff
+ // *_NSGetArgv() == argv
+ // Only applicable to char** version of argv
+#if GTEST_OS_MAC
+#ifndef GTEST_OS_IOS
+ if (*_NSGetArgv() == argv) {
+ *_NSGetArgc() = *argc;
+ }
+#endif
+#endif
+}
+void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) {
+ ParseGoogleTestFlagsOnlyImpl(argc, argv);
+}
+
+// The internal implementation of InitGoogleTest().
+//
+// The type parameter CharType can be instantiated to either char or
+// wchar_t.
+template <typename CharType>
+void InitGoogleTestImpl(int* argc, CharType** argv) {
+ // We don't want to run the initialization code twice.
+ if (GTestIsInitialized()) return;
+
+ if (*argc <= 0) return;
+
+ g_argvs.clear();
+ for (int i = 0; i != *argc; i++) {
+ g_argvs.push_back(StreamableToString(argv[i]));
+ }
+
+#if GTEST_HAS_ABSL
+ absl::InitializeSymbolizer(g_argvs[0].c_str());
+#endif // GTEST_HAS_ABSL
+
+ ParseGoogleTestFlagsOnly(argc, argv);
+ GetUnitTestImpl()->PostFlagParsingInit();
+}
+
+} // namespace internal
+
+// Initializes Google Test. This must be called before calling
+// RUN_ALL_TESTS(). In particular, it parses a command line for the
+// flags that Google Test recognizes. Whenever a Google Test flag is
+// seen, it is removed from argv, and *argc is decremented.
+//
+// No value is returned. Instead, the Google Test flag variables are
+// updated.
+//
+// Calling the function for the second time has no user-visible effect.
+void InitGoogleTest(int* argc, char** argv) {
+#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+ GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv);
+#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+ internal::InitGoogleTestImpl(argc, argv);
+#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+}
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+void InitGoogleTest(int* argc, wchar_t** argv) {
+#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+ GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv);
+#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+ internal::InitGoogleTestImpl(argc, argv);
+#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+}
+
+std::string TempDir() {
+#if defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_)
+ return GTEST_CUSTOM_TEMPDIR_FUNCTION_();
+#endif
+
+#if GTEST_OS_WINDOWS_MOBILE
+ return "\\temp\\";
+#elif GTEST_OS_WINDOWS
+ const char* temp_dir = internal::posix::GetEnv("TEMP");
+ if (temp_dir == NULL || temp_dir[0] == '\0')
+ return "\\temp\\";
+ else if (temp_dir[strlen(temp_dir) - 1] == '\\')
+ return temp_dir;
+ else
+ return std::string(temp_dir) + "\\";
+#elif GTEST_OS_LINUX_ANDROID
+ return "/sdcard/";
+#else
+ return "/tmp/";
+#endif // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Class ScopedTrace
+
+// Pushes the given source file location and message onto a per-thread
+// trace stack maintained by Google Test.
+void ScopedTrace::PushTrace(const char* file, int line, std::string message) {
+ internal::TraceInfo trace;
+ trace.file = file;
+ trace.line = line;
+ trace.message.swap(message);
+
+ UnitTest::GetInstance()->PushGTestTrace(trace);
+}
+
+// Pops the info pushed by the c'tor.
+ScopedTrace::~ScopedTrace()
+ GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) {
+ UnitTest::GetInstance()->PopGTestTrace();
+}
+
+} // namespace testing
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// This file implements death tests.
+
+
+#if GTEST_HAS_DEATH_TEST
+
+# if GTEST_OS_MAC
+# include <crt_externs.h>
+# endif // GTEST_OS_MAC
+
+# include <errno.h>
+# include <fcntl.h>
+# include <limits.h>
+
+# if GTEST_OS_LINUX
+# include <signal.h>
+# endif // GTEST_OS_LINUX
+
+# include <stdarg.h>
+
+# if GTEST_OS_WINDOWS
+# include <windows.h>
+# else
+# include <sys/mman.h>
+# include <sys/wait.h>
+# endif // GTEST_OS_WINDOWS
+
+# if GTEST_OS_QNX
+# include <spawn.h>
+# endif // GTEST_OS_QNX
+
+# if GTEST_OS_FUCHSIA
+# include <lib/fdio/io.h>
+# include <lib/fdio/spawn.h>
+# include <zircon/processargs.h>
+# include <zircon/syscalls.h>
+# include <zircon/syscalls/port.h>
+# endif // GTEST_OS_FUCHSIA
+
+#endif // GTEST_HAS_DEATH_TEST
+
+
+namespace testing {
+
+// Constants.
+
+// The default death test style.
+//
+// This is defined in internal/gtest-port.h as "fast", but can be overridden by
+// a definition in internal/custom/gtest-port.h. The recommended value, which is
+// used internally at Google, is "threadsafe".
+static const char kDefaultDeathTestStyle[] = GTEST_DEFAULT_DEATH_TEST_STYLE;
+
+GTEST_DEFINE_string_(
+ death_test_style,
+ internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle),
+ "Indicates how to run a death test in a forked child process: "
+ "\"threadsafe\" (child process re-executes the test binary "
+ "from the beginning, running only the specific death test) or "
+ "\"fast\" (child process runs the death test immediately "
+ "after forking).");
+
+GTEST_DEFINE_bool_(
+ death_test_use_fork,
+ internal::BoolFromGTestEnv("death_test_use_fork", false),
+ "Instructs to use fork()/_exit() instead of clone() in death tests. "
+ "Ignored and always uses fork() on POSIX systems where clone() is not "
+ "implemented. Useful when running under valgrind or similar tools if "
+ "those do not support clone(). Valgrind 3.3.1 will just fail if "
+ "it sees an unsupported combination of clone() flags. "
+ "It is not recommended to use this flag w/o valgrind though it will "
+ "work in 99% of the cases. Once valgrind is fixed, this flag will "
+ "most likely be removed.");
+
+namespace internal {
+GTEST_DEFINE_string_(
+ internal_run_death_test, "",
+ "Indicates the file, line number, temporal index of "
+ "the single death test to run, and a file descriptor to "
+ "which a success code may be sent, all separated by "
+ "the '|' characters. This flag is specified if and only if the current "
+ "process is a sub-process launched for running a thread-safe "
+ "death test. FOR INTERNAL USE ONLY.");
+} // namespace internal
+
+#if GTEST_HAS_DEATH_TEST
+
+namespace internal {
+
+// Valid only for fast death tests. Indicates the code is running in the
+// child process of a fast style death test.
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+static bool g_in_fast_death_test_child = false;
+# endif
+
+// Returns a Boolean value indicating whether the caller is currently
+// executing in the context of the death test child process. Tools such as
+// Valgrind heap checkers may need this to modify their behavior in death
+// tests. IMPORTANT: This is an internal utility. Using it may break the
+// implementation of death tests. User code MUST NOT use it.
+bool InDeathTestChild() {
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+ // On Windows and Fuchsia, death tests are thread-safe regardless of the value
+ // of the death_test_style flag.
+ return !GTEST_FLAG(internal_run_death_test).empty();
+
+# else
+
+ if (GTEST_FLAG(death_test_style) == "threadsafe")
+ return !GTEST_FLAG(internal_run_death_test).empty();
+ else
+ return g_in_fast_death_test_child;
+#endif
+}
+
+} // namespace internal
+
+// ExitedWithCode constructor.
+ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) {
+}
+
+// ExitedWithCode function-call operator.
+bool ExitedWithCode::operator()(int exit_status) const {
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+ return exit_status == exit_code_;
+
+# else
+
+ return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_;
+
+# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+}
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// KilledBySignal constructor.
+KilledBySignal::KilledBySignal(int signum) : signum_(signum) {
+}
+
+// KilledBySignal function-call operator.
+bool KilledBySignal::operator()(int exit_status) const {
+# if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_)
+ {
+ bool result;
+ if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) {
+ return result;
+ }
+ }
+# endif // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_)
+ return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_;
+}
+# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+
+namespace internal {
+
+// Utilities needed for death tests.
+
+// Generates a textual description of a given exit code, in the format
+// specified by wait(2).
+static std::string ExitSummary(int exit_code) {
+ Message m;
+
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+ m << "Exited with exit status " << exit_code;
+
+# else
+
+ if (WIFEXITED(exit_code)) {
+ m << "Exited with exit status " << WEXITSTATUS(exit_code);
+ } else if (WIFSIGNALED(exit_code)) {
+ m << "Terminated by signal " << WTERMSIG(exit_code);
+ }
+# ifdef WCOREDUMP
+ if (WCOREDUMP(exit_code)) {
+ m << " (core dumped)";
+ }
+# endif
+# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+ return m.GetString();
+}
+
+// Returns true if exit_status describes a process that was terminated
+// by a signal, or exited normally with a nonzero exit code.
+bool ExitedUnsuccessfully(int exit_status) {
+ return !ExitedWithCode(0)(exit_status);
+}
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// Generates a textual failure message when a death test finds more than
+// one thread running, or cannot determine the number of threads, prior
+// to executing the given statement. It is the responsibility of the
+// caller not to pass a thread_count of 1.
+static std::string DeathTestThreadWarning(size_t thread_count) {
+ Message msg;
+ msg << "Death tests use fork(), which is unsafe particularly"
+ << " in a threaded context. For this test, " << GTEST_NAME_ << " ";
+ if (thread_count == 0) {
+ msg << "couldn't detect the number of threads.";
+ } else {
+ msg << "detected " << thread_count << " threads.";
+ }
+ msg << " See "
+ "https://github.com/google/googletest/blob/master/googletest/docs/"
+ "advanced.md#death-tests-and-threads"
+ << " for more explanation and suggested solutions, especially if"
+ << " this is the last message you see before your test times out.";
+ return msg.GetString();
+}
+# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+
+// Flag characters for reporting a death test that did not die.
+static const char kDeathTestLived = 'L';
+static const char kDeathTestReturned = 'R';
+static const char kDeathTestThrew = 'T';
+static const char kDeathTestInternalError = 'I';
+
+#if GTEST_OS_FUCHSIA
+
+// File descriptor used for the pipe in the child process.
+static const int kFuchsiaReadPipeFd = 3;
+
+#endif
+
+// An enumeration describing all of the possible ways that a death test can
+// conclude. DIED means that the process died while executing the test
+// code; LIVED means that process lived beyond the end of the test code;
+// RETURNED means that the test statement attempted to execute a return
+// statement, which is not allowed; THREW means that the test statement
+// returned control by throwing an exception. IN_PROGRESS means the test
+// has not yet concluded.
+// FIXME: Unify names and possibly values for
+// AbortReason, DeathTestOutcome, and flag characters above.
+enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW };
+
+// Routine for aborting the program which is safe to call from an
+// exec-style death test child process, in which case the error
+// message is propagated back to the parent process. Otherwise, the
+// message is simply printed to stderr. In either case, the program
+// then exits with status 1.
+static void DeathTestAbort(const std::string& message) {
+ // On a POSIX system, this function may be called from a threadsafe-style
+ // death test child process, which operates on a very small stack. Use
+ // the heap for any additional non-minuscule memory requirements.
+ const InternalRunDeathTestFlag* const flag =
+ GetUnitTestImpl()->internal_run_death_test_flag();
+ if (flag != NULL) {
+ FILE* parent = posix::FDOpen(flag->write_fd(), "w");
+ fputc(kDeathTestInternalError, parent);
+ fprintf(parent, "%s", message.c_str());
+ fflush(parent);
+ _exit(1);
+ } else {
+ fprintf(stderr, "%s", message.c_str());
+ fflush(stderr);
+ posix::Abort();
+ }
+}
+
+// A replacement for CHECK that calls DeathTestAbort if the assertion
+// fails.
+# define GTEST_DEATH_TEST_CHECK_(expression) \
+ do { \
+ if (!::testing::internal::IsTrue(expression)) { \
+ DeathTestAbort( \
+ ::std::string("CHECK failed: File ") + __FILE__ + ", line " \
+ + ::testing::internal::StreamableToString(__LINE__) + ": " \
+ + #expression); \
+ } \
+ } while (::testing::internal::AlwaysFalse())
+
+// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for
+// evaluating any system call that fulfills two conditions: it must return
+// -1 on failure, and set errno to EINTR when it is interrupted and
+// should be tried again. The macro expands to a loop that repeatedly
+// evaluates the expression as long as it evaluates to -1 and sets
+// errno to EINTR. If the expression evaluates to -1 but errno is
+// something other than EINTR, DeathTestAbort is called.
+# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \
+ do { \
+ int gtest_retval; \
+ do { \
+ gtest_retval = (expression); \
+ } while (gtest_retval == -1 && errno == EINTR); \
+ if (gtest_retval == -1) { \
+ DeathTestAbort( \
+ ::std::string("CHECK failed: File ") + __FILE__ + ", line " \
+ + ::testing::internal::StreamableToString(__LINE__) + ": " \
+ + #expression + " != -1"); \
+ } \
+ } while (::testing::internal::AlwaysFalse())
+
+// Returns the message describing the last system error in errno.
+std::string GetLastErrnoDescription() {
+ return errno == 0 ? "" : posix::StrError(errno);
+}
+
+// This is called from a death test parent process to read a failure
+// message from the death test child process and log it with the FATAL
+// severity. On Windows, the message is read from a pipe handle. On other
+// platforms, it is read from a file descriptor.
+static void FailFromInternalError(int fd) {
+ Message error;
+ char buffer[256];
+ int num_read;
+
+ do {
+ while ((num_read = posix::Read(fd, buffer, 255)) > 0) {
+ buffer[num_read] = '\0';
+ error << buffer;
+ }
+ } while (num_read == -1 && errno == EINTR);
+
+ if (num_read == 0) {
+ GTEST_LOG_(FATAL) << error.GetString();
+ } else {
+ const int last_error = errno;
+ GTEST_LOG_(FATAL) << "Error while reading death test internal: "
+ << GetLastErrnoDescription() << " [" << last_error << "]";
+ }
+}
+
+// Death test constructor. Increments the running death test count
+// for the current test.
+DeathTest::DeathTest() {
+ TestInfo* const info = GetUnitTestImpl()->current_test_info();
+ if (info == NULL) {
+ DeathTestAbort("Cannot run a death test outside of a TEST or "
+ "TEST_F construct");
+ }
+}
+
+// Creates and returns a death test by dispatching to the current
+// death test factory.
+bool DeathTest::Create(const char* statement, const RE* regex,
+ const char* file, int line, DeathTest** test) {
+ return GetUnitTestImpl()->death_test_factory()->Create(
+ statement, regex, file, line, test);
+}
+
+const char* DeathTest::LastMessage() {
+ return last_death_test_message_.c_str();
+}
+
+void DeathTest::set_last_death_test_message(const std::string& message) {
+ last_death_test_message_ = message;
+}
+
+std::string DeathTest::last_death_test_message_;
+
+// Provides cross platform implementation for some death functionality.
+class DeathTestImpl : public DeathTest {
+ protected:
+ DeathTestImpl(const char* a_statement, const RE* a_regex)
+ : statement_(a_statement),
+ regex_(a_regex),
+ spawned_(false),
+ status_(-1),
+ outcome_(IN_PROGRESS),
+ read_fd_(-1),
+ write_fd_(-1) {}
+
+ // read_fd_ is expected to be closed and cleared by a derived class.
+ ~DeathTestImpl() { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); }
+
+ void Abort(AbortReason reason);
+ virtual bool Passed(bool status_ok);
+
+ const char* statement() const { return statement_; }
+ const RE* regex() const { return regex_; }
+ bool spawned() const { return spawned_; }
+ void set_spawned(bool is_spawned) { spawned_ = is_spawned; }
+ int status() const { return status_; }
+ void set_status(int a_status) { status_ = a_status; }
+ DeathTestOutcome outcome() const { return outcome_; }
+ void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; }
+ int read_fd() const { return read_fd_; }
+ void set_read_fd(int fd) { read_fd_ = fd; }
+ int write_fd() const { return write_fd_; }
+ void set_write_fd(int fd) { write_fd_ = fd; }
+
+ // Called in the parent process only. Reads the result code of the death
+ // test child process via a pipe, interprets it to set the outcome_
+ // member, and closes read_fd_. Outputs diagnostics and terminates in
+ // case of unexpected codes.
+ void ReadAndInterpretStatusByte();
+
+ private:
+ // The textual content of the code this object is testing. This class
+ // doesn't own this string and should not attempt to delete it.
+ const char* const statement_;
+ // The regular expression which test output must match. DeathTestImpl
+ // doesn't own this object and should not attempt to delete it.
+ const RE* const regex_;
+ // True if the death test child process has been successfully spawned.
+ bool spawned_;
+ // The exit status of the child process.
+ int status_;
+ // How the death test concluded.
+ DeathTestOutcome outcome_;
+ // Descriptor to the read end of the pipe to the child process. It is
+ // always -1 in the child process. The child keeps its write end of the
+ // pipe in write_fd_.
+ int read_fd_;
+ // Descriptor to the child's write end of the pipe to the parent process.
+ // It is always -1 in the parent process. The parent keeps its end of the
+ // pipe in read_fd_.
+ int write_fd_;
+};
+
+// Called in the parent process only. Reads the result code of the death
+// test child process via a pipe, interprets it to set the outcome_
+// member, and closes read_fd_. Outputs diagnostics and terminates in
+// case of unexpected codes.
+void DeathTestImpl::ReadAndInterpretStatusByte() {
+ char flag;
+ int bytes_read;
+
+ // The read() here blocks until data is available (signifying the
+ // failure of the death test) or until the pipe is closed (signifying
+ // its success), so it's okay to call this in the parent before
+ // the child process has exited.
+ do {
+ bytes_read = posix::Read(read_fd(), &flag, 1);
+ } while (bytes_read == -1 && errno == EINTR);
+
+ if (bytes_read == 0) {
+ set_outcome(DIED);
+ } else if (bytes_read == 1) {
+ switch (flag) {
+ case kDeathTestReturned:
+ set_outcome(RETURNED);
+ break;
+ case kDeathTestThrew:
+ set_outcome(THREW);
+ break;
+ case kDeathTestLived:
+ set_outcome(LIVED);
+ break;
+ case kDeathTestInternalError:
+ FailFromInternalError(read_fd()); // Does not return.
+ break;
+ default:
+ GTEST_LOG_(FATAL) << "Death test child process reported "
+ << "unexpected status byte ("
+ << static_cast<unsigned int>(flag) << ")";
+ }
+ } else {
+ GTEST_LOG_(FATAL) << "Read from death test child process failed: "
+ << GetLastErrnoDescription();
+ }
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd()));
+ set_read_fd(-1);
+}
+
+// Signals that the death test code which should have exited, didn't.
+// Should be called only in a death test child process.
+// Writes a status byte to the child's status file descriptor, then
+// calls _exit(1).
+void DeathTestImpl::Abort(AbortReason reason) {
+ // The parent process considers the death test to be a failure if
+ // it finds any data in our pipe. So, here we write a single flag byte
+ // to the pipe, then exit.
+ const char status_ch =
+ reason == TEST_DID_NOT_DIE ? kDeathTestLived :
+ reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned;
+
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1));
+ // We are leaking the descriptor here because on some platforms (i.e.,
+ // when built as Windows DLL), destructors of global objects will still
+ // run after calling _exit(). On such systems, write_fd_ will be
+ // indirectly closed from the destructor of UnitTestImpl, causing double
+ // close if it is also closed here. On debug configurations, double close
+ // may assert. As there are no in-process buffers to flush here, we are
+ // relying on the OS to close the descriptor after the process terminates
+ // when the destructors are not run.
+ _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash)
+}
+
+// Returns an indented copy of stderr output for a death test.
+// This makes distinguishing death test output lines from regular log lines
+// much easier.
+static ::std::string FormatDeathTestOutput(const ::std::string& output) {
+ ::std::string ret;
+ for (size_t at = 0; ; ) {
+ const size_t line_end = output.find('\n', at);
+ ret += "[ DEATH ] ";
+ if (line_end == ::std::string::npos) {
+ ret += output.substr(at);
+ break;
+ }
+ ret += output.substr(at, line_end + 1 - at);
+ at = line_end + 1;
+ }
+ return ret;
+}
+
+// Assesses the success or failure of a death test, using both private
+// members which have previously been set, and one argument:
+//
+// Private data members:
+// outcome: An enumeration describing how the death test
+// concluded: DIED, LIVED, THREW, or RETURNED. The death test
+// fails in the latter three cases.
+// status: The exit status of the child process. On *nix, it is in the
+// in the format specified by wait(2). On Windows, this is the
+// value supplied to the ExitProcess() API or a numeric code
+// of the exception that terminated the program.
+// regex: A regular expression object to be applied to
+// the test's captured standard error output; the death test
+// fails if it does not match.
+//
+// Argument:
+// status_ok: true if exit_status is acceptable in the context of
+// this particular death test, which fails if it is false
+//
+// Returns true iff all of the above conditions are met. Otherwise, the
+// first failing condition, in the order given above, is the one that is
+// reported. Also sets the last death test message string.
+bool DeathTestImpl::Passed(bool status_ok) {
+ if (!spawned())
+ return false;
+
+ const std::string error_message = GetCapturedStderr();
+
+ bool success = false;
+ Message buffer;
+
+ buffer << "Death test: " << statement() << "\n";
+ switch (outcome()) {
+ case LIVED:
+ buffer << " Result: failed to die.\n"
+ << " Error msg:\n" << FormatDeathTestOutput(error_message);
+ break;
+ case THREW:
+ buffer << " Result: threw an exception.\n"
+ << " Error msg:\n" << FormatDeathTestOutput(error_message);
+ break;
+ case RETURNED:
+ buffer << " Result: illegal return in test statement.\n"
+ << " Error msg:\n" << FormatDeathTestOutput(error_message);
+ break;
+ case DIED:
+ if (status_ok) {
+# if GTEST_USES_PCRE
+ // PCRE regexes support embedded NULs.
+ const bool matched = RE::PartialMatch(error_message, *regex());
+# else
+ const bool matched = RE::PartialMatch(error_message.c_str(), *regex());
+# endif // GTEST_USES_PCRE
+ if (matched) {
+ success = true;
+ } else {
+ buffer << " Result: died but not with expected error.\n"
+ << " Expected: " << regex()->pattern() << "\n"
+ << "Actual msg:\n" << FormatDeathTestOutput(error_message);
+ }
+ } else {
+ buffer << " Result: died but not with expected exit code:\n"
+ << " " << ExitSummary(status()) << "\n"
+ << "Actual msg:\n" << FormatDeathTestOutput(error_message);
+ }
+ break;
+ case IN_PROGRESS:
+ default:
+ GTEST_LOG_(FATAL)
+ << "DeathTest::Passed somehow called before conclusion of test";
+ }
+
+ DeathTest::set_last_death_test_message(buffer.GetString());
+ return success;
+}
+
+# if GTEST_OS_WINDOWS
+// WindowsDeathTest implements death tests on Windows. Due to the
+// specifics of starting new processes on Windows, death tests there are
+// always threadsafe, and Google Test considers the
+// --gtest_death_test_style=fast setting to be equivalent to
+// --gtest_death_test_style=threadsafe there.
+//
+// A few implementation notes: Like the Linux version, the Windows
+// implementation uses pipes for child-to-parent communication. But due to
+// the specifics of pipes on Windows, some extra steps are required:
+//
+// 1. The parent creates a communication pipe and stores handles to both
+// ends of it.
+// 2. The parent starts the child and provides it with the information
+// necessary to acquire the handle to the write end of the pipe.
+// 3. The child acquires the write end of the pipe and signals the parent
+// using a Windows event.
+// 4. Now the parent can release the write end of the pipe on its side. If
+// this is done before step 3, the object's reference count goes down to
+// 0 and it is destroyed, preventing the child from acquiring it. The
+// parent now has to release it, or read operations on the read end of
+// the pipe will not return when the child terminates.
+// 5. The parent reads child's output through the pipe (outcome code and
+// any possible error messages) from the pipe, and its stderr and then
+// determines whether to fail the test.
+//
+// Note: to distinguish Win32 API calls from the local method and function
+// calls, the former are explicitly resolved in the global namespace.
+//
+class WindowsDeathTest : public DeathTestImpl {
+ public:
+ WindowsDeathTest(const char* a_statement,
+ const RE* a_regex,
+ const char* file,
+ int line)
+ : DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {}
+
+ // All of these virtual functions are inherited from DeathTest.
+ virtual int Wait();
+ virtual TestRole AssumeRole();
+
+ private:
+ // The name of the file in which the death test is located.
+ const char* const file_;
+ // The line number on which the death test is located.
+ const int line_;
+ // Handle to the write end of the pipe to the child process.
+ AutoHandle write_handle_;
+ // Child process handle.
+ AutoHandle child_handle_;
+ // Event the child process uses to signal the parent that it has
+ // acquired the handle to the write end of the pipe. After seeing this
+ // event the parent can release its own handles to make sure its
+ // ReadFile() calls return when the child terminates.
+ AutoHandle event_handle_;
+};
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists. As a side effect, sets the
+// outcome data member.
+int WindowsDeathTest::Wait() {
+ if (!spawned())
+ return 0;
+
+ // Wait until the child either signals that it has acquired the write end
+ // of the pipe or it dies.
+ const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() };
+ switch (::WaitForMultipleObjects(2,
+ wait_handles,
+ FALSE, // Waits for any of the handles.
+ INFINITE)) {
+ case WAIT_OBJECT_0:
+ case WAIT_OBJECT_0 + 1:
+ break;
+ default:
+ GTEST_DEATH_TEST_CHECK_(false); // Should not get here.
+ }
+
+ // The child has acquired the write end of the pipe or exited.
+ // We release the handle on our side and continue.
+ write_handle_.Reset();
+ event_handle_.Reset();
+
+ ReadAndInterpretStatusByte();
+
+ // Waits for the child process to exit if it haven't already. This
+ // returns immediately if the child has already exited, regardless of
+ // whether previous calls to WaitForMultipleObjects synchronized on this
+ // handle or not.
+ GTEST_DEATH_TEST_CHECK_(
+ WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(),
+ INFINITE));
+ DWORD status_code;
+ GTEST_DEATH_TEST_CHECK_(
+ ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE);
+ child_handle_.Reset();
+ set_status(static_cast<int>(status_code));
+ return status();
+}
+
+// The AssumeRole process for a Windows death test. It creates a child
+// process with the same executable as the current process to run the
+// death test. The child process is given the --gtest_filter and
+// --gtest_internal_run_death_test flags such that it knows to run the
+// current death test only.
+DeathTest::TestRole WindowsDeathTest::AssumeRole() {
+ const UnitTestImpl* const impl = GetUnitTestImpl();
+ const InternalRunDeathTestFlag* const flag =
+ impl->internal_run_death_test_flag();
+ const TestInfo* const info = impl->current_test_info();
+ const int death_test_index = info->result()->death_test_count();
+
+ if (flag != NULL) {
+ // ParseInternalRunDeathTestFlag() has performed all the necessary
+ // processing.
+ set_write_fd(flag->write_fd());
+ return EXECUTE_TEST;
+ }
+
+ // WindowsDeathTest uses an anonymous pipe to communicate results of
+ // a death test.
+ SECURITY_ATTRIBUTES handles_are_inheritable = {
+ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
+ HANDLE read_handle, write_handle;
+ GTEST_DEATH_TEST_CHECK_(
+ ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable,
+ 0) // Default buffer size.
+ != FALSE);
+ set_read_fd(::_open_osfhandle(reinterpret_cast<intptr_t>(read_handle),
+ O_RDONLY));
+ write_handle_.Reset(write_handle);
+ event_handle_.Reset(::CreateEvent(
+ &handles_are_inheritable,
+ TRUE, // The event will automatically reset to non-signaled state.
+ FALSE, // The initial state is non-signalled.
+ NULL)); // The even is unnamed.
+ GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL);
+ const std::string filter_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "=" +
+ info->test_case_name() + "." + info->name();
+ const std::string internal_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag +
+ "=" + file_ + "|" + StreamableToString(line_) + "|" +
+ StreamableToString(death_test_index) + "|" +
+ StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) +
+ // size_t has the same width as pointers on both 32-bit and 64-bit
+ // Windows platforms.
+ // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx.
+ "|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) +
+ "|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get()));
+
+ char executable_path[_MAX_PATH + 1]; // NOLINT
+ GTEST_DEATH_TEST_CHECK_(
+ _MAX_PATH + 1 != ::GetModuleFileNameA(NULL,
+ executable_path,
+ _MAX_PATH));
+
+ std::string command_line =
+ std::string(::GetCommandLineA()) + " " + filter_flag + " \"" +
+ internal_flag + "\"";
+
+ DeathTest::set_last_death_test_message("");
+
+ CaptureStderr();
+ // Flush the log buffers since the log streams are shared with the child.
+ FlushInfoLog();
+
+ // The child process will share the standard handles with the parent.
+ STARTUPINFOA startup_info;
+ memset(&startup_info, 0, sizeof(STARTUPINFO));
+ startup_info.dwFlags = STARTF_USESTDHANDLES;
+ startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
+ startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
+ startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
+
+ PROCESS_INFORMATION process_info;
+ GTEST_DEATH_TEST_CHECK_(::CreateProcessA(
+ executable_path,
+ const_cast<char*>(command_line.c_str()),
+ NULL, // Retuned process handle is not inheritable.
+ NULL, // Retuned thread handle is not inheritable.
+ TRUE, // Child inherits all inheritable handles (for write_handle_).
+ 0x0, // Default creation flags.
+ NULL, // Inherit the parent's environment.
+ UnitTest::GetInstance()->original_working_dir(),
+ &startup_info,
+ &process_info) != FALSE);
+ child_handle_.Reset(process_info.hProcess);
+ ::CloseHandle(process_info.hThread);
+ set_spawned(true);
+ return OVERSEE_TEST;
+}
+
+# elif GTEST_OS_FUCHSIA
+
+class FuchsiaDeathTest : public DeathTestImpl {
+ public:
+ FuchsiaDeathTest(const char* a_statement,
+ const RE* a_regex,
+ const char* file,
+ int line)
+ : DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {}
+ virtual ~FuchsiaDeathTest() {
+ zx_status_t status = zx_handle_close(child_process_);
+ GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+ status = zx_handle_close(port_);
+ GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+ }
+
+ // All of these virtual functions are inherited from DeathTest.
+ virtual int Wait();
+ virtual TestRole AssumeRole();
+
+ private:
+ // The name of the file in which the death test is located.
+ const char* const file_;
+ // The line number on which the death test is located.
+ const int line_;
+
+ zx_handle_t child_process_ = ZX_HANDLE_INVALID;
+ zx_handle_t port_ = ZX_HANDLE_INVALID;
+};
+
+// Utility class for accumulating command-line arguments.
+class Arguments {
+ public:
+ Arguments() {
+ args_.push_back(NULL);
+ }
+
+ ~Arguments() {
+ for (std::vector<char*>::iterator i = args_.begin(); i != args_.end();
+ ++i) {
+ free(*i);
+ }
+ }
+ void AddArgument(const char* argument) {
+ args_.insert(args_.end() - 1, posix::StrDup(argument));
+ }
+
+ template <typename Str>
+ void AddArguments(const ::std::vector<Str>& arguments) {
+ for (typename ::std::vector<Str>::const_iterator i = arguments.begin();
+ i != arguments.end();
+ ++i) {
+ args_.insert(args_.end() - 1, posix::StrDup(i->c_str()));
+ }
+ }
+ char* const* Argv() {
+ return &args_[0];
+ }
+
+ int size() {
+ return args_.size() - 1;
+ }
+
+ private:
+ std::vector<char*> args_;
+};
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists. As a side effect, sets the
+// outcome data member.
+int FuchsiaDeathTest::Wait() {
+ if (!spawned())
+ return 0;
+
+ // Register to wait for the child process to terminate.
+ zx_status_t status_zx;
+ status_zx = zx_object_wait_async(child_process_,
+ port_,
+ 0 /* key */,
+ ZX_PROCESS_TERMINATED,
+ ZX_WAIT_ASYNC_ONCE);
+ GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+ // Wait for it to terminate, or an exception to be received.
+ zx_port_packet_t packet;
+ status_zx = zx_port_wait(port_, ZX_TIME_INFINITE, &packet);
+ GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+ if (ZX_PKT_IS_EXCEPTION(packet.type)) {
+ // Process encountered an exception. Kill it directly rather than letting
+ // other handlers process the event.
+ status_zx = zx_task_kill(child_process_);
+ GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+ // Now wait for |child_process_| to terminate.
+ zx_signals_t signals = 0;
+ status_zx = zx_object_wait_one(
+ child_process_, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, &signals);
+ GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+ GTEST_DEATH_TEST_CHECK_(signals & ZX_PROCESS_TERMINATED);
+ } else {
+ // Process terminated.
+ GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type));
+ GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED);
+ }
+
+ ReadAndInterpretStatusByte();
+
+ zx_info_process_t buffer;
+ status_zx = zx_object_get_info(
+ child_process_,
+ ZX_INFO_PROCESS,
+ &buffer,
+ sizeof(buffer),
+ nullptr,
+ nullptr);
+ GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+ GTEST_DEATH_TEST_CHECK_(buffer.exited);
+ set_status(buffer.return_code);
+ return status();
+}
+
+// The AssumeRole process for a Fuchsia death test. It creates a child
+// process with the same executable as the current process to run the
+// death test. The child process is given the --gtest_filter and
+// --gtest_internal_run_death_test flags such that it knows to run the
+// current death test only.
+DeathTest::TestRole FuchsiaDeathTest::AssumeRole() {
+ const UnitTestImpl* const impl = GetUnitTestImpl();
+ const InternalRunDeathTestFlag* const flag =
+ impl->internal_run_death_test_flag();
+ const TestInfo* const info = impl->current_test_info();
+ const int death_test_index = info->result()->death_test_count();
+
+ if (flag != NULL) {
+ // ParseInternalRunDeathTestFlag() has performed all the necessary
+ // processing.
+ set_write_fd(kFuchsiaReadPipeFd);
+ return EXECUTE_TEST;
+ }
+
+ CaptureStderr();
+ // Flush the log buffers since the log streams are shared with the child.
+ FlushInfoLog();
+
+ // Build the child process command line.
+ const std::string filter_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "="
+ + info->test_case_name() + "." + info->name();
+ const std::string internal_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "="
+ + file_ + "|"
+ + StreamableToString(line_) + "|"
+ + StreamableToString(death_test_index);
+ Arguments args;
+ args.AddArguments(GetInjectableArgvs());
+ args.AddArgument(filter_flag.c_str());
+ args.AddArgument(internal_flag.c_str());
+
+ // Build the pipe for communication with the child.
+ zx_status_t status;
+ zx_handle_t child_pipe_handle;
+ uint32_t type;
+ status = fdio_pipe_half(&child_pipe_handle, &type);
+ GTEST_DEATH_TEST_CHECK_(status >= 0);
+ set_read_fd(status);
+
+ // Set the pipe handle for the child.
+ fdio_spawn_action_t add_handle_action = {};
+ add_handle_action.action = FDIO_SPAWN_ACTION_ADD_HANDLE;
+ add_handle_action.h.id = PA_HND(type, kFuchsiaReadPipeFd);
+ add_handle_action.h.handle = child_pipe_handle;
+
+ // Spawn the child process.
+ status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL,
+ args.Argv()[0], args.Argv(), nullptr, 1,
+ &add_handle_action, &child_process_, nullptr);
+ GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+
+ // Create an exception port and attach it to the |child_process_|, to allow
+ // us to suppress the system default exception handler from firing.
+ status = zx_port_create(0, &port_);
+ GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+ status = zx_task_bind_exception_port(
+ child_process_, port_, 0 /* key */, 0 /*options */);
+ GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+
+ set_spawned(true);
+ return OVERSEE_TEST;
+}
+
+#else // We are neither on Windows, nor on Fuchsia.
+
+// ForkingDeathTest provides implementations for most of the abstract
+// methods of the DeathTest interface. Only the AssumeRole method is
+// left undefined.
+class ForkingDeathTest : public DeathTestImpl {
+ public:
+ ForkingDeathTest(const char* statement, const RE* regex);
+
+ // All of these virtual functions are inherited from DeathTest.
+ virtual int Wait();
+
+ protected:
+ void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; }
+
+ private:
+ // PID of child process during death test; 0 in the child process itself.
+ pid_t child_pid_;
+};
+
+// Constructs a ForkingDeathTest.
+ForkingDeathTest::ForkingDeathTest(const char* a_statement, const RE* a_regex)
+ : DeathTestImpl(a_statement, a_regex),
+ child_pid_(-1) {}
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists. As a side effect, sets the
+// outcome data member.
+int ForkingDeathTest::Wait() {
+ if (!spawned())
+ return 0;
+
+ ReadAndInterpretStatusByte();
+
+ int status_value;
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0));
+ set_status(status_value);
+ return status_value;
+}
+
+// A concrete death test class that forks, then immediately runs the test
+// in the child process.
+class NoExecDeathTest : public ForkingDeathTest {
+ public:
+ NoExecDeathTest(const char* a_statement, const RE* a_regex) :
+ ForkingDeathTest(a_statement, a_regex) { }
+ virtual TestRole AssumeRole();
+};
+
+// The AssumeRole process for a fork-and-run death test. It implements a
+// straightforward fork, with a simple pipe to transmit the status byte.
+DeathTest::TestRole NoExecDeathTest::AssumeRole() {
+ const size_t thread_count = GetThreadCount();
+ if (thread_count != 1) {
+ GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count);
+ }
+
+ int pipe_fd[2];
+ GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1);
+
+ DeathTest::set_last_death_test_message("");
+ CaptureStderr();
+ // When we fork the process below, the log file buffers are copied, but the
+ // file descriptors are shared. We flush all log files here so that closing
+ // the file descriptors in the child process doesn't throw off the
+ // synchronization between descriptors and buffers in the parent process.
+ // This is as close to the fork as possible to avoid a race condition in case
+ // there are multiple threads running before the death test, and another
+ // thread writes to the log file.
+ FlushInfoLog();
+
+ const pid_t child_pid = fork();
+ GTEST_DEATH_TEST_CHECK_(child_pid != -1);
+ set_child_pid(child_pid);
+ if (child_pid == 0) {
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0]));
+ set_write_fd(pipe_fd[1]);
+ // Redirects all logging to stderr in the child process to prevent
+ // concurrent writes to the log files. We capture stderr in the parent
+ // process and append the child process' output to a log.
+ LogToStderr();
+ // Event forwarding to the listeners of event listener API mush be shut
+ // down in death test subprocesses.
+ GetUnitTestImpl()->listeners()->SuppressEventForwarding();
+ g_in_fast_death_test_child = true;
+ return EXECUTE_TEST;
+ } else {
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1]));
+ set_read_fd(pipe_fd[0]);
+ set_spawned(true);
+ return OVERSEE_TEST;
+ }
+}
+
+// A concrete death test class that forks and re-executes the main
+// program from the beginning, with command-line flags set that cause
+// only this specific death test to be run.
+class ExecDeathTest : public ForkingDeathTest {
+ public:
+ ExecDeathTest(const char* a_statement, const RE* a_regex,
+ const char* file, int line) :
+ ForkingDeathTest(a_statement, a_regex), file_(file), line_(line) { }
+ virtual TestRole AssumeRole();
+ private:
+ static ::std::vector<std::string> GetArgvsForDeathTestChildProcess() {
+ ::std::vector<std::string> args = GetInjectableArgvs();
+# if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_)
+ ::std::vector<std::string> extra_args =
+ GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_();
+ args.insert(args.end(), extra_args.begin(), extra_args.end());
+# endif // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_)
+ return args;
+ }
+ // The name of the file in which the death test is located.
+ const char* const file_;
+ // The line number on which the death test is located.
+ const int line_;
+};
+
+// Utility class for accumulating command-line arguments.
+class Arguments {
+ public:
+ Arguments() {
+ args_.push_back(NULL);
+ }
+
+ ~Arguments() {
+ for (std::vector<char*>::iterator i = args_.begin(); i != args_.end();
+ ++i) {
+ free(*i);
+ }
+ }
+ void AddArgument(const char* argument) {
+ args_.insert(args_.end() - 1, posix::StrDup(argument));
+ }
+
+ template <typename Str>
+ void AddArguments(const ::std::vector<Str>& arguments) {
+ for (typename ::std::vector<Str>::const_iterator i = arguments.begin();
+ i != arguments.end();
+ ++i) {
+ args_.insert(args_.end() - 1, posix::StrDup(i->c_str()));
+ }
+ }
+ char* const* Argv() {
+ return &args_[0];
+ }
+
+ private:
+ std::vector<char*> args_;
+};
+
+// A struct that encompasses the arguments to the child process of a
+// threadsafe-style death test process.
+struct ExecDeathTestArgs {
+ char* const* argv; // Command-line arguments for the child's call to exec
+ int close_fd; // File descriptor to close; the read end of a pipe
+};
+
+# if GTEST_OS_MAC
+inline char** GetEnviron() {
+ // When Google Test is built as a framework on MacOS X, the environ variable
+ // is unavailable. Apple's documentation (man environ) recommends using
+ // _NSGetEnviron() instead.
+ return *_NSGetEnviron();
+}
+# else
+// Some POSIX platforms expect you to declare environ. extern "C" makes
+// it reside in the global namespace.
+extern "C" char** environ;
+inline char** GetEnviron() { return environ; }
+# endif // GTEST_OS_MAC
+
+# if !GTEST_OS_QNX
+// The main function for a threadsafe-style death test child process.
+// This function is called in a clone()-ed process and thus must avoid
+// any potentially unsafe operations like malloc or libc functions.
+static int ExecDeathTestChildMain(void* child_arg) {
+ ExecDeathTestArgs* const args = static_cast<ExecDeathTestArgs*>(child_arg);
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd));
+
+ // We need to execute the test program in the same environment where
+ // it was originally invoked. Therefore we change to the original
+ // working directory first.
+ const char* const original_dir =
+ UnitTest::GetInstance()->original_working_dir();
+ // We can safely call chdir() as it's a direct system call.
+ if (chdir(original_dir) != 0) {
+ DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " +
+ GetLastErrnoDescription());
+ return EXIT_FAILURE;
+ }
+
+ // We can safely call execve() as it's a direct system call. We
+ // cannot use execvp() as it's a libc function and thus potentially
+ // unsafe. Since execve() doesn't search the PATH, the user must
+ // invoke the test program via a valid path that contains at least
+ // one path separator.
+ execve(args->argv[0], args->argv, GetEnviron());
+ DeathTestAbort(std::string("execve(") + args->argv[0] + ", ...) in " +
+ original_dir + " failed: " +
+ GetLastErrnoDescription());
+ return EXIT_FAILURE;
+}
+# endif // !GTEST_OS_QNX
+
+# if GTEST_HAS_CLONE
+// Two utility routines that together determine the direction the stack
+// grows.
+// This could be accomplished more elegantly by a single recursive
+// function, but we want to guard against the unlikely possibility of
+// a smart compiler optimizing the recursion away.
+//
+// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining
+// StackLowerThanAddress into StackGrowsDown, which then doesn't give
+// correct answer.
+static void StackLowerThanAddress(const void* ptr,
+ bool* result) GTEST_NO_INLINE_;
+static void StackLowerThanAddress(const void* ptr, bool* result) {
+ int dummy;
+ *result = (&dummy < ptr);
+}
+
+// Make sure AddressSanitizer does not tamper with the stack here.
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+static bool StackGrowsDown() {
+ int dummy;
+ bool result;
+ StackLowerThanAddress(&dummy, &result);
+ return result;
+}
+# endif // GTEST_HAS_CLONE
+
+// Spawns a child process with the same executable as the current process in
+// a thread-safe manner and instructs it to run the death test. The
+// implementation uses fork(2) + exec. On systems where clone(2) is
+// available, it is used instead, being slightly more thread-safe. On QNX,
+// fork supports only single-threaded environments, so this function uses
+// spawn(2) there instead. The function dies with an error message if
+// anything goes wrong.
+static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) {
+ ExecDeathTestArgs args = { argv, close_fd };
+ pid_t child_pid = -1;
+
+# if GTEST_OS_QNX
+ // Obtains the current directory and sets it to be closed in the child
+ // process.
+ const int cwd_fd = open(".", O_RDONLY);
+ GTEST_DEATH_TEST_CHECK_(cwd_fd != -1);
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC));
+ // We need to execute the test program in the same environment where
+ // it was originally invoked. Therefore we change to the original
+ // working directory first.
+ const char* const original_dir =
+ UnitTest::GetInstance()->original_working_dir();
+ // We can safely call chdir() as it's a direct system call.
+ if (chdir(original_dir) != 0) {
+ DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " +
+ GetLastErrnoDescription());
+ return EXIT_FAILURE;
+ }
+
+ int fd_flags;
+ // Set close_fd to be closed after spawn.
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD));
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(close_fd, F_SETFD,
+ fd_flags | FD_CLOEXEC));
+ struct inheritance inherit = {0};
+ // spawn is a system call.
+ child_pid = spawn(args.argv[0], 0, NULL, &inherit, args.argv, GetEnviron());
+ // Restores the current working directory.
+ GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1);
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd));
+
+# else // GTEST_OS_QNX
+# if GTEST_OS_LINUX
+ // When a SIGPROF signal is received while fork() or clone() are executing,
+ // the process may hang. To avoid this, we ignore SIGPROF here and re-enable
+ // it after the call to fork()/clone() is complete.
+ struct sigaction saved_sigprof_action;
+ struct sigaction ignore_sigprof_action;
+ memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action));
+ sigemptyset(&ignore_sigprof_action.sa_mask);
+ ignore_sigprof_action.sa_handler = SIG_IGN;
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(sigaction(
+ SIGPROF, &ignore_sigprof_action, &saved_sigprof_action));
+# endif // GTEST_OS_LINUX
+
+# if GTEST_HAS_CLONE
+ const bool use_fork = GTEST_FLAG(death_test_use_fork);
+
+ if (!use_fork) {
+ static const bool stack_grows_down = StackGrowsDown();
+ const size_t stack_size = getpagesize();
+ // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead.
+ void* const stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED);
+
+ // Maximum stack alignment in bytes: For a downward-growing stack, this
+ // amount is subtracted from size of the stack space to get an address
+ // that is within the stack space and is aligned on all systems we care
+ // about. As far as I know there is no ABI with stack alignment greater
+ // than 64. We assume stack and stack_size already have alignment of
+ // kMaxStackAlignment.
+ const size_t kMaxStackAlignment = 64;
+ void* const stack_top =
+ static_cast<char*>(stack) +
+ (stack_grows_down ? stack_size - kMaxStackAlignment : 0);
+ GTEST_DEATH_TEST_CHECK_(stack_size > kMaxStackAlignment &&
+ reinterpret_cast<intptr_t>(stack_top) % kMaxStackAlignment == 0);
+
+ child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args);
+
+ GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1);
+ }
+# else
+ const bool use_fork = true;
+# endif // GTEST_HAS_CLONE
+
+ if (use_fork && (child_pid = fork()) == 0) {
+ ExecDeathTestChildMain(&args);
+ _exit(0);
+ }
+# endif // GTEST_OS_QNX
+# if GTEST_OS_LINUX
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(
+ sigaction(SIGPROF, &saved_sigprof_action, NULL));
+# endif // GTEST_OS_LINUX
+
+ GTEST_DEATH_TEST_CHECK_(child_pid != -1);
+ return child_pid;
+}
+
+// The AssumeRole process for a fork-and-exec death test. It re-executes the
+// main program from the beginning, setting the --gtest_filter
+// and --gtest_internal_run_death_test flags to cause only the current
+// death test to be re-run.
+DeathTest::TestRole ExecDeathTest::AssumeRole() {
+ const UnitTestImpl* const impl = GetUnitTestImpl();
+ const InternalRunDeathTestFlag* const flag =
+ impl->internal_run_death_test_flag();
+ const TestInfo* const info = impl->current_test_info();
+ const int death_test_index = info->result()->death_test_count();
+
+ if (flag != NULL) {
+ set_write_fd(flag->write_fd());
+ return EXECUTE_TEST;
+ }
+
+ int pipe_fd[2];
+ GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1);
+ // Clear the close-on-exec flag on the write end of the pipe, lest
+ // it be closed when the child process does an exec:
+ GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1);
+
+ const std::string filter_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "="
+ + info->test_case_name() + "." + info->name();
+ const std::string internal_flag =
+ std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "="
+ + file_ + "|" + StreamableToString(line_) + "|"
+ + StreamableToString(death_test_index) + "|"
+ + StreamableToString(pipe_fd[1]);
+ Arguments args;
+ args.AddArguments(GetArgvsForDeathTestChildProcess());
+ args.AddArgument(filter_flag.c_str());
+ args.AddArgument(internal_flag.c_str());
+
+ DeathTest::set_last_death_test_message("");
+
+ CaptureStderr();
+ // See the comment in NoExecDeathTest::AssumeRole for why the next line
+ // is necessary.
+ FlushInfoLog();
+
+ const pid_t child_pid = ExecDeathTestSpawnChild(args.Argv(), pipe_fd[0]);
+ GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1]));
+ set_child_pid(child_pid);
+ set_read_fd(pipe_fd[0]);
+ set_spawned(true);
+ return OVERSEE_TEST;
+}
+
+# endif // !GTEST_OS_WINDOWS
+
+// Creates a concrete DeathTest-derived class that depends on the
+// --gtest_death_test_style flag, and sets the pointer pointed to
+// by the "test" argument to its address. If the test should be
+// skipped, sets that pointer to NULL. Returns true, unless the
+// flag is set to an invalid value.
+bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex,
+ const char* file, int line,
+ DeathTest** test) {
+ UnitTestImpl* const impl = GetUnitTestImpl();
+ const InternalRunDeathTestFlag* const flag =
+ impl->internal_run_death_test_flag();
+ const int death_test_index = impl->current_test_info()
+ ->increment_death_test_count();
+
+ if (flag != NULL) {
+ if (death_test_index > flag->index()) {
+ DeathTest::set_last_death_test_message(
+ "Death test count (" + StreamableToString(death_test_index)
+ + ") somehow exceeded expected maximum ("
+ + StreamableToString(flag->index()) + ")");
+ return false;
+ }
+
+ if (!(flag->file() == file && flag->line() == line &&
+ flag->index() == death_test_index)) {
+ *test = NULL;
+ return true;
+ }
+ }
+
+# if GTEST_OS_WINDOWS
+
+ if (GTEST_FLAG(death_test_style) == "threadsafe" ||
+ GTEST_FLAG(death_test_style) == "fast") {
+ *test = new WindowsDeathTest(statement, regex, file, line);
+ }
+
+# elif GTEST_OS_FUCHSIA
+
+ if (GTEST_FLAG(death_test_style) == "threadsafe" ||
+ GTEST_FLAG(death_test_style) == "fast") {
+ *test = new FuchsiaDeathTest(statement, regex, file, line);
+ }
+
+# else
+
+ if (GTEST_FLAG(death_test_style) == "threadsafe") {
+ *test = new ExecDeathTest(statement, regex, file, line);
+ } else if (GTEST_FLAG(death_test_style) == "fast") {
+ *test = new NoExecDeathTest(statement, regex);
+ }
+
+# endif // GTEST_OS_WINDOWS
+
+ else { // NOLINT - this is more readable than unbalanced brackets inside #if.
+ DeathTest::set_last_death_test_message(
+ "Unknown death test style \"" + GTEST_FLAG(death_test_style)
+ + "\" encountered");
+ return false;
+ }
+
+ return true;
+}
+
+# if GTEST_OS_WINDOWS
+// Recreates the pipe and event handles from the provided parameters,
+// signals the event, and returns a file descriptor wrapped around the pipe
+// handle. This function is called in the child process only.
+static int GetStatusFileDescriptor(unsigned int parent_process_id,
+ size_t write_handle_as_size_t,
+ size_t event_handle_as_size_t) {
+ AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE,
+ FALSE, // Non-inheritable.
+ parent_process_id));
+ if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) {
+ DeathTestAbort("Unable to open parent process " +
+ StreamableToString(parent_process_id));
+ }
+
+ // FIXME: Replace the following check with a
+ // compile-time assertion when available.
+ GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t));
+
+ const HANDLE write_handle =
+ reinterpret_cast<HANDLE>(write_handle_as_size_t);
+ HANDLE dup_write_handle;
+
+ // The newly initialized handle is accessible only in the parent
+ // process. To obtain one accessible within the child, we need to use
+ // DuplicateHandle.
+ if (!::DuplicateHandle(parent_process_handle.Get(), write_handle,
+ ::GetCurrentProcess(), &dup_write_handle,
+ 0x0, // Requested privileges ignored since
+ // DUPLICATE_SAME_ACCESS is used.
+ FALSE, // Request non-inheritable handler.
+ DUPLICATE_SAME_ACCESS)) {
+ DeathTestAbort("Unable to duplicate the pipe handle " +
+ StreamableToString(write_handle_as_size_t) +
+ " from the parent process " +
+ StreamableToString(parent_process_id));
+ }
+
+ const HANDLE event_handle = reinterpret_cast<HANDLE>(event_handle_as_size_t);
+ HANDLE dup_event_handle;
+
+ if (!::DuplicateHandle(parent_process_handle.Get(), event_handle,
+ ::GetCurrentProcess(), &dup_event_handle,
+ 0x0,
+ FALSE,
+ DUPLICATE_SAME_ACCESS)) {
+ DeathTestAbort("Unable to duplicate the event handle " +
+ StreamableToString(event_handle_as_size_t) +
+ " from the parent process " +
+ StreamableToString(parent_process_id));
+ }
+
+ const int write_fd =
+ ::_open_osfhandle(reinterpret_cast<intptr_t>(dup_write_handle), O_APPEND);
+ if (write_fd == -1) {
+ DeathTestAbort("Unable to convert pipe handle " +
+ StreamableToString(write_handle_as_size_t) +
+ " to a file descriptor");
+ }
+
+ // Signals the parent that the write end of the pipe has been acquired
+ // so the parent can release its own write end.
+ ::SetEvent(dup_event_handle);
+
+ return write_fd;
+}
+# endif // GTEST_OS_WINDOWS
+
+// Returns a newly created InternalRunDeathTestFlag object with fields
+// initialized from the GTEST_FLAG(internal_run_death_test) flag if
+// the flag is specified; otherwise returns NULL.
+InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() {
+ if (GTEST_FLAG(internal_run_death_test) == "") return NULL;
+
+ // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we
+ // can use it here.
+ int line = -1;
+ int index = -1;
+ ::std::vector< ::std::string> fields;
+ SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields);
+ int write_fd = -1;
+
+# if GTEST_OS_WINDOWS
+
+ unsigned int parent_process_id = 0;
+ size_t write_handle_as_size_t = 0;
+ size_t event_handle_as_size_t = 0;
+
+ if (fields.size() != 6
+ || !ParseNaturalNumber(fields[1], &line)
+ || !ParseNaturalNumber(fields[2], &index)
+ || !ParseNaturalNumber(fields[3], &parent_process_id)
+ || !ParseNaturalNumber(fields[4], &write_handle_as_size_t)
+ || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) {
+ DeathTestAbort("Bad --gtest_internal_run_death_test flag: " +
+ GTEST_FLAG(internal_run_death_test));
+ }
+ write_fd = GetStatusFileDescriptor(parent_process_id,
+ write_handle_as_size_t,
+ event_handle_as_size_t);
+
+# elif GTEST_OS_FUCHSIA
+
+ if (fields.size() != 3
+ || !ParseNaturalNumber(fields[1], &line)
+ || !ParseNaturalNumber(fields[2], &index)) {
+ DeathTestAbort("Bad --gtest_internal_run_death_test flag: "
+ + GTEST_FLAG(internal_run_death_test));
+ }
+
+# else
+
+ if (fields.size() != 4
+ || !ParseNaturalNumber(fields[1], &line)
+ || !ParseNaturalNumber(fields[2], &index)
+ || !ParseNaturalNumber(fields[3], &write_fd)) {
+ DeathTestAbort("Bad --gtest_internal_run_death_test flag: "
+ + GTEST_FLAG(internal_run_death_test));
+ }
+
+# endif // GTEST_OS_WINDOWS
+
+ return new InternalRunDeathTestFlag(fields[0], line, index, write_fd);
+}
+
+} // namespace internal
+
+#endif // GTEST_HAS_DEATH_TEST
+
+} // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#include <stdlib.h>
+
+#if GTEST_OS_WINDOWS_MOBILE
+# include <windows.h>
+#elif GTEST_OS_WINDOWS
+# include <direct.h>
+# include <io.h>
+#elif GTEST_OS_SYMBIAN
+// Symbian OpenC has PATH_MAX in sys/syslimits.h
+# include <sys/syslimits.h>
+#else
+# include <limits.h>
+# include <climits> // Some Linux distributions define PATH_MAX here.
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+
+#if GTEST_OS_WINDOWS
+# define GTEST_PATH_MAX_ _MAX_PATH
+#elif defined(PATH_MAX)
+# define GTEST_PATH_MAX_ PATH_MAX
+#elif defined(_XOPEN_PATH_MAX)
+# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX
+#else
+# define GTEST_PATH_MAX_ _POSIX_PATH_MAX
+#endif // GTEST_OS_WINDOWS
+
+namespace testing {
+namespace internal {
+
+#if GTEST_OS_WINDOWS
+// On Windows, '\\' is the standard path separator, but many tools and the
+// Windows API also accept '/' as an alternate path separator. Unless otherwise
+// noted, a file path can contain either kind of path separators, or a mixture
+// of them.
+const char kPathSeparator = '\\';
+const char kAlternatePathSeparator = '/';
+const char kAlternatePathSeparatorString[] = "/";
+# if GTEST_OS_WINDOWS_MOBILE
+// Windows CE doesn't have a current directory. You should not use
+// the current directory in tests on Windows CE, but this at least
+// provides a reasonable fallback.
+const char kCurrentDirectoryString[] = "\\";
+// Windows CE doesn't define INVALID_FILE_ATTRIBUTES
+const DWORD kInvalidFileAttributes = 0xffffffff;
+# else
+const char kCurrentDirectoryString[] = ".\\";
+# endif // GTEST_OS_WINDOWS_MOBILE
+#else
+const char kPathSeparator = '/';
+const char kCurrentDirectoryString[] = "./";
+#endif // GTEST_OS_WINDOWS
+
+// Returns whether the given character is a valid path separator.
+static bool IsPathSeparator(char c) {
+#if GTEST_HAS_ALT_PATH_SEP_
+ return (c == kPathSeparator) || (c == kAlternatePathSeparator);
+#else
+ return c == kPathSeparator;
+#endif
+}
+
+// Returns the current working directory, or "" if unsuccessful.
+FilePath FilePath::GetCurrentDir() {
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT
+ // Windows CE doesn't have a current directory, so we just return
+ // something reasonable.
+ return FilePath(kCurrentDirectoryString);
+#elif GTEST_OS_WINDOWS
+ char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
+ return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd);
+#else
+ char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
+ char* result = getcwd(cwd, sizeof(cwd));
+# if GTEST_OS_NACL
+ // getcwd will likely fail in NaCl due to the sandbox, so return something
+ // reasonable. The user may have provided a shim implementation for getcwd,
+ // however, so fallback only when failure is detected.
+ return FilePath(result == NULL ? kCurrentDirectoryString : cwd);
+# endif // GTEST_OS_NACL
+ return FilePath(result == NULL ? "" : cwd);
+#endif // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Returns a copy of the FilePath with the case-insensitive extension removed.
+// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
+// FilePath("dir/file"). If a case-insensitive extension is not
+// found, returns a copy of the original FilePath.
+FilePath FilePath::RemoveExtension(const char* extension) const {
+ const std::string dot_extension = std::string(".") + extension;
+ if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) {
+ return FilePath(pathname_.substr(
+ 0, pathname_.length() - dot_extension.length()));
+ }
+ return *this;
+}
+
+// Returns a pointer to the last occurrence of a valid path separator in
+// the FilePath. On Windows, for example, both '/' and '\' are valid path
+// separators. Returns NULL if no path separator was found.
+const char* FilePath::FindLastPathSeparator() const {
+ const char* const last_sep = strrchr(c_str(), kPathSeparator);
+#if GTEST_HAS_ALT_PATH_SEP_
+ const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator);
+ // Comparing two pointers of which only one is NULL is undefined.
+ if (last_alt_sep != NULL &&
+ (last_sep == NULL || last_alt_sep > last_sep)) {
+ return last_alt_sep;
+ }
+#endif
+ return last_sep;
+}
+
+// Returns a copy of the FilePath with the directory part removed.
+// Example: FilePath("path/to/file").RemoveDirectoryName() returns
+// FilePath("file"). If there is no directory part ("just_a_file"), it returns
+// the FilePath unmodified. If there is no file part ("just_a_dir/") it
+// returns an empty FilePath ("").
+// On Windows platform, '\' is the path separator, otherwise it is '/'.
+FilePath FilePath::RemoveDirectoryName() const {
+ const char* const last_sep = FindLastPathSeparator();
+ return last_sep ? FilePath(last_sep + 1) : *this;
+}
+
+// RemoveFileName returns the directory path with the filename removed.
+// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
+// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
+// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
+// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
+// On Windows platform, '\' is the path separator, otherwise it is '/'.
+FilePath FilePath::RemoveFileName() const {
+ const char* const last_sep = FindLastPathSeparator();
+ std::string dir;
+ if (last_sep) {
+ dir = std::string(c_str(), last_sep + 1 - c_str());
+ } else {
+ dir = kCurrentDirectoryString;
+ }
+ return FilePath(dir);
+}
+
+// Helper functions for naming files in a directory for xml output.
+
+// Given directory = "dir", base_name = "test", number = 0,
+// extension = "xml", returns "dir/test.xml". If number is greater
+// than zero (e.g., 12), returns "dir/test_12.xml".
+// On Windows platform, uses \ as the separator rather than /.
+FilePath FilePath::MakeFileName(const FilePath& directory,
+ const FilePath& base_name,
+ int number,
+ const char* extension) {
+ std::string file;
+ if (number == 0) {
+ file = base_name.string() + "." + extension;
+ } else {
+ file = base_name.string() + "_" + StreamableToString(number)
+ + "." + extension;
+ }
+ return ConcatPaths(directory, FilePath(file));
+}
+
+// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml".
+// On Windows, uses \ as the separator rather than /.
+FilePath FilePath::ConcatPaths(const FilePath& directory,
+ const FilePath& relative_path) {
+ if (directory.IsEmpty())
+ return relative_path;
+ const FilePath dir(directory.RemoveTrailingPathSeparator());
+ return FilePath(dir.string() + kPathSeparator + relative_path.string());
+}
+
+// Returns true if pathname describes something findable in the file-system,
+// either a file, directory, or whatever.
+bool FilePath::FileOrDirectoryExists() const {
+#if GTEST_OS_WINDOWS_MOBILE
+ LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str());
+ const DWORD attributes = GetFileAttributes(unicode);
+ delete [] unicode;
+ return attributes != kInvalidFileAttributes;
+#else
+ posix::StatStruct file_stat;
+ return posix::Stat(pathname_.c_str(), &file_stat) == 0;
+#endif // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Returns true if pathname describes a directory in the file-system
+// that exists.
+bool FilePath::DirectoryExists() const {
+ bool result = false;
+#if GTEST_OS_WINDOWS
+ // Don't strip off trailing separator if path is a root directory on
+ // Windows (like "C:\\").
+ const FilePath& path(IsRootDirectory() ? *this :
+ RemoveTrailingPathSeparator());
+#else
+ const FilePath& path(*this);
+#endif
+
+#if GTEST_OS_WINDOWS_MOBILE
+ LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
+ const DWORD attributes = GetFileAttributes(unicode);
+ delete [] unicode;
+ if ((attributes != kInvalidFileAttributes) &&
+ (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ result = true;
+ }
+#else
+ posix::StatStruct file_stat;
+ result = posix::Stat(path.c_str(), &file_stat) == 0 &&
+ posix::IsDir(file_stat);
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+ return result;
+}
+
+// Returns true if pathname describes a root directory. (Windows has one
+// root directory per disk drive.)
+bool FilePath::IsRootDirectory() const {
+#if GTEST_OS_WINDOWS
+ // FIXME: on Windows a network share like
+ // \\server\share can be a root directory, although it cannot be the
+ // current directory. Handle this properly.
+ return pathname_.length() == 3 && IsAbsolutePath();
+#else
+ return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
+#endif
+}
+
+// Returns true if pathname describes an absolute path.
+bool FilePath::IsAbsolutePath() const {
+ const char* const name = pathname_.c_str();
+#if GTEST_OS_WINDOWS
+ return pathname_.length() >= 3 &&
+ ((name[0] >= 'a' && name[0] <= 'z') ||
+ (name[0] >= 'A' && name[0] <= 'Z')) &&
+ name[1] == ':' &&
+ IsPathSeparator(name[2]);
+#else
+ return IsPathSeparator(name[0]);
+#endif
+}
+
+// Returns a pathname for a file that does not currently exist. The pathname
+// will be directory/base_name.extension or
+// directory/base_name_<number>.extension if directory/base_name.extension
+// already exists. The number will be incremented until a pathname is found
+// that does not already exist.
+// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
+// There could be a race condition if two or more processes are calling this
+// function at the same time -- they could both pick the same filename.
+FilePath FilePath::GenerateUniqueFileName(const FilePath& directory,
+ const FilePath& base_name,
+ const char* extension) {
+ FilePath full_pathname;
+ int number = 0;
+ do {
+ full_pathname.Set(MakeFileName(directory, base_name, number++, extension));
+ } while (full_pathname.FileOrDirectoryExists());
+ return full_pathname;
+}
+
+// Returns true if FilePath ends with a path separator, which indicates that
+// it is intended to represent a directory. Returns false otherwise.
+// This does NOT check that a directory (or file) actually exists.
+bool FilePath::IsDirectory() const {
+ return !pathname_.empty() &&
+ IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]);
+}
+
+// Create directories so that path exists. Returns true if successful or if
+// the directories already exist; returns false if unable to create directories
+// for any reason.
+bool FilePath::CreateDirectoriesRecursively() const {
+ if (!this->IsDirectory()) {
+ return false;
+ }
+
+ if (pathname_.length() == 0 || this->DirectoryExists()) {
+ return true;
+ }
+
+ const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName());
+ return parent.CreateDirectoriesRecursively() && this->CreateFolder();
+}
+
+// Create the directory so that path exists. Returns true if successful or
+// if the directory already exists; returns false if unable to create the
+// directory for any reason, including if the parent directory does not
+// exist. Not named "CreateDirectory" because that's a macro on Windows.
+bool FilePath::CreateFolder() const {
+#if GTEST_OS_WINDOWS_MOBILE
+ FilePath removed_sep(this->RemoveTrailingPathSeparator());
+ LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
+ int result = CreateDirectory(unicode, NULL) ? 0 : -1;
+ delete [] unicode;
+#elif GTEST_OS_WINDOWS
+ int result = _mkdir(pathname_.c_str());
+#else
+ int result = mkdir(pathname_.c_str(), 0777);
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+ if (result == -1) {
+ return this->DirectoryExists(); // An error is OK if the directory exists.
+ }
+ return true; // No error.
+}
+
+// If input name has a trailing separator character, remove it and return the
+// name, otherwise return the name string unmodified.
+// On Windows platform, uses \ as the separator, other platforms use /.
+FilePath FilePath::RemoveTrailingPathSeparator() const {
+ return IsDirectory()
+ ? FilePath(pathname_.substr(0, pathname_.length() - 1))
+ : *this;
+}
+
+// Removes any redundant separators that might be in the pathname.
+// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
+// redundancies that might be in a pathname involving "." or "..".
+// FIXME: handle Windows network shares (e.g. \\server\share).
+void FilePath::Normalize() {
+ if (pathname_.c_str() == NULL) {
+ pathname_ = "";
+ return;
+ }
+ const char* src = pathname_.c_str();
+ char* const dest = new char[pathname_.length() + 1];
+ char* dest_ptr = dest;
+ memset(dest_ptr, 0, pathname_.length() + 1);
+
+ while (*src != '\0') {
+ *dest_ptr = *src;
+ if (!IsPathSeparator(*src)) {
+ src++;
+ } else {
+#if GTEST_HAS_ALT_PATH_SEP_
+ if (*dest_ptr == kAlternatePathSeparator) {
+ *dest_ptr = kPathSeparator;
+ }
+#endif
+ while (IsPathSeparator(*src))
+ src++;
+ }
+ dest_ptr++;
+ }
+ *dest_ptr = '\0';
+ pathname_ = dest;
+ delete[] dest;
+}
+
+} // namespace internal
+} // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fstream>
+
+#if GTEST_OS_WINDOWS
+# include <windows.h>
+# include <io.h>
+# include <sys/stat.h>
+# include <map> // Used in ThreadLocal.
+#else
+# include <unistd.h>
+#endif // GTEST_OS_WINDOWS
+
+#if GTEST_OS_MAC
+# include <mach/mach_init.h>
+# include <mach/task.h>
+# include <mach/vm_map.h>
+#endif // GTEST_OS_MAC
+
+#if GTEST_OS_QNX
+# include <devctl.h>
+# include <fcntl.h>
+# include <sys/procfs.h>
+#endif // GTEST_OS_QNX
+
+#if GTEST_OS_AIX
+# include <procinfo.h>
+# include <sys/types.h>
+#endif // GTEST_OS_AIX
+
+#if GTEST_OS_FUCHSIA
+# include <zircon/process.h>
+# include <zircon/syscalls.h>
+#endif // GTEST_OS_FUCHSIA
+
+
+namespace testing {
+namespace internal {
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+// MSVC and C++Builder do not provide a definition of STDERR_FILENO.
+const int kStdOutFileno = 1;
+const int kStdErrFileno = 2;
+#else
+const int kStdOutFileno = STDOUT_FILENO;
+const int kStdErrFileno = STDERR_FILENO;
+#endif // _MSC_VER
+
+#if GTEST_OS_LINUX
+
+namespace {
+template <typename T>
+T ReadProcFileField(const std::string& filename, int field) {
+ std::string dummy;
+ std::ifstream file(filename.c_str());
+ while (field-- > 0) {
+ file >> dummy;
+ }
+ T output = 0;
+ file >> output;
+ return output;
+}
+} // namespace
+
+// Returns the number of active threads, or 0 when there is an error.
+size_t GetThreadCount() {
+ const std::string filename =
+ (Message() << "/proc/" << getpid() << "/stat").GetString();
+ return ReadProcFileField<int>(filename, 19);
+}
+
+#elif GTEST_OS_MAC
+
+size_t GetThreadCount() {
+ const task_t task = mach_task_self();
+ mach_msg_type_number_t thread_count;
+ thread_act_array_t thread_list;
+ const kern_return_t status = task_threads(task, &thread_list, &thread_count);
+ if (status == KERN_SUCCESS) {
+ // task_threads allocates resources in thread_list and we need to free them
+ // to avoid leaks.
+ vm_deallocate(task,
+ reinterpret_cast<vm_address_t>(thread_list),
+ sizeof(thread_t) * thread_count);
+ return static_cast<size_t>(thread_count);
+ } else {
+ return 0;
+ }
+}
+
+#elif GTEST_OS_QNX
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+size_t GetThreadCount() {
+ const int fd = open("/proc/self/as", O_RDONLY);
+ if (fd < 0) {
+ return 0;
+ }
+ procfs_info process_info;
+ const int status =
+ devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), NULL);
+ close(fd);
+ if (status == EOK) {
+ return static_cast<size_t>(process_info.num_threads);
+ } else {
+ return 0;
+ }
+}
+
+#elif GTEST_OS_AIX
+
+size_t GetThreadCount() {
+ struct procentry64 entry;
+ pid_t pid = getpid();
+ int status = getprocs64(&entry, sizeof(entry), NULL, 0, &pid, 1);
+ if (status == 1) {
+ return entry.pi_thcount;
+ } else {
+ return 0;
+ }
+}
+
+#elif GTEST_OS_FUCHSIA
+
+size_t GetThreadCount() {
+ int dummy_buffer;
+ size_t avail;
+ zx_status_t status = zx_object_get_info(
+ zx_process_self(),
+ ZX_INFO_PROCESS_THREADS,
+ &dummy_buffer,
+ 0,
+ nullptr,
+ &avail);
+ if (status == ZX_OK) {
+ return avail;
+ } else {
+ return 0;
+ }
+}
+
+#else
+
+size_t GetThreadCount() {
+ // There's no portable way to detect the number of threads, so we just
+ // return 0 to indicate that we cannot detect it.
+ return 0;
+}
+
+#endif // GTEST_OS_LINUX
+
+#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS
+
+void SleepMilliseconds(int n) {
+ ::Sleep(n);
+}
+
+AutoHandle::AutoHandle()
+ : handle_(INVALID_HANDLE_VALUE) {}
+
+AutoHandle::AutoHandle(Handle handle)
+ : handle_(handle) {}
+
+AutoHandle::~AutoHandle() {
+ Reset();
+}
+
+AutoHandle::Handle AutoHandle::Get() const {
+ return handle_;
+}
+
+void AutoHandle::Reset() {
+ Reset(INVALID_HANDLE_VALUE);
+}
+
+void AutoHandle::Reset(HANDLE handle) {
+ // Resetting with the same handle we already own is invalid.
+ if (handle_ != handle) {
+ if (IsCloseable()) {
+ ::CloseHandle(handle_);
+ }
+ handle_ = handle;
+ } else {
+ GTEST_CHECK_(!IsCloseable())
+ << "Resetting a valid handle to itself is likely a programmer error "
+ "and thus not allowed.";
+ }
+}
+
+bool AutoHandle::IsCloseable() const {
+ // Different Windows APIs may use either of these values to represent an
+ // invalid handle.
+ return handle_ != NULL && handle_ != INVALID_HANDLE_VALUE;
+}
+
+Notification::Notification()
+ : event_(::CreateEvent(NULL, // Default security attributes.
+ TRUE, // Do not reset automatically.
+ FALSE, // Initially unset.
+ NULL)) { // Anonymous event.
+ GTEST_CHECK_(event_.Get() != NULL);
+}
+
+void Notification::Notify() {
+ GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE);
+}
+
+void Notification::WaitForNotification() {
+ GTEST_CHECK_(
+ ::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0);
+}
+
+Mutex::Mutex()
+ : owner_thread_id_(0),
+ type_(kDynamic),
+ critical_section_init_phase_(0),
+ critical_section_(new CRITICAL_SECTION) {
+ ::InitializeCriticalSection(critical_section_);
+}
+
+Mutex::~Mutex() {
+ // Static mutexes are leaked intentionally. It is not thread-safe to try
+ // to clean them up.
+ // FIXME: Switch to Slim Reader/Writer (SRW) Locks, which requires
+ // nothing to clean it up but is available only on Vista and later.
+ // https://docs.microsoft.com/en-us/windows/desktop/Sync/slim-reader-writer--srw--locks
+ if (type_ == kDynamic) {
+ ::DeleteCriticalSection(critical_section_);
+ delete critical_section_;
+ critical_section_ = NULL;
+ }
+}
+
+void Mutex::Lock() {
+ ThreadSafeLazyInit();
+ ::EnterCriticalSection(critical_section_);
+ owner_thread_id_ = ::GetCurrentThreadId();
+}
+
+void Mutex::Unlock() {
+ ThreadSafeLazyInit();
+ // We don't protect writing to owner_thread_id_ here, as it's the
+ // caller's responsibility to ensure that the current thread holds the
+ // mutex when this is called.
+ owner_thread_id_ = 0;
+ ::LeaveCriticalSection(critical_section_);
+}
+
+// Does nothing if the current thread holds the mutex. Otherwise, crashes
+// with high probability.
+void Mutex::AssertHeld() {
+ ThreadSafeLazyInit();
+ GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId())
+ << "The current thread is not holding the mutex @" << this;
+}
+
+namespace {
+
+// Use the RAII idiom to flag mem allocs that are intentionally never
+// deallocated. The motivation is to silence the false positive mem leaks
+// that are reported by the debug version of MS's CRT which can only detect
+// if an alloc is missing a matching deallocation.
+// Example:
+// MemoryIsNotDeallocated memory_is_not_deallocated;
+// critical_section_ = new CRITICAL_SECTION;
+//
+class MemoryIsNotDeallocated
+{
+ public:
+ MemoryIsNotDeallocated() : old_crtdbg_flag_(0) {
+#ifdef _MSC_VER
+ old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+ // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT
+ // doesn't report mem leak if there's no matching deallocation.
+ _CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF);
+#endif // _MSC_VER
+ }
+
+ ~MemoryIsNotDeallocated() {
+#ifdef _MSC_VER
+ // Restore the original _CRTDBG_ALLOC_MEM_DF flag
+ _CrtSetDbgFlag(old_crtdbg_flag_);
+#endif // _MSC_VER
+ }
+
+ private:
+ int old_crtdbg_flag_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated);
+};
+
+} // namespace
+
+// Initializes owner_thread_id_ and critical_section_ in static mutexes.
+void Mutex::ThreadSafeLazyInit() {
+ // Dynamic mutexes are initialized in the constructor.
+ if (type_ == kStatic) {
+ switch (
+ ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) {
+ case 0:
+ // If critical_section_init_phase_ was 0 before the exchange, we
+ // are the first to test it and need to perform the initialization.
+ owner_thread_id_ = 0;
+ {
+ // Use RAII to flag that following mem alloc is never deallocated.
+ MemoryIsNotDeallocated memory_is_not_deallocated;
+ critical_section_ = new CRITICAL_SECTION;
+ }
+ ::InitializeCriticalSection(critical_section_);
+ // Updates the critical_section_init_phase_ to 2 to signal
+ // initialization complete.
+ GTEST_CHECK_(::InterlockedCompareExchange(
+ &critical_section_init_phase_, 2L, 1L) ==
+ 1L);
+ break;
+ case 1:
+ // Somebody else is already initializing the mutex; spin until they
+ // are done.
+ while (::InterlockedCompareExchange(&critical_section_init_phase_,
+ 2L,
+ 2L) != 2L) {
+ // Possibly yields the rest of the thread's time slice to other
+ // threads.
+ ::Sleep(0);
+ }
+ break;
+
+ case 2:
+ break; // The mutex is already initialized and ready for use.
+
+ default:
+ GTEST_CHECK_(false)
+ << "Unexpected value of critical_section_init_phase_ "
+ << "while initializing a static mutex.";
+ }
+ }
+}
+
+namespace {
+
+class ThreadWithParamSupport : public ThreadWithParamBase {
+ public:
+ static HANDLE CreateThread(Runnable* runnable,
+ Notification* thread_can_start) {
+ ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start);
+ DWORD thread_id;
+ // FIXME: Consider to use _beginthreadex instead.
+ HANDLE thread_handle = ::CreateThread(
+ NULL, // Default security.
+ 0, // Default stack size.
+ &ThreadWithParamSupport::ThreadMain,
+ param, // Parameter to ThreadMainStatic
+ 0x0, // Default creation flags.
+ &thread_id); // Need a valid pointer for the call to work under Win98.
+ GTEST_CHECK_(thread_handle != NULL) << "CreateThread failed with error "
+ << ::GetLastError() << ".";
+ if (thread_handle == NULL) {
+ delete param;
+ }
+ return thread_handle;
+ }
+
+ private:
+ struct ThreadMainParam {
+ ThreadMainParam(Runnable* runnable, Notification* thread_can_start)
+ : runnable_(runnable),
+ thread_can_start_(thread_can_start) {
+ }
+ scoped_ptr<Runnable> runnable_;
+ // Does not own.
+ Notification* thread_can_start_;
+ };
+
+ static DWORD WINAPI ThreadMain(void* ptr) {
+ // Transfers ownership.
+ scoped_ptr<ThreadMainParam> param(static_cast<ThreadMainParam*>(ptr));
+ if (param->thread_can_start_ != NULL)
+ param->thread_can_start_->WaitForNotification();
+ param->runnable_->Run();
+ return 0;
+ }
+
+ // Prohibit instantiation.
+ ThreadWithParamSupport();
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport);
+};
+
+} // namespace
+
+ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable,
+ Notification* thread_can_start)
+ : thread_(ThreadWithParamSupport::CreateThread(runnable,
+ thread_can_start)) {
+}
+
+ThreadWithParamBase::~ThreadWithParamBase() {
+ Join();
+}
+
+void ThreadWithParamBase::Join() {
+ GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0)
+ << "Failed to join the thread with error " << ::GetLastError() << ".";
+}
+
+// Maps a thread to a set of ThreadIdToThreadLocals that have values
+// instantiated on that thread and notifies them when the thread exits. A
+// ThreadLocal instance is expected to persist until all threads it has
+// values on have terminated.
+class ThreadLocalRegistryImpl {
+ public:
+ // Registers thread_local_instance as having value on the current thread.
+ // Returns a value that can be used to identify the thread from other threads.
+ static ThreadLocalValueHolderBase* GetValueOnCurrentThread(
+ const ThreadLocalBase* thread_local_instance) {
+ DWORD current_thread = ::GetCurrentThreadId();
+ MutexLock lock(&mutex_);
+ ThreadIdToThreadLocals* const thread_to_thread_locals =
+ GetThreadLocalsMapLocked();
+ ThreadIdToThreadLocals::iterator thread_local_pos =
+ thread_to_thread_locals->find(current_thread);
+ if (thread_local_pos == thread_to_thread_locals->end()) {
+ thread_local_pos = thread_to_thread_locals->insert(
+ std::make_pair(current_thread, ThreadLocalValues())).first;
+ StartWatcherThreadFor(current_thread);
+ }
+ ThreadLocalValues& thread_local_values = thread_local_pos->second;
+ ThreadLocalValues::iterator value_pos =
+ thread_local_values.find(thread_local_instance);
+ if (value_pos == thread_local_values.end()) {
+ value_pos =
+ thread_local_values
+ .insert(std::make_pair(
+ thread_local_instance,
+ linked_ptr<ThreadLocalValueHolderBase>(
+ thread_local_instance->NewValueForCurrentThread())))
+ .first;
+ }
+ return value_pos->second.get();
+ }
+
+ static void OnThreadLocalDestroyed(
+ const ThreadLocalBase* thread_local_instance) {
+ std::vector<linked_ptr<ThreadLocalValueHolderBase> > value_holders;
+ // Clean up the ThreadLocalValues data structure while holding the lock, but
+ // defer the destruction of the ThreadLocalValueHolderBases.
+ {
+ MutexLock lock(&mutex_);
+ ThreadIdToThreadLocals* const thread_to_thread_locals =
+ GetThreadLocalsMapLocked();
+ for (ThreadIdToThreadLocals::iterator it =
+ thread_to_thread_locals->begin();
+ it != thread_to_thread_locals->end();
+ ++it) {
+ ThreadLocalValues& thread_local_values = it->second;
+ ThreadLocalValues::iterator value_pos =
+ thread_local_values.find(thread_local_instance);
+ if (value_pos != thread_local_values.end()) {
+ value_holders.push_back(value_pos->second);
+ thread_local_values.erase(value_pos);
+ // This 'if' can only be successful at most once, so theoretically we
+ // could break out of the loop here, but we don't bother doing so.
+ }
+ }
+ }
+ // Outside the lock, let the destructor for 'value_holders' deallocate the
+ // ThreadLocalValueHolderBases.
+ }
+
+ static void OnThreadExit(DWORD thread_id) {
+ GTEST_CHECK_(thread_id != 0) << ::GetLastError();
+ std::vector<linked_ptr<ThreadLocalValueHolderBase> > value_holders;
+ // Clean up the ThreadIdToThreadLocals data structure while holding the
+ // lock, but defer the destruction of the ThreadLocalValueHolderBases.
+ {
+ MutexLock lock(&mutex_);
+ ThreadIdToThreadLocals* const thread_to_thread_locals =
+ GetThreadLocalsMapLocked();
+ ThreadIdToThreadLocals::iterator thread_local_pos =
+ thread_to_thread_locals->find(thread_id);
+ if (thread_local_pos != thread_to_thread_locals->end()) {
+ ThreadLocalValues& thread_local_values = thread_local_pos->second;
+ for (ThreadLocalValues::iterator value_pos =
+ thread_local_values.begin();
+ value_pos != thread_local_values.end();
+ ++value_pos) {
+ value_holders.push_back(value_pos->second);
+ }
+ thread_to_thread_locals->erase(thread_local_pos);
+ }
+ }
+ // Outside the lock, let the destructor for 'value_holders' deallocate the
+ // ThreadLocalValueHolderBases.
+ }
+
+ private:
+ // In a particular thread, maps a ThreadLocal object to its value.
+ typedef std::map<const ThreadLocalBase*,
+ linked_ptr<ThreadLocalValueHolderBase> > ThreadLocalValues;
+ // Stores all ThreadIdToThreadLocals having values in a thread, indexed by
+ // thread's ID.
+ typedef std::map<DWORD, ThreadLocalValues> ThreadIdToThreadLocals;
+
+ // Holds the thread id and thread handle that we pass from
+ // StartWatcherThreadFor to WatcherThreadFunc.
+ typedef std::pair<DWORD, HANDLE> ThreadIdAndHandle;
+
+ static void StartWatcherThreadFor(DWORD thread_id) {
+ // The returned handle will be kept in thread_map and closed by
+ // watcher_thread in WatcherThreadFunc.
+ HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION,
+ FALSE,
+ thread_id);
+ GTEST_CHECK_(thread != NULL);
+ // We need to pass a valid thread ID pointer into CreateThread for it
+ // to work correctly under Win98.
+ DWORD watcher_thread_id;
+ HANDLE watcher_thread = ::CreateThread(
+ NULL, // Default security.
+ 0, // Default stack size
+ &ThreadLocalRegistryImpl::WatcherThreadFunc,
+ reinterpret_cast<LPVOID>(new ThreadIdAndHandle(thread_id, thread)),
+ CREATE_SUSPENDED,
+ &watcher_thread_id);
+ GTEST_CHECK_(watcher_thread != NULL);
+ // Give the watcher thread the same priority as ours to avoid being
+ // blocked by it.
+ ::SetThreadPriority(watcher_thread,
+ ::GetThreadPriority(::GetCurrentThread()));
+ ::ResumeThread(watcher_thread);
+ ::CloseHandle(watcher_thread);
+ }
+
+ // Monitors exit from a given thread and notifies those
+ // ThreadIdToThreadLocals about thread termination.
+ static DWORD WINAPI WatcherThreadFunc(LPVOID param) {
+ const ThreadIdAndHandle* tah =
+ reinterpret_cast<const ThreadIdAndHandle*>(param);
+ GTEST_CHECK_(
+ ::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0);
+ OnThreadExit(tah->first);
+ ::CloseHandle(tah->second);
+ delete tah;
+ return 0;
+ }
+
+ // Returns map of thread local instances.
+ static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() {
+ mutex_.AssertHeld();
+ MemoryIsNotDeallocated memory_is_not_deallocated;
+ static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals();
+ return map;
+ }
+
+ // Protects access to GetThreadLocalsMapLocked() and its return value.
+ static Mutex mutex_;
+ // Protects access to GetThreadMapLocked() and its return value.
+ static Mutex thread_map_mutex_;
+};
+
+Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex);
+Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex);
+
+ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread(
+ const ThreadLocalBase* thread_local_instance) {
+ return ThreadLocalRegistryImpl::GetValueOnCurrentThread(
+ thread_local_instance);
+}
+
+void ThreadLocalRegistry::OnThreadLocalDestroyed(
+ const ThreadLocalBase* thread_local_instance) {
+ ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance);
+}
+
+#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS
+
+#if GTEST_USES_POSIX_RE
+
+// Implements RE. Currently only needed for death tests.
+
+RE::~RE() {
+ if (is_valid_) {
+ // regfree'ing an invalid regex might crash because the content
+ // of the regex is undefined. Since the regex's are essentially
+ // the same, one cannot be valid (or invalid) without the other
+ // being so too.
+ regfree(&partial_regex_);
+ regfree(&full_regex_);
+ }
+ free(const_cast<char*>(pattern_));
+}
+
+// Returns true iff regular expression re matches the entire str.
+bool RE::FullMatch(const char* str, const RE& re) {
+ if (!re.is_valid_) return false;
+
+ regmatch_t match;
+ return regexec(&re.full_regex_, str, 1, &match, 0) == 0;
+}
+
+// Returns true iff regular expression re matches a substring of str
+// (including str itself).
+bool RE::PartialMatch(const char* str, const RE& re) {
+ if (!re.is_valid_) return false;
+
+ regmatch_t match;
+ return regexec(&re.partial_regex_, str, 1, &match, 0) == 0;
+}
+
+// Initializes an RE from its string representation.
+void RE::Init(const char* regex) {
+ pattern_ = posix::StrDup(regex);
+
+ // Reserves enough bytes to hold the regular expression used for a
+ // full match.
+ const size_t full_regex_len = strlen(regex) + 10;
+ char* const full_pattern = new char[full_regex_len];
+
+ snprintf(full_pattern, full_regex_len, "^(%s)$", regex);
+ is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0;
+ // We want to call regcomp(&partial_regex_, ...) even if the
+ // previous expression returns false. Otherwise partial_regex_ may
+ // not be properly initialized can may cause trouble when it's
+ // freed.
+ //
+ // Some implementation of POSIX regex (e.g. on at least some
+ // versions of Cygwin) doesn't accept the empty string as a valid
+ // regex. We change it to an equivalent form "()" to be safe.
+ if (is_valid_) {
+ const char* const partial_regex = (*regex == '\0') ? "()" : regex;
+ is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0;
+ }
+ EXPECT_TRUE(is_valid_)
+ << "Regular expression \"" << regex
+ << "\" is not a valid POSIX Extended regular expression.";
+
+ delete[] full_pattern;
+}
+
+#elif GTEST_USES_SIMPLE_RE
+
+// Returns true iff ch appears anywhere in str (excluding the
+// terminating '\0' character).
+bool IsInSet(char ch, const char* str) {
+ return ch != '\0' && strchr(str, ch) != NULL;
+}
+
+// Returns true iff ch belongs to the given classification. Unlike
+// similar functions in <ctype.h>, these aren't affected by the
+// current locale.
+bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; }
+bool IsAsciiPunct(char ch) {
+ return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~");
+}
+bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); }
+bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); }
+bool IsAsciiWordChar(char ch) {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') ||
+ ('0' <= ch && ch <= '9') || ch == '_';
+}
+
+// Returns true iff "\\c" is a supported escape sequence.
+bool IsValidEscape(char c) {
+ return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW"));
+}
+
+// Returns true iff the given atom (specified by escaped and pattern)
+// matches ch. The result is undefined if the atom is invalid.
+bool AtomMatchesChar(bool escaped, char pattern_char, char ch) {
+ if (escaped) { // "\\p" where p is pattern_char.
+ switch (pattern_char) {
+ case 'd': return IsAsciiDigit(ch);
+ case 'D': return !IsAsciiDigit(ch);
+ case 'f': return ch == '\f';
+ case 'n': return ch == '\n';
+ case 'r': return ch == '\r';
+ case 's': return IsAsciiWhiteSpace(ch);
+ case 'S': return !IsAsciiWhiteSpace(ch);
+ case 't': return ch == '\t';
+ case 'v': return ch == '\v';
+ case 'w': return IsAsciiWordChar(ch);
+ case 'W': return !IsAsciiWordChar(ch);
+ }
+ return IsAsciiPunct(pattern_char) && pattern_char == ch;
+ }
+
+ return (pattern_char == '.' && ch != '\n') || pattern_char == ch;
+}
+
+// Helper function used by ValidateRegex() to format error messages.
+static std::string FormatRegexSyntaxError(const char* regex, int index) {
+ return (Message() << "Syntax error at index " << index
+ << " in simple regular expression \"" << regex << "\": ").GetString();
+}
+
+// Generates non-fatal failures and returns false if regex is invalid;
+// otherwise returns true.
+bool ValidateRegex(const char* regex) {
+ if (regex == NULL) {
+ // FIXME: fix the source file location in the
+ // assertion failures to match where the regex is used in user
+ // code.
+ ADD_FAILURE() << "NULL is not a valid simple regular expression.";
+ return false;
+ }
+
+ bool is_valid = true;
+
+ // True iff ?, *, or + can follow the previous atom.
+ bool prev_repeatable = false;
+ for (int i = 0; regex[i]; i++) {
+ if (regex[i] == '\\') { // An escape sequence
+ i++;
+ if (regex[i] == '\0') {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1)
+ << "'\\' cannot appear at the end.";
+ return false;
+ }
+
+ if (!IsValidEscape(regex[i])) {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1)
+ << "invalid escape sequence \"\\" << regex[i] << "\".";
+ is_valid = false;
+ }
+ prev_repeatable = true;
+ } else { // Not an escape sequence.
+ const char ch = regex[i];
+
+ if (ch == '^' && i > 0) {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+ << "'^' can only appear at the beginning.";
+ is_valid = false;
+ } else if (ch == '$' && regex[i + 1] != '\0') {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+ << "'$' can only appear at the end.";
+ is_valid = false;
+ } else if (IsInSet(ch, "()[]{}|")) {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+ << "'" << ch << "' is unsupported.";
+ is_valid = false;
+ } else if (IsRepeat(ch) && !prev_repeatable) {
+ ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+ << "'" << ch << "' can only follow a repeatable token.";
+ is_valid = false;
+ }
+
+ prev_repeatable = !IsInSet(ch, "^$?*+");
+ }
+ }
+
+ return is_valid;
+}
+
+// Matches a repeated regex atom followed by a valid simple regular
+// expression. The regex atom is defined as c if escaped is false,
+// or \c otherwise. repeat is the repetition meta character (?, *,
+// or +). The behavior is undefined if str contains too many
+// characters to be indexable by size_t, in which case the test will
+// probably time out anyway. We are fine with this limitation as
+// std::string has it too.
+bool MatchRepetitionAndRegexAtHead(
+ bool escaped, char c, char repeat, const char* regex,
+ const char* str) {
+ const size_t min_count = (repeat == '+') ? 1 : 0;
+ const size_t max_count = (repeat == '?') ? 1 :
+ static_cast<size_t>(-1) - 1;
+ // We cannot call numeric_limits::max() as it conflicts with the
+ // max() macro on Windows.
+
+ for (size_t i = 0; i <= max_count; ++i) {
+ // We know that the atom matches each of the first i characters in str.
+ if (i >= min_count && MatchRegexAtHead(regex, str + i)) {
+ // We have enough matches at the head, and the tail matches too.
+ // Since we only care about *whether* the pattern matches str
+ // (as opposed to *how* it matches), there is no need to find a
+ // greedy match.
+ return true;
+ }
+ if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i]))
+ return false;
+ }
+ return false;
+}
+
+// Returns true iff regex matches a prefix of str. regex must be a
+// valid simple regular expression and not start with "^", or the
+// result is undefined.
+bool MatchRegexAtHead(const char* regex, const char* str) {
+ if (*regex == '\0') // An empty regex matches a prefix of anything.
+ return true;
+
+ // "$" only matches the end of a string. Note that regex being
+ // valid guarantees that there's nothing after "$" in it.
+ if (*regex == '$')
+ return *str == '\0';
+
+ // Is the first thing in regex an escape sequence?
+ const bool escaped = *regex == '\\';
+ if (escaped)
+ ++regex;
+ if (IsRepeat(regex[1])) {
+ // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so
+ // here's an indirect recursion. It terminates as the regex gets
+ // shorter in each recursion.
+ return MatchRepetitionAndRegexAtHead(
+ escaped, regex[0], regex[1], regex + 2, str);
+ } else {
+ // regex isn't empty, isn't "$", and doesn't start with a
+ // repetition. We match the first atom of regex with the first
+ // character of str and recurse.
+ return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) &&
+ MatchRegexAtHead(regex + 1, str + 1);
+ }
+}
+
+// Returns true iff regex matches any substring of str. regex must be
+// a valid simple regular expression, or the result is undefined.
+//
+// The algorithm is recursive, but the recursion depth doesn't exceed
+// the regex length, so we won't need to worry about running out of
+// stack space normally. In rare cases the time complexity can be
+// exponential with respect to the regex length + the string length,
+// but usually it's must faster (often close to linear).
+bool MatchRegexAnywhere(const char* regex, const char* str) {
+ if (regex == NULL || str == NULL)
+ return false;
+
+ if (*regex == '^')
+ return MatchRegexAtHead(regex + 1, str);
+
+ // A successful match can be anywhere in str.
+ do {
+ if (MatchRegexAtHead(regex, str))
+ return true;
+ } while (*str++ != '\0');
+ return false;
+}
+
+// Implements the RE class.
+
+RE::~RE() {
+ free(const_cast<char*>(pattern_));
+ free(const_cast<char*>(full_pattern_));
+}
+
+// Returns true iff regular expression re matches the entire str.
+bool RE::FullMatch(const char* str, const RE& re) {
+ return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str);
+}
+
+// Returns true iff regular expression re matches a substring of str
+// (including str itself).
+bool RE::PartialMatch(const char* str, const RE& re) {
+ return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str);
+}
+
+// Initializes an RE from its string representation.
+void RE::Init(const char* regex) {
+ pattern_ = full_pattern_ = NULL;
+ if (regex != NULL) {
+ pattern_ = posix::StrDup(regex);
+ }
+
+ is_valid_ = ValidateRegex(regex);
+ if (!is_valid_) {
+ // No need to calculate the full pattern when the regex is invalid.
+ return;
+ }
+
+ const size_t len = strlen(regex);
+ // Reserves enough bytes to hold the regular expression used for a
+ // full match: we need space to prepend a '^', append a '$', and
+ // terminate the string with '\0'.
+ char* buffer = static_cast<char*>(malloc(len + 3));
+ full_pattern_ = buffer;
+
+ if (*regex != '^')
+ *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'.
+
+ // We don't use snprintf or strncpy, as they trigger a warning when
+ // compiled with VC++ 8.0.
+ memcpy(buffer, regex, len);
+ buffer += len;
+
+ if (len == 0 || regex[len - 1] != '$')
+ *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'.
+
+ *buffer = '\0';
+}
+
+#endif // GTEST_USES_POSIX_RE
+
+const char kUnknownFile[] = "unknown file";
+
+// Formats a source file path and a line number as they would appear
+// in an error message from the compiler used to compile this code.
+GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) {
+ const std::string file_name(file == NULL ? kUnknownFile : file);
+
+ if (line < 0) {
+ return file_name + ":";
+ }
+#ifdef _MSC_VER
+ return file_name + "(" + StreamableToString(line) + "):";
+#else
+ return file_name + ":" + StreamableToString(line) + ":";
+#endif // _MSC_VER
+}
+
+// Formats a file location for compiler-independent XML output.
+// Although this function is not platform dependent, we put it next to
+// FormatFileLocation in order to contrast the two functions.
+// Note that FormatCompilerIndependentFileLocation() does NOT append colon
+// to the file location it produces, unlike FormatFileLocation().
+GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(
+ const char* file, int line) {
+ const std::string file_name(file == NULL ? kUnknownFile : file);
+
+ if (line < 0)
+ return file_name;
+ else
+ return file_name + ":" + StreamableToString(line);
+}
+
+GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line)
+ : severity_(severity) {
+ const char* const marker =
+ severity == GTEST_INFO ? "[ INFO ]" :
+ severity == GTEST_WARNING ? "[WARNING]" :
+ severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]";
+ GetStream() << ::std::endl << marker << " "
+ << FormatFileLocation(file, line).c_str() << ": ";
+}
+
+// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program.
+GTestLog::~GTestLog() {
+ GetStream() << ::std::endl;
+ if (severity_ == GTEST_FATAL) {
+ fflush(stderr);
+ posix::Abort();
+ }
+}
+
+// Disable Microsoft deprecation warnings for POSIX functions called from
+// this class (creat, dup, dup2, and close)
+GTEST_DISABLE_MSC_DEPRECATED_PUSH_()
+
+#if GTEST_HAS_STREAM_REDIRECTION
+
+// Object that captures an output stream (stdout/stderr).
+class CapturedStream {
+ public:
+ // The ctor redirects the stream to a temporary file.
+ explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) {
+# if GTEST_OS_WINDOWS
+ char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT
+ char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT
+
+ ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path);
+ const UINT success = ::GetTempFileNameA(temp_dir_path,
+ "gtest_redir",
+ 0, // Generate unique file name.
+ temp_file_path);
+ GTEST_CHECK_(success != 0)
+ << "Unable to create a temporary file in " << temp_dir_path;
+ const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE);
+ GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file "
+ << temp_file_path;
+ filename_ = temp_file_path;
+# else
+ // There's no guarantee that a test has write access to the current
+ // directory, so we create the temporary file in the /tmp directory
+ // instead. We use /tmp on most systems, and /sdcard on Android.
+ // That's because Android doesn't have /tmp.
+# if GTEST_OS_LINUX_ANDROID
+ // Note: Android applications are expected to call the framework's
+ // Context.getExternalStorageDirectory() method through JNI to get
+ // the location of the world-writable SD Card directory. However,
+ // this requires a Context handle, which cannot be retrieved
+ // globally from native code. Doing so also precludes running the
+ // code as part of a regular standalone executable, which doesn't
+ // run in a Dalvik process (e.g. when running it through 'adb shell').
+ //
+ // The location /sdcard is directly accessible from native code
+ // and is the only location (unofficially) supported by the Android
+ // team. It's generally a symlink to the real SD Card mount point
+ // which can be /mnt/sdcard, /mnt/sdcard0, /system/media/sdcard, or
+ // other OEM-customized locations. Never rely on these, and always
+ // use /sdcard.
+ char name_template[] = "/sdcard/gtest_captured_stream.XXXXXX";
+# else
+ char name_template[] = "/tmp/captured_stream.XXXXXX";
+# endif // GTEST_OS_LINUX_ANDROID
+ const int captured_fd = mkstemp(name_template);
+ filename_ = name_template;
+# endif // GTEST_OS_WINDOWS
+ fflush(NULL);
+ dup2(captured_fd, fd_);
+ close(captured_fd);
+ }
+
+ ~CapturedStream() {
+ remove(filename_.c_str());
+ }
+
+ std::string GetCapturedString() {
+ if (uncaptured_fd_ != -1) {
+ // Restores the original stream.
+ fflush(NULL);
+ dup2(uncaptured_fd_, fd_);
+ close(uncaptured_fd_);
+ uncaptured_fd_ = -1;
+ }
+
+ FILE* const file = posix::FOpen(filename_.c_str(), "r");
+ const std::string content = ReadEntireFile(file);
+ posix::FClose(file);
+ return content;
+ }
+
+ private:
+ const int fd_; // A stream to capture.
+ int uncaptured_fd_;
+ // Name of the temporary file holding the stderr output.
+ ::std::string filename_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream);
+};
+
+GTEST_DISABLE_MSC_DEPRECATED_POP_()
+
+static CapturedStream* g_captured_stderr = NULL;
+static CapturedStream* g_captured_stdout = NULL;
+
+// Starts capturing an output stream (stdout/stderr).
+static void CaptureStream(int fd, const char* stream_name,
+ CapturedStream** stream) {
+ if (*stream != NULL) {
+ GTEST_LOG_(FATAL) << "Only one " << stream_name
+ << " capturer can exist at a time.";
+ }
+ *stream = new CapturedStream(fd);
+}
+
+// Stops capturing the output stream and returns the captured string.
+static std::string GetCapturedStream(CapturedStream** captured_stream) {
+ const std::string content = (*captured_stream)->GetCapturedString();
+
+ delete *captured_stream;
+ *captured_stream = NULL;
+
+ return content;
+}
+
+// Starts capturing stdout.
+void CaptureStdout() {
+ CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout);
+}
+
+// Starts capturing stderr.
+void CaptureStderr() {
+ CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr);
+}
+
+// Stops capturing stdout and returns the captured string.
+std::string GetCapturedStdout() {
+ return GetCapturedStream(&g_captured_stdout);
+}
+
+// Stops capturing stderr and returns the captured string.
+std::string GetCapturedStderr() {
+ return GetCapturedStream(&g_captured_stderr);
+}
+
+#endif // GTEST_HAS_STREAM_REDIRECTION
+
+
+
+
+
+size_t GetFileSize(FILE* file) {
+ fseek(file, 0, SEEK_END);
+ return static_cast<size_t>(ftell(file));
+}
+
+std::string ReadEntireFile(FILE* file) {
+ const size_t file_size = GetFileSize(file);
+ char* const buffer = new char[file_size];
+
+ size_t bytes_last_read = 0; // # of bytes read in the last fread()
+ size_t bytes_read = 0; // # of bytes read so far
+
+ fseek(file, 0, SEEK_SET);
+
+ // Keeps reading the file until we cannot read further or the
+ // pre-determined file size is reached.
+ do {
+ bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file);
+ bytes_read += bytes_last_read;
+ } while (bytes_last_read > 0 && bytes_read < file_size);
+
+ const std::string content(buffer, bytes_read);
+ delete[] buffer;
+
+ return content;
+}
+
+#if GTEST_HAS_DEATH_TEST
+static const std::vector<std::string>* g_injected_test_argvs = NULL; // Owned.
+
+std::vector<std::string> GetInjectableArgvs() {
+ if (g_injected_test_argvs != NULL) {
+ return *g_injected_test_argvs;
+ }
+ return GetArgvs();
+}
+
+void SetInjectableArgvs(const std::vector<std::string>* new_argvs) {
+ if (g_injected_test_argvs != new_argvs) delete g_injected_test_argvs;
+ g_injected_test_argvs = new_argvs;
+}
+
+void SetInjectableArgvs(const std::vector<std::string>& new_argvs) {
+ SetInjectableArgvs(
+ new std::vector<std::string>(new_argvs.begin(), new_argvs.end()));
+}
+
+#if GTEST_HAS_GLOBAL_STRING
+void SetInjectableArgvs(const std::vector< ::string>& new_argvs) {
+ SetInjectableArgvs(
+ new std::vector<std::string>(new_argvs.begin(), new_argvs.end()));
+}
+#endif // GTEST_HAS_GLOBAL_STRING
+
+void ClearInjectableArgvs() {
+ delete g_injected_test_argvs;
+ g_injected_test_argvs = NULL;
+}
+#endif // GTEST_HAS_DEATH_TEST
+
+#if GTEST_OS_WINDOWS_MOBILE
+namespace posix {
+void Abort() {
+ DebugBreak();
+ TerminateProcess(GetCurrentProcess(), 1);
+}
+} // namespace posix
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+// Returns the name of the environment variable corresponding to the
+// given flag. For example, FlagToEnvVar("foo") will return
+// "GTEST_FOO" in the open-source version.
+static std::string FlagToEnvVar(const char* flag) {
+ const std::string full_flag =
+ (Message() << GTEST_FLAG_PREFIX_ << flag).GetString();
+
+ Message env_var;
+ for (size_t i = 0; i != full_flag.length(); i++) {
+ env_var << ToUpper(full_flag.c_str()[i]);
+ }
+
+ return env_var.GetString();
+}
+
+// Parses 'str' for a 32-bit signed integer. If successful, writes
+// the result to *value and returns true; otherwise leaves *value
+// unchanged and returns false.
+bool ParseInt32(const Message& src_text, const char* str, Int32* value) {
+ // Parses the environment variable as a decimal integer.
+ char* end = NULL;
+ const long long_value = strtol(str, &end, 10); // NOLINT
+
+ // Has strtol() consumed all characters in the string?
+ if (*end != '\0') {
+ // No - an invalid character was encountered.
+ Message msg;
+ msg << "WARNING: " << src_text
+ << " is expected to be a 32-bit integer, but actually"
+ << " has value \"" << str << "\".\n";
+ printf("%s", msg.GetString().c_str());
+ fflush(stdout);
+ return false;
+ }
+
+ // Is the parsed value in the range of an Int32?
+ const Int32 result = static_cast<Int32>(long_value);
+ if (long_value == LONG_MAX || long_value == LONG_MIN ||
+ // The parsed value overflows as a long. (strtol() returns
+ // LONG_MAX or LONG_MIN when the input overflows.)
+ result != long_value
+ // The parsed value overflows as an Int32.
+ ) {
+ Message msg;
+ msg << "WARNING: " << src_text
+ << " is expected to be a 32-bit integer, but actually"
+ << " has value " << str << ", which overflows.\n";
+ printf("%s", msg.GetString().c_str());
+ fflush(stdout);
+ return false;
+ }
+
+ *value = result;
+ return true;
+}
+
+// Reads and returns the Boolean environment variable corresponding to
+// the given flag; if it's not set, returns default_value.
+//
+// The value is considered true iff it's not "0".
+bool BoolFromGTestEnv(const char* flag, bool default_value) {
+#if defined(GTEST_GET_BOOL_FROM_ENV_)
+ return GTEST_GET_BOOL_FROM_ENV_(flag, default_value);
+#else
+ const std::string env_var = FlagToEnvVar(flag);
+ const char* const string_value = posix::GetEnv(env_var.c_str());
+ return string_value == NULL ?
+ default_value : strcmp(string_value, "0") != 0;
+#endif // defined(GTEST_GET_BOOL_FROM_ENV_)
+}
+
+// Reads and returns a 32-bit integer stored in the environment
+// variable corresponding to the given flag; if it isn't set or
+// doesn't represent a valid 32-bit integer, returns default_value.
+Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) {
+#if defined(GTEST_GET_INT32_FROM_ENV_)
+ return GTEST_GET_INT32_FROM_ENV_(flag, default_value);
+#else
+ const std::string env_var = FlagToEnvVar(flag);
+ const char* const string_value = posix::GetEnv(env_var.c_str());
+ if (string_value == NULL) {
+ // The environment variable is not set.
+ return default_value;
+ }
+
+ Int32 result = default_value;
+ if (!ParseInt32(Message() << "Environment variable " << env_var,
+ string_value, &result)) {
+ printf("The default value %s is used.\n",
+ (Message() << default_value).GetString().c_str());
+ fflush(stdout);
+ return default_value;
+ }
+
+ return result;
+#endif // defined(GTEST_GET_INT32_FROM_ENV_)
+}
+
+// As a special case for the 'output' flag, if GTEST_OUTPUT is not
+// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build
+// system. The value of XML_OUTPUT_FILE is a filename without the
+// "xml:" prefix of GTEST_OUTPUT.
+// Note that this is meant to be called at the call site so it does
+// not check that the flag is 'output'
+// In essence this checks an env variable called XML_OUTPUT_FILE
+// and if it is set we prepend "xml:" to its value, if it not set we return ""
+std::string OutputFlagAlsoCheckEnvVar(){
+ std::string default_value_for_output_flag = "";
+ const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE");
+ if (NULL != xml_output_file_env) {
+ default_value_for_output_flag = std::string("xml:") + xml_output_file_env;
+ }
+ return default_value_for_output_flag;
+}
+
+// Reads and returns the string environment variable corresponding to
+// the given flag; if it's not set, returns default_value.
+const char* StringFromGTestEnv(const char* flag, const char* default_value) {
+#if defined(GTEST_GET_STRING_FROM_ENV_)
+ return GTEST_GET_STRING_FROM_ENV_(flag, default_value);
+#else
+ const std::string env_var = FlagToEnvVar(flag);
+ const char* const value = posix::GetEnv(env_var.c_str());
+ return value == NULL ? default_value : value;
+#endif // defined(GTEST_GET_STRING_FROM_ENV_)
+}
+
+} // namespace internal
+} // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Test - The Google C++ Testing and Mocking Framework
+//
+// This file implements a universal value printer that can print a
+// value of any type T:
+//
+// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
+//
+// It uses the << operator when possible, and prints the bytes in the
+// object otherwise. A user can override its behavior for a class
+// type Foo by defining either operator<<(::std::ostream&, const Foo&)
+// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that
+// defines Foo.
+
+#include <stdio.h>
+#include <cctype>
+#include <cwchar>
+#include <ostream> // NOLINT
+#include <string>
+
+namespace testing {
+
+namespace {
+
+using ::std::ostream;
+
+// Prints a segment of bytes in the given object.
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start,
+ size_t count, ostream* os) {
+ char text[5] = "";
+ for (size_t i = 0; i != count; i++) {
+ const size_t j = start + i;
+ if (i != 0) {
+ // Organizes the bytes into groups of 2 for easy parsing by
+ // human.
+ if ((j % 2) == 0)
+ *os << ' ';
+ else
+ *os << '-';
+ }
+ GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]);
+ *os << text;
+ }
+}
+
+// Prints the bytes in the given value to the given ostream.
+void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
+ ostream* os) {
+ // Tells the user how big the object is.
+ *os << count << "-byte object <";
+
+ const size_t kThreshold = 132;
+ const size_t kChunkSize = 64;
+ // If the object size is bigger than kThreshold, we'll have to omit
+ // some details by printing only the first and the last kChunkSize
+ // bytes.
+ // FIXME: let the user control the threshold using a flag.
+ if (count < kThreshold) {
+ PrintByteSegmentInObjectTo(obj_bytes, 0, count, os);
+ } else {
+ PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os);
+ *os << " ... ";
+ // Rounds up to 2-byte boundary.
+ const size_t resume_pos = (count - kChunkSize + 1)/2*2;
+ PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os);
+ }
+ *os << ">";
+}
+
+} // namespace
+
+namespace internal2 {
+
+// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
+// given object. The delegation simplifies the implementation, which
+// uses the << operator and thus is easier done outside of the
+// ::testing::internal namespace, which contains a << operator that
+// sometimes conflicts with the one in STL.
+void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
+ ostream* os) {
+ PrintBytesInObjectToImpl(obj_bytes, count, os);
+}
+
+} // namespace internal2
+
+namespace internal {
+
+// Depending on the value of a char (or wchar_t), we print it in one
+// of three formats:
+// - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
+// - as a hexadecimal escape sequence (e.g. '\x7F'), or
+// - as a special escape sequence (e.g. '\r', '\n').
+enum CharFormat {
+ kAsIs,
+ kHexEscape,
+ kSpecialEscape
+};
+
+// Returns true if c is a printable ASCII character. We test the
+// value of c directly instead of calling isprint(), which is buggy on
+// Windows Mobile.
+inline bool IsPrintableAscii(wchar_t c) {
+ return 0x20 <= c && c <= 0x7E;
+}
+
+// Prints a wide or narrow char c as a character literal without the
+// quotes, escaping it when necessary; returns how c was formatted.
+// The template argument UnsignedChar is the unsigned version of Char,
+// which is the type of c.
+template <typename UnsignedChar, typename Char>
+static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) {
+ switch (static_cast<wchar_t>(c)) {
+ case L'\0':
+ *os << "\\0";
+ break;
+ case L'\'':
+ *os << "\\'";
+ break;
+ case L'\\':
+ *os << "\\\\";
+ break;
+ case L'\a':
+ *os << "\\a";
+ break;
+ case L'\b':
+ *os << "\\b";
+ break;
+ case L'\f':
+ *os << "\\f";
+ break;
+ case L'\n':
+ *os << "\\n";
+ break;
+ case L'\r':
+ *os << "\\r";
+ break;
+ case L'\t':
+ *os << "\\t";
+ break;
+ case L'\v':
+ *os << "\\v";
+ break;
+ default:
+ if (IsPrintableAscii(c)) {
+ *os << static_cast<char>(c);
+ return kAsIs;
+ } else {
+ ostream::fmtflags flags = os->flags();
+ *os << "\\x" << std::hex << std::uppercase
+ << static_cast<int>(static_cast<UnsignedChar>(c));
+ os->flags(flags);
+ return kHexEscape;
+ }
+ }
+ return kSpecialEscape;
+}
+
+// Prints a wchar_t c as if it's part of a string literal, escaping it when
+// necessary; returns how c was formatted.
+static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) {
+ switch (c) {
+ case L'\'':
+ *os << "'";
+ return kAsIs;
+ case L'"':
+ *os << "\\\"";
+ return kSpecialEscape;
+ default:
+ return PrintAsCharLiteralTo<wchar_t>(c, os);
+ }
+}
+
+// Prints a char c as if it's part of a string literal, escaping it when
+// necessary; returns how c was formatted.
+static CharFormat PrintAsStringLiteralTo(char c, ostream* os) {
+ return PrintAsStringLiteralTo(
+ static_cast<wchar_t>(static_cast<unsigned char>(c)), os);
+}
+
+// Prints a wide or narrow character c and its code. '\0' is printed
+// as "'\\0'", other unprintable characters are also properly escaped
+// using the standard C++ escape sequence. The template argument
+// UnsignedChar is the unsigned version of Char, which is the type of c.
+template <typename UnsignedChar, typename Char>
+void PrintCharAndCodeTo(Char c, ostream* os) {
+ // First, print c as a literal in the most readable form we can find.
+ *os << ((sizeof(c) > 1) ? "L'" : "'");
+ const CharFormat format = PrintAsCharLiteralTo<UnsignedChar>(c, os);
+ *os << "'";
+
+ // To aid user debugging, we also print c's code in decimal, unless
+ // it's 0 (in which case c was printed as '\\0', making the code
+ // obvious).
+ if (c == 0)
+ return;
+ *os << " (" << static_cast<int>(c);
+
+ // For more convenience, we print c's code again in hexadecimal,
+ // unless c was already printed in the form '\x##' or the code is in
+ // [1, 9].
+ if (format == kHexEscape || (1 <= c && c <= 9)) {
+ // Do nothing.
+ } else {
+ *os << ", 0x" << String::FormatHexInt(static_cast<UnsignedChar>(c));
+ }
+ *os << ")";
+}
+
+void PrintTo(unsigned char c, ::std::ostream* os) {
+ PrintCharAndCodeTo<unsigned char>(c, os);
+}
+void PrintTo(signed char c, ::std::ostream* os) {
+ PrintCharAndCodeTo<unsigned char>(c, os);
+}
+
+// Prints a wchar_t as a symbol if it is printable or as its internal
+// code otherwise and also as its code. L'\0' is printed as "L'\\0'".
+void PrintTo(wchar_t wc, ostream* os) {
+ PrintCharAndCodeTo<wchar_t>(wc, os);
+}
+
+// Prints the given array of characters to the ostream. CharType must be either
+// char or wchar_t.
+// The array starts at begin, the length is len, it may include '\0' characters
+// and may not be NUL-terminated.
+template <typename CharType>
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+static CharFormat PrintCharsAsStringTo(
+ const CharType* begin, size_t len, ostream* os) {
+ const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\"";
+ *os << kQuoteBegin;
+ bool is_previous_hex = false;
+ CharFormat print_format = kAsIs;
+ for (size_t index = 0; index < len; ++index) {
+ const CharType cur = begin[index];
+ if (is_previous_hex && IsXDigit(cur)) {
+ // Previous character is of '\x..' form and this character can be
+ // interpreted as another hexadecimal digit in its number. Break string to
+ // disambiguate.
+ *os << "\" " << kQuoteBegin;
+ }
+ is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape;
+ // Remember if any characters required hex escaping.
+ if (is_previous_hex) {
+ print_format = kHexEscape;
+ }
+ }
+ *os << "\"";
+ return print_format;
+}
+
+// Prints a (const) char/wchar_t array of 'len' elements, starting at address
+// 'begin'. CharType must be either char or wchar_t.
+template <typename CharType>
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+static void UniversalPrintCharArray(
+ const CharType* begin, size_t len, ostream* os) {
+ // The code
+ // const char kFoo[] = "foo";
+ // generates an array of 4, not 3, elements, with the last one being '\0'.
+ //
+ // Therefore when printing a char array, we don't print the last element if
+ // it's '\0', such that the output matches the string literal as it's
+ // written in the source code.
+ if (len > 0 && begin[len - 1] == '\0') {
+ PrintCharsAsStringTo(begin, len - 1, os);
+ return;
+ }
+
+ // If, however, the last element in the array is not '\0', e.g.
+ // const char kFoo[] = { 'f', 'o', 'o' };
+ // we must print the entire array. We also print a message to indicate
+ // that the array is not NUL-terminated.
+ PrintCharsAsStringTo(begin, len, os);
+ *os << " (no terminating NUL)";
+}
+
+// Prints a (const) char array of 'len' elements, starting at address 'begin'.
+void UniversalPrintArray(const char* begin, size_t len, ostream* os) {
+ UniversalPrintCharArray(begin, len, os);
+}
+
+// Prints a (const) wchar_t array of 'len' elements, starting at address
+// 'begin'.
+void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) {
+ UniversalPrintCharArray(begin, len, os);
+}
+
+// Prints the given C string to the ostream.
+void PrintTo(const char* s, ostream* os) {
+ if (s == NULL) {
+ *os << "NULL";
+ } else {
+ *os << ImplicitCast_<const void*>(s) << " pointing to ";
+ PrintCharsAsStringTo(s, strlen(s), os);
+ }
+}
+
+// MSVC compiler can be configured to define whar_t as a typedef
+// of unsigned short. Defining an overload for const wchar_t* in that case
+// would cause pointers to unsigned shorts be printed as wide strings,
+// possibly accessing more memory than intended and causing invalid
+// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when
+// wchar_t is implemented as a native type.
+#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
+// Prints the given wide C string to the ostream.
+void PrintTo(const wchar_t* s, ostream* os) {
+ if (s == NULL) {
+ *os << "NULL";
+ } else {
+ *os << ImplicitCast_<const void*>(s) << " pointing to ";
+ PrintCharsAsStringTo(s, std::wcslen(s), os);
+ }
+}
+#endif // wchar_t is native
+
+namespace {
+
+bool ContainsUnprintableControlCodes(const char* str, size_t length) {
+ const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
+
+ for (size_t i = 0; i < length; i++) {
+ unsigned char ch = *s++;
+ if (std::iscntrl(ch)) {
+ switch (ch) {
+ case '\t':
+ case '\n':
+ case '\r':
+ break;
+ default:
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t<= 0xbf; }
+
+bool IsValidUTF8(const char* str, size_t length) {
+ const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
+
+ for (size_t i = 0; i < length;) {
+ unsigned char lead = s[i++];
+
+ if (lead <= 0x7f) {
+ continue; // single-byte character (ASCII) 0..7F
+ }
+ if (lead < 0xc2) {
+ return false; // trail byte or non-shortest form
+ } else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) {
+ ++i; // 2-byte character
+ } else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length &&
+ IsUTF8TrailByte(s[i]) &&
+ IsUTF8TrailByte(s[i + 1]) &&
+ // check for non-shortest form and surrogate
+ (lead != 0xe0 || s[i] >= 0xa0) &&
+ (lead != 0xed || s[i] < 0xa0)) {
+ i += 2; // 3-byte character
+ } else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length &&
+ IsUTF8TrailByte(s[i]) &&
+ IsUTF8TrailByte(s[i + 1]) &&
+ IsUTF8TrailByte(s[i + 2]) &&
+ // check for non-shortest form
+ (lead != 0xf0 || s[i] >= 0x90) &&
+ (lead != 0xf4 || s[i] < 0x90)) {
+ i += 3; // 4-byte character
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ConditionalPrintAsText(const char* str, size_t length, ostream* os) {
+ if (!ContainsUnprintableControlCodes(str, length) &&
+ IsValidUTF8(str, length)) {
+ *os << "\n As Text: \"" << str << "\"";
+ }
+}
+
+} // anonymous namespace
+
+// Prints a ::string object.
+#if GTEST_HAS_GLOBAL_STRING
+void PrintStringTo(const ::string& s, ostream* os) {
+ if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) {
+ if (GTEST_FLAG(print_utf8)) {
+ ConditionalPrintAsText(s.data(), s.size(), os);
+ }
+ }
+}
+#endif // GTEST_HAS_GLOBAL_STRING
+
+void PrintStringTo(const ::std::string& s, ostream* os) {
+ if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) {
+ if (GTEST_FLAG(print_utf8)) {
+ ConditionalPrintAsText(s.data(), s.size(), os);
+ }
+ }
+}
+
+// Prints a ::wstring object.
+#if GTEST_HAS_GLOBAL_WSTRING
+void PrintWideStringTo(const ::wstring& s, ostream* os) {
+ PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+#if GTEST_HAS_STD_WSTRING
+void PrintWideStringTo(const ::std::wstring& s, ostream* os) {
+ PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+#endif // GTEST_HAS_STD_WSTRING
+
+} // namespace internal
+
+} // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+
+
+namespace testing {
+
+using internal::GetUnitTestImpl;
+
+// Gets the summary of the failure message by omitting the stack trace
+// in it.
+std::string TestPartResult::ExtractSummary(const char* message) {
+ const char* const stack_trace = strstr(message, internal::kStackTraceMarker);
+ return stack_trace == NULL ? message :
+ std::string(message, stack_trace);
+}
+
+// Prints a TestPartResult object.
+std::ostream& operator<<(std::ostream& os, const TestPartResult& result) {
+ return os
+ << result.file_name() << ":" << result.line_number() << ": "
+ << (result.type() == TestPartResult::kSuccess ? "Success" :
+ result.type() == TestPartResult::kFatalFailure ? "Fatal failure" :
+ "Non-fatal failure") << ":\n"
+ << result.message() << std::endl;
+}
+
+// Appends a TestPartResult to the array.
+void TestPartResultArray::Append(const TestPartResult& result) {
+ array_.push_back(result);
+}
+
+// Returns the TestPartResult at the given index (0-based).
+const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const {
+ if (index < 0 || index >= size()) {
+ printf("\nInvalid index (%d) into TestPartResultArray.\n", index);
+ internal::posix::Abort();
+ }
+
+ return array_[index];
+}
+
+// Returns the number of TestPartResult objects in the array.
+int TestPartResultArray::size() const {
+ return static_cast<int>(array_.size());
+}
+
+namespace internal {
+
+HasNewFatalFailureHelper::HasNewFatalFailureHelper()
+ : has_new_fatal_failure_(false),
+ original_reporter_(GetUnitTestImpl()->
+ GetTestPartResultReporterForCurrentThread()) {
+ GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this);
+}
+
+HasNewFatalFailureHelper::~HasNewFatalFailureHelper() {
+ GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(
+ original_reporter_);
+}
+
+void HasNewFatalFailureHelper::ReportTestPartResult(
+ const TestPartResult& result) {
+ if (result.fatally_failed())
+ has_new_fatal_failure_ = true;
+ original_reporter_->ReportTestPartResult(result);
+}
+
+} // namespace internal
+
+} // namespace testing
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+namespace testing {
+namespace internal {
+
+#if GTEST_HAS_TYPED_TEST_P
+
+// Skips to the first non-space char in str. Returns an empty string if str
+// contains only whitespace characters.
+static const char* SkipSpaces(const char* str) {
+ while (IsSpace(*str))
+ str++;
+ return str;
+}
+
+static std::vector<std::string> SplitIntoTestNames(const char* src) {
+ std::vector<std::string> name_vec;
+ src = SkipSpaces(src);
+ for (; src != NULL; src = SkipComma(src)) {
+ name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src)));
+ }
+ return name_vec;
+}
+
+// Verifies that registered_tests match the test names in
+// registered_tests_; returns registered_tests if successful, or
+// aborts the program otherwise.
+const char* TypedTestCasePState::VerifyRegisteredTestNames(
+ const char* file, int line, const char* registered_tests) {
+ typedef RegisteredTestsMap::const_iterator RegisteredTestIter;
+ registered_ = true;
+
+ std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
+
+ Message errors;
+
+ std::set<std::string> tests;
+ for (std::vector<std::string>::const_iterator name_it = name_vec.begin();
+ name_it != name_vec.end(); ++name_it) {
+ const std::string& name = *name_it;
+ if (tests.count(name) != 0) {
+ errors << "Test " << name << " is listed more than once.\n";
+ continue;
+ }
+
+ bool found = false;
+ for (RegisteredTestIter it = registered_tests_.begin();
+ it != registered_tests_.end();
+ ++it) {
+ if (name == it->first) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ tests.insert(name);
+ } else {
+ errors << "No test named " << name
+ << " can be found in this test case.\n";
+ }
+ }
+
+ for (RegisteredTestIter it = registered_tests_.begin();
+ it != registered_tests_.end();
+ ++it) {
+ if (tests.count(it->first) == 0) {
+ errors << "You forgot to list test " << it->first << ".\n";
+ }
+ }
+
+ const std::string& errors_str = errors.GetString();
+ if (errors_str != "") {
+ fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(),
+ errors_str.c_str());
+ fflush(stderr);
+ posix::Abort();
+ }
+
+ return registered_tests;
+}
+
+#endif // GTEST_HAS_TYPED_TEST_P
+
+} // namespace internal
+} // namespace testing
diff --git a/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest.h b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest.h
new file mode 100644
index 00000000..ebb16db7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest.h
@@ -0,0 +1,22109 @@
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the public API for Google Test. It should be
+// included by any test program that uses Google Test.
+//
+// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
+// leave some internal implementation details in this header file.
+// They are clearly marked by comments like this:
+//
+// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+//
+// Such code is NOT meant to be used by a user directly, and is subject
+// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user
+// program!
+//
+// Acknowledgment: Google Test borrowed the idea of automatic test
+// registration from Barthelemy Dagenais' (barthelemy@prologique.com)
+// easyUnit framework.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_H_
+#define GTEST_INCLUDE_GTEST_GTEST_H_
+
+#include <limits>
+#include <ostream>
+#include <vector>
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file declares functions and macros used internally by
+// Google Test. They are subject to change without notice.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Low-level types and utilities for porting Google Test to various
+// platforms. All macros ending with _ and symbols defined in an
+// internal namespace are subject to change without notice. Code
+// outside Google Test MUST NOT USE THEM DIRECTLY. Macros that don't
+// end with _ are part of Google Test's public API and can be used by
+// code outside Google Test.
+//
+// This file is fundamental to Google Test. All other Google Test source
+// files are expected to #include this. Therefore, it cannot #include
+// any other Google Test header.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+
+// Environment-describing macros
+// -----------------------------
+//
+// Google Test can be used in many different environments. Macros in
+// this section tell Google Test what kind of environment it is being
+// used in, such that Google Test can provide environment-specific
+// features and implementations.
+//
+// Google Test tries to automatically detect the properties of its
+// environment, so users usually don't need to worry about these
+// macros. However, the automatic detection is not perfect.
+// Sometimes it's necessary for a user to define some of the following
+// macros in the build script to override Google Test's decisions.
+//
+// If the user doesn't define a macro in the list, Google Test will
+// provide a default definition. After this header is #included, all
+// macros in this list will be defined to either 1 or 0.
+//
+// Notes to maintainers:
+// - Each macro here is a user-tweakable knob; do not grow the list
+// lightly.
+// - Use #if to key off these macros. Don't use #ifdef or "#if
+// defined(...)", which will not work as these macros are ALWAYS
+// defined.
+//
+// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2)
+// is/isn't available.
+// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions
+// are enabled.
+// GTEST_HAS_GLOBAL_STRING - Define it to 1/0 to indicate that ::string
+// is/isn't available
+// GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::wstring
+// is/isn't available
+// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular
+// expressions are/aren't available.
+// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that <pthread.h>
+// is/isn't available.
+// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't
+// enabled.
+// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that
+// std::wstring does/doesn't work (Google Test can
+// be used where std::wstring is unavailable).
+// GTEST_HAS_TR1_TUPLE - Define it to 1/0 to indicate tr1::tuple
+// is/isn't available.
+// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the
+// compiler supports Microsoft's "Structured
+// Exception Handling".
+// GTEST_HAS_STREAM_REDIRECTION
+// - Define it to 1/0 to indicate whether the
+// platform supports I/O stream redirection using
+// dup() and dup2().
+// GTEST_USE_OWN_TR1_TUPLE - Define it to 1/0 to indicate whether Google
+// Test's own tr1 tuple implementation should be
+// used. Unused when the user sets
+// GTEST_HAS_TR1_TUPLE to 0.
+// GTEST_LANG_CXX11 - Define it to 1/0 to indicate that Google Test
+// is building in C++11/C++98 mode.
+// GTEST_LINKED_AS_SHARED_LIBRARY
+// - Define to 1 when compiling tests that use
+// Google Test as a shared library (known as
+// DLL on Windows).
+// GTEST_CREATE_SHARED_LIBRARY
+// - Define to 1 when compiling Google Test itself
+// as a shared library.
+// GTEST_DEFAULT_DEATH_TEST_STYLE
+// - The default value of --gtest_death_test_style.
+// The legacy default has been "fast" in the open
+// source version since 2008. The recommended value
+// is "threadsafe", and can be set in
+// custom/gtest-port.h.
+
+// Platform-indicating macros
+// --------------------------
+//
+// Macros indicating the platform on which Google Test is being used
+// (a macro is defined to 1 if compiled on the given platform;
+// otherwise UNDEFINED -- it's never defined to 0.). Google Test
+// defines these macros automatically. Code outside Google Test MUST
+// NOT define them.
+//
+// GTEST_OS_AIX - IBM AIX
+// GTEST_OS_CYGWIN - Cygwin
+// GTEST_OS_FREEBSD - FreeBSD
+// GTEST_OS_FUCHSIA - Fuchsia
+// GTEST_OS_HPUX - HP-UX
+// GTEST_OS_LINUX - Linux
+// GTEST_OS_LINUX_ANDROID - Google Android
+// GTEST_OS_MAC - Mac OS X
+// GTEST_OS_IOS - iOS
+// GTEST_OS_NACL - Google Native Client (NaCl)
+// GTEST_OS_NETBSD - NetBSD
+// GTEST_OS_OPENBSD - OpenBSD
+// GTEST_OS_QNX - QNX
+// GTEST_OS_SOLARIS - Sun Solaris
+// GTEST_OS_SYMBIAN - Symbian
+// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile)
+// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop
+// GTEST_OS_WINDOWS_MINGW - MinGW
+// GTEST_OS_WINDOWS_MOBILE - Windows Mobile
+// GTEST_OS_WINDOWS_PHONE - Windows Phone
+// GTEST_OS_WINDOWS_RT - Windows Store App/WinRT
+// GTEST_OS_ZOS - z/OS
+//
+// Among the platforms, Cygwin, Linux, Max OS X, and Windows have the
+// most stable support. Since core members of the Google Test project
+// don't have access to other platforms, support for them may be less
+// stable. If you notice any problems on your platform, please notify
+// googletestframework@googlegroups.com (patches for fixing them are
+// even more welcome!).
+//
+// It is possible that none of the GTEST_OS_* macros are defined.
+
+// Feature-indicating macros
+// -------------------------
+//
+// Macros indicating which Google Test features are available (a macro
+// is defined to 1 if the corresponding feature is supported;
+// otherwise UNDEFINED -- it's never defined to 0.). Google Test
+// defines these macros automatically. Code outside Google Test MUST
+// NOT define them.
+//
+// These macros are public so that portable tests can be written.
+// Such tests typically surround code using a feature with an #if
+// which controls that code. For example:
+//
+// #if GTEST_HAS_DEATH_TEST
+// EXPECT_DEATH(DoSomethingDeadly());
+// #endif
+//
+// GTEST_HAS_COMBINE - the Combine() function (for value-parameterized
+// tests)
+// GTEST_HAS_DEATH_TEST - death tests
+// GTEST_HAS_TYPED_TEST - typed tests
+// GTEST_HAS_TYPED_TEST_P - type-parameterized tests
+// GTEST_IS_THREADSAFE - Google Test is thread-safe.
+// GOOGLETEST_CM0007 DO NOT DELETE
+// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with
+// GTEST_HAS_POSIX_RE (see above) which users can
+// define themselves.
+// GTEST_USES_SIMPLE_RE - our own simple regex is used;
+// the above RE\b(s) are mutually exclusive.
+// GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ().
+
+// Misc public macros
+// ------------------
+//
+// GTEST_FLAG(flag_name) - references the variable corresponding to
+// the given Google Test flag.
+
+// Internal utilities
+// ------------------
+//
+// The following macros and utilities are for Google Test's INTERNAL
+// use only. Code outside Google Test MUST NOT USE THEM DIRECTLY.
+//
+// Macros for basic C++ coding:
+// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning.
+// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a
+// variable don't have to be used.
+// GTEST_DISALLOW_ASSIGN_ - disables operator=.
+// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=.
+// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used.
+// GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is
+// suppressed (constant conditional).
+// GTEST_INTENTIONAL_CONST_COND_POP_ - finish code section where MSVC C4127
+// is suppressed.
+//
+// C++11 feature wrappers:
+//
+// testing::internal::forward - portability wrapper for std::forward.
+// testing::internal::move - portability wrapper for std::move.
+//
+// Synchronization:
+// Mutex, MutexLock, ThreadLocal, GetThreadCount()
+// - synchronization primitives.
+//
+// Template meta programming:
+// is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only.
+// IteratorTraits - partial implementation of std::iterator_traits, which
+// is not available in libCstd when compiled with Sun C++.
+//
+// Smart pointers:
+// scoped_ptr - as in TR2.
+//
+// Regular expressions:
+// RE - a simple regular expression class using the POSIX
+// Extended Regular Expression syntax on UNIX-like platforms
+// GOOGLETEST_CM0008 DO NOT DELETE
+// or a reduced regular exception syntax on other
+// platforms, including Windows.
+// Logging:
+// GTEST_LOG_() - logs messages at the specified severity level.
+// LogToStderr() - directs all log messages to stderr.
+// FlushInfoLog() - flushes informational log messages.
+//
+// Stdout and stderr capturing:
+// CaptureStdout() - starts capturing stdout.
+// GetCapturedStdout() - stops capturing stdout and returns the captured
+// string.
+// CaptureStderr() - starts capturing stderr.
+// GetCapturedStderr() - stops capturing stderr and returns the captured
+// string.
+//
+// Integer types:
+// TypeWithSize - maps an integer to a int type.
+// Int32, UInt32, Int64, UInt64, TimeInMillis
+// - integers of known sizes.
+// BiggestInt - the biggest signed integer type.
+//
+// Command-line utilities:
+// GTEST_DECLARE_*() - declares a flag.
+// GTEST_DEFINE_*() - defines a flag.
+// GetInjectableArgvs() - returns the command line as a vector of strings.
+//
+// Environment variable utilities:
+// GetEnv() - gets the value of an environment variable.
+// BoolFromGTestEnv() - parses a bool environment variable.
+// Int32FromGTestEnv() - parses an Int32 environment variable.
+// StringFromGTestEnv() - parses a string environment variable.
+
+#include <ctype.h> // for isspace, etc
+#include <stddef.h> // for ptrdiff_t
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifndef _WIN32_WCE
+# include <sys/types.h>
+# include <sys/stat.h>
+#endif // !_WIN32_WCE
+
+#if defined __APPLE__
+# include <AvailabilityMacros.h>
+# include <TargetConditionals.h>
+#endif
+
+// Brings in the definition of HAS_GLOBAL_STRING. This must be done
+// BEFORE we test HAS_GLOBAL_STRING.
+#include <string> // NOLINT
+#include <algorithm> // NOLINT
+#include <iostream> // NOLINT
+#include <sstream> // NOLINT
+#include <utility>
+#include <vector> // NOLINT
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the GTEST_OS_* macro.
+// It is separate from gtest-port.h so that custom/gtest-port.h can include it.
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+
+// Determines the platform on which Google Test is compiled.
+#ifdef __CYGWIN__
+# define GTEST_OS_CYGWIN 1
+#elif defined __SYMBIAN32__
+# define GTEST_OS_SYMBIAN 1
+#elif defined _WIN32
+# define GTEST_OS_WINDOWS 1
+# ifdef _WIN32_WCE
+# define GTEST_OS_WINDOWS_MOBILE 1
+# elif defined(__MINGW__) || defined(__MINGW32__)
+# define GTEST_OS_WINDOWS_MINGW 1
+# elif defined(WINAPI_FAMILY)
+# include <winapifamily.h>
+# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+# define GTEST_OS_WINDOWS_DESKTOP 1
+# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
+# define GTEST_OS_WINDOWS_PHONE 1
+# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
+# define GTEST_OS_WINDOWS_RT 1
+# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE)
+# define GTEST_OS_WINDOWS_PHONE 1
+# define GTEST_OS_WINDOWS_TV_TITLE 1
+# else
+ // WINAPI_FAMILY defined but no known partition matched.
+ // Default to desktop.
+# define GTEST_OS_WINDOWS_DESKTOP 1
+# endif
+# else
+# define GTEST_OS_WINDOWS_DESKTOP 1
+# endif // _WIN32_WCE
+#elif defined __APPLE__
+# define GTEST_OS_MAC 1
+# if TARGET_OS_IPHONE
+# define GTEST_OS_IOS 1
+# endif
+#elif defined __FreeBSD__
+# define GTEST_OS_FREEBSD 1
+#elif defined __Fuchsia__
+# define GTEST_OS_FUCHSIA 1
+#elif defined __linux__
+# define GTEST_OS_LINUX 1
+# if defined __ANDROID__
+# define GTEST_OS_LINUX_ANDROID 1
+# endif
+#elif defined __MVS__
+# define GTEST_OS_ZOS 1
+#elif defined(__sun) && defined(__SVR4)
+# define GTEST_OS_SOLARIS 1
+#elif defined(_AIX)
+# define GTEST_OS_AIX 1
+#elif defined(__hpux)
+# define GTEST_OS_HPUX 1
+#elif defined __native_client__
+# define GTEST_OS_NACL 1
+#elif defined __NetBSD__
+# define GTEST_OS_NETBSD 1
+#elif defined __OpenBSD__
+# define GTEST_OS_OPENBSD 1
+#elif defined __QNX__
+# define GTEST_OS_QNX 1
+#endif // __CYGWIN__
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+
+#if !defined(GTEST_DEV_EMAIL_)
+# define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com"
+# define GTEST_FLAG_PREFIX_ "gtest_"
+# define GTEST_FLAG_PREFIX_DASH_ "gtest-"
+# define GTEST_FLAG_PREFIX_UPPER_ "GTEST_"
+# define GTEST_NAME_ "Google Test"
+# define GTEST_PROJECT_URL_ "https://github.com/google/googletest/"
+#endif // !defined(GTEST_DEV_EMAIL_)
+
+#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_)
+# define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest"
+#endif // !defined(GTEST_INIT_GOOGLE_TEST_NAME_)
+
+// Determines the version of gcc that is used to compile this.
+#ifdef __GNUC__
+// 40302 means version 4.3.2.
+# define GTEST_GCC_VER_ \
+ (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__)
+#endif // __GNUC__
+
+// Macros for disabling Microsoft Visual C++ warnings.
+//
+// GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385)
+// /* code that triggers warnings C4800 and C4385 */
+// GTEST_DISABLE_MSC_WARNINGS_POP_()
+#if _MSC_VER >= 1400
+# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \
+ __pragma(warning(push)) \
+ __pragma(warning(disable: warnings))
+# define GTEST_DISABLE_MSC_WARNINGS_POP_() \
+ __pragma(warning(pop))
+#else
+// Older versions of MSVC don't have __pragma.
+# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings)
+# define GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+// Clang on Windows does not understand MSVC's pragma warning.
+// We need clang-specific way to disable function deprecation warning.
+#ifdef __clang__
+# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
+ _Pragma("clang diagnostic ignored \"-Wdeprecated-implementations\"")
+#define GTEST_DISABLE_MSC_DEPRECATED_POP_() \
+ _Pragma("clang diagnostic pop")
+#else
+# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996)
+# define GTEST_DISABLE_MSC_DEPRECATED_POP_() \
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+#ifndef GTEST_LANG_CXX11
+// gcc and clang define __GXX_EXPERIMENTAL_CXX0X__ when
+// -std={c,gnu}++{0x,11} is passed. The C++11 standard specifies a
+// value for __cplusplus, and recent versions of clang, gcc, and
+// probably other compilers set that too in C++11 mode.
+# if __GXX_EXPERIMENTAL_CXX0X__ || __cplusplus >= 201103L || _MSC_VER >= 1900
+// Compiling in at least C++11 mode.
+# define GTEST_LANG_CXX11 1
+# else
+# define GTEST_LANG_CXX11 0
+# endif
+#endif
+
+// Distinct from C++11 language support, some environments don't provide
+// proper C++11 library support. Notably, it's possible to build in
+// C++11 mode when targeting Mac OS X 10.6, which has an old libstdc++
+// with no C++11 support.
+//
+// libstdc++ has sufficient C++11 support as of GCC 4.6.0, __GLIBCXX__
+// 20110325, but maintenance releases in the 4.4 and 4.5 series followed
+// this date, so check for those versions by their date stamps.
+// https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html#abi.versioning
+#if GTEST_LANG_CXX11 && \
+ (!defined(__GLIBCXX__) || ( \
+ __GLIBCXX__ >= 20110325ul && /* GCC >= 4.6.0 */ \
+ /* Blacklist of patch releases of older branches: */ \
+ __GLIBCXX__ != 20110416ul && /* GCC 4.4.6 */ \
+ __GLIBCXX__ != 20120313ul && /* GCC 4.4.7 */ \
+ __GLIBCXX__ != 20110428ul && /* GCC 4.5.3 */ \
+ __GLIBCXX__ != 20120702ul)) /* GCC 4.5.4 */
+# define GTEST_STDLIB_CXX11 1
+#endif
+
+// Only use C++11 library features if the library provides them.
+#if GTEST_STDLIB_CXX11
+# define GTEST_HAS_STD_BEGIN_AND_END_ 1
+# define GTEST_HAS_STD_FORWARD_LIST_ 1
+# if !defined(_MSC_VER) || (_MSC_FULL_VER >= 190023824)
+// works only with VS2015U2 and better
+# define GTEST_HAS_STD_FUNCTION_ 1
+# endif
+# define GTEST_HAS_STD_INITIALIZER_LIST_ 1
+# define GTEST_HAS_STD_MOVE_ 1
+# define GTEST_HAS_STD_UNIQUE_PTR_ 1
+# define GTEST_HAS_STD_SHARED_PTR_ 1
+# define GTEST_HAS_UNORDERED_MAP_ 1
+# define GTEST_HAS_UNORDERED_SET_ 1
+#endif
+
+// C++11 specifies that <tuple> provides std::tuple.
+// Some platforms still might not have it, however.
+#if GTEST_LANG_CXX11
+# define GTEST_HAS_STD_TUPLE_ 1
+# if defined(__clang__)
+// Inspired by
+// https://clang.llvm.org/docs/LanguageExtensions.html#include-file-checking-macros
+# if defined(__has_include) && !__has_include(<tuple>)
+# undef GTEST_HAS_STD_TUPLE_
+# endif
+# elif defined(_MSC_VER)
+// Inspired by boost/config/stdlib/dinkumware.hpp
+# if defined(_CPPLIB_VER) && _CPPLIB_VER < 520
+# undef GTEST_HAS_STD_TUPLE_
+# endif
+# elif defined(__GLIBCXX__)
+// Inspired by boost/config/stdlib/libstdcpp3.hpp,
+// http://gcc.gnu.org/gcc-4.2/changes.html and
+// https://web.archive.org/web/20140227044429/gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt01ch01.html#manual.intro.status.standard.200x
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2)
+# undef GTEST_HAS_STD_TUPLE_
+# endif
+# endif
+#endif
+
+// Brings in definitions for functions used in the testing::internal::posix
+// namespace (read, write, close, chdir, isatty, stat). We do not currently
+// use them on Windows Mobile.
+#if GTEST_OS_WINDOWS
+# if !GTEST_OS_WINDOWS_MOBILE
+# include <direct.h>
+# include <io.h>
+# endif
+// In order to avoid having to include <windows.h>, use forward declaration
+#if GTEST_OS_WINDOWS_MINGW && !defined(__MINGW64_VERSION_MAJOR)
+// MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two
+// separate (equivalent) structs, instead of using typedef
+typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION;
+#else
+// Assume CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION.
+// This assumption is verified by
+// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION.
+typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION;
+#endif
+#else
+// This assumes that non-Windows OSes provide unistd.h. For OSes where this
+// is not the case, we need to include headers that provide the functions
+// mentioned above.
+# include <unistd.h>
+# include <strings.h>
+#endif // GTEST_OS_WINDOWS
+
+#if GTEST_OS_LINUX_ANDROID
+// Used to define __ANDROID_API__ matching the target NDK API level.
+# include <android/api-level.h> // NOLINT
+#endif
+
+// Defines this to true iff Google Test can use POSIX regular expressions.
+#ifndef GTEST_HAS_POSIX_RE
+# if GTEST_OS_LINUX_ANDROID
+// On Android, <regex.h> is only available starting with Gingerbread.
+# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9)
+# else
+# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS)
+# endif
+#endif
+
+#if GTEST_USES_PCRE
+// The appropriate headers have already been included.
+
+#elif GTEST_HAS_POSIX_RE
+
+// On some platforms, <regex.h> needs someone to define size_t, and
+// won't compile otherwise. We can #include it here as we already
+// included <stdlib.h>, which is guaranteed to define size_t through
+// <stddef.h>.
+# include <regex.h> // NOLINT
+
+# define GTEST_USES_POSIX_RE 1
+
+#elif GTEST_OS_WINDOWS
+
+// <regex.h> is not available on Windows. Use our own simple regex
+// implementation instead.
+# define GTEST_USES_SIMPLE_RE 1
+
+#else
+
+// <regex.h> may not be available on this platform. Use our own
+// simple regex implementation instead.
+# define GTEST_USES_SIMPLE_RE 1
+
+#endif // GTEST_USES_PCRE
+
+#ifndef GTEST_HAS_EXCEPTIONS
+// The user didn't tell us whether exceptions are enabled, so we need
+// to figure it out.
+# if defined(_MSC_VER) && defined(_CPPUNWIND)
+// MSVC defines _CPPUNWIND to 1 iff exceptions are enabled.
+# define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__BORLANDC__)
+// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS
+// macro to enable exceptions, so we'll do the same.
+// Assumes that exceptions are enabled by default.
+# ifndef _HAS_EXCEPTIONS
+# define _HAS_EXCEPTIONS 1
+# endif // _HAS_EXCEPTIONS
+# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS
+# elif defined(__clang__)
+// clang defines __EXCEPTIONS iff exceptions are enabled before clang 220714,
+// but iff cleanups are enabled after that. In Obj-C++ files, there can be
+// cleanups for ObjC exceptions which also need cleanups, even if C++ exceptions
+// are disabled. clang has __has_feature(cxx_exceptions) which checks for C++
+// exceptions starting at clang r206352, but which checked for cleanups prior to
+// that. To reliably check for C++ exception availability with clang, check for
+// __EXCEPTIONS && __has_feature(cxx_exceptions).
+# define GTEST_HAS_EXCEPTIONS (__EXCEPTIONS && __has_feature(cxx_exceptions))
+# elif defined(__GNUC__) && __EXCEPTIONS
+// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled.
+# define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__SUNPRO_CC)
+// Sun Pro CC supports exceptions. However, there is no compile-time way of
+// detecting whether they are enabled or not. Therefore, we assume that
+// they are enabled unless the user tells us otherwise.
+# define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__IBMCPP__) && __EXCEPTIONS
+// xlC defines __EXCEPTIONS to 1 iff exceptions are enabled.
+# define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__HP_aCC)
+// Exception handling is in effect by default in HP aCC compiler. It has to
+// be turned of by +noeh compiler option if desired.
+# define GTEST_HAS_EXCEPTIONS 1
+# else
+// For other compilers, we assume exceptions are disabled to be
+// conservative.
+# define GTEST_HAS_EXCEPTIONS 0
+# endif // defined(_MSC_VER) || defined(__BORLANDC__)
+#endif // GTEST_HAS_EXCEPTIONS
+
+#if !defined(GTEST_HAS_STD_STRING)
+// Even though we don't use this macro any longer, we keep it in case
+// some clients still depend on it.
+# define GTEST_HAS_STD_STRING 1
+#elif !GTEST_HAS_STD_STRING
+// The user told us that ::std::string isn't available.
+# error "::std::string isn't available."
+#endif // !defined(GTEST_HAS_STD_STRING)
+
+#ifndef GTEST_HAS_GLOBAL_STRING
+# define GTEST_HAS_GLOBAL_STRING 0
+#endif // GTEST_HAS_GLOBAL_STRING
+
+#ifndef GTEST_HAS_STD_WSTRING
+// The user didn't tell us whether ::std::wstring is available, so we need
+// to figure it out.
+// FIXME: uses autoconf to detect whether ::std::wstring
+// is available.
+
+// Cygwin 1.7 and below doesn't support ::std::wstring.
+// Solaris' libc++ doesn't support it either. Android has
+// no support for it at least as recent as Froyo (2.2).
+# define GTEST_HAS_STD_WSTRING \
+ (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS))
+
+#endif // GTEST_HAS_STD_WSTRING
+
+#ifndef GTEST_HAS_GLOBAL_WSTRING
+// The user didn't tell us whether ::wstring is available, so we need
+// to figure it out.
+# define GTEST_HAS_GLOBAL_WSTRING \
+ (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING)
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+// Determines whether RTTI is available.
+#ifndef GTEST_HAS_RTTI
+// The user didn't tell us whether RTTI is enabled, so we need to
+// figure it out.
+
+# ifdef _MSC_VER
+
+# ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled.
+# define GTEST_HAS_RTTI 1
+# else
+# define GTEST_HAS_RTTI 0
+# endif
+
+// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled.
+# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302)
+
+# ifdef __GXX_RTTI
+// When building against STLport with the Android NDK and with
+// -frtti -fno-exceptions, the build fails at link time with undefined
+// references to __cxa_bad_typeid. Note sure if STL or toolchain bug,
+// so disable RTTI when detected.
+# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) && \
+ !defined(__EXCEPTIONS)
+# define GTEST_HAS_RTTI 0
+# else
+# define GTEST_HAS_RTTI 1
+# endif // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS
+# else
+# define GTEST_HAS_RTTI 0
+# endif // __GXX_RTTI
+
+// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends
+// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the
+// first version with C++ support.
+# elif defined(__clang__)
+
+# define GTEST_HAS_RTTI __has_feature(cxx_rtti)
+
+// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if
+// both the typeid and dynamic_cast features are present.
+# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900)
+
+# ifdef __RTTI_ALL__
+# define GTEST_HAS_RTTI 1
+# else
+# define GTEST_HAS_RTTI 0
+# endif
+
+# else
+
+// For all other compilers, we assume RTTI is enabled.
+# define GTEST_HAS_RTTI 1
+
+# endif // _MSC_VER
+
+#endif // GTEST_HAS_RTTI
+
+// It's this header's responsibility to #include <typeinfo> when RTTI
+// is enabled.
+#if GTEST_HAS_RTTI
+# include <typeinfo>
+#endif
+
+// Determines whether Google Test can use the pthreads library.
+#ifndef GTEST_HAS_PTHREAD
+// The user didn't tell us explicitly, so we make reasonable assumptions about
+// which platforms have pthreads support.
+//
+// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0
+// to your compiler flags.
+#define GTEST_HAS_PTHREAD \
+ (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX || GTEST_OS_QNX || \
+ GTEST_OS_FREEBSD || GTEST_OS_NACL || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA)
+#endif // GTEST_HAS_PTHREAD
+
+#if GTEST_HAS_PTHREAD
+// gtest-port.h guarantees to #include <pthread.h> when GTEST_HAS_PTHREAD is
+// true.
+# include <pthread.h> // NOLINT
+
+// For timespec and nanosleep, used below.
+# include <time.h> // NOLINT
+#endif
+
+// Determines if hash_map/hash_set are available.
+// Only used for testing against those containers.
+#if !defined(GTEST_HAS_HASH_MAP_)
+# if defined(_MSC_VER) && (_MSC_VER < 1900)
+# define GTEST_HAS_HASH_MAP_ 1 // Indicates that hash_map is available.
+# define GTEST_HAS_HASH_SET_ 1 // Indicates that hash_set is available.
+# endif // _MSC_VER
+#endif // !defined(GTEST_HAS_HASH_MAP_)
+
+// Determines whether Google Test can use tr1/tuple. You can define
+// this macro to 0 to prevent Google Test from using tuple (any
+// feature depending on tuple with be disabled in this mode).
+#ifndef GTEST_HAS_TR1_TUPLE
+# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR)
+// STLport, provided with the Android NDK, has neither <tr1/tuple> or <tuple>.
+# define GTEST_HAS_TR1_TUPLE 0
+# elif defined(_MSC_VER) && (_MSC_VER >= 1910)
+// Prevent `warning C4996: 'std::tr1': warning STL4002:
+// The non-Standard std::tr1 namespace and TR1-only machinery
+// are deprecated and will be REMOVED.`
+# define GTEST_HAS_TR1_TUPLE 0
+# elif GTEST_LANG_CXX11 && defined(_LIBCPP_VERSION)
+// libc++ doesn't support TR1.
+# define GTEST_HAS_TR1_TUPLE 0
+# else
+// The user didn't tell us not to do it, so we assume it's OK.
+# define GTEST_HAS_TR1_TUPLE 1
+# endif
+#endif // GTEST_HAS_TR1_TUPLE
+
+// Determines whether Google Test's own tr1 tuple implementation
+// should be used.
+#ifndef GTEST_USE_OWN_TR1_TUPLE
+// We use our own tuple implementation on Symbian.
+# if GTEST_OS_SYMBIAN
+# define GTEST_USE_OWN_TR1_TUPLE 1
+# else
+// The user didn't tell us, so we need to figure it out.
+
+// We use our own TR1 tuple if we aren't sure the user has an
+// implementation of it already. At this time, libstdc++ 4.0.0+ and
+// MSVC 2010 are the only mainstream standard libraries that come
+// with a TR1 tuple implementation. NVIDIA's CUDA NVCC compiler
+// pretends to be GCC by defining __GNUC__ and friends, but cannot
+// compile GCC's tuple implementation. MSVC 2008 (9.0) provides TR1
+// tuple in a 323 MB Feature Pack download, which we cannot assume the
+// user has. QNX's QCC compiler is a modified GCC but it doesn't
+// support TR1 tuple. libc++ only provides std::tuple, in C++11 mode,
+// and it can be used with some compilers that define __GNUC__.
+# if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000) \
+ && !GTEST_OS_QNX && !defined(_LIBCPP_VERSION)) \
+ || (_MSC_VER >= 1600 && _MSC_VER < 1900)
+# define GTEST_ENV_HAS_TR1_TUPLE_ 1
+# endif
+
+// C++11 specifies that <tuple> provides std::tuple. Use that if gtest is used
+// in C++11 mode and libstdc++ isn't very old (binaries targeting OS X 10.6
+// can build with clang but need to use gcc4.2's libstdc++).
+# if GTEST_LANG_CXX11 && (!defined(__GLIBCXX__) || __GLIBCXX__ > 20110325)
+# define GTEST_ENV_HAS_STD_TUPLE_ 1
+# endif
+
+# if GTEST_ENV_HAS_TR1_TUPLE_ || GTEST_ENV_HAS_STD_TUPLE_
+# define GTEST_USE_OWN_TR1_TUPLE 0
+# else
+# define GTEST_USE_OWN_TR1_TUPLE 1
+# endif
+# endif // GTEST_OS_SYMBIAN
+#endif // GTEST_USE_OWN_TR1_TUPLE
+
+// To avoid conditional compilation we make it gtest-port.h's responsibility
+// to #include the header implementing tuple.
+#if GTEST_HAS_STD_TUPLE_
+# include <tuple> // IWYU pragma: export
+# define GTEST_TUPLE_NAMESPACE_ ::std
+#endif // GTEST_HAS_STD_TUPLE_
+
+// We include tr1::tuple even if std::tuple is available to define printers for
+// them.
+#if GTEST_HAS_TR1_TUPLE
+# ifndef GTEST_TUPLE_NAMESPACE_
+# define GTEST_TUPLE_NAMESPACE_ ::std::tr1
+# endif // GTEST_TUPLE_NAMESPACE_
+
+# if GTEST_USE_OWN_TR1_TUPLE
+// This file was GENERATED by command:
+// pump.py gtest-tuple.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// Copyright 2009 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Implements a subset of TR1 tuple needed by Google Test and Google Mock.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_
+
+#include <utility> // For ::std::pair.
+
+// The compiler used in Symbian has a bug that prevents us from declaring the
+// tuple template as a friend (it complains that tuple is redefined). This
+// bypasses the bug by declaring the members that should otherwise be
+// private as public.
+// Sun Studio versions < 12 also have the above bug.
+#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590)
+# define GTEST_DECLARE_TUPLE_AS_FRIEND_ public:
+#else
+# define GTEST_DECLARE_TUPLE_AS_FRIEND_ \
+ template <GTEST_10_TYPENAMES_(U)> friend class tuple; \
+ private:
+#endif
+
+// Visual Studio 2010, 2012, and 2013 define symbols in std::tr1 that conflict
+// with our own definitions. Therefore using our own tuple does not work on
+// those compilers.
+#if defined(_MSC_VER) && _MSC_VER >= 1600 /* 1600 is Visual Studio 2010 */
+# error "gtest's tuple doesn't compile on Visual Studio 2010 or later. \
+GTEST_USE_OWN_TR1_TUPLE must be set to 0 on those compilers."
+#endif
+
+// GTEST_n_TUPLE_(T) is the type of an n-tuple.
+#define GTEST_0_TUPLE_(T) tuple<>
+#define GTEST_1_TUPLE_(T) tuple<T##0, void, void, void, void, void, void, \
+ void, void, void>
+#define GTEST_2_TUPLE_(T) tuple<T##0, T##1, void, void, void, void, void, \
+ void, void, void>
+#define GTEST_3_TUPLE_(T) tuple<T##0, T##1, T##2, void, void, void, void, \
+ void, void, void>
+#define GTEST_4_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, void, void, void, \
+ void, void, void>
+#define GTEST_5_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, void, void, \
+ void, void, void>
+#define GTEST_6_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, T##5, void, \
+ void, void, void>
+#define GTEST_7_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, T##5, T##6, \
+ void, void, void>
+#define GTEST_8_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, T##5, T##6, \
+ T##7, void, void>
+#define GTEST_9_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, T##5, T##6, \
+ T##7, T##8, void>
+#define GTEST_10_TUPLE_(T) tuple<T##0, T##1, T##2, T##3, T##4, T##5, T##6, \
+ T##7, T##8, T##9>
+
+// GTEST_n_TYPENAMES_(T) declares a list of n typenames.
+#define GTEST_0_TYPENAMES_(T)
+#define GTEST_1_TYPENAMES_(T) typename T##0
+#define GTEST_2_TYPENAMES_(T) typename T##0, typename T##1
+#define GTEST_3_TYPENAMES_(T) typename T##0, typename T##1, typename T##2
+#define GTEST_4_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3
+#define GTEST_5_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4
+#define GTEST_6_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4, typename T##5
+#define GTEST_7_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4, typename T##5, typename T##6
+#define GTEST_8_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4, typename T##5, typename T##6, typename T##7
+#define GTEST_9_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4, typename T##5, typename T##6, \
+ typename T##7, typename T##8
+#define GTEST_10_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \
+ typename T##3, typename T##4, typename T##5, typename T##6, \
+ typename T##7, typename T##8, typename T##9
+
+// In theory, defining stuff in the ::std namespace is undefined
+// behavior. We can do this as we are playing the role of a standard
+// library vendor.
+namespace std {
+namespace tr1 {
+
+template <typename T0 = void, typename T1 = void, typename T2 = void,
+ typename T3 = void, typename T4 = void, typename T5 = void,
+ typename T6 = void, typename T7 = void, typename T8 = void,
+ typename T9 = void>
+class tuple;
+
+// Anything in namespace gtest_internal is Google Test's INTERNAL
+// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code.
+namespace gtest_internal {
+
+// ByRef<T>::type is T if T is a reference; otherwise it's const T&.
+template <typename T>
+struct ByRef { typedef const T& type; }; // NOLINT
+template <typename T>
+struct ByRef<T&> { typedef T& type; }; // NOLINT
+
+// A handy wrapper for ByRef.
+#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef<T>::type
+
+// AddRef<T>::type is T if T is a reference; otherwise it's T&. This
+// is the same as tr1::add_reference<T>::type.
+template <typename T>
+struct AddRef { typedef T& type; }; // NOLINT
+template <typename T>
+struct AddRef<T&> { typedef T& type; }; // NOLINT
+
+// A handy wrapper for AddRef.
+#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef<T>::type
+
+// A helper for implementing get<k>().
+template <int k> class Get;
+
+// A helper for implementing tuple_element<k, T>. kIndexValid is true
+// iff k < the number of fields in tuple type T.
+template <bool kIndexValid, int kIndex, class Tuple>
+struct TupleElement;
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 0, GTEST_10_TUPLE_(T) > {
+ typedef T0 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 1, GTEST_10_TUPLE_(T) > {
+ typedef T1 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 2, GTEST_10_TUPLE_(T) > {
+ typedef T2 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 3, GTEST_10_TUPLE_(T) > {
+ typedef T3 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 4, GTEST_10_TUPLE_(T) > {
+ typedef T4 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 5, GTEST_10_TUPLE_(T) > {
+ typedef T5 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 6, GTEST_10_TUPLE_(T) > {
+ typedef T6 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 7, GTEST_10_TUPLE_(T) > {
+ typedef T7 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 8, GTEST_10_TUPLE_(T) > {
+ typedef T8 type;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct TupleElement<true, 9, GTEST_10_TUPLE_(T) > {
+ typedef T9 type;
+};
+
+} // namespace gtest_internal
+
+template <>
+class tuple<> {
+ public:
+ tuple() {}
+ tuple(const tuple& /* t */) {}
+ tuple& operator=(const tuple& /* t */) { return *this; }
+};
+
+template <GTEST_1_TYPENAMES_(T)>
+class GTEST_1_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0) : f0_(f0) {}
+
+ tuple(const tuple& t) : f0_(t.f0_) {}
+
+ template <GTEST_1_TYPENAMES_(U)>
+ tuple(const GTEST_1_TUPLE_(U)& t) : f0_(t.f0_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_1_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_1_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_1_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_1_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ return *this;
+ }
+
+ T0 f0_;
+};
+
+template <GTEST_2_TYPENAMES_(T)>
+class GTEST_2_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1) : f0_(f0),
+ f1_(f1) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_) {}
+
+ template <GTEST_2_TYPENAMES_(U)>
+ tuple(const GTEST_2_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_) {}
+ template <typename U0, typename U1>
+ tuple(const ::std::pair<U0, U1>& p) : f0_(p.first), f1_(p.second) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_2_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_2_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+ template <typename U0, typename U1>
+ tuple& operator=(const ::std::pair<U0, U1>& p) {
+ f0_ = p.first;
+ f1_ = p.second;
+ return *this;
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_2_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_2_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+};
+
+template <GTEST_3_TYPENAMES_(T)>
+class GTEST_3_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2) : f0_(f0), f1_(f1), f2_(f2) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {}
+
+ template <GTEST_3_TYPENAMES_(U)>
+ tuple(const GTEST_3_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_3_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_3_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_3_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_3_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+};
+
+template <GTEST_4_TYPENAMES_(T)>
+class GTEST_4_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3) : f0_(f0), f1_(f1), f2_(f2),
+ f3_(f3) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_) {}
+
+ template <GTEST_4_TYPENAMES_(U)>
+ tuple(const GTEST_4_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_4_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_4_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_4_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_4_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+};
+
+template <GTEST_5_TYPENAMES_(T)>
+class GTEST_5_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3,
+ GTEST_BY_REF_(T4) f4) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_) {}
+
+ template <GTEST_5_TYPENAMES_(U)>
+ tuple(const GTEST_5_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_5_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_5_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_5_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_5_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+};
+
+template <GTEST_6_TYPENAMES_(T)>
+class GTEST_6_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4,
+ GTEST_BY_REF_(T5) f5) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4),
+ f5_(f5) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_), f5_(t.f5_) {}
+
+ template <GTEST_6_TYPENAMES_(U)>
+ tuple(const GTEST_6_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_), f5_(t.f5_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_6_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_6_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_6_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_6_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ f5_ = t.f5_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+ T5 f5_;
+};
+
+template <GTEST_7_TYPENAMES_(T)>
+class GTEST_7_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4,
+ GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6) : f0_(f0), f1_(f1), f2_(f2),
+ f3_(f3), f4_(f4), f5_(f5), f6_(f6) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {}
+
+ template <GTEST_7_TYPENAMES_(U)>
+ tuple(const GTEST_7_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_7_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_7_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_7_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_7_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ f5_ = t.f5_;
+ f6_ = t.f6_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+ T5 f5_;
+ T6 f6_;
+};
+
+template <GTEST_8_TYPENAMES_(T)>
+class GTEST_8_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4,
+ GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6,
+ GTEST_BY_REF_(T7) f7) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4),
+ f5_(f5), f6_(f6), f7_(f7) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {}
+
+ template <GTEST_8_TYPENAMES_(U)>
+ tuple(const GTEST_8_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_8_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_8_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_8_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_8_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ f5_ = t.f5_;
+ f6_ = t.f6_;
+ f7_ = t.f7_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+ T5 f5_;
+ T6 f6_;
+ T7 f7_;
+};
+
+template <GTEST_9_TYPENAMES_(T)>
+class GTEST_9_TUPLE_(T) {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4,
+ GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7,
+ GTEST_BY_REF_(T8) f8) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4),
+ f5_(f5), f6_(f6), f7_(f7), f8_(f8) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {}
+
+ template <GTEST_9_TYPENAMES_(U)>
+ tuple(const GTEST_9_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_9_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_9_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_9_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_9_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ f5_ = t.f5_;
+ f6_ = t.f6_;
+ f7_ = t.f7_;
+ f8_ = t.f8_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+ T5 f5_;
+ T6 f6_;
+ T7 f7_;
+ T8 f8_;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+class tuple {
+ public:
+ template <int k> friend class gtest_internal::Get;
+
+ tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_(),
+ f9_() {}
+
+ explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1,
+ GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4,
+ GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7,
+ GTEST_BY_REF_(T8) f8, GTEST_BY_REF_(T9) f9) : f0_(f0), f1_(f1), f2_(f2),
+ f3_(f3), f4_(f4), f5_(f5), f6_(f6), f7_(f7), f8_(f8), f9_(f9) {}
+
+ tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_),
+ f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), f9_(t.f9_) {}
+
+ template <GTEST_10_TYPENAMES_(U)>
+ tuple(const GTEST_10_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_),
+ f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_),
+ f9_(t.f9_) {}
+
+ tuple& operator=(const tuple& t) { return CopyFrom(t); }
+
+ template <GTEST_10_TYPENAMES_(U)>
+ tuple& operator=(const GTEST_10_TUPLE_(U)& t) {
+ return CopyFrom(t);
+ }
+
+ GTEST_DECLARE_TUPLE_AS_FRIEND_
+
+ template <GTEST_10_TYPENAMES_(U)>
+ tuple& CopyFrom(const GTEST_10_TUPLE_(U)& t) {
+ f0_ = t.f0_;
+ f1_ = t.f1_;
+ f2_ = t.f2_;
+ f3_ = t.f3_;
+ f4_ = t.f4_;
+ f5_ = t.f5_;
+ f6_ = t.f6_;
+ f7_ = t.f7_;
+ f8_ = t.f8_;
+ f9_ = t.f9_;
+ return *this;
+ }
+
+ T0 f0_;
+ T1 f1_;
+ T2 f2_;
+ T3 f3_;
+ T4 f4_;
+ T5 f5_;
+ T6 f6_;
+ T7 f7_;
+ T8 f8_;
+ T9 f9_;
+};
+
+// 6.1.3.2 Tuple creation functions.
+
+// Known limitations: we don't support passing an
+// std::tr1::reference_wrapper<T> to make_tuple(). And we don't
+// implement tie().
+
+inline tuple<> make_tuple() { return tuple<>(); }
+
+template <GTEST_1_TYPENAMES_(T)>
+inline GTEST_1_TUPLE_(T) make_tuple(const T0& f0) {
+ return GTEST_1_TUPLE_(T)(f0);
+}
+
+template <GTEST_2_TYPENAMES_(T)>
+inline GTEST_2_TUPLE_(T) make_tuple(const T0& f0, const T1& f1) {
+ return GTEST_2_TUPLE_(T)(f0, f1);
+}
+
+template <GTEST_3_TYPENAMES_(T)>
+inline GTEST_3_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2) {
+ return GTEST_3_TUPLE_(T)(f0, f1, f2);
+}
+
+template <GTEST_4_TYPENAMES_(T)>
+inline GTEST_4_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3) {
+ return GTEST_4_TUPLE_(T)(f0, f1, f2, f3);
+}
+
+template <GTEST_5_TYPENAMES_(T)>
+inline GTEST_5_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4) {
+ return GTEST_5_TUPLE_(T)(f0, f1, f2, f3, f4);
+}
+
+template <GTEST_6_TYPENAMES_(T)>
+inline GTEST_6_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4, const T5& f5) {
+ return GTEST_6_TUPLE_(T)(f0, f1, f2, f3, f4, f5);
+}
+
+template <GTEST_7_TYPENAMES_(T)>
+inline GTEST_7_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4, const T5& f5, const T6& f6) {
+ return GTEST_7_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6);
+}
+
+template <GTEST_8_TYPENAMES_(T)>
+inline GTEST_8_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7) {
+ return GTEST_8_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7);
+}
+
+template <GTEST_9_TYPENAMES_(T)>
+inline GTEST_9_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7,
+ const T8& f8) {
+ return GTEST_9_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8);
+}
+
+template <GTEST_10_TYPENAMES_(T)>
+inline GTEST_10_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2,
+ const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7,
+ const T8& f8, const T9& f9) {
+ return GTEST_10_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9);
+}
+
+// 6.1.3.3 Tuple helper classes.
+
+template <typename Tuple> struct tuple_size;
+
+template <GTEST_0_TYPENAMES_(T)>
+struct tuple_size<GTEST_0_TUPLE_(T) > {
+ static const int value = 0;
+};
+
+template <GTEST_1_TYPENAMES_(T)>
+struct tuple_size<GTEST_1_TUPLE_(T) > {
+ static const int value = 1;
+};
+
+template <GTEST_2_TYPENAMES_(T)>
+struct tuple_size<GTEST_2_TUPLE_(T) > {
+ static const int value = 2;
+};
+
+template <GTEST_3_TYPENAMES_(T)>
+struct tuple_size<GTEST_3_TUPLE_(T) > {
+ static const int value = 3;
+};
+
+template <GTEST_4_TYPENAMES_(T)>
+struct tuple_size<GTEST_4_TUPLE_(T) > {
+ static const int value = 4;
+};
+
+template <GTEST_5_TYPENAMES_(T)>
+struct tuple_size<GTEST_5_TUPLE_(T) > {
+ static const int value = 5;
+};
+
+template <GTEST_6_TYPENAMES_(T)>
+struct tuple_size<GTEST_6_TUPLE_(T) > {
+ static const int value = 6;
+};
+
+template <GTEST_7_TYPENAMES_(T)>
+struct tuple_size<GTEST_7_TUPLE_(T) > {
+ static const int value = 7;
+};
+
+template <GTEST_8_TYPENAMES_(T)>
+struct tuple_size<GTEST_8_TUPLE_(T) > {
+ static const int value = 8;
+};
+
+template <GTEST_9_TYPENAMES_(T)>
+struct tuple_size<GTEST_9_TUPLE_(T) > {
+ static const int value = 9;
+};
+
+template <GTEST_10_TYPENAMES_(T)>
+struct tuple_size<GTEST_10_TUPLE_(T) > {
+ static const int value = 10;
+};
+
+template <int k, class Tuple>
+struct tuple_element {
+ typedef typename gtest_internal::TupleElement<
+ k < (tuple_size<Tuple>::value), k, Tuple>::type type;
+};
+
+#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element<k, Tuple >::type
+
+// 6.1.3.4 Element access.
+
+namespace gtest_internal {
+
+template <>
+class Get<0> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple))
+ Field(Tuple& t) { return t.f0_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple))
+ ConstField(const Tuple& t) { return t.f0_; }
+};
+
+template <>
+class Get<1> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple))
+ Field(Tuple& t) { return t.f1_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple))
+ ConstField(const Tuple& t) { return t.f1_; }
+};
+
+template <>
+class Get<2> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple))
+ Field(Tuple& t) { return t.f2_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple))
+ ConstField(const Tuple& t) { return t.f2_; }
+};
+
+template <>
+class Get<3> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple))
+ Field(Tuple& t) { return t.f3_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple))
+ ConstField(const Tuple& t) { return t.f3_; }
+};
+
+template <>
+class Get<4> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple))
+ Field(Tuple& t) { return t.f4_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple))
+ ConstField(const Tuple& t) { return t.f4_; }
+};
+
+template <>
+class Get<5> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple))
+ Field(Tuple& t) { return t.f5_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple))
+ ConstField(const Tuple& t) { return t.f5_; }
+};
+
+template <>
+class Get<6> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple))
+ Field(Tuple& t) { return t.f6_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple))
+ ConstField(const Tuple& t) { return t.f6_; }
+};
+
+template <>
+class Get<7> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple))
+ Field(Tuple& t) { return t.f7_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple))
+ ConstField(const Tuple& t) { return t.f7_; }
+};
+
+template <>
+class Get<8> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple))
+ Field(Tuple& t) { return t.f8_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple))
+ ConstField(const Tuple& t) { return t.f8_; }
+};
+
+template <>
+class Get<9> {
+ public:
+ template <class Tuple>
+ static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple))
+ Field(Tuple& t) { return t.f9_; } // NOLINT
+
+ template <class Tuple>
+ static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple))
+ ConstField(const Tuple& t) { return t.f9_; }
+};
+
+} // namespace gtest_internal
+
+template <int k, GTEST_10_TYPENAMES_(T)>
+GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T)))
+get(GTEST_10_TUPLE_(T)& t) {
+ return gtest_internal::Get<k>::Field(t);
+}
+
+template <int k, GTEST_10_TYPENAMES_(T)>
+GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T)))
+get(const GTEST_10_TUPLE_(T)& t) {
+ return gtest_internal::Get<k>::ConstField(t);
+}
+
+// 6.1.3.5 Relational operators
+
+// We only implement == and !=, as we don't have a need for the rest yet.
+
+namespace gtest_internal {
+
+// SameSizeTuplePrefixComparator<k, k>::Eq(t1, t2) returns true if the
+// first k fields of t1 equals the first k fields of t2.
+// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if
+// k1 != k2.
+template <int kSize1, int kSize2>
+struct SameSizeTuplePrefixComparator;
+
+template <>
+struct SameSizeTuplePrefixComparator<0, 0> {
+ template <class Tuple1, class Tuple2>
+ static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) {
+ return true;
+ }
+};
+
+template <int k>
+struct SameSizeTuplePrefixComparator<k, k> {
+ template <class Tuple1, class Tuple2>
+ static bool Eq(const Tuple1& t1, const Tuple2& t2) {
+ return SameSizeTuplePrefixComparator<k - 1, k - 1>::Eq(t1, t2) &&
+ ::std::tr1::get<k - 1>(t1) == ::std::tr1::get<k - 1>(t2);
+ }
+};
+
+} // namespace gtest_internal
+
+template <GTEST_10_TYPENAMES_(T), GTEST_10_TYPENAMES_(U)>
+inline bool operator==(const GTEST_10_TUPLE_(T)& t,
+ const GTEST_10_TUPLE_(U)& u) {
+ return gtest_internal::SameSizeTuplePrefixComparator<
+ tuple_size<GTEST_10_TUPLE_(T) >::value,
+ tuple_size<GTEST_10_TUPLE_(U) >::value>::Eq(t, u);
+}
+
+template <GTEST_10_TYPENAMES_(T), GTEST_10_TYPENAMES_(U)>
+inline bool operator!=(const GTEST_10_TUPLE_(T)& t,
+ const GTEST_10_TUPLE_(U)& u) { return !(t == u); }
+
+// 6.1.4 Pairs.
+// Unimplemented.
+
+} // namespace tr1
+} // namespace std
+
+#undef GTEST_0_TUPLE_
+#undef GTEST_1_TUPLE_
+#undef GTEST_2_TUPLE_
+#undef GTEST_3_TUPLE_
+#undef GTEST_4_TUPLE_
+#undef GTEST_5_TUPLE_
+#undef GTEST_6_TUPLE_
+#undef GTEST_7_TUPLE_
+#undef GTEST_8_TUPLE_
+#undef GTEST_9_TUPLE_
+#undef GTEST_10_TUPLE_
+
+#undef GTEST_0_TYPENAMES_
+#undef GTEST_1_TYPENAMES_
+#undef GTEST_2_TYPENAMES_
+#undef GTEST_3_TYPENAMES_
+#undef GTEST_4_TYPENAMES_
+#undef GTEST_5_TYPENAMES_
+#undef GTEST_6_TYPENAMES_
+#undef GTEST_7_TYPENAMES_
+#undef GTEST_8_TYPENAMES_
+#undef GTEST_9_TYPENAMES_
+#undef GTEST_10_TYPENAMES_
+
+#undef GTEST_DECLARE_TUPLE_AS_FRIEND_
+#undef GTEST_BY_REF_
+#undef GTEST_ADD_REF_
+#undef GTEST_TUPLE_ELEMENT_
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_
+# elif GTEST_OS_SYMBIAN
+
+// On Symbian, BOOST_HAS_TR1_TUPLE causes Boost's TR1 tuple library to
+// use STLport's tuple implementation, which unfortunately doesn't
+// work as the copy of STLport distributed with Symbian is incomplete.
+// By making sure BOOST_HAS_TR1_TUPLE is undefined, we force Boost to
+// use its own tuple implementation.
+# ifdef BOOST_HAS_TR1_TUPLE
+# undef BOOST_HAS_TR1_TUPLE
+# endif // BOOST_HAS_TR1_TUPLE
+
+// This prevents <boost/tr1/detail/config.hpp>, which defines
+// BOOST_HAS_TR1_TUPLE, from being #included by Boost's <tuple>.
+# define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED
+# include <tuple> // IWYU pragma: export // NOLINT
+
+# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000)
+// GCC 4.0+ implements tr1/tuple in the <tr1/tuple> header. This does
+// not conform to the TR1 spec, which requires the header to be <tuple>.
+
+# if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302
+// Until version 4.3.2, gcc has a bug that causes <tr1/functional>,
+// which is #included by <tr1/tuple>, to not compile when RTTI is
+// disabled. _TR1_FUNCTIONAL is the header guard for
+// <tr1/functional>. Hence the following #define is used to prevent
+// <tr1/functional> from being included.
+# define _TR1_FUNCTIONAL 1
+# include <tr1/tuple>
+# undef _TR1_FUNCTIONAL // Allows the user to #include
+ // <tr1/functional> if they choose to.
+# else
+# include <tr1/tuple> // NOLINT
+# endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302
+
+// VS 2010 now has tr1 support.
+# elif _MSC_VER >= 1600
+# include <tuple> // IWYU pragma: export // NOLINT
+
+# else // GTEST_USE_OWN_TR1_TUPLE
+# include <tr1/tuple> // IWYU pragma: export // NOLINT
+# endif // GTEST_USE_OWN_TR1_TUPLE
+
+#endif // GTEST_HAS_TR1_TUPLE
+
+// Determines whether clone(2) is supported.
+// Usually it will only be available on Linux, excluding
+// Linux on the Itanium architecture.
+// Also see http://linux.die.net/man/2/clone.
+#ifndef GTEST_HAS_CLONE
+// The user didn't tell us, so we need to figure it out.
+
+# if GTEST_OS_LINUX && !defined(__ia64__)
+# if GTEST_OS_LINUX_ANDROID
+// On Android, clone() became available at different API levels for each 32-bit
+// architecture.
+# if defined(__LP64__) || \
+ (defined(__arm__) && __ANDROID_API__ >= 9) || \
+ (defined(__mips__) && __ANDROID_API__ >= 12) || \
+ (defined(__i386__) && __ANDROID_API__ >= 17)
+# define GTEST_HAS_CLONE 1
+# else
+# define GTEST_HAS_CLONE 0
+# endif
+# else
+# define GTEST_HAS_CLONE 1
+# endif
+# else
+# define GTEST_HAS_CLONE 0
+# endif // GTEST_OS_LINUX && !defined(__ia64__)
+
+#endif // GTEST_HAS_CLONE
+
+// Determines whether to support stream redirection. This is used to test
+// output correctness and to implement death tests.
+#ifndef GTEST_HAS_STREAM_REDIRECTION
+// By default, we assume that stream redirection is supported on all
+// platforms except known mobile ones.
+# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || \
+ GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT
+# define GTEST_HAS_STREAM_REDIRECTION 0
+# else
+# define GTEST_HAS_STREAM_REDIRECTION 1
+# endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN
+#endif // GTEST_HAS_STREAM_REDIRECTION
+
+// Determines whether to support death tests.
+// Google Test does not support death tests for VC 7.1 and earlier as
+// abort() in a VC 7.1 application compiled as GUI in debug config
+// pops up a dialog window that cannot be suppressed programmatically.
+#if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \
+ (GTEST_OS_MAC && !GTEST_OS_IOS) || \
+ (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \
+ GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX || GTEST_OS_HPUX || \
+ GTEST_OS_OPENBSD || GTEST_OS_QNX || GTEST_OS_FREEBSD || \
+ GTEST_OS_NETBSD || GTEST_OS_FUCHSIA)
+# define GTEST_HAS_DEATH_TEST 1
+#endif
+
+// Determines whether to support type-driven tests.
+
+// Typed tests need <typeinfo> and variadic macros, which GCC, VC++ 8.0,
+// Sun Pro CC, IBM Visual Age, and HP aCC support.
+#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__SUNPRO_CC) || \
+ defined(__IBMCPP__) || defined(__HP_aCC)
+# define GTEST_HAS_TYPED_TEST 1
+# define GTEST_HAS_TYPED_TEST_P 1
+#endif
+
+// Determines whether to support Combine(). This only makes sense when
+// value-parameterized tests are enabled. The implementation doesn't
+// work on Sun Studio since it doesn't understand templated conversion
+// operators.
+#if (GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_) && !defined(__SUNPRO_CC)
+# define GTEST_HAS_COMBINE 1
+#endif
+
+// Determines whether the system compiler uses UTF-16 for encoding wide strings.
+#define GTEST_WIDE_STRING_USES_UTF16_ \
+ (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN || GTEST_OS_AIX)
+
+// Determines whether test results can be streamed to a socket.
+#if GTEST_OS_LINUX
+# define GTEST_CAN_STREAM_RESULTS_ 1
+#endif
+
+// Defines some utility macros.
+
+// The GNU compiler emits a warning if nested "if" statements are followed by
+// an "else" statement and braces are not used to explicitly disambiguate the
+// "else" binding. This leads to problems with code like:
+//
+// if (gate)
+// ASSERT_*(condition) << "Some message";
+//
+// The "switch (0) case 0:" idiom is used to suppress this.
+#ifdef __INTEL_COMPILER
+# define GTEST_AMBIGUOUS_ELSE_BLOCKER_
+#else
+# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT
+#endif
+
+// Use this annotation at the end of a struct/class definition to
+// prevent the compiler from optimizing away instances that are never
+// used. This is useful when all interesting logic happens inside the
+// c'tor and / or d'tor. Example:
+//
+// struct Foo {
+// Foo() { ... }
+// } GTEST_ATTRIBUTE_UNUSED_;
+//
+// Also use it after a variable or parameter declaration to tell the
+// compiler the variable/parameter does not have to be used.
+#if defined(__GNUC__) && !defined(COMPILER_ICC)
+# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused))
+#elif defined(__clang__)
+# if __has_attribute(unused)
+# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused))
+# endif
+#endif
+#ifndef GTEST_ATTRIBUTE_UNUSED_
+# define GTEST_ATTRIBUTE_UNUSED_
+#endif
+
+#if GTEST_LANG_CXX11
+# define GTEST_CXX11_EQUALS_DELETE_ = delete
+#else // GTEST_LANG_CXX11
+# define GTEST_CXX11_EQUALS_DELETE_
+#endif // GTEST_LANG_CXX11
+
+// Use this annotation before a function that takes a printf format string.
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(COMPILER_ICC)
+# if defined(__MINGW_PRINTF_FORMAT)
+// MinGW has two different printf implementations. Ensure the format macro
+// matches the selected implementation. See
+// https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/.
+# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \
+ __attribute__((__format__(__MINGW_PRINTF_FORMAT, string_index, \
+ first_to_check)))
+# else
+# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \
+ __attribute__((__format__(__printf__, string_index, first_to_check)))
+# endif
+#else
+# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check)
+#endif
+
+
+// A macro to disallow operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_ASSIGN_(type) \
+ void operator=(type const &) GTEST_CXX11_EQUALS_DELETE_
+
+// A macro to disallow copy constructor and operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type) \
+ type(type const &) GTEST_CXX11_EQUALS_DELETE_; \
+ GTEST_DISALLOW_ASSIGN_(type)
+
+// Tell the compiler to warn about unused return values for functions declared
+// with this macro. The macro should be used on function declarations
+// following the argument list:
+//
+// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_;
+#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC)
+# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result))
+#else
+# define GTEST_MUST_USE_RESULT_
+#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC
+
+// MS C++ compiler emits warning when a conditional expression is compile time
+// constant. In some contexts this warning is false positive and needs to be
+// suppressed. Use the following two macros in such cases:
+//
+// GTEST_INTENTIONAL_CONST_COND_PUSH_()
+// while (true) {
+// GTEST_INTENTIONAL_CONST_COND_POP_()
+// }
+# define GTEST_INTENTIONAL_CONST_COND_PUSH_() \
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127)
+# define GTEST_INTENTIONAL_CONST_COND_POP_() \
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+
+// Determine whether the compiler supports Microsoft's Structured Exception
+// Handling. This is supported by several Windows compilers but generally
+// does not exist on any other system.
+#ifndef GTEST_HAS_SEH
+// The user didn't tell us, so we need to figure it out.
+
+# if defined(_MSC_VER) || defined(__BORLANDC__)
+// These two compilers are known to support SEH.
+# define GTEST_HAS_SEH 1
+# else
+// Assume no SEH.
+# define GTEST_HAS_SEH 0
+# endif
+
+#define GTEST_IS_THREADSAFE \
+ (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ \
+ || (GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT) \
+ || GTEST_HAS_PTHREAD)
+
+#endif // GTEST_HAS_SEH
+
+// GTEST_API_ qualifies all symbols that must be exported. The definitions below
+// are guarded by #ifndef to give embedders a chance to define GTEST_API_ in
+// gtest/internal/custom/gtest-port.h
+#ifndef GTEST_API_
+
+#ifdef _MSC_VER
+# if GTEST_LINKED_AS_SHARED_LIBRARY
+# define GTEST_API_ __declspec(dllimport)
+# elif GTEST_CREATE_SHARED_LIBRARY
+# define GTEST_API_ __declspec(dllexport)
+# endif
+#elif __GNUC__ >= 4 || defined(__clang__)
+# define GTEST_API_ __attribute__((visibility ("default")))
+#endif // _MSC_VER
+
+#endif // GTEST_API_
+
+#ifndef GTEST_API_
+# define GTEST_API_
+#endif // GTEST_API_
+
+#ifndef GTEST_DEFAULT_DEATH_TEST_STYLE
+# define GTEST_DEFAULT_DEATH_TEST_STYLE "fast"
+#endif // GTEST_DEFAULT_DEATH_TEST_STYLE
+
+#ifdef __GNUC__
+// Ask the compiler to never inline a given function.
+# define GTEST_NO_INLINE_ __attribute__((noinline))
+#else
+# define GTEST_NO_INLINE_
+#endif
+
+// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project.
+#if !defined(GTEST_HAS_CXXABI_H_)
+# if defined(__GLIBCXX__) || (defined(_LIBCPP_VERSION) && !defined(_MSC_VER))
+# define GTEST_HAS_CXXABI_H_ 1
+# else
+# define GTEST_HAS_CXXABI_H_ 0
+# endif
+#endif
+
+// A function level attribute to disable checking for use of uninitialized
+// memory when built with MemorySanitizer.
+#if defined(__clang__)
+# if __has_feature(memory_sanitizer)
+# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ \
+ __attribute__((no_sanitize_memory))
+# else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+# endif // __has_feature(memory_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+#endif // __clang__
+
+// A function level attribute to disable AddressSanitizer instrumentation.
+#if defined(__clang__)
+# if __has_feature(address_sanitizer)
+# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \
+ __attribute__((no_sanitize_address))
+# else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+# endif // __has_feature(address_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+#endif // __clang__
+
+// A function level attribute to disable ThreadSanitizer instrumentation.
+#if defined(__clang__)
+# if __has_feature(thread_sanitizer)
+# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ \
+ __attribute__((no_sanitize_thread))
+# else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+# endif // __has_feature(thread_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+#endif // __clang__
+
+namespace testing {
+
+class Message;
+
+#if defined(GTEST_TUPLE_NAMESPACE_)
+// Import tuple and friends into the ::testing namespace.
+// It is part of our interface, having them in ::testing allows us to change
+// their types as needed.
+using GTEST_TUPLE_NAMESPACE_::get;
+using GTEST_TUPLE_NAMESPACE_::make_tuple;
+using GTEST_TUPLE_NAMESPACE_::tuple;
+using GTEST_TUPLE_NAMESPACE_::tuple_size;
+using GTEST_TUPLE_NAMESPACE_::tuple_element;
+#endif // defined(GTEST_TUPLE_NAMESPACE_)
+
+namespace internal {
+
+// A secret type that Google Test users don't know about. It has no
+// definition on purpose. Therefore it's impossible to create a
+// Secret object, which is what we want.
+class Secret;
+
+// The GTEST_COMPILE_ASSERT_ macro can be used to verify that a compile time
+// expression is true. For example, you could use it to verify the
+// size of a static array:
+//
+// GTEST_COMPILE_ASSERT_(GTEST_ARRAY_SIZE_(names) == NUM_NAMES,
+// names_incorrect_size);
+//
+// or to make sure a struct is smaller than a certain size:
+//
+// GTEST_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large);
+//
+// The second argument to the macro is the name of the variable. If
+// the expression is false, most compilers will issue a warning/error
+// containing the name of the variable.
+
+#if GTEST_LANG_CXX11
+# define GTEST_COMPILE_ASSERT_(expr, msg) static_assert(expr, #msg)
+#else // !GTEST_LANG_CXX11
+template <bool>
+ struct CompileAssert {
+};
+
+# define GTEST_COMPILE_ASSERT_(expr, msg) \
+ typedef ::testing::internal::CompileAssert<(static_cast<bool>(expr))> \
+ msg[static_cast<bool>(expr) ? 1 : -1] GTEST_ATTRIBUTE_UNUSED_
+#endif // !GTEST_LANG_CXX11
+
+// Implementation details of GTEST_COMPILE_ASSERT_:
+//
+// (In C++11, we simply use static_assert instead of the following)
+//
+// - GTEST_COMPILE_ASSERT_ works by defining an array type that has -1
+// elements (and thus is invalid) when the expression is false.
+//
+// - The simpler definition
+//
+// #define GTEST_COMPILE_ASSERT_(expr, msg) typedef char msg[(expr) ? 1 : -1]
+//
+// does not work, as gcc supports variable-length arrays whose sizes
+// are determined at run-time (this is gcc's extension and not part
+// of the C++ standard). As a result, gcc fails to reject the
+// following code with the simple definition:
+//
+// int foo;
+// GTEST_COMPILE_ASSERT_(foo, msg); // not supposed to compile as foo is
+// // not a compile-time constant.
+//
+// - By using the type CompileAssert<(bool(expr))>, we ensures that
+// expr is a compile-time constant. (Template arguments must be
+// determined at compile-time.)
+//
+// - The outter parentheses in CompileAssert<(bool(expr))> are necessary
+// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written
+//
+// CompileAssert<bool(expr)>
+//
+// instead, these compilers will refuse to compile
+//
+// GTEST_COMPILE_ASSERT_(5 > 0, some_message);
+//
+// (They seem to think the ">" in "5 > 0" marks the end of the
+// template argument list.)
+//
+// - The array size is (bool(expr) ? 1 : -1), instead of simply
+//
+// ((expr) ? 1 : -1).
+//
+// This is to avoid running into a bug in MS VC 7.1, which
+// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1.
+
+// StaticAssertTypeEqHelper is used by StaticAssertTypeEq defined in gtest.h.
+//
+// This template is declared, but intentionally undefined.
+template <typename T1, typename T2>
+struct StaticAssertTypeEqHelper;
+
+template <typename T>
+struct StaticAssertTypeEqHelper<T, T> {
+ enum { value = true };
+};
+
+// Same as std::is_same<>.
+template <typename T, typename U>
+struct IsSame {
+ enum { value = false };
+};
+template <typename T>
+struct IsSame<T, T> {
+ enum { value = true };
+};
+
+// Evaluates to the number of elements in 'array'.
+#define GTEST_ARRAY_SIZE_(array) (sizeof(array) / sizeof(array[0]))
+
+#if GTEST_HAS_GLOBAL_STRING
+typedef ::string string;
+#else
+typedef ::std::string string;
+#endif // GTEST_HAS_GLOBAL_STRING
+
+#if GTEST_HAS_GLOBAL_WSTRING
+typedef ::wstring wstring;
+#elif GTEST_HAS_STD_WSTRING
+typedef ::std::wstring wstring;
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+// A helper for suppressing warnings on constant condition. It just
+// returns 'condition'.
+GTEST_API_ bool IsTrue(bool condition);
+
+// Defines scoped_ptr.
+
+// This implementation of scoped_ptr is PARTIAL - it only contains
+// enough stuff to satisfy Google Test's need.
+template <typename T>
+class scoped_ptr {
+ public:
+ typedef T element_type;
+
+ explicit scoped_ptr(T* p = NULL) : ptr_(p) {}
+ ~scoped_ptr() { reset(); }
+
+ T& operator*() const { return *ptr_; }
+ T* operator->() const { return ptr_; }
+ T* get() const { return ptr_; }
+
+ T* release() {
+ T* const ptr = ptr_;
+ ptr_ = NULL;
+ return ptr;
+ }
+
+ void reset(T* p = NULL) {
+ if (p != ptr_) {
+ if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type.
+ delete ptr_;
+ }
+ ptr_ = p;
+ }
+ }
+
+ friend void swap(scoped_ptr& a, scoped_ptr& b) {
+ using std::swap;
+ swap(a.ptr_, b.ptr_);
+ }
+
+ private:
+ T* ptr_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr);
+};
+
+// Defines RE.
+
+#if GTEST_USES_PCRE
+// if used, PCRE is injected by custom/gtest-port.h
+#elif GTEST_USES_POSIX_RE || GTEST_USES_SIMPLE_RE
+
+// A simple C++ wrapper for <regex.h>. It uses the POSIX Extended
+// Regular Expression syntax.
+class GTEST_API_ RE {
+ public:
+ // A copy constructor is required by the Standard to initialize object
+ // references from r-values.
+ RE(const RE& other) { Init(other.pattern()); }
+
+ // Constructs an RE from a string.
+ RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT
+
+# if GTEST_HAS_GLOBAL_STRING
+
+ RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT
+
+# endif // GTEST_HAS_GLOBAL_STRING
+
+ RE(const char* regex) { Init(regex); } // NOLINT
+ ~RE();
+
+ // Returns the string representation of the regex.
+ const char* pattern() const { return pattern_; }
+
+ // FullMatch(str, re) returns true iff regular expression re matches
+ // the entire str.
+ // PartialMatch(str, re) returns true iff regular expression re
+ // matches a substring of str (including str itself).
+ //
+ // FIXME: make FullMatch() and PartialMatch() work
+ // when str contains NUL characters.
+ static bool FullMatch(const ::std::string& str, const RE& re) {
+ return FullMatch(str.c_str(), re);
+ }
+ static bool PartialMatch(const ::std::string& str, const RE& re) {
+ return PartialMatch(str.c_str(), re);
+ }
+
+# if GTEST_HAS_GLOBAL_STRING
+
+ static bool FullMatch(const ::string& str, const RE& re) {
+ return FullMatch(str.c_str(), re);
+ }
+ static bool PartialMatch(const ::string& str, const RE& re) {
+ return PartialMatch(str.c_str(), re);
+ }
+
+# endif // GTEST_HAS_GLOBAL_STRING
+
+ static bool FullMatch(const char* str, const RE& re);
+ static bool PartialMatch(const char* str, const RE& re);
+
+ private:
+ void Init(const char* regex);
+
+ // We use a const char* instead of an std::string, as Google Test used to be
+ // used where std::string is not available. FIXME: change to
+ // std::string.
+ const char* pattern_;
+ bool is_valid_;
+
+# if GTEST_USES_POSIX_RE
+
+ regex_t full_regex_; // For FullMatch().
+ regex_t partial_regex_; // For PartialMatch().
+
+# else // GTEST_USES_SIMPLE_RE
+
+ const char* full_pattern_; // For FullMatch();
+
+# endif
+
+ GTEST_DISALLOW_ASSIGN_(RE);
+};
+
+#endif // GTEST_USES_PCRE
+
+// Formats a source file path and a line number as they would appear
+// in an error message from the compiler used to compile this code.
+GTEST_API_ ::std::string FormatFileLocation(const char* file, int line);
+
+// Formats a file location for compiler-independent XML output.
+// Although this function is not platform dependent, we put it next to
+// FormatFileLocation in order to contrast the two functions.
+GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file,
+ int line);
+
+// Defines logging utilities:
+// GTEST_LOG_(severity) - logs messages at the specified severity level. The
+// message itself is streamed into the macro.
+// LogToStderr() - directs all log messages to stderr.
+// FlushInfoLog() - flushes informational log messages.
+
+enum GTestLogSeverity {
+ GTEST_INFO,
+ GTEST_WARNING,
+ GTEST_ERROR,
+ GTEST_FATAL
+};
+
+// Formats log entry severity, provides a stream object for streaming the
+// log message, and terminates the message with a newline when going out of
+// scope.
+class GTEST_API_ GTestLog {
+ public:
+ GTestLog(GTestLogSeverity severity, const char* file, int line);
+
+ // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program.
+ ~GTestLog();
+
+ ::std::ostream& GetStream() { return ::std::cerr; }
+
+ private:
+ const GTestLogSeverity severity_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog);
+};
+
+#if !defined(GTEST_LOG_)
+
+# define GTEST_LOG_(severity) \
+ ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \
+ __FILE__, __LINE__).GetStream()
+
+inline void LogToStderr() {}
+inline void FlushInfoLog() { fflush(NULL); }
+
+#endif // !defined(GTEST_LOG_)
+
+#if !defined(GTEST_CHECK_)
+// INTERNAL IMPLEMENTATION - DO NOT USE.
+//
+// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition
+// is not satisfied.
+// Synopsys:
+// GTEST_CHECK_(boolean_condition);
+// or
+// GTEST_CHECK_(boolean_condition) << "Additional message";
+//
+// This checks the condition and if the condition is not satisfied
+// it prints message about the condition violation, including the
+// condition itself, plus additional message streamed into it, if any,
+// and then it aborts the program. It aborts the program irrespective of
+// whether it is built in the debug mode or not.
+# define GTEST_CHECK_(condition) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::IsTrue(condition)) \
+ ; \
+ else \
+ GTEST_LOG_(FATAL) << "Condition " #condition " failed. "
+#endif // !defined(GTEST_CHECK_)
+
+// An all-mode assert to verify that the given POSIX-style function
+// call returns 0 (indicating success). Known limitation: this
+// doesn't expand to a balanced 'if' statement, so enclose the macro
+// in {} if you need to use it as the only statement in an 'if'
+// branch.
+#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \
+ if (const int gtest_error = (posix_call)) \
+ GTEST_LOG_(FATAL) << #posix_call << "failed with error " \
+ << gtest_error
+
+// Adds reference to a type if it is not a reference type,
+// otherwise leaves it unchanged. This is the same as
+// tr1::add_reference, which is not widely available yet.
+template <typename T>
+struct AddReference { typedef T& type; }; // NOLINT
+template <typename T>
+struct AddReference<T&> { typedef T& type; }; // NOLINT
+
+// A handy wrapper around AddReference that works when the argument T
+// depends on template parameters.
+#define GTEST_ADD_REFERENCE_(T) \
+ typename ::testing::internal::AddReference<T>::type
+
+// Transforms "T" into "const T&" according to standard reference collapsing
+// rules (this is only needed as a backport for C++98 compilers that do not
+// support reference collapsing). Specifically, it transforms:
+//
+// char ==> const char&
+// const char ==> const char&
+// char& ==> char&
+// const char& ==> const char&
+//
+// Note that the non-const reference will not have "const" added. This is
+// standard, and necessary so that "T" can always bind to "const T&".
+template <typename T>
+struct ConstRef { typedef const T& type; };
+template <typename T>
+struct ConstRef<T&> { typedef T& type; };
+
+// The argument T must depend on some template parameters.
+#define GTEST_REFERENCE_TO_CONST_(T) \
+ typename ::testing::internal::ConstRef<T>::type
+
+#if GTEST_HAS_STD_MOVE_
+using std::forward;
+using std::move;
+
+template <typename T>
+struct RvalueRef {
+ typedef T&& type;
+};
+#else // GTEST_HAS_STD_MOVE_
+template <typename T>
+const T& move(const T& t) {
+ return t;
+}
+template <typename T>
+GTEST_ADD_REFERENCE_(T) forward(GTEST_ADD_REFERENCE_(T) t) { return t; }
+
+template <typename T>
+struct RvalueRef {
+ typedef const T& type;
+};
+#endif // GTEST_HAS_STD_MOVE_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Use ImplicitCast_ as a safe version of static_cast for upcasting in
+// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a
+// const Foo*). When you use ImplicitCast_, the compiler checks that
+// the cast is safe. Such explicit ImplicitCast_s are necessary in
+// surprisingly many situations where C++ demands an exact type match
+// instead of an argument type convertable to a target type.
+//
+// The syntax for using ImplicitCast_ is the same as for static_cast:
+//
+// ImplicitCast_<ToType>(expr)
+//
+// ImplicitCast_ would have been part of the C++ standard library,
+// but the proposal was submitted too late. It will probably make
+// its way into the language in the future.
+//
+// This relatively ugly name is intentional. It prevents clashes with
+// similar functions users may have (e.g., implicit_cast). The internal
+// namespace alone is not enough because the function can be found by ADL.
+template<typename To>
+inline To ImplicitCast_(To x) { return x; }
+
+// When you upcast (that is, cast a pointer from type Foo to type
+// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts
+// always succeed. When you downcast (that is, cast a pointer from
+// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because
+// how do you know the pointer is really of type SubclassOfFoo? It
+// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus,
+// when you downcast, you should use this macro. In debug mode, we
+// use dynamic_cast<> to double-check the downcast is legal (we die
+// if it's not). In normal mode, we do the efficient static_cast<>
+// instead. Thus, it's important to test in debug mode to make sure
+// the cast is legal!
+// This is the only place in the code we should use dynamic_cast<>.
+// In particular, you SHOULDN'T be using dynamic_cast<> in order to
+// do RTTI (eg code like this:
+// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo);
+// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo);
+// You should design the code some other way not to need this.
+//
+// This relatively ugly name is intentional. It prevents clashes with
+// similar functions users may have (e.g., down_cast). The internal
+// namespace alone is not enough because the function can be found by ADL.
+template<typename To, typename From> // use like this: DownCast_<T*>(foo);
+inline To DownCast_(From* f) { // so we only accept pointers
+ // Ensures that To is a sub-type of From *. This test is here only
+ // for compile-time type checking, and has no overhead in an
+ // optimized build at run-time, as it will be optimized away
+ // completely.
+ GTEST_INTENTIONAL_CONST_COND_PUSH_()
+ if (false) {
+ GTEST_INTENTIONAL_CONST_COND_POP_()
+ const To to = NULL;
+ ::testing::internal::ImplicitCast_<From*>(to);
+ }
+
+#if GTEST_HAS_RTTI
+ // RTTI: debug mode only!
+ GTEST_CHECK_(f == NULL || dynamic_cast<To>(f) != NULL);
+#endif
+ return static_cast<To>(f);
+}
+
+// Downcasts the pointer of type Base to Derived.
+// Derived must be a subclass of Base. The parameter MUST
+// point to a class of type Derived, not any subclass of it.
+// When RTTI is available, the function performs a runtime
+// check to enforce this.
+template <class Derived, class Base>
+Derived* CheckedDowncastToActualType(Base* base) {
+#if GTEST_HAS_RTTI
+ GTEST_CHECK_(typeid(*base) == typeid(Derived));
+#endif
+
+#if GTEST_HAS_DOWNCAST_
+ return ::down_cast<Derived*>(base);
+#elif GTEST_HAS_RTTI
+ return dynamic_cast<Derived*>(base); // NOLINT
+#else
+ return static_cast<Derived*>(base); // Poor man's downcast.
+#endif
+}
+
+#if GTEST_HAS_STREAM_REDIRECTION
+
+// Defines the stderr capturer:
+// CaptureStdout - starts capturing stdout.
+// GetCapturedStdout - stops capturing stdout and returns the captured string.
+// CaptureStderr - starts capturing stderr.
+// GetCapturedStderr - stops capturing stderr and returns the captured string.
+//
+GTEST_API_ void CaptureStdout();
+GTEST_API_ std::string GetCapturedStdout();
+GTEST_API_ void CaptureStderr();
+GTEST_API_ std::string GetCapturedStderr();
+
+#endif // GTEST_HAS_STREAM_REDIRECTION
+// Returns the size (in bytes) of a file.
+GTEST_API_ size_t GetFileSize(FILE* file);
+
+// Reads the entire content of a file as a string.
+GTEST_API_ std::string ReadEntireFile(FILE* file);
+
+// All command line arguments.
+GTEST_API_ std::vector<std::string> GetArgvs();
+
+#if GTEST_HAS_DEATH_TEST
+
+std::vector<std::string> GetInjectableArgvs();
+// Deprecated: pass the args vector by value instead.
+void SetInjectableArgvs(const std::vector<std::string>* new_argvs);
+void SetInjectableArgvs(const std::vector<std::string>& new_argvs);
+#if GTEST_HAS_GLOBAL_STRING
+void SetInjectableArgvs(const std::vector< ::string>& new_argvs);
+#endif // GTEST_HAS_GLOBAL_STRING
+void ClearInjectableArgvs();
+
+#endif // GTEST_HAS_DEATH_TEST
+
+// Defines synchronization primitives.
+#if GTEST_IS_THREADSAFE
+# if GTEST_HAS_PTHREAD
+// Sleeps for (roughly) n milliseconds. This function is only for testing
+// Google Test's own constructs. Don't use it in user tests, either
+// directly or indirectly.
+inline void SleepMilliseconds(int n) {
+ const timespec time = {
+ 0, // 0 seconds.
+ n * 1000L * 1000L, // And n ms.
+ };
+ nanosleep(&time, NULL);
+}
+# endif // GTEST_HAS_PTHREAD
+
+# if GTEST_HAS_NOTIFICATION_
+// Notification has already been imported into the namespace.
+// Nothing to do here.
+
+# elif GTEST_HAS_PTHREAD
+// Allows a controller thread to pause execution of newly created
+// threads until notified. Instances of this class must be created
+// and destroyed in the controller thread.
+//
+// This class is only for testing Google Test's own constructs. Do not
+// use it in user tests, either directly or indirectly.
+class Notification {
+ public:
+ Notification() : notified_(false) {
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL));
+ }
+ ~Notification() {
+ pthread_mutex_destroy(&mutex_);
+ }
+
+ // Notifies all threads created with this notification to start. Must
+ // be called from the controller thread.
+ void Notify() {
+ pthread_mutex_lock(&mutex_);
+ notified_ = true;
+ pthread_mutex_unlock(&mutex_);
+ }
+
+ // Blocks until the controller thread notifies. Must be called from a test
+ // thread.
+ void WaitForNotification() {
+ for (;;) {
+ pthread_mutex_lock(&mutex_);
+ const bool notified = notified_;
+ pthread_mutex_unlock(&mutex_);
+ if (notified)
+ break;
+ SleepMilliseconds(10);
+ }
+ }
+
+ private:
+ pthread_mutex_t mutex_;
+ bool notified_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification);
+};
+
+# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+
+GTEST_API_ void SleepMilliseconds(int n);
+
+// Provides leak-safe Windows kernel handle ownership.
+// Used in death tests and in threading support.
+class GTEST_API_ AutoHandle {
+ public:
+ // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to
+ // avoid including <windows.h> in this header file. Including <windows.h> is
+ // undesirable because it defines a lot of symbols and macros that tend to
+ // conflict with client code. This assumption is verified by
+ // WindowsTypesTest.HANDLEIsVoidStar.
+ typedef void* Handle;
+ AutoHandle();
+ explicit AutoHandle(Handle handle);
+
+ ~AutoHandle();
+
+ Handle Get() const;
+ void Reset();
+ void Reset(Handle handle);
+
+ private:
+ // Returns true iff the handle is a valid handle object that can be closed.
+ bool IsCloseable() const;
+
+ Handle handle_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle);
+};
+
+// Allows a controller thread to pause execution of newly created
+// threads until notified. Instances of this class must be created
+// and destroyed in the controller thread.
+//
+// This class is only for testing Google Test's own constructs. Do not
+// use it in user tests, either directly or indirectly.
+class GTEST_API_ Notification {
+ public:
+ Notification();
+ void Notify();
+ void WaitForNotification();
+
+ private:
+ AutoHandle event_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification);
+};
+# endif // GTEST_HAS_NOTIFICATION_
+
+// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD
+// defined, but we don't want to use MinGW's pthreads implementation, which
+// has conformance problems with some versions of the POSIX standard.
+# if GTEST_HAS_PTHREAD && !GTEST_OS_WINDOWS_MINGW
+
+// As a C-function, ThreadFuncWithCLinkage cannot be templated itself.
+// Consequently, it cannot select a correct instantiation of ThreadWithParam
+// in order to call its Run(). Introducing ThreadWithParamBase as a
+// non-templated base class for ThreadWithParam allows us to bypass this
+// problem.
+class ThreadWithParamBase {
+ public:
+ virtual ~ThreadWithParamBase() {}
+ virtual void Run() = 0;
+};
+
+// pthread_create() accepts a pointer to a function type with the C linkage.
+// According to the Standard (7.5/1), function types with different linkages
+// are different even if they are otherwise identical. Some compilers (for
+// example, SunStudio) treat them as different types. Since class methods
+// cannot be defined with C-linkage we need to define a free C-function to
+// pass into pthread_create().
+extern "C" inline void* ThreadFuncWithCLinkage(void* thread) {
+ static_cast<ThreadWithParamBase*>(thread)->Run();
+ return NULL;
+}
+
+// Helper class for testing Google Test's multi-threading constructs.
+// To use it, write:
+//
+// void ThreadFunc(int param) { /* Do things with param */ }
+// Notification thread_can_start;
+// ...
+// // The thread_can_start parameter is optional; you can supply NULL.
+// ThreadWithParam<int> thread(&ThreadFunc, 5, &thread_can_start);
+// thread_can_start.Notify();
+//
+// These classes are only for testing Google Test's own constructs. Do
+// not use them in user tests, either directly or indirectly.
+template <typename T>
+class ThreadWithParam : public ThreadWithParamBase {
+ public:
+ typedef void UserThreadFunc(T);
+
+ ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start)
+ : func_(func),
+ param_(param),
+ thread_can_start_(thread_can_start),
+ finished_(false) {
+ ThreadWithParamBase* const base = this;
+ // The thread can be created only after all fields except thread_
+ // have been initialized.
+ GTEST_CHECK_POSIX_SUCCESS_(
+ pthread_create(&thread_, 0, &ThreadFuncWithCLinkage, base));
+ }
+ ~ThreadWithParam() { Join(); }
+
+ void Join() {
+ if (!finished_) {
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, 0));
+ finished_ = true;
+ }
+ }
+
+ virtual void Run() {
+ if (thread_can_start_ != NULL)
+ thread_can_start_->WaitForNotification();
+ func_(param_);
+ }
+
+ private:
+ UserThreadFunc* const func_; // User-supplied thread function.
+ const T param_; // User-supplied parameter to the thread function.
+ // When non-NULL, used to block execution until the controller thread
+ // notifies.
+ Notification* const thread_can_start_;
+ bool finished_; // true iff we know that the thread function has finished.
+ pthread_t thread_; // The native thread object.
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam);
+};
+# endif // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD ||
+ // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+
+# if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+// Mutex and ThreadLocal have already been imported into the namespace.
+// Nothing to do here.
+
+# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+
+// Mutex implements mutex on Windows platforms. It is used in conjunction
+// with class MutexLock:
+//
+// Mutex mutex;
+// ...
+// MutexLock lock(&mutex); // Acquires the mutex and releases it at the
+// // end of the current scope.
+//
+// A static Mutex *must* be defined or declared using one of the following
+// macros:
+// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex);
+// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex);
+//
+// (A non-static Mutex is defined/declared in the usual way).
+class GTEST_API_ Mutex {
+ public:
+ enum MutexType { kStatic = 0, kDynamic = 1 };
+ // We rely on kStaticMutex being 0 as it is to what the linker initializes
+ // type_ in static mutexes. critical_section_ will be initialized lazily
+ // in ThreadSafeLazyInit().
+ enum StaticConstructorSelector { kStaticMutex = 0 };
+
+ // This constructor intentionally does nothing. It relies on type_ being
+ // statically initialized to 0 (effectively setting it to kStatic) and on
+ // ThreadSafeLazyInit() to lazily initialize the rest of the members.
+ explicit Mutex(StaticConstructorSelector /*dummy*/) {}
+
+ Mutex();
+ ~Mutex();
+
+ void Lock();
+
+ void Unlock();
+
+ // Does nothing if the current thread holds the mutex. Otherwise, crashes
+ // with high probability.
+ void AssertHeld();
+
+ private:
+ // Initializes owner_thread_id_ and critical_section_ in static mutexes.
+ void ThreadSafeLazyInit();
+
+ // Per https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503,
+ // we assume that 0 is an invalid value for thread IDs.
+ unsigned int owner_thread_id_;
+
+ // For static mutexes, we rely on these members being initialized to zeros
+ // by the linker.
+ MutexType type_;
+ long critical_section_init_phase_; // NOLINT
+ GTEST_CRITICAL_SECTION* critical_section_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex);
+};
+
+# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+ extern ::testing::internal::Mutex mutex
+
+# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \
+ ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex)
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)". Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+ explicit GTestMutexLock(Mutex* mutex)
+ : mutex_(mutex) { mutex_->Lock(); }
+
+ ~GTestMutexLock() { mutex_->Unlock(); }
+
+ private:
+ Mutex* const mutex_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock);
+};
+
+typedef GTestMutexLock MutexLock;
+
+// Base class for ValueHolder<T>. Allows a caller to hold and delete a value
+// without knowing its type.
+class ThreadLocalValueHolderBase {
+ public:
+ virtual ~ThreadLocalValueHolderBase() {}
+};
+
+// Provides a way for a thread to send notifications to a ThreadLocal
+// regardless of its parameter type.
+class ThreadLocalBase {
+ public:
+ // Creates a new ValueHolder<T> object holding a default value passed to
+ // this ThreadLocal<T>'s constructor and returns it. It is the caller's
+ // responsibility not to call this when the ThreadLocal<T> instance already
+ // has a value on the current thread.
+ virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0;
+
+ protected:
+ ThreadLocalBase() {}
+ virtual ~ThreadLocalBase() {}
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocalBase);
+};
+
+// Maps a thread to a set of ThreadLocals that have values instantiated on that
+// thread and notifies them when the thread exits. A ThreadLocal instance is
+// expected to persist until all threads it has values on have terminated.
+class GTEST_API_ ThreadLocalRegistry {
+ public:
+ // Registers thread_local_instance as having value on the current thread.
+ // Returns a value that can be used to identify the thread from other threads.
+ static ThreadLocalValueHolderBase* GetValueOnCurrentThread(
+ const ThreadLocalBase* thread_local_instance);
+
+ // Invoked when a ThreadLocal instance is destroyed.
+ static void OnThreadLocalDestroyed(
+ const ThreadLocalBase* thread_local_instance);
+};
+
+class GTEST_API_ ThreadWithParamBase {
+ public:
+ void Join();
+
+ protected:
+ class Runnable {
+ public:
+ virtual ~Runnable() {}
+ virtual void Run() = 0;
+ };
+
+ ThreadWithParamBase(Runnable *runnable, Notification* thread_can_start);
+ virtual ~ThreadWithParamBase();
+
+ private:
+ AutoHandle thread_;
+};
+
+// Helper class for testing Google Test's multi-threading constructs.
+template <typename T>
+class ThreadWithParam : public ThreadWithParamBase {
+ public:
+ typedef void UserThreadFunc(T);
+
+ ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start)
+ : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) {
+ }
+ virtual ~ThreadWithParam() {}
+
+ private:
+ class RunnableImpl : public Runnable {
+ public:
+ RunnableImpl(UserThreadFunc* func, T param)
+ : func_(func),
+ param_(param) {
+ }
+ virtual ~RunnableImpl() {}
+ virtual void Run() {
+ func_(param_);
+ }
+
+ private:
+ UserThreadFunc* const func_;
+ const T param_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(RunnableImpl);
+ };
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam);
+};
+
+// Implements thread-local storage on Windows systems.
+//
+// // Thread 1
+// ThreadLocal<int> tl(100); // 100 is the default value for each thread.
+//
+// // Thread 2
+// tl.set(150); // Changes the value for thread 2 only.
+// EXPECT_EQ(150, tl.get());
+//
+// // Thread 1
+// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value.
+// tl.set(200);
+// EXPECT_EQ(200, tl.get());
+//
+// The template type argument T must have a public copy constructor.
+// In addition, the default ThreadLocal constructor requires T to have
+// a public default constructor.
+//
+// The users of a TheadLocal instance have to make sure that all but one
+// threads (including the main one) using that instance have exited before
+// destroying it. Otherwise, the per-thread objects managed for them by the
+// ThreadLocal instance are not guaranteed to be destroyed on all platforms.
+//
+// Google Test only uses global ThreadLocal objects. That means they
+// will die after main() has returned. Therefore, no per-thread
+// object managed by Google Test will be leaked as long as all threads
+// using Google Test have exited when main() returns.
+template <typename T>
+class ThreadLocal : public ThreadLocalBase {
+ public:
+ ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {}
+ explicit ThreadLocal(const T& value)
+ : default_factory_(new InstanceValueHolderFactory(value)) {}
+
+ ~ThreadLocal() { ThreadLocalRegistry::OnThreadLocalDestroyed(this); }
+
+ T* pointer() { return GetOrCreateValue(); }
+ const T* pointer() const { return GetOrCreateValue(); }
+ const T& get() const { return *pointer(); }
+ void set(const T& value) { *pointer() = value; }
+
+ private:
+ // Holds a value of T. Can be deleted via its base class without the caller
+ // knowing the type of T.
+ class ValueHolder : public ThreadLocalValueHolderBase {
+ public:
+ ValueHolder() : value_() {}
+ explicit ValueHolder(const T& value) : value_(value) {}
+
+ T* pointer() { return &value_; }
+
+ private:
+ T value_;
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder);
+ };
+
+
+ T* GetOrCreateValue() const {
+ return static_cast<ValueHolder*>(
+ ThreadLocalRegistry::GetValueOnCurrentThread(this))->pointer();
+ }
+
+ virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const {
+ return default_factory_->MakeNewHolder();
+ }
+
+ class ValueHolderFactory {
+ public:
+ ValueHolderFactory() {}
+ virtual ~ValueHolderFactory() {}
+ virtual ValueHolder* MakeNewHolder() const = 0;
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory);
+ };
+
+ class DefaultValueHolderFactory : public ValueHolderFactory {
+ public:
+ DefaultValueHolderFactory() {}
+ virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); }
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory);
+ };
+
+ class InstanceValueHolderFactory : public ValueHolderFactory {
+ public:
+ explicit InstanceValueHolderFactory(const T& value) : value_(value) {}
+ virtual ValueHolder* MakeNewHolder() const {
+ return new ValueHolder(value_);
+ }
+
+ private:
+ const T value_; // The value for each thread.
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory);
+ };
+
+ scoped_ptr<ValueHolderFactory> default_factory_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal);
+};
+
+# elif GTEST_HAS_PTHREAD
+
+// MutexBase and Mutex implement mutex on pthreads-based platforms.
+class MutexBase {
+ public:
+ // Acquires this mutex.
+ void Lock() {
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_));
+ owner_ = pthread_self();
+ has_owner_ = true;
+ }
+
+ // Releases this mutex.
+ void Unlock() {
+ // Since the lock is being released the owner_ field should no longer be
+ // considered valid. We don't protect writing to has_owner_ here, as it's
+ // the caller's responsibility to ensure that the current thread holds the
+ // mutex when this is called.
+ has_owner_ = false;
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_));
+ }
+
+ // Does nothing if the current thread holds the mutex. Otherwise, crashes
+ // with high probability.
+ void AssertHeld() const {
+ GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self()))
+ << "The current thread is not holding the mutex @" << this;
+ }
+
+ // A static mutex may be used before main() is entered. It may even
+ // be used before the dynamic initialization stage. Therefore we
+ // must be able to initialize a static mutex object at link time.
+ // This means MutexBase has to be a POD and its member variables
+ // have to be public.
+ public:
+ pthread_mutex_t mutex_; // The underlying pthread mutex.
+ // has_owner_ indicates whether the owner_ field below contains a valid thread
+ // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All
+ // accesses to the owner_ field should be protected by a check of this field.
+ // An alternative might be to memset() owner_ to all zeros, but there's no
+ // guarantee that a zero'd pthread_t is necessarily invalid or even different
+ // from pthread_self().
+ bool has_owner_;
+ pthread_t owner_; // The thread holding the mutex.
+};
+
+// Forward-declares a static mutex.
+# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+ extern ::testing::internal::MutexBase mutex
+
+// Defines and statically (i.e. at link time) initializes a static mutex.
+// The initialization list here does not explicitly initialize each field,
+// instead relying on default initialization for the unspecified fields. In
+// particular, the owner_ field (a pthread_t) is not explicitly initialized.
+// This allows initialization to work whether pthread_t is a scalar or struct.
+// The flag -Wmissing-field-initializers must not be specified for this to work.
+#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \
+ ::testing::internal::MutexBase mutex = {PTHREAD_MUTEX_INITIALIZER, false, 0}
+
+// The Mutex class can only be used for mutexes created at runtime. It
+// shares its API with MutexBase otherwise.
+class Mutex : public MutexBase {
+ public:
+ Mutex() {
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL));
+ has_owner_ = false;
+ }
+ ~Mutex() {
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_));
+ }
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex);
+};
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)". Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+ explicit GTestMutexLock(MutexBase* mutex)
+ : mutex_(mutex) { mutex_->Lock(); }
+
+ ~GTestMutexLock() { mutex_->Unlock(); }
+
+ private:
+ MutexBase* const mutex_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock);
+};
+
+typedef GTestMutexLock MutexLock;
+
+// Helpers for ThreadLocal.
+
+// pthread_key_create() requires DeleteThreadLocalValue() to have
+// C-linkage. Therefore it cannot be templatized to access
+// ThreadLocal<T>. Hence the need for class
+// ThreadLocalValueHolderBase.
+class ThreadLocalValueHolderBase {
+ public:
+ virtual ~ThreadLocalValueHolderBase() {}
+};
+
+// Called by pthread to delete thread-local data stored by
+// pthread_setspecific().
+extern "C" inline void DeleteThreadLocalValue(void* value_holder) {
+ delete static_cast<ThreadLocalValueHolderBase*>(value_holder);
+}
+
+// Implements thread-local storage on pthreads-based systems.
+template <typename T>
+class GTEST_API_ ThreadLocal {
+ public:
+ ThreadLocal()
+ : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {}
+ explicit ThreadLocal(const T& value)
+ : key_(CreateKey()),
+ default_factory_(new InstanceValueHolderFactory(value)) {}
+
+ ~ThreadLocal() {
+ // Destroys the managed object for the current thread, if any.
+ DeleteThreadLocalValue(pthread_getspecific(key_));
+
+ // Releases resources associated with the key. This will *not*
+ // delete managed objects for other threads.
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_));
+ }
+
+ T* pointer() { return GetOrCreateValue(); }
+ const T* pointer() const { return GetOrCreateValue(); }
+ const T& get() const { return *pointer(); }
+ void set(const T& value) { *pointer() = value; }
+
+ private:
+ // Holds a value of type T.
+ class ValueHolder : public ThreadLocalValueHolderBase {
+ public:
+ ValueHolder() : value_() {}
+ explicit ValueHolder(const T& value) : value_(value) {}
+
+ T* pointer() { return &value_; }
+
+ private:
+ T value_;
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder);
+ };
+
+ static pthread_key_t CreateKey() {
+ pthread_key_t key;
+ // When a thread exits, DeleteThreadLocalValue() will be called on
+ // the object managed for that thread.
+ GTEST_CHECK_POSIX_SUCCESS_(
+ pthread_key_create(&key, &DeleteThreadLocalValue));
+ return key;
+ }
+
+ T* GetOrCreateValue() const {
+ ThreadLocalValueHolderBase* const holder =
+ static_cast<ThreadLocalValueHolderBase*>(pthread_getspecific(key_));
+ if (holder != NULL) {
+ return CheckedDowncastToActualType<ValueHolder>(holder)->pointer();
+ }
+
+ ValueHolder* const new_holder = default_factory_->MakeNewHolder();
+ ThreadLocalValueHolderBase* const holder_base = new_holder;
+ GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base));
+ return new_holder->pointer();
+ }
+
+ class ValueHolderFactory {
+ public:
+ ValueHolderFactory() {}
+ virtual ~ValueHolderFactory() {}
+ virtual ValueHolder* MakeNewHolder() const = 0;
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory);
+ };
+
+ class DefaultValueHolderFactory : public ValueHolderFactory {
+ public:
+ DefaultValueHolderFactory() {}
+ virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); }
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory);
+ };
+
+ class InstanceValueHolderFactory : public ValueHolderFactory {
+ public:
+ explicit InstanceValueHolderFactory(const T& value) : value_(value) {}
+ virtual ValueHolder* MakeNewHolder() const {
+ return new ValueHolder(value_);
+ }
+
+ private:
+ const T value_; // The value for each thread.
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory);
+ };
+
+ // A key pthreads uses for looking up per-thread values.
+ const pthread_key_t key_;
+ scoped_ptr<ValueHolderFactory> default_factory_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal);
+};
+
+# endif // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+
+#else // GTEST_IS_THREADSAFE
+
+// A dummy implementation of synchronization primitives (mutex, lock,
+// and thread-local variable). Necessary for compiling Google Test where
+// mutex is not supported - using Google Test in multiple threads is not
+// supported on such platforms.
+
+class Mutex {
+ public:
+ Mutex() {}
+ void Lock() {}
+ void Unlock() {}
+ void AssertHeld() const {}
+};
+
+# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+ extern ::testing::internal::Mutex mutex
+
+# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)". Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+ explicit GTestMutexLock(Mutex*) {} // NOLINT
+};
+
+typedef GTestMutexLock MutexLock;
+
+template <typename T>
+class GTEST_API_ ThreadLocal {
+ public:
+ ThreadLocal() : value_() {}
+ explicit ThreadLocal(const T& value) : value_(value) {}
+ T* pointer() { return &value_; }
+ const T* pointer() const { return &value_; }
+ const T& get() const { return value_; }
+ void set(const T& value) { value_ = value; }
+ private:
+ T value_;
+};
+
+#endif // GTEST_IS_THREADSAFE
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+GTEST_API_ size_t GetThreadCount();
+
+// Passing non-POD classes through ellipsis (...) crashes the ARM
+// compiler and generates a warning in Sun Studio before 12u4. The Nokia Symbian
+// and the IBM XL C/C++ compiler try to instantiate a copy constructor
+// for objects passed through ellipsis (...), failing for uncopyable
+// objects. We define this to ensure that only POD is passed through
+// ellipsis on these systems.
+#if defined(__SYMBIAN32__) || defined(__IBMCPP__) || \
+ (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x5130)
+// We lose support for NULL detection where the compiler doesn't like
+// passing non-POD classes through ellipsis (...).
+# define GTEST_ELLIPSIS_NEEDS_POD_ 1
+#else
+# define GTEST_CAN_COMPARE_NULL 1
+#endif
+
+// The Nokia Symbian and IBM XL C/C++ compilers cannot decide between
+// const T& and const T* in a function template. These compilers
+// _can_ decide between class template specializations for T and T*,
+// so a tr1::type_traits-like is_pointer works.
+#if defined(__SYMBIAN32__) || defined(__IBMCPP__)
+# define GTEST_NEEDS_IS_POINTER_ 1
+#endif
+
+template <bool bool_value>
+struct bool_constant {
+ typedef bool_constant<bool_value> type;
+ static const bool value = bool_value;
+};
+template <bool bool_value> const bool bool_constant<bool_value>::value;
+
+typedef bool_constant<false> false_type;
+typedef bool_constant<true> true_type;
+
+template <typename T, typename U>
+struct is_same : public false_type {};
+
+template <typename T>
+struct is_same<T, T> : public true_type {};
+
+
+template <typename T>
+struct is_pointer : public false_type {};
+
+template <typename T>
+struct is_pointer<T*> : public true_type {};
+
+template <typename Iterator>
+struct IteratorTraits {
+ typedef typename Iterator::value_type value_type;
+};
+
+
+template <typename T>
+struct IteratorTraits<T*> {
+ typedef T value_type;
+};
+
+template <typename T>
+struct IteratorTraits<const T*> {
+ typedef T value_type;
+};
+
+#if GTEST_OS_WINDOWS
+# define GTEST_PATH_SEP_ "\\"
+# define GTEST_HAS_ALT_PATH_SEP_ 1
+// The biggest signed integer type the compiler supports.
+typedef __int64 BiggestInt;
+#else
+# define GTEST_PATH_SEP_ "/"
+# define GTEST_HAS_ALT_PATH_SEP_ 0
+typedef long long BiggestInt; // NOLINT
+#endif // GTEST_OS_WINDOWS
+
+// Utilities for char.
+
+// isspace(int ch) and friends accept an unsigned char or EOF. char
+// may be signed, depending on the compiler (or compiler flags).
+// Therefore we need to cast a char to unsigned char before calling
+// isspace(), etc.
+
+inline bool IsAlpha(char ch) {
+ return isalpha(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsAlNum(char ch) {
+ return isalnum(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsDigit(char ch) {
+ return isdigit(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsLower(char ch) {
+ return islower(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsSpace(char ch) {
+ return isspace(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsUpper(char ch) {
+ return isupper(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsXDigit(char ch) {
+ return isxdigit(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsXDigit(wchar_t ch) {
+ const unsigned char low_byte = static_cast<unsigned char>(ch);
+ return ch == low_byte && isxdigit(low_byte) != 0;
+}
+
+inline char ToLower(char ch) {
+ return static_cast<char>(tolower(static_cast<unsigned char>(ch)));
+}
+inline char ToUpper(char ch) {
+ return static_cast<char>(toupper(static_cast<unsigned char>(ch)));
+}
+
+inline std::string StripTrailingSpaces(std::string str) {
+ std::string::iterator it = str.end();
+ while (it != str.begin() && IsSpace(*--it))
+ it = str.erase(it);
+ return str;
+}
+
+// The testing::internal::posix namespace holds wrappers for common
+// POSIX functions. These wrappers hide the differences between
+// Windows/MSVC and POSIX systems. Since some compilers define these
+// standard functions as macros, the wrapper cannot have the same name
+// as the wrapped function.
+
+namespace posix {
+
+// Functions with a different name on Windows.
+
+#if GTEST_OS_WINDOWS
+
+typedef struct _stat StatStruct;
+
+# ifdef __BORLANDC__
+inline int IsATTY(int fd) { return isatty(fd); }
+inline int StrCaseCmp(const char* s1, const char* s2) {
+ return stricmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return strdup(src); }
+# else // !__BORLANDC__
+# if GTEST_OS_WINDOWS_MOBILE
+inline int IsATTY(int /* fd */) { return 0; }
+# else
+inline int IsATTY(int fd) { return _isatty(fd); }
+# endif // GTEST_OS_WINDOWS_MOBILE
+inline int StrCaseCmp(const char* s1, const char* s2) {
+ return _stricmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return _strdup(src); }
+# endif // __BORLANDC__
+
+# if GTEST_OS_WINDOWS_MOBILE
+inline int FileNo(FILE* file) { return reinterpret_cast<int>(_fileno(file)); }
+// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this
+// time and thus not defined there.
+# else
+inline int FileNo(FILE* file) { return _fileno(file); }
+inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); }
+inline int RmDir(const char* dir) { return _rmdir(dir); }
+inline bool IsDir(const StatStruct& st) {
+ return (_S_IFDIR & st.st_mode) != 0;
+}
+# endif // GTEST_OS_WINDOWS_MOBILE
+
+#else
+
+typedef struct stat StatStruct;
+
+inline int FileNo(FILE* file) { return fileno(file); }
+inline int IsATTY(int fd) { return isatty(fd); }
+inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); }
+inline int StrCaseCmp(const char* s1, const char* s2) {
+ return strcasecmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return strdup(src); }
+inline int RmDir(const char* dir) { return rmdir(dir); }
+inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); }
+
+#endif // GTEST_OS_WINDOWS
+
+// Functions deprecated by MSVC 8.0.
+
+GTEST_DISABLE_MSC_DEPRECATED_PUSH_()
+
+inline const char* StrNCpy(char* dest, const char* src, size_t n) {
+ return strncpy(dest, src, n);
+}
+
+// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and
+// StrError() aren't needed on Windows CE at this time and thus not
+// defined there.
+
+#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+inline int ChDir(const char* dir) { return chdir(dir); }
+#endif
+inline FILE* FOpen(const char* path, const char* mode) {
+ return fopen(path, mode);
+}
+#if !GTEST_OS_WINDOWS_MOBILE
+inline FILE *FReopen(const char* path, const char* mode, FILE* stream) {
+ return freopen(path, mode, stream);
+}
+inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); }
+#endif
+inline int FClose(FILE* fp) { return fclose(fp); }
+#if !GTEST_OS_WINDOWS_MOBILE
+inline int Read(int fd, void* buf, unsigned int count) {
+ return static_cast<int>(read(fd, buf, count));
+}
+inline int Write(int fd, const void* buf, unsigned int count) {
+ return static_cast<int>(write(fd, buf, count));
+}
+inline int Close(int fd) { return close(fd); }
+inline const char* StrError(int errnum) { return strerror(errnum); }
+#endif
+inline const char* GetEnv(const char* name) {
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT
+ // We are on Windows CE, which has no environment variables.
+ static_cast<void>(name); // To prevent 'unused argument' warning.
+ return NULL;
+#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9)
+ // Environment variables which we programmatically clear will be set to the
+ // empty string rather than unset (NULL). Handle that case.
+ const char* const env = getenv(name);
+ return (env != NULL && env[0] != '\0') ? env : NULL;
+#else
+ return getenv(name);
+#endif
+}
+
+GTEST_DISABLE_MSC_DEPRECATED_POP_()
+
+#if GTEST_OS_WINDOWS_MOBILE
+// Windows CE has no C library. The abort() function is used in
+// several places in Google Test. This implementation provides a reasonable
+// imitation of standard behaviour.
+void Abort();
+#else
+inline void Abort() { abort(); }
+#endif // GTEST_OS_WINDOWS_MOBILE
+
+} // namespace posix
+
+// MSVC "deprecates" snprintf and issues warnings wherever it is used. In
+// order to avoid these warnings, we need to use _snprintf or _snprintf_s on
+// MSVC-based platforms. We map the GTEST_SNPRINTF_ macro to the appropriate
+// function in order to achieve that. We use macro definition here because
+// snprintf is a variadic function.
+#if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE
+// MSVC 2005 and above support variadic macros.
+# define GTEST_SNPRINTF_(buffer, size, format, ...) \
+ _snprintf_s(buffer, size, size, format, __VA_ARGS__)
+#elif defined(_MSC_VER)
+// Windows CE does not define _snprintf_s and MSVC prior to 2005 doesn't
+// complain about _snprintf.
+# define GTEST_SNPRINTF_ _snprintf
+#else
+# define GTEST_SNPRINTF_ snprintf
+#endif
+
+// The maximum number a BiggestInt can represent. This definition
+// works no matter BiggestInt is represented in one's complement or
+// two's complement.
+//
+// We cannot rely on numeric_limits in STL, as __int64 and long long
+// are not part of standard C++ and numeric_limits doesn't need to be
+// defined for them.
+const BiggestInt kMaxBiggestInt =
+ ~(static_cast<BiggestInt>(1) << (8*sizeof(BiggestInt) - 1));
+
+// This template class serves as a compile-time function from size to
+// type. It maps a size in bytes to a primitive type with that
+// size. e.g.
+//
+// TypeWithSize<4>::UInt
+//
+// is typedef-ed to be unsigned int (unsigned integer made up of 4
+// bytes).
+//
+// Such functionality should belong to STL, but I cannot find it
+// there.
+//
+// Google Test uses this class in the implementation of floating-point
+// comparison.
+//
+// For now it only handles UInt (unsigned int) as that's all Google Test
+// needs. Other types can be easily added in the future if need
+// arises.
+template <size_t size>
+class TypeWithSize {
+ public:
+ // This prevents the user from using TypeWithSize<N> with incorrect
+ // values of N.
+ typedef void UInt;
+};
+
+// The specialization for size 4.
+template <>
+class TypeWithSize<4> {
+ public:
+ // unsigned int has size 4 in both gcc and MSVC.
+ //
+ // As base/basictypes.h doesn't compile on Windows, we cannot use
+ // uint32, uint64, and etc here.
+ typedef int Int;
+ typedef unsigned int UInt;
+};
+
+// The specialization for size 8.
+template <>
+class TypeWithSize<8> {
+ public:
+#if GTEST_OS_WINDOWS
+ typedef __int64 Int;
+ typedef unsigned __int64 UInt;
+#else
+ typedef long long Int; // NOLINT
+ typedef unsigned long long UInt; // NOLINT
+#endif // GTEST_OS_WINDOWS
+};
+
+// Integer types of known sizes.
+typedef TypeWithSize<4>::Int Int32;
+typedef TypeWithSize<4>::UInt UInt32;
+typedef TypeWithSize<8>::Int Int64;
+typedef TypeWithSize<8>::UInt UInt64;
+typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds.
+
+// Utilities for command line flags and environment variables.
+
+// Macro for referencing flags.
+#if !defined(GTEST_FLAG)
+# define GTEST_FLAG(name) FLAGS_gtest_##name
+#endif // !defined(GTEST_FLAG)
+
+#if !defined(GTEST_USE_OWN_FLAGFILE_FLAG_)
+# define GTEST_USE_OWN_FLAGFILE_FLAG_ 1
+#endif // !defined(GTEST_USE_OWN_FLAGFILE_FLAG_)
+
+#if !defined(GTEST_DECLARE_bool_)
+# define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver
+
+// Macros for declaring flags.
+# define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name)
+# define GTEST_DECLARE_int32_(name) \
+ GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name)
+# define GTEST_DECLARE_string_(name) \
+ GTEST_API_ extern ::std::string GTEST_FLAG(name)
+
+// Macros for defining flags.
+# define GTEST_DEFINE_bool_(name, default_val, doc) \
+ GTEST_API_ bool GTEST_FLAG(name) = (default_val)
+# define GTEST_DEFINE_int32_(name, default_val, doc) \
+ GTEST_API_ ::testing::internal::Int32 GTEST_FLAG(name) = (default_val)
+# define GTEST_DEFINE_string_(name, default_val, doc) \
+ GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val)
+
+#endif // !defined(GTEST_DECLARE_bool_)
+
+// Thread annotations
+#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_)
+# define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)
+# define GTEST_LOCK_EXCLUDED_(locks)
+#endif // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_)
+
+// Parses 'str' for a 32-bit signed integer. If successful, writes the result
+// to *value and returns true; otherwise leaves *value unchanged and returns
+// false.
+// FIXME: Find a better way to refactor flag and environment parsing
+// out of both gtest-port.cc and gtest.cc to avoid exporting this utility
+// function.
+bool ParseInt32(const Message& src_text, const char* str, Int32* value);
+
+// Parses a bool/Int32/string from the environment variable
+// corresponding to the given Google Test flag.
+bool BoolFromGTestEnv(const char* flag, bool default_val);
+GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val);
+std::string OutputFlagAlsoCheckEnvVar();
+const char* StringFromGTestEnv(const char* flag, const char* default_val);
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+
+#if GTEST_OS_LINUX
+# include <stdlib.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <unistd.h>
+#endif // GTEST_OS_LINUX
+
+#if GTEST_HAS_EXCEPTIONS
+# include <stdexcept>
+#endif
+
+#include <ctype.h>
+#include <float.h>
+#include <string.h>
+#include <iomanip>
+#include <limits>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the Message class.
+//
+// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
+// leave some internal implementation details in this header file.
+// They are clearly marked by comments like this:
+//
+// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+//
+// Such code is NOT meant to be used by a user directly, and is subject
+// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user
+// program!
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+
+#include <limits>
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// Ensures that there is at least one operator<< in the global namespace.
+// See Message& operator<<(...) below for why.
+void operator<<(const testing::internal::Secret&, int);
+
+namespace testing {
+
+// The Message class works like an ostream repeater.
+//
+// Typical usage:
+//
+// 1. You stream a bunch of values to a Message object.
+// It will remember the text in a stringstream.
+// 2. Then you stream the Message object to an ostream.
+// This causes the text in the Message to be streamed
+// to the ostream.
+//
+// For example;
+//
+// testing::Message foo;
+// foo << 1 << " != " << 2;
+// std::cout << foo;
+//
+// will print "1 != 2".
+//
+// Message is not intended to be inherited from. In particular, its
+// destructor is not virtual.
+//
+// Note that stringstream behaves differently in gcc and in MSVC. You
+// can stream a NULL char pointer to it in the former, but not in the
+// latter (it causes an access violation if you do). The Message
+// class hides this difference by treating a NULL char pointer as
+// "(null)".
+class GTEST_API_ Message {
+ private:
+ // The type of basic IO manipulators (endl, ends, and flush) for
+ // narrow streams.
+ typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&);
+
+ public:
+ // Constructs an empty Message.
+ Message();
+
+ // Copy constructor.
+ Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT
+ *ss_ << msg.GetString();
+ }
+
+ // Constructs a Message from a C-string.
+ explicit Message(const char* str) : ss_(new ::std::stringstream) {
+ *ss_ << str;
+ }
+
+#if GTEST_OS_SYMBIAN
+ // Streams a value (either a pointer or not) to this object.
+ template <typename T>
+ inline Message& operator <<(const T& value) {
+ StreamHelper(typename internal::is_pointer<T>::type(), value);
+ return *this;
+ }
+#else
+ // Streams a non-pointer value to this object.
+ template <typename T>
+ inline Message& operator <<(const T& val) {
+ // Some libraries overload << for STL containers. These
+ // overloads are defined in the global namespace instead of ::std.
+ //
+ // C++'s symbol lookup rule (i.e. Koenig lookup) says that these
+ // overloads are visible in either the std namespace or the global
+ // namespace, but not other namespaces, including the testing
+ // namespace which Google Test's Message class is in.
+ //
+ // To allow STL containers (and other types that has a << operator
+ // defined in the global namespace) to be used in Google Test
+ // assertions, testing::Message must access the custom << operator
+ // from the global namespace. With this using declaration,
+ // overloads of << defined in the global namespace and those
+ // visible via Koenig lookup are both exposed in this function.
+ using ::operator <<;
+ *ss_ << val;
+ return *this;
+ }
+
+ // Streams a pointer value to this object.
+ //
+ // This function is an overload of the previous one. When you
+ // stream a pointer to a Message, this definition will be used as it
+ // is more specialized. (The C++ Standard, section
+ // [temp.func.order].) If you stream a non-pointer, then the
+ // previous definition will be used.
+ //
+ // The reason for this overload is that streaming a NULL pointer to
+ // ostream is undefined behavior. Depending on the compiler, you
+ // may get "0", "(nil)", "(null)", or an access violation. To
+ // ensure consistent result across compilers, we always treat NULL
+ // as "(null)".
+ template <typename T>
+ inline Message& operator <<(T* const& pointer) { // NOLINT
+ if (pointer == NULL) {
+ *ss_ << "(null)";
+ } else {
+ *ss_ << pointer;
+ }
+ return *this;
+ }
+#endif // GTEST_OS_SYMBIAN
+
+ // Since the basic IO manipulators are overloaded for both narrow
+ // and wide streams, we have to provide this specialized definition
+ // of operator <<, even though its body is the same as the
+ // templatized version above. Without this definition, streaming
+ // endl or other basic IO manipulators to Message will confuse the
+ // compiler.
+ Message& operator <<(BasicNarrowIoManip val) {
+ *ss_ << val;
+ return *this;
+ }
+
+ // Instead of 1/0, we want to see true/false for bool values.
+ Message& operator <<(bool b) {
+ return *this << (b ? "true" : "false");
+ }
+
+ // These two overloads allow streaming a wide C string to a Message
+ // using the UTF-8 encoding.
+ Message& operator <<(const wchar_t* wide_c_str);
+ Message& operator <<(wchar_t* wide_c_str);
+
+#if GTEST_HAS_STD_WSTRING
+ // Converts the given wide string to a narrow string using the UTF-8
+ // encoding, and streams the result to this Message object.
+ Message& operator <<(const ::std::wstring& wstr);
+#endif // GTEST_HAS_STD_WSTRING
+
+#if GTEST_HAS_GLOBAL_WSTRING
+ // Converts the given wide string to a narrow string using the UTF-8
+ // encoding, and streams the result to this Message object.
+ Message& operator <<(const ::wstring& wstr);
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+ // Gets the text streamed to this object so far as an std::string.
+ // Each '\0' character in the buffer is replaced with "\\0".
+ //
+ // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+ std::string GetString() const;
+
+ private:
+#if GTEST_OS_SYMBIAN
+ // These are needed as the Nokia Symbian Compiler cannot decide between
+ // const T& and const T* in a function template. The Nokia compiler _can_
+ // decide between class template specializations for T and T*, so a
+ // tr1::type_traits-like is_pointer works, and we can overload on that.
+ template <typename T>
+ inline void StreamHelper(internal::true_type /*is_pointer*/, T* pointer) {
+ if (pointer == NULL) {
+ *ss_ << "(null)";
+ } else {
+ *ss_ << pointer;
+ }
+ }
+ template <typename T>
+ inline void StreamHelper(internal::false_type /*is_pointer*/,
+ const T& value) {
+ // See the comments in Message& operator <<(const T&) above for why
+ // we need this using statement.
+ using ::operator <<;
+ *ss_ << value;
+ }
+#endif // GTEST_OS_SYMBIAN
+
+ // We'll hold the text streamed to this object here.
+ const internal::scoped_ptr< ::std::stringstream> ss_;
+
+ // We declare (but don't implement) this to prevent the compiler
+ // from implementing the assignment operator.
+ void operator=(const Message&);
+};
+
+// Streams a Message to an ostream.
+inline std::ostream& operator <<(std::ostream& os, const Message& sb) {
+ return os << sb.GetString();
+}
+
+namespace internal {
+
+// Converts a streamable value to an std::string. A NULL pointer is
+// converted to "(null)". When the input value is a ::string,
+// ::std::string, ::wstring, or ::std::wstring object, each NUL
+// character in it is replaced with "\\0".
+template <typename T>
+std::string StreamableToString(const T& streamable) {
+ return (Message() << streamable).GetString();
+}
+
+} // namespace internal
+} // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Google Test filepath utilities
+//
+// This header file declares classes and functions used internally by
+// Google Test. They are subject to change without notice.
+//
+// This file is #included in gtest/internal/gtest-internal.h.
+// Do not include this header file separately!
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file declares the String class and functions used internally by
+// Google Test. They are subject to change without notice. They should not used
+// by code external to Google Test.
+//
+// This header file is #included by gtest-internal.h.
+// It should not be #included by other files.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+
+#ifdef __BORLANDC__
+// string.h is not guaranteed to provide strcpy on C++ Builder.
+# include <mem.h>
+#endif
+
+#include <string.h>
+#include <string>
+
+
+namespace testing {
+namespace internal {
+
+// String - an abstract class holding static string utilities.
+class GTEST_API_ String {
+ public:
+ // Static utility methods
+
+ // Clones a 0-terminated C string, allocating memory using new. The
+ // caller is responsible for deleting the return value using
+ // delete[]. Returns the cloned string, or NULL if the input is
+ // NULL.
+ //
+ // This is different from strdup() in string.h, which allocates
+ // memory using malloc().
+ static const char* CloneCString(const char* c_str);
+
+#if GTEST_OS_WINDOWS_MOBILE
+ // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be
+ // able to pass strings to Win32 APIs on CE we need to convert them
+ // to 'Unicode', UTF-16.
+
+ // Creates a UTF-16 wide string from the given ANSI string, allocating
+ // memory using new. The caller is responsible for deleting the return
+ // value using delete[]. Returns the wide string, or NULL if the
+ // input is NULL.
+ //
+ // The wide string is created using the ANSI codepage (CP_ACP) to
+ // match the behaviour of the ANSI versions of Win32 calls and the
+ // C runtime.
+ static LPCWSTR AnsiToUtf16(const char* c_str);
+
+ // Creates an ANSI string from the given wide string, allocating
+ // memory using new. The caller is responsible for deleting the return
+ // value using delete[]. Returns the ANSI string, or NULL if the
+ // input is NULL.
+ //
+ // The returned string is created using the ANSI codepage (CP_ACP) to
+ // match the behaviour of the ANSI versions of Win32 calls and the
+ // C runtime.
+ static const char* Utf16ToAnsi(LPCWSTR utf16_str);
+#endif
+
+ // Compares two C strings. Returns true iff they have the same content.
+ //
+ // Unlike strcmp(), this function can handle NULL argument(s). A
+ // NULL C string is considered different to any non-NULL C string,
+ // including the empty string.
+ static bool CStringEquals(const char* lhs, const char* rhs);
+
+ // Converts a wide C string to a String using the UTF-8 encoding.
+ // NULL will be converted to "(null)". If an error occurred during
+ // the conversion, "(failed to convert from wide string)" is
+ // returned.
+ static std::string ShowWideCString(const wchar_t* wide_c_str);
+
+ // Compares two wide C strings. Returns true iff they have the same
+ // content.
+ //
+ // Unlike wcscmp(), this function can handle NULL argument(s). A
+ // NULL C string is considered different to any non-NULL C string,
+ // including the empty string.
+ static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs);
+
+ // Compares two C strings, ignoring case. Returns true iff they
+ // have the same content.
+ //
+ // Unlike strcasecmp(), this function can handle NULL argument(s).
+ // A NULL C string is considered different to any non-NULL C string,
+ // including the empty string.
+ static bool CaseInsensitiveCStringEquals(const char* lhs,
+ const char* rhs);
+
+ // Compares two wide C strings, ignoring case. Returns true iff they
+ // have the same content.
+ //
+ // Unlike wcscasecmp(), this function can handle NULL argument(s).
+ // A NULL C string is considered different to any non-NULL wide C string,
+ // including the empty string.
+ // NB: The implementations on different platforms slightly differ.
+ // On windows, this method uses _wcsicmp which compares according to LC_CTYPE
+ // environment variable. On GNU platform this method uses wcscasecmp
+ // which compares according to LC_CTYPE category of the current locale.
+ // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the
+ // current locale.
+ static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs,
+ const wchar_t* rhs);
+
+ // Returns true iff the given string ends with the given suffix, ignoring
+ // case. Any string is considered to end with an empty suffix.
+ static bool EndsWithCaseInsensitive(
+ const std::string& str, const std::string& suffix);
+
+ // Formats an int value as "%02d".
+ static std::string FormatIntWidth2(int value); // "%02d" for width == 2
+
+ // Formats an int value as "%X".
+ static std::string FormatHexInt(int value);
+
+ // Formats a byte as "%02X".
+ static std::string FormatByte(unsigned char value);
+
+ private:
+ String(); // Not meant to be instantiated.
+}; // class String
+
+// Gets the content of the stringstream's buffer as an std::string. Each '\0'
+// character in the buffer is replaced with "\\0".
+GTEST_API_ std::string StringStreamToString(::std::stringstream* stream);
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+namespace internal {
+
+// FilePath - a class for file and directory pathname manipulation which
+// handles platform-specific conventions (like the pathname separator).
+// Used for helper functions for naming files in a directory for xml output.
+// Except for Set methods, all methods are const or static, which provides an
+// "immutable value object" -- useful for peace of mind.
+// A FilePath with a value ending in a path separator ("like/this/") represents
+// a directory, otherwise it is assumed to represent a file. In either case,
+// it may or may not represent an actual file or directory in the file system.
+// Names are NOT checked for syntax correctness -- no checking for illegal
+// characters, malformed paths, etc.
+
+class GTEST_API_ FilePath {
+ public:
+ FilePath() : pathname_("") { }
+ FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { }
+
+ explicit FilePath(const std::string& pathname) : pathname_(pathname) {
+ Normalize();
+ }
+
+ FilePath& operator=(const FilePath& rhs) {
+ Set(rhs);
+ return *this;
+ }
+
+ void Set(const FilePath& rhs) {
+ pathname_ = rhs.pathname_;
+ }
+
+ const std::string& string() const { return pathname_; }
+ const char* c_str() const { return pathname_.c_str(); }
+
+ // Returns the current working directory, or "" if unsuccessful.
+ static FilePath GetCurrentDir();
+
+ // Given directory = "dir", base_name = "test", number = 0,
+ // extension = "xml", returns "dir/test.xml". If number is greater
+ // than zero (e.g., 12), returns "dir/test_12.xml".
+ // On Windows platform, uses \ as the separator rather than /.
+ static FilePath MakeFileName(const FilePath& directory,
+ const FilePath& base_name,
+ int number,
+ const char* extension);
+
+ // Given directory = "dir", relative_path = "test.xml",
+ // returns "dir/test.xml".
+ // On Windows, uses \ as the separator rather than /.
+ static FilePath ConcatPaths(const FilePath& directory,
+ const FilePath& relative_path);
+
+ // Returns a pathname for a file that does not currently exist. The pathname
+ // will be directory/base_name.extension or
+ // directory/base_name_<number>.extension if directory/base_name.extension
+ // already exists. The number will be incremented until a pathname is found
+ // that does not already exist.
+ // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
+ // There could be a race condition if two or more processes are calling this
+ // function at the same time -- they could both pick the same filename.
+ static FilePath GenerateUniqueFileName(const FilePath& directory,
+ const FilePath& base_name,
+ const char* extension);
+
+ // Returns true iff the path is "".
+ bool IsEmpty() const { return pathname_.empty(); }
+
+ // If input name has a trailing separator character, removes it and returns
+ // the name, otherwise return the name string unmodified.
+ // On Windows platform, uses \ as the separator, other platforms use /.
+ FilePath RemoveTrailingPathSeparator() const;
+
+ // Returns a copy of the FilePath with the directory part removed.
+ // Example: FilePath("path/to/file").RemoveDirectoryName() returns
+ // FilePath("file"). If there is no directory part ("just_a_file"), it returns
+ // the FilePath unmodified. If there is no file part ("just_a_dir/") it
+ // returns an empty FilePath ("").
+ // On Windows platform, '\' is the path separator, otherwise it is '/'.
+ FilePath RemoveDirectoryName() const;
+
+ // RemoveFileName returns the directory path with the filename removed.
+ // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
+ // If the FilePath is "a_file" or "/a_file", RemoveFileName returns
+ // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
+ // not have a file, like "just/a/dir/", it returns the FilePath unmodified.
+ // On Windows platform, '\' is the path separator, otherwise it is '/'.
+ FilePath RemoveFileName() const;
+
+ // Returns a copy of the FilePath with the case-insensitive extension removed.
+ // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
+ // FilePath("dir/file"). If a case-insensitive extension is not
+ // found, returns a copy of the original FilePath.
+ FilePath RemoveExtension(const char* extension) const;
+
+ // Creates directories so that path exists. Returns true if successful or if
+ // the directories already exist; returns false if unable to create
+ // directories for any reason. Will also return false if the FilePath does
+ // not represent a directory (that is, it doesn't end with a path separator).
+ bool CreateDirectoriesRecursively() const;
+
+ // Create the directory so that path exists. Returns true if successful or
+ // if the directory already exists; returns false if unable to create the
+ // directory for any reason, including if the parent directory does not
+ // exist. Not named "CreateDirectory" because that's a macro on Windows.
+ bool CreateFolder() const;
+
+ // Returns true if FilePath describes something in the file-system,
+ // either a file, directory, or whatever, and that something exists.
+ bool FileOrDirectoryExists() const;
+
+ // Returns true if pathname describes a directory in the file-system
+ // that exists.
+ bool DirectoryExists() const;
+
+ // Returns true if FilePath ends with a path separator, which indicates that
+ // it is intended to represent a directory. Returns false otherwise.
+ // This does NOT check that a directory (or file) actually exists.
+ bool IsDirectory() const;
+
+ // Returns true if pathname describes a root directory. (Windows has one
+ // root directory per disk drive.)
+ bool IsRootDirectory() const;
+
+ // Returns true if pathname describes an absolute path.
+ bool IsAbsolutePath() const;
+
+ private:
+ // Replaces multiple consecutive separators with a single separator.
+ // For example, "bar///foo" becomes "bar/foo". Does not eliminate other
+ // redundancies that might be in a pathname involving "." or "..".
+ //
+ // A pathname with multiple consecutive separators may occur either through
+ // user error or as a result of some scripts or APIs that generate a pathname
+ // with a trailing separator. On other platforms the same API or script
+ // may NOT generate a pathname with a trailing "/". Then elsewhere that
+ // pathname may have another "/" and pathname components added to it,
+ // without checking for the separator already being there.
+ // The script language and operating system may allow paths like "foo//bar"
+ // but some of the functions in FilePath will not handle that correctly. In
+ // particular, RemoveTrailingPathSeparator() only removes one separator, and
+ // it is called in CreateDirectoriesRecursively() assuming that it will change
+ // a pathname from directory syntax (trailing separator) to filename syntax.
+ //
+ // On Windows this method also replaces the alternate path separator '/' with
+ // the primary path separator '\\', so that for example "bar\\/\\foo" becomes
+ // "bar\\foo".
+
+ void Normalize();
+
+ // Returns a pointer to the last occurence of a valid path separator in
+ // the FilePath. On Windows, for example, both '/' and '\' are valid path
+ // separators. Returns NULL if no path separator was found.
+ const char* FindLastPathSeparator() const;
+
+ std::string pathname_;
+}; // class FilePath
+
+} // namespace internal
+} // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+// This file was GENERATED by command:
+// pump.py gtest-type-util.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Type utilities needed for implementing typed and type-parameterized
+// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND!
+//
+// Currently we support at most 50 types in a list, and at most 50
+// type-parameterized tests in one type-parameterized test case.
+// Please contact googletestframework@googlegroups.com if you need
+// more.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+
+
+// #ifdef __GNUC__ is too general here. It is possible to use gcc without using
+// libstdc++ (which is where cxxabi.h comes from).
+# if GTEST_HAS_CXXABI_H_
+# include <cxxabi.h>
+# elif defined(__HP_aCC)
+# include <acxx_demangle.h>
+# endif // GTEST_HASH_CXXABI_H_
+
+namespace testing {
+namespace internal {
+
+// Canonicalizes a given name with respect to the Standard C++ Library.
+// This handles removing the inline namespace within `std` that is
+// used by various standard libraries (e.g., `std::__1`). Names outside
+// of namespace std are returned unmodified.
+inline std::string CanonicalizeForStdLibVersioning(std::string s) {
+ static const char prefix[] = "std::__";
+ if (s.compare(0, strlen(prefix), prefix) == 0) {
+ std::string::size_type end = s.find("::", strlen(prefix));
+ if (end != s.npos) {
+ // Erase everything between the initial `std` and the second `::`.
+ s.erase(strlen("std"), end - strlen("std"));
+ }
+ }
+ return s;
+}
+
+// GetTypeName<T>() returns a human-readable name of type T.
+// NB: This function is also used in Google Mock, so don't move it inside of
+// the typed-test-only section below.
+template <typename T>
+std::string GetTypeName() {
+# if GTEST_HAS_RTTI
+
+ const char* const name = typeid(T).name();
+# if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC)
+ int status = 0;
+ // gcc's implementation of typeid(T).name() mangles the type name,
+ // so we have to demangle it.
+# if GTEST_HAS_CXXABI_H_
+ using abi::__cxa_demangle;
+# endif // GTEST_HAS_CXXABI_H_
+ char* const readable_name = __cxa_demangle(name, 0, 0, &status);
+ const std::string name_str(status == 0 ? readable_name : name);
+ free(readable_name);
+ return CanonicalizeForStdLibVersioning(name_str);
+# else
+ return name;
+# endif // GTEST_HAS_CXXABI_H_ || __HP_aCC
+
+# else
+
+ return "<type>";
+
+# endif // GTEST_HAS_RTTI
+}
+
+#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P
+
+// AssertyTypeEq<T1, T2>::type is defined iff T1 and T2 are the same
+// type. This can be used as a compile-time assertion to ensure that
+// two types are equal.
+
+template <typename T1, typename T2>
+struct AssertTypeEq;
+
+template <typename T>
+struct AssertTypeEq<T, T> {
+ typedef bool type;
+};
+
+// A unique type used as the default value for the arguments of class
+// template Types. This allows us to simulate variadic templates
+// (e.g. Types<int>, Type<int, double>, and etc), which C++ doesn't
+// support directly.
+struct None {};
+
+// The following family of struct and struct templates are used to
+// represent type lists. In particular, TypesN<T1, T2, ..., TN>
+// represents a type list with N types (T1, T2, ..., and TN) in it.
+// Except for Types0, every struct in the family has two member types:
+// Head for the first type in the list, and Tail for the rest of the
+// list.
+
+// The empty type list.
+struct Types0 {};
+
+// Type lists of length 1, 2, 3, and so on.
+
+template <typename T1>
+struct Types1 {
+ typedef T1 Head;
+ typedef Types0 Tail;
+};
+template <typename T1, typename T2>
+struct Types2 {
+ typedef T1 Head;
+ typedef Types1<T2> Tail;
+};
+
+template <typename T1, typename T2, typename T3>
+struct Types3 {
+ typedef T1 Head;
+ typedef Types2<T2, T3> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4>
+struct Types4 {
+ typedef T1 Head;
+ typedef Types3<T2, T3, T4> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+struct Types5 {
+ typedef T1 Head;
+ typedef Types4<T2, T3, T4, T5> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+struct Types6 {
+ typedef T1 Head;
+ typedef Types5<T2, T3, T4, T5, T6> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+struct Types7 {
+ typedef T1 Head;
+ typedef Types6<T2, T3, T4, T5, T6, T7> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+struct Types8 {
+ typedef T1 Head;
+ typedef Types7<T2, T3, T4, T5, T6, T7, T8> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+struct Types9 {
+ typedef T1 Head;
+ typedef Types8<T2, T3, T4, T5, T6, T7, T8, T9> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+struct Types10 {
+ typedef T1 Head;
+ typedef Types9<T2, T3, T4, T5, T6, T7, T8, T9, T10> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11>
+struct Types11 {
+ typedef T1 Head;
+ typedef Types10<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12>
+struct Types12 {
+ typedef T1 Head;
+ typedef Types11<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13>
+struct Types13 {
+ typedef T1 Head;
+ typedef Types12<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14>
+struct Types14 {
+ typedef T1 Head;
+ typedef Types13<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15>
+struct Types15 {
+ typedef T1 Head;
+ typedef Types14<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16>
+struct Types16 {
+ typedef T1 Head;
+ typedef Types15<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17>
+struct Types17 {
+ typedef T1 Head;
+ typedef Types16<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18>
+struct Types18 {
+ typedef T1 Head;
+ typedef Types17<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19>
+struct Types19 {
+ typedef T1 Head;
+ typedef Types18<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20>
+struct Types20 {
+ typedef T1 Head;
+ typedef Types19<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21>
+struct Types21 {
+ typedef T1 Head;
+ typedef Types20<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22>
+struct Types22 {
+ typedef T1 Head;
+ typedef Types21<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23>
+struct Types23 {
+ typedef T1 Head;
+ typedef Types22<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24>
+struct Types24 {
+ typedef T1 Head;
+ typedef Types23<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25>
+struct Types25 {
+ typedef T1 Head;
+ typedef Types24<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26>
+struct Types26 {
+ typedef T1 Head;
+ typedef Types25<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27>
+struct Types27 {
+ typedef T1 Head;
+ typedef Types26<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28>
+struct Types28 {
+ typedef T1 Head;
+ typedef Types27<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29>
+struct Types29 {
+ typedef T1 Head;
+ typedef Types28<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30>
+struct Types30 {
+ typedef T1 Head;
+ typedef Types29<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31>
+struct Types31 {
+ typedef T1 Head;
+ typedef Types30<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32>
+struct Types32 {
+ typedef T1 Head;
+ typedef Types31<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33>
+struct Types33 {
+ typedef T1 Head;
+ typedef Types32<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34>
+struct Types34 {
+ typedef T1 Head;
+ typedef Types33<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35>
+struct Types35 {
+ typedef T1 Head;
+ typedef Types34<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36>
+struct Types36 {
+ typedef T1 Head;
+ typedef Types35<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37>
+struct Types37 {
+ typedef T1 Head;
+ typedef Types36<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38>
+struct Types38 {
+ typedef T1 Head;
+ typedef Types37<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39>
+struct Types39 {
+ typedef T1 Head;
+ typedef Types38<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40>
+struct Types40 {
+ typedef T1 Head;
+ typedef Types39<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41>
+struct Types41 {
+ typedef T1 Head;
+ typedef Types40<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42>
+struct Types42 {
+ typedef T1 Head;
+ typedef Types41<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43>
+struct Types43 {
+ typedef T1 Head;
+ typedef Types42<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44>
+struct Types44 {
+ typedef T1 Head;
+ typedef Types43<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45>
+struct Types45 {
+ typedef T1 Head;
+ typedef Types44<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46>
+struct Types46 {
+ typedef T1 Head;
+ typedef Types45<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47>
+struct Types47 {
+ typedef T1 Head;
+ typedef Types46<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48>
+struct Types48 {
+ typedef T1 Head;
+ typedef Types47<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49>
+struct Types49 {
+ typedef T1 Head;
+ typedef Types48<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48, T49> Tail;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49, typename T50>
+struct Types50 {
+ typedef T1 Head;
+ typedef Types49<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48, T49, T50> Tail;
+};
+
+
+} // namespace internal
+
+// We don't want to require the users to write TypesN<...> directly,
+// as that would require them to count the length. Types<...> is much
+// easier to write, but generates horrible messages when there is a
+// compiler error, as gcc insists on printing out each template
+// argument, even if it has the default value (this means Types<int>
+// will appear as Types<int, None, None, ..., None> in the compiler
+// errors).
+//
+// Our solution is to combine the best part of the two approaches: a
+// user would write Types<T1, ..., TN>, and Google Test will translate
+// that to TypesN<T1, ..., TN> internally to make error messages
+// readable. The translation is done by the 'type' member of the
+// Types template.
+template <typename T1 = internal::None, typename T2 = internal::None,
+ typename T3 = internal::None, typename T4 = internal::None,
+ typename T5 = internal::None, typename T6 = internal::None,
+ typename T7 = internal::None, typename T8 = internal::None,
+ typename T9 = internal::None, typename T10 = internal::None,
+ typename T11 = internal::None, typename T12 = internal::None,
+ typename T13 = internal::None, typename T14 = internal::None,
+ typename T15 = internal::None, typename T16 = internal::None,
+ typename T17 = internal::None, typename T18 = internal::None,
+ typename T19 = internal::None, typename T20 = internal::None,
+ typename T21 = internal::None, typename T22 = internal::None,
+ typename T23 = internal::None, typename T24 = internal::None,
+ typename T25 = internal::None, typename T26 = internal::None,
+ typename T27 = internal::None, typename T28 = internal::None,
+ typename T29 = internal::None, typename T30 = internal::None,
+ typename T31 = internal::None, typename T32 = internal::None,
+ typename T33 = internal::None, typename T34 = internal::None,
+ typename T35 = internal::None, typename T36 = internal::None,
+ typename T37 = internal::None, typename T38 = internal::None,
+ typename T39 = internal::None, typename T40 = internal::None,
+ typename T41 = internal::None, typename T42 = internal::None,
+ typename T43 = internal::None, typename T44 = internal::None,
+ typename T45 = internal::None, typename T46 = internal::None,
+ typename T47 = internal::None, typename T48 = internal::None,
+ typename T49 = internal::None, typename T50 = internal::None>
+struct Types {
+ typedef internal::Types50<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46, T47, T48, T49, T50> type;
+};
+
+template <>
+struct Types<internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types0 type;
+};
+template <typename T1>
+struct Types<T1, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types1<T1> type;
+};
+template <typename T1, typename T2>
+struct Types<T1, T2, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types2<T1, T2> type;
+};
+template <typename T1, typename T2, typename T3>
+struct Types<T1, T2, T3, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types3<T1, T2, T3> type;
+};
+template <typename T1, typename T2, typename T3, typename T4>
+struct Types<T1, T2, T3, T4, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types4<T1, T2, T3, T4> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+struct Types<T1, T2, T3, T4, T5, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types5<T1, T2, T3, T4, T5> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+struct Types<T1, T2, T3, T4, T5, T6, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types6<T1, T2, T3, T4, T5, T6> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+struct Types<T1, T2, T3, T4, T5, T6, T7, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types7<T1, T2, T3, T4, T5, T6, T7> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types8<T1, T2, T3, T4, T5, T6, T7, T8> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types9<T1, T2, T3, T4, T5, T6, T7, T8, T9> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types23<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types24<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types25<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types26<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types27<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types28<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types29<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types30<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types31<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types32<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types33<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types34<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types35<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types36<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types37<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types38<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types39<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types40<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types41<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, internal::None,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types42<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None, internal::None> {
+ typedef internal::Types43<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None, internal::None> {
+ typedef internal::Types44<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44, T45,
+ internal::None, internal::None, internal::None, internal::None,
+ internal::None> {
+ typedef internal::Types45<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44, T45,
+ T46, internal::None, internal::None, internal::None, internal::None> {
+ typedef internal::Types46<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44, T45,
+ T46, T47, internal::None, internal::None, internal::None> {
+ typedef internal::Types47<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46, T47> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44, T45,
+ T46, T47, T48, internal::None, internal::None> {
+ typedef internal::Types48<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46, T47, T48> type;
+};
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49>
+struct Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
+ T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30,
+ T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44, T45,
+ T46, T47, T48, T49, internal::None> {
+ typedef internal::Types49<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46, T47, T48, T49> type;
+};
+
+namespace internal {
+
+# define GTEST_TEMPLATE_ template <typename T> class
+
+// The template "selector" struct TemplateSel<Tmpl> is used to
+// represent Tmpl, which must be a class template with one type
+// parameter, as a type. TemplateSel<Tmpl>::Bind<T>::type is defined
+// as the type Tmpl<T>. This allows us to actually instantiate the
+// template "selected" by TemplateSel<Tmpl>.
+//
+// This trick is necessary for simulating typedef for class templates,
+// which C++ doesn't support directly.
+template <GTEST_TEMPLATE_ Tmpl>
+struct TemplateSel {
+ template <typename T>
+ struct Bind {
+ typedef Tmpl<T> type;
+ };
+};
+
+# define GTEST_BIND_(TmplSel, T) \
+ TmplSel::template Bind<T>::type
+
+// A unique struct template used as the default value for the
+// arguments of class template Templates. This allows us to simulate
+// variadic templates (e.g. Templates<int>, Templates<int, double>,
+// and etc), which C++ doesn't support directly.
+template <typename T>
+struct NoneT {};
+
+// The following family of struct and struct templates are used to
+// represent template lists. In particular, TemplatesN<T1, T2, ...,
+// TN> represents a list of N templates (T1, T2, ..., and TN). Except
+// for Templates0, every struct in the family has two member types:
+// Head for the selector of the first template in the list, and Tail
+// for the rest of the list.
+
+// The empty template list.
+struct Templates0 {};
+
+// Template lists of length 1, 2, 3, and so on.
+
+template <GTEST_TEMPLATE_ T1>
+struct Templates1 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates0 Tail;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2>
+struct Templates2 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates1<T2> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3>
+struct Templates3 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates2<T2, T3> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4>
+struct Templates4 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates3<T2, T3, T4> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5>
+struct Templates5 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates4<T2, T3, T4, T5> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6>
+struct Templates6 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates5<T2, T3, T4, T5, T6> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7>
+struct Templates7 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates6<T2, T3, T4, T5, T6, T7> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8>
+struct Templates8 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates7<T2, T3, T4, T5, T6, T7, T8> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9>
+struct Templates9 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates8<T2, T3, T4, T5, T6, T7, T8, T9> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10>
+struct Templates10 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates9<T2, T3, T4, T5, T6, T7, T8, T9, T10> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11>
+struct Templates11 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates10<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12>
+struct Templates12 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates11<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13>
+struct Templates13 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates12<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14>
+struct Templates14 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates13<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15>
+struct Templates15 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates14<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16>
+struct Templates16 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates15<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17>
+struct Templates17 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates16<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18>
+struct Templates18 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates17<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19>
+struct Templates19 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates18<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20>
+struct Templates20 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates19<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21>
+struct Templates21 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates20<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22>
+struct Templates22 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates21<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23>
+struct Templates23 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates22<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24>
+struct Templates24 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates23<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25>
+struct Templates25 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates24<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26>
+struct Templates26 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates25<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27>
+struct Templates27 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates26<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28>
+struct Templates28 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates27<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29>
+struct Templates29 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates28<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30>
+struct Templates30 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates29<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31>
+struct Templates31 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates30<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32>
+struct Templates32 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates31<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33>
+struct Templates33 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates32<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34>
+struct Templates34 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates33<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35>
+struct Templates35 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates34<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36>
+struct Templates36 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates35<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37>
+struct Templates37 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates36<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38>
+struct Templates38 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates37<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39>
+struct Templates39 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates38<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40>
+struct Templates40 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates39<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41>
+struct Templates41 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates40<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42>
+struct Templates42 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates41<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43>
+struct Templates43 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates42<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44>
+struct Templates44 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates43<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45>
+struct Templates45 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates44<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46>
+struct Templates46 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates45<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45, T46> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47>
+struct Templates47 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates46<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45, T46, T47> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47, GTEST_TEMPLATE_ T48>
+struct Templates48 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates47<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45, T46, T47, T48> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47, GTEST_TEMPLATE_ T48,
+ GTEST_TEMPLATE_ T49>
+struct Templates49 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates48<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45, T46, T47, T48, T49> Tail;
+};
+
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47, GTEST_TEMPLATE_ T48,
+ GTEST_TEMPLATE_ T49, GTEST_TEMPLATE_ T50>
+struct Templates50 {
+ typedef TemplateSel<T1> Head;
+ typedef Templates49<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43, T44, T45, T46, T47, T48, T49, T50> Tail;
+};
+
+
+// We don't want to require the users to write TemplatesN<...> directly,
+// as that would require them to count the length. Templates<...> is much
+// easier to write, but generates horrible messages when there is a
+// compiler error, as gcc insists on printing out each template
+// argument, even if it has the default value (this means Templates<list>
+// will appear as Templates<list, NoneT, NoneT, ..., NoneT> in the compiler
+// errors).
+//
+// Our solution is to combine the best part of the two approaches: a
+// user would write Templates<T1, ..., TN>, and Google Test will translate
+// that to TemplatesN<T1, ..., TN> internally to make error messages
+// readable. The translation is done by the 'type' member of the
+// Templates template.
+template <GTEST_TEMPLATE_ T1 = NoneT, GTEST_TEMPLATE_ T2 = NoneT,
+ GTEST_TEMPLATE_ T3 = NoneT, GTEST_TEMPLATE_ T4 = NoneT,
+ GTEST_TEMPLATE_ T5 = NoneT, GTEST_TEMPLATE_ T6 = NoneT,
+ GTEST_TEMPLATE_ T7 = NoneT, GTEST_TEMPLATE_ T8 = NoneT,
+ GTEST_TEMPLATE_ T9 = NoneT, GTEST_TEMPLATE_ T10 = NoneT,
+ GTEST_TEMPLATE_ T11 = NoneT, GTEST_TEMPLATE_ T12 = NoneT,
+ GTEST_TEMPLATE_ T13 = NoneT, GTEST_TEMPLATE_ T14 = NoneT,
+ GTEST_TEMPLATE_ T15 = NoneT, GTEST_TEMPLATE_ T16 = NoneT,
+ GTEST_TEMPLATE_ T17 = NoneT, GTEST_TEMPLATE_ T18 = NoneT,
+ GTEST_TEMPLATE_ T19 = NoneT, GTEST_TEMPLATE_ T20 = NoneT,
+ GTEST_TEMPLATE_ T21 = NoneT, GTEST_TEMPLATE_ T22 = NoneT,
+ GTEST_TEMPLATE_ T23 = NoneT, GTEST_TEMPLATE_ T24 = NoneT,
+ GTEST_TEMPLATE_ T25 = NoneT, GTEST_TEMPLATE_ T26 = NoneT,
+ GTEST_TEMPLATE_ T27 = NoneT, GTEST_TEMPLATE_ T28 = NoneT,
+ GTEST_TEMPLATE_ T29 = NoneT, GTEST_TEMPLATE_ T30 = NoneT,
+ GTEST_TEMPLATE_ T31 = NoneT, GTEST_TEMPLATE_ T32 = NoneT,
+ GTEST_TEMPLATE_ T33 = NoneT, GTEST_TEMPLATE_ T34 = NoneT,
+ GTEST_TEMPLATE_ T35 = NoneT, GTEST_TEMPLATE_ T36 = NoneT,
+ GTEST_TEMPLATE_ T37 = NoneT, GTEST_TEMPLATE_ T38 = NoneT,
+ GTEST_TEMPLATE_ T39 = NoneT, GTEST_TEMPLATE_ T40 = NoneT,
+ GTEST_TEMPLATE_ T41 = NoneT, GTEST_TEMPLATE_ T42 = NoneT,
+ GTEST_TEMPLATE_ T43 = NoneT, GTEST_TEMPLATE_ T44 = NoneT,
+ GTEST_TEMPLATE_ T45 = NoneT, GTEST_TEMPLATE_ T46 = NoneT,
+ GTEST_TEMPLATE_ T47 = NoneT, GTEST_TEMPLATE_ T48 = NoneT,
+ GTEST_TEMPLATE_ T49 = NoneT, GTEST_TEMPLATE_ T50 = NoneT>
+struct Templates {
+ typedef Templates50<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45, T46, T47, T48, T49, T50> type;
+};
+
+template <>
+struct Templates<NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT> {
+ typedef Templates0 type;
+};
+template <GTEST_TEMPLATE_ T1>
+struct Templates<T1, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT> {
+ typedef Templates1<T1> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2>
+struct Templates<T1, T2, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT> {
+ typedef Templates2<T1, T2> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3>
+struct Templates<T1, T2, T3, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates3<T1, T2, T3> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4>
+struct Templates<T1, T2, T3, T4, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates4<T1, T2, T3, T4> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5>
+struct Templates<T1, T2, T3, T4, T5, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates5<T1, T2, T3, T4, T5> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6>
+struct Templates<T1, T2, T3, T4, T5, T6, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates6<T1, T2, T3, T4, T5, T6> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates7<T1, T2, T3, T4, T5, T6, T7> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates8<T1, T2, T3, T4, T5, T6, T7, T8> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates9<T1, T2, T3, T4, T5, T6, T7, T8, T9> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT> {
+ typedef Templates22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT> {
+ typedef Templates23<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT> {
+ typedef Templates24<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT> {
+ typedef Templates25<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT> {
+ typedef Templates26<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT> {
+ typedef Templates27<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT> {
+ typedef Templates28<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT> {
+ typedef Templates29<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates30<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates31<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates32<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates33<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates34<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates35<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates36<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, NoneT, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates37<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, NoneT, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates38<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates39<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, NoneT, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates40<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, NoneT, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates41<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, NoneT,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates42<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates43<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ NoneT, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates44<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ T45, NoneT, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates45<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ T45, T46, NoneT, NoneT, NoneT, NoneT> {
+ typedef Templates46<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45, T46> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ T45, T46, T47, NoneT, NoneT, NoneT> {
+ typedef Templates47<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45, T46, T47> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47, GTEST_TEMPLATE_ T48>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ T45, T46, T47, T48, NoneT, NoneT> {
+ typedef Templates48<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45, T46, T47, T48> type;
+};
+template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2, GTEST_TEMPLATE_ T3,
+ GTEST_TEMPLATE_ T4, GTEST_TEMPLATE_ T5, GTEST_TEMPLATE_ T6,
+ GTEST_TEMPLATE_ T7, GTEST_TEMPLATE_ T8, GTEST_TEMPLATE_ T9,
+ GTEST_TEMPLATE_ T10, GTEST_TEMPLATE_ T11, GTEST_TEMPLATE_ T12,
+ GTEST_TEMPLATE_ T13, GTEST_TEMPLATE_ T14, GTEST_TEMPLATE_ T15,
+ GTEST_TEMPLATE_ T16, GTEST_TEMPLATE_ T17, GTEST_TEMPLATE_ T18,
+ GTEST_TEMPLATE_ T19, GTEST_TEMPLATE_ T20, GTEST_TEMPLATE_ T21,
+ GTEST_TEMPLATE_ T22, GTEST_TEMPLATE_ T23, GTEST_TEMPLATE_ T24,
+ GTEST_TEMPLATE_ T25, GTEST_TEMPLATE_ T26, GTEST_TEMPLATE_ T27,
+ GTEST_TEMPLATE_ T28, GTEST_TEMPLATE_ T29, GTEST_TEMPLATE_ T30,
+ GTEST_TEMPLATE_ T31, GTEST_TEMPLATE_ T32, GTEST_TEMPLATE_ T33,
+ GTEST_TEMPLATE_ T34, GTEST_TEMPLATE_ T35, GTEST_TEMPLATE_ T36,
+ GTEST_TEMPLATE_ T37, GTEST_TEMPLATE_ T38, GTEST_TEMPLATE_ T39,
+ GTEST_TEMPLATE_ T40, GTEST_TEMPLATE_ T41, GTEST_TEMPLATE_ T42,
+ GTEST_TEMPLATE_ T43, GTEST_TEMPLATE_ T44, GTEST_TEMPLATE_ T45,
+ GTEST_TEMPLATE_ T46, GTEST_TEMPLATE_ T47, GTEST_TEMPLATE_ T48,
+ GTEST_TEMPLATE_ T49>
+struct Templates<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
+ T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43, T44,
+ T45, T46, T47, T48, T49, NoneT> {
+ typedef Templates49<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42, T43, T44, T45, T46, T47, T48, T49> type;
+};
+
+// The TypeList template makes it possible to use either a single type
+// or a Types<...> list in TYPED_TEST_CASE() and
+// INSTANTIATE_TYPED_TEST_CASE_P().
+
+template <typename T>
+struct TypeList {
+ typedef Types1<T> type;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49, typename T50>
+struct TypeList<Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48, T49, T50> > {
+ typedef typename Types<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41, T42, T43, T44, T45, T46, T47, T48, T49, T50>::type type;
+};
+
+#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+
+// Due to C++ preprocessor weirdness, we need double indirection to
+// concatenate two tokens when one of them is __LINE__. Writing
+//
+// foo ## __LINE__
+//
+// will result in the token foo__LINE__, instead of foo followed by
+// the current line number. For more details, see
+// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6
+#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar)
+#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar
+
+// Stringifies its argument.
+#define GTEST_STRINGIFY_(name) #name
+
+class ProtocolMessage;
+namespace proto2 { class Message; }
+
+namespace testing {
+
+// Forward declarations.
+
+class AssertionResult; // Result of an assertion.
+class Message; // Represents a failure message.
+class Test; // Represents a test.
+class TestInfo; // Information about a test.
+class TestPartResult; // Result of a test part.
+class UnitTest; // A collection of test cases.
+
+template <typename T>
+::std::string PrintToString(const T& value);
+
+namespace internal {
+
+struct TraceInfo; // Information about a trace point.
+class TestInfoImpl; // Opaque implementation of TestInfo
+class UnitTestImpl; // Opaque implementation of UnitTest
+
+// The text used in failure messages to indicate the start of the
+// stack trace.
+GTEST_API_ extern const char kStackTraceMarker[];
+
+// Two overloaded helpers for checking at compile time whether an
+// expression is a null pointer literal (i.e. NULL or any 0-valued
+// compile-time integral constant). Their return values have
+// different sizes, so we can use sizeof() to test which version is
+// picked by the compiler. These helpers have no implementations, as
+// we only need their signatures.
+//
+// Given IsNullLiteralHelper(x), the compiler will pick the first
+// version if x can be implicitly converted to Secret*, and pick the
+// second version otherwise. Since Secret is a secret and incomplete
+// type, the only expression a user can write that has type Secret* is
+// a null pointer literal. Therefore, we know that x is a null
+// pointer literal if and only if the first version is picked by the
+// compiler.
+char IsNullLiteralHelper(Secret* p);
+char (&IsNullLiteralHelper(...))[2]; // NOLINT
+
+// A compile-time bool constant that is true if and only if x is a
+// null pointer literal (i.e. NULL or any 0-valued compile-time
+// integral constant).
+#ifdef GTEST_ELLIPSIS_NEEDS_POD_
+// We lose support for NULL detection where the compiler doesn't like
+// passing non-POD classes through ellipsis (...).
+# define GTEST_IS_NULL_LITERAL_(x) false
+#else
+# define GTEST_IS_NULL_LITERAL_(x) \
+ (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1)
+#endif // GTEST_ELLIPSIS_NEEDS_POD_
+
+// Appends the user-supplied message to the Google-Test-generated message.
+GTEST_API_ std::string AppendUserMessage(
+ const std::string& gtest_msg, const Message& user_msg);
+
+#if GTEST_HAS_EXCEPTIONS
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4275 \
+/* an exported class was derived from a class that was not exported */)
+
+// This exception is thrown by (and only by) a failed Google Test
+// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions
+// are enabled). We derive it from std::runtime_error, which is for
+// errors presumably detectable only at run time. Since
+// std::runtime_error inherits from std::exception, many testing
+// frameworks know how to extract and print the message inside it.
+class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error {
+ public:
+ explicit GoogleTestFailureException(const TestPartResult& failure);
+};
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275
+
+#endif // GTEST_HAS_EXCEPTIONS
+
+namespace edit_distance {
+// Returns the optimal edits to go from 'left' to 'right'.
+// All edits cost the same, with replace having lower priority than
+// add/remove.
+// Simple implementation of the Wagner-Fischer algorithm.
+// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm
+enum EditType { kMatch, kAdd, kRemove, kReplace };
+GTEST_API_ std::vector<EditType> CalculateOptimalEdits(
+ const std::vector<size_t>& left, const std::vector<size_t>& right);
+
+// Same as above, but the input is represented as strings.
+GTEST_API_ std::vector<EditType> CalculateOptimalEdits(
+ const std::vector<std::string>& left,
+ const std::vector<std::string>& right);
+
+// Create a diff of the input strings in Unified diff format.
+GTEST_API_ std::string CreateUnifiedDiff(const std::vector<std::string>& left,
+ const std::vector<std::string>& right,
+ size_t context = 2);
+
+} // namespace edit_distance
+
+// Calculate the diff between 'left' and 'right' and return it in unified diff
+// format.
+// If not null, stores in 'total_line_count' the total number of lines found
+// in left + right.
+GTEST_API_ std::string DiffStrings(const std::string& left,
+ const std::string& right,
+ size_t* total_line_count);
+
+// Constructs and returns the message for an equality assertion
+// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure.
+//
+// The first four parameters are the expressions used in the assertion
+// and their values, as strings. For example, for ASSERT_EQ(foo, bar)
+// where foo is 5 and bar is 6, we have:
+//
+// expected_expression: "foo"
+// actual_expression: "bar"
+// expected_value: "5"
+// actual_value: "6"
+//
+// The ignoring_case parameter is true iff the assertion is a
+// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will
+// be inserted into the message.
+GTEST_API_ AssertionResult EqFailure(const char* expected_expression,
+ const char* actual_expression,
+ const std::string& expected_value,
+ const std::string& actual_value,
+ bool ignoring_case);
+
+// Constructs a failure message for Boolean assertions such as EXPECT_TRUE.
+GTEST_API_ std::string GetBoolAssertionFailureMessage(
+ const AssertionResult& assertion_result,
+ const char* expression_text,
+ const char* actual_predicate_value,
+ const char* expected_predicate_value);
+
+// This template class represents an IEEE floating-point number
+// (either single-precision or double-precision, depending on the
+// template parameters).
+//
+// The purpose of this class is to do more sophisticated number
+// comparison. (Due to round-off error, etc, it's very unlikely that
+// two floating-points will be equal exactly. Hence a naive
+// comparison by the == operation often doesn't work.)
+//
+// Format of IEEE floating-point:
+//
+// The most-significant bit being the leftmost, an IEEE
+// floating-point looks like
+//
+// sign_bit exponent_bits fraction_bits
+//
+// Here, sign_bit is a single bit that designates the sign of the
+// number.
+//
+// For float, there are 8 exponent bits and 23 fraction bits.
+//
+// For double, there are 11 exponent bits and 52 fraction bits.
+//
+// More details can be found at
+// http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
+//
+// Template parameter:
+//
+// RawType: the raw floating-point type (either float or double)
+template <typename RawType>
+class FloatingPoint {
+ public:
+ // Defines the unsigned integer type that has the same size as the
+ // floating point number.
+ typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;
+
+ // Constants.
+
+ // # of bits in a number.
+ static const size_t kBitCount = 8*sizeof(RawType);
+
+ // # of fraction bits in a number.
+ static const size_t kFractionBitCount =
+ std::numeric_limits<RawType>::digits - 1;
+
+ // # of exponent bits in a number.
+ static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
+
+ // The mask for the sign bit.
+ static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
+
+ // The mask for the fraction bits.
+ static const Bits kFractionBitMask =
+ ~static_cast<Bits>(0) >> (kExponentBitCount + 1);
+
+ // The mask for the exponent bits.
+ static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
+
+ // How many ULP's (Units in the Last Place) we want to tolerate when
+ // comparing two numbers. The larger the value, the more error we
+ // allow. A 0 value means that two numbers must be exactly the same
+ // to be considered equal.
+ //
+ // The maximum error of a single floating-point operation is 0.5
+ // units in the last place. On Intel CPU's, all floating-point
+ // calculations are done with 80-bit precision, while double has 64
+ // bits. Therefore, 4 should be enough for ordinary use.
+ //
+ // See the following article for more details on ULP:
+ // http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+ static const size_t kMaxUlps = 4;
+
+ // Constructs a FloatingPoint from a raw floating-point number.
+ //
+ // On an Intel CPU, passing a non-normalized NAN (Not a Number)
+ // around may change its bits, although the new value is guaranteed
+ // to be also a NAN. Therefore, don't expect this constructor to
+ // preserve the bits in x when x is a NAN.
+ explicit FloatingPoint(const RawType& x) { u_.value_ = x; }
+
+ // Static methods
+
+ // Reinterprets a bit pattern as a floating-point number.
+ //
+ // This function is needed to test the AlmostEquals() method.
+ static RawType ReinterpretBits(const Bits bits) {
+ FloatingPoint fp(0);
+ fp.u_.bits_ = bits;
+ return fp.u_.value_;
+ }
+
+ // Returns the floating-point number that represent positive infinity.
+ static RawType Infinity() {
+ return ReinterpretBits(kExponentBitMask);
+ }
+
+ // Returns the maximum representable finite floating-point number.
+ static RawType Max();
+
+ // Non-static methods
+
+ // Returns the bits that represents this number.
+ const Bits &bits() const { return u_.bits_; }
+
+ // Returns the exponent bits of this number.
+ Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }
+
+ // Returns the fraction bits of this number.
+ Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }
+
+ // Returns the sign bit of this number.
+ Bits sign_bit() const { return kSignBitMask & u_.bits_; }
+
+ // Returns true iff this is NAN (not a number).
+ bool is_nan() const {
+ // It's a NAN if the exponent bits are all ones and the fraction
+ // bits are not entirely zeros.
+ return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
+ }
+
+ // Returns true iff this number is at most kMaxUlps ULP's away from
+ // rhs. In particular, this function:
+ //
+ // - returns false if either number is (or both are) NAN.
+ // - treats really large numbers as almost equal to infinity.
+ // - thinks +0.0 and -0.0 are 0 DLP's apart.
+ bool AlmostEquals(const FloatingPoint& rhs) const {
+ // The IEEE standard says that any comparison operation involving
+ // a NAN must return false.
+ if (is_nan() || rhs.is_nan()) return false;
+
+ return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
+ <= kMaxUlps;
+ }
+
+ private:
+ // The data type used to store the actual floating-point number.
+ union FloatingPointUnion {
+ RawType value_; // The raw floating-point number.
+ Bits bits_; // The bits that represent the number.
+ };
+
+ // Converts an integer from the sign-and-magnitude representation to
+ // the biased representation. More precisely, let N be 2 to the
+ // power of (kBitCount - 1), an integer x is represented by the
+ // unsigned number x + N.
+ //
+ // For instance,
+ //
+ // -N + 1 (the most negative number representable using
+ // sign-and-magnitude) is represented by 1;
+ // 0 is represented by N; and
+ // N - 1 (the biggest number representable using
+ // sign-and-magnitude) is represented by 2N - 1.
+ //
+ // Read http://en.wikipedia.org/wiki/Signed_number_representations
+ // for more details on signed number representations.
+ static Bits SignAndMagnitudeToBiased(const Bits &sam) {
+ if (kSignBitMask & sam) {
+ // sam represents a negative number.
+ return ~sam + 1;
+ } else {
+ // sam represents a positive number.
+ return kSignBitMask | sam;
+ }
+ }
+
+ // Given two numbers in the sign-and-magnitude representation,
+ // returns the distance between them as an unsigned number.
+ static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
+ const Bits &sam2) {
+ const Bits biased1 = SignAndMagnitudeToBiased(sam1);
+ const Bits biased2 = SignAndMagnitudeToBiased(sam2);
+ return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
+ }
+
+ FloatingPointUnion u_;
+};
+
+// We cannot use std::numeric_limits<T>::max() as it clashes with the max()
+// macro defined by <windows.h>.
+template <>
+inline float FloatingPoint<float>::Max() { return FLT_MAX; }
+template <>
+inline double FloatingPoint<double>::Max() { return DBL_MAX; }
+
+// Typedefs the instances of the FloatingPoint template class that we
+// care to use.
+typedef FloatingPoint<float> Float;
+typedef FloatingPoint<double> Double;
+
+// In order to catch the mistake of putting tests that use different
+// test fixture classes in the same test case, we need to assign
+// unique IDs to fixture classes and compare them. The TypeId type is
+// used to hold such IDs. The user should treat TypeId as an opaque
+// type: the only operation allowed on TypeId values is to compare
+// them for equality using the == operator.
+typedef const void* TypeId;
+
+template <typename T>
+class TypeIdHelper {
+ public:
+ // dummy_ must not have a const type. Otherwise an overly eager
+ // compiler (e.g. MSVC 7.1 & 8.0) may try to merge
+ // TypeIdHelper<T>::dummy_ for different Ts as an "optimization".
+ static bool dummy_;
+};
+
+template <typename T>
+bool TypeIdHelper<T>::dummy_ = false;
+
+// GetTypeId<T>() returns the ID of type T. Different values will be
+// returned for different types. Calling the function twice with the
+// same type argument is guaranteed to return the same ID.
+template <typename T>
+TypeId GetTypeId() {
+ // The compiler is required to allocate a different
+ // TypeIdHelper<T>::dummy_ variable for each T used to instantiate
+ // the template. Therefore, the address of dummy_ is guaranteed to
+ // be unique.
+ return &(TypeIdHelper<T>::dummy_);
+}
+
+// Returns the type ID of ::testing::Test. Always call this instead
+// of GetTypeId< ::testing::Test>() to get the type ID of
+// ::testing::Test, as the latter may give the wrong result due to a
+// suspected linker bug when compiling Google Test as a Mac OS X
+// framework.
+GTEST_API_ TypeId GetTestTypeId();
+
+// Defines the abstract factory interface that creates instances
+// of a Test object.
+class TestFactoryBase {
+ public:
+ virtual ~TestFactoryBase() {}
+
+ // Creates a test instance to run. The instance is both created and destroyed
+ // within TestInfoImpl::Run()
+ virtual Test* CreateTest() = 0;
+
+ protected:
+ TestFactoryBase() {}
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase);
+};
+
+// This class provides implementation of TeastFactoryBase interface.
+// It is used in TEST and TEST_F macros.
+template <class TestClass>
+class TestFactoryImpl : public TestFactoryBase {
+ public:
+ virtual Test* CreateTest() { return new TestClass; }
+};
+
+#if GTEST_OS_WINDOWS
+
+// Predicate-formatters for implementing the HRESULT checking macros
+// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}
+// We pass a long instead of HRESULT to avoid causing an
+// include dependency for the HRESULT type.
+GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr,
+ long hr); // NOLINT
+GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr,
+ long hr); // NOLINT
+
+#endif // GTEST_OS_WINDOWS
+
+// Types of SetUpTestCase() and TearDownTestCase() functions.
+typedef void (*SetUpTestCaseFunc)();
+typedef void (*TearDownTestCaseFunc)();
+
+struct CodeLocation {
+ CodeLocation(const std::string& a_file, int a_line)
+ : file(a_file), line(a_line) {}
+
+ std::string file;
+ int line;
+};
+
+// Creates a new TestInfo object and registers it with Google Test;
+// returns the created object.
+//
+// Arguments:
+//
+// test_case_name: name of the test case
+// name: name of the test
+// type_param the name of the test's type parameter, or NULL if
+// this is not a typed or a type-parameterized test.
+// value_param text representation of the test's value parameter,
+// or NULL if this is not a type-parameterized test.
+// code_location: code location where the test is defined
+// fixture_class_id: ID of the test fixture class
+// set_up_tc: pointer to the function that sets up the test case
+// tear_down_tc: pointer to the function that tears down the test case
+// factory: pointer to the factory that creates a test object.
+// The newly created TestInfo instance will assume
+// ownership of the factory object.
+GTEST_API_ TestInfo* MakeAndRegisterTestInfo(
+ const char* test_case_name,
+ const char* name,
+ const char* type_param,
+ const char* value_param,
+ CodeLocation code_location,
+ TypeId fixture_class_id,
+ SetUpTestCaseFunc set_up_tc,
+ TearDownTestCaseFunc tear_down_tc,
+ TestFactoryBase* factory);
+
+// If *pstr starts with the given prefix, modifies *pstr to be right
+// past the prefix and returns true; otherwise leaves *pstr unchanged
+// and returns false. None of pstr, *pstr, and prefix can be NULL.
+GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr);
+
+#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// State of the definition of a type-parameterized test case.
+class GTEST_API_ TypedTestCasePState {
+ public:
+ TypedTestCasePState() : registered_(false) {}
+
+ // Adds the given test name to defined_test_names_ and return true
+ // if the test case hasn't been registered; otherwise aborts the
+ // program.
+ bool AddTestName(const char* file, int line, const char* case_name,
+ const char* test_name) {
+ if (registered_) {
+ fprintf(stderr, "%s Test %s must be defined before "
+ "REGISTER_TYPED_TEST_CASE_P(%s, ...).\n",
+ FormatFileLocation(file, line).c_str(), test_name, case_name);
+ fflush(stderr);
+ posix::Abort();
+ }
+ registered_tests_.insert(
+ ::std::make_pair(test_name, CodeLocation(file, line)));
+ return true;
+ }
+
+ bool TestExists(const std::string& test_name) const {
+ return registered_tests_.count(test_name) > 0;
+ }
+
+ const CodeLocation& GetCodeLocation(const std::string& test_name) const {
+ RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name);
+ GTEST_CHECK_(it != registered_tests_.end());
+ return it->second;
+ }
+
+ // Verifies that registered_tests match the test names in
+ // defined_test_names_; returns registered_tests if successful, or
+ // aborts the program otherwise.
+ const char* VerifyRegisteredTestNames(
+ const char* file, int line, const char* registered_tests);
+
+ private:
+ typedef ::std::map<std::string, CodeLocation> RegisteredTestsMap;
+
+ bool registered_;
+ RegisteredTestsMap registered_tests_;
+};
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+// Skips to the first non-space char after the first comma in 'str';
+// returns NULL if no comma is found in 'str'.
+inline const char* SkipComma(const char* str) {
+ const char* comma = strchr(str, ',');
+ if (comma == NULL) {
+ return NULL;
+ }
+ while (IsSpace(*(++comma))) {}
+ return comma;
+}
+
+// Returns the prefix of 'str' before the first comma in it; returns
+// the entire string if it contains no comma.
+inline std::string GetPrefixUntilComma(const char* str) {
+ const char* comma = strchr(str, ',');
+ return comma == NULL ? str : std::string(str, comma);
+}
+
+// Splits a given string on a given delimiter, populating a given
+// vector with the fields.
+void SplitString(const ::std::string& str, char delimiter,
+ ::std::vector< ::std::string>* dest);
+
+// The default argument to the template below for the case when the user does
+// not provide a name generator.
+struct DefaultNameGenerator {
+ template <typename T>
+ static std::string GetName(int i) {
+ return StreamableToString(i);
+ }
+};
+
+template <typename Provided = DefaultNameGenerator>
+struct NameGeneratorSelector {
+ typedef Provided type;
+};
+
+template <typename NameGenerator>
+void GenerateNamesRecursively(Types0, std::vector<std::string>*, int) {}
+
+template <typename NameGenerator, typename Types>
+void GenerateNamesRecursively(Types, std::vector<std::string>* result, int i) {
+ result->push_back(NameGenerator::template GetName<typename Types::Head>(i));
+ GenerateNamesRecursively<NameGenerator>(typename Types::Tail(), result,
+ i + 1);
+}
+
+template <typename NameGenerator, typename Types>
+std::vector<std::string> GenerateNames() {
+ std::vector<std::string> result;
+ GenerateNamesRecursively<NameGenerator>(Types(), &result, 0);
+ return result;
+}
+
+// TypeParameterizedTest<Fixture, TestSel, Types>::Register()
+// registers a list of type-parameterized tests with Google Test. The
+// return value is insignificant - we just need to return something
+// such that we can call this function in a namespace scope.
+//
+// Implementation note: The GTEST_TEMPLATE_ macro declares a template
+// template parameter. It's defined in gtest-type-util.h.
+template <GTEST_TEMPLATE_ Fixture, class TestSel, typename Types>
+class TypeParameterizedTest {
+ public:
+ // 'index' is the index of the test in the type list 'Types'
+ // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase,
+ // Types). Valid values for 'index' are [0, N - 1] where N is the
+ // length of Types.
+ static bool Register(const char* prefix, const CodeLocation& code_location,
+ const char* case_name, const char* test_names, int index,
+ const std::vector<std::string>& type_names =
+ GenerateNames<DefaultNameGenerator, Types>()) {
+ typedef typename Types::Head Type;
+ typedef Fixture<Type> FixtureClass;
+ typedef typename GTEST_BIND_(TestSel, Type) TestClass;
+
+ // First, registers the first type-parameterized test in the type
+ // list.
+ MakeAndRegisterTestInfo(
+ (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name +
+ "/" + type_names[index])
+ .c_str(),
+ StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(),
+ GetTypeName<Type>().c_str(),
+ NULL, // No value parameter.
+ code_location, GetTypeId<FixtureClass>(), TestClass::SetUpTestCase,
+ TestClass::TearDownTestCase, new TestFactoryImpl<TestClass>);
+
+ // Next, recurses (at compile time) with the tail of the type list.
+ return TypeParameterizedTest<Fixture, TestSel,
+ typename Types::Tail>::Register(prefix,
+ code_location,
+ case_name,
+ test_names,
+ index + 1,
+ type_names);
+ }
+};
+
+// The base case for the compile time recursion.
+template <GTEST_TEMPLATE_ Fixture, class TestSel>
+class TypeParameterizedTest<Fixture, TestSel, Types0> {
+ public:
+ static bool Register(const char* /*prefix*/, const CodeLocation&,
+ const char* /*case_name*/, const char* /*test_names*/,
+ int /*index*/,
+ const std::vector<std::string>& =
+ std::vector<std::string>() /*type_names*/) {
+ return true;
+ }
+};
+
+// TypeParameterizedTestCase<Fixture, Tests, Types>::Register()
+// registers *all combinations* of 'Tests' and 'Types' with Google
+// Test. The return value is insignificant - we just need to return
+// something such that we can call this function in a namespace scope.
+template <GTEST_TEMPLATE_ Fixture, typename Tests, typename Types>
+class TypeParameterizedTestCase {
+ public:
+ static bool Register(const char* prefix, CodeLocation code_location,
+ const TypedTestCasePState* state, const char* case_name,
+ const char* test_names,
+ const std::vector<std::string>& type_names =
+ GenerateNames<DefaultNameGenerator, Types>()) {
+ std::string test_name = StripTrailingSpaces(
+ GetPrefixUntilComma(test_names));
+ if (!state->TestExists(test_name)) {
+ fprintf(stderr, "Failed to get code location for test %s.%s at %s.",
+ case_name, test_name.c_str(),
+ FormatFileLocation(code_location.file.c_str(),
+ code_location.line).c_str());
+ fflush(stderr);
+ posix::Abort();
+ }
+ const CodeLocation& test_location = state->GetCodeLocation(test_name);
+
+ typedef typename Tests::Head Head;
+
+ // First, register the first test in 'Test' for each type in 'Types'.
+ TypeParameterizedTest<Fixture, Head, Types>::Register(
+ prefix, test_location, case_name, test_names, 0, type_names);
+
+ // Next, recurses (at compile time) with the tail of the test list.
+ return TypeParameterizedTestCase<Fixture, typename Tests::Tail,
+ Types>::Register(prefix, code_location,
+ state, case_name,
+ SkipComma(test_names),
+ type_names);
+ }
+};
+
+// The base case for the compile time recursion.
+template <GTEST_TEMPLATE_ Fixture, typename Types>
+class TypeParameterizedTestCase<Fixture, Templates0, Types> {
+ public:
+ static bool Register(const char* /*prefix*/, const CodeLocation&,
+ const TypedTestCasePState* /*state*/,
+ const char* /*case_name*/, const char* /*test_names*/,
+ const std::vector<std::string>& =
+ std::vector<std::string>() /*type_names*/) {
+ return true;
+ }
+};
+
+#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag. The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in
+// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't.
+GTEST_API_ std::string GetCurrentOsStackTraceExceptTop(
+ UnitTest* unit_test, int skip_count);
+
+// Helpers for suppressing warnings on unreachable code or constant
+// condition.
+
+// Always returns true.
+GTEST_API_ bool AlwaysTrue();
+
+// Always returns false.
+inline bool AlwaysFalse() { return !AlwaysTrue(); }
+
+// Helper for suppressing false warning from Clang on a const char*
+// variable declared in a conditional expression always being NULL in
+// the else branch.
+struct GTEST_API_ ConstCharPtr {
+ ConstCharPtr(const char* str) : value(str) {}
+ operator bool() const { return true; }
+ const char* value;
+};
+
+// A simple Linear Congruential Generator for generating random
+// numbers with a uniform distribution. Unlike rand() and srand(), it
+// doesn't use global state (and therefore can't interfere with user
+// code). Unlike rand_r(), it's portable. An LCG isn't very random,
+// but it's good enough for our purposes.
+class GTEST_API_ Random {
+ public:
+ static const UInt32 kMaxRange = 1u << 31;
+
+ explicit Random(UInt32 seed) : state_(seed) {}
+
+ void Reseed(UInt32 seed) { state_ = seed; }
+
+ // Generates a random number from [0, range). Crashes if 'range' is
+ // 0 or greater than kMaxRange.
+ UInt32 Generate(UInt32 range);
+
+ private:
+ UInt32 state_;
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Random);
+};
+
+// Defining a variable of type CompileAssertTypesEqual<T1, T2> will cause a
+// compiler error iff T1 and T2 are different types.
+template <typename T1, typename T2>
+struct CompileAssertTypesEqual;
+
+template <typename T>
+struct CompileAssertTypesEqual<T, T> {
+};
+
+// Removes the reference from a type if it is a reference type,
+// otherwise leaves it unchanged. This is the same as
+// tr1::remove_reference, which is not widely available yet.
+template <typename T>
+struct RemoveReference { typedef T type; }; // NOLINT
+template <typename T>
+struct RemoveReference<T&> { typedef T type; }; // NOLINT
+
+// A handy wrapper around RemoveReference that works when the argument
+// T depends on template parameters.
+#define GTEST_REMOVE_REFERENCE_(T) \
+ typename ::testing::internal::RemoveReference<T>::type
+
+// Removes const from a type if it is a const type, otherwise leaves
+// it unchanged. This is the same as tr1::remove_const, which is not
+// widely available yet.
+template <typename T>
+struct RemoveConst { typedef T type; }; // NOLINT
+template <typename T>
+struct RemoveConst<const T> { typedef T type; }; // NOLINT
+
+// MSVC 8.0, Sun C++, and IBM XL C++ have a bug which causes the above
+// definition to fail to remove the const in 'const int[3]' and 'const
+// char[3][4]'. The following specialization works around the bug.
+template <typename T, size_t N>
+struct RemoveConst<const T[N]> {
+ typedef typename RemoveConst<T>::type type[N];
+};
+
+#if defined(_MSC_VER) && _MSC_VER < 1400
+// This is the only specialization that allows VC++ 7.1 to remove const in
+// 'const int[3] and 'const int[3][4]'. However, it causes trouble with GCC
+// and thus needs to be conditionally compiled.
+template <typename T, size_t N>
+struct RemoveConst<T[N]> {
+ typedef typename RemoveConst<T>::type type[N];
+};
+#endif
+
+// A handy wrapper around RemoveConst that works when the argument
+// T depends on template parameters.
+#define GTEST_REMOVE_CONST_(T) \
+ typename ::testing::internal::RemoveConst<T>::type
+
+// Turns const U&, U&, const U, and U all into U.
+#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \
+ GTEST_REMOVE_CONST_(GTEST_REMOVE_REFERENCE_(T))
+
+// ImplicitlyConvertible<From, To>::value is a compile-time bool
+// constant that's true iff type From can be implicitly converted to
+// type To.
+template <typename From, typename To>
+class ImplicitlyConvertible {
+ private:
+ // We need the following helper functions only for their types.
+ // They have no implementations.
+
+ // MakeFrom() is an expression whose type is From. We cannot simply
+ // use From(), as the type From may not have a public default
+ // constructor.
+ static typename AddReference<From>::type MakeFrom();
+
+ // These two functions are overloaded. Given an expression
+ // Helper(x), the compiler will pick the first version if x can be
+ // implicitly converted to type To; otherwise it will pick the
+ // second version.
+ //
+ // The first version returns a value of size 1, and the second
+ // version returns a value of size 2. Therefore, by checking the
+ // size of Helper(x), which can be done at compile time, we can tell
+ // which version of Helper() is used, and hence whether x can be
+ // implicitly converted to type To.
+ static char Helper(To);
+ static char (&Helper(...))[2]; // NOLINT
+
+ // We have to put the 'public' section after the 'private' section,
+ // or MSVC refuses to compile the code.
+ public:
+#if defined(__BORLANDC__)
+ // C++Builder cannot use member overload resolution during template
+ // instantiation. The simplest workaround is to use its C++0x type traits
+ // functions (C++Builder 2009 and above only).
+ static const bool value = __is_convertible(From, To);
+#else
+ // MSVC warns about implicitly converting from double to int for
+ // possible loss of data, so we need to temporarily disable the
+ // warning.
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4244)
+ static const bool value =
+ sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1;
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif // __BORLANDC__
+};
+template <typename From, typename To>
+const bool ImplicitlyConvertible<From, To>::value;
+
+// IsAProtocolMessage<T>::value is a compile-time bool constant that's
+// true iff T is type ProtocolMessage, proto2::Message, or a subclass
+// of those.
+template <typename T>
+struct IsAProtocolMessage
+ : public bool_constant<
+ ImplicitlyConvertible<const T*, const ::ProtocolMessage*>::value ||
+ ImplicitlyConvertible<const T*, const ::proto2::Message*>::value> {
+};
+
+// When the compiler sees expression IsContainerTest<C>(0), if C is an
+// STL-style container class, the first overload of IsContainerTest
+// will be viable (since both C::iterator* and C::const_iterator* are
+// valid types and NULL can be implicitly converted to them). It will
+// be picked over the second overload as 'int' is a perfect match for
+// the type of argument 0. If C::iterator or C::const_iterator is not
+// a valid type, the first overload is not viable, and the second
+// overload will be picked. Therefore, we can determine whether C is
+// a container class by checking the type of IsContainerTest<C>(0).
+// The value of the expression is insignificant.
+//
+// In C++11 mode we check the existence of a const_iterator and that an
+// iterator is properly implemented for the container.
+//
+// For pre-C++11 that we look for both C::iterator and C::const_iterator.
+// The reason is that C++ injects the name of a class as a member of the
+// class itself (e.g. you can refer to class iterator as either
+// 'iterator' or 'iterator::iterator'). If we look for C::iterator
+// only, for example, we would mistakenly think that a class named
+// iterator is an STL container.
+//
+// Also note that the simpler approach of overloading
+// IsContainerTest(typename C::const_iterator*) and
+// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++.
+typedef int IsContainer;
+#if GTEST_LANG_CXX11
+template <class C,
+ class Iterator = decltype(::std::declval<const C&>().begin()),
+ class = decltype(::std::declval<const C&>().end()),
+ class = decltype(++::std::declval<Iterator&>()),
+ class = decltype(*::std::declval<Iterator>()),
+ class = typename C::const_iterator>
+IsContainer IsContainerTest(int /* dummy */) {
+ return 0;
+}
+#else
+template <class C>
+IsContainer IsContainerTest(int /* dummy */,
+ typename C::iterator* /* it */ = NULL,
+ typename C::const_iterator* /* const_it */ = NULL) {
+ return 0;
+}
+#endif // GTEST_LANG_CXX11
+
+typedef char IsNotContainer;
+template <class C>
+IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; }
+
+// Trait to detect whether a type T is a hash table.
+// The heuristic used is that the type contains an inner type `hasher` and does
+// not contain an inner type `reverse_iterator`.
+// If the container is iterable in reverse, then order might actually matter.
+template <typename T>
+struct IsHashTable {
+ private:
+ template <typename U>
+ static char test(typename U::hasher*, typename U::reverse_iterator*);
+ template <typename U>
+ static int test(typename U::hasher*, ...);
+ template <typename U>
+ static char test(...);
+
+ public:
+ static const bool value = sizeof(test<T>(0, 0)) == sizeof(int);
+};
+
+template <typename T>
+const bool IsHashTable<T>::value;
+
+template<typename T>
+struct VoidT {
+ typedef void value_type;
+};
+
+template <typename T, typename = void>
+struct HasValueType : false_type {};
+template <typename T>
+struct HasValueType<T, VoidT<typename T::value_type> > : true_type {
+};
+
+template <typename C,
+ bool = sizeof(IsContainerTest<C>(0)) == sizeof(IsContainer),
+ bool = HasValueType<C>::value>
+struct IsRecursiveContainerImpl;
+
+template <typename C, bool HV>
+struct IsRecursiveContainerImpl<C, false, HV> : public false_type {};
+
+// Since the IsRecursiveContainerImpl depends on the IsContainerTest we need to
+// obey the same inconsistencies as the IsContainerTest, namely check if
+// something is a container is relying on only const_iterator in C++11 and
+// is relying on both const_iterator and iterator otherwise
+template <typename C>
+struct IsRecursiveContainerImpl<C, true, false> : public false_type {};
+
+template <typename C>
+struct IsRecursiveContainerImpl<C, true, true> {
+ #if GTEST_LANG_CXX11
+ typedef typename IteratorTraits<typename C::const_iterator>::value_type
+ value_type;
+#else
+ typedef typename IteratorTraits<typename C::iterator>::value_type value_type;
+#endif
+ typedef is_same<value_type, C> type;
+};
+
+// IsRecursiveContainer<Type> is a unary compile-time predicate that
+// evaluates whether C is a recursive container type. A recursive container
+// type is a container type whose value_type is equal to the container type
+// itself. An example for a recursive container type is
+// boost::filesystem::path, whose iterator has a value_type that is equal to
+// boost::filesystem::path.
+template <typename C>
+struct IsRecursiveContainer : public IsRecursiveContainerImpl<C>::type {};
+
+// EnableIf<condition>::type is void when 'Cond' is true, and
+// undefined when 'Cond' is false. To use SFINAE to make a function
+// overload only apply when a particular expression is true, add
+// "typename EnableIf<expression>::type* = 0" as the last parameter.
+template<bool> struct EnableIf;
+template<> struct EnableIf<true> { typedef void type; }; // NOLINT
+
+// Utilities for native arrays.
+
+// ArrayEq() compares two k-dimensional native arrays using the
+// elements' operator==, where k can be any integer >= 0. When k is
+// 0, ArrayEq() degenerates into comparing a single pair of values.
+
+template <typename T, typename U>
+bool ArrayEq(const T* lhs, size_t size, const U* rhs);
+
+// This generic version is used when k is 0.
+template <typename T, typename U>
+inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; }
+
+// This overload is used when k >= 1.
+template <typename T, typename U, size_t N>
+inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) {
+ return internal::ArrayEq(lhs, N, rhs);
+}
+
+// This helper reduces code bloat. If we instead put its logic inside
+// the previous ArrayEq() function, arrays with different sizes would
+// lead to different copies of the template code.
+template <typename T, typename U>
+bool ArrayEq(const T* lhs, size_t size, const U* rhs) {
+ for (size_t i = 0; i != size; i++) {
+ if (!internal::ArrayEq(lhs[i], rhs[i]))
+ return false;
+ }
+ return true;
+}
+
+// Finds the first element in the iterator range [begin, end) that
+// equals elem. Element may be a native array type itself.
+template <typename Iter, typename Element>
+Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) {
+ for (Iter it = begin; it != end; ++it) {
+ if (internal::ArrayEq(*it, elem))
+ return it;
+ }
+ return end;
+}
+
+// CopyArray() copies a k-dimensional native array using the elements'
+// operator=, where k can be any integer >= 0. When k is 0,
+// CopyArray() degenerates into copying a single value.
+
+template <typename T, typename U>
+void CopyArray(const T* from, size_t size, U* to);
+
+// This generic version is used when k is 0.
+template <typename T, typename U>
+inline void CopyArray(const T& from, U* to) { *to = from; }
+
+// This overload is used when k >= 1.
+template <typename T, typename U, size_t N>
+inline void CopyArray(const T(&from)[N], U(*to)[N]) {
+ internal::CopyArray(from, N, *to);
+}
+
+// This helper reduces code bloat. If we instead put its logic inside
+// the previous CopyArray() function, arrays with different sizes
+// would lead to different copies of the template code.
+template <typename T, typename U>
+void CopyArray(const T* from, size_t size, U* to) {
+ for (size_t i = 0; i != size; i++) {
+ internal::CopyArray(from[i], to + i);
+ }
+}
+
+// The relation between an NativeArray object (see below) and the
+// native array it represents.
+// We use 2 different structs to allow non-copyable types to be used, as long
+// as RelationToSourceReference() is passed.
+struct RelationToSourceReference {};
+struct RelationToSourceCopy {};
+
+// Adapts a native array to a read-only STL-style container. Instead
+// of the complete STL container concept, this adaptor only implements
+// members useful for Google Mock's container matchers. New members
+// should be added as needed. To simplify the implementation, we only
+// support Element being a raw type (i.e. having no top-level const or
+// reference modifier). It's the client's responsibility to satisfy
+// this requirement. Element can be an array type itself (hence
+// multi-dimensional arrays are supported).
+template <typename Element>
+class NativeArray {
+ public:
+ // STL-style container typedefs.
+ typedef Element value_type;
+ typedef Element* iterator;
+ typedef const Element* const_iterator;
+
+ // Constructs from a native array. References the source.
+ NativeArray(const Element* array, size_t count, RelationToSourceReference) {
+ InitRef(array, count);
+ }
+
+ // Constructs from a native array. Copies the source.
+ NativeArray(const Element* array, size_t count, RelationToSourceCopy) {
+ InitCopy(array, count);
+ }
+
+ // Copy constructor.
+ NativeArray(const NativeArray& rhs) {
+ (this->*rhs.clone_)(rhs.array_, rhs.size_);
+ }
+
+ ~NativeArray() {
+ if (clone_ != &NativeArray::InitRef)
+ delete[] array_;
+ }
+
+ // STL-style container methods.
+ size_t size() const { return size_; }
+ const_iterator begin() const { return array_; }
+ const_iterator end() const { return array_ + size_; }
+ bool operator==(const NativeArray& rhs) const {
+ return size() == rhs.size() &&
+ ArrayEq(begin(), size(), rhs.begin());
+ }
+
+ private:
+ enum {
+ kCheckTypeIsNotConstOrAReference = StaticAssertTypeEqHelper<
+ Element, GTEST_REMOVE_REFERENCE_AND_CONST_(Element)>::value
+ };
+
+ // Initializes this object with a copy of the input.
+ void InitCopy(const Element* array, size_t a_size) {
+ Element* const copy = new Element[a_size];
+ CopyArray(array, a_size, copy);
+ array_ = copy;
+ size_ = a_size;
+ clone_ = &NativeArray::InitCopy;
+ }
+
+ // Initializes this object with a reference of the input.
+ void InitRef(const Element* array, size_t a_size) {
+ array_ = array;
+ size_ = a_size;
+ clone_ = &NativeArray::InitRef;
+ }
+
+ const Element* array_;
+ size_t size_;
+ void (NativeArray::*clone_)(const Element*, size_t);
+
+ GTEST_DISALLOW_ASSIGN_(NativeArray);
+};
+
+} // namespace internal
+} // namespace testing
+
+#define GTEST_MESSAGE_AT_(file, line, message, result_type) \
+ ::testing::internal::AssertHelper(result_type, file, line, message) \
+ = ::testing::Message()
+
+#define GTEST_MESSAGE_(message, result_type) \
+ GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type)
+
+#define GTEST_FATAL_FAILURE_(message) \
+ return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)
+
+#define GTEST_NONFATAL_FAILURE_(message) \
+ GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)
+
+#define GTEST_SUCCESS_(message) \
+ GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
+
+// Suppress MSVC warning 4702 (unreachable code) for the code following
+// statement if it returns or throws (or doesn't return or throw in some
+// situations).
+#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \
+ if (::testing::internal::AlwaysTrue()) { statement; }
+
+#define GTEST_TEST_THROW_(statement, expected_exception, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::ConstCharPtr gtest_msg = "") { \
+ bool gtest_caught_expected = false; \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } \
+ catch (expected_exception const&) { \
+ gtest_caught_expected = true; \
+ } \
+ catch (...) { \
+ gtest_msg.value = \
+ "Expected: " #statement " throws an exception of type " \
+ #expected_exception ".\n Actual: it throws a different type."; \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+ } \
+ if (!gtest_caught_expected) { \
+ gtest_msg.value = \
+ "Expected: " #statement " throws an exception of type " \
+ #expected_exception ".\n Actual: it throws nothing."; \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \
+ fail(gtest_msg.value)
+
+#define GTEST_TEST_NO_THROW_(statement, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } \
+ catch (...) { \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \
+ fail("Expected: " #statement " doesn't throw an exception.\n" \
+ " Actual: it throws.")
+
+#define GTEST_TEST_ANY_THROW_(statement, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ bool gtest_caught_any = false; \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } \
+ catch (...) { \
+ gtest_caught_any = true; \
+ } \
+ if (!gtest_caught_any) { \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \
+ fail("Expected: " #statement " throws an exception.\n" \
+ " Actual: it doesn't.")
+
+
+// Implements Boolean test assertions such as EXPECT_TRUE. expression can be
+// either a boolean expression or an AssertionResult. text is a textual
+// represenation of expression as it was passed into the EXPECT_TRUE.
+#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (const ::testing::AssertionResult gtest_ar_ = \
+ ::testing::AssertionResult(expression)) \
+ ; \
+ else \
+ fail(::testing::internal::GetBoolAssertionFailureMessage(\
+ gtest_ar_, text, #actual, #expected).c_str())
+
+#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \
+ fail("Expected: " #statement " doesn't generate new fatal " \
+ "failures in the current thread.\n" \
+ " Actual: it does.")
+
+// Expands to the name of the class that implements the given test.
+#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
+ test_case_name##_##test_name##_Test
+
+// Helper macro for defining tests.
+#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
+class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
+ public:\
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
+ private:\
+ virtual void TestBody();\
+ static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(\
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
+};\
+\
+::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
+ ::test_info_ =\
+ ::testing::internal::MakeAndRegisterTestInfo(\
+ #test_case_name, #test_name, NULL, NULL, \
+ ::testing::internal::CodeLocation(__FILE__, __LINE__), \
+ (parent_id), \
+ parent_class::SetUpTestCase, \
+ parent_class::TearDownTestCase, \
+ new ::testing::internal::TestFactoryImpl<\
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
+void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the public API for death tests. It is
+// #included by gtest.h so a user doesn't need to include this
+// directly.
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines internal utilities needed for implementing
+// death tests. They are subject to change without notice.
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+
+
+#include <stdio.h>
+
+namespace testing {
+namespace internal {
+
+GTEST_DECLARE_string_(internal_run_death_test);
+
+// Names of the flags (needed for parsing Google Test flags).
+const char kDeathTestStyleFlag[] = "death_test_style";
+const char kDeathTestUseFork[] = "death_test_use_fork";
+const char kInternalRunDeathTestFlag[] = "internal_run_death_test";
+
+#if GTEST_HAS_DEATH_TEST
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// DeathTest is a class that hides much of the complexity of the
+// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method
+// returns a concrete class that depends on the prevailing death test
+// style, as defined by the --gtest_death_test_style and/or
+// --gtest_internal_run_death_test flags.
+
+// In describing the results of death tests, these terms are used with
+// the corresponding definitions:
+//
+// exit status: The integer exit information in the format specified
+// by wait(2)
+// exit code: The integer code passed to exit(3), _exit(2), or
+// returned from main()
+class GTEST_API_ DeathTest {
+ public:
+ // Create returns false if there was an error determining the
+ // appropriate action to take for the current death test; for example,
+ // if the gtest_death_test_style flag is set to an invalid value.
+ // The LastMessage method will return a more detailed message in that
+ // case. Otherwise, the DeathTest pointer pointed to by the "test"
+ // argument is set. If the death test should be skipped, the pointer
+ // is set to NULL; otherwise, it is set to the address of a new concrete
+ // DeathTest object that controls the execution of the current test.
+ static bool Create(const char* statement, const RE* regex,
+ const char* file, int line, DeathTest** test);
+ DeathTest();
+ virtual ~DeathTest() { }
+
+ // A helper class that aborts a death test when it's deleted.
+ class ReturnSentinel {
+ public:
+ explicit ReturnSentinel(DeathTest* test) : test_(test) { }
+ ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); }
+ private:
+ DeathTest* const test_;
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel);
+ } GTEST_ATTRIBUTE_UNUSED_;
+
+ // An enumeration of possible roles that may be taken when a death
+ // test is encountered. EXECUTE means that the death test logic should
+ // be executed immediately. OVERSEE means that the program should prepare
+ // the appropriate environment for a child process to execute the death
+ // test, then wait for it to complete.
+ enum TestRole { OVERSEE_TEST, EXECUTE_TEST };
+
+ // An enumeration of the three reasons that a test might be aborted.
+ enum AbortReason {
+ TEST_ENCOUNTERED_RETURN_STATEMENT,
+ TEST_THREW_EXCEPTION,
+ TEST_DID_NOT_DIE
+ };
+
+ // Assumes one of the above roles.
+ virtual TestRole AssumeRole() = 0;
+
+ // Waits for the death test to finish and returns its status.
+ virtual int Wait() = 0;
+
+ // Returns true if the death test passed; that is, the test process
+ // exited during the test, its exit status matches a user-supplied
+ // predicate, and its stderr output matches a user-supplied regular
+ // expression.
+ // The user-supplied predicate may be a macro expression rather
+ // than a function pointer or functor, or else Wait and Passed could
+ // be combined.
+ virtual bool Passed(bool exit_status_ok) = 0;
+
+ // Signals that the death test did not die as expected.
+ virtual void Abort(AbortReason reason) = 0;
+
+ // Returns a human-readable outcome message regarding the outcome of
+ // the last death test.
+ static const char* LastMessage();
+
+ static void set_last_death_test_message(const std::string& message);
+
+ private:
+ // A string containing a description of the outcome of the last death test.
+ static std::string last_death_test_message_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest);
+};
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+// Factory interface for death tests. May be mocked out for testing.
+class DeathTestFactory {
+ public:
+ virtual ~DeathTestFactory() { }
+ virtual bool Create(const char* statement, const RE* regex,
+ const char* file, int line, DeathTest** test) = 0;
+};
+
+// A concrete DeathTestFactory implementation for normal use.
+class DefaultDeathTestFactory : public DeathTestFactory {
+ public:
+ virtual bool Create(const char* statement, const RE* regex,
+ const char* file, int line, DeathTest** test);
+};
+
+// Returns true if exit_status describes a process that was terminated
+// by a signal, or exited normally with a nonzero exit code.
+GTEST_API_ bool ExitedUnsuccessfully(int exit_status);
+
+// Traps C++ exceptions escaping statement and reports them as test
+// failures. Note that trapping SEH exceptions is not implemented here.
+# if GTEST_HAS_EXCEPTIONS
+# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } catch (const ::std::exception& gtest_exception) { \
+ fprintf(\
+ stderr, \
+ "\n%s: Caught std::exception-derived exception escaping the " \
+ "death test statement. Exception message: %s\n", \
+ ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \
+ gtest_exception.what()); \
+ fflush(stderr); \
+ death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
+ } catch (...) { \
+ death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
+ }
+
+# else
+# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
+
+# endif
+
+// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*,
+// ASSERT_EXIT*, and EXPECT_EXIT*.
+# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ const ::testing::internal::RE& gtest_regex = (regex); \
+ ::testing::internal::DeathTest* gtest_dt; \
+ if (!::testing::internal::DeathTest::Create(#statement, &gtest_regex, \
+ __FILE__, __LINE__, &gtest_dt)) { \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \
+ } \
+ if (gtest_dt != NULL) { \
+ ::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \
+ gtest_dt_ptr(gtest_dt); \
+ switch (gtest_dt->AssumeRole()) { \
+ case ::testing::internal::DeathTest::OVERSEE_TEST: \
+ if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \
+ } \
+ break; \
+ case ::testing::internal::DeathTest::EXECUTE_TEST: { \
+ ::testing::internal::DeathTest::ReturnSentinel \
+ gtest_sentinel(gtest_dt); \
+ GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \
+ gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \
+ break; \
+ } \
+ default: \
+ break; \
+ } \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \
+ fail(::testing::internal::DeathTest::LastMessage())
+// The symbol "fail" here expands to something into which a message
+// can be streamed.
+
+// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in
+// NDEBUG mode. In this case we need the statements to be executed and the macro
+// must accept a streamed message even though the message is never printed.
+// The regex object is not evaluated, but it is used to prevent "unused"
+// warnings and to avoid an expression that doesn't compile in debug mode.
+#define GTEST_EXECUTE_STATEMENT_(statement, regex) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } else if (!::testing::internal::AlwaysTrue()) { \
+ const ::testing::internal::RE& gtest_regex = (regex); \
+ static_cast<void>(gtest_regex); \
+ } else \
+ ::testing::Message()
+
+// A class representing the parsed contents of the
+// --gtest_internal_run_death_test flag, as it existed when
+// RUN_ALL_TESTS was called.
+class InternalRunDeathTestFlag {
+ public:
+ InternalRunDeathTestFlag(const std::string& a_file,
+ int a_line,
+ int an_index,
+ int a_write_fd)
+ : file_(a_file), line_(a_line), index_(an_index),
+ write_fd_(a_write_fd) {}
+
+ ~InternalRunDeathTestFlag() {
+ if (write_fd_ >= 0)
+ posix::Close(write_fd_);
+ }
+
+ const std::string& file() const { return file_; }
+ int line() const { return line_; }
+ int index() const { return index_; }
+ int write_fd() const { return write_fd_; }
+
+ private:
+ std::string file_;
+ int line_;
+ int index_;
+ int write_fd_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag);
+};
+
+// Returns a newly created InternalRunDeathTestFlag object with fields
+// initialized from the GTEST_FLAG(internal_run_death_test) flag if
+// the flag is specified; otherwise returns NULL.
+InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag();
+
+#endif // GTEST_HAS_DEATH_TEST
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+
+namespace testing {
+
+// This flag controls the style of death tests. Valid values are "threadsafe",
+// meaning that the death test child process will re-execute the test binary
+// from the start, running only a single death test, or "fast",
+// meaning that the child process will execute the test logic immediately
+// after forking.
+GTEST_DECLARE_string_(death_test_style);
+
+#if GTEST_HAS_DEATH_TEST
+
+namespace internal {
+
+// Returns a Boolean value indicating whether the caller is currently
+// executing in the context of the death test child process. Tools such as
+// Valgrind heap checkers may need this to modify their behavior in death
+// tests. IMPORTANT: This is an internal utility. Using it may break the
+// implementation of death tests. User code MUST NOT use it.
+GTEST_API_ bool InDeathTestChild();
+
+} // namespace internal
+
+// The following macros are useful for writing death tests.
+
+// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is
+// executed:
+//
+// 1. It generates a warning if there is more than one active
+// thread. This is because it's safe to fork() or clone() only
+// when there is a single thread.
+//
+// 2. The parent process clone()s a sub-process and runs the death
+// test in it; the sub-process exits with code 0 at the end of the
+// death test, if it hasn't exited already.
+//
+// 3. The parent process waits for the sub-process to terminate.
+//
+// 4. The parent process checks the exit code and error message of
+// the sub-process.
+//
+// Examples:
+//
+// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number");
+// for (int i = 0; i < 5; i++) {
+// EXPECT_DEATH(server.ProcessRequest(i),
+// "Invalid request .* in ProcessRequest()")
+// << "Failed to die on request " << i;
+// }
+//
+// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting");
+//
+// bool KilledBySIGHUP(int exit_code) {
+// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP;
+// }
+//
+// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!");
+//
+// On the regular expressions used in death tests:
+//
+// GOOGLETEST_CM0005 DO NOT DELETE
+// On POSIX-compliant systems (*nix), we use the <regex.h> library,
+// which uses the POSIX extended regex syntax.
+//
+// On other platforms (e.g. Windows or Mac), we only support a simple regex
+// syntax implemented as part of Google Test. This limited
+// implementation should be enough most of the time when writing
+// death tests; though it lacks many features you can find in PCRE
+// or POSIX extended regex syntax. For example, we don't support
+// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
+// repetition count ("x{5,7}"), among others.
+//
+// Below is the syntax that we do support. We chose it to be a
+// subset of both PCRE and POSIX extended regex, so it's easy to
+// learn wherever you come from. In the following: 'A' denotes a
+// literal character, period (.), or a single \\ escape sequence;
+// 'x' and 'y' denote regular expressions; 'm' and 'n' are for
+// natural numbers.
+//
+// c matches any literal character c
+// \\d matches any decimal digit
+// \\D matches any character that's not a decimal digit
+// \\f matches \f
+// \\n matches \n
+// \\r matches \r
+// \\s matches any ASCII whitespace, including \n
+// \\S matches any character that's not a whitespace
+// \\t matches \t
+// \\v matches \v
+// \\w matches any letter, _, or decimal digit
+// \\W matches any character that \\w doesn't match
+// \\c matches any literal character c, which must be a punctuation
+// . matches any single character except \n
+// A? matches 0 or 1 occurrences of A
+// A* matches 0 or many occurrences of A
+// A+ matches 1 or many occurrences of A
+// ^ matches the beginning of a string (not that of each line)
+// $ matches the end of a string (not that of each line)
+// xy matches x followed by y
+//
+// If you accidentally use PCRE or POSIX extended regex features
+// not implemented by us, you will get a run-time failure. In that
+// case, please try to rewrite your regular expression within the
+// above syntax.
+//
+// This implementation is *not* meant to be as highly tuned or robust
+// as a compiled regex library, but should perform well enough for a
+// death test, which already incurs significant overhead by launching
+// a child process.
+//
+// Known caveats:
+//
+// A "threadsafe" style death test obtains the path to the test
+// program from argv[0] and re-executes it in the sub-process. For
+// simplicity, the current implementation doesn't search the PATH
+// when launching the sub-process. This means that the user must
+// invoke the test program via a path that contains at least one
+// path separator (e.g. path/to/foo_test and
+// /absolute/path/to/bar_test are fine, but foo_test is not). This
+// is rarely a problem as people usually don't put the test binary
+// directory in PATH.
+//
+// FIXME: make thread-safe death tests search the PATH.
+
+// Asserts that a given statement causes the program to exit, with an
+// integer exit status that satisfies predicate, and emitting error output
+// that matches regex.
+# define ASSERT_EXIT(statement, predicate, regex) \
+ GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_)
+
+// Like ASSERT_EXIT, but continues on to successive tests in the
+// test case, if any:
+# define EXPECT_EXIT(statement, predicate, regex) \
+ GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_)
+
+// Asserts that a given statement causes the program to exit, either by
+// explicitly exiting with a nonzero exit code or being killed by a
+// signal, and emitting error output that matches regex.
+# define ASSERT_DEATH(statement, regex) \
+ ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex)
+
+// Like ASSERT_DEATH, but continues on to successive tests in the
+// test case, if any:
+# define EXPECT_DEATH(statement, regex) \
+ EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex)
+
+// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*:
+
+// Tests that an exit code describes a normal exit with a given exit code.
+class GTEST_API_ ExitedWithCode {
+ public:
+ explicit ExitedWithCode(int exit_code);
+ bool operator()(int exit_status) const;
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ExitedWithCode& other);
+
+ const int exit_code_;
+};
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// Tests that an exit code describes an exit due to termination by a
+// given signal.
+// GOOGLETEST_CM0006 DO NOT DELETE
+class GTEST_API_ KilledBySignal {
+ public:
+ explicit KilledBySignal(int signum);
+ bool operator()(int exit_status) const;
+ private:
+ const int signum_;
+};
+# endif // !GTEST_OS_WINDOWS
+
+// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode.
+// The death testing framework causes this to have interesting semantics,
+// since the sideeffects of the call are only visible in opt mode, and not
+// in debug mode.
+//
+// In practice, this can be used to test functions that utilize the
+// LOG(DFATAL) macro using the following style:
+//
+// int DieInDebugOr12(int* sideeffect) {
+// if (sideeffect) {
+// *sideeffect = 12;
+// }
+// LOG(DFATAL) << "death";
+// return 12;
+// }
+//
+// TEST(TestCase, TestDieOr12WorksInDgbAndOpt) {
+// int sideeffect = 0;
+// // Only asserts in dbg.
+// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death");
+//
+// #ifdef NDEBUG
+// // opt-mode has sideeffect visible.
+// EXPECT_EQ(12, sideeffect);
+// #else
+// // dbg-mode no visible sideeffect.
+// EXPECT_EQ(0, sideeffect);
+// #endif
+// }
+//
+// This will assert that DieInDebugReturn12InOpt() crashes in debug
+// mode, usually due to a DCHECK or LOG(DFATAL), but returns the
+// appropriate fallback value (12 in this case) in opt mode. If you
+// need to test that a function has appropriate side-effects in opt
+// mode, include assertions against the side-effects. A general
+// pattern for this is:
+//
+// EXPECT_DEBUG_DEATH({
+// // Side-effects here will have an effect after this statement in
+// // opt mode, but none in debug mode.
+// EXPECT_EQ(12, DieInDebugOr12(&sideeffect));
+// }, "death");
+//
+# ifdef NDEBUG
+
+# define EXPECT_DEBUG_DEATH(statement, regex) \
+ GTEST_EXECUTE_STATEMENT_(statement, regex)
+
+# define ASSERT_DEBUG_DEATH(statement, regex) \
+ GTEST_EXECUTE_STATEMENT_(statement, regex)
+
+# else
+
+# define EXPECT_DEBUG_DEATH(statement, regex) \
+ EXPECT_DEATH(statement, regex)
+
+# define ASSERT_DEBUG_DEATH(statement, regex) \
+ ASSERT_DEATH(statement, regex)
+
+# endif // NDEBUG for EXPECT_DEBUG_DEATH
+#endif // GTEST_HAS_DEATH_TEST
+
+// This macro is used for implementing macros such as
+// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where
+// death tests are not supported. Those macros must compile on such systems
+// iff EXPECT_DEATH and ASSERT_DEATH compile with the same parameters on
+// systems that support death tests. This allows one to write such a macro
+// on a system that does not support death tests and be sure that it will
+// compile on a death-test supporting system. It is exposed publicly so that
+// systems that have death-tests with stricter requirements than
+// GTEST_HAS_DEATH_TEST can write their own equivalent of
+// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED.
+//
+// Parameters:
+// statement - A statement that a macro such as EXPECT_DEATH would test
+// for program termination. This macro has to make sure this
+// statement is compiled but not executed, to ensure that
+// EXPECT_DEATH_IF_SUPPORTED compiles with a certain
+// parameter iff EXPECT_DEATH compiles with it.
+// regex - A regex that a macro such as EXPECT_DEATH would use to test
+// the output of statement. This parameter has to be
+// compiled but not evaluated by this macro, to ensure that
+// this macro only accepts expressions that a macro such as
+// EXPECT_DEATH would accept.
+// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED
+// and a return statement for ASSERT_DEATH_IF_SUPPORTED.
+// This ensures that ASSERT_DEATH_IF_SUPPORTED will not
+// compile inside functions where ASSERT_DEATH doesn't
+// compile.
+//
+// The branch that has an always false condition is used to ensure that
+// statement and regex are compiled (and thus syntactically correct) but
+// never executed. The unreachable code macro protects the terminator
+// statement from generating an 'unreachable code' warning in case
+// statement unconditionally returns or throws. The Message constructor at
+// the end allows the syntax of streaming additional messages into the
+// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH.
+# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::testing::internal::AlwaysTrue()) { \
+ GTEST_LOG_(WARNING) \
+ << "Death tests are not supported on this platform.\n" \
+ << "Statement '" #statement "' cannot be verified."; \
+ } else if (::testing::internal::AlwaysFalse()) { \
+ ::testing::internal::RE::PartialMatch(".*", (regex)); \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ terminator; \
+ } else \
+ ::testing::Message()
+
+// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and
+// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if
+// death tests are supported; otherwise they just issue a warning. This is
+// useful when you are combining death test assertions with normal test
+// assertions in one test.
+#if GTEST_HAS_DEATH_TEST
+# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
+ EXPECT_DEATH(statement, regex)
+# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
+ ASSERT_DEATH(statement, regex)
+#else
+# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
+ GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, )
+# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
+ GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return)
+#endif
+
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+// This file was GENERATED by command:
+// pump.py gtest-param-test.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Macros and functions for implementing parameterized tests
+// in Google C++ Testing and Mocking Framework (Google Test)
+//
+// This file is generated by a SCRIPT. DO NOT EDIT BY HAND!
+//
+// GOOGLETEST_CM0001 DO NOT DELETE
+#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+
+
+// Value-parameterized tests allow you to test your code with different
+// parameters without writing multiple copies of the same test.
+//
+// Here is how you use value-parameterized tests:
+
+#if 0
+
+// To write value-parameterized tests, first you should define a fixture
+// class. It is usually derived from testing::TestWithParam<T> (see below for
+// another inheritance scheme that's sometimes useful in more complicated
+// class hierarchies), where the type of your parameter values.
+// TestWithParam<T> is itself derived from testing::Test. T can be any
+// copyable type. If it's a raw pointer, you are responsible for managing the
+// lifespan of the pointed values.
+
+class FooTest : public ::testing::TestWithParam<const char*> {
+ // You can implement all the usual class fixture members here.
+};
+
+// Then, use the TEST_P macro to define as many parameterized tests
+// for this fixture as you want. The _P suffix is for "parameterized"
+// or "pattern", whichever you prefer to think.
+
+TEST_P(FooTest, DoesBlah) {
+ // Inside a test, access the test parameter with the GetParam() method
+ // of the TestWithParam<T> class:
+ EXPECT_TRUE(foo.Blah(GetParam()));
+ ...
+}
+
+TEST_P(FooTest, HasBlahBlah) {
+ ...
+}
+
+// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test
+// case with any set of parameters you want. Google Test defines a number
+// of functions for generating test parameters. They return what we call
+// (surprise!) parameter generators. Here is a summary of them, which
+// are all in the testing namespace:
+//
+//
+// Range(begin, end [, step]) - Yields values {begin, begin+step,
+// begin+step+step, ...}. The values do not
+// include end. step defaults to 1.
+// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}.
+// ValuesIn(container) - Yields values from a C-style array, an STL
+// ValuesIn(begin,end) container, or an iterator range [begin, end).
+// Bool() - Yields sequence {false, true}.
+// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product
+// for the math savvy) of the values generated
+// by the N generators.
+//
+// For more details, see comments at the definitions of these functions below
+// in this file.
+//
+// The following statement will instantiate tests from the FooTest test case
+// each with parameter values "meeny", "miny", and "moe".
+
+INSTANTIATE_TEST_CASE_P(InstantiationName,
+ FooTest,
+ Values("meeny", "miny", "moe"));
+
+// To distinguish different instances of the pattern, (yes, you
+// can instantiate it more then once) the first argument to the
+// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the
+// actual test case name. Remember to pick unique prefixes for different
+// instantiations. The tests from the instantiation above will have
+// these names:
+//
+// * InstantiationName/FooTest.DoesBlah/0 for "meeny"
+// * InstantiationName/FooTest.DoesBlah/1 for "miny"
+// * InstantiationName/FooTest.DoesBlah/2 for "moe"
+// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny"
+// * InstantiationName/FooTest.HasBlahBlah/1 for "miny"
+// * InstantiationName/FooTest.HasBlahBlah/2 for "moe"
+//
+// You can use these names in --gtest_filter.
+//
+// This statement will instantiate all tests from FooTest again, each
+// with parameter values "cat" and "dog":
+
+const char* pets[] = {"cat", "dog"};
+INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets));
+
+// The tests from the instantiation above will have these names:
+//
+// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat"
+// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog"
+// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat"
+// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog"
+//
+// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests
+// in the given test case, whether their definitions come before or
+// AFTER the INSTANTIATE_TEST_CASE_P statement.
+//
+// Please also note that generator expressions (including parameters to the
+// generators) are evaluated in InitGoogleTest(), after main() has started.
+// This allows the user on one hand, to adjust generator parameters in order
+// to dynamically determine a set of tests to run and on the other hand,
+// give the user a chance to inspect the generated tests with Google Test
+// reflection API before RUN_ALL_TESTS() is executed.
+//
+// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc
+// for more examples.
+//
+// In the future, we plan to publish the API for defining new parameter
+// generators. But for now this interface remains part of the internal
+// implementation and is subject to change.
+//
+//
+// A parameterized test fixture must be derived from testing::Test and from
+// testing::WithParamInterface<T>, where T is the type of the parameter
+// values. Inheriting from TestWithParam<T> satisfies that requirement because
+// TestWithParam<T> inherits from both Test and WithParamInterface. In more
+// complicated hierarchies, however, it is occasionally useful to inherit
+// separately from Test and WithParamInterface. For example:
+
+class BaseTest : public ::testing::Test {
+ // You can inherit all the usual members for a non-parameterized test
+ // fixture here.
+};
+
+class DerivedTest : public BaseTest, public ::testing::WithParamInterface<int> {
+ // The usual test fixture members go here too.
+};
+
+TEST_F(BaseTest, HasFoo) {
+ // This is an ordinary non-parameterized test.
+}
+
+TEST_P(DerivedTest, DoesBlah) {
+ // GetParam works just the same here as if you inherit from TestWithParam.
+ EXPECT_TRUE(foo.Blah(GetParam()));
+}
+
+#endif // 0
+
+
+#if !GTEST_OS_SYMBIAN
+# include <utility>
+#endif
+
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Type and function utilities for implementing parameterized tests.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+
+#include <ctype.h>
+
+#include <iterator>
+#include <set>
+#include <utility>
+#include <vector>
+
+// Copyright 2003 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// A "smart" pointer type with reference tracking. Every pointer to a
+// particular object is kept on a circular linked list. When the last pointer
+// to an object is destroyed or reassigned, the object is deleted.
+//
+// Used properly, this deletes the object when the last reference goes away.
+// There are several caveats:
+// - Like all reference counting schemes, cycles lead to leaks.
+// - Each smart pointer is actually two pointers (8 bytes instead of 4).
+// - Every time a pointer is assigned, the entire list of pointers to that
+// object is traversed. This class is therefore NOT SUITABLE when there
+// will often be more than two or three pointers to a particular object.
+// - References are only tracked as long as linked_ptr<> objects are copied.
+// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS
+// will happen (double deletion).
+//
+// A good use of this class is storing object references in STL containers.
+// You can safely put linked_ptr<> in a vector<>.
+// Other uses may not be as good.
+//
+// Note: If you use an incomplete type with linked_ptr<>, the class
+// *containing* linked_ptr<> must have a constructor and destructor (even
+// if they do nothing!).
+//
+// Bill Gibbons suggested we use something like this.
+//
+// Thread Safety:
+// Unlike other linked_ptr implementations, in this implementation
+// a linked_ptr object is thread-safe in the sense that:
+// - it's safe to copy linked_ptr objects concurrently,
+// - it's safe to copy *from* a linked_ptr and read its underlying
+// raw pointer (e.g. via get()) concurrently, and
+// - it's safe to write to two linked_ptrs that point to the same
+// shared object concurrently.
+// FIXME: rename this to safe_linked_ptr to avoid
+// confusion with normal linked_ptr.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_
+
+#include <stdlib.h>
+#include <assert.h>
+
+
+namespace testing {
+namespace internal {
+
+// Protects copying of all linked_ptr objects.
+GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex);
+
+// This is used internally by all instances of linked_ptr<>. It needs to be
+// a non-template class because different types of linked_ptr<> can refer to
+// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)).
+// So, it needs to be possible for different types of linked_ptr to participate
+// in the same circular linked list, so we need a single class type here.
+//
+// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>.
+class linked_ptr_internal {
+ public:
+ // Create a new circle that includes only this instance.
+ void join_new() {
+ next_ = this;
+ }
+
+ // Many linked_ptr operations may change p.link_ for some linked_ptr
+ // variable p in the same circle as this object. Therefore we need
+ // to prevent two such operations from occurring concurrently.
+ //
+ // Note that different types of linked_ptr objects can coexist in a
+ // circle (e.g. linked_ptr<Base>, linked_ptr<Derived1>, and
+ // linked_ptr<Derived2>). Therefore we must use a single mutex to
+ // protect all linked_ptr objects. This can create serious
+ // contention in production code, but is acceptable in a testing
+ // framework.
+
+ // Join an existing circle.
+ void join(linked_ptr_internal const* ptr)
+ GTEST_LOCK_EXCLUDED_(g_linked_ptr_mutex) {
+ MutexLock lock(&g_linked_ptr_mutex);
+
+ linked_ptr_internal const* p = ptr;
+ while (p->next_ != ptr) {
+ assert(p->next_ != this &&
+ "Trying to join() a linked ring we are already in. "
+ "Is GMock thread safety enabled?");
+ p = p->next_;
+ }
+ p->next_ = this;
+ next_ = ptr;
+ }
+
+ // Leave whatever circle we're part of. Returns true if we were the
+ // last member of the circle. Once this is done, you can join() another.
+ bool depart()
+ GTEST_LOCK_EXCLUDED_(g_linked_ptr_mutex) {
+ MutexLock lock(&g_linked_ptr_mutex);
+
+ if (next_ == this) return true;
+ linked_ptr_internal const* p = next_;
+ while (p->next_ != this) {
+ assert(p->next_ != next_ &&
+ "Trying to depart() a linked ring we are not in. "
+ "Is GMock thread safety enabled?");
+ p = p->next_;
+ }
+ p->next_ = next_;
+ return false;
+ }
+
+ private:
+ mutable linked_ptr_internal const* next_;
+};
+
+template <typename T>
+class linked_ptr {
+ public:
+ typedef T element_type;
+
+ // Take over ownership of a raw pointer. This should happen as soon as
+ // possible after the object is created.
+ explicit linked_ptr(T* ptr = NULL) { capture(ptr); }
+ ~linked_ptr() { depart(); }
+
+ // Copy an existing linked_ptr<>, adding ourselves to the list of references.
+ template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); }
+ linked_ptr(linked_ptr const& ptr) { // NOLINT
+ assert(&ptr != this);
+ copy(&ptr);
+ }
+
+ // Assignment releases the old value and acquires the new.
+ template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) {
+ depart();
+ copy(&ptr);
+ return *this;
+ }
+
+ linked_ptr& operator=(linked_ptr const& ptr) {
+ if (&ptr != this) {
+ depart();
+ copy(&ptr);
+ }
+ return *this;
+ }
+
+ // Smart pointer members.
+ void reset(T* ptr = NULL) {
+ depart();
+ capture(ptr);
+ }
+ T* get() const { return value_; }
+ T* operator->() const { return value_; }
+ T& operator*() const { return *value_; }
+
+ bool operator==(T* p) const { return value_ == p; }
+ bool operator!=(T* p) const { return value_ != p; }
+ template <typename U>
+ bool operator==(linked_ptr<U> const& ptr) const {
+ return value_ == ptr.get();
+ }
+ template <typename U>
+ bool operator!=(linked_ptr<U> const& ptr) const {
+ return value_ != ptr.get();
+ }
+
+ private:
+ template <typename U>
+ friend class linked_ptr;
+
+ T* value_;
+ linked_ptr_internal link_;
+
+ void depart() {
+ if (link_.depart()) delete value_;
+ }
+
+ void capture(T* ptr) {
+ value_ = ptr;
+ link_.join_new();
+ }
+
+ template <typename U> void copy(linked_ptr<U> const* ptr) {
+ value_ = ptr->get();
+ if (value_)
+ link_.join(&ptr->link_);
+ else
+ link_.join_new();
+ }
+};
+
+template<typename T> inline
+bool operator==(T* ptr, const linked_ptr<T>& x) {
+ return ptr == x.get();
+}
+
+template<typename T> inline
+bool operator!=(T* ptr, const linked_ptr<T>& x) {
+ return ptr != x.get();
+}
+
+// A function to convert T* into linked_ptr<T>
+// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation
+// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg))
+template <typename T>
+linked_ptr<T> make_linked_ptr(T* ptr) {
+ return linked_ptr<T>(ptr);
+}
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Test - The Google C++ Testing and Mocking Framework
+//
+// This file implements a universal value printer that can print a
+// value of any type T:
+//
+// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
+//
+// A user can teach this function how to print a class type T by
+// defining either operator<<() or PrintTo() in the namespace that
+// defines T. More specifically, the FIRST defined function in the
+// following list will be used (assuming T is defined in namespace
+// foo):
+//
+// 1. foo::PrintTo(const T&, ostream*)
+// 2. operator<<(ostream&, const T&) defined in either foo or the
+// global namespace.
+//
+// However if T is an STL-style container then it is printed element-wise
+// unless foo::PrintTo(const T&, ostream*) is defined. Note that
+// operator<<() is ignored for container types.
+//
+// If none of the above is defined, it will print the debug string of
+// the value if it is a protocol buffer, or print the raw bytes in the
+// value otherwise.
+//
+// To aid debugging: when T is a reference type, the address of the
+// value is also printed; when T is a (const) char pointer, both the
+// pointer value and the NUL-terminated string it points to are
+// printed.
+//
+// We also provide some convenient wrappers:
+//
+// // Prints a value to a string. For a (const or not) char
+// // pointer, the NUL-terminated string (but not the pointer) is
+// // printed.
+// std::string ::testing::PrintToString(const T& value);
+//
+// // Prints a value tersely: for a reference type, the referenced
+// // value (but not the address) is printed; for a (const or not) char
+// // pointer, the NUL-terminated string (but not the pointer) is
+// // printed.
+// void ::testing::internal::UniversalTersePrint(const T& value, ostream*);
+//
+// // Prints value using the type inferred by the compiler. The difference
+// // from UniversalTersePrint() is that this function prints both the
+// // pointer and the NUL-terminated string for a (const or not) char pointer.
+// void ::testing::internal::UniversalPrint(const T& value, ostream*);
+//
+// // Prints the fields of a tuple tersely to a string vector, one
+// // element for each field. Tuple support must be enabled in
+// // gtest-port.h.
+// std::vector<string> UniversalTersePrintTupleFieldsToStrings(
+// const Tuple& value);
+//
+// Known limitation:
+//
+// The print primitives print the elements of an STL-style container
+// using the compiler-inferred type of *iter where iter is a
+// const_iterator of the container. When const_iterator is an input
+// iterator but not a forward iterator, this inferred type may not
+// match value_type, and the print output may be incorrect. In
+// practice, this is rarely a problem as for most containers
+// const_iterator is a forward iterator. We'll fix this if there's an
+// actual need for it. Note that this fix cannot rely on value_type
+// being defined as many user-defined container types don't have
+// value_type.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+#define GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+
+#include <ostream> // NOLINT
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#if GTEST_HAS_STD_TUPLE_
+# include <tuple>
+#endif
+
+#if GTEST_HAS_ABSL
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "absl/types/variant.h"
+#endif // GTEST_HAS_ABSL
+
+namespace testing {
+
+// Definitions in the 'internal' and 'internal2' name spaces are
+// subject to change without notice. DO NOT USE THEM IN USER CODE!
+namespace internal2 {
+
+// Prints the given number of bytes in the given object to the given
+// ostream.
+GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes,
+ size_t count,
+ ::std::ostream* os);
+
+// For selecting which printer to use when a given type has neither <<
+// nor PrintTo().
+enum TypeKind {
+ kProtobuf, // a protobuf type
+ kConvertibleToInteger, // a type implicitly convertible to BiggestInt
+ // (e.g. a named or unnamed enum type)
+#if GTEST_HAS_ABSL
+ kConvertibleToStringView, // a type implicitly convertible to
+ // absl::string_view
+#endif
+ kOtherType // anything else
+};
+
+// TypeWithoutFormatter<T, kTypeKind>::PrintValue(value, os) is called
+// by the universal printer to print a value of type T when neither
+// operator<< nor PrintTo() is defined for T, where kTypeKind is the
+// "kind" of T as defined by enum TypeKind.
+template <typename T, TypeKind kTypeKind>
+class TypeWithoutFormatter {
+ public:
+ // This default version is called when kTypeKind is kOtherType.
+ static void PrintValue(const T& value, ::std::ostream* os) {
+ PrintBytesInObjectTo(static_cast<const unsigned char*>(
+ reinterpret_cast<const void*>(&value)),
+ sizeof(value), os);
+ }
+};
+
+// We print a protobuf using its ShortDebugString() when the string
+// doesn't exceed this many characters; otherwise we print it using
+// DebugString() for better readability.
+const size_t kProtobufOneLinerMaxLength = 50;
+
+template <typename T>
+class TypeWithoutFormatter<T, kProtobuf> {
+ public:
+ static void PrintValue(const T& value, ::std::ostream* os) {
+ std::string pretty_str = value.ShortDebugString();
+ if (pretty_str.length() > kProtobufOneLinerMaxLength) {
+ pretty_str = "\n" + value.DebugString();
+ }
+ *os << ("<" + pretty_str + ">");
+ }
+};
+
+template <typename T>
+class TypeWithoutFormatter<T, kConvertibleToInteger> {
+ public:
+ // Since T has no << operator or PrintTo() but can be implicitly
+ // converted to BiggestInt, we print it as a BiggestInt.
+ //
+ // Most likely T is an enum type (either named or unnamed), in which
+ // case printing it as an integer is the desired behavior. In case
+ // T is not an enum, printing it as an integer is the best we can do
+ // given that it has no user-defined printer.
+ static void PrintValue(const T& value, ::std::ostream* os) {
+ const internal::BiggestInt kBigInt = value;
+ *os << kBigInt;
+ }
+};
+
+#if GTEST_HAS_ABSL
+template <typename T>
+class TypeWithoutFormatter<T, kConvertibleToStringView> {
+ public:
+ // Since T has neither operator<< nor PrintTo() but can be implicitly
+ // converted to absl::string_view, we print it as a absl::string_view.
+ //
+ // Note: the implementation is further below, as it depends on
+ // internal::PrintTo symbol which is defined later in the file.
+ static void PrintValue(const T& value, ::std::ostream* os);
+};
+#endif
+
+// Prints the given value to the given ostream. If the value is a
+// protocol message, its debug string is printed; if it's an enum or
+// of a type implicitly convertible to BiggestInt, it's printed as an
+// integer; otherwise the bytes in the value are printed. This is
+// what UniversalPrinter<T>::Print() does when it knows nothing about
+// type T and T has neither << operator nor PrintTo().
+//
+// A user can override this behavior for a class type Foo by defining
+// a << operator in the namespace where Foo is defined.
+//
+// We put this operator in namespace 'internal2' instead of 'internal'
+// to simplify the implementation, as much code in 'internal' needs to
+// use << in STL, which would conflict with our own << were it defined
+// in 'internal'.
+//
+// Note that this operator<< takes a generic std::basic_ostream<Char,
+// CharTraits> type instead of the more restricted std::ostream. If
+// we define it to take an std::ostream instead, we'll get an
+// "ambiguous overloads" compiler error when trying to print a type
+// Foo that supports streaming to std::basic_ostream<Char,
+// CharTraits>, as the compiler cannot tell whether
+// operator<<(std::ostream&, const T&) or
+// operator<<(std::basic_stream<Char, CharTraits>, const Foo&) is more
+// specific.
+template <typename Char, typename CharTraits, typename T>
+::std::basic_ostream<Char, CharTraits>& operator<<(
+ ::std::basic_ostream<Char, CharTraits>& os, const T& x) {
+ TypeWithoutFormatter<T, (internal::IsAProtocolMessage<T>::value
+ ? kProtobuf
+ : internal::ImplicitlyConvertible<
+ const T&, internal::BiggestInt>::value
+ ? kConvertibleToInteger
+ :
+#if GTEST_HAS_ABSL
+ internal::ImplicitlyConvertible<
+ const T&, absl::string_view>::value
+ ? kConvertibleToStringView
+ :
+#endif
+ kOtherType)>::PrintValue(x, &os);
+ return os;
+}
+
+} // namespace internal2
+} // namespace testing
+
+// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up
+// magic needed for implementing UniversalPrinter won't work.
+namespace testing_internal {
+
+// Used to print a value that is not an STL-style container when the
+// user doesn't define PrintTo() for it.
+template <typename T>
+void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) {
+ // With the following statement, during unqualified name lookup,
+ // testing::internal2::operator<< appears as if it was declared in
+ // the nearest enclosing namespace that contains both
+ // ::testing_internal and ::testing::internal2, i.e. the global
+ // namespace. For more details, refer to the C++ Standard section
+ // 7.3.4-1 [namespace.udir]. This allows us to fall back onto
+ // testing::internal2::operator<< in case T doesn't come with a <<
+ // operator.
+ //
+ // We cannot write 'using ::testing::internal2::operator<<;', which
+ // gcc 3.3 fails to compile due to a compiler bug.
+ using namespace ::testing::internal2; // NOLINT
+
+ // Assuming T is defined in namespace foo, in the next statement,
+ // the compiler will consider all of:
+ //
+ // 1. foo::operator<< (thanks to Koenig look-up),
+ // 2. ::operator<< (as the current namespace is enclosed in ::),
+ // 3. testing::internal2::operator<< (thanks to the using statement above).
+ //
+ // The operator<< whose type matches T best will be picked.
+ //
+ // We deliberately allow #2 to be a candidate, as sometimes it's
+ // impossible to define #1 (e.g. when foo is ::std, defining
+ // anything in it is undefined behavior unless you are a compiler
+ // vendor.).
+ *os << value;
+}
+
+} // namespace testing_internal
+
+namespace testing {
+namespace internal {
+
+// FormatForComparison<ToPrint, OtherOperand>::Format(value) formats a
+// value of type ToPrint that is an operand of a comparison assertion
+// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in
+// the comparison, and is used to help determine the best way to
+// format the value. In particular, when the value is a C string
+// (char pointer) and the other operand is an STL string object, we
+// want to format the C string as a string, since we know it is
+// compared by value with the string object. If the value is a char
+// pointer but the other operand is not an STL string object, we don't
+// know whether the pointer is supposed to point to a NUL-terminated
+// string, and thus want to print it as a pointer to be safe.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+// The default case.
+template <typename ToPrint, typename OtherOperand>
+class FormatForComparison {
+ public:
+ static ::std::string Format(const ToPrint& value) {
+ return ::testing::PrintToString(value);
+ }
+};
+
+// Array.
+template <typename ToPrint, size_t N, typename OtherOperand>
+class FormatForComparison<ToPrint[N], OtherOperand> {
+ public:
+ static ::std::string Format(const ToPrint* value) {
+ return FormatForComparison<const ToPrint*, OtherOperand>::Format(value);
+ }
+};
+
+// By default, print C string as pointers to be safe, as we don't know
+// whether they actually point to a NUL-terminated string.
+
+#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \
+ template <typename OtherOperand> \
+ class FormatForComparison<CharType*, OtherOperand> { \
+ public: \
+ static ::std::string Format(CharType* value) { \
+ return ::testing::PrintToString(static_cast<const void*>(value)); \
+ } \
+ }
+
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t);
+
+#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_
+
+// If a C string is compared with an STL string object, we know it's meant
+// to point to a NUL-terminated string, and thus can print it as a string.
+
+#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \
+ template <> \
+ class FormatForComparison<CharType*, OtherStringType> { \
+ public: \
+ static ::std::string Format(CharType* value) { \
+ return ::testing::PrintToString(value); \
+ } \
+ }
+
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string);
+
+#if GTEST_HAS_GLOBAL_STRING
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::string);
+#endif
+
+#if GTEST_HAS_GLOBAL_WSTRING
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::wstring);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::wstring);
+#endif
+
+#if GTEST_HAS_STD_WSTRING
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring);
+#endif
+
+#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_
+
+// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc)
+// operand to be used in a failure message. The type (but not value)
+// of the other operand may affect the format. This allows us to
+// print a char* as a raw pointer when it is compared against another
+// char* or void*, and print it as a C string when it is compared
+// against an std::string object, for example.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+template <typename T1, typename T2>
+std::string FormatForComparisonFailureMessage(
+ const T1& value, const T2& /* other_operand */) {
+ return FormatForComparison<T1, T2>::Format(value);
+}
+
+// UniversalPrinter<T>::Print(value, ostream_ptr) prints the given
+// value to the given ostream. The caller must ensure that
+// 'ostream_ptr' is not NULL, or the behavior is undefined.
+//
+// We define UniversalPrinter as a class template (as opposed to a
+// function template), as we need to partially specialize it for
+// reference types, which cannot be done with function templates.
+template <typename T>
+class UniversalPrinter;
+
+template <typename T>
+void UniversalPrint(const T& value, ::std::ostream* os);
+
+enum DefaultPrinterType {
+ kPrintContainer,
+ kPrintPointer,
+ kPrintFunctionPointer,
+ kPrintOther,
+};
+template <DefaultPrinterType type> struct WrapPrinterType {};
+
+// Used to print an STL-style container when the user doesn't define
+// a PrintTo() for it.
+template <typename C>
+void DefaultPrintTo(WrapPrinterType<kPrintContainer> /* dummy */,
+ const C& container, ::std::ostream* os) {
+ const size_t kMaxCount = 32; // The maximum number of elements to print.
+ *os << '{';
+ size_t count = 0;
+ for (typename C::const_iterator it = container.begin();
+ it != container.end(); ++it, ++count) {
+ if (count > 0) {
+ *os << ',';
+ if (count == kMaxCount) { // Enough has been printed.
+ *os << " ...";
+ break;
+ }
+ }
+ *os << ' ';
+ // We cannot call PrintTo(*it, os) here as PrintTo() doesn't
+ // handle *it being a native array.
+ internal::UniversalPrint(*it, os);
+ }
+
+ if (count > 0) {
+ *os << ' ';
+ }
+ *os << '}';
+}
+
+// Used to print a pointer that is neither a char pointer nor a member
+// pointer, when the user doesn't define PrintTo() for it. (A member
+// variable pointer or member function pointer doesn't really point to
+// a location in the address space. Their representation is
+// implementation-defined. Therefore they will be printed as raw
+// bytes.)
+template <typename T>
+void DefaultPrintTo(WrapPrinterType<kPrintPointer> /* dummy */,
+ T* p, ::std::ostream* os) {
+ if (p == NULL) {
+ *os << "NULL";
+ } else {
+ // T is not a function type. We just call << to print p,
+ // relying on ADL to pick up user-defined << for their pointer
+ // types, if any.
+ *os << p;
+ }
+}
+template <typename T>
+void DefaultPrintTo(WrapPrinterType<kPrintFunctionPointer> /* dummy */,
+ T* p, ::std::ostream* os) {
+ if (p == NULL) {
+ *os << "NULL";
+ } else {
+ // T is a function type, so '*os << p' doesn't do what we want
+ // (it just prints p as bool). We want to print p as a const
+ // void*.
+ *os << reinterpret_cast<const void*>(p);
+ }
+}
+
+// Used to print a non-container, non-pointer value when the user
+// doesn't define PrintTo() for it.
+template <typename T>
+void DefaultPrintTo(WrapPrinterType<kPrintOther> /* dummy */,
+ const T& value, ::std::ostream* os) {
+ ::testing_internal::DefaultPrintNonContainerTo(value, os);
+}
+
+// Prints the given value using the << operator if it has one;
+// otherwise prints the bytes in it. This is what
+// UniversalPrinter<T>::Print() does when PrintTo() is not specialized
+// or overloaded for type T.
+//
+// A user can override this behavior for a class type Foo by defining
+// an overload of PrintTo() in the namespace where Foo is defined. We
+// give the user this option as sometimes defining a << operator for
+// Foo is not desirable (e.g. the coding style may prevent doing it,
+// or there is already a << operator but it doesn't do what the user
+// wants).
+template <typename T>
+void PrintTo(const T& value, ::std::ostream* os) {
+ // DefaultPrintTo() is overloaded. The type of its first argument
+ // determines which version will be picked.
+ //
+ // Note that we check for container types here, prior to we check
+ // for protocol message types in our operator<<. The rationale is:
+ //
+ // For protocol messages, we want to give people a chance to
+ // override Google Mock's format by defining a PrintTo() or
+ // operator<<. For STL containers, other formats can be
+ // incompatible with Google Mock's format for the container
+ // elements; therefore we check for container types here to ensure
+ // that our format is used.
+ //
+ // Note that MSVC and clang-cl do allow an implicit conversion from
+ // pointer-to-function to pointer-to-object, but clang-cl warns on it.
+ // So don't use ImplicitlyConvertible if it can be helped since it will
+ // cause this warning, and use a separate overload of DefaultPrintTo for
+ // function pointers so that the `*os << p` in the object pointer overload
+ // doesn't cause that warning either.
+ DefaultPrintTo(
+ WrapPrinterType <
+ (sizeof(IsContainerTest<T>(0)) == sizeof(IsContainer)) &&
+ !IsRecursiveContainer<T>::value
+ ? kPrintContainer
+ : !is_pointer<T>::value
+ ? kPrintOther
+#if GTEST_LANG_CXX11
+ : std::is_function<typename std::remove_pointer<T>::type>::value
+#else
+ : !internal::ImplicitlyConvertible<T, const void*>::value
+#endif
+ ? kPrintFunctionPointer
+ : kPrintPointer > (),
+ value, os);
+}
+
+// The following list of PrintTo() overloads tells
+// UniversalPrinter<T>::Print() how to print standard types (built-in
+// types, strings, plain arrays, and pointers).
+
+// Overloads for various char types.
+GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os);
+GTEST_API_ void PrintTo(signed char c, ::std::ostream* os);
+inline void PrintTo(char c, ::std::ostream* os) {
+ // When printing a plain char, we always treat it as unsigned. This
+ // way, the output won't be affected by whether the compiler thinks
+ // char is signed or not.
+ PrintTo(static_cast<unsigned char>(c), os);
+}
+
+// Overloads for other simple built-in types.
+inline void PrintTo(bool x, ::std::ostream* os) {
+ *os << (x ? "true" : "false");
+}
+
+// Overload for wchar_t type.
+// Prints a wchar_t as a symbol if it is printable or as its internal
+// code otherwise and also as its decimal code (except for L'\0').
+// The L'\0' char is printed as "L'\\0'". The decimal code is printed
+// as signed integer when wchar_t is implemented by the compiler
+// as a signed type and is printed as an unsigned integer when wchar_t
+// is implemented as an unsigned type.
+GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os);
+
+// Overloads for C strings.
+GTEST_API_ void PrintTo(const char* s, ::std::ostream* os);
+inline void PrintTo(char* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const char*>(s), os);
+}
+
+// signed/unsigned char is often used for representing binary data, so
+// we print pointers to it as void* to be safe.
+inline void PrintTo(const signed char* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(signed char* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(const unsigned char* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(unsigned char* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const void*>(s), os);
+}
+
+// MSVC can be configured to define wchar_t as a typedef of unsigned
+// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native
+// type. When wchar_t is a typedef, defining an overload for const
+// wchar_t* would cause unsigned short* be printed as a wide string,
+// possibly causing invalid memory accesses.
+#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
+// Overloads for wide C strings
+GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os);
+inline void PrintTo(wchar_t* s, ::std::ostream* os) {
+ PrintTo(ImplicitCast_<const wchar_t*>(s), os);
+}
+#endif
+
+// Overload for C arrays. Multi-dimensional arrays are printed
+// properly.
+
+// Prints the given number of elements in an array, without printing
+// the curly braces.
+template <typename T>
+void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) {
+ UniversalPrint(a[0], os);
+ for (size_t i = 1; i != count; i++) {
+ *os << ", ";
+ UniversalPrint(a[i], os);
+ }
+}
+
+// Overloads for ::string and ::std::string.
+#if GTEST_HAS_GLOBAL_STRING
+GTEST_API_ void PrintStringTo(const ::string&s, ::std::ostream* os);
+inline void PrintTo(const ::string& s, ::std::ostream* os) {
+ PrintStringTo(s, os);
+}
+#endif // GTEST_HAS_GLOBAL_STRING
+
+GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os);
+inline void PrintTo(const ::std::string& s, ::std::ostream* os) {
+ PrintStringTo(s, os);
+}
+
+// Overloads for ::wstring and ::std::wstring.
+#if GTEST_HAS_GLOBAL_WSTRING
+GTEST_API_ void PrintWideStringTo(const ::wstring&s, ::std::ostream* os);
+inline void PrintTo(const ::wstring& s, ::std::ostream* os) {
+ PrintWideStringTo(s, os);
+}
+#endif // GTEST_HAS_GLOBAL_WSTRING
+
+#if GTEST_HAS_STD_WSTRING
+GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os);
+inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) {
+ PrintWideStringTo(s, os);
+}
+#endif // GTEST_HAS_STD_WSTRING
+
+#if GTEST_HAS_ABSL
+// Overload for absl::string_view.
+inline void PrintTo(absl::string_view sp, ::std::ostream* os) {
+ PrintTo(::std::string(sp), os);
+}
+#endif // GTEST_HAS_ABSL
+
+#if GTEST_LANG_CXX11
+inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; }
+#endif // GTEST_LANG_CXX11
+
+#if GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_
+// Helper function for printing a tuple. T must be instantiated with
+// a tuple type.
+template <typename T>
+void PrintTupleTo(const T& t, ::std::ostream* os);
+#endif // GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_
+
+#if GTEST_HAS_TR1_TUPLE
+// Overload for ::std::tr1::tuple. Needed for printing function arguments,
+// which are packed as tuples.
+
+// Overloaded PrintTo() for tuples of various arities. We support
+// tuples of up-to 10 fields. The following implementation works
+// regardless of whether tr1::tuple is implemented using the
+// non-standard variadic template feature or not.
+
+inline void PrintTo(const ::std::tr1::tuple<>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1>
+void PrintTo(const ::std::tr1::tuple<T1>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2>
+void PrintTo(const ::std::tr1::tuple<T1, T2>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4, T5>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4, T5, T6>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4, T5, T6, T7>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4, T5, T6, T7, T8>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+void PrintTo(const ::std::tr1::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+void PrintTo(
+ const ::std::tr1::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>& t,
+ ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+#endif // GTEST_HAS_TR1_TUPLE
+
+#if GTEST_HAS_STD_TUPLE_
+template <typename... Types>
+void PrintTo(const ::std::tuple<Types...>& t, ::std::ostream* os) {
+ PrintTupleTo(t, os);
+}
+#endif // GTEST_HAS_STD_TUPLE_
+
+// Overload for std::pair.
+template <typename T1, typename T2>
+void PrintTo(const ::std::pair<T1, T2>& value, ::std::ostream* os) {
+ *os << '(';
+ // We cannot use UniversalPrint(value.first, os) here, as T1 may be
+ // a reference type. The same for printing value.second.
+ UniversalPrinter<T1>::Print(value.first, os);
+ *os << ", ";
+ UniversalPrinter<T2>::Print(value.second, os);
+ *os << ')';
+}
+
+// Implements printing a non-reference type T by letting the compiler
+// pick the right overload of PrintTo() for T.
+template <typename T>
+class UniversalPrinter {
+ public:
+ // MSVC warns about adding const to a function type, so we want to
+ // disable the warning.
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180)
+
+ // Note: we deliberately don't call this PrintTo(), as that name
+ // conflicts with ::testing::internal::PrintTo in the body of the
+ // function.
+ static void Print(const T& value, ::std::ostream* os) {
+ // By default, ::testing::internal::PrintTo() is used for printing
+ // the value.
+ //
+ // Thanks to Koenig look-up, if T is a class and has its own
+ // PrintTo() function defined in its namespace, that function will
+ // be visible here. Since it is more specific than the generic ones
+ // in ::testing::internal, it will be picked by the compiler in the
+ // following statement - exactly what we want.
+ PrintTo(value, os);
+ }
+
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+};
+
+#if GTEST_HAS_ABSL
+
+// Printer for absl::optional
+
+template <typename T>
+class UniversalPrinter<::absl::optional<T>> {
+ public:
+ static void Print(const ::absl::optional<T>& value, ::std::ostream* os) {
+ *os << '(';
+ if (!value) {
+ *os << "nullopt";
+ } else {
+ UniversalPrint(*value, os);
+ }
+ *os << ')';
+ }
+};
+
+// Printer for absl::variant
+
+template <typename... T>
+class UniversalPrinter<::absl::variant<T...>> {
+ public:
+ static void Print(const ::absl::variant<T...>& value, ::std::ostream* os) {
+ *os << '(';
+ absl::visit(Visitor{os}, value);
+ *os << ')';
+ }
+
+ private:
+ struct Visitor {
+ template <typename U>
+ void operator()(const U& u) const {
+ *os << "'" << GetTypeName<U>() << "' with value ";
+ UniversalPrint(u, os);
+ }
+ ::std::ostream* os;
+ };
+};
+
+#endif // GTEST_HAS_ABSL
+
+// UniversalPrintArray(begin, len, os) prints an array of 'len'
+// elements, starting at address 'begin'.
+template <typename T>
+void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) {
+ if (len == 0) {
+ *os << "{}";
+ } else {
+ *os << "{ ";
+ const size_t kThreshold = 18;
+ const size_t kChunkSize = 8;
+ // If the array has more than kThreshold elements, we'll have to
+ // omit some details by printing only the first and the last
+ // kChunkSize elements.
+ // FIXME: let the user control the threshold using a flag.
+ if (len <= kThreshold) {
+ PrintRawArrayTo(begin, len, os);
+ } else {
+ PrintRawArrayTo(begin, kChunkSize, os);
+ *os << ", ..., ";
+ PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os);
+ }
+ *os << " }";
+ }
+}
+// This overload prints a (const) char array compactly.
+GTEST_API_ void UniversalPrintArray(
+ const char* begin, size_t len, ::std::ostream* os);
+
+// This overload prints a (const) wchar_t array compactly.
+GTEST_API_ void UniversalPrintArray(
+ const wchar_t* begin, size_t len, ::std::ostream* os);
+
+// Implements printing an array type T[N].
+template <typename T, size_t N>
+class UniversalPrinter<T[N]> {
+ public:
+ // Prints the given array, omitting some elements when there are too
+ // many.
+ static void Print(const T (&a)[N], ::std::ostream* os) {
+ UniversalPrintArray(a, N, os);
+ }
+};
+
+// Implements printing a reference type T&.
+template <typename T>
+class UniversalPrinter<T&> {
+ public:
+ // MSVC warns about adding const to a function type, so we want to
+ // disable the warning.
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180)
+
+ static void Print(const T& value, ::std::ostream* os) {
+ // Prints the address of the value. We use reinterpret_cast here
+ // as static_cast doesn't compile when T is a function type.
+ *os << "@" << reinterpret_cast<const void*>(&value) << " ";
+
+ // Then prints the value itself.
+ UniversalPrint(value, os);
+ }
+
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+};
+
+// Prints a value tersely: for a reference type, the referenced value
+// (but not the address) is printed; for a (const) char pointer, the
+// NUL-terminated string (but not the pointer) is printed.
+
+template <typename T>
+class UniversalTersePrinter {
+ public:
+ static void Print(const T& value, ::std::ostream* os) {
+ UniversalPrint(value, os);
+ }
+};
+template <typename T>
+class UniversalTersePrinter<T&> {
+ public:
+ static void Print(const T& value, ::std::ostream* os) {
+ UniversalPrint(value, os);
+ }
+};
+template <typename T, size_t N>
+class UniversalTersePrinter<T[N]> {
+ public:
+ static void Print(const T (&value)[N], ::std::ostream* os) {
+ UniversalPrinter<T[N]>::Print(value, os);
+ }
+};
+template <>
+class UniversalTersePrinter<const char*> {
+ public:
+ static void Print(const char* str, ::std::ostream* os) {
+ if (str == NULL) {
+ *os << "NULL";
+ } else {
+ UniversalPrint(std::string(str), os);
+ }
+ }
+};
+template <>
+class UniversalTersePrinter<char*> {
+ public:
+ static void Print(char* str, ::std::ostream* os) {
+ UniversalTersePrinter<const char*>::Print(str, os);
+ }
+};
+
+#if GTEST_HAS_STD_WSTRING
+template <>
+class UniversalTersePrinter<const wchar_t*> {
+ public:
+ static void Print(const wchar_t* str, ::std::ostream* os) {
+ if (str == NULL) {
+ *os << "NULL";
+ } else {
+ UniversalPrint(::std::wstring(str), os);
+ }
+ }
+};
+#endif
+
+template <>
+class UniversalTersePrinter<wchar_t*> {
+ public:
+ static void Print(wchar_t* str, ::std::ostream* os) {
+ UniversalTersePrinter<const wchar_t*>::Print(str, os);
+ }
+};
+
+template <typename T>
+void UniversalTersePrint(const T& value, ::std::ostream* os) {
+ UniversalTersePrinter<T>::Print(value, os);
+}
+
+// Prints a value using the type inferred by the compiler. The
+// difference between this and UniversalTersePrint() is that for a
+// (const) char pointer, this prints both the pointer and the
+// NUL-terminated string.
+template <typename T>
+void UniversalPrint(const T& value, ::std::ostream* os) {
+ // A workarond for the bug in VC++ 7.1 that prevents us from instantiating
+ // UniversalPrinter with T directly.
+ typedef T T1;
+ UniversalPrinter<T1>::Print(value, os);
+}
+
+typedef ::std::vector< ::std::string> Strings;
+
+// TuplePolicy<TupleT> must provide:
+// - tuple_size
+// size of tuple TupleT.
+// - get<size_t I>(const TupleT& t)
+// static function extracting element I of tuple TupleT.
+// - tuple_element<size_t I>::type
+// type of element I of tuple TupleT.
+template <typename TupleT>
+struct TuplePolicy;
+
+#if GTEST_HAS_TR1_TUPLE
+template <typename TupleT>
+struct TuplePolicy {
+ typedef TupleT Tuple;
+ static const size_t tuple_size = ::std::tr1::tuple_size<Tuple>::value;
+
+ template <size_t I>
+ struct tuple_element : ::std::tr1::tuple_element<static_cast<int>(I), Tuple> {
+ };
+
+ template <size_t I>
+ static typename AddReference<const typename ::std::tr1::tuple_element<
+ static_cast<int>(I), Tuple>::type>::type
+ get(const Tuple& tuple) {
+ return ::std::tr1::get<I>(tuple);
+ }
+};
+template <typename TupleT>
+const size_t TuplePolicy<TupleT>::tuple_size;
+#endif // GTEST_HAS_TR1_TUPLE
+
+#if GTEST_HAS_STD_TUPLE_
+template <typename... Types>
+struct TuplePolicy< ::std::tuple<Types...> > {
+ typedef ::std::tuple<Types...> Tuple;
+ static const size_t tuple_size = ::std::tuple_size<Tuple>::value;
+
+ template <size_t I>
+ struct tuple_element : ::std::tuple_element<I, Tuple> {};
+
+ template <size_t I>
+ static const typename ::std::tuple_element<I, Tuple>::type& get(
+ const Tuple& tuple) {
+ return ::std::get<I>(tuple);
+ }
+};
+template <typename... Types>
+const size_t TuplePolicy< ::std::tuple<Types...> >::tuple_size;
+#endif // GTEST_HAS_STD_TUPLE_
+
+#if GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_
+// This helper template allows PrintTo() for tuples and
+// UniversalTersePrintTupleFieldsToStrings() to be defined by
+// induction on the number of tuple fields. The idea is that
+// TuplePrefixPrinter<N>::PrintPrefixTo(t, os) prints the first N
+// fields in tuple t, and can be defined in terms of
+// TuplePrefixPrinter<N - 1>.
+//
+// The inductive case.
+template <size_t N>
+struct TuplePrefixPrinter {
+ // Prints the first N fields of a tuple.
+ template <typename Tuple>
+ static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) {
+ TuplePrefixPrinter<N - 1>::PrintPrefixTo(t, os);
+ GTEST_INTENTIONAL_CONST_COND_PUSH_()
+ if (N > 1) {
+ GTEST_INTENTIONAL_CONST_COND_POP_()
+ *os << ", ";
+ }
+ UniversalPrinter<
+ typename TuplePolicy<Tuple>::template tuple_element<N - 1>::type>
+ ::Print(TuplePolicy<Tuple>::template get<N - 1>(t), os);
+ }
+
+ // Tersely prints the first N fields of a tuple to a string vector,
+ // one element for each field.
+ template <typename Tuple>
+ static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) {
+ TuplePrefixPrinter<N - 1>::TersePrintPrefixToStrings(t, strings);
+ ::std::stringstream ss;
+ UniversalTersePrint(TuplePolicy<Tuple>::template get<N - 1>(t), &ss);
+ strings->push_back(ss.str());
+ }
+};
+
+// Base case.
+template <>
+struct TuplePrefixPrinter<0> {
+ template <typename Tuple>
+ static void PrintPrefixTo(const Tuple&, ::std::ostream*) {}
+
+ template <typename Tuple>
+ static void TersePrintPrefixToStrings(const Tuple&, Strings*) {}
+};
+
+// Helper function for printing a tuple.
+// Tuple must be either std::tr1::tuple or std::tuple type.
+template <typename Tuple>
+void PrintTupleTo(const Tuple& t, ::std::ostream* os) {
+ *os << "(";
+ TuplePrefixPrinter<TuplePolicy<Tuple>::tuple_size>::PrintPrefixTo(t, os);
+ *os << ")";
+}
+
+// Prints the fields of a tuple tersely to a string vector, one
+// element for each field. See the comment before
+// UniversalTersePrint() for how we define "tersely".
+template <typename Tuple>
+Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) {
+ Strings result;
+ TuplePrefixPrinter<TuplePolicy<Tuple>::tuple_size>::
+ TersePrintPrefixToStrings(value, &result);
+ return result;
+}
+#endif // GTEST_HAS_TR1_TUPLE || GTEST_HAS_STD_TUPLE_
+
+} // namespace internal
+
+#if GTEST_HAS_ABSL
+namespace internal2 {
+template <typename T>
+void TypeWithoutFormatter<T, kConvertibleToStringView>::PrintValue(
+ const T& value, ::std::ostream* os) {
+ internal::PrintTo(absl::string_view(value), os);
+}
+} // namespace internal2
+#endif
+
+template <typename T>
+::std::string PrintToString(const T& value) {
+ ::std::stringstream ss;
+ internal::UniversalTersePrinter<T>::Print(value, &ss);
+ return ss.str();
+}
+
+} // namespace testing
+
+// Include any custom printer added by the local installation.
+// We must include this header at the end to make sure it can use the
+// declarations from this file.
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file provides an injection point for custom printers in a local
+// installation of gTest.
+// It will be included from gtest-printers.h and the overrides in this file
+// will be visible to everyone.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+
+namespace testing {
+
+// Input to a parameterized test name generator, describing a test parameter.
+// Consists of the parameter value and the integer parameter index.
+template <class ParamType>
+struct TestParamInfo {
+ TestParamInfo(const ParamType& a_param, size_t an_index) :
+ param(a_param),
+ index(an_index) {}
+ ParamType param;
+ size_t index;
+};
+
+// A builtin parameterized test name generator which returns the result of
+// testing::PrintToString.
+struct PrintToStringParamName {
+ template <class ParamType>
+ std::string operator()(const TestParamInfo<ParamType>& info) const {
+ return PrintToString(info.param);
+ }
+};
+
+namespace internal {
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Outputs a message explaining invalid registration of different
+// fixture class for the same test case. This may happen when
+// TEST_P macro is used to define two tests with the same name
+// but in different namespaces.
+GTEST_API_ void ReportInvalidTestCaseType(const char* test_case_name,
+ CodeLocation code_location);
+
+template <typename> class ParamGeneratorInterface;
+template <typename> class ParamGenerator;
+
+// Interface for iterating over elements provided by an implementation
+// of ParamGeneratorInterface<T>.
+template <typename T>
+class ParamIteratorInterface {
+ public:
+ virtual ~ParamIteratorInterface() {}
+ // A pointer to the base generator instance.
+ // Used only for the purposes of iterator comparison
+ // to make sure that two iterators belong to the same generator.
+ virtual const ParamGeneratorInterface<T>* BaseGenerator() const = 0;
+ // Advances iterator to point to the next element
+ // provided by the generator. The caller is responsible
+ // for not calling Advance() on an iterator equal to
+ // BaseGenerator()->End().
+ virtual void Advance() = 0;
+ // Clones the iterator object. Used for implementing copy semantics
+ // of ParamIterator<T>.
+ virtual ParamIteratorInterface* Clone() const = 0;
+ // Dereferences the current iterator and provides (read-only) access
+ // to the pointed value. It is the caller's responsibility not to call
+ // Current() on an iterator equal to BaseGenerator()->End().
+ // Used for implementing ParamGenerator<T>::operator*().
+ virtual const T* Current() const = 0;
+ // Determines whether the given iterator and other point to the same
+ // element in the sequence generated by the generator.
+ // Used for implementing ParamGenerator<T>::operator==().
+ virtual bool Equals(const ParamIteratorInterface& other) const = 0;
+};
+
+// Class iterating over elements provided by an implementation of
+// ParamGeneratorInterface<T>. It wraps ParamIteratorInterface<T>
+// and implements the const forward iterator concept.
+template <typename T>
+class ParamIterator {
+ public:
+ typedef T value_type;
+ typedef const T& reference;
+ typedef ptrdiff_t difference_type;
+
+ // ParamIterator assumes ownership of the impl_ pointer.
+ ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {}
+ ParamIterator& operator=(const ParamIterator& other) {
+ if (this != &other)
+ impl_.reset(other.impl_->Clone());
+ return *this;
+ }
+
+ const T& operator*() const { return *impl_->Current(); }
+ const T* operator->() const { return impl_->Current(); }
+ // Prefix version of operator++.
+ ParamIterator& operator++() {
+ impl_->Advance();
+ return *this;
+ }
+ // Postfix version of operator++.
+ ParamIterator operator++(int /*unused*/) {
+ ParamIteratorInterface<T>* clone = impl_->Clone();
+ impl_->Advance();
+ return ParamIterator(clone);
+ }
+ bool operator==(const ParamIterator& other) const {
+ return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_);
+ }
+ bool operator!=(const ParamIterator& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ friend class ParamGenerator<T>;
+ explicit ParamIterator(ParamIteratorInterface<T>* impl) : impl_(impl) {}
+ scoped_ptr<ParamIteratorInterface<T> > impl_;
+};
+
+// ParamGeneratorInterface<T> is the binary interface to access generators
+// defined in other translation units.
+template <typename T>
+class ParamGeneratorInterface {
+ public:
+ typedef T ParamType;
+
+ virtual ~ParamGeneratorInterface() {}
+
+ // Generator interface definition
+ virtual ParamIteratorInterface<T>* Begin() const = 0;
+ virtual ParamIteratorInterface<T>* End() const = 0;
+};
+
+// Wraps ParamGeneratorInterface<T> and provides general generator syntax
+// compatible with the STL Container concept.
+// This class implements copy initialization semantics and the contained
+// ParamGeneratorInterface<T> instance is shared among all copies
+// of the original object. This is possible because that instance is immutable.
+template<typename T>
+class ParamGenerator {
+ public:
+ typedef ParamIterator<T> iterator;
+
+ explicit ParamGenerator(ParamGeneratorInterface<T>* impl) : impl_(impl) {}
+ ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {}
+
+ ParamGenerator& operator=(const ParamGenerator& other) {
+ impl_ = other.impl_;
+ return *this;
+ }
+
+ iterator begin() const { return iterator(impl_->Begin()); }
+ iterator end() const { return iterator(impl_->End()); }
+
+ private:
+ linked_ptr<const ParamGeneratorInterface<T> > impl_;
+};
+
+// Generates values from a range of two comparable values. Can be used to
+// generate sequences of user-defined types that implement operator+() and
+// operator<().
+// This class is used in the Range() function.
+template <typename T, typename IncrementT>
+class RangeGenerator : public ParamGeneratorInterface<T> {
+ public:
+ RangeGenerator(T begin, T end, IncrementT step)
+ : begin_(begin), end_(end),
+ step_(step), end_index_(CalculateEndIndex(begin, end, step)) {}
+ virtual ~RangeGenerator() {}
+
+ virtual ParamIteratorInterface<T>* Begin() const {
+ return new Iterator(this, begin_, 0, step_);
+ }
+ virtual ParamIteratorInterface<T>* End() const {
+ return new Iterator(this, end_, end_index_, step_);
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<T> {
+ public:
+ Iterator(const ParamGeneratorInterface<T>* base, T value, int index,
+ IncrementT step)
+ : base_(base), value_(value), index_(index), step_(step) {}
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<T>* BaseGenerator() const {
+ return base_;
+ }
+ virtual void Advance() {
+ value_ = static_cast<T>(value_ + step_);
+ index_++;
+ }
+ virtual ParamIteratorInterface<T>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const T* Current() const { return &value_; }
+ virtual bool Equals(const ParamIteratorInterface<T>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const int other_index =
+ CheckedDowncastToActualType<const Iterator>(&other)->index_;
+ return index_ == other_index;
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : ParamIteratorInterface<T>(),
+ base_(other.base_), value_(other.value_), index_(other.index_),
+ step_(other.step_) {}
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<T>* const base_;
+ T value_;
+ int index_;
+ const IncrementT step_;
+ }; // class RangeGenerator::Iterator
+
+ static int CalculateEndIndex(const T& begin,
+ const T& end,
+ const IncrementT& step) {
+ int end_index = 0;
+ for (T i = begin; i < end; i = static_cast<T>(i + step))
+ end_index++;
+ return end_index;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const RangeGenerator& other);
+
+ const T begin_;
+ const T end_;
+ const IncrementT step_;
+ // The index for the end() iterator. All the elements in the generated
+ // sequence are indexed (0-based) to aid iterator comparison.
+ const int end_index_;
+}; // class RangeGenerator
+
+
+// Generates values from a pair of STL-style iterators. Used in the
+// ValuesIn() function. The elements are copied from the source range
+// since the source can be located on the stack, and the generator
+// is likely to persist beyond that stack frame.
+template <typename T>
+class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface<T> {
+ public:
+ template <typename ForwardIterator>
+ ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end)
+ : container_(begin, end) {}
+ virtual ~ValuesInIteratorRangeGenerator() {}
+
+ virtual ParamIteratorInterface<T>* Begin() const {
+ return new Iterator(this, container_.begin());
+ }
+ virtual ParamIteratorInterface<T>* End() const {
+ return new Iterator(this, container_.end());
+ }
+
+ private:
+ typedef typename ::std::vector<T> ContainerType;
+
+ class Iterator : public ParamIteratorInterface<T> {
+ public:
+ Iterator(const ParamGeneratorInterface<T>* base,
+ typename ContainerType::const_iterator iterator)
+ : base_(base), iterator_(iterator) {}
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<T>* BaseGenerator() const {
+ return base_;
+ }
+ virtual void Advance() {
+ ++iterator_;
+ value_.reset();
+ }
+ virtual ParamIteratorInterface<T>* Clone() const {
+ return new Iterator(*this);
+ }
+ // We need to use cached value referenced by iterator_ because *iterator_
+ // can return a temporary object (and of type other then T), so just
+ // having "return &*iterator_;" doesn't work.
+ // value_ is updated here and not in Advance() because Advance()
+ // can advance iterator_ beyond the end of the range, and we cannot
+ // detect that fact. The client code, on the other hand, is
+ // responsible for not calling Current() on an out-of-range iterator.
+ virtual const T* Current() const {
+ if (value_.get() == NULL)
+ value_.reset(new T(*iterator_));
+ return value_.get();
+ }
+ virtual bool Equals(const ParamIteratorInterface<T>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ return iterator_ ==
+ CheckedDowncastToActualType<const Iterator>(&other)->iterator_;
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ // The explicit constructor call suppresses a false warning
+ // emitted by gcc when supplied with the -Wextra option.
+ : ParamIteratorInterface<T>(),
+ base_(other.base_),
+ iterator_(other.iterator_) {}
+
+ const ParamGeneratorInterface<T>* const base_;
+ typename ContainerType::const_iterator iterator_;
+ // A cached value of *iterator_. We keep it here to allow access by
+ // pointer in the wrapping iterator's operator->().
+ // value_ needs to be mutable to be accessed in Current().
+ // Use of scoped_ptr helps manage cached value's lifetime,
+ // which is bound by the lifespan of the iterator itself.
+ mutable scoped_ptr<const T> value_;
+ }; // class ValuesInIteratorRangeGenerator::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const ValuesInIteratorRangeGenerator& other);
+
+ const ContainerType container_;
+}; // class ValuesInIteratorRangeGenerator
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Default parameterized test name generator, returns a string containing the
+// integer test parameter index.
+template <class ParamType>
+std::string DefaultParamName(const TestParamInfo<ParamType>& info) {
+ Message name_stream;
+ name_stream << info.index;
+ return name_stream.GetString();
+}
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Parameterized test name overload helpers, which help the
+// INSTANTIATE_TEST_CASE_P macro choose between the default parameterized
+// test name generator and user param name generator.
+template <class ParamType, class ParamNameGenFunctor>
+ParamNameGenFunctor GetParamNameGen(ParamNameGenFunctor func) {
+ return func;
+}
+
+template <class ParamType>
+struct ParamNameGenFunc {
+ typedef std::string Type(const TestParamInfo<ParamType>&);
+};
+
+template <class ParamType>
+typename ParamNameGenFunc<ParamType>::Type *GetParamNameGen() {
+ return DefaultParamName;
+}
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Stores a parameter value and later creates tests parameterized with that
+// value.
+template <class TestClass>
+class ParameterizedTestFactory : public TestFactoryBase {
+ public:
+ typedef typename TestClass::ParamType ParamType;
+ explicit ParameterizedTestFactory(ParamType parameter) :
+ parameter_(parameter) {}
+ virtual Test* CreateTest() {
+ TestClass::SetParam(&parameter_);
+ return new TestClass();
+ }
+
+ private:
+ const ParamType parameter_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// TestMetaFactoryBase is a base class for meta-factories that create
+// test factories for passing into MakeAndRegisterTestInfo function.
+template <class ParamType>
+class TestMetaFactoryBase {
+ public:
+ virtual ~TestMetaFactoryBase() {}
+
+ virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0;
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// TestMetaFactory creates test factories for passing into
+// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives
+// ownership of test factory pointer, same factory object cannot be passed
+// into that method twice. But ParameterizedTestCaseInfo is going to call
+// it for each Test/Parameter value combination. Thus it needs meta factory
+// creator class.
+template <class TestCase>
+class TestMetaFactory
+ : public TestMetaFactoryBase<typename TestCase::ParamType> {
+ public:
+ typedef typename TestCase::ParamType ParamType;
+
+ TestMetaFactory() {}
+
+ virtual TestFactoryBase* CreateTestFactory(ParamType parameter) {
+ return new ParameterizedTestFactory<TestCase>(parameter);
+ }
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestCaseInfoBase is a generic interface
+// to ParameterizedTestCaseInfo classes. ParameterizedTestCaseInfoBase
+// accumulates test information provided by TEST_P macro invocations
+// and generators provided by INSTANTIATE_TEST_CASE_P macro invocations
+// and uses that information to register all resulting test instances
+// in RegisterTests method. The ParameterizeTestCaseRegistry class holds
+// a collection of pointers to the ParameterizedTestCaseInfo objects
+// and calls RegisterTests() on each of them when asked.
+class ParameterizedTestCaseInfoBase {
+ public:
+ virtual ~ParameterizedTestCaseInfoBase() {}
+
+ // Base part of test case name for display purposes.
+ virtual const std::string& GetTestCaseName() const = 0;
+ // Test case id to verify identity.
+ virtual TypeId GetTestCaseTypeId() const = 0;
+ // UnitTest class invokes this method to register tests in this
+ // test case right before running them in RUN_ALL_TESTS macro.
+ // This method should not be called more then once on any single
+ // instance of a ParameterizedTestCaseInfoBase derived class.
+ virtual void RegisterTests() = 0;
+
+ protected:
+ ParameterizedTestCaseInfoBase() {}
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestCaseInfo accumulates tests obtained from TEST_P
+// macro invocations for a particular test case and generators
+// obtained from INSTANTIATE_TEST_CASE_P macro invocations for that
+// test case. It registers tests with all values generated by all
+// generators when asked.
+template <class TestCase>
+class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase {
+ public:
+ // ParamType and GeneratorCreationFunc are private types but are required
+ // for declarations of public methods AddTestPattern() and
+ // AddTestCaseInstantiation().
+ typedef typename TestCase::ParamType ParamType;
+ // A function that returns an instance of appropriate generator type.
+ typedef ParamGenerator<ParamType>(GeneratorCreationFunc)();
+ typedef typename ParamNameGenFunc<ParamType>::Type ParamNameGeneratorFunc;
+
+ explicit ParameterizedTestCaseInfo(
+ const char* name, CodeLocation code_location)
+ : test_case_name_(name), code_location_(code_location) {}
+
+ // Test case base name for display purposes.
+ virtual const std::string& GetTestCaseName() const { return test_case_name_; }
+ // Test case id to verify identity.
+ virtual TypeId GetTestCaseTypeId() const { return GetTypeId<TestCase>(); }
+ // TEST_P macro uses AddTestPattern() to record information
+ // about a single test in a LocalTestInfo structure.
+ // test_case_name is the base name of the test case (without invocation
+ // prefix). test_base_name is the name of an individual test without
+ // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
+ // test case base name and DoBar is test base name.
+ void AddTestPattern(const char* test_case_name,
+ const char* test_base_name,
+ TestMetaFactoryBase<ParamType>* meta_factory) {
+ tests_.push_back(linked_ptr<TestInfo>(new TestInfo(test_case_name,
+ test_base_name,
+ meta_factory)));
+ }
+ // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information
+ // about a generator.
+ int AddTestCaseInstantiation(const std::string& instantiation_name,
+ GeneratorCreationFunc* func,
+ ParamNameGeneratorFunc* name_func,
+ const char* file, int line) {
+ instantiations_.push_back(
+ InstantiationInfo(instantiation_name, func, name_func, file, line));
+ return 0; // Return value used only to run this method in namespace scope.
+ }
+ // UnitTest class invokes this method to register tests in this test case
+ // test cases right before running tests in RUN_ALL_TESTS macro.
+ // This method should not be called more then once on any single
+ // instance of a ParameterizedTestCaseInfoBase derived class.
+ // UnitTest has a guard to prevent from calling this method more then once.
+ virtual void RegisterTests() {
+ for (typename TestInfoContainer::iterator test_it = tests_.begin();
+ test_it != tests_.end(); ++test_it) {
+ linked_ptr<TestInfo> test_info = *test_it;
+ for (typename InstantiationContainer::iterator gen_it =
+ instantiations_.begin(); gen_it != instantiations_.end();
+ ++gen_it) {
+ const std::string& instantiation_name = gen_it->name;
+ ParamGenerator<ParamType> generator((*gen_it->generator)());
+ ParamNameGeneratorFunc* name_func = gen_it->name_func;
+ const char* file = gen_it->file;
+ int line = gen_it->line;
+
+ std::string test_case_name;
+ if ( !instantiation_name.empty() )
+ test_case_name = instantiation_name + "/";
+ test_case_name += test_info->test_case_base_name;
+
+ size_t i = 0;
+ std::set<std::string> test_param_names;
+ for (typename ParamGenerator<ParamType>::iterator param_it =
+ generator.begin();
+ param_it != generator.end(); ++param_it, ++i) {
+ Message test_name_stream;
+
+ std::string param_name = name_func(
+ TestParamInfo<ParamType>(*param_it, i));
+
+ GTEST_CHECK_(IsValidParamName(param_name))
+ << "Parameterized test name '" << param_name
+ << "' is invalid, in " << file
+ << " line " << line << std::endl;
+
+ GTEST_CHECK_(test_param_names.count(param_name) == 0)
+ << "Duplicate parameterized test name '" << param_name
+ << "', in " << file << " line " << line << std::endl;
+
+ test_param_names.insert(param_name);
+
+ test_name_stream << test_info->test_base_name << "/" << param_name;
+ MakeAndRegisterTestInfo(
+ test_case_name.c_str(),
+ test_name_stream.GetString().c_str(),
+ NULL, // No type parameter.
+ PrintToString(*param_it).c_str(),
+ code_location_,
+ GetTestCaseTypeId(),
+ TestCase::SetUpTestCase,
+ TestCase::TearDownTestCase,
+ test_info->test_meta_factory->CreateTestFactory(*param_it));
+ } // for param_it
+ } // for gen_it
+ } // for test_it
+ } // RegisterTests
+
+ private:
+ // LocalTestInfo structure keeps information about a single test registered
+ // with TEST_P macro.
+ struct TestInfo {
+ TestInfo(const char* a_test_case_base_name,
+ const char* a_test_base_name,
+ TestMetaFactoryBase<ParamType>* a_test_meta_factory) :
+ test_case_base_name(a_test_case_base_name),
+ test_base_name(a_test_base_name),
+ test_meta_factory(a_test_meta_factory) {}
+
+ const std::string test_case_base_name;
+ const std::string test_base_name;
+ const scoped_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory;
+ };
+ typedef ::std::vector<linked_ptr<TestInfo> > TestInfoContainer;
+ // Records data received from INSTANTIATE_TEST_CASE_P macros:
+ // <Instantiation name, Sequence generator creation function,
+ // Name generator function, Source file, Source line>
+ struct InstantiationInfo {
+ InstantiationInfo(const std::string &name_in,
+ GeneratorCreationFunc* generator_in,
+ ParamNameGeneratorFunc* name_func_in,
+ const char* file_in,
+ int line_in)
+ : name(name_in),
+ generator(generator_in),
+ name_func(name_func_in),
+ file(file_in),
+ line(line_in) {}
+
+ std::string name;
+ GeneratorCreationFunc* generator;
+ ParamNameGeneratorFunc* name_func;
+ const char* file;
+ int line;
+ };
+ typedef ::std::vector<InstantiationInfo> InstantiationContainer;
+
+ static bool IsValidParamName(const std::string& name) {
+ // Check for empty string
+ if (name.empty())
+ return false;
+
+ // Check for invalid characters
+ for (std::string::size_type index = 0; index < name.size(); ++index) {
+ if (!isalnum(name[index]) && name[index] != '_')
+ return false;
+ }
+
+ return true;
+ }
+
+ const std::string test_case_name_;
+ CodeLocation code_location_;
+ TestInfoContainer tests_;
+ InstantiationContainer instantiations_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo);
+}; // class ParameterizedTestCaseInfo
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestCaseRegistry contains a map of ParameterizedTestCaseInfoBase
+// classes accessed by test case names. TEST_P and INSTANTIATE_TEST_CASE_P
+// macros use it to locate their corresponding ParameterizedTestCaseInfo
+// descriptors.
+class ParameterizedTestCaseRegistry {
+ public:
+ ParameterizedTestCaseRegistry() {}
+ ~ParameterizedTestCaseRegistry() {
+ for (TestCaseInfoContainer::iterator it = test_case_infos_.begin();
+ it != test_case_infos_.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ // Looks up or creates and returns a structure containing information about
+ // tests and instantiations of a particular test case.
+ template <class TestCase>
+ ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder(
+ const char* test_case_name,
+ CodeLocation code_location) {
+ ParameterizedTestCaseInfo<TestCase>* typed_test_info = NULL;
+ for (TestCaseInfoContainer::iterator it = test_case_infos_.begin();
+ it != test_case_infos_.end(); ++it) {
+ if ((*it)->GetTestCaseName() == test_case_name) {
+ if ((*it)->GetTestCaseTypeId() != GetTypeId<TestCase>()) {
+ // Complain about incorrect usage of Google Test facilities
+ // and terminate the program since we cannot guaranty correct
+ // test case setup and tear-down in this case.
+ ReportInvalidTestCaseType(test_case_name, code_location);
+ posix::Abort();
+ } else {
+ // At this point we are sure that the object we found is of the same
+ // type we are looking for, so we downcast it to that type
+ // without further checks.
+ typed_test_info = CheckedDowncastToActualType<
+ ParameterizedTestCaseInfo<TestCase> >(*it);
+ }
+ break;
+ }
+ }
+ if (typed_test_info == NULL) {
+ typed_test_info = new ParameterizedTestCaseInfo<TestCase>(
+ test_case_name, code_location);
+ test_case_infos_.push_back(typed_test_info);
+ }
+ return typed_test_info;
+ }
+ void RegisterTests() {
+ for (TestCaseInfoContainer::iterator it = test_case_infos_.begin();
+ it != test_case_infos_.end(); ++it) {
+ (*it)->RegisterTests();
+ }
+ }
+
+ private:
+ typedef ::std::vector<ParameterizedTestCaseInfoBase*> TestCaseInfoContainer;
+
+ TestCaseInfoContainer test_case_infos_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry);
+};
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+// This file was GENERATED by command:
+// pump.py gtest-param-util-generated.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Type and function utilities for implementing parameterized tests.
+// This file is generated by a SCRIPT. DO NOT EDIT BY HAND!
+//
+// Currently Google Test supports at most 50 arguments in Values,
+// and at most 10 arguments in Combine. Please contact
+// googletestframework@googlegroups.com if you need more.
+// Please note that the number of arguments to Combine is limited
+// by the maximum arity of the implementation of tuple which is
+// currently set at 10.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_
+
+
+namespace testing {
+
+// Forward declarations of ValuesIn(), which is implemented in
+// include/gtest/gtest-param-test.h.
+template <typename ForwardIterator>
+internal::ParamGenerator<
+ typename ::testing::internal::IteratorTraits<ForwardIterator>::value_type>
+ValuesIn(ForwardIterator begin, ForwardIterator end);
+
+template <typename T, size_t N>
+internal::ParamGenerator<T> ValuesIn(const T (&array)[N]);
+
+template <class Container>
+internal::ParamGenerator<typename Container::value_type> ValuesIn(
+ const Container& container);
+
+namespace internal {
+
+// Used in the Values() function to provide polymorphic capabilities.
+template <typename T1>
+class ValueArray1 {
+ public:
+ explicit ValueArray1(T1 v1) : v1_(v1) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray1(const ValueArray1& other) : v1_(other.v1_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray1& other);
+
+ const T1 v1_;
+};
+
+template <typename T1, typename T2>
+class ValueArray2 {
+ public:
+ ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray2(const ValueArray2& other) : v1_(other.v1_), v2_(other.v2_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray2& other);
+
+ const T1 v1_;
+ const T2 v2_;
+};
+
+template <typename T1, typename T2, typename T3>
+class ValueArray3 {
+ public:
+ ValueArray3(T1 v1, T2 v2, T3 v3) : v1_(v1), v2_(v2), v3_(v3) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray3(const ValueArray3& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray3& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4>
+class ValueArray4 {
+ public:
+ ValueArray4(T1 v1, T2 v2, T3 v3, T4 v4) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray4(const ValueArray4& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray4& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+class ValueArray5 {
+ public:
+ ValueArray5(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray5(const ValueArray5& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray5& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+class ValueArray6 {
+ public:
+ ValueArray6(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray6(const ValueArray6& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray6& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+class ValueArray7 {
+ public:
+ ValueArray7(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray7(const ValueArray7& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray7& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+class ValueArray8 {
+ public:
+ ValueArray8(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray8(const ValueArray8& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray8& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+class ValueArray9 {
+ public:
+ ValueArray9(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8,
+ T9 v9) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray9(const ValueArray9& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray9& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+class ValueArray10 {
+ public:
+ ValueArray10(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray10(const ValueArray10& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray10& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11>
+class ValueArray11 {
+ public:
+ ValueArray11(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6),
+ v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray11(const ValueArray11& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray11& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12>
+class ValueArray12 {
+ public:
+ ValueArray12(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5),
+ v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray12(const ValueArray12& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray12& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13>
+class ValueArray13 {
+ public:
+ ValueArray13(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13) : v1_(v1), v2_(v2), v3_(v3), v4_(v4),
+ v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11),
+ v12_(v12), v13_(v13) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray13(const ValueArray13& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray13& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14>
+class ValueArray14 {
+ public:
+ ValueArray14(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray14(const ValueArray14& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray14& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15>
+class ValueArray15 {
+ public:
+ ValueArray15(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray15(const ValueArray15& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray15& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16>
+class ValueArray16 {
+ public:
+ ValueArray16(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9),
+ v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15),
+ v16_(v16) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray16(const ValueArray16& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray16& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17>
+class ValueArray17 {
+ public:
+ ValueArray17(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16,
+ T17 v17) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray17(const ValueArray17& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray17& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18>
+class ValueArray18 {
+ public:
+ ValueArray18(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray18(const ValueArray18& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray18& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19>
+class ValueArray19 {
+ public:
+ ValueArray19(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6),
+ v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13),
+ v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray19(const ValueArray19& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray19& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20>
+class ValueArray20 {
+ public:
+ ValueArray20(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5),
+ v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12),
+ v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18),
+ v19_(v19), v20_(v20) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray20(const ValueArray20& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray20& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21>
+class ValueArray21 {
+ public:
+ ValueArray21(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21) : v1_(v1), v2_(v2), v3_(v3), v4_(v4),
+ v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11),
+ v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17),
+ v18_(v18), v19_(v19), v20_(v20), v21_(v21) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray21(const ValueArray21& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray21& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22>
+class ValueArray22 {
+ public:
+ ValueArray22(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray22(const ValueArray22& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray22& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23>
+class ValueArray23 {
+ public:
+ ValueArray23(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray23(const ValueArray23& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray23& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24>
+class ValueArray24 {
+ public:
+ ValueArray24(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9),
+ v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15),
+ v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21),
+ v22_(v22), v23_(v23), v24_(v24) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray24(const ValueArray24& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray24& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25>
+class ValueArray25 {
+ public:
+ ValueArray25(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24,
+ T25 v25) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray25(const ValueArray25& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray25& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26>
+class ValueArray26 {
+ public:
+ ValueArray26(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray26(const ValueArray26& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray26& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27>
+class ValueArray27 {
+ public:
+ ValueArray27(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6),
+ v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13),
+ v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19),
+ v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25),
+ v26_(v26), v27_(v27) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray27(const ValueArray27& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray27& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28>
+class ValueArray28 {
+ public:
+ ValueArray28(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5),
+ v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12),
+ v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18),
+ v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24),
+ v25_(v25), v26_(v26), v27_(v27), v28_(v28) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray28(const ValueArray28& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray28& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29>
+class ValueArray29 {
+ public:
+ ValueArray29(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29) : v1_(v1), v2_(v2), v3_(v3), v4_(v4),
+ v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11),
+ v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17),
+ v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23),
+ v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray29(const ValueArray29& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray29& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30>
+class ValueArray30 {
+ public:
+ ValueArray30(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray30(const ValueArray30& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray30& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31>
+class ValueArray31 {
+ public:
+ ValueArray31(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30), v31_(v31) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray31(const ValueArray31& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray31& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32>
+class ValueArray32 {
+ public:
+ ValueArray32(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9),
+ v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15),
+ v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21),
+ v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27),
+ v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray32(const ValueArray32& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray32& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33>
+class ValueArray33 {
+ public:
+ ValueArray33(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32,
+ T33 v33) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray33(const ValueArray33& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray33& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34>
+class ValueArray34 {
+ public:
+ ValueArray34(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33), v34_(v34) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray34(const ValueArray34& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray34& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35>
+class ValueArray35 {
+ public:
+ ValueArray35(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6),
+ v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13),
+ v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19),
+ v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25),
+ v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31),
+ v32_(v32), v33_(v33), v34_(v34), v35_(v35) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray35(const ValueArray35& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray35& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36>
+class ValueArray36 {
+ public:
+ ValueArray36(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5),
+ v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12),
+ v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18),
+ v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24),
+ v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30),
+ v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray36(const ValueArray36& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray36& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37>
+class ValueArray37 {
+ public:
+ ValueArray37(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37) : v1_(v1), v2_(v2), v3_(v3), v4_(v4),
+ v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11),
+ v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17),
+ v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23),
+ v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29),
+ v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35),
+ v36_(v36), v37_(v37) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray37(const ValueArray37& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray37& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38>
+class ValueArray38 {
+ public:
+ ValueArray38(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34),
+ v35_(v35), v36_(v36), v37_(v37), v38_(v38) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray38(const ValueArray38& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray38& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39>
+class ValueArray39 {
+ public:
+ ValueArray39(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34),
+ v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray39(const ValueArray39& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray39& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40>
+class ValueArray40 {
+ public:
+ ValueArray40(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9),
+ v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15),
+ v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21),
+ v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27),
+ v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33),
+ v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39),
+ v40_(v40) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray40(const ValueArray40& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray40& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41>
+class ValueArray41 {
+ public:
+ ValueArray41(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40,
+ T41 v41) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38),
+ v39_(v39), v40_(v40), v41_(v41) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray41(const ValueArray41& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray41& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42>
+class ValueArray42 {
+ public:
+ ValueArray42(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38),
+ v39_(v39), v40_(v40), v41_(v41), v42_(v42) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray42(const ValueArray42& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray42& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43>
+class ValueArray43 {
+ public:
+ ValueArray43(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6),
+ v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13),
+ v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19),
+ v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25),
+ v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31),
+ v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37),
+ v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray43(const ValueArray43& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray43& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44>
+class ValueArray44 {
+ public:
+ ValueArray44(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5),
+ v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12),
+ v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18),
+ v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24),
+ v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30),
+ v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36),
+ v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42),
+ v43_(v43), v44_(v44) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray44(const ValueArray44& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray44& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45>
+class ValueArray45 {
+ public:
+ ValueArray45(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45) : v1_(v1), v2_(v2), v3_(v3), v4_(v4),
+ v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11),
+ v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17),
+ v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23),
+ v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29),
+ v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35),
+ v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41),
+ v42_(v42), v43_(v43), v44_(v44), v45_(v45) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray45(const ValueArray45& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray45& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46>
+class ValueArray46 {
+ public:
+ ValueArray46(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) : v1_(v1), v2_(v2), v3_(v3),
+ v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34),
+ v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40),
+ v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_), static_cast<T>(v46_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray46(const ValueArray46& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_), v46_(other.v46_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray46& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+ const T46 v46_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47>
+class ValueArray47 {
+ public:
+ ValueArray47(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) : v1_(v1), v2_(v2),
+ v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10),
+ v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16),
+ v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22),
+ v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28),
+ v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34),
+ v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40),
+ v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46),
+ v47_(v47) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_), static_cast<T>(v46_), static_cast<T>(v47_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray47(const ValueArray47& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_), v46_(other.v46_),
+ v47_(other.v47_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray47& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+ const T46 v46_;
+ const T47 v47_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48>
+class ValueArray48 {
+ public:
+ ValueArray48(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48) : v1_(v1),
+ v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9),
+ v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15),
+ v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21),
+ v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27),
+ v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33),
+ v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39),
+ v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45),
+ v46_(v46), v47_(v47), v48_(v48) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_), static_cast<T>(v46_), static_cast<T>(v47_),
+ static_cast<T>(v48_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray48(const ValueArray48& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_), v46_(other.v46_),
+ v47_(other.v47_), v48_(other.v48_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray48& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+ const T46 v46_;
+ const T47 v47_;
+ const T48 v48_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49>
+class ValueArray49 {
+ public:
+ ValueArray49(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48,
+ T49 v49) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38),
+ v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44),
+ v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_), static_cast<T>(v46_), static_cast<T>(v47_),
+ static_cast<T>(v48_), static_cast<T>(v49_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray49(const ValueArray49& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_), v46_(other.v46_),
+ v47_(other.v47_), v48_(other.v48_), v49_(other.v49_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray49& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+ const T46 v46_;
+ const T47 v47_;
+ const T48 v48_;
+ const T49 v49_;
+};
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49, typename T50>
+class ValueArray50 {
+ public:
+ ValueArray50(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, T49 v49,
+ T50 v50) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7),
+ v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14),
+ v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20),
+ v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26),
+ v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32),
+ v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38),
+ v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44),
+ v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49), v50_(v50) {}
+
+ template <typename T>
+ operator ParamGenerator<T>() const {
+ const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_),
+ static_cast<T>(v3_), static_cast<T>(v4_), static_cast<T>(v5_),
+ static_cast<T>(v6_), static_cast<T>(v7_), static_cast<T>(v8_),
+ static_cast<T>(v9_), static_cast<T>(v10_), static_cast<T>(v11_),
+ static_cast<T>(v12_), static_cast<T>(v13_), static_cast<T>(v14_),
+ static_cast<T>(v15_), static_cast<T>(v16_), static_cast<T>(v17_),
+ static_cast<T>(v18_), static_cast<T>(v19_), static_cast<T>(v20_),
+ static_cast<T>(v21_), static_cast<T>(v22_), static_cast<T>(v23_),
+ static_cast<T>(v24_), static_cast<T>(v25_), static_cast<T>(v26_),
+ static_cast<T>(v27_), static_cast<T>(v28_), static_cast<T>(v29_),
+ static_cast<T>(v30_), static_cast<T>(v31_), static_cast<T>(v32_),
+ static_cast<T>(v33_), static_cast<T>(v34_), static_cast<T>(v35_),
+ static_cast<T>(v36_), static_cast<T>(v37_), static_cast<T>(v38_),
+ static_cast<T>(v39_), static_cast<T>(v40_), static_cast<T>(v41_),
+ static_cast<T>(v42_), static_cast<T>(v43_), static_cast<T>(v44_),
+ static_cast<T>(v45_), static_cast<T>(v46_), static_cast<T>(v47_),
+ static_cast<T>(v48_), static_cast<T>(v49_), static_cast<T>(v50_)};
+ return ValuesIn(array);
+ }
+
+ ValueArray50(const ValueArray50& other) : v1_(other.v1_), v2_(other.v2_),
+ v3_(other.v3_), v4_(other.v4_), v5_(other.v5_), v6_(other.v6_),
+ v7_(other.v7_), v8_(other.v8_), v9_(other.v9_), v10_(other.v10_),
+ v11_(other.v11_), v12_(other.v12_), v13_(other.v13_), v14_(other.v14_),
+ v15_(other.v15_), v16_(other.v16_), v17_(other.v17_), v18_(other.v18_),
+ v19_(other.v19_), v20_(other.v20_), v21_(other.v21_), v22_(other.v22_),
+ v23_(other.v23_), v24_(other.v24_), v25_(other.v25_), v26_(other.v26_),
+ v27_(other.v27_), v28_(other.v28_), v29_(other.v29_), v30_(other.v30_),
+ v31_(other.v31_), v32_(other.v32_), v33_(other.v33_), v34_(other.v34_),
+ v35_(other.v35_), v36_(other.v36_), v37_(other.v37_), v38_(other.v38_),
+ v39_(other.v39_), v40_(other.v40_), v41_(other.v41_), v42_(other.v42_),
+ v43_(other.v43_), v44_(other.v44_), v45_(other.v45_), v46_(other.v46_),
+ v47_(other.v47_), v48_(other.v48_), v49_(other.v49_), v50_(other.v50_) {}
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const ValueArray50& other);
+
+ const T1 v1_;
+ const T2 v2_;
+ const T3 v3_;
+ const T4 v4_;
+ const T5 v5_;
+ const T6 v6_;
+ const T7 v7_;
+ const T8 v8_;
+ const T9 v9_;
+ const T10 v10_;
+ const T11 v11_;
+ const T12 v12_;
+ const T13 v13_;
+ const T14 v14_;
+ const T15 v15_;
+ const T16 v16_;
+ const T17 v17_;
+ const T18 v18_;
+ const T19 v19_;
+ const T20 v20_;
+ const T21 v21_;
+ const T22 v22_;
+ const T23 v23_;
+ const T24 v24_;
+ const T25 v25_;
+ const T26 v26_;
+ const T27 v27_;
+ const T28 v28_;
+ const T29 v29_;
+ const T30 v30_;
+ const T31 v31_;
+ const T32 v32_;
+ const T33 v33_;
+ const T34 v34_;
+ const T35 v35_;
+ const T36 v36_;
+ const T37 v37_;
+ const T38 v38_;
+ const T39 v39_;
+ const T40 v40_;
+ const T41 v41_;
+ const T42 v42_;
+ const T43 v43_;
+ const T44 v44_;
+ const T45 v45_;
+ const T46 v46_;
+ const T47 v47_;
+ const T48 v48_;
+ const T49 v49_;
+ const T50 v50_;
+};
+
+# if GTEST_HAS_COMBINE
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Generates values from the Cartesian product of values produced
+// by the argument generators.
+//
+template <typename T1, typename T2>
+class CartesianProductGenerator2
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2> > {
+ public:
+ typedef ::testing::tuple<T1, T2> ParamType;
+
+ CartesianProductGenerator2(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2)
+ : g1_(g1), g2_(g2) {}
+ virtual ~CartesianProductGenerator2() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current2_;
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator2::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator2& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+}; // class CartesianProductGenerator2
+
+
+template <typename T1, typename T2, typename T3>
+class CartesianProductGenerator3
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3> ParamType;
+
+ CartesianProductGenerator3(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3)
+ : g1_(g1), g2_(g2), g3_(g3) {}
+ virtual ~CartesianProductGenerator3() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current3_;
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator3::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator3& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+}; // class CartesianProductGenerator3
+
+
+template <typename T1, typename T2, typename T3, typename T4>
+class CartesianProductGenerator4
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4> ParamType;
+
+ CartesianProductGenerator4(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {}
+ virtual ~CartesianProductGenerator4() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current4_;
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator4::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator4& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+}; // class CartesianProductGenerator4
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+class CartesianProductGenerator5
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5> ParamType;
+
+ CartesianProductGenerator5(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {}
+ virtual ~CartesianProductGenerator5() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current5_;
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator5::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator5& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+}; // class CartesianProductGenerator5
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+class CartesianProductGenerator6
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5,
+ T6> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5, T6> ParamType;
+
+ CartesianProductGenerator6(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5,
+ const ParamGenerator<T6>& g6)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {}
+ virtual ~CartesianProductGenerator6() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5,
+ const ParamGenerator<T6>& g6,
+ const typename ParamGenerator<T6>::iterator& current6)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5),
+ begin6_(g6.begin()), end6_(g6.end()), current6_(current6) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current6_;
+ if (current6_ == end6_) {
+ current6_ = begin6_;
+ ++current5_;
+ }
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_ &&
+ current6_ == typed_other->current6_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_),
+ begin6_(other.begin6_),
+ end6_(other.end6_),
+ current6_(other.current6_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_, *current6_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_ ||
+ current6_ == end6_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ const typename ParamGenerator<T6>::iterator begin6_;
+ const typename ParamGenerator<T6>::iterator end6_;
+ typename ParamGenerator<T6>::iterator current6_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator6::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator6& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+ const ParamGenerator<T6> g6_;
+}; // class CartesianProductGenerator6
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+class CartesianProductGenerator7
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5, T6,
+ T7> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5, T6, T7> ParamType;
+
+ CartesianProductGenerator7(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5,
+ const ParamGenerator<T6>& g6, const ParamGenerator<T7>& g7)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {}
+ virtual ~CartesianProductGenerator7() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_,
+ g7_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5,
+ const ParamGenerator<T6>& g6,
+ const typename ParamGenerator<T6>::iterator& current6,
+ const ParamGenerator<T7>& g7,
+ const typename ParamGenerator<T7>::iterator& current7)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5),
+ begin6_(g6.begin()), end6_(g6.end()), current6_(current6),
+ begin7_(g7.begin()), end7_(g7.end()), current7_(current7) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current7_;
+ if (current7_ == end7_) {
+ current7_ = begin7_;
+ ++current6_;
+ }
+ if (current6_ == end6_) {
+ current6_ = begin6_;
+ ++current5_;
+ }
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_ &&
+ current6_ == typed_other->current6_ &&
+ current7_ == typed_other->current7_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_),
+ begin6_(other.begin6_),
+ end6_(other.end6_),
+ current6_(other.current6_),
+ begin7_(other.begin7_),
+ end7_(other.end7_),
+ current7_(other.current7_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_, *current6_, *current7_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_ ||
+ current6_ == end6_ ||
+ current7_ == end7_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ const typename ParamGenerator<T6>::iterator begin6_;
+ const typename ParamGenerator<T6>::iterator end6_;
+ typename ParamGenerator<T6>::iterator current6_;
+ const typename ParamGenerator<T7>::iterator begin7_;
+ const typename ParamGenerator<T7>::iterator end7_;
+ typename ParamGenerator<T7>::iterator current7_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator7::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator7& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+ const ParamGenerator<T6> g6_;
+ const ParamGenerator<T7> g7_;
+}; // class CartesianProductGenerator7
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+class CartesianProductGenerator8
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5, T6,
+ T7, T8> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8> ParamType;
+
+ CartesianProductGenerator8(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5,
+ const ParamGenerator<T6>& g6, const ParamGenerator<T7>& g7,
+ const ParamGenerator<T8>& g8)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7),
+ g8_(g8) {}
+ virtual ~CartesianProductGenerator8() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_,
+ g7_.begin(), g8_, g8_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_,
+ g8_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5,
+ const ParamGenerator<T6>& g6,
+ const typename ParamGenerator<T6>::iterator& current6,
+ const ParamGenerator<T7>& g7,
+ const typename ParamGenerator<T7>::iterator& current7,
+ const ParamGenerator<T8>& g8,
+ const typename ParamGenerator<T8>::iterator& current8)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5),
+ begin6_(g6.begin()), end6_(g6.end()), current6_(current6),
+ begin7_(g7.begin()), end7_(g7.end()), current7_(current7),
+ begin8_(g8.begin()), end8_(g8.end()), current8_(current8) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current8_;
+ if (current8_ == end8_) {
+ current8_ = begin8_;
+ ++current7_;
+ }
+ if (current7_ == end7_) {
+ current7_ = begin7_;
+ ++current6_;
+ }
+ if (current6_ == end6_) {
+ current6_ = begin6_;
+ ++current5_;
+ }
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_ &&
+ current6_ == typed_other->current6_ &&
+ current7_ == typed_other->current7_ &&
+ current8_ == typed_other->current8_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_),
+ begin6_(other.begin6_),
+ end6_(other.end6_),
+ current6_(other.current6_),
+ begin7_(other.begin7_),
+ end7_(other.end7_),
+ current7_(other.current7_),
+ begin8_(other.begin8_),
+ end8_(other.end8_),
+ current8_(other.current8_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_, *current6_, *current7_, *current8_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_ ||
+ current6_ == end6_ ||
+ current7_ == end7_ ||
+ current8_ == end8_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ const typename ParamGenerator<T6>::iterator begin6_;
+ const typename ParamGenerator<T6>::iterator end6_;
+ typename ParamGenerator<T6>::iterator current6_;
+ const typename ParamGenerator<T7>::iterator begin7_;
+ const typename ParamGenerator<T7>::iterator end7_;
+ typename ParamGenerator<T7>::iterator current7_;
+ const typename ParamGenerator<T8>::iterator begin8_;
+ const typename ParamGenerator<T8>::iterator end8_;
+ typename ParamGenerator<T8>::iterator current8_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator8::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator8& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+ const ParamGenerator<T6> g6_;
+ const ParamGenerator<T7> g7_;
+ const ParamGenerator<T8> g8_;
+}; // class CartesianProductGenerator8
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+class CartesianProductGenerator9
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5, T6,
+ T7, T8, T9> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9> ParamType;
+
+ CartesianProductGenerator9(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5,
+ const ParamGenerator<T6>& g6, const ParamGenerator<T7>& g7,
+ const ParamGenerator<T8>& g8, const ParamGenerator<T9>& g9)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8),
+ g9_(g9) {}
+ virtual ~CartesianProductGenerator9() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_,
+ g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_,
+ g8_.end(), g9_, g9_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5,
+ const ParamGenerator<T6>& g6,
+ const typename ParamGenerator<T6>::iterator& current6,
+ const ParamGenerator<T7>& g7,
+ const typename ParamGenerator<T7>::iterator& current7,
+ const ParamGenerator<T8>& g8,
+ const typename ParamGenerator<T8>::iterator& current8,
+ const ParamGenerator<T9>& g9,
+ const typename ParamGenerator<T9>::iterator& current9)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5),
+ begin6_(g6.begin()), end6_(g6.end()), current6_(current6),
+ begin7_(g7.begin()), end7_(g7.end()), current7_(current7),
+ begin8_(g8.begin()), end8_(g8.end()), current8_(current8),
+ begin9_(g9.begin()), end9_(g9.end()), current9_(current9) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current9_;
+ if (current9_ == end9_) {
+ current9_ = begin9_;
+ ++current8_;
+ }
+ if (current8_ == end8_) {
+ current8_ = begin8_;
+ ++current7_;
+ }
+ if (current7_ == end7_) {
+ current7_ = begin7_;
+ ++current6_;
+ }
+ if (current6_ == end6_) {
+ current6_ = begin6_;
+ ++current5_;
+ }
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_ &&
+ current6_ == typed_other->current6_ &&
+ current7_ == typed_other->current7_ &&
+ current8_ == typed_other->current8_ &&
+ current9_ == typed_other->current9_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_),
+ begin6_(other.begin6_),
+ end6_(other.end6_),
+ current6_(other.current6_),
+ begin7_(other.begin7_),
+ end7_(other.end7_),
+ current7_(other.current7_),
+ begin8_(other.begin8_),
+ end8_(other.end8_),
+ current8_(other.current8_),
+ begin9_(other.begin9_),
+ end9_(other.end9_),
+ current9_(other.current9_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_, *current6_, *current7_, *current8_,
+ *current9_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_ ||
+ current6_ == end6_ ||
+ current7_ == end7_ ||
+ current8_ == end8_ ||
+ current9_ == end9_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ const typename ParamGenerator<T6>::iterator begin6_;
+ const typename ParamGenerator<T6>::iterator end6_;
+ typename ParamGenerator<T6>::iterator current6_;
+ const typename ParamGenerator<T7>::iterator begin7_;
+ const typename ParamGenerator<T7>::iterator end7_;
+ typename ParamGenerator<T7>::iterator current7_;
+ const typename ParamGenerator<T8>::iterator begin8_;
+ const typename ParamGenerator<T8>::iterator end8_;
+ typename ParamGenerator<T8>::iterator current8_;
+ const typename ParamGenerator<T9>::iterator begin9_;
+ const typename ParamGenerator<T9>::iterator end9_;
+ typename ParamGenerator<T9>::iterator current9_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator9::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator9& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+ const ParamGenerator<T6> g6_;
+ const ParamGenerator<T7> g7_;
+ const ParamGenerator<T8> g8_;
+ const ParamGenerator<T9> g9_;
+}; // class CartesianProductGenerator9
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+class CartesianProductGenerator10
+ : public ParamGeneratorInterface< ::testing::tuple<T1, T2, T3, T4, T5, T6,
+ T7, T8, T9, T10> > {
+ public:
+ typedef ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> ParamType;
+
+ CartesianProductGenerator10(const ParamGenerator<T1>& g1,
+ const ParamGenerator<T2>& g2, const ParamGenerator<T3>& g3,
+ const ParamGenerator<T4>& g4, const ParamGenerator<T5>& g5,
+ const ParamGenerator<T6>& g6, const ParamGenerator<T7>& g7,
+ const ParamGenerator<T8>& g8, const ParamGenerator<T9>& g9,
+ const ParamGenerator<T10>& g10)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8),
+ g9_(g9), g10_(g10) {}
+ virtual ~CartesianProductGenerator10() {}
+
+ virtual ParamIteratorInterface<ParamType>* Begin() const {
+ return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_,
+ g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_,
+ g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin(), g10_, g10_.begin());
+ }
+ virtual ParamIteratorInterface<ParamType>* End() const {
+ return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(),
+ g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_,
+ g8_.end(), g9_, g9_.end(), g10_, g10_.end());
+ }
+
+ private:
+ class Iterator : public ParamIteratorInterface<ParamType> {
+ public:
+ Iterator(const ParamGeneratorInterface<ParamType>* base,
+ const ParamGenerator<T1>& g1,
+ const typename ParamGenerator<T1>::iterator& current1,
+ const ParamGenerator<T2>& g2,
+ const typename ParamGenerator<T2>::iterator& current2,
+ const ParamGenerator<T3>& g3,
+ const typename ParamGenerator<T3>::iterator& current3,
+ const ParamGenerator<T4>& g4,
+ const typename ParamGenerator<T4>::iterator& current4,
+ const ParamGenerator<T5>& g5,
+ const typename ParamGenerator<T5>::iterator& current5,
+ const ParamGenerator<T6>& g6,
+ const typename ParamGenerator<T6>::iterator& current6,
+ const ParamGenerator<T7>& g7,
+ const typename ParamGenerator<T7>::iterator& current7,
+ const ParamGenerator<T8>& g8,
+ const typename ParamGenerator<T8>::iterator& current8,
+ const ParamGenerator<T9>& g9,
+ const typename ParamGenerator<T9>::iterator& current9,
+ const ParamGenerator<T10>& g10,
+ const typename ParamGenerator<T10>::iterator& current10)
+ : base_(base),
+ begin1_(g1.begin()), end1_(g1.end()), current1_(current1),
+ begin2_(g2.begin()), end2_(g2.end()), current2_(current2),
+ begin3_(g3.begin()), end3_(g3.end()), current3_(current3),
+ begin4_(g4.begin()), end4_(g4.end()), current4_(current4),
+ begin5_(g5.begin()), end5_(g5.end()), current5_(current5),
+ begin6_(g6.begin()), end6_(g6.end()), current6_(current6),
+ begin7_(g7.begin()), end7_(g7.end()), current7_(current7),
+ begin8_(g8.begin()), end8_(g8.end()), current8_(current8),
+ begin9_(g9.begin()), end9_(g9.end()), current9_(current9),
+ begin10_(g10.begin()), end10_(g10.end()), current10_(current10) {
+ ComputeCurrentValue();
+ }
+ virtual ~Iterator() {}
+
+ virtual const ParamGeneratorInterface<ParamType>* BaseGenerator() const {
+ return base_;
+ }
+ // Advance should not be called on beyond-of-range iterators
+ // so no component iterators must be beyond end of range, either.
+ virtual void Advance() {
+ assert(!AtEnd());
+ ++current10_;
+ if (current10_ == end10_) {
+ current10_ = begin10_;
+ ++current9_;
+ }
+ if (current9_ == end9_) {
+ current9_ = begin9_;
+ ++current8_;
+ }
+ if (current8_ == end8_) {
+ current8_ = begin8_;
+ ++current7_;
+ }
+ if (current7_ == end7_) {
+ current7_ = begin7_;
+ ++current6_;
+ }
+ if (current6_ == end6_) {
+ current6_ = begin6_;
+ ++current5_;
+ }
+ if (current5_ == end5_) {
+ current5_ = begin5_;
+ ++current4_;
+ }
+ if (current4_ == end4_) {
+ current4_ = begin4_;
+ ++current3_;
+ }
+ if (current3_ == end3_) {
+ current3_ = begin3_;
+ ++current2_;
+ }
+ if (current2_ == end2_) {
+ current2_ = begin2_;
+ ++current1_;
+ }
+ ComputeCurrentValue();
+ }
+ virtual ParamIteratorInterface<ParamType>* Clone() const {
+ return new Iterator(*this);
+ }
+ virtual const ParamType* Current() const { return current_value_.get(); }
+ virtual bool Equals(const ParamIteratorInterface<ParamType>& other) const {
+ // Having the same base generator guarantees that the other
+ // iterator is of the same type and we can downcast.
+ GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+ << "The program attempted to compare iterators "
+ << "from different generators." << std::endl;
+ const Iterator* typed_other =
+ CheckedDowncastToActualType<const Iterator>(&other);
+ // We must report iterators equal if they both point beyond their
+ // respective ranges. That can happen in a variety of fashions,
+ // so we have to consult AtEnd().
+ return (AtEnd() && typed_other->AtEnd()) ||
+ (
+ current1_ == typed_other->current1_ &&
+ current2_ == typed_other->current2_ &&
+ current3_ == typed_other->current3_ &&
+ current4_ == typed_other->current4_ &&
+ current5_ == typed_other->current5_ &&
+ current6_ == typed_other->current6_ &&
+ current7_ == typed_other->current7_ &&
+ current8_ == typed_other->current8_ &&
+ current9_ == typed_other->current9_ &&
+ current10_ == typed_other->current10_);
+ }
+
+ private:
+ Iterator(const Iterator& other)
+ : base_(other.base_),
+ begin1_(other.begin1_),
+ end1_(other.end1_),
+ current1_(other.current1_),
+ begin2_(other.begin2_),
+ end2_(other.end2_),
+ current2_(other.current2_),
+ begin3_(other.begin3_),
+ end3_(other.end3_),
+ current3_(other.current3_),
+ begin4_(other.begin4_),
+ end4_(other.end4_),
+ current4_(other.current4_),
+ begin5_(other.begin5_),
+ end5_(other.end5_),
+ current5_(other.current5_),
+ begin6_(other.begin6_),
+ end6_(other.end6_),
+ current6_(other.current6_),
+ begin7_(other.begin7_),
+ end7_(other.end7_),
+ current7_(other.current7_),
+ begin8_(other.begin8_),
+ end8_(other.end8_),
+ current8_(other.current8_),
+ begin9_(other.begin9_),
+ end9_(other.end9_),
+ current9_(other.current9_),
+ begin10_(other.begin10_),
+ end10_(other.end10_),
+ current10_(other.current10_) {
+ ComputeCurrentValue();
+ }
+
+ void ComputeCurrentValue() {
+ if (!AtEnd())
+ current_value_.reset(new ParamType(*current1_, *current2_, *current3_,
+ *current4_, *current5_, *current6_, *current7_, *current8_,
+ *current9_, *current10_));
+ }
+ bool AtEnd() const {
+ // We must report iterator past the end of the range when either of the
+ // component iterators has reached the end of its range.
+ return
+ current1_ == end1_ ||
+ current2_ == end2_ ||
+ current3_ == end3_ ||
+ current4_ == end4_ ||
+ current5_ == end5_ ||
+ current6_ == end6_ ||
+ current7_ == end7_ ||
+ current8_ == end8_ ||
+ current9_ == end9_ ||
+ current10_ == end10_;
+ }
+
+ // No implementation - assignment is unsupported.
+ void operator=(const Iterator& other);
+
+ const ParamGeneratorInterface<ParamType>* const base_;
+ // begin[i]_ and end[i]_ define the i-th range that Iterator traverses.
+ // current[i]_ is the actual traversing iterator.
+ const typename ParamGenerator<T1>::iterator begin1_;
+ const typename ParamGenerator<T1>::iterator end1_;
+ typename ParamGenerator<T1>::iterator current1_;
+ const typename ParamGenerator<T2>::iterator begin2_;
+ const typename ParamGenerator<T2>::iterator end2_;
+ typename ParamGenerator<T2>::iterator current2_;
+ const typename ParamGenerator<T3>::iterator begin3_;
+ const typename ParamGenerator<T3>::iterator end3_;
+ typename ParamGenerator<T3>::iterator current3_;
+ const typename ParamGenerator<T4>::iterator begin4_;
+ const typename ParamGenerator<T4>::iterator end4_;
+ typename ParamGenerator<T4>::iterator current4_;
+ const typename ParamGenerator<T5>::iterator begin5_;
+ const typename ParamGenerator<T5>::iterator end5_;
+ typename ParamGenerator<T5>::iterator current5_;
+ const typename ParamGenerator<T6>::iterator begin6_;
+ const typename ParamGenerator<T6>::iterator end6_;
+ typename ParamGenerator<T6>::iterator current6_;
+ const typename ParamGenerator<T7>::iterator begin7_;
+ const typename ParamGenerator<T7>::iterator end7_;
+ typename ParamGenerator<T7>::iterator current7_;
+ const typename ParamGenerator<T8>::iterator begin8_;
+ const typename ParamGenerator<T8>::iterator end8_;
+ typename ParamGenerator<T8>::iterator current8_;
+ const typename ParamGenerator<T9>::iterator begin9_;
+ const typename ParamGenerator<T9>::iterator end9_;
+ typename ParamGenerator<T9>::iterator current9_;
+ const typename ParamGenerator<T10>::iterator begin10_;
+ const typename ParamGenerator<T10>::iterator end10_;
+ typename ParamGenerator<T10>::iterator current10_;
+ linked_ptr<ParamType> current_value_;
+ }; // class CartesianProductGenerator10::Iterator
+
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductGenerator10& other);
+
+ const ParamGenerator<T1> g1_;
+ const ParamGenerator<T2> g2_;
+ const ParamGenerator<T3> g3_;
+ const ParamGenerator<T4> g4_;
+ const ParamGenerator<T5> g5_;
+ const ParamGenerator<T6> g6_;
+ const ParamGenerator<T7> g7_;
+ const ParamGenerator<T8> g8_;
+ const ParamGenerator<T9> g9_;
+ const ParamGenerator<T10> g10_;
+}; // class CartesianProductGenerator10
+
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Helper classes providing Combine() with polymorphic features. They allow
+// casting CartesianProductGeneratorN<T> to ParamGenerator<U> if T is
+// convertible to U.
+//
+template <class Generator1, class Generator2>
+class CartesianProductHolder2 {
+ public:
+CartesianProductHolder2(const Generator1& g1, const Generator2& g2)
+ : g1_(g1), g2_(g2) {}
+ template <typename T1, typename T2>
+ operator ParamGenerator< ::testing::tuple<T1, T2> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2> >(
+ new CartesianProductGenerator2<T1, T2>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder2& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+}; // class CartesianProductHolder2
+
+template <class Generator1, class Generator2, class Generator3>
+class CartesianProductHolder3 {
+ public:
+CartesianProductHolder3(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3)
+ : g1_(g1), g2_(g2), g3_(g3) {}
+ template <typename T1, typename T2, typename T3>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3> >(
+ new CartesianProductGenerator3<T1, T2, T3>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder3& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+}; // class CartesianProductHolder3
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4>
+class CartesianProductHolder4 {
+ public:
+CartesianProductHolder4(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {}
+ template <typename T1, typename T2, typename T3, typename T4>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4> >(
+ new CartesianProductGenerator4<T1, T2, T3, T4>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder4& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+}; // class CartesianProductHolder4
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5>
+class CartesianProductHolder5 {
+ public:
+CartesianProductHolder5(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5> >(
+ new CartesianProductGenerator5<T1, T2, T3, T4, T5>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder5& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+}; // class CartesianProductHolder5
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5, class Generator6>
+class CartesianProductHolder6 {
+ public:
+CartesianProductHolder6(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5,
+ const Generator6& g6)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6> >(
+ new CartesianProductGenerator6<T1, T2, T3, T4, T5, T6>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_),
+ static_cast<ParamGenerator<T6> >(g6_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder6& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+ const Generator6 g6_;
+}; // class CartesianProductHolder6
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5, class Generator6, class Generator7>
+class CartesianProductHolder7 {
+ public:
+CartesianProductHolder7(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5,
+ const Generator6& g6, const Generator7& g7)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6,
+ T7> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7> >(
+ new CartesianProductGenerator7<T1, T2, T3, T4, T5, T6, T7>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_),
+ static_cast<ParamGenerator<T6> >(g6_),
+ static_cast<ParamGenerator<T7> >(g7_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder7& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+ const Generator6 g6_;
+ const Generator7 g7_;
+}; // class CartesianProductHolder7
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5, class Generator6, class Generator7,
+ class Generator8>
+class CartesianProductHolder8 {
+ public:
+CartesianProductHolder8(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5,
+ const Generator6& g6, const Generator7& g7, const Generator8& g8)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7),
+ g8_(g8) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7,
+ T8> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8> >(
+ new CartesianProductGenerator8<T1, T2, T3, T4, T5, T6, T7, T8>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_),
+ static_cast<ParamGenerator<T6> >(g6_),
+ static_cast<ParamGenerator<T7> >(g7_),
+ static_cast<ParamGenerator<T8> >(g8_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder8& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+ const Generator6 g6_;
+ const Generator7 g7_;
+ const Generator8 g8_;
+}; // class CartesianProductHolder8
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5, class Generator6, class Generator7,
+ class Generator8, class Generator9>
+class CartesianProductHolder9 {
+ public:
+CartesianProductHolder9(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5,
+ const Generator6& g6, const Generator7& g7, const Generator8& g8,
+ const Generator9& g9)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8),
+ g9_(g9) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8,
+ T9> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8,
+ T9> >(
+ new CartesianProductGenerator9<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_),
+ static_cast<ParamGenerator<T6> >(g6_),
+ static_cast<ParamGenerator<T7> >(g7_),
+ static_cast<ParamGenerator<T8> >(g8_),
+ static_cast<ParamGenerator<T9> >(g9_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder9& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+ const Generator6 g6_;
+ const Generator7 g7_;
+ const Generator8 g8_;
+ const Generator9 g9_;
+}; // class CartesianProductHolder9
+
+template <class Generator1, class Generator2, class Generator3,
+ class Generator4, class Generator5, class Generator6, class Generator7,
+ class Generator8, class Generator9, class Generator10>
+class CartesianProductHolder10 {
+ public:
+CartesianProductHolder10(const Generator1& g1, const Generator2& g2,
+ const Generator3& g3, const Generator4& g4, const Generator5& g5,
+ const Generator6& g6, const Generator7& g7, const Generator8& g8,
+ const Generator9& g9, const Generator10& g10)
+ : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8),
+ g9_(g9), g10_(g10) {}
+ template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+ operator ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9,
+ T10> >() const {
+ return ParamGenerator< ::testing::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9,
+ T10> >(
+ new CartesianProductGenerator10<T1, T2, T3, T4, T5, T6, T7, T8, T9,
+ T10>(
+ static_cast<ParamGenerator<T1> >(g1_),
+ static_cast<ParamGenerator<T2> >(g2_),
+ static_cast<ParamGenerator<T3> >(g3_),
+ static_cast<ParamGenerator<T4> >(g4_),
+ static_cast<ParamGenerator<T5> >(g5_),
+ static_cast<ParamGenerator<T6> >(g6_),
+ static_cast<ParamGenerator<T7> >(g7_),
+ static_cast<ParamGenerator<T8> >(g8_),
+ static_cast<ParamGenerator<T9> >(g9_),
+ static_cast<ParamGenerator<T10> >(g10_)));
+ }
+
+ private:
+ // No implementation - assignment is unsupported.
+ void operator=(const CartesianProductHolder10& other);
+
+ const Generator1 g1_;
+ const Generator2 g2_;
+ const Generator3 g3_;
+ const Generator4 g4_;
+ const Generator5 g5_;
+ const Generator6 g6_;
+ const Generator7 g7_;
+ const Generator8 g8_;
+ const Generator9 g9_;
+ const Generator10 g10_;
+}; // class CartesianProductHolder10
+
+# endif // GTEST_HAS_COMBINE
+
+} // namespace internal
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_
+
+namespace testing {
+
+// Functions producing parameter generators.
+//
+// Google Test uses these generators to produce parameters for value-
+// parameterized tests. When a parameterized test case is instantiated
+// with a particular generator, Google Test creates and runs tests
+// for each element in the sequence produced by the generator.
+//
+// In the following sample, tests from test case FooTest are instantiated
+// each three times with parameter values 3, 5, and 8:
+//
+// class FooTest : public TestWithParam<int> { ... };
+//
+// TEST_P(FooTest, TestThis) {
+// }
+// TEST_P(FooTest, TestThat) {
+// }
+// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8));
+//
+
+// Range() returns generators providing sequences of values in a range.
+//
+// Synopsis:
+// Range(start, end)
+// - returns a generator producing a sequence of values {start, start+1,
+// start+2, ..., }.
+// Range(start, end, step)
+// - returns a generator producing a sequence of values {start, start+step,
+// start+step+step, ..., }.
+// Notes:
+// * The generated sequences never include end. For example, Range(1, 5)
+// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2)
+// returns a generator producing {1, 3, 5, 7}.
+// * start and end must have the same type. That type may be any integral or
+// floating-point type or a user defined type satisfying these conditions:
+// * It must be assignable (have operator=() defined).
+// * It must have operator+() (operator+(int-compatible type) for
+// two-operand version).
+// * It must have operator<() defined.
+// Elements in the resulting sequences will also have that type.
+// * Condition start < end must be satisfied in order for resulting sequences
+// to contain any elements.
+//
+template <typename T, typename IncrementT>
+internal::ParamGenerator<T> Range(T start, T end, IncrementT step) {
+ return internal::ParamGenerator<T>(
+ new internal::RangeGenerator<T, IncrementT>(start, end, step));
+}
+
+template <typename T>
+internal::ParamGenerator<T> Range(T start, T end) {
+ return Range(start, end, 1);
+}
+
+// ValuesIn() function allows generation of tests with parameters coming from
+// a container.
+//
+// Synopsis:
+// ValuesIn(const T (&array)[N])
+// - returns a generator producing sequences with elements from
+// a C-style array.
+// ValuesIn(const Container& container)
+// - returns a generator producing sequences with elements from
+// an STL-style container.
+// ValuesIn(Iterator begin, Iterator end)
+// - returns a generator producing sequences with elements from
+// a range [begin, end) defined by a pair of STL-style iterators. These
+// iterators can also be plain C pointers.
+//
+// Please note that ValuesIn copies the values from the containers
+// passed in and keeps them to generate tests in RUN_ALL_TESTS().
+//
+// Examples:
+//
+// This instantiates tests from test case StringTest
+// each with C-string values of "foo", "bar", and "baz":
+//
+// const char* strings[] = {"foo", "bar", "baz"};
+// INSTANTIATE_TEST_CASE_P(StringSequence, StringTest, ValuesIn(strings));
+//
+// This instantiates tests from test case StlStringTest
+// each with STL strings with values "a" and "b":
+//
+// ::std::vector< ::std::string> GetParameterStrings() {
+// ::std::vector< ::std::string> v;
+// v.push_back("a");
+// v.push_back("b");
+// return v;
+// }
+//
+// INSTANTIATE_TEST_CASE_P(CharSequence,
+// StlStringTest,
+// ValuesIn(GetParameterStrings()));
+//
+//
+// This will also instantiate tests from CharTest
+// each with parameter values 'a' and 'b':
+//
+// ::std::list<char> GetParameterChars() {
+// ::std::list<char> list;
+// list.push_back('a');
+// list.push_back('b');
+// return list;
+// }
+// ::std::list<char> l = GetParameterChars();
+// INSTANTIATE_TEST_CASE_P(CharSequence2,
+// CharTest,
+// ValuesIn(l.begin(), l.end()));
+//
+template <typename ForwardIterator>
+internal::ParamGenerator<
+ typename ::testing::internal::IteratorTraits<ForwardIterator>::value_type>
+ValuesIn(ForwardIterator begin, ForwardIterator end) {
+ typedef typename ::testing::internal::IteratorTraits<ForwardIterator>
+ ::value_type ParamType;
+ return internal::ParamGenerator<ParamType>(
+ new internal::ValuesInIteratorRangeGenerator<ParamType>(begin, end));
+}
+
+template <typename T, size_t N>
+internal::ParamGenerator<T> ValuesIn(const T (&array)[N]) {
+ return ValuesIn(array, array + N);
+}
+
+template <class Container>
+internal::ParamGenerator<typename Container::value_type> ValuesIn(
+ const Container& container) {
+ return ValuesIn(container.begin(), container.end());
+}
+
+// Values() allows generating tests from explicitly specified list of
+// parameters.
+//
+// Synopsis:
+// Values(T v1, T v2, ..., T vN)
+// - returns a generator producing sequences with elements v1, v2, ..., vN.
+//
+// For example, this instantiates tests from test case BarTest each
+// with values "one", "two", and "three":
+//
+// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three"));
+//
+// This instantiates tests from test case BazTest each with values 1, 2, 3.5.
+// The exact type of values will depend on the type of parameter in BazTest.
+//
+// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5));
+//
+// Currently, Values() supports from 1 to 50 parameters.
+//
+template <typename T1>
+internal::ValueArray1<T1> Values(T1 v1) {
+ return internal::ValueArray1<T1>(v1);
+}
+
+template <typename T1, typename T2>
+internal::ValueArray2<T1, T2> Values(T1 v1, T2 v2) {
+ return internal::ValueArray2<T1, T2>(v1, v2);
+}
+
+template <typename T1, typename T2, typename T3>
+internal::ValueArray3<T1, T2, T3> Values(T1 v1, T2 v2, T3 v3) {
+ return internal::ValueArray3<T1, T2, T3>(v1, v2, v3);
+}
+
+template <typename T1, typename T2, typename T3, typename T4>
+internal::ValueArray4<T1, T2, T3, T4> Values(T1 v1, T2 v2, T3 v3, T4 v4) {
+ return internal::ValueArray4<T1, T2, T3, T4>(v1, v2, v3, v4);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+internal::ValueArray5<T1, T2, T3, T4, T5> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5) {
+ return internal::ValueArray5<T1, T2, T3, T4, T5>(v1, v2, v3, v4, v5);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+internal::ValueArray6<T1, T2, T3, T4, T5, T6> Values(T1 v1, T2 v2, T3 v3,
+ T4 v4, T5 v5, T6 v6) {
+ return internal::ValueArray6<T1, T2, T3, T4, T5, T6>(v1, v2, v3, v4, v5, v6);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7>
+internal::ValueArray7<T1, T2, T3, T4, T5, T6, T7> Values(T1 v1, T2 v2, T3 v3,
+ T4 v4, T5 v5, T6 v6, T7 v7) {
+ return internal::ValueArray7<T1, T2, T3, T4, T5, T6, T7>(v1, v2, v3, v4, v5,
+ v6, v7);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8>
+internal::ValueArray8<T1, T2, T3, T4, T5, T6, T7, T8> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) {
+ return internal::ValueArray8<T1, T2, T3, T4, T5, T6, T7, T8>(v1, v2, v3, v4,
+ v5, v6, v7, v8);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9>
+internal::ValueArray9<T1, T2, T3, T4, T5, T6, T7, T8, T9> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) {
+ return internal::ValueArray9<T1, T2, T3, T4, T5, T6, T7, T8, T9>(v1, v2, v3,
+ v4, v5, v6, v7, v8, v9);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10>
+internal::ValueArray10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Values(T1 v1,
+ T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) {
+ return internal::ValueArray10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(v1,
+ v2, v3, v4, v5, v6, v7, v8, v9, v10);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11>
+internal::ValueArray11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10,
+ T11> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11) {
+ return internal::ValueArray11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10,
+ T11>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12>
+internal::ValueArray12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12) {
+ return internal::ValueArray12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13>
+internal::ValueArray13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
+ T13> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13) {
+ return internal::ValueArray13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14>
+internal::ValueArray14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) {
+ return internal::ValueArray14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13,
+ v14);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15>
+internal::ValueArray15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8,
+ T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) {
+ return internal::ValueArray15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12,
+ v13, v14, v15);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16>
+internal::ValueArray16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16) {
+ return internal::ValueArray16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11,
+ v12, v13, v14, v15, v16);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17>
+internal::ValueArray17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17) {
+ return internal::ValueArray17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10,
+ v11, v12, v13, v14, v15, v16, v17);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18>
+internal::ValueArray18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6,
+ T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18) {
+ return internal::ValueArray18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18>(v1, v2, v3, v4, v5, v6, v7, v8, v9,
+ v10, v11, v12, v13, v14, v15, v16, v17, v18);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19>
+internal::ValueArray19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5,
+ T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14,
+ T15 v15, T16 v16, T17 v17, T18 v18, T19 v19) {
+ return internal::ValueArray19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19>(v1, v2, v3, v4, v5, v6, v7, v8,
+ v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20>
+internal::ValueArray20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13,
+ T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20) {
+ return internal::ValueArray20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20>(v1, v2, v3, v4, v5, v6, v7,
+ v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21>
+internal::ValueArray21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13,
+ T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21) {
+ return internal::ValueArray21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21>(v1, v2, v3, v4, v5, v6,
+ v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22>
+internal::ValueArray22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22> Values(T1 v1, T2 v2, T3 v3,
+ T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22) {
+ return internal::ValueArray22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22>(v1, v2, v3, v4,
+ v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
+ v20, v21, v22);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23>
+internal::ValueArray23<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22, T23 v23) {
+ return internal::ValueArray23<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23>(v1, v2, v3,
+ v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
+ v20, v21, v22, v23);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24>
+internal::ValueArray24<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22, T23 v23, T24 v24) {
+ return internal::ValueArray24<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24>(v1, v2,
+ v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18,
+ v19, v20, v21, v22, v23, v24);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25>
+internal::ValueArray25<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25> Values(T1 v1,
+ T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11,
+ T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19,
+ T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25) {
+ return internal::ValueArray25<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25>(v1,
+ v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17,
+ v18, v19, v20, v21, v22, v23, v24, v25);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26>
+internal::ValueArray26<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26) {
+ return internal::ValueArray26<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15,
+ v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27>
+internal::ValueArray27<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
+ T27> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27) {
+ return internal::ValueArray27<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14,
+ v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28>
+internal::ValueArray28<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27,
+ T28> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28) {
+ return internal::ValueArray28<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13,
+ v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27,
+ v28);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29>
+internal::ValueArray29<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29) {
+ return internal::ValueArray29<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12,
+ v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26,
+ v27, v28, v29);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30>
+internal::ValueArray30<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8,
+ T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16,
+ T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24,
+ T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) {
+ return internal::ValueArray30<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11,
+ v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25,
+ v26, v27, v28, v29, v30);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31>
+internal::ValueArray31<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) {
+ return internal::ValueArray31<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10,
+ v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24,
+ v25, v26, v27, v28, v29, v30, v31);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32>
+internal::ValueArray32<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31,
+ T32 v32) {
+ return internal::ValueArray32<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32>(v1, v2, v3, v4, v5, v6, v7, v8, v9,
+ v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23,
+ v24, v25, v26, v27, v28, v29, v30, v31, v32);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33>
+internal::ValueArray33<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6,
+ T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31,
+ T32 v32, T33 v33) {
+ return internal::ValueArray33<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33>(v1, v2, v3, v4, v5, v6, v7, v8,
+ v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23,
+ v24, v25, v26, v27, v28, v29, v30, v31, v32, v33);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34>
+internal::ValueArray34<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5,
+ T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14,
+ T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22,
+ T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30,
+ T31 v31, T32 v32, T33 v33, T34 v34) {
+ return internal::ValueArray34<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34>(v1, v2, v3, v4, v5, v6, v7,
+ v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22,
+ v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35>
+internal::ValueArray35<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13,
+ T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21,
+ T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29,
+ T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35) {
+ return internal::ValueArray35<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35>(v1, v2, v3, v4, v5, v6,
+ v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21,
+ v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36>
+internal::ValueArray36<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13,
+ T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21,
+ T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29,
+ T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36) {
+ return internal::ValueArray36<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36>(v1, v2, v3, v4,
+ v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
+ v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33,
+ v34, v35, v36);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37>
+internal::ValueArray37<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37> Values(T1 v1, T2 v2, T3 v3,
+ T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28,
+ T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36,
+ T37 v37) {
+ return internal::ValueArray37<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37>(v1, v2, v3,
+ v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
+ v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33,
+ v34, v35, v36, v37);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38>
+internal::ValueArray38<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28,
+ T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36,
+ T37 v37, T38 v38) {
+ return internal::ValueArray38<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38>(v1, v2,
+ v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18,
+ v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32,
+ v33, v34, v35, v36, v37, v38);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39>
+internal::ValueArray39<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39> Values(T1 v1, T2 v2,
+ T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12,
+ T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20,
+ T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28,
+ T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36,
+ T37 v37, T38 v38, T39 v39) {
+ return internal::ValueArray39<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39>(v1,
+ v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17,
+ v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31,
+ v32, v33, v34, v35, v36, v37, v38, v39);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40>
+internal::ValueArray40<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40> Values(T1 v1,
+ T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11,
+ T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19,
+ T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27,
+ T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35,
+ T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) {
+ return internal::ValueArray40<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15,
+ v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29,
+ v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41>
+internal::ValueArray41<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
+ T41> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41) {
+ return internal::ValueArray41<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14,
+ v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28,
+ v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42>
+internal::ValueArray42<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41,
+ T42> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42) {
+ return internal::ValueArray42<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13,
+ v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27,
+ v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41,
+ v42);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43>
+internal::ValueArray43<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42,
+ T43> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43) {
+ return internal::ValueArray43<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12,
+ v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26,
+ v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40,
+ v41, v42, v43);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44>
+internal::ValueArray44<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9,
+ T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17,
+ T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25,
+ T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33,
+ T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41,
+ T42 v42, T43 v43, T44 v44) {
+ return internal::ValueArray44<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11,
+ v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25,
+ v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39,
+ v40, v41, v42, v43, v44);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45>
+internal::ValueArray45<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8,
+ T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16,
+ T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24,
+ T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32,
+ T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40,
+ T41 v41, T42 v42, T43 v43, T44 v44, T45 v45) {
+ return internal::ValueArray45<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10,
+ v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24,
+ v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38,
+ v39, v40, v41, v42, v43, v44, v45);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46>
+internal::ValueArray46<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31,
+ T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39,
+ T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) {
+ return internal::ValueArray46<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45, T46>(v1, v2, v3, v4, v5, v6, v7, v8, v9,
+ v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23,
+ v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37,
+ v38, v39, v40, v41, v42, v43, v44, v45, v46);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47>
+internal::ValueArray47<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7,
+ T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31,
+ T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39,
+ T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) {
+ return internal::ValueArray47<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45, T46, T47>(v1, v2, v3, v4, v5, v6, v7, v8,
+ v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23,
+ v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37,
+ v38, v39, v40, v41, v42, v43, v44, v45, v46, v47);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48>
+internal::ValueArray48<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6,
+ T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15,
+ T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23,
+ T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31,
+ T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39,
+ T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47,
+ T48 v48) {
+ return internal::ValueArray48<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45, T46, T47, T48>(v1, v2, v3, v4, v5, v6, v7,
+ v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22,
+ v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36,
+ v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49>
+internal::ValueArray49<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48, T49> Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5,
+ T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14,
+ T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22,
+ T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30,
+ T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38,
+ T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46,
+ T47 v47, T48 v48, T49 v49) {
+ return internal::ValueArray49<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45, T46, T47, T48, T49>(v1, v2, v3, v4, v5, v6,
+ v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21,
+ v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35,
+ v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6, typename T7, typename T8, typename T9, typename T10,
+ typename T11, typename T12, typename T13, typename T14, typename T15,
+ typename T16, typename T17, typename T18, typename T19, typename T20,
+ typename T21, typename T22, typename T23, typename T24, typename T25,
+ typename T26, typename T27, typename T28, typename T29, typename T30,
+ typename T31, typename T32, typename T33, typename T34, typename T35,
+ typename T36, typename T37, typename T38, typename T39, typename T40,
+ typename T41, typename T42, typename T43, typename T44, typename T45,
+ typename T46, typename T47, typename T48, typename T49, typename T50>
+internal::ValueArray50<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
+ T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28,
+ T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40, T41, T42, T43,
+ T44, T45, T46, T47, T48, T49, T50> Values(T1 v1, T2 v2, T3 v3, T4 v4,
+ T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13,
+ T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21,
+ T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29,
+ T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37,
+ T38 v38, T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45,
+ T46 v46, T47 v47, T48 v48, T49 v49, T50 v50) {
+ return internal::ValueArray50<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,
+ T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25,
+ T26, T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39,
+ T40, T41, T42, T43, T44, T45, T46, T47, T48, T49, T50>(v1, v2, v3, v4,
+ v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
+ v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33,
+ v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47,
+ v48, v49, v50);
+}
+
+// Bool() allows generating tests with parameters in a set of (false, true).
+//
+// Synopsis:
+// Bool()
+// - returns a generator producing sequences with elements {false, true}.
+//
+// It is useful when testing code that depends on Boolean flags. Combinations
+// of multiple flags can be tested when several Bool()'s are combined using
+// Combine() function.
+//
+// In the following example all tests in the test case FlagDependentTest
+// will be instantiated twice with parameters false and true.
+//
+// class FlagDependentTest : public testing::TestWithParam<bool> {
+// virtual void SetUp() {
+// external_flag = GetParam();
+// }
+// }
+// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool());
+//
+inline internal::ParamGenerator<bool> Bool() {
+ return Values(false, true);
+}
+
+# if GTEST_HAS_COMBINE
+// Combine() allows the user to combine two or more sequences to produce
+// values of a Cartesian product of those sequences' elements.
+//
+// Synopsis:
+// Combine(gen1, gen2, ..., genN)
+// - returns a generator producing sequences with elements coming from
+// the Cartesian product of elements from the sequences generated by
+// gen1, gen2, ..., genN. The sequence elements will have a type of
+// tuple<T1, T2, ..., TN> where T1, T2, ..., TN are the types
+// of elements from sequences produces by gen1, gen2, ..., genN.
+//
+// Combine can have up to 10 arguments. This number is currently limited
+// by the maximum number of elements in the tuple implementation used by Google
+// Test.
+//
+// Example:
+//
+// This will instantiate tests in test case AnimalTest each one with
+// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
+// tuple("dog", BLACK), and tuple("dog", WHITE):
+//
+// enum Color { BLACK, GRAY, WHITE };
+// class AnimalTest
+// : public testing::TestWithParam<tuple<const char*, Color> > {...};
+//
+// TEST_P(AnimalTest, AnimalLooksNice) {...}
+//
+// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest,
+// Combine(Values("cat", "dog"),
+// Values(BLACK, WHITE)));
+//
+// This will instantiate tests in FlagDependentTest with all variations of two
+// Boolean flags:
+//
+// class FlagDependentTest
+// : public testing::TestWithParam<tuple<bool, bool> > {
+// virtual void SetUp() {
+// // Assigns external_flag_1 and external_flag_2 values from the tuple.
+// tie(external_flag_1, external_flag_2) = GetParam();
+// }
+// };
+//
+// TEST_P(FlagDependentTest, TestFeature1) {
+// // Test your code using external_flag_1 and external_flag_2 here.
+// }
+// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest,
+// Combine(Bool(), Bool()));
+//
+template <typename Generator1, typename Generator2>
+internal::CartesianProductHolder2<Generator1, Generator2> Combine(
+ const Generator1& g1, const Generator2& g2) {
+ return internal::CartesianProductHolder2<Generator1, Generator2>(
+ g1, g2);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3>
+internal::CartesianProductHolder3<Generator1, Generator2, Generator3> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3) {
+ return internal::CartesianProductHolder3<Generator1, Generator2, Generator3>(
+ g1, g2, g3);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4>
+internal::CartesianProductHolder4<Generator1, Generator2, Generator3,
+ Generator4> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4) {
+ return internal::CartesianProductHolder4<Generator1, Generator2, Generator3,
+ Generator4>(
+ g1, g2, g3, g4);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5>
+internal::CartesianProductHolder5<Generator1, Generator2, Generator3,
+ Generator4, Generator5> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5) {
+ return internal::CartesianProductHolder5<Generator1, Generator2, Generator3,
+ Generator4, Generator5>(
+ g1, g2, g3, g4, g5);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5, typename Generator6>
+internal::CartesianProductHolder6<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5, const Generator6& g6) {
+ return internal::CartesianProductHolder6<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6>(
+ g1, g2, g3, g4, g5, g6);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5, typename Generator6,
+ typename Generator7>
+internal::CartesianProductHolder7<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5, const Generator6& g6,
+ const Generator7& g7) {
+ return internal::CartesianProductHolder7<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7>(
+ g1, g2, g3, g4, g5, g6, g7);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5, typename Generator6,
+ typename Generator7, typename Generator8>
+internal::CartesianProductHolder8<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5, const Generator6& g6,
+ const Generator7& g7, const Generator8& g8) {
+ return internal::CartesianProductHolder8<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8>(
+ g1, g2, g3, g4, g5, g6, g7, g8);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5, typename Generator6,
+ typename Generator7, typename Generator8, typename Generator9>
+internal::CartesianProductHolder9<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8,
+ Generator9> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5, const Generator6& g6,
+ const Generator7& g7, const Generator8& g8, const Generator9& g9) {
+ return internal::CartesianProductHolder9<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8, Generator9>(
+ g1, g2, g3, g4, g5, g6, g7, g8, g9);
+}
+
+template <typename Generator1, typename Generator2, typename Generator3,
+ typename Generator4, typename Generator5, typename Generator6,
+ typename Generator7, typename Generator8, typename Generator9,
+ typename Generator10>
+internal::CartesianProductHolder10<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8, Generator9,
+ Generator10> Combine(
+ const Generator1& g1, const Generator2& g2, const Generator3& g3,
+ const Generator4& g4, const Generator5& g5, const Generator6& g6,
+ const Generator7& g7, const Generator8& g8, const Generator9& g9,
+ const Generator10& g10) {
+ return internal::CartesianProductHolder10<Generator1, Generator2, Generator3,
+ Generator4, Generator5, Generator6, Generator7, Generator8, Generator9,
+ Generator10>(
+ g1, g2, g3, g4, g5, g6, g7, g8, g9, g10);
+}
+# endif // GTEST_HAS_COMBINE
+
+# define TEST_P(test_case_name, test_name) \
+ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
+ : public test_case_name { \
+ public: \
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \
+ virtual void TestBody(); \
+ private: \
+ static int AddToRegistry() { \
+ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \
+ GetTestCasePatternHolder<test_case_name>(\
+ #test_case_name, \
+ ::testing::internal::CodeLocation(\
+ __FILE__, __LINE__))->AddTestPattern(\
+ GTEST_STRINGIFY_(test_case_name), \
+ GTEST_STRINGIFY_(test_name), \
+ new ::testing::internal::TestMetaFactory< \
+ GTEST_TEST_CLASS_NAME_(\
+ test_case_name, test_name)>()); \
+ return 0; \
+ } \
+ static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(\
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \
+ }; \
+ int GTEST_TEST_CLASS_NAME_(test_case_name, \
+ test_name)::gtest_registering_dummy_ = \
+ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \
+ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
+
+// The optional last argument to INSTANTIATE_TEST_CASE_P allows the user
+// to specify a function or functor that generates custom test name suffixes
+// based on the test parameters. The function should accept one argument of
+// type testing::TestParamInfo<class ParamType>, and return std::string.
+//
+// testing::PrintToStringParamName is a builtin test suffix generator that
+// returns the value of testing::PrintToString(GetParam()).
+//
+// Note: test names must be non-empty, unique, and may only contain ASCII
+// alphanumeric characters or underscore. Because PrintToString adds quotes
+// to std::string and C strings, it won't work for these types.
+
+# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator, ...) \
+ static ::testing::internal::ParamGenerator<test_case_name::ParamType> \
+ gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \
+ static ::std::string gtest_##prefix##test_case_name##_EvalGenerateName_( \
+ const ::testing::TestParamInfo<test_case_name::ParamType>& info) { \
+ return ::testing::internal::GetParamNameGen<test_case_name::ParamType> \
+ (__VA_ARGS__)(info); \
+ } \
+ static int gtest_##prefix##test_case_name##_dummy_ GTEST_ATTRIBUTE_UNUSED_ = \
+ ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \
+ GetTestCasePatternHolder<test_case_name>(\
+ #test_case_name, \
+ ::testing::internal::CodeLocation(\
+ __FILE__, __LINE__))->AddTestCaseInstantiation(\
+ #prefix, \
+ &gtest_##prefix##test_case_name##_EvalGenerator_, \
+ &gtest_##prefix##test_case_name##_EvalGenerateName_, \
+ __FILE__, __LINE__)
+
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+// Copyright 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Google C++ Testing and Mocking Framework definitions useful in production code.
+// GOOGLETEST_CM0003 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_
+#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_
+
+// When you need to test the private or protected members of a class,
+// use the FRIEND_TEST macro to declare your tests as friends of the
+// class. For example:
+//
+// class MyClass {
+// private:
+// void PrivateMethod();
+// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
+// };
+//
+// class MyClassTest : public testing::Test {
+// // ...
+// };
+//
+// TEST_F(MyClassTest, PrivateMethodWorks) {
+// // Can call MyClass::PrivateMethod() here.
+// }
+//
+// Note: The test class must be in the same namespace as the class being tested.
+// For example, putting MyClassTest in an anonymous namespace will not work.
+
+#define FRIEND_TEST(test_case_name, test_name)\
+friend class test_case_name##_##test_name##_Test
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+
+#include <iosfwd>
+#include <vector>
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// A copyable object representing the result of a test part (i.e. an
+// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()).
+//
+// Don't inherit from TestPartResult as its destructor is not virtual.
+class GTEST_API_ TestPartResult {
+ public:
+ // The possible outcomes of a test part (i.e. an assertion or an
+ // explicit SUCCEED(), FAIL(), or ADD_FAILURE()).
+ enum Type {
+ kSuccess, // Succeeded.
+ kNonFatalFailure, // Failed but the test can continue.
+ kFatalFailure // Failed and the test should be terminated.
+ };
+
+ // C'tor. TestPartResult does NOT have a default constructor.
+ // Always use this constructor (with parameters) to create a
+ // TestPartResult object.
+ TestPartResult(Type a_type,
+ const char* a_file_name,
+ int a_line_number,
+ const char* a_message)
+ : type_(a_type),
+ file_name_(a_file_name == NULL ? "" : a_file_name),
+ line_number_(a_line_number),
+ summary_(ExtractSummary(a_message)),
+ message_(a_message) {
+ }
+
+ // Gets the outcome of the test part.
+ Type type() const { return type_; }
+
+ // Gets the name of the source file where the test part took place, or
+ // NULL if it's unknown.
+ const char* file_name() const {
+ return file_name_.empty() ? NULL : file_name_.c_str();
+ }
+
+ // Gets the line in the source file where the test part took place,
+ // or -1 if it's unknown.
+ int line_number() const { return line_number_; }
+
+ // Gets the summary of the failure message.
+ const char* summary() const { return summary_.c_str(); }
+
+ // Gets the message associated with the test part.
+ const char* message() const { return message_.c_str(); }
+
+ // Returns true iff the test part passed.
+ bool passed() const { return type_ == kSuccess; }
+
+ // Returns true iff the test part failed.
+ bool failed() const { return type_ != kSuccess; }
+
+ // Returns true iff the test part non-fatally failed.
+ bool nonfatally_failed() const { return type_ == kNonFatalFailure; }
+
+ // Returns true iff the test part fatally failed.
+ bool fatally_failed() const { return type_ == kFatalFailure; }
+
+ private:
+ Type type_;
+
+ // Gets the summary of the failure message by omitting the stack
+ // trace in it.
+ static std::string ExtractSummary(const char* message);
+
+ // The name of the source file where the test part took place, or
+ // "" if the source file is unknown.
+ std::string file_name_;
+ // The line in the source file where the test part took place, or -1
+ // if the line number is unknown.
+ int line_number_;
+ std::string summary_; // The test failure summary.
+ std::string message_; // The test failure message.
+};
+
+// Prints a TestPartResult object.
+std::ostream& operator<<(std::ostream& os, const TestPartResult& result);
+
+// An array of TestPartResult objects.
+//
+// Don't inherit from TestPartResultArray as its destructor is not
+// virtual.
+class GTEST_API_ TestPartResultArray {
+ public:
+ TestPartResultArray() {}
+
+ // Appends the given TestPartResult to the array.
+ void Append(const TestPartResult& result);
+
+ // Returns the TestPartResult at the given index (0-based).
+ const TestPartResult& GetTestPartResult(int index) const;
+
+ // Returns the number of TestPartResult objects in the array.
+ int size() const;
+
+ private:
+ std::vector<TestPartResult> array_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray);
+};
+
+// This interface knows how to report a test part result.
+class GTEST_API_ TestPartResultReporterInterface {
+ public:
+ virtual ~TestPartResultReporterInterface() {}
+
+ virtual void ReportTestPartResult(const TestPartResult& result) = 0;
+};
+
+namespace internal {
+
+// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a
+// statement generates new fatal failures. To do so it registers itself as the
+// current test part result reporter. Besides checking if fatal failures were
+// reported, it only delegates the reporting to the former result reporter.
+// The original result reporter is restored in the destructor.
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+class GTEST_API_ HasNewFatalFailureHelper
+ : public TestPartResultReporterInterface {
+ public:
+ HasNewFatalFailureHelper();
+ virtual ~HasNewFatalFailureHelper();
+ virtual void ReportTestPartResult(const TestPartResult& result);
+ bool has_new_fatal_failure() const { return has_new_fatal_failure_; }
+ private:
+ bool has_new_fatal_failure_;
+ TestPartResultReporterInterface* original_reporter_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper);
+};
+
+} // namespace internal
+
+} // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+
+// This header implements typed tests and type-parameterized tests.
+
+// Typed (aka type-driven) tests repeat the same test for types in a
+// list. You must know which types you want to test with when writing
+// typed tests. Here's how you do it:
+
+#if 0
+
+// First, define a fixture class template. It should be parameterized
+// by a type. Remember to derive it from testing::Test.
+template <typename T>
+class FooTest : public testing::Test {
+ public:
+ ...
+ typedef std::list<T> List;
+ static T shared_;
+ T value_;
+};
+
+// Next, associate a list of types with the test case, which will be
+// repeated for each type in the list. The typedef is necessary for
+// the macro to parse correctly.
+typedef testing::Types<char, int, unsigned int> MyTypes;
+TYPED_TEST_CASE(FooTest, MyTypes);
+
+// If the type list contains only one type, you can write that type
+// directly without Types<...>:
+// TYPED_TEST_CASE(FooTest, int);
+
+// Then, use TYPED_TEST() instead of TEST_F() to define as many typed
+// tests for this test case as you want.
+TYPED_TEST(FooTest, DoesBlah) {
+ // Inside a test, refer to TypeParam to get the type parameter.
+ // Since we are inside a derived class template, C++ requires use to
+ // visit the members of FooTest via 'this'.
+ TypeParam n = this->value_;
+
+ // To visit static members of the fixture, add the TestFixture::
+ // prefix.
+ n += TestFixture::shared_;
+
+ // To refer to typedefs in the fixture, add the "typename
+ // TestFixture::" prefix.
+ typename TestFixture::List values;
+ values.push_back(n);
+ ...
+}
+
+TYPED_TEST(FooTest, HasPropertyA) { ... }
+
+// TYPED_TEST_CASE takes an optional third argument which allows to specify a
+// class that generates custom test name suffixes based on the type. This should
+// be a class which has a static template function GetName(int index) returning
+// a string for each type. The provided integer index equals the index of the
+// type in the provided type list. In many cases the index can be ignored.
+//
+// For example:
+// class MyTypeNames {
+// public:
+// template <typename T>
+// static std::string GetName(int) {
+// if (std::is_same<T, char>()) return "char";
+// if (std::is_same<T, int>()) return "int";
+// if (std::is_same<T, unsigned int>()) return "unsignedInt";
+// }
+// };
+// TYPED_TEST_CASE(FooTest, MyTypes, MyTypeNames);
+
+#endif // 0
+
+// Type-parameterized tests are abstract test patterns parameterized
+// by a type. Compared with typed tests, type-parameterized tests
+// allow you to define the test pattern without knowing what the type
+// parameters are. The defined pattern can be instantiated with
+// different types any number of times, in any number of translation
+// units.
+//
+// If you are designing an interface or concept, you can define a
+// suite of type-parameterized tests to verify properties that any
+// valid implementation of the interface/concept should have. Then,
+// each implementation can easily instantiate the test suite to verify
+// that it conforms to the requirements, without having to write
+// similar tests repeatedly. Here's an example:
+
+#if 0
+
+// First, define a fixture class template. It should be parameterized
+// by a type. Remember to derive it from testing::Test.
+template <typename T>
+class FooTest : public testing::Test {
+ ...
+};
+
+// Next, declare that you will define a type-parameterized test case
+// (the _P suffix is for "parameterized" or "pattern", whichever you
+// prefer):
+TYPED_TEST_CASE_P(FooTest);
+
+// Then, use TYPED_TEST_P() to define as many type-parameterized tests
+// for this type-parameterized test case as you want.
+TYPED_TEST_P(FooTest, DoesBlah) {
+ // Inside a test, refer to TypeParam to get the type parameter.
+ TypeParam n = 0;
+ ...
+}
+
+TYPED_TEST_P(FooTest, HasPropertyA) { ... }
+
+// Now the tricky part: you need to register all test patterns before
+// you can instantiate them. The first argument of the macro is the
+// test case name; the rest are the names of the tests in this test
+// case.
+REGISTER_TYPED_TEST_CASE_P(FooTest,
+ DoesBlah, HasPropertyA);
+
+// Finally, you are free to instantiate the pattern with the types you
+// want. If you put the above code in a header file, you can #include
+// it in multiple C++ source files and instantiate it multiple times.
+//
+// To distinguish different instances of the pattern, the first
+// argument to the INSTANTIATE_* macro is a prefix that will be added
+// to the actual test case name. Remember to pick unique prefixes for
+// different instances.
+typedef testing::Types<char, int, unsigned int> MyTypes;
+INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes);
+
+// If the type list contains only one type, you can write that type
+// directly without Types<...>:
+// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int);
+//
+// Similar to the optional argument of TYPED_TEST_CASE above,
+// INSTANTIATE_TEST_CASE_P takes an optional fourth argument which allows to
+// generate custom names.
+// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes, MyTypeNames);
+
+#endif // 0
+
+
+// Implements typed tests.
+
+#if GTEST_HAS_TYPED_TEST
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the name of the typedef for the type parameters of the
+// given test case.
+# define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_
+
+// Expands to the name of the typedef for the NameGenerator, responsible for
+// creating the suffixes of the name.
+#define GTEST_NAME_GENERATOR_(TestCaseName) \
+ gtest_type_params_##TestCaseName##_NameGenerator
+
+// The 'Types' template argument below must have spaces around it
+// since some compilers may choke on '>>' when passing a template
+// instance (e.g. Types<int>)
+# define TYPED_TEST_CASE(CaseName, Types, ...) \
+ typedef ::testing::internal::TypeList< Types >::type GTEST_TYPE_PARAMS_( \
+ CaseName); \
+ typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \
+ GTEST_NAME_GENERATOR_(CaseName)
+
+# define TYPED_TEST(CaseName, TestName) \
+ template <typename gtest_TypeParam_> \
+ class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \
+ : public CaseName<gtest_TypeParam_> { \
+ private: \
+ typedef CaseName<gtest_TypeParam_> TestFixture; \
+ typedef gtest_TypeParam_ TypeParam; \
+ virtual void TestBody(); \
+ }; \
+ static bool gtest_##CaseName##_##TestName##_registered_ \
+ GTEST_ATTRIBUTE_UNUSED_ = \
+ ::testing::internal::TypeParameterizedTest< \
+ CaseName, \
+ ::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(CaseName, \
+ TestName)>, \
+ GTEST_TYPE_PARAMS_( \
+ CaseName)>::Register("", \
+ ::testing::internal::CodeLocation( \
+ __FILE__, __LINE__), \
+ #CaseName, #TestName, 0, \
+ ::testing::internal::GenerateNames< \
+ GTEST_NAME_GENERATOR_(CaseName), \
+ GTEST_TYPE_PARAMS_(CaseName)>()); \
+ template <typename gtest_TypeParam_> \
+ void GTEST_TEST_CLASS_NAME_(CaseName, \
+ TestName)<gtest_TypeParam_>::TestBody()
+
+#endif // GTEST_HAS_TYPED_TEST
+
+// Implements type-parameterized tests.
+
+#if GTEST_HAS_TYPED_TEST_P
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the namespace name that the type-parameterized tests for
+// the given type-parameterized test case are defined in. The exact
+// name of the namespace is subject to change without notice.
+# define GTEST_CASE_NAMESPACE_(TestCaseName) \
+ gtest_case_##TestCaseName##_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the name of the variable used to remember the names of
+// the defined tests in the given test case.
+# define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \
+ gtest_typed_test_case_p_state_##TestCaseName##_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY.
+//
+// Expands to the name of the variable used to remember the names of
+// the registered tests in the given test case.
+# define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \
+ gtest_registered_test_names_##TestCaseName##_
+
+// The variables defined in the type-parameterized test macros are
+// static as typically these macros are used in a .h file that can be
+// #included in multiple translation units linked together.
+# define TYPED_TEST_CASE_P(CaseName) \
+ static ::testing::internal::TypedTestCasePState \
+ GTEST_TYPED_TEST_CASE_P_STATE_(CaseName)
+
+# define TYPED_TEST_P(CaseName, TestName) \
+ namespace GTEST_CASE_NAMESPACE_(CaseName) { \
+ template <typename gtest_TypeParam_> \
+ class TestName : public CaseName<gtest_TypeParam_> { \
+ private: \
+ typedef CaseName<gtest_TypeParam_> TestFixture; \
+ typedef gtest_TypeParam_ TypeParam; \
+ virtual void TestBody(); \
+ }; \
+ static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \
+ GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\
+ __FILE__, __LINE__, #CaseName, #TestName); \
+ } \
+ template <typename gtest_TypeParam_> \
+ void GTEST_CASE_NAMESPACE_(CaseName)::TestName<gtest_TypeParam_>::TestBody()
+
+# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \
+ namespace GTEST_CASE_NAMESPACE_(CaseName) { \
+ typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \
+ } \
+ static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) \
+ GTEST_ATTRIBUTE_UNUSED_ = \
+ GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames( \
+ __FILE__, __LINE__, #__VA_ARGS__)
+
+// The 'Types' template argument below must have spaces around it
+// since some compilers may choke on '>>' when passing a template
+// instance (e.g. Types<int>)
+# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types, ...) \
+ static bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \
+ ::testing::internal::TypeParameterizedTestCase< \
+ CaseName, GTEST_CASE_NAMESPACE_(CaseName)::gtest_AllTests_, \
+ ::testing::internal::TypeList< Types >::type>:: \
+ Register(#Prefix, \
+ ::testing::internal::CodeLocation(__FILE__, __LINE__), \
+ &GTEST_TYPED_TEST_CASE_P_STATE_(CaseName), #CaseName, \
+ GTEST_REGISTERED_TEST_NAMES_(CaseName), \
+ ::testing::internal::GenerateNames< \
+ ::testing::internal::NameGeneratorSelector< \
+ __VA_ARGS__>::type, \
+ ::testing::internal::TypeList< Types >::type>())
+
+#endif // GTEST_HAS_TYPED_TEST_P
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// Depending on the platform, different string classes are available.
+// On Linux, in addition to ::std::string, Google also makes use of
+// class ::string, which has the same interface as ::std::string, but
+// has a different implementation.
+//
+// You can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that
+// ::string is available AND is a distinct type to ::std::string, or
+// define it to 0 to indicate otherwise.
+//
+// If ::std::string and ::string are the same class on your platform
+// due to aliasing, you should define GTEST_HAS_GLOBAL_STRING to 0.
+//
+// If you do not define GTEST_HAS_GLOBAL_STRING, it is defined
+// heuristically.
+
+namespace testing {
+
+// Silence C4100 (unreferenced formal parameter) and 4805
+// unsafe mix of type 'const int' and type 'const bool'
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4805)
+# pragma warning(disable:4100)
+#endif
+
+
+// Declares the flags.
+
+// This flag temporary enables the disabled tests.
+GTEST_DECLARE_bool_(also_run_disabled_tests);
+
+// This flag brings the debugger on an assertion failure.
+GTEST_DECLARE_bool_(break_on_failure);
+
+// This flag controls whether Google Test catches all test-thrown exceptions
+// and logs them as failures.
+GTEST_DECLARE_bool_(catch_exceptions);
+
+// This flag enables using colors in terminal output. Available values are
+// "yes" to enable colors, "no" (disable colors), or "auto" (the default)
+// to let Google Test decide.
+GTEST_DECLARE_string_(color);
+
+// This flag sets up the filter to select by name using a glob pattern
+// the tests to run. If the filter is not given all tests are executed.
+GTEST_DECLARE_string_(filter);
+
+// This flag controls whether Google Test installs a signal handler that dumps
+// debugging information when fatal signals are raised.
+GTEST_DECLARE_bool_(install_failure_signal_handler);
+
+// This flag causes the Google Test to list tests. None of the tests listed
+// are actually run if the flag is provided.
+GTEST_DECLARE_bool_(list_tests);
+
+// This flag controls whether Google Test emits a detailed XML report to a file
+// in addition to its normal textual output.
+GTEST_DECLARE_string_(output);
+
+// This flags control whether Google Test prints the elapsed time for each
+// test.
+GTEST_DECLARE_bool_(print_time);
+
+// This flags control whether Google Test prints UTF8 characters as text.
+GTEST_DECLARE_bool_(print_utf8);
+
+// This flag specifies the random number seed.
+GTEST_DECLARE_int32_(random_seed);
+
+// This flag sets how many times the tests are repeated. The default value
+// is 1. If the value is -1 the tests are repeating forever.
+GTEST_DECLARE_int32_(repeat);
+
+// This flag controls whether Google Test includes Google Test internal
+// stack frames in failure stack traces.
+GTEST_DECLARE_bool_(show_internal_stack_frames);
+
+// When this flag is specified, tests' order is randomized on every iteration.
+GTEST_DECLARE_bool_(shuffle);
+
+// This flag specifies the maximum number of stack frames to be
+// printed in a failure message.
+GTEST_DECLARE_int32_(stack_trace_depth);
+
+// When this flag is specified, a failed assertion will throw an
+// exception if exceptions are enabled, or exit the program with a
+// non-zero code otherwise. For use with an external test framework.
+GTEST_DECLARE_bool_(throw_on_failure);
+
+// When this flag is set with a "host:port" string, on supported
+// platforms test results are streamed to the specified port on
+// the specified host machine.
+GTEST_DECLARE_string_(stream_result_to);
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+GTEST_DECLARE_string_(flagfile);
+#endif // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+// The upper limit for valid stack trace depths.
+const int kMaxStackTraceDepth = 100;
+
+namespace internal {
+
+class AssertHelper;
+class DefaultGlobalTestPartResultReporter;
+class ExecDeathTest;
+class NoExecDeathTest;
+class FinalSuccessChecker;
+class GTestFlagSaver;
+class StreamingListenerTest;
+class TestResultAccessor;
+class TestEventListenersAccessor;
+class TestEventRepeater;
+class UnitTestRecordPropertyTestHelper;
+class WindowsDeathTest;
+class FuchsiaDeathTest;
+class UnitTestImpl* GetUnitTestImpl();
+void ReportFailureInUnknownLocation(TestPartResult::Type result_type,
+ const std::string& message);
+
+} // namespace internal
+
+// The friend relationship of some of these classes is cyclic.
+// If we don't forward declare them the compiler might confuse the classes
+// in friendship clauses with same named classes on the scope.
+class Test;
+class TestCase;
+class TestInfo;
+class UnitTest;
+
+// A class for indicating whether an assertion was successful. When
+// the assertion wasn't successful, the AssertionResult object
+// remembers a non-empty message that describes how it failed.
+//
+// To create an instance of this class, use one of the factory functions
+// (AssertionSuccess() and AssertionFailure()).
+//
+// This class is useful for two purposes:
+// 1. Defining predicate functions to be used with Boolean test assertions
+// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts
+// 2. Defining predicate-format functions to be
+// used with predicate assertions (ASSERT_PRED_FORMAT*, etc).
+//
+// For example, if you define IsEven predicate:
+//
+// testing::AssertionResult IsEven(int n) {
+// if ((n % 2) == 0)
+// return testing::AssertionSuccess();
+// else
+// return testing::AssertionFailure() << n << " is odd";
+// }
+//
+// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5)))
+// will print the message
+//
+// Value of: IsEven(Fib(5))
+// Actual: false (5 is odd)
+// Expected: true
+//
+// instead of a more opaque
+//
+// Value of: IsEven(Fib(5))
+// Actual: false
+// Expected: true
+//
+// in case IsEven is a simple Boolean predicate.
+//
+// If you expect your predicate to be reused and want to support informative
+// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up
+// about half as often as positive ones in our tests), supply messages for
+// both success and failure cases:
+//
+// testing::AssertionResult IsEven(int n) {
+// if ((n % 2) == 0)
+// return testing::AssertionSuccess() << n << " is even";
+// else
+// return testing::AssertionFailure() << n << " is odd";
+// }
+//
+// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print
+//
+// Value of: IsEven(Fib(6))
+// Actual: true (8 is even)
+// Expected: false
+//
+// NB: Predicates that support negative Boolean assertions have reduced
+// performance in positive ones so be careful not to use them in tests
+// that have lots (tens of thousands) of positive Boolean assertions.
+//
+// To use this class with EXPECT_PRED_FORMAT assertions such as:
+//
+// // Verifies that Foo() returns an even number.
+// EXPECT_PRED_FORMAT1(IsEven, Foo());
+//
+// you need to define:
+//
+// testing::AssertionResult IsEven(const char* expr, int n) {
+// if ((n % 2) == 0)
+// return testing::AssertionSuccess();
+// else
+// return testing::AssertionFailure()
+// << "Expected: " << expr << " is even\n Actual: it's " << n;
+// }
+//
+// If Foo() returns 5, you will see the following message:
+//
+// Expected: Foo() is even
+// Actual: it's 5
+//
+class GTEST_API_ AssertionResult {
+ public:
+ // Copy constructor.
+ // Used in EXPECT_TRUE/FALSE(assertion_result).
+ AssertionResult(const AssertionResult& other);
+
+#if defined(_MSC_VER) && _MSC_VER < 1910
+ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */)
+#endif
+
+ // Used in the EXPECT_TRUE/FALSE(bool_expression).
+ //
+ // T must be contextually convertible to bool.
+ //
+ // The second parameter prevents this overload from being considered if
+ // the argument is implicitly convertible to AssertionResult. In that case
+ // we want AssertionResult's copy constructor to be used.
+ template <typename T>
+ explicit AssertionResult(
+ const T& success,
+ typename internal::EnableIf<
+ !internal::ImplicitlyConvertible<T, AssertionResult>::value>::type*
+ /*enabler*/ = NULL)
+ : success_(success) {}
+
+#if defined(_MSC_VER) && _MSC_VER < 1910
+ GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+ // Assignment operator.
+ AssertionResult& operator=(AssertionResult other) {
+ swap(other);
+ return *this;
+ }
+
+ // Returns true iff the assertion succeeded.
+ operator bool() const { return success_; } // NOLINT
+
+ // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
+ AssertionResult operator!() const;
+
+ // Returns the text streamed into this AssertionResult. Test assertions
+ // use it when they fail (i.e., the predicate's outcome doesn't match the
+ // assertion's expectation). When nothing has been streamed into the
+ // object, returns an empty string.
+ const char* message() const {
+ return message_.get() != NULL ? message_->c_str() : "";
+ }
+ // FIXME: Remove this after making sure no clients use it.
+ // Deprecated; please use message() instead.
+ const char* failure_message() const { return message(); }
+
+ // Streams a custom failure message into this object.
+ template <typename T> AssertionResult& operator<<(const T& value) {
+ AppendMessage(Message() << value);
+ return *this;
+ }
+
+ // Allows streaming basic output manipulators such as endl or flush into
+ // this object.
+ AssertionResult& operator<<(
+ ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) {
+ AppendMessage(Message() << basic_manipulator);
+ return *this;
+ }
+
+ private:
+ // Appends the contents of message to message_.
+ void AppendMessage(const Message& a_message) {
+ if (message_.get() == NULL)
+ message_.reset(new ::std::string);
+ message_->append(a_message.GetString().c_str());
+ }
+
+ // Swap the contents of this AssertionResult with other.
+ void swap(AssertionResult& other);
+
+ // Stores result of the assertion predicate.
+ bool success_;
+ // Stores the message describing the condition in case the expectation
+ // construct is not satisfied with the predicate's outcome.
+ // Referenced via a pointer to avoid taking too much stack frame space
+ // with test assertions.
+ internal::scoped_ptr< ::std::string> message_;
+};
+
+// Makes a successful assertion result.
+GTEST_API_ AssertionResult AssertionSuccess();
+
+// Makes a failed assertion result.
+GTEST_API_ AssertionResult AssertionFailure();
+
+// Makes a failed assertion result with the given failure message.
+// Deprecated; use AssertionFailure() << msg.
+GTEST_API_ AssertionResult AssertionFailure(const Message& msg);
+
+} // namespace testing
+
+// Includes the auto-generated header that implements a family of generic
+// predicate assertion macros. This include comes late because it relies on
+// APIs declared above.
+// Copyright 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file is AUTOMATICALLY GENERATED on 01/02/2018 by command
+// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND!
+//
+// Implements a family of generic predicate assertion macros.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+
+
+namespace testing {
+
+// This header implements a family of generic predicate assertion
+// macros:
+//
+// ASSERT_PRED_FORMAT1(pred_format, v1)
+// ASSERT_PRED_FORMAT2(pred_format, v1, v2)
+// ...
+//
+// where pred_format is a function or functor that takes n (in the
+// case of ASSERT_PRED_FORMATn) values and their source expression
+// text, and returns a testing::AssertionResult. See the definition
+// of ASSERT_EQ in gtest.h for an example.
+//
+// If you don't care about formatting, you can use the more
+// restrictive version:
+//
+// ASSERT_PRED1(pred, v1)
+// ASSERT_PRED2(pred, v1, v2)
+// ...
+//
+// where pred is an n-ary function or functor that returns bool,
+// and the values v1, v2, ..., must support the << operator for
+// streaming to std::ostream.
+//
+// We also define the EXPECT_* variations.
+//
+// For now we only support predicates whose arity is at most 5.
+
+// GTEST_ASSERT_ is the basic statement to which all of the assertions
+// in this file reduce. Don't use this in your code.
+
+#define GTEST_ASSERT_(expression, on_failure) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (const ::testing::AssertionResult gtest_ar = (expression)) \
+ ; \
+ else \
+ on_failure(gtest_ar.failure_message())
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use
+// this in your code.
+template <typename Pred,
+ typename T1>
+AssertionResult AssertPred1Helper(const char* pred_text,
+ const char* e1,
+ Pred pred,
+ const T1& v1) {
+ if (pred(v1)) return AssertionSuccess();
+
+ return AssertionFailure() << pred_text << "("
+ << e1 << ") evaluates to false, where"
+ << "\n" << e1 << " evaluates to " << v1;
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\
+ GTEST_ASSERT_(pred_format(#v1, v1), \
+ on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use
+// this in your code.
+#define GTEST_PRED1_(pred, v1, on_failure)\
+ GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \
+ #v1, \
+ pred, \
+ v1), on_failure)
+
+// Unary predicate assertion macros.
+#define EXPECT_PRED_FORMAT1(pred_format, v1) \
+ GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED1(pred, v1) \
+ GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT1(pred_format, v1) \
+ GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED1(pred, v1) \
+ GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use
+// this in your code.
+template <typename Pred,
+ typename T1,
+ typename T2>
+AssertionResult AssertPred2Helper(const char* pred_text,
+ const char* e1,
+ const char* e2,
+ Pred pred,
+ const T1& v1,
+ const T2& v2) {
+ if (pred(v1, v2)) return AssertionSuccess();
+
+ return AssertionFailure() << pred_text << "("
+ << e1 << ", "
+ << e2 << ") evaluates to false, where"
+ << "\n" << e1 << " evaluates to " << v1
+ << "\n" << e2 << " evaluates to " << v2;
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\
+ GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \
+ on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use
+// this in your code.
+#define GTEST_PRED2_(pred, v1, v2, on_failure)\
+ GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \
+ #v1, \
+ #v2, \
+ pred, \
+ v1, \
+ v2), on_failure)
+
+// Binary predicate assertion macros.
+#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
+ GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED2(pred, v1, v2) \
+ GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
+ GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED2(pred, v1, v2) \
+ GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use
+// this in your code.
+template <typename Pred,
+ typename T1,
+ typename T2,
+ typename T3>
+AssertionResult AssertPred3Helper(const char* pred_text,
+ const char* e1,
+ const char* e2,
+ const char* e3,
+ Pred pred,
+ const T1& v1,
+ const T2& v2,
+ const T3& v3) {
+ if (pred(v1, v2, v3)) return AssertionSuccess();
+
+ return AssertionFailure() << pred_text << "("
+ << e1 << ", "
+ << e2 << ", "
+ << e3 << ") evaluates to false, where"
+ << "\n" << e1 << " evaluates to " << v1
+ << "\n" << e2 << " evaluates to " << v2
+ << "\n" << e3 << " evaluates to " << v3;
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\
+ GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \
+ on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use
+// this in your code.
+#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\
+ GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \
+ #v1, \
+ #v2, \
+ #v3, \
+ pred, \
+ v1, \
+ v2, \
+ v3), on_failure)
+
+// Ternary predicate assertion macros.
+#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \
+ GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED3(pred, v1, v2, v3) \
+ GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \
+ GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED3(pred, v1, v2, v3) \
+ GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use
+// this in your code.
+template <typename Pred,
+ typename T1,
+ typename T2,
+ typename T3,
+ typename T4>
+AssertionResult AssertPred4Helper(const char* pred_text,
+ const char* e1,
+ const char* e2,
+ const char* e3,
+ const char* e4,
+ Pred pred,
+ const T1& v1,
+ const T2& v2,
+ const T3& v3,
+ const T4& v4) {
+ if (pred(v1, v2, v3, v4)) return AssertionSuccess();
+
+ return AssertionFailure() << pred_text << "("
+ << e1 << ", "
+ << e2 << ", "
+ << e3 << ", "
+ << e4 << ") evaluates to false, where"
+ << "\n" << e1 << " evaluates to " << v1
+ << "\n" << e2 << " evaluates to " << v2
+ << "\n" << e3 << " evaluates to " << v3
+ << "\n" << e4 << " evaluates to " << v4;
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\
+ GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \
+ on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use
+// this in your code.
+#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\
+ GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \
+ #v1, \
+ #v2, \
+ #v3, \
+ #v4, \
+ pred, \
+ v1, \
+ v2, \
+ v3, \
+ v4), on_failure)
+
+// 4-ary predicate assertion macros.
+#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
+ GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED4(pred, v1, v2, v3, v4) \
+ GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
+ GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED4(pred, v1, v2, v3, v4) \
+ GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use
+// this in your code.
+template <typename Pred,
+ typename T1,
+ typename T2,
+ typename T3,
+ typename T4,
+ typename T5>
+AssertionResult AssertPred5Helper(const char* pred_text,
+ const char* e1,
+ const char* e2,
+ const char* e3,
+ const char* e4,
+ const char* e5,
+ Pred pred,
+ const T1& v1,
+ const T2& v2,
+ const T3& v3,
+ const T4& v4,
+ const T5& v5) {
+ if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess();
+
+ return AssertionFailure() << pred_text << "("
+ << e1 << ", "
+ << e2 << ", "
+ << e3 << ", "
+ << e4 << ", "
+ << e5 << ") evaluates to false, where"
+ << "\n" << e1 << " evaluates to " << v1
+ << "\n" << e2 << " evaluates to " << v2
+ << "\n" << e3 << " evaluates to " << v3
+ << "\n" << e4 << " evaluates to " << v4
+ << "\n" << e5 << " evaluates to " << v5;
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\
+ GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \
+ on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use
+// this in your code.
+#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\
+ GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \
+ #v1, \
+ #v2, \
+ #v3, \
+ #v4, \
+ #v5, \
+ pred, \
+ v1, \
+ v2, \
+ v3, \
+ v4, \
+ v5), on_failure)
+
+// 5-ary predicate assertion macros.
+#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
+ GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \
+ GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
+ GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \
+ GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
+
+
+
+} // namespace testing
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+
+namespace testing {
+
+// The abstract class that all tests inherit from.
+//
+// In Google Test, a unit test program contains one or many TestCases, and
+// each TestCase contains one or many Tests.
+//
+// When you define a test using the TEST macro, you don't need to
+// explicitly derive from Test - the TEST macro automatically does
+// this for you.
+//
+// The only time you derive from Test is when defining a test fixture
+// to be used in a TEST_F. For example:
+//
+// class FooTest : public testing::Test {
+// protected:
+// void SetUp() override { ... }
+// void TearDown() override { ... }
+// ...
+// };
+//
+// TEST_F(FooTest, Bar) { ... }
+// TEST_F(FooTest, Baz) { ... }
+//
+// Test is not copyable.
+class GTEST_API_ Test {
+ public:
+ friend class TestInfo;
+
+ // Defines types for pointers to functions that set up and tear down
+ // a test case.
+ typedef internal::SetUpTestCaseFunc SetUpTestCaseFunc;
+ typedef internal::TearDownTestCaseFunc TearDownTestCaseFunc;
+
+ // The d'tor is virtual as we intend to inherit from Test.
+ virtual ~Test();
+
+ // Sets up the stuff shared by all tests in this test case.
+ //
+ // Google Test will call Foo::SetUpTestCase() before running the first
+ // test in test case Foo. Hence a sub-class can define its own
+ // SetUpTestCase() method to shadow the one defined in the super
+ // class.
+ static void SetUpTestCase() {}
+
+ // Tears down the stuff shared by all tests in this test case.
+ //
+ // Google Test will call Foo::TearDownTestCase() after running the last
+ // test in test case Foo. Hence a sub-class can define its own
+ // TearDownTestCase() method to shadow the one defined in the super
+ // class.
+ static void TearDownTestCase() {}
+
+ // Returns true iff the current test has a fatal failure.
+ static bool HasFatalFailure();
+
+ // Returns true iff the current test has a non-fatal failure.
+ static bool HasNonfatalFailure();
+
+ // Returns true iff the current test has a (either fatal or
+ // non-fatal) failure.
+ static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); }
+
+ // Logs a property for the current test, test case, or for the entire
+ // invocation of the test program when used outside of the context of a
+ // test case. Only the last value for a given key is remembered. These
+ // are public static so they can be called from utility functions that are
+ // not members of the test fixture. Calls to RecordProperty made during
+ // lifespan of the test (from the moment its constructor starts to the
+ // moment its destructor finishes) will be output in XML as attributes of
+ // the <testcase> element. Properties recorded from fixture's
+ // SetUpTestCase or TearDownTestCase are logged as attributes of the
+ // corresponding <testsuite> element. Calls to RecordProperty made in the
+ // global context (before or after invocation of RUN_ALL_TESTS and from
+ // SetUp/TearDown method of Environment objects registered with Google
+ // Test) will be output as attributes of the <testsuites> element.
+ static void RecordProperty(const std::string& key, const std::string& value);
+ static void RecordProperty(const std::string& key, int value);
+
+ protected:
+ // Creates a Test object.
+ Test();
+
+ // Sets up the test fixture.
+ virtual void SetUp();
+
+ // Tears down the test fixture.
+ virtual void TearDown();
+
+ private:
+ // Returns true iff the current test has the same fixture class as
+ // the first test in the current test case.
+ static bool HasSameFixtureClass();
+
+ // Runs the test after the test fixture has been set up.
+ //
+ // A sub-class must implement this to define the test logic.
+ //
+ // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM.
+ // Instead, use the TEST or TEST_F macro.
+ virtual void TestBody() = 0;
+
+ // Sets up, executes, and tears down the test.
+ void Run();
+
+ // Deletes self. We deliberately pick an unusual name for this
+ // internal method to avoid clashing with names used in user TESTs.
+ void DeleteSelf_() { delete this; }
+
+ const internal::scoped_ptr< GTEST_FLAG_SAVER_ > gtest_flag_saver_;
+
+ // Often a user misspells SetUp() as Setup() and spends a long time
+ // wondering why it is never called by Google Test. The declaration of
+ // the following method is solely for catching such an error at
+ // compile time:
+ //
+ // - The return type is deliberately chosen to be not void, so it
+ // will be a conflict if void Setup() is declared in the user's
+ // test fixture.
+ //
+ // - This method is private, so it will be another compiler error
+ // if the method is called from the user's test fixture.
+ //
+ // DO NOT OVERRIDE THIS FUNCTION.
+ //
+ // If you see an error about overriding the following function or
+ // about it being private, you have mis-spelled SetUp() as Setup().
+ struct Setup_should_be_spelled_SetUp {};
+ virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; }
+
+ // We disallow copying Tests.
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(Test);
+};
+
+typedef internal::TimeInMillis TimeInMillis;
+
+// A copyable object representing a user specified test property which can be
+// output as a key/value string pair.
+//
+// Don't inherit from TestProperty as its destructor is not virtual.
+class TestProperty {
+ public:
+ // C'tor. TestProperty does NOT have a default constructor.
+ // Always use this constructor (with parameters) to create a
+ // TestProperty object.
+ TestProperty(const std::string& a_key, const std::string& a_value) :
+ key_(a_key), value_(a_value) {
+ }
+
+ // Gets the user supplied key.
+ const char* key() const {
+ return key_.c_str();
+ }
+
+ // Gets the user supplied value.
+ const char* value() const {
+ return value_.c_str();
+ }
+
+ // Sets a new value, overriding the one supplied in the constructor.
+ void SetValue(const std::string& new_value) {
+ value_ = new_value;
+ }
+
+ private:
+ // The key supplied by the user.
+ std::string key_;
+ // The value supplied by the user.
+ std::string value_;
+};
+
+// The result of a single Test. This includes a list of
+// TestPartResults, a list of TestProperties, a count of how many
+// death tests there are in the Test, and how much time it took to run
+// the Test.
+//
+// TestResult is not copyable.
+class GTEST_API_ TestResult {
+ public:
+ // Creates an empty TestResult.
+ TestResult();
+
+ // D'tor. Do not inherit from TestResult.
+ ~TestResult();
+
+ // Gets the number of all test parts. This is the sum of the number
+ // of successful test parts and the number of failed test parts.
+ int total_part_count() const;
+
+ // Returns the number of the test properties.
+ int test_property_count() const;
+
+ // Returns true iff the test passed (i.e. no test part failed).
+ bool Passed() const { return !Failed(); }
+
+ // Returns true iff the test failed.
+ bool Failed() const;
+
+ // Returns true iff the test fatally failed.
+ bool HasFatalFailure() const;
+
+ // Returns true iff the test has a non-fatal failure.
+ bool HasNonfatalFailure() const;
+
+ // Returns the elapsed time, in milliseconds.
+ TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+ // Returns the i-th test part result among all the results. i can range from 0
+ // to total_part_count() - 1. If i is not in that range, aborts the program.
+ const TestPartResult& GetTestPartResult(int i) const;
+
+ // Returns the i-th test property. i can range from 0 to
+ // test_property_count() - 1. If i is not in that range, aborts the
+ // program.
+ const TestProperty& GetTestProperty(int i) const;
+
+ private:
+ friend class TestInfo;
+ friend class TestCase;
+ friend class UnitTest;
+ friend class internal::DefaultGlobalTestPartResultReporter;
+ friend class internal::ExecDeathTest;
+ friend class internal::TestResultAccessor;
+ friend class internal::UnitTestImpl;
+ friend class internal::WindowsDeathTest;
+ friend class internal::FuchsiaDeathTest;
+
+ // Gets the vector of TestPartResults.
+ const std::vector<TestPartResult>& test_part_results() const {
+ return test_part_results_;
+ }
+
+ // Gets the vector of TestProperties.
+ const std::vector<TestProperty>& test_properties() const {
+ return test_properties_;
+ }
+
+ // Sets the elapsed time.
+ void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; }
+
+ // Adds a test property to the list. The property is validated and may add
+ // a non-fatal failure if invalid (e.g., if it conflicts with reserved
+ // key names). If a property is already recorded for the same key, the
+ // value will be updated, rather than storing multiple values for the same
+ // key. xml_element specifies the element for which the property is being
+ // recorded and is used for validation.
+ void RecordProperty(const std::string& xml_element,
+ const TestProperty& test_property);
+
+ // Adds a failure if the key is a reserved attribute of Google Test
+ // testcase tags. Returns true if the property is valid.
+ // FIXME: Validate attribute names are legal and human readable.
+ static bool ValidateTestProperty(const std::string& xml_element,
+ const TestProperty& test_property);
+
+ // Adds a test part result to the list.
+ void AddTestPartResult(const TestPartResult& test_part_result);
+
+ // Returns the death test count.
+ int death_test_count() const { return death_test_count_; }
+
+ // Increments the death test count, returning the new count.
+ int increment_death_test_count() { return ++death_test_count_; }
+
+ // Clears the test part results.
+ void ClearTestPartResults();
+
+ // Clears the object.
+ void Clear();
+
+ // Protects mutable state of the property vector and of owned
+ // properties, whose values may be updated.
+ internal::Mutex test_properites_mutex_;
+
+ // The vector of TestPartResults
+ std::vector<TestPartResult> test_part_results_;
+ // The vector of TestProperties
+ std::vector<TestProperty> test_properties_;
+ // Running count of death tests.
+ int death_test_count_;
+ // The elapsed time, in milliseconds.
+ TimeInMillis elapsed_time_;
+
+ // We disallow copying TestResult.
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult);
+}; // class TestResult
+
+// A TestInfo object stores the following information about a test:
+//
+// Test case name
+// Test name
+// Whether the test should be run
+// A function pointer that creates the test object when invoked
+// Test result
+//
+// The constructor of TestInfo registers itself with the UnitTest
+// singleton such that the RUN_ALL_TESTS() macro knows which tests to
+// run.
+class GTEST_API_ TestInfo {
+ public:
+ // Destructs a TestInfo object. This function is not virtual, so
+ // don't inherit from TestInfo.
+ ~TestInfo();
+
+ // Returns the test case name.
+ const char* test_case_name() const { return test_case_name_.c_str(); }
+
+ // Returns the test name.
+ const char* name() const { return name_.c_str(); }
+
+ // Returns the name of the parameter type, or NULL if this is not a typed
+ // or a type-parameterized test.
+ const char* type_param() const {
+ if (type_param_.get() != NULL)
+ return type_param_->c_str();
+ return NULL;
+ }
+
+ // Returns the text representation of the value parameter, or NULL if this
+ // is not a value-parameterized test.
+ const char* value_param() const {
+ if (value_param_.get() != NULL)
+ return value_param_->c_str();
+ return NULL;
+ }
+
+ // Returns the file name where this test is defined.
+ const char* file() const { return location_.file.c_str(); }
+
+ // Returns the line where this test is defined.
+ int line() const { return location_.line; }
+
+ // Return true if this test should not be run because it's in another shard.
+ bool is_in_another_shard() const { return is_in_another_shard_; }
+
+ // Returns true if this test should run, that is if the test is not
+ // disabled (or it is disabled but the also_run_disabled_tests flag has
+ // been specified) and its full name matches the user-specified filter.
+ //
+ // Google Test allows the user to filter the tests by their full names.
+ // The full name of a test Bar in test case Foo is defined as
+ // "Foo.Bar". Only the tests that match the filter will run.
+ //
+ // A filter is a colon-separated list of glob (not regex) patterns,
+ // optionally followed by a '-' and a colon-separated list of
+ // negative patterns (tests to exclude). A test is run if it
+ // matches one of the positive patterns and does not match any of
+ // the negative patterns.
+ //
+ // For example, *A*:Foo.* is a filter that matches any string that
+ // contains the character 'A' or starts with "Foo.".
+ bool should_run() const { return should_run_; }
+
+ // Returns true iff this test will appear in the XML report.
+ bool is_reportable() const {
+ // The XML report includes tests matching the filter, excluding those
+ // run in other shards.
+ return matches_filter_ && !is_in_another_shard_;
+ }
+
+ // Returns the result of the test.
+ const TestResult* result() const { return &result_; }
+
+ private:
+#if GTEST_HAS_DEATH_TEST
+ friend class internal::DefaultDeathTestFactory;
+#endif // GTEST_HAS_DEATH_TEST
+ friend class Test;
+ friend class TestCase;
+ friend class internal::UnitTestImpl;
+ friend class internal::StreamingListenerTest;
+ friend TestInfo* internal::MakeAndRegisterTestInfo(
+ const char* test_case_name,
+ const char* name,
+ const char* type_param,
+ const char* value_param,
+ internal::CodeLocation code_location,
+ internal::TypeId fixture_class_id,
+ Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc,
+ internal::TestFactoryBase* factory);
+
+ // Constructs a TestInfo object. The newly constructed instance assumes
+ // ownership of the factory object.
+ TestInfo(const std::string& test_case_name,
+ const std::string& name,
+ const char* a_type_param, // NULL if not a type-parameterized test
+ const char* a_value_param, // NULL if not a value-parameterized test
+ internal::CodeLocation a_code_location,
+ internal::TypeId fixture_class_id,
+ internal::TestFactoryBase* factory);
+
+ // Increments the number of death tests encountered in this test so
+ // far.
+ int increment_death_test_count() {
+ return result_.increment_death_test_count();
+ }
+
+ // Creates the test object, runs it, records its result, and then
+ // deletes it.
+ void Run();
+
+ static void ClearTestResult(TestInfo* test_info) {
+ test_info->result_.Clear();
+ }
+
+ // These fields are immutable properties of the test.
+ const std::string test_case_name_; // Test case name
+ const std::string name_; // Test name
+ // Name of the parameter type, or NULL if this is not a typed or a
+ // type-parameterized test.
+ const internal::scoped_ptr<const ::std::string> type_param_;
+ // Text representation of the value parameter, or NULL if this is not a
+ // value-parameterized test.
+ const internal::scoped_ptr<const ::std::string> value_param_;
+ internal::CodeLocation location_;
+ const internal::TypeId fixture_class_id_; // ID of the test fixture class
+ bool should_run_; // True iff this test should run
+ bool is_disabled_; // True iff this test is disabled
+ bool matches_filter_; // True if this test matches the
+ // user-specified filter.
+ bool is_in_another_shard_; // Will be run in another shard.
+ internal::TestFactoryBase* const factory_; // The factory that creates
+ // the test object
+
+ // This field is mutable and needs to be reset before running the
+ // test for the second time.
+ TestResult result_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo);
+};
+
+// A test case, which consists of a vector of TestInfos.
+//
+// TestCase is not copyable.
+class GTEST_API_ TestCase {
+ public:
+ // Creates a TestCase with the given name.
+ //
+ // TestCase does NOT have a default constructor. Always use this
+ // constructor to create a TestCase object.
+ //
+ // Arguments:
+ //
+ // name: name of the test case
+ // a_type_param: the name of the test's type parameter, or NULL if
+ // this is not a type-parameterized test.
+ // set_up_tc: pointer to the function that sets up the test case
+ // tear_down_tc: pointer to the function that tears down the test case
+ TestCase(const char* name, const char* a_type_param,
+ Test::SetUpTestCaseFunc set_up_tc,
+ Test::TearDownTestCaseFunc tear_down_tc);
+
+ // Destructor of TestCase.
+ virtual ~TestCase();
+
+ // Gets the name of the TestCase.
+ const char* name() const { return name_.c_str(); }
+
+ // Returns the name of the parameter type, or NULL if this is not a
+ // type-parameterized test case.
+ const char* type_param() const {
+ if (type_param_.get() != NULL)
+ return type_param_->c_str();
+ return NULL;
+ }
+
+ // Returns true if any test in this test case should run.
+ bool should_run() const { return should_run_; }
+
+ // Gets the number of successful tests in this test case.
+ int successful_test_count() const;
+
+ // Gets the number of failed tests in this test case.
+ int failed_test_count() const;
+
+ // Gets the number of disabled tests that will be reported in the XML report.
+ int reportable_disabled_test_count() const;
+
+ // Gets the number of disabled tests in this test case.
+ int disabled_test_count() const;
+
+ // Gets the number of tests to be printed in the XML report.
+ int reportable_test_count() const;
+
+ // Get the number of tests in this test case that should run.
+ int test_to_run_count() const;
+
+ // Gets the number of all tests in this test case.
+ int total_test_count() const;
+
+ // Returns true iff the test case passed.
+ bool Passed() const { return !Failed(); }
+
+ // Returns true iff the test case failed.
+ bool Failed() const { return failed_test_count() > 0; }
+
+ // Returns the elapsed time, in milliseconds.
+ TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+ // Returns the i-th test among all the tests. i can range from 0 to
+ // total_test_count() - 1. If i is not in that range, returns NULL.
+ const TestInfo* GetTestInfo(int i) const;
+
+ // Returns the TestResult that holds test properties recorded during
+ // execution of SetUpTestCase and TearDownTestCase.
+ const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; }
+
+ private:
+ friend class Test;
+ friend class internal::UnitTestImpl;
+
+ // Gets the (mutable) vector of TestInfos in this TestCase.
+ std::vector<TestInfo*>& test_info_list() { return test_info_list_; }
+
+ // Gets the (immutable) vector of TestInfos in this TestCase.
+ const std::vector<TestInfo*>& test_info_list() const {
+ return test_info_list_;
+ }
+
+ // Returns the i-th test among all the tests. i can range from 0 to
+ // total_test_count() - 1. If i is not in that range, returns NULL.
+ TestInfo* GetMutableTestInfo(int i);
+
+ // Sets the should_run member.
+ void set_should_run(bool should) { should_run_ = should; }
+
+ // Adds a TestInfo to this test case. Will delete the TestInfo upon
+ // destruction of the TestCase object.
+ void AddTestInfo(TestInfo * test_info);
+
+ // Clears the results of all tests in this test case.
+ void ClearResult();
+
+ // Clears the results of all tests in the given test case.
+ static void ClearTestCaseResult(TestCase* test_case) {
+ test_case->ClearResult();
+ }
+
+ // Runs every test in this TestCase.
+ void Run();
+
+ // Runs SetUpTestCase() for this TestCase. This wrapper is needed
+ // for catching exceptions thrown from SetUpTestCase().
+ void RunSetUpTestCase() { (*set_up_tc_)(); }
+
+ // Runs TearDownTestCase() for this TestCase. This wrapper is
+ // needed for catching exceptions thrown from TearDownTestCase().
+ void RunTearDownTestCase() { (*tear_down_tc_)(); }
+
+ // Returns true iff test passed.
+ static bool TestPassed(const TestInfo* test_info) {
+ return test_info->should_run() && test_info->result()->Passed();
+ }
+
+ // Returns true iff test failed.
+ static bool TestFailed(const TestInfo* test_info) {
+ return test_info->should_run() && test_info->result()->Failed();
+ }
+
+ // Returns true iff the test is disabled and will be reported in the XML
+ // report.
+ static bool TestReportableDisabled(const TestInfo* test_info) {
+ return test_info->is_reportable() && test_info->is_disabled_;
+ }
+
+ // Returns true iff test is disabled.
+ static bool TestDisabled(const TestInfo* test_info) {
+ return test_info->is_disabled_;
+ }
+
+ // Returns true iff this test will appear in the XML report.
+ static bool TestReportable(const TestInfo* test_info) {
+ return test_info->is_reportable();
+ }
+
+ // Returns true if the given test should run.
+ static bool ShouldRunTest(const TestInfo* test_info) {
+ return test_info->should_run();
+ }
+
+ // Shuffles the tests in this test case.
+ void ShuffleTests(internal::Random* random);
+
+ // Restores the test order to before the first shuffle.
+ void UnshuffleTests();
+
+ // Name of the test case.
+ std::string name_;
+ // Name of the parameter type, or NULL if this is not a typed or a
+ // type-parameterized test.
+ const internal::scoped_ptr<const ::std::string> type_param_;
+ // The vector of TestInfos in their original order. It owns the
+ // elements in the vector.
+ std::vector<TestInfo*> test_info_list_;
+ // Provides a level of indirection for the test list to allow easy
+ // shuffling and restoring the test order. The i-th element in this
+ // vector is the index of the i-th test in the shuffled test list.
+ std::vector<int> test_indices_;
+ // Pointer to the function that sets up the test case.
+ Test::SetUpTestCaseFunc set_up_tc_;
+ // Pointer to the function that tears down the test case.
+ Test::TearDownTestCaseFunc tear_down_tc_;
+ // True iff any test in this test case should run.
+ bool should_run_;
+ // Elapsed time, in milliseconds.
+ TimeInMillis elapsed_time_;
+ // Holds test properties recorded during execution of SetUpTestCase and
+ // TearDownTestCase.
+ TestResult ad_hoc_test_result_;
+
+ // We disallow copying TestCases.
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestCase);
+};
+
+// An Environment object is capable of setting up and tearing down an
+// environment. You should subclass this to define your own
+// environment(s).
+//
+// An Environment object does the set-up and tear-down in virtual
+// methods SetUp() and TearDown() instead of the constructor and the
+// destructor, as:
+//
+// 1. You cannot safely throw from a destructor. This is a problem
+// as in some cases Google Test is used where exceptions are enabled, and
+// we may want to implement ASSERT_* using exceptions where they are
+// available.
+// 2. You cannot use ASSERT_* directly in a constructor or
+// destructor.
+class Environment {
+ public:
+ // The d'tor is virtual as we need to subclass Environment.
+ virtual ~Environment() {}
+
+ // Override this to define how to set up the environment.
+ virtual void SetUp() {}
+
+ // Override this to define how to tear down the environment.
+ virtual void TearDown() {}
+ private:
+ // If you see an error about overriding the following function or
+ // about it being private, you have mis-spelled SetUp() as Setup().
+ struct Setup_should_be_spelled_SetUp {};
+ virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; }
+};
+
+#if GTEST_HAS_EXCEPTIONS
+
+// Exception which can be thrown from TestEventListener::OnTestPartResult.
+class GTEST_API_ AssertionException
+ : public internal::GoogleTestFailureException {
+ public:
+ explicit AssertionException(const TestPartResult& result)
+ : GoogleTestFailureException(result) {}
+};
+
+#endif // GTEST_HAS_EXCEPTIONS
+
+// The interface for tracing execution of tests. The methods are organized in
+// the order the corresponding events are fired.
+class TestEventListener {
+ public:
+ virtual ~TestEventListener() {}
+
+ // Fired before any test activity starts.
+ virtual void OnTestProgramStart(const UnitTest& unit_test) = 0;
+
+ // Fired before each iteration of tests starts. There may be more than
+ // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration
+ // index, starting from 0.
+ virtual void OnTestIterationStart(const UnitTest& unit_test,
+ int iteration) = 0;
+
+ // Fired before environment set-up for each iteration of tests starts.
+ virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0;
+
+ // Fired after environment set-up for each iteration of tests ends.
+ virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0;
+
+ // Fired before the test case starts.
+ virtual void OnTestCaseStart(const TestCase& test_case) = 0;
+
+ // Fired before the test starts.
+ virtual void OnTestStart(const TestInfo& test_info) = 0;
+
+ // Fired after a failed assertion or a SUCCEED() invocation.
+ // If you want to throw an exception from this function to skip to the next
+ // TEST, it must be AssertionException defined above, or inherited from it.
+ virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;
+
+ // Fired after the test ends.
+ virtual void OnTestEnd(const TestInfo& test_info) = 0;
+
+ // Fired after the test case ends.
+ virtual void OnTestCaseEnd(const TestCase& test_case) = 0;
+
+ // Fired before environment tear-down for each iteration of tests starts.
+ virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0;
+
+ // Fired after environment tear-down for each iteration of tests ends.
+ virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0;
+
+ // Fired after each iteration of tests finishes.
+ virtual void OnTestIterationEnd(const UnitTest& unit_test,
+ int iteration) = 0;
+
+ // Fired after all test activities have ended.
+ virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0;
+};
+
+// The convenience class for users who need to override just one or two
+// methods and are not concerned that a possible change to a signature of
+// the methods they override will not be caught during the build. For
+// comments about each method please see the definition of TestEventListener
+// above.
+class EmptyTestEventListener : public TestEventListener {
+ public:
+ virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestIterationStart(const UnitTest& /*unit_test*/,
+ int /*iteration*/) {}
+ virtual void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) {}
+ virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestCaseStart(const TestCase& /*test_case*/) {}
+ virtual void OnTestStart(const TestInfo& /*test_info*/) {}
+ virtual void OnTestPartResult(const TestPartResult& /*test_part_result*/) {}
+ virtual void OnTestEnd(const TestInfo& /*test_info*/) {}
+ virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {}
+ virtual void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) {}
+ virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {}
+ virtual void OnTestIterationEnd(const UnitTest& /*unit_test*/,
+ int /*iteration*/) {}
+ virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {}
+};
+
+// TestEventListeners lets users add listeners to track events in Google Test.
+class GTEST_API_ TestEventListeners {
+ public:
+ TestEventListeners();
+ ~TestEventListeners();
+
+ // Appends an event listener to the end of the list. Google Test assumes
+ // the ownership of the listener (i.e. it will delete the listener when
+ // the test program finishes).
+ void Append(TestEventListener* listener);
+
+ // Removes the given event listener from the list and returns it. It then
+ // becomes the caller's responsibility to delete the listener. Returns
+ // NULL if the listener is not found in the list.
+ TestEventListener* Release(TestEventListener* listener);
+
+ // Returns the standard listener responsible for the default console
+ // output. Can be removed from the listeners list to shut down default
+ // console output. Note that removing this object from the listener list
+ // with Release transfers its ownership to the caller and makes this
+ // function return NULL the next time.
+ TestEventListener* default_result_printer() const {
+ return default_result_printer_;
+ }
+
+ // Returns the standard listener responsible for the default XML output
+ // controlled by the --gtest_output=xml flag. Can be removed from the
+ // listeners list by users who want to shut down the default XML output
+ // controlled by this flag and substitute it with custom one. Note that
+ // removing this object from the listener list with Release transfers its
+ // ownership to the caller and makes this function return NULL the next
+ // time.
+ TestEventListener* default_xml_generator() const {
+ return default_xml_generator_;
+ }
+
+ private:
+ friend class TestCase;
+ friend class TestInfo;
+ friend class internal::DefaultGlobalTestPartResultReporter;
+ friend class internal::NoExecDeathTest;
+ friend class internal::TestEventListenersAccessor;
+ friend class internal::UnitTestImpl;
+
+ // Returns repeater that broadcasts the TestEventListener events to all
+ // subscribers.
+ TestEventListener* repeater();
+
+ // Sets the default_result_printer attribute to the provided listener.
+ // The listener is also added to the listener list and previous
+ // default_result_printer is removed from it and deleted. The listener can
+ // also be NULL in which case it will not be added to the list. Does
+ // nothing if the previous and the current listener objects are the same.
+ void SetDefaultResultPrinter(TestEventListener* listener);
+
+ // Sets the default_xml_generator attribute to the provided listener. The
+ // listener is also added to the listener list and previous
+ // default_xml_generator is removed from it and deleted. The listener can
+ // also be NULL in which case it will not be added to the list. Does
+ // nothing if the previous and the current listener objects are the same.
+ void SetDefaultXmlGenerator(TestEventListener* listener);
+
+ // Controls whether events will be forwarded by the repeater to the
+ // listeners in the list.
+ bool EventForwardingEnabled() const;
+ void SuppressEventForwarding();
+
+ // The actual list of listeners.
+ internal::TestEventRepeater* repeater_;
+ // Listener responsible for the standard result output.
+ TestEventListener* default_result_printer_;
+ // Listener responsible for the creation of the XML output file.
+ TestEventListener* default_xml_generator_;
+
+ // We disallow copying TestEventListeners.
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners);
+};
+
+// A UnitTest consists of a vector of TestCases.
+//
+// This is a singleton class. The only instance of UnitTest is
+// created when UnitTest::GetInstance() is first called. This
+// instance is never deleted.
+//
+// UnitTest is not copyable.
+//
+// This class is thread-safe as long as the methods are called
+// according to their specification.
+class GTEST_API_ UnitTest {
+ public:
+ // Gets the singleton UnitTest object. The first time this method
+ // is called, a UnitTest object is constructed and returned.
+ // Consecutive calls will return the same object.
+ static UnitTest* GetInstance();
+
+ // Runs all tests in this UnitTest object and prints the result.
+ // Returns 0 if successful, or 1 otherwise.
+ //
+ // This method can only be called from the main thread.
+ //
+ // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+ int Run() GTEST_MUST_USE_RESULT_;
+
+ // Returns the working directory when the first TEST() or TEST_F()
+ // was executed. The UnitTest object owns the string.
+ const char* original_working_dir() const;
+
+ // Returns the TestCase object for the test that's currently running,
+ // or NULL if no test is running.
+ const TestCase* current_test_case() const
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Returns the TestInfo object for the test that's currently running,
+ // or NULL if no test is running.
+ const TestInfo* current_test_info() const
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Returns the random seed used at the start of the current test run.
+ int random_seed() const;
+
+ // Returns the ParameterizedTestCaseRegistry object used to keep track of
+ // value-parameterized tests and instantiate and register them.
+ //
+ // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+ internal::ParameterizedTestCaseRegistry& parameterized_test_registry()
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Gets the number of successful test cases.
+ int successful_test_case_count() const;
+
+ // Gets the number of failed test cases.
+ int failed_test_case_count() const;
+
+ // Gets the number of all test cases.
+ int total_test_case_count() const;
+
+ // Gets the number of all test cases that contain at least one test
+ // that should run.
+ int test_case_to_run_count() const;
+
+ // Gets the number of successful tests.
+ int successful_test_count() const;
+
+ // Gets the number of failed tests.
+ int failed_test_count() const;
+
+ // Gets the number of disabled tests that will be reported in the XML report.
+ int reportable_disabled_test_count() const;
+
+ // Gets the number of disabled tests.
+ int disabled_test_count() const;
+
+ // Gets the number of tests to be printed in the XML report.
+ int reportable_test_count() const;
+
+ // Gets the number of all tests.
+ int total_test_count() const;
+
+ // Gets the number of tests that should run.
+ int test_to_run_count() const;
+
+ // Gets the time of the test program start, in ms from the start of the
+ // UNIX epoch.
+ TimeInMillis start_timestamp() const;
+
+ // Gets the elapsed time, in milliseconds.
+ TimeInMillis elapsed_time() const;
+
+ // Returns true iff the unit test passed (i.e. all test cases passed).
+ bool Passed() const;
+
+ // Returns true iff the unit test failed (i.e. some test case failed
+ // or something outside of all tests failed).
+ bool Failed() const;
+
+ // Gets the i-th test case among all the test cases. i can range from 0 to
+ // total_test_case_count() - 1. If i is not in that range, returns NULL.
+ const TestCase* GetTestCase(int i) const;
+
+ // Returns the TestResult containing information on test failures and
+ // properties logged outside of individual test cases.
+ const TestResult& ad_hoc_test_result() const;
+
+ // Returns the list of event listeners that can be used to track events
+ // inside Google Test.
+ TestEventListeners& listeners();
+
+ private:
+ // Registers and returns a global test environment. When a test
+ // program is run, all global test environments will be set-up in
+ // the order they were registered. After all tests in the program
+ // have finished, all global test environments will be torn-down in
+ // the *reverse* order they were registered.
+ //
+ // The UnitTest object takes ownership of the given environment.
+ //
+ // This method can only be called from the main thread.
+ Environment* AddEnvironment(Environment* env);
+
+ // Adds a TestPartResult to the current TestResult object. All
+ // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc)
+ // eventually call this to report their results. The user code
+ // should use the assertion macros instead of calling this directly.
+ void AddTestPartResult(TestPartResult::Type result_type,
+ const char* file_name,
+ int line_number,
+ const std::string& message,
+ const std::string& os_stack_trace)
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Adds a TestProperty to the current TestResult object when invoked from
+ // inside a test, to current TestCase's ad_hoc_test_result_ when invoked
+ // from SetUpTestCase or TearDownTestCase, or to the global property set
+ // when invoked elsewhere. If the result already contains a property with
+ // the same key, the value will be updated.
+ void RecordProperty(const std::string& key, const std::string& value);
+
+ // Gets the i-th test case among all the test cases. i can range from 0 to
+ // total_test_case_count() - 1. If i is not in that range, returns NULL.
+ TestCase* GetMutableTestCase(int i);
+
+ // Accessors for the implementation object.
+ internal::UnitTestImpl* impl() { return impl_; }
+ const internal::UnitTestImpl* impl() const { return impl_; }
+
+ // These classes and functions are friends as they need to access private
+ // members of UnitTest.
+ friend class ScopedTrace;
+ friend class Test;
+ friend class internal::AssertHelper;
+ friend class internal::StreamingListenerTest;
+ friend class internal::UnitTestRecordPropertyTestHelper;
+ friend Environment* AddGlobalTestEnvironment(Environment* env);
+ friend internal::UnitTestImpl* internal::GetUnitTestImpl();
+ friend void internal::ReportFailureInUnknownLocation(
+ TestPartResult::Type result_type,
+ const std::string& message);
+
+ // Creates an empty UnitTest.
+ UnitTest();
+
+ // D'tor
+ virtual ~UnitTest();
+
+ // Pushes a trace defined by SCOPED_TRACE() on to the per-thread
+ // Google Test trace stack.
+ void PushGTestTrace(const internal::TraceInfo& trace)
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Pops a trace from the per-thread Google Test trace stack.
+ void PopGTestTrace()
+ GTEST_LOCK_EXCLUDED_(mutex_);
+
+ // Protects mutable state in *impl_. This is mutable as some const
+ // methods need to lock it too.
+ mutable internal::Mutex mutex_;
+
+ // Opaque implementation object. This field is never changed once
+ // the object is constructed. We don't mark it as const here, as
+ // doing so will cause a warning in the constructor of UnitTest.
+ // Mutable state in *impl_ is protected by mutex_.
+ internal::UnitTestImpl* impl_;
+
+ // We disallow copying UnitTest.
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest);
+};
+
+// A convenient wrapper for adding an environment for the test
+// program.
+//
+// You should call this before RUN_ALL_TESTS() is called, probably in
+// main(). If you use gtest_main, you need to call this before main()
+// starts for it to take effect. For example, you can define a global
+// variable like this:
+//
+// testing::Environment* const foo_env =
+// testing::AddGlobalTestEnvironment(new FooEnvironment);
+//
+// However, we strongly recommend you to write your own main() and
+// call AddGlobalTestEnvironment() there, as relying on initialization
+// of global variables makes the code harder to read and may cause
+// problems when you register multiple environments from different
+// translation units and the environments have dependencies among them
+// (remember that the compiler doesn't guarantee the order in which
+// global variables from different translation units are initialized).
+inline Environment* AddGlobalTestEnvironment(Environment* env) {
+ return UnitTest::GetInstance()->AddEnvironment(env);
+}
+
+// Initializes Google Test. This must be called before calling
+// RUN_ALL_TESTS(). In particular, it parses a command line for the
+// flags that Google Test recognizes. Whenever a Google Test flag is
+// seen, it is removed from argv, and *argc is decremented.
+//
+// No value is returned. Instead, the Google Test flag variables are
+// updated.
+//
+// Calling the function for the second time has no user-visible effect.
+GTEST_API_ void InitGoogleTest(int* argc, char** argv);
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv);
+
+namespace internal {
+
+// Separate the error generating code from the code path to reduce the stack
+// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers
+// when calling EXPECT_* in a tight loop.
+template <typename T1, typename T2>
+AssertionResult CmpHelperEQFailure(const char* lhs_expression,
+ const char* rhs_expression,
+ const T1& lhs, const T2& rhs) {
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ FormatForComparisonFailureMessage(lhs, rhs),
+ FormatForComparisonFailureMessage(rhs, lhs),
+ false);
+}
+
+// The helper function for {ASSERT|EXPECT}_EQ.
+template <typename T1, typename T2>
+AssertionResult CmpHelperEQ(const char* lhs_expression,
+ const char* rhs_expression,
+ const T1& lhs,
+ const T2& rhs) {
+ if (lhs == rhs) {
+ return AssertionSuccess();
+ }
+
+ return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs);
+}
+
+// With this overloaded version, we allow anonymous enums to be used
+// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums
+// can be implicitly cast to BiggestInt.
+GTEST_API_ AssertionResult CmpHelperEQ(const char* lhs_expression,
+ const char* rhs_expression,
+ BiggestInt lhs,
+ BiggestInt rhs);
+
+// The helper class for {ASSERT|EXPECT}_EQ. The template argument
+// lhs_is_null_literal is true iff the first argument to ASSERT_EQ()
+// is a null pointer literal. The following default implementation is
+// for lhs_is_null_literal being false.
+template <bool lhs_is_null_literal>
+class EqHelper {
+ public:
+ // This templatized version is for the general case.
+ template <typename T1, typename T2>
+ static AssertionResult Compare(const char* lhs_expression,
+ const char* rhs_expression,
+ const T1& lhs,
+ const T2& rhs) {
+ return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
+ }
+
+ // With this overloaded version, we allow anonymous enums to be used
+ // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous
+ // enums can be implicitly cast to BiggestInt.
+ //
+ // Even though its body looks the same as the above version, we
+ // cannot merge the two, as it will make anonymous enums unhappy.
+ static AssertionResult Compare(const char* lhs_expression,
+ const char* rhs_expression,
+ BiggestInt lhs,
+ BiggestInt rhs) {
+ return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
+ }
+};
+
+// This specialization is used when the first argument to ASSERT_EQ()
+// is a null pointer literal, like NULL, false, or 0.
+template <>
+class EqHelper<true> {
+ public:
+ // We define two overloaded versions of Compare(). The first
+ // version will be picked when the second argument to ASSERT_EQ() is
+ // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or
+ // EXPECT_EQ(false, a_bool).
+ template <typename T1, typename T2>
+ static AssertionResult Compare(
+ const char* lhs_expression,
+ const char* rhs_expression,
+ const T1& lhs,
+ const T2& rhs,
+ // The following line prevents this overload from being considered if T2
+ // is not a pointer type. We need this because ASSERT_EQ(NULL, my_ptr)
+ // expands to Compare("", "", NULL, my_ptr), which requires a conversion
+ // to match the Secret* in the other overload, which would otherwise make
+ // this template match better.
+ typename EnableIf<!is_pointer<T2>::value>::type* = 0) {
+ return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
+ }
+
+ // This version will be picked when the second argument to ASSERT_EQ() is a
+ // pointer, e.g. ASSERT_EQ(NULL, a_pointer).
+ template <typename T>
+ static AssertionResult Compare(
+ const char* lhs_expression,
+ const char* rhs_expression,
+ // We used to have a second template parameter instead of Secret*. That
+ // template parameter would deduce to 'long', making this a better match
+ // than the first overload even without the first overload's EnableIf.
+ // Unfortunately, gcc with -Wconversion-null warns when "passing NULL to
+ // non-pointer argument" (even a deduced integral argument), so the old
+ // implementation caused warnings in user code.
+ Secret* /* lhs (NULL) */,
+ T* rhs) {
+ // We already know that 'lhs' is a null pointer.
+ return CmpHelperEQ(lhs_expression, rhs_expression,
+ static_cast<T*>(NULL), rhs);
+ }
+};
+
+// Separate the error generating code from the code path to reduce the stack
+// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers
+// when calling EXPECT_OP in a tight loop.
+template <typename T1, typename T2>
+AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2,
+ const T1& val1, const T2& val2,
+ const char* op) {
+ return AssertionFailure()
+ << "Expected: (" << expr1 << ") " << op << " (" << expr2
+ << "), actual: " << FormatForComparisonFailureMessage(val1, val2)
+ << " vs " << FormatForComparisonFailureMessage(val2, val1);
+}
+
+// A macro for implementing the helper functions needed to implement
+// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste
+// of similar code.
+//
+// For each templatized helper function, we also define an overloaded
+// version for BiggestInt in order to reduce code bloat and allow
+// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled
+// with gcc 4.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+#define GTEST_IMPL_CMP_HELPER_(op_name, op)\
+template <typename T1, typename T2>\
+AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \
+ const T1& val1, const T2& val2) {\
+ if (val1 op val2) {\
+ return AssertionSuccess();\
+ } else {\
+ return CmpHelperOpFailure(expr1, expr2, val1, val2, #op);\
+ }\
+}\
+GTEST_API_ AssertionResult CmpHelper##op_name(\
+ const char* expr1, const char* expr2, BiggestInt val1, BiggestInt val2)
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+// Implements the helper function for {ASSERT|EXPECT}_NE
+GTEST_IMPL_CMP_HELPER_(NE, !=);
+// Implements the helper function for {ASSERT|EXPECT}_LE
+GTEST_IMPL_CMP_HELPER_(LE, <=);
+// Implements the helper function for {ASSERT|EXPECT}_LT
+GTEST_IMPL_CMP_HELPER_(LT, <);
+// Implements the helper function for {ASSERT|EXPECT}_GE
+GTEST_IMPL_CMP_HELPER_(GE, >=);
+// Implements the helper function for {ASSERT|EXPECT}_GT
+GTEST_IMPL_CMP_HELPER_(GT, >);
+
+#undef GTEST_IMPL_CMP_HELPER_
+
+// The helper function for {ASSERT|EXPECT}_STREQ.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRCASEEQ.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRNE.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRCASENE.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression,
+ const char* s2_expression,
+ const char* s1,
+ const char* s2);
+
+
+// Helper function for *_STREQ on wide strings.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression,
+ const char* s2_expression,
+ const wchar_t* s1,
+ const wchar_t* s2);
+
+// Helper function for *_STRNE on wide strings.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression,
+ const char* s2_expression,
+ const wchar_t* s1,
+ const wchar_t* s2);
+
+} // namespace internal
+
+// IsSubstring() and IsNotSubstring() are intended to be used as the
+// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by
+// themselves. They check whether needle is a substring of haystack
+// (NULL is considered a substring of itself only), and return an
+// appropriate error message when they fail.
+//
+// The {needle,haystack}_expr arguments are the stringified
+// expressions that generated the two real arguments.
+GTEST_API_ AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const char* needle, const char* haystack);
+GTEST_API_ AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const wchar_t* needle, const wchar_t* haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const char* needle, const char* haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const wchar_t* needle, const wchar_t* haystack);
+GTEST_API_ AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::string& needle, const ::std::string& haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::string& needle, const ::std::string& haystack);
+
+#if GTEST_HAS_STD_WSTRING
+GTEST_API_ AssertionResult IsSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::wstring& needle, const ::std::wstring& haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+ const char* needle_expr, const char* haystack_expr,
+ const ::std::wstring& needle, const ::std::wstring& haystack);
+#endif // GTEST_HAS_STD_WSTRING
+
+namespace internal {
+
+// Helper template function for comparing floating-points.
+//
+// Template parameter:
+//
+// RawType: the raw floating-point type (either float or double)
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+template <typename RawType>
+AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
+ const char* rhs_expression,
+ RawType lhs_value,
+ RawType rhs_value) {
+ const FloatingPoint<RawType> lhs(lhs_value), rhs(rhs_value);
+
+ if (lhs.AlmostEquals(rhs)) {
+ return AssertionSuccess();
+ }
+
+ ::std::stringstream lhs_ss;
+ lhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+ << lhs_value;
+
+ ::std::stringstream rhs_ss;
+ rhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+ << rhs_value;
+
+ return EqFailure(lhs_expression,
+ rhs_expression,
+ StringStreamToString(&lhs_ss),
+ StringStreamToString(&rhs_ss),
+ false);
+}
+
+// Helper function for implementing ASSERT_NEAR.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1,
+ const char* expr2,
+ const char* abs_error_expr,
+ double val1,
+ double val2,
+ double abs_error);
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+// A class that enables one to stream messages to assertion macros
+class GTEST_API_ AssertHelper {
+ public:
+ // Constructor.
+ AssertHelper(TestPartResult::Type type,
+ const char* file,
+ int line,
+ const char* message);
+ ~AssertHelper();
+
+ // Message assignment is a semantic trick to enable assertion
+ // streaming; see the GTEST_MESSAGE_ macro below.
+ void operator=(const Message& message) const;
+
+ private:
+ // We put our data in a struct so that the size of the AssertHelper class can
+ // be as small as possible. This is important because gcc is incapable of
+ // re-using stack space even for temporary variables, so every EXPECT_EQ
+ // reserves stack space for another AssertHelper.
+ struct AssertHelperData {
+ AssertHelperData(TestPartResult::Type t,
+ const char* srcfile,
+ int line_num,
+ const char* msg)
+ : type(t), file(srcfile), line(line_num), message(msg) { }
+
+ TestPartResult::Type const type;
+ const char* const file;
+ int const line;
+ std::string const message;
+
+ private:
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData);
+ };
+
+ AssertHelperData* const data_;
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper);
+};
+
+} // namespace internal
+
+// The pure interface class that all value-parameterized tests inherit from.
+// A value-parameterized class must inherit from both ::testing::Test and
+// ::testing::WithParamInterface. In most cases that just means inheriting
+// from ::testing::TestWithParam, but more complicated test hierarchies
+// may need to inherit from Test and WithParamInterface at different levels.
+//
+// This interface has support for accessing the test parameter value via
+// the GetParam() method.
+//
+// Use it with one of the parameter generator defining functions, like Range(),
+// Values(), ValuesIn(), Bool(), and Combine().
+//
+// class FooTest : public ::testing::TestWithParam<int> {
+// protected:
+// FooTest() {
+// // Can use GetParam() here.
+// }
+// virtual ~FooTest() {
+// // Can use GetParam() here.
+// }
+// virtual void SetUp() {
+// // Can use GetParam() here.
+// }
+// virtual void TearDown {
+// // Can use GetParam() here.
+// }
+// };
+// TEST_P(FooTest, DoesBar) {
+// // Can use GetParam() method here.
+// Foo foo;
+// ASSERT_TRUE(foo.DoesBar(GetParam()));
+// }
+// INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10));
+
+template <typename T>
+class WithParamInterface {
+ public:
+ typedef T ParamType;
+ virtual ~WithParamInterface() {}
+
+ // The current parameter value. Is also available in the test fixture's
+ // constructor. This member function is non-static, even though it only
+ // references static data, to reduce the opportunity for incorrect uses
+ // like writing 'WithParamInterface<bool>::GetParam()' for a test that
+ // uses a fixture whose parameter type is int.
+ const ParamType& GetParam() const {
+ GTEST_CHECK_(parameter_ != NULL)
+ << "GetParam() can only be called inside a value-parameterized test "
+ << "-- did you intend to write TEST_P instead of TEST_F?";
+ return *parameter_;
+ }
+
+ private:
+ // Sets parameter value. The caller is responsible for making sure the value
+ // remains alive and unchanged throughout the current test.
+ static void SetParam(const ParamType* parameter) {
+ parameter_ = parameter;
+ }
+
+ // Static value used for accessing parameter during a test lifetime.
+ static const ParamType* parameter_;
+
+ // TestClass must be a subclass of WithParamInterface<T> and Test.
+ template <class TestClass> friend class internal::ParameterizedTestFactory;
+};
+
+template <typename T>
+const T* WithParamInterface<T>::parameter_ = NULL;
+
+// Most value-parameterized classes can ignore the existence of
+// WithParamInterface, and can just inherit from ::testing::TestWithParam.
+
+template <typename T>
+class TestWithParam : public Test, public WithParamInterface<T> {
+};
+
+// Macros for indicating success/failure in test code.
+
+// ADD_FAILURE unconditionally adds a failure to the current test.
+// SUCCEED generates a success - it doesn't automatically make the
+// current test successful, as a test is only successful when it has
+// no failure.
+//
+// EXPECT_* verifies that a certain condition is satisfied. If not,
+// it behaves like ADD_FAILURE. In particular:
+//
+// EXPECT_TRUE verifies that a Boolean condition is true.
+// EXPECT_FALSE verifies that a Boolean condition is false.
+//
+// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except
+// that they will also abort the current function on failure. People
+// usually want the fail-fast behavior of FAIL and ASSERT_*, but those
+// writing data-driven tests often find themselves using ADD_FAILURE
+// and EXPECT_* more.
+
+// Generates a nonfatal failure with a generic message.
+#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed")
+
+// Generates a nonfatal failure at the given source file location with
+// a generic message.
+#define ADD_FAILURE_AT(file, line) \
+ GTEST_MESSAGE_AT_(file, line, "Failed", \
+ ::testing::TestPartResult::kNonFatalFailure)
+
+// Generates a fatal failure with a generic message.
+#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed")
+
+// Define this macro to 1 to omit the definition of FAIL(), which is a
+// generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_FAIL
+# define FAIL() GTEST_FAIL()
+#endif
+
+// Generates a success with a generic message.
+#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded")
+
+// Define this macro to 1 to omit the definition of SUCCEED(), which
+// is a generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_SUCCEED
+# define SUCCEED() GTEST_SUCCEED()
+#endif
+
+// Macros for testing exceptions.
+//
+// * {ASSERT|EXPECT}_THROW(statement, expected_exception):
+// Tests that the statement throws the expected exception.
+// * {ASSERT|EXPECT}_NO_THROW(statement):
+// Tests that the statement doesn't throw any exception.
+// * {ASSERT|EXPECT}_ANY_THROW(statement):
+// Tests that the statement throws an exception.
+
+#define EXPECT_THROW(statement, expected_exception) \
+ GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_NO_THROW(statement) \
+ GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_ANY_THROW(statement) \
+ GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_THROW(statement, expected_exception) \
+ GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_)
+#define ASSERT_NO_THROW(statement) \
+ GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_)
+#define ASSERT_ANY_THROW(statement) \
+ GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_)
+
+// Boolean assertions. Condition can be either a Boolean expression or an
+// AssertionResult. For more information on how to use AssertionResult with
+// these macros see comments on that class.
+#define EXPECT_TRUE(condition) \
+ GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
+ GTEST_NONFATAL_FAILURE_)
+#define EXPECT_FALSE(condition) \
+ GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \
+ GTEST_NONFATAL_FAILURE_)
+#define ASSERT_TRUE(condition) \
+ GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
+ GTEST_FATAL_FAILURE_)
+#define ASSERT_FALSE(condition) \
+ GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \
+ GTEST_FATAL_FAILURE_)
+
+// Macros for testing equalities and inequalities.
+//
+// * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2
+// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2
+// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2
+// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2
+// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2
+// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2
+//
+// When they are not, Google Test prints both the tested expressions and
+// their actual values. The values must be compatible built-in types,
+// or you will get a compiler error. By "compatible" we mean that the
+// values can be compared by the respective operator.
+//
+// Note:
+//
+// 1. It is possible to make a user-defined type work with
+// {ASSERT|EXPECT}_??(), but that requires overloading the
+// comparison operators and is thus discouraged by the Google C++
+// Usage Guide. Therefore, you are advised to use the
+// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are
+// equal.
+//
+// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on
+// pointers (in particular, C strings). Therefore, if you use it
+// with two C strings, you are testing how their locations in memory
+// are related, not how their content is related. To compare two C
+// strings by content, use {ASSERT|EXPECT}_STR*().
+//
+// 3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to
+// {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you
+// what the actual value is when it fails, and similarly for the
+// other comparisons.
+//
+// 4. Do not depend on the order in which {ASSERT|EXPECT}_??()
+// evaluate their arguments, which is undefined.
+//
+// 5. These macros evaluate their arguments exactly once.
+//
+// Examples:
+//
+// EXPECT_NE(Foo(), 5);
+// EXPECT_EQ(a_pointer, NULL);
+// ASSERT_LT(i, array_size);
+// ASSERT_GT(records.size(), 0) << "There is no record left.";
+
+#define EXPECT_EQ(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal:: \
+ EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::Compare, \
+ val1, val2)
+#define EXPECT_NE(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define EXPECT_LE(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
+#define EXPECT_LT(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define EXPECT_GE(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2)
+#define EXPECT_GT(val1, val2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+
+#define GTEST_ASSERT_EQ(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal:: \
+ EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::Compare, \
+ val1, val2)
+#define GTEST_ASSERT_NE(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define GTEST_ASSERT_LE(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
+#define GTEST_ASSERT_LT(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define GTEST_ASSERT_GE(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2)
+#define GTEST_ASSERT_GT(val1, val2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+
+// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of
+// ASSERT_XY(), which clashes with some users' own code.
+
+#if !GTEST_DONT_DEFINE_ASSERT_EQ
+# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_NE
+# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_LE
+# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_LT
+# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_GE
+# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_GT
+# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2)
+#endif
+
+// C-string Comparisons. All tests treat NULL and any non-NULL string
+// as different. Two NULLs are equal.
+//
+// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2
+// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2
+// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case
+// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case
+//
+// For wide or narrow string objects, you can use the
+// {ASSERT|EXPECT}_??() macros.
+//
+// Don't depend on the order in which the arguments are evaluated,
+// which is undefined.
+//
+// These macros evaluate their arguments exactly once.
+
+#define EXPECT_STREQ(s1, s2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2)
+#define EXPECT_STRNE(s1, s2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2)
+#define EXPECT_STRCASEEQ(s1, s2) \
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2)
+#define EXPECT_STRCASENE(s1, s2)\
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2)
+
+#define ASSERT_STREQ(s1, s2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2)
+#define ASSERT_STRNE(s1, s2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2)
+#define ASSERT_STRCASEEQ(s1, s2) \
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2)
+#define ASSERT_STRCASENE(s1, s2)\
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2)
+
+// Macros for comparing floating-point numbers.
+//
+// * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2):
+// Tests that two float values are almost equal.
+// * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2):
+// Tests that two double values are almost equal.
+// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error):
+// Tests that v1 and v2 are within the given distance to each other.
+//
+// Google Test uses ULP-based comparison to automatically pick a default
+// error bound that is appropriate for the operands. See the
+// FloatingPoint template class in gtest-internal.h if you are
+// interested in the implementation details.
+
+#define EXPECT_FLOAT_EQ(val1, val2)\
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \
+ val1, val2)
+
+#define EXPECT_DOUBLE_EQ(val1, val2)\
+ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \
+ val1, val2)
+
+#define ASSERT_FLOAT_EQ(val1, val2)\
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \
+ val1, val2)
+
+#define ASSERT_DOUBLE_EQ(val1, val2)\
+ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \
+ val1, val2)
+
+#define EXPECT_NEAR(val1, val2, abs_error)\
+ EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \
+ val1, val2, abs_error)
+
+#define ASSERT_NEAR(val1, val2, abs_error)\
+ ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \
+ val1, val2, abs_error)
+
+// These predicate format functions work on floating-point values, and
+// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g.
+//
+// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0);
+
+// Asserts that val1 is less than, or almost equal to, val2. Fails
+// otherwise. In particular, it fails if either val1 or val2 is NaN.
+GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2,
+ float val1, float val2);
+GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2,
+ double val1, double val2);
+
+
+#if GTEST_OS_WINDOWS
+
+// Macros that test for HRESULT failure and success, these are only useful
+// on Windows, and rely on Windows SDK macros and APIs to compile.
+//
+// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr)
+//
+// When expr unexpectedly fails or succeeds, Google Test prints the
+// expected result and the actual result with both a human-readable
+// string representation of the error, if available, as well as the
+// hex result code.
+# define EXPECT_HRESULT_SUCCEEDED(expr) \
+ EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))
+
+# define ASSERT_HRESULT_SUCCEEDED(expr) \
+ ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))
+
+# define EXPECT_HRESULT_FAILED(expr) \
+ EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr))
+
+# define ASSERT_HRESULT_FAILED(expr) \
+ ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr))
+
+#endif // GTEST_OS_WINDOWS
+
+// Macros that execute statement and check that it doesn't generate new fatal
+// failures in the current thread.
+//
+// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement);
+//
+// Examples:
+//
+// EXPECT_NO_FATAL_FAILURE(Process());
+// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed";
+//
+#define ASSERT_NO_FATAL_FAILURE(statement) \
+ GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_)
+#define EXPECT_NO_FATAL_FAILURE(statement) \
+ GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_)
+
+// Causes a trace (including the given source file path and line number,
+// and the given message) to be included in every test failure message generated
+// by code in the scope of the lifetime of an instance of this class. The effect
+// is undone with the destruction of the instance.
+//
+// The message argument can be anything streamable to std::ostream.
+//
+// Example:
+// testing::ScopedTrace trace("file.cc", 123, "message");
+//
+class GTEST_API_ ScopedTrace {
+ public:
+ // The c'tor pushes the given source file location and message onto
+ // a trace stack maintained by Google Test.
+
+ // Template version. Uses Message() to convert the values into strings.
+ // Slow, but flexible.
+ template <typename T>
+ ScopedTrace(const char* file, int line, const T& message) {
+ PushTrace(file, line, (Message() << message).GetString());
+ }
+
+ // Optimize for some known types.
+ ScopedTrace(const char* file, int line, const char* message) {
+ PushTrace(file, line, message ? message : "(null)");
+ }
+
+#if GTEST_HAS_GLOBAL_STRING
+ ScopedTrace(const char* file, int line, const ::string& message) {
+ PushTrace(file, line, message);
+ }
+#endif
+
+ ScopedTrace(const char* file, int line, const std::string& message) {
+ PushTrace(file, line, message);
+ }
+
+ // The d'tor pops the info pushed by the c'tor.
+ //
+ // Note that the d'tor is not virtual in order to be efficient.
+ // Don't inherit from ScopedTrace!
+ ~ScopedTrace();
+
+ private:
+ void PushTrace(const char* file, int line, std::string message);
+
+ GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace);
+} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its
+ // c'tor and d'tor. Therefore it doesn't
+ // need to be used otherwise.
+
+// Causes a trace (including the source file path, the current line
+// number, and the given message) to be included in every test failure
+// message generated by code in the current scope. The effect is
+// undone when the control leaves the current scope.
+//
+// The message argument can be anything streamable to std::ostream.
+//
+// In the implementation, we include the current line number as part
+// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s
+// to appear in the same block - as long as they are on different
+// lines.
+//
+// Assuming that each thread maintains its own stack of traces.
+// Therefore, a SCOPED_TRACE() would (correctly) only affect the
+// assertions in its own thread.
+#define SCOPED_TRACE(message) \
+ ::testing::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\
+ __FILE__, __LINE__, (message))
+
+
+// Compile-time assertion for type equality.
+// StaticAssertTypeEq<type1, type2>() compiles iff type1 and type2 are
+// the same type. The value it returns is not interesting.
+//
+// Instead of making StaticAssertTypeEq a class template, we make it a
+// function template that invokes a helper class template. This
+// prevents a user from misusing StaticAssertTypeEq<T1, T2> by
+// defining objects of that type.
+//
+// CAVEAT:
+//
+// When used inside a method of a class template,
+// StaticAssertTypeEq<T1, T2>() is effective ONLY IF the method is
+// instantiated. For example, given:
+//
+// template <typename T> class Foo {
+// public:
+// void Bar() { testing::StaticAssertTypeEq<int, T>(); }
+// };
+//
+// the code:
+//
+// void Test1() { Foo<bool> foo; }
+//
+// will NOT generate a compiler error, as Foo<bool>::Bar() is never
+// actually instantiated. Instead, you need:
+//
+// void Test2() { Foo<bool> foo; foo.Bar(); }
+//
+// to cause a compiler error.
+template <typename T1, typename T2>
+bool StaticAssertTypeEq() {
+ (void)internal::StaticAssertTypeEqHelper<T1, T2>();
+ return true;
+}
+
+// Defines a test.
+//
+// The first parameter is the name of the test case, and the second
+// parameter is the name of the test within the test case.
+//
+// The convention is to end the test case name with "Test". For
+// example, a test case for the Foo class can be named FooTest.
+//
+// Test code should appear between braces after an invocation of
+// this macro. Example:
+//
+// TEST(FooTest, InitializesCorrectly) {
+// Foo foo;
+// EXPECT_TRUE(foo.StatusIsOK());
+// }
+
+// Note that we call GetTestTypeId() instead of GetTypeId<
+// ::testing::Test>() here to get the type ID of testing::Test. This
+// is to work around a suspected linker bug when using Google Test as
+// a framework on Mac OS X. The bug causes GetTypeId<
+// ::testing::Test>() to return different values depending on whether
+// the call is from the Google Test framework itself or from user test
+// code. GetTestTypeId() is guaranteed to always return the same
+// value, as it always calls GetTypeId<>() from the Google Test
+// framework.
+#define GTEST_TEST(test_case_name, test_name)\
+ GTEST_TEST_(test_case_name, test_name, \
+ ::testing::Test, ::testing::internal::GetTestTypeId())
+
+// Define this macro to 1 to omit the definition of TEST(), which
+// is a generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_TEST
+# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
+#endif
+
+// Defines a test that uses a test fixture.
+//
+// The first parameter is the name of the test fixture class, which
+// also doubles as the test case name. The second parameter is the
+// name of the test within the test case.
+//
+// A test fixture class must be declared earlier. The user should put
+// the test code between braces after using this macro. Example:
+//
+// class FooTest : public testing::Test {
+// protected:
+// virtual void SetUp() { b_.AddElement(3); }
+//
+// Foo a_;
+// Foo b_;
+// };
+//
+// TEST_F(FooTest, InitializesCorrectly) {
+// EXPECT_TRUE(a_.StatusIsOK());
+// }
+//
+// TEST_F(FooTest, ReturnsElementCountCorrectly) {
+// EXPECT_EQ(a_.size(), 0);
+// EXPECT_EQ(b_.size(), 1);
+// }
+
+#define TEST_F(test_fixture, test_name)\
+ GTEST_TEST_(test_fixture, test_name, test_fixture, \
+ ::testing::internal::GetTypeId<test_fixture>())
+
+// Returns a path to temporary directory.
+// Tries to determine an appropriate directory for the platform.
+GTEST_API_ std::string TempDir();
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+} // namespace testing
+
+// Use this function in main() to run all tests. It returns 0 if all
+// tests are successful, or 1 otherwise.
+//
+// RUN_ALL_TESTS() should be invoked after the command line has been
+// parsed by InitGoogleTest().
+//
+// This function was formerly a macro; thus, it is in the global
+// namespace and has an all-caps name.
+int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_;
+
+inline int RUN_ALL_TESTS() {
+ return ::testing::UnitTest::GetInstance()->Run();
+}
+
+GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
+
+#endif // GTEST_INCLUDE_GTEST_GTEST_H_
diff --git a/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest_main.cc b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest_main.cc
new file mode 100644
index 00000000..2113f621
--- /dev/null
+++ b/storage/rocksdb/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest_main.cc
@@ -0,0 +1,37 @@
+// Copyright 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdio.h>
+#include "gtest/gtest.h"
+
+GTEST_API_ int main(int argc, char **argv) {
+ printf("Running main() from %s\n", __FILE__);
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/thirdparty.inc b/storage/rocksdb/rocksdb/thirdparty.inc
new file mode 100644
index 00000000..25ecdab8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/thirdparty.inc
@@ -0,0 +1,268 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# Edit definitions below to specify paths to include files and libraries of all 3rd party libraries
+
+# TODO: Make this work with find_package and/or get rid of it
+#
+# This example assumes all the libraries locate in directories under THIRDPARTY_HOME environment variable
+# Set environment variable THIRDPARTY_HOME to point to your third party libraries home (Unix style dir separators)
+# or change the paths below to reflect where the libraries actually reside
+#
+set (THIRDPARTY_LIBS "") # Initialization, don't touch
+
+#
+# Defaults
+#
+set(GFLAGS_HOME $ENV{THIRDPARTY_HOME}/Gflags.Library)
+set(GFLAGS_INCLUDE ${GFLAGS_HOME}/build/native/include)
+set(GFLAGS_LIB_DEBUG ${GFLAGS_HOME}/lib/native/debug/amd64/gflags.lib)
+set(GFLAGS_LIB_RELEASE ${GFLAGS_HOME}/lib/native/retail/amd64/gflags.lib)
+
+# ================================================== GFLAGS ==================================================
+# For compatibility
+if (GFLAGS)
+ set(WITH_GFLAGS ON)
+endif ()
+
+if (WITH_GFLAGS)
+ message(STATUS "GFLAGS library is enabled")
+
+ if(DEFINED ENV{GFLAGS_INCLUDE})
+ set(GFLAGS_INCLUDE $ENV{GFLAGS_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{GFLAGS_LIB_DEBUG})
+ set(GFLAGS_LIB_DEBUG $ENV{GFLAGS_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{GFLAGS_LIB_RELEASE})
+ set(GFLAGS_LIB_RELEASE $ENV{GFLAGS_LIB_RELEASE})
+ endif()
+
+ set(GFLAGS_CXX_FLAGS -DGFLAGS=gflags)
+ set(GFLAGS_LIBS debug ${GFLAGS_LIB_DEBUG} optimized ${GFLAGS_LIB_RELEASE})
+
+ add_definitions(${GFLAGS_CXX_FLAGS})
+ include_directories(${GFLAGS_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${GFLAGS_LIBS})
+else ()
+ message(STATUS "GFLAGS library is disabled")
+endif ()
+
+# ================================================== SNAPPY ==================================================
+#
+# Edit these 4 lines to define paths to Snappy
+#
+set(SNAPPY_HOME $ENV{THIRDPARTY_HOME}/Snappy.Library)
+set(SNAPPY_INCLUDE ${SNAPPY_HOME}/build/native/inc/inc)
+set(SNAPPY_LIB_DEBUG ${SNAPPY_HOME}/lib/native/debug/amd64/snappy.lib)
+set(SNAPPY_LIB_RELEASE ${SNAPPY_HOME}/lib/native/retail/amd64/snappy.lib)
+
+# For compatibility
+if(SNAPPY)
+ set(WITH_SNAPPY ON)
+endif ()
+
+if (WITH_SNAPPY)
+ message(STATUS "SNAPPY library is enabled")
+
+ if(DEFINED ENV{SNAPPY_INCLUDE})
+ set(SNAPPY_INCLUDE $ENV{SNAPPY_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{SNAPPY_LIB_DEBUG})
+ set(SNAPPY_LIB_DEBUG $ENV{SNAPPY_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{SNAPPY_LIB_RELEASE})
+ set(SNAPPY_LIB_RELEASE $ENV{SNAPPY_LIB_RELEASE})
+ endif()
+
+ set(SNAPPY_CXX_FLAGS -DSNAPPY)
+ set(SNAPPY_LIBS debug ${SNAPPY_LIB_DEBUG} optimized ${SNAPPY_LIB_RELEASE})
+
+ add_definitions(${SNAPPY_CXX_FLAGS})
+ include_directories(${SNAPPY_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${SNAPPY_LIBS})
+else ()
+ message(STATUS "SNAPPY library is disabled")
+endif ()
+
+# ================================================== LZ4 ==================================================
+#
+# Edit these 4 lines to define paths to LZ4
+#
+set(LZ4_HOME $ENV{THIRDPARTY_HOME}/LZ4.Library)
+set(LZ4_INCLUDE ${LZ4_HOME}/build/native/inc/inc)
+set(LZ4_LIB_DEBUG ${LZ4_HOME}/lib/native/debug/amd64/lz4.lib)
+set(LZ4_LIB_RELEASE ${LZ4_HOME}/lib/native/retail/amd64/lz4.lib)
+
+
+# For compatibility
+if (LZ4)
+ set(WITH_LZ4 ON)
+endif ()
+
+if (WITH_LZ4)
+ message(STATUS "LZ4 library is enabled")
+
+ if(DEFINED ENV{LZ4_INCLUDE})
+ set(LZ4_INCLUDE $ENV{LZ4_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{LZ4_LIB_DEBUG})
+ set(LZ4_LIB_DEBUG $ENV{LZ4_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{LZ4_LIB_RELEASE})
+ set(LZ4_LIB_RELEASE $ENV{LZ4_LIB_RELEASE})
+ endif()
+
+ set(LZ4_CXX_FLAGS -DLZ4)
+ set(LZ4_LIBS debug ${LZ4_LIB_DEBUG} optimized ${LZ4_LIB_RELEASE})
+
+ add_definitions(${LZ4_CXX_FLAGS})
+ include_directories(${LZ4_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${LZ4_LIBS})
+else ()
+ message(STATUS "LZ4 library is disabled")
+endif ()
+
+# ================================================== ZLIB ==================================================
+#
+# Edit these 4 lines to define paths to ZLIB
+#
+set(ZLIB_HOME $ENV{THIRDPARTY_HOME}/ZLIB.Library)
+set(ZLIB_INCLUDE ${ZLIB_HOME}/build/native/inc/inc)
+set(ZLIB_LIB_DEBUG ${ZLIB_HOME}/lib/native/debug/amd64/zlib.lib)
+set(ZLIB_LIB_RELEASE ${ZLIB_HOME}/lib/native/retail/amd64/zlib.lib)
+
+# For compatibilty
+if (ZLIB)
+ set(WITH_ZLIB ON)
+endif ()
+
+if (WITH_ZLIB)
+ message(STATUS "ZLIB library is enabled")
+
+ if(DEFINED ENV{ZLIB_INCLUDE})
+ set(ZLIB_INCLUDE $ENV{ZLIB_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{ZLIB_LIB_DEBUG})
+ set(ZLIB_LIB_DEBUG $ENV{ZLIB_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{ZLIB_LIB_RELEASE})
+ set(ZLIB_LIB_RELEASE $ENV{ZLIB_LIB_RELEASE})
+ endif()
+
+ set(ZLIB_CXX_FLAGS -DZLIB)
+ set(ZLIB_LIBS debug ${ZLIB_LIB_DEBUG} optimized ${ZLIB_LIB_RELEASE})
+
+ add_definitions(${ZLIB_CXX_FLAGS})
+ include_directories(${ZLIB_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${ZLIB_LIBS})
+else ()
+ message(STATUS "ZLIB library is disabled")
+endif ()
+
+# ================================================== XPRESS ==================================================
+# This makes use of built-in Windows API, no additional includes, links to a system lib
+
+# For compatibilty
+if (XPRESS)
+ set(WITH_XPRESS ON)
+endif ()
+
+if (WITH_XPRESS)
+ message(STATUS "XPRESS is enabled")
+
+ add_definitions(-DXPRESS)
+
+ # We are using the implementation provided by the system
+ set (SYSTEM_LIBS ${SYSTEM_LIBS} Cabinet.lib)
+else ()
+ message(STATUS "XPRESS is disabled")
+endif ()
+
+
+# ================================================== ZSTD ==================================================
+#
+# Edit these 4 lines to define paths to ZSTD
+#
+set(ZSTD_HOME $ENV{THIRDPARTY_HOME}/ZSTD.Library)
+set(ZSTD_INCLUDE ${ZSTD_HOME}/build/native/inc)
+set(ZSTD_LIB_DEBUG ${ZSTD_HOME}/lib/native/debug/amd64/libzstd_static.lib)
+set(ZSTD_LIB_RELEASE ${ZSTD_HOME}/lib/native/retail/amd64/libzstd_static.lib)
+
+# For compatibility
+if (ZSTD)
+ set(WITH_ZSTD ON)
+endif ()
+
+if (WITH_ZSTD)
+ message(STATUS "ZSTD library is enabled")
+
+ if(DEFINED ENV{ZSTD_INCLUDE})
+ set(ZSTD_INCLUDE $ENV{ZSTD_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{ZSTD_LIB_DEBUG})
+ set(ZSTD_LIB_DEBUG $ENV{ZSTD_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{ZSTD_LIB_RELEASE})
+ set(ZSTD_LIB_RELEASE $ENV{ZSTD_LIB_RELEASE})
+ endif()
+
+ # ZSTD_STATIC_LINKING_ONLY only allows us to create an allocation functions override
+ # When jemalloc is in use
+ set(ZSTD_LIBS debug ${ZSTD_LIB_DEBUG} optimized ${ZSTD_LIB_RELEASE})
+
+ add_definitions(-DZSTD -DZSTD_STATIC_LINKING_ONLY)
+ include_directories(${ZSTD_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${ZSTD_LIBS})
+else ()
+ message(STATUS "ZSTD library is disabled")
+endif ()
+
+#
+# Edit these 4 lines to define paths to Jemalloc
+#
+set(JEMALLOC_HOME $ENV{THIRDPARTY_HOME}/Jemalloc.Library)
+set(JEMALLOC_INCLUDE ${JEMALLOC_HOME}/build/native/inc)
+set(JEMALLOC_LIB_DEBUG ${JEMALLOC_HOME}/lib/native/debug/amd64/jemalloc.lib)
+set(JEMALLOC_LIB_RELEASE ${JEMALLOC_HOME}/lib/native/retail/amd64/jemalloc.lib)
+
+# ================================================== JEMALLOC ==================================================
+if(JEMALLOC)
+ set(WITH_JEMALLOC ON)
+endif()
+
+if (WITH_JEMALLOC)
+ message(STATUS "JEMALLOC library is enabled")
+ set(JEMALLOC_CXX_FLAGS "-DROCKSDB_JEMALLOC -DJEMALLOC_EXPORT= -DJEMALLOC_NO_RENAME")
+
+ if(DEFINED ENV{JEMALLOC_INCLUDE})
+ set(JEMALLOC_INCLUDE $ENV{JEMALLOC_INCLUDE})
+ endif()
+
+ if(DEFINED ENV{JEMALLOC_LIB_DEBUG})
+ set(JEMALLOC_LIB_DEBUG $ENV{JEMALLOC_LIB_DEBUG})
+ endif()
+
+ if(DEFINED ENV{JEMALLOC_LIB_RELEASE})
+ set(JEMALLOC_LIB_RELEASE $ENV{JEMALLOC_LIB_RELEASE})
+ endif()
+
+ set(JEMALLOC_LIBS debug ${JEMALLOC_LIB_DEBUG} optimized ${JEMALLOC_LIB_RELEASE})
+
+ add_definitions(${JEMALLOC_CXX_FLAGS})
+ include_directories(${JEMALLOC_INCLUDE})
+ set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${JEMALLOC_LIBS})
+ set (ARTIFACT_SUFFIX "_je")
+
+else ()
+ set (ARTIFACT_SUFFIX "")
+ message(STATUS "JEMALLOC library is disabled")
+endif ()
diff --git a/storage/rocksdb/rocksdb/tools/CMakeLists.txt b/storage/rocksdb/rocksdb/tools/CMakeLists.txt
new file mode 100644
index 00000000..50ee3b1b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(TOOLS
+ sst_dump.cc
+ db_sanity_test.cc
+ write_stress.cc
+ ldb.cc
+ db_repl_stress.cc
+ dump/rocksdb_dump.cc
+ dump/rocksdb_undump.cc)
+foreach(src ${TOOLS})
+ get_filename_component(exename ${src} NAME_WE)
+ add_executable(${exename}${ARTIFACT_SUFFIX}
+ ${src})
+ target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${ROCKSDB_LIB})
+ list(APPEND tool_deps ${exename})
+endforeach()
+
+list(APPEND tool_deps)
+
+add_custom_target(ldb_tests
+ COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/ldb_tests.py
+ DEPENDS ldb)
diff --git a/storage/rocksdb/rocksdb/tools/Dockerfile b/storage/rocksdb/rocksdb/tools/Dockerfile
new file mode 100644
index 00000000..1d5ead7f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/Dockerfile
@@ -0,0 +1,5 @@
+FROM buildpack-deps:wheezy
+
+ADD ./ldb /rocksdb/tools/ldb
+
+CMD /rocksdb/tools/ldb
diff --git a/storage/rocksdb/rocksdb/tools/advisor/README.md b/storage/rocksdb/rocksdb/tools/advisor/README.md
new file mode 100644
index 00000000..f1e7165e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/README.md
@@ -0,0 +1,96 @@
+# Rocksdb Tuning Advisor
+
+## Motivation
+
+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/master/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/master/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/master/tools/advisor/advisor/rule_parser_example.py)
+creates appropriate DataSource objects (for Rocksdb
+[logs](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_log_parser.py),
+[options](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_options_parser.py),
+[statistics](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_stats_fetcher.py) etc.)
+and provides them to the [Rules Engine](https://github.com/facebook/rocksdb/blob/master/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
+
+### Prerequisites
+The tool needs the following to run:
+* python3
+
+### Running the tool
+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
+```
+
+### Command-line arguments
+
+Most important amongst all the input that the Advisor needs, are the rules
+spec and starting Rocksdb configuration. The configuration is provided as the
+familiar Rocksdb Options file (refer [example](https://github.com/facebook/rocksdb/blob/master/examples/rocksdb_option_file_example.ini)).
+The Rules spec is written in the INI format (more details in
+[rules.ini](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rules.ini)).
+
+In brief, a Rule is made of conditions and is triggered when all its
+constituent conditions are triggered. When triggered, a Rule suggests changes
+(increase/decrease/set to a suggested value) to certain Rocksdb options that
+aim to improve Rocksdb performance. Every Condition has a 'source' i.e.
+the data source that would be checked for triggering that condition.
+For example, a log Condition (with 'source=LOG') is triggered if a particular
+'regex' is found in the Rocksdb LOG files. As of now the Rules Engine
+supports 3 types of Conditions (and consequently data-sources):
+LOG, OPTIONS, TIME_SERIES. The TIME_SERIES data can be sourced from the
+Rocksdb [statistics](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/statistics.h)
+or [perf context](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/perf_context.h).
+
+For more information about the remaining command-line arguments, run:
+
+```shell
+cd rocksdb/tools/advisor
+python3 -m advisor.rule_parser_example --help
+```
+
+### Sample output
+
+Here, 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'}
+```
+
+## Running the tests
+
+Tests for the code have been added to the
+[test/](https://github.com/facebook/rocksdb/tree/master/tools/advisor/test)
+directory. For example, to run the unit tests for db_log_parser.py:
+
+```shell
+cd rocksdb/tools/advisor
+python3 -m unittest -v test.test_db_log_parser
+```
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/__init__.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/__init__.py
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/bench_runner.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/bench_runner.py
new file mode 100644
index 00000000..7c7ee788
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/bench_runner.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from abc import ABC, abstractmethod
+import re
+
+
+class BenchmarkRunner(ABC):
+ @staticmethod
+ @abstractmethod
+ def is_metric_better(new_metric, old_metric):
+ pass
+
+ @abstractmethod
+ def run_experiment(self):
+ # should return a list of DataSource objects
+ pass
+
+ @staticmethod
+ def get_info_log_file_name(log_dir, db_path):
+ # Example: DB Path = /dev/shm and OPTIONS file has option
+ # db_log_dir=/tmp/rocks/, then the name of the log file will be
+ # 'dev_shm_LOG' and its location will be /tmp/rocks. If db_log_dir is
+ # not specified in the OPTIONS file, then the location of the log file
+ # will be /dev/shm and the name of the file will be 'LOG'
+ file_name = ''
+ if log_dir:
+ # refer GetInfoLogPrefix() in rocksdb/util/filename.cc
+ # example db_path: /dev/shm/dbbench
+ file_name = db_path[1:] # to ignore the leading '/' character
+ to_be_replaced = re.compile('[^0-9a-zA-Z\-_\.]')
+ for character in to_be_replaced.findall(db_path):
+ file_name = file_name.replace(character, '_')
+ if not file_name.endswith('_'):
+ file_name += '_'
+ file_name += 'LOG'
+ return file_name
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/config_optimizer_example.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/config_optimizer_example.py
new file mode 100644
index 00000000..e3736387
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/config_optimizer_example.py
@@ -0,0 +1,134 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (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 argparse
+from advisor.db_config_optimizer import ConfigOptimizer
+from advisor.db_log_parser import NO_COL_FAMILY
+from advisor.db_options_parser import DatabaseOptions
+from advisor.rule_parser import RulesSpec
+
+
+CONFIG_OPT_NUM_ITER = 10
+
+
+def main(args):
+ # initialise the RulesSpec parser
+ rule_spec_parser = RulesSpec(args.rules_spec)
+ # initialise the benchmark runner
+ bench_runner_module = __import__(
+ args.benchrunner_module, fromlist=[args.benchrunner_class]
+ )
+ bench_runner_class = getattr(bench_runner_module, args.benchrunner_class)
+ ods_args = {}
+ if args.ods_client and args.ods_entity:
+ ods_args['client_script'] = args.ods_client
+ ods_args['entity'] = args.ods_entity
+ if args.ods_key_prefix:
+ ods_args['key_prefix'] = args.ods_key_prefix
+ db_bench_runner = bench_runner_class(args.benchrunner_pos_args, ods_args)
+ # initialise the database configuration
+ db_options = DatabaseOptions(args.rocksdb_options, args.misc_options)
+ # set the frequency at which stats are dumped in the LOG file and the
+ # location of the LOG file.
+ db_log_dump_settings = {
+ "DBOptions.stats_dump_period_sec": {
+ NO_COL_FAMILY: args.stats_dump_period_sec
+ }
+ }
+ db_options.update_options(db_log_dump_settings)
+ # initialise the configuration optimizer
+ config_optimizer = ConfigOptimizer(
+ db_bench_runner,
+ db_options,
+ rule_spec_parser,
+ args.base_db_path
+ )
+ # run the optimiser to improve the database configuration for given
+ # benchmarks, with the help of expert-specified rules
+ final_db_options = config_optimizer.run()
+ # generate the final rocksdb options file
+ print(
+ 'Final configuration in: ' +
+ final_db_options.generate_options_config('final')
+ )
+ print(
+ 'Final miscellaneous options: ' +
+ repr(final_db_options.get_misc_options())
+ )
+
+
+if __name__ == '__main__':
+ '''
+ An example run of this tool from the command-line would look like:
+ python3 -m advisor.config_optimizer_example
+ --base_db_path=/tmp/rocksdbtest-155919/dbbench
+ --rocksdb_options=temp/OPTIONS_boot.tmp --misc_options bloom_bits=2
+ --rules_spec=advisor/rules.ini --stats_dump_period_sec=20
+ --benchrunner_module=advisor.db_bench_runner
+ --benchrunner_class=DBBenchRunner --benchrunner_pos_args ./../../db_bench
+ readwhilewriting use_existing_db=true duration=90
+ '''
+ parser = argparse.ArgumentParser(description='This script is used for\
+ searching for a better database configuration')
+ parser.add_argument(
+ '--rocksdb_options', required=True, type=str,
+ help='path of the starting Rocksdb OPTIONS file'
+ )
+ # these are options that are column-family agnostic and are not yet
+ # supported by the Rocksdb Options file: eg. bloom_bits=2
+ parser.add_argument(
+ '--misc_options', nargs='*',
+ help='whitespace-separated list of options that are not supported ' +
+ 'by the Rocksdb OPTIONS file, given in the ' +
+ '<option_name>=<option_value> format eg. "bloom_bits=2 ' +
+ 'rate_limiter_bytes_per_sec=128000000"')
+ parser.add_argument(
+ '--base_db_path', required=True, type=str,
+ help='path for the Rocksdb database'
+ )
+ parser.add_argument(
+ '--rules_spec', required=True, type=str,
+ help='path of the file containing the expert-specified Rules'
+ )
+ parser.add_argument(
+ '--stats_dump_period_sec', required=True, type=int,
+ help='the frequency (in seconds) at which STATISTICS are printed to ' +
+ 'the Rocksdb LOG file'
+ )
+ # ODS arguments
+ parser.add_argument(
+ '--ods_client', type=str, help='the ODS client binary'
+ )
+ parser.add_argument(
+ '--ods_entity', type=str,
+ help='the servers for which the ODS stats need to be fetched'
+ )
+ parser.add_argument(
+ '--ods_key_prefix', type=str,
+ help='the prefix that needs to be attached to the keys of time ' +
+ 'series to be fetched from ODS'
+ )
+ # benchrunner_module example: advisor.db_benchmark_client
+ parser.add_argument(
+ '--benchrunner_module', required=True, type=str,
+ help='the module containing the BenchmarkRunner class to be used by ' +
+ 'the Optimizer, example: advisor.db_bench_runner'
+ )
+ # benchrunner_class example: DBBenchRunner
+ parser.add_argument(
+ '--benchrunner_class', required=True, type=str,
+ help='the name of the BenchmarkRunner class to be used by the ' +
+ 'Optimizer, should be present in the module provided in the ' +
+ 'benchrunner_module argument, example: DBBenchRunner'
+ )
+ parser.add_argument(
+ '--benchrunner_pos_args', nargs='*',
+ help='whitespace-separated positional arguments that are passed on ' +
+ 'to the constructor of the BenchmarkRunner class provided in the ' +
+ 'benchrunner_class argument, example: "use_existing_db=true ' +
+ 'duration=900"'
+ )
+ args = parser.parse_args()
+ main(args)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_bench_runner.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_bench_runner.py
new file mode 100644
index 00000000..54424440
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_bench_runner.py
@@ -0,0 +1,245 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.bench_runner import BenchmarkRunner
+from advisor.db_log_parser import DataSource, DatabaseLogs, NO_COL_FAMILY
+from advisor.db_stats_fetcher import (
+ LogStatsParser, OdsStatsFetcher, DatabasePerfContext
+)
+import shutil
+import subprocess
+import time
+
+
+'''
+NOTE: This is not thread-safe, because the output file is simply overwritten.
+'''
+
+
+class DBBenchRunner(BenchmarkRunner):
+ OUTPUT_FILE = "temp/dbbench_out.tmp"
+ ERROR_FILE = "temp/dbbench_err.tmp"
+ DB_PATH = "DB path"
+ THROUGHPUT = "ops/sec"
+ PERF_CON = " PERF_CONTEXT:"
+
+ @staticmethod
+ def is_metric_better(new_metric, old_metric):
+ # for db_bench 'throughput' is the metric returned by run_experiment
+ return new_metric >= old_metric
+
+ @staticmethod
+ def get_opt_args_str(misc_options_dict):
+ # given a dictionary of options and their values, return a string
+ # that can be appended as command-line arguments
+ optional_args_str = ""
+ for option_name, option_value in misc_options_dict.items():
+ if option_value:
+ optional_args_str += (
+ " --" + option_name + "=" + str(option_value)
+ )
+ return optional_args_str
+
+ def __init__(self, positional_args, ods_args=None):
+ # parse positional_args list appropriately
+ self.db_bench_binary = positional_args[0]
+ self.benchmark = positional_args[1]
+ self.db_bench_args = None
+ if len(positional_args) > 2:
+ # options list with each option given as "<option>=<value>"
+ self.db_bench_args = positional_args[2:]
+ # save ods_args, if provided
+ self.ods_args = ods_args
+
+ def _parse_output(self, get_perf_context=False):
+ '''
+ Sample db_bench output after running 'readwhilewriting' benchmark:
+ DB path: [/tmp/rocksdbtest-155919/dbbench]\n
+ readwhilewriting : 16.582 micros/op 60305 ops/sec; 4.2 MB/s (3433828\
+ of 5427999 found)\n
+ PERF_CONTEXT:\n
+ user_key_comparison_count = 500466712, block_cache_hit_count = ...\n
+ '''
+ output = {
+ self.THROUGHPUT: None, self.DB_PATH: None, self.PERF_CON: None
+ }
+ perf_context_begins = False
+ with open(self.OUTPUT_FILE, 'r') as fp:
+ for line in fp:
+ if line.startswith(self.benchmark):
+ # line from sample output:
+ # readwhilewriting : 16.582 micros/op 60305 ops/sec; \
+ # 4.2 MB/s (3433828 of 5427999 found)\n
+ print(line) # print output of the benchmark run
+ token_list = line.strip().split()
+ for ix, token in enumerate(token_list):
+ if token.startswith(self.THROUGHPUT):
+ # in above example, throughput = 60305 ops/sec
+ output[self.THROUGHPUT] = (
+ float(token_list[ix - 1])
+ )
+ break
+ elif get_perf_context and line.startswith(self.PERF_CON):
+ # the following lines in the output contain perf context
+ # statistics (refer example above)
+ perf_context_begins = True
+ elif get_perf_context and perf_context_begins:
+ # Sample perf_context output:
+ # user_key_comparison_count = 500, block_cache_hit_count =\
+ # 468, block_read_count = 580, block_read_byte = 445, ...
+ token_list = line.strip().split(',')
+ # token_list = ['user_key_comparison_count = 500',
+ # 'block_cache_hit_count = 468','block_read_count = 580'...
+ perf_context = {
+ tk.split('=')[0].strip(): tk.split('=')[1].strip()
+ for tk in token_list
+ if tk
+ }
+ # TODO(poojam23): this is a hack and should be replaced
+ # with the timestamp that db_bench will provide per printed
+ # perf_context
+ timestamp = int(time.time())
+ perf_context_ts = {}
+ for stat in perf_context.keys():
+ perf_context_ts[stat] = {
+ timestamp: int(perf_context[stat])
+ }
+ output[self.PERF_CON] = perf_context_ts
+ perf_context_begins = False
+ elif line.startswith(self.DB_PATH):
+ # line from sample output:
+ # DB path: [/tmp/rocksdbtest-155919/dbbench]\n
+ output[self.DB_PATH] = (
+ line.split('[')[1].split(']')[0]
+ )
+ return output
+
+ def get_log_options(self, db_options, db_path):
+ # get the location of the LOG file and the frequency at which stats are
+ # dumped in the LOG file
+ log_dir_path = None
+ stats_freq_sec = None
+ logs_file_prefix = None
+
+ # fetch frequency at which the stats are dumped in the Rocksdb logs
+ dump_period = 'DBOptions.stats_dump_period_sec'
+ # fetch the directory, if specified, in which the Rocksdb logs are
+ # dumped, by default logs are dumped in same location as database
+ log_dir = 'DBOptions.db_log_dir'
+ log_options = db_options.get_options([dump_period, log_dir])
+ if dump_period in log_options:
+ stats_freq_sec = int(log_options[dump_period][NO_COL_FAMILY])
+ if log_dir in log_options:
+ log_dir_path = log_options[log_dir][NO_COL_FAMILY]
+
+ log_file_name = DBBenchRunner.get_info_log_file_name(
+ log_dir_path, db_path
+ )
+
+ if not log_dir_path:
+ log_dir_path = db_path
+ if not log_dir_path.endswith('/'):
+ log_dir_path += '/'
+
+ logs_file_prefix = log_dir_path + log_file_name
+ return (logs_file_prefix, stats_freq_sec)
+
+ def _get_options_command_line_args_str(self, curr_options):
+ '''
+ This method uses the provided Rocksdb OPTIONS to create a string of
+ command-line arguments for db_bench.
+ The --options_file argument is always given and the options that are
+ not supported by the OPTIONS file are given as separate arguments.
+ '''
+ optional_args_str = DBBenchRunner.get_opt_args_str(
+ curr_options.get_misc_options()
+ )
+ # generate an options configuration file
+ options_file = curr_options.generate_options_config(nonce='12345')
+ optional_args_str += " --options_file=" + options_file
+ return optional_args_str
+
+ def _setup_db_before_experiment(self, curr_options, db_path):
+ # remove destination directory if it already exists
+ try:
+ shutil.rmtree(db_path, ignore_errors=True)
+ except OSError as e:
+ print('Error: rmdir ' + e.filename + ' ' + e.strerror)
+ # setup database with a million keys using the fillrandom benchmark
+ command = "%s --benchmarks=fillrandom --db=%s --num=1000000" % (
+ self.db_bench_binary, db_path
+ )
+ args_str = self._get_options_command_line_args_str(curr_options)
+ command += args_str
+ self._run_command(command)
+
+ def _build_experiment_command(self, curr_options, db_path):
+ command = "%s --benchmarks=%s --statistics --perf_level=3 --db=%s" % (
+ self.db_bench_binary, self.benchmark, db_path
+ )
+ # fetch the command-line arguments string for providing Rocksdb options
+ args_str = self._get_options_command_line_args_str(curr_options)
+ # handle the command-line args passed in the constructor, these
+ # arguments are specific to db_bench
+ for cmd_line_arg in self.db_bench_args:
+ args_str += (" --" + cmd_line_arg)
+ command += args_str
+ return command
+
+ def _run_command(self, command):
+ out_file = open(self.OUTPUT_FILE, "w+")
+ err_file = open(self.ERROR_FILE, "w+")
+ print('executing... - ' + command)
+ subprocess.call(command, shell=True, stdout=out_file, stderr=err_file)
+ out_file.close()
+ err_file.close()
+
+ def run_experiment(self, db_options, db_path):
+ # setup the Rocksdb database before running experiment
+ self._setup_db_before_experiment(db_options, db_path)
+ # get the command to run the experiment
+ command = self._build_experiment_command(db_options, db_path)
+ experiment_start_time = int(time.time())
+ # run experiment
+ self._run_command(command)
+ experiment_end_time = int(time.time())
+ # parse the db_bench experiment output
+ parsed_output = self._parse_output(get_perf_context=True)
+
+ # get the log files path prefix and frequency at which Rocksdb stats
+ # are dumped in the logs
+ logs_file_prefix, stats_freq_sec = self.get_log_options(
+ db_options, parsed_output[self.DB_PATH]
+ )
+ # create the Rocksbd LOGS object
+ db_logs = DatabaseLogs(
+ logs_file_prefix, db_options.get_column_families()
+ )
+ # Create the Log STATS object
+ db_log_stats = LogStatsParser(logs_file_prefix, stats_freq_sec)
+ # Create the PerfContext STATS object
+ db_perf_context = DatabasePerfContext(
+ parsed_output[self.PERF_CON], 0, False
+ )
+ # create the data-sources dictionary
+ data_sources = {
+ DataSource.Type.DB_OPTIONS: [db_options],
+ DataSource.Type.LOG: [db_logs],
+ DataSource.Type.TIME_SERIES: [db_log_stats, db_perf_context]
+ }
+ # Create the ODS STATS object
+ if self.ods_args:
+ key_prefix = ''
+ if 'key_prefix' in self.ods_args:
+ key_prefix = self.ods_args['key_prefix']
+ data_sources[DataSource.Type.TIME_SERIES].append(OdsStatsFetcher(
+ self.ods_args['client_script'],
+ self.ods_args['entity'],
+ experiment_start_time,
+ experiment_end_time,
+ key_prefix
+ ))
+ # return the experiment's data-sources and throughput
+ return data_sources, parsed_output[self.THROUGHPUT]
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_config_optimizer.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_config_optimizer.py
new file mode 100644
index 00000000..508c0f8f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_config_optimizer.py
@@ -0,0 +1,282 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_log_parser import NO_COL_FAMILY
+from advisor.db_options_parser import DatabaseOptions
+from advisor.rule_parser import Suggestion
+import copy
+import random
+
+
+class ConfigOptimizer:
+ SCOPE = 'scope'
+ SUGG_VAL = 'suggested values'
+
+ @staticmethod
+ def apply_action_on_value(old_value, action, suggested_values):
+ chosen_sugg_val = None
+ if suggested_values:
+ chosen_sugg_val = random.choice(list(suggested_values))
+ new_value = None
+ if action is Suggestion.Action.set or not old_value:
+ assert(chosen_sugg_val)
+ new_value = chosen_sugg_val
+ else:
+ # For increase/decrease actions, currently the code tries to make
+ # a 30% change in the option's value per iteration. An addend is
+ # also present (+1 or -1) to handle the cases when the option's
+ # old value was 0 or the final int() conversion suppressed the 30%
+ # change made to the option
+ old_value = float(old_value)
+ mul = 0
+ add = 0
+ if action is Suggestion.Action.increase:
+ if old_value < 0:
+ mul = 0.7
+ add = 2
+ else:
+ mul = 1.3
+ add = 2
+ elif action is Suggestion.Action.decrease:
+ if old_value < 0:
+ mul = 1.3
+ add = -2
+ else:
+ mul = 0.7
+ add = -2
+ new_value = int(old_value * mul + add)
+ return new_value
+
+ @staticmethod
+ def improve_db_config(options, rule, suggestions_dict):
+ # this method takes ONE 'rule' and applies all its suggestions on the
+ # appropriate options
+ required_options = []
+ rule_suggestions = []
+ for sugg_name in rule.get_suggestions():
+ option = suggestions_dict[sugg_name].option
+ action = suggestions_dict[sugg_name].action
+ # A Suggestion in the rules spec must have the 'option' and
+ # 'action' fields defined, always call perform_checks() method
+ # after parsing the rules file using RulesSpec
+ assert(option)
+ assert(action)
+ required_options.append(option)
+ rule_suggestions.append(suggestions_dict[sugg_name])
+ current_config = options.get_options(required_options)
+ # Create the updated configuration from the rule's suggestions
+ updated_config = {}
+ for sugg in rule_suggestions:
+ # case: when the option is not present in the current configuration
+ if sugg.option not in current_config:
+ try:
+ new_value = ConfigOptimizer.apply_action_on_value(
+ None, sugg.action, sugg.suggested_values
+ )
+ if sugg.option not in updated_config:
+ updated_config[sugg.option] = {}
+ if DatabaseOptions.is_misc_option(sugg.option):
+ # this suggestion is on an option that is not yet
+ # supported by the Rocksdb OPTIONS file and so it is
+ # not prefixed by a section type.
+ updated_config[sugg.option][NO_COL_FAMILY] = new_value
+ else:
+ for col_fam in rule.get_trigger_column_families():
+ updated_config[sugg.option][col_fam] = new_value
+ except AssertionError:
+ print(
+ 'WARNING(ConfigOptimizer): provide suggested_values ' +
+ 'for ' + sugg.option
+ )
+ continue
+ # case: when the option is present in the current configuration
+ if NO_COL_FAMILY in current_config[sugg.option]:
+ old_value = current_config[sugg.option][NO_COL_FAMILY]
+ try:
+ new_value = ConfigOptimizer.apply_action_on_value(
+ old_value, sugg.action, sugg.suggested_values
+ )
+ if sugg.option not in updated_config:
+ updated_config[sugg.option] = {}
+ updated_config[sugg.option][NO_COL_FAMILY] = new_value
+ except AssertionError:
+ print(
+ 'WARNING(ConfigOptimizer): provide suggested_values ' +
+ 'for ' + sugg.option
+ )
+ else:
+ for col_fam in rule.get_trigger_column_families():
+ old_value = None
+ if col_fam in current_config[sugg.option]:
+ old_value = current_config[sugg.option][col_fam]
+ try:
+ new_value = ConfigOptimizer.apply_action_on_value(
+ old_value, sugg.action, sugg.suggested_values
+ )
+ if sugg.option not in updated_config:
+ updated_config[sugg.option] = {}
+ updated_config[sugg.option][col_fam] = new_value
+ except AssertionError:
+ print(
+ 'WARNING(ConfigOptimizer): provide ' +
+ 'suggested_values for ' + sugg.option
+ )
+ return current_config, updated_config
+
+ @staticmethod
+ def pick_rule_to_apply(rules, last_rule_name, rules_tried, backtrack):
+ if not rules:
+ print('\nNo more rules triggered!')
+ return None
+ # if the last rule provided an improvement in the database performance,
+ # and it was triggered again (i.e. it is present in 'rules'), then pick
+ # the same rule for this iteration too.
+ if last_rule_name and not backtrack:
+ for rule in rules:
+ if rule.name == last_rule_name:
+ return rule
+ # there was no previous rule OR the previous rule did not improve db
+ # performance OR it was not triggered for this iteration,
+ # then pick another rule that has not been tried yet
+ for rule in rules:
+ if rule.name not in rules_tried:
+ return rule
+ print('\nAll rules have been exhausted')
+ return None
+
+ @staticmethod
+ def apply_suggestions(
+ triggered_rules,
+ current_rule_name,
+ rules_tried,
+ backtrack,
+ curr_options,
+ suggestions_dict
+ ):
+ curr_rule = ConfigOptimizer.pick_rule_to_apply(
+ triggered_rules, current_rule_name, rules_tried, backtrack
+ )
+ if not curr_rule:
+ return tuple([None]*4)
+ # if a rule has been picked for improving db_config, update rules_tried
+ rules_tried.add(curr_rule.name)
+ # get updated config based on the picked rule
+ curr_conf, updated_conf = ConfigOptimizer.improve_db_config(
+ curr_options, curr_rule, suggestions_dict
+ )
+ conf_diff = DatabaseOptions.get_options_diff(curr_conf, updated_conf)
+ if not conf_diff: # the current and updated configs are the same
+ curr_rule, rules_tried, curr_conf, updated_conf = (
+ ConfigOptimizer.apply_suggestions(
+ triggered_rules,
+ None,
+ rules_tried,
+ backtrack,
+ curr_options,
+ suggestions_dict
+ )
+ )
+ print('returning from apply_suggestions')
+ return (curr_rule, rules_tried, curr_conf, updated_conf)
+
+ # TODO(poojam23): check if this method is required or can we directly set
+ # the config equal to the curr_config
+ @staticmethod
+ def get_backtrack_config(curr_config, updated_config):
+ diff = DatabaseOptions.get_options_diff(curr_config, updated_config)
+ bt_config = {}
+ for option in diff:
+ bt_config[option] = {}
+ for col_fam in diff[option]:
+ bt_config[option][col_fam] = diff[option][col_fam][0]
+ print(bt_config)
+ return bt_config
+
+ def __init__(self, bench_runner, db_options, rule_parser, base_db):
+ self.bench_runner = bench_runner
+ self.db_options = db_options
+ self.rule_parser = rule_parser
+ self.base_db_path = base_db
+
+ def run(self):
+ # In every iteration of this method's optimization loop we pick ONE
+ # RULE from all the triggered rules and apply all its suggestions to
+ # the appropriate options.
+ # bootstrapping the optimizer
+ print('Bootstrapping optimizer:')
+ options = copy.deepcopy(self.db_options)
+ old_data_sources, old_metric = (
+ self.bench_runner.run_experiment(options, self.base_db_path)
+ )
+ print('Initial metric: ' + str(old_metric))
+ self.rule_parser.load_rules_from_spec()
+ self.rule_parser.perform_section_checks()
+ triggered_rules = self.rule_parser.get_triggered_rules(
+ old_data_sources, options.get_column_families()
+ )
+ print('\nTriggered:')
+ self.rule_parser.print_rules(triggered_rules)
+ backtrack = False
+ rules_tried = set()
+ curr_rule, rules_tried, curr_conf, updated_conf = (
+ ConfigOptimizer.apply_suggestions(
+ triggered_rules,
+ None,
+ rules_tried,
+ backtrack,
+ options,
+ self.rule_parser.get_suggestions_dict()
+ )
+ )
+ # the optimizer loop
+ while curr_rule:
+ print('\nRule picked for next iteration:')
+ print(curr_rule.name)
+ print('\ncurrent config:')
+ print(curr_conf)
+ print('updated config:')
+ print(updated_conf)
+ options.update_options(updated_conf)
+ # run bench_runner with updated config
+ new_data_sources, new_metric = (
+ self.bench_runner.run_experiment(options, self.base_db_path)
+ )
+ print('\nnew metric: ' + str(new_metric))
+ backtrack = not self.bench_runner.is_metric_better(
+ new_metric, old_metric
+ )
+ # update triggered_rules, metric, data_sources, if required
+ if backtrack:
+ # revert changes to options config
+ print('\nBacktracking to previous configuration')
+ backtrack_conf = ConfigOptimizer.get_backtrack_config(
+ curr_conf, updated_conf
+ )
+ options.update_options(backtrack_conf)
+ else:
+ # run advisor on new data sources
+ self.rule_parser.load_rules_from_spec() # reboot the advisor
+ self.rule_parser.perform_section_checks()
+ triggered_rules = self.rule_parser.get_triggered_rules(
+ new_data_sources, options.get_column_families()
+ )
+ print('\nTriggered:')
+ self.rule_parser.print_rules(triggered_rules)
+ old_metric = new_metric
+ old_data_sources = new_data_sources
+ rules_tried = set()
+ # pick rule to work on and set curr_rule to that
+ curr_rule, rules_tried, curr_conf, updated_conf = (
+ ConfigOptimizer.apply_suggestions(
+ triggered_rules,
+ curr_rule.name,
+ rules_tried,
+ backtrack,
+ options,
+ self.rule_parser.get_suggestions_dict()
+ )
+ )
+ # return the final database options configuration
+ return options
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_log_parser.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_log_parser.py
new file mode 100644
index 00000000..efd41a81
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_log_parser.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from abc import ABC, abstractmethod
+from calendar import timegm
+from enum import Enum
+import glob
+import re
+import time
+
+
+NO_COL_FAMILY = 'DB_WIDE'
+
+
+class DataSource(ABC):
+ class Type(Enum):
+ LOG = 1
+ DB_OPTIONS = 2
+ TIME_SERIES = 3
+
+ def __init__(self, type):
+ self.type = type
+
+ @abstractmethod
+ def check_and_trigger_conditions(self, conditions):
+ pass
+
+
+class Log:
+ @staticmethod
+ def is_new_log(log_line):
+ # The assumption is that a new log will start with a date printed in
+ # the below regex format.
+ date_regex = '\d{4}/\d{2}/\d{2}-\d{2}:\d{2}:\d{2}\.\d{6}'
+ return re.match(date_regex, log_line)
+
+ def __init__(self, log_line, column_families):
+ token_list = log_line.strip().split()
+ self.time = token_list[0]
+ self.context = token_list[1]
+ self.message = " ".join(token_list[2:])
+ self.column_family = None
+ # example log for 'default' column family:
+ # "2018/07/25-17:29:05.176080 7f969de68700 [db/compaction_job.cc:1634]
+ # [default] [JOB 3] Compacting 24@0 + 16@1 files to L1, score 6.00\n"
+ for col_fam in column_families:
+ search_for_str = '\[' + col_fam + '\]'
+ if re.search(search_for_str, self.message):
+ self.column_family = col_fam
+ break
+ if not self.column_family:
+ self.column_family = NO_COL_FAMILY
+
+ def get_human_readable_time(self):
+ # example from a log line: '2018/07/25-11:25:45.782710'
+ return self.time
+
+ def get_column_family(self):
+ return self.column_family
+
+ def get_context(self):
+ return self.context
+
+ def get_message(self):
+ return self.message
+
+ def append_message(self, remaining_log):
+ self.message = self.message + '\n' + remaining_log.strip()
+
+ def get_timestamp(self):
+ # example: '2018/07/25-11:25:45.782710' will be converted to the GMT
+ # Unix timestamp 1532517945 (note: this method assumes that self.time
+ # is in GMT)
+ hr_time = self.time + 'GMT'
+ timestamp = timegm(time.strptime(hr_time, "%Y/%m/%d-%H:%M:%S.%f%Z"))
+ return timestamp
+
+ def __repr__(self):
+ return (
+ 'time: ' + self.time + '; context: ' + self.context +
+ '; col_fam: ' + self.column_family +
+ '; message: ' + self.message
+ )
+
+
+class DatabaseLogs(DataSource):
+ def __init__(self, logs_path_prefix, column_families):
+ super().__init__(DataSource.Type.LOG)
+ self.logs_path_prefix = logs_path_prefix
+ self.column_families = column_families
+
+ def trigger_conditions_for_log(self, conditions, log):
+ # For a LogCondition object, trigger is:
+ # Dict[column_family_name, List[Log]]. This explains why the condition
+ # was triggered and for which column families.
+ for cond in conditions:
+ if re.search(cond.regex, log.get_message(), re.IGNORECASE):
+ trigger = cond.get_trigger()
+ if not trigger:
+ trigger = {}
+ if log.get_column_family() not in trigger:
+ trigger[log.get_column_family()] = []
+ trigger[log.get_column_family()].append(log)
+ cond.set_trigger(trigger)
+
+ def check_and_trigger_conditions(self, conditions):
+ for file_name in glob.glob(self.logs_path_prefix + '*'):
+ # TODO(poojam23): find a way to distinguish between log files
+ # - generated in the current experiment but are labeled 'old'
+ # because they LOGs exceeded the file size limit AND
+ # - generated in some previous experiment that are also labeled
+ # 'old' and were not deleted for some reason
+ if re.search('old', file_name, re.IGNORECASE):
+ continue
+ with open(file_name, 'r') as db_logs:
+ new_log = None
+ for line in db_logs:
+ if Log.is_new_log(line):
+ if new_log:
+ self.trigger_conditions_for_log(
+ conditions, new_log
+ )
+ new_log = Log(line, self.column_families)
+ else:
+ # To account for logs split into multiple lines
+ new_log.append_message(line)
+ # Check for the last log in the file.
+ if new_log:
+ self.trigger_conditions_for_log(conditions, new_log)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_options_parser.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_options_parser.py
new file mode 100644
index 00000000..e689d892
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_options_parser.py
@@ -0,0 +1,358 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (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 copy
+from advisor.db_log_parser import DataSource, NO_COL_FAMILY
+from advisor.ini_parser import IniParser
+import os
+
+
+class OptionsSpecParser(IniParser):
+ @staticmethod
+ def is_new_option(line):
+ return '=' in line
+
+ @staticmethod
+ def get_section_type(line):
+ '''
+ Example section header: [TableOptions/BlockBasedTable "default"]
+ Here ConfigurationOptimizer returned would be
+ 'TableOptions.BlockBasedTable'
+ '''
+ section_path = line.strip()[1:-1].split()[0]
+ section_type = '.'.join(section_path.split('/'))
+ return section_type
+
+ @staticmethod
+ def get_section_name(line):
+ # example: get_section_name('[CFOptions "default"]')
+ token_list = line.strip()[1:-1].split('"')
+ # token_list = ['CFOptions', 'default', '']
+ if len(token_list) < 3:
+ return None
+ return token_list[1] # return 'default'
+
+ @staticmethod
+ def get_section_str(section_type, section_name):
+ # Example:
+ # Case 1: get_section_str('DBOptions', NO_COL_FAMILY)
+ # Case 2: get_section_str('TableOptions.BlockBasedTable', 'default')
+ section_type = '/'.join(section_type.strip().split('.'))
+ # Case 1: section_type = 'DBOptions'
+ # Case 2: section_type = 'TableOptions/BlockBasedTable'
+ section_str = '[' + section_type
+ if section_name == NO_COL_FAMILY:
+ # Case 1: '[DBOptions]'
+ return (section_str + ']')
+ else:
+ # Case 2: '[TableOptions/BlockBasedTable "default"]'
+ return section_str + ' "' + section_name + '"]'
+
+ @staticmethod
+ def get_option_str(key, values):
+ option_str = key + '='
+ # get_option_str('db_log_dir', None), returns 'db_log_dir='
+ if values:
+ # example:
+ # get_option_str('max_bytes_for_level_multiplier_additional',
+ # [1,1,1,1,1,1,1]), returned string:
+ # 'max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1'
+ if isinstance(values, list):
+ for value in values:
+ option_str += (str(value) + ':')
+ option_str = option_str[:-1]
+ else:
+ # example: get_option_str('write_buffer_size', 1048576)
+ # returned string: 'write_buffer_size=1048576'
+ option_str += str(values)
+ return option_str
+
+
+class DatabaseOptions(DataSource):
+
+ @staticmethod
+ def is_misc_option(option_name):
+ # these are miscellaneous options that are not yet supported by the
+ # Rocksdb options file, hence they are not prefixed with any section
+ # name
+ return '.' not in option_name
+
+ @staticmethod
+ def get_options_diff(opt_old, opt_new):
+ # type: Dict[option, Dict[col_fam, value]] X 2 ->
+ # Dict[option, Dict[col_fam, Tuple(old_value, new_value)]]
+ # note: diff should contain a tuple of values only if they are
+ # different from each other
+ options_union = set(opt_old.keys()).union(set(opt_new.keys()))
+ diff = {}
+ for opt in options_union:
+ diff[opt] = {}
+ # if option in options_union, then it must be in one of the configs
+ if opt not in opt_old:
+ for col_fam in opt_new[opt]:
+ diff[opt][col_fam] = (None, opt_new[opt][col_fam])
+ elif opt not in opt_new:
+ for col_fam in opt_old[opt]:
+ diff[opt][col_fam] = (opt_old[opt][col_fam], None)
+ else:
+ for col_fam in opt_old[opt]:
+ if col_fam in opt_new[opt]:
+ if opt_old[opt][col_fam] != opt_new[opt][col_fam]:
+ diff[opt][col_fam] = (
+ opt_old[opt][col_fam],
+ opt_new[opt][col_fam]
+ )
+ else:
+ diff[opt][col_fam] = (opt_old[opt][col_fam], None)
+ for col_fam in opt_new[opt]:
+ if col_fam in opt_old[opt]:
+ if opt_old[opt][col_fam] != opt_new[opt][col_fam]:
+ diff[opt][col_fam] = (
+ opt_old[opt][col_fam],
+ opt_new[opt][col_fam]
+ )
+ else:
+ diff[opt][col_fam] = (None, opt_new[opt][col_fam])
+ if not diff[opt]:
+ diff.pop(opt)
+ return diff
+
+ def __init__(self, rocksdb_options, misc_options=None):
+ super().__init__(DataSource.Type.DB_OPTIONS)
+ # The options are stored in the following data structure:
+ # Dict[section_type, Dict[section_name, Dict[option_name, value]]]
+ self.options_dict = None
+ self.column_families = None
+ # Load the options from the given file to a dictionary.
+ self.load_from_source(rocksdb_options)
+ # Setup the miscellaneous options expected to be List[str], where each
+ # element in the List has the format "<option_name>=<option_value>"
+ # These options are the ones that are not yet supported by the Rocksdb
+ # OPTIONS file, so they are provided separately
+ self.setup_misc_options(misc_options)
+
+ def setup_misc_options(self, misc_options):
+ self.misc_options = {}
+ if misc_options:
+ for option_pair_str in misc_options:
+ option_name = option_pair_str.split('=')[0].strip()
+ option_value = option_pair_str.split('=')[1].strip()
+ self.misc_options[option_name] = option_value
+
+ def load_from_source(self, options_path):
+ self.options_dict = {}
+ with open(options_path, 'r') as db_options:
+ for line in db_options:
+ line = OptionsSpecParser.remove_trailing_comment(line)
+ if not line:
+ continue
+ if OptionsSpecParser.is_section_header(line):
+ curr_sec_type = (
+ OptionsSpecParser.get_section_type(line)
+ )
+ curr_sec_name = OptionsSpecParser.get_section_name(line)
+ if curr_sec_type not in self.options_dict:
+ self.options_dict[curr_sec_type] = {}
+ if not curr_sec_name:
+ curr_sec_name = NO_COL_FAMILY
+ self.options_dict[curr_sec_type][curr_sec_name] = {}
+ # example: if the line read from the Rocksdb OPTIONS file
+ # is [CFOptions "default"], then the section type is
+ # CFOptions and 'default' is the name of a column family
+ # that for this database, so it's added to the list of
+ # column families stored in this object
+ if curr_sec_type == 'CFOptions':
+ if not self.column_families:
+ self.column_families = []
+ self.column_families.append(curr_sec_name)
+ elif OptionsSpecParser.is_new_option(line):
+ key, value = OptionsSpecParser.get_key_value_pair(line)
+ self.options_dict[curr_sec_type][curr_sec_name][key] = (
+ value
+ )
+ else:
+ error = 'Not able to parse line in Options file.'
+ OptionsSpecParser.exit_with_parse_error(line, error)
+
+ def get_misc_options(self):
+ # these are options that are not yet supported by the Rocksdb OPTIONS
+ # file, hence they are provided and stored separately
+ return self.misc_options
+
+ def get_column_families(self):
+ return self.column_families
+
+ def get_all_options(self):
+ # This method returns all the options that are stored in this object as
+ # a: Dict[<sec_type>.<option_name>: Dict[col_fam, option_value]]
+ all_options = []
+ # Example: in the section header '[CFOptions "default"]' read from the
+ # OPTIONS file, sec_type='CFOptions'
+ for sec_type in self.options_dict:
+ for col_fam in self.options_dict[sec_type]:
+ for opt_name in self.options_dict[sec_type][col_fam]:
+ option = sec_type + '.' + opt_name
+ all_options.append(option)
+ all_options.extend(list(self.misc_options.keys()))
+ return self.get_options(all_options)
+
+ def get_options(self, reqd_options):
+ # type: List[str] -> Dict[str, Dict[str, Any]]
+ # List[option] -> Dict[option, Dict[col_fam, value]]
+ reqd_options_dict = {}
+ for option in reqd_options:
+ if DatabaseOptions.is_misc_option(option):
+ # the option is not prefixed by '<section_type>.' because it is
+ # not yet supported by the Rocksdb OPTIONS file; so it has to
+ # be fetched from the misc_options dictionary
+ if option not in self.misc_options:
+ continue
+ if option not in reqd_options_dict:
+ reqd_options_dict[option] = {}
+ reqd_options_dict[option][NO_COL_FAMILY] = (
+ self.misc_options[option]
+ )
+ else:
+ # Example: option = 'TableOptions.BlockBasedTable.block_align'
+ # then, sec_type = 'TableOptions.BlockBasedTable'
+ sec_type = '.'.join(option.split('.')[:-1])
+ # opt_name = 'block_align'
+ opt_name = option.split('.')[-1]
+ if sec_type not in self.options_dict:
+ continue
+ for col_fam in self.options_dict[sec_type]:
+ if opt_name in self.options_dict[sec_type][col_fam]:
+ if option not in reqd_options_dict:
+ reqd_options_dict[option] = {}
+ reqd_options_dict[option][col_fam] = (
+ self.options_dict[sec_type][col_fam][opt_name]
+ )
+ return reqd_options_dict
+
+ def update_options(self, options):
+ # An example 'options' object looks like:
+ # {'DBOptions.max_background_jobs': {NO_COL_FAMILY: 2},
+ # 'CFOptions.write_buffer_size': {'default': 1048576, 'cf_A': 128000},
+ # 'bloom_bits': {NO_COL_FAMILY: 4}}
+ for option in options:
+ if DatabaseOptions.is_misc_option(option):
+ # this is a misc_option i.e. an option that is not yet
+ # supported by the Rocksdb OPTIONS file, so it is not prefixed
+ # by '<section_type>.' and must be stored in the separate
+ # misc_options dictionary
+ if NO_COL_FAMILY not in options[option]:
+ print(
+ 'WARNING(DatabaseOptions.update_options): not ' +
+ 'updating option ' + option + ' because it is in ' +
+ 'misc_option format but its scope is not ' +
+ NO_COL_FAMILY + '. Check format of option.'
+ )
+ continue
+ self.misc_options[option] = options[option][NO_COL_FAMILY]
+ else:
+ sec_name = '.'.join(option.split('.')[:-1])
+ opt_name = option.split('.')[-1]
+ if sec_name not in self.options_dict:
+ self.options_dict[sec_name] = {}
+ for col_fam in options[option]:
+ # if the option is not already present in the dictionary,
+ # it will be inserted, else it will be updated to the new
+ # value
+ if col_fam not in self.options_dict[sec_name]:
+ self.options_dict[sec_name][col_fam] = {}
+ self.options_dict[sec_name][col_fam][opt_name] = (
+ copy.deepcopy(options[option][col_fam])
+ )
+
+ def generate_options_config(self, nonce):
+ # this method generates a Rocksdb OPTIONS file in the INI format from
+ # the options stored in self.options_dict
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ file_name = '../temp/OPTIONS_' + str(nonce) + '.tmp'
+ file_path = os.path.join(this_path, file_name)
+ with open(file_path, 'w') as fp:
+ for section in self.options_dict:
+ for col_fam in self.options_dict[section]:
+ fp.write(
+ OptionsSpecParser.get_section_str(section, col_fam) +
+ '\n'
+ )
+ for option in self.options_dict[section][col_fam]:
+ values = self.options_dict[section][col_fam][option]
+ fp.write(
+ OptionsSpecParser.get_option_str(option, values) +
+ '\n'
+ )
+ fp.write('\n')
+ return file_path
+
+ def check_and_trigger_conditions(self, conditions):
+ for cond in conditions:
+ reqd_options_dict = self.get_options(cond.options)
+ # This contains the indices of options that are specific to some
+ # column family and are not database-wide options.
+ incomplete_option_ix = []
+ options = []
+ missing_reqd_option = False
+ for ix, option in enumerate(cond.options):
+ if option not in reqd_options_dict:
+ print(
+ 'WARNING(DatabaseOptions.check_and_trigger): ' +
+ 'skipping condition ' + cond.name + ' because it '
+ 'requires option ' + option + ' but this option is' +
+ ' not available'
+ )
+ missing_reqd_option = True
+ break # required option is absent
+ if NO_COL_FAMILY in reqd_options_dict[option]:
+ options.append(reqd_options_dict[option][NO_COL_FAMILY])
+ else:
+ options.append(None)
+ incomplete_option_ix.append(ix)
+
+ if missing_reqd_option:
+ continue
+
+ # if all the options are database-wide options
+ if not incomplete_option_ix:
+ try:
+ if eval(cond.eval_expr):
+ cond.set_trigger({NO_COL_FAMILY: options})
+ except Exception as e:
+ print(
+ 'WARNING(DatabaseOptions) check_and_trigger:' + str(e)
+ )
+ continue
+
+ # for all the options that are not database-wide, we look for their
+ # values specific to column families
+ col_fam_options_dict = {}
+ for col_fam in self.column_families:
+ present = True
+ for ix in incomplete_option_ix:
+ option = cond.options[ix]
+ if col_fam not in reqd_options_dict[option]:
+ present = False
+ break
+ options[ix] = reqd_options_dict[option][col_fam]
+ if present:
+ try:
+ if eval(cond.eval_expr):
+ col_fam_options_dict[col_fam] = (
+ copy.deepcopy(options)
+ )
+ except Exception as e:
+ print(
+ 'WARNING(DatabaseOptions) check_and_trigger: ' +
+ str(e)
+ )
+ # Trigger for an OptionCondition object is of the form:
+ # Dict[col_fam_name: List[option_value]]
+ # where col_fam_name is the name of a column family for which
+ # 'eval_expr' evaluated to True and List[option_value] is the list
+ # of values of the options specified in the condition's 'options'
+ # field
+ if col_fam_options_dict:
+ cond.set_trigger(col_fam_options_dict)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_stats_fetcher.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_stats_fetcher.py
new file mode 100755
index 00000000..cf497cf1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_stats_fetcher.py
@@ -0,0 +1,338 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_log_parser import Log
+from advisor.db_timeseries_parser import TimeSeriesData, NO_ENTITY
+import copy
+import glob
+import re
+import subprocess
+import time
+
+
+class LogStatsParser(TimeSeriesData):
+ STATS = 'STATISTICS:'
+
+ @staticmethod
+ def parse_log_line_for_stats(log_line):
+ # Example stat line (from LOG file):
+ # "rocksdb.db.get.micros P50 : 8.4 P95 : 21.8 P99 : 33.9 P100 : 92.0\n"
+ token_list = log_line.strip().split()
+ # token_list = ['rocksdb.db.get.micros', 'P50', ':', '8.4', 'P95', ':',
+ # '21.8', 'P99', ':', '33.9', 'P100', ':', '92.0']
+ stat_prefix = token_list[0] + '.' # 'rocksdb.db.get.micros.'
+ stat_values = [
+ token
+ for token in token_list[1:]
+ if token != ':'
+ ]
+ # stat_values = ['P50', '8.4', 'P95', '21.8', 'P99', '33.9', 'P100',
+ # '92.0']
+ stat_dict = {}
+ for ix, metric in enumerate(stat_values):
+ if ix % 2 == 0:
+ stat_name = stat_prefix + metric
+ stat_name = stat_name.lower() # Note: case insensitive names
+ else:
+ stat_dict[stat_name] = float(metric)
+ # stat_dict = {'rocksdb.db.get.micros.p50': 8.4,
+ # 'rocksdb.db.get.micros.p95': 21.8, 'rocksdb.db.get.micros.p99': 33.9,
+ # 'rocksdb.db.get.micros.p100': 92.0}
+ return stat_dict
+
+ def __init__(self, logs_path_prefix, stats_freq_sec):
+ super().__init__()
+ self.logs_file_prefix = logs_path_prefix
+ self.stats_freq_sec = stats_freq_sec
+ self.duration_sec = 60
+
+ def get_keys_from_conditions(self, conditions):
+ # Note: case insensitive stat names
+ reqd_stats = []
+ for cond in conditions:
+ for key in cond.keys:
+ key = key.lower()
+ # some keys are prepended with '[]' for OdsStatsFetcher to
+ # replace this with the appropriate key_prefix, remove these
+ # characters here since the LogStatsParser does not need
+ # a prefix
+ if key.startswith('[]'):
+ reqd_stats.append(key[2:])
+ else:
+ reqd_stats.append(key)
+ return reqd_stats
+
+ def add_to_timeseries(self, log, reqd_stats):
+ # this method takes in the Log object that contains the Rocksdb stats
+ # and a list of required stats, then it parses the stats line by line
+ # to fetch required stats and add them to the keys_ts object
+ # Example: reqd_stats = ['rocksdb.block.cache.hit.count',
+ # 'rocksdb.db.get.micros.p99']
+ # Let log.get_message() returns following string:
+ # "[WARN] [db/db_impl.cc:485] STATISTICS:\n
+ # rocksdb.block.cache.miss COUNT : 1459\n
+ # rocksdb.block.cache.hit COUNT : 37\n
+ # ...
+ # rocksdb.db.get.micros P50 : 15.6 P95 : 39.7 P99 : 62.6 P100 : 148.0\n
+ # ..."
+ new_lines = log.get_message().split('\n')
+ # let log_ts = 1532518219
+ log_ts = log.get_timestamp()
+ # example updates to keys_ts:
+ # keys_ts[NO_ENTITY]['rocksdb.db.get.micros.p99'][1532518219] = 62.6
+ # keys_ts[NO_ENTITY]['rocksdb.block.cache.hit.count'][1532518219] = 37
+ for line in new_lines[1:]: # new_lines[0] does not contain any stats
+ stats_on_line = self.parse_log_line_for_stats(line)
+ for stat in stats_on_line:
+ if stat in reqd_stats:
+ if stat not in self.keys_ts[NO_ENTITY]:
+ self.keys_ts[NO_ENTITY][stat] = {}
+ self.keys_ts[NO_ENTITY][stat][log_ts] = stats_on_line[stat]
+
+ def fetch_timeseries(self, reqd_stats):
+ # this method parses the Rocksdb LOG file and generates timeseries for
+ # each of the statistic in the list reqd_stats
+ self.keys_ts = {NO_ENTITY: {}}
+ for file_name in glob.glob(self.logs_file_prefix + '*'):
+ # TODO(poojam23): find a way to distinguish between 'old' log files
+ # from current and previous experiments, present in the same
+ # directory
+ if re.search('old', file_name, re.IGNORECASE):
+ continue
+ with open(file_name, 'r') as db_logs:
+ new_log = None
+ for line in db_logs:
+ if Log.is_new_log(line):
+ if (
+ new_log and
+ re.search(self.STATS, new_log.get_message())
+ ):
+ self.add_to_timeseries(new_log, reqd_stats)
+ new_log = Log(line, column_families=[])
+ else:
+ # To account for logs split into multiple lines
+ new_log.append_message(line)
+ # Check for the last log in the file.
+ if new_log and re.search(self.STATS, new_log.get_message()):
+ self.add_to_timeseries(new_log, reqd_stats)
+
+
+class DatabasePerfContext(TimeSeriesData):
+ # TODO(poojam23): check if any benchrunner provides PerfContext sampled at
+ # regular intervals
+ def __init__(self, perf_context_ts, stats_freq_sec, cumulative):
+ '''
+ perf_context_ts is expected to be in the following format:
+ Dict[metric, Dict[timestamp, value]], where for
+ each (metric, timestamp) pair, the value is database-wide (i.e.
+ summed over all the threads involved)
+ if stats_freq_sec == 0, per-metric only one value is reported
+ '''
+ super().__init__()
+ self.stats_freq_sec = stats_freq_sec
+ self.keys_ts = {NO_ENTITY: perf_context_ts}
+ if cumulative:
+ self.unaccumulate_metrics()
+
+ def unaccumulate_metrics(self):
+ # if the perf context metrics provided are cumulative in nature, this
+ # method can be used to convert them to a disjoint format
+ epoch_ts = copy.deepcopy(self.keys_ts)
+ for stat in self.keys_ts[NO_ENTITY]:
+ timeseries = sorted(
+ list(self.keys_ts[NO_ENTITY][stat].keys()), reverse=True
+ )
+ if len(timeseries) < 2:
+ continue
+ for ix, ts in enumerate(timeseries[:-1]):
+ epoch_ts[NO_ENTITY][stat][ts] = (
+ epoch_ts[NO_ENTITY][stat][ts] -
+ epoch_ts[NO_ENTITY][stat][timeseries[ix+1]]
+ )
+ if epoch_ts[NO_ENTITY][stat][ts] < 0:
+ raise ValueError('DBPerfContext: really cumulative?')
+ # drop the smallest timestamp in the timeseries for this metric
+ epoch_ts[NO_ENTITY][stat].pop(timeseries[-1])
+ self.keys_ts = epoch_ts
+
+ def get_keys_from_conditions(self, conditions):
+ reqd_stats = []
+ for cond in conditions:
+ reqd_stats.extend([key.lower() for key in cond.keys])
+ return reqd_stats
+
+ def fetch_timeseries(self, statistics):
+ # this method is redundant for DatabasePerfContext because the __init__
+ # does the job of populating 'keys_ts'
+ pass
+
+
+class OdsStatsFetcher(TimeSeriesData):
+ # class constants
+ OUTPUT_FILE = 'temp/stats_out.tmp'
+ ERROR_FILE = 'temp/stats_err.tmp'
+ RAPIDO_COMMAND = "%s --entity=%s --key=%s --tstart=%s --tend=%s --showtime"
+
+ # static methods
+ @staticmethod
+ def _get_string_in_quotes(value):
+ return '"' + str(value) + '"'
+
+ @staticmethod
+ def _get_time_value_pair(pair_string):
+ # example pair_string: '[1532544591, 97.3653601828]'
+ pair_string = pair_string.replace('[', '')
+ pair_string = pair_string.replace(']', '')
+ pair = pair_string.split(',')
+ first = int(pair[0].strip())
+ second = float(pair[1].strip())
+ return [first, second]
+
+ @staticmethod
+ def _get_ods_cli_stime(start_time):
+ diff = int(time.time() - int(start_time))
+ stime = str(diff) + '_s'
+ return stime
+
+ def __init__(
+ self, client, entities, start_time, end_time, key_prefix=None
+ ):
+ super().__init__()
+ self.client = client
+ self.entities = entities
+ self.start_time = start_time
+ self.end_time = end_time
+ self.key_prefix = key_prefix
+ self.stats_freq_sec = 60
+ self.duration_sec = 60
+
+ def execute_script(self, command):
+ print('executing...')
+ print(command)
+ out_file = open(self.OUTPUT_FILE, "w+")
+ err_file = open(self.ERROR_FILE, "w+")
+ subprocess.call(command, shell=True, stdout=out_file, stderr=err_file)
+ out_file.close()
+ err_file.close()
+
+ def parse_rapido_output(self):
+ # Output looks like the following:
+ # <entity_name>\t<key_name>\t[[ts, value], [ts, value], ...]
+ # ts = timestamp; value = value of key_name in entity_name at time ts
+ self.keys_ts = {}
+ with open(self.OUTPUT_FILE, 'r') as fp:
+ for line in fp:
+ token_list = line.strip().split('\t')
+ entity = token_list[0]
+ key = token_list[1]
+ if entity not in self.keys_ts:
+ self.keys_ts[entity] = {}
+ if key not in self.keys_ts[entity]:
+ self.keys_ts[entity][key] = {}
+ list_of_lists = [
+ self._get_time_value_pair(pair_string)
+ for pair_string in token_list[2].split('],')
+ ]
+ value = {pair[0]: pair[1] for pair in list_of_lists}
+ self.keys_ts[entity][key] = value
+
+ def parse_ods_output(self):
+ # Output looks like the following:
+ # <entity_name>\t<key_name>\t<timestamp>\t<value>
+ # there is one line per (entity_name, key_name, timestamp)
+ self.keys_ts = {}
+ with open(self.OUTPUT_FILE, 'r') as fp:
+ for line in fp:
+ token_list = line.split()
+ entity = token_list[0]
+ if entity not in self.keys_ts:
+ self.keys_ts[entity] = {}
+ key = token_list[1]
+ if key not in self.keys_ts[entity]:
+ self.keys_ts[entity][key] = {}
+ self.keys_ts[entity][key][token_list[2]] = token_list[3]
+
+ def fetch_timeseries(self, statistics):
+ # this method fetches the timeseries of required stats from the ODS
+ # service and populates the 'keys_ts' object appropriately
+ print('OdsStatsFetcher: fetching ' + str(statistics))
+ if re.search('rapido', self.client, re.IGNORECASE):
+ command = self.RAPIDO_COMMAND % (
+ self.client,
+ self._get_string_in_quotes(self.entities),
+ self._get_string_in_quotes(','.join(statistics)),
+ self._get_string_in_quotes(self.start_time),
+ self._get_string_in_quotes(self.end_time)
+ )
+ # Run the tool and fetch the time-series data
+ self.execute_script(command)
+ # Parse output and populate the 'keys_ts' map
+ self.parse_rapido_output()
+ elif re.search('ods', self.client, re.IGNORECASE):
+ command = (
+ self.client + ' ' +
+ '--stime=' + self._get_ods_cli_stime(self.start_time) + ' ' +
+ self._get_string_in_quotes(self.entities) + ' ' +
+ self._get_string_in_quotes(','.join(statistics))
+ )
+ # Run the tool and fetch the time-series data
+ self.execute_script(command)
+ # Parse output and populate the 'keys_ts' map
+ self.parse_ods_output()
+
+ def get_keys_from_conditions(self, conditions):
+ reqd_stats = []
+ for cond in conditions:
+ for key in cond.keys:
+ use_prefix = False
+ if key.startswith('[]'):
+ use_prefix = True
+ key = key[2:]
+ # TODO(poojam23): this is very hacky and needs to be improved
+ if key.startswith("rocksdb"):
+ key += ".60"
+ if use_prefix:
+ if not self.key_prefix:
+ print('Warning: OdsStatsFetcher might need key prefix')
+ print('for the key: ' + key)
+ else:
+ key = self.key_prefix + "." + key
+ reqd_stats.append(key)
+ return reqd_stats
+
+ def fetch_rate_url(self, entities, keys, window_len, percent, display):
+ # type: (List[str], List[str], str, str, bool) -> str
+ transform_desc = (
+ "rate(" + str(window_len) + ",duration=" + str(self.duration_sec)
+ )
+ if percent:
+ transform_desc = transform_desc + ",%)"
+ else:
+ transform_desc = transform_desc + ")"
+ if re.search('rapido', self.client, re.IGNORECASE):
+ command = self.RAPIDO_COMMAND + " --transform=%s --url=%s"
+ command = command % (
+ self.client,
+ self._get_string_in_quotes(','.join(entities)),
+ self._get_string_in_quotes(','.join(keys)),
+ self._get_string_in_quotes(self.start_time),
+ self._get_string_in_quotes(self.end_time),
+ self._get_string_in_quotes(transform_desc),
+ self._get_string_in_quotes(display)
+ )
+ elif re.search('ods', self.client, re.IGNORECASE):
+ command = (
+ self.client + ' ' +
+ '--stime=' + self._get_ods_cli_stime(self.start_time) + ' ' +
+ '--fburlonly ' +
+ self._get_string_in_quotes(entities) + ' ' +
+ self._get_string_in_quotes(','.join(keys)) + ' ' +
+ self._get_string_in_quotes(transform_desc)
+ )
+ self.execute_script(command)
+ url = ""
+ with open(self.OUTPUT_FILE, 'r') as fp:
+ url = fp.readline()
+ return url
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/db_timeseries_parser.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_timeseries_parser.py
new file mode 100644
index 00000000..308eb139
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/db_timeseries_parser.py
@@ -0,0 +1,208 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from abc import abstractmethod
+from advisor.db_log_parser import DataSource
+from enum import Enum
+import math
+
+
+NO_ENTITY = 'ENTITY_PLACEHOLDER'
+
+
+class TimeSeriesData(DataSource):
+ class Behavior(Enum):
+ bursty = 1
+ evaluate_expression = 2
+
+ class AggregationOperator(Enum):
+ avg = 1
+ max = 2
+ min = 3
+ latest = 4
+ oldest = 5
+
+ def __init__(self):
+ super().__init__(DataSource.Type.TIME_SERIES)
+ self.keys_ts = None # Dict[entity, Dict[key, Dict[timestamp, value]]]
+ self.stats_freq_sec = None
+
+ @abstractmethod
+ def get_keys_from_conditions(self, conditions):
+ # This method takes in a list of time-series conditions; for each
+ # condition it manipulates the 'keys' in the way that is supported by
+ # the subclass implementing this method
+ pass
+
+ @abstractmethod
+ def fetch_timeseries(self, required_statistics):
+ # this method takes in a list of statistics and fetches the timeseries
+ # for each of them and populates the 'keys_ts' dictionary
+ pass
+
+ def fetch_burst_epochs(
+ self, entities, statistic, window_sec, threshold, percent
+ ):
+ # type: (str, int, float, bool) -> Dict[str, Dict[int, float]]
+ # this method calculates the (percent) rate change in the 'statistic'
+ # for each entity (over 'window_sec' seconds) and returns the epochs
+ # where this rate change is greater than or equal to the 'threshold'
+ # value
+ if self.stats_freq_sec == 0:
+ # not time series data, cannot check for bursty behavior
+ return
+ if window_sec < self.stats_freq_sec:
+ window_sec = self.stats_freq_sec
+ # 'window_samples' is the number of windows to go back to
+ # compare the current window with, while calculating rate change.
+ window_samples = math.ceil(window_sec / self.stats_freq_sec)
+ burst_epochs = {}
+ # if percent = False:
+ # curr_val = value at window for which rate change is being calculated
+ # prev_val = value at window that is window_samples behind curr_window
+ # Then rate_without_percent =
+ # ((curr_val-prev_val)*duration_sec)/(curr_timestamp-prev_timestamp)
+ # if percent = True:
+ # rate_with_percent = (rate_without_percent * 100) / prev_val
+ # These calculations are in line with the rate() transform supported
+ # by ODS
+ for entity in entities:
+ if statistic not in self.keys_ts[entity]:
+ continue
+ timestamps = sorted(list(self.keys_ts[entity][statistic].keys()))
+ for ix in range(window_samples, len(timestamps), 1):
+ first_ts = timestamps[ix - window_samples]
+ last_ts = timestamps[ix]
+ first_val = self.keys_ts[entity][statistic][first_ts]
+ last_val = self.keys_ts[entity][statistic][last_ts]
+ diff = last_val - first_val
+ if percent:
+ diff = diff * 100 / first_val
+ rate = (diff * self.duration_sec) / (last_ts - first_ts)
+ # if the rate change is greater than the provided threshold,
+ # then the condition is triggered for entity at time 'last_ts'
+ if rate >= threshold:
+ if entity not in burst_epochs:
+ burst_epochs[entity] = {}
+ burst_epochs[entity][last_ts] = rate
+ return burst_epochs
+
+ def fetch_aggregated_values(self, entity, statistics, aggregation_op):
+ # type: (str, AggregationOperator) -> Dict[str, float]
+ # this method performs the aggregation specified by 'aggregation_op'
+ # on the timeseries of 'statistics' for 'entity' and returns:
+ # Dict[statistic, aggregated_value]
+ result = {}
+ for stat in statistics:
+ if stat not in self.keys_ts[entity]:
+ continue
+ agg_val = None
+ if aggregation_op is self.AggregationOperator.latest:
+ latest_timestamp = max(list(self.keys_ts[entity][stat].keys()))
+ agg_val = self.keys_ts[entity][stat][latest_timestamp]
+ elif aggregation_op is self.AggregationOperator.oldest:
+ oldest_timestamp = min(list(self.keys_ts[entity][stat].keys()))
+ agg_val = self.keys_ts[entity][stat][oldest_timestamp]
+ elif aggregation_op is self.AggregationOperator.max:
+ agg_val = max(list(self.keys_ts[entity][stat].values()))
+ elif aggregation_op is self.AggregationOperator.min:
+ agg_val = min(list(self.keys_ts[entity][stat].values()))
+ elif aggregation_op is self.AggregationOperator.avg:
+ values = list(self.keys_ts[entity][stat].values())
+ agg_val = sum(values) / len(values)
+ result[stat] = agg_val
+ return result
+
+ def check_and_trigger_conditions(self, conditions):
+ # get the list of statistics that need to be fetched
+ reqd_keys = self.get_keys_from_conditions(conditions)
+ # fetch the required statistics and populate the map 'keys_ts'
+ self.fetch_timeseries(reqd_keys)
+ # Trigger the appropriate conditions
+ for cond in conditions:
+ complete_keys = self.get_keys_from_conditions([cond])
+ # Get the entities that have all statistics required by 'cond':
+ # an entity is checked for a given condition only if we possess all
+ # of the condition's 'keys' for that entity
+ entities_with_stats = []
+ for entity in self.keys_ts:
+ stat_missing = False
+ for stat in complete_keys:
+ if stat not in self.keys_ts[entity]:
+ stat_missing = True
+ break
+ if not stat_missing:
+ entities_with_stats.append(entity)
+ if not entities_with_stats:
+ continue
+ if cond.behavior is self.Behavior.bursty:
+ # for a condition that checks for bursty behavior, only one key
+ # should be present in the condition's 'keys' field
+ result = self.fetch_burst_epochs(
+ entities_with_stats,
+ complete_keys[0], # there should be only one key
+ cond.window_sec,
+ cond.rate_threshold,
+ True
+ )
+ # Trigger in this case is:
+ # Dict[entity_name, Dict[timestamp, rate_change]]
+ # where the inner dictionary contains rate_change values when
+ # the rate_change >= threshold provided, with the
+ # corresponding timestamps
+ if result:
+ cond.set_trigger(result)
+ elif cond.behavior is self.Behavior.evaluate_expression:
+ self.handle_evaluate_expression(
+ cond,
+ complete_keys,
+ entities_with_stats
+ )
+
+ def handle_evaluate_expression(self, condition, statistics, entities):
+ trigger = {}
+ # check 'condition' for each of these entities
+ for entity in entities:
+ if hasattr(condition, 'aggregation_op'):
+ # in this case, the aggregation operation is performed on each
+ # of the condition's 'keys' and then with aggregated values
+ # condition's 'expression' is evaluated; if it evaluates to
+ # True, then list of the keys values is added to the
+ # condition's trigger: Dict[entity_name, List[stats]]
+ result = self.fetch_aggregated_values(
+ entity, statistics, condition.aggregation_op
+ )
+ keys = [result[key] for key in statistics]
+ try:
+ if eval(condition.expression):
+ trigger[entity] = keys
+ except Exception as e:
+ print(
+ 'WARNING(TimeSeriesData) check_and_trigger: ' + str(e)
+ )
+ else:
+ # assumption: all stats have same series of timestamps
+ # this is similar to the above but 'expression' is evaluated at
+ # each timestamp, since there is no aggregation, and all the
+ # epochs are added to the trigger when the condition's
+ # 'expression' evaluated to true; so trigger is:
+ # Dict[entity, Dict[timestamp, List[stats]]]
+ for epoch in self.keys_ts[entity][statistics[0]].keys():
+ keys = [
+ self.keys_ts[entity][key][epoch]
+ for key in statistics
+ ]
+ try:
+ if eval(condition.expression):
+ if entity not in trigger:
+ trigger[entity] = {}
+ trigger[entity][epoch] = keys
+ except Exception as e:
+ print(
+ 'WARNING(TimeSeriesData) check_and_trigger: ' +
+ str(e)
+ )
+ if trigger:
+ condition.set_trigger(trigger)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/ini_parser.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/ini_parser.py
new file mode 100644
index 00000000..4776ef20
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/ini_parser.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from enum import Enum
+
+
+class IniParser:
+ class Element(Enum):
+ rule = 1
+ cond = 2
+ sugg = 3
+ key_val = 4
+ comment = 5
+
+ @staticmethod
+ def remove_trailing_comment(line):
+ line = line.strip()
+ comment_start = line.find('#')
+ if comment_start > -1:
+ return line[:comment_start]
+ return line
+
+ @staticmethod
+ def is_section_header(line):
+ # A section header looks like: [Rule "my-new-rule"]. Essentially,
+ # a line that is in square-brackets.
+ line = line.strip()
+ if line.startswith('[') and line.endswith(']'):
+ return True
+ return False
+
+ @staticmethod
+ def get_section_name(line):
+ # For a section header: [Rule "my-new-rule"], this method will return
+ # "my-new-rule".
+ token_list = line.strip()[1:-1].split('"')
+ if len(token_list) < 3:
+ error = 'needed section header: [<section_type> "<section_name>"]'
+ raise ValueError('Parsing error: ' + error + '\n' + line)
+ return token_list[1]
+
+ @staticmethod
+ def get_element(line):
+ line = IniParser.remove_trailing_comment(line)
+ if not line:
+ return IniParser.Element.comment
+ if IniParser.is_section_header(line):
+ if line.strip()[1:-1].startswith('Suggestion'):
+ return IniParser.Element.sugg
+ if line.strip()[1:-1].startswith('Rule'):
+ return IniParser.Element.rule
+ if line.strip()[1:-1].startswith('Condition'):
+ return IniParser.Element.cond
+ if '=' in line:
+ return IniParser.Element.key_val
+ error = 'not a recognizable RulesSpec element'
+ raise ValueError('Parsing error: ' + error + '\n' + line)
+
+ @staticmethod
+ def get_key_value_pair(line):
+ line = line.strip()
+ key = line.split('=')[0].strip()
+ value = "=".join(line.split('=')[1:])
+ if value == "": # if the option has no value
+ return (key, None)
+ values = IniParser.get_list_from_value(value)
+ if len(values) == 1:
+ return (key, value)
+ return (key, values)
+
+ @staticmethod
+ def get_list_from_value(value):
+ values = value.strip().split(':')
+ return values
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser.py
new file mode 100644
index 00000000..592218f4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser.py
@@ -0,0 +1,528 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from abc import ABC, abstractmethod
+from advisor.db_log_parser import DataSource, NO_COL_FAMILY
+from advisor.db_timeseries_parser import TimeSeriesData
+from enum import Enum
+from advisor.ini_parser import IniParser
+import re
+
+
+class Section(ABC):
+ def __init__(self, name):
+ self.name = name
+
+ @abstractmethod
+ def set_parameter(self, key, value):
+ pass
+
+ @abstractmethod
+ def perform_checks(self):
+ pass
+
+
+class Rule(Section):
+ def __init__(self, name):
+ super().__init__(name)
+ self.conditions = None
+ self.suggestions = None
+ self.overlap_time_seconds = None
+ self.trigger_entities = None
+ self.trigger_column_families = None
+
+ def set_parameter(self, key, value):
+ # If the Rule is associated with a single suggestion/condition, then
+ # value will be a string and not a list. Hence, convert it to a single
+ # element list before storing it in self.suggestions or
+ # self.conditions.
+ if key == 'conditions':
+ if isinstance(value, str):
+ self.conditions = [value]
+ else:
+ self.conditions = value
+ elif key == 'suggestions':
+ if isinstance(value, str):
+ self.suggestions = [value]
+ else:
+ self.suggestions = value
+ elif key == 'overlap_time_period':
+ self.overlap_time_seconds = value
+
+ def get_suggestions(self):
+ return self.suggestions
+
+ def perform_checks(self):
+ if not self.conditions or len(self.conditions) < 1:
+ raise ValueError(
+ self.name + ': rule must have at least one condition'
+ )
+ if not self.suggestions or len(self.suggestions) < 1:
+ raise ValueError(
+ self.name + ': rule must have at least one suggestion'
+ )
+ if self.overlap_time_seconds:
+ if len(self.conditions) != 2:
+ raise ValueError(
+ self.name + ": rule must be associated with 2 conditions\
+ in order to check for a time dependency between them"
+ )
+ time_format = '^\d+[s|m|h|d]$'
+ if (
+ not
+ re.match(time_format, self.overlap_time_seconds, re.IGNORECASE)
+ ):
+ raise ValueError(
+ self.name + ": overlap_time_seconds format: \d+[s|m|h|d]"
+ )
+ else: # convert to seconds
+ in_seconds = int(self.overlap_time_seconds[:-1])
+ if self.overlap_time_seconds[-1] == 'm':
+ in_seconds *= 60
+ elif self.overlap_time_seconds[-1] == 'h':
+ in_seconds *= (60 * 60)
+ elif self.overlap_time_seconds[-1] == 'd':
+ in_seconds *= (24 * 60 * 60)
+ self.overlap_time_seconds = in_seconds
+
+ def get_overlap_timestamps(self, key1_trigger_epochs, key2_trigger_epochs):
+ # this method takes in 2 timeseries i.e. timestamps at which the
+ # rule's 2 TIME_SERIES conditions were triggered and it finds
+ # (if present) the first pair of timestamps at which the 2 conditions
+ # were triggered within 'overlap_time_seconds' of each other
+ key1_lower_bounds = [
+ epoch - self.overlap_time_seconds
+ for epoch in key1_trigger_epochs
+ ]
+ key1_lower_bounds.sort()
+ key2_trigger_epochs.sort()
+ trigger_ix = 0
+ overlap_pair = None
+ for key1_lb in key1_lower_bounds:
+ while (
+ key2_trigger_epochs[trigger_ix] < key1_lb and
+ trigger_ix < len(key2_trigger_epochs)
+ ):
+ trigger_ix += 1
+ if trigger_ix >= len(key2_trigger_epochs):
+ break
+ if (
+ key2_trigger_epochs[trigger_ix] <=
+ key1_lb + (2 * self.overlap_time_seconds)
+ ):
+ overlap_pair = (
+ key2_trigger_epochs[trigger_ix],
+ key1_lb + self.overlap_time_seconds
+ )
+ break
+ return overlap_pair
+
+ def get_trigger_entities(self):
+ return self.trigger_entities
+
+ def get_trigger_column_families(self):
+ return self.trigger_column_families
+
+ def is_triggered(self, conditions_dict, column_families):
+ if self.overlap_time_seconds:
+ condition1 = conditions_dict[self.conditions[0]]
+ condition2 = conditions_dict[self.conditions[1]]
+ if not (
+ condition1.get_data_source() is DataSource.Type.TIME_SERIES and
+ condition2.get_data_source() is DataSource.Type.TIME_SERIES
+ ):
+ raise ValueError(self.name + ': need 2 timeseries conditions')
+
+ map1 = condition1.get_trigger()
+ map2 = condition2.get_trigger()
+ if not (map1 and map2):
+ return False
+
+ self.trigger_entities = {}
+ is_triggered = False
+ entity_intersection = (
+ set(map1.keys()).intersection(set(map2.keys()))
+ )
+ for entity in entity_intersection:
+ overlap_timestamps_pair = (
+ self.get_overlap_timestamps(
+ list(map1[entity].keys()), list(map2[entity].keys())
+ )
+ )
+ if overlap_timestamps_pair:
+ self.trigger_entities[entity] = overlap_timestamps_pair
+ is_triggered = True
+ if is_triggered:
+ self.trigger_column_families = set(column_families)
+ return is_triggered
+ else:
+ all_conditions_triggered = True
+ self.trigger_column_families = set(column_families)
+ for cond_name in self.conditions:
+ cond = conditions_dict[cond_name]
+ if not cond.get_trigger():
+ all_conditions_triggered = False
+ break
+ if (
+ cond.get_data_source() is DataSource.Type.LOG or
+ cond.get_data_source() is DataSource.Type.DB_OPTIONS
+ ):
+ cond_col_fam = set(cond.get_trigger().keys())
+ if NO_COL_FAMILY in cond_col_fam:
+ cond_col_fam = set(column_families)
+ self.trigger_column_families = (
+ self.trigger_column_families.intersection(cond_col_fam)
+ )
+ elif cond.get_data_source() is DataSource.Type.TIME_SERIES:
+ cond_entities = set(cond.get_trigger().keys())
+ if self.trigger_entities is None:
+ self.trigger_entities = cond_entities
+ else:
+ self.trigger_entities = (
+ self.trigger_entities.intersection(cond_entities)
+ )
+ if not (self.trigger_entities or self.trigger_column_families):
+ all_conditions_triggered = False
+ break
+ if not all_conditions_triggered: # clean up if rule not triggered
+ self.trigger_column_families = None
+ self.trigger_entities = None
+ return all_conditions_triggered
+
+ def __repr__(self):
+ # Append conditions
+ rule_string = "Rule: " + self.name + " has conditions:: "
+ is_first = True
+ for cond in self.conditions:
+ if is_first:
+ rule_string += cond
+ is_first = False
+ else:
+ rule_string += (" AND " + cond)
+ # Append suggestions
+ rule_string += "\nsuggestions:: "
+ is_first = True
+ for sugg in self.suggestions:
+ if is_first:
+ rule_string += sugg
+ is_first = False
+ else:
+ rule_string += (", " + sugg)
+ if self.trigger_entities:
+ rule_string += (', entities:: ' + str(self.trigger_entities))
+ if self.trigger_column_families:
+ rule_string += (', col_fam:: ' + str(self.trigger_column_families))
+ # Return constructed string
+ return rule_string
+
+
+class Suggestion(Section):
+ class Action(Enum):
+ set = 1
+ increase = 2
+ decrease = 3
+
+ def __init__(self, name):
+ super().__init__(name)
+ self.option = None
+ self.action = None
+ self.suggested_values = None
+ self.description = None
+
+ def set_parameter(self, key, value):
+ if key == 'option':
+ # Note:
+ # case 1: 'option' is supported by Rocksdb OPTIONS file; in this
+ # case the option belongs to one of the sections in the config
+ # file and it's name is prefixed by "<section_type>."
+ # case 2: 'option' is not supported by Rocksdb OPTIONS file; the
+ # option is not expected to have the character '.' in its name
+ self.option = value
+ elif key == 'action':
+ if self.option and not value:
+ raise ValueError(self.name + ': provide action for option')
+ self.action = self.Action[value]
+ elif key == 'suggested_values':
+ if isinstance(value, str):
+ self.suggested_values = [value]
+ else:
+ self.suggested_values = value
+ elif key == 'description':
+ self.description = value
+
+ def perform_checks(self):
+ if not self.description:
+ if not self.option:
+ raise ValueError(self.name + ': provide option or description')
+ if not self.action:
+ raise ValueError(self.name + ': provide action for option')
+ if self.action is self.Action.set and not self.suggested_values:
+ raise ValueError(
+ self.name + ': provide suggested value for option'
+ )
+
+ def __repr__(self):
+ sugg_string = "Suggestion: " + self.name
+ if self.description:
+ sugg_string += (' description : ' + self.description)
+ else:
+ sugg_string += (
+ ' option : ' + self.option + ' action : ' + self.action.name
+ )
+ if self.suggested_values:
+ sugg_string += (
+ ' suggested_values : ' + str(self.suggested_values)
+ )
+ return sugg_string
+
+
+class Condition(Section):
+ def __init__(self, name):
+ super().__init__(name)
+ self.data_source = None
+ self.trigger = None
+
+ def perform_checks(self):
+ if not self.data_source:
+ raise ValueError(self.name + ': condition not tied to data source')
+
+ def set_data_source(self, data_source):
+ self.data_source = data_source
+
+ def get_data_source(self):
+ return self.data_source
+
+ def reset_trigger(self):
+ self.trigger = None
+
+ def set_trigger(self, condition_trigger):
+ self.trigger = condition_trigger
+
+ def get_trigger(self):
+ return self.trigger
+
+ def is_triggered(self):
+ if self.trigger:
+ return True
+ return False
+
+ def set_parameter(self, key, value):
+ # must be defined by the subclass
+ raise NotImplementedError(self.name + ': provide source for condition')
+
+
+class LogCondition(Condition):
+ @classmethod
+ def create(cls, base_condition):
+ base_condition.set_data_source(DataSource.Type['LOG'])
+ base_condition.__class__ = cls
+ return base_condition
+
+ def set_parameter(self, key, value):
+ if key == 'regex':
+ self.regex = value
+
+ def perform_checks(self):
+ super().perform_checks()
+ if not self.regex:
+ raise ValueError(self.name + ': provide regex for log condition')
+
+ def __repr__(self):
+ log_cond_str = "LogCondition: " + self.name
+ log_cond_str += (" regex: " + self.regex)
+ # if self.trigger:
+ # log_cond_str += (" trigger: " + str(self.trigger))
+ return log_cond_str
+
+
+class OptionCondition(Condition):
+ @classmethod
+ def create(cls, base_condition):
+ base_condition.set_data_source(DataSource.Type['DB_OPTIONS'])
+ base_condition.__class__ = cls
+ return base_condition
+
+ def set_parameter(self, key, value):
+ if key == 'options':
+ if isinstance(value, str):
+ self.options = [value]
+ else:
+ self.options = value
+ elif key == 'evaluate':
+ self.eval_expr = value
+
+ def perform_checks(self):
+ super().perform_checks()
+ if not self.options:
+ raise ValueError(self.name + ': options missing in condition')
+ if not self.eval_expr:
+ raise ValueError(self.name + ': expression missing in condition')
+
+ def __repr__(self):
+ opt_cond_str = "OptionCondition: " + self.name
+ opt_cond_str += (" options: " + str(self.options))
+ opt_cond_str += (" expression: " + self.eval_expr)
+ if self.trigger:
+ opt_cond_str += (" trigger: " + str(self.trigger))
+ return opt_cond_str
+
+
+class TimeSeriesCondition(Condition):
+ @classmethod
+ def create(cls, base_condition):
+ base_condition.set_data_source(DataSource.Type['TIME_SERIES'])
+ base_condition.__class__ = cls
+ return base_condition
+
+ def set_parameter(self, key, value):
+ if key == 'keys':
+ if isinstance(value, str):
+ self.keys = [value]
+ else:
+ self.keys = value
+ elif key == 'behavior':
+ self.behavior = TimeSeriesData.Behavior[value]
+ elif key == 'rate_threshold':
+ self.rate_threshold = float(value)
+ elif key == 'window_sec':
+ self.window_sec = int(value)
+ elif key == 'evaluate':
+ self.expression = value
+ elif key == 'aggregation_op':
+ self.aggregation_op = TimeSeriesData.AggregationOperator[value]
+
+ def perform_checks(self):
+ if not self.keys:
+ raise ValueError(self.name + ': specify timeseries key')
+ if not self.behavior:
+ raise ValueError(self.name + ': specify triggering behavior')
+ if self.behavior is TimeSeriesData.Behavior.bursty:
+ if not self.rate_threshold:
+ raise ValueError(self.name + ': specify rate burst threshold')
+ if not self.window_sec:
+ self.window_sec = 300 # default window length is 5 minutes
+ if len(self.keys) > 1:
+ raise ValueError(self.name + ': specify only one key')
+ elif self.behavior is TimeSeriesData.Behavior.evaluate_expression:
+ if not (self.expression):
+ raise ValueError(self.name + ': specify evaluation expression')
+ else:
+ raise ValueError(self.name + ': trigger behavior not supported')
+
+ def __repr__(self):
+ ts_cond_str = "TimeSeriesCondition: " + self.name
+ ts_cond_str += (" statistics: " + str(self.keys))
+ ts_cond_str += (" behavior: " + self.behavior.name)
+ if self.behavior is TimeSeriesData.Behavior.bursty:
+ ts_cond_str += (" rate_threshold: " + str(self.rate_threshold))
+ ts_cond_str += (" window_sec: " + str(self.window_sec))
+ if self.behavior is TimeSeriesData.Behavior.evaluate_expression:
+ ts_cond_str += (" expression: " + self.expression)
+ if hasattr(self, 'aggregation_op'):
+ ts_cond_str += (" aggregation_op: " + self.aggregation_op.name)
+ if self.trigger:
+ ts_cond_str += (" trigger: " + str(self.trigger))
+ return ts_cond_str
+
+
+class RulesSpec:
+ def __init__(self, rules_path):
+ self.file_path = rules_path
+
+ def initialise_fields(self):
+ self.rules_dict = {}
+ self.conditions_dict = {}
+ self.suggestions_dict = {}
+
+ def perform_section_checks(self):
+ for rule in self.rules_dict.values():
+ rule.perform_checks()
+ for cond in self.conditions_dict.values():
+ cond.perform_checks()
+ for sugg in self.suggestions_dict.values():
+ sugg.perform_checks()
+
+ def load_rules_from_spec(self):
+ self.initialise_fields()
+ with open(self.file_path, 'r') as db_rules:
+ curr_section = None
+ for line in db_rules:
+ line = IniParser.remove_trailing_comment(line)
+ if not line:
+ continue
+ element = IniParser.get_element(line)
+ if element is IniParser.Element.comment:
+ continue
+ elif element is not IniParser.Element.key_val:
+ curr_section = element # it's a new IniParser header
+ section_name = IniParser.get_section_name(line)
+ if element is IniParser.Element.rule:
+ new_rule = Rule(section_name)
+ self.rules_dict[section_name] = new_rule
+ elif element is IniParser.Element.cond:
+ new_cond = Condition(section_name)
+ self.conditions_dict[section_name] = new_cond
+ elif element is IniParser.Element.sugg:
+ new_suggestion = Suggestion(section_name)
+ self.suggestions_dict[section_name] = new_suggestion
+ elif element is IniParser.Element.key_val:
+ key, value = IniParser.get_key_value_pair(line)
+ if curr_section is IniParser.Element.rule:
+ new_rule.set_parameter(key, value)
+ elif curr_section is IniParser.Element.cond:
+ if key == 'source':
+ if value == 'LOG':
+ new_cond = LogCondition.create(new_cond)
+ elif value == 'OPTIONS':
+ new_cond = OptionCondition.create(new_cond)
+ elif value == 'TIME_SERIES':
+ new_cond = TimeSeriesCondition.create(new_cond)
+ else:
+ new_cond.set_parameter(key, value)
+ elif curr_section is IniParser.Element.sugg:
+ new_suggestion.set_parameter(key, value)
+
+ def get_rules_dict(self):
+ return self.rules_dict
+
+ def get_conditions_dict(self):
+ return self.conditions_dict
+
+ def get_suggestions_dict(self):
+ return self.suggestions_dict
+
+ def get_triggered_rules(self, data_sources, column_families):
+ self.trigger_conditions(data_sources)
+ triggered_rules = []
+ for rule in self.rules_dict.values():
+ if rule.is_triggered(self.conditions_dict, column_families):
+ triggered_rules.append(rule)
+ return triggered_rules
+
+ def trigger_conditions(self, data_sources):
+ for source_type in data_sources:
+ cond_subset = [
+ cond
+ for cond in self.conditions_dict.values()
+ if cond.get_data_source() is source_type
+ ]
+ if not cond_subset:
+ continue
+ for source in data_sources[source_type]:
+ source.check_and_trigger_conditions(cond_subset)
+
+ def print_rules(self, rules):
+ for rule in rules:
+ print('\nRule: ' + rule.name)
+ for cond_name in rule.conditions:
+ print(repr(self.conditions_dict[cond_name]))
+ for sugg_name in rule.suggestions:
+ print(repr(self.suggestions_dict[sugg_name]))
+ if rule.trigger_entities:
+ print('scope: entities:')
+ print(rule.trigger_entities)
+ if rule.trigger_column_families:
+ print('scope: col_fam:')
+ print(rule.trigger_column_families)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser_example.py b/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser_example.py
new file mode 100644
index 00000000..d2348e5a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/rule_parser_example.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.rule_parser import RulesSpec
+from advisor.db_log_parser import DatabaseLogs, DataSource
+from advisor.db_options_parser import DatabaseOptions
+from advisor.db_stats_fetcher import LogStatsParser, OdsStatsFetcher
+import argparse
+
+
+def main(args):
+ # initialise the RulesSpec parser
+ rule_spec_parser = RulesSpec(args.rules_spec)
+ rule_spec_parser.load_rules_from_spec()
+ rule_spec_parser.perform_section_checks()
+ # initialize the DatabaseOptions object
+ db_options = DatabaseOptions(args.rocksdb_options)
+ # Create DatabaseLogs object
+ db_logs = DatabaseLogs(
+ args.log_files_path_prefix, db_options.get_column_families()
+ )
+ # Create the Log STATS object
+ db_log_stats = LogStatsParser(
+ args.log_files_path_prefix, args.stats_dump_period_sec
+ )
+ data_sources = {
+ DataSource.Type.DB_OPTIONS: [db_options],
+ DataSource.Type.LOG: [db_logs],
+ DataSource.Type.TIME_SERIES: [db_log_stats]
+ }
+ if args.ods_client:
+ data_sources[DataSource.Type.TIME_SERIES].append(OdsStatsFetcher(
+ args.ods_client,
+ args.ods_entity,
+ args.ods_tstart,
+ args.ods_tend,
+ args.ods_key_prefix
+ ))
+ triggered_rules = rule_spec_parser.get_triggered_rules(
+ data_sources, db_options.get_column_families()
+ )
+ rule_spec_parser.print_rules(triggered_rules)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Use this script to get\
+ suggestions for improving Rocksdb performance.')
+ parser.add_argument(
+ '--rules_spec', required=True, type=str,
+ help='path of the file containing the expert-specified Rules'
+ )
+ parser.add_argument(
+ '--rocksdb_options', required=True, type=str,
+ help='path of the starting Rocksdb OPTIONS file'
+ )
+ parser.add_argument(
+ '--log_files_path_prefix', required=True, type=str,
+ help='path prefix of the Rocksdb LOG files'
+ )
+ parser.add_argument(
+ '--stats_dump_period_sec', required=True, type=int,
+ help='the frequency (in seconds) at which STATISTICS are printed to ' +
+ 'the Rocksdb LOG file'
+ )
+ # ODS arguments
+ parser.add_argument(
+ '--ods_client', type=str, help='the ODS client binary'
+ )
+ parser.add_argument(
+ '--ods_entity', type=str,
+ help='the servers for which the ODS stats need to be fetched'
+ )
+ parser.add_argument(
+ '--ods_key_prefix', type=str,
+ help='the prefix that needs to be attached to the keys of time ' +
+ 'series to be fetched from ODS'
+ )
+ parser.add_argument(
+ '--ods_tstart', type=int,
+ help='start time of timeseries to be fetched from ODS'
+ )
+ parser.add_argument(
+ '--ods_tend', type=int,
+ help='end time of timeseries to be fetched from ODS'
+ )
+ args = parser.parse_args()
+ main(args)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/advisor/rules.ini b/storage/rocksdb/rocksdb/tools/advisor/advisor/rules.ini
new file mode 100644
index 00000000..ec7a07e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/advisor/rules.ini
@@ -0,0 +1,214 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+#
+# FORMAT: very similar to the Rocksdb ini file in terms of syntax
+# (refer rocksdb/examples/rocksdb_option_file_example.ini)
+#
+# The Rules INI file is made up of multiple sections and each section is made
+# up of multiple key-value pairs. The recognized section types are:
+# Rule, Suggestion, Condition. Each section must have a name specified in ""
+# in the section header. This name acts as an identifier in that section
+# type's namespace. A section header looks like:
+# [<section_type> "<section_name_identifier>"]
+#
+# There should be at least one Rule section in the file with its corresponding
+# Condition and Suggestion sections. A Rule is triggered only when all of its
+# conditions are triggered. The order in which a Rule's conditions and
+# suggestions are specified has no significance.
+#
+# A Condition must be associated with a data source specified by the parameter
+# 'source' and this must be the first parameter specified for the Condition.
+# A condition can be associated with one or more Rules.
+#
+# A Suggestion is an advised change to a Rocksdb option to improve the
+# performance of the database in some way. Every suggestion can be a part of
+# one or more Rules.
+
+[Rule "stall-too-many-memtables"]
+suggestions=inc-bg-flush:inc-write-buffer
+conditions=stall-too-many-memtables
+
+[Condition "stall-too-many-memtables"]
+source=LOG
+regex=Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+
+
+[Rule "stall-too-many-L0"]
+suggestions=inc-max-subcompactions:inc-max-bg-compactions:inc-write-buffer-size:dec-max-bytes-for-level-base:inc-l0-slowdown-writes-trigger
+conditions=stall-too-many-L0
+
+[Condition "stall-too-many-L0"]
+source=LOG
+regex=Stalling writes because we have \d+ level-0 files
+
+[Rule "stop-too-many-L0"]
+suggestions=inc-max-bg-compactions:inc-write-buffer-size:inc-l0-stop-writes-trigger
+conditions=stop-too-many-L0
+
+[Condition "stop-too-many-L0"]
+source=LOG
+regex=Stopping writes because we have \d+ level-0 files
+
+[Rule "stall-too-many-compaction-bytes"]
+suggestions=inc-max-bg-compactions:inc-write-buffer-size:inc-hard-pending-compaction-bytes-limit:inc-soft-pending-compaction-bytes-limit
+conditions=stall-too-many-compaction-bytes
+
+[Condition "stall-too-many-compaction-bytes"]
+source=LOG
+regex=Stalling writes because of estimated pending compaction bytes \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
+
+[Suggestion "inc-max-subcompactions"]
+option=DBOptions.max_subcompactions
+action=increase
+
+[Suggestion "inc-max-bg-compactions"]
+option=DBOptions.max_background_compactions
+action=increase
+suggested_values=2
+
+[Suggestion "inc-write-buffer-size"]
+option=CFOptions.write_buffer_size
+action=increase
+
+[Suggestion "dec-max-bytes-for-level-base"]
+option=CFOptions.max_bytes_for_level_base
+action=decrease
+
+[Suggestion "inc-l0-slowdown-writes-trigger"]
+option=CFOptions.level0_slowdown_writes_trigger
+action=increase
+
+[Suggestion "inc-l0-stop-writes-trigger"]
+option=CFOptions.level0_stop_writes_trigger
+action=increase
+
+[Suggestion "inc-hard-pending-compaction-bytes-limit"]
+option=CFOptions.hard_pending_compaction_bytes_limit
+action=increase
+
+[Suggestion "inc-soft-pending-compaction-bytes-limit"]
+option=CFOptions.soft_pending_compaction_bytes_limit
+action=increase
+
+[Rule "level0-level1-ratio"]
+conditions=level0-level1-ratio
+suggestions=inc-base-max-bytes
+
+[Condition "level0-level1-ratio"]
+source=OPTIONS
+options=CFOptions.level0_file_num_compaction_trigger:CFOptions.write_buffer_size:CFOptions.max_bytes_for_level_base
+evaluate=int(options[0])*int(options[1])-int(options[2])>=1 # should evaluate to a boolean, condition triggered if evaluates to true
+
+[Suggestion "inc-base-max-bytes"]
+option=CFOptions.max_bytes_for_level_base
+action=increase
+
+[Rules "tuning-iostat-burst"]
+conditions=large-db-get-p99
+suggestions=bytes-per-sync-non0:wal-bytes-per-sync-non0:set-rate-limiter
+#overlap_time_period=10m
+
+[Condition "write-burst"]
+source=TIME_SERIES
+keys=dyno.flash_write_bytes_per_sec
+behavior=bursty
+window_sec=300 # the smaller this window, the more sensitivity to changes in the time series, so the rate_threshold should be bigger; when it's 60, then same as diff(%)
+rate_threshold=20
+
+[Condition "large-p99-read-latency"]
+source=TIME_SERIES
+keys=[]rocksdb.read.block.get.micros.p99
+behavior=bursty
+window_sec=300
+rate_threshold=10
+
+[Condition "large-db-get-p99"]
+source=TIME_SERIES
+keys=[]rocksdb.db.get.micros.p50:[]rocksdb.db.get.micros.p99
+behavior=evaluate_expression
+evaluate=(keys[1]/keys[0])>5
+
+[Suggestion "bytes-per-sync-non0"]
+option=DBOptions.bytes_per_sync
+action=set
+suggested_values=1048576
+
+[Suggestion "wal-bytes-per-sync-non0"]
+option=DBOptions.wal_bytes_per_sync
+action=set
+suggested_values=1048576
+
+[Suggestion "set-rate-limiter"]
+option=rate_limiter_bytes_per_sec
+action=set
+suggested_values=1024000
+
+[Rule "bloom-filter-percent-useful"]
+conditions=bloom-filter-percent-useful
+suggestions=inc-bloom-bits-per-key
+
+[Condition "bloom-filter-percent-useful"]
+source=TIME_SERIES
+keys=[]rocksdb.bloom.filter.useful.count:[]rocksdb.bloom.filter.full.positive.count:[]rocksdb.bloom.filter.full.true.positive.count
+behavior=evaluate_expression
+evaluate=((keys[0]+keys[2])/(keys[0]+keys[1]))<0.9 # should evaluate to a boolean
+aggregation_op=latest
+
+[Rule "bloom-not-enabled"]
+conditions=bloom-not-enabled
+suggestions=inc-bloom-bits-per-key
+
+[Condition "bloom-not-enabled"]
+source=TIME_SERIES
+keys=[]rocksdb.bloom.filter.useful.count:[]rocksdb.bloom.filter.full.positive.count:[]rocksdb.bloom.filter.full.true.positive.count
+behavior=evaluate_expression
+evaluate=keys[0]+keys[1]+keys[2]==0
+aggregation_op=avg
+
+[Suggestion "inc-bloom-bits-per-key"]
+option=bloom_bits
+action=increase
+suggested_values=2
+
+[Rule "small-l0-files"]
+conditions=small-l0-files
+suggestions=dec-max-bytes-for-level-base:inc-write-buffer-size
+
+[Condition "small-l0-files"]
+source=OPTIONS
+options=CFOptions.max_bytes_for_level_base:CFOptions.level0_file_num_compaction_trigger:CFOptions.write_buffer_size
+evaluate=int(options[0])>(10*int(options[1])*int(options[2]))
+
+[Rule "decompress-time-long"]
+conditions=decompress-time-long
+suggestions=dec-block-size:inc-block-cache-size:faster-compression-type
+
+[Condition "decompress-time-long"]
+source=TIME_SERIES
+keys=block_decompress_time:block_read_time:block_checksum_time
+behavior=evaluate_expression
+evaluate=(keys[0]/(keys[0]+keys[1]+keys[2]))>0.3
+
+[Suggestion "dec-block-size"]
+option=TableOptions.BlockBasedTable.block_size
+action=decrease
+
+[Suggestion "inc-block-cache-size"]
+option=cache_size
+action=increase
+suggested_values=16000000
+
+[Suggestion "faster-compression-type"]
+option=CFOptions.compression
+action=set
+suggested_values=kLZ4Compression
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/__init__.py b/storage/rocksdb/rocksdb/tools/advisor/test/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/__init__.py
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-0 b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-0
new file mode 100644
index 00000000..3c9d5164
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-0
@@ -0,0 +1,30 @@
+2018/05/25-14:30:05.601692 7f82bd676200 RocksDB version: 5.14.0
+2018/05/25-14:30:07.626719 7f82ba72e700 (Original Log Time 2018/05/25-14:30:07.621966) [db/db_impl_compaction_flush.cc:1424] Calling FlushMemTableToOutputFile with column family [default], flush slots available 1, compaction slots available 1, flush slots scheduled 1, compaction slots scheduled 0
+2018/05/25-14:30:07.626725 7f82ba72e700 [db/flush_job.cc:301] [default] [JOB 3] Flushing memtable with next log file: 8
+2018/05/25-14:30:07.626738 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283807626732, "job": 3, "event": "flush_started", "num_memtables": 1, "num_entries": 28018, "num_deletes": 0, "memory_usage": 4065512, "flush_reason": "Write Buffer Full"}
+2018/05/25-14:30:07.626740 7f82ba72e700 [db/flush_job.cc:331] [default] [JOB 3] Level-0 flush table #10: started
+2018/05/25-14:30:07.764232 7f82b2f20700 [db/db_impl_write.cc:1373] [default] New memtable created with log file: #11. Immutable memtables: 1.
+2018/05/25-14:30:07.764240 7f82b2f20700 [WARN] [db/column_family.cc:743] [default] Stopping writes because we have 2 immutable memtables (waiting for flush), max_write_buffer_number is set to 2
+2018/05/23-11:53:12.800143 7f9f36b40700 [WARN] [db/column_family.cc:799] [default] Stalling writes because we have 4 level-0 files rate 39886
+2018/05/23-11:53:12.800143 7f9f36b40700 [WARN] [db/column_family.cc:799] [default] Stopping writes because we have 4 level-0 files rate 39886
+2018/05/25-14:30:09.398302 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283809398276, "cf_name": "default", "job": 3, "event": "table_file_creation", "file_number": 10, "file_size": 1890434, "table_properties": {"data_size": 1876749, "index_size": 23346, "filter_size": 0, "raw_key_size": 663120, "raw_average_key_size": 24, "raw_value_size": 2763000, "raw_average_value_size": 100, "num_data_blocks": 838, "num_entries": 27630, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:30:09.398351 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 3] Level-0 flush table #10: 1890434 bytes OK
+2018/05/25-14:30:25.491635 7f82ba72e700 [db/flush_job.cc:331] [default] [JOB 10] Level-0 flush table #23: started
+2018/05/25-14:30:25.643618 7f82b2f20700 [db/db_impl_write.cc:1373] [default] New memtable created with log file: #24. Immutable memtables: 1.
+2018/05/25-14:30:25.643633 7f82b2f20700 [WARN] [db/column_family.cc:743] [default] Stopping writes because we have 2 immutable memtables (waiting for flush), max_write_buffer_number is set to 2
+2018/05/25-14:30:27.288181 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283827288158, "cf_name": "default", "job": 10, "event": "table_file_creation", "file_number": 23, "file_size": 1893200, "table_properties": {"data_size": 1879460, "index_size": 23340, "filter_size": 0, "raw_key_size": 663360, "raw_average_key_size": 24, "raw_value_size": 2764000, "raw_average_value_size": 100, "num_data_blocks": 838, "num_entries": 27640, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:30:27.288210 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 10] Level-0 flush table #23: 1893200 bytes OK
+2018/05/25-14:30:27.289353 7f82ba72e700 [WARN] [db/column_family.cc:764] [default] Stalling writes because of estimated pending compaction bytes 14410584
+2018/05/25-14:30:27.289390 7f82ba72e700 (Original Log Time 2018/05/25-14:30:27.288829) [db/memtable_list.cc:377] [default] Level-0 commit table #23 started
+2018/05/25-14:30:27.289393 7f82ba72e700 (Original Log Time 2018/05/25-14:30:27.289332) [db/memtable_list.cc:409] [default] Level-0 commit table #23: memtable #1 done
+2018/05/25-14:34:21.047206 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527284061047181, "cf_name": "default", "job": 44, "event": "table_file_creation", "file_number": 84, "file_size": 1890780, "table_properties": {"data_size": 1877100, "index_size": 23309, "filter_size": 0, "raw_key_size": 662808, "raw_average_key_size": 24, "raw_value_size": 2761700, "raw_average_value_size": 100, "num_data_blocks": 837, "num_entries": 27617, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:34:21.047233 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 44] Level-0 flush table #84: 1890780 bytes OK
+2018/05/25-14:34:21.048017 7f82ba72e700 (Original Log Time 2018/05/25-14:34:21.048005) EVENT_LOG_v1 {"time_micros": 1527284061047997, "job": 44, "event": "flush_finished", "output_compression": "Snappy", "lsm_state": [2, 1, 0, 0, 0, 0, 0], "immutable_memtables": 1}
+2018/05/25-14:34:21.048592 7f82bd676200 [DEBUG] [db/db_impl_files.cc:261] [JOB 45] Delete /tmp/rocksdbtest-155919/dbbench/000084.sst type=2 #84 -- OK
+2018/05/25-14:34:21.048603 7f82bd676200 EVENT_LOG_v1 {"time_micros": 1527284061048600, "job": 45, "event": "table_file_deletion", "file_number": 84}
+2018/05/25-14:34:21.048981 7f82bd676200 [db/db_impl.cc:398] Shutdown complete
+2018/05/25-14:34:21.049000 7f82bd676200 [db/db_impl.cc:563] [col-fam-A] random log message for testing
+2018/05/25-14:34:21.049010 7f82bd676200 [db/db_impl.cc:234] [col-fam-B] log continuing on next line
+remaining part of the log
+2018/05/25-14:34:21.049020 7f82bd676200 [db/db_impl.cc:653] [col-fam-A] another random log message
+2018/05/25-14:34:21.049025 7f82bd676200 [db/db_impl.cc:331] [unknown] random log message no column family
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-1 b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-1
new file mode 100644
index 00000000..b163f9a9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/LOG-1
@@ -0,0 +1,25 @@
+2018/05/25-14:30:05.601692 7f82bd676200 RocksDB version: 5.14.0
+2018/05/25-14:30:07.626719 7f82ba72e700 (Original Log Time 2018/05/25-14:30:07.621966) [db/db_impl_compaction_flush.cc:1424] Calling FlushMemTableToOutputFile with column family [default], flush slots available 1, compaction slots available 1, flush slots scheduled 1, compaction slots scheduled 0
+2018/05/25-14:30:07.626725 7f82ba72e700 [db/flush_job.cc:301] [default] [JOB 3] Flushing memtable with next log file: 8
+2018/05/25-14:30:07.626738 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283807626732, "job": 3, "event": "flush_started", "num_memtables": 1, "num_entries": 28018, "num_deletes": 0, "memory_usage": 4065512, "flush_reason": "Write Buffer Full"}
+2018/05/25-14:30:07.626740 7f82ba72e700 [db/flush_job.cc:331] [default] [JOB 3] Level-0 flush table #10: started
+2018/05/25-14:30:07.764232 7f82b2f20700 [db/db_impl_write.cc:1373] [default] New memtable created with log file: #11. Immutable memtables: 1.
+2018/05/25-14:30:07.764240 7f82b2f20700 [WARN] [db/column_family.cc:743] [default] Stopping writes because we have 2 immutable memtables (waiting for flush), max_write_buffer_number is set to 2
+2018/05/23-11:53:12.800143 7f9f36b40700 [WARN] [db/column_family.cc:799] [default] Stalling writes because we have 4 level-0 files rate 39886
+2018/05/23-11:53:12.800143 7f9f36b40700 [WARN] [db/column_family.cc:799] [default] Stopping writes because we have 4 level-0 files rate 39886
+2018/05/25-14:30:09.398302 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283809398276, "cf_name": "default", "job": 3, "event": "table_file_creation", "file_number": 10, "file_size": 1890434, "table_properties": {"data_size": 1876749, "index_size": 23346, "filter_size": 0, "raw_key_size": 663120, "raw_average_key_size": 24, "raw_value_size": 2763000, "raw_average_value_size": 100, "num_data_blocks": 838, "num_entries": 27630, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:30:09.398351 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 3] Level-0 flush table #10: 1890434 bytes OK
+2018/05/25-14:30:25.491635 7f82ba72e700 [db/flush_job.cc:331] [default] [JOB 10] Level-0 flush table #23: started
+2018/05/25-14:30:25.643618 7f82b2f20700 [db/db_impl_write.cc:1373] [default] New memtable created with log file: #24. Immutable memtables: 1.
+2018/05/25-14:30:25.643633 7f82b2f20700 [WARN] [db/column_family.cc:743] [default] Stopping writes because we have 2 immutable memtables (waiting for flush), max_write_buffer_number is set to 2
+2018/05/25-14:30:27.288181 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527283827288158, "cf_name": "default", "job": 10, "event": "table_file_creation", "file_number": 23, "file_size": 1893200, "table_properties": {"data_size": 1879460, "index_size": 23340, "filter_size": 0, "raw_key_size": 663360, "raw_average_key_size": 24, "raw_value_size": 2764000, "raw_average_value_size": 100, "num_data_blocks": 838, "num_entries": 27640, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:30:27.288210 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 10] Level-0 flush table #23: 1893200 bytes OK
+2018/05/25-14:30:27.289353 7f82ba72e700 [WARN] [db/column_family.cc:764] [default] Stopping writes because of estimated pending compaction bytes 14410584
+2018/05/25-14:30:27.289390 7f82ba72e700 (Original Log Time 2018/05/25-14:30:27.288829) [db/memtable_list.cc:377] [default] Level-0 commit table #23 started
+2018/05/25-14:30:27.289393 7f82ba72e700 (Original Log Time 2018/05/25-14:30:27.289332) [db/memtable_list.cc:409] [default] Level-0 commit table #23: memtable #1 done
+2018/05/25-14:34:21.047206 7f82ba72e700 EVENT_LOG_v1 {"time_micros": 1527284061047181, "cf_name": "default", "job": 44, "event": "table_file_creation", "file_number": 84, "file_size": 1890780, "table_properties": {"data_size": 1877100, "index_size": 23309, "filter_size": 0, "raw_key_size": 662808, "raw_average_key_size": 24, "raw_value_size": 2761700, "raw_average_value_size": 100, "num_data_blocks": 837, "num_entries": 27617, "filter_policy_name": "", "kDeletedKeys": "0", "kMergeOperands": "0"}}
+2018/05/25-14:34:21.047233 7f82ba72e700 [db/flush_job.cc:371] [default] [JOB 44] Level-0 flush table #84: 1890780 bytes OK
+2018/05/25-14:34:21.048017 7f82ba72e700 (Original Log Time 2018/05/25-14:34:21.048005) EVENT_LOG_v1 {"time_micros": 1527284061047997, "job": 44, "event": "flush_finished", "output_compression": "Snappy", "lsm_state": [2, 1, 0, 0, 0, 0, 0], "immutable_memtables": 1}
+2018/05/25-14:34:21.048592 7f82bd676200 [DEBUG] [db/db_impl_files.cc:261] [JOB 45] Delete /tmp/rocksdbtest-155919/dbbench/000084.sst type=2 #84 -- OK
+2018/05/25-14:34:21.048603 7f82bd676200 EVENT_LOG_v1 {"time_micros": 1527284061048600, "job": 45, "event": "table_file_deletion", "file_number": 84}
+2018/05/25-14:34:21.048981 7f82bd676200 [db/db_impl.cc:398] Shutdown complete
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/OPTIONS-000005 b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/OPTIONS-000005
new file mode 100644
index 00000000..009edb04
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/OPTIONS-000005
@@ -0,0 +1,49 @@
+# This is a RocksDB option file.
+#
+# For detailed file format spec, please refer to the example file
+# in examples/rocksdb_option_file_example.ini
+#
+
+[Version]
+ rocksdb_version=5.14.0
+ options_file_version=1.1
+
+[DBOptions]
+ manual_wal_flush=false
+ allow_ingest_behind=false
+ db_write_buffer_size=0
+ db_log_dir=
+ random_access_max_buffer_size=1048576
+
+[CFOptions "default"]
+ ttl=0
+ max_bytes_for_level_base=268435456
+ max_bytes_for_level_multiplier=10.000000
+ level0_file_num_compaction_trigger=4
+ level0_stop_writes_trigger=36
+ write_buffer_size=4194000
+ min_write_buffer_number_to_merge=1
+ num_levels=7
+ compaction_filter_factory=nullptr
+ compaction_style=kCompactionStyleLevel
+
+[TableOptions/BlockBasedTable "default"]
+ block_align=false
+ index_type=kBinarySearch
+
+[CFOptions "col_fam_A"]
+ttl=0
+max_bytes_for_level_base=268435456
+max_bytes_for_level_multiplier=10.000000
+level0_file_num_compaction_trigger=5
+level0_stop_writes_trigger=36
+write_buffer_size=1024000
+min_write_buffer_number_to_merge=1
+num_levels=5
+compaction_filter_factory=nullptr
+compaction_style=kCompactionStyleLevel
+
+[TableOptions/BlockBasedTable "col_fam_A"]
+block_align=true
+block_restart_interval=16
+index_type=kBinarySearch
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/log_stats_parser_keys_ts b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/log_stats_parser_keys_ts
new file mode 100644
index 00000000..e8ade9e3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/log_stats_parser_keys_ts
@@ -0,0 +1,3 @@
+rocksdb.number.block.decompressed.count: 1530896335 88.0, 1530896361 788338.0, 1530896387 1539256.0, 1530896414 2255696.0, 1530896440 3009325.0, 1530896466 3767183.0, 1530896492 4529775.0, 1530896518 5297809.0, 1530896545 6033802.0, 1530896570 6794129.0
+rocksdb.db.get.micros.p50: 1530896335 295.5, 1530896361 16.561841, 1530896387 16.20677, 1530896414 16.31508, 1530896440 16.346602, 1530896466 16.284669, 1530896492 16.16005, 1530896518 16.069096, 1530896545 16.028746, 1530896570 15.9638
+rocksdb.manifest.file.sync.micros.p99: 1530896335 649.0, 1530896361 835.0, 1530896387 1435.0, 1530896414 9938.0, 1530896440 9938.0, 1530896466 9938.0, 1530896492 9938.0, 1530896518 1882.0, 1530896545 1837.0, 1530896570 1792.0
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err1.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err1.ini
new file mode 100644
index 00000000..23be55dd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err1.ini
@@ -0,0 +1,56 @@
+[Rule "missing-suggestions"]
+suggestions=
+conditions=missing-source
+
+[Condition "normal-rule"]
+source=LOG
+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
+
+[Suggestion "inc-write-buffer"]
+option=CFOptions.max_write_buffer_number
+action=increase
+
+[Rule "missing-conditions"]
+conditions=
+suggestions=missing-description
+
+[Condition "missing-options"]
+source=OPTIONS
+options=
+evaluate=int(options[0])*int(options[1])-int(options[2])<(-251659456) # should evaluate to a boolean
+
+[Rule "missing-expression"]
+conditions=missing-expression
+suggestions=missing-description
+
+[Condition "missing-expression"]
+source=OPTIONS
+options=CFOptions.level0_file_num_compaction_trigger:CFOptions.write_buffer_size:CFOptions.max_bytes_for_level_base
+evaluate=
+
+[Suggestion "missing-description"]
+description=
+
+[Rule "stop-too-many-L0"]
+suggestions=inc-max-bg-compactions:missing-action:inc-l0-stop-writes-trigger
+conditions=missing-regex
+
+[Condition "missing-regex"]
+source=LOG
+regex=
+
+[Suggestion "missing-option"]
+option=
+action=increase
+
+[Suggestion "normal-suggestion"]
+option=CFOptions.write_buffer_size
+action=increase
+
+[Suggestion "inc-l0-stop-writes-trigger"]
+option=CFOptions.level0_stop_writes_trigger
+action=increase
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err2.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err2.ini
new file mode 100644
index 00000000..bce21dba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err2.ini
@@ -0,0 +1,15 @@
+[Rule "normal-rule"]
+suggestions=inc-bg-flush:inc-write-buffer
+conditions=missing-source
+
+[Condition "missing-source"]
+source=
+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
+
+[Suggestion "inc-write-buffer"]
+option=CFOptions.max_write_buffer_number
+action=increase
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err3.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err3.ini
new file mode 100644
index 00000000..73c06e46
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err3.ini
@@ -0,0 +1,15 @@
+[Rule "normal-rule"]
+suggestions=missing-action:inc-write-buffer
+conditions=missing-source
+
+[Condition "normal-condition"]
+source=LOG
+regex=Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+
+
+[Suggestion "missing-action"]
+option=DBOptions.max_background_flushes
+action=
+
+[Suggestion "inc-write-buffer"]
+option=CFOptions.max_write_buffer_number
+action=increase
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err4.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err4.ini
new file mode 100644
index 00000000..4d4aa3c7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/rules_err4.ini
@@ -0,0 +1,15 @@
+[Rule "normal-rule"]
+suggestions=inc-bg-flush
+conditions=missing-source
+
+[Condition "normal-condition"]
+source=LOG
+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
+
+[Suggestion] # missing section name
+option=CFOptions.max_write_buffer_number
+action=increase
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/test_rules.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/test_rules.ini
new file mode 100644
index 00000000..97b9374f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/test_rules.ini
@@ -0,0 +1,47 @@
+[Rule "single-condition-false"]
+suggestions=inc-bg-flush:inc-write-buffer
+conditions=log-4-false
+
+[Rule "multiple-conds-true"]
+suggestions=inc-write-buffer
+conditions=log-1-true:log-2-true:log-3-true
+
+[Rule "multiple-conds-one-false"]
+suggestions=inc-bg-flush
+conditions=log-1-true:log-4-false:log-3-true
+
+[Rule "multiple-conds-all-false"]
+suggestions=l0-l1-ratio-health-check
+conditions=log-4-false:options-1-false
+
+[Condition "log-1-true"]
+source=LOG
+regex=Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+
+
+[Condition "log-2-true"]
+source=LOG
+regex=Stalling writes because we have \d+ level-0 files
+
+[Condition "log-3-true"]
+source=LOG
+regex=Stopping writes because we have \d+ level-0 files
+
+[Condition "log-4-false"]
+source=LOG
+regex=Stalling writes because of estimated pending compaction bytes \d+
+
+[Condition "options-1-false"]
+source=OPTIONS
+options=CFOptions.level0_file_num_compaction_trigger:CFOptions.write_buffer_size:DBOptions.random_access_max_buffer_size
+evaluate=int(options[0])*int(options[1])-int(options[2])<0 # should evaluate to a boolean
+
+[Suggestion "inc-bg-flush"]
+option=DBOptions.max_background_flushes
+action=increase
+
+[Suggestion "inc-write-buffer"]
+option=CFOptions.max_write_buffer_number
+action=increase
+
+[Suggestion "l0-l1-ratio-health-check"]
+description='modify options such that (level0_file_num_compaction_trigger * write_buffer_size - max_bytes_for_level_base < 5) is satisfied'
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/input_files/triggered_rules.ini b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/triggered_rules.ini
new file mode 100644
index 00000000..83b96da2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/input_files/triggered_rules.ini
@@ -0,0 +1,83 @@
+[Rule "stall-too-many-memtables"]
+suggestions=inc-bg-flush:inc-write-buffer
+conditions=stall-too-many-memtables
+
+[Condition "stall-too-many-memtables"]
+source=LOG
+regex=Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+
+
+[Rule "stall-too-many-L0"]
+suggestions=inc-max-subcompactions:inc-max-bg-compactions:inc-write-buffer-size:dec-max-bytes-for-level-base:inc-l0-slowdown-writes-trigger
+conditions=stall-too-many-L0
+
+[Condition "stall-too-many-L0"]
+source=LOG
+regex=Stalling writes because we have \d+ level-0 files
+
+[Rule "stop-too-many-L0"]
+suggestions=inc-max-bg-compactions:inc-write-buffer-size:inc-l0-stop-writes-trigger
+conditions=stop-too-many-L0
+
+[Condition "stop-too-many-L0"]
+source=LOG
+regex=Stopping writes because we have \d+ level-0 files
+
+[Rule "stall-too-many-compaction-bytes"]
+suggestions=inc-max-bg-compactions:inc-write-buffer-size:inc-hard-pending-compaction-bytes-limit:inc-soft-pending-compaction-bytes-limit
+conditions=stall-too-many-compaction-bytes
+
+[Condition "stall-too-many-compaction-bytes"]
+source=LOG
+regex=Stalling writes because of estimated pending compaction bytes \d+
+
+[Suggestion "inc-bg-flush"]
+option=DBOptions.max_background_flushes
+action=increase
+
+[Suggestion "inc-write-buffer"]
+option=CFOptions.max_write_buffer_number
+action=increase
+
+[Suggestion "inc-max-subcompactions"]
+option=DBOptions.max_subcompactions
+action=increase
+
+[Suggestion "inc-max-bg-compactions"]
+option=DBOptions.max_background_compactions
+action=increase
+
+[Suggestion "inc-write-buffer-size"]
+option=CFOptions.write_buffer_size
+action=increase
+
+[Suggestion "dec-max-bytes-for-level-base"]
+option=CFOptions.max_bytes_for_level_base
+action=decrease
+
+[Suggestion "inc-l0-slowdown-writes-trigger"]
+option=CFOptions.level0_slowdown_writes_trigger
+action=increase
+
+[Suggestion "inc-l0-stop-writes-trigger"]
+option=CFOptions.level0_stop_writes_trigger
+action=increase
+
+[Suggestion "inc-hard-pending-compaction-bytes-limit"]
+option=CFOptions.hard_pending_compaction_bytes_limit
+action=increase
+
+[Suggestion "inc-soft-pending-compaction-bytes-limit"]
+option=CFOptions.soft_pending_compaction_bytes_limit
+action=increase
+
+[Rule "level0-level1-ratio"]
+conditions=level0-level1-ratio
+suggestions=l0-l1-ratio-health-check
+
+[Condition "level0-level1-ratio"]
+source=OPTIONS
+options=CFOptions.level0_file_num_compaction_trigger:CFOptions.write_buffer_size:CFOptions.max_bytes_for_level_base
+evaluate=int(options[0])*int(options[1])-int(options[2])>=-268173312 # should evaluate to a boolean, condition triggered if evaluates to true
+
+[Suggestion "l0-l1-ratio-health-check"]
+description='modify options such that (level0_file_num_compaction_trigger * write_buffer_size - max_bytes_for_level_base < -268173312) is satisfied'
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/test_db_bench_runner.py b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_bench_runner.py
new file mode 100644
index 00000000..1c4f77d5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_bench_runner.py
@@ -0,0 +1,147 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_bench_runner import DBBenchRunner
+from advisor.db_log_parser import NO_COL_FAMILY, DataSource
+from advisor.db_options_parser import DatabaseOptions
+import os
+import unittest
+
+
+class TestDBBenchRunnerMethods(unittest.TestCase):
+ def setUp(self):
+ self.pos_args = [
+ './../../db_bench',
+ 'overwrite',
+ 'use_existing_db=true',
+ 'duration=10'
+ ]
+ self.bench_runner = DBBenchRunner(self.pos_args)
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ options_path = os.path.join(this_path, 'input_files/OPTIONS-000005')
+ self.db_options = DatabaseOptions(options_path)
+
+ def test_setup(self):
+ self.assertEqual(self.bench_runner.db_bench_binary, self.pos_args[0])
+ self.assertEqual(self.bench_runner.benchmark, self.pos_args[1])
+ self.assertSetEqual(
+ set(self.bench_runner.db_bench_args), set(self.pos_args[2:])
+ )
+
+ def test_get_info_log_file_name(self):
+ log_file_name = DBBenchRunner.get_info_log_file_name(
+ None, 'random_path'
+ )
+ self.assertEqual(log_file_name, 'LOG')
+
+ log_file_name = DBBenchRunner.get_info_log_file_name(
+ '/dev/shm/', '/tmp/rocksdbtest-155919/dbbench/'
+ )
+ self.assertEqual(log_file_name, 'tmp_rocksdbtest-155919_dbbench_LOG')
+
+ def test_get_opt_args_str(self):
+ misc_opt_dict = {'bloom_bits': 2, 'empty_opt': None, 'rate_limiter': 3}
+ optional_args_str = DBBenchRunner.get_opt_args_str(misc_opt_dict)
+ self.assertEqual(optional_args_str, ' --bloom_bits=2 --rate_limiter=3')
+
+ def test_get_log_options(self):
+ db_path = '/tmp/rocksdb-155919/dbbench'
+ # when db_log_dir is present in the db_options
+ update_dict = {
+ 'DBOptions.db_log_dir': {NO_COL_FAMILY: '/dev/shm'},
+ 'DBOptions.stats_dump_period_sec': {NO_COL_FAMILY: '20'}
+ }
+ self.db_options.update_options(update_dict)
+ log_file_prefix, stats_freq = self.bench_runner.get_log_options(
+ self.db_options, db_path
+ )
+ self.assertEqual(
+ log_file_prefix, '/dev/shm/tmp_rocksdb-155919_dbbench_LOG'
+ )
+ self.assertEqual(stats_freq, 20)
+
+ update_dict = {
+ 'DBOptions.db_log_dir': {NO_COL_FAMILY: None},
+ 'DBOptions.stats_dump_period_sec': {NO_COL_FAMILY: '30'}
+ }
+ self.db_options.update_options(update_dict)
+ log_file_prefix, stats_freq = self.bench_runner.get_log_options(
+ self.db_options, db_path
+ )
+ self.assertEqual(log_file_prefix, '/tmp/rocksdb-155919/dbbench/LOG')
+ self.assertEqual(stats_freq, 30)
+
+ def test_build_experiment_command(self):
+ # add some misc_options to db_options
+ update_dict = {
+ 'bloom_bits': {NO_COL_FAMILY: 2},
+ 'rate_limiter_bytes_per_sec': {NO_COL_FAMILY: 128000000}
+ }
+ self.db_options.update_options(update_dict)
+ db_path = '/dev/shm'
+ experiment_command = self.bench_runner._build_experiment_command(
+ self.db_options, db_path
+ )
+ opt_args_str = DBBenchRunner.get_opt_args_str(
+ self.db_options.get_misc_options()
+ )
+ opt_args_str += (
+ ' --options_file=' +
+ self.db_options.generate_options_config('12345')
+ )
+ for arg in self.pos_args[2:]:
+ opt_args_str += (' --' + arg)
+ expected_command = (
+ self.pos_args[0] + ' --benchmarks=' + self.pos_args[1] +
+ ' --statistics --perf_level=3 --db=' + db_path + opt_args_str
+ )
+ self.assertEqual(experiment_command, expected_command)
+
+
+class TestDBBenchRunner(unittest.TestCase):
+ def setUp(self):
+ # Note: the db_bench binary should be present in the rocksdb/ directory
+ self.pos_args = [
+ './../../db_bench',
+ 'overwrite',
+ 'use_existing_db=true',
+ 'duration=20'
+ ]
+ self.bench_runner = DBBenchRunner(self.pos_args)
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ options_path = os.path.join(this_path, 'input_files/OPTIONS-000005')
+ self.db_options = DatabaseOptions(options_path)
+
+ def test_experiment_output(self):
+ update_dict = {'bloom_bits': {NO_COL_FAMILY: 2}}
+ self.db_options.update_options(update_dict)
+ db_path = '/dev/shm'
+ data_sources, throughput = self.bench_runner.run_experiment(
+ self.db_options, db_path
+ )
+ self.assertEqual(
+ data_sources[DataSource.Type.DB_OPTIONS][0].type,
+ DataSource.Type.DB_OPTIONS
+ )
+ self.assertEqual(
+ data_sources[DataSource.Type.LOG][0].type,
+ DataSource.Type.LOG
+ )
+ self.assertEqual(len(data_sources[DataSource.Type.TIME_SERIES]), 2)
+ self.assertEqual(
+ data_sources[DataSource.Type.TIME_SERIES][0].type,
+ DataSource.Type.TIME_SERIES
+ )
+ self.assertEqual(
+ data_sources[DataSource.Type.TIME_SERIES][1].type,
+ DataSource.Type.TIME_SERIES
+ )
+ self.assertEqual(
+ data_sources[DataSource.Type.TIME_SERIES][1].stats_freq_sec, 0
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/test_db_log_parser.py b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_log_parser.py
new file mode 100644
index 00000000..b7043043
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_log_parser.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_log_parser import DatabaseLogs, Log, NO_COL_FAMILY
+from advisor.rule_parser import Condition, LogCondition
+import os
+import unittest
+
+
+class TestLog(unittest.TestCase):
+ def setUp(self):
+ self.column_families = ['default', 'col_fam_A']
+
+ def test_get_column_family(self):
+ test_log = (
+ "2018/05/25-14:34:21.047233 7f82ba72e700 [db/flush_job.cc:371] " +
+ "[col_fam_A] [JOB 44] Level-0 flush table #84: 1890780 bytes OK"
+ )
+ db_log = Log(test_log, self.column_families)
+ self.assertEqual('col_fam_A', db_log.get_column_family())
+
+ test_log = (
+ "2018/05/25-14:34:21.047233 7f82ba72e700 [db/flush_job.cc:371] " +
+ "[JOB 44] Level-0 flush table #84: 1890780 bytes OK"
+ )
+ db_log = Log(test_log, self.column_families)
+ db_log.append_message('[default] some remaining part of log')
+ self.assertEqual(NO_COL_FAMILY, db_log.get_column_family())
+
+ def test_get_methods(self):
+ hr_time = "2018/05/25-14:30:25.491635"
+ context = "7f82ba72e700"
+ message = (
+ "[db/flush_job.cc:331] [default] [JOB 10] Level-0 flush table " +
+ "#23: started"
+ )
+ test_log = hr_time + " " + context + " " + message
+ db_log = Log(test_log, self.column_families)
+ self.assertEqual(db_log.get_message(), message)
+ remaining_message = "[col_fam_A] some more logs"
+ db_log.append_message(remaining_message)
+ self.assertEqual(
+ db_log.get_human_readable_time(), "2018/05/25-14:30:25.491635"
+ )
+ self.assertEqual(db_log.get_context(), "7f82ba72e700")
+ self.assertEqual(db_log.get_timestamp(), 1527258625)
+ self.assertEqual(
+ db_log.get_message(), str(message + '\n' + remaining_message)
+ )
+
+ def test_is_new_log(self):
+ new_log = "2018/05/25-14:34:21.047233 context random new log"
+ remaining_log = "2018/05/25 not really a new log"
+ self.assertTrue(Log.is_new_log(new_log))
+ self.assertFalse(Log.is_new_log(remaining_log))
+
+
+class TestDatabaseLogs(unittest.TestCase):
+ def test_check_and_trigger_conditions(self):
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ logs_path_prefix = os.path.join(this_path, 'input_files/LOG-0')
+ column_families = ['default', 'col-fam-A', 'col-fam-B']
+ db_logs = DatabaseLogs(logs_path_prefix, column_families)
+ # matches, has 2 col_fams
+ condition1 = LogCondition.create(Condition('cond-A'))
+ condition1.set_parameter('regex', 'random log message')
+ # matches, multiple lines message
+ condition2 = LogCondition.create(Condition('cond-B'))
+ condition2.set_parameter('regex', 'continuing on next line')
+ # does not match
+ condition3 = LogCondition.create(Condition('cond-C'))
+ condition3.set_parameter('regex', 'this should match no log')
+ db_logs.check_and_trigger_conditions(
+ [condition1, condition2, condition3]
+ )
+ cond1_trigger = condition1.get_trigger()
+ self.assertEqual(2, len(cond1_trigger.keys()))
+ self.assertSetEqual(
+ {'col-fam-A', NO_COL_FAMILY}, set(cond1_trigger.keys())
+ )
+ self.assertEqual(2, len(cond1_trigger['col-fam-A']))
+ messages = [
+ "[db/db_impl.cc:563] [col-fam-A] random log message for testing",
+ "[db/db_impl.cc:653] [col-fam-A] another random log message"
+ ]
+ self.assertIn(cond1_trigger['col-fam-A'][0].get_message(), messages)
+ self.assertIn(cond1_trigger['col-fam-A'][1].get_message(), messages)
+ self.assertEqual(1, len(cond1_trigger[NO_COL_FAMILY]))
+ self.assertEqual(
+ cond1_trigger[NO_COL_FAMILY][0].get_message(),
+ "[db/db_impl.cc:331] [unknown] random log message no column family"
+ )
+ cond2_trigger = condition2.get_trigger()
+ self.assertEqual(['col-fam-B'], list(cond2_trigger.keys()))
+ self.assertEqual(1, len(cond2_trigger['col-fam-B']))
+ self.assertEqual(
+ cond2_trigger['col-fam-B'][0].get_message(),
+ "[db/db_impl.cc:234] [col-fam-B] log continuing on next line\n" +
+ "remaining part of the log"
+ )
+ self.assertIsNone(condition3.get_trigger())
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/test_db_options_parser.py b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_options_parser.py
new file mode 100644
index 00000000..d53a9bdb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_options_parser.py
@@ -0,0 +1,216 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_log_parser import NO_COL_FAMILY
+from advisor.db_options_parser import DatabaseOptions
+from advisor.rule_parser import Condition, OptionCondition
+import os
+import unittest
+
+
+class TestDatabaseOptions(unittest.TestCase):
+ def setUp(self):
+ self.this_path = os.path.abspath(os.path.dirname(__file__))
+ self.og_options = os.path.join(
+ self.this_path, 'input_files/OPTIONS-000005'
+ )
+ misc_options = [
+ 'bloom_bits = 4', 'rate_limiter_bytes_per_sec = 1024000'
+ ]
+ # create the options object
+ self.db_options = DatabaseOptions(self.og_options, misc_options)
+ # perform clean-up before running tests
+ self.generated_options = os.path.join(
+ self.this_path, '../temp/OPTIONS_testing.tmp'
+ )
+ if os.path.isfile(self.generated_options):
+ os.remove(self.generated_options)
+
+ def test_get_options_diff(self):
+ old_opt = {
+ 'DBOptions.stats_dump_freq_sec': {NO_COL_FAMILY: '20'},
+ 'CFOptions.write_buffer_size': {
+ 'default': '1024000',
+ 'col_fam_A': '128000',
+ 'col_fam_B': '128000000'
+ },
+ 'DBOptions.use_fsync': {NO_COL_FAMILY: 'true'},
+ 'DBOptions.max_log_file_size': {NO_COL_FAMILY: '128000000'}
+ }
+ new_opt = {
+ 'bloom_bits': {NO_COL_FAMILY: '4'},
+ 'CFOptions.write_buffer_size': {
+ 'default': '128000000',
+ 'col_fam_A': '128000',
+ 'col_fam_C': '128000000'
+ },
+ 'DBOptions.use_fsync': {NO_COL_FAMILY: 'true'},
+ 'DBOptions.max_log_file_size': {NO_COL_FAMILY: '0'}
+ }
+ diff = DatabaseOptions.get_options_diff(old_opt, new_opt)
+
+ expected_diff = {
+ 'DBOptions.stats_dump_freq_sec': {NO_COL_FAMILY: ('20', None)},
+ 'bloom_bits': {NO_COL_FAMILY: (None, '4')},
+ 'CFOptions.write_buffer_size': {
+ 'default': ('1024000', '128000000'),
+ 'col_fam_B': ('128000000', None),
+ 'col_fam_C': (None, '128000000')
+ },
+ 'DBOptions.max_log_file_size': {NO_COL_FAMILY: ('128000000', '0')}
+ }
+ self.assertDictEqual(diff, expected_diff)
+
+ def test_is_misc_option(self):
+ self.assertTrue(DatabaseOptions.is_misc_option('bloom_bits'))
+ self.assertFalse(
+ DatabaseOptions.is_misc_option('DBOptions.stats_dump_freq_sec')
+ )
+
+ def test_set_up(self):
+ options = self.db_options.get_all_options()
+ self.assertEqual(22, len(options.keys()))
+ expected_misc_options = {
+ 'bloom_bits': '4', 'rate_limiter_bytes_per_sec': '1024000'
+ }
+ self.assertDictEqual(
+ expected_misc_options, self.db_options.get_misc_options()
+ )
+ self.assertListEqual(
+ ['default', 'col_fam_A'], self.db_options.get_column_families()
+ )
+
+ def test_get_options(self):
+ opt_to_get = [
+ 'DBOptions.manual_wal_flush', 'DBOptions.db_write_buffer_size',
+ 'bloom_bits', 'CFOptions.compaction_filter_factory',
+ 'CFOptions.num_levels', 'rate_limiter_bytes_per_sec',
+ 'TableOptions.BlockBasedTable.block_align', 'random_option'
+ ]
+ options = self.db_options.get_options(opt_to_get)
+ expected_options = {
+ 'DBOptions.manual_wal_flush': {NO_COL_FAMILY: 'false'},
+ 'DBOptions.db_write_buffer_size': {NO_COL_FAMILY: '0'},
+ 'bloom_bits': {NO_COL_FAMILY: '4'},
+ 'CFOptions.compaction_filter_factory': {
+ 'default': 'nullptr', 'col_fam_A': 'nullptr'
+ },
+ 'CFOptions.num_levels': {'default': '7', 'col_fam_A': '5'},
+ 'rate_limiter_bytes_per_sec': {NO_COL_FAMILY: '1024000'},
+ 'TableOptions.BlockBasedTable.block_align': {
+ 'default': 'false', 'col_fam_A': 'true'
+ }
+ }
+ self.assertDictEqual(expected_options, options)
+
+ def test_update_options(self):
+ # add new, update old, set old
+ # before updating
+ expected_old_opts = {
+ 'DBOptions.db_log_dir': {NO_COL_FAMILY: None},
+ 'DBOptions.manual_wal_flush': {NO_COL_FAMILY: 'false'},
+ 'bloom_bits': {NO_COL_FAMILY: '4'},
+ 'CFOptions.num_levels': {'default': '7', 'col_fam_A': '5'},
+ 'TableOptions.BlockBasedTable.block_restart_interval': {
+ 'col_fam_A': '16'
+ }
+ }
+ get_opts = list(expected_old_opts.keys())
+ options = self.db_options.get_options(get_opts)
+ self.assertEqual(expected_old_opts, options)
+ # after updating options
+ update_opts = {
+ 'DBOptions.db_log_dir': {NO_COL_FAMILY: '/dev/shm'},
+ 'DBOptions.manual_wal_flush': {NO_COL_FAMILY: 'true'},
+ 'bloom_bits': {NO_COL_FAMILY: '2'},
+ 'CFOptions.num_levels': {'col_fam_A': '7'},
+ 'TableOptions.BlockBasedTable.block_restart_interval': {
+ 'default': '32'
+ },
+ 'random_misc_option': {NO_COL_FAMILY: 'something'}
+ }
+ self.db_options.update_options(update_opts)
+ update_opts['CFOptions.num_levels']['default'] = '7'
+ update_opts['TableOptions.BlockBasedTable.block_restart_interval'] = {
+ 'default': '32', 'col_fam_A': '16'
+ }
+ get_opts.append('random_misc_option')
+ options = self.db_options.get_options(get_opts)
+ self.assertDictEqual(update_opts, options)
+ expected_misc_options = {
+ 'bloom_bits': '2',
+ 'rate_limiter_bytes_per_sec': '1024000',
+ 'random_misc_option': 'something'
+ }
+ self.assertDictEqual(
+ expected_misc_options, self.db_options.get_misc_options()
+ )
+
+ def test_generate_options_config(self):
+ # make sure file does not exist from before
+ self.assertFalse(os.path.isfile(self.generated_options))
+ self.db_options.generate_options_config('testing')
+ self.assertTrue(os.path.isfile(self.generated_options))
+
+ def test_check_and_trigger_conditions(self):
+ # options only from CFOptions
+ # setup the OptionCondition objects to check and trigger
+ update_dict = {
+ 'CFOptions.level0_file_num_compaction_trigger': {'col_fam_A': '4'},
+ 'CFOptions.max_bytes_for_level_base': {'col_fam_A': '10'}
+ }
+ self.db_options.update_options(update_dict)
+ cond1 = Condition('opt-cond-1')
+ cond1 = OptionCondition.create(cond1)
+ cond1.set_parameter(
+ 'options', [
+ 'CFOptions.level0_file_num_compaction_trigger',
+ 'TableOptions.BlockBasedTable.block_restart_interval',
+ 'CFOptions.max_bytes_for_level_base'
+ ]
+ )
+ cond1.set_parameter(
+ 'evaluate',
+ 'int(options[0])*int(options[1])-int(options[2])>=0'
+ )
+ # only DBOptions
+ cond2 = Condition('opt-cond-2')
+ cond2 = OptionCondition.create(cond2)
+ cond2.set_parameter(
+ 'options', [
+ 'DBOptions.db_write_buffer_size',
+ 'bloom_bits',
+ 'rate_limiter_bytes_per_sec'
+ ]
+ )
+ cond2.set_parameter(
+ 'evaluate',
+ '(int(options[2]) * int(options[1]) * int(options[0]))==0'
+ )
+ # mix of CFOptions and DBOptions
+ cond3 = Condition('opt-cond-3')
+ cond3 = OptionCondition.create(cond3)
+ cond3.set_parameter(
+ 'options', [
+ 'DBOptions.db_write_buffer_size', # 0
+ 'CFOptions.num_levels', # 5, 7
+ 'bloom_bits' # 4
+ ]
+ )
+ cond3.set_parameter(
+ 'evaluate', 'int(options[2])*int(options[0])+int(options[1])>6'
+ )
+ self.db_options.check_and_trigger_conditions([cond1, cond2, cond3])
+
+ cond1_trigger = {'col_fam_A': ['4', '16', '10']}
+ self.assertDictEqual(cond1_trigger, cond1.get_trigger())
+ cond2_trigger = {NO_COL_FAMILY: ['0', '4', '1024000']}
+ self.assertDictEqual(cond2_trigger, cond2.get_trigger())
+ cond3_trigger = {'default': ['0', '7', '4']}
+ self.assertDictEqual(cond3_trigger, cond3.get_trigger())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/test_db_stats_fetcher.py b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_stats_fetcher.py
new file mode 100644
index 00000000..afbbe833
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/test_db_stats_fetcher.py
@@ -0,0 +1,126 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (found in the
+# COPYING file in the root directory) and Apache 2.0 License
+# (found in the LICENSE.Apache file in the root directory).
+
+from advisor.db_stats_fetcher import LogStatsParser, DatabasePerfContext
+from advisor.db_timeseries_parser import NO_ENTITY
+from advisor.rule_parser import Condition, TimeSeriesCondition
+import os
+import time
+import unittest
+from unittest.mock import MagicMock
+
+
+class TestLogStatsParser(unittest.TestCase):
+ def setUp(self):
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ stats_file = os.path.join(
+ this_path, 'input_files/log_stats_parser_keys_ts'
+ )
+ # populate the keys_ts dictionary of LogStatsParser
+ self.stats_dict = {NO_ENTITY: {}}
+ with open(stats_file, 'r') as fp:
+ for line in fp:
+ stat_name = line.split(':')[0].strip()
+ self.stats_dict[NO_ENTITY][stat_name] = {}
+ token_list = line.split(':')[1].strip().split(',')
+ for token in token_list:
+ timestamp = int(token.split()[0])
+ value = float(token.split()[1])
+ self.stats_dict[NO_ENTITY][stat_name][timestamp] = value
+ self.log_stats_parser = LogStatsParser('dummy_log_file', 20)
+ self.log_stats_parser.keys_ts = self.stats_dict
+
+ def test_check_and_trigger_conditions_bursty(self):
+ # mock fetch_timeseries() because 'keys_ts' has been pre-populated
+ self.log_stats_parser.fetch_timeseries = MagicMock()
+ # condition: bursty
+ cond1 = Condition('cond-1')
+ cond1 = TimeSeriesCondition.create(cond1)
+ cond1.set_parameter('keys', 'rocksdb.db.get.micros.p50')
+ cond1.set_parameter('behavior', 'bursty')
+ cond1.set_parameter('window_sec', 40)
+ cond1.set_parameter('rate_threshold', 0)
+ self.log_stats_parser.check_and_trigger_conditions([cond1])
+ expected_cond_trigger = {
+ NO_ENTITY: {1530896440: 0.9767546362322214}
+ }
+ self.assertDictEqual(expected_cond_trigger, cond1.get_trigger())
+ # ensure that fetch_timeseries() was called once
+ self.log_stats_parser.fetch_timeseries.assert_called_once()
+
+ def test_check_and_trigger_conditions_eval_agg(self):
+ # mock fetch_timeseries() because 'keys_ts' has been pre-populated
+ self.log_stats_parser.fetch_timeseries = MagicMock()
+ # condition: evaluate_expression
+ cond1 = Condition('cond-1')
+ cond1 = TimeSeriesCondition.create(cond1)
+ cond1.set_parameter('keys', 'rocksdb.db.get.micros.p50')
+ cond1.set_parameter('behavior', 'evaluate_expression')
+ keys = [
+ 'rocksdb.manifest.file.sync.micros.p99',
+ 'rocksdb.db.get.micros.p50'
+ ]
+ cond1.set_parameter('keys', keys)
+ cond1.set_parameter('aggregation_op', 'latest')
+ # condition evaluates to FALSE
+ cond1.set_parameter('evaluate', 'keys[0]-(keys[1]*100)>200')
+ self.log_stats_parser.check_and_trigger_conditions([cond1])
+ expected_cond_trigger = {NO_ENTITY: [1792.0, 15.9638]}
+ self.assertIsNone(cond1.get_trigger())
+ # condition evaluates to TRUE
+ cond1.set_parameter('evaluate', 'keys[0]-(keys[1]*100)<200')
+ self.log_stats_parser.check_and_trigger_conditions([cond1])
+ expected_cond_trigger = {NO_ENTITY: [1792.0, 15.9638]}
+ self.assertDictEqual(expected_cond_trigger, cond1.get_trigger())
+ # ensure that fetch_timeseries() was called
+ self.log_stats_parser.fetch_timeseries.assert_called()
+
+ def test_check_and_trigger_conditions_eval(self):
+ # mock fetch_timeseries() because 'keys_ts' has been pre-populated
+ self.log_stats_parser.fetch_timeseries = MagicMock()
+ # condition: evaluate_expression
+ cond1 = Condition('cond-1')
+ cond1 = TimeSeriesCondition.create(cond1)
+ cond1.set_parameter('keys', 'rocksdb.db.get.micros.p50')
+ cond1.set_parameter('behavior', 'evaluate_expression')
+ keys = [
+ 'rocksdb.manifest.file.sync.micros.p99',
+ 'rocksdb.db.get.micros.p50'
+ ]
+ cond1.set_parameter('keys', keys)
+ cond1.set_parameter('evaluate', 'keys[0]-(keys[1]*100)>500')
+ self.log_stats_parser.check_and_trigger_conditions([cond1])
+ expected_trigger = {NO_ENTITY: {
+ 1530896414: [9938.0, 16.31508],
+ 1530896440: [9938.0, 16.346602],
+ 1530896466: [9938.0, 16.284669],
+ 1530896492: [9938.0, 16.16005]
+ }}
+ self.assertDictEqual(expected_trigger, cond1.get_trigger())
+ self.log_stats_parser.fetch_timeseries.assert_called_once()
+
+
+class TestDatabasePerfContext(unittest.TestCase):
+ def test_unaccumulate_metrics(self):
+ perf_dict = {
+ "user_key_comparison_count": 675903942,
+ "block_cache_hit_count": 830086,
+ }
+ timestamp = int(time.time())
+ perf_ts = {}
+ for key in perf_dict:
+ perf_ts[key] = {}
+ start_val = perf_dict[key]
+ for ix in range(5):
+ perf_ts[key][timestamp+(ix*10)] = start_val + (2 * ix * ix)
+ db_perf_context = DatabasePerfContext(perf_ts, 10, True)
+ timestamps = [timestamp+(ix*10) for ix in range(1, 5, 1)]
+ values = [val for val in range(2, 15, 4)]
+ inner_dict = {timestamps[ix]: values[ix] for ix in range(4)}
+ expected_keys_ts = {NO_ENTITY: {
+ 'user_key_comparison_count': inner_dict,
+ 'block_cache_hit_count': inner_dict
+ }}
+ self.assertDictEqual(expected_keys_ts, db_perf_context.keys_ts)
diff --git a/storage/rocksdb/rocksdb/tools/advisor/test/test_rule_parser.py b/storage/rocksdb/rocksdb/tools/advisor/test/test_rule_parser.py
new file mode 100644
index 00000000..9f1d0bf5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/advisor/test/test_rule_parser.py
@@ -0,0 +1,234 @@
+# Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+# This source code is licensed under both the GPLv2 (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 os
+import unittest
+from advisor.rule_parser import RulesSpec
+from advisor.db_log_parser import DatabaseLogs, DataSource
+from advisor.db_options_parser import DatabaseOptions
+
+RuleToSuggestions = {
+ "stall-too-many-memtables": [
+ 'inc-bg-flush',
+ 'inc-write-buffer'
+ ],
+ "stall-too-many-L0": [
+ 'inc-max-subcompactions',
+ 'inc-max-bg-compactions',
+ 'inc-write-buffer-size',
+ 'dec-max-bytes-for-level-base',
+ 'inc-l0-slowdown-writes-trigger'
+ ],
+ "stop-too-many-L0": [
+ 'inc-max-bg-compactions',
+ 'inc-write-buffer-size',
+ 'inc-l0-stop-writes-trigger'
+ ],
+ "stall-too-many-compaction-bytes": [
+ 'inc-max-bg-compactions',
+ 'inc-write-buffer-size',
+ 'inc-hard-pending-compaction-bytes-limit',
+ 'inc-soft-pending-compaction-bytes-limit'
+ ],
+ "level0-level1-ratio": [
+ 'l0-l1-ratio-health-check'
+ ]
+}
+
+
+class TestAllRulesTriggered(unittest.TestCase):
+ def setUp(self):
+ # load the Rules
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ ini_path = os.path.join(this_path, 'input_files/triggered_rules.ini')
+ self.db_rules = RulesSpec(ini_path)
+ self.db_rules.load_rules_from_spec()
+ self.db_rules.perform_section_checks()
+ # load the data sources: LOG and OPTIONS
+ log_path = os.path.join(this_path, 'input_files/LOG-0')
+ options_path = os.path.join(this_path, 'input_files/OPTIONS-000005')
+ db_options_parser = DatabaseOptions(options_path)
+ self.column_families = db_options_parser.get_column_families()
+ db_logs_parser = DatabaseLogs(log_path, self.column_families)
+ self.data_sources = {
+ DataSource.Type.DB_OPTIONS: [db_options_parser],
+ DataSource.Type.LOG: [db_logs_parser]
+ }
+
+ def test_triggered_conditions(self):
+ conditions_dict = self.db_rules.get_conditions_dict()
+ rules_dict = self.db_rules.get_rules_dict()
+ # Make sure none of the conditions is triggered beforehand
+ for cond in conditions_dict.values():
+ self.assertFalse(cond.is_triggered(), repr(cond))
+ for rule in rules_dict.values():
+ self.assertFalse(
+ rule.is_triggered(conditions_dict, self.column_families),
+ repr(rule)
+ )
+
+ # # Trigger the conditions as per the data sources.
+ # trigger_conditions(, conditions_dict)
+
+ # Get the set of rules that have been triggered
+ triggered_rules = self.db_rules.get_triggered_rules(
+ self.data_sources, self.column_families
+ )
+
+ # Make sure each condition and rule is triggered
+ for cond in conditions_dict.values():
+ if cond.get_data_source() is DataSource.Type.TIME_SERIES:
+ continue
+ self.assertTrue(cond.is_triggered(), repr(cond))
+
+ for rule in rules_dict.values():
+ self.assertIn(rule, triggered_rules)
+ # Check the suggestions made by the triggered rules
+ for sugg in rule.get_suggestions():
+ self.assertIn(sugg, RuleToSuggestions[rule.name])
+
+ for rule in triggered_rules:
+ self.assertIn(rule, rules_dict.values())
+ for sugg in RuleToSuggestions[rule.name]:
+ self.assertIn(sugg, rule.get_suggestions())
+
+
+class TestConditionsConjunctions(unittest.TestCase):
+ def setUp(self):
+ # load the Rules
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ ini_path = os.path.join(this_path, 'input_files/test_rules.ini')
+ self.db_rules = RulesSpec(ini_path)
+ self.db_rules.load_rules_from_spec()
+ self.db_rules.perform_section_checks()
+ # load the data sources: LOG and OPTIONS
+ log_path = os.path.join(this_path, 'input_files/LOG-1')
+ options_path = os.path.join(this_path, 'input_files/OPTIONS-000005')
+ db_options_parser = DatabaseOptions(options_path)
+ self.column_families = db_options_parser.get_column_families()
+ db_logs_parser = DatabaseLogs(log_path, self.column_families)
+ self.data_sources = {
+ DataSource.Type.DB_OPTIONS: [db_options_parser],
+ DataSource.Type.LOG: [db_logs_parser]
+ }
+
+ def test_condition_conjunctions(self):
+ conditions_dict = self.db_rules.get_conditions_dict()
+ rules_dict = self.db_rules.get_rules_dict()
+ # Make sure none of the conditions is triggered beforehand
+ for cond in conditions_dict.values():
+ self.assertFalse(cond.is_triggered(), repr(cond))
+ for rule in rules_dict.values():
+ self.assertFalse(
+ rule.is_triggered(conditions_dict, self.column_families),
+ repr(rule)
+ )
+
+ # Trigger the conditions as per the data sources.
+ self.db_rules.trigger_conditions(self.data_sources)
+
+ # Check for the conditions
+ conds_triggered = ['log-1-true', 'log-2-true', 'log-3-true']
+ conds_not_triggered = ['log-4-false', 'options-1-false']
+ for cond in conds_triggered:
+ self.assertTrue(conditions_dict[cond].is_triggered(), repr(cond))
+ for cond in conds_not_triggered:
+ self.assertFalse(conditions_dict[cond].is_triggered(), repr(cond))
+
+ # Check for the rules
+ rules_triggered = ['multiple-conds-true']
+ rules_not_triggered = [
+ 'single-condition-false',
+ 'multiple-conds-one-false',
+ 'multiple-conds-all-false'
+ ]
+ for rule_name in rules_triggered:
+ rule = rules_dict[rule_name]
+ self.assertTrue(
+ rule.is_triggered(conditions_dict, self.column_families),
+ repr(rule)
+ )
+ for rule_name in rules_not_triggered:
+ rule = rules_dict[rule_name]
+ self.assertFalse(
+ rule.is_triggered(conditions_dict, self.column_families),
+ repr(rule)
+ )
+
+
+class TestSanityChecker(unittest.TestCase):
+ def setUp(self):
+ this_path = os.path.abspath(os.path.dirname(__file__))
+ ini_path = os.path.join(this_path, 'input_files/rules_err1.ini')
+ db_rules = RulesSpec(ini_path)
+ db_rules.load_rules_from_spec()
+ self.rules_dict = db_rules.get_rules_dict()
+ self.conditions_dict = db_rules.get_conditions_dict()
+ self.suggestions_dict = db_rules.get_suggestions_dict()
+
+ def test_rule_missing_suggestions(self):
+ regex = '.*rule must have at least one suggestion.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.rules_dict['missing-suggestions'].perform_checks()
+
+ def test_rule_missing_conditions(self):
+ regex = '.*rule must have at least one condition.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.rules_dict['missing-conditions'].perform_checks()
+
+ def test_condition_missing_regex(self):
+ regex = '.*provide regex for log condition.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.conditions_dict['missing-regex'].perform_checks()
+
+ def test_condition_missing_options(self):
+ regex = '.*options missing in condition.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.conditions_dict['missing-options'].perform_checks()
+
+ def test_condition_missing_expression(self):
+ regex = '.*expression missing in condition.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.conditions_dict['missing-expression'].perform_checks()
+
+ def test_suggestion_missing_option(self):
+ regex = '.*provide option or description.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.suggestions_dict['missing-option'].perform_checks()
+
+ def test_suggestion_missing_description(self):
+ regex = '.*provide option or description.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ self.suggestions_dict['missing-description'].perform_checks()
+
+
+class TestParsingErrors(unittest.TestCase):
+ def setUp(self):
+ self.this_path = os.path.abspath(os.path.dirname(__file__))
+
+ def test_condition_missing_source(self):
+ ini_path = os.path.join(self.this_path, 'input_files/rules_err2.ini')
+ db_rules = RulesSpec(ini_path)
+ regex = '.*provide source for condition.*'
+ with self.assertRaisesRegex(NotImplementedError, regex):
+ db_rules.load_rules_from_spec()
+
+ def test_suggestion_missing_action(self):
+ ini_path = os.path.join(self.this_path, 'input_files/rules_err3.ini')
+ db_rules = RulesSpec(ini_path)
+ regex = '.*provide action for option.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ db_rules.load_rules_from_spec()
+
+ def test_section_no_name(self):
+ ini_path = os.path.join(self.this_path, 'input_files/rules_err4.ini')
+ db_rules = RulesSpec(ini_path)
+ regex = 'Parsing error: needed section header:.*'
+ with self.assertRaisesRegex(ValueError, regex):
+ db_rules.load_rules_from_spec()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/storage/rocksdb/rocksdb/tools/analyze_txn_stress_test.sh b/storage/rocksdb/rocksdb/tools/analyze_txn_stress_test.sh
new file mode 100755
index 00000000..477b1fac
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/analyze_txn_stress_test.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# Usage:
+# 1. Enable ROCKS_LOG_DETAILS in util/logging.h
+# 2. Run ./transaction_test --gtest_filter="MySQLStyleTransactionTest/MySQLStyleTransactionTest.TransactionStressTest/*" --gtest_break_on_failure
+# 3. SET=1 # 2 or 3
+# 4. LOG=/dev/shm/transaction_testdb_8600601584148590297/LOG
+# 5. grep RandomTransactionVerify $LOG | cut -d' ' -f 12 | sort -n # to find verify snapshots
+# 5. vn=1345
+# 6. vn_1=1340
+# 4. . tools/tools/analyze_txn_stress_test.sh
+echo Input params:
+# The rocksdb LOG path
+echo $LOG
+# Snapshot at which we got RandomTransactionVerify failure
+echo $vn
+# The snapshot before that where RandomTransactionVerify passed
+echo $vn_1
+# The stress tests use 3 sets, one or more might have shown inconsistent results.
+SET=${SET-1} # 1 or 2 or 3
+echo Checking set number $SET
+
+# Find the txns that committed between the two snapshots, and gather their changes made by them in /tmp/changes.txt
+# 2019/02/28-15:25:51.655477 7fffec9ff700 [DEBUG] [ilities/transactions/write_prepared_txn_db.cc:416] Txn 68497 Committing with 68498
+grep Committing $LOG | awk '{if ($9 <= vn && $9 > vn_1) print $0}' vn=$vn vn_1=${vn_1} > /tmp/txn.txt
+# 2019/02/28-15:25:49.046464 7fffe81f5700 [DEBUG] [il/transaction_test_util.cc:216] Commit of 65541 OK (txn12936193128775589751-9089)
+for i in `cat /tmp/txn.txt | awk '{print $6}'`; do grep "Commit of $i " $LOG; done > /tmp/names.txt
+for n in `cat /tmp/names.txt | awk '{print $9}'`; do grep $n $LOG; done > /tmp/changes.txt
+echo "Sum of the changes:"
+cat /tmp/changes.txt | grep Insert | awk '{print $12}' | cut -d= -f1 | cut -d+ -f2 | awk '{sum+=$1} END{print sum}'
+
+# Gather read values at each snapshot
+# 2019/02/28-15:25:51.655926 7fffebbff700 [DEBUG] [il/transaction_test_util.cc:347] VerifyRead at 67972 (67693): 000230 value: 15983
+grep "VerifyRead at ${vn_1} (.*): 000${SET}" $LOG | cut -d' ' -f 9- > /tmp/va.txt
+grep "VerifyRead at ${vn} (.*): 000${SET}" $LOG | cut -d' ' -f 9- > /tmp/vb.txt
+
+# For each key in the 2nd snapshot, find the value read by 1st, do the adds, and see if the results match.
+IFS=$'\n'
+for l in `cat /tmp/vb.txt`;
+do
+ grep $l /tmp/va.txt > /dev/null ;
+ if [[ $? -ne 0 ]]; then
+ #echo $l
+ k=`echo $l | awk '{print $1}'`;
+ v=`echo $l | awk '{print $3}'`;
+ # 2019/02/28-15:25:19.350111 7fffe81f5700 [DEBUG] [il/transaction_test_util.cc:194] Insert (txn12936193128775589751-2298) OK snap: 16289 key:000219 value: 3772+95=3867
+ exp=`grep "\<$k\>" /tmp/changes.txt | tail -1 | cut -d= -f2`;
+ if [[ $v -ne $exp ]]; then echo $l; fi
+ else
+ k=`echo $l | awk '{print $1}'`;
+ grep "\<$k\>" /tmp/changes.txt
+ fi;
+done
+
+# Check that all the keys read in the 1st snapshot are still visible in the 2nd
+for l in `cat /tmp/va.txt`;
+do
+ k=`echo $l | awk '{print $1}'`;
+ grep "\<$k\>" /tmp/vb.txt > /dev/null
+ if [[ $? -ne 0 ]]; then
+ echo missing key $k
+ fi
+done
+
+# The following found a bug in ValidateSnapshot. It checks if the adds on each key match up.
+grep Insert /tmp/changes.txt | cut -d' ' -f 10 | sort | uniq > /tmp/keys.txt
+for k in `cat /tmp/keys.txt`;
+do
+ grep "\<$k\>" /tmp/changes.txt > /tmp/adds.txt;
+ # 2019/02/28-15:25:19.350111 7fffe81f5700 [DEBUG] [il/transaction_test_util.cc:194] Insert (txn12936193128775589751-2298) OK snap: 16289 key:000219 value: 3772+95=3867
+ START=`head -1 /tmp/adds.txt | cut -d' ' -f 12 | cut -d+ -f1`
+ END=`tail -1 /tmp/adds.txt | cut -d' ' -f 12 | cut -d= -f2`
+ ADDS=`cat /tmp/adds.txt | grep Insert | awk '{print $12}' | cut -d= -f1 | cut -d+ -f2 | awk '{sum+=$1} END{print sum}'`
+ EXP=$((START+ADDS))
+ # If first + all the adds != last then there was an issue with ValidateSnapshot.
+ if [[ $END -ne $EXP ]]; then echo inconsistent txn: $k $START+$ADDS=$END; cat /tmp/adds.txt; return 1; fi
+done
diff --git a/storage/rocksdb/rocksdb/tools/auto_sanity_test.sh b/storage/rocksdb/rocksdb/tools/auto_sanity_test.sh
new file mode 100755
index 00000000..4670ef9b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/auto_sanity_test.sh
@@ -0,0 +1,93 @@
+# shellcheck disable=SC2148
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+TMP_DIR="${TMPDIR:-/tmp}/rocksdb-sanity-test"
+
+if [ "$#" -lt 2 ]; then
+ echo "usage: ./auto_sanity_test.sh [new_commit] [old_commit]"
+ echo "Missing either [new_commit] or [old_commit], perform sanity check with the latest and 10th latest commits."
+ recent_commits=`git log | grep -e "^commit [a-z0-9]\+$"| head -n10 | sed -e 's/commit //g'`
+ commit_new=`echo "$recent_commits" | head -n1`
+ commit_old=`echo "$recent_commits" | tail -n1`
+ echo "the most recent commits are:"
+ echo "$recent_commits"
+else
+ commit_new=$1
+ commit_old=$2
+fi
+
+if [ ! -d $TMP_DIR ]; then
+ mkdir $TMP_DIR
+fi
+dir_new="${TMP_DIR}/${commit_new}"
+dir_old="${TMP_DIR}/${commit_old}"
+
+function makestuff() {
+ echo "make clean"
+ make clean > /dev/null
+ echo "make db_sanity_test -j32"
+ make db_sanity_test -j32 > /dev/null
+ if [ $? -ne 0 ]; then
+ echo "[ERROR] Failed to perform 'make db_sanity_test'"
+ exit 1
+ fi
+}
+
+rm -r -f $dir_new
+rm -r -f $dir_old
+
+echo "Running db sanity check with commits $commit_new and $commit_old."
+
+echo "============================================================="
+echo "Making build $commit_new"
+git checkout $commit_new
+if [ $? -ne 0 ]; then
+ echo "[ERROR] Can't checkout $commit_new"
+ exit 1
+fi
+makestuff
+mv db_sanity_test new_db_sanity_test
+echo "Creating db based on the new commit --- $commit_new"
+./new_db_sanity_test $dir_new create
+cp ./tools/db_sanity_test.cc $dir_new
+cp ./tools/auto_sanity_test.sh $dir_new
+
+echo "============================================================="
+echo "Making build $commit_old"
+git checkout $commit_old
+if [ $? -ne 0 ]; then
+ echo "[ERROR] Can't checkout $commit_old"
+ exit 1
+fi
+cp -f $dir_new/db_sanity_test.cc ./tools/.
+cp -f $dir_new/auto_sanity_test.sh ./tools/.
+makestuff
+mv db_sanity_test old_db_sanity_test
+echo "Creating db based on the old commit --- $commit_old"
+./old_db_sanity_test $dir_old create
+
+echo "============================================================="
+echo "[Backward Compatibility Check]"
+echo "Verifying old db $dir_old using the new commit --- $commit_new"
+./new_db_sanity_test $dir_old verify
+if [ $? -ne 0 ]; then
+ echo "[ERROR] Backward Compatibility Check fails:"
+ echo " Verification of $dir_old using commit $commit_new failed."
+ exit 2
+fi
+
+echo "============================================================="
+echo "[Forward Compatibility Check]"
+echo "Verifying new db $dir_new using the old commit --- $commit_old"
+./old_db_sanity_test $dir_new verify
+if [ $? -ne 0 ]; then
+ echo "[ERROR] Forward Compatibility Check fails:"
+ echo " $dir_new using commit $commit_old failed."
+ exit 2
+fi
+
+rm old_db_sanity_test
+rm new_db_sanity_test
+rm -rf $dir_new
+rm -rf $dir_old
+
+echo "Auto sanity test passed!"
diff --git a/storage/rocksdb/rocksdb/tools/benchmark.sh b/storage/rocksdb/rocksdb/tools/benchmark.sh
new file mode 100755
index 00000000..f27926ed
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/benchmark.sh
@@ -0,0 +1,525 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# REQUIRE: db_bench binary exists in the current directory
+
+if [ $# -ne 1 ]; then
+ echo -n "./benchmark.sh [bulkload/fillseq/overwrite/filluniquerandom/"
+ echo "readrandom/readwhilewriting/readwhilemerging/updaterandom/"
+ echo "mergerandom/randomtransaction/compact]"
+ exit 0
+fi
+
+# Make it easier to run only the compaction test. Getting valid data requires
+# a number of iterations and having an ability to run the test separately from
+# rest of the benchmarks helps.
+if [ "$COMPACTION_TEST" == "1" -a "$1" != "universal_compaction" ]; then
+ echo "Skipping $1 because it's not a compaction test."
+ exit 0
+fi
+
+# size constants
+K=1024
+M=$((1024 * K))
+G=$((1024 * M))
+T=$((1024 * G))
+
+if [ -z $DB_DIR ]; then
+ echo "DB_DIR is not defined"
+ exit 0
+fi
+
+if [ -z $WAL_DIR ]; then
+ echo "WAL_DIR is not defined"
+ exit 0
+fi
+
+output_dir=${OUTPUT_DIR:-/tmp/}
+if [ ! -d $output_dir ]; then
+ mkdir -p $output_dir
+fi
+
+# all multithreaded tests run with sync=1 unless
+# $DB_BENCH_NO_SYNC is defined
+syncval="1"
+if [ ! -z $DB_BENCH_NO_SYNC ]; then
+ echo "Turning sync off for all multithreaded tests"
+ syncval="0";
+fi
+
+num_threads=${NUM_THREADS:-64}
+mb_written_per_sec=${MB_WRITE_PER_SEC:-0}
+# Only for tests that do range scans
+num_nexts_per_seek=${NUM_NEXTS_PER_SEEK:-10}
+cache_size=${CACHE_SIZE:-$((17179869184))}
+compression_max_dict_bytes=${COMPRESSION_MAX_DICT_BYTES:-0}
+compression_type=${COMPRESSION_TYPE:-zstd}
+duration=${DURATION:-0}
+
+num_keys=${NUM_KEYS:-8000000000}
+key_size=${KEY_SIZE:-20}
+value_size=${VALUE_SIZE:-400}
+block_size=${BLOCK_SIZE:-8192}
+
+const_params="
+ --db=$DB_DIR \
+ --wal_dir=$WAL_DIR \
+ \
+ --num=$num_keys \
+ --num_levels=6 \
+ --key_size=$key_size \
+ --value_size=$value_size \
+ --block_size=$block_size \
+ --cache_size=$cache_size \
+ --cache_numshardbits=6 \
+ --compression_max_dict_bytes=$compression_max_dict_bytes \
+ --compression_ratio=0.5 \
+ --compression_type=$compression_type \
+ --level_compaction_dynamic_level_bytes=true \
+ --bytes_per_sync=$((8 * M)) \
+ --cache_index_and_filter_blocks=0 \
+ --pin_l0_filter_and_index_blocks_in_cache=1 \
+ --benchmark_write_rate_limit=$(( 1024 * 1024 * $mb_written_per_sec )) \
+ \
+ --hard_rate_limit=3 \
+ --rate_limit_delay_max_milliseconds=1000000 \
+ --write_buffer_size=$((128 * M)) \
+ --target_file_size_base=$((128 * M)) \
+ --max_bytes_for_level_base=$((1 * G)) \
+ \
+ --verify_checksum=1 \
+ --delete_obsolete_files_period_micros=$((60 * M)) \
+ --max_bytes_for_level_multiplier=8 \
+ \
+ --statistics=0 \
+ --stats_per_interval=1 \
+ --stats_interval_seconds=60 \
+ --histogram=1 \
+ \
+ --memtablerep=skip_list \
+ --bloom_bits=10 \
+ --open_files=-1"
+
+l0_config="
+ --level0_file_num_compaction_trigger=4 \
+ --level0_stop_writes_trigger=20"
+
+if [ $duration -gt 0 ]; then
+ const_params="$const_params --duration=$duration"
+fi
+
+params_w="$const_params \
+ $l0_config \
+ --max_background_compactions=16 \
+ --max_write_buffer_number=8 \
+ --max_background_flushes=7"
+
+params_bulkload="$const_params \
+ --max_background_compactions=16 \
+ --max_write_buffer_number=8 \
+ --allow_concurrent_memtable_write=false \
+ --max_background_flushes=7 \
+ --level0_file_num_compaction_trigger=$((10 * M)) \
+ --level0_slowdown_writes_trigger=$((10 * M)) \
+ --level0_stop_writes_trigger=$((10 * M))"
+
+params_fillseq="$params_w \
+ --allow_concurrent_memtable_write=false"
+#
+# Tune values for level and universal compaction.
+# For universal compaction, these level0_* options mean total sorted of runs in
+# LSM. In level-based compaction, it means number of L0 files.
+#
+params_level_compact="$const_params \
+ --max_background_flushes=4 \
+ --max_write_buffer_number=4 \
+ --level0_file_num_compaction_trigger=4 \
+ --level0_slowdown_writes_trigger=16 \
+ --level0_stop_writes_trigger=20"
+
+params_univ_compact="$const_params \
+ --max_background_flushes=4 \
+ --max_write_buffer_number=4 \
+ --level0_file_num_compaction_trigger=8 \
+ --level0_slowdown_writes_trigger=16 \
+ --level0_stop_writes_trigger=20"
+
+function summarize_result {
+ test_out=$1
+ test_name=$2
+ bench_name=$3
+
+ # Note that this function assumes that the benchmark executes long enough so
+ # that "Compaction Stats" is written to stdout at least once. If it won't
+ # happen then empty output from grep when searching for "Sum" will cause
+ # syntax errors.
+ uptime=$( grep ^Uptime\(secs $test_out | tail -1 | awk '{ printf "%.0f", $2 }' )
+ stall_time=$( grep "^Cumulative stall" $test_out | tail -1 | awk '{ print $3 }' )
+ stall_pct=$( grep "^Cumulative stall" $test_out| tail -1 | awk '{ print $5 }' )
+ ops_sec=$( grep ^${bench_name} $test_out | awk '{ print $5 }' )
+ mb_sec=$( grep ^${bench_name} $test_out | awk '{ print $7 }' )
+ lo_wgb=$( grep "^ L0" $test_out | tail -1 | awk '{ print $9 }' )
+ sum_wgb=$( grep "^ Sum" $test_out | tail -1 | awk '{ print $9 }' )
+ sum_size=$( grep "^ Sum" $test_out | tail -1 | awk '{ printf "%.1f", $3 / 1024.0 }' )
+ wamp=$( echo "scale=1; $sum_wgb / $lo_wgb" | bc )
+ wmb_ps=$( echo "scale=1; ( $sum_wgb * 1024.0 ) / $uptime" | bc )
+ usecs_op=$( grep ^${bench_name} $test_out | awk '{ printf "%.1f", $3 }' )
+ p50=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.1f", $3 }' )
+ p75=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.1f", $5 }' )
+ p99=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $7 }' )
+ p999=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $9 }' )
+ p9999=$( grep "^Percentiles:" $test_out | tail -1 | awk '{ printf "%.0f", $11 }' )
+ echo -e "$ops_sec\t$mb_sec\t$sum_size\t$lo_wgb\t$sum_wgb\t$wamp\t$wmb_ps\t$usecs_op\t$p50\t$p75\t$p99\t$p999\t$p9999\t$uptime\t$stall_time\t$stall_pct\t$test_name" \
+ >> $output_dir/report.txt
+}
+
+function run_bulkload {
+ # This runs with a vector memtable and the WAL disabled to load faster. It is still crash safe and the
+ # client can discover where to restart a load after a crash. I think this is a good way to load.
+ echo "Bulk loading $num_keys random keys"
+ cmd="./db_bench --benchmarks=fillrandom \
+ --use_existing_db=0 \
+ --disable_auto_compactions=1 \
+ --sync=0 \
+ $params_bulkload \
+ --threads=1 \
+ --memtablerep=vector \
+ --allow_concurrent_memtable_write=false \
+ --disable_wal=1 \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/benchmark_bulkload_fillrandom.log"
+ echo $cmd | tee $output_dir/benchmark_bulkload_fillrandom.log
+ eval $cmd
+ summarize_result $output_dir/benchmark_bulkload_fillrandom.log bulkload fillrandom
+ echo "Compacting..."
+ cmd="./db_bench --benchmarks=compact \
+ --use_existing_db=1 \
+ --disable_auto_compactions=1 \
+ --sync=0 \
+ $params_w \
+ --threads=1 \
+ 2>&1 | tee -a $output_dir/benchmark_bulkload_compact.log"
+ echo $cmd | tee $output_dir/benchmark_bulkload_compact.log
+ eval $cmd
+}
+
+#
+# Parameter description:
+#
+# $1 - 1 if I/O statistics should be collected.
+# $2 - compaction type to use (level=0, universal=1).
+# $3 - number of subcompactions.
+# $4 - number of maximum background compactions.
+#
+function run_manual_compaction_worker {
+ # This runs with a vector memtable and the WAL disabled to load faster.
+ # It is still crash safe and the client can discover where to restart a
+ # load after a crash. I think this is a good way to load.
+ echo "Bulk loading $num_keys random keys for manual compaction."
+
+ fillrandom_output_file=$output_dir/benchmark_man_compact_fillrandom_$3.log
+ man_compact_output_log=$output_dir/benchmark_man_compact_$3.log
+
+ if [ "$2" == "1" ]; then
+ extra_params=$params_univ_compact
+ else
+ extra_params=$params_level_compact
+ fi
+
+ # Make sure that fillrandom uses the same compaction options as compact.
+ cmd="./db_bench --benchmarks=fillrandom \
+ --use_existing_db=0 \
+ --disable_auto_compactions=0 \
+ --sync=0 \
+ $extra_params \
+ --threads=$num_threads \
+ --compaction_measure_io_stats=$1 \
+ --compaction_style=$2 \
+ --subcompactions=$3 \
+ --memtablerep=vector \
+ --allow_concurrent_memtable_write=false \
+ --disable_wal=1 \
+ --max_background_compactions=$4 \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $fillrandom_output_file"
+
+ echo $cmd | tee $fillrandom_output_file
+ eval $cmd
+
+ summarize_result $fillrandom_output_file man_compact_fillrandom_$3 fillrandom
+
+ echo "Compacting with $3 subcompactions specified ..."
+
+ # This is the part we're really interested in. Given that compact benchmark
+ # doesn't output regular statistics then we'll just use the time command to
+ # measure how long this step takes.
+ cmd="{ \
+ time ./db_bench --benchmarks=compact \
+ --use_existing_db=1 \
+ --disable_auto_compactions=0 \
+ --sync=0 \
+ $extra_params \
+ --threads=$num_threads \
+ --compaction_measure_io_stats=$1 \
+ --compaction_style=$2 \
+ --subcompactions=$3 \
+ --max_background_compactions=$4 \
+ ;}
+ 2>&1 | tee -a $man_compact_output_log"
+
+ echo $cmd | tee $man_compact_output_log
+ eval $cmd
+
+ # Can't use summarize_result here. One way to analyze the results is to run
+ # "grep real" on the resulting log files.
+}
+
+function run_univ_compaction {
+ # Always ask for I/O statistics to be measured.
+ io_stats=1
+
+ # Values: kCompactionStyleLevel = 0x0, kCompactionStyleUniversal = 0x1.
+ compaction_style=1
+
+ # Define a set of benchmarks.
+ subcompactions=(1 2 4 8 16)
+ max_background_compactions=(16 16 8 4 2)
+
+ i=0
+ total=${#subcompactions[@]}
+
+ # Execute a set of benchmarks to cover variety of scenarios.
+ while [ "$i" -lt "$total" ]
+ do
+ run_manual_compaction_worker $io_stats $compaction_style ${subcompactions[$i]} \
+ ${max_background_compactions[$i]}
+ ((i++))
+ done
+}
+
+function run_fillseq {
+ # This runs with a vector memtable. WAL can be either disabled or enabled
+ # depending on the input parameter (1 for disabled, 0 for enabled). The main
+ # benefit behind disabling WAL is to make loading faster. It is still crash
+ # safe and the client can discover where to restart a load after a crash. I
+ # think this is a good way to load.
+
+ # Make sure that we'll have unique names for all the files so that data won't
+ # be overwritten.
+ if [ $1 == 1 ]; then
+ log_file_name=$output_dir/benchmark_fillseq.wal_disabled.v${value_size}.log
+ test_name=fillseq.wal_disabled.v${value_size}
+ else
+ log_file_name=$output_dir/benchmark_fillseq.wal_enabled.v${value_size}.log
+ test_name=fillseq.wal_enabled.v${value_size}
+ fi
+
+ echo "Loading $num_keys keys sequentially"
+ cmd="./db_bench --benchmarks=fillseq \
+ --use_existing_db=0 \
+ --sync=0 \
+ $params_fillseq \
+ --min_level_to_compress=0 \
+ --threads=1 \
+ --memtablerep=vector \
+ --allow_concurrent_memtable_write=false \
+ --disable_wal=$1 \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $log_file_name"
+ echo $cmd | tee $log_file_name
+ eval $cmd
+
+ # The constant "fillseq" which we pass to db_bench is the benchmark name.
+ summarize_result $log_file_name $test_name fillseq
+}
+
+function run_change {
+ operation=$1
+ echo "Do $num_keys random $operation"
+ out_name="benchmark_${operation}.t${num_threads}.s${syncval}.log"
+ cmd="./db_bench --benchmarks=$operation \
+ --use_existing_db=1 \
+ --sync=$syncval \
+ $params_w \
+ --threads=$num_threads \
+ --merge_operator=\"put\" \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} ${operation}.t${num_threads}.s${syncval} $operation
+}
+
+function run_filluniquerandom {
+ echo "Loading $num_keys unique keys randomly"
+ cmd="./db_bench --benchmarks=filluniquerandom \
+ --use_existing_db=0 \
+ --sync=0 \
+ $params_w \
+ --threads=1 \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/benchmark_filluniquerandom.log"
+ echo $cmd | tee $output_dir/benchmark_filluniquerandom.log
+ eval $cmd
+ summarize_result $output_dir/benchmark_filluniquerandom.log filluniquerandom filluniquerandom
+}
+
+function run_readrandom {
+ echo "Reading $num_keys random keys"
+ out_name="benchmark_readrandom.t${num_threads}.log"
+ cmd="./db_bench --benchmarks=readrandom \
+ --use_existing_db=1 \
+ $params_w \
+ --threads=$num_threads \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} readrandom.t${num_threads} readrandom
+}
+
+function run_readwhile {
+ operation=$1
+ echo "Reading $num_keys random keys while $operation"
+ out_name="benchmark_readwhile${operation}.t${num_threads}.log"
+ cmd="./db_bench --benchmarks=readwhile${operation} \
+ --use_existing_db=1 \
+ --sync=$syncval \
+ $params_w \
+ --threads=$num_threads \
+ --merge_operator=\"put\" \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} readwhile${operation}.t${num_threads} readwhile${operation}
+}
+
+function run_rangewhile {
+ operation=$1
+ full_name=$2
+ reverse_arg=$3
+ out_name="benchmark_${full_name}.t${num_threads}.log"
+ echo "Range scan $num_keys random keys while ${operation} for reverse_iter=${reverse_arg}"
+ cmd="./db_bench --benchmarks=seekrandomwhile${operation} \
+ --use_existing_db=1 \
+ --sync=$syncval \
+ $params_w \
+ --threads=$num_threads \
+ --merge_operator=\"put\" \
+ --seek_nexts=$num_nexts_per_seek \
+ --reverse_iterator=$reverse_arg \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} ${full_name}.t${num_threads} seekrandomwhile${operation}
+}
+
+function run_range {
+ full_name=$1
+ reverse_arg=$2
+ out_name="benchmark_${full_name}.t${num_threads}.log"
+ echo "Range scan $num_keys random keys for reverse_iter=${reverse_arg}"
+ cmd="./db_bench --benchmarks=seekrandom \
+ --use_existing_db=1 \
+ $params_w \
+ --threads=$num_threads \
+ --seek_nexts=$num_nexts_per_seek \
+ --reverse_iterator=$reverse_arg \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} ${full_name}.t${num_threads} seekrandom
+}
+
+function run_randomtransaction {
+ echo "..."
+ cmd="./db_bench $params_r --benchmarks=randomtransaction \
+ --num=$num_keys \
+ --transaction_db \
+ --threads=5 \
+ --transaction_sets=5 \
+ 2>&1 | tee $output_dir/benchmark_randomtransaction.log"
+ echo $cmd | tee $output_dir/benchmark_rangescanwhilewriting.log
+ eval $cmd
+}
+
+function now() {
+ echo `date +"%s"`
+}
+
+report="$output_dir/report.txt"
+schedule="$output_dir/schedule.txt"
+
+echo "===== Benchmark ====="
+
+# Run!!!
+IFS=',' read -a jobs <<< $1
+# shellcheck disable=SC2068
+for job in ${jobs[@]}; do
+
+ if [ $job != debug ]; then
+ echo "Start $job at `date`" | tee -a $schedule
+ fi
+
+ start=$(now)
+ if [ $job = bulkload ]; then
+ run_bulkload
+ elif [ $job = fillseq_disable_wal ]; then
+ run_fillseq 1
+ elif [ $job = fillseq_enable_wal ]; then
+ run_fillseq 0
+ elif [ $job = overwrite ]; then
+ syncval="0"
+ params_w="$params_w \
+ --writes=125000000 \
+ --subcompactions=4 \
+ --soft_pending_compaction_bytes_limit=$((1 * T)) \
+ --hard_pending_compaction_bytes_limit=$((4 * T)) "
+ run_change overwrite
+ elif [ $job = updaterandom ]; then
+ run_change updaterandom
+ elif [ $job = mergerandom ]; then
+ run_change mergerandom
+ elif [ $job = filluniquerandom ]; then
+ run_filluniquerandom
+ elif [ $job = readrandom ]; then
+ run_readrandom
+ elif [ $job = fwdrange ]; then
+ run_range $job false
+ elif [ $job = revrange ]; then
+ run_range $job true
+ elif [ $job = readwhilewriting ]; then
+ run_readwhile writing
+ elif [ $job = readwhilemerging ]; then
+ run_readwhile merging
+ elif [ $job = fwdrangewhilewriting ]; then
+ run_rangewhile writing $job false
+ elif [ $job = revrangewhilewriting ]; then
+ run_rangewhile writing $job true
+ elif [ $job = fwdrangewhilemerging ]; then
+ run_rangewhile merging $job false
+ elif [ $job = revrangewhilemerging ]; then
+ run_rangewhile merging $job true
+ elif [ $job = randomtransaction ]; then
+ run_randomtransaction
+ elif [ $job = universal_compaction ]; then
+ run_univ_compaction
+ elif [ $job = debug ]; then
+ num_keys=1000; # debug
+ echo "Setting num_keys to $num_keys"
+ else
+ echo "unknown job $job"
+ exit
+ fi
+ end=$(now)
+
+ if [ $job != debug ]; then
+ echo "Complete $job in $((end-start)) seconds" | tee -a $schedule
+ fi
+
+ echo -e "ops/sec\tmb/sec\tSize-GB\tL0_GB\tSum_GB\tW-Amp\tW-MB/s\tusec/op\tp50\tp75\tp99\tp99.9\tp99.99\tUptime\tStall-time\tStall%\tTest"
+ tail -1 $output_dir/report.txt
+
+done
diff --git a/storage/rocksdb/rocksdb/tools/benchmark_leveldb.sh b/storage/rocksdb/rocksdb/tools/benchmark_leveldb.sh
new file mode 100755
index 00000000..069b53a9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/benchmark_leveldb.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# REQUIRE: db_bench binary exists in the current directory
+#
+# This should be used with the LevelDB fork listed here to use additional test options.
+# For more details on the changes see the blog post listed below.
+# https://github.com/mdcallag/leveldb-1
+# http://smalldatum.blogspot.com/2015/04/comparing-leveldb-and-rocksdb-take-2.html
+
+if [ $# -ne 1 ]; then
+ echo -n "./benchmark.sh [fillseq/overwrite/readrandom/readwhilewriting]"
+ exit 0
+fi
+
+# size constants
+K=1024
+M=$((1024 * K))
+G=$((1024 * M))
+
+if [ -z $DB_DIR ]; then
+ echo "DB_DIR is not defined"
+ exit 0
+fi
+
+output_dir=${OUTPUT_DIR:-/tmp/}
+if [ ! -d $output_dir ]; then
+ mkdir -p $output_dir
+fi
+
+# all multithreaded tests run with sync=1 unless
+# $DB_BENCH_NO_SYNC is defined
+syncval="1"
+if [ ! -z $DB_BENCH_NO_SYNC ]; then
+ echo "Turning sync off for all multithreaded tests"
+ syncval="0";
+fi
+
+num_threads=${NUM_THREADS:-16}
+# Only for *whilewriting, *whilemerging
+writes_per_second=${WRITES_PER_SECOND:-$((10 * K))}
+cache_size=${CACHE_SIZE:-$((1 * G))}
+
+num_keys=${NUM_KEYS:-$((1 * G))}
+key_size=20
+value_size=${VALUE_SIZE:-400}
+block_size=${BLOCK_SIZE:-4096}
+
+const_params="
+ --db=$DB_DIR \
+ \
+ --num=$num_keys \
+ --value_size=$value_size \
+ --cache_size=$cache_size \
+ --compression_ratio=0.5 \
+ \
+ --write_buffer_size=$((2 * M)) \
+ \
+ --histogram=1 \
+ \
+ --bloom_bits=10 \
+ --open_files=$((20 * K))"
+
+params_w="$const_params "
+
+function summarize_result {
+ test_out=$1
+ test_name=$2
+ bench_name=$3
+ nthr=$4
+
+ usecs_op=$( grep ^${bench_name} $test_out | awk '{ printf "%.1f", $3 }' )
+ mb_sec=$( grep ^${bench_name} $test_out | awk '{ printf "%.1f", $5 }' )
+ ops=$( grep "^Count:" $test_out | awk '{ print $2 }' )
+ ops_sec=$( echo "scale=0; (1000000.0 * $nthr) / $usecs_op" | bc )
+ avg=$( grep "^Count:" $test_out | awk '{ printf "%.1f", $4 }' )
+ p50=$( grep "^Min:" $test_out | awk '{ printf "%.1f", $4 }' )
+ echo -e "$ops_sec\t$mb_sec\t$usecs_op\t$avg\t$p50\t$test_name" \
+ >> $output_dir/report.txt
+}
+
+function run_fillseq {
+ # This runs with a vector memtable and the WAL disabled to load faster. It is still crash safe and the
+ # client can discover where to restart a load after a crash. I think this is a good way to load.
+ echo "Loading $num_keys keys sequentially"
+ cmd="./db_bench --benchmarks=fillseq \
+ --use_existing_db=0 \
+ --sync=0 \
+ $params_w \
+ --threads=1 \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/benchmark_fillseq.v${value_size}.log"
+ echo $cmd | tee $output_dir/benchmark_fillseq.v${value_size}.log
+ eval $cmd
+ summarize_result $output_dir/benchmark_fillseq.v${value_size}.log fillseq.v${value_size} fillseq 1
+}
+
+function run_change {
+ operation=$1
+ echo "Do $num_keys random $operation"
+ out_name="benchmark_${operation}.t${num_threads}.s${syncval}.log"
+ cmd="./db_bench --benchmarks=$operation \
+ --use_existing_db=1 \
+ --sync=$syncval \
+ $params_w \
+ --threads=$num_threads \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} ${operation}.t${num_threads}.s${syncval} $operation $num_threads
+}
+
+function run_readrandom {
+ echo "Reading $num_keys random keys"
+ out_name="benchmark_readrandom.t${num_threads}.log"
+ cmd="./db_bench --benchmarks=readrandom \
+ --use_existing_db=1 \
+ $params_w \
+ --threads=$num_threads \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} readrandom.t${num_threads} readrandom $num_threads
+}
+
+function run_readwhile {
+ operation=$1
+ echo "Reading $num_keys random keys while $operation"
+ out_name="benchmark_readwhile${operation}.t${num_threads}.log"
+ cmd="./db_bench --benchmarks=readwhile${operation} \
+ --use_existing_db=1 \
+ --sync=$syncval \
+ $params_w \
+ --threads=$num_threads \
+ --writes_per_second=$writes_per_second \
+ --seed=$( date +%s ) \
+ 2>&1 | tee -a $output_dir/${out_name}"
+ echo $cmd | tee $output_dir/${out_name}
+ eval $cmd
+ summarize_result $output_dir/${out_name} readwhile${operation}.t${num_threads} readwhile${operation} $num_threads
+}
+
+function now() {
+ echo `date +"%s"`
+}
+
+report="$output_dir/report.txt"
+schedule="$output_dir/schedule.txt"
+
+echo "===== Benchmark ====="
+
+# Run!!!
+IFS=',' read -a jobs <<< $1
+# shellcheck disable=SC2068
+for job in ${jobs[@]}; do
+
+ if [ $job != debug ]; then
+ echo "Start $job at `date`" | tee -a $schedule
+ fi
+
+ start=$(now)
+ if [ $job = fillseq ]; then
+ run_fillseq
+ elif [ $job = overwrite ]; then
+ run_change overwrite
+ elif [ $job = readrandom ]; then
+ run_readrandom
+ elif [ $job = readwhilewriting ]; then
+ run_readwhile writing
+ elif [ $job = debug ]; then
+ num_keys=1000; # debug
+ echo "Setting num_keys to $num_keys"
+ else
+ echo "unknown job $job"
+ exit
+ fi
+ end=$(now)
+
+ if [ $job != debug ]; then
+ echo "Complete $job in $((end-start)) seconds" | tee -a $schedule
+ fi
+
+ echo -e "ops/sec\tmb/sec\tusec/op\tavg\tp50\tTest"
+ tail -1 $output_dir/report.txt
+
+done
diff --git a/storage/rocksdb/rocksdb/tools/blob_dump.cc b/storage/rocksdb/rocksdb/tools/blob_dump.cc
new file mode 100644
index 00000000..2ae73ecf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/blob_dump.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include <getopt.h>
+#include <cstdio>
+#include <string>
+#include <unordered_map>
+
+#include "utilities/blob_db/blob_dump_tool.h"
+
+using namespace ROCKSDB_NAMESPACE;
+using namespace ROCKSDB_NAMESPACE::blob_db;
+
+int main(int argc, char** argv) {
+ using DisplayType = BlobDumpTool::DisplayType;
+ const std::unordered_map<std::string, DisplayType> display_types = {
+ {"none", DisplayType::kNone},
+ {"raw", DisplayType::kRaw},
+ {"hex", DisplayType::kHex},
+ {"detail", DisplayType::kDetail},
+ };
+ const struct option options[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"file", required_argument, nullptr, 'f'},
+ {"show_key", optional_argument, nullptr, 'k'},
+ {"show_blob", optional_argument, nullptr, 'b'},
+ {"show_uncompressed_blob", optional_argument, nullptr, 'r'},
+ {"show_summary", optional_argument, nullptr, 's'},
+ };
+ DisplayType show_key = DisplayType::kRaw;
+ DisplayType show_blob = DisplayType::kNone;
+ DisplayType show_uncompressed_blob = DisplayType::kNone;
+ bool show_summary = false;
+ std::string file;
+ while (true) {
+ int c = getopt_long(argc, argv, "hk::b::f:", options, nullptr);
+ if (c < 0) {
+ break;
+ }
+ std::string arg_str(optarg ? optarg : "");
+ switch (c) {
+ case 'h':
+ fprintf(stdout,
+ "Usage: blob_dump --file=filename "
+ "[--show_key[=none|raw|hex|detail]] "
+ "[--show_blob[=none|raw|hex|detail]] "
+ "[--show_uncompressed_blob[=none|raw|hex|detail]] "
+ "[--show_summary]\n");
+ return 0;
+ case 'f':
+ file = optarg;
+ break;
+ case 'k':
+ if (optarg) {
+ if (display_types.count(arg_str) == 0) {
+ fprintf(stderr, "Unrecognized key display type.\n");
+ return -1;
+ }
+ show_key = display_types.at(arg_str);
+ }
+ break;
+ case 'b':
+ if (optarg) {
+ if (display_types.count(arg_str) == 0) {
+ fprintf(stderr, "Unrecognized blob display type.\n");
+ return -1;
+ }
+ show_blob = display_types.at(arg_str);
+ } else {
+ show_blob = DisplayType::kHex;
+ }
+ break;
+ case 'r':
+ if (optarg) {
+ if (display_types.count(arg_str) == 0) {
+ fprintf(stderr, "Unrecognized blob display type.\n");
+ return -1;
+ }
+ show_uncompressed_blob = display_types.at(arg_str);
+ } else {
+ show_uncompressed_blob = DisplayType::kHex;
+ }
+ break;
+ case 's':
+ show_summary = true;
+ break;
+ default:
+ fprintf(stderr, "Unrecognized option.\n");
+ return -1;
+ }
+ }
+ BlobDumpTool tool;
+ Status s =
+ tool.Run(file, show_key, show_blob, show_uncompressed_blob, show_summary);
+ if (!s.ok()) {
+ fprintf(stderr, "Failed: %s\n", s.ToString().c_str());
+ return -1;
+ }
+ return 0;
+}
+#else
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return -1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/__init__.py b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/__init__.py
new file mode 100644
index 00000000..8dbe96a7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.py b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.py
new file mode 100644
index 00000000..67307df5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.py
@@ -0,0 +1,2000 @@
+#!/usr/bin/env python3
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+import gc
+import heapq
+import random
+import sys
+import time
+from collections import OrderedDict
+from os import path
+
+import numpy as np
+
+
+kSampleSize = 64 # The sample size used when performing eviction.
+kMicrosInSecond = 1000000
+kSecondsInMinute = 60
+kSecondsInHour = 3600
+
+
+class TraceRecord:
+ """
+ A trace record represents a block access.
+ It holds the same struct as BlockCacheTraceRecord in
+ trace_replay/block_cache_tracer.h
+ """
+
+ def __init__(
+ self,
+ access_time,
+ block_id,
+ block_type,
+ block_size,
+ cf_id,
+ cf_name,
+ level,
+ fd,
+ caller,
+ no_insert,
+ get_id,
+ key_id,
+ kv_size,
+ is_hit,
+ referenced_key_exist_in_block,
+ num_keys_in_block,
+ table_id,
+ seq_number,
+ block_key_size,
+ key_size,
+ block_offset_in_file,
+ next_access_seq_no,
+ ):
+ self.access_time = access_time
+ self.block_id = block_id
+ self.block_type = block_type
+ self.block_size = block_size + block_key_size
+ self.cf_id = cf_id
+ self.cf_name = cf_name
+ self.level = level
+ self.fd = fd
+ self.caller = caller
+ if no_insert == 1:
+ self.no_insert = True
+ else:
+ self.no_insert = False
+ self.get_id = get_id
+ self.key_id = key_id
+ self.kv_size = kv_size
+ if is_hit == 1:
+ self.is_hit = True
+ else:
+ self.is_hit = False
+ if referenced_key_exist_in_block == 1:
+ self.referenced_key_exist_in_block = True
+ else:
+ self.referenced_key_exist_in_block = False
+ self.num_keys_in_block = num_keys_in_block
+ self.table_id = table_id
+ self.seq_number = seq_number
+ self.block_key_size = block_key_size
+ self.key_size = key_size
+ self.block_offset_in_file = block_offset_in_file
+ self.next_access_seq_no = next_access_seq_no
+
+
+class CacheEntry:
+ """A cache entry stored in the cache."""
+
+ def __init__(
+ self,
+ value_size,
+ cf_id,
+ level,
+ block_type,
+ table_id,
+ access_number,
+ time_s,
+ num_hits=0,
+ ):
+ self.value_size = value_size
+ self.last_access_number = access_number
+ self.num_hits = num_hits
+ self.cf_id = 0
+ self.level = level
+ self.block_type = block_type
+ self.last_access_time = time_s
+ self.insertion_time = time_s
+ self.table_id = table_id
+
+ def __repr__(self):
+ """Debug string."""
+ return "(s={},last={},hits={},cf={},l={},bt={})\n".format(
+ self.value_size,
+ self.last_access_number,
+ self.num_hits,
+ self.cf_id,
+ self.level,
+ self.block_type,
+ )
+
+ def cost_class(self, cost_class_label):
+ if cost_class_label == "table_bt":
+ return "{}-{}".format(self.table_id, self.block_type)
+ elif cost_class_label == "table":
+ return "{}".format(self.table_id)
+ elif cost_class_label == "bt":
+ return "{}".format(self.block_type)
+ elif cost_class_label == "cf":
+ return "{}".format(self.cf_id)
+ elif cost_class_label == "cf_bt":
+ return "{}-{}".format(self.cf_id, self.block_type)
+ elif cost_class_label == "table_level_bt":
+ return "{}-{}-{}".format(self.table_id, self.level, self.block_type)
+ assert False, "Unknown cost class label {}".format(cost_class_label)
+ return None
+
+
+class HashEntry:
+ """A hash entry stored in a hash table."""
+
+ def __init__(self, key, hash, value):
+ self.key = key
+ self.hash = hash
+ self.value = value
+
+ def __repr__(self):
+ return "k={},h={},v=[{}]".format(self.key, self.hash, self.value)
+
+
+class HashTable:
+ """
+ A custom implementation of hash table to support fast random sampling.
+ It is closed hashing and uses chaining to resolve hash conflicts.
+ It grows/shrinks the hash table upon insertion/deletion to support
+ fast lookups and random samplings.
+ """
+
+ def __init__(self):
+ self.initial_size = 32
+ self.table = [None] * self.initial_size
+ self.elements = 0
+
+ def random_sample(self, sample_size):
+ """Randomly sample 'sample_size' hash entries from the table."""
+ samples = []
+ index = random.randint(0, len(self.table) - 1)
+ pos = index
+ # Starting from index, adding hash entries to the sample list until
+ # sample_size is met or we ran out of entries.
+ while True:
+ if self.table[pos] is not None:
+ for i in range(len(self.table[pos])):
+ if self.table[pos][i] is None:
+ continue
+ samples.append(self.table[pos][i])
+ if len(samples) == sample_size:
+ break
+ pos += 1
+ pos = pos % len(self.table)
+ if pos == index or len(samples) == sample_size:
+ break
+ assert len(samples) <= sample_size
+ return samples
+
+ def __repr__(self):
+ all_entries = []
+ for i in range(len(self.table)):
+ if self.table[i] is None:
+ continue
+ for j in range(len(self.table[i])):
+ if self.table[i][j] is not None:
+ all_entries.append(self.table[i][j])
+ return "{}".format(all_entries)
+
+ def values(self):
+ all_values = []
+ for i in range(len(self.table)):
+ if self.table[i] is None:
+ continue
+ for j in range(len(self.table[i])):
+ if self.table[i][j] is not None:
+ all_values.append(self.table[i][j].value)
+ return all_values
+
+ def __len__(self):
+ return self.elements
+
+ def insert(self, key, hash, value):
+ """
+ Insert a hash entry in the table. Replace the old entry if it already
+ exists.
+ """
+ self.grow()
+ inserted = False
+ index = hash % len(self.table)
+ if self.table[index] is None:
+ self.table[index] = []
+ # Search for the entry first.
+ for i in range(len(self.table[index])):
+ if self.table[index][i] is None:
+ continue
+ if self.table[index][i].hash == hash and self.table[index][i].key == key:
+ # The entry already exists in the table.
+ self.table[index][i] = HashEntry(key, hash, value)
+ return
+
+ # Find an empty slot.
+ for i in range(len(self.table[index])):
+ if self.table[index][i] is None:
+ self.table[index][i] = HashEntry(key, hash, value)
+ inserted = True
+ break
+ if not inserted:
+ self.table[index].append(HashEntry(key, hash, value))
+ self.elements += 1
+
+ def resize(self, new_size):
+ if new_size == len(self.table):
+ return
+ if new_size < self.initial_size:
+ return
+ if self.elements < 100:
+ return
+ new_table = [None] * new_size
+ # Copy 'self.table' to new_table.
+ for i in range(len(self.table)):
+ entries = self.table[i]
+ if entries is None:
+ continue
+ for j in range(len(entries)):
+ if entries[j] is None:
+ continue
+ index = entries[j].hash % new_size
+ if new_table[index] is None:
+ new_table[index] = []
+ new_table[index].append(entries[j])
+ self.table = new_table
+ del new_table
+ # Manually call python gc here to free the memory as 'self.table'
+ # might be very large.
+ gc.collect()
+
+ def grow(self):
+ if self.elements < 4 * len(self.table):
+ return
+ new_size = int(len(self.table) * 1.5)
+ self.resize(new_size)
+
+ def delete(self, key, hash):
+ index = hash % len(self.table)
+ deleted = False
+ deleted_entry = None
+ if self.table[index] is None:
+ return
+ for i in range(len(self.table[index])):
+ if (
+ self.table[index][i] is not None
+ and self.table[index][i].hash == hash
+ and self.table[index][i].key == key
+ ):
+ deleted_entry = self.table[index][i]
+ self.table[index][i] = None
+ self.elements -= 1
+ deleted = True
+ break
+ if deleted:
+ self.shrink()
+ return deleted_entry
+
+ def shrink(self):
+ if self.elements * 2 >= len(self.table):
+ return
+ new_size = int(len(self.table) * 0.7)
+ self.resize(new_size)
+
+ def lookup(self, key, hash):
+ index = hash % len(self.table)
+ if self.table[index] is None:
+ return None
+ for i in range(len(self.table[index])):
+ if (
+ self.table[index][i] is not None
+ and self.table[index][i].hash == hash
+ and self.table[index][i].key == key
+ ):
+ return self.table[index][i].value
+ return None
+
+
+class MissRatioStats:
+ def __init__(self, time_unit):
+ self.num_misses = 0
+ self.num_accesses = 0
+ self.time_unit = time_unit
+ self.time_misses = {}
+ self.time_miss_bytes = {}
+ self.time_accesses = {}
+
+ def update_metrics(self, access_time, is_hit, miss_bytes):
+ access_time /= kMicrosInSecond * self.time_unit
+ self.num_accesses += 1
+ if access_time not in self.time_accesses:
+ self.time_accesses[access_time] = 0
+ self.time_accesses[access_time] += 1
+ if not is_hit:
+ self.num_misses += 1
+ if access_time not in self.time_misses:
+ self.time_misses[access_time] = 0
+ self.time_miss_bytes[access_time] = 0
+ self.time_misses[access_time] += 1
+ self.time_miss_bytes[access_time] += miss_bytes
+
+ def reset_counter(self):
+ self.num_misses = 0
+ self.num_accesses = 0
+ self.time_miss_bytes.clear()
+ self.time_misses.clear()
+ self.time_accesses.clear()
+
+ def compute_miss_bytes(self):
+ miss_bytes = []
+ for at in self.time_miss_bytes:
+ miss_bytes.append(self.time_miss_bytes[at])
+ miss_bytes = sorted(miss_bytes)
+ avg_miss_bytes = 0
+ p95_miss_bytes = 0
+ for i in range(len(miss_bytes)):
+ avg_miss_bytes += float(miss_bytes[i]) / float(len(miss_bytes))
+
+ p95_index = min(int(0.95 * float(len(miss_bytes))), len(miss_bytes) - 1)
+ p95_miss_bytes = miss_bytes[p95_index]
+ return avg_miss_bytes, p95_miss_bytes
+
+ def miss_ratio(self):
+ return float(self.num_misses) * 100.0 / float(self.num_accesses)
+
+ def write_miss_timeline(
+ self, cache_type, cache_size, target_cf_name, result_dir, start, end
+ ):
+ start /= kMicrosInSecond * self.time_unit
+ end /= kMicrosInSecond * self.time_unit
+ header_file_path = "{}/header-ml-miss-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ if not path.exists(header_file_path):
+ with open(header_file_path, "w+") as header_file:
+ header = "time"
+ for trace_time in range(start, end):
+ header += ",{}".format(trace_time)
+ header_file.write(header + "\n")
+ file_path = "{}/data-ml-miss-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ with open(file_path, "w+") as file:
+ row = "{}".format(cache_type)
+ for trace_time in range(start, end):
+ row += ",{}".format(self.time_misses.get(trace_time, 0))
+ file.write(row + "\n")
+
+ def write_miss_ratio_timeline(
+ self, cache_type, cache_size, target_cf_name, result_dir, start, end
+ ):
+ start /= kMicrosInSecond * self.time_unit
+ end /= kMicrosInSecond * self.time_unit
+ header_file_path = "{}/header-ml-miss-ratio-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ if not path.exists(header_file_path):
+ with open(header_file_path, "w+") as header_file:
+ header = "time"
+ for trace_time in range(start, end):
+ header += ",{}".format(trace_time)
+ header_file.write(header + "\n")
+ file_path = "{}/data-ml-miss-ratio-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ with open(file_path, "w+") as file:
+ row = "{}".format(cache_type)
+ for trace_time in range(start, end):
+ naccesses = self.time_accesses.get(trace_time, 0)
+ miss_ratio = 0
+ if naccesses > 0:
+ miss_ratio = float(
+ self.time_misses.get(trace_time, 0) * 100.0
+ ) / float(naccesses)
+ row += ",{0:.2f}".format(miss_ratio)
+ file.write(row + "\n")
+
+
+class PolicyStats:
+ def __init__(self, time_unit, policies):
+ self.time_selected_polices = {}
+ self.time_accesses = {}
+ self.policy_names = {}
+ self.time_unit = time_unit
+ for i in range(len(policies)):
+ self.policy_names[i] = policies[i].policy_name()
+
+ def update_metrics(self, access_time, selected_policy):
+ access_time /= kMicrosInSecond * self.time_unit
+ if access_time not in self.time_accesses:
+ self.time_accesses[access_time] = 0
+ self.time_accesses[access_time] += 1
+ if access_time not in self.time_selected_polices:
+ self.time_selected_polices[access_time] = {}
+ policy_name = self.policy_names[selected_policy]
+ if policy_name not in self.time_selected_polices[access_time]:
+ self.time_selected_polices[access_time][policy_name] = 0
+ self.time_selected_polices[access_time][policy_name] += 1
+
+ def write_policy_timeline(
+ self, cache_type, cache_size, target_cf_name, result_dir, start, end
+ ):
+ start /= kMicrosInSecond * self.time_unit
+ end /= kMicrosInSecond * self.time_unit
+ header_file_path = "{}/header-ml-policy-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ if not path.exists(header_file_path):
+ with open(header_file_path, "w+") as header_file:
+ header = "time"
+ for trace_time in range(start, end):
+ header += ",{}".format(trace_time)
+ header_file.write(header + "\n")
+ file_path = "{}/data-ml-policy-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ with open(file_path, "w+") as file:
+ for policy in self.policy_names:
+ policy_name = self.policy_names[policy]
+ row = "{}-{}".format(cache_type, policy_name)
+ for trace_time in range(start, end):
+ row += ",{}".format(
+ self.time_selected_polices.get(trace_time, {}).get(
+ policy_name, 0
+ )
+ )
+ file.write(row + "\n")
+
+ def write_policy_ratio_timeline(
+ self, cache_type, cache_size, target_cf_name, file_path, start, end
+ ):
+ start /= kMicrosInSecond * self.time_unit
+ end /= kMicrosInSecond * self.time_unit
+ header_file_path = "{}/header-ml-policy-ratio-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ if not path.exists(header_file_path):
+ with open(header_file_path, "w+") as header_file:
+ header = "time"
+ for trace_time in range(start, end):
+ header += ",{}".format(trace_time)
+ header_file.write(header + "\n")
+ file_path = "{}/data-ml-policy-ratio-timeline-{}-{}-{}-{}".format(
+ result_dir, self.time_unit, cache_type, cache_size, target_cf_name
+ )
+ with open(file_path, "w+") as file:
+ for policy in self.policy_names:
+ policy_name = self.policy_names[policy]
+ row = "{}-{}".format(cache_type, policy_name)
+ for trace_time in range(start, end):
+ naccesses = self.time_accesses.get(trace_time, 0)
+ ratio = 0
+ if naccesses > 0:
+ ratio = float(
+ self.time_selected_polices.get(trace_time, {}).get(
+ policy_name, 0
+ )
+ * 100.0
+ ) / float(naccesses)
+ row += ",{0:.2f}".format(ratio)
+ file.write(row + "\n")
+
+
+class Policy(object):
+ """
+ A policy maintains a set of evicted keys. It returns a reward of one to
+ itself if it has not evicted a missing key. Otherwise, it gives itself 0
+ reward.
+ """
+
+ def __init__(self):
+ self.evicted_keys = {}
+
+ def evict(self, key, max_size):
+ self.evicted_keys[key] = 0
+
+ def delete(self, key):
+ self.evicted_keys.pop(key, None)
+
+ def prioritize_samples(self, samples, auxilliary_info):
+ raise NotImplementedError
+
+ def policy_name(self):
+ raise NotImplementedError
+
+ def generate_reward(self, key):
+ if key in self.evicted_keys:
+ return 0
+ return 1
+
+
+class LRUPolicy(Policy):
+ def prioritize_samples(self, samples, auxilliary_info):
+ return sorted(
+ samples,
+ cmp=lambda e1, e2: e1.value.last_access_number
+ - e2.value.last_access_number,
+ )
+
+ def policy_name(self):
+ return "lru"
+
+
+class MRUPolicy(Policy):
+ def prioritize_samples(self, samples, auxilliary_info):
+ return sorted(
+ samples,
+ cmp=lambda e1, e2: e2.value.last_access_number
+ - e1.value.last_access_number,
+ )
+
+ def policy_name(self):
+ return "mru"
+
+
+class LFUPolicy(Policy):
+ def prioritize_samples(self, samples, auxilliary_info):
+ return sorted(samples, cmp=lambda e1, e2: e1.value.num_hits - e2.value.num_hits)
+
+ def policy_name(self):
+ return "lfu"
+
+
+class HyperbolicPolicy(Policy):
+ """
+ An implementation of Hyperbolic caching.
+
+ Aaron Blankstein, Siddhartha Sen, and Michael J. Freedman. 2017.
+ Hyperbolic caching: flexible caching for web applications. In Proceedings
+ of the 2017 USENIX Conference on Usenix Annual Technical Conference
+ (USENIX ATC '17). USENIX Association, Berkeley, CA, USA, 499-511.
+ """
+
+ def compare(self, e1, e2, now):
+ e1_duration = max(0, (now - e1.value.insertion_time) / kMicrosInSecond) * float(
+ e1.value.value_size
+ )
+ e2_duration = max(0, (now - e2.value.insertion_time) / kMicrosInSecond) * float(
+ e2.value.value_size
+ )
+ if e1_duration == e2_duration:
+ return e1.value.num_hits - e2.value.num_hits
+ if e1_duration == 0:
+ return 1
+ if e2_duration == 0:
+ return 1
+ diff = (float(e1.value.num_hits) / (float(e1_duration))) - (
+ float(e2.value.num_hits) / float(e2_duration)
+ )
+ if diff == 0:
+ return 0
+ elif diff > 0:
+ return 1
+ else:
+ return -1
+
+ def prioritize_samples(self, samples, auxilliary_info):
+ assert len(auxilliary_info) == 3
+ now = auxilliary_info[0]
+ return sorted(samples, cmp=lambda e1, e2: self.compare(e1, e2, now))
+
+ def policy_name(self):
+ return "hb"
+
+
+class CostClassPolicy(Policy):
+ """
+ We calculate the hit density of a cost class as
+ number of hits / total size in cache * average duration in the cache.
+
+ An entry has a higher priority if its class's hit density is higher.
+ """
+
+ def compare(self, e1, e2, now, cost_classes, cost_class_label):
+ e1_class = e1.value.cost_class(cost_class_label)
+ e2_class = e2.value.cost_class(cost_class_label)
+
+ assert e1_class in cost_classes
+ assert e2_class in cost_classes
+
+ e1_entry = cost_classes[e1_class]
+ e2_entry = cost_classes[e2_class]
+ e1_density = e1_entry.density(now)
+ e2_density = e2_entry.density(now)
+ e1_hits = cost_classes[e1_class].hits
+ e2_hits = cost_classes[e2_class].hits
+
+ if e1_density == e2_density:
+ return e1_hits - e2_hits
+
+ if e1_entry.num_entries_in_cache == 0:
+ return -1
+ if e2_entry.num_entries_in_cache == 0:
+ return 1
+
+ if e1_density == 0:
+ return 1
+ if e2_density == 0:
+ return -1
+ diff = (float(e1_hits) / float(e1_density)) - (
+ float(e2_hits) / float(e2_density)
+ )
+ if diff == 0:
+ return 0
+ elif diff > 0:
+ return 1
+ else:
+ return -1
+
+ def prioritize_samples(self, samples, auxilliary_info):
+ assert len(auxilliary_info) == 3
+ now = auxilliary_info[0]
+ cost_classes = auxilliary_info[1]
+ cost_class_label = auxilliary_info[2]
+ return sorted(
+ samples,
+ cmp=lambda e1, e2: self.compare(
+ e1, e2, now, cost_classes, cost_class_label
+ ),
+ )
+
+ def policy_name(self):
+ return "cc"
+
+
+class Cache(object):
+ """
+ This is the base class for the implementations of alternative cache
+ replacement policies.
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key):
+ self.cache_size = cache_size
+ self.used_size = 0
+ self.per_second_miss_ratio_stats = MissRatioStats(1)
+ self.miss_ratio_stats = MissRatioStats(kSecondsInMinute)
+ self.per_hour_miss_ratio_stats = MissRatioStats(kSecondsInHour)
+ # 0: disabled. 1: enabled. Insert both row and the refereneced data block.
+ # 2: enabled. Insert only the row but NOT the referenced data block.
+ self.enable_cache_row_key = enable_cache_row_key
+ self.get_id_row_key_map = {}
+ self.max_seen_get_id = 0
+ self.retain_get_id_range = 100000
+
+ def block_key(self, trace_record):
+ return "b{}".format(trace_record.block_id)
+
+ def row_key(self, trace_record):
+ return "g{}-{}".format(trace_record.fd, trace_record.key_id)
+
+ def _lookup(self, trace_record, key, hash):
+ """
+ Look up the key in the cache.
+ Returns true upon a cache hit, false otherwise.
+ """
+ raise NotImplementedError
+
+ def _evict(self, trace_record, key, hash, value_size):
+ """
+ Evict entries in the cache until there is enough room to insert the new
+ entry with 'value_size'.
+ """
+ raise NotImplementedError
+
+ def _insert(self, trace_record, key, hash, value_size):
+ """
+ Insert the new entry into the cache.
+ """
+ raise NotImplementedError
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ """
+ A custom admission policy to decide whether we should admit the new
+ entry upon a cache miss.
+ Returns true if the new entry should be admitted, false otherwise.
+ """
+ raise NotImplementedError
+
+ def cache_name(self):
+ """
+ The name of the replacement policy.
+ """
+ raise NotImplementedError
+
+ def is_ml_cache(self):
+ return False
+
+ def _update_stats(self, access_time, is_hit, miss_bytes):
+ self.per_second_miss_ratio_stats.update_metrics(access_time, is_hit, miss_bytes)
+ self.miss_ratio_stats.update_metrics(access_time, is_hit, miss_bytes)
+ self.per_hour_miss_ratio_stats.update_metrics(access_time, is_hit, miss_bytes)
+
+ def access(self, trace_record):
+ """
+ Access a trace record. The simulator calls this function to access a
+ trace record.
+ """
+ assert self.used_size <= self.cache_size
+ if (
+ self.enable_cache_row_key > 0
+ and trace_record.caller == 1
+ and trace_record.key_id != 0
+ and trace_record.get_id != 0
+ ):
+ # This is a get request.
+ self._access_row(trace_record)
+ return
+ is_hit = self._access_kv(
+ trace_record,
+ self.block_key(trace_record),
+ trace_record.block_id,
+ trace_record.block_size,
+ trace_record.no_insert,
+ )
+ self._update_stats(
+ trace_record.access_time, is_hit=is_hit, miss_bytes=trace_record.block_size
+ )
+
+ def _access_row(self, trace_record):
+ row_key = self.row_key(trace_record)
+ self.max_seen_get_id = max(self.max_seen_get_id, trace_record.get_id)
+ self.get_id_row_key_map.pop(
+ self.max_seen_get_id - self.retain_get_id_range, None
+ )
+ if trace_record.get_id not in self.get_id_row_key_map:
+ self.get_id_row_key_map[trace_record.get_id] = {}
+ self.get_id_row_key_map[trace_record.get_id]["h"] = False
+ if self.get_id_row_key_map[trace_record.get_id]["h"]:
+ # We treat future accesses as hits since this get request
+ # completes.
+ # print("row hit 1")
+ self._update_stats(trace_record.access_time, is_hit=True, miss_bytes=0)
+ return
+ if row_key not in self.get_id_row_key_map[trace_record.get_id]:
+ # First time seen this key.
+ is_hit = self._access_kv(
+ trace_record,
+ key=row_key,
+ hash=trace_record.key_id,
+ value_size=trace_record.kv_size,
+ no_insert=False,
+ )
+ inserted = False
+ if trace_record.kv_size > 0:
+ inserted = True
+ self.get_id_row_key_map[trace_record.get_id][row_key] = inserted
+ self.get_id_row_key_map[trace_record.get_id]["h"] = is_hit
+ if self.get_id_row_key_map[trace_record.get_id]["h"]:
+ # We treat future accesses as hits since this get request
+ # completes.
+ # print("row hit 2")
+ self._update_stats(trace_record.access_time, is_hit=True, miss_bytes=0)
+ return
+ # Access its blocks.
+ no_insert = trace_record.no_insert
+ if (
+ self.enable_cache_row_key == 2
+ and trace_record.kv_size > 0
+ and trace_record.block_type == 9
+ ):
+ no_insert = True
+ is_hit = self._access_kv(
+ trace_record,
+ key=self.block_key(trace_record),
+ hash=trace_record.block_id,
+ value_size=trace_record.block_size,
+ no_insert=no_insert,
+ )
+ self._update_stats(
+ trace_record.access_time, is_hit, miss_bytes=trace_record.block_size
+ )
+ if (
+ trace_record.kv_size > 0
+ and not self.get_id_row_key_map[trace_record.get_id][row_key]
+ ):
+ # Insert the row key-value pair.
+ self._access_kv(
+ trace_record,
+ key=row_key,
+ hash=trace_record.key_id,
+ value_size=trace_record.kv_size,
+ no_insert=False,
+ )
+ # Mark as inserted.
+ self.get_id_row_key_map[trace_record.get_id][row_key] = True
+
+ def _access_kv(self, trace_record, key, hash, value_size, no_insert):
+ # Sanity checks.
+ assert self.used_size <= self.cache_size
+ if self._lookup(trace_record, key, hash):
+ # A cache hit.
+ return True
+ if no_insert or value_size <= 0:
+ return False
+ # A cache miss.
+ if value_size > self.cache_size:
+ # The block is too large to fit into the cache.
+ return False
+ self._evict(trace_record, key, hash, value_size)
+ if self._should_admit(trace_record, key, hash, value_size):
+ self._insert(trace_record, key, hash, value_size)
+ self.used_size += value_size
+ return False
+
+
+class CostClassEntry:
+ """
+ A cost class maintains aggregated statistics of cached entries in a class.
+ For example, we may define block type as a class. Then, cached blocks of the
+ same type will share one cost class entry.
+ """
+
+ def __init__(self):
+ self.hits = 0
+ self.num_entries_in_cache = 0
+ self.size_in_cache = 0
+ self.sum_insertion_times = 0
+ self.sum_last_access_time = 0
+
+ def insert(self, trace_record, key, value_size):
+ self.size_in_cache += value_size
+ self.num_entries_in_cache += 1
+ self.sum_insertion_times += trace_record.access_time / kMicrosInSecond
+ self.sum_last_access_time += trace_record.access_time / kMicrosInSecond
+
+ def remove(self, insertion_time, last_access_time, key, value_size, num_hits):
+ self.hits -= num_hits
+ self.num_entries_in_cache -= 1
+ self.sum_insertion_times -= insertion_time / kMicrosInSecond
+ self.size_in_cache -= value_size
+ self.sum_last_access_time -= last_access_time / kMicrosInSecond
+
+ def update_on_hit(self, trace_record, last_access_time):
+ self.hits += 1
+ self.sum_last_access_time -= last_access_time / kMicrosInSecond
+ self.sum_last_access_time += trace_record.access_time / kMicrosInSecond
+
+ def avg_lifetime_in_cache(self, now):
+ avg_insertion_time = self.sum_insertion_times / self.num_entries_in_cache
+ return now / kMicrosInSecond - avg_insertion_time
+
+ def avg_last_access_time(self):
+ if self.num_entries_in_cache == 0:
+ return 0
+ return float(self.sum_last_access_time) / float(self.num_entries_in_cache)
+
+ def avg_size(self):
+ if self.num_entries_in_cache == 0:
+ return 0
+ return float(self.sum_last_access_time) / float(self.num_entries_in_cache)
+
+ def density(self, now):
+ avg_insertion_time = self.sum_insertion_times / self.num_entries_in_cache
+ in_cache_duration = now / kMicrosInSecond - avg_insertion_time
+ return self.size_in_cache * in_cache_duration
+
+
+class MLCache(Cache):
+ """
+ MLCache is the base class for implementations of alternative replacement
+ policies using reinforcement learning.
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key, policies, cost_class_label):
+ super(MLCache, self).__init__(cache_size, enable_cache_row_key)
+ self.table = HashTable()
+ self.policy_stats = PolicyStats(kSecondsInMinute, policies)
+ self.per_hour_policy_stats = PolicyStats(kSecondsInHour, policies)
+ self.policies = policies
+ self.cost_classes = {}
+ self.cost_class_label = cost_class_label
+
+ def is_ml_cache(self):
+ return True
+
+ def _lookup(self, trace_record, key, hash):
+ value = self.table.lookup(key, hash)
+ if value is not None:
+ # Update the entry's cost class statistics.
+ if self.cost_class_label is not None:
+ cost_class = value.cost_class(self.cost_class_label)
+ assert cost_class in self.cost_classes
+ self.cost_classes[cost_class].update_on_hit(
+ trace_record, value.last_access_time
+ )
+ # Update the entry's last access time.
+ self.table.insert(
+ key,
+ hash,
+ CacheEntry(
+ value_size=value.value_size,
+ cf_id=value.cf_id,
+ level=value.level,
+ block_type=value.block_type,
+ table_id=value.table_id,
+ access_number=self.miss_ratio_stats.num_accesses,
+ time_s=trace_record.access_time,
+ num_hits=value.num_hits + 1,
+ ),
+ )
+ return True
+ return False
+
+ def _evict(self, trace_record, key, hash, value_size):
+ # Select a policy, random sample kSampleSize keys from the cache, then
+ # evict keys in the sample set until we have enough room for the new
+ # entry.
+ policy_index = self._select_policy(trace_record, key)
+ assert policy_index < len(self.policies) and policy_index >= 0
+ self.policies[policy_index].delete(key)
+ self.policy_stats.update_metrics(trace_record.access_time, policy_index)
+ self.per_hour_policy_stats.update_metrics(
+ trace_record.access_time, policy_index
+ )
+ while self.used_size + value_size > self.cache_size:
+ # Randomly sample n entries.
+ samples = self.table.random_sample(kSampleSize)
+ samples = self.policies[policy_index].prioritize_samples(
+ samples,
+ [trace_record.access_time, self.cost_classes, self.cost_class_label],
+ )
+ for hash_entry in samples:
+ assert self.table.delete(hash_entry.key, hash_entry.hash) is not None
+ self.used_size -= hash_entry.value.value_size
+ self.policies[policy_index].evict(
+ key=hash_entry.key, max_size=self.table.elements
+ )
+ # Update the entry's cost class statistics.
+ if self.cost_class_label is not None:
+ cost_class = hash_entry.value.cost_class(self.cost_class_label)
+ assert cost_class in self.cost_classes
+ self.cost_classes[cost_class].remove(
+ hash_entry.value.insertion_time,
+ hash_entry.value.last_access_time,
+ key,
+ hash_entry.value.value_size,
+ hash_entry.value.num_hits,
+ )
+ if self.used_size + value_size <= self.cache_size:
+ break
+
+ def _insert(self, trace_record, key, hash, value_size):
+ assert self.used_size + value_size <= self.cache_size
+ entry = CacheEntry(
+ value_size,
+ trace_record.cf_id,
+ trace_record.level,
+ trace_record.block_type,
+ trace_record.table_id,
+ self.miss_ratio_stats.num_accesses,
+ trace_record.access_time,
+ )
+ # Update the entry's cost class statistics.
+ if self.cost_class_label is not None:
+ cost_class = entry.cost_class(self.cost_class_label)
+ if cost_class not in self.cost_classes:
+ self.cost_classes[cost_class] = CostClassEntry()
+ self.cost_classes[cost_class].insert(trace_record, key, value_size)
+ self.table.insert(key, hash, entry)
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return True
+
+ def _select_policy(self, trace_record, key):
+ raise NotImplementedError
+
+
+class ThompsonSamplingCache(MLCache):
+ """
+ An implementation of Thompson Sampling for the Bernoulli Bandit.
+
+ Daniel J. Russo, Benjamin Van Roy, Abbas Kazerouni, Ian Osband,
+ and Zheng Wen. 2018. A Tutorial on Thompson Sampling. Found.
+ Trends Mach. Learn. 11, 1 (July 2018), 1-96.
+ DOI: https://doi.org/10.1561/2200000070
+ """
+
+ def __init__(
+ self,
+ cache_size,
+ enable_cache_row_key,
+ policies,
+ cost_class_label,
+ init_a=1,
+ init_b=1,
+ ):
+ super(ThompsonSamplingCache, self).__init__(
+ cache_size, enable_cache_row_key, policies, cost_class_label
+ )
+ self._as = {}
+ self._bs = {}
+ for _i in range(len(policies)):
+ self._as = [init_a] * len(self.policies)
+ self._bs = [init_b] * len(self.policies)
+
+ def _select_policy(self, trace_record, key):
+ if len(self.policies) == 1:
+ return 0
+ samples = [
+ np.random.beta(self._as[x], self._bs[x]) for x in range(len(self.policies))
+ ]
+ selected_policy = max(range(len(self.policies)), key=lambda x: samples[x])
+ reward = self.policies[selected_policy].generate_reward(key)
+ assert reward <= 1 and reward >= 0
+ self._as[selected_policy] += reward
+ self._bs[selected_policy] += 1 - reward
+ return selected_policy
+
+ def cache_name(self):
+ if self.enable_cache_row_key:
+ return "Hybrid ThompsonSampling with cost class {} (ts_hybrid)".format(
+ self.cost_class_label
+ )
+ return "ThompsonSampling with cost class {} (ts)".format(self.cost_class_label)
+
+
+class LinUCBCache(MLCache):
+ """
+ An implementation of LinUCB with disjoint linear models.
+
+ Lihong Li, Wei Chu, John Langford, and Robert E. Schapire. 2010.
+ A contextual-bandit approach to personalized news article recommendation.
+ In Proceedings of the 19th international conference on World wide web
+ (WWW '10). ACM, New York, NY, USA, 661-670.
+ DOI=http://dx.doi.org/10.1145/1772690.1772758
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key, policies, cost_class_label):
+ super(LinUCBCache, self).__init__(
+ cache_size, enable_cache_row_key, policies, cost_class_label
+ )
+ self.nfeatures = 4 # Block type, level, cf.
+ self.th = np.zeros((len(self.policies), self.nfeatures))
+ self.eps = 0.2
+ self.b = np.zeros_like(self.th)
+ self.A = np.zeros((len(self.policies), self.nfeatures, self.nfeatures))
+ self.A_inv = np.zeros((len(self.policies), self.nfeatures, self.nfeatures))
+ for i in range(len(self.policies)):
+ self.A[i] = np.identity(self.nfeatures)
+ self.th_hat = np.zeros_like(self.th)
+ self.p = np.zeros(len(self.policies))
+ self.alph = 0.2
+
+ def _select_policy(self, trace_record, key):
+ if len(self.policies) == 1:
+ return 0
+ x_i = np.zeros(self.nfeatures) # The current context vector
+ x_i[0] = trace_record.block_type
+ x_i[1] = trace_record.level
+ x_i[2] = trace_record.cf_id
+ p = np.zeros(len(self.policies))
+ for a in range(len(self.policies)):
+ self.th_hat[a] = self.A_inv[a].dot(self.b[a])
+ ta = x_i.dot(self.A_inv[a]).dot(x_i)
+ a_upper_ci = self.alph * np.sqrt(ta)
+ a_mean = self.th_hat[a].dot(x_i)
+ p[a] = a_mean + a_upper_ci
+ p = p + (np.random.random(len(p)) * 0.000001)
+ selected_policy = p.argmax()
+ reward = self.policies[selected_policy].generate_reward(key)
+ assert reward <= 1 and reward >= 0
+ self.A[selected_policy] += np.outer(x_i, x_i)
+ self.b[selected_policy] += reward * x_i
+ self.A_inv[selected_policy] = np.linalg.inv(self.A[selected_policy])
+ del x_i
+ return selected_policy
+
+ def cache_name(self):
+ if self.enable_cache_row_key:
+ return "Hybrid LinUCB with cost class {} (linucb_hybrid)".format(
+ self.cost_class_label
+ )
+ return "LinUCB with cost class {} (linucb)".format(self.cost_class_label)
+
+
+class OPTCacheEntry:
+ """
+ A cache entry for the OPT algorithm. The entries are sorted based on its
+ next access sequence number in reverse order, i.e., the entry which next
+ access is the furthest in the future is ordered before other entries.
+ """
+
+ def __init__(self, key, next_access_seq_no, value_size):
+ self.key = key
+ self.next_access_seq_no = next_access_seq_no
+ self.value_size = value_size
+ self.is_removed = False
+
+ def __cmp__(self, other):
+ if other.next_access_seq_no != self.next_access_seq_no:
+ return other.next_access_seq_no - self.next_access_seq_no
+ return self.value_size - other.value_size
+
+ def __repr__(self):
+ return "({} {} {} {})".format(
+ self.key, self.next_access_seq_no, self.value_size, self.is_removed
+ )
+
+
+class PQTable:
+ """
+ A hash table with a priority queue.
+ """
+
+ def __init__(self):
+ # A list of entries arranged in a heap sorted based on the entry custom
+ # implementation of __cmp__
+ self.pq = []
+ self.table = {}
+
+ def pqinsert(self, entry):
+ "Add a new key or update the priority of an existing key"
+ # Remove the entry from the table first.
+ removed_entry = self.table.pop(entry.key, None)
+ if removed_entry:
+ # Mark as removed since there is no 'remove' API in heappq.
+ # Instead, an entry in pq is removed lazily when calling pop.
+ removed_entry.is_removed = True
+ self.table[entry.key] = entry
+ heapq.heappush(self.pq, entry)
+ return removed_entry
+
+ def pqpop(self):
+ while self.pq:
+ entry = heapq.heappop(self.pq)
+ if not entry.is_removed:
+ del self.table[entry.key]
+ return entry
+ return None
+
+ def pqpeek(self):
+ while self.pq:
+ entry = self.pq[0]
+ if not entry.is_removed:
+ return entry
+ heapq.heappop(self.pq)
+ return
+
+ def __contains__(self, k):
+ return k in self.table
+
+ def __getitem__(self, k):
+ return self.table[k]
+
+ def __len__(self):
+ return len(self.table)
+
+ def values(self):
+ return self.table.values()
+
+
+class OPTCache(Cache):
+ """
+ An implementation of the Belady MIN algorithm. OPTCache evicts an entry
+ in the cache whose next access occurs furthest in the future.
+
+ Note that Belady MIN algorithm is optimal assuming all blocks having the
+ same size and a missing entry will be inserted in the cache.
+ These are NOT true for the block cache trace since blocks have different
+ sizes and we may not insert a block into the cache upon a cache miss.
+ However, it is still useful to serve as a "theoretical upper bound" on the
+ lowest miss ratio we can achieve given a cache size.
+
+ L. A. Belady. 1966. A Study of Replacement Algorithms for a
+ Virtual-storage Computer. IBM Syst. J. 5, 2 (June 1966), 78-101.
+ DOI=http://dx.doi.org/10.1147/sj.52.0078
+ """
+
+ def __init__(self, cache_size):
+ super(OPTCache, self).__init__(cache_size, enable_cache_row_key=0)
+ self.table = PQTable()
+
+ def _lookup(self, trace_record, key, hash):
+ if key not in self.table:
+ return False
+ # A cache hit. Update its next access time.
+ assert (
+ self.table.pqinsert(
+ OPTCacheEntry(
+ key, trace_record.next_access_seq_no, self.table[key].value_size
+ )
+ )
+ is not None
+ )
+ return True
+
+ def _evict(self, trace_record, key, hash, value_size):
+ while self.used_size + value_size > self.cache_size:
+ evict_entry = self.table.pqpop()
+ assert evict_entry is not None
+ self.used_size -= evict_entry.value_size
+
+ def _insert(self, trace_record, key, hash, value_size):
+ assert (
+ self.table.pqinsert(
+ OPTCacheEntry(key, trace_record.next_access_seq_no, value_size)
+ )
+ is None
+ )
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return True
+
+ def cache_name(self):
+ return "Belady MIN (opt)"
+
+
+class GDSizeEntry:
+ """
+ A cache entry for the greedy dual size replacement policy.
+ """
+
+ def __init__(self, key, value_size, priority):
+ self.key = key
+ self.value_size = value_size
+ self.priority = priority
+ self.is_removed = False
+
+ def __cmp__(self, other):
+ if other.priority != self.priority:
+ return self.priority - other.priority
+ return self.value_size - other.value_size
+
+ def __repr__(self):
+ return "({} {} {} {})".format(
+ self.key, self.next_access_seq_no, self.value_size, self.is_removed
+ )
+
+
+class GDSizeCache(Cache):
+ """
+ An implementation of the greedy dual size algorithm.
+ We define cost as an entry's size.
+
+ See https://www.usenix.org/legacy/publications/library/proceedings/usits97/full_papers/cao/cao_html/node8.html
+ and N. Young. The k-server dual and loose competitiveness for paging.
+ Algorithmica,June 1994, vol. 11,(no.6):525-41.
+ Rewritten version of ''On-line caching as cache size varies'',
+ in The 2nd Annual ACM-SIAM Symposium on Discrete Algorithms, 241-250, 1991.
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key):
+ super(GDSizeCache, self).__init__(cache_size, enable_cache_row_key)
+ self.table = PQTable()
+ self.L = 0.0
+
+ def cache_name(self):
+ if self.enable_cache_row_key:
+ return "Hybrid GreedyDualSize (gdsize_hybrid)"
+ return "GreedyDualSize (gdsize)"
+
+ def _lookup(self, trace_record, key, hash):
+ if key not in self.table:
+ return False
+ # A cache hit. Update its priority.
+ entry = self.table[key]
+ assert (
+ self.table.pqinsert(
+ GDSizeEntry(key, entry.value_size, self.L + entry.value_size)
+ )
+ is not None
+ )
+ return True
+
+ def _evict(self, trace_record, key, hash, value_size):
+ while self.used_size + value_size > self.cache_size:
+ evict_entry = self.table.pqpop()
+ assert evict_entry is not None
+ self.L = evict_entry.priority
+ self.used_size -= evict_entry.value_size
+
+ def _insert(self, trace_record, key, hash, value_size):
+ assert (
+ self.table.pqinsert(GDSizeEntry(key, value_size, self.L + value_size))
+ is None
+ )
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return True
+
+
+class Deque(object):
+ """A Deque class facilitates the implementation of LRU and ARC."""
+
+ def __init__(self):
+ self.od = OrderedDict()
+
+ def appendleft(self, k):
+ if k in self.od:
+ del self.od[k]
+ self.od[k] = None
+
+ def pop(self):
+ item = self.od.popitem(last=False) if self.od else None
+ if item is not None:
+ return item[0]
+ return None
+
+ def remove(self, k):
+ del self.od[k]
+
+ def __len__(self):
+ return len(self.od)
+
+ def __contains__(self, k):
+ return k in self.od
+
+ def __iter__(self):
+ return reversed(self.od)
+
+ def __repr__(self):
+ return "Deque(%r)" % (list(self),)
+
+
+class ARCCache(Cache):
+ """
+ An implementation of ARC. ARC assumes that all blocks are having the
+ same size. The size of index and filter blocks are variable. To accommodate
+ this, we modified ARC as follows:
+ 1) We use 16 KB as the average block size and calculate the number of blocks
+ (c) in the cache.
+ 2) When we insert an entry, the cache evicts entries in both t1 and t2
+ queues until it has enough space for the new entry. This also requires
+ modification of the algorithm to maintain a maximum of 2*c blocks.
+
+ Nimrod Megiddo and Dharmendra S. Modha. 2003. ARC: A Self-Tuning, Low
+ Overhead Replacement Cache. In Proceedings of the 2nd USENIX Conference on
+ File and Storage Technologies (FAST '03). USENIX Association, Berkeley, CA,
+ USA, 115-130.
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key):
+ super(ARCCache, self).__init__(cache_size, enable_cache_row_key)
+ self.table = {}
+ self.c = cache_size / 16 * 1024 # Number of elements in the cache.
+ self.p = 0 # Target size for the list T1
+ # L1: only once recently
+ self.t1 = Deque() # T1: recent cache entries
+ self.b1 = Deque() # B1: ghost entries recently evicted from the T1 cache
+ # L2: at least twice recently
+ self.t2 = Deque() # T2: frequent entries
+ self.b2 = Deque() # B2: ghost entries recently evicted from the T2 cache
+
+ def _replace(self, key, value_size):
+ while self.used_size + value_size > self.cache_size:
+ if self.t1 and ((key in self.b2) or (len(self.t1) > self.p)):
+ old = self.t1.pop()
+ self.b1.appendleft(old)
+ else:
+ if self.t2:
+ old = self.t2.pop()
+ self.b2.appendleft(old)
+ else:
+ old = self.t1.pop()
+ self.b1.appendleft(old)
+ self.used_size -= self.table[old].value_size
+ del self.table[old]
+
+ def _lookup(self, trace_record, key, hash):
+ # Case I: key is in T1 or T2.
+ # Move key to MRU position in T2.
+ if key in self.t1:
+ self.t1.remove(key)
+ self.t2.appendleft(key)
+ return True
+
+ if key in self.t2:
+ self.t2.remove(key)
+ self.t2.appendleft(key)
+ return True
+ return False
+
+ def _evict(self, trace_record, key, hash, value_size):
+ # Case II: key is in B1
+ # Move x from B1 to the MRU position in T2 (also fetch x to the cache).
+ if key in self.b1:
+ self.p = min(self.c, self.p + max(len(self.b2) / len(self.b1), 1))
+ self._replace(key, value_size)
+ self.b1.remove(key)
+ self.t2.appendleft(key)
+ return
+
+ # Case III: key is in B2
+ # Move x from B2 to the MRU position in T2 (also fetch x to the cache).
+ if key in self.b2:
+ self.p = max(0, self.p - max(len(self.b1) / len(self.b2), 1))
+ self._replace(key, value_size)
+ self.b2.remove(key)
+ self.t2.appendleft(key)
+ return
+
+ # Case IV: key is not in (T1 u B1 u T2 u B2)
+ self._replace(key, value_size)
+ while len(self.t1) + len(self.b1) >= self.c and self.b1:
+ self.b1.pop()
+
+ total = len(self.t1) + len(self.b1) + len(self.t2) + len(self.b2)
+ while total >= (2 * self.c) and self.b2:
+ self.b2.pop()
+ total -= 1
+ # Finally, move it to MRU position in T1.
+ self.t1.appendleft(key)
+ return
+
+ def _insert(self, trace_record, key, hash, value_size):
+ self.table[key] = CacheEntry(
+ value_size,
+ trace_record.cf_id,
+ trace_record.level,
+ trace_record.block_type,
+ trace_record.table_id,
+ 0,
+ trace_record.access_time,
+ )
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return True
+
+ def cache_name(self):
+ if self.enable_cache_row_key:
+ return "Hybrid Adaptive Replacement Cache (arc_hybrid)"
+ return "Adaptive Replacement Cache (arc)"
+
+
+class LRUCache(Cache):
+ """
+ A strict LRU queue.
+ """
+
+ def __init__(self, cache_size, enable_cache_row_key):
+ super(LRUCache, self).__init__(cache_size, enable_cache_row_key)
+ self.table = {}
+ self.lru = Deque()
+
+ def cache_name(self):
+ if self.enable_cache_row_key:
+ return "Hybrid LRU (lru_hybrid)"
+ return "LRU (lru)"
+
+ def _lookup(self, trace_record, key, hash):
+ if key not in self.table:
+ return False
+ # A cache hit. Update LRU queue.
+ self.lru.remove(key)
+ self.lru.appendleft(key)
+ return True
+
+ def _evict(self, trace_record, key, hash, value_size):
+ while self.used_size + value_size > self.cache_size:
+ evict_key = self.lru.pop()
+ self.used_size -= self.table[evict_key].value_size
+ del self.table[evict_key]
+
+ def _insert(self, trace_record, key, hash, value_size):
+ self.table[key] = CacheEntry(
+ value_size,
+ trace_record.cf_id,
+ trace_record.level,
+ trace_record.block_type,
+ trace_record.table_id,
+ 0,
+ trace_record.access_time,
+ )
+ self.lru.appendleft(key)
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return True
+
+
+class TraceCache(Cache):
+ """
+ A trace cache. Lookup returns true if the trace observes a cache hit.
+ It is used to maintain cache hits observed in the trace.
+ """
+
+ def __init__(self, cache_size):
+ super(TraceCache, self).__init__(cache_size, enable_cache_row_key=0)
+
+ def _lookup(self, trace_record, key, hash):
+ return trace_record.is_hit
+
+ def _evict(self, trace_record, key, hash, value_size):
+ pass
+
+ def _insert(self, trace_record, key, hash, value_size):
+ pass
+
+ def _should_admit(self, trace_record, key, hash, value_size):
+ return False
+
+ def cache_name(self):
+ return "Trace"
+
+
+def parse_cache_size(cs):
+ cs = cs.replace("\n", "")
+ if cs[-1] == "M":
+ return int(cs[: len(cs) - 1]) * 1024 * 1024
+ if cs[-1] == "G":
+ return int(cs[: len(cs) - 1]) * 1024 * 1024 * 1024
+ if cs[-1] == "T":
+ return int(cs[: len(cs) - 1]) * 1024 * 1024 * 1024 * 1024
+ return int(cs)
+
+
+def create_cache(cache_type, cache_size, downsample_size):
+ cache_size = cache_size / downsample_size
+ enable_cache_row_key = 0
+ if "hybridn" in cache_type:
+ enable_cache_row_key = 2
+ cache_type = cache_type[:-8]
+ if "hybrid" in cache_type:
+ enable_cache_row_key = 1
+ cache_type = cache_type[:-7]
+ if cache_type == "ts":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [LRUPolicy(), LFUPolicy(), HyperbolicPolicy()],
+ cost_class_label=None,
+ )
+ elif cache_type == "linucb":
+ return LinUCBCache(
+ cache_size,
+ enable_cache_row_key,
+ [LRUPolicy(), LFUPolicy(), HyperbolicPolicy()],
+ cost_class_label=None,
+ )
+ elif cache_type == "pylru":
+ return ThompsonSamplingCache(
+ cache_size, enable_cache_row_key, [LRUPolicy()], cost_class_label=None
+ )
+ elif cache_type == "pymru":
+ return ThompsonSamplingCache(
+ cache_size, enable_cache_row_key, [MRUPolicy()], cost_class_label=None
+ )
+ elif cache_type == "pylfu":
+ return ThompsonSamplingCache(
+ cache_size, enable_cache_row_key, [LFUPolicy()], cost_class_label=None
+ )
+ elif cache_type == "pyhb":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [HyperbolicPolicy()],
+ cost_class_label=None,
+ )
+ elif cache_type == "pycctbbt":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [CostClassPolicy()],
+ cost_class_label="table_bt",
+ )
+ elif cache_type == "pycccf":
+ return ThompsonSamplingCache(
+ cache_size, enable_cache_row_key, [CostClassPolicy()], cost_class_label="cf"
+ )
+ elif cache_type == "pycctblevelbt":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [CostClassPolicy()],
+ cost_class_label="table_level_bt",
+ )
+ elif cache_type == "pycccfbt":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [CostClassPolicy()],
+ cost_class_label="cf_bt",
+ )
+ elif cache_type == "pycctb":
+ return ThompsonSamplingCache(
+ cache_size,
+ enable_cache_row_key,
+ [CostClassPolicy()],
+ cost_class_label="table",
+ )
+ elif cache_type == "pyccbt":
+ return ThompsonSamplingCache(
+ cache_size, enable_cache_row_key, [CostClassPolicy()], cost_class_label="bt"
+ )
+ elif cache_type == "opt":
+ if enable_cache_row_key:
+ print("opt does not support hybrid mode.")
+ assert False
+ return OPTCache(cache_size)
+ elif cache_type == "trace":
+ if enable_cache_row_key:
+ print("trace does not support hybrid mode.")
+ assert False
+ return TraceCache(cache_size)
+ elif cache_type == "lru":
+ return LRUCache(cache_size, enable_cache_row_key)
+ elif cache_type == "arc":
+ return ARCCache(cache_size, enable_cache_row_key)
+ elif cache_type == "gdsize":
+ return GDSizeCache(cache_size, enable_cache_row_key)
+ else:
+ print("Unknown cache type {}".format(cache_type))
+ assert False
+ return None
+
+
+class BlockAccessTimeline:
+ """
+ BlockAccessTimeline stores all accesses of a block.
+ """
+
+ def __init__(self):
+ self.accesses = []
+ self.current_access_index = 1
+
+ def get_next_access(self):
+ if self.current_access_index == len(self.accesses):
+ return sys.maxsize
+ next_access_seq_no = self.accesses[self.current_access_index]
+ self.current_access_index += 1
+ return next_access_seq_no
+
+
+def percent(e1, e2):
+ if e2 == 0:
+ return -1
+ return float(e1) * 100.0 / float(e2)
+
+
+def is_target_cf(access_cf, target_cf_name):
+ if target_cf_name == "all":
+ return True
+ return access_cf == target_cf_name
+
+
+def run(
+ trace_file_path,
+ cache_type,
+ cache,
+ warmup_seconds,
+ max_accesses_to_process,
+ target_cf_name,
+):
+ warmup_complete = False
+ trace_miss_ratio_stats = MissRatioStats(kSecondsInMinute)
+ access_seq_no = 0
+ time_interval = 1
+ start_time = time.time()
+ trace_start_time = 0
+ trace_duration = 0
+ is_opt_cache = False
+ if cache.cache_name() == "Belady MIN (opt)":
+ is_opt_cache = True
+
+ block_access_timelines = {}
+ num_no_inserts = 0
+ num_blocks_with_no_size = 0
+ num_inserts_block_with_no_size = 0
+
+ if is_opt_cache:
+ # Read all blocks in memory and stores their access times so that OPT
+ # can use this information to evict the cached key which next access is
+ # the furthest in the future.
+ print("Preprocessing block traces.")
+ with open(trace_file_path, "r") as trace_file:
+ for line in trace_file:
+ if (
+ max_accesses_to_process != -1
+ and access_seq_no > max_accesses_to_process
+ ):
+ break
+ ts = line.split(",")
+ timestamp = int(ts[0])
+ cf_name = ts[5]
+ if not is_target_cf(cf_name, target_cf_name):
+ continue
+ if trace_start_time == 0:
+ trace_start_time = timestamp
+ trace_duration = timestamp - trace_start_time
+ block_id = int(ts[1])
+ block_size = int(ts[3])
+ no_insert = int(ts[9])
+ if block_id not in block_access_timelines:
+ block_access_timelines[block_id] = BlockAccessTimeline()
+ if block_size == 0:
+ num_blocks_with_no_size += 1
+ block_access_timelines[block_id].accesses.append(access_seq_no)
+ access_seq_no += 1
+ if no_insert == 1:
+ num_no_inserts += 1
+ if no_insert == 0 and block_size == 0:
+ num_inserts_block_with_no_size += 1
+ if access_seq_no % 100 != 0:
+ continue
+ now = time.time()
+ if now - start_time > time_interval * 10:
+ print(
+ "Take {} seconds to process {} trace records with trace "
+ "duration of {} seconds. Throughput: {} records/second.".format(
+ now - start_time,
+ access_seq_no,
+ trace_duration / 1000000,
+ access_seq_no / (now - start_time),
+ )
+ )
+ time_interval += 1
+ print(
+ "Trace contains {0} blocks, {1}({2:.2f}%) blocks with no size."
+ "{3} accesses, {4}({5:.2f}%) accesses with no_insert,"
+ "{6}({7:.2f}%) accesses that want to insert but block size is 0.".format(
+ len(block_access_timelines),
+ num_blocks_with_no_size,
+ percent(num_blocks_with_no_size, len(block_access_timelines)),
+ access_seq_no,
+ num_no_inserts,
+ percent(num_no_inserts, access_seq_no),
+ num_inserts_block_with_no_size,
+ percent(num_inserts_block_with_no_size, access_seq_no),
+ )
+ )
+
+ access_seq_no = 0
+ time_interval = 1
+ start_time = time.time()
+ trace_start_time = 0
+ trace_duration = 0
+ print("Running simulated {} cache on block traces.".format(cache.cache_name()))
+ with open(trace_file_path, "r") as trace_file:
+ for line in trace_file:
+ if (
+ max_accesses_to_process != -1
+ and access_seq_no > max_accesses_to_process
+ ):
+ break
+ if access_seq_no % 1000000 == 0:
+ # Force a python gc periodically to reduce memory usage.
+ gc.collect()
+ ts = line.split(",")
+ timestamp = int(ts[0])
+ cf_name = ts[5]
+ if not is_target_cf(cf_name, target_cf_name):
+ continue
+ if trace_start_time == 0:
+ trace_start_time = timestamp
+ trace_duration = timestamp - trace_start_time
+ if (
+ not warmup_complete
+ and warmup_seconds > 0
+ and trace_duration > warmup_seconds * 1000000
+ ):
+ cache.miss_ratio_stats.reset_counter()
+ warmup_complete = True
+ next_access_seq_no = 0
+ block_id = int(ts[1])
+ if is_opt_cache:
+ next_access_seq_no = block_access_timelines[block_id].get_next_access()
+ record = TraceRecord(
+ access_time=int(ts[0]),
+ block_id=int(ts[1]),
+ block_type=int(ts[2]),
+ block_size=int(ts[3]),
+ cf_id=int(ts[4]),
+ cf_name=ts[5],
+ level=int(ts[6]),
+ fd=int(ts[7]),
+ caller=int(ts[8]),
+ no_insert=int(ts[9]),
+ get_id=int(ts[10]),
+ key_id=int(ts[11]),
+ kv_size=int(ts[12]),
+ is_hit=int(ts[13]),
+ referenced_key_exist_in_block=int(ts[14]),
+ num_keys_in_block=int(ts[15]),
+ table_id=int(ts[16]),
+ seq_number=int(ts[17]),
+ block_key_size=int(ts[18]),
+ key_size=int(ts[19]),
+ block_offset_in_file=int(ts[20]),
+ next_access_seq_no=next_access_seq_no,
+ )
+ trace_miss_ratio_stats.update_metrics(
+ record.access_time, is_hit=record.is_hit, miss_bytes=record.block_size
+ )
+ cache.access(record)
+ access_seq_no += 1
+ del record
+ del ts
+ if access_seq_no % 100 != 0:
+ continue
+ # Report progress every 10 seconds.
+ now = time.time()
+ if now - start_time > time_interval * 10:
+ print(
+ "Take {} seconds to process {} trace records with trace "
+ "duration of {} seconds. Throughput: {} records/second. "
+ "Trace miss ratio {}".format(
+ now - start_time,
+ access_seq_no,
+ trace_duration / 1000000,
+ access_seq_no / (now - start_time),
+ trace_miss_ratio_stats.miss_ratio(),
+ )
+ )
+ time_interval += 1
+ print(
+ "{},0,0,{},{},{}".format(
+ cache_type,
+ cache.cache_size,
+ cache.miss_ratio_stats.miss_ratio(),
+ cache.miss_ratio_stats.num_accesses,
+ )
+ )
+ now = time.time()
+ print(
+ "Take {} seconds to process {} trace records with trace duration of {} "
+ "seconds. Throughput: {} records/second. Trace miss ratio {}".format(
+ now - start_time,
+ access_seq_no,
+ trace_duration / 1000000,
+ access_seq_no / (now - start_time),
+ trace_miss_ratio_stats.miss_ratio(),
+ )
+ )
+ print(
+ "{},0,0,{},{},{}".format(
+ cache_type,
+ cache.cache_size,
+ cache.miss_ratio_stats.miss_ratio(),
+ cache.miss_ratio_stats.num_accesses,
+ )
+ )
+ return trace_start_time, trace_duration
+
+
+def report_stats(
+ cache,
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+):
+ cache_label = "{}-{}-{}".format(cache_type, cache_size, target_cf_name)
+ with open("{}/data-ml-mrc-{}".format(result_dir, cache_label), "w+") as mrc_file:
+ mrc_file.write(
+ "{},0,0,{},{},{}\n".format(
+ cache_type,
+ cache_size,
+ cache.miss_ratio_stats.miss_ratio(),
+ cache.miss_ratio_stats.num_accesses,
+ )
+ )
+
+ cache_stats = [
+ cache.per_second_miss_ratio_stats,
+ cache.miss_ratio_stats,
+ cache.per_hour_miss_ratio_stats,
+ ]
+ for i in range(len(cache_stats)):
+ avg_miss_bytes, p95_miss_bytes = cache_stats[i].compute_miss_bytes()
+
+ with open(
+ "{}/data-ml-avgmb-{}-{}".format(
+ result_dir, cache_stats[i].time_unit, cache_label
+ ),
+ "w+",
+ ) as mb_file:
+ mb_file.write(
+ "{},0,0,{},{}\n".format(cache_type, cache_size, avg_miss_bytes)
+ )
+
+ with open(
+ "{}/data-ml-p95mb-{}-{}".format(
+ result_dir, cache_stats[i].time_unit, cache_label
+ ),
+ "w+",
+ ) as mb_file:
+ mb_file.write(
+ "{},0,0,{},{}\n".format(cache_type, cache_size, p95_miss_bytes)
+ )
+
+ cache_stats[i].write_miss_timeline(
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+ )
+ cache_stats[i].write_miss_ratio_timeline(
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+ )
+
+ if not cache.is_ml_cache():
+ return
+
+ policy_stats = [cache.policy_stats, cache.per_hour_policy_stats]
+ for i in range(len(policy_stats)):
+ policy_stats[i].write_policy_timeline(
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+ )
+ policy_stats[i].write_policy_ratio_timeline(
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+ )
+
+
+if __name__ == "__main__":
+ if len(sys.argv) <= 8:
+ print(
+ "Must provide 8 arguments.\n"
+ "1) Cache type (ts, linucb, arc, lru, opt, pylru, pymru, pylfu, "
+ "pyhb, gdsize, trace). One may evaluate the hybrid row_block cache "
+ "by appending '_hybrid' to a cache_type, e.g., ts_hybrid. "
+ "Note that hybrid is not supported with opt and trace. \n"
+ "2) Cache size (xM, xG, xT).\n"
+ "3) The sampling frequency used to collect the trace. (The "
+ "simulation scales down the cache size by the sampling frequency).\n"
+ "4) Warmup seconds (The number of seconds used for warmup).\n"
+ "5) Trace file path.\n"
+ "6) Result directory (A directory that saves generated results)\n"
+ "7) Max number of accesses to process\n"
+ "8) The target column family. (The simulation will only run "
+ "accesses on the target column family. If it is set to all, "
+ "it will run against all accesses.)"
+ )
+ exit(1)
+ print("Arguments: {}".format(sys.argv))
+ cache_type = sys.argv[1]
+ cache_size = parse_cache_size(sys.argv[2])
+ downsample_size = int(sys.argv[3])
+ warmup_seconds = int(sys.argv[4])
+ trace_file_path = sys.argv[5]
+ result_dir = sys.argv[6]
+ max_accesses_to_process = int(sys.argv[7])
+ target_cf_name = sys.argv[8]
+ cache = create_cache(cache_type, cache_size, downsample_size)
+ trace_start_time, trace_duration = run(
+ trace_file_path,
+ cache_type,
+ cache,
+ warmup_seconds,
+ max_accesses_to_process,
+ target_cf_name,
+ )
+ trace_end_time = trace_start_time + trace_duration
+ report_stats(
+ cache,
+ cache_type,
+ cache_size,
+ target_cf_name,
+ result_dir,
+ trace_start_time,
+ trace_end_time,
+ )
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.sh b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.sh
new file mode 100644
index 00000000..295f734a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim.sh
@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+# A shell script to run a batch of pysims and combine individual pysim output files.
+#
+# Usage: bash block_cache_pysim.sh trace_file_path result_dir downsample_size warmup_seconds max_jobs
+# trace_file_path: The file path that stores the traces.
+# result_dir: The directory to store pysim results. The output files from a pysim is stores in result_dir/ml
+# downsample_size: The downsample size used to collect the trace.
+# warmup_seconds: The number of seconds used for warmup.
+# max_jobs: The max number of concurrent pysims to run.
+
+# Install required packages to run simulations.
+# sudo dnf install -y numpy scipy python-matplotlib ipython python-pandas sympy python-nose atlas-devel
+ulimit -c 0
+
+if [ $# -ne 5 ]; then
+ echo "Usage: ./block_cache_pysim.sh trace_file_path result_dir downsample_size warmup_seconds max_jobs"
+ exit 0
+fi
+
+trace_file="$1"
+result_dir="$2"
+downsample_size="$3"
+warmup_seconds="$4"
+max_jobs="$5"
+max_num_accesses=100000000
+current_jobs=1
+
+ml_tmp_result_dir="$result_dir/ml"
+rm -rf "$ml_tmp_result_dir"
+mkdir -p "$result_dir"
+mkdir -p "$ml_tmp_result_dir"
+
+# Report miss ratio in the trace.
+current_jobs=$(ps aux | grep pysim | grep python | grep -cv grep)
+for cf_name in "all"
+do
+for cache_size in "1G" "2G" "4G" "8G" "16G" #"12G" "16G" "1T"
+do
+for cache_type in "opt" "lru" "pylru" "pycctbbt" "pyhb" "ts" "trace" "lru_hybrid" #"pycctblevelbt" #"lru_hybridn" "opt" #"pylru" "pylru_hybrid" "pycctbbt" "pycccfbt" "trace"
+do
+ if [[ $cache_type == "trace" && $cache_size != "16G" ]]; then
+ # We only need to collect miss ratios observed in the trace once.
+ continue
+ fi
+ while [ "$current_jobs" -ge "$max_jobs" ]
+ do
+ sleep 10
+ echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
+ current_jobs=$(ps aux | grep pysim | grep python | grep -cv grep)
+ echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
+ done
+ output="log-ml-$cache_type-$cache_size-$cf_name"
+ echo "Running simulation for $cache_type, cache size $cache_size, and cf_name $cf_name. Number of running jobs: $current_jobs. "
+ nohup python block_cache_pysim.py "$cache_type" "$cache_size" "$downsample_size" "$warmup_seconds" "$trace_file" "$ml_tmp_result_dir" "$max_num_accesses" "$cf_name" >& "$ml_tmp_result_dir/$output" &
+ current_jobs=$((current_jobs+1))
+done
+done
+done
+
+# Wait for all jobs to complete.
+while [ $current_jobs -gt 0 ]
+do
+ sleep 10
+ echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
+ current_jobs=$(ps aux | grep pysim | grep python | grep -cv grep)
+ echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
+done
+
+echo "Combine individual pysim output files"
+
+rm -rf "$result_dir/ml_*"
+for header in "header-" "data-"
+do
+for fn in "$ml_tmp_result_dir"/*
+do
+ sum_file=""
+ time_unit=""
+ capacity=""
+ target_cf_name=""
+ if [[ $fn == *"timeline"* ]]; then
+ tmpfn="$fn"
+ IFS='-' read -ra elements <<< "$tmpfn"
+ time_unit_index=0
+ capacity_index=0
+ for i in "${elements[@]}"
+ do
+ if [[ $i == "timeline" ]]; then
+ break
+ fi
+ time_unit_index=$((time_unit_index+1))
+ done
+ time_unit_index=$((time_unit_index+1))
+ capacity_index=$((time_unit_index+2))
+ target_cf_name_index=$((time_unit_index+3))
+ time_unit="${elements[$time_unit_index]}_"
+ capacity="${elements[$capacity_index]}_"
+ target_cf_name="${elements[$target_cf_name_index]}_"
+ fi
+
+ if [[ $fn == *"${header}ml-policy-timeline"* ]]; then
+ sum_file="$result_dir/ml_${target_cf_name}${capacity}${time_unit}policy_timeline"
+ fi
+ if [[ $fn == *"${header}ml-policy-ratio-timeline"* ]]; then
+ sum_file="$result_dir/ml_${target_cf_name}${capacity}${time_unit}policy_ratio_timeline"
+ fi
+ if [[ $fn == *"${header}ml-miss-timeline"* ]]; then
+ sum_file="$result_dir/ml_${target_cf_name}${capacity}${time_unit}miss_timeline"
+ fi
+ if [[ $fn == *"${header}ml-miss-ratio-timeline"* ]]; then
+ sum_file="$result_dir/ml_${target_cf_name}${capacity}${time_unit}miss_ratio_timeline"
+ fi
+ if [[ $fn == *"${header}ml-mrc"* ]]; then
+ tmpfn="$fn"
+ IFS='-' read -ra elements <<< "$tmpfn"
+ target_cf_name=${elements[-1]}
+ sum_file="${result_dir}/ml_${target_cf_name}_mrc"
+ fi
+ if [[ $fn == *"${header}ml-avgmb"* ]]; then
+ tmpfn="$fn"
+ IFS='-' read -ra elements <<< "$tmpfn"
+ time_unit=${elements[3]}
+ target_cf_name=${elements[-1]}
+ sum_file="${result_dir}/ml_${time_unit}_${target_cf_name}_avgmb"
+ fi
+ if [[ $fn == *"${header}ml-p95mb"* ]]; then
+ tmpfn="$fn"
+ IFS='-' read -ra elements <<< "$tmpfn"
+ time_unit=${elements[3]}
+ target_cf_name=${elements[-1]}
+ sum_file="${result_dir}/ml_${time_unit}_${target_cf_name}_p95mb"
+ fi
+ if [[ $sum_file == "" ]]; then
+ continue
+ fi
+ if [[ $header == "header-" ]]; then
+ if [ -e "$sum_file" ]; then
+ continue
+ fi
+ fi
+ cat "$fn" >> "$sum_file"
+done
+done
+
+echo "Done"
+for fn in $result_dir/*
+do
+ if [[ $fn == *"_mrc" || $fn == *"_avgmb" || $fn == *"_p95mb" ]]; then
+ # Sort MRC file by cache_type and cache_size.
+ tmp_file="$result_dir/tmp_mrc"
+ cat "$fn" | sort -t ',' -k1,1 -k4,4n > "$tmp_file"
+ cat "$tmp_file" > "$fn"
+ rm -rf "$tmp_file"
+ fi
+done
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim_test.py b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim_test.py
new file mode 100644
index 00000000..4b2bdeba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_pysim_test.py
@@ -0,0 +1,734 @@
+#!/usr/bin/env python3
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+import os
+import random
+import sys
+
+from block_cache_pysim import (
+ ARCCache,
+ CacheEntry,
+ GDSizeCache,
+ HashTable,
+ HyperbolicPolicy,
+ LFUPolicy,
+ LinUCBCache,
+ LRUCache,
+ LRUPolicy,
+ MRUPolicy,
+ OPTCache,
+ OPTCacheEntry,
+ ThompsonSamplingCache,
+ TraceCache,
+ TraceRecord,
+ create_cache,
+ kMicrosInSecond,
+ kSampleSize,
+ run,
+)
+
+
+def test_hash_table():
+ print("Test hash table")
+ table = HashTable()
+ data_size = 10000
+ for i in range(data_size):
+ table.insert("k{}".format(i), i, "v{}".format(i))
+ for i in range(data_size):
+ assert table.lookup("k{}".format(i), i) is not None
+ for i in range(data_size):
+ table.delete("k{}".format(i), i)
+ for i in range(data_size):
+ assert table.lookup("k{}".format(i), i) is None
+
+ truth_map = {}
+ n = 1000000
+ records = 100
+ for i in range(n):
+ key_id = random.randint(0, records)
+ v = random.randint(0, records)
+ key = "k{}".format(key_id)
+ value = CacheEntry(v, v, v, v, v, v, v)
+ action = random.randint(0, 10)
+ assert len(truth_map) == table.elements, "{} {} {}".format(
+ len(truth_map), table.elements, i
+ )
+ if action <= 8:
+ if key in truth_map:
+ assert table.lookup(key, key_id) is not None
+ assert truth_map[key].value_size == table.lookup(key, key_id).value_size
+ else:
+ assert table.lookup(key, key_id) is None
+ table.insert(key, key_id, value)
+ truth_map[key] = value
+ else:
+ deleted = table.delete(key, key_id)
+ if deleted:
+ assert key in truth_map
+ if key in truth_map:
+ del truth_map[key]
+
+ # Check all keys are unique in the sample set.
+ for _i in range(10):
+ samples = table.random_sample(kSampleSize)
+ unique_keys = {}
+ for sample in samples:
+ unique_keys[sample.key] = True
+ assert len(samples) == len(unique_keys)
+
+ assert len(table) == len(truth_map)
+ for key in truth_map:
+ assert table.lookup(key, int(key[1:])) is not None
+ assert truth_map[key].value_size == table.lookup(key, int(key[1:])).value_size
+ print("Test hash table: Success")
+
+
+def assert_metrics(cache, expected_value, expected_value_size=1, custom_hashtable=True):
+ assert cache.used_size == expected_value[0], "Expected {}, Actual {}".format(
+ expected_value[0], cache.used_size
+ )
+ assert (
+ cache.miss_ratio_stats.num_accesses == expected_value[1]
+ ), "Expected {}, Actual {}".format(
+ expected_value[1], cache.miss_ratio_stats.num_accesses
+ )
+ assert (
+ cache.miss_ratio_stats.num_misses == expected_value[2]
+ ), "Expected {}, Actual {}".format(
+ expected_value[2], cache.miss_ratio_stats.num_misses
+ )
+ assert len(cache.table) == len(expected_value[3]) + len(
+ expected_value[4]
+ ), "Expected {}, Actual {}".format(
+ len(expected_value[3]) + len(expected_value[4]), cache.table.elements
+ )
+ for expeceted_k in expected_value[3]:
+ if custom_hashtable:
+ val = cache.table.lookup("b{}".format(expeceted_k), expeceted_k)
+ else:
+ val = cache.table["b{}".format(expeceted_k)]
+ assert val is not None, "Expected {} Actual: Not Exist {}, Table: {}".format(
+ expeceted_k, expected_value, cache.table
+ )
+ assert val.value_size == expected_value_size
+ for expeceted_k in expected_value[4]:
+ if custom_hashtable:
+ val = cache.table.lookup("g0-{}".format(expeceted_k), expeceted_k)
+ else:
+ val = cache.table["g0-{}".format(expeceted_k)]
+ assert val is not None
+ assert val.value_size == expected_value_size
+
+
+# Access k1, k1, k2, k3, k3, k3, k4
+# When k4 is inserted,
+# LRU should evict k1.
+# LFU should evict k2.
+# MRU should evict k3.
+def test_cache(cache, expected_value, custom_hashtable=True):
+ k1 = TraceRecord(
+ access_time=0,
+ block_id=1,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1,
+ key_id=1,
+ kv_size=5,
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=0,
+ )
+ k2 = TraceRecord(
+ access_time=1,
+ block_id=2,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1,
+ key_id=1,
+ kv_size=5,
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=0,
+ )
+ k3 = TraceRecord(
+ access_time=2,
+ block_id=3,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1,
+ key_id=1,
+ kv_size=5,
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=0,
+ )
+ k4 = TraceRecord(
+ access_time=3,
+ block_id=4,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1,
+ key_id=1,
+ kv_size=5,
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=0,
+ )
+ sequence = [k1, k1, k2, k3, k3, k3]
+ index = 0
+ expected_values = []
+ # Access k1, miss.
+ expected_values.append([1, 1, 1, [1], []])
+ # Access k1, hit.
+ expected_values.append([1, 2, 1, [1], []])
+ # Access k2, miss.
+ expected_values.append([2, 3, 2, [1, 2], []])
+ # Access k3, miss.
+ expected_values.append([3, 4, 3, [1, 2, 3], []])
+ # Access k3, hit.
+ expected_values.append([3, 5, 3, [1, 2, 3], []])
+ # Access k3, hit.
+ expected_values.append([3, 6, 3, [1, 2, 3], []])
+ access_time = 0
+ for access in sequence:
+ access.access_time = access_time
+ cache.access(access)
+ assert_metrics(
+ cache,
+ expected_values[index],
+ expected_value_size=1,
+ custom_hashtable=custom_hashtable,
+ )
+ access_time += 1
+ index += 1
+ k4.access_time = access_time
+ cache.access(k4)
+ assert_metrics(
+ cache, expected_value, expected_value_size=1, custom_hashtable=custom_hashtable
+ )
+
+
+def test_lru_cache(cache, custom_hashtable):
+ print("Test LRU cache")
+ # Access k4, miss. evict k1
+ test_cache(cache, [3, 7, 4, [2, 3, 4], []], custom_hashtable)
+ print("Test LRU cache: Success")
+
+
+def test_mru_cache():
+ print("Test MRU cache")
+ policies = []
+ policies.append(MRUPolicy())
+ # Access k4, miss. evict k3
+ test_cache(
+ ThompsonSamplingCache(3, False, policies, cost_class_label=None),
+ [3, 7, 4, [1, 2, 4], []],
+ )
+ print("Test MRU cache: Success")
+
+
+def test_lfu_cache():
+ print("Test LFU cache")
+ policies = []
+ policies.append(LFUPolicy())
+ # Access k4, miss. evict k2
+ test_cache(
+ ThompsonSamplingCache(3, False, policies, cost_class_label=None),
+ [3, 7, 4, [1, 3, 4], []],
+ )
+ print("Test LFU cache: Success")
+
+
+def test_mix(cache):
+ print("Test Mix {} cache".format(cache.cache_name()))
+ n = 100000
+ records = 100
+ block_size_table = {}
+ trace_num_misses = 0
+ for i in range(n):
+ key_id = random.randint(0, records)
+ vs = random.randint(0, 10)
+ now = i * kMicrosInSecond
+ block_size = vs
+ if key_id in block_size_table:
+ block_size = block_size_table[key_id]
+ else:
+ block_size_table[key_id] = block_size
+ is_hit = key_id % 2
+ if is_hit == 0:
+ trace_num_misses += 1
+ k = TraceRecord(
+ access_time=now,
+ block_id=key_id,
+ block_type=1,
+ block_size=block_size,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=key_id,
+ key_id=key_id,
+ kv_size=5,
+ is_hit=is_hit,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=vs,
+ )
+ cache.access(k)
+ assert cache.miss_ratio_stats.miss_ratio() > 0
+ if cache.cache_name() == "Trace":
+ assert cache.miss_ratio_stats.num_accesses == n
+ assert cache.miss_ratio_stats.num_misses == trace_num_misses
+ else:
+ assert cache.used_size <= cache.cache_size
+ all_values = cache.table.values()
+ cached_size = 0
+ for value in all_values:
+ cached_size += value.value_size
+ assert cached_size == cache.used_size, "Expeced {} Actual {}".format(
+ cache.used_size, cached_size
+ )
+ print("Test Mix {} cache: Success".format(cache.cache_name()))
+
+
+def test_end_to_end():
+ print("Test All caches")
+ n = 100000
+ nblocks = 1000
+ block_size = 16 * 1024
+ ncfs = 7
+ nlevels = 6
+ nfds = 100000
+ trace_file_path = "test_trace"
+ # All blocks are of the same size so that OPT must achieve the lowest miss
+ # ratio.
+ with open(trace_file_path, "w+") as trace_file:
+ access_records = ""
+ for i in range(n):
+ key_id = random.randint(0, nblocks)
+ cf_id = random.randint(0, ncfs)
+ level = random.randint(0, nlevels)
+ fd = random.randint(0, nfds)
+ now = i * kMicrosInSecond
+ access_record = ""
+ access_record += "{},".format(now)
+ access_record += "{},".format(key_id)
+ access_record += "{},".format(9) # block type
+ access_record += "{},".format(block_size) # block size
+ access_record += "{},".format(cf_id)
+ access_record += "cf_{},".format(cf_id)
+ access_record += "{},".format(level)
+ access_record += "{},".format(fd)
+ access_record += "{},".format(key_id % 3) # caller
+ access_record += "{},".format(0) # no insert
+ access_record += "{},".format(i) # get_id
+ access_record += "{},".format(i) # key_id
+ access_record += "{},".format(100) # kv_size
+ access_record += "{},".format(1) # is_hit
+ access_record += "{},".format(1) # referenced_key_exist_in_block
+ access_record += "{},".format(10) # num_keys_in_block
+ access_record += "{},".format(1) # table_id
+ access_record += "{},".format(0) # seq_number
+ access_record += "{},".format(10) # block key size
+ access_record += "{},".format(20) # key size
+ access_record += "{},".format(0) # block offset
+ access_record = access_record[:-1]
+ access_records += access_record + "\n"
+ trace_file.write(access_records)
+
+ print("Test All caches: Start testing caches")
+ cache_size = block_size * nblocks / 10
+ downsample_size = 1
+ cache_ms = {}
+ for cache_type in [
+ "ts",
+ "opt",
+ "lru",
+ "pylru",
+ "linucb",
+ "gdsize",
+ "pyccbt",
+ "pycctbbt",
+ ]:
+ cache = create_cache(cache_type, cache_size, downsample_size)
+ run(trace_file_path, cache_type, cache, 0, -1, "all")
+ cache_ms[cache_type] = cache
+ assert cache.miss_ratio_stats.num_accesses == n
+
+ for cache_type in cache_ms:
+ cache = cache_ms[cache_type]
+ ms = cache.miss_ratio_stats.miss_ratio()
+ assert ms <= 100.0 and ms >= 0.0
+ # OPT should perform the best.
+ assert cache_ms["opt"].miss_ratio_stats.miss_ratio() <= ms
+ assert cache.used_size <= cache.cache_size
+ all_values = cache.table.values()
+ cached_size = 0
+ for value in all_values:
+ cached_size += value.value_size
+ assert cached_size == cache.used_size, "Expeced {} Actual {}".format(
+ cache.used_size, cached_size
+ )
+ print("Test All {}: Success".format(cache.cache_name()))
+
+ os.remove(trace_file_path)
+ print("Test All: Success")
+
+
+def test_hybrid(cache):
+ print("Test {} cache".format(cache.cache_name()))
+ k = TraceRecord(
+ access_time=0,
+ block_id=1,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1, # the first get request.
+ key_id=1,
+ kv_size=0, # no size.
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=0,
+ )
+ cache.access(k) # Expect a miss.
+ # used size, num accesses, num misses, hash table size, blocks, get keys.
+ assert_metrics(cache, [1, 1, 1, [1], []])
+ k.access_time += 1
+ k.kv_size = 1
+ k.block_id = 2
+ cache.access(k) # k should be inserted.
+ assert_metrics(cache, [3, 2, 2, [1, 2], [1]])
+ k.access_time += 1
+ k.block_id = 3
+ cache.access(k) # k should not be inserted again.
+ assert_metrics(cache, [4, 3, 3, [1, 2, 3], [1]])
+ # A second get request referencing the same key.
+ k.access_time += 1
+ k.get_id = 2
+ k.block_id = 4
+ k.kv_size = 0
+ cache.access(k) # k should observe a hit. No block access.
+ assert_metrics(cache, [4, 4, 3, [1, 2, 3], [1]])
+
+ # A third get request searches three files, three different keys.
+ # And the second key observes a hit.
+ k.access_time += 1
+ k.kv_size = 1
+ k.get_id = 3
+ k.block_id = 3
+ k.key_id = 2
+ cache.access(k) # k should observe a miss. block 3 observes a hit.
+ assert_metrics(cache, [5, 5, 3, [1, 2, 3], [1, 2]])
+
+ k.access_time += 1
+ k.kv_size = 1
+ k.get_id = 3
+ k.block_id = 4
+ k.kv_size = 1
+ k.key_id = 1
+ cache.access(k) # k1 should observe a hit.
+ assert_metrics(cache, [5, 6, 3, [1, 2, 3], [1, 2]])
+
+ k.access_time += 1
+ k.kv_size = 1
+ k.get_id = 3
+ k.block_id = 4
+ k.kv_size = 1
+ k.key_id = 3
+ # k3 should observe a miss.
+ # However, as the get already complete, we should not access k3 any more.
+ cache.access(k)
+ assert_metrics(cache, [5, 7, 3, [1, 2, 3], [1, 2]])
+
+ # A fourth get request searches one file and two blocks. One row key.
+ k.access_time += 1
+ k.get_id = 4
+ k.block_id = 5
+ k.key_id = 4
+ k.kv_size = 1
+ cache.access(k)
+ assert_metrics(cache, [7, 8, 4, [1, 2, 3, 5], [1, 2, 4]])
+
+ # A bunch of insertions which evict cached row keys.
+ for i in range(6, 100):
+ k.access_time += 1
+ k.get_id = 0
+ k.block_id = i
+ cache.access(k)
+
+ k.get_id = 4
+ k.block_id = 100 # A different block.
+ k.key_id = 4 # Same row key and should not be inserted again.
+ k.kv_size = 1
+ cache.access(k)
+ assert_metrics(
+ cache, [kSampleSize, 103, 99, [i for i in range(101 - kSampleSize, 101)], []]
+ )
+ print("Test {} cache: Success".format(cache.cache_name()))
+
+
+def test_opt_cache():
+ print("Test OPT cache")
+ cache = OPTCache(3)
+ # seq: 0, 1, 2, 3, 4, 5, 6, 7, 8
+ # key: k1, k2, k3, k4, k5, k6, k7, k1, k8
+ # next_access: 7, 19, 18, M, M, 17, 16, 25, M
+ k = TraceRecord(
+ access_time=0,
+ block_id=1,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1, # the first get request.
+ key_id=1,
+ kv_size=0, # no size.
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=7,
+ )
+ cache.access(k)
+ assert_metrics(
+ cache, [1, 1, 1, [1], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 2
+ k.next_access_seq_no = 19
+ cache.access(k)
+ assert_metrics(
+ cache, [2, 2, 2, [1, 2], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 3
+ k.next_access_seq_no = 18
+ cache.access(k)
+ assert_metrics(
+ cache, [3, 3, 3, [1, 2, 3], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 4
+ k.next_access_seq_no = sys.maxsize # Never accessed again.
+ cache.access(k)
+ # Evict 2 since its next access 19 is the furthest in the future.
+ assert_metrics(
+ cache, [3, 4, 4, [1, 3, 4], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 5
+ k.next_access_seq_no = sys.maxsize # Never accessed again.
+ cache.access(k)
+ # Evict 4 since its next access MAXINT is the furthest in the future.
+ assert_metrics(
+ cache, [3, 5, 5, [1, 3, 5], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 6
+ k.next_access_seq_no = 17
+ cache.access(k)
+ # Evict 5 since its next access MAXINT is the furthest in the future.
+ assert_metrics(
+ cache, [3, 6, 6, [1, 3, 6], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 7
+ k.next_access_seq_no = 16
+ cache.access(k)
+ # Evict 3 since its next access 18 is the furthest in the future.
+ assert_metrics(
+ cache, [3, 7, 7, [1, 6, 7], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 1
+ k.next_access_seq_no = 25
+ cache.access(k)
+ assert_metrics(
+ cache, [3, 8, 7, [1, 6, 7], []], expected_value_size=1, custom_hashtable=False
+ )
+ k.access_time += 1
+ k.block_id = 8
+ k.next_access_seq_no = sys.maxsize
+ cache.access(k)
+ # Evict 1 since its next access 25 is the furthest in the future.
+ assert_metrics(
+ cache, [3, 9, 8, [6, 7, 8], []], expected_value_size=1, custom_hashtable=False
+ )
+
+ # Insert a large kv pair to evict all keys.
+ k.access_time += 1
+ k.block_id = 10
+ k.block_size = 3
+ k.next_access_seq_no = sys.maxsize
+ cache.access(k)
+ assert_metrics(
+ cache, [3, 10, 9, [10], []], expected_value_size=3, custom_hashtable=False
+ )
+ print("Test OPT cache: Success")
+
+
+def test_trace_cache():
+ print("Test trace cache")
+ cache = TraceCache(0)
+ k = TraceRecord(
+ access_time=0,
+ block_id=1,
+ block_type=1,
+ block_size=1,
+ cf_id=0,
+ cf_name="",
+ level=0,
+ fd=0,
+ caller=1,
+ no_insert=0,
+ get_id=1,
+ key_id=1,
+ kv_size=0,
+ is_hit=1,
+ referenced_key_exist_in_block=1,
+ num_keys_in_block=0,
+ table_id=0,
+ seq_number=0,
+ block_key_size=0,
+ key_size=0,
+ block_offset_in_file=0,
+ next_access_seq_no=7,
+ )
+ cache.access(k)
+ assert cache.miss_ratio_stats.num_accesses == 1
+ assert cache.miss_ratio_stats.num_misses == 0
+ k.is_hit = 0
+ cache.access(k)
+ assert cache.miss_ratio_stats.num_accesses == 2
+ assert cache.miss_ratio_stats.num_misses == 1
+ print("Test trace cache: Success")
+
+
+if __name__ == "__main__":
+ test_hash_table()
+ test_trace_cache()
+ test_opt_cache()
+ test_lru_cache(
+ ThompsonSamplingCache(
+ 3, enable_cache_row_key=0, policies=[LRUPolicy()], cost_class_label=None
+ ),
+ custom_hashtable=True,
+ )
+ test_lru_cache(LRUCache(3, enable_cache_row_key=0), custom_hashtable=False)
+ test_mru_cache()
+ test_lfu_cache()
+ test_hybrid(
+ ThompsonSamplingCache(
+ kSampleSize,
+ enable_cache_row_key=1,
+ policies=[LRUPolicy()],
+ cost_class_label=None,
+ )
+ )
+ test_hybrid(
+ LinUCBCache(
+ kSampleSize,
+ enable_cache_row_key=1,
+ policies=[LRUPolicy()],
+ cost_class_label=None,
+ )
+ )
+ for cache_type in [
+ "ts",
+ "opt",
+ "arc",
+ "pylfu",
+ "pymru",
+ "trace",
+ "pyhb",
+ "lru",
+ "pylru",
+ "linucb",
+ "gdsize",
+ "pycctbbt",
+ "pycctb",
+ "pyccbt",
+ ]:
+ for enable_row_cache in [0, 1, 2]:
+ cache_type_str = cache_type
+ if cache_type != "opt" and cache_type != "trace":
+ if enable_row_cache == 1:
+ cache_type_str += "_hybrid"
+ elif enable_row_cache == 2:
+ cache_type_str += "_hybridn"
+ test_mix(create_cache(cache_type_str, cache_size=100, downsample_size=1))
+ test_end_to_end()
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.cc b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.cc
new file mode 100644
index 00000000..f90cb794
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.cc
@@ -0,0 +1,2308 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifdef GFLAGS
+#include "tools/block_cache_analyzer/block_cache_trace_analyzer.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <random>
+#include <sstream>
+
+#include "monitoring/histogram.h"
+#include "util/gflags_compat.h"
+#include "util/string_util.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+
+DEFINE_string(block_cache_trace_path, "", "The trace file path.");
+DEFINE_bool(is_block_cache_human_readable_trace, false,
+ "Is the trace file provided for analysis generated by running "
+ "block_cache_trace_analyzer with "
+ "FLAGS_human_readable_trace_file_path is specified.");
+DEFINE_string(
+ block_cache_sim_config_path, "",
+ "The config file path. One cache configuration per line. The format of a "
+ "cache configuration is "
+ "cache_name,num_shard_bits,ghost_capacity,cache_capacity_1,...,cache_"
+ "capacity_N. Supported cache names are lru, lru_priority, lru_hybrid, and "
+ "lru_hybrid_no_insert_on_row_miss. User may also add a prefix 'ghost_' to "
+ "a cache_name to add a ghost cache in front of the real cache. "
+ "ghost_capacity and cache_capacity can be xK, xM or xG where x is a "
+ "positive number.");
+DEFINE_int32(block_cache_trace_downsample_ratio, 1,
+ "The trace collected accesses on one in every "
+ "block_cache_trace_downsample_ratio blocks. We scale "
+ "down the simulated cache size by this ratio.");
+DEFINE_bool(print_block_size_stats, false,
+ "Print block size distribution and the distribution break down by "
+ "block type and column family.");
+DEFINE_bool(print_access_count_stats, false,
+ "Print access count distribution and the distribution break down "
+ "by block type and column family.");
+DEFINE_bool(print_data_block_access_count_stats, false,
+ "Print data block accesses by user Get and Multi-Get.");
+DEFINE_int32(cache_sim_warmup_seconds, 0,
+ "The number of seconds to warmup simulated caches. The hit/miss "
+ "counters are reset after the warmup completes.");
+DEFINE_int32(analyze_bottom_k_access_count_blocks, 0,
+ "Print out detailed access information for blocks with their "
+ "number of accesses are the bottom k among all blocks.");
+DEFINE_int32(analyze_top_k_access_count_blocks, 0,
+ "Print out detailed access information for blocks with their "
+ "number of accesses are the top k among all blocks.");
+DEFINE_string(block_cache_analysis_result_dir, "",
+ "The directory that saves block cache analysis results.");
+DEFINE_string(
+ timeline_labels, "",
+ "Group the number of accesses per block per second using these labels. "
+ "Possible labels are a combination of the following: cf (column family), "
+ "sst, level, bt (block type), caller, block. For example, label \"cf_bt\" "
+ "means the number of acccess per second is grouped by unique pairs of "
+ "\"cf_bt\". A label \"all\" contains the aggregated number of accesses per "
+ "second across all possible labels.");
+DEFINE_string(reuse_distance_labels, "",
+ "Group the reuse distance of a block using these labels. Reuse "
+ "distance is defined as the cumulated size of unique blocks read "
+ "between two consecutive accesses on the same block.");
+DEFINE_string(
+ reuse_distance_buckets, "",
+ "Group blocks by their reuse distances given these buckets. For "
+ "example, if 'reuse_distance_buckets' is '1K,1M,1G', we will "
+ "create four buckets. The first three buckets contain the number of "
+ "blocks with reuse distance less than 1KB, between 1K and 1M, between 1M "
+ "and 1G, respectively. The last bucket contains the number of blocks with "
+ "reuse distance larger than 1G. ");
+DEFINE_string(
+ reuse_interval_labels, "",
+ "Group the reuse interval of a block using these labels. Reuse "
+ "interval is defined as the time between two consecutive accesses "
+ "on the same block.");
+DEFINE_string(
+ reuse_interval_buckets, "",
+ "Group blocks by their reuse interval given these buckets. For "
+ "example, if 'reuse_distance_buckets' is '1,10,100', we will "
+ "create four buckets. The first three buckets contain the number of "
+ "blocks with reuse interval less than 1 second, between 1 second and 10 "
+ "seconds, between 10 seconds and 100 seconds, respectively. The last "
+ "bucket contains the number of blocks with reuse interval longer than 100 "
+ "seconds.");
+DEFINE_string(
+ reuse_lifetime_labels, "",
+ "Group the reuse lifetime of a block using these labels. Reuse "
+ "lifetime is defined as the time interval between the first access on a "
+ "block and the last access on the same block. For blocks that are only "
+ "accessed once, its lifetime is set to kMaxUint64.");
+DEFINE_string(
+ reuse_lifetime_buckets, "",
+ "Group blocks by their reuse lifetime given these buckets. For "
+ "example, if 'reuse_lifetime_buckets' is '1,10,100', we will "
+ "create four buckets. The first three buckets contain the number of "
+ "blocks with reuse lifetime less than 1 second, between 1 second and 10 "
+ "seconds, between 10 seconds and 100 seconds, respectively. The last "
+ "bucket contains the number of blocks with reuse lifetime longer than 100 "
+ "seconds.");
+DEFINE_string(
+ analyze_callers, "",
+ "The list of callers to perform a detailed analysis on. If speicfied, the "
+ "analyzer will output a detailed percentage of accesses for each caller "
+ "break down by column family, level, and block type. A list of available "
+ "callers are: Get, MultiGet, Iterator, ApproximateSize, VerifyChecksum, "
+ "SSTDumpTool, ExternalSSTIngestion, Repair, Prefetch, Compaction, "
+ "CompactionRefill, Flush, SSTFileReader, Uncategorized.");
+DEFINE_string(access_count_buckets, "",
+ "Group number of blocks by their access count given these "
+ "buckets. If specified, the analyzer will output a detailed "
+ "analysis on the number of blocks grouped by their access count "
+ "break down by block type and column family.");
+DEFINE_int32(analyze_blocks_reuse_k_reuse_window, 0,
+ "Analyze the percentage of blocks that are accessed in the "
+ "[k, 2*k] seconds are accessed again in the next [2*k, 3*k], "
+ "[3*k, 4*k],...,[k*(n-1), k*n] seconds. ");
+DEFINE_string(analyze_get_spatial_locality_labels, "",
+ "Group data blocks using these labels.");
+DEFINE_string(analyze_get_spatial_locality_buckets, "",
+ "Group data blocks by their statistics using these buckets.");
+DEFINE_string(skew_labels, "",
+ "Group the access count of a block using these labels.");
+DEFINE_string(skew_buckets, "", "Group the skew labels using these buckets.");
+DEFINE_bool(mrc_only, false,
+ "Evaluate alternative cache policies only. When this flag is true, "
+ "the analyzer does NOT maintain states of each block in memory for "
+ "analysis. It only feeds the accesses into the cache simulators.");
+DEFINE_string(
+ analyze_correlation_coefficients_labels, "",
+ "Analyze the correlation coefficients of features such as number of past "
+ "accesses with regard to the number of accesses till the next access.");
+DEFINE_int32(analyze_correlation_coefficients_max_number_of_values, 1000000,
+ "The maximum number of values for a feature. If the number of "
+ "values for a feature is larger than this max, it randomly "
+ "selects 'max' number of values.");
+DEFINE_string(human_readable_trace_file_path, "",
+ "The filt path that saves human readable access records.");
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+
+const std::string kMissRatioCurveFileName = "mrc";
+const std::string kGroupbyBlock = "block";
+const std::string kGroupbyTable = "table";
+const std::string kGroupbyColumnFamily = "cf";
+const std::string kGroupbySSTFile = "sst";
+const std::string kGroupbyBlockType = "bt";
+const std::string kGroupbyCaller = "caller";
+const std::string kGroupbyLevel = "level";
+const std::string kGroupbyAll = "all";
+const std::set<std::string> kGroupbyLabels{
+ kGroupbyBlock, kGroupbyColumnFamily, kGroupbySSTFile, kGroupbyLevel,
+ kGroupbyBlockType, kGroupbyCaller, kGroupbyAll};
+const std::string kSupportedCacheNames =
+ " lru ghost_lru lru_priority ghost_lru_priority lru_hybrid "
+ "ghost_lru_hybrid lru_hybrid_no_insert_on_row_miss "
+ "ghost_lru_hybrid_no_insert_on_row_miss ";
+
+// The suffix for the generated csv files.
+const std::string kFileNameSuffixMissRatioTimeline = "miss_ratio_timeline";
+const std::string kFileNameSuffixMissTimeline = "miss_timeline";
+const std::string kFileNameSuffixSkew = "skewness";
+const std::string kFileNameSuffixAccessTimeline = "access_timeline";
+const std::string kFileNameSuffixCorrelation = "correlation_input";
+const std::string kFileNameSuffixAvgReuseIntervalNaccesses =
+ "avg_reuse_interval_naccesses";
+const std::string kFileNameSuffixAvgReuseInterval = "avg_reuse_interval";
+const std::string kFileNameSuffixReuseInterval = "access_reuse_interval";
+const std::string kFileNameSuffixReuseLifetime = "reuse_lifetime";
+const std::string kFileNameSuffixAccessReuseBlocksTimeline =
+ "reuse_blocks_timeline";
+const std::string kFileNameSuffixPercentOfAccessSummary =
+ "percentage_of_accesses_summary";
+const std::string kFileNameSuffixPercentRefKeys = "percent_ref_keys";
+const std::string kFileNameSuffixPercentDataSizeOnRefKeys =
+ "percent_data_size_on_ref_keys";
+const std::string kFileNameSuffixPercentAccessesOnRefKeys =
+ "percent_accesses_on_ref_keys";
+const std::string kFileNameSuffixAccessCountSummary = "access_count_summary";
+
+std::string block_type_to_string(TraceType type) {
+ switch (type) {
+ case kBlockTraceFilterBlock:
+ return "Filter";
+ case kBlockTraceDataBlock:
+ return "Data";
+ case kBlockTraceIndexBlock:
+ return "Index";
+ case kBlockTraceRangeDeletionBlock:
+ return "RangeDeletion";
+ case kBlockTraceUncompressionDictBlock:
+ return "UncompressionDict";
+ default:
+ break;
+ }
+ // This cannot happen.
+ return "InvalidType";
+}
+
+std::string caller_to_string(TableReaderCaller caller) {
+ switch (caller) {
+ case kUserGet:
+ return "Get";
+ case kUserMultiGet:
+ return "MultiGet";
+ case kUserIterator:
+ return "Iterator";
+ case kUserApproximateSize:
+ return "ApproximateSize";
+ case kUserVerifyChecksum:
+ return "VerifyChecksum";
+ case kSSTDumpTool:
+ return "SSTDumpTool";
+ case kExternalSSTIngestion:
+ return "ExternalSSTIngestion";
+ case kRepair:
+ return "Repair";
+ case kPrefetch:
+ return "Prefetch";
+ case kCompaction:
+ return "Compaction";
+ case kCompactionRefill:
+ return "CompactionRefill";
+ case kFlush:
+ return "Flush";
+ case kSSTFileReader:
+ return "SSTFileReader";
+ case kUncategorized:
+ return "Uncategorized";
+ default:
+ break;
+ }
+ // This cannot happen.
+ return "InvalidCaller";
+}
+
+TableReaderCaller string_to_caller(std::string caller_str) {
+ if (caller_str == "Get") {
+ return kUserGet;
+ } else if (caller_str == "MultiGet") {
+ return kUserMultiGet;
+ } else if (caller_str == "Iterator") {
+ return kUserIterator;
+ } else if (caller_str == "ApproximateSize") {
+ return kUserApproximateSize;
+ } else if (caller_str == "VerifyChecksum") {
+ return kUserVerifyChecksum;
+ } else if (caller_str == "SSTDumpTool") {
+ return kSSTDumpTool;
+ } else if (caller_str == "ExternalSSTIngestion") {
+ return kExternalSSTIngestion;
+ } else if (caller_str == "Repair") {
+ return kRepair;
+ } else if (caller_str == "Prefetch") {
+ return kPrefetch;
+ } else if (caller_str == "Compaction") {
+ return kCompaction;
+ } else if (caller_str == "CompactionRefill") {
+ return kCompactionRefill;
+ } else if (caller_str == "Flush") {
+ return kFlush;
+ } else if (caller_str == "SSTFileReader") {
+ return kSSTFileReader;
+ } else if (caller_str == "Uncategorized") {
+ return kUncategorized;
+ }
+ return TableReaderCaller::kMaxBlockCacheLookupCaller;
+}
+
+bool is_user_access(TableReaderCaller caller) {
+ switch (caller) {
+ case kUserGet:
+ case kUserMultiGet:
+ case kUserIterator:
+ case kUserApproximateSize:
+ case kUserVerifyChecksum:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+const char kBreakLine[] =
+ "***************************************************************\n";
+
+void print_break_lines(uint32_t num_break_lines) {
+ for (uint32_t i = 0; i < num_break_lines; i++) {
+ fprintf(stdout, kBreakLine);
+ }
+}
+
+double percent(uint64_t numerator, uint64_t denomenator) {
+ if (denomenator == 0) {
+ return -1;
+ }
+ return static_cast<double>(numerator * 100.0 / denomenator);
+}
+
+std::map<uint64_t, uint64_t> adjust_time_unit(
+ const std::map<uint64_t, uint64_t>& time_stats, uint64_t time_unit) {
+ if (time_unit == 1) {
+ return time_stats;
+ }
+ std::map<uint64_t, uint64_t> adjusted_time_stats;
+ for (auto const& time : time_stats) {
+ adjusted_time_stats[static_cast<uint64_t>(time.first / time_unit)] +=
+ time.second;
+ }
+ return adjusted_time_stats;
+}
+} // namespace
+
+void BlockCacheTraceAnalyzer::WriteMissRatioCurves() const {
+ if (!cache_simulator_) {
+ return;
+ }
+ if (output_dir_.empty()) {
+ return;
+ }
+ uint64_t trace_duration =
+ trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
+ uint64_t total_accesses = access_sequence_number_;
+ const std::string output_miss_ratio_curve_path =
+ output_dir_ + "/" + std::to_string(trace_duration) + "_" +
+ std::to_string(total_accesses) + "_" + kMissRatioCurveFileName;
+ std::ofstream out(output_miss_ratio_curve_path);
+ if (!out.is_open()) {
+ return;
+ }
+ // Write header.
+ const std::string header =
+ "cache_name,num_shard_bits,ghost_capacity,capacity,miss_ratio,total_"
+ "accesses";
+ out << header << std::endl;
+ for (auto const& config_caches : cache_simulator_->sim_caches()) {
+ const CacheConfiguration& config = config_caches.first;
+ for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
+ double miss_ratio =
+ config_caches.second[i]->miss_ratio_stats().miss_ratio();
+ // Write the body.
+ out << config.cache_name;
+ out << ",";
+ out << config.num_shard_bits;
+ out << ",";
+ out << config.ghost_cache_capacity;
+ out << ",";
+ out << config.cache_capacities[i];
+ out << ",";
+ out << std::fixed << std::setprecision(4) << miss_ratio;
+ out << ",";
+ out << config_caches.second[i]->miss_ratio_stats().total_accesses();
+ out << std::endl;
+ }
+ }
+ out.close();
+}
+
+void BlockCacheTraceAnalyzer::UpdateFeatureVectors(
+ const std::vector<uint64_t>& access_sequence_number_timeline,
+ const std::vector<uint64_t>& access_timeline, const std::string& label,
+ std::map<std::string, Features>* label_features,
+ std::map<std::string, Predictions>* label_predictions) const {
+ if (access_sequence_number_timeline.empty() || access_timeline.empty()) {
+ return;
+ }
+ assert(access_timeline.size() == access_sequence_number_timeline.size());
+ uint64_t prev_access_sequence_number = access_sequence_number_timeline[0];
+ uint64_t prev_access_timestamp = access_timeline[0];
+ for (uint32_t i = 0; i < access_sequence_number_timeline.size(); i++) {
+ uint64_t num_accesses_since_last_access =
+ access_sequence_number_timeline[i] - prev_access_sequence_number;
+ uint64_t elapsed_time_since_last_access =
+ access_timeline[i] - prev_access_timestamp;
+ prev_access_sequence_number = access_sequence_number_timeline[i];
+ prev_access_timestamp = access_timeline[i];
+ if (i < access_sequence_number_timeline.size() - 1) {
+ (*label_features)[label].num_accesses_since_last_access.push_back(
+ num_accesses_since_last_access);
+ (*label_features)[label].num_past_accesses.push_back(i);
+ (*label_features)[label].elapsed_time_since_last_access.push_back(
+ elapsed_time_since_last_access);
+ }
+ if (i >= 1) {
+ (*label_predictions)[label].num_accesses_till_next_access.push_back(
+ num_accesses_since_last_access);
+ (*label_predictions)[label].elapsed_time_till_next_access.push_back(
+ elapsed_time_since_last_access);
+ }
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteMissRatioTimeline(uint64_t time_unit) const {
+ if (!cache_simulator_ || output_dir_.empty()) {
+ return;
+ }
+ std::map<uint64_t, std::map<std::string, std::map<uint64_t, double>>>
+ cs_name_timeline;
+ uint64_t start_time = port::kMaxUint64;
+ uint64_t end_time = 0;
+ const std::map<uint64_t, uint64_t>& trace_num_misses =
+ adjust_time_unit(miss_ratio_stats_.num_misses_timeline(), time_unit);
+ const std::map<uint64_t, uint64_t>& trace_num_accesses =
+ adjust_time_unit(miss_ratio_stats_.num_accesses_timeline(), time_unit);
+ assert(trace_num_misses.size() == trace_num_accesses.size());
+ for (auto const& num_miss : trace_num_misses) {
+ uint64_t time = num_miss.first;
+ start_time = std::min(start_time, time);
+ end_time = std::max(end_time, time);
+ uint64_t miss = num_miss.second;
+ auto it = trace_num_accesses.find(time);
+ assert(it != trace_num_accesses.end());
+ uint64_t access = it->second;
+ cs_name_timeline[port::kMaxUint64]["trace"][time] = percent(miss, access);
+ }
+ for (auto const& config_caches : cache_simulator_->sim_caches()) {
+ const CacheConfiguration& config = config_caches.first;
+ std::string cache_label = config.cache_name + "-" +
+ std::to_string(config.num_shard_bits) + "-" +
+ std::to_string(config.ghost_cache_capacity);
+ for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
+ const std::map<uint64_t, uint64_t>& num_misses = adjust_time_unit(
+ config_caches.second[i]->miss_ratio_stats().num_misses_timeline(),
+ time_unit);
+ const std::map<uint64_t, uint64_t>& num_accesses = adjust_time_unit(
+ config_caches.second[i]->miss_ratio_stats().num_accesses_timeline(),
+ time_unit);
+ assert(num_misses.size() == num_accesses.size());
+ for (auto const& num_miss : num_misses) {
+ uint64_t time = num_miss.first;
+ start_time = std::min(start_time, time);
+ end_time = std::max(end_time, time);
+ uint64_t miss = num_miss.second;
+ auto it = num_accesses.find(time);
+ assert(it != num_accesses.end());
+ uint64_t access = it->second;
+ cs_name_timeline[config.cache_capacities[i]][cache_label][time] =
+ percent(miss, access);
+ }
+ }
+ }
+ for (auto const& it : cs_name_timeline) {
+ const std::string output_miss_ratio_timeline_path =
+ output_dir_ + "/" + std::to_string(it.first) + "_" +
+ std::to_string(time_unit) + "_" + kFileNameSuffixMissRatioTimeline;
+ std::ofstream out(output_miss_ratio_timeline_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("time");
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ header += ",";
+ header += std::to_string(now);
+ }
+ out << header << std::endl;
+ for (auto const& label : it.second) {
+ std::string row(label.first);
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ auto misses = label.second.find(now);
+ row += ",";
+ if (misses != label.second.end()) {
+ row += std::to_string(misses->second);
+ } else {
+ row += "0";
+ }
+ }
+ out << row << std::endl;
+ }
+ out.close();
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteMissTimeline(uint64_t time_unit) const {
+ if (!cache_simulator_ || output_dir_.empty()) {
+ return;
+ }
+ std::map<uint64_t, std::map<std::string, std::map<uint64_t, uint64_t>>>
+ cs_name_timeline;
+ uint64_t start_time = port::kMaxUint64;
+ uint64_t end_time = 0;
+ const std::map<uint64_t, uint64_t>& trace_num_misses =
+ adjust_time_unit(miss_ratio_stats_.num_misses_timeline(), time_unit);
+ for (auto const& num_miss : trace_num_misses) {
+ uint64_t time = num_miss.first;
+ start_time = std::min(start_time, time);
+ end_time = std::max(end_time, time);
+ uint64_t miss = num_miss.second;
+ cs_name_timeline[port::kMaxUint64]["trace"][time] = miss;
+ }
+ for (auto const& config_caches : cache_simulator_->sim_caches()) {
+ const CacheConfiguration& config = config_caches.first;
+ std::string cache_label = config.cache_name + "-" +
+ std::to_string(config.num_shard_bits) + "-" +
+ std::to_string(config.ghost_cache_capacity);
+ for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
+ const std::map<uint64_t, uint64_t>& num_misses = adjust_time_unit(
+ config_caches.second[i]->miss_ratio_stats().num_misses_timeline(),
+ time_unit);
+ for (auto const& num_miss : num_misses) {
+ uint64_t time = num_miss.first;
+ start_time = std::min(start_time, time);
+ end_time = std::max(end_time, time);
+ uint64_t miss = num_miss.second;
+ cs_name_timeline[config.cache_capacities[i]][cache_label][time] = miss;
+ }
+ }
+ }
+ for (auto const& it : cs_name_timeline) {
+ const std::string output_miss_ratio_timeline_path =
+ output_dir_ + "/" + std::to_string(it.first) + "_" +
+ std::to_string(time_unit) + "_" + kFileNameSuffixMissTimeline;
+ std::ofstream out(output_miss_ratio_timeline_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("time");
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ header += ",";
+ header += std::to_string(now);
+ }
+ out << header << std::endl;
+ for (auto const& label : it.second) {
+ std::string row(label.first);
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ auto misses = label.second.find(now);
+ row += ",";
+ if (misses != label.second.end()) {
+ row += std::to_string(misses->second);
+ } else {
+ row += "0";
+ }
+ }
+ out << row << std::endl;
+ }
+ out.close();
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteSkewness(
+ const std::string& label_str, const std::vector<uint64_t>& percent_buckets,
+ TraceType target_block_type) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, uint64_t> label_naccesses;
+ uint64_t total_naccesses = 0;
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ if (target_block_type != TraceType::kTraceMax &&
+ target_block_type != type) {
+ return;
+ }
+ const std::string label = BuildLabel(
+ labels, cf_name, fd, level, type,
+ TableReaderCaller::kMaxBlockCacheLookupCaller, block_id, block);
+ label_naccesses[label] += block.num_accesses;
+ total_naccesses += block.num_accesses;
+ };
+ TraverseBlocks(block_callback, &labels);
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_bucket_naccesses;
+ std::vector<std::pair<std::string, uint64_t>> pairs;
+ for (auto const& itr : label_naccesses) {
+ pairs.push_back(itr);
+ }
+ // Sort in descending order.
+ sort(pairs.begin(), pairs.end(),
+ [=](const std::pair<std::string, uint64_t>& a,
+ const std::pair<std::string, uint64_t>& b) {
+ return b.second < a.second;
+ });
+
+ size_t prev_start_index = 0;
+ for (auto const& percent : percent_buckets) {
+ label_bucket_naccesses[label_str][percent] = 0;
+ size_t end_index = 0;
+ if (percent == port::kMaxUint64) {
+ end_index = label_naccesses.size();
+ } else {
+ end_index = percent * label_naccesses.size() / 100;
+ }
+ for (size_t i = prev_start_index; i < end_index; i++) {
+ label_bucket_naccesses[label_str][percent] += pairs[i].second;
+ }
+ prev_start_index = end_index;
+ }
+ std::string filename_suffix;
+ if (target_block_type != TraceType::kTraceMax) {
+ filename_suffix = block_type_to_string(target_block_type);
+ filename_suffix += "_";
+ }
+ filename_suffix += kFileNameSuffixSkew;
+ WriteStatsToFile(label_str, percent_buckets, filename_suffix,
+ label_bucket_naccesses, total_naccesses);
+}
+
+void BlockCacheTraceAnalyzer::WriteCorrelationFeatures(
+ const std::string& label_str, uint32_t max_number_of_values) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, Features> label_features;
+ std::map<std::string, Predictions> label_predictions;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t fd, uint32_t level,
+ TraceType block_type, const std::string& /*block_key*/,
+ uint64_t /*block_key_id*/, const BlockAccessInfo& block) {
+ if (block.table_id == 0 && labels.find(kGroupbyTable) != labels.end()) {
+ // We only know table id information for get requests.
+ return;
+ }
+ if (labels.find(kGroupbyCaller) != labels.end()) {
+ // Group by caller.
+ for (auto const& caller_map : block.caller_access_timeline) {
+ const std::string label =
+ BuildLabel(labels, cf_name, fd, level, block_type,
+ caller_map.first, /*block_id=*/0, block);
+ auto it = block.caller_access_sequence__number_timeline.find(
+ caller_map.first);
+ assert(it != block.caller_access_sequence__number_timeline.end());
+ UpdateFeatureVectors(it->second, caller_map.second, label,
+ &label_features, &label_predictions);
+ }
+ return;
+ }
+ const std::string label =
+ BuildLabel(labels, cf_name, fd, level, block_type,
+ TableReaderCaller::kMaxBlockCacheLookupCaller,
+ /*block_id=*/0, block);
+ UpdateFeatureVectors(block.access_sequence_number_timeline,
+ block.access_timeline, label, &label_features,
+ &label_predictions);
+ };
+ TraverseBlocks(block_callback, &labels);
+ WriteCorrelationFeaturesToFile(label_str, label_features, label_predictions,
+ max_number_of_values);
+}
+
+void BlockCacheTraceAnalyzer::WriteCorrelationFeaturesToFile(
+ const std::string& label,
+ const std::map<std::string, Features>& label_features,
+ const std::map<std::string, Predictions>& label_predictions,
+ uint32_t max_number_of_values) const {
+ std::default_random_engine rand_engine(static_cast<std::default_random_engine::result_type>(env_->NowMicros()));
+ for (auto const& label_feature_vectors : label_features) {
+ const Features& past = label_feature_vectors.second;
+ auto it = label_predictions.find(label_feature_vectors.first);
+ assert(it != label_predictions.end());
+ const Predictions& future = it->second;
+ const std::string output_path = output_dir_ + "/" + label + "_" +
+ label_feature_vectors.first + "_" +
+ kFileNameSuffixCorrelation;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header(
+ "num_accesses_since_last_access,elapsed_time_since_last_access,num_"
+ "past_accesses,num_accesses_till_next_access,elapsed_time_till_next_"
+ "access");
+ out << header << std::endl;
+ std::vector<uint32_t> indexes;
+ for (uint32_t i = 0; i < past.num_accesses_since_last_access.size(); i++) {
+ indexes.push_back(i);
+ }
+ std::shuffle(indexes.begin(), indexes.end(), rand_engine);
+ for (uint32_t i = 0; i < max_number_of_values && i < indexes.size(); i++) {
+ uint32_t rand_index = indexes[i];
+ out << std::to_string(past.num_accesses_since_last_access[rand_index])
+ << ",";
+ out << std::to_string(past.elapsed_time_since_last_access[rand_index])
+ << ",";
+ out << std::to_string(past.num_past_accesses[rand_index]) << ",";
+ out << std::to_string(future.num_accesses_till_next_access[rand_index])
+ << ",";
+ out << std::to_string(future.elapsed_time_till_next_access[rand_index])
+ << std::endl;
+ }
+ out.close();
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteCorrelationFeaturesForGet(
+ uint32_t max_number_of_values) const {
+ std::string label = "GetKeyInfo";
+ std::map<std::string, Features> label_features;
+ std::map<std::string, Predictions> label_predictions;
+ for (auto const& get_info : get_key_info_map_) {
+ const GetKeyInfo& info = get_info.second;
+ UpdateFeatureVectors(info.access_sequence_number_timeline,
+ info.access_timeline, label, &label_features,
+ &label_predictions);
+ }
+ WriteCorrelationFeaturesToFile(label, label_features, label_predictions,
+ max_number_of_values);
+}
+
+std::set<std::string> BlockCacheTraceAnalyzer::ParseLabelStr(
+ const std::string& label_str) const {
+ std::stringstream ss(label_str);
+ std::set<std::string> labels;
+ // label_str is in the form of "label1_label2_label3", e.g., cf_bt.
+ while (ss.good()) {
+ std::string label_name;
+ getline(ss, label_name, '_');
+ if (kGroupbyLabels.find(label_name) == kGroupbyLabels.end()) {
+ // Unknown label name.
+ fprintf(stderr, "Unknown label name %s, label string %s\n",
+ label_name.c_str(), label_str.c_str());
+ return {};
+ }
+ labels.insert(label_name);
+ }
+ return labels;
+}
+
+std::string BlockCacheTraceAnalyzer::BuildLabel(
+ const std::set<std::string>& labels, const std::string& cf_name,
+ uint64_t fd, uint32_t level, TraceType type, TableReaderCaller caller,
+ uint64_t block_key, const BlockAccessInfo& block) const {
+ std::map<std::string, std::string> label_value_map;
+ label_value_map[kGroupbyAll] = kGroupbyAll;
+ label_value_map[kGroupbyLevel] = std::to_string(level);
+ label_value_map[kGroupbyCaller] = caller_to_string(caller);
+ label_value_map[kGroupbySSTFile] = std::to_string(fd);
+ label_value_map[kGroupbyBlockType] = block_type_to_string(type);
+ label_value_map[kGroupbyColumnFamily] = cf_name;
+ label_value_map[kGroupbyBlock] = std::to_string(block_key);
+ label_value_map[kGroupbyTable] = std::to_string(block.table_id);
+ // Concatenate the label values.
+ std::string label;
+ for (auto const& l : labels) {
+ label += label_value_map[l];
+ label += "-";
+ }
+ if (!label.empty()) {
+ label.pop_back();
+ }
+ return label;
+}
+
+void BlockCacheTraceAnalyzer::TraverseBlocks(
+ std::function<void(const std::string& /*cf_name*/, uint64_t /*fd*/,
+ uint32_t /*level*/, TraceType /*block_type*/,
+ const std::string& /*block_key*/,
+ uint64_t /*block_key_id*/,
+ const BlockAccessInfo& /*block_access_info*/)>
+ block_callback,
+ std::set<std::string>* labels) const {
+ for (auto const& cf_aggregates : cf_aggregates_map_) {
+ // Stats per column family.
+ const std::string& cf_name = cf_aggregates.first;
+ for (auto const& file_aggregates : cf_aggregates.second.fd_aggregates_map) {
+ // Stats per SST file.
+ const uint64_t fd = file_aggregates.first;
+ const uint32_t level = file_aggregates.second.level;
+ for (auto const& block_type_aggregates :
+ file_aggregates.second.block_type_aggregates_map) {
+ // Stats per block type.
+ const TraceType type = block_type_aggregates.first;
+ for (auto const& block_access_info :
+ block_type_aggregates.second.block_access_info_map) {
+ // Stats per block.
+ if (labels && block_access_info.second.table_id == 0 &&
+ labels->find(kGroupbyTable) != labels->end()) {
+ // We only know table id information for get requests.
+ return;
+ }
+ block_callback(cf_name, fd, level, type, block_access_info.first,
+ block_access_info.second.block_id,
+ block_access_info.second);
+ }
+ }
+ }
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteGetSpatialLocality(
+ const std::string& label_str,
+ const std::vector<uint64_t>& percent_buckets) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_pnrefkeys_nblocks;
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_pnrefs_nblocks;
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_pndatasize_nblocks;
+ uint64_t nblocks = 0;
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType /*block_type*/,
+ const std::string& /*block_key*/,
+ uint64_t /*block_key_id*/,
+ const BlockAccessInfo& block) {
+ if (block.num_keys == 0) {
+ return;
+ }
+ uint64_t naccesses = 0;
+ for (auto const& key_access : block.key_num_access_map) {
+ for (auto const& caller_access : key_access.second) {
+ if (caller_access.first == TableReaderCaller::kUserGet) {
+ naccesses += caller_access.second;
+ }
+ }
+ }
+ const std::string label =
+ BuildLabel(labels, cf_name, fd, level, TraceType::kBlockTraceDataBlock,
+ TableReaderCaller::kUserGet, /*block_id=*/0, block);
+
+ const uint64_t percent_referenced_for_existing_keys =
+ static_cast<uint64_t>(std::max(
+ percent(block.key_num_access_map.size(), block.num_keys), 0.0));
+ const uint64_t percent_accesses_for_existing_keys =
+ static_cast<uint64_t>(std::max(
+ percent(block.num_referenced_key_exist_in_block, naccesses), 0.0));
+ const uint64_t percent_referenced_data_size = static_cast<uint64_t>(
+ std::max(percent(block.referenced_data_size, block.block_size), 0.0));
+ if (label_pnrefkeys_nblocks.find(label) == label_pnrefkeys_nblocks.end()) {
+ for (auto const& percent_bucket : percent_buckets) {
+ label_pnrefkeys_nblocks[label][percent_bucket] = 0;
+ label_pnrefs_nblocks[label][percent_bucket] = 0;
+ label_pndatasize_nblocks[label][percent_bucket] = 0;
+ }
+ }
+ label_pnrefkeys_nblocks[label]
+ .upper_bound(percent_referenced_for_existing_keys)
+ ->second += 1;
+ label_pnrefs_nblocks[label]
+ .upper_bound(percent_accesses_for_existing_keys)
+ ->second += 1;
+ label_pndatasize_nblocks[label]
+ .upper_bound(percent_referenced_data_size)
+ ->second += 1;
+ nblocks += 1;
+ };
+ TraverseBlocks(block_callback, &labels);
+ WriteStatsToFile(label_str, percent_buckets, kFileNameSuffixPercentRefKeys,
+ label_pnrefkeys_nblocks, nblocks);
+ WriteStatsToFile(label_str, percent_buckets,
+ kFileNameSuffixPercentAccessesOnRefKeys,
+ label_pnrefs_nblocks, nblocks);
+ WriteStatsToFile(label_str, percent_buckets,
+ kFileNameSuffixPercentDataSizeOnRefKeys,
+ label_pndatasize_nblocks, nblocks);
+}
+
+void BlockCacheTraceAnalyzer::WriteAccessTimeline(const std::string& label_str,
+ uint64_t time_unit,
+ bool user_access_only) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ uint64_t start_time = port::kMaxUint64;
+ uint64_t end_time = 0;
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_access_timeline;
+ std::map<uint64_t, std::vector<std::string>> access_count_block_id_map;
+
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ uint64_t naccesses = 0;
+ for (auto const& timeline : block.caller_num_accesses_timeline) {
+ const TableReaderCaller caller = timeline.first;
+ if (user_access_only && !is_user_access(caller)) {
+ continue;
+ }
+ const std::string label =
+ BuildLabel(labels, cf_name, fd, level, type, caller, block_id, block);
+ for (auto const& naccess : timeline.second) {
+ const uint64_t timestamp = naccess.first / time_unit;
+ const uint64_t num = naccess.second;
+ label_access_timeline[label][timestamp] += num;
+ start_time = std::min(start_time, timestamp);
+ end_time = std::max(end_time, timestamp);
+ naccesses += num;
+ }
+ }
+ if (naccesses > 0) {
+ access_count_block_id_map[naccesses].push_back(std::to_string(block_id));
+ }
+ };
+ TraverseBlocks(block_callback, &labels);
+
+ // We have label_access_timeline now. Write them into a file.
+ const std::string user_access_prefix =
+ user_access_only ? "user_access_only_" : "all_access_";
+ const std::string output_path = output_dir_ + "/" + user_access_prefix +
+ label_str + "_" + std::to_string(time_unit) +
+ "_" + kFileNameSuffixAccessTimeline;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("time");
+ if (labels.find("block") != labels.end()) {
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ header += ",";
+ header += std::to_string(now);
+ }
+ out << header << std::endl;
+ // Write the most frequently accessed blocks first.
+ for (auto naccess_it = access_count_block_id_map.rbegin();
+ naccess_it != access_count_block_id_map.rend(); naccess_it++) {
+ for (auto& block_id_it : naccess_it->second) {
+ std::string row(block_id_it);
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ auto it = label_access_timeline[block_id_it].find(now);
+ row += ",";
+ if (it != label_access_timeline[block_id_it].end()) {
+ row += std::to_string(it->second);
+ } else {
+ row += "0";
+ }
+ }
+ out << row << std::endl;
+ }
+ }
+ out.close();
+ return;
+ }
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ header += ",";
+ header += std::to_string(now);
+ }
+ out << header << std::endl;
+ for (auto const& label : label_access_timeline) {
+ std::string row(label.first);
+ for (uint64_t now = start_time; now <= end_time; now++) {
+ auto it = label.second.find(now);
+ row += ",";
+ if (it != label.second.end()) {
+ row += std::to_string(it->second);
+ } else {
+ row += "0";
+ }
+ }
+ out << row << std::endl;
+ }
+
+ out.close();
+}
+
+void BlockCacheTraceAnalyzer::WriteReuseDistance(
+ const std::string& label_str,
+ const std::vector<uint64_t>& distance_buckets) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_distance_num_reuses;
+ uint64_t total_num_reuses = 0;
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ const std::string label = BuildLabel(
+ labels, cf_name, fd, level, type,
+ TableReaderCaller::kMaxBlockCacheLookupCaller, block_id, block);
+ if (label_distance_num_reuses.find(label) ==
+ label_distance_num_reuses.end()) {
+ // The first time we encounter this label.
+ for (auto const& distance_bucket : distance_buckets) {
+ label_distance_num_reuses[label][distance_bucket] = 0;
+ }
+ }
+ for (auto const& reuse_distance : block.reuse_distance_count) {
+ label_distance_num_reuses[label]
+ .upper_bound(reuse_distance.first)
+ ->second += reuse_distance.second;
+ total_num_reuses += reuse_distance.second;
+ }
+ };
+ TraverseBlocks(block_callback, &labels);
+ // We have label_naccesses and label_distance_num_reuses now. Write them into
+ // a file.
+ const std::string output_path =
+ output_dir_ + "/" + label_str + "_reuse_distance";
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("bucket");
+ for (auto const& label_it : label_distance_num_reuses) {
+ header += ",";
+ header += label_it.first;
+ }
+ out << header << std::endl;
+ for (auto const& bucket : distance_buckets) {
+ std::string row(std::to_string(bucket));
+ for (auto const& label_it : label_distance_num_reuses) {
+ auto const& it = label_it.second.find(bucket);
+ assert(it != label_it.second.end());
+ row += ",";
+ row += std::to_string(percent(it->second, total_num_reuses));
+ }
+ out << row << std::endl;
+ }
+ out.close();
+}
+
+void BlockCacheTraceAnalyzer::UpdateReuseIntervalStats(
+ const std::string& label, const std::vector<uint64_t>& time_buckets,
+ const std::map<uint64_t, uint64_t> timeline,
+ std::map<std::string, std::map<uint64_t, uint64_t>>* label_time_num_reuses,
+ uint64_t* total_num_reuses) const {
+ assert(label_time_num_reuses);
+ assert(total_num_reuses);
+ if (label_time_num_reuses->find(label) == label_time_num_reuses->end()) {
+ // The first time we encounter this label.
+ for (auto const& time_bucket : time_buckets) {
+ (*label_time_num_reuses)[label][time_bucket] = 0;
+ }
+ }
+ auto it = timeline.begin();
+ uint64_t prev_timestamp = it->first;
+ const uint64_t prev_num = it->second;
+ it++;
+ // Reused within one second.
+ if (prev_num > 1) {
+ (*label_time_num_reuses)[label].upper_bound(0)->second += prev_num - 1;
+ *total_num_reuses += prev_num - 1;
+ }
+ while (it != timeline.end()) {
+ const uint64_t timestamp = it->first;
+ const uint64_t num = it->second;
+ const uint64_t reuse_interval = timestamp - prev_timestamp;
+ (*label_time_num_reuses)[label].upper_bound(reuse_interval)->second += 1;
+ if (num > 1) {
+ (*label_time_num_reuses)[label].upper_bound(0)->second += num - 1;
+ }
+ prev_timestamp = timestamp;
+ *total_num_reuses += num;
+ it++;
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteStatsToFile(
+ const std::string& label_str, const std::vector<uint64_t>& time_buckets,
+ const std::string& filename_suffix,
+ const std::map<std::string, std::map<uint64_t, uint64_t>>& label_data,
+ uint64_t ntotal) const {
+ const std::string output_path =
+ output_dir_ + "/" + label_str + "_" + filename_suffix;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("bucket");
+ for (auto const& label_it : label_data) {
+ header += ",";
+ header += label_it.first;
+ }
+ out << header << std::endl;
+ for (auto const& bucket : time_buckets) {
+ std::string row(std::to_string(bucket));
+ for (auto const& label_it : label_data) {
+ auto const& it = label_it.second.find(bucket);
+ assert(it != label_it.second.end());
+ row += ",";
+ row += std::to_string(percent(it->second, ntotal));
+ }
+ out << row << std::endl;
+ }
+ out.close();
+}
+
+void BlockCacheTraceAnalyzer::WriteReuseInterval(
+ const std::string& label_str,
+ const std::vector<uint64_t>& time_buckets) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_time_num_reuses;
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_avg_reuse_nblocks;
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_avg_reuse_naccesses;
+
+ uint64_t total_num_reuses = 0;
+ uint64_t total_nblocks = 0;
+ uint64_t total_accesses = 0;
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ total_nblocks++;
+ total_accesses += block.num_accesses;
+ uint64_t avg_reuse_interval = 0;
+ if (block.num_accesses > 1) {
+ avg_reuse_interval = ((block.last_access_time - block.first_access_time) /
+ kMicrosInSecond) /
+ block.num_accesses;
+ } else {
+ avg_reuse_interval = port::kMaxUint64 - 1;
+ }
+ if (labels.find(kGroupbyCaller) != labels.end()) {
+ for (auto const& timeline : block.caller_num_accesses_timeline) {
+ const TableReaderCaller caller = timeline.first;
+ const std::string label = BuildLabel(labels, cf_name, fd, level, type,
+ caller, block_id, block);
+ UpdateReuseIntervalStats(label, time_buckets, timeline.second,
+ &label_time_num_reuses, &total_num_reuses);
+ }
+ return;
+ }
+ // Does not group by caller so we need to flatten the access timeline.
+ const std::string label = BuildLabel(
+ labels, cf_name, fd, level, type,
+ TableReaderCaller::kMaxBlockCacheLookupCaller, block_id, block);
+ std::map<uint64_t, uint64_t> timeline;
+ for (auto const& caller_timeline : block.caller_num_accesses_timeline) {
+ for (auto const& time_naccess : caller_timeline.second) {
+ timeline[time_naccess.first] += time_naccess.second;
+ }
+ }
+ UpdateReuseIntervalStats(label, time_buckets, timeline,
+ &label_time_num_reuses, &total_num_reuses);
+ if (label_avg_reuse_nblocks.find(label) == label_avg_reuse_nblocks.end()) {
+ for (auto const& time_bucket : time_buckets) {
+ label_avg_reuse_nblocks[label][time_bucket] = 0;
+ label_avg_reuse_naccesses[label][time_bucket] = 0;
+ }
+ }
+ label_avg_reuse_nblocks[label].upper_bound(avg_reuse_interval)->second += 1;
+ label_avg_reuse_naccesses[label].upper_bound(avg_reuse_interval)->second +=
+ block.num_accesses;
+ };
+ TraverseBlocks(block_callback, &labels);
+
+ // Write the stats into files.
+ WriteStatsToFile(label_str, time_buckets, kFileNameSuffixReuseInterval,
+ label_time_num_reuses, total_num_reuses);
+ WriteStatsToFile(label_str, time_buckets, kFileNameSuffixAvgReuseInterval,
+ label_avg_reuse_nblocks, total_nblocks);
+ WriteStatsToFile(label_str, time_buckets,
+ kFileNameSuffixAvgReuseIntervalNaccesses,
+ label_avg_reuse_naccesses, total_accesses);
+}
+
+void BlockCacheTraceAnalyzer::WriteReuseLifetime(
+ const std::string& label_str,
+ const std::vector<uint64_t>& time_buckets) const {
+ std::set<std::string> labels = ParseLabelStr(label_str);
+ std::map<std::string, std::map<uint64_t, uint64_t>> label_lifetime_nblocks;
+ uint64_t total_nblocks = 0;
+ auto block_callback = [&](const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ uint64_t lifetime = 0;
+ if (block.num_accesses > 1) {
+ lifetime =
+ (block.last_access_time - block.first_access_time) / kMicrosInSecond;
+ } else {
+ lifetime = port::kMaxUint64 - 1;
+ }
+ const std::string label = BuildLabel(
+ labels, cf_name, fd, level, type,
+ TableReaderCaller::kMaxBlockCacheLookupCaller, block_id, block);
+
+ if (label_lifetime_nblocks.find(label) == label_lifetime_nblocks.end()) {
+ // The first time we encounter this label.
+ for (auto const& time_bucket : time_buckets) {
+ label_lifetime_nblocks[label][time_bucket] = 0;
+ }
+ }
+ label_lifetime_nblocks[label].upper_bound(lifetime)->second += 1;
+ total_nblocks += 1;
+ };
+ TraverseBlocks(block_callback, &labels);
+ WriteStatsToFile(label_str, time_buckets, kFileNameSuffixReuseLifetime,
+ label_lifetime_nblocks, total_nblocks);
+}
+
+void BlockCacheTraceAnalyzer::WriteBlockReuseTimeline(
+ const uint64_t reuse_window, bool user_access_only, TraceType block_type) const {
+ // A map from block key to an array of bools that states whether a block is
+ // accessed in a time window.
+ std::map<uint64_t, std::vector<bool>> block_accessed;
+ const uint64_t trace_duration =
+ trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
+ const uint64_t reuse_vector_size = (trace_duration / reuse_window);
+ if (reuse_vector_size < 2) {
+ // The reuse window is less than 2. We cannot calculate the reused
+ // percentage of blocks.
+ return;
+ }
+ auto block_callback = [&](const std::string& /*cf_name*/, uint64_t /*fd*/,
+ uint32_t /*level*/, TraceType /*type*/,
+ const std::string& /*block_key*/, uint64_t block_id,
+ const BlockAccessInfo& block) {
+ if (block_accessed.find(block_id) == block_accessed.end()) {
+ block_accessed[block_id].resize(reuse_vector_size);
+ for (uint64_t i = 0; i < reuse_vector_size; i++) {
+ block_accessed[block_id][i] = false;
+ }
+ }
+ for (auto const& caller_num : block.caller_num_accesses_timeline) {
+ const TableReaderCaller caller = caller_num.first;
+ for (auto const& timeline : caller_num.second) {
+ const uint64_t timestamp = timeline.first;
+ const uint64_t elapsed_time =
+ timestamp - trace_start_timestamp_in_seconds_;
+ if (!user_access_only || is_user_access(caller)) {
+ uint64_t index =
+ std::min(elapsed_time / reuse_window, reuse_vector_size - 1);
+ block_accessed[block_id][index] = true;
+ }
+ }
+ }
+ };
+ TraverseBlocks(block_callback);
+
+ // A cell is the number of blocks accessed in a reuse window.
+ std::unique_ptr<uint64_t[]> reuse_table(new uint64_t[reuse_vector_size * reuse_vector_size]);
+ for (uint64_t start_time = 0; start_time < reuse_vector_size; start_time++) {
+ // Initialize the reuse_table.
+ for (uint64_t i = 0; i < reuse_vector_size; i++) {
+ reuse_table[start_time * reuse_vector_size + i] = 0;
+ }
+ // Examine all blocks.
+ for (auto const& block : block_accessed) {
+ for (uint64_t i = start_time; i < reuse_vector_size; i++) {
+ if (block.second[start_time] && block.second[i]) {
+ // This block is accessed at start time and at the current time. We
+ // increment reuse_table[start_time][i] since it is reused at the ith
+ // window.
+ reuse_table[start_time * reuse_vector_size + i]++;
+ }
+ }
+ }
+ }
+ const std::string user_access_prefix =
+ user_access_only ? "_user_access_only_" : "_all_access_";
+ const std::string output_path =
+ output_dir_ + "/" + block_type_to_string(block_type) +
+ user_access_prefix + std::to_string(reuse_window) + "_" +
+ kFileNameSuffixAccessReuseBlocksTimeline;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("start_time");
+ for (uint64_t start_time = 0; start_time < reuse_vector_size; start_time++) {
+ header += ",";
+ header += std::to_string(start_time);
+ }
+ out << header << std::endl;
+ for (uint64_t start_time = 0; start_time < reuse_vector_size; start_time++) {
+ std::string row(std::to_string(start_time * reuse_window));
+ for (uint64_t j = 0; j < reuse_vector_size; j++) {
+ row += ",";
+ if (j < start_time) {
+ row += "100.0";
+ } else {
+ row += std::to_string(percent(reuse_table[start_time * reuse_vector_size + j],
+ reuse_table[start_time * reuse_vector_size + start_time]));
+ }
+ }
+ out << row << std::endl;
+ }
+ out.close();
+}
+
+std::string BlockCacheTraceAnalyzer::OutputPercentAccessStats(
+ uint64_t total_accesses,
+ const std::map<std::string, uint64_t>& cf_access_count) const {
+ std::string row;
+ for (auto const& cf_aggregates : cf_aggregates_map_) {
+ const std::string& cf_name = cf_aggregates.first;
+ const auto& naccess = cf_access_count.find(cf_name);
+ row += ",";
+ if (naccess != cf_access_count.end()) {
+ row += std::to_string(percent(naccess->second, total_accesses));
+ } else {
+ row += "0";
+ }
+ }
+ return row;
+}
+
+void BlockCacheTraceAnalyzer::WritePercentAccessSummaryStats() const {
+ std::map<TableReaderCaller, std::map<std::string, uint64_t>>
+ caller_cf_accesses;
+ uint64_t total_accesses = 0;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t /*fd*/, uint32_t /*level*/,
+ TraceType /*type*/, const std::string& /*block_key*/,
+ uint64_t /*block_id*/, const BlockAccessInfo& block) {
+ for (auto const& caller_num : block.caller_num_access_map) {
+ const TableReaderCaller caller = caller_num.first;
+ const uint64_t naccess = caller_num.second;
+ caller_cf_accesses[caller][cf_name] += naccess;
+ total_accesses += naccess;
+ }
+ };
+ TraverseBlocks(block_callback);
+
+ const std::string output_path =
+ output_dir_ + "/" + kFileNameSuffixPercentOfAccessSummary;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("caller");
+ for (auto const& cf_name : cf_aggregates_map_) {
+ header += ",";
+ header += cf_name.first;
+ }
+ out << header << std::endl;
+ for (auto const& cf_naccess_it : caller_cf_accesses) {
+ const TableReaderCaller caller = cf_naccess_it.first;
+ std::string row;
+ row += caller_to_string(caller);
+ row += OutputPercentAccessStats(total_accesses, cf_naccess_it.second);
+ out << row << std::endl;
+ }
+ out.close();
+}
+
+void BlockCacheTraceAnalyzer::WriteDetailedPercentAccessSummaryStats(
+ TableReaderCaller analyzing_caller) const {
+ std::map<uint32_t, std::map<std::string, uint64_t>> level_cf_accesses;
+ std::map<TraceType, std::map<std::string, uint64_t>> bt_cf_accesses;
+ uint64_t total_accesses = 0;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t /*fd*/, uint32_t level,
+ TraceType type, const std::string& /*block_key*/,
+ uint64_t /*block_id*/, const BlockAccessInfo& block) {
+ for (auto const& caller_num : block.caller_num_access_map) {
+ const TableReaderCaller caller = caller_num.first;
+ if (caller == analyzing_caller) {
+ const uint64_t naccess = caller_num.second;
+ level_cf_accesses[level][cf_name] += naccess;
+ bt_cf_accesses[type][cf_name] += naccess;
+ total_accesses += naccess;
+ }
+ }
+ };
+ TraverseBlocks(block_callback);
+ {
+ const std::string output_path =
+ output_dir_ + "/" + caller_to_string(analyzing_caller) + "_level_" +
+ kFileNameSuffixPercentOfAccessSummary;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("level");
+ for (auto const& cf_name : cf_aggregates_map_) {
+ header += ",";
+ header += cf_name.first;
+ }
+ out << header << std::endl;
+ for (auto const& level_naccess_it : level_cf_accesses) {
+ const uint32_t level = level_naccess_it.first;
+ std::string row;
+ row += std::to_string(level);
+ row += OutputPercentAccessStats(total_accesses, level_naccess_it.second);
+ out << row << std::endl;
+ }
+ out.close();
+ }
+ {
+ const std::string output_path =
+ output_dir_ + "/" + caller_to_string(analyzing_caller) + "_bt_" +
+ kFileNameSuffixPercentOfAccessSummary;
+ std::ofstream out(output_path);
+ if (!out.is_open()) {
+ return;
+ }
+ std::string header("bt");
+ for (auto const& cf_name : cf_aggregates_map_) {
+ header += ",";
+ header += cf_name.first;
+ }
+ out << header << std::endl;
+ for (auto const& bt_naccess_it : bt_cf_accesses) {
+ const TraceType bt = bt_naccess_it.first;
+ std::string row;
+ row += block_type_to_string(bt);
+ row += OutputPercentAccessStats(total_accesses, bt_naccess_it.second);
+ out << row << std::endl;
+ }
+ out.close();
+ }
+}
+
+void BlockCacheTraceAnalyzer::WriteAccessCountSummaryStats(
+ const std::vector<uint64_t>& access_count_buckets,
+ bool user_access_only) const {
+ // x: buckets.
+ // y: # of accesses.
+ std::map<std::string, std::map<uint64_t, uint64_t>> bt_access_nblocks;
+ std::map<std::string, std::map<uint64_t, uint64_t>> cf_access_nblocks;
+ uint64_t total_nblocks = 0;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t /*fd*/, uint32_t /*level*/,
+ TraceType type, const std::string& /*block_key*/,
+ uint64_t /*block_id*/, const BlockAccessInfo& block) {
+ const std::string type_str = block_type_to_string(type);
+ if (cf_access_nblocks.find(cf_name) == cf_access_nblocks.end()) {
+ // initialize.
+ for (auto& access : access_count_buckets) {
+ cf_access_nblocks[cf_name][access] = 0;
+ }
+ }
+ if (bt_access_nblocks.find(type_str) == bt_access_nblocks.end()) {
+ // initialize.
+ for (auto& access : access_count_buckets) {
+ bt_access_nblocks[type_str][access] = 0;
+ }
+ }
+ uint64_t naccesses = 0;
+ for (auto const& caller_access : block.caller_num_access_map) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ naccesses += caller_access.second;
+ }
+ }
+ if (naccesses == 0) {
+ return;
+ }
+ total_nblocks += 1;
+ bt_access_nblocks[type_str].upper_bound(naccesses)->second += 1;
+ cf_access_nblocks[cf_name].upper_bound(naccesses)->second += 1;
+ };
+ TraverseBlocks(block_callback);
+ const std::string user_access_prefix =
+ user_access_only ? "user_access_only_" : "all_access_";
+ WriteStatsToFile("cf", access_count_buckets,
+ user_access_prefix + kFileNameSuffixAccessCountSummary,
+ cf_access_nblocks, total_nblocks);
+ WriteStatsToFile("bt", access_count_buckets,
+ user_access_prefix + kFileNameSuffixAccessCountSummary,
+ bt_access_nblocks, total_nblocks);
+}
+
+BlockCacheTraceAnalyzer::BlockCacheTraceAnalyzer(
+ const std::string& trace_file_path, const std::string& output_dir,
+ const std::string& human_readable_trace_file_path,
+ bool compute_reuse_distance, bool mrc_only,
+ bool is_human_readable_trace_file,
+ std::unique_ptr<BlockCacheTraceSimulator>&& cache_simulator)
+ : env_(ROCKSDB_NAMESPACE::Env::Default()),
+ trace_file_path_(trace_file_path),
+ output_dir_(output_dir),
+ human_readable_trace_file_path_(human_readable_trace_file_path),
+ compute_reuse_distance_(compute_reuse_distance),
+ mrc_only_(mrc_only),
+ is_human_readable_trace_file_(is_human_readable_trace_file),
+ cache_simulator_(std::move(cache_simulator)) {}
+
+void BlockCacheTraceAnalyzer::ComputeReuseDistance(
+ BlockAccessInfo* info) const {
+ assert(info);
+ if (info->num_accesses == 0) {
+ return;
+ }
+ uint64_t reuse_distance = 0;
+ for (auto const& block_key : info->unique_blocks_since_last_access) {
+ auto const& it = block_info_map_.find(block_key);
+ // This block must exist.
+ assert(it != block_info_map_.end());
+ reuse_distance += it->second->block_size;
+ }
+ info->reuse_distance_count[reuse_distance] += 1;
+ // We clear this hash set since this is the second access on this block.
+ info->unique_blocks_since_last_access.clear();
+}
+
+Status BlockCacheTraceAnalyzer::RecordAccess(
+ const BlockCacheTraceRecord& access) {
+ ColumnFamilyAccessInfoAggregate& cf_aggr = cf_aggregates_map_[access.cf_name];
+ SSTFileAccessInfoAggregate& file_aggr =
+ cf_aggr.fd_aggregates_map[access.sst_fd_number];
+ file_aggr.level = access.level;
+ BlockTypeAccessInfoAggregate& block_type_aggr =
+ file_aggr.block_type_aggregates_map[access.block_type];
+ if (block_type_aggr.block_access_info_map.find(access.block_key) ==
+ block_type_aggr.block_access_info_map.end()) {
+ block_type_aggr.block_access_info_map[access.block_key].block_id =
+ unique_block_id_;
+ unique_block_id_++;
+ }
+ BlockAccessInfo& block_access_info =
+ block_type_aggr.block_access_info_map[access.block_key];
+ if (compute_reuse_distance_) {
+ ComputeReuseDistance(&block_access_info);
+ }
+ block_access_info.AddAccess(access, access_sequence_number_);
+ block_info_map_[access.block_key] = &block_access_info;
+ uint64_t get_key_id = 0;
+ if (access.caller == TableReaderCaller::kUserGet &&
+ access.get_id != BlockCacheTraceHelper::kReservedGetId) {
+ std::string user_key = ExtractUserKey(access.referenced_key).ToString();
+ if (get_key_info_map_.find(user_key) == get_key_info_map_.end()) {
+ get_key_info_map_[user_key].key_id = unique_get_key_id_;
+ unique_get_key_id_++;
+ }
+ get_key_id = get_key_info_map_[user_key].key_id;
+ get_key_info_map_[user_key].AddAccess(access, access_sequence_number_);
+ }
+
+ if (compute_reuse_distance_) {
+ // Add this block to all existing blocks.
+ for (auto& cf_aggregates : cf_aggregates_map_) {
+ for (auto& file_aggregates : cf_aggregates.second.fd_aggregates_map) {
+ for (auto& block_type_aggregates :
+ file_aggregates.second.block_type_aggregates_map) {
+ for (auto& existing_block :
+ block_type_aggregates.second.block_access_info_map) {
+ existing_block.second.unique_blocks_since_last_access.insert(
+ access.block_key);
+ }
+ }
+ }
+ }
+ }
+ return human_readable_trace_writer_.WriteHumanReadableTraceRecord(
+ access, block_access_info.block_id, get_key_id);
+}
+
+Status BlockCacheTraceAnalyzer::Analyze() {
+ std::unique_ptr<BlockCacheTraceReader> reader;
+ Status s = Status::OK();
+ if (is_human_readable_trace_file_) {
+ reader.reset(new BlockCacheHumanReadableTraceReader(trace_file_path_));
+ } else {
+ std::unique_ptr<TraceReader> trace_reader;
+ s = NewFileTraceReader(env_, EnvOptions(), trace_file_path_, &trace_reader);
+ if (!s.ok()) {
+ return s;
+ }
+ reader.reset(new BlockCacheTraceReader(std::move(trace_reader)));
+ s = reader->ReadHeader(&header_);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ if (!human_readable_trace_file_path_.empty()) {
+ s = human_readable_trace_writer_.NewWritableFile(
+ human_readable_trace_file_path_, env_);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ uint64_t start = env_->NowMicros();
+ uint64_t time_interval = 0;
+ while (s.ok()) {
+ BlockCacheTraceRecord access;
+ s = reader->ReadAccess(&access);
+ if (!s.ok()) {
+ break;
+ }
+ if (!mrc_only_) {
+ s = RecordAccess(access);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ if (trace_start_timestamp_in_seconds_ == 0) {
+ trace_start_timestamp_in_seconds_ =
+ access.access_timestamp / kMicrosInSecond;
+ }
+ trace_end_timestamp_in_seconds_ = access.access_timestamp / kMicrosInSecond;
+ miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
+ is_user_access(access.caller),
+ access.is_cache_hit == Boolean::kFalse);
+ if (cache_simulator_) {
+ cache_simulator_->Access(access);
+ }
+ access_sequence_number_++;
+ uint64_t now = env_->NowMicros();
+ uint64_t duration = (now - start) / kMicrosInSecond;
+ if (duration > 10 * time_interval) {
+ uint64_t trace_duration =
+ trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
+ fprintf(stdout,
+ "Running for %" PRIu64 " seconds: Processed %" PRIu64
+ " records/second. Trace duration %" PRIu64
+ " seconds. Observed miss ratio %.2f\n",
+ duration, duration > 0 ? access_sequence_number_ / duration : 0,
+ trace_duration, miss_ratio_stats_.miss_ratio());
+ time_interval++;
+ }
+ }
+ uint64_t now = env_->NowMicros();
+ uint64_t duration = (now - start) / kMicrosInSecond;
+ uint64_t trace_duration =
+ trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
+ fprintf(stdout,
+ "Running for %" PRIu64 " seconds: Processed %" PRIu64
+ " records/second. Trace duration %" PRIu64
+ " seconds. Observed miss ratio %.2f\n",
+ duration, duration > 0 ? access_sequence_number_ / duration : 0,
+ trace_duration, miss_ratio_stats_.miss_ratio());
+ return s;
+}
+
+void BlockCacheTraceAnalyzer::PrintBlockSizeStats() const {
+ HistogramStat bs_stats;
+ std::map<TraceType, HistogramStat> bt_stats_map;
+ std::map<std::string, std::map<TraceType, HistogramStat>> cf_bt_stats_map;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t /*fd*/, uint32_t /*level*/,
+ TraceType type, const std::string& /*block_key*/,
+ uint64_t /*block_id*/, const BlockAccessInfo& block) {
+ if (block.block_size == 0) {
+ // Block size may be 0 when 1) compaction observes a cache miss and
+ // does not insert the missing block into the cache again. 2)
+ // fetching filter blocks in SST files at the last level.
+ return;
+ }
+ bs_stats.Add(block.block_size);
+ bt_stats_map[type].Add(block.block_size);
+ cf_bt_stats_map[cf_name][type].Add(block.block_size);
+ };
+ TraverseBlocks(block_callback);
+ fprintf(stdout, "Block size stats: \n%s", bs_stats.ToString().c_str());
+ for (auto const& bt_stats : bt_stats_map) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout, "Block size stats for block type %s: \n%s",
+ block_type_to_string(bt_stats.first).c_str(),
+ bt_stats.second.ToString().c_str());
+ }
+ for (auto const& cf_bt_stats : cf_bt_stats_map) {
+ const std::string& cf_name = cf_bt_stats.first;
+ for (auto const& bt_stats : cf_bt_stats.second) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout,
+ "Block size stats for column family %s and block type %s: \n%s",
+ cf_name.c_str(), block_type_to_string(bt_stats.first).c_str(),
+ bt_stats.second.ToString().c_str());
+ }
+ }
+}
+
+void BlockCacheTraceAnalyzer::PrintAccessCountStats(bool user_access_only,
+ uint32_t bottom_k,
+ uint32_t top_k) const {
+ HistogramStat access_stats;
+ std::map<TraceType, HistogramStat> bt_stats_map;
+ std::map<std::string, std::map<TraceType, HistogramStat>> cf_bt_stats_map;
+ std::map<uint64_t, std::vector<std::string>> access_count_blocks;
+ auto block_callback = [&](const std::string& cf_name, uint64_t /*fd*/,
+ uint32_t /*level*/, TraceType type,
+ const std::string& block_key, uint64_t /*block_id*/,
+ const BlockAccessInfo& block) {
+ uint64_t naccesses = 0;
+ for (auto const& caller_access : block.caller_num_access_map) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ naccesses += caller_access.second;
+ }
+ }
+ if (naccesses == 0) {
+ return;
+ }
+ if (type == TraceType::kBlockTraceDataBlock) {
+ access_count_blocks[naccesses].push_back(block_key);
+ }
+ access_stats.Add(naccesses);
+ bt_stats_map[type].Add(naccesses);
+ cf_bt_stats_map[cf_name][type].Add(naccesses);
+ };
+ TraverseBlocks(block_callback);
+ fprintf(stdout,
+ "Block access count stats: The number of accesses per block. %s\n%s",
+ user_access_only ? "User accesses only" : "All accesses",
+ access_stats.ToString().c_str());
+ uint32_t bottom_k_index = 0;
+ for (auto naccess_it = access_count_blocks.begin();
+ naccess_it != access_count_blocks.end(); naccess_it++) {
+ bottom_k_index++;
+ if (bottom_k_index >= bottom_k) {
+ break;
+ }
+ std::map<TableReaderCaller, uint64_t> caller_naccesses;
+ uint64_t naccesses = 0;
+ for (auto const& block_id : naccess_it->second) {
+ BlockAccessInfo* block = block_info_map_.find(block_id)->second;
+ for (auto const& caller_access : block->caller_num_access_map) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ caller_naccesses[caller_access.first] += caller_access.second;
+ naccesses += caller_access.second;
+ }
+ }
+ }
+ std::string statistics("Caller:");
+ for (auto const& caller_naccessess_it : caller_naccesses) {
+ statistics += caller_to_string(caller_naccessess_it.first);
+ statistics += ":";
+ statistics +=
+ std::to_string(percent(caller_naccessess_it.second, naccesses));
+ statistics += ",";
+ }
+ fprintf(stdout,
+ "Bottom %" PRIu32 " access count. Access count=%" PRIu64
+ " nblocks=%" ROCKSDB_PRIszt " %s\n",
+ bottom_k, naccess_it->first, naccess_it->second.size(),
+ statistics.c_str());
+ }
+
+ uint32_t top_k_index = 0;
+ for (auto naccess_it = access_count_blocks.rbegin();
+ naccess_it != access_count_blocks.rend(); naccess_it++) {
+ top_k_index++;
+ if (top_k_index >= top_k) {
+ break;
+ }
+ for (auto const& block_id : naccess_it->second) {
+ BlockAccessInfo* block = block_info_map_.find(block_id)->second;
+ std::string statistics("Caller:");
+ uint64_t naccesses = 0;
+ for (auto const& caller_access : block->caller_num_access_map) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ naccesses += caller_access.second;
+ }
+ }
+ assert(naccesses > 0);
+ for (auto const& caller_access : block->caller_num_access_map) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ statistics += ",";
+ statistics += caller_to_string(caller_access.first);
+ statistics += ":";
+ statistics +=
+ std::to_string(percent(caller_access.second, naccesses));
+ }
+ }
+ uint64_t ref_keys_accesses = 0;
+ uint64_t ref_keys_does_not_exist_accesses = 0;
+ for (auto const& ref_key_caller_access : block->key_num_access_map) {
+ for (auto const& caller_access : ref_key_caller_access.second) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ ref_keys_accesses += caller_access.second;
+ }
+ }
+ }
+ for (auto const& ref_key_caller_access :
+ block->non_exist_key_num_access_map) {
+ for (auto const& caller_access : ref_key_caller_access.second) {
+ if (!user_access_only || is_user_access(caller_access.first)) {
+ ref_keys_does_not_exist_accesses += caller_access.second;
+ }
+ }
+ }
+ statistics += ",nkeys=";
+ statistics += std::to_string(block->num_keys);
+ statistics += ",block_size=";
+ statistics += std::to_string(block->block_size);
+ statistics += ",num_ref_keys=";
+ statistics += std::to_string(block->key_num_access_map.size());
+ statistics += ",percent_access_ref_keys=";
+ statistics += std::to_string(percent(ref_keys_accesses, naccesses));
+ statistics += ",num_ref_keys_does_not_exist=";
+ statistics += std::to_string(block->non_exist_key_num_access_map.size());
+ statistics += ",percent_access_ref_keys_does_not_exist=";
+ statistics +=
+ std::to_string(percent(ref_keys_does_not_exist_accesses, naccesses));
+ statistics += ",ref_data_size=";
+ statistics += std::to_string(block->referenced_data_size);
+ fprintf(stdout,
+ "Top %" PRIu32 " access count blocks access_count=%" PRIu64
+ " %s\n",
+ top_k, naccess_it->first, statistics.c_str());
+ }
+ }
+
+ for (auto const& bt_stats : bt_stats_map) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout, "Break down by block type %s: \n%s",
+ block_type_to_string(bt_stats.first).c_str(),
+ bt_stats.second.ToString().c_str());
+ }
+ for (auto const& cf_bt_stats : cf_bt_stats_map) {
+ const std::string& cf_name = cf_bt_stats.first;
+ for (auto const& bt_stats : cf_bt_stats.second) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout,
+ "Break down by column family %s and block type "
+ "%s: \n%s",
+ cf_name.c_str(), block_type_to_string(bt_stats.first).c_str(),
+ bt_stats.second.ToString().c_str());
+ }
+ }
+}
+
+void BlockCacheTraceAnalyzer::PrintDataBlockAccessStats() const {
+ HistogramStat existing_keys_stats;
+ std::map<std::string, HistogramStat> cf_existing_keys_stats_map;
+ HistogramStat non_existing_keys_stats;
+ std::map<std::string, HistogramStat> cf_non_existing_keys_stats_map;
+ HistogramStat block_access_stats;
+ std::map<std::string, HistogramStat> cf_block_access_info;
+ HistogramStat percent_referenced_bytes;
+ std::map<std::string, HistogramStat> cf_percent_referenced_bytes;
+ // Total number of accesses in a data block / number of keys in a data block.
+ HistogramStat avg_naccesses_per_key_in_a_data_block;
+ std::map<std::string, HistogramStat> cf_avg_naccesses_per_key_in_a_data_block;
+ // The standard deviation on the number of accesses of a key in a data block.
+ HistogramStat stdev_naccesses_per_key_in_a_data_block;
+ std::map<std::string, HistogramStat>
+ cf_stdev_naccesses_per_key_in_a_data_block;
+ auto block_callback =
+ [&](const std::string& cf_name, uint64_t /*fd*/, uint32_t /*level*/,
+ TraceType /*type*/, const std::string& /*block_key*/,
+ uint64_t /*block_id*/, const BlockAccessInfo& block) {
+ if (block.num_keys == 0) {
+ return;
+ }
+ // Use four decimal points.
+ uint64_t percent_referenced_for_existing_keys = (uint64_t)(
+ ((double)block.key_num_access_map.size() / (double)block.num_keys) *
+ 10000.0);
+ uint64_t percent_referenced_for_non_existing_keys =
+ (uint64_t)(((double)block.non_exist_key_num_access_map.size() /
+ (double)block.num_keys) *
+ 10000.0);
+ uint64_t percent_accesses_for_existing_keys =
+ (uint64_t)(((double)block.num_referenced_key_exist_in_block /
+ (double)block.num_accesses) *
+ 10000.0);
+
+ HistogramStat hist_naccess_per_key;
+ for (auto const& key_access : block.key_num_access_map) {
+ for (auto const& caller_access : key_access.second) {
+ hist_naccess_per_key.Add(caller_access.second);
+ }
+ }
+ uint64_t avg_accesses =
+ static_cast<uint64_t>(hist_naccess_per_key.Average());
+ uint64_t stdev_accesses =
+ static_cast<uint64_t>(hist_naccess_per_key.StandardDeviation());
+ avg_naccesses_per_key_in_a_data_block.Add(avg_accesses);
+ cf_avg_naccesses_per_key_in_a_data_block[cf_name].Add(avg_accesses);
+ stdev_naccesses_per_key_in_a_data_block.Add(stdev_accesses);
+ cf_stdev_naccesses_per_key_in_a_data_block[cf_name].Add(stdev_accesses);
+
+ existing_keys_stats.Add(percent_referenced_for_existing_keys);
+ cf_existing_keys_stats_map[cf_name].Add(
+ percent_referenced_for_existing_keys);
+ non_existing_keys_stats.Add(percent_referenced_for_non_existing_keys);
+ cf_non_existing_keys_stats_map[cf_name].Add(
+ percent_referenced_for_non_existing_keys);
+ block_access_stats.Add(percent_accesses_for_existing_keys);
+ cf_block_access_info[cf_name].Add(percent_accesses_for_existing_keys);
+ };
+ TraverseBlocks(block_callback);
+ fprintf(stdout,
+ "Histogram on the number of referenced keys existing in a block over "
+ "the total number of keys in a block: \n%s",
+ existing_keys_stats.ToString().c_str());
+ for (auto const& cf_stats : cf_existing_keys_stats_map) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout, "Break down by column family %s: \n%s",
+ cf_stats.first.c_str(), cf_stats.second.ToString().c_str());
+ }
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(
+ stdout,
+ "Histogram on the number of referenced keys DO NOT exist in a block over "
+ "the total number of keys in a block: \n%s",
+ non_existing_keys_stats.ToString().c_str());
+ for (auto const& cf_stats : cf_non_existing_keys_stats_map) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout, "Break down by column family %s: \n%s",
+ cf_stats.first.c_str(), cf_stats.second.ToString().c_str());
+ }
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout,
+ "Histogram on the number of accesses on keys exist in a block over "
+ "the total number of accesses in a block: \n%s",
+ block_access_stats.ToString().c_str());
+ for (auto const& cf_stats : cf_block_access_info) {
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout, "Break down by column family %s: \n%s",
+ cf_stats.first.c_str(), cf_stats.second.ToString().c_str());
+ }
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(
+ stdout,
+ "Histogram on the average number of accesses per key in a block: \n%s",
+ avg_naccesses_per_key_in_a_data_block.ToString().c_str());
+ for (auto const& cf_stats : cf_avg_naccesses_per_key_in_a_data_block) {
+ fprintf(stdout, "Break down by column family %s: \n%s",
+ cf_stats.first.c_str(), cf_stats.second.ToString().c_str());
+ }
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout,
+ "Histogram on the standard deviation of the number of accesses per "
+ "key in a block: \n%s",
+ stdev_naccesses_per_key_in_a_data_block.ToString().c_str());
+ for (auto const& cf_stats : cf_stdev_naccesses_per_key_in_a_data_block) {
+ fprintf(stdout, "Break down by column family %s: \n%s",
+ cf_stats.first.c_str(), cf_stats.second.ToString().c_str());
+ }
+}
+
+void BlockCacheTraceAnalyzer::PrintStatsSummary() const {
+ uint64_t total_num_files = 0;
+ uint64_t total_num_blocks = 0;
+ uint64_t total_num_accesses = 0;
+ std::map<TraceType, uint64_t> bt_num_blocks_map;
+ std::map<TableReaderCaller, uint64_t> caller_num_access_map;
+ std::map<TableReaderCaller, std::map<TraceType, uint64_t>>
+ caller_bt_num_access_map;
+ std::map<TableReaderCaller, std::map<uint32_t, uint64_t>>
+ caller_level_num_access_map;
+ for (auto const& cf_aggregates : cf_aggregates_map_) {
+ // Stats per column family.
+ const std::string& cf_name = cf_aggregates.first;
+ uint64_t cf_num_files = 0;
+ uint64_t cf_num_blocks = 0;
+ std::map<TraceType, uint64_t> cf_bt_blocks;
+ uint64_t cf_num_accesses = 0;
+ std::map<TableReaderCaller, uint64_t> cf_caller_num_accesses_map;
+ std::map<TableReaderCaller, std::map<uint64_t, uint64_t>>
+ cf_caller_level_num_accesses_map;
+ std::map<TableReaderCaller, std::map<uint64_t, uint64_t>>
+ cf_caller_file_num_accesses_map;
+ std::map<TableReaderCaller, std::map<TraceType, uint64_t>>
+ cf_caller_bt_num_accesses_map;
+ total_num_files += cf_aggregates.second.fd_aggregates_map.size();
+ for (auto const& file_aggregates : cf_aggregates.second.fd_aggregates_map) {
+ // Stats per SST file.
+ const uint64_t fd = file_aggregates.first;
+ const uint32_t level = file_aggregates.second.level;
+ cf_num_files++;
+ for (auto const& block_type_aggregates :
+ file_aggregates.second.block_type_aggregates_map) {
+ // Stats per block type.
+ const TraceType type = block_type_aggregates.first;
+ cf_bt_blocks[type] +=
+ block_type_aggregates.second.block_access_info_map.size();
+ total_num_blocks +=
+ block_type_aggregates.second.block_access_info_map.size();
+ bt_num_blocks_map[type] +=
+ block_type_aggregates.second.block_access_info_map.size();
+ for (auto const& block_access_info :
+ block_type_aggregates.second.block_access_info_map) {
+ // Stats per block.
+ cf_num_blocks++;
+ for (auto const& stats :
+ block_access_info.second.caller_num_access_map) {
+ // Stats per caller.
+ const TableReaderCaller caller = stats.first;
+ const uint64_t num_accesses = stats.second;
+ // Overall stats.
+ total_num_accesses += num_accesses;
+ caller_num_access_map[caller] += num_accesses;
+ caller_bt_num_access_map[caller][type] += num_accesses;
+ caller_level_num_access_map[caller][level] += num_accesses;
+ // Column Family stats.
+ cf_num_accesses += num_accesses;
+ cf_caller_num_accesses_map[caller] += num_accesses;
+ cf_caller_level_num_accesses_map[caller][level] += num_accesses;
+ cf_caller_file_num_accesses_map[caller][fd] += num_accesses;
+ cf_caller_bt_num_accesses_map[caller][type] += num_accesses;
+ }
+ }
+ }
+ }
+
+ // Print stats.
+ print_break_lines(/*num_break_lines=*/3);
+ fprintf(stdout, "Statistics for column family %s:\n", cf_name.c_str());
+ fprintf(stdout,
+ " Number of files:%" PRIu64 " Number of blocks: %" PRIu64
+ " Number of accesses: %" PRIu64 "\n",
+ cf_num_files, cf_num_blocks, cf_num_accesses);
+ for (auto block_type : cf_bt_blocks) {
+ fprintf(stdout, "Number of %s blocks: %" PRIu64 " Percent: %.2f\n",
+ block_type_to_string(block_type.first).c_str(), block_type.second,
+ percent(block_type.second, cf_num_blocks));
+ }
+ for (auto caller : cf_caller_num_accesses_map) {
+ const uint64_t naccesses = caller.second;
+ print_break_lines(/*num_break_lines=*/1);
+ fprintf(stdout,
+ "Caller %s: Number of accesses %" PRIu64 " Percent: %.2f\n",
+ caller_to_string(caller.first).c_str(), naccesses,
+ percent(naccesses, cf_num_accesses));
+ fprintf(stdout, "Caller %s: Number of accesses per level break down\n",
+ caller_to_string(caller.first).c_str());
+ for (auto naccess_level :
+ cf_caller_level_num_accesses_map[caller.first]) {
+ fprintf(stdout,
+ "\t Level %" PRIu64 ": Number of accesses: %" PRIu64
+ " Percent: %.2f\n",
+ naccess_level.first, naccess_level.second,
+ percent(naccess_level.second, naccesses));
+ }
+ fprintf(stdout, "Caller %s: Number of accesses per file break down\n",
+ caller_to_string(caller.first).c_str());
+ for (auto naccess_file : cf_caller_file_num_accesses_map[caller.first]) {
+ fprintf(stdout,
+ "\t File %" PRIu64 ": Number of accesses: %" PRIu64
+ " Percent: %.2f\n",
+ naccess_file.first, naccess_file.second,
+ percent(naccess_file.second, naccesses));
+ }
+ fprintf(stdout,
+ "Caller %s: Number of accesses per block type break down\n",
+ caller_to_string(caller.first).c_str());
+ for (auto naccess_type : cf_caller_bt_num_accesses_map[caller.first]) {
+ fprintf(stdout,
+ "\t Block Type %s: Number of accesses: %" PRIu64
+ " Percent: %.2f\n",
+ block_type_to_string(naccess_type.first).c_str(),
+ naccess_type.second, percent(naccess_type.second, naccesses));
+ }
+ }
+ }
+ print_break_lines(/*num_break_lines=*/3);
+ fprintf(stdout, "Overall statistics:\n");
+ fprintf(stdout,
+ "Number of files: %" PRIu64 " Number of blocks: %" PRIu64
+ " Number of accesses: %" PRIu64 "\n",
+ total_num_files, total_num_blocks, total_num_accesses);
+ for (auto block_type : bt_num_blocks_map) {
+ fprintf(stdout, "Number of %s blocks: %" PRIu64 " Percent: %.2f\n",
+ block_type_to_string(block_type.first).c_str(), block_type.second,
+ percent(block_type.second, total_num_blocks));
+ }
+ for (auto caller : caller_num_access_map) {
+ print_break_lines(/*num_break_lines=*/1);
+ uint64_t naccesses = caller.second;
+ fprintf(stdout, "Caller %s: Number of accesses %" PRIu64 " Percent: %.2f\n",
+ caller_to_string(caller.first).c_str(), naccesses,
+ percent(naccesses, total_num_accesses));
+ fprintf(stdout, "Caller %s: Number of accesses per level break down\n",
+ caller_to_string(caller.first).c_str());
+ for (auto naccess_level : caller_level_num_access_map[caller.first]) {
+ fprintf(stdout,
+ "\t Level %d: Number of accesses: %" PRIu64 " Percent: %.2f\n",
+ naccess_level.first, naccess_level.second,
+ percent(naccess_level.second, naccesses));
+ }
+ fprintf(stdout, "Caller %s: Number of accesses per block type break down\n",
+ caller_to_string(caller.first).c_str());
+ for (auto naccess_type : caller_bt_num_access_map[caller.first]) {
+ fprintf(stdout,
+ "\t Block Type %s: Number of accesses: %" PRIu64
+ " Percent: %.2f\n",
+ block_type_to_string(naccess_type.first).c_str(),
+ naccess_type.second, percent(naccess_type.second, naccesses));
+ }
+ }
+}
+
+std::vector<CacheConfiguration> parse_cache_config_file(
+ const std::string& config_path) {
+ std::ifstream file(config_path);
+ if (!file.is_open()) {
+ return {};
+ }
+ std::vector<CacheConfiguration> configs;
+ std::string line;
+ while (getline(file, line)) {
+ CacheConfiguration cache_config;
+ std::stringstream ss(line);
+ std::vector<std::string> config_strs;
+ while (ss.good()) {
+ std::string substr;
+ getline(ss, substr, ',');
+ config_strs.push_back(substr);
+ }
+ // Sanity checks.
+ if (config_strs.size() < 4) {
+ fprintf(stderr, "Invalid cache simulator configuration %s\n",
+ line.c_str());
+ exit(1);
+ }
+ if (kSupportedCacheNames.find(" " + config_strs[0] + " ") ==
+ std::string::npos) {
+ fprintf(stderr, "Invalid cache name %s. Supported cache names are %s\n",
+ line.c_str(), kSupportedCacheNames.c_str());
+ exit(1);
+ }
+ cache_config.cache_name = config_strs[0];
+ cache_config.num_shard_bits = ParseUint32(config_strs[1]);
+ cache_config.ghost_cache_capacity = ParseUint64(config_strs[2]);
+ for (uint32_t i = 3; i < config_strs.size(); i++) {
+ uint64_t capacity = ParseUint64(config_strs[i]);
+ if (capacity == 0) {
+ fprintf(stderr, "Invalid cache capacity %s, %s\n",
+ config_strs[i].c_str(), line.c_str());
+ exit(1);
+ }
+ cache_config.cache_capacities.push_back(capacity);
+ }
+ configs.push_back(cache_config);
+ }
+ file.close();
+ return configs;
+}
+
+std::vector<uint64_t> parse_buckets(const std::string& bucket_str) {
+ std::vector<uint64_t> buckets;
+ std::stringstream ss(bucket_str);
+ while (ss.good()) {
+ std::string bucket;
+ getline(ss, bucket, ',');
+ buckets.push_back(ParseUint64(bucket));
+ }
+ buckets.push_back(port::kMaxUint64);
+ return buckets;
+}
+
+int block_cache_trace_analyzer_tool(int argc, char** argv) {
+ ParseCommandLineFlags(&argc, &argv, true);
+ if (FLAGS_block_cache_trace_path.empty()) {
+ fprintf(stderr, "block cache trace path is empty\n");
+ exit(1);
+ }
+ uint64_t warmup_seconds =
+ FLAGS_cache_sim_warmup_seconds > 0 ? FLAGS_cache_sim_warmup_seconds : 0;
+ uint32_t downsample_ratio = FLAGS_block_cache_trace_downsample_ratio > 0
+ ? FLAGS_block_cache_trace_downsample_ratio
+ : 0;
+ std::vector<CacheConfiguration> cache_configs =
+ parse_cache_config_file(FLAGS_block_cache_sim_config_path);
+ std::unique_ptr<BlockCacheTraceSimulator> cache_simulator;
+ if (!cache_configs.empty()) {
+ cache_simulator.reset(new BlockCacheTraceSimulator(
+ warmup_seconds, downsample_ratio, cache_configs));
+ Status s = cache_simulator->InitializeCaches();
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot initialize cache simulators %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ }
+ BlockCacheTraceAnalyzer analyzer(
+ FLAGS_block_cache_trace_path, FLAGS_block_cache_analysis_result_dir,
+ FLAGS_human_readable_trace_file_path,
+ !FLAGS_reuse_distance_labels.empty(), FLAGS_mrc_only,
+ FLAGS_is_block_cache_human_readable_trace, std::move(cache_simulator));
+ Status s = analyzer.Analyze();
+ if (!s.IsIncomplete() && !s.ok()) {
+ // Read all traces.
+ fprintf(stderr, "Cannot process the trace %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ fprintf(stdout, "Status: %s\n", s.ToString().c_str());
+ analyzer.WriteMissRatioCurves();
+ analyzer.WriteMissRatioTimeline(1);
+ analyzer.WriteMissRatioTimeline(kSecondInMinute);
+ analyzer.WriteMissRatioTimeline(kSecondInHour);
+ analyzer.WriteMissTimeline(1);
+ analyzer.WriteMissTimeline(kSecondInMinute);
+ analyzer.WriteMissTimeline(kSecondInHour);
+
+ if (FLAGS_mrc_only) {
+ fprintf(stdout,
+ "Skipping the analysis statistics since the user wants to compute "
+ "MRC only");
+ return 0;
+ }
+
+ analyzer.PrintStatsSummary();
+ if (FLAGS_print_access_count_stats) {
+ print_break_lines(/*num_break_lines=*/3);
+ analyzer.PrintAccessCountStats(
+ /*user_access_only=*/false, FLAGS_analyze_bottom_k_access_count_blocks,
+ FLAGS_analyze_top_k_access_count_blocks);
+ print_break_lines(/*num_break_lines=*/3);
+ analyzer.PrintAccessCountStats(
+ /*user_access_only=*/true, FLAGS_analyze_bottom_k_access_count_blocks,
+ FLAGS_analyze_top_k_access_count_blocks);
+ }
+ if (FLAGS_print_block_size_stats) {
+ print_break_lines(/*num_break_lines=*/3);
+ analyzer.PrintBlockSizeStats();
+ }
+ if (FLAGS_print_data_block_access_count_stats) {
+ print_break_lines(/*num_break_lines=*/3);
+ analyzer.PrintDataBlockAccessStats();
+ }
+ print_break_lines(/*num_break_lines=*/3);
+
+ if (!FLAGS_timeline_labels.empty()) {
+ std::stringstream ss(FLAGS_timeline_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ if (label.find("block") != std::string::npos) {
+ analyzer.WriteAccessTimeline(label, kSecondInMinute, true);
+ analyzer.WriteAccessTimeline(label, kSecondInMinute, false);
+ analyzer.WriteAccessTimeline(label, kSecondInHour, true);
+ analyzer.WriteAccessTimeline(label, kSecondInHour, false);
+ } else {
+ analyzer.WriteAccessTimeline(label, kSecondInMinute, false);
+ analyzer.WriteAccessTimeline(label, kSecondInHour, false);
+ }
+ }
+ }
+
+ if (!FLAGS_analyze_callers.empty()) {
+ analyzer.WritePercentAccessSummaryStats();
+ std::stringstream ss(FLAGS_analyze_callers);
+ while (ss.good()) {
+ std::string caller;
+ getline(ss, caller, ',');
+ analyzer.WriteDetailedPercentAccessSummaryStats(string_to_caller(caller));
+ }
+ }
+
+ if (!FLAGS_access_count_buckets.empty()) {
+ std::vector<uint64_t> buckets = parse_buckets(FLAGS_access_count_buckets);
+ analyzer.WriteAccessCountSummaryStats(buckets, /*user_access_only=*/true);
+ analyzer.WriteAccessCountSummaryStats(buckets, /*user_access_only=*/false);
+ }
+
+ if (!FLAGS_reuse_distance_labels.empty() &&
+ !FLAGS_reuse_distance_buckets.empty()) {
+ std::vector<uint64_t> buckets = parse_buckets(FLAGS_reuse_distance_buckets);
+ std::stringstream ss(FLAGS_reuse_distance_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ analyzer.WriteReuseDistance(label, buckets);
+ }
+ }
+
+ if (!FLAGS_reuse_interval_labels.empty() &&
+ !FLAGS_reuse_interval_buckets.empty()) {
+ std::vector<uint64_t> buckets = parse_buckets(FLAGS_reuse_interval_buckets);
+ std::stringstream ss(FLAGS_reuse_interval_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ analyzer.WriteReuseInterval(label, buckets);
+ }
+ }
+
+ if (!FLAGS_reuse_lifetime_labels.empty() &&
+ !FLAGS_reuse_lifetime_buckets.empty()) {
+ std::vector<uint64_t> buckets = parse_buckets(FLAGS_reuse_lifetime_buckets);
+ std::stringstream ss(FLAGS_reuse_lifetime_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ analyzer.WriteReuseLifetime(label, buckets);
+ }
+ }
+
+ if (FLAGS_analyze_blocks_reuse_k_reuse_window != 0) {
+ std::vector<TraceType> block_types{TraceType::kBlockTraceIndexBlock,
+ TraceType::kBlockTraceDataBlock,
+ TraceType::kBlockTraceFilterBlock};
+ for (auto block_type : block_types) {
+ analyzer.WriteBlockReuseTimeline(
+ FLAGS_analyze_blocks_reuse_k_reuse_window,
+ /*user_access_only=*/true, block_type);
+ analyzer.WriteBlockReuseTimeline(
+ FLAGS_analyze_blocks_reuse_k_reuse_window,
+ /*user_access_only=*/false, block_type);
+ }
+ }
+
+ if (!FLAGS_analyze_get_spatial_locality_labels.empty() &&
+ !FLAGS_analyze_get_spatial_locality_buckets.empty()) {
+ std::vector<uint64_t> buckets =
+ parse_buckets(FLAGS_analyze_get_spatial_locality_buckets);
+ std::stringstream ss(FLAGS_analyze_get_spatial_locality_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ analyzer.WriteGetSpatialLocality(label, buckets);
+ }
+ }
+
+ if (!FLAGS_analyze_correlation_coefficients_labels.empty()) {
+ std::stringstream ss(FLAGS_analyze_correlation_coefficients_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ analyzer.WriteCorrelationFeatures(
+ label, FLAGS_analyze_correlation_coefficients_max_number_of_values);
+ }
+ analyzer.WriteCorrelationFeaturesForGet(
+ FLAGS_analyze_correlation_coefficients_max_number_of_values);
+ }
+
+ if (!FLAGS_skew_labels.empty() && !FLAGS_skew_buckets.empty()) {
+ std::vector<uint64_t> buckets = parse_buckets(FLAGS_skew_buckets);
+ std::stringstream ss(FLAGS_skew_labels);
+ while (ss.good()) {
+ std::string label;
+ getline(ss, label, ',');
+ if (label.find("block") != std::string::npos) {
+ analyzer.WriteSkewness(label, buckets,
+ TraceType::kBlockTraceIndexBlock);
+ analyzer.WriteSkewness(label, buckets,
+ TraceType::kBlockTraceFilterBlock);
+ analyzer.WriteSkewness(label, buckets, TraceType::kBlockTraceDataBlock);
+ analyzer.WriteSkewness(label, buckets, TraceType::kTraceMax);
+ } else {
+ analyzer.WriteSkewness(label, buckets, TraceType::kTraceMax);
+ }
+ }
+ }
+ return 0;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // GFLAGS
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.h b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.h
new file mode 100644
index 00000000..48a54481
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer.h
@@ -0,0 +1,393 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <set>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "rocksdb/env.h"
+#include "rocksdb/utilities/sim_cache.h"
+#include "trace_replay/block_cache_tracer.h"
+#include "utilities/simulator_cache/cache_simulator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Statistics of a key refereneced by a Get.
+struct GetKeyInfo {
+ uint64_t key_id = 0;
+ std::vector<uint64_t> access_sequence_number_timeline;
+ std::vector<uint64_t> access_timeline;
+
+ void AddAccess(const BlockCacheTraceRecord& access,
+ uint64_t access_sequnce_number) {
+ access_sequence_number_timeline.push_back(access_sequnce_number);
+ access_timeline.push_back(access.access_timestamp);
+ }
+};
+
+// Statistics of a block.
+struct BlockAccessInfo {
+ uint64_t block_id = 0;
+ uint64_t table_id = 0;
+ uint64_t block_offset = 0;
+ uint64_t num_accesses = 0;
+ uint64_t block_size = 0;
+ uint64_t first_access_time = 0;
+ uint64_t last_access_time = 0;
+ uint64_t num_keys = 0;
+ std::map<std::string, std::map<TableReaderCaller, uint64_t>>
+ key_num_access_map; // for keys exist in this block.
+ std::map<std::string, std::map<TableReaderCaller, uint64_t>>
+ non_exist_key_num_access_map; // for keys do not exist in this block.
+ uint64_t num_referenced_key_exist_in_block = 0;
+ uint64_t referenced_data_size = 0;
+ std::map<TableReaderCaller, uint64_t> caller_num_access_map;
+ // caller:timestamp:number_of_accesses. The granularity of the timestamp is
+ // seconds.
+ std::map<TableReaderCaller, std::map<uint64_t, uint64_t>>
+ caller_num_accesses_timeline;
+ // Unique blocks since the last access.
+ std::set<std::string> unique_blocks_since_last_access;
+ // Number of reuses grouped by reuse distance.
+ std::map<uint64_t, uint64_t> reuse_distance_count;
+
+ // The access sequence numbers of this block.
+ std::vector<uint64_t> access_sequence_number_timeline;
+ std::map<TableReaderCaller, std::vector<uint64_t>>
+ caller_access_sequence__number_timeline;
+ // The access timestamp in microseconds of this block.
+ std::vector<uint64_t> access_timeline;
+ std::map<TableReaderCaller, std::vector<uint64_t>> caller_access_timeline;
+
+ void AddAccess(const BlockCacheTraceRecord& access,
+ uint64_t access_sequnce_number) {
+ if (block_size != 0 && access.block_size != 0) {
+ assert(block_size == access.block_size);
+ }
+ if (num_keys != 0 && access.num_keys_in_block != 0) {
+ assert(num_keys == access.num_keys_in_block);
+ }
+ if (first_access_time == 0) {
+ first_access_time = access.access_timestamp;
+ }
+ table_id = BlockCacheTraceHelper::GetTableId(access);
+ block_offset = BlockCacheTraceHelper::GetBlockOffsetInFile(access);
+ last_access_time = access.access_timestamp;
+ block_size = access.block_size;
+ caller_num_access_map[access.caller]++;
+ num_accesses++;
+ // access.access_timestamp is in microsecond.
+ const uint64_t timestamp_in_seconds =
+ access.access_timestamp / kMicrosInSecond;
+ caller_num_accesses_timeline[access.caller][timestamp_in_seconds] += 1;
+ // Populate the feature vectors.
+ access_sequence_number_timeline.push_back(access_sequnce_number);
+ caller_access_sequence__number_timeline[access.caller].push_back(
+ access_sequnce_number);
+ access_timeline.push_back(access.access_timestamp);
+ caller_access_timeline[access.caller].push_back(access.access_timestamp);
+ if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(access.block_type,
+ access.caller)) {
+ num_keys = access.num_keys_in_block;
+ if (access.referenced_key_exist_in_block == Boolean::kTrue) {
+ if (key_num_access_map.find(access.referenced_key) ==
+ key_num_access_map.end()) {
+ referenced_data_size += access.referenced_data_size;
+ }
+ key_num_access_map[access.referenced_key][access.caller]++;
+ num_referenced_key_exist_in_block++;
+ if (referenced_data_size > block_size && block_size != 0) {
+ ParsedInternalKey internal_key;
+ ParseInternalKey(access.referenced_key, &internal_key);
+ }
+ } else {
+ non_exist_key_num_access_map[access.referenced_key][access.caller]++;
+ }
+ }
+ }
+};
+
+// Aggregates stats of a block given a block type.
+struct BlockTypeAccessInfoAggregate {
+ std::map<std::string, BlockAccessInfo> block_access_info_map;
+};
+
+// Aggregates BlockTypeAggregate given a SST file.
+struct SSTFileAccessInfoAggregate {
+ uint32_t level;
+ std::map<TraceType, BlockTypeAccessInfoAggregate> block_type_aggregates_map;
+};
+
+// Aggregates SSTFileAggregate given a column family.
+struct ColumnFamilyAccessInfoAggregate {
+ std::map<uint64_t, SSTFileAccessInfoAggregate> fd_aggregates_map;
+};
+
+struct Features {
+ std::vector<uint64_t> elapsed_time_since_last_access;
+ std::vector<uint64_t> num_accesses_since_last_access;
+ std::vector<uint64_t> num_past_accesses;
+};
+
+struct Predictions {
+ std::vector<uint64_t> elapsed_time_till_next_access;
+ std::vector<uint64_t> num_accesses_till_next_access;
+};
+
+class BlockCacheTraceAnalyzer {
+ public:
+ BlockCacheTraceAnalyzer(
+ const std::string& trace_file_path, const std::string& output_dir,
+ const std::string& human_readable_trace_file_path,
+ bool compute_reuse_distance, bool mrc_only,
+ bool is_human_readable_trace_file,
+ std::unique_ptr<BlockCacheTraceSimulator>&& cache_simulator);
+ ~BlockCacheTraceAnalyzer() = default;
+ // No copy and move.
+ BlockCacheTraceAnalyzer(const BlockCacheTraceAnalyzer&) = delete;
+ BlockCacheTraceAnalyzer& operator=(const BlockCacheTraceAnalyzer&) = delete;
+ BlockCacheTraceAnalyzer(BlockCacheTraceAnalyzer&&) = delete;
+ BlockCacheTraceAnalyzer& operator=(BlockCacheTraceAnalyzer&&) = delete;
+
+ // Read all access records in the given trace_file, maintains the stats of
+ // a block, and aggregates the information by block type, sst file, and column
+ // family. Subsequently, the caller may call Print* functions to print
+ // statistics.
+ Status Analyze();
+
+ // Print a summary of statistics of the trace, e.g.,
+ // Number of files: 2 Number of blocks: 50 Number of accesses: 50
+ // Number of Index blocks: 10
+ // Number of Filter blocks: 10
+ // Number of Data blocks: 10
+ // Number of UncompressionDict blocks: 10
+ // Number of RangeDeletion blocks: 10
+ // ***************************************************************
+ // Caller Get: Number of accesses 10
+ // Caller Get: Number of accesses per level break down
+ // Level 0: Number of accesses: 10
+ // Caller Get: Number of accesses per block type break down
+ // Block Type Index: Number of accesses: 2
+ // Block Type Filter: Number of accesses: 2
+ // Block Type Data: Number of accesses: 2
+ // Block Type UncompressionDict: Number of accesses: 2
+ // Block Type RangeDeletion: Number of accesses: 2
+ void PrintStatsSummary() const;
+
+ // Print block size distribution and the distribution break down by block type
+ // and column family.
+ void PrintBlockSizeStats() const;
+
+ // Print access count distribution and the distribution break down by block
+ // type and column family.
+ void PrintAccessCountStats(bool user_access_only, uint32_t bottom_k,
+ uint32_t top_k) const;
+
+ // Print data block accesses by user Get and Multi-Get.
+ // It prints out 1) A histogram on the percentage of keys accessed in a data
+ // block break down by if a referenced key exists in the data block andthe
+ // histogram break down by column family. 2) A histogram on the percentage of
+ // accesses on keys exist in a data block and its break down by column family.
+ void PrintDataBlockAccessStats() const;
+
+ // Write the percentage of accesses break down by column family into a csv
+ // file saved in 'output_dir'.
+ //
+ // The file is named "percentage_of_accesses_summary". The file format is
+ // caller,cf_0,cf_1,...,cf_n where the cf_i is the column family name found in
+ // the trace.
+ void WritePercentAccessSummaryStats() const;
+
+ // Write the percentage of accesses for the given caller break down by column
+ // family, level, and block type into a csv file saved in 'output_dir'.
+ //
+ // It generates two files: 1) caller_level_percentage_of_accesses_summary and
+ // 2) caller_bt_percentage_of_accesses_summary which break down by the level
+ // and block type, respectively. The file format is
+ // level/bt,cf_0,cf_1,...,cf_n where cf_i is the column family name found in
+ // the trace.
+ void WriteDetailedPercentAccessSummaryStats(TableReaderCaller caller) const;
+
+ // Write the access count summary into a csv file saved in 'output_dir'.
+ // It groups blocks by their access count.
+ //
+ // It generates two files: 1) cf_access_count_summary and 2)
+ // bt_access_count_summary which break down the access count by column family
+ // and block type, respectively. The file format is
+ // cf/bt,bucket_0,bucket_1,...,bucket_N.
+ void WriteAccessCountSummaryStats(
+ const std::vector<uint64_t>& access_count_buckets,
+ bool user_access_only) const;
+
+ // Write miss ratio curves of simulated cache configurations into a csv file
+ // named "mrc" saved in 'output_dir'.
+ //
+ // The file format is
+ // "cache_name,num_shard_bits,capacity,miss_ratio,total_accesses".
+ void WriteMissRatioCurves() const;
+
+ // Write miss ratio timeline of simulated cache configurations into several
+ // csv files, one per cache capacity saved in 'output_dir'.
+ //
+ // The file format is
+ // "time,label_1_access_per_second,label_2_access_per_second,...,label_N_access_per_second"
+ // where N is the number of unique cache names
+ // (cache_name+num_shard_bits+ghost_capacity).
+ void WriteMissRatioTimeline(uint64_t time_unit) const;
+
+ // Write misses timeline of simulated cache configurations into several
+ // csv files, one per cache capacity saved in 'output_dir'.
+ //
+ // The file format is
+ // "time,label_1_access_per_second,label_2_access_per_second,...,label_N_access_per_second"
+ // where N is the number of unique cache names
+ // (cache_name+num_shard_bits+ghost_capacity).
+ void WriteMissTimeline(uint64_t time_unit) const;
+
+ // Write the access timeline into a csv file saved in 'output_dir'.
+ //
+ // The file is named "label_access_timeline".The file format is
+ // "time,label_1_access_per_second,label_2_access_per_second,...,label_N_access_per_second"
+ // where N is the number of unique labels found in the trace.
+ void WriteAccessTimeline(const std::string& label, uint64_t time_unit,
+ bool user_access_only) const;
+
+ // Write the reuse distance into a csv file saved in 'output_dir'. Reuse
+ // distance is defined as the cumulated size of unique blocks read between two
+ // consective accesses on the same block.
+ //
+ // The file is named "label_reuse_distance". The file format is
+ // bucket,label_1,label_2,...,label_N.
+ void WriteReuseDistance(const std::string& label_str,
+ const std::vector<uint64_t>& distance_buckets) const;
+
+ // Write the reuse interval into a csv file saved in 'output_dir'. Reuse
+ // interval is defined as the time between two consecutive accesses on the
+ // same block.
+ //
+ // The file is named "label_reuse_interval". The file format is
+ // bucket,label_1,label_2,...,label_N.
+ void WriteReuseInterval(const std::string& label_str,
+ const std::vector<uint64_t>& time_buckets) const;
+
+ // Write the reuse lifetime into a csv file saved in 'output_dir'. Reuse
+ // lifetime is defined as the time interval between the first access of a
+ // block and its last access.
+ //
+ // The file is named "label_reuse_lifetime". The file format is
+ // bucket,label_1,label_2,...,label_N.
+ void WriteReuseLifetime(const std::string& label_str,
+ const std::vector<uint64_t>& time_buckets) const;
+
+ // Write the reuse timeline into a csv file saved in 'output_dir'.
+ //
+ // The file is named
+ // "block_type_user_access_only_reuse_window_reuse_timeline". The file format
+ // is start_time,0,1,...,N where N equals trace_duration / reuse_window.
+ void WriteBlockReuseTimeline(const uint64_t reuse_window, bool user_access_only,
+ TraceType block_type) const;
+
+ // Write the Get spatical locality into csv files saved in 'output_dir'.
+ //
+ // It generates three csv files. label_percent_ref_keys,
+ // label_percent_accesses_on_ref_keys, and
+ // label_percent_data_size_on_ref_keys.
+ void WriteGetSpatialLocality(
+ const std::string& label_str,
+ const std::vector<uint64_t>& percent_buckets) const;
+
+ void WriteCorrelationFeatures(const std::string& label_str,
+ uint32_t max_number_of_values) const;
+
+ void WriteCorrelationFeaturesForGet(uint32_t max_number_of_values) const;
+
+ void WriteSkewness(const std::string& label_str,
+ const std::vector<uint64_t>& percent_buckets,
+ TraceType target_block_type) const;
+
+ const std::map<std::string, ColumnFamilyAccessInfoAggregate>&
+ TEST_cf_aggregates_map() const {
+ return cf_aggregates_map_;
+ }
+
+ private:
+ std::set<std::string> ParseLabelStr(const std::string& label_str) const;
+
+ std::string BuildLabel(const std::set<std::string>& labels,
+ const std::string& cf_name, uint64_t fd,
+ uint32_t level, TraceType type,
+ TableReaderCaller caller, uint64_t block_key,
+ const BlockAccessInfo& block) const;
+
+ void ComputeReuseDistance(BlockAccessInfo* info) const;
+
+ Status RecordAccess(const BlockCacheTraceRecord& access);
+
+ void UpdateReuseIntervalStats(
+ const std::string& label, const std::vector<uint64_t>& time_buckets,
+ const std::map<uint64_t, uint64_t> timeline,
+ std::map<std::string, std::map<uint64_t, uint64_t>>*
+ label_time_num_reuses,
+ uint64_t* total_num_reuses) const;
+
+ std::string OutputPercentAccessStats(
+ uint64_t total_accesses,
+ const std::map<std::string, uint64_t>& cf_access_count) const;
+
+ void WriteStatsToFile(
+ const std::string& label_str, const std::vector<uint64_t>& time_buckets,
+ const std::string& filename_suffix,
+ const std::map<std::string, std::map<uint64_t, uint64_t>>& label_data,
+ uint64_t ntotal) const;
+
+ void TraverseBlocks(
+ std::function<void(const std::string& /*cf_name*/, uint64_t /*fd*/,
+ uint32_t /*level*/, TraceType /*block_type*/,
+ const std::string& /*block_key*/,
+ uint64_t /*block_key_id*/,
+ const BlockAccessInfo& /*block_access_info*/)>
+ block_callback,
+ std::set<std::string>* labels = nullptr) const;
+
+ void UpdateFeatureVectors(
+ const std::vector<uint64_t>& access_sequence_number_timeline,
+ const std::vector<uint64_t>& access_timeline, const std::string& label,
+ std::map<std::string, Features>* label_features,
+ std::map<std::string, Predictions>* label_predictions) const;
+
+ void WriteCorrelationFeaturesToFile(
+ const std::string& label,
+ const std::map<std::string, Features>& label_features,
+ const std::map<std::string, Predictions>& label_predictions,
+ uint32_t max_number_of_values) const;
+
+ ROCKSDB_NAMESPACE::Env* env_;
+ const std::string trace_file_path_;
+ const std::string output_dir_;
+ std::string human_readable_trace_file_path_;
+ const bool compute_reuse_distance_;
+ const bool mrc_only_;
+ const bool is_human_readable_trace_file_;
+
+ BlockCacheTraceHeader header_;
+ std::unique_ptr<BlockCacheTraceSimulator> cache_simulator_;
+ std::map<std::string, ColumnFamilyAccessInfoAggregate> cf_aggregates_map_;
+ std::map<std::string, BlockAccessInfo*> block_info_map_;
+ std::unordered_map<std::string, GetKeyInfo> get_key_info_map_;
+ uint64_t access_sequence_number_ = 0;
+ uint64_t trace_start_timestamp_in_seconds_ = 0;
+ uint64_t trace_end_timestamp_in_seconds_ = 0;
+ MissRatioStats miss_ratio_stats_;
+ uint64_t unique_block_id_ = 1;
+ uint64_t unique_get_key_id_ = 1;
+ BlockCacheHumanReadableTraceWriter human_readable_trace_writer_;
+};
+
+int block_cache_trace_analyzer_tool(int argc, char** argv);
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_plot.py b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_plot.py
new file mode 100644
index 00000000..0fdaa415
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_plot.py
@@ -0,0 +1,721 @@
+#!/usr/bin/env python3
+import csv
+import math
+import os
+import random
+import sys
+
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.backends.backend_pdf
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+import seaborn as sns
+
+
+# Make sure a legend has the same color across all generated graphs.
+def get_cmap(n, name="hsv"):
+ """Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
+ RGB color; the keyword argument name must be a standard mpl colormap name."""
+ return plt.cm.get_cmap(name, n)
+
+
+color_index = 0
+bar_color_maps = {}
+colors = []
+n_colors = 360
+linear_colors = get_cmap(n_colors)
+for i in range(n_colors):
+ colors.append(linear_colors(i))
+# Shuffle the colors so that adjacent bars in a graph are obvious to differentiate.
+random.shuffle(colors)
+
+
+def num_to_gb(n):
+ one_gb = 1024 * 1024 * 1024
+ if float(n) % one_gb == 0:
+ return "{}".format(n / one_gb)
+ # Keep two decimal points.
+ return "{0:.2f}".format(float(n) / one_gb)
+
+
+def plot_miss_stats_graphs(
+ csv_result_dir, output_result_dir, file_prefix, file_suffix, ylabel, pdf_file_name
+):
+ miss_ratios = {}
+ for file in os.listdir(csv_result_dir):
+ if not file.startswith(file_prefix):
+ continue
+ if not file.endswith(file_suffix):
+ continue
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ mrc_file_path = csv_result_dir + "/" + file
+ with open(mrc_file_path, "r") as csvfile:
+ rows = csv.reader(csvfile, delimiter=",")
+ for row in rows:
+ cache_name = row[0]
+ num_shard_bits = int(row[1])
+ ghost_capacity = int(row[2])
+ capacity = int(row[3])
+ miss_ratio = float(row[4])
+ config = "{}-{}-{}".format(cache_name, num_shard_bits, ghost_capacity)
+ if config not in miss_ratios:
+ miss_ratios[config] = {}
+ miss_ratios[config]["x"] = []
+ miss_ratios[config]["y"] = []
+ miss_ratios[config]["x"].append(capacity)
+ miss_ratios[config]["y"].append(miss_ratio)
+ fig = plt.figure()
+ for config in miss_ratios:
+ plt.plot(
+ miss_ratios[config]["x"], miss_ratios[config]["y"], label=config
+ )
+ plt.xlabel("Cache capacity")
+ plt.ylabel(ylabel)
+ plt.xscale("log", basex=2)
+ plt.ylim(ymin=0)
+ plt.title("{}".format(file))
+ plt.legend()
+ fig.savefig(
+ output_result_dir + "/{}.pdf".format(pdf_file_name), bbox_inches="tight"
+ )
+
+
+def plot_miss_stats_diff_lru_graphs(
+ csv_result_dir, output_result_dir, file_prefix, file_suffix, ylabel, pdf_file_name
+):
+ miss_ratios = {}
+ for file in os.listdir(csv_result_dir):
+ if not file.startswith(file_prefix):
+ continue
+ if not file.endswith(file_suffix):
+ continue
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ mrc_file_path = csv_result_dir + "/" + file
+ with open(mrc_file_path, "r") as csvfile:
+ rows = csv.reader(csvfile, delimiter=",")
+ for row in rows:
+ cache_name = row[0]
+ num_shard_bits = int(row[1])
+ ghost_capacity = int(row[2])
+ capacity = int(row[3])
+ miss_ratio = float(row[4])
+ config = "{}-{}-{}".format(cache_name, num_shard_bits, ghost_capacity)
+ if config not in miss_ratios:
+ miss_ratios[config] = {}
+ miss_ratios[config]["x"] = []
+ miss_ratios[config]["y"] = []
+ miss_ratios[config]["x"].append(capacity)
+ miss_ratios[config]["y"].append(miss_ratio)
+ if "lru-0-0" not in miss_ratios:
+ return
+ fig = plt.figure()
+ for config in miss_ratios:
+ diffs = [0] * len(miss_ratios["lru-0-0"]["x"])
+ for i in range(len(miss_ratios["lru-0-0"]["x"])):
+ for j in range(len(miss_ratios[config]["x"])):
+ if miss_ratios["lru-0-0"]["x"][i] == miss_ratios[config]["x"][j]:
+ diffs[i] = (
+ miss_ratios[config]["y"][j] - miss_ratios["lru-0-0"]["y"][i]
+ )
+ break
+ plt.plot(miss_ratios["lru-0-0"]["x"], diffs, label=config)
+ plt.xlabel("Cache capacity")
+ plt.ylabel(ylabel)
+ plt.xscale("log", basex=2)
+ plt.title("{}".format(file))
+ plt.legend()
+ fig.savefig(
+ output_result_dir + "/{}.pdf".format(pdf_file_name), bbox_inches="tight"
+ )
+
+
+def sanitize(label):
+ # matplotlib cannot plot legends that is prefixed with "_"
+ # so we need to remove them here.
+ index = 0
+ for i in range(len(label)):
+ if label[i] == "_":
+ index += 1
+ else:
+ break
+ data = label[index:]
+ # The value of uint64_max in c++.
+ if "18446744073709551615" in data:
+ return "max"
+ return data
+
+
+# Read the csv file vertically, i.e., group the data by columns.
+def read_data_for_plot_vertical(csvfile):
+ x = []
+ labels = []
+ label_stats = {}
+ csv_rows = csv.reader(csvfile, delimiter=",")
+ data_rows = []
+ for row in csv_rows:
+ data_rows.append(row)
+ # header
+ for i in range(1, len(data_rows[0])):
+ labels.append(sanitize(data_rows[0][i]))
+ label_stats[i - 1] = []
+ for i in range(1, len(data_rows)):
+ for j in range(len(data_rows[i])):
+ if j == 0:
+ x.append(sanitize(data_rows[i][j]))
+ continue
+ label_stats[j - 1].append(float(data_rows[i][j]))
+ return x, labels, label_stats
+
+
+# Read the csv file horizontally, i.e., group the data by rows.
+def read_data_for_plot_horizontal(csvfile):
+ x = []
+ labels = []
+ label_stats = {}
+ csv_rows = csv.reader(csvfile, delimiter=",")
+ data_rows = []
+ for row in csv_rows:
+ data_rows.append(row)
+ # header
+ for i in range(1, len(data_rows)):
+ labels.append(sanitize(data_rows[i][0]))
+ label_stats[i - 1] = []
+ for i in range(1, len(data_rows[0])):
+ x.append(sanitize(data_rows[0][i]))
+ for i in range(1, len(data_rows)):
+ for j in range(len(data_rows[i])):
+ if j == 0:
+ # label
+ continue
+ label_stats[i - 1].append(float(data_rows[i][j]))
+ return x, labels, label_stats
+
+
+def read_data_for_plot(csvfile, vertical):
+ if vertical:
+ return read_data_for_plot_vertical(csvfile)
+ return read_data_for_plot_horizontal(csvfile)
+
+
+def plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix,
+ filename_suffix,
+ pdf_name,
+ xlabel,
+ ylabel,
+ title,
+ vertical,
+ legend,
+):
+ global color_index, bar_color_maps, colors
+ pdf = matplotlib.backends.backend_pdf.PdfPages(output_result_dir + "/" + pdf_name)
+ for file in os.listdir(csv_result_dir):
+ if not file.endswith(filename_suffix):
+ continue
+ if not file.startswith(filename_prefix):
+ continue
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ with open(csv_result_dir + "/" + file, "r") as csvfile:
+ x, labels, label_stats = read_data_for_plot(csvfile, vertical)
+ if len(x) == 0 or len(labels) == 0:
+ continue
+ # plot figure
+ fig = plt.figure()
+ for label_index in label_stats:
+ # Assign a unique color to this label.
+ if labels[label_index] not in bar_color_maps:
+ bar_color_maps[labels[label_index]] = colors[color_index]
+ color_index += 1
+ plt.plot(
+ [int(x[i]) for i in range(len(x) - 1)],
+ label_stats[label_index][:-1],
+ label=labels[label_index],
+ color=bar_color_maps[labels[label_index]],
+ )
+
+ # Translate time unit into x labels.
+ if "_60" in file:
+ plt.xlabel("{} (Minute)".format(xlabel))
+ if "_3600" in file:
+ plt.xlabel("{} (Hour)".format(xlabel))
+ plt.ylabel(ylabel)
+ plt.title("{} {}".format(title, file))
+ if legend:
+ plt.legend()
+ pdf.savefig(fig)
+ pdf.close()
+
+
+def plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix,
+ pdf_name,
+ xlabel,
+ ylabel,
+ title,
+ vertical,
+ x_prefix,
+):
+ global color_index, bar_color_maps, colors
+ pdf = matplotlib.backends.backend_pdf.PdfPages(
+ "{}/{}".format(output_result_dir, pdf_name)
+ )
+ for file in os.listdir(csv_result_dir):
+ if not file.endswith(filename_suffix):
+ continue
+ with open(csv_result_dir + "/" + file, "r") as csvfile:
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ x, labels, label_stats = read_data_for_plot(csvfile, vertical)
+ if len(x) == 0 or len(label_stats) == 0:
+ continue
+ # Plot figure
+ fig = plt.figure()
+ ind = np.arange(len(x)) # the x locations for the groups
+ width = 0.5 # the width of the bars: can also be len(x) sequence
+ bars = []
+ bottom_bars = []
+ for _i in label_stats[0]:
+ bottom_bars.append(0)
+ for i in range(0, len(label_stats)):
+ # Assign a unique color to this label.
+ if labels[i] not in bar_color_maps:
+ bar_color_maps[labels[i]] = colors[color_index]
+ color_index += 1
+ p = plt.bar(
+ ind,
+ label_stats[i],
+ width,
+ bottom=bottom_bars,
+ color=bar_color_maps[labels[i]],
+ )
+ bars.append(p[0])
+ for j in range(len(label_stats[i])):
+ bottom_bars[j] += label_stats[i][j]
+ plt.xlabel(xlabel)
+ plt.ylabel(ylabel)
+ plt.xticks(
+ ind, [x_prefix + x[i] for i in range(len(x))], rotation=20, fontsize=8
+ )
+ plt.legend(bars, labels)
+ plt.title("{} filename:{}".format(title, file))
+ pdf.savefig(fig)
+ pdf.close()
+
+
+def plot_heatmap(csv_result_dir, output_result_dir, filename_suffix, pdf_name, title):
+ pdf = matplotlib.backends.backend_pdf.PdfPages(
+ "{}/{}".format(output_result_dir, pdf_name)
+ )
+ for file in os.listdir(csv_result_dir):
+ if not file.endswith(filename_suffix):
+ continue
+ csv_file_name = "{}/{}".format(csv_result_dir, file)
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ corr_table = pd.read_csv(csv_file_name)
+ corr_table = corr_table.pivot("label", "corr", "value")
+ fig = plt.figure()
+ sns.heatmap(corr_table, annot=True, linewidths=0.5, fmt=".2")
+ plt.title("{} filename:{}".format(title, file))
+ pdf.savefig(fig)
+ pdf.close()
+
+
+def plot_timeline(csv_result_dir, output_result_dir):
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="access_timeline",
+ pdf_name="access_time.pdf",
+ xlabel="Time",
+ ylabel="Throughput",
+ title="Access timeline with group by label",
+ vertical=False,
+ legend=True,
+ )
+
+
+def convert_to_0_if_nan(n):
+ if math.isnan(n):
+ return 0.0
+ return n
+
+
+def plot_correlation(csv_result_dir, output_result_dir):
+ # Processing the correlation input first.
+ label_str_file = {}
+ for file in os.listdir(csv_result_dir):
+ if not file.endswith("correlation_input"):
+ continue
+ csv_file_name = "{}/{}".format(csv_result_dir, file)
+ print("Processing file {}/{}".format(csv_result_dir, file))
+ corr_table = pd.read_csv(csv_file_name)
+ label_str = file.split("_")[0]
+ label = file[len(label_str) + 1 :]
+ label = label[: len(label) - len("_correlation_input")]
+
+ output_file = "{}/{}_correlation_output".format(csv_result_dir, label_str)
+ if output_file not in label_str_file:
+ f = open("{}/{}_correlation_output".format(csv_result_dir, label_str), "w+")
+ label_str_file[output_file] = f
+ f.write("label,corr,value\n")
+ f = label_str_file[output_file]
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "LA+A",
+ convert_to_0_if_nan(
+ corr_table["num_accesses_since_last_access"].corr(
+ corr_table["num_accesses_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "PA+A",
+ convert_to_0_if_nan(
+ corr_table["num_past_accesses"].corr(
+ corr_table["num_accesses_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "LT+A",
+ convert_to_0_if_nan(
+ corr_table["elapsed_time_since_last_access"].corr(
+ corr_table["num_accesses_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "LA+T",
+ convert_to_0_if_nan(
+ corr_table["num_accesses_since_last_access"].corr(
+ corr_table["elapsed_time_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "LT+T",
+ convert_to_0_if_nan(
+ corr_table["elapsed_time_since_last_access"].corr(
+ corr_table["elapsed_time_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ f.write(
+ "{},{},{}\n".format(
+ label,
+ "PA+T",
+ convert_to_0_if_nan(
+ corr_table["num_past_accesses"].corr(
+ corr_table["elapsed_time_till_next_access"], method="spearman"
+ )
+ ),
+ )
+ )
+ for label_str in label_str_file:
+ label_str_file[label_str].close()
+
+ plot_heatmap(
+ csv_result_dir,
+ output_result_dir,
+ "correlation_output",
+ "correlation.pdf",
+ "Correlation",
+ )
+
+
+def plot_reuse_graphs(csv_result_dir, output_result_dir):
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="avg_reuse_interval_naccesses",
+ pdf_name="avg_reuse_interval_naccesses.pdf",
+ xlabel="",
+ ylabel="Percentage of accesses",
+ title="Average reuse interval",
+ vertical=True,
+ x_prefix="< ",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="avg_reuse_interval",
+ pdf_name="avg_reuse_interval.pdf",
+ xlabel="",
+ ylabel="Percentage of blocks",
+ title="Average reuse interval",
+ vertical=True,
+ x_prefix="< ",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="access_reuse_interval",
+ pdf_name="reuse_interval.pdf",
+ xlabel="Seconds",
+ ylabel="Percentage of accesses",
+ title="Reuse interval",
+ vertical=True,
+ x_prefix="< ",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="reuse_lifetime",
+ pdf_name="reuse_lifetime.pdf",
+ xlabel="Seconds",
+ ylabel="Percentage of blocks",
+ title="Reuse lifetime",
+ vertical=True,
+ x_prefix="< ",
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="reuse_blocks_timeline",
+ pdf_name="reuse_blocks_timeline.pdf",
+ xlabel="",
+ ylabel="Percentage of blocks",
+ title="Reuse blocks timeline",
+ vertical=False,
+ legend=False,
+ )
+
+
+def plot_percentage_access_summary(csv_result_dir, output_result_dir):
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="percentage_of_accesses_summary",
+ pdf_name="percentage_access.pdf",
+ xlabel="",
+ ylabel="Percentage of accesses",
+ title="",
+ vertical=True,
+ x_prefix="",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="percent_ref_keys",
+ pdf_name="percent_ref_keys.pdf",
+ xlabel="",
+ ylabel="Percentage of blocks",
+ title="",
+ vertical=True,
+ x_prefix="",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="percent_data_size_on_ref_keys",
+ pdf_name="percent_data_size_on_ref_keys.pdf",
+ xlabel="",
+ ylabel="Percentage of blocks",
+ title="",
+ vertical=True,
+ x_prefix="",
+ )
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="percent_accesses_on_ref_keys",
+ pdf_name="percent_accesses_on_ref_keys.pdf",
+ xlabel="",
+ ylabel="Percentage of blocks",
+ title="",
+ vertical=True,
+ x_prefix="",
+ )
+
+
+def plot_access_count_summary(csv_result_dir, output_result_dir):
+ plot_stacked_bar_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_suffix="access_count_summary",
+ pdf_name="access_count_summary.pdf",
+ xlabel="Access count",
+ ylabel="Percentage of blocks",
+ title="",
+ vertical=True,
+ x_prefix="< ",
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="skewness",
+ pdf_name="skew.pdf",
+ xlabel="",
+ ylabel="Percentage of accesses",
+ title="Skewness",
+ vertical=True,
+ legend=False,
+ )
+
+
+def plot_miss_ratio_timeline(csv_result_dir, output_result_dir):
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="3600_miss_ratio_timeline",
+ pdf_name="miss_ratio_timeline.pdf",
+ xlabel="Time",
+ ylabel="Miss Ratio (%)",
+ title="Miss ratio timeline",
+ vertical=False,
+ legend=True,
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="3600_miss_timeline",
+ pdf_name="miss_timeline.pdf",
+ xlabel="Time",
+ ylabel="# of misses ",
+ title="Miss timeline",
+ vertical=False,
+ legend=True,
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="3600_miss_timeline",
+ pdf_name="miss_timeline.pdf",
+ xlabel="Time",
+ ylabel="# of misses ",
+ title="Miss timeline",
+ vertical=False,
+ legend=True,
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="3600_policy_timeline",
+ pdf_name="policy_timeline.pdf",
+ xlabel="Time",
+ ylabel="# of times a policy is selected ",
+ title="Policy timeline",
+ vertical=False,
+ legend=True,
+ )
+ plot_line_charts(
+ csv_result_dir,
+ output_result_dir,
+ filename_prefix="",
+ filename_suffix="3600_policy_ratio_timeline",
+ pdf_name="policy_ratio_timeline.pdf",
+ xlabel="Time",
+ ylabel="Percentage of times a policy is selected ",
+ title="Policy timeline",
+ vertical=False,
+ legend=True,
+ )
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ print(
+ "Must provide two arguments: \n"
+ "1) The directory that saves a list of "
+ "directories which contain block cache trace analyzer result files. \n"
+ "2) the directory to save plotted graphs. \n"
+ )
+ exit(1)
+ csv_result_dir = sys.argv[1]
+ output_result_dir = sys.argv[2]
+ print(
+ "Processing directory {} and save graphs to {}.".format(
+ csv_result_dir, output_result_dir
+ )
+ )
+ for csv_relative_dir in os.listdir(csv_result_dir):
+ csv_abs_dir = csv_result_dir + "/" + csv_relative_dir
+ result_dir = output_result_dir + "/" + csv_relative_dir
+ if not os.path.isdir(csv_abs_dir):
+ print("{} is not a directory".format(csv_abs_dir))
+ continue
+ print("Processing experiment dir: {}".format(csv_relative_dir))
+ if not os.path.exists(result_dir):
+ os.makedirs(result_dir)
+ plot_access_count_summary(csv_abs_dir, result_dir)
+ plot_timeline(csv_abs_dir, result_dir)
+ plot_miss_ratio_timeline(csv_result_dir, output_result_dir)
+ plot_correlation(csv_abs_dir, result_dir)
+ plot_reuse_graphs(csv_abs_dir, result_dir)
+ plot_percentage_access_summary(csv_abs_dir, result_dir)
+ plot_miss_stats_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="",
+ file_suffix="mrc",
+ ylabel="Miss ratio (%)",
+ pdf_file_name="mrc",
+ )
+ plot_miss_stats_diff_lru_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="",
+ file_suffix="mrc",
+ ylabel="Miss ratio (%)",
+ pdf_file_name="mrc_diff_lru",
+ )
+ # The following stats are only available in pysim.
+ for time_unit in ["1", "60", "3600"]:
+ plot_miss_stats_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="ml_{}_".format(time_unit),
+ file_suffix="p95mb",
+ ylabel="p95 number of byte miss per {} seconds".format(time_unit),
+ pdf_file_name="p95mb_per{}_seconds".format(time_unit),
+ )
+ plot_miss_stats_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="ml_{}_".format(time_unit),
+ file_suffix="avgmb",
+ ylabel="Average number of byte miss per {} seconds".format(time_unit),
+ pdf_file_name="avgmb_per{}_seconds".format(time_unit),
+ )
+ plot_miss_stats_diff_lru_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="ml_{}_".format(time_unit),
+ file_suffix="p95mb",
+ ylabel="p95 number of byte miss per {} seconds".format(time_unit),
+ pdf_file_name="p95mb_per{}_seconds_diff_lru".format(time_unit),
+ )
+ plot_miss_stats_diff_lru_graphs(
+ csv_abs_dir,
+ result_dir,
+ file_prefix="ml_{}_".format(time_unit),
+ file_suffix="avgmb",
+ ylabel="Average number of byte miss per {} seconds".format(time_unit),
+ pdf_file_name="avgmb_per{}_seconds_diff_lru".format(time_unit),
+ )
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
new file mode 100644
index 00000000..1dc72362
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
@@ -0,0 +1,717 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr,
+ "Please install gflags to run block_cache_trace_analyzer_test\n");
+ return 1;
+}
+#else
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#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/block_cache_analyzer/block_cache_trace_analyzer.h"
+#include "trace_replay/block_cache_tracer.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 kSSTStoringEvenKeys = 100;
+const uint64_t kSSTStoringOddKeys = 101;
+const std::string kRefKeyPrefix = "test-get-";
+const uint64_t kNumKeysInBlock = 1024;
+const int kMaxArgCount = 100;
+const size_t kArgBufferSize = 100000;
+} // namespace
+
+class BlockCacheTracerTest : public testing::Test {
+ public:
+ BlockCacheTracerTest() {
+ test_path_ = test::PerThreadDBPath("block_cache_tracer_test");
+ env_ = ROCKSDB_NAMESPACE::Env::Default();
+ EXPECT_OK(env_->CreateDir(test_path_));
+ trace_file_path_ = test_path_ + "/block_cache_trace";
+ block_cache_sim_config_path_ = test_path_ + "/block_cache_sim_config";
+ timeline_labels_ =
+ "block,all,cf,sst,level,bt,caller,cf_sst,cf_level,cf_bt,cf_caller";
+ reuse_distance_labels_ =
+ "block,all,cf,sst,level,bt,caller,cf_sst,cf_level,cf_bt,cf_caller";
+ reuse_distance_buckets_ = "1,1K,1M,1G";
+ reuse_interval_labels_ = "block,all,cf,sst,level,bt,cf_sst,cf_level,cf_bt";
+ reuse_interval_buckets_ = "1,10,100,1000";
+ reuse_lifetime_labels_ = "block,all,cf,sst,level,bt,cf_sst,cf_level,cf_bt";
+ reuse_lifetime_buckets_ = "1,10,100,1000";
+ analyzing_callers_ = "Get,Iterator";
+ access_count_buckets_ = "2,3,4,5,10";
+ analyze_get_spatial_locality_labels_ = "all";
+ analyze_get_spatial_locality_buckets_ = "10,20,30,40,50,60,70,80,90,100";
+ }
+
+ ~BlockCacheTracerTest() override {
+ if (getenv("KEEP_DB")) {
+ printf("The trace file is still at %s\n", trace_file_path_.c_str());
+ return;
+ }
+ 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;
+ }
+ // This cannot happend.
+ 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;
+ uint64_t timestamp = (key_id + 1) * kMicrosInSecond;
+ 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 = timestamp;
+ record.cf_id = kCFId;
+ record.cf_name = kDefaultColumnFamilyName;
+ record.caller = GetCaller(key_id);
+ record.level = kLevel;
+ if (key_id % 2 == 0) {
+ record.sst_fd_number = kSSTStoringEvenKeys;
+ } else {
+ record.sst_fd_number = kSSTStoringOddKeys;
+ }
+ record.is_cache_hit = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ // 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) + std::string(8, 0);
+ record.referenced_key_exist_in_block = Boolean::kTrue;
+ record.num_keys_in_block = kNumKeysInBlock;
+ ASSERT_OK(writer->WriteBlockAccess(
+ record, record.block_key, record.cf_name, record.referenced_key));
+ }
+ }
+
+ void AssertBlockAccessInfo(
+ uint32_t key_id, TraceType type,
+ const std::map<std::string, BlockAccessInfo>& block_access_info_map) {
+ auto key_id_str = kBlockKeyPrefix + std::to_string(key_id);
+ ASSERT_TRUE(block_access_info_map.find(key_id_str) !=
+ block_access_info_map.end());
+ auto& block_access_info = block_access_info_map.find(key_id_str)->second;
+ ASSERT_EQ(1, block_access_info.num_accesses);
+ ASSERT_EQ(kBlockSize + key_id, block_access_info.block_size);
+ ASSERT_GT(block_access_info.first_access_time, 0);
+ ASSERT_GT(block_access_info.last_access_time, 0);
+ ASSERT_EQ(1, block_access_info.caller_num_access_map.size());
+ TableReaderCaller expected_caller = GetCaller(key_id);
+ ASSERT_TRUE(block_access_info.caller_num_access_map.find(expected_caller) !=
+ block_access_info.caller_num_access_map.end());
+ ASSERT_EQ(
+ 1,
+ block_access_info.caller_num_access_map.find(expected_caller)->second);
+
+ if ((expected_caller == TableReaderCaller::kUserGet ||
+ expected_caller == TableReaderCaller::kUserMultiGet) &&
+ type == TraceType::kBlockTraceDataBlock) {
+ ASSERT_EQ(kNumKeysInBlock, block_access_info.num_keys);
+ ASSERT_EQ(1, block_access_info.key_num_access_map.size());
+ ASSERT_EQ(0, block_access_info.non_exist_key_num_access_map.size());
+ ASSERT_EQ(1, block_access_info.num_referenced_key_exist_in_block);
+ }
+ }
+
+ void RunBlockCacheTraceAnalyzer() {
+ std::vector<std::string> params = {
+ "./block_cache_trace_analyzer",
+ "-block_cache_trace_path=" + trace_file_path_,
+ "-block_cache_sim_config_path=" + block_cache_sim_config_path_,
+ "-block_cache_analysis_result_dir=" + test_path_,
+ "-print_block_size_stats",
+ "-print_access_count_stats",
+ "-print_data_block_access_count_stats",
+ "-cache_sim_warmup_seconds=0",
+ "-analyze_bottom_k_access_count_blocks=5",
+ "-analyze_top_k_access_count_blocks=5",
+ "-analyze_blocks_reuse_k_reuse_window=5",
+ "-timeline_labels=" + timeline_labels_,
+ "-reuse_distance_labels=" + reuse_distance_labels_,
+ "-reuse_distance_buckets=" + reuse_distance_buckets_,
+ "-reuse_interval_labels=" + reuse_interval_labels_,
+ "-reuse_interval_buckets=" + reuse_interval_buckets_,
+ "-reuse_lifetime_labels=" + reuse_lifetime_labels_,
+ "-reuse_lifetime_buckets=" + reuse_lifetime_buckets_,
+ "-analyze_callers=" + analyzing_callers_,
+ "-access_count_buckets=" + access_count_buckets_,
+ "-analyze_get_spatial_locality_labels=" +
+ analyze_get_spatial_locality_labels_,
+ "-analyze_get_spatial_locality_buckets=" +
+ analyze_get_spatial_locality_buckets_,
+ "-analyze_correlation_coefficients_labels=all",
+ "-skew_labels=all",
+ "-skew_buckets=10,50,100"};
+ 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<int>(arg.size()) + 1;
+ }
+ ASSERT_EQ(0,
+ ROCKSDB_NAMESPACE::block_cache_trace_analyzer_tool(argc, argv));
+ }
+
+ Env* env_;
+ EnvOptions env_options_;
+ std::string block_cache_sim_config_path_;
+ std::string trace_file_path_;
+ std::string test_path_;
+ std::string timeline_labels_;
+ std::string reuse_distance_labels_;
+ std::string reuse_distance_buckets_;
+ std::string reuse_interval_labels_;
+ std::string reuse_interval_buckets_;
+ std::string reuse_lifetime_labels_;
+ std::string reuse_lifetime_buckets_;
+ std::string analyzing_callers_;
+ std::string access_count_buckets_;
+ std::string analyze_get_spatial_locality_labels_;
+ std::string analyze_get_spatial_locality_buckets_;
+};
+
+TEST_F(BlockCacheTracerTest, BlockCacheAnalyzer) {
+ {
+ // Generate a trace file.
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ BlockCacheTraceWriter writer(env_, trace_opt, std::move(trace_writer));
+ ASSERT_OK(writer.WriteHeader());
+ WriteBlockAccess(&writer, 0, TraceType::kBlockTraceDataBlock, 50);
+ ASSERT_OK(env_->FileExists(trace_file_path_));
+ }
+ {
+ // Generate a cache sim config.
+ std::string config = "lru,1,0,1K,1M,1G";
+ std::ofstream out(block_cache_sim_config_path_);
+ ASSERT_TRUE(out.is_open());
+ out << config << std::endl;
+ out.close();
+ }
+ RunBlockCacheTraceAnalyzer();
+ {
+ // Validate the cache miss ratios.
+ std::vector<uint64_t> expected_capacities{1024, 1024 * 1024,
+ 1024 * 1024 * 1024};
+ const std::string mrc_path = test_path_ + "/49_50_mrc";
+ std::ifstream infile(mrc_path);
+ uint32_t config_index = 0;
+ std::string line;
+ // Read header.
+ ASSERT_TRUE(getline(infile, line));
+ while (getline(infile, line)) {
+ std::stringstream ss(line);
+ std::vector<std::string> result_strs;
+ while (ss.good()) {
+ std::string substr;
+ getline(ss, substr, ',');
+ result_strs.push_back(substr);
+ }
+ ASSERT_EQ(6, result_strs.size());
+ ASSERT_LT(config_index, expected_capacities.size());
+ ASSERT_EQ("lru", result_strs[0]); // cache_name
+ ASSERT_EQ("1", result_strs[1]); // num_shard_bits
+ ASSERT_EQ("0", result_strs[2]); // ghost_cache_capacity
+ ASSERT_EQ(std::to_string(expected_capacities[config_index]),
+ result_strs[3]); // cache_capacity
+ ASSERT_EQ("100.0000", result_strs[4]); // miss_ratio
+ ASSERT_EQ("50", result_strs[5]); // number of accesses.
+ config_index++;
+ }
+ ASSERT_EQ(expected_capacities.size(), config_index);
+ infile.close();
+ ASSERT_OK(env_->DeleteFile(mrc_path));
+
+ const std::vector<std::string> time_units{"1", "60", "3600"};
+ expected_capacities.push_back(port::kMaxUint64);
+ for (auto const& expected_capacity : expected_capacities) {
+ for (auto const& time_unit : time_units) {
+ const std::string miss_ratio_timeline_path =
+ test_path_ + "/" + std::to_string(expected_capacity) + "_" +
+ time_unit + "_miss_ratio_timeline";
+ std::ifstream mrt_file(miss_ratio_timeline_path);
+ // Read header.
+ ASSERT_TRUE(getline(mrt_file, line));
+ ASSERT_TRUE(getline(mrt_file, line));
+ std::stringstream ss(line);
+ bool read_header = false;
+ while (ss.good()) {
+ std::string substr;
+ getline(ss, substr, ',');
+ if (!read_header) {
+ if (expected_capacity == port::kMaxUint64) {
+ ASSERT_EQ("trace", substr);
+ } else {
+ ASSERT_EQ("lru-1-0", substr);
+ }
+ read_header = true;
+ continue;
+ }
+ ASSERT_DOUBLE_EQ(100.0, ParseDouble(substr));
+ }
+ ASSERT_FALSE(getline(mrt_file, line));
+ mrt_file.close();
+ ASSERT_OK(env_->DeleteFile(miss_ratio_timeline_path));
+ }
+ for (auto const& time_unit : time_units) {
+ const std::string miss_timeline_path =
+ test_path_ + "/" + std::to_string(expected_capacity) + "_" +
+ time_unit + "_miss_timeline";
+ std::ifstream mt_file(miss_timeline_path);
+ // Read header.
+ ASSERT_TRUE(getline(mt_file, line));
+ ASSERT_TRUE(getline(mt_file, line));
+ std::stringstream ss(line);
+ uint32_t num_misses = 0;
+ while (ss.good()) {
+ std::string substr;
+ getline(ss, substr, ',');
+ if (num_misses == 0) {
+ if (expected_capacity == port::kMaxUint64) {
+ ASSERT_EQ("trace", substr);
+ } else {
+ ASSERT_EQ("lru-1-0", substr);
+ }
+ num_misses++;
+ continue;
+ }
+ num_misses += ParseInt(substr);
+ }
+ ASSERT_EQ(51, num_misses);
+ ASSERT_FALSE(getline(mt_file, line));
+ mt_file.close();
+ ASSERT_OK(env_->DeleteFile(miss_timeline_path));
+ }
+ }
+ }
+ {
+ // Validate the skewness csv file.
+ const std::string skewness_file_path = test_path_ + "/all_skewness";
+ std::ifstream skew_file(skewness_file_path);
+ // Read header.
+ std::string line;
+ ASSERT_TRUE(getline(skew_file, line));
+ std::stringstream ss(line);
+ double sum_percent = 0;
+ while (getline(skew_file, line)) {
+ std::stringstream ss_naccess(line);
+ std::string substr;
+ bool read_label = false;
+ while (ss_naccess.good()) {
+ ASSERT_TRUE(getline(ss_naccess, substr, ','));
+ if (!read_label) {
+ read_label = true;
+ continue;
+ }
+ sum_percent += ParseDouble(substr);
+ }
+ }
+ ASSERT_EQ(100.0, sum_percent);
+ ASSERT_FALSE(getline(skew_file, line));
+ skew_file.close();
+ ASSERT_OK(env_->DeleteFile(skewness_file_path));
+ }
+ {
+ // Validate the timeline csv files.
+ const std::vector<std::string> time_units{"_60", "_3600"};
+ const std::vector<std::string> user_access_only_flags{"user_access_only_",
+ "all_access_"};
+ for (auto const& user_access_only : user_access_only_flags) {
+ for (auto const& unit : time_units) {
+ std::stringstream ss(timeline_labels_);
+ while (ss.good()) {
+ std::string l;
+ ASSERT_TRUE(getline(ss, l, ','));
+ if (l.find("block") == std::string::npos) {
+ if (user_access_only != "all_access_") {
+ continue;
+ }
+ }
+ const std::string timeline_file = test_path_ + "/" +
+ user_access_only + l + unit +
+ "_access_timeline";
+ std::ifstream infile(timeline_file);
+ std::string line;
+ const uint64_t expected_naccesses = 50;
+ const uint64_t expected_user_accesses = 30;
+ ASSERT_TRUE(getline(infile, line)) << timeline_file;
+ uint32_t naccesses = 0;
+ while (getline(infile, line)) {
+ std::stringstream ss_naccess(line);
+ std::string substr;
+ bool read_label = false;
+ while (ss_naccess.good()) {
+ ASSERT_TRUE(getline(ss_naccess, substr, ','));
+ if (!read_label) {
+ read_label = true;
+ continue;
+ }
+ naccesses += ParseUint32(substr);
+ }
+ }
+ if (user_access_only == "user_access_only_") {
+ ASSERT_EQ(expected_user_accesses, naccesses) << timeline_file;
+ } else {
+ ASSERT_EQ(expected_naccesses, naccesses) << timeline_file;
+ }
+ ASSERT_OK(env_->DeleteFile(timeline_file));
+ }
+ }
+ }
+ }
+ {
+ // Validate the reuse_interval and reuse_distance csv files.
+ std::map<std::string, std::string> test_reuse_csv_files;
+ test_reuse_csv_files["_access_reuse_interval"] = reuse_interval_labels_;
+ test_reuse_csv_files["_reuse_distance"] = reuse_distance_labels_;
+ test_reuse_csv_files["_reuse_lifetime"] = reuse_lifetime_labels_;
+ test_reuse_csv_files["_avg_reuse_interval"] = reuse_interval_labels_;
+ test_reuse_csv_files["_avg_reuse_interval_naccesses"] =
+ reuse_interval_labels_;
+ for (auto const& test : test_reuse_csv_files) {
+ const std::string& file_suffix = test.first;
+ const std::string& labels = test.second;
+ const uint32_t expected_num_rows = 5;
+ std::stringstream ss(labels);
+ while (ss.good()) {
+ std::string l;
+ ASSERT_TRUE(getline(ss, l, ','));
+ const std::string reuse_csv_file = test_path_ + "/" + l + file_suffix;
+ std::ifstream infile(reuse_csv_file);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line));
+ double npercentage = 0;
+ uint32_t nrows = 0;
+ while (getline(infile, line)) {
+ std::stringstream ss_naccess(line);
+ bool label_read = false;
+ nrows++;
+ while (ss_naccess.good()) {
+ std::string substr;
+ ASSERT_TRUE(getline(ss_naccess, substr, ','));
+ if (!label_read) {
+ label_read = true;
+ continue;
+ }
+ npercentage += ParseDouble(substr);
+ }
+ }
+ ASSERT_EQ(expected_num_rows, nrows);
+ if ("_reuse_lifetime" == test.first ||
+ "_avg_reuse_interval" == test.first ||
+ "_avg_reuse_interval_naccesses" == test.first) {
+ ASSERT_EQ(100, npercentage) << reuse_csv_file;
+ } else {
+ ASSERT_LT(npercentage, 0);
+ }
+ ASSERT_OK(env_->DeleteFile(reuse_csv_file));
+ }
+ }
+ }
+
+ {
+ // Validate the percentage of accesses summary.
+ const std::string percent_access_summary_file =
+ test_path_ + "/percentage_of_accesses_summary";
+ std::ifstream infile(percent_access_summary_file);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line));
+ std::set<std::string> callers;
+ std::set<std::string> expected_callers{"Get", "MultiGet", "Iterator",
+ "Prefetch", "Compaction"};
+ while (getline(infile, line)) {
+ std::stringstream caller_percent(line);
+ std::string caller;
+ ASSERT_TRUE(getline(caller_percent, caller, ','));
+ std::string percent;
+ ASSERT_TRUE(getline(caller_percent, percent, ','));
+ ASSERT_FALSE(caller_percent.good());
+ callers.insert(caller);
+ ASSERT_EQ(20, ParseDouble(percent));
+ }
+ ASSERT_EQ(expected_callers.size(), callers.size());
+ for (auto caller : callers) {
+ ASSERT_TRUE(expected_callers.find(caller) != expected_callers.end());
+ }
+ ASSERT_OK(env_->DeleteFile(percent_access_summary_file));
+ }
+ {
+ // Validate the percentage of accesses summary by analyzing callers.
+ std::stringstream analyzing_callers(analyzing_callers_);
+ while (analyzing_callers.good()) {
+ std::string caller;
+ ASSERT_TRUE(getline(analyzing_callers, caller, ','));
+ std::vector<std::string> breakdowns{"level", "bt"};
+ for (auto breakdown : breakdowns) {
+ const std::string file_name = test_path_ + "/" + caller + "_" +
+ breakdown +
+ "_percentage_of_accesses_summary";
+ std::ifstream infile(file_name);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line));
+ double sum = 0;
+ while (getline(infile, line)) {
+ std::stringstream label_percent(line);
+ std::string label;
+ ASSERT_TRUE(getline(label_percent, label, ','));
+ std::string percent;
+ ASSERT_TRUE(getline(label_percent, percent, ','));
+ ASSERT_FALSE(label_percent.good());
+ sum += ParseDouble(percent);
+ }
+ ASSERT_EQ(100, sum);
+ ASSERT_OK(env_->DeleteFile(file_name));
+ }
+ }
+ }
+ const std::vector<std::string> access_types{"user_access_only", "all_access"};
+ const std::vector<std::string> prefix{"bt", "cf"};
+ for (auto const& pre : prefix) {
+ for (auto const& access_type : access_types) {
+ {
+ // Validate the access count summary.
+ const std::string bt_access_count_summary = test_path_ + "/" + pre +
+ "_" + access_type +
+ "_access_count_summary";
+ std::ifstream infile(bt_access_count_summary);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line));
+ double sum_percent = 0;
+ while (getline(infile, line)) {
+ std::stringstream bt_percent(line);
+ std::string bt;
+ ASSERT_TRUE(getline(bt_percent, bt, ','));
+ std::string percent;
+ ASSERT_TRUE(getline(bt_percent, percent, ','));
+ sum_percent += ParseDouble(percent);
+ }
+ ASSERT_EQ(100.0, sum_percent);
+ ASSERT_OK(env_->DeleteFile(bt_access_count_summary));
+ }
+ }
+ }
+ for (auto const& access_type : access_types) {
+ std::vector<std::string> block_types{"Index", "Data", "Filter"};
+ for (auto block_type : block_types) {
+ // Validate reuse block timeline.
+ const std::string reuse_blocks_timeline = test_path_ + "/" + block_type +
+ "_" + access_type +
+ "_5_reuse_blocks_timeline";
+ std::ifstream infile(reuse_blocks_timeline);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line)) << reuse_blocks_timeline;
+ uint32_t index = 0;
+ while (getline(infile, line)) {
+ std::stringstream timeline(line);
+ bool start_time = false;
+ double sum = 0;
+ while (timeline.good()) {
+ std::string value;
+ ASSERT_TRUE(getline(timeline, value, ','));
+ if (!start_time) {
+ start_time = true;
+ continue;
+ }
+ sum += ParseDouble(value);
+ }
+ index++;
+ ASSERT_LT(sum, 100.0 * index + 1) << reuse_blocks_timeline;
+ }
+ ASSERT_OK(env_->DeleteFile(reuse_blocks_timeline));
+ }
+ }
+
+ std::stringstream ss(analyze_get_spatial_locality_labels_);
+ while (ss.good()) {
+ std::string l;
+ ASSERT_TRUE(getline(ss, l, ','));
+ const std::vector<std::string> spatial_locality_files{
+ "_percent_ref_keys", "_percent_accesses_on_ref_keys",
+ "_percent_data_size_on_ref_keys"};
+ for (auto const& spatial_locality_file : spatial_locality_files) {
+ const std::string filename = test_path_ + "/" + l + spatial_locality_file;
+ std::ifstream infile(filename);
+ std::string line;
+ ASSERT_TRUE(getline(infile, line));
+ double sum_percent = 0;
+ uint32_t nrows = 0;
+ while (getline(infile, line)) {
+ std::stringstream bt_percent(line);
+ std::string bt;
+ ASSERT_TRUE(getline(bt_percent, bt, ','));
+ std::string percent;
+ ASSERT_TRUE(getline(bt_percent, percent, ','));
+ sum_percent += ParseDouble(percent);
+ nrows++;
+ }
+ ASSERT_EQ(11, nrows);
+ ASSERT_EQ(100.0, sum_percent);
+ ASSERT_OK(env_->DeleteFile(filename));
+ }
+ }
+ ASSERT_OK(env_->DeleteFile(block_cache_sim_config_path_));
+}
+
+TEST_F(BlockCacheTracerTest, MixedBlocks) {
+ {
+ // Generate a trace file containing a mix of blocks.
+ // It contains two SST files with 25 blocks of odd numbered block_key in
+ // kSSTStoringOddKeys and 25 blocks of even numbered blocks_key in
+ // kSSTStoringEvenKeys.
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ BlockCacheTraceWriter writer(env_, trace_opt, std::move(trace_writer));
+ ASSERT_OK(writer.WriteHeader());
+ // Write blocks of different types.
+ WriteBlockAccess(&writer, 0, TraceType::kBlockTraceUncompressionDictBlock,
+ 10);
+ WriteBlockAccess(&writer, 10, TraceType::kBlockTraceDataBlock, 10);
+ WriteBlockAccess(&writer, 20, TraceType::kBlockTraceFilterBlock, 10);
+ WriteBlockAccess(&writer, 30, TraceType::kBlockTraceIndexBlock, 10);
+ WriteBlockAccess(&writer, 40, TraceType::kBlockTraceRangeDeletionBlock, 10);
+ ASSERT_OK(env_->FileExists(trace_file_path_));
+ }
+
+ {
+ // Verify trace file is generated correctly.
+ std::unique_ptr<TraceReader> 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, header.rocksdb_major_version);
+ ASSERT_EQ(kMinorVersion, header.rocksdb_minor_version);
+ // Read blocks.
+ BlockCacheTraceAnalyzer analyzer(
+ trace_file_path_,
+ /*output_miss_ratio_curve_path=*/"",
+ /*human_readable_trace_file_path=*/"",
+ /*compute_reuse_distance=*/true,
+ /*mrc_only=*/false,
+ /*is_block_cache_human_readable_trace=*/false,
+ /*simulator=*/nullptr);
+ // The analyzer ends when it detects an incomplete access record.
+ ASSERT_EQ(Status::Incomplete(""), analyzer.Analyze());
+ const uint64_t expected_num_cfs = 1;
+ std::vector<uint64_t> expected_fds{kSSTStoringOddKeys, kSSTStoringEvenKeys};
+ const std::vector<TraceType> expected_types{
+ TraceType::kBlockTraceUncompressionDictBlock,
+ TraceType::kBlockTraceDataBlock, TraceType::kBlockTraceFilterBlock,
+ TraceType::kBlockTraceIndexBlock,
+ TraceType::kBlockTraceRangeDeletionBlock};
+ const uint64_t expected_num_keys_per_type = 5;
+
+ auto& stats = analyzer.TEST_cf_aggregates_map();
+ ASSERT_EQ(expected_num_cfs, stats.size());
+ ASSERT_TRUE(stats.find(kDefaultColumnFamilyName) != stats.end());
+ auto& cf_stats = stats.find(kDefaultColumnFamilyName)->second;
+ ASSERT_EQ(expected_fds.size(), cf_stats.fd_aggregates_map.size());
+ for (auto fd_id : expected_fds) {
+ ASSERT_TRUE(cf_stats.fd_aggregates_map.find(fd_id) !=
+ cf_stats.fd_aggregates_map.end());
+ ASSERT_EQ(kLevel, cf_stats.fd_aggregates_map.find(fd_id)->second.level);
+ auto& block_type_aggregates_map = cf_stats.fd_aggregates_map.find(fd_id)
+ ->second.block_type_aggregates_map;
+ ASSERT_EQ(expected_types.size(), block_type_aggregates_map.size());
+ uint32_t key_id = 0;
+ for (auto type : expected_types) {
+ ASSERT_TRUE(block_type_aggregates_map.find(type) !=
+ block_type_aggregates_map.end());
+ auto& block_access_info_map =
+ block_type_aggregates_map.find(type)->second.block_access_info_map;
+ // Each block type has 5 blocks.
+ ASSERT_EQ(expected_num_keys_per_type, block_access_info_map.size());
+ for (uint32_t i = 0; i < 10; i++) {
+ // Verify that odd numbered blocks are stored in kSSTStoringOddKeys
+ // and even numbered blocks are stored in kSSTStoringEvenKeys.
+ auto key_id_str = kBlockKeyPrefix + std::to_string(key_id);
+ if (fd_id == kSSTStoringOddKeys) {
+ if (key_id % 2 == 1) {
+ AssertBlockAccessInfo(key_id, type, block_access_info_map);
+ } else {
+ ASSERT_TRUE(block_access_info_map.find(key_id_str) ==
+ block_access_info_map.end());
+ }
+ } else {
+ if (key_id % 2 == 1) {
+ ASSERT_TRUE(block_access_info_map.find(key_id_str) ==
+ block_access_info_map.end());
+ } else {
+ AssertBlockAccessInfo(key_id, type, block_access_info_map);
+ }
+ }
+ key_id++;
+ }
+ }
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+#endif // GFLAG
+#else
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "block_cache_trace_analyzer_test is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc
new file mode 100644
index 00000000..44fec559
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else // GFLAGS
+#include "tools/block_cache_analyzer/block_cache_trace_analyzer.h"
+int main(int argc, char** argv) {
+ return ROCKSDB_NAMESPACE::block_cache_trace_analyzer_tool(argc, argv);
+}
+#endif // GFLAGS
+#else // ROCKSDB_LITE
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return 1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/check_all_python.py b/storage/rocksdb/rocksdb/tools/check_all_python.py
new file mode 100644
index 00000000..17fe95ea
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/check_all_python.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python2
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+import glob
+
+# Checks that all python files in the repository are at least free of syntax
+# errors. This provides a minimal pre-/post-commit check for python file
+# modifications.
+
+filenames = []
+# Avoid scanning all of ./ because there might be other external repos
+# linked in.
+for base in ["buckifier", "build_tools", "coverage", "tools"]:
+ # Clean this up when we finally upgrade to Python 3
+ for suff in ["*", "*/*", "*/*/*"]:
+ filenames += glob.glob(base + "/" + suff + ".py")
+
+for filename in filenames:
+ source = open(filename, 'r').read() + '\n'
+ # Parses and syntax checks the file, throwing on error. (No pyc written.)
+ _ = compile(source, filename, 'exec')
+
+print("No syntax errors in {0} .py files".format(len(filenames)))
diff --git a/storage/rocksdb/rocksdb/tools/check_format_compatible.sh b/storage/rocksdb/rocksdb/tools/check_format_compatible.sh
new file mode 100755
index 00000000..98c2bb5c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/check_format_compatible.sh
@@ -0,0 +1,191 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+# A shell script to load some pre generated data file to a DB using ldb tool
+# ./ldb needs to be avaible to be executed.
+#
+# Usage: <SCRIPT> [checkout]
+# `checkout` can be a tag, commit or branch name. Will build using it and check DBs generated by all previous branches (or tags for very old versions without branch) can be opened by it.
+# Return value 0 means all regression tests pass. 1 if not pass.
+
+scriptpath=`dirname $BASH_SOURCE`
+test_dir=${TEST_TMPDIR:-"/tmp"}"/format_compatible_check"
+script_copy_dir=$test_dir"/script_copy"
+input_data_path=$test_dir"/test_data_input/"
+
+mkdir $test_dir || true
+mkdir $input_data_path || true
+rm -rf $script_copy_dir
+cp $scriptpath $script_copy_dir -rf
+
+# Generate random files.
+for i in {1..6}
+do
+ input_data[$i]=$input_data_path/data$i
+ echo == Generating random input file ${input_data[$i]}
+ python - <<EOF
+import random
+random.seed($i)
+symbols=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
+with open('${input_data[$i]}', 'w') as f:
+ for i in range(1,1024):
+ k = ""
+ for j in range(1, random.randint(1,32)):
+ k=k + symbols[random.randint(0, len(symbols) - 1)]
+ vb = ""
+ for j in range(1, random.randint(0,128)):
+ vb = vb + symbols[random.randint(0, len(symbols) - 1)]
+ v = ""
+ for j in range(1, random.randint(1, 5)):
+ v = v + vb
+ print >> f, k + " ==> " + v
+EOF
+done
+
+# Generate file(s) with sorted keys.
+sorted_input_data=$input_data_path/sorted_data
+echo == Generating file with sorted keys ${sorted_input_data}
+python - <<EOF
+with open('${sorted_input_data}', 'w') as f:
+ for i in range(0,10):
+ k = str(i)
+ v = "value" + k
+ print >> f, k + " ==> " + v
+EOF
+
+declare -a backward_compatible_checkout_objs=("2.2.fb.branch" "2.3.fb.branch" "2.4.fb.branch" "2.5.fb.branch" "2.6.fb.branch" "2.7.fb.branch" "2.8.1.fb" "3.0.fb.branch" "3.1.fb" "3.2.fb" "3.3.fb" "3.4.fb" "3.5.fb" "3.6.fb" "3.7.fb" "3.8.fb" "3.9.fb")
+declare -a forward_compatible_checkout_objs=("4.2.fb" "4.3.fb" "4.4.fb" "4.5.fb" "4.6.fb" "4.7.fb" "4.8.fb" "4.9.fb" "4.10.fb" "4.11.fb" "4.12.fb" "4.13.fb" "5.0.fb" "5.1.fb" "5.2.fb" "5.3.fb" "5.4.fb" "5.5.fb" "5.6.fb" "5.7.fb" "5.8.fb" "5.9.fb" "5.10.fb")
+declare -a forward_compatible_with_options_checkout_objs=("5.11.fb" "5.12.fb" "5.13.fb" "5.14.fb" "5.15.fb" "5.16.fb" "5.17.fb" "5.18.fb" "6.0.fb" "6.1.fb" "6.2.fb" "6.3.fb" "6.4.fb" "6.5.fb")
+declare -a checkout_objs=(${backward_compatible_checkout_objs[@]} ${forward_compatible_checkout_objs[@]} ${forward_compatible_with_options_checkout_objs[@]})
+declare -a extern_sst_ingestion_compatible_checkout_objs=("5.14.fb" "5.15.fb" "5.16.fb" "5.17.fb" "5.18.fb" "6.0.fb" "6.1.fb" "6.2.fb" "6.3.fb" "6.4.fb" "6.5.fb")
+
+generate_db()
+{
+ set +e
+ $script_copy_dir/generate_random_db.sh $1 $2
+ if [ $? -ne 0 ]; then
+ echo ==== Error loading data from $2 to $1 ====
+ exit 1
+ fi
+ set -e
+}
+
+compare_db()
+{
+ set +e
+ $script_copy_dir/verify_random_db.sh $1 $2 $3 $4 $5
+ if [ $? -ne 0 ]; then
+ echo ==== Read different content from $1 and $2 or error happened. ====
+ exit 1
+ fi
+ set -e
+}
+
+write_external_sst()
+{
+ set +e
+ $script_copy_dir/write_external_sst.sh $1 $2 $3
+ if [ $? -ne 0 ]; then
+ echo ==== Error writing external SST file using data from $1 to $3 ====
+ exit 1
+ fi
+ set -e
+}
+
+ingest_external_sst()
+{
+ set +e
+ $script_copy_dir/ingest_external_sst.sh $1 $2
+ if [ $? -ne 0 ]; then
+ echo ==== Error ingesting external SST in $2 to DB at $1 ====
+ exit 1
+ fi
+ set -e
+}
+
+# Sandcastle sets us up with a remote that is just another directory on the same
+# machine and doesn't have our branches. Need to fetch them so checkout works.
+# Remote add may fail if added previously (we don't cleanup).
+git remote add github_origin "https://github.com/facebook/rocksdb.git"
+set -e
+https_proxy="fwdproxy:8080" git fetch github_origin
+
+# Compatibility test for external SST file ingestion
+for checkout_obj in "${extern_sst_ingestion_compatible_checkout_objs[@]}"
+do
+ echo == Generating DB with extern SST file in "$checkout_obj" ...
+ https_proxy="fwdproxy:8080" git checkout github_origin/$checkout_obj -b $checkout_obj
+ make clean
+ make ldb -j32
+ write_external_sst $input_data_path $test_dir/$checkout_obj $test_dir/$checkout_obj
+ ingest_external_sst $test_dir/$checkout_obj $test_dir/$checkout_obj
+done
+
+checkout_flag=${1:-"master"}
+
+echo == Building $checkout_flag debug
+https_proxy="fwdproxy:8080" git checkout github_origin/$checkout_flag -b tmp-$checkout_flag
+make clean
+make ldb -j32
+compare_base_db_dir=$test_dir"/base_db_dir"
+write_external_sst $input_data_path $compare_base_db_dir $compare_base_db_dir
+ingest_external_sst $compare_base_db_dir $compare_base_db_dir
+
+for checkout_obj in "${extern_sst_ingestion_compatible_checkout_objs[@]}"
+do
+ echo == Build "$checkout_obj" and try to open DB generated using $checkout_flag
+ git checkout $checkout_obj
+ make clean
+ make ldb -j32
+ compare_db $test_dir/$checkout_obj $compare_base_db_dir db_dump.txt 1 1
+ git checkout tmp-$checkout_flag
+ # Clean up
+ git branch -D $checkout_obj
+done
+
+echo == Finish compatibility test for SST ingestion.
+
+for checkout_obj in "${checkout_objs[@]}"
+do
+ echo == Generating DB from "$checkout_obj" ...
+ https_proxy="fwdproxy:8080" git checkout github_origin/$checkout_obj -b $checkout_obj
+ make clean
+ make ldb -j32
+ generate_db $input_data_path $test_dir/$checkout_obj
+done
+
+checkout_flag=${1:-"master"}
+
+echo == Building $checkout_flag debug
+git checkout tmp-$checkout_flag
+make clean
+make ldb -j32
+compare_base_db_dir=$test_dir"/base_db_dir"
+echo == Generate compare base DB to $compare_base_db_dir
+generate_db $input_data_path $compare_base_db_dir
+
+for checkout_obj in "${checkout_objs[@]}"
+do
+ echo == Opening DB from "$checkout_obj" using debug build of $checkout_flag ...
+ compare_db $test_dir/$checkout_obj $compare_base_db_dir db_dump.txt 1 0
+done
+
+for checkout_obj in "${forward_compatible_checkout_objs[@]}"
+do
+ echo == Build "$checkout_obj" and try to open DB generated using $checkout_flag...
+ git checkout $checkout_obj
+ make clean
+ make ldb -j32
+ compare_db $test_dir/$checkout_obj $compare_base_db_dir forward_${checkout_obj}_dump.txt 0
+done
+
+for checkout_obj in "${forward_compatible_with_options_checkout_objs[@]}"
+do
+ echo == Build "$checkout_obj" and try to open DB generated using $checkout_flag with its options...
+ git checkout $checkout_obj
+ make clean
+ make ldb -j32
+ compare_db $test_dir/$checkout_obj $compare_base_db_dir forward_${checkout_obj}_dump.txt 1 1
+done
+
+echo ==== Compatibility Test PASSED ====
diff --git a/storage/rocksdb/rocksdb/tools/db_bench.cc b/storage/rocksdb/rocksdb/tools/db_bench.cc
new file mode 100644
index 00000000..d4fb50c1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_bench.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+#include <rocksdb/db_bench_tool.h>
+int main(int argc, char** argv) {
+ return ROCKSDB_NAMESPACE::db_bench_tool(argc, argv);
+}
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/tools/db_bench_tool.cc b/storage/rocksdb/rocksdb/tools/db_bench_tool.cc
new file mode 100644
index 00000000..5c2ca01e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_bench_tool.cc
@@ -0,0 +1,7048 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifdef NUMA
+#include <numa.h>
+#include <numaif.h>
+#endif
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <atomic>
+#include <cinttypes>
+#include <condition_variable>
+#include <cstddef>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+#include "db/db_impl/db_impl.h"
+#include "db/malloc_stats.h"
+#include "db/version_set.h"
+#include "hdfs/env_hdfs.h"
+#include "monitoring/histogram.h"
+#include "monitoring/statistics.h"
+#include "options/cf_options.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/memtablerep.h"
+#include "rocksdb/options.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/persistent_cache.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/stats_history.h"
+#include "rocksdb/utilities/object_registry.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/options_util.h"
+#include "rocksdb/utilities/sim_cache.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/write_batch.h"
+#include "test_util/testutil.h"
+#include "test_util/transaction_test_util.h"
+#include "util/cast_util.h"
+#include "util/compression.h"
+#include "util/crc32c.h"
+#include "util/gflags_compat.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+#include "util/stderr_logger.h"
+#include "util/string_util.h"
+#include "util/xxhash.h"
+#include "utilities/blob_db/blob_db.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/bytesxor.h"
+#include "utilities/merge_operators/sortlist.h"
+#include "utilities/persistent_cache/block_cache_tier.h"
+
+#ifdef OS_WIN
+#include <io.h> // open/close
+#endif
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::RegisterFlagValidator;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_string(
+ benchmarks,
+ "fillseq,"
+ "fillseqdeterministic,"
+ "fillsync,"
+ "fillrandom,"
+ "filluniquerandomdeterministic,"
+ "overwrite,"
+ "readrandom,"
+ "newiterator,"
+ "newiteratorwhilewriting,"
+ "seekrandom,"
+ "seekrandomwhilewriting,"
+ "seekrandomwhilemerging,"
+ "readseq,"
+ "readreverse,"
+ "compact,"
+ "compactall,"
+ "multireadrandom,"
+ "mixgraph,"
+ "readseq,"
+ "readtorowcache,"
+ "readtocache,"
+ "readreverse,"
+ "readwhilewriting,"
+ "readwhilemerging,"
+ "readwhilescanning,"
+ "readrandomwriterandom,"
+ "updaterandom,"
+ "xorupdaterandom,"
+ "randomwithverify,"
+ "fill100K,"
+ "crc32c,"
+ "xxhash,"
+ "compress,"
+ "uncompress,"
+ "acquireload,"
+ "fillseekseq,"
+ "randomtransaction,"
+ "randomreplacekeys,"
+ "timeseries,"
+ "getmergeoperands",
+
+ "Comma-separated list of operations to run in the specified"
+ " order. Available benchmarks:\n"
+ "\tfillseq -- write N values in sequential key"
+ " order in async mode\n"
+ "\tfillseqdeterministic -- write N values in the specified"
+ " key order and keep the shape of the LSM tree\n"
+ "\tfillrandom -- write N values in random key order in async"
+ " mode\n"
+ "\tfilluniquerandomdeterministic -- write N values in a random"
+ " key order and keep the shape of the LSM tree\n"
+ "\toverwrite -- overwrite N values in random key order in"
+ " async mode\n"
+ "\tfillsync -- write N/1000 values in random key order in "
+ "sync mode\n"
+ "\tfill100K -- write N/1000 100K values in random order in"
+ " async mode\n"
+ "\tdeleteseq -- delete N keys in sequential order\n"
+ "\tdeleterandom -- delete N keys in random order\n"
+ "\treadseq -- read N times sequentially\n"
+ "\treadtocache -- 1 thread reading database sequentially\n"
+ "\treadreverse -- read N times in reverse order\n"
+ "\treadrandom -- read N times in random order\n"
+ "\treadmissing -- read N missing keys in random order\n"
+ "\treadwhilewriting -- 1 writer, N threads doing random "
+ "reads\n"
+ "\treadwhilemerging -- 1 merger, N threads doing random "
+ "reads\n"
+ "\treadwhilescanning -- 1 thread doing full table scan, "
+ "N threads doing random reads\n"
+ "\treadrandomwriterandom -- N threads doing random-read, "
+ "random-write\n"
+ "\tupdaterandom -- N threads doing read-modify-write for random "
+ "keys\n"
+ "\txorupdaterandom -- N threads doing read-XOR-write for "
+ "random keys\n"
+ "\tappendrandom -- N threads doing read-modify-write with "
+ "growing values\n"
+ "\tmergerandom -- same as updaterandom/appendrandom using merge"
+ " operator. "
+ "Must be used with merge_operator\n"
+ "\treadrandommergerandom -- perform N random read-or-merge "
+ "operations. Must be used with merge_operator\n"
+ "\tnewiterator -- repeated iterator creation\n"
+ "\tseekrandom -- N random seeks, call Next seek_nexts times "
+ "per seek\n"
+ "\tseekrandomwhilewriting -- seekrandom and 1 thread doing "
+ "overwrite\n"
+ "\tseekrandomwhilemerging -- seekrandom and 1 thread doing "
+ "merge\n"
+ "\tcrc32c -- repeated crc32c of 4K of data\n"
+ "\txxhash -- repeated xxHash of 4K of data\n"
+ "\tacquireload -- load N*1000 times\n"
+ "\tfillseekseq -- write N values in sequential key, then read "
+ "them by seeking to each key\n"
+ "\trandomtransaction -- execute N random transactions and "
+ "verify correctness\n"
+ "\trandomreplacekeys -- randomly replaces N keys by deleting "
+ "the old version and putting the new version\n\n"
+ "\ttimeseries -- 1 writer generates time series data "
+ "and multiple readers doing random reads on id\n\n"
+ "Meta operations:\n"
+ "\tcompact -- Compact the entire DB; If multiple, randomly choose one\n"
+ "\tcompactall -- Compact the entire DB\n"
+ "\tstats -- Print DB stats\n"
+ "\tresetstats -- Reset DB stats\n"
+ "\tlevelstats -- Print the number of files and bytes per level\n"
+ "\tsstables -- Print sstable info\n"
+ "\theapprofile -- Dump a heap profile (if supported by this port)\n"
+ "\treplay -- replay the trace file specified with trace_file\n"
+ "\tgetmergeoperands -- Insert lots of merge records which are a list of "
+ "sorted ints for a key and then compare performance of lookup for another "
+ "key "
+ "by doing a Get followed by binary searching in the large sorted list vs "
+ "doing a GetMergeOperands and binary searching in the operands which are"
+ "sorted sub-lists. The MergeOperator used is sortlist.h\n");
+
+DEFINE_int64(num, 1000000, "Number of key/values to place in database");
+
+DEFINE_int64(numdistinct, 1000,
+ "Number of distinct keys to use. Used in RandomWithVerify to "
+ "read/write on fewer keys so that gets are more likely to find the"
+ " key and puts are more likely to update the same key");
+
+DEFINE_int64(merge_keys, -1,
+ "Number of distinct keys to use for MergeRandom and "
+ "ReadRandomMergeRandom. "
+ "If negative, there will be FLAGS_num keys.");
+DEFINE_int32(num_column_families, 1, "Number of Column Families to use.");
+
+DEFINE_int32(
+ num_hot_column_families, 0,
+ "Number of Hot Column Families. If more than 0, only write to this "
+ "number of column families. After finishing all the writes to them, "
+ "create new set of column families and insert to them. Only used "
+ "when num_column_families > 1.");
+
+DEFINE_string(column_family_distribution, "",
+ "Comma-separated list of percentages, where the ith element "
+ "indicates the probability of an op using the ith column family. "
+ "The number of elements must be `num_hot_column_families` if "
+ "specified; otherwise, it must be `num_column_families`. The "
+ "sum of elements must be 100. E.g., if `num_column_families=4`, "
+ "and `num_hot_column_families=0`, a valid list could be "
+ "\"10,20,30,40\".");
+
+DEFINE_int64(reads, -1, "Number of read operations to do. "
+ "If negative, do FLAGS_num reads.");
+
+DEFINE_int64(deletes, -1, "Number of delete operations to do. "
+ "If negative, do FLAGS_num deletions.");
+
+DEFINE_int32(bloom_locality, 0, "Control bloom filter probes locality");
+
+DEFINE_int64(seed, 0, "Seed base for random number generators. "
+ "When 0 it is deterministic.");
+
+DEFINE_int32(threads, 1, "Number of concurrent threads to run.");
+
+DEFINE_int32(duration, 0, "Time in seconds for the random-ops tests to run."
+ " When 0 then num & reads determine the test duration");
+
+DEFINE_string(value_size_distribution_type, "fixed",
+ "Value size distribution type: fixed, uniform, normal");
+
+DEFINE_int32(value_size, 100, "Size of each value in fixed distribution");
+static unsigned int value_size = 100;
+
+DEFINE_int32(value_size_min, 100, "Min size of random value");
+
+DEFINE_int32(value_size_max, 102400, "Max size of random value");
+
+DEFINE_int32(seek_nexts, 0,
+ "How many times to call Next() after Seek() in "
+ "fillseekseq, seekrandom, seekrandomwhilewriting and "
+ "seekrandomwhilemerging");
+
+DEFINE_bool(reverse_iterator, false,
+ "When true use Prev rather than Next for iterators that do "
+ "Seek and then Next");
+
+DEFINE_int64(max_scan_distance, 0,
+ "Used to define iterate_upper_bound (or iterate_lower_bound "
+ "if FLAGS_reverse_iterator is set to true) when value is nonzero");
+
+DEFINE_bool(use_uint64_comparator, false, "use Uint64 user comparator");
+
+DEFINE_int64(batch_size, 1, "Batch size");
+
+static bool ValidateKeySize(const char* /*flagname*/, int32_t /*value*/) {
+ return true;
+}
+
+static bool ValidateUint32Range(const char* flagname, uint64_t value) {
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ fprintf(stderr, "Invalid value for --%s: %lu, overflow\n", flagname,
+ (unsigned long)value);
+ return false;
+ }
+ return true;
+}
+
+DEFINE_int32(key_size, 16, "size of each key");
+
+DEFINE_int32(num_multi_db, 0,
+ "Number of DBs used in the benchmark. 0 means single DB.");
+
+DEFINE_double(compression_ratio, 0.5, "Arrange to generate values that shrink"
+ " to this fraction of their original size after compression");
+
+DEFINE_double(read_random_exp_range, 0.0,
+ "Read random's key will be generated using distribution of "
+ "num * exp(-r) where r is uniform number from 0 to this value. "
+ "The larger the number is, the more skewed the reads are. "
+ "Only used in readrandom and multireadrandom benchmarks.");
+
+DEFINE_bool(histogram, false, "Print histogram of operation timings");
+
+DEFINE_bool(enable_numa, false,
+ "Make operations aware of NUMA architecture and bind memory "
+ "and cpus corresponding to nodes together. In NUMA, memory "
+ "in same node as CPUs are closer when compared to memory in "
+ "other nodes. Reads can be faster when the process is bound to "
+ "CPU and memory of same node. Use \"$numactl --hardware\" command "
+ "to see NUMA memory architecture.");
+
+DEFINE_int64(db_write_buffer_size,
+ ROCKSDB_NAMESPACE::Options().db_write_buffer_size,
+ "Number of bytes to buffer in all memtables before compacting");
+
+DEFINE_bool(cost_write_buffer_to_cache, false,
+ "The usage of memtable is costed to the block cache");
+
+DEFINE_int64(write_buffer_size, 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"
+ " write_buffer_size bytes.");
+
+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_int32(max_background_jobs,
+ ROCKSDB_NAMESPACE::Options().max_background_jobs,
+ "The maximum number of concurrent background jobs 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(num_high_pri_threads, 0,
+ "The maximum number of concurrent background compactions"
+ " that can occur in parallel.");
+
+DEFINE_int32(num_low_pri_threads, 0,
+ "The maximum number of concurrent background compactions"
+ " that can occur in parallel.");
+
+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(base_background_compactions, -1, "DEPRECATED");
+
+DEFINE_uint64(subcompactions, 1,
+ "Maximum number of subcompactions to divide L0-L1 compactions "
+ "into.");
+static const bool FLAGS_subcompactions_dummy
+ __attribute__((__unused__)) = RegisterFlagValidator(&FLAGS_subcompactions,
+ &ValidateUint32Range);
+
+DEFINE_int32(max_background_flushes,
+ ROCKSDB_NAMESPACE::Options().max_background_flushes,
+ "The maximum number of concurrent background flushes"
+ " that can occur in parallel.");
+
+static ROCKSDB_NAMESPACE::CompactionStyle FLAGS_compaction_style_e;
+DEFINE_int32(compaction_style,
+ (int32_t)ROCKSDB_NAMESPACE::Options().compaction_style,
+ "style of compaction: level-based, universal and fifo");
+
+static ROCKSDB_NAMESPACE::CompactionPri FLAGS_compaction_pri_e;
+DEFINE_int32(compaction_pri,
+ (int32_t)ROCKSDB_NAMESPACE::Options().compaction_pri,
+ "priority of files to compaction: by size or by data age");
+
+DEFINE_int32(universal_size_ratio, 0,
+ "Percentage flexibility while comparing file size"
+ " (for universal compaction only).");
+
+DEFINE_int32(universal_min_merge_width, 0, "The minimum number of files in a"
+ " single compaction run (for universal compaction only).");
+
+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(universal_compression_size_percent, -1,
+ "The percentage of the database to compress for universal "
+ "compaction. -1 means compress everything.");
+
+DEFINE_bool(universal_allow_trivial_move, false,
+ "Allow trivial move in universal compaction.");
+
+DEFINE_int64(cache_size, 8 << 20, // 8MB
+ "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 non-negative.");
+
+DEFINE_double(cache_high_pri_pool_ratio, 0.0,
+ "Ratio of block cache reserve for high pri blocks. "
+ "If > 0.0, we also enable "
+ "cache_index_and_filter_blocks_with_high_priority.");
+
+DEFINE_bool(use_clock_cache, false,
+ "Replace default LRU block cache with clock cache.");
+
+DEFINE_int64(simcache_size, -1,
+ "Number of bytes to use as a simcache of "
+ "uncompressed data. Nagative value disables simcache.");
+
+DEFINE_bool(cache_index_and_filter_blocks, false,
+ "Cache index/filter blocks in block cache.");
+
+DEFINE_bool(partition_index_and_filters, false,
+ "Partition index and filter blocks.");
+
+DEFINE_bool(partition_index, false, "Partition index blocks");
+
+DEFINE_int64(metadata_block_size,
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().metadata_block_size,
+ "Max partition size when partitioning index/filters");
+
+// The default reduces the overhead of reading time with flash. With HDD, which
+// offers much less throughput, however, this number better to be set to 1.
+DEFINE_int32(ops_between_duration_checks, 1000,
+ "Check duration limit every x ops");
+
+DEFINE_bool(pin_l0_filter_and_index_blocks_in_cache, false,
+ "Pin index/filter blocks of L0 files in block cache.");
+
+DEFINE_bool(
+ pin_top_level_index_and_filter, false,
+ "Pin top-level index of partitioned index/filter blocks in block cache.");
+
+DEFINE_int32(block_size,
+ static_cast<int32_t>(
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_size),
+ "Number of bytes in a block.");
+
+DEFINE_int32(format_version,
+ static_cast<int32_t>(
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().format_version),
+ "Format version of SST files.");
+
+DEFINE_int32(block_restart_interval,
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_restart_interval,
+ "Number of keys between restart points "
+ "for delta encoding of keys in data block.");
+
+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_int32(read_amp_bytes_per_bit,
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().read_amp_bytes_per_bit,
+ "Number of bytes per bit to be used in block read-amp bitmap");
+
+DEFINE_bool(
+ enable_index_compression,
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().enable_index_compression,
+ "Compress the index block");
+
+DEFINE_bool(block_align,
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_align,
+ "Align data blocks on page size");
+
+DEFINE_bool(use_data_block_hash_index, false,
+ "if use kDataBlockBinaryAndHash "
+ "instead of kDataBlockBinarySearch. "
+ "This is valid if only we use BlockTable");
+
+DEFINE_double(data_block_hash_table_util_ratio, 0.75,
+ "util ratio for data block hash index table. "
+ "This is only valid if use_data_block_hash_index is "
+ "set to true");
+
+DEFINE_int64(compressed_cache_size, -1,
+ "Number of bytes to use as a cache of compressed data.");
+
+DEFINE_int64(row_cache_size, 0,
+ "Number of bytes to use as a cache of individual rows"
+ " (0 = disabled).");
+
+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_int32(file_opening_threads,
+ ROCKSDB_NAMESPACE::Options().max_file_opening_threads,
+ "If open_files is set to -1, this option set the number of "
+ "threads that will be used to open files during DB::Open()");
+
+DEFINE_bool(new_table_reader_for_compaction_inputs, true,
+ "If true, uses a separate file handle for compaction inputs");
+
+DEFINE_int32(compaction_readahead_size, 0, "Compaction readahead size");
+
+DEFINE_int32(log_readahead_size, 0, "WAL and manifest readahead size");
+
+DEFINE_int32(random_access_max_buffer_size, 1024 * 1024,
+ "Maximum windows randomaccess buffer size");
+
+DEFINE_int32(writable_file_max_buffer_size, 1024 * 1024,
+ "Maximum write buffer for Writable File");
+
+DEFINE_int32(bloom_bits, -1, "Bloom filter bits per key. Negative means"
+ " use default settings.");
+DEFINE_double(memtable_bloom_size_ratio, 0,
+ "Ratio of memtable size used for bloom filter. 0 means no bloom "
+ "filter.");
+DEFINE_bool(memtable_whole_key_filtering, false,
+ "Try to use whole key bloom filter in memtables.");
+DEFINE_bool(memtable_use_huge_page, false,
+ "Try to use huge page in memtables.");
+
+DEFINE_bool(use_existing_db, false, "If true, do not destroy the existing"
+ " database. If you set this flag and also specify a benchmark that"
+ " wants a fresh database, that benchmark will fail.");
+
+DEFINE_bool(use_existing_keys, false,
+ "If true, uses existing keys in the DB, "
+ "rather than generating new ones. This involves some startup "
+ "latency to load all keys into memory. It is supported for the "
+ "same read/overwrite benchmarks as `-use_existing_db=true`, which "
+ "must also be set for this flag to be enabled. When this flag is "
+ "set, the value for `-num` will be ignored.");
+
+DEFINE_bool(show_table_properties, false,
+ "If true, then per-level table"
+ " properties will be printed on every stats-interval when"
+ " stats_interval is set and stats_per_interval is on.");
+
+DEFINE_string(db, "", "Use the db with the following name.");
+
+// Read cache flags
+
+DEFINE_string(read_cache_path, "",
+ "If not empty string, a read cache will be used in this path");
+
+DEFINE_int64(read_cache_size, 4LL * 1024 * 1024 * 1024,
+ "Maximum size of the read cache");
+
+DEFINE_bool(read_cache_direct_write, true,
+ "Whether to use Direct IO for writing to the read cache");
+
+DEFINE_bool(read_cache_direct_read, true,
+ "Whether to use Direct IO for reading from read cache");
+
+DEFINE_bool(use_keep_filter, false, "Whether to use a noop compaction filter");
+
+static bool ValidateCacheNumshardbits(const char* flagname, int32_t value) {
+ if (value >= 20) {
+ fprintf(stderr, "Invalid value for --%s: %d, must be < 20\n",
+ flagname, value);
+ return false;
+ }
+ return true;
+}
+
+DEFINE_bool(verify_checksum, true,
+ "Verify checksum for every block read"
+ " from storage");
+
+DEFINE_bool(statistics, false, "Database statistics");
+DEFINE_int32(stats_level, ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers,
+ "stats level for statistics");
+DEFINE_string(statistics_string, "", "Serialized statistics string");
+static class std::shared_ptr<ROCKSDB_NAMESPACE::Statistics> dbstats;
+
+DEFINE_int64(writes, -1, "Number of write operations to do. If negative, do"
+ " --num reads.");
+
+DEFINE_bool(finish_after_writes, false, "Write thread terminates after all writes are finished");
+
+DEFINE_bool(sync, false, "Sync all writes to disk");
+
+DEFINE_bool(use_fsync, false, "If true, issue fsync instead of fdatasync");
+
+DEFINE_bool(disable_wal, false, "If true, do not write WAL for write.");
+
+DEFINE_string(wal_dir, "", "If not empty, use the given dir for WAL");
+
+DEFINE_string(truth_db, "/dev/shm/truth_db/dbbench",
+ "Truth key/values used when using verify");
+
+DEFINE_int32(num_levels, 7, "The total number of levels");
+
+DEFINE_int64(target_file_size_base,
+ ROCKSDB_NAMESPACE::Options().target_file_size_base,
+ "Target file size at level-1");
+
+DEFINE_int32(target_file_size_multiplier,
+ ROCKSDB_NAMESPACE::Options().target_file_size_multiplier,
+ "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_bool(level_compaction_dynamic_level_bytes, false,
+ "Whether level size base is dynamic");
+
+DEFINE_double(max_bytes_for_level_multiplier, 10,
+ "A multiplier to compute max bytes for level-N (N >= 2)");
+
+static std::vector<int> FLAGS_max_bytes_for_level_multiplier_additional_v;
+DEFINE_string(max_bytes_for_level_multiplier_additional, "",
+ "A vector that specifies additional fanout per level");
+
+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(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_file_num_compaction_trigger,
+ ROCKSDB_NAMESPACE::Options().level0_file_num_compaction_trigger,
+ "Number of files in level-0"
+ " when compactions start");
+
+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(readwritepercent, 90, "Ratio of reads to reads/writes (expressed"
+ " as percentage) for the ReadRandomWriteRandom workload. The "
+ "default value 90 means 90% operations out of all reads and writes"
+ " operations are reads. In other words, 9 gets for every 1 put.");
+
+DEFINE_int32(mergereadpercent, 70, "Ratio of merges to merges&reads (expressed"
+ " as percentage) for the ReadRandomMergeRandom workload. The"
+ " default value 70 means 70% out of all read and merge operations"
+ " are merges. In other words, 7 merges for every 3 gets.");
+
+DEFINE_int32(deletepercent, 2, "Percentage of deletes out of reads/writes/"
+ "deletes (used in RandomWithVerify only). RandomWithVerify "
+ "calculates writepercent as (100 - FLAGS_readwritepercent - "
+ "deletepercent), so deletepercent must be smaller than (100 - "
+ "FLAGS_readwritepercent)");
+
+DEFINE_bool(optimize_filters_for_hits, false,
+ "Optimizes bloom filters for workloads for most lookups return "
+ "a value. For now this doesn't create bloom filters for the max "
+ "level of the LSM to reduce metadata that should fit in RAM. ");
+
+DEFINE_uint64(delete_obsolete_files_period_micros, 0,
+ "Ignored. Left here for backward compatibility");
+
+DEFINE_int64(writes_before_delete_range, 0,
+ "Number of writes before DeleteRange is called regularly.");
+
+DEFINE_int64(writes_per_range_tombstone, 0,
+ "Number of writes between range tombstones");
+
+DEFINE_int64(range_tombstone_width, 100, "Number of keys in tombstone's range");
+
+DEFINE_int64(max_num_range_tombstones, 0,
+ "Maximum number of range tombstones "
+ "to insert.");
+
+DEFINE_bool(expand_range_tombstones, false,
+ "Expand range tombstone into sequential regular tombstones.");
+
+#ifndef ROCKSDB_LITE
+// Transactions Options
+DEFINE_bool(optimistic_transaction_db, false,
+ "Open a OptimisticTransactionDB instance. "
+ "Required for randomtransaction benchmark.");
+
+DEFINE_bool(transaction_db, false,
+ "Open a TransactionDB instance. "
+ "Required for randomtransaction benchmark.");
+
+DEFINE_uint64(transaction_sets, 2,
+ "Number of keys each transaction will "
+ "modify (use in RandomTransaction only). Max: 9999");
+
+DEFINE_bool(transaction_set_snapshot, false,
+ "Setting to true will have each transaction call SetSnapshot()"
+ " upon creation.");
+
+DEFINE_int32(transaction_sleep, 0,
+ "Max microseconds to sleep in between "
+ "reading and writing a value (used in RandomTransaction only). ");
+
+DEFINE_uint64(transaction_lock_timeout, 100,
+ "If using a transaction_db, specifies the lock wait timeout in"
+ " milliseconds before failing a transaction waiting on a lock");
+DEFINE_string(
+ options_file, "",
+ "The path to a RocksDB options file. If specified, then db_bench will "
+ "run with the RocksDB options in the default column family of the "
+ "specified options file. "
+ "Note that with this setting, db_bench will ONLY accept the following "
+ "RocksDB options related command-line arguments, all other arguments "
+ "that are related to RocksDB options will be ignored:\n"
+ "\t--use_existing_db\n"
+ "\t--use_existing_keys\n"
+ "\t--statistics\n"
+ "\t--row_cache_size\n"
+ "\t--row_cache_numshardbits\n"
+ "\t--enable_io_prio\n"
+ "\t--dump_malloc_stats\n"
+ "\t--num_multi_db\n");
+
+// FIFO Compaction Options
+DEFINE_uint64(fifo_compaction_max_table_files_size_mb, 0,
+ "The limit of total table file sizes to trigger FIFO compaction");
+
+DEFINE_bool(fifo_compaction_allow_compaction, true,
+ "Allow compaction in FIFO compaction.");
+
+DEFINE_uint64(fifo_compaction_ttl, 0, "TTL for the SST Files in seconds.");
+
+// Blob DB Options
+DEFINE_bool(use_blob_db, false,
+ "Open a BlobDB instance. "
+ "Required for large value benchmark.");
+
+DEFINE_bool(
+ blob_db_enable_gc,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().enable_garbage_collection,
+ "Enable BlobDB garbage collection.");
+
+DEFINE_double(
+ blob_db_gc_cutoff,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().garbage_collection_cutoff,
+ "Cutoff ratio for BlobDB garbage collection.");
+
+DEFINE_bool(blob_db_is_fifo,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().is_fifo,
+ "Enable FIFO eviction strategy in BlobDB.");
+
+DEFINE_uint64(blob_db_max_db_size,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().max_db_size,
+ "Max size limit of the directory where blob files are stored.");
+
+DEFINE_uint64(
+ blob_db_max_ttl_range, 0,
+ "TTL range to generate BlobDB data (in seconds). 0 means no TTL.");
+
+DEFINE_uint64(blob_db_ttl_range_secs,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().ttl_range_secs,
+ "TTL bucket size to use when creating blob files.");
+
+DEFINE_uint64(blob_db_min_blob_size,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().min_blob_size,
+ "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,
+ "Bytes to sync blob file at.");
+
+DEFINE_uint64(blob_db_file_size,
+ ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().blob_file_size,
+ "Target size of each blob file.");
+
+DEFINE_string(blob_db_compression_type, "snappy",
+ "Algorithm to use to compress blob in blob file");
+static enum ROCKSDB_NAMESPACE::CompressionType
+ FLAGS_blob_db_compression_type_e = ROCKSDB_NAMESPACE::kSnappyCompression;
+
+// Secondary DB instance Options
+DEFINE_bool(use_secondary_db, false,
+ "Open a RocksDB secondary instance. A primary instance can be "
+ "running in another db_bench process.");
+
+DEFINE_string(secondary_path, "",
+ "Path to a directory used by the secondary instance to store "
+ "private files, e.g. info log.");
+
+DEFINE_int32(secondary_update_interval, 5,
+ "Secondary instance attempts to catch up with the primary every "
+ "secondary_update_interval seconds.");
+
+#endif // ROCKSDB_LITE
+
+DEFINE_bool(report_bg_io_stats, false,
+ "Measure times spents on I/Os while in compactions. ");
+
+DEFINE_bool(use_stderr_info_logger, false,
+ "Write info logs to stderr instead of to LOG file. ");
+
+DEFINE_string(trace_file, "", "Trace workload to a file. ");
+
+DEFINE_int32(trace_replay_fast_forward, 1,
+ "Fast forward trace replay, must >= 1. ");
+DEFINE_int32(block_cache_trace_sampling_frequency, 1,
+ "Block cache trace sampling frequency, termed s. It uses spatial "
+ "downsampling and samples accesses to one out of s blocks.");
+DEFINE_int64(
+ block_cache_trace_max_trace_file_size_in_bytes,
+ uint64_t{64} * 1024 * 1024 * 1024,
+ "The maximum block cache trace file size in bytes. Block cache accesses "
+ "will not be logged if the trace file size exceeds this threshold. Default "
+ "is 64 GB.");
+DEFINE_string(block_cache_trace_file, "", "Block cache trace file path.");
+DEFINE_int32(trace_replay_threads, 1,
+ "The number of threads to replay, must >=1.");
+
+static enum ROCKSDB_NAMESPACE::CompressionType StringToCompressionType(
+ const char* ctype) {
+ assert(ctype);
+
+ if (!strcasecmp(ctype, "none"))
+ return ROCKSDB_NAMESPACE::kNoCompression;
+ else if (!strcasecmp(ctype, "snappy"))
+ return ROCKSDB_NAMESPACE::kSnappyCompression;
+ else if (!strcasecmp(ctype, "zlib"))
+ return ROCKSDB_NAMESPACE::kZlibCompression;
+ else if (!strcasecmp(ctype, "bzip2"))
+ return ROCKSDB_NAMESPACE::kBZip2Compression;
+ else if (!strcasecmp(ctype, "lz4"))
+ return ROCKSDB_NAMESPACE::kLZ4Compression;
+ else if (!strcasecmp(ctype, "lz4hc"))
+ return ROCKSDB_NAMESPACE::kLZ4HCCompression;
+ else if (!strcasecmp(ctype, "xpress"))
+ return ROCKSDB_NAMESPACE::kXpressCompression;
+ else if (!strcasecmp(ctype, "zstd"))
+ return ROCKSDB_NAMESPACE::kZSTD;
+
+ fprintf(stdout, "Cannot parse compression type '%s'\n", ctype);
+ return ROCKSDB_NAMESPACE::kSnappyCompression; // default value
+}
+
+static std::string ColumnFamilyName(size_t i) {
+ if (i == 0) {
+ return ROCKSDB_NAMESPACE::kDefaultColumnFamilyName;
+ } else {
+ char name[100];
+ snprintf(name, sizeof(name), "column_family_name_%06zu", i);
+ return std::string(name);
+ }
+}
+
+DEFINE_string(compression_type, "snappy",
+ "Algorithm to use to compress the database");
+static enum ROCKSDB_NAMESPACE::CompressionType FLAGS_compression_type_e =
+ ROCKSDB_NAMESPACE::kSnappyCompression;
+
+DEFINE_int64(sample_for_compression, 0, "Sample every N block for compression");
+
+DEFINE_int32(compression_level, ROCKSDB_NAMESPACE::CompressionOptions().level,
+ "Compression level. The meaning of this value is library-"
+ "dependent. If unset, we try to use the default for the library "
+ "specified in `--compression_type`");
+
+DEFINE_int32(compression_max_dict_bytes,
+ ROCKSDB_NAMESPACE::CompressionOptions().max_dict_bytes,
+ "Maximum size of dictionary used to prime the compression "
+ "library.");
+
+DEFINE_int32(compression_zstd_max_train_bytes,
+ ROCKSDB_NAMESPACE::CompressionOptions().zstd_max_train_bytes,
+ "Maximum size of training data passed to zstd's dictionary "
+ "trainer.");
+
+DEFINE_int32(min_level_to_compress, -1, "If non-negative, compression starts"
+ " from this level. Levels with number < min_level_to_compress are"
+ " not compressed. Otherwise, apply compression_type to "
+ "all levels.");
+
+static bool ValidateTableCacheNumshardbits(const char* flagname,
+ int32_t value) {
+ if (0 >= value || value > 20) {
+ fprintf(stderr, "Invalid value for --%s: %d, must be 0 < val <= 20\n",
+ flagname, value);
+ return false;
+ }
+ return true;
+}
+DEFINE_int32(table_cache_numshardbits, 4, "");
+
+#ifndef ROCKSDB_LITE
+DEFINE_string(env_uri, "", "URI for registry Env lookup. Mutually exclusive"
+ " with --hdfs.");
+#endif // ROCKSDB_LITE
+DEFINE_string(hdfs, "", "Name of hdfs environment. Mutually exclusive with"
+ " --env_uri.");
+
+static std::shared_ptr<ROCKSDB_NAMESPACE::Env> env_guard;
+
+static ROCKSDB_NAMESPACE::Env* FLAGS_env = ROCKSDB_NAMESPACE::Env::Default();
+
+DEFINE_int64(stats_interval, 0, "Stats are reported every N operations when "
+ "this is greater than zero. When 0 the interval grows over time.");
+
+DEFINE_int64(stats_interval_seconds, 0, "Report stats every N seconds. This "
+ "overrides stats_interval when both are > 0.");
+
+DEFINE_int32(stats_per_interval, 0, "Reports additional stats per interval when"
+ " this is greater than 0.");
+
+DEFINE_int64(report_interval_seconds, 0,
+ "If greater than zero, it will write simple stats in CVS format "
+ "to --report_file every N seconds");
+
+DEFINE_string(report_file, "report.csv",
+ "Filename where some simple stats are reported to (if "
+ "--report_interval_seconds is bigger than 0)");
+
+DEFINE_int32(thread_status_per_interval, 0,
+ "Takes and report a snapshot of the current status of each thread"
+ " when this is greater than 0.");
+
+DEFINE_int32(perf_level, ROCKSDB_NAMESPACE::PerfLevel::kDisable,
+ "Level of perf collection");
+
+static bool ValidateRateLimit(const char* flagname, double value) {
+ const double EPSILON = 1e-10;
+ if ( value < -EPSILON ) {
+ fprintf(stderr, "Invalid value for --%s: %12.6f, must be >= 0.0\n",
+ flagname, value);
+ return false;
+ }
+ return true;
+}
+DEFINE_double(soft_rate_limit, 0.0, "DEPRECATED");
+
+DEFINE_double(hard_rate_limit, 0.0, "DEPRECATED");
+
+DEFINE_uint64(soft_pending_compaction_bytes_limit, 64ull * 1024 * 1024 * 1024,
+ "Slowdown writes if pending compaction bytes exceed this number");
+
+DEFINE_uint64(hard_pending_compaction_bytes_limit, 128ull * 1024 * 1024 * 1024,
+ "Stop writes if pending compaction bytes exceed this number");
+
+DEFINE_uint64(delayed_write_rate, 8388608u,
+ "Limited bytes allowed to DB when soft_rate_limit or "
+ "level0_slowdown_writes_trigger triggers");
+
+DEFINE_bool(enable_pipelined_write, true,
+ "Allow WAL and memtable writes to be pipelined");
+
+DEFINE_bool(unordered_write, false,
+ "Allow WAL and memtable writes to be pipelined");
+
+DEFINE_bool(allow_concurrent_memtable_write, true,
+ "Allow multi-writers to update mem tables in parallel.");
+
+DEFINE_bool(inplace_update_support,
+ ROCKSDB_NAMESPACE::Options().inplace_update_support,
+ "Support in-place memtable update for smaller or same-size values");
+
+DEFINE_uint64(inplace_update_num_locks,
+ ROCKSDB_NAMESPACE::Options().inplace_update_num_locks,
+ "Number of RW locks to protect in-place memtable updates");
+
+DEFINE_bool(enable_write_thread_adaptive_yield, true,
+ "Use a yielding spin loop for brief writer thread waits.");
+
+DEFINE_uint64(
+ write_thread_max_yield_usec, 100,
+ "Maximum microseconds for enable_write_thread_adaptive_yield operation.");
+
+DEFINE_uint64(write_thread_slow_yield_usec, 3,
+ "The threshold at which a slow yield is considered a signal that "
+ "other processes or threads want the core.");
+
+DEFINE_int32(rate_limit_delay_max_milliseconds, 1000,
+ "When hard_rate_limit is set then this is the max time a put will"
+ " be stalled.");
+
+DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value.");
+
+DEFINE_bool(rate_limiter_auto_tuned, false,
+ "Enable dynamic adjustment of rate limit according to demand for "
+ "background I/O");
+
+
+DEFINE_bool(sine_write_rate, false,
+ "Use a sine wave write_rate_limit");
+
+DEFINE_uint64(sine_write_rate_interval_milliseconds, 10000,
+ "Interval of which the sine wave write_rate_limit is recalculated");
+
+DEFINE_double(sine_a, 1,
+ "A in f(x) = A sin(bx + c) + d");
+
+DEFINE_double(sine_b, 1,
+ "B in f(x) = A sin(bx + c) + d");
+
+DEFINE_double(sine_c, 0,
+ "C in f(x) = A sin(bx + c) + d");
+
+DEFINE_double(sine_d, 1,
+ "D in f(x) = A sin(bx + c) + d");
+
+DEFINE_bool(rate_limit_bg_reads, false,
+ "Use options.rate_limiter on compaction reads");
+
+DEFINE_uint64(
+ benchmark_write_rate_limit, 0,
+ "If non-zero, db_bench will rate-limit the writes going into RocksDB. This "
+ "is the global rate in bytes/second.");
+
+// the parameters of mix_graph
+DEFINE_double(keyrange_dist_a, 0.0,
+ "The parameter 'a' of prefix average access distribution "
+ "f(x)=a*exp(b*x)+c*exp(d*x)");
+DEFINE_double(keyrange_dist_b, 0.0,
+ "The parameter 'b' of prefix average access distribution "
+ "f(x)=a*exp(b*x)+c*exp(d*x)");
+DEFINE_double(keyrange_dist_c, 0.0,
+ "The parameter 'c' of prefix average access distribution"
+ "f(x)=a*exp(b*x)+c*exp(d*x)");
+DEFINE_double(keyrange_dist_d, 0.0,
+ "The parameter 'd' of prefix average access distribution"
+ "f(x)=a*exp(b*x)+c*exp(d*x)");
+DEFINE_int64(keyrange_num, 1,
+ "The number of key ranges that are in the same prefix "
+ "group, each prefix range will have its key acccess "
+ "distribution");
+DEFINE_double(key_dist_a, 0.0,
+ "The parameter 'a' of key access distribution model "
+ "f(x)=a*x^b");
+DEFINE_double(key_dist_b, 0.0,
+ "The parameter 'b' of key access distribution model "
+ "f(x)=a*x^b");
+DEFINE_double(value_theta, 0.0,
+ "The parameter 'theta' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(value_k, 0.0,
+ "The parameter 'k' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(value_sigma, 0.0,
+ "The parameter 'theta' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(iter_theta, 0.0,
+ "The parameter 'theta' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(iter_k, 0.0,
+ "The parameter 'k' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(iter_sigma, 0.0,
+ "The parameter 'sigma' of Generized Pareto Distribution "
+ "f(x)=(1/sigma)*(1+k*(x-theta)/sigma)^-(1/k+1)");
+DEFINE_double(mix_get_ratio, 1.0,
+ "The ratio of Get queries of mix_graph workload");
+DEFINE_double(mix_put_ratio, 0.0,
+ "The ratio of Put queries of mix_graph workload");
+DEFINE_double(mix_seek_ratio, 0.0,
+ "The ratio of Seek queries of mix_graph workload");
+DEFINE_int64(mix_max_scan_len, 10000, "The max scan length of Iterator");
+DEFINE_int64(mix_ave_kv_size, 512,
+ "The average key-value size of this workload");
+DEFINE_int64(mix_max_value_size, 1024, "The max value size of this workload");
+DEFINE_double(
+ sine_mix_rate_noise, 0.0,
+ "Add the noise ratio to the sine rate, it is between 0.0 and 1.0");
+DEFINE_bool(sine_mix_rate, false,
+ "Enable the sine QPS control on the mix workload");
+DEFINE_uint64(
+ sine_mix_rate_interval_milliseconds, 10000,
+ "Interval of which the sine wave read_rate_limit is recalculated");
+DEFINE_int64(mix_accesses, -1,
+ "The total query accesses of mix_graph workload");
+
+DEFINE_uint64(
+ benchmark_read_rate_limit, 0,
+ "If non-zero, db_bench will rate-limit the reads from RocksDB. This "
+ "is the global rate in ops/second.");
+
+DEFINE_uint64(max_compaction_bytes,
+ ROCKSDB_NAMESPACE::Options().max_compaction_bytes,
+ "Max bytes allowed in one compaction");
+
+#ifndef ROCKSDB_LITE
+DEFINE_bool(readonly, false, "Run read only benchmarks.");
+
+DEFINE_bool(print_malloc_stats, false,
+ "Print malloc stats to stdout after benchmarks finish.");
+#endif // ROCKSDB_LITE
+
+DEFINE_bool(disable_auto_compactions, false, "Do not auto trigger compactions");
+
+DEFINE_uint64(wal_ttl_seconds, 0, "Set the TTL for the WAL Files in seconds.");
+DEFINE_uint64(wal_size_limit_MB, 0, "Set the size limit for the WAL Files"
+ " in MB.");
+DEFINE_uint64(max_total_wal_size, 0, "Set total max WAL size");
+
+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 background flush and compaction writes");
+
+DEFINE_bool(advise_random_on_open,
+ ROCKSDB_NAMESPACE::Options().advise_random_on_open,
+ "Advise random access on table file open");
+
+DEFINE_string(compaction_fadvice, "NORMAL",
+ "Access pattern advice when a file is compacted");
+static auto FLAGS_compaction_fadvice_e =
+ ROCKSDB_NAMESPACE::Options().access_hint_on_compaction_start;
+
+DEFINE_bool(use_tailing_iterator, false,
+ "Use tailing iterator to access a series of keys instead of get");
+
+DEFINE_bool(use_adaptive_mutex, ROCKSDB_NAMESPACE::Options().use_adaptive_mutex,
+ "Use adaptive mutex");
+
+DEFINE_uint64(bytes_per_sync, ROCKSDB_NAMESPACE::Options().bytes_per_sync,
+ "Allows OS to incrementally sync SST files to disk while they are"
+ " being written, in the background. Issue one request for every"
+ " bytes_per_sync written. 0 turns it off.");
+
+DEFINE_uint64(wal_bytes_per_sync,
+ ROCKSDB_NAMESPACE::Options().wal_bytes_per_sync,
+ "Allows OS to incrementally sync WAL files to disk while they are"
+ " being written, in the background. Issue one request for every"
+ " wal_bytes_per_sync written. 0 turns it off.");
+
+DEFINE_bool(use_single_deletes, true,
+ "Use single deletes (used in RandomReplaceKeys only).");
+
+DEFINE_double(stddev, 2000.0,
+ "Standard deviation of normal distribution used for picking keys"
+ " (used in RandomReplaceKeys only).");
+
+DEFINE_int32(key_id_range, 100000,
+ "Range of possible value of key id (used in TimeSeries only).");
+
+DEFINE_string(expire_style, "none",
+ "Style to remove expired time entries. Can be one of the options "
+ "below: none (do not expired data), compaction_filter (use a "
+ "compaction filter to remove expired data), delete (seek IDs and "
+ "remove expired data) (used in TimeSeries only).");
+
+DEFINE_uint64(
+ time_range, 100000,
+ "Range of timestamp that store in the database (used in TimeSeries"
+ " only).");
+
+DEFINE_int32(num_deletion_threads, 1,
+ "Number of threads to do deletion (used in TimeSeries and delete "
+ "expire_style only).");
+
+DEFINE_int32(max_successive_merges, 0, "Maximum number of successive merge"
+ " operations on a key in the memtable");
+
+static bool ValidatePrefixSize(const char* flagname, int32_t value) {
+ if (value < 0 || value>=2000000000) {
+ fprintf(stderr, "Invalid value for --%s: %d. 0<= PrefixSize <=2000000000\n",
+ flagname, value);
+ return false;
+ }
+ return true;
+}
+
+DEFINE_int32(prefix_size, 0, "control the prefix size for HashSkipList and "
+ "plain table");
+DEFINE_int64(keys_per_prefix, 0, "control average number of keys generated "
+ "per prefix, 0 means no special handling of the prefix, "
+ "i.e. use the prefix comes with the generated random number.");
+DEFINE_bool(total_order_seek, false,
+ "Enable total order seek regardless of index format.");
+DEFINE_bool(prefix_same_as_start, false,
+ "Enforce iterator to return keys with prefix same as seek key.");
+DEFINE_bool(
+ seek_missing_prefix, false,
+ "Iterator seek to keys with non-exist prefixes. Require prefix_size > 8");
+
+DEFINE_int32(memtable_insert_with_hint_prefix_size, 0,
+ "If non-zero, enable "
+ "memtable insert with hint with the given prefix size.");
+DEFINE_bool(enable_io_prio, false, "Lower the background flush/compaction "
+ "threads' IO priority");
+DEFINE_bool(enable_cpu_prio, false, "Lower the background flush/compaction "
+ "threads' CPU priority");
+DEFINE_bool(identity_as_first_hash, false, "the first hash function of cuckoo "
+ "table becomes an identity function. This is only valid when key "
+ "is 8 bytes");
+DEFINE_bool(dump_malloc_stats, true, "Dump malloc stats in LOG ");
+DEFINE_uint64(stats_dump_period_sec,
+ ROCKSDB_NAMESPACE::Options().stats_dump_period_sec,
+ "Gap between printing stats to log in seconds");
+DEFINE_uint64(stats_persist_period_sec,
+ ROCKSDB_NAMESPACE::Options().stats_persist_period_sec,
+ "Gap between persisting stats in seconds");
+DEFINE_bool(persist_stats_to_disk,
+ ROCKSDB_NAMESPACE::Options().persist_stats_to_disk,
+ "whether to persist stats to disk");
+DEFINE_uint64(stats_history_buffer_size,
+ ROCKSDB_NAMESPACE::Options().stats_history_buffer_size,
+ "Max number of stats snapshots to keep in memory");
+DEFINE_int64(multiread_stride, 0,
+ "Stride length for the keys in a MultiGet batch");
+DEFINE_bool(multiread_batched, false, "Use the new MultiGet API");
+
+enum RepFactory {
+ kSkipList,
+ kPrefixHash,
+ kVectorRep,
+ kHashLinkedList,
+};
+
+static enum RepFactory StringToRepFactory(const char* ctype) {
+ assert(ctype);
+
+ if (!strcasecmp(ctype, "skip_list"))
+ return kSkipList;
+ else if (!strcasecmp(ctype, "prefix_hash"))
+ return kPrefixHash;
+ else if (!strcasecmp(ctype, "vector"))
+ return kVectorRep;
+ else if (!strcasecmp(ctype, "hash_linkedlist"))
+ return kHashLinkedList;
+
+ fprintf(stdout, "Cannot parse memreptable %s\n", ctype);
+ return kSkipList;
+}
+
+static enum RepFactory FLAGS_rep_factory;
+DEFINE_string(memtablerep, "skip_list", "");
+DEFINE_int64(hash_bucket_count, 1024 * 1024, "hash bucket count");
+DEFINE_bool(use_plain_table, false, "if use plain table "
+ "instead of block-based table format");
+DEFINE_bool(use_cuckoo_table, false, "if use cuckoo table format");
+DEFINE_double(cuckoo_hash_ratio, 0.9, "Hash ratio for Cuckoo SST table.");
+DEFINE_bool(use_hash_search, false, "if use kHashSearch "
+ "instead of kBinarySearch. "
+ "This is valid if only we use BlockTable");
+DEFINE_bool(use_block_based_filter, false, "if use kBlockBasedFilter "
+ "instead of kFullFilter for filter block. "
+ "This is valid if only we use BlockTable");
+DEFINE_string(merge_operator, "", "The merge operator to use with the database."
+ "If a new merge operator is specified, be sure to use fresh"
+ " database The possible merge operators are defined in"
+ " utilities/merge_operators.h");
+DEFINE_int32(skip_list_lookahead, 0, "Used with skip_list memtablerep; try "
+ "linear search first for this many steps from the previous "
+ "position");
+DEFINE_bool(report_file_operations, false, "if report number of file "
+ "operations");
+DEFINE_int32(readahead_size, 0, "Iterator readahead size");
+
+static const bool FLAGS_soft_rate_limit_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_soft_rate_limit, &ValidateRateLimit);
+
+static const bool FLAGS_hard_rate_limit_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_hard_rate_limit, &ValidateRateLimit);
+
+static const bool FLAGS_prefix_size_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_prefix_size, &ValidatePrefixSize);
+
+static const bool FLAGS_key_size_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_key_size, &ValidateKeySize);
+
+static const bool FLAGS_cache_numshardbits_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_cache_numshardbits,
+ &ValidateCacheNumshardbits);
+
+static const bool FLAGS_readwritepercent_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_readwritepercent, &ValidateInt32Percent);
+
+DEFINE_int32(disable_seek_compaction, false,
+ "Not used, left here for backwards compatibility");
+
+static const bool FLAGS_deletepercent_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_deletepercent, &ValidateInt32Percent);
+static const bool FLAGS_table_cache_numshardbits_dummy __attribute__((__unused__)) =
+ RegisterFlagValidator(&FLAGS_table_cache_numshardbits,
+ &ValidateTableCacheNumshardbits);
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+struct ReportFileOpCounters {
+ std::atomic<int> open_counter_;
+ std::atomic<int> read_counter_;
+ std::atomic<int> append_counter_;
+ std::atomic<uint64_t> bytes_read_;
+ std::atomic<uint64_t> bytes_written_;
+};
+
+// A special Env to records and report file operations in db_bench
+class ReportFileOpEnv : public EnvWrapper {
+ public:
+ explicit ReportFileOpEnv(Env* base) : EnvWrapper(base) { reset(); }
+
+ void reset() {
+ counters_.open_counter_ = 0;
+ counters_.read_counter_ = 0;
+ counters_.append_counter_ = 0;
+ counters_.bytes_read_ = 0;
+ counters_.bytes_written_ = 0;
+ }
+
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& soptions) override {
+ class CountingFile : public SequentialFile {
+ private:
+ std::unique_ptr<SequentialFile> target_;
+ ReportFileOpCounters* counters_;
+
+ public:
+ CountingFile(std::unique_ptr<SequentialFile>&& target,
+ ReportFileOpCounters* counters)
+ : target_(std::move(target)), counters_(counters) {}
+
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ counters_->read_counter_.fetch_add(1, std::memory_order_relaxed);
+ Status rv = target_->Read(n, result, scratch);
+ counters_->bytes_read_.fetch_add(result->size(),
+ std::memory_order_relaxed);
+ return rv;
+ }
+
+ Status Skip(uint64_t n) override { return target_->Skip(n); }
+ };
+
+ Status s = target()->NewSequentialFile(f, r, soptions);
+ if (s.ok()) {
+ counters()->open_counter_.fetch_add(1, std::memory_order_relaxed);
+ r->reset(new CountingFile(std::move(*r), counters()));
+ }
+ return s;
+ }
+
+ Status NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& soptions) override {
+ class CountingFile : public RandomAccessFile {
+ private:
+ std::unique_ptr<RandomAccessFile> target_;
+ ReportFileOpCounters* counters_;
+
+ public:
+ CountingFile(std::unique_ptr<RandomAccessFile>&& target,
+ ReportFileOpCounters* counters)
+ : target_(std::move(target)), counters_(counters) {}
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ counters_->read_counter_.fetch_add(1, std::memory_order_relaxed);
+ Status rv = target_->Read(offset, n, result, scratch);
+ counters_->bytes_read_.fetch_add(result->size(),
+ std::memory_order_relaxed);
+ return rv;
+ }
+ };
+
+ Status s = target()->NewRandomAccessFile(f, r, soptions);
+ if (s.ok()) {
+ counters()->open_counter_.fetch_add(1, std::memory_order_relaxed);
+ r->reset(new CountingFile(std::move(*r), counters()));
+ }
+ return s;
+ }
+
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& soptions) override {
+ class CountingFile : public WritableFile {
+ private:
+ std::unique_ptr<WritableFile> target_;
+ ReportFileOpCounters* counters_;
+
+ public:
+ CountingFile(std::unique_ptr<WritableFile>&& target,
+ ReportFileOpCounters* counters)
+ : target_(std::move(target)), counters_(counters) {}
+
+ Status Append(const Slice& data) override {
+ counters_->append_counter_.fetch_add(1, std::memory_order_relaxed);
+ Status rv = target_->Append(data);
+ counters_->bytes_written_.fetch_add(data.size(),
+ std::memory_order_relaxed);
+ return rv;
+ }
+
+ Status Truncate(uint64_t size) override { return target_->Truncate(size); }
+ Status Close() override { return target_->Close(); }
+ Status Flush() override { return target_->Flush(); }
+ Status Sync() override { return target_->Sync(); }
+ };
+
+ Status s = target()->NewWritableFile(f, r, soptions);
+ if (s.ok()) {
+ counters()->open_counter_.fetch_add(1, std::memory_order_relaxed);
+ r->reset(new CountingFile(std::move(*r), counters()));
+ }
+ return s;
+ }
+
+ // getter
+ ReportFileOpCounters* counters() { return &counters_; }
+
+ private:
+ ReportFileOpCounters counters_;
+};
+
+} // namespace
+
+enum DistributionType : unsigned char {
+ kFixed = 0,
+ kUniform,
+ kNormal
+};
+
+static enum DistributionType FLAGS_value_size_distribution_type_e = kFixed;
+
+static enum DistributionType StringToDistributionType(const char* ctype) {
+ assert(ctype);
+
+ if (!strcasecmp(ctype, "fixed"))
+ return kFixed;
+ else if (!strcasecmp(ctype, "uniform"))
+ return kUniform;
+ else if (!strcasecmp(ctype, "normal"))
+ return kNormal;
+
+ fprintf(stdout, "Cannot parse distribution type '%s'\n", ctype);
+ return kFixed; // default value
+}
+
+class BaseDistribution {
+ public:
+ BaseDistribution(unsigned int min, unsigned int max) :
+ min_value_size_(min),
+ max_value_size_(max) {}
+ virtual ~BaseDistribution() {}
+
+ unsigned int Generate() {
+ auto val = Get();
+ if (NeedTruncate()) {
+ val = std::max(min_value_size_, val);
+ val = std::min(max_value_size_, val);
+ }
+ return val;
+ }
+ private:
+ virtual unsigned int Get() = 0;
+ virtual bool NeedTruncate() {
+ return true;
+ }
+ unsigned int min_value_size_;
+ unsigned int max_value_size_;
+};
+
+class FixedDistribution : public BaseDistribution
+{
+ public:
+ FixedDistribution(unsigned int size) :
+ BaseDistribution(size, size),
+ size_(size) {}
+ private:
+ virtual unsigned int Get() override {
+ return size_;
+ }
+ virtual bool NeedTruncate() override {
+ return false;
+ }
+ unsigned int size_;
+};
+
+class NormalDistribution
+ : public BaseDistribution, public std::normal_distribution<double> {
+ public:
+ NormalDistribution(unsigned int min, unsigned int max) :
+ BaseDistribution(min, max),
+ // 99.7% values within the range [min, max].
+ std::normal_distribution<double>((double)(min + max) / 2.0 /*mean*/,
+ (double)(max - min) / 6.0 /*stddev*/),
+ gen_(rd_()) {}
+ private:
+ virtual unsigned int Get() override {
+ return static_cast<unsigned int>((*this)(gen_));
+ }
+ std::random_device rd_;
+ std::mt19937 gen_;
+};
+
+class UniformDistribution
+ : public BaseDistribution,
+ public std::uniform_int_distribution<unsigned int> {
+ public:
+ UniformDistribution(unsigned int min, unsigned int max) :
+ BaseDistribution(min, max),
+ std::uniform_int_distribution<unsigned int>(min, max),
+ gen_(rd_()) {}
+ private:
+ virtual unsigned int Get() override {
+ return (*this)(gen_);
+ }
+ virtual bool NeedTruncate() override {
+ return false;
+ }
+ std::random_device rd_;
+ std::mt19937 gen_;
+};
+
+// Helper for quickly generating random data.
+class RandomGenerator {
+ private:
+ std::string data_;
+ unsigned int pos_;
+ std::unique_ptr<BaseDistribution> dist_;
+
+ public:
+
+ RandomGenerator() {
+ auto max_value_size = FLAGS_value_size_max;
+ switch (FLAGS_value_size_distribution_type_e) {
+ case kUniform:
+ dist_.reset(new UniformDistribution(FLAGS_value_size_min,
+ FLAGS_value_size_max));
+ break;
+ case kNormal:
+ dist_.reset(new NormalDistribution(FLAGS_value_size_min,
+ FLAGS_value_size_max));
+ break;
+ case kFixed:
+ default:
+ dist_.reset(new FixedDistribution(value_size));
+ max_value_size = value_size;
+ }
+ // We use a limited amount of data over and over again and ensure
+ // that it is larger than the compression window (32KB), and also
+ // large enough to serve all typical value sizes we want to write.
+ Random rnd(301);
+ std::string piece;
+ while (data_.size() < (unsigned)std::max(1048576, max_value_size)) {
+ // Add a short fragment that is as compressible as specified
+ // by FLAGS_compression_ratio.
+ test::CompressibleString(&rnd, FLAGS_compression_ratio, 100, &piece);
+ data_.append(piece);
+ }
+ pos_ = 0;
+ }
+
+ Slice Generate(unsigned int len) {
+ assert(len <= data_.size());
+ if (pos_ + len > data_.size()) {
+ pos_ = 0;
+ }
+ pos_ += len;
+ return Slice(data_.data() + pos_ - len, len);
+ }
+
+ Slice Generate() {
+ auto len = dist_->Generate();
+ return Generate(len);
+ }
+};
+
+static void AppendWithSpace(std::string* str, Slice msg) {
+ if (msg.empty()) return;
+ if (!str->empty()) {
+ str->push_back(' ');
+ }
+ str->append(msg.data(), msg.size());
+}
+
+struct DBWithColumnFamilies {
+ std::vector<ColumnFamilyHandle*> cfh;
+ DB* db;
+#ifndef ROCKSDB_LITE
+ OptimisticTransactionDB* opt_txn_db;
+#endif // ROCKSDB_LITE
+ std::atomic<size_t> num_created; // Need to be updated after all the
+ // new entries in cfh are set.
+ size_t num_hot; // Number of column families to be queried at each moment.
+ // After each CreateNewCf(), another num_hot number of new
+ // Column families will be created and used to be queried.
+ port::Mutex create_cf_mutex; // Only one thread can execute CreateNewCf()
+ std::vector<int> cfh_idx_to_prob; // ith index holds probability of operating
+ // on cfh[i].
+
+ DBWithColumnFamilies()
+ : db(nullptr)
+#ifndef ROCKSDB_LITE
+ , opt_txn_db(nullptr)
+#endif // ROCKSDB_LITE
+ {
+ cfh.clear();
+ num_created = 0;
+ num_hot = 0;
+ }
+
+ DBWithColumnFamilies(const DBWithColumnFamilies& other)
+ : cfh(other.cfh),
+ db(other.db),
+#ifndef ROCKSDB_LITE
+ opt_txn_db(other.opt_txn_db),
+#endif // ROCKSDB_LITE
+ num_created(other.num_created.load()),
+ num_hot(other.num_hot),
+ cfh_idx_to_prob(other.cfh_idx_to_prob) {
+ }
+
+ void DeleteDBs() {
+ std::for_each(cfh.begin(), cfh.end(),
+ [](ColumnFamilyHandle* cfhi) { delete cfhi; });
+ cfh.clear();
+#ifndef ROCKSDB_LITE
+ if (opt_txn_db) {
+ delete opt_txn_db;
+ opt_txn_db = nullptr;
+ } else {
+ delete db;
+ db = nullptr;
+ }
+#else
+ delete db;
+ db = nullptr;
+#endif // ROCKSDB_LITE
+ }
+
+ ColumnFamilyHandle* GetCfh(int64_t rand_num) {
+ assert(num_hot > 0);
+ size_t rand_offset = 0;
+ if (!cfh_idx_to_prob.empty()) {
+ assert(cfh_idx_to_prob.size() == num_hot);
+ int sum = 0;
+ while (sum + cfh_idx_to_prob[rand_offset] < rand_num % 100) {
+ sum += cfh_idx_to_prob[rand_offset];
+ ++rand_offset;
+ }
+ assert(rand_offset < cfh_idx_to_prob.size());
+ } else {
+ rand_offset = rand_num % num_hot;
+ }
+ return cfh[num_created.load(std::memory_order_acquire) - num_hot +
+ rand_offset];
+ }
+
+ // stage: assume CF from 0 to stage * num_hot has be created. Need to create
+ // stage * num_hot + 1 to stage * (num_hot + 1).
+ void CreateNewCf(ColumnFamilyOptions options, int64_t stage) {
+ MutexLock l(&create_cf_mutex);
+ if ((stage + 1) * num_hot <= num_created) {
+ // Already created.
+ return;
+ }
+ auto new_num_created = num_created + num_hot;
+ assert(new_num_created <= cfh.size());
+ for (size_t i = num_created; i < new_num_created; i++) {
+ Status s =
+ db->CreateColumnFamily(options, ColumnFamilyName(i), &(cfh[i]));
+ if (!s.ok()) {
+ fprintf(stderr, "create column family error: %s\n",
+ s.ToString().c_str());
+ abort();
+ }
+ }
+ num_created.store(new_num_created, std::memory_order_release);
+ }
+};
+
+// a class that reports stats to CSV file
+class ReporterAgent {
+ public:
+ ReporterAgent(Env* env, const std::string& fname,
+ uint64_t report_interval_secs)
+ : env_(env),
+ total_ops_done_(0),
+ last_report_(0),
+ report_interval_secs_(report_interval_secs),
+ stop_(false) {
+ auto s = env_->NewWritableFile(fname, &report_file_, EnvOptions());
+ if (s.ok()) {
+ s = report_file_->Append(Header() + "\n");
+ }
+ if (s.ok()) {
+ s = report_file_->Flush();
+ }
+ if (!s.ok()) {
+ fprintf(stderr, "Can't open %s: %s\n", fname.c_str(),
+ s.ToString().c_str());
+ abort();
+ }
+
+ reporting_thread_ = port::Thread([&]() { SleepAndReport(); });
+ }
+
+ ~ReporterAgent() {
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ stop_ = true;
+ stop_cv_.notify_all();
+ }
+ reporting_thread_.join();
+ }
+
+ // thread safe
+ void ReportFinishedOps(int64_t num_ops) {
+ total_ops_done_.fetch_add(num_ops);
+ }
+
+ private:
+ std::string Header() const { return "secs_elapsed,interval_qps"; }
+ void SleepAndReport() {
+ auto time_started = env_->NowMicros();
+ while (true) {
+ {
+ std::unique_lock<std::mutex> lk(mutex_);
+ if (stop_ ||
+ stop_cv_.wait_for(lk, std::chrono::seconds(report_interval_secs_),
+ [&]() { return stop_; })) {
+ // stopping
+ break;
+ }
+ // else -> timeout, which means time for a report!
+ }
+ auto total_ops_done_snapshot = total_ops_done_.load();
+ // round the seconds elapsed
+ auto secs_elapsed =
+ (env_->NowMicros() - time_started + kMicrosInSecond / 2) /
+ kMicrosInSecond;
+ std::string report = ToString(secs_elapsed) + "," +
+ ToString(total_ops_done_snapshot - last_report_) +
+ "\n";
+ auto s = report_file_->Append(report);
+ if (s.ok()) {
+ s = report_file_->Flush();
+ }
+ if (!s.ok()) {
+ fprintf(stderr,
+ "Can't write to report file (%s), stopping the reporting\n",
+ s.ToString().c_str());
+ break;
+ }
+ last_report_ = total_ops_done_snapshot;
+ }
+ }
+
+ Env* env_;
+ std::unique_ptr<WritableFile> report_file_;
+ std::atomic<int64_t> total_ops_done_;
+ int64_t last_report_;
+ const uint64_t report_interval_secs_;
+ ROCKSDB_NAMESPACE::port::Thread reporting_thread_;
+ std::mutex mutex_;
+ // will notify on stop
+ std::condition_variable stop_cv_;
+ bool stop_;
+};
+
+enum OperationType : unsigned char {
+ kRead = 0,
+ kWrite,
+ kDelete,
+ kSeek,
+ kMerge,
+ kUpdate,
+ kCompress,
+ kUncompress,
+ kCrc,
+ kHash,
+ kOthers
+};
+
+static std::unordered_map<OperationType, std::string, std::hash<unsigned char>>
+ OperationTypeString = {
+ {kRead, "read"},
+ {kWrite, "write"},
+ {kDelete, "delete"},
+ {kSeek, "seek"},
+ {kMerge, "merge"},
+ {kUpdate, "update"},
+ {kCompress, "compress"},
+ {kCompress, "uncompress"},
+ {kCrc, "crc"},
+ {kHash, "hash"},
+ {kOthers, "op"}
+};
+
+class CombinedStats;
+class Stats {
+ private:
+ int id_;
+ uint64_t start_;
+ uint64_t sine_interval_;
+ uint64_t finish_;
+ double seconds_;
+ uint64_t done_;
+ uint64_t last_report_done_;
+ uint64_t next_report_;
+ uint64_t bytes_;
+ uint64_t last_op_finish_;
+ uint64_t last_report_finish_;
+ std::unordered_map<OperationType, std::shared_ptr<HistogramImpl>,
+ std::hash<unsigned char>> hist_;
+ std::string message_;
+ bool exclude_from_merge_;
+ ReporterAgent* reporter_agent_; // does not own
+ friend class CombinedStats;
+
+ public:
+ Stats() { Start(-1); }
+
+ void SetReporterAgent(ReporterAgent* reporter_agent) {
+ reporter_agent_ = reporter_agent;
+ }
+
+ void Start(int id) {
+ id_ = id;
+ next_report_ = FLAGS_stats_interval ? FLAGS_stats_interval : 100;
+ last_op_finish_ = start_;
+ hist_.clear();
+ done_ = 0;
+ last_report_done_ = 0;
+ bytes_ = 0;
+ seconds_ = 0;
+ start_ = FLAGS_env->NowMicros();
+ sine_interval_ = FLAGS_env->NowMicros();
+ finish_ = start_;
+ last_report_finish_ = start_;
+ message_.clear();
+ // When set, stats from this thread won't be merged with others.
+ exclude_from_merge_ = false;
+ }
+
+ void Merge(const Stats& other) {
+ if (other.exclude_from_merge_)
+ return;
+
+ for (auto it = other.hist_.begin(); it != other.hist_.end(); ++it) {
+ auto this_it = hist_.find(it->first);
+ if (this_it != hist_.end()) {
+ this_it->second->Merge(*(other.hist_.at(it->first)));
+ } else {
+ hist_.insert({ it->first, it->second });
+ }
+ }
+
+ done_ += other.done_;
+ 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_.empty()) message_ = other.message_;
+ }
+
+ void Stop() {
+ finish_ = FLAGS_env->NowMicros();
+ seconds_ = (finish_ - start_) * 1e-6;
+ }
+
+ void AddMessage(Slice msg) {
+ AppendWithSpace(&message_, msg);
+ }
+
+ void SetId(int id) { id_ = id; }
+ void SetExcludeFromMerge() { exclude_from_merge_ = true; }
+
+ void PrintThreadStatus() {
+ std::vector<ThreadStatus> thread_list;
+ FLAGS_env->GetThreadList(&thread_list);
+
+ fprintf(stderr, "\n%18s %10s %12s %20s %13s %45s %12s %s\n",
+ "ThreadID", "ThreadType", "cfName", "Operation",
+ "ElapsedTime", "Stage", "State", "OperationProperties");
+
+ int64_t current_time = 0;
+ FLAGS_env->GetCurrentTime(&current_time);
+ for (auto ts : thread_list) {
+ fprintf(stderr, "%18" PRIu64 " %10s %12s %20s %13s %45s %12s",
+ ts.thread_id,
+ ThreadStatus::GetThreadTypeName(ts.thread_type).c_str(),
+ ts.cf_name.c_str(),
+ ThreadStatus::GetOperationName(ts.operation_type).c_str(),
+ ThreadStatus::MicrosToString(ts.op_elapsed_micros).c_str(),
+ ThreadStatus::GetOperationStageName(ts.operation_stage).c_str(),
+ ThreadStatus::GetStateName(ts.state_type).c_str());
+
+ auto op_properties = ThreadStatus::InterpretOperationProperties(
+ ts.operation_type, ts.op_properties);
+ for (const auto& op_prop : op_properties) {
+ fprintf(stderr, " %s %" PRIu64" |",
+ op_prop.first.c_str(), op_prop.second);
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+
+ void ResetSineInterval() {
+ sine_interval_ = FLAGS_env->NowMicros();
+ }
+
+ uint64_t GetSineInterval() {
+ return sine_interval_;
+ }
+
+ uint64_t GetStart() {
+ return start_;
+ }
+
+ void ResetLastOpTime() {
+ // Set to now to avoid latency from calls to SleepForMicroseconds
+ last_op_finish_ = FLAGS_env->NowMicros();
+ }
+
+ void FinishedOps(DBWithColumnFamilies* db_with_cfh, DB* db, int64_t num_ops,
+ enum OperationType op_type = kOthers) {
+ if (reporter_agent_) {
+ reporter_agent_->ReportFinishedOps(num_ops);
+ }
+ if (FLAGS_histogram) {
+ uint64_t now = FLAGS_env->NowMicros();
+ uint64_t micros = now - last_op_finish_;
+
+ if (hist_.find(op_type) == hist_.end())
+ {
+ auto hist_temp = std::make_shared<HistogramImpl>();
+ hist_.insert({op_type, std::move(hist_temp)});
+ }
+ hist_[op_type]->Add(micros);
+
+ if (micros > 20000 && !FLAGS_stats_interval) {
+ fprintf(stderr, "long op: %" PRIu64 " micros%30s\r", micros, "");
+ fflush(stderr);
+ }
+ last_op_finish_ = now;
+ }
+
+ done_ += num_ops;
+ if (done_ >= next_report_) {
+ if (!FLAGS_stats_interval) {
+ 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(stderr, "... finished %" PRIu64 " ops%30s\r", done_, "");
+ } else {
+ uint64_t now = FLAGS_env->NowMicros();
+ int64_t usecs_since_last = now - last_report_finish_;
+
+ // Determine whether to print status where interval is either
+ // each N operations or each N seconds.
+
+ if (FLAGS_stats_interval_seconds &&
+ usecs_since_last < (FLAGS_stats_interval_seconds * 1000000)) {
+ // Don't check again for this many operations
+ next_report_ += FLAGS_stats_interval;
+
+ } else {
+
+ fprintf(stderr,
+ "%s ... thread %d: (%" PRIu64 ",%" PRIu64 ") ops and "
+ "(%.1f,%.1f) ops/second in (%.6f,%.6f) seconds\n",
+ FLAGS_env->TimeToString(now/1000000).c_str(),
+ id_,
+ done_ - last_report_done_, done_,
+ (done_ - last_report_done_) /
+ (usecs_since_last / 1000000.0),
+ done_ / ((now - start_) / 1000000.0),
+ (now - last_report_finish_) / 1000000.0,
+ (now - start_) / 1000000.0);
+
+ if (id_ == 0 && FLAGS_stats_per_interval) {
+ std::string stats;
+
+ if (db_with_cfh && db_with_cfh->num_created.load()) {
+ for (size_t i = 0; i < db_with_cfh->num_created.load(); ++i) {
+ if (db->GetProperty(db_with_cfh->cfh[i], "rocksdb.cfstats",
+ &stats))
+ fprintf(stderr, "%s\n", stats.c_str());
+ if (FLAGS_show_table_properties) {
+ for (int level = 0; level < FLAGS_num_levels; ++level) {
+ if (db->GetProperty(
+ db_with_cfh->cfh[i],
+ "rocksdb.aggregated-table-properties-at-level" +
+ ToString(level),
+ &stats)) {
+ if (stats.find("# entries=0") == std::string::npos) {
+ fprintf(stderr, "Level[%d]: %s\n", level,
+ stats.c_str());
+ }
+ }
+ }
+ }
+ }
+ } else if (db) {
+ if (db->GetProperty("rocksdb.stats", &stats)) {
+ fprintf(stderr, "%s\n", stats.c_str());
+ }
+ if (FLAGS_show_table_properties) {
+ for (int level = 0; level < FLAGS_num_levels; ++level) {
+ if (db->GetProperty(
+ "rocksdb.aggregated-table-properties-at-level" +
+ ToString(level),
+ &stats)) {
+ if (stats.find("# entries=0") == std::string::npos) {
+ fprintf(stderr, "Level[%d]: %s\n", level, stats.c_str());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ next_report_ += FLAGS_stats_interval;
+ last_report_finish_ = now;
+ last_report_done_ = done_;
+ }
+ }
+ if (id_ == 0 && FLAGS_thread_status_per_interval) {
+ PrintThreadStatus();
+ }
+ fflush(stderr);
+ }
+ }
+
+ void AddBytes(int64_t n) {
+ bytes_ += n;
+ }
+
+ void Report(const Slice& name) {
+ // Pretend at least one op was done in case we are running a benchmark
+ // that does not call FinishedOps().
+ if (done_ < 1) done_ = 1;
+
+ std::string extra;
+ if (bytes_ > 0) {
+ // Rate is computed on actual elapsed time, not the sum of per-thread
+ // elapsed times.
+ double elapsed = (finish_ - start_) * 1e-6;
+ char rate[100];
+ snprintf(rate, sizeof(rate), "%6.1f MB/s",
+ (bytes_ / 1048576.0) / elapsed);
+ extra = rate;
+ }
+ AppendWithSpace(&extra, message_);
+ double elapsed = (finish_ - start_) * 1e-6;
+ double throughput = (double)done_/elapsed;
+
+ fprintf(stdout, "%-12s : %11.3f micros/op %ld ops/sec;%s%s\n",
+ name.ToString().c_str(),
+ seconds_ * 1e6 / done_,
+ (long)throughput,
+ (extra.empty() ? "" : " "),
+ extra.c_str());
+ if (FLAGS_histogram) {
+ for (auto it = hist_.begin(); it != hist_.end(); ++it) {
+ fprintf(stdout, "Microseconds per %s:\n%s\n",
+ OperationTypeString[it->first].c_str(),
+ it->second->ToString().c_str());
+ }
+ }
+ if (FLAGS_report_file_operations) {
+ ReportFileOpEnv* env = static_cast<ReportFileOpEnv*>(FLAGS_env);
+ ReportFileOpCounters* counters = env->counters();
+ fprintf(stdout, "Num files opened: %d\n",
+ counters->open_counter_.load(std::memory_order_relaxed));
+ fprintf(stdout, "Num Read(): %d\n",
+ counters->read_counter_.load(std::memory_order_relaxed));
+ fprintf(stdout, "Num Append(): %d\n",
+ counters->append_counter_.load(std::memory_order_relaxed));
+ fprintf(stdout, "Num bytes read: %" PRIu64 "\n",
+ counters->bytes_read_.load(std::memory_order_relaxed));
+ fprintf(stdout, "Num bytes written: %" PRIu64 "\n",
+ counters->bytes_written_.load(std::memory_order_relaxed));
+ env->reset();
+ }
+ fflush(stdout);
+ }
+};
+
+class CombinedStats {
+ public:
+ void AddStats(const Stats& stat) {
+ uint64_t total_ops = stat.done_;
+ uint64_t total_bytes_ = stat.bytes_;
+ double elapsed;
+
+ if (total_ops < 1) {
+ total_ops = 1;
+ }
+
+ elapsed = (stat.finish_ - stat.start_) * 1e-6;
+ throughput_ops_.emplace_back(total_ops / elapsed);
+
+ if (total_bytes_ > 0) {
+ double mbs = (total_bytes_ / 1048576.0);
+ throughput_mbs_.emplace_back(mbs / elapsed);
+ }
+ }
+
+ void Report(const std::string& bench_name) {
+ const char* name = bench_name.c_str();
+ int num_runs = static_cast<int>(throughput_ops_.size());
+
+ if (throughput_mbs_.size() == throughput_ops_.size()) {
+ fprintf(stdout,
+ "%s [AVG %d runs] : %d ops/sec; %6.1f MB/sec\n"
+ "%s [MEDIAN %d runs] : %d ops/sec; %6.1f MB/sec\n",
+ name, num_runs, static_cast<int>(CalcAvg(throughput_ops_)),
+ CalcAvg(throughput_mbs_), name, num_runs,
+ static_cast<int>(CalcMedian(throughput_ops_)),
+ CalcMedian(throughput_mbs_));
+ } else {
+ fprintf(stdout,
+ "%s [AVG %d runs] : %d ops/sec\n"
+ "%s [MEDIAN %d runs] : %d ops/sec\n",
+ name, num_runs, static_cast<int>(CalcAvg(throughput_ops_)), name,
+ num_runs, static_cast<int>(CalcMedian(throughput_ops_)));
+ }
+ }
+
+ private:
+ double CalcAvg(std::vector<double> data) {
+ double avg = 0;
+ for (double x : data) {
+ avg += x;
+ }
+ avg = avg / data.size();
+ return avg;
+ }
+
+ double CalcMedian(std::vector<double> data) {
+ assert(data.size() > 0);
+ std::sort(data.begin(), data.end());
+
+ size_t mid = data.size() / 2;
+ if (data.size() % 2 == 1) {
+ // Odd number of entries
+ return data[mid];
+ } else {
+ // Even number of entries
+ return (data[mid] + data[mid - 1]) / 2;
+ }
+ }
+
+ std::vector<double> throughput_ops_;
+ std::vector<double> throughput_mbs_;
+};
+
+class TimestampEmulator {
+ private:
+ std::atomic<uint64_t> timestamp_;
+
+ public:
+ TimestampEmulator() : timestamp_(0) {}
+ uint64_t Get() const { return timestamp_.load(); }
+ void Inc() { timestamp_++; }
+};
+
+// State shared by all concurrent executions of the same benchmark.
+struct SharedState {
+ port::Mutex mu;
+ port::CondVar cv;
+ int total;
+ int perf_level;
+ std::shared_ptr<RateLimiter> write_rate_limiter;
+ std::shared_ptr<RateLimiter> read_rate_limiter;
+
+ // Each thread goes through the following states:
+ // (1) initializing
+ // (2) waiting for others to be initialized
+ // (3) running
+ // (4) done
+
+ long num_initialized;
+ long num_done;
+ bool start;
+
+ SharedState() : cv(&mu), perf_level(FLAGS_perf_level) { }
+};
+
+// Per-thread state for concurrent executions of the same benchmark.
+struct ThreadState {
+ int tid; // 0..n-1 when running in n threads
+ Random64 rand; // Has different seeds for different threads
+ Stats stats;
+ SharedState* shared;
+
+ /* implicit */ ThreadState(int index)
+ : tid(index),
+ rand((FLAGS_seed ? FLAGS_seed : 1000) + index) {
+ }
+};
+
+class Duration {
+ public:
+ Duration(uint64_t max_seconds, int64_t max_ops, int64_t ops_per_stage = 0) {
+ max_seconds_ = max_seconds;
+ max_ops_= max_ops;
+ ops_per_stage_ = (ops_per_stage > 0) ? ops_per_stage : max_ops;
+ ops_ = 0;
+ start_at_ = FLAGS_env->NowMicros();
+ }
+
+ int64_t GetStage() { return std::min(ops_, max_ops_ - 1) / ops_per_stage_; }
+
+ bool Done(int64_t increment) {
+ if (increment <= 0) increment = 1; // avoid Done(0) and infinite loops
+ ops_ += increment;
+
+ if (max_seconds_) {
+ // Recheck every appx 1000 ops (exact iff increment is factor of 1000)
+ auto granularity = FLAGS_ops_between_duration_checks;
+ if ((ops_ / granularity) != ((ops_ - increment) / granularity)) {
+ uint64_t now = FLAGS_env->NowMicros();
+ return ((now - start_at_) / 1000000) >= max_seconds_;
+ } else {
+ return false;
+ }
+ } else {
+ return ops_ > max_ops_;
+ }
+ }
+
+ private:
+ uint64_t max_seconds_;
+ int64_t max_ops_;
+ int64_t ops_per_stage_;
+ int64_t ops_;
+ uint64_t start_at_;
+};
+
+class Benchmark {
+ private:
+ std::shared_ptr<Cache> cache_;
+ std::shared_ptr<Cache> compressed_cache_;
+ std::shared_ptr<const FilterPolicy> filter_policy_;
+ const SliceTransform* prefix_extractor_;
+ DBWithColumnFamilies db_;
+ std::vector<DBWithColumnFamilies> multi_dbs_;
+ int64_t num_;
+ int key_size_;
+ int prefix_size_;
+ int64_t keys_per_prefix_;
+ int64_t entries_per_batch_;
+ int64_t writes_before_delete_range_;
+ int64_t writes_per_range_tombstone_;
+ int64_t range_tombstone_width_;
+ int64_t max_num_range_tombstones_;
+ WriteOptions write_options_;
+ Options open_options_; // keep options around to properly destroy db later
+#ifndef ROCKSDB_LITE
+ TraceOptions trace_options_;
+ TraceOptions block_cache_trace_options_;
+#endif
+ int64_t reads_;
+ int64_t deletes_;
+ double read_random_exp_range_;
+ int64_t writes_;
+ int64_t readwrites_;
+ int64_t merge_keys_;
+ bool report_file_operations_;
+ bool use_blob_db_;
+ std::vector<std::string> keys_;
+
+ class ErrorHandlerListener : public EventListener {
+ public:
+#ifndef ROCKSDB_LITE
+ ErrorHandlerListener()
+ : mutex_(),
+ cv_(&mutex_),
+ no_auto_recovery_(false),
+ recovery_complete_(false) {}
+
+ ~ErrorHandlerListener() override {}
+
+ void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/,
+ Status /*bg_error*/,
+ bool* auto_recovery) override {
+ if (*auto_recovery && no_auto_recovery_) {
+ *auto_recovery = false;
+ }
+ }
+
+ void OnErrorRecoveryCompleted(Status /*old_bg_error*/) override {
+ InstrumentedMutexLock l(&mutex_);
+ recovery_complete_ = true;
+ cv_.SignalAll();
+ }
+
+ bool WaitForRecovery(uint64_t abs_time_us) {
+ InstrumentedMutexLock l(&mutex_);
+ if (!recovery_complete_) {
+ cv_.TimedWait(abs_time_us);
+ }
+ if (recovery_complete_) {
+ recovery_complete_ = false;
+ return true;
+ }
+ return false;
+ }
+
+ void EnableAutoRecovery(bool enable = true) { no_auto_recovery_ = !enable; }
+
+ private:
+ InstrumentedMutex mutex_;
+ InstrumentedCondVar cv_;
+ bool no_auto_recovery_;
+ bool recovery_complete_;
+#else // ROCKSDB_LITE
+ bool WaitForRecovery(uint64_t /*abs_time_us*/) { return true; }
+ void EnableAutoRecovery(bool /*enable*/) {}
+#endif // ROCKSDB_LITE
+ };
+
+ std::shared_ptr<ErrorHandlerListener> listener_;
+
+ bool SanityCheck() {
+ if (FLAGS_compression_ratio > 1) {
+ fprintf(stderr, "compression_ratio should be between 0 and 1\n");
+ return false;
+ }
+ return true;
+ }
+
+ inline bool CompressSlice(const CompressionInfo& compression_info,
+ const Slice& input, std::string* compressed) {
+ bool ok = true;
+ switch (FLAGS_compression_type_e) {
+ case ROCKSDB_NAMESPACE::kSnappyCompression:
+ ok = Snappy_Compress(compression_info, input.data(), input.size(),
+ compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kZlibCompression:
+ ok = Zlib_Compress(compression_info, 2, input.data(), input.size(),
+ compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kBZip2Compression:
+ ok = BZip2_Compress(compression_info, 2, input.data(), input.size(),
+ compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kLZ4Compression:
+ ok = LZ4_Compress(compression_info, 2, input.data(), input.size(),
+ compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kLZ4HCCompression:
+ ok = LZ4HC_Compress(compression_info, 2, input.data(), input.size(),
+ compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kXpressCompression:
+ ok = XPRESS_Compress(input.data(),
+ input.size(), compressed);
+ break;
+ case ROCKSDB_NAMESPACE::kZSTD:
+ ok = ZSTD_Compress(compression_info, input.data(), input.size(),
+ compressed);
+ break;
+ default:
+ ok = false;
+ }
+ return ok;
+ }
+
+ void PrintHeader() {
+ PrintEnvironment();
+ fprintf(stdout, "Keys: %d bytes each\n", FLAGS_key_size);
+ auto avg_value_size = FLAGS_value_size;
+ if (FLAGS_value_size_distribution_type_e == kFixed) {
+ fprintf(stdout, "Values: %d bytes each (%d bytes after compression)\n",
+ avg_value_size,
+ static_cast<int>(avg_value_size * FLAGS_compression_ratio + 0.5));
+ } else {
+ avg_value_size = (FLAGS_value_size_min + FLAGS_value_size_max) / 2;
+ fprintf(stdout, "Values: %d avg bytes each (%d bytes after compression)\n",
+ avg_value_size,
+ static_cast<int>(avg_value_size * FLAGS_compression_ratio + 0.5));
+ fprintf(stdout, "Values Distribution: %s (min: %d, max: %d)\n",
+ FLAGS_value_size_distribution_type.c_str(),
+ FLAGS_value_size_min, FLAGS_value_size_max);
+ }
+ fprintf(stdout, "Entries: %" PRIu64 "\n", num_);
+ fprintf(stdout, "Prefix: %d bytes\n", FLAGS_prefix_size);
+ fprintf(stdout, "Keys per prefix: %" PRIu64 "\n", keys_per_prefix_);
+ fprintf(stdout, "RawSize: %.1f MB (estimated)\n",
+ ((static_cast<int64_t>(FLAGS_key_size + avg_value_size) * num_)
+ / 1048576.0));
+ fprintf(stdout, "FileSize: %.1f MB (estimated)\n",
+ (((FLAGS_key_size + avg_value_size * FLAGS_compression_ratio)
+ * num_)
+ / 1048576.0));
+ fprintf(stdout, "Write rate: %" PRIu64 " bytes/second\n",
+ FLAGS_benchmark_write_rate_limit);
+ fprintf(stdout, "Read rate: %" PRIu64 " ops/second\n",
+ FLAGS_benchmark_read_rate_limit);
+ if (FLAGS_enable_numa) {
+ fprintf(stderr, "Running in NUMA enabled mode.\n");
+#ifndef NUMA
+ fprintf(stderr, "NUMA is not defined in the system.\n");
+ exit(1);
+#else
+ if (numa_available() == -1) {
+ fprintf(stderr, "NUMA is not supported by the system.\n");
+ exit(1);
+ }
+#endif
+ }
+
+ auto compression = CompressionTypeToString(FLAGS_compression_type_e);
+ fprintf(stdout, "Compression: %s\n", compression.c_str());
+ fprintf(stdout, "Compression sampling rate: %" PRId64 "\n",
+ FLAGS_sample_for_compression);
+
+ switch (FLAGS_rep_factory) {
+ case kPrefixHash:
+ fprintf(stdout, "Memtablerep: prefix_hash\n");
+ break;
+ case kSkipList:
+ fprintf(stdout, "Memtablerep: skip_list\n");
+ break;
+ case kVectorRep:
+ fprintf(stdout, "Memtablerep: vector\n");
+ break;
+ case kHashLinkedList:
+ fprintf(stdout, "Memtablerep: hash_linkedlist\n");
+ break;
+ }
+ fprintf(stdout, "Perf Level: %d\n", FLAGS_perf_level);
+
+ PrintWarnings(compression.c_str());
+ fprintf(stdout, "------------------------------------------------\n");
+ }
+
+ void PrintWarnings(const char* compression) {
+#if defined(__GNUC__) && !defined(__OPTIMIZE__)
+ fprintf(stdout,
+ "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n"
+ );
+#endif
+#ifndef NDEBUG
+ fprintf(stdout,
+ "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
+#endif
+ if (FLAGS_compression_type_e != ROCKSDB_NAMESPACE::kNoCompression) {
+ // The test string should not be too small.
+ const int len = FLAGS_block_size;
+ std::string input_str(len, 'y');
+ std::string compressed;
+ CompressionOptions opts;
+ CompressionContext context(FLAGS_compression_type_e);
+ CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
+ FLAGS_compression_type_e,
+ FLAGS_sample_for_compression);
+ bool result = CompressSlice(info, Slice(input_str), &compressed);
+
+ if (!result) {
+ fprintf(stdout, "WARNING: %s compression is not enabled\n",
+ compression);
+ } else if (compressed.size() >= input_str.size()) {
+ fprintf(stdout, "WARNING: %s compression is not effective\n",
+ compression);
+ }
+ }
+ }
+
+// Current the following isn't equivalent to OS_LINUX.
+#if defined(__linux)
+ static Slice TrimSpace(Slice s) {
+ unsigned int start = 0;
+ while (start < s.size() && isspace(s[start])) {
+ start++;
+ }
+ unsigned int limit = static_cast<unsigned int>(s.size());
+ while (limit > start && isspace(s[limit-1])) {
+ limit--;
+ }
+ return Slice(s.data() + start, limit - start);
+ }
+#endif
+
+ void PrintEnvironment() {
+ fprintf(stderr, "RocksDB: version %d.%d\n",
+ kMajorVersion, kMinorVersion);
+
+#if defined(__linux)
+ time_t now = time(nullptr);
+ char buf[52];
+ // Lint complains about ctime() usage, so replace it with ctime_r(). The
+ // requirement is to provide a buffer which is at least 26 bytes.
+ fprintf(stderr, "Date: %s",
+ ctime_r(&now, buf)); // ctime_r() adds newline
+
+ FILE* cpuinfo = fopen("/proc/cpuinfo", "r");
+ if (cpuinfo != nullptr) {
+ char line[1000];
+ int num_cpus = 0;
+ std::string cpu_type;
+ std::string cache_size;
+ while (fgets(line, sizeof(line), cpuinfo) != nullptr) {
+ const char* sep = strchr(line, ':');
+ if (sep == nullptr) {
+ continue;
+ }
+ Slice key = TrimSpace(Slice(line, sep - 1 - line));
+ Slice val = TrimSpace(Slice(sep + 1));
+ if (key == "model name") {
+ ++num_cpus;
+ cpu_type = val.ToString();
+ } else if (key == "cache size") {
+ cache_size = val.ToString();
+ }
+ }
+ fclose(cpuinfo);
+ fprintf(stderr, "CPU: %d * %s\n", num_cpus, cpu_type.c_str());
+ fprintf(stderr, "CPUCache: %s\n", cache_size.c_str());
+ }
+#endif
+ }
+
+ static bool KeyExpired(const TimestampEmulator* timestamp_emulator,
+ const Slice& key) {
+ const char* pos = key.data();
+ pos += 8;
+ uint64_t timestamp = 0;
+ if (port::kLittleEndian) {
+ int bytes_to_fill = 8;
+ for (int i = 0; i < bytes_to_fill; ++i) {
+ timestamp |= (static_cast<uint64_t>(static_cast<unsigned char>(pos[i]))
+ << ((bytes_to_fill - i - 1) << 3));
+ }
+ } else {
+ memcpy(&timestamp, pos, sizeof(timestamp));
+ }
+ return timestamp_emulator->Get() - timestamp > FLAGS_time_range;
+ }
+
+ class ExpiredTimeFilter : public CompactionFilter {
+ public:
+ explicit ExpiredTimeFilter(
+ const std::shared_ptr<TimestampEmulator>& timestamp_emulator)
+ : timestamp_emulator_(timestamp_emulator) {}
+ bool Filter(int /*level*/, const Slice& key,
+ const Slice& /*existing_value*/, std::string* /*new_value*/,
+ bool* /*value_changed*/) const override {
+ return KeyExpired(timestamp_emulator_.get(), key);
+ }
+ const char* Name() const override { return "ExpiredTimeFilter"; }
+
+ private:
+ std::shared_ptr<TimestampEmulator> timestamp_emulator_;
+ };
+
+ 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"; }
+ };
+
+ std::shared_ptr<Cache> NewCache(int64_t capacity) {
+ if (capacity <= 0) {
+ return nullptr;
+ }
+ if (FLAGS_use_clock_cache) {
+ auto cache = NewClockCache(static_cast<size_t>(capacity),
+ FLAGS_cache_numshardbits);
+ if (!cache) {
+ fprintf(stderr, "Clock cache not supported.");
+ exit(1);
+ }
+ return cache;
+ } else {
+ return NewLRUCache(
+ static_cast<size_t>(capacity), FLAGS_cache_numshardbits,
+ false /*strict_capacity_limit*/, FLAGS_cache_high_pri_pool_ratio);
+ }
+ }
+
+ public:
+ Benchmark()
+ : cache_(NewCache(FLAGS_cache_size)),
+ compressed_cache_(NewCache(FLAGS_compressed_cache_size)),
+ filter_policy_(FLAGS_bloom_bits >= 0
+ ? NewBloomFilterPolicy(FLAGS_bloom_bits,
+ FLAGS_use_block_based_filter)
+ : nullptr),
+ prefix_extractor_(NewFixedPrefixTransform(FLAGS_prefix_size)),
+ num_(FLAGS_num),
+ key_size_(FLAGS_key_size),
+ prefix_size_(FLAGS_prefix_size),
+ keys_per_prefix_(FLAGS_keys_per_prefix),
+ entries_per_batch_(1),
+ reads_(FLAGS_reads < 0 ? FLAGS_num : FLAGS_reads),
+ read_random_exp_range_(0.0),
+ writes_(FLAGS_writes < 0 ? FLAGS_num : FLAGS_writes),
+ readwrites_(
+ (FLAGS_writes < 0 && FLAGS_reads < 0)
+ ? FLAGS_num
+ : ((FLAGS_writes > FLAGS_reads) ? FLAGS_writes : FLAGS_reads)),
+ merge_keys_(FLAGS_merge_keys < 0 ? FLAGS_num : FLAGS_merge_keys),
+ report_file_operations_(FLAGS_report_file_operations),
+#ifndef ROCKSDB_LITE
+ use_blob_db_(FLAGS_use_blob_db)
+#else
+ use_blob_db_(false)
+#endif // !ROCKSDB_LITE
+ {
+ // use simcache instead of cache
+ if (FLAGS_simcache_size >= 0) {
+ if (FLAGS_cache_numshardbits >= 1) {
+ cache_ =
+ NewSimCache(cache_, FLAGS_simcache_size, FLAGS_cache_numshardbits);
+ } else {
+ cache_ = NewSimCache(cache_, FLAGS_simcache_size, 0);
+ }
+ }
+
+ if (report_file_operations_) {
+ if (!FLAGS_hdfs.empty()) {
+ fprintf(stderr,
+ "--hdfs and --report_file_operations cannot be enabled "
+ "at the same time");
+ exit(1);
+ }
+ FLAGS_env = new ReportFileOpEnv(FLAGS_env);
+ }
+
+ if (FLAGS_prefix_size > FLAGS_key_size) {
+ fprintf(stderr, "prefix size is larger than key size");
+ exit(1);
+ }
+
+ std::vector<std::string> files;
+ FLAGS_env->GetChildren(FLAGS_db, &files);
+ for (size_t i = 0; i < files.size(); i++) {
+ if (Slice(files[i]).starts_with("heap-")) {
+ FLAGS_env->DeleteFile(FLAGS_db + "/" + files[i]);
+ }
+ }
+ if (!FLAGS_use_existing_db) {
+ Options options;
+ options.env = FLAGS_env;
+ if (!FLAGS_wal_dir.empty()) {
+ options.wal_dir = FLAGS_wal_dir;
+ }
+#ifndef ROCKSDB_LITE
+ if (use_blob_db_) {
+ blob_db::DestroyBlobDB(FLAGS_db, options, blob_db::BlobDBOptions());
+ }
+#endif // !ROCKSDB_LITE
+ DestroyDB(FLAGS_db, options);
+ if (!FLAGS_wal_dir.empty()) {
+ FLAGS_env->DeleteDir(FLAGS_wal_dir);
+ }
+
+ if (FLAGS_num_multi_db > 1) {
+ FLAGS_env->CreateDir(FLAGS_db);
+ if (!FLAGS_wal_dir.empty()) {
+ FLAGS_env->CreateDir(FLAGS_wal_dir);
+ }
+ }
+ }
+
+ listener_.reset(new ErrorHandlerListener());
+ }
+
+ ~Benchmark() {
+ db_.DeleteDBs();
+ delete prefix_extractor_;
+ if (cache_.get() != nullptr) {
+ // this will leak, but we're shutting down so nobody cares
+ cache_->DisownData();
+ }
+ }
+
+ Slice AllocateKey(std::unique_ptr<const char[]>* key_guard) {
+ char* data = new char[key_size_];
+ const char* const_data = data;
+ key_guard->reset(const_data);
+ return Slice(key_guard->get(), key_size_);
+ }
+
+ // Generate key according to the given specification and random number.
+ // The resulting key will have the following format (if keys_per_prefix_
+ // is positive), extra trailing bytes are either cut off or padded with '0'.
+ // The prefix value is derived from key value.
+ // ----------------------------
+ // | prefix 00000 | key 00000 |
+ // ----------------------------
+ // If keys_per_prefix_ is 0, the key is simply a binary representation of
+ // random number followed by trailing '0's
+ // ----------------------------
+ // | key 00000 |
+ // ----------------------------
+ void GenerateKeyFromInt(uint64_t v, int64_t num_keys, Slice* key) {
+ if (!keys_.empty()) {
+ assert(FLAGS_use_existing_keys);
+ assert(keys_.size() == static_cast<size_t>(num_keys));
+ assert(v < static_cast<uint64_t>(num_keys));
+ *key = keys_[v];
+ return;
+ }
+ char* start = const_cast<char*>(key->data());
+ char* pos = start;
+ if (keys_per_prefix_ > 0) {
+ int64_t num_prefix = num_keys / keys_per_prefix_;
+ int64_t prefix = v % num_prefix;
+ int bytes_to_fill = std::min(prefix_size_, 8);
+ if (port::kLittleEndian) {
+ for (int i = 0; i < bytes_to_fill; ++i) {
+ pos[i] = (prefix >> ((bytes_to_fill - i - 1) << 3)) & 0xFF;
+ }
+ } else {
+ memcpy(pos, static_cast<void*>(&prefix), bytes_to_fill);
+ }
+ if (prefix_size_ > 8) {
+ // fill the rest with 0s
+ memset(pos + 8, '0', prefix_size_ - 8);
+ }
+ pos += prefix_size_;
+ }
+
+ int bytes_to_fill = std::min(key_size_ - static_cast<int>(pos - start), 8);
+ if (port::kLittleEndian) {
+ for (int i = 0; i < bytes_to_fill; ++i) {
+ pos[i] = (v >> ((bytes_to_fill - i - 1) << 3)) & 0xFF;
+ }
+ } else {
+ memcpy(pos, static_cast<void*>(&v), bytes_to_fill);
+ }
+ pos += bytes_to_fill;
+ if (key_size_ > pos - start) {
+ memset(pos, '0', key_size_ - (pos - start));
+ }
+ }
+
+ void GenerateKeyFromIntForSeek(uint64_t v, int64_t num_keys, Slice* key) {
+ GenerateKeyFromInt(v, num_keys, key);
+ if (FLAGS_seek_missing_prefix) {
+ assert(prefix_size_ > 8);
+ char* key_ptr = const_cast<char*>(key->data());
+ // This rely on GenerateKeyFromInt filling paddings with '0's.
+ // Putting a '1' will create a non-existing prefix.
+ key_ptr[8] = '1';
+ }
+ }
+
+ std::string GetPathForMultiple(std::string base_name, size_t id) {
+ if (!base_name.empty()) {
+#ifndef OS_WIN
+ if (base_name.back() != '/') {
+ base_name += '/';
+ }
+#else
+ if (base_name.back() != '\\') {
+ base_name += '\\';
+ }
+#endif
+ }
+ return base_name + ToString(id);
+ }
+
+ void VerifyDBFromDB(std::string& truth_db_name) {
+ DBWithColumnFamilies truth_db;
+ auto s = DB::OpenForReadOnly(open_options_, truth_db_name, &truth_db.db);
+ if (!s.ok()) {
+ fprintf(stderr, "open error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ ReadOptions ro;
+ ro.total_order_seek = true;
+ std::unique_ptr<Iterator> truth_iter(truth_db.db->NewIterator(ro));
+ std::unique_ptr<Iterator> db_iter(db_.db->NewIterator(ro));
+ // Verify that all the key/values in truth_db are retrivable in db with
+ // ::Get
+ fprintf(stderr, "Verifying db >= truth_db with ::Get...\n");
+ for (truth_iter->SeekToFirst(); truth_iter->Valid(); truth_iter->Next()) {
+ std::string value;
+ s = db_.db->Get(ro, truth_iter->key(), &value);
+ assert(s.ok());
+ // TODO(myabandeh): provide debugging hints
+ assert(Slice(value) == truth_iter->value());
+ }
+ // Verify that the db iterator does not give any extra key/value
+ fprintf(stderr, "Verifying db == truth_db...\n");
+ for (db_iter->SeekToFirst(), truth_iter->SeekToFirst(); db_iter->Valid();
+ db_iter->Next(), truth_iter->Next()) {
+ assert(truth_iter->Valid());
+ assert(truth_iter->value() == db_iter->value());
+ }
+ // No more key should be left unchecked in truth_db
+ assert(!truth_iter->Valid());
+ fprintf(stderr, "...Verified\n");
+ }
+
+ void Run() {
+ if (!SanityCheck()) {
+ exit(1);
+ }
+ Open(&open_options_);
+ PrintHeader();
+ std::stringstream benchmark_stream(FLAGS_benchmarks);
+ std::string name;
+ std::unique_ptr<ExpiredTimeFilter> filter;
+ while (std::getline(benchmark_stream, name, ',')) {
+ // Sanitize parameters
+ num_ = FLAGS_num;
+ reads_ = (FLAGS_reads < 0 ? FLAGS_num : FLAGS_reads);
+ writes_ = (FLAGS_writes < 0 ? FLAGS_num : FLAGS_writes);
+ deletes_ = (FLAGS_deletes < 0 ? FLAGS_num : FLAGS_deletes);
+ value_size = FLAGS_value_size;
+ key_size_ = FLAGS_key_size;
+ entries_per_batch_ = FLAGS_batch_size;
+ writes_before_delete_range_ = FLAGS_writes_before_delete_range;
+ writes_per_range_tombstone_ = FLAGS_writes_per_range_tombstone;
+ range_tombstone_width_ = FLAGS_range_tombstone_width;
+ max_num_range_tombstones_ = FLAGS_max_num_range_tombstones;
+ write_options_ = WriteOptions();
+ read_random_exp_range_ = FLAGS_read_random_exp_range;
+ if (FLAGS_sync) {
+ write_options_.sync = true;
+ }
+ write_options_.disableWAL = FLAGS_disable_wal;
+
+ void (Benchmark::*method)(ThreadState*) = nullptr;
+ void (Benchmark::*post_process_method)() = nullptr;
+
+ bool fresh_db = false;
+ int num_threads = FLAGS_threads;
+
+ int num_repeat = 1;
+ int num_warmup = 0;
+ if (!name.empty() && *name.rbegin() == ']') {
+ auto it = name.find('[');
+ if (it == std::string::npos) {
+ fprintf(stderr, "unknown benchmark arguments '%s'\n", name.c_str());
+ exit(1);
+ }
+ std::string args = name.substr(it + 1);
+ args.resize(args.size() - 1);
+ name.resize(it);
+
+ std::string bench_arg;
+ std::stringstream args_stream(args);
+ while (std::getline(args_stream, bench_arg, '-')) {
+ if (bench_arg.empty()) {
+ continue;
+ }
+ if (bench_arg[0] == 'X') {
+ // Repeat the benchmark n times
+ std::string num_str = bench_arg.substr(1);
+ num_repeat = std::stoi(num_str);
+ } else if (bench_arg[0] == 'W') {
+ // Warm up the benchmark for n times
+ std::string num_str = bench_arg.substr(1);
+ num_warmup = std::stoi(num_str);
+ }
+ }
+ }
+
+ // Both fillseqdeterministic and filluniquerandomdeterministic
+ // fill the levels except the max level with UNIQUE_RANDOM
+ // and fill the max level with fillseq and filluniquerandom, respectively
+ if (name == "fillseqdeterministic" ||
+ name == "filluniquerandomdeterministic") {
+ if (!FLAGS_disable_auto_compactions) {
+ fprintf(stderr,
+ "Please disable_auto_compactions in FillDeterministic "
+ "benchmark\n");
+ exit(1);
+ }
+ if (num_threads > 1) {
+ fprintf(stderr,
+ "filldeterministic multithreaded not supported"
+ ", use 1 thread\n");
+ num_threads = 1;
+ }
+ fresh_db = true;
+ if (name == "fillseqdeterministic") {
+ method = &Benchmark::WriteSeqDeterministic;
+ } else {
+ method = &Benchmark::WriteUniqueRandomDeterministic;
+ }
+ } else if (name == "fillseq") {
+ fresh_db = true;
+ method = &Benchmark::WriteSeq;
+ } else if (name == "fillbatch") {
+ fresh_db = true;
+ entries_per_batch_ = 1000;
+ method = &Benchmark::WriteSeq;
+ } else if (name == "fillrandom") {
+ fresh_db = true;
+ method = &Benchmark::WriteRandom;
+ } else if (name == "filluniquerandom") {
+ fresh_db = true;
+ if (num_threads > 1) {
+ fprintf(stderr,
+ "filluniquerandom multithreaded not supported"
+ ", use 1 thread");
+ num_threads = 1;
+ }
+ method = &Benchmark::WriteUniqueRandom;
+ } else if (name == "overwrite") {
+ method = &Benchmark::WriteRandom;
+ } else if (name == "fillsync") {
+ fresh_db = true;
+ num_ /= 1000;
+ write_options_.sync = true;
+ method = &Benchmark::WriteRandom;
+ } else if (name == "fill100K") {
+ fresh_db = true;
+ num_ /= 1000;
+ value_size = 100 * 1000;
+ method = &Benchmark::WriteRandom;
+ } else if (name == "readseq") {
+ method = &Benchmark::ReadSequential;
+ } else if (name == "readtorowcache") {
+ if (!FLAGS_use_existing_keys || !FLAGS_row_cache_size) {
+ fprintf(stderr,
+ "Please set use_existing_keys to true and specify a "
+ "row cache size in readtorowcache benchmark\n");
+ exit(1);
+ }
+ method = &Benchmark::ReadToRowCache;
+ } else if (name == "readtocache") {
+ method = &Benchmark::ReadSequential;
+ num_threads = 1;
+ reads_ = num_;
+ } else if (name == "readreverse") {
+ method = &Benchmark::ReadReverse;
+ } else if (name == "readrandom") {
+ if (FLAGS_multiread_stride) {
+ fprintf(stderr, "entries_per_batch = %" PRIi64 "\n",
+ entries_per_batch_);
+ }
+ method = &Benchmark::ReadRandom;
+ } else if (name == "readrandomfast") {
+ method = &Benchmark::ReadRandomFast;
+ } else if (name == "multireadrandom") {
+ fprintf(stderr, "entries_per_batch = %" PRIi64 "\n",
+ entries_per_batch_);
+ method = &Benchmark::MultiReadRandom;
+ } else if (name == "mixgraph") {
+ method = &Benchmark::MixGraph;
+ } else if (name == "readmissing") {
+ ++key_size_;
+ method = &Benchmark::ReadRandom;
+ } else if (name == "newiterator") {
+ method = &Benchmark::IteratorCreation;
+ } else if (name == "newiteratorwhilewriting") {
+ num_threads++; // Add extra thread for writing
+ method = &Benchmark::IteratorCreationWhileWriting;
+ } else if (name == "seekrandom") {
+ method = &Benchmark::SeekRandom;
+ } else if (name == "seekrandomwhilewriting") {
+ num_threads++; // Add extra thread for writing
+ method = &Benchmark::SeekRandomWhileWriting;
+ } else if (name == "seekrandomwhilemerging") {
+ num_threads++; // Add extra thread for merging
+ method = &Benchmark::SeekRandomWhileMerging;
+ } else if (name == "readrandomsmall") {
+ reads_ /= 1000;
+ method = &Benchmark::ReadRandom;
+ } else if (name == "deleteseq") {
+ method = &Benchmark::DeleteSeq;
+ } else if (name == "deleterandom") {
+ method = &Benchmark::DeleteRandom;
+ } else if (name == "readwhilewriting") {
+ num_threads++; // Add extra thread for writing
+ method = &Benchmark::ReadWhileWriting;
+ } else if (name == "readwhilemerging") {
+ num_threads++; // Add extra thread for writing
+ method = &Benchmark::ReadWhileMerging;
+ } else if (name == "readwhilescanning") {
+ num_threads++; // Add extra thread for scaning
+ method = &Benchmark::ReadWhileScanning;
+ } else if (name == "readrandomwriterandom") {
+ method = &Benchmark::ReadRandomWriteRandom;
+ } else if (name == "readrandommergerandom") {
+ if (FLAGS_merge_operator.empty()) {
+ fprintf(stdout, "%-12s : skipped (--merge_operator is unknown)\n",
+ name.c_str());
+ exit(1);
+ }
+ method = &Benchmark::ReadRandomMergeRandom;
+ } else if (name == "updaterandom") {
+ method = &Benchmark::UpdateRandom;
+ } else if (name == "xorupdaterandom") {
+ method = &Benchmark::XORUpdateRandom;
+ } else if (name == "appendrandom") {
+ method = &Benchmark::AppendRandom;
+ } else if (name == "mergerandom") {
+ if (FLAGS_merge_operator.empty()) {
+ fprintf(stdout, "%-12s : skipped (--merge_operator is unknown)\n",
+ name.c_str());
+ exit(1);
+ }
+ method = &Benchmark::MergeRandom;
+ } else if (name == "randomwithverify") {
+ method = &Benchmark::RandomWithVerify;
+ } else if (name == "fillseekseq") {
+ method = &Benchmark::WriteSeqSeekSeq;
+ } else if (name == "compact") {
+ method = &Benchmark::Compact;
+ } else if (name == "compactall") {
+ CompactAll();
+ } else if (name == "crc32c") {
+ method = &Benchmark::Crc32c;
+ } else if (name == "xxhash") {
+ method = &Benchmark::xxHash;
+ } else if (name == "acquireload") {
+ method = &Benchmark::AcquireLoad;
+ } else if (name == "compress") {
+ method = &Benchmark::Compress;
+ } else if (name == "uncompress") {
+ method = &Benchmark::Uncompress;
+#ifndef ROCKSDB_LITE
+ } else if (name == "randomtransaction") {
+ method = &Benchmark::RandomTransaction;
+ post_process_method = &Benchmark::RandomTransactionVerify;
+#endif // ROCKSDB_LITE
+ } else if (name == "randomreplacekeys") {
+ fresh_db = true;
+ method = &Benchmark::RandomReplaceKeys;
+ } else if (name == "timeseries") {
+ timestamp_emulator_.reset(new TimestampEmulator());
+ if (FLAGS_expire_style == "compaction_filter") {
+ filter.reset(new ExpiredTimeFilter(timestamp_emulator_));
+ fprintf(stdout, "Compaction filter is used to remove expired data");
+ open_options_.compaction_filter = filter.get();
+ }
+ fresh_db = true;
+ method = &Benchmark::TimeSeries;
+ } else if (name == "stats") {
+ PrintStats("rocksdb.stats");
+ } else if (name == "resetstats") {
+ ResetStats();
+ } else if (name == "verify") {
+ VerifyDBFromDB(FLAGS_truth_db);
+ } else if (name == "levelstats") {
+ PrintStats("rocksdb.levelstats");
+ } else if (name == "sstables") {
+ PrintStats("rocksdb.sstables");
+ } else if (name == "stats_history") {
+ PrintStatsHistory();
+ } else if (name == "replay") {
+ if (num_threads > 1) {
+ fprintf(stderr, "Multi-threaded replay is not yet supported\n");
+ exit(1);
+ }
+ if (FLAGS_trace_file == "") {
+ fprintf(stderr, "Please set --trace_file to be replayed from\n");
+ exit(1);
+ }
+ method = &Benchmark::Replay;
+ } else if (name == "getmergeoperands") {
+ method = &Benchmark::GetMergeOperands;
+ } else if (!name.empty()) { // No error message for empty name
+ fprintf(stderr, "unknown benchmark '%s'\n", name.c_str());
+ exit(1);
+ }
+
+ if (fresh_db) {
+ if (FLAGS_use_existing_db) {
+ fprintf(stdout, "%-12s : skipped (--use_existing_db is true)\n",
+ name.c_str());
+ method = nullptr;
+ } else {
+ if (db_.db != nullptr) {
+ db_.DeleteDBs();
+ DestroyDB(FLAGS_db, open_options_);
+ }
+ Options options = open_options_;
+ for (size_t i = 0; i < multi_dbs_.size(); i++) {
+ delete multi_dbs_[i].db;
+ if (!open_options_.wal_dir.empty()) {
+ options.wal_dir = GetPathForMultiple(open_options_.wal_dir, i);
+ }
+ DestroyDB(GetPathForMultiple(FLAGS_db, i), options);
+ }
+ multi_dbs_.clear();
+ }
+ Open(&open_options_); // use open_options for the last accessed
+ }
+
+ if (method != nullptr) {
+ fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
+
+#ifndef ROCKSDB_LITE
+ // A trace_file option can be provided both for trace and replay
+ // operations. But db_bench does not support tracing and replaying at
+ // the same time, for now. So, start tracing only when it is not a
+ // replay.
+ if (FLAGS_trace_file != "" && name != "replay") {
+ std::unique_ptr<TraceWriter> trace_writer;
+ Status s = NewFileTraceWriter(FLAGS_env, EnvOptions(),
+ FLAGS_trace_file, &trace_writer);
+ if (!s.ok()) {
+ fprintf(stderr, "Encountered an error starting a trace, %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ s = db_.db->StartTrace(trace_options_, std::move(trace_writer));
+ if (!s.ok()) {
+ fprintf(stderr, "Encountered an error starting a trace, %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ fprintf(stdout, "Tracing the workload to: [%s]\n",
+ FLAGS_trace_file.c_str());
+ }
+ // Start block cache tracing.
+ if (!FLAGS_block_cache_trace_file.empty()) {
+ // Sanity checks.
+ if (FLAGS_block_cache_trace_sampling_frequency <= 0) {
+ fprintf(stderr,
+ "Block cache trace sampling frequency must be higher than "
+ "0.\n");
+ exit(1);
+ }
+ if (FLAGS_block_cache_trace_max_trace_file_size_in_bytes <= 0) {
+ fprintf(stderr,
+ "The maximum file size for block cache tracing must be "
+ "higher than 0.\n");
+ exit(1);
+ }
+ block_cache_trace_options_.max_trace_file_size =
+ FLAGS_block_cache_trace_max_trace_file_size_in_bytes;
+ block_cache_trace_options_.sampling_frequency =
+ FLAGS_block_cache_trace_sampling_frequency;
+ std::unique_ptr<TraceWriter> block_cache_trace_writer;
+ Status s = NewFileTraceWriter(FLAGS_env, EnvOptions(),
+ FLAGS_block_cache_trace_file,
+ &block_cache_trace_writer);
+ if (!s.ok()) {
+ fprintf(stderr,
+ "Encountered an error when creating trace writer, %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ s = db_.db->StartBlockCacheTrace(block_cache_trace_options_,
+ std::move(block_cache_trace_writer));
+ if (!s.ok()) {
+ fprintf(
+ stderr,
+ "Encountered an error when starting block cache tracing, %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ fprintf(stdout, "Tracing block cache accesses to: [%s]\n",
+ FLAGS_block_cache_trace_file.c_str());
+ }
+#endif // ROCKSDB_LITE
+
+ if (num_warmup > 0) {
+ printf("Warming up benchmark by running %d times\n", num_warmup);
+ }
+
+ for (int i = 0; i < num_warmup; i++) {
+ RunBenchmark(num_threads, name, method);
+ }
+
+ if (num_repeat > 1) {
+ printf("Running benchmark for %d times\n", num_repeat);
+ }
+
+ CombinedStats combined_stats;
+ for (int i = 0; i < num_repeat; i++) {
+ Stats stats = RunBenchmark(num_threads, name, method);
+ combined_stats.AddStats(stats);
+ }
+ if (num_repeat > 1) {
+ combined_stats.Report(name);
+ }
+ }
+ if (post_process_method != nullptr) {
+ (this->*post_process_method)();
+ }
+ }
+
+ if (secondary_update_thread_) {
+ secondary_update_stopped_.store(1, std::memory_order_relaxed);
+ secondary_update_thread_->join();
+ secondary_update_thread_.reset();
+ }
+
+#ifndef ROCKSDB_LITE
+ if (name != "replay" && FLAGS_trace_file != "") {
+ Status s = db_.db->EndTrace();
+ if (!s.ok()) {
+ fprintf(stderr, "Encountered an error ending the trace, %s\n",
+ s.ToString().c_str());
+ }
+ }
+ if (!FLAGS_block_cache_trace_file.empty()) {
+ Status s = db_.db->EndBlockCacheTrace();
+ if (!s.ok()) {
+ fprintf(stderr,
+ "Encountered an error ending the block cache tracing, %s\n",
+ s.ToString().c_str());
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ if (FLAGS_statistics) {
+ fprintf(stdout, "STATISTICS:\n%s\n", dbstats->ToString().c_str());
+ }
+ if (FLAGS_simcache_size >= 0) {
+ fprintf(stdout, "SIMULATOR CACHE STATISTICS:\n%s\n",
+ static_cast_with_check<SimCache, Cache>(cache_.get())
+ ->ToString()
+ .c_str());
+ }
+
+#ifndef ROCKSDB_LITE
+ if (FLAGS_use_secondary_db) {
+ fprintf(stdout, "Secondary instance updated %" PRIu64 " times.\n",
+ secondary_db_updates_);
+ }
+#endif // ROCKSDB_LITE
+ }
+
+ private:
+ std::shared_ptr<TimestampEmulator> timestamp_emulator_;
+ std::unique_ptr<port::Thread> secondary_update_thread_;
+ std::atomic<int> secondary_update_stopped_{0};
+#ifndef ROCKSDB_LITE
+ uint64_t secondary_db_updates_ = 0;
+#endif // ROCKSDB_LITE
+ struct ThreadArg {
+ Benchmark* bm;
+ SharedState* shared;
+ ThreadState* thread;
+ void (Benchmark::*method)(ThreadState*);
+ };
+
+ static void ThreadBody(void* v) {
+ ThreadArg* arg = reinterpret_cast<ThreadArg*>(v);
+ SharedState* shared = arg->shared;
+ ThreadState* thread = arg->thread;
+ {
+ MutexLock l(&shared->mu);
+ shared->num_initialized++;
+ if (shared->num_initialized >= shared->total) {
+ shared->cv.SignalAll();
+ }
+ while (!shared->start) {
+ shared->cv.Wait();
+ }
+ }
+
+ SetPerfLevel(static_cast<PerfLevel> (shared->perf_level));
+ perf_context.EnablePerLevelPerfContext();
+ thread->stats.Start(thread->tid);
+ (arg->bm->*(arg->method))(thread);
+ thread->stats.Stop();
+
+ {
+ MutexLock l(&shared->mu);
+ shared->num_done++;
+ if (shared->num_done >= shared->total) {
+ shared->cv.SignalAll();
+ }
+ }
+ }
+
+ Stats RunBenchmark(int n, Slice name,
+ void (Benchmark::*method)(ThreadState*)) {
+ SharedState shared;
+ shared.total = n;
+ shared.num_initialized = 0;
+ shared.num_done = 0;
+ shared.start = false;
+ if (FLAGS_benchmark_write_rate_limit > 0) {
+ shared.write_rate_limiter.reset(
+ NewGenericRateLimiter(FLAGS_benchmark_write_rate_limit));
+ }
+ if (FLAGS_benchmark_read_rate_limit > 0) {
+ shared.read_rate_limiter.reset(NewGenericRateLimiter(
+ FLAGS_benchmark_read_rate_limit, 100000 /* refill_period_us */,
+ 10 /* fairness */, RateLimiter::Mode::kReadsOnly));
+ }
+
+ std::unique_ptr<ReporterAgent> reporter_agent;
+ if (FLAGS_report_interval_seconds > 0) {
+ reporter_agent.reset(new ReporterAgent(FLAGS_env, FLAGS_report_file,
+ FLAGS_report_interval_seconds));
+ }
+
+ ThreadArg* arg = new ThreadArg[n];
+
+ for (int i = 0; i < n; i++) {
+#ifdef NUMA
+ if (FLAGS_enable_numa) {
+ // Performs a local allocation of memory to threads in numa node.
+ int n_nodes = numa_num_task_nodes(); // Number of nodes in NUMA.
+ numa_exit_on_error = 1;
+ int numa_node = i % n_nodes;
+ bitmask* nodes = numa_allocate_nodemask();
+ numa_bitmask_clearall(nodes);
+ numa_bitmask_setbit(nodes, numa_node);
+ // numa_bind() call binds the process to the node and these
+ // properties are passed on to the thread that is created in
+ // StartThread method called later in the loop.
+ numa_bind(nodes);
+ numa_set_strict(1);
+ numa_free_nodemask(nodes);
+ }
+#endif
+ arg[i].bm = this;
+ arg[i].method = method;
+ arg[i].shared = &shared;
+ arg[i].thread = new ThreadState(i);
+ arg[i].thread->stats.SetReporterAgent(reporter_agent.get());
+ arg[i].thread->shared = &shared;
+ FLAGS_env->StartThread(ThreadBody, &arg[i]);
+ }
+
+ shared.mu.Lock();
+ while (shared.num_initialized < n) {
+ shared.cv.Wait();
+ }
+
+ shared.start = true;
+ shared.cv.SignalAll();
+ while (shared.num_done < n) {
+ shared.cv.Wait();
+ }
+ shared.mu.Unlock();
+
+ // Stats for some threads can be excluded.
+ Stats merge_stats;
+ for (int i = 0; i < n; i++) {
+ merge_stats.Merge(arg[i].thread->stats);
+ }
+ merge_stats.Report(name);
+
+ for (int i = 0; i < n; i++) {
+ delete arg[i].thread;
+ }
+ delete[] arg;
+
+ return merge_stats;
+ }
+
+ void Crc32c(ThreadState* thread) {
+ // Checksum about 500MB of data total
+ const int size = FLAGS_block_size; // use --block_size option for db_bench
+ std::string labels = "(" + ToString(FLAGS_block_size) + " per op)";
+ const char* label = labels.c_str();
+
+ std::string data(size, 'x');
+ int64_t bytes = 0;
+ uint32_t crc = 0;
+ while (bytes < 500 * 1048576) {
+ crc = crc32c::Value(data.data(), size);
+ thread->stats.FinishedOps(nullptr, nullptr, 1, kCrc);
+ bytes += size;
+ }
+ // Print so result is not dead
+ fprintf(stderr, "... crc=0x%x\r", static_cast<unsigned int>(crc));
+
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(label);
+ }
+
+ void xxHash(ThreadState* thread) {
+ // Checksum about 500MB of data total
+ const int size = 4096;
+ const char* label = "(4K per op)";
+ std::string data(size, 'x');
+ int64_t bytes = 0;
+ unsigned int xxh32 = 0;
+ while (bytes < 500 * 1048576) {
+ xxh32 = XXH32(data.data(), size, 0);
+ thread->stats.FinishedOps(nullptr, nullptr, 1, kHash);
+ bytes += size;
+ }
+ // Print so result is not dead
+ fprintf(stderr, "... xxh32=0x%x\r", static_cast<unsigned int>(xxh32));
+
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(label);
+ }
+
+ void AcquireLoad(ThreadState* thread) {
+ int dummy;
+ std::atomic<void*> ap(&dummy);
+ int count = 0;
+ void *ptr = nullptr;
+ thread->stats.AddMessage("(each op is 1000 loads)");
+ while (count < 100000) {
+ for (int i = 0; i < 1000; i++) {
+ ptr = ap.load(std::memory_order_acquire);
+ }
+ count++;
+ thread->stats.FinishedOps(nullptr, nullptr, 1, kOthers);
+ }
+ if (ptr == nullptr) exit(1); // Disable unused variable warning.
+ }
+
+ void Compress(ThreadState *thread) {
+ RandomGenerator gen;
+ Slice input = gen.Generate(FLAGS_block_size);
+ int64_t bytes = 0;
+ int64_t produced = 0;
+ bool ok = true;
+ std::string compressed;
+ CompressionOptions opts;
+ CompressionContext context(FLAGS_compression_type_e);
+ CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
+ FLAGS_compression_type_e,
+ FLAGS_sample_for_compression);
+ // Compress 1G
+ while (ok && bytes < int64_t(1) << 30) {
+ compressed.clear();
+ ok = CompressSlice(info, input, &compressed);
+ produced += compressed.size();
+ bytes += input.size();
+ thread->stats.FinishedOps(nullptr, nullptr, 1, kCompress);
+ }
+
+ if (!ok) {
+ thread->stats.AddMessage("(compression failure)");
+ } else {
+ char buf[340];
+ snprintf(buf, sizeof(buf), "(output: %.1f%%)",
+ (produced * 100.0) / bytes);
+ thread->stats.AddMessage(buf);
+ thread->stats.AddBytes(bytes);
+ }
+ }
+
+ void Uncompress(ThreadState *thread) {
+ RandomGenerator gen;
+ Slice input = gen.Generate(FLAGS_block_size);
+ std::string compressed;
+
+ CompressionContext compression_ctx(FLAGS_compression_type_e);
+ CompressionOptions compression_opts;
+ CompressionInfo compression_info(
+ compression_opts, compression_ctx, CompressionDict::GetEmptyDict(),
+ FLAGS_compression_type_e, FLAGS_sample_for_compression);
+ UncompressionContext uncompression_ctx(FLAGS_compression_type_e);
+ UncompressionInfo uncompression_info(uncompression_ctx,
+ UncompressionDict::GetEmptyDict(),
+ FLAGS_compression_type_e);
+
+ bool ok = CompressSlice(compression_info, input, &compressed);
+ int64_t bytes = 0;
+ int decompress_size;
+ while (ok && bytes < 1024 * 1048576) {
+ CacheAllocationPtr uncompressed;
+ switch (FLAGS_compression_type_e) {
+ case ROCKSDB_NAMESPACE::kSnappyCompression: {
+ // get size and allocate here to make comparison fair
+ size_t ulength = 0;
+ if (!Snappy_GetUncompressedLength(compressed.data(),
+ compressed.size(), &ulength)) {
+ ok = false;
+ break;
+ }
+ uncompressed = AllocateBlock(ulength, nullptr);
+ ok = Snappy_Uncompress(compressed.data(), compressed.size(),
+ uncompressed.get());
+ break;
+ }
+ case ROCKSDB_NAMESPACE::kZlibCompression:
+ uncompressed =
+ Zlib_Uncompress(uncompression_info, compressed.data(),
+ compressed.size(), &decompress_size, 2);
+ ok = uncompressed.get() != nullptr;
+ break;
+ case ROCKSDB_NAMESPACE::kBZip2Compression:
+ uncompressed = BZip2_Uncompress(compressed.data(), compressed.size(),
+ &decompress_size, 2);
+ ok = uncompressed.get() != nullptr;
+ break;
+ case ROCKSDB_NAMESPACE::kLZ4Compression:
+ uncompressed = LZ4_Uncompress(uncompression_info, compressed.data(),
+ compressed.size(), &decompress_size, 2);
+ ok = uncompressed.get() != nullptr;
+ break;
+ case ROCKSDB_NAMESPACE::kLZ4HCCompression:
+ uncompressed = LZ4_Uncompress(uncompression_info, compressed.data(),
+ compressed.size(), &decompress_size, 2);
+ ok = uncompressed.get() != nullptr;
+ break;
+ case ROCKSDB_NAMESPACE::kXpressCompression:
+ uncompressed.reset(XPRESS_Uncompress(
+ compressed.data(), compressed.size(), &decompress_size));
+ ok = uncompressed.get() != nullptr;
+ break;
+ case ROCKSDB_NAMESPACE::kZSTD:
+ uncompressed = ZSTD_Uncompress(uncompression_info, compressed.data(),
+ compressed.size(), &decompress_size);
+ ok = uncompressed.get() != nullptr;
+ break;
+ default:
+ ok = false;
+ }
+ bytes += input.size();
+ thread->stats.FinishedOps(nullptr, nullptr, 1, kUncompress);
+ }
+
+ if (!ok) {
+ thread->stats.AddMessage("(compression failure)");
+ } else {
+ thread->stats.AddBytes(bytes);
+ }
+ }
+
+ // Returns true if the options is initialized from the specified
+ // options file.
+ bool InitializeOptionsFromFile(Options* opts) {
+#ifndef ROCKSDB_LITE
+ printf("Initializing RocksDB Options from the specified file\n");
+ DBOptions db_opts;
+ std::vector<ColumnFamilyDescriptor> cf_descs;
+ if (FLAGS_options_file != "") {
+ auto s = LoadOptionsFromFile(FLAGS_options_file, FLAGS_env, &db_opts,
+ &cf_descs);
+ db_opts.env = FLAGS_env;
+ if (s.ok()) {
+ *opts = Options(db_opts, cf_descs[0].options);
+ return true;
+ }
+ fprintf(stderr, "Unable to load options file %s --- %s\n",
+ FLAGS_options_file.c_str(), s.ToString().c_str());
+ exit(1);
+ }
+#else
+ (void)opts;
+#endif
+ return false;
+ }
+
+ void InitializeOptionsFromFlags(Options* opts) {
+ printf("Initializing RocksDB Options from command-line flags\n");
+ Options& options = *opts;
+
+ assert(db_.db == nullptr);
+
+ options.env = FLAGS_env;
+ options.max_open_files = FLAGS_open_files;
+ if (FLAGS_cost_write_buffer_to_cache || FLAGS_db_write_buffer_size != 0) {
+ options.write_buffer_manager.reset(
+ new WriteBufferManager(FLAGS_db_write_buffer_size, cache_));
+ }
+ 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.max_background_jobs = FLAGS_max_background_jobs;
+ options.max_background_compactions = FLAGS_max_background_compactions;
+ options.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
+ options.max_background_flushes = FLAGS_max_background_flushes;
+ options.compaction_style = FLAGS_compaction_style_e;
+ options.compaction_pri = FLAGS_compaction_pri_e;
+ 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;
+#ifndef ROCKSDB_LITE
+ options.ttl = FLAGS_fifo_compaction_ttl;
+ options.compaction_options_fifo = CompactionOptionsFIFO(
+ FLAGS_fifo_compaction_max_table_files_size_mb * 1024 * 1024,
+ FLAGS_fifo_compaction_allow_compaction);
+#endif // ROCKSDB_LITE
+ if (FLAGS_prefix_size != 0) {
+ options.prefix_extractor.reset(
+ NewFixedPrefixTransform(FLAGS_prefix_size));
+ }
+ if (FLAGS_use_uint64_comparator) {
+ options.comparator = test::Uint64Comparator();
+ if (FLAGS_key_size != 8) {
+ fprintf(stderr, "Using Uint64 comparator but key size is not 8.\n");
+ exit(1);
+ }
+ }
+ if (FLAGS_use_stderr_info_logger) {
+ options.info_log.reset(new StderrLogger());
+ }
+ options.memtable_huge_page_size = FLAGS_memtable_use_huge_page ? 2048 : 0;
+ options.memtable_prefix_bloom_size_ratio = FLAGS_memtable_bloom_size_ratio;
+ options.memtable_whole_key_filtering = FLAGS_memtable_whole_key_filtering;
+ if (FLAGS_memtable_insert_with_hint_prefix_size > 0) {
+ options.memtable_insert_with_hint_prefix_extractor.reset(
+ NewCappedPrefixTransform(
+ FLAGS_memtable_insert_with_hint_prefix_size));
+ }
+ options.bloom_locality = FLAGS_bloom_locality;
+ options.max_file_opening_threads = FLAGS_file_opening_threads;
+ options.new_table_reader_for_compaction_inputs =
+ FLAGS_new_table_reader_for_compaction_inputs;
+ options.compaction_readahead_size = FLAGS_compaction_readahead_size;
+ options.log_readahead_size = FLAGS_log_readahead_size;
+ options.random_access_max_buffer_size = FLAGS_random_access_max_buffer_size;
+ options.writable_file_max_buffer_size = FLAGS_writable_file_max_buffer_size;
+ options.use_fsync = FLAGS_use_fsync;
+ options.num_levels = FLAGS_num_levels;
+ 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.level_compaction_dynamic_level_bytes =
+ FLAGS_level_compaction_dynamic_level_bytes;
+ options.max_bytes_for_level_multiplier =
+ FLAGS_max_bytes_for_level_multiplier;
+ if ((FLAGS_prefix_size == 0) && (FLAGS_rep_factory == kPrefixHash ||
+ FLAGS_rep_factory == kHashLinkedList)) {
+ fprintf(stderr, "prefix_size should be non-zero if PrefixHash or "
+ "HashLinkedList memtablerep is used\n");
+ exit(1);
+ }
+ switch (FLAGS_rep_factory) {
+ case kSkipList:
+ options.memtable_factory.reset(new SkipListFactory(
+ FLAGS_skip_list_lookahead));
+ break;
+#ifndef ROCKSDB_LITE
+ case kPrefixHash:
+ options.memtable_factory.reset(
+ NewHashSkipListRepFactory(FLAGS_hash_bucket_count));
+ break;
+ case kHashLinkedList:
+ options.memtable_factory.reset(NewHashLinkListRepFactory(
+ FLAGS_hash_bucket_count));
+ break;
+ case kVectorRep:
+ options.memtable_factory.reset(
+ new VectorRepFactory
+ );
+ break;
+#else
+ default:
+ fprintf(stderr, "Only skip list is supported in lite mode\n");
+ exit(1);
+#endif // ROCKSDB_LITE
+ }
+ if (FLAGS_use_plain_table) {
+#ifndef ROCKSDB_LITE
+ if (FLAGS_rep_factory != kPrefixHash &&
+ FLAGS_rep_factory != kHashLinkedList) {
+ fprintf(stderr, "Waring: plain table is used with skipList\n");
+ }
+
+ int bloom_bits_per_key = FLAGS_bloom_bits;
+ if (bloom_bits_per_key < 0) {
+ bloom_bits_per_key = 0;
+ }
+
+ PlainTableOptions plain_table_options;
+ plain_table_options.user_key_len = FLAGS_key_size;
+ plain_table_options.bloom_bits_per_key = bloom_bits_per_key;
+ plain_table_options.hash_table_ratio = 0.75;
+ options.table_factory = std::shared_ptr<TableFactory>(
+ NewPlainTableFactory(plain_table_options));
+#else
+ fprintf(stderr, "Plain table is not supported in lite mode\n");
+ exit(1);
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_use_cuckoo_table) {
+#ifndef ROCKSDB_LITE
+ if (FLAGS_cuckoo_hash_ratio > 1 || FLAGS_cuckoo_hash_ratio < 0) {
+ fprintf(stderr, "Invalid cuckoo_hash_ratio\n");
+ exit(1);
+ }
+
+ if (!FLAGS_mmap_read) {
+ fprintf(stderr, "cuckoo table format requires mmap read to operate\n");
+ exit(1);
+ }
+
+ ROCKSDB_NAMESPACE::CuckooTableOptions table_options;
+ table_options.hash_table_ratio = FLAGS_cuckoo_hash_ratio;
+ table_options.identity_as_first_hash = FLAGS_identity_as_first_hash;
+ options.table_factory = std::shared_ptr<TableFactory>(
+ NewCuckooTableFactory(table_options));
+#else
+ fprintf(stderr, "Cuckoo table is not supported in lite mode\n");
+ exit(1);
+#endif // ROCKSDB_LITE
+ } else {
+ BlockBasedTableOptions block_based_options;
+ if (FLAGS_use_hash_search) {
+ if (FLAGS_prefix_size == 0) {
+ fprintf(stderr,
+ "prefix_size not assigned when enable use_hash_search \n");
+ exit(1);
+ }
+ block_based_options.index_type = BlockBasedTableOptions::kHashSearch;
+ } else {
+ block_based_options.index_type = BlockBasedTableOptions::kBinarySearch;
+ }
+ if (FLAGS_partition_index_and_filters || FLAGS_partition_index) {
+ if (FLAGS_use_hash_search) {
+ fprintf(stderr,
+ "use_hash_search is incompatible with "
+ "partition index and is ignored");
+ }
+ block_based_options.index_type =
+ BlockBasedTableOptions::kTwoLevelIndexSearch;
+ block_based_options.metadata_block_size = FLAGS_metadata_block_size;
+ if (FLAGS_partition_index_and_filters) {
+ block_based_options.partition_filters = true;
+ }
+ }
+ if (cache_ == nullptr) {
+ block_based_options.no_block_cache = true;
+ }
+ block_based_options.cache_index_and_filter_blocks =
+ FLAGS_cache_index_and_filter_blocks;
+ block_based_options.pin_l0_filter_and_index_blocks_in_cache =
+ FLAGS_pin_l0_filter_and_index_blocks_in_cache;
+ block_based_options.pin_top_level_index_and_filter =
+ FLAGS_pin_top_level_index_and_filter;
+ if (FLAGS_cache_high_pri_pool_ratio > 1e-6) { // > 0.0 + eps
+ block_based_options.cache_index_and_filter_blocks_with_high_priority =
+ true;
+ }
+ block_based_options.block_cache = cache_;
+ block_based_options.block_cache_compressed = compressed_cache_;
+ block_based_options.block_size = FLAGS_block_size;
+ block_based_options.block_restart_interval = FLAGS_block_restart_interval;
+ block_based_options.index_block_restart_interval =
+ FLAGS_index_block_restart_interval;
+ block_based_options.filter_policy = filter_policy_;
+ block_based_options.format_version =
+ static_cast<uint32_t>(FLAGS_format_version);
+ block_based_options.read_amp_bytes_per_bit = FLAGS_read_amp_bytes_per_bit;
+ block_based_options.enable_index_compression =
+ FLAGS_enable_index_compression;
+ block_based_options.block_align = FLAGS_block_align;
+ if (FLAGS_use_data_block_hash_index) {
+ block_based_options.data_block_index_type =
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions::kDataBlockBinaryAndHash;
+ } else {
+ block_based_options.data_block_index_type =
+ ROCKSDB_NAMESPACE::BlockBasedTableOptions::kDataBlockBinarySearch;
+ }
+ block_based_options.data_block_hash_table_util_ratio =
+ FLAGS_data_block_hash_table_util_ratio;
+ if (FLAGS_read_cache_path != "") {
+#ifndef ROCKSDB_LITE
+ Status rc_status;
+
+ // Read cache need to be provided with a the Logger, we will put all
+ // reac cache logs in the read cache path in a file named rc_LOG
+ rc_status = FLAGS_env->CreateDirIfMissing(FLAGS_read_cache_path);
+ std::shared_ptr<Logger> read_cache_logger;
+ if (rc_status.ok()) {
+ rc_status = FLAGS_env->NewLogger(FLAGS_read_cache_path + "/rc_LOG",
+ &read_cache_logger);
+ }
+
+ if (rc_status.ok()) {
+ PersistentCacheConfig rc_cfg(FLAGS_env, FLAGS_read_cache_path,
+ FLAGS_read_cache_size,
+ read_cache_logger);
+
+ rc_cfg.enable_direct_reads = FLAGS_read_cache_direct_read;
+ rc_cfg.enable_direct_writes = FLAGS_read_cache_direct_write;
+ rc_cfg.writer_qdepth = 4;
+ rc_cfg.writer_dispatch_size = 4 * 1024;
+
+ auto pcache = std::make_shared<BlockCacheTier>(rc_cfg);
+ block_based_options.persistent_cache = pcache;
+ rc_status = pcache->Open();
+ }
+
+ if (!rc_status.ok()) {
+ fprintf(stderr, "Error initializing read cache, %s\n",
+ rc_status.ToString().c_str());
+ exit(1);
+ }
+#else
+ fprintf(stderr, "Read cache is not supported in LITE\n");
+ exit(1);
+
+#endif
+ }
+ options.table_factory.reset(
+ NewBlockBasedTableFactory(block_based_options));
+ }
+ if (FLAGS_max_bytes_for_level_multiplier_additional_v.size() > 0) {
+ if (FLAGS_max_bytes_for_level_multiplier_additional_v.size() !=
+ static_cast<unsigned int>(FLAGS_num_levels)) {
+ fprintf(stderr, "Insufficient number of fanouts specified %d\n",
+ static_cast<int>(
+ FLAGS_max_bytes_for_level_multiplier_additional_v.size()));
+ exit(1);
+ }
+ options.max_bytes_for_level_multiplier_additional =
+ FLAGS_max_bytes_for_level_multiplier_additional_v;
+ }
+ options.level0_stop_writes_trigger = FLAGS_level0_stop_writes_trigger;
+ options.level0_file_num_compaction_trigger =
+ FLAGS_level0_file_num_compaction_trigger;
+ options.level0_slowdown_writes_trigger =
+ FLAGS_level0_slowdown_writes_trigger;
+ options.compression = FLAGS_compression_type_e;
+ options.sample_for_compression = FLAGS_sample_for_compression;
+ options.WAL_ttl_seconds = FLAGS_wal_ttl_seconds;
+ options.WAL_size_limit_MB = FLAGS_wal_size_limit_MB;
+ options.max_total_wal_size = FLAGS_max_total_wal_size;
+
+ if (FLAGS_min_level_to_compress >= 0) {
+ assert(FLAGS_min_level_to_compress <= FLAGS_num_levels);
+ options.compression_per_level.resize(FLAGS_num_levels);
+ for (int i = 0; i < FLAGS_min_level_to_compress; i++) {
+ options.compression_per_level[i] = kNoCompression;
+ }
+ for (int i = FLAGS_min_level_to_compress;
+ i < FLAGS_num_levels; i++) {
+ options.compression_per_level[i] = FLAGS_compression_type_e;
+ }
+ }
+ options.soft_rate_limit = FLAGS_soft_rate_limit;
+ options.hard_rate_limit = FLAGS_hard_rate_limit;
+ options.soft_pending_compaction_bytes_limit =
+ FLAGS_soft_pending_compaction_bytes_limit;
+ options.hard_pending_compaction_bytes_limit =
+ FLAGS_hard_pending_compaction_bytes_limit;
+ options.delayed_write_rate = FLAGS_delayed_write_rate;
+ options.allow_concurrent_memtable_write =
+ FLAGS_allow_concurrent_memtable_write;
+ options.inplace_update_support = FLAGS_inplace_update_support;
+ options.inplace_update_num_locks = FLAGS_inplace_update_num_locks;
+ options.enable_write_thread_adaptive_yield =
+ FLAGS_enable_write_thread_adaptive_yield;
+ options.enable_pipelined_write = FLAGS_enable_pipelined_write;
+ options.unordered_write = FLAGS_unordered_write;
+ options.write_thread_max_yield_usec = FLAGS_write_thread_max_yield_usec;
+ options.write_thread_slow_yield_usec = FLAGS_write_thread_slow_yield_usec;
+ options.rate_limit_delay_max_milliseconds =
+ FLAGS_rate_limit_delay_max_milliseconds;
+ options.table_cache_numshardbits = FLAGS_table_cache_numshardbits;
+ options.max_compaction_bytes = FLAGS_max_compaction_bytes;
+ options.disable_auto_compactions = FLAGS_disable_auto_compactions;
+ options.optimize_filters_for_hits = FLAGS_optimize_filters_for_hits;
+
+ // fill storage options
+ options.advise_random_on_open = FLAGS_advise_random_on_open;
+ options.access_hint_on_compaction_start = FLAGS_compaction_fadvice_e;
+ options.use_adaptive_mutex = FLAGS_use_adaptive_mutex;
+ options.bytes_per_sync = FLAGS_bytes_per_sync;
+ options.wal_bytes_per_sync = FLAGS_wal_bytes_per_sync;
+
+ // merge operator options
+ options.merge_operator = MergeOperators::CreateFromStringId(
+ FLAGS_merge_operator);
+ if (options.merge_operator == nullptr && !FLAGS_merge_operator.empty()) {
+ fprintf(stderr, "invalid merge operator: %s\n",
+ FLAGS_merge_operator.c_str());
+ exit(1);
+ }
+ options.max_successive_merges = FLAGS_max_successive_merges;
+ options.report_bg_io_stats = FLAGS_report_bg_io_stats;
+
+ // set universal style compaction configurations, if applicable
+ if (FLAGS_universal_size_ratio != 0) {
+ options.compaction_options_universal.size_ratio =
+ FLAGS_universal_size_ratio;
+ }
+ if (FLAGS_universal_min_merge_width != 0) {
+ options.compaction_options_universal.min_merge_width =
+ FLAGS_universal_min_merge_width;
+ }
+ if (FLAGS_universal_max_merge_width != 0) {
+ options.compaction_options_universal.max_merge_width =
+ FLAGS_universal_max_merge_width;
+ }
+ if (FLAGS_universal_max_size_amplification_percent != 0) {
+ options.compaction_options_universal.max_size_amplification_percent =
+ FLAGS_universal_max_size_amplification_percent;
+ }
+ if (FLAGS_universal_compression_size_percent != -1) {
+ options.compaction_options_universal.compression_size_percent =
+ FLAGS_universal_compression_size_percent;
+ }
+ options.compaction_options_universal.allow_trivial_move =
+ FLAGS_universal_allow_trivial_move;
+ if (FLAGS_thread_status_per_interval > 0) {
+ options.enable_thread_tracking = true;
+ }
+
+#ifndef ROCKSDB_LITE
+ if (FLAGS_readonly && FLAGS_transaction_db) {
+ fprintf(stderr, "Cannot use readonly flag with transaction_db\n");
+ exit(1);
+ }
+ if (FLAGS_use_secondary_db &&
+ (FLAGS_transaction_db || FLAGS_optimistic_transaction_db)) {
+ fprintf(stderr, "Cannot use use_secondary_db flag with transaction_db\n");
+ exit(1);
+ }
+#endif // ROCKSDB_LITE
+
+ }
+
+ void InitializeOptionsGeneral(Options* opts) {
+ Options& options = *opts;
+
+ options.create_missing_column_families = FLAGS_num_column_families > 1;
+ options.statistics = dbstats;
+ options.wal_dir = FLAGS_wal_dir;
+ options.create_if_missing = !FLAGS_use_existing_db;
+ options.dump_malloc_stats = FLAGS_dump_malloc_stats;
+ options.stats_dump_period_sec =
+ static_cast<unsigned int>(FLAGS_stats_dump_period_sec);
+ options.stats_persist_period_sec =
+ static_cast<unsigned int>(FLAGS_stats_persist_period_sec);
+ options.persist_stats_to_disk = FLAGS_persist_stats_to_disk;
+ options.stats_history_buffer_size =
+ static_cast<size_t>(FLAGS_stats_history_buffer_size);
+
+ options.compression_opts.level = FLAGS_compression_level;
+ options.compression_opts.max_dict_bytes = FLAGS_compression_max_dict_bytes;
+ options.compression_opts.zstd_max_train_bytes =
+ FLAGS_compression_zstd_max_train_bytes;
+ // If this is a block based table, set some related options
+ if (options.table_factory->Name() == BlockBasedTableFactory::kName &&
+ options.table_factory->GetOptions() != nullptr) {
+ BlockBasedTableOptions* table_options =
+ reinterpret_cast<BlockBasedTableOptions*>(
+ options.table_factory->GetOptions());
+ if (FLAGS_cache_size) {
+ table_options->block_cache = cache_;
+ }
+ if (FLAGS_bloom_bits >= 0) {
+ table_options->filter_policy.reset(NewBloomFilterPolicy(
+ FLAGS_bloom_bits, FLAGS_use_block_based_filter));
+ }
+ }
+ if (FLAGS_row_cache_size) {
+ if (FLAGS_cache_numshardbits >= 1) {
+ options.row_cache =
+ NewLRUCache(FLAGS_row_cache_size, FLAGS_cache_numshardbits);
+ } else {
+ options.row_cache = NewLRUCache(FLAGS_row_cache_size);
+ }
+ }
+ if (FLAGS_enable_io_prio) {
+ FLAGS_env->LowerThreadPoolIOPriority(Env::LOW);
+ FLAGS_env->LowerThreadPoolIOPriority(Env::HIGH);
+ }
+ if (FLAGS_enable_cpu_prio) {
+ FLAGS_env->LowerThreadPoolCPUPriority(Env::LOW);
+ FLAGS_env->LowerThreadPoolCPUPriority(Env::HIGH);
+ }
+ options.env = FLAGS_env;
+ if (FLAGS_sine_write_rate) {
+ FLAGS_benchmark_write_rate_limit = static_cast<uint64_t>(SineRate(0));
+ }
+
+ if (FLAGS_rate_limiter_bytes_per_sec > 0) {
+ if (FLAGS_rate_limit_bg_reads &&
+ !FLAGS_new_table_reader_for_compaction_inputs) {
+ fprintf(stderr,
+ "rate limit compaction reads must have "
+ "new_table_reader_for_compaction_inputs set\n");
+ exit(1);
+ }
+ options.rate_limiter.reset(NewGenericRateLimiter(
+ FLAGS_rate_limiter_bytes_per_sec, 100 * 1000 /* refill_period_us */,
+ 10 /* fairness */,
+ FLAGS_rate_limit_bg_reads ? RateLimiter::Mode::kReadsOnly
+ : RateLimiter::Mode::kWritesOnly,
+ FLAGS_rate_limiter_auto_tuned));
+ }
+
+ options.listeners.emplace_back(listener_);
+ if (FLAGS_num_multi_db <= 1) {
+ OpenDb(options, FLAGS_db, &db_);
+ } else {
+ multi_dbs_.clear();
+ multi_dbs_.resize(FLAGS_num_multi_db);
+ auto wal_dir = options.wal_dir;
+ for (int i = 0; i < FLAGS_num_multi_db; i++) {
+ if (!wal_dir.empty()) {
+ options.wal_dir = GetPathForMultiple(wal_dir, i);
+ }
+ OpenDb(options, GetPathForMultiple(FLAGS_db, i), &multi_dbs_[i]);
+ }
+ options.wal_dir = wal_dir;
+ }
+
+ // KeepFilter is a noop filter, this can be used to test compaction filter
+ if (FLAGS_use_keep_filter) {
+ options.compaction_filter = new KeepFilter();
+ fprintf(stdout, "A noop compaction filter is used\n");
+ }
+
+ if (FLAGS_use_existing_keys) {
+ // Only work on single database
+ assert(db_.db != nullptr);
+ ReadOptions read_opts;
+ read_opts.total_order_seek = true;
+ Iterator* iter = db_.db->NewIterator(read_opts);
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ keys_.emplace_back(iter->key().ToString());
+ }
+ delete iter;
+ FLAGS_num = keys_.size();
+ }
+ }
+
+ void Open(Options* opts) {
+ if (!InitializeOptionsFromFile(opts)) {
+ InitializeOptionsFromFlags(opts);
+ }
+
+ InitializeOptionsGeneral(opts);
+ }
+
+ void OpenDb(Options options, const std::string& db_name,
+ DBWithColumnFamilies* db) {
+ Status s;
+ // Open with column families if necessary.
+ if (FLAGS_num_column_families > 1) {
+ size_t num_hot = FLAGS_num_column_families;
+ if (FLAGS_num_hot_column_families > 0 &&
+ FLAGS_num_hot_column_families < FLAGS_num_column_families) {
+ num_hot = FLAGS_num_hot_column_families;
+ } else {
+ FLAGS_num_hot_column_families = FLAGS_num_column_families;
+ }
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < num_hot; i++) {
+ column_families.push_back(ColumnFamilyDescriptor(
+ ColumnFamilyName(i), ColumnFamilyOptions(options)));
+ }
+ std::vector<int> cfh_idx_to_prob;
+ if (!FLAGS_column_family_distribution.empty()) {
+ std::stringstream cf_prob_stream(FLAGS_column_family_distribution);
+ std::string cf_prob;
+ int sum = 0;
+ while (std::getline(cf_prob_stream, cf_prob, ',')) {
+ cfh_idx_to_prob.push_back(std::stoi(cf_prob));
+ sum += cfh_idx_to_prob.back();
+ }
+ if (sum != 100) {
+ fprintf(stderr, "column_family_distribution items must sum to 100\n");
+ exit(1);
+ }
+ if (cfh_idx_to_prob.size() != num_hot) {
+ fprintf(stderr,
+ "got %" ROCKSDB_PRIszt
+ " column_family_distribution items; expected "
+ "%" ROCKSDB_PRIszt "\n",
+ cfh_idx_to_prob.size(), num_hot);
+ exit(1);
+ }
+ }
+#ifndef ROCKSDB_LITE
+ if (FLAGS_readonly) {
+ s = DB::OpenForReadOnly(options, db_name, column_families,
+ &db->cfh, &db->db);
+ } else if (FLAGS_optimistic_transaction_db) {
+ s = OptimisticTransactionDB::Open(options, db_name, column_families,
+ &db->cfh, &db->opt_txn_db);
+ if (s.ok()) {
+ db->db = db->opt_txn_db->GetBaseDB();
+ }
+ } else if (FLAGS_transaction_db) {
+ TransactionDB* ptr;
+ TransactionDBOptions txn_db_options;
+ if (options.unordered_write) {
+ options.two_write_queues = true;
+ txn_db_options.skip_concurrency_control = true;
+ txn_db_options.write_policy = WRITE_PREPARED;
+ }
+ s = TransactionDB::Open(options, txn_db_options, db_name,
+ column_families, &db->cfh, &ptr);
+ if (s.ok()) {
+ db->db = ptr;
+ }
+ } else {
+ s = DB::Open(options, db_name, column_families, &db->cfh, &db->db);
+ }
+#else
+ s = DB::Open(options, db_name, column_families, &db->cfh, &db->db);
+#endif // ROCKSDB_LITE
+ db->cfh.resize(FLAGS_num_column_families);
+ db->num_created = num_hot;
+ db->num_hot = num_hot;
+ db->cfh_idx_to_prob = std::move(cfh_idx_to_prob);
+#ifndef ROCKSDB_LITE
+ } else if (FLAGS_readonly) {
+ s = DB::OpenForReadOnly(options, db_name, &db->db);
+ } else if (FLAGS_optimistic_transaction_db) {
+ s = OptimisticTransactionDB::Open(options, db_name, &db->opt_txn_db);
+ if (s.ok()) {
+ db->db = db->opt_txn_db->GetBaseDB();
+ }
+ } else if (FLAGS_transaction_db) {
+ TransactionDB* ptr = nullptr;
+ TransactionDBOptions txn_db_options;
+ if (options.unordered_write) {
+ options.two_write_queues = true;
+ txn_db_options.skip_concurrency_control = true;
+ txn_db_options.write_policy = WRITE_PREPARED;
+ }
+ s = CreateLoggerFromOptions(db_name, options, &options.info_log);
+ if (s.ok()) {
+ s = TransactionDB::Open(options, txn_db_options, db_name, &ptr);
+ }
+ if (s.ok()) {
+ db->db = ptr;
+ }
+ } else if (FLAGS_use_blob_db) {
+ blob_db::BlobDBOptions blob_db_options;
+ blob_db_options.enable_garbage_collection = FLAGS_blob_db_enable_gc;
+ blob_db_options.garbage_collection_cutoff = FLAGS_blob_db_gc_cutoff;
+ blob_db_options.is_fifo = FLAGS_blob_db_is_fifo;
+ blob_db_options.max_db_size = FLAGS_blob_db_max_db_size;
+ blob_db_options.ttl_range_secs = FLAGS_blob_db_ttl_range_secs;
+ 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.compression = FLAGS_blob_db_compression_type_e;
+ blob_db::BlobDB* ptr = nullptr;
+ s = blob_db::BlobDB::Open(options, blob_db_options, db_name, &ptr);
+ if (s.ok()) {
+ db->db = ptr;
+ }
+ } else if (FLAGS_use_secondary_db) {
+ if (FLAGS_secondary_path.empty()) {
+ std::string default_secondary_path;
+ FLAGS_env->GetTestDirectory(&default_secondary_path);
+ default_secondary_path += "/dbbench_secondary";
+ FLAGS_secondary_path = default_secondary_path;
+ }
+ s = DB::OpenAsSecondary(options, db_name, FLAGS_secondary_path, &db->db);
+ if (s.ok() && FLAGS_secondary_update_interval > 0) {
+ secondary_update_thread_.reset(new port::Thread(
+ [this](int interval, DBWithColumnFamilies* _db) {
+ while (0 == secondary_update_stopped_.load(
+ std::memory_order_relaxed)) {
+ Status secondary_update_status =
+ _db->db->TryCatchUpWithPrimary();
+ if (!secondary_update_status.ok()) {
+ fprintf(stderr, "Failed to catch up with primary: %s\n",
+ secondary_update_status.ToString().c_str());
+ break;
+ }
+ ++secondary_db_updates_;
+ FLAGS_env->SleepForMicroseconds(interval * 1000000);
+ }
+ },
+ FLAGS_secondary_update_interval, db));
+ }
+#endif // ROCKSDB_LITE
+ } else {
+ s = DB::Open(options, db_name, &db->db);
+ }
+ if (!s.ok()) {
+ fprintf(stderr, "open error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ }
+
+ enum WriteMode {
+ RANDOM, SEQUENTIAL, UNIQUE_RANDOM
+ };
+
+ void WriteSeqDeterministic(ThreadState* thread) {
+ DoDeterministicCompact(thread, open_options_.compaction_style, SEQUENTIAL);
+ }
+
+ void WriteUniqueRandomDeterministic(ThreadState* thread) {
+ DoDeterministicCompact(thread, open_options_.compaction_style,
+ UNIQUE_RANDOM);
+ }
+
+ void WriteSeq(ThreadState* thread) {
+ DoWrite(thread, SEQUENTIAL);
+ }
+
+ void WriteRandom(ThreadState* thread) {
+ DoWrite(thread, RANDOM);
+ }
+
+ void WriteUniqueRandom(ThreadState* thread) {
+ DoWrite(thread, UNIQUE_RANDOM);
+ }
+
+ class KeyGenerator {
+ public:
+ KeyGenerator(Random64* rand, WriteMode mode, uint64_t num,
+ uint64_t /*num_per_set*/ = 64 * 1024)
+ : rand_(rand), mode_(mode), num_(num), next_(0) {
+ if (mode_ == UNIQUE_RANDOM) {
+ // NOTE: if memory consumption of this approach becomes a concern,
+ // we can either break it into pieces and only random shuffle a section
+ // each time. Alternatively, use a bit map implementation
+ // (https://reviews.facebook.net/differential/diff/54627/)
+ values_.resize(num_);
+ for (uint64_t i = 0; i < num_; ++i) {
+ values_[i] = i;
+ }
+ std::shuffle(
+ values_.begin(), values_.end(),
+ std::default_random_engine(static_cast<unsigned int>(FLAGS_seed)));
+ }
+ }
+
+ uint64_t Next() {
+ switch (mode_) {
+ case SEQUENTIAL:
+ return next_++;
+ case RANDOM:
+ return rand_->Next() % num_;
+ case UNIQUE_RANDOM:
+ assert(next_ < num_);
+ return values_[next_++];
+ }
+ assert(false);
+ return std::numeric_limits<uint64_t>::max();
+ }
+
+ private:
+ Random64* rand_;
+ WriteMode mode_;
+ const uint64_t num_;
+ uint64_t next_;
+ std::vector<uint64_t> values_;
+ };
+
+ DB* SelectDB(ThreadState* thread) {
+ return SelectDBWithCfh(thread)->db;
+ }
+
+ DBWithColumnFamilies* SelectDBWithCfh(ThreadState* thread) {
+ return SelectDBWithCfh(thread->rand.Next());
+ }
+
+ DBWithColumnFamilies* SelectDBWithCfh(uint64_t rand_int) {
+ if (db_.db != nullptr) {
+ return &db_;
+ } else {
+ return &multi_dbs_[rand_int % multi_dbs_.size()];
+ }
+ }
+
+ double SineRate(double x) {
+ return FLAGS_sine_a*sin((FLAGS_sine_b*x) + FLAGS_sine_c) + FLAGS_sine_d;
+ }
+
+ void DoWrite(ThreadState* thread, WriteMode write_mode) {
+ const int test_duration = write_mode == RANDOM ? FLAGS_duration : 0;
+ const int64_t num_ops = writes_ == 0 ? num_ : writes_;
+
+ size_t num_key_gens = 1;
+ if (db_.db == nullptr) {
+ num_key_gens = multi_dbs_.size();
+ }
+ std::vector<std::unique_ptr<KeyGenerator>> key_gens(num_key_gens);
+ int64_t max_ops = num_ops * num_key_gens;
+ int64_t ops_per_stage = max_ops;
+ if (FLAGS_num_column_families > 1 && FLAGS_num_hot_column_families > 0) {
+ ops_per_stage = (max_ops - 1) / (FLAGS_num_column_families /
+ FLAGS_num_hot_column_families) +
+ 1;
+ }
+
+ Duration duration(test_duration, max_ops, ops_per_stage);
+ for (size_t i = 0; i < num_key_gens; i++) {
+ key_gens[i].reset(new KeyGenerator(&(thread->rand), write_mode,
+ num_ + max_num_range_tombstones_,
+ ops_per_stage));
+ }
+
+ if (num_ != FLAGS_num) {
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " ops)", num_);
+ thread->stats.AddMessage(msg);
+ }
+
+ RandomGenerator gen;
+ WriteBatch batch;
+ Status s;
+ int64_t bytes = 0;
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ std::unique_ptr<const char[]> begin_key_guard;
+ Slice begin_key = AllocateKey(&begin_key_guard);
+ std::unique_ptr<const char[]> end_key_guard;
+ Slice end_key = AllocateKey(&end_key_guard);
+ std::vector<std::unique_ptr<const char[]>> expanded_key_guards;
+ std::vector<Slice> expanded_keys;
+ if (FLAGS_expand_range_tombstones) {
+ expanded_key_guards.resize(range_tombstone_width_);
+ for (auto& expanded_key_guard : expanded_key_guards) {
+ expanded_keys.emplace_back(AllocateKey(&expanded_key_guard));
+ }
+ }
+
+ int64_t stage = 0;
+ int64_t num_written = 0;
+ while (!duration.Done(entries_per_batch_)) {
+ if (duration.GetStage() != stage) {
+ stage = duration.GetStage();
+ if (db_.db != nullptr) {
+ db_.CreateNewCf(open_options_, stage);
+ } else {
+ for (auto& db : multi_dbs_) {
+ db.CreateNewCf(open_options_, stage);
+ }
+ }
+ }
+
+ size_t id = thread->rand.Next() % num_key_gens;
+ DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(id);
+ batch.Clear();
+ int64_t batch_bytes = 0;
+
+ for (int64_t j = 0; j < entries_per_batch_; j++) {
+ int64_t rand_num = key_gens[id]->Next();
+ GenerateKeyFromInt(rand_num, FLAGS_num, &key);
+ Slice val = gen.Generate();
+ if (use_blob_db_) {
+#ifndef ROCKSDB_LITE
+ blob_db::BlobDB* blobdb =
+ static_cast<blob_db::BlobDB*>(db_with_cfh->db);
+ if (FLAGS_blob_db_max_ttl_range > 0) {
+ int ttl = rand() % FLAGS_blob_db_max_ttl_range;
+ s = blobdb->PutWithTTL(write_options_, key, val, ttl);
+ } else {
+ s = blobdb->Put(write_options_, key, val);
+ }
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_num_column_families <= 1) {
+ batch.Put(key, val);
+ } else {
+ // We use same rand_num as seed for key and column family so that we
+ // can deterministically find the cfh corresponding to a particular
+ // key while reading the key.
+ batch.Put(db_with_cfh->GetCfh(rand_num), key,
+ val);
+ }
+ batch_bytes += val.size() + key_size_;
+ bytes += val.size() + key_size_;
+ ++num_written;
+ if (writes_per_range_tombstone_ > 0 &&
+ num_written > writes_before_delete_range_ &&
+ (num_written - writes_before_delete_range_) /
+ writes_per_range_tombstone_ <=
+ max_num_range_tombstones_ &&
+ (num_written - writes_before_delete_range_) %
+ writes_per_range_tombstone_ ==
+ 0) {
+ int64_t begin_num = key_gens[id]->Next();
+ if (FLAGS_expand_range_tombstones) {
+ for (int64_t offset = 0; offset < range_tombstone_width_;
+ ++offset) {
+ GenerateKeyFromInt(begin_num + offset, FLAGS_num,
+ &expanded_keys[offset]);
+ if (use_blob_db_) {
+#ifndef ROCKSDB_LITE
+ s = db_with_cfh->db->Delete(write_options_,
+ expanded_keys[offset]);
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_num_column_families <= 1) {
+ batch.Delete(expanded_keys[offset]);
+ } else {
+ batch.Delete(db_with_cfh->GetCfh(rand_num),
+ expanded_keys[offset]);
+ }
+ }
+ } else {
+ GenerateKeyFromInt(begin_num, FLAGS_num, &begin_key);
+ GenerateKeyFromInt(begin_num + range_tombstone_width_, FLAGS_num,
+ &end_key);
+ if (use_blob_db_) {
+#ifndef ROCKSDB_LITE
+ s = db_with_cfh->db->DeleteRange(
+ write_options_, db_with_cfh->db->DefaultColumnFamily(),
+ begin_key, end_key);
+#endif // ROCKSDB_LITE
+ } else if (FLAGS_num_column_families <= 1) {
+ batch.DeleteRange(begin_key, end_key);
+ } else {
+ batch.DeleteRange(db_with_cfh->GetCfh(rand_num), begin_key,
+ end_key);
+ }
+ }
+ }
+ }
+ if (thread->shared->write_rate_limiter.get() != nullptr) {
+ thread->shared->write_rate_limiter->Request(
+ batch_bytes, Env::IO_HIGH,
+ nullptr /* stats */, RateLimiter::OpType::kWrite);
+ // Set time at which last op finished to Now() to hide latency and
+ // sleep from rate limiter. Also, do the check once per batch, not
+ // once per write.
+ thread->stats.ResetLastOpTime();
+ }
+ if (!use_blob_db_) {
+ s = db_with_cfh->db->Write(write_options_, &batch);
+ }
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db,
+ entries_per_batch_, kWrite);
+ if (FLAGS_sine_write_rate) {
+ uint64_t now = FLAGS_env->NowMicros();
+
+ uint64_t usecs_since_last;
+ if (now > thread->stats.GetSineInterval()) {
+ usecs_since_last = now - thread->stats.GetSineInterval();
+ } else {
+ usecs_since_last = 0;
+ }
+
+ if (usecs_since_last >
+ (FLAGS_sine_write_rate_interval_milliseconds * uint64_t{1000})) {
+ double usecs_since_start =
+ static_cast<double>(now - thread->stats.GetStart());
+ thread->stats.ResetSineInterval();
+ uint64_t write_rate =
+ static_cast<uint64_t>(SineRate(usecs_since_start / 1000000.0));
+ thread->shared->write_rate_limiter.reset(
+ NewGenericRateLimiter(write_rate));
+ }
+ }
+ if (!s.ok()) {
+ s = listener_->WaitForRecovery(600000000) ? Status::OK() : s;
+ }
+
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ }
+ thread->stats.AddBytes(bytes);
+ }
+
+ Status DoDeterministicCompact(ThreadState* thread,
+ CompactionStyle compaction_style,
+ WriteMode write_mode) {
+#ifndef ROCKSDB_LITE
+ ColumnFamilyMetaData meta;
+ std::vector<DB*> db_list;
+ if (db_.db != nullptr) {
+ db_list.push_back(db_.db);
+ } else {
+ for (auto& db : multi_dbs_) {
+ db_list.push_back(db.db);
+ }
+ }
+ std::vector<Options> options_list;
+ for (auto db : db_list) {
+ options_list.push_back(db->GetOptions());
+ if (compaction_style != kCompactionStyleFIFO) {
+ db->SetOptions({{"disable_auto_compactions", "1"},
+ {"level0_slowdown_writes_trigger", "400000000"},
+ {"level0_stop_writes_trigger", "400000000"}});
+ } else {
+ db->SetOptions({{"disable_auto_compactions", "1"}});
+ }
+ }
+
+ assert(!db_list.empty());
+ auto num_db = db_list.size();
+ size_t num_levels = static_cast<size_t>(open_options_.num_levels);
+ size_t output_level = open_options_.num_levels - 1;
+ std::vector<std::vector<std::vector<SstFileMetaData>>> sorted_runs(num_db);
+ std::vector<size_t> num_files_at_level0(num_db, 0);
+ if (compaction_style == kCompactionStyleLevel) {
+ if (num_levels == 0) {
+ return Status::InvalidArgument("num_levels should be larger than 1");
+ }
+ bool should_stop = false;
+ while (!should_stop) {
+ if (sorted_runs[0].empty()) {
+ DoWrite(thread, write_mode);
+ } else {
+ DoWrite(thread, UNIQUE_RANDOM);
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ auto db = db_list[i];
+ db->Flush(FlushOptions());
+ db->GetColumnFamilyMetaData(&meta);
+ if (num_files_at_level0[i] == meta.levels[0].files.size() ||
+ writes_ == 0) {
+ should_stop = true;
+ continue;
+ }
+ sorted_runs[i].emplace_back(
+ meta.levels[0].files.begin(),
+ meta.levels[0].files.end() - num_files_at_level0[i]);
+ num_files_at_level0[i] = meta.levels[0].files.size();
+ if (sorted_runs[i].back().size() == 1) {
+ should_stop = true;
+ continue;
+ }
+ if (sorted_runs[i].size() == output_level) {
+ auto& L1 = sorted_runs[i].back();
+ L1.erase(L1.begin(), L1.begin() + L1.size() / 3);
+ should_stop = true;
+ continue;
+ }
+ }
+ writes_ /= static_cast<int64_t>(open_options_.max_bytes_for_level_multiplier);
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ if (sorted_runs[i].size() < num_levels - 1) {
+ fprintf(stderr, "n is too small to fill %" ROCKSDB_PRIszt " levels\n", num_levels);
+ exit(1);
+ }
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ auto db = db_list[i];
+ auto compactionOptions = CompactionOptions();
+ compactionOptions.compression = FLAGS_compression_type_e;
+ auto options = db->GetOptions();
+ MutableCFOptions mutable_cf_options(options);
+ for (size_t j = 0; j < sorted_runs[i].size(); j++) {
+ compactionOptions.output_file_size_limit =
+ MaxFileSizeForLevel(mutable_cf_options,
+ static_cast<int>(output_level), compaction_style);
+ std::cout << sorted_runs[i][j].size() << std::endl;
+ db->CompactFiles(compactionOptions, {sorted_runs[i][j].back().name,
+ sorted_runs[i][j].front().name},
+ static_cast<int>(output_level - j) /*level*/);
+ }
+ }
+ } else if (compaction_style == kCompactionStyleUniversal) {
+ auto ratio = open_options_.compaction_options_universal.size_ratio;
+ bool should_stop = false;
+ while (!should_stop) {
+ if (sorted_runs[0].empty()) {
+ DoWrite(thread, write_mode);
+ } else {
+ DoWrite(thread, UNIQUE_RANDOM);
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ auto db = db_list[i];
+ db->Flush(FlushOptions());
+ db->GetColumnFamilyMetaData(&meta);
+ if (num_files_at_level0[i] == meta.levels[0].files.size() ||
+ writes_ == 0) {
+ should_stop = true;
+ continue;
+ }
+ sorted_runs[i].emplace_back(
+ meta.levels[0].files.begin(),
+ meta.levels[0].files.end() - num_files_at_level0[i]);
+ num_files_at_level0[i] = meta.levels[0].files.size();
+ if (sorted_runs[i].back().size() == 1) {
+ should_stop = true;
+ continue;
+ }
+ num_files_at_level0[i] = meta.levels[0].files.size();
+ }
+ writes_ = static_cast<int64_t>(writes_* static_cast<double>(100) / (ratio + 200));
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ if (sorted_runs[i].size() < num_levels) {
+ fprintf(stderr, "n is too small to fill %" ROCKSDB_PRIszt " levels\n", num_levels);
+ exit(1);
+ }
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ auto db = db_list[i];
+ auto compactionOptions = CompactionOptions();
+ compactionOptions.compression = FLAGS_compression_type_e;
+ auto options = db->GetOptions();
+ MutableCFOptions mutable_cf_options(options);
+ for (size_t j = 0; j < sorted_runs[i].size(); j++) {
+ compactionOptions.output_file_size_limit =
+ MaxFileSizeForLevel(mutable_cf_options,
+ static_cast<int>(output_level), compaction_style);
+ db->CompactFiles(
+ compactionOptions,
+ {sorted_runs[i][j].back().name, sorted_runs[i][j].front().name},
+ (output_level > j ? static_cast<int>(output_level - j)
+ : 0) /*level*/);
+ }
+ }
+ } else if (compaction_style == kCompactionStyleFIFO) {
+ if (num_levels != 1) {
+ return Status::InvalidArgument(
+ "num_levels should be 1 for FIFO compaction");
+ }
+ if (FLAGS_num_multi_db != 0) {
+ return Status::InvalidArgument("Doesn't support multiDB");
+ }
+ auto db = db_list[0];
+ std::vector<std::string> file_names;
+ while (true) {
+ if (sorted_runs[0].empty()) {
+ DoWrite(thread, write_mode);
+ } else {
+ DoWrite(thread, UNIQUE_RANDOM);
+ }
+ db->Flush(FlushOptions());
+ db->GetColumnFamilyMetaData(&meta);
+ auto total_size = meta.levels[0].size;
+ if (total_size >=
+ db->GetOptions().compaction_options_fifo.max_table_files_size) {
+ for (auto file_meta : meta.levels[0].files) {
+ file_names.emplace_back(file_meta.name);
+ }
+ break;
+ }
+ }
+ // TODO(shuzhang1989): Investigate why CompactFiles not working
+ // auto compactionOptions = CompactionOptions();
+ // db->CompactFiles(compactionOptions, file_names, 0);
+ auto compactionOptions = CompactRangeOptions();
+ db->CompactRange(compactionOptions, nullptr, nullptr);
+ } else {
+ fprintf(stdout,
+ "%-12s : skipped (-compaction_stype=kCompactionStyleNone)\n",
+ "filldeterministic");
+ return Status::InvalidArgument("None compaction is not supported");
+ }
+
+// Verify seqno and key range
+// Note: the seqno get changed at the max level by implementation
+// optimization, so skip the check of the max level.
+#ifndef NDEBUG
+ for (size_t k = 0; k < num_db; k++) {
+ auto db = db_list[k];
+ db->GetColumnFamilyMetaData(&meta);
+ // verify the number of sorted runs
+ if (compaction_style == kCompactionStyleLevel) {
+ assert(num_levels - 1 == sorted_runs[k].size());
+ } else if (compaction_style == kCompactionStyleUniversal) {
+ assert(meta.levels[0].files.size() + num_levels - 1 ==
+ sorted_runs[k].size());
+ } else if (compaction_style == kCompactionStyleFIFO) {
+ // TODO(gzh): FIFO compaction
+ db->GetColumnFamilyMetaData(&meta);
+ auto total_size = meta.levels[0].size;
+ assert(total_size <=
+ db->GetOptions().compaction_options_fifo.max_table_files_size);
+ break;
+ }
+
+ // verify smallest/largest seqno and key range of each sorted run
+ auto max_level = num_levels - 1;
+ int level;
+ for (size_t i = 0; i < sorted_runs[k].size(); i++) {
+ level = static_cast<int>(max_level - i);
+ SequenceNumber sorted_run_smallest_seqno = kMaxSequenceNumber;
+ SequenceNumber sorted_run_largest_seqno = 0;
+ std::string sorted_run_smallest_key, sorted_run_largest_key;
+ bool first_key = true;
+ for (auto fileMeta : sorted_runs[k][i]) {
+ sorted_run_smallest_seqno =
+ std::min(sorted_run_smallest_seqno, fileMeta.smallest_seqno);
+ sorted_run_largest_seqno =
+ std::max(sorted_run_largest_seqno, fileMeta.largest_seqno);
+ if (first_key ||
+ db->DefaultColumnFamily()->GetComparator()->Compare(
+ fileMeta.smallestkey, sorted_run_smallest_key) < 0) {
+ sorted_run_smallest_key = fileMeta.smallestkey;
+ }
+ if (first_key ||
+ db->DefaultColumnFamily()->GetComparator()->Compare(
+ fileMeta.largestkey, sorted_run_largest_key) > 0) {
+ sorted_run_largest_key = fileMeta.largestkey;
+ }
+ first_key = false;
+ }
+ if (compaction_style == kCompactionStyleLevel ||
+ (compaction_style == kCompactionStyleUniversal && level > 0)) {
+ SequenceNumber level_smallest_seqno = kMaxSequenceNumber;
+ SequenceNumber level_largest_seqno = 0;
+ for (auto fileMeta : meta.levels[level].files) {
+ level_smallest_seqno =
+ std::min(level_smallest_seqno, fileMeta.smallest_seqno);
+ level_largest_seqno =
+ std::max(level_largest_seqno, fileMeta.largest_seqno);
+ }
+ assert(sorted_run_smallest_key ==
+ meta.levels[level].files.front().smallestkey);
+ assert(sorted_run_largest_key ==
+ meta.levels[level].files.back().largestkey);
+ if (level != static_cast<int>(max_level)) {
+ // compaction at max_level would change sequence number
+ assert(sorted_run_smallest_seqno == level_smallest_seqno);
+ assert(sorted_run_largest_seqno == level_largest_seqno);
+ }
+ } else if (compaction_style == kCompactionStyleUniversal) {
+ // level <= 0 means sorted runs on level 0
+ auto level0_file =
+ meta.levels[0].files[sorted_runs[k].size() - 1 - i];
+ assert(sorted_run_smallest_key == level0_file.smallestkey);
+ assert(sorted_run_largest_key == level0_file.largestkey);
+ if (level != static_cast<int>(max_level)) {
+ assert(sorted_run_smallest_seqno == level0_file.smallest_seqno);
+ assert(sorted_run_largest_seqno == level0_file.largest_seqno);
+ }
+ }
+ }
+ }
+#endif
+ // print the size of each sorted_run
+ for (size_t k = 0; k < num_db; k++) {
+ auto db = db_list[k];
+ fprintf(stdout,
+ "---------------------- DB %" ROCKSDB_PRIszt " LSM ---------------------\n", k);
+ db->GetColumnFamilyMetaData(&meta);
+ for (auto& levelMeta : meta.levels) {
+ if (levelMeta.files.empty()) {
+ continue;
+ }
+ if (levelMeta.level == 0) {
+ for (auto& fileMeta : levelMeta.files) {
+ fprintf(stdout, "Level[%d]: %s(size: %" ROCKSDB_PRIszt " bytes)\n",
+ levelMeta.level, fileMeta.name.c_str(), fileMeta.size);
+ }
+ } else {
+ fprintf(stdout, "Level[%d]: %s - %s(total size: %" PRIi64 " bytes)\n",
+ levelMeta.level, levelMeta.files.front().name.c_str(),
+ levelMeta.files.back().name.c_str(), levelMeta.size);
+ }
+ }
+ }
+ for (size_t i = 0; i < num_db; i++) {
+ db_list[i]->SetOptions(
+ {{"disable_auto_compactions",
+ std::to_string(options_list[i].disable_auto_compactions)},
+ {"level0_slowdown_writes_trigger",
+ std::to_string(options_list[i].level0_slowdown_writes_trigger)},
+ {"level0_stop_writes_trigger",
+ std::to_string(options_list[i].level0_stop_writes_trigger)}});
+ }
+ return Status::OK();
+#else
+ (void)thread;
+ (void)compaction_style;
+ (void)write_mode;
+ fprintf(stderr, "Rocksdb Lite doesn't support filldeterministic\n");
+ return Status::NotSupported(
+ "Rocksdb Lite doesn't support filldeterministic");
+#endif // ROCKSDB_LITE
+ }
+
+ void ReadSequential(ThreadState* thread) {
+ if (db_.db != nullptr) {
+ ReadSequential(thread, db_.db);
+ } else {
+ for (const auto& db_with_cfh : multi_dbs_) {
+ ReadSequential(thread, db_with_cfh.db);
+ }
+ }
+ }
+
+ void ReadSequential(ThreadState* thread, DB* db) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ options.tailing = FLAGS_use_tailing_iterator;
+
+ Iterator* iter = db->NewIterator(options);
+ int64_t i = 0;
+ int64_t bytes = 0;
+ for (iter->SeekToFirst(); i < reads_ && iter->Valid(); iter->Next()) {
+ bytes += iter->key().size() + iter->value().size();
+ thread->stats.FinishedOps(nullptr, db, 1, kRead);
+ ++i;
+
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ i % 1024 == 1023) {
+ thread->shared->read_rate_limiter->Request(1024, Env::IO_HIGH,
+ nullptr /* stats */,
+ RateLimiter::OpType::kRead);
+ }
+ }
+
+ delete iter;
+ thread->stats.AddBytes(bytes);
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ void ReadToRowCache(ThreadState* thread) {
+ int64_t read = 0;
+ int64_t found = 0;
+ int64_t bytes = 0;
+ int64_t key_rand = 0;
+ ReadOptions options(FLAGS_verify_checksum, true);
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ PinnableSlice pinnable_val;
+
+ while (key_rand < FLAGS_num) {
+ DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(thread);
+ // We use same key_rand as seed for key and column family so that we can
+ // deterministically find the cfh corresponding to a particular key, as it
+ // is done in DoWrite method.
+ GenerateKeyFromInt(key_rand, FLAGS_num, &key);
+ key_rand++;
+ read++;
+ Status s;
+ if (FLAGS_num_column_families > 1) {
+ s = db_with_cfh->db->Get(options, db_with_cfh->GetCfh(key_rand), key,
+ &pinnable_val);
+ } else {
+ pinnable_val.Reset();
+ s = db_with_cfh->db->Get(options,
+ db_with_cfh->db->DefaultColumnFamily(), key,
+ &pinnable_val);
+ }
+
+ if (s.ok()) {
+ found++;
+ bytes += key.size() + pinnable_val.size();
+ } else if (!s.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n", s.ToString().c_str());
+ abort();
+ }
+
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ read % 256 == 255) {
+ thread->shared->read_rate_limiter->Request(
+ 256, Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kRead);
+ }
+
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kRead);
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)\n", found,
+ read);
+
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ void ReadReverse(ThreadState* thread) {
+ if (db_.db != nullptr) {
+ ReadReverse(thread, db_.db);
+ } else {
+ for (const auto& db_with_cfh : multi_dbs_) {
+ ReadReverse(thread, db_with_cfh.db);
+ }
+ }
+ }
+
+ void ReadReverse(ThreadState* thread, DB* db) {
+ Iterator* iter = db->NewIterator(ReadOptions(FLAGS_verify_checksum, true));
+ int64_t i = 0;
+ int64_t bytes = 0;
+ for (iter->SeekToLast(); i < reads_ && iter->Valid(); iter->Prev()) {
+ bytes += iter->key().size() + iter->value().size();
+ thread->stats.FinishedOps(nullptr, db, 1, kRead);
+ ++i;
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ i % 1024 == 1023) {
+ thread->shared->read_rate_limiter->Request(1024, Env::IO_HIGH,
+ nullptr /* stats */,
+ RateLimiter::OpType::kRead);
+ }
+ }
+ delete iter;
+ thread->stats.AddBytes(bytes);
+ }
+
+ void ReadRandomFast(ThreadState* thread) {
+ int64_t read = 0;
+ int64_t found = 0;
+ int64_t nonexist = 0;
+ ReadOptions options(FLAGS_verify_checksum, true);
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ std::string value;
+ DB* db = SelectDBWithCfh(thread)->db;
+
+ int64_t pot = 1;
+ while (pot < FLAGS_num) {
+ pot <<= 1;
+ }
+
+ Duration duration(FLAGS_duration, reads_);
+ do {
+ for (int i = 0; i < 100; ++i) {
+ int64_t key_rand = thread->rand.Next() & (pot - 1);
+ GenerateKeyFromInt(key_rand, FLAGS_num, &key);
+ ++read;
+ auto status = db->Get(options, key, &value);
+ if (status.ok()) {
+ ++found;
+ } else if (!status.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n",
+ status.ToString().c_str());
+ abort();
+ }
+ if (key_rand >= FLAGS_num) {
+ ++nonexist;
+ }
+ }
+ if (thread->shared->read_rate_limiter.get() != nullptr) {
+ thread->shared->read_rate_limiter->Request(
+ 100, Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kRead);
+ }
+
+ thread->stats.FinishedOps(nullptr, db, 100, kRead);
+ } while (!duration.Done(100));
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found, "
+ "issued %" PRIu64 " non-exist keys)\n",
+ found, read, nonexist);
+
+ thread->stats.AddMessage(msg);
+
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ int64_t GetRandomKey(Random64* rand) {
+ uint64_t rand_int = rand->Next();
+ int64_t key_rand;
+ if (read_random_exp_range_ == 0) {
+ key_rand = rand_int % FLAGS_num;
+ } else {
+ const uint64_t kBigInt = static_cast<uint64_t>(1U) << 62;
+ long double order = -static_cast<long double>(rand_int % kBigInt) /
+ static_cast<long double>(kBigInt) *
+ read_random_exp_range_;
+ long double exp_ran = std::exp(order);
+ uint64_t rand_num =
+ static_cast<int64_t>(exp_ran * static_cast<long double>(FLAGS_num));
+ // Map to a different number to avoid locality.
+ const uint64_t kBigPrime = 0x5bd1e995;
+ // Overflow is like %(2^64). Will have little impact of results.
+ key_rand = static_cast<int64_t>((rand_num * kBigPrime) % FLAGS_num);
+ }
+ return key_rand;
+ }
+
+ void ReadRandom(ThreadState* thread) {
+ int64_t read = 0;
+ int64_t found = 0;
+ int64_t bytes = 0;
+ int num_keys = 0;
+ int64_t key_rand = GetRandomKey(&thread->rand);
+ ReadOptions options(FLAGS_verify_checksum, true);
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ PinnableSlice pinnable_val;
+
+ Duration duration(FLAGS_duration, reads_);
+ while (!duration.Done(1)) {
+ DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(thread);
+ // We use same key_rand as seed for key and column family so that we can
+ // deterministically find the cfh corresponding to a particular key, as it
+ // is done in DoWrite method.
+ GenerateKeyFromInt(key_rand, FLAGS_num, &key);
+ if (entries_per_batch_ > 1 && FLAGS_multiread_stride) {
+ if (++num_keys == entries_per_batch_) {
+ num_keys = 0;
+ key_rand = GetRandomKey(&thread->rand);
+ if ((key_rand + (entries_per_batch_ - 1) * FLAGS_multiread_stride) >=
+ FLAGS_num) {
+ key_rand = FLAGS_num - entries_per_batch_ * FLAGS_multiread_stride;
+ }
+ } else {
+ key_rand += FLAGS_multiread_stride;
+ }
+ } else {
+ key_rand = GetRandomKey(&thread->rand);
+ }
+ read++;
+ Status s;
+ if (FLAGS_num_column_families > 1) {
+ s = db_with_cfh->db->Get(options, db_with_cfh->GetCfh(key_rand), key,
+ &pinnable_val);
+ } else {
+ pinnable_val.Reset();
+ s = db_with_cfh->db->Get(options,
+ db_with_cfh->db->DefaultColumnFamily(), key,
+ &pinnable_val);
+ }
+ if (s.ok()) {
+ found++;
+ bytes += key.size() + pinnable_val.size();
+ } else if (!s.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n", s.ToString().c_str());
+ abort();
+ }
+
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ read % 256 == 255) {
+ thread->shared->read_rate_limiter->Request(
+ 256, Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kRead);
+ }
+
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kRead);
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)\n",
+ found, read);
+
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ // Calls MultiGet over a list of keys from a random distribution.
+ // Returns the total number of keys found.
+ void MultiReadRandom(ThreadState* thread) {
+ int64_t read = 0;
+ int64_t num_multireads = 0;
+ int64_t found = 0;
+ ReadOptions options(FLAGS_verify_checksum, true);
+ std::vector<Slice> keys;
+ std::vector<std::unique_ptr<const char[]> > key_guards;
+ std::vector<std::string> values(entries_per_batch_);
+ PinnableSlice* pin_values = new PinnableSlice[entries_per_batch_];
+ std::unique_ptr<PinnableSlice[]> pin_values_guard(pin_values);
+ std::vector<Status> stat_list(entries_per_batch_);
+ while (static_cast<int64_t>(keys.size()) < entries_per_batch_) {
+ key_guards.push_back(std::unique_ptr<const char[]>());
+ keys.push_back(AllocateKey(&key_guards.back()));
+ }
+
+ Duration duration(FLAGS_duration, reads_);
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ if (FLAGS_multiread_stride) {
+ int64_t key = GetRandomKey(&thread->rand);
+ if ((key + (entries_per_batch_ - 1) * FLAGS_multiread_stride) >=
+ static_cast<int64_t>(FLAGS_num)) {
+ key = FLAGS_num - entries_per_batch_ * FLAGS_multiread_stride;
+ }
+ for (int64_t i = 0; i < entries_per_batch_; ++i) {
+ GenerateKeyFromInt(key, FLAGS_num, &keys[i]);
+ key += FLAGS_multiread_stride;
+ }
+ } else {
+ for (int64_t i = 0; i < entries_per_batch_; ++i) {
+ GenerateKeyFromInt(GetRandomKey(&thread->rand), FLAGS_num, &keys[i]);
+ }
+ }
+ if (!FLAGS_multiread_batched) {
+ std::vector<Status> statuses = db->MultiGet(options, keys, &values);
+ assert(static_cast<int64_t>(statuses.size()) == entries_per_batch_);
+
+ read += entries_per_batch_;
+ num_multireads++;
+ for (int64_t i = 0; i < entries_per_batch_; ++i) {
+ if (statuses[i].ok()) {
+ ++found;
+ } else if (!statuses[i].IsNotFound()) {
+ fprintf(stderr, "MultiGet returned an error: %s\n",
+ statuses[i].ToString().c_str());
+ abort();
+ }
+ }
+ } else {
+ db->MultiGet(options, db->DefaultColumnFamily(), keys.size(),
+ keys.data(), pin_values, stat_list.data());
+
+ read += entries_per_batch_;
+ num_multireads++;
+ for (int64_t i = 0; i < entries_per_batch_; ++i) {
+ if (stat_list[i].ok()) {
+ ++found;
+ } else if (!stat_list[i].IsNotFound()) {
+ fprintf(stderr, "MultiGet returned an error: %s\n",
+ stat_list[i].ToString().c_str());
+ abort();
+ }
+ stat_list[i] = Status::OK();
+ pin_values[i].Reset();
+ }
+ }
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ num_multireads % 256 == 255) {
+ thread->shared->read_rate_limiter->Request(
+ 256 * entries_per_batch_, Env::IO_HIGH, nullptr /* stats */,
+ RateLimiter::OpType::kRead);
+ }
+ thread->stats.FinishedOps(nullptr, db, entries_per_batch_, kRead);
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)",
+ found, read);
+ thread->stats.AddMessage(msg);
+ }
+
+ // The inverse function of Pareto distribution
+ int64_t ParetoCdfInversion(double u, double theta, double k, double sigma) {
+ double ret;
+ if (k == 0.0) {
+ ret = theta - sigma * std::log(u);
+ } else {
+ ret = theta + sigma * (std::pow(u, -1 * k) - 1) / k;
+ }
+ return static_cast<int64_t>(ceil(ret));
+ }
+ // The inverse function of power distribution (y=ax^b)
+ int64_t PowerCdfInversion(double u, double a, double b) {
+ double ret;
+ ret = std::pow((u / a), (1 / b));
+ return static_cast<int64_t>(ceil(ret));
+ }
+
+ // Add the noice to the QPS
+ double AddNoise(double origin, double noise_ratio) {
+ if (noise_ratio < 0.0 || noise_ratio > 1.0) {
+ return origin;
+ }
+ int band_int = static_cast<int>(FLAGS_sine_a);
+ double delta = (rand() % band_int - band_int / 2) * noise_ratio;
+ if (origin + delta < 0) {
+ return origin;
+ } else {
+ return (origin + delta);
+ }
+ }
+
+ // Decide the ratio of different query types
+ // 0 Get, 1 Put, 2 Seek, 3 SeekForPrev, 4 Delete, 5 SingleDelete, 6 merge
+ class QueryDecider {
+ public:
+ std::vector<int> type_;
+ std::vector<double> ratio_;
+ int range_;
+
+ QueryDecider() {}
+ ~QueryDecider() {}
+
+ Status Initiate(std::vector<double> ratio_input) {
+ int range_max = 1000;
+ double sum = 0.0;
+ for (auto& ratio : ratio_input) {
+ sum += ratio;
+ }
+ range_ = 0;
+ for (auto& ratio : ratio_input) {
+ range_ += static_cast<int>(ceil(range_max * (ratio / sum)));
+ type_.push_back(range_);
+ ratio_.push_back(ratio / sum);
+ }
+ return Status::OK();
+ }
+
+ int GetType(int64_t rand_num) {
+ if (rand_num < 0) {
+ rand_num = rand_num * (-1);
+ }
+ assert(range_ != 0);
+ int pos = static_cast<int>(rand_num % range_);
+ for (int i = 0; i < static_cast<int>(type_.size()); i++) {
+ if (pos < type_[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
+ };
+
+ // KeyrangeUnit is the struct of a keyrange. It is used in a keyrange vector
+ // to transfer a random value to one keyrange based on the hotness.
+ struct KeyrangeUnit {
+ int64_t keyrange_start;
+ int64_t keyrange_access;
+ int64_t keyrange_keys;
+ };
+
+ // From our observations, the prefix hotness (key-range hotness) follows
+ // the two-term-exponential distribution: f(x) = a*exp(b*x) + c*exp(d*x).
+ // However, we cannot directly use the inverse function to decide a
+ // key-range from a random distribution. To achieve it, we create a list of
+ // KeyrangeUnit, each KeyrangeUnit occupies a range of integers whose size is
+ // decided based on the hotness of the key-range. When a random value is
+ // generated based on uniform distribution, we map it to the KeyrangeUnit Vec
+ // and one KeyrangeUnit is selected. The probability of a KeyrangeUnit being
+ // selected is the same as the hotness of this KeyrangeUnit. After that, the
+ // key can be randomly allocated to the key-range of this KeyrangeUnit, or we
+ // can based on the power distribution (y=ax^b) to generate the offset of
+ // the key in the selected key-range. In this way, we generate the keyID
+ // based on the hotness of the prefix and also the key hotness distribution.
+ class GenerateTwoTermExpKeys {
+ public:
+ int64_t keyrange_rand_max_;
+ int64_t keyrange_size_;
+ int64_t keyrange_num_;
+ bool initiated_;
+ std::vector<KeyrangeUnit> keyrange_set_;
+
+ GenerateTwoTermExpKeys() {
+ keyrange_rand_max_ = FLAGS_num;
+ initiated_ = false;
+ }
+
+ ~GenerateTwoTermExpKeys() {}
+
+ // Initiate the KeyrangeUnit vector and calculate the size of each
+ // KeyrangeUnit.
+ Status InitiateExpDistribution(int64_t total_keys, double prefix_a,
+ double prefix_b, double prefix_c,
+ double prefix_d) {
+ int64_t amplify = 0;
+ int64_t keyrange_start = 0;
+ initiated_ = true;
+ if (FLAGS_keyrange_num <= 0) {
+ keyrange_num_ = 1;
+ } else {
+ keyrange_num_ = FLAGS_keyrange_num;
+ }
+ keyrange_size_ = total_keys / keyrange_num_;
+
+ // Calculate the key-range shares size based on the input parameters
+ for (int64_t pfx = keyrange_num_; pfx >= 1; pfx--) {
+ // Step 1. Calculate the probability that this key range will be
+ // accessed in a query. It is based on the two-term expoential
+ // distribution
+ double keyrange_p = prefix_a * std::exp(prefix_b * pfx) +
+ prefix_c * std::exp(prefix_d * pfx);
+ if (keyrange_p < std::pow(10.0, -16.0)) {
+ keyrange_p = 0.0;
+ }
+ // Step 2. Calculate the amplify
+ // In order to allocate a query to a key-range based on the random
+ // number generated for this query, we need to extend the probability
+ // of each key range from [0,1] to [0, amplify]. Amplify is calculated
+ // by 1/(smallest key-range probability). In this way, we ensure that
+ // all key-ranges are assigned with an Integer that >=0
+ if (amplify == 0 && keyrange_p > 0) {
+ amplify = static_cast<int64_t>(std::floor(1 / keyrange_p)) + 1;
+ }
+
+ // Step 3. For each key-range, we calculate its position in the
+ // [0, amplify] range, including the start, the size (keyrange_access)
+ KeyrangeUnit p_unit;
+ p_unit.keyrange_start = keyrange_start;
+ if (0.0 >= keyrange_p) {
+ p_unit.keyrange_access = 0;
+ } else {
+ p_unit.keyrange_access =
+ static_cast<int64_t>(std::floor(amplify * keyrange_p));
+ }
+ p_unit.keyrange_keys = keyrange_size_;
+ keyrange_set_.push_back(p_unit);
+ keyrange_start += p_unit.keyrange_access;
+ }
+ keyrange_rand_max_ = keyrange_start;
+
+ // Step 4. Shuffle the key-ranges randomly
+ // Since the access probability is calculated from small to large,
+ // If we do not re-allocate them, hot key-ranges are always at the end
+ // and cold key-ranges are at the begin of the key space. Therefore, the
+ // key-ranges are shuffled and the rand seed is only decide by the
+ // key-range hotness distribution. With the same distribution parameters
+ // the shuffle results are the same.
+ Random64 rand_loca(keyrange_rand_max_);
+ for (int64_t i = 0; i < FLAGS_keyrange_num; i++) {
+ int64_t pos = rand_loca.Next() % FLAGS_keyrange_num;
+ assert(i >= 0 && i < static_cast<int64_t>(keyrange_set_.size()) &&
+ pos >= 0 && pos < static_cast<int64_t>(keyrange_set_.size()));
+ std::swap(keyrange_set_[i], keyrange_set_[pos]);
+ }
+
+ // Step 5. Recalculate the prefix start postion after shuffling
+ int64_t offset = 0;
+ for (auto& p_unit : keyrange_set_) {
+ p_unit.keyrange_start = offset;
+ offset += p_unit.keyrange_access;
+ }
+
+ return Status::OK();
+ }
+
+ // Generate the Key ID according to the input ini_rand and key distribution
+ int64_t DistGetKeyID(int64_t ini_rand, double key_dist_a,
+ double key_dist_b) {
+ int64_t keyrange_rand = ini_rand % keyrange_rand_max_;
+
+ // Calculate and select one key-range that contains the new key
+ int64_t start = 0, end = static_cast<int64_t>(keyrange_set_.size());
+ while (start + 1 < end) {
+ int64_t mid = start + (end - start) / 2;
+ assert(mid >= 0 && mid < static_cast<int64_t>(keyrange_set_.size()));
+ if (keyrange_rand < keyrange_set_[mid].keyrange_start) {
+ end = mid;
+ } else {
+ start = mid;
+ }
+ }
+ int64_t keyrange_id = start;
+
+ // Select one key in the key-range and compose the keyID
+ int64_t key_offset = 0, key_seed;
+ if (key_dist_a == 0.0 && key_dist_b == 0.0) {
+ key_offset = ini_rand % keyrange_size_;
+ } else {
+ key_seed = static_cast<int64_t>(
+ ceil(std::pow((ini_rand / key_dist_a), (1 / key_dist_b))));
+ Random64 rand_key(key_seed);
+ key_offset = static_cast<int64_t>(rand_key.Next()) % keyrange_size_;
+ }
+ return keyrange_size_ * keyrange_id + key_offset;
+ }
+ };
+
+ // The social graph wokrload mixed with Get, Put, Iterator queries.
+ // The value size and iterator length follow Pareto distribution.
+ // The overall key access follow power distribution. If user models the
+ // workload based on different key-ranges (or different prefixes), user
+ // can use two-term-exponential distribution to fit the workload. User
+ // needs to decides the ratio between Get, Put, Iterator queries before
+ // starting the benchmark.
+ void MixGraph(ThreadState* thread) {
+ int64_t read = 0; // including single gets and Next of iterators
+ int64_t gets = 0;
+ int64_t puts = 0;
+ int64_t found = 0;
+ int64_t seek = 0;
+ int64_t seek_found = 0;
+ int64_t bytes = 0;
+ const int64_t default_value_max = 1 * 1024 * 1024;
+ int64_t value_max = default_value_max;
+ int64_t scan_len_max = FLAGS_mix_max_scan_len;
+ double write_rate = 1000000.0;
+ double read_rate = 1000000.0;
+ bool use_prefix_modeling = false;
+ GenerateTwoTermExpKeys gen_exp;
+ std::vector<double> ratio{FLAGS_mix_get_ratio, FLAGS_mix_put_ratio,
+ FLAGS_mix_seek_ratio};
+ char value_buffer[default_value_max];
+ QueryDecider query;
+ RandomGenerator gen;
+ Status s;
+ if (value_max > FLAGS_mix_max_value_size) {
+ value_max = FLAGS_mix_max_value_size;
+ }
+
+ ReadOptions options(FLAGS_verify_checksum, true);
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ PinnableSlice pinnable_val;
+ query.Initiate(ratio);
+
+ // the limit of qps initiation
+ if (FLAGS_sine_a != 0 || FLAGS_sine_d != 0) {
+ thread->shared->read_rate_limiter.reset(NewGenericRateLimiter(
+ static_cast<int64_t>(read_rate), 100000 /* refill_period_us */, 10 /* fairness */,
+ RateLimiter::Mode::kReadsOnly));
+ thread->shared->write_rate_limiter.reset(
+ NewGenericRateLimiter(static_cast<int64_t>(write_rate)));
+ }
+
+ // Decide if user wants to use prefix based key generation
+ if (FLAGS_keyrange_dist_a != 0.0 || FLAGS_keyrange_dist_b != 0.0 ||
+ FLAGS_keyrange_dist_c != 0.0 || FLAGS_keyrange_dist_d != 0.0) {
+ use_prefix_modeling = true;
+ gen_exp.InitiateExpDistribution(
+ FLAGS_num, FLAGS_keyrange_dist_a, FLAGS_keyrange_dist_b,
+ FLAGS_keyrange_dist_c, FLAGS_keyrange_dist_d);
+ }
+
+ Duration duration(FLAGS_duration, reads_);
+ while (!duration.Done(1)) {
+ DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(thread);
+ int64_t ini_rand, rand_v, key_rand, key_seed;
+ ini_rand = GetRandomKey(&thread->rand);
+ rand_v = ini_rand % FLAGS_num;
+ double u = static_cast<double>(rand_v) / FLAGS_num;
+
+ // Generate the keyID based on the key hotness and prefix hotness
+ if (use_prefix_modeling) {
+ key_rand =
+ gen_exp.DistGetKeyID(ini_rand, FLAGS_key_dist_a, FLAGS_key_dist_b);
+ } else {
+ key_seed = PowerCdfInversion(u, FLAGS_key_dist_a, FLAGS_key_dist_b);
+ Random64 rand(key_seed);
+ key_rand = static_cast<int64_t>(rand.Next()) % FLAGS_num;
+ }
+ GenerateKeyFromInt(key_rand, FLAGS_num, &key);
+ int query_type = query.GetType(rand_v);
+
+ // change the qps
+ uint64_t now = FLAGS_env->NowMicros();
+ uint64_t usecs_since_last;
+ if (now > thread->stats.GetSineInterval()) {
+ usecs_since_last = now - thread->stats.GetSineInterval();
+ } else {
+ usecs_since_last = 0;
+ }
+
+ if (usecs_since_last >
+ (FLAGS_sine_mix_rate_interval_milliseconds * uint64_t{1000})) {
+ double usecs_since_start =
+ static_cast<double>(now - thread->stats.GetStart());
+ thread->stats.ResetSineInterval();
+ double mix_rate_with_noise = AddNoise(
+ SineRate(usecs_since_start / 1000000.0), FLAGS_sine_mix_rate_noise);
+ read_rate = mix_rate_with_noise * (query.ratio_[0] + query.ratio_[2]);
+ write_rate =
+ mix_rate_with_noise * query.ratio_[1] * FLAGS_mix_ave_kv_size;
+
+ thread->shared->write_rate_limiter.reset(
+ NewGenericRateLimiter(static_cast<int64_t>(write_rate)));
+ thread->shared->read_rate_limiter.reset(NewGenericRateLimiter(
+ static_cast<int64_t>(read_rate),
+ FLAGS_sine_mix_rate_interval_milliseconds * uint64_t{1000}, 10,
+ RateLimiter::Mode::kReadsOnly));
+ }
+ // Start the query
+ if (query_type == 0) {
+ // the Get query
+ gets++;
+ read++;
+ if (FLAGS_num_column_families > 1) {
+ s = db_with_cfh->db->Get(options, db_with_cfh->GetCfh(key_rand), key,
+ &pinnable_val);
+ } else {
+ pinnable_val.Reset();
+ s = db_with_cfh->db->Get(options,
+ db_with_cfh->db->DefaultColumnFamily(), key,
+ &pinnable_val);
+ }
+
+ if (s.ok()) {
+ found++;
+ bytes += key.size() + pinnable_val.size();
+ } else if (!s.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n", s.ToString().c_str());
+ abort();
+ }
+
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ read % 256 == 255) {
+ thread->shared->read_rate_limiter->Request(
+ 256, Env::IO_HIGH, nullptr /* stats */,
+ RateLimiter::OpType::kRead);
+ }
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kRead);
+ } else if (query_type == 1) {
+ // the Put query
+ puts++;
+ int64_t val_size = ParetoCdfInversion(
+ u, FLAGS_value_theta, FLAGS_value_k, FLAGS_value_sigma);
+ if (val_size < 0) {
+ val_size = 10;
+ } else if (val_size > value_max) {
+ val_size = val_size % value_max;
+ }
+ s = db_with_cfh->db->Put(
+ write_options_, key,
+ gen.Generate(static_cast<unsigned int>(val_size)));
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+
+ if (thread->shared->write_rate_limiter) {
+ thread->shared->write_rate_limiter->Request(
+ key.size() + val_size, Env::IO_HIGH, nullptr /*stats*/,
+ RateLimiter::OpType::kWrite);
+ }
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kWrite);
+ } else if (query_type == 2) {
+ // Seek query
+ if (db_with_cfh->db != nullptr) {
+ Iterator* single_iter = nullptr;
+ single_iter = db_with_cfh->db->NewIterator(options);
+ if (single_iter != nullptr) {
+ single_iter->Seek(key);
+ seek++;
+ read++;
+ if (single_iter->Valid() && single_iter->key().compare(key) == 0) {
+ seek_found++;
+ }
+ int64_t scan_length =
+ ParetoCdfInversion(u, FLAGS_iter_theta, FLAGS_iter_k,
+ FLAGS_iter_sigma) %
+ scan_len_max;
+ for (int64_t j = 0; j < scan_length && single_iter->Valid(); j++) {
+ Slice value = single_iter->value();
+ memcpy(value_buffer, value.data(),
+ std::min(value.size(), sizeof(value_buffer)));
+ bytes += single_iter->key().size() + single_iter->value().size();
+ single_iter->Next();
+ assert(single_iter->status().ok());
+ }
+ }
+ delete single_iter;
+ }
+ thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kSeek);
+ }
+ }
+ char msg[256];
+ snprintf(msg, sizeof(msg),
+ "( Gets:%" PRIu64 " Puts:%" PRIu64 " Seek:%" PRIu64 " of %" PRIu64
+ " in %" PRIu64 " found)\n",
+ gets, puts, seek, found, read);
+
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ void IteratorCreation(ThreadState* thread) {
+ Duration duration(FLAGS_duration, reads_);
+ ReadOptions options(FLAGS_verify_checksum, true);
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ Iterator* iter = db->NewIterator(options);
+ delete iter;
+ thread->stats.FinishedOps(nullptr, db, 1, kOthers);
+ }
+ }
+
+ void IteratorCreationWhileWriting(ThreadState* thread) {
+ if (thread->tid > 0) {
+ IteratorCreation(thread);
+ } else {
+ BGWriter(thread, kWrite);
+ }
+ }
+
+ void SeekRandom(ThreadState* thread) {
+ int64_t read = 0;
+ int64_t found = 0;
+ int64_t bytes = 0;
+ ReadOptions options(FLAGS_verify_checksum, true);
+ options.total_order_seek = FLAGS_total_order_seek;
+ options.prefix_same_as_start = FLAGS_prefix_same_as_start;
+ options.tailing = FLAGS_use_tailing_iterator;
+ options.readahead_size = FLAGS_readahead_size;
+
+ Iterator* single_iter = nullptr;
+ std::vector<Iterator*> multi_iters;
+ if (db_.db != nullptr) {
+ single_iter = db_.db->NewIterator(options);
+ } else {
+ for (const auto& db_with_cfh : multi_dbs_) {
+ multi_iters.push_back(db_with_cfh.db->NewIterator(options));
+ }
+ }
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ std::unique_ptr<const char[]> upper_bound_key_guard;
+ Slice upper_bound = AllocateKey(&upper_bound_key_guard);
+ std::unique_ptr<const char[]> lower_bound_key_guard;
+ Slice lower_bound = AllocateKey(&lower_bound_key_guard);
+
+ Duration duration(FLAGS_duration, reads_);
+ char value_buffer[256];
+ while (!duration.Done(1)) {
+ int64_t seek_pos = thread->rand.Next() % FLAGS_num;
+ GenerateKeyFromIntForSeek(static_cast<uint64_t>(seek_pos), FLAGS_num,
+ &key);
+ if (FLAGS_max_scan_distance != 0) {
+ if (FLAGS_reverse_iterator) {
+ GenerateKeyFromInt(
+ static_cast<uint64_t>(std::max(
+ static_cast<int64_t>(0), seek_pos - FLAGS_max_scan_distance)),
+ FLAGS_num, &lower_bound);
+ options.iterate_lower_bound = &lower_bound;
+ } else {
+ auto min_num =
+ std::min(FLAGS_num, seek_pos + FLAGS_max_scan_distance);
+ GenerateKeyFromInt(static_cast<uint64_t>(min_num), FLAGS_num,
+ &upper_bound);
+ options.iterate_upper_bound = &upper_bound;
+ }
+ }
+
+ if (!FLAGS_use_tailing_iterator) {
+ if (db_.db != nullptr) {
+ delete single_iter;
+ single_iter = db_.db->NewIterator(options);
+ } else {
+ for (auto iter : multi_iters) {
+ delete iter;
+ }
+ multi_iters.clear();
+ for (const auto& db_with_cfh : multi_dbs_) {
+ multi_iters.push_back(db_with_cfh.db->NewIterator(options));
+ }
+ }
+ }
+ // Pick a Iterator to use
+ Iterator* iter_to_use = single_iter;
+ if (single_iter == nullptr) {
+ iter_to_use = multi_iters[thread->rand.Next() % multi_iters.size()];
+ }
+
+ iter_to_use->Seek(key);
+ read++;
+ if (iter_to_use->Valid() && iter_to_use->key().compare(key) == 0) {
+ found++;
+ }
+
+ for (int j = 0; j < FLAGS_seek_nexts && iter_to_use->Valid(); ++j) {
+ // Copy out iterator's value to make sure we read them.
+ Slice value = iter_to_use->value();
+ memcpy(value_buffer, value.data(),
+ std::min(value.size(), sizeof(value_buffer)));
+ bytes += iter_to_use->key().size() + iter_to_use->value().size();
+
+ if (!FLAGS_reverse_iterator) {
+ iter_to_use->Next();
+ } else {
+ iter_to_use->Prev();
+ }
+ assert(iter_to_use->status().ok());
+ }
+
+ if (thread->shared->read_rate_limiter.get() != nullptr &&
+ read % 256 == 255) {
+ thread->shared->read_rate_limiter->Request(
+ 256, Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kRead);
+ }
+
+ thread->stats.FinishedOps(&db_, db_.db, 1, kSeek);
+ }
+ delete single_iter;
+ for (auto iter : multi_iters) {
+ delete iter;
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)\n",
+ found, read);
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ void SeekRandomWhileWriting(ThreadState* thread) {
+ if (thread->tid > 0) {
+ SeekRandom(thread);
+ } else {
+ BGWriter(thread, kWrite);
+ }
+ }
+
+ void SeekRandomWhileMerging(ThreadState* thread) {
+ if (thread->tid > 0) {
+ SeekRandom(thread);
+ } else {
+ BGWriter(thread, kMerge);
+ }
+ }
+
+ void DoDelete(ThreadState* thread, bool seq) {
+ WriteBatch batch;
+ Duration duration(seq ? 0 : FLAGS_duration, deletes_);
+ int64_t i = 0;
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ while (!duration.Done(entries_per_batch_)) {
+ DB* db = SelectDB(thread);
+ batch.Clear();
+ for (int64_t j = 0; j < entries_per_batch_; ++j) {
+ const int64_t k = seq ? i + j : (thread->rand.Next() % FLAGS_num);
+ GenerateKeyFromInt(k, FLAGS_num, &key);
+ batch.Delete(key);
+ }
+ auto s = db->Write(write_options_, &batch);
+ thread->stats.FinishedOps(nullptr, db, entries_per_batch_, kDelete);
+ if (!s.ok()) {
+ fprintf(stderr, "del error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ i += entries_per_batch_;
+ }
+ }
+
+ void DeleteSeq(ThreadState* thread) {
+ DoDelete(thread, true);
+ }
+
+ void DeleteRandom(ThreadState* thread) {
+ DoDelete(thread, false);
+ }
+
+ void ReadWhileWriting(ThreadState* thread) {
+ if (thread->tid > 0) {
+ ReadRandom(thread);
+ } else {
+ BGWriter(thread, kWrite);
+ }
+ }
+
+ void ReadWhileMerging(ThreadState* thread) {
+ if (thread->tid > 0) {
+ ReadRandom(thread);
+ } else {
+ BGWriter(thread, kMerge);
+ }
+ }
+
+ void BGWriter(ThreadState* thread, enum OperationType write_merge) {
+ // Special thread that keeps writing until other threads are done.
+ RandomGenerator gen;
+ int64_t bytes = 0;
+
+ std::unique_ptr<RateLimiter> write_rate_limiter;
+ if (FLAGS_benchmark_write_rate_limit > 0) {
+ write_rate_limiter.reset(
+ NewGenericRateLimiter(FLAGS_benchmark_write_rate_limit));
+ }
+
+ // Don't merge stats from this thread with the readers.
+ thread->stats.SetExcludeFromMerge();
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ uint32_t written = 0;
+ bool hint_printed = false;
+
+ while (true) {
+ DB* db = SelectDB(thread);
+ {
+ MutexLock l(&thread->shared->mu);
+ if (FLAGS_finish_after_writes && written == writes_) {
+ fprintf(stderr, "Exiting the writer after %u writes...\n", written);
+ break;
+ }
+ if (thread->shared->num_done + 1 >= thread->shared->num_initialized) {
+ // Other threads have finished
+ if (FLAGS_finish_after_writes) {
+ // Wait for the writes to be finished
+ if (!hint_printed) {
+ fprintf(stderr, "Reads are finished. Have %d more writes to do\n",
+ static_cast<int>(writes_) - written);
+ hint_printed = true;
+ }
+ } else {
+ // Finish the write immediately
+ break;
+ }
+ }
+ }
+
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
+ Status s;
+
+ Slice val = gen.Generate();
+ if (write_merge == kWrite) {
+ s = db->Put(write_options_, key, val);
+ } else {
+ s = db->Merge(write_options_, key, val);
+ }
+ written++;
+
+ if (!s.ok()) {
+ fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ bytes += key.size() + val.size();
+ thread->stats.FinishedOps(&db_, db_.db, 1, kWrite);
+
+ if (FLAGS_benchmark_write_rate_limit > 0) {
+ write_rate_limiter->Request(
+ key.size() + val.size(), Env::IO_HIGH,
+ nullptr /* stats */, RateLimiter::OpType::kWrite);
+ }
+ }
+ thread->stats.AddBytes(bytes);
+ }
+
+ void ReadWhileScanning(ThreadState* thread) {
+ if (thread->tid > 0) {
+ ReadRandom(thread);
+ } else {
+ BGScan(thread);
+ }
+ }
+
+ void BGScan(ThreadState* thread) {
+ if (FLAGS_num_multi_db > 0) {
+ fprintf(stderr, "Not supporting multiple DBs.\n");
+ abort();
+ }
+ assert(db_.db != nullptr);
+ ReadOptions read_options;
+ Iterator* iter = db_.db->NewIterator(read_options);
+
+ fprintf(stderr, "num reads to do %" PRIu64 "\n", reads_);
+ Duration duration(FLAGS_duration, reads_);
+ uint64_t num_seek_to_first = 0;
+ uint64_t num_next = 0;
+ while (!duration.Done(1)) {
+ if (!iter->Valid()) {
+ iter->SeekToFirst();
+ num_seek_to_first++;
+ } else if (!iter->status().ok()) {
+ fprintf(stderr, "Iterator error: %s\n",
+ iter->status().ToString().c_str());
+ abort();
+ } else {
+ iter->Next();
+ num_next++;
+ }
+
+ thread->stats.FinishedOps(&db_, db_.db, 1, kSeek);
+ }
+ delete iter;
+ }
+
+ // Given a key K and value V, this puts (K+"0", V), (K+"1", V), (K+"2", V)
+ // in DB atomically i.e in a single batch. Also refer GetMany.
+ Status PutMany(DB* db, const WriteOptions& writeoptions, const Slice& key,
+ const Slice& value) {
+ std::string suffixes[3] = {"2", "1", "0"};
+ std::string keys[3];
+
+ WriteBatch batch;
+ Status s;
+ for (int i = 0; i < 3; i++) {
+ keys[i] = key.ToString() + suffixes[i];
+ batch.Put(keys[i], value);
+ }
+
+ s = db->Write(writeoptions, &batch);
+ return s;
+ }
+
+
+ // Given a key K, this deletes (K+"0", V), (K+"1", V), (K+"2", V)
+ // in DB atomically i.e in a single batch. Also refer GetMany.
+ Status DeleteMany(DB* db, const WriteOptions& writeoptions,
+ const Slice& key) {
+ std::string suffixes[3] = {"1", "2", "0"};
+ std::string keys[3];
+
+ WriteBatch batch;
+ Status s;
+ for (int i = 0; i < 3; i++) {
+ keys[i] = key.ToString() + suffixes[i];
+ batch.Delete(keys[i]);
+ }
+
+ s = db->Write(writeoptions, &batch);
+ return s;
+ }
+
+ // Given a key K and value V, this gets values for K+"0", K+"1" and K+"2"
+ // in the same snapshot, and verifies that all the values are identical.
+ // ASSUMES that PutMany was used to put (K, V) into the DB.
+ Status GetMany(DB* db, const ReadOptions& readoptions, const Slice& key,
+ std::string* value) {
+ std::string suffixes[3] = {"0", "1", "2"};
+ std::string keys[3];
+ Slice key_slices[3];
+ std::string values[3];
+ ReadOptions readoptionscopy = readoptions;
+ readoptionscopy.snapshot = db->GetSnapshot();
+ Status s;
+ for (int i = 0; i < 3; i++) {
+ keys[i] = key.ToString() + suffixes[i];
+ key_slices[i] = keys[i];
+ s = db->Get(readoptionscopy, key_slices[i], value);
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "get error: %s\n", s.ToString().c_str());
+ values[i] = "";
+ // we continue after error rather than exiting so that we can
+ // find more errors if any
+ } else if (s.IsNotFound()) {
+ values[i] = "";
+ } else {
+ values[i] = *value;
+ }
+ }
+ db->ReleaseSnapshot(readoptionscopy.snapshot);
+
+ if ((values[0] != values[1]) || (values[1] != values[2])) {
+ fprintf(stderr, "inconsistent values for key %s: %s, %s, %s\n",
+ key.ToString().c_str(), values[0].c_str(), values[1].c_str(),
+ values[2].c_str());
+ // we continue after error rather than exiting so that we can
+ // find more errors if any
+ }
+
+ return s;
+ }
+
+ // Differs from readrandomwriterandom in the following ways:
+ // (a) Uses GetMany/PutMany to read/write key values. Refer to those funcs.
+ // (b) Does deletes as well (per FLAGS_deletepercent)
+ // (c) In order to achieve high % of 'found' during lookups, and to do
+ // multiple writes (including puts and deletes) it uses upto
+ // FLAGS_numdistinct distinct keys instead of FLAGS_num distinct keys.
+ // (d) Does not have a MultiGet option.
+ void RandomWithVerify(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string value;
+ int64_t found = 0;
+ int get_weight = 0;
+ int put_weight = 0;
+ int delete_weight = 0;
+ int64_t gets_done = 0;
+ int64_t puts_done = 0;
+ int64_t deletes_done = 0;
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ // the number of iterations is the larger of read_ or write_
+ for (int64_t i = 0; i < readwrites_; i++) {
+ DB* db = SelectDB(thread);
+ if (get_weight == 0 && put_weight == 0 && delete_weight == 0) {
+ // one batch completed, reinitialize for next batch
+ get_weight = FLAGS_readwritepercent;
+ delete_weight = FLAGS_deletepercent;
+ put_weight = 100 - get_weight - delete_weight;
+ }
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_numdistinct,
+ FLAGS_numdistinct, &key);
+ if (get_weight > 0) {
+ // do all the gets first
+ Status s = GetMany(db, options, key, &value);
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "getmany error: %s\n", s.ToString().c_str());
+ // we continue after error rather than exiting so that we can
+ // find more errors if any
+ } else if (!s.IsNotFound()) {
+ found++;
+ }
+ get_weight--;
+ gets_done++;
+ thread->stats.FinishedOps(&db_, db_.db, 1, kRead);
+ } else if (put_weight > 0) {
+ // then do all the corresponding number of puts
+ // for all the gets we have done earlier
+ Status s = PutMany(db, write_options_, key, gen.Generate());
+ if (!s.ok()) {
+ fprintf(stderr, "putmany error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ put_weight--;
+ puts_done++;
+ thread->stats.FinishedOps(&db_, db_.db, 1, kWrite);
+ } else if (delete_weight > 0) {
+ Status s = DeleteMany(db, write_options_, key);
+ if (!s.ok()) {
+ fprintf(stderr, "deletemany error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ delete_weight--;
+ deletes_done++;
+ thread->stats.FinishedOps(&db_, db_.db, 1, kDelete);
+ }
+ }
+ char msg[128];
+ snprintf(msg, sizeof(msg),
+ "( get:%" PRIu64 " put:%" PRIu64 " del:%" PRIu64 " total:%" \
+ PRIu64 " found:%" PRIu64 ")",
+ gets_done, puts_done, deletes_done, readwrites_, found);
+ thread->stats.AddMessage(msg);
+ }
+
+ // This is different from ReadWhileWriting because it does not use
+ // an extra thread.
+ void ReadRandomWriteRandom(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string value;
+ int64_t found = 0;
+ int get_weight = 0;
+ int put_weight = 0;
+ int64_t reads_done = 0;
+ int64_t writes_done = 0;
+ Duration duration(FLAGS_duration, readwrites_);
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ // the number of iterations is the larger of read_ or write_
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
+ if (get_weight == 0 && put_weight == 0) {
+ // one batch completed, reinitialize for next batch
+ get_weight = FLAGS_readwritepercent;
+ put_weight = 100 - get_weight;
+ }
+ if (get_weight > 0) {
+ // do all the gets first
+ Status s = db->Get(options, key, &value);
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "get error: %s\n", s.ToString().c_str());
+ // we continue after error rather than exiting so that we can
+ // find more errors if any
+ } else if (!s.IsNotFound()) {
+ found++;
+ }
+ get_weight--;
+ reads_done++;
+ thread->stats.FinishedOps(nullptr, db, 1, kRead);
+ } else if (put_weight > 0) {
+ // then do all the corresponding number of puts
+ // for all the gets we have done earlier
+ Status s = db->Put(write_options_, key, gen.Generate());
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ put_weight--;
+ writes_done++;
+ thread->stats.FinishedOps(nullptr, db, 1, kWrite);
+ }
+ }
+ char msg[100];
+ snprintf(msg, sizeof(msg), "( reads:%" PRIu64 " writes:%" PRIu64 \
+ " total:%" PRIu64 " found:%" PRIu64 ")",
+ reads_done, writes_done, readwrites_, found);
+ thread->stats.AddMessage(msg);
+ }
+
+ //
+ // Read-modify-write for random keys
+ void UpdateRandom(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string value;
+ int64_t found = 0;
+ int64_t bytes = 0;
+ Duration duration(FLAGS_duration, readwrites_);
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ // the number of iterations is the larger of read_ or write_
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
+
+ auto status = db->Get(options, key, &value);
+ if (status.ok()) {
+ ++found;
+ bytes += key.size() + value.size();
+ } else if (!status.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n",
+ status.ToString().c_str());
+ abort();
+ }
+
+ if (thread->shared->write_rate_limiter) {
+ thread->shared->write_rate_limiter->Request(
+ key.size() + value.size(), Env::IO_HIGH, nullptr /*stats*/,
+ RateLimiter::OpType::kWrite);
+ }
+
+ Slice val = gen.Generate();
+ Status s = db->Put(write_options_, key, val);
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ bytes += key.size() + val.size();
+ thread->stats.FinishedOps(nullptr, db, 1, kUpdate);
+ }
+ char msg[100];
+ snprintf(msg, sizeof(msg),
+ "( updates:%" PRIu64 " found:%" PRIu64 ")", readwrites_, found);
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+ }
+
+ // Read-XOR-write for random keys. Xors the existing value with a randomly
+ // generated value, and stores the result. Assuming A in the array of bytes
+ // representing the existing value, we generate an array B of the same size,
+ // then compute C = A^B as C[i]=A[i]^B[i], and store C
+ void XORUpdateRandom(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string existing_value;
+ int64_t found = 0;
+ Duration duration(FLAGS_duration, readwrites_);
+
+ BytesXOROperator xor_operator;
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ // the number of iterations is the larger of read_ or write_
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
+
+ auto status = db->Get(options, key, &existing_value);
+ if (status.ok()) {
+ ++found;
+ } else if (!status.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n",
+ status.ToString().c_str());
+ exit(1);
+ }
+
+ Slice value = gen.Generate(static_cast<unsigned int>(existing_value.size()));
+ std::string new_value;
+
+ if (status.ok()) {
+ Slice existing_value_slice = Slice(existing_value);
+ xor_operator.XOR(&existing_value_slice, value, &new_value);
+ } else {
+ xor_operator.XOR(nullptr, value, &new_value);
+ }
+
+ Status s = db->Put(write_options_, key, Slice(new_value));
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ thread->stats.FinishedOps(nullptr, db, 1);
+ }
+ char msg[100];
+ snprintf(msg, sizeof(msg),
+ "( updates:%" PRIu64 " found:%" PRIu64 ")", readwrites_, found);
+ thread->stats.AddMessage(msg);
+ }
+
+ // Read-modify-write for random keys.
+ // Each operation causes the key grow by value_size (simulating an append).
+ // Generally used for benchmarking against merges of similar type
+ void AppendRandom(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string value;
+ int64_t found = 0;
+ int64_t bytes = 0;
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ // The number of iterations is the larger of read_ or write_
+ Duration duration(FLAGS_duration, readwrites_);
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
+
+ auto status = db->Get(options, key, &value);
+ if (status.ok()) {
+ ++found;
+ bytes += key.size() + value.size();
+ } else if (!status.IsNotFound()) {
+ fprintf(stderr, "Get returned an error: %s\n",
+ status.ToString().c_str());
+ abort();
+ } else {
+ // If not existing, then just assume an empty string of data
+ value.clear();
+ }
+
+ // Update the value (by appending data)
+ Slice operand = gen.Generate();
+ if (value.size() > 0) {
+ // Use a delimiter to match the semantics for StringAppendOperator
+ value.append(1,',');
+ }
+ value.append(operand.data(), operand.size());
+
+ // Write back to the database
+ Status s = db->Put(write_options_, key, value);
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ bytes += key.size() + value.size();
+ thread->stats.FinishedOps(nullptr, db, 1, kUpdate);
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "( updates:%" PRIu64 " found:%" PRIu64 ")",
+ readwrites_, found);
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+ }
+
+ // Read-modify-write for random keys (using MergeOperator)
+ // The merge operator to use should be defined by FLAGS_merge_operator
+ // Adjust FLAGS_value_size so that the keys are reasonable for this operator
+ // Assumes that the merge operator is non-null (i.e.: is well-defined)
+ //
+ // For example, use FLAGS_merge_operator="uint64add" and FLAGS_value_size=8
+ // to simulate random additions over 64-bit integers using merge.
+ //
+ // The number of merges on the same key can be controlled by adjusting
+ // FLAGS_merge_keys.
+ void MergeRandom(ThreadState* thread) {
+ RandomGenerator gen;
+ int64_t bytes = 0;
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ // The number of iterations is the larger of read_ or write_
+ Duration duration(FLAGS_duration, readwrites_);
+ while (!duration.Done(1)) {
+ DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(thread);
+ int64_t key_rand = thread->rand.Next() % merge_keys_;
+ GenerateKeyFromInt(key_rand, merge_keys_, &key);
+
+ Status s;
+ Slice val = gen.Generate();
+ if (FLAGS_num_column_families > 1) {
+ s = db_with_cfh->db->Merge(write_options_,
+ db_with_cfh->GetCfh(key_rand), key,
+ val);
+ } else {
+ s = db_with_cfh->db->Merge(write_options_,
+ db_with_cfh->db->DefaultColumnFamily(), key,
+ val);
+ }
+
+ if (!s.ok()) {
+ fprintf(stderr, "merge error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ bytes += key.size() + val.size();
+ thread->stats.FinishedOps(nullptr, db_with_cfh->db, 1, kMerge);
+ }
+
+ // Print some statistics
+ char msg[100];
+ snprintf(msg, sizeof(msg), "( updates:%" PRIu64 ")", readwrites_);
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+ }
+
+ // Read and merge random keys. The amount of reads and merges are controlled
+ // by adjusting FLAGS_num and FLAGS_mergereadpercent. The number of distinct
+ // keys (and thus also the number of reads and merges on the same key) can be
+ // adjusted with FLAGS_merge_keys.
+ //
+ // As with MergeRandom, the merge operator to use should be defined by
+ // FLAGS_merge_operator.
+ void ReadRandomMergeRandom(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ RandomGenerator gen;
+ std::string value;
+ int64_t num_hits = 0;
+ int64_t num_gets = 0;
+ int64_t num_merges = 0;
+ size_t max_length = 0;
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ // the number of iterations is the larger of read_ or write_
+ Duration duration(FLAGS_duration, readwrites_);
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+ GenerateKeyFromInt(thread->rand.Next() % merge_keys_, merge_keys_, &key);
+
+ bool do_merge = int(thread->rand.Next() % 100) < FLAGS_mergereadpercent;
+
+ if (do_merge) {
+ Status s = db->Merge(write_options_, key, gen.Generate());
+ if (!s.ok()) {
+ fprintf(stderr, "merge error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ num_merges++;
+ thread->stats.FinishedOps(nullptr, db, 1, kMerge);
+ } else {
+ Status s = db->Get(options, key, &value);
+ if (value.length() > max_length)
+ max_length = value.length();
+
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "get error: %s\n", s.ToString().c_str());
+ // we continue after error rather than exiting so that we can
+ // find more errors if any
+ } else if (!s.IsNotFound()) {
+ num_hits++;
+ }
+ num_gets++;
+ thread->stats.FinishedOps(nullptr, db, 1, kRead);
+ }
+ }
+
+ char msg[100];
+ snprintf(msg, sizeof(msg),
+ "(reads:%" PRIu64 " merges:%" PRIu64 " total:%" PRIu64
+ " hits:%" PRIu64 " maxlength:%" ROCKSDB_PRIszt ")",
+ num_gets, num_merges, readwrites_, num_hits, max_length);
+ thread->stats.AddMessage(msg);
+ }
+
+ void WriteSeqSeekSeq(ThreadState* thread) {
+ writes_ = FLAGS_num;
+ DoWrite(thread, SEQUENTIAL);
+ // exclude writes from the ops/sec calculation
+ thread->stats.Start(thread->tid);
+
+ DB* db = SelectDB(thread);
+ std::unique_ptr<Iterator> iter(
+ db->NewIterator(ReadOptions(FLAGS_verify_checksum, true)));
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ for (int64_t i = 0; i < FLAGS_num; ++i) {
+ GenerateKeyFromInt(i, FLAGS_num, &key);
+ iter->Seek(key);
+ assert(iter->Valid() && iter->key() == key);
+ thread->stats.FinishedOps(nullptr, db, 1, kSeek);
+
+ for (int j = 0; j < FLAGS_seek_nexts && i + 1 < FLAGS_num; ++j) {
+ if (!FLAGS_reverse_iterator) {
+ iter->Next();
+ } else {
+ iter->Prev();
+ }
+ GenerateKeyFromInt(++i, FLAGS_num, &key);
+ assert(iter->Valid() && iter->key() == key);
+ thread->stats.FinishedOps(nullptr, db, 1, kSeek);
+ }
+
+ iter->Seek(key);
+ assert(iter->Valid() && iter->key() == key);
+ thread->stats.FinishedOps(nullptr, db, 1, kSeek);
+ }
+ }
+
+ bool binary_search(std::vector<int>& data, int start, int end, int key) {
+ if (data.empty()) return false;
+ if (start > end) return false;
+ int mid = start + (end - start) / 2;
+ if (mid > static_cast<int>(data.size()) - 1) return false;
+ if (data[mid] == key) {
+ return true;
+ } else if (data[mid] > key) {
+ return binary_search(data, start, mid - 1, key);
+ } else {
+ return binary_search(data, mid + 1, end, key);
+ }
+ }
+
+ // Does a bunch of merge operations for a key(key1) where the merge operand
+ // is a sorted list. Next performance comparison is done between doing a Get
+ // for key1 followed by searching for another key(key2) in the large sorted
+ // list vs calling GetMergeOperands for key1 and then searching for the key2
+ // in all the sorted sub-lists. Later case is expected to be a lot faster.
+ void GetMergeOperands(ThreadState* thread) {
+ DB* db = SelectDB(thread);
+ const int kTotalValues = 100000;
+ const int kListSize = 100;
+ std::string key = "my_key";
+ std::string value;
+
+ for (int i = 1; i < kTotalValues; i++) {
+ if (i % kListSize == 0) {
+ // Remove trailing ','
+ value.pop_back();
+ db->Merge(WriteOptions(), key, value);
+ value.clear();
+ } else {
+ value.append(std::to_string(i)).append(",");
+ }
+ }
+
+ SortList s;
+ std::vector<int> data;
+ // This value can be experimented with and it will demonstrate the
+ // perf difference between doing a Get and searching for lookup_key in the
+ // resultant large sorted list vs doing GetMergeOperands and searching
+ // for lookup_key within this resultant sorted sub-lists.
+ int lookup_key = 1;
+
+ // Get API call
+ std::cout << "--- Get API call --- \n";
+ PinnableSlice p_slice;
+ uint64_t st = FLAGS_env->NowNanos();
+ db->Get(ReadOptions(), db->DefaultColumnFamily(), key, &p_slice);
+ s.MakeVector(data, p_slice);
+ bool found =
+ binary_search(data, 0, static_cast<int>(data.size() - 1), lookup_key);
+ std::cout << "Found key? " << std::to_string(found) << "\n";
+ uint64_t sp = FLAGS_env->NowNanos();
+ std::cout << "Get: " << (sp - st) / 1000000000.0 << " seconds\n";
+ std::string* dat_ = p_slice.GetSelf();
+ std::cout << "Sample data from Get API call: " << dat_->substr(0, 10)
+ << "\n";
+ data.clear();
+
+ // GetMergeOperands API call
+ std::cout << "--- GetMergeOperands API --- \n";
+ std::vector<PinnableSlice> a_slice((kTotalValues / kListSize) + 1);
+ st = FLAGS_env->NowNanos();
+ int number_of_operands = 0;
+ GetMergeOperandsOptions get_merge_operands_options;
+ get_merge_operands_options.expected_max_number_of_operands =
+ (kTotalValues / 100) + 1;
+ db->GetMergeOperands(ReadOptions(), db->DefaultColumnFamily(), key,
+ a_slice.data(), &get_merge_operands_options,
+ &number_of_operands);
+ for (PinnableSlice& psl : a_slice) {
+ s.MakeVector(data, psl);
+ found =
+ binary_search(data, 0, static_cast<int>(data.size() - 1), lookup_key);
+ data.clear();
+ if (found) break;
+ }
+ std::cout << "Found key? " << std::to_string(found) << "\n";
+ sp = FLAGS_env->NowNanos();
+ std::cout << "Get Merge operands: " << (sp - st) / 1000000000.0
+ << " seconds \n";
+ int to_print = 0;
+ std::cout << "Sample data from GetMergeOperands API call: ";
+ for (PinnableSlice& psl : a_slice) {
+ std::cout << "List: " << to_print << " : " << *psl.GetSelf() << "\n";
+ if (to_print++ > 2) break;
+ }
+ }
+
+#ifndef ROCKSDB_LITE
+ // This benchmark stress tests Transactions. For a given --duration (or
+ // total number of --writes, a Transaction will perform a read-modify-write
+ // to increment the value of a key in each of N(--transaction-sets) sets of
+ // keys (where each set has --num keys). If --threads is set, this will be
+ // done in parallel.
+ //
+ // To test transactions, use --transaction_db=true. Not setting this
+ // parameter
+ // will run the same benchmark without transactions.
+ //
+ // RandomTransactionVerify() will then validate the correctness of the results
+ // by checking if the sum of all keys in each set is the same.
+ void RandomTransaction(ThreadState* thread) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ Duration duration(FLAGS_duration, readwrites_);
+ ReadOptions read_options(FLAGS_verify_checksum, true);
+ uint16_t num_prefix_ranges = static_cast<uint16_t>(FLAGS_transaction_sets);
+ uint64_t transactions_done = 0;
+
+ if (num_prefix_ranges == 0 || num_prefix_ranges > 9999) {
+ fprintf(stderr, "invalid value for transaction_sets\n");
+ abort();
+ }
+
+ TransactionOptions txn_options;
+ txn_options.lock_timeout = FLAGS_transaction_lock_timeout;
+ txn_options.set_snapshot = FLAGS_transaction_set_snapshot;
+
+ RandomTransactionInserter inserter(&thread->rand, write_options_,
+ read_options, FLAGS_num,
+ num_prefix_ranges);
+
+ if (FLAGS_num_multi_db > 1) {
+ fprintf(stderr,
+ "Cannot run RandomTransaction benchmark with "
+ "FLAGS_multi_db > 1.");
+ abort();
+ }
+
+ while (!duration.Done(1)) {
+ bool success;
+
+ // RandomTransactionInserter will attempt to insert a key for each
+ // # of FLAGS_transaction_sets
+ if (FLAGS_optimistic_transaction_db) {
+ success = inserter.OptimisticTransactionDBInsert(db_.opt_txn_db);
+ } else if (FLAGS_transaction_db) {
+ TransactionDB* txn_db = reinterpret_cast<TransactionDB*>(db_.db);
+ success = inserter.TransactionDBInsert(txn_db, txn_options);
+ } else {
+ success = inserter.DBInsert(db_.db);
+ }
+
+ if (!success) {
+ fprintf(stderr, "Unexpected error: %s\n",
+ inserter.GetLastStatus().ToString().c_str());
+ abort();
+ }
+
+ thread->stats.FinishedOps(nullptr, db_.db, 1, kOthers);
+ transactions_done++;
+ }
+
+ char msg[100];
+ if (FLAGS_optimistic_transaction_db || FLAGS_transaction_db) {
+ snprintf(msg, sizeof(msg),
+ "( transactions:%" PRIu64 " aborts:%" PRIu64 ")",
+ transactions_done, inserter.GetFailureCount());
+ } else {
+ snprintf(msg, sizeof(msg), "( batches:%" PRIu64 " )", transactions_done);
+ }
+ thread->stats.AddMessage(msg);
+
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ thread->stats.AddBytes(static_cast<int64_t>(inserter.GetBytesInserted()));
+ }
+
+ // Verifies consistency of data after RandomTransaction() has been run.
+ // Since each iteration of RandomTransaction() incremented a key in each set
+ // by the same value, the sum of the keys in each set should be the same.
+ void RandomTransactionVerify() {
+ if (!FLAGS_transaction_db && !FLAGS_optimistic_transaction_db) {
+ // transactions not used, nothing to verify.
+ return;
+ }
+
+ Status s =
+ RandomTransactionInserter::Verify(db_.db,
+ static_cast<uint16_t>(FLAGS_transaction_sets));
+
+ if (s.ok()) {
+ fprintf(stdout, "RandomTransactionVerify Success.\n");
+ } else {
+ fprintf(stdout, "RandomTransactionVerify FAILED!!\n");
+ }
+ }
+#endif // ROCKSDB_LITE
+
+ // Writes and deletes random keys without overwriting keys.
+ //
+ // This benchmark is intended to partially replicate the behavior of MyRocks
+ // secondary indices: All data is stored in keys and updates happen by
+ // deleting the old version of the key and inserting the new version.
+ void RandomReplaceKeys(ThreadState* thread) {
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+ std::vector<uint32_t> counters(FLAGS_numdistinct, 0);
+ size_t max_counter = 50;
+ RandomGenerator gen;
+
+ Status s;
+ DB* db = SelectDB(thread);
+ for (int64_t i = 0; i < FLAGS_numdistinct; i++) {
+ GenerateKeyFromInt(i * max_counter, FLAGS_num, &key);
+ s = db->Put(write_options_, key, gen.Generate());
+ if (!s.ok()) {
+ fprintf(stderr, "Operation failed: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ }
+
+ db->GetSnapshot();
+
+ std::default_random_engine generator;
+ std::normal_distribution<double> distribution(FLAGS_numdistinct / 2.0,
+ FLAGS_stddev);
+ Duration duration(FLAGS_duration, FLAGS_num);
+ while (!duration.Done(1)) {
+ int64_t rnd_id = static_cast<int64_t>(distribution(generator));
+ int64_t key_id = std::max(std::min(FLAGS_numdistinct - 1, rnd_id),
+ static_cast<int64_t>(0));
+ GenerateKeyFromInt(key_id * max_counter + counters[key_id], FLAGS_num,
+ &key);
+ s = FLAGS_use_single_deletes ? db->SingleDelete(write_options_, key)
+ : db->Delete(write_options_, key);
+ if (s.ok()) {
+ counters[key_id] = (counters[key_id] + 1) % max_counter;
+ GenerateKeyFromInt(key_id * max_counter + counters[key_id], FLAGS_num,
+ &key);
+ s = db->Put(write_options_, key, Slice());
+ }
+
+ if (!s.ok()) {
+ fprintf(stderr, "Operation failed: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+
+ thread->stats.FinishedOps(nullptr, db, 1, kOthers);
+ }
+
+ char msg[200];
+ snprintf(msg, sizeof(msg),
+ "use single deletes: %d, "
+ "standard deviation: %lf\n",
+ FLAGS_use_single_deletes, FLAGS_stddev);
+ thread->stats.AddMessage(msg);
+ }
+
+ void TimeSeriesReadOrDelete(ThreadState* thread, bool do_deletion) {
+ ReadOptions options(FLAGS_verify_checksum, true);
+ int64_t read = 0;
+ int64_t found = 0;
+ int64_t bytes = 0;
+
+ Iterator* iter = nullptr;
+ // Only work on single database
+ assert(db_.db != nullptr);
+ iter = db_.db->NewIterator(options);
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ char value_buffer[256];
+ while (true) {
+ {
+ MutexLock l(&thread->shared->mu);
+ if (thread->shared->num_done >= 1) {
+ // Write thread have finished
+ break;
+ }
+ }
+ if (!FLAGS_use_tailing_iterator) {
+ delete iter;
+ iter = db_.db->NewIterator(options);
+ }
+ // Pick a Iterator to use
+
+ int64_t key_id = thread->rand.Next() % FLAGS_key_id_range;
+ GenerateKeyFromInt(key_id, FLAGS_num, &key);
+ // Reset last 8 bytes to 0
+ char* start = const_cast<char*>(key.data());
+ start += key.size() - 8;
+ memset(start, 0, 8);
+ ++read;
+
+ bool key_found = false;
+ // Seek the prefix
+ for (iter->Seek(key); iter->Valid() && iter->key().starts_with(key);
+ iter->Next()) {
+ key_found = true;
+ // Copy out iterator's value to make sure we read them.
+ if (do_deletion) {
+ bytes += iter->key().size();
+ if (KeyExpired(timestamp_emulator_.get(), iter->key())) {
+ thread->stats.FinishedOps(&db_, db_.db, 1, kDelete);
+ db_.db->Delete(write_options_, iter->key());
+ } else {
+ break;
+ }
+ } else {
+ bytes += iter->key().size() + iter->value().size();
+ thread->stats.FinishedOps(&db_, db_.db, 1, kRead);
+ Slice value = iter->value();
+ memcpy(value_buffer, value.data(),
+ std::min(value.size(), sizeof(value_buffer)));
+
+ assert(iter->status().ok());
+ }
+ }
+ found += key_found;
+
+ if (thread->shared->read_rate_limiter.get() != nullptr) {
+ thread->shared->read_rate_limiter->Request(
+ 1, Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kRead);
+ }
+ }
+ delete iter;
+
+ char msg[100];
+ snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)", found,
+ read);
+ thread->stats.AddBytes(bytes);
+ thread->stats.AddMessage(msg);
+ if (FLAGS_perf_level > ROCKSDB_NAMESPACE::PerfLevel::kDisable) {
+ thread->stats.AddMessage(std::string("PERF_CONTEXT:\n") +
+ get_perf_context()->ToString());
+ }
+ }
+
+ void TimeSeriesWrite(ThreadState* thread) {
+ // Special thread that keeps writing until other threads are done.
+ RandomGenerator gen;
+ int64_t bytes = 0;
+
+ // Don't merge stats from this thread with the readers.
+ thread->stats.SetExcludeFromMerge();
+
+ std::unique_ptr<RateLimiter> write_rate_limiter;
+ if (FLAGS_benchmark_write_rate_limit > 0) {
+ write_rate_limiter.reset(
+ NewGenericRateLimiter(FLAGS_benchmark_write_rate_limit));
+ }
+
+ std::unique_ptr<const char[]> key_guard;
+ Slice key = AllocateKey(&key_guard);
+
+ Duration duration(FLAGS_duration, writes_);
+ while (!duration.Done(1)) {
+ DB* db = SelectDB(thread);
+
+ uint64_t key_id = thread->rand.Next() % FLAGS_key_id_range;
+ // Write key id
+ GenerateKeyFromInt(key_id, FLAGS_num, &key);
+ // Write timestamp
+
+ char* start = const_cast<char*>(key.data());
+ char* pos = start + 8;
+ int bytes_to_fill =
+ std::min(key_size_ - static_cast<int>(pos - start), 8);
+ uint64_t timestamp_value = timestamp_emulator_->Get();
+ if (port::kLittleEndian) {
+ for (int i = 0; i < bytes_to_fill; ++i) {
+ pos[i] = (timestamp_value >> ((bytes_to_fill - i - 1) << 3)) & 0xFF;
+ }
+ } else {
+ memcpy(pos, static_cast<void*>(&timestamp_value), bytes_to_fill);
+ }
+
+ timestamp_emulator_->Inc();
+
+ Status s;
+ Slice val = gen.Generate();
+ s = db->Put(write_options_, key, val);
+
+ if (!s.ok()) {
+ fprintf(stderr, "put error: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ bytes = key.size() + val.size();
+ thread->stats.FinishedOps(&db_, db_.db, 1, kWrite);
+ thread->stats.AddBytes(bytes);
+
+ if (FLAGS_benchmark_write_rate_limit > 0) {
+ write_rate_limiter->Request(
+ key.size() + val.size(), Env::IO_HIGH,
+ nullptr /* stats */, RateLimiter::OpType::kWrite);
+ }
+ }
+ }
+
+ void TimeSeries(ThreadState* thread) {
+ if (thread->tid > 0) {
+ bool do_deletion = FLAGS_expire_style == "delete" &&
+ thread->tid <= FLAGS_num_deletion_threads;
+ TimeSeriesReadOrDelete(thread, do_deletion);
+ } else {
+ TimeSeriesWrite(thread);
+ thread->stats.Stop();
+ thread->stats.Report("timeseries write");
+ }
+ }
+
+ void Compact(ThreadState* thread) {
+ DB* db = SelectDB(thread);
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction =
+ BottommostLevelCompaction::kForceOptimized;
+ db->CompactRange(cro, nullptr, nullptr);
+ }
+
+ void CompactAll() {
+ if (db_.db != nullptr) {
+ db_.db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ }
+ for (const auto& db_with_cfh : multi_dbs_) {
+ db_with_cfh.db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ }
+ }
+
+ void ResetStats() {
+ if (db_.db != nullptr) {
+ db_.db->ResetStats();
+ }
+ for (const auto& db_with_cfh : multi_dbs_) {
+ db_with_cfh.db->ResetStats();
+ }
+ }
+
+ void PrintStatsHistory() {
+ if (db_.db != nullptr) {
+ PrintStatsHistoryImpl(db_.db, false);
+ }
+ for (const auto& db_with_cfh : multi_dbs_) {
+ PrintStatsHistoryImpl(db_with_cfh.db, true);
+ }
+ }
+
+ void PrintStatsHistoryImpl(DB* db, bool print_header) {
+ if (print_header) {
+ fprintf(stdout, "\n==== DB: %s ===\n", db->GetName().c_str());
+ }
+
+ std::unique_ptr<StatsHistoryIterator> shi;
+ Status s = db->GetStatsHistory(0, port::kMaxUint64, &shi);
+ if (!s.ok()) {
+ fprintf(stdout, "%s\n", s.ToString().c_str());
+ return;
+ }
+ assert(shi);
+ while (shi->Valid()) {
+ uint64_t stats_time = shi->GetStatsTime();
+ fprintf(stdout, "------ %s ------\n",
+ TimeToHumanString(static_cast<int>(stats_time)).c_str());
+ for (auto& entry : shi->GetStatsMap()) {
+ fprintf(stdout, " %" PRIu64 " %s %" PRIu64 "\n", stats_time,
+ entry.first.c_str(), entry.second);
+ }
+ shi->Next();
+ }
+ }
+
+ void PrintStats(const char* key) {
+ if (db_.db != nullptr) {
+ PrintStats(db_.db, key, false);
+ }
+ for (const auto& db_with_cfh : multi_dbs_) {
+ PrintStats(db_with_cfh.db, key, true);
+ }
+ }
+
+ void PrintStats(DB* db, const char* key, bool print_header = false) {
+ if (print_header) {
+ fprintf(stdout, "\n==== DB: %s ===\n", db->GetName().c_str());
+ }
+ std::string stats;
+ if (!db->GetProperty(key, &stats)) {
+ stats = "(failed)";
+ }
+ fprintf(stdout, "\n%s\n", stats.c_str());
+ }
+
+ void Replay(ThreadState* thread) {
+ if (db_.db != nullptr) {
+ Replay(thread, &db_);
+ }
+ }
+
+ void Replay(ThreadState* /*thread*/, DBWithColumnFamilies* db_with_cfh) {
+ Status s;
+ std::unique_ptr<TraceReader> trace_reader;
+ s = NewFileTraceReader(FLAGS_env, EnvOptions(), FLAGS_trace_file,
+ &trace_reader);
+ if (!s.ok()) {
+ fprintf(
+ stderr,
+ "Encountered an error creating a TraceReader from the trace file. "
+ "Error: %s\n",
+ s.ToString().c_str());
+ exit(1);
+ }
+ Replayer replayer(db_with_cfh->db, db_with_cfh->cfh,
+ std::move(trace_reader));
+ replayer.SetFastForward(
+ static_cast<uint32_t>(FLAGS_trace_replay_fast_forward));
+ s = replayer.MultiThreadReplay(
+ static_cast<uint32_t>(FLAGS_trace_replay_threads));
+ if (s.ok()) {
+ fprintf(stdout, "Replay started from trace_file: %s\n",
+ FLAGS_trace_file.c_str());
+ } else {
+ fprintf(stderr, "Starting replay failed. Error: %s\n",
+ s.ToString().c_str());
+ }
+ }
+};
+
+int db_bench_tool(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ static bool initialized = false;
+ if (!initialized) {
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [OPTIONS]...");
+ initialized = true;
+ }
+ ParseCommandLineFlags(&argc, &argv, true);
+ FLAGS_compaction_style_e =
+ (ROCKSDB_NAMESPACE::CompactionStyle)FLAGS_compaction_style;
+#ifndef ROCKSDB_LITE
+ if (FLAGS_statistics && !FLAGS_statistics_string.empty()) {
+ fprintf(stderr,
+ "Cannot provide both --statistics and --statistics_string.\n");
+ exit(1);
+ }
+ if (!FLAGS_statistics_string.empty()) {
+ Status s = ObjectRegistry::NewInstance()->NewSharedObject<Statistics>(
+ FLAGS_statistics_string, &dbstats);
+ if (dbstats == nullptr) {
+ fprintf(stderr,
+ "No Statistics registered matching string: %s status=%s\n",
+ FLAGS_statistics_string.c_str(), s.ToString().c_str());
+ exit(1);
+ }
+ }
+#endif // ROCKSDB_LITE
+ if (FLAGS_statistics) {
+ dbstats = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ }
+ if (dbstats) {
+ dbstats->set_stats_level(static_cast<StatsLevel>(FLAGS_stats_level));
+ }
+ FLAGS_compaction_pri_e =
+ (ROCKSDB_NAMESPACE::CompactionPri)FLAGS_compaction_pri;
+
+ std::vector<std::string> fanout = ROCKSDB_NAMESPACE::StringSplit(
+ FLAGS_max_bytes_for_level_multiplier_additional, ',');
+ for (size_t j = 0; j < fanout.size(); j++) {
+ FLAGS_max_bytes_for_level_multiplier_additional_v.push_back(
+#ifndef CYGWIN
+ std::stoi(fanout[j]));
+#else
+ stoi(fanout[j]));
+#endif
+ }
+
+ FLAGS_compression_type_e =
+ StringToCompressionType(FLAGS_compression_type.c_str());
+
+#ifndef ROCKSDB_LITE
+ FLAGS_blob_db_compression_type_e =
+ StringToCompressionType(FLAGS_blob_db_compression_type.c_str());
+
+ if (!FLAGS_hdfs.empty() && !FLAGS_env_uri.empty()) {
+ fprintf(stderr, "Cannot provide both --hdfs and --env_uri.\n");
+ exit(1);
+ } else if (!FLAGS_env_uri.empty()) {
+ Status s = Env::LoadEnv(FLAGS_env_uri, &FLAGS_env, &env_guard);
+ if (FLAGS_env == nullptr) {
+ fprintf(stderr, "No Env registered for URI: %s\n", FLAGS_env_uri.c_str());
+ exit(1);
+ }
+ }
+#endif // ROCKSDB_LITE
+ if (FLAGS_use_existing_keys && !FLAGS_use_existing_db) {
+ fprintf(stderr,
+ "`-use_existing_db` must be true for `-use_existing_keys` to be "
+ "settable\n");
+ exit(1);
+ }
+
+ if (!FLAGS_hdfs.empty()) {
+ FLAGS_env = new ROCKSDB_NAMESPACE::HdfsEnv(FLAGS_hdfs);
+ }
+
+ if (!strcasecmp(FLAGS_compaction_fadvice.c_str(), "NONE"))
+ FLAGS_compaction_fadvice_e = ROCKSDB_NAMESPACE::Options::NONE;
+ else if (!strcasecmp(FLAGS_compaction_fadvice.c_str(), "NORMAL"))
+ FLAGS_compaction_fadvice_e = ROCKSDB_NAMESPACE::Options::NORMAL;
+ else if (!strcasecmp(FLAGS_compaction_fadvice.c_str(), "SEQUENTIAL"))
+ FLAGS_compaction_fadvice_e = ROCKSDB_NAMESPACE::Options::SEQUENTIAL;
+ else if (!strcasecmp(FLAGS_compaction_fadvice.c_str(), "WILLNEED"))
+ FLAGS_compaction_fadvice_e = ROCKSDB_NAMESPACE::Options::WILLNEED;
+ else {
+ fprintf(stdout, "Unknown compaction fadvice:%s\n",
+ FLAGS_compaction_fadvice.c_str());
+ }
+
+ FLAGS_value_size_distribution_type_e =
+ StringToDistributionType(FLAGS_value_size_distribution_type.c_str());
+
+ FLAGS_rep_factory = StringToRepFactory(FLAGS_memtablerep.c_str());
+
+ // Note options sanitization may increase thread pool sizes according to
+ // max_background_flushes/max_background_compactions/max_background_jobs
+ FLAGS_env->SetBackgroundThreads(FLAGS_num_high_pri_threads,
+ ROCKSDB_NAMESPACE::Env::Priority::HIGH);
+ FLAGS_env->SetBackgroundThreads(FLAGS_num_bottom_pri_threads,
+ ROCKSDB_NAMESPACE::Env::Priority::BOTTOM);
+ FLAGS_env->SetBackgroundThreads(FLAGS_num_low_pri_threads,
+ ROCKSDB_NAMESPACE::Env::Priority::LOW);
+
+ // Choose a location for the test database if none given with --db=<path>
+ if (FLAGS_db.empty()) {
+ std::string default_db_path;
+ FLAGS_env->GetTestDirectory(&default_db_path);
+ default_db_path += "/dbbench";
+ FLAGS_db = default_db_path;
+ }
+
+ if (FLAGS_stats_interval_seconds > 0) {
+ // When both are set then FLAGS_stats_interval determines the frequency
+ // at which the timer is checked for FLAGS_stats_interval_seconds
+ FLAGS_stats_interval = 1000;
+ }
+
+ if (FLAGS_seek_missing_prefix && FLAGS_prefix_size <= 8) {
+ fprintf(stderr, "prefix_size > 8 required by --seek_missing_prefix\n");
+ exit(1);
+ }
+
+ ROCKSDB_NAMESPACE::Benchmark benchmark;
+ benchmark.Run();
+
+#ifndef ROCKSDB_LITE
+ if (FLAGS_print_malloc_stats) {
+ std::string stats_string;
+ ROCKSDB_NAMESPACE::DumpMallocStats(&stats_string);
+ fprintf(stdout, "Malloc stats:\n%s\n", stats_string.c_str());
+ }
+#endif // ROCKSDB_LITE
+
+ return 0;
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/tools/db_bench_tool_test.cc b/storage/rocksdb/rocksdb/tools/db_bench_tool_test.cc
new file mode 100644
index 00000000..821c602b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_bench_tool_test.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "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<std::string>& 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;
+ }
+ }
+
+ void RunDbBench(const std::string& options_file_name) {
+ AppendArgs({"./db_bench", "--benchmarks=fillseq", "--use_existing_db=0",
+ "--num=1000",
+ 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;
+ std::vector<ColumnFamilyDescriptor> cf_descs;
+ ASSERT_OK(LoadLatestOptions(db_path_, FileSystem::Default(),
+ &loaded_db_opts, &cf_descs));
+
+ ASSERT_OK(
+ RocksDBOptionsParser::VerifyDBOptions(DBOptions(opt), loaded_db_opts));
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(ColumnFamilyOptions(opt),
+ cf_descs[0].options));
+
+ // check with the default rocksdb options and expect failure
+ ASSERT_NOK(
+ RocksDBOptionsParser::VerifyDBOptions(DBOptions(), loaded_db_opts));
+ ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions(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;
+ opt.create_if_missing = true;
+ opt.max_open_files = 256;
+ opt.max_background_compactions = 10;
+ opt.arena_block_size = 8388608;
+ ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"},
+ {ColumnFamilyOptions(opt)}, kOptionsFileName,
+ Env::Default()));
+
+ // 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, OptionsFileUniversal) {
+ const std::string kOptionsFileName = test_path_ + "/OPTIONS_test";
+
+ Options opt;
+ opt.compaction_style = kCompactionStyleUniversal;
+ opt.num_levels = 1;
+ opt.create_if_missing = true;
+ opt.max_open_files = 256;
+ opt.max_background_compactions = 10;
+ opt.arena_block_size = 8388608;
+ ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"},
+ {ColumnFamilyOptions(opt)}, kOptionsFileName,
+ Env::Default()));
+
+ // 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;
+ opt.compaction_style = kCompactionStyleUniversal;
+ opt.num_levels = 12;
+ opt.create_if_missing = true;
+ opt.max_open_files = 256;
+ opt.max_background_compactions = 10;
+ opt.arena_block_size = 8388608;
+ ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"},
+ {ColumnFamilyOptions(opt)}, kOptionsFileName,
+ Env::Default()));
+
+ // 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
+ new_table_reader_for_compaction_inputs=false
+ keep_log_file_num=10
+ skip_stats_update_on_db_open=false
+ max_manifest_file_size=18446744073709551615
+ db_log_dir=
+ skip_log_error_on_recovery=false
+ 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
+
+
+[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=kSnappyCompression
+ 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
+ soft_rate_limit=0.000000
+ 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
+ purge_redundant_kvs_while_flush=true
+ filter_deletes=false
+ hard_pending_compaction_bytes_limit=0
+ disable_auto_compactions=false
+ compaction_measure_io_stats=false
+
+[TableOptions/BlockBasedTable "default"]
+ format_version=0
+ skip_table_builder_flush=false
+ cache_index_and_filter_blocks=false
+ flush_block_policy_factory=FlushBlockBySizePolicyFactory
+ hash_index_allow_collision=true
+ 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<WritableFile> writable;
+ ASSERT_OK(Env::Default()->NewWritableFile(kOptionsFileName, &writable,
+ EnvOptions()));
+ ASSERT_OK(writable->Append(options_file_content));
+ ASSERT_OK(writable->Close());
+
+ DBOptions db_opt;
+ std::vector<ColumnFamilyDescriptor> cf_descs;
+ ASSERT_OK(LoadOptionsFromFile(kOptionsFileName, Env::Default(), &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(opt);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ google::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/storage/rocksdb/rocksdb/tools/db_crashtest.py b/storage/rocksdb/rocksdb/tools/db_crashtest.py
new file mode 100644
index 00000000..bf690b1e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_crashtest.py
@@ -0,0 +1,499 @@
+#!/usr/bin/env python2
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+import os
+import sys
+import time
+import random
+import tempfile
+import subprocess
+import shutil
+import argparse
+
+# params overwrite priority:
+# for default:
+# default_params < {blackbox,whitebox}_default_params < args
+# for simple:
+# default_params < {blackbox,whitebox}_default_params <
+# simple_default_params <
+# {blackbox,whitebox}_simple_default_params < args
+# for cf_consistency:
+# default_params < {blackbox,whitebox}_default_params <
+# cf_consistency_params < args
+# for txn:
+# default_params < {blackbox,whitebox}_default_params < txn_params < args
+
+expected_values_file = tempfile.NamedTemporaryFile()
+
+default_params = {
+ "acquire_snapshot_one_in": 10000,
+ "block_size": 16384,
+ "bloom_bits": lambda: random.choice([random.randint(0,19),
+ random.lognormvariate(2.3, 1.3)]),
+ "cache_index_and_filter_blocks": lambda: random.randint(0, 1),
+ "cache_size": 1048576,
+ "checkpoint_one_in": 1000000,
+ "compression_type": lambda: random.choice(
+ ["none", "snappy", "zlib", "bzip2", "lz4", "lz4hc", "xpress", "zstd"]),
+ "bottommost_compression_type": lambda:
+ "disable" if random.randint(0, 1) == 0 else
+ random.choice(
+ ["none", "snappy", "zlib", "bzip2", "lz4", "lz4hc", "xpress",
+ "zstd"]),
+ "checksum_type" : lambda: random.choice(["kCRC32c", "kxxHash", "kxxHash64"]),
+ "compression_max_dict_bytes": lambda: 16384 * random.randint(0, 1),
+ "compression_zstd_max_train_bytes": lambda: 65536 * random.randint(0, 1),
+ "clear_column_family_one_in": 0,
+ "compact_files_one_in": 1000000,
+ "compact_range_one_in": 1000000,
+ "delpercent": 4,
+ "delrangepercent": 1,
+ "destroy_db_initially": 0,
+ "enable_pipelined_write": lambda: random.randint(0, 1),
+ "expected_values_path": expected_values_file.name,
+ "flush_one_in": 1000000,
+ "get_live_files_and_wal_files_one_in": 1000000,
+ # Temporarily disable hash index
+ "index_type": lambda: random.choice([0,2]),
+ "max_background_compactions": 20,
+ "max_bytes_for_level_base": 10485760,
+ "max_key": 100000000,
+ "max_write_buffer_number": 3,
+ "mmap_read": lambda: random.randint(0, 1),
+ "nooverwritepercent": 1,
+ "open_files": lambda : random.choice([-1, 500000]),
+ "partition_filters": lambda: random.randint(0, 1),
+ "pause_background_one_in": 1000000,
+ "prefixpercent": 5,
+ "progress_reports": 0,
+ "readpercent": 45,
+ "recycle_log_file_num": lambda: random.randint(0, 1),
+ "reopen": 20,
+ "snapshot_hold_ops": 100000,
+ "long_running_snapshots": lambda: random.randint(0, 1),
+ "subcompactions": lambda: random.randint(1, 4),
+ "target_file_size_base": 2097152,
+ "target_file_size_multiplier": 2,
+ "use_direct_reads": lambda: random.randint(0, 1),
+ "use_direct_io_for_flush_and_compaction": lambda: random.randint(0, 1),
+ "use_full_merge_v1": lambda: random.randint(0, 1),
+ "use_merge": lambda: random.randint(0, 1),
+ "verify_checksum": 1,
+ "write_buffer_size": 4 * 1024 * 1024,
+ "writepercent": 35,
+ "format_version": lambda: random.choice([2, 3, 4, 5, 5]),
+ "index_block_restart_interval": lambda: random.choice(range(1, 16)),
+ "use_multiget" : lambda: random.randint(0, 1),
+ "periodic_compaction_seconds" :
+ lambda: random.choice([0, 0, 1, 2, 10, 100, 1000]),
+ "compaction_ttl" : lambda: random.choice([0, 0, 1, 2, 10, 100, 1000]),
+ # Test small max_manifest_file_size in a smaller chance, as most of the
+ # time we wnat manifest history to be preserved to help debug
+ "max_manifest_file_size" : lambda : random.choice(
+ [t * 16384 if t < 3 else 1024 * 1024 * 1024 for t in range(1, 30)]),
+ # Sync mode might make test runs slower so running it in a smaller chance
+ "sync" : lambda : random.choice(
+ [1 if t == 0 else 0 for t in range(0, 20)]),
+ # Disable compation_readahead_size because the test is not passing.
+ #"compaction_readahead_size" : lambda : random.choice(
+ # [0, 0, 1024 * 1024]),
+ "db_write_buffer_size" : lambda: random.choice(
+ [0, 0, 0, 1024 * 1024, 8 * 1024 * 1024, 128 * 1024 * 1024]),
+ "avoid_unnecessary_blocking_io" : random.randint(0, 1),
+ "write_dbid_to_manifest" : random.randint(0, 1),
+ "max_write_batch_group_size_bytes" : lambda: random.choice(
+ [16, 64, 1024 * 1024, 16 * 1024 * 1024]),
+ "level_compaction_dynamic_level_bytes" : True,
+ "verify_checksum_one_in": 1000000,
+ "verify_db_one_in": 100000,
+ "continuous_verification_interval" : 0,
+ "max_key_len": 3,
+ "key_len_percent_dist": "1,30,69"
+}
+
+_TEST_DIR_ENV_VAR = 'TEST_TMPDIR'
+
+
+def get_dbname(test_name):
+ test_tmpdir = os.environ.get(_TEST_DIR_ENV_VAR)
+ if test_tmpdir is None or test_tmpdir == "":
+ dbname = tempfile.mkdtemp(prefix='rocksdb_crashtest_' + test_name)
+ else:
+ dbname = test_tmpdir + "/rocksdb_crashtest_" + test_name
+ shutil.rmtree(dbname, True)
+ os.mkdir(dbname)
+ return dbname
+
+
+def is_direct_io_supported(dbname):
+ with tempfile.NamedTemporaryFile(dir=dbname) as f:
+ try:
+ os.open(f.name, os.O_DIRECT)
+ except:
+ return False
+ return True
+
+
+blackbox_default_params = {
+ # total time for this script to test db_stress
+ "duration": 6000,
+ # time for one db_stress instance to run
+ "interval": 120,
+ # since we will be killing anyway, use large value for ops_per_thread
+ "ops_per_thread": 100000000,
+ "set_options_one_in": 10000,
+ "test_batches_snapshots": 1,
+}
+
+whitebox_default_params = {
+ "duration": 10000,
+ "log2_keys_per_lock": 10,
+ "ops_per_thread": 200000,
+ "random_kill_odd": 888887,
+ "test_batches_snapshots": lambda: random.randint(0, 1),
+}
+
+simple_default_params = {
+ "allow_concurrent_memtable_write": lambda: random.randint(0, 1),
+ "column_families": 1,
+ "max_background_compactions": 1,
+ "max_bytes_for_level_base": 67108864,
+ "memtablerep": "skip_list",
+ "prefixpercent": 0,
+ "readpercent": 50,
+ "prefix_size" : -1,
+ "target_file_size_base": 16777216,
+ "target_file_size_multiplier": 1,
+ "test_batches_snapshots": 0,
+ "write_buffer_size": 32 * 1024 * 1024,
+ "level_compaction_dynamic_level_bytes": False,
+}
+
+blackbox_simple_default_params = {
+ "open_files": -1,
+ "set_options_one_in": 0,
+}
+
+whitebox_simple_default_params = {}
+
+cf_consistency_params = {
+ "disable_wal": lambda: random.randint(0, 1),
+ "reopen": 0,
+ "test_cf_consistency": 1,
+ # use small value for write_buffer_size so that RocksDB triggers flush
+ # more frequently
+ "write_buffer_size": 1024 * 1024,
+ "enable_pipelined_write": lambda: random.randint(0, 1),
+}
+
+txn_params = {
+ "use_txn" : 1,
+ # Avoid lambda to set it once for the entire test
+ "txn_write_policy": random.randint(0, 2),
+ "unordered_write": random.randint(0, 1),
+ "disable_wal": 0,
+ # OpenReadOnly after checkpoint is not currnetly compatible with WritePrepared txns
+ "checkpoint_one_in": 0,
+ # pipeline write is not currnetly compatible with WritePrepared txns
+ "enable_pipelined_write": 0,
+}
+
+def finalize_and_sanitize(src_params):
+ dest_params = dict([(k, v() if callable(v) else v)
+ for (k, v) in src_params.items()])
+ if dest_params.get("compression_type") != "zstd" or \
+ dest_params.get("compression_max_dict_bytes") == 0:
+ dest_params["compression_zstd_max_train_bytes"] = 0
+ if dest_params.get("allow_concurrent_memtable_write", 1) == 1:
+ dest_params["memtablerep"] = "skip_list"
+ if dest_params["mmap_read"] == 1 or not is_direct_io_supported(
+ dest_params["db"]):
+ dest_params["use_direct_io_for_flush_and_compaction"] = 0
+ dest_params["use_direct_reads"] = 0
+ # DeleteRange is not currnetly compatible with Txns
+ if dest_params.get("test_batches_snapshots") == 1 or \
+ dest_params.get("use_txn") == 1:
+ dest_params["delpercent"] += dest_params["delrangepercent"]
+ dest_params["delrangepercent"] = 0
+ # Only under WritePrepared txns, unordered_write would provide the same guarnatees as vanilla rocksdb
+ if dest_params.get("unordered_write", 0) == 1:
+ dest_params["txn_write_policy"] = 1
+ dest_params["allow_concurrent_memtable_write"] = 1
+ if dest_params.get("disable_wal", 0) == 1:
+ dest_params["atomic_flush"] = 1
+ dest_params["sync"] = 0
+ if dest_params.get("open_files", 1) != -1:
+ # Compaction TTL and periodic compactions are only compatible
+ # with open_files = -1
+ dest_params["compaction_ttl"] = 0
+ dest_params["periodic_compaction_seconds"] = 0
+ if dest_params.get("compaction_style", 0) == 2:
+ # Disable compaction TTL in FIFO compaction, because right
+ # now assertion failures are triggered.
+ dest_params["compaction_ttl"] = 0
+ dest_params["periodic_compaction_seconds"] = 0
+ if dest_params["partition_filters"] == 1:
+ if dest_params["index_type"] != 2:
+ dest_params["partition_filters"] = 0
+ else:
+ dest_params["use_block_based_filter"] = 0
+ if dest_params.get("atomic_flush", 0) == 1:
+ # disable pipelined write when atomic flush is used.
+ dest_params["enable_pipelined_write"] = 0
+ return dest_params
+
+def gen_cmd_params(args):
+ params = {}
+
+ params.update(default_params)
+ if args.test_type == 'blackbox':
+ params.update(blackbox_default_params)
+ if args.test_type == 'whitebox':
+ params.update(whitebox_default_params)
+ if args.simple:
+ params.update(simple_default_params)
+ if args.test_type == 'blackbox':
+ params.update(blackbox_simple_default_params)
+ if args.test_type == 'whitebox':
+ params.update(whitebox_simple_default_params)
+ if args.cf_consistency:
+ params.update(cf_consistency_params)
+ if args.txn:
+ params.update(txn_params)
+
+ for k, v in vars(args).items():
+ if v is not None:
+ params[k] = v
+ return params
+
+
+def gen_cmd(params, unknown_params):
+ finalzied_params = finalize_and_sanitize(params)
+ cmd = ['./db_stress'] + [
+ '--{0}={1}'.format(k, v)
+ for k, v in [(k, finalzied_params[k]) for k in sorted(finalzied_params)]
+ if k not in set(['test_type', 'simple', 'duration', 'interval',
+ 'random_kill_odd', 'cf_consistency', 'txn'])
+ and v is not None] + unknown_params
+ return cmd
+
+
+# This script runs and kills db_stress multiple times. It checks consistency
+# in case of unsafe crashes in RocksDB.
+def blackbox_crash_main(args, unknown_args):
+ cmd_params = gen_cmd_params(args)
+ dbname = get_dbname('blackbox')
+ exit_time = time.time() + cmd_params['duration']
+
+ print("Running blackbox-crash-test with \n"
+ + "interval_between_crash=" + str(cmd_params['interval']) + "\n"
+ + "total-duration=" + str(cmd_params['duration']) + "\n")
+
+ while time.time() < exit_time:
+ run_had_errors = False
+ killtime = time.time() + cmd_params['interval']
+
+ cmd = gen_cmd(dict(
+ cmd_params.items() +
+ {'db': dbname}.items()), unknown_args)
+
+ child = subprocess.Popen(cmd, stderr=subprocess.PIPE)
+ print("Running db_stress with pid=%d: %s\n\n"
+ % (child.pid, ' '.join(cmd)))
+
+ stop_early = False
+ while time.time() < killtime:
+ if child.poll() is not None:
+ print("WARNING: db_stress ended before kill: exitcode=%d\n"
+ % child.returncode)
+ stop_early = True
+ break
+ time.sleep(1)
+
+ if not stop_early:
+ if child.poll() is not None:
+ print("WARNING: db_stress ended before kill: exitcode=%d\n"
+ % child.returncode)
+ else:
+ child.kill()
+ print("KILLED %d\n" % child.pid)
+ time.sleep(1) # time to stabilize after a kill
+
+ while True:
+ line = child.stderr.readline().strip()
+ if line == '':
+ break
+ elif not line.startswith('WARNING'):
+ run_had_errors = True
+ print('stderr has error message:')
+ print('***' + line + '***')
+
+ if run_had_errors:
+ sys.exit(2)
+
+ time.sleep(1) # time to stabilize before the next run
+
+ # we need to clean up after ourselves -- only do this on test success
+ shutil.rmtree(dbname, True)
+
+
+# This python script runs db_stress multiple times. Some runs with
+# kill_random_test that causes rocksdb to crash at various points in code.
+def whitebox_crash_main(args, unknown_args):
+ cmd_params = gen_cmd_params(args)
+ dbname = get_dbname('whitebox')
+
+ cur_time = time.time()
+ exit_time = cur_time + cmd_params['duration']
+ half_time = cur_time + cmd_params['duration'] / 2
+
+ print("Running whitebox-crash-test with \n"
+ + "total-duration=" + str(cmd_params['duration']) + "\n")
+
+ total_check_mode = 4
+ check_mode = 0
+ kill_random_test = cmd_params['random_kill_odd']
+ kill_mode = 0
+
+ while time.time() < exit_time:
+ if check_mode == 0:
+ additional_opts = {
+ # use large ops per thread since we will kill it anyway
+ "ops_per_thread": 100 * cmd_params['ops_per_thread'],
+ }
+ # run with kill_random_test, with three modes.
+ # Mode 0 covers all kill points. Mode 1 covers less kill points but
+ # increases change of triggering them. Mode 2 covers even less
+ # frequent kill points and further increases triggering change.
+ if kill_mode == 0:
+ additional_opts.update({
+ "kill_random_test": kill_random_test,
+ })
+ elif kill_mode == 1:
+ if cmd_params.get('disable_wal', 0) == 1:
+ my_kill_odd = kill_random_test / 50 + 1
+ else:
+ my_kill_odd = kill_random_test / 10 + 1
+ additional_opts.update({
+ "kill_random_test": my_kill_odd,
+ "kill_prefix_blacklist": "WritableFileWriter::Append,"
+ + "WritableFileWriter::WriteBuffered",
+ })
+ elif kill_mode == 2:
+ # TODO: May need to adjust random odds if kill_random_test
+ # is too small.
+ additional_opts.update({
+ "kill_random_test": (kill_random_test / 5000 + 1),
+ "kill_prefix_blacklist": "WritableFileWriter::Append,"
+ "WritableFileWriter::WriteBuffered,"
+ "PosixMmapFile::Allocate,WritableFileWriter::Flush",
+ })
+ # Run kill mode 0, 1 and 2 by turn.
+ kill_mode = (kill_mode + 1) % 3
+ elif check_mode == 1:
+ # normal run with universal compaction mode
+ additional_opts = {
+ "kill_random_test": None,
+ "ops_per_thread": cmd_params['ops_per_thread'],
+ "compaction_style": 1,
+ }
+ elif check_mode == 2:
+ # normal run with FIFO compaction mode
+ # ops_per_thread is divided by 5 because FIFO compaction
+ # style is quite a bit slower on reads with lot of files
+ additional_opts = {
+ "kill_random_test": None,
+ "ops_per_thread": cmd_params['ops_per_thread'] / 5,
+ "compaction_style": 2,
+ }
+ else:
+ # normal run
+ additional_opts = {
+ "kill_random_test": None,
+ "ops_per_thread": cmd_params['ops_per_thread'],
+ }
+
+ cmd = gen_cmd(dict(cmd_params.items() + additional_opts.items()
+ + {'db': dbname}.items()), unknown_args)
+
+ print "Running:" + ' '.join(cmd) + "\n" # noqa: E999 T25377293 Grandfathered in
+
+ popen = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ stdoutdata, stderrdata = popen.communicate()
+ retncode = popen.returncode
+ msg = ("check_mode={0}, kill option={1}, exitcode={2}\n".format(
+ check_mode, additional_opts['kill_random_test'], retncode))
+ print msg
+ print stdoutdata
+
+ expected = False
+ if additional_opts['kill_random_test'] is None and (retncode == 0):
+ # we expect zero retncode if no kill option
+ expected = True
+ elif additional_opts['kill_random_test'] is not None and retncode <= 0:
+ # When kill option is given, the test MIGHT kill itself.
+ # If it does, negative retncode is expected. Otherwise 0.
+ expected = True
+
+ if not expected:
+ print "TEST FAILED. See kill option and exit code above!!!\n"
+ sys.exit(1)
+
+ stdoutdata = stdoutdata.lower()
+ errorcount = (stdoutdata.count('error') -
+ stdoutdata.count('got errors 0 times'))
+ print "#times error occurred in output is " + str(errorcount) + "\n"
+
+ if (errorcount > 0):
+ print "TEST FAILED. Output has 'error'!!!\n"
+ sys.exit(2)
+ if (stdoutdata.find('fail') >= 0):
+ print "TEST FAILED. Output has 'fail'!!!\n"
+ sys.exit(2)
+
+ # First half of the duration, keep doing kill test. For the next half,
+ # try different modes.
+ if time.time() > half_time:
+ # we need to clean up after ourselves -- only do this on test
+ # success
+ shutil.rmtree(dbname, True)
+ os.mkdir(dbname)
+ cmd_params.pop('expected_values_path', None)
+ check_mode = (check_mode + 1) % total_check_mode
+
+ time.sleep(1) # time to stabilize after a kill
+
+
+def main():
+ parser = argparse.ArgumentParser(description="This script runs and kills \
+ db_stress multiple times")
+ parser.add_argument("test_type", choices=["blackbox", "whitebox"])
+ parser.add_argument("--simple", action="store_true")
+ parser.add_argument("--cf_consistency", action='store_true')
+ parser.add_argument("--txn", action='store_true')
+
+ all_params = dict(default_params.items()
+ + blackbox_default_params.items()
+ + whitebox_default_params.items()
+ + simple_default_params.items()
+ + blackbox_simple_default_params.items()
+ + whitebox_simple_default_params.items())
+
+ for k, v in all_params.items():
+ parser.add_argument("--" + k, type=type(v() if callable(v) else v))
+ # unknown_args are passed directly to db_stress
+ args, unknown_args = parser.parse_known_args()
+
+ test_tmpdir = os.environ.get(_TEST_DIR_ENV_VAR)
+ if test_tmpdir is not None and not os.path.isdir(test_tmpdir):
+ print('%s env var is set to a non-existent directory: %s' %
+ (_TEST_DIR_ENV_VAR, test_tmpdir))
+ sys.exit(1)
+
+ if args.test_type == 'blackbox':
+ blackbox_crash_main(args, unknown_args)
+ if args.test_type == 'whitebox':
+ whitebox_crash_main(args, unknown_args)
+
+if __name__ == '__main__':
+ main()
diff --git a/storage/rocksdb/rocksdb/tools/db_repl_stress.cc b/storage/rocksdb/rocksdb/tools/db_repl_stress.cc
new file mode 100644
index 00000000..717f5d3d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_repl_stress.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include <atomic>
+#include <cstdio>
+
+#include "db/write_batch_internal.h"
+#include "rocksdb/db.h"
+#include "rocksdb/types.h"
+#include "test_util/testutil.h"
+#include "util/gflags_compat.h"
+
+// Run a thread to perform Put's.
+// Another thread uses GetUpdatesSince API to keep getting the updates.
+// options :
+// --num_inserts = the num of inserts the first thread should perform.
+// --wal_ttl = the wal ttl for the run.
+
+using namespace ROCKSDB_NAMESPACE;
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+struct DataPumpThread {
+ size_t no_records;
+ DB* db; // Assumption DB is Open'ed already.
+};
+
+static std::string RandomString(Random* rnd, int len) {
+ std::string r;
+ test::RandomString(rnd, len, &r);
+ return r;
+}
+
+static void DataPumpThreadBody(void* arg) {
+ DataPumpThread* t = reinterpret_cast<DataPumpThread*>(arg);
+ DB* db = t->db;
+ Random rnd(301);
+ size_t i = 0;
+ while (i++ < t->no_records) {
+ if (!db->Put(WriteOptions(), Slice(RandomString(&rnd, 500)),
+ Slice(RandomString(&rnd, 500)))
+ .ok()) {
+ fprintf(stderr, "Error in put\n");
+ exit(1);
+ }
+ }
+}
+
+struct ReplicationThread {
+ std::atomic<bool> stop;
+ DB* db;
+ volatile size_t no_read;
+};
+
+static void ReplicationThreadBody(void* arg) {
+ ReplicationThread* t = reinterpret_cast<ReplicationThread*>(arg);
+ DB* db = t->db;
+ std::unique_ptr<TransactionLogIterator> iter;
+ SequenceNumber currentSeqNum = 1;
+ while (!t->stop.load(std::memory_order_acquire)) {
+ iter.reset();
+ Status s;
+ while (!db->GetUpdatesSince(currentSeqNum, &iter).ok()) {
+ if (t->stop.load(std::memory_order_acquire)) {
+ return;
+ }
+ }
+ fprintf(stderr, "Refreshing iterator\n");
+ for (; iter->Valid(); iter->Next(), t->no_read++, currentSeqNum++) {
+ BatchResult res = iter->GetBatch();
+ if (res.sequence != currentSeqNum) {
+ fprintf(stderr, "Missed a seq no. b/w %ld and %ld\n",
+ (long)currentSeqNum, (long)res.sequence);
+ exit(1);
+ }
+ }
+ }
+}
+
+DEFINE_uint64(num_inserts, 1000,
+ "the num of inserts the first thread should"
+ " perform.");
+DEFINE_uint64(wal_ttl_seconds, 1000, "the wal ttl for the run(in seconds)");
+DEFINE_uint64(wal_size_limit_MB, 10,
+ "the wal size limit for the run"
+ "(in MB)");
+
+int main(int argc, const char** argv) {
+ SetUsageMessage(
+ std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " --num_inserts=<num_inserts> --wal_ttl_seconds=<WAL_ttl_seconds>" +
+ " --wal_size_limit_MB=<WAL_size_limit_MB>");
+ ParseCommandLineFlags(&argc, const_cast<char***>(&argv), true);
+
+ Env* env = Env::Default();
+ std::string default_db_path;
+ env->GetTestDirectory(&default_db_path);
+ default_db_path += "db_repl_stress";
+ Options options;
+ options.create_if_missing = true;
+ options.WAL_ttl_seconds = FLAGS_wal_ttl_seconds;
+ options.WAL_size_limit_MB = FLAGS_wal_size_limit_MB;
+ DB* db;
+ DestroyDB(default_db_path, options);
+
+ Status s = DB::Open(options, default_db_path, &db);
+
+ if (!s.ok()) {
+ fprintf(stderr, "Could not open DB due to %s\n", s.ToString().c_str());
+ exit(1);
+ }
+
+ DataPumpThread dataPump;
+ dataPump.no_records = FLAGS_num_inserts;
+ dataPump.db = db;
+ env->StartThread(DataPumpThreadBody, &dataPump);
+
+ ReplicationThread replThread;
+ replThread.db = db;
+ replThread.no_read = 0;
+ replThread.stop.store(false, std::memory_order_release);
+
+ env->StartThread(ReplicationThreadBody, &replThread);
+ while (replThread.no_read < FLAGS_num_inserts)
+ ;
+ replThread.stop.store(true, std::memory_order_release);
+ if (replThread.no_read < dataPump.no_records) {
+ // no. read should be => than inserted.
+ fprintf(stderr,
+ "No. of Record's written and read not same\nRead : %" ROCKSDB_PRIszt
+ " Written : %" ROCKSDB_PRIszt "\n",
+ replThread.no_read, dataPump.no_records);
+ exit(1);
+ }
+ fprintf(stderr, "Successful!\n");
+ exit(0);
+}
+
+#endif // GFLAGS
+
+#else // ROCKSDB_LITE
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return 1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/db_sanity_test.cc b/storage/rocksdb/rocksdb/tools/db_sanity_test.cc
new file mode 100644
index 00000000..b483ee84
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/db_sanity_test.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+#include <cstdlib>
+#include <vector>
+#include <memory>
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/table.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/filter_policy.h"
+#include "port/port.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();
+ DestroyDB(dbname, options);
+ DB* db = nullptr;
+ Status s = DB::Open(options, dbname, &db);
+ std::unique_ptr<DB> db_guard(db);
+ if (!s.ok()) {
+ return s;
+ }
+ for (int i = 0; i < 1000000; ++i) {
+ std::string k = "key" + ToString(i);
+ std::string v = "value" + ToString(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> db_guard(db);
+ if (!s.ok()) {
+ return s;
+ }
+ for (int i = 0; i < 1000000; ++i) {
+ std::string k = "key" + ToString(i);
+ std::string v = "value" + ToString(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_;
+};
+
+#ifndef ROCKSDB_LITE
+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_;
+};
+#endif // ROCKSDB_LITE
+
+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<SanityTest*> sanity_tests = {
+ new SanityTestBasic(path),
+ new SanityTestSpecialComparator(path),
+ new SanityTestZlibCompression(path),
+ new SanityTestZlibCompressionVersion2(path),
+ new SanityTestLZ4Compression(path),
+ new SanityTestLZ4HCCompression(path),
+ new SanityTestZSTDCompression(path),
+#ifndef ROCKSDB_LITE
+ new SanityTestPlainTableFactory(path),
+#endif // ROCKSDB_LITE
+ 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 <path> [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/storage/rocksdb/rocksdb/tools/dbench_monitor b/storage/rocksdb/rocksdb/tools/dbench_monitor
new file mode 100755
index 00000000..d85f9d07
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/dbench_monitor
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+#
+#(c) 2004-present, Facebook Inc. All rights reserved.
+#
+#see LICENSE file for more information on use/redistribution rights.
+#
+
+#
+#dbench_monitor: monitor db_bench process for violation of memory utilization
+#
+#default usage will monitor 'virtual memory size'. See below for standard options
+#passed to db_bench during this test.
+#
+# See also: ./pflag for the actual monitoring script that does the work
+#
+#NOTE:
+# You may end up with some /tmp/ files if db_bench OR
+# this script OR ./pflag was killed unceremoniously
+#
+# If you see the script taking a long time, trying "kill"
+# will usually cleanly exit.
+#
+#
+DIR=`dirname $0`
+LOG=/tmp/`basename $0`.$$
+DB_BENCH="$DIR/../db_bench";
+PFLAG=${DIR}/pflag
+
+usage() {
+ cat <<HELP; exit
+
+Usage: $0 [-h]
+
+-h: prints this help message
+
+This program will run the db_bench script to monitor memory usage
+using the 'pflag' program. It launches db_bench with default settings
+for certain arguments. You can change the defaults passed to
+'db_bench' program, by setting the following environment
+variables:
+
+ bs [block_size]
+ ztype [compression_type]
+ benches [benchmarks]
+ reads [reads]
+ threads [threads]
+ cs [cache_size]
+ vsize [value_size]
+ comp [compression_ratio]
+ num [num]
+
+See the code for more info
+
+HELP
+
+}
+
+[ ! -x ${DB_BENCH} ] && echo "WARNING: ${DB_BENCH} doesn't exist, abort!" && exit -1;
+
+[ "x$1" = "x-h" ] && usage;
+
+trap 'rm -f ${LOG}; kill ${PID}; echo "Interrupted, exiting";' 1 2 3 15
+
+touch $LOG;
+
+: ${bs:=16384}
+: ${ztype:=zlib}
+: ${benches:=readwhilewriting}
+: ${reads:=$((1*1024*1024))};
+: ${threads:=8}
+: ${vsize:=2000}
+: ${comp:=0.5}
+: ${num:=10000}
+: ${cs:=$((1*1024*1024*1024))};
+
+DEBUG=1 #Set to 0 to remove chattiness
+
+
+if [ "x$DEBUG" != "x" ]; then
+ #
+ #NOTE: under some circumstances, --use_existing_db may leave LOCK files under ${TMPDIR}/rocksdb/*
+ #cleanup the dir and re-run
+ #
+ echo DEBUG: Will run $DB_BENCH --block_size=$bs --compression_type=$ztype --benchmarks="$benches" --reads="$reads" --threads="$threads" --cache_size=$cs --value_size=$vsize --compression_ratio=$comp --num=$num --use_existing_db
+
+fi
+
+$DB_BENCH --block_size=$bs --compression_type=$ztype --benchmarks="$benches" --reads="$reads" --threads="$threads" --cache_size=$cs --value_size=$vsize --compression_ratio=$comp --num=$num --use_existing_db >$LOG 2>&1 &
+
+if [ $? -ne 0 ]; then
+ warn "WARNING: ${DB_BENCH} did not launch successfully! Abort!";
+ exit;
+fi
+PID=$!
+
+#
+#Start the monitoring. Default is "vsz" monitoring for upto cache_size ($cs) value of virtual mem
+#You could also monitor RSS and CPUTIME (bsdtime). Try 'pflag -h' for how to do this
+#
+${PFLAG} -p $PID -v
+
+rm -f $LOG;
diff --git a/storage/rocksdb/rocksdb/tools/dump/db_dump_tool.cc b/storage/rocksdb/rocksdb/tools/dump/db_dump_tool.cc
new file mode 100644
index 00000000..be3ff796
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/dump/db_dump_tool.cc
@@ -0,0 +1,259 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <cinttypes>
+#include <iostream>
+
+#include "rocksdb/db.h"
+#include "rocksdb/db_dump_tool.h"
+#include "rocksdb/env.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool DbDumpTool::Run(const DumpOptions& dump_options,
+ ROCKSDB_NAMESPACE::Options options) {
+ ROCKSDB_NAMESPACE::DB* dbptr;
+ ROCKSDB_NAMESPACE::Status status;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> dumpfile;
+ char hostname[1024];
+ int64_t timesec = 0;
+ std::string abspath;
+ char json[4096];
+
+ static const char* magicstr = "ROCKDUMP";
+ static const char versionstr[8] = {0, 0, 0, 0, 0, 0, 0, 1};
+
+ ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
+
+ // Open the database
+ options.create_if_missing = false;
+ status = ROCKSDB_NAMESPACE::DB::OpenForReadOnly(options, dump_options.db_path,
+ &dbptr);
+ if (!status.ok()) {
+ std::cerr << "Unable to open database '" << dump_options.db_path
+ << "' for reading: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<ROCKSDB_NAMESPACE::DB> db(dbptr);
+
+ status = env->NewWritableFile(dump_options.dump_location, &dumpfile,
+ ROCKSDB_NAMESPACE::EnvOptions());
+ if (!status.ok()) {
+ std::cerr << "Unable to open dump file '" << dump_options.dump_location
+ << "' for writing: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ ROCKSDB_NAMESPACE::Slice magicslice(magicstr, 8);
+ status = dumpfile->Append(magicslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ ROCKSDB_NAMESPACE::Slice versionslice(versionstr, 8);
+ status = dumpfile->Append(versionslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ if (dump_options.anonymous) {
+ snprintf(json, sizeof(json), "{}");
+ } else {
+ status = env->GetHostName(hostname, sizeof(hostname));
+ status = env->GetCurrentTime(&timesec);
+ status = env->GetAbsolutePath(dump_options.db_path, &abspath);
+ snprintf(json, sizeof(json),
+ "{ \"database-path\": \"%s\", \"hostname\": \"%s\", "
+ "\"creation-time\": %" PRIi64 " }",
+ abspath.c_str(), hostname, timesec);
+ }
+
+ ROCKSDB_NAMESPACE::Slice infoslice(json, strlen(json));
+ char infosize[4];
+ ROCKSDB_NAMESPACE::EncodeFixed32(infosize, (uint32_t)infoslice.size());
+ ROCKSDB_NAMESPACE::Slice infosizeslice(infosize, 4);
+ status = dumpfile->Append(infosizeslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+ status = dumpfile->Append(infoslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> it(
+ db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ char keysize[4];
+ ROCKSDB_NAMESPACE::EncodeFixed32(keysize, (uint32_t)it->key().size());
+ ROCKSDB_NAMESPACE::Slice keysizeslice(keysize, 4);
+ status = dumpfile->Append(keysizeslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+ status = dumpfile->Append(it->key());
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ char valsize[4];
+ ROCKSDB_NAMESPACE::EncodeFixed32(valsize, (uint32_t)it->value().size());
+ ROCKSDB_NAMESPACE::Slice valsizeslice(valsize, 4);
+ status = dumpfile->Append(valsizeslice);
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+ status = dumpfile->Append(it->value());
+ if (!status.ok()) {
+ std::cerr << "Append failed: " << status.ToString() << std::endl;
+ return false;
+ }
+ }
+ if (!it->status().ok()) {
+ std::cerr << "Database iteration failed: " << status.ToString()
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool DbUndumpTool::Run(const UndumpOptions& undump_options,
+ ROCKSDB_NAMESPACE::Options options) {
+ ROCKSDB_NAMESPACE::DB* dbptr;
+ ROCKSDB_NAMESPACE::Status status;
+ ROCKSDB_NAMESPACE::Env* env;
+ std::unique_ptr<ROCKSDB_NAMESPACE::SequentialFile> dumpfile;
+ ROCKSDB_NAMESPACE::Slice slice;
+ char scratch8[8];
+
+ static const char* magicstr = "ROCKDUMP";
+ static const char versionstr[8] = {0, 0, 0, 0, 0, 0, 0, 1};
+
+ env = ROCKSDB_NAMESPACE::Env::Default();
+
+ status = env->NewSequentialFile(undump_options.dump_location, &dumpfile,
+ ROCKSDB_NAMESPACE::EnvOptions());
+ if (!status.ok()) {
+ std::cerr << "Unable to open dump file '" << undump_options.dump_location
+ << "' for reading: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ status = dumpfile->Read(8, &slice, scratch8);
+ if (!status.ok() || slice.size() != 8 ||
+ memcmp(slice.data(), magicstr, 8) != 0) {
+ std::cerr << "File '" << undump_options.dump_location
+ << "' is not a recognizable dump file." << std::endl;
+ return false;
+ }
+
+ status = dumpfile->Read(8, &slice, scratch8);
+ if (!status.ok() || slice.size() != 8 ||
+ memcmp(slice.data(), versionstr, 8) != 0) {
+ std::cerr << "File '" << undump_options.dump_location
+ << "' version not recognized." << std::endl;
+ return false;
+ }
+
+ status = dumpfile->Read(4, &slice, scratch8);
+ if (!status.ok() || slice.size() != 4) {
+ std::cerr << "Unable to read info blob size." << std::endl;
+ return false;
+ }
+ uint32_t infosize = ROCKSDB_NAMESPACE::DecodeFixed32(slice.data());
+ status = dumpfile->Skip(infosize);
+ if (!status.ok()) {
+ std::cerr << "Unable to skip info blob: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ options.create_if_missing = true;
+ status = ROCKSDB_NAMESPACE::DB::Open(options, undump_options.db_path, &dbptr);
+ if (!status.ok()) {
+ std::cerr << "Unable to open database '" << undump_options.db_path
+ << "' for writing: " << status.ToString() << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<ROCKSDB_NAMESPACE::DB> db(dbptr);
+
+ uint32_t last_keysize = 64;
+ size_t last_valsize = 1 << 20;
+ std::unique_ptr<char[]> keyscratch(new char[last_keysize]);
+ std::unique_ptr<char[]> valscratch(new char[last_valsize]);
+
+ while (1) {
+ uint32_t keysize, valsize;
+ ROCKSDB_NAMESPACE::Slice keyslice;
+ ROCKSDB_NAMESPACE::Slice valslice;
+
+ status = dumpfile->Read(4, &slice, scratch8);
+ if (!status.ok() || slice.size() != 4) break;
+ keysize = ROCKSDB_NAMESPACE::DecodeFixed32(slice.data());
+ if (keysize > last_keysize) {
+ while (keysize > last_keysize) last_keysize *= 2;
+ keyscratch = std::unique_ptr<char[]>(new char[last_keysize]);
+ }
+
+ status = dumpfile->Read(keysize, &keyslice, keyscratch.get());
+ if (!status.ok() || keyslice.size() != keysize) {
+ std::cerr << "Key read failure: "
+ << (status.ok() ? "insufficient data" : status.ToString())
+ << std::endl;
+ return false;
+ }
+
+ status = dumpfile->Read(4, &slice, scratch8);
+ if (!status.ok() || slice.size() != 4) {
+ std::cerr << "Unable to read value size: "
+ << (status.ok() ? "insufficient data" : status.ToString())
+ << std::endl;
+ return false;
+ }
+ valsize = ROCKSDB_NAMESPACE::DecodeFixed32(slice.data());
+ if (valsize > last_valsize) {
+ while (valsize > last_valsize) last_valsize *= 2;
+ valscratch = std::unique_ptr<char[]>(new char[last_valsize]);
+ }
+
+ status = dumpfile->Read(valsize, &valslice, valscratch.get());
+ if (!status.ok() || valslice.size() != valsize) {
+ std::cerr << "Unable to read value: "
+ << (status.ok() ? "insufficient data" : status.ToString())
+ << std::endl;
+ return false;
+ }
+
+ status = db->Put(ROCKSDB_NAMESPACE::WriteOptions(), keyslice, valslice);
+ if (!status.ok()) {
+ fprintf(stderr, "Unable to write database entry\n");
+ return false;
+ }
+ }
+
+ if (undump_options.compact_db) {
+ status = db->CompactRange(ROCKSDB_NAMESPACE::CompactRangeOptions(), nullptr,
+ nullptr);
+ if (!status.ok()) {
+ fprintf(stderr,
+ "Unable to compact the database after loading the dumped file\n");
+ return false;
+ }
+ }
+ return true;
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/dump/rocksdb_dump.cc b/storage/rocksdb/rocksdb/tools/dump/rocksdb_dump.cc
new file mode 100644
index 00000000..358457e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/dump/rocksdb_dump.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+#if !(defined GFLAGS) || defined(ROCKSDB_LITE)
+
+#include <cstdio>
+int main() {
+#ifndef GFLAGS
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+#endif
+#ifdef ROCKSDB_LITE
+ fprintf(stderr, "DbDumpTool is not supported in ROCKSDB_LITE\n");
+#endif
+ return 1;
+}
+
+#else
+
+#include "rocksdb/convenience.h"
+#include "rocksdb/db_dump_tool.h"
+#include "util/gflags_compat.h"
+
+DEFINE_string(db_path, "", "Path to the db that will be dumped");
+DEFINE_string(dump_location, "", "Path to where the dump file location");
+DEFINE_bool(anonymous, false,
+ "Remove information like db path, creation time from dumped file");
+DEFINE_string(db_options, "",
+ "Options string used to open the database that will be dumped");
+
+int main(int argc, char** argv) {
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
+
+ if (FLAGS_db_path == "" || FLAGS_dump_location == "") {
+ fprintf(stderr, "Please set --db_path and --dump_location\n");
+ return 1;
+ }
+
+ ROCKSDB_NAMESPACE::DumpOptions dump_options;
+ dump_options.db_path = FLAGS_db_path;
+ dump_options.dump_location = FLAGS_dump_location;
+ dump_options.anonymous = FLAGS_anonymous;
+
+ ROCKSDB_NAMESPACE::Options db_options;
+ if (FLAGS_db_options != "") {
+ ROCKSDB_NAMESPACE::Options parsed_options;
+ ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::GetOptionsFromString(
+ db_options, FLAGS_db_options, &parsed_options);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot parse provided db_options\n");
+ return 1;
+ }
+ db_options = parsed_options;
+ }
+
+ ROCKSDB_NAMESPACE::DbDumpTool tool;
+ if (!tool.Run(dump_options, db_options)) {
+ return 1;
+ }
+ return 0;
+}
+#endif // !(defined GFLAGS) || defined(ROCKSDB_LITE)
diff --git a/storage/rocksdb/rocksdb/tools/dump/rocksdb_undump.cc b/storage/rocksdb/rocksdb/tools/dump/rocksdb_undump.cc
new file mode 100644
index 00000000..2ff12854
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/dump/rocksdb_undump.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+#if !(defined GFLAGS) || defined(ROCKSDB_LITE)
+
+#include <cstdio>
+int main() {
+#ifndef GFLAGS
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+#endif
+#ifdef ROCKSDB_LITE
+ fprintf(stderr, "DbUndumpTool is not supported in ROCKSDB_LITE\n");
+#endif
+ return 1;
+}
+
+#else
+
+#include "rocksdb/convenience.h"
+#include "rocksdb/db_dump_tool.h"
+#include "util/gflags_compat.h"
+
+DEFINE_string(dump_location, "", "Path to the dump file that will be loaded");
+DEFINE_string(db_path, "", "Path to the db that we will undump the file into");
+DEFINE_bool(compact, false, "Compact the db after loading the dumped file");
+DEFINE_string(db_options, "",
+ "Options string used to open the database that will be loaded");
+
+int main(int argc, char **argv) {
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
+
+ if (FLAGS_db_path == "" || FLAGS_dump_location == "") {
+ fprintf(stderr, "Please set --db_path and --dump_location\n");
+ return 1;
+ }
+
+ ROCKSDB_NAMESPACE::UndumpOptions undump_options;
+ undump_options.db_path = FLAGS_db_path;
+ undump_options.dump_location = FLAGS_dump_location;
+ undump_options.compact_db = FLAGS_compact;
+
+ ROCKSDB_NAMESPACE::Options db_options;
+ if (FLAGS_db_options != "") {
+ ROCKSDB_NAMESPACE::Options parsed_options;
+ ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::GetOptionsFromString(
+ db_options, FLAGS_db_options, &parsed_options);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot parse provided db_options\n");
+ return 1;
+ }
+ db_options = parsed_options;
+ }
+
+ ROCKSDB_NAMESPACE::DbUndumpTool tool;
+ if (!tool.Run(undump_options, db_options)) {
+ return 1;
+ }
+ return 0;
+}
+#endif // !(defined GFLAGS) || defined(ROCKSDB_LITE)
diff --git a/storage/rocksdb/rocksdb/tools/generate_random_db.sh b/storage/rocksdb/rocksdb/tools/generate_random_db.sh
new file mode 100755
index 00000000..5b596261
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/generate_random_db.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+# A shell script to load some pre generated data file to a DB using ldb tool
+# ./ldb needs to be avaible to be executed.
+#
+# Usage: <SCRIPT> <input_data_path> <DB Path>
+
+if [ "$#" -lt 2 ]; then
+ echo "usage: $BASH_SOURCE <input_data_path> <DB Path>"
+ exit 1
+fi
+
+input_data_dir=$1
+db_dir=$2
+rm -rf $db_dir
+
+echo == Loading data from $input_data_dir to $db_dir
+
+declare -a compression_opts=("no" "snappy" "zlib" "bzip2")
+
+set -e
+
+n=0
+
+for f in `ls -1 $input_data_dir`
+do
+ echo == Loading $f with compression ${compression_opts[n % 4]}
+ ./ldb load --db=$db_dir --compression_type=${compression_opts[n % 4]} --bloom_bits=10 --auto_compaction=false --create_if_missing < $input_data_dir/$f
+ let "n = n + 1"
+done
diff --git a/storage/rocksdb/rocksdb/tools/ingest_external_sst.sh b/storage/rocksdb/rocksdb/tools/ingest_external_sst.sh
new file mode 100755
index 00000000..8e2fed7c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ingest_external_sst.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+#
+
+if [ "$#" -lt 2 ]; then
+ echo "usage: $BASH_SOURCE <DB Path> <External SST Dir>"
+ exit 1
+fi
+
+db_dir=$1
+external_sst_dir=$2
+
+for f in `find $external_sst_dir -name extern_sst*`
+do
+ echo == Ingesting external SST file $f to DB at $db_dir
+ ./ldb --db=$db_dir --create_if_missing ingest_extern_sst $f
+done
diff --git a/storage/rocksdb/rocksdb/tools/ldb.cc b/storage/rocksdb/rocksdb/tools/ldb.cc
new file mode 100644
index 00000000..482383be
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/ldb_tool.h"
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::LDBTool tool;
+ tool.Run(argc, argv);
+ return 0;
+}
+#else
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return 1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/ldb_cmd.cc b/storage/rocksdb/rocksdb/tools/ldb_cmd.cc
new file mode 100644
index 00000000..48d32d2e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb_cmd.cc
@@ -0,0 +1,3437 @@
+
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "rocksdb/utilities/ldb_cmd.h"
+
+#include <cinttypes>
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "db/log_reader.h"
+#include "db/write_batch_internal.h"
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "port/port_dirent.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/file_checksum.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/utilities/backupable_db.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "rocksdb/utilities/debug.h"
+#include "rocksdb/utilities/options_util.h"
+#include "rocksdb/write_batch.h"
+#include "rocksdb/write_buffer_manager.h"
+#include "table/scoped_arena_iterator.h"
+#include "tools/ldb_cmd_impl.h"
+#include "tools/sst_dump_tool_imp.h"
+#include "util/cast_util.h"
+#include "util/coding.h"
+#include "util/file_checksum_helper.h"
+#include "util/stderr_logger.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+#include "utilities/ttl/db_ttl_impl.h"
+
+#include <cstdlib>
+#include <ctime>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+class FileChecksumFuncCrc32c;
+
+const std::string LDBCommand::ARG_ENV_URI = "env_uri";
+const std::string LDBCommand::ARG_DB = "db";
+const std::string LDBCommand::ARG_PATH = "path";
+const std::string LDBCommand::ARG_SECONDARY_PATH = "secondary_path";
+const std::string LDBCommand::ARG_HEX = "hex";
+const std::string LDBCommand::ARG_KEY_HEX = "key_hex";
+const std::string LDBCommand::ARG_VALUE_HEX = "value_hex";
+const std::string LDBCommand::ARG_CF_NAME = "column_family";
+const std::string LDBCommand::ARG_TTL = "ttl";
+const std::string LDBCommand::ARG_TTL_START = "start_time";
+const std::string LDBCommand::ARG_TTL_END = "end_time";
+const std::string LDBCommand::ARG_TIMESTAMP = "timestamp";
+const std::string LDBCommand::ARG_TRY_LOAD_OPTIONS = "try_load_options";
+const std::string LDBCommand::ARG_IGNORE_UNKNOWN_OPTIONS =
+ "ignore_unknown_options";
+const std::string LDBCommand::ARG_FROM = "from";
+const std::string LDBCommand::ARG_TO = "to";
+const std::string LDBCommand::ARG_MAX_KEYS = "max_keys";
+const std::string LDBCommand::ARG_BLOOM_BITS = "bloom_bits";
+const std::string LDBCommand::ARG_FIX_PREFIX_LEN = "fix_prefix_len";
+const std::string LDBCommand::ARG_COMPRESSION_TYPE = "compression_type";
+const std::string LDBCommand::ARG_COMPRESSION_MAX_DICT_BYTES =
+ "compression_max_dict_bytes";
+const std::string LDBCommand::ARG_BLOCK_SIZE = "block_size";
+const std::string LDBCommand::ARG_AUTO_COMPACTION = "auto_compaction";
+const std::string LDBCommand::ARG_DB_WRITE_BUFFER_SIZE = "db_write_buffer_size";
+const std::string LDBCommand::ARG_WRITE_BUFFER_SIZE = "write_buffer_size";
+const std::string LDBCommand::ARG_FILE_SIZE = "file_size";
+const std::string LDBCommand::ARG_CREATE_IF_MISSING = "create_if_missing";
+const std::string LDBCommand::ARG_NO_VALUE = "no_value";
+
+const char* LDBCommand::DELIM = " ==> ";
+
+namespace {
+
+void DumpWalFile(Options options, std::string wal_file, bool print_header,
+ bool print_values, bool is_write_committed,
+ LDBCommandExecuteResult* exec_state);
+
+void DumpSstFile(Options options, std::string filename, bool output_hex,
+ bool show_properties);
+};
+
+LDBCommand* LDBCommand::InitFromCmdLineArgs(
+ int argc, char** argv, const Options& options,
+ const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families) {
+ std::vector<std::string> args;
+ for (int i = 1; i < argc; i++) {
+ args.push_back(argv[i]);
+ }
+ return InitFromCmdLineArgs(args, options, ldb_options, column_families,
+ SelectCommand);
+}
+
+/**
+ * Parse the command-line arguments and create the appropriate LDBCommand2
+ * instance.
+ * The command line arguments must be in the following format:
+ * ./ldb --db=PATH_TO_DB [--commonOpt1=commonOpt1Val] ..
+ * COMMAND <PARAM1> <PARAM2> ... [-cmdSpecificOpt1=cmdSpecificOpt1Val] ..
+ * This is similar to the command line format used by HBaseClientTool.
+ * Command name is not included in args.
+ * Returns nullptr if the command-line cannot be parsed.
+ */
+LDBCommand* LDBCommand::InitFromCmdLineArgs(
+ const std::vector<std::string>& args, const Options& options,
+ const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* /*column_families*/,
+ const std::function<LDBCommand*(const ParsedParams&)>& selector) {
+ // --x=y command line arguments are added as x->y map entries in
+ // parsed_params.option_map.
+ //
+ // Command-line arguments of the form --hex end up in this array as hex to
+ // parsed_params.flags
+ ParsedParams parsed_params;
+
+ // Everything other than option_map and flags. Represents commands
+ // and their parameters. For eg: put key1 value1 go into this vector.
+ std::vector<std::string> cmdTokens;
+
+ const std::string OPTION_PREFIX = "--";
+
+ for (const auto& arg : args) {
+ if (arg[0] == '-' && arg[1] == '-'){
+ std::vector<std::string> splits = StringSplit(arg, '=');
+ // --option_name=option_value
+ if (splits.size() == 2) {
+ std::string optionKey = splits[0].substr(OPTION_PREFIX.size());
+ parsed_params.option_map[optionKey] = splits[1];
+ } else if (splits.size() == 1) {
+ // --flag_name
+ std::string optionKey = splits[0].substr(OPTION_PREFIX.size());
+ parsed_params.flags.push_back(optionKey);
+ } else {
+ // --option_name=option_value, option_value contains '='
+ std::string optionKey = splits[0].substr(OPTION_PREFIX.size());
+ parsed_params.option_map[optionKey] =
+ arg.substr(splits[0].length() + 1);
+ }
+ } else {
+ cmdTokens.push_back(arg);
+ }
+ }
+
+ if (cmdTokens.size() < 1) {
+ fprintf(stderr, "Command not specified!");
+ return nullptr;
+ }
+
+ parsed_params.cmd = cmdTokens[0];
+ parsed_params.cmd_params.assign(cmdTokens.begin() + 1, cmdTokens.end());
+
+ LDBCommand* command = selector(parsed_params);
+
+ if (command) {
+ command->SetDBOptions(options);
+ command->SetLDBOptions(ldb_options);
+ }
+ return command;
+}
+
+LDBCommand* LDBCommand::SelectCommand(const ParsedParams& parsed_params) {
+ if (parsed_params.cmd == GetCommand::Name()) {
+ return new GetCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == PutCommand::Name()) {
+ return new PutCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == BatchPutCommand::Name()) {
+ return new BatchPutCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == ScanCommand::Name()) {
+ return new ScanCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == DeleteCommand::Name()) {
+ return new DeleteCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == DeleteRangeCommand::Name()) {
+ return new DeleteRangeCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == ApproxSizeCommand::Name()) {
+ return new ApproxSizeCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == DBQuerierCommand::Name()) {
+ return new DBQuerierCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == CompactorCommand::Name()) {
+ return new CompactorCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == WALDumperCommand::Name()) {
+ return new WALDumperCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == ReduceDBLevelsCommand::Name()) {
+ return new ReduceDBLevelsCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == ChangeCompactionStyleCommand::Name()) {
+ return new ChangeCompactionStyleCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == DBDumperCommand::Name()) {
+ return new DBDumperCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == DBLoaderCommand::Name()) {
+ return new DBLoaderCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == ManifestDumpCommand::Name()) {
+ return new ManifestDumpCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == FileChecksumDumpCommand::Name()) {
+ return new FileChecksumDumpCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == ListColumnFamiliesCommand::Name()) {
+ return new ListColumnFamiliesCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == CreateColumnFamilyCommand::Name()) {
+ return new CreateColumnFamilyCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == DropColumnFamilyCommand::Name()) {
+ return new DropColumnFamilyCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == DBFileDumperCommand::Name()) {
+ return new DBFileDumperCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == InternalDumpCommand::Name()) {
+ return new InternalDumpCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == CheckConsistencyCommand::Name()) {
+ return new CheckConsistencyCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == CheckPointCommand::Name()) {
+ return new CheckPointCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == RepairCommand::Name()) {
+ return new RepairCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == BackupCommand::Name()) {
+ return new BackupCommand(parsed_params.cmd_params, parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == RestoreCommand::Name()) {
+ return new RestoreCommand(parsed_params.cmd_params,
+ parsed_params.option_map, parsed_params.flags);
+ } else if (parsed_params.cmd == WriteExternalSstFilesCommand::Name()) {
+ return new WriteExternalSstFilesCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == IngestExternalSstFilesCommand::Name()) {
+ return new IngestExternalSstFilesCommand(parsed_params.cmd_params,
+ parsed_params.option_map,
+ parsed_params.flags);
+ } else if (parsed_params.cmd == ListFileRangeDeletesCommand::Name()) {
+ return new ListFileRangeDeletesCommand(parsed_params.option_map,
+ parsed_params.flags);
+ }
+ return nullptr;
+}
+
+/* Run the command, and return the execute result. */
+void LDBCommand::Run() {
+ if (!exec_state_.IsNotStarted()) {
+ return;
+ }
+
+ if (!options_.env || options_.env == Env::Default()) {
+ Env* env = Env::Default();
+ Status s = Env::LoadEnv(env_uri_, &env, &env_guard_);
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "LoadEnv: %s\n", s.ToString().c_str());
+ exec_state_ = LDBCommandExecuteResult::Failed(s.ToString());
+ return;
+ }
+ options_.env = env;
+ }
+
+ options_.file_system.reset(new LegacyFileSystemWrapper(options_.env));
+
+ if (db_ == nullptr && !NoDBOpen()) {
+ OpenDB();
+ if (exec_state_.IsFailed() && try_load_options_) {
+ // We don't always return if there is a failure because a WAL file or
+ // manifest file can be given to "dump" command so we should continue.
+ // --try_load_options is not valid in those cases.
+ return;
+ }
+ }
+
+ // We'll intentionally proceed even if the DB can't be opened because users
+ // can also specify a filename, not just a directory.
+ DoCommand();
+
+ if (exec_state_.IsNotStarted()) {
+ exec_state_ = LDBCommandExecuteResult::Succeed("");
+ }
+
+ if (db_ != nullptr) {
+ CloseDB();
+ }
+}
+
+LDBCommand::LDBCommand(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags, bool is_read_only,
+ const std::vector<std::string>& valid_cmd_line_options)
+ : db_(nullptr),
+ db_ttl_(nullptr),
+ is_read_only_(is_read_only),
+ is_key_hex_(false),
+ is_value_hex_(false),
+ is_db_ttl_(false),
+ timestamp_(false),
+ try_load_options_(false),
+ ignore_unknown_options_(false),
+ create_if_missing_(false),
+ option_map_(options),
+ flags_(flags),
+ valid_cmd_line_options_(valid_cmd_line_options) {
+ std::map<std::string, std::string>::const_iterator itr = options.find(ARG_DB);
+ if (itr != options.end()) {
+ db_path_ = itr->second;
+ }
+
+ itr = options.find(ARG_ENV_URI);
+ if (itr != options.end()) {
+ env_uri_ = itr->second;
+ }
+
+ itr = options.find(ARG_CF_NAME);
+ if (itr != options.end()) {
+ column_family_name_ = itr->second;
+ } else {
+ column_family_name_ = kDefaultColumnFamilyName;
+ }
+
+ itr = options.find(ARG_SECONDARY_PATH);
+ secondary_path_ = "";
+ if (itr != options.end()) {
+ secondary_path_ = itr->second;
+ }
+
+ is_key_hex_ = IsKeyHex(options, flags);
+ is_value_hex_ = IsValueHex(options, flags);
+ is_db_ttl_ = IsFlagPresent(flags, ARG_TTL);
+ timestamp_ = IsFlagPresent(flags, ARG_TIMESTAMP);
+ try_load_options_ = IsFlagPresent(flags, ARG_TRY_LOAD_OPTIONS);
+ ignore_unknown_options_ = IsFlagPresent(flags, ARG_IGNORE_UNKNOWN_OPTIONS);
+}
+
+void LDBCommand::OpenDB() {
+ if (!create_if_missing_ && try_load_options_) {
+ Status s = LoadLatestOptions(db_path_, options_.env, &options_,
+ &column_families_, ignore_unknown_options_);
+ if (!s.ok() && !s.IsNotFound()) {
+ // Option file exists but load option file error.
+ std::string msg = s.ToString();
+ exec_state_ = LDBCommandExecuteResult::Failed(msg);
+ db_ = nullptr;
+ return;
+ }
+ if (options_.env->FileExists(options_.wal_dir).IsNotFound()) {
+ options_.wal_dir = db_path_;
+ fprintf(
+ stderr,
+ "wal_dir loaded from the option file doesn't exist. Ignore it.\n");
+ }
+
+ // If merge operator is not set, set a string append operator. There is
+ // no harm doing it.
+ for (auto& cf_entry : column_families_) {
+ if (!cf_entry.options.merge_operator) {
+ cf_entry.options.merge_operator =
+ MergeOperators::CreateStringAppendOperator(':');
+ }
+ }
+ }
+ options_ = PrepareOptionsForOpenDB();
+ if (!exec_state_.IsNotStarted()) {
+ return;
+ }
+ if (column_families_.empty() && !options_.merge_operator) {
+ // No harm to add a general merge operator if it is not specified.
+ options_.merge_operator = MergeOperators::CreateStringAppendOperator(':');
+ }
+ // Open the DB.
+ Status st;
+ std::vector<ColumnFamilyHandle*> handles_opened;
+ if (is_db_ttl_) {
+ // ldb doesn't yet support TTL DB with multiple column families
+ if (!column_family_name_.empty() || !column_families_.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "ldb doesn't support TTL DB with multiple column families");
+ }
+ if (!secondary_path_.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Open as secondary is not supported for TTL DB yet.");
+ }
+ if (is_read_only_) {
+ st = DBWithTTL::Open(options_, db_path_, &db_ttl_, 0, true);
+ } else {
+ st = DBWithTTL::Open(options_, db_path_, &db_ttl_);
+ }
+ db_ = db_ttl_;
+ } else {
+ if (column_families_.empty()) {
+ // Try to figure out column family lists
+ std::vector<std::string> cf_list;
+ st = DB::ListColumnFamilies(options_, db_path_, &cf_list);
+ // There is possible the DB doesn't exist yet, for "create if not
+ // "existing case". The failure is ignored here. We rely on DB::Open()
+ // to give us the correct error message for problem with opening
+ // existing DB.
+ if (st.ok() && cf_list.size() > 1) {
+ // Ignore single column family DB.
+ for (auto cf_name : cf_list) {
+ column_families_.emplace_back(cf_name, options_);
+ }
+ }
+ }
+ if (is_read_only_ && secondary_path_.empty()) {
+ if (column_families_.empty()) {
+ st = DB::OpenForReadOnly(options_, db_path_, &db_);
+ } else {
+ st = DB::OpenForReadOnly(options_, db_path_, column_families_,
+ &handles_opened, &db_);
+ }
+ } else {
+ if (column_families_.empty()) {
+ if (secondary_path_.empty()) {
+ st = DB::Open(options_, db_path_, &db_);
+ } else {
+ st = DB::OpenAsSecondary(options_, db_path_, secondary_path_, &db_);
+ }
+ } else {
+ if (secondary_path_.empty()) {
+ st = DB::Open(options_, db_path_, column_families_, &handles_opened,
+ &db_);
+ } else {
+ st = DB::OpenAsSecondary(options_, db_path_, secondary_path_,
+ column_families_, &handles_opened, &db_);
+ }
+ }
+ }
+ }
+ if (!st.ok()) {
+ std::string msg = st.ToString();
+ exec_state_ = LDBCommandExecuteResult::Failed(msg);
+ } else if (!handles_opened.empty()) {
+ assert(handles_opened.size() == column_families_.size());
+ bool found_cf_name = false;
+ for (size_t i = 0; i < handles_opened.size(); i++) {
+ cf_handles_[column_families_[i].name] = handles_opened[i];
+ if (column_family_name_ == column_families_[i].name) {
+ found_cf_name = true;
+ }
+ }
+ if (!found_cf_name) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Non-existing column family " + column_family_name_);
+ CloseDB();
+ }
+ } else {
+ // We successfully opened DB in single column family mode.
+ assert(column_families_.empty());
+ if (column_family_name_ != kDefaultColumnFamilyName) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Non-existing column family " + column_family_name_);
+ CloseDB();
+ }
+ }
+}
+
+void LDBCommand::CloseDB() {
+ if (db_ != nullptr) {
+ for (auto& pair : cf_handles_) {
+ delete pair.second;
+ }
+ delete db_;
+ db_ = nullptr;
+ }
+}
+
+ColumnFamilyHandle* LDBCommand::GetCfHandle() {
+ if (!cf_handles_.empty()) {
+ auto it = cf_handles_.find(column_family_name_);
+ if (it == cf_handles_.end()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Cannot find column family " + column_family_name_);
+ } else {
+ return it->second;
+ }
+ }
+ return db_->DefaultColumnFamily();
+}
+
+std::vector<std::string> LDBCommand::BuildCmdLineOptions(
+ std::vector<std::string> options) {
+ std::vector<std::string> ret = {ARG_ENV_URI,
+ ARG_DB,
+ ARG_SECONDARY_PATH,
+ ARG_BLOOM_BITS,
+ ARG_BLOCK_SIZE,
+ ARG_AUTO_COMPACTION,
+ ARG_COMPRESSION_TYPE,
+ ARG_COMPRESSION_MAX_DICT_BYTES,
+ ARG_WRITE_BUFFER_SIZE,
+ ARG_FILE_SIZE,
+ ARG_FIX_PREFIX_LEN,
+ ARG_TRY_LOAD_OPTIONS,
+ ARG_IGNORE_UNKNOWN_OPTIONS,
+ ARG_CF_NAME};
+ ret.insert(ret.end(), options.begin(), options.end());
+ return ret;
+}
+
+/**
+ * Parses the specific integer option and fills in the value.
+ * Returns true if the option is found.
+ * Returns false if the option is not found or if there is an error parsing the
+ * value. If there is an error, the specified exec_state is also
+ * updated.
+ */
+bool LDBCommand::ParseIntOption(
+ const std::map<std::string, std::string>& /*options*/,
+ const std::string& option, int& value,
+ LDBCommandExecuteResult& exec_state) {
+ std::map<std::string, std::string>::const_iterator itr =
+ option_map_.find(option);
+ if (itr != option_map_.end()) {
+ try {
+#if defined(CYGWIN)
+ value = strtol(itr->second.c_str(), 0, 10);
+#else
+ value = std::stoi(itr->second);
+#endif
+ return true;
+ } catch (const std::invalid_argument&) {
+ exec_state =
+ LDBCommandExecuteResult::Failed(option + " has an invalid value.");
+ } catch (const std::out_of_range&) {
+ exec_state = LDBCommandExecuteResult::Failed(
+ option + " has a value out-of-range.");
+ }
+ }
+ return false;
+}
+
+/**
+ * Parses the specified option and fills in the value.
+ * Returns true if the option is found.
+ * Returns false otherwise.
+ */
+bool LDBCommand::ParseStringOption(
+ const std::map<std::string, std::string>& /*options*/,
+ const std::string& option, std::string* value) {
+ auto itr = option_map_.find(option);
+ if (itr != option_map_.end()) {
+ *value = itr->second;
+ return true;
+ }
+ return false;
+}
+
+Options LDBCommand::PrepareOptionsForOpenDB() {
+ ColumnFamilyOptions* cf_opts;
+ auto column_families_iter =
+ std::find_if(column_families_.begin(), column_families_.end(),
+ [this](const ColumnFamilyDescriptor& cf_desc) {
+ return cf_desc.name == column_family_name_;
+ });
+ if (column_families_iter != column_families_.end()) {
+ cf_opts = &column_families_iter->options;
+ } else {
+ cf_opts = static_cast<ColumnFamilyOptions*>(&options_);
+ }
+ DBOptions* db_opts = static_cast<DBOptions*>(&options_);
+ db_opts->create_if_missing = false;
+
+ std::map<std::string, std::string>::const_iterator itr;
+
+ BlockBasedTableOptions table_options;
+ bool use_table_options = false;
+ int bits;
+ if (ParseIntOption(option_map_, ARG_BLOOM_BITS, bits, exec_state_)) {
+ if (bits > 0) {
+ use_table_options = true;
+ table_options.filter_policy.reset(NewBloomFilterPolicy(bits));
+ } else {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed(ARG_BLOOM_BITS + " must be > 0.");
+ }
+ }
+
+ int block_size;
+ if (ParseIntOption(option_map_, ARG_BLOCK_SIZE, block_size, exec_state_)) {
+ if (block_size > 0) {
+ use_table_options = true;
+ table_options.block_size = block_size;
+ } else {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed(ARG_BLOCK_SIZE + " must be > 0.");
+ }
+ }
+
+ if (use_table_options) {
+ cf_opts->table_factory.reset(NewBlockBasedTableFactory(table_options));
+ }
+
+ itr = option_map_.find(ARG_AUTO_COMPACTION);
+ if (itr != option_map_.end()) {
+ cf_opts->disable_auto_compactions = !StringToBool(itr->second);
+ }
+
+ itr = option_map_.find(ARG_COMPRESSION_TYPE);
+ if (itr != option_map_.end()) {
+ std::string comp = itr->second;
+ if (comp == "no") {
+ cf_opts->compression = kNoCompression;
+ } else if (comp == "snappy") {
+ cf_opts->compression = kSnappyCompression;
+ } else if (comp == "zlib") {
+ cf_opts->compression = kZlibCompression;
+ } else if (comp == "bzip2") {
+ cf_opts->compression = kBZip2Compression;
+ } else if (comp == "lz4") {
+ cf_opts->compression = kLZ4Compression;
+ } else if (comp == "lz4hc") {
+ cf_opts->compression = kLZ4HCCompression;
+ } else if (comp == "xpress") {
+ cf_opts->compression = kXpressCompression;
+ } else if (comp == "zstd") {
+ cf_opts->compression = kZSTD;
+ } else {
+ // Unknown compression.
+ exec_state_ =
+ LDBCommandExecuteResult::Failed("Unknown compression level: " + comp);
+ }
+ }
+
+ int compression_max_dict_bytes;
+ if (ParseIntOption(option_map_, ARG_COMPRESSION_MAX_DICT_BYTES,
+ compression_max_dict_bytes, exec_state_)) {
+ if (compression_max_dict_bytes >= 0) {
+ cf_opts->compression_opts.max_dict_bytes = compression_max_dict_bytes;
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_COMPRESSION_MAX_DICT_BYTES + " must be >= 0.");
+ }
+ }
+
+ int db_write_buffer_size;
+ if (ParseIntOption(option_map_, ARG_DB_WRITE_BUFFER_SIZE,
+ db_write_buffer_size, exec_state_)) {
+ if (db_write_buffer_size >= 0) {
+ db_opts->db_write_buffer_size = db_write_buffer_size;
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(ARG_DB_WRITE_BUFFER_SIZE +
+ " must be >= 0.");
+ }
+ }
+
+ int write_buffer_size;
+ if (ParseIntOption(option_map_, ARG_WRITE_BUFFER_SIZE, write_buffer_size,
+ exec_state_)) {
+ if (write_buffer_size > 0) {
+ cf_opts->write_buffer_size = write_buffer_size;
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(ARG_WRITE_BUFFER_SIZE +
+ " must be > 0.");
+ }
+ }
+
+ int file_size;
+ if (ParseIntOption(option_map_, ARG_FILE_SIZE, file_size, exec_state_)) {
+ if (file_size > 0) {
+ cf_opts->target_file_size_base = file_size;
+ } else {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed(ARG_FILE_SIZE + " must be > 0.");
+ }
+ }
+
+ if (db_opts->db_paths.size() == 0) {
+ db_opts->db_paths.emplace_back(db_path_,
+ std::numeric_limits<uint64_t>::max());
+ }
+
+ int fix_prefix_len;
+ if (ParseIntOption(option_map_, ARG_FIX_PREFIX_LEN, fix_prefix_len,
+ exec_state_)) {
+ if (fix_prefix_len > 0) {
+ cf_opts->prefix_extractor.reset(
+ NewFixedPrefixTransform(static_cast<size_t>(fix_prefix_len)));
+ } else {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed(ARG_FIX_PREFIX_LEN + " must be > 0.");
+ }
+ }
+
+ // TODO(ajkr): this return value doesn't reflect the CF options changed, so
+ // subcommands that rely on this won't see the effect of CF-related CLI args.
+ // Such subcommands need to be changed to properly support CFs.
+ return options_;
+}
+
+bool LDBCommand::ParseKeyValue(const std::string& line, std::string* key,
+ std::string* value, bool is_key_hex,
+ bool is_value_hex) {
+ size_t pos = line.find(DELIM);
+ if (pos != std::string::npos) {
+ *key = line.substr(0, pos);
+ *value = line.substr(pos + strlen(DELIM));
+ if (is_key_hex) {
+ *key = HexToString(*key);
+ }
+ if (is_value_hex) {
+ *value = HexToString(*value);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Make sure that ONLY the command-line options and flags expected by this
+ * command are specified on the command-line. Extraneous options are usually
+ * the result of user error.
+ * Returns true if all checks pass. Else returns false, and prints an
+ * appropriate error msg to stderr.
+ */
+bool LDBCommand::ValidateCmdLineOptions() {
+ for (std::map<std::string, std::string>::const_iterator itr =
+ option_map_.begin();
+ itr != option_map_.end(); ++itr) {
+ if (std::find(valid_cmd_line_options_.begin(),
+ valid_cmd_line_options_.end(),
+ itr->first) == valid_cmd_line_options_.end()) {
+ fprintf(stderr, "Invalid command-line option %s\n", itr->first.c_str());
+ return false;
+ }
+ }
+
+ for (std::vector<std::string>::const_iterator itr = flags_.begin();
+ itr != flags_.end(); ++itr) {
+ if (std::find(valid_cmd_line_options_.begin(),
+ valid_cmd_line_options_.end(),
+ *itr) == valid_cmd_line_options_.end()) {
+ fprintf(stderr, "Invalid command-line flag %s\n", itr->c_str());
+ return false;
+ }
+ }
+
+ if (!NoDBOpen() && option_map_.find(ARG_DB) == option_map_.end() &&
+ option_map_.find(ARG_PATH) == option_map_.end()) {
+ fprintf(stderr, "Either %s or %s must be specified.\n", ARG_DB.c_str(),
+ ARG_PATH.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+std::string LDBCommand::HexToString(const std::string& str) {
+ std::string result;
+ std::string::size_type len = str.length();
+ if (len < 2 || str[0] != '0' || str[1] != 'x') {
+ fprintf(stderr, "Invalid hex input %s. Must start with 0x\n", str.c_str());
+ throw "Invalid hex input";
+ }
+ if (!Slice(str.data() + 2, len - 2).DecodeHex(&result)) {
+ throw "Invalid hex input";
+ }
+ return result;
+}
+
+std::string LDBCommand::StringToHex(const std::string& str) {
+ std::string result("0x");
+ result.append(Slice(str).ToString(true));
+ return result;
+}
+
+std::string LDBCommand::PrintKeyValue(const std::string& key,
+ const std::string& value, bool is_key_hex,
+ bool is_value_hex) {
+ std::string result;
+ result.append(is_key_hex ? StringToHex(key) : key);
+ result.append(DELIM);
+ result.append(is_value_hex ? StringToHex(value) : value);
+ return result;
+}
+
+std::string LDBCommand::PrintKeyValue(const std::string& key,
+ const std::string& value, bool is_hex) {
+ return PrintKeyValue(key, value, is_hex, is_hex);
+}
+
+std::string LDBCommand::HelpRangeCmdArgs() {
+ std::ostringstream str_stream;
+ str_stream << " ";
+ str_stream << "[--" << ARG_FROM << "] ";
+ str_stream << "[--" << ARG_TO << "] ";
+ return str_stream.str();
+}
+
+bool LDBCommand::IsKeyHex(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags) {
+ return (IsFlagPresent(flags, ARG_HEX) || IsFlagPresent(flags, ARG_KEY_HEX) ||
+ ParseBooleanOption(options, ARG_HEX, false) ||
+ ParseBooleanOption(options, ARG_KEY_HEX, false));
+}
+
+bool LDBCommand::IsValueHex(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags) {
+ return (IsFlagPresent(flags, ARG_HEX) ||
+ IsFlagPresent(flags, ARG_VALUE_HEX) ||
+ ParseBooleanOption(options, ARG_HEX, false) ||
+ ParseBooleanOption(options, ARG_VALUE_HEX, false));
+}
+
+bool LDBCommand::ParseBooleanOption(
+ const std::map<std::string, std::string>& options,
+ const std::string& option, bool default_val) {
+ std::map<std::string, std::string>::const_iterator itr = options.find(option);
+ if (itr != options.end()) {
+ std::string option_val = itr->second;
+ return StringToBool(itr->second);
+ }
+ return default_val;
+}
+
+bool LDBCommand::StringToBool(std::string val) {
+ std::transform(val.begin(), val.end(), val.begin(),
+ [](char ch) -> char { return (char)::tolower(ch); });
+
+ if (val == "true") {
+ return true;
+ } else if (val == "false") {
+ return false;
+ } else {
+ throw "Invalid value for boolean argument";
+ }
+}
+
+CompactorCommand::CompactorCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_FROM, ARG_TO, ARG_HEX, ARG_KEY_HEX,
+ ARG_VALUE_HEX, ARG_TTL})),
+ null_from_(true),
+ null_to_(true) {
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_FROM);
+ if (itr != options.end()) {
+ null_from_ = false;
+ from_ = itr->second;
+ }
+
+ itr = options.find(ARG_TO);
+ if (itr != options.end()) {
+ null_to_ = false;
+ to_ = itr->second;
+ }
+
+ if (is_key_hex_) {
+ if (!null_from_) {
+ from_ = HexToString(from_);
+ }
+ if (!null_to_) {
+ to_ = HexToString(to_);
+ }
+ }
+}
+
+void CompactorCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(CompactorCommand::Name());
+ ret.append(HelpRangeCmdArgs());
+ ret.append("\n");
+}
+
+void CompactorCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ Slice* begin = nullptr;
+ Slice* end = nullptr;
+ if (!null_from_) {
+ begin = new Slice(from_);
+ }
+ if (!null_to_) {
+ end = new Slice(to_);
+ }
+
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
+
+ db_->CompactRange(cro, GetCfHandle(), begin, end);
+ exec_state_ = LDBCommandExecuteResult::Succeed("");
+
+ delete begin;
+ delete end;
+}
+
+// ---------------------------------------------------------------------------
+const std::string DBLoaderCommand::ARG_DISABLE_WAL = "disable_wal";
+const std::string DBLoaderCommand::ARG_BULK_LOAD = "bulk_load";
+const std::string DBLoaderCommand::ARG_COMPACT = "compact";
+
+DBLoaderCommand::DBLoaderCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, false,
+ BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
+ ARG_TO, ARG_CREATE_IF_MISSING, ARG_DISABLE_WAL,
+ ARG_BULK_LOAD, ARG_COMPACT})),
+ disable_wal_(false),
+ bulk_load_(false),
+ compact_(false) {
+ create_if_missing_ = IsFlagPresent(flags, ARG_CREATE_IF_MISSING);
+ disable_wal_ = IsFlagPresent(flags, ARG_DISABLE_WAL);
+ bulk_load_ = IsFlagPresent(flags, ARG_BULK_LOAD);
+ compact_ = IsFlagPresent(flags, ARG_COMPACT);
+}
+
+void DBLoaderCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DBLoaderCommand::Name());
+ ret.append(" [--" + ARG_CREATE_IF_MISSING + "]");
+ ret.append(" [--" + ARG_DISABLE_WAL + "]");
+ ret.append(" [--" + ARG_BULK_LOAD + "]");
+ ret.append(" [--" + ARG_COMPACT + "]");
+ ret.append("\n");
+}
+
+Options DBLoaderCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.create_if_missing = create_if_missing_;
+ if (bulk_load_) {
+ opt.PrepareForBulkLoad();
+ }
+ return opt;
+}
+
+void DBLoaderCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ WriteOptions write_options;
+ if (disable_wal_) {
+ write_options.disableWAL = true;
+ }
+
+ int bad_lines = 0;
+ std::string line;
+ // prefer ifstream getline performance vs that from std::cin istream
+ std::ifstream ifs_stdin("/dev/stdin");
+ std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin;
+ while (getline(*istream_p, line, '\n')) {
+ std::string key;
+ std::string value;
+ if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) {
+ db_->Put(write_options, GetCfHandle(), Slice(key), Slice(value));
+ } else if (0 == line.find("Keys in range:")) {
+ // ignore this line
+ } else if (0 == line.find("Created bg thread 0x")) {
+ // ignore this line
+ } else {
+ bad_lines ++;
+ }
+ }
+
+ if (bad_lines > 0) {
+ std::cout << "Warning: " << bad_lines << " bad lines ignored." << std::endl;
+ }
+ if (compact_) {
+ db_->CompactRange(CompactRangeOptions(), GetCfHandle(), nullptr, nullptr);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+namespace {
+
+void DumpManifestFile(Options options, std::string file, bool verbose, bool hex,
+ bool json) {
+ EnvOptions sopt;
+ std::string dbname("dummy");
+ std::shared_ptr<Cache> tc(NewLRUCache(options.max_open_files - 10,
+ options.table_cache_numshardbits));
+ // Notice we are using the default options not through SanitizeOptions(),
+ // if VersionSet::DumpManifest() depends on any option done by
+ // SanitizeOptions(), we need to initialize it manually.
+ options.db_paths.emplace_back("dummy", 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,
+ /*block_cache_tracer=*/nullptr);
+ Status s = versions.DumpManifest(options, file, verbose, hex, json);
+ if (!s.ok()) {
+ fprintf(stderr, "Error in processing file %s %s\n", file.c_str(),
+ s.ToString().c_str());
+ }
+}
+
+} // namespace
+
+const std::string ManifestDumpCommand::ARG_VERBOSE = "verbose";
+const std::string ManifestDumpCommand::ARG_JSON = "json";
+const std::string ManifestDumpCommand::ARG_PATH = "path";
+
+void ManifestDumpCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ManifestDumpCommand::Name());
+ ret.append(" [--" + ARG_VERBOSE + "]");
+ ret.append(" [--" + ARG_JSON + "]");
+ ret.append(" [--" + ARG_PATH + "=<path_to_manifest_file>]");
+ ret.append("\n");
+}
+
+ManifestDumpCommand::ManifestDumpCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, false,
+ BuildCmdLineOptions({ARG_VERBOSE, ARG_PATH, ARG_HEX, ARG_JSON})),
+ verbose_(false),
+ json_(false),
+ path_("") {
+ verbose_ = IsFlagPresent(flags, ARG_VERBOSE);
+ json_ = IsFlagPresent(flags, ARG_JSON);
+
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_PATH);
+ if (itr != options.end()) {
+ path_ = itr->second;
+ if (path_.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed("--path: missing pathname");
+ }
+ }
+}
+
+void ManifestDumpCommand::DoCommand() {
+
+ std::string manifestfile;
+
+ if (!path_.empty()) {
+ manifestfile = path_;
+ } else {
+ // We need to find the manifest file by searching the directory
+ // containing the db for files of the form MANIFEST_[0-9]+
+
+ std::vector<std::string> files;
+ Status s = options_.env->GetChildren(db_path_, &files);
+ if (!s.ok()) {
+ std::string err_msg = s.ToString();
+ err_msg.append(": Failed to list the content of ");
+ err_msg.append(db_path_);
+ exec_state_ = LDBCommandExecuteResult::Failed(err_msg);
+ return;
+ }
+ const std::string kManifestNamePrefix = "MANIFEST-";
+ std::string matched_file;
+#ifdef OS_WIN
+ const char kPathDelim = '\\';
+#else
+ const char kPathDelim = '/';
+#endif
+ for (const auto& file_path : files) {
+ // Some Env::GetChildren() return absolute paths. Some directories' path
+ // end with path delim, e.g. '/' or '\\'.
+ size_t pos = file_path.find_last_of(kPathDelim);
+ if (pos == file_path.size() - 1) {
+ continue;
+ }
+ std::string fname;
+ if (pos != std::string::npos) {
+ // Absolute path.
+ fname.assign(file_path, pos + 1, file_path.size() - pos - 1);
+ } else {
+ fname = file_path;
+ }
+ uint64_t file_num = 0;
+ FileType file_type = kLogFile; // Just for initialization
+ if (ParseFileName(fname, &file_num, &file_type) &&
+ file_type == kDescriptorFile) {
+ if (!matched_file.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Multiple MANIFEST files found; use --path to select one");
+ return;
+ } else {
+ matched_file.swap(fname);
+ }
+ }
+ }
+ if (matched_file.empty()) {
+ std::string err_msg("No MANIFEST found in ");
+ err_msg.append(db_path_);
+ exec_state_ = LDBCommandExecuteResult::Failed(err_msg);
+ return;
+ }
+ if (db_path_[db_path_.length() - 1] != '/') {
+ db_path_.append("/");
+ }
+ manifestfile = db_path_ + matched_file;
+ }
+
+ if (verbose_) {
+ fprintf(stdout, "Processing Manifest file %s\n", manifestfile.c_str());
+ }
+
+ DumpManifestFile(options_, manifestfile, verbose_, is_key_hex_, json_);
+
+ if (verbose_) {
+ fprintf(stdout, "Processing Manifest file %s done\n", manifestfile.c_str());
+ }
+}
+
+// ----------------------------------------------------------------------------
+namespace {
+
+void GetLiveFilesChecksumInfoFromVersionSet(Options options,
+ const std::string& db_path,
+ FileChecksumList* checksum_list) {
+ EnvOptions sopt;
+ Status s;
+ std::string dbname(db_path);
+ std::shared_ptr<Cache> tc(NewLRUCache(options.max_open_files - 10,
+ options.table_cache_numshardbits));
+ // Notice we are using the default options not through SanitizeOptions(),
+ // if VersionSet::GetLiveFilesChecksumInfo depends on any option done by
+ // SanitizeOptions(), we need to initialize it manually.
+ options.db_paths.emplace_back(db_path, 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,
+ /*block_cache_tracer=*/nullptr);
+ std::vector<std::string> cf_name_list;
+ s = versions.ListColumnFamilies(&cf_name_list, db_path,
+ options.file_system.get());
+ if (s.ok()) {
+ std::vector<ColumnFamilyDescriptor> cf_list;
+ for (const auto& name : cf_name_list) {
+ cf_list.emplace_back(name, ColumnFamilyOptions(options));
+ }
+ s = versions.Recover(cf_list, true);
+ }
+ if (s.ok()) {
+ s = versions.GetLiveFilesChecksumInfo(checksum_list);
+ }
+ if (!s.ok()) {
+ fprintf(stderr, "Error Status: %s", s.ToString().c_str());
+ }
+}
+
+} // namespace
+
+const std::string FileChecksumDumpCommand::ARG_PATH = "path";
+
+void FileChecksumDumpCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(FileChecksumDumpCommand::Name());
+ ret.append(" [--" + ARG_PATH + "=<path_to_manifest_file>]");
+ ret.append("\n");
+}
+
+FileChecksumDumpCommand::FileChecksumDumpCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false, BuildCmdLineOptions({ARG_PATH})),
+ path_("") {
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_PATH);
+ if (itr != options.end()) {
+ path_ = itr->second;
+ if (path_.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed("--path: missing pathname");
+ }
+ }
+}
+
+void FileChecksumDumpCommand::DoCommand() {
+ // print out the checksum information in the following format:
+ // sst file number, checksum function name, checksum value
+ // sst file number, checksum function name, checksum value
+ // ......
+
+ std::unique_ptr<FileChecksumList> checksum_list(NewFileChecksumList());
+ GetLiveFilesChecksumInfoFromVersionSet(options_, db_path_,
+ checksum_list.get());
+ if (checksum_list != nullptr) {
+ std::vector<uint64_t> file_numbers;
+ std::vector<std::string> checksums;
+ std::vector<std::string> checksum_func_names;
+ Status s = checksum_list->GetAllFileChecksums(&file_numbers, &checksums,
+ &checksum_func_names);
+ if (s.ok()) {
+ for (size_t i = 0; i < file_numbers.size(); i++) {
+ assert(i < file_numbers.size());
+ assert(i < checksums.size());
+ assert(i < checksum_func_names.size());
+ fprintf(stdout, "%" PRId64 ", %s, %s\n", file_numbers[i],
+ checksum_func_names[i].c_str(), checksums[i].c_str());
+ }
+ }
+ fprintf(stdout, "Print SST file checksum information finished \n");
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void ListColumnFamiliesCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ListColumnFamiliesCommand::Name());
+ ret.append("\n");
+}
+
+ListColumnFamiliesCommand::ListColumnFamiliesCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {}
+
+void ListColumnFamiliesCommand::DoCommand() {
+ std::vector<std::string> column_families;
+ Status s = DB::ListColumnFamilies(options_, db_path_, &column_families);
+ if (!s.ok()) {
+ fprintf(stderr, "Error in processing db %s %s\n", db_path_.c_str(),
+ s.ToString().c_str());
+ } else {
+ fprintf(stdout, "Column families in %s: \n{", db_path_.c_str());
+ bool first = true;
+ for (auto cf : column_families) {
+ if (!first) {
+ fprintf(stdout, ", ");
+ }
+ first = false;
+ fprintf(stdout, "%s", cf.c_str());
+ }
+ fprintf(stdout, "}\n");
+ }
+}
+
+void CreateColumnFamilyCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(CreateColumnFamilyCommand::Name());
+ ret.append(" --db=<db_path> <new_column_family_name>");
+ ret.append("\n");
+}
+
+CreateColumnFamilyCommand::CreateColumnFamilyCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true, {ARG_DB}) {
+ if (params.size() != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "new column family name must be specified");
+ } else {
+ new_cf_name_ = params[0];
+ }
+}
+
+void CreateColumnFamilyCommand::DoCommand() {
+ ColumnFamilyHandle* new_cf_handle = nullptr;
+ Status st = db_->CreateColumnFamily(options_, new_cf_name_, &new_cf_handle);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Fail to create new column family: " + st.ToString());
+ }
+ delete new_cf_handle;
+ CloseDB();
+}
+
+void DropColumnFamilyCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DropColumnFamilyCommand::Name());
+ ret.append(" --db=<db_path> <column_family_name_to_drop>");
+ ret.append("\n");
+}
+
+DropColumnFamilyCommand::DropColumnFamilyCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true, {ARG_DB}) {
+ if (params.size() != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "The name of column family to drop must be specified");
+ } else {
+ cf_name_to_drop_ = params[0];
+ }
+}
+
+void DropColumnFamilyCommand::DoCommand() {
+ auto iter = cf_handles_.find(cf_name_to_drop_);
+ if (iter == cf_handles_.end()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Column family: " + cf_name_to_drop_ + " doesn't exist in db.");
+ return;
+ }
+ ColumnFamilyHandle* cf_handle_to_drop = iter->second;
+ Status st = db_->DropColumnFamily(cf_handle_to_drop);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Fail to drop column family: " + st.ToString());
+ }
+ CloseDB();
+}
+
+// ----------------------------------------------------------------------------
+namespace {
+
+// This function only called when it's the sane case of >1 buckets in time-range
+// Also called only when timekv falls between ttl_start and ttl_end provided
+void IncBucketCounts(std::vector<uint64_t>& bucket_counts, int ttl_start,
+ int time_range, int bucket_size, int timekv,
+ int num_buckets) {
+#ifdef NDEBUG
+ (void)time_range;
+ (void)num_buckets;
+#endif
+ assert(time_range > 0 && timekv >= ttl_start && bucket_size > 0 &&
+ timekv < (ttl_start + time_range) && num_buckets > 1);
+ int bucket = (timekv - ttl_start) / bucket_size;
+ bucket_counts[bucket]++;
+}
+
+void PrintBucketCounts(const std::vector<uint64_t>& bucket_counts,
+ int ttl_start, int ttl_end, int bucket_size,
+ int num_buckets) {
+ int time_point = ttl_start;
+ for(int i = 0; i < num_buckets - 1; i++, time_point += bucket_size) {
+ fprintf(stdout, "Keys in range %s to %s : %lu\n",
+ TimeToHumanString(time_point).c_str(),
+ TimeToHumanString(time_point + bucket_size).c_str(),
+ (unsigned long)bucket_counts[i]);
+ }
+ fprintf(stdout, "Keys in range %s to %s : %lu\n",
+ TimeToHumanString(time_point).c_str(),
+ TimeToHumanString(ttl_end).c_str(),
+ (unsigned long)bucket_counts[num_buckets - 1]);
+}
+
+} // namespace
+
+const std::string InternalDumpCommand::ARG_COUNT_ONLY = "count_only";
+const std::string InternalDumpCommand::ARG_COUNT_DELIM = "count_delim";
+const std::string InternalDumpCommand::ARG_STATS = "stats";
+const std::string InternalDumpCommand::ARG_INPUT_KEY_HEX = "input_key_hex";
+
+InternalDumpCommand::InternalDumpCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, true,
+ BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
+ ARG_TO, ARG_MAX_KEYS, ARG_COUNT_ONLY,
+ ARG_COUNT_DELIM, ARG_STATS, ARG_INPUT_KEY_HEX})),
+ has_from_(false),
+ has_to_(false),
+ max_keys_(-1),
+ delim_("."),
+ count_only_(false),
+ count_delim_(false),
+ print_stats_(false),
+ is_input_key_hex_(false) {
+ has_from_ = ParseStringOption(options, ARG_FROM, &from_);
+ has_to_ = ParseStringOption(options, ARG_TO, &to_);
+
+ ParseIntOption(options, ARG_MAX_KEYS, max_keys_, exec_state_);
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_COUNT_DELIM);
+ if (itr != options.end()) {
+ delim_ = itr->second;
+ count_delim_ = true;
+ // fprintf(stdout,"delim = %c\n",delim_[0]);
+ } else {
+ count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM);
+ delim_=".";
+ }
+
+ print_stats_ = IsFlagPresent(flags, ARG_STATS);
+ count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY);
+ is_input_key_hex_ = IsFlagPresent(flags, ARG_INPUT_KEY_HEX);
+
+ if (is_input_key_hex_) {
+ if (has_from_) {
+ from_ = HexToString(from_);
+ }
+ if (has_to_) {
+ to_ = HexToString(to_);
+ }
+ }
+}
+
+void InternalDumpCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(InternalDumpCommand::Name());
+ ret.append(HelpRangeCmdArgs());
+ ret.append(" [--" + ARG_INPUT_KEY_HEX + "]");
+ ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
+ ret.append(" [--" + ARG_COUNT_ONLY + "]");
+ ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]");
+ ret.append(" [--" + ARG_STATS + "]");
+ ret.append("\n");
+}
+
+void InternalDumpCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ if (print_stats_) {
+ std::string stats;
+ if (db_->GetProperty(GetCfHandle(), "rocksdb.stats", &stats)) {
+ fprintf(stdout, "%s\n", stats.c_str());
+ }
+ }
+
+ // Cast as DBImpl to get internal iterator
+ std::vector<KeyVersion> key_versions;
+ Status st = GetAllKeyVersions(db_, GetCfHandle(), from_, to_, max_keys_,
+ &key_versions);
+ if (!st.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ return;
+ }
+ std::string rtype1, rtype2, row, val;
+ rtype2 = "";
+ uint64_t c=0;
+ uint64_t s1=0,s2=0;
+
+ long long count = 0;
+ for (auto& key_version : key_versions) {
+ InternalKey ikey(key_version.user_key, key_version.sequence,
+ static_cast<ValueType>(key_version.type));
+ if (has_to_ && ikey.user_key() == to_) {
+ // GetAllKeyVersions() includes keys with user key `to_`, but idump has
+ // traditionally excluded such keys.
+ break;
+ }
+ ++count;
+ int k;
+ if (count_delim_) {
+ rtype1 = "";
+ s1=0;
+ row = ikey.Encode().ToString();
+ val = key_version.value;
+ for(k=0;row[k]!='\x01' && row[k]!='\0';k++)
+ s1++;
+ for(k=0;val[k]!='\x01' && val[k]!='\0';k++)
+ s1++;
+ for(int j=0;row[j]!=delim_[0] && row[j]!='\0' && row[j]!='\x01';j++)
+ rtype1+=row[j];
+ if(rtype2.compare("") && rtype2.compare(rtype1)!=0) {
+ fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n",
+ rtype2.c_str(), c, s2);
+ c=1;
+ s2=s1;
+ rtype2 = rtype1;
+ } else {
+ c++;
+ s2+=s1;
+ rtype2=rtype1;
+ }
+ }
+
+ if (!count_only_ && !count_delim_) {
+ std::string key = ikey.DebugString(is_key_hex_);
+ std::string value = Slice(key_version.value).ToString(is_value_hex_);
+ std::cout << key << " => " << value << "\n";
+ }
+
+ // Terminate if maximum number of keys have been dumped
+ if (max_keys_ > 0 && count >= max_keys_) break;
+ }
+ if(count_delim_) {
+ fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n",
+ rtype2.c_str(), c, s2);
+ } else {
+ fprintf(stdout, "Internal keys in range: %lld\n", count);
+ }
+}
+
+const std::string DBDumperCommand::ARG_COUNT_ONLY = "count_only";
+const std::string DBDumperCommand::ARG_COUNT_DELIM = "count_delim";
+const std::string DBDumperCommand::ARG_STATS = "stats";
+const std::string DBDumperCommand::ARG_TTL_BUCKET = "bucket";
+
+DBDumperCommand::DBDumperCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true,
+ BuildCmdLineOptions(
+ {ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
+ ARG_TO, ARG_MAX_KEYS, ARG_COUNT_ONLY, ARG_COUNT_DELIM,
+ ARG_STATS, ARG_TTL_START, ARG_TTL_END, ARG_TTL_BUCKET,
+ ARG_TIMESTAMP, ARG_PATH})),
+ null_from_(true),
+ null_to_(true),
+ max_keys_(-1),
+ count_only_(false),
+ count_delim_(false),
+ print_stats_(false) {
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_FROM);
+ if (itr != options.end()) {
+ null_from_ = false;
+ from_ = itr->second;
+ }
+
+ itr = options.find(ARG_TO);
+ if (itr != options.end()) {
+ null_to_ = false;
+ to_ = itr->second;
+ }
+
+ itr = options.find(ARG_MAX_KEYS);
+ if (itr != options.end()) {
+ try {
+#if defined(CYGWIN)
+ max_keys_ = strtol(itr->second.c_str(), 0, 10);
+#else
+ max_keys_ = std::stoi(itr->second);
+#endif
+ } catch (const std::invalid_argument&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS +
+ " has an invalid value");
+ } catch (const std::out_of_range&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_MAX_KEYS + " has a value out-of-range");
+ }
+ }
+ itr = options.find(ARG_COUNT_DELIM);
+ if (itr != options.end()) {
+ delim_ = itr->second;
+ count_delim_ = true;
+ } else {
+ count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM);
+ delim_=".";
+ }
+
+ print_stats_ = IsFlagPresent(flags, ARG_STATS);
+ count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY);
+
+ if (is_key_hex_) {
+ if (!null_from_) {
+ from_ = HexToString(from_);
+ }
+ if (!null_to_) {
+ to_ = HexToString(to_);
+ }
+ }
+
+ itr = options.find(ARG_PATH);
+ if (itr != options.end()) {
+ path_ = itr->second;
+ if (db_path_.empty()) {
+ db_path_ = path_;
+ }
+ }
+}
+
+void DBDumperCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DBDumperCommand::Name());
+ ret.append(HelpRangeCmdArgs());
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
+ ret.append(" [--" + ARG_TIMESTAMP + "]");
+ ret.append(" [--" + ARG_COUNT_ONLY + "]");
+ ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]");
+ ret.append(" [--" + ARG_STATS + "]");
+ ret.append(" [--" + ARG_TTL_BUCKET + "=<N>]");
+ ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
+ ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]");
+ ret.append(" [--" + ARG_PATH + "=<path_to_a_file>]");
+ ret.append("\n");
+}
+
+/**
+ * Handles two separate cases:
+ *
+ * 1) --db is specified - just dump the database.
+ *
+ * 2) --path is specified - determine based on file extension what dumping
+ * function to call. Please note that we intentionally use the extension
+ * and avoid probing the file contents under the assumption that renaming
+ * the files is not a supported scenario.
+ *
+ */
+void DBDumperCommand::DoCommand() {
+ if (!db_) {
+ assert(!path_.empty());
+ std::string fileName = GetFileNameFromPath(path_);
+ uint64_t number;
+ FileType type;
+
+ exec_state_ = LDBCommandExecuteResult::Succeed("");
+
+ if (!ParseFileName(fileName, &number, &type)) {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed("Can't parse file type: " + path_);
+ return;
+ }
+
+ switch (type) {
+ case kLogFile:
+ // TODO(myabandeh): allow configuring is_write_commited
+ DumpWalFile(options_, path_, /* print_header_ */ true,
+ /* print_values_ */ true, true /* is_write_commited */,
+ &exec_state_);
+ break;
+ case kTableFile:
+ DumpSstFile(options_, path_, is_key_hex_, /* show_properties */ true);
+ break;
+ case kDescriptorFile:
+ DumpManifestFile(options_, path_, /* verbose_ */ false, is_key_hex_,
+ /* json_ */ false);
+ break;
+ default:
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "File type not supported: " + path_);
+ break;
+ }
+
+ } else {
+ DoDumpCommand();
+ }
+}
+
+void DBDumperCommand::DoDumpCommand() {
+ assert(nullptr != db_);
+ assert(path_.empty());
+
+ // Parse command line args
+ uint64_t count = 0;
+ if (print_stats_) {
+ std::string stats;
+ if (db_->GetProperty("rocksdb.stats", &stats)) {
+ fprintf(stdout, "%s\n", stats.c_str());
+ }
+ }
+
+ // Setup key iterator
+ ReadOptions scan_read_opts;
+ scan_read_opts.total_order_seek = true;
+ Iterator* iter = db_->NewIterator(scan_read_opts, GetCfHandle());
+ Status st = iter->status();
+ if (!st.ok()) {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed("Iterator error." + st.ToString());
+ }
+
+ if (!null_from_) {
+ iter->Seek(from_);
+ } else {
+ iter->SeekToFirst();
+ }
+
+ int max_keys = max_keys_;
+ int ttl_start;
+ if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) {
+ ttl_start = DBWithTTLImpl::kMinTimestamp; // TTL introduction time
+ }
+ int ttl_end;
+ if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) {
+ ttl_end = DBWithTTLImpl::kMaxTimestamp; // Max time allowed by TTL feature
+ }
+ if (ttl_end < ttl_start) {
+ fprintf(stderr, "Error: End time can't be less than start time\n");
+ delete iter;
+ return;
+ }
+ int time_range = ttl_end - ttl_start;
+ int bucket_size;
+ if (!ParseIntOption(option_map_, ARG_TTL_BUCKET, bucket_size, exec_state_) ||
+ bucket_size <= 0) {
+ bucket_size = time_range; // Will have just 1 bucket by default
+ }
+ //cretaing variables for row count of each type
+ std::string rtype1, rtype2, row, val;
+ rtype2 = "";
+ uint64_t c=0;
+ uint64_t s1=0,s2=0;
+
+ // At this point, bucket_size=0 => time_range=0
+ int num_buckets = (bucket_size >= time_range)
+ ? 1
+ : ((time_range + bucket_size - 1) / bucket_size);
+ std::vector<uint64_t> bucket_counts(num_buckets, 0);
+ if (is_db_ttl_ && !count_only_ && timestamp_ && !count_delim_) {
+ fprintf(stdout, "Dumping key-values from %s to %s\n",
+ TimeToHumanString(ttl_start).c_str(),
+ TimeToHumanString(ttl_end).c_str());
+ }
+
+ HistogramImpl vsize_hist;
+
+ for (; iter->Valid(); iter->Next()) {
+ int rawtime = 0;
+ // If end marker was specified, we stop before it
+ if (!null_to_ && (iter->key().ToString() >= to_))
+ break;
+ // Terminate if maximum number of keys have been dumped
+ if (max_keys == 0)
+ break;
+ if (is_db_ttl_) {
+ TtlIterator* it_ttl = static_cast_with_check<TtlIterator, Iterator>(iter);
+ rawtime = it_ttl->timestamp();
+ if (rawtime < ttl_start || rawtime >= ttl_end) {
+ continue;
+ }
+ }
+ if (max_keys > 0) {
+ --max_keys;
+ }
+ if (is_db_ttl_ && num_buckets > 1) {
+ IncBucketCounts(bucket_counts, ttl_start, time_range, bucket_size,
+ rawtime, num_buckets);
+ }
+ ++count;
+ if (count_delim_) {
+ rtype1 = "";
+ row = iter->key().ToString();
+ val = iter->value().ToString();
+ s1 = row.size()+val.size();
+ for(int j=0;row[j]!=delim_[0] && row[j]!='\0';j++)
+ rtype1+=row[j];
+ if(rtype2.compare("") && rtype2.compare(rtype1)!=0) {
+ fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n",
+ rtype2.c_str(), c, s2);
+ c=1;
+ s2=s1;
+ rtype2 = rtype1;
+ } else {
+ c++;
+ s2+=s1;
+ rtype2=rtype1;
+ }
+
+ }
+
+ if (count_only_) {
+ vsize_hist.Add(iter->value().size());
+ }
+
+ if (!count_only_ && !count_delim_) {
+ if (is_db_ttl_ && timestamp_) {
+ fprintf(stdout, "%s ", TimeToHumanString(rawtime).c_str());
+ }
+ std::string str =
+ PrintKeyValue(iter->key().ToString(), iter->value().ToString(),
+ is_key_hex_, is_value_hex_);
+ fprintf(stdout, "%s\n", str.c_str());
+ }
+ }
+
+ if (num_buckets > 1 && is_db_ttl_) {
+ PrintBucketCounts(bucket_counts, ttl_start, ttl_end, bucket_size,
+ num_buckets);
+ } else if(count_delim_) {
+ fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n",
+ rtype2.c_str(), c, s2);
+ } else {
+ fprintf(stdout, "Keys in range: %" PRIu64 "\n", count);
+ }
+
+ if (count_only_) {
+ fprintf(stdout, "Value size distribution: \n");
+ fprintf(stdout, "%s\n", vsize_hist.ToString().c_str());
+ }
+ // Clean up
+ delete iter;
+}
+
+const std::string ReduceDBLevelsCommand::ARG_NEW_LEVELS = "new_levels";
+const std::string ReduceDBLevelsCommand::ARG_PRINT_OLD_LEVELS =
+ "print_old_levels";
+
+ReduceDBLevelsCommand::ReduceDBLevelsCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_NEW_LEVELS, ARG_PRINT_OLD_LEVELS})),
+ old_levels_(1 << 7),
+ new_levels_(-1),
+ print_old_levels_(false) {
+ ParseIntOption(option_map_, ARG_NEW_LEVELS, new_levels_, exec_state_);
+ print_old_levels_ = IsFlagPresent(flags, ARG_PRINT_OLD_LEVELS);
+
+ if(new_levels_ <= 0) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ " Use --" + ARG_NEW_LEVELS + " to specify a new level number\n");
+ }
+}
+
+std::vector<std::string> ReduceDBLevelsCommand::PrepareArgs(
+ const std::string& db_path, int new_levels, bool print_old_level) {
+ std::vector<std::string> ret;
+ ret.push_back("reduce_levels");
+ ret.push_back("--" + ARG_DB + "=" + db_path);
+ ret.push_back("--" + ARG_NEW_LEVELS + "=" +
+ ROCKSDB_NAMESPACE::ToString(new_levels));
+ if(print_old_level) {
+ ret.push_back("--" + ARG_PRINT_OLD_LEVELS);
+ }
+ return ret;
+}
+
+void ReduceDBLevelsCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ReduceDBLevelsCommand::Name());
+ ret.append(" --" + ARG_NEW_LEVELS + "=<New number of levels>");
+ ret.append(" [--" + ARG_PRINT_OLD_LEVELS + "]");
+ ret.append("\n");
+}
+
+Options ReduceDBLevelsCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.num_levels = old_levels_;
+ opt.max_bytes_for_level_multiplier_additional.resize(opt.num_levels, 1);
+ // Disable size compaction
+ opt.max_bytes_for_level_base = 1ULL << 50;
+ opt.max_bytes_for_level_multiplier = 1;
+ return opt;
+}
+
+Status ReduceDBLevelsCommand::GetOldNumOfLevels(Options& opt,
+ int* levels) {
+ ImmutableDBOptions db_options(opt);
+ EnvOptions soptions;
+ std::shared_ptr<Cache> tc(
+ NewLRUCache(opt.max_open_files - 10, opt.table_cache_numshardbits));
+ const InternalKeyComparator cmp(opt.comparator);
+ WriteController wc(opt.delayed_write_rate);
+ WriteBufferManager wb(opt.db_write_buffer_size);
+ VersionSet versions(db_path_, &db_options, soptions, tc.get(), &wb, &wc,
+ /*block_cache_tracer=*/nullptr);
+ std::vector<ColumnFamilyDescriptor> dummy;
+ ColumnFamilyDescriptor dummy_descriptor(kDefaultColumnFamilyName,
+ ColumnFamilyOptions(opt));
+ dummy.push_back(dummy_descriptor);
+ // We rely the VersionSet::Recover to tell us the internal data structures
+ // in the db. And the Recover() should never do any change
+ // (like LogAndApply) to the manifest file.
+ Status st = versions.Recover(dummy);
+ if (!st.ok()) {
+ return st;
+ }
+ int max = -1;
+ auto default_cfd = versions.GetColumnFamilySet()->GetDefault();
+ for (int i = 0; i < default_cfd->NumberLevels(); i++) {
+ if (default_cfd->current()->storage_info()->NumLevelFiles(i)) {
+ max = i;
+ }
+ }
+
+ *levels = max + 1;
+ return st;
+}
+
+void ReduceDBLevelsCommand::DoCommand() {
+ if (new_levels_ <= 1) {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed("Invalid number of levels.\n");
+ return;
+ }
+
+ Status st;
+ Options opt = PrepareOptionsForOpenDB();
+ int old_level_num = -1;
+ opt.file_system.reset(new LegacyFileSystemWrapper(opt.env));
+ ;
+ st = GetOldNumOfLevels(opt, &old_level_num);
+ if (!st.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ return;
+ }
+
+ if (print_old_levels_) {
+ fprintf(stdout, "The old number of levels in use is %d\n", old_level_num);
+ }
+
+ if (old_level_num <= new_levels_) {
+ return;
+ }
+
+ old_levels_ = old_level_num;
+
+ OpenDB();
+ if (exec_state_.IsFailed()) {
+ return;
+ }
+ assert(db_ != nullptr);
+ // Compact the whole DB to put all files to the highest level.
+ fprintf(stdout, "Compacting the db...\n");
+ db_->CompactRange(CompactRangeOptions(), GetCfHandle(), nullptr, nullptr);
+ CloseDB();
+
+ EnvOptions soptions;
+ st = VersionSet::ReduceNumberOfLevels(db_path_, &opt, soptions, new_levels_);
+ if (!st.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ return;
+ }
+}
+
+const std::string ChangeCompactionStyleCommand::ARG_OLD_COMPACTION_STYLE =
+ "old_compaction_style";
+const std::string ChangeCompactionStyleCommand::ARG_NEW_COMPACTION_STYLE =
+ "new_compaction_style";
+
+ChangeCompactionStyleCommand::ChangeCompactionStyleCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions(
+ {ARG_OLD_COMPACTION_STYLE, ARG_NEW_COMPACTION_STYLE})),
+ old_compaction_style_(-1),
+ new_compaction_style_(-1) {
+ ParseIntOption(option_map_, ARG_OLD_COMPACTION_STYLE, old_compaction_style_,
+ exec_state_);
+ if (old_compaction_style_ != kCompactionStyleLevel &&
+ old_compaction_style_ != kCompactionStyleUniversal) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Use --" + ARG_OLD_COMPACTION_STYLE + " to specify old compaction " +
+ "style. Check ldb help for proper compaction style value.\n");
+ return;
+ }
+
+ ParseIntOption(option_map_, ARG_NEW_COMPACTION_STYLE, new_compaction_style_,
+ exec_state_);
+ if (new_compaction_style_ != kCompactionStyleLevel &&
+ new_compaction_style_ != kCompactionStyleUniversal) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Use --" + ARG_NEW_COMPACTION_STYLE + " to specify new compaction " +
+ "style. Check ldb help for proper compaction style value.\n");
+ return;
+ }
+
+ if (new_compaction_style_ == old_compaction_style_) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Old compaction style is the same as new compaction style. "
+ "Nothing to do.\n");
+ return;
+ }
+
+ if (old_compaction_style_ == kCompactionStyleUniversal &&
+ new_compaction_style_ == kCompactionStyleLevel) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Convert from universal compaction to level compaction. "
+ "Nothing to do.\n");
+ return;
+ }
+}
+
+void ChangeCompactionStyleCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ChangeCompactionStyleCommand::Name());
+ ret.append(" --" + ARG_OLD_COMPACTION_STYLE + "=<Old compaction style: 0 " +
+ "for level compaction, 1 for universal compaction>");
+ ret.append(" --" + ARG_NEW_COMPACTION_STYLE + "=<New compaction style: 0 " +
+ "for level compaction, 1 for universal compaction>");
+ ret.append("\n");
+}
+
+Options ChangeCompactionStyleCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+
+ if (old_compaction_style_ == kCompactionStyleLevel &&
+ new_compaction_style_ == kCompactionStyleUniversal) {
+ // In order to convert from level compaction to universal compaction, we
+ // need to compact all data into a single file and move it to level 0.
+ opt.disable_auto_compactions = true;
+ opt.target_file_size_base = INT_MAX;
+ opt.target_file_size_multiplier = 1;
+ opt.max_bytes_for_level_base = INT_MAX;
+ opt.max_bytes_for_level_multiplier = 1;
+ }
+
+ return opt;
+}
+
+void ChangeCompactionStyleCommand::DoCommand() {
+ // print db stats before we have made any change
+ std::string property;
+ std::string files_per_level;
+ for (int i = 0; i < db_->NumberLevels(GetCfHandle()); i++) {
+ db_->GetProperty(GetCfHandle(),
+ "rocksdb.num-files-at-level" + NumberToString(i),
+ &property);
+
+ // format print string
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%s%s", (i ? "," : ""), property.c_str());
+ files_per_level += buf;
+ }
+ fprintf(stdout, "files per level before compaction: %s\n",
+ files_per_level.c_str());
+
+ // manual compact into a single file and move the file to level 0
+ CompactRangeOptions compact_options;
+ compact_options.change_level = true;
+ compact_options.target_level = 0;
+ db_->CompactRange(compact_options, GetCfHandle(), nullptr, nullptr);
+
+ // verify compaction result
+ files_per_level = "";
+ int num_files = 0;
+ for (int i = 0; i < db_->NumberLevels(GetCfHandle()); i++) {
+ db_->GetProperty(GetCfHandle(),
+ "rocksdb.num-files-at-level" + NumberToString(i),
+ &property);
+
+ // format print string
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%s%s", (i ? "," : ""), property.c_str());
+ files_per_level += buf;
+
+ num_files = atoi(property.c_str());
+
+ // level 0 should have only 1 file
+ if (i == 0 && num_files != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Number of db files at "
+ "level 0 after compaction is " +
+ ToString(num_files) + ", not 1.\n");
+ return;
+ }
+ // other levels should have no file
+ if (i > 0 && num_files != 0) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Number of db files at "
+ "level " +
+ ToString(i) + " after compaction is " + ToString(num_files) +
+ ", not 0.\n");
+ return;
+ }
+ }
+
+ fprintf(stdout, "files per level after compaction: %s\n",
+ files_per_level.c_str());
+}
+
+// ----------------------------------------------------------------------------
+
+namespace {
+
+struct StdErrReporter : public log::Reader::Reporter {
+ void Corruption(size_t /*bytes*/, const Status& s) override {
+ std::cerr << "Corruption detected in log file " << s.ToString() << "\n";
+ }
+};
+
+class InMemoryHandler : public WriteBatch::Handler {
+ public:
+ InMemoryHandler(std::stringstream& row, bool print_values,
+ bool write_after_commit = false)
+ : Handler(),
+ row_(row),
+ print_values_(print_values),
+ write_after_commit_(write_after_commit) {}
+
+ void commonPutMerge(const Slice& key, const Slice& value) {
+ std::string k = LDBCommand::StringToHex(key.ToString());
+ if (print_values_) {
+ std::string v = LDBCommand::StringToHex(value.ToString());
+ row_ << k << " : ";
+ row_ << v << " ";
+ } else {
+ row_ << k << " ";
+ }
+ }
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
+ row_ << "PUT(" << cf << ") : ";
+ commonPutMerge(key, value);
+ return Status::OK();
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
+ row_ << "MERGE(" << cf << ") : ";
+ commonPutMerge(key, value);
+ return Status::OK();
+ }
+
+ Status MarkNoop(bool) override {
+ row_ << "NOOP ";
+ return Status::OK();
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ row_ << "DELETE(" << cf << ") : ";
+ row_ << LDBCommand::StringToHex(key.ToString()) << " ";
+ return Status::OK();
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ row_ << "SINGLE_DELETE(" << cf << ") : ";
+ row_ << LDBCommand::StringToHex(key.ToString()) << " ";
+ return Status::OK();
+ }
+
+ Status DeleteRangeCF(uint32_t cf, const Slice& begin_key,
+ const Slice& end_key) override {
+ row_ << "DELETE_RANGE(" << cf << ") : ";
+ row_ << LDBCommand::StringToHex(begin_key.ToString()) << " ";
+ row_ << LDBCommand::StringToHex(end_key.ToString()) << " ";
+ return Status::OK();
+ }
+
+ Status MarkBeginPrepare(bool unprepare) override {
+ row_ << "BEGIN_PREPARE(";
+ row_ << (unprepare ? "true" : "false") << ") ";
+ return Status::OK();
+ }
+
+ Status MarkEndPrepare(const Slice& xid) override {
+ row_ << "END_PREPARE(";
+ row_ << LDBCommand::StringToHex(xid.ToString()) << ") ";
+ return Status::OK();
+ }
+
+ Status MarkRollback(const Slice& xid) override {
+ row_ << "ROLLBACK(";
+ row_ << LDBCommand::StringToHex(xid.ToString()) << ") ";
+ return Status::OK();
+ }
+
+ Status MarkCommit(const Slice& xid) override {
+ row_ << "COMMIT(";
+ row_ << LDBCommand::StringToHex(xid.ToString()) << ") ";
+ return Status::OK();
+ }
+
+ ~InMemoryHandler() override {}
+
+ protected:
+ bool WriteAfterCommit() const override { return write_after_commit_; }
+
+ private:
+ std::stringstream& row_;
+ bool print_values_;
+ bool write_after_commit_;
+};
+
+void DumpWalFile(Options options, std::string wal_file, bool print_header,
+ bool print_values, bool is_write_committed,
+ LDBCommandExecuteResult* exec_state) {
+ Env* env = options.env;
+ EnvOptions soptions(options);
+ std::unique_ptr<SequentialFileReader> wal_file_reader;
+
+ Status status;
+ {
+ std::unique_ptr<SequentialFile> file;
+ status = env->NewSequentialFile(wal_file, &file, soptions);
+ if (status.ok()) {
+ wal_file_reader.reset(new SequentialFileReader(
+ NewLegacySequentialFileWrapper(file), wal_file));
+ }
+ }
+ if (!status.ok()) {
+ if (exec_state) {
+ *exec_state = LDBCommandExecuteResult::Failed("Failed to open WAL file " +
+ status.ToString());
+ } else {
+ std::cerr << "Error: Failed to open WAL file " << status.ToString()
+ << std::endl;
+ }
+ } else {
+ StdErrReporter reporter;
+ uint64_t log_number;
+ FileType type;
+
+ // we need the log number, but ParseFilename expects dbname/NNN.log.
+ std::string sanitized = wal_file;
+ size_t lastslash = sanitized.rfind('/');
+ if (lastslash != std::string::npos)
+ sanitized = sanitized.substr(lastslash + 1);
+ if (!ParseFileName(sanitized, &log_number, &type)) {
+ // bogus input, carry on as best we can
+ log_number = 0;
+ }
+ log::Reader reader(options.info_log, std::move(wal_file_reader), &reporter,
+ true /* checksum */, log_number);
+ std::string scratch;
+ WriteBatch batch;
+ Slice record;
+ std::stringstream row;
+ if (print_header) {
+ std::cout << "Sequence,Count,ByteSize,Physical Offset,Key(s)";
+ if (print_values) {
+ std::cout << " : value ";
+ }
+ std::cout << "\n";
+ }
+ while (reader.ReadRecord(&record, &scratch)) {
+ row.str("");
+ if (record.size() < WriteBatchInternal::kHeader) {
+ reporter.Corruption(record.size(),
+ Status::Corruption("log record too small"));
+ } else {
+ WriteBatchInternal::SetContents(&batch, record);
+ row << WriteBatchInternal::Sequence(&batch) << ",";
+ row << WriteBatchInternal::Count(&batch) << ",";
+ row << WriteBatchInternal::ByteSize(&batch) << ",";
+ row << reader.LastRecordOffset() << ",";
+ InMemoryHandler handler(row, print_values, is_write_committed);
+ batch.Iterate(&handler);
+ row << "\n";
+ }
+ std::cout << row.str();
+ }
+ }
+}
+
+} // namespace
+
+const std::string WALDumperCommand::ARG_WAL_FILE = "walfile";
+const std::string WALDumperCommand::ARG_WRITE_COMMITTED = "write_committed";
+const std::string WALDumperCommand::ARG_PRINT_VALUE = "print_value";
+const std::string WALDumperCommand::ARG_PRINT_HEADER = "header";
+
+WALDumperCommand::WALDumperCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true,
+ BuildCmdLineOptions({ARG_WAL_FILE, ARG_WRITE_COMMITTED,
+ ARG_PRINT_HEADER, ARG_PRINT_VALUE})),
+ print_header_(false),
+ print_values_(false),
+ is_write_committed_(false) {
+ wal_file_.clear();
+
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_WAL_FILE);
+ if (itr != options.end()) {
+ wal_file_ = itr->second;
+ }
+
+
+ print_header_ = IsFlagPresent(flags, ARG_PRINT_HEADER);
+ print_values_ = IsFlagPresent(flags, ARG_PRINT_VALUE);
+ is_write_committed_ = ParseBooleanOption(options, ARG_WRITE_COMMITTED, true);
+
+ if (wal_file_.empty()) {
+ exec_state_ = LDBCommandExecuteResult::Failed("Argument " + ARG_WAL_FILE +
+ " must be specified.");
+ }
+}
+
+void WALDumperCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(WALDumperCommand::Name());
+ ret.append(" --" + ARG_WAL_FILE + "=<write_ahead_log_file_path>");
+ ret.append(" [--" + ARG_PRINT_HEADER + "] ");
+ ret.append(" [--" + ARG_PRINT_VALUE + "] ");
+ ret.append(" [--" + ARG_WRITE_COMMITTED + "=true|false] ");
+ ret.append("\n");
+}
+
+void WALDumperCommand::DoCommand() {
+ DumpWalFile(options_, wal_file_, print_header_, print_values_,
+ is_write_committed_, &exec_state_);
+}
+
+// ----------------------------------------------------------------------------
+
+GetCommand::GetCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, true,
+ BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
+ if (params.size() != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "<key> must be specified for the get command");
+ } else {
+ key_ = params.at(0);
+ }
+
+ if (is_key_hex_) {
+ key_ = HexToString(key_);
+ }
+}
+
+void GetCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(GetCommand::Name());
+ ret.append(" <key>");
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append("\n");
+}
+
+void GetCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ std::string value;
+ Status st = db_->Get(ReadOptions(), GetCfHandle(), key_, &value);
+ if (st.ok()) {
+ fprintf(stdout, "%s\n",
+ (is_value_hex_ ? StringToHex(value) : value).c_str());
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+ApproxSizeCommand::ApproxSizeCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true,
+ BuildCmdLineOptions(
+ {ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, ARG_TO})) {
+ if (options.find(ARG_FROM) != options.end()) {
+ start_key_ = options.find(ARG_FROM)->second;
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_FROM + " must be specified for approxsize command");
+ return;
+ }
+
+ if (options.find(ARG_TO) != options.end()) {
+ end_key_ = options.find(ARG_TO)->second;
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_TO + " must be specified for approxsize command");
+ return;
+ }
+
+ if (is_key_hex_) {
+ start_key_ = HexToString(start_key_);
+ end_key_ = HexToString(end_key_);
+ }
+}
+
+void ApproxSizeCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ApproxSizeCommand::Name());
+ ret.append(HelpRangeCmdArgs());
+ ret.append("\n");
+}
+
+void ApproxSizeCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Range ranges[1];
+ ranges[0] = Range(start_key_, end_key_);
+ uint64_t sizes[1];
+ db_->GetApproximateSizes(GetCfHandle(), ranges, 1, sizes);
+ fprintf(stdout, "%lu\n", (unsigned long)sizes[0]);
+ /* Weird that GetApproximateSizes() returns void, although documentation
+ * says that it returns a Status object.
+ if (!st.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+ */
+}
+
+// ----------------------------------------------------------------------------
+
+BatchPutCommand::BatchPutCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
+ ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) {
+ if (params.size() < 2) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "At least one <key> <value> pair must be specified batchput.");
+ } else if (params.size() % 2 != 0) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Equal number of <key>s and <value>s must be specified for batchput.");
+ } else {
+ for (size_t i = 0; i < params.size(); i += 2) {
+ std::string key = params.at(i);
+ std::string value = params.at(i + 1);
+ key_values_.push_back(std::pair<std::string, std::string>(
+ is_key_hex_ ? HexToString(key) : key,
+ is_value_hex_ ? HexToString(value) : value));
+ }
+ }
+ create_if_missing_ = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING);
+}
+
+void BatchPutCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(BatchPutCommand::Name());
+ ret.append(" <key> <value> [<key> <value>] [..]");
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append("\n");
+}
+
+void BatchPutCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ WriteBatch batch;
+
+ for (std::vector<std::pair<std::string, std::string>>::const_iterator itr =
+ key_values_.begin();
+ itr != key_values_.end(); ++itr) {
+ batch.Put(GetCfHandle(), itr->first, itr->second);
+ }
+ Status st = db_->Write(WriteOptions(), &batch);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+Options BatchPutCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.create_if_missing = create_if_missing_;
+ return opt;
+}
+
+// ----------------------------------------------------------------------------
+
+ScanCommand::ScanCommand(const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, true,
+ BuildCmdLineOptions({ARG_TTL, ARG_NO_VALUE, ARG_HEX, ARG_KEY_HEX,
+ ARG_TO, ARG_VALUE_HEX, ARG_FROM, ARG_TIMESTAMP,
+ ARG_MAX_KEYS, ARG_TTL_START, ARG_TTL_END})),
+ start_key_specified_(false),
+ end_key_specified_(false),
+ max_keys_scanned_(-1),
+ no_value_(false) {
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_FROM);
+ if (itr != options.end()) {
+ start_key_ = itr->second;
+ if (is_key_hex_) {
+ start_key_ = HexToString(start_key_);
+ }
+ start_key_specified_ = true;
+ }
+ itr = options.find(ARG_TO);
+ if (itr != options.end()) {
+ end_key_ = itr->second;
+ if (is_key_hex_) {
+ end_key_ = HexToString(end_key_);
+ }
+ end_key_specified_ = true;
+ }
+
+ std::vector<std::string>::const_iterator vitr =
+ std::find(flags.begin(), flags.end(), ARG_NO_VALUE);
+ if (vitr != flags.end()) {
+ no_value_ = true;
+ }
+
+ itr = options.find(ARG_MAX_KEYS);
+ if (itr != options.end()) {
+ try {
+#if defined(CYGWIN)
+ max_keys_scanned_ = strtol(itr->second.c_str(), 0, 10);
+#else
+ max_keys_scanned_ = std::stoi(itr->second);
+#endif
+ } catch (const std::invalid_argument&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS +
+ " has an invalid value");
+ } catch (const std::out_of_range&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_MAX_KEYS + " has a value out-of-range");
+ }
+ }
+}
+
+void ScanCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ScanCommand::Name());
+ ret.append(HelpRangeCmdArgs());
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append(" [--" + ARG_TIMESTAMP + "]");
+ ret.append(" [--" + ARG_MAX_KEYS + "=<N>q] ");
+ ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
+ ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]");
+ ret.append(" [--" + ARG_NO_VALUE + "]");
+ ret.append("\n");
+}
+
+void ScanCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ int num_keys_scanned = 0;
+ ReadOptions scan_read_opts;
+ scan_read_opts.total_order_seek = true;
+ Iterator* it = db_->NewIterator(scan_read_opts, GetCfHandle());
+ if (start_key_specified_) {
+ it->Seek(start_key_);
+ } else {
+ it->SeekToFirst();
+ }
+ int ttl_start;
+ if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) {
+ ttl_start = DBWithTTLImpl::kMinTimestamp; // TTL introduction time
+ }
+ int ttl_end;
+ if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) {
+ ttl_end = DBWithTTLImpl::kMaxTimestamp; // Max time allowed by TTL feature
+ }
+ if (ttl_end < ttl_start) {
+ fprintf(stderr, "Error: End time can't be less than start time\n");
+ delete it;
+ return;
+ }
+ if (is_db_ttl_ && timestamp_) {
+ fprintf(stdout, "Scanning key-values from %s to %s\n",
+ TimeToHumanString(ttl_start).c_str(),
+ TimeToHumanString(ttl_end).c_str());
+ }
+ for ( ;
+ it->Valid() && (!end_key_specified_ || it->key().ToString() < end_key_);
+ it->Next()) {
+ if (is_db_ttl_) {
+ TtlIterator* it_ttl = static_cast_with_check<TtlIterator, Iterator>(it);
+ int rawtime = it_ttl->timestamp();
+ if (rawtime < ttl_start || rawtime >= ttl_end) {
+ continue;
+ }
+ if (timestamp_) {
+ fprintf(stdout, "%s ", TimeToHumanString(rawtime).c_str());
+ }
+ }
+
+ Slice key_slice = it->key();
+
+ std::string formatted_key;
+ if (is_key_hex_) {
+ formatted_key = "0x" + key_slice.ToString(true /* hex */);
+ key_slice = formatted_key;
+ } else if (ldb_options_.key_formatter) {
+ formatted_key = ldb_options_.key_formatter->Format(key_slice);
+ key_slice = formatted_key;
+ }
+
+ if (no_value_) {
+ fprintf(stdout, "%.*s\n", static_cast<int>(key_slice.size()),
+ key_slice.data());
+ } else {
+ Slice val_slice = it->value();
+ std::string formatted_value;
+ if (is_value_hex_) {
+ formatted_value = "0x" + val_slice.ToString(true /* hex */);
+ val_slice = formatted_value;
+ }
+ fprintf(stdout, "%.*s : %.*s\n", static_cast<int>(key_slice.size()),
+ key_slice.data(), static_cast<int>(val_slice.size()),
+ val_slice.data());
+ }
+
+ num_keys_scanned++;
+ if (max_keys_scanned_ >= 0 && num_keys_scanned >= max_keys_scanned_) {
+ break;
+ }
+ }
+ if (!it->status().ok()) { // Check for any errors found during the scan
+ exec_state_ = LDBCommandExecuteResult::Failed(it->status().ToString());
+ }
+ delete it;
+}
+
+// ----------------------------------------------------------------------------
+
+DeleteCommand::DeleteCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
+ if (params.size() != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "KEY must be specified for the delete command");
+ } else {
+ key_ = params.at(0);
+ if (is_key_hex_) {
+ key_ = HexToString(key_);
+ }
+ }
+}
+
+void DeleteCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DeleteCommand::Name() + " <key>");
+ ret.append("\n");
+}
+
+void DeleteCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Status st = db_->Delete(WriteOptions(), GetCfHandle(), key_);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+DeleteRangeCommand::DeleteRangeCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
+ if (params.size() != 2) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "begin and end keys must be specified for the delete command");
+ } else {
+ begin_key_ = params.at(0);
+ end_key_ = params.at(1);
+ if (is_key_hex_) {
+ begin_key_ = HexToString(begin_key_);
+ end_key_ = HexToString(end_key_);
+ }
+ }
+}
+
+void DeleteRangeCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DeleteRangeCommand::Name() + " <begin key> <end key>");
+ ret.append("\n");
+}
+
+void DeleteRangeCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Status st =
+ db_->DeleteRange(WriteOptions(), GetCfHandle(), begin_key_, end_key_);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+PutCommand::PutCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false,
+ BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
+ ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) {
+ if (params.size() != 2) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "<key> and <value> must be specified for the put command");
+ } else {
+ key_ = params.at(0);
+ value_ = params.at(1);
+ }
+
+ if (is_key_hex_) {
+ key_ = HexToString(key_);
+ }
+
+ if (is_value_hex_) {
+ value_ = HexToString(value_);
+ }
+ create_if_missing_ = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING);
+}
+
+void PutCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(PutCommand::Name());
+ ret.append(" <key> <value> ");
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append("\n");
+}
+
+void PutCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Status st = db_->Put(WriteOptions(), GetCfHandle(), key_, value_);
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+Options PutCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.create_if_missing = create_if_missing_;
+ return opt;
+}
+
+// ----------------------------------------------------------------------------
+
+const char* DBQuerierCommand::HELP_CMD = "help";
+const char* DBQuerierCommand::GET_CMD = "get";
+const char* DBQuerierCommand::PUT_CMD = "put";
+const char* DBQuerierCommand::DELETE_CMD = "delete";
+
+DBQuerierCommand::DBQuerierCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, false,
+ BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
+
+}
+
+void DBQuerierCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DBQuerierCommand::Name());
+ ret.append(" [--" + ARG_TTL + "]");
+ ret.append("\n");
+ ret.append(" Starts a REPL shell. Type help for list of available "
+ "commands.");
+ ret.append("\n");
+}
+
+void DBQuerierCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ ReadOptions read_options;
+ WriteOptions write_options;
+
+ std::string line;
+ std::string key;
+ std::string value;
+ while (getline(std::cin, line, '\n')) {
+ // Parse line into std::vector<std::string>
+ std::vector<std::string> tokens;
+ size_t pos = 0;
+ while (true) {
+ size_t pos2 = line.find(' ', pos);
+ if (pos2 == std::string::npos) {
+ break;
+ }
+ tokens.push_back(line.substr(pos, pos2-pos));
+ pos = pos2 + 1;
+ }
+ tokens.push_back(line.substr(pos));
+
+ const std::string& cmd = tokens[0];
+
+ if (cmd == HELP_CMD) {
+ fprintf(stdout,
+ "get <key>\n"
+ "put <key> <value>\n"
+ "delete <key>\n");
+ } else if (cmd == DELETE_CMD && tokens.size() == 2) {
+ key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]);
+ db_->Delete(write_options, GetCfHandle(), Slice(key));
+ fprintf(stdout, "Successfully deleted %s\n", tokens[1].c_str());
+ } else if (cmd == PUT_CMD && tokens.size() == 3) {
+ key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]);
+ value = (is_value_hex_ ? HexToString(tokens[2]) : tokens[2]);
+ db_->Put(write_options, GetCfHandle(), Slice(key), Slice(value));
+ fprintf(stdout, "Successfully put %s %s\n",
+ tokens[1].c_str(), tokens[2].c_str());
+ } else if (cmd == GET_CMD && tokens.size() == 2) {
+ key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]);
+ if (db_->Get(read_options, GetCfHandle(), Slice(key), &value).ok()) {
+ fprintf(stdout, "%s\n", PrintKeyValue(key, value,
+ is_key_hex_, is_value_hex_).c_str());
+ } else {
+ fprintf(stdout, "Not found %s\n", tokens[1].c_str());
+ }
+ } else {
+ fprintf(stdout, "Unknown command %s\n", line.c_str());
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+CheckConsistencyCommand::CheckConsistencyCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {}
+
+void CheckConsistencyCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(CheckConsistencyCommand::Name());
+ ret.append("\n");
+}
+
+void CheckConsistencyCommand::DoCommand() {
+ Options opt = PrepareOptionsForOpenDB();
+ opt.paranoid_checks = true;
+ if (!exec_state_.IsNotStarted()) {
+ return;
+ }
+ DB* db;
+ Status st = DB::OpenForReadOnly(opt, db_path_, &db, false);
+ delete db;
+ if (st.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+const std::string CheckPointCommand::ARG_CHECKPOINT_DIR = "checkpoint_dir";
+
+CheckPointCommand::CheckPointCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false /* is_read_only */,
+ BuildCmdLineOptions({ARG_CHECKPOINT_DIR})) {
+ auto itr = options.find(ARG_CHECKPOINT_DIR);
+ if (itr != options.end()) {
+ checkpoint_dir_ = itr->second;
+ }
+}
+
+void CheckPointCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(CheckPointCommand::Name());
+ ret.append(" [--" + ARG_CHECKPOINT_DIR + "] ");
+ ret.append("\n");
+}
+
+void CheckPointCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Checkpoint* checkpoint;
+ Status status = Checkpoint::Create(db_, &checkpoint);
+ status = checkpoint->CreateCheckpoint(checkpoint_dir_);
+ if (status.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(status.ToString());
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+RepairCommand::RepairCommand(const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {}
+
+void RepairCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(RepairCommand::Name());
+ ret.append("\n");
+}
+
+void RepairCommand::DoCommand() {
+ Options options = PrepareOptionsForOpenDB();
+ options.info_log.reset(new StderrLogger(InfoLogLevel::WARN_LEVEL));
+ Status status = RepairDB(db_path_, options);
+ if (status.ok()) {
+ fprintf(stdout, "OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(status.ToString());
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+const std::string BackupableCommand::ARG_NUM_THREADS = "num_threads";
+const std::string BackupableCommand::ARG_BACKUP_ENV_URI = "backup_env_uri";
+const std::string BackupableCommand::ARG_BACKUP_DIR = "backup_dir";
+const std::string BackupableCommand::ARG_STDERR_LOG_LEVEL = "stderr_log_level";
+
+BackupableCommand::BackupableCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, false /* is_read_only */,
+ BuildCmdLineOptions({ARG_BACKUP_ENV_URI, ARG_BACKUP_DIR,
+ ARG_NUM_THREADS, ARG_STDERR_LOG_LEVEL})),
+ num_threads_(1) {
+ auto itr = options.find(ARG_NUM_THREADS);
+ if (itr != options.end()) {
+ num_threads_ = std::stoi(itr->second);
+ }
+ itr = options.find(ARG_BACKUP_ENV_URI);
+ if (itr != options.end()) {
+ backup_env_uri_ = itr->second;
+ }
+ itr = options.find(ARG_BACKUP_DIR);
+ if (itr == options.end()) {
+ exec_state_ = LDBCommandExecuteResult::Failed("--" + ARG_BACKUP_DIR +
+ ": missing backup directory");
+ } else {
+ backup_dir_ = itr->second;
+ }
+
+ itr = options.find(ARG_STDERR_LOG_LEVEL);
+ if (itr != options.end()) {
+ int stderr_log_level = std::stoi(itr->second);
+ if (stderr_log_level < 0 ||
+ stderr_log_level >= InfoLogLevel::NUM_INFO_LOG_LEVELS) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_STDERR_LOG_LEVEL + " must be >= 0 and < " +
+ std::to_string(InfoLogLevel::NUM_INFO_LOG_LEVELS) + ".");
+ } else {
+ logger_.reset(
+ new StderrLogger(static_cast<InfoLogLevel>(stderr_log_level)));
+ }
+ }
+}
+
+void BackupableCommand::Help(const std::string& name, std::string& ret) {
+ ret.append(" ");
+ ret.append(name);
+ ret.append(" [--" + ARG_BACKUP_ENV_URI + "] ");
+ ret.append(" [--" + ARG_BACKUP_DIR + "] ");
+ ret.append(" [--" + ARG_NUM_THREADS + "] ");
+ ret.append(" [--" + ARG_STDERR_LOG_LEVEL + "=<int (InfoLogLevel)>] ");
+ ret.append("\n");
+}
+
+// ----------------------------------------------------------------------------
+
+BackupCommand::BackupCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : BackupableCommand(params, options, flags) {}
+
+void BackupCommand::Help(std::string& ret) {
+ BackupableCommand::Help(Name(), ret);
+}
+
+void BackupCommand::DoCommand() {
+ BackupEngine* backup_engine;
+ Status status;
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ fprintf(stdout, "open db OK\n");
+ Env* custom_env = nullptr;
+ Env::LoadEnv(backup_env_uri_, &custom_env, &backup_env_guard_);
+ assert(custom_env != nullptr);
+
+ BackupableDBOptions backup_options =
+ BackupableDBOptions(backup_dir_, custom_env);
+ backup_options.info_log = logger_.get();
+ backup_options.max_background_operations = num_threads_;
+ status = BackupEngine::Open(custom_env, backup_options, &backup_engine);
+ if (status.ok()) {
+ fprintf(stdout, "open backup engine OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(status.ToString());
+ return;
+ }
+ status = backup_engine->CreateNewBackup(db_);
+ if (status.ok()) {
+ fprintf(stdout, "create new backup OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(status.ToString());
+ return;
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+RestoreCommand::RestoreCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : BackupableCommand(params, options, flags) {}
+
+void RestoreCommand::Help(std::string& ret) {
+ BackupableCommand::Help(Name(), ret);
+}
+
+void RestoreCommand::DoCommand() {
+ Env* custom_env = nullptr;
+ Env::LoadEnv(backup_env_uri_, &custom_env, &backup_env_guard_);
+ assert(custom_env != nullptr);
+
+ std::unique_ptr<BackupEngineReadOnly> restore_engine;
+ Status status;
+ {
+ BackupableDBOptions opts(backup_dir_, custom_env);
+ opts.info_log = logger_.get();
+ opts.max_background_operations = num_threads_;
+ BackupEngineReadOnly* raw_restore_engine_ptr;
+ status =
+ BackupEngineReadOnly::Open(custom_env, opts, &raw_restore_engine_ptr);
+ if (status.ok()) {
+ restore_engine.reset(raw_restore_engine_ptr);
+ }
+ }
+ if (status.ok()) {
+ fprintf(stdout, "open restore engine OK\n");
+ status = restore_engine->RestoreDBFromLatestBackup(db_path_, db_path_);
+ }
+ if (status.ok()) {
+ fprintf(stdout, "restore from backup OK\n");
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(status.ToString());
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+namespace {
+
+void DumpSstFile(Options options, std::string filename, bool output_hex,
+ bool show_properties) {
+ std::string from_key;
+ std::string to_key;
+ if (filename.length() <= 4 ||
+ filename.rfind(".sst") != filename.length() - 4) {
+ std::cout << "Invalid sst file name." << std::endl;
+ return;
+ }
+ // no verification
+ // TODO: add support for decoding blob indexes in ldb as well
+ ROCKSDB_NAMESPACE::SstFileDumper dumper(
+ options, filename, /* verify_checksum */ false, output_hex,
+ /* decode_blob_index */ false);
+ Status st = dumper.ReadSequential(true, std::numeric_limits<uint64_t>::max(),
+ false, // has_from
+ from_key, false, // has_to
+ to_key);
+ if (!st.ok()) {
+ std::cerr << "Error in reading SST file " << filename << st.ToString()
+ << std::endl;
+ return;
+ }
+
+ if (show_properties) {
+ const ROCKSDB_NAMESPACE::TableProperties* table_properties;
+
+ std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties>
+ table_properties_from_reader;
+ st = dumper.ReadTableProperties(&table_properties_from_reader);
+ if (!st.ok()) {
+ std::cerr << filename << ": " << st.ToString()
+ << ". Try to use initial table properties" << std::endl;
+ table_properties = dumper.GetInitTableProperties();
+ } else {
+ table_properties = table_properties_from_reader.get();
+ }
+ if (table_properties != nullptr) {
+ std::cout << std::endl << "Table Properties:" << std::endl;
+ std::cout << table_properties->ToString("\n") << std::endl;
+ }
+ }
+}
+
+} // namespace
+
+DBFileDumperCommand::DBFileDumperCommand(
+ const std::vector<std::string>& /*params*/,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true, BuildCmdLineOptions({})) {}
+
+void DBFileDumperCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(DBFileDumperCommand::Name());
+ ret.append("\n");
+}
+
+void DBFileDumperCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ Status s;
+
+ std::cout << "Manifest File" << std::endl;
+ std::cout << "==============================" << std::endl;
+ std::string manifest_filename;
+ s = ReadFileToString(db_->GetEnv(), CurrentFileName(db_->GetName()),
+ &manifest_filename);
+ if (!s.ok() || manifest_filename.empty() ||
+ manifest_filename.back() != '\n') {
+ std::cerr << "Error when reading CURRENT file "
+ << CurrentFileName(db_->GetName()) << std::endl;
+ }
+ // remove the trailing '\n'
+ manifest_filename.resize(manifest_filename.size() - 1);
+ std::string manifest_filepath = db_->GetName() + "/" + manifest_filename;
+ std::cout << manifest_filepath << std::endl;
+ DumpManifestFile(options_, manifest_filepath, false, false, false);
+ std::cout << std::endl;
+
+ std::cout << "SST Files" << std::endl;
+ std::cout << "==============================" << std::endl;
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ for (auto& fileMetadata : metadata) {
+ std::string filename = fileMetadata.db_path + fileMetadata.name;
+ std::cout << filename << " level:" << fileMetadata.level << std::endl;
+ std::cout << "------------------------------" << std::endl;
+ DumpSstFile(options_, filename, false, true);
+ std::cout << std::endl;
+ }
+ std::cout << std::endl;
+
+ std::cout << "Write Ahead Log Files" << std::endl;
+ std::cout << "==============================" << std::endl;
+ ROCKSDB_NAMESPACE::VectorLogPtr wal_files;
+ s = db_->GetSortedWalFiles(wal_files);
+ if (!s.ok()) {
+ std::cerr << "Error when getting WAL files" << std::endl;
+ } else {
+ for (auto& wal : wal_files) {
+ // TODO(qyang): option.wal_dir should be passed into ldb command
+ std::string filename = db_->GetOptions().wal_dir + wal->PathName();
+ std::cout << filename << std::endl;
+ // TODO(myabandeh): allow configuring is_write_commited
+ DumpWalFile(options_, filename, true, true, true /* is_write_commited */,
+ &exec_state_);
+ }
+ }
+}
+
+void WriteExternalSstFilesCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(WriteExternalSstFilesCommand::Name());
+ ret.append(" <output_sst_path>");
+ ret.append("\n");
+}
+
+WriteExternalSstFilesCommand::WriteExternalSstFilesCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, false /* is_read_only */,
+ BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
+ ARG_TO, ARG_CREATE_IF_MISSING})) {
+ create_if_missing_ =
+ IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
+ ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
+ if (params.size() != 1) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "output SST file path must be specified");
+ } else {
+ output_sst_path_ = params.at(0);
+ }
+}
+
+void WriteExternalSstFilesCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ ColumnFamilyHandle* cfh = GetCfHandle();
+ SstFileWriter sst_file_writer(EnvOptions(), db_->GetOptions(), cfh);
+ Status status = sst_file_writer.Open(output_sst_path_);
+ if (!status.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed("failed to open SST file: " +
+ status.ToString());
+ return;
+ }
+
+ int bad_lines = 0;
+ std::string line;
+ std::ifstream ifs_stdin("/dev/stdin");
+ std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin;
+ while (getline(*istream_p, line, '\n')) {
+ std::string key;
+ std::string value;
+ if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) {
+ status = sst_file_writer.Put(key, value);
+ if (!status.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "failed to write record to file: " + status.ToString());
+ return;
+ }
+ } else if (0 == line.find("Keys in range:")) {
+ // ignore this line
+ } else if (0 == line.find("Created bg thread 0x")) {
+ // ignore this line
+ } else {
+ bad_lines++;
+ }
+ }
+
+ status = sst_file_writer.Finish();
+ if (!status.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "Failed to finish writing to file: " + status.ToString());
+ return;
+ }
+
+ if (bad_lines > 0) {
+ fprintf(stderr, "Warning: %d bad lines ignored.\n", bad_lines);
+ }
+ exec_state_ = LDBCommandExecuteResult::Succeed(
+ "external SST file written to " + output_sst_path_);
+}
+
+Options WriteExternalSstFilesCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.create_if_missing = create_if_missing_;
+ return opt;
+}
+
+const std::string IngestExternalSstFilesCommand::ARG_MOVE_FILES = "move_files";
+const std::string IngestExternalSstFilesCommand::ARG_SNAPSHOT_CONSISTENCY =
+ "snapshot_consistency";
+const std::string IngestExternalSstFilesCommand::ARG_ALLOW_GLOBAL_SEQNO =
+ "allow_global_seqno";
+const std::string IngestExternalSstFilesCommand::ARG_ALLOW_BLOCKING_FLUSH =
+ "allow_blocking_flush";
+const std::string IngestExternalSstFilesCommand::ARG_INGEST_BEHIND =
+ "ingest_behind";
+const std::string IngestExternalSstFilesCommand::ARG_WRITE_GLOBAL_SEQNO =
+ "write_global_seqno";
+
+void IngestExternalSstFilesCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(IngestExternalSstFilesCommand::Name());
+ ret.append(" <input_sst_path>");
+ ret.append(" [--" + ARG_MOVE_FILES + "] ");
+ ret.append(" [--" + ARG_SNAPSHOT_CONSISTENCY + "] ");
+ ret.append(" [--" + ARG_ALLOW_GLOBAL_SEQNO + "] ");
+ ret.append(" [--" + ARG_ALLOW_BLOCKING_FLUSH + "] ");
+ ret.append(" [--" + ARG_INGEST_BEHIND + "] ");
+ ret.append(" [--" + ARG_WRITE_GLOBAL_SEQNO + "] ");
+ ret.append("\n");
+}
+
+IngestExternalSstFilesCommand::IngestExternalSstFilesCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(
+ options, flags, false /* is_read_only */,
+ BuildCmdLineOptions({ARG_MOVE_FILES, ARG_SNAPSHOT_CONSISTENCY,
+ ARG_ALLOW_GLOBAL_SEQNO, ARG_CREATE_IF_MISSING,
+ ARG_ALLOW_BLOCKING_FLUSH, ARG_INGEST_BEHIND,
+ ARG_WRITE_GLOBAL_SEQNO})),
+ move_files_(false),
+ snapshot_consistency_(true),
+ allow_global_seqno_(true),
+ allow_blocking_flush_(true),
+ ingest_behind_(false),
+ write_global_seqno_(true) {
+ create_if_missing_ =
+ IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
+ ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
+ move_files_ = IsFlagPresent(flags, ARG_MOVE_FILES) ||
+ ParseBooleanOption(options, ARG_MOVE_FILES, false);
+ snapshot_consistency_ =
+ IsFlagPresent(flags, ARG_SNAPSHOT_CONSISTENCY) ||
+ ParseBooleanOption(options, ARG_SNAPSHOT_CONSISTENCY, true);
+ allow_global_seqno_ =
+ IsFlagPresent(flags, ARG_ALLOW_GLOBAL_SEQNO) ||
+ ParseBooleanOption(options, ARG_ALLOW_GLOBAL_SEQNO, true);
+ allow_blocking_flush_ =
+ IsFlagPresent(flags, ARG_ALLOW_BLOCKING_FLUSH) ||
+ ParseBooleanOption(options, ARG_ALLOW_BLOCKING_FLUSH, true);
+ ingest_behind_ = IsFlagPresent(flags, ARG_INGEST_BEHIND) ||
+ ParseBooleanOption(options, ARG_INGEST_BEHIND, false);
+ write_global_seqno_ =
+ IsFlagPresent(flags, ARG_WRITE_GLOBAL_SEQNO) ||
+ ParseBooleanOption(options, ARG_WRITE_GLOBAL_SEQNO, true);
+
+ if (allow_global_seqno_) {
+ if (!write_global_seqno_) {
+ fprintf(stderr,
+ "Warning: not writing global_seqno to the ingested SST can\n"
+ "prevent older versions of RocksDB from being able to open it\n");
+ }
+ } else {
+ if (write_global_seqno_) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "ldb cannot write global_seqno to the ingested SST when global_seqno "
+ "is not allowed");
+ }
+ }
+
+ if (params.size() != 1) {
+ exec_state_ =
+ LDBCommandExecuteResult::Failed("input SST path must be specified");
+ } else {
+ input_sst_path_ = params.at(0);
+ }
+}
+
+void IngestExternalSstFilesCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+ if (GetExecuteState().IsFailed()) {
+ return;
+ }
+ ColumnFamilyHandle* cfh = GetCfHandle();
+ IngestExternalFileOptions ifo;
+ ifo.move_files = move_files_;
+ ifo.snapshot_consistency = snapshot_consistency_;
+ ifo.allow_global_seqno = allow_global_seqno_;
+ ifo.allow_blocking_flush = allow_blocking_flush_;
+ ifo.ingest_behind = ingest_behind_;
+ ifo.write_global_seqno = write_global_seqno_;
+ Status status = db_->IngestExternalFile(cfh, {input_sst_path_}, ifo);
+ if (!status.ok()) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ "failed to ingest external SST: " + status.ToString());
+ } else {
+ exec_state_ =
+ LDBCommandExecuteResult::Succeed("external SST files ingested");
+ }
+}
+
+Options IngestExternalSstFilesCommand::PrepareOptionsForOpenDB() {
+ Options opt = LDBCommand::PrepareOptionsForOpenDB();
+ opt.create_if_missing = create_if_missing_;
+ return opt;
+}
+
+ListFileRangeDeletesCommand::ListFileRangeDeletesCommand(
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags)
+ : LDBCommand(options, flags, true, BuildCmdLineOptions({ARG_MAX_KEYS})) {
+ std::map<std::string, std::string>::const_iterator itr =
+ options.find(ARG_MAX_KEYS);
+ if (itr != options.end()) {
+ try {
+#if defined(CYGWIN)
+ max_keys_ = strtol(itr->second.c_str(), 0, 10);
+#else
+ max_keys_ = std::stoi(itr->second);
+#endif
+ } catch (const std::invalid_argument&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS +
+ " has an invalid value");
+ } catch (const std::out_of_range&) {
+ exec_state_ = LDBCommandExecuteResult::Failed(
+ ARG_MAX_KEYS + " has a value out-of-range");
+ }
+ }
+}
+
+void ListFileRangeDeletesCommand::Help(std::string& ret) {
+ ret.append(" ");
+ ret.append(ListFileRangeDeletesCommand::Name());
+ ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
+ ret.append(" : print tombstones in SST files.\n");
+}
+
+void ListFileRangeDeletesCommand::DoCommand() {
+ if (!db_) {
+ assert(GetExecuteState().IsFailed());
+ return;
+ }
+
+ DBImpl* db_impl = static_cast_with_check<DBImpl, DB>(db_->GetRootDB());
+
+ std::string out_str;
+
+ Status st =
+ db_impl->TablesRangeTombstoneSummary(GetCfHandle(), max_keys_, &out_str);
+ if (st.ok()) {
+ TEST_SYNC_POINT_CALLBACK(
+ "ListFileRangeDeletesCommand::DoCommand:BeforePrint", &out_str);
+ fprintf(stdout, "%s\n", out_str.c_str());
+ } else {
+ exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/ldb_cmd_impl.h b/storage/rocksdb/rocksdb/tools/ldb_cmd_impl.h
new file mode 100644
index 00000000..8477cae6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb_cmd_impl.h
@@ -0,0 +1,628 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/utilities/ldb_cmd.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace ROCKSDB_NAMESPACE {
+
+class CompactorCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "compact"; }
+
+ CompactorCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+
+ virtual void DoCommand() override;
+
+ private:
+ bool null_from_;
+ std::string from_;
+ bool null_to_;
+ std::string to_;
+};
+
+class DBFileDumperCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "dump_live_files"; }
+
+ DBFileDumperCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+
+ virtual void DoCommand() override;
+};
+
+class DBDumperCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "dump"; }
+
+ DBDumperCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+
+ virtual void DoCommand() override;
+
+ private:
+ /**
+ * Extract file name from the full path. We handle both the forward slash (/)
+ * and backslash (\) to make sure that different OS-s are supported.
+ */
+ static std::string GetFileNameFromPath(const std::string& s) {
+ std::size_t n = s.find_last_of("/\\");
+
+ if (std::string::npos == n) {
+ return s;
+ } else {
+ return s.substr(n + 1);
+ }
+ }
+
+ void DoDumpCommand();
+
+ bool null_from_;
+ std::string from_;
+ bool null_to_;
+ std::string to_;
+ int max_keys_;
+ std::string delim_;
+ bool count_only_;
+ bool count_delim_;
+ bool print_stats_;
+ std::string path_;
+
+ static const std::string ARG_COUNT_ONLY;
+ static const std::string ARG_COUNT_DELIM;
+ static const std::string ARG_STATS;
+ static const std::string ARG_TTL_BUCKET;
+};
+
+class InternalDumpCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "idump"; }
+
+ InternalDumpCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+
+ virtual void DoCommand() override;
+
+ private:
+ bool has_from_;
+ std::string from_;
+ bool has_to_;
+ std::string to_;
+ int max_keys_;
+ std::string delim_;
+ bool count_only_;
+ bool count_delim_;
+ bool print_stats_;
+ bool is_input_key_hex_;
+
+ static const std::string ARG_DELIM;
+ static const std::string ARG_COUNT_ONLY;
+ static const std::string ARG_COUNT_DELIM;
+ static const std::string ARG_STATS;
+ static const std::string ARG_INPUT_KEY_HEX;
+};
+
+class DBLoaderCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "load"; }
+
+ DBLoaderCommand(std::string& db_name, std::vector<std::string>& args);
+
+ DBLoaderCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ private:
+ bool disable_wal_;
+ bool bulk_load_;
+ bool compact_;
+
+ static const std::string ARG_DISABLE_WAL;
+ static const std::string ARG_BULK_LOAD;
+ static const std::string ARG_COMPACT;
+};
+
+class ManifestDumpCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "manifest_dump"; }
+
+ ManifestDumpCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return true; }
+
+ private:
+ bool verbose_;
+ bool json_;
+ std::string path_;
+
+ static const std::string ARG_VERBOSE;
+ static const std::string ARG_JSON;
+ static const std::string ARG_PATH;
+};
+
+class FileChecksumDumpCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "file_checksum_dump"; }
+
+ FileChecksumDumpCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ void DoCommand() override;
+
+ bool NoDBOpen() override { return true; }
+
+ private:
+ std::string path_;
+
+ static const std::string ARG_PATH;
+};
+
+class ListColumnFamiliesCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "list_column_families"; }
+
+ ListColumnFamiliesCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return true; }
+};
+
+class CreateColumnFamilyCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "create_column_family"; }
+
+ CreateColumnFamilyCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return false; }
+
+ private:
+ std::string new_cf_name_;
+};
+
+class DropColumnFamilyCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "drop_column_family"; }
+
+ DropColumnFamilyCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return false; }
+
+ private:
+ std::string cf_name_to_drop_;
+};
+
+class ReduceDBLevelsCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "reduce_levels"; }
+
+ ReduceDBLevelsCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return true; }
+
+ static void Help(std::string& msg);
+
+ static std::vector<std::string> PrepareArgs(const std::string& db_path,
+ int new_levels,
+ bool print_old_level = false);
+
+ private:
+ int old_levels_;
+ int new_levels_;
+ bool print_old_levels_;
+
+ static const std::string ARG_NEW_LEVELS;
+ static const std::string ARG_PRINT_OLD_LEVELS;
+
+ Status GetOldNumOfLevels(Options& opt, int* levels);
+};
+
+class ChangeCompactionStyleCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "change_compaction_style"; }
+
+ ChangeCompactionStyleCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& msg);
+
+ private:
+ int old_compaction_style_;
+ int new_compaction_style_;
+
+ static const std::string ARG_OLD_COMPACTION_STYLE;
+ static const std::string ARG_NEW_COMPACTION_STYLE;
+};
+
+class WALDumperCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "dump_wal"; }
+
+ WALDumperCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual bool NoDBOpen() override { return true; }
+
+ static void Help(std::string& ret);
+ virtual void DoCommand() override;
+
+ private:
+ bool print_header_;
+ std::string wal_file_;
+ bool print_values_;
+ bool is_write_committed_; // default will be set to true
+
+ static const std::string ARG_WAL_FILE;
+ static const std::string ARG_WRITE_COMMITTED;
+ static const std::string ARG_PRINT_HEADER;
+ static const std::string ARG_PRINT_VALUE;
+};
+
+class GetCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "get"; }
+
+ GetCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string key_;
+};
+
+class ApproxSizeCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "approxsize"; }
+
+ ApproxSizeCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string start_key_;
+ std::string end_key_;
+};
+
+class BatchPutCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "batchput"; }
+
+ BatchPutCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ private:
+ /**
+ * The key-values to be inserted.
+ */
+ std::vector<std::pair<std::string, std::string>> key_values_;
+};
+
+class ScanCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "scan"; }
+
+ ScanCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string start_key_;
+ std::string end_key_;
+ bool start_key_specified_;
+ bool end_key_specified_;
+ int max_keys_scanned_;
+ bool no_value_;
+};
+
+class DeleteCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "delete"; }
+
+ DeleteCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string key_;
+};
+
+class DeleteRangeCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "deleterange"; }
+
+ DeleteRangeCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string begin_key_;
+ std::string end_key_;
+};
+
+class PutCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "put"; }
+
+ PutCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ private:
+ std::string key_;
+ std::string value_;
+};
+
+/**
+ * Command that starts up a REPL shell that allows
+ * get/put/delete.
+ */
+class DBQuerierCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "query"; }
+
+ DBQuerierCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ static void Help(std::string& ret);
+
+ virtual void DoCommand() override;
+
+ private:
+ static const char* HELP_CMD;
+ static const char* GET_CMD;
+ static const char* PUT_CMD;
+ static const char* DELETE_CMD;
+};
+
+class CheckConsistencyCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "checkconsistency"; }
+
+ CheckConsistencyCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return true; }
+
+ static void Help(std::string& ret);
+};
+
+class CheckPointCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "checkpoint"; }
+
+ CheckPointCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ std::string checkpoint_dir_;
+ private:
+ static const std::string ARG_CHECKPOINT_DIR;
+};
+
+class RepairCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "repair"; }
+
+ RepairCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return true; }
+
+ static void Help(std::string& ret);
+};
+
+class BackupableCommand : public LDBCommand {
+ public:
+ BackupableCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ protected:
+ static void Help(const std::string& name, std::string& ret);
+ std::string backup_env_uri_;
+ std::string backup_dir_;
+ int num_threads_;
+ std::unique_ptr<Logger> logger_;
+ std::shared_ptr<Env> backup_env_guard_;
+
+ private:
+ static const std::string ARG_BACKUP_DIR;
+ static const std::string ARG_BACKUP_ENV_URI;
+ static const std::string ARG_NUM_THREADS;
+ static const std::string ARG_STDERR_LOG_LEVEL;
+};
+
+class BackupCommand : public BackupableCommand {
+ public:
+ static std::string Name() { return "backup"; }
+ BackupCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+ virtual void DoCommand() override;
+ static void Help(std::string& ret);
+};
+
+class RestoreCommand : public BackupableCommand {
+ public:
+ static std::string Name() { return "restore"; }
+ RestoreCommand(const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+ virtual void DoCommand() override;
+ virtual bool NoDBOpen() override { return true; }
+ static void Help(std::string& ret);
+};
+
+class WriteExternalSstFilesCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "write_extern_sst"; }
+ WriteExternalSstFilesCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return false; }
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string output_sst_path_;
+};
+
+class IngestExternalSstFilesCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "ingest_extern_sst"; }
+ IngestExternalSstFilesCommand(
+ const std::vector<std::string>& params,
+ const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ virtual void DoCommand() override;
+
+ virtual bool NoDBOpen() override { return false; }
+
+ virtual Options PrepareOptionsForOpenDB() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ std::string input_sst_path_;
+ bool move_files_;
+ bool snapshot_consistency_;
+ bool allow_global_seqno_;
+ bool allow_blocking_flush_;
+ bool ingest_behind_;
+ bool write_global_seqno_;
+
+ static const std::string ARG_MOVE_FILES;
+ static const std::string ARG_SNAPSHOT_CONSISTENCY;
+ static const std::string ARG_ALLOW_GLOBAL_SEQNO;
+ static const std::string ARG_ALLOW_BLOCKING_FLUSH;
+ static const std::string ARG_INGEST_BEHIND;
+ static const std::string ARG_WRITE_GLOBAL_SEQNO;
+};
+
+// Command that prints out range delete tombstones in SST files.
+class ListFileRangeDeletesCommand : public LDBCommand {
+ public:
+ static std::string Name() { return "list_file_range_deletes"; }
+
+ ListFileRangeDeletesCommand(const std::map<std::string, std::string>& options,
+ const std::vector<std::string>& flags);
+
+ void DoCommand() override;
+
+ static void Help(std::string& ret);
+
+ private:
+ int max_keys_ = 1000;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/tools/ldb_cmd_test.cc b/storage/rocksdb/rocksdb/tools/ldb_cmd_test.cc
new file mode 100644
index 00000000..80e2f0d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb_cmd_test.cc
@@ -0,0 +1,585 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/ldb_cmd.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/file_checksum.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/file_checksum_helper.h"
+
+using std::string;
+using std::vector;
+using std::map;
+
+namespace ROCKSDB_NAMESPACE {
+
+class LdbCmdTest : public testing::Test {
+ public:
+ LdbCmdTest() : testing::Test() {}
+
+ Env* TryLoadCustomOrDefaultEnv() {
+ const char* test_env_uri = getenv("TEST_ENV_URI");
+ if (!test_env_uri) {
+ return Env::Default();
+ }
+ Env* env = Env::Default();
+ Env::LoadEnv(test_env_uri, &env, &env_guard_);
+ return env;
+ }
+
+ private:
+ std::shared_ptr<Env> env_guard_;
+};
+
+TEST_F(LdbCmdTest, HexToString) {
+ // map input to expected outputs.
+ // odd number of "hex" half bytes doesn't make sense
+ map<string, vector<int>> 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<int>((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<string> 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> env(NewMemEnv(base_env));
+ Options opts;
+ opts.env = env.get();
+ opts.create_if_missing = true;
+
+ opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
+
+ DB* db = nullptr;
+ std::string dbname = test::TmpDir();
+ 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<SequentialFile> 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<char[]> scratch(new char[2048]);
+ bool first_read = true;
+ Slice result;
+ FileChecksumFunc* file_checksum_func =
+ options_.sst_file_checksum_func.get();
+ if (file_checksum_func == nullptr) {
+ cur_checksum = kUnknownFileChecksum;
+ checksum_func_name = kUnknownFileChecksumFuncName;
+ } else {
+ checksum_func_name = file_checksum_func->Name();
+ s = file_reader->Read(2048, &result, scratch.get());
+ if (!s.ok()) {
+ return s;
+ }
+ while (result.size() != 0) {
+ if (first_read) {
+ first_read = false;
+ cur_checksum =
+ file_checksum_func->Value(scratch.get(), result.size());
+ } else {
+ cur_checksum = file_checksum_func->Extend(cur_checksum, scratch.get(),
+ result.size());
+ }
+ s = file_reader->Read(2048, &result, scratch.get());
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+
+ 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<LiveFileMetaData>& live_files) {
+ // Step 1: verify if the dbname_ is correct
+ if (dbname_[dbname_.length() - 1] != '/') {
+ dbname_.append("/");
+ }
+
+ // Step 2, get the the checksum information by recovering the VersionSet
+ // from Manifest.
+ std::unique_ptr<FileChecksumList> checksum_list(NewFileChecksumList());
+ EnvOptions sopt;
+ std::shared_ptr<Cache> 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);
+ std::vector<std::string> cf_name_list;
+ Status s;
+ s = versions.ListColumnFamilies(&cf_name_list, dbname_,
+ options_.file_system.get());
+ if (s.ok()) {
+ std::vector<ColumnFamilyDescriptor> 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: " +
+ ToString(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);
+ std::vector<LiveFileMetaData> live_files;
+ db_->GetLiveFilesMetaData(&live_files);
+ for (auto a_file : live_files) {
+ Status cs = VerifyChecksum(a_file);
+ if (!cs.ok()) {
+ return cs;
+ }
+ }
+ return Status::OK();
+ }
+};
+
+TEST_F(LdbCmdTest, DumpFileChecksumNoChecksum) {
+ Env* base_env = TryLoadCustomOrDefaultEnv();
+ std::unique_ptr<Env> env(NewMemEnv(base_env));
+ Options opts;
+ opts.env = env.get();
+ opts.create_if_missing = true;
+ opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
+
+ DB* db = nullptr;
+ std::string dbname = test::TmpDir();
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ ASSERT_OK(db->Put(wopts, buf, v));
+ }
+ ASSERT_OK(db->Flush(fopts));
+
+ char arg1[] = "./ldb";
+ char arg2[1024];
+ snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
+ char arg3[] = "file_checksum_dump";
+ char* argv[] = {arg1, arg2, arg3};
+
+ ASSERT_EQ(0,
+ LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
+
+ // 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_EQ(0,
+ LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
+
+ // Verify the checksum information in memory is the same as that in Manifest;
+ std::vector<LiveFileMetaData> live_files;
+ db->GetLiveFilesMetaData(&live_files);
+ delete db;
+ ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files));
+}
+
+TEST_F(LdbCmdTest, DumpFileChecksumCRC32) {
+ Env* base_env = TryLoadCustomOrDefaultEnv();
+ std::unique_ptr<Env> env(NewMemEnv(base_env));
+ Options opts;
+ opts.env = env.get();
+ opts.create_if_missing = true;
+ opts.sst_file_checksum_func =
+ std::shared_ptr<FileChecksumFunc>(CreateFileChecksumFuncCrc32c());
+ opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
+
+ DB* db = nullptr;
+ std::string dbname = test::TmpDir();
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ 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;
+ test::RandomString(&rnd, 100, &v);
+ ASSERT_OK(db->Put(wopts, buf, v));
+ }
+ ASSERT_OK(db->Flush(fopts));
+
+ char arg1[] = "./ldb";
+ char arg2[1024];
+ snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str());
+ char arg3[] = "file_checksum_dump";
+ char* argv[] = {arg1, arg2, arg3};
+
+ ASSERT_EQ(0,
+ LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
+
+ // 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_EQ(0,
+ LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr));
+
+ // Verify the checksum information in memory is the same as that in Manifest;
+ std::vector<LiveFileMetaData> live_files;
+ db->GetLiveFilesMetaData(&live_files);
+ delete db;
+ ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files));
+}
+
+TEST_F(LdbCmdTest, OptionParsing) {
+ // test parsing flags
+ Options opts;
+ opts.env = TryLoadCustomOrDefaultEnv();
+ {
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string, std::string> 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> env(NewMemEnv(base_env));
+ Options opts;
+ opts.env = env.get();
+ opts.create_if_missing = true;
+
+ DB* db = nullptr;
+ std::string dbname = test::TmpDir();
+ 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<std::string*>(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<std::string*>(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();
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as LDBCommand is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/ldb_test.py b/storage/rocksdb/rocksdb/tools/ldb_test.py
new file mode 100644
index 00000000..74bb7fb1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb_test.py
@@ -0,0 +1,595 @@
+#!/usr/bin/env python2
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+import os
+import glob
+import os.path
+import shutil
+import subprocess
+import time
+import unittest
+import tempfile
+import re
+
+def my_check_output(*popenargs, **kwargs):
+ """
+ If we had python 2.7, we should simply use subprocess.check_output.
+ This is a stop-gap solution for python 2.6
+ """
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, it will be overridden.')
+ process = subprocess.Popen(stderr=subprocess.PIPE, stdout=subprocess.PIPE,
+ *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise Exception("Exit code is not 0. It is %d. Command: %s" %
+ (retcode, cmd))
+ return output
+
+def run_err_null(cmd):
+ return os.system(cmd + " 2>/dev/null ")
+
+class LDBTestCase(unittest.TestCase):
+ def setUp(self):
+ self.TMP_DIR = tempfile.mkdtemp(prefix="ldb_test_")
+ self.DB_NAME = "testdb"
+
+ def tearDown(self):
+ assert(self.TMP_DIR.strip() != "/"
+ and self.TMP_DIR.strip() != "/tmp"
+ and self.TMP_DIR.strip() != "/tmp/") #Just some paranoia
+
+ shutil.rmtree(self.TMP_DIR)
+
+ def dbParam(self, dbName):
+ return "--db=%s" % os.path.join(self.TMP_DIR, dbName)
+
+ def assertRunOKFull(self, params, expectedOutput, unexpected=False,
+ isPattern=False):
+ """
+ All command-line params must be specified.
+ Allows full flexibility in testing; for example: missing db param.
+
+ """
+ output = my_check_output("./ldb %s |grep -v \"Created bg thread\"" %
+ params, shell=True)
+ if not unexpected:
+ if isPattern:
+ self.assertNotEqual(expectedOutput.search(output.strip()),
+ None)
+ else:
+ self.assertEqual(output.strip(), expectedOutput.strip())
+ else:
+ if isPattern:
+ self.assertEqual(expectedOutput.search(output.strip()), None)
+ else:
+ self.assertNotEqual(output.strip(), expectedOutput.strip())
+
+ def assertRunFAILFull(self, params):
+ """
+ All command-line params must be specified.
+ Allows full flexibility in testing; for example: missing db param.
+
+ """
+ try:
+
+ my_check_output("./ldb %s >/dev/null 2>&1 |grep -v \"Created bg \
+ thread\"" % params, shell=True)
+ except Exception:
+ return
+ self.fail(
+ "Exception should have been raised for command with params: %s" %
+ params)
+
+ def assertRunOK(self, params, expectedOutput, unexpected=False):
+ """
+ Uses the default test db.
+
+ """
+ self.assertRunOKFull("%s %s" % (self.dbParam(self.DB_NAME), params),
+ expectedOutput, unexpected)
+
+ def assertRunFAIL(self, params):
+ """
+ Uses the default test db.
+ """
+ self.assertRunFAILFull("%s %s" % (self.dbParam(self.DB_NAME), params))
+
+ def testSimpleStringPutGet(self):
+ print "Running testSimpleStringPutGet..."
+ self.assertRunFAIL("put x1 y1")
+ self.assertRunOK("put --create_if_missing x1 y1", "OK")
+ self.assertRunOK("get x1", "y1")
+ self.assertRunFAIL("get x2")
+
+ self.assertRunOK("put x2 y2", "OK")
+ self.assertRunOK("get x1", "y1")
+ self.assertRunOK("get x2", "y2")
+ self.assertRunFAIL("get x3")
+
+ self.assertRunOK("scan --from=x1 --to=z", "x1 : y1\nx2 : y2")
+ self.assertRunOK("put x3 y3", "OK")
+
+ self.assertRunOK("scan --from=x1 --to=z", "x1 : y1\nx2 : y2\nx3 : y3")
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3")
+ self.assertRunOK("scan --from=x", "x1 : y1\nx2 : y2\nx3 : y3")
+
+ self.assertRunOK("scan --to=x2", "x1 : y1")
+ self.assertRunOK("scan --from=x1 --to=z --max_keys=1", "x1 : y1")
+ self.assertRunOK("scan --from=x1 --to=z --max_keys=2",
+ "x1 : y1\nx2 : y2")
+
+ self.assertRunOK("scan --from=x1 --to=z --max_keys=3",
+ "x1 : y1\nx2 : y2\nx3 : y3")
+ self.assertRunOK("scan --from=x1 --to=z --max_keys=4",
+ "x1 : y1\nx2 : y2\nx3 : y3")
+ self.assertRunOK("scan --from=x1 --to=x2", "x1 : y1")
+ self.assertRunOK("scan --from=x2 --to=x4", "x2 : y2\nx3 : y3")
+ self.assertRunFAIL("scan --from=x4 --to=z") # No results => FAIL
+ self.assertRunFAIL("scan --from=x1 --to=z --max_keys=foo")
+
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3")
+
+ self.assertRunOK("delete x1", "OK")
+ self.assertRunOK("scan", "x2 : y2\nx3 : y3")
+
+ self.assertRunOK("delete NonExistentKey", "OK")
+ # It is weird that GET and SCAN raise exception for
+ # non-existent key, while delete does not
+
+ self.assertRunOK("checkconsistency", "OK")
+
+ def dumpDb(self, params, dumpFile):
+ return 0 == run_err_null("./ldb dump %s > %s" % (params, dumpFile))
+
+ def loadDb(self, params, dumpFile):
+ return 0 == run_err_null("cat %s | ./ldb load %s" % (dumpFile, params))
+
+ def writeExternSst(self, params, inputDumpFile, outputSst):
+ return 0 == run_err_null("cat %s | ./ldb write_extern_sst %s %s"
+ % (inputDumpFile, outputSst, params))
+
+ def ingestExternSst(self, params, inputSst):
+ return 0 == run_err_null("./ldb ingest_extern_sst %s %s"
+ % (inputSst, params))
+
+ def testStringBatchPut(self):
+ print "Running testStringBatchPut..."
+ self.assertRunOK("batchput x1 y1 --create_if_missing", "OK")
+ self.assertRunOK("scan", "x1 : y1")
+ self.assertRunOK("batchput x2 y2 x3 y3 \"x4 abc\" \"y4 xyz\"", "OK")
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 abc : y4 xyz")
+ self.assertRunFAIL("batchput")
+ self.assertRunFAIL("batchput k1")
+ self.assertRunFAIL("batchput k1 v1 k2")
+
+ def testCountDelimDump(self):
+ print "Running testCountDelimDump..."
+ self.assertRunOK("batchput x.1 x1 --create_if_missing", "OK")
+ self.assertRunOK("batchput y.abc abc y.2 2 z.13c pqr", "OK")
+ self.assertRunOK("dump --count_delim", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
+ self.assertRunOK("dump --count_delim=\".\"", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
+ self.assertRunOK("batchput x,2 x2 x,abc xabc", "OK")
+ self.assertRunOK("dump --count_delim=\",\"", "x => count:2\tsize:14\nx.1 => count:1\tsize:5\ny.2 => count:1\tsize:4\ny.abc => count:1\tsize:8\nz.13c => count:1\tsize:8")
+
+ def testCountDelimIDump(self):
+ print "Running testCountDelimIDump..."
+ self.assertRunOK("batchput x.1 x1 --create_if_missing", "OK")
+ self.assertRunOK("batchput y.abc abc y.2 2 z.13c pqr", "OK")
+ self.assertRunOK("idump --count_delim", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
+ self.assertRunOK("idump --count_delim=\".\"", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
+ self.assertRunOK("batchput x,2 x2 x,abc xabc", "OK")
+ self.assertRunOK("idump --count_delim=\",\"", "x => count:2\tsize:14\nx.1 => count:1\tsize:5\ny.2 => count:1\tsize:4\ny.abc => count:1\tsize:8\nz.13c => count:1\tsize:8")
+
+ def testInvalidCmdLines(self):
+ print "Running testInvalidCmdLines..."
+ # db not specified
+ self.assertRunFAILFull("put 0x6133 0x6233 --hex --create_if_missing")
+ # No param called he
+ self.assertRunFAIL("put 0x6133 0x6233 --he --create_if_missing")
+ # max_keys is not applicable for put
+ self.assertRunFAIL("put 0x6133 0x6233 --max_keys=1 --create_if_missing")
+ # hex has invalid boolean value
+
+ def testHexPutGet(self):
+ print "Running testHexPutGet..."
+ self.assertRunOK("put a1 b1 --create_if_missing", "OK")
+ self.assertRunOK("scan", "a1 : b1")
+ self.assertRunOK("scan --hex", "0x6131 : 0x6231")
+ self.assertRunFAIL("put --hex 6132 6232")
+ self.assertRunOK("put --hex 0x6132 0x6232", "OK")
+ self.assertRunOK("scan --hex", "0x6131 : 0x6231\n0x6132 : 0x6232")
+ self.assertRunOK("scan", "a1 : b1\na2 : b2")
+ self.assertRunOK("get a1", "b1")
+ self.assertRunOK("get --hex 0x6131", "0x6231")
+ self.assertRunOK("get a2", "b2")
+ self.assertRunOK("get --hex 0x6132", "0x6232")
+ self.assertRunOK("get --key_hex 0x6132", "b2")
+ self.assertRunOK("get --key_hex --value_hex 0x6132", "0x6232")
+ self.assertRunOK("get --value_hex a2", "0x6232")
+ self.assertRunOK("scan --key_hex --value_hex",
+ "0x6131 : 0x6231\n0x6132 : 0x6232")
+ self.assertRunOK("scan --hex --from=0x6131 --to=0x6133",
+ "0x6131 : 0x6231\n0x6132 : 0x6232")
+ self.assertRunOK("scan --hex --from=0x6131 --to=0x6132",
+ "0x6131 : 0x6231")
+ self.assertRunOK("scan --key_hex", "0x6131 : b1\n0x6132 : b2")
+ self.assertRunOK("scan --value_hex", "a1 : 0x6231\na2 : 0x6232")
+ self.assertRunOK("batchput --hex 0x6133 0x6233 0x6134 0x6234", "OK")
+ self.assertRunOK("scan", "a1 : b1\na2 : b2\na3 : b3\na4 : b4")
+ self.assertRunOK("delete --hex 0x6133", "OK")
+ self.assertRunOK("scan", "a1 : b1\na2 : b2\na4 : b4")
+ self.assertRunOK("checkconsistency", "OK")
+
+ def testTtlPutGet(self):
+ print "Running testTtlPutGet..."
+ self.assertRunOK("put a1 b1 --ttl --create_if_missing", "OK")
+ self.assertRunOK("scan --hex", "0x6131 : 0x6231", True)
+ self.assertRunOK("dump --ttl ", "a1 ==> b1", True)
+ self.assertRunOK("dump --hex --ttl ",
+ "0x6131 ==> 0x6231\nKeys in range: 1")
+ self.assertRunOK("scan --hex --ttl", "0x6131 : 0x6231")
+ self.assertRunOK("get --value_hex a1", "0x6231", True)
+ self.assertRunOK("get --ttl a1", "b1")
+ self.assertRunOK("put a3 b3 --create_if_missing", "OK")
+ # fails because timstamp's length is greater than value's
+ self.assertRunFAIL("get --ttl a3")
+ self.assertRunOK("checkconsistency", "OK")
+
+ def testInvalidCmdLines(self): # noqa: F811 T25377293 Grandfathered in
+ print "Running testInvalidCmdLines..."
+ # db not specified
+ self.assertRunFAILFull("put 0x6133 0x6233 --hex --create_if_missing")
+ # No param called he
+ self.assertRunFAIL("put 0x6133 0x6233 --he --create_if_missing")
+ # max_keys is not applicable for put
+ self.assertRunFAIL("put 0x6133 0x6233 --max_keys=1 --create_if_missing")
+ # hex has invalid boolean value
+ self.assertRunFAIL("put 0x6133 0x6233 --hex=Boo --create_if_missing")
+
+ def testDumpLoad(self):
+ print "Running testDumpLoad..."
+ self.assertRunOK("batchput --create_if_missing x1 y1 x2 y2 x3 y3 x4 y4",
+ "OK")
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+ origDbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+
+ # Dump and load without any additional params specified
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump1")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump1")
+ self.assertTrue(self.dumpDb("--db=%s" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --create_if_missing" % loadedDbPath, dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ # Dump and load in hex
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump2")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump2")
+ self.assertTrue(self.dumpDb("--db=%s --hex" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --hex --create_if_missing" % loadedDbPath, dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ # Dump only a portion of the key range
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump3")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump3")
+ self.assertTrue(self.dumpDb(
+ "--db=%s --from=x1 --to=x3" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --create_if_missing" % loadedDbPath, dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath, "x1 : y1\nx2 : y2")
+
+ # Dump upto max_keys rows
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump4")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump4")
+ self.assertTrue(self.dumpDb(
+ "--db=%s --max_keys=3" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --create_if_missing" % loadedDbPath, dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3")
+
+ # Load into an existing db, create_if_missing is not specified
+ self.assertTrue(self.dumpDb("--db=%s" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb("--db=%s" % loadedDbPath, dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ # Dump and load with WAL disabled
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump5")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump5")
+ self.assertTrue(self.dumpDb("--db=%s" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --disable_wal --create_if_missing" % loadedDbPath,
+ dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ # Dump and load with lots of extra params specified
+ extraParams = " ".join(["--bloom_bits=14", "--block_size=1024",
+ "--auto_compaction=true",
+ "--write_buffer_size=4194304",
+ "--file_size=2097152"])
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump6")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump6")
+ self.assertTrue(self.dumpDb(
+ "--db=%s %s" % (origDbPath, extraParams), dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s %s --create_if_missing" % (loadedDbPath, extraParams),
+ dumpFilePath))
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ # Dump with count_only
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump7")
+ loadedDbPath = os.path.join(self.TMP_DIR, "loaded_from_dump7")
+ self.assertTrue(self.dumpDb(
+ "--db=%s --count_only" % origDbPath, dumpFilePath))
+ self.assertTrue(self.loadDb(
+ "--db=%s --create_if_missing" % loadedDbPath, dumpFilePath))
+ # DB should have atleast one value for scan to work
+ self.assertRunOKFull("put --db=%s k1 v1" % loadedDbPath, "OK")
+ self.assertRunOKFull("scan --db=%s" % loadedDbPath, "k1 : v1")
+
+ # Dump command fails because of typo in params
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump8")
+ self.assertFalse(self.dumpDb(
+ "--db=%s --create_if_missing" % origDbPath, dumpFilePath))
+
+ def testIDumpBasics(self):
+ print "Running testIDumpBasics..."
+ self.assertRunOK("put a val --create_if_missing", "OK")
+ self.assertRunOK("put b val", "OK")
+ self.assertRunOK(
+ "idump", "'a' seq:1, type:1 => val\n"
+ "'b' seq:2, type:1 => val\nInternal keys in range: 2")
+ self.assertRunOK(
+ "idump --input_key_hex --from=%s --to=%s" % (hex(ord('a')),
+ hex(ord('b'))),
+ "'a' seq:1, type:1 => val\nInternal keys in range: 1")
+
+ def testMiscAdminTask(self):
+ print "Running testMiscAdminTask..."
+ # These tests need to be improved; for example with asserts about
+ # whether compaction or level reduction actually took place.
+ self.assertRunOK("batchput --create_if_missing x1 y1 x2 y2 x3 y3 x4 y4",
+ "OK")
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+ origDbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+
+ self.assertTrue(0 == run_err_null(
+ "./ldb compact --db=%s" % origDbPath))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ self.assertTrue(0 == run_err_null(
+ "./ldb reduce_levels --db=%s --new_levels=2" % origDbPath))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ self.assertTrue(0 == run_err_null(
+ "./ldb reduce_levels --db=%s --new_levels=3" % origDbPath))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ self.assertTrue(0 == run_err_null(
+ "./ldb compact --db=%s --from=x1 --to=x3" % origDbPath))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ self.assertTrue(0 == run_err_null(
+ "./ldb compact --db=%s --hex --from=0x6131 --to=0x6134"
+ % origDbPath))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ #TODO(dilip): Not sure what should be passed to WAL.Currently corrupted.
+ self.assertTrue(0 == run_err_null(
+ "./ldb dump_wal --db=%s --walfile=%s --header" % (
+ origDbPath, os.path.join(origDbPath, "LOG"))))
+ self.assertRunOK("scan", "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+
+ def testCheckConsistency(self):
+ print "Running testCheckConsistency..."
+
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+ self.assertRunOK("put x1 y1 --create_if_missing", "OK")
+ self.assertRunOK("put x2 y2", "OK")
+ self.assertRunOK("get x1", "y1")
+ self.assertRunOK("checkconsistency", "OK")
+
+ sstFilePath = my_check_output("ls %s" % os.path.join(dbPath, "*.sst"),
+ shell=True)
+
+ # Modify the file
+ my_check_output("echo 'evil' > %s" % sstFilePath, shell=True)
+ self.assertRunFAIL("checkconsistency")
+
+ # Delete the file
+ my_check_output("rm -f %s" % sstFilePath, shell=True)
+ self.assertRunFAIL("checkconsistency")
+
+ def dumpLiveFiles(self, params, dumpFile):
+ return 0 == run_err_null("./ldb dump_live_files %s > %s" % (
+ params, dumpFile))
+
+ def testDumpLiveFiles(self):
+ print "Running testDumpLiveFiles..."
+
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+ self.assertRunOK("put x1 y1 --create_if_missing", "OK")
+ self.assertRunOK("put x2 y2", "OK")
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump1")
+ self.assertTrue(self.dumpLiveFiles("--db=%s" % dbPath, dumpFilePath))
+ self.assertRunOK("delete x1", "OK")
+ self.assertRunOK("put x3 y3", "OK")
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump2")
+ self.assertTrue(self.dumpLiveFiles("--db=%s" % dbPath, dumpFilePath))
+
+ def getManifests(self, directory):
+ return glob.glob(directory + "/MANIFEST-*")
+
+ def getSSTFiles(self, directory):
+ return glob.glob(directory + "/*.sst")
+
+ def getWALFiles(self, directory):
+ return glob.glob(directory + "/*.log")
+
+ def copyManifests(self, src, dest):
+ return 0 == run_err_null("cp " + src + " " + dest)
+
+ def testManifestDump(self):
+ print "Running testManifestDump..."
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+ self.assertRunOK("put 1 1 --create_if_missing", "OK")
+ self.assertRunOK("put 2 2", "OK")
+ self.assertRunOK("put 3 3", "OK")
+ # Pattern to expect from manifest_dump.
+ num = "[0-9]+"
+ st = ".*"
+ subpat = st + " seq:" + num + ", type:" + num
+ regex = num + ":" + num + "\[" + subpat + ".." + subpat + "\]"
+ expected_pattern = re.compile(regex)
+ cmd = "manifest_dump --db=%s"
+ manifest_files = self.getManifests(dbPath)
+ self.assertTrue(len(manifest_files) == 1)
+ # Test with the default manifest file in dbPath.
+ self.assertRunOKFull(cmd % dbPath, expected_pattern,
+ unexpected=False, isPattern=True)
+ self.copyManifests(manifest_files[0], manifest_files[0] + "1")
+ manifest_files = self.getManifests(dbPath)
+ self.assertTrue(len(manifest_files) == 2)
+ # Test with multiple manifest files in dbPath.
+ self.assertRunFAILFull(cmd % dbPath)
+ # Running it with the copy we just created should pass.
+ self.assertRunOKFull((cmd + " --path=%s")
+ % (dbPath, manifest_files[1]),
+ expected_pattern, unexpected=False,
+ isPattern=True)
+ # Make sure that using the dump with --path will result in identical
+ # output as just using manifest_dump.
+ cmd = "dump --path=%s"
+ self.assertRunOKFull((cmd)
+ % (manifest_files[1]),
+ expected_pattern, unexpected=False,
+ isPattern=True)
+
+ def testSSTDump(self):
+ print "Running testSSTDump..."
+
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+ self.assertRunOK("put sst1 sst1_val --create_if_missing", "OK")
+ self.assertRunOK("put sst2 sst2_val", "OK")
+ self.assertRunOK("get sst1", "sst1_val")
+
+ # Pattern to expect from SST dump.
+ regex = ".*Sst file format:.*"
+ expected_pattern = re.compile(regex)
+
+ sst_files = self.getSSTFiles(dbPath)
+ self.assertTrue(len(sst_files) >= 1)
+ cmd = "dump --path=%s"
+ self.assertRunOKFull((cmd)
+ % (sst_files[0]),
+ expected_pattern, unexpected=False,
+ isPattern=True)
+
+ def testWALDump(self):
+ print "Running testWALDump..."
+
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME)
+ self.assertRunOK("put wal1 wal1_val --create_if_missing", "OK")
+ self.assertRunOK("put wal2 wal2_val", "OK")
+ self.assertRunOK("get wal1", "wal1_val")
+
+ # Pattern to expect from WAL dump.
+ regex = "^Sequence,Count,ByteSize,Physical Offset,Key\(s\).*"
+ expected_pattern = re.compile(regex)
+
+ wal_files = self.getWALFiles(dbPath)
+ self.assertTrue(len(wal_files) >= 1)
+ cmd = "dump --path=%s"
+ self.assertRunOKFull((cmd)
+ % (wal_files[0]),
+ expected_pattern, unexpected=False,
+ isPattern=True)
+
+ def testListColumnFamilies(self):
+ print "Running testListColumnFamilies..."
+ self.assertRunOK("put x1 y1 --create_if_missing", "OK")
+ cmd = "list_column_families | grep -v \"Column families\""
+ # Test on valid dbPath.
+ self.assertRunOK(cmd, "{default}")
+ # Test on empty path.
+ self.assertRunFAIL(cmd)
+
+ def testColumnFamilies(self):
+ print "Running testColumnFamilies..."
+ dbPath = os.path.join(self.TMP_DIR, self.DB_NAME) # noqa: F841 T25377293 Grandfathered in
+ self.assertRunOK("put cf1_1 1 --create_if_missing", "OK")
+ self.assertRunOK("put cf1_2 2 --create_if_missing", "OK")
+ self.assertRunOK("put cf1_3 3 --try_load_options", "OK")
+ # Given non-default column family to single CF DB.
+ self.assertRunFAIL("get cf1_1 --column_family=two")
+ self.assertRunOK("create_column_family two", "OK")
+ self.assertRunOK("put cf2_1 1 --create_if_missing --column_family=two",
+ "OK")
+ self.assertRunOK("put cf2_2 2 --create_if_missing --column_family=two",
+ "OK")
+ self.assertRunOK("delete cf1_2", "OK")
+ self.assertRunOK("create_column_family three", "OK")
+ self.assertRunOK("delete cf2_2 --column_family=two", "OK")
+ self.assertRunOK(
+ "put cf3_1 3 --create_if_missing --column_family=three",
+ "OK")
+ self.assertRunOK("get cf1_1 --column_family=default", "1")
+ self.assertRunOK("dump --column_family=two",
+ "cf2_1 ==> 1\nKeys in range: 1")
+ self.assertRunOK("dump --column_family=two --try_load_options",
+ "cf2_1 ==> 1\nKeys in range: 1")
+ self.assertRunOK("dump",
+ "cf1_1 ==> 1\ncf1_3 ==> 3\nKeys in range: 2")
+ self.assertRunOK("get cf2_1 --column_family=two",
+ "1")
+ self.assertRunOK("get cf3_1 --column_family=three",
+ "3")
+ self.assertRunOK("drop_column_family three", "OK")
+ # non-existing column family.
+ self.assertRunFAIL("get cf3_1 --column_family=four")
+ self.assertRunFAIL("drop_column_family four")
+
+ def testIngestExternalSst(self):
+ print "Running testIngestExternalSst..."
+
+ # Dump, load, write external sst and ingest it in another db
+ dbPath = os.path.join(self.TMP_DIR, "db1")
+ self.assertRunOK(
+ "batchput --db=%s --create_if_missing x1 y1 x2 y2 x3 y3 x4 y4"
+ % dbPath,
+ "OK")
+ self.assertRunOK("scan --db=%s" % dbPath,
+ "x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
+ dumpFilePath = os.path.join(self.TMP_DIR, "dump1")
+ with open(dumpFilePath, 'w') as f:
+ f.write("x1 ==> y10\nx2 ==> y20\nx3 ==> y30\nx4 ==> y40")
+ externSstPath = os.path.join(self.TMP_DIR, "extern_data1.sst")
+ self.assertTrue(self.writeExternSst("--create_if_missing --db=%s"
+ % dbPath,
+ dumpFilePath,
+ externSstPath))
+ # cannot ingest if allow_global_seqno is false
+ self.assertFalse(
+ self.ingestExternSst(
+ "--create_if_missing --allow_global_seqno=false --db=%s"
+ % dbPath,
+ externSstPath))
+ self.assertTrue(
+ self.ingestExternSst(
+ "--create_if_missing --allow_global_seqno --db=%s"
+ % dbPath,
+ externSstPath))
+ self.assertRunOKFull("scan --db=%s" % dbPath,
+ "x1 : y10\nx2 : y20\nx3 : y30\nx4 : y40")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/storage/rocksdb/rocksdb/tools/ldb_tool.cc b/storage/rocksdb/rocksdb/tools/ldb_tool.cc
new file mode 100644
index 00000000..8174b7e0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/ldb_tool.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "rocksdb/ldb_tool.h"
+#include "rocksdb/utilities/ldb_cmd.h"
+#include "tools/ldb_cmd_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+LDBOptions::LDBOptions() {}
+
+void LDBCommandRunner::PrintHelp(const LDBOptions& ldb_options,
+ const char* /*exec_name*/) {
+ std::string ret;
+
+ ret.append(ldb_options.print_help_header);
+ ret.append("\n\n");
+ ret.append("commands MUST specify --" + LDBCommand::ARG_DB +
+ "=<full_path_to_db_directory> when necessary\n");
+ ret.append("\n");
+ ret.append("commands can optionally specify --" + LDBCommand::ARG_ENV_URI +
+ "=<uri_of_environment> if necessary\n\n");
+ ret.append(
+ "The following optional parameters control if keys/values are "
+ "input/output as hex or as plain strings:\n");
+ ret.append(" --" + LDBCommand::ARG_KEY_HEX +
+ " : Keys are input/output as hex\n");
+ ret.append(" --" + LDBCommand::ARG_VALUE_HEX +
+ " : Values are input/output as hex\n");
+ ret.append(" --" + LDBCommand::ARG_HEX +
+ " : Both keys and values are input/output as hex\n");
+ ret.append("\n");
+
+ ret.append(
+ "The following optional parameters control the database "
+ "internals:\n");
+ ret.append(
+ " --" + LDBCommand::ARG_CF_NAME +
+ "=<string> : name of the column family to operate on. default: default "
+ "column family\n");
+ ret.append(" --" + LDBCommand::ARG_TTL +
+ " with 'put','get','scan','dump','query','batchput'"
+ " : DB supports ttl and value is internally timestamp-suffixed\n");
+ ret.append(" --" + LDBCommand::ARG_TRY_LOAD_OPTIONS +
+ " : Try to load option file from DB.\n");
+ ret.append(" --" + LDBCommand::ARG_IGNORE_UNKNOWN_OPTIONS +
+ " : Ignore unknown options when loading option file.\n");
+ ret.append(" --" + LDBCommand::ARG_BLOOM_BITS + "=<int,e.g.:14>\n");
+ ret.append(" --" + LDBCommand::ARG_FIX_PREFIX_LEN + "=<int,e.g.:14>\n");
+ ret.append(" --" + LDBCommand::ARG_COMPRESSION_TYPE +
+ "=<no|snappy|zlib|bzip2|lz4|lz4hc|xpress|zstd>\n");
+ ret.append(" --" + LDBCommand::ARG_COMPRESSION_MAX_DICT_BYTES +
+ "=<int,e.g.:16384>\n");
+ ret.append(" --" + LDBCommand::ARG_BLOCK_SIZE + "=<block_size_in_bytes>\n");
+ ret.append(" --" + LDBCommand::ARG_AUTO_COMPACTION + "=<true|false>\n");
+ ret.append(" --" + LDBCommand::ARG_DB_WRITE_BUFFER_SIZE +
+ "=<int,e.g.:16777216>\n");
+ ret.append(" --" + LDBCommand::ARG_WRITE_BUFFER_SIZE +
+ "=<int,e.g.:4194304>\n");
+ ret.append(" --" + LDBCommand::ARG_FILE_SIZE + "=<int,e.g.:2097152>\n");
+
+ ret.append("\n\n");
+ ret.append("Data Access Commands:\n");
+ PutCommand::Help(ret);
+ GetCommand::Help(ret);
+ BatchPutCommand::Help(ret);
+ ScanCommand::Help(ret);
+ DeleteCommand::Help(ret);
+ DeleteRangeCommand::Help(ret);
+ DBQuerierCommand::Help(ret);
+ ApproxSizeCommand::Help(ret);
+ CheckConsistencyCommand::Help(ret);
+ ListFileRangeDeletesCommand::Help(ret);
+
+ ret.append("\n\n");
+ ret.append("Admin Commands:\n");
+ WALDumperCommand::Help(ret);
+ CompactorCommand::Help(ret);
+ ReduceDBLevelsCommand::Help(ret);
+ ChangeCompactionStyleCommand::Help(ret);
+ DBDumperCommand::Help(ret);
+ DBLoaderCommand::Help(ret);
+ ManifestDumpCommand::Help(ret);
+ FileChecksumDumpCommand::Help(ret);
+ ListColumnFamiliesCommand::Help(ret);
+ CreateColumnFamilyCommand::Help(ret);
+ DropColumnFamilyCommand::Help(ret);
+ DBFileDumperCommand::Help(ret);
+ InternalDumpCommand::Help(ret);
+ RepairCommand::Help(ret);
+ BackupCommand::Help(ret);
+ RestoreCommand::Help(ret);
+ CheckPointCommand::Help(ret);
+ WriteExternalSstFilesCommand::Help(ret);
+ IngestExternalSstFilesCommand::Help(ret);
+
+ fprintf(stderr, "%s\n", ret.c_str());
+}
+
+int LDBCommandRunner::RunCommand(
+ int argc, char** argv, Options options, const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families) {
+ if (argc <= 2) {
+ PrintHelp(ldb_options, argv[0]);
+ return 1;
+ }
+
+ LDBCommand* cmdObj = LDBCommand::InitFromCmdLineArgs(
+ argc, argv, options, ldb_options, column_families);
+ if (cmdObj == nullptr) {
+ fprintf(stderr, "Unknown command\n");
+ PrintHelp(ldb_options, argv[0]);
+ return 1;
+ }
+
+ if (!cmdObj->ValidateCmdLineOptions()) {
+ return 1;
+ }
+
+ cmdObj->Run();
+ LDBCommandExecuteResult ret = cmdObj->GetExecuteState();
+ fprintf(stderr, "%s\n", ret.ToString().c_str());
+ delete cmdObj;
+
+ return ret.IsFailed() ? 1 : 0;
+}
+
+void LDBTool::Run(int argc, char** argv, Options options,
+ const LDBOptions& ldb_options,
+ const std::vector<ColumnFamilyDescriptor>* column_families) {
+ int error_code = LDBCommandRunner::RunCommand(argc, argv, options,
+ ldb_options, column_families);
+ exit(error_code);
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/pflag b/storage/rocksdb/rocksdb/tools/pflag
new file mode 100755
index 00000000..f3394a66
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/pflag
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+#
+#(c) 2004-present, Facebook, all rights reserved.
+# See the LICENSE file for usage and distribution rights.
+#
+
+trap 'echo "Caught exception, dying"; exit' 1 2 3 15
+
+ME=`basename $0`
+SERVER=`hostname`
+
+#parameters used
+#
+Dump_Config=0
+DEBUG=
+OS=`/bin/uname -s`
+VMEM=
+RSS=
+CPU=
+VERBOSE=
+VAR=
+LIMIT=
+ACTION=
+N=
+WAIT=
+
+#
+#supported OS: Linux only for now. Easy to add
+#
+oscheck() {
+ case ${OS} in
+ Linux)
+ VMEM=vsz
+ RSS=rss
+ CPU=bsdtime
+ ;;
+ *)
+ die "Unsupported OS ${OS}. Send a bug report with OS you need supported. Thanks."
+ ;;
+ esac
+}
+
+
+verbose() {
+ if [ "x$DEBUG" != "x" ]; then
+ echo "$@" >&2
+ fi
+}
+
+warn() {
+ echo "$@" >&2
+}
+
+die() {
+ echo "ERROR: " "$@" >&2;
+ exit;
+}
+
+dump_config() {
+ cat <<EOCONFIG;
+$ME running on ${HOSTNAME} at `date`
+
+Configuration for this run:
+ PID to monitor : ${PID}
+ Resource monitored : ${VAR}
+ Resource limit : ${LIMIT}
+ Check every : ${WAIT} seconds
+ No. of times run : ${N}
+ What to do : ${ACTION}
+EOCONFIG
+
+}
+
+usage() {
+ cat <<USAGE; exit
+$@
+
+Usage ${ME} -p pid [-x {VMEM|RSS|CPU}] -l limit [-a {warn|die|kill}] [-n cycles] [-w wait]
+
+Monitor a process for set of violations. Options:
+
+ -p: PID of process to monitor
+
+ -x: metric to sense. Currently only VMEM/RSS/CPU are supported. Defaults to VMEM
+
+ -l: what is the threshold/limit for the metric that is being sensed.
+ Examples: "-l 100m", "-l 1.5g" (for VMEM/RSS), "-l 5:04" 5:04 in BSDTIME for CPU
+ NOTE: defaults to 1GB
+
+ -a: action. Currently {warn|die|kill} are supported.
+ The default action is to 'warn'. Here is the behavior:
+
+ warn: complain if usage exceeds threshold, but continue monitoring
+ kill: complain, kill the db_bench process and exit
+ die: if usage exceeds threshold, die immediately
+
+ -n: number of cycles to monitor. Default is to monitor until PID no longer exists.
+
+ -w: wait time per cycle of monitoring. Default is 5 seconds.
+
+ -v: verbose messaging
+
+USAGE
+
+}
+
+#set default values if none given
+set_defaults_if_noopt_given() {
+
+ : ${VAR:=vsz}
+ : ${LIMIT:=1024000}
+ : ${WAIT:=5}
+ : ${N:=999999}
+ : ${ACTION:=warn}
+}
+
+validate_options() {
+ if [ "x$PID" = "x" -a $Dump_Config -ne 1 ]; then
+ usage "PID is mandatory"
+ fi
+}
+
+###### START
+
+
+ while getopts ":p:x:l:a:n:t:vhd" opt; do
+ case $opt in
+ d)
+ Dump_Config=1
+ ;;
+ h)
+ usage;
+ ;;
+ a)
+ ACTION=${OPTARG};
+ ;;
+ v)
+ DEBUG=1;
+ ;;
+ p)
+ PID=$OPTARG;
+ ;;
+ x)
+ VAR=$OPTARG;
+ ;;
+ l)
+ LIMIT=$OPTARG;
+ ;;
+ w)
+ WAIT=$OPTARG;
+ ;;
+ n)
+ N=$OPTARG;
+ ;;
+ \?)
+ usage;
+ ;;
+ esac
+ done
+
+oscheck;
+set_defaults_if_noopt_given;
+validate_options;
+
+if [ $Dump_Config -eq 1 ]; then
+ dump_config;
+ exit;
+fi
+
+Done=0
+
+verbose "Trying ${N} times, Waiting ${WAIT} seconds each iteration";
+
+while [ $Done -eq 0 ]; do
+ VAL=`/bin/ps h -p $PID -o ${VAR} | perl -pe 'chomp; s/(.*)m/$1 * 1024/e; s/(.*)g/$1 * 1024 * 1024/e;'`
+ if [ ${VAL:=0} -eq 0 ]; then
+ warn "Process $PID ended without incident."
+ Done=1;
+ break;
+ fi
+
+ if [ $VAL -ge $LIMIT ]; then
+ Done=1;
+ else
+ echo "Value of '${VAR}' (${VAL}) is less than ${LIMIT} for PID ${PID}"
+ sleep $WAIT;
+ fi
+ if [ $Done -eq 1 ]; then
+
+ if [ "$ACTION" = "kill" ]; then
+ kill ${PID} || kill -3 ${PID}
+ exit;
+
+ elif [ "$ACTION" = "warn" ]; then
+
+ # go back to monitoring.
+
+ warn "`date` WARNING: ${VAR} breached threshold ${LIMIT}, actual is ${VAL}"
+ Done=0 #go back to monitoring
+
+ elif [ "$ACTION" = "die" ]; then
+ warn "WARNING: dying without killing process ${PID} on ${SERVER}"
+ warn "The process details are below: "
+ warn "`ps -p ${PID} -o pid,ppid,bsdtime,rss,vsz,cmd,args`"
+ warn ""
+
+ #should we send email/notify someone? TODO... for now, bail.
+
+ exit -1;
+
+ fi
+ else
+ :
+ #warn "INFO: PID $PID, $VAR = $VAL, limit ($LIMIT) not exceeded";
+ fi
+done
+
diff --git a/storage/rocksdb/rocksdb/tools/rdb/.gitignore b/storage/rocksdb/rocksdb/tools/rdb/.gitignore
new file mode 100644
index 00000000..378eac25
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/storage/rocksdb/rocksdb/tools/rdb/API.md b/storage/rocksdb/rocksdb/tools/rdb/API.md
new file mode 100644
index 00000000..e9c2e592
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/API.md
@@ -0,0 +1,178 @@
+# JavaScript API
+
+## DBWrapper
+
+### Constructor
+
+ # Creates a new database wrapper object
+ RDB()
+
+### Open
+
+ # Open a new or existing RocksDB database.
+ #
+ # db_name (string) - Location of the database (inside the
+ # `/tmp` directory).
+ # column_families (string[]) - Names of additional column families
+ # beyond the default. If there are no other
+ # column families, this argument can be
+ # left off.
+ #
+ # Returns true if the database was opened successfully, or false otherwise
+ db_obj.(db_name, column_families = [])
+
+### Get
+
+ # Get the value of a given key.
+ #
+ # key (string) - Which key to get the value of.
+ # column_family (string) - Which column family to check for the key.
+ # This argument can be left off for the default
+ # column family
+ #
+ # Returns the value (string) that is associated with the given key if
+ # one exists, or null otherwise.
+ db_obj.get(key, column_family = { default })
+
+### Put
+
+ # Associate a value with a key.
+ #
+ # key (string) - Which key to associate the value with.
+ # value (string) - The value to associate with the key.
+ # column_family (string) - Which column family to put the key-value pair
+ # in. This argument can be left off for the
+ # default column family.
+ #
+ # Returns true if the key-value pair was successfully stored in the
+ # database, or false otherwise.
+ db_obj.put(key, value, column_family = { default })
+
+### Delete
+
+ # Delete a value associated with a given key.
+ #
+ # key (string) - Which key to delete the value of..
+ # column_family (string) - Which column family to check for the key.
+ # This argument can be left off for the default
+ # column family
+ #
+ # Returns true if an error occurred while trying to delete the key in
+ # the database, or false otherwise. Note that this is NOT the same as
+ # whether a value was deleted; in the case of a specified key not having
+ # a value, this will still return true. Use the `get` method prior to
+ # this method to check if a value existed before the call to `delete`.
+ db_obj.delete(key, column_family = { default })
+
+### Dump
+
+ # Print out all the key-value pairs in a given column family of the
+ # database.
+ #
+ # column_family (string) - Which column family to dump the pairs from.
+ # This argument can be left off for the default
+ # column family.
+ #
+ # Returns true if the keys were successfully read from the database, or
+ # false otherwise.
+ db_obj.dump(column_family = { default })
+
+### WriteBatch
+
+ # Execute an atomic batch of writes (i.e. puts and deletes) to the
+ # database.
+ #
+ # cf_batches (BatchObject[]; see below) - Put and Delete writes grouped
+ # by column family to execute
+ # atomically.
+ #
+ # Returns true if the argument array was well-formed and was
+ # successfully written to the database, or false otherwise.
+ db_obj.writeBatch(cf_batches)
+
+### CreateColumnFamily
+
+ # Create a new column family for the database.
+ #
+ # column_family_name (string) - Name of the new column family.
+ #
+ # Returns true if the new column family was successfully created, or
+ # false otherwise.
+ db_obj.createColumnFamily(column_family_name)
+
+### CompactRange
+
+ # Compact the underlying storage for a given range.
+ #
+ # In addition to the endpoints of the range, the method is overloaded to
+ # accept a non-default column family, a set of options, or both.
+ #
+ # begin (string) - First key in the range to compact.
+ # end (string) - Last key in the range to compact.
+ # options (object) - Contains a subset of the following key-value
+ # pairs:
+ # * 'target_level' => int
+ # * 'target_path_id' => int
+ # column_family (string) - Which column family to compact the range in.
+ db_obj.compactRange(begin, end)
+ db_obj.compactRange(begin, end, options)
+ db_obj.compactRange(begin, end, column_family)
+ db_obj.compactRange(begin, end, options, column_family)
+
+
+
+### Close
+
+ # Close an a database and free the memory associated with it.
+ #
+ # Return null.
+ # db_obj.close()
+
+
+## BatchObject
+
+### Structure
+
+A BatchObject must have at least one of the following key-value pairs:
+
+* 'put' => Array of ['string1', 'string1'] pairs, each of which signifies that
+the key 'string1' should be associated with the value 'string2'
+* 'delete' => Array of strings, each of which is a key whose value should be
+deleted.
+
+The following key-value pair is optional:
+
+* 'column_family' => The name (string) of the column family to apply the
+changes to.
+
+### Examples
+
+ # Writes the key-value pairs 'firstname' => 'Saghm' and
+ # 'lastname' => 'Rossi' atomically to the database.
+ db_obj.writeBatch([
+ {
+ put: [ ['firstname', 'Saghm'], ['lastname', 'Rossi'] ]
+ }
+ ]);
+
+
+ # Deletes the values associated with 'firstname' and 'lastname' in
+ # the default column family and adds the key 'number_of_people' with
+ # with the value '2'. Additionally, adds the key-value pair
+ # 'name' => 'Saghm Rossi' to the column family 'user1' and the pair
+ # 'name' => 'Matt Blaze' to the column family 'user2'. All writes
+ # are done atomically.
+ db_obj.writeBatch([
+ {
+ put: [ ['number_of_people', '2'] ],
+ delete: ['firstname', 'lastname']
+ },
+ {
+ put: [ ['name', 'Saghm Rossi'] ],
+ column_family: 'user1'
+ },
+ {
+ put: [ ['name', Matt Blaze'] ],
+ column_family: 'user2'
+ }
+ ]);
diff --git a/storage/rocksdb/rocksdb/tools/rdb/README.md b/storage/rocksdb/rocksdb/tools/rdb/README.md
new file mode 100644
index 00000000..f69b3f7b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/README.md
@@ -0,0 +1,40 @@
+# RDB - RocksDB Shell
+
+RDB is a NodeJS-based shell interface to RocksDB. It can also be used as a
+JavaScript binding for RocksDB within a Node application.
+
+## Setup/Compilation
+
+### Requirements
+
+* static RocksDB library (i.e. librocksdb.a)
+* libsnappy
+* node (tested onv0.10.33, no guarantees on anything else!)
+* node-gyp
+* python2 (for node-gyp; tested with 2.7.8)
+
+### Installation
+
+NOTE: If your default `python` binary is not a version of python2, add
+the arguments `--python /path/to/python2` to the `node-gyp` commands.
+
+1. Make sure you have the static library (i.e. "librocksdb.a") in the root
+directory of your rocksdb installation. If not, `cd` there and run
+`make static_lib`.
+
+2. Run `node-gyp configure` to generate the build.
+
+3. Run `node-gyp build` to compile RDB.
+
+## Usage
+
+### Running the shell
+
+Assuming everything compiled correctly, you can run the `rdb` executable
+located in the root of the `tools/rdb` directory to start the shell. The file is
+just a shell script that runs the node shell and loads the constructor for the
+RDB object into the top-level function `RDB`.
+
+### JavaScript API
+
+See `API.md` for how to use RocksDB from the shell.
diff --git a/storage/rocksdb/rocksdb/tools/rdb/binding.gyp b/storage/rocksdb/rocksdb/tools/rdb/binding.gyp
new file mode 100644
index 00000000..89145541
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/binding.gyp
@@ -0,0 +1,25 @@
+{
+ "targets": [
+ {
+ "target_name": "rdb",
+ "sources": [
+ "rdb.cc",
+ "db_wrapper.cc",
+ "db_wrapper.h"
+ ],
+ "cflags_cc!": [
+ "-fno-exceptions"
+ ],
+ "cflags_cc+": [
+ "-std=c++11",
+ ],
+ "include_dirs+": [
+ "../../include"
+ ],
+ "libraries": [
+ "../../../librocksdb.a",
+ "-lsnappy"
+ ],
+ }
+ ]
+}
diff --git a/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.cc b/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.cc
new file mode 100644
index 00000000..632cf05a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.cc
@@ -0,0 +1,526 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#include <iostream>
+#include <memory>
+#include <vector>
+#include <v8.h>
+#include <node.h>
+
+#include "db/_wrapper.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+
+namespace {
+ void printWithBackSlashes(std::string str) {
+ for (std::string::size_type i = 0; i < str.size(); i++) {
+ if (str[i] == '\\' || str[i] == '"') {
+ std::cout << "\\";
+ }
+
+ std::cout << str[i];
+ }
+ }
+
+ bool has_key_for_array(Local<Object> obj, std::string key) {
+ return obj->Has(String::NewSymbol(key.c_str())) &&
+ obj->Get(String::NewSymbol(key.c_str()))->IsArray();
+ }
+}
+
+using namespace v8;
+
+
+Persistent<Function> DBWrapper::constructor;
+
+DBWrapper::DBWrapper() {
+ options_.IncreaseParallelism();
+ options_.OptimizeLevelStyleCompaction();
+ options_.disable_auto_compactions = true;
+ options_.create_if_missing = true;
+}
+
+DBWrapper::~DBWrapper() {
+ delete db_;
+}
+
+bool DBWrapper::HasFamilyNamed(std::string& name, DBWrapper* db) {
+ return db->columnFamilies_.find(name) != db->columnFamilies_.end();
+}
+
+
+void DBWrapper::Init(Handle<Object> exports) {
+ Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+ tpl->SetClassName(String::NewSymbol("DBWrapper"));
+ tpl->InstanceTemplate()->SetInternalFieldCount(8);
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("open"),
+ FunctionTemplate::New(Open)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("get"),
+ FunctionTemplate::New(Get)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("put"),
+ FunctionTemplate::New(Put)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("delete"),
+ FunctionTemplate::New(Delete)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("dump"),
+ FunctionTemplate::New(Dump)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("createColumnFamily"),
+ FunctionTemplate::New(CreateColumnFamily)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("writeBatch"),
+ FunctionTemplate::New(WriteBatch)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("compactRange"),
+ FunctionTemplate::New(CompactRange)->GetFunction());
+
+ constructor = Persistent<Function>::New(tpl->GetFunction());
+ exports->Set(String::NewSymbol("DBWrapper"), constructor);
+}
+
+Handle<Value> DBWrapper::Open(const Arguments& args) {
+ HandleScope scope;
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+
+ if (!(args[0]->IsString() &&
+ (args[1]->IsUndefined() || args[1]->IsArray()))) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ std::string db_file = *v8::String::Utf8Value(args[0]->ToString());
+
+ std::vector<std::string> cfs = {ROCKSDB_NAMESPACE::kDefaultColumnFamilyName};
+
+ if (!args[1]->IsUndefined()) {
+ Handle<Array> array = Handle<Array>::Cast(args[1]);
+ for (uint i = 0; i < array->Length(); i++) {
+ if (!array->Get(i)->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ cfs.push_back(*v8::String::Utf8Value(array->Get(i)->ToString()));
+ }
+ }
+
+ if (cfs.size() == 1) {
+ db_wrapper->status_ = ROCKSDB_NAMESPACE::DB::Open(
+ db_wrapper->options_, db_file, &db_wrapper->db_);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> families;
+
+ for (std::vector<int>::size_type i = 0; i < cfs.size(); i++) {
+ families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(
+ cfs[i], ROCKSDB_NAMESPACE::ColumnFamilyOptions()));
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> handles;
+ db_wrapper->status_ = ROCKSDB_NAMESPACE::DB::Open(
+ db_wrapper->options_, db_file, families, &handles, &db_wrapper->db_);
+
+ if (!db_wrapper->status_.ok()) {
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+ }
+
+ for (std::vector<int>::size_type i = 0; i < handles.size(); i++) {
+ db_wrapper->columnFamilies_[cfs[i]] = handles[i];
+ }
+
+ return scope.Close(Boolean::New(true));
+}
+
+
+Handle<Value> DBWrapper::New(const Arguments& args) {
+ HandleScope scope;
+ Handle<Value> to_return;
+
+ if (args.IsConstructCall()) {
+ DBWrapper* db_wrapper = new DBWrapper();
+ db_wrapper->Wrap(args.This());
+
+ return args.This();
+ }
+
+ const int argc = 0;
+ Local<Value> argv[0] = {};
+
+ return scope.Close(constructor->NewInstance(argc, argv));
+}
+
+Handle<Value> DBWrapper::Get(const Arguments& args) {
+ HandleScope scope;
+
+ if (!(args[0]->IsString() &&
+ (args[1]->IsUndefined() || args[1]->IsString()))) {
+ return scope.Close(Null());
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string key = *v8::String::Utf8Value(args[0]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[1]->ToString());
+ std::string value;
+
+ if (args[1]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Get(ROCKSDB_NAMESPACE::ReadOptions(), key, &value);
+ } else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Get(ROCKSDB_NAMESPACE::ReadOptions(),
+ db_wrapper->columnFamilies_[cf], key, &value);
+ } else {
+ return scope.Close(Null());
+ }
+
+ Handle<Value> v = db_wrapper->status_.ok() ?
+ String::NewSymbol(value.c_str()) : Null();
+
+ return scope.Close(v);
+}
+
+Handle<Value> DBWrapper::Put(const Arguments& args) {
+ HandleScope scope;
+
+ if (!(args[0]->IsString() && args[1]->IsString() &&
+ (args[2]->IsUndefined() || args[2]->IsString()))) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string key = *v8::String::Utf8Value(args[0]->ToString());
+ std::string value = *v8::String::Utf8Value(args[1]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[2]->ToString());
+
+ if (args[2]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, value);
+ } else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Put(ROCKSDB_NAMESPACE::WriteOptions(),
+ db_wrapper->columnFamilies_[cf], key, value);
+ } else {
+ return scope.Close(Boolean::New(false));
+ }
+
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::Delete(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string arg0 = *v8::String::Utf8Value(args[0]->ToString());
+ std::string arg1 = *v8::String::Utf8Value(args[1]->ToString());
+
+ if (args[1]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Delete(ROCKSDB_NAMESPACE::WriteOptions(), arg0);
+ } else {
+ if (!db_wrapper->HasFamilyNamed(arg1, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+ db_wrapper->status_ =
+ db_wrapper->db_->Delete(ROCKSDB_NAMESPACE::WriteOptions(),
+ db_wrapper->columnFamilies_[arg1], arg0);
+ }
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::Dump(const Arguments& args) {
+ HandleScope scope;
+ std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> iterator;
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string arg0 = *v8::String::Utf8Value(args[0]->ToString());
+
+ if (args[0]->IsUndefined()) {
+ iterator.reset(
+ db_wrapper->db_->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()));
+ } else {
+ if (!db_wrapper->HasFamilyNamed(arg0, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ iterator.reset(db_wrapper->db_->NewIterator(
+ ROCKSDB_NAMESPACE::ReadOptions(), db_wrapper->columnFamilies_[arg0]));
+ }
+
+ iterator->SeekToFirst();
+
+ while (iterator->Valid()) {
+ std::cout << "\"";
+ printWithBackSlashes(iterator->key().ToString());
+ std::cout << "\" => \"";
+ printWithBackSlashes(iterator->value().ToString());
+ std::cout << "\"\n";
+ iterator->Next();
+ }
+
+ return scope.Close(Boolean::New(true));
+}
+
+Handle<Value> DBWrapper::CreateColumnFamily(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string cf_name = *v8::String::Utf8Value(args[0]->ToString());
+
+ if (db_wrapper->HasFamilyNamed(cf_name, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf;
+ db_wrapper->status_ = db_wrapper->db_->CreateColumnFamily(
+ ROCKSDB_NAMESPACE::ColumnFamilyOptions(), cf_name, &cf);
+
+ if (!db_wrapper->status_.ok()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ db_wrapper->columnFamilies_[cf_name] = cf;
+
+ return scope.Close(Boolean::New(true));
+}
+
+bool DBWrapper::AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array) {
+ Handle<Array> put_pair;
+ for (uint i = 0; i < array->Length(); i++) {
+ if (del) {
+ if (!array->Get(i)->IsString()) {
+ return false;
+ }
+
+ batch.Delete(*v8::String::Utf8Value(array->Get(i)->ToString()));
+ continue;
+ }
+
+ if (!array->Get(i)->IsArray()) {
+ return false;
+ }
+
+ put_pair = Handle<Array>::Cast(array->Get(i));
+
+ if (!put_pair->Get(0)->IsString() || !put_pair->Get(1)->IsString()) {
+ return false;
+ }
+
+ batch.Put(
+ *v8::String::Utf8Value(put_pair->Get(0)->ToString()),
+ *v8::String::Utf8Value(put_pair->Get(1)->ToString()));
+ }
+
+ return true;
+}
+
+bool DBWrapper::AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array, DBWrapper* db_wrapper,
+ std::string cf) {
+ Handle<Array> put_pair;
+ for (uint i = 0; i < array->Length(); i++) {
+ if (del) {
+ if (!array->Get(i)->IsString()) {
+ return false;
+ }
+
+ batch.Delete(
+ db_wrapper->columnFamilies_[cf],
+ *v8::String::Utf8Value(array->Get(i)->ToString()));
+ continue;
+ }
+
+ if (!array->Get(i)->IsArray()) {
+ return false;
+ }
+
+ put_pair = Handle<Array>::Cast(array->Get(i));
+
+ if (!put_pair->Get(0)->IsString() || !put_pair->Get(1)->IsString()) {
+ return false;
+ }
+
+ batch.Put(
+ db_wrapper->columnFamilies_[cf],
+ *v8::String::Utf8Value(put_pair->Get(0)->ToString()),
+ *v8::String::Utf8Value(put_pair->Get(1)->ToString()));
+ }
+
+ return true;
+}
+
+Handle<Value> DBWrapper::WriteBatch(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsArray()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ Handle<Array> sub_batches = Handle<Array>::Cast(args[0]);
+ Local<Object> sub_batch;
+ ROCKSDB_NAMESPACE::WriteBatch batch;
+ bool well_formed;
+
+ for (uint i = 0; i < sub_batches->Length(); i++) {
+ if (!sub_batches->Get(i)->IsObject()) {
+ return scope.Close(Boolean::New(false));
+ }
+ sub_batch = sub_batches->Get(i)->ToObject();
+
+ if (sub_batch->Has(String::NewSymbol("column_family"))) {
+ if (!has_key_for_array(sub_batch, "put") &&
+ !has_key_for_array(sub_batch, "delete")) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ well_formed = db_wrapper->AddToBatch(
+ batch, false,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("put"))),
+ db_wrapper, *v8::String::Utf8Value(sub_batch->Get(
+ String::NewSymbol("column_family"))));
+
+ well_formed = db_wrapper->AddToBatch(
+ batch, true,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("delete"))),
+ db_wrapper, *v8::String::Utf8Value(sub_batch->Get(
+ String::NewSymbol("column_family"))));
+ } else {
+ well_formed = db_wrapper->AddToBatch(
+ batch, false,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("put"))));
+ well_formed = db_wrapper->AddToBatch(
+ batch, true,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("delete"))));
+
+ if (!well_formed) {
+ return scope.Close(Boolean::New(false));
+ }
+ }
+ }
+
+ db_wrapper->status_ =
+ db_wrapper->db_->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactRangeDefault(const Arguments& args) {
+ HandleScope scope;
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(&end, &begin);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactColumnFamily(const Arguments& args) {
+ HandleScope scope;
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[2]->ToString());
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ db_wrapper->columnFamilies_[cf], &begin, &end);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactOptions(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[2]->IsObject()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ Local<Object> options = args[2]->ToObject();
+ int target_level = -1, target_path_id = 0;
+
+ if (options->Has(String::NewSymbol("target_level")) &&
+ options->Get(String::NewSymbol("target_level"))->IsInt32()) {
+ target_level = (int)(options->Get(
+ String::NewSymbol("target_level"))->ToInt32()->Value());
+
+ if (options->Has(String::NewSymbol("target_path_id")) ||
+ options->Get(String::NewSymbol("target_path_id"))->IsInt32()) {
+ target_path_id = (int)(options->Get(
+ String::NewSymbol("target_path_id"))->ToInt32()->Value());
+ }
+ }
+
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ &begin, &end, true, target_level, target_path_id
+ );
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactAll(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[2]->IsObject() || !args[3]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ Local<Object> options = args[2]->ToObject();
+ std::string cf = *v8::String::Utf8Value(args[3]->ToString());
+
+ int target_level = -1, target_path_id = 0;
+
+ if (options->Has(String::NewSymbol("target_level")) &&
+ options->Get(String::NewSymbol("target_level"))->IsInt32()) {
+ target_level = (int)(options->Get(
+ String::NewSymbol("target_level"))->ToInt32()->Value());
+
+ if (options->Has(String::NewSymbol("target_path_id")) ||
+ options->Get(String::NewSymbol("target_path_id"))->IsInt32()) {
+ target_path_id = (int)(options->Get(
+ String::NewSymbol("target_path_id"))->ToInt32()->Value());
+ }
+ }
+
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ db_wrapper->columnFamilies_[cf], &begin, &end, true, target_level,
+ target_path_id);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactRange(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString() || !args[1]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ switch(args.Length()) {
+ case 2:
+ return CompactRangeDefault(args);
+ case 3:
+ return args[2]->IsString() ? CompactColumnFamily(args) :
+ CompactOptions(args);
+ default:
+ return CompactAll(args);
+ }
+}
+
+Handle<Value> DBWrapper::Close(const Arguments& args) {
+ HandleScope scope;
+
+ delete ObjectWrap::Unwrap<DBWrapper>(args.This());
+
+ return scope.Close(Null());
+}
diff --git a/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.h b/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.h
new file mode 100644
index 00000000..4b57320c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/db_wrapper.h
@@ -0,0 +1,60 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#ifndef DBWRAPPER_H
+#define DBWRAPPER_H
+
+#include <map>
+#include <node.h>
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/options.h"
+
+using namespace v8;
+
+// Used to encapsulate a particular instance of an opened database.
+//
+// This object should not be used directly in C++; it exists solely to provide
+// a mapping from a JavaScript object to a C++ code that can use the RocksDB
+// API.
+class DBWrapper : public node::ObjectWrap {
+ public:
+ static void Init(Handle<Object> exports);
+
+ private:
+ explicit DBWrapper();
+ ~DBWrapper();
+
+ // Helper methods
+ static bool HasFamilyNamed(std::string& name, DBWrapper* db);
+ static bool AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array);
+ static bool AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array, DBWrapper* db_wrapper,
+ std::string cf);
+ static Handle<Value> CompactRangeDefault(const v8::Arguments& args);
+ static Handle<Value> CompactColumnFamily(const Arguments& args);
+ static Handle<Value> CompactOptions(const Arguments& args);
+ static Handle<Value> CompactAll(const Arguments& args);
+
+ // C++ mappings of API methods
+ static Persistent<v8::Function> constructor;
+ static Handle<Value> Open(const Arguments& args);
+ static Handle<Value> New(const Arguments& args);
+ static Handle<Value> Get(const Arguments& args);
+ static Handle<Value> Put(const Arguments& args);
+ static Handle<Value> Delete(const Arguments& args);
+ static Handle<Value> Dump(const Arguments& args);
+ static Handle<Value> WriteBatch(const Arguments& args);
+ static Handle<Value> CreateColumnFamily(const Arguments& args);
+ static Handle<Value> CompactRange(const Arguments& args);
+ static Handle<Value> Close(const Arguments& args);
+
+ // Internal fields
+ ROCKSDB_NAMESPACE::Options options_;
+ ROCKSDB_NAMESPACE::Status status_;
+ ROCKSDB_NAMESPACE::DB* db_;
+ std::unordered_map<std::string, ROCKSDB_NAMESPACE::ColumnFamilyHandle*>
+ columnFamilies_;
+};
+
+#endif
diff --git a/storage/rocksdb/rocksdb/tools/rdb/rdb b/storage/rocksdb/rocksdb/tools/rdb/rdb
new file mode 100755
index 00000000..05da1158
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/rdb
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+node -e "RDB = require('./build/Release/rdb').DBWrapper; console.log('Loaded rocksdb in variable RDB'); repl = require('repl').start('> ');"
diff --git a/storage/rocksdb/rocksdb/tools/rdb/rdb.cc b/storage/rocksdb/rocksdb/tools/rdb/rdb.cc
new file mode 100644
index 00000000..119fcc41
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/rdb.cc
@@ -0,0 +1,16 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#ifndef BUILDING_NODE_EXTENSION
+#define BUILDING_NODE_EXTENSION
+#endif
+
+#include <node.h>
+#include <v8.h>
+#include "db/_wrapper.h"
+
+using namespace v8;
+
+void InitAll(Handle<Object> exports) {
+ DBWrapper::Init(exports);
+}
+
+NODE_MODULE(rdb, InitAll)
diff --git a/storage/rocksdb/rocksdb/tools/rdb/unit_test.js b/storage/rocksdb/rocksdb/tools/rdb/unit_test.js
new file mode 100644
index 00000000..d01ae1df
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rdb/unit_test.js
@@ -0,0 +1,125 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+assert = require('assert')
+RDB = require('./build/Release/rdb').DBWrapper
+exec = require('child_process').exec
+util = require('util')
+
+DB_NAME = '/tmp/rocksdbtest-' + process.getuid()
+
+a = RDB()
+assert.equal(a.open(DB_NAME, ['b']), false)
+
+exec(
+ util.format(
+ "node -e \"RDB = require('./build/Release/rdb').DBWrapper; \
+ a = RDB('%s'); a.createColumnFamily('b')\"",
+ DB_NAME
+ ).exitCode, null
+)
+
+
+exec(
+ util.format(
+ "node -e \"RDB = require('./build/Release/rdb').DBWrapper; \
+ a = RDB('%s', ['b'])\"",
+ DB_NAME
+ ).exitCode, null
+)
+
+exec('rm -rf ' + DB_NAME)
+
+a = RDB()
+assert.equal(a.open(DB_NAME, ['a']), false)
+assert(a.open(DB_NAME), true)
+assert(a.createColumnFamily('temp'))
+
+b = RDB()
+assert.equal(b.open(DB_NAME), false)
+
+exec('rm -rf ' + DB_NAME)
+
+DB_NAME += 'b'
+
+a = RDB()
+assert(a.open(DB_NAME))
+assert.equal(a.constructor.name, 'DBWrapper')
+assert.equal(a.createColumnFamily(), false)
+assert.equal(a.createColumnFamily(1), false)
+assert.equal(a.createColumnFamily(['']), false)
+assert(a.createColumnFamily('b'))
+assert.equal(a.createColumnFamily('b'), false)
+
+// Get and Put
+assert.equal(a.get(1), null)
+assert.equal(a.get(['a']), null)
+assert.equal(a.get('a', 1), null)
+assert.equal(a.get(1, 'a'), null)
+assert.equal(a.get(1, 1), null)
+
+assert.equal(a.put(1), false)
+assert.equal(a.put(['a']), false)
+assert.equal(a.put('a', 1), false)
+assert.equal(a.put(1, 'a'), false)
+assert.equal(a.put(1, 1), false)
+assert.equal(a.put('a', 'a', 1), false)
+assert.equal(a.put('a', 1, 'a'), false)
+assert.equal(a.put(1, 'a', 'a'), false)
+assert.equal(a.put('a', 1, 1), false)
+assert.equal(a.put(1, 'a', 1), false)
+assert.equal(a.put(1, 1, 'a'), false)
+assert.equal(a.put(1, 1, 1), false)
+
+
+assert.equal(a.get(), null)
+assert.equal(a.get('a'), null)
+assert.equal(a.get('a', 'c'), null)
+assert.equal(a.put(), false)
+assert.equal(a.put('a'), false)
+assert.equal(a.get('a', 'b', 'c'), null)
+
+assert(a.put('a', 'axe'))
+assert(a.put('a', 'first'))
+assert.equal(a.get('a'), 'first')
+assert.equal(a.get('a', 'b'), null)
+assert.equal(a.get('a', 'c'), null)
+
+assert(a.put('a', 'apple', 'b'))
+assert.equal(a.get('a', 'b'), 'apple')
+assert.equal(a.get('a'), 'first')
+assert(a.put('b', 'butter', 'b'), 'butter')
+assert(a.put('b', 'banana', 'b'))
+assert.equal(a.get('b', 'b'), 'banana')
+assert.equal(a.get('b'), null)
+assert.equal(a.get('b', 'c'), null)
+
+// Delete
+assert.equal(a.delete(1), false)
+assert.equal(a.delete('a', 1), false)
+assert.equal(a.delete(1, 'a'), false)
+assert.equal(a.delete(1, 1), false)
+
+assert.equal(a.delete('b'), true)
+assert(a.delete('a'))
+assert.equal(a.get('a'), null)
+assert.equal(a.get('a', 'b'), 'apple')
+assert.equal(a.delete('c', 'c'), false)
+assert.equal(a.delete('c', 'b'), true)
+assert(a.delete('b', 'b'))
+assert.equal(a.get('b', 'b'), null)
+
+// Dump
+console.log("MARKER 1")
+assert(a.dump())
+console.log("Should be no output between 'MARKER 1' and here\n")
+console.log('Next line should be "a" => "apple"')
+assert(a.dump('b'))
+
+console.log("\nMARKER 2")
+assert.equal(a.dump('c'), false)
+console.log("Should be no output between 'MARKER 2' and here\n")
+
+// WriteBatch
+
+
+// Clean up test database
+exec('rm -rf ' + DB_NAME)
diff --git a/storage/rocksdb/rocksdb/tools/reduce_levels_test.cc b/storage/rocksdb/rocksdb/tools/reduce_levels_test.cc
new file mode 100644
index 00000000..1b1044f9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/reduce_levels_test.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#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/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ReduceLevelTest : public testing::Test {
+public:
+ ReduceLevelTest() {
+ dbname_ = test::PerThreadDBPath("db_reduce_levels_test");
+ 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 = reinterpret_cast<DBImpl*>(db_);
+ return db_impl->TEST_FlushMemTable();
+ }
+
+ void MoveL0FileToLevel(int level) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(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" + NumberToString(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<std::string> 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"));
+ 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"));
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as LDBCommand is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/regression_test.sh b/storage/rocksdb/rocksdb/tools/regression_test.sh
new file mode 100755
index 00000000..79963738
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/regression_test.sh
@@ -0,0 +1,470 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# The RocksDB regression test script.
+# REQUIREMENT: must be able to run make db_bench in the current directory
+#
+# This script will do the following things in order:
+#
+# 1. check out the specified rocksdb commit.
+# 2. build db_bench using the specified commit
+# 3. setup test directory $TEST_PATH. If not specified, then the test directory
+# will be "/tmp/rocksdb/regression_test"
+# 4. run set of benchmarks on the specified host
+# (can be either locally or remotely)
+# 5. generate report in the $RESULT_PATH. If RESULT_PATH is not specified,
+# RESULT_PATH will be set to $TEST_PATH/current_time
+#
+# = Examples =
+# * Run the regression test using rocksdb commit abcdef that outputs results
+# and temp files in "/my/output/dir"
+#r
+# TEST_PATH=/my/output/dir COMMIT_ID=abcdef ./tools/regression_test.sh
+#
+# * Run the regression test on a remost host under "/my/output/dir" directory
+# and stores the result locally in "/my/benchmark/results" using commit
+# abcdef and with the rocksdb options specified in /my/path/to/OPTIONS-012345
+# with 1000000000 keys in each benchmark in the regression test where each
+# key and value are 100 and 900 bytes respectively:
+#
+# REMOTE_USER_AT_HOST=yhchiang@my.remote.host \
+# TEST_PATH=/my/output/dir \
+# RESULT_PATH=/my/benchmark/results \
+# COMMIT_ID=abcdef \
+# OPTIONS_FILE=/my/path/to/OPTIONS-012345 \
+# NUM_KEYS=1000000000 \
+# KEY_SIZE=100 \
+# VALUE_SIZE=900 \
+# ./tools/regression_test.sh
+#
+# = Regression test environmental parameters =
+# DEBUG: If true, then the script will not checkout master and build db_bench
+# if db_bench already exists
+# Default: 0
+# TEST_MODE: If 1, run fillseqdeterminstic and benchmarks both
+# if 0, only run fillseqdeterministc
+# if 2, only run benchmarks
+# Default: 1
+# TEST_PATH: the root directory of the regression test.
+# Default: "/tmp/rocksdb/regression_test"
+# RESULT_PATH: the directory where the regression results will be generated.
+# Default: "$TEST_PATH/current_time"
+# REMOTE_USER_AT_HOST: If set, then test will run on the specified host under
+# TEST_PATH directory and outputs test results locally in RESULT_PATH
+# The REMOTE_USER_AT_HOST should follow the format user-id@host.name
+# DB_PATH: the path where the rocksdb database will be created during the
+# regression test. Default: $TEST_PATH/db
+# WAL_PATH: the path where the rocksdb WAL will be outputed.
+# Default: $TEST_PATH/wal
+# OPTIONS_FILE: If specified, then the regression test will use the specified
+# file to initialize the RocksDB options in its benchmarks. Note that
+# this feature only work for commits after 88acd93 or rocksdb version
+# later than 4.9.
+# DELETE_TEST_PATH: If true, then the test directory will be deleted
+# after the script ends.
+# Default: 0
+#
+# = db_bench parameters =
+# NUM_THREADS: The number of concurrent foreground threads that will issue
+# database operations in the benchmark. Default: 16.
+# NUM_KEYS: The key range that will be used in the entire regression test.
+# Default: 1G.
+# NUM_OPS: The number of operations (reads, writes, or deletes) that will
+# be issued in EACH thread.
+# Default: $NUM_KEYS / $NUM_THREADS
+# KEY_SIZE: The size of each key in bytes in db_bench. Default: 100.
+# VALUE_SIZE: The size of each value in bytes in db_bench. Default: 900.
+# CACHE_SIZE: The size of RocksDB block cache used in db_bench. Default: 1G
+# STATISTICS: If 1, then statistics is on in db_bench. Default: 0.
+# COMPRESSION_RATIO: The compression ratio of the key generated in db_bench.
+# Default: 0.5.
+# HISTOGRAM: If 1, then the histogram feature on performance feature is on.
+# STATS_PER_INTERVAL: If 1, then the statistics will be reported for every
+# STATS_INTERVAL_SECONDS seconds. Default 1.
+# STATS_INTERVAL_SECONDS: If STATS_PER_INTERVAL is set to 1, then statistics
+# will be reported for every STATS_INTERVAL_SECONDS. Default 60.
+# MAX_BACKGROUND_FLUSHES: The maxinum number of concurrent flushes in
+# db_bench. Default: 4.
+# MAX_BACKGROUND_COMPACTIONS: The maximum number of concurrent compactions
+# in db_bench. Default: 16.
+# NUM_HIGH_PRI_THREADS: The number of high-pri threads available for
+# concurrent flushes in db_bench. Default: 4.
+# NUM_LOW_PRI_THREADS: The number of low-pri threads available for
+# concurrent compactions in db_bench. Default: 16.
+# SEEK_NEXTS: Controls how many Next() will be called after seek.
+# Default: 10.
+# SEED: random seed that controls the randomness of the benchmark.
+# Default: $( date +%s )
+
+#==============================================================================
+# CONSTANT
+#==============================================================================
+TITLE_FORMAT="%40s,%25s,%30s,%7s,%9s,%8s,"
+TITLE_FORMAT+="%10s,%13s,%14s,%11s,%12s,"
+TITLE_FORMAT+="%7s,%11s,"
+TITLE_FORMAT+="%9s,%10s,%10s,%10s,%10s,%10s,%5s,"
+TITLE_FORMAT+="%5s,%5s,%5s" # time
+TITLE_FORMAT+="\n"
+
+DATA_FORMAT="%40s,%25s,%30s,%7s,%9s,%8s,"
+DATA_FORMAT+="%10s,%13.0f,%14s,%11s,%12s,"
+DATA_FORMAT+="%7s,%11s,"
+DATA_FORMAT+="%9.0f,%10.0f,%10.0f,%10.0f,%10.0f,%10.0f,%5.0f,"
+DATA_FORMAT+="%5.0f,%5.0f,%5.0f" # time
+DATA_FORMAT+="\n"
+
+MAIN_PATTERN="$1""[[:blank:]]+:.*[[:blank:]]+([0-9\.]+)[[:blank:]]+ops/sec"
+PERC_PATTERN="Percentiles: P50: ([0-9\.]+) P75: ([0-9\.]+) "
+PERC_PATTERN+="P99: ([0-9\.]+) P99.9: ([0-9\.]+) P99.99: ([0-9\.]+)"
+#==============================================================================
+
+function main {
+ TEST_ROOT_DIR=${TEST_PATH:-"/tmp/rocksdb/regression_test"}
+ init_arguments $TEST_ROOT_DIR
+
+ build_db_bench_and_ldb
+
+ setup_test_directory
+ if [ $TEST_MODE -le 1 ]; then
+ tmp=$DB_PATH
+ DB_PATH=$ORIGIN_PATH
+ test_remote "test -d $DB_PATH"
+ if [[ $? -ne 0 ]]; then
+ echo "Building DB..."
+ # compactall alone will not print ops or threads, which will fail update_report
+ run_db_bench "fillseq,compactall" $NUM_KEYS 1 0 0
+ fi
+ DB_PATH=$tmp
+ fi
+ if [ $TEST_MODE -ge 1 ]; then
+ build_checkpoint
+ run_db_bench "readrandom"
+ run_db_bench "readwhilewriting"
+ run_db_bench "deleterandom" $((NUM_KEYS / 10 / $NUM_THREADS))
+ run_db_bench "seekrandom"
+ run_db_bench "seekrandomwhilewriting"
+ fi
+
+ cleanup_test_directory $TEST_ROOT_DIR
+ echo ""
+ echo "Benchmark completed! Results are available in $RESULT_PATH"
+}
+
+############################################################################
+function init_arguments {
+ K=1024
+ M=$((1024 * K))
+ G=$((1024 * M))
+
+ current_time=$(date +"%F-%H:%M:%S")
+ RESULT_PATH=${RESULT_PATH:-"$1/results/$current_time"}
+ COMMIT_ID=`git log | head -n1 | cut -c 8-`
+ SUMMARY_FILE="$RESULT_PATH/SUMMARY.csv"
+
+ DB_PATH=${3:-"$1/db"}
+ ORIGIN_PATH=${ORIGIN_PATH:-"$(dirname $(dirname $DB_PATH))/db"}
+ WAL_PATH=${4:-""}
+ if [ -z "$REMOTE_USER_AT_HOST" ]; then
+ DB_BENCH_DIR=${5:-"."}
+ else
+ DB_BENCH_DIR=${5:-"$1/db_bench"}
+ fi
+
+ DEBUG=${DEBUG:-0}
+ TEST_MODE=${TEST_MODE:-1}
+ SCP=${SCP:-"scp"}
+ SSH=${SSH:-"ssh"}
+ NUM_THREADS=${NUM_THREADS:-16}
+ NUM_KEYS=${NUM_KEYS:-$((1 * G))} # key range
+ NUM_OPS=${NUM_OPS:-$(($NUM_KEYS / $NUM_THREADS))}
+ KEY_SIZE=${KEY_SIZE:-100}
+ VALUE_SIZE=${VALUE_SIZE:-900}
+ CACHE_SIZE=${CACHE_SIZE:-$((1 * G))}
+ STATISTICS=${STATISTICS:-0}
+ COMPRESSION_RATIO=${COMPRESSION_RATIO:-0.5}
+ HISTOGRAM=${HISTOGRAM:-1}
+ NUM_MULTI_DB=${NUM_MULTI_DB:-1}
+ STATS_PER_INTERVAL=${STATS_PER_INTERVAL:-1}
+ STATS_INTERVAL_SECONDS=${STATS_INTERVAL_SECONDS:-600}
+ MAX_BACKGROUND_FLUSHES=${MAX_BACKGROUND_FLUSHES:-4}
+ MAX_BACKGROUND_COMPACTIONS=${MAX_BACKGROUND_COMPACTIONS:-16}
+ NUM_HIGH_PRI_THREADS=${NUM_HIGH_PRI_THREADS:-4}
+ NUM_LOW_PRI_THREADS=${NUM_LOW_PRI_THREADS:-16}
+ DELETE_TEST_PATH=${DELETE_TEST_PATH:-0}
+ SEEK_NEXTS=${SEEK_NEXTS:-10}
+ SEED=${SEED:-$( date +%s )}
+}
+
+# $1 --- benchmark name
+# $2 --- number of operations. Default: $NUM_KEYS
+# $3 --- number of threads. Default $NUM_THREADS
+# $4 --- use_existing_db. Default: 1
+# $5 --- update_report. Default: 1
+function run_db_bench {
+ # this will terminate all currently-running db_bench
+ find_db_bench_cmd="ps aux | grep db_bench | grep -v grep | grep -v aux | awk '{print \$2}'"
+
+ ops=${2:-$NUM_OPS}
+ threads=${3:-$NUM_THREADS}
+ USE_EXISTING_DB=${4:-1}
+ UPDATE_REPORT=${5:-1}
+ echo ""
+ echo "======================================================================="
+ echo "Benchmark $1"
+ echo "======================================================================="
+ echo ""
+ db_bench_error=0
+ options_file_arg=$(setup_options_file)
+ echo "$options_file_arg"
+ # use `which time` to avoid using bash's internal time command
+ db_bench_cmd="("'\$(which time)'" -p $DB_BENCH_DIR/db_bench \
+ --benchmarks=$1 --db=$DB_PATH --wal_dir=$WAL_PATH \
+ --use_existing_db=$USE_EXISTING_DB \
+ --disable_auto_compactions \
+ --threads=$threads \
+ --num=$NUM_KEYS \
+ --reads=$ops \
+ --writes=$ops \
+ --deletes=$ops \
+ --key_size=$KEY_SIZE \
+ --value_size=$VALUE_SIZE \
+ --cache_size=$CACHE_SIZE \
+ --statistics=$STATISTICS \
+ $options_file_arg \
+ --compression_ratio=$COMPRESSION_RATIO \
+ --histogram=$HISTOGRAM \
+ --seek_nexts=$SEEK_NEXTS \
+ --stats_per_interval=$STATS_PER_INTERVAL \
+ --stats_interval_seconds=$STATS_INTERVAL_SECONDS \
+ --max_background_flushes=$MAX_BACKGROUND_FLUSHES \
+ --num_multi_db=$NUM_MULTI_DB \
+ --max_background_compactions=$MAX_BACKGROUND_COMPACTIONS \
+ --num_high_pri_threads=$NUM_HIGH_PRI_THREADS \
+ --num_low_pri_threads=$NUM_LOW_PRI_THREADS \
+ --seed=$SEED) 2>&1"
+ ps_cmd="ps aux"
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ echo "Running benchmark remotely on $REMOTE_USER_AT_HOST"
+ db_bench_cmd="$SSH $REMOTE_USER_AT_HOST \"$db_bench_cmd\""
+ ps_cmd="$SSH $REMOTE_USER_AT_HOST $ps_cmd"
+ fi
+
+ ## make sure no db_bench is running
+ # The following statement is necessary make sure "eval $ps_cmd" will success.
+ # Otherwise, if we simply check whether "$(eval $ps_cmd | grep db_bench)" is
+ # successful or not, then it will always be false since grep will return
+ # non-zero status when there's no matching output.
+ ps_output="$(eval $ps_cmd)"
+ exit_on_error $? "$ps_cmd"
+
+ # perform the actual command to check whether db_bench is running
+ grep_output="$(eval $ps_cmd | grep db_bench | grep -v grep)"
+ if [ "$grep_output" != "" ]; then
+ echo "Stopped regression_test.sh as there're still db_bench processes running:"
+ echo $grep_output
+ echo "Clean up test directory"
+ cleanup_test_directory $TEST_ROOT_DIR
+ exit 2
+ fi
+
+ ## run the db_bench
+ cmd="($db_bench_cmd || db_bench_error=1) | tee -a $RESULT_PATH/$1"
+ exit_on_error $?
+ echo $cmd
+ eval $cmd
+ exit_on_error $db_bench_error
+ if [ $UPDATE_REPORT -ne 0 ]; then
+ update_report "$1" "$RESULT_PATH/$1" $ops $threads
+ fi
+}
+
+function build_checkpoint {
+ cmd_prefix=""
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ cmd_prefix="$SSH $REMOTE_USER_AT_HOST "
+ fi
+ if [ $NUM_MULTI_DB -gt 1 ]; then
+ dirs=$($cmd_prefix find $ORIGIN_PATH -type d -links 2)
+ for dir in $dirs; do
+ db_index=$(basename $dir)
+ echo "Building checkpoints: $ORIGIN_PATH/$db_index -> $DB_PATH/$db_index ..."
+ $cmd_prefix $DB_BENCH_DIR/ldb checkpoint --checkpoint_dir=$DB_PATH/$db_index \
+ --db=$ORIGIN_PATH/$db_index 2>&1
+ done
+ else
+ # checkpoint cannot build in directory already exists
+ $cmd_prefix rm -rf $DB_PATH
+ echo "Building checkpoint: $ORIGIN_PATH -> $DB_PATH ..."
+ $cmd_prefix $DB_BENCH_DIR/ldb checkpoint --checkpoint_dir=$DB_PATH \
+ --db=$ORIGIN_PATH 2>&1
+ fi
+}
+
+function multiply {
+ echo "$1 * $2" | bc
+}
+
+# $1 --- name of the benchmark
+# $2 --- the filename of the output log of db_bench
+function update_report {
+ main_result=`cat $2 | grep $1`
+ exit_on_error $?
+ perc_statement=`cat $2 | grep Percentile`
+ exit_on_error $?
+
+ # Obtain micros / op
+
+ [[ $main_result =~ $MAIN_PATTERN ]]
+ ops_per_s=${BASH_REMATCH[1]}
+
+ # Obtain percentile information
+ [[ $perc_statement =~ $PERC_PATTERN ]]
+ perc[0]=${BASH_REMATCH[1]} # p50
+ perc[1]=${BASH_REMATCH[2]} # p75
+ perc[2]=${BASH_REMATCH[3]} # p99
+ perc[3]=${BASH_REMATCH[4]} # p99.9
+ perc[4]=${BASH_REMATCH[5]} # p99.99
+
+ # Parse the output of the time command
+ real_sec=`tail -3 $2 | grep real | awk '{print $2}'`
+ user_sec=`tail -3 $2 | grep user | awk '{print $2}'`
+ sys_sec=`tail -3 $2 | grep sys | awk '{print $2}'`
+
+ (printf "$DATA_FORMAT" \
+ $COMMIT_ID $1 $REMOTE_USER_AT_HOST $NUM_MULTI_DB $NUM_KEYS $KEY_SIZE $VALUE_SIZE \
+ $(multiply $COMPRESSION_RATIO 100) \
+ $3 $4 $CACHE_SIZE \
+ $MAX_BACKGROUND_FLUSHES $MAX_BACKGROUND_COMPACTIONS \
+ $ops_per_s \
+ $(multiply ${perc[0]} 1000) \
+ $(multiply ${perc[1]} 1000) \
+ $(multiply ${perc[2]} 1000) \
+ $(multiply ${perc[3]} 1000) \
+ $(multiply ${perc[4]} 1000) \
+ $DEBUG \
+ $real_sec \
+ $user_sec \
+ $sys_sec \
+ >> $SUMMARY_FILE)
+ exit_on_error $?
+}
+
+function exit_on_error {
+ if [ $1 -ne 0 ]; then
+ echo ""
+ echo "ERROR: Benchmark did not complete successfully."
+ if ! [ -z "$2" ]; then
+ echo "Failure command: $2"
+ fi
+ echo "Partial results are output to $RESULT_PATH"
+ echo "ERROR" >> $SUMMARY_FILE
+ exit $1
+ fi
+}
+
+function checkout_rocksdb {
+ echo "Checking out commit $1 ..."
+
+ git fetch --all
+ exit_on_error $?
+
+ git checkout $1
+ exit_on_error $?
+}
+
+function build_db_bench_and_ldb {
+ echo "Building db_bench & ldb ..."
+
+ make clean
+ exit_on_error $?
+
+ DEBUG_LEVEL=0 PORTABLE=1 make db_bench ldb -j32
+ exit_on_error $?
+}
+
+function run_remote {
+ test_remote "$1"
+ exit_on_error $? "$1"
+}
+
+function test_remote {
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ cmd="$SSH $REMOTE_USER_AT_HOST '$1'"
+ else
+ cmd="$1"
+ fi
+ eval "$cmd"
+}
+
+function run_local {
+ eval "$1"
+ exit_on_error $?
+}
+
+function setup_options_file {
+ if ! [ -z "$OPTIONS_FILE" ]; then
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ options_file="$DB_BENCH_DIR/OPTIONS_FILE"
+ run_local "$SCP $OPTIONS_FILE $REMOTE_USER_AT_HOST:$options_file"
+ else
+ options_file="$OPTIONS_FILE"
+ fi
+ echo "--options_file=$options_file"
+ fi
+ echo ""
+}
+
+function setup_test_directory {
+ echo "Deleting old regression test directories and creating new ones"
+
+ run_remote "rm -rf $DB_PATH"
+ run_remote "rm -rf $DB_BENCH_DIR"
+ run_local "rm -rf $RESULT_PATH"
+
+ if ! [ -z "$WAL_PATH" ]; then
+ run_remote "rm -rf $WAL_PATH"
+ run_remote "mkdir -p $WAL_PATH"
+ fi
+
+ run_remote "mkdir -p $DB_PATH"
+
+ run_remote "mkdir -p $DB_BENCH_DIR"
+ run_remote "ls -l $DB_BENCH_DIR"
+
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ run_local "$SCP ./db_bench $REMOTE_USER_AT_HOST:$DB_BENCH_DIR/db_bench"
+ run_local "$SCP ./ldb $REMOTE_USER_AT_HOST:$DB_BENCH_DIR/ldb"
+ fi
+
+ run_local "mkdir -p $RESULT_PATH"
+
+ (printf $TITLE_FORMAT \
+ "commit id" "benchmark" "user@host" "num-dbs" "key-range" "key-size" \
+ "value-size" "compress-rate" "ops-per-thread" "num-threads" "cache-size" \
+ "flushes" "compactions" \
+ "ops-per-s" "p50" "p75" "p99" "p99.9" "p99.99" "debug" \
+ "real-sec" "user-sec" "sys-sec" \
+ >> $SUMMARY_FILE)
+ exit_on_error $?
+}
+
+function cleanup_test_directory {
+
+ if [ $DELETE_TEST_PATH -ne 0 ]; then
+ echo "Clear old regression test directories and creating new ones"
+ run_remote "rm -rf $DB_PATH"
+ run_remote "rm -rf $WAL_PATH"
+ if ! [ -z "$REMOTE_USER_AT_HOST" ]; then
+ run_remote "rm -rf $DB_BENCH_DIR"
+ fi
+ run_remote "rm -rf $1"
+ else
+ echo "------------ DEBUG MODE ------------"
+ echo "DB PATH: $DB_PATH"
+ echo "WAL PATH: $WAL_PATH"
+ fi
+}
+
+############################################################################
+
+# shellcheck disable=SC2068
+main $@
diff --git a/storage/rocksdb/rocksdb/tools/report_lite_binary_size.sh b/storage/rocksdb/rocksdb/tools/report_lite_binary_size.sh
new file mode 100755
index 00000000..e3fe7bc2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/report_lite_binary_size.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# Script to report lite build binary size for latest RocksDB commits.
+# Usage:
+# ./report_lite_binary_size [num_recent_commits]
+
+num_recent_commits=${1:-10}
+
+echo "Computing RocksDB lite build binary size for the most recent $num_recent_commits commits."
+
+for ((i=0; i < num_recent_commits; i++))
+do
+ git checkout master~$i
+ commit_hash=$(git show -s --format=%H)
+ commit_time=$(git show -s --format=%ct)
+
+ # It would be nice to check if scuba already have a record for the commit,
+ # but sandcastle don't seems to have scuba CLI installed.
+
+ make clean
+ make OPT=-DROCKSDB_LITE static_lib
+
+ if make OPT=-DROCKSDB_LITE static_lib
+ then
+ build_succeeded='true'
+ strip librocksdb.a
+ binary_size=$(stat -c %s librocksdb.a)
+ else
+ build_succeeded='false'
+ binary_size=0
+ fi
+
+ current_time="\"time\": $(date +%s)"
+ commit_hash="\"hash\": \"$commit_hash\""
+ commit_time="\"commit_time\": $commit_time"
+ build_succeeded="\"build_succeeded\": \"$build_succeeded\""
+ binary_size="\"binary_size\": $binary_size"
+
+ scribe_log="{\"int\":{$current_time, $commit_time, $binary_size}, \"normal\":{$commit_hash, $build_succeeded}}"
+ echo "Logging to scribe: $scribe_log"
+ scribe_cat perfpipe_rocksdb_lite_build "$scribe_log"
+done
diff --git a/storage/rocksdb/rocksdb/tools/rocksdb_dump_test.sh b/storage/rocksdb/rocksdb/tools/rocksdb_dump_test.sh
new file mode 100755
index 00000000..532c5326
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/rocksdb_dump_test.sh
@@ -0,0 +1,9 @@
+# shellcheck disable=SC2148
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+TESTDIR=`mktemp -d ${TMPDIR:-/tmp}/rocksdb-dump-test.XXXXX`
+DUMPFILE="tools/sample-dump.dmp"
+
+# Verify that the sample dump file is undumpable and then redumpable.
+./rocksdb_undump --dump_location=$DUMPFILE --db_path=$TESTDIR/db
+./rocksdb_dump --anonymous --db_path=$TESTDIR/db --dump_location=$TESTDIR/dump
+cmp $DUMPFILE $TESTDIR/dump
diff --git a/storage/rocksdb/rocksdb/tools/run_flash_bench.sh b/storage/rocksdb/rocksdb/tools/run_flash_bench.sh
new file mode 100755
index 00000000..26e25384
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/run_flash_bench.sh
@@ -0,0 +1,359 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# REQUIRE: benchmark.sh exists in the current directory
+# After execution of this script, log files are generated in $output_dir.
+# report.txt provides a high level statistics
+
+# This should be run from the parent of the tools directory. The command line is:
+# [$env_vars] tools/run_flash_bench.sh [list-of-threads]
+#
+# This runs a sequence of tests in the following sequence:
+# step 1) load - bulkload, compact, fillseq, overwrite
+# step 2) read-only for each number of threads
+# step 3) read-write for each number of threads
+# step 4) merge for each number of threads
+#
+# The list of threads is optional and when not set is equivalent to "24".
+# Were list-of-threads specified as "1 2 4" then the tests in steps 2, 3 and
+# 4 above would be repeated for 1, 2 and 4 threads. The tests in step 1 are
+# only run for 1 thread.
+
+# Test output is written to $OUTPUT_DIR, currently /tmp/output. The performance
+# summary is in $OUTPUT_DIR/report.txt. There is one file in $OUTPUT_DIR per
+# test and the tests are listed below.
+#
+# The environment variables are also optional. The variables are:
+#
+# NKEYS - number of key/value pairs to load
+# BG_MBWRITEPERSEC - write rate limit in MB/second for tests in which
+# there is one thread doing writes and stats are
+# reported for read threads. "BG" stands for background.
+# If this is too large then the non-writer threads can get
+# starved. This is used for the "readwhile" tests.
+# FG_MBWRITEPERSEC - write rate limit in MB/second for tests like overwrite
+# where stats are reported for the write threads.
+# NSECONDS - number of seconds for which to run each test in steps 2,
+# 3 and 4. There are currently 15 tests in those steps and
+# they are repeated for each entry in list-of-threads so
+# this variable lets you control the total duration to
+# finish the benchmark.
+# RANGE_LIMIT - the number of rows to read per range query for tests that
+# do range queries.
+# VAL_SIZE - the length of the value in the key/value pairs loaded.
+# You can estimate the size of the test database from this,
+# NKEYS and the compression rate (--compression_ratio) set
+# in tools/benchmark.sh
+# BLOCK_LENGTH - value for db_bench --block_size
+# CACHE_BYTES - the size of the RocksDB block cache in bytes
+# DATA_DIR - directory in which to create database files
+# LOG_DIR - directory in which to create WAL files, may be the same
+# as DATA_DIR
+# DO_SETUP - when set to 0 then a backup of the database is copied from
+# $DATA_DIR.bak to $DATA_DIR and the load tests from step 1
+# The WAL directory is also copied from a backup if
+# DATA_DIR != LOG_DIR. This allows tests from steps 2, 3, 4
+# to be repeated faster.
+# SAVE_SETUP - saves a copy of the database at the end of step 1 to
+# $DATA_DIR.bak. When LOG_DIR != DATA_DIR then it is copied
+# to $LOG_DIR.bak.
+# SKIP_LOW_PRI_TESTS - skip some of the tests which aren't crucial for getting
+# actionable benchmarking data (look for keywords "bulkload",
+# "sync=1", and "while merging").
+#
+
+# Size constants
+K=1024
+M=$((1024 * K))
+G=$((1024 * M))
+
+num_keys=${NKEYS:-$((1 * G))}
+# write rate for readwhile... tests
+bg_mbwps=${BG_MBWRITEPERSEC:-4}
+# write rate for tests other than readwhile, 0 means no limit
+fg_mbwps=${FG_MBWRITEPERSEC:-0}
+duration=${NSECONDS:-$((60 * 60))}
+nps=${RANGE_LIMIT:-10}
+vs=${VAL_SIZE:-400}
+cs=${CACHE_BYTES:-$(( 1 * G ))}
+bs=${BLOCK_LENGTH:-8192}
+
+# If no command line arguments then run for 24 threads.
+if [[ $# -eq 0 ]]; then
+ nthreads=( 24 )
+else
+ nthreads=( "$@" )
+fi
+
+for num_thr in "${nthreads[@]}" ; do
+ echo Will run for $num_thr threads
+done
+
+# Update these parameters before execution !!!
+db_dir=${DATA_DIR:-"/tmp/rocksdb/"}
+wal_dir=${LOG_DIR:-"/tmp/rocksdb/"}
+
+do_setup=${DO_SETUP:-1}
+save_setup=${SAVE_SETUP:-0}
+
+# By default we'll run all the tests. Set this to skip a set of tests which
+# aren't critical for getting key metrics.
+skip_low_pri_tests=${SKIP_LOW_PRI_TESTS:-0}
+
+if [[ $skip_low_pri_tests == 1 ]]; then
+ echo "Skipping some non-critical tests because SKIP_LOW_PRI_TESTS is set."
+fi
+
+output_dir="${TMPDIR:-/tmp}/output"
+
+ARGS="\
+OUTPUT_DIR=$output_dir \
+NUM_KEYS=$num_keys \
+DB_DIR=$db_dir \
+WAL_DIR=$wal_dir \
+VALUE_SIZE=$vs \
+BLOCK_SIZE=$bs \
+CACHE_SIZE=$cs"
+
+mkdir -p $output_dir
+echo -e "ops/sec\tmb/sec\tSize-GB\tL0_GB\tSum_GB\tW-Amp\tW-MB/s\tusec/op\tp50\tp75\tp99\tp99.9\tp99.99\tUptime\tStall-time\tStall%\tTest" \
+ > $output_dir/report.txt
+
+# Notes on test sequence:
+# step 1) Setup database via sequential fill followed by overwrite to fragment it.
+# Done without setting DURATION to make sure that overwrite does $num_keys writes
+# step 2) read-only tests for all levels of concurrency requested
+# step 3) non read-only tests for all levels of concurrency requested
+# step 4) merge tests for all levels of concurrency requested. These must come last.
+
+###### Setup the database
+
+if [[ $do_setup != 0 ]]; then
+ echo Doing setup
+
+ if [[ $skip_low_pri_tests != 1 ]]; then
+ # Test 1: bulk load
+ env $ARGS ./tools/benchmark.sh bulkload
+ fi
+
+ # Test 2a: sequential fill with large values to get peak ingest
+ # adjust NUM_KEYS given the use of larger values
+ env $ARGS BLOCK_SIZE=$((1 * M)) VALUE_SIZE=$((32 * K)) NUM_KEYS=$(( num_keys / 64 )) \
+ ./tools/benchmark.sh fillseq_disable_wal
+
+ # Test 2b: sequential fill with the configured value size
+ env $ARGS ./tools/benchmark.sh fillseq_disable_wal
+
+ # Test 2c: same as 2a, but with WAL being enabled.
+ env $ARGS BLOCK_SIZE=$((1 * M)) VALUE_SIZE=$((32 * K)) NUM_KEYS=$(( num_keys / 64 )) \
+ ./tools/benchmark.sh fillseq_enable_wal
+
+ # Test 2d: same as 2b, but with WAL being enabled.
+ env $ARGS ./tools/benchmark.sh fillseq_enable_wal
+
+ # Test 3: single-threaded overwrite
+ env $ARGS NUM_THREADS=1 DB_BENCH_NO_SYNC=1 ./tools/benchmark.sh overwrite
+
+else
+ echo Restoring from backup
+
+ rm -rf $db_dir
+
+ if [ ! -d ${db_dir}.bak ]; then
+ echo Database backup does not exist at ${db_dir}.bak
+ exit -1
+ fi
+
+ echo Restore database from ${db_dir}.bak
+ cp -p -r ${db_dir}.bak $db_dir
+
+ if [[ $db_dir != $wal_dir ]]; then
+ rm -rf $wal_dir
+
+ if [ ! -d ${wal_dir}.bak ]; then
+ echo WAL backup does not exist at ${wal_dir}.bak
+ exit -1
+ fi
+
+ echo Restore WAL from ${wal_dir}.bak
+ cp -p -r ${wal_dir}.bak $wal_dir
+ fi
+fi
+
+if [[ $save_setup != 0 ]]; then
+ echo Save database to ${db_dir}.bak
+ cp -p -r $db_dir ${db_dir}.bak
+
+ if [[ $db_dir != $wal_dir ]]; then
+ echo Save WAL to ${wal_dir}.bak
+ cp -p -r $wal_dir ${wal_dir}.bak
+ fi
+fi
+
+###### Read-only tests
+
+for num_thr in "${nthreads[@]}" ; do
+ # Test 4: random read
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr ./tools/benchmark.sh readrandom
+
+ # Test 5: random range scans
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr NUM_NEXTS_PER_SEEK=$nps \
+ ./tools/benchmark.sh fwdrange
+
+ # Test 6: random reverse range scans
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr NUM_NEXTS_PER_SEEK=$nps \
+ ./tools/benchmark.sh revrange
+done
+
+###### Non read-only tests
+
+for num_thr in "${nthreads[@]}" ; do
+ # Test 7: overwrite with sync=0
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$fg_mbwps \
+ DB_BENCH_NO_SYNC=1 ./tools/benchmark.sh overwrite
+
+ if [[ $skip_low_pri_tests != 1 ]]; then
+ # Test 8: overwrite with sync=1
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$fg_mbwps \
+ ./tools/benchmark.sh overwrite
+ fi
+
+ # Test 9: random update with sync=0
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr DB_BENCH_NO_SYNC=1 \
+ ./tools/benchmark.sh updaterandom
+
+ if [[ $skip_low_pri_tests != 1 ]]; then
+ # Test 10: random update with sync=1
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr ./tools/benchmark.sh updaterandom
+ fi
+
+ # Test 11: random read while writing
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 ./tools/benchmark.sh readwhilewriting
+
+ # Test 12: range scan while writing
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 NUM_NEXTS_PER_SEEK=$nps ./tools/benchmark.sh fwdrangewhilewriting
+
+ # Test 13: reverse range scan while writing
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 NUM_NEXTS_PER_SEEK=$nps ./tools/benchmark.sh revrangewhilewriting
+done
+
+###### Merge tests
+
+for num_thr in "${nthreads[@]}" ; do
+ # Test 14: random merge with sync=0
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$fg_mbwps \
+ DB_BENCH_NO_SYNC=1 ./tools/benchmark.sh mergerandom
+
+ if [[ $skip_low_pri_tests != 1 ]]; then
+ # Test 15: random merge with sync=1
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$fg_mbwps \
+ ./tools/benchmark.sh mergerandom
+
+ # Test 16: random read while merging
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 ./tools/benchmark.sh readwhilemerging
+
+ # Test 17: range scan while merging
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 NUM_NEXTS_PER_SEEK=$nps ./tools/benchmark.sh fwdrangewhilemerging
+
+ # Test 18: reverse range scan while merging
+ env $ARGS DURATION=$duration NUM_THREADS=$num_thr MB_WRITE_PER_SEC=$bg_mbwps \
+ DB_BENCH_NO_SYNC=1 NUM_NEXTS_PER_SEEK=$nps ./tools/benchmark.sh revrangewhilemerging
+ fi
+done
+
+###### Universal compaction tests.
+
+# Use a single thread to reduce the variability in the benchmark.
+env $ARGS COMPACTION_TEST=1 NUM_THREADS=1 ./tools/benchmark.sh universal_compaction
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo bulkload > $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep bulkload $output_dir/report.txt >> $output_dir/report2.txt
+fi
+
+echo fillseq_wal_disabled >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep fillseq.wal_disabled $output_dir/report.txt >> $output_dir/report2.txt
+
+echo fillseq_wal_enabled >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep fillseq.wal_enabled $output_dir/report.txt >> $output_dir/report2.txt
+
+echo overwrite sync=0 >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep overwrite $output_dir/report.txt | grep \.s0 >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo overwrite sync=1 >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep overwrite $output_dir/report.txt | grep \.s1 >> $output_dir/report2.txt
+fi
+
+echo updaterandom sync=0 >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep updaterandom $output_dir/report.txt | grep \.s0 >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo updaterandom sync=1 >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep updaterandom $output_dir/report.txt | grep \.s1 >> $output_dir/report2.txt
+fi
+
+echo mergerandom sync=0 >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep mergerandom $output_dir/report.txt | grep \.s0 >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo mergerandom sync=1 >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep mergerandom $output_dir/report.txt | grep \.s1 >> $output_dir/report2.txt
+fi
+
+echo readrandom >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep readrandom $output_dir/report.txt >> $output_dir/report2.txt
+
+echo fwdrange >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep fwdrange\.t $output_dir/report.txt >> $output_dir/report2.txt
+
+echo revrange >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep revrange\.t $output_dir/report.txt >> $output_dir/report2.txt
+
+echo readwhile >> $output_dir/report2.txt >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep readwhilewriting $output_dir/report.txt >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo readwhile >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep readwhilemerging $output_dir/report.txt >> $output_dir/report2.txt
+fi
+
+echo fwdreadwhilewriting >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep fwdrangewhilewriting $output_dir/report.txt >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo fwdreadwhilemerging >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep fwdrangewhilemerg $output_dir/report.txt >> $output_dir/report2.txt
+fi
+
+echo revreadwhilewriting >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep revrangewhilewriting $output_dir/report.txt >> $output_dir/report2.txt
+
+if [[ $skip_low_pri_tests != 1 ]]; then
+ echo revreadwhilemerging >> $output_dir/report2.txt
+ head -1 $output_dir/report.txt >> $output_dir/report2.txt
+ grep revrangewhilemerg $output_dir/report.txt >> $output_dir/report2.txt
+fi
+
+cat $output_dir/report2.txt
diff --git a/storage/rocksdb/rocksdb/tools/run_leveldb.sh b/storage/rocksdb/rocksdb/tools/run_leveldb.sh
new file mode 100755
index 00000000..2fce8b12
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/run_leveldb.sh
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+# REQUIRE: benchmark_leveldb.sh exists in the current directory
+# After execution of this script, log files are generated in $output_dir.
+# report.txt provides a high level statistics
+#
+# This should be used with the LevelDB fork listed here to use additional test options.
+# For more details on the changes see the blog post listed below.
+# https://github.com/mdcallag/leveldb-1
+# http://smalldatum.blogspot.com/2015/04/comparing-leveldb-and-rocksdb-take-2.html
+#
+# This should be run from the parent of the tools directory. The command line is:
+# [$env_vars] tools/run_flash_bench.sh [list-of-threads]
+#
+# This runs a sequence of tests in the following sequence:
+# step 1) load - bulkload, compact, fillseq, overwrite
+# step 2) read-only for each number of threads
+# step 3) read-write for each number of threads
+#
+# The list of threads is optional and when not set is equivalent to "24".
+# Were list-of-threads specified as "1 2 4" then the tests in steps 2, 3 and
+# 4 above would be repeated for 1, 2 and 4 threads. The tests in step 1 are
+# only run for 1 thread.
+
+# Test output is written to $OUTPUT_DIR, currently /tmp/output. The performance
+# summary is in $OUTPUT_DIR/report.txt. There is one file in $OUTPUT_DIR per
+# test and the tests are listed below.
+#
+# The environment variables are also optional. The variables are:
+# NKEYS - number of key/value pairs to load
+# NWRITESPERSEC - the writes/second rate limit for the *whilewriting* tests.
+# If this is too large then the non-writer threads can get
+# starved.
+# VAL_SIZE - the length of the value in the key/value pairs loaded.
+# You can estimate the size of the test database from this,
+# NKEYS and the compression rate (--compression_ratio) set
+# in tools/benchmark_leveldb.sh
+# BLOCK_LENGTH - value for db_bench --block_size
+# CACHE_BYTES - the size of the RocksDB block cache in bytes
+# DATA_DIR - directory in which to create database files
+# DO_SETUP - when set to 0 then a backup of the database is copied from
+# $DATA_DIR.bak to $DATA_DIR and the load tests from step 1
+# This allows tests from steps 2, 3 to be repeated faster.
+# SAVE_SETUP - saves a copy of the database at the end of step 1 to
+# $DATA_DIR.bak.
+
+# Size constants
+K=1024
+M=$((1024 * K))
+G=$((1024 * M))
+
+num_keys=${NKEYS:-$((1 * G))}
+wps=${NWRITESPERSEC:-$((10 * K))}
+vs=${VAL_SIZE:-400}
+cs=${CACHE_BYTES:-$(( 1 * G ))}
+bs=${BLOCK_LENGTH:-4096}
+
+# If no command line arguments then run for 24 threads.
+if [[ $# -eq 0 ]]; then
+ nthreads=( 24 )
+else
+ nthreads=( "$@" )
+fi
+
+for num_thr in "${nthreads[@]}" ; do
+ echo Will run for $num_thr threads
+done
+
+# Update these parameters before execution !!!
+db_dir=${DATA_DIR:-"/tmp/rocksdb/"}
+
+do_setup=${DO_SETUP:-1}
+save_setup=${SAVE_SETUP:-0}
+
+output_dir="${TMPDIR:-/tmp}/output"
+
+ARGS="\
+OUTPUT_DIR=$output_dir \
+NUM_KEYS=$num_keys \
+DB_DIR=$db_dir \
+VALUE_SIZE=$vs \
+BLOCK_SIZE=$bs \
+CACHE_SIZE=$cs"
+
+mkdir -p $output_dir
+echo -e "ops/sec\tmb/sec\tusec/op\tavg\tp50\tTest" \
+ > $output_dir/report.txt
+
+# Notes on test sequence:
+# step 1) Setup database via sequential fill followed by overwrite to fragment it.
+# Done without setting DURATION to make sure that overwrite does $num_keys writes
+# step 2) read-only tests for all levels of concurrency requested
+# step 3) non read-only tests for all levels of concurrency requested
+
+###### Setup the database
+
+if [[ $do_setup != 0 ]]; then
+ echo Doing setup
+
+ # Test 2a: sequential fill with large values to get peak ingest
+ # adjust NUM_KEYS given the use of larger values
+ env $ARGS BLOCK_SIZE=$((1 * M)) VALUE_SIZE=$((32 * K)) NUM_KEYS=$(( num_keys / 64 )) \
+ ./tools/benchmark_leveldb.sh fillseq
+
+ # Test 2b: sequential fill with the configured value size
+ env $ARGS ./tools/benchmark_leveldb.sh fillseq
+
+ # Test 3: single-threaded overwrite
+ env $ARGS NUM_THREADS=1 DB_BENCH_NO_SYNC=1 ./tools/benchmark_leveldb.sh overwrite
+
+else
+ echo Restoring from backup
+
+ rm -rf $db_dir
+
+ if [ ! -d ${db_dir}.bak ]; then
+ echo Database backup does not exist at ${db_dir}.bak
+ exit -1
+ fi
+
+ echo Restore database from ${db_dir}.bak
+ cp -p -r ${db_dir}.bak $db_dir
+fi
+
+if [[ $save_setup != 0 ]]; then
+ echo Save database to ${db_dir}.bak
+ cp -p -r $db_dir ${db_dir}.bak
+fi
+
+###### Read-only tests
+
+for num_thr in "${nthreads[@]}" ; do
+ # Test 4: random read
+ env $ARGS NUM_THREADS=$num_thr ./tools/benchmark_leveldb.sh readrandom
+
+done
+
+###### Non read-only tests
+
+for num_thr in "${nthreads[@]}" ; do
+ # Test 7: overwrite with sync=0
+ env $ARGS NUM_THREADS=$num_thr DB_BENCH_NO_SYNC=1 \
+ ./tools/benchmark_leveldb.sh overwrite
+
+ # Test 8: overwrite with sync=1
+ # Not run for now because LevelDB db_bench doesn't have an option to limit the
+ # test run to X seconds and doing sync-per-commit for --num can take too long.
+ # env $ARGS NUM_THREADS=$num_thr ./tools/benchmark_leveldb.sh overwrite
+
+ # Test 11: random read while writing
+ env $ARGS NUM_THREADS=$num_thr WRITES_PER_SECOND=$wps \
+ ./tools/benchmark_leveldb.sh readwhilewriting
+
+done
+
+echo bulkload > $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep bulkload $output_dir/report.txt >> $output_dir/report2.txt
+echo fillseq >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep fillseq $output_dir/report.txt >> $output_dir/report2.txt
+echo overwrite sync=0 >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep overwrite $output_dir/report.txt | grep \.s0 >> $output_dir/report2.txt
+echo overwrite sync=1 >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep overwrite $output_dir/report.txt | grep \.s1 >> $output_dir/report2.txt
+echo readrandom >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep readrandom $output_dir/report.txt >> $output_dir/report2.txt
+echo readwhile >> $output_dir/report2.txt >> $output_dir/report2.txt
+head -1 $output_dir/report.txt >> $output_dir/report2.txt
+grep readwhilewriting $output_dir/report.txt >> $output_dir/report2.txt
+
+cat $output_dir/report2.txt
diff --git a/storage/rocksdb/rocksdb/tools/sample-dump.dmp b/storage/rocksdb/rocksdb/tools/sample-dump.dmp
new file mode 100644
index 00000000..4ec3a773
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/sample-dump.dmp
Binary files differ
diff --git a/storage/rocksdb/rocksdb/tools/sst_dump.cc b/storage/rocksdb/rocksdb/tools/sst_dump.cc
new file mode 100644
index 00000000..1a0258c6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/sst_dump.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/sst_dump_tool.h"
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::SSTDumpTool tool;
+ tool.Run(argc, argv);
+ return 0;
+}
+#else
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return 1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/sst_dump_test.cc b/storage/rocksdb/rocksdb/tools/sst_dump_test.cc
new file mode 100644
index 00000000..b8c475c3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/sst_dump_test.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <stdint.h>
+#include "rocksdb/sst_dump_tool.h"
+
+#include "file/random_access_file_reader.h"
+#include "port/stack_trace.h"
+#include "rocksdb/filter_policy.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 optLength = 100;
+
+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 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 createSST(const Options& opts, const std::string& file_name) {
+ Env* env = opts.env;
+ EnvOptions env_options(opts);
+ ReadOptions read_options;
+ const ImmutableCFOptions imoptions(opts);
+ const MutableCFOptions moptions(opts);
+ ROCKSDB_NAMESPACE::InternalKeyComparator ikc(opts.comparator);
+ std::unique_ptr<TableBuilder> tb;
+
+ std::unique_ptr<WritableFile> file;
+ ASSERT_OK(env->NewWritableFile(file_name, &file, env_options));
+
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory> >
+ int_tbl_prop_collector_factories;
+ std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(file)), file_name, EnvOptions()));
+ 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, 0 /* sample_for_compression */,
+ CompressionOptions(), false /* skip_filters */, column_family_name,
+ unknown_level),
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ file_writer.get()));
+
+ // Populate slightly more than 1K keys
+ uint32_t num_keys = 1024;
+ for (uint32_t i = 0; i < num_keys; i++) {
+ tb->Add(MakeKey(i), MakeValue(i));
+ }
+ tb->Finish();
+ file_writer->Close();
+}
+
+void cleanup(const Options& opts, const std::string& file_name) {
+ Env* env = opts.env;
+ 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);
+}
+} // namespace
+
+// Test for sst dump tool "raw" mode
+class SSTDumpToolTest : public testing::Test {
+ std::string test_dir_;
+ Env* env_;
+ std::shared_ptr<Env> env_guard_;
+
+ public:
+ SSTDumpToolTest() : env_(Env::Default()) {
+ const char* test_env_uri = getenv("TEST_ENV_URI");
+ if (test_env_uri) {
+ Env::LoadEnv(test_env_uri, &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 <std::size_t N>
+ void PopulateCommandArgs(const std::string& file_path, const char* command,
+ char* (&usage)[N]) const {
+ for (int i = 0; i < static_cast<int>(N); ++i) {
+ usage[i] = new char[optLength];
+ }
+ snprintf(usage[0], optLength, "./sst_dump");
+ snprintf(usage[1], optLength, "%s", command);
+ snprintf(usage[2], optLength, "--file=%s", file_path.c_str());
+ }
+};
+
+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, 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<Env> 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];
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+extern "C" {
+void RegisterCustomObjects(int argc, char** argv);
+}
+#else
+void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
+#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ RegisterCustomObjects(argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as SSTDumpTool is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE return RUN_ALL_TESTS();
diff --git a/storage/rocksdb/rocksdb/tools/sst_dump_tool.cc b/storage/rocksdb/rocksdb/tools/sst_dump_tool.cc
new file mode 100644
index 00000000..be4dc961
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/sst_dump_tool.cc
@@ -0,0 +1,778 @@
+
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "tools/sst_dump_tool_imp.h"
+
+#include <cinttypes>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "db/blob_index.h"
+#include "db/memtable.h"
+#include "db/write_batch_internal.h"
+#include "env/composite_env_wrapper.h"
+#include "options/cf_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/utilities/ldb_cmd.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_builder.h"
+#include "table/format.h"
+#include "table/meta_blocks.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/table_reader.h"
+#include "util/compression.h"
+#include "util/random.h"
+
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+SstFileDumper::SstFileDumper(const Options& options,
+ const std::string& file_path, bool verify_checksum,
+ bool output_hex, bool decode_blob_index)
+ : file_name_(file_path),
+ read_num_(0),
+ verify_checksum_(verify_checksum),
+ output_hex_(output_hex),
+ decode_blob_index_(decode_blob_index),
+ options_(options),
+ ioptions_(options_),
+ moptions_(ColumnFamilyOptions(options_)),
+ internal_comparator_(BytewiseComparator()) {
+ fprintf(stdout, "Process %s\n", file_path.c_str());
+ init_result_ = GetTableReader(file_name_);
+}
+
+extern const uint64_t kBlockBasedTableMagicNumber;
+extern const uint64_t kLegacyBlockBasedTableMagicNumber;
+extern const uint64_t kPlainTableMagicNumber;
+extern const uint64_t kLegacyPlainTableMagicNumber;
+
+const char* testFileName = "test_file_name";
+
+static const std::vector<std::pair<CompressionType, const char*>>
+ kCompressions = {
+ {CompressionType::kNoCompression, "kNoCompression"},
+ {CompressionType::kSnappyCompression, "kSnappyCompression"},
+ {CompressionType::kZlibCompression, "kZlibCompression"},
+ {CompressionType::kBZip2Compression, "kBZip2Compression"},
+ {CompressionType::kLZ4Compression, "kLZ4Compression"},
+ {CompressionType::kLZ4HCCompression, "kLZ4HCCompression"},
+ {CompressionType::kXpressCompression, "kXpressCompression"},
+ {CompressionType::kZSTD, "kZSTD"}};
+
+Status SstFileDumper::GetTableReader(const std::string& file_path) {
+ // Warning about 'magic_number' being uninitialized shows up only in UBsan
+ // builds. Though access is guarded by 's.ok()' checks, fix the issue to
+ // avoid any warnings.
+ uint64_t magic_number = Footer::kInvalidTableMagicNumber;
+
+ // read table magic number
+ Footer footer;
+
+ std::unique_ptr<RandomAccessFile> file;
+ uint64_t file_size = 0;
+ Status s = options_.env->NewRandomAccessFile(file_path, &file, soptions_);
+ if (s.ok()) {
+ s = options_.env->GetFileSize(file_path, &file_size);
+ }
+
+ file_.reset(new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
+ file_path));
+
+ if (s.ok()) {
+ s = ReadFooterFromFile(file_.get(), nullptr /* prefetch_buffer */,
+ file_size, &footer);
+ }
+ if (s.ok()) {
+ magic_number = footer.table_magic_number();
+ }
+
+ if (s.ok()) {
+ if (magic_number == kPlainTableMagicNumber ||
+ magic_number == kLegacyPlainTableMagicNumber) {
+ soptions_.use_mmap_reads = true;
+ options_.env->NewRandomAccessFile(file_path, &file, soptions_);
+ file_.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(file), file_path));
+ }
+ options_.comparator = &internal_comparator_;
+ // For old sst format, ReadTableProperties might fail but file can be read
+ if (ReadTableProperties(magic_number, file_.get(), file_size).ok()) {
+ SetTableOptionsByMagicNumber(magic_number);
+ } else {
+ SetOldTableOptions();
+ }
+ }
+
+ if (s.ok()) {
+ s = NewTableReader(ioptions_, soptions_, internal_comparator_, file_size,
+ &table_reader_);
+ }
+ return s;
+}
+
+Status SstFileDumper::NewTableReader(
+ const ImmutableCFOptions& /*ioptions*/, const EnvOptions& /*soptions*/,
+ const InternalKeyComparator& /*internal_comparator*/, uint64_t file_size,
+ std::unique_ptr<TableReader>* /*table_reader*/) {
+ // We need to turn off pre-fetching of index and filter nodes for
+ // BlockBasedTable
+ if (BlockBasedTableFactory::kName == options_.table_factory->Name()) {
+ return options_.table_factory->NewTableReader(
+ TableReaderOptions(ioptions_, moptions_.prefix_extractor.get(),
+ soptions_, internal_comparator_),
+ std::move(file_), file_size, &table_reader_, /*enable_prefetch=*/false);
+ }
+
+ // For all other factory implementation
+ return options_.table_factory->NewTableReader(
+ TableReaderOptions(ioptions_, moptions_.prefix_extractor.get(), soptions_,
+ internal_comparator_),
+ std::move(file_), file_size, &table_reader_);
+}
+
+Status SstFileDumper::VerifyChecksum() {
+ // We could pass specific readahead setting into read options if needed.
+ return table_reader_->VerifyChecksum(ReadOptions(),
+ TableReaderCaller::kSSTDumpTool);
+}
+
+Status SstFileDumper::DumpTable(const std::string& out_filename) {
+ std::unique_ptr<WritableFile> out_file;
+ Env* env = options_.env;
+ env->NewWritableFile(out_filename, &out_file, soptions_);
+ Status s = table_reader_->DumpTable(out_file.get());
+ out_file->Close();
+ return s;
+}
+
+uint64_t SstFileDumper::CalculateCompressedTableSize(
+ const TableBuilderOptions& tb_options, size_t block_size,
+ uint64_t* num_data_blocks) {
+ std::unique_ptr<WritableFile> out_file;
+ std::unique_ptr<Env> env(NewMemEnv(options_.env));
+ env->NewWritableFile(testFileName, &out_file, soptions_);
+ std::unique_ptr<WritableFileWriter> dest_writer;
+ dest_writer.reset(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(out_file)),
+ testFileName, soptions_));
+ BlockBasedTableOptions table_options;
+ table_options.block_size = block_size;
+ BlockBasedTableFactory block_based_tf(table_options);
+ std::unique_ptr<TableBuilder> table_builder;
+ table_builder.reset(block_based_tf.NewTableBuilder(
+ tb_options,
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
+ dest_writer.get()));
+ std::unique_ptr<InternalIterator> iter(table_reader_->NewIterator(
+ ReadOptions(), moptions_.prefix_extractor.get(), /*arena=*/nullptr,
+ /*skip_filters=*/false, TableReaderCaller::kSSTDumpTool));
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ if (!iter->status().ok()) {
+ fputs(iter->status().ToString().c_str(), stderr);
+ exit(1);
+ }
+ table_builder->Add(iter->key(), iter->value());
+ }
+ Status s = table_builder->Finish();
+ if (!s.ok()) {
+ fputs(s.ToString().c_str(), stderr);
+ exit(1);
+ }
+ uint64_t size = table_builder->FileSize();
+ assert(num_data_blocks != nullptr);
+ *num_data_blocks = table_builder->GetTableProperties().num_data_blocks;
+ env->DeleteFile(testFileName);
+ return size;
+}
+
+int SstFileDumper::ShowAllCompressionSizes(
+ size_t block_size,
+ const std::vector<std::pair<CompressionType, const char*>>&
+ compression_types) {
+ ReadOptions read_options;
+ Options opts;
+ opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ opts.statistics->set_stats_level(StatsLevel::kAll);
+ const ImmutableCFOptions imoptions(opts);
+ const ColumnFamilyOptions cfo(opts);
+ const MutableCFOptions moptions(cfo);
+ ROCKSDB_NAMESPACE::InternalKeyComparator ikc(opts.comparator);
+ std::vector<std::unique_ptr<IntTblPropCollectorFactory> >
+ block_based_table_factories;
+
+ fprintf(stdout, "Block Size: %" ROCKSDB_PRIszt "\n", block_size);
+
+ for (auto& i : compression_types) {
+ if (CompressionTypeSupported(i.first)) {
+ CompressionOptions compress_opt;
+ std::string column_family_name;
+ int unknown_level = -1;
+ TableBuilderOptions tb_opts(
+ imoptions, moptions, ikc, &block_based_table_factories, i.first,
+ 0 /* sample_for_compression */, compress_opt,
+ false /* skip_filters */, column_family_name, unknown_level);
+ uint64_t num_data_blocks = 0;
+ uint64_t file_size =
+ CalculateCompressedTableSize(tb_opts, block_size, &num_data_blocks);
+ fprintf(stdout, "Compression: %-24s", i.second);
+ fprintf(stdout, " Size: %10" PRIu64, file_size);
+ fprintf(stdout, " Blocks: %6" PRIu64, num_data_blocks);
+ const uint64_t compressed_blocks =
+ opts.statistics->getAndResetTickerCount(NUMBER_BLOCK_COMPRESSED);
+ const uint64_t not_compressed_blocks =
+ opts.statistics->getAndResetTickerCount(NUMBER_BLOCK_NOT_COMPRESSED);
+ // When the option enable_index_compression is true,
+ // NUMBER_BLOCK_COMPRESSED is incremented for index block(s).
+ if ((compressed_blocks + not_compressed_blocks) > num_data_blocks) {
+ num_data_blocks = compressed_blocks + not_compressed_blocks;
+ }
+ const uint64_t ratio_not_compressed_blocks =
+ (num_data_blocks - compressed_blocks) - not_compressed_blocks;
+ const double compressed_pcnt =
+ (0 == num_data_blocks) ? 0.0
+ : ((static_cast<double>(compressed_blocks) /
+ static_cast<double>(num_data_blocks)) *
+ 100.0);
+ const double ratio_not_compressed_pcnt =
+ (0 == num_data_blocks)
+ ? 0.0
+ : ((static_cast<double>(ratio_not_compressed_blocks) /
+ static_cast<double>(num_data_blocks)) *
+ 100.0);
+ const double not_compressed_pcnt =
+ (0 == num_data_blocks)
+ ? 0.0
+ : ((static_cast<double>(not_compressed_blocks) /
+ static_cast<double>(num_data_blocks)) *
+ 100.0);
+ fprintf(stdout, " Compressed: %6" PRIu64 " (%5.1f%%)", compressed_blocks,
+ compressed_pcnt);
+ fprintf(stdout, " Not compressed (ratio): %6" PRIu64 " (%5.1f%%)",
+ ratio_not_compressed_blocks, ratio_not_compressed_pcnt);
+ fprintf(stdout, " Not compressed (abort): %6" PRIu64 " (%5.1f%%)\n",
+ not_compressed_blocks, not_compressed_pcnt);
+ } else {
+ fprintf(stdout, "Unsupported compression type: %s.\n", i.second);
+ }
+ }
+ return 0;
+}
+Status SstFileDumper::ReadTableProperties(uint64_t table_magic_number,
+ RandomAccessFileReader* file,
+ uint64_t file_size) {
+ TableProperties* table_properties = nullptr;
+ Status s = ROCKSDB_NAMESPACE::ReadTableProperties(
+ file, file_size, table_magic_number, ioptions_, &table_properties);
+ if (s.ok()) {
+ table_properties_.reset(table_properties);
+ } else {
+ fprintf(stdout, "Not able to read table properties\n");
+ }
+ return s;
+}
+
+Status SstFileDumper::SetTableOptionsByMagicNumber(
+ uint64_t table_magic_number) {
+ assert(table_properties_);
+ if (table_magic_number == kBlockBasedTableMagicNumber ||
+ table_magic_number == kLegacyBlockBasedTableMagicNumber) {
+ options_.table_factory = std::make_shared<BlockBasedTableFactory>();
+ fprintf(stdout, "Sst file format: block-based\n");
+ auto& props = table_properties_->user_collected_properties;
+ auto pos = props.find(BlockBasedTablePropertyNames::kIndexType);
+ if (pos != props.end()) {
+ auto index_type_on_file = static_cast<BlockBasedTableOptions::IndexType>(
+ DecodeFixed32(pos->second.c_str()));
+ if (index_type_on_file ==
+ BlockBasedTableOptions::IndexType::kHashSearch) {
+ options_.prefix_extractor.reset(NewNoopTransform());
+ }
+ }
+ } else if (table_magic_number == kPlainTableMagicNumber ||
+ table_magic_number == kLegacyPlainTableMagicNumber) {
+ options_.allow_mmap_reads = true;
+
+ 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;
+ plain_table_options.index_sparseness = 1;
+ plain_table_options.huge_page_tlb_size = 0;
+ plain_table_options.encoding_type = kPlain;
+ plain_table_options.full_scan_mode = true;
+
+ options_.table_factory.reset(NewPlainTableFactory(plain_table_options));
+ fprintf(stdout, "Sst file format: plain table\n");
+ } else {
+ char error_msg_buffer[80];
+ snprintf(error_msg_buffer, sizeof(error_msg_buffer) - 1,
+ "Unsupported table magic number --- %lx",
+ (long)table_magic_number);
+ return Status::InvalidArgument(error_msg_buffer);
+ }
+
+ return Status::OK();
+}
+
+Status SstFileDumper::SetOldTableOptions() {
+ assert(table_properties_ == nullptr);
+ options_.table_factory = std::make_shared<BlockBasedTableFactory>();
+ fprintf(stdout, "Sst file format: block-based(old version)\n");
+
+ return Status::OK();
+}
+
+Status SstFileDumper::ReadSequential(bool print_kv, uint64_t read_num,
+ bool has_from, const std::string& from_key,
+ bool has_to, const std::string& to_key,
+ bool use_from_as_prefix) {
+ if (!table_reader_) {
+ return init_result_;
+ }
+
+ InternalIterator* iter = table_reader_->NewIterator(
+ ReadOptions(verify_checksum_, false), moptions_.prefix_extractor.get(),
+ /*arena=*/nullptr, /*skip_filters=*/false,
+ TableReaderCaller::kSSTDumpTool);
+ uint64_t i = 0;
+ if (has_from) {
+ InternalKey ikey;
+ ikey.SetMinPossibleForUserKey(from_key);
+ iter->Seek(ikey.Encode());
+ } else {
+ iter->SeekToFirst();
+ }
+ for (; iter->Valid(); iter->Next()) {
+ Slice key = iter->key();
+ Slice value = iter->value();
+ ++i;
+ if (read_num > 0 && i > read_num)
+ break;
+
+ ParsedInternalKey ikey;
+ if (!ParseInternalKey(key, &ikey)) {
+ std::cerr << "Internal Key ["
+ << key.ToString(true /* in hex*/)
+ << "] parse error!\n";
+ continue;
+ }
+
+ // the key returned is not prefixed with out 'from' key
+ if (use_from_as_prefix && !ikey.user_key.starts_with(from_key)) {
+ break;
+ }
+
+ // If end marker was specified, we stop before it
+ if (has_to && BytewiseComparator()->Compare(ikey.user_key, to_key) >= 0) {
+ break;
+ }
+
+ if (print_kv) {
+ if (!decode_blob_index_ || ikey.type != kTypeBlobIndex) {
+ fprintf(stdout, "%s => %s\n", ikey.DebugString(output_hex_).c_str(),
+ value.ToString(output_hex_).c_str());
+ } else {
+ BlobIndex blob_index;
+
+ const Status s = blob_index.DecodeFrom(value);
+ if (!s.ok()) {
+ fprintf(stderr, "%s => error decoding blob index\n",
+ ikey.DebugString(output_hex_).c_str());
+ continue;
+ }
+
+ fprintf(stdout, "%s => %s\n", ikey.DebugString(output_hex_).c_str(),
+ blob_index.DebugString(output_hex_).c_str());
+ }
+ }
+ }
+
+ read_num_ += i;
+
+ Status ret = iter->status();
+ delete iter;
+ return ret;
+}
+
+Status SstFileDumper::ReadTableProperties(
+ std::shared_ptr<const TableProperties>* table_properties) {
+ if (!table_reader_) {
+ return init_result_;
+ }
+
+ *table_properties = table_reader_->GetTableProperties();
+ return init_result_;
+}
+
+namespace {
+
+void print_help() {
+ fprintf(
+ stderr,
+ R"(sst_dump --file=<data_dir_OR_sst_file> [--command=check|scan|raw|recompress]
+ --file=<data_dir_OR_sst_file>
+ Path to SST file or directory containing SST files
+
+ --env_uri=<uri of underlying Env>
+ URI of underlying Env
+
+ --command=check|scan|raw|verify
+ check: Iterate over entries in files but don't print anything except if an error is encountered (default command)
+ scan: Iterate over entries in files and print them to screen
+ raw: Dump all the table contents to <file_name>_dump.txt
+ verify: Iterate all the blocks in files verifying checksum to detect possible corruption but don't print anything except if a corruption is encountered
+ recompress: reports the SST file size if recompressed with different
+ compression types
+
+ --output_hex
+ Can be combined with scan command to print the keys and values in Hex
+
+ --decode_blob_index
+ Decode blob indexes and print them in a human-readable format during scans.
+
+ --from=<user_key>
+ Key to start reading from when executing check|scan
+
+ --to=<user_key>
+ Key to stop reading at when executing check|scan
+
+ --prefix=<user_key>
+ Returns all keys with this prefix when executing check|scan
+ Cannot be used in conjunction with --from
+
+ --read_num=<num>
+ Maximum number of entries to read when executing check|scan
+
+ --verify_checksum
+ Verify file checksum when executing check|scan
+
+ --input_key_hex
+ Can be combined with --from and --to to indicate that these values are encoded in Hex
+
+ --show_properties
+ Print table properties after iterating over the file when executing
+ check|scan|raw
+
+ --set_block_size=<block_size>
+ Can be combined with --command=recompress to set the block size that will
+ be used when trying different compression algorithms
+
+ --compression_types=<comma-separated list of CompressionType members, e.g.,
+ kSnappyCompression>
+ Can be combined with --command=recompress to run recompression for this
+ list of compression types
+
+ --parse_internal_key=<0xKEY>
+ Convenience option to parse an internal key on the command line. Dumps the
+ internal key in hex format {'key' @ SN: type}
+)");
+}
+
+} // namespace
+
+int SSTDumpTool::Run(int argc, char** argv, Options options) {
+ const char* env_uri = nullptr;
+ const char* dir_or_file = nullptr;
+ uint64_t read_num = std::numeric_limits<uint64_t>::max();
+ std::string command;
+
+ char junk;
+ uint64_t n;
+ bool verify_checksum = false;
+ bool output_hex = false;
+ bool decode_blob_index = false;
+ bool input_key_hex = false;
+ bool has_from = false;
+ bool has_to = false;
+ bool use_from_as_prefix = false;
+ bool show_properties = false;
+ bool show_summary = false;
+ bool set_block_size = false;
+ std::string from_key;
+ std::string to_key;
+ std::string block_size_str;
+ size_t block_size = 0;
+ std::vector<std::pair<CompressionType, const char*>> compression_types;
+ uint64_t total_num_files = 0;
+ uint64_t total_num_data_blocks = 0;
+ uint64_t total_data_block_size = 0;
+ uint64_t total_index_block_size = 0;
+ uint64_t total_filter_block_size = 0;
+ for (int i = 1; i < argc; i++) {
+ if (strncmp(argv[i], "--env_uri=", 10) == 0) {
+ env_uri = argv[i] + 10;
+ } else if (strncmp(argv[i], "--file=", 7) == 0) {
+ dir_or_file = argv[i] + 7;
+ } else if (strcmp(argv[i], "--output_hex") == 0) {
+ output_hex = true;
+ } else if (strcmp(argv[i], "--decode_blob_index") == 0) {
+ decode_blob_index = true;
+ } else if (strcmp(argv[i], "--input_key_hex") == 0) {
+ input_key_hex = true;
+ } else if (sscanf(argv[i], "--read_num=%lu%c", (unsigned long*)&n, &junk) ==
+ 1) {
+ read_num = n;
+ } else if (strcmp(argv[i], "--verify_checksum") == 0) {
+ verify_checksum = true;
+ } else if (strncmp(argv[i], "--command=", 10) == 0) {
+ command = argv[i] + 10;
+ } else if (strncmp(argv[i], "--from=", 7) == 0) {
+ from_key = argv[i] + 7;
+ has_from = true;
+ } else if (strncmp(argv[i], "--to=", 5) == 0) {
+ to_key = argv[i] + 5;
+ has_to = true;
+ } else if (strncmp(argv[i], "--prefix=", 9) == 0) {
+ from_key = argv[i] + 9;
+ use_from_as_prefix = true;
+ } else if (strcmp(argv[i], "--show_properties") == 0) {
+ show_properties = true;
+ } else if (strcmp(argv[i], "--show_summary") == 0) {
+ show_summary = true;
+ } else if (strncmp(argv[i], "--set_block_size=", 17) == 0) {
+ set_block_size = true;
+ block_size_str = argv[i] + 17;
+ std::istringstream iss(block_size_str);
+ iss >> block_size;
+ if (iss.fail()) {
+ fprintf(stderr, "block size must be numeric\n");
+ exit(1);
+ }
+ } else if (strncmp(argv[i], "--compression_types=", 20) == 0) {
+ std::string compression_types_csv = argv[i] + 20;
+ std::istringstream iss(compression_types_csv);
+ std::string compression_type;
+ while (std::getline(iss, compression_type, ',')) {
+ auto iter = std::find_if(
+ kCompressions.begin(), kCompressions.end(),
+ [&compression_type](std::pair<CompressionType, const char*> curr) {
+ return curr.second == compression_type;
+ });
+ if (iter == kCompressions.end()) {
+ fprintf(stderr, "%s is not a valid CompressionType\n",
+ compression_type.c_str());
+ exit(1);
+ }
+ compression_types.emplace_back(*iter);
+ }
+ } else if (strncmp(argv[i], "--parse_internal_key=", 21) == 0) {
+ std::string in_key(argv[i] + 21);
+ try {
+ in_key = ROCKSDB_NAMESPACE::LDBCommand::HexToString(in_key);
+ } catch (...) {
+ std::cerr << "ERROR: Invalid key input '"
+ << in_key
+ << "' Use 0x{hex representation of internal rocksdb key}" << std::endl;
+ return -1;
+ }
+ Slice sl_key = ROCKSDB_NAMESPACE::Slice(in_key);
+ ParsedInternalKey ikey;
+ int retc = 0;
+ if (!ParseInternalKey(sl_key, &ikey)) {
+ std::cerr << "Internal Key [" << sl_key.ToString(true /* in hex*/)
+ << "] parse error!\n";
+ retc = -1;
+ }
+ fprintf(stdout, "key=%s\n", ikey.DebugString(true).c_str());
+ return retc;
+ } else {
+ fprintf(stderr, "Unrecognized argument '%s'\n\n", argv[i]);
+ print_help();
+ exit(1);
+ }
+ }
+
+ if (use_from_as_prefix && has_from) {
+ fprintf(stderr, "Cannot specify --prefix and --from\n\n");
+ exit(1);
+ }
+
+ if (input_key_hex) {
+ if (has_from || use_from_as_prefix) {
+ from_key = ROCKSDB_NAMESPACE::LDBCommand::HexToString(from_key);
+ }
+ if (has_to) {
+ to_key = ROCKSDB_NAMESPACE::LDBCommand::HexToString(to_key);
+ }
+ }
+
+ if (dir_or_file == nullptr) {
+ fprintf(stderr, "file or directory must be specified.\n\n");
+ print_help();
+ exit(1);
+ }
+
+ std::shared_ptr<ROCKSDB_NAMESPACE::Env> env_guard;
+
+ // If caller of SSTDumpTool::Run(...) does not specify a different env other
+ // than Env::Default(), then try to load custom env based on dir_or_file.
+ // Otherwise, the caller is responsible for creating custom env.
+ if (!options.env || options.env == ROCKSDB_NAMESPACE::Env::Default()) {
+ Env* env = Env::Default();
+ Status s = Env::LoadEnv(env_uri ? env_uri : "", &env, &env_guard);
+ if (!s.ok() && !s.IsNotFound()) {
+ fprintf(stderr, "LoadEnv: %s\n", s.ToString().c_str());
+ exit(1);
+ }
+ options.env = env;
+ } else {
+ fprintf(stdout, "options.env is %p\n", options.env);
+ }
+
+ std::vector<std::string> filenames;
+ ROCKSDB_NAMESPACE::Env* env = options.env;
+ ROCKSDB_NAMESPACE::Status st = env->GetChildren(dir_or_file, &filenames);
+ bool dir = true;
+ if (!st.ok()) {
+ filenames.clear();
+ filenames.push_back(dir_or_file);
+ dir = false;
+ }
+
+ fprintf(stdout, "from [%s] to [%s]\n",
+ ROCKSDB_NAMESPACE::Slice(from_key).ToString(true).c_str(),
+ ROCKSDB_NAMESPACE::Slice(to_key).ToString(true).c_str());
+
+ uint64_t total_read = 0;
+ for (size_t i = 0; i < filenames.size(); i++) {
+ std::string filename = filenames.at(i);
+ if (filename.length() <= 4 ||
+ filename.rfind(".sst") != filename.length() - 4) {
+ // ignore
+ continue;
+ }
+ if (dir) {
+ filename = std::string(dir_or_file) + "/" + filename;
+ }
+
+ ROCKSDB_NAMESPACE::SstFileDumper dumper(options, filename, verify_checksum,
+ output_hex, decode_blob_index);
+ if (!dumper.getStatus().ok()) {
+ fprintf(stderr, "%s: %s\n", filename.c_str(),
+ dumper.getStatus().ToString().c_str());
+ continue;
+ }
+
+ if (command == "recompress") {
+ dumper.ShowAllCompressionSizes(
+ set_block_size ? block_size : 16384,
+ compression_types.empty() ? kCompressions : compression_types);
+ return 0;
+ }
+
+ if (command == "raw") {
+ std::string out_filename = filename.substr(0, filename.length() - 4);
+ out_filename.append("_dump.txt");
+
+ st = dumper.DumpTable(out_filename);
+ if (!st.ok()) {
+ fprintf(stderr, "%s: %s\n", filename.c_str(), st.ToString().c_str());
+ exit(1);
+ } else {
+ fprintf(stdout, "raw dump written to file %s\n", &out_filename[0]);
+ }
+ continue;
+ }
+
+ // scan all files in give file path.
+ if (command == "" || command == "scan" || command == "check") {
+ st = dumper.ReadSequential(
+ command == "scan", read_num > 0 ? (read_num - total_read) : read_num,
+ has_from || use_from_as_prefix, from_key, has_to, to_key,
+ use_from_as_prefix);
+ if (!st.ok()) {
+ fprintf(stderr, "%s: %s\n", filename.c_str(),
+ st.ToString().c_str());
+ }
+ total_read += dumper.GetReadNumber();
+ if (read_num > 0 && total_read > read_num) {
+ break;
+ }
+ }
+
+ if (command == "verify") {
+ st = dumper.VerifyChecksum();
+ if (!st.ok()) {
+ fprintf(stderr, "%s is corrupted: %s\n", filename.c_str(),
+ st.ToString().c_str());
+ } else {
+ fprintf(stdout, "The file is ok\n");
+ }
+ continue;
+ }
+
+ if (show_properties || show_summary) {
+ const ROCKSDB_NAMESPACE::TableProperties* table_properties;
+
+ std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties>
+ table_properties_from_reader;
+ st = dumper.ReadTableProperties(&table_properties_from_reader);
+ if (!st.ok()) {
+ fprintf(stderr, "%s: %s\n", filename.c_str(), st.ToString().c_str());
+ fprintf(stderr, "Try to use initial table properties\n");
+ table_properties = dumper.GetInitTableProperties();
+ } else {
+ table_properties = table_properties_from_reader.get();
+ }
+ if (table_properties != nullptr) {
+ if (show_properties) {
+ fprintf(stdout,
+ "Table Properties:\n"
+ "------------------------------\n"
+ " %s",
+ table_properties->ToString("\n ", ": ").c_str());
+ }
+ total_num_files += 1;
+ total_num_data_blocks += table_properties->num_data_blocks;
+ total_data_block_size += table_properties->data_size;
+ total_index_block_size += table_properties->index_size;
+ total_filter_block_size += table_properties->filter_size;
+ if (show_properties) {
+ fprintf(stdout,
+ "Raw user collected properties\n"
+ "------------------------------\n");
+ for (const auto& kv : table_properties->user_collected_properties) {
+ std::string prop_name = kv.first;
+ std::string prop_val = Slice(kv.second).ToString(true);
+ fprintf(stdout, " # %s: 0x%s\n", prop_name.c_str(),
+ prop_val.c_str());
+ }
+ }
+ } else {
+ fprintf(stderr, "Reader unexpectedly returned null properties\n");
+ }
+ }
+ }
+ if (show_summary) {
+ fprintf(stdout, "total number of files: %" PRIu64 "\n", total_num_files);
+ fprintf(stdout, "total number of data blocks: %" PRIu64 "\n",
+ total_num_data_blocks);
+ fprintf(stdout, "total data block size: %" PRIu64 "\n",
+ total_data_block_size);
+ fprintf(stdout, "total index block size: %" PRIu64 "\n",
+ total_index_block_size);
+ fprintf(stdout, "total filter block size: %" PRIu64 "\n",
+ total_filter_block_size);
+ }
+ return 0;
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/sst_dump_tool_imp.h b/storage/rocksdb/rocksdb/tools/sst_dump_tool_imp.h
new file mode 100644
index 00000000..28c217ef
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/sst_dump_tool_imp.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/sst_dump_tool.h"
+
+#include <memory>
+#include <string>
+#include "db/dbformat.h"
+#include "file/writable_file_writer.h"
+#include "options/cf_options.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SstFileDumper {
+ public:
+ explicit SstFileDumper(const Options& options, const std::string& file_name,
+ bool verify_checksum, bool output_hex,
+ bool decode_blob_index);
+
+ Status ReadSequential(bool print_kv, uint64_t read_num, bool has_from,
+ const std::string& from_key, bool has_to,
+ const std::string& to_key,
+ bool use_from_as_prefix = false);
+
+ Status ReadTableProperties(
+ std::shared_ptr<const TableProperties>* table_properties);
+ uint64_t GetReadNumber() { return read_num_; }
+ TableProperties* GetInitTableProperties() { return table_properties_.get(); }
+
+ Status VerifyChecksum();
+ Status DumpTable(const std::string& out_filename);
+ Status getStatus() { return init_result_; }
+
+ int ShowAllCompressionSizes(
+ size_t block_size,
+ const std::vector<std::pair<CompressionType, const char*>>&
+ compression_types);
+
+ private:
+ // Get the TableReader implementation for the sst file
+ Status GetTableReader(const std::string& file_path);
+ Status ReadTableProperties(uint64_t table_magic_number,
+ RandomAccessFileReader* file, uint64_t file_size);
+
+ uint64_t CalculateCompressedTableSize(const TableBuilderOptions& tb_options,
+ size_t block_size,
+ uint64_t* num_data_blocks);
+
+ Status SetTableOptionsByMagicNumber(uint64_t table_magic_number);
+ Status SetOldTableOptions();
+
+ // Helper function to call the factory with settings specific to the
+ // factory implementation
+ Status NewTableReader(const ImmutableCFOptions& ioptions,
+ const EnvOptions& soptions,
+ const InternalKeyComparator& internal_comparator,
+ uint64_t file_size,
+ std::unique_ptr<TableReader>* table_reader);
+
+ std::string file_name_;
+ uint64_t read_num_;
+ bool verify_checksum_;
+ bool output_hex_;
+ bool decode_blob_index_;
+ EnvOptions soptions_;
+
+ // options_ and internal_comparator_ will also be used in
+ // ReadSequential internally (specifically, seek-related operations)
+ Options options_;
+
+ Status init_result_;
+ std::unique_ptr<TableReader> table_reader_;
+ std::unique_ptr<RandomAccessFileReader> file_;
+
+ const ImmutableCFOptions ioptions_;
+ const MutableCFOptions moptions_;
+ InternalKeyComparator internal_comparator_;
+ std::unique_ptr<TableProperties> table_properties_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/trace_analyzer.cc b/storage/rocksdb/rocksdb/tools/trace_analyzer.cc
new file mode 100644
index 00000000..958078d1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/trace_analyzer.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+#include "tools/trace_analyzer_tool.h"
+int main(int argc, char** argv) {
+ return ROCKSDB_NAMESPACE::trace_analyzer_tool(argc, argv);
+}
+#endif
+#else
+#include <stdio.h>
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Not supported in lite mode.\n");
+ return 1;
+}
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/trace_analyzer_test.cc b/storage/rocksdb/rocksdb/tools/trace_analyzer_test.cc
new file mode 100644
index 00000000..d98ea2e8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/trace_analyzer_test.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#ifndef GFLAGS
+#include <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run trace_analyzer test\n");
+ return 1;
+}
+#else
+
+#include <chrono>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <thread>
+
+#include "db/db_test_util.h"
+#include "file/read_write_util.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
+
+// 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_);
+ dbname_ = test_path_ + "/db";
+ }
+
+ ~TraceAnalyzerTest() override {}
+
+ void GenerateTrace(std::string trace_path) {
+ Options options;
+ options.create_if_missing = true;
+ options.merge_operator = MergeOperators::CreatePutOperator();
+ ReadOptions ro;
+ WriteOptions wo;
+ TraceOptions trace_opt;
+ DB* db_ = nullptr;
+ std::string value;
+ std::unique_ptr<TraceWriter> 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));
+
+ ASSERT_OK(db_->Get(ro, "a", &value));
+ single_iter = db_->NewIterator(ro);
+ single_iter->Seek("a");
+ single_iter->SeekForPrev("b");
+ delete single_iter;
+ std::this_thread::sleep_for (std::chrono::seconds(1));
+
+ db_->Get(ro, "g", &value);
+
+ ASSERT_OK(db_->EndTrace());
+
+ ASSERT_OK(env_->FileExists(trace_path));
+
+ std::unique_ptr<WritableFile> 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<std::string>& 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<int>(arg.size()) + 1;
+ }
+
+ ASSERT_EQ(0, ROCKSDB_NAMESPACE::trace_analyzer_tool(argc, argv));
+ }
+
+ void CheckFileContent(const std::vector<std::string>& cnt,
+ std::string file_path, bool full_content) {
+ ASSERT_OK(env_->FileExists(file_path));
+ std::unique_ptr<SequentialFile> f_ptr;
+ ASSERT_OK(env_->NewSequentialFile(file_path, &f_ptr, env_options_));
+
+ std::string get_line;
+ std::istringstream iss;
+ bool has_data = true;
+ std::vector<std::string> result;
+ uint32_t count;
+ Status s;
+ std::unique_ptr<FSSequentialFile> file =
+ NewLegacySequentialFileWrapper(f_ptr);
+ SequentialFileReader sf_reader(std::move(file), file_path,
+ 4096 /* filereadahead_size */);
+
+ for (count = 0; ReadOneLine(&iss, &sf_reader, &get_line, &has_data, &s);
+ ++count) {
+ ASSERT_OK(s);
+ result.push_back(get_line);
+ }
+
+ ASSERT_EQ(cnt.size(), result.size());
+ for (int i = 0; i < static_cast<int>(result.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<std::string>& paras_diff,
+ std::string output_path, std::string trace_path) {
+ std::vector<std::string> 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);
+ }
+ 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<std::string> paras = {"-analyze_get"};
+ 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<std::string> 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 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<std::string> 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<std::string> 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<std::string> paras = {"-analyze_put"};
+ 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<std::string> 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 0 0 0 0 0 0 2"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of Put
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> paras = {"-analyze_delete"};
+ 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<std::string> k_stats = {"0 0 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 1 0 0 0 0 0 3"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of Delete
+ std::vector<std::string> 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<std::string> 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<std::string> paras = {"-analyze_merge"};
+ 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<std::string> 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 1 0 0 1 0 0 4"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of Merge
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> paras = {"-analyze_single_delete"};
+ 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<std::string> k_stats = {"0 0 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 1 1 0 1 0 0 5"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of SingleDelete
+ std::vector<std::string> 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<std::string> 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<std::string> paras = {"-analyze_range_delete"};
+ 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<std::string> k_stats = {"0 0 0 1 1.000000", "0 0 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 1 1 2 1 0 0 7"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of DeleteRange
+ std::vector<std::string> 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<std::string> 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<std::string> paras = {"-analyze_iterator"};
+ 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<std::string> k_stats = {"0 0 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<std::string> 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<std::string> k_sequence = {"1", "5", "2", "3", "4",
+ "0", "6", "7", "0"};
+ file_path = output_path + "/test-human_readable_trace.txt";
+ CheckFileContent(k_sequence, file_path, false);
+
+ // Check the prefix
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> all_qps = {"1 1 1 1 2 1 1 1 9"};
+ file_path = output_path + "/test-qps_stats.txt";
+ CheckFileContent(all_qps, file_path, true);
+
+ // Check the qps of Iterator_Seek
+ std::vector<std::string> 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<std::string> 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 0 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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+#endif // GFLAG
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "Trace_analyzer test is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE return RUN_ALL_TESTS();
diff --git a/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.cc b/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.cc
new file mode 100644
index 00000000..e75845c2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.cc
@@ -0,0 +1,2001 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#ifdef GFLAGS
+#ifdef NUMA
+#include <numa.h>
+#include <numaif.h>
+#endif
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+
+#include <cinttypes>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+
+#include "db/db_impl/db_impl.h"
+#include "db/memtable.h"
+#include "db/write_batch_internal.h"
+#include "env/composite_env_wrapper.h"
+#include "file/read_write_util.h"
+#include "file/writable_file_writer.h"
+#include "options/cf_options.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/status.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/utilities/ldb_cmd.h"
+#include "rocksdb/write_batch.h"
+#include "table/meta_blocks.h"
+#include "table/plain/plain_table_factory.h"
+#include "table/table_reader.h"
+#include "tools/trace_analyzer_tool.h"
+#include "trace_replay/trace_replay.h"
+#include "util/coding.h"
+#include "util/compression.h"
+#include "util/gflags_compat.h"
+#include "util/random.h"
+#include "util/string_util.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::RegisterFlagValidator;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_string(trace_path, "", "The trace file path.");
+DEFINE_string(output_dir, "", "The directory to store the output files.");
+DEFINE_string(output_prefix, "trace",
+ "The prefix used for all the output files.");
+DEFINE_bool(output_key_stats, false,
+ "Output the key access count statistics to file\n"
+ "for accessed keys:\n"
+ "file name: <prefix>-<query_type>-<cf_id>-accessed_key_stats.txt\n"
+ "Format:[cf_id value_size access_keyid access_count]\n"
+ "for the whole key space keys:\n"
+ "File name: <prefix>-<query_type>-<cf_id>-whole_key_stats.txt\n"
+ "Format:[whole_key_space_keyid access_count]");
+DEFINE_bool(output_access_count_stats, false,
+ "Output the access count distribution statistics to file.\n"
+ "File name: <prefix>-<query_type>-<cf_id>-accessed_"
+ "key_count_distribution.txt \n"
+ "Format:[access_count number_of_access_count]");
+DEFINE_bool(output_time_series, false,
+ "Output the access time in second of each key, "
+ "such that we can have the time series data of the queries \n"
+ "File name: <prefix>-<query_type>-<cf_id>-time_series.txt\n"
+ "Format:[type_id time_in_sec access_keyid].");
+DEFINE_bool(try_process_corrupted_trace, false,
+ "In default, trace_analyzer will exit if the trace file is "
+ "corrupted due to the unexpected tracing cases. If this option "
+ "is enabled, trace_analyzer will stop reading the trace file, "
+ "and start analyzing the read-in data.");
+DEFINE_int32(output_prefix_cut, 0,
+ "The number of bytes as prefix to cut the keys.\n"
+ "If it is enabled, it will generate the following:\n"
+ "For accessed keys:\n"
+ "File name: <prefix>-<query_type>-<cf_id>-"
+ "accessed_key_prefix_cut.txt \n"
+ "Format:[acessed_keyid access_count_of_prefix "
+ "number_of_keys_in_prefix average_key_access "
+ "prefix_succ_ratio prefix]\n"
+ "For whole key space keys:\n"
+ "File name: <prefix>-<query_type>-<cf_id>"
+ "-whole_key_prefix_cut.txt\n"
+ "Format:[start_keyid_in_whole_keyspace prefix]\n"
+ "if 'output_qps_stats' and 'top_k' are enabled, it will output:\n"
+ "File name: <prefix>-<query_type>-<cf_id>"
+ "-accessed_top_k_qps_prefix_cut.txt\n"
+ "Format:[the_top_ith_qps_time QPS], [prefix qps_of_this_second].");
+DEFINE_bool(convert_to_human_readable_trace, false,
+ "Convert the binary trace file to a human readable txt file "
+ "for further processing. "
+ "This file will be extremely large "
+ "(similar size as the original binary trace file). "
+ "You can specify 'no_key' to reduce the size, if key is not "
+ "needed in the next step.\n"
+ "File name: <prefix>_human_readable_trace.txt\n"
+ "Format:[type_id cf_id value_size time_in_micorsec <key>].");
+DEFINE_bool(output_qps_stats, false,
+ "Output the query per second(qps) statistics \n"
+ "For the overall qps, it will contain all qps of each query type. "
+ "The time is started from the first trace record\n"
+ "File name: <prefix>_qps_stats.txt\n"
+ "Format: [qps_type_1 qps_type_2 ...... overall_qps]\n"
+ "For each cf and query, it will have its own qps output.\n"
+ "File name: <prefix>-<query_type>-<cf_id>_qps_stats.txt \n"
+ "Format:[query_count_in_this_second].");
+DEFINE_bool(no_print, false, "Do not print out any result");
+DEFINE_string(
+ print_correlation, "",
+ "intput format: [correlation pairs][.,.]\n"
+ "Output the query correlations between the pairs of query types "
+ "listed in the parameter, input should select the operations from:\n"
+ "get, put, delete, single_delete, rangle_delete, merge. No space "
+ "between the pairs separated by commar. Example: =[get,get]... "
+ "It will print out the number of pairs of 'A after B' and "
+ "the average time interval between the two query.");
+DEFINE_string(key_space_dir, "",
+ "<the directory stores full key space files> \n"
+ "The key space files should be: <column family id>.txt");
+DEFINE_bool(analyze_get, false, "Analyze the Get query.");
+DEFINE_bool(analyze_put, false, "Analyze the Put query.");
+DEFINE_bool(analyze_delete, false, "Analyze the Delete query.");
+DEFINE_bool(analyze_single_delete, false, "Analyze the SingleDelete query.");
+DEFINE_bool(analyze_range_delete, false, "Analyze the DeleteRange query.");
+DEFINE_bool(analyze_merge, false, "Analyze the Merge query.");
+DEFINE_bool(analyze_iterator, false,
+ " Analyze the iterate query like seek() and seekForPrev().");
+DEFINE_bool(no_key, false,
+ " Does not output the key to the result files to make smaller.");
+DEFINE_bool(print_overall_stats, true,
+ " Print the stats of the whole trace, "
+ "like total requests, keys, and etc.");
+DEFINE_bool(output_key_distribution, false, "Print the key size distribution.");
+DEFINE_bool(
+ output_value_distribution, false,
+ "Out put the value size distribution, only available for Put and Merge.\n"
+ "File name: <prefix>-<query_type>-<cf_id>"
+ "-accessed_value_size_distribution.txt\n"
+ "Format:[Number_of_value_size_between x and "
+ "x+value_interval is: <the count>]");
+DEFINE_int32(print_top_k_access, 1,
+ "<top K of the variables to be printed> "
+ "Print the top k accessed keys, top k accessed prefix "
+ "and etc.");
+DEFINE_int32(output_ignore_count, 0,
+ "<threshold>, ignores the access count <= this value, "
+ "it will shorter the output.");
+DEFINE_int32(value_interval, 8,
+ "To output the value distribution, we need to set the value "
+ "intervals and make the statistic of the value size distribution "
+ "in different intervals. The default is 8.");
+DEFINE_double(sample_ratio, 1.0,
+ "If the trace size is extremely huge or user want to sample "
+ "the trace when analyzing, sample ratio can be set (0, 1.0]");
+
+namespace ROCKSDB_NAMESPACE {
+
+std::map<std::string, int> taOptToIndex = {
+ {"get", 0}, {"put", 1},
+ {"delete", 2}, {"single_delete", 3},
+ {"range_delete", 4}, {"merge", 5},
+ {"iterator_Seek", 6}, {"iterator_SeekForPrev", 7}};
+
+std::map<int, std::string> taIndexToOpt = {
+ {0, "get"}, {1, "put"},
+ {2, "delete"}, {3, "single_delete"},
+ {4, "range_delete"}, {5, "merge"},
+ {6, "iterator_Seek"}, {7, "iterator_SeekForPrev"}};
+
+namespace {
+
+uint64_t MultiplyCheckOverflow(uint64_t op1, uint64_t op2) {
+ if (op1 == 0 || op2 == 0) {
+ return 0;
+ }
+ if (port::kMaxUint64 / op1 < op2) {
+ return op1;
+ }
+ return (op1 * op2);
+}
+
+void DecodeCFAndKeyFromString(std::string& buffer, uint32_t* cf_id, Slice* key) {
+ Slice buf(buffer);
+ GetFixed32(&buf, cf_id);
+ GetLengthPrefixedSlice(&buf, key);
+}
+
+} // namespace
+
+// The default constructor of AnalyzerOptions
+AnalyzerOptions::AnalyzerOptions()
+ : correlation_map(kTaTypeNum, std::vector<int>(kTaTypeNum, -1)) {}
+
+AnalyzerOptions::~AnalyzerOptions() {}
+
+void AnalyzerOptions::SparseCorrelationInput(const std::string& in_str) {
+ std::string cur = in_str;
+ if (cur.size() == 0) {
+ return;
+ }
+ while (!cur.empty()) {
+ if (cur.compare(0, 1, "[") != 0) {
+ fprintf(stderr, "Invalid correlation input: %s\n", in_str.c_str());
+ exit(1);
+ }
+ std::string opt1, opt2;
+ std::size_t split = cur.find_first_of(",");
+ if (split != std::string::npos) {
+ opt1 = cur.substr(1, split - 1);
+ } else {
+ fprintf(stderr, "Invalid correlation input: %s\n", in_str.c_str());
+ exit(1);
+ }
+ std::size_t end = cur.find_first_of("]");
+ if (end != std::string::npos) {
+ opt2 = cur.substr(split + 1, end - split - 1);
+ } else {
+ fprintf(stderr, "Invalid correlation input: %s\n", in_str.c_str());
+ exit(1);
+ }
+ cur = cur.substr(end + 1);
+
+ if (taOptToIndex.find(opt1) != taOptToIndex.end() &&
+ taOptToIndex.find(opt2) != taOptToIndex.end()) {
+ correlation_list.push_back(
+ std::make_pair(taOptToIndex[opt1], taOptToIndex[opt2]));
+ } else {
+ fprintf(stderr, "Invalid correlation input: %s\n", in_str.c_str());
+ exit(1);
+ }
+ }
+
+ int sequence = 0;
+ for (auto& it : correlation_list) {
+ correlation_map[it.first][it.second] = sequence;
+ sequence++;
+ }
+ return;
+}
+
+// The trace statistic struct constructor
+TraceStats::TraceStats() {
+ cf_id = 0;
+ cf_name = "0";
+ a_count = 0;
+ a_key_id = 0;
+ a_key_size_sqsum = 0;
+ a_key_size_sum = 0;
+ a_key_mid = 0;
+ a_value_size_sqsum = 0;
+ a_value_size_sum = 0;
+ a_value_mid = 0;
+ a_peak_qps = 0;
+ a_ave_qps = 0.0;
+}
+
+TraceStats::~TraceStats() {}
+
+// The trace analyzer constructor
+TraceAnalyzer::TraceAnalyzer(std::string& trace_path, std::string& output_path,
+ AnalyzerOptions _analyzer_opts)
+ : trace_name_(trace_path),
+ output_path_(output_path),
+ analyzer_opts_(_analyzer_opts) {
+ ROCKSDB_NAMESPACE::EnvOptions env_options;
+ env_ = ROCKSDB_NAMESPACE::Env::Default();
+ offset_ = 0;
+ c_time_ = 0;
+ total_requests_ = 0;
+ total_access_keys_ = 0;
+ total_gets_ = 0;
+ total_writes_ = 0;
+ trace_create_time_ = 0;
+ begin_time_ = 0;
+ end_time_ = 0;
+ time_series_start_ = 0;
+ cur_time_sec_ = 0;
+ if (FLAGS_sample_ratio > 1.0 || FLAGS_sample_ratio <= 0) {
+ sample_max_ = 1;
+ } else {
+ sample_max_ = static_cast<uint32_t>(1.0 / FLAGS_sample_ratio);
+ }
+
+ ta_.resize(kTaTypeNum);
+ ta_[0].type_name = "get";
+ if (FLAGS_analyze_get) {
+ ta_[0].enabled = true;
+ } else {
+ ta_[0].enabled = false;
+ }
+ ta_[1].type_name = "put";
+ if (FLAGS_analyze_put) {
+ ta_[1].enabled = true;
+ } else {
+ ta_[1].enabled = false;
+ }
+ ta_[2].type_name = "delete";
+ if (FLAGS_analyze_delete) {
+ ta_[2].enabled = true;
+ } else {
+ ta_[2].enabled = false;
+ }
+ ta_[3].type_name = "single_delete";
+ if (FLAGS_analyze_single_delete) {
+ ta_[3].enabled = true;
+ } else {
+ ta_[3].enabled = false;
+ }
+ ta_[4].type_name = "range_delete";
+ if (FLAGS_analyze_range_delete) {
+ ta_[4].enabled = true;
+ } else {
+ ta_[4].enabled = false;
+ }
+ ta_[5].type_name = "merge";
+ if (FLAGS_analyze_merge) {
+ ta_[5].enabled = true;
+ } else {
+ ta_[5].enabled = false;
+ }
+ ta_[6].type_name = "iterator_Seek";
+ if (FLAGS_analyze_iterator) {
+ ta_[6].enabled = true;
+ } else {
+ ta_[6].enabled = false;
+ }
+ ta_[7].type_name = "iterator_SeekForPrev";
+ if (FLAGS_analyze_iterator) {
+ ta_[7].enabled = true;
+ } else {
+ ta_[7].enabled = false;
+ }
+ for (int i = 0; i < kTaTypeNum; i++) {
+ ta_[i].sample_count = 0;
+ }
+}
+
+TraceAnalyzer::~TraceAnalyzer() {}
+
+// Prepare the processing
+// Initiate the global trace reader and writer here
+Status TraceAnalyzer::PrepareProcessing() {
+ Status s;
+ // Prepare the trace reader
+ s = NewFileTraceReader(env_, env_options_, trace_name_, &trace_reader_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Prepare and open the trace sequence file writer if needed
+ if (FLAGS_convert_to_human_readable_trace) {
+ std::string trace_sequence_name;
+ trace_sequence_name =
+ output_path_ + "/" + FLAGS_output_prefix + "-human_readable_trace.txt";
+ s = env_->NewWritableFile(trace_sequence_name, &trace_sequence_f_,
+ env_options_);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // prepare the general QPS file writer
+ if (FLAGS_output_qps_stats) {
+ std::string qps_stats_name;
+ qps_stats_name =
+ output_path_ + "/" + FLAGS_output_prefix + "-qps_stats.txt";
+ s = env_->NewWritableFile(qps_stats_name, &qps_f_, env_options_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ qps_stats_name =
+ output_path_ + "/" + FLAGS_output_prefix + "-cf_qps_stats.txt";
+ s = env_->NewWritableFile(qps_stats_name, &cf_qps_f_, env_options_);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ return Status::OK();
+}
+
+Status TraceAnalyzer::ReadTraceHeader(Trace* header) {
+ assert(header != nullptr);
+ Status s = ReadTraceRecord(header);
+ if (!s.ok()) {
+ return s;
+ }
+ if (header->type != kTraceBegin) {
+ return Status::Corruption("Corrupted trace file. Incorrect header.");
+ }
+ if (header->payload.substr(0, kTraceMagic.length()) != kTraceMagic) {
+ return Status::Corruption("Corrupted trace file. Incorrect magic.");
+ }
+
+ return s;
+}
+
+Status TraceAnalyzer::ReadTraceFooter(Trace* footer) {
+ assert(footer != nullptr);
+ Status s = ReadTraceRecord(footer);
+ if (!s.ok()) {
+ return s;
+ }
+ if (footer->type != kTraceEnd) {
+ return Status::Corruption("Corrupted trace file. Incorrect footer.");
+ }
+ return s;
+}
+
+Status TraceAnalyzer::ReadTraceRecord(Trace* trace) {
+ assert(trace != nullptr);
+ std::string encoded_trace;
+ Status s = trace_reader_->Read(&encoded_trace);
+ if (!s.ok()) {
+ return s;
+ }
+
+ Slice enc_slice = Slice(encoded_trace);
+ GetFixed64(&enc_slice, &trace->ts);
+ trace->type = static_cast<TraceType>(enc_slice[0]);
+ enc_slice.remove_prefix(kTraceTypeSize + kTracePayloadLengthSize);
+ trace->payload = enc_slice.ToString();
+ return s;
+}
+
+// process the trace itself and redirect the trace content
+// to different operation type handler. With different race
+// format, this function can be changed
+Status TraceAnalyzer::StartProcessing() {
+ Status s;
+ Trace header;
+ s = ReadTraceHeader(&header);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot read the header\n");
+ return s;
+ }
+ trace_create_time_ = header.ts;
+ if (FLAGS_output_time_series) {
+ time_series_start_ = header.ts;
+ }
+
+ Trace trace;
+ while (s.ok()) {
+ trace.reset();
+ s = ReadTraceRecord(&trace);
+ if (!s.ok()) {
+ break;
+ }
+
+ total_requests_++;
+ end_time_ = trace.ts;
+ if (trace.type == kTraceWrite) {
+ total_writes_++;
+ c_time_ = trace.ts;
+ WriteBatch batch(trace.payload);
+
+ // Note that, if the write happens in a transaction,
+ // 'Write' will be called twice, one for Prepare, one for
+ // Commit. Thus, in the trace, for the same WriteBatch, there
+ // will be two reords if it is in a transaction. Here, we only
+ // process the reord that is committed. If write is non-transaction,
+ // HasBeginPrepare()==false, so we process it normally.
+ if (batch.HasBeginPrepare() && !batch.HasCommit()) {
+ continue;
+ }
+ TraceWriteHandler write_handler(this);
+ s = batch.Iterate(&write_handler);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot process the write batch in the trace\n");
+ return s;
+ }
+ } else if (trace.type == kTraceGet) {
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKeyFromString(trace.payload, &cf_id, &key);
+ total_gets_++;
+
+ s = HandleGet(cf_id, key.ToString(), trace.ts, 1);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot process the get in the trace\n");
+ return s;
+ }
+ } else if (trace.type == kTraceIteratorSeek ||
+ trace.type == kTraceIteratorSeekForPrev) {
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKeyFromString(trace.payload, &cf_id, &key);
+ s = HandleIter(cf_id, key.ToString(), trace.ts, trace.type);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot process the iterator in the trace\n");
+ return s;
+ }
+ } else if (trace.type == kTraceEnd) {
+ break;
+ }
+ }
+ if (s.IsIncomplete()) {
+ // Fix it: Reaching eof returns Incomplete status at the moment.
+ //
+ return Status::OK();
+ }
+ return s;
+}
+
+// After the trace is processed by StartProcessing, the statistic data
+// is stored in the map or other in memory data structures. To get the
+// other statistic result such as key size distribution, value size
+// distribution, these data structures are re-processed here.
+Status TraceAnalyzer::MakeStatistics() {
+ int ret;
+ Status s;
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ for (auto& stat : ta_[type].stats) {
+ stat.second.a_key_id = 0;
+ for (auto& record : stat.second.a_key_stats) {
+ record.second.key_id = stat.second.a_key_id;
+ stat.second.a_key_id++;
+ if (record.second.access_count <=
+ static_cast<uint64_t>(FLAGS_output_ignore_count)) {
+ continue;
+ }
+
+ // Generate the key access count distribution data
+ if (FLAGS_output_access_count_stats) {
+ if (stat.second.a_count_stats.find(record.second.access_count) ==
+ stat.second.a_count_stats.end()) {
+ stat.second.a_count_stats[record.second.access_count] = 1;
+ } else {
+ stat.second.a_count_stats[record.second.access_count]++;
+ }
+ }
+
+ // Generate the key size distribution data
+ if (FLAGS_output_key_distribution) {
+ if (stat.second.a_key_size_stats.find(record.first.size()) ==
+ stat.second.a_key_size_stats.end()) {
+ stat.second.a_key_size_stats[record.first.size()] = 1;
+ } else {
+ stat.second.a_key_size_stats[record.first.size()]++;
+ }
+ }
+
+ if (!FLAGS_print_correlation.empty()) {
+ s = MakeStatisticCorrelation(stat.second, record.second);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+
+ // Output the prefix cut or the whole content of the accessed key space
+ if (FLAGS_output_key_stats || FLAGS_output_prefix_cut > 0) {
+ s = MakeStatisticKeyStatsOrPrefix(stat.second);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // output the access count distribution
+ if (FLAGS_output_access_count_stats && stat.second.a_count_dist_f) {
+ for (auto& record : stat.second.a_count_stats) {
+ ret = snprintf(buffer_, sizeof(buffer_),
+ "access_count: %" PRIu64 " num: %" PRIu64 "\n",
+ record.first, record.second);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_count_dist_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write access count distribution file failed\n");
+ return s;
+ }
+ }
+ }
+
+ // find the medium of the key size
+ uint64_t k_count = 0;
+ bool get_mid = false;
+ for (auto& record : stat.second.a_key_size_stats) {
+ k_count += record.second;
+ if (!get_mid && k_count >= stat.second.a_key_mid) {
+ stat.second.a_key_mid = record.first;
+ get_mid = true;
+ }
+ if (FLAGS_output_key_distribution && stat.second.a_key_size_f) {
+ ret = snprintf(buffer_, sizeof(buffer_), "%" PRIu64 " %" PRIu64 "\n",
+ record.first, record.second);
+ if (ret < 0) {
+ return Status::IOError("Format output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_key_size_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write key size distribution file failed\n");
+ return s;
+ }
+ }
+ }
+
+ // output the value size distribution
+ uint64_t v_begin = 0, v_end = 0, v_count = 0;
+ get_mid = false;
+ for (auto& record : stat.second.a_value_size_stats) {
+ v_begin = v_end;
+ v_end = (record.first + 1) * FLAGS_value_interval;
+ v_count += record.second;
+ if (!get_mid && v_count >= stat.second.a_count / 2) {
+ stat.second.a_value_mid = (v_begin + v_end) / 2;
+ get_mid = true;
+ }
+ if (FLAGS_output_value_distribution && stat.second.a_value_size_f &&
+ (type == TraceOperationType::kPut ||
+ type == TraceOperationType::kMerge)) {
+ ret = snprintf(buffer_, sizeof(buffer_),
+ "Number_of_value_size_between %" PRIu64 " and %" PRIu64
+ " is: %" PRIu64 "\n",
+ v_begin, v_end, record.second);
+ if (ret < 0) {
+ return Status::IOError("Format output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_value_size_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write value size distribution file failed\n");
+ return s;
+ }
+ }
+ }
+ }
+ }
+
+ // Make the QPS statistics
+ if (FLAGS_output_qps_stats) {
+ s = MakeStatisticQPS();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ return Status::OK();
+}
+
+// Process the statistics of the key access and
+// prefix of the accessed keys if required
+Status TraceAnalyzer::MakeStatisticKeyStatsOrPrefix(TraceStats& stats) {
+ int ret;
+ Status s;
+ std::string prefix = "0";
+ uint64_t prefix_access = 0;
+ uint64_t prefix_count = 0;
+ uint64_t prefix_succ_access = 0;
+ double prefix_ave_access = 0.0;
+ stats.a_succ_count = 0;
+ for (auto& record : stats.a_key_stats) {
+ // write the key access statistic file
+ if (!stats.a_key_f) {
+ return Status::IOError("Failed to open accessed_key_stats file.");
+ }
+ stats.a_succ_count += record.second.succ_count;
+ double succ_ratio = 0.0;
+ if (record.second.access_count > 0) {
+ succ_ratio = (static_cast<double>(record.second.succ_count)) /
+ record.second.access_count;
+ }
+ ret = snprintf(buffer_, sizeof(buffer_),
+ "%u %zu %" PRIu64 " %" PRIu64 " %f\n", record.second.cf_id,
+ record.second.value_size, record.second.key_id,
+ record.second.access_count, succ_ratio);
+ if (ret < 0) {
+ return Status::IOError("Format output failed");
+ }
+ std::string printout(buffer_);
+ s = stats.a_key_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write key access file failed\n");
+ return s;
+ }
+
+ // write the prefix cut of the accessed keys
+ if (FLAGS_output_prefix_cut > 0 && stats.a_prefix_cut_f) {
+ if (record.first.compare(0, FLAGS_output_prefix_cut, prefix) != 0) {
+ std::string prefix_out =
+ ROCKSDB_NAMESPACE::LDBCommand::StringToHex(prefix);
+ if (prefix_count == 0) {
+ prefix_ave_access = 0.0;
+ } else {
+ prefix_ave_access =
+ (static_cast<double>(prefix_access)) / prefix_count;
+ }
+ double prefix_succ_ratio = 0.0;
+ if (prefix_access > 0) {
+ prefix_succ_ratio =
+ (static_cast<double>(prefix_succ_access)) / prefix_access;
+ }
+ ret =
+ snprintf(buffer_, sizeof(buffer_),
+ "%" PRIu64 " %" PRIu64 " %" PRIu64 " %f %f %s\n",
+ record.second.key_id, prefix_access, prefix_count,
+ prefix_ave_access, prefix_succ_ratio, prefix_out.c_str());
+ if (ret < 0) {
+ return Status::IOError("Format output failed");
+ }
+ std::string pout(buffer_);
+ s = stats.a_prefix_cut_f->Append(pout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write accessed key prefix file failed\n");
+ return s;
+ }
+
+ // make the top k statistic for the prefix
+ if (static_cast<int32_t>(stats.top_k_prefix_access.size()) <
+ FLAGS_print_top_k_access) {
+ stats.top_k_prefix_access.push(
+ std::make_pair(prefix_access, prefix_out));
+ } else {
+ if (prefix_access > stats.top_k_prefix_access.top().first) {
+ stats.top_k_prefix_access.pop();
+ stats.top_k_prefix_access.push(
+ std::make_pair(prefix_access, prefix_out));
+ }
+ }
+
+ if (static_cast<int32_t>(stats.top_k_prefix_ave.size()) <
+ FLAGS_print_top_k_access) {
+ stats.top_k_prefix_ave.push(
+ std::make_pair(prefix_ave_access, prefix_out));
+ } else {
+ if (prefix_ave_access > stats.top_k_prefix_ave.top().first) {
+ stats.top_k_prefix_ave.pop();
+ stats.top_k_prefix_ave.push(
+ std::make_pair(prefix_ave_access, prefix_out));
+ }
+ }
+
+ prefix = record.first.substr(0, FLAGS_output_prefix_cut);
+ prefix_access = 0;
+ prefix_count = 0;
+ prefix_succ_access = 0;
+ }
+ prefix_access += record.second.access_count;
+ prefix_count += 1;
+ prefix_succ_access += record.second.succ_count;
+ }
+ }
+ return Status::OK();
+}
+
+// Process the statistics of different query type
+// correlations
+Status TraceAnalyzer::MakeStatisticCorrelation(TraceStats& stats,
+ StatsUnit& unit) {
+ if (stats.correlation_output.size() !=
+ analyzer_opts_.correlation_list.size()) {
+ return Status::Corruption("Cannot make the statistic of correlation.");
+ }
+
+ for (int i = 0; i < static_cast<int>(analyzer_opts_.correlation_list.size());
+ i++) {
+ if (i >= static_cast<int>(stats.correlation_output.size()) ||
+ i >= static_cast<int>(unit.v_correlation.size())) {
+ break;
+ }
+ stats.correlation_output[i].first += unit.v_correlation[i].count;
+ stats.correlation_output[i].second += unit.v_correlation[i].total_ts;
+ }
+ return Status::OK();
+}
+
+// Process the statistics of QPS
+Status TraceAnalyzer::MakeStatisticQPS() {
+ if(begin_time_ == 0) {
+ begin_time_ = trace_create_time_;
+ }
+ uint32_t duration =
+ static_cast<uint32_t>((end_time_ - begin_time_) / 1000000);
+ int ret;
+ Status s;
+ std::vector<std::vector<uint32_t>> type_qps(
+ duration, std::vector<uint32_t>(kTaTypeNum + 1, 0));
+ std::vector<uint64_t> qps_sum(kTaTypeNum + 1, 0);
+ std::vector<uint32_t> qps_peak(kTaTypeNum + 1, 0);
+ qps_ave_.resize(kTaTypeNum + 1);
+
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ for (auto& stat : ta_[type].stats) {
+ uint32_t time_line = 0;
+ uint64_t cf_qps_sum = 0;
+ for (auto& time_it : stat.second.a_qps_stats) {
+ if (time_it.first >= duration) {
+ continue;
+ }
+ type_qps[time_it.first][kTaTypeNum] += time_it.second;
+ type_qps[time_it.first][type] += time_it.second;
+ cf_qps_sum += time_it.second;
+ if (time_it.second > stat.second.a_peak_qps) {
+ stat.second.a_peak_qps = time_it.second;
+ }
+ if (stat.second.a_qps_f) {
+ while (time_line < time_it.first) {
+ ret = snprintf(buffer_, sizeof(buffer_), "%u\n", 0);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_qps_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write QPS file failed\n");
+ return s;
+ }
+ time_line++;
+ }
+ ret = snprintf(buffer_, sizeof(buffer_), "%u\n", time_it.second);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_qps_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write QPS file failed\n");
+ return s;
+ }
+ if (time_line == time_it.first) {
+ time_line++;
+ }
+ }
+
+ // Process the top k QPS peaks
+ if (FLAGS_output_prefix_cut > 0) {
+ if (static_cast<int32_t>(stat.second.top_k_qps_sec.size()) <
+ FLAGS_print_top_k_access) {
+ stat.second.top_k_qps_sec.push(
+ std::make_pair(time_it.second, time_it.first));
+ } else {
+ if (stat.second.top_k_qps_sec.size() > 0 &&
+ stat.second.top_k_qps_sec.top().first < time_it.second) {
+ stat.second.top_k_qps_sec.pop();
+ stat.second.top_k_qps_sec.push(
+ std::make_pair(time_it.second, time_it.first));
+ }
+ }
+ }
+ }
+ if (duration == 0) {
+ stat.second.a_ave_qps = 0;
+ } else {
+ stat.second.a_ave_qps = (static_cast<double>(cf_qps_sum)) / duration;
+ }
+
+ // Output the accessed unique key number change overtime
+ if (stat.second.a_key_num_f) {
+ uint64_t cur_uni_key =
+ static_cast<uint64_t>(stat.second.a_key_stats.size());
+ double cur_ratio = 0.0;
+ uint64_t cur_num = 0;
+ for (uint32_t i = 0; i < duration; i++) {
+ auto find_time = stat.second.uni_key_num.find(i);
+ if (find_time != stat.second.uni_key_num.end()) {
+ cur_ratio = (static_cast<double>(find_time->second)) / cur_uni_key;
+ cur_num = find_time->second;
+ }
+ ret = snprintf(buffer_, sizeof(buffer_), "%" PRIu64 " %.12f\n",
+ cur_num, cur_ratio);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_key_num_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr,
+ "Write accessed unique key number change file failed\n");
+ return s;
+ }
+ }
+ }
+
+ // output the prefix of top k access peak
+ if (FLAGS_output_prefix_cut > 0 && stat.second.a_top_qps_prefix_f) {
+ while (!stat.second.top_k_qps_sec.empty()) {
+ ret = snprintf(buffer_, sizeof(buffer_), "At time: %u with QPS: %u\n",
+ stat.second.top_k_qps_sec.top().second,
+ stat.second.top_k_qps_sec.top().first);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.second.a_top_qps_prefix_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write prefix QPS top K file failed\n");
+ return s;
+ }
+ uint32_t qps_time = stat.second.top_k_qps_sec.top().second;
+ stat.second.top_k_qps_sec.pop();
+ if (stat.second.a_qps_prefix_stats.find(qps_time) !=
+ stat.second.a_qps_prefix_stats.end()) {
+ for (auto& qps_prefix : stat.second.a_qps_prefix_stats[qps_time]) {
+ std::string qps_prefix_out =
+ ROCKSDB_NAMESPACE::LDBCommand::StringToHex(qps_prefix.first);
+ ret = snprintf(buffer_, sizeof(buffer_),
+ "The prefix: %s Access count: %u\n",
+ qps_prefix_out.c_str(), qps_prefix.second);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string pout(buffer_);
+ s = stat.second.a_top_qps_prefix_f->Append(pout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write prefix QPS top K file failed\n");
+ return s;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (qps_f_) {
+ for (uint32_t i = 0; i < duration; i++) {
+ for (int type = 0; type <= kTaTypeNum; type++) {
+ if (type < kTaTypeNum) {
+ ret = snprintf(buffer_, sizeof(buffer_), "%u ", type_qps[i][type]);
+ } else {
+ ret = snprintf(buffer_, sizeof(buffer_), "%u\n", type_qps[i][type]);
+ }
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = qps_f_->Append(printout);
+ if (!s.ok()) {
+ return s;
+ }
+ qps_sum[type] += type_qps[i][type];
+ if (type_qps[i][type] > qps_peak[type]) {
+ qps_peak[type] = type_qps[i][type];
+ }
+ }
+ }
+ }
+
+ if (cf_qps_f_) {
+ int cfs_size = static_cast<uint32_t>(cfs_.size());
+ uint32_t v;
+ for (uint32_t i = 0; i < duration; i++) {
+ for (int cf = 0; cf < cfs_size; cf++) {
+ if (cfs_[cf].cf_qps.find(i) != cfs_[cf].cf_qps.end()) {
+ v = cfs_[cf].cf_qps[i];
+ } else {
+ v = 0;
+ }
+ if (cf < cfs_size - 1) {
+ ret = snprintf(buffer_, sizeof(buffer_), "%u ", v);
+ } else {
+ ret = snprintf(buffer_, sizeof(buffer_), "%u\n", v);
+ }
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = cf_qps_f_->Append(printout);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+ }
+
+ qps_peak_ = qps_peak;
+ for (int type = 0; type <= kTaTypeNum; type++) {
+ if (duration == 0) {
+ qps_ave_[type] = 0;
+ } else {
+ qps_ave_[type] = (static_cast<double>(qps_sum[type])) / duration;
+ }
+ }
+
+ return Status::OK();
+}
+
+// In reprocessing, if we have the whole key space
+// we can output the access count of all keys in a cf
+// we can make some statistics of the whole key space
+// also, we output the top k accessed keys here
+Status TraceAnalyzer::ReProcessing() {
+ int ret;
+ Status s;
+ for (auto& cf_it : cfs_) {
+ uint32_t cf_id = cf_it.first;
+
+ // output the time series;
+ if (FLAGS_output_time_series) {
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled ||
+ ta_[type].stats.find(cf_id) == ta_[type].stats.end()) {
+ continue;
+ }
+ TraceStats& stat = ta_[type].stats[cf_id];
+ if (!stat.time_series_f) {
+ fprintf(stderr, "Cannot write time_series of '%s' in '%u'\n",
+ ta_[type].type_name.c_str(), cf_id);
+ continue;
+ }
+ while (!stat.time_series.empty()) {
+ uint64_t key_id = 0;
+ auto found = stat.a_key_stats.find(stat.time_series.front().key);
+ if (found != stat.a_key_stats.end()) {
+ key_id = found->second.key_id;
+ }
+ ret =
+ snprintf(buffer_, sizeof(buffer_), "%u %" PRIu64 " %" PRIu64 "\n",
+ stat.time_series.front().type,
+ stat.time_series.front().ts, key_id);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.time_series_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write time series file failed\n");
+ return s;
+ }
+ stat.time_series.pop_front();
+ }
+ }
+ }
+
+ // process the whole key space if needed
+ if (!FLAGS_key_space_dir.empty()) {
+ std::string whole_key_path =
+ FLAGS_key_space_dir + "/" + std::to_string(cf_id) + ".txt";
+ std::string input_key, get_key;
+ std::vector<std::string> prefix(kTaTypeNum);
+ std::istringstream iss;
+ bool has_data = true;
+ std::unique_ptr<SequentialFile> wkey_input_f;
+
+ s = env_->NewSequentialFile(whole_key_path, &wkey_input_f, env_options_);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot open the whole key space file of CF: %u\n",
+ cf_id);
+ wkey_input_f.reset();
+ }
+
+ if (wkey_input_f) {
+ std::unique_ptr<FSSequentialFile> file;
+ file = NewLegacySequentialFileWrapper(wkey_input_f);
+ size_t kTraceFileReadaheadSize = 2 * 1024 * 1024;
+ SequentialFileReader sf_reader(
+ std::move(file), whole_key_path,
+ kTraceFileReadaheadSize /* filereadahead_size */);
+ for (cfs_[cf_id].w_count = 0;
+ ReadOneLine(&iss, &sf_reader, &get_key, &has_data, &s);
+ ++cfs_[cf_id].w_count) {
+ if (!s.ok()) {
+ fprintf(stderr, "Read whole key space file failed\n");
+ return s;
+ }
+
+ input_key = ROCKSDB_NAMESPACE::LDBCommand::HexToString(get_key);
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ TraceStats& stat = ta_[type].stats[cf_id];
+ if (stat.w_key_f) {
+ if (stat.a_key_stats.find(input_key) != stat.a_key_stats.end()) {
+ ret = snprintf(buffer_, sizeof(buffer_),
+ "%" PRIu64 " %" PRIu64 "\n", cfs_[cf_id].w_count,
+ stat.a_key_stats[input_key].access_count);
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.w_key_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr, "Write whole key space access file failed\n");
+ return s;
+ }
+ }
+ }
+
+ // Output the prefix cut file of the whole key space
+ if (FLAGS_output_prefix_cut > 0 && stat.w_prefix_cut_f) {
+ if (input_key.compare(0, FLAGS_output_prefix_cut, prefix[type]) !=
+ 0) {
+ prefix[type] = input_key.substr(0, FLAGS_output_prefix_cut);
+ std::string prefix_out =
+ ROCKSDB_NAMESPACE::LDBCommand::StringToHex(prefix[type]);
+ ret = snprintf(buffer_, sizeof(buffer_), "%" PRIu64 " %s\n",
+ cfs_[cf_id].w_count, prefix_out.c_str());
+ if (ret < 0) {
+ return Status::IOError("Format the output failed");
+ }
+ std::string printout(buffer_);
+ s = stat.w_prefix_cut_f->Append(printout);
+ if (!s.ok()) {
+ fprintf(stderr,
+ "Write whole key space prefix cut file failed\n");
+ return s;
+ }
+ }
+ }
+ }
+
+ // Make the statistics fo the key size distribution
+ if (FLAGS_output_key_distribution) {
+ if (cfs_[cf_id].w_key_size_stats.find(input_key.size()) ==
+ cfs_[cf_id].w_key_size_stats.end()) {
+ cfs_[cf_id].w_key_size_stats[input_key.size()] = 1;
+ } else {
+ cfs_[cf_id].w_key_size_stats[input_key.size()]++;
+ }
+ }
+ }
+ }
+ }
+
+ // process the top k accessed keys
+ if (FLAGS_print_top_k_access > 0) {
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled ||
+ ta_[type].stats.find(cf_id) == ta_[type].stats.end()) {
+ continue;
+ }
+ TraceStats& stat = ta_[type].stats[cf_id];
+ for (auto& record : stat.a_key_stats) {
+ if (static_cast<int32_t>(stat.top_k_queue.size()) <
+ FLAGS_print_top_k_access) {
+ stat.top_k_queue.push(
+ std::make_pair(record.second.access_count, record.first));
+ } else {
+ if (record.second.access_count > stat.top_k_queue.top().first) {
+ stat.top_k_queue.pop();
+ stat.top_k_queue.push(
+ std::make_pair(record.second.access_count, record.first));
+ }
+ }
+ }
+ }
+ }
+ }
+ return Status::OK();
+}
+
+// End the processing, print the requested results
+Status TraceAnalyzer::EndProcessing() {
+ if (trace_sequence_f_) {
+ trace_sequence_f_->Close();
+ }
+ if (FLAGS_no_print) {
+ return Status::OK();
+ }
+ PrintStatistics();
+ CloseOutputFiles();
+ return Status::OK();
+}
+
+// Insert the corresponding key statistics to the correct type
+// and correct CF, output the time-series file if needed
+Status TraceAnalyzer::KeyStatsInsertion(const uint32_t& type,
+ const uint32_t& cf_id,
+ const std::string& key,
+ const size_t value_size,
+ const uint64_t ts) {
+ Status s;
+ StatsUnit unit;
+ unit.key_id = 0;
+ unit.cf_id = cf_id;
+ unit.value_size = value_size;
+ unit.access_count = 1;
+ unit.latest_ts = ts;
+ if (type != TraceOperationType::kGet || value_size > 0) {
+ unit.succ_count = 1;
+ } else {
+ unit.succ_count = 0;
+ }
+ unit.v_correlation.resize(analyzer_opts_.correlation_list.size());
+ for (int i = 0;
+ i < (static_cast<int>(analyzer_opts_.correlation_list.size())); i++) {
+ unit.v_correlation[i].count = 0;
+ unit.v_correlation[i].total_ts = 0;
+ }
+ std::string prefix;
+ if (FLAGS_output_prefix_cut > 0) {
+ prefix = key.substr(0, FLAGS_output_prefix_cut);
+ }
+
+ if (begin_time_ == 0) {
+ begin_time_ = ts;
+ }
+ uint32_t time_in_sec;
+ if (ts < begin_time_) {
+ time_in_sec = 0;
+ } else {
+ time_in_sec = static_cast<uint32_t>((ts - begin_time_) / 1000000);
+ }
+
+ uint64_t dist_value_size = value_size / FLAGS_value_interval;
+ auto found_stats = ta_[type].stats.find(cf_id);
+ if (found_stats == ta_[type].stats.end()) {
+ ta_[type].stats[cf_id].cf_id = cf_id;
+ ta_[type].stats[cf_id].cf_name = std::to_string(cf_id);
+ ta_[type].stats[cf_id].a_count = 1;
+ ta_[type].stats[cf_id].a_key_id = 0;
+ ta_[type].stats[cf_id].a_key_size_sqsum = MultiplyCheckOverflow(
+ static_cast<uint64_t>(key.size()), static_cast<uint64_t>(key.size()));
+ ta_[type].stats[cf_id].a_key_size_sum = key.size();
+ ta_[type].stats[cf_id].a_value_size_sqsum = MultiplyCheckOverflow(
+ static_cast<uint64_t>(value_size), static_cast<uint64_t>(value_size));
+ ta_[type].stats[cf_id].a_value_size_sum = value_size;
+ s = OpenStatsOutputFiles(ta_[type].type_name, ta_[type].stats[cf_id]);
+ if (!FLAGS_print_correlation.empty()) {
+ s = StatsUnitCorrelationUpdate(unit, type, ts, key);
+ }
+ ta_[type].stats[cf_id].a_key_stats[key] = unit;
+ ta_[type].stats[cf_id].a_value_size_stats[dist_value_size] = 1;
+ ta_[type].stats[cf_id].a_qps_stats[time_in_sec] = 1;
+ ta_[type].stats[cf_id].correlation_output.resize(
+ analyzer_opts_.correlation_list.size());
+ if (FLAGS_output_prefix_cut > 0) {
+ std::map<std::string, uint32_t> tmp_qps_map;
+ tmp_qps_map[prefix] = 1;
+ ta_[type].stats[cf_id].a_qps_prefix_stats[time_in_sec] = tmp_qps_map;
+ }
+ if (time_in_sec != cur_time_sec_) {
+ ta_[type].stats[cf_id].uni_key_num[cur_time_sec_] =
+ static_cast<uint64_t>(ta_[type].stats[cf_id].a_key_stats.size());
+ cur_time_sec_ = time_in_sec;
+ }
+ } else {
+ found_stats->second.a_count++;
+ found_stats->second.a_key_size_sqsum += MultiplyCheckOverflow(
+ static_cast<uint64_t>(key.size()), static_cast<uint64_t>(key.size()));
+ found_stats->second.a_key_size_sum += key.size();
+ found_stats->second.a_value_size_sqsum += MultiplyCheckOverflow(
+ static_cast<uint64_t>(value_size), static_cast<uint64_t>(value_size));
+ found_stats->second.a_value_size_sum += value_size;
+ auto found_key = found_stats->second.a_key_stats.find(key);
+ if (found_key == found_stats->second.a_key_stats.end()) {
+ found_stats->second.a_key_stats[key] = unit;
+ } else {
+ found_key->second.access_count++;
+ if (type != TraceOperationType::kGet || value_size > 0) {
+ found_key->second.succ_count++;
+ }
+ if (!FLAGS_print_correlation.empty()) {
+ s = StatsUnitCorrelationUpdate(found_key->second, type, ts, key);
+ }
+ }
+ if (time_in_sec != cur_time_sec_) {
+ found_stats->second.uni_key_num[cur_time_sec_] =
+ static_cast<uint64_t>(found_stats->second.a_key_stats.size());
+ cur_time_sec_ = time_in_sec;
+ }
+
+ auto found_value =
+ found_stats->second.a_value_size_stats.find(dist_value_size);
+ if (found_value == found_stats->second.a_value_size_stats.end()) {
+ found_stats->second.a_value_size_stats[dist_value_size] = 1;
+ } else {
+ found_value->second++;
+ }
+
+ auto found_qps = found_stats->second.a_qps_stats.find(time_in_sec);
+ if (found_qps == found_stats->second.a_qps_stats.end()) {
+ found_stats->second.a_qps_stats[time_in_sec] = 1;
+ } else {
+ found_qps->second++;
+ }
+
+ if (FLAGS_output_prefix_cut > 0) {
+ auto found_qps_prefix =
+ found_stats->second.a_qps_prefix_stats.find(time_in_sec);
+ if (found_qps_prefix == found_stats->second.a_qps_prefix_stats.end()) {
+ std::map<std::string, uint32_t> tmp_qps_map;
+ found_stats->second.a_qps_prefix_stats[time_in_sec] = tmp_qps_map;
+ }
+ if (found_stats->second.a_qps_prefix_stats[time_in_sec].find(prefix) ==
+ found_stats->second.a_qps_prefix_stats[time_in_sec].end()) {
+ found_stats->second.a_qps_prefix_stats[time_in_sec][prefix] = 1;
+ } else {
+ found_stats->second.a_qps_prefix_stats[time_in_sec][prefix]++;
+ }
+ }
+ }
+
+ if (cfs_.find(cf_id) == cfs_.end()) {
+ CfUnit cf_unit;
+ cf_unit.cf_id = cf_id;
+ cf_unit.w_count = 0;
+ cf_unit.a_count = 0;
+ cfs_[cf_id] = cf_unit;
+ }
+
+ if (FLAGS_output_qps_stats) {
+ cfs_[cf_id].cf_qps[time_in_sec]++;
+ }
+
+ if (FLAGS_output_time_series) {
+ TraceUnit trace_u;
+ trace_u.type = type;
+ trace_u.key = key;
+ trace_u.value_size = value_size;
+ trace_u.ts = (ts - time_series_start_) / 1000000;
+ trace_u.cf_id = cf_id;
+ ta_[type].stats[cf_id].time_series.push_back(trace_u);
+ }
+
+ return Status::OK();
+}
+
+// Update the correlation unit of each key if enabled
+Status TraceAnalyzer::StatsUnitCorrelationUpdate(StatsUnit& unit,
+ const uint32_t& type_second,
+ const uint64_t& ts,
+ const std::string& key) {
+ if (type_second >= kTaTypeNum) {
+ fprintf(stderr, "Unknown Type Id: %u\n", type_second);
+ return Status::NotFound();
+ }
+
+ for (int type_first = 0; type_first < kTaTypeNum; type_first++) {
+ if (type_first >= static_cast<int>(ta_.size()) ||
+ type_first >= static_cast<int>(analyzer_opts_.correlation_map.size())) {
+ break;
+ }
+ if (analyzer_opts_.correlation_map[type_first][type_second] < 0 ||
+ ta_[type_first].stats.find(unit.cf_id) == ta_[type_first].stats.end() ||
+ ta_[type_first].stats[unit.cf_id].a_key_stats.find(key) ==
+ ta_[type_first].stats[unit.cf_id].a_key_stats.end() ||
+ ta_[type_first].stats[unit.cf_id].a_key_stats[key].latest_ts == ts) {
+ continue;
+ }
+
+ int correlation_id =
+ analyzer_opts_.correlation_map[type_first][type_second];
+
+ // after get the x-y operation time or x, update;
+ if (correlation_id < 0 ||
+ correlation_id >= static_cast<int>(unit.v_correlation.size())) {
+ continue;
+ }
+ unit.v_correlation[correlation_id].count++;
+ unit.v_correlation[correlation_id].total_ts +=
+ (ts - ta_[type_first].stats[unit.cf_id].a_key_stats[key].latest_ts);
+ }
+
+ unit.latest_ts = ts;
+ return Status::OK();
+}
+
+// when a new trace statistic is created, the file handler
+// pointers should be initiated if needed according to
+// the trace analyzer options
+Status TraceAnalyzer::OpenStatsOutputFiles(const std::string& type,
+ TraceStats& new_stats) {
+ Status s;
+ if (FLAGS_output_key_stats) {
+ s = CreateOutputFile(type, new_stats.cf_name, "accessed_key_stats.txt",
+ &new_stats.a_key_f);
+ s = CreateOutputFile(type, new_stats.cf_name,
+ "accessed_unique_key_num_change.txt",
+ &new_stats.a_key_num_f);
+ if (!FLAGS_key_space_dir.empty()) {
+ s = CreateOutputFile(type, new_stats.cf_name, "whole_key_stats.txt",
+ &new_stats.w_key_f);
+ }
+ }
+
+ if (FLAGS_output_access_count_stats) {
+ s = CreateOutputFile(type, new_stats.cf_name,
+ "accessed_key_count_distribution.txt",
+ &new_stats.a_count_dist_f);
+ }
+
+ if (FLAGS_output_prefix_cut > 0) {
+ s = CreateOutputFile(type, new_stats.cf_name, "accessed_key_prefix_cut.txt",
+ &new_stats.a_prefix_cut_f);
+ if (!FLAGS_key_space_dir.empty()) {
+ s = CreateOutputFile(type, new_stats.cf_name, "whole_key_prefix_cut.txt",
+ &new_stats.w_prefix_cut_f);
+ }
+
+ if (FLAGS_output_qps_stats) {
+ s = CreateOutputFile(type, new_stats.cf_name,
+ "accessed_top_k_qps_prefix_cut.txt",
+ &new_stats.a_top_qps_prefix_f);
+ }
+ }
+
+ if (FLAGS_output_time_series) {
+ s = CreateOutputFile(type, new_stats.cf_name, "time_series.txt",
+ &new_stats.time_series_f);
+ }
+
+ if (FLAGS_output_value_distribution) {
+ s = CreateOutputFile(type, new_stats.cf_name,
+ "accessed_value_size_distribution.txt",
+ &new_stats.a_value_size_f);
+ }
+
+ if (FLAGS_output_key_distribution) {
+ s = CreateOutputFile(type, new_stats.cf_name,
+ "accessed_key_size_distribution.txt",
+ &new_stats.a_key_size_f);
+ }
+
+ if (FLAGS_output_qps_stats) {
+ s = CreateOutputFile(type, new_stats.cf_name, "qps_stats.txt",
+ &new_stats.a_qps_f);
+ }
+
+ return Status::OK();
+}
+
+// create the output path of the files to be opened
+Status TraceAnalyzer::CreateOutputFile(
+ const std::string& type, const std::string& cf_name,
+ const std::string& ending,
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>* f_ptr) {
+ std::string path;
+ path = output_path_ + "/" + FLAGS_output_prefix + "-" + type + "-" + cf_name +
+ "-" + ending;
+ Status s;
+ s = env_->NewWritableFile(path, f_ptr, env_options_);
+ if (!s.ok()) {
+ fprintf(stderr, "Cannot open file: %s\n", path.c_str());
+ exit(1);
+ }
+ return Status::OK();
+}
+
+// Close the output files in the TraceStats if they are opened
+void TraceAnalyzer::CloseOutputFiles() {
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ for (auto& stat : ta_[type].stats) {
+ if (stat.second.time_series_f) {
+ stat.second.time_series_f->Close();
+ }
+
+ if (stat.second.a_key_f) {
+ stat.second.a_key_f->Close();
+ }
+
+ if (stat.second.a_key_num_f) {
+ stat.second.a_key_num_f->Close();
+ }
+
+ if (stat.second.a_count_dist_f) {
+ stat.second.a_count_dist_f->Close();
+ }
+
+ if (stat.second.a_prefix_cut_f) {
+ stat.second.a_prefix_cut_f->Close();
+ }
+
+ if (stat.second.a_value_size_f) {
+ stat.second.a_value_size_f->Close();
+ }
+
+ if (stat.second.a_key_size_f) {
+ stat.second.a_key_size_f->Close();
+ }
+
+ if (stat.second.a_qps_f) {
+ stat.second.a_qps_f->Close();
+ }
+
+ if (stat.second.a_top_qps_prefix_f) {
+ stat.second.a_top_qps_prefix_f->Close();
+ }
+
+ if (stat.second.w_key_f) {
+ stat.second.w_key_f->Close();
+ }
+ if (stat.second.w_prefix_cut_f) {
+ stat.second.w_prefix_cut_f->Close();
+ }
+ }
+ }
+ return;
+}
+
+// Handle the Get request in the trace
+Status TraceAnalyzer::HandleGet(uint32_t column_family_id,
+ const std::string& key, const uint64_t& ts,
+ const uint32_t& get_ret) {
+ Status s;
+ size_t value_size = 0;
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kGet, column_family_id, key,
+ value_size, ts);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kGet].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kGet].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kGet].sample_count > 0) {
+ ta_[TraceOperationType::kGet].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kGet].sample_count++;
+
+ if (!ta_[TraceOperationType::kGet].enabled) {
+ return Status::OK();
+ }
+ if (get_ret == 1) {
+ value_size = 10;
+ }
+ s = KeyStatsInsertion(TraceOperationType::kGet, column_family_id, key,
+ value_size, ts);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the Put request in the write batch of the trace
+Status TraceAnalyzer::HandlePut(uint32_t column_family_id, const Slice& key,
+ const Slice& value) {
+ Status s;
+ size_t value_size = value.ToString().size();
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kPut, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kPut].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kPut].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kPut].sample_count > 0) {
+ ta_[TraceOperationType::kPut].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kPut].sample_count++;
+
+ if (!ta_[TraceOperationType::kPut].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(TraceOperationType::kPut, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the Delete request in the write batch of the trace
+Status TraceAnalyzer::HandleDelete(uint32_t column_family_id,
+ const Slice& key) {
+ Status s;
+ size_t value_size = 0;
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kDelete, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kDelete].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kDelete].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kDelete].sample_count > 0) {
+ ta_[TraceOperationType::kDelete].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kDelete].sample_count++;
+
+ if (!ta_[TraceOperationType::kDelete].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(TraceOperationType::kDelete, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the SingleDelete request in the write batch of the trace
+Status TraceAnalyzer::HandleSingleDelete(uint32_t column_family_id,
+ const Slice& key) {
+ Status s;
+ size_t value_size = 0;
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kSingleDelete, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kSingleDelete].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kSingleDelete].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kSingleDelete].sample_count > 0) {
+ ta_[TraceOperationType::kSingleDelete].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kSingleDelete].sample_count++;
+
+ if (!ta_[TraceOperationType::kSingleDelete].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(TraceOperationType::kSingleDelete, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the DeleteRange request in the write batch of the trace
+Status TraceAnalyzer::HandleDeleteRange(uint32_t column_family_id,
+ const Slice& begin_key,
+ const Slice& end_key) {
+ Status s;
+ size_t value_size = 0;
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kRangeDelete, column_family_id,
+ begin_key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kRangeDelete].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kRangeDelete].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kRangeDelete].sample_count > 0) {
+ ta_[TraceOperationType::kRangeDelete].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kRangeDelete].sample_count++;
+
+ if (!ta_[TraceOperationType::kRangeDelete].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(TraceOperationType::kRangeDelete, column_family_id,
+ begin_key.ToString(), value_size, c_time_);
+ s = KeyStatsInsertion(TraceOperationType::kRangeDelete, column_family_id,
+ end_key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the Merge request in the write batch of the trace
+Status TraceAnalyzer::HandleMerge(uint32_t column_family_id, const Slice& key,
+ const Slice& value) {
+ Status s;
+ size_t value_size = value.ToString().size();
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(TraceOperationType::kMerge, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[TraceOperationType::kMerge].sample_count >= sample_max_) {
+ ta_[TraceOperationType::kMerge].sample_count = 0;
+ }
+ if (ta_[TraceOperationType::kMerge].sample_count > 0) {
+ ta_[TraceOperationType::kMerge].sample_count++;
+ return Status::OK();
+ }
+ ta_[TraceOperationType::kMerge].sample_count++;
+
+ if (!ta_[TraceOperationType::kMerge].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(TraceOperationType::kMerge, column_family_id,
+ key.ToString(), value_size, c_time_);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Handle the Iterator request in the trace
+Status TraceAnalyzer::HandleIter(uint32_t column_family_id,
+ const std::string& key, const uint64_t& ts,
+ TraceType& trace_type) {
+ Status s;
+ size_t value_size = 0;
+ int type = -1;
+ if (trace_type == kTraceIteratorSeek) {
+ type = TraceOperationType::kIteratorSeek;
+ } else if (trace_type == kTraceIteratorSeekForPrev) {
+ type = TraceOperationType::kIteratorSeekForPrev;
+ } else {
+ return s;
+ }
+ if (type == -1) {
+ return s;
+ }
+
+ if (FLAGS_convert_to_human_readable_trace && trace_sequence_f_) {
+ s = WriteTraceSequence(type, column_family_id, key, value_size, ts);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to write the trace sequence to file");
+ }
+ }
+
+ if (ta_[type].sample_count >= sample_max_) {
+ ta_[type].sample_count = 0;
+ }
+ if (ta_[type].sample_count > 0) {
+ ta_[type].sample_count++;
+ return Status::OK();
+ }
+ ta_[type].sample_count++;
+
+ if (!ta_[type].enabled) {
+ return Status::OK();
+ }
+ s = KeyStatsInsertion(type, column_family_id, key, value_size, ts);
+ if (!s.ok()) {
+ return Status::Corruption("Failed to insert key statistics");
+ }
+ return s;
+}
+
+// Before the analyzer is closed, the requested general statistic results are
+// printed out here. In current stage, these information are not output to
+// the files.
+// -----type
+// |__cf_id
+// |_statistics
+void TraceAnalyzer::PrintStatistics() {
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ ta_[type].total_keys = 0;
+ ta_[type].total_access = 0;
+ ta_[type].total_succ_access = 0;
+ printf("\n################# Operation Type: %s #####################\n",
+ ta_[type].type_name.c_str());
+ if (qps_ave_.size() == kTaTypeNum + 1) {
+ printf("Peak QPS is: %u Average QPS is: %f\n", qps_peak_[type],
+ qps_ave_[type]);
+ }
+ for (auto& stat_it : ta_[type].stats) {
+ if (stat_it.second.a_count == 0) {
+ continue;
+ }
+ TraceStats& stat = stat_it.second;
+ uint64_t total_a_keys = static_cast<uint64_t>(stat.a_key_stats.size());
+ double key_size_ave = 0.0;
+ double value_size_ave = 0.0;
+ double key_size_vari = 0.0;
+ double value_size_vari = 0.0;
+ if (stat.a_count > 0) {
+ key_size_ave =
+ (static_cast<double>(stat.a_key_size_sum)) / stat.a_count;
+ value_size_ave =
+ (static_cast<double>(stat.a_value_size_sum)) / stat.a_count;
+ key_size_vari = std::sqrt((static_cast<double>(stat.a_key_size_sqsum)) /
+ stat.a_count -
+ key_size_ave * key_size_ave);
+ value_size_vari = std::sqrt(
+ (static_cast<double>(stat.a_value_size_sqsum)) / stat.a_count -
+ value_size_ave * value_size_ave);
+ }
+ if (value_size_ave == 0.0) {
+ stat.a_value_mid = 0;
+ }
+ cfs_[stat.cf_id].a_count += total_a_keys;
+ ta_[type].total_keys += total_a_keys;
+ ta_[type].total_access += stat.a_count;
+ ta_[type].total_succ_access += stat.a_succ_count;
+ printf("*********************************************************\n");
+ printf("colume family id: %u\n", stat.cf_id);
+ printf("Total number of queries to this cf by %s: %" PRIu64 "\n",
+ ta_[type].type_name.c_str(), stat.a_count);
+ printf("Total unique keys in this cf: %" PRIu64 "\n", total_a_keys);
+ printf("Average key size: %f key size medium: %" PRIu64
+ " Key size Variation: %f\n",
+ key_size_ave, stat.a_key_mid, key_size_vari);
+ if (type == kPut || type == kMerge) {
+ printf("Average value size: %f Value size medium: %" PRIu64
+ " Value size variation: %f\n",
+ value_size_ave, stat.a_value_mid, value_size_vari);
+ }
+ printf("Peak QPS is: %u Average QPS is: %f\n", stat.a_peak_qps,
+ stat.a_ave_qps);
+
+ // print the top k accessed key and its access count
+ if (FLAGS_print_top_k_access > 0) {
+ printf("The Top %d keys that are accessed:\n",
+ FLAGS_print_top_k_access);
+ while (!stat.top_k_queue.empty()) {
+ std::string hex_key = ROCKSDB_NAMESPACE::LDBCommand::StringToHex(
+ stat.top_k_queue.top().second);
+ printf("Access_count: %" PRIu64 " %s\n", stat.top_k_queue.top().first,
+ hex_key.c_str());
+ stat.top_k_queue.pop();
+ }
+ }
+
+ // print the top k access prefix range and
+ // top k prefix range with highest average access per key
+ if (FLAGS_output_prefix_cut > 0) {
+ printf("The Top %d accessed prefix range:\n", FLAGS_print_top_k_access);
+ while (!stat.top_k_prefix_access.empty()) {
+ printf("Prefix: %s Access count: %" PRIu64 "\n",
+ stat.top_k_prefix_access.top().second.c_str(),
+ stat.top_k_prefix_access.top().first);
+ stat.top_k_prefix_access.pop();
+ }
+
+ printf("The Top %d prefix with highest access per key:\n",
+ FLAGS_print_top_k_access);
+ while (!stat.top_k_prefix_ave.empty()) {
+ printf("Prefix: %s access per key: %f\n",
+ stat.top_k_prefix_ave.top().second.c_str(),
+ stat.top_k_prefix_ave.top().first);
+ stat.top_k_prefix_ave.pop();
+ }
+ }
+
+ // print the operation correlations
+ if (!FLAGS_print_correlation.empty()) {
+ for (int correlation = 0;
+ correlation <
+ static_cast<int>(analyzer_opts_.correlation_list.size());
+ correlation++) {
+ printf(
+ "The correlation statistics of '%s' after '%s' is:",
+ taIndexToOpt[analyzer_opts_.correlation_list[correlation].second]
+ .c_str(),
+ taIndexToOpt[analyzer_opts_.correlation_list[correlation].first]
+ .c_str());
+ double correlation_ave = 0.0;
+ if (stat.correlation_output[correlation].first > 0) {
+ correlation_ave =
+ (static_cast<double>(
+ stat.correlation_output[correlation].second)) /
+ (stat.correlation_output[correlation].first * 1000);
+ }
+ printf(" total numbers: %" PRIu64 " average time: %f(ms)\n",
+ stat.correlation_output[correlation].first, correlation_ave);
+ }
+ }
+ }
+ printf("*********************************************************\n");
+ printf("Total keys of '%s' is: %" PRIu64 "\n", ta_[type].type_name.c_str(),
+ ta_[type].total_keys);
+ printf("Total access is: %" PRIu64 "\n", ta_[type].total_access);
+ total_access_keys_ += ta_[type].total_keys;
+ }
+
+ // Print the overall statistic information of the trace
+ printf("\n*********************************************************\n");
+ printf("*********************************************************\n");
+ printf("The column family based statistics\n");
+ for (auto& cf : cfs_) {
+ printf("The column family id: %u\n", cf.first);
+ printf("The whole key space key numbers: %" PRIu64 "\n", cf.second.w_count);
+ printf("The accessed key space key numbers: %" PRIu64 "\n",
+ cf.second.a_count);
+ }
+
+ if (FLAGS_print_overall_stats) {
+ printf("\n*********************************************************\n");
+ printf("*********************************************************\n");
+ if (qps_peak_.size() == kTaTypeNum + 1) {
+ printf("Average QPS per second: %f Peak QPS: %u\n", qps_ave_[kTaTypeNum],
+ qps_peak_[kTaTypeNum]);
+ }
+ printf("The statistics related to query number need to times: %u\n",
+ sample_max_);
+ printf("Total_requests: %" PRIu64 " Total_accessed_keys: %" PRIu64
+ " Total_gets: %" PRIu64 " Total_write_batch: %" PRIu64 "\n",
+ total_requests_, total_access_keys_, total_gets_, total_writes_);
+ for (int type = 0; type < kTaTypeNum; type++) {
+ if (!ta_[type].enabled) {
+ continue;
+ }
+ printf("Operation: '%s' has: %" PRIu64 "\n", ta_[type].type_name.c_str(),
+ ta_[type].total_access);
+ }
+ }
+}
+
+// Write the trace sequence to file
+Status TraceAnalyzer::WriteTraceSequence(const uint32_t& type,
+ const uint32_t& cf_id,
+ const std::string& key,
+ const size_t value_size,
+ const uint64_t ts) {
+ std::string hex_key = ROCKSDB_NAMESPACE::LDBCommand::StringToHex(key);
+ int ret;
+ ret = snprintf(buffer_, sizeof(buffer_), "%u %u %zu %" PRIu64 "\n", type,
+ cf_id, value_size, ts);
+ if (ret < 0) {
+ return Status::IOError("failed to format the output");
+ }
+ std::string printout(buffer_);
+ if (!FLAGS_no_key) {
+ printout = hex_key + " " + printout;
+ }
+ return trace_sequence_f_->Append(printout);
+}
+
+// The entrance function of Trace_Analyzer
+int trace_analyzer_tool(int argc, char** argv) {
+ std::string trace_path;
+ std::string output_path;
+
+ AnalyzerOptions analyzer_opts;
+
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ if (!FLAGS_print_correlation.empty()) {
+ analyzer_opts.SparseCorrelationInput(FLAGS_print_correlation);
+ }
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ new TraceAnalyzer(FLAGS_trace_path, FLAGS_output_dir, analyzer_opts));
+
+ if (!analyzer) {
+ fprintf(stderr, "Cannot initiate the trace analyzer\n");
+ exit(1);
+ }
+
+ ROCKSDB_NAMESPACE::Status s = analyzer->PrepareProcessing();
+ if (!s.ok()) {
+ fprintf(stderr, "%s\n", s.getState());
+ fprintf(stderr, "Cannot initiate the trace reader\n");
+ exit(1);
+ }
+
+ s = analyzer->StartProcessing();
+ if (!s.ok() && !FLAGS_try_process_corrupted_trace) {
+ fprintf(stderr, "%s\n", s.getState());
+ fprintf(stderr, "Cannot processing the trace\n");
+ exit(1);
+ }
+
+ s = analyzer->MakeStatistics();
+ if (!s.ok()) {
+ fprintf(stderr, "%s\n", s.getState());
+ analyzer->EndProcessing();
+ fprintf(stderr, "Cannot make the statistics\n");
+ exit(1);
+ }
+
+ s = analyzer->ReProcessing();
+ if (!s.ok()) {
+ fprintf(stderr, "%s\n", s.getState());
+ fprintf(stderr, "Cannot re-process the trace for more statistics\n");
+ analyzer->EndProcessing();
+ exit(1);
+ }
+
+ s = analyzer->EndProcessing();
+ if (!s.ok()) {
+ fprintf(stderr, "%s\n", s.getState());
+ fprintf(stderr, "Cannot close the trace analyzer\n");
+ exit(1);
+ }
+
+ return 0;
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // Endif of Gflag
+#endif // RocksDB LITE
diff --git a/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.h b/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.h
new file mode 100644
index 00000000..d2df2c82
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/trace_analyzer_tool.h
@@ -0,0 +1,292 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "rocksdb/env.h"
+#include "rocksdb/trace_reader_writer.h"
+#include "rocksdb/write_batch.h"
+#include "trace_replay/trace_replay.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+class WriteBatch;
+
+enum TraceOperationType : int {
+ kGet = 0,
+ kPut = 1,
+ kDelete = 2,
+ kSingleDelete = 3,
+ kRangeDelete = 4,
+ kMerge = 5,
+ kIteratorSeek = 6,
+ kIteratorSeekForPrev = 7,
+ kTaTypeNum = 8
+};
+
+struct TraceUnit {
+ uint64_t ts;
+ uint32_t type;
+ uint32_t cf_id;
+ size_t value_size;
+ std::string key;
+};
+
+struct TypeCorrelation {
+ uint64_t count;
+ uint64_t total_ts;
+};
+
+struct StatsUnit {
+ uint64_t key_id;
+ uint64_t access_count;
+ uint64_t latest_ts;
+ uint64_t succ_count; // current only used to count Get if key found
+ uint32_t cf_id;
+ size_t value_size;
+ std::vector<TypeCorrelation> v_correlation;
+};
+
+class AnalyzerOptions {
+ public:
+ std::vector<std::vector<int>> correlation_map;
+ std::vector<std::pair<int, int>> correlation_list;
+
+ AnalyzerOptions();
+
+ ~AnalyzerOptions();
+
+ void SparseCorrelationInput(const std::string& in_str);
+};
+
+// Note that, for the variable names in the trace_analyzer,
+// Starting with 'a_' means the variable is used for 'accessed_keys'.
+// Starting with 'w_' means it is used for 'the whole key space'.
+// Ending with '_f' means a file write or reader pointer.
+// For example, 'a_count' means 'accessed_keys_count',
+// 'w_key_f' means 'whole_key_space_file'.
+
+struct TraceStats {
+ uint32_t cf_id;
+ std::string cf_name;
+ uint64_t a_count;
+ uint64_t a_succ_count;
+ uint64_t a_key_id;
+ uint64_t a_key_size_sqsum;
+ uint64_t a_key_size_sum;
+ uint64_t a_key_mid;
+ uint64_t a_value_size_sqsum;
+ uint64_t a_value_size_sum;
+ uint64_t a_value_mid;
+ uint32_t a_peak_qps;
+ double a_ave_qps;
+ std::map<std::string, StatsUnit> a_key_stats;
+ std::map<uint64_t, uint64_t> a_count_stats;
+ std::map<uint64_t, uint64_t> a_key_size_stats;
+ std::map<uint64_t, uint64_t> a_value_size_stats;
+ std::map<uint32_t, uint32_t> a_qps_stats;
+ std::map<uint32_t, std::map<std::string, uint32_t>> a_qps_prefix_stats;
+ std::priority_queue<std::pair<uint64_t, std::string>,
+ std::vector<std::pair<uint64_t, std::string>>,
+ std::greater<std::pair<uint64_t, std::string>>>
+ top_k_queue;
+ std::priority_queue<std::pair<uint64_t, std::string>,
+ std::vector<std::pair<uint64_t, std::string>>,
+ std::greater<std::pair<uint64_t, std::string>>>
+ top_k_prefix_access;
+ std::priority_queue<std::pair<double, std::string>,
+ std::vector<std::pair<double, std::string>>,
+ std::greater<std::pair<double, std::string>>>
+ top_k_prefix_ave;
+ std::priority_queue<std::pair<uint32_t, uint32_t>,
+ std::vector<std::pair<uint32_t, uint32_t>>,
+ std::greater<std::pair<uint32_t, uint32_t>>>
+ top_k_qps_sec;
+ std::list<TraceUnit> time_series;
+ std::vector<std::pair<uint64_t, uint64_t>> correlation_output;
+ std::map<uint32_t, uint64_t> uni_key_num;
+
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> time_series_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_count_dist_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_prefix_cut_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_value_size_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_size_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_num_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_qps_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_top_qps_prefix_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> w_key_f;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> w_prefix_cut_f;
+
+ TraceStats();
+ ~TraceStats();
+ TraceStats(const TraceStats&) = delete;
+ TraceStats& operator=(const TraceStats&) = delete;
+ TraceStats(TraceStats&&) = default;
+ TraceStats& operator=(TraceStats&&) = default;
+};
+
+struct TypeUnit {
+ std::string type_name;
+ bool enabled;
+ uint64_t total_keys;
+ uint64_t total_access;
+ uint64_t total_succ_access;
+ uint32_t sample_count;
+ std::map<uint32_t, TraceStats> stats;
+ TypeUnit() = default;
+ ~TypeUnit() = default;
+ TypeUnit(const TypeUnit&) = delete;
+ TypeUnit& operator=(const TypeUnit&) = delete;
+ TypeUnit(TypeUnit&&) = default;
+ TypeUnit& operator=(TypeUnit&&) = default;
+};
+
+struct CfUnit {
+ uint32_t cf_id;
+ uint64_t w_count; // total keys in this cf if we use the whole key space
+ uint64_t a_count; // the total keys in this cf that are accessed
+ std::map<uint64_t, uint64_t> w_key_size_stats; // whole key space key size
+ // statistic this cf
+ std::map<uint32_t, uint32_t> cf_qps;
+};
+
+class TraceAnalyzer {
+ public:
+ TraceAnalyzer(std::string& trace_path, std::string& output_path,
+ AnalyzerOptions _analyzer_opts);
+ ~TraceAnalyzer();
+
+ Status PrepareProcessing();
+
+ Status StartProcessing();
+
+ Status MakeStatistics();
+
+ Status ReProcessing();
+
+ Status EndProcessing();
+
+ Status WriteTraceUnit(TraceUnit& unit);
+
+ // The trace processing functions for different type
+ Status HandleGet(uint32_t column_family_id, const std::string& key,
+ const uint64_t& ts, const uint32_t& get_ret);
+ Status HandlePut(uint32_t column_family_id, const Slice& key,
+ const Slice& value);
+ Status HandleDelete(uint32_t column_family_id, const Slice& key);
+ Status HandleSingleDelete(uint32_t column_family_id, const Slice& key);
+ Status HandleDeleteRange(uint32_t column_family_id, const Slice& begin_key,
+ const Slice& end_key);
+ Status HandleMerge(uint32_t column_family_id, const Slice& key,
+ const Slice& value);
+ Status HandleIter(uint32_t column_family_id, const std::string& key,
+ const uint64_t& ts, TraceType& trace_type);
+ std::vector<TypeUnit>& GetTaVector() { return ta_; }
+
+ private:
+ ROCKSDB_NAMESPACE::Env* env_;
+ EnvOptions env_options_;
+ std::unique_ptr<TraceReader> trace_reader_;
+ size_t offset_;
+ char buffer_[1024];
+ uint64_t c_time_;
+ std::string trace_name_;
+ std::string output_path_;
+ AnalyzerOptions analyzer_opts_;
+ uint64_t total_requests_;
+ uint64_t total_access_keys_;
+ uint64_t total_gets_;
+ uint64_t total_writes_;
+ uint64_t trace_create_time_;
+ uint64_t begin_time_;
+ uint64_t end_time_;
+ uint64_t time_series_start_;
+ uint32_t sample_max_;
+ uint32_t cur_time_sec_;
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>
+ trace_sequence_f_; // readable trace
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> qps_f_; // overall qps
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>
+ cf_qps_f_; // The qps of each CF>
+ std::vector<TypeUnit> ta_; // The main statistic collecting data structure
+ std::map<uint32_t, CfUnit> cfs_; // All the cf_id appears in this trace;
+ std::vector<uint32_t> qps_peak_;
+ std::vector<double> qps_ave_;
+
+ Status ReadTraceHeader(Trace* header);
+ Status ReadTraceFooter(Trace* footer);
+ Status ReadTraceRecord(Trace* trace);
+ Status KeyStatsInsertion(const uint32_t& type, const uint32_t& cf_id,
+ const std::string& key, const size_t value_size,
+ const uint64_t ts);
+ Status StatsUnitCorrelationUpdate(StatsUnit& unit, const uint32_t& type,
+ const uint64_t& ts, const std::string& key);
+ Status OpenStatsOutputFiles(const std::string& type, TraceStats& new_stats);
+ Status CreateOutputFile(
+ const std::string& type, const std::string& cf_name,
+ const std::string& ending,
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>* f_ptr);
+ void CloseOutputFiles();
+
+ void PrintStatistics();
+ Status TraceUnitWriter(
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>& f_ptr, TraceUnit& unit);
+ Status WriteTraceSequence(const uint32_t& type, const uint32_t& cf_id,
+ const std::string& key, const size_t value_size,
+ const uint64_t ts);
+ Status MakeStatisticKeyStatsOrPrefix(TraceStats& stats);
+ Status MakeStatisticCorrelation(TraceStats& stats, StatsUnit& unit);
+ Status MakeStatisticQPS();
+};
+
+// write bach handler to be used for WriteBache iterator
+// when processing the write trace
+class TraceWriteHandler : public WriteBatch::Handler {
+ public:
+ TraceWriteHandler() { ta_ptr = nullptr; }
+ explicit TraceWriteHandler(TraceAnalyzer* _ta_ptr) { ta_ptr = _ta_ptr; }
+ ~TraceWriteHandler() {}
+
+ virtual Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ return ta_ptr->HandlePut(column_family_id, key, value);
+ }
+ virtual Status DeleteCF(uint32_t column_family_id,
+ const Slice& key) override {
+ return ta_ptr->HandleDelete(column_family_id, key);
+ }
+ virtual Status SingleDeleteCF(uint32_t column_family_id,
+ const Slice& key) override {
+ return ta_ptr->HandleSingleDelete(column_family_id, key);
+ }
+ virtual Status DeleteRangeCF(uint32_t column_family_id,
+ const Slice& begin_key,
+ const Slice& end_key) override {
+ return ta_ptr->HandleDeleteRange(column_family_id, begin_key, end_key);
+ }
+ virtual Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ return ta_ptr->HandleMerge(column_family_id, key, value);
+ }
+
+ private:
+ TraceAnalyzer* ta_ptr;
+};
+
+int trace_analyzer_tool(int argc, char** argv);
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/tools/verify_random_db.sh b/storage/rocksdb/rocksdb/tools/verify_random_db.sh
new file mode 100755
index 00000000..817e4b98
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/verify_random_db.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+# A shell script to verify DB generated by generate_random_db.sh cannot opened and read correct data.
+# ./ldb needs to be avaible to be executed.
+#
+# Usage: <SCRIPT> <DB Path>
+
+scriptpath=`dirname $BASH_SOURCE`
+if [ "$#" -lt 2 ]; then
+ echo "usage: $BASH_SOURCE <db_directory> <compare_base_db_directory> [dump_file_name] [if_try_load_options] [if_ignore_unknown_options]"
+ exit 1
+fi
+
+db_dir=$1
+base_db_dir=$2
+dump_file_name=${3:-"dump_file.txt"}
+try_load_options=${4:-"1"}
+ignore_unknown_options=${5:-"0"}
+db_dump=$db_dir"/"$dump_file_name
+base_db_dump=$base_db_dir"/"$dump_file_name
+extra_param=
+
+if [ "$try_load_options" = "1" ]; then
+ extra_param=" --try_load_options "
+fi
+
+if [ "$ignore_unknown_options" = "1" ]; then
+ extra_param=" --ignore_unknown_options "
+fi
+
+set -e
+echo == Dumping data from $db_dir to $db_dump
+./ldb dump --db=$db_dir $extra_param > $db_dump
+
+echo == Dumping data from $base_db_dir to $base_db_dump
+./ldb dump --db=$base_db_dir $extra_param > $base_db_dump
+
+diff $db_dump $base_db_dir
diff --git a/storage/rocksdb/rocksdb/tools/write_external_sst.sh b/storage/rocksdb/rocksdb/tools/write_external_sst.sh
new file mode 100755
index 00000000..3b02d082
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/write_external_sst.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#
+#
+
+if [ "$#" -lt 3 ]; then
+ echo "usage: $BASH_SOURCE <input_data_path> <DB Path> <extern SST dir>"
+ exit 1
+fi
+
+input_data_dir=$1
+db_dir=$2
+extern_sst_dir=$3
+rm -rf $db_dir
+
+set -e
+
+n=0
+
+for f in `find $input_data_dir -name sorted_data*`
+do
+ echo == Writing external SST file $f to $extern_sst_dir/extern_sst${n}
+ ./ldb --db=$db_dir --create_if_missing write_extern_sst $extern_sst_dir/extern_sst${n} < $f
+ let "n = n + 1"
+done
diff --git a/storage/rocksdb/rocksdb/tools/write_stress.cc b/storage/rocksdb/rocksdb/tools/write_stress.cc
new file mode 100644
index 00000000..15b1da88
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/write_stress.cc
@@ -0,0 +1,305 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+//
+// The goal of this tool is to be a simple stress test with focus on catching:
+// * bugs in compaction/flush processes, especially the ones that cause
+// assertion errors
+// * bugs in the code that deletes obsolete files
+//
+// There are two parts of the test:
+// * write_stress, a binary that writes to the database
+// * write_stress_runner.py, a script that invokes and kills write_stress
+//
+// Here are some interesting parts of write_stress:
+// * Runs with very high concurrency of compactions and flushes (32 threads
+// total) and tries to create a huge amount of small files
+// * The keys written to the database are not uniformly distributed -- there is
+// a 3-character prefix that mutates occasionally (in prefix mutator thread), in
+// such a way that the first character mutates slower than second, which mutates
+// slower than third character. That way, the compaction stress tests some
+// interesting compaction features like trivial moves and bottommost level
+// calculation
+// * There is a thread that creates an iterator, holds it for couple of seconds
+// and then iterates over all keys. This is supposed to test RocksDB's abilities
+// to keep the files alive when there are references to them.
+// * Some writes trigger WAL sync. This is stress testing our WAL sync code.
+// * At the end of the run, we make sure that we didn't leak any of the sst
+// files
+//
+// write_stress_runner.py changes the mode in which we run write_stress and also
+// kills and restarts it. There are some interesting characteristics:
+// * At the beginning we divide the full test runtime into smaller parts --
+// shorter runtimes (couple of seconds) and longer runtimes (100, 1000) seconds
+// * The first time we run write_stress, we destroy the old DB. Every next time
+// during the test, we use the same DB.
+// * We can run in kill mode or clean-restart mode. Kill mode kills the
+// write_stress violently.
+// * We can run in mode where delete_obsolete_files_with_fullscan is true or
+// false
+// * We can run with low_open_files mode turned on or off. When it's turned on,
+// we configure table cache to only hold a couple of files -- that way we need
+// to reopen files every time we access them.
+//
+// Another goal was to create a stress test without a lot of parameters. So
+// tools/write_stress_runner.py should only take one parameter -- runtime_sec
+// and it should figure out everything else on its own.
+
+#include <cstdio>
+
+#ifndef GFLAGS
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include <atomic>
+#include <cinttypes>
+#include <random>
+#include <set>
+#include <string>
+#include <thread>
+
+#include "file/filename.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "util/gflags_compat.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::RegisterFlagValidator;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_int32(key_size, 10, "Key size");
+DEFINE_int32(value_size, 100, "Value size");
+DEFINE_string(db, "", "Use the db with the following name.");
+DEFINE_bool(destroy_db, true,
+ "Destroy the existing DB before running the test");
+
+DEFINE_int32(runtime_sec, 10 * 60, "How long are we running for, in seconds");
+DEFINE_int32(seed, 139, "Random seed");
+
+DEFINE_double(prefix_mutate_period_sec, 1.0,
+ "How often are we going to mutate the prefix");
+DEFINE_double(first_char_mutate_probability, 0.1,
+ "How likely are we to mutate the first char every period");
+DEFINE_double(second_char_mutate_probability, 0.2,
+ "How likely are we to mutate the second char every period");
+DEFINE_double(third_char_mutate_probability, 0.5,
+ "How likely are we to mutate the third char every period");
+
+DEFINE_int32(iterator_hold_sec, 5,
+ "How long will the iterator hold files before it gets destroyed");
+
+DEFINE_double(sync_probability, 0.01, "How often are we syncing writes");
+DEFINE_bool(delete_obsolete_files_with_fullscan, false,
+ "If true, we delete obsolete files after each compaction/flush "
+ "using GetChildren() API");
+DEFINE_bool(low_open_files_mode, false,
+ "If true, we set max_open_files to 20, so that every file access "
+ "needs to reopen it");
+
+namespace ROCKSDB_NAMESPACE {
+
+static const int kPrefixSize = 3;
+
+class WriteStress {
+ public:
+ WriteStress() : stop_(false) {
+ // initialize key_prefix
+ for (int i = 0; i < kPrefixSize; ++i) {
+ key_prefix_[i].store('a');
+ }
+
+ // Choose a location for the test database if none given with --db=<path>
+ if (FLAGS_db.empty()) {
+ std::string default_db_path;
+ Env::Default()->GetTestDirectory(&default_db_path);
+ default_db_path += "/write_stress";
+ FLAGS_db = default_db_path;
+ }
+
+ Options options;
+ if (FLAGS_destroy_db) {
+ DestroyDB(FLAGS_db, options); // ignore
+ }
+
+ // make the LSM tree deep, so that we have many concurrent flushes and
+ // compactions
+ options.create_if_missing = true;
+ options.write_buffer_size = 256 * 1024; // 256k
+ options.max_bytes_for_level_base = 1 * 1024 * 1024; // 1MB
+ options.target_file_size_base = 100 * 1024; // 100k
+ options.max_write_buffer_number = 16;
+ options.max_background_compactions = 16;
+ options.max_background_flushes = 16;
+ options.max_open_files = FLAGS_low_open_files_mode ? 20 : -1;
+ if (FLAGS_delete_obsolete_files_with_fullscan) {
+ options.delete_obsolete_files_period_micros = 0;
+ }
+
+ // open DB
+ DB* db;
+ Status s = DB::Open(options, FLAGS_db, &db);
+ if (!s.ok()) {
+ fprintf(stderr, "Can't open database: %s\n", s.ToString().c_str());
+ std::abort();
+ }
+ db_.reset(db);
+ }
+
+ void WriteThread() {
+ std::mt19937 rng(static_cast<unsigned int>(FLAGS_seed));
+ std::uniform_real_distribution<double> dist(0, 1);
+
+ auto random_string = [](std::mt19937& r, int len) {
+ std::uniform_int_distribution<int> char_dist('a', 'z');
+ std::string ret;
+ for (int i = 0; i < len; ++i) {
+ ret += static_cast<char>(char_dist(r));
+ }
+ return ret;
+ };
+
+ while (!stop_.load(std::memory_order_relaxed)) {
+ std::string prefix;
+ prefix.resize(kPrefixSize);
+ for (int i = 0; i < kPrefixSize; ++i) {
+ prefix[i] = key_prefix_[i].load(std::memory_order_relaxed);
+ }
+ auto key = prefix + random_string(rng, FLAGS_key_size - kPrefixSize);
+ auto value = random_string(rng, FLAGS_value_size);
+ WriteOptions woptions;
+ woptions.sync = dist(rng) < FLAGS_sync_probability;
+ auto s = db_->Put(woptions, key, value);
+ if (!s.ok()) {
+ fprintf(stderr, "Write to DB failed: %s\n", s.ToString().c_str());
+ std::abort();
+ }
+ }
+ }
+
+ void IteratorHoldThread() {
+ while (!stop_.load(std::memory_order_relaxed)) {
+ std::unique_ptr<Iterator> iterator(db_->NewIterator(ReadOptions()));
+ Env::Default()->SleepForMicroseconds(FLAGS_iterator_hold_sec * 1000 *
+ 1000LL);
+ for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
+ }
+ if (!iterator->status().ok()) {
+ fprintf(stderr, "Iterator statuts not OK: %s\n",
+ iterator->status().ToString().c_str());
+ std::abort();
+ }
+ }
+ }
+
+ void PrefixMutatorThread() {
+ std::mt19937 rng(static_cast<unsigned int>(FLAGS_seed));
+ std::uniform_real_distribution<double> dist(0, 1);
+ std::uniform_int_distribution<int> char_dist('a', 'z');
+ while (!stop_.load(std::memory_order_relaxed)) {
+ Env::Default()->SleepForMicroseconds(static_cast<int>(
+ FLAGS_prefix_mutate_period_sec *
+ 1000 * 1000LL));
+ if (dist(rng) < FLAGS_first_char_mutate_probability) {
+ key_prefix_[0].store(static_cast<char>(char_dist(rng)), std::memory_order_relaxed);
+ }
+ if (dist(rng) < FLAGS_second_char_mutate_probability) {
+ key_prefix_[1].store(static_cast<char>(char_dist(rng)), std::memory_order_relaxed);
+ }
+ if (dist(rng) < FLAGS_third_char_mutate_probability) {
+ key_prefix_[2].store(static_cast<char>(char_dist(rng)), std::memory_order_relaxed);
+ }
+ }
+ }
+
+ int Run() {
+ threads_.emplace_back([&]() { WriteThread(); });
+ threads_.emplace_back([&]() { PrefixMutatorThread(); });
+ threads_.emplace_back([&]() { IteratorHoldThread(); });
+
+ if (FLAGS_runtime_sec == -1) {
+ // infinite runtime, until we get killed
+ while (true) {
+ Env::Default()->SleepForMicroseconds(1000 * 1000);
+ }
+ }
+
+ Env::Default()->SleepForMicroseconds(FLAGS_runtime_sec * 1000 * 1000);
+
+ stop_.store(true, std::memory_order_relaxed);
+ for (auto& t : threads_) {
+ t.join();
+ }
+ threads_.clear();
+
+// Skip checking for leaked files in ROCKSDB_LITE since we don't have access to
+// function GetLiveFilesMetaData
+#ifndef ROCKSDB_LITE
+ // let's see if we leaked some files
+ db_->PauseBackgroundWork();
+ std::vector<LiveFileMetaData> metadata;
+ db_->GetLiveFilesMetaData(&metadata);
+ std::set<uint64_t> sst_file_numbers;
+ for (const auto& file : metadata) {
+ uint64_t number;
+ FileType type;
+ if (!ParseFileName(file.name, &number, "LOG", &type)) {
+ continue;
+ }
+ if (type == kTableFile) {
+ sst_file_numbers.insert(number);
+ }
+ }
+
+ std::vector<std::string> children;
+ Env::Default()->GetChildren(FLAGS_db, &children);
+ for (const auto& child : children) {
+ uint64_t number;
+ FileType type;
+ if (!ParseFileName(child, &number, "LOG", &type)) {
+ continue;
+ }
+ if (type == kTableFile) {
+ if (sst_file_numbers.find(number) == sst_file_numbers.end()) {
+ fprintf(stderr,
+ "Found a table file in DB path that should have been "
+ "deleted: %s\n",
+ child.c_str());
+ std::abort();
+ }
+ }
+ }
+ db_->ContinueBackgroundWork();
+#endif // !ROCKSDB_LITE
+
+ return 0;
+ }
+
+ private:
+ // each key is prepended with this prefix. we occasionally change it. third
+ // letter is changed more frequently than second, which is changed more
+ // frequently than the first one.
+ std::atomic<char> key_prefix_[kPrefixSize];
+ std::atomic<bool> stop_;
+ std::vector<port::Thread> threads_;
+ std::unique_ptr<DB> db_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [OPTIONS]...");
+ ParseCommandLineFlags(&argc, &argv, true);
+ ROCKSDB_NAMESPACE::WriteStress write_stress;
+ return write_stress.Run();
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/tools/write_stress_runner.py b/storage/rocksdb/rocksdb/tools/write_stress_runner.py
new file mode 100644
index 00000000..fc0c99c2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/tools/write_stress_runner.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python2
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+import subprocess
+import argparse
+import random
+import time
+import sys
+
+
+def generate_runtimes(total_runtime):
+ # combination of short runtimes and long runtimes, with heavier
+ # weight on short runtimes
+ possible_runtimes_sec = range(1, 10) + range(1, 20) + [100, 1000]
+ runtimes = []
+ while total_runtime > 0:
+ chosen = random.choice(possible_runtimes_sec)
+ chosen = min(chosen, total_runtime)
+ runtimes.append(chosen)
+ total_runtime -= chosen
+ return runtimes
+
+
+def main(args):
+ runtimes = generate_runtimes(int(args.runtime_sec))
+ print "Going to execute write stress for " + str(runtimes) # noqa: E999 T25377293 Grandfathered in
+ first_time = True
+
+ for runtime in runtimes:
+ kill = random.choice([False, True])
+
+ cmd = './write_stress --runtime_sec=' + \
+ ("-1" if kill else str(runtime))
+
+ if len(args.db) > 0:
+ cmd = cmd + ' --db=' + args.db
+
+ if first_time:
+ first_time = False
+ else:
+ # use current db
+ cmd = cmd + ' --destroy_db=false'
+ if random.choice([False, True]):
+ cmd = cmd + ' --delete_obsolete_files_with_fullscan=true'
+ if random.choice([False, True]):
+ cmd = cmd + ' --low_open_files_mode=true'
+
+ print("Running write_stress for %d seconds (%s): %s" %
+ (runtime, ("kill-mode" if kill else "clean-shutdown-mode"),
+ cmd))
+
+ child = subprocess.Popen([cmd], shell=True)
+ killtime = time.time() + runtime
+ while not kill or time.time() < killtime:
+ time.sleep(1)
+ if child.poll() is not None:
+ if child.returncode == 0:
+ break
+ else:
+ print("ERROR: write_stress died with exitcode=%d\n"
+ % child.returncode)
+ sys.exit(1)
+ if kill:
+ child.kill()
+ # breathe
+ time.sleep(3)
+
+if __name__ == '__main__':
+ random.seed(time.time())
+ parser = argparse.ArgumentParser(description="This script runs and kills \
+ write_stress multiple times")
+ parser.add_argument("--runtime_sec", default='1000')
+ parser.add_argument("--db", default='')
+ args = parser.parse_args()
+ main(args)
diff --git a/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.cc b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.cc
new file mode 100644
index 00000000..9a96f1ba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.cc
@@ -0,0 +1,497 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+#include <cstdio>
+#include <cstdlib>
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+#include "util/hash.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+const unsigned int kCharSize = 1;
+
+bool ShouldTrace(const Slice& block_key, const TraceOptions& trace_options) {
+ if (trace_options.sampling_frequency == 0 ||
+ trace_options.sampling_frequency == 1) {
+ return true;
+ }
+ // We use spatial downsampling so that we have a complete access history for a
+ // block.
+ return 0 == fastrange64(GetSliceNPHash64(block_key),
+ trace_options.sampling_frequency);
+}
+} // namespace
+
+const uint64_t kMicrosInSecond = 1000 * 1000;
+const uint64_t kSecondInMinute = 60;
+const uint64_t kSecondInHour = 3600;
+const std::string BlockCacheTraceHelper::kUnknownColumnFamilyName =
+ "UnknownColumnFamily";
+const uint64_t BlockCacheTraceHelper::kReservedGetId = 0;
+
+bool BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(
+ TraceType block_type, TableReaderCaller caller) {
+ return (block_type == TraceType::kBlockTraceDataBlock) &&
+ IsGetOrMultiGet(caller);
+}
+
+bool BlockCacheTraceHelper::IsGetOrMultiGet(TableReaderCaller caller) {
+ return caller == TableReaderCaller::kUserGet ||
+ caller == TableReaderCaller::kUserMultiGet;
+}
+
+bool BlockCacheTraceHelper::IsUserAccess(TableReaderCaller caller) {
+ return caller == TableReaderCaller::kUserGet ||
+ caller == TableReaderCaller::kUserMultiGet ||
+ caller == TableReaderCaller::kUserIterator ||
+ caller == TableReaderCaller::kUserApproximateSize ||
+ caller == TableReaderCaller::kUserVerifyChecksum;
+}
+
+std::string BlockCacheTraceHelper::ComputeRowKey(
+ const BlockCacheTraceRecord& access) {
+ if (!IsGetOrMultiGet(access.caller)) {
+ return "";
+ }
+ Slice key = ExtractUserKey(access.referenced_key);
+ return std::to_string(access.sst_fd_number) + "_" + key.ToString();
+}
+
+uint64_t BlockCacheTraceHelper::GetTableId(
+ const BlockCacheTraceRecord& access) {
+ if (!IsGetOrMultiGet(access.caller) || access.referenced_key.size() < 4) {
+ return 0;
+ }
+ return static_cast<uint64_t>(DecodeFixed32(access.referenced_key.data())) + 1;
+}
+
+uint64_t BlockCacheTraceHelper::GetSequenceNumber(
+ const BlockCacheTraceRecord& access) {
+ if (!IsGetOrMultiGet(access.caller)) {
+ return 0;
+ }
+ return access.get_from_user_specified_snapshot == Boolean::kFalse
+ ? 0
+ : 1 + GetInternalKeySeqno(access.referenced_key);
+}
+
+uint64_t BlockCacheTraceHelper::GetBlockOffsetInFile(
+ const BlockCacheTraceRecord& access) {
+ Slice input(access.block_key);
+ uint64_t offset = 0;
+ while (true) {
+ uint64_t tmp = 0;
+ if (GetVarint64(&input, &tmp)) {
+ offset = tmp;
+ } else {
+ break;
+ }
+ }
+ return offset;
+}
+
+BlockCacheTraceWriter::BlockCacheTraceWriter(
+ Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer)
+ : env_(env),
+ trace_options_(trace_options),
+ trace_writer_(std::move(trace_writer)) {}
+
+Status BlockCacheTraceWriter::WriteBlockAccess(
+ const BlockCacheTraceRecord& record, const Slice& block_key,
+ const Slice& cf_name, const Slice& referenced_key) {
+ uint64_t trace_file_size = trace_writer_->GetFileSize();
+ if (trace_file_size > trace_options_.max_trace_file_size) {
+ return Status::OK();
+ }
+ Trace trace;
+ trace.ts = record.access_timestamp;
+ trace.type = record.block_type;
+ PutLengthPrefixedSlice(&trace.payload, block_key);
+ PutFixed64(&trace.payload, record.block_size);
+ PutFixed64(&trace.payload, record.cf_id);
+ PutLengthPrefixedSlice(&trace.payload, cf_name);
+ PutFixed32(&trace.payload, record.level);
+ PutFixed64(&trace.payload, record.sst_fd_number);
+ trace.payload.push_back(record.caller);
+ trace.payload.push_back(record.is_cache_hit);
+ trace.payload.push_back(record.no_insert);
+ if (BlockCacheTraceHelper::IsGetOrMultiGet(record.caller)) {
+ PutFixed64(&trace.payload, record.get_id);
+ trace.payload.push_back(record.get_from_user_specified_snapshot);
+ PutLengthPrefixedSlice(&trace.payload, referenced_key);
+ }
+ if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(record.block_type,
+ record.caller)) {
+ PutFixed64(&trace.payload, record.referenced_data_size);
+ PutFixed64(&trace.payload, record.num_keys_in_block);
+ trace.payload.push_back(record.referenced_key_exist_in_block);
+ }
+ std::string encoded_trace;
+ TracerHelper::EncodeTrace(trace, &encoded_trace);
+ return trace_writer_->Write(encoded_trace);
+}
+
+Status BlockCacheTraceWriter::WriteHeader() {
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = TraceType::kTraceBegin;
+ PutLengthPrefixedSlice(&trace.payload, kTraceMagic);
+ PutFixed32(&trace.payload, kMajorVersion);
+ PutFixed32(&trace.payload, kMinorVersion);
+ std::string encoded_trace;
+ TracerHelper::EncodeTrace(trace, &encoded_trace);
+ return trace_writer_->Write(encoded_trace);
+}
+
+BlockCacheTraceReader::BlockCacheTraceReader(
+ std::unique_ptr<TraceReader>&& reader)
+ : trace_reader_(std::move(reader)) {}
+
+Status BlockCacheTraceReader::ReadHeader(BlockCacheTraceHeader* header) {
+ assert(header != nullptr);
+ std::string encoded_trace;
+ Status s = trace_reader_->Read(&encoded_trace);
+ if (!s.ok()) {
+ return s;
+ }
+ Trace trace;
+ s = TracerHelper::DecodeTrace(encoded_trace, &trace);
+ if (!s.ok()) {
+ return s;
+ }
+ header->start_time = trace.ts;
+ Slice enc_slice = Slice(trace.payload);
+ Slice magnic_number;
+ if (!GetLengthPrefixedSlice(&enc_slice, &magnic_number)) {
+ return Status::Corruption(
+ "Corrupted header in the trace file: Failed to read the magic number.");
+ }
+ if (magnic_number.ToString() != kTraceMagic) {
+ return Status::Corruption(
+ "Corrupted header in the trace file: Magic number does not match.");
+ }
+ if (!GetFixed32(&enc_slice, &header->rocksdb_major_version)) {
+ return Status::Corruption(
+ "Corrupted header in the trace file: Failed to read rocksdb major "
+ "version number.");
+ }
+ if (!GetFixed32(&enc_slice, &header->rocksdb_minor_version)) {
+ return Status::Corruption(
+ "Corrupted header in the trace file: Failed to read rocksdb minor "
+ "version number.");
+ }
+ // We should have retrieved all information in the header.
+ if (!enc_slice.empty()) {
+ return Status::Corruption(
+ "Corrupted header in the trace file: The length of header is too "
+ "long.");
+ }
+ return Status::OK();
+}
+
+Status BlockCacheTraceReader::ReadAccess(BlockCacheTraceRecord* record) {
+ assert(record);
+ std::string encoded_trace;
+ Status s = trace_reader_->Read(&encoded_trace);
+ if (!s.ok()) {
+ return s;
+ }
+ Trace trace;
+ s = TracerHelper::DecodeTrace(encoded_trace, &trace);
+ if (!s.ok()) {
+ return s;
+ }
+ record->access_timestamp = trace.ts;
+ record->block_type = trace.type;
+ Slice enc_slice = Slice(trace.payload);
+
+ Slice block_key;
+ if (!GetLengthPrefixedSlice(&enc_slice, &block_key)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read block key.");
+ }
+ record->block_key = block_key.ToString();
+ if (!GetFixed64(&enc_slice, &record->block_size)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read block size.");
+ }
+ if (!GetFixed64(&enc_slice, &record->cf_id)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read column family ID.");
+ }
+ Slice cf_name;
+ if (!GetLengthPrefixedSlice(&enc_slice, &cf_name)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read column family name.");
+ }
+ record->cf_name = cf_name.ToString();
+ if (!GetFixed32(&enc_slice, &record->level)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read level.");
+ }
+ if (!GetFixed64(&enc_slice, &record->sst_fd_number)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read SST file number.");
+ }
+ if (enc_slice.empty()) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read caller.");
+ }
+ record->caller = static_cast<TableReaderCaller>(enc_slice[0]);
+ enc_slice.remove_prefix(kCharSize);
+ if (enc_slice.empty()) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read is_cache_hit.");
+ }
+ record->is_cache_hit = static_cast<Boolean>(enc_slice[0]);
+ enc_slice.remove_prefix(kCharSize);
+ if (enc_slice.empty()) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read no_insert.");
+ }
+ record->no_insert = static_cast<Boolean>(enc_slice[0]);
+ enc_slice.remove_prefix(kCharSize);
+ if (BlockCacheTraceHelper::IsGetOrMultiGet(record->caller)) {
+ if (!GetFixed64(&enc_slice, &record->get_id)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read the get id.");
+ }
+ if (enc_slice.empty()) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read "
+ "get_from_user_specified_snapshot.");
+ }
+ record->get_from_user_specified_snapshot =
+ static_cast<Boolean>(enc_slice[0]);
+ enc_slice.remove_prefix(kCharSize);
+ Slice referenced_key;
+ if (!GetLengthPrefixedSlice(&enc_slice, &referenced_key)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read the referenced key.");
+ }
+ record->referenced_key = referenced_key.ToString();
+ }
+ if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(record->block_type,
+ record->caller)) {
+ if (!GetFixed64(&enc_slice, &record->referenced_data_size)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read the referenced data size.");
+ }
+ if (!GetFixed64(&enc_slice, &record->num_keys_in_block)) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read the number of keys in the "
+ "block.");
+ }
+ if (enc_slice.empty()) {
+ return Status::Incomplete(
+ "Incomplete access record: Failed to read "
+ "referenced_key_exist_in_block.");
+ }
+ record->referenced_key_exist_in_block = static_cast<Boolean>(enc_slice[0]);
+ }
+ return Status::OK();
+}
+
+BlockCacheHumanReadableTraceWriter::~BlockCacheHumanReadableTraceWriter() {
+ if (human_readable_trace_file_writer_) {
+ human_readable_trace_file_writer_->Flush();
+ human_readable_trace_file_writer_->Close();
+ }
+}
+
+Status BlockCacheHumanReadableTraceWriter::NewWritableFile(
+ const std::string& human_readable_trace_file_path,
+ ROCKSDB_NAMESPACE::Env* env) {
+ if (human_readable_trace_file_path.empty()) {
+ return Status::InvalidArgument(
+ "The provided human_readable_trace_file_path is null.");
+ }
+ return env->NewWritableFile(human_readable_trace_file_path,
+ &human_readable_trace_file_writer_, EnvOptions());
+}
+
+Status BlockCacheHumanReadableTraceWriter::WriteHumanReadableTraceRecord(
+ const BlockCacheTraceRecord& access, uint64_t block_id,
+ uint64_t get_key_id) {
+ if (!human_readable_trace_file_writer_) {
+ return Status::OK();
+ }
+ int ret = snprintf(
+ trace_record_buffer_, sizeof(trace_record_buffer_),
+ "%" PRIu64 ",%" PRIu64 ",%u,%" PRIu64 ",%" PRIu64 ",%s,%" PRIu32
+ ",%" PRIu64 ",%u,%u,%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%u,%u,%" PRIu64
+ ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 "\n",
+ access.access_timestamp, block_id, access.block_type, access.block_size,
+ access.cf_id, access.cf_name.c_str(), access.level, access.sst_fd_number,
+ access.caller, access.no_insert, access.get_id, get_key_id,
+ access.referenced_data_size, access.is_cache_hit,
+ access.referenced_key_exist_in_block, access.num_keys_in_block,
+ BlockCacheTraceHelper::GetTableId(access),
+ BlockCacheTraceHelper::GetSequenceNumber(access),
+ static_cast<uint64_t>(access.block_key.size()),
+ static_cast<uint64_t>(access.referenced_key.size()),
+ BlockCacheTraceHelper::GetBlockOffsetInFile(access));
+ if (ret < 0) {
+ return Status::IOError("failed to format the output");
+ }
+ std::string printout(trace_record_buffer_);
+ return human_readable_trace_file_writer_->Append(printout);
+}
+
+BlockCacheHumanReadableTraceReader::BlockCacheHumanReadableTraceReader(
+ const std::string& trace_file_path)
+ : BlockCacheTraceReader(/*trace_reader=*/nullptr) {
+ human_readable_trace_reader_.open(trace_file_path, std::ifstream::in);
+}
+
+BlockCacheHumanReadableTraceReader::~BlockCacheHumanReadableTraceReader() {
+ human_readable_trace_reader_.close();
+}
+
+Status BlockCacheHumanReadableTraceReader::ReadHeader(
+ BlockCacheTraceHeader* /*header*/) {
+ return Status::OK();
+}
+
+Status BlockCacheHumanReadableTraceReader::ReadAccess(
+ BlockCacheTraceRecord* record) {
+ std::string line;
+ if (!std::getline(human_readable_trace_reader_, line)) {
+ return Status::Incomplete("No more records to read.");
+ }
+ std::stringstream ss(line);
+ std::vector<std::string> record_strs;
+ while (ss.good()) {
+ std::string substr;
+ getline(ss, substr, ',');
+ record_strs.push_back(substr);
+ }
+ if (record_strs.size() != 21) {
+ return Status::Incomplete("Records format is wrong.");
+ }
+
+ record->access_timestamp = ParseUint64(record_strs[0]);
+ uint64_t block_key = ParseUint64(record_strs[1]);
+ record->block_type = static_cast<TraceType>(ParseUint64(record_strs[2]));
+ record->block_size = ParseUint64(record_strs[3]);
+ record->cf_id = ParseUint64(record_strs[4]);
+ record->cf_name = record_strs[5];
+ record->level = static_cast<uint32_t>(ParseUint64(record_strs[6]));
+ record->sst_fd_number = ParseUint64(record_strs[7]);
+ record->caller = static_cast<TableReaderCaller>(ParseUint64(record_strs[8]));
+ record->no_insert = static_cast<Boolean>(ParseUint64(record_strs[9]));
+ record->get_id = ParseUint64(record_strs[10]);
+ uint64_t get_key_id = ParseUint64(record_strs[11]);
+
+ record->referenced_data_size = ParseUint64(record_strs[12]);
+ record->is_cache_hit = static_cast<Boolean>(ParseUint64(record_strs[13]));
+ record->referenced_key_exist_in_block =
+ static_cast<Boolean>(ParseUint64(record_strs[14]));
+ record->num_keys_in_block = ParseUint64(record_strs[15]);
+ uint64_t table_id = ParseUint64(record_strs[16]);
+ if (table_id > 0) {
+ // Decrement since valid table id in the trace file equals traced table id
+ // + 1.
+ table_id -= 1;
+ }
+ uint64_t get_sequence_number = ParseUint64(record_strs[17]);
+ if (get_sequence_number > 0) {
+ record->get_from_user_specified_snapshot = Boolean::kTrue;
+ // Decrement since valid seq number in the trace file equals traced seq
+ // number + 1.
+ get_sequence_number -= 1;
+ }
+ uint64_t block_key_size = ParseUint64(record_strs[18]);
+ uint64_t get_key_size = ParseUint64(record_strs[19]);
+ uint64_t block_offset = ParseUint64(record_strs[20]);
+
+ std::string tmp_block_key;
+ PutVarint64(&tmp_block_key, block_key);
+ PutVarint64(&tmp_block_key, block_offset);
+ // Append 1 until the size is the same as traced block key size.
+ while (record->block_key.size() < block_key_size - tmp_block_key.size()) {
+ record->block_key += "1";
+ }
+ record->block_key += tmp_block_key;
+
+ if (get_key_id != 0) {
+ std::string tmp_get_key;
+ PutFixed64(&tmp_get_key, get_key_id);
+ PutFixed64(&tmp_get_key, get_sequence_number << 8);
+ PutFixed32(&record->referenced_key, static_cast<uint32_t>(table_id));
+ // Append 1 until the size is the same as traced key size.
+ while (record->referenced_key.size() < get_key_size - tmp_get_key.size()) {
+ record->referenced_key += "1";
+ }
+ record->referenced_key += tmp_get_key;
+ }
+ return Status::OK();
+}
+
+BlockCacheTracer::BlockCacheTracer() { writer_.store(nullptr); }
+
+BlockCacheTracer::~BlockCacheTracer() { EndTrace(); }
+
+Status BlockCacheTracer::StartTrace(
+ Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer) {
+ InstrumentedMutexLock lock_guard(&trace_writer_mutex_);
+ if (writer_.load()) {
+ return Status::Busy();
+ }
+ get_id_counter_.store(1);
+ trace_options_ = trace_options;
+ writer_.store(
+ new BlockCacheTraceWriter(env, trace_options, std::move(trace_writer)));
+ return writer_.load()->WriteHeader();
+}
+
+void BlockCacheTracer::EndTrace() {
+ InstrumentedMutexLock lock_guard(&trace_writer_mutex_);
+ if (!writer_.load()) {
+ return;
+ }
+ delete writer_.load();
+ writer_.store(nullptr);
+}
+
+Status BlockCacheTracer::WriteBlockAccess(const BlockCacheTraceRecord& record,
+ const Slice& block_key,
+ const Slice& cf_name,
+ const Slice& referenced_key) {
+ if (!writer_.load() || !ShouldTrace(block_key, trace_options_)) {
+ return Status::OK();
+ }
+ InstrumentedMutexLock lock_guard(&trace_writer_mutex_);
+ if (!writer_.load()) {
+ return Status::OK();
+ }
+ return writer_.load()->WriteBlockAccess(record, block_key, cf_name,
+ referenced_key);
+}
+
+uint64_t BlockCacheTracer::NextGetId() {
+ if (!writer_.load(std::memory_order_relaxed)) {
+ return BlockCacheTraceHelper::kReservedGetId;
+ }
+ uint64_t prev_value = get_id_counter_.fetch_add(1);
+ if (prev_value == BlockCacheTraceHelper::kReservedGetId) {
+ // fetch and add again.
+ return get_id_counter_.fetch_add(1);
+ }
+ return prev_value;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.h b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.h
new file mode 100644
index 00000000..5849273d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer.h
@@ -0,0 +1,294 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <fstream>
+
+#include "monitoring/instrumented_mutex.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/trace_reader_writer.h"
+#include "table/table_reader_caller.h"
+#include "trace_replay/trace_replay.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+extern const uint64_t kMicrosInSecond;
+extern const uint64_t kSecondInMinute;
+extern const uint64_t kSecondInHour;
+
+struct BlockCacheTraceRecord;
+
+class BlockCacheTraceHelper {
+ public:
+ static bool IsGetOrMultiGetOnDataBlock(TraceType block_type,
+ TableReaderCaller caller);
+ static bool IsGetOrMultiGet(TableReaderCaller caller);
+ static bool IsUserAccess(TableReaderCaller caller);
+ // Row key is a concatenation of the access's fd_number and the referenced
+ // user key.
+ static std::string ComputeRowKey(const BlockCacheTraceRecord& access);
+ // The first four bytes of the referenced key in a Get request is the table
+ // id.
+ static uint64_t GetTableId(const BlockCacheTraceRecord& access);
+ // The sequence number of a get request is the last part of the referenced
+ // key.
+ static uint64_t GetSequenceNumber(const BlockCacheTraceRecord& access);
+ // Block offset in a file is the last varint64 in the block key.
+ static uint64_t GetBlockOffsetInFile(const BlockCacheTraceRecord& access);
+
+ static const std::string kUnknownColumnFamilyName;
+ static const uint64_t kReservedGetId;
+};
+
+// Lookup context for tracing block cache accesses.
+// We trace block accesses at five places:
+// 1. BlockBasedTable::GetFilter
+// 2. BlockBasedTable::GetUncompressedDict.
+// 3. BlockBasedTable::MaybeReadAndLoadToCache. (To trace access on data, index,
+// and range deletion block.)
+// 4. BlockBasedTable::Get. (To trace the referenced key and whether the
+// referenced key exists in a fetched data block.)
+// 5. BlockBasedTable::MultiGet. (To trace the referenced key and whether the
+// referenced key exists in a fetched data block.)
+// The context is created at:
+// 1. BlockBasedTable::Get. (kUserGet)
+// 2. BlockBasedTable::MultiGet. (kUserMGet)
+// 3. BlockBasedTable::NewIterator. (either kUserIterator, kCompaction, or
+// external SST ingestion calls this function.)
+// 4. BlockBasedTable::Open. (kPrefetch)
+// 5. Index/Filter::CacheDependencies. (kPrefetch)
+// 6. BlockBasedTable::ApproximateOffsetOf. (kCompaction or
+// kUserApproximateSize).
+struct BlockCacheLookupContext {
+ BlockCacheLookupContext(const TableReaderCaller& _caller) : caller(_caller) {}
+ BlockCacheLookupContext(const TableReaderCaller& _caller, uint64_t _get_id,
+ bool _get_from_user_specified_snapshot)
+ : caller(_caller),
+ get_id(_get_id),
+ get_from_user_specified_snapshot(_get_from_user_specified_snapshot) {}
+ const TableReaderCaller caller;
+ // These are populated when we perform lookup/insert on block cache. The block
+ // cache tracer uses these inforation when logging the block access at
+ // BlockBasedTable::GET and BlockBasedTable::MultiGet.
+ bool is_cache_hit = false;
+ bool no_insert = false;
+ TraceType block_type = TraceType::kTraceMax;
+ uint64_t block_size = 0;
+ std::string block_key;
+ uint64_t num_keys_in_block = 0;
+ // The unique id associated with Get and MultiGet. This enables us to track
+ // how many blocks a Get/MultiGet request accesses. We can also measure the
+ // impact of row cache vs block cache.
+ uint64_t get_id = 0;
+ std::string referenced_key;
+ bool get_from_user_specified_snapshot = false;
+
+ void FillLookupContext(bool _is_cache_hit, bool _no_insert,
+ TraceType _block_type, uint64_t _block_size,
+ const std::string& _block_key,
+ uint64_t _num_keys_in_block) {
+ is_cache_hit = _is_cache_hit;
+ no_insert = _no_insert;
+ block_type = _block_type;
+ block_size = _block_size;
+ block_key = _block_key;
+ num_keys_in_block = _num_keys_in_block;
+ }
+};
+
+enum Boolean : char { kTrue = 1, kFalse = 0 };
+
+struct BlockCacheTraceRecord {
+ // Required fields for all accesses.
+ uint64_t access_timestamp = 0;
+ std::string block_key;
+ TraceType block_type = TraceType::kTraceMax;
+ uint64_t block_size = 0;
+ uint64_t cf_id = 0;
+ std::string cf_name;
+ uint32_t level = 0;
+ uint64_t sst_fd_number = 0;
+ TableReaderCaller caller = TableReaderCaller::kMaxBlockCacheLookupCaller;
+ Boolean is_cache_hit = Boolean::kFalse;
+ Boolean no_insert = Boolean::kFalse;
+ // Required field for Get and MultiGet
+ uint64_t get_id = BlockCacheTraceHelper::kReservedGetId;
+ Boolean get_from_user_specified_snapshot = Boolean::kFalse;
+ std::string referenced_key;
+ // Required fields for data block and user Get/Multi-Get only.
+ uint64_t referenced_data_size = 0;
+ uint64_t num_keys_in_block = 0;
+ Boolean referenced_key_exist_in_block = Boolean::kFalse;
+
+ BlockCacheTraceRecord() {}
+
+ BlockCacheTraceRecord(
+ uint64_t _access_timestamp, std::string _block_key, TraceType _block_type,
+ uint64_t _block_size, uint64_t _cf_id, std::string _cf_name,
+ uint32_t _level, uint64_t _sst_fd_number, TableReaderCaller _caller,
+ bool _is_cache_hit, bool _no_insert,
+ uint64_t _get_id = BlockCacheTraceHelper::kReservedGetId,
+ bool _get_from_user_specified_snapshot = false,
+ std::string _referenced_key = "", uint64_t _referenced_data_size = 0,
+ uint64_t _num_keys_in_block = 0,
+ bool _referenced_key_exist_in_block = false)
+ : access_timestamp(_access_timestamp),
+ block_key(_block_key),
+ block_type(_block_type),
+ block_size(_block_size),
+ cf_id(_cf_id),
+ cf_name(_cf_name),
+ level(_level),
+ sst_fd_number(_sst_fd_number),
+ caller(_caller),
+ is_cache_hit(_is_cache_hit ? Boolean::kTrue : Boolean::kFalse),
+ no_insert(_no_insert ? Boolean::kTrue : Boolean::kFalse),
+ get_id(_get_id),
+ get_from_user_specified_snapshot(_get_from_user_specified_snapshot
+ ? Boolean::kTrue
+ : Boolean::kFalse),
+ referenced_key(_referenced_key),
+ referenced_data_size(_referenced_data_size),
+ num_keys_in_block(_num_keys_in_block),
+ referenced_key_exist_in_block(
+ _referenced_key_exist_in_block ? Boolean::kTrue : Boolean::kFalse) {
+ }
+};
+
+struct BlockCacheTraceHeader {
+ uint64_t start_time;
+ uint32_t rocksdb_major_version;
+ uint32_t rocksdb_minor_version;
+};
+
+// BlockCacheTraceWriter captures all RocksDB block cache accesses using a
+// user-provided TraceWriter. Every RocksDB operation is written as a single
+// trace. Each trace will have a timestamp and type, followed by the trace
+// payload.
+class BlockCacheTraceWriter {
+ public:
+ BlockCacheTraceWriter(Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer);
+ ~BlockCacheTraceWriter() = default;
+ // No copy and move.
+ BlockCacheTraceWriter(const BlockCacheTraceWriter&) = delete;
+ BlockCacheTraceWriter& operator=(const BlockCacheTraceWriter&) = delete;
+ BlockCacheTraceWriter(BlockCacheTraceWriter&&) = delete;
+ BlockCacheTraceWriter& operator=(BlockCacheTraceWriter&&) = delete;
+
+ // Pass Slice references to avoid copy.
+ Status WriteBlockAccess(const BlockCacheTraceRecord& record,
+ const Slice& block_key, const Slice& cf_name,
+ const Slice& referenced_key);
+
+ // Write a trace header at the beginning, typically on initiating a trace,
+ // with some metadata like a magic number and RocksDB version.
+ Status WriteHeader();
+
+ private:
+ Env* env_;
+ TraceOptions trace_options_;
+ std::unique_ptr<TraceWriter> trace_writer_;
+};
+
+// Write a trace record in human readable format, see
+// https://github.com/facebook/rocksdb/wiki/Block-cache-analysis-and-simulation-tools#trace-format
+// for details.
+class BlockCacheHumanReadableTraceWriter {
+ public:
+ ~BlockCacheHumanReadableTraceWriter();
+
+ Status NewWritableFile(const std::string& human_readable_trace_file_path,
+ ROCKSDB_NAMESPACE::Env* env);
+
+ Status WriteHumanReadableTraceRecord(const BlockCacheTraceRecord& access,
+ uint64_t block_id, uint64_t get_key_id);
+
+ private:
+ char trace_record_buffer_[1024 * 1024];
+ std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>
+ human_readable_trace_file_writer_;
+};
+
+// BlockCacheTraceReader helps read the trace file generated by
+// BlockCacheTraceWriter using a user provided TraceReader.
+class BlockCacheTraceReader {
+ public:
+ BlockCacheTraceReader(std::unique_ptr<TraceReader>&& reader);
+ ~BlockCacheTraceReader() = default;
+ // No copy and move.
+ BlockCacheTraceReader(const BlockCacheTraceReader&) = delete;
+ BlockCacheTraceReader& operator=(const BlockCacheTraceReader&) = delete;
+ BlockCacheTraceReader(BlockCacheTraceReader&&) = delete;
+ BlockCacheTraceReader& operator=(BlockCacheTraceReader&&) = delete;
+
+ Status ReadHeader(BlockCacheTraceHeader* header);
+
+ Status ReadAccess(BlockCacheTraceRecord* record);
+
+ private:
+ std::unique_ptr<TraceReader> trace_reader_;
+};
+
+// Read a trace record in human readable format, see
+// https://github.com/facebook/rocksdb/wiki/Block-cache-analysis-and-simulation-tools#trace-format
+// for detailed.
+class BlockCacheHumanReadableTraceReader : public BlockCacheTraceReader {
+ public:
+ BlockCacheHumanReadableTraceReader(const std::string& trace_file_path);
+
+ ~BlockCacheHumanReadableTraceReader();
+
+ Status ReadHeader(BlockCacheTraceHeader* header);
+
+ Status ReadAccess(BlockCacheTraceRecord* record);
+
+ private:
+ std::ifstream human_readable_trace_reader_;
+};
+
+// A block cache tracer. It downsamples the accesses according to
+// trace_options and uses BlockCacheTraceWriter to write the access record to
+// the trace file.
+class BlockCacheTracer {
+ public:
+ BlockCacheTracer();
+ ~BlockCacheTracer();
+ // No copy and move.
+ BlockCacheTracer(const BlockCacheTracer&) = delete;
+ BlockCacheTracer& operator=(const BlockCacheTracer&) = delete;
+ BlockCacheTracer(BlockCacheTracer&&) = delete;
+ BlockCacheTracer& operator=(BlockCacheTracer&&) = delete;
+
+ // Start writing block cache accesses to the trace_writer.
+ Status StartTrace(Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer);
+
+ // Stop writing block cache accesses to the trace_writer.
+ void EndTrace();
+
+ bool is_tracing_enabled() const {
+ return writer_.load(std::memory_order_relaxed);
+ }
+
+ Status WriteBlockAccess(const BlockCacheTraceRecord& record,
+ const Slice& block_key, const Slice& cf_name,
+ const Slice& referenced_key);
+
+ // GetId cycles from 1 to port::kMaxUint64.
+ uint64_t NextGetId();
+
+ private:
+ TraceOptions trace_options_;
+ // A mutex protects the writer_.
+ InstrumentedMutex trace_writer_mutex_;
+ std::atomic<BlockCacheTraceWriter*> writer_;
+ std::atomic<uint64_t> get_id_counter_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer_test.cc b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer_test.cc
new file mode 100644
index 00000000..b2960089
--- /dev/null
+++ b/storage/rocksdb/rocksdb/trace_replay/block_cache_tracer_test.cc
@@ -0,0 +1,378 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/env.h"
+#include "rocksdb/status.h"
+#include "rocksdb/trace_reader_writer.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();
+ 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);
+ }
+
+ 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 = env_->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 = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ // 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 = Boolean::kTrue;
+ // 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 = Boolean::kTrue;
+ 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 = env_->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 = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ record.referenced_key = kRefKeyPrefix + std::to_string(key_id);
+ record.referenced_key_exist_in_block = Boolean::kTrue;
+ 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_EQ(Boolean::kFalse, record.is_cache_hit);
+ ASSERT_EQ(Boolean::kFalse, record.no_insert);
+ if (record.caller == TableReaderCaller::kUserGet ||
+ record.caller == TableReaderCaller::kUserMultiGet) {
+ ASSERT_EQ(key_id + 1, record.get_id);
+ ASSERT_EQ(Boolean::kTrue, 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_EQ(Boolean::kFalse, 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_EQ(Boolean::kTrue, 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_EQ(Boolean::kFalse, record.referenced_key_exist_in_block);
+ ASSERT_EQ(0, record.num_keys_in_block);
+ ASSERT_EQ(0, record.referenced_data_size);
+ }
+ }
+
+ Env* env_;
+ EnvOptions env_options_;
+ std::string trace_file_path_;
+ std::string test_path_;
+};
+
+TEST_F(BlockCacheTracerTest, AtomicWriteBeforeStartTrace) {
+ BlockCacheTraceRecord record = GenerateAccessRecord();
+ {
+ std::unique_ptr<TraceWriter> 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<TraceReader> 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();
+ {
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ BlockCacheTracer writer;
+ ASSERT_OK(writer.StartTrace(env_, trace_opt, std::move(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<TraceReader> 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, header.rocksdb_major_version);
+ ASSERT_EQ(kMinorVersion, header.rocksdb_minor_version);
+ VerifyAccess(&reader, 0, TraceType::kBlockTraceDataBlock, 1);
+ ASSERT_NOK(reader.ReadAccess(&record));
+ }
+}
+
+TEST_F(BlockCacheTracerTest, ConsecutiveStartTrace) {
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(
+ NewFileTraceWriter(env_, env_options_, trace_file_path_, &trace_writer));
+ BlockCacheTracer writer;
+ ASSERT_OK(writer.StartTrace(env_, trace_opt, std::move(trace_writer)));
+ ASSERT_NOK(writer.StartTrace(env_, trace_opt, std::move(trace_writer)));
+ ASSERT_OK(env_->FileExists(trace_file_path_));
+}
+
+TEST_F(BlockCacheTracerTest, AtomicNoWriteAfterEndTrace) {
+ BlockCacheTraceRecord record = GenerateAccessRecord();
+ {
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ BlockCacheTracer writer;
+ ASSERT_OK(writer.StartTrace(env_, trace_opt, std::move(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<TraceReader> 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, header.rocksdb_major_version);
+ ASSERT_EQ(kMinorVersion, header.rocksdb_minor_version);
+ VerifyAccess(&reader, 0, TraceType::kBlockTraceDataBlock, 1);
+ ASSERT_NOK(reader.ReadAccess(&record));
+ }
+}
+
+TEST_F(BlockCacheTracerTest, NextGetId) {
+ BlockCacheTracer writer;
+ {
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ // 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(env_, trace_opt, std::move(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.
+ {
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ ASSERT_OK(writer.StartTrace(env_, trace_opt, std::move(trace_writer)));
+ ASSERT_EQ(1, writer.NextGetId());
+ }
+}
+
+TEST_F(BlockCacheTracerTest, MixedBlocks) {
+ {
+ // Generate a trace file containing a mix of blocks.
+ TraceOptions trace_opt;
+ std::unique_ptr<TraceWriter> trace_writer;
+ ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_,
+ &trace_writer));
+ BlockCacheTraceWriter writer(env_, trace_opt, std::move(trace_writer));
+ ASSERT_OK(writer.WriteHeader());
+ // Write blocks of different types.
+ WriteBlockAccess(&writer, 0, TraceType::kBlockTraceUncompressionDictBlock,
+ 10);
+ WriteBlockAccess(&writer, 10, TraceType::kBlockTraceDataBlock, 10);
+ WriteBlockAccess(&writer, 20, TraceType::kBlockTraceFilterBlock, 10);
+ WriteBlockAccess(&writer, 30, TraceType::kBlockTraceIndexBlock, 10);
+ WriteBlockAccess(&writer, 40, TraceType::kBlockTraceRangeDeletionBlock, 10);
+ ASSERT_OK(env_->FileExists(trace_file_path_));
+ }
+
+ {
+ // Verify trace file is generated correctly.
+ std::unique_ptr<TraceReader> 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, header.rocksdb_major_version);
+ ASSERT_EQ(kMinorVersion, 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 = Boolean::kTrue;
+ 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_EQ(Boolean::kFalse, read_record.is_cache_hit);
+ ASSERT_EQ(Boolean::kFalse, read_record.no_insert);
+ ASSERT_EQ(1, read_record.get_id);
+ ASSERT_EQ(Boolean::kTrue, read_record.get_from_user_specified_snapshot);
+ ASSERT_EQ(Boolean::kTrue, 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/trace_replay/trace_replay.cc b/storage/rocksdb/rocksdb/trace_replay/trace_replay.cc
new file mode 100644
index 00000000..a0f9a504
--- /dev/null
+++ b/storage/rocksdb/rocksdb/trace_replay/trace_replay.cc
@@ -0,0 +1,485 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/trace_replay.h"
+
+#include <chrono>
+#include <sstream>
+#include <thread>
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/write_batch.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+#include "util/threadpool_imp.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const std::string kTraceMagic = "feedcafedeadbeef";
+
+namespace {
+void EncodeCFAndKey(std::string* dst, uint32_t cf_id, const Slice& key) {
+ PutFixed32(dst, cf_id);
+ PutLengthPrefixedSlice(dst, key);
+}
+
+void DecodeCFAndKey(std::string& buffer, uint32_t* cf_id, Slice* key) {
+ Slice buf(buffer);
+ GetFixed32(&buf, cf_id);
+ GetLengthPrefixedSlice(&buf, key);
+}
+} // namespace
+
+void TracerHelper::EncodeTrace(const Trace& trace, std::string* encoded_trace) {
+ assert(encoded_trace);
+ PutFixed64(encoded_trace, trace.ts);
+ encoded_trace->push_back(trace.type);
+ PutFixed32(encoded_trace, static_cast<uint32_t>(trace.payload.size()));
+ encoded_trace->append(trace.payload);
+}
+
+Status TracerHelper::DecodeTrace(const std::string& encoded_trace,
+ Trace* trace) {
+ assert(trace != nullptr);
+ Slice enc_slice = Slice(encoded_trace);
+ if (!GetFixed64(&enc_slice, &trace->ts)) {
+ return Status::Incomplete("Decode trace string failed");
+ }
+ if (enc_slice.size() < kTraceTypeSize + kTracePayloadLengthSize) {
+ return Status::Incomplete("Decode trace string failed");
+ }
+ trace->type = static_cast<TraceType>(enc_slice[0]);
+ enc_slice.remove_prefix(kTraceTypeSize + kTracePayloadLengthSize);
+ trace->payload = enc_slice.ToString();
+ return Status::OK();
+}
+
+Tracer::Tracer(Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer)
+ : env_(env),
+ trace_options_(trace_options),
+ trace_writer_(std::move(trace_writer)),
+ trace_request_count_ (0) {
+ WriteHeader();
+}
+
+Tracer::~Tracer() { trace_writer_.reset(); }
+
+Status Tracer::Write(WriteBatch* write_batch) {
+ TraceType trace_type = kTraceWrite;
+ if (ShouldSkipTrace(trace_type)) {
+ return Status::OK();
+ }
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = trace_type;
+ trace.payload = write_batch->Data();
+ return WriteTrace(trace);
+}
+
+Status Tracer::Get(ColumnFamilyHandle* column_family, const Slice& key) {
+ TraceType trace_type = kTraceGet;
+ if (ShouldSkipTrace(trace_type)) {
+ return Status::OK();
+ }
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = trace_type;
+ EncodeCFAndKey(&trace.payload, column_family->GetID(), key);
+ return WriteTrace(trace);
+}
+
+Status Tracer::IteratorSeek(const uint32_t& cf_id, const Slice& key) {
+ TraceType trace_type = kTraceIteratorSeek;
+ if (ShouldSkipTrace(trace_type)) {
+ return Status::OK();
+ }
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = trace_type;
+ EncodeCFAndKey(&trace.payload, cf_id, key);
+ return WriteTrace(trace);
+}
+
+Status Tracer::IteratorSeekForPrev(const uint32_t& cf_id, const Slice& key) {
+ TraceType trace_type = kTraceIteratorSeekForPrev;
+ if (ShouldSkipTrace(trace_type)) {
+ return Status::OK();
+ }
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = trace_type;
+ EncodeCFAndKey(&trace.payload, cf_id, key);
+ return WriteTrace(trace);
+}
+
+bool Tracer::ShouldSkipTrace(const TraceType& trace_type) {
+ if (IsTraceFileOverMax()) {
+ return true;
+ }
+ if ((trace_options_.filter & kTraceFilterGet
+ && trace_type == kTraceGet)
+ || (trace_options_.filter & kTraceFilterWrite
+ && trace_type == kTraceWrite)) {
+ return true;
+ }
+ ++trace_request_count_;
+ if (trace_request_count_ < trace_options_.sampling_frequency) {
+ return true;
+ }
+ trace_request_count_ = 0;
+ return false;
+}
+
+bool Tracer::IsTraceFileOverMax() {
+ uint64_t trace_file_size = trace_writer_->GetFileSize();
+ return (trace_file_size > trace_options_.max_trace_file_size);
+}
+
+Status Tracer::WriteHeader() {
+ std::ostringstream s;
+ s << kTraceMagic << "\t"
+ << "Trace Version: 0.1\t"
+ << "RocksDB Version: " << kMajorVersion << "." << kMinorVersion << "\t"
+ << "Format: Timestamp OpType Payload\n";
+ std::string header(s.str());
+
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = kTraceBegin;
+ trace.payload = header;
+ return WriteTrace(trace);
+}
+
+Status Tracer::WriteFooter() {
+ Trace trace;
+ trace.ts = env_->NowMicros();
+ trace.type = kTraceEnd;
+ trace.payload = "";
+ return WriteTrace(trace);
+}
+
+Status Tracer::WriteTrace(const Trace& trace) {
+ std::string encoded_trace;
+ TracerHelper::EncodeTrace(trace, &encoded_trace);
+ return trace_writer_->Write(Slice(encoded_trace));
+}
+
+Status Tracer::Close() { return WriteFooter(); }
+
+Replayer::Replayer(DB* db, const std::vector<ColumnFamilyHandle*>& handles,
+ std::unique_ptr<TraceReader>&& reader)
+ : trace_reader_(std::move(reader)) {
+ assert(db != nullptr);
+ db_ = static_cast<DBImpl*>(db->GetRootDB());
+ env_ = Env::Default();
+ for (ColumnFamilyHandle* cfh : handles) {
+ cf_map_[cfh->GetID()] = cfh;
+ }
+ fast_forward_ = 1;
+}
+
+Replayer::~Replayer() { trace_reader_.reset(); }
+
+Status Replayer::SetFastForward(uint32_t fast_forward) {
+ Status s;
+ if (fast_forward < 1) {
+ s = Status::InvalidArgument("Wrong fast forward speed!");
+ } else {
+ fast_forward_ = fast_forward;
+ s = Status::OK();
+ }
+ return s;
+}
+
+Status Replayer::Replay() {
+ Status s;
+ Trace header;
+ s = ReadHeader(&header);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::chrono::system_clock::time_point replay_epoch =
+ std::chrono::system_clock::now();
+ WriteOptions woptions;
+ ReadOptions roptions;
+ Trace trace;
+ uint64_t ops = 0;
+ Iterator* single_iter = nullptr;
+ while (s.ok()) {
+ trace.reset();
+ s = ReadTrace(&trace);
+ if (!s.ok()) {
+ break;
+ }
+
+ std::this_thread::sleep_until(
+ replay_epoch +
+ std::chrono::microseconds((trace.ts - header.ts) / fast_forward_));
+ if (trace.type == kTraceWrite) {
+ WriteBatch batch(trace.payload);
+ db_->Write(woptions, &batch);
+ ops++;
+ } else if (trace.type == kTraceGet) {
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(trace.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map_.find(cf_id) == cf_map_.end()) {
+ return Status::Corruption("Invalid Column Family ID.");
+ }
+
+ std::string value;
+ if (cf_id == 0) {
+ db_->Get(roptions, key, &value);
+ } else {
+ db_->Get(roptions, cf_map_[cf_id], key, &value);
+ }
+ ops++;
+ } else if (trace.type == kTraceIteratorSeek) {
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(trace.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map_.find(cf_id) == cf_map_.end()) {
+ return Status::Corruption("Invalid Column Family ID.");
+ }
+
+ if (cf_id == 0) {
+ single_iter = db_->NewIterator(roptions);
+ } else {
+ single_iter = db_->NewIterator(roptions, cf_map_[cf_id]);
+ }
+ single_iter->Seek(key);
+ ops++;
+ delete single_iter;
+ } else if (trace.type == kTraceIteratorSeekForPrev) {
+ // Currently, only support to call the Seek()
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(trace.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map_.find(cf_id) == cf_map_.end()) {
+ return Status::Corruption("Invalid Column Family ID.");
+ }
+
+ if (cf_id == 0) {
+ single_iter = db_->NewIterator(roptions);
+ } else {
+ single_iter = db_->NewIterator(roptions, cf_map_[cf_id]);
+ }
+ single_iter->SeekForPrev(key);
+ ops++;
+ delete single_iter;
+ } else if (trace.type == kTraceEnd) {
+ // Do nothing for now.
+ // TODO: Add some validations later.
+ break;
+ }
+ }
+
+ if (s.IsIncomplete()) {
+ // Reaching eof returns Incomplete status at the moment.
+ // Could happen when killing a process without calling EndTrace() API.
+ // TODO: Add better error handling.
+ return Status::OK();
+ }
+ return s;
+}
+
+// The trace can be replayed with multithread by configurnge the number of
+// threads in the thread pool. Trace records are read from the trace file
+// sequentially and the corresponding queries are scheduled in the task
+// queue based on the timestamp. Currently, we support Write_batch (Put,
+// Delete, SingleDelete, DeleteRange), Get, Iterator (Seek and SeekForPrev).
+Status Replayer::MultiThreadReplay(uint32_t threads_num) {
+ Status s;
+ Trace header;
+ s = ReadHeader(&header);
+ if (!s.ok()) {
+ return s;
+ }
+
+ ThreadPoolImpl thread_pool;
+ thread_pool.SetHostEnv(env_);
+
+ if (threads_num > 1) {
+ thread_pool.SetBackgroundThreads(static_cast<int>(threads_num));
+ } else {
+ thread_pool.SetBackgroundThreads(1);
+ }
+
+ std::chrono::system_clock::time_point replay_epoch =
+ std::chrono::system_clock::now();
+ WriteOptions woptions;
+ ReadOptions roptions;
+ uint64_t ops = 0;
+ while (s.ok()) {
+ std::unique_ptr<ReplayerWorkerArg> ra(new ReplayerWorkerArg);
+ ra->db = db_;
+ s = ReadTrace(&(ra->trace_entry));
+ if (!s.ok()) {
+ break;
+ }
+ ra->woptions = woptions;
+ ra->roptions = roptions;
+
+ std::this_thread::sleep_until(
+ replay_epoch + std::chrono::microseconds(
+ (ra->trace_entry.ts - header.ts) / fast_forward_));
+ if (ra->trace_entry.type == kTraceWrite) {
+ thread_pool.Schedule(&Replayer::BGWorkWriteBatch, ra.release(), nullptr,
+ nullptr);
+ ops++;
+ } else if (ra->trace_entry.type == kTraceGet) {
+ thread_pool.Schedule(&Replayer::BGWorkGet, ra.release(), nullptr,
+ nullptr);
+ ops++;
+ } else if (ra->trace_entry.type == kTraceIteratorSeek) {
+ thread_pool.Schedule(&Replayer::BGWorkIterSeek, ra.release(), nullptr,
+ nullptr);
+ ops++;
+ } else if (ra->trace_entry.type == kTraceIteratorSeekForPrev) {
+ thread_pool.Schedule(&Replayer::BGWorkIterSeekForPrev, ra.release(),
+ nullptr, nullptr);
+ ops++;
+ } else if (ra->trace_entry.type == kTraceEnd) {
+ // Do nothing for now.
+ // TODO: Add some validations later.
+ break;
+ } else {
+ // Other trace entry types that are not implemented for replay.
+ // To finish the replay, we continue the process.
+ continue;
+ }
+ }
+
+ if (s.IsIncomplete()) {
+ // Reaching eof returns Incomplete status at the moment.
+ // Could happen when killing a process without calling EndTrace() API.
+ // TODO: Add better error handling.
+ s = Status::OK();
+ }
+ thread_pool.JoinAllThreads();
+ return s;
+}
+
+Status Replayer::ReadHeader(Trace* header) {
+ assert(header != nullptr);
+ Status s = ReadTrace(header);
+ if (!s.ok()) {
+ return s;
+ }
+ if (header->type != kTraceBegin) {
+ return Status::Corruption("Corrupted trace file. Incorrect header.");
+ }
+ if (header->payload.substr(0, kTraceMagic.length()) != kTraceMagic) {
+ return Status::Corruption("Corrupted trace file. Incorrect magic.");
+ }
+
+ return s;
+}
+
+Status Replayer::ReadFooter(Trace* footer) {
+ assert(footer != nullptr);
+ Status s = ReadTrace(footer);
+ if (!s.ok()) {
+ return s;
+ }
+ if (footer->type != kTraceEnd) {
+ return Status::Corruption("Corrupted trace file. Incorrect footer.");
+ }
+
+ // TODO: Add more validations later
+ return s;
+}
+
+Status Replayer::ReadTrace(Trace* trace) {
+ assert(trace != nullptr);
+ std::string encoded_trace;
+ Status s = trace_reader_->Read(&encoded_trace);
+ if (!s.ok()) {
+ return s;
+ }
+ return TracerHelper::DecodeTrace(encoded_trace, trace);
+}
+
+void Replayer::BGWorkGet(void* arg) {
+ std::unique_ptr<ReplayerWorkerArg> ra(
+ reinterpret_cast<ReplayerWorkerArg*>(arg));
+ auto cf_map = static_cast<std::unordered_map<uint32_t, ColumnFamilyHandle*>*>(
+ ra->cf_map);
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(ra->trace_entry.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map->find(cf_id) == cf_map->end()) {
+ return;
+ }
+
+ std::string value;
+ if (cf_id == 0) {
+ ra->db->Get(ra->roptions, key, &value);
+ } else {
+ ra->db->Get(ra->roptions, (*cf_map)[cf_id], key, &value);
+ }
+
+ return;
+}
+
+void Replayer::BGWorkWriteBatch(void* arg) {
+ std::unique_ptr<ReplayerWorkerArg> ra(
+ reinterpret_cast<ReplayerWorkerArg*>(arg));
+ WriteBatch batch(ra->trace_entry.payload);
+ ra->db->Write(ra->woptions, &batch);
+ return;
+}
+
+void Replayer::BGWorkIterSeek(void* arg) {
+ std::unique_ptr<ReplayerWorkerArg> ra(
+ reinterpret_cast<ReplayerWorkerArg*>(arg));
+ auto cf_map = static_cast<std::unordered_map<uint32_t, ColumnFamilyHandle*>*>(
+ ra->cf_map);
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(ra->trace_entry.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map->find(cf_id) == cf_map->end()) {
+ return;
+ }
+
+ std::string value;
+ Iterator* single_iter = nullptr;
+ if (cf_id == 0) {
+ single_iter = ra->db->NewIterator(ra->roptions);
+ } else {
+ single_iter = ra->db->NewIterator(ra->roptions, (*cf_map)[cf_id]);
+ }
+ single_iter->Seek(key);
+ delete single_iter;
+ return;
+}
+
+void Replayer::BGWorkIterSeekForPrev(void* arg) {
+ std::unique_ptr<ReplayerWorkerArg> ra(
+ reinterpret_cast<ReplayerWorkerArg*>(arg));
+ auto cf_map = static_cast<std::unordered_map<uint32_t, ColumnFamilyHandle*>*>(
+ ra->cf_map);
+ uint32_t cf_id = 0;
+ Slice key;
+ DecodeCFAndKey(ra->trace_entry.payload, &cf_id, &key);
+ if (cf_id > 0 && cf_map->find(cf_id) == cf_map->end()) {
+ return;
+ }
+
+ std::string value;
+ Iterator* single_iter = nullptr;
+ if (cf_id == 0) {
+ single_iter = ra->db->NewIterator(ra->roptions);
+ } else {
+ single_iter = ra->db->NewIterator(ra->roptions, (*cf_map)[cf_id]);
+ }
+ single_iter->SeekForPrev(key);
+ delete single_iter;
+ return;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/trace_replay/trace_replay.h b/storage/rocksdb/rocksdb/trace_replay/trace_replay.h
new file mode 100644
index 00000000..e7ef598f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/trace_replay/trace_replay.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "rocksdb/trace_reader_writer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This file contains Tracer and Replayer classes that enable capturing and
+// replaying RocksDB traces.
+
+class ColumnFamilyHandle;
+class ColumnFamilyData;
+class DB;
+class DBImpl;
+class Slice;
+class WriteBatch;
+
+extern const std::string kTraceMagic;
+const unsigned int kTraceTimestampSize = 8;
+const unsigned int kTraceTypeSize = 1;
+const unsigned int kTracePayloadLengthSize = 4;
+const unsigned int kTraceMetadataSize =
+ kTraceTimestampSize + kTraceTypeSize + kTracePayloadLengthSize;
+
+// Supported Trace types.
+enum TraceType : char {
+ kTraceBegin = 1,
+ kTraceEnd = 2,
+ kTraceWrite = 3,
+ kTraceGet = 4,
+ kTraceIteratorSeek = 5,
+ kTraceIteratorSeekForPrev = 6,
+ // Block cache related types.
+ kBlockTraceIndexBlock = 7,
+ kBlockTraceFilterBlock = 8,
+ kBlockTraceDataBlock = 9,
+ kBlockTraceUncompressionDictBlock = 10,
+ kBlockTraceRangeDeletionBlock = 11,
+ // All trace types should be added before kTraceMax
+ kTraceMax,
+};
+
+// TODO: This should also be made part of public interface to help users build
+// custom TracerReaders and TraceWriters.
+//
+// The data structure that defines a single trace.
+struct Trace {
+ uint64_t ts; // timestamp
+ TraceType type;
+ std::string payload;
+
+ void reset() {
+ ts = 0;
+ type = kTraceMax;
+ payload.clear();
+ }
+};
+
+class TracerHelper {
+ public:
+ // Encode a trace object into the given string.
+ static void EncodeTrace(const Trace& trace, std::string* encoded_trace);
+
+ // Decode a string into the given trace object.
+ static Status DecodeTrace(const std::string& encoded_trace, Trace* trace);
+};
+
+// Tracer captures all RocksDB operations using a user-provided TraceWriter.
+// Every RocksDB operation is written as a single trace. Each trace will have a
+// timestamp and type, followed by the trace payload.
+class Tracer {
+ public:
+ Tracer(Env* env, const TraceOptions& trace_options,
+ std::unique_ptr<TraceWriter>&& trace_writer);
+ ~Tracer();
+
+ // Trace all write operations -- Put, Merge, Delete, SingleDelete, Write
+ Status Write(WriteBatch* write_batch);
+
+ // Trace Get operations.
+ Status Get(ColumnFamilyHandle* cfname, const Slice& key);
+
+ // Trace Iterators.
+ Status IteratorSeek(const uint32_t& cf_id, const Slice& key);
+ Status IteratorSeekForPrev(const uint32_t& cf_id, const Slice& key);
+
+ // Returns true if the trace is over the configured max trace file limit.
+ // False otherwise.
+ bool IsTraceFileOverMax();
+
+ // Writes a trace footer at the end of the tracing
+ Status Close();
+
+ private:
+ // Write a trace header at the beginning, typically on initiating a trace,
+ // with some metadata like a magic number, trace version, RocksDB version, and
+ // trace format.
+ Status WriteHeader();
+
+ // Write a trace footer, typically on ending a trace, with some metadata.
+ Status WriteFooter();
+
+ // Write a single trace using the provided TraceWriter to the underlying
+ // system, say, a filesystem or a streaming service.
+ Status WriteTrace(const Trace& trace);
+
+ // Helps in filtering and sampling of traces.
+ // Returns true if a trace should be skipped, false otherwise.
+ bool ShouldSkipTrace(const TraceType& type);
+
+ Env* env_;
+ TraceOptions trace_options_;
+ std::unique_ptr<TraceWriter> trace_writer_;
+ uint64_t trace_request_count_;
+};
+
+// Replayer helps to replay the captured RocksDB operations, using a user
+// provided TraceReader.
+// The Replayer is instantiated via db_bench today, on using "replay" benchmark.
+class Replayer {
+ public:
+ Replayer(DB* db, const std::vector<ColumnFamilyHandle*>& handles,
+ std::unique_ptr<TraceReader>&& reader);
+ ~Replayer();
+
+ // Replay all the traces from the provided trace stream, taking the delay
+ // between the traces into consideration.
+ Status Replay();
+
+ // Replay the provide trace stream, which is the same as Replay(), with
+ // multi-threads. Queries are scheduled in the thread pool job queue.
+ // User can set the number of threads in the thread pool.
+ Status MultiThreadReplay(uint32_t threads_num);
+
+ // Enables fast forwarding a replay by reducing the delay between the ingested
+ // traces.
+ // fast_forward : Rate of replay speedup.
+ // If 1, replay the operations at the same rate as in the trace stream.
+ // If > 1, speed up the replay by this amount.
+ Status SetFastForward(uint32_t fast_forward);
+
+ private:
+ Status ReadHeader(Trace* header);
+ Status ReadFooter(Trace* footer);
+ Status ReadTrace(Trace* trace);
+
+ // The background function for MultiThreadReplay to execute Get query
+ // based on the trace records.
+ static void BGWorkGet(void* arg);
+
+ // The background function for MultiThreadReplay to execute WriteBatch
+ // (Put, Delete, SingleDelete, DeleteRange) based on the trace records.
+ static void BGWorkWriteBatch(void* arg);
+
+ // The background function for MultiThreadReplay to execute Iterator (Seek)
+ // based on the trace records.
+ static void BGWorkIterSeek(void* arg);
+
+ // The background function for MultiThreadReplay to execute Iterator
+ // (SeekForPrev) based on the trace records.
+ static void BGWorkIterSeekForPrev(void* arg);
+
+ DBImpl* db_;
+ Env* env_;
+ std::unique_ptr<TraceReader> trace_reader_;
+ std::unordered_map<uint32_t, ColumnFamilyHandle*> cf_map_;
+ uint32_t fast_forward_;
+};
+
+// The passin arg of MultiThreadRepkay for each trace record.
+struct ReplayerWorkerArg {
+ DB* db;
+ Trace trace_entry;
+ std::unordered_map<uint32_t, ColumnFamilyHandle*>* cf_map;
+ WriteOptions woptions;
+ ReadOptions roptions;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/aligned_buffer.h b/storage/rocksdb/rocksdb/util/aligned_buffer.h
new file mode 100644
index 00000000..c7b0728c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/aligned_buffer.h
@@ -0,0 +1,248 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This file contains utilities to handle the alignment of pages and buffers.
+
+// Truncate to a multiple of page_size, which is also a page boundary. This
+// helps to figuring out the right alignment.
+// Example:
+// TruncateToPageBoundary(5000, 4096) => 4096
+// TruncateToPageBoundary(10000, 4096) => 8192
+inline size_t TruncateToPageBoundary(size_t page_size, size_t s) {
+ s -= (s & (page_size - 1));
+ assert((s % page_size) == 0);
+ return s;
+}
+
+// Round up x to a multiple of y.
+// Example:
+// Roundup(13, 5) => 15
+// Roundup(201, 16) => 208
+inline size_t Roundup(size_t x, size_t y) {
+ return ((x + y - 1) / y) * y;
+}
+
+// Round down x to a multiple of y.
+// Example:
+// Rounddown(13, 5) => 10
+// Rounddown(201, 16) => 192
+inline size_t Rounddown(size_t x, size_t y) { return (x / y) * y; }
+
+// AlignedBuffer manages a buffer by taking alignment into consideration, and
+// aligns the buffer start and end positions. It is mainly used for direct I/O,
+// though it can be used other purposes as well.
+// It also supports expanding the managed buffer, and copying whole or part of
+// the data from old buffer into the new expanded buffer. Such a copy especially
+// helps in cases avoiding an IO to re-fetch the data from disk.
+//
+// Example:
+// AlignedBuffer buf;
+// buf.Alignment(alignment);
+// buf.AllocateNewBuffer(user_requested_buf_size);
+// ...
+// buf.AllocateNewBuffer(2*user_requested_buf_size, /*copy_data*/ true,
+// copy_offset, copy_len);
+class AlignedBuffer {
+ size_t alignment_;
+ std::unique_ptr<char[]> buf_;
+ size_t capacity_;
+ size_t cursize_;
+ char* bufstart_;
+
+public:
+ AlignedBuffer()
+ : alignment_(),
+ capacity_(0),
+ cursize_(0),
+ bufstart_(nullptr) {
+ }
+
+ AlignedBuffer(AlignedBuffer&& o) ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ AlignedBuffer& operator=(AlignedBuffer&& o) ROCKSDB_NOEXCEPT {
+ alignment_ = std::move(o.alignment_);
+ buf_ = std::move(o.buf_);
+ capacity_ = std::move(o.capacity_);
+ cursize_ = std::move(o.cursize_);
+ bufstart_ = std::move(o.bufstart_);
+ return *this;
+ }
+
+ AlignedBuffer(const AlignedBuffer&) = delete;
+
+ AlignedBuffer& operator=(const AlignedBuffer&) = delete;
+
+ static bool isAligned(const void* ptr, size_t alignment) {
+ return reinterpret_cast<uintptr_t>(ptr) % alignment == 0;
+ }
+
+ static bool isAligned(size_t n, size_t alignment) {
+ return n % alignment == 0;
+ }
+
+ size_t Alignment() const {
+ return alignment_;
+ }
+
+ size_t Capacity() const {
+ return capacity_;
+ }
+
+ size_t CurrentSize() const {
+ return cursize_;
+ }
+
+ const char* BufferStart() const {
+ return bufstart_;
+ }
+
+ char* BufferStart() { return bufstart_; }
+
+ void Clear() {
+ cursize_ = 0;
+ }
+
+ void Alignment(size_t alignment) {
+ assert(alignment > 0);
+ assert((alignment & (alignment - 1)) == 0);
+ alignment_ = alignment;
+ }
+
+ // Allocates a new buffer and sets the start position to the first aligned
+ // byte.
+ //
+ // requested_capacity: requested new buffer capacity. This capacity will be
+ // rounded up based on alignment.
+ // copy_data: Copy data from old buffer to new buffer. If copy_offset and
+ // copy_len are not passed in and the new requested capacity is bigger
+ // than the existing buffer's capacity, the data in the exising buffer is
+ // fully copied over to the new buffer.
+ // copy_offset: Copy data from this offset in old buffer.
+ // copy_len: Number of bytes to copy.
+ //
+ // The function does nothing if the new requested_capacity is smaller than
+ // the current buffer capacity and copy_data is true i.e. the old buffer is
+ // retained as is.
+ void AllocateNewBuffer(size_t requested_capacity, bool copy_data = false,
+ uint64_t copy_offset = 0, size_t copy_len = 0) {
+ assert(alignment_ > 0);
+ assert((alignment_ & (alignment_ - 1)) == 0);
+
+ copy_len = copy_len > 0 ? copy_len : cursize_;
+ if (copy_data && requested_capacity < copy_len) {
+ // If we are downsizing to a capacity that is smaller than the current
+ // data in the buffer -- Ignore the request.
+ return;
+ }
+
+ size_t new_capacity = Roundup(requested_capacity, alignment_);
+ char* new_buf = new char[new_capacity + alignment_];
+ char* new_bufstart = reinterpret_cast<char*>(
+ (reinterpret_cast<uintptr_t>(new_buf) + (alignment_ - 1)) &
+ ~static_cast<uintptr_t>(alignment_ - 1));
+
+ if (copy_data) {
+ assert(bufstart_ + copy_offset + copy_len <= bufstart_ + cursize_);
+ memcpy(new_bufstart, bufstart_ + copy_offset, copy_len);
+ cursize_ = copy_len;
+ } else {
+ cursize_ = 0;
+ }
+
+ bufstart_ = new_bufstart;
+ capacity_ = new_capacity;
+ buf_.reset(new_buf);
+ }
+
+ // Append to the buffer.
+ //
+ // src : source to copy the data from.
+ // append_size : number of bytes to copy from src.
+ // Returns the number of bytes appended.
+ //
+ // If append_size is more than the remaining buffer size only the
+ // remaining-size worth of bytes are copied.
+ size_t Append(const char* src, size_t append_size) {
+ size_t buffer_remaining = capacity_ - cursize_;
+ size_t to_copy = std::min(append_size, buffer_remaining);
+
+ if (to_copy > 0) {
+ memcpy(bufstart_ + cursize_, src, to_copy);
+ cursize_ += to_copy;
+ }
+ return to_copy;
+ }
+
+ // Read from the buffer.
+ //
+ // dest : destination buffer to copy the data to.
+ // offset : the buffer offset to start reading from.
+ // read_size : the number of bytes to copy from the buffer to dest.
+ // Returns the number of bytes read/copied to dest.
+ size_t Read(char* dest, size_t offset, size_t read_size) const {
+ assert(offset < cursize_);
+
+ size_t to_read = 0;
+ if(offset < cursize_) {
+ to_read = std::min(cursize_ - offset, read_size);
+ }
+ if (to_read > 0) {
+ memcpy(dest, bufstart_ + offset, to_read);
+ }
+ return to_read;
+ }
+
+ // Pad to the end of alignment with "padding"
+ void PadToAlignmentWith(int padding) {
+ size_t total_size = Roundup(cursize_, alignment_);
+ size_t pad_size = total_size - cursize_;
+
+ if (pad_size > 0) {
+ assert((pad_size + cursize_) <= capacity_);
+ memset(bufstart_ + cursize_, padding, pad_size);
+ cursize_ += pad_size;
+ }
+ }
+
+ void PadWith(size_t pad_size, int padding) {
+ assert((pad_size + cursize_) <= capacity_);
+ memset(bufstart_ + cursize_, padding, pad_size);
+ cursize_ += pad_size;
+ }
+
+ // After a partial flush move the tail to the beginning of the buffer.
+ void RefitTail(size_t tail_offset, size_t tail_size) {
+ if (tail_size > 0) {
+ memmove(bufstart_, bufstart_ + tail_offset, tail_size);
+ }
+ cursize_ = tail_size;
+ }
+
+ // Returns a place to start appending.
+ // WARNING: Note that it is possible to write past the end of the buffer if
+ // the buffer is modified without using the write APIs or encapsulation
+ // offered by AlignedBuffer. It is up to the user to guard against such
+ // errors.
+ char* Destination() {
+ return bufstart_ + cursize_;
+ }
+
+ void Size(size_t cursize) {
+ cursize_ = cursize;
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/autovector.h b/storage/rocksdb/rocksdb/util/autovector.h
new file mode 100644
index 00000000..1e6c4716
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/autovector.h
@@ -0,0 +1,361 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cassert>
+#include <initializer_list>
+#include <iterator>
+#include <stdexcept>
+#include <vector>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_LITE
+template <class T, size_t kSize = 8>
+class autovector : public std::vector<T> {
+ using std::vector<T>::vector;
+};
+#else
+// A vector that leverages pre-allocated stack-based array to achieve better
+// performance for array with small amount of items.
+//
+// The interface resembles that of vector, but with less features since we aim
+// to solve the problem that we have in hand, rather than implementing a
+// full-fledged generic container.
+//
+// Currently we don't support:
+// * reserve()/shrink_to_fit()
+// If used correctly, in most cases, people should not touch the
+// underlying vector at all.
+// * random insert()/erase(), please only use push_back()/pop_back().
+// * No move/swap operations. Each autovector instance has a
+// stack-allocated array and if we want support move/swap operations, we
+// need to copy the arrays other than just swapping the pointers. In this
+// case we'll just explicitly forbid these operations since they may
+// lead users to make false assumption by thinking they are inexpensive
+// operations.
+//
+// Naming style of public methods almost follows that of the STL's.
+template <class T, size_t kSize = 8>
+class autovector {
+ public:
+ // General STL-style container member types.
+ typedef T value_type;
+ typedef typename std::vector<T>::difference_type difference_type;
+ typedef typename std::vector<T>::size_type size_type;
+ typedef value_type& reference;
+ typedef const value_type& const_reference;
+ typedef value_type* pointer;
+ typedef const value_type* const_pointer;
+
+ // This class is the base for regular/const iterator
+ template <class TAutoVector, class TValueType>
+ class iterator_impl {
+ public:
+ // -- iterator traits
+ typedef iterator_impl<TAutoVector, TValueType> self_type;
+ typedef TValueType value_type;
+ typedef TValueType& reference;
+ typedef TValueType* pointer;
+ typedef typename TAutoVector::difference_type difference_type;
+ typedef std::random_access_iterator_tag iterator_category;
+
+ iterator_impl(TAutoVector* vect, size_t index)
+ : vect_(vect), index_(index) {};
+ iterator_impl(const iterator_impl&) = default;
+ ~iterator_impl() {}
+ iterator_impl& operator=(const iterator_impl&) = default;
+
+ // -- Advancement
+ // ++iterator
+ self_type& operator++() {
+ ++index_;
+ return *this;
+ }
+
+ // iterator++
+ self_type operator++(int) {
+ auto old = *this;
+ ++index_;
+ return old;
+ }
+
+ // --iterator
+ self_type& operator--() {
+ --index_;
+ return *this;
+ }
+
+ // iterator--
+ self_type operator--(int) {
+ auto old = *this;
+ --index_;
+ return old;
+ }
+
+ self_type operator-(difference_type len) const {
+ return self_type(vect_, index_ - len);
+ }
+
+ difference_type operator-(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ - other.index_;
+ }
+
+ self_type operator+(difference_type len) const {
+ return self_type(vect_, index_ + len);
+ }
+
+ self_type& operator+=(difference_type len) {
+ index_ += len;
+ return *this;
+ }
+
+ self_type& operator-=(difference_type len) {
+ index_ -= len;
+ return *this;
+ }
+
+ // -- Reference
+ reference operator*() const {
+ assert(vect_->size() >= index_);
+ return (*vect_)[index_];
+ }
+
+ pointer operator->() const {
+ assert(vect_->size() >= index_);
+ return &(*vect_)[index_];
+ }
+
+ reference operator[](difference_type len) const {
+ return *(*this + len);
+ }
+
+ // -- Logical Operators
+ bool operator==(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ == other.index_;
+ }
+
+ bool operator!=(const self_type& other) const { return !(*this == other); }
+
+ bool operator>(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ > other.index_;
+ }
+
+ bool operator<(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ < other.index_;
+ }
+
+ bool operator>=(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ >= other.index_;
+ }
+
+ bool operator<=(const self_type& other) const {
+ assert(vect_ == other.vect_);
+ return index_ <= other.index_;
+ }
+
+ private:
+ TAutoVector* vect_ = nullptr;
+ size_t index_ = 0;
+ };
+
+ typedef iterator_impl<autovector, value_type> iterator;
+ typedef iterator_impl<const autovector, const value_type> const_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ autovector() : values_(reinterpret_cast<pointer>(buf_)) {}
+
+ autovector(std::initializer_list<T> init_list)
+ : values_(reinterpret_cast<pointer>(buf_)) {
+ for (const T& item : init_list) {
+ push_back(item);
+ }
+ }
+
+ ~autovector() { clear(); }
+
+ // -- Immutable operations
+ // Indicate if all data resides in in-stack data structure.
+ bool only_in_stack() const {
+ // If no element was inserted at all, the vector's capacity will be `0`.
+ return vect_.capacity() == 0;
+ }
+
+ size_type size() const { return num_stack_items_ + vect_.size(); }
+
+ // resize does not guarantee anything about the contents of the newly
+ // available elements
+ void resize(size_type n) {
+ if (n > kSize) {
+ vect_.resize(n - kSize);
+ while (num_stack_items_ < kSize) {
+ new ((void*)(&values_[num_stack_items_++])) value_type();
+ }
+ num_stack_items_ = kSize;
+ } else {
+ vect_.clear();
+ while (num_stack_items_ < n) {
+ new ((void*)(&values_[num_stack_items_++])) value_type();
+ }
+ while (num_stack_items_ > n) {
+ values_[--num_stack_items_].~value_type();
+ }
+ }
+ }
+
+ bool empty() const { return size() == 0; }
+
+ const_reference operator[](size_type n) const {
+ assert(n < size());
+ if (n < kSize) {
+ return values_[n];
+ }
+ return vect_[n - kSize];
+ }
+
+ reference operator[](size_type n) {
+ assert(n < size());
+ if (n < kSize) {
+ return values_[n];
+ }
+ return vect_[n - kSize];
+ }
+
+ const_reference at(size_type n) const {
+ assert(n < size());
+ return (*this)[n];
+ }
+
+ reference at(size_type n) {
+ assert(n < size());
+ return (*this)[n];
+ }
+
+ reference front() {
+ assert(!empty());
+ return *begin();
+ }
+
+ const_reference front() const {
+ assert(!empty());
+ return *begin();
+ }
+
+ reference back() {
+ assert(!empty());
+ return *(end() - 1);
+ }
+
+ const_reference back() const {
+ assert(!empty());
+ return *(end() - 1);
+ }
+
+ // -- Mutable Operations
+ void push_back(T&& item) {
+ if (num_stack_items_ < kSize) {
+ new ((void*)(&values_[num_stack_items_])) value_type();
+ values_[num_stack_items_++] = std::move(item);
+ } else {
+ vect_.push_back(item);
+ }
+ }
+
+ void push_back(const T& item) {
+ if (num_stack_items_ < kSize) {
+ new ((void*)(&values_[num_stack_items_])) value_type();
+ values_[num_stack_items_++] = item;
+ } else {
+ vect_.push_back(item);
+ }
+ }
+
+ template <class... Args>
+ void emplace_back(Args&&... args) {
+ if (num_stack_items_ < kSize) {
+ new ((void*)(&values_[num_stack_items_++]))
+ value_type(std::forward<Args>(args)...);
+ } else {
+ vect_.emplace_back(std::forward<Args>(args)...);
+ }
+ }
+
+ void pop_back() {
+ assert(!empty());
+ if (!vect_.empty()) {
+ vect_.pop_back();
+ } else {
+ values_[--num_stack_items_].~value_type();
+ }
+ }
+
+ void clear() {
+ while (num_stack_items_ > 0) {
+ values_[--num_stack_items_].~value_type();
+ }
+ vect_.clear();
+ }
+
+ // -- Copy and Assignment
+ autovector& assign(const autovector& other);
+
+ autovector(const autovector& other) { assign(other); }
+
+ autovector& operator=(const autovector& other) { return assign(other); }
+
+ // -- Iterator Operations
+ iterator begin() { return iterator(this, 0); }
+
+ const_iterator begin() const { return const_iterator(this, 0); }
+
+ iterator end() { return iterator(this, this->size()); }
+
+ const_iterator end() const { return const_iterator(this, this->size()); }
+
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+
+ private:
+ size_type num_stack_items_ = 0; // current number of items
+ alignas(alignof(
+ value_type)) char buf_[kSize *
+ sizeof(value_type)]; // the first `kSize` items
+ pointer values_;
+ // used only if there are more than `kSize` items.
+ std::vector<T> vect_;
+};
+
+template <class T, size_t kSize>
+autovector<T, kSize>& autovector<T, kSize>::assign(const autovector& other) {
+ values_ = reinterpret_cast<pointer>(buf_);
+ // copy the internal vector
+ vect_.assign(other.vect_.begin(), other.vect_.end());
+
+ // copy array
+ num_stack_items_ = other.num_stack_items_;
+ std::copy(other.values_, other.values_ + num_stack_items_, values_);
+
+ return *this;
+}
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/autovector_test.cc b/storage/rocksdb/rocksdb/util/autovector_test.cc
new file mode 100644
index 00000000..adddd1b0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/autovector_test.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <iostream>
+#include <string>
+#include <utility>
+
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/autovector.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 <class T>
+void AssertAutoVectorOnlyInStack(autovector<T, kSize>* vec, bool result) {
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(vec->only_in_stack(), result);
+#else
+ (void) vec;
+ (void) result;
+#endif // !ROCKSDB_LITE
+}
+} // namespace
+
+TEST_F(AutoVectorTest, PushBackAndPopBack) {
+ autovector<size_t, kSize> 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) {
+ typedef std::pair<size_t, std::string> ValType;
+ autovector<ValType, kSize> vec;
+
+ for (size_t i = 0; i < 1000 * kSize; ++i) {
+ vec.emplace_back(i, ToString(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(ToString(i + 123), vec[i].second);
+ }
+
+ vec.clear();
+ ASSERT_TRUE(vec.empty());
+ AssertAutoVectorOnlyInStack(&vec, false);
+}
+
+TEST_F(AutoVectorTest, Resize) {
+ autovector<size_t, kSize> 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<size_t, kSize>& a, const autovector<size_t, kSize>& b) {
+ ASSERT_EQ(a.size(), b.size());
+ ASSERT_EQ(a.empty(), b.empty());
+#ifndef ROCKSDB_LITE
+ ASSERT_EQ(a.only_in_stack(), b.only_in_stack());
+#endif // !ROCKSDB_LITE
+ 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<size_t, kSize> vec;
+ for (size_t i = 0; i < size; ++i) {
+ vec.push_back(i);
+ }
+
+ {
+ autovector<size_t, kSize> other;
+ other = vec;
+ AssertEqual(other, vec);
+ }
+
+ {
+ autovector<size_t, kSize> other(vec);
+ AssertEqual(other, vec);
+ }
+ }
+}
+
+TEST_F(AutoVectorTest, Iterators) {
+ autovector<std::string, kSize> vec;
+ for (size_t i = 0; i < kSize * 1000; ++i) {
+ vec.push_back(ToString(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<size_t>(pos - vec.begin());
+ ASSERT_EQ(i + 2, diff);
+ }
+}
+
+namespace {
+std::vector<std::string> GetTestKeys(size_t size) {
+ std::vector<std::string> keys;
+ keys.resize(size);
+
+ int index = 0;
+ for (auto& key : keys) {
+ key = "item-" + ROCKSDB_NAMESPACE::ToString(index++);
+ }
+ return keys;
+}
+} // namespace
+
+template <class TVector>
+void BenchmarkVectorCreationAndInsertion(
+ std::string name, size_t ops, size_t item_size,
+ const std::vector<typename TVector::value_type>& 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 <class TVector>
+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<std::string>
+// and autovector<std::string>. We chose string for comparison because in most
+// of our use cases we used std::vector<std::string>.
+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<std::string>>(
+ "std::vector<std::string>", kOps, insertions, string_keys);
+ BenchmarkVectorCreationAndInsertion<autovector<std::string, kSize>>(
+ "autovector<std::string>", 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<uint64_t> 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<uint64_t>>(
+ "std::vector<uint64_t>", kOps, insertions, int_keys);
+ BenchmarkVectorCreationAndInsertion<autovector<uint64_t, kSize>>(
+ "autovector<uint64_t>", 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<std::string>>("std::vector", kOps,
+ elem_size);
+ BenchmarkSequenceAccess<autovector<std::string, kSize>>("autovector", kOps,
+ elem_size);
+ cout << "-----------------------------------" << endl;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/bloom_impl.h b/storage/rocksdb/rocksdb/util/bloom_impl.h
new file mode 100644
index 00000000..54c04848
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/bloom_impl.h
@@ -0,0 +1,483 @@
+// Copyright (c) 2019-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// Implementation details of various Bloom filter implementations used in
+// RocksDB. (DynamicBloom is in a separate file for now because it
+// supports concurrent write.)
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <cmath>
+
+#include "rocksdb/slice.h"
+#include "util/hash.h"
+
+#ifdef HAVE_AVX2
+#include <immintrin.h>
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class BloomMath {
+ public:
+ // False positive rate of a standard Bloom filter, for given ratio of
+ // filter memory bits to added keys, and number of probes per operation.
+ // (The false positive rate is effectively independent of scale, assuming
+ // the implementation scales OK.)
+ static double StandardFpRate(double bits_per_key, int num_probes) {
+ // Standard very-good-estimate formula. See
+ // https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives
+ return std::pow(1.0 - std::exp(-num_probes / bits_per_key), num_probes);
+ }
+
+ // False positive rate of a "blocked"/"shareded"/"cache-local" Bloom filter,
+ // for given ratio of filter memory bits to added keys, number of probes per
+ // operation (all within the given block or cache line size), and block or
+ // cache line size.
+ static double CacheLocalFpRate(double bits_per_key, int num_probes,
+ int cache_line_bits) {
+ double keys_per_cache_line = cache_line_bits / bits_per_key;
+ // A reasonable estimate is the average of the FP rates for one standard
+ // deviation above and below the mean bucket occupancy. See
+ // https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#the-math
+ double keys_stddev = std::sqrt(keys_per_cache_line);
+ double crowded_fp = StandardFpRate(
+ cache_line_bits / (keys_per_cache_line + keys_stddev), num_probes);
+ double uncrowded_fp = StandardFpRate(
+ cache_line_bits / (keys_per_cache_line - keys_stddev), num_probes);
+ return (crowded_fp + uncrowded_fp) / 2;
+ }
+
+ // False positive rate of querying a new item against `num_keys` items, all
+ // hashed to `fingerprint_bits` bits. (This assumes the fingerprint hashes
+ // themselves are stored losslessly. See Section 4 of
+ // http://www.ccs.neu.edu/home/pete/pub/bloom-filters-verification.pdf)
+ static double FingerprintFpRate(size_t num_keys, int fingerprint_bits) {
+ double inv_fingerprint_space = std::pow(0.5, fingerprint_bits);
+ // Base estimate assumes each key maps to a unique fingerprint.
+ // Could be > 1 in extreme cases.
+ double base_estimate = num_keys * inv_fingerprint_space;
+ // To account for potential overlap, we choose between two formulas
+ if (base_estimate > 0.0001) {
+ // A very good formula assuming we don't construct a floating point
+ // number extremely close to 1. Always produces a probability < 1.
+ return 1.0 - std::exp(-base_estimate);
+ } else {
+ // A very good formula when base_estimate is far below 1. (Subtract
+ // away the integral-approximated sum that some key has same hash as
+ // one coming before it in a list.)
+ return base_estimate - (base_estimate * base_estimate * 0.5);
+ }
+ }
+
+ // Returns the probably of either of two independent(-ish) events
+ // happening, given their probabilities. (This is useful for combining
+ // results from StandardFpRate or CacheLocalFpRate with FingerprintFpRate
+ // for a hash-efficient Bloom filter's FP rate. See Section 4 of
+ // http://www.ccs.neu.edu/home/pete/pub/bloom-filters-verification.pdf)
+ static double IndependentProbabilitySum(double rate1, double rate2) {
+ // Use formula that avoids floating point extremely close to 1 if
+ // rates are extremely small.
+ return rate1 + rate2 - (rate1 * rate2);
+ }
+};
+
+// A fast, flexible, and accurate cache-local Bloom implementation with
+// SIMD-optimized query performance (currently using AVX2 on Intel). Write
+// performance and non-SIMD read are very good, benefiting from fastrange32
+// used in place of % and single-cycle multiplication on recent processors.
+//
+// Most other SIMD Bloom implementations sacrifice flexibility and/or
+// accuracy by requiring num_probes to be a power of two and restricting
+// where each probe can occur in a cache line. This implementation sacrifices
+// SIMD-optimization for add (might still be possible, especially with AVX512)
+// in favor of allowing any num_probes, not crossing cache line boundary,
+// and accuracy close to theoretical best accuracy for a cache-local Bloom.
+// E.g. theoretical best for 10 bits/key, num_probes=6, and 512-bit bucket
+// (Intel cache line size) is 0.9535% FP rate. This implementation yields
+// about 0.957%. (Compare to LegacyLocalityBloomImpl<false> at 1.138%, or
+// about 0.951% for 1024-bit buckets, cache line size for some ARM CPUs.)
+//
+// This implementation can use a 32-bit hash (let h2 be h1 * 0x9e3779b9) or
+// a 64-bit hash (split into two uint32s). With many millions of keys, the
+// false positive rate associated with using a 32-bit hash can dominate the
+// false positive rate of the underlying filter. At 10 bits/key setting, the
+// inflection point is about 40 million keys, so 32-bit hash is a bad idea
+// with 10s of millions of keys or more.
+//
+// Despite accepting a 64-bit hash, this implementation uses 32-bit fastrange
+// to pick a cache line, which can be faster than 64-bit in some cases.
+// This only hurts accuracy as you get into 10s of GB for a single filter,
+// and accuracy abruptly breaks down at 256GB (2^32 cache lines). Switch to
+// 64-bit fastrange if you need filters so big. ;)
+//
+// Using only a 32-bit input hash within each cache line has negligible
+// impact for any reasonable cache line / bucket size, for arbitrary filter
+// size, and potentially saves intermediate data size in some cases vs.
+// tracking full 64 bits. (Even in an implementation using 64-bit arithmetic
+// to generate indices, I might do the same, as a single multiplication
+// suffices to generate a sufficiently mixed 64 bits from 32 bits.)
+//
+// This implementation is currently tied to Intel cache line size, 64 bytes ==
+// 512 bits. If there's sufficient demand for other cache line sizes, this is
+// a pretty good implementation to extend, but slight performance enhancements
+// are possible with an alternate implementation (probably not very compatible
+// with SIMD):
+// (1) Use rotation in addition to multiplication for remixing
+// (like murmur hash). (Using multiplication alone *slightly* hurts accuracy
+// because lower bits never depend on original upper bits.)
+// (2) Extract more than one bit index from each re-mix. (Only if rotation
+// or similar is part of remix, because otherwise you're making the
+// multiplication-only problem worse.)
+// (3) Re-mix full 64 bit hash, to get maximum number of bit indices per
+// re-mix.
+//
+class FastLocalBloomImpl {
+ public:
+ // NOTE: this has only been validated to enough accuracy for producing
+ // reasonable warnings / user feedback, not for making functional decisions.
+ static double EstimatedFpRate(size_t keys, size_t bytes, int num_probes,
+ int hash_bits) {
+ return BloomMath::IndependentProbabilitySum(
+ BloomMath::CacheLocalFpRate(8.0 * bytes / keys, num_probes,
+ /*cache line bits*/ 512),
+ BloomMath::FingerprintFpRate(keys, hash_bits));
+ }
+
+ static inline int ChooseNumProbes(int millibits_per_key) {
+ // Since this implementation can (with AVX2) make up to 8 probes
+ // for the same cost, we pick the most accurate num_probes, based
+ // on actual tests of the implementation. Note that for higher
+ // bits/key, the best choice for cache-local Bloom can be notably
+ // smaller than standard bloom, e.g. 9 instead of 11 @ 16 b/k.
+ if (millibits_per_key <= 2080) {
+ return 1;
+ } else if (millibits_per_key <= 3580) {
+ return 2;
+ } else if (millibits_per_key <= 5100) {
+ return 3;
+ } else if (millibits_per_key <= 6640) {
+ return 4;
+ } else if (millibits_per_key <= 8300) {
+ return 5;
+ } else if (millibits_per_key <= 10070) {
+ return 6;
+ } else if (millibits_per_key <= 11720) {
+ return 7;
+ } else if (millibits_per_key <= 14001) {
+ // Would be something like <= 13800 but sacrificing *slightly* for
+ // more settings using <= 8 probes.
+ return 8;
+ } else if (millibits_per_key <= 16050) {
+ return 9;
+ } else if (millibits_per_key <= 18300) {
+ return 10;
+ } else if (millibits_per_key <= 22001) {
+ return 11;
+ } else if (millibits_per_key <= 25501) {
+ return 12;
+ } else if (millibits_per_key > 50000) {
+ // Top out at 24 probes (three sets of 8)
+ return 24;
+ } else {
+ // Roughly optimal choices for remaining range
+ // e.g.
+ // 28000 -> 12, 28001 -> 13
+ // 50000 -> 23, 50001 -> 24
+ return (millibits_per_key - 1) / 2000 - 1;
+ }
+ }
+
+ static inline void AddHash(uint32_t h1, uint32_t h2, uint32_t len_bytes,
+ int num_probes, char *data) {
+ uint32_t bytes_to_cache_line = fastrange32(len_bytes >> 6, h1) << 6;
+ AddHashPrepared(h2, num_probes, data + bytes_to_cache_line);
+ }
+
+ static inline void AddHashPrepared(uint32_t h2, int num_probes,
+ char *data_at_cache_line) {
+ uint32_t h = h2;
+ for (int i = 0; i < num_probes; ++i, h *= uint32_t{0x9e3779b9}) {
+ // 9-bit address within 512 bit cache line
+ int bitpos = h >> (32 - 9);
+ data_at_cache_line[bitpos >> 3] |= (uint8_t{1} << (bitpos & 7));
+ }
+ }
+
+ static inline void PrepareHash(uint32_t h1, uint32_t len_bytes,
+ const char *data,
+ uint32_t /*out*/ *byte_offset) {
+ uint32_t bytes_to_cache_line = fastrange32(len_bytes >> 6, h1) << 6;
+ PREFETCH(data + bytes_to_cache_line, 0 /* rw */, 1 /* locality */);
+ PREFETCH(data + bytes_to_cache_line + 63, 0 /* rw */, 1 /* locality */);
+ *byte_offset = bytes_to_cache_line;
+ }
+
+ static inline bool HashMayMatch(uint32_t h1, uint32_t h2, uint32_t len_bytes,
+ int num_probes, const char *data) {
+ uint32_t bytes_to_cache_line = fastrange32(len_bytes >> 6, h1) << 6;
+ return HashMayMatchPrepared(h2, num_probes, data + bytes_to_cache_line);
+ }
+
+ static inline bool HashMayMatchPrepared(uint32_t h2, int num_probes,
+ const char *data_at_cache_line) {
+ uint32_t h = h2;
+#ifdef HAVE_AVX2
+ int rem_probes = num_probes;
+
+ // NOTE: For better performance for num_probes in {1, 2, 9, 10, 17, 18,
+ // etc.} one can insert specialized code for rem_probes <= 2, bypassing
+ // the SIMD code in those cases. There is a detectable but minor overhead
+ // applied to other values of num_probes (when not statically determined),
+ // but smoother performance curve vs. num_probes. But for now, when
+ // in doubt, don't add unnecessary code.
+
+ // Powers of 32-bit golden ratio, mod 2**32.
+ const __m256i multipliers =
+ _mm256_setr_epi32(0x00000001, 0x9e3779b9, 0xe35e67b1, 0x734297e9,
+ 0x35fbe861, 0xdeb7c719, 0x448b211, 0x3459b749);
+
+ for (;;) {
+ // Eight copies of hash
+ __m256i hash_vector = _mm256_set1_epi32(h);
+
+ // Same effect as repeated multiplication by 0x9e3779b9 thanks to
+ // associativity of multiplication.
+ hash_vector = _mm256_mullo_epi32(hash_vector, multipliers);
+
+ // Now the top 9 bits of each of the eight 32-bit values in
+ // hash_vector are bit addresses for probes within the cache line.
+ // While the platform-independent code uses byte addressing (6 bits
+ // to pick a byte + 3 bits to pick a bit within a byte), here we work
+ // with 32-bit words (4 bits to pick a word + 5 bits to pick a bit
+ // within a word) because that works well with AVX2 and is equivalent
+ // under little-endian.
+
+ // Shift each right by 28 bits to get 4-bit word addresses.
+ const __m256i word_addresses = _mm256_srli_epi32(hash_vector, 28);
+
+ // Gather 32-bit values spread over 512 bits by 4-bit address. In
+ // essence, we are dereferencing eight pointers within the cache
+ // line.
+ //
+ // Option 1: AVX2 gather (seems to be a little slow - understandable)
+ // const __m256i value_vector =
+ // _mm256_i32gather_epi32(static_cast<const int
+ // *>(data_at_cache_line),
+ // word_addresses,
+ // /*bytes / i32*/ 4);
+ // END Option 1
+ // Potentially unaligned as we're not *always* cache-aligned -> loadu
+ const __m256i *mm_data =
+ reinterpret_cast<const __m256i *>(data_at_cache_line);
+ __m256i lower = _mm256_loadu_si256(mm_data);
+ __m256i upper = _mm256_loadu_si256(mm_data + 1);
+ // Option 2: AVX512VL permute hack
+ // Only negligibly faster than Option 3, so not yet worth supporting
+ // const __m256i value_vector =
+ // _mm256_permutex2var_epi32(lower, word_addresses, upper);
+ // END Option 2
+ // Option 3: AVX2 permute+blend hack
+ // Use lowest three bits to order probing values, as if all from same
+ // 256 bit piece.
+ lower = _mm256_permutevar8x32_epi32(lower, word_addresses);
+ upper = _mm256_permutevar8x32_epi32(upper, word_addresses);
+ // Just top 1 bit of address, to select between lower and upper.
+ const __m256i upper_lower_selector = _mm256_srai_epi32(hash_vector, 31);
+ // Finally: the next 8 probed 32-bit values, in probing sequence order.
+ const __m256i value_vector =
+ _mm256_blendv_epi8(lower, upper, upper_lower_selector);
+ // END Option 3
+
+ // We might not need to probe all 8, so build a mask for selecting only
+ // what we need. (The k_selector(s) could be pre-computed but that
+ // doesn't seem to make a noticeable performance difference.)
+ const __m256i zero_to_seven = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
+ // Subtract rem_probes from each of those constants
+ __m256i k_selector =
+ _mm256_sub_epi32(zero_to_seven, _mm256_set1_epi32(rem_probes));
+ // Negative after subtract -> use/select
+ // Keep only high bit (logical shift right each by 31).
+ k_selector = _mm256_srli_epi32(k_selector, 31);
+
+ // Strip off the 4 bit word address (shift left)
+ __m256i bit_addresses = _mm256_slli_epi32(hash_vector, 4);
+ // And keep only 5-bit (32 - 27) bit-within-32-bit-word addresses.
+ bit_addresses = _mm256_srli_epi32(bit_addresses, 27);
+ // Build a bit mask
+ const __m256i bit_mask = _mm256_sllv_epi32(k_selector, bit_addresses);
+
+ // Like ((~value_vector) & bit_mask) == 0)
+ bool match = _mm256_testc_si256(value_vector, bit_mask) != 0;
+
+ // This check first so that it's easy for branch predictor to optimize
+ // num_probes <= 8 case, making it free of unpredictable branches.
+ if (rem_probes <= 8) {
+ return match;
+ } else if (!match) {
+ return false;
+ }
+ // otherwise
+ // Need another iteration. 0xab25f4c1 == golden ratio to the 8th power
+ h *= 0xab25f4c1;
+ rem_probes -= 8;
+ }
+#else
+ for (int i = 0; i < num_probes; ++i, h *= uint32_t{0x9e3779b9}) {
+ // 9-bit address within 512 bit cache line
+ int bitpos = h >> (32 - 9);
+ if ((data_at_cache_line[bitpos >> 3] & (char(1) << (bitpos & 7))) == 0) {
+ return false;
+ }
+ }
+ return true;
+#endif
+ }
+};
+
+// A legacy Bloom filter implementation with no locality of probes (slow).
+// It uses double hashing to generate a sequence of hash values.
+// Asymptotic analysis is in [Kirsch,Mitzenmacher 2006], but known to have
+// subtle accuracy flaws for practical sizes [Dillinger,Manolios 2004].
+//
+// DO NOT REUSE
+//
+class LegacyNoLocalityBloomImpl {
+ public:
+ static inline int ChooseNumProbes(int bits_per_key) {
+ // We intentionally round down to reduce probing cost a little bit
+ int num_probes = static_cast<int>(bits_per_key * 0.69); // 0.69 =~ ln(2)
+ if (num_probes < 1) num_probes = 1;
+ if (num_probes > 30) num_probes = 30;
+ return num_probes;
+ }
+
+ static inline void AddHash(uint32_t h, uint32_t total_bits, int num_probes,
+ char *data) {
+ const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
+ for (int i = 0; i < num_probes; i++) {
+ const uint32_t bitpos = h % total_bits;
+ data[bitpos / 8] |= (1 << (bitpos % 8));
+ h += delta;
+ }
+ }
+
+ static inline bool HashMayMatch(uint32_t h, uint32_t total_bits,
+ int num_probes, const char *data) {
+ const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
+ for (int i = 0; i < num_probes; i++) {
+ const uint32_t bitpos = h % total_bits;
+ if ((data[bitpos / 8] & (1 << (bitpos % 8))) == 0) {
+ return false;
+ }
+ h += delta;
+ }
+ return true;
+ }
+};
+
+// A legacy Bloom filter implementation with probes local to a single
+// cache line (fast). Because SST files might be transported between
+// platforms, the cache line size is a parameter rather than hard coded.
+// (But if specified as a constant parameter, an optimizing compiler
+// should take advantage of that.)
+//
+// When ExtraRotates is false, this implementation is notably deficient in
+// accuracy. Specifically, it uses double hashing with a 1/512 chance of the
+// increment being zero (when cache line size is 512 bits). Thus, there's a
+// 1/512 chance of probing only one index, which we'd expect to incur about
+// a 1/2 * 1/512 or absolute 0.1% FP rate penalty. More detail at
+// https://github.com/facebook/rocksdb/issues/4120
+//
+// DO NOT REUSE
+//
+template <bool ExtraRotates>
+class LegacyLocalityBloomImpl {
+ private:
+ static inline uint32_t GetLine(uint32_t h, uint32_t num_lines) {
+ uint32_t offset_h = ExtraRotates ? (h >> 11) | (h << 21) : h;
+ return offset_h % num_lines;
+ }
+
+ public:
+ // NOTE: this has only been validated to enough accuracy for producing
+ // reasonable warnings / user feedback, not for making functional decisions.
+ static double EstimatedFpRate(size_t keys, size_t bytes, int num_probes) {
+ double bits_per_key = 8.0 * bytes / keys;
+ double filter_rate = BloomMath::CacheLocalFpRate(bits_per_key, num_probes,
+ /*cache line bits*/ 512);
+ if (!ExtraRotates) {
+ // Good estimate of impact of flaw in index computation.
+ // Adds roughly 0.002 around 50 bits/key and 0.001 around 100 bits/key.
+ // The + 22 shifts it nicely to fit for lower bits/key.
+ filter_rate += 0.1 / (bits_per_key * 0.75 + 22);
+ } else {
+ // Not yet validated
+ assert(false);
+ }
+ // Always uses 32-bit hash
+ double fingerprint_rate = BloomMath::FingerprintFpRate(keys, 32);
+ return BloomMath::IndependentProbabilitySum(filter_rate, fingerprint_rate);
+ }
+
+ static inline void AddHash(uint32_t h, uint32_t num_lines, int num_probes,
+ char *data, int log2_cache_line_bytes) {
+ const int log2_cache_line_bits = log2_cache_line_bytes + 3;
+
+ char *data_at_offset =
+ data + (GetLine(h, num_lines) << log2_cache_line_bytes);
+ const uint32_t delta = (h >> 17) | (h << 15);
+ for (int i = 0; i < num_probes; ++i) {
+ // Mask to bit-within-cache-line address
+ const uint32_t bitpos = h & ((1 << log2_cache_line_bits) - 1);
+ data_at_offset[bitpos / 8] |= (1 << (bitpos % 8));
+ if (ExtraRotates) {
+ h = (h >> log2_cache_line_bits) | (h << (32 - log2_cache_line_bits));
+ }
+ h += delta;
+ }
+ }
+
+ static inline void PrepareHashMayMatch(uint32_t h, uint32_t num_lines,
+ const char *data,
+ uint32_t /*out*/ *byte_offset,
+ int log2_cache_line_bytes) {
+ uint32_t b = GetLine(h, num_lines) << log2_cache_line_bytes;
+ PREFETCH(data + b, 0 /* rw */, 1 /* locality */);
+ PREFETCH(data + b + ((1 << log2_cache_line_bytes) - 1), 0 /* rw */,
+ 1 /* locality */);
+ *byte_offset = b;
+ }
+
+ static inline bool HashMayMatch(uint32_t h, uint32_t num_lines,
+ int num_probes, const char *data,
+ int log2_cache_line_bytes) {
+ uint32_t b = GetLine(h, num_lines) << log2_cache_line_bytes;
+ return HashMayMatchPrepared(h, num_probes, data + b, log2_cache_line_bytes);
+ }
+
+ static inline bool HashMayMatchPrepared(uint32_t h, int num_probes,
+ const char *data_at_offset,
+ int log2_cache_line_bytes) {
+ const int log2_cache_line_bits = log2_cache_line_bytes + 3;
+
+ const uint32_t delta = (h >> 17) | (h << 15);
+ for (int i = 0; i < num_probes; ++i) {
+ // Mask to bit-within-cache-line address
+ const uint32_t bitpos = h & ((1 << log2_cache_line_bits) - 1);
+ if (((data_at_offset[bitpos / 8]) & (1 << (bitpos % 8))) == 0) {
+ return false;
+ }
+ if (ExtraRotates) {
+ h = (h >> log2_cache_line_bits) | (h << (32 - log2_cache_line_bits));
+ }
+ h += delta;
+ }
+ return true;
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/bloom_test.cc b/storage/rocksdb/rocksdb/util/bloom_test.cc
new file mode 100644
index 00000000..c88d7ee3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/bloom_test.cc
@@ -0,0 +1,912 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run this test... Skipping...\n");
+ return 0;
+}
+#else
+
+#include <array>
+#include <cmath>
+#include <vector>
+
+#include "logging/logging.h"
+#include "memory/arena.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;
+
+DEFINE_int32(bits_per_key, 10, "");
+
+namespace ROCKSDB_NAMESPACE {
+
+static const int kVerbose = 1;
+
+static Slice Key(int i, char* buffer) {
+ std::string s;
+ PutFixed32(&s, static_cast<uint32_t>(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 BlockBasedBloomTest : public testing::Test {
+ private:
+ std::unique_ptr<const FilterPolicy> policy_;
+ std::string filter_;
+ std::vector<std::string> keys_;
+
+ public:
+ BlockBasedBloomTest() { ResetPolicy(); }
+
+ void Reset() {
+ keys_.clear();
+ filter_.clear();
+ }
+
+ void ResetPolicy(double bits_per_key) {
+ policy_.reset(new BloomFilterPolicy(bits_per_key,
+ BloomFilterPolicy::kDeprecatedBlock));
+ Reset();
+ }
+
+ void ResetPolicy() { ResetPolicy(FLAGS_bits_per_key); }
+
+ void Add(const Slice& s) {
+ keys_.push_back(s.ToString());
+ }
+
+ void Build() {
+ std::vector<Slice> key_slices;
+ for (size_t i = 0; i < keys_.size(); i++) {
+ key_slices.push_back(Slice(keys_[i]));
+ }
+ filter_.clear();
+ policy_->CreateFilter(&key_slices[0], static_cast<int>(key_slices.size()),
+ &filter_);
+ keys_.clear();
+ if (kVerbose >= 2) DumpFilter();
+ }
+
+ size_t FilterSize() const {
+ return filter_.size();
+ }
+
+ Slice FilterData() const { return Slice(filter_); }
+
+ void DumpFilter() {
+ fprintf(stderr, "F(");
+ for (size_t i = 0; i+1 < filter_.size(); i++) {
+ const unsigned int c = static_cast<unsigned int>(filter_[i]);
+ for (int j = 0; j < 8; j++) {
+ fprintf(stderr, "%c", (c & (1 <<j)) ? '1' : '.');
+ }
+ }
+ fprintf(stderr, ")\n");
+ }
+
+ bool Matches(const Slice& s) {
+ if (!keys_.empty()) {
+ Build();
+ }
+ return policy_->KeyMayMatch(s, filter_);
+ }
+
+ 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_F(BlockBasedBloomTest, EmptyFilter) {
+ ASSERT_TRUE(! Matches("hello"));
+ ASSERT_TRUE(! Matches("world"));
+}
+
+TEST_F(BlockBasedBloomTest, Small) {
+ Add("hello");
+ Add("world");
+ ASSERT_TRUE(Matches("hello"));
+ ASSERT_TRUE(Matches("world"));
+ ASSERT_TRUE(! Matches("x"));
+ ASSERT_TRUE(! Matches("foo"));
+}
+
+TEST_F(BlockBasedBloomTest, VaryingLengths) {
+ 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();
+
+ ASSERT_LE(FilterSize(), (size_t)((length * 10 / 8) + 40)) << length;
+
+ // 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<int>(FilterSize()));
+ }
+ ASSERT_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);
+ }
+ ASSERT_LE(mediocre_filters, good_filters/5);
+}
+
+// Ensure the implementation doesn't accidentally change in an
+// incompatible way
+TEST_F(BlockBasedBloomTest, Schema) {
+ char buffer[sizeof(int)];
+
+ ResetPolicy(8); // num_probes = 5
+ for (int key = 0; key < 87; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 3589896109U);
+
+ ResetPolicy(9); // num_probes = 6
+ for (int key = 0; key < 87; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 969445585);
+
+ ResetPolicy(11); // num_probes = 7
+ for (int key = 0; key < 87; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 1694458207);
+
+ ResetPolicy(10); // num_probes = 6
+ for (int key = 0; key < 87; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 2373646410U);
+
+ ResetPolicy(10);
+ for (int key = /*CHANGED*/ 1; key < 87; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 1908442116);
+
+ ResetPolicy(10);
+ for (int key = 1; key < /*CHANGED*/ 88; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), 3057004015U);
+
+ // With new fractional bits_per_key, check that we are rounding to
+ // whole bits per key for old Bloom filters.
+ ResetPolicy(9.5); // Treated as 10
+ for (int key = 1; key < 88; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), /*SAME*/ 3057004015U);
+
+ ResetPolicy(10.499); // Treated as 10
+ for (int key = 1; key < 88; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ ASSERT_EQ(BloomHash(FilterData()), /*SAME*/ 3057004015U);
+
+ ResetPolicy();
+}
+
+// Different bits-per-byte
+
+class FullBloomTest : public testing::TestWithParam<BloomFilterPolicy::Mode> {
+ private:
+ BlockBasedTableOptions table_options_;
+ std::shared_ptr<const FilterPolicy>& policy_;
+ std::unique_ptr<FilterBitsBuilder> bits_builder_;
+ std::unique_ptr<FilterBitsReader> bits_reader_;
+ std::unique_ptr<const char[]> 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<BuiltinFilterBitsBuilder&>(*bits_builder_);
+ }
+
+ const BloomFilterPolicy* GetBloomFilterPolicy() {
+ // Throws on bad cast
+ return &dynamic_cast<const BloomFilterPolicy&>(*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_.reset(new BloomFilterPolicy(bits_per_key, GetParam()));
+ 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<int8_t>(buf_.get()[filter_size_ - 5]);
+ if (raw_num_probes == -1) { // New bloom filter marker
+ return static_cast<uint8_t>(buf_.get()[filter_size_ - 3]);
+ } else {
+ return raw_num_probes;
+ }
+ }
+
+ 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;
+ }
+
+ uint32_t SelectByImpl(uint32_t for_legacy_bloom,
+ uint32_t for_fast_local_bloom) {
+ switch (GetParam()) {
+ case BloomFilterPolicy::kLegacyBloom:
+ return for_legacy_bloom;
+ case BloomFilterPolicy::kFastLocalBloom:
+ return for_fast_local_bloom;
+ case BloomFilterPolicy::kDeprecatedBlock:
+ case BloomFilterPolicy::kAuto:
+ /* N/A */;
+ }
+ // otherwise
+ assert(false);
+ return 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: enforced minimum is 1 bit per key (1000 millibits), and enforced
+ // maximum is 100 bits per key (100000 millibits).
+ for (auto bpk :
+ std::vector<std::pair<double, int> >{{-HUGE_VAL, 1000},
+ {-INFINITY, 1000},
+ {0.0, 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 = GetBloomFilterPolicy();
+ 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 = GetBloomFilterPolicy();
+ EXPECT_EQ(bpk.second, bfp->GetMillibitsPerKey());
+ EXPECT_EQ((bpk.second + 500) / 1000, bfp->GetWholeBitsPerKey());
+
+ auto bits_builder = GetBuiltinFilterBitsBuilder();
+ for (int n = 1; n < 100; n++) {
+ auto space = bits_builder->CalculateSpace(n);
+ auto n2 = bits_builder->CalculateNumEntry(space);
+ EXPECT_GE(n2, n);
+ auto space2 = bits_builder->CalculateSpace(n2);
+ EXPECT_EQ(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();
+
+ ASSERT_LE(FilterSize(),
+ (size_t)((length * 10 / 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<int>(FilterSize()));
+ }
+ ASSERT_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);
+ }
+ ASSERT_LE(mediocre_filters, good_filters/5);
+}
+
+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) {
+ char buffer[sizeof(int)];
+
+ // 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.
+
+ ResetPolicy(2); // num_probes = 1
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 1);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(1567096579, 1964771444, 2659542661U),
+ 3817481309U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("11,13,17,25,29,30,35,37,45,53", FirstFPs(10));
+ }
+
+ ResetPolicy(3); // num_probes = 2
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 2);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(2707206547U, 2571983456U, 218344685),
+ 2807269961U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("4,15,17,24,27,28,29,53,63,70", FirstFPs(10));
+ }
+
+ ResetPolicy(5); // num_probes = 3
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 3);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(515748486, 94611728, 2436112214U),
+ 204628445));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("15,24,29,39,53,87,89,100,103,104", FirstFPs(10));
+ }
+
+ ResetPolicy(8); // num_probes = 5
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 5);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(1302145999, 2811644657U, 756553699),
+ 355564975));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("16,60,66,126,220,238,244,256,265,287", FirstFPs(10));
+ }
+
+ ResetPolicy(9); // num_probes = 6
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 6);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(2092755149, 661139132, 1182970461),
+ 2137566013U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("156,367,791,872,945,1015,1139,1159,1265,1435", FirstFPs(10));
+ }
+
+ ResetPolicy(11); // num_probes = 7
+ for (int key = 0; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 7);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(3755609649U, 1812694762, 1449142939),
+ 2561502687U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("34,74,130,236,643,882,962,1015,1035,1110", 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(GetNumProbesFromFilterData(), SelectByImpl(9, 8));
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(178861123, 379087593, 2574136516U),
+ 3709876890U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("130,240,522,565,989,2002,2526,3147,3543", 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(GetNumProbesFromFilterData(), SelectByImpl(11, 9));
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(1129406313, 3049154394U, 1727750964),
+ 1087138490));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("3299,3611,3916,6620,7822,8079,8482,8942,10167", 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(GetNumProbesFromFilterData(), 6);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(1478976371, 2910591341U, 1182970461),
+ 2498541272U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("16,126,133,422,466,472,813,1002,1035,1159", FirstFPs(10));
+ }
+
+ ResetPolicy(10);
+ for (int key = /*CHANGED*/ 1; key < 2087; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 6);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(4205696321U, 1132081253U, 2385981855U),
+ 2058382345U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("16,126,133,422,466,472,813,1002,1035,1159", FirstFPs(10));
+ }
+
+ ResetPolicy(10);
+ for (int key = 1; key < /*CHANGED*/ 2088; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), 6);
+ EXPECT_EQ(
+ BloomHash(FilterData()),
+ SelectByImpl(SelectByCacheLineSize(2885052954U, 769447944, 4175124908U),
+ 23699164));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ("16,126,133,422,466,472,813,1002,1035,1159", 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(GetNumProbesFromFilterData(), 6);
+ EXPECT_EQ(BloomHash(FilterData()),
+ SelectByImpl(/*SAME*/ SelectByCacheLineSize(2885052954U, 769447944,
+ 4175124908U),
+ /*CHANGED*/ 3166884174U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ(/*CHANGED*/ "126,156,367,444,458,791,813,976,1015,1035",
+ FirstFPs(10));
+ }
+
+ ResetPolicy(10.499);
+ for (int key = 1; key < 2088; key++) {
+ Add(Key(key, buffer));
+ }
+ Build();
+ EXPECT_EQ(GetNumProbesFromFilterData(), SelectByImpl(6, 7));
+ EXPECT_EQ(BloomHash(FilterData()),
+ SelectByImpl(/*SAME*/ SelectByCacheLineSize(2885052954U, 769447944,
+ 4175124908U),
+ /*CHANGED*/ 4098502778U));
+ if (GetParam() == BloomFilterPolicy::kFastLocalBloom) {
+ EXPECT_EQ(/*CHANGED*/ "16,236,240,472,1015,1045,1111,1409,1465,1612",
+ 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<char, 3000> 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<char>(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<char>(0x7b7b >> (i % 7));
+ }
+ return ResetNoFill(len_without_metadata, num_lines, num_probes);
+ }
+};
+
+TEST_P(FullBloomTest, RawSchema) {
+ RawFilterTester cft;
+ // 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());
+}
+
+TEST_P(FullBloomTest, CorruptFilters) {
+ RawFilterTester cft;
+
+ for (bool fill : {false, true}) {
+ // 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(-1, 3, 6, fill));
+ ASSERT_FALSE(Matches("hello"));
+ ASSERT_FALSE(Matches("world"));
+
+ OpenRaw(cft.Reset(-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 255 / -1
+ OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 255, fill));
+ ASSERT_TRUE(Matches("hello"));
+ ASSERT_TRUE(Matches("world"));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(Full, FullBloomTest,
+ testing::Values(BloomFilterPolicy::kLegacyBloom,
+ BloomFilterPolicy::kFastLocalBloom));
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ return RUN_ALL_TESTS();
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/util/build_version.cc.in b/storage/rocksdb/rocksdb/util/build_version.cc.in
new file mode 100644
index 00000000..85695801
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/build_version.cc.in
@@ -0,0 +1,5 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#include "build_version.h"
+const char* rocksdb_build_git_sha = "rocksdb_build_git_sha:@@GIT_SHA@@";
+const char* rocksdb_build_git_date = "rocksdb_build_git_date:@@GIT_DATE_TIME@@";
+const char* rocksdb_build_compile_date = __DATE__;
diff --git a/storage/rocksdb/rocksdb/util/build_version.h b/storage/rocksdb/rocksdb/util/build_version.h
new file mode 100644
index 00000000..36ff92c0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/build_version.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#if !defined(IOS_CROSS_COMPILE)
+// if we compile with Xcode, we don't run build_detect_version, so we don't
+// generate these variables
+// this variable tells us about the git revision
+extern const char* rocksdb_build_git_sha;
+
+// Date on which the code was compiled:
+extern const char* rocksdb_build_compile_date;
+#endif
diff --git a/storage/rocksdb/rocksdb/util/cast_util.h b/storage/rocksdb/rocksdb/util/cast_util.h
new file mode 100644
index 00000000..d7d9a9e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/cast_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+namespace ROCKSDB_NAMESPACE {
+// The helper function to assert the move from dynamic_cast<> to
+// static_cast<> is correct. This function is to deal with legacy code.
+// It is not recommanded to add new code to issue class casting. The preferred
+// solution is to implement the functionality without a need of casting.
+template <class DestClass, class SrcClass>
+inline DestClass* static_cast_with_check(SrcClass* x) {
+ DestClass* ret = static_cast<DestClass*>(x);
+#ifdef ROCKSDB_USE_RTTI
+ assert(ret == dynamic_cast<DestClass*>(x));
+#endif
+ return ret;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/channel.h b/storage/rocksdb/rocksdb/util/channel.h
new file mode 100644
index 00000000..705fa2d2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/channel.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <condition_variable>
+#include <mutex>
+#include <queue>
+#include <utility>
+
+namespace ROCKSDB_NAMESPACE {
+
+template <class T>
+class channel {
+ public:
+ explicit channel() : eof_(false) {}
+
+ channel(const channel&) = delete;
+ void operator=(const channel&) = delete;
+
+ void sendEof() {
+ std::lock_guard<std::mutex> lk(lock_);
+ eof_ = true;
+ cv_.notify_all();
+ }
+
+ bool eof() {
+ std::lock_guard<std::mutex> lk(lock_);
+ return buffer_.empty() && eof_;
+ }
+
+ size_t size() const {
+ std::lock_guard<std::mutex> lk(lock_);
+ return buffer_.size();
+ }
+
+ // writes elem to the queue
+ void write(T&& elem) {
+ std::unique_lock<std::mutex> lk(lock_);
+ buffer_.emplace(std::forward<T>(elem));
+ cv_.notify_one();
+ }
+
+ /// Moves a dequeued element onto elem, blocking until an element
+ /// is available.
+ // returns false if EOF
+ bool read(T& elem) {
+ std::unique_lock<std::mutex> lk(lock_);
+ cv_.wait(lk, [&] { return eof_ || !buffer_.empty(); });
+ if (eof_ && buffer_.empty()) {
+ return false;
+ }
+ elem = std::move(buffer_.front());
+ buffer_.pop();
+ cv_.notify_one();
+ return true;
+ }
+
+ private:
+ std::condition_variable cv_;
+ mutable std::mutex lock_;
+ std::queue<T> buffer_;
+ bool eof_;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/coding.cc b/storage/rocksdb/rocksdb/util/coding.cc
new file mode 100644
index 00000000..a54324d2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/coding.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include "rocksdb/slice.h"
+#include "rocksdb/slice_transform.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// conversion' conversion from 'type1' to 'type2', possible loss of data
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4244)
+#endif
+char* EncodeVarint32(char* dst, uint32_t v) {
+ // Operate on characters as unsigneds
+ unsigned char* ptr = reinterpret_cast<unsigned char*>(dst);
+ static const int B = 128;
+ if (v < (1 << 7)) {
+ *(ptr++) = v;
+ } else if (v < (1 << 14)) {
+ *(ptr++) = v | B;
+ *(ptr++) = v >> 7;
+ } else if (v < (1 << 21)) {
+ *(ptr++) = v | B;
+ *(ptr++) = (v >> 7) | B;
+ *(ptr++) = v >> 14;
+ } else if (v < (1 << 28)) {
+ *(ptr++) = v | B;
+ *(ptr++) = (v >> 7) | B;
+ *(ptr++) = (v >> 14) | B;
+ *(ptr++) = v >> 21;
+ } else {
+ *(ptr++) = v | B;
+ *(ptr++) = (v >> 7) | B;
+ *(ptr++) = (v >> 14) | B;
+ *(ptr++) = (v >> 21) | B;
+ *(ptr++) = v >> 28;
+ }
+ return reinterpret_cast<char*>(ptr);
+}
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+const char* GetVarint32PtrFallback(const char* p, const char* limit,
+ uint32_t* value) {
+ uint32_t result = 0;
+ for (uint32_t shift = 0; shift <= 28 && p < limit; shift += 7) {
+ uint32_t byte = *(reinterpret_cast<const unsigned char*>(p));
+ p++;
+ if (byte & 128) {
+ // More bytes are present
+ result |= ((byte & 127) << shift);
+ } else {
+ result |= (byte << shift);
+ *value = result;
+ return reinterpret_cast<const char*>(p);
+ }
+ }
+ return nullptr;
+}
+
+const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
+ uint64_t result = 0;
+ for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
+ uint64_t byte = *(reinterpret_cast<const unsigned char*>(p));
+ p++;
+ if (byte & 128) {
+ // More bytes are present
+ result |= ((byte & 127) << shift);
+ } else {
+ result |= (byte << shift);
+ *value = result;
+ return reinterpret_cast<const char*>(p);
+ }
+ }
+ return nullptr;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/coding.h b/storage/rocksdb/rocksdb/util/coding.h
new file mode 100644
index 00000000..d46654ec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/coding.h
@@ -0,0 +1,480 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Endian-neutral encoding:
+// * Fixed-length numbers are encoded with least-significant byte first
+// * In addition we support variable length "varint" encoding
+// * Strings are encoded prefixed by their length in varint format
+
+#pragma once
+#include <algorithm>
+#include <stdint.h>
+#include <string.h>
+#include <string>
+
+#include "rocksdb/write_batch.h"
+#include "port/port.h"
+
+// Some processors does not allow unaligned access to memory
+#if defined(__sparc)
+ #define PLATFORM_UNALIGNED_ACCESS_NOT_ALLOWED
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+// The maximum length of a varint in bytes for 64-bit.
+const unsigned int kMaxVarint64Length = 10;
+
+// Standard Put... routines append to a string
+extern void PutFixed16(std::string* dst, uint16_t value);
+extern void PutFixed32(std::string* dst, uint32_t value);
+extern void PutFixed64(std::string* dst, uint64_t value);
+extern void PutVarint32(std::string* dst, uint32_t value);
+extern void PutVarint32Varint32(std::string* dst, uint32_t value1,
+ uint32_t value2);
+extern void PutVarint32Varint32Varint32(std::string* dst, uint32_t value1,
+ uint32_t value2, uint32_t value3);
+extern void PutVarint64(std::string* dst, uint64_t value);
+extern void PutVarint64Varint64(std::string* dst, uint64_t value1,
+ uint64_t value2);
+extern void PutVarint32Varint64(std::string* dst, uint32_t value1,
+ uint64_t value2);
+extern void PutVarint32Varint32Varint64(std::string* dst, uint32_t value1,
+ uint32_t value2, uint64_t value3);
+extern void PutLengthPrefixedSlice(std::string* dst, const Slice& value);
+extern void PutLengthPrefixedSliceParts(std::string* dst,
+ const SliceParts& slice_parts);
+extern void PutLengthPrefixedSlicePartsWithPadding(
+ std::string* dst, const SliceParts& slice_parts, size_t pad_sz);
+
+// Standard Get... routines parse a value from the beginning of a Slice
+// and advance the slice past the parsed value.
+extern bool GetFixed64(Slice* input, uint64_t* value);
+extern bool GetFixed32(Slice* input, uint32_t* value);
+extern bool GetFixed16(Slice* input, uint16_t* value);
+extern bool GetVarint32(Slice* input, uint32_t* value);
+extern bool GetVarint64(Slice* input, uint64_t* value);
+extern bool GetVarsignedint64(Slice* input, int64_t* value);
+extern bool GetLengthPrefixedSlice(Slice* input, Slice* result);
+// This function assumes data is well-formed.
+extern Slice GetLengthPrefixedSlice(const char* data);
+
+extern Slice GetSliceUntil(Slice* slice, char delimiter);
+
+// Borrowed from
+// https://github.com/facebook/fbthrift/blob/449a5f77f9f9bae72c9eb5e78093247eef185c04/thrift/lib/cpp/util/VarintUtils-inl.h#L202-L208
+constexpr inline uint64_t i64ToZigzag(const int64_t l) {
+ return (static_cast<uint64_t>(l) << 1) ^ static_cast<uint64_t>(l >> 63);
+}
+inline int64_t zigzagToI64(uint64_t n) {
+ return (n >> 1) ^ -static_cast<int64_t>(n & 1);
+}
+
+// Pointer-based variants of GetVarint... These either store a value
+// in *v and return a pointer just past the parsed value, or return
+// nullptr on error. These routines only look at bytes in the range
+// [p..limit-1]
+extern const char* GetVarint32Ptr(const char* p,const char* limit, uint32_t* v);
+extern const char* GetVarint64Ptr(const char* p,const char* limit, uint64_t* v);
+inline const char* GetVarsignedint64Ptr(const char* p, const char* limit,
+ int64_t* value) {
+ uint64_t u = 0;
+ const char* ret = GetVarint64Ptr(p, limit, &u);
+ *value = zigzagToI64(u);
+ return ret;
+}
+
+// Returns the length of the varint32 or varint64 encoding of "v"
+extern int VarintLength(uint64_t v);
+
+// Lower-level versions of Put... that write directly into a character buffer
+// REQUIRES: dst has enough space for the value being written
+extern void EncodeFixed16(char* dst, uint16_t value);
+extern void EncodeFixed32(char* dst, uint32_t value);
+extern void EncodeFixed64(char* dst, uint64_t value);
+
+// Lower-level versions of Put... that write directly into a character buffer
+// and return a pointer just past the last byte written.
+// REQUIRES: dst has enough space for the value being written
+extern char* EncodeVarint32(char* dst, uint32_t value);
+extern char* EncodeVarint64(char* dst, uint64_t value);
+
+// Lower-level versions of Get... that read directly from a character buffer
+// without any bounds checking.
+
+inline uint16_t DecodeFixed16(const char* ptr) {
+ if (port::kLittleEndian) {
+ // Load the raw bytes
+ uint16_t result;
+ memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
+ return result;
+ } else {
+ return ((static_cast<uint16_t>(static_cast<unsigned char>(ptr[0]))) |
+ (static_cast<uint16_t>(static_cast<unsigned char>(ptr[1])) << 8));
+ }
+}
+
+inline uint32_t DecodeFixed32(const char* ptr) {
+ if (port::kLittleEndian) {
+ // Load the raw bytes
+ uint32_t result;
+ memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
+ return result;
+ } else {
+ return ((static_cast<uint32_t>(static_cast<unsigned char>(ptr[0])))
+ | (static_cast<uint32_t>(static_cast<unsigned char>(ptr[1])) << 8)
+ | (static_cast<uint32_t>(static_cast<unsigned char>(ptr[2])) << 16)
+ | (static_cast<uint32_t>(static_cast<unsigned char>(ptr[3])) << 24));
+ }
+}
+
+inline uint64_t DecodeFixed64(const char* ptr) {
+ if (port::kLittleEndian) {
+ // Load the raw bytes
+ uint64_t result;
+ memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
+ return result;
+ } else {
+ uint64_t lo = DecodeFixed32(ptr);
+ uint64_t hi = DecodeFixed32(ptr + 4);
+ return (hi << 32) | lo;
+ }
+}
+
+// Internal routine for use by fallback path of GetVarint32Ptr
+extern const char* GetVarint32PtrFallback(const char* p,
+ const char* limit,
+ uint32_t* value);
+inline const char* GetVarint32Ptr(const char* p,
+ const char* limit,
+ uint32_t* value) {
+ if (p < limit) {
+ uint32_t result = *(reinterpret_cast<const unsigned char*>(p));
+ if ((result & 128) == 0) {
+ *value = result;
+ return p + 1;
+ }
+ }
+ return GetVarint32PtrFallback(p, limit, value);
+}
+
+// -- Implementation of the functions declared above
+inline void EncodeFixed16(char* buf, uint16_t value) {
+ if (port::kLittleEndian) {
+ memcpy(buf, &value, sizeof(value));
+ } else {
+ buf[0] = value & 0xff;
+ buf[1] = (value >> 8) & 0xff;
+ }
+}
+
+inline void EncodeFixed32(char* buf, uint32_t value) {
+ if (port::kLittleEndian) {
+ memcpy(buf, &value, sizeof(value));
+ } else {
+ buf[0] = value & 0xff;
+ buf[1] = (value >> 8) & 0xff;
+ buf[2] = (value >> 16) & 0xff;
+ buf[3] = (value >> 24) & 0xff;
+ }
+}
+
+inline void EncodeFixed64(char* buf, uint64_t value) {
+ if (port::kLittleEndian) {
+ memcpy(buf, &value, sizeof(value));
+ } else {
+ buf[0] = value & 0xff;
+ buf[1] = (value >> 8) & 0xff;
+ buf[2] = (value >> 16) & 0xff;
+ buf[3] = (value >> 24) & 0xff;
+ buf[4] = (value >> 32) & 0xff;
+ buf[5] = (value >> 40) & 0xff;
+ buf[6] = (value >> 48) & 0xff;
+ buf[7] = (value >> 56) & 0xff;
+ }
+}
+
+// Pull the last 8 bits and cast it to a character
+inline void PutFixed16(std::string* dst, uint16_t value) {
+ if (port::kLittleEndian) {
+ dst->append(const_cast<const char*>(reinterpret_cast<char*>(&value)),
+ sizeof(value));
+ } else {
+ char buf[sizeof(value)];
+ EncodeFixed16(buf, value);
+ dst->append(buf, sizeof(buf));
+ }
+}
+
+inline void PutFixed32(std::string* dst, uint32_t value) {
+ if (port::kLittleEndian) {
+ dst->append(const_cast<const char*>(reinterpret_cast<char*>(&value)),
+ sizeof(value));
+ } else {
+ char buf[sizeof(value)];
+ EncodeFixed32(buf, value);
+ dst->append(buf, sizeof(buf));
+ }
+}
+
+inline void PutFixed64(std::string* dst, uint64_t value) {
+ if (port::kLittleEndian) {
+ dst->append(const_cast<const char*>(reinterpret_cast<char*>(&value)),
+ sizeof(value));
+ } else {
+ char buf[sizeof(value)];
+ EncodeFixed64(buf, value);
+ dst->append(buf, sizeof(buf));
+ }
+}
+
+inline void PutVarint32(std::string* dst, uint32_t v) {
+ char buf[5];
+ char* ptr = EncodeVarint32(buf, v);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarint32Varint32(std::string* dst, uint32_t v1, uint32_t v2) {
+ char buf[10];
+ char* ptr = EncodeVarint32(buf, v1);
+ ptr = EncodeVarint32(ptr, v2);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarint32Varint32Varint32(std::string* dst, uint32_t v1,
+ uint32_t v2, uint32_t v3) {
+ char buf[15];
+ char* ptr = EncodeVarint32(buf, v1);
+ ptr = EncodeVarint32(ptr, v2);
+ ptr = EncodeVarint32(ptr, v3);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline char* EncodeVarint64(char* dst, uint64_t v) {
+ static const unsigned int B = 128;
+ unsigned char* ptr = reinterpret_cast<unsigned char*>(dst);
+ while (v >= B) {
+ *(ptr++) = (v & (B - 1)) | B;
+ v >>= 7;
+ }
+ *(ptr++) = static_cast<unsigned char>(v);
+ return reinterpret_cast<char*>(ptr);
+}
+
+inline void PutVarint64(std::string* dst, uint64_t v) {
+ char buf[kMaxVarint64Length];
+ char* ptr = EncodeVarint64(buf, v);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarsignedint64(std::string* dst, int64_t v) {
+ char buf[kMaxVarint64Length];
+ // Using Zigzag format to convert signed to unsigned
+ char* ptr = EncodeVarint64(buf, i64ToZigzag(v));
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarint64Varint64(std::string* dst, uint64_t v1, uint64_t v2) {
+ char buf[20];
+ char* ptr = EncodeVarint64(buf, v1);
+ ptr = EncodeVarint64(ptr, v2);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarint32Varint64(std::string* dst, uint32_t v1, uint64_t v2) {
+ char buf[15];
+ char* ptr = EncodeVarint32(buf, v1);
+ ptr = EncodeVarint64(ptr, v2);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutVarint32Varint32Varint64(std::string* dst, uint32_t v1,
+ uint32_t v2, uint64_t v3) {
+ char buf[20];
+ char* ptr = EncodeVarint32(buf, v1);
+ ptr = EncodeVarint32(ptr, v2);
+ ptr = EncodeVarint64(ptr, v3);
+ dst->append(buf, static_cast<size_t>(ptr - buf));
+}
+
+inline void PutLengthPrefixedSlice(std::string* dst, const Slice& value) {
+ PutVarint32(dst, static_cast<uint32_t>(value.size()));
+ dst->append(value.data(), value.size());
+}
+
+inline void PutLengthPrefixedSliceParts(std::string* dst, size_t total_bytes,
+ const SliceParts& slice_parts) {
+ for (int i = 0; i < slice_parts.num_parts; ++i) {
+ total_bytes += slice_parts.parts[i].size();
+ }
+ PutVarint32(dst, static_cast<uint32_t>(total_bytes));
+ for (int i = 0; i < slice_parts.num_parts; ++i) {
+ dst->append(slice_parts.parts[i].data(), slice_parts.parts[i].size());
+ }
+}
+
+inline void PutLengthPrefixedSliceParts(std::string* dst,
+ const SliceParts& slice_parts) {
+ PutLengthPrefixedSliceParts(dst, /*total_bytes=*/0, slice_parts);
+}
+
+inline void PutLengthPrefixedSlicePartsWithPadding(
+ std::string* dst, const SliceParts& slice_parts, size_t pad_sz) {
+ PutLengthPrefixedSliceParts(dst, /*total_bytes=*/pad_sz, slice_parts);
+ dst->append(pad_sz, '\0');
+}
+
+inline int VarintLength(uint64_t v) {
+ int len = 1;
+ while (v >= 128) {
+ v >>= 7;
+ len++;
+ }
+ return len;
+}
+
+inline bool GetFixed64(Slice* input, uint64_t* value) {
+ if (input->size() < sizeof(uint64_t)) {
+ return false;
+ }
+ *value = DecodeFixed64(input->data());
+ input->remove_prefix(sizeof(uint64_t));
+ return true;
+}
+
+inline bool GetFixed32(Slice* input, uint32_t* value) {
+ if (input->size() < sizeof(uint32_t)) {
+ return false;
+ }
+ *value = DecodeFixed32(input->data());
+ input->remove_prefix(sizeof(uint32_t));
+ return true;
+}
+
+inline bool GetFixed16(Slice* input, uint16_t* value) {
+ if (input->size() < sizeof(uint16_t)) {
+ return false;
+ }
+ *value = DecodeFixed16(input->data());
+ input->remove_prefix(sizeof(uint16_t));
+ return true;
+}
+
+inline bool GetVarint32(Slice* input, uint32_t* value) {
+ const char* p = input->data();
+ const char* limit = p + input->size();
+ const char* q = GetVarint32Ptr(p, limit, value);
+ if (q == nullptr) {
+ return false;
+ } else {
+ *input = Slice(q, static_cast<size_t>(limit - q));
+ return true;
+ }
+}
+
+inline bool GetVarint64(Slice* input, uint64_t* value) {
+ const char* p = input->data();
+ const char* limit = p + input->size();
+ const char* q = GetVarint64Ptr(p, limit, value);
+ if (q == nullptr) {
+ return false;
+ } else {
+ *input = Slice(q, static_cast<size_t>(limit - q));
+ return true;
+ }
+}
+
+inline bool GetVarsignedint64(Slice* input, int64_t* value) {
+ const char* p = input->data();
+ const char* limit = p + input->size();
+ const char* q = GetVarsignedint64Ptr(p, limit, value);
+ if (q == nullptr) {
+ return false;
+ } else {
+ *input = Slice(q, static_cast<size_t>(limit - q));
+ return true;
+ }
+}
+
+// Provide an interface for platform independent endianness transformation
+inline uint64_t EndianTransform(uint64_t input, size_t size) {
+ char* pos = reinterpret_cast<char*>(&input);
+ uint64_t ret_val = 0;
+ for (size_t i = 0; i < size; ++i) {
+ ret_val |= (static_cast<uint64_t>(static_cast<unsigned char>(pos[i]))
+ << ((size - i - 1) << 3));
+ }
+ return ret_val;
+}
+
+inline bool GetLengthPrefixedSlice(Slice* input, Slice* result) {
+ uint32_t len = 0;
+ if (GetVarint32(input, &len) && input->size() >= len) {
+ *result = Slice(input->data(), len);
+ input->remove_prefix(len);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+inline Slice GetLengthPrefixedSlice(const char* data) {
+ uint32_t len = 0;
+ // +5: we assume "data" is not corrupted
+ // unsigned char is 7 bits, uint32_t is 32 bits, need 5 unsigned char
+ auto p = GetVarint32Ptr(data, data + 5 /* limit */, &len);
+ return Slice(p, len);
+}
+
+inline Slice GetSliceUntil(Slice* slice, char delimiter) {
+ uint32_t len = 0;
+ for (len = 0; len < slice->size() && slice->data()[len] != delimiter; ++len) {
+ // nothing
+ }
+
+ Slice ret(slice->data(), len);
+ slice->remove_prefix(len + ((len < slice->size()) ? 1 : 0));
+ return ret;
+}
+
+template<class T>
+#ifdef ROCKSDB_UBSAN_RUN
+#if defined(__clang__)
+__attribute__((__no_sanitize__("alignment")))
+#elif defined(__GNUC__)
+__attribute__((__no_sanitize_undefined__))
+#endif
+#endif
+inline void PutUnaligned(T *memory, const T &value) {
+#if defined(PLATFORM_UNALIGNED_ACCESS_NOT_ALLOWED)
+ char *nonAlignedMemory = reinterpret_cast<char*>(memory);
+ memcpy(nonAlignedMemory, reinterpret_cast<const char*>(&value), sizeof(T));
+#else
+ *memory = value;
+#endif
+}
+
+template<class T>
+#ifdef ROCKSDB_UBSAN_RUN
+#if defined(__clang__)
+__attribute__((__no_sanitize__("alignment")))
+#elif defined(__GNUC__)
+__attribute__((__no_sanitize_undefined__))
+#endif
+#endif
+inline void GetUnaligned(const T *memory, T *value) {
+#if defined(PLATFORM_UNALIGNED_ACCESS_NOT_ALLOWED)
+ char *nonAlignedMemory = reinterpret_cast<char*>(value);
+ memcpy(nonAlignedMemory, reinterpret_cast<const char*>(memory), sizeof(T));
+#else
+ *value = *memory;
+#endif
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/coding_test.cc b/storage/rocksdb/rocksdb/util/coding_test.cc
new file mode 100644
index 00000000..383e3f51
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/coding_test.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<uint64_t>(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<uint64_t>(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<int>(dst[0]));
+ ASSERT_EQ(0x02, static_cast<int>(dst[1]));
+ ASSERT_EQ(0x03, static_cast<int>(dst[2]));
+ ASSERT_EQ(0x04, static_cast<int>(dst[3]));
+
+ dst.clear();
+ PutFixed64(&dst, 0x0807060504030201ull);
+ ASSERT_EQ(8U, dst.size());
+ ASSERT_EQ(0x01, static_cast<int>(dst[0]));
+ ASSERT_EQ(0x02, static_cast<int>(dst[1]));
+ ASSERT_EQ(0x03, static_cast<int>(dst[2]));
+ ASSERT_EQ(0x04, static_cast<int>(dst[3]));
+ ASSERT_EQ(0x05, static_cast<int>(dst[4]));
+ ASSERT_EQ(0x06, static_cast<int>(dst[5]));
+ ASSERT_EQ(0x07, static_cast<int>(dst[6]));
+ ASSERT_EQ(0x08, static_cast<int>(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<uint64_t> values;
+ // Some special values
+ values.push_back(0);
+ values.push_back(100);
+ values.push_back(~static_cast<uint64_t>(0));
+ values.push_back(~static_cast<uint64_t>(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 < s.size() - 1; 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 < s.size() - 1; 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/compaction_job_stats_impl.cc b/storage/rocksdb/rocksdb/util/compaction_job_stats_impl.cc
new file mode 100644
index 00000000..4d16891e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/compaction_job_stats_impl.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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_job_stats.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+void CompactionJobStats::Reset() {
+ elapsed_micros = 0;
+ cpu_micros = 0;
+
+ num_input_records = 0;
+ num_input_files = 0;
+ num_input_files_at_output_level = 0;
+
+ num_output_records = 0;
+ num_output_files = 0;
+
+ is_manual_compaction = 0;
+
+ total_input_bytes = 0;
+ total_output_bytes = 0;
+
+ num_records_replaced = 0;
+
+ total_input_raw_key_bytes = 0;
+ total_input_raw_value_bytes = 0;
+
+ num_input_deletion_records = 0;
+ num_expired_deletion_records = 0;
+
+ num_corrupt_keys = 0;
+
+ file_write_nanos = 0;
+ file_range_sync_nanos = 0;
+ file_fsync_nanos = 0;
+ file_prepare_write_nanos = 0;
+
+ smallest_output_key_prefix.clear();
+ largest_output_key_prefix.clear();
+
+ num_single_del_fallthru = 0;
+ num_single_del_mismatch = 0;
+}
+
+void CompactionJobStats::Add(const CompactionJobStats& stats) {
+ elapsed_micros += stats.elapsed_micros;
+ cpu_micros += stats.cpu_micros;
+
+ num_input_records += stats.num_input_records;
+ num_input_files += stats.num_input_files;
+ num_input_files_at_output_level += stats.num_input_files_at_output_level;
+
+ num_output_records += stats.num_output_records;
+ num_output_files += stats.num_output_files;
+
+ total_input_bytes += stats.total_input_bytes;
+ total_output_bytes += stats.total_output_bytes;
+
+ num_records_replaced += stats.num_records_replaced;
+
+ total_input_raw_key_bytes += stats.total_input_raw_key_bytes;
+ total_input_raw_value_bytes += stats.total_input_raw_value_bytes;
+
+ num_input_deletion_records += stats.num_input_deletion_records;
+ num_expired_deletion_records += stats.num_expired_deletion_records;
+
+ num_corrupt_keys += stats.num_corrupt_keys;
+
+ file_write_nanos += stats.file_write_nanos;
+ file_range_sync_nanos += stats.file_range_sync_nanos;
+ file_fsync_nanos += stats.file_fsync_nanos;
+ file_prepare_write_nanos += stats.file_prepare_write_nanos;
+
+ num_single_del_fallthru += stats.num_single_del_fallthru;
+ num_single_del_mismatch += stats.num_single_del_mismatch;
+}
+
+#else
+
+void CompactionJobStats::Reset() {}
+
+void CompactionJobStats::Add(const CompactionJobStats& /*stats*/) {}
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/comparator.cc b/storage/rocksdb/rocksdb/util/comparator.cc
new file mode 100644
index 00000000..48340bd9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/comparator.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/comparator.h"
+#include <stdint.h>
+#include <algorithm>
+#include <memory>
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+class BytewiseComparatorImpl : public Comparator {
+ public:
+ BytewiseComparatorImpl() { }
+
+ const char* Name() const override { return "leveldb.BytewiseComparator"; }
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ return a.compare(b);
+ }
+
+ bool Equal(const Slice& a, const Slice& b) const override { return a == b; }
+
+ void FindShortestSeparator(std::string* start,
+ const Slice& limit) const override {
+ // Find length of common prefix
+ size_t min_length = std::min(start->size(), limit.size());
+ size_t diff_index = 0;
+ while ((diff_index < min_length) &&
+ ((*start)[diff_index] == limit[diff_index])) {
+ diff_index++;
+ }
+
+ if (diff_index >= min_length) {
+ // Do not shorten if one string is a prefix of the other
+ } else {
+ uint8_t start_byte = static_cast<uint8_t>((*start)[diff_index]);
+ uint8_t limit_byte = static_cast<uint8_t>(limit[diff_index]);
+ if (start_byte >= limit_byte) {
+ // Cannot shorten since limit is smaller than start or start is
+ // already the shortest possible.
+ return;
+ }
+ assert(start_byte < limit_byte);
+
+ if (diff_index < limit.size() - 1 || start_byte + 1 < limit_byte) {
+ (*start)[diff_index]++;
+ start->resize(diff_index + 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.
+ diff_index++;
+
+ while (diff_index < start->size()) {
+ // Keep moving until we find the first non 0xFF byte to
+ // increment it
+ if (static_cast<uint8_t>((*start)[diff_index]) <
+ static_cast<uint8_t>(0xff)) {
+ (*start)[diff_index]++;
+ start->resize(diff_index + 1);
+ break;
+ }
+ diff_index++;
+ }
+ }
+ assert(Compare(*start, limit) < 0);
+ }
+ }
+
+ void FindShortSuccessor(std::string* key) const override {
+ // Find first character that can be incremented
+ size_t n = key->size();
+ for (size_t i = 0; i < n; i++) {
+ const uint8_t byte = (*key)[i];
+ if (byte != static_cast<uint8_t>(0xff)) {
+ (*key)[i] = byte + 1;
+ key->resize(i+1);
+ return;
+ }
+ }
+ // *key is a run of 0xffs. Leave it alone.
+ }
+
+ bool IsSameLengthImmediateSuccessor(const Slice& s,
+ const Slice& t) const override {
+ if (s.size() != t.size() || s.size() == 0) {
+ return false;
+ }
+ size_t diff_ind = s.difference_offset(t);
+ // same slice
+ if (diff_ind >= s.size()) return false;
+ uint8_t byte_s = static_cast<uint8_t>(s[diff_ind]);
+ uint8_t byte_t = static_cast<uint8_t>(t[diff_ind]);
+ // first different byte must be consecutive, and remaining bytes must be
+ // 0xff for s and 0x00 for t
+ if (byte_s != uint8_t{0xff} && byte_s + 1 == byte_t) {
+ for (size_t i = diff_ind + 1; i < s.size(); ++i) {
+ byte_s = static_cast<uint8_t>(s[i]);
+ byte_t = static_cast<uint8_t>(t[i]);
+ if (byte_s != uint8_t{0xff} || byte_t != uint8_t{0x00}) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool CanKeysWithDifferentByteContentsBeEqual() const override {
+ return false;
+ }
+
+ int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
+ return a.compare(b);
+ }
+};
+
+class ReverseBytewiseComparatorImpl : public BytewiseComparatorImpl {
+ public:
+ ReverseBytewiseComparatorImpl() { }
+
+ const char* Name() const override {
+ return "rocksdb.ReverseBytewiseComparator";
+ }
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ return -a.compare(b);
+ }
+
+ void FindShortestSeparator(std::string* start,
+ const Slice& limit) const override {
+ // Find length of common prefix
+ size_t min_length = std::min(start->size(), limit.size());
+ size_t diff_index = 0;
+ while ((diff_index < min_length) &&
+ ((*start)[diff_index] == limit[diff_index])) {
+ diff_index++;
+ }
+
+ assert(diff_index <= min_length);
+ if (diff_index == min_length) {
+ // 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 {
+ uint8_t start_byte = static_cast<uint8_t>((*start)[diff_index]);
+ uint8_t limit_byte = static_cast<uint8_t>(limit[diff_index]);
+ if (start_byte > limit_byte && diff_index < start->size() - 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->resize(diff_index + 1);
+#ifndef NDEBUG
+ assert(old_start >= *start);
+#endif
+ assert(Slice(*start).compare(limit) > 0);
+ }
+ }
+ }
+
+ void FindShortSuccessor(std::string* /*key*/) const override {
+ // Don't do anything for simplicity.
+ }
+
+ bool CanKeysWithDifferentByteContentsBeEqual() const override {
+ return false;
+ }
+
+ int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
+ return -a.compare(b);
+ }
+};
+}// namespace
+
+const Comparator* BytewiseComparator() {
+ static BytewiseComparatorImpl bytewise;
+ return &bytewise;
+}
+
+const Comparator* ReverseBytewiseComparator() {
+ static ReverseBytewiseComparatorImpl rbytewise;
+ return &rbytewise;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/compression.h b/storage/rocksdb/rocksdb/util/compression.h
new file mode 100644
index 00000000..8169841b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/compression.h
@@ -0,0 +1,1407 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <limits>
+#ifdef ROCKSDB_MALLOC_USABLE_SIZE
+#ifdef OS_FREEBSD
+#include <malloc_np.h>
+#else // OS_FREEBSD
+#include <malloc.h>
+#endif // OS_FREEBSD
+#endif // ROCKSDB_MALLOC_USABLE_SIZE
+#include <string>
+
+#include "memory/memory_allocator.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+#include "util/coding.h"
+#include "util/compression_context_cache.h"
+#include "util/string_util.h"
+
+#ifdef SNAPPY
+#include <snappy.h>
+#endif
+
+#ifdef ZLIB
+#include <zlib.h>
+#endif
+
+#ifdef BZIP2
+#include <bzlib.h>
+#endif
+
+#if defined(LZ4)
+#include <lz4.h>
+#include <lz4hc.h>
+#endif
+
+#if defined(ZSTD)
+#include <zstd.h>
+#if ZSTD_VERSION_NUMBER >= 10103 // v1.1.3+
+#include <zdict.h>
+#endif // ZSTD_VERSION_NUMBER >= 10103
+namespace ROCKSDB_NAMESPACE {
+// Need this for the context allocation override
+// On windows we need to do this explicitly
+#if (ZSTD_VERSION_NUMBER >= 500)
+#if defined(ROCKSDB_JEMALLOC) && defined(OS_WIN) && \
+ defined(ZSTD_STATIC_LINKING_ONLY)
+#define ROCKSDB_ZSTD_CUSTOM_MEM
+namespace port {
+ZSTD_customMem GetJeZstdAllocationOverrides();
+} // namespace port
+#endif // defined(ROCKSDB_JEMALLOC) && defined(OS_WIN) &&
+ // defined(ZSTD_STATIC_LINKING_ONLY)
+
+// We require `ZSTD_sizeof_DDict` and `ZSTD_createDDict_byReference` to use
+// `ZSTD_DDict`. The former was introduced in v1.0.0 and the latter was
+// introduced in v1.1.3. But an important bug fix for `ZSTD_sizeof_DDict` came
+// in v1.1.4, so that is the version we require. As of today's latest version
+// (v1.3.8), they are both still in the experimental API, which means they are
+// only exported when the compiler flag `ZSTD_STATIC_LINKING_ONLY` is set.
+#if defined(ZSTD_STATIC_LINKING_ONLY) && ZSTD_VERSION_NUMBER >= 10104
+#define ROCKSDB_ZSTD_DDICT
+#endif // defined(ZSTD_STATIC_LINKING_ONLY) && ZSTD_VERSION_NUMBER >= 10104
+
+// Cached data represents a portion that can be re-used
+// If, in the future we have more than one native context to
+// cache we can arrange this as a tuple
+class ZSTDUncompressCachedData {
+ public:
+ using ZSTDNativeContext = ZSTD_DCtx*;
+ ZSTDUncompressCachedData() {}
+ // Init from cache
+ ZSTDUncompressCachedData(const ZSTDUncompressCachedData& o) = delete;
+ ZSTDUncompressCachedData& operator=(const ZSTDUncompressCachedData&) = delete;
+ ZSTDUncompressCachedData(ZSTDUncompressCachedData&& o) ROCKSDB_NOEXCEPT
+ : ZSTDUncompressCachedData() {
+ *this = std::move(o);
+ }
+ ZSTDUncompressCachedData& operator=(ZSTDUncompressCachedData&& o)
+ ROCKSDB_NOEXCEPT {
+ assert(zstd_ctx_ == nullptr);
+ std::swap(zstd_ctx_, o.zstd_ctx_);
+ std::swap(cache_idx_, o.cache_idx_);
+ return *this;
+ }
+ ZSTDNativeContext Get() const { return zstd_ctx_; }
+ int64_t GetCacheIndex() const { return cache_idx_; }
+ void CreateIfNeeded() {
+ if (zstd_ctx_ == nullptr) {
+#ifdef ROCKSDB_ZSTD_CUSTOM_MEM
+ zstd_ctx_ =
+ ZSTD_createDCtx_advanced(port::GetJeZstdAllocationOverrides());
+#else // ROCKSDB_ZSTD_CUSTOM_MEM
+ zstd_ctx_ = ZSTD_createDCtx();
+#endif // ROCKSDB_ZSTD_CUSTOM_MEM
+ cache_idx_ = -1;
+ }
+ }
+ void InitFromCache(const ZSTDUncompressCachedData& o, int64_t idx) {
+ zstd_ctx_ = o.zstd_ctx_;
+ cache_idx_ = idx;
+ }
+ ~ZSTDUncompressCachedData() {
+ if (zstd_ctx_ != nullptr && cache_idx_ == -1) {
+ ZSTD_freeDCtx(zstd_ctx_);
+ }
+ }
+
+ private:
+ ZSTDNativeContext zstd_ctx_ = nullptr;
+ int64_t cache_idx_ = -1; // -1 means this instance owns the context
+};
+#endif // (ZSTD_VERSION_NUMBER >= 500)
+} // namespace ROCKSDB_NAMESPACE
+#endif // ZSTD
+
+#if !(defined ZSTD) || !(ZSTD_VERSION_NUMBER >= 500)
+namespace ROCKSDB_NAMESPACE {
+class ZSTDUncompressCachedData {
+ void* padding; // unused
+ public:
+ using ZSTDNativeContext = void*;
+ ZSTDUncompressCachedData() {}
+ ZSTDUncompressCachedData(const ZSTDUncompressCachedData&) {}
+ ZSTDUncompressCachedData& operator=(const ZSTDUncompressCachedData&) = delete;
+ ZSTDUncompressCachedData(ZSTDUncompressCachedData&&)
+ ROCKSDB_NOEXCEPT = default;
+ ZSTDUncompressCachedData& operator=(ZSTDUncompressCachedData&&)
+ ROCKSDB_NOEXCEPT = default;
+ ZSTDNativeContext Get() const { return nullptr; }
+ int64_t GetCacheIndex() const { return -1; }
+ void CreateIfNeeded() {}
+ void InitFromCache(const ZSTDUncompressCachedData&, int64_t) {}
+ private:
+ void ignore_padding__() { padding = nullptr; }
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif
+
+#if defined(XPRESS)
+#include "port/xpress.h"
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+// Holds dictionary and related data, like ZSTD's digested compression
+// dictionary.
+struct CompressionDict {
+#if ZSTD_VERSION_NUMBER >= 700
+ ZSTD_CDict* zstd_cdict_ = nullptr;
+#endif // ZSTD_VERSION_NUMBER >= 700
+ std::string dict_;
+
+ public:
+#if ZSTD_VERSION_NUMBER >= 700
+ CompressionDict(std::string dict, CompressionType type, int level) {
+#else // ZSTD_VERSION_NUMBER >= 700
+ CompressionDict(std::string dict, CompressionType /*type*/, int /*level*/) {
+#endif // ZSTD_VERSION_NUMBER >= 700
+ dict_ = std::move(dict);
+#if ZSTD_VERSION_NUMBER >= 700
+ zstd_cdict_ = nullptr;
+ if (!dict_.empty() && (type == kZSTD || type == kZSTDNotFinalCompression)) {
+ if (level == CompressionOptions::kDefaultCompressionLevel) {
+ // 3 is the value of ZSTD_CLEVEL_DEFAULT (not exposed publicly), see
+ // https://github.com/facebook/zstd/issues/1148
+ level = 3;
+ }
+ // Should be safe (but slower) if below call fails as we'll use the
+ // raw dictionary to compress.
+ zstd_cdict_ = ZSTD_createCDict(dict_.data(), dict_.size(), level);
+ assert(zstd_cdict_ != nullptr);
+ }
+#endif // ZSTD_VERSION_NUMBER >= 700
+ }
+
+ ~CompressionDict() {
+#if ZSTD_VERSION_NUMBER >= 700
+ size_t res = 0;
+ if (zstd_cdict_ != nullptr) {
+ res = ZSTD_freeCDict(zstd_cdict_);
+ }
+ assert(res == 0); // Last I checked they can't fail
+ (void)res; // prevent unused var warning
+#endif // ZSTD_VERSION_NUMBER >= 700
+ }
+
+#if ZSTD_VERSION_NUMBER >= 700
+ const ZSTD_CDict* GetDigestedZstdCDict() const { return zstd_cdict_; }
+#endif // ZSTD_VERSION_NUMBER >= 700
+
+ Slice GetRawDict() const { return dict_; }
+
+ static const CompressionDict& GetEmptyDict() {
+ static CompressionDict empty_dict{};
+ return empty_dict;
+ }
+
+ CompressionDict() = default;
+ // Disable copy/move
+ CompressionDict(const CompressionDict&) = delete;
+ CompressionDict& operator=(const CompressionDict&) = delete;
+ CompressionDict(CompressionDict&&) = delete;
+ CompressionDict& operator=(CompressionDict&&) = delete;
+};
+
+// Holds dictionary and related data, like ZSTD's digested uncompression
+// dictionary.
+struct UncompressionDict {
+ // Block containing the data for the compression dictionary in case the
+ // constructor that takes a string parameter is used.
+ std::string dict_;
+
+ // Block containing the data for the compression dictionary in case the
+ // constructor that takes a Slice parameter is used and the passed in
+ // CacheAllocationPtr is not nullptr.
+ CacheAllocationPtr allocation_;
+
+ // Slice pointing to the compression dictionary data. Can point to
+ // dict_, allocation_, or some other memory location, depending on how
+ // the object was constructed.
+ Slice slice_;
+
+#ifdef ROCKSDB_ZSTD_DDICT
+ // Processed version of the contents of slice_ for ZSTD compression.
+ ZSTD_DDict* zstd_ddict_ = nullptr;
+#endif // ROCKSDB_ZSTD_DDICT
+
+#ifdef ROCKSDB_ZSTD_DDICT
+ UncompressionDict(std::string dict, bool using_zstd)
+#else // ROCKSDB_ZSTD_DDICT
+ UncompressionDict(std::string dict, bool /* using_zstd */)
+#endif // ROCKSDB_ZSTD_DDICT
+ : dict_(std::move(dict)), slice_(dict_) {
+#ifdef ROCKSDB_ZSTD_DDICT
+ if (!slice_.empty() && using_zstd) {
+ zstd_ddict_ = ZSTD_createDDict_byReference(slice_.data(), slice_.size());
+ assert(zstd_ddict_ != nullptr);
+ }
+#endif // ROCKSDB_ZSTD_DDICT
+ }
+
+#ifdef ROCKSDB_ZSTD_DDICT
+ UncompressionDict(Slice slice, CacheAllocationPtr&& allocation,
+ bool using_zstd)
+#else // ROCKSDB_ZSTD_DDICT
+ UncompressionDict(Slice slice, CacheAllocationPtr&& allocation,
+ bool /* using_zstd */)
+#endif // ROCKSDB_ZSTD_DDICT
+ : allocation_(std::move(allocation)), slice_(std::move(slice)) {
+#ifdef ROCKSDB_ZSTD_DDICT
+ if (!slice_.empty() && using_zstd) {
+ zstd_ddict_ = ZSTD_createDDict_byReference(slice_.data(), slice_.size());
+ assert(zstd_ddict_ != nullptr);
+ }
+#endif // ROCKSDB_ZSTD_DDICT
+ }
+
+ UncompressionDict(UncompressionDict&& rhs)
+ : dict_(std::move(rhs.dict_)),
+ allocation_(std::move(rhs.allocation_)),
+ slice_(std::move(rhs.slice_))
+#ifdef ROCKSDB_ZSTD_DDICT
+ ,
+ zstd_ddict_(rhs.zstd_ddict_)
+#endif
+ {
+#ifdef ROCKSDB_ZSTD_DDICT
+ rhs.zstd_ddict_ = nullptr;
+#endif
+ }
+
+ ~UncompressionDict() {
+#ifdef ROCKSDB_ZSTD_DDICT
+ size_t res = 0;
+ if (zstd_ddict_ != nullptr) {
+ res = ZSTD_freeDDict(zstd_ddict_);
+ }
+ assert(res == 0); // Last I checked they can't fail
+ (void)res; // prevent unused var warning
+#endif // ROCKSDB_ZSTD_DDICT
+ }
+
+ UncompressionDict& operator=(UncompressionDict&& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ dict_ = std::move(rhs.dict_);
+ allocation_ = std::move(rhs.allocation_);
+ slice_ = std::move(rhs.slice_);
+
+#ifdef ROCKSDB_ZSTD_DDICT
+ zstd_ddict_ = rhs.zstd_ddict_;
+ rhs.zstd_ddict_ = nullptr;
+#endif
+
+ return *this;
+ }
+
+ // The object is self-contained if the string constructor is used, or the
+ // Slice constructor is invoked with a non-null allocation. Otherwise, it
+ // is the caller's responsibility to ensure that the underlying storage
+ // outlives this object.
+ bool own_bytes() const { return !dict_.empty() || allocation_; }
+
+ const Slice& GetRawDict() const { return slice_; }
+
+#ifdef ROCKSDB_ZSTD_DDICT
+ const ZSTD_DDict* GetDigestedZstdDDict() const { return zstd_ddict_; }
+#endif // ROCKSDB_ZSTD_DDICT
+
+ static const UncompressionDict& GetEmptyDict() {
+ static UncompressionDict empty_dict{};
+ return empty_dict;
+ }
+
+ size_t ApproximateMemoryUsage() const {
+ size_t usage = sizeof(struct UncompressionDict);
+ usage += dict_.size();
+ if (allocation_) {
+ auto allocator = allocation_.get_deleter().allocator;
+ if (allocator) {
+ usage += allocator->UsableSize(allocation_.get(), slice_.size());
+ } else {
+ usage += slice_.size();
+ }
+ }
+#ifdef ROCKSDB_ZSTD_DDICT
+ usage += ZSTD_sizeof_DDict(zstd_ddict_);
+#endif // ROCKSDB_ZSTD_DDICT
+ return usage;
+ }
+
+ UncompressionDict() = default;
+ // Disable copy
+ UncompressionDict(const CompressionDict&) = delete;
+ UncompressionDict& operator=(const CompressionDict&) = delete;
+};
+
+class CompressionContext {
+ private:
+#if defined(ZSTD) && (ZSTD_VERSION_NUMBER >= 500)
+ ZSTD_CCtx* zstd_ctx_ = nullptr;
+ void CreateNativeContext(CompressionType type) {
+ if (type == kZSTD || type == kZSTDNotFinalCompression) {
+#ifdef ROCKSDB_ZSTD_CUSTOM_MEM
+ zstd_ctx_ =
+ ZSTD_createCCtx_advanced(port::GetJeZstdAllocationOverrides());
+#else // ROCKSDB_ZSTD_CUSTOM_MEM
+ zstd_ctx_ = ZSTD_createCCtx();
+#endif // ROCKSDB_ZSTD_CUSTOM_MEM
+ }
+ }
+ void DestroyNativeContext() {
+ if (zstd_ctx_ != nullptr) {
+ ZSTD_freeCCtx(zstd_ctx_);
+ }
+ }
+
+ public:
+ // callable inside ZSTD_Compress
+ ZSTD_CCtx* ZSTDPreallocCtx() const {
+ assert(zstd_ctx_ != nullptr);
+ return zstd_ctx_;
+ }
+
+#else // ZSTD && (ZSTD_VERSION_NUMBER >= 500)
+ private:
+ void CreateNativeContext(CompressionType /* type */) {}
+ void DestroyNativeContext() {}
+#endif // ZSTD && (ZSTD_VERSION_NUMBER >= 500)
+ public:
+ explicit CompressionContext(CompressionType type) {
+ CreateNativeContext(type);
+ }
+ ~CompressionContext() { DestroyNativeContext(); }
+ CompressionContext(const CompressionContext&) = delete;
+ CompressionContext& operator=(const CompressionContext&) = delete;
+};
+
+class CompressionInfo {
+ const CompressionOptions& opts_;
+ const CompressionContext& context_;
+ const CompressionDict& dict_;
+ const CompressionType type_;
+ const uint64_t sample_for_compression_;
+
+ public:
+ CompressionInfo(const CompressionOptions& _opts,
+ const CompressionContext& _context,
+ const CompressionDict& _dict, CompressionType _type,
+ uint64_t _sample_for_compression)
+ : opts_(_opts),
+ context_(_context),
+ dict_(_dict),
+ type_(_type),
+ sample_for_compression_(_sample_for_compression) {}
+
+ const CompressionOptions& options() const { return opts_; }
+ const CompressionContext& context() const { return context_; }
+ const CompressionDict& dict() const { return dict_; }
+ CompressionType type() const { return type_; }
+ uint64_t SampleForCompression() const { return sample_for_compression_; }
+};
+
+class UncompressionContext {
+ private:
+ CompressionContextCache* ctx_cache_ = nullptr;
+ ZSTDUncompressCachedData uncomp_cached_data_;
+
+ public:
+ struct NoCache {};
+ // Do not use context cache, used by TableBuilder
+ UncompressionContext(NoCache, CompressionType /* type */) {}
+
+ explicit UncompressionContext(CompressionType type) {
+ if (type == kZSTD || type == kZSTDNotFinalCompression) {
+ ctx_cache_ = CompressionContextCache::Instance();
+ uncomp_cached_data_ = ctx_cache_->GetCachedZSTDUncompressData();
+ }
+ }
+ ~UncompressionContext() {
+ if (uncomp_cached_data_.GetCacheIndex() != -1) {
+ assert(ctx_cache_ != nullptr);
+ ctx_cache_->ReturnCachedZSTDUncompressData(
+ uncomp_cached_data_.GetCacheIndex());
+ }
+ }
+ UncompressionContext(const UncompressionContext&) = delete;
+ UncompressionContext& operator=(const UncompressionContext&) = delete;
+
+ ZSTDUncompressCachedData::ZSTDNativeContext GetZSTDContext() const {
+ return uncomp_cached_data_.Get();
+ }
+};
+
+class UncompressionInfo {
+ const UncompressionContext& context_;
+ const UncompressionDict& dict_;
+ const CompressionType type_;
+
+ public:
+ UncompressionInfo(const UncompressionContext& _context,
+ const UncompressionDict& _dict, CompressionType _type)
+ : context_(_context), dict_(_dict), type_(_type) {}
+
+ const UncompressionContext& context() const { return context_; }
+ const UncompressionDict& dict() const { return dict_; }
+ CompressionType type() const { return type_; }
+};
+
+inline bool Snappy_Supported() {
+#ifdef SNAPPY
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool Zlib_Supported() {
+#ifdef ZLIB
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool BZip2_Supported() {
+#ifdef BZIP2
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool LZ4_Supported() {
+#ifdef LZ4
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool XPRESS_Supported() {
+#ifdef XPRESS
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool ZSTD_Supported() {
+#ifdef ZSTD
+ // ZSTD format is finalized since version 0.8.0.
+ return (ZSTD_versionNumber() >= 800);
+#else
+ return false;
+#endif
+}
+
+inline bool ZSTDNotFinal_Supported() {
+#ifdef ZSTD
+ return true;
+#else
+ return false;
+#endif
+}
+
+inline bool CompressionTypeSupported(CompressionType compression_type) {
+ switch (compression_type) {
+ case kNoCompression:
+ return true;
+ case kSnappyCompression:
+ return Snappy_Supported();
+ case kZlibCompression:
+ return Zlib_Supported();
+ case kBZip2Compression:
+ return BZip2_Supported();
+ case kLZ4Compression:
+ return LZ4_Supported();
+ case kLZ4HCCompression:
+ return LZ4_Supported();
+ case kXpressCompression:
+ return XPRESS_Supported();
+ case kZSTDNotFinalCompression:
+ return ZSTDNotFinal_Supported();
+ case kZSTD:
+ return ZSTD_Supported();
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+inline std::string CompressionTypeToString(CompressionType compression_type) {
+ switch (compression_type) {
+ case kNoCompression:
+ return "NoCompression";
+ case kSnappyCompression:
+ return "Snappy";
+ case kZlibCompression:
+ return "Zlib";
+ case kBZip2Compression:
+ return "BZip2";
+ case kLZ4Compression:
+ return "LZ4";
+ case kLZ4HCCompression:
+ return "LZ4HC";
+ case kXpressCompression:
+ return "Xpress";
+ case kZSTD:
+ return "ZSTD";
+ case kZSTDNotFinalCompression:
+ return "ZSTDNotFinal";
+ case kDisableCompressionOption:
+ return "DisableOption";
+ default:
+ assert(false);
+ return "";
+ }
+}
+
+inline std::string CompressionOptionsToString(
+ CompressionOptions& compression_options) {
+ std::string result;
+ result.reserve(512);
+ result.append("window_bits=")
+ .append(ToString(compression_options.window_bits))
+ .append("; ");
+ result.append("level=")
+ .append(ToString(compression_options.level))
+ .append("; ");
+ result.append("strategy=")
+ .append(ToString(compression_options.strategy))
+ .append("; ");
+ result.append("max_dict_bytes=")
+ .append(ToString(compression_options.max_dict_bytes))
+ .append("; ");
+ result.append("zstd_max_train_bytes=")
+ .append(ToString(compression_options.zstd_max_train_bytes))
+ .append("; ");
+ result.append("enabled=")
+ .append(ToString(compression_options.enabled))
+ .append("; ");
+ return result;
+}
+
+// compress_format_version can have two values:
+// 1 -- decompressed sizes for BZip2 and Zlib are not included in the compressed
+// block. Also, decompressed sizes for LZ4 are encoded in platform-dependent
+// way.
+// 2 -- Zlib, BZip2 and LZ4 encode decompressed size as Varint32 just before the
+// start of compressed block. Snappy format is the same as version 1.
+
+inline bool Snappy_Compress(const CompressionInfo& /*info*/, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef SNAPPY
+ output->resize(snappy::MaxCompressedLength(length));
+ size_t outlen;
+ snappy::RawCompress(input, length, &(*output)[0], &outlen);
+ output->resize(outlen);
+ return true;
+#else
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+inline bool Snappy_GetUncompressedLength(const char* input, size_t length,
+ size_t* result) {
+#ifdef SNAPPY
+ return snappy::GetUncompressedLength(input, length, result);
+#else
+ (void)input;
+ (void)length;
+ (void)result;
+ return false;
+#endif
+}
+
+inline bool Snappy_Uncompress(const char* input, size_t length, char* output) {
+#ifdef SNAPPY
+ return snappy::RawUncompress(input, length, output);
+#else
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+namespace compression {
+// returns size
+inline size_t PutDecompressedSizeInfo(std::string* output, uint32_t length) {
+ PutVarint32(output, length);
+ return output->size();
+}
+
+inline bool GetDecompressedSizeInfo(const char** input_data,
+ size_t* input_length,
+ uint32_t* output_len) {
+ auto new_input_data =
+ GetVarint32Ptr(*input_data, *input_data + *input_length, output_len);
+ if (new_input_data == nullptr) {
+ return false;
+ }
+ *input_length -= (new_input_data - *input_data);
+ *input_data = new_input_data;
+ return true;
+}
+} // namespace compression
+
+// compress_format_version == 1 -- decompressed size is not included in the
+// block header
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline bool Zlib_Compress(const CompressionInfo& info,
+ uint32_t compress_format_version, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef ZLIB
+ if (length > std::numeric_limits<uint32_t>::max()) {
+ // Can't compress more than 4GB
+ return false;
+ }
+
+ size_t output_header_len = 0;
+ if (compress_format_version == 2) {
+ output_header_len = compression::PutDecompressedSizeInfo(
+ output, static_cast<uint32_t>(length));
+ }
+ // Resize output to be the plain data length.
+ // This may not be big enough if the compression actually expands data.
+ output->resize(output_header_len + length);
+
+ // The memLevel parameter specifies how much memory should be allocated for
+ // the internal compression state.
+ // memLevel=1 uses minimum memory but is slow and reduces compression ratio.
+ // memLevel=9 uses maximum memory for optimal speed.
+ // The default value is 8. See zconf.h for more details.
+ static const int memLevel = 8;
+ int level;
+ if (info.options().level == CompressionOptions::kDefaultCompressionLevel) {
+ level = Z_DEFAULT_COMPRESSION;
+ } else {
+ level = info.options().level;
+ }
+ z_stream _stream;
+ memset(&_stream, 0, sizeof(z_stream));
+ int st = deflateInit2(&_stream, level, Z_DEFLATED, info.options().window_bits,
+ memLevel, info.options().strategy);
+ if (st != Z_OK) {
+ return false;
+ }
+
+ Slice compression_dict = info.dict().GetRawDict();
+ if (compression_dict.size()) {
+ // Initialize the compression library's dictionary
+ st = deflateSetDictionary(
+ &_stream, reinterpret_cast<const Bytef*>(compression_dict.data()),
+ static_cast<unsigned int>(compression_dict.size()));
+ if (st != Z_OK) {
+ deflateEnd(&_stream);
+ return false;
+ }
+ }
+
+ // Compress the input, and put compressed data in output.
+ _stream.next_in = (Bytef*)input;
+ _stream.avail_in = static_cast<unsigned int>(length);
+
+ // Initialize the output size.
+ _stream.avail_out = static_cast<unsigned int>(length);
+ _stream.next_out = reinterpret_cast<Bytef*>(&(*output)[output_header_len]);
+
+ bool compressed = false;
+ st = deflate(&_stream, Z_FINISH);
+ if (st == Z_STREAM_END) {
+ compressed = true;
+ output->resize(output->size() - _stream.avail_out);
+ }
+ // The only return value we really care about is Z_STREAM_END.
+ // Z_OK means insufficient output space. This means the compression is
+ // bigger than decompressed size. Just fail the compression in that case.
+
+ deflateEnd(&_stream);
+ return compressed;
+#else
+ (void)info;
+ (void)compress_format_version;
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is not included in the
+// block header
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline CacheAllocationPtr Zlib_Uncompress(
+ const UncompressionInfo& info, const char* input_data, size_t input_length,
+ int* decompress_size, uint32_t compress_format_version,
+ MemoryAllocator* allocator = nullptr, int windowBits = -14) {
+#ifdef ZLIB
+ uint32_t output_len = 0;
+ if (compress_format_version == 2) {
+ if (!compression::GetDecompressedSizeInfo(&input_data, &input_length,
+ &output_len)) {
+ return nullptr;
+ }
+ } else {
+ // Assume the decompressed data size will 5x of compressed size, but round
+ // to the page size
+ size_t proposed_output_len = ((input_length * 5) & (~(4096 - 1))) + 4096;
+ output_len = static_cast<uint32_t>(
+ std::min(proposed_output_len,
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max())));
+ }
+
+ z_stream _stream;
+ memset(&_stream, 0, sizeof(z_stream));
+
+ // For raw inflate, the windowBits should be -8..-15.
+ // If windowBits is bigger than zero, it will use either zlib
+ // header or gzip header. Adding 32 to it will do automatic detection.
+ int st =
+ inflateInit2(&_stream, windowBits > 0 ? windowBits + 32 : windowBits);
+ if (st != Z_OK) {
+ return nullptr;
+ }
+
+ const Slice& compression_dict = info.dict().GetRawDict();
+ if (compression_dict.size()) {
+ // Initialize the compression library's dictionary
+ st = inflateSetDictionary(
+ &_stream, reinterpret_cast<const Bytef*>(compression_dict.data()),
+ static_cast<unsigned int>(compression_dict.size()));
+ if (st != Z_OK) {
+ return nullptr;
+ }
+ }
+
+ _stream.next_in = (Bytef*)input_data;
+ _stream.avail_in = static_cast<unsigned int>(input_length);
+
+ auto output = AllocateBlock(output_len, allocator);
+
+ _stream.next_out = (Bytef*)output.get();
+ _stream.avail_out = static_cast<unsigned int>(output_len);
+
+ bool done = false;
+ while (!done) {
+ st = inflate(&_stream, Z_SYNC_FLUSH);
+ switch (st) {
+ case Z_STREAM_END:
+ done = true;
+ break;
+ case Z_OK: {
+ // No output space. Increase the output space by 20%.
+ // We should never run out of output space if
+ // compress_format_version == 2
+ assert(compress_format_version != 2);
+ size_t old_sz = output_len;
+ uint32_t output_len_delta = output_len / 5;
+ output_len += output_len_delta < 10 ? 10 : output_len_delta;
+ auto tmp = AllocateBlock(output_len, allocator);
+ memcpy(tmp.get(), output.get(), old_sz);
+ output = std::move(tmp);
+
+ // Set more output.
+ _stream.next_out = (Bytef*)(output.get() + old_sz);
+ _stream.avail_out = static_cast<unsigned int>(output_len - old_sz);
+ break;
+ }
+ case Z_BUF_ERROR:
+ default:
+ inflateEnd(&_stream);
+ return nullptr;
+ }
+ }
+
+ // If we encoded decompressed block size, we should have no bytes left
+ assert(compress_format_version != 2 || _stream.avail_out == 0);
+ *decompress_size = static_cast<int>(output_len - _stream.avail_out);
+ inflateEnd(&_stream);
+ return output;
+#else
+ (void)info;
+ (void)input_data;
+ (void)input_length;
+ (void)decompress_size;
+ (void)compress_format_version;
+ (void)allocator;
+ (void)windowBits;
+ return nullptr;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is not included in the
+// block header
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+inline bool BZip2_Compress(const CompressionInfo& /*info*/,
+ uint32_t compress_format_version, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef BZIP2
+ if (length > std::numeric_limits<uint32_t>::max()) {
+ // Can't compress more than 4GB
+ return false;
+ }
+ size_t output_header_len = 0;
+ if (compress_format_version == 2) {
+ output_header_len = compression::PutDecompressedSizeInfo(
+ output, static_cast<uint32_t>(length));
+ }
+ // Resize output to be the plain data length.
+ // This may not be big enough if the compression actually expands data.
+ output->resize(output_header_len + length);
+
+ bz_stream _stream;
+ memset(&_stream, 0, sizeof(bz_stream));
+
+ // Block size 1 is 100K.
+ // 0 is for silent.
+ // 30 is the default workFactor
+ int st = BZ2_bzCompressInit(&_stream, 1, 0, 30);
+ if (st != BZ_OK) {
+ return false;
+ }
+
+ // Compress the input, and put compressed data in output.
+ _stream.next_in = (char*)input;
+ _stream.avail_in = static_cast<unsigned int>(length);
+
+ // Initialize the output size.
+ _stream.avail_out = static_cast<unsigned int>(length);
+ _stream.next_out = reinterpret_cast<char*>(&(*output)[output_header_len]);
+
+ bool compressed = false;
+ st = BZ2_bzCompress(&_stream, BZ_FINISH);
+ if (st == BZ_STREAM_END) {
+ compressed = true;
+ output->resize(output->size() - _stream.avail_out);
+ }
+ // The only return value we really care about is BZ_STREAM_END.
+ // BZ_FINISH_OK means insufficient output space. This means the compression
+ // is bigger than decompressed size. Just fail the compression in that case.
+
+ BZ2_bzCompressEnd(&_stream);
+ return compressed;
+#else
+ (void)compress_format_version;
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is not included in the
+// block header
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+inline CacheAllocationPtr BZip2_Uncompress(
+ const char* input_data, size_t input_length, int* decompress_size,
+ uint32_t compress_format_version, MemoryAllocator* allocator = nullptr) {
+#ifdef BZIP2
+ uint32_t output_len = 0;
+ if (compress_format_version == 2) {
+ if (!compression::GetDecompressedSizeInfo(&input_data, &input_length,
+ &output_len)) {
+ return nullptr;
+ }
+ } else {
+ // Assume the decompressed data size will 5x of compressed size, but round
+ // to the next page size
+ size_t proposed_output_len = ((input_length * 5) & (~(4096 - 1))) + 4096;
+ output_len = static_cast<uint32_t>(
+ std::min(proposed_output_len,
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max())));
+ }
+
+ bz_stream _stream;
+ memset(&_stream, 0, sizeof(bz_stream));
+
+ int st = BZ2_bzDecompressInit(&_stream, 0, 0);
+ if (st != BZ_OK) {
+ return nullptr;
+ }
+
+ _stream.next_in = (char*)input_data;
+ _stream.avail_in = static_cast<unsigned int>(input_length);
+
+ auto output = AllocateBlock(output_len, allocator);
+
+ _stream.next_out = (char*)output.get();
+ _stream.avail_out = static_cast<unsigned int>(output_len);
+
+ bool done = false;
+ while (!done) {
+ st = BZ2_bzDecompress(&_stream);
+ switch (st) {
+ case BZ_STREAM_END:
+ done = true;
+ break;
+ case BZ_OK: {
+ // No output space. Increase the output space by 20%.
+ // We should never run out of output space if
+ // compress_format_version == 2
+ assert(compress_format_version != 2);
+ uint32_t old_sz = output_len;
+ output_len = output_len * 1.2;
+ auto tmp = AllocateBlock(output_len, allocator);
+ memcpy(tmp.get(), output.get(), old_sz);
+ output = std::move(tmp);
+
+ // Set more output.
+ _stream.next_out = (char*)(output.get() + old_sz);
+ _stream.avail_out = static_cast<unsigned int>(output_len - old_sz);
+ break;
+ }
+ default:
+ BZ2_bzDecompressEnd(&_stream);
+ return nullptr;
+ }
+ }
+
+ // If we encoded decompressed block size, we should have no bytes left
+ assert(compress_format_version != 2 || _stream.avail_out == 0);
+ *decompress_size = static_cast<int>(output_len - _stream.avail_out);
+ BZ2_bzDecompressEnd(&_stream);
+ return output;
+#else
+ (void)input_data;
+ (void)input_length;
+ (void)decompress_size;
+ (void)compress_format_version;
+ (void)allocator;
+ return nullptr;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is included in the
+// block header using memcpy, which makes database non-portable)
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline bool LZ4_Compress(const CompressionInfo& info,
+ uint32_t compress_format_version, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef LZ4
+ if (length > std::numeric_limits<uint32_t>::max()) {
+ // Can't compress more than 4GB
+ return false;
+ }
+
+ size_t output_header_len = 0;
+ if (compress_format_version == 2) {
+ // new encoding, using varint32 to store size information
+ output_header_len = compression::PutDecompressedSizeInfo(
+ output, static_cast<uint32_t>(length));
+ } else {
+ // legacy encoding, which is not really portable (depends on big/little
+ // endianness)
+ output_header_len = 8;
+ output->resize(output_header_len);
+ char* p = const_cast<char*>(output->c_str());
+ memcpy(p, &length, sizeof(length));
+ }
+ int compress_bound = LZ4_compressBound(static_cast<int>(length));
+ output->resize(static_cast<size_t>(output_header_len + compress_bound));
+
+ int outlen;
+#if LZ4_VERSION_NUMBER >= 10400 // r124+
+ LZ4_stream_t* stream = LZ4_createStream();
+ Slice compression_dict = info.dict().GetRawDict();
+ if (compression_dict.size()) {
+ LZ4_loadDict(stream, compression_dict.data(),
+ static_cast<int>(compression_dict.size()));
+ }
+#if LZ4_VERSION_NUMBER >= 10700 // r129+
+ outlen =
+ LZ4_compress_fast_continue(stream, input, &(*output)[output_header_len],
+ static_cast<int>(length), compress_bound, 1);
+#else // up to r128
+ outlen = LZ4_compress_limitedOutput_continue(
+ stream, input, &(*output)[output_header_len], static_cast<int>(length),
+ compress_bound);
+#endif
+ LZ4_freeStream(stream);
+#else // up to r123
+ outlen = LZ4_compress_limitedOutput(input, &(*output)[output_header_len],
+ static_cast<int>(length), compress_bound);
+#endif // LZ4_VERSION_NUMBER >= 10400
+
+ if (outlen == 0) {
+ return false;
+ }
+ output->resize(static_cast<size_t>(output_header_len + outlen));
+ return true;
+#else // LZ4
+ (void)info;
+ (void)compress_format_version;
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is included in the
+// block header using memcpy, which makes database non-portable)
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline CacheAllocationPtr LZ4_Uncompress(const UncompressionInfo& info,
+ const char* input_data,
+ size_t input_length,
+ int* decompress_size,
+ uint32_t compress_format_version,
+ MemoryAllocator* allocator = nullptr) {
+#ifdef LZ4
+ uint32_t output_len = 0;
+ if (compress_format_version == 2) {
+ // new encoding, using varint32 to store size information
+ if (!compression::GetDecompressedSizeInfo(&input_data, &input_length,
+ &output_len)) {
+ return nullptr;
+ }
+ } else {
+ // legacy encoding, which is not really portable (depends on big/little
+ // endianness)
+ if (input_length < 8) {
+ return nullptr;
+ }
+ memcpy(&output_len, input_data, sizeof(output_len));
+ input_length -= 8;
+ input_data += 8;
+ }
+
+ auto output = AllocateBlock(output_len, allocator);
+#if LZ4_VERSION_NUMBER >= 10400 // r124+
+ LZ4_streamDecode_t* stream = LZ4_createStreamDecode();
+ const Slice& compression_dict = info.dict().GetRawDict();
+ if (compression_dict.size()) {
+ LZ4_setStreamDecode(stream, compression_dict.data(),
+ static_cast<int>(compression_dict.size()));
+ }
+ *decompress_size = LZ4_decompress_safe_continue(
+ stream, input_data, output.get(), static_cast<int>(input_length),
+ static_cast<int>(output_len));
+ LZ4_freeStreamDecode(stream);
+#else // up to r123
+ *decompress_size = LZ4_decompress_safe(input_data, output.get(),
+ static_cast<int>(input_length),
+ static_cast<int>(output_len));
+#endif // LZ4_VERSION_NUMBER >= 10400
+
+ if (*decompress_size < 0) {
+ return nullptr;
+ }
+ assert(*decompress_size == static_cast<int>(output_len));
+ return output;
+#else // LZ4
+ (void)info;
+ (void)input_data;
+ (void)input_length;
+ (void)decompress_size;
+ (void)compress_format_version;
+ (void)allocator;
+ return nullptr;
+#endif
+}
+
+// compress_format_version == 1 -- decompressed size is included in the
+// block header using memcpy, which makes database non-portable)
+// compress_format_version == 2 -- decompressed size is included in the block
+// header in varint32 format
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline bool LZ4HC_Compress(const CompressionInfo& info,
+ uint32_t compress_format_version, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef LZ4
+ if (length > std::numeric_limits<uint32_t>::max()) {
+ // Can't compress more than 4GB
+ return false;
+ }
+
+ size_t output_header_len = 0;
+ if (compress_format_version == 2) {
+ // new encoding, using varint32 to store size information
+ output_header_len = compression::PutDecompressedSizeInfo(
+ output, static_cast<uint32_t>(length));
+ } else {
+ // legacy encoding, which is not really portable (depends on big/little
+ // endianness)
+ output_header_len = 8;
+ output->resize(output_header_len);
+ char* p = const_cast<char*>(output->c_str());
+ memcpy(p, &length, sizeof(length));
+ }
+ int compress_bound = LZ4_compressBound(static_cast<int>(length));
+ output->resize(static_cast<size_t>(output_header_len + compress_bound));
+
+ int outlen;
+ int level;
+ if (info.options().level == CompressionOptions::kDefaultCompressionLevel) {
+ level = 0; // lz4hc.h says any value < 1 will be sanitized to default
+ } else {
+ level = info.options().level;
+ }
+#if LZ4_VERSION_NUMBER >= 10400 // r124+
+ LZ4_streamHC_t* stream = LZ4_createStreamHC();
+ LZ4_resetStreamHC(stream, level);
+ Slice compression_dict = info.dict().GetRawDict();
+ const char* compression_dict_data =
+ compression_dict.size() > 0 ? compression_dict.data() : nullptr;
+ size_t compression_dict_size = compression_dict.size();
+ LZ4_loadDictHC(stream, compression_dict_data,
+ static_cast<int>(compression_dict_size));
+
+#if LZ4_VERSION_NUMBER >= 10700 // r129+
+ outlen =
+ LZ4_compress_HC_continue(stream, input, &(*output)[output_header_len],
+ static_cast<int>(length), compress_bound);
+#else // r124-r128
+ outlen = LZ4_compressHC_limitedOutput_continue(
+ stream, input, &(*output)[output_header_len], static_cast<int>(length),
+ compress_bound);
+#endif // LZ4_VERSION_NUMBER >= 10700
+ LZ4_freeStreamHC(stream);
+
+#elif LZ4_VERSION_MAJOR // r113-r123
+ outlen = LZ4_compressHC2_limitedOutput(input, &(*output)[output_header_len],
+ static_cast<int>(length),
+ compress_bound, level);
+#else // up to r112
+ outlen =
+ LZ4_compressHC_limitedOutput(input, &(*output)[output_header_len],
+ static_cast<int>(length), compress_bound);
+#endif // LZ4_VERSION_NUMBER >= 10400
+
+ if (outlen == 0) {
+ return false;
+ }
+ output->resize(static_cast<size_t>(output_header_len + outlen));
+ return true;
+#else // LZ4
+ (void)info;
+ (void)compress_format_version;
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+#ifdef XPRESS
+inline bool XPRESS_Compress(const char* input, size_t length,
+ std::string* output) {
+ return port::xpress::Compress(input, length, output);
+}
+#else
+inline bool XPRESS_Compress(const char* /*input*/, size_t /*length*/,
+ std::string* /*output*/) {
+ return false;
+}
+#endif
+
+#ifdef XPRESS
+inline char* XPRESS_Uncompress(const char* input_data, size_t input_length,
+ int* decompress_size) {
+ return port::xpress::Decompress(input_data, input_length, decompress_size);
+}
+#else
+inline char* XPRESS_Uncompress(const char* /*input_data*/,
+ size_t /*input_length*/,
+ int* /*decompress_size*/) {
+ return nullptr;
+}
+#endif
+
+inline bool ZSTD_Compress(const CompressionInfo& info, const char* input,
+ size_t length, ::std::string* output) {
+#ifdef ZSTD
+ if (length > std::numeric_limits<uint32_t>::max()) {
+ // Can't compress more than 4GB
+ return false;
+ }
+
+ size_t output_header_len = compression::PutDecompressedSizeInfo(
+ output, static_cast<uint32_t>(length));
+
+ size_t compressBound = ZSTD_compressBound(length);
+ output->resize(static_cast<size_t>(output_header_len + compressBound));
+ size_t outlen = 0;
+ int level;
+ if (info.options().level == CompressionOptions::kDefaultCompressionLevel) {
+ // 3 is the value of ZSTD_CLEVEL_DEFAULT (not exposed publicly), see
+ // https://github.com/facebook/zstd/issues/1148
+ level = 3;
+ } else {
+ level = info.options().level;
+ }
+#if ZSTD_VERSION_NUMBER >= 500 // v0.5.0+
+ ZSTD_CCtx* context = info.context().ZSTDPreallocCtx();
+ assert(context != nullptr);
+#if ZSTD_VERSION_NUMBER >= 700 // v0.7.0+
+ if (info.dict().GetDigestedZstdCDict() != nullptr) {
+ outlen = ZSTD_compress_usingCDict(context, &(*output)[output_header_len],
+ compressBound, input, length,
+ info.dict().GetDigestedZstdCDict());
+ }
+#endif // ZSTD_VERSION_NUMBER >= 700
+ if (outlen == 0) {
+ outlen = ZSTD_compress_usingDict(context, &(*output)[output_header_len],
+ compressBound, input, length,
+ info.dict().GetRawDict().data(),
+ info.dict().GetRawDict().size(), level);
+ }
+#else // up to v0.4.x
+ outlen = ZSTD_compress(&(*output)[output_header_len], compressBound, input,
+ length, level);
+#endif // ZSTD_VERSION_NUMBER >= 500
+ if (outlen == 0) {
+ return false;
+ }
+ output->resize(output_header_len + outlen);
+ return true;
+#else // ZSTD
+ (void)info;
+ (void)input;
+ (void)length;
+ (void)output;
+ return false;
+#endif
+}
+
+// @param compression_dict Data for presetting the compression library's
+// dictionary.
+inline CacheAllocationPtr ZSTD_Uncompress(
+ const UncompressionInfo& info, const char* input_data, size_t input_length,
+ int* decompress_size, MemoryAllocator* allocator = nullptr) {
+#ifdef ZSTD
+ uint32_t output_len = 0;
+ if (!compression::GetDecompressedSizeInfo(&input_data, &input_length,
+ &output_len)) {
+ return nullptr;
+ }
+
+ auto output = AllocateBlock(output_len, allocator);
+ size_t actual_output_length = 0;
+#if ZSTD_VERSION_NUMBER >= 500 // v0.5.0+
+ ZSTD_DCtx* context = info.context().GetZSTDContext();
+ assert(context != nullptr);
+#ifdef ROCKSDB_ZSTD_DDICT
+ if (info.dict().GetDigestedZstdDDict() != nullptr) {
+ actual_output_length = ZSTD_decompress_usingDDict(
+ context, output.get(), output_len, input_data, input_length,
+ info.dict().GetDigestedZstdDDict());
+ }
+#endif // ROCKSDB_ZSTD_DDICT
+ if (actual_output_length == 0) {
+ actual_output_length = ZSTD_decompress_usingDict(
+ context, output.get(), output_len, input_data, input_length,
+ info.dict().GetRawDict().data(), info.dict().GetRawDict().size());
+ }
+#else // up to v0.4.x
+ (void)info;
+ actual_output_length =
+ ZSTD_decompress(output.get(), output_len, input_data, input_length);
+#endif // ZSTD_VERSION_NUMBER >= 500
+ assert(actual_output_length == output_len);
+ *decompress_size = static_cast<int>(actual_output_length);
+ return output;
+#else // ZSTD
+ (void)info;
+ (void)input_data;
+ (void)input_length;
+ (void)decompress_size;
+ (void)allocator;
+ return nullptr;
+#endif
+}
+
+inline bool ZSTD_TrainDictionarySupported() {
+#ifdef ZSTD
+ // Dictionary trainer is available since v0.6.1 for static linking, but not
+ // available for dynamic linking until v1.1.3. For now we enable the feature
+ // in v1.1.3+ only.
+ return (ZSTD_versionNumber() >= 10103);
+#else
+ return false;
+#endif
+}
+
+inline std::string ZSTD_TrainDictionary(const std::string& samples,
+ const std::vector<size_t>& sample_lens,
+ size_t max_dict_bytes) {
+ // Dictionary trainer is available since v0.6.1 for static linking, but not
+ // available for dynamic linking until v1.1.3. For now we enable the feature
+ // in v1.1.3+ only.
+#if ZSTD_VERSION_NUMBER >= 10103 // v1.1.3+
+ assert(samples.empty() == sample_lens.empty());
+ if (samples.empty()) {
+ return "";
+ }
+ std::string dict_data(max_dict_bytes, '\0');
+ size_t dict_len = ZDICT_trainFromBuffer(
+ &dict_data[0], max_dict_bytes, &samples[0], &sample_lens[0],
+ static_cast<unsigned>(sample_lens.size()));
+ if (ZDICT_isError(dict_len)) {
+ return "";
+ }
+ assert(dict_len <= max_dict_bytes);
+ dict_data.resize(dict_len);
+ return dict_data;
+#else // up to v1.1.2
+ assert(false);
+ (void)samples;
+ (void)sample_lens;
+ (void)max_dict_bytes;
+ return "";
+#endif // ZSTD_VERSION_NUMBER >= 10103
+}
+
+inline std::string ZSTD_TrainDictionary(const std::string& samples,
+ size_t sample_len_shift,
+ size_t max_dict_bytes) {
+ // Dictionary trainer is available since v0.6.1, but ZSTD was marked stable
+ // only since v0.8.0. For now we enable the feature in stable versions only.
+#if ZSTD_VERSION_NUMBER >= 10103 // v1.1.3+
+ // skips potential partial sample at the end of "samples"
+ size_t num_samples = samples.size() >> sample_len_shift;
+ std::vector<size_t> sample_lens(num_samples, size_t(1) << sample_len_shift);
+ return ZSTD_TrainDictionary(samples, sample_lens, max_dict_bytes);
+#else // up to v1.1.2
+ assert(false);
+ (void)samples;
+ (void)sample_len_shift;
+ (void)max_dict_bytes;
+ return "";
+#endif // ZSTD_VERSION_NUMBER >= 10103
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/compression_context_cache.cc b/storage/rocksdb/rocksdb/util/compression_context_cache.cc
new file mode 100644
index 00000000..a06cfb27
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/compression_context_cache.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/compression_context_cache.h"
+
+#include "util/compression.h"
+#include "util/core_local.h"
+
+#include <atomic>
+
+namespace ROCKSDB_NAMESPACE {
+namespace compression_cache {
+
+void* const SentinelValue = nullptr;
+// Cache ZSTD uncompression contexts for reads
+// if needed we can add ZSTD compression context caching
+// which is currently is not done since BlockBasedTableBuilder
+// simply creates one compression context per new SST file.
+struct ZSTDCachedData {
+ // We choose to cache the below structure instead of a ptr
+ // because we want to avoid a) native types leak b) make
+ // cache use transparent for the user
+ ZSTDUncompressCachedData uncomp_cached_data_;
+ std::atomic<void*> zstd_uncomp_sentinel_;
+
+ char
+ padding[(CACHE_LINE_SIZE -
+ (sizeof(ZSTDUncompressCachedData) + sizeof(std::atomic<void*>)) %
+ CACHE_LINE_SIZE)]; // unused padding field
+
+ ZSTDCachedData() : zstd_uncomp_sentinel_(&uncomp_cached_data_) {}
+ ZSTDCachedData(const ZSTDCachedData&) = delete;
+ ZSTDCachedData& operator=(const ZSTDCachedData&) = delete;
+
+ ZSTDUncompressCachedData GetUncompressData(int64_t idx) {
+ ZSTDUncompressCachedData result;
+ void* expected = &uncomp_cached_data_;
+ if (zstd_uncomp_sentinel_.compare_exchange_strong(expected,
+ SentinelValue)) {
+ uncomp_cached_data_.CreateIfNeeded();
+ result.InitFromCache(uncomp_cached_data_, idx);
+ } else {
+ // Creates one time use data
+ result.CreateIfNeeded();
+ }
+ return result;
+ }
+ // Return the entry back into circulation
+ // This is executed only when we successfully obtained
+ // in the first place
+ void ReturnUncompressData() {
+ if (zstd_uncomp_sentinel_.exchange(&uncomp_cached_data_) != SentinelValue) {
+ // Means we are returning while not having it acquired.
+ assert(false);
+ }
+ }
+};
+static_assert(sizeof(ZSTDCachedData) % CACHE_LINE_SIZE == 0,
+ "Expected CACHE_LINE_SIZE alignment");
+} // namespace compression_cache
+
+using namespace compression_cache;
+
+class CompressionContextCache::Rep {
+ public:
+ Rep() {}
+ ZSTDUncompressCachedData GetZSTDUncompressData() {
+ auto p = per_core_uncompr_.AccessElementAndIndex();
+ int64_t idx = static_cast<int64_t>(p.second);
+ return p.first->GetUncompressData(idx);
+ }
+ void ReturnZSTDUncompressData(int64_t idx) {
+ assert(idx >= 0);
+ auto* cn = per_core_uncompr_.AccessAtCore(static_cast<size_t>(idx));
+ cn->ReturnUncompressData();
+ }
+
+ private:
+ CoreLocalArray<ZSTDCachedData> per_core_uncompr_;
+};
+
+CompressionContextCache::CompressionContextCache() : rep_(new Rep()) {}
+
+CompressionContextCache* CompressionContextCache::Instance() {
+ static CompressionContextCache instance;
+ return &instance;
+}
+
+void CompressionContextCache::InitSingleton() { Instance(); }
+
+ZSTDUncompressCachedData
+CompressionContextCache::GetCachedZSTDUncompressData() {
+ return rep_->GetZSTDUncompressData();
+}
+
+void CompressionContextCache::ReturnCachedZSTDUncompressData(int64_t idx) {
+ rep_->ReturnZSTDUncompressData(idx);
+}
+
+CompressionContextCache::~CompressionContextCache() { delete rep_; }
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/compression_context_cache.h b/storage/rocksdb/rocksdb/util/compression_context_cache.h
new file mode 100644
index 00000000..7b7b2d50
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/compression_context_cache.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+
+// Compression context cache allows to cache compression/uncompression contexts
+// This helps with Random Read latencies and reduces CPU utilization
+// Caching is implemented using CoreLocal facility. Compression/Uncompression
+// instances are cached on a per core basis using CoreLocalArray. A borrowed
+// instance is atomically replaced with a sentinel value for the time of being
+// used. If it turns out that another thread is already makes use of the
+// instance we still create one on the heap which is later is destroyed.
+
+#pragma once
+
+#include <stdint.h>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+class ZSTDUncompressCachedData;
+
+class CompressionContextCache {
+ public:
+ // Singleton
+ static CompressionContextCache* Instance();
+ static void InitSingleton();
+ CompressionContextCache(const CompressionContextCache&) = delete;
+ CompressionContextCache& operator=(const CompressionContextCache&) = delete;
+
+ ZSTDUncompressCachedData GetCachedZSTDUncompressData();
+ void ReturnCachedZSTDUncompressData(int64_t idx);
+
+ private:
+ // Singleton
+ CompressionContextCache();
+ ~CompressionContextCache();
+
+ class Rep;
+ Rep* rep_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.cc b/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.cc
new file mode 100644
index 00000000..2342677d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/concurrent_task_limiter_impl.h"
+#include "rocksdb/concurrent_task_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+ConcurrentTaskLimiterImpl::ConcurrentTaskLimiterImpl(
+ const std::string& name, int32_t max_outstanding_task)
+ : name_(name),
+ max_outstanding_tasks_{max_outstanding_task},
+ outstanding_tasks_{0} {
+
+}
+
+ConcurrentTaskLimiterImpl::~ConcurrentTaskLimiterImpl() {
+ assert(outstanding_tasks_ == 0);
+}
+
+const std::string& ConcurrentTaskLimiterImpl::GetName() const {
+ return name_;
+}
+
+void ConcurrentTaskLimiterImpl::SetMaxOutstandingTask(int32_t limit) {
+ max_outstanding_tasks_.store(limit, std::memory_order_relaxed);
+}
+
+void ConcurrentTaskLimiterImpl::ResetMaxOutstandingTask() {
+ max_outstanding_tasks_.store(-1, std::memory_order_relaxed);
+}
+
+int32_t ConcurrentTaskLimiterImpl::GetOutstandingTask() const {
+ return outstanding_tasks_.load(std::memory_order_relaxed);
+}
+
+std::unique_ptr<TaskLimiterToken> ConcurrentTaskLimiterImpl::GetToken(
+ bool force) {
+ int32_t limit = max_outstanding_tasks_.load(std::memory_order_relaxed);
+ int32_t tasks = outstanding_tasks_.load(std::memory_order_relaxed);
+ // force = true, bypass the throttle.
+ // limit < 0 means unlimited tasks.
+ while (force || limit < 0 || tasks < limit) {
+ if (outstanding_tasks_.compare_exchange_weak(tasks, tasks + 1)) {
+ return std::unique_ptr<TaskLimiterToken>(new TaskLimiterToken(this));
+ }
+ }
+ return nullptr;
+}
+
+ConcurrentTaskLimiter* NewConcurrentTaskLimiter(
+ const std::string& name, int32_t limit) {
+ return new ConcurrentTaskLimiterImpl(name, limit);
+}
+
+TaskLimiterToken::~TaskLimiterToken() {
+ --limiter_->outstanding_tasks_;
+ assert(limiter_->outstanding_tasks_ >= 0);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.h b/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.h
new file mode 100644
index 00000000..d8c1e03c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/concurrent_task_limiter_impl.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <memory>
+
+#include "rocksdb/env.h"
+#include "rocksdb/concurrent_task_limiter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TaskLimiterToken;
+
+class ConcurrentTaskLimiterImpl : public ConcurrentTaskLimiter {
+ public:
+ explicit ConcurrentTaskLimiterImpl(const std::string& name,
+ int32_t max_outstanding_task);
+ // No copying allowed
+ ConcurrentTaskLimiterImpl(const ConcurrentTaskLimiterImpl&) = delete;
+ ConcurrentTaskLimiterImpl& operator=(const ConcurrentTaskLimiterImpl&) =
+ delete;
+
+ virtual ~ConcurrentTaskLimiterImpl();
+
+ virtual const std::string& GetName() const override;
+
+ virtual void SetMaxOutstandingTask(int32_t limit) override;
+
+ virtual void ResetMaxOutstandingTask() override;
+
+ virtual int32_t GetOutstandingTask() const override;
+
+ // Request token for adding a new task.
+ // If force == true, it requests a token bypassing throttle.
+ // Returns nullptr if it got throttled.
+ virtual std::unique_ptr<TaskLimiterToken> GetToken(bool force);
+
+ private:
+ friend class TaskLimiterToken;
+
+ std::string name_;
+ std::atomic<int32_t> max_outstanding_tasks_;
+ std::atomic<int32_t> outstanding_tasks_;
+};
+
+class TaskLimiterToken {
+ public:
+ explicit TaskLimiterToken(ConcurrentTaskLimiterImpl* limiter)
+ : limiter_(limiter) {}
+ ~TaskLimiterToken();
+
+ private:
+ ConcurrentTaskLimiterImpl* limiter_;
+
+ // no copying allowed
+ TaskLimiterToken(const TaskLimiterToken&) = delete;
+ void operator=(const TaskLimiterToken&) = delete;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/core_local.h b/storage/rocksdb/rocksdb/util/core_local.h
new file mode 100644
index 00000000..b444a115
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/core_local.h
@@ -0,0 +1,83 @@
+// 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).
+
+#pragma once
+
+#include <cstddef>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "port/likely.h"
+#include "port/port.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// An array of core-local values. Ideally the value type, T, is cache aligned to
+// prevent false sharing.
+template <typename T>
+class CoreLocalArray {
+ public:
+ CoreLocalArray();
+
+ size_t Size() const;
+ // returns pointer to the element corresponding to the core that the thread
+ // currently runs on.
+ T* Access() const;
+ // same as above, but also returns the core index, which the client can cache
+ // to reduce how often core ID needs to be retrieved. Only do this if some
+ // inaccuracy is tolerable, as the thread may migrate to a different core.
+ std::pair<T*, size_t> AccessElementAndIndex() const;
+ // returns pointer to element for the specified core index. This can be used,
+ // e.g., for aggregation, or if the client caches core index.
+ T* AccessAtCore(size_t core_idx) const;
+
+ private:
+ std::unique_ptr<T[]> data_;
+ int size_shift_;
+};
+
+template <typename T>
+CoreLocalArray<T>::CoreLocalArray() {
+ int num_cpus = static_cast<int>(std::thread::hardware_concurrency());
+ // find a power of two >= num_cpus and >= 8
+ size_shift_ = 3;
+ while (1 << size_shift_ < num_cpus) {
+ ++size_shift_;
+ }
+ data_.reset(new T[static_cast<size_t>(1) << size_shift_]);
+}
+
+template <typename T>
+size_t CoreLocalArray<T>::Size() const {
+ return static_cast<size_t>(1) << size_shift_;
+}
+
+template <typename T>
+T* CoreLocalArray<T>::Access() const {
+ return AccessElementAndIndex().first;
+}
+
+template <typename T>
+std::pair<T*, size_t> CoreLocalArray<T>::AccessElementAndIndex() const {
+ int cpuid = port::PhysicalCoreID();
+ size_t core_idx;
+ if (UNLIKELY(cpuid < 0)) {
+ // cpu id unavailable, just pick randomly
+ core_idx = Random::GetTLSInstance()->Uniform(1 << size_shift_);
+ } else {
+ core_idx = static_cast<size_t>(cpuid & ((1 << size_shift_) - 1));
+ }
+ return {AccessAtCore(core_idx), core_idx};
+}
+
+template <typename T>
+T* CoreLocalArray<T>::AccessAtCore(size_t core_idx) const {
+ assert(core_idx < static_cast<size_t>(1) << size_shift_);
+ return &data_[core_idx];
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/crc32c.cc b/storage/rocksdb/rocksdb/util/crc32c.cc
new file mode 100644
index 00000000..f09368f9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c.cc
@@ -0,0 +1,1255 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// A portable implementation of crc32c, optimized to handle
+// four bytes at a time.
+#include "util/crc32c.h"
+#include <stdint.h>
+#ifdef HAVE_SSE42
+#include <nmmintrin.h>
+#include <wmmintrin.h>
+#endif
+#include "util/coding.h"
+#include "util/util.h"
+
+#include "util/crc32c_arm64.h"
+
+#ifdef __powerpc64__
+#include "util/crc32c_ppc.h"
+#include "util/crc32c_ppc_constants.h"
+
+#if __linux__
+#include <sys/auxv.h>
+
+#ifndef PPC_FEATURE2_VEC_CRYPTO
+#define PPC_FEATURE2_VEC_CRYPTO 0x02000000
+#endif
+
+#ifndef AT_HWCAP2
+#define AT_HWCAP2 26
+#endif
+
+#endif /* __linux__ */
+
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+namespace crc32c {
+
+#if defined(HAVE_POWER8) && defined(HAS_ALTIVEC)
+#ifdef __powerpc64__
+static int arch_ppc_crc32 = 0;
+#endif /* __powerpc64__ */
+#endif
+
+static const uint32_t table0_[256] = {
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4,
+ 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb,
+ 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24,
+ 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b,
+ 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54,
+ 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b,
+ 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35,
+ 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5,
+ 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45,
+ 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a,
+ 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595,
+ 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48,
+ 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687,
+ 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198,
+ 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38,
+ 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8,
+ 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096,
+ 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789,
+ 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46,
+ 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9,
+ 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36,
+ 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829,
+ 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93,
+ 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043,
+ 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3,
+ 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc,
+ 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033,
+ 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652,
+ 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d,
+ 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982,
+ 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622,
+ 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2,
+ 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530,
+ 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f,
+ 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0,
+ 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f,
+ 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90,
+ 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f,
+ 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1,
+ 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321,
+ 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81,
+ 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e,
+ 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
+};
+static const uint32_t table1_[256] = {
+ 0x00000000, 0x13a29877, 0x274530ee, 0x34e7a899,
+ 0x4e8a61dc, 0x5d28f9ab, 0x69cf5132, 0x7a6dc945,
+ 0x9d14c3b8, 0x8eb65bcf, 0xba51f356, 0xa9f36b21,
+ 0xd39ea264, 0xc03c3a13, 0xf4db928a, 0xe7790afd,
+ 0x3fc5f181, 0x2c6769f6, 0x1880c16f, 0x0b225918,
+ 0x714f905d, 0x62ed082a, 0x560aa0b3, 0x45a838c4,
+ 0xa2d13239, 0xb173aa4e, 0x859402d7, 0x96369aa0,
+ 0xec5b53e5, 0xfff9cb92, 0xcb1e630b, 0xd8bcfb7c,
+ 0x7f8be302, 0x6c297b75, 0x58ced3ec, 0x4b6c4b9b,
+ 0x310182de, 0x22a31aa9, 0x1644b230, 0x05e62a47,
+ 0xe29f20ba, 0xf13db8cd, 0xc5da1054, 0xd6788823,
+ 0xac154166, 0xbfb7d911, 0x8b507188, 0x98f2e9ff,
+ 0x404e1283, 0x53ec8af4, 0x670b226d, 0x74a9ba1a,
+ 0x0ec4735f, 0x1d66eb28, 0x298143b1, 0x3a23dbc6,
+ 0xdd5ad13b, 0xcef8494c, 0xfa1fe1d5, 0xe9bd79a2,
+ 0x93d0b0e7, 0x80722890, 0xb4958009, 0xa737187e,
+ 0xff17c604, 0xecb55e73, 0xd852f6ea, 0xcbf06e9d,
+ 0xb19da7d8, 0xa23f3faf, 0x96d89736, 0x857a0f41,
+ 0x620305bc, 0x71a19dcb, 0x45463552, 0x56e4ad25,
+ 0x2c896460, 0x3f2bfc17, 0x0bcc548e, 0x186eccf9,
+ 0xc0d23785, 0xd370aff2, 0xe797076b, 0xf4359f1c,
+ 0x8e585659, 0x9dface2e, 0xa91d66b7, 0xbabffec0,
+ 0x5dc6f43d, 0x4e646c4a, 0x7a83c4d3, 0x69215ca4,
+ 0x134c95e1, 0x00ee0d96, 0x3409a50f, 0x27ab3d78,
+ 0x809c2506, 0x933ebd71, 0xa7d915e8, 0xb47b8d9f,
+ 0xce1644da, 0xddb4dcad, 0xe9537434, 0xfaf1ec43,
+ 0x1d88e6be, 0x0e2a7ec9, 0x3acdd650, 0x296f4e27,
+ 0x53028762, 0x40a01f15, 0x7447b78c, 0x67e52ffb,
+ 0xbf59d487, 0xacfb4cf0, 0x981ce469, 0x8bbe7c1e,
+ 0xf1d3b55b, 0xe2712d2c, 0xd69685b5, 0xc5341dc2,
+ 0x224d173f, 0x31ef8f48, 0x050827d1, 0x16aabfa6,
+ 0x6cc776e3, 0x7f65ee94, 0x4b82460d, 0x5820de7a,
+ 0xfbc3faf9, 0xe861628e, 0xdc86ca17, 0xcf245260,
+ 0xb5499b25, 0xa6eb0352, 0x920cabcb, 0x81ae33bc,
+ 0x66d73941, 0x7575a136, 0x419209af, 0x523091d8,
+ 0x285d589d, 0x3bffc0ea, 0x0f186873, 0x1cbaf004,
+ 0xc4060b78, 0xd7a4930f, 0xe3433b96, 0xf0e1a3e1,
+ 0x8a8c6aa4, 0x992ef2d3, 0xadc95a4a, 0xbe6bc23d,
+ 0x5912c8c0, 0x4ab050b7, 0x7e57f82e, 0x6df56059,
+ 0x1798a91c, 0x043a316b, 0x30dd99f2, 0x237f0185,
+ 0x844819fb, 0x97ea818c, 0xa30d2915, 0xb0afb162,
+ 0xcac27827, 0xd960e050, 0xed8748c9, 0xfe25d0be,
+ 0x195cda43, 0x0afe4234, 0x3e19eaad, 0x2dbb72da,
+ 0x57d6bb9f, 0x447423e8, 0x70938b71, 0x63311306,
+ 0xbb8de87a, 0xa82f700d, 0x9cc8d894, 0x8f6a40e3,
+ 0xf50789a6, 0xe6a511d1, 0xd242b948, 0xc1e0213f,
+ 0x26992bc2, 0x353bb3b5, 0x01dc1b2c, 0x127e835b,
+ 0x68134a1e, 0x7bb1d269, 0x4f567af0, 0x5cf4e287,
+ 0x04d43cfd, 0x1776a48a, 0x23910c13, 0x30339464,
+ 0x4a5e5d21, 0x59fcc556, 0x6d1b6dcf, 0x7eb9f5b8,
+ 0x99c0ff45, 0x8a626732, 0xbe85cfab, 0xad2757dc,
+ 0xd74a9e99, 0xc4e806ee, 0xf00fae77, 0xe3ad3600,
+ 0x3b11cd7c, 0x28b3550b, 0x1c54fd92, 0x0ff665e5,
+ 0x759baca0, 0x663934d7, 0x52de9c4e, 0x417c0439,
+ 0xa6050ec4, 0xb5a796b3, 0x81403e2a, 0x92e2a65d,
+ 0xe88f6f18, 0xfb2df76f, 0xcfca5ff6, 0xdc68c781,
+ 0x7b5fdfff, 0x68fd4788, 0x5c1aef11, 0x4fb87766,
+ 0x35d5be23, 0x26772654, 0x12908ecd, 0x013216ba,
+ 0xe64b1c47, 0xf5e98430, 0xc10e2ca9, 0xd2acb4de,
+ 0xa8c17d9b, 0xbb63e5ec, 0x8f844d75, 0x9c26d502,
+ 0x449a2e7e, 0x5738b609, 0x63df1e90, 0x707d86e7,
+ 0x0a104fa2, 0x19b2d7d5, 0x2d557f4c, 0x3ef7e73b,
+ 0xd98eedc6, 0xca2c75b1, 0xfecbdd28, 0xed69455f,
+ 0x97048c1a, 0x84a6146d, 0xb041bcf4, 0xa3e32483
+};
+static const uint32_t table2_[256] = {
+ 0x00000000, 0xa541927e, 0x4f6f520d, 0xea2ec073,
+ 0x9edea41a, 0x3b9f3664, 0xd1b1f617, 0x74f06469,
+ 0x38513ec5, 0x9d10acbb, 0x773e6cc8, 0xd27ffeb6,
+ 0xa68f9adf, 0x03ce08a1, 0xe9e0c8d2, 0x4ca15aac,
+ 0x70a27d8a, 0xd5e3eff4, 0x3fcd2f87, 0x9a8cbdf9,
+ 0xee7cd990, 0x4b3d4bee, 0xa1138b9d, 0x045219e3,
+ 0x48f3434f, 0xedb2d131, 0x079c1142, 0xa2dd833c,
+ 0xd62de755, 0x736c752b, 0x9942b558, 0x3c032726,
+ 0xe144fb14, 0x4405696a, 0xae2ba919, 0x0b6a3b67,
+ 0x7f9a5f0e, 0xdadbcd70, 0x30f50d03, 0x95b49f7d,
+ 0xd915c5d1, 0x7c5457af, 0x967a97dc, 0x333b05a2,
+ 0x47cb61cb, 0xe28af3b5, 0x08a433c6, 0xade5a1b8,
+ 0x91e6869e, 0x34a714e0, 0xde89d493, 0x7bc846ed,
+ 0x0f382284, 0xaa79b0fa, 0x40577089, 0xe516e2f7,
+ 0xa9b7b85b, 0x0cf62a25, 0xe6d8ea56, 0x43997828,
+ 0x37691c41, 0x92288e3f, 0x78064e4c, 0xdd47dc32,
+ 0xc76580d9, 0x622412a7, 0x880ad2d4, 0x2d4b40aa,
+ 0x59bb24c3, 0xfcfab6bd, 0x16d476ce, 0xb395e4b0,
+ 0xff34be1c, 0x5a752c62, 0xb05bec11, 0x151a7e6f,
+ 0x61ea1a06, 0xc4ab8878, 0x2e85480b, 0x8bc4da75,
+ 0xb7c7fd53, 0x12866f2d, 0xf8a8af5e, 0x5de93d20,
+ 0x29195949, 0x8c58cb37, 0x66760b44, 0xc337993a,
+ 0x8f96c396, 0x2ad751e8, 0xc0f9919b, 0x65b803e5,
+ 0x1148678c, 0xb409f5f2, 0x5e273581, 0xfb66a7ff,
+ 0x26217bcd, 0x8360e9b3, 0x694e29c0, 0xcc0fbbbe,
+ 0xb8ffdfd7, 0x1dbe4da9, 0xf7908dda, 0x52d11fa4,
+ 0x1e704508, 0xbb31d776, 0x511f1705, 0xf45e857b,
+ 0x80aee112, 0x25ef736c, 0xcfc1b31f, 0x6a802161,
+ 0x56830647, 0xf3c29439, 0x19ec544a, 0xbcadc634,
+ 0xc85da25d, 0x6d1c3023, 0x8732f050, 0x2273622e,
+ 0x6ed23882, 0xcb93aafc, 0x21bd6a8f, 0x84fcf8f1,
+ 0xf00c9c98, 0x554d0ee6, 0xbf63ce95, 0x1a225ceb,
+ 0x8b277743, 0x2e66e53d, 0xc448254e, 0x6109b730,
+ 0x15f9d359, 0xb0b84127, 0x5a968154, 0xffd7132a,
+ 0xb3764986, 0x1637dbf8, 0xfc191b8b, 0x595889f5,
+ 0x2da8ed9c, 0x88e97fe2, 0x62c7bf91, 0xc7862def,
+ 0xfb850ac9, 0x5ec498b7, 0xb4ea58c4, 0x11abcaba,
+ 0x655baed3, 0xc01a3cad, 0x2a34fcde, 0x8f756ea0,
+ 0xc3d4340c, 0x6695a672, 0x8cbb6601, 0x29faf47f,
+ 0x5d0a9016, 0xf84b0268, 0x1265c21b, 0xb7245065,
+ 0x6a638c57, 0xcf221e29, 0x250cde5a, 0x804d4c24,
+ 0xf4bd284d, 0x51fcba33, 0xbbd27a40, 0x1e93e83e,
+ 0x5232b292, 0xf77320ec, 0x1d5de09f, 0xb81c72e1,
+ 0xccec1688, 0x69ad84f6, 0x83834485, 0x26c2d6fb,
+ 0x1ac1f1dd, 0xbf8063a3, 0x55aea3d0, 0xf0ef31ae,
+ 0x841f55c7, 0x215ec7b9, 0xcb7007ca, 0x6e3195b4,
+ 0x2290cf18, 0x87d15d66, 0x6dff9d15, 0xc8be0f6b,
+ 0xbc4e6b02, 0x190ff97c, 0xf321390f, 0x5660ab71,
+ 0x4c42f79a, 0xe90365e4, 0x032da597, 0xa66c37e9,
+ 0xd29c5380, 0x77ddc1fe, 0x9df3018d, 0x38b293f3,
+ 0x7413c95f, 0xd1525b21, 0x3b7c9b52, 0x9e3d092c,
+ 0xeacd6d45, 0x4f8cff3b, 0xa5a23f48, 0x00e3ad36,
+ 0x3ce08a10, 0x99a1186e, 0x738fd81d, 0xd6ce4a63,
+ 0xa23e2e0a, 0x077fbc74, 0xed517c07, 0x4810ee79,
+ 0x04b1b4d5, 0xa1f026ab, 0x4bdee6d8, 0xee9f74a6,
+ 0x9a6f10cf, 0x3f2e82b1, 0xd50042c2, 0x7041d0bc,
+ 0xad060c8e, 0x08479ef0, 0xe2695e83, 0x4728ccfd,
+ 0x33d8a894, 0x96993aea, 0x7cb7fa99, 0xd9f668e7,
+ 0x9557324b, 0x3016a035, 0xda386046, 0x7f79f238,
+ 0x0b899651, 0xaec8042f, 0x44e6c45c, 0xe1a75622,
+ 0xdda47104, 0x78e5e37a, 0x92cb2309, 0x378ab177,
+ 0x437ad51e, 0xe63b4760, 0x0c158713, 0xa954156d,
+ 0xe5f54fc1, 0x40b4ddbf, 0xaa9a1dcc, 0x0fdb8fb2,
+ 0x7b2bebdb, 0xde6a79a5, 0x3444b9d6, 0x91052ba8
+};
+static const uint32_t table3_[256] = {
+ 0x00000000, 0xdd45aab8, 0xbf672381, 0x62228939,
+ 0x7b2231f3, 0xa6679b4b, 0xc4451272, 0x1900b8ca,
+ 0xf64463e6, 0x2b01c95e, 0x49234067, 0x9466eadf,
+ 0x8d665215, 0x5023f8ad, 0x32017194, 0xef44db2c,
+ 0xe964b13d, 0x34211b85, 0x560392bc, 0x8b463804,
+ 0x924680ce, 0x4f032a76, 0x2d21a34f, 0xf06409f7,
+ 0x1f20d2db, 0xc2657863, 0xa047f15a, 0x7d025be2,
+ 0x6402e328, 0xb9474990, 0xdb65c0a9, 0x06206a11,
+ 0xd725148b, 0x0a60be33, 0x6842370a, 0xb5079db2,
+ 0xac072578, 0x71428fc0, 0x136006f9, 0xce25ac41,
+ 0x2161776d, 0xfc24ddd5, 0x9e0654ec, 0x4343fe54,
+ 0x5a43469e, 0x8706ec26, 0xe524651f, 0x3861cfa7,
+ 0x3e41a5b6, 0xe3040f0e, 0x81268637, 0x5c632c8f,
+ 0x45639445, 0x98263efd, 0xfa04b7c4, 0x27411d7c,
+ 0xc805c650, 0x15406ce8, 0x7762e5d1, 0xaa274f69,
+ 0xb327f7a3, 0x6e625d1b, 0x0c40d422, 0xd1057e9a,
+ 0xaba65fe7, 0x76e3f55f, 0x14c17c66, 0xc984d6de,
+ 0xd0846e14, 0x0dc1c4ac, 0x6fe34d95, 0xb2a6e72d,
+ 0x5de23c01, 0x80a796b9, 0xe2851f80, 0x3fc0b538,
+ 0x26c00df2, 0xfb85a74a, 0x99a72e73, 0x44e284cb,
+ 0x42c2eeda, 0x9f874462, 0xfda5cd5b, 0x20e067e3,
+ 0x39e0df29, 0xe4a57591, 0x8687fca8, 0x5bc25610,
+ 0xb4868d3c, 0x69c32784, 0x0be1aebd, 0xd6a40405,
+ 0xcfa4bccf, 0x12e11677, 0x70c39f4e, 0xad8635f6,
+ 0x7c834b6c, 0xa1c6e1d4, 0xc3e468ed, 0x1ea1c255,
+ 0x07a17a9f, 0xdae4d027, 0xb8c6591e, 0x6583f3a6,
+ 0x8ac7288a, 0x57828232, 0x35a00b0b, 0xe8e5a1b3,
+ 0xf1e51979, 0x2ca0b3c1, 0x4e823af8, 0x93c79040,
+ 0x95e7fa51, 0x48a250e9, 0x2a80d9d0, 0xf7c57368,
+ 0xeec5cba2, 0x3380611a, 0x51a2e823, 0x8ce7429b,
+ 0x63a399b7, 0xbee6330f, 0xdcc4ba36, 0x0181108e,
+ 0x1881a844, 0xc5c402fc, 0xa7e68bc5, 0x7aa3217d,
+ 0x52a0c93f, 0x8fe56387, 0xedc7eabe, 0x30824006,
+ 0x2982f8cc, 0xf4c75274, 0x96e5db4d, 0x4ba071f5,
+ 0xa4e4aad9, 0x79a10061, 0x1b838958, 0xc6c623e0,
+ 0xdfc69b2a, 0x02833192, 0x60a1b8ab, 0xbde41213,
+ 0xbbc47802, 0x6681d2ba, 0x04a35b83, 0xd9e6f13b,
+ 0xc0e649f1, 0x1da3e349, 0x7f816a70, 0xa2c4c0c8,
+ 0x4d801be4, 0x90c5b15c, 0xf2e73865, 0x2fa292dd,
+ 0x36a22a17, 0xebe780af, 0x89c50996, 0x5480a32e,
+ 0x8585ddb4, 0x58c0770c, 0x3ae2fe35, 0xe7a7548d,
+ 0xfea7ec47, 0x23e246ff, 0x41c0cfc6, 0x9c85657e,
+ 0x73c1be52, 0xae8414ea, 0xcca69dd3, 0x11e3376b,
+ 0x08e38fa1, 0xd5a62519, 0xb784ac20, 0x6ac10698,
+ 0x6ce16c89, 0xb1a4c631, 0xd3864f08, 0x0ec3e5b0,
+ 0x17c35d7a, 0xca86f7c2, 0xa8a47efb, 0x75e1d443,
+ 0x9aa50f6f, 0x47e0a5d7, 0x25c22cee, 0xf8878656,
+ 0xe1873e9c, 0x3cc29424, 0x5ee01d1d, 0x83a5b7a5,
+ 0xf90696d8, 0x24433c60, 0x4661b559, 0x9b241fe1,
+ 0x8224a72b, 0x5f610d93, 0x3d4384aa, 0xe0062e12,
+ 0x0f42f53e, 0xd2075f86, 0xb025d6bf, 0x6d607c07,
+ 0x7460c4cd, 0xa9256e75, 0xcb07e74c, 0x16424df4,
+ 0x106227e5, 0xcd278d5d, 0xaf050464, 0x7240aedc,
+ 0x6b401616, 0xb605bcae, 0xd4273597, 0x09629f2f,
+ 0xe6264403, 0x3b63eebb, 0x59416782, 0x8404cd3a,
+ 0x9d0475f0, 0x4041df48, 0x22635671, 0xff26fcc9,
+ 0x2e238253, 0xf36628eb, 0x9144a1d2, 0x4c010b6a,
+ 0x5501b3a0, 0x88441918, 0xea669021, 0x37233a99,
+ 0xd867e1b5, 0x05224b0d, 0x6700c234, 0xba45688c,
+ 0xa345d046, 0x7e007afe, 0x1c22f3c7, 0xc167597f,
+ 0xc747336e, 0x1a0299d6, 0x782010ef, 0xa565ba57,
+ 0xbc65029d, 0x6120a825, 0x0302211c, 0xde478ba4,
+ 0x31035088, 0xec46fa30, 0x8e647309, 0x5321d9b1,
+ 0x4a21617b, 0x9764cbc3, 0xf54642fa, 0x2803e842
+};
+
+// Used to fetch a naturally-aligned 32-bit word in little endian byte-order
+static inline uint32_t LE_LOAD32(const uint8_t *p) {
+ return DecodeFixed32(reinterpret_cast<const char*>(p));
+}
+
+#if defined(HAVE_SSE42) && (defined(__LP64__) || defined(_WIN64))
+static inline uint64_t LE_LOAD64(const uint8_t *p) {
+ return DecodeFixed64(reinterpret_cast<const char*>(p));
+}
+#endif
+
+static inline void Slow_CRC32(uint64_t* l, uint8_t const **p) {
+ uint32_t c = static_cast<uint32_t>(*l ^ LE_LOAD32(*p));
+ *p += 4;
+ *l = table3_[c & 0xff] ^
+ table2_[(c >> 8) & 0xff] ^
+ table1_[(c >> 16) & 0xff] ^
+ table0_[c >> 24];
+ // DO it twice.
+ c = static_cast<uint32_t>(*l ^ LE_LOAD32(*p));
+ *p += 4;
+ *l = table3_[c & 0xff] ^
+ table2_[(c >> 8) & 0xff] ^
+ table1_[(c >> 16) & 0xff] ^
+ table0_[c >> 24];
+}
+
+static inline void Fast_CRC32(uint64_t* l, uint8_t const **p) {
+#ifndef HAVE_SSE42
+ Slow_CRC32(l, p);
+#elif defined(__LP64__) || defined(_WIN64)
+ *l = _mm_crc32_u64(*l, LE_LOAD64(*p));
+ *p += 8;
+#else
+ *l = _mm_crc32_u32(static_cast<unsigned int>(*l), LE_LOAD32(*p));
+ *p += 4;
+ *l = _mm_crc32_u32(static_cast<unsigned int>(*l), LE_LOAD32(*p));
+ *p += 4;
+#endif
+}
+
+template<void (*CRC32)(uint64_t*, uint8_t const**)>
+uint32_t ExtendImpl(uint32_t crc, const char* buf, size_t size) {
+
+ const uint8_t *p = reinterpret_cast<const uint8_t *>(buf);
+ const uint8_t *e = p + size;
+ uint64_t l = crc ^ 0xffffffffu;
+
+// Align n to (1 << m) byte boundary
+#define ALIGN(n, m) ((n + ((1 << m) - 1)) & ~((1 << m) - 1))
+
+#define STEP1 do { \
+ int c = (l & 0xff) ^ *p++; \
+ l = table0_[c] ^ (l >> 8); \
+} while (0)
+
+
+ // Point x at first 16-byte aligned byte in string. This might be
+ // just past the end of the string.
+ const uintptr_t pval = reinterpret_cast<uintptr_t>(p);
+ const uint8_t* x = reinterpret_cast<const uint8_t*>(ALIGN(pval, 4));
+ if (x <= e) {
+ // Process bytes until finished or p is 16-byte aligned
+ while (p != x) {
+ STEP1;
+ }
+ }
+ // Process bytes 16 at a time
+ while ((e-p) >= 16) {
+ CRC32(&l, &p);
+ CRC32(&l, &p);
+ }
+ // Process bytes 8 at a time
+ while ((e-p) >= 8) {
+ CRC32(&l, &p);
+ }
+ // Process the last few bytes
+ while (p != e) {
+ STEP1;
+ }
+#undef STEP1
+#undef ALIGN
+ return static_cast<uint32_t>(l ^ 0xffffffffu);
+}
+
+// Detect if ARM64 CRC or not.
+#ifndef HAVE_ARM64_CRC
+// Detect if SS42 or not.
+#ifndef HAVE_POWER8
+
+static bool isSSE42() {
+#ifndef HAVE_SSE42
+ return false;
+#elif defined(__GNUC__) && defined(__x86_64__) && !defined(IOS_CROSS_COMPILE)
+ uint32_t c_;
+ __asm__("cpuid" : "=c"(c_) : "a"(1) : "ebx", "edx");
+ return c_ & (1U << 20); // copied from CpuId.h in Folly. Test SSE42
+#elif defined(_WIN64)
+ int info[4];
+ __cpuidex(info, 0x00000001, 0);
+ return (info[2] & ((int)1 << 20)) != 0;
+#else
+ return false;
+#endif
+}
+
+static bool isPCLMULQDQ() {
+#ifndef HAVE_SSE42
+// in build_detect_platform we set this macro when both SSE42 and PCLMULQDQ are
+// supported by compiler
+ return false;
+#elif defined(__GNUC__) && defined(__x86_64__) && !defined(IOS_CROSS_COMPILE)
+ uint32_t c_;
+ __asm__("cpuid" : "=c"(c_) : "a"(1) : "ebx", "edx");
+ return c_ & (1U << 1); // PCLMULQDQ is in bit 1 (not bit 0)
+#elif defined(_WIN64)
+ int info[4];
+ __cpuidex(info, 0x00000001, 0);
+ return (info[2] & ((int)1 << 1)) != 0;
+#else
+ return false;
+#endif
+}
+
+#endif // HAVE_POWER8
+#endif // HAVE_ARM64_CRC
+
+typedef uint32_t (*Function)(uint32_t, const char*, size_t);
+
+#if defined(HAVE_POWER8) && defined(HAS_ALTIVEC)
+uint32_t ExtendPPCImpl(uint32_t crc, const char *buf, size_t size) {
+ return crc32c_ppc(crc, (const unsigned char *)buf, size);
+}
+
+#if __linux__
+static int arch_ppc_probe(void) {
+ arch_ppc_crc32 = 0;
+
+#if defined(__powerpc64__)
+ if (getauxval(AT_HWCAP2) & PPC_FEATURE2_VEC_CRYPTO) arch_ppc_crc32 = 1;
+#endif /* __powerpc64__ */
+
+ return arch_ppc_crc32;
+}
+#endif // __linux__
+
+static bool isAltiVec() {
+ if (arch_ppc_probe()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+#endif
+
+#if defined(__linux__) && defined(HAVE_ARM64_CRC)
+uint32_t ExtendARMImpl(uint32_t crc, const char *buf, size_t size) {
+ return crc32c_arm64(crc, (const unsigned char *)buf, size);
+}
+#endif
+
+std::string IsFastCrc32Supported() {
+ bool has_fast_crc = false;
+ std::string fast_zero_msg;
+ std::string arch;
+#ifdef HAVE_POWER8
+#ifdef HAS_ALTIVEC
+ if (arch_ppc_probe()) {
+ has_fast_crc = true;
+ arch = "PPC";
+ }
+#else
+ has_fast_crc = false;
+ arch = "PPC";
+#endif
+#elif defined(__linux__) && defined(HAVE_ARM64_CRC)
+ if (crc32c_runtime_check()) {
+ has_fast_crc = true;
+ arch = "Arm64";
+ } else {
+ has_fast_crc = false;
+ arch = "Arm64";
+ }
+#else
+ has_fast_crc = isSSE42();
+ arch = "x86";
+#endif
+ if (has_fast_crc) {
+ fast_zero_msg.append("Supported on " + arch);
+ }
+ else {
+ fast_zero_msg.append("Not supported on " + arch);
+ }
+ return fast_zero_msg;
+}
+
+
+/*
+ * Copyright 2016 Ferry Toth, Exalon Delft BV, The Netherlands
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the author be held liable for any damages
+ * arising from the use of this software.
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ * Ferry Toth
+ * ftoth@exalondelft.nl
+ *
+ * https://github.com/htot/crc32c
+ *
+ * Modified by Facebook
+ *
+ * Original intel whitepaper:
+ * "Fast CRC Computation for iSCSI Polynomial Using CRC32 Instruction"
+ * https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf
+ *
+ * This version is from the folly library, created by Dave Watson <davejwatson@fb.com>
+ *
+*/
+#if defined HAVE_SSE42 && defined HAVE_PCLMUL
+
+#define CRCtriplet(crc, buf, offset) \
+ crc##0 = _mm_crc32_u64(crc##0, *(buf##0 + offset)); \
+ crc##1 = _mm_crc32_u64(crc##1, *(buf##1 + offset)); \
+ crc##2 = _mm_crc32_u64(crc##2, *(buf##2 + offset));
+
+#define CRCduplet(crc, buf, offset) \
+ crc##0 = _mm_crc32_u64(crc##0, *(buf##0 + offset)); \
+ crc##1 = _mm_crc32_u64(crc##1, *(buf##1 + offset));
+
+#define CRCsinglet(crc, buf, offset) \
+ crc = _mm_crc32_u64(crc, *(uint64_t*)(buf + offset));
+
+
+// Numbers taken directly from intel whitepaper.
+// clang-format off
+const uint64_t clmul_constants[] = {
+ 0x14cd00bd6, 0x105ec76f0, 0x0ba4fc28e, 0x14cd00bd6,
+ 0x1d82c63da, 0x0f20c0dfe, 0x09e4addf8, 0x0ba4fc28e,
+ 0x039d3b296, 0x1384aa63a, 0x102f9b8a2, 0x1d82c63da,
+ 0x14237f5e6, 0x01c291d04, 0x00d3b6092, 0x09e4addf8,
+ 0x0c96cfdc0, 0x0740eef02, 0x18266e456, 0x039d3b296,
+ 0x0daece73e, 0x0083a6eec, 0x0ab7aff2a, 0x102f9b8a2,
+ 0x1248ea574, 0x1c1733996, 0x083348832, 0x14237f5e6,
+ 0x12c743124, 0x02ad91c30, 0x0b9e02b86, 0x00d3b6092,
+ 0x018b33a4e, 0x06992cea2, 0x1b331e26a, 0x0c96cfdc0,
+ 0x17d35ba46, 0x07e908048, 0x1bf2e8b8a, 0x18266e456,
+ 0x1a3e0968a, 0x11ed1f9d8, 0x0ce7f39f4, 0x0daece73e,
+ 0x061d82e56, 0x0f1d0f55e, 0x0d270f1a2, 0x0ab7aff2a,
+ 0x1c3f5f66c, 0x0a87ab8a8, 0x12ed0daac, 0x1248ea574,
+ 0x065863b64, 0x08462d800, 0x11eef4f8e, 0x083348832,
+ 0x1ee54f54c, 0x071d111a8, 0x0b3e32c28, 0x12c743124,
+ 0x0064f7f26, 0x0ffd852c6, 0x0dd7e3b0c, 0x0b9e02b86,
+ 0x0f285651c, 0x0dcb17aa4, 0x010746f3c, 0x018b33a4e,
+ 0x1c24afea4, 0x0f37c5aee, 0x0271d9844, 0x1b331e26a,
+ 0x08e766a0c, 0x06051d5a2, 0x093a5f730, 0x17d35ba46,
+ 0x06cb08e5c, 0x11d5ca20e, 0x06b749fb2, 0x1bf2e8b8a,
+ 0x1167f94f2, 0x021f3d99c, 0x0cec3662e, 0x1a3e0968a,
+ 0x19329634a, 0x08f158014, 0x0e6fc4e6a, 0x0ce7f39f4,
+ 0x08227bb8a, 0x1a5e82106, 0x0b0cd4768, 0x061d82e56,
+ 0x13c2b89c4, 0x188815ab2, 0x0d7a4825c, 0x0d270f1a2,
+ 0x10f5ff2ba, 0x105405f3e, 0x00167d312, 0x1c3f5f66c,
+ 0x0f6076544, 0x0e9adf796, 0x026f6a60a, 0x12ed0daac,
+ 0x1a2adb74e, 0x096638b34, 0x19d34af3a, 0x065863b64,
+ 0x049c3cc9c, 0x1e50585a0, 0x068bce87a, 0x11eef4f8e,
+ 0x1524fa6c6, 0x19f1c69dc, 0x16cba8aca, 0x1ee54f54c,
+ 0x042d98888, 0x12913343e, 0x1329d9f7e, 0x0b3e32c28,
+ 0x1b1c69528, 0x088f25a3a, 0x02178513a, 0x0064f7f26,
+ 0x0e0ac139e, 0x04e36f0b0, 0x0170076fa, 0x0dd7e3b0c,
+ 0x141a1a2e2, 0x0bd6f81f8, 0x16ad828b4, 0x0f285651c,
+ 0x041d17b64, 0x19425cbba, 0x1fae1cc66, 0x010746f3c,
+ 0x1a75b4b00, 0x18db37e8a, 0x0f872e54c, 0x1c24afea4,
+ 0x01e41e9fc, 0x04c144932, 0x086d8e4d2, 0x0271d9844,
+ 0x160f7af7a, 0x052148f02, 0x05bb8f1bc, 0x08e766a0c,
+ 0x0a90fd27a, 0x0a3c6f37a, 0x0b3af077a, 0x093a5f730,
+ 0x04984d782, 0x1d22c238e, 0x0ca6ef3ac, 0x06cb08e5c,
+ 0x0234e0b26, 0x063ded06a, 0x1d88abd4a, 0x06b749fb2,
+ 0x04597456a, 0x04d56973c, 0x0e9e28eb4, 0x1167f94f2,
+ 0x07b3ff57a, 0x19385bf2e, 0x0c9c8b782, 0x0cec3662e,
+ 0x13a9cba9e, 0x0e417f38a, 0x093e106a4, 0x19329634a,
+ 0x167001a9c, 0x14e727980, 0x1ddffc5d4, 0x0e6fc4e6a,
+ 0x00df04680, 0x0d104b8fc, 0x02342001e, 0x08227bb8a,
+ 0x00a2a8d7e, 0x05b397730, 0x168763fa6, 0x0b0cd4768,
+ 0x1ed5a407a, 0x0e78eb416, 0x0d2c3ed1a, 0x13c2b89c4,
+ 0x0995a5724, 0x1641378f0, 0x19b1afbc4, 0x0d7a4825c,
+ 0x109ffedc0, 0x08d96551c, 0x0f2271e60, 0x10f5ff2ba,
+ 0x00b0bf8ca, 0x00bf80dd2, 0x123888b7a, 0x00167d312,
+ 0x1e888f7dc, 0x18dcddd1c, 0x002ee03b2, 0x0f6076544,
+ 0x183e8d8fe, 0x06a45d2b2, 0x133d7a042, 0x026f6a60a,
+ 0x116b0f50c, 0x1dd3e10e8, 0x05fabe670, 0x1a2adb74e,
+ 0x130004488, 0x0de87806c, 0x000bcf5f6, 0x19d34af3a,
+ 0x18f0c7078, 0x014338754, 0x017f27698, 0x049c3cc9c,
+ 0x058ca5f00, 0x15e3e77ee, 0x1af900c24, 0x068bce87a,
+ 0x0b5cfca28, 0x0dd07448e, 0x0ded288f8, 0x1524fa6c6,
+ 0x059f229bc, 0x1d8048348, 0x06d390dec, 0x16cba8aca,
+ 0x037170390, 0x0a3e3e02c, 0x06353c1cc, 0x042d98888,
+ 0x0c4584f5c, 0x0d73c7bea, 0x1f16a3418, 0x1329d9f7e,
+ 0x0531377e2, 0x185137662, 0x1d8d9ca7c, 0x1b1c69528,
+ 0x0b25b29f2, 0x18a08b5bc, 0x19fb2a8b0, 0x02178513a,
+ 0x1a08fe6ac, 0x1da758ae0, 0x045cddf4e, 0x0e0ac139e,
+ 0x1a91647f2, 0x169cf9eb0, 0x1a0f717c4, 0x0170076fa,
+};
+
+// Compute the crc32c value for buffer smaller than 8
+#ifdef ROCKSDB_UBSAN_RUN
+#if defined(__clang__)
+__attribute__((__no_sanitize__("alignment")))
+#elif defined(__GNUC__)
+__attribute__((__no_sanitize_undefined__))
+#endif
+#endif
+inline void align_to_8(
+ size_t len,
+ uint64_t& crc0, // crc so far, updated on return
+ const unsigned char*& next) { // next data pointer, updated on return
+ uint32_t crc32bit = static_cast<uint32_t>(crc0);
+ if (len & 0x04) {
+ crc32bit = _mm_crc32_u32(crc32bit, *(uint32_t*)next);
+ next += sizeof(uint32_t);
+ }
+ if (len & 0x02) {
+ crc32bit = _mm_crc32_u16(crc32bit, *(uint16_t*)next);
+ next += sizeof(uint16_t);
+ }
+ if (len & 0x01) {
+ crc32bit = _mm_crc32_u8(crc32bit, *(next));
+ next++;
+ }
+ crc0 = crc32bit;
+}
+
+//
+// CombineCRC performs pclmulqdq multiplication of 2 partial CRC's and a well
+// chosen constant and xor's these with the remaining CRC.
+//
+inline uint64_t CombineCRC(
+ size_t block_size,
+ uint64_t crc0,
+ uint64_t crc1,
+ uint64_t crc2,
+ const uint64_t* next2) {
+ const auto multiplier =
+ *(reinterpret_cast<const __m128i*>(clmul_constants) + block_size - 1);
+ const auto crc0_xmm = _mm_set_epi64x(0, crc0);
+ const auto res0 = _mm_clmulepi64_si128(crc0_xmm, multiplier, 0x00);
+ const auto crc1_xmm = _mm_set_epi64x(0, crc1);
+ const auto res1 = _mm_clmulepi64_si128(crc1_xmm, multiplier, 0x10);
+ const auto res = _mm_xor_si128(res0, res1);
+ crc0 = _mm_cvtsi128_si64(res);
+ crc0 = crc0 ^ *((uint64_t*)next2 - 1);
+ crc2 = _mm_crc32_u64(crc2, crc0);
+ return crc2;
+}
+
+// Compute CRC-32C using the Intel hardware instruction.
+#ifdef ROCKSDB_UBSAN_RUN
+#if defined(__clang__)
+__attribute__((__no_sanitize__("alignment")))
+#elif defined(__GNUC__)
+__attribute__((__no_sanitize_undefined__))
+#endif
+#endif
+uint32_t crc32c_3way(uint32_t crc, const char* buf, size_t len) {
+ const unsigned char* next = (const unsigned char*)buf;
+ uint64_t count;
+ uint64_t crc0, crc1, crc2;
+ crc0 = crc ^ 0xffffffffu;
+
+
+ if (len >= 8) {
+ // if len > 216 then align and use triplets
+ if (len > 216) {
+ {
+ // Work on the bytes (< 8) before the first 8-byte alignment addr starts
+ uint64_t align_bytes = (8 - (uintptr_t)next) & 7;
+ len -= align_bytes;
+ align_to_8(align_bytes, crc0, next);
+ }
+
+ // Now work on the remaining blocks
+ count = len / 24; // number of triplets
+ len %= 24; // bytes remaining
+ uint64_t n = count >> 7; // #blocks = first block + full blocks
+ uint64_t block_size = count & 127;
+ if (block_size == 0) {
+ block_size = 128;
+ } else {
+ n++;
+ }
+ // points to the first byte of the next block
+ const uint64_t* next0 = (uint64_t*)next + block_size;
+ const uint64_t* next1 = next0 + block_size;
+ const uint64_t* next2 = next1 + block_size;
+
+ crc1 = crc2 = 0;
+ // Use Duff's device, a for() loop inside a switch()
+ // statement. This needs to execute at least once, round len
+ // down to nearest triplet multiple
+ switch (block_size) {
+ case 128:
+ do {
+ // jumps here for a full block of len 128
+ CRCtriplet(crc, next, -128);
+ FALLTHROUGH_INTENDED;
+ case 127:
+ // jumps here or below for the first block smaller
+ CRCtriplet(crc, next, -127);
+ FALLTHROUGH_INTENDED;
+ case 126:
+ CRCtriplet(crc, next, -126); // than 128
+ FALLTHROUGH_INTENDED;
+ case 125:
+ CRCtriplet(crc, next, -125);
+ FALLTHROUGH_INTENDED;
+ case 124:
+ CRCtriplet(crc, next, -124);
+ FALLTHROUGH_INTENDED;
+ case 123:
+ CRCtriplet(crc, next, -123);
+ FALLTHROUGH_INTENDED;
+ case 122:
+ CRCtriplet(crc, next, -122);
+ FALLTHROUGH_INTENDED;
+ case 121:
+ CRCtriplet(crc, next, -121);
+ FALLTHROUGH_INTENDED;
+ case 120:
+ CRCtriplet(crc, next, -120);
+ FALLTHROUGH_INTENDED;
+ case 119:
+ CRCtriplet(crc, next, -119);
+ FALLTHROUGH_INTENDED;
+ case 118:
+ CRCtriplet(crc, next, -118);
+ FALLTHROUGH_INTENDED;
+ case 117:
+ CRCtriplet(crc, next, -117);
+ FALLTHROUGH_INTENDED;
+ case 116:
+ CRCtriplet(crc, next, -116);
+ FALLTHROUGH_INTENDED;
+ case 115:
+ CRCtriplet(crc, next, -115);
+ FALLTHROUGH_INTENDED;
+ case 114:
+ CRCtriplet(crc, next, -114);
+ FALLTHROUGH_INTENDED;
+ case 113:
+ CRCtriplet(crc, next, -113);
+ FALLTHROUGH_INTENDED;
+ case 112:
+ CRCtriplet(crc, next, -112);
+ FALLTHROUGH_INTENDED;
+ case 111:
+ CRCtriplet(crc, next, -111);
+ FALLTHROUGH_INTENDED;
+ case 110:
+ CRCtriplet(crc, next, -110);
+ FALLTHROUGH_INTENDED;
+ case 109:
+ CRCtriplet(crc, next, -109);
+ FALLTHROUGH_INTENDED;
+ case 108:
+ CRCtriplet(crc, next, -108);
+ FALLTHROUGH_INTENDED;
+ case 107:
+ CRCtriplet(crc, next, -107);
+ FALLTHROUGH_INTENDED;
+ case 106:
+ CRCtriplet(crc, next, -106);
+ FALLTHROUGH_INTENDED;
+ case 105:
+ CRCtriplet(crc, next, -105);
+ FALLTHROUGH_INTENDED;
+ case 104:
+ CRCtriplet(crc, next, -104);
+ FALLTHROUGH_INTENDED;
+ case 103:
+ CRCtriplet(crc, next, -103);
+ FALLTHROUGH_INTENDED;
+ case 102:
+ CRCtriplet(crc, next, -102);
+ FALLTHROUGH_INTENDED;
+ case 101:
+ CRCtriplet(crc, next, -101);
+ FALLTHROUGH_INTENDED;
+ case 100:
+ CRCtriplet(crc, next, -100);
+ FALLTHROUGH_INTENDED;
+ case 99:
+ CRCtriplet(crc, next, -99);
+ FALLTHROUGH_INTENDED;
+ case 98:
+ CRCtriplet(crc, next, -98);
+ FALLTHROUGH_INTENDED;
+ case 97:
+ CRCtriplet(crc, next, -97);
+ FALLTHROUGH_INTENDED;
+ case 96:
+ CRCtriplet(crc, next, -96);
+ FALLTHROUGH_INTENDED;
+ case 95:
+ CRCtriplet(crc, next, -95);
+ FALLTHROUGH_INTENDED;
+ case 94:
+ CRCtriplet(crc, next, -94);
+ FALLTHROUGH_INTENDED;
+ case 93:
+ CRCtriplet(crc, next, -93);
+ FALLTHROUGH_INTENDED;
+ case 92:
+ CRCtriplet(crc, next, -92);
+ FALLTHROUGH_INTENDED;
+ case 91:
+ CRCtriplet(crc, next, -91);
+ FALLTHROUGH_INTENDED;
+ case 90:
+ CRCtriplet(crc, next, -90);
+ FALLTHROUGH_INTENDED;
+ case 89:
+ CRCtriplet(crc, next, -89);
+ FALLTHROUGH_INTENDED;
+ case 88:
+ CRCtriplet(crc, next, -88);
+ FALLTHROUGH_INTENDED;
+ case 87:
+ CRCtriplet(crc, next, -87);
+ FALLTHROUGH_INTENDED;
+ case 86:
+ CRCtriplet(crc, next, -86);
+ FALLTHROUGH_INTENDED;
+ case 85:
+ CRCtriplet(crc, next, -85);
+ FALLTHROUGH_INTENDED;
+ case 84:
+ CRCtriplet(crc, next, -84);
+ FALLTHROUGH_INTENDED;
+ case 83:
+ CRCtriplet(crc, next, -83);
+ FALLTHROUGH_INTENDED;
+ case 82:
+ CRCtriplet(crc, next, -82);
+ FALLTHROUGH_INTENDED;
+ case 81:
+ CRCtriplet(crc, next, -81);
+ FALLTHROUGH_INTENDED;
+ case 80:
+ CRCtriplet(crc, next, -80);
+ FALLTHROUGH_INTENDED;
+ case 79:
+ CRCtriplet(crc, next, -79);
+ FALLTHROUGH_INTENDED;
+ case 78:
+ CRCtriplet(crc, next, -78);
+ FALLTHROUGH_INTENDED;
+ case 77:
+ CRCtriplet(crc, next, -77);
+ FALLTHROUGH_INTENDED;
+ case 76:
+ CRCtriplet(crc, next, -76);
+ FALLTHROUGH_INTENDED;
+ case 75:
+ CRCtriplet(crc, next, -75);
+ FALLTHROUGH_INTENDED;
+ case 74:
+ CRCtriplet(crc, next, -74);
+ FALLTHROUGH_INTENDED;
+ case 73:
+ CRCtriplet(crc, next, -73);
+ FALLTHROUGH_INTENDED;
+ case 72:
+ CRCtriplet(crc, next, -72);
+ FALLTHROUGH_INTENDED;
+ case 71:
+ CRCtriplet(crc, next, -71);
+ FALLTHROUGH_INTENDED;
+ case 70:
+ CRCtriplet(crc, next, -70);
+ FALLTHROUGH_INTENDED;
+ case 69:
+ CRCtriplet(crc, next, -69);
+ FALLTHROUGH_INTENDED;
+ case 68:
+ CRCtriplet(crc, next, -68);
+ FALLTHROUGH_INTENDED;
+ case 67:
+ CRCtriplet(crc, next, -67);
+ FALLTHROUGH_INTENDED;
+ case 66:
+ CRCtriplet(crc, next, -66);
+ FALLTHROUGH_INTENDED;
+ case 65:
+ CRCtriplet(crc, next, -65);
+ FALLTHROUGH_INTENDED;
+ case 64:
+ CRCtriplet(crc, next, -64);
+ FALLTHROUGH_INTENDED;
+ case 63:
+ CRCtriplet(crc, next, -63);
+ FALLTHROUGH_INTENDED;
+ case 62:
+ CRCtriplet(crc, next, -62);
+ FALLTHROUGH_INTENDED;
+ case 61:
+ CRCtriplet(crc, next, -61);
+ FALLTHROUGH_INTENDED;
+ case 60:
+ CRCtriplet(crc, next, -60);
+ FALLTHROUGH_INTENDED;
+ case 59:
+ CRCtriplet(crc, next, -59);
+ FALLTHROUGH_INTENDED;
+ case 58:
+ CRCtriplet(crc, next, -58);
+ FALLTHROUGH_INTENDED;
+ case 57:
+ CRCtriplet(crc, next, -57);
+ FALLTHROUGH_INTENDED;
+ case 56:
+ CRCtriplet(crc, next, -56);
+ FALLTHROUGH_INTENDED;
+ case 55:
+ CRCtriplet(crc, next, -55);
+ FALLTHROUGH_INTENDED;
+ case 54:
+ CRCtriplet(crc, next, -54);
+ FALLTHROUGH_INTENDED;
+ case 53:
+ CRCtriplet(crc, next, -53);
+ FALLTHROUGH_INTENDED;
+ case 52:
+ CRCtriplet(crc, next, -52);
+ FALLTHROUGH_INTENDED;
+ case 51:
+ CRCtriplet(crc, next, -51);
+ FALLTHROUGH_INTENDED;
+ case 50:
+ CRCtriplet(crc, next, -50);
+ FALLTHROUGH_INTENDED;
+ case 49:
+ CRCtriplet(crc, next, -49);
+ FALLTHROUGH_INTENDED;
+ case 48:
+ CRCtriplet(crc, next, -48);
+ FALLTHROUGH_INTENDED;
+ case 47:
+ CRCtriplet(crc, next, -47);
+ FALLTHROUGH_INTENDED;
+ case 46:
+ CRCtriplet(crc, next, -46);
+ FALLTHROUGH_INTENDED;
+ case 45:
+ CRCtriplet(crc, next, -45);
+ FALLTHROUGH_INTENDED;
+ case 44:
+ CRCtriplet(crc, next, -44);
+ FALLTHROUGH_INTENDED;
+ case 43:
+ CRCtriplet(crc, next, -43);
+ FALLTHROUGH_INTENDED;
+ case 42:
+ CRCtriplet(crc, next, -42);
+ FALLTHROUGH_INTENDED;
+ case 41:
+ CRCtriplet(crc, next, -41);
+ FALLTHROUGH_INTENDED;
+ case 40:
+ CRCtriplet(crc, next, -40);
+ FALLTHROUGH_INTENDED;
+ case 39:
+ CRCtriplet(crc, next, -39);
+ FALLTHROUGH_INTENDED;
+ case 38:
+ CRCtriplet(crc, next, -38);
+ FALLTHROUGH_INTENDED;
+ case 37:
+ CRCtriplet(crc, next, -37);
+ FALLTHROUGH_INTENDED;
+ case 36:
+ CRCtriplet(crc, next, -36);
+ FALLTHROUGH_INTENDED;
+ case 35:
+ CRCtriplet(crc, next, -35);
+ FALLTHROUGH_INTENDED;
+ case 34:
+ CRCtriplet(crc, next, -34);
+ FALLTHROUGH_INTENDED;
+ case 33:
+ CRCtriplet(crc, next, -33);
+ FALLTHROUGH_INTENDED;
+ case 32:
+ CRCtriplet(crc, next, -32);
+ FALLTHROUGH_INTENDED;
+ case 31:
+ CRCtriplet(crc, next, -31);
+ FALLTHROUGH_INTENDED;
+ case 30:
+ CRCtriplet(crc, next, -30);
+ FALLTHROUGH_INTENDED;
+ case 29:
+ CRCtriplet(crc, next, -29);
+ FALLTHROUGH_INTENDED;
+ case 28:
+ CRCtriplet(crc, next, -28);
+ FALLTHROUGH_INTENDED;
+ case 27:
+ CRCtriplet(crc, next, -27);
+ FALLTHROUGH_INTENDED;
+ case 26:
+ CRCtriplet(crc, next, -26);
+ FALLTHROUGH_INTENDED;
+ case 25:
+ CRCtriplet(crc, next, -25);
+ FALLTHROUGH_INTENDED;
+ case 24:
+ CRCtriplet(crc, next, -24);
+ FALLTHROUGH_INTENDED;
+ case 23:
+ CRCtriplet(crc, next, -23);
+ FALLTHROUGH_INTENDED;
+ case 22:
+ CRCtriplet(crc, next, -22);
+ FALLTHROUGH_INTENDED;
+ case 21:
+ CRCtriplet(crc, next, -21);
+ FALLTHROUGH_INTENDED;
+ case 20:
+ CRCtriplet(crc, next, -20);
+ FALLTHROUGH_INTENDED;
+ case 19:
+ CRCtriplet(crc, next, -19);
+ FALLTHROUGH_INTENDED;
+ case 18:
+ CRCtriplet(crc, next, -18);
+ FALLTHROUGH_INTENDED;
+ case 17:
+ CRCtriplet(crc, next, -17);
+ FALLTHROUGH_INTENDED;
+ case 16:
+ CRCtriplet(crc, next, -16);
+ FALLTHROUGH_INTENDED;
+ case 15:
+ CRCtriplet(crc, next, -15);
+ FALLTHROUGH_INTENDED;
+ case 14:
+ CRCtriplet(crc, next, -14);
+ FALLTHROUGH_INTENDED;
+ case 13:
+ CRCtriplet(crc, next, -13);
+ FALLTHROUGH_INTENDED;
+ case 12:
+ CRCtriplet(crc, next, -12);
+ FALLTHROUGH_INTENDED;
+ case 11:
+ CRCtriplet(crc, next, -11);
+ FALLTHROUGH_INTENDED;
+ case 10:
+ CRCtriplet(crc, next, -10);
+ FALLTHROUGH_INTENDED;
+ case 9:
+ CRCtriplet(crc, next, -9);
+ FALLTHROUGH_INTENDED;
+ case 8:
+ CRCtriplet(crc, next, -8);
+ FALLTHROUGH_INTENDED;
+ case 7:
+ CRCtriplet(crc, next, -7);
+ FALLTHROUGH_INTENDED;
+ case 6:
+ CRCtriplet(crc, next, -6);
+ FALLTHROUGH_INTENDED;
+ case 5:
+ CRCtriplet(crc, next, -5);
+ FALLTHROUGH_INTENDED;
+ case 4:
+ CRCtriplet(crc, next, -4);
+ FALLTHROUGH_INTENDED;
+ case 3:
+ CRCtriplet(crc, next, -3);
+ FALLTHROUGH_INTENDED;
+ case 2:
+ CRCtriplet(crc, next, -2);
+ FALLTHROUGH_INTENDED;
+ case 1:
+ CRCduplet(crc, next, -1); // the final triplet is actually only 2
+ //{ CombineCRC(); }
+ crc0 = CombineCRC(block_size, crc0, crc1, crc2, next2);
+ if (--n > 0) {
+ crc1 = crc2 = 0;
+ block_size = 128;
+ // points to the first byte of the next block
+ next0 = next2 + 128;
+ next1 = next0 + 128; // from here on all blocks are 128 long
+ next2 = next1 + 128;
+ }
+ FALLTHROUGH_INTENDED;
+ case 0:;
+ } while (n > 0);
+ }
+ next = (const unsigned char*)next2;
+ }
+ uint64_t count2 = len >> 3; // 216 of less bytes is 27 or less singlets
+ len = len & 7;
+ next += (count2 * 8);
+ switch (count2) {
+ case 27:
+ CRCsinglet(crc0, next, -27 * 8);
+ FALLTHROUGH_INTENDED;
+ case 26:
+ CRCsinglet(crc0, next, -26 * 8);
+ FALLTHROUGH_INTENDED;
+ case 25:
+ CRCsinglet(crc0, next, -25 * 8);
+ FALLTHROUGH_INTENDED;
+ case 24:
+ CRCsinglet(crc0, next, -24 * 8);
+ FALLTHROUGH_INTENDED;
+ case 23:
+ CRCsinglet(crc0, next, -23 * 8);
+ FALLTHROUGH_INTENDED;
+ case 22:
+ CRCsinglet(crc0, next, -22 * 8);
+ FALLTHROUGH_INTENDED;
+ case 21:
+ CRCsinglet(crc0, next, -21 * 8);
+ FALLTHROUGH_INTENDED;
+ case 20:
+ CRCsinglet(crc0, next, -20 * 8);
+ FALLTHROUGH_INTENDED;
+ case 19:
+ CRCsinglet(crc0, next, -19 * 8);
+ FALLTHROUGH_INTENDED;
+ case 18:
+ CRCsinglet(crc0, next, -18 * 8);
+ FALLTHROUGH_INTENDED;
+ case 17:
+ CRCsinglet(crc0, next, -17 * 8);
+ FALLTHROUGH_INTENDED;
+ case 16:
+ CRCsinglet(crc0, next, -16 * 8);
+ FALLTHROUGH_INTENDED;
+ case 15:
+ CRCsinglet(crc0, next, -15 * 8);
+ FALLTHROUGH_INTENDED;
+ case 14:
+ CRCsinglet(crc0, next, -14 * 8);
+ FALLTHROUGH_INTENDED;
+ case 13:
+ CRCsinglet(crc0, next, -13 * 8);
+ FALLTHROUGH_INTENDED;
+ case 12:
+ CRCsinglet(crc0, next, -12 * 8);
+ FALLTHROUGH_INTENDED;
+ case 11:
+ CRCsinglet(crc0, next, -11 * 8);
+ FALLTHROUGH_INTENDED;
+ case 10:
+ CRCsinglet(crc0, next, -10 * 8);
+ FALLTHROUGH_INTENDED;
+ case 9:
+ CRCsinglet(crc0, next, -9 * 8);
+ FALLTHROUGH_INTENDED;
+ case 8:
+ CRCsinglet(crc0, next, -8 * 8);
+ FALLTHROUGH_INTENDED;
+ case 7:
+ CRCsinglet(crc0, next, -7 * 8);
+ FALLTHROUGH_INTENDED;
+ case 6:
+ CRCsinglet(crc0, next, -6 * 8);
+ FALLTHROUGH_INTENDED;
+ case 5:
+ CRCsinglet(crc0, next, -5 * 8);
+ FALLTHROUGH_INTENDED;
+ case 4:
+ CRCsinglet(crc0, next, -4 * 8);
+ FALLTHROUGH_INTENDED;
+ case 3:
+ CRCsinglet(crc0, next, -3 * 8);
+ FALLTHROUGH_INTENDED;
+ case 2:
+ CRCsinglet(crc0, next, -2 * 8);
+ FALLTHROUGH_INTENDED;
+ case 1:
+ CRCsinglet(crc0, next, -1 * 8);
+ FALLTHROUGH_INTENDED;
+ case 0:;
+ }
+ }
+ {
+ align_to_8(len, crc0, next);
+ return (uint32_t)crc0 ^ 0xffffffffu;
+ }
+}
+
+#endif //HAVE_SSE42 && HAVE_PCLMUL
+
+static inline Function Choose_Extend() {
+#ifdef HAVE_POWER8
+ return isAltiVec() ? ExtendPPCImpl : ExtendImpl<Slow_CRC32>;
+#elif defined(__linux__) && defined(HAVE_ARM64_CRC)
+ if(crc32c_runtime_check()) {
+ return ExtendARMImpl;
+ } else {
+ return ExtendImpl<Slow_CRC32>;
+ }
+#else
+ if (isSSE42()) {
+ if (isPCLMULQDQ()) {
+#if defined HAVE_SSE42 && defined HAVE_PCLMUL && !defined NO_THREEWAY_CRC32C
+ return crc32c_3way;
+#else
+ return ExtendImpl<Fast_CRC32>; // Fast_CRC32 will check HAVE_SSE42 itself
+#endif
+ }
+ else { // no runtime PCLMULQDQ support but has SSE42 support
+ return ExtendImpl<Fast_CRC32>;
+ }
+ } // end of isSSE42()
+ else {
+ return ExtendImpl<Slow_CRC32>;
+ }
+#endif
+}
+
+static Function ChosenExtend = Choose_Extend();
+uint32_t Extend(uint32_t crc, const char* buf, size_t size) {
+ return ChosenExtend(crc, buf, size);
+}
+
+
+} // namespace crc32c
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/crc32c.h b/storage/rocksdb/rocksdb/util/crc32c.h
new file mode 100644
index 00000000..a3c7cd1d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stddef.h>
+#include <stdint.h>
+#include <string>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace crc32c {
+
+extern std::string IsFastCrc32Supported();
+
+// Return the crc32c of concat(A, data[0,n-1]) where init_crc is the
+// crc32c of some string A. Extend() is often used to maintain the
+// crc32c of a stream of data.
+extern uint32_t Extend(uint32_t init_crc, const char* data, size_t n);
+
+// Return the crc32c of data[0,n-1]
+inline uint32_t Value(const char* data, size_t n) {
+ return Extend(0, data, n);
+}
+
+static const uint32_t kMaskDelta = 0xa282ead8ul;
+
+// Return a masked representation of crc.
+//
+// Motivation: it is problematic to compute the CRC of a string that
+// contains embedded CRCs. Therefore we recommend that CRCs stored
+// somewhere (e.g., in files) should be masked before being stored.
+inline uint32_t Mask(uint32_t crc) {
+ // Rotate right by 15 bits and add a constant.
+ return ((crc >> 15) | (crc << 17)) + kMaskDelta;
+}
+
+// Return the crc whose masked representation is masked_crc.
+inline uint32_t Unmask(uint32_t masked_crc) {
+ uint32_t rot = masked_crc - kMaskDelta;
+ return ((rot >> 17) | (rot << 15));
+}
+
+} // namespace crc32c
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/crc32c_arm64.cc b/storage/rocksdb/rocksdb/util/crc32c_arm64.cc
new file mode 100644
index 00000000..591c623a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_arm64.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, Arm Limited and 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 "util/crc32c_arm64.h"
+
+#if defined(__linux__) && defined(HAVE_ARM64_CRC)
+
+#include <asm/hwcap.h>
+#include <sys/auxv.h>
+#ifndef HWCAP_CRC32
+#define HWCAP_CRC32 (1 << 7)
+#endif
+
+#ifdef HAVE_ARM64_CRYPTO
+/* unfolding to compute 8 * 3 = 24 bytes parallelly */
+#define CRC32C24BYTES(ITR) \
+ crc1 = crc32c_u64(crc1, *(buf64 + BLK_LENGTH + (ITR))); \
+ crc2 = crc32c_u64(crc2, *(buf64 + BLK_LENGTH * 2 + (ITR))); \
+ crc0 = crc32c_u64(crc0, *(buf64 + (ITR)));
+
+/* unfolding to compute 24 * 7 = 168 bytes parallelly */
+#define CRC32C7X24BYTES(ITR) \
+ do { \
+ CRC32C24BYTES((ITR)*7 + 0) \
+ CRC32C24BYTES((ITR)*7 + 1) \
+ CRC32C24BYTES((ITR)*7 + 2) \
+ CRC32C24BYTES((ITR)*7 + 3) \
+ CRC32C24BYTES((ITR)*7 + 4) \
+ CRC32C24BYTES((ITR)*7 + 5) \
+ CRC32C24BYTES((ITR)*7 + 6) \
+ } while (0)
+#endif
+
+uint32_t crc32c_runtime_check(void) {
+ uint64_t auxv = getauxval(AT_HWCAP);
+ return (auxv & HWCAP_CRC32) != 0;
+}
+
+uint32_t crc32c_arm64(uint32_t crc, unsigned char const *data,
+ unsigned len) {
+ const uint8_t *buf8;
+ const uint64_t *buf64 = (uint64_t *)data;
+ int length = (int)len;
+ crc ^= 0xffffffff;
+
+#ifdef HAVE_ARM64_CRYPTO
+/* Crc32c Parallel computation
+ * Algorithm comes from Intel whitepaper:
+ * crc-iscsi-polynomial-crc32-instruction-paper
+ *
+ * Input data is divided into three equal-sized blocks
+ * Three parallel blocks (crc0, crc1, crc2) for 1024 Bytes
+ * One Block: 42(BLK_LENGTH) * 8(step length: crc32c_u64) bytes
+ */
+#define BLK_LENGTH 42
+ while (length >= 1024) {
+ uint64_t t0, t1;
+ uint32_t crc0 = 0, crc1 = 0, crc2 = 0;
+
+ /* Parallel Param:
+ * k0 = CRC32(x ^ (42 * 8 * 8 * 2 - 1));
+ * k1 = CRC32(x ^ (42 * 8 * 8 - 1));
+ */
+ uint32_t k0 = 0xe417f38a, k1 = 0x8f158014;
+
+ /* Prefetch data for following block to avoid cache miss */
+ PREF1KL1((uint8_t *)buf64, 1024);
+
+ /* First 8 byte for better pipelining */
+ crc0 = crc32c_u64(crc, *buf64++);
+
+ /* 3 blocks crc32c parallel computation
+ * Macro unfolding to compute parallelly
+ * 168 * 6 = 1008 (bytes)
+ */
+ CRC32C7X24BYTES(0);
+ CRC32C7X24BYTES(1);
+ CRC32C7X24BYTES(2);
+ CRC32C7X24BYTES(3);
+ CRC32C7X24BYTES(4);
+ CRC32C7X24BYTES(5);
+ buf64 += (BLK_LENGTH * 3);
+
+ /* Last 8 bytes */
+ crc = crc32c_u64(crc2, *buf64++);
+
+ t0 = (uint64_t)vmull_p64(crc0, k0);
+ t1 = (uint64_t)vmull_p64(crc1, k1);
+
+ /* Merge (crc0, crc1, crc2) -> crc */
+ crc1 = crc32c_u64(0, t1);
+ crc ^= crc1;
+ crc0 = crc32c_u64(0, t0);
+ crc ^= crc0;
+
+ length -= 1024;
+ }
+
+ if (length == 0) return crc ^ (0xffffffffU);
+#endif
+ buf8 = (const uint8_t *)buf64;
+ while (length >= 8) {
+ crc = crc32c_u64(crc, *(const uint64_t *)buf8);
+ buf8 += 8;
+ length -= 8;
+ }
+
+ /* The following is more efficient than the straight loop */
+ if (length >= 4) {
+ crc = crc32c_u32(crc, *(const uint32_t *)buf8);
+ buf8 += 4;
+ length -= 4;
+ }
+
+ if (length >= 2) {
+ crc = crc32c_u16(crc, *(const uint16_t *)buf8);
+ buf8 += 2;
+ length -= 2;
+ }
+
+ if (length >= 1) crc = crc32c_u8(crc, *buf8);
+
+ crc ^= 0xffffffff;
+ return crc;
+}
+
+#endif
diff --git a/storage/rocksdb/rocksdb/util/crc32c_arm64.h b/storage/rocksdb/rocksdb/util/crc32c_arm64.h
new file mode 100644
index 00000000..66fe30c1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_arm64.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, Arm Limited and 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).
+
+#ifndef UTIL_CRC32C_ARM64_H
+#define UTIL_CRC32C_ARM64_H
+
+#include <cinttypes>
+
+#if defined(__aarch64__) || defined(__AARCH64__)
+
+#ifdef __ARM_FEATURE_CRC32
+#define HAVE_ARM64_CRC
+#include <arm_acle.h>
+#define crc32c_u8(crc, v) __crc32cb(crc, v)
+#define crc32c_u16(crc, v) __crc32ch(crc, v)
+#define crc32c_u32(crc, v) __crc32cw(crc, v)
+#define crc32c_u64(crc, v) __crc32cd(crc, v)
+#define PREF4X64L1(buffer, PREF_OFFSET, ITR) \
+ __asm__("PRFM PLDL1KEEP, [%x[v],%[c]]" ::[v] "r"(buffer), \
+ [c] "I"((PREF_OFFSET) + ((ITR) + 0) * 64)); \
+ __asm__("PRFM PLDL1KEEP, [%x[v],%[c]]" ::[v] "r"(buffer), \
+ [c] "I"((PREF_OFFSET) + ((ITR) + 1) * 64)); \
+ __asm__("PRFM PLDL1KEEP, [%x[v],%[c]]" ::[v] "r"(buffer), \
+ [c] "I"((PREF_OFFSET) + ((ITR) + 2) * 64)); \
+ __asm__("PRFM PLDL1KEEP, [%x[v],%[c]]" ::[v] "r"(buffer), \
+ [c] "I"((PREF_OFFSET) + ((ITR) + 3) * 64));
+
+#define PREF1KL1(buffer, PREF_OFFSET) \
+ PREF4X64L1(buffer, (PREF_OFFSET), 0) \
+ PREF4X64L1(buffer, (PREF_OFFSET), 4) \
+ PREF4X64L1(buffer, (PREF_OFFSET), 8) \
+ PREF4X64L1(buffer, (PREF_OFFSET), 12)
+
+extern uint32_t crc32c_arm64(uint32_t crc, unsigned char const *data, unsigned len);
+extern uint32_t crc32c_runtime_check(void);
+
+#ifdef __ARM_FEATURE_CRYPTO
+#define HAVE_ARM64_CRYPTO
+#include <arm_neon.h>
+#endif // __ARM_FEATURE_CRYPTO
+#endif // __ARM_FEATURE_CRC32
+
+#endif // defined(__aarch64__) || defined(__AARCH64__)
+
+#endif
diff --git a/storage/rocksdb/rocksdb/util/crc32c_ppc.c b/storage/rocksdb/rocksdb/util/crc32c_ppc.c
new file mode 100644
index 00000000..888a4943
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_ppc.c
@@ -0,0 +1,94 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (c) 2017 International Business Machines Corp.
+// All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+#define CRC_TABLE
+#include <stdint.h>
+#include <stdlib.h>
+#include <strings.h>
+#include "util/crc32c_ppc_constants.h"
+
+#define VMX_ALIGN 16
+#define VMX_ALIGN_MASK (VMX_ALIGN - 1)
+
+#ifdef REFLECT
+static unsigned int crc32_align(unsigned int crc, unsigned char const *p,
+ unsigned long len) {
+ while (len--) crc = crc_table[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+ return crc;
+}
+#endif
+
+#ifdef HAVE_POWER8
+unsigned int __crc32_vpmsum(unsigned int crc, unsigned char const *p,
+ unsigned long len);
+
+static uint32_t crc32_vpmsum(uint32_t crc, unsigned char const *data,
+ unsigned len) {
+ unsigned int prealign;
+ unsigned int tail;
+
+#ifdef CRC_XOR
+ crc ^= 0xffffffff;
+#endif
+
+ if (len < VMX_ALIGN + VMX_ALIGN_MASK) {
+ crc = crc32_align(crc, data, (unsigned long)len);
+ goto out;
+ }
+
+ if ((unsigned long)data & VMX_ALIGN_MASK) {
+ prealign = VMX_ALIGN - ((unsigned long)data & VMX_ALIGN_MASK);
+ crc = crc32_align(crc, data, prealign);
+ len -= prealign;
+ data += prealign;
+ }
+
+ crc = __crc32_vpmsum(crc, data, (unsigned long)len & ~VMX_ALIGN_MASK);
+
+ tail = len & VMX_ALIGN_MASK;
+ if (tail) {
+ data += len & ~VMX_ALIGN_MASK;
+ crc = crc32_align(crc, data, tail);
+ }
+
+out:
+#ifdef CRC_XOR
+ crc ^= 0xffffffff;
+#endif
+
+ return crc;
+}
+
+/* This wrapper function works around the fact that crc32_vpmsum
+ * does not gracefully handle the case where the data pointer is NULL. There
+ * may be room for performance improvement here.
+ */
+uint32_t crc32c_ppc(uint32_t crc, unsigned char const *data, unsigned len) {
+ unsigned char *buf2;
+
+ if (!data) {
+ buf2 = (unsigned char *)malloc(len);
+ bzero(buf2, len);
+ crc = crc32_vpmsum(crc, buf2, len);
+ free(buf2);
+ } else {
+ crc = crc32_vpmsum(crc, data, (unsigned long)len);
+ }
+ return crc;
+}
+
+#else /* HAVE_POWER8 */
+
+/* This symbol has to exist on non-ppc architectures (and on legacy
+ * ppc systems using power7 or below) in order to compile properly
+ * there, even though it won't be called.
+ */
+uint32_t crc32c_ppc(uint32_t crc, unsigned char const *data, unsigned len) {
+ return 0;
+}
+
+#endif /* HAVE_POWER8 */
diff --git a/storage/rocksdb/rocksdb/util/crc32c_ppc.h b/storage/rocksdb/rocksdb/util/crc32c_ppc.h
new file mode 100644
index 00000000..c359061c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_ppc.h
@@ -0,0 +1,19 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (c) 2017 International Business Machines Corp.
+// All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern uint32_t crc32c_ppc(uint32_t crc, unsigned char const *buffer,
+ unsigned len);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/storage/rocksdb/rocksdb/util/crc32c_ppc_asm.S b/storage/rocksdb/rocksdb/util/crc32c_ppc_asm.S
new file mode 100644
index 00000000..a317bf96
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_ppc_asm.S
@@ -0,0 +1,752 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (c) 2015 Anton Blanchard <anton@au.ibm.com>, IBM
+// Copyright (c) 2017 International Business Machines Corp.
+// All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <ppc-asm.h>
+#include "ppc-opcode.h"
+
+#undef toc
+
+#ifndef r1
+#define r1 1
+#endif
+
+#ifndef r2
+#define r2 2
+#endif
+
+ .section .rodata
+.balign 16
+
+.byteswap_constant:
+ /* byte reverse permute constant */
+ .octa 0x0F0E0D0C0B0A09080706050403020100
+
+#define __ASSEMBLY__
+#include "crc32c_ppc_constants.h"
+
+ .text
+
+#if defined(__BIG_ENDIAN__) && defined(REFLECT)
+#define BYTESWAP_DATA
+#elif defined(__LITTLE_ENDIAN__) && !defined(REFLECT)
+#define BYTESWAP_DATA
+#else
+#undef BYTESWAP_DATA
+#endif
+
+#define off16 r25
+#define off32 r26
+#define off48 r27
+#define off64 r28
+#define off80 r29
+#define off96 r30
+#define off112 r31
+
+#define const1 v24
+#define const2 v25
+
+#define byteswap v26
+#define mask_32bit v27
+#define mask_64bit v28
+#define zeroes v29
+
+#ifdef BYTESWAP_DATA
+#define VPERM(A, B, C, D) vperm A, B, C, D
+#else
+#define VPERM(A, B, C, D)
+#endif
+
+/* unsigned int __crc32_vpmsum(unsigned int crc, void *p, unsigned long len) */
+FUNC_START(__crc32_vpmsum)
+ std r31,-8(r1)
+ std r30,-16(r1)
+ std r29,-24(r1)
+ std r28,-32(r1)
+ std r27,-40(r1)
+ std r26,-48(r1)
+ std r25,-56(r1)
+
+ li off16,16
+ li off32,32
+ li off48,48
+ li off64,64
+ li off80,80
+ li off96,96
+ li off112,112
+ li r0,0
+
+ /* Enough room for saving 10 non volatile VMX registers */
+ subi r6,r1,56+10*16
+ subi r7,r1,56+2*16
+
+ stvx v20,0,r6
+ stvx v21,off16,r6
+ stvx v22,off32,r6
+ stvx v23,off48,r6
+ stvx v24,off64,r6
+ stvx v25,off80,r6
+ stvx v26,off96,r6
+ stvx v27,off112,r6
+ stvx v28,0,r7
+ stvx v29,off16,r7
+
+ mr r10,r3
+
+ vxor zeroes,zeroes,zeroes
+ vspltisw v0,-1
+
+ vsldoi mask_32bit,zeroes,v0,4
+ vsldoi mask_64bit,zeroes,v0,8
+
+ /* Get the initial value into v8 */
+ vxor v8,v8,v8
+ MTVRD(v8, r3)
+#ifdef REFLECT
+ vsldoi v8,zeroes,v8,8 /* shift into bottom 32 bits */
+#else
+ vsldoi v8,v8,zeroes,4 /* shift into top 32 bits */
+#endif
+
+#ifdef BYTESWAP_DATA
+ addis r3,r2,.byteswap_constant@toc@ha
+ addi r3,r3,.byteswap_constant@toc@l
+
+ lvx byteswap,0,r3
+ addi r3,r3,16
+#endif
+
+ cmpdi r5,256
+ blt .Lshort
+
+ rldicr r6,r5,0,56
+
+ /* Checksum in blocks of MAX_SIZE */
+1: lis r7,MAX_SIZE@h
+ ori r7,r7,MAX_SIZE@l
+ mr r9,r7
+ cmpd r6,r7
+ bgt 2f
+ mr r7,r6
+2: subf r6,r7,r6
+
+ /* our main loop does 128 bytes at a time */
+ srdi r7,r7,7
+
+ /*
+ * Work out the offset into the constants table to start at. Each
+ * constant is 16 bytes, and it is used against 128 bytes of input
+ * data - 128 / 16 = 8
+ */
+ sldi r8,r7,4
+ srdi r9,r9,3
+ subf r8,r8,r9
+
+ /* We reduce our final 128 bytes in a separate step */
+ addi r7,r7,-1
+ mtctr r7
+
+ addis r3,r2,.constants@toc@ha
+ addi r3,r3,.constants@toc@l
+
+ /* Find the start of our constants */
+ add r3,r3,r8
+
+ /* zero v0-v7 which will contain our checksums */
+ vxor v0,v0,v0
+ vxor v1,v1,v1
+ vxor v2,v2,v2
+ vxor v3,v3,v3
+ vxor v4,v4,v4
+ vxor v5,v5,v5
+ vxor v6,v6,v6
+ vxor v7,v7,v7
+
+ lvx const1,0,r3
+
+ /*
+ * If we are looping back to consume more data we use the values
+ * already in v16-v23.
+ */
+ cmpdi r0,1
+ beq 2f
+
+ /* First warm up pass */
+ lvx v16,0,r4
+ lvx v17,off16,r4
+ VPERM(v16,v16,v16,byteswap)
+ VPERM(v17,v17,v17,byteswap)
+ lvx v18,off32,r4
+ lvx v19,off48,r4
+ VPERM(v18,v18,v18,byteswap)
+ VPERM(v19,v19,v19,byteswap)
+ lvx v20,off64,r4
+ lvx v21,off80,r4
+ VPERM(v20,v20,v20,byteswap)
+ VPERM(v21,v21,v21,byteswap)
+ lvx v22,off96,r4
+ lvx v23,off112,r4
+ VPERM(v22,v22,v22,byteswap)
+ VPERM(v23,v23,v23,byteswap)
+ addi r4,r4,8*16
+
+ /* xor in initial value */
+ vxor v16,v16,v8
+
+2: bdz .Lfirst_warm_up_done
+
+ addi r3,r3,16
+ lvx const2,0,r3
+
+ /* Second warm up pass */
+ VPMSUMD(v8,v16,const1)
+ lvx v16,0,r4
+ VPERM(v16,v16,v16,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v9,v17,const1)
+ lvx v17,off16,r4
+ VPERM(v17,v17,v17,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v10,v18,const1)
+ lvx v18,off32,r4
+ VPERM(v18,v18,v18,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v11,v19,const1)
+ lvx v19,off48,r4
+ VPERM(v19,v19,v19,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v12,v20,const1)
+ lvx v20,off64,r4
+ VPERM(v20,v20,v20,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v13,v21,const1)
+ lvx v21,off80,r4
+ VPERM(v21,v21,v21,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v14,v22,const1)
+ lvx v22,off96,r4
+ VPERM(v22,v22,v22,byteswap)
+ ori r2,r2,0
+
+ VPMSUMD(v15,v23,const1)
+ lvx v23,off112,r4
+ VPERM(v23,v23,v23,byteswap)
+
+ addi r4,r4,8*16
+
+ bdz .Lfirst_cool_down
+
+ /*
+ * main loop. We modulo schedule it such that it takes three iterations
+ * to complete - first iteration load, second iteration vpmsum, third
+ * iteration xor.
+ */
+ .balign 16
+4: lvx const1,0,r3
+ addi r3,r3,16
+ ori r2,r2,0
+
+ vxor v0,v0,v8
+ VPMSUMD(v8,v16,const2)
+ lvx v16,0,r4
+ VPERM(v16,v16,v16,byteswap)
+ ori r2,r2,0
+
+ vxor v1,v1,v9
+ VPMSUMD(v9,v17,const2)
+ lvx v17,off16,r4
+ VPERM(v17,v17,v17,byteswap)
+ ori r2,r2,0
+
+ vxor v2,v2,v10
+ VPMSUMD(v10,v18,const2)
+ lvx v18,off32,r4
+ VPERM(v18,v18,v18,byteswap)
+ ori r2,r2,0
+
+ vxor v3,v3,v11
+ VPMSUMD(v11,v19,const2)
+ lvx v19,off48,r4
+ VPERM(v19,v19,v19,byteswap)
+ lvx const2,0,r3
+ ori r2,r2,0
+
+ vxor v4,v4,v12
+ VPMSUMD(v12,v20,const1)
+ lvx v20,off64,r4
+ VPERM(v20,v20,v20,byteswap)
+ ori r2,r2,0
+
+ vxor v5,v5,v13
+ VPMSUMD(v13,v21,const1)
+ lvx v21,off80,r4
+ VPERM(v21,v21,v21,byteswap)
+ ori r2,r2,0
+
+ vxor v6,v6,v14
+ VPMSUMD(v14,v22,const1)
+ lvx v22,off96,r4
+ VPERM(v22,v22,v22,byteswap)
+ ori r2,r2,0
+
+ vxor v7,v7,v15
+ VPMSUMD(v15,v23,const1)
+ lvx v23,off112,r4
+ VPERM(v23,v23,v23,byteswap)
+
+ addi r4,r4,8*16
+
+ bdnz 4b
+
+.Lfirst_cool_down:
+ /* First cool down pass */
+ lvx const1,0,r3
+ addi r3,r3,16
+
+ vxor v0,v0,v8
+ VPMSUMD(v8,v16,const1)
+ ori r2,r2,0
+
+ vxor v1,v1,v9
+ VPMSUMD(v9,v17,const1)
+ ori r2,r2,0
+
+ vxor v2,v2,v10
+ VPMSUMD(v10,v18,const1)
+ ori r2,r2,0
+
+ vxor v3,v3,v11
+ VPMSUMD(v11,v19,const1)
+ ori r2,r2,0
+
+ vxor v4,v4,v12
+ VPMSUMD(v12,v20,const1)
+ ori r2,r2,0
+
+ vxor v5,v5,v13
+ VPMSUMD(v13,v21,const1)
+ ori r2,r2,0
+
+ vxor v6,v6,v14
+ VPMSUMD(v14,v22,const1)
+ ori r2,r2,0
+
+ vxor v7,v7,v15
+ VPMSUMD(v15,v23,const1)
+ ori r2,r2,0
+
+.Lsecond_cool_down:
+ /* Second cool down pass */
+ vxor v0,v0,v8
+ vxor v1,v1,v9
+ vxor v2,v2,v10
+ vxor v3,v3,v11
+ vxor v4,v4,v12
+ vxor v5,v5,v13
+ vxor v6,v6,v14
+ vxor v7,v7,v15
+
+#ifdef REFLECT
+ /*
+ * vpmsumd produces a 96 bit result in the least significant bits
+ * of the register. Since we are bit reflected we have to shift it
+ * left 32 bits so it occupies the least significant bits in the
+ * bit reflected domain.
+ */
+ vsldoi v0,v0,zeroes,4
+ vsldoi v1,v1,zeroes,4
+ vsldoi v2,v2,zeroes,4
+ vsldoi v3,v3,zeroes,4
+ vsldoi v4,v4,zeroes,4
+ vsldoi v5,v5,zeroes,4
+ vsldoi v6,v6,zeroes,4
+ vsldoi v7,v7,zeroes,4
+#endif
+
+ /* xor with last 1024 bits */
+ lvx v8,0,r4
+ lvx v9,off16,r4
+ VPERM(v8,v8,v8,byteswap)
+ VPERM(v9,v9,v9,byteswap)
+ lvx v10,off32,r4
+ lvx v11,off48,r4
+ VPERM(v10,v10,v10,byteswap)
+ VPERM(v11,v11,v11,byteswap)
+ lvx v12,off64,r4
+ lvx v13,off80,r4
+ VPERM(v12,v12,v12,byteswap)
+ VPERM(v13,v13,v13,byteswap)
+ lvx v14,off96,r4
+ lvx v15,off112,r4
+ VPERM(v14,v14,v14,byteswap)
+ VPERM(v15,v15,v15,byteswap)
+
+ addi r4,r4,8*16
+
+ vxor v16,v0,v8
+ vxor v17,v1,v9
+ vxor v18,v2,v10
+ vxor v19,v3,v11
+ vxor v20,v4,v12
+ vxor v21,v5,v13
+ vxor v22,v6,v14
+ vxor v23,v7,v15
+
+ li r0,1
+ cmpdi r6,0
+ addi r6,r6,128
+ bne 1b
+
+ /* Work out how many bytes we have left */
+ andi. r5,r5,127
+
+ /* Calculate where in the constant table we need to start */
+ subfic r6,r5,128
+ add r3,r3,r6
+
+ /* How many 16 byte chunks are in the tail */
+ srdi r7,r5,4
+ mtctr r7
+
+ /*
+ * Reduce the previously calculated 1024 bits to 64 bits, shifting
+ * 32 bits to include the trailing 32 bits of zeros
+ */
+ lvx v0,0,r3
+ lvx v1,off16,r3
+ lvx v2,off32,r3
+ lvx v3,off48,r3
+ lvx v4,off64,r3
+ lvx v5,off80,r3
+ lvx v6,off96,r3
+ lvx v7,off112,r3
+ addi r3,r3,8*16
+
+ VPMSUMW(v0,v16,v0)
+ VPMSUMW(v1,v17,v1)
+ VPMSUMW(v2,v18,v2)
+ VPMSUMW(v3,v19,v3)
+ VPMSUMW(v4,v20,v4)
+ VPMSUMW(v5,v21,v5)
+ VPMSUMW(v6,v22,v6)
+ VPMSUMW(v7,v23,v7)
+
+ /* Now reduce the tail (0 - 112 bytes) */
+ cmpdi r7,0
+ beq 1f
+
+ lvx v16,0,r4
+ lvx v17,0,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off16,r4
+ lvx v17,off16,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off32,r4
+ lvx v17,off32,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off48,r4
+ lvx v17,off48,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off64,r4
+ lvx v17,off64,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off80,r4
+ lvx v17,off80,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+ bdz 1f
+
+ lvx v16,off96,r4
+ lvx v17,off96,r3
+ VPERM(v16,v16,v16,byteswap)
+ VPMSUMW(v16,v16,v17)
+ vxor v0,v0,v16
+
+ /* Now xor all the parallel chunks together */
+1: vxor v0,v0,v1
+ vxor v2,v2,v3
+ vxor v4,v4,v5
+ vxor v6,v6,v7
+
+ vxor v0,v0,v2
+ vxor v4,v4,v6
+
+ vxor v0,v0,v4
+
+.Lbarrett_reduction:
+ /* Barrett constants */
+ addis r3,r2,.barrett_constants@toc@ha
+ addi r3,r3,.barrett_constants@toc@l
+
+ lvx const1,0,r3
+ lvx const2,off16,r3
+
+ vsldoi v1,v0,v0,8
+ vxor v0,v0,v1 /* xor two 64 bit results together */
+
+#ifdef REFLECT
+ /* shift left one bit */
+ vspltisb v1,1
+ vsl v0,v0,v1
+#endif
+
+ vand v0,v0,mask_64bit
+
+#ifndef REFLECT
+ /*
+ * Now for the Barrett reduction algorithm. The idea is to calculate q,
+ * the multiple of our polynomial that we need to subtract. By
+ * doing the computation 2x bits higher (ie 64 bits) and shifting the
+ * result back down 2x bits, we round down to the nearest multiple.
+ */
+ VPMSUMD(v1,v0,const1) /* ma */
+ vsldoi v1,zeroes,v1,8 /* q = floor(ma/(2^64)) */
+ VPMSUMD(v1,v1,const2) /* qn */
+ vxor v0,v0,v1 /* a - qn, subtraction is xor in GF(2) */
+
+ /*
+ * Get the result into r3. We need to shift it left 8 bytes:
+ * V0 [ 0 1 2 X ]
+ * V0 [ 0 X 2 3 ]
+ */
+ vsldoi v0,v0,zeroes,8 /* shift result into top 64 bits */
+#else
+ /*
+ * The reflected version of Barrett reduction. Instead of bit
+ * reflecting our data (which is expensive to do), we bit reflect our
+ * constants and our algorithm, which means the intermediate data in
+ * our vector registers goes from 0-63 instead of 63-0. We can reflect
+ * the algorithm because we don't carry in mod 2 arithmetic.
+ */
+ vand v1,v0,mask_32bit /* bottom 32 bits of a */
+ VPMSUMD(v1,v1,const1) /* ma */
+ vand v1,v1,mask_32bit /* bottom 32bits of ma */
+ VPMSUMD(v1,v1,const2) /* qn */
+ vxor v0,v0,v1 /* a - qn, subtraction is xor in GF(2) */
+
+ /*
+ * Since we are bit reflected, the result (ie the low 32 bits) is in
+ * the high 32 bits. We just need to shift it left 4 bytes
+ * V0 [ 0 1 X 3 ]
+ * V0 [ 0 X 2 3 ]
+ */
+ vsldoi v0,v0,zeroes,4 /* shift result into top 64 bits of */
+#endif
+
+ /* Get it into r3 */
+ MFVRD(r3, v0)
+
+.Lout:
+ subi r6,r1,56+10*16
+ subi r7,r1,56+2*16
+
+ lvx v20,0,r6
+ lvx v21,off16,r6
+ lvx v22,off32,r6
+ lvx v23,off48,r6
+ lvx v24,off64,r6
+ lvx v25,off80,r6
+ lvx v26,off96,r6
+ lvx v27,off112,r6
+ lvx v28,0,r7
+ lvx v29,off16,r7
+
+ ld r31,-8(r1)
+ ld r30,-16(r1)
+ ld r29,-24(r1)
+ ld r28,-32(r1)
+ ld r27,-40(r1)
+ ld r26,-48(r1)
+ ld r25,-56(r1)
+
+ blr
+
+.Lfirst_warm_up_done:
+ lvx const1,0,r3
+ addi r3,r3,16
+
+ VPMSUMD(v8,v16,const1)
+ VPMSUMD(v9,v17,const1)
+ VPMSUMD(v10,v18,const1)
+ VPMSUMD(v11,v19,const1)
+ VPMSUMD(v12,v20,const1)
+ VPMSUMD(v13,v21,const1)
+ VPMSUMD(v14,v22,const1)
+ VPMSUMD(v15,v23,const1)
+
+ b .Lsecond_cool_down
+
+.Lshort:
+ cmpdi r5,0
+ beq .Lzero
+
+ addis r3,r2,.short_constants@toc@ha
+ addi r3,r3,.short_constants@toc@l
+
+ /* Calculate where in the constant table we need to start */
+ subfic r6,r5,256
+ add r3,r3,r6
+
+ /* How many 16 byte chunks? */
+ srdi r7,r5,4
+ mtctr r7
+
+ vxor v19,v19,v19
+ vxor v20,v20,v20
+
+ lvx v0,0,r4
+ lvx v16,0,r3
+ VPERM(v0,v0,v16,byteswap)
+ vxor v0,v0,v8 /* xor in initial value */
+ VPMSUMW(v0,v0,v16)
+ bdz .Lv0
+
+ lvx v1,off16,r4
+ lvx v17,off16,r3
+ VPERM(v1,v1,v17,byteswap)
+ VPMSUMW(v1,v1,v17)
+ bdz .Lv1
+
+ lvx v2,off32,r4
+ lvx v16,off32,r3
+ VPERM(v2,v2,v16,byteswap)
+ VPMSUMW(v2,v2,v16)
+ bdz .Lv2
+
+ lvx v3,off48,r4
+ lvx v17,off48,r3
+ VPERM(v3,v3,v17,byteswap)
+ VPMSUMW(v3,v3,v17)
+ bdz .Lv3
+
+ lvx v4,off64,r4
+ lvx v16,off64,r3
+ VPERM(v4,v4,v16,byteswap)
+ VPMSUMW(v4,v4,v16)
+ bdz .Lv4
+
+ lvx v5,off80,r4
+ lvx v17,off80,r3
+ VPERM(v5,v5,v17,byteswap)
+ VPMSUMW(v5,v5,v17)
+ bdz .Lv5
+
+ lvx v6,off96,r4
+ lvx v16,off96,r3
+ VPERM(v6,v6,v16,byteswap)
+ VPMSUMW(v6,v6,v16)
+ bdz .Lv6
+
+ lvx v7,off112,r4
+ lvx v17,off112,r3
+ VPERM(v7,v7,v17,byteswap)
+ VPMSUMW(v7,v7,v17)
+ bdz .Lv7
+
+ addi r3,r3,128
+ addi r4,r4,128
+
+ lvx v8,0,r4
+ lvx v16,0,r3
+ VPERM(v8,v8,v16,byteswap)
+ VPMSUMW(v8,v8,v16)
+ bdz .Lv8
+
+ lvx v9,off16,r4
+ lvx v17,off16,r3
+ VPERM(v9,v9,v17,byteswap)
+ VPMSUMW(v9,v9,v17)
+ bdz .Lv9
+
+ lvx v10,off32,r4
+ lvx v16,off32,r3
+ VPERM(v10,v10,v16,byteswap)
+ VPMSUMW(v10,v10,v16)
+ bdz .Lv10
+
+ lvx v11,off48,r4
+ lvx v17,off48,r3
+ VPERM(v11,v11,v17,byteswap)
+ VPMSUMW(v11,v11,v17)
+ bdz .Lv11
+
+ lvx v12,off64,r4
+ lvx v16,off64,r3
+ VPERM(v12,v12,v16,byteswap)
+ VPMSUMW(v12,v12,v16)
+ bdz .Lv12
+
+ lvx v13,off80,r4
+ lvx v17,off80,r3
+ VPERM(v13,v13,v17,byteswap)
+ VPMSUMW(v13,v13,v17)
+ bdz .Lv13
+
+ lvx v14,off96,r4
+ lvx v16,off96,r3
+ VPERM(v14,v14,v16,byteswap)
+ VPMSUMW(v14,v14,v16)
+ bdz .Lv14
+
+ lvx v15,off112,r4
+ lvx v17,off112,r3
+ VPERM(v15,v15,v17,byteswap)
+ VPMSUMW(v15,v15,v17)
+
+.Lv15: vxor v19,v19,v15
+.Lv14: vxor v20,v20,v14
+.Lv13: vxor v19,v19,v13
+.Lv12: vxor v20,v20,v12
+.Lv11: vxor v19,v19,v11
+.Lv10: vxor v20,v20,v10
+.Lv9: vxor v19,v19,v9
+.Lv8: vxor v20,v20,v8
+.Lv7: vxor v19,v19,v7
+.Lv6: vxor v20,v20,v6
+.Lv5: vxor v19,v19,v5
+.Lv4: vxor v20,v20,v4
+.Lv3: vxor v19,v19,v3
+.Lv2: vxor v20,v20,v2
+.Lv1: vxor v19,v19,v1
+.Lv0: vxor v20,v20,v0
+
+ vxor v0,v19,v20
+
+ b .Lbarrett_reduction
+
+.Lzero:
+ mr r3,r10
+ b .Lout
+
+FUNC_END(__crc32_vpmsum)
diff --git a/storage/rocksdb/rocksdb/util/crc32c_ppc_constants.h b/storage/rocksdb/rocksdb/util/crc32c_ppc_constants.h
new file mode 100644
index 00000000..f6494cd0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_ppc_constants.h
@@ -0,0 +1,900 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (C) 2015, 2017 International Business Machines Corp.
+// All rights reserved.
+// This source code is licensed under both the GPLv2 (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 CRC 0x1edc6f41
+#define REFLECT
+#define CRC_XOR
+
+#ifndef __ASSEMBLY__
+#ifdef CRC_TABLE
+static const unsigned int crc_table[] = {
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
+ 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
+ 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
+ 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
+ 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
+ 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
+ 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
+ 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
+ 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
+ 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
+ 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
+ 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
+ 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
+ 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
+ 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
+ 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
+ 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
+ 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
+ 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
+ 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
+ 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
+ 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351,
+};
+
+#endif
+
+#else
+#define MAX_SIZE 32768
+.constants :
+
+ /* Reduce 262144 kbits to 1024 bits */
+ /* x^261120 mod p(x)` << 1, x^261184 mod p(x)` << 1 */
+ .octa 0x00000000b6ca9e20000000009c37c408
+
+ /* x^260096 mod p(x)` << 1, x^260160 mod p(x)` << 1 */
+ .octa 0x00000000350249a800000001b51df26c
+
+ /* x^259072 mod p(x)` << 1, x^259136 mod p(x)` << 1 */
+ .octa 0x00000001862dac54000000000724b9d0
+
+ /* x^258048 mod p(x)` << 1, x^258112 mod p(x)` << 1 */
+ .octa 0x00000001d87fb48c00000001c00532fe
+
+ /* x^257024 mod p(x)` << 1, x^257088 mod p(x)` << 1 */
+ .octa 0x00000001f39b699e00000000f05a9362
+
+ /* x^256000 mod p(x)` << 1, x^256064 mod p(x)` << 1 */
+ .octa 0x0000000101da11b400000001e1007970
+
+ /* x^254976 mod p(x)` << 1, x^255040 mod p(x)` << 1 */
+ .octa 0x00000001cab571e000000000a57366ee
+
+ /* x^253952 mod p(x)` << 1, x^254016 mod p(x)` << 1 */
+ .octa 0x00000000c7020cfe0000000192011284
+
+ /* x^252928 mod p(x)` << 1, x^252992 mod p(x)` << 1 */
+ .octa 0x00000000cdaed1ae0000000162716d9a
+
+ /* x^251904 mod p(x)` << 1, x^251968 mod p(x)` << 1 */
+ .octa 0x00000001e804effc00000000cd97ecde
+
+ /* x^250880 mod p(x)` << 1, x^250944 mod p(x)` << 1 */
+ .octa 0x0000000077c3ea3a0000000058812bc0
+
+ /* x^249856 mod p(x)` << 1, x^249920 mod p(x)` << 1 */
+ .octa 0x0000000068df31b40000000088b8c12e
+
+ /* x^248832 mod p(x)` << 1, x^248896 mod p(x)` << 1 */
+ .octa 0x00000000b059b6c200000001230b234c
+
+ /* x^247808 mod p(x)` << 1, x^247872 mod p(x)` << 1 */
+ .octa 0x0000000145fb8ed800000001120b416e
+
+ /* x^246784 mod p(x)` << 1, x^246848 mod p(x)` << 1 */
+ .octa 0x00000000cbc0916800000001974aecb0
+
+ /* x^245760 mod p(x)` << 1, x^245824 mod p(x)` << 1 */
+ .octa 0x000000005ceeedc2000000008ee3f226
+
+ /* x^244736 mod p(x)` << 1, x^244800 mod p(x)` << 1 */
+ .octa 0x0000000047d74e8600000001089aba9a
+
+ /* x^243712 mod p(x)` << 1, x^243776 mod p(x)` << 1 */
+ .octa 0x00000001407e9e220000000065113872
+
+ /* x^242688 mod p(x)` << 1, x^242752 mod p(x)` << 1 */
+ .octa 0x00000001da967bda000000005c07ec10
+
+ /* x^241664 mod p(x)` << 1, x^241728 mod p(x)` << 1 */
+ .octa 0x000000006c8983680000000187590924
+
+ /* x^240640 mod p(x)` << 1, x^240704 mod p(x)` << 1 */
+ .octa 0x00000000f2d14c9800000000e35da7c6
+
+ /* x^239616 mod p(x)` << 1, x^239680 mod p(x)` << 1 */
+ .octa 0x00000001993c6ad4000000000415855a
+
+ /* x^238592 mod p(x)` << 1, x^238656 mod p(x)` << 1 */
+ .octa 0x000000014683d1ac0000000073617758
+
+ /* x^237568 mod p(x)` << 1, x^237632 mod p(x)` << 1 */
+ .octa 0x00000001a7c93e6c0000000176021d28
+
+ /* x^236544 mod p(x)` << 1, x^236608 mod p(x)` << 1 */
+ .octa 0x000000010211e90a00000001c358fd0a
+
+ /* x^235520 mod p(x)` << 1, x^235584 mod p(x)` << 1 */
+ .octa 0x000000001119403e00000001ff7a2c18
+
+ /* x^234496 mod p(x)` << 1, x^234560 mod p(x)` << 1 */
+ .octa 0x000000001c3261aa00000000f2d9f7e4
+
+ /* x^233472 mod p(x)` << 1, x^233536 mod p(x)` << 1 */
+ .octa 0x000000014e37a634000000016cf1f9c8
+
+ /* x^232448 mod p(x)` << 1, x^232512 mod p(x)` << 1 */
+ .octa 0x0000000073786c0c000000010af9279a
+
+ /* x^231424 mod p(x)` << 1, x^231488 mod p(x)` << 1 */
+ .octa 0x000000011dc037f80000000004f101e8
+
+ /* x^230400 mod p(x)` << 1, x^230464 mod p(x)` << 1 */
+ .octa 0x0000000031433dfc0000000070bcf184
+
+ /* x^229376 mod p(x)` << 1, x^229440 mod p(x)` << 1 */
+ .octa 0x000000009cde8348000000000a8de642
+
+ /* x^228352 mod p(x)` << 1, x^228416 mod p(x)` << 1 */
+ .octa 0x0000000038d3c2a60000000062ea130c
+
+ /* x^227328 mod p(x)` << 1, x^227392 mod p(x)` << 1 */
+ .octa 0x000000011b25f26000000001eb31cbb2
+
+ /* x^226304 mod p(x)` << 1, x^226368 mod p(x)` << 1 */
+ .octa 0x000000001629e6f00000000170783448
+
+ /* x^225280 mod p(x)` << 1, x^225344 mod p(x)` << 1 */
+ .octa 0x0000000160838b4c00000001a684b4c6
+
+ /* x^224256 mod p(x)` << 1, x^224320 mod p(x)` << 1 */
+ .octa 0x000000007a44011c00000000253ca5b4
+
+ /* x^223232 mod p(x)` << 1, x^223296 mod p(x)` << 1 */
+ .octa 0x00000000226f417a0000000057b4b1e2
+
+ /* x^222208 mod p(x)` << 1, x^222272 mod p(x)` << 1 */
+ .octa 0x0000000045eb2eb400000000b6bd084c
+
+ /* x^221184 mod p(x)` << 1, x^221248 mod p(x)` << 1 */
+ .octa 0x000000014459d70c0000000123c2d592
+
+ /* x^220160 mod p(x)` << 1, x^220224 mod p(x)` << 1 */
+ .octa 0x00000001d406ed8200000000159dafce
+
+ /* x^219136 mod p(x)` << 1, x^219200 mod p(x)` << 1 */
+ .octa 0x0000000160c8e1a80000000127e1a64e
+
+ /* x^218112 mod p(x)` << 1, x^218176 mod p(x)` << 1 */
+ .octa 0x0000000027ba80980000000056860754
+
+ /* x^217088 mod p(x)` << 1, x^217152 mod p(x)` << 1 */
+ .octa 0x000000006d92d01800000001e661aae8
+
+ /* x^216064 mod p(x)` << 1, x^216128 mod p(x)` << 1 */
+ .octa 0x000000012ed7e3f200000000f82c6166
+
+ /* x^215040 mod p(x)` << 1, x^215104 mod p(x)` << 1 */
+ .octa 0x000000002dc8778800000000c4f9c7ae
+
+ /* x^214016 mod p(x)` << 1, x^214080 mod p(x)` << 1 */
+ .octa 0x0000000018240bb80000000074203d20
+
+ /* x^212992 mod p(x)` << 1, x^213056 mod p(x)` << 1 */
+ .octa 0x000000001ad381580000000198173052
+
+ /* x^211968 mod p(x)` << 1, x^212032 mod p(x)` << 1 */
+ .octa 0x00000001396b78f200000001ce8aba54
+
+ /* x^210944 mod p(x)` << 1, x^211008 mod p(x)` << 1 */
+ .octa 0x000000011a68133400000001850d5d94
+
+ /* x^209920 mod p(x)` << 1, x^209984 mod p(x)` << 1 */
+ .octa 0x000000012104732e00000001d609239c
+
+ /* x^208896 mod p(x)` << 1, x^208960 mod p(x)` << 1 */
+ .octa 0x00000000a140d90c000000001595f048
+
+ /* x^207872 mod p(x)` << 1, x^207936 mod p(x)` << 1 */
+ .octa 0x00000001b7215eda0000000042ccee08
+
+ /* x^206848 mod p(x)` << 1, x^206912 mod p(x)` << 1 */
+ .octa 0x00000001aaf1df3c000000010a389d74
+
+ /* x^205824 mod p(x)` << 1, x^205888 mod p(x)` << 1 */
+ .octa 0x0000000029d15b8a000000012a840da6
+
+ /* x^204800 mod p(x)` << 1, x^204864 mod p(x)` << 1 */
+ .octa 0x00000000f1a96922000000001d181c0c
+
+ /* x^203776 mod p(x)` << 1, x^203840 mod p(x)` << 1 */
+ .octa 0x00000001ac80d03c0000000068b7d1f6
+
+ /* x^202752 mod p(x)` << 1, x^202816 mod p(x)` << 1 */
+ .octa 0x000000000f11d56a000000005b0f14fc
+
+ /* x^201728 mod p(x)` << 1, x^201792 mod p(x)` << 1 */
+ .octa 0x00000001f1c022a20000000179e9e730
+
+ /* x^200704 mod p(x)` << 1, x^200768 mod p(x)` << 1 */
+ .octa 0x0000000173d00ae200000001ce1368d6
+
+ /* x^199680 mod p(x)` << 1, x^199744 mod p(x)` << 1 */
+ .octa 0x00000001d4ffe4ac0000000112c3a84c
+
+ /* x^198656 mod p(x)` << 1, x^198720 mod p(x)` << 1 */
+ .octa 0x000000016edc5ae400000000de940fee
+
+ /* x^197632 mod p(x)` << 1, x^197696 mod p(x)` << 1 */
+ .octa 0x00000001f1a0214000000000fe896b7e
+
+ /* x^196608 mod p(x)` << 1, x^196672 mod p(x)` << 1 */
+ .octa 0x00000000ca0b28a000000001f797431c
+
+ /* x^195584 mod p(x)` << 1, x^195648 mod p(x)` << 1 */
+ .octa 0x00000001928e30a20000000053e989ba
+
+ /* x^194560 mod p(x)` << 1, x^194624 mod p(x)` << 1 */
+ .octa 0x0000000097b1b002000000003920cd16
+
+ /* x^193536 mod p(x)` << 1, x^193600 mod p(x)` << 1 */
+ .octa 0x00000000b15bf90600000001e6f579b8
+
+ /* x^192512 mod p(x)` << 1, x^192576 mod p(x)` << 1 */
+ .octa 0x00000000411c5d52000000007493cb0a
+
+ /* x^191488 mod p(x)` << 1, x^191552 mod p(x)` << 1 */
+ .octa 0x00000001c36f330000000001bdd376d8
+
+ /* x^190464 mod p(x)` << 1, x^190528 mod p(x)` << 1 */
+ .octa 0x00000001119227e0000000016badfee6
+
+ /* x^189440 mod p(x)` << 1, x^189504 mod p(x)` << 1 */
+ .octa 0x00000000114d47020000000071de5c58
+
+ /* x^188416 mod p(x)` << 1, x^188480 mod p(x)` << 1 */
+ .octa 0x00000000458b5b9800000000453f317c
+
+ /* x^187392 mod p(x)` << 1, x^187456 mod p(x)` << 1 */
+ .octa 0x000000012e31fb8e0000000121675cce
+
+ /* x^186368 mod p(x)` << 1, x^186432 mod p(x)` << 1 */
+ .octa 0x000000005cf619d800000001f409ee92
+
+ /* x^185344 mod p(x)` << 1, x^185408 mod p(x)` << 1 */
+ .octa 0x0000000063f4d8b200000000f36b9c88
+
+ /* x^184320 mod p(x)` << 1, x^184384 mod p(x)` << 1 */
+ .octa 0x000000004138dc8a0000000036b398f4
+
+ /* x^183296 mod p(x)` << 1, x^183360 mod p(x)` << 1 */
+ .octa 0x00000001d29ee8e000000001748f9adc
+
+ /* x^182272 mod p(x)` << 1, x^182336 mod p(x)` << 1 */
+ .octa 0x000000006a08ace800000001be94ec00
+
+ /* x^181248 mod p(x)` << 1, x^181312 mod p(x)` << 1 */
+ .octa 0x0000000127d4201000000000b74370d6
+
+ /* x^180224 mod p(x)` << 1, x^180288 mod p(x)` << 1 */
+ .octa 0x0000000019d76b6200000001174d0b98
+
+ /* x^179200 mod p(x)` << 1, x^179264 mod p(x)` << 1 */
+ .octa 0x00000001b1471f6e00000000befc06a4
+
+ /* x^178176 mod p(x)` << 1, x^178240 mod p(x)` << 1 */
+ .octa 0x00000001f64c19cc00000001ae125288
+
+ /* x^177152 mod p(x)` << 1, x^177216 mod p(x)` << 1 */
+ .octa 0x00000000003c0ea00000000095c19b34
+
+ /* x^176128 mod p(x)` << 1, x^176192 mod p(x)` << 1 */
+ .octa 0x000000014d73abf600000001a78496f2
+
+ /* x^175104 mod p(x)` << 1, x^175168 mod p(x)` << 1 */
+ .octa 0x00000001620eb84400000001ac5390a0
+
+ /* x^174080 mod p(x)` << 1, x^174144 mod p(x)` << 1 */
+ .octa 0x0000000147655048000000002a80ed6e
+
+ /* x^173056 mod p(x)` << 1, x^173120 mod p(x)` << 1 */
+ .octa 0x0000000067b5077e00000001fa9b0128
+
+ /* x^172032 mod p(x)` << 1, x^172096 mod p(x)` << 1 */
+ .octa 0x0000000010ffe20600000001ea94929e
+
+ /* x^171008 mod p(x)` << 1, x^171072 mod p(x)` << 1 */
+ .octa 0x000000000fee8f1e0000000125f4305c
+
+ /* x^169984 mod p(x)` << 1, x^170048 mod p(x)` << 1 */
+ .octa 0x00000001da26fbae00000001471e2002
+
+ /* x^168960 mod p(x)` << 1, x^169024 mod p(x)` << 1 */
+ .octa 0x00000001b3a8bd880000000132d2253a
+
+ /* x^167936 mod p(x)` << 1, x^168000 mod p(x)` << 1 */
+ .octa 0x00000000e8f3898e00000000f26b3592
+
+ /* x^166912 mod p(x)` << 1, x^166976 mod p(x)` << 1 */
+ .octa 0x00000000b0d0d28c00000000bc8b67b0
+
+ /* x^165888 mod p(x)` << 1, x^165952 mod p(x)` << 1 */
+ .octa 0x0000000030f2a798000000013a826ef2
+
+ /* x^164864 mod p(x)` << 1, x^164928 mod p(x)` << 1 */
+ .octa 0x000000000fba10020000000081482c84
+
+ /* x^163840 mod p(x)` << 1, x^163904 mod p(x)` << 1 */
+ .octa 0x00000000bdb9bd7200000000e77307c2
+
+ /* x^162816 mod p(x)` << 1, x^162880 mod p(x)` << 1 */
+ .octa 0x0000000075d3bf5a00000000d4a07ec8
+
+ /* x^161792 mod p(x)` << 1, x^161856 mod p(x)` << 1 */
+ .octa 0x00000000ef1f98a00000000017102100
+
+ /* x^160768 mod p(x)` << 1, x^160832 mod p(x)` << 1 */
+ .octa 0x00000000689c760200000000db406486
+
+ /* x^159744 mod p(x)` << 1, x^159808 mod p(x)` << 1 */
+ .octa 0x000000016d5fa5fe0000000192db7f88
+
+ /* x^158720 mod p(x)` << 1, x^158784 mod p(x)` << 1 */
+ .octa 0x00000001d0d2b9ca000000018bf67b1e
+
+ /* x^157696 mod p(x)` << 1, x^157760 mod p(x)` << 1 */
+ .octa 0x0000000041e7b470000000007c09163e
+
+ /* x^156672 mod p(x)` << 1, x^156736 mod p(x)` << 1 */
+ .octa 0x00000001cbb6495e000000000adac060
+
+ /* x^155648 mod p(x)` << 1, x^155712 mod p(x)` << 1 */
+ .octa 0x000000010052a0b000000000bd8316ae
+
+ /* x^154624 mod p(x)` << 1, x^154688 mod p(x)` << 1 */
+ .octa 0x00000001d8effb5c000000019f09ab54
+
+ /* x^153600 mod p(x)` << 1, x^153664 mod p(x)` << 1 */
+ .octa 0x00000001d969853c0000000125155542
+
+ /* x^152576 mod p(x)` << 1, x^152640 mod p(x)` << 1 */
+ .octa 0x00000000523ccce2000000018fdb5882
+
+ /* x^151552 mod p(x)` << 1, x^151616 mod p(x)` << 1 */
+ .octa 0x000000001e2436bc00000000e794b3f4
+
+ /* x^150528 mod p(x)` << 1, x^150592 mod p(x)` << 1 */
+ .octa 0x00000000ddd1c3a2000000016f9bb022
+
+ /* x^149504 mod p(x)` << 1, x^149568 mod p(x)` << 1 */
+ .octa 0x0000000019fcfe3800000000290c9978
+
+ /* x^148480 mod p(x)` << 1, x^148544 mod p(x)` << 1 */
+ .octa 0x00000001ce95db640000000083c0f350
+
+ /* x^147456 mod p(x)` << 1, x^147520 mod p(x)` << 1 */
+ .octa 0x00000000af5828060000000173ea6628
+
+ /* x^146432 mod p(x)` << 1, x^146496 mod p(x)` << 1 */
+ .octa 0x00000001006388f600000001c8b4e00a
+
+ /* x^145408 mod p(x)` << 1, x^145472 mod p(x)` << 1 */
+ .octa 0x0000000179eca00a00000000de95d6aa
+
+ /* x^144384 mod p(x)` << 1, x^144448 mod p(x)` << 1 */
+ .octa 0x0000000122410a6a000000010b7f7248
+
+ /* x^143360 mod p(x)` << 1, x^143424 mod p(x)` << 1 */
+ .octa 0x000000004288e87c00000001326e3a06
+
+ /* x^142336 mod p(x)` << 1, x^142400 mod p(x)` << 1 */
+ .octa 0x000000016c5490da00000000bb62c2e6
+
+ /* x^141312 mod p(x)` << 1, x^141376 mod p(x)` << 1 */
+ .octa 0x00000000d1c71f6e0000000156a4b2c2
+
+ /* x^140288 mod p(x)` << 1, x^140352 mod p(x)` << 1 */
+ .octa 0x00000001b4ce08a6000000011dfe763a
+
+ /* x^139264 mod p(x)` << 1, x^139328 mod p(x)` << 1 */
+ .octa 0x00000001466ba60c000000007bcca8e2
+
+ /* x^138240 mod p(x)` << 1, x^138304 mod p(x)` << 1 */
+ .octa 0x00000001f6c488a40000000186118faa
+
+ /* x^137216 mod p(x)` << 1, x^137280 mod p(x)` << 1 */
+ .octa 0x000000013bfb06820000000111a65a88
+
+ /* x^136192 mod p(x)` << 1, x^136256 mod p(x)` << 1 */
+ .octa 0x00000000690e9e54000000003565e1c4
+
+ /* x^135168 mod p(x)` << 1, x^135232 mod p(x)` << 1 */
+ .octa 0x00000000281346b6000000012ed02a82
+
+ /* x^134144 mod p(x)` << 1, x^134208 mod p(x)` << 1 */
+ .octa 0x000000015646402400000000c486ecfc
+
+ /* x^133120 mod p(x)` << 1, x^133184 mod p(x)` << 1 */
+ .octa 0x000000016063a8dc0000000001b951b2
+
+ /* x^132096 mod p(x)` << 1, x^132160 mod p(x)` << 1 */
+ .octa 0x0000000116a663620000000048143916
+
+ /* x^131072 mod p(x)` << 1, x^131136 mod p(x)` << 1 */
+ .octa 0x000000017e8aa4d200000001dc2ae124
+
+ /* x^130048 mod p(x)` << 1, x^130112 mod p(x)` << 1 */
+ .octa 0x00000001728eb10c00000001416c58d6
+
+ /* x^129024 mod p(x)` << 1, x^129088 mod p(x)` << 1 */
+ .octa 0x00000001b08fd7fa00000000a479744a
+
+ /* x^128000 mod p(x)` << 1, x^128064 mod p(x)` << 1 */
+ .octa 0x00000001092a16e80000000096ca3a26
+
+ /* x^126976 mod p(x)` << 1, x^127040 mod p(x)` << 1 */
+ .octa 0x00000000a505637c00000000ff223d4e
+
+ /* x^125952 mod p(x)` << 1, x^126016 mod p(x)` << 1 */
+ .octa 0x00000000d94869b2000000010e84da42
+
+ /* x^124928 mod p(x)` << 1, x^124992 mod p(x)` << 1 */
+ .octa 0x00000001c8b203ae00000001b61ba3d0
+
+ /* x^123904 mod p(x)` << 1, x^123968 mod p(x)` << 1 */
+ .octa 0x000000005704aea000000000680f2de8
+
+ /* x^122880 mod p(x)` << 1, x^122944 mod p(x)` << 1 */
+ .octa 0x000000012e295fa2000000008772a9a8
+
+ /* x^121856 mod p(x)` << 1, x^121920 mod p(x)` << 1 */
+ .octa 0x000000011d0908bc0000000155f295bc
+
+ /* x^120832 mod p(x)` << 1, x^120896 mod p(x)` << 1 */
+ .octa 0x0000000193ed97ea00000000595f9282
+
+ /* x^119808 mod p(x)` << 1, x^119872 mod p(x)` << 1 */
+ .octa 0x000000013a0f1c520000000164b1c25a
+
+ /* x^118784 mod p(x)` << 1, x^118848 mod p(x)` << 1 */
+ .octa 0x000000010c2c40c000000000fbd67c50
+
+ /* x^117760 mod p(x)` << 1, x^117824 mod p(x)` << 1 */
+ .octa 0x00000000ff6fac3e0000000096076268
+
+ /* x^116736 mod p(x)` << 1, x^116800 mod p(x)` << 1 */
+ .octa 0x000000017b3609c000000001d288e4cc
+
+ /* x^115712 mod p(x)` << 1, x^115776 mod p(x)` << 1 */
+ .octa 0x0000000088c8c92200000001eaac1bdc
+
+ /* x^114688 mod p(x)` << 1, x^114752 mod p(x)` << 1 */
+ .octa 0x00000001751baae600000001f1ea39e2
+
+ /* x^113664 mod p(x)` << 1, x^113728 mod p(x)` << 1 */
+ .octa 0x000000010795297200000001eb6506fc
+
+ /* x^112640 mod p(x)` << 1, x^112704 mod p(x)` << 1 */
+ .octa 0x0000000162b00abe000000010f806ffe
+
+ /* x^111616 mod p(x)` << 1, x^111680 mod p(x)` << 1 */
+ .octa 0x000000000d7b404c000000010408481e
+
+ /* x^110592 mod p(x)` << 1, x^110656 mod p(x)` << 1 */
+ .octa 0x00000000763b13d40000000188260534
+
+ /* x^109568 mod p(x)` << 1, x^109632 mod p(x)` << 1 */
+ .octa 0x00000000f6dc22d80000000058fc73e0
+
+ /* x^108544 mod p(x)` << 1, x^108608 mod p(x)` << 1 */
+ .octa 0x000000007daae06000000000391c59b8
+
+ /* x^107520 mod p(x)` << 1, x^107584 mod p(x)` << 1 */
+ .octa 0x000000013359ab7c000000018b638400
+
+ /* x^106496 mod p(x)` << 1, x^106560 mod p(x)` << 1 */
+ .octa 0x000000008add438a000000011738f5c4
+
+ /* x^105472 mod p(x)` << 1, x^105536 mod p(x)` << 1 */
+ .octa 0x00000001edbefdea000000008cf7c6da
+
+ /* x^104448 mod p(x)` << 1, x^104512 mod p(x)` << 1 */
+ .octa 0x000000004104e0f800000001ef97fb16
+
+ /* x^103424 mod p(x)` << 1, x^103488 mod p(x)` << 1 */
+ .octa 0x00000000b48a82220000000102130e20
+
+ /* x^102400 mod p(x)` << 1, x^102464 mod p(x)` << 1 */
+ .octa 0x00000001bcb4684400000000db968898
+
+ /* x^101376 mod p(x)` << 1, x^101440 mod p(x)` << 1 */
+ .octa 0x000000013293ce0a00000000b5047b5e
+
+ /* x^100352 mod p(x)` << 1, x^100416 mod p(x)` << 1 */
+ .octa 0x00000001710d0844000000010b90fdb2
+
+ /* x^99328 mod p(x)` << 1, x^99392 mod p(x)` << 1 */
+ .octa 0x0000000117907f6e000000004834a32e
+
+ /* x^98304 mod p(x)` << 1, x^98368 mod p(x)` << 1 */
+ .octa 0x0000000087ddf93e0000000059c8f2b0
+
+ /* x^97280 mod p(x)` << 1, x^97344 mod p(x)` << 1 */
+ .octa 0x000000005970e9b00000000122cec508
+
+ /* x^96256 mod p(x)` << 1, x^96320 mod p(x)` << 1 */
+ .octa 0x0000000185b2b7d0000000000a330cda
+
+ /* x^95232 mod p(x)` << 1, x^95296 mod p(x)` << 1 */
+ .octa 0x00000001dcee0efc000000014a47148c
+
+ /* x^94208 mod p(x)` << 1, x^94272 mod p(x)` << 1 */
+ .octa 0x0000000030da27220000000042c61cb8
+
+ /* x^93184 mod p(x)` << 1, x^93248 mod p(x)` << 1 */
+ .octa 0x000000012f925a180000000012fe6960
+
+ /* x^92160 mod p(x)` << 1, x^92224 mod p(x)` << 1 */
+ .octa 0x00000000dd2e357c00000000dbda2c20
+
+ /* x^91136 mod p(x)` << 1, x^91200 mod p(x)` << 1 */
+ .octa 0x00000000071c80de000000011122410c
+
+ /* x^90112 mod p(x)` << 1, x^90176 mod p(x)` << 1 */
+ .octa 0x000000011513140a00000000977b2070
+
+ /* x^89088 mod p(x)` << 1, x^89152 mod p(x)` << 1 */
+ .octa 0x00000001df876e8e000000014050438e
+
+ /* x^88064 mod p(x)` << 1, x^88128 mod p(x)` << 1 */
+ .octa 0x000000015f81d6ce0000000147c840e8
+
+ /* x^87040 mod p(x)` << 1, x^87104 mod p(x)` << 1 */
+ .octa 0x000000019dd94dbe00000001cc7c88ce
+
+ /* x^86016 mod p(x)` << 1, x^86080 mod p(x)` << 1 */
+ .octa 0x00000001373d206e00000001476b35a4
+
+ /* x^84992 mod p(x)` << 1, x^85056 mod p(x)` << 1 */
+ .octa 0x00000000668ccade000000013d52d508
+
+ /* x^83968 mod p(x)` << 1, x^84032 mod p(x)` << 1 */
+ .octa 0x00000001b192d268000000008e4be32e
+
+ /* x^82944 mod p(x)` << 1, x^83008 mod p(x)` << 1 */
+ .octa 0x00000000e30f3a7800000000024120fe
+
+ /* x^81920 mod p(x)` << 1, x^81984 mod p(x)` << 1 */
+ .octa 0x000000010ef1f7bc00000000ddecddb4
+
+ /* x^80896 mod p(x)` << 1, x^80960 mod p(x)` << 1 */
+ .octa 0x00000001f5ac738000000000d4d403bc
+
+ /* x^79872 mod p(x)` << 1, x^79936 mod p(x)` << 1 */
+ .octa 0x000000011822ea7000000001734b89aa
+
+ /* x^78848 mod p(x)` << 1, x^78912 mod p(x)` << 1 */
+ .octa 0x00000000c3a33848000000010e7a58d6
+
+ /* x^77824 mod p(x)` << 1, x^77888 mod p(x)` << 1 */
+ .octa 0x00000001bd151c2400000001f9f04e9c
+
+ /* x^76800 mod p(x)` << 1, x^76864 mod p(x)` << 1 */
+ .octa 0x0000000056002d7600000000b692225e
+
+ /* x^75776 mod p(x)` << 1, x^75840 mod p(x)` << 1 */
+ .octa 0x000000014657c4f4000000019b8d3f3e
+
+ /* x^74752 mod p(x)` << 1, x^74816 mod p(x)` << 1 */
+ .octa 0x0000000113742d7c00000001a874f11e
+
+ /* x^73728 mod p(x)` << 1, x^73792 mod p(x)` << 1 */
+ .octa 0x000000019c5920ba000000010d5a4254
+
+ /* x^72704 mod p(x)` << 1, x^72768 mod p(x)` << 1 */
+ .octa 0x000000005216d2d600000000bbb2f5d6
+
+ /* x^71680 mod p(x)` << 1, x^71744 mod p(x)` << 1 */
+ .octa 0x0000000136f5ad8a0000000179cc0e36
+
+ /* x^70656 mod p(x)` << 1, x^70720 mod p(x)` << 1 */
+ .octa 0x000000018b07beb600000001dca1da4a
+
+ /* x^69632 mod p(x)` << 1, x^69696 mod p(x)` << 1 */
+ .octa 0x00000000db1e93b000000000feb1a192
+
+ /* x^68608 mod p(x)` << 1, x^68672 mod p(x)` << 1 */
+ .octa 0x000000000b96fa3a00000000d1eeedd6
+
+ /* x^67584 mod p(x)` << 1, x^67648 mod p(x)` << 1 */
+ .octa 0x00000001d9968af0000000008fad9bb4
+
+ /* x^66560 mod p(x)` << 1, x^66624 mod p(x)` << 1 */
+ .octa 0x000000000e4a77a200000001884938e4
+
+ /* x^65536 mod p(x)` << 1, x^65600 mod p(x)` << 1 */
+ .octa 0x00000000508c2ac800000001bc2e9bc0
+
+ /* x^64512 mod p(x)` << 1, x^64576 mod p(x)` << 1 */
+ .octa 0x0000000021572a8000000001f9658a68
+
+ /* x^63488 mod p(x)` << 1, x^63552 mod p(x)` << 1 */
+ .octa 0x00000001b859daf2000000001b9224fc
+
+ /* x^62464 mod p(x)` << 1, x^62528 mod p(x)` << 1 */
+ .octa 0x000000016f7884740000000055b2fb84
+
+ /* x^61440 mod p(x)` << 1, x^61504 mod p(x)` << 1 */
+ .octa 0x00000001b438810e000000018b090348
+
+ /* x^60416 mod p(x)` << 1, x^60480 mod p(x)` << 1 */
+ .octa 0x0000000095ddc6f2000000011ccbd5ea
+
+ /* x^59392 mod p(x)` << 1, x^59456 mod p(x)` << 1 */
+ .octa 0x00000001d977c20c0000000007ae47f8
+
+ /* x^58368 mod p(x)` << 1, x^58432 mod p(x)` << 1 */
+ .octa 0x00000000ebedb99a0000000172acbec0
+
+ /* x^57344 mod p(x)` << 1, x^57408 mod p(x)` << 1 */
+ .octa 0x00000001df9e9e9200000001c6e3ff20
+
+ /* x^56320 mod p(x)` << 1, x^56384 mod p(x)` << 1 */
+ .octa 0x00000001a4a3f95200000000e1b38744
+
+ /* x^55296 mod p(x)` << 1, x^55360 mod p(x)` << 1 */
+ .octa 0x00000000e2f5122000000000791585b2
+
+ /* x^54272 mod p(x)` << 1, x^54336 mod p(x)` << 1 */
+ .octa 0x000000004aa01f3e00000000ac53b894
+
+ /* x^53248 mod p(x)` << 1, x^53312 mod p(x)` << 1 */
+ .octa 0x00000000b3e90a5800000001ed5f2cf4
+
+ /* x^52224 mod p(x)` << 1, x^52288 mod p(x)` << 1 */
+ .octa 0x000000000c9ca2aa00000001df48b2e0
+
+ /* x^51200 mod p(x)` << 1, x^51264 mod p(x)` << 1 */
+ .octa 0x000000015168231600000000049c1c62
+
+ /* x^50176 mod p(x)` << 1, x^50240 mod p(x)` << 1 */
+ .octa 0x0000000036fce78c000000017c460c12
+
+ /* x^49152 mod p(x)` << 1, x^49216 mod p(x)` << 1 */
+ .octa 0x000000009037dc10000000015be4da7e
+
+ /* x^48128 mod p(x)` << 1, x^48192 mod p(x)` << 1 */
+ .octa 0x00000000d3298582000000010f38f668
+
+ /* x^47104 mod p(x)` << 1, x^47168 mod p(x)` << 1 */
+ .octa 0x00000001b42e8ad60000000039f40a00
+
+ /* x^46080 mod p(x)` << 1, x^46144 mod p(x)` << 1 */
+ .octa 0x00000000142a983800000000bd4c10c4
+
+ /* x^45056 mod p(x)` << 1, x^45120 mod p(x)` << 1 */
+ .octa 0x0000000109c7f1900000000042db1d98
+
+ /* x^44032 mod p(x)` << 1, x^44096 mod p(x)` << 1 */
+ .octa 0x0000000056ff931000000001c905bae6
+
+ /* x^43008 mod p(x)` << 1, x^43072 mod p(x)` << 1 */
+ .octa 0x00000001594513aa00000000069d40ea
+
+ /* x^41984 mod p(x)` << 1, x^42048 mod p(x)` << 1 */
+ .octa 0x00000001e3b5b1e8000000008e4fbad0
+
+ /* x^40960 mod p(x)` << 1, x^41024 mod p(x)` << 1 */
+ .octa 0x000000011dd5fc080000000047bedd46
+
+ /* x^39936 mod p(x)` << 1, x^40000 mod p(x)` << 1 */
+ .octa 0x00000001675f0cc20000000026396bf8
+
+ /* x^38912 mod p(x)` << 1, x^38976 mod p(x)` << 1 */
+ .octa 0x00000000d1c8dd4400000000379beb92
+
+ /* x^37888 mod p(x)` << 1, x^37952 mod p(x)` << 1 */
+ .octa 0x0000000115ebd3d8000000000abae54a
+
+ /* x^36864 mod p(x)` << 1, x^36928 mod p(x)` << 1 */
+ .octa 0x00000001ecbd0dac0000000007e6a128
+
+ /* x^35840 mod p(x)` << 1, x^35904 mod p(x)` << 1 */
+ .octa 0x00000000cdf67af2000000000ade29d2
+
+ /* x^34816 mod p(x)` << 1, x^34880 mod p(x)` << 1 */
+ .octa 0x000000004c01ff4c00000000f974c45c
+
+ /* x^33792 mod p(x)` << 1, x^33856 mod p(x)` << 1 */
+ .octa 0x00000000f2d8657e00000000e77ac60a
+
+ /* x^32768 mod p(x)` << 1, x^32832 mod p(x)` << 1 */
+ .octa 0x000000006bae74c40000000145895816
+
+ /* x^31744 mod p(x)` << 1, x^31808 mod p(x)` << 1 */
+ .octa 0x0000000152af8aa00000000038e362be
+
+ /* x^30720 mod p(x)` << 1, x^30784 mod p(x)` << 1 */
+ .octa 0x0000000004663802000000007f991a64
+
+ /* x^29696 mod p(x)` << 1, x^29760 mod p(x)` << 1 */
+ .octa 0x00000001ab2f5afc00000000fa366d3a
+
+ /* x^28672 mod p(x)` << 1, x^28736 mod p(x)` << 1 */
+ .octa 0x0000000074a4ebd400000001a2bb34f0
+
+ /* x^27648 mod p(x)` << 1, x^27712 mod p(x)` << 1 */
+ .octa 0x00000001d7ab3a4c0000000028a9981e
+
+ /* x^26624 mod p(x)` << 1, x^26688 mod p(x)` << 1 */
+ .octa 0x00000001a8da60c600000001dbc672be
+
+ /* x^25600 mod p(x)` << 1, x^25664 mod p(x)` << 1 */
+ .octa 0x000000013cf6382000000000b04d77f6
+
+ /* x^24576 mod p(x)` << 1, x^24640 mod p(x)` << 1 */
+ .octa 0x00000000bec12e1e0000000124400d96
+
+ /* x^23552 mod p(x)` << 1, x^23616 mod p(x)` << 1 */
+ .octa 0x00000001c6368010000000014ca4b414
+
+ /* x^22528 mod p(x)` << 1, x^22592 mod p(x)` << 1 */
+ .octa 0x00000001e6e78758000000012fe2c938
+
+ /* x^21504 mod p(x)` << 1, x^21568 mod p(x)` << 1 */
+ .octa 0x000000008d7f2b3c00000001faed01e6
+
+ /* x^20480 mod p(x)` << 1, x^20544 mod p(x)` << 1 */
+ .octa 0x000000016b4a156e000000007e80ecfe
+
+ /* x^19456 mod p(x)` << 1, x^19520 mod p(x)` << 1 */
+ .octa 0x00000001c63cfeb60000000098daee94
+
+ /* x^18432 mod p(x)` << 1, x^18496 mod p(x)` << 1 */
+ .octa 0x000000015f902670000000010a04edea
+
+ /* x^17408 mod p(x)` << 1, x^17472 mod p(x)` << 1 */
+ .octa 0x00000001cd5de11e00000001c00b4524
+
+ /* x^16384 mod p(x)` << 1, x^16448 mod p(x)` << 1 */
+ .octa 0x000000001acaec540000000170296550
+
+ /* x^15360 mod p(x)` << 1, x^15424 mod p(x)` << 1 */
+ .octa 0x000000002bd0ca780000000181afaa48
+
+ /* x^14336 mod p(x)` << 1, x^14400 mod p(x)` << 1 */
+ .octa 0x0000000032d63d5c0000000185a31ffa
+
+ /* x^13312 mod p(x)` << 1, x^13376 mod p(x)` << 1 */
+ .octa 0x000000001c6d4e4c000000002469f608
+
+ /* x^12288 mod p(x)` << 1, x^12352 mod p(x)` << 1 */
+ .octa 0x0000000106a60b92000000006980102a
+
+ /* x^11264 mod p(x)` << 1, x^11328 mod p(x)` << 1 */
+ .octa 0x00000000d3855e120000000111ea9ca8
+
+ /* x^10240 mod p(x)` << 1, x^10304 mod p(x)` << 1 */
+ .octa 0x00000000e312563600000001bd1d29ce
+
+ /* x^9216 mod p(x)` << 1, x^9280 mod p(x)` << 1 */
+ .octa 0x000000009e8f7ea400000001b34b9580
+
+ /* x^8192 mod p(x)` << 1, x^8256 mod p(x)` << 1 */
+ .octa 0x00000001c82e562c000000003076054e
+
+ /* x^7168 mod p(x)` << 1, x^7232 mod p(x)` << 1 */
+ .octa 0x00000000ca9f09ce000000012a608ea4
+
+ /* x^6144 mod p(x)` << 1, x^6208 mod p(x)` << 1 */
+ .octa 0x00000000c63764e600000000784d05fe
+
+ /* x^5120 mod p(x)` << 1, x^5184 mod p(x)` << 1 */
+ .octa 0x0000000168d2e49e000000016ef0d82a
+
+ /* x^4096 mod p(x)` << 1, x^4160 mod p(x)` << 1 */
+ .octa 0x00000000e986c1480000000075bda454
+
+ /* x^3072 mod p(x)` << 1, x^3136 mod p(x)` << 1 */
+ .octa 0x00000000cfb65894000000003dc0a1c4
+
+ /* x^2048 mod p(x)` << 1, x^2112 mod p(x)` << 1 */
+ .octa 0x0000000111cadee400000000e9a5d8be
+
+ /* x^1024 mod p(x)` << 1, x^1088 mod p(x)` << 1 */
+ .octa 0x0000000171fb63ce00000001609bc4b4
+
+ .short_constants :
+
+ /* Reduce final 1024-2048 bits to 64 bits, shifting 32 bits to include
+ the trailing 32 bits of zeros */
+ /* x^1952 mod p(x)`, x^1984 mod p(x)`, x^2016 mod p(x)`, x^2048 mod
+ p(x)` */
+ .octa 0x7fec2963e5bf80485cf015c388e56f72
+
+ /* x^1824 mod p(x)`, x^1856 mod p(x)`, x^1888 mod p(x)`, x^1920 mod
+ p(x)` */
+ .octa 0x38e888d4844752a9963a18920246e2e6
+
+ /* x^1696 mod p(x)`, x^1728 mod p(x)`, x^1760 mod p(x)`, x^1792 mod
+ p(x)` */
+ .octa 0x42316c00730206ad419a441956993a31
+
+ /* x^1568 mod p(x)`, x^1600 mod p(x)`, x^1632 mod p(x)`, x^1664 mod
+ p(x)` */
+ .octa 0x543d5c543e65ddf9924752ba2b830011
+
+ /* x^1440 mod p(x)`, x^1472 mod p(x)`, x^1504 mod p(x)`, x^1536 mod
+ p(x)` */
+ .octa 0x78e87aaf56767c9255bd7f9518e4a304
+
+ /* x^1312 mod p(x)`, x^1344 mod p(x)`, x^1376 mod p(x)`, x^1408 mod
+ p(x)` */
+ .octa 0x8f68fcec1903da7f6d76739fe0553f1e
+
+ /* x^1184 mod p(x)`, x^1216 mod p(x)`, x^1248 mod p(x)`, x^1280 mod
+ p(x)` */
+ .octa 0x3f4840246791d588c133722b1fe0b5c3
+
+ /* x^1056 mod p(x)`, x^1088 mod p(x)`, x^1120 mod p(x)`, x^1152 mod
+ p(x)` */
+ .octa 0x34c96751b04de25a64b67ee0e55ef1f3
+
+ /* x^928 mod p(x)`, x^960 mod p(x)`, x^992 mod p(x)`, x^1024 mod p(x)`
+ */
+ .octa 0x156c8e180b4a395b069db049b8fdb1e7
+
+ /* x^800 mod p(x)`, x^832 mod p(x)`, x^864 mod p(x)`, x^896 mod p(x)` */
+ .octa 0xe0b99ccbe661f7bea11bfaf3c9e90b9e
+
+ /* x^672 mod p(x)`, x^704 mod p(x)`, x^736 mod p(x)`, x^768 mod p(x)` */
+ .octa 0x041d37768cd75659817cdc5119b29a35
+
+ /* x^544 mod p(x)`, x^576 mod p(x)`, x^608 mod p(x)`, x^640 mod p(x)` */
+ .octa 0x3a0777818cfaa9651ce9d94b36c41f1c
+
+ /* x^416 mod p(x)`, x^448 mod p(x)`, x^480 mod p(x)`, x^512 mod p(x)` */
+ .octa 0x0e148e8252377a554f256efcb82be955
+
+ /* x^288 mod p(x)`, x^320 mod p(x)`, x^352 mod p(x)`, x^384 mod p(x)` */
+ .octa 0x9c25531d19e65ddeec1631edb2dea967
+
+ /* x^160 mod p(x)`, x^192 mod p(x)`, x^224 mod p(x)`, x^256 mod p(x)` */
+ .octa 0x790606ff9957c0a65d27e147510ac59a
+
+ /* x^32 mod p(x)`, x^64 mod p(x)`, x^96 mod p(x)`, x^128 mod p(x)` */
+ .octa 0x82f63b786ea2d55ca66805eb18b8ea18
+
+ .barrett_constants :
+ /* 33 bit reflected Barrett constant m - (4^32)/n */
+ .octa 0x000000000000000000000000dea713f1 /* x^64 div p(x)` */
+ /* 33 bit reflected Barrett constant n */
+ .octa 0x00000000000000000000000105ec76f1
+#endif
diff --git a/storage/rocksdb/rocksdb/util/crc32c_test.cc b/storage/rocksdb/rocksdb/util/crc32c_test.cc
new file mode 100644
index 00000000..3e4f7396
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/crc32c_test.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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"
+
+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<char>(i);
+ }
+ ASSERT_EQ(0x46dd794eU, Value(buf, sizeof(buf)));
+
+ for (int i = 0; i < 32; i++) {
+ buf[i] = static_cast<char>(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<char*>(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)))));
+}
+
+} // 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<const signed char*>(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) {
+ ::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<char*>(dst),
+ fnv64_buf((const char*)src, sizeof(uint64_t)));
+ dst++;
+ src += sizeof(uint64_t);
+ }
+
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/defer.h b/storage/rocksdb/rocksdb/util/defer.h
new file mode 100644
index 00000000..cb0b42a3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/defer.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+
+namespace ROCKSDB_NAMESPACE {
+
+// Defers the execution of the provided function until the Defer
+// object goes out of scope.
+//
+// Usage example:
+//
+// Status DeferTest() {
+// Status s;
+// Defer defer([&s]() {
+// if (!s.ok()) {
+// // do cleanups ...
+// }
+// });
+// // do something ...
+// if (!s.ok()) return;
+// // do some other things ...
+// return s;
+// }
+//
+// The above code ensures that cleanups will always happen on returning.
+//
+// Without the help of Defer, you can
+// 1. every time when !s.ok(), do the cleanup;
+// 2. instead of returning when !s.ok(), continue the work only when s.ok(),
+// but sometimes, this might lead to nested blocks of "if (s.ok()) {...}".
+//
+// With the help of Defer, you can centralize the cleanup logic inside the
+// lambda passed to Defer, and you can return immediately on failure when necessary.
+class Defer final {
+ public:
+ Defer(std::function<void()>&& fn) : fn_(std::move(fn)) {}
+ ~Defer() { fn_(); }
+
+ // Disallow copy.
+ Defer(const Defer&) = delete;
+ Defer& operator=(const Defer&) = delete;
+
+ private:
+ std::function<void()> fn_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/defer_test.cc b/storage/rocksdb/rocksdb/util/defer_test.cc
new file mode 100644
index 00000000..e13b25ef
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/defer_test.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/port.h"
+#include "port/stack_trace.h"
+#include "test_util/testharness.h"
+#include "util/defer.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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/duplicate_detector.h b/storage/rocksdb/rocksdb/util/duplicate_detector.h
new file mode 100644
index 00000000..72920ca3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/duplicate_detector.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cinttypes>
+
+#include "util/set_comparator.h"
+
+namespace ROCKSDB_NAMESPACE {
+// During recovery if the memtable is flushed we cannot rely on its help on
+// duplicate key detection and as key insert will not be attempted. This class
+// will be used as a emulator of memtable to tell if insertion of a key/seq
+// would have resulted in duplication.
+class DuplicateDetector {
+ public:
+ explicit DuplicateDetector(DBImpl* db) : db_(db) {}
+ bool IsDuplicateKeySeq(uint32_t cf, const Slice& key, SequenceNumber seq) {
+ assert(seq >= batch_seq_);
+ if (batch_seq_ != seq) { // it is a new batch
+ keys_.clear();
+ }
+ batch_seq_ = seq;
+ CFKeys& cf_keys = keys_[cf];
+ if (cf_keys.size() == 0) { // just inserted
+ InitWithComp(cf);
+ }
+ auto it = cf_keys.insert(key);
+ if (it.second == false) { // second is false if a element already existed.
+ keys_.clear();
+ InitWithComp(cf);
+ keys_[cf].insert(key);
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ SequenceNumber batch_seq_ = 0;
+ DBImpl* db_;
+ using CFKeys = std::set<Slice, SetComparator>;
+ std::map<uint32_t, CFKeys> keys_;
+ void InitWithComp(const uint32_t cf) {
+ auto h = db_->GetColumnFamilyHandle(cf);
+ if (!h) {
+ // TODO(myabandeh): This is not a concern in MyRocks as drop cf is not
+ // implemented yet. When it does, we should return proper error instead
+ // of throwing exception.
+ ROCKS_LOG_FATAL(
+ db_->immutable_db_options().info_log,
+ "Recovering an entry from the dropped column family %" PRIu32
+ ". WAL must must have been emptied before dropping the column "
+ "family", cf);
+#ifndef ROCKSDB_LITE
+ throw std::runtime_error(
+ "Recovering an entry from a dropped column family. "
+ "WAL must must have been flushed before dropping the column "
+ "family");
+#endif
+ return;
+ }
+ auto cmp = h->GetComparator();
+ keys_[cf] = CFKeys(SetComparator(cmp));
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/dynamic_bloom.cc b/storage/rocksdb/rocksdb/util/dynamic_bloom.cc
new file mode 100644
index 00000000..60d4b2cf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/dynamic_bloom.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "dynamic_bloom.h"
+
+#include <algorithm>
+
+#include "memory/allocator.h"
+#include "port/port.h"
+#include "rocksdb/slice.h"
+#include "util/hash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+uint32_t roundUpToPow2(uint32_t x) {
+ uint32_t rv = 1;
+ while (rv < x) {
+ rv <<= 1;
+ }
+ return rv;
+}
+}
+
+DynamicBloom::DynamicBloom(Allocator* allocator, uint32_t total_bits,
+ uint32_t num_probes, size_t huge_page_tlb_size,
+ Logger* logger)
+ // Round down, except round up with 1
+ : kNumDoubleProbes((num_probes + (num_probes == 1)) / 2) {
+ assert(num_probes % 2 == 0); // limitation of current implementation
+ assert(num_probes <= 10); // limitation of current implementation
+ assert(kNumDoubleProbes > 0);
+
+ // Determine how much to round off + align by so that x ^ i (that's xor) is
+ // a valid u64 index if x is a valid u64 index and 0 <= i < kNumDoubleProbes.
+ uint32_t block_bytes = /*bytes/u64*/ 8 *
+ /*u64s*/ std::max(1U, roundUpToPow2(kNumDoubleProbes));
+ uint32_t block_bits = block_bytes * 8;
+ uint32_t blocks = (total_bits + block_bits - 1) / block_bits;
+ uint32_t sz = blocks * block_bytes;
+ kLen = sz / /*bytes/u64*/ 8;
+ assert(kLen > 0);
+#ifndef NDEBUG
+ for (uint32_t i = 0; i < kNumDoubleProbes; ++i) {
+ // Ensure probes starting at last word are in range
+ assert(((kLen - 1) ^ i) < kLen);
+ }
+#endif
+
+ // Padding to correct for allocation not originally aligned on block_bytes
+ // boundary
+ sz += block_bytes - 1;
+ assert(allocator);
+
+ char* raw = allocator->AllocateAligned(sz, huge_page_tlb_size, logger);
+ memset(raw, 0, sz);
+ auto block_offset = reinterpret_cast<uintptr_t>(raw) % block_bytes;
+ if (block_offset > 0) {
+ // Align on block_bytes boundary
+ raw += block_bytes - block_offset;
+ }
+ static_assert(sizeof(std::atomic<uint64_t>) == sizeof(uint64_t),
+ "Expecting zero-space-overhead atomic");
+ data_ = reinterpret_cast<std::atomic<uint64_t>*>(raw);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/dynamic_bloom.h b/storage/rocksdb/rocksdb/util/dynamic_bloom.h
new file mode 100644
index 00000000..d1f22cc7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/dynamic_bloom.h
@@ -0,0 +1,214 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <array>
+#include <string>
+#include "port/port.h"
+#include "rocksdb/slice.h"
+#include "table/multiget_context.h"
+#include "util/hash.h"
+
+#include <atomic>
+#include <memory>
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+class Allocator;
+class Logger;
+
+// A Bloom filter intended only to be used in memory, never serialized in a way
+// that could lead to schema incompatibility. Supports opt-in lock-free
+// concurrent access.
+//
+// This implementation is also intended for applications generally preferring
+// speed vs. maximum accuracy: roughly 0.9x BF op latency for 1.1x FP rate.
+// For 1% FP rate, that means that the latency of a look-up triggered by an FP
+// should be less than roughly 100x the cost of a Bloom filter op.
+//
+// For simplicity and performance, the current implementation requires
+// num_probes to be a multiple of two and <= 10.
+//
+class DynamicBloom {
+ public:
+ // allocator: pass allocator to bloom filter, hence trace the usage of memory
+ // total_bits: fixed total bits for the bloom
+ // num_probes: number of hash probes for a single key
+ // hash_func: customized hash function
+ // huge_page_tlb_size: if >0, try to allocate bloom bytes from huge page TLB
+ // within this page size. Need to reserve huge pages for
+ // it to be allocated, like:
+ // sysctl -w vm.nr_hugepages=20
+ // See linux doc Documentation/vm/hugetlbpage.txt
+ explicit DynamicBloom(Allocator* allocator, uint32_t total_bits,
+ uint32_t num_probes = 6, size_t huge_page_tlb_size = 0,
+ Logger* logger = nullptr);
+
+ ~DynamicBloom() {}
+
+ // Assuming single threaded access to this function.
+ void Add(const Slice& key);
+
+ // Like Add, but may be called concurrent with other functions.
+ void AddConcurrently(const Slice& key);
+
+ // Assuming single threaded access to this function.
+ void AddHash(uint32_t hash);
+
+ // Like AddHash, but may be called concurrent with other functions.
+ void AddHashConcurrently(uint32_t hash);
+
+ // Multithreaded access to this function is OK
+ bool MayContain(const Slice& key) const;
+
+ void MayContain(int num_keys, Slice** keys, bool* may_match) const;
+
+ // Multithreaded access to this function is OK
+ bool MayContainHash(uint32_t hash) const;
+
+ void Prefetch(uint32_t h);
+
+ private:
+ // Length of the structure, in 64-bit words. For this structure, "word"
+ // will always refer to 64-bit words.
+ uint32_t kLen;
+ // We make the k probes in pairs, two for each 64-bit read/write. Thus,
+ // this stores k/2, the number of words to double-probe.
+ const uint32_t kNumDoubleProbes;
+
+ std::atomic<uint64_t>* data_;
+
+ // or_func(ptr, mask) should effect *ptr |= mask with the appropriate
+ // concurrency safety, working with bytes.
+ template <typename OrFunc>
+ void AddHash(uint32_t hash, const OrFunc& or_func);
+
+ bool DoubleProbe(uint32_t h32, size_t a) const;
+};
+
+inline void DynamicBloom::Add(const Slice& key) { AddHash(BloomHash(key)); }
+
+inline void DynamicBloom::AddConcurrently(const Slice& key) {
+ AddHashConcurrently(BloomHash(key));
+}
+
+inline void DynamicBloom::AddHash(uint32_t hash) {
+ AddHash(hash, [](std::atomic<uint64_t>* ptr, uint64_t mask) {
+ ptr->store(ptr->load(std::memory_order_relaxed) | mask,
+ std::memory_order_relaxed);
+ });
+}
+
+inline void DynamicBloom::AddHashConcurrently(uint32_t hash) {
+ AddHash(hash, [](std::atomic<uint64_t>* ptr, uint64_t mask) {
+ // Happens-before between AddHash and MaybeContains is handled by
+ // access to versions_->LastSequence(), so all we have to do here is
+ // avoid races (so we don't give the compiler a license to mess up
+ // our code) and not lose bits. std::memory_order_relaxed is enough
+ // for that.
+ if ((mask & ptr->load(std::memory_order_relaxed)) != mask) {
+ ptr->fetch_or(mask, std::memory_order_relaxed);
+ }
+ });
+}
+
+inline bool DynamicBloom::MayContain(const Slice& key) const {
+ return (MayContainHash(BloomHash(key)));
+}
+
+inline void DynamicBloom::MayContain(int num_keys, Slice** keys,
+ bool* may_match) const {
+ std::array<uint32_t, MultiGetContext::MAX_BATCH_SIZE> hashes;
+ std::array<size_t, MultiGetContext::MAX_BATCH_SIZE> byte_offsets;
+ for (int i = 0; i < num_keys; ++i) {
+ hashes[i] = BloomHash(*keys[i]);
+ size_t a = fastrange32(kLen, hashes[i]);
+ PREFETCH(data_ + a, 0, 3);
+ byte_offsets[i] = a;
+ }
+
+ for (int i = 0; i < num_keys; i++) {
+ may_match[i] = DoubleProbe(hashes[i], byte_offsets[i]);
+ }
+}
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+// local variable is initialized but not referenced
+#pragma warning(disable : 4189)
+#endif
+inline void DynamicBloom::Prefetch(uint32_t h32) {
+ size_t a = fastrange32(kLen, h32);
+ PREFETCH(data_ + a, 0, 3);
+}
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+// Speed hacks in this implementation:
+// * Uses fastrange instead of %
+// * Minimum logic to determine first (and all) probed memory addresses.
+// (Uses constant bit-xor offsets from the starting probe address.)
+// * (Major) Two probes per 64-bit memory fetch/write.
+// Code simplification / optimization: only allow even number of probes.
+// * Very fast and effective (murmur-like) hash expansion/re-mixing. (At
+// least on recent CPUs, integer multiplication is very cheap. Each 64-bit
+// remix provides five pairs of bit addresses within a uint64_t.)
+// Code simplification / optimization: only allow up to 10 probes, from a
+// single 64-bit remix.
+//
+// The FP rate penalty for this implementation, vs. standard Bloom filter, is
+// roughly 1.12x on top of the 1.15x penalty for a 512-bit cache-local Bloom.
+// This implementation does not explicitly use the cache line size, but is
+// effectively cache-local (up to 16 probes) because of the bit-xor offsetting.
+//
+// NB: could easily be upgraded to support a 64-bit hash and
+// total_bits > 2^32 (512MB). (The latter is a bad idea without the former,
+// because of false positives.)
+
+inline bool DynamicBloom::MayContainHash(uint32_t h32) const {
+ size_t a = fastrange32(kLen, h32);
+ PREFETCH(data_ + a, 0, 3);
+ return DoubleProbe(h32, a);
+}
+
+inline bool DynamicBloom::DoubleProbe(uint32_t h32, size_t byte_offset) const {
+ // Expand/remix with 64-bit golden ratio
+ uint64_t h = 0x9e3779b97f4a7c13ULL * h32;
+ for (unsigned i = 0;; ++i) {
+ // Two bit probes per uint64_t probe
+ uint64_t mask =
+ ((uint64_t)1 << (h & 63)) | ((uint64_t)1 << ((h >> 6) & 63));
+ uint64_t val = data_[byte_offset ^ i].load(std::memory_order_relaxed);
+ if (i + 1 >= kNumDoubleProbes) {
+ return (val & mask) == mask;
+ } else if ((val & mask) != mask) {
+ return false;
+ }
+ h = (h >> 12) | (h << 52);
+ }
+}
+
+template <typename OrFunc>
+inline void DynamicBloom::AddHash(uint32_t h32, const OrFunc& or_func) {
+ size_t a = fastrange32(kLen, h32);
+ PREFETCH(data_ + a, 0, 3);
+ // Expand/remix with 64-bit golden ratio
+ uint64_t h = 0x9e3779b97f4a7c13ULL * h32;
+ for (unsigned i = 0;; ++i) {
+ // Two bit probes per uint64_t probe
+ uint64_t mask =
+ ((uint64_t)1 << (h & 63)) | ((uint64_t)1 << ((h >> 6) & 63));
+ or_func(&data_[a ^ i], mask);
+ if (i + 1 >= kNumDoubleProbes) {
+ return;
+ }
+ h = (h >> 12) | (h << 52);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/dynamic_bloom_test.cc b/storage/rocksdb/rocksdb/util/dynamic_bloom_test.cc
new file mode 100644
index 00000000..47d04a36
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/dynamic_bloom_test.cc
@@ -0,0 +1,324 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run this test... Skipping...\n");
+ return 0;
+}
+#else
+
+#include <algorithm>
+#include <atomic>
+#include <cinttypes>
+#include <functional>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include "dynamic_bloom.h"
+#include "logging/logging.h"
+#include "memory/arena.h"
+#include "port/port.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<char *>(&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<char *>(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<uint32_t>(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(Env::Default());
+ uint32_t num_probes = static_cast<uint32_t>(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<double>(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<double>(elapsed) / count);
+ }
+}
+
+TEST_F(DynamicBloomTest, concurrent_with_perf) {
+ uint32_t num_probes = static_cast<uint32_t>(FLAGS_num_probes);
+
+ uint32_t m_limit = FLAGS_enable_perf ? 8 : 1;
+
+ uint32_t num_threads = 4;
+ std::vector<port::Thread> 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<uint64_t> elapsed(0);
+
+ std::function<void(size_t)> adder([&](size_t t) {
+ KeyMaker km;
+ StopWatchNano timer(Env::Default());
+ 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<double>(elapsed) / num_threads / num_keys);
+
+ elapsed = 0;
+ std::function<void(size_t)> hitter([&](size_t t) {
+ KeyMaker km;
+ StopWatchNano timer(Env::Default());
+ 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<double>(elapsed) / num_threads / num_keys);
+
+ elapsed = 0;
+ std::atomic<uint32_t> false_positives(0);
+ std::function<void(size_t)> misser([&](size_t t) {
+ KeyMaker km;
+ StopWatchNano timer(Env::Default());
+ 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<double>(elapsed) / num_threads / num_keys,
+ false_positives.load() * 100.0 / num_keys);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ return RUN_ALL_TESTS();
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/util/file_checksum_helper.cc b/storage/rocksdb/rocksdb/util/file_checksum_helper.cc
new file mode 100644
index 00000000..51dcf6a8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/file_checksum_helper.cc
@@ -0,0 +1,85 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/file_checksum_helper.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void FileChecksumListImpl::reset() { checksum_map_.clear(); }
+
+size_t FileChecksumListImpl::size() const { return checksum_map_.size(); }
+
+Status FileChecksumListImpl::GetAllFileChecksums(
+ std::vector<uint64_t>* file_numbers, std::vector<std::string>* checksums,
+ std::vector<std::string>* checksum_func_names) {
+ if (file_numbers == nullptr || checksums == nullptr ||
+ checksum_func_names == nullptr) {
+ return Status::InvalidArgument("Pointer has not been initiated");
+ }
+
+ for (auto i : checksum_map_) {
+ file_numbers->push_back(i.first);
+ checksums->push_back(i.second.first);
+ checksum_func_names->push_back(i.second.second);
+ }
+ return Status::OK();
+}
+
+Status FileChecksumListImpl::SearchOneFileChecksum(
+ uint64_t file_number, std::string* checksum,
+ std::string* checksum_func_name) {
+ if (checksum == nullptr || checksum_func_name == nullptr) {
+ return Status::InvalidArgument("Pointer has not been initiated");
+ }
+
+ auto it = checksum_map_.find(file_number);
+ if (it == checksum_map_.end()) {
+ return Status::NotFound();
+ } else {
+ *checksum = it->second.first;
+ *checksum_func_name = it->second.second;
+ }
+ return Status::OK();
+}
+
+Status FileChecksumListImpl::InsertOneFileChecksum(
+ uint64_t file_number, const std::string& checksum,
+ const std::string& checksum_func_name) {
+ auto it = checksum_map_.find(file_number);
+ if (it == checksum_map_.end()) {
+ checksum_map_.insert(std::make_pair(
+ file_number, std::make_pair(checksum, checksum_func_name)));
+ } else {
+ it->second.first = checksum;
+ it->second.second = checksum_func_name;
+ }
+ return Status::OK();
+}
+
+Status FileChecksumListImpl::RemoveOneFileChecksum(uint64_t file_number) {
+ auto it = checksum_map_.find(file_number);
+ if (it == checksum_map_.end()) {
+ return Status::NotFound();
+ } else {
+ checksum_map_.erase(it);
+ }
+ return Status::OK();
+}
+
+FileChecksumList* NewFileChecksumList() {
+ FileChecksumListImpl* checksum_list = new FileChecksumListImpl();
+ return checksum_list;
+}
+
+FileChecksumFunc* CreateFileChecksumFuncCrc32c() {
+ FileChecksumFunc* file_checksum_crc32c = new FileChecksumFuncCrc32c();
+ return file_checksum_crc32c;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/file_checksum_helper.h b/storage/rocksdb/rocksdb/util/file_checksum_helper.h
new file mode 100644
index 00000000..7ad9ea73
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/file_checksum_helper.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cassert>
+#include <unordered_map>
+#include "port/port.h"
+#include "rocksdb/file_checksum.h"
+#include "rocksdb/status.h"
+#include "util/crc32c.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This is the class to generate the file checksum based on Crc32. It
+// will be used as the default checksum method for SST file checksum
+class FileChecksumFuncCrc32c : public FileChecksumFunc {
+ public:
+ std::string Extend(const std::string& init_checksum, const char* data,
+ size_t n) override {
+ assert(data != nullptr);
+ uint32_t checksum_value = StringToUint32(init_checksum);
+ return Uint32ToString(crc32c::Extend(checksum_value, data, n));
+ }
+
+ std::string Value(const char* data, size_t n) override {
+ assert(data != nullptr);
+ return Uint32ToString(crc32c::Value(data, n));
+ }
+
+ std::string ProcessChecksum(const std::string& checksum) override {
+ uint32_t checksum_value = StringToUint32(checksum);
+ return Uint32ToString(crc32c::Mask(checksum_value));
+ }
+
+ const char* Name() const override { return "FileChecksumCrc32c"; }
+
+ // Convert a uint32_t type data into a 4 bytes string.
+ static std::string Uint32ToString(uint32_t v) {
+ std::string s;
+ if (port::kLittleEndian) {
+ s.append(reinterpret_cast<char*>(&v), sizeof(v));
+ } else {
+ char buf[sizeof(v)];
+ buf[0] = v & 0xff;
+ buf[1] = (v >> 8) & 0xff;
+ buf[2] = (v >> 16) & 0xff;
+ buf[3] = (v >> 24) & 0xff;
+ s.append(buf, sizeof(v));
+ }
+ size_t i = 0, j = s.size() - 1;
+ while (i < j) {
+ char tmp = s[i];
+ s[i] = s[j];
+ s[j] = tmp;
+ ++i;
+ --j;
+ }
+ return s;
+ }
+
+ // Convert a 4 bytes size string into a uint32_t type data.
+ static uint32_t StringToUint32(std::string s) {
+ assert(s.size() == sizeof(uint32_t));
+ size_t i = 0, j = s.size() - 1;
+ while (i < j) {
+ char tmp = s[i];
+ s[i] = s[j];
+ s[j] = tmp;
+ ++i;
+ --j;
+ }
+ uint32_t v = 0;
+ if (port::kLittleEndian) {
+ memcpy(&v, s.c_str(), sizeof(uint32_t));
+ } else {
+ const char* buf = s.c_str();
+ v |= static_cast<uint32_t>(buf[0]);
+ v |= (static_cast<uint32_t>(buf[1]) << 8);
+ v |= (static_cast<uint32_t>(buf[2]) << 16);
+ v |= (static_cast<uint32_t>(buf[3]) << 24);
+ }
+ return v;
+ }
+};
+
+// The default implementaion of FileChecksumList
+class FileChecksumListImpl : public FileChecksumList {
+ public:
+ FileChecksumListImpl() {}
+ void reset() override;
+
+ size_t size() const override;
+
+ Status GetAllFileChecksums(
+ std::vector<uint64_t>* file_numbers, std::vector<std::string>* checksums,
+ std::vector<std::string>* checksum_func_names) override;
+
+ Status SearchOneFileChecksum(uint64_t file_number, std::string* checksum,
+ std::string* checksum_func_name) override;
+
+ Status InsertOneFileChecksum(uint64_t file_number,
+ const std::string& checksum,
+ const std::string& checksum_func_name) override;
+
+ Status RemoveOneFileChecksum(uint64_t file_number) override;
+
+ private:
+ // Key is the file number, the first portion of the value is checksum, the
+ // second portion of the value is checksum function name.
+ std::unordered_map<uint64_t, std::pair<std::string, std::string>>
+ checksum_map_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/file_reader_writer_test.cc b/storage/rocksdb/rocksdb/util/file_reader_writer_test.cc
new file mode 100644
index 00000000..f37bd593
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/file_reader_writer_test.cc
@@ -0,0 +1,444 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <vector>
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "file/readahead_raf.h"
+#include "file/sequence_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WritableFileWriterTest : public testing::Test {};
+
+const uint32_t kMb = 1 << 20;
+
+TEST_F(WritableFileWriterTest, RangeSync) {
+ class FakeWF : public WritableFile {
+ public:
+ explicit FakeWF() : size_(0), last_synced_(0) {}
+ ~FakeWF() override {}
+
+ Status Append(const Slice& data) override {
+ size_ += data.size();
+ return Status::OK();
+ }
+ Status Truncate(uint64_t /*size*/) override { return Status::OK(); }
+ Status Close() 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 Status::OK();
+ }
+ Status Flush() override { return Status::OK(); }
+ Status Sync() override { return Status::OK(); }
+ Status Fsync() override { return Status::OK(); }
+ void SetIOPriority(Env::IOPriority /*pri*/) override {}
+ uint64_t GetFileSize() 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;
+ }
+ Status InvalidateCache(size_t /*offset*/, size_t /*length*/) override {
+ return Status::OK();
+ }
+
+ protected:
+ Status Allocate(uint64_t /*offset*/, uint64_t /*len*/) override {
+ return Status::OK();
+ }
+ Status RangeSync(uint64_t offset, uint64_t nbytes) 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 Status::OK();
+ }
+
+ uint64_t size_;
+ uint64_t last_synced_;
+ };
+
+ EnvOptions env_options;
+ env_options.bytes_per_sync = kMb;
+ std::unique_ptr<FakeWF> wf(new FakeWF);
+ std::unique_ptr<WritableFileWriter> writer(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(wf)),
+ "" /* don't care */, env_options));
+ Random r(301);
+ std::unique_ptr<char[]> 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);
+ writer->Append(Slice(large_buf.get(), num));
+
+ // Flush in a chance of 1/10.
+ if (r.Uniform(10) == 0) {
+ writer->Flush();
+ }
+ }
+ writer->Close();
+}
+
+TEST_F(WritableFileWriterTest, IncrementalBuffer) {
+ class FakeWF : public WritableFile {
+ 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 {}
+
+ Status Append(const Slice& data) override {
+ file_data_->append(data.data(), data.size());
+ size_ += data.size();
+ return Status::OK();
+ }
+ Status PositionedAppend(const Slice& data, uint64_t pos) 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 Status::OK();
+ }
+
+ Status Truncate(uint64_t size) override {
+ file_data_->resize(size);
+ return Status::OK();
+ }
+ Status Close() override { return Status::OK(); }
+ Status Flush() override { return Status::OK(); }
+ Status Sync() override { return Status::OK(); }
+ Status Fsync() override { return Status::OK(); }
+ void SetIOPriority(Env::IOPriority /*pri*/) override {}
+ uint64_t GetFileSize() 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;
+ }
+ Status InvalidateCache(size_t /*offset*/, size_t /*length*/) override {
+ return Status::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<FakeWF> wf(new FakeWF(&actual,
+#ifndef ROCKSDB_LITE
+ attempt % 2 == 1,
+#else
+ false,
+#endif
+ no_flush));
+ std::unique_ptr<WritableFileWriter> writer(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(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;
+ test::RandomString(&r, num, &random_string);
+ 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) {
+ writer->Flush();
+ }
+ }
+ writer->Flush();
+ writer->Close();
+ ASSERT_EQ(target.size(), actual.size());
+ ASSERT_EQ(target, actual);
+ }
+}
+
+#ifndef ROCKSDB_LITE
+TEST_F(WritableFileWriterTest, AppendStatusReturn) {
+ class FakeWF : public WritableFile {
+ public:
+ explicit FakeWF() : use_direct_io_(false), io_error_(false) {}
+
+ bool use_direct_io() const override { return use_direct_io_; }
+ Status Append(const Slice& /*data*/) override {
+ if (io_error_) {
+ return Status::IOError("Fake IO error");
+ }
+ return Status::OK();
+ }
+ Status PositionedAppend(const Slice& /*data*/, uint64_t) override {
+ if (io_error_) {
+ return Status::IOError("Fake IO error");
+ }
+ return Status::OK();
+ }
+ Status Close() override { return Status::OK(); }
+ Status Flush() override { return Status::OK(); }
+ Status Sync() override { return Status::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<FakeWF> wf(new FakeWF());
+ wf->Setuse_direct_io(true);
+ std::unique_ptr<WritableFileWriter> writer(
+ new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(wf)),
+ "" /* don't care */, EnvOptions()));
+
+ ASSERT_OK(writer->Append(std::string(2 * kMb, 'a')));
+
+ // Next call to WritableFile::Append() should fail
+ LegacyWritableFileWrapper* file =
+ static_cast<LegacyWritableFileWrapper*>(writer->writable_file());
+ static_cast<FakeWF*>(file->target())->SetIOError(true);
+ ASSERT_NOK(writer->Append(std::string(2 * kMb, 'b')));
+}
+#endif
+
+class ReadaheadRandomAccessFileTest
+ : public testing::Test,
+ public testing::WithParamInterface<size_t> {
+ public:
+ static std::vector<size_t> 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;
+ test_read_holder_->Read(offset, n, &result, scratch_.get());
+ return std::string(result.data(), result.size());
+ }
+ void ResetSourceStr(const std::string& str = "") {
+ auto write_holder =
+ std::unique_ptr<WritableFileWriter>(test::GetWritableFileWriter(
+ new test::StringSink(&control_contents_), "" /* don't care */));
+ write_holder->Append(Slice(str));
+ write_holder->Flush();
+ auto read_holder = std::unique_ptr<RandomAccessFile>(
+ 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<RandomAccessFile> test_read_holder_;
+ std::unique_ptr<char[]> 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<int>(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<int>(GetReadaheadSize()));
+ std::string str =
+ test::RandomHumanReadableString(&rng, static_cast<int>(strLen));
+ ResetSourceStr(str);
+ for (int test = 1; test <= 100; ++test) {
+ size_t offset = rng.Uniform(static_cast<int>(strLen));
+ size_t n = rng.Uniform(static_cast<int>(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<int>(GetReadaheadSize()));
+ std::string str =
+ test::RandomHumanReadableString(&rng, static_cast<int>(strLen));
+ ResetSourceStr(str);
+ for (int test = 1; test <= 100; ++test) {
+ size_t offset = rng.Uniform(static_cast<int>(strLen));
+ size_t n =
+ GetReadaheadSize() + rng.Uniform(static_cast<int>(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<size_t> {
+ public:
+ static std::vector<size_t> 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;
+ test_read_holder_->Read(n, &result, scratch_.get());
+ 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<SequentialFile>(
+ new test::SeqStringSource(str, &seq_read_count_));
+ test_read_holder_.reset(new SequentialFileReader(
+ NewLegacySequentialFileWrapper(read_holder), "test", readahead_size_));
+ }
+ size_t GetReadaheadSize() const { return readahead_size_; }
+
+ private:
+ size_t readahead_size_;
+ std::unique_ptr<SequentialFileReader> test_read_holder_;
+ std::unique_ptr<char[]> scratch_;
+ std::atomic<int> 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<int>(GetReadaheadSize()));
+ std::string str =
+ test::RandomHumanReadableString(&rng, static_cast<int>(strLen));
+ ResetSourceStr(str);
+ size_t offset = 0;
+ for (int test = 1; test <= 100; ++test) {
+ size_t n = rng.Uniform(static_cast<int>(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<int>(GetReadaheadSize()));
+ std::string str =
+ test::RandomHumanReadableString(&rng, static_cast<int>(strLen));
+ ResetSourceStr(str);
+ size_t offset = 0;
+ for (int test = 1; test <= 100; ++test) {
+ size_t n = GetReadaheadSize() +
+ rng.Uniform(static_cast<int>(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 ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/filelock_test.cc b/storage/rocksdb/rocksdb/util/filelock_test.cc
new file mode 100644
index 00000000..20efd513
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/filelock_test.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+#include "rocksdb/env.h"
+
+#include <fcntl.h>
+#include <vector>
+#include "test_util/testharness.h"
+#include "util/coding.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.
+ ASSERT_TRUE(LockFile(&lock2).IsIOError());
+
+ // 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/filter_bench.cc b/storage/rocksdb/rocksdb/util/filter_bench.cc
new file mode 100644
index 00000000..b474eee4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/filter_bench.cc
@@ -0,0 +1,751 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+#if !defined(GFLAGS) || defined(ROCKSDB_LITE)
+#include <cstdio>
+int main() {
+ fprintf(stderr, "filter_bench requires gflags and !ROCKSDB_LITE\n");
+ return 1;
+}
+#else
+
+#include <cinttypes>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+#include "memory/arena.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "table/block_based/filter_policy_internal.h"
+#include "table/block_based/full_filter_block.h"
+#include "table/block_based/mock_block_based_table.h"
+#include "table/plain/plain_table_bloom.h"
+#include "util/gflags_compat.h"
+#include "util/hash.h"
+#include "util/random.h"
+#include "util/stderr_logger.h"
+#include "util/stop_watch.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::RegisterFlagValidator;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+DEFINE_uint32(seed, 0, "Seed for random number generators");
+
+DEFINE_double(working_mem_size_mb, 200,
+ "MB of memory to get up to among all filters, unless "
+ "m_keys_total_max is specified.");
+
+DEFINE_uint32(average_keys_per_filter, 10000,
+ "Average number of keys per filter");
+
+DEFINE_double(vary_key_count_ratio, 0.4,
+ "Vary number of keys by up to +/- vary_key_count_ratio * "
+ "average_keys_per_filter.");
+
+DEFINE_uint32(key_size, 24, "Average number of bytes for each key");
+
+DEFINE_bool(vary_key_alignment, true,
+ "Whether to vary key alignment (default: at least 32-bit "
+ "alignment)");
+
+DEFINE_uint32(vary_key_size_log2_interval, 5,
+ "Use same key size 2^n times, then change. Key size varies from "
+ "-2 to +2 bytes vs. average, unless n>=30 to fix key size.");
+
+DEFINE_uint32(batch_size, 8, "Number of keys to group in each batch");
+
+DEFINE_double(bits_per_key, 10.0, "Bits per key setting for filters");
+
+DEFINE_double(m_queries, 200, "Millions of queries for each test mode");
+
+DEFINE_double(m_keys_total_max, 0,
+ "Maximum total keys added to filters, in millions. "
+ "0 (default) disables. Non-zero overrides working_mem_size_mb "
+ "option.");
+
+DEFINE_bool(use_full_block_reader, false,
+ "Use FullFilterBlockReader interface rather than FilterBitsReader");
+
+DEFINE_bool(use_plain_table_bloom, false,
+ "Use PlainTableBloom structure and interface rather than "
+ "FilterBitsReader/FullFilterBlockReader");
+
+DEFINE_bool(new_builder, false,
+ "Whether to create a new builder for each new filter");
+
+DEFINE_uint32(impl, 0,
+ "Select filter implementation. Without -use_plain_table_bloom:"
+ "0 = full filter, 1 = block-based filter. With "
+ "-use_plain_table_bloom: 0 = no locality, 1 = locality.");
+
+DEFINE_bool(net_includes_hashing, false,
+ "Whether query net ns/op times should include hashing. "
+ "(if not, dry run will include hashing) "
+ "(build times always include hashing)");
+
+DEFINE_bool(quick, false, "Run more limited set of tests, fewer queries");
+
+DEFINE_bool(best_case, false, "Run limited tests only for best-case");
+
+DEFINE_bool(allow_bad_fp_rate, false, "Continue even if FP rate is bad");
+
+DEFINE_bool(legend, false,
+ "Print more information about interpreting results instead of "
+ "running tests");
+
+DEFINE_uint32(runs, 1, "Number of times to rebuild and run benchmark tests");
+
+void _always_assert_fail(int line, const char *file, const char *expr) {
+ fprintf(stderr, "%s: %d: Assertion %s failed\n", file, line, expr);
+ abort();
+}
+
+#define ALWAYS_ASSERT(cond) \
+ ((cond) ? (void)0 : ::_always_assert_fail(__LINE__, __FILE__, #cond))
+
+#ifndef NDEBUG
+// This could affect build times enough that we should not include it for
+// accurate speed tests
+#define PREDICT_FP_RATE
+#endif
+
+using ROCKSDB_NAMESPACE::Arena;
+using ROCKSDB_NAMESPACE::BlockContents;
+using ROCKSDB_NAMESPACE::BloomFilterPolicy;
+using ROCKSDB_NAMESPACE::BloomHash;
+using ROCKSDB_NAMESPACE::BuiltinFilterBitsBuilder;
+using ROCKSDB_NAMESPACE::CachableEntry;
+using ROCKSDB_NAMESPACE::EncodeFixed32;
+using ROCKSDB_NAMESPACE::fastrange32;
+using ROCKSDB_NAMESPACE::FilterBitsReader;
+using ROCKSDB_NAMESPACE::FilterBuildingContext;
+using ROCKSDB_NAMESPACE::FullFilterBlockReader;
+using ROCKSDB_NAMESPACE::GetSliceHash;
+using ROCKSDB_NAMESPACE::GetSliceHash64;
+using ROCKSDB_NAMESPACE::Lower32of64;
+using ROCKSDB_NAMESPACE::ParsedFullFilterBlock;
+using ROCKSDB_NAMESPACE::PlainTableBloomV1;
+using ROCKSDB_NAMESPACE::Random32;
+using ROCKSDB_NAMESPACE::Slice;
+using ROCKSDB_NAMESPACE::StderrLogger;
+using ROCKSDB_NAMESPACE::mock::MockBlockBasedTableTester;
+
+struct KeyMaker {
+ KeyMaker(size_t avg_size)
+ : smallest_size_(avg_size -
+ (FLAGS_vary_key_size_log2_interval >= 30 ? 2 : 0)),
+ 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<char[]> 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) {
+ size_t start = FLAGS_vary_key_alignment ? val_num % 4 : 0;
+ size_t len = smallest_size_;
+ if (FLAGS_vary_key_size_log2_interval < 30) {
+ // To get range [avg_size - 2, avg_size + 2]
+ // use range [smallest_size, smallest_size + 4]
+ len += fastrange32(
+ (val_num >> FLAGS_vary_key_size_log2_interval) * 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 Slice(data, len);
+ }
+};
+
+void PrintWarnings() {
+#if defined(__GNUC__) && !defined(__OPTIMIZE__)
+ fprintf(stdout,
+ "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n");
+#endif
+#ifndef NDEBUG
+ fprintf(stdout,
+ "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
+#endif
+}
+
+struct FilterInfo {
+ uint32_t filter_id_ = 0;
+ std::unique_ptr<const char[]> owner_;
+ Slice filter_;
+ uint32_t keys_added_ = 0;
+ std::unique_ptr<FilterBitsReader> reader_;
+ std::unique_ptr<FullFilterBlockReader> full_block_reader_;
+ std::unique_ptr<PlainTableBloomV1> plain_table_bloom_;
+ uint64_t outside_queries_ = 0;
+ uint64_t false_positives_ = 0;
+};
+
+enum TestMode {
+ kSingleFilter,
+ kBatchPrepared,
+ kBatchUnprepared,
+ kFiftyOneFilter,
+ kEightyTwentyFilter,
+ kRandomFilter,
+};
+
+static const std::vector<TestMode> allTestModes = {
+ kSingleFilter, kBatchPrepared, kBatchUnprepared,
+ kFiftyOneFilter, kEightyTwentyFilter, kRandomFilter,
+};
+
+static const std::vector<TestMode> quickTestModes = {
+ kSingleFilter,
+ kRandomFilter,
+};
+
+static const std::vector<TestMode> bestCaseTestModes = {
+ kSingleFilter,
+};
+
+const char *TestModeToString(TestMode tm) {
+ switch (tm) {
+ case kSingleFilter:
+ return "Single filter";
+ case kBatchPrepared:
+ return "Batched, prepared";
+ case kBatchUnprepared:
+ return "Batched, unprepared";
+ case kFiftyOneFilter:
+ return "Skewed 50% in 1%";
+ case kEightyTwentyFilter:
+ return "Skewed 80% in 20%";
+ case kRandomFilter:
+ return "Random filter";
+ }
+ return "Bad TestMode";
+}
+
+// Do just enough to keep some data dependence for the
+// compiler / CPU
+static uint32_t DryRunNoHash(Slice &s) {
+ uint32_t sz = static_cast<uint32_t>(s.size());
+ if (sz >= 4) {
+ return sz + s.data()[3];
+ } else {
+ return sz;
+ }
+}
+
+static uint32_t DryRunHash32(Slice &s) {
+ // Same perf characteristics as GetSliceHash()
+ return BloomHash(s);
+}
+
+static uint32_t DryRunHash64(Slice &s) {
+ return Lower32of64(GetSliceHash64(s));
+}
+
+struct FilterBench : public MockBlockBasedTableTester {
+ std::vector<KeyMaker> kms_;
+ std::vector<FilterInfo> infos_;
+ Random32 random_;
+ std::ostringstream fp_rate_report_;
+ Arena arena_;
+ StderrLogger stderr_logger_;
+ double m_queries_;
+
+ FilterBench()
+ : MockBlockBasedTableTester(new BloomFilterPolicy(
+ FLAGS_bits_per_key,
+ static_cast<BloomFilterPolicy::Mode>(FLAGS_impl))),
+ random_(FLAGS_seed),
+ m_queries_(0) {
+ for (uint32_t i = 0; i < FLAGS_batch_size; ++i) {
+ kms_.emplace_back(FLAGS_key_size < 8 ? 8 : FLAGS_key_size);
+ }
+ ioptions_.info_log = &stderr_logger_;
+ }
+
+ void Go();
+
+ double RandomQueryTest(uint32_t inside_threshold, bool dry_run,
+ TestMode mode);
+};
+
+void FilterBench::Go() {
+ if (FLAGS_use_plain_table_bloom && FLAGS_use_full_block_reader) {
+ throw std::runtime_error(
+ "Can't combine -use_plain_table_bloom and -use_full_block_reader");
+ }
+ if (FLAGS_use_plain_table_bloom) {
+ if (FLAGS_impl > 1) {
+ throw std::runtime_error(
+ "-impl must currently be >= 0 and <= 1 for Plain table");
+ }
+ } else {
+ if (FLAGS_impl == 1) {
+ throw std::runtime_error(
+ "Block-based filter not currently supported by filter_bench");
+ }
+ if (FLAGS_impl > 2) {
+ throw std::runtime_error(
+ "-impl must currently be 0 or 2 for Block-based table");
+ }
+ }
+
+ if (FLAGS_vary_key_count_ratio < 0.0 || FLAGS_vary_key_count_ratio > 1.0) {
+ throw std::runtime_error("-vary_key_count_ratio must be >= 0.0 and <= 1.0");
+ }
+
+ // For example, average_keys_per_filter = 100, vary_key_count_ratio = 0.1.
+ // Varys up to +/- 10 keys. variance_range = 21 (generating value 0..20).
+ // variance_offset = 10, so value - offset average value is always 0.
+ const uint32_t variance_range =
+ 1 + 2 * static_cast<uint32_t>(FLAGS_vary_key_count_ratio *
+ FLAGS_average_keys_per_filter);
+ const uint32_t variance_offset = variance_range / 2;
+
+ const std::vector<TestMode> &testModes =
+ FLAGS_best_case ? bestCaseTestModes
+ : FLAGS_quick ? quickTestModes : allTestModes;
+
+ m_queries_ = FLAGS_m_queries;
+ double working_mem_size_mb = FLAGS_working_mem_size_mb;
+ if (FLAGS_quick) {
+ m_queries_ /= 7.0;
+ } else if (FLAGS_best_case) {
+ m_queries_ /= 3.0;
+ working_mem_size_mb /= 10.0;
+ }
+
+ std::cout << "Building..." << std::endl;
+
+ std::unique_ptr<BuiltinFilterBitsBuilder> builder;
+
+ size_t total_memory_used = 0;
+ size_t total_keys_added = 0;
+#ifdef PREDICT_FP_RATE
+ double weighted_predicted_fp_rate = 0.0;
+#endif
+ size_t max_total_keys;
+ size_t max_mem;
+ if (FLAGS_m_keys_total_max > 0) {
+ max_total_keys = static_cast<size_t>(1000000 * FLAGS_m_keys_total_max);
+ max_mem = SIZE_MAX;
+ } else {
+ max_total_keys = SIZE_MAX;
+ max_mem = static_cast<size_t>(1024 * 1024 * working_mem_size_mb);
+ }
+
+ ROCKSDB_NAMESPACE::StopWatchNano timer(ROCKSDB_NAMESPACE::Env::Default(),
+ true);
+
+ infos_.clear();
+ while ((working_mem_size_mb == 0 || total_memory_used < max_mem) &&
+ total_keys_added < max_total_keys) {
+ uint32_t filter_id = random_.Next();
+ uint32_t keys_to_add = FLAGS_average_keys_per_filter +
+ fastrange32(random_.Next(), variance_range) -
+ variance_offset;
+ if (max_total_keys - total_keys_added < keys_to_add) {
+ keys_to_add = static_cast<uint32_t>(max_total_keys - total_keys_added);
+ }
+ infos_.emplace_back();
+ FilterInfo &info = infos_.back();
+ info.filter_id_ = filter_id;
+ info.keys_added_ = keys_to_add;
+ if (FLAGS_use_plain_table_bloom) {
+ info.plain_table_bloom_.reset(new PlainTableBloomV1());
+ info.plain_table_bloom_->SetTotalBits(
+ &arena_, static_cast<uint32_t>(keys_to_add * FLAGS_bits_per_key),
+ FLAGS_impl, 0 /*huge_page*/, nullptr /*logger*/);
+ for (uint32_t i = 0; i < keys_to_add; ++i) {
+ uint32_t hash = GetSliceHash(kms_[0].Get(filter_id, i));
+ info.plain_table_bloom_->AddHash(hash);
+ }
+ info.filter_ = info.plain_table_bloom_->GetRawData();
+ } else {
+ if (!builder) {
+ builder.reset(&dynamic_cast<BuiltinFilterBitsBuilder &>(*GetBuilder()));
+ }
+ for (uint32_t i = 0; i < keys_to_add; ++i) {
+ builder->AddKey(kms_[0].Get(filter_id, i));
+ }
+ info.filter_ = builder->Finish(&info.owner_);
+#ifdef PREDICT_FP_RATE
+ weighted_predicted_fp_rate +=
+ keys_to_add *
+ builder->EstimatedFpRate(keys_to_add, info.filter_.size());
+#endif
+ if (FLAGS_new_builder) {
+ builder.reset();
+ }
+ info.reader_.reset(
+ table_options_.filter_policy->GetFilterBitsReader(info.filter_));
+ CachableEntry<ParsedFullFilterBlock> block(
+ new ParsedFullFilterBlock(table_options_.filter_policy.get(),
+ BlockContents(info.filter_)),
+ nullptr /* cache */, nullptr /* cache_handle */,
+ true /* own_value */);
+ info.full_block_reader_.reset(
+ new FullFilterBlockReader(table_.get(), std::move(block)));
+ }
+ total_memory_used += info.filter_.size();
+ total_keys_added += keys_to_add;
+ }
+
+ uint64_t elapsed_nanos = timer.ElapsedNanos();
+ double ns = double(elapsed_nanos) / total_keys_added;
+ std::cout << "Build avg ns/key: " << ns << std::endl;
+ std::cout << "Number of filters: " << infos_.size() << std::endl;
+ std::cout << "Total memory (MB): " << total_memory_used / 1024.0 / 1024.0
+ << std::endl;
+
+ double bpk = total_memory_used * 8.0 / total_keys_added;
+ std::cout << "Bits/key actual: " << bpk << std::endl;
+#ifdef PREDICT_FP_RATE
+ std::cout << "Predicted FP rate %: "
+ << 100.0 * (weighted_predicted_fp_rate / total_keys_added)
+ << std::endl;
+#endif
+ if (!FLAGS_quick && !FLAGS_best_case) {
+ double tolerable_rate = std::pow(2.0, -(bpk - 1.0) / (1.4 + bpk / 50.0));
+ std::cout << "Best possible FP rate %: " << 100.0 * std::pow(2.0, -bpk)
+ << std::endl;
+ std::cout << "Tolerable FP rate %: " << 100.0 * tolerable_rate << std::endl;
+
+ std::cout << "----------------------------" << std::endl;
+ std::cout << "Verifying..." << std::endl;
+
+ uint32_t outside_q_per_f =
+ static_cast<uint32_t>(m_queries_ * 1000000 / infos_.size());
+ uint64_t fps = 0;
+ for (uint32_t i = 0; i < infos_.size(); ++i) {
+ FilterInfo &info = infos_[i];
+ for (uint32_t j = 0; j < info.keys_added_; ++j) {
+ if (FLAGS_use_plain_table_bloom) {
+ uint32_t hash = GetSliceHash(kms_[0].Get(info.filter_id_, j));
+ ALWAYS_ASSERT(info.plain_table_bloom_->MayContainHash(hash));
+ } else {
+ ALWAYS_ASSERT(
+ info.reader_->MayMatch(kms_[0].Get(info.filter_id_, j)));
+ }
+ }
+ for (uint32_t j = 0; j < outside_q_per_f; ++j) {
+ if (FLAGS_use_plain_table_bloom) {
+ uint32_t hash =
+ GetSliceHash(kms_[0].Get(info.filter_id_, j | 0x80000000));
+ fps += info.plain_table_bloom_->MayContainHash(hash);
+ } else {
+ fps += info.reader_->MayMatch(
+ kms_[0].Get(info.filter_id_, j | 0x80000000));
+ }
+ }
+ }
+ std::cout << " No FNs :)" << std::endl;
+ double prelim_rate = double(fps) / outside_q_per_f / infos_.size();
+ std::cout << " Prelim FP rate %: " << (100.0 * prelim_rate) << std::endl;
+
+ if (!FLAGS_allow_bad_fp_rate) {
+ ALWAYS_ASSERT(prelim_rate < tolerable_rate);
+ }
+ }
+
+ std::cout << "----------------------------" << std::endl;
+ std::cout << "Mixed inside/outside queries..." << std::endl;
+ // 50% each inside and outside
+ uint32_t inside_threshold = UINT32_MAX / 2;
+ for (TestMode tm : testModes) {
+ random_.Seed(FLAGS_seed + 1);
+ double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
+ random_.Seed(FLAGS_seed + 1);
+ double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
+ std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d)
+ << std::endl;
+ }
+
+ if (!FLAGS_quick) {
+ std::cout << "----------------------------" << std::endl;
+ std::cout << "Inside queries (mostly)..." << std::endl;
+ // Do about 95% inside queries rather than 100% so that branch predictor
+ // can't give itself an artifically crazy advantage.
+ inside_threshold = UINT32_MAX / 20 * 19;
+ for (TestMode tm : testModes) {
+ random_.Seed(FLAGS_seed + 1);
+ double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
+ random_.Seed(FLAGS_seed + 1);
+ double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
+ std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d)
+ << std::endl;
+ }
+
+ std::cout << "----------------------------" << std::endl;
+ std::cout << "Outside queries (mostly)..." << std::endl;
+ // Do about 95% outside queries rather than 100% so that branch predictor
+ // can't give itself an artifically crazy advantage.
+ inside_threshold = UINT32_MAX / 20;
+ for (TestMode tm : testModes) {
+ random_.Seed(FLAGS_seed + 2);
+ double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
+ random_.Seed(FLAGS_seed + 2);
+ double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
+ std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d)
+ << std::endl;
+ }
+ }
+ std::cout << fp_rate_report_.str();
+
+ std::cout << "----------------------------" << std::endl;
+ std::cout << "Done. (For more info, run with -legend or -help.)" << std::endl;
+}
+
+double FilterBench::RandomQueryTest(uint32_t inside_threshold, bool dry_run,
+ TestMode mode) {
+ for (auto &info : infos_) {
+ info.outside_queries_ = 0;
+ info.false_positives_ = 0;
+ }
+
+ auto dry_run_hash_fn = DryRunNoHash;
+ if (!FLAGS_net_includes_hashing) {
+ if (FLAGS_impl < 2 || FLAGS_use_plain_table_bloom) {
+ dry_run_hash_fn = DryRunHash32;
+ } else {
+ dry_run_hash_fn = DryRunHash64;
+ }
+ }
+
+ uint32_t num_infos = static_cast<uint32_t>(infos_.size());
+ uint32_t dry_run_hash = 0;
+ uint64_t max_queries = static_cast<uint64_t>(m_queries_ * 1000000 + 0.50);
+ // Some filters may be considered secondary in order to implement skewed
+ // queries. num_primary_filters is the number that are to be treated as
+ // equal, and any remainder will be treated as secondary.
+ uint32_t num_primary_filters = num_infos;
+ // The proportion (when divided by 2^32 - 1) of filter queries going to
+ // the primary filters (default = all). The remainder of queries are
+ // against secondary filters.
+ uint32_t primary_filter_threshold = 0xffffffff;
+ if (mode == kSingleFilter) {
+ // 100% of queries to 1 filter
+ num_primary_filters = 1;
+ } else if (mode == kFiftyOneFilter) {
+ // 50% of queries
+ primary_filter_threshold /= 2;
+ // to 1% of filters
+ num_primary_filters = (num_primary_filters + 99) / 100;
+ } else if (mode == kEightyTwentyFilter) {
+ // 80% of queries
+ primary_filter_threshold = primary_filter_threshold / 5 * 4;
+ // to 20% of filters
+ num_primary_filters = (num_primary_filters + 4) / 5;
+ }
+ uint32_t batch_size = 1;
+ std::unique_ptr<Slice[]> batch_slices;
+ std::unique_ptr<Slice *[]> batch_slice_ptrs;
+ std::unique_ptr<bool[]> batch_results;
+ if (mode == kBatchPrepared || mode == kBatchUnprepared) {
+ batch_size = static_cast<uint32_t>(kms_.size());
+ }
+
+ batch_slices.reset(new Slice[batch_size]);
+ batch_slice_ptrs.reset(new Slice *[batch_size]);
+ batch_results.reset(new bool[batch_size]);
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ batch_results[i] = false;
+ batch_slice_ptrs[i] = &batch_slices[i];
+ }
+
+ ROCKSDB_NAMESPACE::StopWatchNano timer(ROCKSDB_NAMESPACE::Env::Default(),
+ true);
+
+ for (uint64_t q = 0; q < max_queries; q += batch_size) {
+ bool inside_this_time = random_.Next() <= inside_threshold;
+
+ uint32_t filter_index;
+ if (random_.Next() <= primary_filter_threshold) {
+ filter_index = random_.Uniformish(num_primary_filters);
+ } else {
+ // secondary
+ filter_index = num_primary_filters +
+ random_.Uniformish(num_infos - num_primary_filters);
+ }
+ FilterInfo &info = infos_[filter_index];
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ if (inside_this_time) {
+ batch_slices[i] =
+ kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_));
+ } else {
+ batch_slices[i] =
+ kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_) |
+ uint32_t{0x80000000});
+ info.outside_queries_++;
+ }
+ }
+ // TODO: implement batched interface to full block reader
+ // TODO: implement batched interface to plain table bloom
+ if (mode == kBatchPrepared && !FLAGS_use_full_block_reader &&
+ !FLAGS_use_plain_table_bloom) {
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ batch_results[i] = false;
+ }
+ if (dry_run) {
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ batch_results[i] = true;
+ dry_run_hash += dry_run_hash_fn(batch_slices[i]);
+ }
+ } else {
+ info.reader_->MayMatch(batch_size, batch_slice_ptrs.get(),
+ batch_results.get());
+ }
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ if (inside_this_time) {
+ ALWAYS_ASSERT(batch_results[i]);
+ } else {
+ info.false_positives_ += batch_results[i];
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < batch_size; ++i) {
+ bool may_match;
+ if (FLAGS_use_plain_table_bloom) {
+ if (dry_run) {
+ dry_run_hash += dry_run_hash_fn(batch_slices[i]);
+ may_match = true;
+ } else {
+ uint32_t hash = GetSliceHash(batch_slices[i]);
+ may_match = info.plain_table_bloom_->MayContainHash(hash);
+ }
+ } else if (FLAGS_use_full_block_reader) {
+ if (dry_run) {
+ dry_run_hash += dry_run_hash_fn(batch_slices[i]);
+ may_match = true;
+ } else {
+ may_match = info.full_block_reader_->KeyMayMatch(
+ batch_slices[i],
+ /*prefix_extractor=*/nullptr,
+ /*block_offset=*/ROCKSDB_NAMESPACE::kNotValid,
+ /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
+ /*get_context=*/nullptr,
+ /*lookup_context=*/nullptr);
+ }
+ } else {
+ if (dry_run) {
+ dry_run_hash += dry_run_hash_fn(batch_slices[i]);
+ may_match = true;
+ } else {
+ may_match = info.reader_->MayMatch(batch_slices[i]);
+ }
+ }
+ if (inside_this_time) {
+ ALWAYS_ASSERT(may_match);
+ } else {
+ info.false_positives_ += may_match;
+ }
+ }
+ }
+ }
+
+ uint64_t elapsed_nanos = timer.ElapsedNanos();
+ double ns = double(elapsed_nanos) / max_queries;
+
+ if (!FLAGS_quick) {
+ if (dry_run) {
+ // Printing part of hash prevents dry run components from being optimized
+ // away by compiler
+ std::cout << " Dry run (" << std::hex << (dry_run_hash & 0xfffff)
+ << std::dec << ") ";
+ } else {
+ std::cout << " Gross filter ";
+ }
+ std::cout << "ns/op: " << ns << std::endl;
+ }
+
+ if (!dry_run) {
+ fp_rate_report_.str("");
+ uint64_t q = 0;
+ uint64_t fp = 0;
+ double worst_fp_rate = 0.0;
+ double best_fp_rate = 1.0;
+ for (auto &info : infos_) {
+ q += info.outside_queries_;
+ fp += info.false_positives_;
+ if (info.outside_queries_ > 0) {
+ double fp_rate = double(info.false_positives_) / info.outside_queries_;
+ worst_fp_rate = std::max(worst_fp_rate, fp_rate);
+ best_fp_rate = std::min(best_fp_rate, fp_rate);
+ }
+ }
+ fp_rate_report_ << " Average FP rate %: " << 100.0 * fp / q << std::endl;
+ if (!FLAGS_quick && !FLAGS_best_case) {
+ fp_rate_report_ << " Worst FP rate %: " << 100.0 * worst_fp_rate
+ << std::endl;
+ fp_rate_report_ << " Best FP rate %: " << 100.0 * best_fp_rate
+ << std::endl;
+ fp_rate_report_ << " Best possible bits/key: "
+ << -std::log(double(fp) / q) / std::log(2.0) << std::endl;
+ }
+ }
+ return ns;
+}
+
+int main(int argc, char **argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [-quick] [OTHER OPTIONS]...");
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ PrintWarnings();
+
+ if (FLAGS_legend) {
+ std::cout
+ << "Legend:" << std::endl
+ << " \"Inside\" - key that was added to filter" << std::endl
+ << " \"Outside\" - key that was not added to filter" << std::endl
+ << " \"FN\" - false negative query (must not happen)" << std::endl
+ << " \"FP\" - false positive query (OK at low rate)" << std::endl
+ << " \"Dry run\" - cost of testing and hashing overhead." << std::endl
+ << " \"Gross filter\" - cost of filter queries including testing "
+ << "\n and hashing overhead." << std::endl
+ << " \"net\" - best estimate of time in filter operation, without "
+ << "\n testing and hashing overhead (gross filter - dry run)"
+ << std::endl
+ << " \"ns/op\" - nanoseconds per operation (key query or add)"
+ << std::endl
+ << " \"Single filter\" - essentially minimum cost, assuming filter"
+ << "\n fits easily in L1 CPU cache." << std::endl
+ << " \"Batched, prepared\" - several queries at once against a"
+ << "\n randomly chosen filter, using multi-query interface."
+ << std::endl
+ << " \"Batched, unprepared\" - similar, but using serial calls"
+ << "\n to single query interface." << std::endl
+ << " \"Random filter\" - a filter is chosen at random as target"
+ << "\n of each query." << std::endl
+ << " \"Skewed X% in Y%\" - like \"Random filter\" except Y% of"
+ << "\n the filters are designated as \"hot\" and receive X%"
+ << "\n of queries." << std::endl;
+ } else {
+ FilterBench b;
+ for (uint32_t i = 0; i < FLAGS_runs; ++i) {
+ b.Go();
+ FLAGS_seed += 100;
+ b.random_.Seed(FLAGS_seed);
+ }
+ }
+
+ return 0;
+}
+
+#endif // !defined(GFLAGS) || defined(ROCKSDB_LITE)
diff --git a/storage/rocksdb/rocksdb/util/gflags_compat.h b/storage/rocksdb/rocksdb/util/gflags_compat.h
new file mode 100644
index 00000000..d5a30ce7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/gflags_compat.h
@@ -0,0 +1,19 @@
+// 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 <gflags/gflags.h>
+
+#ifndef GFLAGS_NAMESPACE
+// in case it's not defined in old versions, that's probably because it was
+// still google by default.
+#define GFLAGS_NAMESPACE google
+#endif
+
+#ifndef DEFINE_uint32
+// DEFINE_uint32 does not appear in older versions of gflags. This should be
+// a sane definition for those versions.
+#define DEFINE_uint32(name, val, txt) \
+ DEFINE_VARIABLE(GFLAGS_NAMESPACE::uint32, U, name, val, txt)
+#endif
diff --git a/storage/rocksdb/rocksdb/util/hash.cc b/storage/rocksdb/rocksdb/util/hash.cc
new file mode 100644
index 00000000..d72be8a4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/hash.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string.h>
+#include "util/coding.h"
+#include "util/hash.h"
+#include "util/util.h"
+#include "util/xxhash.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+uint32_t Hash(const char* data, size_t n, uint32_t seed) {
+ // MurmurHash1 - fast but mediocre quality
+ // https://github.com/aappleby/smhasher/wiki/MurmurHash1
+ //
+ const uint32_t m = 0xc6a4a793;
+ const uint32_t r = 24;
+ const char* limit = data + n;
+ uint32_t h = static_cast<uint32_t>(seed ^ (n * m));
+
+ // Pick up four bytes at a time
+ while (data + 4 <= limit) {
+ uint32_t w = DecodeFixed32(data);
+ data += 4;
+ h += w;
+ h *= m;
+ h ^= (h >> 16);
+ }
+
+ // Pick up remaining bytes
+ switch (limit - data) {
+ // Note: The original hash implementation used data[i] << shift, which
+ // promotes the char to int and then performs the shift. If the char is
+ // negative, the shift is undefined behavior in C++. The hash algorithm is
+ // part of the format definition, so we cannot change it; to obtain the same
+ // behavior in a legal way we just cast to uint32_t, which will do
+ // sign-extension. To guarantee compatibility with architectures where chars
+ // are unsigned we first cast the char to int8_t.
+ case 3:
+ h += static_cast<uint32_t>(static_cast<int8_t>(data[2])) << 16;
+ FALLTHROUGH_INTENDED;
+ case 2:
+ h += static_cast<uint32_t>(static_cast<int8_t>(data[1])) << 8;
+ FALLTHROUGH_INTENDED;
+ case 1:
+ h += static_cast<uint32_t>(static_cast<int8_t>(data[0]));
+ h *= m;
+ h ^= (h >> r);
+ break;
+ }
+ return h;
+}
+
+// We are standardizing on a preview release of XXH3, because that's
+// the best available at time of standardizing.
+//
+// In testing (mostly Intel Skylake), this hash function is much more
+// thorough than Hash32 and is almost universally faster. Hash() only
+// seems faster when passing runtime-sized keys of the same small size
+// (less than about 24 bytes) thousands of times in a row; this seems
+// to allow the branch predictor to work some magic. XXH3's speed is
+// much less dependent on branch prediction.
+//
+// Hashing with a prefix extractor is potentially a common case of
+// hashing objects of small, predictable size. We could consider
+// bundling hash functions specialized for particular lengths with
+// the prefix extractors.
+uint64_t Hash64(const char* data, size_t n, uint64_t seed) {
+ return XXH3p_64bits_withSeed(data, n, seed);
+}
+
+uint64_t Hash64(const char* data, size_t n) {
+ // Same as seed = 0
+ return XXH3p_64bits(data, n);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/hash.h b/storage/rocksdb/rocksdb/util/hash.h
new file mode 100644
index 00000000..17490a36
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/hash.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+//
+// Common hash functions with convenient interfaces. If hashing a
+// statically-sized input in a performance-critical context, consider
+// calling a specific hash implementation directly, such as
+// XXH3p_64bits from xxhash.h.
+//
+// Since this is a very common header, implementation details are kept
+// out-of-line. Out-of-lining also aids in tracking the time spent in
+// hashing functions. Inlining is of limited benefit for runtime-sized
+// hash inputs.
+
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Stable/persistent 64-bit hash. Higher quality and generally faster than
+// Hash(), especially for inputs > 24 bytes.
+extern uint64_t Hash64(const char* data, size_t n, uint64_t seed);
+
+// Specific optimization without seed (same as seed = 0)
+extern uint64_t Hash64(const char* data, size_t n);
+
+// Non-persistent hash. Must only used for in-memory data structure.
+// The hash results are thus applicable to change. (Thus, it rarely makes
+// sense to specify a seed for this function.)
+inline uint64_t NPHash64(const char* data, size_t n, uint32_t seed) {
+ // Currently same as Hash64
+ return Hash64(data, n, seed);
+}
+
+// Specific optimization without seed (same as seed = 0)
+inline uint64_t NPHash64(const char* data, size_t n) {
+ // Currently same as Hash64
+ return Hash64(data, n);
+}
+
+// Stable/persistent 32-bit hash. Moderate quality and high speed on
+// small inputs.
+// TODO: consider rename to Hash32
+extern uint32_t Hash(const char* data, size_t n, uint32_t seed);
+
+// TODO: consider rename to LegacyBloomHash32
+inline uint32_t BloomHash(const Slice& key) {
+ return Hash(key.data(), key.size(), 0xbc9f1d34);
+}
+
+inline uint64_t GetSliceHash64(const Slice& key) {
+ return Hash64(key.data(), key.size());
+}
+
+inline uint64_t GetSliceNPHash64(const Slice& s) {
+ return NPHash64(s.data(), s.size());
+}
+
+// TODO: consider rename to GetSliceHash32
+inline uint32_t GetSliceHash(const Slice& s) {
+ return Hash(s.data(), s.size(), 397);
+}
+
+// Useful for splitting up a 64-bit hash
+inline uint32_t Upper32of64(uint64_t v) {
+ return static_cast<uint32_t>(v >> 32);
+}
+inline uint32_t Lower32of64(uint64_t v) { return static_cast<uint32_t>(v); }
+
+// std::hash compatible interface.
+// TODO: consider rename to SliceHasher32
+struct SliceHasher {
+ uint32_t operator()(const Slice& s) const { return GetSliceHash(s); }
+};
+
+// An alternative to % for mapping a hash value to an arbitrary range. See
+// https://github.com/lemire/fastrange
+inline uint32_t fastrange32(uint32_t hash, uint32_t range) {
+ uint64_t product = uint64_t{range} * hash;
+ return static_cast<uint32_t>(product >> 32);
+}
+
+// An alternative to % for mapping a 64-bit hash value to an arbitrary range
+// that fits in size_t. See https://github.com/lemire/fastrange
+// We find size_t more convenient than uint64_t for the range, with side
+// benefit of better optimization on 32-bit platforms.
+inline size_t fastrange64(uint64_t hash, size_t range) {
+#if defined(HAVE_UINT128_EXTENSION)
+ // Can use compiler's 128-bit type. Trust it to do the right thing.
+ __uint128_t wide = __uint128_t{range} * hash;
+ return static_cast<size_t>(wide >> 64);
+#else
+ // Fall back: full decomposition.
+ // NOTE: GCC seems to fully understand this code as 64-bit x {32 or 64}-bit
+ // -> {96 or 128}-bit multiplication and optimize it down to a single
+ // wide-result multiplication (64-bit platform) or two wide-result
+ // multiplications (32-bit platforms, where range64 >> 32 is zero).
+ uint64_t range64 = range; // ok to shift by 32, even if size_t is 32-bit
+ uint64_t tmp = uint64_t{range64 & 0xffffFFFF} * uint64_t{hash & 0xffffFFFF};
+ tmp >>= 32;
+ tmp += uint64_t{range64 & 0xffffFFFF} * uint64_t{hash >> 32};
+ // Avoid overflow: first add lower 32 of tmp2, and later upper 32
+ uint64_t tmp2 = uint64_t{range64 >> 32} * uint64_t{hash & 0xffffFFFF};
+ tmp += static_cast<uint32_t>(tmp2);
+ tmp >>= 32;
+ tmp += (tmp2 >> 32);
+ tmp += uint64_t{range64 >> 32} * uint64_t{hash >> 32};
+ return static_cast<size_t>(tmp);
+#endif
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/hash_map.h b/storage/rocksdb/rocksdb/util/hash_map.h
new file mode 100644
index 00000000..6eb42506
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/hash_map.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <array>
+#include <utility>
+
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// This is similar to std::unordered_map, except that it tries to avoid
+// allocating or deallocating memory as much as possible. With
+// std::unordered_map, an allocation/deallocation is made for every insertion
+// or deletion because of the requirement that iterators remain valid even
+// with insertions or deletions. This means that the hash chains will be
+// implemented as linked lists.
+//
+// This implementation uses autovector as hash chains insteads.
+//
+template <typename K, typename V, size_t size = 128>
+class HashMap {
+ std::array<autovector<std::pair<K, V>, 1>, size> table_;
+
+ public:
+ bool Contains(K key) {
+ auto& bucket = table_[key % size];
+ auto it = std::find_if(
+ bucket.begin(), bucket.end(),
+ [key](const std::pair<K, V>& p) { return p.first == key; });
+ return it != bucket.end();
+ }
+
+ void Insert(K key, V value) {
+ auto& bucket = table_[key % size];
+ bucket.push_back({key, value});
+ }
+
+ void Delete(K key) {
+ auto& bucket = table_[key % size];
+ auto it = std::find_if(
+ bucket.begin(), bucket.end(),
+ [key](const std::pair<K, V>& p) { return p.first == key; });
+ if (it != bucket.end()) {
+ auto last = bucket.end() - 1;
+ if (it != last) {
+ *it = *last;
+ }
+ bucket.pop_back();
+ }
+ }
+
+ V& Get(K key) {
+ auto& bucket = table_[key % size];
+ auto it = std::find_if(
+ bucket.begin(), bucket.end(),
+ [key](const std::pair<K, V>& p) { return p.first == key; });
+ return it->second;
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/hash_test.cc b/storage/rocksdb/rocksdb/util/hash_test.cc
new file mode 100644
index 00000000..9c3c6efe
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/hash_test.cc
@@ -0,0 +1,377 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstring>
+#include <vector>
+
+#include "test_util/testharness.h"
+#include "util/coding.h"
+#include "util/hash.h"
+
+using ROCKSDB_NAMESPACE::EncodeFixed32;
+using ROCKSDB_NAMESPACE::GetSliceHash64;
+using ROCKSDB_NAMESPACE::Hash;
+using ROCKSDB_NAMESPACE::Hash64;
+using ROCKSDB_NAMESPACE::Lower32of64;
+using ROCKSDB_NAMESPACE::Slice;
+using ROCKSDB_NAMESPACE::Upper32of64;
+
+// 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 GetSliceHash64
+ 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<size_t>(h % 61)]);
+ }
+ return rv;
+}
+
+// XXH3p 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(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
+}
+
+// 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);
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/heap.h b/storage/rocksdb/rocksdb/util/heap.h
new file mode 100644
index 00000000..c72d944d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/heap.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cstdint>
+#include <functional>
+#include "port/port.h"
+#include "util/autovector.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Binary heap implementation optimized for use in multi-way merge sort.
+// Comparison to std::priority_queue:
+// - In libstdc++, std::priority_queue::pop() usually performs just over logN
+// comparisons but never fewer.
+// - std::priority_queue does not have a replace-top operation, requiring a
+// pop+push. If the replacement element is the new top, this requires
+// around 2logN comparisons.
+// - This heap's pop() uses a "schoolbook" downheap which requires up to ~2logN
+// comparisons.
+// - This heap provides a replace_top() operation which requires [1, 2logN]
+// comparisons. When the replacement element is also the new top, this
+// takes just 1 or 2 comparisons.
+//
+// The last property can yield an order-of-magnitude performance improvement
+// when merge-sorting real-world non-random data. If the merge operation is
+// likely to take chunks of elements from the same input stream, only 1
+// comparison per element is needed. In RocksDB-land, this happens when
+// compacting a database where keys are not randomly distributed across L0
+// files but nearby keys are likely to be in the same L0 file.
+//
+// The container uses the same counterintuitive ordering as
+// std::priority_queue: the comparison operator is expected to provide the
+// less-than relation, but top() will return the maximum.
+
+template<typename T, typename Compare = std::less<T>>
+class BinaryHeap {
+ public:
+ BinaryHeap() { }
+ explicit BinaryHeap(Compare cmp) : cmp_(std::move(cmp)) { }
+
+ void push(const T& value) {
+ data_.push_back(value);
+ upheap(data_.size() - 1);
+ }
+
+ void push(T&& value) {
+ data_.push_back(std::move(value));
+ upheap(data_.size() - 1);
+ }
+
+ const T& top() const {
+ assert(!empty());
+ return data_.front();
+ }
+
+ void replace_top(const T& value) {
+ assert(!empty());
+ data_.front() = value;
+ downheap(get_root());
+ }
+
+ void replace_top(T&& value) {
+ assert(!empty());
+ data_.front() = std::move(value);
+ downheap(get_root());
+ }
+
+ void pop() {
+ assert(!empty());
+ data_.front() = std::move(data_.back());
+ data_.pop_back();
+ if (!empty()) {
+ downheap(get_root());
+ } else {
+ reset_root_cmp_cache();
+ }
+ }
+
+ void swap(BinaryHeap &other) {
+ std::swap(cmp_, other.cmp_);
+ data_.swap(other.data_);
+ std::swap(root_cmp_cache_, other.root_cmp_cache_);
+ }
+
+ void clear() {
+ data_.clear();
+ reset_root_cmp_cache();
+ }
+
+ bool empty() const { return data_.empty(); }
+
+ size_t size() const { return data_.size(); }
+
+ void reset_root_cmp_cache() { root_cmp_cache_ = port::kMaxSizet; }
+
+ private:
+ static inline size_t get_root() { return 0; }
+ static inline size_t get_parent(size_t index) { return (index - 1) / 2; }
+ static inline size_t get_left(size_t index) { return 2 * index + 1; }
+ static inline size_t get_right(size_t index) { return 2 * index + 2; }
+
+ void upheap(size_t index) {
+ T v = std::move(data_[index]);
+ while (index > get_root()) {
+ const size_t parent = get_parent(index);
+ if (!cmp_(data_[parent], v)) {
+ break;
+ }
+ data_[index] = std::move(data_[parent]);
+ index = parent;
+ }
+ data_[index] = std::move(v);
+ reset_root_cmp_cache();
+ }
+
+ void downheap(size_t index) {
+ T v = std::move(data_[index]);
+
+ size_t picked_child = port::kMaxSizet;
+ while (1) {
+ const size_t left_child = get_left(index);
+ if (get_left(index) >= data_.size()) {
+ break;
+ }
+ const size_t right_child = left_child + 1;
+ assert(right_child == get_right(index));
+ picked_child = left_child;
+ if (index == 0 && root_cmp_cache_ < data_.size()) {
+ picked_child = root_cmp_cache_;
+ } else if (right_child < data_.size() &&
+ cmp_(data_[left_child], data_[right_child])) {
+ picked_child = right_child;
+ }
+ if (!cmp_(v, data_[picked_child])) {
+ break;
+ }
+ data_[index] = std::move(data_[picked_child]);
+ index = picked_child;
+ }
+
+ if (index == 0) {
+ // We did not change anything in the tree except for the value
+ // of the root node, left and right child did not change, we can
+ // cache that `picked_child` is the smallest child
+ // so next time we compare againist it directly
+ root_cmp_cache_ = picked_child;
+ } else {
+ // the tree changed, reset cache
+ reset_root_cmp_cache();
+ }
+
+ data_[index] = std::move(v);
+ }
+
+ Compare cmp_;
+ autovector<T> data_;
+ // Used to reduce number of cmp_ calls in downheap()
+ size_t root_cmp_cache_ = port::kMaxSizet;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/heap_test.cc b/storage/rocksdb/rocksdb/util/heap_test.cc
new file mode 100644
index 00000000..1f188e73
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/heap_test.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <gtest/gtest.h>
+
+#include <climits>
+
+#include <queue>
+#include <random>
+#include <utility>
+
+#include "util/heap.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<size_t, HeapTestValue, int64_t>;
+
+class HeapTest : public ::testing::TestWithParam<Params> {
+};
+
+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<HeapTestValue> heap;
+ std::priority_queue<HeapTestValue> ref;
+
+ std::mt19937 rng(static_cast<unsigned int>(RNG_SEED));
+ std::uniform_int_distribution<HeapTestValue> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+#ifdef GFLAGS
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
+#endif // GFLAGS
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/kv_map.h b/storage/rocksdb/rocksdb/util/kv_map.h
new file mode 100644
index 00000000..0f713cce
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/kv_map.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <map>
+#include <string>
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace stl_wrappers {
+
+struct LessOfComparator {
+ explicit LessOfComparator(const Comparator* c = BytewiseComparator())
+ : cmp(c) {}
+
+ bool operator()(const std::string& a, const std::string& b) const {
+ return cmp->Compare(Slice(a), Slice(b)) < 0;
+ }
+ bool operator()(const Slice& a, const Slice& b) const {
+ return cmp->Compare(a, b) < 0;
+ }
+
+ const Comparator* cmp;
+};
+
+typedef std::map<std::string, std::string, LessOfComparator> KVMap;
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/log_write_bench.cc b/storage/rocksdb/rocksdb/util/log_write_bench.cc
new file mode 100644
index 00000000..60798bab
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/log_write_bench.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstdio>
+int main() {
+ fprintf(stderr, "Please install gflags to run rocksdb tools\n");
+ return 1;
+}
+#else
+
+#include "file/writable_file_writer.h"
+#include "monitoring/histogram.h"
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/gflags_compat.h"
+
+using GFLAGS_NAMESPACE::ParseCommandLineFlags;
+using GFLAGS_NAMESPACE::SetUsageMessage;
+
+// A simple benchmark to simulate transactional logs
+
+DEFINE_int32(num_records, 6000, "Number of records.");
+DEFINE_int32(record_size, 249, "Size of each record.");
+DEFINE_int32(record_interval, 10000, "Interval between records (microSec)");
+DEFINE_int32(bytes_per_sync, 0, "bytes_per_sync parameter in EnvOptions");
+DEFINE_bool(enable_sync, false, "sync after each write.");
+
+namespace ROCKSDB_NAMESPACE {
+void RunBenchmark() {
+ std::string file_name = test::PerThreadDBPath("log_write_benchmark.log");
+ DBOptions options;
+ Env* env = Env::Default();
+ EnvOptions env_options = env->OptimizeForLogWrite(EnvOptions(), options);
+ env_options.bytes_per_sync = FLAGS_bytes_per_sync;
+ std::unique_ptr<WritableFile> file;
+ env->NewWritableFile(file_name, &file, env_options);
+ std::unique_ptr<WritableFileWriter> writer;
+ writer.reset(new WritableFileWriter(std::move(file), file_name, env_options,
+ env, nullptr /* stats */,
+ options.listeners));
+
+ std::string record;
+ record.assign(FLAGS_record_size, 'X');
+
+ HistogramImpl hist;
+
+ uint64_t start_time = env->NowMicros();
+ for (int i = 0; i < FLAGS_num_records; i++) {
+ uint64_t start_nanos = env->NowNanos();
+ writer->Append(record);
+ writer->Flush();
+ if (FLAGS_enable_sync) {
+ writer->Sync(false);
+ }
+ hist.Add(env->NowNanos() - start_nanos);
+
+ if (i % 1000 == 1) {
+ fprintf(stderr, "Wrote %d records...\n", i);
+ }
+
+ int time_to_sleep =
+ (i + 1) * FLAGS_record_interval - (env->NowMicros() - start_time);
+ if (time_to_sleep > 0) {
+ env->SleepForMicroseconds(time_to_sleep);
+ }
+ }
+
+ fprintf(stderr, "Distribution of latency of append+flush: \n%s",
+ hist.ToString().c_str());
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
+ " [OPTIONS]...");
+ ParseCommandLineFlags(&argc, &argv, true);
+
+ ROCKSDB_NAMESPACE::RunBenchmark();
+ return 0;
+}
+
+#endif // GFLAGS
diff --git a/storage/rocksdb/rocksdb/util/murmurhash.cc b/storage/rocksdb/rocksdb/util/murmurhash.cc
new file mode 100644
index 00000000..3b759c5e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/murmurhash.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+/*
+ Murmurhash from http://sites.google.com/site/murmurhash/
+
+ All code is released to the public domain. For business purposes, Murmurhash
+ is under the MIT license.
+*/
+#include "murmurhash.h"
+#include "util/util.h"
+
+#if defined(__x86_64__)
+
+// -------------------------------------------------------------------
+//
+// The same caveats as 32-bit MurmurHash2 apply here - beware of alignment
+// and endian-ness issues if used across multiple platforms.
+//
+// 64-bit hash for 64-bit platforms
+
+#ifdef ROCKSDB_UBSAN_RUN
+#if defined(__clang__)
+__attribute__((__no_sanitize__("alignment")))
+#elif defined(__GNUC__)
+__attribute__((__no_sanitize_undefined__))
+#endif
+#endif
+uint64_t MurmurHash64A ( const void * key, int len, unsigned int seed )
+{
+ const uint64_t m = 0xc6a4a7935bd1e995;
+ const int r = 47;
+
+ uint64_t h = seed ^ (len * m);
+
+ const uint64_t * data = (const uint64_t *)key;
+ const uint64_t * end = data + (len/8);
+
+ while(data != end)
+ {
+ uint64_t k = *data++;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+ }
+
+ const unsigned char * data2 = (const unsigned char*)data;
+
+ switch(len & 7)
+ {
+ case 7: h ^= ((uint64_t)data2[6]) << 48; FALLTHROUGH_INTENDED;
+ case 6: h ^= ((uint64_t)data2[5]) << 40; FALLTHROUGH_INTENDED;
+ case 5: h ^= ((uint64_t)data2[4]) << 32; FALLTHROUGH_INTENDED;
+ case 4: h ^= ((uint64_t)data2[3]) << 24; FALLTHROUGH_INTENDED;
+ case 3: h ^= ((uint64_t)data2[2]) << 16; FALLTHROUGH_INTENDED;
+ case 2: h ^= ((uint64_t)data2[1]) << 8; FALLTHROUGH_INTENDED;
+ case 1: h ^= ((uint64_t)data2[0]);
+ h *= m;
+ };
+
+ h ^= h >> r;
+ h *= m;
+ h ^= h >> r;
+
+ return h;
+}
+
+#elif defined(__i386__)
+
+// -------------------------------------------------------------------
+//
+// Note - This code makes a few assumptions about how your machine behaves -
+//
+// 1. We can read a 4-byte value from any address without crashing
+// 2. sizeof(int) == 4
+//
+// And it has a few limitations -
+//
+// 1. It will not work incrementally.
+// 2. It will not produce the same results on little-endian and big-endian
+// machines.
+
+unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed )
+{
+ // 'm' and 'r' are mixing constants generated offline.
+ // They're not really 'magic', they just happen to work well.
+
+ const unsigned int m = 0x5bd1e995;
+ const int r = 24;
+
+ // Initialize the hash to a 'random' value
+
+ unsigned int h = seed ^ len;
+
+ // Mix 4 bytes at a time into the hash
+
+ const unsigned char * data = (const unsigned char *)key;
+
+ while(len >= 4)
+ {
+ unsigned int k = *(unsigned int *)data;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ data += 4;
+ len -= 4;
+ }
+
+ // Handle the last few bytes of the input array
+
+ switch(len)
+ {
+ case 3: h ^= data[2] << 16; FALLTHROUGH_INTENDED;
+ case 2: h ^= data[1] << 8; FALLTHROUGH_INTENDED;
+ case 1: h ^= data[0];
+ h *= m;
+ };
+
+ // Do a few final mixes of the hash to ensure the last few
+ // bytes are well-incorporated.
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ return h;
+}
+
+#else
+
+// -------------------------------------------------------------------
+//
+// Same as MurmurHash2, but endian- and alignment-neutral.
+// Half the speed though, alas.
+
+unsigned int MurmurHashNeutral2 ( const void * key, int len, unsigned int seed )
+{
+ const unsigned int m = 0x5bd1e995;
+ const int r = 24;
+
+ unsigned int h = seed ^ len;
+
+ const unsigned char * data = (const unsigned char *)key;
+
+ while(len >= 4)
+ {
+ unsigned int k;
+
+ k = data[0];
+ k |= data[1] << 8;
+ k |= data[2] << 16;
+ k |= data[3] << 24;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ data += 4;
+ len -= 4;
+ }
+
+ switch(len)
+ {
+ case 3: h ^= data[2] << 16; FALLTHROUGH_INTENDED;
+ case 2: h ^= data[1] << 8; FALLTHROUGH_INTENDED;
+ case 1: h ^= data[0];
+ h *= m;
+ };
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ return h;
+}
+
+#endif
diff --git a/storage/rocksdb/rocksdb/util/murmurhash.h b/storage/rocksdb/rocksdb/util/murmurhash.h
new file mode 100644
index 00000000..1dbb5739
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/murmurhash.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+/*
+ Murmurhash from http://sites.google.com/site/murmurhash/
+
+ All code is released to the public domain. For business purposes, Murmurhash
+ is under the MIT license.
+*/
+#pragma once
+#include <stdint.h>
+#include "rocksdb/slice.h"
+
+#if defined(__x86_64__)
+#define MURMUR_HASH MurmurHash64A
+uint64_t MurmurHash64A ( const void * key, int len, unsigned int seed );
+#define MurmurHash MurmurHash64A
+typedef uint64_t murmur_t;
+
+#elif defined(__i386__)
+#define MURMUR_HASH MurmurHash2
+unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed );
+#define MurmurHash MurmurHash2
+typedef unsigned int murmur_t;
+
+#else
+#define MURMUR_HASH MurmurHashNeutral2
+unsigned int MurmurHashNeutral2 ( const void * key, int len, unsigned int seed );
+#define MurmurHash MurmurHashNeutral2
+typedef unsigned int murmur_t;
+#endif
+
+// Allow slice to be hashable by murmur hash.
+namespace ROCKSDB_NAMESPACE {
+struct murmur_hash {
+ size_t operator()(const Slice& slice) const {
+ return MurmurHash(slice.data(), static_cast<int>(slice.size()), 0);
+ }
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/mutexlock.h b/storage/rocksdb/rocksdb/util/mutexlock.h
new file mode 100644
index 00000000..91ba4fda
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/mutexlock.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <assert.h>
+#include <atomic>
+#include <mutex>
+#include <thread>
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Helper class that locks a mutex on construction and unlocks the mutex when
+// the destructor of the MutexLock object is invoked.
+//
+// Typical usage:
+//
+// void MyClass::MyMethod() {
+// MutexLock l(&mu_); // mu_ is an instance variable
+// ... some complex code, possibly with multiple return paths ...
+// }
+
+class MutexLock {
+ public:
+ explicit MutexLock(port::Mutex *mu) : mu_(mu) {
+ this->mu_->Lock();
+ }
+ // No copying allowed
+ MutexLock(const MutexLock &) = delete;
+ void operator=(const MutexLock &) = delete;
+
+ ~MutexLock() { this->mu_->Unlock(); }
+
+ private:
+ port::Mutex *const mu_;
+};
+
+//
+// Acquire a ReadLock on the specified RWMutex.
+// The Lock will be automatically released then the
+// object goes out of scope.
+//
+class ReadLock {
+ public:
+ explicit ReadLock(port::RWMutex *mu) : mu_(mu) {
+ this->mu_->ReadLock();
+ }
+ // No copying allowed
+ ReadLock(const ReadLock &) = delete;
+ void operator=(const ReadLock &) = delete;
+
+ ~ReadLock() { this->mu_->ReadUnlock(); }
+
+ private:
+ port::RWMutex *const mu_;
+};
+
+//
+// Automatically unlock a locked mutex when the object is destroyed
+//
+class ReadUnlock {
+ public:
+ explicit ReadUnlock(port::RWMutex *mu) : mu_(mu) { mu->AssertHeld(); }
+ // No copying allowed
+ ReadUnlock(const ReadUnlock &) = delete;
+ ReadUnlock &operator=(const ReadUnlock &) = delete;
+
+ ~ReadUnlock() { mu_->ReadUnlock(); }
+
+ private:
+ port::RWMutex *const mu_;
+};
+
+//
+// Acquire a WriteLock on the specified RWMutex.
+// The Lock will be automatically released then the
+// object goes out of scope.
+//
+class WriteLock {
+ public:
+ explicit WriteLock(port::RWMutex *mu) : mu_(mu) {
+ this->mu_->WriteLock();
+ }
+ // No copying allowed
+ WriteLock(const WriteLock &) = delete;
+ void operator=(const WriteLock &) = delete;
+
+ ~WriteLock() { this->mu_->WriteUnlock(); }
+
+ private:
+ port::RWMutex *const mu_;
+};
+
+//
+// SpinMutex has very low overhead for low-contention cases. Method names
+// are chosen so you can use std::unique_lock or std::lock_guard with it.
+//
+class SpinMutex {
+ public:
+ SpinMutex() : locked_(false) {}
+
+ bool try_lock() {
+ auto currently_locked = locked_.load(std::memory_order_relaxed);
+ return !currently_locked &&
+ locked_.compare_exchange_weak(currently_locked, true,
+ std::memory_order_acquire,
+ std::memory_order_relaxed);
+ }
+
+ void lock() {
+ for (size_t tries = 0;; ++tries) {
+ if (try_lock()) {
+ // success
+ break;
+ }
+ port::AsmVolatilePause();
+ if (tries > 100) {
+ std::this_thread::yield();
+ }
+ }
+ }
+
+ void unlock() { locked_.store(false, std::memory_order_release); }
+
+ private:
+ std::atomic<bool> locked_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/ppc-opcode.h b/storage/rocksdb/rocksdb/util/ppc-opcode.h
new file mode 100644
index 00000000..5cc5af0e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/ppc-opcode.h
@@ -0,0 +1,27 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (c) 2017 International Business Machines Corp.
+// All rights reserved.
+// This source code is licensed under both the GPLv2 (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 __PPC_RA(a) (((a)&0x1f) << 16)
+#define __PPC_RB(b) (((b)&0x1f) << 11)
+#define __PPC_XA(a) ((((a)&0x1f) << 16) | (((a)&0x20) >> 3))
+#define __PPC_XB(b) ((((b)&0x1f) << 11) | (((b)&0x20) >> 4))
+#define __PPC_XS(s) ((((s)&0x1f) << 21) | (((s)&0x20) >> 5))
+#define __PPC_XT(s) __PPC_XS(s)
+#define VSX_XX3(t, a, b) (__PPC_XT(t) | __PPC_XA(a) | __PPC_XB(b))
+#define VSX_XX1(s, a, b) (__PPC_XS(s) | __PPC_RA(a) | __PPC_RB(b))
+
+#define PPC_INST_VPMSUMW 0x10000488
+#define PPC_INST_VPMSUMD 0x100004c8
+#define PPC_INST_MFVSRD 0x7c000066
+#define PPC_INST_MTVSRD 0x7c000166
+
+#define VPMSUMW(t, a, b) .long PPC_INST_VPMSUMW | VSX_XX3((t), a, b)
+#define VPMSUMD(t, a, b) .long PPC_INST_VPMSUMD | VSX_XX3((t), a, b)
+#define MFVRD(a, t) .long PPC_INST_MFVSRD | VSX_XX1((t) + 32, a, 0)
+#define MTVRD(t, a) .long PPC_INST_MTVSRD | VSX_XX1((t) + 32, a, 0)
diff --git a/storage/rocksdb/rocksdb/util/random.cc b/storage/rocksdb/rocksdb/util/random.cc
new file mode 100644
index 00000000..38c36def
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/random.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/random.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <thread>
+#include <utility>
+
+#include "port/likely.h"
+#include "util/thread_local.h"
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+#define STORAGE_DECL static __thread
+#else
+#define STORAGE_DECL static
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+Random* Random::GetTLSInstance() {
+ STORAGE_DECL Random* tls_instance;
+ STORAGE_DECL std::aligned_storage<sizeof(Random)>::type tls_instance_bytes;
+
+ auto rv = tls_instance;
+ if (UNLIKELY(rv == nullptr)) {
+ size_t seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ rv = new (&tls_instance_bytes) Random((uint32_t)seed);
+ tls_instance = rv;
+ }
+ return rv;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/random.h b/storage/rocksdb/rocksdb/util/random.h
new file mode 100644
index 00000000..f97b2126
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/random.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdint.h>
+#include <random>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A very simple random number generator. Not especially good at
+// generating truly random bits, but good enough for our needs in this
+// package.
+class Random {
+ private:
+ enum : uint32_t {
+ M = 2147483647L // 2^31-1
+ };
+ enum : uint64_t {
+ A = 16807 // bits 14, 8, 7, 5, 2, 1, 0
+ };
+
+ uint32_t seed_;
+
+ static uint32_t GoodSeed(uint32_t s) { return (s & M) != 0 ? (s & M) : 1; }
+
+ public:
+ // This is the largest value that can be returned from Next()
+ enum : uint32_t { kMaxNext = M };
+
+ explicit Random(uint32_t s) : seed_(GoodSeed(s)) {}
+
+ void Reset(uint32_t s) { seed_ = GoodSeed(s); }
+
+ uint32_t Next() {
+ // We are computing
+ // seed_ = (seed_ * A) % M, where M = 2^31-1
+ //
+ // seed_ must not be zero or M, or else all subsequent computed values
+ // will be zero or M respectively. For all other values, seed_ will end
+ // up cycling through every number in [1,M-1]
+ uint64_t product = seed_ * A;
+
+ // Compute (product % M) using the fact that ((x << 31) % M) == x.
+ seed_ = static_cast<uint32_t>((product >> 31) + (product & M));
+ // The first reduction may overflow by 1 bit, so we may need to
+ // repeat. mod == M is not possible; using > allows the faster
+ // sign-bit-based test.
+ if (seed_ > M) {
+ seed_ -= M;
+ }
+ return seed_;
+ }
+
+ // Returns a uniformly distributed value in the range [0..n-1]
+ // REQUIRES: n > 0
+ uint32_t Uniform(int n) { return Next() % n; }
+
+ // Randomly returns true ~"1/n" of the time, and false otherwise.
+ // REQUIRES: n > 0
+ bool OneIn(int n) { return Uniform(n) == 0; }
+
+ // "Optional" one-in-n, where 0 or negative always returns false
+ // (may or may not consume a random value)
+ bool OneInOpt(int n) { return n > 0 && OneIn(n); }
+
+ // Returns random bool that is true for the given percentage of
+ // calls on average. Zero or less is always false and 100 or more
+ // is always true (may or may not consume a random value)
+ bool PercentTrue(int percentage) {
+ return static_cast<int>(Uniform(100)) < percentage;
+ }
+
+ // Skewed: pick "base" uniformly from range [0,max_log] and then
+ // return "base" random bits. The effect is to pick a number in the
+ // range [0,2^max_log-1] with exponential bias towards smaller numbers.
+ uint32_t Skewed(int max_log) {
+ return Uniform(1 << Uniform(max_log + 1));
+ }
+
+ // Returns a Random instance for use by the current thread without
+ // additional locking
+ static Random* GetTLSInstance();
+};
+
+// A good 32-bit random number generator based on std::mt19937.
+// This exists in part to avoid compiler variance in warning about coercing
+// uint_fast32_t from mt19937 to uint32_t.
+class Random32 {
+ private:
+ std::mt19937 generator_;
+
+ public:
+ explicit Random32(uint32_t s) : generator_(s) {}
+
+ // Generates the next random number
+ uint32_t Next() { return static_cast<uint32_t>(generator_()); }
+
+ // Returns a uniformly distributed value in the range [0..n-1]
+ // REQUIRES: n > 0
+ uint32_t Uniform(uint32_t n) {
+ return static_cast<uint32_t>(
+ std::uniform_int_distribution<std::mt19937::result_type>(
+ 0, n - 1)(generator_));
+ }
+
+ // Returns an *almost* uniformly distributed value in the range [0..n-1].
+ // Much faster than Uniform().
+ // REQUIRES: n > 0
+ uint32_t Uniformish(uint32_t n) {
+ // fastrange (without the header)
+ return static_cast<uint32_t>((uint64_t(generator_()) * uint64_t(n)) >> 32);
+ }
+
+ // Randomly returns true ~"1/n" of the time, and false otherwise.
+ // REQUIRES: n > 0
+ bool OneIn(uint32_t n) { return Uniform(n) == 0; }
+
+ // Skewed: pick "base" uniformly from range [0,max_log] and then
+ // return "base" random bits. The effect is to pick a number in the
+ // range [0,2^max_log-1] with exponential bias towards smaller numbers.
+ uint32_t Skewed(int max_log) {
+ return Uniform(uint32_t{1} << Uniform(max_log + 1));
+ }
+
+ // Reset the seed of the generator to the given value
+ void Seed(uint32_t new_seed) { generator_.seed(new_seed); }
+};
+
+// A good 64-bit random number generator based on std::mt19937_64
+class Random64 {
+ private:
+ std::mt19937_64 generator_;
+
+ public:
+ explicit Random64(uint64_t s) : generator_(s) { }
+
+ // Generates the next random number
+ uint64_t Next() { return generator_(); }
+
+ // Returns a uniformly distributed value in the range [0..n-1]
+ // REQUIRES: n > 0
+ uint64_t Uniform(uint64_t n) {
+ return std::uniform_int_distribution<uint64_t>(0, n - 1)(generator_);
+ }
+
+ // Randomly returns true ~"1/n" of the time, and false otherwise.
+ // REQUIRES: n > 0
+ bool OneIn(uint64_t n) { return Uniform(n) == 0; }
+
+ // Skewed: pick "base" uniformly from range [0,max_log] and then
+ // return "base" random bits. The effect is to pick a number in the
+ // range [0,2^max_log-1] with exponential bias towards smaller numbers.
+ uint64_t Skewed(int max_log) {
+ return Uniform(uint64_t(1) << Uniform(max_log + 1));
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/random_test.cc b/storage/rocksdb/rocksdb/util/random_test.cc
new file mode 100644
index 00000000..ad520819
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/random_test.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <cstring>
+#include <vector>
+
+#include "test_util/testharness.h"
+#include "util/random.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<int> counts(range, 0);
+
+ for (int i = 0; i < range * average; ++i) {
+ ++counts.at(r.Uniform(range));
+ }
+ int max_variance = static_cast<int>(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<int>(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<int>(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) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/rate_limiter.cc b/storage/rocksdb/rocksdb/util/rate_limiter.cc
new file mode 100644
index 00000000..b1eefe62
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/rate_limiter.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/statistics.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "test_util/sync_point.h"
+#include "util/aligned_buffer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+size_t RateLimiter::RequestToken(size_t bytes, size_t alignment,
+ Env::IOPriority io_priority, Statistics* stats,
+ RateLimiter::OpType op_type) {
+ if (io_priority < Env::IO_TOTAL && IsRateLimited(op_type)) {
+ bytes = std::min(bytes, static_cast<size_t>(GetSingleBurstBytes()));
+
+ if (alignment > 0) {
+ // Here we may actually require more than burst and block
+ // but we can not write less than one page at a time on direct I/O
+ // thus we may want not to use ratelimiter
+ bytes = std::max(alignment, TruncateToPageBoundary(alignment, bytes));
+ }
+ Request(bytes, io_priority, stats, op_type);
+ }
+ return bytes;
+}
+
+// Pending request
+struct GenericRateLimiter::Req {
+ explicit Req(int64_t _bytes, port::Mutex* _mu)
+ : request_bytes(_bytes), bytes(_bytes), cv(_mu), granted(false) {}
+ int64_t request_bytes;
+ int64_t bytes;
+ port::CondVar cv;
+ bool granted;
+};
+
+GenericRateLimiter::GenericRateLimiter(int64_t rate_bytes_per_sec,
+ int64_t refill_period_us,
+ int32_t fairness, RateLimiter::Mode mode,
+ Env* env, bool auto_tuned)
+ : RateLimiter(mode),
+ refill_period_us_(refill_period_us),
+ rate_bytes_per_sec_(auto_tuned ? rate_bytes_per_sec / 2
+ : rate_bytes_per_sec),
+ refill_bytes_per_period_(
+ CalculateRefillBytesPerPeriod(rate_bytes_per_sec_)),
+ env_(env),
+ stop_(false),
+ exit_cv_(&request_mutex_),
+ requests_to_wait_(0),
+ available_bytes_(0),
+ next_refill_us_(NowMicrosMonotonic(env_)),
+ fairness_(fairness > 100 ? 100 : fairness),
+ rnd_((uint32_t)time(nullptr)),
+ leader_(nullptr),
+ auto_tuned_(auto_tuned),
+ num_drains_(0),
+ prev_num_drains_(0),
+ max_bytes_per_sec_(rate_bytes_per_sec),
+ tuned_time_(NowMicrosMonotonic(env_)) {
+ total_requests_[0] = 0;
+ total_requests_[1] = 0;
+ total_bytes_through_[0] = 0;
+ total_bytes_through_[1] = 0;
+}
+
+GenericRateLimiter::~GenericRateLimiter() {
+ MutexLock g(&request_mutex_);
+ stop_ = true;
+ requests_to_wait_ = static_cast<int32_t>(queue_[Env::IO_LOW].size() +
+ queue_[Env::IO_HIGH].size());
+ for (auto& r : queue_[Env::IO_HIGH]) {
+ r->cv.Signal();
+ }
+ for (auto& r : queue_[Env::IO_LOW]) {
+ r->cv.Signal();
+ }
+ while (requests_to_wait_ > 0) {
+ exit_cv_.Wait();
+ }
+}
+
+// This API allows user to dynamically change rate limiter's bytes per second.
+void GenericRateLimiter::SetBytesPerSecond(int64_t bytes_per_second) {
+ assert(bytes_per_second > 0);
+ rate_bytes_per_sec_ = bytes_per_second;
+ refill_bytes_per_period_.store(
+ CalculateRefillBytesPerPeriod(bytes_per_second),
+ std::memory_order_relaxed);
+}
+
+void GenericRateLimiter::Request(int64_t bytes, const Env::IOPriority pri,
+ Statistics* stats) {
+ assert(bytes <= refill_bytes_per_period_.load(std::memory_order_relaxed));
+ TEST_SYNC_POINT("GenericRateLimiter::Request");
+ TEST_SYNC_POINT_CALLBACK("GenericRateLimiter::Request:1",
+ &rate_bytes_per_sec_);
+ MutexLock g(&request_mutex_);
+
+ if (auto_tuned_) {
+ static const int kRefillsPerTune = 100;
+ std::chrono::microseconds now(NowMicrosMonotonic(env_));
+ if (now - tuned_time_ >=
+ kRefillsPerTune * std::chrono::microseconds(refill_period_us_)) {
+ Tune();
+ }
+ }
+
+ if (stop_) {
+ return;
+ }
+
+ ++total_requests_[pri];
+
+ if (available_bytes_ >= bytes) {
+ // Refill thread assigns quota and notifies requests waiting on
+ // the queue under mutex. So if we get here, that means nobody
+ // is waiting?
+ available_bytes_ -= bytes;
+ total_bytes_through_[pri] += bytes;
+ return;
+ }
+
+ // Request cannot be satisfied at this moment, enqueue
+ Req r(bytes, &request_mutex_);
+ queue_[pri].push_back(&r);
+
+ do {
+ bool timedout = false;
+ // Leader election, candidates can be:
+ // (1) a new incoming request,
+ // (2) a previous leader, whose quota has not been not assigned yet due
+ // to lower priority
+ // (3) a previous waiter at the front of queue, who got notified by
+ // previous leader
+ if (leader_ == nullptr &&
+ ((!queue_[Env::IO_HIGH].empty() &&
+ &r == queue_[Env::IO_HIGH].front()) ||
+ (!queue_[Env::IO_LOW].empty() &&
+ &r == queue_[Env::IO_LOW].front()))) {
+ leader_ = &r;
+ int64_t delta = next_refill_us_ - NowMicrosMonotonic(env_);
+ delta = delta > 0 ? delta : 0;
+ if (delta == 0) {
+ timedout = true;
+ } else {
+ int64_t wait_until = env_->NowMicros() + delta;
+ RecordTick(stats, NUMBER_RATE_LIMITER_DRAINS);
+ ++num_drains_;
+ timedout = r.cv.TimedWait(wait_until);
+ }
+ } else {
+ // Not at the front of queue or an leader has already been elected
+ r.cv.Wait();
+ }
+
+ // request_mutex_ is held from now on
+ if (stop_) {
+ --requests_to_wait_;
+ exit_cv_.Signal();
+ return;
+ }
+
+ // Make sure the waken up request is always the header of its queue
+ assert(r.granted ||
+ (!queue_[Env::IO_HIGH].empty() &&
+ &r == queue_[Env::IO_HIGH].front()) ||
+ (!queue_[Env::IO_LOW].empty() &&
+ &r == queue_[Env::IO_LOW].front()));
+ assert(leader_ == nullptr ||
+ (!queue_[Env::IO_HIGH].empty() &&
+ leader_ == queue_[Env::IO_HIGH].front()) ||
+ (!queue_[Env::IO_LOW].empty() &&
+ leader_ == queue_[Env::IO_LOW].front()));
+
+ if (leader_ == &r) {
+ // Waken up from TimedWait()
+ if (timedout) {
+ // Time to do refill!
+ Refill();
+
+ // Re-elect a new leader regardless. This is to simplify the
+ // election handling.
+ leader_ = nullptr;
+
+ // Notify the header of queue if current leader is going away
+ if (r.granted) {
+ // Current leader already got granted with quota. Notify header
+ // of waiting queue to participate next round of election.
+ assert((queue_[Env::IO_HIGH].empty() ||
+ &r != queue_[Env::IO_HIGH].front()) &&
+ (queue_[Env::IO_LOW].empty() ||
+ &r != queue_[Env::IO_LOW].front()));
+ if (!queue_[Env::IO_HIGH].empty()) {
+ queue_[Env::IO_HIGH].front()->cv.Signal();
+ } else if (!queue_[Env::IO_LOW].empty()) {
+ queue_[Env::IO_LOW].front()->cv.Signal();
+ }
+ // Done
+ break;
+ }
+ } else {
+ // Spontaneous wake up, need to continue to wait
+ assert(!r.granted);
+ leader_ = nullptr;
+ }
+ } else {
+ // Waken up by previous leader:
+ // (1) if requested quota is granted, it is done.
+ // (2) if requested quota is not granted, this means current thread
+ // was picked as a new leader candidate (previous leader got quota).
+ // It needs to participate leader election because a new request may
+ // come in before this thread gets waken up. So it may actually need
+ // to do Wait() again.
+ assert(!timedout);
+ }
+ } while (!r.granted);
+}
+
+void GenericRateLimiter::Refill() {
+ TEST_SYNC_POINT("GenericRateLimiter::Refill");
+ next_refill_us_ = NowMicrosMonotonic(env_) + refill_period_us_;
+ // Carry over the left over quota from the last period
+ auto refill_bytes_per_period =
+ refill_bytes_per_period_.load(std::memory_order_relaxed);
+ if (available_bytes_ < refill_bytes_per_period) {
+ available_bytes_ += refill_bytes_per_period;
+ }
+
+ int use_low_pri_first = rnd_.OneIn(fairness_) ? 0 : 1;
+ for (int q = 0; q < 2; ++q) {
+ auto use_pri = (use_low_pri_first == q) ? Env::IO_LOW : Env::IO_HIGH;
+ auto* queue = &queue_[use_pri];
+ while (!queue->empty()) {
+ auto* next_req = queue->front();
+ if (available_bytes_ < next_req->request_bytes) {
+ // avoid starvation
+ next_req->request_bytes -= available_bytes_;
+ available_bytes_ = 0;
+ break;
+ }
+ available_bytes_ -= next_req->request_bytes;
+ next_req->request_bytes = 0;
+ total_bytes_through_[use_pri] += next_req->bytes;
+ queue->pop_front();
+
+ next_req->granted = true;
+ if (next_req != leader_) {
+ // Quota granted, signal the thread
+ next_req->cv.Signal();
+ }
+ }
+ }
+}
+
+int64_t GenericRateLimiter::CalculateRefillBytesPerPeriod(
+ int64_t rate_bytes_per_sec) {
+ if (port::kMaxInt64 / rate_bytes_per_sec < refill_period_us_) {
+ // Avoid unexpected result in the overflow case. The result now is still
+ // inaccurate but is a number that is large enough.
+ return port::kMaxInt64 / 1000000;
+ } else {
+ return std::max(kMinRefillBytesPerPeriod,
+ rate_bytes_per_sec * refill_period_us_ / 1000000);
+ }
+}
+
+Status GenericRateLimiter::Tune() {
+ const int kLowWatermarkPct = 50;
+ const int kHighWatermarkPct = 90;
+ const int kAdjustFactorPct = 5;
+ // computed rate limit will be in
+ // `[max_bytes_per_sec_ / kAllowedRangeFactor, max_bytes_per_sec_]`.
+ const int kAllowedRangeFactor = 20;
+
+ std::chrono::microseconds prev_tuned_time = tuned_time_;
+ tuned_time_ = std::chrono::microseconds(NowMicrosMonotonic(env_));
+
+ int64_t elapsed_intervals = (tuned_time_ - prev_tuned_time +
+ std::chrono::microseconds(refill_period_us_) -
+ std::chrono::microseconds(1)) /
+ std::chrono::microseconds(refill_period_us_);
+ // We tune every kRefillsPerTune intervals, so the overflow and division-by-
+ // zero conditions should never happen.
+ assert(num_drains_ - prev_num_drains_ <= port::kMaxInt64 / 100);
+ assert(elapsed_intervals > 0);
+ int64_t drained_pct =
+ (num_drains_ - prev_num_drains_) * 100 / elapsed_intervals;
+
+ int64_t prev_bytes_per_sec = GetBytesPerSecond();
+ int64_t new_bytes_per_sec;
+ if (drained_pct == 0) {
+ new_bytes_per_sec = max_bytes_per_sec_ / kAllowedRangeFactor;
+ } else if (drained_pct < kLowWatermarkPct) {
+ // sanitize to prevent overflow
+ int64_t sanitized_prev_bytes_per_sec =
+ std::min(prev_bytes_per_sec, port::kMaxInt64 / 100);
+ new_bytes_per_sec =
+ std::max(max_bytes_per_sec_ / kAllowedRangeFactor,
+ sanitized_prev_bytes_per_sec * 100 / (100 + kAdjustFactorPct));
+ } else if (drained_pct > kHighWatermarkPct) {
+ // sanitize to prevent overflow
+ int64_t sanitized_prev_bytes_per_sec = std::min(
+ prev_bytes_per_sec, port::kMaxInt64 / (100 + kAdjustFactorPct));
+ new_bytes_per_sec =
+ std::min(max_bytes_per_sec_,
+ sanitized_prev_bytes_per_sec * (100 + kAdjustFactorPct) / 100);
+ } else {
+ new_bytes_per_sec = prev_bytes_per_sec;
+ }
+ if (new_bytes_per_sec != prev_bytes_per_sec) {
+ SetBytesPerSecond(new_bytes_per_sec);
+ }
+ num_drains_ = prev_num_drains_;
+ return Status::OK();
+}
+
+RateLimiter* NewGenericRateLimiter(
+ int64_t rate_bytes_per_sec, int64_t refill_period_us /* = 100 * 1000 */,
+ int32_t fairness /* = 10 */,
+ RateLimiter::Mode mode /* = RateLimiter::Mode::kWritesOnly */,
+ bool auto_tuned /* = false */) {
+ assert(rate_bytes_per_sec > 0);
+ assert(refill_period_us > 0);
+ assert(fairness > 0);
+ return new GenericRateLimiter(rate_bytes_per_sec, refill_period_us, fairness,
+ mode, Env::Default(), auto_tuned);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/rate_limiter.h b/storage/rocksdb/rocksdb/util/rate_limiter.h
new file mode 100644
index 00000000..5f047a56
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/rate_limiter.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <atomic>
+#include <chrono>
+#include <deque>
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/rate_limiter.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class GenericRateLimiter : public RateLimiter {
+ public:
+ GenericRateLimiter(int64_t refill_bytes, int64_t refill_period_us,
+ int32_t fairness, RateLimiter::Mode mode, Env* env,
+ bool auto_tuned);
+
+ virtual ~GenericRateLimiter();
+
+ // This API allows user to dynamically change rate limiter's bytes per second.
+ virtual void SetBytesPerSecond(int64_t bytes_per_second) override;
+
+ // Request for token to write bytes. If this request can not be satisfied,
+ // the call is blocked. Caller is responsible to make sure
+ // bytes <= GetSingleBurstBytes()
+ using RateLimiter::Request;
+ virtual void Request(const int64_t bytes, const Env::IOPriority pri,
+ Statistics* stats) override;
+
+ virtual int64_t GetSingleBurstBytes() const override {
+ return refill_bytes_per_period_.load(std::memory_order_relaxed);
+ }
+
+ virtual int64_t GetTotalBytesThrough(
+ const Env::IOPriority pri = Env::IO_TOTAL) const override {
+ MutexLock g(&request_mutex_);
+ if (pri == Env::IO_TOTAL) {
+ return total_bytes_through_[Env::IO_LOW] +
+ total_bytes_through_[Env::IO_HIGH];
+ }
+ return total_bytes_through_[pri];
+ }
+
+ virtual int64_t GetTotalRequests(
+ const Env::IOPriority pri = Env::IO_TOTAL) const override {
+ MutexLock g(&request_mutex_);
+ if (pri == Env::IO_TOTAL) {
+ return total_requests_[Env::IO_LOW] + total_requests_[Env::IO_HIGH];
+ }
+ return total_requests_[pri];
+ }
+
+ virtual int64_t GetBytesPerSecond() const override {
+ return rate_bytes_per_sec_;
+ }
+
+ private:
+ void Refill();
+ int64_t CalculateRefillBytesPerPeriod(int64_t rate_bytes_per_sec);
+ Status Tune();
+
+ uint64_t NowMicrosMonotonic(Env* env) {
+ return env->NowNanos() / std::milli::den;
+ }
+
+ // This mutex guard all internal states
+ mutable port::Mutex request_mutex_;
+
+ const int64_t kMinRefillBytesPerPeriod = 100;
+
+ const int64_t refill_period_us_;
+
+ int64_t rate_bytes_per_sec_;
+ // This variable can be changed dynamically.
+ std::atomic<int64_t> refill_bytes_per_period_;
+ Env* const env_;
+
+ bool stop_;
+ port::CondVar exit_cv_;
+ int32_t requests_to_wait_;
+
+ int64_t total_requests_[Env::IO_TOTAL];
+ int64_t total_bytes_through_[Env::IO_TOTAL];
+ int64_t available_bytes_;
+ int64_t next_refill_us_;
+
+ int32_t fairness_;
+ Random rnd_;
+
+ struct Req;
+ Req* leader_;
+ std::deque<Req*> queue_[Env::IO_TOTAL];
+
+ bool auto_tuned_;
+ int64_t num_drains_;
+ int64_t prev_num_drains_;
+ const int64_t max_bytes_per_sec_;
+ std::chrono::microseconds tuned_time_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/rate_limiter_test.cc b/storage/rocksdb/rocksdb/util/rate_limiter_test.cc
new file mode 100644
index 00000000..6ca91e9e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/rate_limiter_test.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <chrono>
+#include <cinttypes>
+#include <limits>
+
+#include "db/db_test_util.h"
+#include "rocksdb/env.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 {};
+
+TEST_F(RateLimiterTest, OverflowRate) {
+ GenericRateLimiter limiter(port::kMaxInt64, 1000, 10,
+ RateLimiter::Mode::kWritesOnly, Env::Default(),
+ false /* auto_tuned */);
+ ASSERT_GT(limiter.GetSingleBurstBytes(), 1000000000ll);
+}
+
+TEST_F(RateLimiterTest, StartStop) {
+ std::unique_ptr<RateLimiter> limiter(NewGenericRateLimiter(100, 100, 10));
+}
+
+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, Env::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));
+ }
+ }
+}
+
+#if !(defined(TRAVIS) && defined(OS_MACOSX))
+TEST_F(RateLimiterTest, Rate) {
+ auto* env = Env::Default();
+ struct Arg {
+ Arg(int32_t _target_rate, int _burst)
+ : limiter(NewGenericRateLimiter(_target_rate, 100 * 1000, 10)),
+ request_size(_target_rate / 10),
+ burst(_burst) {}
+ std::unique_ptr<RateLimiter> limiter;
+ int32_t request_size;
+ int burst;
+ };
+
+ auto writer = [](void* p) {
+ auto* thread_env = Env::Default();
+ auto* arg = static_cast<Arg*>(p);
+ // Test for 2 seconds
+ auto until = thread_env->NowMicros() + 2 * 1000000;
+ Random r((uint32_t)(thread_env->NowNanos() %
+ std::numeric_limits<uint32_t>::max()));
+ while (thread_env->NowMicros() < until) {
+ for (int i = 0; i < static_cast<int>(r.Skewed(arg->burst) + 1); ++i) {
+ arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1,
+ Env::IO_HIGH, nullptr /* stats */,
+ RateLimiter::OpType::kWrite);
+ }
+ arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1, Env::IO_LOW,
+ nullptr /* stats */, RateLimiter::OpType::kWrite);
+ }
+ };
+
+ 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);
+
+ ASSERT_GE(rate / target, 0.80);
+ ASSERT_LE(rate / target, 1.25);
+ }
+ }
+}
+#endif
+
+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<RateLimiter> _limiter)
+ : request_size(_request_size), pri(_pri), limiter(_limiter) {}
+ int32_t request_size;
+ Env::IOPriority pri;
+ std::shared_ptr<RateLimiter> limiter;
+ };
+
+ auto writer = [](void* p) {
+ auto* arg = static_cast<Arg*>(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<RateLimiter> limiter =
+ std::make_shared<GenericRateLimiter>(
+ target, refill_period, 10, RateLimiter::Mode::kWritesOnly,
+ Env::Default(), false /* auto_tuned */);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"GenericRateLimiter::Request",
+ "RateLimiterTest::LimitChangeTest:changeLimitStart"},
+ {"RateLimiterTest::LimitChangeTest:changeLimitEnd",
+ "GenericRateLimiter::Refill"}});
+ 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);
+ }
+ }
+}
+
+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());
+ special_env.no_slowdown_ = true;
+ special_env.time_elapse_only_sleep_ = true;
+
+ auto stats = CreateDBStatistics();
+ std::unique_ptr<RateLimiter> rate_limiter(new GenericRateLimiter(
+ 1000 /* rate_bytes_per_sec */,
+ std::chrono::microseconds(kTimePerRefill).count(), 10 /* fairness */,
+ RateLimiter::Mode::kWritesOnly, &special_env, true /* auto_tuned */));
+
+ // Use callback to advance time because we need to advance (1) after Request()
+ // has determined the bytes are not available; and (2) before Refill()
+ // computes the next refill time (ensuring refill time in the future allows
+ // the next request to drain the rate limiter).
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GenericRateLimiter::Refill", [&](void* /*arg*/) {
+ special_env.SleepForMicroseconds(static_cast<int>(
+ std::chrono::microseconds(kTimePerRefill).count()));
+ });
+ 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();
+
+ // 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<int>(
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/repeatable_thread.h b/storage/rocksdb/rocksdb/util/repeatable_thread.h
new file mode 100644
index 00000000..1ac8edee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/repeatable_thread.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <functional>
+#include <string>
+
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "test_util/mock_time_env.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Simple wrapper around port::Thread that supports calling a callback every
+// X seconds. If you pass in 0, then it will call your callback repeatedly
+// without delay.
+class RepeatableThread {
+ public:
+ RepeatableThread(std::function<void()> function,
+ const std::string& thread_name, Env* env, uint64_t delay_us,
+ uint64_t initial_delay_us = 0)
+ : function_(function),
+ thread_name_("rocksdb:" + thread_name),
+ env_(env),
+ delay_us_(delay_us),
+ initial_delay_us_(initial_delay_us),
+ mutex_(env),
+ cond_var_(&mutex_),
+ running_(true),
+#ifndef NDEBUG
+ waiting_(false),
+ run_count_(0),
+#endif
+ thread_([this] { thread(); }) {
+ }
+
+ void cancel() {
+ {
+ InstrumentedMutexLock l(&mutex_);
+ if (!running_) {
+ return;
+ }
+ running_ = false;
+ cond_var_.SignalAll();
+ }
+ thread_.join();
+ }
+
+ bool IsRunning() { return running_; }
+
+ ~RepeatableThread() { cancel(); }
+
+#ifndef NDEBUG
+ // Wait until RepeatableThread starting waiting, call the optional callback,
+ // then wait for one run of RepeatableThread. Tests can use provide a
+ // custom env object to mock time, and use the callback here to bump current
+ // time and trigger RepeatableThread. See repeatable_thread_test for example.
+ //
+ // Note: only support one caller of this method.
+ void TEST_WaitForRun(std::function<void()> callback = nullptr) {
+ InstrumentedMutexLock l(&mutex_);
+ while (!waiting_) {
+ cond_var_.Wait();
+ }
+ uint64_t prev_count = run_count_;
+ if (callback != nullptr) {
+ callback();
+ }
+ cond_var_.SignalAll();
+ while (!(run_count_ > prev_count)) {
+ cond_var_.Wait();
+ }
+ }
+#endif
+
+ private:
+ bool wait(uint64_t delay) {
+ InstrumentedMutexLock l(&mutex_);
+ if (running_ && delay > 0) {
+ uint64_t wait_until = env_->NowMicros() + delay;
+#ifndef NDEBUG
+ waiting_ = true;
+ cond_var_.SignalAll();
+#endif
+ while (running_) {
+ cond_var_.TimedWait(wait_until);
+ if (env_->NowMicros() >= wait_until) {
+ break;
+ }
+ }
+#ifndef NDEBUG
+ waiting_ = false;
+#endif
+ }
+ return running_;
+ }
+
+ void thread() {
+#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2, 12)
+ // Set thread name.
+ auto thread_handle = thread_.native_handle();
+ int ret __attribute__((__unused__)) =
+ pthread_setname_np(thread_handle, thread_name_.c_str());
+ assert(ret == 0);
+#endif
+#endif
+
+ assert(delay_us_ > 0);
+ if (!wait(initial_delay_us_)) {
+ return;
+ }
+ do {
+ function_();
+#ifndef NDEBUG
+ {
+ InstrumentedMutexLock l(&mutex_);
+ run_count_++;
+ cond_var_.SignalAll();
+ }
+#endif
+ } while (wait(delay_us_));
+ }
+
+ const std::function<void()> function_;
+ const std::string thread_name_;
+ Env* const env_;
+ const uint64_t delay_us_;
+ const uint64_t initial_delay_us_;
+
+ // Mutex lock should be held when accessing running_, waiting_
+ // and run_count_.
+ InstrumentedMutex mutex_;
+ InstrumentedCondVar cond_var_;
+ bool running_;
+#ifndef NDEBUG
+ // RepeatableThread waiting for timeout.
+ bool waiting_;
+ // Times function_ had run.
+ uint64_t run_count_;
+#endif
+ port::Thread thread_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/repeatable_thread_test.cc b/storage/rocksdb/rocksdb/util/repeatable_thread_test.cc
new file mode 100644
index 00000000..a18aa6cd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/repeatable_thread_test.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <memory>
+
+#include "db/db_test_util.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "util/repeatable_thread.h"
+
+class RepeatableThreadTest : public testing::Test {
+ public:
+ RepeatableThreadTest()
+ : mock_env_(new ROCKSDB_NAMESPACE::MockTimeEnv(
+ ROCKSDB_NAMESPACE::Env::Default())) {}
+
+ protected:
+ std::unique_ptr<ROCKSDB_NAMESPACE::MockTimeEnv> mock_env_;
+};
+
+TEST_F(RepeatableThreadTest, TimedTest) {
+ constexpr uint64_t kSecond = 1000000; // 1s = 1000000us
+ constexpr int kIteration = 3;
+ ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
+ ROCKSDB_NAMESPACE::port::Mutex mutex;
+ ROCKSDB_NAMESPACE::port::CondVar test_cv(&mutex);
+ int count = 0;
+ uint64_t prev_time = env->NowMicros();
+ ROCKSDB_NAMESPACE::RepeatableThread thread(
+ [&] {
+ ROCKSDB_NAMESPACE::MutexLock l(&mutex);
+ count++;
+ uint64_t now = env->NowMicros();
+ assert(count == 1 || prev_time + 1 * kSecond <= now);
+ prev_time = now;
+ if (count >= kIteration) {
+ test_cv.SignalAll();
+ }
+ },
+ "rt_test", env, 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_env_->set_current_time(0); // in seconds
+ std::atomic<int> 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_env->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_env->NowMicros will be greater than the current real
+ // time. However, 1000 seconds should be sufficient in most cases.
+ uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
+ if (time_us < mock_env_->RealNowMicros()) {
+ *reinterpret_cast<uint64_t*>(arg) = mock_env_->RealNowMicros() + 1000;
+ }
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+#endif // OS_MACOSX && !NDEBUG
+
+ ROCKSDB_NAMESPACE::RepeatableThread thread(
+ [&] { count++; }, "rt_test", mock_env_.get(), 1 * kSecond, 1 * kSecond);
+ for (int i = 1; i <= kIteration; i++) {
+ // Bump current time
+ thread.TEST_WaitForRun([&] { mock_env_->set_current_time(i); });
+ }
+ // Test function should be exectued exactly kIteraion times.
+ ASSERT_EQ(kIteration, count.load());
+
+ // Test cancel
+ thread.cancel();
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/set_comparator.h b/storage/rocksdb/rocksdb/util/set_comparator.h
new file mode 100644
index 00000000..9b5cfc1d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/set_comparator.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+namespace ROCKSDB_NAMESPACE {
+// A comparator to be used in std::set
+struct SetComparator {
+ explicit SetComparator() : user_comparator_(BytewiseComparator()) {}
+ explicit SetComparator(const Comparator* user_comparator)
+ : user_comparator_(user_comparator ? user_comparator
+ : BytewiseComparator()) {}
+ bool operator()(const Slice& lhs, const Slice& rhs) const {
+ return user_comparator_->Compare(lhs, rhs) < 0;
+ }
+
+ private:
+ const Comparator* user_comparator_;
+};
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/slice.cc b/storage/rocksdb/rocksdb/util/slice.cc
new file mode 100644
index 00000000..6db11cc9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/slice.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include "rocksdb/slice_transform.h"
+#include "rocksdb/slice.h"
+#include "util/string_util.h"
+#include <stdio.h>
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+class FixedPrefixTransform : public SliceTransform {
+ private:
+ size_t prefix_len_;
+ std::string name_;
+
+ public:
+ explicit FixedPrefixTransform(size_t prefix_len)
+ : prefix_len_(prefix_len),
+ // Note that if any part of the name format changes, it will require
+ // changes on options_helper in order to make RocksDBOptionsParser work
+ // for the new change.
+ // TODO(yhchiang): move serialization / deserializaion code inside
+ // the class implementation itself.
+ name_("rocksdb.FixedPrefix." + ToString(prefix_len_)) {}
+
+ const char* Name() const override { return name_.c_str(); }
+
+ Slice Transform(const Slice& src) const override {
+ assert(InDomain(src));
+ return Slice(src.data(), prefix_len_);
+ }
+
+ bool InDomain(const Slice& src) const override {
+ return (src.size() >= prefix_len_);
+ }
+
+ bool InRange(const Slice& dst) const override {
+ return (dst.size() == prefix_len_);
+ }
+
+ bool FullLengthEnabled(size_t* len) const override {
+ *len = prefix_len_;
+ return true;
+ }
+
+ bool SameResultWhenAppended(const Slice& prefix) const override {
+ return InDomain(prefix);
+ }
+};
+
+class CappedPrefixTransform : public SliceTransform {
+ private:
+ size_t cap_len_;
+ std::string name_;
+
+ public:
+ explicit CappedPrefixTransform(size_t cap_len)
+ : cap_len_(cap_len),
+ // Note that if any part of the name format changes, it will require
+ // changes on options_helper in order to make RocksDBOptionsParser work
+ // for the new change.
+ // TODO(yhchiang): move serialization / deserializaion code inside
+ // the class implementation itself.
+ name_("rocksdb.CappedPrefix." + ToString(cap_len_)) {}
+
+ const char* Name() const override { return name_.c_str(); }
+
+ Slice Transform(const Slice& src) const override {
+ assert(InDomain(src));
+ return Slice(src.data(), std::min(cap_len_, src.size()));
+ }
+
+ bool InDomain(const Slice& /*src*/) const override { return true; }
+
+ bool InRange(const Slice& dst) const override {
+ return (dst.size() <= cap_len_);
+ }
+
+ bool FullLengthEnabled(size_t* len) const override {
+ *len = cap_len_;
+ return true;
+ }
+
+ bool SameResultWhenAppended(const Slice& prefix) const override {
+ return prefix.size() >= cap_len_;
+ }
+};
+
+class NoopTransform : public SliceTransform {
+ public:
+ explicit NoopTransform() { }
+
+ const char* Name() const override { return "rocksdb.Noop"; }
+
+ Slice Transform(const Slice& src) const override { return src; }
+
+ bool InDomain(const Slice& /*src*/) const override { return true; }
+
+ bool InRange(const Slice& /*dst*/) const override { return true; }
+
+ bool SameResultWhenAppended(const Slice& /*prefix*/) const override {
+ return false;
+ }
+};
+
+}
+
+// 2 small internal utility functions, for efficient hex conversions
+// and no need for snprintf, toupper etc...
+// Originally from wdt/util/EncryptionUtils.cpp - for ToString(true)/DecodeHex:
+char toHex(unsigned char v) {
+ if (v <= 9) {
+ return '0' + v;
+ }
+ return 'A' + v - 10;
+}
+// most of the code is for validation/error check
+int fromHex(char c) {
+ // toupper:
+ if (c >= 'a' && c <= 'f') {
+ c -= ('a' - 'A'); // aka 0x20
+ }
+ // validation
+ if (c < '0' || (c > '9' && (c < 'A' || c > 'F'))) {
+ return -1; // invalid not 0-9A-F hex char
+ }
+ if (c <= '9') {
+ return c - '0';
+ }
+ return c - 'A' + 10;
+}
+
+Slice::Slice(const SliceParts& parts, std::string* buf) {
+ size_t length = 0;
+ for (int i = 0; i < parts.num_parts; ++i) {
+ length += parts.parts[i].size();
+ }
+ buf->reserve(length);
+
+ for (int i = 0; i < parts.num_parts; ++i) {
+ buf->append(parts.parts[i].data(), parts.parts[i].size());
+ }
+ data_ = buf->data();
+ size_ = buf->size();
+}
+
+// Return a string that contains the copy of the referenced data.
+std::string Slice::ToString(bool hex) const {
+ std::string result; // RVO/NRVO/move
+ if (hex) {
+ result.reserve(2 * size_);
+ for (size_t i = 0; i < size_; ++i) {
+ unsigned char c = data_[i];
+ result.push_back(toHex(c >> 4));
+ result.push_back(toHex(c & 0xf));
+ }
+ return result;
+ } else {
+ result.assign(data_, size_);
+ return result;
+ }
+}
+
+// Originally from rocksdb/utilities/ldb_cmd.h
+bool Slice::DecodeHex(std::string* result) const {
+ std::string::size_type len = size_;
+ if (len % 2) {
+ // Hex string must be even number of hex digits to get complete bytes back
+ return false;
+ }
+ if (!result) {
+ return false;
+ }
+ result->clear();
+ result->reserve(len / 2);
+
+ for (size_t i = 0; i < len;) {
+ int h1 = fromHex(data_[i++]);
+ if (h1 < 0) {
+ return false;
+ }
+ int h2 = fromHex(data_[i++]);
+ if (h2 < 0) {
+ return false;
+ }
+ result->push_back(static_cast<char>((h1 << 4) | h2));
+ }
+ return true;
+}
+
+const SliceTransform* NewFixedPrefixTransform(size_t prefix_len) {
+ return new FixedPrefixTransform(prefix_len);
+}
+
+const SliceTransform* NewCappedPrefixTransform(size_t cap_len) {
+ return new CappedPrefixTransform(cap_len);
+}
+
+const SliceTransform* NewNoopTransform() {
+ return new NoopTransform;
+}
+
+PinnableSlice::PinnableSlice(PinnableSlice&& other) {
+ *this = std::move(other);
+}
+
+PinnableSlice& PinnableSlice::operator=(PinnableSlice&& other) {
+ if (this != &other) {
+ Cleanable::Reset();
+ Cleanable::operator=(std::move(other));
+ size_ = other.size_;
+ pinned_ = other.pinned_;
+ if (pinned_) {
+ data_ = other.data_;
+ // When it's pinned, buf should no longer be of use.
+ } else {
+ if (other.buf_ == &other.self_space_) {
+ self_space_ = std::move(other.self_space_);
+ buf_ = &self_space_;
+ data_ = buf_->data();
+ } else {
+ buf_ = other.buf_;
+ data_ = other.data_;
+ }
+ }
+ other.self_space_.clear();
+ other.buf_ = &other.self_space_;
+ other.pinned_ = false;
+ other.PinSelf();
+ }
+ return *this;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/slice_test.cc b/storage/rocksdb/rocksdb/util/slice_test.cc
new file mode 100644
index 00000000..9e8a8e34
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/slice_test.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/slice.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Use this to keep track of the cleanups that were actually performed
+void Multiplier(void* arg1, void* arg2) {
+ int* res = reinterpret_cast<int*>(arg1);
+ int* num = reinterpret_cast<int*>(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);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/slice_transform_test.cc b/storage/rocksdb/rocksdb/util/slice_transform_test.cc
new file mode 100644
index 00000000..6550cade
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/slice_transform_test.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<const SliceTransform> 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<Iterator> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/status.cc b/storage/rocksdb/rocksdb/util/status.cc
new file mode 100644
index 00000000..3b1ffde5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/status.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/status.h"
+#include <stdio.h>
+#ifdef OS_WIN
+#include <string.h>
+#endif
+#include <cstring>
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const char* Status::CopyState(const char* state) {
+#ifdef OS_WIN
+ const size_t cch = std::strlen(state) + 1; // +1 for the null terminator
+ char* result = new char[cch];
+ errno_t ret
+#if defined(_MSC_VER)
+ ;
+#else
+ __attribute__((__unused__));
+#endif
+ ret = strncpy_s(result, cch, state, cch - 1);
+ result[cch - 1] = '\0';
+ assert(ret == 0);
+ return result;
+#else
+ const size_t cch = std::strlen(state) + 1; // +1 for the null terminator
+ return std::strncpy(new char[cch], state, cch);
+#endif
+}
+
+static const char* msgs[static_cast<int>(Status::kMaxSubCode)] = {
+ "", // kNone
+ "Timeout Acquiring Mutex", // kMutexTimeout
+ "Timeout waiting to lock key", // kLockTimeout
+ "Failed to acquire lock due to max_num_locks limit", // kLockLimit
+ "No space left on device", // kNoSpace
+ "Deadlock", // kDeadlock
+ "Stale file handle", // kStaleFile
+ "Memory limit reached", // kMemoryLimit
+ "Space limit reached", // kSpaceLimit
+ "No such file or directory", // kPathNotFound
+ // KMergeOperandsInsufficientCapacity
+ "Insufficient capacity for merge operands",
+ // kManualCompactionPaused
+ "Manual compaction paused",
+};
+
+Status::Status(Code _code, SubCode _subcode, const Slice& msg,
+ const Slice& msg2)
+ : code_(_code), subcode_(_subcode), sev_(kNoError) {
+ assert(code_ != kOk);
+ assert(subcode_ != kMaxSubCode);
+ const size_t len1 = msg.size();
+ const size_t len2 = msg2.size();
+ const size_t size = len1 + (len2 ? (2 + len2) : 0);
+ char* const result = new char[size + 1]; // +1 for null terminator
+ memcpy(result, msg.data(), len1);
+ if (len2) {
+ result[len1] = ':';
+ result[len1 + 1] = ' ';
+ memcpy(result + len1 + 2, msg2.data(), len2);
+ }
+ result[size] = '\0'; // null terminator for C style string
+ state_ = result;
+}
+
+std::string Status::ToString() const {
+ char tmp[30];
+ const char* type;
+ switch (code_) {
+ case kOk:
+ return "OK";
+ case kNotFound:
+ type = "NotFound: ";
+ break;
+ case kCorruption:
+ type = "Corruption: ";
+ break;
+ case kNotSupported:
+ type = "Not implemented: ";
+ break;
+ case kInvalidArgument:
+ type = "Invalid argument: ";
+ break;
+ case kIOError:
+ type = "IO error: ";
+ break;
+ case kMergeInProgress:
+ type = "Merge in progress: ";
+ break;
+ case kIncomplete:
+ type = "Result incomplete: ";
+ break;
+ case kShutdownInProgress:
+ type = "Shutdown in progress: ";
+ break;
+ case kTimedOut:
+ type = "Operation timed out: ";
+ break;
+ case kAborted:
+ type = "Operation aborted: ";
+ break;
+ case kBusy:
+ type = "Resource busy: ";
+ break;
+ case kExpired:
+ type = "Operation expired: ";
+ break;
+ case kTryAgain:
+ type = "Operation failed. Try again.: ";
+ break;
+ case kColumnFamilyDropped:
+ type = "Column family dropped: ";
+ break;
+ default:
+ snprintf(tmp, sizeof(tmp), "Unknown code(%d): ",
+ static_cast<int>(code()));
+ type = tmp;
+ break;
+ }
+ std::string result(type);
+ if (subcode_ != kNone) {
+ uint32_t index = static_cast<int32_t>(subcode_);
+ assert(sizeof(msgs) > index);
+ result.append(msgs[index]);
+ }
+
+ if (state_ != nullptr) {
+ result.append(state_);
+ }
+ return result;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/stderr_logger.h b/storage/rocksdb/rocksdb/util/stderr_logger.h
new file mode 100644
index 00000000..abf8f570
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/stderr_logger.h
@@ -0,0 +1,31 @@
+// 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).
+
+#pragma once
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Prints logs to stderr for faster debugging
+class StderrLogger : public Logger {
+ public:
+ explicit StderrLogger(const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL)
+ : Logger(log_level) {}
+
+ // Brings overloaded Logv()s into scope so they're not hidden when we override
+ // a subset of them.
+ using Logger::Logv;
+
+ virtual void Logv(const char* format, va_list ap) override {
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "\n");
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/stop_watch.h b/storage/rocksdb/rocksdb/util/stop_watch.h
new file mode 100644
index 00000000..ad490596
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/stop_watch.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/statistics.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+// Auto-scoped.
+// Records the measure time into the corresponding histogram if statistics
+// is not nullptr. It is also saved into *elapsed if the pointer is not nullptr
+// and overwrite is true, it will be added to *elapsed if overwrite is false.
+class StopWatch {
+ public:
+ StopWatch(Env* const env, Statistics* statistics, const uint32_t hist_type,
+ uint64_t* elapsed = nullptr, bool overwrite = true,
+ bool delay_enabled = false)
+ : env_(env),
+ statistics_(statistics),
+ hist_type_(hist_type),
+ elapsed_(elapsed),
+ overwrite_(overwrite),
+ stats_enabled_(statistics &&
+ statistics->get_stats_level() >=
+ StatsLevel::kExceptTimers &&
+ statistics->HistEnabledForType(hist_type)),
+ delay_enabled_(delay_enabled),
+ total_delay_(0),
+ delay_start_time_(0),
+ start_time_((stats_enabled_ || elapsed != nullptr) ? env->NowMicros()
+ : 0) {}
+
+ ~StopWatch() {
+ if (elapsed_) {
+ if (overwrite_) {
+ *elapsed_ = env_->NowMicros() - start_time_;
+ } else {
+ *elapsed_ += env_->NowMicros() - start_time_;
+ }
+ }
+ if (elapsed_ && delay_enabled_) {
+ *elapsed_ -= total_delay_;
+ }
+ if (stats_enabled_) {
+ statistics_->reportTimeToHistogram(
+ hist_type_, (elapsed_ != nullptr)
+ ? *elapsed_
+ : (env_->NowMicros() - start_time_));
+ }
+ }
+
+ void DelayStart() {
+ // if delay_start_time_ is not 0, it means we are already tracking delay,
+ // so delay_start_time_ should not be overwritten
+ if (elapsed_ && delay_enabled_ && delay_start_time_ == 0) {
+ delay_start_time_ = env_->NowMicros();
+ }
+ }
+
+ void DelayStop() {
+ if (elapsed_ && delay_enabled_ && delay_start_time_ != 0) {
+ total_delay_ += env_->NowMicros() - delay_start_time_;
+ }
+ // reset to 0 means currently no delay is being tracked, so two consecutive
+ // calls to DelayStop will not increase total_delay_
+ delay_start_time_ = 0;
+ }
+
+ uint64_t GetDelay() const { return delay_enabled_ ? total_delay_ : 0; }
+
+ uint64_t start_time() const { return start_time_; }
+
+ private:
+ Env* const env_;
+ Statistics* statistics_;
+ const uint32_t hist_type_;
+ uint64_t* elapsed_;
+ bool overwrite_;
+ bool stats_enabled_;
+ bool delay_enabled_;
+ uint64_t total_delay_;
+ uint64_t delay_start_time_;
+ const uint64_t start_time_;
+};
+
+// a nano second precision stopwatch
+class StopWatchNano {
+ public:
+ explicit StopWatchNano(Env* const env, bool auto_start = false)
+ : env_(env), start_(0) {
+ if (auto_start) {
+ Start();
+ }
+ }
+
+ void Start() { start_ = env_->NowNanos(); }
+
+ uint64_t ElapsedNanos(bool reset = false) {
+ auto now = env_->NowNanos();
+ auto elapsed = now - start_;
+ if (reset) {
+ start_ = now;
+ }
+ return elapsed;
+ }
+
+ uint64_t ElapsedNanosSafe(bool reset = false) {
+ return (env_ != nullptr) ? ElapsedNanos(reset) : 0U;
+ }
+
+ private:
+ Env* const env_;
+ uint64_t start_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/string_util.cc b/storage/rocksdb/rocksdb/util/string_util.cc
new file mode 100644
index 00000000..bb234f23
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/string_util.cc
@@ -0,0 +1,409 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/string_util.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+#include "port/port.h"
+#include "port/sys_time.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const std::string kNullptrString = "nullptr";
+
+std::vector<std::string> StringSplit(const std::string& arg, char delim) {
+ std::vector<std::string> splits;
+ std::stringstream ss(arg);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ splits.push_back(item);
+ }
+ return splits;
+}
+
+// for micros < 10ms, print "XX us".
+// for micros < 10sec, print "XX ms".
+// for micros >= 10 sec, print "XX sec".
+// for micros <= 1 hour, print Y:X M:S".
+// for micros > 1 hour, print Z:Y:X H:M:S".
+int AppendHumanMicros(uint64_t micros, char* output, int len,
+ bool fixed_format) {
+ if (micros < 10000 && !fixed_format) {
+ return snprintf(output, len, "%" PRIu64 " us", micros);
+ } else if (micros < 10000000 && !fixed_format) {
+ return snprintf(output, len, "%.3lf ms",
+ static_cast<double>(micros) / 1000);
+ } else if (micros < 1000000l * 60 && !fixed_format) {
+ return snprintf(output, len, "%.3lf sec",
+ static_cast<double>(micros) / 1000000);
+ } else if (micros < 1000000ll * 60 * 60 && !fixed_format) {
+ return snprintf(output, len, "%02" PRIu64 ":%05.3f M:S",
+ micros / 1000000 / 60,
+ static_cast<double>(micros % 60000000) / 1000000);
+ } else {
+ return snprintf(output, len, "%02" PRIu64 ":%02" PRIu64 ":%05.3f H:M:S",
+ micros / 1000000 / 3600, (micros / 1000000 / 60) % 60,
+ static_cast<double>(micros % 60000000) / 1000000);
+ }
+}
+
+// for sizes >=10TB, print "XXTB"
+// for sizes >=10GB, print "XXGB"
+// etc.
+// append file size summary to output and return the len
+int AppendHumanBytes(uint64_t bytes, char* output, int len) {
+ const uint64_t ull10 = 10;
+ if (bytes >= ull10 << 40) {
+ return snprintf(output, len, "%" PRIu64 "TB", bytes >> 40);
+ } else if (bytes >= ull10 << 30) {
+ return snprintf(output, len, "%" PRIu64 "GB", bytes >> 30);
+ } else if (bytes >= ull10 << 20) {
+ return snprintf(output, len, "%" PRIu64 "MB", bytes >> 20);
+ } else if (bytes >= ull10 << 10) {
+ return snprintf(output, len, "%" PRIu64 "KB", bytes >> 10);
+ } else {
+ return snprintf(output, len, "%" PRIu64 "B", bytes);
+ }
+}
+
+void AppendNumberTo(std::string* str, uint64_t num) {
+ char buf[30];
+ snprintf(buf, sizeof(buf), "%" PRIu64, num);
+ str->append(buf);
+}
+
+void AppendEscapedStringTo(std::string* str, const Slice& value) {
+ for (size_t i = 0; i < value.size(); i++) {
+ char c = value[i];
+ if (c >= ' ' && c <= '~') {
+ str->push_back(c);
+ } else {
+ char buf[10];
+ snprintf(buf, sizeof(buf), "\\x%02x",
+ static_cast<unsigned int>(c) & 0xff);
+ str->append(buf);
+ }
+ }
+}
+
+std::string NumberToString(uint64_t num) {
+ std::string r;
+ AppendNumberTo(&r, num);
+ return r;
+}
+
+std::string NumberToHumanString(int64_t num) {
+ char buf[19];
+ int64_t absnum = num < 0 ? -num : num;
+ if (absnum < 10000) {
+ snprintf(buf, sizeof(buf), "%" PRIi64, num);
+ } else if (absnum < 10000000) {
+ snprintf(buf, sizeof(buf), "%" PRIi64 "K", num / 1000);
+ } else if (absnum < 10000000000LL) {
+ snprintf(buf, sizeof(buf), "%" PRIi64 "M", num / 1000000);
+ } else {
+ snprintf(buf, sizeof(buf), "%" PRIi64 "G", num / 1000000000);
+ }
+ return std::string(buf);
+}
+
+std::string BytesToHumanString(uint64_t bytes) {
+ const char* size_name[] = {"KB", "MB", "GB", "TB"};
+ double final_size = static_cast<double>(bytes);
+ size_t size_idx;
+
+ // always start with KB
+ final_size /= 1024;
+ size_idx = 0;
+
+ while (size_idx < 3 && final_size >= 1024) {
+ final_size /= 1024;
+ size_idx++;
+ }
+
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%.2f %s", final_size, size_name[size_idx]);
+ return std::string(buf);
+}
+
+std::string TimeToHumanString(int unixtime) {
+ char time_buffer[80];
+ time_t rawtime = unixtime;
+ struct tm tInfo;
+ struct tm* timeinfo = localtime_r(&rawtime, &tInfo);
+ assert(timeinfo == &tInfo);
+ strftime(time_buffer, 80, "%c", timeinfo);
+ return std::string(time_buffer);
+}
+
+std::string EscapeString(const Slice& value) {
+ std::string r;
+ AppendEscapedStringTo(&r, value);
+ return r;
+}
+
+bool ConsumeDecimalNumber(Slice* in, uint64_t* val) {
+ uint64_t v = 0;
+ int digits = 0;
+ while (!in->empty()) {
+ char c = (*in)[0];
+ if (c >= '0' && c <= '9') {
+ ++digits;
+ const unsigned int delta = (c - '0');
+ static const uint64_t kMaxUint64 = ~static_cast<uint64_t>(0);
+ if (v > kMaxUint64 / 10 ||
+ (v == kMaxUint64 / 10 && delta > kMaxUint64 % 10)) {
+ // Overflow
+ return false;
+ }
+ v = (v * 10) + delta;
+ in->remove_prefix(1);
+ } else {
+ break;
+ }
+ }
+ *val = v;
+ return (digits > 0);
+}
+
+bool isSpecialChar(const char c) {
+ if (c == '\\' || c == '#' || c == ':' || c == '\r' || c == '\n') {
+ return true;
+ }
+ return false;
+}
+
+namespace {
+using CharMap = std::pair<char, char>;
+}
+
+char UnescapeChar(const char c) {
+ static const CharMap convert_map[] = {{'r', '\r'}, {'n', '\n'}};
+
+ auto iter = std::find_if(std::begin(convert_map), std::end(convert_map),
+ [c](const CharMap& p) { return p.first == c; });
+
+ if (iter == std::end(convert_map)) {
+ return c;
+ }
+ return iter->second;
+}
+
+char EscapeChar(const char c) {
+ static const CharMap convert_map[] = {{'\n', 'n'}, {'\r', 'r'}};
+
+ auto iter = std::find_if(std::begin(convert_map), std::end(convert_map),
+ [c](const CharMap& p) { return p.first == c; });
+
+ if (iter == std::end(convert_map)) {
+ return c;
+ }
+ return iter->second;
+}
+
+std::string EscapeOptionString(const std::string& raw_string) {
+ std::string output;
+ for (auto c : raw_string) {
+ if (isSpecialChar(c)) {
+ output += '\\';
+ output += EscapeChar(c);
+ } else {
+ output += c;
+ }
+ }
+
+ return output;
+}
+
+std::string UnescapeOptionString(const std::string& escaped_string) {
+ bool escaped = false;
+ std::string output;
+
+ for (auto c : escaped_string) {
+ if (escaped) {
+ output += UnescapeChar(c);
+ escaped = false;
+ } else {
+ if (c == '\\') {
+ escaped = true;
+ continue;
+ }
+ output += c;
+ }
+ }
+ return output;
+}
+
+std::string trim(const std::string& str) {
+ if (str.empty()) return std::string();
+ size_t start = 0;
+ size_t end = str.size() - 1;
+ while (isspace(str[start]) != 0 && start < end) {
+ ++start;
+ }
+ while (isspace(str[end]) != 0 && start < end) {
+ --end;
+ }
+ if (start <= end) {
+ return str.substr(start, end - start + 1);
+ }
+ return std::string();
+}
+
+#ifndef ROCKSDB_LITE
+
+bool ParseBoolean(const std::string& type, const std::string& value) {
+ if (value == "true" || value == "1") {
+ return true;
+ } else if (value == "false" || value == "0") {
+ return false;
+ }
+ throw std::invalid_argument(type);
+}
+
+uint32_t ParseUint32(const std::string& value) {
+ uint64_t num = ParseUint64(value);
+ if ((num >> 32LL) == 0) {
+ return static_cast<uint32_t>(num);
+ } else {
+ throw std::out_of_range(value);
+ }
+}
+
+int32_t ParseInt32(const std::string& value) {
+ int64_t num = ParseInt64(value);
+ if (num <= port::kMaxInt32 && num >= port::kMinInt32) {
+ return static_cast<int32_t>(num);
+ } else {
+ throw std::out_of_range(value);
+ }
+}
+
+#endif
+
+uint64_t ParseUint64(const std::string& value) {
+ size_t endchar;
+#ifndef CYGWIN
+ uint64_t num = std::stoull(value.c_str(), &endchar);
+#else
+ char* endptr;
+ uint64_t num = std::strtoul(value.c_str(), &endptr, 0);
+ endchar = endptr - value.c_str();
+#endif
+
+ if (endchar < value.length()) {
+ char c = value[endchar];
+ if (c == 'k' || c == 'K')
+ num <<= 10LL;
+ else if (c == 'm' || c == 'M')
+ num <<= 20LL;
+ else if (c == 'g' || c == 'G')
+ num <<= 30LL;
+ else if (c == 't' || c == 'T')
+ num <<= 40LL;
+ }
+
+ return num;
+}
+
+int64_t ParseInt64(const std::string& value) {
+ size_t endchar;
+#ifndef CYGWIN
+ int64_t num = std::stoll(value.c_str(), &endchar);
+#else
+ char* endptr;
+ int64_t num = std::strtoll(value.c_str(), &endptr, 0);
+ endchar = endptr - value.c_str();
+#endif
+
+ if (endchar < value.length()) {
+ char c = value[endchar];
+ if (c == 'k' || c == 'K')
+ num <<= 10LL;
+ else if (c == 'm' || c == 'M')
+ num <<= 20LL;
+ else if (c == 'g' || c == 'G')
+ num <<= 30LL;
+ else if (c == 't' || c == 'T')
+ num <<= 40LL;
+ }
+
+ return num;
+}
+
+int ParseInt(const std::string& value) {
+ size_t endchar;
+#ifndef CYGWIN
+ int num = std::stoi(value.c_str(), &endchar);
+#else
+ char* endptr;
+ int num = std::strtoul(value.c_str(), &endptr, 0);
+ endchar = endptr - value.c_str();
+#endif
+
+ if (endchar < value.length()) {
+ char c = value[endchar];
+ if (c == 'k' || c == 'K')
+ num <<= 10;
+ else if (c == 'm' || c == 'M')
+ num <<= 20;
+ else if (c == 'g' || c == 'G')
+ num <<= 30;
+ }
+
+ return num;
+}
+
+double ParseDouble(const std::string& value) {
+#ifndef CYGWIN
+ return std::stod(value);
+#else
+ return std::strtod(value.c_str(), 0);
+#endif
+}
+
+size_t ParseSizeT(const std::string& value) {
+ return static_cast<size_t>(ParseUint64(value));
+}
+
+std::vector<int> ParseVectorInt(const std::string& value) {
+ std::vector<int> result;
+ size_t start = 0;
+ while (start < value.size()) {
+ size_t end = value.find(':', start);
+ if (end == std::string::npos) {
+ result.push_back(ParseInt(value.substr(start)));
+ break;
+ } else {
+ result.push_back(ParseInt(value.substr(start, end - start)));
+ start = end + 1;
+ }
+ }
+ return result;
+}
+
+bool SerializeIntVector(const std::vector<int>& vec, std::string* value) {
+ *value = "";
+ for (size_t i = 0; i < vec.size(); ++i) {
+ if (i > 0) {
+ *value += ":";
+ }
+ *value += ToString(vec[i]);
+ }
+ return true;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/string_util.h b/storage/rocksdb/rocksdb/util/string_util.h
new file mode 100644
index 00000000..a761be66
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/string_util.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <sstream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "rocksdb/rocksdb_namespace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Slice;
+
+extern std::vector<std::string> StringSplit(const std::string& arg, char delim);
+
+template <typename T>
+inline std::string ToString(T value) {
+#if !(defined OS_ANDROID) && !(defined CYGWIN) && !(defined OS_FREEBSD)
+ return std::to_string(value);
+#else
+ // Andorid or cygwin doesn't support all of C++11, std::to_string() being
+ // one of the not supported features.
+ std::ostringstream os;
+ os << value;
+ return os.str();
+#endif
+}
+
+// Append a human-readable printout of "num" to *str
+extern void AppendNumberTo(std::string* str, uint64_t num);
+
+// Append a human-readable printout of "value" to *str.
+// Escapes any non-printable characters found in "value".
+extern void AppendEscapedStringTo(std::string* str, const Slice& value);
+
+// Return a string printout of "num"
+extern std::string NumberToString(uint64_t num);
+
+// Return a human-readable version of num.
+// for num >= 10.000, prints "xxK"
+// for num >= 10.000.000, prints "xxM"
+// for num >= 10.000.000.000, prints "xxG"
+extern std::string NumberToHumanString(int64_t num);
+
+// Return a human-readable version of bytes
+// ex: 1048576 -> 1.00 GB
+extern std::string BytesToHumanString(uint64_t bytes);
+
+// Return a human-readable version of unix time
+// ex: 1562116015 -> "Tue Jul 2 18:06:55 2019"
+extern std::string TimeToHumanString(int unixtime);
+
+// Append a human-readable time in micros.
+int AppendHumanMicros(uint64_t micros, char* output, int len,
+ bool fixed_format);
+
+// Append a human-readable size in bytes
+int AppendHumanBytes(uint64_t bytes, char* output, int len);
+
+// Return a human-readable version of "value".
+// Escapes any non-printable characters found in "value".
+extern std::string EscapeString(const Slice& value);
+
+// Parse a human-readable number from "*in" into *value. On success,
+// advances "*in" past the consumed number and sets "*val" to the
+// numeric value. Otherwise, returns false and leaves *in in an
+// unspecified state.
+extern bool ConsumeDecimalNumber(Slice* in, uint64_t* val);
+
+// Returns true if the input char "c" is considered as a special character
+// that will be escaped when EscapeOptionString() is called.
+//
+// @param c the input char
+// @return true if the input char "c" is considered as a special character.
+// @see EscapeOptionString
+bool isSpecialChar(const char c);
+
+// If the input char is an escaped char, it will return the its
+// associated raw-char. Otherwise, the function will simply return
+// the original input char.
+char UnescapeChar(const char c);
+
+// If the input char is a control char, it will return the its
+// associated escaped char. Otherwise, the function will simply return
+// the original input char.
+char EscapeChar(const char c);
+
+// Converts a raw string to an escaped string. Escaped-characters are
+// defined via the isSpecialChar() function. When a char in the input
+// string "raw_string" is classified as a special characters, then it
+// will be prefixed by '\' in the output.
+//
+// It's inverse function is UnescapeOptionString().
+// @param raw_string the input string
+// @return the '\' escaped string of the input "raw_string"
+// @see isSpecialChar, UnescapeOptionString
+std::string EscapeOptionString(const std::string& raw_string);
+
+// The inverse function of EscapeOptionString. It converts
+// an '\' escaped string back to a raw string.
+//
+// @param escaped_string the input '\' escaped string
+// @return the raw string of the input "escaped_string"
+std::string UnescapeOptionString(const std::string& escaped_string);
+
+std::string trim(const std::string& str);
+
+#ifndef ROCKSDB_LITE
+bool ParseBoolean(const std::string& type, const std::string& value);
+
+uint32_t ParseUint32(const std::string& value);
+
+int32_t ParseInt32(const std::string& value);
+#endif
+
+uint64_t ParseUint64(const std::string& value);
+
+int ParseInt(const std::string& value);
+
+int64_t ParseInt64(const std::string& value);
+
+double ParseDouble(const std::string& value);
+
+size_t ParseSizeT(const std::string& value);
+
+std::vector<int> ParseVectorInt(const std::string& value);
+
+bool SerializeIntVector(const std::vector<int>& vec, std::string* value);
+
+extern const std::string kNullptrString;
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/thread_list_test.cc b/storage/rocksdb/rocksdb/util/thread_list_test.cc
new file mode 100644
index 00000000..44c3ebc9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/thread_list_test.cc
@@ -0,0 +1,352 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <mutex>
+#include <condition_variable>
+
+#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<std::mutex> l(mutex_);
+ running_count_++;
+ 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<std::mutex> l(mutex_);
+ should_run_ = false;
+ bg_cv_.notify_all();
+ }
+
+ void WaitUntilScheduled(int job_count, Env* env) {
+ while (running_count_ < job_count) {
+ env->SleepForMicroseconds(1000);
+ }
+ }
+
+ void WaitUntilDone() {
+ std::unique_lock<std::mutex> l(mutex_);
+ while (running_count_ > 0) {
+ bg_cv_.wait(l);
+ }
+ }
+
+ static void DoSimulatedTask(void* arg) {
+ reinterpret_cast<SimulatedBackgroundTask*>(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<int> 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;
+ env->SetBackgroundThreads(kHighPriorityThreads, Env::HIGH);
+ env->SetBackgroundThreads(kLowPriorityThreads, Env::LOW);
+
+ SimulatedBackgroundTask running_task(
+ reinterpret_cast<void*>(1234), "running",
+ reinterpret_cast<void*>(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, env);
+
+ std::vector<ThreadStatus> thread_list;
+
+ // Verify the number of running threads in each pool.
+ 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]++;
+ }
+ }
+ 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();
+
+ // Verify none of the threads are running
+ 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<ThreadStatus>& 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<void*>(1234), "running",
+ reinterpret_cast<void*>(5678), "pikachu",
+ ThreadStatus::OP_FLUSH);
+
+ const int kCompactionWriteTasks = 4;
+ SimulatedBackgroundTask compaction_write_task(
+ reinterpret_cast<void*>(1234), "running",
+ reinterpret_cast<void*>(5678), "pikachu",
+ ThreadStatus::OP_COMPACTION);
+
+ const int kCompactionReadTasks = 5;
+ SimulatedBackgroundTask compaction_read_task(
+ reinterpret_cast<void*>(1234), "running",
+ reinterpret_cast<void*>(5678), "pikachu",
+ ThreadStatus::OP_COMPACTION);
+
+ const int kCompactionWaitTasks = 6;
+ SimulatedBackgroundTask compaction_wait_task(
+ reinterpret_cast<void*>(1234), "running",
+ reinterpret_cast<void*>(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, env);
+
+ for (int t = 0; t < kCompactionWriteTasks; ++t) {
+ env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask,
+ &compaction_write_task, Env::Priority::LOW);
+ }
+ compaction_write_task.WaitUntilScheduled(kCompactionWriteTasks, env);
+
+ for (int t = 0; t < kCompactionReadTasks; ++t) {
+ env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask,
+ &compaction_read_task, Env::Priority::LOW);
+ }
+ compaction_read_task.WaitUntilScheduled(kCompactionReadTasks, env);
+
+ for (int t = 0; t < kCompactionWaitTasks; ++t) {
+ env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask,
+ &compaction_wait_task, Env::Priority::LOW);
+ }
+ compaction_wait_task.WaitUntilScheduled(kCompactionWaitTasks, env);
+
+ // verify the thread-status
+ int operation_counts[ThreadStatus::NUM_OP_TYPES] = {0};
+ int state_counts[ThreadStatus::NUM_STATE_TYPES] = {0};
+
+ std::vector<ThreadStatus> thread_list;
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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) {
+ ::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/storage/rocksdb/rocksdb/util/thread_local.cc b/storage/rocksdb/rocksdb/util/thread_local.cc
new file mode 100644
index 00000000..9fa16f60
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/thread_local.cc
@@ -0,0 +1,554 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/thread_local.h"
+#include "util/mutexlock.h"
+#include "port/likely.h"
+#include <stdlib.h>
+
+namespace ROCKSDB_NAMESPACE {
+
+struct Entry {
+ Entry() : ptr(nullptr) {}
+ Entry(const Entry& e) : ptr(e.ptr.load(std::memory_order_relaxed)) {}
+ std::atomic<void*> ptr;
+};
+
+class StaticMeta;
+
+// This is the structure that is declared as "thread_local" storage.
+// The vector keep list of atomic pointer for all instances for "current"
+// thread. The vector is indexed by an Id that is unique in process and
+// associated with one ThreadLocalPtr instance. The Id is assigned by a
+// global StaticMeta singleton. So if we instantiated 3 ThreadLocalPtr
+// instances, each thread will have a ThreadData with a vector of size 3:
+// ---------------------------------------------------
+// | | instance 1 | instance 2 | instnace 3 |
+// ---------------------------------------------------
+// | thread 1 | void* | void* | void* | <- ThreadData
+// ---------------------------------------------------
+// | thread 2 | void* | void* | void* | <- ThreadData
+// ---------------------------------------------------
+// | thread 3 | void* | void* | void* | <- ThreadData
+// ---------------------------------------------------
+struct ThreadData {
+ explicit ThreadData(ThreadLocalPtr::StaticMeta* _inst)
+ : entries(),
+ next(nullptr),
+ prev(nullptr),
+ inst(_inst) {}
+ std::vector<Entry> entries;
+ ThreadData* next;
+ ThreadData* prev;
+ ThreadLocalPtr::StaticMeta* inst;
+};
+
+class ThreadLocalPtr::StaticMeta {
+public:
+ StaticMeta();
+
+ // Return the next available Id
+ uint32_t GetId();
+ // Return the next available Id without claiming it
+ uint32_t PeekId() const;
+ // Return the given Id back to the free pool. This also triggers
+ // UnrefHandler for associated pointer value (if not NULL) for all threads.
+ void ReclaimId(uint32_t id);
+
+ // Return the pointer value for the given id for the current thread.
+ void* Get(uint32_t id) const;
+ // Reset the pointer value for the given id for the current thread.
+ void Reset(uint32_t id, void* ptr);
+ // Atomically swap the supplied ptr and return the previous value
+ void* Swap(uint32_t id, void* ptr);
+ // Atomically compare and swap the provided value only if it equals
+ // to expected value.
+ bool CompareAndSwap(uint32_t id, void* ptr, void*& expected);
+ // Reset all thread local data to replacement, and return non-nullptr
+ // data for all existing threads
+ void Scrape(uint32_t id, autovector<void*>* ptrs, void* const replacement);
+ // Update res by applying func on each thread-local value. Holds a lock that
+ // prevents unref handler from running during this call, but clients must
+ // still provide external synchronization since the owning thread can
+ // access the values without internal locking, e.g., via Get() and Reset().
+ void Fold(uint32_t id, FoldFunc func, void* res);
+
+ // Register the UnrefHandler for id
+ void SetHandler(uint32_t id, UnrefHandler handler);
+
+ // protect inst, next_instance_id_, free_instance_ids_, head_,
+ // ThreadData.entries
+ //
+ // Note that here we prefer function static variable instead of the usual
+ // global static variable. The reason is that c++ destruction order of
+ // static variables in the reverse order of their construction order.
+ // However, C++ does not guarantee any construction order when global
+ // static variables are defined in different files, while the function
+ // static variables are initialized when their function are first called.
+ // As a result, the construction order of the function static variables
+ // can be controlled by properly invoke their first function calls in
+ // the right order.
+ //
+ // For instance, the following function contains a function static
+ // variable. We place a dummy function call of this inside
+ // Env::Default() to ensure the construction order of the construction
+ // order.
+ static port::Mutex* Mutex();
+
+ // Returns the member mutex of the current StaticMeta. In general,
+ // Mutex() should be used instead of this one. However, in case where
+ // the static variable inside Instance() goes out of scope, MemberMutex()
+ // should be used. One example is OnThreadExit() function.
+ port::Mutex* MemberMutex() { return &mutex_; }
+
+private:
+ // Get UnrefHandler for id with acquiring mutex
+ // REQUIRES: mutex locked
+ UnrefHandler GetHandler(uint32_t id);
+
+ // Triggered before a thread terminates
+ static void OnThreadExit(void* ptr);
+
+ // Add current thread's ThreadData to the global chain
+ // REQUIRES: mutex locked
+ void AddThreadData(ThreadData* d);
+
+ // Remove current thread's ThreadData from the global chain
+ // REQUIRES: mutex locked
+ void RemoveThreadData(ThreadData* d);
+
+ static ThreadData* GetThreadLocal();
+
+ uint32_t next_instance_id_;
+ // Used to recycle Ids in case ThreadLocalPtr is instantiated and destroyed
+ // frequently. This also prevents it from blowing up the vector space.
+ autovector<uint32_t> free_instance_ids_;
+ // Chain all thread local structure together. This is necessary since
+ // when one ThreadLocalPtr gets destroyed, we need to loop over each
+ // thread's version of pointer corresponding to that instance and
+ // call UnrefHandler for it.
+ ThreadData head_;
+
+ std::unordered_map<uint32_t, UnrefHandler> handler_map_;
+
+ // The private mutex. Developers should always use Mutex() instead of
+ // using this variable directly.
+ port::Mutex mutex_;
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+ // Thread local storage
+ static __thread ThreadData* tls_;
+#endif
+
+ // Used to make thread exit trigger possible if !defined(OS_MACOSX).
+ // Otherwise, used to retrieve thread data.
+ pthread_key_t pthread_key_;
+};
+
+
+#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
+__thread ThreadData* ThreadLocalPtr::StaticMeta::tls_ = nullptr;
+#endif
+
+// Windows doesn't support a per-thread destructor with its
+// TLS primitives. So, we build it manually by inserting a
+// function to be called on each thread's exit.
+// See http://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
+// and http://www.nynaeve.net/?p=183
+//
+// really we do this to have clear conscience since using TLS with thread-pools
+// is iffy
+// although OK within a request. But otherwise, threads have no identity in its
+// modern use.
+
+// This runs on windows only called from the System Loader
+#ifdef OS_WIN
+
+// Windows cleanup routine is invoked from a System Loader with a different
+// signature so we can not directly hookup the original OnThreadExit which is
+// private member
+// so we make StaticMeta class share with the us the address of the function so
+// we can invoke it.
+namespace wintlscleanup {
+
+// This is set to OnThreadExit in StaticMeta singleton constructor
+UnrefHandler thread_local_inclass_routine = nullptr;
+pthread_key_t thread_local_key = pthread_key_t (-1);
+
+// Static callback function to call with each thread termination.
+void NTAPI WinOnThreadExit(PVOID module, DWORD reason, PVOID reserved) {
+ // We decided to punt on PROCESS_EXIT
+ if (DLL_THREAD_DETACH == reason) {
+ if (thread_local_key != pthread_key_t(-1) &&
+ thread_local_inclass_routine != nullptr) {
+ void* tls = TlsGetValue(thread_local_key);
+ if (tls != nullptr) {
+ thread_local_inclass_routine(tls);
+ }
+ }
+ }
+}
+
+} // wintlscleanup
+
+// extern "C" suppresses C++ name mangling so we know the symbol name for the
+// linker /INCLUDE:symbol pragma above.
+extern "C" {
+
+#ifdef _MSC_VER
+// The linker must not discard thread_callback_on_exit. (We force a reference
+// to this variable with a linker /include:symbol pragma to ensure that.) If
+// this variable is discarded, the OnThreadExit function will never be called.
+#ifndef _X86_
+
+// .CRT section is merged with .rdata on x64 so it must be constant data.
+#pragma const_seg(".CRT$XLB")
+// When defining a const variable, it must have external linkage to be sure the
+// linker doesn't discard it.
+extern const PIMAGE_TLS_CALLBACK p_thread_callback_on_exit;
+const PIMAGE_TLS_CALLBACK p_thread_callback_on_exit =
+ wintlscleanup::WinOnThreadExit;
+// Reset the default section.
+#pragma const_seg()
+
+#pragma comment(linker, "/include:_tls_used")
+#pragma comment(linker, "/include:p_thread_callback_on_exit")
+
+#else // _X86_
+
+#pragma data_seg(".CRT$XLB")
+PIMAGE_TLS_CALLBACK p_thread_callback_on_exit = wintlscleanup::WinOnThreadExit;
+// Reset the default section.
+#pragma data_seg()
+
+#pragma comment(linker, "/INCLUDE:__tls_used")
+#pragma comment(linker, "/INCLUDE:_p_thread_callback_on_exit")
+
+#endif // _X86_
+
+#else
+// https://github.com/couchbase/gperftools/blob/master/src/windows/port.cc
+BOOL WINAPI DllMain(HINSTANCE h, DWORD dwReason, PVOID pv) {
+ if (dwReason == DLL_THREAD_DETACH)
+ wintlscleanup::WinOnThreadExit(h, dwReason, pv);
+ return TRUE;
+}
+#endif
+} // extern "C"
+
+#endif // OS_WIN
+
+void ThreadLocalPtr::InitSingletons() { ThreadLocalPtr::Instance(); }
+
+ThreadLocalPtr::StaticMeta* ThreadLocalPtr::Instance() {
+ // Here we prefer function static variable instead of global
+ // static variable as function static variable is initialized
+ // when the function is first call. As a result, we can properly
+ // control their construction order by properly preparing their
+ // first function call.
+ //
+ // Note that here we decide to make "inst" a static pointer w/o deleting
+ // it at the end instead of a static variable. This is to avoid the following
+ // destruction order disaster happens when a child thread using ThreadLocalPtr
+ // dies AFTER the main thread dies: When a child thread happens to use
+ // ThreadLocalPtr, it will try to delete its thread-local data on its
+ // OnThreadExit when the child thread dies. However, OnThreadExit depends
+ // on the following variable. As a result, if the main thread dies before any
+ // child thread happen to use ThreadLocalPtr dies, then the destruction of
+ // the following variable will go first, then OnThreadExit, therefore causing
+ // invalid access.
+ //
+ // The above problem can be solved by using thread_local to store tls_ instead
+ // of using __thread. The major difference between thread_local and __thread
+ // is that thread_local supports dynamic construction and destruction of
+ // non-primitive typed variables. As a result, we can guarantee the
+ // destruction order even when the main thread dies before any child threads.
+ // However, thread_local is not supported in all compilers that accept -std=c++11
+ // (e.g., eg Mac with XCode < 8. XCode 8+ supports thread_local).
+ static ThreadLocalPtr::StaticMeta* inst = new ThreadLocalPtr::StaticMeta();
+ return inst;
+}
+
+port::Mutex* ThreadLocalPtr::StaticMeta::Mutex() { return &Instance()->mutex_; }
+
+void ThreadLocalPtr::StaticMeta::OnThreadExit(void* ptr) {
+ auto* tls = static_cast<ThreadData*>(ptr);
+ assert(tls != nullptr);
+
+ // Use the cached StaticMeta::Instance() instead of directly calling
+ // the variable inside StaticMeta::Instance() might already go out of
+ // scope here in case this OnThreadExit is called after the main thread
+ // dies.
+ auto* inst = tls->inst;
+ pthread_setspecific(inst->pthread_key_, nullptr);
+
+ MutexLock l(inst->MemberMutex());
+ inst->RemoveThreadData(tls);
+ // Unref stored pointers of current thread from all instances
+ uint32_t id = 0;
+ for (auto& e : tls->entries) {
+ void* raw = e.ptr.load();
+ if (raw != nullptr) {
+ auto unref = inst->GetHandler(id);
+ if (unref != nullptr) {
+ unref(raw);
+ }
+ }
+ ++id;
+ }
+ // Delete thread local structure no matter if it is Mac platform
+ delete tls;
+}
+
+ThreadLocalPtr::StaticMeta::StaticMeta()
+ : next_instance_id_(0),
+ head_(this),
+ pthread_key_(0) {
+ if (pthread_key_create(&pthread_key_, &OnThreadExit) != 0) {
+ abort();
+ }
+
+ // OnThreadExit is not getting called on the main thread.
+ // Call through the static destructor mechanism to avoid memory leak.
+ //
+ // Caveats: ~A() will be invoked _after_ ~StaticMeta for the global
+ // singleton (destructors are invoked in reverse order of constructor
+ // _completion_); the latter must not mutate internal members. This
+ // cleanup mechanism inherently relies on use-after-release of the
+ // StaticMeta, and is brittle with respect to compiler-specific handling
+ // of memory backing destructed statically-scoped objects. Perhaps
+ // registering with atexit(3) would be more robust.
+ //
+// This is not required on Windows.
+#if !defined(OS_WIN)
+ static struct A {
+ ~A() {
+#ifndef ROCKSDB_SUPPORT_THREAD_LOCAL
+ ThreadData* tls_ =
+ static_cast<ThreadData*>(pthread_getspecific(Instance()->pthread_key_));
+#endif
+ if (tls_) {
+ OnThreadExit(tls_);
+ }
+ }
+ } a;
+#endif // !defined(OS_WIN)
+
+ head_.next = &head_;
+ head_.prev = &head_;
+
+#ifdef OS_WIN
+ // Share with Windows its cleanup routine and the key
+ wintlscleanup::thread_local_inclass_routine = OnThreadExit;
+ wintlscleanup::thread_local_key = pthread_key_;
+#endif
+}
+
+void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadData* d) {
+ Mutex()->AssertHeld();
+ d->next = &head_;
+ d->prev = head_.prev;
+ head_.prev->next = d;
+ head_.prev = d;
+}
+
+void ThreadLocalPtr::StaticMeta::RemoveThreadData(
+ ThreadData* d) {
+ Mutex()->AssertHeld();
+ d->next->prev = d->prev;
+ d->prev->next = d->next;
+ d->next = d->prev = d;
+}
+
+ThreadData* ThreadLocalPtr::StaticMeta::GetThreadLocal() {
+#ifndef ROCKSDB_SUPPORT_THREAD_LOCAL
+ // Make this local variable name look like a member variable so that we
+ // can share all the code below
+ ThreadData* tls_ =
+ static_cast<ThreadData*>(pthread_getspecific(Instance()->pthread_key_));
+#endif
+
+ if (UNLIKELY(tls_ == nullptr)) {
+ auto* inst = Instance();
+ tls_ = new ThreadData(inst);
+ {
+ // Register it in the global chain, needs to be done before thread exit
+ // handler registration
+ MutexLock l(Mutex());
+ inst->AddThreadData(tls_);
+ }
+ // Even it is not OS_MACOSX, need to register value for pthread_key_ so that
+ // its exit handler will be triggered.
+ if (pthread_setspecific(inst->pthread_key_, tls_) != 0) {
+ {
+ MutexLock l(Mutex());
+ inst->RemoveThreadData(tls_);
+ }
+ delete tls_;
+ abort();
+ }
+ }
+ return tls_;
+}
+
+void* ThreadLocalPtr::StaticMeta::Get(uint32_t id) const {
+ auto* tls = GetThreadLocal();
+ if (UNLIKELY(id >= tls->entries.size())) {
+ return nullptr;
+ }
+ return tls->entries[id].ptr.load(std::memory_order_acquire);
+}
+
+void ThreadLocalPtr::StaticMeta::Reset(uint32_t id, void* ptr) {
+ auto* tls = GetThreadLocal();
+ if (UNLIKELY(id >= tls->entries.size())) {
+ // Need mutex to protect entries access within ReclaimId
+ MutexLock l(Mutex());
+ tls->entries.resize(id + 1);
+ }
+ tls->entries[id].ptr.store(ptr, std::memory_order_release);
+}
+
+void* ThreadLocalPtr::StaticMeta::Swap(uint32_t id, void* ptr) {
+ auto* tls = GetThreadLocal();
+ if (UNLIKELY(id >= tls->entries.size())) {
+ // Need mutex to protect entries access within ReclaimId
+ MutexLock l(Mutex());
+ tls->entries.resize(id + 1);
+ }
+ return tls->entries[id].ptr.exchange(ptr, std::memory_order_acquire);
+}
+
+bool ThreadLocalPtr::StaticMeta::CompareAndSwap(uint32_t id, void* ptr,
+ void*& expected) {
+ auto* tls = GetThreadLocal();
+ if (UNLIKELY(id >= tls->entries.size())) {
+ // Need mutex to protect entries access within ReclaimId
+ MutexLock l(Mutex());
+ tls->entries.resize(id + 1);
+ }
+ return tls->entries[id].ptr.compare_exchange_strong(
+ expected, ptr, std::memory_order_release, std::memory_order_relaxed);
+}
+
+void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs,
+ void* const replacement) {
+ MutexLock l(Mutex());
+ for (ThreadData* t = head_.next; t != &head_; t = t->next) {
+ if (id < t->entries.size()) {
+ void* ptr =
+ t->entries[id].ptr.exchange(replacement, std::memory_order_acquire);
+ if (ptr != nullptr) {
+ ptrs->push_back(ptr);
+ }
+ }
+ }
+}
+
+void ThreadLocalPtr::StaticMeta::Fold(uint32_t id, FoldFunc func, void* res) {
+ MutexLock l(Mutex());
+ for (ThreadData* t = head_.next; t != &head_; t = t->next) {
+ if (id < t->entries.size()) {
+ void* ptr = t->entries[id].ptr.load();
+ if (ptr != nullptr) {
+ func(ptr, res);
+ }
+ }
+ }
+}
+
+uint32_t ThreadLocalPtr::TEST_PeekId() {
+ return Instance()->PeekId();
+}
+
+void ThreadLocalPtr::StaticMeta::SetHandler(uint32_t id, UnrefHandler handler) {
+ MutexLock l(Mutex());
+ handler_map_[id] = handler;
+}
+
+UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) {
+ Mutex()->AssertHeld();
+ auto iter = handler_map_.find(id);
+ if (iter == handler_map_.end()) {
+ return nullptr;
+ }
+ return iter->second;
+}
+
+uint32_t ThreadLocalPtr::StaticMeta::GetId() {
+ MutexLock l(Mutex());
+ if (free_instance_ids_.empty()) {
+ return next_instance_id_++;
+ }
+
+ uint32_t id = free_instance_ids_.back();
+ free_instance_ids_.pop_back();
+ return id;
+}
+
+uint32_t ThreadLocalPtr::StaticMeta::PeekId() const {
+ MutexLock l(Mutex());
+ if (!free_instance_ids_.empty()) {
+ return free_instance_ids_.back();
+ }
+ return next_instance_id_;
+}
+
+void ThreadLocalPtr::StaticMeta::ReclaimId(uint32_t id) {
+ // This id is not used, go through all thread local data and release
+ // corresponding value
+ MutexLock l(Mutex());
+ auto unref = GetHandler(id);
+ for (ThreadData* t = head_.next; t != &head_; t = t->next) {
+ if (id < t->entries.size()) {
+ void* ptr = t->entries[id].ptr.exchange(nullptr);
+ if (ptr != nullptr && unref != nullptr) {
+ unref(ptr);
+ }
+ }
+ }
+ handler_map_[id] = nullptr;
+ free_instance_ids_.push_back(id);
+}
+
+ThreadLocalPtr::ThreadLocalPtr(UnrefHandler handler)
+ : id_(Instance()->GetId()) {
+ if (handler != nullptr) {
+ Instance()->SetHandler(id_, handler);
+ }
+}
+
+ThreadLocalPtr::~ThreadLocalPtr() {
+ Instance()->ReclaimId(id_);
+}
+
+void* ThreadLocalPtr::Get() const {
+ return Instance()->Get(id_);
+}
+
+void ThreadLocalPtr::Reset(void* ptr) {
+ Instance()->Reset(id_, ptr);
+}
+
+void* ThreadLocalPtr::Swap(void* ptr) {
+ return Instance()->Swap(id_, ptr);
+}
+
+bool ThreadLocalPtr::CompareAndSwap(void* ptr, void*& expected) {
+ return Instance()->CompareAndSwap(id_, ptr, expected);
+}
+
+void ThreadLocalPtr::Scrape(autovector<void*>* ptrs, void* const replacement) {
+ Instance()->Scrape(id_, ptrs, replacement);
+}
+
+void ThreadLocalPtr::Fold(FoldFunc func, void* res) {
+ Instance()->Fold(id_, func, res);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/thread_local.h b/storage/rocksdb/rocksdb/util/thread_local.h
new file mode 100644
index 00000000..c4b762ab
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/thread_local.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <atomic>
+#include <functional>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "util/autovector.h"
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Cleanup function that will be called for a stored thread local
+// pointer (if not NULL) when one of the following happens:
+// (1) a thread terminates
+// (2) a ThreadLocalPtr is destroyed
+//
+// Warning: this function is called while holding a global mutex. The same mutex
+// is used (at least in some cases) by most methods of ThreadLocalPtr, and it's
+// shared across all instances of ThreadLocalPtr. Thereforere extra care
+// is needed to avoid deadlocks. In particular, the handler shouldn't lock any
+// mutexes and shouldn't call any methods of any ThreadLocalPtr instances,
+// unless you know what you're doing.
+typedef void (*UnrefHandler)(void* ptr);
+
+// ThreadLocalPtr stores only values of pointer type. Different from
+// the usual thread-local-storage, ThreadLocalPtr has the ability to
+// distinguish data coming from different threads and different
+// ThreadLocalPtr instances. For example, if a regular thread_local
+// variable A is declared in DBImpl, two DBImpl objects would share
+// the same A. However, a ThreadLocalPtr that is defined under the
+// scope of DBImpl can avoid such confliction. As a result, its memory
+// usage would be O(# of threads * # of ThreadLocalPtr instances).
+class ThreadLocalPtr {
+ public:
+ explicit ThreadLocalPtr(UnrefHandler handler = nullptr);
+
+ ThreadLocalPtr(const ThreadLocalPtr&) = delete;
+ ThreadLocalPtr& operator=(const ThreadLocalPtr&) = delete;
+
+ ~ThreadLocalPtr();
+
+ // Return the current pointer stored in thread local
+ void* Get() const;
+
+ // Set a new pointer value to the thread local storage.
+ void Reset(void* ptr);
+
+ // Atomically swap the supplied ptr and return the previous value
+ void* Swap(void* ptr);
+
+ // Atomically compare the stored value with expected. Set the new
+ // pointer value to thread local only if the comparison is true.
+ // Otherwise, expected returns the stored value.
+ // Return true on success, false on failure
+ bool CompareAndSwap(void* ptr, void*& expected);
+
+ // Reset all thread local data to replacement, and return non-nullptr
+ // data for all existing threads
+ void Scrape(autovector<void*>* ptrs, void* const replacement);
+
+ typedef std::function<void(void*, void*)> FoldFunc;
+ // Update res by applying func on each thread-local value. Holds a lock that
+ // prevents unref handler from running during this call, but clients must
+ // still provide external synchronization since the owning thread can
+ // access the values without internal locking, e.g., via Get() and Reset().
+ void Fold(FoldFunc func, void* res);
+
+ // Add here for testing
+ // Return the next available Id without claiming it
+ static uint32_t TEST_PeekId();
+
+ // Initialize the static singletons of the ThreadLocalPtr.
+ //
+ // If this function is not called, then the singletons will be
+ // automatically initialized when they are used.
+ //
+ // Calling this function twice or after the singletons have been
+ // initialized will be no-op.
+ static void InitSingletons();
+
+ class StaticMeta;
+
+private:
+
+ static StaticMeta* Instance();
+
+ const uint32_t id_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/thread_local_test.cc b/storage/rocksdb/rocksdb/util/thread_local_test.cc
new file mode 100644
index 00000000..b788b177
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/thread_local_test.cc
@@ -0,0 +1,580 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <thread>
+#include <atomic>
+#include <string>
+
+#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"
+#include "util/thread_local.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;
+
+ auto func = [](void* ptr) {
+ auto& params = *static_cast<Params*>(ptr);
+
+ ASSERT_TRUE(params.tls1.Get() == nullptr);
+ params.tls1.Reset(reinterpret_cast<int*>(1));
+ ASSERT_TRUE(params.tls1.Get() == reinterpret_cast<int*>(1));
+ params.tls1.Reset(reinterpret_cast<int*>(2));
+ ASSERT_TRUE(params.tls1.Get() == reinterpret_cast<int*>(2));
+
+ ASSERT_TRUE(params.tls2->Get() == nullptr);
+ params.tls2->Reset(reinterpret_cast<int*>(1));
+ ASSERT_TRUE(params.tls2->Get() == reinterpret_cast<int*>(1));
+ params.tls2->Reset(reinterpret_cast<int*>(2));
+ ASSERT_TRUE(params.tls2->Get() == reinterpret_cast<int*>(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 + 1u);
+ // Another new thread, read/write should not see value from previous thread
+ env_->StartThread(func, static_cast<void*>(&p));
+ mu.Lock();
+ while (p.completed != iter + 1) {
+ cv.Wait();
+ }
+ mu.Unlock();
+ ASSERT_EQ(IDChecker::PeekId(), base_id + 1u);
+ }
+}
+
+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<Params*>(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<size_t*>(own));
+ p.tls2->Reset(reinterpret_cast<size_t*>(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<size_t*>(own));
+ ASSERT_TRUE(p.tls2->Get() == reinterpret_cast<size_t*>(own + 1));
+ if (p.doWrite) {
+ p.tls1.Reset(reinterpret_cast<size_t*>(own));
+ p.tls2->Reset(reinterpret_cast<size_t*>(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_->StartThread(func, static_cast<void*>(&p1));
+ }
+ for (int th = 0; th < p2.total; ++th) {
+ env_->StartThread(func, static_cast<void*>(&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<Params*>(ptr);
+ p.mu->Lock();
+ ++(*p.unref);
+ p.mu->Unlock();
+ };
+
+ // Case 0: no unref triggered if ThreadLocalPtr is never accessed
+ auto func0 = [](void* ptr) {
+ auto& p = *static_cast<Params*>(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_->StartThread(func0, static_cast<void*>(&p));
+ }
+ env_->WaitForJoin();
+ ASSERT_EQ(unref_count, 0);
+ }
+
+ // Case 1: unref triggered by thread exit
+ auto func1 = [](void* ptr) {
+ auto& p = *static_cast<Params*>(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_->StartThread(func1, static_cast<void*>(&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 = [](void* ptr) {
+ auto& p = *static_cast<Params*>(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_->StartThread(func2, static_cast<void*>(&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<void*>(1));
+ ASSERT_EQ(reinterpret_cast<int64_t>(tls.Swap(nullptr)), 1);
+ ASSERT_TRUE(tls.Swap(reinterpret_cast<void*>(2)) == nullptr);
+ ASSERT_EQ(reinterpret_cast<int64_t>(tls.Get()), 2);
+ ASSERT_EQ(reinterpret_cast<int64_t>(tls.Swap(reinterpret_cast<void*>(3))), 2);
+}
+
+TEST_F(ThreadLocalTest, Scrape) {
+ auto unref = [](void* ptr) {
+ auto& p = *static_cast<Params*>(ptr);
+ p.mu->Lock();
+ ++(*p.unref);
+ p.mu->Unlock();
+ };
+
+ auto func = [](void* ptr) {
+ auto& p = *static_cast<Params*>(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_->StartThread(func, static_cast<void*>(&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<void*> 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<std::atomic<int64_t>*>(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<Params*>(ptr);
+ ASSERT_TRUE(p.tls1.Get() == nullptr);
+ p.tls1.Reset(new std::atomic<int64_t>(0));
+
+ for (int i = 0; i < kItersPerThread; ++i) {
+ static_cast<std::atomic<int64_t>*>(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, static_cast<void*>(&params));
+ }
+
+ // 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<int64_t*>(res);
+ *sum_ptr += static_cast<std::atomic<int64_t>*>(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<void*>(1)) == nullptr);
+ void* expected = reinterpret_cast<void*>(1);
+ // Swap in 2
+ ASSERT_TRUE(tls.CompareAndSwap(reinterpret_cast<void*>(2), expected));
+ expected = reinterpret_cast<void*>(100);
+ // Fail Swap, still 2
+ ASSERT_TRUE(!tls.CompareAndSwap(reinterpret_cast<void*>(2), expected));
+ ASSERT_EQ(expected, reinterpret_cast<void*>(2));
+ // Swap in 3
+ expected = reinterpret_cast<void*>(2);
+ ASSERT_TRUE(tls.CompareAndSwap(reinterpret_cast<void*>(3), expected));
+ ASSERT_EQ(tls.Get(), reinterpret_cast<void*>(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();
+
+#ifndef ROCKSDB_LITE
+ try {
+#endif // ROCKSDB_LITE
+ ROCKSDB_NAMESPACE::port::Thread th(&AccessThreadLocal, nullptr);
+ th.detach();
+ TEST_SYNC_POINT("MainThreadDiesFirst:End");
+#ifndef ROCKSDB_LITE
+ } catch (const std::system_error& ex) {
+ std::cerr << "Start thread: " << ex.code() << std::endl;
+ FAIL();
+ }
+#endif // ROCKSDB_LITE
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/util/thread_operation.h b/storage/rocksdb/rocksdb/util/thread_operation.h
new file mode 100644
index 00000000..b5d4b690
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/thread_operation.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 defines the structures for thread operation and state.
+// Thread operations are used to describe high level action of a
+// thread such as doing compaction or flush, while thread state
+// are used to describe lower-level action such as reading /
+// writing a file or waiting for a mutex. Operations and states
+// are designed to be independent. Typically, a thread usually involves
+// in one operation and one state at any specific point in time.
+
+#pragma once
+
+#include "rocksdb/thread_status.h"
+
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_USING_THREAD_STATUS
+
+// The structure that describes a major thread operation.
+struct OperationInfo {
+ const ThreadStatus::OperationType type;
+ const std::string name;
+};
+
+// The global operation table.
+//
+// When updating a status of a thread, the pointer of the OperationInfo
+// of the current ThreadStatusData will be pointing to one of the
+// rows in this global table.
+//
+// Note that it's not designed to be constant as in the future we
+// might consider adding global count to the OperationInfo.
+static OperationInfo global_operation_table[] = {
+ {ThreadStatus::OP_UNKNOWN, ""},
+ {ThreadStatus::OP_COMPACTION, "Compaction"},
+ {ThreadStatus::OP_FLUSH, "Flush"}
+};
+
+struct OperationStageInfo {
+ const ThreadStatus::OperationStage stage;
+ const std::string name;
+};
+
+// A table maintains the mapping from stage type to stage string.
+// Note that the string must be changed accordingly when the
+// associated function name changed.
+static OperationStageInfo global_op_stage_table[] = {
+ {ThreadStatus::STAGE_UNKNOWN, ""},
+ {ThreadStatus::STAGE_FLUSH_RUN,
+ "FlushJob::Run"},
+ {ThreadStatus::STAGE_FLUSH_WRITE_L0,
+ "FlushJob::WriteLevel0Table"},
+ {ThreadStatus::STAGE_COMPACTION_PREPARE,
+ "CompactionJob::Prepare"},
+ {ThreadStatus::STAGE_COMPACTION_RUN,
+ "CompactionJob::Run"},
+ {ThreadStatus::STAGE_COMPACTION_PROCESS_KV,
+ "CompactionJob::ProcessKeyValueCompaction"},
+ {ThreadStatus::STAGE_COMPACTION_INSTALL,
+ "CompactionJob::Install"},
+ {ThreadStatus::STAGE_COMPACTION_SYNC_FILE,
+ "CompactionJob::FinishCompactionOutputFile"},
+ {ThreadStatus::STAGE_PICK_MEMTABLES_TO_FLUSH,
+ "MemTableList::PickMemtablesToFlush"},
+ {ThreadStatus::STAGE_MEMTABLE_ROLLBACK,
+ "MemTableList::RollbackMemtableFlush"},
+ {ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS,
+ "MemTableList::TryInstallMemtableFlushResults"},
+};
+
+// The structure that describes a state.
+struct StateInfo {
+ const ThreadStatus::StateType type;
+ const std::string name;
+};
+
+// The global state table.
+//
+// When updating a status of a thread, the pointer of the StateInfo
+// of the current ThreadStatusData will be pointing to one of the
+// rows in this global table.
+static StateInfo global_state_table[] = {
+ {ThreadStatus::STATE_UNKNOWN, ""},
+ {ThreadStatus::STATE_MUTEX_WAIT, "Mutex Wait"},
+};
+
+struct OperationProperty {
+ int code;
+ std::string name;
+};
+
+static OperationProperty compaction_operation_properties[] = {
+ {ThreadStatus::COMPACTION_JOB_ID, "JobID"},
+ {ThreadStatus::COMPACTION_INPUT_OUTPUT_LEVEL, "InputOutputLevel"},
+ {ThreadStatus::COMPACTION_PROP_FLAGS, "Manual/Deletion/Trivial"},
+ {ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES, "TotalInputBytes"},
+ {ThreadStatus::COMPACTION_BYTES_READ, "BytesRead"},
+ {ThreadStatus::COMPACTION_BYTES_WRITTEN, "BytesWritten"},
+};
+
+static OperationProperty flush_operation_properties[] = {
+ {ThreadStatus::FLUSH_JOB_ID, "JobID"},
+ {ThreadStatus::FLUSH_BYTES_MEMTABLES, "BytesMemtables"},
+ {ThreadStatus::FLUSH_BYTES_WRITTEN, "BytesWritten"}
+};
+
+#else
+
+struct OperationInfo {
+};
+
+struct StateInfo {
+};
+
+#endif // ROCKSDB_USING_THREAD_STATUS
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/threadpool_imp.cc b/storage/rocksdb/rocksdb/util/threadpool_imp.cc
new file mode 100644
index 00000000..bf216c95
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/threadpool_imp.cc
@@ -0,0 +1,507 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/threadpool_imp.h"
+
+#include "monitoring/thread_status_util.h"
+#include "port/port.h"
+
+#ifndef OS_WIN
+# include <unistd.h>
+#endif
+
+#ifdef OS_LINUX
+# include <sys/syscall.h>
+# include <sys/resource.h>
+#endif
+
+#include <stdlib.h>
+#include <algorithm>
+#include <atomic>
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+namespace ROCKSDB_NAMESPACE {
+
+void ThreadPoolImpl::PthreadCall(const char* label, int result) {
+ if (result != 0) {
+ fprintf(stderr, "pthread %s: %s\n", label, strerror(result));
+ abort();
+ }
+}
+
+struct ThreadPoolImpl::Impl {
+
+ Impl();
+ ~Impl();
+
+ void JoinThreads(bool wait_for_jobs_to_complete);
+
+ void SetBackgroundThreadsInternal(int num, bool allow_reduce);
+ int GetBackgroundThreads();
+
+ unsigned int GetQueueLen() const {
+ return queue_len_.load(std::memory_order_relaxed);
+ }
+
+ void LowerIOPriority();
+
+ void LowerCPUPriority();
+
+ void WakeUpAllThreads() {
+ bgsignal_.notify_all();
+ }
+
+ void BGThread(size_t thread_id);
+
+ void StartBGThreads();
+
+ void Submit(std::function<void()>&& schedule,
+ std::function<void()>&& unschedule, void* tag);
+
+ int UnSchedule(void* arg);
+
+ void SetHostEnv(Env* env) { env_ = env; }
+
+ Env* GetHostEnv() const { return env_; }
+
+ bool HasExcessiveThread() const {
+ return static_cast<int>(bgthreads_.size()) > total_threads_limit_;
+ }
+
+ // Return true iff the current thread is the excessive thread to terminate.
+ // Always terminate the running thread that is added last, even if there are
+ // more than one thread to terminate.
+ bool IsLastExcessiveThread(size_t thread_id) const {
+ return HasExcessiveThread() && thread_id == bgthreads_.size() - 1;
+ }
+
+ bool IsExcessiveThread(size_t thread_id) const {
+ return static_cast<int>(thread_id) >= total_threads_limit_;
+ }
+
+ // Return the thread priority.
+ // This would allow its member-thread to know its priority.
+ Env::Priority GetThreadPriority() const { return priority_; }
+
+ // Set the thread priority.
+ void SetThreadPriority(Env::Priority priority) { priority_ = priority; }
+
+private:
+ static void BGThreadWrapper(void* arg);
+
+ bool low_io_priority_;
+ bool low_cpu_priority_;
+ Env::Priority priority_;
+ Env* env_;
+
+ int total_threads_limit_;
+ std::atomic_uint queue_len_; // Queue length. Used for stats reporting
+ bool exit_all_threads_;
+ bool wait_for_jobs_to_complete_;
+
+ // Entry per Schedule()/Submit() call
+ struct BGItem {
+ void* tag = nullptr;
+ std::function<void()> function;
+ std::function<void()> unschedFunction;
+ };
+
+ using BGQueue = std::deque<BGItem>;
+ BGQueue queue_;
+
+ std::mutex mu_;
+ std::condition_variable bgsignal_;
+ std::vector<port::Thread> bgthreads_;
+};
+
+
+inline
+ThreadPoolImpl::Impl::Impl()
+ :
+ low_io_priority_(false),
+ low_cpu_priority_(false),
+ priority_(Env::LOW),
+ env_(nullptr),
+ total_threads_limit_(0),
+ queue_len_(),
+ exit_all_threads_(false),
+ wait_for_jobs_to_complete_(false),
+ queue_(),
+ mu_(),
+ bgsignal_(),
+ bgthreads_() {
+}
+
+inline
+ThreadPoolImpl::Impl::~Impl() { assert(bgthreads_.size() == 0U); }
+
+void ThreadPoolImpl::Impl::JoinThreads(bool wait_for_jobs_to_complete) {
+
+ std::unique_lock<std::mutex> lock(mu_);
+ assert(!exit_all_threads_);
+
+ wait_for_jobs_to_complete_ = wait_for_jobs_to_complete;
+ exit_all_threads_ = true;
+ // prevent threads from being recreated right after they're joined, in case
+ // the user is concurrently submitting jobs.
+ total_threads_limit_ = 0;
+
+ lock.unlock();
+
+ bgsignal_.notify_all();
+
+ for (auto& th : bgthreads_) {
+ th.join();
+ }
+
+ bgthreads_.clear();
+
+ exit_all_threads_ = false;
+ wait_for_jobs_to_complete_ = false;
+}
+
+inline
+void ThreadPoolImpl::Impl::LowerIOPriority() {
+ std::lock_guard<std::mutex> lock(mu_);
+ low_io_priority_ = true;
+}
+
+inline
+void ThreadPoolImpl::Impl::LowerCPUPriority() {
+ std::lock_guard<std::mutex> lock(mu_);
+ low_cpu_priority_ = true;
+}
+
+void ThreadPoolImpl::Impl::BGThread(size_t thread_id) {
+ bool low_io_priority = false;
+ bool low_cpu_priority = false;
+
+ while (true) {
+ // Wait until there is an item that is ready to run
+ std::unique_lock<std::mutex> lock(mu_);
+ // Stop waiting if the thread needs to do work or needs to terminate.
+ while (!exit_all_threads_ && !IsLastExcessiveThread(thread_id) &&
+ (queue_.empty() || IsExcessiveThread(thread_id))) {
+ bgsignal_.wait(lock);
+ }
+
+ if (exit_all_threads_) { // mechanism to let BG threads exit safely
+
+ if (!wait_for_jobs_to_complete_ ||
+ queue_.empty()) {
+ break;
+ }
+ }
+
+ if (IsLastExcessiveThread(thread_id)) {
+ // Current thread is the last generated one and is excessive.
+ // We always terminate excessive thread in the reverse order of
+ // generation time.
+ auto& terminating_thread = bgthreads_.back();
+ terminating_thread.detach();
+ bgthreads_.pop_back();
+
+ if (HasExcessiveThread()) {
+ // There is still at least more excessive thread to terminate.
+ WakeUpAllThreads();
+ }
+ break;
+ }
+
+ auto func = std::move(queue_.front().function);
+ queue_.pop_front();
+
+ queue_len_.store(static_cast<unsigned int>(queue_.size()),
+ std::memory_order_relaxed);
+
+ bool decrease_io_priority = (low_io_priority != low_io_priority_);
+ bool decrease_cpu_priority = (low_cpu_priority != low_cpu_priority_);
+ lock.unlock();
+
+#ifdef OS_LINUX
+ if (decrease_cpu_priority) {
+ setpriority(
+ PRIO_PROCESS,
+ // Current thread.
+ 0,
+ // Lowest priority possible.
+ 19);
+ low_cpu_priority = true;
+ }
+
+ if (decrease_io_priority) {
+#define IOPRIO_CLASS_SHIFT (13)
+#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
+ // Put schedule into IOPRIO_CLASS_IDLE class (lowest)
+ // These system calls only have an effect when used in conjunction
+ // with an I/O scheduler that supports I/O priorities. As at
+ // kernel 2.6.17 the only such scheduler is the Completely
+ // Fair Queuing (CFQ) I/O scheduler.
+ // To change scheduler:
+ // echo cfq > /sys/block/<device_name>/queue/schedule
+ // Tunables to consider:
+ // /sys/block/<device_name>/queue/slice_idle
+ // /sys/block/<device_name>/queue/slice_sync
+ syscall(SYS_ioprio_set, 1, // IOPRIO_WHO_PROCESS
+ 0, // current thread
+ IOPRIO_PRIO_VALUE(3, 0));
+ low_io_priority = true;
+ }
+#else
+ (void)decrease_io_priority; // avoid 'unused variable' error
+ (void)decrease_cpu_priority;
+#endif
+ func();
+ }
+}
+
+// Helper struct for passing arguments when creating threads.
+struct BGThreadMetadata {
+ ThreadPoolImpl::Impl* thread_pool_;
+ size_t thread_id_; // Thread count in the thread.
+ BGThreadMetadata(ThreadPoolImpl::Impl* thread_pool, size_t thread_id)
+ : thread_pool_(thread_pool), thread_id_(thread_id) {}
+};
+
+void ThreadPoolImpl::Impl::BGThreadWrapper(void* arg) {
+ BGThreadMetadata* meta = reinterpret_cast<BGThreadMetadata*>(arg);
+ size_t thread_id = meta->thread_id_;
+ ThreadPoolImpl::Impl* tp = meta->thread_pool_;
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ // initialize it because compiler isn't good enough to see we don't use it
+ // uninitialized
+ ThreadStatus::ThreadType thread_type = ThreadStatus::NUM_THREAD_TYPES;
+ switch (tp->GetThreadPriority()) {
+ case Env::Priority::HIGH:
+ thread_type = ThreadStatus::HIGH_PRIORITY;
+ break;
+ case Env::Priority::LOW:
+ thread_type = ThreadStatus::LOW_PRIORITY;
+ break;
+ case Env::Priority::BOTTOM:
+ thread_type = ThreadStatus::BOTTOM_PRIORITY;
+ break;
+ case Env::Priority::USER:
+ thread_type = ThreadStatus::USER;
+ break;
+ case Env::Priority::TOTAL:
+ assert(false);
+ return;
+ }
+ assert(thread_type != ThreadStatus::NUM_THREAD_TYPES);
+ ThreadStatusUtil::RegisterThread(tp->GetHostEnv(), thread_type);
+#endif
+ delete meta;
+ tp->BGThread(thread_id);
+#ifdef ROCKSDB_USING_THREAD_STATUS
+ ThreadStatusUtil::UnregisterThread();
+#endif
+ return;
+}
+
+void ThreadPoolImpl::Impl::SetBackgroundThreadsInternal(int num,
+ bool allow_reduce) {
+ std::lock_guard<std::mutex> lock(mu_);
+ if (exit_all_threads_) {
+ return;
+ }
+ if (num > total_threads_limit_ ||
+ (num < total_threads_limit_ && allow_reduce)) {
+ total_threads_limit_ = std::max(0, num);
+ WakeUpAllThreads();
+ StartBGThreads();
+ }
+}
+
+int ThreadPoolImpl::Impl::GetBackgroundThreads() {
+ std::unique_lock<std::mutex> lock(mu_);
+ return total_threads_limit_;
+}
+
+void ThreadPoolImpl::Impl::StartBGThreads() {
+ // Start background thread if necessary
+ while ((int)bgthreads_.size() < total_threads_limit_) {
+
+ port::Thread p_t(&BGThreadWrapper,
+ new BGThreadMetadata(this, bgthreads_.size()));
+
+// Set the thread name to aid debugging
+#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2, 12)
+ auto th_handle = p_t.native_handle();
+ std::string thread_priority = Env::PriorityToString(GetThreadPriority());
+ std::ostringstream thread_name_stream;
+ thread_name_stream << "rocksdb:";
+ for (char c : thread_priority) {
+ thread_name_stream << static_cast<char>(tolower(c));
+ }
+ thread_name_stream << bgthreads_.size();
+ pthread_setname_np(th_handle, thread_name_stream.str().c_str());
+#endif
+#endif
+ bgthreads_.push_back(std::move(p_t));
+ }
+}
+
+void ThreadPoolImpl::Impl::Submit(std::function<void()>&& schedule,
+ std::function<void()>&& unschedule, void* tag) {
+
+ std::lock_guard<std::mutex> lock(mu_);
+
+ if (exit_all_threads_) {
+ return;
+ }
+
+ StartBGThreads();
+
+ // Add to priority queue
+ queue_.push_back(BGItem());
+
+ auto& item = queue_.back();
+ item.tag = tag;
+ item.function = std::move(schedule);
+ item.unschedFunction = std::move(unschedule);
+
+ queue_len_.store(static_cast<unsigned int>(queue_.size()),
+ std::memory_order_relaxed);
+
+ if (!HasExcessiveThread()) {
+ // Wake up at least one waiting thread.
+ bgsignal_.notify_one();
+ } else {
+ // Need to wake up all threads to make sure the one woken
+ // up is not the one to terminate.
+ WakeUpAllThreads();
+ }
+}
+
+int ThreadPoolImpl::Impl::UnSchedule(void* arg) {
+ int count = 0;
+
+ std::vector<std::function<void()>> candidates;
+ {
+ std::lock_guard<std::mutex> lock(mu_);
+
+ // Remove from priority queue
+ BGQueue::iterator it = queue_.begin();
+ while (it != queue_.end()) {
+ if (arg == (*it).tag) {
+ if (it->unschedFunction) {
+ candidates.push_back(std::move(it->unschedFunction));
+ }
+ it = queue_.erase(it);
+ count++;
+ } else {
+ ++it;
+ }
+ }
+ queue_len_.store(static_cast<unsigned int>(queue_.size()),
+ std::memory_order_relaxed);
+ }
+
+
+ // Run unschedule functions outside the mutex
+ for (auto& f : candidates) {
+ f();
+ }
+
+ return count;
+}
+
+ThreadPoolImpl::ThreadPoolImpl() :
+ impl_(new Impl()) {
+}
+
+
+ThreadPoolImpl::~ThreadPoolImpl() {
+}
+
+void ThreadPoolImpl::JoinAllThreads() {
+ impl_->JoinThreads(false);
+}
+
+void ThreadPoolImpl::SetBackgroundThreads(int num) {
+ impl_->SetBackgroundThreadsInternal(num, true);
+}
+
+int ThreadPoolImpl::GetBackgroundThreads() {
+ return impl_->GetBackgroundThreads();
+}
+
+unsigned int ThreadPoolImpl::GetQueueLen() const {
+ return impl_->GetQueueLen();
+}
+
+void ThreadPoolImpl::WaitForJobsAndJoinAllThreads() {
+ impl_->JoinThreads(true);
+}
+
+void ThreadPoolImpl::LowerIOPriority() {
+ impl_->LowerIOPriority();
+}
+
+void ThreadPoolImpl::LowerCPUPriority() {
+ impl_->LowerCPUPriority();
+}
+
+void ThreadPoolImpl::IncBackgroundThreadsIfNeeded(int num) {
+ impl_->SetBackgroundThreadsInternal(num, false);
+}
+
+void ThreadPoolImpl::SubmitJob(const std::function<void()>& job) {
+ auto copy(job);
+ impl_->Submit(std::move(copy), std::function<void()>(), nullptr);
+}
+
+
+void ThreadPoolImpl::SubmitJob(std::function<void()>&& job) {
+ impl_->Submit(std::move(job), std::function<void()>(), nullptr);
+}
+
+void ThreadPoolImpl::Schedule(void(*function)(void* arg1), void* arg,
+ void* tag, void(*unschedFunction)(void* arg)) {
+ if (unschedFunction == nullptr) {
+ impl_->Submit(std::bind(function, arg), std::function<void()>(), tag);
+ } else {
+ impl_->Submit(std::bind(function, arg), std::bind(unschedFunction, arg),
+ tag);
+ }
+}
+
+int ThreadPoolImpl::UnSchedule(void* arg) {
+ return impl_->UnSchedule(arg);
+}
+
+void ThreadPoolImpl::SetHostEnv(Env* env) { impl_->SetHostEnv(env); }
+
+Env* ThreadPoolImpl::GetHostEnv() const { return impl_->GetHostEnv(); }
+
+// Return the thread priority.
+// This would allow its member-thread to know its priority.
+Env::Priority ThreadPoolImpl::GetThreadPriority() const {
+ return impl_->GetThreadPriority();
+}
+
+// Set the thread priority.
+void ThreadPoolImpl::SetThreadPriority(Env::Priority priority) {
+ impl_->SetThreadPriority(priority);
+}
+
+ThreadPool* NewThreadPool(int num_threads) {
+ ThreadPoolImpl* thread_pool = new ThreadPoolImpl();
+ thread_pool->SetBackgroundThreads(num_threads);
+ return thread_pool;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/threadpool_imp.h b/storage/rocksdb/rocksdb/util/threadpool_imp.h
new file mode 100644
index 00000000..e3c12577
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/threadpool_imp.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "rocksdb/threadpool.h"
+#include "rocksdb/env.h"
+
+#include <memory>
+#include <functional>
+
+namespace ROCKSDB_NAMESPACE {
+
+class ThreadPoolImpl : public ThreadPool {
+ public:
+ ThreadPoolImpl();
+ ~ThreadPoolImpl();
+
+ ThreadPoolImpl(ThreadPoolImpl&&) = delete;
+ ThreadPoolImpl& operator=(ThreadPoolImpl&&) = delete;
+
+ // Implement ThreadPool interfaces
+
+ // Wait for all threads to finish.
+ // Discards all the jobs that did not
+ // start executing and waits for those running
+ // to complete
+ void JoinAllThreads() override;
+
+ // Set the number of background threads that will be executing the
+ // scheduled jobs.
+ void SetBackgroundThreads(int num) override;
+ int GetBackgroundThreads() override;
+
+ // Get the number of jobs scheduled in the ThreadPool queue.
+ unsigned int GetQueueLen() const override;
+
+ // Waits for all jobs to complete those
+ // that already started running and those that did not
+ // start yet
+ void WaitForJobsAndJoinAllThreads() override;
+
+ // Make threads to run at a lower kernel IO priority
+ // Currently only has effect on Linux
+ void LowerIOPriority();
+
+ // Make threads to run at a lower kernel CPU priority
+ // Currently only has effect on Linux
+ void LowerCPUPriority();
+
+ // Ensure there is at aleast num threads in the pool
+ // but do not kill threads if there are more
+ void IncBackgroundThreadsIfNeeded(int num);
+
+ // Submit a fire and forget job
+ // These jobs can not be unscheduled
+
+ // This allows to submit the same job multiple times
+ void SubmitJob(const std::function<void()>&) override;
+ // This moves the function in for efficiency
+ void SubmitJob(std::function<void()>&&) override;
+
+ // Schedule a job with an unschedule tag and unschedule function
+ // Can be used to filter and unschedule jobs by a tag
+ // that are still in the queue and did not start running
+ void Schedule(void (*function)(void* arg1), void* arg, void* tag,
+ void (*unschedFunction)(void* arg));
+
+ // Filter jobs that are still in a queue and match
+ // the given tag. Remove them from a queue if any
+ // and for each such job execute an unschedule function
+ // if such was given at scheduling time.
+ int UnSchedule(void* tag);
+
+ void SetHostEnv(Env* env);
+
+ Env* GetHostEnv() const;
+
+ // Return the thread priority.
+ // This would allow its member-thread to know its priority.
+ Env::Priority GetThreadPriority() const;
+
+ // Set the thread priority.
+ void SetThreadPriority(Env::Priority priority);
+
+ static void PthreadCall(const char* label, int result);
+
+ struct Impl;
+
+ private:
+
+ // Current public virtual interface does not provide usable
+ // functionality and thus can not be used internally to
+ // facade different implementations.
+ //
+ // We propose a pimpl idiom in order to easily replace the thread pool impl
+ // w/o touching the header file but providing a different .cc potentially
+ // CMake option driven.
+ //
+ // Another option is to introduce a Env::MakeThreadPool() virtual interface
+ // and override the environment. This would require refactoring ThreadPool usage.
+ //
+ // We can also combine these two approaches
+ std::unique_ptr<Impl> impl_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/timer_queue.h b/storage/rocksdb/rocksdb/util/timer_queue.h
new file mode 100644
index 00000000..3bd51753
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/timer_queue.h
@@ -0,0 +1,230 @@
+// 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.
+
+#pragma once
+
+#include <assert.h>
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <queue>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "port/port.h"
+#include "test_util/sync_point.h"
+
+// Allows execution of handlers at a specified time in the future
+// Guarantees:
+// - All handlers are executed ONCE, even if cancelled (aborted parameter will
+// be set to true)
+// - If TimerQueue is destroyed, it will cancel all handlers.
+// - Handlers are ALWAYS executed in the Timer Queue worker thread.
+// - Handlers execution order is NOT guaranteed
+//
+////////////////////////////////////////////////////////////////////////////////
+// borrowed from
+// http://www.crazygaze.com/blog/2016/03/24/portable-c-timer-queue/
+class TimerQueue {
+ public:
+ TimerQueue() : m_th(&TimerQueue::run, this) {}
+
+ ~TimerQueue() { shutdown(); }
+
+ // This function is not thread-safe.
+ void shutdown() {
+ if (closed_) {
+ return;
+ }
+ cancelAll();
+ // Abusing the timer queue to trigger the shutdown.
+ add(0, [this](bool) {
+ m_finish = true;
+ return std::make_pair(false, 0);
+ });
+ m_th.join();
+ closed_ = true;
+ }
+
+ // Adds a new timer
+ // \return
+ // Returns the ID of the new timer. You can use this ID to cancel the
+ // timer
+ uint64_t add(int64_t milliseconds,
+ std::function<std::pair<bool, int64_t>(bool)> handler) {
+ WorkItem item;
+ Clock::time_point tp = Clock::now();
+ item.end = tp + std::chrono::milliseconds(milliseconds);
+ TEST_SYNC_POINT_CALLBACK("TimeQueue::Add:item.end", &item.end);
+ item.period = milliseconds;
+ item.handler = std::move(handler);
+
+ std::unique_lock<std::mutex> lk(m_mtx);
+ uint64_t id = ++m_idcounter;
+ item.id = id;
+ m_items.push(std::move(item));
+
+ // Something changed, so wake up timer thread
+ m_checkWork.notify_one();
+ return id;
+ }
+
+ // Cancels the specified timer
+ // \return
+ // 1 if the timer was cancelled.
+ // 0 if you were too late to cancel (or the timer ID was never valid to
+ // start with)
+ size_t cancel(uint64_t id) {
+ // Instead of removing the item from the container (thus breaking the
+ // heap integrity), we set the item as having no handler, and put
+ // that handler on a new item at the top for immediate execution
+ // The timer thread will then ignore the original item, since it has no
+ // handler.
+ std::unique_lock<std::mutex> lk(m_mtx);
+ for (auto&& item : m_items.getContainer()) {
+ if (item.id == id && item.handler) {
+ WorkItem newItem;
+ // Zero time, so it stays at the top for immediate execution
+ newItem.end = Clock::time_point();
+ newItem.id = 0; // Means it is a canceled item
+ // Move the handler from item to newitem (thus clearing item)
+ newItem.handler = std::move(item.handler);
+ m_items.push(std::move(newItem));
+
+ // Something changed, so wake up timer thread
+ m_checkWork.notify_one();
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ // Cancels all timers
+ // \return
+ // The number of timers cancelled
+ size_t cancelAll() {
+ // Setting all "end" to 0 (for immediate execution) is ok,
+ // since it maintains the heap integrity
+ std::unique_lock<std::mutex> lk(m_mtx);
+ m_cancel = true;
+ for (auto&& item : m_items.getContainer()) {
+ if (item.id && item.handler) {
+ item.end = Clock::time_point();
+ item.id = 0;
+ }
+ }
+ auto ret = m_items.size();
+
+ m_checkWork.notify_one();
+ return ret;
+ }
+
+ private:
+ using Clock = std::chrono::steady_clock;
+ TimerQueue(const TimerQueue&) = delete;
+ TimerQueue& operator=(const TimerQueue&) = delete;
+
+ void run() {
+ std::unique_lock<std::mutex> lk(m_mtx);
+ while (!m_finish) {
+ auto end = calcWaitTime_lock();
+ if (end.first) {
+ // Timers found, so wait until it expires (or something else
+ // changes)
+ m_checkWork.wait_until(lk, end.second);
+ } else {
+ // No timers exist, so wait forever until something changes
+ m_checkWork.wait(lk);
+ }
+
+ // Check and execute as much work as possible, such as, all expired
+ // timers
+ checkWork(&lk);
+ }
+
+ // If we are shutting down, we should not have any items left,
+ // since the shutdown cancels all items
+ assert(m_items.size() == 0);
+ }
+
+ std::pair<bool, Clock::time_point> calcWaitTime_lock() {
+ while (m_items.size()) {
+ if (m_items.top().handler) {
+ // Item present, so return the new wait time
+ return std::make_pair(true, m_items.top().end);
+ } else {
+ // Discard empty handlers (they were cancelled)
+ m_items.pop();
+ }
+ }
+
+ // No items found, so return no wait time (causes the thread to wait
+ // indefinitely)
+ return std::make_pair(false, Clock::time_point());
+ }
+
+ void checkWork(std::unique_lock<std::mutex>* lk) {
+ while (m_items.size() && m_items.top().end <= Clock::now()) {
+ WorkItem item(m_items.top());
+ m_items.pop();
+
+ if (item.handler) {
+ (*lk).unlock();
+ auto reschedule_pair = item.handler(item.id == 0);
+ (*lk).lock();
+ if (!m_cancel && reschedule_pair.first) {
+ int64_t new_period = (reschedule_pair.second == -1)
+ ? item.period
+ : reschedule_pair.second;
+
+ item.period = new_period;
+ item.end = Clock::now() + std::chrono::milliseconds(new_period);
+ m_items.push(std::move(item));
+ }
+ }
+ }
+ }
+
+ bool m_finish = false;
+ bool m_cancel = false;
+ uint64_t m_idcounter = 0;
+ std::condition_variable m_checkWork;
+
+ struct WorkItem {
+ Clock::time_point end;
+ int64_t period;
+ uint64_t id; // id==0 means it was cancelled
+ std::function<std::pair<bool, int64_t>(bool)> handler;
+ bool operator>(const WorkItem& other) const { return end > other.end; }
+ };
+
+ std::mutex m_mtx;
+ // Inheriting from priority_queue, so we can access the internal container
+ class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
+ std::greater<WorkItem>> {
+ public:
+ std::vector<WorkItem>& getContainer() { return this->c; }
+ } m_items;
+ ROCKSDB_NAMESPACE::port::Thread m_th;
+ bool closed_ = false;
+};
diff --git a/storage/rocksdb/rocksdb/util/timer_queue_test.cc b/storage/rocksdb/rocksdb/util/timer_queue_test.cc
new file mode 100644
index 00000000..5f5f08f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/timer_queue_test.cc
@@ -0,0 +1,72 @@
+// 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 <future>
+
+namespace Timing {
+
+using Clock = std::chrono::high_resolution_clock;
+double now() {
+ static auto start = Clock::now();
+ return std::chrono::duration<double, std::milli>(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/storage/rocksdb/rocksdb/util/user_comparator_wrapper.h b/storage/rocksdb/rocksdb/util/user_comparator_wrapper.h
new file mode 100644
index 00000000..fdb27f33
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/user_comparator_wrapper.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "monitoring/perf_context_imp.h"
+#include "rocksdb/comparator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Wrapper of user comparator, with auto increment to
+// perf_context.user_key_comparison_count.
+class UserComparatorWrapper final : public Comparator {
+ public:
+ explicit UserComparatorWrapper(const Comparator* const user_cmp)
+ : user_comparator_(user_cmp) {}
+
+ ~UserComparatorWrapper() = default;
+
+ const Comparator* user_comparator() const { return user_comparator_; }
+
+ int Compare(const Slice& a, const Slice& b) const override {
+ PERF_COUNTER_ADD(user_key_comparison_count, 1);
+ return user_comparator_->Compare(a, b);
+ }
+
+ bool Equal(const Slice& a, const Slice& b) const override {
+ PERF_COUNTER_ADD(user_key_comparison_count, 1);
+ return user_comparator_->Equal(a, b);
+ }
+
+ const char* Name() const override { return user_comparator_->Name(); }
+
+ void FindShortestSeparator(std::string* start,
+ const Slice& limit) const override {
+ return user_comparator_->FindShortestSeparator(start, limit);
+ }
+
+ void FindShortSuccessor(std::string* key) const override {
+ return user_comparator_->FindShortSuccessor(key);
+ }
+
+ const Comparator* GetRootComparator() const override {
+ return user_comparator_->GetRootComparator();
+ }
+
+ bool IsSameLengthImmediateSuccessor(const Slice& s,
+ const Slice& t) const override {
+ return user_comparator_->IsSameLengthImmediateSuccessor(s, t);
+ }
+
+ bool CanKeysWithDifferentByteContentsBeEqual() const override {
+ return user_comparator_->CanKeysWithDifferentByteContentsBeEqual();
+ }
+
+ private:
+ const Comparator* user_comparator_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/util.h b/storage/rocksdb/rocksdb/util/util.h
new file mode 100644
index 00000000..a5fd3649
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/util.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef FALLTHROUGH_INTENDED
+#if defined(__clang__)
+#define FALLTHROUGH_INTENDED [[clang::fallthrough]]
+#elif defined(__GNUC__) && __GNUC__ >= 7
+#define FALLTHROUGH_INTENDED [[gnu::fallthrough]]
+#else
+#define FALLTHROUGH_INTENDED do {} while (0)
+#endif
+#endif
diff --git a/storage/rocksdb/rocksdb/util/vector_iterator.h b/storage/rocksdb/rocksdb/util/vector_iterator.h
new file mode 100644
index 00000000..fc26ec0c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/vector_iterator.h
@@ -0,0 +1,101 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#pragma once
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "db/dbformat.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice.h"
+#include "table/internal_iterator.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Iterator over a vector of keys/values
+class VectorIterator : public InternalIterator {
+ public:
+ VectorIterator(std::vector<std::string> keys, std::vector<std::string> values,
+ const InternalKeyComparator* icmp)
+ : keys_(std::move(keys)),
+ values_(std::move(values)),
+ indexed_cmp_(icmp, &keys_),
+ current_(0) {
+ assert(keys_.size() == values_.size());
+
+ indices_.reserve(keys_.size());
+ for (size_t i = 0; i < keys_.size(); i++) {
+ indices_.push_back(i);
+ }
+ std::sort(indices_.begin(), indices_.end(), indexed_cmp_);
+ }
+
+ virtual bool Valid() const override {
+ return !indices_.empty() && current_ < indices_.size();
+ }
+
+ virtual void SeekToFirst() override { current_ = 0; }
+ virtual void SeekToLast() override { current_ = indices_.size() - 1; }
+
+ virtual void Seek(const Slice& target) override {
+ current_ = std::lower_bound(indices_.begin(), indices_.end(), target,
+ indexed_cmp_) -
+ indices_.begin();
+ }
+
+ virtual void SeekForPrev(const Slice& target) override {
+ current_ = std::lower_bound(indices_.begin(), indices_.end(), target,
+ indexed_cmp_) -
+ indices_.begin();
+ if (!Valid()) {
+ SeekToLast();
+ } else {
+ Prev();
+ }
+ }
+
+ virtual void Next() override { current_++; }
+ virtual void Prev() override { current_--; }
+
+ virtual Slice key() const override {
+ return Slice(keys_[indices_[current_]]);
+ }
+ virtual Slice value() const override {
+ return Slice(values_[indices_[current_]]);
+ }
+
+ virtual Status status() const override { return Status::OK(); }
+
+ virtual bool IsKeyPinned() const override { return true; }
+ virtual bool IsValuePinned() const override { return true; }
+
+ private:
+ struct IndexedKeyComparator {
+ IndexedKeyComparator(const InternalKeyComparator* c,
+ const std::vector<std::string>* ks)
+ : cmp(c), keys(ks) {}
+
+ bool operator()(size_t a, size_t b) const {
+ return cmp->Compare((*keys)[a], (*keys)[b]) < 0;
+ }
+
+ bool operator()(size_t a, const Slice& b) const {
+ return cmp->Compare((*keys)[a], b) < 0;
+ }
+
+ bool operator()(const Slice& a, size_t b) const {
+ return cmp->Compare(a, (*keys)[b]) < 0;
+ }
+
+ const InternalKeyComparator* cmp;
+ const std::vector<std::string>* keys;
+ };
+
+ std::vector<std::string> keys_;
+ std::vector<std::string> values_;
+ IndexedKeyComparator indexed_cmp_;
+ std::vector<size_t> indices_;
+ size_t current_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/util/xxh3p.h b/storage/rocksdb/rocksdb/util/xxh3p.h
new file mode 100644
index 00000000..0a3cd980
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/xxh3p.h
@@ -0,0 +1,1648 @@
+// 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).
+/*
+ xxHash - Extremely Fast Hash algorithm
+ Development source file for `xxh3`
+ Copyright (C) 2019-present, Yann Collet.
+
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - xxHash source repository : https://github.com/Cyan4973/xxHash
+*/
+
+/* RocksDB Note: This file contains a preview release (xxhash repository
+ version 0.7.2) of XXH3 that is unlikely to be compatible with the final
+ version of XXH3. We have therefore renamed this XXH3p ("preview"), for
+ clarity so that we can continue to use this version even after
+ integrating a newer incompatible version.
+*/
+
+/* Note :
+ This file is separated for development purposes.
+ It will be integrated into `xxhash.c` when development phase is complete.
+*/
+
+#ifndef XXH3p_H
+#define XXH3p_H
+
+
+/* === Dependencies === */
+
+#undef XXH_INLINE_ALL /* in case it's already defined */
+#define XXH_INLINE_ALL
+#include "xxhash.h"
+
+
+/* === Compiler specifics === */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */
+# define XXH_RESTRICT restrict
+#else
+/* note : it might be useful to define __restrict or __restrict__ for some C++ compilers */
+# define XXH_RESTRICT /* disable */
+#endif
+
+#if defined(__GNUC__)
+# if defined(__AVX2__)
+# include <immintrin.h>
+# elif defined(__SSE2__)
+# include <emmintrin.h>
+# elif defined(__ARM_NEON__) || defined(__ARM_NEON)
+# define inline __inline__ /* clang bug */
+# include <arm_neon.h>
+# undef inline
+# endif
+#elif defined(_MSC_VER)
+# include <intrin.h>
+#endif
+
+/*
+ * Sanity check.
+ *
+ * XXH3 only requires these features to be efficient:
+ *
+ * - Usable unaligned access
+ * - A 32-bit or 64-bit ALU
+ * - If 32-bit, a decent ADC instruction
+ * - A 32 or 64-bit multiply with a 64-bit result
+ *
+ * Almost all 32-bit and 64-bit targets meet this, except for Thumb-1, the
+ * classic 16-bit only subset of ARM's instruction set.
+ *
+ * First of all, Thumb-1 lacks support for the UMULL instruction which
+ * performs the important long multiply. This means numerous __aeabi_lmul
+ * calls.
+ *
+ * Second of all, the 8 functional registers are just not enough.
+ * Setup for __aeabi_lmul, byteshift loads, pointers, and all arithmetic need
+ * Lo registers, and this shuffling results in thousands more MOVs than A32.
+ *
+ * A32 and T32 don't have this limitation. They can access all 14 registers,
+ * do a 32->64 multiply with UMULL, and the flexible operand is helpful too.
+ *
+ * If compiling Thumb-1 for a target which supports ARM instructions, we
+ * will give a warning.
+ *
+ * Usually, if this happens, it is because of an accident and you probably
+ * need to specify -march, as you probably meant to compileh for a newer
+ * architecture.
+ */
+#if defined(__thumb__) && !defined(__thumb2__) && defined(__ARM_ARCH_ISA_ARM)
+# warning "XXH3 is highly inefficient without ARM or Thumb-2."
+#endif
+
+/* ==========================================
+ * Vectorization detection
+ * ========================================== */
+#define XXH_SCALAR 0
+#define XXH_SSE2 1
+#define XXH_AVX2 2
+#define XXH_NEON 3
+#define XXH_VSX 4
+
+#ifndef XXH_VECTOR /* can be defined on command line */
+# if defined(__AVX2__)
+# define XXH_VECTOR XXH_AVX2
+# elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
+# define XXH_VECTOR XXH_SSE2
+# elif defined(__GNUC__) /* msvc support maybe later */ \
+ && (defined(__ARM_NEON__) || defined(__ARM_NEON)) \
+ && (defined(__LITTLE_ENDIAN__) /* We only support little endian NEON */ \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+# define XXH_VECTOR XXH_NEON
+# elif defined(__PPC64__) && defined(__POWER8_VECTOR__) && defined(__GNUC__)
+# define XXH_VECTOR XXH_VSX
+# else
+# define XXH_VECTOR XXH_SCALAR
+# endif
+#endif
+
+/* control alignment of accumulator,
+ * for compatibility with fast vector loads */
+#ifndef XXH_ACC_ALIGN
+# if XXH_VECTOR == 0 /* scalar */
+# define XXH_ACC_ALIGN 8
+# elif XXH_VECTOR == 1 /* sse2 */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == 2 /* avx2 */
+# define XXH_ACC_ALIGN 32
+# elif XXH_VECTOR == 3 /* neon */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == 4 /* vsx */
+# define XXH_ACC_ALIGN 16
+# endif
+#endif
+
+/* xxh_u64 XXH_mult32to64(xxh_u32 a, xxh_u64 b) { return (xxh_u64)a * (xxh_u64)b; } */
+#if defined(_MSC_VER) && defined(_M_IX86)
+# include <intrin.h>
+# define XXH_mult32to64(x, y) __emulu(x, y)
+#else
+# define XXH_mult32to64(x, y) ((xxh_u64)((x) & 0xFFFFFFFF) * (xxh_u64)((y) & 0xFFFFFFFF))
+#endif
+
+/* VSX stuff. It's a lot because VSX support is mediocre across compilers and
+ * there is a lot of mischief with endianness. */
+#if XXH_VECTOR == XXH_VSX
+# include <altivec.h>
+# undef vector
+typedef __vector unsigned long long U64x2;
+typedef __vector unsigned char U8x16;
+typedef __vector unsigned U32x4;
+
+#ifndef XXH_VSX_BE
+# if defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_VSX_BE 1
+# elif defined(__VEC_ELEMENT_REG_ORDER__) && __VEC_ELEMENT_REG_ORDER__ == __ORDER_BIG_ENDIAN__
+# warning "-maltivec=be is not recommended. Please use native endianness."
+# define XXH_VSX_BE 1
+# else
+# define XXH_VSX_BE 0
+# endif
+#endif
+
+/* We need some helpers for big endian mode. */
+#if XXH_VSX_BE
+/* A wrapper for POWER9's vec_revb. */
+# ifdef __POWER9_VECTOR__
+# define XXH_vec_revb vec_revb
+# else
+XXH_FORCE_INLINE U64x2 XXH_vec_revb(U64x2 val)
+{
+ U8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
+ 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08 };
+ return vec_perm(val, val, vByteSwap);
+}
+# endif
+
+/* Power8 Crypto gives us vpermxor which is very handy for
+ * PPC64EB.
+ *
+ * U8x16 vpermxor(U8x16 a, U8x16 b, U8x16 mask)
+ * {
+ * U8x16 ret;
+ * for (int i = 0; i < 16; i++) {
+ * ret[i] = a[mask[i] & 0xF] ^ b[mask[i] >> 4];
+ * }
+ * return ret;
+ * }
+ *
+ * Because both of the main loops load the key, swap, and xor it with input,
+ * we can combine the key swap into this instruction.
+ */
+# ifdef vec_permxor
+# define XXH_vec_permxor vec_permxor
+# else
+# define XXH_vec_permxor __builtin_crypto_vpermxor
+# endif
+#endif /* XXH_VSX_BE */
+/*
+ * Because we reinterpret the multiply, there are endian memes: vec_mulo actually becomes
+ * vec_mule.
+ *
+ * Additionally, the intrinsic wasn't added until GCC 8, despite existing for a while.
+ * Clang has an easy way to control this, we can just use the builtin which doesn't swap.
+ * GCC needs inline assembly. */
+#if __has_builtin(__builtin_altivec_vmuleuw)
+# define XXH_vec_mulo __builtin_altivec_vmulouw
+# define XXH_vec_mule __builtin_altivec_vmuleuw
+#else
+/* Adapted from https://github.com/google/highwayhash/blob/master/highwayhash/hh_vsx.h. */
+XXH_FORCE_INLINE U64x2 XXH_vec_mulo(U32x4 a, U32x4 b) {
+ U64x2 result;
+ __asm__("vmulouw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+XXH_FORCE_INLINE U64x2 XXH_vec_mule(U32x4 a, U32x4 b) {
+ U64x2 result;
+ __asm__("vmuleuw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+#endif /* __has_builtin(__builtin_altivec_vmuleuw) */
+#endif /* XXH_VECTOR == XXH_VSX */
+
+/* prefetch
+ * can be disabled, by declaring XXH_NO_PREFETCH build macro */
+#if defined(XXH_NO_PREFETCH)
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+#else
+# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */
+# include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
+# define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
+# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
+# define XXH_PREFETCH(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */)
+# else
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+# endif
+#endif /* XXH_NO_PREFETCH */
+
+
+/* ==========================================
+ * XXH3 default settings
+ * ========================================== */
+
+#define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3p_SECRET_SIZE_MIN */
+
+#if (XXH_SECRET_DEFAULT_SIZE < XXH3p_SECRET_SIZE_MIN)
+# error "default keyset is not large enough"
+#endif
+
+XXH_ALIGN(64) static const xxh_u8 kSecret[XXH_SECRET_DEFAULT_SIZE] = {
+ 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
+ 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
+ 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
+ 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
+ 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
+ 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
+ 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
+ 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
+
+ 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
+ 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
+ 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
+ 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
+};
+
+/*
+ * GCC for x86 has a tendency to use SSE in this loop. While it
+ * successfully avoids swapping (as MUL overwrites EAX and EDX), it
+ * slows it down because instead of free register swap shifts, it
+ * must use pshufd and punpckl/hd.
+ *
+ * To prevent this, we use this attribute to shut off SSE.
+ */
+#if defined(__GNUC__) && !defined(__clang__) && defined(__i386__)
+__attribute__((__target__("no-sse")))
+#endif
+static XXH128_hash_t
+XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
+{
+ /*
+ * GCC/Clang __uint128_t method.
+ *
+ * On most 64-bit targets, GCC and Clang define a __uint128_t type.
+ * This is usually the best way as it usually uses a native long 64-bit
+ * multiply, such as MULQ on x86_64 or MUL + UMULH on aarch64.
+ *
+ * Usually.
+ *
+ * Despite being a 32-bit platform, Clang (and emscripten) define this
+ * type despite not having the arithmetic for it. This results in a
+ * laggy compiler builtin call which calculates a full 128-bit multiply.
+ * In that case it is best to use the portable one.
+ * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677
+ */
+#if defined(__GNUC__) && !defined(__wasm__) \
+ && defined(__SIZEOF_INT128__) \
+ || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128)
+
+ __uint128_t product = (__uint128_t)lhs * (__uint128_t)rhs;
+ XXH128_hash_t const r128 = { (xxh_u64)(product), (xxh_u64)(product >> 64) };
+ return r128;
+
+ /*
+ * MSVC for x64's _umul128 method.
+ *
+ * xxh_u64 _umul128(xxh_u64 Multiplier, xxh_u64 Multiplicand, xxh_u64 *HighProduct);
+ *
+ * This compiles to single operand MUL on x64.
+ */
+#elif defined(_M_X64) || defined(_M_IA64)
+
+#ifndef _MSC_VER
+# pragma intrinsic(_umul128)
+#endif
+ xxh_u64 product_high;
+ xxh_u64 const product_low = _umul128(lhs, rhs, &product_high);
+ XXH128_hash_t const r128 = { product_low, product_high };
+ return r128;
+
+#else
+ /*
+ * Portable scalar method. Optimized for 32-bit and 64-bit ALUs.
+ *
+ * This is a fast and simple grade school multiply, which is shown
+ * below with base 10 arithmetic instead of base 0x100000000.
+ *
+ * 9 3 // D2 lhs = 93
+ * x 7 5 // D2 rhs = 75
+ * ----------
+ * 1 5 // D2 lo_lo = (93 % 10) * (75 % 10)
+ * 4 5 | // D2 hi_lo = (93 / 10) * (75 % 10)
+ * 2 1 | // D2 lo_hi = (93 % 10) * (75 / 10)
+ * + 6 3 | | // D2 hi_hi = (93 / 10) * (75 / 10)
+ * ---------
+ * 2 7 | // D2 cross = (15 / 10) + (45 % 10) + 21
+ * + 6 7 | | // D2 upper = (27 / 10) + (45 / 10) + 63
+ * ---------
+ * 6 9 7 5
+ *
+ * The reasons for adding the products like this are:
+ * 1. It avoids manual carry tracking. Just like how
+ * (9 * 9) + 9 + 9 = 99, the same applies with this for
+ * UINT64_MAX. This avoids a lot of complexity.
+ *
+ * 2. It hints for, and on Clang, compiles to, the powerful UMAAL
+ * instruction available in ARMv6+ A32/T32, which is shown below:
+ *
+ * void UMAAL(xxh_u32 *RdLo, xxh_u32 *RdHi, xxh_u32 Rn, xxh_u32 Rm)
+ * {
+ * xxh_u64 product = (xxh_u64)*RdLo * (xxh_u64)*RdHi + Rn + Rm;
+ * *RdLo = (xxh_u32)(product & 0xFFFFFFFF);
+ * *RdHi = (xxh_u32)(product >> 32);
+ * }
+ *
+ * This instruction was designed for efficient long multiplication,
+ * and allows this to be calculated in only 4 instructions which
+ * is comparable to some 64-bit ALUs.
+ *
+ * 3. It isn't terrible on other platforms. Usually this will be
+ * a couple of 32-bit ADD/ADCs.
+ */
+
+ /* First calculate all of the cross products. */
+ xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF);
+ xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF);
+ xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32);
+ xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32);
+
+ /* Now add the products together. These will never overflow. */
+ xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
+ xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi;
+ xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF);
+
+ XXH128_hash_t r128 = { lower, upper };
+ return r128;
+#endif
+}
+
+/*
+ * We want to keep the attribute here because a target switch
+ * disables inlining.
+ *
+ * Does a 64-bit to 128-bit multiply, then XOR folds it.
+ * The reason for the separate function is to prevent passing
+ * too many structs around by value. This will hopefully inline
+ * the multiply, but we don't force it.
+ */
+#if defined(__GNUC__) && !defined(__clang__) && defined(__i386__)
+__attribute__((__target__("no-sse")))
+#endif
+static xxh_u64
+XXH3p_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs)
+{
+ XXH128_hash_t product = XXH_mult64to128(lhs, rhs);
+ return product.low64 ^ product.high64;
+}
+
+
+static XXH64_hash_t XXH3p_avalanche(xxh_u64 h64)
+{
+ h64 ^= h64 >> 37;
+ h64 *= PRIME64_3;
+ h64 ^= h64 >> 32;
+ return h64;
+}
+
+
+/* ==========================================
+ * Short keys
+ * ========================================== */
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combined = ((xxh_u32)c1) | (((xxh_u32)c2) << 8) | (((xxh_u32)c3) << 16) | (((xxh_u32)len) << 24);
+ xxh_u64 const keyed = (xxh_u64)combined ^ (XXH_readLE32(secret) + seed);
+ xxh_u64 const mixed = keyed * PRIME64_1;
+ return XXH3p_avalanche(mixed);
+ }
+}
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ { xxh_u32 const input_lo = XXH_readLE32(input);
+ xxh_u32 const input_hi = XXH_readLE32(input + len - 4);
+ xxh_u64 const input_64 = input_lo | ((xxh_u64)input_hi << 32);
+ xxh_u64 const keyed = input_64 ^ (XXH_readLE64(secret) + seed);
+ xxh_u64 const mix64 = len + ((keyed ^ (keyed >> 51)) * PRIME32_1);
+ return XXH3p_avalanche((mix64 ^ (mix64 >> 47)) * PRIME64_2);
+ }
+}
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const input_lo = XXH_readLE64(input) ^ (XXH_readLE64(secret) + seed);
+ xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ (XXH_readLE64(secret + 8) - seed);
+ xxh_u64 const acc = len + (input_lo + input_hi) + XXH3p_mul128_fold64(input_lo, input_hi);
+ return XXH3p_avalanche(acc);
+ }
+}
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (len > 8) return XXH3p_len_9to16_64b(input, len, secret, seed);
+ if (len >= 4) return XXH3p_len_4to8_64b(input, len, secret, seed);
+ if (len) return XXH3p_len_1to3_64b(input, len, secret, seed);
+ /*
+ * RocksDB modification from XXH3 preview: zero result for empty
+ * string can be problematic for multiplication-based algorithms.
+ * Return a hash of the seed instead.
+ */
+ return XXH3p_mul128_fold64(seed + XXH_readLE64(secret), PRIME64_2);
+ }
+}
+
+
+/* === Long Keys === */
+
+#define STRIPE_LEN 64
+#define XXH_SECRET_CONSUME_RATE 8 /* nb of secret bytes consumed at each accumulation */
+#define ACC_NB (STRIPE_LEN / sizeof(xxh_u64))
+
+typedef enum { XXH3p_acc_64bits, XXH3p_acc_128bits } XXH3p_accWidth_e;
+
+XXH_FORCE_INLINE void
+XXH3p_accumulate_512( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret,
+ XXH3p_accWidth_e accWidth)
+{
+#if (XXH_VECTOR == XXH_AVX2)
+
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { XXH_ALIGN(32) __m256i* const xacc = (__m256i *) acc;
+ const __m256i* const xinput = (const __m256i *) input; /* not really aligned, just for ptr arithmetic, and because _mm256_loadu_si256() requires this type */
+ const __m256i* const xsecret = (const __m256i *) secret; /* not really aligned, just for ptr arithmetic, and because _mm256_loadu_si256() requires this type */
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN/sizeof(__m256i); i++) {
+ __m256i const data_vec = _mm256_loadu_si256 (xinput+i);
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); /* uint32 dk[8] = {d0+k0, d1+k1, d2+k2, d3+k3, ...} */
+ __m256i const product = _mm256_mul_epu32 (data_key, _mm256_shuffle_epi32 (data_key, 0x31)); /* uint64 mul[4] = {dk0*dk1, dk2*dk3, ...} */
+ if (accWidth == XXH3p_acc_128bits) {
+ __m256i const data_swap = _mm256_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2));
+ __m256i const sum = _mm256_add_epi64(xacc[i], data_swap);
+ xacc[i] = _mm256_add_epi64(product, sum);
+ } else { /* XXH3p_acc_64bits */
+ __m256i const sum = _mm256_add_epi64(xacc[i], data_vec);
+ xacc[i] = _mm256_add_epi64(product, sum);
+ }
+ } }
+
+#elif (XXH_VECTOR == XXH_SSE2)
+
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { XXH_ALIGN(16) __m128i* const xacc = (__m128i *) acc;
+ const __m128i* const xinput = (const __m128i *) input; /* not really aligned, just for ptr arithmetic, and because _mm_loadu_si128() requires this type */
+ const __m128i* const xsecret = (const __m128i *) secret; /* not really aligned, just for ptr arithmetic, and because _mm_loadu_si128() requires this type */
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN/sizeof(__m128i); i++) {
+ __m128i const data_vec = _mm_loadu_si128 (xinput+i);
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec); /* uint32 dk[8] = {d0+k0, d1+k1, d2+k2, d3+k3, ...} */
+ __m128i const product = _mm_mul_epu32 (data_key, _mm_shuffle_epi32 (data_key, 0x31)); /* uint64 mul[4] = {dk0*dk1, dk2*dk3, ...} */
+ if (accWidth == XXH3p_acc_128bits) {
+ __m128i const data_swap = _mm_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2));
+ __m128i const sum = _mm_add_epi64(xacc[i], data_swap);
+ xacc[i] = _mm_add_epi64(product, sum);
+ } else { /* XXH3p_acc_64bits */
+ __m128i const sum = _mm_add_epi64(xacc[i], data_vec);
+ xacc[i] = _mm_add_epi64(product, sum);
+ }
+ } }
+
+#elif (XXH_VECTOR == XXH_NEON)
+
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ {
+ XXH_ALIGN(16) uint64x2_t* const xacc = (uint64x2_t *) acc;
+ /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */
+ uint8_t const* const xinput = (const uint8_t *) input;
+ uint8_t const* const xsecret = (const uint8_t *) secret;
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN / sizeof(uint64x2_t); i++) {
+#if !defined(__aarch64__) && !defined(__arm64__) && defined(__GNUC__) /* ARM32-specific hack */
+ /* vzip on ARMv7 Clang generates a lot of vmovs (technically vorrs) without this.
+ * vzip on 32-bit ARM NEON will overwrite the original register, and I think that Clang
+ * assumes I don't want to destroy it and tries to make a copy. This slows down the code
+ * a lot.
+ * aarch64 not only uses an entirely different syntax, but it requires three
+ * instructions...
+ * ext v1.16B, v0.16B, #8 // select high bits because aarch64 can't address them directly
+ * zip1 v3.2s, v0.2s, v1.2s // first zip
+ * zip2 v2.2s, v0.2s, v1.2s // second zip
+ * ...to do what ARM does in one:
+ * vzip.32 d0, d1 // Interleave high and low bits and overwrite. */
+
+ /* data_vec = xsecret[i]; */
+ uint8x16_t const data_vec = vld1q_u8(xinput + (i * 16));
+ /* key_vec = xsecret[i]; */
+ uint8x16_t const key_vec = vld1q_u8(xsecret + (i * 16));
+ /* data_key = data_vec ^ key_vec; */
+ uint32x4_t data_key;
+
+ if (accWidth == XXH3p_acc_64bits) {
+ /* Add first to prevent register swaps */
+ /* xacc[i] += data_vec; */
+ xacc[i] = vaddq_u64 (xacc[i], vreinterpretq_u64_u8(data_vec));
+ } else { /* XXH3p_acc_128bits */
+ /* xacc[i] += swap(data_vec); */
+ /* can probably be optimized better */
+ uint64x2_t const data64 = vreinterpretq_u64_u8(data_vec);
+ uint64x2_t const swapped= vextq_u64(data64, data64, 1);
+ xacc[i] = vaddq_u64 (xacc[i], swapped);
+ }
+
+ data_key = vreinterpretq_u32_u8(veorq_u8(data_vec, key_vec));
+
+ /* Here's the magic. We use the quirkiness of vzip to shuffle data_key in place.
+ * shuffle: data_key[0, 1, 2, 3] = data_key[0, 2, 1, 3] */
+ __asm__("vzip.32 %e0, %f0" : "+w" (data_key));
+ /* xacc[i] += (uint64x2_t) data_key[0, 1] * (uint64x2_t) data_key[2, 3]; */
+ xacc[i] = vmlal_u32(xacc[i], vget_low_u32(data_key), vget_high_u32(data_key));
+
+#else
+ /* On aarch64, vshrn/vmovn seems to be equivalent to, if not faster than, the vzip method. */
+
+ /* data_vec = xsecret[i]; */
+ uint8x16_t const data_vec = vld1q_u8(xinput + (i * 16));
+ /* key_vec = xsecret[i]; */
+ uint8x16_t const key_vec = vld1q_u8(xsecret + (i * 16));
+ /* data_key = data_vec ^ key_vec; */
+ uint64x2_t const data_key = vreinterpretq_u64_u8(veorq_u8(data_vec, key_vec));
+ /* data_key_lo = (uint32x2_t) (data_key & 0xFFFFFFFF); */
+ uint32x2_t const data_key_lo = vmovn_u64 (data_key);
+ /* data_key_hi = (uint32x2_t) (data_key >> 32); */
+ uint32x2_t const data_key_hi = vshrn_n_u64 (data_key, 32);
+ if (accWidth == XXH3p_acc_64bits) {
+ /* xacc[i] += data_vec; */
+ xacc[i] = vaddq_u64 (xacc[i], vreinterpretq_u64_u8(data_vec));
+ } else { /* XXH3p_acc_128bits */
+ /* xacc[i] += swap(data_vec); */
+ uint64x2_t const data64 = vreinterpretq_u64_u8(data_vec);
+ uint64x2_t const swapped= vextq_u64(data64, data64, 1);
+ xacc[i] = vaddq_u64 (xacc[i], swapped);
+ }
+ /* xacc[i] += (uint64x2_t) data_key_lo * (uint64x2_t) data_key_hi; */
+ xacc[i] = vmlal_u32 (xacc[i], data_key_lo, data_key_hi);
+
+#endif
+ }
+ }
+
+#elif (XXH_VECTOR == XXH_VSX)
+ U64x2* const xacc = (U64x2*) acc; /* presumed aligned */
+ U64x2 const* const xinput = (U64x2 const*) input; /* no alignment restriction */
+ U64x2 const* const xsecret = (U64x2 const*) secret; /* no alignment restriction */
+ U64x2 const v32 = { 32, 32 };
+#if XXH_VSX_BE
+ U8x16 const vXorSwap = { 0x07, 0x16, 0x25, 0x34, 0x43, 0x52, 0x61, 0x70,
+ 0x8F, 0x9E, 0xAD, 0xBC, 0xCB, 0xDA, 0xE9, 0xF8 };
+#endif
+ size_t i;
+ for (i = 0; i < STRIPE_LEN / sizeof(U64x2); i++) {
+ /* data_vec = xinput[i]; */
+ /* key_vec = xsecret[i]; */
+#if XXH_VSX_BE
+ /* byteswap */
+ U64x2 const data_vec = XXH_vec_revb(vec_vsx_ld(0, xinput + i));
+ U64x2 const key_raw = vec_vsx_ld(0, xsecret + i);
+ /* See comment above. data_key = data_vec ^ swap(xsecret[i]); */
+ U64x2 const data_key = (U64x2)XXH_vec_permxor((U8x16)data_vec, (U8x16)key_raw, vXorSwap);
+#else
+ U64x2 const data_vec = vec_vsx_ld(0, xinput + i);
+ U64x2 const key_vec = vec_vsx_ld(0, xsecret + i);
+ U64x2 const data_key = data_vec ^ key_vec;
+#endif
+ /* shuffled = (data_key << 32) | (data_key >> 32); */
+ U32x4 const shuffled = (U32x4)vec_rl(data_key, v32);
+ /* product = ((U64x2)data_key & 0xFFFFFFFF) * ((U64x2)shuffled & 0xFFFFFFFF); */
+ U64x2 const product = XXH_vec_mulo((U32x4)data_key, shuffled);
+ xacc[i] += product;
+
+ if (accWidth == XXH3p_acc_64bits) {
+ xacc[i] += data_vec;
+ } else { /* XXH3p_acc_128bits */
+ /* swap high and low halves */
+ U64x2 const data_swapped = vec_xxpermdi(data_vec, data_vec, 2);
+ xacc[i] += data_swapped;
+ }
+ }
+
+#else /* scalar variant of Accumulator - universal */
+
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned on 32-bytes boundaries, little hint for the auto-vectorizer */
+ const xxh_u8* const xinput = (const xxh_u8*) input; /* no alignment restriction */
+ const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
+ size_t i;
+ XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0);
+ for (i=0; i < ACC_NB; i++) {
+ xxh_u64 const data_val = XXH_readLE64(xinput + 8*i);
+ xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + i*8);
+
+ if (accWidth == XXH3p_acc_64bits) {
+ xacc[i] += data_val;
+ } else {
+ xacc[i ^ 1] += data_val; /* swap adjacent lanes */
+ }
+ xacc[i] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32);
+ }
+#endif
+}
+
+XXH_FORCE_INLINE void
+XXH3p_scrambleAcc(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+#if (XXH_VECTOR == XXH_AVX2)
+
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { XXH_ALIGN(32) __m256i* const xacc = (__m256i*) acc;
+ const __m256i* const xsecret = (const __m256i *) secret; /* not really aligned, just for ptr arithmetic, and because _mm256_loadu_si256() requires this argument type */
+ const __m256i prime32 = _mm256_set1_epi32((int)PRIME32_1);
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN/sizeof(__m256i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m256i const acc_vec = xacc[i];
+ __m256i const shifted = _mm256_srli_epi64 (acc_vec, 47);
+ __m256i const data_vec = _mm256_xor_si256 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret; */
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec);
+
+ /* xacc[i] *= PRIME32_1; */
+ __m256i const data_key_hi = _mm256_shuffle_epi32 (data_key, 0x31);
+ __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32);
+ __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32));
+ }
+ }
+
+#elif (XXH_VECTOR == XXH_SSE2)
+
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { XXH_ALIGN(16) __m128i* const xacc = (__m128i*) acc;
+ const __m128i* const xsecret = (const __m128i *) secret; /* not really aligned, just for ptr arithmetic, and because _mm_loadu_si128() requires this argument type */
+ const __m128i prime32 = _mm_set1_epi32((int)PRIME32_1);
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN/sizeof(__m128i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m128i const acc_vec = xacc[i];
+ __m128i const shifted = _mm_srli_epi64 (acc_vec, 47);
+ __m128i const data_vec = _mm_xor_si128 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret; */
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec);
+
+ /* xacc[i] *= PRIME32_1; */
+ __m128i const data_key_hi = _mm_shuffle_epi32 (data_key, 0x31);
+ __m128i const prod_lo = _mm_mul_epu32 (data_key, prime32);
+ __m128i const prod_hi = _mm_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm_add_epi64(prod_lo, _mm_slli_epi64(prod_hi, 32));
+ }
+ }
+
+#elif (XXH_VECTOR == XXH_NEON)
+
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+
+ { uint64x2_t* const xacc = (uint64x2_t*) acc;
+ uint8_t const* const xsecret = (uint8_t const*) secret;
+ uint32x2_t const prime = vdup_n_u32 (PRIME32_1);
+
+ size_t i;
+ for (i=0; i < STRIPE_LEN/sizeof(uint64x2_t); i++) {
+ /* data_vec = xacc[i] ^ (xacc[i] >> 47); */
+ uint64x2_t const acc_vec = xacc[i];
+ uint64x2_t const shifted = vshrq_n_u64 (acc_vec, 47);
+ uint64x2_t const data_vec = veorq_u64 (acc_vec, shifted);
+
+ /* key_vec = xsecret[i]; */
+ uint32x4_t const key_vec = vreinterpretq_u32_u8(vld1q_u8(xsecret + (i * 16)));
+ /* data_key = data_vec ^ key_vec; */
+ uint32x4_t const data_key = veorq_u32 (vreinterpretq_u32_u64(data_vec), key_vec);
+ /* shuffled = { data_key[0, 2], data_key[1, 3] }; */
+ uint32x2x2_t const shuffled = vzip_u32 (vget_low_u32(data_key), vget_high_u32(data_key));
+
+ /* data_key *= PRIME32_1 */
+
+ /* prod_hi = (data_key >> 32) * PRIME32_1; */
+ uint64x2_t const prod_hi = vmull_u32 (shuffled.val[1], prime);
+ /* xacc[i] = prod_hi << 32; */
+ xacc[i] = vshlq_n_u64(prod_hi, 32);
+ /* xacc[i] += (prod_hi & 0xFFFFFFFF) * PRIME32_1; */
+ xacc[i] = vmlal_u32(xacc[i], shuffled.val[0], prime);
+ } }
+
+#elif (XXH_VECTOR == XXH_VSX)
+
+ U64x2* const xacc = (U64x2*) acc;
+ const U64x2* const xsecret = (const U64x2*) secret;
+ /* constants */
+ U64x2 const v32 = { 32, 32 };
+ U64x2 const v47 = { 47, 47 };
+ U32x4 const prime = { PRIME32_1, PRIME32_1, PRIME32_1, PRIME32_1 };
+ size_t i;
+#if XXH_VSX_BE
+ /* endian swap */
+ U8x16 const vXorSwap = { 0x07, 0x16, 0x25, 0x34, 0x43, 0x52, 0x61, 0x70,
+ 0x8F, 0x9E, 0xAD, 0xBC, 0xCB, 0xDA, 0xE9, 0xF8 };
+#endif
+ for (i = 0; i < STRIPE_LEN / sizeof(U64x2); i++) {
+ U64x2 const acc_vec = xacc[i];
+ U64x2 const data_vec = acc_vec ^ (acc_vec >> v47);
+ /* key_vec = xsecret[i]; */
+#if XXH_VSX_BE
+ /* swap bytes words */
+ U64x2 const key_raw = vec_vsx_ld(0, xsecret + i);
+ U64x2 const data_key = (U64x2)XXH_vec_permxor((U8x16)data_vec, (U8x16)key_raw, vXorSwap);
+#else
+ U64x2 const key_vec = vec_vsx_ld(0, xsecret + i);
+ U64x2 const data_key = data_vec ^ key_vec;
+#endif
+
+ /* data_key *= PRIME32_1 */
+
+ /* prod_lo = ((U64x2)data_key & 0xFFFFFFFF) * ((U64x2)prime & 0xFFFFFFFF); */
+ U64x2 const prod_even = XXH_vec_mule((U32x4)data_key, prime);
+ /* prod_hi = ((U64x2)data_key >> 32) * ((U64x2)prime >> 32); */
+ U64x2 const prod_odd = XXH_vec_mulo((U32x4)data_key, prime);
+ xacc[i] = prod_odd + (prod_even << v32);
+ }
+
+#else /* scalar variant of Scrambler - universal */
+
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned on 32-bytes boundaries, little hint for the auto-vectorizer */
+ const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
+ size_t i;
+ XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0);
+ for (i=0; i < ACC_NB; i++) {
+ xxh_u64 const key64 = XXH_readLE64(xsecret + 8*i);
+ xxh_u64 acc64 = xacc[i];
+ acc64 ^= acc64 >> 47;
+ acc64 ^= key64;
+ acc64 *= PRIME32_1;
+ xacc[i] = acc64;
+ }
+
+#endif
+}
+
+#define XXH_PREFETCH_DIST 384
+
+/* assumption : nbStripes will not overflow secret size */
+XXH_FORCE_INLINE void
+XXH3p_accumulate( xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret,
+ size_t nbStripes,
+ XXH3p_accWidth_e accWidth)
+{
+ size_t n;
+ for (n = 0; n < nbStripes; n++ ) {
+ const xxh_u8* const in = input + n*STRIPE_LEN;
+ XXH_PREFETCH(in + XXH_PREFETCH_DIST);
+ XXH3p_accumulate_512(acc,
+ in,
+ secret + n*XXH_SECRET_CONSUME_RATE,
+ accWidth);
+ }
+}
+
+/* note : clang auto-vectorizes well in SS2 mode _if_ this function is `static`,
+ * and doesn't auto-vectorize it at all if it is `FORCE_INLINE`.
+ * However, it auto-vectorizes better AVX2 if it is `FORCE_INLINE`
+ * Pretty much every other modes and compilers prefer `FORCE_INLINE`.
+ */
+
+#if defined(__clang__) && (XXH_VECTOR==0) && !defined(__AVX2__) && !defined(__arm__) && !defined(__thumb__)
+static void
+#else
+XXH_FORCE_INLINE void
+#endif
+XXH3p_hashLong_internal_loop( xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH3p_accWidth_e accWidth)
+{
+ size_t const nb_rounds = (secretSize - STRIPE_LEN) / XXH_SECRET_CONSUME_RATE;
+ size_t const block_len = STRIPE_LEN * nb_rounds;
+ size_t const nb_blocks = len / block_len;
+
+ size_t n;
+
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN);
+
+ for (n = 0; n < nb_blocks; n++) {
+ XXH3p_accumulate(acc, input + n*block_len, secret, nb_rounds, accWidth);
+ XXH3p_scrambleAcc(acc, secret + secretSize - STRIPE_LEN);
+ }
+
+ /* last partial block */
+ XXH_ASSERT(len > STRIPE_LEN);
+ { size_t const nbStripes = (len - (block_len * nb_blocks)) / STRIPE_LEN;
+ XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE));
+ XXH3p_accumulate(acc, input + nb_blocks*block_len, secret, nbStripes, accWidth);
+
+ /* last stripe */
+ if (len & (STRIPE_LEN - 1)) {
+ const xxh_u8* const p = input + len - STRIPE_LEN;
+#define XXH_SECRET_LASTACC_START 7 /* do not align on 8, so that secret is different from scrambler */
+ XXH3p_accumulate_512(acc, p, secret + secretSize - STRIPE_LEN - XXH_SECRET_LASTACC_START, accWidth);
+ } }
+}
+
+XXH_FORCE_INLINE xxh_u64
+XXH3p_mix2Accs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret)
+{
+ return XXH3p_mul128_fold64(
+ acc[0] ^ XXH_readLE64(secret),
+ acc[1] ^ XXH_readLE64(secret+8) );
+}
+
+static XXH64_hash_t
+XXH3p_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 start)
+{
+ xxh_u64 result64 = start;
+
+ result64 += XXH3p_mix2Accs(acc+0, secret + 0);
+ result64 += XXH3p_mix2Accs(acc+2, secret + 16);
+ result64 += XXH3p_mix2Accs(acc+4, secret + 32);
+ result64 += XXH3p_mix2Accs(acc+6, secret + 48);
+
+ return XXH3p_avalanche(result64);
+}
+
+#define XXH3p_INIT_ACC { PRIME32_3, PRIME64_1, PRIME64_2, PRIME64_3, \
+ PRIME64_4, PRIME32_2, PRIME64_5, PRIME32_1 };
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_hashLong_internal(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[ACC_NB] = XXH3p_INIT_ACC;
+
+ XXH3p_hashLong_internal_loop(acc, input, len, secret, secretSize, XXH3p_acc_64bits);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+#define XXH_SECRET_MERGEACCS_START 11 /* do not align on 8, so that secret is different from accumulator */
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ return XXH3p_mergeAccs(acc, secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * PRIME64_1);
+}
+
+
+XXH_NO_INLINE XXH64_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_64b_defaultSecret(const xxh_u8* XXH_RESTRICT input, size_t len)
+{
+ return XXH3p_hashLong_internal(input, len, kSecret, sizeof(kSecret));
+}
+
+XXH_NO_INLINE XXH64_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_64b_withSecret(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize)
+{
+ return XXH3p_hashLong_internal(input, len, secret, secretSize);
+}
+
+
+XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
+{
+ if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64);
+ memcpy(dst, &v64, sizeof(v64));
+}
+
+/* XXH3p_initCustomSecret() :
+ * destination `customSecret` is presumed allocated and same size as `kSecret`.
+ */
+XXH_FORCE_INLINE void XXH3p_initCustomSecret(xxh_u8* customSecret, xxh_u64 seed64)
+{
+ int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16;
+ int i;
+
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
+
+ for (i=0; i < nbRounds; i++) {
+ XXH_writeLE64(customSecret + 16*i, XXH_readLE64(kSecret + 16*i) + seed64);
+ XXH_writeLE64(customSecret + 16*i + 8, XXH_readLE64(kSecret + 16*i + 8) - seed64);
+ }
+}
+
+
+/* XXH3p_hashLong_64b_withSeed() :
+ * Generate a custom key,
+ * based on alteration of default kSecret with the seed,
+ * and then use this key for long mode hashing.
+ * This operation is decently fast but nonetheless costs a little bit of time.
+ * Try to avoid it whenever possible (typically when seed==0).
+ */
+XXH_NO_INLINE XXH64_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_64b_withSeed(const xxh_u8* input, size_t len, XXH64_hash_t seed)
+{
+ XXH_ALIGN(8) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ if (seed==0) return XXH3p_hashLong_64b_defaultSecret(input, len);
+ XXH3p_initCustomSecret(secret, seed);
+ return XXH3p_hashLong_internal(input, len, secret, sizeof(secret));
+}
+
+
+XXH_FORCE_INLINE xxh_u64 XXH3p_mix16B(const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret, xxh_u64 seed64)
+{
+ xxh_u64 const input_lo = XXH_readLE64(input);
+ xxh_u64 const input_hi = XXH_readLE64(input+8);
+ return XXH3p_mul128_fold64(
+ input_lo ^ (XXH_readLE64(secret) + seed64),
+ input_hi ^ (XXH_readLE64(secret+8) - seed64) );
+}
+
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3p_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { xxh_u64 acc = len * PRIME64_1;
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc += XXH3p_mix16B(input+48, secret+96, seed);
+ acc += XXH3p_mix16B(input+len-64, secret+112, seed);
+ }
+ acc += XXH3p_mix16B(input+32, secret+64, seed);
+ acc += XXH3p_mix16B(input+len-48, secret+80, seed);
+ }
+ acc += XXH3p_mix16B(input+16, secret+32, seed);
+ acc += XXH3p_mix16B(input+len-32, secret+48, seed);
+ }
+ acc += XXH3p_mix16B(input+0, secret+0, seed);
+ acc += XXH3p_mix16B(input+len-16, secret+16, seed);
+
+ return XXH3p_avalanche(acc);
+ }
+}
+
+#define XXH3p_MIDSIZE_MAX 240
+
+XXH_NO_INLINE XXH64_hash_t
+XXH3p_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3p_MIDSIZE_MAX);
+
+ #define XXH3p_MIDSIZE_STARTOFFSET 3
+ #define XXH3p_MIDSIZE_LASTOFFSET 17
+
+ { xxh_u64 acc = len * PRIME64_1;
+ int const nbRounds = (int)len / 16;
+ int i;
+ for (i=0; i<8; i++) {
+ acc += XXH3p_mix16B(input+(16*i), secret+(16*i), seed);
+ }
+ acc = XXH3p_avalanche(acc);
+ XXH_ASSERT(nbRounds >= 8);
+ for (i=8 ; i < nbRounds; i++) {
+ acc += XXH3p_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3p_MIDSIZE_STARTOFFSET, seed);
+ }
+ /* last bytes */
+ acc += XXH3p_mix16B(input + len - 16, secret + XXH3p_SECRET_SIZE_MIN - XXH3p_MIDSIZE_LASTOFFSET, seed);
+ return XXH3p_avalanche(acc);
+ }
+}
+
+/* === Public entry point === */
+
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits(const void* input, size_t len)
+{
+ if (len <= 16) return XXH3p_len_0to16_64b((const xxh_u8*)input, len, kSecret, 0);
+ if (len <= 128) return XXH3p_len_17to128_64b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), 0);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_64b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), 0);
+ return XXH3p_hashLong_64b_defaultSecret((const xxh_u8*)input, len);
+}
+
+XXH_PUBLIC_API XXH64_hash_t
+XXH3p_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN);
+ /* if an action must be taken should `secret` conditions not be respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash */
+ if (len <= 16) return XXH3p_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, 0);
+ if (len <= 128) return XXH3p_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, 0);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, 0);
+ return XXH3p_hashLong_64b_withSecret((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize);
+}
+
+XXH_PUBLIC_API XXH64_hash_t
+XXH3p_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
+{
+ if (len <= 16) return XXH3p_len_0to16_64b((const xxh_u8*)input, len, kSecret, seed);
+ if (len <= 128) return XXH3p_len_17to128_64b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), seed);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_64b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), seed);
+ return XXH3p_hashLong_64b_withSeed((const xxh_u8*)input, len, seed);
+}
+
+/* === XXH3 streaming === */
+
+XXH_PUBLIC_API XXH3p_state_t* XXH3p_createState(void)
+{
+ return (XXH3p_state_t*)XXH_malloc(sizeof(XXH3p_state_t));
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH3p_freeState(XXH3p_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API void
+XXH3p_copyState(XXH3p_state_t* dst_state, const XXH3p_state_t* src_state)
+{
+ memcpy(dst_state, src_state, sizeof(*dst_state));
+}
+
+static void
+XXH3p_64bits_reset_internal(XXH3p_state_t* statePtr,
+ XXH64_hash_t seed,
+ const xxh_u8* secret, size_t secretSize)
+{
+ XXH_ASSERT(statePtr != NULL);
+ memset(statePtr, 0, sizeof(*statePtr));
+ statePtr->acc[0] = PRIME32_3;
+ statePtr->acc[1] = PRIME64_1;
+ statePtr->acc[2] = PRIME64_2;
+ statePtr->acc[3] = PRIME64_3;
+ statePtr->acc[4] = PRIME64_4;
+ statePtr->acc[5] = PRIME32_2;
+ statePtr->acc[6] = PRIME64_5;
+ statePtr->acc[7] = PRIME32_1;
+ statePtr->seed = seed;
+ XXH_ASSERT(secret != NULL);
+ statePtr->secret = secret;
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN);
+ statePtr->secretLimit = (XXH32_hash_t)(secretSize - STRIPE_LEN);
+ statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_64bits_reset(XXH3p_state_t* statePtr)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_64bits_reset_internal(statePtr, 0, kSecret, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_64bits_reset_withSecret(XXH3p_state_t* statePtr, const void* secret, size_t secretSize)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_64bits_reset_internal(statePtr, 0, (const xxh_u8*)secret, secretSize);
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3p_SECRET_SIZE_MIN) return XXH_ERROR;
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_64bits_reset_withSeed(XXH3p_state_t* statePtr, XXH64_hash_t seed)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_64bits_reset_internal(statePtr, seed, kSecret, XXH_SECRET_DEFAULT_SIZE);
+ XXH3p_initCustomSecret(statePtr->customSecret, seed);
+ statePtr->secret = statePtr->customSecret;
+ return XXH_OK;
+}
+
+XXH_FORCE_INLINE void
+XXH3p_consumeStripes( xxh_u64* acc,
+ XXH32_hash_t* nbStripesSoFarPtr, XXH32_hash_t nbStripesPerBlock,
+ const xxh_u8* input, size_t totalStripes,
+ const xxh_u8* secret, size_t secretLimit,
+ XXH3p_accWidth_e accWidth)
+{
+ XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock);
+ if (nbStripesPerBlock - *nbStripesSoFarPtr <= totalStripes) {
+ /* need a scrambling operation */
+ size_t const nbStripes = nbStripesPerBlock - *nbStripesSoFarPtr;
+ XXH3p_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripes, accWidth);
+ XXH3p_scrambleAcc(acc, secret + secretLimit);
+ XXH3p_accumulate(acc, input + nbStripes * STRIPE_LEN, secret, totalStripes - nbStripes, accWidth);
+ *nbStripesSoFarPtr = (XXH32_hash_t)(totalStripes - nbStripes);
+ } else {
+ XXH3p_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, totalStripes, accWidth);
+ *nbStripesSoFarPtr += (XXH32_hash_t)totalStripes;
+ }
+}
+
+XXH_FORCE_INLINE XXH_errorcode
+XXH3p_update(XXH3p_state_t* state, const xxh_u8* input, size_t len, XXH3p_accWidth_e accWidth)
+{
+ if (input==NULL)
+#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ return XXH_OK;
+#else
+ return XXH_ERROR;
+#endif
+
+ { const xxh_u8* const bEnd = input + len;
+
+ state->totalLen += len;
+
+ if (state->bufferedSize + len <= XXH3p_INTERNALBUFFER_SIZE) { /* fill in tmp buffer */
+ XXH_memcpy(state->buffer + state->bufferedSize, input, len);
+ state->bufferedSize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+ /* input now > XXH3p_INTERNALBUFFER_SIZE */
+
+ #define XXH3p_INTERNALBUFFER_STRIPES (XXH3p_INTERNALBUFFER_SIZE / STRIPE_LEN)
+ XXH_STATIC_ASSERT(XXH3p_INTERNALBUFFER_SIZE % STRIPE_LEN == 0); /* clean multiple */
+
+ if (state->bufferedSize) { /* some input within internal buffer: fill then consume it */
+ size_t const loadSize = XXH3p_INTERNALBUFFER_SIZE - state->bufferedSize;
+ XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize);
+ input += loadSize;
+ XXH3p_consumeStripes(state->acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, XXH3p_INTERNALBUFFER_STRIPES,
+ state->secret, state->secretLimit,
+ accWidth);
+ state->bufferedSize = 0;
+ }
+
+ /* consume input by full buffer quantities */
+ if (input+XXH3p_INTERNALBUFFER_SIZE <= bEnd) {
+ const xxh_u8* const limit = bEnd - XXH3p_INTERNALBUFFER_SIZE;
+ do {
+ XXH3p_consumeStripes(state->acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ input, XXH3p_INTERNALBUFFER_STRIPES,
+ state->secret, state->secretLimit,
+ accWidth);
+ input += XXH3p_INTERNALBUFFER_SIZE;
+ } while (input<=limit);
+ }
+
+ if (input < bEnd) { /* some remaining input input : buffer it */
+ XXH_memcpy(state->buffer, input, (size_t)(bEnd-input));
+ state->bufferedSize = (XXH32_hash_t)(bEnd-input);
+ }
+ }
+
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_64bits_update(XXH3p_state_t* state, const void* input, size_t len)
+{
+ return XXH3p_update(state, (const xxh_u8*)input, len, XXH3p_acc_64bits);
+}
+
+
+XXH_FORCE_INLINE void
+XXH3p_digest_long (XXH64_hash_t* acc, const XXH3p_state_t* state, XXH3p_accWidth_e accWidth)
+{
+ memcpy(acc, state->acc, sizeof(state->acc)); /* digest locally, state remains unaltered, and can continue ingesting more input afterwards */
+ if (state->bufferedSize >= STRIPE_LEN) {
+ size_t const totalNbStripes = state->bufferedSize / STRIPE_LEN;
+ XXH32_hash_t nbStripesSoFar = state->nbStripesSoFar;
+ XXH3p_consumeStripes(acc,
+ &nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, totalNbStripes,
+ state->secret, state->secretLimit,
+ accWidth);
+ if (state->bufferedSize % STRIPE_LEN) { /* one last partial stripe */
+ XXH3p_accumulate_512(acc,
+ state->buffer + state->bufferedSize - STRIPE_LEN,
+ state->secret + state->secretLimit - XXH_SECRET_LASTACC_START,
+ accWidth);
+ }
+ } else { /* bufferedSize < STRIPE_LEN */
+ if (state->bufferedSize) { /* one last stripe */
+ xxh_u8 lastStripe[STRIPE_LEN];
+ size_t const catchupSize = STRIPE_LEN - state->bufferedSize;
+ memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
+ memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
+ XXH3p_accumulate_512(acc,
+ lastStripe,
+ state->secret + state->secretLimit - XXH_SECRET_LASTACC_START,
+ accWidth);
+ } }
+}
+
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits_digest (const XXH3p_state_t* state)
+{
+ if (state->totalLen > XXH3p_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[ACC_NB];
+ XXH3p_digest_long(acc, state, XXH3p_acc_64bits);
+ return XXH3p_mergeAccs(acc, state->secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)state->totalLen * PRIME64_1);
+ }
+ /* len <= XXH3p_MIDSIZE_MAX : short code */
+ if (state->seed)
+ return XXH3p_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3p_64bits_withSecret(state->buffer, (size_t)(state->totalLen), state->secret, state->secretLimit + STRIPE_LEN);
+}
+
+/* ==========================================
+ * XXH3 128 bits (=> XXH128)
+ * ========================================== */
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combinedl = ((xxh_u32)c1) + (((xxh_u32)c2) << 8) + (((xxh_u32)c3) << 16) + (((xxh_u32)len) << 24);
+ xxh_u32 const combinedh = XXH_swap32(combinedl);
+ xxh_u64 const keyed_lo = (xxh_u64)combinedl ^ (XXH_readLE32(secret) + seed);
+ xxh_u64 const keyed_hi = (xxh_u64)combinedh ^ (XXH_readLE32(secret+4) - seed);
+ xxh_u64 const mixedl = keyed_lo * PRIME64_1;
+ xxh_u64 const mixedh = keyed_hi * PRIME64_5;
+ XXH128_hash_t const h128 = { XXH3p_avalanche(mixedl) /*low64*/, XXH3p_avalanche(mixedh) /*high64*/ };
+ return h128;
+ }
+}
+
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ { xxh_u32 const input_lo = XXH_readLE32(input);
+ xxh_u32 const input_hi = XXH_readLE32(input + len - 4);
+ xxh_u64 const input_64_lo = input_lo + ((xxh_u64)input_hi << 32);
+ xxh_u64 const input_64_hi = XXH_swap64(input_64_lo);
+ xxh_u64 const keyed_lo = input_64_lo ^ (XXH_readLE64(secret) + seed);
+ xxh_u64 const keyed_hi = input_64_hi ^ (XXH_readLE64(secret + 8) - seed);
+ xxh_u64 const mix64l1 = len + ((keyed_lo ^ (keyed_lo >> 51)) * PRIME32_1);
+ xxh_u64 const mix64l2 = (mix64l1 ^ (mix64l1 >> 47)) * PRIME64_2;
+ xxh_u64 const mix64h1 = ((keyed_hi ^ (keyed_hi >> 47)) * PRIME64_1) - len;
+ xxh_u64 const mix64h2 = (mix64h1 ^ (mix64h1 >> 43)) * PRIME64_4;
+ { XXH128_hash_t const h128 = { XXH3p_avalanche(mix64l2) /*low64*/, XXH3p_avalanche(mix64h2) /*high64*/ };
+ return h128;
+ } }
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const input_lo = XXH_readLE64(input) ^ (XXH_readLE64(secret) + seed);
+ xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ (XXH_readLE64(secret+8) - seed);
+ XXH128_hash_t m128 = XXH_mult64to128(input_lo ^ input_hi, PRIME64_1);
+ xxh_u64 const lenContrib = XXH_mult32to64(len, PRIME32_5);
+ m128.low64 += lenContrib;
+ m128.high64 += input_hi * PRIME64_1;
+ m128.low64 ^= (m128.high64 >> 32);
+ { XXH128_hash_t h128 = XXH_mult64to128(m128.low64, PRIME64_2);
+ h128.high64 += m128.high64 * PRIME64_2;
+ h128.low64 = XXH3p_avalanche(h128.low64);
+ h128.high64 = XXH3p_avalanche(h128.high64);
+ return h128;
+ } }
+}
+
+/* Assumption : `secret` size is >= 16
+ * Note : it should be >= XXH3p_SECRET_SIZE_MIN anyway */
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (len > 8) return XXH3p_len_9to16_128b(input, len, secret, seed);
+ if (len >= 4) return XXH3p_len_4to8_128b(input, len, secret, seed);
+ if (len) return XXH3p_len_1to3_128b(input, len, secret, seed);
+ { XXH128_hash_t const h128 = { 0, 0 };
+ return h128;
+ } }
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_hashLong_128b_internal(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[ACC_NB] = XXH3p_INIT_ACC;
+
+ XXH3p_hashLong_internal_loop(acc, input, len, secret, secretSize, XXH3p_acc_128bits);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { xxh_u64 const low64 = XXH3p_mergeAccs(acc, secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * PRIME64_1);
+ xxh_u64 const high64 = XXH3p_mergeAccs(acc, secret + secretSize - sizeof(acc) - XXH_SECRET_MERGEACCS_START, ~((xxh_u64)len * PRIME64_2));
+ XXH128_hash_t const h128 = { low64, high64 };
+ return h128;
+ }
+}
+
+XXH_NO_INLINE XXH128_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_128b_defaultSecret(const xxh_u8* input, size_t len)
+{
+ return XXH3p_hashLong_128b_internal(input, len, kSecret, sizeof(kSecret));
+}
+
+XXH_NO_INLINE XXH128_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_128b_withSecret(const xxh_u8* input, size_t len,
+ const xxh_u8* secret, size_t secretSize)
+{
+ return XXH3p_hashLong_128b_internal(input, len, secret, secretSize);
+}
+
+XXH_NO_INLINE XXH128_hash_t /* It's important for performance that XXH3p_hashLong is not inlined. Not sure why (uop cache maybe ?), but difference is large and easily measurable */
+XXH3p_hashLong_128b_withSeed(const xxh_u8* input, size_t len, XXH64_hash_t seed)
+{
+ XXH_ALIGN(8) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ if (seed == 0) return XXH3p_hashLong_128b_defaultSecret(input, len);
+ XXH3p_initCustomSecret(secret, seed);
+ return XXH3p_hashLong_128b_internal(input, len, secret, sizeof(secret));
+}
+
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ acc.low64 += XXH3p_mix16B (input_1, secret+0, seed);
+ acc.low64 ^= XXH_readLE64(input_2) + XXH_readLE64(input_2 + 8);
+ acc.high64 += XXH3p_mix16B (input_2, secret+16, seed);
+ acc.high64 ^= XXH_readLE64(input_1) + XXH_readLE64(input_1 + 8);
+ return acc;
+}
+
+XXH_NO_INLINE XXH128_hash_t
+XXH3p_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3p_MIDSIZE_MAX);
+
+ { XXH128_hash_t acc;
+ int const nbRounds = (int)len / 32;
+ int i;
+ acc.low64 = len * PRIME64_1;
+ acc.high64 = 0;
+ for (i=0; i<4; i++) {
+ acc = XXH128_mix32B(acc, input+(32*i), input+(32*i)+16, secret+(32*i), seed);
+ }
+ acc.low64 = XXH3p_avalanche(acc.low64);
+ acc.high64 = XXH3p_avalanche(acc.high64);
+ XXH_ASSERT(nbRounds >= 4);
+ for (i=4 ; i < nbRounds; i++) {
+ acc = XXH128_mix32B(acc, input+(32*i), input+(32*i)+16, secret+XXH3p_MIDSIZE_STARTOFFSET+(32*(i-4)), seed);
+ }
+ /* last bytes */
+ acc = XXH128_mix32B(acc, input + len - 16, input + len - 32, secret + XXH3p_SECRET_SIZE_MIN - XXH3p_MIDSIZE_LASTOFFSET - 16, 0ULL - seed);
+
+ { xxh_u64 const low64 = acc.low64 + acc.high64;
+ xxh_u64 const high64 = (acc.low64 * PRIME64_1) + (acc.high64 * PRIME64_4) + ((len - seed) * PRIME64_2);
+ XXH128_hash_t const h128 = { XXH3p_avalanche(low64), (XXH64_hash_t)0 - XXH3p_avalanche(high64) };
+ return h128;
+ }
+ }
+}
+
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3p_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { XXH128_hash_t acc;
+ acc.low64 = len * PRIME64_1;
+ acc.high64 = 0;
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc = XXH128_mix32B(acc, input+48, input+len-64, secret+96, seed);
+ }
+ acc = XXH128_mix32B(acc, input+32, input+len-48, secret+64, seed);
+ }
+ acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed);
+ }
+ acc = XXH128_mix32B(acc, input, input+len-16, secret, seed);
+ { xxh_u64 const low64 = acc.low64 + acc.high64;
+ xxh_u64 const high64 = (acc.low64 * PRIME64_1) + (acc.high64 * PRIME64_4) + ((len - seed) * PRIME64_2);
+ XXH128_hash_t const h128 = { XXH3p_avalanche(low64), (XXH64_hash_t)0 - XXH3p_avalanche(high64) };
+ return h128;
+ }
+ }
+}
+
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits(const void* input, size_t len)
+{
+ if (len <= 16) return XXH3p_len_0to16_128b((const xxh_u8*)input, len, kSecret, 0);
+ if (len <= 128) return XXH3p_len_17to128_128b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), 0);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_128b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), 0);
+ return XXH3p_hashLong_128b_defaultSecret((const xxh_u8*)input, len);
+}
+
+XXH_PUBLIC_API XXH128_hash_t
+XXH3p_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
+{
+ XXH_ASSERT(secretSize >= XXH3p_SECRET_SIZE_MIN);
+ /* if an action must be taken should `secret` conditions not be respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash */
+ if (len <= 16) return XXH3p_len_0to16_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, 0);
+ if (len <= 128) return XXH3p_len_17to128_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, 0);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, 0);
+ return XXH3p_hashLong_128b_withSecret((const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize);
+}
+
+XXH_PUBLIC_API XXH128_hash_t
+XXH3p_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
+{
+ if (len <= 16) return XXH3p_len_0to16_128b((const xxh_u8*)input, len, kSecret, seed);
+ if (len <= 128) return XXH3p_len_17to128_128b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), seed);
+ if (len <= XXH3p_MIDSIZE_MAX) return XXH3p_len_129to240_128b((const xxh_u8*)input, len, kSecret, sizeof(kSecret), seed);
+ return XXH3p_hashLong_128b_withSeed((const xxh_u8*)input, len, seed);
+}
+
+XXH_PUBLIC_API XXH128_hash_t
+XXH128(const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3p_128bits_withSeed(input, len, seed);
+}
+
+
+/* === XXH3 128-bit streaming === */
+
+/* all the functions are actually the same as for 64-bit streaming variant,
+ just the reset one is different (different initial acc values for 0,5,6,7),
+ and near the end of the digest function */
+
+static void
+XXH3p_128bits_reset_internal(XXH3p_state_t* statePtr,
+ XXH64_hash_t seed,
+ const xxh_u8* secret, size_t secretSize)
+{
+ XXH3p_64bits_reset_internal(statePtr, seed, secret, secretSize);
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_128bits_reset(XXH3p_state_t* statePtr)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_128bits_reset_internal(statePtr, 0, kSecret, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_128bits_reset_withSecret(XXH3p_state_t* statePtr, const void* secret, size_t secretSize)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_128bits_reset_internal(statePtr, 0, (const xxh_u8*)secret, secretSize);
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3p_SECRET_SIZE_MIN) return XXH_ERROR;
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_128bits_reset_withSeed(XXH3p_state_t* statePtr, XXH64_hash_t seed)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3p_128bits_reset_internal(statePtr, seed, kSecret, XXH_SECRET_DEFAULT_SIZE);
+ XXH3p_initCustomSecret(statePtr->customSecret, seed);
+ statePtr->secret = statePtr->customSecret;
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3p_128bits_update(XXH3p_state_t* state, const void* input, size_t len)
+{
+ return XXH3p_update(state, (const xxh_u8*)input, len, XXH3p_acc_128bits);
+}
+
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits_digest (const XXH3p_state_t* state)
+{
+ if (state->totalLen > XXH3p_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[ACC_NB];
+ XXH3p_digest_long(acc, state, XXH3p_acc_128bits);
+ XXH_ASSERT(state->secretLimit + STRIPE_LEN >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { xxh_u64 const low64 = XXH3p_mergeAccs(acc, state->secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)state->totalLen * PRIME64_1);
+ xxh_u64 const high64 = XXH3p_mergeAccs(acc, state->secret + state->secretLimit + STRIPE_LEN - sizeof(acc) - XXH_SECRET_MERGEACCS_START, ~((xxh_u64)state->totalLen * PRIME64_2));
+ XXH128_hash_t const h128 = { low64, high64 };
+ return h128;
+ }
+ }
+ /* len <= XXH3p_MIDSIZE_MAX : short code */
+ if (state->seed)
+ return XXH3p_128bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3p_128bits_withSecret(state->buffer, (size_t)(state->totalLen), state->secret, state->secretLimit + STRIPE_LEN);
+}
+
+/* 128-bit utility functions */
+
+#include <string.h> /* memcmp */
+
+/* return : 1 is equal, 0 if different */
+XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
+{
+ /* note : XXH128_hash_t is compact, it has no padding byte */
+ return !(memcmp(&h1, &h2, sizeof(h1)));
+}
+
+/* This prototype is compatible with stdlib's qsort().
+ * return : >0 if *h128_1 > *h128_2
+ * <0 if *h128_1 < *h128_2
+ * =0 if *h128_1 == *h128_2 */
+XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2)
+{
+ XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1;
+ XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2;
+ int const hcmp = (h1.high64 > h2.high64) - (h2.high64 > h1.high64);
+ /* note : bets that, in most cases, hash values are different */
+ if (hcmp) return hcmp;
+ return (h1.low64 > h2.low64) - (h2.low64 > h1.low64);
+}
+
+
+/*====== Canonical representation ======*/
+XXH_PUBLIC_API void
+XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) {
+ hash.high64 = XXH_swap64(hash.high64);
+ hash.low64 = XXH_swap64(hash.low64);
+ }
+ memcpy(dst, &hash.high64, sizeof(hash.high64));
+ memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
+}
+
+XXH_PUBLIC_API XXH128_hash_t
+XXH128_hashFromCanonical(const XXH128_canonical_t* src)
+{
+ XXH128_hash_t h;
+ h.high64 = XXH_readBE64(src);
+ h.low64 = XXH_readBE64(src->digest + 8);
+ return h;
+}
+
+
+
+#endif /* XXH3p_H */
diff --git a/storage/rocksdb/rocksdb/util/xxhash.cc b/storage/rocksdb/rocksdb/util/xxhash.cc
new file mode 100644
index 00000000..6620ae8b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/xxhash.cc
@@ -0,0 +1,1160 @@
+// 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).
+/*
+* xxHash - Fast Hash algorithm
+* Copyright (C) 2012-2016, Yann Collet
+*
+* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+*
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following disclaimer
+* in the documentation and/or other materials provided with the
+* distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* You can contact the author at :
+* - xxHash homepage: http://www.xxhash.com
+* - xxHash source repository : https://github.com/Cyan4973/xxHash
+*/
+
+
+/* since xxhash.c can be included (via XXH_INLINE_ALL),
+ * it's good practice to protect it with guard
+ * in case of multiples inclusions */
+#ifndef XXHASH_C_01393879
+#define XXHASH_C_01393879
+
+/* *************************************
+* Tuning parameters
+***************************************/
+/*!XXH_FORCE_MEMORY_ACCESS :
+ * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable.
+ * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal.
+ * The below switch allow to select different access method for improved performance.
+ * Method 0 (default) : use `memcpy()`. Safe and portable.
+ * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).
+ * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.
+ * Method 2 : direct access. This method doesn't depend on compiler but violate C standard.
+ * It can generate buggy code on targets which do not support unaligned memory accesses.
+ * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)
+ * See http://stackoverflow.com/a/32095106/646947 for details.
+ * Prefer these methods in priority order (0 > 1 > 2)
+ */
+#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
+# if !defined(__clang__) && defined(__GNUC__) && defined(__ARM_FEATURE_UNALIGNED) && defined(__ARM_ARCH) && (__ARM_ARCH == 6)
+# define XXH_FORCE_MEMORY_ACCESS 2
+# elif !defined(__clang__) && ((defined(__INTEL_COMPILER) && !defined(_WIN32)) || \
+ (defined(__GNUC__) && (defined(__ARM_ARCH) && __ARM_ARCH >= 7)))
+# define XXH_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+/*!XXH_ACCEPT_NULL_INPUT_POINTER :
+ * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault.
+ * When this macro is enabled, xxHash actively checks input for null pointer.
+ * It it is, result for null input pointers is the same as a null-length input.
+ */
+#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */
+# define XXH_ACCEPT_NULL_INPUT_POINTER 0
+#endif
+
+/*!XXH_FORCE_ALIGN_CHECK :
+ * This is a minor performance trick, only useful with lots of very small keys.
+ * It means : check for aligned/unaligned input.
+ * The check costs one initial branch per hash;
+ * set it to 0 when the input is guaranteed to be aligned,
+ * or when alignment doesn't matter for performance.
+ */
+#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
+# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+# define XXH_FORCE_ALIGN_CHECK 0
+# else
+# define XXH_FORCE_ALIGN_CHECK 1
+# endif
+#endif
+
+/*!XXH_REROLL:
+ * Whether to reroll XXH32_finalize, and XXH64_finalize,
+ * instead of using an unrolled jump table/if statement loop.
+ *
+ * This is automatically defined on -Os/-Oz on GCC and Clang. */
+#ifndef XXH_REROLL
+# if defined(__OPTIMIZE_SIZE__)
+# define XXH_REROLL 1
+# else
+# define XXH_REROLL 0
+# endif
+#endif
+
+/* *************************************
+* Includes & Memory related functions
+***************************************/
+/*! Modify the local functions below should you wish to use some other memory routines
+* for malloc(), free() */
+#include <stdlib.h>
+static void* XXH_malloc(size_t s) { return malloc(s); }
+static void XXH_free (void* p) { free(p); }
+/*! and for memcpy() */
+#include <string.h>
+static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); }
+
+#include <limits.h> /* ULLONG_MAX */
+
+#ifndef XXH_STATIC_LINKING_ONLY
+#define XXH_STATIC_LINKING_ONLY
+#endif
+
+#include "xxhash.h"
+
+/* BEGIN RocksDB customizations */
+#include "util/util.h" /* for FALLTHROUGH_INTENDED, inserted as appropriate */
+/* END RocksDB customizations */
+
+/* *************************************
+* Compiler Specific Options
+***************************************/
+#ifdef _MSC_VER /* Visual Studio */
+# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
+# define XXH_FORCE_INLINE static __forceinline
+# define XXH_NO_INLINE static __declspec(noinline)
+#else
+# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
+# ifdef __GNUC__
+# define XXH_FORCE_INLINE static inline __attribute__((always_inline))
+# define XXH_NO_INLINE static __attribute__((noinline))
+# else
+# define XXH_FORCE_INLINE static inline
+# define XXH_NO_INLINE static
+# endif
+# else
+# define XXH_FORCE_INLINE static
+# define XXH_NO_INLINE static
+# endif /* __STDC_VERSION__ */
+#endif
+
+
+
+/* *************************************
+* Debug
+***************************************/
+/* DEBUGLEVEL is expected to be defined externally,
+ * typically through compiler command line.
+ * Value must be a number. */
+#ifndef DEBUGLEVEL
+# define DEBUGLEVEL 0
+#endif
+
+#if (DEBUGLEVEL>=1)
+# include <assert.h> /* note : can still be disabled with NDEBUG */
+# define XXH_ASSERT(c) assert(c)
+#else
+# define XXH_ASSERT(c) ((void)0)
+#endif
+
+/* note : use after variable declarations */
+#define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; }
+
+
+/* *************************************
+* Basic Types
+***************************************/
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint8_t xxh_u8;
+#else
+ typedef unsigned char xxh_u8;
+#endif
+typedef XXH32_hash_t xxh_u32;
+
+
+/* === Memory access === */
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
+/* currently only defined for gcc and icc */
+typedef union { xxh_u32 u32; } __attribute__((packed)) unalign;
+static xxh_u32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; }
+
+#else
+
+/* portable and safe solution. Generally efficient.
+ * see : http://stackoverflow.com/a/32095106/646947
+ */
+static xxh_u32 XXH_read32(const void* memPtr)
+{
+ xxh_u32 val;
+ memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+
+/* === Endianess === */
+typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess;
+
+/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */
+#ifndef XXH_CPU_LITTLE_ENDIAN
+# if defined(_WIN32) /* Windows is always little endian */ \
+ || defined(__LITTLE_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 1
+# elif defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 0
+# else
+static int XXH_isLittleEndian(void)
+{
+ const union { xxh_u32 u; xxh_u8 c[4]; } one = { 1 }; /* don't use static : performance detrimental */
+ return one.c[0];
+}
+# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian()
+# endif
+#endif
+
+
+
+
+/* ****************************************
+* Compiler-specific Functions and Macros
+******************************************/
+#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+#ifndef __has_builtin
+# define __has_builtin(x) 0
+#endif
+
+#if !defined(NO_CLANG_BUILTIN) && __has_builtin(__builtin_rotateleft32) && __has_builtin(__builtin_rotateleft64)
+# define XXH_rotl32 __builtin_rotateleft32
+# define XXH_rotl64 __builtin_rotateleft64
+/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */
+#elif defined(_MSC_VER)
+# define XXH_rotl32(x,r) _rotl(x,r)
+# define XXH_rotl64(x,r) _rotl64(x,r)
+#else
+# define XXH_rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
+# define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r))))
+#endif
+
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap32 _byteswap_ulong
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap32 __builtin_bswap32
+#else
+static xxh_u32 XXH_swap32 (xxh_u32 x)
+{
+ return ((x << 24) & 0xff000000 ) |
+ ((x << 8) & 0x00ff0000 ) |
+ ((x >> 8) & 0x0000ff00 ) |
+ ((x >> 24) & 0x000000ff );
+}
+#endif
+
+
+/* ***************************
+* Memory reads
+*****************************/
+typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;
+
+XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr));
+}
+
+static xxh_u32 XXH_readBE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr);
+}
+
+XXH_FORCE_INLINE xxh_u32
+XXH_readLE32_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned) {
+ return XXH_readLE32(ptr);
+ } else {
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u32*)ptr : XXH_swap32(*(const xxh_u32*)ptr);
+ }
+}
+
+
+/* *************************************
+* Misc
+***************************************/
+XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
+
+
+/* *******************************************************************
+* 32-bit hash functions
+*********************************************************************/
+static const xxh_u32 PRIME32_1 = 0x9E3779B1U; /* 0b10011110001101110111100110110001 */
+static const xxh_u32 PRIME32_2 = 0x85EBCA77U; /* 0b10000101111010111100101001110111 */
+static const xxh_u32 PRIME32_3 = 0xC2B2AE3DU; /* 0b11000010101100101010111000111101 */
+static const xxh_u32 PRIME32_4 = 0x27D4EB2FU; /* 0b00100111110101001110101100101111 */
+static const xxh_u32 PRIME32_5 = 0x165667B1U; /* 0b00010110010101100110011110110001 */
+
+static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
+{
+ acc += input * PRIME32_2;
+ acc = XXH_rotl32(acc, 13);
+ acc *= PRIME32_1;
+#if defined(__GNUC__) && defined(__SSE4_1__) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+ /* UGLY HACK:
+ * This inline assembly hack forces acc into a normal register. This is the
+ * only thing that prevents GCC and Clang from autovectorizing the XXH32 loop
+ * (pragmas and attributes don't work for some resason) without globally
+ * disabling SSE4.1.
+ *
+ * The reason we want to avoid vectorization is because despite working on
+ * 4 integers at a time, there are multiple factors slowing XXH32 down on
+ * SSE4:
+ * - There's a ridiculous amount of lag from pmulld (10 cycles of latency on newer chips!)
+ * making it slightly slower to multiply four integers at once compared to four
+ * integers independently. Even when pmulld was fastest, Sandy/Ivy Bridge, it is
+ * still not worth it to go into SSE just to multiply unless doing a long operation.
+ *
+ * - Four instructions are required to rotate,
+ * movqda tmp, v // not required with VEX encoding
+ * pslld tmp, 13 // tmp <<= 13
+ * psrld v, 19 // x >>= 19
+ * por v, tmp // x |= tmp
+ * compared to one for scalar:
+ * roll v, 13 // reliably fast across the board
+ * shldl v, v, 13 // Sandy Bridge and later prefer this for some reason
+ *
+ * - Instruction level parallelism is actually more beneficial here because the
+ * SIMD actually serializes this operation: While v1 is rotating, v2 can load data,
+ * while v3 can multiply. SSE forces them to operate together.
+ *
+ * How this hack works:
+ * __asm__("" // Declare an assembly block but don't declare any instructions
+ * : // However, as an Input/Output Operand,
+ * "+r" // constrain a read/write operand (+) as a general purpose register (r).
+ * (acc) // and set acc as the operand
+ * );
+ *
+ * Because of the 'r', the compiler has promised that seed will be in a
+ * general purpose register and the '+' says that it will be 'read/write',
+ * so it has to assume it has changed. It is like volatile without all the
+ * loads and stores.
+ *
+ * Since the argument has to be in a normal register (not an SSE register),
+ * each time XXH32_round is called, it is impossible to vectorize. */
+ __asm__("" : "+r" (acc));
+#endif
+ return acc;
+}
+
+/* mix all bits */
+static xxh_u32 XXH32_avalanche(xxh_u32 h32)
+{
+ h32 ^= h32 >> 15;
+ h32 *= PRIME32_2;
+ h32 ^= h32 >> 13;
+ h32 *= PRIME32_3;
+ h32 ^= h32 >> 16;
+ return(h32);
+}
+
+#define XXH_get32bits(p) XXH_readLE32_align(p, align)
+
+static xxh_u32
+XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+#define PROCESS1 \
+ h32 += (*ptr++) * PRIME32_5; \
+ h32 = XXH_rotl32(h32, 11) * PRIME32_1 ;
+
+#define PROCESS4 \
+ h32 += XXH_get32bits(ptr) * PRIME32_3; \
+ ptr+=4; \
+ h32 = XXH_rotl32(h32, 17) * PRIME32_4 ;
+
+ /* Compact rerolled version */
+ if (XXH_REROLL) {
+ len &= 15;
+ while (len >= 4) {
+ PROCESS4;
+ len -= 4;
+ }
+ while (len > 0) {
+ PROCESS1;
+ --len;
+ }
+ return XXH32_avalanche(h32);
+ } else {
+ switch(len&15) /* or switch(bEnd - p) */ {
+ case 12: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 8: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 4: PROCESS4;
+ return XXH32_avalanche(h32);
+
+ case 13: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 9: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 5: PROCESS4;
+ PROCESS1;
+ return XXH32_avalanche(h32);
+
+ case 14: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 10: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 6: PROCESS4;
+ PROCESS1;
+ PROCESS1;
+ return XXH32_avalanche(h32);
+
+ case 15: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 11: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 7: PROCESS4;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 3: PROCESS1;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 2: PROCESS1;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 1: PROCESS1;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 0: return XXH32_avalanche(h32);
+ }
+ XXH_ASSERT(0);
+ return h32; /* reaching this point is deemed impossible */
+ }
+}
+
+XXH_FORCE_INLINE xxh_u32
+XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
+{
+ const xxh_u8* bEnd = input + len;
+ xxh_u32 h32;
+
+#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ if (input==NULL) {
+ len=0;
+ bEnd=input=(const xxh_u8*)(size_t)16;
+ }
+#endif
+
+ if (len>=16) {
+ const xxh_u8* const limit = bEnd - 15;
+ xxh_u32 v1 = seed + PRIME32_1 + PRIME32_2;
+ xxh_u32 v2 = seed + PRIME32_2;
+ xxh_u32 v3 = seed + 0;
+ xxh_u32 v4 = seed - PRIME32_1;
+
+ do {
+ v1 = XXH32_round(v1, XXH_get32bits(input)); input += 4;
+ v2 = XXH32_round(v2, XXH_get32bits(input)); input += 4;
+ v3 = XXH32_round(v3, XXH_get32bits(input)); input += 4;
+ v4 = XXH32_round(v4, XXH_get32bits(input)); input += 4;
+ } while (input < limit);
+
+ h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7)
+ + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+ } else {
+ h32 = seed + PRIME32_5;
+ }
+
+ h32 += (xxh_u32)len;
+
+ return XXH32_finalize(h32, input, len&15, align);
+}
+
+
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed)
+{
+#if 0
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH32_state_t state;
+ XXH32_reset(&state, seed);
+ XXH32_update(&state, (const xxh_u8*)input, len);
+ return XXH32_digest(&state);
+
+#else
+
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+#endif
+}
+
+
+
+/*====== Hash streaming ======*/
+
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
+{
+ return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
+}
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
+{
+ memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed)
+{
+ XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
+ memset(&state, 0, sizeof(state));
+ state.v1 = seed + PRIME32_1 + PRIME32_2;
+ state.v2 = seed + PRIME32_2;
+ state.v3 = seed + 0;
+ state.v4 = seed - PRIME32_1;
+ /* do not write into reserved, planned to be removed in a future version */
+ memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));
+ return XXH_OK;
+}
+
+
+XXH_PUBLIC_API XXH_errorcode
+XXH32_update(XXH32_state_t* state, const void* input, size_t len)
+{
+ if (input==NULL)
+#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ return XXH_OK;
+#else
+ return XXH_ERROR;
+#endif
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len_32 += (XXH32_hash_t)len;
+ state->large_len |= (XXH32_hash_t)((len>=16) | (state->total_len_32>=16));
+
+ if (state->memsize + len < 16) { /* fill in tmp buffer */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, len);
+ state->memsize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* some data left from previous update */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, 16-state->memsize);
+ { const xxh_u32* p32 = state->mem32;
+ state->v1 = XXH32_round(state->v1, XXH_readLE32(p32)); p32++;
+ state->v2 = XXH32_round(state->v2, XXH_readLE32(p32)); p32++;
+ state->v3 = XXH32_round(state->v3, XXH_readLE32(p32)); p32++;
+ state->v4 = XXH32_round(state->v4, XXH_readLE32(p32));
+ }
+ p += 16-state->memsize;
+ state->memsize = 0;
+ }
+
+ // uintptr_t casts added to avoid array-bounds error on
+ // some inlined calls
+ if ((uintptr_t)p <= (uintptr_t)bEnd - 16) {
+ const uintptr_t limit = (uintptr_t)bEnd - 16;
+ xxh_u32 v1 = state->v1;
+ xxh_u32 v2 = state->v2;
+ xxh_u32 v3 = state->v3;
+ xxh_u32 v4 = state->v4;
+
+ do {
+ v1 = XXH32_round(v1, XXH_readLE32(p)); p+=4;
+ v2 = XXH32_round(v2, XXH_readLE32(p)); p+=4;
+ v3 = XXH32_round(v3, XXH_readLE32(p)); p+=4;
+ v4 = XXH32_round(v4, XXH_readLE32(p)); p+=4;
+ } while ((uintptr_t)p <= limit);
+
+ state->v1 = v1;
+ state->v2 = v2;
+ state->v3 = v3;
+ state->v4 = v4;
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem32, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* state)
+{
+ xxh_u32 h32;
+
+ if (state->large_len) {
+ h32 = XXH_rotl32(state->v1, 1)
+ + XXH_rotl32(state->v2, 7)
+ + XXH_rotl32(state->v3, 12)
+ + XXH_rotl32(state->v4, 18);
+ } else {
+ h32 = state->v3 /* == seed */ + PRIME32_5;
+ }
+
+ h32 += state->total_len_32;
+
+ return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned);
+}
+
+
+/*====== Canonical representation ======*/
+
+/*! Default XXH result types are basic unsigned 32 and 64 bits.
+* The canonical representation follows human-readable write convention, aka big-endian (large digits first).
+* These functions allow transformation of hash result into and from its canonical format.
+* This way, hash values can be written into a file or buffer, remaining comparable across different systems.
+*/
+
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
+ memcpy(dst, &hash, sizeof(*dst));
+}
+
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
+{
+ return XXH_readBE32(src);
+}
+
+
+#ifndef XXH_NO_LONG_LONG
+
+/* *******************************************************************
+* 64-bit hash functions
+*********************************************************************/
+
+/*====== Memory access ======*/
+
+typedef XXH64_hash_t xxh_u64;
+
+
+/*! XXH_REROLL_XXH64:
+ * Whether to reroll the XXH64_finalize() loop.
+ *
+ * Just like XXH32, we can unroll the XXH64_finalize() loop. This can be a performance gain
+ * on 64-bit hosts, as only one jump is required.
+ *
+ * However, on 32-bit hosts, because arithmetic needs to be done with two 32-bit registers,
+ * and 64-bit arithmetic needs to be simulated, it isn't beneficial to unroll. The code becomes
+ * ridiculously large (the largest function in the binary on i386!), and rerolling it saves
+ * anywhere from 3kB to 20kB. It is also slightly faster because it fits into cache better
+ * and is more likely to be inlined by the compiler.
+ *
+ * If XXH_REROLL is defined, this is ignored and the loop is always rerolled. */
+#ifndef XXH_REROLL_XXH64
+# if (defined(__ILP32__) || defined(_ILP32)) /* ILP32 is often defined on 32-bit GCC family */ \
+ || !(defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) /* x86-64 */ \
+ || defined(_M_ARM64) || defined(__aarch64__) || defined(__arm64__) /* aarch64 */ \
+ || defined(__PPC64__) || defined(__PPC64LE__) || defined(__ppc64__) || defined(__powerpc64__) /* ppc64 */ \
+ || defined(__mips64__) || defined(__mips64)) /* mips64 */ \
+ || (!defined(SIZE_MAX) || SIZE_MAX < ULLONG_MAX) /* check limits */
+# define XXH_REROLL_XXH64 1
+# else
+# define XXH_REROLL_XXH64 0
+# endif
+#endif /* !defined(XXH_REROLL_XXH64) */
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static xxh_u64 XXH_read64(const void* memPtr) { return *(const xxh_u64*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
+/* currently only defined for gcc and icc */
+typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64;
+static xxh_u64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; }
+
+#else
+
+/* portable and safe solution. Generally efficient.
+ * see : http://stackoverflow.com/a/32095106/646947
+ */
+
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ xxh_u64 val;
+ memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap64 _byteswap_uint64
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap64 __builtin_bswap64
+#else
+static xxh_u64 XXH_swap64 (xxh_u64 x)
+{
+ return ((x << 56) & 0xff00000000000000ULL) |
+ ((x << 40) & 0x00ff000000000000ULL) |
+ ((x << 24) & 0x0000ff0000000000ULL) |
+ ((x << 8) & 0x000000ff00000000ULL) |
+ ((x >> 8) & 0x00000000ff000000ULL) |
+ ((x >> 24) & 0x0000000000ff0000ULL) |
+ ((x >> 40) & 0x000000000000ff00ULL) |
+ ((x >> 56) & 0x00000000000000ffULL);
+}
+#endif
+
+XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr));
+}
+
+static xxh_u64 XXH_readBE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr);
+}
+
+XXH_FORCE_INLINE xxh_u64
+XXH_readLE64_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned)
+ return XXH_readLE64(ptr);
+ else
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u64*)ptr : XXH_swap64(*(const xxh_u64*)ptr);
+}
+
+
+/*====== xxh64 ======*/
+
+static const xxh_u64 PRIME64_1 = 0x9E3779B185EBCA87ULL; /* 0b1001111000110111011110011011000110000101111010111100101010000111 */
+static const xxh_u64 PRIME64_2 = 0xC2B2AE3D27D4EB4FULL; /* 0b1100001010110010101011100011110100100111110101001110101101001111 */
+static const xxh_u64 PRIME64_3 = 0x165667B19E3779F9ULL; /* 0b0001011001010110011001111011000110011110001101110111100111111001 */
+static const xxh_u64 PRIME64_4 = 0x85EBCA77C2B2AE63ULL; /* 0b1000010111101011110010100111011111000010101100101010111001100011 */
+static const xxh_u64 PRIME64_5 = 0x27D4EB2F165667C5ULL; /* 0b0010011111010100111010110010111100010110010101100110011111000101 */
+
+static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input)
+{
+ acc += input * PRIME64_2;
+ acc = XXH_rotl64(acc, 31);
+ acc *= PRIME64_1;
+ return acc;
+}
+
+static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val)
+{
+ val = XXH64_round(0, val);
+ acc ^= val;
+ acc = acc * PRIME64_1 + PRIME64_4;
+ return acc;
+}
+
+static xxh_u64 XXH64_avalanche(xxh_u64 h64)
+{
+ h64 ^= h64 >> 33;
+ h64 *= PRIME64_2;
+ h64 ^= h64 >> 29;
+ h64 *= PRIME64_3;
+ h64 ^= h64 >> 32;
+ return h64;
+}
+
+
+#define XXH_get64bits(p) XXH_readLE64_align(p, align)
+
+static xxh_u64
+XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+#define PROCESS1_64 \
+ h64 ^= (*ptr++) * PRIME64_5; \
+ h64 = XXH_rotl64(h64, 11) * PRIME64_1;
+
+#define PROCESS4_64 \
+ h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * PRIME64_1; \
+ ptr+=4; \
+ h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3;
+
+#define PROCESS8_64 { \
+ xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr)); \
+ ptr+=8; \
+ h64 ^= k1; \
+ h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \
+}
+
+ /* Rerolled version for 32-bit targets is faster and much smaller. */
+ if (XXH_REROLL || XXH_REROLL_XXH64) {
+ len &= 31;
+ while (len >= 8) {
+ PROCESS8_64;
+ len -= 8;
+ }
+ if (len >= 4) {
+ PROCESS4_64;
+ len -= 4;
+ }
+ while (len > 0) {
+ PROCESS1_64;
+ --len;
+ }
+ return XXH64_avalanche(h64);
+ } else {
+ switch(len & 31) {
+ case 24: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 16: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 8: PROCESS8_64;
+ return XXH64_avalanche(h64);
+
+ case 28: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 20: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 12: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 4: PROCESS4_64;
+ return XXH64_avalanche(h64);
+
+ case 25: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 17: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 9: PROCESS8_64;
+ PROCESS1_64;
+ return XXH64_avalanche(h64);
+
+ case 29: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 21: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 13: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 5: PROCESS4_64;
+ PROCESS1_64;
+ return XXH64_avalanche(h64);
+
+ case 26: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 18: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 10: PROCESS8_64;
+ PROCESS1_64;
+ PROCESS1_64;
+ return XXH64_avalanche(h64);
+
+ case 30: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 22: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 14: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 6: PROCESS4_64;
+ PROCESS1_64;
+ PROCESS1_64;
+ return XXH64_avalanche(h64);
+
+ case 27: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 19: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 11: PROCESS8_64;
+ PROCESS1_64;
+ PROCESS1_64;
+ PROCESS1_64;
+ return XXH64_avalanche(h64);
+
+ case 31: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 23: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 15: PROCESS8_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 7: PROCESS4_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 3: PROCESS1_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 2: PROCESS1_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 1: PROCESS1_64;
+ FALLTHROUGH_INTENDED;
+ /* fallthrough */
+ case 0: return XXH64_avalanche(h64);
+ }
+ }
+ /* impossible to reach */
+ XXH_ASSERT(0);
+ return 0; /* unreachable, but some compilers complain without it */
+}
+
+XXH_FORCE_INLINE xxh_u64
+XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align)
+{
+ const xxh_u8* bEnd = input + len;
+ xxh_u64 h64;
+
+#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ if (input==NULL) {
+ len=0;
+ bEnd=input=(const xxh_u8*)(size_t)32;
+ }
+#endif
+
+ if (len>=32) {
+ const xxh_u8* const limit = bEnd - 32;
+ xxh_u64 v1 = seed + PRIME64_1 + PRIME64_2;
+ xxh_u64 v2 = seed + PRIME64_2;
+ xxh_u64 v3 = seed + 0;
+ xxh_u64 v4 = seed - PRIME64_1;
+
+ do {
+ v1 = XXH64_round(v1, XXH_get64bits(input)); input+=8;
+ v2 = XXH64_round(v2, XXH_get64bits(input)); input+=8;
+ v3 = XXH64_round(v3, XXH_get64bits(input)); input+=8;
+ v4 = XXH64_round(v4, XXH_get64bits(input)); input+=8;
+ } while (input<=limit);
+
+ h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+ h64 = XXH64_mergeRound(h64, v1);
+ h64 = XXH64_mergeRound(h64, v2);
+ h64 = XXH64_mergeRound(h64, v3);
+ h64 = XXH64_mergeRound(h64, v4);
+
+ } else {
+ h64 = seed + PRIME64_5;
+ }
+
+ h64 += (xxh_u64) len;
+
+ return XXH64_finalize(h64, input, len, align);
+}
+
+
+XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t seed)
+{
+#if 0
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH64_state_t state;
+ XXH64_reset(&state, seed);
+ XXH64_update(&state, (const xxh_u8*)input, len);
+ return XXH64_digest(&state);
+
+#else
+
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+
+#endif
+}
+
+/*====== Hash Streaming ======*/
+
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
+{
+ return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
+}
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)
+{
+ memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t seed)
+{
+ XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
+ memset(&state, 0, sizeof(state));
+ state.v1 = seed + PRIME64_1 + PRIME64_2;
+ state.v2 = seed + PRIME64_2;
+ state.v3 = seed + 0;
+ state.v4 = seed - PRIME64_1;
+ /* do not write into reserved64, might be removed in a future version */
+ memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved64));
+ return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode
+XXH64_update (XXH64_state_t* state, const void* input, size_t len)
+{
+ if (input==NULL)
+#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ return XXH_OK;
+#else
+ return XXH_ERROR;
+#endif
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len += len;
+
+ if (state->memsize + len < 32) { /* fill in tmp buffer */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, len);
+ state->memsize += (xxh_u32)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* tmp buffer is full */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, 32-state->memsize);
+ state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0));
+ state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1));
+ state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2));
+ state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3));
+ p += 32-state->memsize;
+ state->memsize = 0;
+ }
+
+ // uintptr_t casts added to avoid array-bounds error on
+ // some inlined calls
+ if ((uintptr_t)p + 32 <= (uintptr_t)bEnd) {
+ const uintptr_t limit = (uintptr_t)bEnd - 32;
+ xxh_u64 v1 = state->v1;
+ xxh_u64 v2 = state->v2;
+ xxh_u64 v3 = state->v3;
+ xxh_u64 v4 = state->v4;
+
+ do {
+ v1 = XXH64_round(v1, XXH_readLE64(p)); p+=8;
+ v2 = XXH64_round(v2, XXH_readLE64(p)); p+=8;
+ v3 = XXH64_round(v3, XXH_readLE64(p)); p+=8;
+ v4 = XXH64_round(v4, XXH_readLE64(p)); p+=8;
+ } while ((uintptr_t)p <= limit);
+
+ state->v1 = v1;
+ state->v2 = v2;
+ state->v3 = v3;
+ state->v4 = v4;
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem64, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* state)
+{
+ xxh_u64 h64;
+
+ if (state->total_len >= 32) {
+ xxh_u64 const v1 = state->v1;
+ xxh_u64 const v2 = state->v2;
+ xxh_u64 const v3 = state->v3;
+ xxh_u64 const v4 = state->v4;
+
+ h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+ h64 = XXH64_mergeRound(h64, v1);
+ h64 = XXH64_mergeRound(h64, v2);
+ h64 = XXH64_mergeRound(h64, v3);
+ h64 = XXH64_mergeRound(h64, v4);
+ } else {
+ h64 = state->v3 /*seed*/ + PRIME64_5;
+ }
+
+ h64 += (xxh_u64) state->total_len;
+
+ return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned);
+}
+
+
+/*====== Canonical representation ======*/
+
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
+ memcpy(dst, &hash, sizeof(*dst));
+}
+
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)
+{
+ return XXH_readBE64(src);
+}
+
+
+
+/* *********************************************************************
+* XXH3
+* New generation hash designed for speed on small keys and vectorization
+************************************************************************ */
+
+#include "xxh3p.h" /* XXH3 preview for RocksDB */
+
+
+#endif /* XXH_NO_LONG_LONG */
+
+#endif /* XXHASH_C_01393879 */
diff --git a/storage/rocksdb/rocksdb/util/xxhash.h b/storage/rocksdb/rocksdb/util/xxhash.h
new file mode 100644
index 00000000..59d9b97d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/util/xxhash.h
@@ -0,0 +1,598 @@
+// 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).
+/*
+ xxHash - Extremely Fast Hash algorithm
+ Header File
+ Copyright (C) 2012-2016, Yann Collet.
+
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - xxHash source repository : https://github.com/Cyan4973/xxHash
+*/
+
+/* Notice extracted from xxHash homepage :
+
+xxHash is an extremely fast Hash algorithm, running at RAM speed limits.
+It also successfully passes all tests from the SMHasher suite.
+
+Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)
+
+Name Speed Q.Score Author
+xxHash 5.4 GB/s 10
+CrapWow 3.2 GB/s 2 Andrew
+MumurHash 3a 2.7 GB/s 10 Austin Appleby
+SpookyHash 2.0 GB/s 10 Bob Jenkins
+SBox 1.4 GB/s 9 Bret Mulvey
+Lookup3 1.2 GB/s 9 Bob Jenkins
+SuperFastHash 1.2 GB/s 1 Paul Hsieh
+CityHash64 1.05 GB/s 10 Pike & Alakuijala
+FNV 0.55 GB/s 5 Fowler, Noll, Vo
+CRC32 0.43 GB/s # 9
+MD5-32 0.33 GB/s 10 Ronald L. Rivest
+SHA1-32 0.28 GB/s 10
+
+Note #: other CRC32 implementations can be over 40x faster than SMHasher's:
+http://fastcompression.blogspot.com/2019/03/presenting-xxh3.html?showComment=1552696407071#c3490092340461170735
+
+Q.Score is a measure of quality of the hash function.
+It depends on successfully passing SMHasher test set.
+10 is a perfect score.
+
+A 64-bit version, named XXH64, is available since r35.
+It offers much better speed, but for 64-bit applications only.
+Name Speed on 64 bits Speed on 32 bits
+XXH64 13.8 GB/s 1.9 GB/s
+XXH32 6.8 GB/s 6.0 GB/s
+*/
+
+#ifndef XXHASH_H_5627135585666179
+#define XXHASH_H_5627135585666179 1
+
+/* BEGIN RocksDB customizations */
+#ifndef XXH_STATIC_LINKING_ONLY
+#define XXH_STATIC_LINKING_ONLY 1 /* access experimental APIs like XXH3 */
+#endif
+#define XXH_NAMESPACE ROCKSDB_
+/* END RocksDB customizations */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+/* ****************************
+* Definitions
+******************************/
+#include <stddef.h> /* size_t */
+typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
+
+
+/* ****************************
+ * API modifier
+ ******************************/
+/** XXH_INLINE_ALL (and XXH_PRIVATE_API)
+ * This build macro includes xxhash functions in `static` mode
+ * in order to inline them, and remove their symbol from the public list.
+ * Inlining offers great performance improvement on small keys,
+ * and dramatic ones when length is expressed as a compile-time constant.
+ * See https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html .
+ * Methodology :
+ * #define XXH_INLINE_ALL
+ * #include "xxhash.h"
+ * `xxhash.c` is automatically included.
+ * It's not useful to compile and link it as a separate object.
+ */
+#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
+# ifndef XXH_STATIC_LINKING_ONLY
+# define XXH_STATIC_LINKING_ONLY
+# endif
+# if defined(__GNUC__)
+# define XXH_PUBLIC_API static __inline __attribute__((unused))
+# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# define XXH_PUBLIC_API static inline
+# elif defined(_MSC_VER)
+# define XXH_PUBLIC_API static __inline
+# else
+ /* this version may generate warnings for unused static functions */
+# define XXH_PUBLIC_API static
+# endif
+#else
+# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+# ifdef XXH_EXPORT
+# define XXH_PUBLIC_API __declspec(dllexport)
+# elif XXH_IMPORT
+# define XXH_PUBLIC_API __declspec(dllimport)
+# endif
+# else
+# define XXH_PUBLIC_API /* do nothing */
+# endif
+#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */
+
+/*! XXH_NAMESPACE, aka Namespace Emulation :
+ *
+ * If you want to include _and expose_ xxHash functions from within your own library,
+ * but also want to avoid symbol collisions with other libraries which may also include xxHash,
+ *
+ * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library
+ * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values).
+ *
+ * Note that no change is required within the calling program as long as it includes `xxhash.h` :
+ * regular symbol name will be automatically translated by this header.
+ */
+#ifdef XXH_NAMESPACE
+# define XXH_CAT(A,B) A##B
+# define XXH_NAME2(A,B) XXH_CAT(A,B)
+# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber)
+# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32)
+# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState)
+# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState)
+# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset)
+# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update)
+# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest)
+# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState)
+# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash)
+# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical)
+# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64)
+# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState)
+# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState)
+# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset)
+# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update)
+# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest)
+# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState)
+# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash)
+# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical)
+#endif
+
+
+/* *************************************
+* Version
+***************************************/
+#define XXH_VERSION_MAJOR 0
+#define XXH_VERSION_MINOR 7
+#define XXH_VERSION_RELEASE 2
+#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
+XXH_PUBLIC_API unsigned XXH_versionNumber (void);
+
+
+/*-**********************************************************************
+* 32-bit hash
+************************************************************************/
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint32_t XXH32_hash_t;
+#else
+# include <limits.h>
+# if UINT_MAX == 0xFFFFFFFFUL
+ typedef unsigned int XXH32_hash_t;
+# else
+# if ULONG_MAX == 0xFFFFFFFFUL
+ typedef unsigned long XXH32_hash_t;
+# else
+# error "unsupported platform : need a 32-bit type"
+# endif
+# endif
+#endif
+
+/*! XXH32() :
+ Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input".
+ The memory between input & input+length must be valid (allocated and read-accessible).
+ "seed" can be used to alter the result predictably.
+ Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
+
+/*====== Streaming ======*/
+
+/*
+ * Streaming functions generate the xxHash value from an incrememtal input.
+ * This method is slower than single-call functions, due to state management.
+ * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized.
+ *
+ * XXH state must first be allocated, using XXH*_createState() .
+ *
+ * Start a new hash by initializing state with a seed, using XXH*_reset().
+ *
+ * Then, feed the hash state by calling XXH*_update() as many times as necessary.
+ * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error.
+ *
+ * Finally, a hash value can be produced anytime, by using XXH*_digest().
+ * This function returns the nn-bits hash as an int or long long.
+ *
+ * It's still possible to continue inserting input into the hash state after a digest,
+ * and generate some new hash values later on, by invoking again XXH*_digest().
+ *
+ * When done, release the state, using XXH*_freeState().
+ */
+
+typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr);
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);
+
+XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed);
+XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr);
+
+/*====== Canonical representation ======*/
+
+/* Default return values from XXH functions are basic unsigned 32 and 64 bits.
+ * This the simplest and fastest format for further post-processing.
+ * However, this leaves open the question of what is the order of bytes,
+ * since little and big endian conventions will write the same number differently.
+ *
+ * The canonical representation settles this issue,
+ * by mandating big-endian convention,
+ * aka, the same convention as human-readable numbers (large digits first).
+ * When writing hash values to storage, sending them over a network, or printing them,
+ * it's highly recommended to use the canonical representation,
+ * to ensure portability across a wider range of systems, present and future.
+ *
+ * The following functions allow transformation of hash values into and from canonical format.
+ */
+
+typedef struct { unsigned char digest[4]; } XXH32_canonical_t;
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+
+
+#ifndef XXH_NO_LONG_LONG
+/*-**********************************************************************
+* 64-bit hash
+************************************************************************/
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# include <stdint.h>
+ typedef uint64_t XXH64_hash_t;
+#else
+ /* the following type must have a width of 64-bit */
+ typedef unsigned long long XXH64_hash_t;
+#endif
+
+/*! XXH64() :
+ Calculate the 64-bit hash of sequence of length "len" stored at memory address "input".
+ "seed" can be used to alter the result predictably.
+ This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark).
+*/
+XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, XXH64_hash_t seed);
+
+/*====== Streaming ======*/
+typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr);
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state);
+
+XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr);
+
+/*====== Canonical representation ======*/
+typedef struct { unsigned char digest[8]; } XXH64_canonical_t;
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);
+
+
+#endif /* XXH_NO_LONG_LONG */
+
+
+
+#ifdef XXH_STATIC_LINKING_ONLY
+
+/* ================================================================================================
+ This section contains declarations which are not guaranteed to remain stable.
+ They may change in future versions, becoming incompatible with a different version of the library.
+ These declarations should only be used with static linking.
+ Never use them in association with dynamic linking !
+=================================================================================================== */
+
+/* These definitions are only present to allow
+ * static allocation of XXH state, on stack or in a struct for example.
+ * Never **ever** use members directly. */
+
+struct XXH32_state_s {
+ XXH32_hash_t total_len_32;
+ XXH32_hash_t large_len;
+ XXH32_hash_t v1;
+ XXH32_hash_t v2;
+ XXH32_hash_t v3;
+ XXH32_hash_t v4;
+ XXH32_hash_t mem32[4];
+ XXH32_hash_t memsize;
+ XXH32_hash_t reserved; /* never read nor write, might be removed in a future version */
+}; /* typedef'd to XXH32_state_t */
+
+#ifndef XXH_NO_LONG_LONG /* remove 64-bit support */
+struct XXH64_state_s {
+ XXH64_hash_t total_len;
+ XXH64_hash_t v1;
+ XXH64_hash_t v2;
+ XXH64_hash_t v3;
+ XXH64_hash_t v4;
+ XXH64_hash_t mem64[4];
+ XXH32_hash_t memsize;
+ XXH32_hash_t reserved32; /* required for padding anyway */
+ XXH64_hash_t reserved64; /* never read nor write, might be removed in a future version */
+}; /* typedef'd to XXH64_state_t */
+#endif /* XXH_NO_LONG_LONG */
+
+
+/*-**********************************************************************
+* XXH3
+* New experimental hash
+************************************************************************/
+#ifndef XXH_NO_LONG_LONG
+
+
+/* ============================================
+ * XXH3 is a new hash algorithm,
+ * featuring improved speed performance for both small and large inputs.
+ * See full speed analysis at : http://fastcompression.blogspot.com/2019/03/presenting-xxh3.html
+ * In general, expect XXH3 to run about ~2x faster on large inputs,
+ * and >3x faster on small ones, though exact differences depend on platform.
+ *
+ * The algorithm is portable, will generate the same hash on all platforms.
+ * It benefits greatly from vectorization units, but does not require it.
+ *
+ * XXH3 offers 2 variants, _64bits and _128bits.
+ * When only 64 bits are needed, prefer calling the _64bits variant :
+ * it reduces the amount of mixing, resulting in faster speed on small inputs.
+ * It's also generally simpler to manipulate a scalar return type than a struct.
+ *
+ * The XXH3 algorithm is still considered experimental.
+ * Produced results can still change between versions.
+ * Results produced by v0.7.x are not comparable with results from v0.7.y .
+ * It's nonetheless possible to use XXH3 for ephemeral data (local sessions),
+ * but avoid storing values in long-term storage for later reads.
+ *
+ * The API supports one-shot hashing, streaming mode, and custom secrets.
+ *
+ * There are still a number of opened questions that community can influence during the experimental period.
+ * I'm trying to list a few of them below, though don't consider this list as complete.
+ *
+ * - 128-bits output type : currently defined as a structure of two 64-bits fields.
+ * That's because 128-bit values do not exist in C standard.
+ * Note that it means that, at byte level, result is not identical depending on endianess.
+ * However, at field level, they are identical on all platforms.
+ * The canonical representation solves the issue of identical byte-level representation across platforms,
+ * which is necessary for serialization.
+ * Q1 : Would there be a better representation for a 128-bit hash result ?
+ * Q2 : Are the names of the inner 64-bit fields important ? Should they be changed ?
+ *
+ * - Prototype XXH128() : XXH128() uses the same arguments as XXH64(), for consistency.
+ * It means it maps to XXH3p_128bits_withSeed().
+ * This variant is slightly slower than XXH3p_128bits(),
+ * because the seed is now part of the algorithm, and can't be simplified.
+ * Is that a good idea ?
+ *
+ * - Seed type for XXH128() : currently, it's a single 64-bit value, like the 64-bit variant.
+ * It could be argued that it's more logical to offer a 128-bit seed input parameter for a 128-bit hash.
+ * But 128-bit seed is more difficult to use, since it requires to pass a structure instead of a scalar value.
+ * Such a variant could either replace current one, or become an additional one.
+ * Farmhash, for example, offers both variants (the 128-bits seed variant is called `doubleSeed`).
+ * Follow up question : if both 64-bit and 128-bit seeds are allowed, which variant should be called XXH128 ?
+ *
+ * - Result for len==0 : Currently, the result of hashing a zero-length input is always `0`.
+ * It seems okay as a return value when using "default" secret and seed.
+ * But is it still fine to return `0` when secret or seed are non-default ?
+ * Are there use cases which could depend on generating a different hash result for zero-length input when the secret is different ?
+ *
+ * - Consistency (1) : Streaming XXH128 uses an XXH3 state, which is the same state as XXH3p_64bits().
+ * It means a 128bit streaming loop must invoke the following symbols :
+ * XXH3p_createState(), XXH3p_128bits_reset(), XXH3p_128bits_update() (loop), XXH3p_128bits_digest(), XXH3p_freeState().
+ * Is that consistent enough ?
+ *
+ * - Consistency (2) : The canonical representation of `XXH3p_64bits` is provided by existing functions
+ * XXH64_canonicalFromHash(), and reverse operation XXH64_hashFromCanonical().
+ * As a mirror, canonical functions for XXH128_hash_t results generated by `XXH3p_128bits`
+ * are XXH128_canonicalFromHash() and XXH128_hashFromCanonical().
+ * Which means, `XXH3` doesn't appear in the names, because canonical functions operate on a type,
+ * independently of which algorithm was used to generate that type.
+ * Is that consistent enough ?
+ */
+
+#ifdef XXH_NAMESPACE
+# define XXH3p_64bits XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits)
+# define XXH3p_64bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_withSecret)
+# define XXH3p_64bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_withSeed)
+
+# define XXH3p_createState XXH_NAME2(XXH_NAMESPACE, XXH3p_createState)
+# define XXH3p_freeState XXH_NAME2(XXH_NAMESPACE, XXH3p_freeState)
+# define XXH3p_copyState XXH_NAME2(XXH_NAMESPACE, XXH3p_copyState)
+
+# define XXH3p_64bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_reset)
+# define XXH3p_64bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_reset_withSeed)
+# define XXH3p_64bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_reset_withSecret)
+# define XXH3p_64bits_update XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_update)
+# define XXH3p_64bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3p_64bits_digest)
+#endif
+
+/* XXH3p_64bits() :
+ * default 64-bit variant, using default secret and default seed of 0.
+ * It's the fastest variant. */
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits(const void* data, size_t len);
+
+/* XXH3p_64bits_withSecret() :
+ * It's possible to provide any blob of bytes as a "secret" to generate the hash.
+ * This makes it more difficult for an external actor to prepare an intentional collision.
+ * The secret *must* be large enough (>= XXH3p_SECRET_SIZE_MIN).
+ * It should consist of random bytes.
+ * Avoid repeating same character, or sequences of bytes,
+ * and especially avoid swathes of \0.
+ * Failure to respect these conditions will result in a poor quality hash.
+ */
+#define XXH3p_SECRET_SIZE_MIN 136
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
+
+/* XXH3p_64bits_withSeed() :
+ * This variant generates on the fly a custom secret,
+ * based on the default secret, altered using the `seed` value.
+ * While this operation is decently fast, note that it's not completely free.
+ * note : seed==0 produces same results as XXH3p_64bits() */
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits_withSeed(const void* data, size_t len, XXH64_hash_t seed);
+
+
+/* streaming 64-bit */
+
+#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11+ */
+# include <stdalign.h>
+# define XXH_ALIGN(n) alignas(n)
+#elif defined(__GNUC__)
+# define XXH_ALIGN(n) __attribute__ ((aligned(n)))
+#elif defined(_MSC_VER)
+# define XXH_ALIGN(n) __declspec(align(n))
+#else
+# define XXH_ALIGN(n) /* disabled */
+#endif
+
+typedef struct XXH3p_state_s XXH3p_state_t;
+
+#define XXH3p_SECRET_DEFAULT_SIZE 192 /* minimum XXH3p_SECRET_SIZE_MIN */
+#define XXH3p_INTERNALBUFFER_SIZE 256
+struct XXH3p_state_s {
+ XXH_ALIGN(64) XXH64_hash_t acc[8];
+ XXH_ALIGN(64) unsigned char customSecret[XXH3p_SECRET_DEFAULT_SIZE]; /* used to store a custom secret generated from the seed. Makes state larger. Design might change */
+ XXH_ALIGN(64) unsigned char buffer[XXH3p_INTERNALBUFFER_SIZE];
+ XXH32_hash_t bufferedSize;
+ XXH32_hash_t nbStripesPerBlock;
+ XXH32_hash_t nbStripesSoFar;
+ XXH32_hash_t secretLimit;
+ XXH32_hash_t reserved32;
+ XXH32_hash_t reserved32_2;
+ XXH64_hash_t totalLen;
+ XXH64_hash_t seed;
+ XXH64_hash_t reserved64;
+ const unsigned char* secret; /* note : there is some padding after, due to alignment on 64 bytes */
+}; /* typedef'd to XXH3p_state_t */
+
+/* Streaming requires state maintenance.
+ * This operation costs memory and cpu.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer using one-shot functions whenever possible. */
+
+XXH_PUBLIC_API XXH3p_state_t* XXH3p_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH3p_freeState(XXH3p_state_t* statePtr);
+XXH_PUBLIC_API void XXH3p_copyState(XXH3p_state_t* dst_state, const XXH3p_state_t* src_state);
+
+
+/* XXH3p_64bits_reset() :
+ * initialize with default parameters.
+ * result will be equivalent to `XXH3p_64bits()`. */
+XXH_PUBLIC_API XXH_errorcode XXH3p_64bits_reset(XXH3p_state_t* statePtr);
+/* XXH3p_64bits_reset_withSeed() :
+ * generate a custom secret from `seed`, and store it into state.
+ * digest will be equivalent to `XXH3p_64bits_withSeed()`. */
+XXH_PUBLIC_API XXH_errorcode XXH3p_64bits_reset_withSeed(XXH3p_state_t* statePtr, XXH64_hash_t seed);
+/* XXH3p_64bits_reset_withSecret() :
+ * `secret` is referenced, and must outlive the hash streaming session.
+ * secretSize must be >= XXH3p_SECRET_SIZE_MIN.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3p_64bits_reset_withSecret(XXH3p_state_t* statePtr, const void* secret, size_t secretSize);
+
+XXH_PUBLIC_API XXH_errorcode XXH3p_64bits_update (XXH3p_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH64_hash_t XXH3p_64bits_digest (const XXH3p_state_t* statePtr);
+
+
+/* 128-bit */
+
+#ifdef XXH_NAMESPACE
+# define XXH128 XXH_NAME2(XXH_NAMESPACE, XXH128)
+# define XXH3p_128bits XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits)
+# define XXH3p_128bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_withSeed)
+# define XXH3p_128bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_withSecret)
+
+# define XXH3p_128bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_reset)
+# define XXH3p_128bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_reset_withSeed)
+# define XXH3p_128bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_reset_withSecret)
+# define XXH3p_128bits_update XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_update)
+# define XXH3p_128bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3p_128bits_digest)
+
+# define XXH128_isEqual XXH_NAME2(XXH_NAMESPACE, XXH128_isEqual)
+# define XXH128_cmp XXH_NAME2(XXH_NAMESPACE, XXH128_cmp)
+# define XXH128_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH128_canonicalFromHash)
+# define XXH128_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH128_hashFromCanonical)
+#endif
+
+typedef struct {
+ XXH64_hash_t low64;
+ XXH64_hash_t high64;
+} XXH128_hash_t;
+
+XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits(const void* data, size_t len);
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits_withSeed(const void* data, size_t len, XXH64_hash_t seed); /* == XXH128() */
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
+
+XXH_PUBLIC_API XXH_errorcode XXH3p_128bits_reset(XXH3p_state_t* statePtr);
+XXH_PUBLIC_API XXH_errorcode XXH3p_128bits_reset_withSeed(XXH3p_state_t* statePtr, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH_errorcode XXH3p_128bits_reset_withSecret(XXH3p_state_t* statePtr, const void* secret, size_t secretSize);
+
+XXH_PUBLIC_API XXH_errorcode XXH3p_128bits_update (XXH3p_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH128_hash_t XXH3p_128bits_digest (const XXH3p_state_t* statePtr);
+
+
+/* Note : for better performance, following functions can be inlined,
+ * using XXH_INLINE_ALL */
+
+/* return : 1 is equal, 0 if different */
+XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2);
+
+/* This comparator is compatible with stdlib's qsort().
+ * return : >0 if *h128_1 > *h128_2
+ * <0 if *h128_1 < *h128_2
+ * =0 if *h128_1 == *h128_2 */
+XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2);
+
+
+/*====== Canonical representation ======*/
+typedef struct { unsigned char digest[16]; } XXH128_canonical_t;
+XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash);
+XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src);
+
+
+#endif /* XXH_NO_LONG_LONG */
+
+
+/*-**********************************************************************
+* XXH_INLINE_ALL
+************************************************************************/
+#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
+# include "xxhash.cc" /* include xxhash function bodies as `static`, for inlining */
+#endif
+
+
+
+#endif /* XXH_STATIC_LINKING_ONLY */
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* XXHASH_H_5627135585666179 */
diff --git a/storage/rocksdb/rocksdb/utilities/backupable/backupable_db.cc b/storage/rocksdb/rocksdb/utilities/backupable/backupable_db.cc
new file mode 100644
index 00000000..0ca67670
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/backupable/backupable_db.cc
@@ -0,0 +1,1989 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <stdlib.h>
+#include <algorithm>
+#include <atomic>
+#include <cinttypes>
+#include <functional>
+#include <future>
+#include <limits>
+#include <map>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "file/sequence_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/utilities/backupable_db.h"
+#include "test_util/sync_point.h"
+#include "util/channel.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+#include "util/string_util.h"
+#include "utilities/checkpoint/checkpoint_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void BackupStatistics::IncrementNumberSuccessBackup() {
+ number_success_backup++;
+}
+void BackupStatistics::IncrementNumberFailBackup() {
+ number_fail_backup++;
+}
+
+uint32_t BackupStatistics::GetNumberSuccessBackup() const {
+ return number_success_backup;
+}
+uint32_t BackupStatistics::GetNumberFailBackup() const {
+ return number_fail_backup;
+}
+
+std::string BackupStatistics::ToString() const {
+ char result[50];
+ snprintf(result, sizeof(result), "# success backup: %u, # fail backup: %u",
+ GetNumberSuccessBackup(), GetNumberFailBackup());
+ return result;
+}
+
+void BackupableDBOptions::Dump(Logger* logger) const {
+ ROCKS_LOG_INFO(logger, " Options.backup_dir: %s",
+ backup_dir.c_str());
+ ROCKS_LOG_INFO(logger, " Options.backup_env: %p", backup_env);
+ ROCKS_LOG_INFO(logger, " Options.share_table_files: %d",
+ static_cast<int>(share_table_files));
+ ROCKS_LOG_INFO(logger, " Options.info_log: %p", info_log);
+ ROCKS_LOG_INFO(logger, " Options.sync: %d",
+ static_cast<int>(sync));
+ ROCKS_LOG_INFO(logger, " Options.destroy_old_data: %d",
+ static_cast<int>(destroy_old_data));
+ ROCKS_LOG_INFO(logger, " Options.backup_log_files: %d",
+ static_cast<int>(backup_log_files));
+ ROCKS_LOG_INFO(logger, " Options.backup_rate_limit: %" PRIu64,
+ backup_rate_limit);
+ ROCKS_LOG_INFO(logger, " Options.restore_rate_limit: %" PRIu64,
+ restore_rate_limit);
+ ROCKS_LOG_INFO(logger, "Options.max_background_operations: %d",
+ max_background_operations);
+}
+
+// -------- BackupEngineImpl class ---------
+class BackupEngineImpl : public BackupEngine {
+ public:
+ BackupEngineImpl(Env* db_env, const BackupableDBOptions& options,
+ bool read_only = false);
+ ~BackupEngineImpl() override;
+ Status CreateNewBackupWithMetadata(DB* db, const std::string& app_metadata,
+ bool flush_before_backup = false,
+ std::function<void()> progress_callback =
+ []() {}) override;
+ Status PurgeOldBackups(uint32_t num_backups_to_keep) override;
+ Status DeleteBackup(BackupID backup_id) override;
+ void StopBackup() override {
+ stop_backup_.store(true, std::memory_order_release);
+ }
+ Status GarbageCollect() override;
+
+ // The returned BackupInfos are in chronological order, which means the
+ // latest backup comes last.
+ void GetBackupInfo(std::vector<BackupInfo>* backup_info) override;
+ void GetCorruptedBackups(std::vector<BackupID>* corrupt_backup_ids) override;
+ Status RestoreDBFromBackup(
+ BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) override;
+ Status RestoreDBFromLatestBackup(
+ const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) override {
+ return RestoreDBFromBackup(latest_valid_backup_id_, db_dir, wal_dir,
+ restore_options);
+ }
+
+ Status VerifyBackup(BackupID backup_id) override;
+
+ Status Initialize();
+
+ private:
+ void DeleteChildren(const std::string& dir, uint32_t file_type_filter = 0);
+ Status DeleteBackupInternal(BackupID backup_id);
+
+ // Extends the "result" map with pathname->size mappings for the contents of
+ // "dir" in "env". Pathnames are prefixed with "dir".
+ Status InsertPathnameToSizeBytes(
+ const std::string& dir, Env* env,
+ std::unordered_map<std::string, uint64_t>* result);
+
+ struct FileInfo {
+ FileInfo(const std::string& fname, uint64_t sz, uint32_t checksum)
+ : refs(0), filename(fname), size(sz), checksum_value(checksum) {}
+
+ FileInfo(const FileInfo&) = delete;
+ FileInfo& operator=(const FileInfo&) = delete;
+
+ int refs;
+ const std::string filename;
+ const uint64_t size;
+ const uint32_t checksum_value;
+ };
+
+ class BackupMeta {
+ public:
+ BackupMeta(
+ const std::string& meta_filename, const std::string& meta_tmp_filename,
+ std::unordered_map<std::string, std::shared_ptr<FileInfo>>* file_infos,
+ Env* env)
+ : timestamp_(0),
+ sequence_number_(0),
+ size_(0),
+ meta_filename_(meta_filename),
+ meta_tmp_filename_(meta_tmp_filename),
+ file_infos_(file_infos),
+ env_(env) {}
+
+ BackupMeta(const BackupMeta&) = delete;
+ BackupMeta& operator=(const BackupMeta&) = delete;
+
+ ~BackupMeta() {}
+
+ void RecordTimestamp() {
+ env_->GetCurrentTime(&timestamp_);
+ }
+ int64_t GetTimestamp() const {
+ return timestamp_;
+ }
+ uint64_t GetSize() const {
+ return size_;
+ }
+ uint32_t GetNumberFiles() { return static_cast<uint32_t>(files_.size()); }
+ void SetSequenceNumber(uint64_t sequence_number) {
+ sequence_number_ = sequence_number;
+ }
+ uint64_t GetSequenceNumber() {
+ return sequence_number_;
+ }
+
+ const std::string& GetAppMetadata() const { return app_metadata_; }
+
+ void SetAppMetadata(const std::string& app_metadata) {
+ app_metadata_ = app_metadata;
+ }
+
+ Status AddFile(std::shared_ptr<FileInfo> file_info);
+
+ Status Delete(bool delete_meta = true);
+
+ bool Empty() {
+ return files_.empty();
+ }
+
+ std::shared_ptr<FileInfo> GetFile(const std::string& filename) const {
+ auto it = file_infos_->find(filename);
+ if (it == file_infos_->end())
+ return nullptr;
+ return it->second;
+ }
+
+ const std::vector<std::shared_ptr<FileInfo>>& GetFiles() {
+ return files_;
+ }
+
+ // @param abs_path_to_size Pre-fetched file sizes (bytes).
+ Status LoadFromFile(
+ const std::string& backup_dir,
+ const std::unordered_map<std::string, uint64_t>& abs_path_to_size);
+ Status StoreToFile(bool sync);
+
+ std::string GetInfoString() {
+ std::ostringstream ss;
+ ss << "Timestamp: " << timestamp_ << std::endl;
+ char human_size[16];
+ AppendHumanBytes(size_, human_size, sizeof(human_size));
+ ss << "Size: " << human_size << std::endl;
+ ss << "Files:" << std::endl;
+ for (const auto& file : files_) {
+ AppendHumanBytes(file->size, human_size, sizeof(human_size));
+ ss << file->filename << ", size " << human_size << ", refs "
+ << file->refs << std::endl;
+ }
+ return ss.str();
+ }
+
+ private:
+ int64_t timestamp_;
+ // sequence number is only approximate, should not be used
+ // by clients
+ uint64_t sequence_number_;
+ uint64_t size_;
+ std::string app_metadata_;
+ std::string const meta_filename_;
+ std::string const meta_tmp_filename_;
+ // files with relative paths (without "/" prefix!!)
+ std::vector<std::shared_ptr<FileInfo>> files_;
+ std::unordered_map<std::string, std::shared_ptr<FileInfo>>* file_infos_;
+ Env* env_;
+
+ static const size_t max_backup_meta_file_size_ = 10 * 1024 * 1024; // 10MB
+ }; // BackupMeta
+
+ inline std::string GetAbsolutePath(
+ const std::string &relative_path = "") const {
+ assert(relative_path.size() == 0 || relative_path[0] != '/');
+ return options_.backup_dir + "/" + relative_path;
+ }
+ inline std::string GetPrivateDirRel() const {
+ return "private";
+ }
+ inline std::string GetSharedChecksumDirRel() const {
+ return "shared_checksum";
+ }
+ inline std::string GetPrivateFileRel(BackupID backup_id,
+ bool tmp = false,
+ const std::string& file = "") const {
+ assert(file.size() == 0 || file[0] != '/');
+ return GetPrivateDirRel() + "/" + ROCKSDB_NAMESPACE::ToString(backup_id) +
+ (tmp ? ".tmp" : "") + "/" + file;
+ }
+ inline std::string GetSharedFileRel(const std::string& file = "",
+ bool tmp = false) const {
+ assert(file.size() == 0 || file[0] != '/');
+ return std::string("shared/") + (tmp ? "." : "") + file +
+ (tmp ? ".tmp" : "");
+ }
+ inline std::string GetSharedFileWithChecksumRel(const std::string& file = "",
+ bool tmp = false) const {
+ assert(file.size() == 0 || file[0] != '/');
+ return GetSharedChecksumDirRel() + "/" + (tmp ? "." : "") + file +
+ (tmp ? ".tmp" : "");
+ }
+ inline std::string GetSharedFileWithChecksum(const std::string& file,
+ const uint32_t checksum_value,
+ const uint64_t file_size) const {
+ assert(file.size() == 0 || file[0] != '/');
+ std::string file_copy = file;
+ return file_copy.insert(file_copy.find_last_of('.'),
+ "_" + ROCKSDB_NAMESPACE::ToString(checksum_value) +
+ "_" + ROCKSDB_NAMESPACE::ToString(file_size));
+ }
+ inline std::string GetFileFromChecksumFile(const std::string& file) const {
+ assert(file.size() == 0 || file[0] != '/');
+ std::string file_copy = file;
+ size_t first_underscore = file_copy.find_first_of('_');
+ return file_copy.erase(first_underscore,
+ file_copy.find_last_of('.') - first_underscore);
+ }
+ inline std::string GetBackupMetaDir() const {
+ return GetAbsolutePath("meta");
+ }
+ inline std::string GetBackupMetaFile(BackupID backup_id, bool tmp) const {
+ return GetBackupMetaDir() + "/" + (tmp ? "." : "") +
+ ROCKSDB_NAMESPACE::ToString(backup_id) + (tmp ? ".tmp" : "");
+ }
+
+ // If size_limit == 0, there is no size limit, copy everything.
+ //
+ // Exactly one of src and contents must be non-empty.
+ //
+ // @param src If non-empty, the file is copied from this pathname.
+ // @param contents If non-empty, the file will be created with these contents.
+ Status CopyOrCreateFile(const std::string& src, const std::string& dst,
+ const std::string& contents, Env* src_env,
+ Env* dst_env, const EnvOptions& src_env_options,
+ bool sync, RateLimiter* rate_limiter,
+ uint64_t* size = nullptr,
+ uint32_t* checksum_value = nullptr,
+ uint64_t size_limit = 0,
+ std::function<void()> progress_callback = []() {});
+
+ Status CalculateChecksum(const std::string& src, Env* src_env,
+ const EnvOptions& src_env_options,
+ uint64_t size_limit, uint32_t* checksum_value);
+
+ struct CopyOrCreateResult {
+ uint64_t size;
+ uint32_t checksum_value;
+ Status status;
+ };
+
+ // Exactly one of src_path and contents must be non-empty. If src_path is
+ // non-empty, the file is copied from this pathname. Otherwise, if contents is
+ // non-empty, the file will be created at dst_path with these contents.
+ struct CopyOrCreateWorkItem {
+ std::string src_path;
+ std::string dst_path;
+ std::string contents;
+ Env* src_env;
+ Env* dst_env;
+ EnvOptions src_env_options;
+ bool sync;
+ RateLimiter* rate_limiter;
+ uint64_t size_limit;
+ std::promise<CopyOrCreateResult> result;
+ std::function<void()> progress_callback;
+
+ CopyOrCreateWorkItem()
+ : src_path(""),
+ dst_path(""),
+ contents(""),
+ src_env(nullptr),
+ dst_env(nullptr),
+ src_env_options(),
+ sync(false),
+ rate_limiter(nullptr),
+ size_limit(0) {}
+
+ CopyOrCreateWorkItem(const CopyOrCreateWorkItem&) = delete;
+ CopyOrCreateWorkItem& operator=(const CopyOrCreateWorkItem&) = delete;
+
+ CopyOrCreateWorkItem(CopyOrCreateWorkItem&& o) ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ CopyOrCreateWorkItem& operator=(CopyOrCreateWorkItem&& o) ROCKSDB_NOEXCEPT {
+ src_path = std::move(o.src_path);
+ dst_path = std::move(o.dst_path);
+ contents = std::move(o.contents);
+ src_env = o.src_env;
+ dst_env = o.dst_env;
+ src_env_options = std::move(o.src_env_options);
+ sync = o.sync;
+ rate_limiter = o.rate_limiter;
+ size_limit = o.size_limit;
+ result = std::move(o.result);
+ progress_callback = std::move(o.progress_callback);
+ return *this;
+ }
+
+ CopyOrCreateWorkItem(std::string _src_path, std::string _dst_path,
+ std::string _contents, Env* _src_env, Env* _dst_env,
+ EnvOptions _src_env_options, bool _sync,
+ RateLimiter* _rate_limiter, uint64_t _size_limit,
+ std::function<void()> _progress_callback = []() {})
+ : src_path(std::move(_src_path)),
+ dst_path(std::move(_dst_path)),
+ contents(std::move(_contents)),
+ src_env(_src_env),
+ dst_env(_dst_env),
+ src_env_options(std::move(_src_env_options)),
+ sync(_sync),
+ rate_limiter(_rate_limiter),
+ size_limit(_size_limit),
+ progress_callback(_progress_callback) {}
+ };
+
+ struct BackupAfterCopyOrCreateWorkItem {
+ std::future<CopyOrCreateResult> result;
+ bool shared;
+ bool needed_to_copy;
+ Env* backup_env;
+ std::string dst_path_tmp;
+ std::string dst_path;
+ std::string dst_relative;
+ BackupAfterCopyOrCreateWorkItem()
+ : shared(false),
+ needed_to_copy(false),
+ backup_env(nullptr),
+ dst_path_tmp(""),
+ dst_path(""),
+ dst_relative("") {}
+
+ BackupAfterCopyOrCreateWorkItem(BackupAfterCopyOrCreateWorkItem&& o)
+ ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ BackupAfterCopyOrCreateWorkItem& operator=(
+ BackupAfterCopyOrCreateWorkItem&& o) ROCKSDB_NOEXCEPT {
+ result = std::move(o.result);
+ shared = o.shared;
+ needed_to_copy = o.needed_to_copy;
+ backup_env = o.backup_env;
+ dst_path_tmp = std::move(o.dst_path_tmp);
+ dst_path = std::move(o.dst_path);
+ dst_relative = std::move(o.dst_relative);
+ return *this;
+ }
+
+ BackupAfterCopyOrCreateWorkItem(std::future<CopyOrCreateResult>&& _result,
+ bool _shared, bool _needed_to_copy,
+ Env* _backup_env, std::string _dst_path_tmp,
+ std::string _dst_path,
+ std::string _dst_relative)
+ : result(std::move(_result)),
+ shared(_shared),
+ needed_to_copy(_needed_to_copy),
+ backup_env(_backup_env),
+ dst_path_tmp(std::move(_dst_path_tmp)),
+ dst_path(std::move(_dst_path)),
+ dst_relative(std::move(_dst_relative)) {}
+ };
+
+ struct RestoreAfterCopyOrCreateWorkItem {
+ std::future<CopyOrCreateResult> result;
+ uint32_t checksum_value;
+ RestoreAfterCopyOrCreateWorkItem()
+ : checksum_value(0) {}
+ RestoreAfterCopyOrCreateWorkItem(std::future<CopyOrCreateResult>&& _result,
+ uint32_t _checksum_value)
+ : result(std::move(_result)), checksum_value(_checksum_value) {}
+ RestoreAfterCopyOrCreateWorkItem(RestoreAfterCopyOrCreateWorkItem&& o)
+ ROCKSDB_NOEXCEPT {
+ *this = std::move(o);
+ }
+
+ RestoreAfterCopyOrCreateWorkItem& operator=(
+ RestoreAfterCopyOrCreateWorkItem&& o) ROCKSDB_NOEXCEPT {
+ result = std::move(o.result);
+ checksum_value = o.checksum_value;
+ return *this;
+ }
+ };
+
+ bool initialized_;
+ std::mutex byte_report_mutex_;
+ channel<CopyOrCreateWorkItem> files_to_copy_or_create_;
+ std::vector<port::Thread> threads_;
+ // Certain operations like PurgeOldBackups and DeleteBackup will trigger
+ // automatic GarbageCollect (true) unless we've already done one in this
+ // session and have not failed to delete backup files since then (false).
+ bool might_need_garbage_collect_ = true;
+
+ // Adds a file to the backup work queue to be copied or created if it doesn't
+ // already exist.
+ //
+ // Exactly one of src_dir and contents must be non-empty.
+ //
+ // @param src_dir If non-empty, the file in this directory named fname will be
+ // copied.
+ // @param fname Name of destination file and, in case of copy, source file.
+ // @param contents If non-empty, the file will be created with these contents.
+ Status AddBackupFileWorkItem(
+ std::unordered_set<std::string>& live_dst_paths,
+ std::vector<BackupAfterCopyOrCreateWorkItem>& backup_items_to_finish,
+ BackupID backup_id, bool shared, const std::string& src_dir,
+ const std::string& fname, // starts with "/"
+ const EnvOptions& src_env_options, RateLimiter* rate_limiter,
+ uint64_t size_bytes, uint64_t size_limit = 0,
+ bool shared_checksum = false,
+ std::function<void()> progress_callback = []() {},
+ const std::string& contents = std::string());
+
+ // backup state data
+ BackupID latest_backup_id_;
+ BackupID latest_valid_backup_id_;
+ std::map<BackupID, std::unique_ptr<BackupMeta>> backups_;
+ std::map<BackupID, std::pair<Status, std::unique_ptr<BackupMeta>>>
+ corrupt_backups_;
+ std::unordered_map<std::string,
+ std::shared_ptr<FileInfo>> backuped_file_infos_;
+ std::atomic<bool> stop_backup_;
+
+ // options data
+ BackupableDBOptions options_;
+ Env* db_env_;
+ Env* backup_env_;
+
+ // directories
+ std::unique_ptr<Directory> backup_directory_;
+ std::unique_ptr<Directory> shared_directory_;
+ std::unique_ptr<Directory> meta_directory_;
+ std::unique_ptr<Directory> private_directory_;
+
+ static const size_t kDefaultCopyFileBufferSize = 5 * 1024 * 1024LL; // 5MB
+ size_t copy_file_buffer_size_;
+ bool read_only_;
+ BackupStatistics backup_statistics_;
+ static const size_t kMaxAppMetaSize = 1024 * 1024; // 1MB
+};
+
+Status BackupEngine::Open(Env* env, const BackupableDBOptions& options,
+ BackupEngine** backup_engine_ptr) {
+ std::unique_ptr<BackupEngineImpl> backup_engine(
+ new BackupEngineImpl(env, options));
+ auto s = backup_engine->Initialize();
+ if (!s.ok()) {
+ *backup_engine_ptr = nullptr;
+ return s;
+ }
+ *backup_engine_ptr = backup_engine.release();
+ return Status::OK();
+}
+
+BackupEngineImpl::BackupEngineImpl(Env* db_env,
+ const BackupableDBOptions& options,
+ bool read_only)
+ : initialized_(false),
+ latest_backup_id_(0),
+ latest_valid_backup_id_(0),
+ stop_backup_(false),
+ options_(options),
+ db_env_(db_env),
+ backup_env_(options.backup_env != nullptr ? options.backup_env : db_env_),
+ copy_file_buffer_size_(kDefaultCopyFileBufferSize),
+ read_only_(read_only) {
+ if (options_.backup_rate_limiter == nullptr &&
+ options_.backup_rate_limit > 0) {
+ options_.backup_rate_limiter.reset(
+ NewGenericRateLimiter(options_.backup_rate_limit));
+ }
+ if (options_.restore_rate_limiter == nullptr &&
+ options_.restore_rate_limit > 0) {
+ options_.restore_rate_limiter.reset(
+ NewGenericRateLimiter(options_.restore_rate_limit));
+ }
+}
+
+BackupEngineImpl::~BackupEngineImpl() {
+ files_to_copy_or_create_.sendEof();
+ for (auto& t : threads_) {
+ t.join();
+ }
+ LogFlush(options_.info_log);
+}
+
+Status BackupEngineImpl::Initialize() {
+ assert(!initialized_);
+ initialized_ = true;
+ if (read_only_) {
+ ROCKS_LOG_INFO(options_.info_log, "Starting read_only backup engine");
+ }
+ options_.Dump(options_.info_log);
+
+ if (!read_only_) {
+ // we might need to clean up from previous crash or I/O errors
+ might_need_garbage_collect_ = true;
+
+ if (options_.max_valid_backups_to_open != port::kMaxInt32) {
+ options_.max_valid_backups_to_open = port::kMaxInt32;
+ ROCKS_LOG_WARN(
+ options_.info_log,
+ "`max_valid_backups_to_open` is not set to the default value. Ignoring "
+ "its value since BackupEngine is not read-only.");
+ }
+
+ // gather the list of directories that we need to create
+ std::vector<std::pair<std::string, std::unique_ptr<Directory>*>>
+ directories;
+ directories.emplace_back(GetAbsolutePath(), &backup_directory_);
+ if (options_.share_table_files) {
+ if (options_.share_files_with_checksum) {
+ directories.emplace_back(
+ GetAbsolutePath(GetSharedFileWithChecksumRel()),
+ &shared_directory_);
+ } else {
+ directories.emplace_back(GetAbsolutePath(GetSharedFileRel()),
+ &shared_directory_);
+ }
+ }
+ directories.emplace_back(GetAbsolutePath(GetPrivateDirRel()),
+ &private_directory_);
+ directories.emplace_back(GetBackupMetaDir(), &meta_directory_);
+ // create all the dirs we need
+ for (const auto& d : directories) {
+ auto s = backup_env_->CreateDirIfMissing(d.first);
+ if (s.ok()) {
+ s = backup_env_->NewDirectory(d.first, d.second);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+
+ std::vector<std::string> backup_meta_files;
+ {
+ auto s = backup_env_->GetChildren(GetBackupMetaDir(), &backup_meta_files);
+ if (s.IsNotFound()) {
+ return Status::NotFound(GetBackupMetaDir() + " is missing");
+ } else if (!s.ok()) {
+ return s;
+ }
+ }
+ // create backups_ structure
+ for (auto& file : backup_meta_files) {
+ if (file == "." || file == "..") {
+ continue;
+ }
+ ROCKS_LOG_INFO(options_.info_log, "Detected backup %s", file.c_str());
+ BackupID backup_id = 0;
+ sscanf(file.c_str(), "%u", &backup_id);
+ if (backup_id == 0 || file != ROCKSDB_NAMESPACE::ToString(backup_id)) {
+ if (!read_only_) {
+ // invalid file name, delete that
+ auto s = backup_env_->DeleteFile(GetBackupMetaDir() + "/" + file);
+ ROCKS_LOG_INFO(options_.info_log,
+ "Unrecognized meta file %s, deleting -- %s",
+ file.c_str(), s.ToString().c_str());
+ }
+ continue;
+ }
+ assert(backups_.find(backup_id) == backups_.end());
+ backups_.insert(std::make_pair(
+ backup_id, std::unique_ptr<BackupMeta>(new BackupMeta(
+ GetBackupMetaFile(backup_id, false /* tmp */),
+ GetBackupMetaFile(backup_id, true /* tmp */),
+ &backuped_file_infos_, backup_env_))));
+ }
+
+ latest_backup_id_ = 0;
+ latest_valid_backup_id_ = 0;
+ if (options_.destroy_old_data) { // Destroy old data
+ assert(!read_only_);
+ ROCKS_LOG_INFO(
+ options_.info_log,
+ "Backup Engine started with destroy_old_data == true, deleting all "
+ "backups");
+ auto s = PurgeOldBackups(0);
+ if (s.ok()) {
+ s = GarbageCollect();
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ } else { // Load data from storage
+ std::unordered_map<std::string, uint64_t> abs_path_to_size;
+ for (const auto& rel_dir :
+ {GetSharedFileRel(), GetSharedFileWithChecksumRel()}) {
+ const auto abs_dir = GetAbsolutePath(rel_dir);
+ InsertPathnameToSizeBytes(abs_dir, backup_env_, &abs_path_to_size);
+ }
+ // load the backups if any, until valid_backups_to_open of the latest
+ // non-corrupted backups have been successfully opened.
+ int valid_backups_to_open = options_.max_valid_backups_to_open;
+ for (auto backup_iter = backups_.rbegin();
+ backup_iter != backups_.rend();
+ ++backup_iter) {
+ assert(latest_backup_id_ == 0 || latest_backup_id_ > backup_iter->first);
+ if (latest_backup_id_ == 0) {
+ latest_backup_id_ = backup_iter->first;
+ }
+ if (valid_backups_to_open == 0) {
+ break;
+ }
+
+ InsertPathnameToSizeBytes(
+ GetAbsolutePath(GetPrivateFileRel(backup_iter->first)), backup_env_,
+ &abs_path_to_size);
+ Status s = backup_iter->second->LoadFromFile(options_.backup_dir,
+ abs_path_to_size);
+ if (s.IsCorruption()) {
+ ROCKS_LOG_INFO(options_.info_log, "Backup %u corrupted -- %s",
+ backup_iter->first, s.ToString().c_str());
+ corrupt_backups_.insert(
+ std::make_pair(backup_iter->first,
+ std::make_pair(s, std::move(backup_iter->second))));
+ } else if (!s.ok()) {
+ // Distinguish corruption errors from errors in the backup Env.
+ // Errors in the backup Env (i.e., this code path) will cause Open() to
+ // fail, whereas corruption errors would not cause Open() failures.
+ return s;
+ } else {
+ ROCKS_LOG_INFO(options_.info_log, "Loading backup %" PRIu32 " OK:\n%s",
+ backup_iter->first,
+ backup_iter->second->GetInfoString().c_str());
+ assert(latest_valid_backup_id_ == 0 ||
+ latest_valid_backup_id_ > backup_iter->first);
+ if (latest_valid_backup_id_ == 0) {
+ latest_valid_backup_id_ = backup_iter->first;
+ }
+ --valid_backups_to_open;
+ }
+ }
+
+ for (const auto& corrupt : corrupt_backups_) {
+ backups_.erase(backups_.find(corrupt.first));
+ }
+ // erase the backups before max_valid_backups_to_open
+ int num_unopened_backups;
+ if (options_.max_valid_backups_to_open == 0) {
+ num_unopened_backups = 0;
+ } else {
+ num_unopened_backups =
+ std::max(0, static_cast<int>(backups_.size()) -
+ options_.max_valid_backups_to_open);
+ }
+ for (int i = 0; i < num_unopened_backups; ++i) {
+ assert(backups_.begin()->second->Empty());
+ backups_.erase(backups_.begin());
+ }
+ }
+
+ ROCKS_LOG_INFO(options_.info_log, "Latest backup is %u", latest_backup_id_);
+ ROCKS_LOG_INFO(options_.info_log, "Latest valid backup is %u",
+ latest_valid_backup_id_);
+
+ // set up threads perform copies from files_to_copy_or_create_ in the
+ // background
+ for (int t = 0; t < options_.max_background_operations; t++) {
+ threads_.emplace_back([this]() {
+#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2, 12)
+ pthread_setname_np(pthread_self(), "backup_engine");
+#endif
+#endif
+ CopyOrCreateWorkItem work_item;
+ while (files_to_copy_or_create_.read(work_item)) {
+ CopyOrCreateResult result;
+ result.status = CopyOrCreateFile(
+ work_item.src_path, work_item.dst_path, work_item.contents,
+ work_item.src_env, work_item.dst_env, work_item.src_env_options,
+ work_item.sync, work_item.rate_limiter, &result.size,
+ &result.checksum_value, work_item.size_limit,
+ work_item.progress_callback);
+ work_item.result.set_value(std::move(result));
+ }
+ });
+ }
+ ROCKS_LOG_INFO(options_.info_log, "Initialized BackupEngine");
+
+ return Status::OK();
+}
+
+Status BackupEngineImpl::CreateNewBackupWithMetadata(
+ DB* db, const std::string& app_metadata, bool flush_before_backup,
+ std::function<void()> progress_callback) {
+ assert(initialized_);
+ assert(!read_only_);
+ if (app_metadata.size() > kMaxAppMetaSize) {
+ return Status::InvalidArgument("App metadata too large");
+ }
+
+ BackupID new_backup_id = latest_backup_id_ + 1;
+
+ assert(backups_.find(new_backup_id) == backups_.end());
+
+ auto private_dir = GetAbsolutePath(GetPrivateFileRel(new_backup_id));
+ Status s = backup_env_->FileExists(private_dir);
+ if (s.ok()) {
+ // maybe last backup failed and left partial state behind, clean it up.
+ // need to do this before updating backups_ such that a private dir
+ // named after new_backup_id will be cleaned up.
+ // (If an incomplete new backup is followed by an incomplete delete
+ // of the latest full backup, then there could be more than one next
+ // id with a private dir, the last thing to be deleted in delete
+ // backup, but all will be cleaned up with a GarbageCollect.)
+ s = GarbageCollect();
+ } else if (s.IsNotFound()) {
+ // normal case, the new backup's private dir doesn't exist yet
+ s = Status::OK();
+ }
+
+ auto ret = backups_.insert(std::make_pair(
+ new_backup_id, std::unique_ptr<BackupMeta>(new BackupMeta(
+ GetBackupMetaFile(new_backup_id, false /* tmp */),
+ GetBackupMetaFile(new_backup_id, true /* tmp */),
+ &backuped_file_infos_, backup_env_))));
+ assert(ret.second == true);
+ auto& new_backup = ret.first->second;
+ new_backup->RecordTimestamp();
+ new_backup->SetAppMetadata(app_metadata);
+
+ auto start_backup = backup_env_->NowMicros();
+
+ ROCKS_LOG_INFO(options_.info_log,
+ "Started the backup process -- creating backup %u",
+ new_backup_id);
+ if (s.ok()) {
+ s = backup_env_->CreateDir(private_dir);
+ }
+
+ RateLimiter* rate_limiter = options_.backup_rate_limiter.get();
+ if (rate_limiter) {
+ copy_file_buffer_size_ = static_cast<size_t>(rate_limiter->GetSingleBurstBytes());
+ }
+
+ // A set into which we will insert the dst_paths that are calculated for live
+ // files and live WAL files.
+ // This is used to check whether a live files shares a dst_path with another
+ // live file.
+ std::unordered_set<std::string> live_dst_paths;
+
+ std::vector<BackupAfterCopyOrCreateWorkItem> backup_items_to_finish;
+ // Add a CopyOrCreateWorkItem to the channel for each live file
+ db->DisableFileDeletions();
+ if (s.ok()) {
+ CheckpointImpl checkpoint(db);
+ uint64_t sequence_number = 0;
+ DBOptions db_options = db->GetDBOptions();
+ EnvOptions src_raw_env_options(db_options);
+ s = checkpoint.CreateCustomCheckpoint(
+ db_options,
+ [&](const std::string& /*src_dirname*/, const std::string& /*fname*/,
+ FileType) {
+ // custom checkpoint will switch to calling copy_file_cb after it sees
+ // NotSupported returned from link_file_cb.
+ return Status::NotSupported();
+ } /* link_file_cb */,
+ [&](const std::string& src_dirname, const std::string& fname,
+ uint64_t size_limit_bytes, FileType type) {
+ if (type == kLogFile && !options_.backup_log_files) {
+ return Status::OK();
+ }
+ Log(options_.info_log, "add file for backup %s", fname.c_str());
+ uint64_t size_bytes = 0;
+ Status st;
+ if (type == kTableFile) {
+ st = db_env_->GetFileSize(src_dirname + fname, &size_bytes);
+ }
+ EnvOptions src_env_options;
+ switch (type) {
+ case kLogFile:
+ src_env_options =
+ db_env_->OptimizeForLogRead(src_raw_env_options);
+ break;
+ case kTableFile:
+ src_env_options = db_env_->OptimizeForCompactionTableRead(
+ src_raw_env_options, ImmutableDBOptions(db_options));
+ break;
+ case kDescriptorFile:
+ src_env_options =
+ db_env_->OptimizeForManifestRead(src_raw_env_options);
+ break;
+ default:
+ // Other backed up files (like options file) are not read by live
+ // DB, so don't need to worry about avoiding mixing buffered and
+ // direct I/O. Just use plain defaults.
+ src_env_options = src_raw_env_options;
+ break;
+ }
+ if (st.ok()) {
+ st = AddBackupFileWorkItem(
+ live_dst_paths, backup_items_to_finish, new_backup_id,
+ options_.share_table_files && type == kTableFile, src_dirname,
+ fname, src_env_options, rate_limiter, size_bytes,
+ size_limit_bytes,
+ options_.share_files_with_checksum && type == kTableFile,
+ progress_callback);
+ }
+ return st;
+ } /* copy_file_cb */,
+ [&](const std::string& fname, const std::string& contents, FileType) {
+ Log(options_.info_log, "add file for backup %s", fname.c_str());
+ return AddBackupFileWorkItem(
+ live_dst_paths, backup_items_to_finish, new_backup_id,
+ false /* shared */, "" /* src_dir */, fname,
+ EnvOptions() /* src_env_options */, rate_limiter, contents.size(),
+ 0 /* size_limit */, false /* shared_checksum */,
+ progress_callback, contents);
+ } /* create_file_cb */,
+ &sequence_number, flush_before_backup ? 0 : port::kMaxUint64);
+ if (s.ok()) {
+ new_backup->SetSequenceNumber(sequence_number);
+ }
+ }
+ ROCKS_LOG_INFO(options_.info_log, "add files for backup done, wait finish.");
+ Status item_status;
+ for (auto& item : backup_items_to_finish) {
+ item.result.wait();
+ auto result = item.result.get();
+ item_status = result.status;
+ if (item_status.ok() && item.shared && item.needed_to_copy) {
+ item_status = item.backup_env->RenameFile(item.dst_path_tmp,
+ item.dst_path);
+ }
+ if (item_status.ok()) {
+ item_status = new_backup.get()->AddFile(
+ std::make_shared<FileInfo>(item.dst_relative,
+ result.size,
+ result.checksum_value));
+ }
+ if (!item_status.ok()) {
+ s = item_status;
+ }
+ }
+
+ // we copied all the files, enable file deletions
+ db->EnableFileDeletions(false);
+
+ auto backup_time = backup_env_->NowMicros() - start_backup;
+
+ if (s.ok()) {
+ // persist the backup metadata on the disk
+ s = new_backup->StoreToFile(options_.sync);
+ }
+ if (s.ok() && options_.sync) {
+ std::unique_ptr<Directory> backup_private_directory;
+ backup_env_->NewDirectory(
+ GetAbsolutePath(GetPrivateFileRel(new_backup_id, false)),
+ &backup_private_directory);
+ if (backup_private_directory != nullptr) {
+ s = backup_private_directory->Fsync();
+ }
+ if (s.ok() && private_directory_ != nullptr) {
+ s = private_directory_->Fsync();
+ }
+ if (s.ok() && meta_directory_ != nullptr) {
+ s = meta_directory_->Fsync();
+ }
+ if (s.ok() && shared_directory_ != nullptr) {
+ s = shared_directory_->Fsync();
+ }
+ if (s.ok() && backup_directory_ != nullptr) {
+ s = backup_directory_->Fsync();
+ }
+ }
+
+ if (s.ok()) {
+ backup_statistics_.IncrementNumberSuccessBackup();
+ }
+ if (!s.ok()) {
+ backup_statistics_.IncrementNumberFailBackup();
+ // clean all the files we might have created
+ ROCKS_LOG_INFO(options_.info_log, "Backup failed -- %s",
+ s.ToString().c_str());
+ ROCKS_LOG_INFO(options_.info_log, "Backup Statistics %s\n",
+ backup_statistics_.ToString().c_str());
+ // delete files that we might have already written
+ might_need_garbage_collect_ = true;
+ DeleteBackup(new_backup_id);
+ return s;
+ }
+
+ // here we know that we succeeded and installed the new backup
+ // in the LATEST_BACKUP file
+ latest_backup_id_ = new_backup_id;
+ latest_valid_backup_id_ = new_backup_id;
+ ROCKS_LOG_INFO(options_.info_log, "Backup DONE. All is good");
+
+ // backup_speed is in byte/second
+ double backup_speed = new_backup->GetSize() / (1.048576 * backup_time);
+ ROCKS_LOG_INFO(options_.info_log, "Backup number of files: %u",
+ new_backup->GetNumberFiles());
+ char human_size[16];
+ AppendHumanBytes(new_backup->GetSize(), human_size, sizeof(human_size));
+ ROCKS_LOG_INFO(options_.info_log, "Backup size: %s", human_size);
+ ROCKS_LOG_INFO(options_.info_log, "Backup time: %" PRIu64 " microseconds",
+ backup_time);
+ ROCKS_LOG_INFO(options_.info_log, "Backup speed: %.3f MB/s", backup_speed);
+ ROCKS_LOG_INFO(options_.info_log, "Backup Statistics %s",
+ backup_statistics_.ToString().c_str());
+ return s;
+}
+
+Status BackupEngineImpl::PurgeOldBackups(uint32_t num_backups_to_keep) {
+ assert(initialized_);
+ assert(!read_only_);
+
+ // Best effort deletion even with errors
+ Status overall_status = Status::OK();
+
+ ROCKS_LOG_INFO(options_.info_log, "Purging old backups, keeping %u",
+ num_backups_to_keep);
+ std::vector<BackupID> to_delete;
+ auto itr = backups_.begin();
+ while ((backups_.size() - to_delete.size()) > num_backups_to_keep) {
+ to_delete.push_back(itr->first);
+ itr++;
+ }
+ for (auto backup_id : to_delete) {
+ auto s = DeleteBackupInternal(backup_id);
+ if (!s.ok()) {
+ overall_status = s;
+ }
+ }
+ // Clean up after any incomplete backup deletion, potentially from
+ // earlier session.
+ if (might_need_garbage_collect_) {
+ auto s = GarbageCollect();
+ if (!s.ok() && overall_status.ok()) {
+ overall_status = s;
+ }
+ }
+ return overall_status;
+}
+
+Status BackupEngineImpl::DeleteBackup(BackupID backup_id) {
+ auto s1 = DeleteBackupInternal(backup_id);
+ auto s2 = Status::OK();
+
+ // Clean up after any incomplete backup deletion, potentially from
+ // earlier session.
+ if (might_need_garbage_collect_) {
+ s2 = GarbageCollect();
+ }
+
+ if (!s1.ok()) {
+ return s1;
+ } else {
+ return s2;
+ }
+}
+
+// Does not auto-GarbageCollect
+Status BackupEngineImpl::DeleteBackupInternal(BackupID backup_id) {
+ assert(initialized_);
+ assert(!read_only_);
+
+ ROCKS_LOG_INFO(options_.info_log, "Deleting backup %u", backup_id);
+ auto backup = backups_.find(backup_id);
+ if (backup != backups_.end()) {
+ auto s = backup->second->Delete();
+ if (!s.ok()) {
+ return s;
+ }
+ backups_.erase(backup);
+ } else {
+ auto corrupt = corrupt_backups_.find(backup_id);
+ if (corrupt == corrupt_backups_.end()) {
+ return Status::NotFound("Backup not found");
+ }
+ auto s = corrupt->second.second->Delete();
+ if (!s.ok()) {
+ return s;
+ }
+ corrupt_backups_.erase(corrupt);
+ }
+
+ // After removing meta file, best effort deletion even with errors.
+ // (Don't delete other files if we can't delete the meta file right
+ // now.)
+ std::vector<std::string> to_delete;
+ for (auto& itr : backuped_file_infos_) {
+ if (itr.second->refs == 0) {
+ Status s = backup_env_->DeleteFile(GetAbsolutePath(itr.first));
+ ROCKS_LOG_INFO(options_.info_log, "Deleting %s -- %s", itr.first.c_str(),
+ s.ToString().c_str());
+ to_delete.push_back(itr.first);
+ if (!s.ok()) {
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+ }
+ for (auto& td : to_delete) {
+ backuped_file_infos_.erase(td);
+ }
+
+ // take care of private dirs -- GarbageCollect() will take care of them
+ // if they are not empty
+ std::string private_dir = GetPrivateFileRel(backup_id);
+ Status s = backup_env_->DeleteDir(GetAbsolutePath(private_dir));
+ ROCKS_LOG_INFO(options_.info_log, "Deleting private dir %s -- %s",
+ private_dir.c_str(), s.ToString().c_str());
+ if (!s.ok()) {
+ // Full gc or trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ return Status::OK();
+}
+
+void BackupEngineImpl::GetBackupInfo(std::vector<BackupInfo>* backup_info) {
+ assert(initialized_);
+ backup_info->reserve(backups_.size());
+ for (auto& backup : backups_) {
+ if (!backup.second->Empty()) {
+ backup_info->push_back(BackupInfo(
+ backup.first, backup.second->GetTimestamp(), backup.second->GetSize(),
+ backup.second->GetNumberFiles(), backup.second->GetAppMetadata()));
+ }
+ }
+}
+
+void
+BackupEngineImpl::GetCorruptedBackups(
+ std::vector<BackupID>* corrupt_backup_ids) {
+ assert(initialized_);
+ corrupt_backup_ids->reserve(corrupt_backups_.size());
+ for (auto& backup : corrupt_backups_) {
+ corrupt_backup_ids->push_back(backup.first);
+ }
+}
+
+Status BackupEngineImpl::RestoreDBFromBackup(
+ BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options) {
+ assert(initialized_);
+ auto corrupt_itr = corrupt_backups_.find(backup_id);
+ if (corrupt_itr != corrupt_backups_.end()) {
+ return corrupt_itr->second.first;
+ }
+ auto backup_itr = backups_.find(backup_id);
+ if (backup_itr == backups_.end()) {
+ return Status::NotFound("Backup not found");
+ }
+ auto& backup = backup_itr->second;
+ if (backup->Empty()) {
+ return Status::NotFound("Backup not found");
+ }
+
+ ROCKS_LOG_INFO(options_.info_log, "Restoring backup id %u\n", backup_id);
+ ROCKS_LOG_INFO(options_.info_log, "keep_log_files: %d\n",
+ static_cast<int>(restore_options.keep_log_files));
+
+ // just in case. Ignore errors
+ db_env_->CreateDirIfMissing(db_dir);
+ db_env_->CreateDirIfMissing(wal_dir);
+
+ if (restore_options.keep_log_files) {
+ // delete files in db_dir, but keep all the log files
+ DeleteChildren(db_dir, 1 << kLogFile);
+ // move all the files from archive dir to wal_dir
+ std::string archive_dir = ArchivalDirectory(wal_dir);
+ std::vector<std::string> archive_files;
+ db_env_->GetChildren(archive_dir, &archive_files); // ignore errors
+ for (const auto& f : archive_files) {
+ uint64_t number;
+ FileType type;
+ bool ok = ParseFileName(f, &number, &type);
+ if (ok && type == kLogFile) {
+ ROCKS_LOG_INFO(options_.info_log,
+ "Moving log file from archive/ to wal_dir: %s",
+ f.c_str());
+ Status s =
+ db_env_->RenameFile(archive_dir + "/" + f, wal_dir + "/" + f);
+ if (!s.ok()) {
+ // if we can't move log file from archive_dir to wal_dir,
+ // we should fail, since it might mean data loss
+ return s;
+ }
+ }
+ }
+ } else {
+ DeleteChildren(wal_dir);
+ DeleteChildren(ArchivalDirectory(wal_dir));
+ DeleteChildren(db_dir);
+ }
+
+ RateLimiter* rate_limiter = options_.restore_rate_limiter.get();
+ if (rate_limiter) {
+ copy_file_buffer_size_ = static_cast<size_t>(rate_limiter->GetSingleBurstBytes());
+ }
+ Status s;
+ std::vector<RestoreAfterCopyOrCreateWorkItem> restore_items_to_finish;
+ for (const auto& file_info : backup->GetFiles()) {
+ const std::string &file = file_info->filename;
+ std::string dst;
+ // 1. extract the filename
+ size_t slash = file.find_last_of('/');
+ // file will either be shared/<file>, shared_checksum/<file_crc32_size>
+ // or private/<number>/<file>
+ assert(slash != std::string::npos);
+ dst = file.substr(slash + 1);
+
+ // if the file was in shared_checksum, extract the real file name
+ // in this case the file is <number>_<checksum>_<size>.<type>
+ if (file.substr(0, slash) == GetSharedChecksumDirRel()) {
+ dst = GetFileFromChecksumFile(dst);
+ }
+
+ // 2. find the filetype
+ uint64_t number;
+ FileType type;
+ bool ok = ParseFileName(dst, &number, &type);
+ if (!ok) {
+ return Status::Corruption("Backup corrupted");
+ }
+ // 3. Construct the final path
+ // kLogFile lives in wal_dir and all the rest live in db_dir
+ dst = ((type == kLogFile) ? wal_dir : db_dir) +
+ "/" + dst;
+
+ ROCKS_LOG_INFO(options_.info_log, "Restoring %s to %s\n", file.c_str(),
+ dst.c_str());
+ CopyOrCreateWorkItem copy_or_create_work_item(
+ GetAbsolutePath(file), dst, "" /* contents */, backup_env_, db_env_,
+ EnvOptions() /* src_env_options */, false, rate_limiter,
+ 0 /* size_limit */);
+ RestoreAfterCopyOrCreateWorkItem after_copy_or_create_work_item(
+ copy_or_create_work_item.result.get_future(),
+ file_info->checksum_value);
+ files_to_copy_or_create_.write(std::move(copy_or_create_work_item));
+ restore_items_to_finish.push_back(
+ std::move(after_copy_or_create_work_item));
+ }
+ Status item_status;
+ for (auto& item : restore_items_to_finish) {
+ item.result.wait();
+ auto result = item.result.get();
+ item_status = result.status;
+ // Note: It is possible that both of the following bad-status cases occur
+ // during copying. But, we only return one status.
+ if (!item_status.ok()) {
+ s = item_status;
+ break;
+ } else if (item.checksum_value != result.checksum_value) {
+ s = Status::Corruption("Checksum check failed");
+ break;
+ }
+ }
+
+ ROCKS_LOG_INFO(options_.info_log, "Restoring done -- %s\n",
+ s.ToString().c_str());
+ return s;
+}
+
+Status BackupEngineImpl::VerifyBackup(BackupID backup_id) {
+ assert(initialized_);
+ auto corrupt_itr = corrupt_backups_.find(backup_id);
+ if (corrupt_itr != corrupt_backups_.end()) {
+ return corrupt_itr->second.first;
+ }
+
+ auto backup_itr = backups_.find(backup_id);
+ if (backup_itr == backups_.end()) {
+ return Status::NotFound();
+ }
+
+ auto& backup = backup_itr->second;
+ if (backup->Empty()) {
+ return Status::NotFound();
+ }
+
+ ROCKS_LOG_INFO(options_.info_log, "Verifying backup id %u\n", backup_id);
+
+ std::unordered_map<std::string, uint64_t> curr_abs_path_to_size;
+ for (const auto& rel_dir : {GetPrivateFileRel(backup_id), GetSharedFileRel(),
+ GetSharedFileWithChecksumRel()}) {
+ const auto abs_dir = GetAbsolutePath(rel_dir);
+ InsertPathnameToSizeBytes(abs_dir, backup_env_, &curr_abs_path_to_size);
+ }
+
+ for (const auto& file_info : backup->GetFiles()) {
+ const auto abs_path = GetAbsolutePath(file_info->filename);
+ if (curr_abs_path_to_size.find(abs_path) == curr_abs_path_to_size.end()) {
+ return Status::NotFound("File missing: " + abs_path);
+ }
+ if (file_info->size != curr_abs_path_to_size[abs_path]) {
+ return Status::Corruption("File corrupted: " + abs_path);
+ }
+ }
+ return Status::OK();
+}
+
+Status BackupEngineImpl::CopyOrCreateFile(
+ const std::string& src, const std::string& dst, const std::string& contents,
+ Env* src_env, Env* dst_env, const EnvOptions& src_env_options, bool sync,
+ RateLimiter* rate_limiter, uint64_t* size, uint32_t* checksum_value,
+ uint64_t size_limit, std::function<void()> progress_callback) {
+ assert(src.empty() != contents.empty());
+ Status s;
+ std::unique_ptr<WritableFile> dst_file;
+ std::unique_ptr<SequentialFile> src_file;
+ EnvOptions dst_env_options;
+ dst_env_options.use_mmap_writes = false;
+ // TODO:(gzh) maybe use direct reads/writes here if possible
+ if (size != nullptr) {
+ *size = 0;
+ }
+ if (checksum_value != nullptr) {
+ *checksum_value = 0;
+ }
+
+ // Check if size limit is set. if not, set it to very big number
+ if (size_limit == 0) {
+ size_limit = std::numeric_limits<uint64_t>::max();
+ }
+
+ s = dst_env->NewWritableFile(dst, &dst_file, dst_env_options);
+ if (s.ok() && !src.empty()) {
+ s = src_env->NewSequentialFile(src, &src_file, src_env_options);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<WritableFileWriter> dest_writer(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(dst_file)), dst, dst_env_options));
+ std::unique_ptr<SequentialFileReader> src_reader;
+ std::unique_ptr<char[]> buf;
+ if (!src.empty()) {
+ src_reader.reset(new SequentialFileReader(
+ NewLegacySequentialFileWrapper(src_file), src));
+ buf.reset(new char[copy_file_buffer_size_]);
+ }
+
+ Slice data;
+ uint64_t processed_buffer_size = 0;
+ do {
+ if (stop_backup_.load(std::memory_order_acquire)) {
+ return Status::Incomplete("Backup stopped");
+ }
+ if (!src.empty()) {
+ size_t buffer_to_read = (copy_file_buffer_size_ < size_limit)
+ ? copy_file_buffer_size_
+ : static_cast<size_t>(size_limit);
+ s = src_reader->Read(buffer_to_read, &data, buf.get());
+ processed_buffer_size += buffer_to_read;
+ } else {
+ data = contents;
+ }
+ size_limit -= data.size();
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (size != nullptr) {
+ *size += data.size();
+ }
+ if (checksum_value != nullptr) {
+ *checksum_value =
+ crc32c::Extend(*checksum_value, data.data(), data.size());
+ }
+ s = dest_writer->Append(data);
+ if (rate_limiter != nullptr) {
+ rate_limiter->Request(data.size(), Env::IO_LOW, nullptr /* stats */,
+ RateLimiter::OpType::kWrite);
+ }
+ if (processed_buffer_size > options_.callback_trigger_interval_size) {
+ processed_buffer_size -= options_.callback_trigger_interval_size;
+ std::lock_guard<std::mutex> lock(byte_report_mutex_);
+ progress_callback();
+ }
+ } while (s.ok() && contents.empty() && data.size() > 0 && size_limit > 0);
+
+ if (s.ok() && sync) {
+ s = dest_writer->Sync(false);
+ }
+ if (s.ok()) {
+ s = dest_writer->Close();
+ }
+ return s;
+}
+
+// fname will always start with "/"
+Status BackupEngineImpl::AddBackupFileWorkItem(
+ std::unordered_set<std::string>& live_dst_paths,
+ std::vector<BackupAfterCopyOrCreateWorkItem>& backup_items_to_finish,
+ BackupID backup_id, bool shared, const std::string& src_dir,
+ const std::string& fname, const EnvOptions& src_env_options,
+ RateLimiter* rate_limiter, uint64_t size_bytes, uint64_t size_limit,
+ bool shared_checksum, std::function<void()> progress_callback,
+ const std::string& contents) {
+ assert(!fname.empty() && fname[0] == '/');
+ assert(contents.empty() != src_dir.empty());
+
+ std::string dst_relative = fname.substr(1);
+ std::string dst_relative_tmp;
+ Status s;
+ uint32_t checksum_value = 0;
+
+ if (shared && shared_checksum) {
+ // add checksum and file length to the file name
+ s = CalculateChecksum(src_dir + fname, db_env_, src_env_options, size_limit,
+ &checksum_value);
+ if (!s.ok()) {
+ return s;
+ }
+ if (size_bytes == port::kMaxUint64) {
+ return Status::NotFound("File missing: " + src_dir + fname);
+ }
+ dst_relative =
+ GetSharedFileWithChecksum(dst_relative, checksum_value, size_bytes);
+ dst_relative_tmp = GetSharedFileWithChecksumRel(dst_relative, true);
+ dst_relative = GetSharedFileWithChecksumRel(dst_relative, false);
+ } else if (shared) {
+ dst_relative_tmp = GetSharedFileRel(dst_relative, true);
+ dst_relative = GetSharedFileRel(dst_relative, false);
+ } else {
+ dst_relative = GetPrivateFileRel(backup_id, false, dst_relative);
+ }
+
+ // We copy into `temp_dest_path` and, once finished, rename it to
+ // `final_dest_path`. This allows files to atomically appear at
+ // `final_dest_path`. We can copy directly to the final path when atomicity
+ // is unnecessary, like for files in private backup directories.
+ const std::string* copy_dest_path;
+ std::string temp_dest_path;
+ std::string final_dest_path = GetAbsolutePath(dst_relative);
+ if (!dst_relative_tmp.empty()) {
+ temp_dest_path = GetAbsolutePath(dst_relative_tmp);
+ copy_dest_path = &temp_dest_path;
+ } else {
+ copy_dest_path = &final_dest_path;
+ }
+
+ // if it's shared, we also need to check if it exists -- if it does, no need
+ // to copy it again.
+ bool need_to_copy = true;
+ // true if final_dest_path is the same path as another live file
+ const bool same_path =
+ live_dst_paths.find(final_dest_path) != live_dst_paths.end();
+
+ bool file_exists = false;
+ if (shared && !same_path) {
+ Status exist = backup_env_->FileExists(final_dest_path);
+ if (exist.ok()) {
+ file_exists = true;
+ } else if (exist.IsNotFound()) {
+ file_exists = false;
+ } else {
+ assert(s.IsIOError());
+ return exist;
+ }
+ }
+
+ if (!contents.empty()) {
+ need_to_copy = false;
+ } else if (shared && (same_path || file_exists)) {
+ need_to_copy = false;
+ if (shared_checksum) {
+ ROCKS_LOG_INFO(options_.info_log,
+ "%s already present, with checksum %u and size %" PRIu64,
+ fname.c_str(), checksum_value, size_bytes);
+ } else if (backuped_file_infos_.find(dst_relative) ==
+ backuped_file_infos_.end() && !same_path) {
+ // file already exists, but it's not referenced by any backup. overwrite
+ // the file
+ ROCKS_LOG_INFO(
+ options_.info_log,
+ "%s already present, but not referenced by any backup. We will "
+ "overwrite the file.",
+ fname.c_str());
+ need_to_copy = true;
+ backup_env_->DeleteFile(final_dest_path);
+ } else {
+ // the file is present and referenced by a backup
+ ROCKS_LOG_INFO(options_.info_log,
+ "%s already present, calculate checksum", fname.c_str());
+ s = CalculateChecksum(src_dir + fname, db_env_, src_env_options,
+ size_limit, &checksum_value);
+ }
+ }
+ live_dst_paths.insert(final_dest_path);
+
+ if (!contents.empty() || need_to_copy) {
+ ROCKS_LOG_INFO(options_.info_log, "Copying %s to %s", fname.c_str(),
+ copy_dest_path->c_str());
+ CopyOrCreateWorkItem copy_or_create_work_item(
+ src_dir.empty() ? "" : src_dir + fname, *copy_dest_path, contents,
+ db_env_, backup_env_, src_env_options, options_.sync, rate_limiter,
+ size_limit, progress_callback);
+ BackupAfterCopyOrCreateWorkItem after_copy_or_create_work_item(
+ copy_or_create_work_item.result.get_future(), shared, need_to_copy,
+ backup_env_, temp_dest_path, final_dest_path, dst_relative);
+ files_to_copy_or_create_.write(std::move(copy_or_create_work_item));
+ backup_items_to_finish.push_back(std::move(after_copy_or_create_work_item));
+ } else {
+ std::promise<CopyOrCreateResult> promise_result;
+ BackupAfterCopyOrCreateWorkItem after_copy_or_create_work_item(
+ promise_result.get_future(), shared, need_to_copy, backup_env_,
+ temp_dest_path, final_dest_path, dst_relative);
+ backup_items_to_finish.push_back(std::move(after_copy_or_create_work_item));
+ CopyOrCreateResult result;
+ result.status = s;
+ result.size = size_bytes;
+ result.checksum_value = checksum_value;
+ promise_result.set_value(std::move(result));
+ }
+ return s;
+}
+
+Status BackupEngineImpl::CalculateChecksum(const std::string& src, Env* src_env,
+ const EnvOptions& src_env_options,
+ uint64_t size_limit,
+ uint32_t* checksum_value) {
+ *checksum_value = 0;
+ if (size_limit == 0) {
+ size_limit = std::numeric_limits<uint64_t>::max();
+ }
+
+ std::unique_ptr<SequentialFile> src_file;
+ Status s = src_env->NewSequentialFile(src, &src_file, src_env_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<SequentialFileReader> src_reader(
+ new SequentialFileReader(NewLegacySequentialFileWrapper(src_file), src));
+ std::unique_ptr<char[]> buf(new char[copy_file_buffer_size_]);
+ Slice data;
+
+ do {
+ if (stop_backup_.load(std::memory_order_acquire)) {
+ return Status::Incomplete("Backup stopped");
+ }
+ size_t buffer_to_read = (copy_file_buffer_size_ < size_limit) ?
+ copy_file_buffer_size_ : static_cast<size_t>(size_limit);
+ s = src_reader->Read(buffer_to_read, &data, buf.get());
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ size_limit -= data.size();
+ *checksum_value = crc32c::Extend(*checksum_value, data.data(), data.size());
+ } while (data.size() > 0 && size_limit > 0);
+
+ return s;
+}
+
+void BackupEngineImpl::DeleteChildren(const std::string& dir,
+ uint32_t file_type_filter) {
+ std::vector<std::string> children;
+ db_env_->GetChildren(dir, &children); // ignore errors
+
+ for (const auto& f : children) {
+ uint64_t number;
+ FileType type;
+ bool ok = ParseFileName(f, &number, &type);
+ if (ok && (file_type_filter & (1 << type))) {
+ // don't delete this file
+ continue;
+ }
+ db_env_->DeleteFile(dir + "/" + f); // ignore errors
+ }
+}
+
+Status BackupEngineImpl::InsertPathnameToSizeBytes(
+ const std::string& dir, Env* env,
+ std::unordered_map<std::string, uint64_t>* result) {
+ assert(result != nullptr);
+ std::vector<Env::FileAttributes> files_attrs;
+ Status status = env->FileExists(dir);
+ if (status.ok()) {
+ status = env->GetChildrenFileAttributes(dir, &files_attrs);
+ } else if (status.IsNotFound()) {
+ // Insert no entries can be considered success
+ status = Status::OK();
+ }
+ const bool slash_needed = dir.empty() || dir.back() != '/';
+ for (const auto& file_attrs : files_attrs) {
+ result->emplace(dir + (slash_needed ? "/" : "") + file_attrs.name,
+ file_attrs.size_bytes);
+ }
+ return status;
+}
+
+Status BackupEngineImpl::GarbageCollect() {
+ assert(!read_only_);
+
+ // We will make a best effort to remove all garbage even in the presence
+ // of inconsistencies or I/O failures that inhibit finding garbage.
+ Status overall_status = Status::OK();
+ // If all goes well, we don't need another auto-GC this session
+ might_need_garbage_collect_ = false;
+
+ ROCKS_LOG_INFO(options_.info_log, "Starting garbage collection");
+
+ // delete obsolete shared files
+ for (bool with_checksum : {false, true}) {
+ std::vector<std::string> shared_children;
+ {
+ std::string shared_path;
+ if (with_checksum) {
+ shared_path = GetAbsolutePath(GetSharedFileWithChecksumRel());
+ } else {
+ shared_path = GetAbsolutePath(GetSharedFileRel());
+ }
+ auto s = backup_env_->FileExists(shared_path);
+ if (s.ok()) {
+ s = backup_env_->GetChildren(shared_path, &shared_children);
+ } else if (s.IsNotFound()) {
+ s = Status::OK();
+ }
+ if (!s.ok()) {
+ overall_status = s;
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+ for (auto& child : shared_children) {
+ if (child == "." || child == "..") {
+ continue;
+ }
+ std::string rel_fname;
+ if (with_checksum) {
+ rel_fname = GetSharedFileWithChecksumRel(child);
+ } else {
+ rel_fname = GetSharedFileRel(child);
+ }
+ auto child_itr = backuped_file_infos_.find(rel_fname);
+ // if it's not refcounted, delete it
+ if (child_itr == backuped_file_infos_.end() ||
+ child_itr->second->refs == 0) {
+ // this might be a directory, but DeleteFile will just fail in that
+ // case, so we're good
+ Status s = backup_env_->DeleteFile(GetAbsolutePath(rel_fname));
+ ROCKS_LOG_INFO(options_.info_log, "Deleting %s -- %s",
+ rel_fname.c_str(), s.ToString().c_str());
+ backuped_file_infos_.erase(rel_fname);
+ if (!s.ok()) {
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+ }
+ }
+
+ // delete obsolete private files
+ std::vector<std::string> private_children;
+ {
+ auto s = backup_env_->GetChildren(GetAbsolutePath(GetPrivateDirRel()),
+ &private_children);
+ if (!s.ok()) {
+ overall_status = s;
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+ for (auto& child : private_children) {
+ if (child == "." || child == "..") {
+ continue;
+ }
+
+ BackupID backup_id = 0;
+ bool tmp_dir = child.find(".tmp") != std::string::npos;
+ sscanf(child.c_str(), "%u", &backup_id);
+ if (!tmp_dir && // if it's tmp_dir, delete it
+ (backup_id == 0 || backups_.find(backup_id) != backups_.end())) {
+ // it's either not a number or it's still alive. continue
+ continue;
+ }
+ // here we have to delete the dir and all its children
+ std::string full_private_path =
+ GetAbsolutePath(GetPrivateFileRel(backup_id));
+ std::vector<std::string> subchildren;
+ backup_env_->GetChildren(full_private_path, &subchildren);
+ for (auto& subchild : subchildren) {
+ if (subchild == "." || subchild == "..") {
+ continue;
+ }
+ Status s = backup_env_->DeleteFile(full_private_path + subchild);
+ ROCKS_LOG_INFO(options_.info_log, "Deleting %s -- %s",
+ (full_private_path + subchild).c_str(),
+ s.ToString().c_str());
+ if (!s.ok()) {
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+ // finally delete the private dir
+ Status s = backup_env_->DeleteDir(full_private_path);
+ ROCKS_LOG_INFO(options_.info_log, "Deleting dir %s -- %s",
+ full_private_path.c_str(), s.ToString().c_str());
+ if (!s.ok()) {
+ // Trying again later might work
+ might_need_garbage_collect_ = true;
+ }
+ }
+
+ assert(overall_status.ok() || might_need_garbage_collect_);
+ return overall_status;
+}
+
+// ------- BackupMeta class --------
+
+Status BackupEngineImpl::BackupMeta::AddFile(
+ std::shared_ptr<FileInfo> file_info) {
+ auto itr = file_infos_->find(file_info->filename);
+ if (itr == file_infos_->end()) {
+ auto ret = file_infos_->insert({file_info->filename, file_info});
+ if (ret.second) {
+ itr = ret.first;
+ itr->second->refs = 1;
+ } else {
+ // if this happens, something is seriously wrong
+ return Status::Corruption("In memory metadata insertion error");
+ }
+ } else {
+ if (itr->second->checksum_value != file_info->checksum_value) {
+ return Status::Corruption(
+ "Checksum mismatch for existing backup file. Delete old backups and "
+ "try again.");
+ }
+ ++itr->second->refs; // increase refcount if already present
+ }
+
+ size_ += file_info->size;
+ files_.push_back(itr->second);
+
+ return Status::OK();
+}
+
+Status BackupEngineImpl::BackupMeta::Delete(bool delete_meta) {
+ Status s;
+ for (const auto& file : files_) {
+ --file->refs; // decrease refcount
+ }
+ files_.clear();
+ // delete meta file
+ if (delete_meta) {
+ s = env_->FileExists(meta_filename_);
+ if (s.ok()) {
+ s = env_->DeleteFile(meta_filename_);
+ } else if (s.IsNotFound()) {
+ s = Status::OK(); // nothing to delete
+ }
+ }
+ timestamp_ = 0;
+ return s;
+}
+
+Slice kMetaDataPrefix("metadata ");
+
+// each backup meta file is of the format:
+// <timestamp>
+// <seq number>
+// <metadata(literal string)> <metadata> (optional)
+// <number of files>
+// <file1> <crc32(literal string)> <crc32_value>
+// <file2> <crc32(literal string)> <crc32_value>
+// ...
+Status BackupEngineImpl::BackupMeta::LoadFromFile(
+ const std::string& backup_dir,
+ const std::unordered_map<std::string, uint64_t>& abs_path_to_size) {
+ assert(Empty());
+ Status s;
+ std::unique_ptr<SequentialFile> backup_meta_file;
+ s = env_->NewSequentialFile(meta_filename_, &backup_meta_file, EnvOptions());
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<SequentialFileReader> backup_meta_reader(
+ new SequentialFileReader(NewLegacySequentialFileWrapper(backup_meta_file),
+ meta_filename_));
+ std::unique_ptr<char[]> buf(new char[max_backup_meta_file_size_ + 1]);
+ Slice data;
+ s = backup_meta_reader->Read(max_backup_meta_file_size_, &data, buf.get());
+
+ if (!s.ok() || data.size() == max_backup_meta_file_size_) {
+ return s.ok() ? Status::Corruption("File size too big") : s;
+ }
+ buf[data.size()] = 0;
+
+ uint32_t num_files = 0;
+ char *next;
+ timestamp_ = strtoull(data.data(), &next, 10);
+ data.remove_prefix(next - data.data() + 1); // +1 for '\n'
+ sequence_number_ = strtoull(data.data(), &next, 10);
+ data.remove_prefix(next - data.data() + 1); // +1 for '\n'
+
+ if (data.starts_with(kMetaDataPrefix)) {
+ // app metadata present
+ data.remove_prefix(kMetaDataPrefix.size());
+ Slice hex_encoded_metadata = GetSliceUntil(&data, '\n');
+ bool decode_success = hex_encoded_metadata.DecodeHex(&app_metadata_);
+ if (!decode_success) {
+ return Status::Corruption(
+ "Failed to decode stored hex encoded app metadata");
+ }
+ }
+
+ num_files = static_cast<uint32_t>(strtoul(data.data(), &next, 10));
+ data.remove_prefix(next - data.data() + 1); // +1 for '\n'
+
+ std::vector<std::shared_ptr<FileInfo>> files;
+
+ Slice checksum_prefix("crc32 ");
+
+ for (uint32_t i = 0; s.ok() && i < num_files; ++i) {
+ auto line = GetSliceUntil(&data, '\n');
+ std::string filename = GetSliceUntil(&line, ' ').ToString();
+
+ uint64_t size;
+ const std::shared_ptr<FileInfo> file_info = GetFile(filename);
+ if (file_info) {
+ size = file_info->size;
+ } else {
+ std::string abs_path = backup_dir + "/" + filename;
+ try {
+ size = abs_path_to_size.at(abs_path);
+ } catch (std::out_of_range&) {
+ return Status::Corruption("Size missing for pathname: " + abs_path);
+ }
+ }
+
+ if (line.empty()) {
+ return Status::Corruption("File checksum is missing for " + filename +
+ " in " + meta_filename_);
+ }
+
+ uint32_t checksum_value = 0;
+ if (line.starts_with(checksum_prefix)) {
+ line.remove_prefix(checksum_prefix.size());
+ checksum_value = static_cast<uint32_t>(
+ strtoul(line.data(), nullptr, 10));
+ if (line != ROCKSDB_NAMESPACE::ToString(checksum_value)) {
+ return Status::Corruption("Invalid checksum value for " + filename +
+ " in " + meta_filename_);
+ }
+ } else {
+ return Status::Corruption("Unknown checksum type for " + filename +
+ " in " + meta_filename_);
+ }
+
+ files.emplace_back(new FileInfo(filename, size, checksum_value));
+ }
+
+ if (s.ok() && data.size() > 0) {
+ // file has to be read completely. if not, we count it as corruption
+ s = Status::Corruption("Tailing data in backup meta file in " +
+ meta_filename_);
+ }
+
+ if (s.ok()) {
+ files_.reserve(files.size());
+ for (const auto& file_info : files) {
+ s = AddFile(file_info);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+
+ return s;
+}
+
+Status BackupEngineImpl::BackupMeta::StoreToFile(bool sync) {
+ Status s;
+ std::unique_ptr<WritableFile> backup_meta_file;
+ EnvOptions env_options;
+ env_options.use_mmap_writes = false;
+ env_options.use_direct_writes = false;
+ s = env_->NewWritableFile(meta_tmp_filename_, &backup_meta_file, env_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<char[]> buf(new char[max_backup_meta_file_size_]);
+ size_t len = 0, buf_size = max_backup_meta_file_size_;
+ len += snprintf(buf.get(), buf_size, "%" PRId64 "\n", timestamp_);
+ len += snprintf(buf.get() + len, buf_size - len, "%" PRIu64 "\n",
+ sequence_number_);
+ if (!app_metadata_.empty()) {
+ std::string hex_encoded_metadata =
+ Slice(app_metadata_).ToString(/* hex */ true);
+
+ // +1 to accommodate newline character
+ size_t hex_meta_strlen = kMetaDataPrefix.ToString().length() + hex_encoded_metadata.length() + 1;
+ if (hex_meta_strlen >= buf_size) {
+ return Status::Corruption("Buffer too small to fit backup metadata");
+ }
+ else if (len + hex_meta_strlen >= buf_size) {
+ backup_meta_file->Append(Slice(buf.get(), len));
+ buf.reset();
+ std::unique_ptr<char[]> new_reset_buf(
+ new char[max_backup_meta_file_size_]);
+ buf.swap(new_reset_buf);
+ len = 0;
+ }
+ len += snprintf(buf.get() + len, buf_size - len, "%s%s\n",
+ kMetaDataPrefix.ToString().c_str(),
+ hex_encoded_metadata.c_str());
+ }
+
+ char writelen_temp[19];
+ if (len + snprintf(writelen_temp, sizeof(writelen_temp),
+ "%" ROCKSDB_PRIszt "\n", files_.size()) >= buf_size) {
+ backup_meta_file->Append(Slice(buf.get(), len));
+ buf.reset();
+ std::unique_ptr<char[]> new_reset_buf(new char[max_backup_meta_file_size_]);
+ buf.swap(new_reset_buf);
+ len = 0;
+ }
+ {
+ const char *const_write = writelen_temp;
+ len += snprintf(buf.get() + len, buf_size - len, "%s", const_write);
+ }
+
+ for (const auto& file : files_) {
+ // use crc32 for now, switch to something else if needed
+
+ size_t newlen = len + file->filename.length() + snprintf(writelen_temp,
+ sizeof(writelen_temp), " crc32 %u\n", file->checksum_value);
+ const char *const_write = writelen_temp;
+ if (newlen >= buf_size) {
+ backup_meta_file->Append(Slice(buf.get(), len));
+ buf.reset();
+ std::unique_ptr<char[]> new_reset_buf(
+ new char[max_backup_meta_file_size_]);
+ buf.swap(new_reset_buf);
+ len = 0;
+ }
+ len += snprintf(buf.get() + len, buf_size - len, "%s%s",
+ file->filename.c_str(), const_write);
+ }
+
+ s = backup_meta_file->Append(Slice(buf.get(), len));
+ if (s.ok() && sync) {
+ s = backup_meta_file->Sync();
+ }
+ if (s.ok()) {
+ s = backup_meta_file->Close();
+ }
+ if (s.ok()) {
+ s = env_->RenameFile(meta_tmp_filename_, meta_filename_);
+ }
+ return s;
+}
+
+// -------- BackupEngineReadOnlyImpl ---------
+class BackupEngineReadOnlyImpl : public BackupEngineReadOnly {
+ public:
+ BackupEngineReadOnlyImpl(Env* db_env, const BackupableDBOptions& options)
+ : backup_engine_(new BackupEngineImpl(db_env, options, true)) {}
+
+ ~BackupEngineReadOnlyImpl() override {}
+
+ // The returned BackupInfos are in chronological order, which means the
+ // latest backup comes last.
+ void GetBackupInfo(std::vector<BackupInfo>* backup_info) override {
+ backup_engine_->GetBackupInfo(backup_info);
+ }
+
+ void GetCorruptedBackups(std::vector<BackupID>* corrupt_backup_ids) override {
+ backup_engine_->GetCorruptedBackups(corrupt_backup_ids);
+ }
+
+ Status RestoreDBFromBackup(
+ BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) override {
+ return backup_engine_->RestoreDBFromBackup(backup_id, db_dir, wal_dir,
+ restore_options);
+ }
+
+ Status RestoreDBFromLatestBackup(
+ const std::string& db_dir, const std::string& wal_dir,
+ const RestoreOptions& restore_options = RestoreOptions()) override {
+ return backup_engine_->RestoreDBFromLatestBackup(db_dir, wal_dir,
+ restore_options);
+ }
+
+ Status VerifyBackup(BackupID backup_id) override {
+ return backup_engine_->VerifyBackup(backup_id);
+ }
+
+ Status Initialize() { return backup_engine_->Initialize(); }
+
+ private:
+ std::unique_ptr<BackupEngineImpl> backup_engine_;
+};
+
+Status BackupEngineReadOnly::Open(Env* env, const BackupableDBOptions& options,
+ BackupEngineReadOnly** backup_engine_ptr) {
+ if (options.destroy_old_data) {
+ return Status::InvalidArgument(
+ "Can't destroy old data with ReadOnly BackupEngine");
+ }
+ std::unique_ptr<BackupEngineReadOnlyImpl> backup_engine(
+ new BackupEngineReadOnlyImpl(env, options));
+ auto s = backup_engine->Initialize();
+ if (!s.ok()) {
+ *backup_engine_ptr = nullptr;
+ return s;
+ }
+ *backup_engine_ptr = backup_engine.release();
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/backupable/backupable_db_test.cc b/storage/rocksdb/rocksdb/utilities/backupable/backupable_db_test.cc
new file mode 100644
index 00000000..efdb34b3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/backupable/backupable_db_test.cc
@@ -0,0 +1,1863 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+#if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <utility>
+
+#include "db/db_impl/db_impl.h"
+#include "env/env_chroot.h"
+#include "file/filename.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/rate_limiter.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/backupable_db.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/mutexlock.h"
+#include "util/random.h"
+#include "util/stderr_logger.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+class DummyDB : public StackableDB {
+ public:
+ /* implicit */
+ DummyDB(const Options& options, const std::string& dbname)
+ : StackableDB(nullptr), options_(options), dbname_(dbname),
+ deletions_enabled_(true), sequence_number_(0) {}
+
+ SequenceNumber GetLatestSequenceNumber() const override {
+ return ++sequence_number_;
+ }
+
+ const std::string& GetName() const override { return dbname_; }
+
+ Env* GetEnv() const override { return options_.env; }
+
+ using DB::GetOptions;
+ Options GetOptions(ColumnFamilyHandle* /*column_family*/) const override {
+ return options_;
+ }
+
+ DBOptions GetDBOptions() const override { return DBOptions(options_); }
+
+ Status EnableFileDeletions(bool /*force*/) override {
+ EXPECT_TRUE(!deletions_enabled_);
+ deletions_enabled_ = true;
+ return Status::OK();
+ }
+
+ Status DisableFileDeletions() override {
+ EXPECT_TRUE(deletions_enabled_);
+ deletions_enabled_ = false;
+ return Status::OK();
+ }
+
+ Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,
+ bool /*flush_memtable*/ = true) override {
+ EXPECT_TRUE(!deletions_enabled_);
+ vec = live_files_;
+ *mfs = 100;
+ return Status::OK();
+ }
+
+ ColumnFamilyHandle* DefaultColumnFamily() const override { return nullptr; }
+
+ class DummyLogFile : public LogFile {
+ public:
+ /* implicit */
+ DummyLogFile(const std::string& path, bool alive = true)
+ : path_(path), alive_(alive) {}
+
+ std::string PathName() const override { return path_; }
+
+ uint64_t LogNumber() const override {
+ // what business do you have calling this method?
+ ADD_FAILURE();
+ return 0;
+ }
+
+ WalFileType Type() const override {
+ return alive_ ? kAliveLogFile : kArchivedLogFile;
+ }
+
+ SequenceNumber StartSequence() const override {
+ // this seqnum guarantees the dummy file will be included in the backup
+ // as long as it is alive.
+ return kMaxSequenceNumber;
+ }
+
+ uint64_t SizeFileBytes() const override { return 0; }
+
+ private:
+ std::string path_;
+ bool alive_;
+ }; // DummyLogFile
+
+ Status GetSortedWalFiles(VectorLogPtr& files) override {
+ EXPECT_TRUE(!deletions_enabled_);
+ files.resize(wal_files_.size());
+ for (size_t i = 0; i < files.size(); ++i) {
+ files[i].reset(
+ new DummyLogFile(wal_files_[i].first, wal_files_[i].second));
+ }
+ return Status::OK();
+ }
+
+ // To avoid FlushWAL called on stacked db which is nullptr
+ Status FlushWAL(bool /*sync*/) override { return Status::OK(); }
+
+ std::vector<std::string> live_files_;
+ // pair<filename, alive?>
+ std::vector<std::pair<std::string, bool>> wal_files_;
+ private:
+ Options options_;
+ std::string dbname_;
+ bool deletions_enabled_;
+ mutable SequenceNumber sequence_number_;
+}; // DummyDB
+
+class TestEnv : public EnvWrapper {
+ public:
+ explicit TestEnv(Env* t) : EnvWrapper(t) {}
+
+ class DummySequentialFile : public SequentialFile {
+ public:
+ explicit DummySequentialFile(bool fail_reads)
+ : SequentialFile(), rnd_(5), fail_reads_(fail_reads) {}
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ if (fail_reads_) {
+ return Status::IOError();
+ }
+ size_t read_size = (n > size_left) ? size_left : n;
+ for (size_t i = 0; i < read_size; ++i) {
+ scratch[i] = rnd_.Next() & 255;
+ }
+ *result = Slice(scratch, read_size);
+ size_left -= read_size;
+ return Status::OK();
+ }
+
+ Status Skip(uint64_t n) override {
+ size_left = (n > size_left) ? size_left - n : 0;
+ return Status::OK();
+ }
+
+ private:
+ size_t size_left = 200;
+ Random rnd_;
+ bool fail_reads_;
+ };
+
+ Status NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& options) override {
+ MutexLock l(&mutex_);
+ if (dummy_sequential_file_) {
+ r->reset(
+ new TestEnv::DummySequentialFile(dummy_sequential_file_fail_reads_));
+ return Status::OK();
+ } else {
+ Status s = EnvWrapper::NewSequentialFile(f, r, options);
+ if (s.ok()) {
+ if ((*r)->use_direct_io()) {
+ ++num_direct_seq_readers_;
+ }
+ ++num_seq_readers_;
+ }
+ return s;
+ }
+ }
+
+ Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) override {
+ MutexLock l(&mutex_);
+ written_files_.push_back(f);
+ if (limit_written_files_ <= 0) {
+ return Status::NotSupported("Sorry, can't do this");
+ }
+ limit_written_files_--;
+ Status s = EnvWrapper::NewWritableFile(f, r, options);
+ if (s.ok()) {
+ if ((*r)->use_direct_io()) {
+ ++num_direct_writers_;
+ }
+ ++num_writers_;
+ }
+ return s;
+ }
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override {
+ MutexLock l(&mutex_);
+ Status s = EnvWrapper::NewRandomAccessFile(fname, result, options);
+ if (s.ok()) {
+ if ((*result)->use_direct_io()) {
+ ++num_direct_rand_readers_;
+ }
+ ++num_rand_readers_;
+ }
+ return s;
+ }
+
+ Status DeleteFile(const std::string& fname) override {
+ MutexLock l(&mutex_);
+ if (fail_delete_files_) {
+ return Status::IOError();
+ }
+ EXPECT_GT(limit_delete_files_, 0U);
+ limit_delete_files_--;
+ return EnvWrapper::DeleteFile(fname);
+ }
+
+ Status DeleteDir(const std::string& dirname) override {
+ MutexLock l(&mutex_);
+ if (fail_delete_files_) {
+ return Status::IOError();
+ }
+ return EnvWrapper::DeleteDir(dirname);
+ }
+
+ void AssertWrittenFiles(std::vector<std::string>& should_have_written) {
+ MutexLock l(&mutex_);
+ std::sort(should_have_written.begin(), should_have_written.end());
+ std::sort(written_files_.begin(), written_files_.end());
+
+ ASSERT_EQ(should_have_written, written_files_);
+ }
+
+ void ClearWrittenFiles() {
+ MutexLock l(&mutex_);
+ written_files_.clear();
+ }
+
+ void SetLimitWrittenFiles(uint64_t limit) {
+ MutexLock l(&mutex_);
+ limit_written_files_ = limit;
+ }
+
+ void SetLimitDeleteFiles(uint64_t limit) {
+ MutexLock l(&mutex_);
+ limit_delete_files_ = limit;
+ }
+
+ void SetDeleteFileFailure(bool fail) {
+ MutexLock l(&mutex_);
+ fail_delete_files_ = fail;
+ }
+
+ void SetDummySequentialFile(bool dummy_sequential_file) {
+ MutexLock l(&mutex_);
+ dummy_sequential_file_ = dummy_sequential_file;
+ }
+ void SetDummySequentialFileFailReads(bool dummy_sequential_file_fail_reads) {
+ MutexLock l(&mutex_);
+ dummy_sequential_file_fail_reads_ = dummy_sequential_file_fail_reads;
+ }
+
+ void SetGetChildrenFailure(bool fail) { get_children_failure_ = fail; }
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* r) override {
+ if (get_children_failure_) {
+ return Status::IOError("SimulatedFailure");
+ }
+ return EnvWrapper::GetChildren(dir, r);
+ }
+
+ // Some test cases do not actually create the test files (e.g., see
+ // DummyDB::live_files_) - for those cases, we mock those files' attributes
+ // so CreateNewBackup() can get their attributes.
+ void SetFilenamesForMockedAttrs(const std::vector<std::string>& filenames) {
+ filenames_for_mocked_attrs_ = filenames;
+ }
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<Env::FileAttributes>* r) override {
+ if (filenames_for_mocked_attrs_.size() > 0) {
+ for (const auto& filename : filenames_for_mocked_attrs_) {
+ r->push_back({dir + filename, 10 /* size_bytes */});
+ }
+ return Status::OK();
+ }
+ return EnvWrapper::GetChildrenFileAttributes(dir, r);
+ }
+ Status GetFileSize(const std::string& path, uint64_t* size_bytes) override {
+ if (filenames_for_mocked_attrs_.size() > 0) {
+ auto fname = path.substr(path.find_last_of('/'));
+ auto filename_iter = std::find(filenames_for_mocked_attrs_.begin(),
+ filenames_for_mocked_attrs_.end(), fname);
+ if (filename_iter != filenames_for_mocked_attrs_.end()) {
+ *size_bytes = 10;
+ return Status::OK();
+ }
+ return Status::NotFound(fname);
+ }
+ return EnvWrapper::GetFileSize(path, size_bytes);
+ }
+
+ void SetCreateDirIfMissingFailure(bool fail) {
+ create_dir_if_missing_failure_ = fail;
+ }
+ Status CreateDirIfMissing(const std::string& d) override {
+ if (create_dir_if_missing_failure_) {
+ return Status::IOError("SimulatedFailure");
+ }
+ return EnvWrapper::CreateDirIfMissing(d);
+ }
+
+ void SetNewDirectoryFailure(bool fail) { new_directory_failure_ = fail; }
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override {
+ if (new_directory_failure_) {
+ return Status::IOError("SimulatedFailure");
+ }
+ return EnvWrapper::NewDirectory(name, result);
+ }
+
+ void ClearFileOpenCounters() {
+ MutexLock l(&mutex_);
+ num_rand_readers_ = 0;
+ num_direct_rand_readers_ = 0;
+ num_seq_readers_ = 0;
+ num_direct_seq_readers_ = 0;
+ num_writers_ = 0;
+ num_direct_writers_ = 0;
+ }
+
+ int num_rand_readers() { return num_rand_readers_; }
+ int num_direct_rand_readers() { return num_direct_rand_readers_; }
+ int num_seq_readers() { return num_seq_readers_; }
+ int num_direct_seq_readers() { return num_direct_seq_readers_; }
+ int num_writers() { return num_writers_; }
+ int num_direct_writers() { return num_direct_writers_; }
+
+ private:
+ port::Mutex mutex_;
+ bool dummy_sequential_file_ = false;
+ bool dummy_sequential_file_fail_reads_ = false;
+ std::vector<std::string> written_files_;
+ std::vector<std::string> filenames_for_mocked_attrs_;
+ uint64_t limit_written_files_ = 1000000;
+ uint64_t limit_delete_files_ = 1000000;
+ bool fail_delete_files_ = false;
+
+ bool get_children_failure_ = false;
+ bool create_dir_if_missing_failure_ = false;
+ bool new_directory_failure_ = false;
+
+ // Keeps track of how many files of each type were successfully opened, and
+ // out of those, how many were opened with direct I/O.
+ std::atomic<int> num_rand_readers_;
+ std::atomic<int> num_direct_rand_readers_;
+ std::atomic<int> num_seq_readers_;
+ std::atomic<int> num_direct_seq_readers_;
+ std::atomic<int> num_writers_;
+ std::atomic<int> num_direct_writers_;
+}; // TestEnv
+
+class FileManager : public EnvWrapper {
+ public:
+ explicit FileManager(Env* t) : EnvWrapper(t), rnd_(5) {}
+
+ Status DeleteRandomFileInDir(const std::string& dir) {
+ std::vector<std::string> children;
+ GetChildren(dir, &children);
+ if (children.size() <= 2) { // . and ..
+ return Status::NotFound("");
+ }
+ while (true) {
+ int i = rnd_.Next() % children.size();
+ if (children[i] != "." && children[i] != "..") {
+ return DeleteFile(dir + "/" + children[i]);
+ }
+ }
+ // should never get here
+ assert(false);
+ return Status::NotFound("");
+ }
+
+ Status AppendToRandomFileInDir(const std::string& dir,
+ const std::string& data) {
+ std::vector<std::string> children;
+ GetChildren(dir, &children);
+ if (children.size() <= 2) {
+ return Status::NotFound("");
+ }
+ while (true) {
+ int i = rnd_.Next() % children.size();
+ if (children[i] != "." && children[i] != "..") {
+ return WriteToFile(dir + "/" + children[i], data);
+ }
+ }
+ // should never get here
+ assert(false);
+ return Status::NotFound("");
+ }
+
+ Status CorruptFile(const std::string& fname, uint64_t bytes_to_corrupt) {
+ std::string file_contents;
+ Status s = ReadFileToString(this, fname, &file_contents);
+ if (!s.ok()) {
+ return s;
+ }
+ s = DeleteFile(fname);
+ if (!s.ok()) {
+ return s;
+ }
+
+ for (uint64_t i = 0; i < bytes_to_corrupt; ++i) {
+ std::string tmp;
+ test::RandomString(&rnd_, 1, &tmp);
+ file_contents[rnd_.Next() % file_contents.size()] = tmp[0];
+ }
+ return WriteToFile(fname, file_contents);
+ }
+
+ Status CorruptChecksum(const std::string& fname, bool appear_valid) {
+ std::string metadata;
+ Status s = ReadFileToString(this, fname, &metadata);
+ if (!s.ok()) {
+ return s;
+ }
+ s = DeleteFile(fname);
+ if (!s.ok()) {
+ return s;
+ }
+
+ auto pos = metadata.find("private");
+ if (pos == std::string::npos) {
+ return Status::Corruption("private file is expected");
+ }
+ pos = metadata.find(" crc32 ", pos + 6);
+ if (pos == std::string::npos) {
+ return Status::Corruption("checksum not found");
+ }
+
+ if (metadata.size() < pos + 7) {
+ return Status::Corruption("bad CRC32 checksum value");
+ }
+
+ if (appear_valid) {
+ if (metadata[pos + 8] == '\n') {
+ // single digit value, safe to insert one more digit
+ metadata.insert(pos + 8, 1, '0');
+ } else {
+ metadata.erase(pos + 8, 1);
+ }
+ } else {
+ metadata[pos + 7] = 'a';
+ }
+
+ return WriteToFile(fname, metadata);
+ }
+
+ Status WriteToFile(const std::string& fname, const std::string& data) {
+ std::unique_ptr<WritableFile> file;
+ EnvOptions env_options;
+ env_options.use_mmap_writes = false;
+ Status s = EnvWrapper::NewWritableFile(fname, &file, env_options);
+ if (!s.ok()) {
+ return s;
+ }
+ return file->Append(Slice(data));
+ }
+
+ private:
+ Random rnd_;
+}; // FileManager
+
+// utility functions
+static size_t FillDB(DB* db, int from, int to) {
+ size_t bytes_written = 0;
+ for (int i = from; i < to; ++i) {
+ std::string key = "testkey" + ToString(i);
+ std::string value = "testvalue" + ToString(i);
+ bytes_written += key.size() + value.size();
+
+ EXPECT_OK(db->Put(WriteOptions(), Slice(key), Slice(value)));
+ }
+ return bytes_written;
+}
+
+static void AssertExists(DB* db, int from, int to) {
+ for (int i = from; i < to; ++i) {
+ std::string key = "testkey" + ToString(i);
+ std::string value;
+ Status s = db->Get(ReadOptions(), Slice(key), &value);
+ ASSERT_EQ(value, "testvalue" + ToString(i));
+ }
+}
+
+static void AssertEmpty(DB* db, int from, int to) {
+ for (int i = from; i < to; ++i) {
+ std::string key = "testkey" + ToString(i);
+ std::string value = "testvalue" + ToString(i);
+
+ Status s = db->Get(ReadOptions(), Slice(key), &value);
+ ASSERT_TRUE(s.IsNotFound());
+ }
+}
+
+class BackupableDBTest : public testing::Test {
+ public:
+ enum ShareOption {
+ kNoShare,
+ kShareNoChecksum,
+ kShareWithChecksum,
+ };
+
+ const std::vector<ShareOption> kAllShareOptions = {
+ kNoShare, kShareNoChecksum, kShareWithChecksum};
+
+ BackupableDBTest() {
+ // set up files
+ std::string db_chroot = test::PerThreadDBPath("backupable_db");
+ std::string backup_chroot = test::PerThreadDBPath("backupable_db_backup");
+ Env::Default()->CreateDir(db_chroot);
+ Env::Default()->CreateDir(backup_chroot);
+ dbname_ = "/tempdb";
+ backupdir_ = "/tempbk";
+
+ // set up envs
+ db_chroot_env_.reset(NewChrootEnv(Env::Default(), db_chroot));
+ backup_chroot_env_.reset(NewChrootEnv(Env::Default(), backup_chroot));
+ test_db_env_.reset(new TestEnv(db_chroot_env_.get()));
+ test_backup_env_.reset(new TestEnv(backup_chroot_env_.get()));
+ file_manager_.reset(new FileManager(backup_chroot_env_.get()));
+
+ // set up db options
+ options_.create_if_missing = true;
+ options_.paranoid_checks = true;
+ options_.write_buffer_size = 1 << 17; // 128KB
+ options_.env = test_db_env_.get();
+ options_.wal_dir = dbname_;
+
+ // Create logger
+ DBOptions logger_options;
+ logger_options.env = db_chroot_env_.get();
+ CreateLoggerFromOptions(dbname_, logger_options, &logger_);
+
+ // set up backup db options
+ backupable_options_.reset(new BackupableDBOptions(
+ backupdir_, test_backup_env_.get(), true, logger_.get(), true));
+
+ // most tests will use multi-threaded backups
+ backupable_options_->max_background_operations = 7;
+
+ // delete old files in db
+ DestroyDB(dbname_, options_);
+ }
+
+ DB* OpenDB() {
+ DB* db;
+ EXPECT_OK(DB::Open(options_, dbname_, &db));
+ return db;
+ }
+
+ void OpenDBAndBackupEngine(bool destroy_old_data = false, bool dummy = false,
+ ShareOption shared_option = kShareNoChecksum) {
+ // reset all the defaults
+ test_backup_env_->SetLimitWrittenFiles(1000000);
+ test_db_env_->SetLimitWrittenFiles(1000000);
+ test_db_env_->SetDummySequentialFile(dummy);
+
+ DB* db;
+ if (dummy) {
+ dummy_db_ = new DummyDB(options_, dbname_);
+ db = dummy_db_;
+ } else {
+ ASSERT_OK(DB::Open(options_, dbname_, &db));
+ }
+ db_.reset(db);
+ backupable_options_->destroy_old_data = destroy_old_data;
+ backupable_options_->share_table_files = shared_option != kNoShare;
+ backupable_options_->share_files_with_checksum =
+ shared_option == kShareWithChecksum;
+ BackupEngine* backup_engine;
+ ASSERT_OK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ backup_engine_.reset(backup_engine);
+ }
+
+ void CloseDBAndBackupEngine() {
+ db_.reset();
+ backup_engine_.reset();
+ }
+
+ void OpenBackupEngine() {
+ backupable_options_->destroy_old_data = false;
+ BackupEngine* backup_engine;
+ ASSERT_OK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ backup_engine_.reset(backup_engine);
+ }
+
+ void CloseBackupEngine() { backup_engine_.reset(nullptr); }
+
+ // restores backup backup_id and asserts the existence of
+ // [start_exist, end_exist> and not-existence of
+ // [end_exist, end>
+ //
+ // if backup_id == 0, it means restore from latest
+ // if end == 0, don't check AssertEmpty
+ void AssertBackupConsistency(BackupID backup_id, uint32_t start_exist,
+ uint32_t end_exist, uint32_t end = 0,
+ bool keep_log_files = false) {
+ RestoreOptions restore_options(keep_log_files);
+ bool opened_backup_engine = false;
+ if (backup_engine_.get() == nullptr) {
+ opened_backup_engine = true;
+ OpenBackupEngine();
+ }
+ if (backup_id > 0) {
+ ASSERT_OK(backup_engine_->RestoreDBFromBackup(backup_id, dbname_, dbname_,
+ restore_options));
+ } else {
+ ASSERT_OK(backup_engine_->RestoreDBFromLatestBackup(dbname_, dbname_,
+ restore_options));
+ }
+ DB* db = OpenDB();
+ AssertExists(db, start_exist, end_exist);
+ if (end != 0) {
+ AssertEmpty(db, end_exist, end);
+ }
+ delete db;
+ if (opened_backup_engine) {
+ CloseBackupEngine();
+ }
+ }
+
+ void DeleteLogFiles() {
+ std::vector<std::string> delete_logs;
+ db_chroot_env_->GetChildren(dbname_, &delete_logs);
+ for (auto f : delete_logs) {
+ uint64_t number;
+ FileType type;
+ bool ok = ParseFileName(f, &number, &type);
+ if (ok && type == kLogFile) {
+ db_chroot_env_->DeleteFile(dbname_ + "/" + f);
+ }
+ }
+ }
+
+ // files
+ std::string dbname_;
+ std::string backupdir_;
+
+ // logger_ must be above backup_engine_ such that the engine's destructor,
+ // which uses a raw pointer to the logger, executes first.
+ std::shared_ptr<Logger> logger_;
+
+ // envs
+ std::unique_ptr<Env> db_chroot_env_;
+ std::unique_ptr<Env> backup_chroot_env_;
+ std::unique_ptr<TestEnv> test_db_env_;
+ std::unique_ptr<TestEnv> test_backup_env_;
+ std::unique_ptr<FileManager> file_manager_;
+
+ // all the dbs!
+ DummyDB* dummy_db_; // BackupableDB owns dummy_db_
+ std::unique_ptr<DB> db_;
+ std::unique_ptr<BackupEngine> backup_engine_;
+
+ // options
+ Options options_;
+
+ protected:
+ std::unique_ptr<BackupableDBOptions> backupable_options_;
+}; // BackupableDBTest
+
+void AppendPath(const std::string& path, std::vector<std::string>& v) {
+ for (auto& f : v) {
+ f = path + f;
+ }
+}
+
+class BackupableDBTestWithParam : public BackupableDBTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ BackupableDBTestWithParam() {
+ backupable_options_->share_files_with_checksum = GetParam();
+ }
+};
+
+// This test verifies that the verifyBackup method correctly identifies
+// invalid backups
+TEST_P(BackupableDBTestWithParam, VerifyBackup) {
+ const int keys_iteration = 5000;
+ Random rnd(6);
+ Status s;
+ OpenDBAndBackupEngine(true);
+ // create five backups
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ }
+ CloseDBAndBackupEngine();
+
+ OpenDBAndBackupEngine();
+ // ---------- case 1. - valid backup -----------
+ ASSERT_TRUE(backup_engine_->VerifyBackup(1).ok());
+
+ // ---------- case 2. - delete a file -----------i
+ file_manager_->DeleteRandomFileInDir(backupdir_ + "/private/1");
+ ASSERT_TRUE(backup_engine_->VerifyBackup(1).IsNotFound());
+
+ // ---------- case 3. - corrupt a file -----------
+ std::string append_data = "Corrupting a random file";
+ file_manager_->AppendToRandomFileInDir(backupdir_ + "/private/2",
+ append_data);
+ ASSERT_TRUE(backup_engine_->VerifyBackup(2).IsCorruption());
+
+ // ---------- case 4. - invalid backup -----------
+ ASSERT_TRUE(backup_engine_->VerifyBackup(6).IsNotFound());
+ CloseDBAndBackupEngine();
+}
+
+// open DB, write, close DB, backup, restore, repeat
+TEST_P(BackupableDBTestWithParam, OfflineIntegrationTest) {
+ // has to be a big number, so that it triggers the memtable flush
+ const int keys_iteration = 5000;
+ const int max_key = keys_iteration * 4 + 10;
+ // first iter -- flush before backup
+ // second iter -- don't flush before backup
+ for (int iter = 0; iter < 2; ++iter) {
+ // delete old data
+ DestroyDB(dbname_, options_);
+ bool destroy_data = true;
+
+ // every iteration --
+ // 1. insert new data in the DB
+ // 2. backup the DB
+ // 3. destroy the db
+ // 4. restore the db, check everything is still there
+ for (int i = 0; i < 5; ++i) {
+ // in last iteration, put smaller amount of data,
+ int fill_up_to = std::min(keys_iteration * (i + 1), max_key);
+ // ---- insert new data and back up ----
+ OpenDBAndBackupEngine(destroy_data);
+ destroy_data = false;
+ FillDB(db_.get(), keys_iteration * i, fill_up_to);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), iter == 0));
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+
+ // ---- make sure it's empty ----
+ DB* db = OpenDB();
+ AssertEmpty(db, 0, fill_up_to);
+ delete db;
+
+ // ---- restore the DB ----
+ OpenBackupEngine();
+ if (i >= 3) { // test purge old backups
+ // when i == 4, purge to only 1 backup
+ // when i == 3, purge to 2 backups
+ ASSERT_OK(backup_engine_->PurgeOldBackups(5 - i));
+ }
+ // ---- make sure the data is there ---
+ AssertBackupConsistency(0, 0, fill_up_to, max_key);
+ CloseBackupEngine();
+ }
+ }
+}
+
+// open DB, write, backup, write, backup, close, restore
+TEST_P(BackupableDBTestWithParam, OnlineIntegrationTest) {
+ // has to be a big number, so that it triggers the memtable flush
+ const int keys_iteration = 5000;
+ const int max_key = keys_iteration * 4 + 10;
+ Random rnd(7);
+ // delete old data
+ DestroyDB(dbname_, options_);
+
+ OpenDBAndBackupEngine(true);
+ // write some data, backup, repeat
+ for (int i = 0; i < 5; ++i) {
+ if (i == 4) {
+ // delete backup number 2, online delete!
+ ASSERT_OK(backup_engine_->DeleteBackup(2));
+ }
+ // in last iteration, put smaller amount of data,
+ // so that backups can share sst files
+ int fill_up_to = std::min(keys_iteration * (i + 1), max_key);
+ FillDB(db_.get(), keys_iteration * i, fill_up_to);
+ // we should get consistent results with flush_before_backup
+ // set to both true and false
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)));
+ }
+ // close and destroy
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+
+ // ---- make sure it's empty ----
+ DB* db = OpenDB();
+ AssertEmpty(db, 0, max_key);
+ delete db;
+
+ // ---- restore every backup and verify all the data is there ----
+ OpenBackupEngine();
+ for (int i = 1; i <= 5; ++i) {
+ if (i == 2) {
+ // we deleted backup 2
+ Status s = backup_engine_->RestoreDBFromBackup(2, dbname_, dbname_);
+ ASSERT_TRUE(!s.ok());
+ } else {
+ int fill_up_to = std::min(keys_iteration * i, max_key);
+ AssertBackupConsistency(i, 0, fill_up_to, max_key);
+ }
+ }
+
+ // delete some backups -- this should leave only backups 3 and 5 alive
+ ASSERT_OK(backup_engine_->DeleteBackup(4));
+ ASSERT_OK(backup_engine_->PurgeOldBackups(2));
+
+ std::vector<BackupInfo> backup_info;
+ backup_engine_->GetBackupInfo(&backup_info);
+ ASSERT_EQ(2UL, backup_info.size());
+
+ // check backup 3
+ AssertBackupConsistency(3, 0, 3 * keys_iteration, max_key);
+ // check backup 5
+ AssertBackupConsistency(5, 0, max_key);
+
+ CloseBackupEngine();
+}
+
+INSTANTIATE_TEST_CASE_P(BackupableDBTestWithParam, BackupableDBTestWithParam,
+ ::testing::Bool());
+
+// this will make sure that backup does not copy the same file twice
+TEST_F(BackupableDBTest, NoDoubleCopy_And_AutoGC) {
+ OpenDBAndBackupEngine(true, true);
+
+ // should write 5 DB files + one meta file
+ test_backup_env_->SetLimitWrittenFiles(7);
+ test_backup_env_->ClearWrittenFiles();
+ test_db_env_->SetLimitWrittenFiles(0);
+ dummy_db_->live_files_ = {"/00010.sst", "/00011.sst", "/CURRENT",
+ "/MANIFEST-01"};
+ dummy_db_->wal_files_ = {{"/00011.log", true}, {"/00012.log", false}};
+ test_db_env_->SetFilenamesForMockedAttrs(dummy_db_->live_files_);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), false));
+ std::vector<std::string> should_have_written = {
+ "/shared/.00010.sst.tmp", "/shared/.00011.sst.tmp", "/private/1/CURRENT",
+ "/private/1/MANIFEST-01", "/private/1/00011.log", "/meta/.1.tmp"};
+ AppendPath(backupdir_, should_have_written);
+ test_backup_env_->AssertWrittenFiles(should_have_written);
+
+ char db_number = '1';
+
+ for (std::string other_sst : {"00015.sst", "00017.sst", "00019.sst"}) {
+ // should write 4 new DB files + one meta file
+ // should not write/copy 00010.sst, since it's already there!
+ test_backup_env_->SetLimitWrittenFiles(6);
+ test_backup_env_->ClearWrittenFiles();
+
+ dummy_db_->live_files_ = {"/00010.sst", "/" + other_sst, "/CURRENT",
+ "/MANIFEST-01"};
+ dummy_db_->wal_files_ = {{"/00011.log", true}, {"/00012.log", false}};
+ test_db_env_->SetFilenamesForMockedAttrs(dummy_db_->live_files_);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), false));
+ // should not open 00010.sst - it's already there
+
+ ++db_number;
+ std::string private_dir = std::string("/private/") + db_number;
+ should_have_written = {
+ "/shared/." + other_sst + ".tmp", private_dir + "/CURRENT",
+ private_dir + "/MANIFEST-01", private_dir + "/00011.log",
+ std::string("/meta/.") + db_number + ".tmp"};
+ AppendPath(backupdir_, should_have_written);
+ test_backup_env_->AssertWrittenFiles(should_have_written);
+ }
+
+ ASSERT_OK(backup_engine_->DeleteBackup(1));
+ ASSERT_OK(test_backup_env_->FileExists(backupdir_ + "/shared/00010.sst"));
+
+ // 00011.sst was only in backup 1, should be deleted
+ ASSERT_EQ(Status::NotFound(),
+ test_backup_env_->FileExists(backupdir_ + "/shared/00011.sst"));
+ ASSERT_OK(test_backup_env_->FileExists(backupdir_ + "/shared/00015.sst"));
+
+ // MANIFEST file size should be only 100
+ uint64_t size = 0;
+ test_backup_env_->GetFileSize(backupdir_ + "/private/2/MANIFEST-01", &size);
+ ASSERT_EQ(100UL, size);
+ test_backup_env_->GetFileSize(backupdir_ + "/shared/00015.sst", &size);
+ ASSERT_EQ(200UL, size);
+
+ CloseBackupEngine();
+
+ //
+ // Now simulate incomplete delete by removing just meta
+ //
+ ASSERT_OK(test_backup_env_->DeleteFile(backupdir_ + "/meta/2"));
+
+ OpenBackupEngine();
+
+ // 1 appears to be removed, so
+ // 2 non-corrupt and 0 corrupt seen
+ std::vector<BackupInfo> backup_info;
+ std::vector<BackupID> corrupt_backup_ids;
+ backup_engine_->GetBackupInfo(&backup_info);
+ backup_engine_->GetCorruptedBackups(&corrupt_backup_ids);
+ ASSERT_EQ(2UL, backup_info.size());
+ ASSERT_EQ(0UL, corrupt_backup_ids.size());
+
+ // Keep the two we see, but this should suffice to purge unreferenced
+ // shared files from incomplete delete.
+ ASSERT_OK(backup_engine_->PurgeOldBackups(2));
+
+ // Make sure dangling sst file has been removed (somewhere along this
+ // process). GarbageCollect should not be needed.
+ ASSERT_EQ(Status::NotFound(),
+ test_backup_env_->FileExists(backupdir_ + "/shared/00015.sst"));
+ ASSERT_OK(test_backup_env_->FileExists(backupdir_ + "/shared/00017.sst"));
+ ASSERT_OK(test_backup_env_->FileExists(backupdir_ + "/shared/00019.sst"));
+
+ // Now actually purge a good one
+ ASSERT_OK(backup_engine_->PurgeOldBackups(1));
+
+ ASSERT_EQ(Status::NotFound(),
+ test_backup_env_->FileExists(backupdir_ + "/shared/00017.sst"));
+ ASSERT_OK(test_backup_env_->FileExists(backupdir_ + "/shared/00019.sst"));
+
+ CloseDBAndBackupEngine();
+}
+
+// test various kind of corruptions that may happen:
+// 1. Not able to write a file for backup - that backup should fail,
+// everything else should work
+// 2. Corrupted backup meta file or missing backuped file - we should
+// not be able to open that backup, but all other backups should be
+// fine
+// 3. Corrupted checksum value - if the checksum is not a valid uint32_t,
+// db open should fail, otherwise, it aborts during the restore process.
+TEST_F(BackupableDBTest, CorruptionsTest) {
+ const int keys_iteration = 5000;
+ Random rnd(6);
+ Status s;
+
+ OpenDBAndBackupEngine(true);
+ // create five backups
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)));
+ }
+
+ // ---------- case 1. - fail a write -----------
+ // try creating backup 6, but fail a write
+ FillDB(db_.get(), keys_iteration * 5, keys_iteration * 6);
+ test_backup_env_->SetLimitWrittenFiles(2);
+ // should fail
+ s = backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2));
+ ASSERT_TRUE(!s.ok());
+ test_backup_env_->SetLimitWrittenFiles(1000000);
+ // latest backup should have all the keys
+ CloseDBAndBackupEngine();
+ AssertBackupConsistency(0, 0, keys_iteration * 5, keys_iteration * 6);
+
+ // --------- case 2. corrupted backup meta or missing backuped file ----
+ ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/meta/5", 3));
+ // since 5 meta is now corrupted, latest backup should be 4
+ AssertBackupConsistency(0, 0, keys_iteration * 4, keys_iteration * 5);
+ OpenBackupEngine();
+ s = backup_engine_->RestoreDBFromBackup(5, dbname_, dbname_);
+ ASSERT_TRUE(!s.ok());
+ CloseBackupEngine();
+ ASSERT_OK(file_manager_->DeleteRandomFileInDir(backupdir_ + "/private/4"));
+ // 4 is corrupted, 3 is the latest backup now
+ AssertBackupConsistency(0, 0, keys_iteration * 3, keys_iteration * 5);
+ OpenBackupEngine();
+ s = backup_engine_->RestoreDBFromBackup(4, dbname_, dbname_);
+ CloseBackupEngine();
+ ASSERT_TRUE(!s.ok());
+
+ // --------- case 3. corrupted checksum value ----
+ ASSERT_OK(file_manager_->CorruptChecksum(backupdir_ + "/meta/3", false));
+ // checksum of backup 3 is an invalid value, this can be detected at
+ // db open time, and it reverts to the previous backup automatically
+ AssertBackupConsistency(0, 0, keys_iteration * 2, keys_iteration * 5);
+ // checksum of the backup 2 appears to be valid, this can cause checksum
+ // mismatch and abort restore process
+ ASSERT_OK(file_manager_->CorruptChecksum(backupdir_ + "/meta/2", true));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/2"));
+ OpenBackupEngine();
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/2"));
+ s = backup_engine_->RestoreDBFromBackup(2, dbname_, dbname_);
+ ASSERT_TRUE(!s.ok());
+
+ // make sure that no corrupt backups have actually been deleted!
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/1"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/2"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/3"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/4"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/5"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/1"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/2"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/3"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/4"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/5"));
+
+ // delete the corrupt backups and then make sure they're actually deleted
+ ASSERT_OK(backup_engine_->DeleteBackup(5));
+ ASSERT_OK(backup_engine_->DeleteBackup(4));
+ ASSERT_OK(backup_engine_->DeleteBackup(3));
+ ASSERT_OK(backup_engine_->DeleteBackup(2));
+ // Should not be needed anymore with auto-GC on DeleteBackup
+ //(void)backup_engine_->GarbageCollect();
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/meta/5"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/private/5"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/meta/4"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/private/4"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/meta/3"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/private/3"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/meta/2"));
+ ASSERT_EQ(Status::NotFound(),
+ file_manager_->FileExists(backupdir_ + "/private/2"));
+
+ CloseBackupEngine();
+ AssertBackupConsistency(0, 0, keys_iteration * 1, keys_iteration * 5);
+
+ // new backup should be 2!
+ OpenDBAndBackupEngine();
+ FillDB(db_.get(), keys_iteration * 1, keys_iteration * 2);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)));
+ CloseDBAndBackupEngine();
+ AssertBackupConsistency(2, 0, keys_iteration * 2, keys_iteration * 5);
+}
+
+TEST_F(BackupableDBTest, InterruptCreationTest) {
+ // Interrupt backup creation by failing new writes and failing cleanup of the
+ // partial state. Then verify a subsequent backup can still succeed.
+ const int keys_iteration = 5000;
+ Random rnd(6);
+
+ OpenDBAndBackupEngine(true /* destroy_old_data */);
+ FillDB(db_.get(), 0, keys_iteration);
+ test_backup_env_->SetLimitWrittenFiles(2);
+ test_backup_env_->SetDeleteFileFailure(true);
+ // should fail creation
+ ASSERT_FALSE(
+ backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)).ok());
+ CloseDBAndBackupEngine();
+ // should also fail cleanup so the tmp directory stays behind
+ ASSERT_OK(backup_chroot_env_->FileExists(backupdir_ + "/private/1/"));
+
+ OpenDBAndBackupEngine(false /* destroy_old_data */);
+ test_backup_env_->SetLimitWrittenFiles(1000000);
+ test_backup_env_->SetDeleteFileFailure(false);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)));
+ // latest backup should have all the keys
+ CloseDBAndBackupEngine();
+ AssertBackupConsistency(0, 0, keys_iteration);
+}
+
+inline std::string OptionsPath(std::string ret, int backupID) {
+ ret += "/private/";
+ ret += std::to_string(backupID);
+ ret += "/";
+ return ret;
+}
+
+// Backup the LATEST options file to
+// "<backup_dir>/private/<backup_id>/OPTIONS<number>"
+
+TEST_F(BackupableDBTest, BackupOptions) {
+ OpenDBAndBackupEngine(true);
+ for (int i = 1; i < 5; i++) {
+ std::string name;
+ std::vector<std::string> filenames;
+ // Must reset() before reset(OpenDB()) again.
+ // Calling OpenDB() while *db_ is existing will cause LOCK issue
+ db_.reset();
+ db_.reset(OpenDB());
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ ROCKSDB_NAMESPACE::GetLatestOptionsFileName(db_->GetName(), options_.env,
+ &name);
+ ASSERT_OK(file_manager_->FileExists(OptionsPath(backupdir_, i) + name));
+ backup_chroot_env_->GetChildren(OptionsPath(backupdir_, i), &filenames);
+ for (auto fn : filenames) {
+ if (fn.compare(0, 7, "OPTIONS") == 0) {
+ ASSERT_EQ(name, fn);
+ }
+ }
+ }
+
+ CloseDBAndBackupEngine();
+}
+
+TEST_F(BackupableDBTest, SetOptionsBackupRaceCondition) {
+ OpenDBAndBackupEngine(true);
+ SyncPoint::GetInstance()->LoadDependency(
+ {{"CheckpointImpl::CreateCheckpoint:SavedLiveFiles1",
+ "BackupableDBTest::SetOptionsBackupRaceCondition:BeforeSetOptions"},
+ {"BackupableDBTest::SetOptionsBackupRaceCondition:AfterSetOptions",
+ "CheckpointImpl::CreateCheckpoint:SavedLiveFiles2"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::port::Thread setoptions_thread{[this]() {
+ TEST_SYNC_POINT(
+ "BackupableDBTest::SetOptionsBackupRaceCondition:BeforeSetOptions");
+ DBImpl* dbi = static_cast<DBImpl*>(db_.get());
+ // Change arbitrary option to trigger OPTIONS file deletion
+ ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(),
+ {{"paranoid_file_checks", "false"}}));
+ ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(),
+ {{"paranoid_file_checks", "true"}}));
+ ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(),
+ {{"paranoid_file_checks", "false"}}));
+ TEST_SYNC_POINT(
+ "BackupableDBTest::SetOptionsBackupRaceCondition:AfterSetOptions");
+ }};
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get()));
+ setoptions_thread.join();
+ CloseDBAndBackupEngine();
+}
+
+// This test verifies we don't delete the latest backup when read-only option is
+// set
+TEST_F(BackupableDBTest, NoDeleteWithReadOnly) {
+ const int keys_iteration = 5000;
+ Random rnd(6);
+ Status s;
+
+ OpenDBAndBackupEngine(true);
+ // create five backups
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(rnd.Next() % 2)));
+ }
+ CloseDBAndBackupEngine();
+ ASSERT_OK(file_manager_->WriteToFile(backupdir_ + "/LATEST_BACKUP", "4"));
+
+ backupable_options_->destroy_old_data = false;
+ BackupEngineReadOnly* read_only_backup_engine;
+ ASSERT_OK(BackupEngineReadOnly::Open(backup_chroot_env_.get(),
+ *backupable_options_,
+ &read_only_backup_engine));
+
+ // assert that data from backup 5 is still here (even though LATEST_BACKUP
+ // says 4 is latest)
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/meta/5"));
+ ASSERT_OK(file_manager_->FileExists(backupdir_ + "/private/5"));
+
+ // Behavior change: We now ignore LATEST_BACKUP contents. This means that
+ // we should have 5 backups, even if LATEST_BACKUP says 4.
+ std::vector<BackupInfo> backup_info;
+ read_only_backup_engine->GetBackupInfo(&backup_info);
+ ASSERT_EQ(5UL, backup_info.size());
+ delete read_only_backup_engine;
+}
+
+TEST_F(BackupableDBTest, FailOverwritingBackups) {
+ options_.write_buffer_size = 1024 * 1024 * 1024; // 1GB
+ options_.disable_auto_compactions = true;
+
+ // create backups 1, 2, 3, 4, 5
+ OpenDBAndBackupEngine(true);
+ for (int i = 0; i < 5; ++i) {
+ CloseDBAndBackupEngine();
+ DeleteLogFiles();
+ OpenDBAndBackupEngine(false);
+ FillDB(db_.get(), 100 * i, 100 * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ }
+ CloseDBAndBackupEngine();
+
+ // restore 3
+ OpenBackupEngine();
+ ASSERT_OK(backup_engine_->RestoreDBFromBackup(3, dbname_, dbname_));
+ CloseBackupEngine();
+
+ OpenDBAndBackupEngine(false);
+ FillDB(db_.get(), 0, 300);
+ Status s = backup_engine_->CreateNewBackup(db_.get(), true);
+ // the new backup fails because new table files
+ // clash with old table files from backups 4 and 5
+ // (since write_buffer_size is huge, we can be sure that
+ // each backup will generate only one sst file and that
+ // a file generated by a new backup is the same as
+ // sst file generated by backup 4)
+ ASSERT_TRUE(s.IsCorruption());
+ ASSERT_OK(backup_engine_->DeleteBackup(4));
+ ASSERT_OK(backup_engine_->DeleteBackup(5));
+ // now, the backup can succeed
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ CloseDBAndBackupEngine();
+}
+
+TEST_F(BackupableDBTest, NoShareTableFiles) {
+ const int keys_iteration = 5000;
+ OpenDBAndBackupEngine(true, false, kNoShare);
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(i % 2)));
+ }
+ CloseDBAndBackupEngine();
+
+ for (int i = 0; i < 5; ++i) {
+ AssertBackupConsistency(i + 1, 0, keys_iteration * (i + 1),
+ keys_iteration * 6);
+ }
+}
+
+// Verify that you can backup and restore with share_files_with_checksum on
+TEST_F(BackupableDBTest, ShareTableFilesWithChecksums) {
+ const int keys_iteration = 5000;
+ OpenDBAndBackupEngine(true, false, kShareWithChecksum);
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), !!(i % 2)));
+ }
+ CloseDBAndBackupEngine();
+
+ for (int i = 0; i < 5; ++i) {
+ AssertBackupConsistency(i + 1, 0, keys_iteration * (i + 1),
+ keys_iteration * 6);
+ }
+}
+
+// Verify that you can backup and restore using share_files_with_checksum set to
+// false and then transition this option to true
+TEST_F(BackupableDBTest, ShareTableFilesWithChecksumsTransition) {
+ const int keys_iteration = 5000;
+ // set share_files_with_checksum to false
+ OpenDBAndBackupEngine(true, false, kShareNoChecksum);
+ for (int i = 0; i < 5; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ }
+ CloseDBAndBackupEngine();
+
+ for (int i = 0; i < 5; ++i) {
+ AssertBackupConsistency(i + 1, 0, keys_iteration * (i + 1),
+ keys_iteration * 6);
+ }
+
+ // set share_files_with_checksum to true and do some more backups
+ OpenDBAndBackupEngine(false /* destroy_old_data */, false,
+ kShareWithChecksum);
+ for (int i = 5; i < 10; ++i) {
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ }
+ CloseDBAndBackupEngine();
+
+ // Verify first (about to delete)
+ AssertBackupConsistency(1, 0, keys_iteration, keys_iteration * 11);
+
+ // For an extra challenge, make sure that GarbageCollect / DeleteBackup
+ // is OK even if we open without share_table_files
+ OpenDBAndBackupEngine(false /* destroy_old_data */, false, kNoShare);
+ backup_engine_->DeleteBackup(1);
+ backup_engine_->GarbageCollect();
+ CloseDBAndBackupEngine();
+
+ // Verify rest (not deleted)
+ for (int i = 1; i < 10; ++i) {
+ AssertBackupConsistency(i + 1, 0, keys_iteration * (i + 1),
+ keys_iteration * 11);
+ }
+}
+
+// This test simulates cleaning up after aborted or incomplete creation
+// of a new backup.
+TEST_F(BackupableDBTest, DeleteTmpFiles) {
+ for (int cleanup_fn : {1, 2, 3, 4}) {
+ for (ShareOption shared_option : kAllShareOptions) {
+ OpenDBAndBackupEngine(false /* destroy_old_data */, false /* dummy */,
+ shared_option);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get()));
+ BackupID next_id = 1;
+ BackupID oldest_id = std::numeric_limits<BackupID>::max();
+ {
+ std::vector<BackupInfo> backup_info;
+ backup_engine_->GetBackupInfo(&backup_info);
+ for (const auto& bi : backup_info) {
+ next_id = std::max(next_id, bi.backup_id + 1);
+ oldest_id = std::min(oldest_id, bi.backup_id);
+ }
+ }
+ CloseDBAndBackupEngine();
+
+ // An aborted or incomplete new backup will always be in the next
+ // id (maybe more)
+ std::string next_private = "private/" + std::to_string(next_id);
+
+ // NOTE: both shared and shared_checksum should be cleaned up
+ // regardless of how the backup engine is opened.
+ std::vector<std::string> tmp_files_and_dirs;
+ for (const auto& dir_and_file : {
+ std::make_pair(std::string("shared"),
+ std::string(".00006.sst.tmp")),
+ std::make_pair(std::string("shared_checksum"),
+ std::string(".00007.sst.tmp")),
+ std::make_pair(next_private, std::string("00003.sst")),
+ }) {
+ std::string dir = backupdir_ + "/" + dir_and_file.first;
+ file_manager_->CreateDir(dir);
+ ASSERT_OK(file_manager_->FileExists(dir));
+
+ std::string file = dir + "/" + dir_and_file.second;
+ file_manager_->WriteToFile(file, "tmp");
+ ASSERT_OK(file_manager_->FileExists(file));
+
+ tmp_files_and_dirs.push_back(file);
+ }
+ if (cleanup_fn != /*CreateNewBackup*/ 4) {
+ // This exists after CreateNewBackup because it's deleted then
+ // re-created.
+ tmp_files_and_dirs.push_back(backupdir_ + "/" + next_private);
+ }
+
+ OpenDBAndBackupEngine(false /* destroy_old_data */, false /* dummy */,
+ shared_option);
+ // Need to call one of these explicitly to delete tmp files
+ switch (cleanup_fn) {
+ case 1:
+ ASSERT_OK(backup_engine_->GarbageCollect());
+ break;
+ case 2:
+ ASSERT_OK(backup_engine_->DeleteBackup(oldest_id));
+ break;
+ case 3:
+ ASSERT_OK(backup_engine_->PurgeOldBackups(1));
+ break;
+ case 4:
+ // Does a garbage collect if it sees that next private dir exists
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get()));
+ break;
+ default:
+ assert(false);
+ }
+ CloseDBAndBackupEngine();
+ for (std::string file_or_dir : tmp_files_and_dirs) {
+ if (file_manager_->FileExists(file_or_dir) != Status::NotFound()) {
+ FAIL() << file_or_dir << " was expected to be deleted." << cleanup_fn;
+ }
+ }
+ }
+ }
+}
+
+TEST_F(BackupableDBTest, KeepLogFiles) {
+ backupable_options_->backup_log_files = false;
+ // basically infinite
+ options_.WAL_ttl_seconds = 24 * 60 * 60;
+ OpenDBAndBackupEngine(true);
+ FillDB(db_.get(), 0, 100);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ FillDB(db_.get(), 100, 200);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), false));
+ FillDB(db_.get(), 200, 300);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ FillDB(db_.get(), 300, 400);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ FillDB(db_.get(), 400, 500);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ CloseDBAndBackupEngine();
+
+ // all data should be there if we call with keep_log_files = true
+ AssertBackupConsistency(0, 0, 500, 600, true);
+}
+
+TEST_F(BackupableDBTest, RateLimiting) {
+ size_t const kMicrosPerSec = 1000 * 1000LL;
+ uint64_t const MB = 1024 * 1024;
+
+ const std::vector<std::pair<uint64_t, uint64_t>> limits(
+ {{1 * MB, 5 * MB}, {2 * MB, 3 * MB}});
+
+ std::shared_ptr<RateLimiter> backupThrottler(NewGenericRateLimiter(1));
+ std::shared_ptr<RateLimiter> restoreThrottler(NewGenericRateLimiter(1));
+
+ for (bool makeThrottler : {false, true}) {
+ if (makeThrottler) {
+ backupable_options_->backup_rate_limiter = backupThrottler;
+ backupable_options_->restore_rate_limiter = restoreThrottler;
+ }
+ // iter 0 -- single threaded
+ // iter 1 -- multi threaded
+ for (int iter = 0; iter < 2; ++iter) {
+ for (const auto& limit : limits) {
+ // destroy old data
+ DestroyDB(dbname_, Options());
+ if (makeThrottler) {
+ backupThrottler->SetBytesPerSecond(limit.first);
+ restoreThrottler->SetBytesPerSecond(limit.second);
+ } else {
+ backupable_options_->backup_rate_limit = limit.first;
+ backupable_options_->restore_rate_limit = limit.second;
+ }
+ backupable_options_->max_background_operations = (iter == 0) ? 1 : 10;
+ options_.compression = kNoCompression;
+ OpenDBAndBackupEngine(true);
+ size_t bytes_written = FillDB(db_.get(), 0, 100000);
+
+ auto start_backup = db_chroot_env_->NowMicros();
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), false));
+ auto backup_time = db_chroot_env_->NowMicros() - start_backup;
+ auto rate_limited_backup_time =
+ (bytes_written * kMicrosPerSec) / limit.first;
+ ASSERT_GT(backup_time, 0.8 * rate_limited_backup_time);
+
+ CloseDBAndBackupEngine();
+
+ OpenBackupEngine();
+ auto start_restore = db_chroot_env_->NowMicros();
+ ASSERT_OK(backup_engine_->RestoreDBFromLatestBackup(dbname_, dbname_));
+ auto restore_time = db_chroot_env_->NowMicros() - start_restore;
+ CloseBackupEngine();
+ auto rate_limited_restore_time =
+ (bytes_written * kMicrosPerSec) / limit.second;
+ ASSERT_GT(restore_time, 0.8 * rate_limited_restore_time);
+
+ AssertBackupConsistency(0, 0, 100000, 100010);
+ }
+ }
+ }
+}
+
+TEST_F(BackupableDBTest, ReadOnlyBackupEngine) {
+ DestroyDB(dbname_, options_);
+ OpenDBAndBackupEngine(true);
+ FillDB(db_.get(), 0, 100);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ FillDB(db_.get(), 100, 200);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+
+ backupable_options_->destroy_old_data = false;
+ test_backup_env_->ClearWrittenFiles();
+ test_backup_env_->SetLimitDeleteFiles(0);
+ BackupEngineReadOnly* read_only_backup_engine;
+ ASSERT_OK(BackupEngineReadOnly::Open(
+ db_chroot_env_.get(), *backupable_options_, &read_only_backup_engine));
+ std::vector<BackupInfo> backup_info;
+ read_only_backup_engine->GetBackupInfo(&backup_info);
+ ASSERT_EQ(backup_info.size(), 2U);
+
+ RestoreOptions restore_options(false);
+ ASSERT_OK(read_only_backup_engine->RestoreDBFromLatestBackup(
+ dbname_, dbname_, restore_options));
+ delete read_only_backup_engine;
+ std::vector<std::string> should_have_written;
+ test_backup_env_->AssertWrittenFiles(should_have_written);
+
+ DB* db = OpenDB();
+ AssertExists(db, 0, 200);
+ delete db;
+}
+
+TEST_F(BackupableDBTest, ProgressCallbackDuringBackup) {
+ DestroyDB(dbname_, options_);
+ OpenDBAndBackupEngine(true);
+ FillDB(db_.get(), 0, 100);
+ bool is_callback_invoked = false;
+ ASSERT_OK(backup_engine_->CreateNewBackup(
+ db_.get(), true,
+ [&is_callback_invoked]() { is_callback_invoked = true; }));
+
+ ASSERT_TRUE(is_callback_invoked);
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+}
+
+TEST_F(BackupableDBTest, GarbageCollectionBeforeBackup) {
+ DestroyDB(dbname_, options_);
+ OpenDBAndBackupEngine(true);
+
+ backup_chroot_env_->CreateDirIfMissing(backupdir_ + "/shared");
+ std::string file_five = backupdir_ + "/shared/000007.sst";
+ std::string file_five_contents = "I'm not really a sst file";
+ // this depends on the fact that 00007.sst is the first file created by the DB
+ ASSERT_OK(file_manager_->WriteToFile(file_five, file_five_contents));
+
+ FillDB(db_.get(), 0, 100);
+ // backup overwrites file 000007.sst
+ ASSERT_TRUE(backup_engine_->CreateNewBackup(db_.get(), true).ok());
+
+ std::string new_file_five_contents;
+ ASSERT_OK(ReadFileToString(backup_chroot_env_.get(), file_five,
+ &new_file_five_contents));
+ // file 000007.sst was overwritten
+ ASSERT_TRUE(new_file_five_contents != file_five_contents);
+
+ CloseDBAndBackupEngine();
+
+ AssertBackupConsistency(0, 0, 100);
+}
+
+// Test that we properly propagate Env failures
+TEST_F(BackupableDBTest, EnvFailures) {
+ BackupEngine* backup_engine;
+
+ // get children failure
+ {
+ test_backup_env_->SetGetChildrenFailure(true);
+ ASSERT_NOK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ test_backup_env_->SetGetChildrenFailure(false);
+ }
+
+ // created dir failure
+ {
+ test_backup_env_->SetCreateDirIfMissingFailure(true);
+ ASSERT_NOK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ test_backup_env_->SetCreateDirIfMissingFailure(false);
+ }
+
+ // new directory failure
+ {
+ test_backup_env_->SetNewDirectoryFailure(true);
+ ASSERT_NOK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ test_backup_env_->SetNewDirectoryFailure(false);
+ }
+
+ // Read from meta-file failure
+ {
+ DestroyDB(dbname_, options_);
+ OpenDBAndBackupEngine(true);
+ FillDB(db_.get(), 0, 100);
+ ASSERT_TRUE(backup_engine_->CreateNewBackup(db_.get(), true).ok());
+ CloseDBAndBackupEngine();
+ test_backup_env_->SetDummySequentialFile(true);
+ test_backup_env_->SetDummySequentialFileFailReads(true);
+ backupable_options_->destroy_old_data = false;
+ ASSERT_NOK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ test_backup_env_->SetDummySequentialFile(false);
+ test_backup_env_->SetDummySequentialFileFailReads(false);
+ }
+
+ // no failure
+ {
+ ASSERT_OK(BackupEngine::Open(test_db_env_.get(), *backupable_options_,
+ &backup_engine));
+ delete backup_engine;
+ }
+}
+
+// Verify manifest can roll while a backup is being created with the old
+// manifest.
+TEST_F(BackupableDBTest, ChangeManifestDuringBackupCreation) {
+ DestroyDB(dbname_, options_);
+ options_.max_manifest_file_size = 0; // always rollover manifest for file add
+ OpenDBAndBackupEngine(true);
+ FillDB(db_.get(), 0, 100);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"CheckpointImpl::CreateCheckpoint:SavedLiveFiles1",
+ "VersionSet::LogAndApply:WriteManifest"},
+ {"VersionSet::LogAndApply:WriteManifestDone",
+ "CheckpointImpl::CreateCheckpoint:SavedLiveFiles2"},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread flush_thread{
+ [this]() { ASSERT_OK(db_->Flush(FlushOptions())); }};
+
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), false));
+
+ flush_thread.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ // The last manifest roll would've already been cleaned up by the full scan
+ // that happens when CreateNewBackup invokes EnableFileDeletions. We need to
+ // trigger another roll to verify non-full scan purges stale manifests.
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db_.get());
+ std::string prev_manifest_path =
+ DescriptorFileName(dbname_, db_impl->TEST_Current_Manifest_FileNo());
+ FillDB(db_.get(), 0, 100);
+ ASSERT_OK(db_chroot_env_->FileExists(prev_manifest_path));
+ ASSERT_OK(db_->Flush(FlushOptions()));
+ ASSERT_TRUE(db_chroot_env_->FileExists(prev_manifest_path).IsNotFound());
+
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+ AssertBackupConsistency(0, 0, 100);
+}
+
+// see https://github.com/facebook/rocksdb/issues/921
+TEST_F(BackupableDBTest, Issue921Test) {
+ BackupEngine* backup_engine;
+ backupable_options_->share_table_files = false;
+ backup_chroot_env_->CreateDirIfMissing(backupable_options_->backup_dir);
+ backupable_options_->backup_dir += "/new_dir";
+ ASSERT_OK(BackupEngine::Open(backup_chroot_env_.get(), *backupable_options_,
+ &backup_engine));
+
+ delete backup_engine;
+}
+
+TEST_F(BackupableDBTest, BackupWithMetadata) {
+ const int keys_iteration = 5000;
+ OpenDBAndBackupEngine(true);
+ // create five backups
+ for (int i = 0; i < 5; ++i) {
+ const std::string metadata = std::to_string(i);
+ FillDB(db_.get(), keys_iteration * i, keys_iteration * (i + 1));
+ ASSERT_OK(
+ backup_engine_->CreateNewBackupWithMetadata(db_.get(), metadata, true));
+ }
+ CloseDBAndBackupEngine();
+
+ OpenDBAndBackupEngine();
+ std::vector<BackupInfo> backup_infos;
+ backup_engine_->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(5, backup_infos.size());
+ for (int i = 0; i < 5; i++) {
+ ASSERT_EQ(std::to_string(i), backup_infos[i].app_metadata);
+ }
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+}
+
+TEST_F(BackupableDBTest, BinaryMetadata) {
+ OpenDBAndBackupEngine(true);
+ std::string binaryMetadata = "abc\ndef";
+ binaryMetadata.push_back('\0');
+ binaryMetadata.append("ghi");
+ ASSERT_OK(
+ backup_engine_->CreateNewBackupWithMetadata(db_.get(), binaryMetadata));
+ CloseDBAndBackupEngine();
+
+ OpenDBAndBackupEngine();
+ std::vector<BackupInfo> backup_infos;
+ backup_engine_->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(1, backup_infos.size());
+ ASSERT_EQ(binaryMetadata, backup_infos[0].app_metadata);
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+}
+
+TEST_F(BackupableDBTest, MetadataTooLarge) {
+ OpenDBAndBackupEngine(true);
+ std::string largeMetadata(1024 * 1024 + 1, 0);
+ ASSERT_NOK(
+ backup_engine_->CreateNewBackupWithMetadata(db_.get(), largeMetadata));
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+}
+
+TEST_F(BackupableDBTest, LimitBackupsOpened) {
+ // Verify the specified max backups are opened, including skipping over
+ // corrupted backups.
+ //
+ // Setup:
+ // - backups 1, 2, and 4 are valid
+ // - backup 3 is corrupt
+ // - max_valid_backups_to_open == 2
+ //
+ // Expectation: the engine opens backups 4 and 2 since those are latest two
+ // non-corrupt backups.
+ const int kNumKeys = 5000;
+ OpenDBAndBackupEngine(true);
+ for (int i = 1; i <= 4; ++i) {
+ FillDB(db_.get(), kNumKeys * i, kNumKeys * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ if (i == 3) {
+ ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/meta/3", 3));
+ }
+ }
+ CloseDBAndBackupEngine();
+
+ backupable_options_->max_valid_backups_to_open = 2;
+ backupable_options_->destroy_old_data = false;
+ BackupEngineReadOnly* read_only_backup_engine;
+ ASSERT_OK(BackupEngineReadOnly::Open(backup_chroot_env_.get(),
+ *backupable_options_,
+ &read_only_backup_engine));
+
+ std::vector<BackupInfo> backup_infos;
+ read_only_backup_engine->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(2, backup_infos.size());
+ ASSERT_EQ(2, backup_infos[0].backup_id);
+ ASSERT_EQ(4, backup_infos[1].backup_id);
+ delete read_only_backup_engine;
+}
+
+TEST_F(BackupableDBTest, IgnoreLimitBackupsOpenedWhenNotReadOnly) {
+ // Verify the specified max_valid_backups_to_open is ignored if the engine
+ // is not read-only.
+ //
+ // Setup:
+ // - backups 1, 2, and 4 are valid
+ // - backup 3 is corrupt
+ // - max_valid_backups_to_open == 2
+ //
+ // Expectation: the engine opens backups 4, 2, and 1 since those are latest
+ // non-corrupt backups, by ignoring max_valid_backups_to_open == 2.
+ const int kNumKeys = 5000;
+ OpenDBAndBackupEngine(true);
+ for (int i = 1; i <= 4; ++i) {
+ FillDB(db_.get(), kNumKeys * i, kNumKeys * (i + 1));
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ if (i == 3) {
+ ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/meta/3", 3));
+ }
+ }
+ CloseDBAndBackupEngine();
+
+ backupable_options_->max_valid_backups_to_open = 2;
+ OpenDBAndBackupEngine();
+ std::vector<BackupInfo> backup_infos;
+ backup_engine_->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(3, backup_infos.size());
+ ASSERT_EQ(1, backup_infos[0].backup_id);
+ ASSERT_EQ(2, backup_infos[1].backup_id);
+ ASSERT_EQ(4, backup_infos[2].backup_id);
+ CloseDBAndBackupEngine();
+ DestroyDB(dbname_, options_);
+}
+
+TEST_F(BackupableDBTest, CreateWhenLatestBackupCorrupted) {
+ // we should pick an ID greater than corrupted backups' IDs so creation can
+ // succeed even when latest backup is corrupted.
+ const int kNumKeys = 5000;
+ OpenDBAndBackupEngine(true /* destroy_old_data */);
+ FillDB(db_.get(), 0 /* from */, kNumKeys);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(),
+ true /* flush_before_backup */));
+ ASSERT_OK(file_manager_->CorruptFile(backupdir_ + "/meta/1",
+ 3 /* bytes_to_corrupt */));
+ CloseDBAndBackupEngine();
+
+ OpenDBAndBackupEngine();
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(),
+ true /* flush_before_backup */));
+ std::vector<BackupInfo> backup_infos;
+ backup_engine_->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(1, backup_infos.size());
+ ASSERT_EQ(2, backup_infos[0].backup_id);
+}
+
+TEST_F(BackupableDBTest, WriteOnlyEngineNoSharedFileDeletion) {
+ // Verifies a write-only BackupEngine does not delete files belonging to valid
+ // backups when GarbageCollect, PurgeOldBackups, or DeleteBackup are called.
+ const int kNumKeys = 5000;
+ for (int i = 0; i < 3; ++i) {
+ OpenDBAndBackupEngine(i == 0 /* destroy_old_data */);
+ FillDB(db_.get(), i * kNumKeys, (i + 1) * kNumKeys);
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(), true));
+ CloseDBAndBackupEngine();
+
+ backupable_options_->max_valid_backups_to_open = 0;
+ OpenDBAndBackupEngine();
+ switch (i) {
+ case 0:
+ ASSERT_OK(backup_engine_->GarbageCollect());
+ break;
+ case 1:
+ ASSERT_OK(backup_engine_->PurgeOldBackups(1 /* num_backups_to_keep */));
+ break;
+ case 2:
+ ASSERT_OK(backup_engine_->DeleteBackup(2 /* backup_id */));
+ break;
+ default:
+ assert(false);
+ }
+ CloseDBAndBackupEngine();
+
+ backupable_options_->max_valid_backups_to_open = port::kMaxInt32;
+ AssertBackupConsistency(i + 1, 0, (i + 1) * kNumKeys);
+ }
+}
+
+TEST_P(BackupableDBTestWithParam, BackupUsingDirectIO) {
+ // Tests direct I/O on the backup engine's reads and writes on the DB env and
+ // backup env
+ // We use ChrootEnv underneath so the below line checks for direct I/O support
+ // in the chroot directory, not the true filesystem root.
+ if (!test::IsDirectIOSupported(test_db_env_.get(), "/")) {
+ return;
+ }
+ const int kNumKeysPerBackup = 100;
+ const int kNumBackups = 3;
+ options_.use_direct_reads = true;
+ OpenDBAndBackupEngine(true /* destroy_old_data */);
+ for (int i = 0; i < kNumBackups; ++i) {
+ FillDB(db_.get(), i * kNumKeysPerBackup /* from */,
+ (i + 1) * kNumKeysPerBackup /* to */);
+ ASSERT_OK(db_->Flush(FlushOptions()));
+
+ // Clear the file open counters and then do a bunch of backup engine ops.
+ // For all ops, files should be opened in direct mode.
+ test_backup_env_->ClearFileOpenCounters();
+ test_db_env_->ClearFileOpenCounters();
+ CloseBackupEngine();
+ OpenBackupEngine();
+ ASSERT_OK(backup_engine_->CreateNewBackup(db_.get(),
+ false /* flush_before_backup */));
+ ASSERT_OK(backup_engine_->VerifyBackup(i + 1));
+ CloseBackupEngine();
+ OpenBackupEngine();
+ std::vector<BackupInfo> backup_infos;
+ backup_engine_->GetBackupInfo(&backup_infos);
+ ASSERT_EQ(static_cast<size_t>(i + 1), backup_infos.size());
+
+ // Verify backup engine always opened files with direct I/O
+ ASSERT_EQ(0, test_db_env_->num_writers());
+ ASSERT_EQ(0, test_db_env_->num_rand_readers());
+ ASSERT_GT(test_db_env_->num_direct_seq_readers(), 0);
+ // Currently the DB doesn't support reading WALs or manifest with direct
+ // I/O, so subtract two.
+ ASSERT_EQ(test_db_env_->num_seq_readers() - 2,
+ test_db_env_->num_direct_seq_readers());
+ ASSERT_EQ(0, test_db_env_->num_rand_readers());
+ }
+ CloseDBAndBackupEngine();
+
+ for (int i = 0; i < kNumBackups; ++i) {
+ AssertBackupConsistency(i + 1 /* backup_id */,
+ i * kNumKeysPerBackup /* start_exist */,
+ (i + 1) * kNumKeysPerBackup /* end_exist */,
+ (i + 2) * kNumKeysPerBackup /* end */);
+ }
+}
+
+} // anon namespace
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as BackupableDB is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.cc
new file mode 100644
index 00000000..5900f092
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.cc
@@ -0,0 +1,329 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_compaction_filter.h"
+#include "db/dbformat.h"
+
+#include <cinttypes>
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+CompactionFilter::Decision BlobIndexCompactionFilterBase::FilterV2(
+ int /*level*/, const Slice& key, ValueType value_type, const Slice& value,
+ std::string* /*new_value*/, std::string* /*skip_until*/) const {
+ if (value_type != kBlobIndex) {
+ return Decision::kKeep;
+ }
+ BlobIndex blob_index;
+ Status s = blob_index.DecodeFrom(value);
+ if (!s.ok()) {
+ // Unable to decode blob index. Keeping the value.
+ return Decision::kKeep;
+ }
+ if (blob_index.HasTTL() && blob_index.expiration() <= current_time_) {
+ // Expired
+ expired_count_++;
+ expired_size_ += key.size() + value.size();
+ return Decision::kRemove;
+ }
+ if (!blob_index.IsInlined() &&
+ blob_index.file_number() < context_.next_file_number &&
+ context_.current_blob_files.count(blob_index.file_number()) == 0) {
+ // Corresponding blob file gone (most likely, evicted by FIFO eviction).
+ evicted_count_++;
+ evicted_size_ += key.size() + value.size();
+ return Decision::kRemove;
+ }
+ if (context_.fifo_eviction_seq > 0 && blob_index.HasTTL() &&
+ blob_index.expiration() < context_.evict_expiration_up_to) {
+ // Hack: Internal key is passed to BlobIndexCompactionFilter for it to
+ // get sequence number.
+ ParsedInternalKey ikey;
+ bool ok = ParseInternalKey(key, &ikey);
+ // Remove keys that could have been remove by last FIFO eviction.
+ // If get error while parsing key, ignore and continue.
+ if (ok && ikey.sequence < context_.fifo_eviction_seq) {
+ evicted_count_++;
+ evicted_size_ += key.size() + value.size();
+ return Decision::kRemove;
+ }
+ }
+ return Decision::kKeep;
+}
+
+BlobIndexCompactionFilterGC::~BlobIndexCompactionFilterGC() {
+ if (blob_file_) {
+ CloseAndRegisterNewBlobFile();
+ }
+
+ assert(context_gc_.blob_db_impl);
+
+ ROCKS_LOG_INFO(context_gc_.blob_db_impl->db_options_.info_log,
+ "GC pass finished %s: encountered %" PRIu64 " blobs (%" PRIu64
+ " bytes), relocated %" PRIu64 " blobs (%" PRIu64
+ " bytes), created %" PRIu64 " new blob file(s)",
+ !gc_stats_.HasError() ? "successfully" : "with failure",
+ gc_stats_.AllBlobs(), gc_stats_.AllBytes(),
+ gc_stats_.RelocatedBlobs(), gc_stats_.RelocatedBytes(),
+ gc_stats_.NewFiles());
+
+ RecordTick(statistics(), BLOB_DB_GC_NUM_KEYS_RELOCATED,
+ gc_stats_.RelocatedBlobs());
+ RecordTick(statistics(), BLOB_DB_GC_BYTES_RELOCATED,
+ gc_stats_.RelocatedBytes());
+ RecordTick(statistics(), BLOB_DB_GC_NUM_NEW_FILES, gc_stats_.NewFiles());
+ RecordTick(statistics(), BLOB_DB_GC_FAILURES, gc_stats_.HasError());
+}
+
+CompactionFilter::BlobDecision BlobIndexCompactionFilterGC::PrepareBlobOutput(
+ const Slice& key, const Slice& existing_value,
+ std::string* new_value) const {
+ assert(new_value);
+
+ const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ (void)blob_db_impl;
+
+ assert(blob_db_impl);
+ assert(blob_db_impl->bdb_options_.enable_garbage_collection);
+
+ BlobIndex blob_index;
+ const Status s = blob_index.DecodeFrom(existing_value);
+ if (!s.ok()) {
+ gc_stats_.SetError();
+ return BlobDecision::kCorruption;
+ }
+
+ if (blob_index.IsInlined()) {
+ gc_stats_.AddBlob(blob_index.value().size());
+
+ return BlobDecision::kKeep;
+ }
+
+ gc_stats_.AddBlob(blob_index.size());
+
+ if (blob_index.HasTTL()) {
+ return BlobDecision::kKeep;
+ }
+
+ if (blob_index.file_number() >= context_gc_.cutoff_file_number) {
+ return BlobDecision::kKeep;
+ }
+
+ // Note: each compaction generates its own blob files, which, depending on the
+ // workload, might result in many small blob files. The total number of files
+ // is bounded though (determined by the number of compactions and the blob
+ // file size option).
+ if (!OpenNewBlobFileIfNeeded()) {
+ gc_stats_.SetError();
+ return BlobDecision::kIOError;
+ }
+
+ PinnableSlice blob;
+ CompressionType compression_type = kNoCompression;
+ if (!ReadBlobFromOldFile(key, blob_index, &blob, &compression_type)) {
+ gc_stats_.SetError();
+ return BlobDecision::kIOError;
+ }
+
+ uint64_t new_blob_file_number = 0;
+ uint64_t new_blob_offset = 0;
+ if (!WriteBlobToNewFile(key, blob, &new_blob_file_number, &new_blob_offset)) {
+ gc_stats_.SetError();
+ return BlobDecision::kIOError;
+ }
+
+ if (!CloseAndRegisterNewBlobFileIfNeeded()) {
+ gc_stats_.SetError();
+ return BlobDecision::kIOError;
+ }
+
+ BlobIndex::EncodeBlob(new_value, new_blob_file_number, new_blob_offset,
+ blob.size(), compression_type);
+
+ gc_stats_.AddRelocatedBlob(blob_index.size());
+
+ return BlobDecision::kChangeValue;
+}
+
+bool BlobIndexCompactionFilterGC::OpenNewBlobFileIfNeeded() const {
+ if (blob_file_) {
+ assert(writer_);
+ return true;
+ }
+
+ BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+
+ const Status s = blob_db_impl->CreateBlobFileAndWriter(
+ /* has_ttl */ false, ExpirationRange(), "GC", &blob_file_, &writer_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
+ "Error opening new blob file during GC, status: %s",
+ s.ToString().c_str());
+
+ return false;
+ }
+
+ assert(blob_file_);
+ assert(writer_);
+
+ gc_stats_.AddNewFile();
+
+ return true;
+}
+
+bool BlobIndexCompactionFilterGC::ReadBlobFromOldFile(
+ const Slice& key, const BlobIndex& blob_index, PinnableSlice* blob,
+ CompressionType* compression_type) const {
+ BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+
+ const Status s = blob_db_impl->GetRawBlobFromFile(
+ key, blob_index.file_number(), blob_index.offset(), blob_index.size(),
+ blob, compression_type);
+
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
+ "Error reading blob during GC, key: %s (%s), status: %s",
+ key.ToString(/* output_hex */ true).c_str(),
+ blob_index.DebugString(/* output_hex */ true).c_str(),
+ s.ToString().c_str());
+
+ return false;
+ }
+
+ return true;
+}
+
+bool BlobIndexCompactionFilterGC::WriteBlobToNewFile(
+ const Slice& key, const Slice& blob, uint64_t* new_blob_file_number,
+ uint64_t* new_blob_offset) const {
+ assert(new_blob_file_number);
+ assert(new_blob_offset);
+
+ assert(blob_file_);
+ *new_blob_file_number = blob_file_->BlobFileNumber();
+
+ assert(writer_);
+ uint64_t new_key_offset = 0;
+ const Status s = writer_->AddRecord(key, blob, kNoExpiration, &new_key_offset,
+ new_blob_offset);
+
+ if (!s.ok()) {
+ const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+
+ ROCKS_LOG_ERROR(
+ blob_db_impl->db_options_.info_log,
+ "Error writing blob to new file %s during GC, key: %s, status: %s",
+ blob_file_->PathName().c_str(),
+ key.ToString(/* output_hex */ true).c_str(), s.ToString().c_str());
+ return false;
+ }
+
+ const uint64_t new_size =
+ BlobLogRecord::kHeaderSize + key.size() + blob.size();
+ blob_file_->BlobRecordAdded(new_size);
+
+ BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+
+ blob_db_impl->total_blob_size_ += new_size;
+
+ return true;
+}
+
+bool BlobIndexCompactionFilterGC::CloseAndRegisterNewBlobFileIfNeeded() const {
+ const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+
+ assert(blob_file_);
+ if (blob_file_->GetFileSize() < blob_db_impl->bdb_options_.blob_file_size) {
+ return true;
+ }
+
+ return CloseAndRegisterNewBlobFile();
+}
+
+bool BlobIndexCompactionFilterGC::CloseAndRegisterNewBlobFile() const {
+ BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
+ assert(blob_db_impl);
+ assert(blob_file_);
+
+ Status s;
+
+ {
+ WriteLock wl(&blob_db_impl->mutex_);
+
+ s = blob_db_impl->CloseBlobFile(blob_file_);
+
+ // Note: we delay registering the new blob file until it's closed to
+ // prevent FIFO eviction from processing it during the GC run.
+ blob_db_impl->RegisterBlobFile(blob_file_);
+ }
+
+ assert(blob_file_->Immutable());
+ blob_file_.reset();
+
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
+ "Error closing new blob file %s during GC, status: %s",
+ blob_file_->PathName().c_str(), s.ToString().c_str());
+
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<CompactionFilter>
+BlobIndexCompactionFilterFactory::CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) {
+ assert(env());
+
+ int64_t current_time = 0;
+ Status s = env()->GetCurrentTime(&current_time);
+ if (!s.ok()) {
+ return nullptr;
+ }
+ assert(current_time >= 0);
+
+ assert(blob_db_impl());
+
+ BlobCompactionContext context;
+ blob_db_impl()->GetCompactionContext(&context);
+
+ return std::unique_ptr<CompactionFilter>(new BlobIndexCompactionFilter(
+ std::move(context), current_time, statistics()));
+}
+
+std::unique_ptr<CompactionFilter>
+BlobIndexCompactionFilterFactoryGC::CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) {
+ assert(env());
+
+ int64_t current_time = 0;
+ Status s = env()->GetCurrentTime(&current_time);
+ if (!s.ok()) {
+ return nullptr;
+ }
+ assert(current_time >= 0);
+
+ assert(blob_db_impl());
+
+ BlobCompactionContext context;
+ BlobCompactionContextGC context_gc;
+ blob_db_impl()->GetCompactionContext(&context, &context_gc);
+
+ return std::unique_ptr<CompactionFilter>(new BlobIndexCompactionFilterGC(
+ std::move(context), std::move(context_gc), current_time, statistics()));
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.h
new file mode 100644
index 00000000..409df26a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_compaction_filter.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <unordered_set>
+
+#include "db/blob_index.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/env.h"
+#include "utilities/blob_db/blob_db_gc_stats.h"
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+struct BlobCompactionContext {
+ uint64_t next_file_number = 0;
+ std::unordered_set<uint64_t> current_blob_files;
+ SequenceNumber fifo_eviction_seq = 0;
+ uint64_t evict_expiration_up_to = 0;
+};
+
+struct BlobCompactionContextGC {
+ BlobDBImpl* blob_db_impl = nullptr;
+ uint64_t cutoff_file_number = 0;
+};
+
+// Compaction filter that deletes expired blob indexes from the base DB.
+// Comes into two varieties, one for the non-GC case and one for the GC case.
+class BlobIndexCompactionFilterBase : public CompactionFilter {
+ public:
+ BlobIndexCompactionFilterBase(BlobCompactionContext&& context,
+ uint64_t current_time, Statistics* stats)
+ : context_(std::move(context)),
+ current_time_(current_time),
+ statistics_(stats) {}
+
+ ~BlobIndexCompactionFilterBase() override {
+ RecordTick(statistics_, BLOB_DB_BLOB_INDEX_EXPIRED_COUNT, expired_count_);
+ RecordTick(statistics_, BLOB_DB_BLOB_INDEX_EXPIRED_SIZE, expired_size_);
+ RecordTick(statistics_, BLOB_DB_BLOB_INDEX_EVICTED_COUNT, evicted_count_);
+ RecordTick(statistics_, BLOB_DB_BLOB_INDEX_EVICTED_SIZE, evicted_size_);
+ }
+
+ // Filter expired blob indexes regardless of snapshots.
+ bool IgnoreSnapshots() const override { return true; }
+
+ Decision FilterV2(int /*level*/, const Slice& key, ValueType value_type,
+ const Slice& value, std::string* /*new_value*/,
+ std::string* /*skip_until*/) const override;
+
+ protected:
+ Statistics* statistics() const { return statistics_; }
+
+ private:
+ BlobCompactionContext context_;
+ const uint64_t current_time_;
+ Statistics* statistics_;
+ // It is safe to not using std::atomic since the compaction filter, created
+ // from a compaction filter factroy, will not be called from multiple threads.
+ mutable uint64_t expired_count_ = 0;
+ mutable uint64_t expired_size_ = 0;
+ mutable uint64_t evicted_count_ = 0;
+ mutable uint64_t evicted_size_ = 0;
+};
+
+class BlobIndexCompactionFilter : public BlobIndexCompactionFilterBase {
+ public:
+ BlobIndexCompactionFilter(BlobCompactionContext&& context,
+ uint64_t current_time, Statistics* stats)
+ : BlobIndexCompactionFilterBase(std::move(context), current_time, stats) {
+ }
+
+ const char* Name() const override { return "BlobIndexCompactionFilter"; }
+};
+
+class BlobIndexCompactionFilterGC : public BlobIndexCompactionFilterBase {
+ public:
+ BlobIndexCompactionFilterGC(BlobCompactionContext&& context,
+ BlobCompactionContextGC&& context_gc,
+ uint64_t current_time, Statistics* stats)
+ : BlobIndexCompactionFilterBase(std::move(context), current_time, stats),
+ context_gc_(std::move(context_gc)) {}
+
+ ~BlobIndexCompactionFilterGC() override;
+
+ const char* Name() const override { return "BlobIndexCompactionFilterGC"; }
+
+ BlobDecision PrepareBlobOutput(const Slice& key, const Slice& existing_value,
+ std::string* new_value) const override;
+
+ private:
+ bool OpenNewBlobFileIfNeeded() const;
+ bool ReadBlobFromOldFile(const Slice& key, const BlobIndex& blob_index,
+ PinnableSlice* blob,
+ CompressionType* compression_type) const;
+ bool WriteBlobToNewFile(const Slice& key, const Slice& blob,
+ uint64_t* new_blob_file_number,
+ uint64_t* new_blob_offset) const;
+ bool CloseAndRegisterNewBlobFileIfNeeded() const;
+ bool CloseAndRegisterNewBlobFile() const;
+
+ private:
+ BlobCompactionContextGC context_gc_;
+ mutable std::shared_ptr<BlobFile> blob_file_;
+ mutable std::shared_ptr<Writer> writer_;
+ mutable BlobDBGarbageCollectionStats gc_stats_;
+};
+
+// Compaction filter factory; similarly to the filters above, it comes
+// in two flavors, one that creates filters that support GC, and one
+// that creates non-GC filters.
+class BlobIndexCompactionFilterFactoryBase : public CompactionFilterFactory {
+ public:
+ BlobIndexCompactionFilterFactoryBase(BlobDBImpl* _blob_db_impl, Env* _env,
+ Statistics* _statistics)
+ : blob_db_impl_(_blob_db_impl), env_(_env), statistics_(_statistics) {}
+
+ protected:
+ BlobDBImpl* blob_db_impl() const { return blob_db_impl_; }
+ Env* env() const { return env_; }
+ Statistics* statistics() const { return statistics_; }
+
+ private:
+ BlobDBImpl* blob_db_impl_;
+ Env* env_;
+ Statistics* statistics_;
+};
+
+class BlobIndexCompactionFilterFactory
+ : public BlobIndexCompactionFilterFactoryBase {
+ public:
+ BlobIndexCompactionFilterFactory(BlobDBImpl* _blob_db_impl, Env* _env,
+ Statistics* _statistics)
+ : BlobIndexCompactionFilterFactoryBase(_blob_db_impl, _env, _statistics) {
+ }
+
+ const char* Name() const override {
+ return "BlobIndexCompactionFilterFactory";
+ }
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override;
+};
+
+class BlobIndexCompactionFilterFactoryGC
+ : public BlobIndexCompactionFilterFactoryBase {
+ public:
+ BlobIndexCompactionFilterFactoryGC(BlobDBImpl* _blob_db_impl, Env* _env,
+ Statistics* _statistics)
+ : BlobIndexCompactionFilterFactoryBase(_blob_db_impl, _env, _statistics) {
+ }
+
+ const char* Name() const override {
+ return "BlobIndexCompactionFilterFactoryGC";
+ }
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.cc
new file mode 100644
index 00000000..f568ecd1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_db.h"
+
+#include <cinttypes>
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+Status BlobDB::Open(const Options& options, const BlobDBOptions& bdb_options,
+ const std::string& dbname, BlobDB** blob_db) {
+ *blob_db = nullptr;
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> handles;
+ Status s = BlobDB::Open(db_options, bdb_options, dbname, column_families,
+ &handles, blob_db);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ // i can delete the handle since DBImpl is always holding a reference to
+ // default column family
+ delete handles[0];
+ }
+ return s;
+}
+
+Status BlobDB::Open(const DBOptions& db_options,
+ const BlobDBOptions& bdb_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ BlobDB** blob_db) {
+ if (column_families.size() != 1 ||
+ column_families[0].name != kDefaultColumnFamilyName) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+
+ BlobDBImpl* blob_db_impl = new BlobDBImpl(dbname, bdb_options, db_options,
+ column_families[0].options);
+ Status s = blob_db_impl->Open(handles);
+ if (s.ok()) {
+ *blob_db = static_cast<BlobDB*>(blob_db_impl);
+ } else {
+ delete blob_db_impl;
+ *blob_db = nullptr;
+ }
+ return s;
+}
+
+BlobDB::BlobDB() : StackableDB(nullptr) {}
+
+void BlobDBOptions::Dump(Logger* log) const {
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.blob_dir: %s",
+ blob_dir.c_str());
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.path_relative: %d",
+ path_relative);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.is_fifo: %d",
+ is_fifo);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.max_db_size: %" PRIu64,
+ max_db_size);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.ttl_range_secs: %" PRIu64,
+ ttl_range_secs);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.min_blob_size: %" PRIu64,
+ min_blob_size);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.bytes_per_sync: %" PRIu64,
+ bytes_per_sync);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.blob_file_size: %" PRIu64,
+ blob_file_size);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.compression: %d",
+ static_cast<int>(compression));
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.enable_garbage_collection: %d",
+ enable_garbage_collection);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.garbage_collection_cutoff: %f",
+ garbage_collection_cutoff);
+ ROCKS_LOG_HEADER(
+ log, " BlobDBOptions.disable_background_tasks: %d",
+ disable_background_tasks);
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.h
new file mode 100644
index 00000000..72a58043
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db.h
@@ -0,0 +1,261 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+#include <string>
+#include <vector>
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/stackable_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace blob_db {
+
+// A wrapped database which puts values of KV pairs in a separate log
+// and store location to the log in the underlying DB.
+//
+// The factory needs to be moved to include/rocksdb/utilities to allow
+// users to use blob DB.
+
+struct BlobDBOptions {
+ // name of the directory under main db, where blobs will be stored.
+ // default is "blob_dir"
+ std::string blob_dir = "blob_dir";
+
+ // whether the blob_dir path is relative or absolute.
+ bool path_relative = true;
+
+ // When max_db_size is reached, evict blob files to free up space
+ // instead of returnning NoSpace error on write. Blob files will be
+ // evicted from oldest to newest, based on file creation time.
+ bool is_fifo = false;
+
+ // Maximum size of the database (including SST files and blob files).
+ //
+ // Default: 0 (no limits)
+ uint64_t max_db_size = 0;
+
+ // a new bucket is opened, for ttl_range. So if ttl_range is 600seconds
+ // (10 minutes), and the first bucket starts at 1471542000
+ // then the blob buckets will be
+ // first bucket is 1471542000 - 1471542600
+ // second bucket is 1471542600 - 1471543200
+ // and so on
+ uint64_t ttl_range_secs = 3600;
+
+ // The smallest value to store in blob log. Values smaller than this threshold
+ // will be inlined in base DB together with the key.
+ uint64_t min_blob_size = 0;
+
+ // Allows OS to incrementally sync blob files to disk for every
+ // bytes_per_sync bytes written. Users shouldn't rely on it for
+ // persistency guarantee.
+ uint64_t bytes_per_sync = 512 * 1024;
+
+ // the target size of each blob file. File will become immutable
+ // after it exceeds that size
+ uint64_t blob_file_size = 256 * 1024 * 1024;
+
+ // what compression to use for Blob's
+ CompressionType compression = kNoCompression;
+
+ // If enabled, BlobDB cleans up stale blobs in non-TTL files during compaction
+ // by rewriting the remaining live blobs to new files.
+ bool enable_garbage_collection = false;
+
+ // The cutoff in terms of blob file age for garbage collection. Blobs in
+ // the oldest N non-TTL blob files will be rewritten when encountered during
+ // compaction, where N = garbage_collection_cutoff * number_of_non_TTL_files.
+ double garbage_collection_cutoff = 0.25;
+
+ // Disable all background job. Used for test only.
+ bool disable_background_tasks = false;
+
+ void Dump(Logger* log) const;
+};
+
+class BlobDB : public StackableDB {
+ public:
+ using ROCKSDB_NAMESPACE::StackableDB::Put;
+ virtual Status Put(const WriteOptions& options, const Slice& key,
+ const Slice& value) override = 0;
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ return Put(options, key, value);
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::Delete;
+ virtual Status Delete(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ assert(db_ != nullptr);
+ return db_->Delete(options, column_family, key);
+ }
+
+ virtual Status PutWithTTL(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t ttl) = 0;
+ virtual Status PutWithTTL(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value, uint64_t ttl) {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ return PutWithTTL(options, key, value, ttl);
+ }
+
+ // Put with expiration. Key with expiration time equal to
+ // std::numeric_limits<uint64_t>::max() means the key don't expire.
+ virtual Status PutUntil(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t expiration) = 0;
+ virtual Status PutUntil(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value, uint64_t expiration) {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ return PutUntil(options, key, value, expiration);
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override = 0;
+
+ // Get value and expiration.
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value, uint64_t* expiration) = 0;
+ virtual Status Get(const ReadOptions& options, const Slice& key,
+ PinnableSlice* value, uint64_t* expiration) {
+ return Get(options, DefaultColumnFamily(), key, value, expiration);
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override = 0;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override {
+ for (auto column_family : column_families) {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return std::vector<Status>(
+ column_families.size(),
+ Status::NotSupported(
+ "Blob DB doesn't support non-default column family."));
+ }
+ }
+ return MultiGet(options, keys, values);
+ }
+ virtual void MultiGet(const ReadOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const size_t num_keys, const Slice* /*keys*/,
+ PinnableSlice* /*values*/, Status* statuses,
+ const bool /*sorted_input*/ = false) override {
+ for (size_t i = 0; i < num_keys; ++i) {
+ statuses[i] = Status::NotSupported(
+ "Blob DB doesn't support batched MultiGet");
+ }
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::SingleDelete;
+ virtual Status SingleDelete(const WriteOptions& /*wopts*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in blob db.");
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::Merge;
+ virtual Status Merge(const WriteOptions& /*options*/,
+ ColumnFamilyHandle* /*column_family*/,
+ const Slice& /*key*/, const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in blob db.");
+ }
+
+ virtual Status Write(const WriteOptions& opts,
+ WriteBatch* updates) override = 0;
+ using ROCKSDB_NAMESPACE::StackableDB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& options) override = 0;
+ virtual Iterator* NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) override {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ // Blob DB doesn't support non-default column family.
+ return nullptr;
+ }
+ return NewIterator(options);
+ }
+
+ Status CompactFiles(
+ const CompactionOptions& compact_options,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) override = 0;
+ Status CompactFiles(
+ const CompactionOptions& compact_options,
+ ColumnFamilyHandle* column_family,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) override {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+
+ return CompactFiles(compact_options, input_file_names, output_level,
+ output_path_id, output_file_names, compaction_job_info);
+ }
+
+ using ROCKSDB_NAMESPACE::StackableDB::Close;
+ virtual Status Close() override = 0;
+
+ // Opening blob db.
+ static Status Open(const Options& options, const BlobDBOptions& bdb_options,
+ const std::string& dbname, BlobDB** blob_db);
+
+ static Status Open(const DBOptions& db_options,
+ const BlobDBOptions& bdb_options,
+ const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ BlobDB** blob_db);
+
+ virtual BlobDBOptions GetBlobDBOptions() const = 0;
+
+ virtual Status SyncBlobFiles() = 0;
+
+ virtual ~BlobDB() {}
+
+ protected:
+ explicit BlobDB();
+};
+
+// Destroy the content of the database.
+Status DestroyBlobDB(const std::string& dbname, const Options& options,
+ const BlobDBOptions& bdb_options);
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_gc_stats.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_gc_stats.h
new file mode 100644
index 00000000..1e6e4a25
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_gc_stats.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace blob_db {
+
+/**
+ * Statistics related to a single garbage collection pass (i.e. a single
+ * (sub)compaction).
+ */
+class BlobDBGarbageCollectionStats {
+ public:
+ uint64_t AllBlobs() const { return all_blobs_; }
+ uint64_t AllBytes() const { return all_bytes_; }
+ uint64_t RelocatedBlobs() const { return relocated_blobs_; }
+ uint64_t RelocatedBytes() const { return relocated_bytes_; }
+ uint64_t NewFiles() const { return new_files_; }
+ bool HasError() const { return error_; }
+
+ void AddBlob(uint64_t size) {
+ ++all_blobs_;
+ all_bytes_ += size;
+ }
+
+ void AddRelocatedBlob(uint64_t size) {
+ ++relocated_blobs_;
+ relocated_bytes_ += size;
+ }
+
+ void AddNewFile() { ++new_files_; }
+
+ void SetError() { error_ = true; }
+
+ private:
+ uint64_t all_blobs_ = 0;
+ uint64_t all_bytes_ = 0;
+ uint64_t relocated_blobs_ = 0;
+ uint64_t relocated_bytes_ = 0;
+ uint64_t new_files_ = 0;
+ bool error_ = false;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.cc
new file mode 100644
index 00000000..5f2ca249
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.cc
@@ -0,0 +1,2116 @@
+
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_db_impl.h"
+#include <algorithm>
+#include <cinttypes>
+#include <iomanip>
+#include <memory>
+#include <sstream>
+
+#include "db/blob_index.h"
+#include "db/db_impl/db_impl.h"
+#include "db/write_batch_internal.h"
+#include "env/composite_env_wrapper.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "file/random_access_file_reader.h"
+#include "file/sst_file_manager_impl.h"
+#include "file/writable_file_writer.h"
+#include "logging/logging.h"
+#include "monitoring/instrumented_mutex.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/utilities/stackable_db.h"
+#include "rocksdb/utilities/transaction.h"
+#include "table/block_based/block.h"
+#include "table/block_based/block_based_table_builder.h"
+#include "table/block_based/block_builder.h"
+#include "table/meta_blocks.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/crc32c.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+#include "util/stop_watch.h"
+#include "util/timer_queue.h"
+#include "utilities/blob_db/blob_compaction_filter.h"
+#include "utilities/blob_db/blob_db_iterator.h"
+#include "utilities/blob_db/blob_db_listener.h"
+
+namespace {
+int kBlockBasedTableVersionFormat = 2;
+} // end namespace
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+bool BlobFileComparator::operator()(
+ const std::shared_ptr<BlobFile>& lhs,
+ const std::shared_ptr<BlobFile>& rhs) const {
+ return lhs->BlobFileNumber() > rhs->BlobFileNumber();
+}
+
+bool BlobFileComparatorTTL::operator()(
+ const std::shared_ptr<BlobFile>& lhs,
+ const std::shared_ptr<BlobFile>& rhs) const {
+ assert(lhs->HasTTL() && rhs->HasTTL());
+ if (lhs->expiration_range_.first < rhs->expiration_range_.first) {
+ return true;
+ }
+ if (lhs->expiration_range_.first > rhs->expiration_range_.first) {
+ return false;
+ }
+ return lhs->BlobFileNumber() < rhs->BlobFileNumber();
+}
+
+BlobDBImpl::BlobDBImpl(const std::string& dbname,
+ const BlobDBOptions& blob_db_options,
+ const DBOptions& db_options,
+ const ColumnFamilyOptions& cf_options)
+ : BlobDB(),
+ dbname_(dbname),
+ db_impl_(nullptr),
+ env_(db_options.env),
+ bdb_options_(blob_db_options),
+ db_options_(db_options),
+ cf_options_(cf_options),
+ env_options_(db_options),
+ statistics_(db_options_.statistics.get()),
+ next_file_number_(1),
+ flush_sequence_(0),
+ closed_(true),
+ open_file_count_(0),
+ total_blob_size_(0),
+ live_sst_size_(0),
+ fifo_eviction_seq_(0),
+ evict_expiration_up_to_(0),
+ debug_level_(0) {
+ blob_dir_ = (bdb_options_.path_relative)
+ ? dbname + "/" + bdb_options_.blob_dir
+ : bdb_options_.blob_dir;
+ env_options_.bytes_per_sync = blob_db_options.bytes_per_sync;
+}
+
+BlobDBImpl::~BlobDBImpl() {
+ tqueue_.shutdown();
+ // CancelAllBackgroundWork(db_, true);
+ Status s __attribute__((__unused__)) = Close();
+ assert(s.ok());
+}
+
+Status BlobDBImpl::Close() {
+ if (closed_) {
+ return Status::OK();
+ }
+ closed_ = true;
+
+ // Close base DB before BlobDBImpl destructs to stop event listener and
+ // compaction filter call.
+ Status s = db_->Close();
+ // delete db_ anyway even if close failed.
+ delete db_;
+ // Reset pointers to avoid StackableDB delete the pointer again.
+ db_ = nullptr;
+ db_impl_ = nullptr;
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = SyncBlobFiles();
+ return s;
+}
+
+BlobDBOptions BlobDBImpl::GetBlobDBOptions() const { return bdb_options_; }
+
+Status BlobDBImpl::Open(std::vector<ColumnFamilyHandle*>* handles) {
+ assert(handles != nullptr);
+ assert(db_ == nullptr);
+
+ if (blob_dir_.empty()) {
+ return Status::NotSupported("No blob directory in options");
+ }
+
+ if (cf_options_.compaction_filter != nullptr ||
+ cf_options_.compaction_filter_factory != nullptr) {
+ return Status::NotSupported("Blob DB doesn't support compaction filter.");
+ }
+
+ if (bdb_options_.garbage_collection_cutoff < 0.0 ||
+ bdb_options_.garbage_collection_cutoff > 1.0) {
+ return Status::InvalidArgument(
+ "Garbage collection cutoff must be in the interval [0.0, 1.0]");
+ }
+
+ // Temporarily disable compactions in the base DB during open; save the user
+ // defined value beforehand so we can restore it once BlobDB is initialized.
+ // Note: this is only needed if garbage collection is enabled.
+ const bool disable_auto_compactions = cf_options_.disable_auto_compactions;
+
+ if (bdb_options_.enable_garbage_collection) {
+ cf_options_.disable_auto_compactions = true;
+ }
+
+ Status s;
+
+ // Create info log.
+ if (db_options_.info_log == nullptr) {
+ s = CreateLoggerFromOptions(dbname_, db_options_, &db_options_.info_log);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log, "Opening BlobDB...");
+
+ // Open blob directory.
+ s = env_->CreateDirIfMissing(blob_dir_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to create blob_dir %s, status: %s",
+ blob_dir_.c_str(), s.ToString().c_str());
+ }
+ s = env_->NewDirectory(blob_dir_, &dir_ent_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to open blob_dir %s, status: %s", blob_dir_.c_str(),
+ s.ToString().c_str());
+ return s;
+ }
+
+ // Open blob files.
+ s = OpenAllBlobFiles();
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Update options
+ if (bdb_options_.enable_garbage_collection) {
+ db_options_.listeners.push_back(std::make_shared<BlobDBListenerGC>(this));
+ cf_options_.compaction_filter_factory =
+ std::make_shared<BlobIndexCompactionFilterFactoryGC>(this, env_,
+ statistics_);
+ } else {
+ db_options_.listeners.push_back(std::make_shared<BlobDBListener>(this));
+ cf_options_.compaction_filter_factory =
+ std::make_shared<BlobIndexCompactionFilterFactory>(this, env_,
+ statistics_);
+ }
+
+ // Open base db.
+ ColumnFamilyDescriptor cf_descriptor(kDefaultColumnFamilyName, cf_options_);
+ s = DB::Open(db_options_, dbname_, {cf_descriptor}, handles, &db_);
+ if (!s.ok()) {
+ return s;
+ }
+ db_impl_ = static_cast_with_check<DBImpl, DB>(db_->GetRootDB());
+
+ // Initialize SST file <-> oldest blob file mapping if garbage collection
+ // is enabled.
+ if (bdb_options_.enable_garbage_collection) {
+ std::vector<LiveFileMetaData> live_files;
+ db_->GetLiveFilesMetaData(&live_files);
+
+ InitializeBlobFileToSstMapping(live_files);
+
+ MarkUnreferencedBlobFilesObsoleteDuringOpen();
+
+ if (!disable_auto_compactions) {
+ s = db_->EnableAutoCompaction(*handles);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Failed to enable automatic compactions during open, status: %s",
+ s.ToString().c_str());
+ return s;
+ }
+ }
+ }
+
+ // Add trash files in blob dir to file delete scheduler.
+ SstFileManagerImpl* sfm = static_cast<SstFileManagerImpl*>(
+ db_impl_->immutable_db_options().sst_file_manager.get());
+ DeleteScheduler::CleanupDirectory(env_, sfm, blob_dir_);
+
+ UpdateLiveSSTSize();
+
+ // Start background jobs.
+ if (!bdb_options_.disable_background_tasks) {
+ StartBackgroundTasks();
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log, "BlobDB pointer %p", this);
+ bdb_options_.Dump(db_options_.info_log.get());
+ closed_ = false;
+ return s;
+}
+
+void BlobDBImpl::StartBackgroundTasks() {
+ // store a call to a member function and object
+ tqueue_.add(
+ kReclaimOpenFilesPeriodMillisecs,
+ std::bind(&BlobDBImpl::ReclaimOpenFiles, this, std::placeholders::_1));
+ tqueue_.add(
+ kDeleteObsoleteFilesPeriodMillisecs,
+ std::bind(&BlobDBImpl::DeleteObsoleteFiles, this, std::placeholders::_1));
+ tqueue_.add(kSanityCheckPeriodMillisecs,
+ std::bind(&BlobDBImpl::SanityCheck, this, std::placeholders::_1));
+ tqueue_.add(
+ kEvictExpiredFilesPeriodMillisecs,
+ std::bind(&BlobDBImpl::EvictExpiredFiles, this, std::placeholders::_1));
+}
+
+Status BlobDBImpl::GetAllBlobFiles(std::set<uint64_t>* file_numbers) {
+ assert(file_numbers != nullptr);
+ std::vector<std::string> all_files;
+ Status s = env_->GetChildren(blob_dir_, &all_files);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to get list of blob files, status: %s",
+ s.ToString().c_str());
+ return s;
+ }
+
+ for (const auto& file_name : all_files) {
+ uint64_t file_number;
+ FileType type;
+ bool success = ParseFileName(file_name, &file_number, &type);
+ if (success && type == kBlobFile) {
+ file_numbers->insert(file_number);
+ } else {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Skipping file in blob directory: %s", file_name.c_str());
+ }
+ }
+
+ return s;
+}
+
+Status BlobDBImpl::OpenAllBlobFiles() {
+ std::set<uint64_t> file_numbers;
+ Status s = GetAllBlobFiles(&file_numbers);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (!file_numbers.empty()) {
+ next_file_number_.store(*file_numbers.rbegin() + 1);
+ }
+
+ std::ostringstream blob_file_oss;
+ std::ostringstream live_imm_oss;
+ std::ostringstream obsolete_file_oss;
+
+ for (auto& file_number : file_numbers) {
+ std::shared_ptr<BlobFile> blob_file = std::make_shared<BlobFile>(
+ this, blob_dir_, file_number, db_options_.info_log.get());
+ blob_file->MarkImmutable(/* sequence */ 0);
+
+ // Read file header and footer
+ Status read_metadata_status = blob_file->ReadMetadata(env_, env_options_);
+ if (read_metadata_status.IsCorruption()) {
+ // Remove incomplete file.
+ if (!obsolete_files_.empty()) {
+ obsolete_file_oss << ", ";
+ }
+ obsolete_file_oss << file_number;
+
+ ObsoleteBlobFile(blob_file, 0 /*obsolete_seq*/, false /*update_size*/);
+ continue;
+ } else if (!read_metadata_status.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Unable to read metadata of blob file %" PRIu64
+ ", status: '%s'",
+ file_number, read_metadata_status.ToString().c_str());
+ return read_metadata_status;
+ }
+
+ total_blob_size_ += blob_file->GetFileSize();
+
+ if (!blob_files_.empty()) {
+ blob_file_oss << ", ";
+ }
+ blob_file_oss << file_number;
+
+ blob_files_[file_number] = blob_file;
+
+ if (!blob_file->HasTTL()) {
+ if (!live_imm_non_ttl_blob_files_.empty()) {
+ live_imm_oss << ", ";
+ }
+ live_imm_oss << file_number;
+
+ live_imm_non_ttl_blob_files_[file_number] = blob_file;
+ }
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Found %" ROCKSDB_PRIszt " blob files: %s", blob_files_.size(),
+ blob_file_oss.str().c_str());
+ ROCKS_LOG_INFO(
+ db_options_.info_log, "Found %" ROCKSDB_PRIszt " non-TTL blob files: %s",
+ live_imm_non_ttl_blob_files_.size(), live_imm_oss.str().c_str());
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Found %" ROCKSDB_PRIszt
+ " incomplete or corrupted blob files: %s",
+ obsolete_files_.size(), obsolete_file_oss.str().c_str());
+ return s;
+}
+
+template <typename Linker>
+void BlobDBImpl::LinkSstToBlobFileImpl(uint64_t sst_file_number,
+ uint64_t blob_file_number,
+ Linker linker) {
+ assert(bdb_options_.enable_garbage_collection);
+ assert(blob_file_number != kInvalidBlobFileNumber);
+
+ auto it = blob_files_.find(blob_file_number);
+ if (it == blob_files_.end()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Blob file %" PRIu64
+ " not found while trying to link "
+ "SST file %" PRIu64,
+ blob_file_number, sst_file_number);
+ return;
+ }
+
+ BlobFile* const blob_file = it->second.get();
+ assert(blob_file);
+
+ linker(blob_file, sst_file_number);
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Blob file %" PRIu64 " linked to SST file %" PRIu64,
+ blob_file_number, sst_file_number);
+}
+
+void BlobDBImpl::LinkSstToBlobFile(uint64_t sst_file_number,
+ uint64_t blob_file_number) {
+ auto linker = [](BlobFile* blob_file, uint64_t sst_file) {
+ WriteLock file_lock(&blob_file->mutex_);
+ blob_file->LinkSstFile(sst_file);
+ };
+
+ LinkSstToBlobFileImpl(sst_file_number, blob_file_number, linker);
+}
+
+void BlobDBImpl::LinkSstToBlobFileNoLock(uint64_t sst_file_number,
+ uint64_t blob_file_number) {
+ auto linker = [](BlobFile* blob_file, uint64_t sst_file) {
+ blob_file->LinkSstFile(sst_file);
+ };
+
+ LinkSstToBlobFileImpl(sst_file_number, blob_file_number, linker);
+}
+
+void BlobDBImpl::UnlinkSstFromBlobFile(uint64_t sst_file_number,
+ uint64_t blob_file_number) {
+ assert(bdb_options_.enable_garbage_collection);
+ assert(blob_file_number != kInvalidBlobFileNumber);
+
+ auto it = blob_files_.find(blob_file_number);
+ if (it == blob_files_.end()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Blob file %" PRIu64
+ " not found while trying to unlink "
+ "SST file %" PRIu64,
+ blob_file_number, sst_file_number);
+ return;
+ }
+
+ BlobFile* const blob_file = it->second.get();
+ assert(blob_file);
+
+ {
+ WriteLock file_lock(&blob_file->mutex_);
+ blob_file->UnlinkSstFile(sst_file_number);
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Blob file %" PRIu64 " unlinked from SST file %" PRIu64,
+ blob_file_number, sst_file_number);
+}
+
+void BlobDBImpl::InitializeBlobFileToSstMapping(
+ const std::vector<LiveFileMetaData>& live_files) {
+ assert(bdb_options_.enable_garbage_collection);
+
+ for (const auto& live_file : live_files) {
+ const uint64_t sst_file_number = live_file.file_number;
+ const uint64_t blob_file_number = live_file.oldest_blob_file_number;
+
+ if (blob_file_number == kInvalidBlobFileNumber) {
+ continue;
+ }
+
+ LinkSstToBlobFileNoLock(sst_file_number, blob_file_number);
+ }
+}
+
+void BlobDBImpl::ProcessFlushJobInfo(const FlushJobInfo& info) {
+ assert(bdb_options_.enable_garbage_collection);
+
+ WriteLock lock(&mutex_);
+
+ if (info.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ LinkSstToBlobFile(info.file_number, info.oldest_blob_file_number);
+ }
+
+ assert(flush_sequence_ < info.largest_seqno);
+ flush_sequence_ = info.largest_seqno;
+
+ MarkUnreferencedBlobFilesObsolete();
+}
+
+void BlobDBImpl::ProcessCompactionJobInfo(const CompactionJobInfo& info) {
+ assert(bdb_options_.enable_garbage_collection);
+
+ if (!info.status.ok()) {
+ return;
+ }
+
+ // Note: the same SST file may appear in both the input and the output
+ // file list in case of a trivial move. We walk through the two lists
+ // below in a fashion that's similar to merge sort to detect this.
+
+ auto cmp = [](const CompactionFileInfo& lhs, const CompactionFileInfo& rhs) {
+ return lhs.file_number < rhs.file_number;
+ };
+
+ auto inputs = info.input_file_infos;
+ auto iit = inputs.begin();
+ const auto iit_end = inputs.end();
+
+ std::sort(iit, iit_end, cmp);
+
+ auto outputs = info.output_file_infos;
+ auto oit = outputs.begin();
+ const auto oit_end = outputs.end();
+
+ std::sort(oit, oit_end, cmp);
+
+ WriteLock lock(&mutex_);
+
+ while (iit != iit_end && oit != oit_end) {
+ const auto& input = *iit;
+ const auto& output = *oit;
+
+ if (input.file_number == output.file_number) {
+ ++iit;
+ ++oit;
+ } else if (input.file_number < output.file_number) {
+ if (input.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ UnlinkSstFromBlobFile(input.file_number, input.oldest_blob_file_number);
+ }
+
+ ++iit;
+ } else {
+ assert(output.file_number < input.file_number);
+
+ if (output.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ LinkSstToBlobFile(output.file_number, output.oldest_blob_file_number);
+ }
+
+ ++oit;
+ }
+ }
+
+ while (iit != iit_end) {
+ const auto& input = *iit;
+
+ if (input.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ UnlinkSstFromBlobFile(input.file_number, input.oldest_blob_file_number);
+ }
+
+ ++iit;
+ }
+
+ while (oit != oit_end) {
+ const auto& output = *oit;
+
+ if (output.oldest_blob_file_number != kInvalidBlobFileNumber) {
+ LinkSstToBlobFile(output.file_number, output.oldest_blob_file_number);
+ }
+
+ ++oit;
+ }
+
+ MarkUnreferencedBlobFilesObsolete();
+}
+
+bool BlobDBImpl::MarkBlobFileObsoleteIfNeeded(
+ const std::shared_ptr<BlobFile>& blob_file, SequenceNumber obsolete_seq) {
+ assert(blob_file);
+ assert(!blob_file->HasTTL());
+ assert(blob_file->Immutable());
+ assert(bdb_options_.enable_garbage_collection);
+
+ // Note: FIFO eviction could have marked this file obsolete already.
+ if (blob_file->Obsolete()) {
+ return true;
+ }
+
+ // We cannot mark this file (or any higher-numbered files for that matter)
+ // obsolete if it is referenced by any memtables or SSTs. We keep track of
+ // the SSTs explicitly. To account for memtables, we keep track of the highest
+ // sequence number received in flush notifications, and we do not mark the
+ // blob file obsolete if there are still unflushed memtables from before
+ // the time the blob file was closed.
+ if (blob_file->GetImmutableSequence() > flush_sequence_ ||
+ !blob_file->GetLinkedSstFiles().empty()) {
+ return false;
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Blob file %" PRIu64 " is no longer needed, marking obsolete",
+ blob_file->BlobFileNumber());
+
+ ObsoleteBlobFile(blob_file, obsolete_seq, /* update_size */ true);
+ return true;
+}
+
+template <class Functor>
+void BlobDBImpl::MarkUnreferencedBlobFilesObsoleteImpl(Functor mark_if_needed) {
+ assert(bdb_options_.enable_garbage_collection);
+
+ // Iterate through all live immutable non-TTL blob files, and mark them
+ // obsolete assuming no SST files or memtables rely on the blobs in them.
+ // Note: we need to stop as soon as we find a blob file that has any
+ // linked SSTs (or one potentially referenced by memtables).
+
+ uint64_t obsoleted_files = 0;
+
+ auto it = live_imm_non_ttl_blob_files_.begin();
+ while (it != live_imm_non_ttl_blob_files_.end()) {
+ const auto& blob_file = it->second;
+ assert(blob_file);
+ assert(blob_file->BlobFileNumber() == it->first);
+ assert(!blob_file->HasTTL());
+ assert(blob_file->Immutable());
+
+ // Small optimization: Obsolete() does an atomic read, so we can do
+ // this check without taking a lock on the blob file's mutex.
+ if (blob_file->Obsolete()) {
+ it = live_imm_non_ttl_blob_files_.erase(it);
+ continue;
+ }
+
+ if (!mark_if_needed(blob_file)) {
+ break;
+ }
+
+ it = live_imm_non_ttl_blob_files_.erase(it);
+
+ ++obsoleted_files;
+ }
+
+ if (obsoleted_files > 0) {
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "%" PRIu64 " blob file(s) marked obsolete by GC",
+ obsoleted_files);
+ RecordTick(statistics_, BLOB_DB_GC_NUM_FILES, obsoleted_files);
+ }
+}
+
+void BlobDBImpl::MarkUnreferencedBlobFilesObsolete() {
+ const SequenceNumber obsolete_seq = GetLatestSequenceNumber();
+
+ MarkUnreferencedBlobFilesObsoleteImpl(
+ [=](const std::shared_ptr<BlobFile>& blob_file) {
+ WriteLock file_lock(&blob_file->mutex_);
+ return MarkBlobFileObsoleteIfNeeded(blob_file, obsolete_seq);
+ });
+}
+
+void BlobDBImpl::MarkUnreferencedBlobFilesObsoleteDuringOpen() {
+ MarkUnreferencedBlobFilesObsoleteImpl(
+ [=](const std::shared_ptr<BlobFile>& blob_file) {
+ return MarkBlobFileObsoleteIfNeeded(blob_file, /* obsolete_seq */ 0);
+ });
+}
+
+void BlobDBImpl::CloseRandomAccessLocked(
+ const std::shared_ptr<BlobFile>& bfile) {
+ bfile->CloseRandomAccessLocked();
+ open_file_count_--;
+}
+
+Status BlobDBImpl::GetBlobFileReader(
+ const std::shared_ptr<BlobFile>& blob_file,
+ std::shared_ptr<RandomAccessFileReader>* reader) {
+ assert(reader != nullptr);
+ bool fresh_open = false;
+ Status s = blob_file->GetReader(env_, env_options_, reader, &fresh_open);
+ if (s.ok() && fresh_open) {
+ assert(*reader != nullptr);
+ open_file_count_++;
+ }
+ return s;
+}
+
+std::shared_ptr<BlobFile> BlobDBImpl::NewBlobFile(
+ bool has_ttl, const ExpirationRange& expiration_range,
+ const std::string& reason) {
+ assert(has_ttl == (expiration_range.first || expiration_range.second));
+
+ uint64_t file_num = next_file_number_++;
+
+ const uint32_t column_family_id =
+ static_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily())->GetID();
+ auto blob_file = std::make_shared<BlobFile>(
+ this, blob_dir_, file_num, db_options_.info_log.get(), column_family_id,
+ bdb_options_.compression, has_ttl, expiration_range);
+
+ ROCKS_LOG_DEBUG(db_options_.info_log, "New blob file created: %s reason='%s'",
+ blob_file->PathName().c_str(), reason.c_str());
+ LogFlush(db_options_.info_log);
+
+ return blob_file;
+}
+
+void BlobDBImpl::RegisterBlobFile(std::shared_ptr<BlobFile> blob_file) {
+ const uint64_t blob_file_number = blob_file->BlobFileNumber();
+
+ auto it = blob_files_.lower_bound(blob_file_number);
+ assert(it == blob_files_.end() || it->first != blob_file_number);
+
+ blob_files_.insert(it,
+ std::map<uint64_t, std::shared_ptr<BlobFile>>::value_type(
+ blob_file_number, std::move(blob_file)));
+}
+
+Status BlobDBImpl::CreateWriterLocked(const std::shared_ptr<BlobFile>& bfile) {
+ std::string fpath(bfile->PathName());
+ std::unique_ptr<WritableFile> wfile;
+
+ Status s = env_->ReopenWritableFile(fpath, &wfile, env_options_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to open blob file for write: %s status: '%s'"
+ " exists: '%s'",
+ fpath.c_str(), s.ToString().c_str(),
+ env_->FileExists(fpath).ToString().c_str());
+ return s;
+ }
+
+ std::unique_ptr<WritableFileWriter> fwriter;
+ fwriter.reset(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(wfile)), fpath, env_options_));
+
+ uint64_t boffset = bfile->GetFileSize();
+ if (debug_level_ >= 2 && boffset) {
+ ROCKS_LOG_DEBUG(db_options_.info_log,
+ "Open blob file: %s with offset: %" PRIu64, fpath.c_str(),
+ boffset);
+ }
+
+ Writer::ElemType et = Writer::kEtNone;
+ if (bfile->file_size_ == BlobLogHeader::kSize) {
+ et = Writer::kEtFileHdr;
+ } else if (bfile->file_size_ > BlobLogHeader::kSize) {
+ et = Writer::kEtRecord;
+ } else if (bfile->file_size_) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "Open blob file: %s with wrong size: %" PRIu64,
+ fpath.c_str(), boffset);
+ return Status::Corruption("Invalid blob file size");
+ }
+
+ bfile->log_writer_ = std::make_shared<Writer>(
+ std::move(fwriter), env_, statistics_, bfile->file_number_,
+ bdb_options_.bytes_per_sync, db_options_.use_fsync, boffset);
+ bfile->log_writer_->last_elem_type_ = et;
+
+ return s;
+}
+
+std::shared_ptr<BlobFile> BlobDBImpl::FindBlobFileLocked(
+ uint64_t expiration) const {
+ if (open_ttl_files_.empty()) {
+ return nullptr;
+ }
+
+ std::shared_ptr<BlobFile> tmp = std::make_shared<BlobFile>();
+ tmp->SetHasTTL(true);
+ tmp->expiration_range_ = std::make_pair(expiration, 0);
+ tmp->file_number_ = std::numeric_limits<uint64_t>::max();
+
+ auto citr = open_ttl_files_.equal_range(tmp);
+ if (citr.first == open_ttl_files_.end()) {
+ assert(citr.second == open_ttl_files_.end());
+
+ std::shared_ptr<BlobFile> check = *(open_ttl_files_.rbegin());
+ return (check->expiration_range_.second <= expiration) ? nullptr : check;
+ }
+
+ if (citr.first != citr.second) {
+ return *(citr.first);
+ }
+
+ auto finditr = citr.second;
+ if (finditr != open_ttl_files_.begin()) {
+ --finditr;
+ }
+
+ bool b2 = (*finditr)->expiration_range_.second <= expiration;
+ bool b1 = (*finditr)->expiration_range_.first > expiration;
+
+ return (b1 || b2) ? nullptr : (*finditr);
+}
+
+Status BlobDBImpl::CheckOrCreateWriterLocked(
+ const std::shared_ptr<BlobFile>& blob_file,
+ std::shared_ptr<Writer>* writer) {
+ assert(writer != nullptr);
+ *writer = blob_file->GetWriter();
+ if (*writer != nullptr) {
+ return Status::OK();
+ }
+ Status s = CreateWriterLocked(blob_file);
+ if (s.ok()) {
+ *writer = blob_file->GetWriter();
+ }
+ return s;
+}
+
+Status BlobDBImpl::CreateBlobFileAndWriter(
+ bool has_ttl, const ExpirationRange& expiration_range,
+ const std::string& reason, std::shared_ptr<BlobFile>* blob_file,
+ std::shared_ptr<Writer>* writer) {
+ assert(has_ttl == (expiration_range.first || expiration_range.second));
+ assert(blob_file);
+ assert(writer);
+
+ *blob_file = NewBlobFile(has_ttl, expiration_range, reason);
+ assert(*blob_file);
+
+ // file not visible, hence no lock
+ Status s = CheckOrCreateWriterLocked(*blob_file, writer);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to get writer for blob file: %s, error: %s",
+ (*blob_file)->PathName().c_str(), s.ToString().c_str());
+ return s;
+ }
+
+ assert(*writer);
+
+ s = (*writer)->WriteHeader((*blob_file)->header_);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to write header to new blob file: %s"
+ " status: '%s'",
+ (*blob_file)->PathName().c_str(), s.ToString().c_str());
+ return s;
+ }
+
+ (*blob_file)->SetFileSize(BlobLogHeader::kSize);
+ total_blob_size_ += BlobLogHeader::kSize;
+
+ return s;
+}
+
+Status BlobDBImpl::SelectBlobFile(std::shared_ptr<BlobFile>* blob_file) {
+ assert(blob_file);
+
+ {
+ ReadLock rl(&mutex_);
+
+ if (open_non_ttl_file_) {
+ assert(!open_non_ttl_file_->Immutable());
+ *blob_file = open_non_ttl_file_;
+ return Status::OK();
+ }
+ }
+
+ // Check again
+ WriteLock wl(&mutex_);
+
+ if (open_non_ttl_file_) {
+ assert(!open_non_ttl_file_->Immutable());
+ *blob_file = open_non_ttl_file_;
+ return Status::OK();
+ }
+
+ std::shared_ptr<Writer> writer;
+ const Status s = CreateBlobFileAndWriter(
+ /* has_ttl */ false, ExpirationRange(),
+ /* reason */ "SelectBlobFile", blob_file, &writer);
+ if (!s.ok()) {
+ return s;
+ }
+
+ RegisterBlobFile(*blob_file);
+ open_non_ttl_file_ = *blob_file;
+
+ return s;
+}
+
+Status BlobDBImpl::SelectBlobFileTTL(uint64_t expiration,
+ std::shared_ptr<BlobFile>* blob_file) {
+ assert(blob_file);
+ assert(expiration != kNoExpiration);
+
+ {
+ ReadLock rl(&mutex_);
+
+ *blob_file = FindBlobFileLocked(expiration);
+ if (*blob_file != nullptr) {
+ assert(!(*blob_file)->Immutable());
+ return Status::OK();
+ }
+ }
+
+ // Check again
+ WriteLock wl(&mutex_);
+
+ *blob_file = FindBlobFileLocked(expiration);
+ if (*blob_file != nullptr) {
+ assert(!(*blob_file)->Immutable());
+ return Status::OK();
+ }
+
+ const uint64_t exp_low =
+ (expiration / bdb_options_.ttl_range_secs) * bdb_options_.ttl_range_secs;
+ const uint64_t exp_high = exp_low + bdb_options_.ttl_range_secs;
+ const ExpirationRange expiration_range(exp_low, exp_high);
+
+ std::ostringstream oss;
+ oss << "SelectBlobFileTTL range: [" << exp_low << ',' << exp_high << ')';
+
+ std::shared_ptr<Writer> writer;
+ const Status s =
+ CreateBlobFileAndWriter(/* has_ttl */ true, expiration_range,
+ /* reason */ oss.str(), blob_file, &writer);
+ if (!s.ok()) {
+ return s;
+ }
+
+ RegisterBlobFile(*blob_file);
+ open_ttl_files_.insert(*blob_file);
+
+ return s;
+}
+
+class BlobDBImpl::BlobInserter : public WriteBatch::Handler {
+ private:
+ const WriteOptions& options_;
+ BlobDBImpl* blob_db_impl_;
+ uint32_t default_cf_id_;
+ WriteBatch batch_;
+
+ public:
+ BlobInserter(const WriteOptions& options, BlobDBImpl* blob_db_impl,
+ uint32_t default_cf_id)
+ : options_(options),
+ blob_db_impl_(blob_db_impl),
+ default_cf_id_(default_cf_id) {}
+
+ WriteBatch* batch() { return &batch_; }
+
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ if (column_family_id != default_cf_id_) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ Status s = blob_db_impl_->PutBlobValue(options_, key, value, kNoExpiration,
+ &batch_);
+ return s;
+ }
+
+ Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
+ if (column_family_id != default_cf_id_) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ Status s = WriteBatchInternal::Delete(&batch_, column_family_id, key);
+ return s;
+ }
+
+ virtual Status DeleteRange(uint32_t column_family_id, const Slice& begin_key,
+ const Slice& end_key) {
+ if (column_family_id != default_cf_id_) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ Status s = WriteBatchInternal::DeleteRange(&batch_, column_family_id,
+ begin_key, end_key);
+ return s;
+ }
+
+ Status SingleDeleteCF(uint32_t /*column_family_id*/,
+ const Slice& /*key*/) override {
+ return Status::NotSupported("Not supported operation in blob db.");
+ }
+
+ Status MergeCF(uint32_t /*column_family_id*/, const Slice& /*key*/,
+ const Slice& /*value*/) override {
+ return Status::NotSupported("Not supported operation in blob db.");
+ }
+
+ void LogData(const Slice& blob) override { batch_.PutLogData(blob); }
+};
+
+Status BlobDBImpl::Write(const WriteOptions& options, WriteBatch* updates) {
+ StopWatch write_sw(env_, statistics_, BLOB_DB_WRITE_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_WRITE);
+ uint32_t default_cf_id =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily())->GetID();
+ Status s;
+ BlobInserter blob_inserter(options, this, default_cf_id);
+ {
+ // Release write_mutex_ before DB write to avoid race condition with
+ // flush begin listener, which also require write_mutex_ to sync
+ // blob files.
+ MutexLock l(&write_mutex_);
+ s = updates->Iterate(&blob_inserter);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ return db_->Write(options, blob_inserter.batch());
+}
+
+Status BlobDBImpl::Put(const WriteOptions& options, const Slice& key,
+ const Slice& value) {
+ return PutUntil(options, key, value, kNoExpiration);
+}
+
+Status BlobDBImpl::PutWithTTL(const WriteOptions& options,
+ const Slice& key, const Slice& value,
+ uint64_t ttl) {
+ uint64_t now = EpochNow();
+ uint64_t expiration = kNoExpiration - now > ttl ? now + ttl : kNoExpiration;
+ return PutUntil(options, key, value, expiration);
+}
+
+Status BlobDBImpl::PutUntil(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t expiration) {
+ StopWatch write_sw(env_, statistics_, BLOB_DB_WRITE_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_PUT);
+ Status s;
+ WriteBatch batch;
+ {
+ // Release write_mutex_ before DB write to avoid race condition with
+ // flush begin listener, which also require write_mutex_ to sync
+ // blob files.
+ MutexLock l(&write_mutex_);
+ s = PutBlobValue(options, key, value, expiration, &batch);
+ }
+ if (s.ok()) {
+ s = db_->Write(options, &batch);
+ }
+ return s;
+}
+
+Status BlobDBImpl::PutBlobValue(const WriteOptions& /*options*/,
+ const Slice& key, const Slice& value,
+ uint64_t expiration, WriteBatch* batch) {
+ write_mutex_.AssertHeld();
+ Status s;
+ std::string index_entry;
+ uint32_t column_family_id =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily())->GetID();
+ if (value.size() < bdb_options_.min_blob_size) {
+ if (expiration == kNoExpiration) {
+ // Put as normal value
+ s = batch->Put(key, value);
+ RecordTick(statistics_, BLOB_DB_WRITE_INLINED);
+ } else {
+ // Inlined with TTL
+ BlobIndex::EncodeInlinedTTL(&index_entry, expiration, value);
+ s = WriteBatchInternal::PutBlobIndex(batch, column_family_id, key,
+ index_entry);
+ RecordTick(statistics_, BLOB_DB_WRITE_INLINED_TTL);
+ }
+ } else {
+ std::string compression_output;
+ Slice value_compressed = GetCompressedSlice(value, &compression_output);
+
+ std::string headerbuf;
+ Writer::ConstructBlobHeader(&headerbuf, key, value_compressed, expiration);
+
+ // Check DB size limit before selecting blob file to
+ // Since CheckSizeAndEvictBlobFiles() can close blob files, it needs to be
+ // done before calling SelectBlobFile().
+ s = CheckSizeAndEvictBlobFiles(headerbuf.size() + key.size() +
+ value_compressed.size());
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::shared_ptr<BlobFile> blob_file;
+ if (expiration != kNoExpiration) {
+ s = SelectBlobFileTTL(expiration, &blob_file);
+ } else {
+ s = SelectBlobFile(&blob_file);
+ }
+ if (s.ok()) {
+ assert(blob_file != nullptr);
+ assert(blob_file->GetCompressionType() == bdb_options_.compression);
+ s = AppendBlob(blob_file, headerbuf, key, value_compressed, expiration,
+ &index_entry);
+ }
+ if (s.ok()) {
+ if (expiration != kNoExpiration) {
+ WriteLock file_lock(&blob_file->mutex_);
+ blob_file->ExtendExpirationRange(expiration);
+ }
+ s = CloseBlobFileIfNeeded(blob_file);
+ }
+ if (s.ok()) {
+ s = WriteBatchInternal::PutBlobIndex(batch, column_family_id, key,
+ index_entry);
+ }
+ if (s.ok()) {
+ if (expiration == kNoExpiration) {
+ RecordTick(statistics_, BLOB_DB_WRITE_BLOB);
+ } else {
+ RecordTick(statistics_, BLOB_DB_WRITE_BLOB_TTL);
+ }
+ } else {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Failed to append blob to FILE: %s: KEY: %s VALSZ: %" ROCKSDB_PRIszt
+ " status: '%s' blob_file: '%s'",
+ blob_file->PathName().c_str(), key.ToString().c_str(), value.size(),
+ s.ToString().c_str(), blob_file->DumpState().c_str());
+ }
+ }
+
+ RecordTick(statistics_, BLOB_DB_NUM_KEYS_WRITTEN);
+ RecordTick(statistics_, BLOB_DB_BYTES_WRITTEN, key.size() + value.size());
+ RecordInHistogram(statistics_, BLOB_DB_KEY_SIZE, key.size());
+ RecordInHistogram(statistics_, BLOB_DB_VALUE_SIZE, value.size());
+
+ return s;
+}
+
+Slice BlobDBImpl::GetCompressedSlice(const Slice& raw,
+ std::string* compression_output) const {
+ if (bdb_options_.compression == kNoCompression) {
+ return raw;
+ }
+ StopWatch compression_sw(env_, statistics_, BLOB_DB_COMPRESSION_MICROS);
+ CompressionType type = bdb_options_.compression;
+ CompressionOptions opts;
+ CompressionContext context(type);
+ CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(), type,
+ 0 /* sample_for_compression */);
+ CompressBlock(raw, info, &type, kBlockBasedTableVersionFormat, false,
+ compression_output, nullptr, nullptr);
+ return *compression_output;
+}
+
+Status BlobDBImpl::CompactFiles(
+ const CompactionOptions& compact_options,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id, std::vector<std::string>* const output_file_names,
+ CompactionJobInfo* compaction_job_info) {
+ // Note: we need CompactionJobInfo to be able to track updates to the
+ // blob file <-> SST mappings, so we provide one if the user hasn't,
+ // assuming that GC is enabled.
+ CompactionJobInfo info{};
+ if (bdb_options_.enable_garbage_collection && !compaction_job_info) {
+ compaction_job_info = &info;
+ }
+
+ const Status s =
+ db_->CompactFiles(compact_options, input_file_names, output_level,
+ output_path_id, output_file_names, compaction_job_info);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (bdb_options_.enable_garbage_collection) {
+ assert(compaction_job_info);
+ ProcessCompactionJobInfo(*compaction_job_info);
+ }
+
+ return s;
+}
+
+void BlobDBImpl::GetCompactionContextCommon(
+ BlobCompactionContext* context) const {
+ assert(context);
+
+ context->next_file_number = next_file_number_.load();
+ context->current_blob_files.clear();
+ for (auto& p : blob_files_) {
+ context->current_blob_files.insert(p.first);
+ }
+ context->fifo_eviction_seq = fifo_eviction_seq_;
+ context->evict_expiration_up_to = evict_expiration_up_to_;
+}
+
+void BlobDBImpl::GetCompactionContext(BlobCompactionContext* context) {
+ assert(context);
+
+ ReadLock l(&mutex_);
+ GetCompactionContextCommon(context);
+}
+
+void BlobDBImpl::GetCompactionContext(BlobCompactionContext* context,
+ BlobCompactionContextGC* context_gc) {
+ assert(context);
+ assert(context_gc);
+
+ ReadLock l(&mutex_);
+ GetCompactionContextCommon(context);
+
+ context_gc->blob_db_impl = this;
+
+ if (!live_imm_non_ttl_blob_files_.empty()) {
+ auto it = live_imm_non_ttl_blob_files_.begin();
+ std::advance(it, bdb_options_.garbage_collection_cutoff *
+ live_imm_non_ttl_blob_files_.size());
+ context_gc->cutoff_file_number = it != live_imm_non_ttl_blob_files_.end()
+ ? it->first
+ : std::numeric_limits<uint64_t>::max();
+ }
+}
+
+void BlobDBImpl::UpdateLiveSSTSize() {
+ uint64_t live_sst_size = 0;
+ bool ok = GetIntProperty(DB::Properties::kLiveSstFilesSize, &live_sst_size);
+ if (ok) {
+ live_sst_size_.store(live_sst_size);
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Updated total SST file size: %" PRIu64 " bytes.",
+ live_sst_size);
+ } else {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Failed to update total SST file size after flush or compaction.");
+ }
+ {
+ // Trigger FIFO eviction if needed.
+ MutexLock l(&write_mutex_);
+ Status s = CheckSizeAndEvictBlobFiles(0, true /*force*/);
+ if (s.IsNoSpace()) {
+ ROCKS_LOG_WARN(db_options_.info_log,
+ "DB grow out-of-space after SST size updated. Current live"
+ " SST size: %" PRIu64
+ " , current blob files size: %" PRIu64 ".",
+ live_sst_size_.load(), total_blob_size_.load());
+ }
+ }
+}
+
+Status BlobDBImpl::CheckSizeAndEvictBlobFiles(uint64_t blob_size,
+ bool force_evict) {
+ write_mutex_.AssertHeld();
+
+ uint64_t live_sst_size = live_sst_size_.load();
+ if (bdb_options_.max_db_size == 0 ||
+ live_sst_size + total_blob_size_.load() + blob_size <=
+ bdb_options_.max_db_size) {
+ return Status::OK();
+ }
+
+ if (bdb_options_.is_fifo == false ||
+ (!force_evict && live_sst_size + blob_size > bdb_options_.max_db_size)) {
+ // FIFO eviction is disabled, or no space to insert new blob even we evict
+ // all blob files.
+ return Status::NoSpace(
+ "Write failed, as writing it would exceed max_db_size limit.");
+ }
+
+ std::vector<std::shared_ptr<BlobFile>> candidate_files;
+ CopyBlobFiles(&candidate_files);
+ std::sort(candidate_files.begin(), candidate_files.end(),
+ BlobFileComparator());
+ fifo_eviction_seq_ = GetLatestSequenceNumber();
+
+ WriteLock l(&mutex_);
+
+ while (!candidate_files.empty() &&
+ live_sst_size + total_blob_size_.load() + blob_size >
+ bdb_options_.max_db_size) {
+ std::shared_ptr<BlobFile> blob_file = candidate_files.back();
+ candidate_files.pop_back();
+ WriteLock file_lock(&blob_file->mutex_);
+ if (blob_file->Obsolete()) {
+ // File already obsoleted by someone else.
+ assert(blob_file->Immutable());
+ continue;
+ }
+ // FIFO eviction can evict open blob files.
+ if (!blob_file->Immutable()) {
+ Status s = CloseBlobFile(blob_file);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ assert(blob_file->Immutable());
+ auto expiration_range = blob_file->GetExpirationRange();
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Evict oldest blob file since DB out of space. Current "
+ "live SST file size: %" PRIu64 ", total blob size: %" PRIu64
+ ", max db size: %" PRIu64 ", evicted blob file #%" PRIu64
+ ".",
+ live_sst_size, total_blob_size_.load(),
+ bdb_options_.max_db_size, blob_file->BlobFileNumber());
+ ObsoleteBlobFile(blob_file, fifo_eviction_seq_, true /*update_size*/);
+ evict_expiration_up_to_ = expiration_range.first;
+ RecordTick(statistics_, BLOB_DB_FIFO_NUM_FILES_EVICTED);
+ RecordTick(statistics_, BLOB_DB_FIFO_NUM_KEYS_EVICTED,
+ blob_file->BlobCount());
+ RecordTick(statistics_, BLOB_DB_FIFO_BYTES_EVICTED,
+ blob_file->GetFileSize());
+ TEST_SYNC_POINT("BlobDBImpl::EvictOldestBlobFile:Evicted");
+ }
+ if (live_sst_size + total_blob_size_.load() + blob_size >
+ bdb_options_.max_db_size) {
+ return Status::NoSpace(
+ "Write failed, as writing it would exceed max_db_size limit.");
+ }
+ return Status::OK();
+}
+
+Status BlobDBImpl::AppendBlob(const std::shared_ptr<BlobFile>& bfile,
+ const std::string& headerbuf, const Slice& key,
+ const Slice& value, uint64_t expiration,
+ std::string* index_entry) {
+ Status s;
+ uint64_t blob_offset = 0;
+ uint64_t key_offset = 0;
+ {
+ WriteLock lockbfile_w(&bfile->mutex_);
+ std::shared_ptr<Writer> writer;
+ s = CheckOrCreateWriterLocked(bfile, &writer);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // write the blob to the blob log.
+ s = writer->EmitPhysicalRecord(headerbuf, key, value, &key_offset,
+ &blob_offset);
+ }
+
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Invalid status in AppendBlob: %s status: '%s'",
+ bfile->PathName().c_str(), s.ToString().c_str());
+ return s;
+ }
+
+ uint64_t size_put = headerbuf.size() + key.size() + value.size();
+ bfile->BlobRecordAdded(size_put);
+ total_blob_size_ += size_put;
+
+ if (expiration == kNoExpiration) {
+ BlobIndex::EncodeBlob(index_entry, bfile->BlobFileNumber(), blob_offset,
+ value.size(), bdb_options_.compression);
+ } else {
+ BlobIndex::EncodeBlobTTL(index_entry, expiration, bfile->BlobFileNumber(),
+ blob_offset, value.size(),
+ bdb_options_.compression);
+ }
+
+ return s;
+}
+
+std::vector<Status> BlobDBImpl::MultiGet(
+ const ReadOptions& read_options,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ StopWatch multiget_sw(env_, statistics_, BLOB_DB_MULTIGET_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_MULTIGET);
+ // Get a snapshot to avoid blob file get deleted between we
+ // fetch and index entry and reading from the file.
+ ReadOptions ro(read_options);
+ bool snapshot_created = SetSnapshotIfNeeded(&ro);
+
+ std::vector<Status> statuses;
+ statuses.reserve(keys.size());
+ values->clear();
+ values->reserve(keys.size());
+ PinnableSlice value;
+ for (size_t i = 0; i < keys.size(); i++) {
+ statuses.push_back(Get(ro, DefaultColumnFamily(), keys[i], &value));
+ values->push_back(value.ToString());
+ value.Reset();
+ }
+ if (snapshot_created) {
+ db_->ReleaseSnapshot(ro.snapshot);
+ }
+ return statuses;
+}
+
+bool BlobDBImpl::SetSnapshotIfNeeded(ReadOptions* read_options) {
+ assert(read_options != nullptr);
+ if (read_options->snapshot != nullptr) {
+ return false;
+ }
+ read_options->snapshot = db_->GetSnapshot();
+ return true;
+}
+
+Status BlobDBImpl::GetBlobValue(const Slice& key, const Slice& index_entry,
+ PinnableSlice* value, uint64_t* expiration) {
+ assert(value);
+
+ BlobIndex blob_index;
+ Status s = blob_index.DecodeFrom(index_entry);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (blob_index.HasTTL() && blob_index.expiration() <= EpochNow()) {
+ return Status::NotFound("Key expired");
+ }
+
+ if (expiration != nullptr) {
+ if (blob_index.HasTTL()) {
+ *expiration = blob_index.expiration();
+ } else {
+ *expiration = kNoExpiration;
+ }
+ }
+
+ if (blob_index.IsInlined()) {
+ // TODO(yiwu): If index_entry is a PinnableSlice, we can also pin the same
+ // memory buffer to avoid extra copy.
+ value->PinSelf(blob_index.value());
+ return Status::OK();
+ }
+
+ CompressionType compression_type = kNoCompression;
+ s = GetRawBlobFromFile(key, blob_index.file_number(), blob_index.offset(),
+ blob_index.size(), value, &compression_type);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (compression_type != kNoCompression) {
+ BlockContents contents;
+ auto cfh = static_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily());
+
+ {
+ StopWatch decompression_sw(env_, statistics_,
+ BLOB_DB_DECOMPRESSION_MICROS);
+ UncompressionContext context(compression_type);
+ UncompressionInfo info(context, UncompressionDict::GetEmptyDict(),
+ compression_type);
+ s = UncompressBlockContentsForCompressionType(
+ info, value->data(), value->size(), &contents,
+ kBlockBasedTableVersionFormat, *(cfh->cfd()->ioptions()));
+ }
+
+ if (!s.ok()) {
+ if (debug_level_ >= 2) {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Uncompression error during blob read from file: %" PRIu64
+ " blob_offset: %" PRIu64 " blob_size: %" PRIu64
+ " key: %s status: '%s'",
+ blob_index.file_number(), blob_index.offset(), blob_index.size(),
+ key.ToString(/* output_hex */ true).c_str(), s.ToString().c_str());
+ }
+
+ return Status::Corruption("Unable to uncompress blob.");
+ }
+
+ value->PinSelf(contents.data);
+ }
+
+ return Status::OK();
+}
+
+Status BlobDBImpl::GetRawBlobFromFile(const Slice& key, uint64_t file_number,
+ uint64_t offset, uint64_t size,
+ PinnableSlice* value,
+ CompressionType* compression_type) {
+ assert(value);
+ assert(compression_type);
+ assert(*compression_type == kNoCompression);
+
+ if (!size) {
+ value->PinSelf("");
+ return Status::OK();
+ }
+
+ // offset has to have certain min, as we will read CRC
+ // later from the Blob Header, which needs to be also a
+ // valid offset.
+ if (offset <
+ (BlobLogHeader::kSize + BlobLogRecord::kHeaderSize + key.size())) {
+ if (debug_level_ >= 2) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Invalid blob index file_number: %" PRIu64
+ " blob_offset: %" PRIu64 " blob_size: %" PRIu64
+ " key: %s",
+ file_number, offset, size,
+ key.ToString(/* output_hex */ true).c_str());
+ }
+
+ return Status::NotFound("Invalid blob offset");
+ }
+
+ std::shared_ptr<BlobFile> blob_file;
+
+ {
+ ReadLock rl(&mutex_);
+ auto it = blob_files_.find(file_number);
+
+ // file was deleted
+ if (it == blob_files_.end()) {
+ return Status::NotFound("Blob Not Found as blob file missing");
+ }
+
+ blob_file = it->second;
+ }
+
+ *compression_type = blob_file->GetCompressionType();
+
+ // takes locks when called
+ std::shared_ptr<RandomAccessFileReader> reader;
+ Status s = GetBlobFileReader(blob_file, &reader);
+ if (!s.ok()) {
+ return s;
+ }
+
+ assert(offset >= key.size() + sizeof(uint32_t));
+ const uint64_t record_offset = offset - key.size() - sizeof(uint32_t);
+ const uint64_t record_size = sizeof(uint32_t) + key.size() + size;
+
+ // Allocate the buffer. This is safe in C++11
+ std::string buffer_str(static_cast<size_t>(record_size), static_cast<char>(0));
+ char* buffer = &buffer_str[0];
+
+ // A partial blob record contain checksum, key and value.
+ Slice blob_record;
+
+ {
+ StopWatch read_sw(env_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
+ s = reader->Read(record_offset, static_cast<size_t>(record_size), &blob_record, buffer);
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, blob_record.size());
+ }
+
+ if (!s.ok()) {
+ ROCKS_LOG_DEBUG(
+ db_options_.info_log,
+ "Failed to read blob from blob file %" PRIu64 ", blob_offset: %" PRIu64
+ ", blob_size: %" PRIu64 ", key_size: %" ROCKSDB_PRIszt ", status: '%s'",
+ file_number, offset, size, key.size(), s.ToString().c_str());
+ return s;
+ }
+
+ if (blob_record.size() != record_size) {
+ ROCKS_LOG_DEBUG(
+ db_options_.info_log,
+ "Failed to read blob from blob file %" PRIu64 ", blob_offset: %" PRIu64
+ ", blob_size: %" PRIu64 ", key_size: %" ROCKSDB_PRIszt
+ ", read %" ROCKSDB_PRIszt " bytes, expected %" PRIu64 " bytes",
+ file_number, offset, size, key.size(), blob_record.size(), record_size);
+
+ return Status::Corruption("Failed to retrieve blob from blob index.");
+ }
+
+ Slice crc_slice(blob_record.data(), sizeof(uint32_t));
+ Slice blob_value(blob_record.data() + sizeof(uint32_t) + key.size(),
+ static_cast<size_t>(size));
+
+ uint32_t crc_exp = 0;
+ if (!GetFixed32(&crc_slice, &crc_exp)) {
+ ROCKS_LOG_DEBUG(
+ db_options_.info_log,
+ "Unable to decode CRC from blob file %" PRIu64 ", blob_offset: %" PRIu64
+ ", blob_size: %" PRIu64 ", key size: %" ROCKSDB_PRIszt ", status: '%s'",
+ file_number, offset, size, key.size(), s.ToString().c_str());
+ return Status::Corruption("Unable to decode checksum.");
+ }
+
+ uint32_t crc = crc32c::Value(blob_record.data() + sizeof(uint32_t),
+ blob_record.size() - sizeof(uint32_t));
+ crc = crc32c::Mask(crc); // Adjust for storage
+ if (crc != crc_exp) {
+ if (debug_level_ >= 2) {
+ ROCKS_LOG_ERROR(
+ db_options_.info_log,
+ "Blob crc mismatch file: %" PRIu64 " blob_offset: %" PRIu64
+ " blob_size: %" PRIu64 " key: %s status: '%s'",
+ file_number, offset, size,
+ key.ToString(/* output_hex */ true).c_str(), s.ToString().c_str());
+ }
+
+ return Status::Corruption("Corruption. Blob CRC mismatch");
+ }
+
+ value->PinSelf(blob_value);
+
+ return Status::OK();
+}
+
+Status BlobDBImpl::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) {
+ return Get(read_options, column_family, key, value, nullptr /*expiration*/);
+}
+
+Status BlobDBImpl::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value, uint64_t* expiration) {
+ StopWatch get_sw(env_, statistics_, BLOB_DB_GET_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_GET);
+ return GetImpl(read_options, column_family, key, value, expiration);
+}
+
+Status BlobDBImpl::GetImpl(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value, uint64_t* expiration) {
+ if (column_family->GetID() != DefaultColumnFamily()->GetID()) {
+ return Status::NotSupported(
+ "Blob DB doesn't support non-default column family.");
+ }
+ // Get a snapshot to avoid blob file get deleted between we
+ // fetch and index entry and reading from the file.
+ // TODO(yiwu): For Get() retry if file not found would be a simpler strategy.
+ ReadOptions ro(read_options);
+ bool snapshot_created = SetSnapshotIfNeeded(&ro);
+
+ PinnableSlice index_entry;
+ Status s;
+ bool is_blob_index = false;
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.value = &index_entry;
+ get_impl_options.is_blob_index = &is_blob_index;
+ s = db_impl_->GetImpl(ro, key, get_impl_options);
+ if (expiration != nullptr) {
+ *expiration = kNoExpiration;
+ }
+ RecordTick(statistics_, BLOB_DB_NUM_KEYS_READ);
+ if (s.ok()) {
+ if (is_blob_index) {
+ s = GetBlobValue(key, index_entry, value, expiration);
+ } else {
+ // The index entry is the value itself in this case.
+ value->PinSelf(index_entry);
+ }
+ RecordTick(statistics_, BLOB_DB_BYTES_READ, value->size());
+ }
+ if (snapshot_created) {
+ db_->ReleaseSnapshot(ro.snapshot);
+ }
+ return s;
+}
+
+std::pair<bool, int64_t> BlobDBImpl::SanityCheck(bool aborted) {
+ if (aborted) {
+ return std::make_pair(false, -1);
+ }
+
+ ReadLock rl(&mutex_);
+
+ ROCKS_LOG_INFO(db_options_.info_log, "Starting Sanity Check");
+ ROCKS_LOG_INFO(db_options_.info_log, "Number of files %" ROCKSDB_PRIszt,
+ blob_files_.size());
+ ROCKS_LOG_INFO(db_options_.info_log, "Number of open files %" ROCKSDB_PRIszt,
+ open_ttl_files_.size());
+
+ for (const auto& blob_file : open_ttl_files_) {
+ (void)blob_file;
+ assert(!blob_file->Immutable());
+ }
+
+ for (const auto& pair : live_imm_non_ttl_blob_files_) {
+ const auto& blob_file = pair.second;
+ (void)blob_file;
+ assert(!blob_file->HasTTL());
+ assert(blob_file->Immutable());
+ }
+
+ uint64_t now = EpochNow();
+
+ for (auto blob_file_pair : blob_files_) {
+ auto blob_file = blob_file_pair.second;
+ char buf[1000];
+ int pos = snprintf(buf, sizeof(buf),
+ "Blob file %" PRIu64 ", size %" PRIu64
+ ", blob count %" PRIu64 ", immutable %d",
+ blob_file->BlobFileNumber(), blob_file->GetFileSize(),
+ blob_file->BlobCount(), blob_file->Immutable());
+ if (blob_file->HasTTL()) {
+ ExpirationRange expiration_range;
+
+ {
+ ReadLock file_lock(&blob_file->mutex_);
+ expiration_range = blob_file->GetExpirationRange();
+ }
+
+ pos += snprintf(buf + pos, sizeof(buf) - pos,
+ ", expiration range (%" PRIu64 ", %" PRIu64 ")",
+ expiration_range.first, expiration_range.second);
+ if (!blob_file->Obsolete()) {
+ pos += snprintf(buf + pos, sizeof(buf) - pos,
+ ", expire in %" PRIu64 " seconds",
+ expiration_range.second - now);
+ }
+ }
+ if (blob_file->Obsolete()) {
+ pos += snprintf(buf + pos, sizeof(buf) - pos, ", obsolete at %" PRIu64,
+ blob_file->GetObsoleteSequence());
+ }
+ snprintf(buf + pos, sizeof(buf) - pos, ".");
+ ROCKS_LOG_INFO(db_options_.info_log, "%s", buf);
+ }
+
+ // reschedule
+ return std::make_pair(true, -1);
+}
+
+Status BlobDBImpl::CloseBlobFile(std::shared_ptr<BlobFile> bfile) {
+ assert(bfile);
+ assert(!bfile->Immutable());
+ assert(!bfile->Obsolete());
+
+ if (bfile->HasTTL() || bfile == open_non_ttl_file_) {
+ write_mutex_.AssertHeld();
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Closing blob file %" PRIu64 ". Path: %s",
+ bfile->BlobFileNumber(), bfile->PathName().c_str());
+
+ const SequenceNumber sequence = GetLatestSequenceNumber();
+
+ const Status s = bfile->WriteFooterAndCloseLocked(sequence);
+
+ if (s.ok()) {
+ total_blob_size_ += BlobLogFooter::kSize;
+ } else {
+ bfile->MarkImmutable(sequence);
+
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to close blob file %" PRIu64 "with error: %s",
+ bfile->BlobFileNumber(), s.ToString().c_str());
+ }
+
+ if (bfile->HasTTL()) {
+ size_t erased __attribute__((__unused__));
+ erased = open_ttl_files_.erase(bfile);
+ } else {
+ if (bfile == open_non_ttl_file_) {
+ open_non_ttl_file_ = nullptr;
+ }
+
+ const uint64_t blob_file_number = bfile->BlobFileNumber();
+ auto it = live_imm_non_ttl_blob_files_.lower_bound(blob_file_number);
+ assert(it == live_imm_non_ttl_blob_files_.end() ||
+ it->first != blob_file_number);
+ live_imm_non_ttl_blob_files_.insert(
+ it, std::map<uint64_t, std::shared_ptr<BlobFile>>::value_type(
+ blob_file_number, bfile));
+ }
+
+ return s;
+}
+
+Status BlobDBImpl::CloseBlobFileIfNeeded(std::shared_ptr<BlobFile>& bfile) {
+ write_mutex_.AssertHeld();
+
+ // atomic read
+ if (bfile->GetFileSize() < bdb_options_.blob_file_size) {
+ return Status::OK();
+ }
+
+ WriteLock lock(&mutex_);
+ WriteLock file_lock(&bfile->mutex_);
+
+ assert(!bfile->Obsolete() || bfile->Immutable());
+ if (bfile->Immutable()) {
+ return Status::OK();
+ }
+
+ return CloseBlobFile(bfile);
+}
+
+void BlobDBImpl::ObsoleteBlobFile(std::shared_ptr<BlobFile> blob_file,
+ SequenceNumber obsolete_seq,
+ bool update_size) {
+ assert(blob_file->Immutable());
+ assert(!blob_file->Obsolete());
+
+ // Should hold write lock of mutex_ or during DB open.
+ blob_file->MarkObsolete(obsolete_seq);
+ obsolete_files_.push_back(blob_file);
+ assert(total_blob_size_.load() >= blob_file->GetFileSize());
+ if (update_size) {
+ total_blob_size_ -= blob_file->GetFileSize();
+ }
+}
+
+bool BlobDBImpl::VisibleToActiveSnapshot(
+ const std::shared_ptr<BlobFile>& bfile) {
+ assert(bfile->Obsolete());
+
+ // We check whether the oldest snapshot is no less than the last sequence
+ // by the time the blob file become obsolete. If so, the blob file is not
+ // visible to all existing snapshots.
+ //
+ // If we keep track of the earliest sequence of the keys in the blob file,
+ // we could instead check if there's a snapshot falls in range
+ // [earliest_sequence, obsolete_sequence). But doing so will make the
+ // implementation more complicated.
+ SequenceNumber obsolete_sequence = bfile->GetObsoleteSequence();
+ SequenceNumber oldest_snapshot = kMaxSequenceNumber;
+ {
+ // Need to lock DBImpl mutex before access snapshot list.
+ InstrumentedMutexLock l(db_impl_->mutex());
+ auto& snapshots = db_impl_->snapshots();
+ if (!snapshots.empty()) {
+ oldest_snapshot = snapshots.oldest()->GetSequenceNumber();
+ }
+ }
+ bool visible = oldest_snapshot < obsolete_sequence;
+ if (visible) {
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Obsolete blob file %" PRIu64 " (obsolete at %" PRIu64
+ ") visible to oldest snapshot %" PRIu64 ".",
+ bfile->BlobFileNumber(), obsolete_sequence, oldest_snapshot);
+ }
+ return visible;
+}
+
+std::pair<bool, int64_t> BlobDBImpl::EvictExpiredFiles(bool aborted) {
+ if (aborted) {
+ return std::make_pair(false, -1);
+ }
+
+ TEST_SYNC_POINT("BlobDBImpl::EvictExpiredFiles:0");
+ TEST_SYNC_POINT("BlobDBImpl::EvictExpiredFiles:1");
+
+ std::vector<std::shared_ptr<BlobFile>> process_files;
+ uint64_t now = EpochNow();
+ {
+ ReadLock rl(&mutex_);
+ for (auto p : blob_files_) {
+ auto& blob_file = p.second;
+ ReadLock file_lock(&blob_file->mutex_);
+ if (blob_file->HasTTL() && !blob_file->Obsolete() &&
+ blob_file->GetExpirationRange().second <= now) {
+ process_files.push_back(blob_file);
+ }
+ }
+ }
+
+ TEST_SYNC_POINT("BlobDBImpl::EvictExpiredFiles:2");
+ TEST_SYNC_POINT("BlobDBImpl::EvictExpiredFiles:3");
+ TEST_SYNC_POINT_CALLBACK("BlobDBImpl::EvictExpiredFiles:cb", nullptr);
+
+ SequenceNumber seq = GetLatestSequenceNumber();
+ {
+ MutexLock l(&write_mutex_);
+ WriteLock lock(&mutex_);
+ for (auto& blob_file : process_files) {
+ WriteLock file_lock(&blob_file->mutex_);
+
+ // Need to double check if the file is obsolete.
+ if (blob_file->Obsolete()) {
+ assert(blob_file->Immutable());
+ continue;
+ }
+
+ if (!blob_file->Immutable()) {
+ CloseBlobFile(blob_file);
+ }
+
+ assert(blob_file->Immutable());
+
+ ObsoleteBlobFile(blob_file, seq, true /*update_size*/);
+ }
+ }
+
+ return std::make_pair(true, -1);
+}
+
+Status BlobDBImpl::SyncBlobFiles() {
+ MutexLock l(&write_mutex_);
+
+ std::vector<std::shared_ptr<BlobFile>> process_files;
+ {
+ ReadLock rl(&mutex_);
+ for (auto fitr : open_ttl_files_) {
+ process_files.push_back(fitr);
+ }
+ if (open_non_ttl_file_ != nullptr) {
+ process_files.push_back(open_non_ttl_file_);
+ }
+ }
+
+ Status s;
+ for (auto& blob_file : process_files) {
+ s = blob_file->Fsync();
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to sync blob file %" PRIu64 ", status: %s",
+ blob_file->BlobFileNumber(), s.ToString().c_str());
+ return s;
+ }
+ }
+
+ s = dir_ent_->Fsync();
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "Failed to sync blob directory, status: %s",
+ s.ToString().c_str());
+ }
+ return s;
+}
+
+std::pair<bool, int64_t> BlobDBImpl::ReclaimOpenFiles(bool aborted) {
+ if (aborted) return std::make_pair(false, -1);
+
+ if (open_file_count_.load() < kOpenFilesTrigger) {
+ return std::make_pair(true, -1);
+ }
+
+ // in the future, we should sort by last_access_
+ // instead of closing every file
+ ReadLock rl(&mutex_);
+ for (auto const& ent : blob_files_) {
+ auto bfile = ent.second;
+ if (bfile->last_access_.load() == -1) continue;
+
+ WriteLock lockbfile_w(&bfile->mutex_);
+ CloseRandomAccessLocked(bfile);
+ }
+
+ return std::make_pair(true, -1);
+}
+
+std::pair<bool, int64_t> BlobDBImpl::DeleteObsoleteFiles(bool aborted) {
+ if (aborted) {
+ return std::make_pair(false, -1);
+ }
+
+ MutexLock delete_file_lock(&delete_file_mutex_);
+ if (disable_file_deletions_ > 0) {
+ return std::make_pair(true, -1);
+ }
+
+ std::list<std::shared_ptr<BlobFile>> tobsolete;
+ {
+ WriteLock wl(&mutex_);
+ if (obsolete_files_.empty()) {
+ return std::make_pair(true, -1);
+ }
+ tobsolete.swap(obsolete_files_);
+ }
+
+ bool file_deleted = false;
+ for (auto iter = tobsolete.begin(); iter != tobsolete.end();) {
+ auto bfile = *iter;
+ {
+ ReadLock lockbfile_r(&bfile->mutex_);
+ if (VisibleToActiveSnapshot(bfile)) {
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Could not delete file due to snapshot failure %s",
+ bfile->PathName().c_str());
+ ++iter;
+ continue;
+ }
+ }
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Will delete file due to snapshot success %s",
+ bfile->PathName().c_str());
+
+ {
+ WriteLock wl(&mutex_);
+ blob_files_.erase(bfile->BlobFileNumber());
+ }
+
+ Status s = DeleteDBFile(&(db_impl_->immutable_db_options()),
+ bfile->PathName(), blob_dir_, true,
+ /*force_fg=*/false);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log,
+ "File failed to be deleted as obsolete %s",
+ bfile->PathName().c_str());
+ ++iter;
+ continue;
+ }
+
+ file_deleted = true;
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "File deleted as obsolete from blob dir %s",
+ bfile->PathName().c_str());
+
+ iter = tobsolete.erase(iter);
+ }
+
+ // directory change. Fsync
+ if (file_deleted) {
+ Status s = dir_ent_->Fsync();
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(db_options_.info_log, "Failed to sync dir %s: %s",
+ blob_dir_.c_str(), s.ToString().c_str());
+ }
+ }
+
+ // put files back into obsolete if for some reason, delete failed
+ if (!tobsolete.empty()) {
+ WriteLock wl(&mutex_);
+ for (auto bfile : tobsolete) {
+ blob_files_.insert(std::make_pair(bfile->BlobFileNumber(), bfile));
+ obsolete_files_.push_front(bfile);
+ }
+ }
+
+ return std::make_pair(!aborted, -1);
+}
+
+void BlobDBImpl::CopyBlobFiles(
+ std::vector<std::shared_ptr<BlobFile>>* bfiles_copy) {
+ ReadLock rl(&mutex_);
+ for (auto const& p : blob_files_) {
+ bfiles_copy->push_back(p.second);
+ }
+}
+
+Iterator* BlobDBImpl::NewIterator(const ReadOptions& read_options) {
+ auto* cfd =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily())->cfd();
+ // Get a snapshot to avoid blob file get deleted between we
+ // fetch and index entry and reading from the file.
+ ManagedSnapshot* own_snapshot = nullptr;
+ const Snapshot* snapshot = read_options.snapshot;
+ if (snapshot == nullptr) {
+ own_snapshot = new ManagedSnapshot(db_);
+ snapshot = own_snapshot->snapshot();
+ }
+ auto* iter = db_impl_->NewIteratorImpl(
+ read_options, cfd, snapshot->GetSequenceNumber(),
+ nullptr /*read_callback*/, true /*allow_blob*/);
+ return new BlobDBIterator(own_snapshot, iter, this, env_, statistics_);
+}
+
+Status DestroyBlobDB(const std::string& dbname, const Options& options,
+ const BlobDBOptions& bdb_options) {
+ const ImmutableDBOptions soptions(SanitizeOptions(dbname, options));
+ Env* env = soptions.env;
+
+ Status status;
+ std::string blobdir;
+ blobdir = (bdb_options.path_relative) ? dbname + "/" + bdb_options.blob_dir
+ : bdb_options.blob_dir;
+
+ std::vector<std::string> filenames;
+ env->GetChildren(blobdir, &filenames);
+
+ for (const auto& f : filenames) {
+ uint64_t number;
+ FileType type;
+ if (ParseFileName(f, &number, &type) && type == kBlobFile) {
+ Status del = DeleteDBFile(&soptions, blobdir + "/" + f, blobdir, true,
+ /*force_fg=*/false);
+ if (status.ok() && !del.ok()) {
+ status = del;
+ }
+ }
+ }
+ env->DeleteDir(blobdir);
+
+ Status destroy = DestroyDB(dbname, options);
+ if (status.ok() && !destroy.ok()) {
+ status = destroy;
+ }
+
+ return status;
+}
+
+#ifndef NDEBUG
+Status BlobDBImpl::TEST_GetBlobValue(const Slice& key, const Slice& index_entry,
+ PinnableSlice* value) {
+ return GetBlobValue(key, index_entry, value);
+}
+
+void BlobDBImpl::TEST_AddDummyBlobFile(uint64_t blob_file_number,
+ SequenceNumber immutable_sequence) {
+ auto blob_file = std::make_shared<BlobFile>(this, blob_dir_, blob_file_number,
+ db_options_.info_log.get());
+ blob_file->MarkImmutable(immutable_sequence);
+
+ blob_files_[blob_file_number] = blob_file;
+ live_imm_non_ttl_blob_files_[blob_file_number] = blob_file;
+}
+
+std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetBlobFiles() const {
+ ReadLock l(&mutex_);
+ std::vector<std::shared_ptr<BlobFile>> blob_files;
+ for (auto& p : blob_files_) {
+ blob_files.emplace_back(p.second);
+ }
+ return blob_files;
+}
+
+std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetLiveImmNonTTLFiles()
+ const {
+ ReadLock l(&mutex_);
+ std::vector<std::shared_ptr<BlobFile>> live_imm_non_ttl_files;
+ for (const auto& pair : live_imm_non_ttl_blob_files_) {
+ live_imm_non_ttl_files.emplace_back(pair.second);
+ }
+ return live_imm_non_ttl_files;
+}
+
+std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetObsoleteFiles()
+ const {
+ ReadLock l(&mutex_);
+ std::vector<std::shared_ptr<BlobFile>> obsolete_files;
+ for (auto& bfile : obsolete_files_) {
+ obsolete_files.emplace_back(bfile);
+ }
+ return obsolete_files;
+}
+
+void BlobDBImpl::TEST_DeleteObsoleteFiles() {
+ DeleteObsoleteFiles(false /*abort*/);
+}
+
+Status BlobDBImpl::TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile) {
+ MutexLock l(&write_mutex_);
+ WriteLock lock(&mutex_);
+ WriteLock file_lock(&bfile->mutex_);
+
+ return CloseBlobFile(bfile);
+}
+
+void BlobDBImpl::TEST_ObsoleteBlobFile(std::shared_ptr<BlobFile>& blob_file,
+ SequenceNumber obsolete_seq,
+ bool update_size) {
+ return ObsoleteBlobFile(blob_file, obsolete_seq, update_size);
+}
+
+void BlobDBImpl::TEST_EvictExpiredFiles() {
+ EvictExpiredFiles(false /*abort*/);
+}
+
+uint64_t BlobDBImpl::TEST_live_sst_size() { return live_sst_size_.load(); }
+
+void BlobDBImpl::TEST_InitializeBlobFileToSstMapping(
+ const std::vector<LiveFileMetaData>& live_files) {
+ InitializeBlobFileToSstMapping(live_files);
+}
+
+void BlobDBImpl::TEST_ProcessFlushJobInfo(const FlushJobInfo& info) {
+ ProcessFlushJobInfo(info);
+}
+
+void BlobDBImpl::TEST_ProcessCompactionJobInfo(const CompactionJobInfo& info) {
+ ProcessCompactionJobInfo(info);
+}
+
+#endif // !NDEBUG
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.h
new file mode 100644
index 00000000..c1e649cc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl.h
@@ -0,0 +1,495 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <atomic>
+#include <condition_variable>
+#include <limits>
+#include <list>
+#include <memory>
+#include <set>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "db/db_iter.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/db.h"
+#include "rocksdb/listener.h"
+#include "rocksdb/options.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/wal_filter.h"
+#include "util/mutexlock.h"
+#include "util/timer_queue.h"
+#include "utilities/blob_db/blob_db.h"
+#include "utilities/blob_db/blob_file.h"
+#include "utilities/blob_db/blob_log_format.h"
+#include "utilities/blob_db/blob_log_reader.h"
+#include "utilities/blob_db/blob_log_writer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBImpl;
+class ColumnFamilyHandle;
+class ColumnFamilyData;
+struct FlushJobInfo;
+
+namespace blob_db {
+
+struct BlobCompactionContext;
+struct BlobCompactionContextGC;
+class BlobDBImpl;
+class BlobFile;
+
+// Comparator to sort "TTL" aware Blob files based on the lower value of
+// TTL range.
+struct BlobFileComparatorTTL {
+ bool operator()(const std::shared_ptr<BlobFile>& lhs,
+ const std::shared_ptr<BlobFile>& rhs) const;
+};
+
+struct BlobFileComparator {
+ bool operator()(const std::shared_ptr<BlobFile>& lhs,
+ const std::shared_ptr<BlobFile>& rhs) const;
+};
+
+/**
+ * The implementation class for BlobDB. It manages the blob logs, which
+ * are sequentially written files. Blob logs can be of the TTL or non-TTL
+ * varieties; the former are cleaned up when they expire, while the latter
+ * are (optionally) garbage collected.
+ */
+class BlobDBImpl : public BlobDB {
+ friend class BlobFile;
+ friend class BlobDBIterator;
+ friend class BlobDBListener;
+ friend class BlobDBListenerGC;
+ friend class BlobIndexCompactionFilterGC;
+
+ public:
+ // deletions check period
+ static constexpr uint32_t kDeleteCheckPeriodMillisecs = 2 * 1000;
+
+ // sanity check task
+ static constexpr uint32_t kSanityCheckPeriodMillisecs = 20 * 60 * 1000;
+
+ // how many random access open files can we tolerate
+ static constexpr uint32_t kOpenFilesTrigger = 100;
+
+ // how often to schedule reclaim open files.
+ static constexpr uint32_t kReclaimOpenFilesPeriodMillisecs = 1 * 1000;
+
+ // how often to schedule delete obs files periods
+ static constexpr uint32_t kDeleteObsoleteFilesPeriodMillisecs = 10 * 1000;
+
+ // how often to schedule expired files eviction.
+ static constexpr uint32_t kEvictExpiredFilesPeriodMillisecs = 10 * 1000;
+
+ // when should oldest file be evicted:
+ // on reaching 90% of blob_dir_size
+ static constexpr double kEvictOldestFileAtSize = 0.9;
+
+ using BlobDB::Put;
+ Status Put(const WriteOptions& options, const Slice& key,
+ const Slice& value) override;
+
+ using BlobDB::Get;
+ Status Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value) override;
+
+ Status Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value,
+ uint64_t* expiration) override;
+
+ using BlobDB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& read_options) override;
+
+ using BlobDB::NewIterators;
+ virtual Status NewIterators(
+ const ReadOptions& /*read_options*/,
+ const std::vector<ColumnFamilyHandle*>& /*column_families*/,
+ std::vector<Iterator*>* /*iterators*/) override {
+ return Status::NotSupported("Not implemented");
+ }
+
+ using BlobDB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& read_options,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override;
+
+ virtual Status Close() override;
+
+ using BlobDB::PutWithTTL;
+ Status PutWithTTL(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t ttl) override;
+
+ using BlobDB::PutUntil;
+ Status PutUntil(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t expiration) override;
+
+ using BlobDB::CompactFiles;
+ Status CompactFiles(
+ const CompactionOptions& compact_options,
+ const std::vector<std::string>& input_file_names, const int output_level,
+ const int output_path_id = -1,
+ std::vector<std::string>* const output_file_names = nullptr,
+ CompactionJobInfo* compaction_job_info = nullptr) override;
+
+ BlobDBOptions GetBlobDBOptions() const override;
+
+ BlobDBImpl(const std::string& dbname, const BlobDBOptions& bdb_options,
+ const DBOptions& db_options,
+ const ColumnFamilyOptions& cf_options);
+
+ virtual Status DisableFileDeletions() override;
+
+ virtual Status EnableFileDeletions(bool force) override;
+
+ virtual Status GetLiveFiles(std::vector<std::string>&,
+ uint64_t* manifest_file_size,
+ bool flush_memtable = true) override;
+ virtual void GetLiveFilesMetaData(std::vector<LiveFileMetaData>*) override;
+
+ ~BlobDBImpl();
+
+ Status Open(std::vector<ColumnFamilyHandle*>* handles);
+
+ Status SyncBlobFiles() override;
+
+ // Common part of the two GetCompactionContext methods below.
+ // REQUIRES: read lock on mutex_
+ void GetCompactionContextCommon(BlobCompactionContext* context) const;
+
+ void GetCompactionContext(BlobCompactionContext* context);
+ void GetCompactionContext(BlobCompactionContext* context,
+ BlobCompactionContextGC* context_gc);
+
+#ifndef NDEBUG
+ Status TEST_GetBlobValue(const Slice& key, const Slice& index_entry,
+ PinnableSlice* value);
+
+ void TEST_AddDummyBlobFile(uint64_t blob_file_number,
+ SequenceNumber immutable_sequence);
+
+ std::vector<std::shared_ptr<BlobFile>> TEST_GetBlobFiles() const;
+
+ std::vector<std::shared_ptr<BlobFile>> TEST_GetLiveImmNonTTLFiles() const;
+
+ std::vector<std::shared_ptr<BlobFile>> TEST_GetObsoleteFiles() const;
+
+ Status TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile);
+
+ void TEST_ObsoleteBlobFile(std::shared_ptr<BlobFile>& blob_file,
+ SequenceNumber obsolete_seq = 0,
+ bool update_size = true);
+
+ void TEST_EvictExpiredFiles();
+
+ void TEST_DeleteObsoleteFiles();
+
+ uint64_t TEST_live_sst_size();
+
+ const std::string& TEST_blob_dir() const { return blob_dir_; }
+
+ void TEST_InitializeBlobFileToSstMapping(
+ const std::vector<LiveFileMetaData>& live_files);
+
+ void TEST_ProcessFlushJobInfo(const FlushJobInfo& info);
+
+ void TEST_ProcessCompactionJobInfo(const CompactionJobInfo& info);
+
+#endif // !NDEBUG
+
+ private:
+ class BlobInserter;
+
+ // Create a snapshot if there isn't one in read options.
+ // Return true if a snapshot is created.
+ bool SetSnapshotIfNeeded(ReadOptions* read_options);
+
+ Status GetImpl(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value, uint64_t* expiration = nullptr);
+
+ Status GetBlobValue(const Slice& key, const Slice& index_entry,
+ PinnableSlice* value, uint64_t* expiration = nullptr);
+
+ Status GetRawBlobFromFile(const Slice& key, uint64_t file_number,
+ uint64_t offset, uint64_t size,
+ PinnableSlice* value,
+ CompressionType* compression_type);
+
+ Slice GetCompressedSlice(const Slice& raw,
+ std::string* compression_output) const;
+
+ // Close a file by appending a footer, and removes file from open files list.
+ // REQUIRES: lock held on write_mutex_, write lock held on both the db mutex_
+ // and the blob file's mutex_. If called on a blob file which is visible only
+ // to a single thread (like in the case of new files written during GC), the
+ // locks on write_mutex_ and the blob file's mutex_ can be avoided.
+ Status CloseBlobFile(std::shared_ptr<BlobFile> bfile);
+
+ // Close a file if its size exceeds blob_file_size
+ // REQUIRES: lock held on write_mutex_.
+ Status CloseBlobFileIfNeeded(std::shared_ptr<BlobFile>& bfile);
+
+ // Mark file as obsolete and move the file to obsolete file list.
+ //
+ // REQUIRED: hold write lock of mutex_ or during DB open.
+ void ObsoleteBlobFile(std::shared_ptr<BlobFile> blob_file,
+ SequenceNumber obsolete_seq, bool update_size);
+
+ Status PutBlobValue(const WriteOptions& options, const Slice& key,
+ const Slice& value, uint64_t expiration,
+ WriteBatch* batch);
+
+ Status AppendBlob(const std::shared_ptr<BlobFile>& bfile,
+ const std::string& headerbuf, const Slice& key,
+ const Slice& value, uint64_t expiration,
+ std::string* index_entry);
+
+ // Create a new blob file and associated writer.
+ Status CreateBlobFileAndWriter(bool has_ttl,
+ const ExpirationRange& expiration_range,
+ const std::string& reason,
+ std::shared_ptr<BlobFile>* blob_file,
+ std::shared_ptr<Writer>* writer);
+
+ // Get the open non-TTL blob log file, or create a new one if no such file
+ // exists.
+ Status SelectBlobFile(std::shared_ptr<BlobFile>* blob_file);
+
+ // Get the open TTL blob log file for a certain expiration, or create a new
+ // one if no such file exists.
+ Status SelectBlobFileTTL(uint64_t expiration,
+ std::shared_ptr<BlobFile>* blob_file);
+
+ std::shared_ptr<BlobFile> FindBlobFileLocked(uint64_t expiration) const;
+
+ // periodic sanity check. Bunch of checks
+ std::pair<bool, int64_t> SanityCheck(bool aborted);
+
+ // Delete files that have been marked obsolete (either because of TTL
+ // or GC). Check whether any snapshots exist which refer to the same.
+ std::pair<bool, int64_t> DeleteObsoleteFiles(bool aborted);
+
+ // periodically check if open blob files and their TTL's has expired
+ // if expired, close the sequential writer and make the file immutable
+ std::pair<bool, int64_t> EvictExpiredFiles(bool aborted);
+
+ // if the number of open files, approaches ULIMIT's this
+ // task will close random readers, which are kept around for
+ // efficiency
+ std::pair<bool, int64_t> ReclaimOpenFiles(bool aborted);
+
+ std::pair<bool, int64_t> RemoveTimerQ(TimerQueue* tq, bool aborted);
+
+ // Adds the background tasks to the timer queue
+ void StartBackgroundTasks();
+
+ // add a new Blob File
+ std::shared_ptr<BlobFile> NewBlobFile(bool has_ttl,
+ const ExpirationRange& expiration_range,
+ const std::string& reason);
+
+ // Register a new blob file.
+ // REQUIRES: write lock on mutex_.
+ void RegisterBlobFile(std::shared_ptr<BlobFile> blob_file);
+
+ // collect all the blob log files from the blob directory
+ Status GetAllBlobFiles(std::set<uint64_t>* file_numbers);
+
+ // Open all blob files found in blob_dir.
+ Status OpenAllBlobFiles();
+
+ // Link an SST to a blob file. Comes in locking and non-locking varieties
+ // (the latter is used during Open).
+ template <typename Linker>
+ void LinkSstToBlobFileImpl(uint64_t sst_file_number,
+ uint64_t blob_file_number, Linker linker);
+
+ void LinkSstToBlobFile(uint64_t sst_file_number, uint64_t blob_file_number);
+
+ void LinkSstToBlobFileNoLock(uint64_t sst_file_number,
+ uint64_t blob_file_number);
+
+ // Unlink an SST from a blob file.
+ void UnlinkSstFromBlobFile(uint64_t sst_file_number,
+ uint64_t blob_file_number);
+
+ // Initialize the mapping between blob files and SSTs during Open.
+ void InitializeBlobFileToSstMapping(
+ const std::vector<LiveFileMetaData>& live_files);
+
+ // Update the mapping between blob files and SSTs after a flush and mark
+ // any unneeded blob files obsolete.
+ void ProcessFlushJobInfo(const FlushJobInfo& info);
+
+ // Update the mapping between blob files and SSTs after a compaction and
+ // mark any unneeded blob files obsolete.
+ void ProcessCompactionJobInfo(const CompactionJobInfo& info);
+
+ // Mark an immutable non-TTL blob file obsolete assuming it has no more SSTs
+ // linked to it, and all memtables from before the blob file became immutable
+ // have been flushed. Note: should only be called if the condition holds for
+ // all lower-numbered non-TTL blob files as well.
+ bool MarkBlobFileObsoleteIfNeeded(const std::shared_ptr<BlobFile>& blob_file,
+ SequenceNumber obsolete_seq);
+
+ // Mark all immutable non-TTL blob files that aren't needed by any SSTs as
+ // obsolete. Comes in two varieties; the version used during Open need not
+ // worry about locking or snapshots.
+ template <class Functor>
+ void MarkUnreferencedBlobFilesObsoleteImpl(Functor mark_if_needed);
+
+ void MarkUnreferencedBlobFilesObsolete();
+ void MarkUnreferencedBlobFilesObsoleteDuringOpen();
+
+ void UpdateLiveSSTSize();
+
+ Status GetBlobFileReader(const std::shared_ptr<BlobFile>& blob_file,
+ std::shared_ptr<RandomAccessFileReader>* reader);
+
+ // hold write mutex on file and call.
+ // Close the above Random Access reader
+ void CloseRandomAccessLocked(const std::shared_ptr<BlobFile>& bfile);
+
+ // hold write mutex on file and call
+ // creates a sequential (append) writer for this blobfile
+ Status CreateWriterLocked(const std::shared_ptr<BlobFile>& bfile);
+
+ // returns a Writer object for the file. If writer is not
+ // already present, creates one. Needs Write Mutex to be held
+ Status CheckOrCreateWriterLocked(const std::shared_ptr<BlobFile>& blob_file,
+ std::shared_ptr<Writer>* writer);
+
+ // checks if there is no snapshot which is referencing the
+ // blobs
+ bool VisibleToActiveSnapshot(const std::shared_ptr<BlobFile>& file);
+ bool FileDeleteOk_SnapshotCheckLocked(const std::shared_ptr<BlobFile>& bfile);
+
+ void CopyBlobFiles(std::vector<std::shared_ptr<BlobFile>>* bfiles_copy);
+
+ uint64_t EpochNow() { return env_->NowMicros() / 1000000; }
+
+ // Check if inserting a new blob will make DB grow out of space.
+ // If is_fifo = true, FIFO eviction will be triggered to make room for the
+ // new blob. If force_evict = true, FIFO eviction will evict blob files
+ // even eviction will not make enough room for the new blob.
+ Status CheckSizeAndEvictBlobFiles(uint64_t blob_size,
+ bool force_evict = false);
+
+ // name of the database directory
+ std::string dbname_;
+
+ // the base DB
+ DBImpl* db_impl_;
+ Env* env_;
+
+ // the options that govern the behavior of Blob Storage
+ BlobDBOptions bdb_options_;
+ DBOptions db_options_;
+ ColumnFamilyOptions cf_options_;
+ EnvOptions env_options_;
+
+ // Raw pointer of statistic. db_options_ has a std::shared_ptr to hold
+ // ownership.
+ Statistics* statistics_;
+
+ // by default this is "blob_dir" under dbname_
+ // but can be configured
+ std::string blob_dir_;
+
+ // pointer to directory
+ std::unique_ptr<Directory> dir_ent_;
+
+ // Read Write Mutex, which protects all the data structures
+ // HEAVILY TRAFFICKED
+ mutable port::RWMutex mutex_;
+
+ // Writers has to hold write_mutex_ before writing.
+ mutable port::Mutex write_mutex_;
+
+ // counter for blob file number
+ std::atomic<uint64_t> next_file_number_;
+
+ // entire metadata of all the BLOB files memory
+ std::map<uint64_t, std::shared_ptr<BlobFile>> blob_files_;
+
+ // All live immutable non-TTL blob files.
+ std::map<uint64_t, std::shared_ptr<BlobFile>> live_imm_non_ttl_blob_files_;
+
+ // The largest sequence number that has been flushed.
+ SequenceNumber flush_sequence_;
+
+ // opened non-TTL blob file.
+ std::shared_ptr<BlobFile> open_non_ttl_file_;
+
+ // all the blob files which are currently being appended to based
+ // on variety of incoming TTL's
+ std::set<std::shared_ptr<BlobFile>, BlobFileComparatorTTL> open_ttl_files_;
+
+ // Flag to check whether Close() has been called on this DB
+ bool closed_;
+
+ // timer based queue to execute tasks
+ TimerQueue tqueue_;
+
+ // number of files opened for random access/GET
+ // counter is used to monitor and close excess RA files.
+ std::atomic<uint32_t> open_file_count_;
+
+ // Total size of all live blob files (i.e. exclude obsolete files).
+ std::atomic<uint64_t> total_blob_size_;
+
+ // total size of SST files.
+ std::atomic<uint64_t> live_sst_size_;
+
+ // Latest FIFO eviction timestamp
+ //
+ // REQUIRES: access with metex_ lock held.
+ uint64_t fifo_eviction_seq_;
+
+ // The expiration up to which latest FIFO eviction evicts.
+ //
+ // REQUIRES: access with metex_ lock held.
+ uint64_t evict_expiration_up_to_;
+
+ std::list<std::shared_ptr<BlobFile>> obsolete_files_;
+
+ // DeleteObsoleteFiles, DiableFileDeletions and EnableFileDeletions block
+ // on the mutex to avoid contention.
+ //
+ // While DeleteObsoleteFiles hold both mutex_ and delete_file_mutex_, note
+ // the difference. mutex_ only needs to be held when access the
+ // data-structure, and delete_file_mutex_ needs to be held the whole time
+ // during DeleteObsoleteFiles to avoid being run simultaneously with
+ // DisableFileDeletions.
+ //
+ // If both of mutex_ and delete_file_mutex_ needs to be held, it is adviced
+ // to hold delete_file_mutex_ first to avoid deadlock.
+ mutable port::Mutex delete_file_mutex_;
+
+ // Each call of DisableFileDeletions will increase disable_file_deletion_
+ // by 1. EnableFileDeletions will either decrease the count by 1 or reset
+ // it to zeor, depending on the force flag.
+ //
+ // REQUIRES: access with delete_file_mutex_ held.
+ int disable_file_deletions_ = 0;
+
+ uint32_t debug_level_;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl_filesnapshot.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl_filesnapshot.cc
new file mode 100644
index 00000000..168c7ce9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_impl_filesnapshot.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_db_impl.h"
+
+#include "file/filename.h"
+#include "logging/logging.h"
+#include "util/mutexlock.h"
+
+// BlobDBImpl methods to get snapshot of files, e.g. for replication.
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+Status BlobDBImpl::DisableFileDeletions() {
+ // Disable base DB file deletions.
+ Status s = db_impl_->DisableFileDeletions();
+ if (!s.ok()) {
+ return s;
+ }
+
+ int count = 0;
+ {
+ // Hold delete_file_mutex_ to make sure no DeleteObsoleteFiles job
+ // is running.
+ MutexLock l(&delete_file_mutex_);
+ count = ++disable_file_deletions_;
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log,
+ "Disalbed blob file deletions. count: %d", count);
+ return Status::OK();
+}
+
+Status BlobDBImpl::EnableFileDeletions(bool force) {
+ // Enable base DB file deletions.
+ Status s = db_impl_->EnableFileDeletions(force);
+ if (!s.ok()) {
+ return s;
+ }
+
+ int count = 0;
+ {
+ MutexLock l(&delete_file_mutex_);
+ if (force) {
+ disable_file_deletions_ = 0;
+ } else if (disable_file_deletions_ > 0) {
+ count = --disable_file_deletions_;
+ }
+ assert(count >= 0);
+ }
+
+ ROCKS_LOG_INFO(db_options_.info_log, "Enabled blob file deletions. count: %d",
+ count);
+ // Consider trigger DeleteobsoleteFiles once after re-enabled, if we are to
+ // make DeleteobsoleteFiles re-run interval configuration.
+ return Status::OK();
+}
+
+Status BlobDBImpl::GetLiveFiles(std::vector<std::string>& ret,
+ uint64_t* manifest_file_size,
+ bool flush_memtable) {
+ if (!bdb_options_.path_relative) {
+ return Status::NotSupported(
+ "Not able to get relative blob file path from absolute blob_dir.");
+ }
+ // Hold a lock in the beginning to avoid updates to base DB during the call
+ ReadLock rl(&mutex_);
+ Status s = db_->GetLiveFiles(ret, manifest_file_size, flush_memtable);
+ if (!s.ok()) {
+ return s;
+ }
+ ret.reserve(ret.size() + blob_files_.size());
+ for (auto bfile_pair : blob_files_) {
+ auto blob_file = bfile_pair.second;
+ // Path should be relative to db_name, but begin with slash.
+ ret.emplace_back(
+ BlobFileName("", bdb_options_.blob_dir, blob_file->BlobFileNumber()));
+ }
+ return Status::OK();
+}
+
+void BlobDBImpl::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
+ // Path should be relative to db_name.
+ assert(bdb_options_.path_relative);
+ // Hold a lock in the beginning to avoid updates to base DB during the call
+ ReadLock rl(&mutex_);
+ db_->GetLiveFilesMetaData(metadata);
+ for (auto bfile_pair : blob_files_) {
+ auto blob_file = bfile_pair.second;
+ LiveFileMetaData filemetadata;
+ filemetadata.size = static_cast<size_t>(blob_file->GetFileSize());
+ const uint64_t file_number = blob_file->BlobFileNumber();
+ // Path should be relative to db_name, but begin with slash.
+ filemetadata.name = BlobFileName("", bdb_options_.blob_dir, file_number);
+ filemetadata.file_number = file_number;
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily());
+ filemetadata.column_family_name = cfh->GetName();
+ metadata->emplace_back(filemetadata);
+ }
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_iterator.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_iterator.h
new file mode 100644
index 00000000..af07117e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_iterator.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "db/arena_wrapped_db_iter.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/iterator.h"
+#include "util/stop_watch.h"
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+using ROCKSDB_NAMESPACE::ManagedSnapshot;
+
+class BlobDBIterator : public Iterator {
+ public:
+ BlobDBIterator(ManagedSnapshot* snapshot, ArenaWrappedDBIter* iter,
+ BlobDBImpl* blob_db, Env* env, Statistics* statistics)
+ : snapshot_(snapshot),
+ iter_(iter),
+ blob_db_(blob_db),
+ env_(env),
+ statistics_(statistics) {}
+
+ virtual ~BlobDBIterator() = default;
+
+ bool Valid() const override {
+ if (!iter_->Valid()) {
+ return false;
+ }
+ return status_.ok();
+ }
+
+ Status status() const override {
+ if (!iter_->status().ok()) {
+ return iter_->status();
+ }
+ return status_;
+ }
+
+ void SeekToFirst() override {
+ StopWatch seek_sw(env_, statistics_, BLOB_DB_SEEK_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_SEEK);
+ iter_->SeekToFirst();
+ while (UpdateBlobValue()) {
+ iter_->Next();
+ }
+ }
+
+ void SeekToLast() override {
+ StopWatch seek_sw(env_, statistics_, BLOB_DB_SEEK_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_SEEK);
+ iter_->SeekToLast();
+ while (UpdateBlobValue()) {
+ iter_->Prev();
+ }
+ }
+
+ void Seek(const Slice& target) override {
+ StopWatch seek_sw(env_, statistics_, BLOB_DB_SEEK_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_SEEK);
+ iter_->Seek(target);
+ while (UpdateBlobValue()) {
+ iter_->Next();
+ }
+ }
+
+ void SeekForPrev(const Slice& target) override {
+ StopWatch seek_sw(env_, statistics_, BLOB_DB_SEEK_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_SEEK);
+ iter_->SeekForPrev(target);
+ while (UpdateBlobValue()) {
+ iter_->Prev();
+ }
+ }
+
+ void Next() override {
+ assert(Valid());
+ StopWatch next_sw(env_, statistics_, BLOB_DB_NEXT_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_NEXT);
+ iter_->Next();
+ while (UpdateBlobValue()) {
+ iter_->Next();
+ }
+ }
+
+ void Prev() override {
+ assert(Valid());
+ StopWatch prev_sw(env_, statistics_, BLOB_DB_PREV_MICROS);
+ RecordTick(statistics_, BLOB_DB_NUM_PREV);
+ iter_->Prev();
+ while (UpdateBlobValue()) {
+ iter_->Prev();
+ }
+ }
+
+ Slice key() const override {
+ assert(Valid());
+ return iter_->key();
+ }
+
+ Slice value() const override {
+ assert(Valid());
+ if (!iter_->IsBlob()) {
+ return iter_->value();
+ }
+ return value_;
+ }
+
+ // Iterator::Refresh() not supported.
+
+ private:
+ // Return true if caller should continue to next value.
+ bool UpdateBlobValue() {
+ value_.Reset();
+ status_ = Status::OK();
+ if (iter_->Valid() && iter_->status().ok() && iter_->IsBlob()) {
+ Status s = blob_db_->GetBlobValue(iter_->key(), iter_->value(), &value_);
+ if (s.IsNotFound()) {
+ return true;
+ } else {
+ if (!s.ok()) {
+ status_ = s;
+ }
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ std::unique_ptr<ManagedSnapshot> snapshot_;
+ std::unique_ptr<ArenaWrappedDBIter> iter_;
+ BlobDBImpl* blob_db_;
+ Env* env_;
+ Statistics* statistics_;
+ Status status_;
+ PinnableSlice value_;
+};
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_listener.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_listener.h
new file mode 100644
index 00000000..c26d7bd2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_listener.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <atomic>
+
+#include "rocksdb/listener.h"
+#include "util/mutexlock.h"
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+class BlobDBListener : public EventListener {
+ public:
+ explicit BlobDBListener(BlobDBImpl* blob_db_impl)
+ : blob_db_impl_(blob_db_impl) {}
+
+ void OnFlushBegin(DB* /*db*/, const FlushJobInfo& /*info*/) override {
+ assert(blob_db_impl_ != nullptr);
+ blob_db_impl_->SyncBlobFiles();
+ }
+
+ void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& /*info*/) override {
+ assert(blob_db_impl_ != nullptr);
+ blob_db_impl_->UpdateLiveSSTSize();
+ }
+
+ void OnCompactionCompleted(DB* /*db*/,
+ const CompactionJobInfo& /*info*/) override {
+ assert(blob_db_impl_ != nullptr);
+ blob_db_impl_->UpdateLiveSSTSize();
+ }
+
+ protected:
+ BlobDBImpl* blob_db_impl_;
+};
+
+class BlobDBListenerGC : public BlobDBListener {
+ public:
+ explicit BlobDBListenerGC(BlobDBImpl* blob_db_impl)
+ : BlobDBListener(blob_db_impl) {}
+
+ void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
+ BlobDBListener::OnFlushCompleted(db, info);
+
+ assert(blob_db_impl_);
+ blob_db_impl_->ProcessFlushJobInfo(info);
+ }
+
+ void OnCompactionCompleted(DB* db, const CompactionJobInfo& info) override {
+ BlobDBListener::OnCompactionCompleted(db, info);
+
+ assert(blob_db_impl_);
+ blob_db_impl_->ProcessCompactionJobInfo(info);
+ }
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_test.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_test.cc
new file mode 100644
index 00000000..9fee52e8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_db_test.cc
@@ -0,0 +1,1992 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <algorithm>
+#include <chrono>
+#include <cstdlib>
+#include <iomanip>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "db/blob_index.h"
+#include "db/db_test_util.h"
+#include "env/composite_env_wrapper.h"
+#include "file/file_util.h"
+#include "file/sst_file_manager_impl.h"
+#include "port/port.h"
+#include "rocksdb/utilities/debug.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "util/cast_util.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "utilities/blob_db/blob_db.h"
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+class BlobDBTest : public testing::Test {
+ public:
+ const int kMaxBlobSize = 1 << 14;
+
+ struct BlobIndexVersion {
+ BlobIndexVersion() = default;
+ BlobIndexVersion(std::string _user_key, uint64_t _file_number,
+ uint64_t _expiration, SequenceNumber _sequence,
+ ValueType _type)
+ : user_key(std::move(_user_key)),
+ file_number(_file_number),
+ expiration(_expiration),
+ sequence(_sequence),
+ type(_type) {}
+
+ std::string user_key;
+ uint64_t file_number = kInvalidBlobFileNumber;
+ uint64_t expiration = kNoExpiration;
+ SequenceNumber sequence = 0;
+ ValueType type = kTypeValue;
+ };
+
+ BlobDBTest()
+ : dbname_(test::PerThreadDBPath("blob_db_test")),
+ mock_env_(new MockTimeEnv(Env::Default())),
+ fault_injection_env_(new FaultInjectionTestEnv(Env::Default())),
+ blob_db_(nullptr) {
+ Status s = DestroyBlobDB(dbname_, Options(), BlobDBOptions());
+ assert(s.ok());
+ }
+
+ ~BlobDBTest() override {
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+ Destroy();
+ }
+
+ Status TryOpen(BlobDBOptions bdb_options = BlobDBOptions(),
+ Options options = Options()) {
+ options.create_if_missing = true;
+ return BlobDB::Open(options, bdb_options, dbname_, &blob_db_);
+ }
+
+ void Open(BlobDBOptions bdb_options = BlobDBOptions(),
+ Options options = Options()) {
+ ASSERT_OK(TryOpen(bdb_options, options));
+ }
+
+ void Reopen(BlobDBOptions bdb_options = BlobDBOptions(),
+ Options options = Options()) {
+ assert(blob_db_ != nullptr);
+ delete blob_db_;
+ blob_db_ = nullptr;
+ Open(bdb_options, options);
+ }
+
+ void Close() {
+ assert(blob_db_ != nullptr);
+ delete blob_db_;
+ blob_db_ = nullptr;
+ }
+
+ void Destroy() {
+ if (blob_db_) {
+ Options options = blob_db_->GetOptions();
+ BlobDBOptions bdb_options = blob_db_->GetBlobDBOptions();
+ delete blob_db_;
+ blob_db_ = nullptr;
+ ASSERT_OK(DestroyBlobDB(dbname_, options, bdb_options));
+ }
+ }
+
+ BlobDBImpl *blob_db_impl() {
+ return reinterpret_cast<BlobDBImpl *>(blob_db_);
+ }
+
+ Status Put(const Slice &key, const Slice &value,
+ std::map<std::string, std::string> *data = nullptr) {
+ Status s = blob_db_->Put(WriteOptions(), key, value);
+ if (data != nullptr) {
+ (*data)[key.ToString()] = value.ToString();
+ }
+ return s;
+ }
+
+ void Delete(const std::string &key,
+ std::map<std::string, std::string> *data = nullptr) {
+ ASSERT_OK(blob_db_->Delete(WriteOptions(), key));
+ if (data != nullptr) {
+ data->erase(key);
+ }
+ }
+
+ Status PutWithTTL(const Slice &key, const Slice &value, uint64_t ttl,
+ std::map<std::string, std::string> *data = nullptr) {
+ Status s = blob_db_->PutWithTTL(WriteOptions(), key, value, ttl);
+ if (data != nullptr) {
+ (*data)[key.ToString()] = value.ToString();
+ }
+ return s;
+ }
+
+ Status PutUntil(const Slice &key, const Slice &value, uint64_t expiration) {
+ return blob_db_->PutUntil(WriteOptions(), key, value, expiration);
+ }
+
+ void PutRandomWithTTL(const std::string &key, uint64_t ttl, Random *rnd,
+ std::map<std::string, std::string> *data = nullptr) {
+ int len = rnd->Next() % kMaxBlobSize + 1;
+ std::string value = test::RandomHumanReadableString(rnd, len);
+ ASSERT_OK(
+ blob_db_->PutWithTTL(WriteOptions(), Slice(key), Slice(value), ttl));
+ if (data != nullptr) {
+ (*data)[key] = value;
+ }
+ }
+
+ void PutRandomUntil(const std::string &key, uint64_t expiration, Random *rnd,
+ std::map<std::string, std::string> *data = nullptr) {
+ int len = rnd->Next() % kMaxBlobSize + 1;
+ std::string value = test::RandomHumanReadableString(rnd, len);
+ ASSERT_OK(blob_db_->PutUntil(WriteOptions(), Slice(key), Slice(value),
+ expiration));
+ if (data != nullptr) {
+ (*data)[key] = value;
+ }
+ }
+
+ void PutRandom(const std::string &key, Random *rnd,
+ std::map<std::string, std::string> *data = nullptr) {
+ PutRandom(blob_db_, key, rnd, data);
+ }
+
+ void PutRandom(DB *db, const std::string &key, Random *rnd,
+ std::map<std::string, std::string> *data = nullptr) {
+ int len = rnd->Next() % kMaxBlobSize + 1;
+ std::string value = test::RandomHumanReadableString(rnd, len);
+ ASSERT_OK(db->Put(WriteOptions(), Slice(key), Slice(value)));
+ if (data != nullptr) {
+ (*data)[key] = value;
+ }
+ }
+
+ void PutRandomToWriteBatch(
+ const std::string &key, Random *rnd, WriteBatch *batch,
+ std::map<std::string, std::string> *data = nullptr) {
+ int len = rnd->Next() % kMaxBlobSize + 1;
+ std::string value = test::RandomHumanReadableString(rnd, len);
+ ASSERT_OK(batch->Put(key, value));
+ if (data != nullptr) {
+ (*data)[key] = value;
+ }
+ }
+
+ // Verify blob db contain expected data and nothing more.
+ void VerifyDB(const std::map<std::string, std::string> &data) {
+ VerifyDB(blob_db_, data);
+ }
+
+ void VerifyDB(DB *db, const std::map<std::string, std::string> &data) {
+ // Verify normal Get
+ auto* cfh = db->DefaultColumnFamily();
+ for (auto &p : data) {
+ PinnableSlice value_slice;
+ ASSERT_OK(db->Get(ReadOptions(), cfh, p.first, &value_slice));
+ ASSERT_EQ(p.second, value_slice.ToString());
+ std::string value;
+ ASSERT_OK(db->Get(ReadOptions(), cfh, p.first, &value));
+ ASSERT_EQ(p.second, value);
+ }
+
+ // Verify iterators
+ Iterator *iter = db->NewIterator(ReadOptions());
+ iter->SeekToFirst();
+ for (auto &p : data) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(p.first, iter->key().ToString());
+ ASSERT_EQ(p.second, iter->value().ToString());
+ iter->Next();
+ }
+ ASSERT_FALSE(iter->Valid());
+ ASSERT_OK(iter->status());
+ delete iter;
+ }
+
+ void VerifyBaseDB(
+ const std::map<std::string, KeyVersion> &expected_versions) {
+ auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
+ DB *db = blob_db_->GetRootDB();
+ const size_t kMaxKeys = 10000;
+ std::vector<KeyVersion> versions;
+ GetAllKeyVersions(db, "", "", kMaxKeys, &versions);
+ ASSERT_EQ(expected_versions.size(), versions.size());
+ size_t i = 0;
+ for (auto &key_version : expected_versions) {
+ const KeyVersion &expected_version = key_version.second;
+ ASSERT_EQ(expected_version.user_key, versions[i].user_key);
+ ASSERT_EQ(expected_version.sequence, versions[i].sequence);
+ ASSERT_EQ(expected_version.type, versions[i].type);
+ if (versions[i].type == kTypeValue) {
+ ASSERT_EQ(expected_version.value, versions[i].value);
+ } else {
+ ASSERT_EQ(kTypeBlobIndex, versions[i].type);
+ PinnableSlice value;
+ ASSERT_OK(bdb_impl->TEST_GetBlobValue(versions[i].user_key,
+ versions[i].value, &value));
+ ASSERT_EQ(expected_version.value, value.ToString());
+ }
+ i++;
+ }
+ }
+
+ void VerifyBaseDBBlobIndex(
+ const std::map<std::string, BlobIndexVersion> &expected_versions) {
+ const size_t kMaxKeys = 10000;
+ std::vector<KeyVersion> versions;
+ ASSERT_OK(
+ GetAllKeyVersions(blob_db_->GetRootDB(), "", "", kMaxKeys, &versions));
+ ASSERT_EQ(versions.size(), expected_versions.size());
+
+ size_t i = 0;
+ for (const auto &expected_pair : expected_versions) {
+ const BlobIndexVersion &expected_version = expected_pair.second;
+
+ ASSERT_EQ(versions[i].user_key, expected_version.user_key);
+ ASSERT_EQ(versions[i].sequence, expected_version.sequence);
+ ASSERT_EQ(versions[i].type, expected_version.type);
+ if (versions[i].type != kTypeBlobIndex) {
+ ASSERT_EQ(kInvalidBlobFileNumber, expected_version.file_number);
+ ASSERT_EQ(kNoExpiration, expected_version.expiration);
+
+ ++i;
+ continue;
+ }
+
+ BlobIndex blob_index;
+ ASSERT_OK(blob_index.DecodeFrom(versions[i].value));
+
+ const uint64_t file_number = !blob_index.IsInlined()
+ ? blob_index.file_number()
+ : kInvalidBlobFileNumber;
+ ASSERT_EQ(file_number, expected_version.file_number);
+
+ const uint64_t expiration =
+ blob_index.HasTTL() ? blob_index.expiration() : kNoExpiration;
+ ASSERT_EQ(expiration, expected_version.expiration);
+
+ ++i;
+ }
+ }
+
+ void InsertBlobs() {
+ WriteOptions wo;
+ std::string value;
+
+ Random rnd(301);
+ for (size_t i = 0; i < 100000; i++) {
+ uint64_t ttl = rnd.Next() % 86400;
+ PutRandomWithTTL("key" + ToString(i % 500), ttl, &rnd, nullptr);
+ }
+
+ for (size_t i = 0; i < 10; i++) {
+ Delete("key" + ToString(i % 500));
+ }
+ }
+
+ const std::string dbname_;
+ std::unique_ptr<MockTimeEnv> mock_env_;
+ std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
+ BlobDB *blob_db_;
+}; // class BlobDBTest
+
+TEST_F(BlobDBTest, Put) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("key" + ToString(i), &rnd, &data);
+ }
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, PutWithTTL) {
+ Random rnd(301);
+ Options options;
+ options.env = mock_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.ttl_range_secs = 1000;
+ bdb_options.min_blob_size = 0;
+ bdb_options.blob_file_size = 256 * 1000 * 1000;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options, options);
+ std::map<std::string, std::string> data;
+ mock_env_->set_current_time(50);
+ for (size_t i = 0; i < 100; i++) {
+ uint64_t ttl = rnd.Next() % 100;
+ PutRandomWithTTL("key" + ToString(i), ttl, &rnd,
+ (ttl <= 50 ? nullptr : &data));
+ }
+ mock_env_->set_current_time(100);
+ auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
+ auto blob_files = bdb_impl->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ ASSERT_TRUE(blob_files[0]->HasTTL());
+ ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, PutUntil) {
+ Random rnd(301);
+ Options options;
+ options.env = mock_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.ttl_range_secs = 1000;
+ bdb_options.min_blob_size = 0;
+ bdb_options.blob_file_size = 256 * 1000 * 1000;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options, options);
+ std::map<std::string, std::string> data;
+ mock_env_->set_current_time(50);
+ for (size_t i = 0; i < 100; i++) {
+ uint64_t expiration = rnd.Next() % 100 + 50;
+ PutRandomUntil("key" + ToString(i), expiration, &rnd,
+ (expiration <= 100 ? nullptr : &data));
+ }
+ mock_env_->set_current_time(100);
+ auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
+ auto blob_files = bdb_impl->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ ASSERT_TRUE(blob_files[0]->HasTTL());
+ ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, StackableDBGet) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("key" + ToString(i), &rnd, &data);
+ }
+ for (size_t i = 0; i < 100; i++) {
+ StackableDB *db = blob_db_;
+ ColumnFamilyHandle *column_family = db->DefaultColumnFamily();
+ std::string key = "key" + ToString(i);
+ PinnableSlice pinnable_value;
+ ASSERT_OK(db->Get(ReadOptions(), column_family, key, &pinnable_value));
+ std::string string_value;
+ ASSERT_OK(db->Get(ReadOptions(), column_family, key, &string_value));
+ ASSERT_EQ(string_value, pinnable_value.ToString());
+ ASSERT_EQ(string_value, data[key]);
+ }
+}
+
+TEST_F(BlobDBTest, GetExpiration) {
+ Options options;
+ options.env = mock_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.disable_background_tasks = true;
+ mock_env_->set_current_time(100);
+ Open(bdb_options, options);
+ Put("key1", "value1");
+ PutWithTTL("key2", "value2", 200);
+ PinnableSlice value;
+ uint64_t expiration;
+ ASSERT_OK(blob_db_->Get(ReadOptions(), "key1", &value, &expiration));
+ ASSERT_EQ("value1", value.ToString());
+ ASSERT_EQ(kNoExpiration, expiration);
+ ASSERT_OK(blob_db_->Get(ReadOptions(), "key2", &value, &expiration));
+ ASSERT_EQ("value2", value.ToString());
+ ASSERT_EQ(300 /* = 100 + 200 */, expiration);
+}
+
+TEST_F(BlobDBTest, GetIOError) {
+ Options options;
+ options.env = fault_injection_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0; // Make sure value write to blob file
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options, options);
+ ColumnFamilyHandle *column_family = blob_db_->DefaultColumnFamily();
+ PinnableSlice value;
+ ASSERT_OK(Put("foo", "bar"));
+ fault_injection_env_->SetFilesystemActive(false, Status::IOError());
+ Status s = blob_db_->Get(ReadOptions(), column_family, "foo", &value);
+ ASSERT_TRUE(s.IsIOError());
+ // Reactivate file system to allow test to close DB.
+ fault_injection_env_->SetFilesystemActive(true);
+}
+
+TEST_F(BlobDBTest, PutIOError) {
+ Options options;
+ options.env = fault_injection_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0; // Make sure value write to blob file
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options, options);
+ fault_injection_env_->SetFilesystemActive(false, Status::IOError());
+ ASSERT_TRUE(Put("foo", "v1").IsIOError());
+ fault_injection_env_->SetFilesystemActive(true, Status::IOError());
+ ASSERT_OK(Put("bar", "v1"));
+}
+
+TEST_F(BlobDBTest, WriteBatch) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < 10; j++) {
+ PutRandomToWriteBatch("key" + ToString(j * 100 + i), &rnd, &batch, &data);
+ }
+ blob_db_->Write(WriteOptions(), &batch);
+ }
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, Delete) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("key" + ToString(i), &rnd, &data);
+ }
+ for (size_t i = 0; i < 100; i += 5) {
+ Delete("key" + ToString(i), &data);
+ }
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, DeleteBatch) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("key" + ToString(i), &rnd);
+ }
+ WriteBatch batch;
+ for (size_t i = 0; i < 100; i++) {
+ batch.Delete("key" + ToString(i));
+ }
+ ASSERT_OK(blob_db_->Write(WriteOptions(), &batch));
+ // DB should be empty.
+ VerifyDB({});
+}
+
+TEST_F(BlobDBTest, Override) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (int i = 0; i < 10000; i++) {
+ PutRandom("key" + ToString(i), &rnd, nullptr);
+ }
+ // override all the keys
+ for (int i = 0; i < 10000; i++) {
+ PutRandom("key" + ToString(i), &rnd, &data);
+ }
+ VerifyDB(data);
+}
+
+#ifdef SNAPPY
+TEST_F(BlobDBTest, Compression) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ bdb_options.compression = CompressionType::kSnappyCompression;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("put-key" + ToString(i), &rnd, &data);
+ }
+ for (int i = 0; i < 100; i++) {
+ WriteBatch batch;
+ for (size_t j = 0; j < 10; j++) {
+ PutRandomToWriteBatch("write-batch-key" + ToString(j * 100 + i), &rnd,
+ &batch, &data);
+ }
+ blob_db_->Write(WriteOptions(), &batch);
+ }
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, DecompressAfterReopen) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ bdb_options.compression = CompressionType::kSnappyCompression;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("put-key" + ToString(i), &rnd, &data);
+ }
+ VerifyDB(data);
+ bdb_options.compression = CompressionType::kNoCompression;
+ Reopen(bdb_options);
+ VerifyDB(data);
+}
+#endif
+
+TEST_F(BlobDBTest, MultipleWriters) {
+ Open(BlobDBOptions());
+
+ std::vector<port::Thread> workers;
+ std::vector<std::map<std::string, std::string>> data_set(10);
+ for (uint32_t i = 0; i < 10; i++)
+ workers.push_back(port::Thread(
+ [&](uint32_t id) {
+ Random rnd(301 + id);
+ for (int j = 0; j < 100; j++) {
+ std::string key = "key" + ToString(id) + "_" + ToString(j);
+ if (id < 5) {
+ PutRandom(key, &rnd, &data_set[id]);
+ } else {
+ WriteBatch batch;
+ PutRandomToWriteBatch(key, &rnd, &batch, &data_set[id]);
+ blob_db_->Write(WriteOptions(), &batch);
+ }
+ }
+ },
+ i));
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 10; i++) {
+ workers[i].join();
+ data.insert(data_set[i].begin(), data_set[i].end());
+ }
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, SstFileManager) {
+ // run the same test for Get(), MultiGet() and Iterator each.
+ std::shared_ptr<SstFileManager> sst_file_manager(
+ NewSstFileManager(mock_env_.get()));
+ sst_file_manager->SetDeleteRateBytesPerSecond(1);
+ SstFileManagerImpl *sfm =
+ static_cast<SstFileManagerImpl *>(sst_file_manager.get());
+
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.enable_garbage_collection = true;
+ bdb_options.garbage_collection_cutoff = 1.0;
+ Options db_options;
+
+ int files_scheduled_to_delete = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SstFileManagerImpl::ScheduleFileDeletion", [&](void *arg) {
+ assert(arg);
+ const std::string *const file_path =
+ static_cast<const std::string *>(arg);
+ if (file_path->find(".blob") != std::string::npos) {
+ ++files_scheduled_to_delete;
+ }
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+ db_options.sst_file_manager = sst_file_manager;
+
+ Open(bdb_options, db_options);
+
+ // Create one obselete file and clean it.
+ blob_db_->Put(WriteOptions(), "foo", "bar");
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ std::shared_ptr<BlobFile> bfile = blob_files[0];
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+
+ // Even if SSTFileManager is not set, DB is creating a dummy one.
+ ASSERT_EQ(1, files_scheduled_to_delete);
+ Destroy();
+ // Make sure that DestroyBlobDB() also goes through delete scheduler.
+ ASSERT_EQ(2, files_scheduled_to_delete);
+ SyncPoint::GetInstance()->DisableProcessing();
+ sfm->WaitForEmptyTrash();
+}
+
+TEST_F(BlobDBTest, SstFileManagerRestart) {
+ int files_scheduled_to_delete = 0;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "SstFileManagerImpl::ScheduleFileDeletion", [&](void *arg) {
+ assert(arg);
+ const std::string *const file_path =
+ static_cast<const std::string *>(arg);
+ if (file_path->find(".blob") != std::string::npos) {
+ ++files_scheduled_to_delete;
+ }
+ });
+
+ // run the same test for Get(), MultiGet() and Iterator each.
+ std::shared_ptr<SstFileManager> sst_file_manager(
+ NewSstFileManager(mock_env_.get()));
+ sst_file_manager->SetDeleteRateBytesPerSecond(1);
+ SstFileManagerImpl *sfm =
+ static_cast<SstFileManagerImpl *>(sst_file_manager.get());
+
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ Options db_options;
+
+ SyncPoint::GetInstance()->EnableProcessing();
+ db_options.sst_file_manager = sst_file_manager;
+
+ Open(bdb_options, db_options);
+ std::string blob_dir = blob_db_impl()->TEST_blob_dir();
+ blob_db_->Put(WriteOptions(), "foo", "bar");
+ Close();
+
+ // Create 3 dummy trash files under the blob_dir
+ LegacyFileSystemWrapper fs(db_options.env);
+ CreateFile(&fs, blob_dir + "/000666.blob.trash", "", false);
+ CreateFile(&fs, blob_dir + "/000888.blob.trash", "", true);
+ CreateFile(&fs, blob_dir + "/something_not_match.trash", "", false);
+
+ // Make sure that reopening the DB rescan the existing trash files
+ Open(bdb_options, db_options);
+ ASSERT_EQ(files_scheduled_to_delete, 2);
+
+ sfm->WaitForEmptyTrash();
+
+ // There should be exact one file under the blob dir now.
+ std::vector<std::string> all_files;
+ ASSERT_OK(db_options.env->GetChildren(blob_dir, &all_files));
+ int nfiles = 0;
+ for (const auto &f : all_files) {
+ assert(!f.empty());
+ if (f[0] == '.') {
+ continue;
+ }
+ nfiles++;
+ }
+ ASSERT_EQ(nfiles, 1);
+
+ SyncPoint::GetInstance()->DisableProcessing();
+}
+
+TEST_F(BlobDBTest, SnapshotAndGarbageCollection) {
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.enable_garbage_collection = true;
+ bdb_options.garbage_collection_cutoff = 1.0;
+ bdb_options.disable_background_tasks = true;
+
+ // i = when to take snapshot
+ for (int i = 0; i < 4; i++) {
+ Destroy();
+ Open(bdb_options);
+
+ const Snapshot *snapshot = nullptr;
+
+ // First file
+ ASSERT_OK(Put("key1", "value"));
+ if (i == 0) {
+ snapshot = blob_db_->GetSnapshot();
+ }
+
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
+
+ // Second file
+ ASSERT_OK(Put("key2", "value"));
+ if (i == 1) {
+ snapshot = blob_db_->GetSnapshot();
+ }
+
+ blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(2, blob_files.size());
+ auto bfile = blob_files[1];
+ ASSERT_FALSE(bfile->Immutable());
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
+
+ // Third file
+ ASSERT_OK(Put("key3", "value"));
+ if (i == 2) {
+ snapshot = blob_db_->GetSnapshot();
+ }
+
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_TRUE(bfile->Obsolete());
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(),
+ bfile->GetObsoleteSequence());
+
+ Delete("key2");
+ if (i == 3) {
+ snapshot = blob_db_->GetSnapshot();
+ }
+
+ ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+
+ if (i >= 2) {
+ // The snapshot shouldn't see data in bfile
+ ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
+ blob_db_->ReleaseSnapshot(snapshot);
+ } else {
+ // The snapshot will see data in bfile, so the file shouldn't be deleted
+ ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
+ blob_db_->ReleaseSnapshot(snapshot);
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
+ }
+ }
+}
+
+TEST_F(BlobDBTest, ColumnFamilyNotSupported) {
+ Options options;
+ options.env = mock_env_.get();
+ mock_env_->set_current_time(0);
+ Open(BlobDBOptions(), options);
+ ColumnFamilyHandle *default_handle = blob_db_->DefaultColumnFamily();
+ ColumnFamilyHandle *handle = nullptr;
+ std::string value;
+ std::vector<std::string> values;
+ // The call simply pass through to base db. It should succeed.
+ ASSERT_OK(
+ blob_db_->CreateColumnFamily(ColumnFamilyOptions(), "foo", &handle));
+ ASSERT_TRUE(blob_db_->Put(WriteOptions(), handle, "k", "v").IsNotSupported());
+ ASSERT_TRUE(blob_db_->PutWithTTL(WriteOptions(), handle, "k", "v", 60)
+ .IsNotSupported());
+ ASSERT_TRUE(blob_db_->PutUntil(WriteOptions(), handle, "k", "v", 100)
+ .IsNotSupported());
+ WriteBatch batch;
+ batch.Put("k1", "v1");
+ batch.Put(handle, "k2", "v2");
+ ASSERT_TRUE(blob_db_->Write(WriteOptions(), &batch).IsNotSupported());
+ ASSERT_TRUE(blob_db_->Get(ReadOptions(), "k1", &value).IsNotFound());
+ ASSERT_TRUE(
+ blob_db_->Get(ReadOptions(), handle, "k", &value).IsNotSupported());
+ auto statuses = blob_db_->MultiGet(ReadOptions(), {default_handle, handle},
+ {"k1", "k2"}, &values);
+ ASSERT_EQ(2, statuses.size());
+ ASSERT_TRUE(statuses[0].IsNotSupported());
+ ASSERT_TRUE(statuses[1].IsNotSupported());
+ ASSERT_EQ(nullptr, blob_db_->NewIterator(ReadOptions(), handle));
+ delete handle;
+}
+
+TEST_F(BlobDBTest, GetLiveFilesMetaData) {
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.blob_dir = "blob_dir";
+ bdb_options.path_relative = true;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (size_t i = 0; i < 100; i++) {
+ PutRandom("key" + ToString(i), &rnd, &data);
+ }
+ std::vector<LiveFileMetaData> metadata;
+ blob_db_->GetLiveFilesMetaData(&metadata);
+ ASSERT_EQ(1U, metadata.size());
+ // Path should be relative to db_name, but begin with slash.
+ std::string filename = "/blob_dir/000001.blob";
+ ASSERT_EQ(filename, metadata[0].name);
+ ASSERT_EQ(1, metadata[0].file_number);
+ ASSERT_EQ("default", metadata[0].column_family_name);
+ std::vector<std::string> livefile;
+ uint64_t mfs;
+ ASSERT_OK(blob_db_->GetLiveFiles(livefile, &mfs, false));
+ ASSERT_EQ(4U, livefile.size());
+ ASSERT_EQ(filename, livefile[3]);
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, MigrateFromPlainRocksDB) {
+ constexpr size_t kNumKey = 20;
+ constexpr size_t kNumIteration = 10;
+ Random rnd(301);
+ std::map<std::string, std::string> data;
+ std::vector<bool> is_blob(kNumKey, false);
+
+ // Write to plain rocksdb.
+ Options options;
+ options.create_if_missing = true;
+ DB *db = nullptr;
+ ASSERT_OK(DB::Open(options, dbname_, &db));
+ for (size_t i = 0; i < kNumIteration; i++) {
+ auto key_index = rnd.Next() % kNumKey;
+ std::string key = "key" + ToString(key_index);
+ PutRandom(db, key, &rnd, &data);
+ }
+ VerifyDB(db, data);
+ delete db;
+ db = nullptr;
+
+ // Open as blob db. Verify it can read existing data.
+ Open();
+ VerifyDB(blob_db_, data);
+ for (size_t i = 0; i < kNumIteration; i++) {
+ auto key_index = rnd.Next() % kNumKey;
+ std::string key = "key" + ToString(key_index);
+ is_blob[key_index] = true;
+ PutRandom(blob_db_, key, &rnd, &data);
+ }
+ VerifyDB(blob_db_, data);
+ delete blob_db_;
+ blob_db_ = nullptr;
+
+ // Verify plain db return error for keys written by blob db.
+ ASSERT_OK(DB::Open(options, dbname_, &db));
+ std::string value;
+ for (size_t i = 0; i < kNumKey; i++) {
+ std::string key = "key" + ToString(i);
+ Status s = db->Get(ReadOptions(), key, &value);
+ if (data.count(key) == 0) {
+ ASSERT_TRUE(s.IsNotFound());
+ } else if (is_blob[i]) {
+ ASSERT_TRUE(s.IsNotSupported());
+ } else {
+ ASSERT_OK(s);
+ ASSERT_EQ(data[key], value);
+ }
+ }
+ delete db;
+}
+
+// Test to verify that a NoSpace IOError Status is returned on reaching
+// max_db_size limit.
+TEST_F(BlobDBTest, OutOfSpace) {
+ // Use mock env to stop wall clock.
+ Options options;
+ options.env = mock_env_.get();
+ BlobDBOptions bdb_options;
+ bdb_options.max_db_size = 200;
+ bdb_options.is_fifo = false;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+
+ // Each stored blob has an overhead of about 42 bytes currently.
+ // So a small key + a 100 byte blob should take up ~150 bytes in the db.
+ std::string value(100, 'v');
+ ASSERT_OK(blob_db_->PutWithTTL(WriteOptions(), "key1", value, 60));
+
+ // Putting another blob should fail as ading it would exceed the max_db_size
+ // limit.
+ Status s = blob_db_->PutWithTTL(WriteOptions(), "key2", value, 60);
+ ASSERT_TRUE(s.IsIOError());
+ ASSERT_TRUE(s.IsNoSpace());
+}
+
+TEST_F(BlobDBTest, FIFOEviction) {
+ BlobDBOptions bdb_options;
+ bdb_options.max_db_size = 200;
+ bdb_options.blob_file_size = 100;
+ bdb_options.is_fifo = true;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+
+ std::atomic<int> evict_count{0};
+ SyncPoint::GetInstance()->SetCallBack(
+ "BlobDBImpl::EvictOldestBlobFile:Evicted",
+ [&](void *) { evict_count++; });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ // Each stored blob has an overhead of 32 bytes currently.
+ // So a 100 byte blob should take up 132 bytes.
+ std::string value(100, 'v');
+ ASSERT_OK(blob_db_->PutWithTTL(WriteOptions(), "key1", value, 10));
+ VerifyDB({{"key1", value}});
+
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+
+ // Adding another 100 bytes blob would take the total size to 264 bytes
+ // (2*132). max_db_size will be exceeded
+ // than max_db_size and trigger FIFO eviction.
+ ASSERT_OK(blob_db_->PutWithTTL(WriteOptions(), "key2", value, 60));
+ ASSERT_EQ(1, evict_count);
+ // key1 will exist until corresponding file be deleted.
+ VerifyDB({{"key1", value}, {"key2", value}});
+
+ // Adding another 100 bytes blob without TTL.
+ ASSERT_OK(blob_db_->Put(WriteOptions(), "key3", value));
+ ASSERT_EQ(2, evict_count);
+ // key1 and key2 will exist until corresponding file be deleted.
+ VerifyDB({{"key1", value}, {"key2", value}, {"key3", value}});
+
+ // The fourth blob file, without TTL.
+ ASSERT_OK(blob_db_->Put(WriteOptions(), "key4", value));
+ ASSERT_EQ(3, evict_count);
+ VerifyDB(
+ {{"key1", value}, {"key2", value}, {"key3", value}, {"key4", value}});
+
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(4, blob_files.size());
+ ASSERT_TRUE(blob_files[0]->Obsolete());
+ ASSERT_TRUE(blob_files[1]->Obsolete());
+ ASSERT_TRUE(blob_files[2]->Obsolete());
+ ASSERT_FALSE(blob_files[3]->Obsolete());
+ auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_EQ(3, obsolete_files.size());
+ ASSERT_EQ(blob_files[0], obsolete_files[0]);
+ ASSERT_EQ(blob_files[1], obsolete_files[1]);
+ ASSERT_EQ(blob_files[2], obsolete_files[2]);
+
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_TRUE(obsolete_files.empty());
+ VerifyDB({{"key4", value}});
+}
+
+TEST_F(BlobDBTest, FIFOEviction_NoOldestFileToEvict) {
+ Options options;
+ BlobDBOptions bdb_options;
+ bdb_options.max_db_size = 1000;
+ bdb_options.blob_file_size = 5000;
+ bdb_options.is_fifo = true;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+
+ std::atomic<int> evict_count{0};
+ SyncPoint::GetInstance()->SetCallBack(
+ "BlobDBImpl::EvictOldestBlobFile:Evicted",
+ [&](void *) { evict_count++; });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ std::string value(2000, 'v');
+ ASSERT_TRUE(Put("foo", std::string(2000, 'v')).IsNoSpace());
+ ASSERT_EQ(0, evict_count);
+}
+
+TEST_F(BlobDBTest, FIFOEviction_NoEnoughBlobFilesToEvict) {
+ BlobDBOptions bdb_options;
+ bdb_options.is_fifo = true;
+ bdb_options.min_blob_size = 100;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ // Use mock env to stop wall clock.
+ options.env = mock_env_.get();
+ options.disable_auto_compactions = true;
+ auto statistics = CreateDBStatistics();
+ options.statistics = statistics;
+ Open(bdb_options, options);
+
+ ASSERT_EQ(0, blob_db_impl()->TEST_live_sst_size());
+ std::string small_value(50, 'v');
+ std::map<std::string, std::string> data;
+ // Insert some data into LSM tree to make sure FIFO eviction take SST
+ // file size into account.
+ for (int i = 0; i < 1000; i++) {
+ ASSERT_OK(Put("key" + ToString(i), small_value, &data));
+ }
+ ASSERT_OK(blob_db_->Flush(FlushOptions()));
+ uint64_t live_sst_size = 0;
+ ASSERT_TRUE(blob_db_->GetIntProperty(DB::Properties::kTotalSstFilesSize,
+ &live_sst_size));
+ ASSERT_TRUE(live_sst_size > 0);
+ ASSERT_EQ(live_sst_size, blob_db_impl()->TEST_live_sst_size());
+
+ bdb_options.max_db_size = live_sst_size + 2000;
+ Reopen(bdb_options, options);
+ ASSERT_EQ(live_sst_size, blob_db_impl()->TEST_live_sst_size());
+
+ std::string value_1k(1000, 'v');
+ ASSERT_OK(PutWithTTL("large_key1", value_1k, 60, &data));
+ ASSERT_EQ(0, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ VerifyDB(data);
+ // large_key2 evicts large_key1
+ ASSERT_OK(PutWithTTL("large_key2", value_1k, 60, &data));
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ data.erase("large_key1");
+ VerifyDB(data);
+ // large_key3 get no enough space even after evicting large_key2, so it
+ // instead return no space error.
+ std::string value_2k(2000, 'v');
+ ASSERT_TRUE(PutWithTTL("large_key3", value_2k, 60).IsNoSpace());
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ // Verify large_key2 still exists.
+ VerifyDB(data);
+}
+
+// Test flush or compaction will trigger FIFO eviction since they update
+// total SST file size.
+TEST_F(BlobDBTest, FIFOEviction_TriggerOnSSTSizeChange) {
+ BlobDBOptions bdb_options;
+ bdb_options.max_db_size = 1000;
+ bdb_options.is_fifo = true;
+ bdb_options.min_blob_size = 100;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ // Use mock env to stop wall clock.
+ options.env = mock_env_.get();
+ auto statistics = CreateDBStatistics();
+ options.statistics = statistics;
+ options.compression = kNoCompression;
+ Open(bdb_options, options);
+
+ std::string value(800, 'v');
+ ASSERT_OK(PutWithTTL("large_key", value, 60));
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(0, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ VerifyDB({{"large_key", value}});
+
+ // Insert some small keys and flush to bring DB out of space.
+ std::map<std::string, std::string> data;
+ for (int i = 0; i < 10; i++) {
+ ASSERT_OK(Put("key" + ToString(i), "v", &data));
+ }
+ ASSERT_OK(blob_db_->Flush(FlushOptions()));
+
+ // Verify large_key is deleted by FIFO eviction.
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_EQ(0, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ VerifyDB(data);
+}
+
+TEST_F(BlobDBTest, InlineSmallValues) {
+ constexpr uint64_t kMaxExpiration = 1000;
+ Random rnd(301);
+ BlobDBOptions bdb_options;
+ bdb_options.ttl_range_secs = kMaxExpiration;
+ bdb_options.min_blob_size = 100;
+ bdb_options.blob_file_size = 256 * 1000 * 1000;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ options.env = mock_env_.get();
+ mock_env_->set_current_time(0);
+ Open(bdb_options, options);
+ std::map<std::string, std::string> data;
+ std::map<std::string, KeyVersion> versions;
+ for (size_t i = 0; i < 1000; i++) {
+ bool is_small_value = rnd.Next() % 2;
+ bool has_ttl = rnd.Next() % 2;
+ uint64_t expiration = rnd.Next() % kMaxExpiration;
+ int len = is_small_value ? 50 : 200;
+ std::string key = "key" + ToString(i);
+ std::string value = test::RandomHumanReadableString(&rnd, len);
+ std::string blob_index;
+ data[key] = value;
+ SequenceNumber sequence = blob_db_->GetLatestSequenceNumber() + 1;
+ if (!has_ttl) {
+ ASSERT_OK(blob_db_->Put(WriteOptions(), key, value));
+ } else {
+ ASSERT_OK(blob_db_->PutUntil(WriteOptions(), key, value, expiration));
+ }
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(), sequence);
+ versions[key] =
+ KeyVersion(key, value, sequence,
+ (is_small_value && !has_ttl) ? kTypeValue : kTypeBlobIndex);
+ }
+ VerifyDB(data);
+ VerifyBaseDB(versions);
+ auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
+ auto blob_files = bdb_impl->TEST_GetBlobFiles();
+ ASSERT_EQ(2, blob_files.size());
+ std::shared_ptr<BlobFile> non_ttl_file;
+ std::shared_ptr<BlobFile> ttl_file;
+ if (blob_files[0]->HasTTL()) {
+ ttl_file = blob_files[0];
+ non_ttl_file = blob_files[1];
+ } else {
+ non_ttl_file = blob_files[0];
+ ttl_file = blob_files[1];
+ }
+ ASSERT_FALSE(non_ttl_file->HasTTL());
+ ASSERT_TRUE(ttl_file->HasTTL());
+}
+
+TEST_F(BlobDBTest, CompactionFilterNotSupported) {
+ 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<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context & /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new TestCompactionFilter());
+ }
+ };
+ for (int i = 0; i < 2; i++) {
+ Options options;
+ if (i == 0) {
+ options.compaction_filter = new TestCompactionFilter();
+ } else {
+ options.compaction_filter_factory.reset(
+ new TestCompactionFilterFactory());
+ }
+ ASSERT_TRUE(TryOpen(BlobDBOptions(), options).IsNotSupported());
+ delete options.compaction_filter;
+ }
+}
+
+// Test comapction filter should remove any expired blob index.
+TEST_F(BlobDBTest, FilterExpiredBlobIndex) {
+ constexpr size_t kNumKeys = 100;
+ constexpr size_t kNumPuts = 1000;
+ constexpr uint64_t kMaxExpiration = 1000;
+ constexpr uint64_t kCompactTime = 500;
+ constexpr uint64_t kMinBlobSize = 100;
+ Random rnd(301);
+ mock_env_->set_current_time(0);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = kMinBlobSize;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ options.env = mock_env_.get();
+ Open(bdb_options, options);
+
+ std::map<std::string, std::string> data;
+ std::map<std::string, std::string> data_after_compact;
+ for (size_t i = 0; i < kNumPuts; i++) {
+ bool is_small_value = rnd.Next() % 2;
+ bool has_ttl = rnd.Next() % 2;
+ uint64_t expiration = rnd.Next() % kMaxExpiration;
+ int len = is_small_value ? 10 : 200;
+ std::string key = "key" + ToString(rnd.Next() % kNumKeys);
+ std::string value = test::RandomHumanReadableString(&rnd, len);
+ if (!has_ttl) {
+ if (is_small_value) {
+ std::string blob_entry;
+ BlobIndex::EncodeInlinedTTL(&blob_entry, expiration, value);
+ // Fake blob index with TTL. See what it will do.
+ ASSERT_GT(kMinBlobSize, blob_entry.size());
+ value = blob_entry;
+ }
+ ASSERT_OK(Put(key, value));
+ data_after_compact[key] = value;
+ } else {
+ ASSERT_OK(PutUntil(key, value, expiration));
+ if (expiration <= kCompactTime) {
+ data_after_compact.erase(key);
+ } else {
+ data_after_compact[key] = value;
+ }
+ }
+ data[key] = value;
+ }
+ VerifyDB(data);
+
+ mock_env_->set_current_time(kCompactTime);
+ // Take a snapshot before compaction. Make sure expired blob indexes is
+ // filtered regardless of snapshot.
+ const Snapshot *snapshot = blob_db_->GetSnapshot();
+ // Issue manual compaction to trigger compaction filter.
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ blob_db_->ReleaseSnapshot(snapshot);
+ // Verify expired blob index are filtered.
+ std::vector<KeyVersion> versions;
+ const size_t kMaxKeys = 10000;
+ GetAllKeyVersions(blob_db_, "", "", kMaxKeys, &versions);
+ ASSERT_EQ(data_after_compact.size(), versions.size());
+ for (auto &version : versions) {
+ ASSERT_TRUE(data_after_compact.count(version.user_key) > 0);
+ }
+ VerifyDB(data_after_compact);
+}
+
+// Test compaction filter should remove any blob index where corresponding
+// blob file has been removed.
+TEST_F(BlobDBTest, FilterFileNotAvailable) {
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ options.disable_auto_compactions = true;
+ Open(bdb_options, options);
+
+ ASSERT_OK(Put("foo", "v1"));
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ ASSERT_EQ(1, blob_files[0]->BlobFileNumber());
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
+
+ ASSERT_OK(Put("bar", "v2"));
+ blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(2, blob_files.size());
+ ASSERT_EQ(2, blob_files[1]->BlobFileNumber());
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[1]));
+
+ const size_t kMaxKeys = 10000;
+
+ DB *base_db = blob_db_->GetRootDB();
+ std::vector<KeyVersion> versions;
+ ASSERT_OK(GetAllKeyVersions(base_db, "", "", kMaxKeys, &versions));
+ ASSERT_EQ(2, versions.size());
+ ASSERT_EQ("bar", versions[0].user_key);
+ ASSERT_EQ("foo", versions[1].user_key);
+ VerifyDB({{"bar", "v2"}, {"foo", "v1"}});
+
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_OK(GetAllKeyVersions(base_db, "", "", kMaxKeys, &versions));
+ ASSERT_EQ(2, versions.size());
+ ASSERT_EQ("bar", versions[0].user_key);
+ ASSERT_EQ("foo", versions[1].user_key);
+ VerifyDB({{"bar", "v2"}, {"foo", "v1"}});
+
+ // Remove the first blob file and compact. foo should be remove from base db.
+ blob_db_impl()->TEST_ObsoleteBlobFile(blob_files[0]);
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_OK(GetAllKeyVersions(base_db, "", "", kMaxKeys, &versions));
+ ASSERT_EQ(1, versions.size());
+ ASSERT_EQ("bar", versions[0].user_key);
+ VerifyDB({{"bar", "v2"}});
+
+ // Remove the second blob file and compact. bar should be remove from base db.
+ blob_db_impl()->TEST_ObsoleteBlobFile(blob_files[1]);
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ ASSERT_OK(GetAllKeyVersions(base_db, "", "", kMaxKeys, &versions));
+ ASSERT_EQ(0, versions.size());
+ VerifyDB({});
+}
+
+// Test compaction filter should filter any inlined TTL keys that would have
+// been dropped by last FIFO eviction if they are store out-of-line.
+TEST_F(BlobDBTest, FilterForFIFOEviction) {
+ Random rnd(215);
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 100;
+ bdb_options.ttl_range_secs = 60;
+ bdb_options.max_db_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ // Use mock env to stop wall clock.
+ mock_env_->set_current_time(0);
+ options.env = mock_env_.get();
+ auto statistics = CreateDBStatistics();
+ options.statistics = statistics;
+ options.disable_auto_compactions = true;
+ Open(bdb_options, options);
+
+ std::map<std::string, std::string> data;
+ std::map<std::string, std::string> data_after_compact;
+ // Insert some small values that will be inlined.
+ for (int i = 0; i < 1000; i++) {
+ std::string key = "key" + ToString(i);
+ std::string value = test::RandomHumanReadableString(&rnd, 50);
+ uint64_t ttl = rnd.Next() % 120 + 1;
+ ASSERT_OK(PutWithTTL(key, value, ttl, &data));
+ if (ttl >= 60) {
+ data_after_compact[key] = value;
+ }
+ }
+ uint64_t num_keys_to_evict = data.size() - data_after_compact.size();
+ ASSERT_OK(blob_db_->Flush(FlushOptions()));
+ uint64_t live_sst_size = blob_db_impl()->TEST_live_sst_size();
+ ASSERT_GT(live_sst_size, 0);
+ VerifyDB(data);
+
+ bdb_options.max_db_size = live_sst_size + 30000;
+ bdb_options.is_fifo = true;
+ Reopen(bdb_options, options);
+ VerifyDB(data);
+
+ // Put two large values, each on a different blob file.
+ std::string large_value(10000, 'v');
+ ASSERT_OK(PutWithTTL("large_key1", large_value, 90));
+ ASSERT_OK(PutWithTTL("large_key2", large_value, 150));
+ ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(0, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ data["large_key1"] = large_value;
+ data["large_key2"] = large_value;
+ VerifyDB(data);
+
+ // Put a third large value which will bring the DB out of space.
+ // FIFO eviction will evict the file of large_key1.
+ ASSERT_OK(PutWithTTL("large_key3", large_value, 150));
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ data.erase("large_key1");
+ data["large_key3"] = large_value;
+ VerifyDB(data);
+
+ // Putting some more small values. These values shouldn't be evicted by
+ // compaction filter since they are inserted after FIFO eviction.
+ ASSERT_OK(PutWithTTL("foo", "v", 30, &data_after_compact));
+ ASSERT_OK(PutWithTTL("bar", "v", 30, &data_after_compact));
+
+ // FIFO eviction doesn't trigger again since there enough room for the flush.
+ ASSERT_OK(blob_db_->Flush(FlushOptions()));
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+
+ // Manual compact and check if compaction filter evict those keys with
+ // expiration < 60.
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ // All keys with expiration < 60, plus large_key1 is filtered by
+ // compaction filter.
+ ASSERT_EQ(num_keys_to_evict + 1,
+ statistics->getTickerCount(BLOB_DB_BLOB_INDEX_EVICTED_COUNT));
+ ASSERT_EQ(1, statistics->getTickerCount(BLOB_DB_FIFO_NUM_FILES_EVICTED));
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ data_after_compact["large_key2"] = large_value;
+ data_after_compact["large_key3"] = large_value;
+ VerifyDB(data_after_compact);
+}
+
+TEST_F(BlobDBTest, GarbageCollection) {
+ constexpr size_t kNumPuts = 1 << 10;
+
+ constexpr uint64_t kExpiration = 1000;
+ constexpr uint64_t kCompactTime = 500;
+
+ constexpr uint64_t kKeySize = 7; // "key" + 4 digits
+
+ constexpr uint64_t kSmallValueSize = 1 << 6;
+ constexpr uint64_t kLargeValueSize = 1 << 8;
+ constexpr uint64_t kMinBlobSize = 1 << 7;
+ static_assert(kSmallValueSize < kMinBlobSize, "");
+ static_assert(kLargeValueSize > kMinBlobSize, "");
+
+ constexpr size_t kBlobsPerFile = 8;
+ constexpr size_t kNumBlobFiles = kNumPuts / kBlobsPerFile;
+ constexpr uint64_t kBlobFileSize =
+ BlobLogHeader::kSize +
+ (BlobLogRecord::kHeaderSize + kKeySize + kLargeValueSize) * kBlobsPerFile;
+
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = kMinBlobSize;
+ bdb_options.blob_file_size = kBlobFileSize;
+ bdb_options.enable_garbage_collection = true;
+ bdb_options.garbage_collection_cutoff = 0.25;
+ bdb_options.disable_background_tasks = true;
+
+ Options options;
+ options.env = mock_env_.get();
+ options.statistics = CreateDBStatistics();
+
+ Open(bdb_options, options);
+
+ std::map<std::string, std::string> data;
+ std::map<std::string, KeyVersion> blob_value_versions;
+ std::map<std::string, BlobIndexVersion> blob_index_versions;
+
+ Random rnd(301);
+
+ // Add a bunch of large non-TTL values. These will be written to non-TTL
+ // blob files and will be subject to GC.
+ for (size_t i = 0; i < kNumPuts; ++i) {
+ std::ostringstream oss;
+ oss << "key" << std::setw(4) << std::setfill('0') << i;
+
+ const std::string key(oss.str());
+ const std::string value(
+ test::RandomHumanReadableString(&rnd, kLargeValueSize));
+ const SequenceNumber sequence = blob_db_->GetLatestSequenceNumber() + 1;
+
+ ASSERT_OK(Put(key, value));
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(), sequence);
+
+ data[key] = value;
+ blob_value_versions[key] = KeyVersion(key, value, sequence, kTypeBlobIndex);
+ blob_index_versions[key] =
+ BlobIndexVersion(key, /* file_number */ (i >> 3) + 1, kNoExpiration,
+ sequence, kTypeBlobIndex);
+ }
+
+ // Add some small and/or TTL values that will be ignored during GC.
+ // First, add a large TTL value will be written to its own TTL blob file.
+ {
+ const std::string key("key2000");
+ const std::string value(
+ test::RandomHumanReadableString(&rnd, kLargeValueSize));
+ const SequenceNumber sequence = blob_db_->GetLatestSequenceNumber() + 1;
+
+ ASSERT_OK(PutUntil(key, value, kExpiration));
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(), sequence);
+
+ data[key] = value;
+ blob_value_versions[key] = KeyVersion(key, value, sequence, kTypeBlobIndex);
+ blob_index_versions[key] =
+ BlobIndexVersion(key, /* file_number */ kNumBlobFiles + 1, kExpiration,
+ sequence, kTypeBlobIndex);
+ }
+
+ // Now add a small TTL value (which will be inlined).
+ {
+ const std::string key("key3000");
+ const std::string value(
+ test::RandomHumanReadableString(&rnd, kSmallValueSize));
+ const SequenceNumber sequence = blob_db_->GetLatestSequenceNumber() + 1;
+
+ ASSERT_OK(PutUntil(key, value, kExpiration));
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(), sequence);
+
+ data[key] = value;
+ blob_value_versions[key] = KeyVersion(key, value, sequence, kTypeBlobIndex);
+ blob_index_versions[key] = BlobIndexVersion(
+ key, kInvalidBlobFileNumber, kExpiration, sequence, kTypeBlobIndex);
+ }
+
+ // Finally, add a small non-TTL value (which will be stored as a regular
+ // value).
+ {
+ const std::string key("key4000");
+ const std::string value(
+ test::RandomHumanReadableString(&rnd, kSmallValueSize));
+ const SequenceNumber sequence = blob_db_->GetLatestSequenceNumber() + 1;
+
+ ASSERT_OK(Put(key, value));
+ ASSERT_EQ(blob_db_->GetLatestSequenceNumber(), sequence);
+
+ data[key] = value;
+ blob_value_versions[key] = KeyVersion(key, value, sequence, kTypeValue);
+ blob_index_versions[key] = BlobIndexVersion(
+ key, kInvalidBlobFileNumber, kNoExpiration, sequence, kTypeValue);
+ }
+
+ VerifyDB(data);
+ VerifyBaseDB(blob_value_versions);
+ VerifyBaseDBBlobIndex(blob_index_versions);
+
+ // At this point, we should have 128 immutable non-TTL files with file numbers
+ // 1..128.
+ {
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), kNumBlobFiles);
+ for (size_t i = 0; i < kNumBlobFiles; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 1);
+ ASSERT_EQ(live_imm_files[i]->GetFileSize(),
+ kBlobFileSize + BlobLogFooter::kSize);
+ }
+ }
+
+ mock_env_->set_current_time(kCompactTime);
+
+ ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+
+ // We expect the data to remain the same and the blobs from the oldest N files
+ // to be moved to new files. Sequence numbers get zeroed out during the
+ // compaction.
+ VerifyDB(data);
+
+ for (auto &pair : blob_value_versions) {
+ KeyVersion &version = pair.second;
+ version.sequence = 0;
+ }
+
+ VerifyBaseDB(blob_value_versions);
+
+ const uint64_t cutoff = static_cast<uint64_t>(
+ bdb_options.garbage_collection_cutoff * kNumBlobFiles);
+ for (auto &pair : blob_index_versions) {
+ BlobIndexVersion &version = pair.second;
+
+ version.sequence = 0;
+
+ if (version.file_number == kInvalidBlobFileNumber) {
+ continue;
+ }
+
+ if (version.file_number > cutoff) {
+ continue;
+ }
+
+ version.file_number += kNumBlobFiles + 1;
+ }
+
+ VerifyBaseDBBlobIndex(blob_index_versions);
+
+ const Statistics *const statistics = options.statistics.get();
+ assert(statistics);
+
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_FILES), cutoff);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_NEW_FILES), cutoff);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_FAILURES), 0);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_KEYS_RELOCATED),
+ cutoff * kBlobsPerFile);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_BYTES_RELOCATED),
+ cutoff * kBlobsPerFile * kLargeValueSize);
+
+ // At this point, we should have 128 immutable non-TTL files with file numbers
+ // 33..128 and 130..161. (129 was taken by the TTL blob file.)
+ {
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), kNumBlobFiles);
+ for (size_t i = 0; i < kNumBlobFiles; ++i) {
+ uint64_t expected_file_number = i + cutoff + 1;
+ if (expected_file_number > kNumBlobFiles) {
+ ++expected_file_number;
+ }
+
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), expected_file_number);
+ ASSERT_EQ(live_imm_files[i]->GetFileSize(),
+ kBlobFileSize + BlobLogFooter::kSize);
+ }
+ }
+}
+
+TEST_F(BlobDBTest, GarbageCollectionFailure) {
+ BlobDBOptions bdb_options;
+ bdb_options.min_blob_size = 0;
+ bdb_options.enable_garbage_collection = true;
+ bdb_options.garbage_collection_cutoff = 1.0;
+ bdb_options.disable_background_tasks = true;
+
+ Options db_options;
+ db_options.statistics = CreateDBStatistics();
+
+ Open(bdb_options, db_options);
+
+ // Write a couple of valid blobs.
+ Put("foo", "bar");
+ Put("dead", "beef");
+
+ // Write a fake blob reference into the base DB that cannot be parsed.
+ WriteBatch batch;
+ ASSERT_OK(WriteBatchInternal::PutBlobIndex(
+ &batch, blob_db_->DefaultColumnFamily()->GetID(), "key",
+ "not a valid blob index"));
+ ASSERT_OK(blob_db_->GetRootDB()->Write(WriteOptions(), &batch));
+
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(blob_files.size(), 1);
+ auto blob_file = blob_files[0];
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_file));
+
+ ASSERT_TRUE(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)
+ .IsCorruption());
+
+ const Statistics *const statistics = db_options.statistics.get();
+ assert(statistics);
+
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_FILES), 0);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_NEW_FILES), 1);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_FAILURES), 1);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_NUM_KEYS_RELOCATED), 2);
+ ASSERT_EQ(statistics->getTickerCount(BLOB_DB_GC_BYTES_RELOCATED), 7);
+}
+
+// File should be evicted after expiration.
+TEST_F(BlobDBTest, EvictExpiredFile) {
+ BlobDBOptions bdb_options;
+ bdb_options.ttl_range_secs = 100;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = true;
+ Options options;
+ options.env = mock_env_.get();
+ Open(bdb_options, options);
+ mock_env_->set_current_time(50);
+ std::map<std::string, std::string> data;
+ ASSERT_OK(PutWithTTL("foo", "bar", 100, &data));
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ auto blob_file = blob_files[0];
+ ASSERT_FALSE(blob_file->Immutable());
+ ASSERT_FALSE(blob_file->Obsolete());
+ VerifyDB(data);
+ mock_env_->set_current_time(250);
+ // The key should expired now.
+ blob_db_impl()->TEST_EvictExpiredFiles();
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ ASSERT_TRUE(blob_file->Immutable());
+ ASSERT_TRUE(blob_file->Obsolete());
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_EQ(0, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(0, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ // Make sure we don't return garbage value after blob file being evicted,
+ // but the blob index still exists in the LSM tree.
+ std::string val = "";
+ ASSERT_TRUE(blob_db_->Get(ReadOptions(), "foo", &val).IsNotFound());
+ ASSERT_EQ("", val);
+}
+
+TEST_F(BlobDBTest, DisableFileDeletions) {
+ BlobDBOptions bdb_options;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+ std::map<std::string, std::string> data;
+ for (bool force : {true, false}) {
+ ASSERT_OK(Put("foo", "v", &data));
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ auto blob_file = blob_files[0];
+ ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_file));
+ blob_db_impl()->TEST_ObsoleteBlobFile(blob_file);
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ // Call DisableFileDeletions twice.
+ ASSERT_OK(blob_db_->DisableFileDeletions());
+ ASSERT_OK(blob_db_->DisableFileDeletions());
+ // File deletions should be disabled.
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ VerifyDB(data);
+ // Enable file deletions once. If force=true, file deletion is enabled.
+ // Otherwise it needs to enable it for a second time.
+ ASSERT_OK(blob_db_->EnableFileDeletions(force));
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ if (!force) {
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(1, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ VerifyDB(data);
+ // Call EnableFileDeletions a second time.
+ ASSERT_OK(blob_db_->EnableFileDeletions(false));
+ blob_db_impl()->TEST_DeleteObsoleteFiles();
+ }
+ // Regardless of value of `force`, file should be deleted by now.
+ ASSERT_EQ(0, blob_db_impl()->TEST_GetBlobFiles().size());
+ ASSERT_EQ(0, blob_db_impl()->TEST_GetObsoleteFiles().size());
+ VerifyDB({});
+ }
+}
+
+TEST_F(BlobDBTest, MaintainBlobFileToSstMapping) {
+ BlobDBOptions bdb_options;
+ bdb_options.enable_garbage_collection = true;
+ bdb_options.disable_background_tasks = true;
+ Open(bdb_options);
+
+ // Register some dummy blob files.
+ blob_db_impl()->TEST_AddDummyBlobFile(1, /* immutable_sequence */ 200);
+ blob_db_impl()->TEST_AddDummyBlobFile(2, /* immutable_sequence */ 300);
+ blob_db_impl()->TEST_AddDummyBlobFile(3, /* immutable_sequence */ 400);
+ blob_db_impl()->TEST_AddDummyBlobFile(4, /* immutable_sequence */ 500);
+ blob_db_impl()->TEST_AddDummyBlobFile(5, /* immutable_sequence */ 600);
+
+ // Initialize the blob <-> SST file mapping. First, add some SST files with
+ // blob file references, then some without.
+ std::vector<LiveFileMetaData> live_files;
+
+ for (uint64_t i = 1; i <= 10; ++i) {
+ LiveFileMetaData live_file;
+ live_file.file_number = i;
+ live_file.oldest_blob_file_number = ((i - 1) % 5) + 1;
+
+ live_files.emplace_back(live_file);
+ }
+
+ for (uint64_t i = 11; i <= 20; ++i) {
+ LiveFileMetaData live_file;
+ live_file.file_number = i;
+
+ live_files.emplace_back(live_file);
+ }
+
+ blob_db_impl()->TEST_InitializeBlobFileToSstMapping(live_files);
+
+ // Check that the blob <-> SST mappings have been correctly initialized.
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+
+ ASSERT_EQ(blob_files.size(), 5);
+
+ {
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 5);
+ for (size_t i = 0; i < 5; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 1);
+ }
+
+ ASSERT_TRUE(blob_db_impl()->TEST_GetObsoleteFiles().empty());
+ }
+
+ {
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};
+ const std::vector<bool> expected_obsolete{false, false, false, false,
+ false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 5);
+ for (size_t i = 0; i < 5; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 1);
+ }
+
+ ASSERT_TRUE(blob_db_impl()->TEST_GetObsoleteFiles().empty());
+ }
+
+ // Simulate a flush where the SST does not reference any blob files.
+ {
+ FlushJobInfo info{};
+ info.file_number = 21;
+ info.smallest_seqno = 1;
+ info.largest_seqno = 100;
+
+ blob_db_impl()->TEST_ProcessFlushJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};
+ const std::vector<bool> expected_obsolete{false, false, false, false,
+ false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 5);
+ for (size_t i = 0; i < 5; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 1);
+ }
+
+ ASSERT_TRUE(blob_db_impl()->TEST_GetObsoleteFiles().empty());
+ }
+
+ // Simulate a flush where the SST references a blob file.
+ {
+ FlushJobInfo info{};
+ info.file_number = 22;
+ info.oldest_blob_file_number = 5;
+ info.smallest_seqno = 101;
+ info.largest_seqno = 200;
+
+ blob_db_impl()->TEST_ProcessFlushJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10, 22}};
+ const std::vector<bool> expected_obsolete{false, false, false, false,
+ false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 5);
+ for (size_t i = 0; i < 5; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 1);
+ }
+
+ ASSERT_TRUE(blob_db_impl()->TEST_GetObsoleteFiles().empty());
+ }
+
+ // Simulate a compaction. Some inputs and outputs have blob file references,
+ // some don't. There is also a trivial move (which means the SST appears on
+ // both the input and the output list). Blob file 1 loses all its linked SSTs,
+ // and since it got marked immutable at sequence number 200 which has already
+ // been flushed, it can be marked obsolete.
+ {
+ CompactionJobInfo info{};
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 1, 1});
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 2, 2});
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 6, 1});
+ info.input_file_infos.emplace_back(
+ CompactionFileInfo{1, 11, kInvalidBlobFileNumber});
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 22, 5});
+ info.output_file_infos.emplace_back(CompactionFileInfo{2, 22, 5});
+ info.output_file_infos.emplace_back(CompactionFileInfo{2, 23, 3});
+ info.output_file_infos.emplace_back(
+ CompactionFileInfo{2, 24, kInvalidBlobFileNumber});
+
+ blob_db_impl()->TEST_ProcessCompactionJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {}, {7}, {3, 8, 23}, {4, 9}, {5, 10, 22}};
+ const std::vector<bool> expected_obsolete{true, false, false, false, false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 4);
+ for (size_t i = 0; i < 4; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 2);
+ }
+
+ auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_EQ(obsolete_files.size(), 1);
+ ASSERT_EQ(obsolete_files[0]->BlobFileNumber(), 1);
+ }
+
+ // Simulate a failed compaction. No mappings should be updated.
+ {
+ CompactionJobInfo info{};
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 7, 2});
+ info.input_file_infos.emplace_back(CompactionFileInfo{2, 22, 5});
+ info.output_file_infos.emplace_back(CompactionFileInfo{2, 25, 3});
+ info.status = Status::Corruption();
+
+ blob_db_impl()->TEST_ProcessCompactionJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {}, {7}, {3, 8, 23}, {4, 9}, {5, 10, 22}};
+ const std::vector<bool> expected_obsolete{true, false, false, false, false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 4);
+ for (size_t i = 0; i < 4; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 2);
+ }
+
+ auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_EQ(obsolete_files.size(), 1);
+ ASSERT_EQ(obsolete_files[0]->BlobFileNumber(), 1);
+ }
+
+ // Simulate another compaction. Blob file 2 loses all its linked SSTs
+ // but since it got marked immutable at sequence number 300 which hasn't
+ // been flushed yet, it cannot be marked obsolete at this point.
+ {
+ CompactionJobInfo info{};
+ info.input_file_infos.emplace_back(CompactionFileInfo{1, 7, 2});
+ info.input_file_infos.emplace_back(CompactionFileInfo{2, 22, 5});
+ info.output_file_infos.emplace_back(CompactionFileInfo{2, 25, 3});
+
+ blob_db_impl()->TEST_ProcessCompactionJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {}, {}, {3, 8, 23, 25}, {4, 9}, {5, 10}};
+ const std::vector<bool> expected_obsolete{true, false, false, false, false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 4);
+ for (size_t i = 0; i < 4; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 2);
+ }
+
+ auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_EQ(obsolete_files.size(), 1);
+ ASSERT_EQ(obsolete_files[0]->BlobFileNumber(), 1);
+ }
+
+ // Simulate a flush with largest sequence number 300. This will make it
+ // possible to mark blob file 2 obsolete.
+ {
+ FlushJobInfo info{};
+ info.file_number = 26;
+ info.smallest_seqno = 201;
+ info.largest_seqno = 300;
+
+ blob_db_impl()->TEST_ProcessFlushJobInfo(info);
+
+ const std::vector<std::unordered_set<uint64_t>> expected_sst_files{
+ {}, {}, {3, 8, 23, 25}, {4, 9}, {5, 10}};
+ const std::vector<bool> expected_obsolete{true, true, false, false, false};
+ for (size_t i = 0; i < 5; ++i) {
+ const auto &blob_file = blob_files[i];
+ ASSERT_EQ(blob_file->GetLinkedSstFiles(), expected_sst_files[i]);
+ ASSERT_EQ(blob_file->Obsolete(), expected_obsolete[i]);
+ }
+
+ auto live_imm_files = blob_db_impl()->TEST_GetLiveImmNonTTLFiles();
+ ASSERT_EQ(live_imm_files.size(), 3);
+ for (size_t i = 0; i < 3; ++i) {
+ ASSERT_EQ(live_imm_files[i]->BlobFileNumber(), i + 3);
+ }
+
+ auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
+ ASSERT_EQ(obsolete_files.size(), 2);
+ ASSERT_EQ(obsolete_files[0]->BlobFileNumber(), 1);
+ ASSERT_EQ(obsolete_files[1]->BlobFileNumber(), 2);
+ }
+}
+
+TEST_F(BlobDBTest, ShutdownWait) {
+ BlobDBOptions bdb_options;
+ bdb_options.ttl_range_secs = 100;
+ bdb_options.min_blob_size = 0;
+ bdb_options.disable_background_tasks = false;
+ Options options;
+ options.env = mock_env_.get();
+
+ SyncPoint::GetInstance()->LoadDependency({
+ {"BlobDBImpl::EvictExpiredFiles:0", "BlobDBTest.ShutdownWait:0"},
+ {"BlobDBTest.ShutdownWait:1", "BlobDBImpl::EvictExpiredFiles:1"},
+ {"BlobDBImpl::EvictExpiredFiles:2", "BlobDBTest.ShutdownWait:2"},
+ {"BlobDBTest.ShutdownWait:3", "BlobDBImpl::EvictExpiredFiles:3"},
+ });
+ // Force all tasks to be scheduled immediately.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TimeQueue::Add:item.end", [&](void *arg) {
+ std::chrono::steady_clock::time_point *tp =
+ static_cast<std::chrono::steady_clock::time_point *>(arg);
+ *tp =
+ std::chrono::steady_clock::now() - std::chrono::milliseconds(10000);
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "BlobDBImpl::EvictExpiredFiles:cb", [&](void * /*arg*/) {
+ // Sleep 3 ms to increase the chance of data race.
+ // We've synced up the code so that EvictExpiredFiles()
+ // is called concurrently with ~BlobDBImpl().
+ // ~BlobDBImpl() is supposed to wait for all background
+ // task to shutdown before doing anything else. In order
+ // to use the same test to reproduce a bug of the waiting
+ // logic, we wait a little bit here, so that TSAN can
+ // catch the data race.
+ // We should improve the test if we find a better way.
+ Env::Default()->SleepForMicroseconds(3000);
+ });
+
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ Open(bdb_options, options);
+ mock_env_->set_current_time(50);
+ std::map<std::string, std::string> data;
+ ASSERT_OK(PutWithTTL("foo", "bar", 100, &data));
+ auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
+ ASSERT_EQ(1, blob_files.size());
+ auto blob_file = blob_files[0];
+ ASSERT_FALSE(blob_file->Immutable());
+ ASSERT_FALSE(blob_file->Obsolete());
+ VerifyDB(data);
+
+ TEST_SYNC_POINT("BlobDBTest.ShutdownWait:0");
+ mock_env_->set_current_time(250);
+ // The key should expired now.
+ TEST_SYNC_POINT("BlobDBTest.ShutdownWait:1");
+
+ TEST_SYNC_POINT("BlobDBTest.ShutdownWait:2");
+ TEST_SYNC_POINT("BlobDBTest.ShutdownWait:3");
+ Close();
+
+ SyncPoint::GetInstance()->DisableProcessing();
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+
+// A black-box test for the ttl wrapper around rocksdb
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as BlobDB is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.cc
new file mode 100644
index 00000000..58f26128
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_dump_tool.h"
+#include <stdio.h>
+#include <cinttypes>
+#include <iostream>
+#include <memory>
+#include <string>
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "file/readahead_raf.h"
+#include "port/port.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/env.h"
+#include "table/format.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+BlobDumpTool::BlobDumpTool()
+ : reader_(nullptr), buffer_(nullptr), buffer_size_(0) {}
+
+Status BlobDumpTool::Run(const std::string& filename, DisplayType show_key,
+ DisplayType show_blob,
+ DisplayType show_uncompressed_blob,
+ bool show_summary) {
+ constexpr size_t kReadaheadSize = 2 * 1024 * 1024;
+ Status s;
+ Env* env = Env::Default();
+ s = env->FileExists(filename);
+ if (!s.ok()) {
+ return s;
+ }
+ uint64_t file_size = 0;
+ s = env->GetFileSize(filename, &file_size);
+ if (!s.ok()) {
+ return s;
+ }
+ std::unique_ptr<RandomAccessFile> file;
+ s = env->NewRandomAccessFile(filename, &file, EnvOptions());
+ if (!s.ok()) {
+ return s;
+ }
+ file = NewReadaheadRandomAccessFile(std::move(file), kReadaheadSize);
+ if (file_size == 0) {
+ return Status::Corruption("File is empty.");
+ }
+ reader_.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(file), filename));
+ uint64_t offset = 0;
+ uint64_t footer_offset = 0;
+ CompressionType compression = kNoCompression;
+ s = DumpBlobLogHeader(&offset, &compression);
+ if (!s.ok()) {
+ return s;
+ }
+ s = DumpBlobLogFooter(file_size, &footer_offset);
+ if (!s.ok()) {
+ return s;
+ }
+ uint64_t total_records = 0;
+ uint64_t total_key_size = 0;
+ uint64_t total_blob_size = 0;
+ uint64_t total_uncompressed_blob_size = 0;
+ if (show_key != DisplayType::kNone || show_summary) {
+ while (offset < footer_offset) {
+ s = DumpRecord(show_key, show_blob, show_uncompressed_blob, show_summary,
+ compression, &offset, &total_records, &total_key_size,
+ &total_blob_size, &total_uncompressed_blob_size);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ if (show_summary) {
+ fprintf(stdout, "Summary:\n");
+ fprintf(stdout, " total records: %" PRIu64 "\n", total_records);
+ fprintf(stdout, " total key size: %" PRIu64 "\n", total_key_size);
+ fprintf(stdout, " total blob size: %" PRIu64 "\n", total_blob_size);
+ if (compression != kNoCompression) {
+ fprintf(stdout, " total raw blob size: %" PRIu64 "\n",
+ total_uncompressed_blob_size);
+ }
+ }
+ return s;
+}
+
+Status BlobDumpTool::Read(uint64_t offset, size_t size, Slice* result) {
+ if (buffer_size_ < size) {
+ if (buffer_size_ == 0) {
+ buffer_size_ = 4096;
+ }
+ while (buffer_size_ < size) {
+ buffer_size_ *= 2;
+ }
+ buffer_.reset(new char[buffer_size_]);
+ }
+ Status s = reader_->Read(offset, size, result, buffer_.get());
+ if (!s.ok()) {
+ return s;
+ }
+ if (result->size() != size) {
+ return Status::Corruption("Reach the end of the file unexpectedly.");
+ }
+ return s;
+}
+
+Status BlobDumpTool::DumpBlobLogHeader(uint64_t* offset,
+ CompressionType* compression) {
+ Slice slice;
+ Status s = Read(0, BlobLogHeader::kSize, &slice);
+ if (!s.ok()) {
+ return s;
+ }
+ BlobLogHeader header;
+ s = header.DecodeFrom(slice);
+ if (!s.ok()) {
+ return s;
+ }
+ fprintf(stdout, "Blob log header:\n");
+ fprintf(stdout, " Version : %" PRIu32 "\n", header.version);
+ fprintf(stdout, " Column Family ID : %" PRIu32 "\n",
+ header.column_family_id);
+ std::string compression_str;
+ if (!GetStringFromCompressionType(&compression_str, header.compression)
+ .ok()) {
+ compression_str = "Unrecongnized compression type (" +
+ ToString((int)header.compression) + ")";
+ }
+ fprintf(stdout, " Compression : %s\n", compression_str.c_str());
+ fprintf(stdout, " Expiration range : %s\n",
+ GetString(header.expiration_range).c_str());
+ *offset = BlobLogHeader::kSize;
+ *compression = header.compression;
+ return s;
+}
+
+Status BlobDumpTool::DumpBlobLogFooter(uint64_t file_size,
+ uint64_t* footer_offset) {
+ auto no_footer = [&]() {
+ *footer_offset = file_size;
+ fprintf(stdout, "No blob log footer.\n");
+ return Status::OK();
+ };
+ if (file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
+ return no_footer();
+ }
+ Slice slice;
+ *footer_offset = file_size - BlobLogFooter::kSize;
+ Status s = Read(*footer_offset, BlobLogFooter::kSize, &slice);
+ if (!s.ok()) {
+ return s;
+ }
+ BlobLogFooter footer;
+ s = footer.DecodeFrom(slice);
+ if (!s.ok()) {
+ return no_footer();
+ }
+ fprintf(stdout, "Blob log footer:\n");
+ fprintf(stdout, " Blob count : %" PRIu64 "\n", footer.blob_count);
+ fprintf(stdout, " Expiration Range : %s\n",
+ GetString(footer.expiration_range).c_str());
+ return s;
+}
+
+Status BlobDumpTool::DumpRecord(DisplayType show_key, DisplayType show_blob,
+ DisplayType show_uncompressed_blob,
+ bool show_summary, CompressionType compression,
+ uint64_t* offset, uint64_t* total_records,
+ uint64_t* total_key_size,
+ uint64_t* total_blob_size,
+ uint64_t* total_uncompressed_blob_size) {
+ if (show_key != DisplayType::kNone) {
+ fprintf(stdout, "Read record with offset 0x%" PRIx64 " (%" PRIu64 "):\n",
+ *offset, *offset);
+ }
+ Slice slice;
+ Status s = Read(*offset, BlobLogRecord::kHeaderSize, &slice);
+ if (!s.ok()) {
+ return s;
+ }
+ BlobLogRecord record;
+ s = record.DecodeHeaderFrom(slice);
+ if (!s.ok()) {
+ return s;
+ }
+ uint64_t key_size = record.key_size;
+ uint64_t value_size = record.value_size;
+ if (show_key != DisplayType::kNone) {
+ fprintf(stdout, " key size : %" PRIu64 "\n", key_size);
+ fprintf(stdout, " value size : %" PRIu64 "\n", value_size);
+ fprintf(stdout, " expiration : %" PRIu64 "\n", record.expiration);
+ }
+ *offset += BlobLogRecord::kHeaderSize;
+ s = Read(*offset, static_cast<size_t>(key_size + value_size), &slice);
+ if (!s.ok()) {
+ return s;
+ }
+ // Decompress value
+ std::string uncompressed_value;
+ if (compression != kNoCompression &&
+ (show_uncompressed_blob != DisplayType::kNone || show_summary)) {
+ BlockContents contents;
+ UncompressionContext context(compression);
+ UncompressionInfo info(context, UncompressionDict::GetEmptyDict(),
+ compression);
+ s = UncompressBlockContentsForCompressionType(
+ info, slice.data() + key_size, static_cast<size_t>(value_size),
+ &contents, 2 /*compress_format_version*/,
+ ImmutableCFOptions(Options()));
+ if (!s.ok()) {
+ return s;
+ }
+ uncompressed_value = contents.data.ToString();
+ }
+ if (show_key != DisplayType::kNone) {
+ fprintf(stdout, " key : ");
+ DumpSlice(Slice(slice.data(), static_cast<size_t>(key_size)), show_key);
+ if (show_blob != DisplayType::kNone) {
+ fprintf(stdout, " blob : ");
+ DumpSlice(Slice(slice.data() + static_cast<size_t>(key_size), static_cast<size_t>(value_size)), show_blob);
+ }
+ if (show_uncompressed_blob != DisplayType::kNone) {
+ fprintf(stdout, " raw blob : ");
+ DumpSlice(Slice(uncompressed_value), show_uncompressed_blob);
+ }
+ }
+ *offset += key_size + value_size;
+ *total_records += 1;
+ *total_key_size += key_size;
+ *total_blob_size += value_size;
+ *total_uncompressed_blob_size += uncompressed_value.size();
+ return s;
+}
+
+void BlobDumpTool::DumpSlice(const Slice s, DisplayType type) {
+ if (type == DisplayType::kRaw) {
+ fprintf(stdout, "%s\n", s.ToString().c_str());
+ } else if (type == DisplayType::kHex) {
+ fprintf(stdout, "%s\n", s.ToString(true /*hex*/).c_str());
+ } else if (type == DisplayType::kDetail) {
+ char buf[100];
+ for (size_t i = 0; i < s.size(); i += 16) {
+ memset(buf, 0, sizeof(buf));
+ for (size_t j = 0; j < 16 && i + j < s.size(); j++) {
+ unsigned char c = s[i + j];
+ snprintf(buf + j * 3 + 15, 2, "%x", c >> 4);
+ snprintf(buf + j * 3 + 16, 2, "%x", c & 0xf);
+ snprintf(buf + j + 65, 2, "%c", (0x20 <= c && c <= 0x7e) ? c : '.');
+ }
+ for (size_t p = 0; p < sizeof(buf) - 1; p++) {
+ if (buf[p] == 0) {
+ buf[p] = ' ';
+ }
+ }
+ fprintf(stdout, "%s\n", i == 0 ? buf + 15 : buf);
+ }
+ }
+}
+
+template <class T>
+std::string BlobDumpTool::GetString(std::pair<T, T> p) {
+ if (p.first == 0 && p.second == 0) {
+ return "nil";
+ }
+ return "(" + ToString(p.first) + ", " + ToString(p.second) + ")";
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.h
new file mode 100644
index 00000000..498543ad
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_dump_tool.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <memory>
+#include <string>
+#include <utility>
+#include "file/random_access_file_reader.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "utilities/blob_db/blob_log_format.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+class BlobDumpTool {
+ public:
+ enum class DisplayType {
+ kNone,
+ kRaw,
+ kHex,
+ kDetail,
+ };
+
+ BlobDumpTool();
+
+ Status Run(const std::string& filename, DisplayType show_key,
+ DisplayType show_blob, DisplayType show_uncompressed_blob,
+ bool show_summary);
+
+ private:
+ std::unique_ptr<RandomAccessFileReader> reader_;
+ std::unique_ptr<char[]> buffer_;
+ size_t buffer_size_;
+
+ Status Read(uint64_t offset, size_t size, Slice* result);
+ Status DumpBlobLogHeader(uint64_t* offset, CompressionType* compression);
+ Status DumpBlobLogFooter(uint64_t file_size, uint64_t* footer_offset);
+ Status DumpRecord(DisplayType show_key, DisplayType show_blob,
+ DisplayType show_uncompressed_blob, bool show_summary,
+ CompressionType compression, uint64_t* offset,
+ uint64_t* total_records, uint64_t* total_key_size,
+ uint64_t* total_blob_size,
+ uint64_t* total_uncompressed_blob_size);
+ void DumpSlice(const Slice s, DisplayType type);
+
+ template <class T>
+ std::string GetString(std::pair<T, T> p);
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.cc
new file mode 100644
index 00000000..f32e2952
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.cc
@@ -0,0 +1,320 @@
+
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "utilities/blob_db/blob_file.h"
+
+#include <stdio.h>
+#include <cinttypes>
+
+#include <algorithm>
+#include <memory>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "file/readahead_raf.h"
+#include "logging/logging.h"
+#include "utilities/blob_db/blob_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace blob_db {
+
+BlobFile::BlobFile(const BlobDBImpl* p, const std::string& bdir, uint64_t fn,
+ Logger* info_log)
+ : parent_(p), path_to_dir_(bdir), file_number_(fn), info_log_(info_log) {}
+
+BlobFile::BlobFile(const BlobDBImpl* p, const std::string& bdir, uint64_t fn,
+ Logger* info_log, uint32_t column_family_id,
+ CompressionType compression, bool has_ttl,
+ const ExpirationRange& expiration_range)
+ : parent_(p),
+ path_to_dir_(bdir),
+ file_number_(fn),
+ info_log_(info_log),
+ column_family_id_(column_family_id),
+ compression_(compression),
+ has_ttl_(has_ttl),
+ expiration_range_(expiration_range),
+ header_(column_family_id, compression, has_ttl, expiration_range),
+ header_valid_(true) {}
+
+BlobFile::~BlobFile() {
+ if (obsolete_) {
+ std::string pn(PathName());
+ Status s = Env::Default()->DeleteFile(PathName());
+ if (!s.ok()) {
+ // ROCKS_LOG_INFO(db_options_.info_log,
+ // "File could not be deleted %s", pn.c_str());
+ }
+ }
+}
+
+uint32_t BlobFile::GetColumnFamilyId() const { return column_family_id_; }
+
+std::string BlobFile::PathName() const {
+ return BlobFileName(path_to_dir_, file_number_);
+}
+
+std::shared_ptr<Reader> BlobFile::OpenRandomAccessReader(
+ Env* env, const DBOptions& db_options,
+ const EnvOptions& env_options) const {
+ constexpr size_t kReadaheadSize = 2 * 1024 * 1024;
+ std::unique_ptr<RandomAccessFile> sfile;
+ std::string path_name(PathName());
+ Status s = env->NewRandomAccessFile(path_name, &sfile, env_options);
+ if (!s.ok()) {
+ // report something here.
+ return nullptr;
+ }
+ sfile = NewReadaheadRandomAccessFile(std::move(sfile), kReadaheadSize);
+
+ std::unique_ptr<RandomAccessFileReader> sfile_reader;
+ sfile_reader.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(sfile), path_name));
+
+ std::shared_ptr<Reader> log_reader = std::make_shared<Reader>(
+ std::move(sfile_reader), db_options.env, db_options.statistics.get());
+
+ return log_reader;
+}
+
+std::string BlobFile::DumpState() const {
+ char str[1000];
+ snprintf(
+ str, sizeof(str),
+ "path: %s fn: %" PRIu64 " blob_count: %" PRIu64 " file_size: %" PRIu64
+ " closed: %d obsolete: %d expiration_range: (%" PRIu64 ", %" PRIu64
+ "), writer: %d reader: %d",
+ path_to_dir_.c_str(), file_number_, blob_count_.load(), file_size_.load(),
+ closed_.load(), obsolete_.load(), expiration_range_.first,
+ expiration_range_.second, (!!log_writer_), (!!ra_file_reader_));
+ return str;
+}
+
+void BlobFile::MarkObsolete(SequenceNumber sequence) {
+ assert(Immutable());
+ obsolete_sequence_ = sequence;
+ obsolete_.store(true);
+}
+
+bool BlobFile::NeedsFsync(bool hard, uint64_t bytes_per_sync) const {
+ assert(last_fsync_ <= file_size_);
+ return (hard) ? file_size_ > last_fsync_
+ : (file_size_ - last_fsync_) >= bytes_per_sync;
+}
+
+Status BlobFile::WriteFooterAndCloseLocked(SequenceNumber sequence) {
+ BlobLogFooter footer;
+ footer.blob_count = blob_count_;
+ if (HasTTL()) {
+ footer.expiration_range = expiration_range_;
+ }
+
+ // this will close the file and reset the Writable File Pointer.
+ Status s = log_writer_->AppendFooter(footer);
+ if (s.ok()) {
+ closed_ = true;
+ immutable_sequence_ = sequence;
+ file_size_ += BlobLogFooter::kSize;
+ }
+ // delete the sequential writer
+ log_writer_.reset();
+ return s;
+}
+
+Status BlobFile::ReadFooter(BlobLogFooter* bf) {
+ if (file_size_ < (BlobLogHeader::kSize + BlobLogFooter::kSize)) {
+ return Status::IOError("File does not have footer", PathName());
+ }
+
+ uint64_t footer_offset = file_size_ - BlobLogFooter::kSize;
+ // assume that ra_file_reader_ is valid before we enter this
+ assert(ra_file_reader_);
+
+ Slice result;
+ char scratch[BlobLogFooter::kSize + 10];
+ Status s = ra_file_reader_->Read(footer_offset, BlobLogFooter::kSize, &result,
+ scratch);
+ if (!s.ok()) return s;
+ if (result.size() != BlobLogFooter::kSize) {
+ // should not happen
+ return Status::IOError("EOF reached before footer");
+ }
+
+ s = bf->DecodeFrom(result);
+ return s;
+}
+
+Status BlobFile::SetFromFooterLocked(const BlobLogFooter& footer) {
+ // assume that file has been fully fsync'd
+ last_fsync_.store(file_size_);
+ blob_count_ = footer.blob_count;
+ expiration_range_ = footer.expiration_range;
+ closed_ = true;
+ return Status::OK();
+}
+
+Status BlobFile::Fsync() {
+ Status s;
+ if (log_writer_.get()) {
+ s = log_writer_->Sync();
+ last_fsync_.store(file_size_.load());
+ }
+ return s;
+}
+
+void BlobFile::CloseRandomAccessLocked() {
+ ra_file_reader_.reset();
+ last_access_ = -1;
+}
+
+Status BlobFile::GetReader(Env* env, const EnvOptions& env_options,
+ std::shared_ptr<RandomAccessFileReader>* reader,
+ bool* fresh_open) {
+ assert(reader != nullptr);
+ assert(fresh_open != nullptr);
+ *fresh_open = false;
+ int64_t current_time = 0;
+ env->GetCurrentTime(&current_time);
+ last_access_.store(current_time);
+ Status s;
+
+ {
+ ReadLock lockbfile_r(&mutex_);
+ if (ra_file_reader_) {
+ *reader = ra_file_reader_;
+ return s;
+ }
+ }
+
+ WriteLock lockbfile_w(&mutex_);
+ // Double check.
+ if (ra_file_reader_) {
+ *reader = ra_file_reader_;
+ return s;
+ }
+
+ std::unique_ptr<RandomAccessFile> rfile;
+ s = env->NewRandomAccessFile(PathName(), &rfile, env_options);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to open blob file for random-read: %s status: '%s'"
+ " exists: '%s'",
+ PathName().c_str(), s.ToString().c_str(),
+ env->FileExists(PathName()).ToString().c_str());
+ return s;
+ }
+
+ ra_file_reader_ = std::make_shared<RandomAccessFileReader>(
+ NewLegacyRandomAccessFileWrapper(rfile), PathName());
+ *reader = ra_file_reader_;
+ *fresh_open = true;
+ return s;
+}
+
+Status BlobFile::ReadMetadata(Env* env, const EnvOptions& env_options) {
+ assert(Immutable());
+ // Get file size.
+ uint64_t file_size = 0;
+ Status s = env->GetFileSize(PathName(), &file_size);
+ if (s.ok()) {
+ file_size_ = file_size;
+ } else {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to get size of blob file %" PRIu64
+ ", status: %s",
+ file_number_, s.ToString().c_str());
+ return s;
+ }
+ if (file_size < BlobLogHeader::kSize) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Incomplete blob file blob file %" PRIu64
+ ", size: %" PRIu64,
+ file_number_, file_size);
+ return Status::Corruption("Incomplete blob file header.");
+ }
+
+ // Create file reader.
+ std::unique_ptr<RandomAccessFile> file;
+ s = env->NewRandomAccessFile(PathName(), &file, env_options);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to open blob file %" PRIu64 ", status: %s",
+ file_number_, s.ToString().c_str());
+ return s;
+ }
+ std::unique_ptr<RandomAccessFileReader> file_reader(
+ new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
+ PathName()));
+
+ // Read file header.
+ char header_buf[BlobLogHeader::kSize];
+ Slice header_slice;
+ s = file_reader->Read(0, BlobLogHeader::kSize, &header_slice, header_buf);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to read header of blob file %" PRIu64
+ ", status: %s",
+ file_number_, s.ToString().c_str());
+ return s;
+ }
+ BlobLogHeader header;
+ s = header.DecodeFrom(header_slice);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to decode header of blob file %" PRIu64
+ ", status: %s",
+ file_number_, s.ToString().c_str());
+ return s;
+ }
+ column_family_id_ = header.column_family_id;
+ compression_ = header.compression;
+ has_ttl_ = header.has_ttl;
+ if (has_ttl_) {
+ expiration_range_ = header.expiration_range;
+ }
+ header_valid_ = true;
+
+ // Read file footer.
+ if (file_size_ < BlobLogHeader::kSize + BlobLogFooter::kSize) {
+ // OK not to have footer.
+ assert(!footer_valid_);
+ return Status::OK();
+ }
+ char footer_buf[BlobLogFooter::kSize];
+ Slice footer_slice;
+ s = file_reader->Read(file_size - BlobLogFooter::kSize, BlobLogFooter::kSize,
+ &footer_slice, footer_buf);
+ if (!s.ok()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "Failed to read footer of blob file %" PRIu64
+ ", status: %s",
+ file_number_, s.ToString().c_str());
+ return s;
+ }
+ BlobLogFooter footer;
+ s = footer.DecodeFrom(footer_slice);
+ if (!s.ok()) {
+ // OK not to have footer.
+ assert(!footer_valid_);
+ return Status::OK();
+ }
+ blob_count_ = footer.blob_count;
+ if (has_ttl_) {
+ assert(header.expiration_range.first <= footer.expiration_range.first);
+ assert(header.expiration_range.second >= footer.expiration_range.second);
+ expiration_range_ = footer.expiration_range;
+ }
+ footer_valid_ = true;
+ return Status::OK();
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.h
new file mode 100644
index 00000000..17d39b54
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_file.h
@@ -0,0 +1,252 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <atomic>
+#include <limits>
+#include <memory>
+#include <unordered_set>
+
+#include "file/random_access_file_reader.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "rocksdb/options.h"
+#include "utilities/blob_db/blob_log_format.h"
+#include "utilities/blob_db/blob_log_reader.h"
+#include "utilities/blob_db/blob_log_writer.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+class BlobDBImpl;
+
+class BlobFile {
+ friend class BlobDBImpl;
+ friend struct BlobFileComparator;
+ friend struct BlobFileComparatorTTL;
+ friend class BlobIndexCompactionFilterGC;
+
+ private:
+ // access to parent
+ const BlobDBImpl* parent_{nullptr};
+
+ // path to blob directory
+ std::string path_to_dir_;
+
+ // the id of the file.
+ // the above 2 are created during file creation and never changed
+ // after that
+ uint64_t file_number_{0};
+
+ // The file numbers of the SST files whose oldest blob file reference
+ // points to this blob file.
+ std::unordered_set<uint64_t> linked_sst_files_;
+
+ // Info log.
+ Logger* info_log_{nullptr};
+
+ // Column family id.
+ uint32_t column_family_id_{std::numeric_limits<uint32_t>::max()};
+
+ // Compression type of blobs in the file
+ CompressionType compression_{kNoCompression};
+
+ // If true, the keys in this file all has TTL. Otherwise all keys don't
+ // have TTL.
+ bool has_ttl_{false};
+
+ // TTL range of blobs in the file.
+ ExpirationRange expiration_range_;
+
+ // number of blobs in the file
+ std::atomic<uint64_t> blob_count_{0};
+
+ // size of the file
+ std::atomic<uint64_t> file_size_{0};
+
+ BlobLogHeader header_;
+
+ // closed_ = true implies the file is no more mutable
+ // no more blobs will be appended and the footer has been written out
+ std::atomic<bool> closed_{false};
+
+ // The latest sequence number when the file was closed/made immutable.
+ SequenceNumber immutable_sequence_{0};
+
+ // Whether the file was marked obsolete (due to either TTL or GC).
+ // obsolete_ still needs to do iterator/snapshot checks
+ std::atomic<bool> obsolete_{false};
+
+ // The last sequence number by the time the file marked as obsolete.
+ // Data in this file is visible to a snapshot taken before the sequence.
+ SequenceNumber obsolete_sequence_{0};
+
+ // Sequential/Append writer for blobs
+ std::shared_ptr<Writer> log_writer_;
+
+ // random access file reader for GET calls
+ std::shared_ptr<RandomAccessFileReader> ra_file_reader_;
+
+ // This Read-Write mutex is per file specific and protects
+ // all the datastructures
+ mutable port::RWMutex mutex_;
+
+ // time when the random access reader was last created.
+ std::atomic<std::int64_t> last_access_{-1};
+
+ // last time file was fsync'd/fdatasyncd
+ std::atomic<uint64_t> last_fsync_{0};
+
+ bool header_valid_{false};
+
+ bool footer_valid_{false};
+
+ public:
+ BlobFile() = default;
+
+ BlobFile(const BlobDBImpl* parent, const std::string& bdir, uint64_t fnum,
+ Logger* info_log);
+
+ BlobFile(const BlobDBImpl* parent, const std::string& bdir, uint64_t fnum,
+ Logger* info_log, uint32_t column_family_id,
+ CompressionType compression, bool has_ttl,
+ const ExpirationRange& expiration_range);
+
+ ~BlobFile();
+
+ uint32_t GetColumnFamilyId() const;
+
+ // Returns log file's absolute pathname.
+ std::string PathName() const;
+
+ // Primary identifier for blob file.
+ // once the file is created, this never changes
+ uint64_t BlobFileNumber() const { return file_number_; }
+
+ // Get the set of SST files whose oldest blob file reference points to
+ // this file.
+ const std::unordered_set<uint64_t>& GetLinkedSstFiles() const {
+ return linked_sst_files_;
+ }
+
+ // Link an SST file whose oldest blob file reference points to this file.
+ void LinkSstFile(uint64_t sst_file_number) {
+ assert(linked_sst_files_.find(sst_file_number) == linked_sst_files_.end());
+ linked_sst_files_.insert(sst_file_number);
+ }
+
+ // Unlink an SST file whose oldest blob file reference points to this file.
+ void UnlinkSstFile(uint64_t sst_file_number) {
+ auto it = linked_sst_files_.find(sst_file_number);
+ assert(it != linked_sst_files_.end());
+ linked_sst_files_.erase(it);
+ }
+
+ // the following functions are atomic, and don't need
+ // read lock
+ uint64_t BlobCount() const {
+ return blob_count_.load(std::memory_order_acquire);
+ }
+
+ std::string DumpState() const;
+
+ // if the file is not taking any more appends.
+ bool Immutable() const { return closed_.load(); }
+
+ // Mark the file as immutable.
+ // REQUIRES: write lock held, or access from single thread (on DB open).
+ void MarkImmutable(SequenceNumber sequence) {
+ closed_ = true;
+ immutable_sequence_ = sequence;
+ }
+
+ SequenceNumber GetImmutableSequence() const {
+ assert(Immutable());
+ return immutable_sequence_;
+ }
+
+ // Whether the file was marked obsolete (due to either TTL or GC).
+ bool Obsolete() const {
+ assert(Immutable() || !obsolete_.load());
+ return obsolete_.load();
+ }
+
+ // Mark file as obsolete (due to either TTL or GC). The file is not visible to
+ // snapshots with sequence greater or equal to the given sequence.
+ void MarkObsolete(SequenceNumber sequence);
+
+ SequenceNumber GetObsoleteSequence() const {
+ assert(Obsolete());
+ return obsolete_sequence_;
+ }
+
+ // we will assume this is atomic
+ bool NeedsFsync(bool hard, uint64_t bytes_per_sync) const;
+
+ Status Fsync();
+
+ uint64_t GetFileSize() const {
+ return file_size_.load(std::memory_order_acquire);
+ }
+
+ // All Get functions which are not atomic, will need ReadLock on the mutex
+
+ ExpirationRange GetExpirationRange() const { return expiration_range_; }
+
+ void ExtendExpirationRange(uint64_t expiration) {
+ expiration_range_.first = std::min(expiration_range_.first, expiration);
+ expiration_range_.second = std::max(expiration_range_.second, expiration);
+ }
+
+ bool HasTTL() const { return has_ttl_; }
+
+ void SetHasTTL(bool has_ttl) { has_ttl_ = has_ttl; }
+
+ CompressionType GetCompressionType() const { return compression_; }
+
+ std::shared_ptr<Writer> GetWriter() const { return log_writer_; }
+
+ // Read blob file header and footer. Return corruption if file header is
+ // malform or incomplete. If footer is malform or incomplete, set
+ // footer_valid_ to false and return Status::OK.
+ Status ReadMetadata(Env* env, const EnvOptions& env_options);
+
+ Status GetReader(Env* env, const EnvOptions& env_options,
+ std::shared_ptr<RandomAccessFileReader>* reader,
+ bool* fresh_open);
+
+ private:
+ std::shared_ptr<Reader> OpenRandomAccessReader(
+ Env* env, const DBOptions& db_options,
+ const EnvOptions& env_options) const;
+
+ Status ReadFooter(BlobLogFooter* footer);
+
+ Status WriteFooterAndCloseLocked(SequenceNumber sequence);
+
+ void CloseRandomAccessLocked();
+
+ // this is used, when you are reading only the footer of a
+ // previously closed file
+ Status SetFromFooterLocked(const BlobLogFooter& footer);
+
+ void set_expiration_range(const ExpirationRange& expiration_range) {
+ expiration_range_ = expiration_range;
+ }
+
+ // The following functions are atomic, and don't need locks
+ void SetFileSize(uint64_t fs) { file_size_ = fs; }
+
+ void SetBlobCount(uint64_t bc) { blob_count_ = bc; }
+
+ void BlobRecordAdded(uint64_t record_size) {
+ ++blob_count_;
+ file_size_ += record_size;
+ }
+};
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.cc
new file mode 100644
index 00000000..64894ca7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_log_format.h"
+
+#include "util/coding.h"
+#include "util/crc32c.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+void BlobLogHeader::EncodeTo(std::string* dst) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(BlobLogHeader::kSize);
+ PutFixed32(dst, kMagicNumber);
+ PutFixed32(dst, version);
+ PutFixed32(dst, column_family_id);
+ unsigned char flags = (has_ttl ? 1 : 0);
+ dst->push_back(flags);
+ dst->push_back(compression);
+ PutFixed64(dst, expiration_range.first);
+ PutFixed64(dst, expiration_range.second);
+}
+
+Status BlobLogHeader::DecodeFrom(Slice src) {
+ static const std::string kErrorMessage =
+ "Error while decoding blob log header";
+ if (src.size() != BlobLogHeader::kSize) {
+ return Status::Corruption(kErrorMessage,
+ "Unexpected blob file header size");
+ }
+ uint32_t magic_number;
+ unsigned char flags;
+ if (!GetFixed32(&src, &magic_number) || !GetFixed32(&src, &version) ||
+ !GetFixed32(&src, &column_family_id)) {
+ return Status::Corruption(
+ kErrorMessage,
+ "Error decoding magic number, version and column family id");
+ }
+ if (magic_number != kMagicNumber) {
+ return Status::Corruption(kErrorMessage, "Magic number mismatch");
+ }
+ if (version != kVersion1) {
+ return Status::Corruption(kErrorMessage, "Unknown header version");
+ }
+ flags = src.data()[0];
+ compression = static_cast<CompressionType>(src.data()[1]);
+ has_ttl = (flags & 1) == 1;
+ src.remove_prefix(2);
+ if (!GetFixed64(&src, &expiration_range.first) ||
+ !GetFixed64(&src, &expiration_range.second)) {
+ return Status::Corruption(kErrorMessage, "Error decoding expiration range");
+ }
+ return Status::OK();
+}
+
+void BlobLogFooter::EncodeTo(std::string* dst) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(BlobLogFooter::kSize);
+ PutFixed32(dst, kMagicNumber);
+ PutFixed64(dst, blob_count);
+ PutFixed64(dst, expiration_range.first);
+ PutFixed64(dst, expiration_range.second);
+ crc = crc32c::Value(dst->c_str(), dst->size());
+ crc = crc32c::Mask(crc);
+ PutFixed32(dst, crc);
+}
+
+Status BlobLogFooter::DecodeFrom(Slice src) {
+ static const std::string kErrorMessage =
+ "Error while decoding blob log footer";
+ if (src.size() != BlobLogFooter::kSize) {
+ return Status::Corruption(kErrorMessage,
+ "Unexpected blob file footer size");
+ }
+ uint32_t src_crc = 0;
+ src_crc = crc32c::Value(src.data(), BlobLogFooter::kSize - sizeof(uint32_t));
+ src_crc = crc32c::Mask(src_crc);
+ uint32_t magic_number = 0;
+ if (!GetFixed32(&src, &magic_number) || !GetFixed64(&src, &blob_count) ||
+ !GetFixed64(&src, &expiration_range.first) ||
+ !GetFixed64(&src, &expiration_range.second) || !GetFixed32(&src, &crc)) {
+ return Status::Corruption(kErrorMessage, "Error decoding content");
+ }
+ if (magic_number != kMagicNumber) {
+ return Status::Corruption(kErrorMessage, "Magic number mismatch");
+ }
+ if (src_crc != crc) {
+ return Status::Corruption(kErrorMessage, "CRC mismatch");
+ }
+ return Status::OK();
+}
+
+void BlobLogRecord::EncodeHeaderTo(std::string* dst) {
+ assert(dst != nullptr);
+ dst->clear();
+ dst->reserve(BlobLogRecord::kHeaderSize + key.size() + value.size());
+ PutFixed64(dst, key.size());
+ PutFixed64(dst, value.size());
+ PutFixed64(dst, expiration);
+ header_crc = crc32c::Value(dst->c_str(), dst->size());
+ header_crc = crc32c::Mask(header_crc);
+ PutFixed32(dst, header_crc);
+ blob_crc = crc32c::Value(key.data(), key.size());
+ blob_crc = crc32c::Extend(blob_crc, value.data(), value.size());
+ blob_crc = crc32c::Mask(blob_crc);
+ PutFixed32(dst, blob_crc);
+}
+
+Status BlobLogRecord::DecodeHeaderFrom(Slice src) {
+ static const std::string kErrorMessage = "Error while decoding blob record";
+ if (src.size() != BlobLogRecord::kHeaderSize) {
+ return Status::Corruption(kErrorMessage,
+ "Unexpected blob record header size");
+ }
+ uint32_t src_crc = 0;
+ src_crc = crc32c::Value(src.data(), BlobLogRecord::kHeaderSize - 8);
+ src_crc = crc32c::Mask(src_crc);
+ if (!GetFixed64(&src, &key_size) || !GetFixed64(&src, &value_size) ||
+ !GetFixed64(&src, &expiration) || !GetFixed32(&src, &header_crc) ||
+ !GetFixed32(&src, &blob_crc)) {
+ return Status::Corruption(kErrorMessage, "Error decoding content");
+ }
+ if (src_crc != header_crc) {
+ return Status::Corruption(kErrorMessage, "Header CRC mismatch");
+ }
+ return Status::OK();
+}
+
+Status BlobLogRecord::CheckBlobCRC() const {
+ uint32_t expected_crc = 0;
+ expected_crc = crc32c::Value(key.data(), key.size());
+ expected_crc = crc32c::Extend(expected_crc, value.data(), value.size());
+ expected_crc = crc32c::Mask(expected_crc);
+ if (expected_crc != blob_crc) {
+ return Status::Corruption("Blob CRC mismatch");
+ }
+ return Status::OK();
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.h
new file mode 100644
index 00000000..26cdf6e7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_format.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+// Log format information shared by reader and writer.
+
+#pragma once
+
+#ifndef ROCKSDB_LITE
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+constexpr uint32_t kMagicNumber = 2395959; // 0x00248f37
+constexpr uint32_t kVersion1 = 1;
+constexpr uint64_t kNoExpiration = std::numeric_limits<uint64_t>::max();
+
+using ExpirationRange = std::pair<uint64_t, uint64_t>;
+
+// Format of blob log file header (30 bytes):
+//
+// +--------------+---------+---------+-------+-------------+-------------------+
+// | magic number | version | cf id | flags | compression | expiration range |
+// +--------------+---------+---------+-------+-------------+-------------------+
+// | Fixed32 | Fixed32 | Fixed32 | char | char | Fixed64 Fixed64 |
+// +--------------+---------+---------+-------+-------------+-------------------+
+//
+// List of flags:
+// has_ttl: Whether the file contain TTL data.
+//
+// Expiration range in the header is a rough range based on
+// blob_db_options.ttl_range_secs.
+struct BlobLogHeader {
+ static constexpr size_t kSize = 30;
+
+ BlobLogHeader() = default;
+ BlobLogHeader(uint32_t _column_family_id, CompressionType _compression,
+ bool _has_ttl, const ExpirationRange& _expiration_range)
+ : column_family_id(_column_family_id),
+ compression(_compression),
+ has_ttl(_has_ttl),
+ expiration_range(_expiration_range) {}
+
+ uint32_t version = kVersion1;
+ uint32_t column_family_id = 0;
+ CompressionType compression = kNoCompression;
+ bool has_ttl = false;
+ ExpirationRange expiration_range;
+
+ void EncodeTo(std::string* dst);
+
+ Status DecodeFrom(Slice slice);
+};
+
+// Format of blob log file footer (32 bytes):
+//
+// +--------------+------------+-------------------+------------+
+// | magic number | blob count | expiration range | footer CRC |
+// +--------------+------------+-------------------+------------+
+// | Fixed32 | Fixed64 | Fixed64 + Fixed64 | Fixed32 |
+// +--------------+------------+-------------------+------------+
+//
+// The footer will be presented only when the blob file is properly closed.
+//
+// Unlike the same field in file header, expiration range in the footer is the
+// range of smallest and largest expiration of the data in this file.
+struct BlobLogFooter {
+ static constexpr size_t kSize = 32;
+
+ uint64_t blob_count = 0;
+ ExpirationRange expiration_range = std::make_pair(0, 0);
+ uint32_t crc = 0;
+
+ void EncodeTo(std::string* dst);
+
+ Status DecodeFrom(Slice slice);
+};
+
+// Blob record format (32 bytes header + key + value):
+//
+// +------------+--------------+------------+------------+----------+---------+-----------+
+// | key length | value length | expiration | header CRC | blob CRC | key | value |
+// +------------+--------------+------------+------------+----------+---------+-----------+
+// | Fixed64 | Fixed64 | Fixed64 | Fixed32 | Fixed32 | key len | value len |
+// +------------+--------------+------------+------------+----------+---------+-----------+
+//
+// If file has has_ttl = false, expiration field is always 0, and the blob
+// doesn't has expiration.
+//
+// Also note that if compression is used, value is compressed value and value
+// length is compressed value length.
+//
+// Header CRC is the checksum of (key_len + val_len + expiration), while
+// blob CRC is the checksum of (key + value).
+//
+// We could use variable length encoding (Varint64) to save more space, but it
+// make reader more complicated.
+struct BlobLogRecord {
+ // header include fields up to blob CRC
+ static constexpr size_t kHeaderSize = 32;
+
+ uint64_t key_size = 0;
+ uint64_t value_size = 0;
+ uint64_t expiration = 0;
+ uint32_t header_crc = 0;
+ uint32_t blob_crc = 0;
+ Slice key;
+ Slice value;
+ std::unique_ptr<char[]> key_buf;
+ std::unique_ptr<char[]> value_buf;
+
+ uint64_t record_size() const { return kHeaderSize + key_size + value_size; }
+
+ void EncodeHeaderTo(std::string* dst);
+
+ Status DecodeHeaderFrom(Slice src);
+
+ Status CheckBlobCRC() const;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.cc
new file mode 100644
index 00000000..1a4b5ac8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_log_reader.h"
+
+#include <algorithm>
+
+#include "file/random_access_file_reader.h"
+#include "monitoring/statistics.h"
+#include "util/stop_watch.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+Reader::Reader(std::unique_ptr<RandomAccessFileReader>&& file_reader, Env* env,
+ Statistics* statistics)
+ : file_(std::move(file_reader)),
+ env_(env),
+ statistics_(statistics),
+ buffer_(),
+ next_byte_(0) {}
+
+Status Reader::ReadSlice(uint64_t size, Slice* slice, char* buf) {
+ StopWatch read_sw(env_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
+ Status s = file_->Read(next_byte_, static_cast<size_t>(size), slice, buf);
+ next_byte_ += size;
+ if (!s.ok()) {
+ return s;
+ }
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, slice->size());
+ if (slice->size() != size) {
+ return Status::Corruption("EOF reached while reading record");
+ }
+ return s;
+}
+
+Status Reader::ReadHeader(BlobLogHeader* header) {
+ assert(file_.get() != nullptr);
+ assert(next_byte_ == 0);
+ Status s = ReadSlice(BlobLogHeader::kSize, &buffer_, header_buf_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (buffer_.size() != BlobLogHeader::kSize) {
+ return Status::Corruption("EOF reached before file header");
+ }
+
+ return header->DecodeFrom(buffer_);
+}
+
+Status Reader::ReadRecord(BlobLogRecord* record, ReadLevel level,
+ uint64_t* blob_offset) {
+ Status s = ReadSlice(BlobLogRecord::kHeaderSize, &buffer_, header_buf_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (buffer_.size() != BlobLogRecord::kHeaderSize) {
+ return Status::Corruption("EOF reached before record header");
+ }
+
+ s = record->DecodeHeaderFrom(buffer_);
+ if (!s.ok()) {
+ return s;
+ }
+
+ uint64_t kb_size = record->key_size + record->value_size;
+ if (blob_offset != nullptr) {
+ *blob_offset = next_byte_ + record->key_size;
+ }
+
+ switch (level) {
+ case kReadHeader:
+ next_byte_ += kb_size;
+ break;
+
+ case kReadHeaderKey:
+ record->key_buf.reset(new char[record->key_size]);
+ s = ReadSlice(record->key_size, &record->key, record->key_buf.get());
+ next_byte_ += record->value_size;
+ break;
+
+ case kReadHeaderKeyBlob:
+ record->key_buf.reset(new char[record->key_size]);
+ s = ReadSlice(record->key_size, &record->key, record->key_buf.get());
+ if (s.ok()) {
+ record->value_buf.reset(new char[record->value_size]);
+ s = ReadSlice(record->value_size, &record->value,
+ record->value_buf.get());
+ }
+ if (s.ok()) {
+ s = record->CheckBlobCRC();
+ }
+ break;
+ }
+ return s;
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.h
new file mode 100644
index 00000000..45fda284
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_reader.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <memory>
+#include <string>
+
+#include "file/random_access_file_reader.h"
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "utilities/blob_db/blob_log_format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SequentialFileReader;
+class Logger;
+
+namespace blob_db {
+
+/**
+ * Reader is a general purpose log stream reader implementation. The actual job
+ * of reading from the device is implemented by the SequentialFile interface.
+ *
+ * Please see Writer for details on the file and record layout.
+ */
+class Reader {
+ public:
+ enum ReadLevel {
+ kReadHeader,
+ kReadHeaderKey,
+ kReadHeaderKeyBlob,
+ };
+
+ // Create a reader that will return log records from "*file".
+ // "*file" must remain live while this Reader is in use.
+ Reader(std::unique_ptr<RandomAccessFileReader>&& file_reader, Env* env,
+ Statistics* statistics);
+ // No copying allowed
+ Reader(const Reader&) = delete;
+ Reader& operator=(const Reader&) = delete;
+
+ ~Reader() = default;
+
+ Status ReadHeader(BlobLogHeader* header);
+
+ // Read the next record into *record. Returns true if read
+ // successfully, false if we hit end of the input. May use
+ // "*scratch" as temporary storage. The contents filled in *record
+ // will only be valid until the next mutating operation on this
+ // reader or the next mutation to *scratch.
+ // If blob_offset is non-null, return offset of the blob through it.
+ Status ReadRecord(BlobLogRecord* record, ReadLevel level = kReadHeader,
+ uint64_t* blob_offset = nullptr);
+
+ void ResetNextByte() { next_byte_ = 0; }
+
+ uint64_t GetNextByte() const { return next_byte_; }
+
+ private:
+ Status ReadSlice(uint64_t size, Slice* slice, char* buf);
+
+ const std::unique_ptr<RandomAccessFileReader> file_;
+ Env* env_;
+ Statistics* statistics_;
+
+ Slice buffer_;
+ char header_buf_[BlobLogRecord::kHeaderSize];
+
+ // which byte to read next. For asserting proper usage
+ uint64_t next_byte_;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.cc b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.cc
new file mode 100644
index 00000000..2fe92263
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/blob_db/blob_log_writer.h"
+
+#include <cstdint>
+#include <string>
+
+#include "file/writable_file_writer.h"
+#include "monitoring/statistics.h"
+#include "rocksdb/env.h"
+#include "util/coding.h"
+#include "util/stop_watch.h"
+#include "utilities/blob_db/blob_log_format.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace blob_db {
+
+Writer::Writer(std::unique_ptr<WritableFileWriter>&& dest, Env* env,
+ Statistics* statistics, uint64_t log_number, uint64_t bpsync,
+ bool use_fs, uint64_t boffset)
+ : dest_(std::move(dest)),
+ env_(env),
+ statistics_(statistics),
+ log_number_(log_number),
+ block_offset_(boffset),
+ bytes_per_sync_(bpsync),
+ next_sync_offset_(0),
+ use_fsync_(use_fs),
+ last_elem_type_(kEtNone) {}
+
+Status Writer::Sync() {
+ StopWatch sync_sw(env_, statistics_, BLOB_DB_BLOB_FILE_SYNC_MICROS);
+ Status s = dest_->Sync(use_fsync_);
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_SYNCED);
+ return s;
+}
+
+Status Writer::WriteHeader(BlobLogHeader& header) {
+ assert(block_offset_ == 0);
+ assert(last_elem_type_ == kEtNone);
+ std::string str;
+ header.EncodeTo(&str);
+
+ Status s = dest_->Append(Slice(str));
+ if (s.ok()) {
+ block_offset_ += str.size();
+ s = dest_->Flush();
+ }
+ last_elem_type_ = kEtFileHdr;
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_WRITTEN,
+ BlobLogHeader::kSize);
+ return s;
+}
+
+Status Writer::AppendFooter(BlobLogFooter& footer) {
+ assert(block_offset_ != 0);
+ assert(last_elem_type_ == kEtFileHdr || last_elem_type_ == kEtRecord);
+
+ std::string str;
+ footer.EncodeTo(&str);
+
+ Status s = dest_->Append(Slice(str));
+ if (s.ok()) {
+ block_offset_ += str.size();
+ s = dest_->Close();
+ dest_.reset();
+ }
+
+ last_elem_type_ = kEtFileFooter;
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_WRITTEN,
+ BlobLogFooter::kSize);
+ return s;
+}
+
+Status Writer::AddRecord(const Slice& key, const Slice& val,
+ uint64_t expiration, uint64_t* key_offset,
+ uint64_t* blob_offset) {
+ assert(block_offset_ != 0);
+ assert(last_elem_type_ == kEtFileHdr || last_elem_type_ == kEtRecord);
+
+ std::string buf;
+ ConstructBlobHeader(&buf, key, val, expiration);
+
+ Status s = EmitPhysicalRecord(buf, key, val, key_offset, blob_offset);
+ return s;
+}
+
+Status Writer::AddRecord(const Slice& key, const Slice& val,
+ uint64_t* key_offset, uint64_t* blob_offset) {
+ assert(block_offset_ != 0);
+ assert(last_elem_type_ == kEtFileHdr || last_elem_type_ == kEtRecord);
+
+ std::string buf;
+ ConstructBlobHeader(&buf, key, val, 0);
+
+ Status s = EmitPhysicalRecord(buf, key, val, key_offset, blob_offset);
+ return s;
+}
+
+void Writer::ConstructBlobHeader(std::string* buf, const Slice& key,
+ const Slice& val, uint64_t expiration) {
+ BlobLogRecord record;
+ record.key = key;
+ record.value = val;
+ record.expiration = expiration;
+ record.EncodeHeaderTo(buf);
+}
+
+Status Writer::EmitPhysicalRecord(const std::string& headerbuf,
+ const Slice& key, const Slice& val,
+ uint64_t* key_offset, uint64_t* blob_offset) {
+ StopWatch write_sw(env_, statistics_, BLOB_DB_BLOB_FILE_WRITE_MICROS);
+ Status s = dest_->Append(Slice(headerbuf));
+ if (s.ok()) {
+ s = dest_->Append(key);
+ }
+ if (s.ok()) {
+ s = dest_->Append(val);
+ }
+ if (s.ok()) {
+ s = dest_->Flush();
+ }
+
+ *key_offset = block_offset_ + BlobLogRecord::kHeaderSize;
+ *blob_offset = *key_offset + key.size();
+ block_offset_ = *blob_offset + val.size();
+ last_elem_type_ = kEtRecord;
+ RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_WRITTEN,
+ BlobLogRecord::kHeaderSize + key.size() + val.size());
+ return s;
+}
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.h b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.h
new file mode 100644
index 00000000..29dbd00f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/blob_db/blob_log_writer.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "rocksdb/env.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/statistics.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "utilities/blob_db/blob_log_format.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WritableFileWriter;
+
+namespace blob_db {
+
+/**
+ * Writer is the blob log stream writer. It provides an append-only
+ * abstraction for writing blob data.
+ *
+ *
+ * Look at blob_db_format.h to see the details of the record formats.
+ */
+
+class Writer {
+ public:
+ // Create a writer that will append data to "*dest".
+ // "*dest" must be initially empty.
+ // "*dest" must remain live while this Writer is in use.
+ Writer(std::unique_ptr<WritableFileWriter>&& dest, Env* env,
+ Statistics* statistics, uint64_t log_number, uint64_t bpsync,
+ bool use_fsync, uint64_t boffset = 0);
+ // No copying allowed
+ Writer(const Writer&) = delete;
+ Writer& operator=(const Writer&) = delete;
+
+ ~Writer() = default;
+
+ static void ConstructBlobHeader(std::string* buf, const Slice& key,
+ const Slice& val, uint64_t expiration);
+
+ Status AddRecord(const Slice& key, const Slice& val, uint64_t* key_offset,
+ uint64_t* blob_offset);
+
+ Status AddRecord(const Slice& key, const Slice& val, uint64_t expiration,
+ uint64_t* key_offset, uint64_t* blob_offset);
+
+ Status EmitPhysicalRecord(const std::string& headerbuf, const Slice& key,
+ const Slice& val, uint64_t* key_offset,
+ uint64_t* blob_offset);
+
+ Status AppendFooter(BlobLogFooter& footer);
+
+ Status WriteHeader(BlobLogHeader& header);
+
+ WritableFileWriter* file() { return dest_.get(); }
+
+ const WritableFileWriter* file() const { return dest_.get(); }
+
+ uint64_t get_log_number() const { return log_number_; }
+
+ bool ShouldSync() const { return block_offset_ > next_sync_offset_; }
+
+ Status Sync();
+
+ void ResetSyncPointer() { next_sync_offset_ += bytes_per_sync_; }
+
+ private:
+ std::unique_ptr<WritableFileWriter> dest_;
+ Env* env_;
+ Statistics* statistics_;
+ uint64_t log_number_;
+ uint64_t block_offset_; // Current offset in block
+ uint64_t bytes_per_sync_;
+ uint64_t next_sync_offset_;
+ bool use_fsync_;
+
+ public:
+ enum ElemType { kEtNone, kEtFileHdr, kEtRecord, kEtFileFooter };
+ ElemType last_elem_type_;
+};
+
+} // namespace blob_db
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.cc b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.cc
new file mode 100644
index 00000000..f0a00e4d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "utilities/cassandra/cassandra_compaction_filter.h"
+#include <string>
+#include "rocksdb/slice.h"
+#include "utilities/cassandra/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+const char* CassandraCompactionFilter::Name() const {
+ return "CassandraCompactionFilter";
+}
+
+CompactionFilter::Decision CassandraCompactionFilter::FilterV2(
+ int /*level*/, const Slice& /*key*/, ValueType value_type,
+ const Slice& existing_value, std::string* new_value,
+ std::string* /*skip_until*/) const {
+ bool value_changed = false;
+ RowValue row_value = RowValue::Deserialize(
+ existing_value.data(), existing_value.size());
+ RowValue compacted =
+ purge_ttl_on_expiration_
+ ? row_value.RemoveExpiredColumns(&value_changed)
+ : row_value.ConvertExpiredColumnsToTombstones(&value_changed);
+
+ if (value_type == ValueType::kValue) {
+ compacted = compacted.RemoveTombstones(gc_grace_period_in_seconds_);
+ }
+
+ if(compacted.Empty()) {
+ return Decision::kRemove;
+ }
+
+ if (value_changed) {
+ compacted.Serialize(new_value);
+ return Decision::kChangeValue;
+ }
+
+ return Decision::kKeep;
+}
+
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.h b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.h
new file mode 100644
index 00000000..ac258810
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_compaction_filter.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <string>
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+/**
+ * Compaction filter for removing expired Cassandra data with ttl.
+ * If option `purge_ttl_on_expiration` is set to true, expired data
+ * will be directly purged. Otherwise expired data will be converted
+ * tombstones first, then be eventally removed after gc grace period.
+ * `purge_ttl_on_expiration` should only be on in the case all the
+ * writes have same ttl setting, otherwise it could bring old data back.
+ *
+ * Compaction filter is also in charge of removing tombstone that has been
+ * promoted to kValue type after serials of merging in compaction.
+ */
+class CassandraCompactionFilter : public CompactionFilter {
+public:
+ explicit CassandraCompactionFilter(bool purge_ttl_on_expiration,
+ int32_t gc_grace_period_in_seconds)
+ : purge_ttl_on_expiration_(purge_ttl_on_expiration),
+ gc_grace_period_in_seconds_(gc_grace_period_in_seconds) {}
+
+ const char* Name() const override;
+ virtual Decision FilterV2(int level, const Slice& key, ValueType value_type,
+ const Slice& existing_value, std::string* new_value,
+ std::string* skip_until) const override;
+
+private:
+ bool purge_ttl_on_expiration_;
+ int32_t gc_grace_period_in_seconds_;
+};
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_format_test.cc b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_format_test.cc
new file mode 100644
index 00000000..a8e6ad3f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_format_test.cc
@@ -0,0 +1,367 @@
+// 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 <cstring>
+#include <memory>
+#include "test_util/testharness.h"
+#include "utilities/cassandra/format.h"
+#include "utilities/cassandra/serialize.h"
+#include "utilities/cassandra/test_utils.h"
+
+using namespace ROCKSDB_NAMESPACE::cassandra;
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+TEST(ColumnTest, Column) {
+ char data[4] = {'d', 'a', 't', 'a'};
+ int8_t mask = 0;
+ int8_t index = 1;
+ int64_t timestamp = 1494022807044;
+ Column c = Column(mask, index, timestamp, sizeof(data), data);
+
+ EXPECT_EQ(c.Index(), index);
+ EXPECT_EQ(c.Timestamp(), timestamp);
+ EXPECT_EQ(c.Size(), 14 + sizeof(data));
+
+ // Verify the serialization.
+ std::string dest;
+ dest.reserve(c.Size() * 2);
+ c.Serialize(&dest);
+
+ EXPECT_EQ(dest.size(), c.Size());
+ std::size_t offset = 0;
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), mask);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), timestamp);
+ offset += sizeof(int64_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), sizeof(data));
+ offset += sizeof(int32_t);
+ EXPECT_TRUE(std::memcmp(data, dest.c_str() + offset, sizeof(data)) == 0);
+
+ // Verify the deserialization.
+ std::string saved_dest = dest;
+ std::shared_ptr<Column> c1 = Column::Deserialize(saved_dest.c_str(), 0);
+ EXPECT_EQ(c1->Index(), index);
+ EXPECT_EQ(c1->Timestamp(), timestamp);
+ EXPECT_EQ(c1->Size(), 14 + sizeof(data));
+
+ c1->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 2 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str(), dest.c_str() + c.Size(), c.Size()) == 0);
+
+ // Verify the ColumnBase::Deserialization.
+ saved_dest = dest;
+ std::shared_ptr<ColumnBase> c2 =
+ ColumnBase::Deserialize(saved_dest.c_str(), c.Size());
+ c2->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 3 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str() + c.Size(), dest.c_str() + c.Size() * 2, c.Size())
+ == 0);
+}
+
+TEST(ExpiringColumnTest, ExpiringColumn) {
+ char data[4] = {'d', 'a', 't', 'a'};
+ int8_t mask = ColumnTypeMask::EXPIRATION_MASK;
+ int8_t index = 3;
+ int64_t timestamp = 1494022807044;
+ int32_t ttl = 3600;
+ ExpiringColumn c = ExpiringColumn(mask, index, timestamp,
+ sizeof(data), data, ttl);
+
+ EXPECT_EQ(c.Index(), index);
+ EXPECT_EQ(c.Timestamp(), timestamp);
+ EXPECT_EQ(c.Size(), 18 + sizeof(data));
+
+ // Verify the serialization.
+ std::string dest;
+ dest.reserve(c.Size() * 2);
+ c.Serialize(&dest);
+
+ EXPECT_EQ(dest.size(), c.Size());
+ std::size_t offset = 0;
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), mask);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), timestamp);
+ offset += sizeof(int64_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), sizeof(data));
+ offset += sizeof(int32_t);
+ EXPECT_TRUE(std::memcmp(data, dest.c_str() + offset, sizeof(data)) == 0);
+ offset += sizeof(data);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), ttl);
+
+ // Verify the deserialization.
+ std::string saved_dest = dest;
+ std::shared_ptr<ExpiringColumn> c1 =
+ ExpiringColumn::Deserialize(saved_dest.c_str(), 0);
+ EXPECT_EQ(c1->Index(), index);
+ EXPECT_EQ(c1->Timestamp(), timestamp);
+ EXPECT_EQ(c1->Size(), 18 + sizeof(data));
+
+ c1->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 2 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str(), dest.c_str() + c.Size(), c.Size()) == 0);
+
+ // Verify the ColumnBase::Deserialization.
+ saved_dest = dest;
+ std::shared_ptr<ColumnBase> c2 =
+ ColumnBase::Deserialize(saved_dest.c_str(), c.Size());
+ c2->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 3 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str() + c.Size(), dest.c_str() + c.Size() * 2, c.Size())
+ == 0);
+}
+
+TEST(TombstoneTest, TombstoneCollectable) {
+ int32_t now = (int32_t)time(nullptr);
+ int32_t gc_grace_seconds = 16440;
+ int32_t time_delta_seconds = 10;
+ EXPECT_TRUE(Tombstone(ColumnTypeMask::DELETION_MASK, 0,
+ now - gc_grace_seconds - time_delta_seconds,
+ ToMicroSeconds(now - gc_grace_seconds - time_delta_seconds))
+ .Collectable(gc_grace_seconds));
+ EXPECT_FALSE(Tombstone(ColumnTypeMask::DELETION_MASK, 0,
+ now - gc_grace_seconds + time_delta_seconds,
+ ToMicroSeconds(now - gc_grace_seconds + time_delta_seconds))
+ .Collectable(gc_grace_seconds));
+}
+
+TEST(TombstoneTest, Tombstone) {
+ int8_t mask = ColumnTypeMask::DELETION_MASK;
+ int8_t index = 2;
+ int32_t local_deletion_time = 1494022807;
+ int64_t marked_for_delete_at = 1494022807044;
+ Tombstone c = Tombstone(mask, index, local_deletion_time,
+ marked_for_delete_at);
+
+ EXPECT_EQ(c.Index(), index);
+ EXPECT_EQ(c.Timestamp(), marked_for_delete_at);
+ EXPECT_EQ(c.Size(), 14);
+
+ // Verify the serialization.
+ std::string dest;
+ dest.reserve(c.Size() * 2);
+ c.Serialize(&dest);
+
+ EXPECT_EQ(dest.size(), c.Size());
+ std::size_t offset = 0;
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), mask);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), local_deletion_time);
+ offset += sizeof(int32_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), marked_for_delete_at);
+
+ // Verify the deserialization.
+ std::shared_ptr<Tombstone> c1 = Tombstone::Deserialize(dest.c_str(), 0);
+ EXPECT_EQ(c1->Index(), index);
+ EXPECT_EQ(c1->Timestamp(), marked_for_delete_at);
+ EXPECT_EQ(c1->Size(), 14);
+
+ c1->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 2 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str(), dest.c_str() + c.Size(), c.Size()) == 0);
+
+ // Verify the ColumnBase::Deserialization.
+ std::shared_ptr<ColumnBase> c2 =
+ ColumnBase::Deserialize(dest.c_str(), c.Size());
+ c2->Serialize(&dest);
+ EXPECT_EQ(dest.size(), 3 * c.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str() + c.Size(), dest.c_str() + c.Size() * 2, c.Size())
+ == 0);
+}
+
+TEST(RowValueTest, RowTombstone) {
+ int32_t local_deletion_time = 1494022807;
+ int64_t marked_for_delete_at = 1494022807044;
+ RowValue r = RowValue(local_deletion_time, marked_for_delete_at);
+
+ EXPECT_EQ(r.Size(), 12);
+ EXPECT_EQ(r.IsTombstone(), true);
+ EXPECT_EQ(r.LastModifiedTime(), marked_for_delete_at);
+
+ // Verify the serialization.
+ std::string dest;
+ dest.reserve(r.Size() * 2);
+ r.Serialize(&dest);
+
+ EXPECT_EQ(dest.size(), r.Size());
+ std::size_t offset = 0;
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), local_deletion_time);
+ offset += sizeof(int32_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), marked_for_delete_at);
+
+ // Verify the deserialization.
+ RowValue r1 = RowValue::Deserialize(dest.c_str(), r.Size());
+ EXPECT_EQ(r1.Size(), 12);
+ EXPECT_EQ(r1.IsTombstone(), true);
+ EXPECT_EQ(r1.LastModifiedTime(), marked_for_delete_at);
+
+ r1.Serialize(&dest);
+ EXPECT_EQ(dest.size(), 2 * r.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str(), dest.c_str() + r.Size(), r.Size()) == 0);
+}
+
+TEST(RowValueTest, RowWithColumns) {
+ std::vector<std::shared_ptr<ColumnBase>> columns;
+ int64_t last_modified_time = 1494022807048;
+ std::size_t columns_data_size = 0;
+
+ char e_data[5] = {'e', 'd', 'a', 't', 'a'};
+ int8_t e_index = 0;
+ int64_t e_timestamp = 1494022807044;
+ int32_t e_ttl = 3600;
+ columns.push_back(std::shared_ptr<ExpiringColumn>(
+ new ExpiringColumn(ColumnTypeMask::EXPIRATION_MASK, e_index,
+ e_timestamp, sizeof(e_data), e_data, e_ttl)));
+ columns_data_size += columns[0]->Size();
+
+ char c_data[4] = {'d', 'a', 't', 'a'};
+ int8_t c_index = 1;
+ int64_t c_timestamp = 1494022807048;
+ columns.push_back(std::shared_ptr<Column>(
+ new Column(0, c_index, c_timestamp, sizeof(c_data), c_data)));
+ columns_data_size += columns[1]->Size();
+
+ int8_t t_index = 2;
+ int32_t t_local_deletion_time = 1494022801;
+ int64_t t_marked_for_delete_at = 1494022807043;
+ columns.push_back(std::shared_ptr<Tombstone>(
+ new Tombstone(ColumnTypeMask::DELETION_MASK,
+ t_index, t_local_deletion_time, t_marked_for_delete_at)));
+ columns_data_size += columns[2]->Size();
+
+ RowValue r = RowValue(std::move(columns), last_modified_time);
+
+ EXPECT_EQ(r.Size(), columns_data_size + 12);
+ EXPECT_EQ(r.IsTombstone(), false);
+ EXPECT_EQ(r.LastModifiedTime(), last_modified_time);
+
+ // Verify the serialization.
+ std::string dest;
+ dest.reserve(r.Size() * 2);
+ r.Serialize(&dest);
+
+ EXPECT_EQ(dest.size(), r.Size());
+ std::size_t offset = 0;
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset),
+ std::numeric_limits<int32_t>::max());
+ offset += sizeof(int32_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset),
+ std::numeric_limits<int64_t>::min());
+ offset += sizeof(int64_t);
+
+ // Column0: ExpiringColumn
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset),
+ ColumnTypeMask::EXPIRATION_MASK);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), e_index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), e_timestamp);
+ offset += sizeof(int64_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), sizeof(e_data));
+ offset += sizeof(int32_t);
+ EXPECT_TRUE(std::memcmp(e_data, dest.c_str() + offset, sizeof(e_data)) == 0);
+ offset += sizeof(e_data);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), e_ttl);
+ offset += sizeof(int32_t);
+
+ // Column1: Column
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), 0);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), c_index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), c_timestamp);
+ offset += sizeof(int64_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), sizeof(c_data));
+ offset += sizeof(int32_t);
+ EXPECT_TRUE(std::memcmp(c_data, dest.c_str() + offset, sizeof(c_data)) == 0);
+ offset += sizeof(c_data);
+
+ // Column2: Tombstone
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset),
+ ColumnTypeMask::DELETION_MASK);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int8_t>(dest.c_str(), offset), t_index);
+ offset += sizeof(int8_t);
+ EXPECT_EQ(Deserialize<int32_t>(dest.c_str(), offset), t_local_deletion_time);
+ offset += sizeof(int32_t);
+ EXPECT_EQ(Deserialize<int64_t>(dest.c_str(), offset), t_marked_for_delete_at);
+
+ // Verify the deserialization.
+ RowValue r1 = RowValue::Deserialize(dest.c_str(), r.Size());
+ EXPECT_EQ(r1.Size(), columns_data_size + 12);
+ EXPECT_EQ(r1.IsTombstone(), false);
+ EXPECT_EQ(r1.LastModifiedTime(), last_modified_time);
+
+ r1.Serialize(&dest);
+ EXPECT_EQ(dest.size(), 2 * r.Size());
+ EXPECT_TRUE(
+ std::memcmp(dest.c_str(), dest.c_str() + r.Size(), r.Size()) == 0);
+}
+
+TEST(RowValueTest, PurgeTtlShouldRemvoeAllColumnsExpired) {
+ int64_t now = time(nullptr);
+
+ auto row_value = CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, ToMicroSeconds(now)),
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now - kTtl - 10)), //expired
+ CreateTestColumnSpec(kExpiringColumn, 2, ToMicroSeconds(now)), // not expired
+ CreateTestColumnSpec(kTombstone, 3, ToMicroSeconds(now))
+ });
+
+ bool changed = false;
+ auto purged = row_value.RemoveExpiredColumns(&changed);
+ EXPECT_TRUE(changed);
+ EXPECT_EQ(purged.columns_.size(), 3);
+ VerifyRowValueColumns(purged.columns_, 0, kColumn, 0, ToMicroSeconds(now));
+ VerifyRowValueColumns(purged.columns_, 1, kExpiringColumn, 2, ToMicroSeconds(now));
+ VerifyRowValueColumns(purged.columns_, 2, kTombstone, 3, ToMicroSeconds(now));
+
+ purged.RemoveExpiredColumns(&changed);
+ EXPECT_FALSE(changed);
+}
+
+TEST(RowValueTest, ExpireTtlShouldConvertExpiredColumnsToTombstones) {
+ int64_t now = time(nullptr);
+
+ auto row_value = CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, ToMicroSeconds(now)),
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now - kTtl - 10)), //expired
+ CreateTestColumnSpec(kExpiringColumn, 2, ToMicroSeconds(now)), // not expired
+ CreateTestColumnSpec(kTombstone, 3, ToMicroSeconds(now))
+ });
+
+ bool changed = false;
+ auto compacted = row_value.ConvertExpiredColumnsToTombstones(&changed);
+ EXPECT_TRUE(changed);
+ EXPECT_EQ(compacted.columns_.size(), 4);
+ VerifyRowValueColumns(compacted.columns_, 0, kColumn, 0, ToMicroSeconds(now));
+ VerifyRowValueColumns(compacted.columns_, 1, kTombstone, 1, ToMicroSeconds(now - 10));
+ VerifyRowValueColumns(compacted.columns_, 2, kExpiringColumn, 2, ToMicroSeconds(now));
+ VerifyRowValueColumns(compacted.columns_, 3, kTombstone, 3, ToMicroSeconds(now));
+
+ compacted.ConvertExpiredColumnsToTombstones(&changed);
+ EXPECT_FALSE(changed);
+}
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_functional_test.cc b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_functional_test.cc
new file mode 100644
index 00000000..50198842
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_functional_test.cc
@@ -0,0 +1,311 @@
+// 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 <iostream>
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "test_util/testharness.h"
+#include "util/random.h"
+#include "utilities/cassandra/cassandra_compaction_filter.h"
+#include "utilities/cassandra/merge_operator.h"
+#include "utilities/cassandra/test_utils.h"
+#include "utilities/merge_operators.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+// Path to the database on file system
+const std::string kDbName = test::PerThreadDBPath("cassandra_functional_test");
+
+class CassandraStore {
+ public:
+ explicit CassandraStore(std::shared_ptr<DB> db)
+ : db_(db), write_option_(), get_option_() {
+ assert(db);
+ }
+
+ bool Append(const std::string& key, const RowValue& val){
+ std::string result;
+ val.Serialize(&result);
+ Slice valSlice(result.data(), result.size());
+ auto s = db_->Merge(write_option_, key, valSlice);
+
+ if (s.ok()) {
+ return true;
+ } else {
+ std::cerr << "ERROR " << s.ToString() << std::endl;
+ return false;
+ }
+ }
+
+ bool Put(const std::string& key, const RowValue& val) {
+ std::string result;
+ val.Serialize(&result);
+ Slice valSlice(result.data(), result.size());
+ auto s = db_->Put(write_option_, key, valSlice);
+ if (s.ok()) {
+ return true;
+ } else {
+ std::cerr << "ERROR " << s.ToString() << std::endl;
+ return false;
+ }
+ }
+
+ void Flush() {
+ dbfull()->TEST_FlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ }
+
+ void Compact() {
+ dbfull()->TEST_CompactRange(
+ 0, nullptr, nullptr, db_->DefaultColumnFamily());
+ }
+
+ std::tuple<bool, RowValue> Get(const std::string& key){
+ std::string result;
+ auto s = db_->Get(get_option_, key, &result);
+
+ if (s.ok()) {
+ return std::make_tuple(true,
+ RowValue::Deserialize(result.data(),
+ result.size()));
+ }
+
+ if (!s.IsNotFound()) {
+ std::cerr << "ERROR " << s.ToString() << std::endl;
+ }
+
+ return std::make_tuple(false, RowValue(0, 0));
+ }
+
+ private:
+ std::shared_ptr<DB> db_;
+ WriteOptions write_option_;
+ ReadOptions get_option_;
+
+ DBImpl* dbfull() { return reinterpret_cast<DBImpl*>(db_.get()); }
+};
+
+class TestCompactionFilterFactory : public CompactionFilterFactory {
+public:
+ explicit TestCompactionFilterFactory(bool purge_ttl_on_expiration,
+ int32_t gc_grace_period_in_seconds)
+ : purge_ttl_on_expiration_(purge_ttl_on_expiration),
+ gc_grace_period_in_seconds_(gc_grace_period_in_seconds) {}
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(new CassandraCompactionFilter(
+ purge_ttl_on_expiration_, gc_grace_period_in_seconds_));
+ }
+
+ const char* Name() const override { return "TestCompactionFilterFactory"; }
+
+private:
+ bool purge_ttl_on_expiration_;
+ int32_t gc_grace_period_in_seconds_;
+};
+
+
+// The class for unit-testing
+class CassandraFunctionalTest : public testing::Test {
+public:
+ CassandraFunctionalTest() {
+ DestroyDB(kDbName, Options()); // Start each test with a fresh DB
+ }
+
+ std::shared_ptr<DB> OpenDb() {
+ DB* db;
+ Options options;
+ options.create_if_missing = true;
+ options.merge_operator.reset(new CassandraValueMergeOperator(gc_grace_period_in_seconds_));
+ auto* cf_factory = new TestCompactionFilterFactory(
+ purge_ttl_on_expiration_, gc_grace_period_in_seconds_);
+ options.compaction_filter_factory.reset(cf_factory);
+ EXPECT_OK(DB::Open(options, kDbName, &db));
+ return std::shared_ptr<DB>(db);
+ }
+
+ bool purge_ttl_on_expiration_ = false;
+ int32_t gc_grace_period_in_seconds_ = 100;
+};
+
+// THE TEST CASES BEGIN HERE
+
+TEST_F(CassandraFunctionalTest, SimpleMergeTest) {
+ CassandraStore store(OpenDb());
+ int64_t now = time(nullptr);
+
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kTombstone, 0, ToMicroSeconds(now + 5)),
+ CreateTestColumnSpec(kColumn, 1, ToMicroSeconds(now + 8)),
+ CreateTestColumnSpec(kExpiringColumn, 2, ToMicroSeconds(now + 5)),
+ }));
+ store.Append("k1",CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, ToMicroSeconds(now + 2)),
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now + 5)),
+ CreateTestColumnSpec(kTombstone, 2, ToMicroSeconds(now + 7)),
+ CreateTestColumnSpec(kExpiringColumn, 7, ToMicroSeconds(now + 17)),
+ }));
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now + 6)),
+ CreateTestColumnSpec(kTombstone, 1, ToMicroSeconds(now + 5)),
+ CreateTestColumnSpec(kColumn, 2, ToMicroSeconds(now + 4)),
+ CreateTestColumnSpec(kTombstone, 11, ToMicroSeconds(now + 11)),
+ }));
+
+ auto ret = store.Get("k1");
+
+ ASSERT_TRUE(std::get<0>(ret));
+ RowValue& merged = std::get<1>(ret);
+ EXPECT_EQ(merged.columns_.size(), 5);
+ VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 0, ToMicroSeconds(now + 6));
+ VerifyRowValueColumns(merged.columns_, 1, kColumn, 1, ToMicroSeconds(now + 8));
+ VerifyRowValueColumns(merged.columns_, 2, kTombstone, 2, ToMicroSeconds(now + 7));
+ VerifyRowValueColumns(merged.columns_, 3, kExpiringColumn, 7, ToMicroSeconds(now + 17));
+ VerifyRowValueColumns(merged.columns_, 4, kTombstone, 11, ToMicroSeconds(now + 11));
+}
+
+TEST_F(CassandraFunctionalTest,
+ CompactionShouldConvertExpiredColumnsToTombstone) {
+ CassandraStore store(OpenDb());
+ int64_t now= time(nullptr);
+
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)), //expired
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now - kTtl + 10)), // not expired
+ CreateTestColumnSpec(kTombstone, 3, ToMicroSeconds(now))
+ }));
+
+ store.Flush();
+
+ store.Append("k1",CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)), //expired
+ CreateTestColumnSpec(kColumn, 2, ToMicroSeconds(now))
+ }));
+
+ store.Flush();
+ store.Compact();
+
+ auto ret = store.Get("k1");
+ ASSERT_TRUE(std::get<0>(ret));
+ RowValue& merged = std::get<1>(ret);
+ EXPECT_EQ(merged.columns_.size(), 4);
+ VerifyRowValueColumns(merged.columns_, 0, kTombstone, 0, ToMicroSeconds(now - 10));
+ VerifyRowValueColumns(merged.columns_, 1, kExpiringColumn, 1, ToMicroSeconds(now - kTtl + 10));
+ VerifyRowValueColumns(merged.columns_, 2, kColumn, 2, ToMicroSeconds(now));
+ VerifyRowValueColumns(merged.columns_, 3, kTombstone, 3, ToMicroSeconds(now));
+}
+
+
+TEST_F(CassandraFunctionalTest,
+ CompactionShouldPurgeExpiredColumnsIfPurgeTtlIsOn) {
+ purge_ttl_on_expiration_ = true;
+ CassandraStore store(OpenDb());
+ int64_t now = time(nullptr);
+
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)), //expired
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now)), // not expired
+ CreateTestColumnSpec(kTombstone, 3, ToMicroSeconds(now))
+ }));
+
+ store.Flush();
+
+ store.Append("k1",CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)), //expired
+ CreateTestColumnSpec(kColumn, 2, ToMicroSeconds(now))
+ }));
+
+ store.Flush();
+ store.Compact();
+
+ auto ret = store.Get("k1");
+ ASSERT_TRUE(std::get<0>(ret));
+ RowValue& merged = std::get<1>(ret);
+ EXPECT_EQ(merged.columns_.size(), 3);
+ VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 1, ToMicroSeconds(now));
+ VerifyRowValueColumns(merged.columns_, 1, kColumn, 2, ToMicroSeconds(now));
+ VerifyRowValueColumns(merged.columns_, 2, kTombstone, 3, ToMicroSeconds(now));
+}
+
+TEST_F(CassandraFunctionalTest,
+ CompactionShouldRemoveRowWhenAllColumnsExpiredIfPurgeTtlIsOn) {
+ purge_ttl_on_expiration_ = true;
+ CassandraStore store(OpenDb());
+ int64_t now = time(nullptr);
+
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)),
+ CreateTestColumnSpec(kExpiringColumn, 1, ToMicroSeconds(now - kTtl - 20)),
+ }));
+
+ store.Flush();
+
+ store.Append("k1",CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)),
+ }));
+
+ store.Flush();
+ store.Compact();
+ ASSERT_FALSE(std::get<0>(store.Get("k1")));
+}
+
+TEST_F(CassandraFunctionalTest,
+ CompactionShouldRemoveTombstoneExceedingGCGracePeriod) {
+ purge_ttl_on_expiration_ = true;
+ CassandraStore store(OpenDb());
+ int64_t now = time(nullptr);
+
+ store.Append("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kTombstone, 0, ToMicroSeconds(now - gc_grace_period_in_seconds_ - 1)),
+ CreateTestColumnSpec(kColumn, 1, ToMicroSeconds(now))
+ }));
+
+ store.Append("k2", CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, ToMicroSeconds(now))
+ }));
+
+ store.Flush();
+
+ store.Append("k1",CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 1, ToMicroSeconds(now)),
+ }));
+
+ store.Flush();
+ store.Compact();
+
+ auto ret = store.Get("k1");
+ ASSERT_TRUE(std::get<0>(ret));
+ RowValue& gced = std::get<1>(ret);
+ EXPECT_EQ(gced.columns_.size(), 1);
+ VerifyRowValueColumns(gced.columns_, 0, kColumn, 1, ToMicroSeconds(now));
+}
+
+TEST_F(CassandraFunctionalTest, CompactionShouldRemoveTombstoneFromPut) {
+ purge_ttl_on_expiration_ = true;
+ CassandraStore store(OpenDb());
+ int64_t now = time(nullptr);
+
+ store.Put("k1", CreateTestRowValue({
+ CreateTestColumnSpec(kTombstone, 0, ToMicroSeconds(now - gc_grace_period_in_seconds_ - 1)),
+ }));
+
+ store.Flush();
+ store.Compact();
+ ASSERT_FALSE(std::get<0>(store.Get("k1")));
+}
+
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_row_merge_test.cc b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_row_merge_test.cc
new file mode 100644
index 00000000..9e9ff149
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_row_merge_test.cc
@@ -0,0 +1,112 @@
+// 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 <memory>
+#include "test_util/testharness.h"
+#include "utilities/cassandra/format.h"
+#include "utilities/cassandra/test_utils.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+TEST(RowValueMergeTest, Merge) {
+ std::vector<RowValue> row_values;
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kTombstone, 0, 5),
+ CreateTestColumnSpec(kColumn, 1, 8),
+ CreateTestColumnSpec(kExpiringColumn, 2, 5),
+ })
+ );
+
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, 2),
+ CreateTestColumnSpec(kExpiringColumn, 1, 5),
+ CreateTestColumnSpec(kTombstone, 2, 7),
+ CreateTestColumnSpec(kExpiringColumn, 7, 17),
+ })
+ );
+
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kExpiringColumn, 0, 6),
+ CreateTestColumnSpec(kTombstone, 1, 5),
+ CreateTestColumnSpec(kColumn, 2, 4),
+ CreateTestColumnSpec(kTombstone, 11, 11),
+ })
+ );
+
+ RowValue merged = RowValue::Merge(std::move(row_values));
+ EXPECT_FALSE(merged.IsTombstone());
+ EXPECT_EQ(merged.columns_.size(), 5);
+ VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 0, 6);
+ VerifyRowValueColumns(merged.columns_, 1, kColumn, 1, 8);
+ VerifyRowValueColumns(merged.columns_, 2, kTombstone, 2, 7);
+ VerifyRowValueColumns(merged.columns_, 3, kExpiringColumn, 7, 17);
+ VerifyRowValueColumns(merged.columns_, 4, kTombstone, 11, 11);
+}
+
+TEST(RowValueMergeTest, MergeWithRowTombstone) {
+ std::vector<RowValue> row_values;
+
+ // A row tombstone.
+ row_values.push_back(
+ CreateRowTombstone(11)
+ );
+
+ // This row's timestamp is smaller than tombstone.
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 0, 5),
+ CreateTestColumnSpec(kColumn, 1, 6),
+ })
+ );
+
+ // Some of the column's row is smaller, some is larger.
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 2, 10),
+ CreateTestColumnSpec(kColumn, 3, 12),
+ })
+ );
+
+ // All of the column's rows are larger than tombstone.
+ row_values.push_back(
+ CreateTestRowValue({
+ CreateTestColumnSpec(kColumn, 4, 13),
+ CreateTestColumnSpec(kColumn, 5, 14),
+ })
+ );
+
+ RowValue merged = RowValue::Merge(std::move(row_values));
+ EXPECT_FALSE(merged.IsTombstone());
+ EXPECT_EQ(merged.columns_.size(), 3);
+ VerifyRowValueColumns(merged.columns_, 0, kColumn, 3, 12);
+ VerifyRowValueColumns(merged.columns_, 1, kColumn, 4, 13);
+ VerifyRowValueColumns(merged.columns_, 2, kColumn, 5, 14);
+
+ // If the tombstone's timestamp is the latest, then it returns a
+ // row tombstone.
+ row_values.push_back(
+ CreateRowTombstone(15)
+ );
+
+ row_values.push_back(
+ CreateRowTombstone(17)
+ );
+
+ merged = RowValue::Merge(std::move(row_values));
+ EXPECT_TRUE(merged.IsTombstone());
+ EXPECT_EQ(merged.LastModifiedTime(), 17);
+}
+
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_serialize_test.cc b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_serialize_test.cc
new file mode 100644
index 00000000..491540bf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/cassandra_serialize_test.cc
@@ -0,0 +1,188 @@
+// 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 "test_util/testharness.h"
+#include "utilities/cassandra/serialize.h"
+
+using namespace ROCKSDB_NAMESPACE::cassandra;
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+TEST(SerializeTest, SerializeI64) {
+ std::string dest;
+ Serialize<int64_t>(0, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}),
+ dest);
+
+ dest.clear();
+ Serialize<int64_t>(1, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01'}),
+ dest);
+
+
+ dest.clear();
+ Serialize<int64_t>(-1, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'}),
+ dest);
+
+ dest.clear();
+ Serialize<int64_t>(9223372036854775807, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x7f', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'}),
+ dest);
+
+ dest.clear();
+ Serialize<int64_t>(-9223372036854775807, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01'}),
+ dest);
+}
+
+TEST(SerializeTest, DeserializeI64) {
+ std::string dest;
+ std::size_t offset = dest.size();
+ Serialize<int64_t>(0, &dest);
+ EXPECT_EQ(0, Deserialize<int64_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int64_t>(1, &dest);
+ EXPECT_EQ(1, Deserialize<int64_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int64_t>(-1, &dest);
+ EXPECT_EQ(-1, Deserialize<int64_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int64_t>(-9223372036854775807, &dest);
+ EXPECT_EQ(-9223372036854775807, Deserialize<int64_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int64_t>(9223372036854775807, &dest);
+ EXPECT_EQ(9223372036854775807, Deserialize<int64_t>(dest.c_str(), offset));
+}
+
+TEST(SerializeTest, SerializeI32) {
+ std::string dest;
+ Serialize<int32_t>(0, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x00', '\x00', '\x00', '\x00'}),
+ dest);
+
+ dest.clear();
+ Serialize<int32_t>(1, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x00', '\x00', '\x00', '\x01'}),
+ dest);
+
+
+ dest.clear();
+ Serialize<int32_t>(-1, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\xff', '\xff', '\xff', '\xff'}),
+ dest);
+
+ dest.clear();
+ Serialize<int32_t>(2147483647, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x7f', '\xff', '\xff', '\xff'}),
+ dest);
+
+ dest.clear();
+ Serialize<int32_t>(-2147483648LL, &dest);
+ EXPECT_EQ(
+ std::string(
+ {'\x80', '\x00', '\x00', '\x00'}),
+ dest);
+}
+
+TEST(SerializeTest, DeserializeI32) {
+ std::string dest;
+ std::size_t offset = dest.size();
+ Serialize<int32_t>(0, &dest);
+ EXPECT_EQ(0, Deserialize<int32_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int32_t>(1, &dest);
+ EXPECT_EQ(1, Deserialize<int32_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int32_t>(-1, &dest);
+ EXPECT_EQ(-1, Deserialize<int32_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int32_t>(2147483647, &dest);
+ EXPECT_EQ(2147483647, Deserialize<int32_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int32_t>(-2147483648LL, &dest);
+ EXPECT_EQ(-2147483648LL, Deserialize<int32_t>(dest.c_str(), offset));
+}
+
+TEST(SerializeTest, SerializeI8) {
+ std::string dest;
+ Serialize<int8_t>(0, &dest);
+ EXPECT_EQ(std::string({'\x00'}), dest);
+
+ dest.clear();
+ Serialize<int8_t>(1, &dest);
+ EXPECT_EQ(std::string({'\x01'}), dest);
+
+
+ dest.clear();
+ Serialize<int8_t>(-1, &dest);
+ EXPECT_EQ(std::string({'\xff'}), dest);
+
+ dest.clear();
+ Serialize<int8_t>(127, &dest);
+ EXPECT_EQ(std::string({'\x7f'}), dest);
+
+ dest.clear();
+ Serialize<int8_t>(-128, &dest);
+ EXPECT_EQ(std::string({'\x80'}), dest);
+}
+
+TEST(SerializeTest, DeserializeI8) {
+ std::string dest;
+ std::size_t offset = dest.size();
+ Serialize<int8_t>(0, &dest);
+ EXPECT_EQ(0, Deserialize<int8_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int8_t>(1, &dest);
+ EXPECT_EQ(1, Deserialize<int8_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int8_t>(-1, &dest);
+ EXPECT_EQ(-1, Deserialize<int8_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int8_t>(127, &dest);
+ EXPECT_EQ(127, Deserialize<int8_t>(dest.c_str(), offset));
+
+ offset = dest.size();
+ Serialize<int8_t>(-128, &dest);
+ EXPECT_EQ(-128, Deserialize<int8_t>(dest.c_str(), offset));
+}
+
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/format.cc b/storage/rocksdb/rocksdb/utilities/cassandra/format.cc
new file mode 100644
index 00000000..a767f41e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/format.cc
@@ -0,0 +1,390 @@
+// 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 "format.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+
+#include "utilities/cassandra/serialize.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+namespace {
+const int32_t kDefaultLocalDeletionTime =
+ std::numeric_limits<int32_t>::max();
+const int64_t kDefaultMarkedForDeleteAt =
+ std::numeric_limits<int64_t>::min();
+}
+
+ColumnBase::ColumnBase(int8_t mask, int8_t index)
+ : mask_(mask), index_(index) {}
+
+std::size_t ColumnBase::Size() const {
+ return sizeof(mask_) + sizeof(index_);
+}
+
+int8_t ColumnBase::Mask() const {
+ return mask_;
+}
+
+int8_t ColumnBase::Index() const {
+ return index_;
+}
+
+void ColumnBase::Serialize(std::string* dest) const {
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int8_t>(mask_, dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int8_t>(index_, dest);
+}
+
+std::shared_ptr<ColumnBase> ColumnBase::Deserialize(const char* src,
+ std::size_t offset) {
+ int8_t mask = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ if ((mask & ColumnTypeMask::DELETION_MASK) != 0) {
+ return Tombstone::Deserialize(src, offset);
+ } else if ((mask & ColumnTypeMask::EXPIRATION_MASK) != 0) {
+ return ExpiringColumn::Deserialize(src, offset);
+ } else {
+ return Column::Deserialize(src, offset);
+ }
+}
+
+Column::Column(
+ int8_t mask,
+ int8_t index,
+ int64_t timestamp,
+ int32_t value_size,
+ const char* value
+) : ColumnBase(mask, index), timestamp_(timestamp),
+ value_size_(value_size), value_(value) {}
+
+int64_t Column::Timestamp() const {
+ return timestamp_;
+}
+
+std::size_t Column::Size() const {
+ return ColumnBase::Size() + sizeof(timestamp_) + sizeof(value_size_)
+ + value_size_;
+}
+
+void Column::Serialize(std::string* dest) const {
+ ColumnBase::Serialize(dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int64_t>(timestamp_, dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int32_t>(value_size_, dest);
+ dest->append(value_, value_size_);
+}
+
+std::shared_ptr<Column> Column::Deserialize(const char *src,
+ std::size_t offset) {
+ int8_t mask = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(mask);
+ int8_t index = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(index);
+ int64_t timestamp =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int64_t>(src, offset);
+ offset += sizeof(timestamp);
+ int32_t value_size =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int32_t>(src, offset);
+ offset += sizeof(value_size);
+ return std::make_shared<Column>(
+ mask, index, timestamp, value_size, src + offset);
+}
+
+ExpiringColumn::ExpiringColumn(
+ int8_t mask,
+ int8_t index,
+ int64_t timestamp,
+ int32_t value_size,
+ const char* value,
+ int32_t ttl
+) : Column(mask, index, timestamp, value_size, value),
+ ttl_(ttl) {}
+
+std::size_t ExpiringColumn::Size() const {
+ return Column::Size() + sizeof(ttl_);
+}
+
+void ExpiringColumn::Serialize(std::string* dest) const {
+ Column::Serialize(dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int32_t>(ttl_, dest);
+}
+
+std::chrono::time_point<std::chrono::system_clock> ExpiringColumn::TimePoint() const {
+ return std::chrono::time_point<std::chrono::system_clock>(std::chrono::microseconds(Timestamp()));
+}
+
+std::chrono::seconds ExpiringColumn::Ttl() const {
+ return std::chrono::seconds(ttl_);
+}
+
+bool ExpiringColumn::Expired() const {
+ return TimePoint() + Ttl() < std::chrono::system_clock::now();
+}
+
+std::shared_ptr<Tombstone> ExpiringColumn::ToTombstone() const {
+ auto expired_at = (TimePoint() + Ttl()).time_since_epoch();
+ int32_t local_deletion_time = static_cast<int32_t>(
+ std::chrono::duration_cast<std::chrono::seconds>(expired_at).count());
+ int64_t marked_for_delete_at =
+ std::chrono::duration_cast<std::chrono::microseconds>(expired_at).count();
+ return std::make_shared<Tombstone>(
+ static_cast<int8_t>(ColumnTypeMask::DELETION_MASK),
+ Index(),
+ local_deletion_time,
+ marked_for_delete_at);
+}
+
+std::shared_ptr<ExpiringColumn> ExpiringColumn::Deserialize(
+ const char *src,
+ std::size_t offset) {
+ int8_t mask = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(mask);
+ int8_t index = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(index);
+ int64_t timestamp =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int64_t>(src, offset);
+ offset += sizeof(timestamp);
+ int32_t value_size =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int32_t>(src, offset);
+ offset += sizeof(value_size);
+ const char* value = src + offset;
+ offset += value_size;
+ int32_t ttl = ROCKSDB_NAMESPACE::cassandra::Deserialize<int32_t>(src, offset);
+ return std::make_shared<ExpiringColumn>(
+ mask, index, timestamp, value_size, value, ttl);
+}
+
+Tombstone::Tombstone(
+ int8_t mask,
+ int8_t index,
+ int32_t local_deletion_time,
+ int64_t marked_for_delete_at
+) : ColumnBase(mask, index), local_deletion_time_(local_deletion_time),
+ marked_for_delete_at_(marked_for_delete_at) {}
+
+int64_t Tombstone::Timestamp() const {
+ return marked_for_delete_at_;
+}
+
+std::size_t Tombstone::Size() const {
+ return ColumnBase::Size() + sizeof(local_deletion_time_)
+ + sizeof(marked_for_delete_at_);
+}
+
+void Tombstone::Serialize(std::string* dest) const {
+ ColumnBase::Serialize(dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int32_t>(local_deletion_time_, dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int64_t>(marked_for_delete_at_, dest);
+}
+
+bool Tombstone::Collectable(int32_t gc_grace_period_in_seconds) const {
+ auto local_deleted_at = std::chrono::time_point<std::chrono::system_clock>(
+ std::chrono::seconds(local_deletion_time_));
+ auto gc_grace_period = std::chrono::seconds(gc_grace_period_in_seconds);
+ return local_deleted_at + gc_grace_period < std::chrono::system_clock::now();
+}
+
+std::shared_ptr<Tombstone> Tombstone::Deserialize(const char *src,
+ std::size_t offset) {
+ int8_t mask = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(mask);
+ int8_t index = ROCKSDB_NAMESPACE::cassandra::Deserialize<int8_t>(src, offset);
+ offset += sizeof(index);
+ int32_t local_deletion_time =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int32_t>(src, offset);
+ offset += sizeof(int32_t);
+ int64_t marked_for_delete_at =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int64_t>(src, offset);
+ return std::make_shared<Tombstone>(
+ mask, index, local_deletion_time, marked_for_delete_at);
+}
+
+RowValue::RowValue(int32_t local_deletion_time, int64_t marked_for_delete_at)
+ : local_deletion_time_(local_deletion_time),
+ marked_for_delete_at_(marked_for_delete_at), columns_(),
+ last_modified_time_(0) {}
+
+RowValue::RowValue(Columns columns,
+ int64_t last_modified_time)
+ : local_deletion_time_(kDefaultLocalDeletionTime),
+ marked_for_delete_at_(kDefaultMarkedForDeleteAt),
+ columns_(std::move(columns)), last_modified_time_(last_modified_time) {}
+
+std::size_t RowValue::Size() const {
+ std::size_t size = sizeof(local_deletion_time_)
+ + sizeof(marked_for_delete_at_);
+ for (const auto& column : columns_) {
+ size += column -> Size();
+ }
+ return size;
+}
+
+int64_t RowValue::LastModifiedTime() const {
+ if (IsTombstone()) {
+ return marked_for_delete_at_;
+ } else {
+ return last_modified_time_;
+ }
+}
+
+bool RowValue::IsTombstone() const {
+ return marked_for_delete_at_ > kDefaultMarkedForDeleteAt;
+}
+
+void RowValue::Serialize(std::string* dest) const {
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int32_t>(local_deletion_time_, dest);
+ ROCKSDB_NAMESPACE::cassandra::Serialize<int64_t>(marked_for_delete_at_, dest);
+ for (const auto& column : columns_) {
+ column -> Serialize(dest);
+ }
+}
+
+RowValue RowValue::RemoveExpiredColumns(bool* changed) const {
+ *changed = false;
+ Columns new_columns;
+ for (auto& column : columns_) {
+ if(column->Mask() == ColumnTypeMask::EXPIRATION_MASK) {
+ std::shared_ptr<ExpiringColumn> expiring_column =
+ std::static_pointer_cast<ExpiringColumn>(column);
+
+ if(expiring_column->Expired()){
+ *changed = true;
+ continue;
+ }
+ }
+
+ new_columns.push_back(column);
+ }
+ return RowValue(std::move(new_columns), last_modified_time_);
+}
+
+RowValue RowValue::ConvertExpiredColumnsToTombstones(bool* changed) const {
+ *changed = false;
+ Columns new_columns;
+ for (auto& column : columns_) {
+ if(column->Mask() == ColumnTypeMask::EXPIRATION_MASK) {
+ std::shared_ptr<ExpiringColumn> expiring_column =
+ std::static_pointer_cast<ExpiringColumn>(column);
+
+ if(expiring_column->Expired()) {
+ std::shared_ptr<Tombstone> tombstone = expiring_column->ToTombstone();
+ new_columns.push_back(tombstone);
+ *changed = true;
+ continue;
+ }
+ }
+ new_columns.push_back(column);
+ }
+ return RowValue(std::move(new_columns), last_modified_time_);
+}
+
+RowValue RowValue::RemoveTombstones(int32_t gc_grace_period) const {
+ Columns new_columns;
+ for (auto& column : columns_) {
+ if (column->Mask() == ColumnTypeMask::DELETION_MASK) {
+ std::shared_ptr<Tombstone> tombstone =
+ std::static_pointer_cast<Tombstone>(column);
+
+ if (tombstone->Collectable(gc_grace_period)) {
+ continue;
+ }
+ }
+
+ new_columns.push_back(column);
+ }
+ return RowValue(std::move(new_columns), last_modified_time_);
+}
+
+bool RowValue::Empty() const {
+ return columns_.empty();
+}
+
+RowValue RowValue::Deserialize(const char *src, std::size_t size) {
+ std::size_t offset = 0;
+ assert(size >= sizeof(local_deletion_time_) + sizeof(marked_for_delete_at_));
+ int32_t local_deletion_time =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int32_t>(src, offset);
+ offset += sizeof(int32_t);
+ int64_t marked_for_delete_at =
+ ROCKSDB_NAMESPACE::cassandra::Deserialize<int64_t>(src, offset);
+ offset += sizeof(int64_t);
+ if (offset == size) {
+ return RowValue(local_deletion_time, marked_for_delete_at);
+ }
+
+ assert(local_deletion_time == kDefaultLocalDeletionTime);
+ assert(marked_for_delete_at == kDefaultMarkedForDeleteAt);
+ Columns columns;
+ int64_t last_modified_time = 0;
+ while (offset < size) {
+ auto c = ColumnBase::Deserialize(src, offset);
+ offset += c -> Size();
+ assert(offset <= size);
+ last_modified_time = std::max(last_modified_time, c -> Timestamp());
+ columns.push_back(std::move(c));
+ }
+
+ return RowValue(std::move(columns), last_modified_time);
+}
+
+// Merge multiple row values into one.
+// For each column in rows with same index, we pick the one with latest
+// timestamp. And we also take row tombstone into consideration, by iterating
+// each row from reverse timestamp order, and stop once we hit the first
+// row tombstone.
+RowValue RowValue::Merge(std::vector<RowValue>&& values) {
+ assert(values.size() > 0);
+ if (values.size() == 1) {
+ return std::move(values[0]);
+ }
+
+ // Merge columns by their last modified time, and skip once we hit
+ // a row tombstone.
+ std::sort(values.begin(), values.end(),
+ [](const RowValue& r1, const RowValue& r2) {
+ return r1.LastModifiedTime() > r2.LastModifiedTime();
+ });
+
+ std::map<int8_t, std::shared_ptr<ColumnBase>> merged_columns;
+ int64_t tombstone_timestamp = 0;
+
+ for (auto& value : values) {
+ if (value.IsTombstone()) {
+ if (merged_columns.size() == 0) {
+ return std::move(value);
+ }
+ tombstone_timestamp = value.LastModifiedTime();
+ break;
+ }
+ for (auto& column : value.columns_) {
+ int8_t index = column->Index();
+ if (merged_columns.find(index) == merged_columns.end()) {
+ merged_columns[index] = column;
+ } else {
+ if (column->Timestamp() > merged_columns[index]->Timestamp()) {
+ merged_columns[index] = column;
+ }
+ }
+ }
+ }
+
+ int64_t last_modified_time = 0;
+ Columns columns;
+ for (auto& pair: merged_columns) {
+ // For some row, its last_modified_time > row tombstone_timestamp, but
+ // it might have rows whose timestamp is ealier than tombstone, so we
+ // ned to filter these rows.
+ if (pair.second->Timestamp() <= tombstone_timestamp) {
+ continue;
+ }
+ last_modified_time = std::max(last_modified_time, pair.second->Timestamp());
+ columns.push_back(std::move(pair.second));
+ }
+ return RowValue(std::move(columns), last_modified_time);
+}
+
+} // namepsace cassandrda
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/format.h b/storage/rocksdb/rocksdb/utilities/cassandra/format.h
new file mode 100644
index 00000000..3f9b433c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/format.h
@@ -0,0 +1,197 @@
+// 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).
+
+/**
+ * The encoding of Cassandra Row Value.
+ *
+ * A Cassandra Row Value could either be a row tombstone,
+ * or contains multiple columns, it has following fields:
+ *
+ * struct row_value {
+ * int32_t local_deletion_time; // Time in second when the row is deleted,
+ * // only used for Cassandra tombstone gc.
+ * int64_t marked_for_delete_at; // Ms that marked this row is deleted.
+ * struct column_base columns[]; // For non tombstone row, all columns
+ * // are stored here.
+ * }
+ *
+ * If the local_deletion_time and marked_for_delete_at is set, then this is
+ * a tombstone, otherwise it contains multiple columns.
+ *
+ * There are three type of Columns: Normal Column, Expiring Column and Column
+ * Tombstone, which have following fields:
+ *
+ * // Identify the type of the column.
+ * enum mask {
+ * DELETION_MASK = 0x01,
+ * EXPIRATION_MASK = 0x02,
+ * };
+ *
+ * struct column {
+ * int8_t mask = 0;
+ * int8_t index;
+ * int64_t timestamp;
+ * int32_t value_length;
+ * char value[value_length];
+ * }
+ *
+ * struct expiring_column {
+ * int8_t mask = mask.EXPIRATION_MASK;
+ * int8_t index;
+ * int64_t timestamp;
+ * int32_t value_length;
+ * char value[value_length];
+ * int32_t ttl;
+ * }
+ *
+ * struct tombstone_column {
+ * int8_t mask = mask.DELETION_MASK;
+ * int8_t index;
+ * int32_t local_deletion_time; // Similar to row_value's field.
+ * int64_t marked_for_delete_at;
+ * }
+ */
+
+#pragma once
+#include <chrono>
+#include <memory>
+#include <vector>
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+// Identify the type of the column.
+enum ColumnTypeMask {
+ DELETION_MASK = 0x01,
+ EXPIRATION_MASK = 0x02,
+};
+
+
+class ColumnBase {
+public:
+ ColumnBase(int8_t mask, int8_t index);
+ virtual ~ColumnBase() = default;
+
+ virtual int64_t Timestamp() const = 0;
+ virtual int8_t Mask() const;
+ virtual int8_t Index() const;
+ virtual std::size_t Size() const;
+ virtual void Serialize(std::string* dest) const;
+ static std::shared_ptr<ColumnBase> Deserialize(const char* src,
+ std::size_t offset);
+
+private:
+ int8_t mask_;
+ int8_t index_;
+};
+
+class Column : public ColumnBase {
+public:
+ Column(int8_t mask, int8_t index, int64_t timestamp,
+ int32_t value_size, const char* value);
+
+ virtual int64_t Timestamp() const override;
+ virtual std::size_t Size() const override;
+ virtual void Serialize(std::string* dest) const override;
+ static std::shared_ptr<Column> Deserialize(const char* src,
+ std::size_t offset);
+
+private:
+ int64_t timestamp_;
+ int32_t value_size_;
+ const char* value_;
+};
+
+class Tombstone : public ColumnBase {
+public:
+ Tombstone(int8_t mask, int8_t index,
+ int32_t local_deletion_time, int64_t marked_for_delete_at);
+
+ virtual int64_t Timestamp() const override;
+ virtual std::size_t Size() const override;
+ virtual void Serialize(std::string* dest) const override;
+ bool Collectable(int32_t gc_grace_period) const;
+ static std::shared_ptr<Tombstone> Deserialize(const char* src,
+ std::size_t offset);
+
+private:
+ int32_t local_deletion_time_;
+ int64_t marked_for_delete_at_;
+};
+
+class ExpiringColumn : public Column {
+public:
+ ExpiringColumn(int8_t mask, int8_t index, int64_t timestamp,
+ int32_t value_size, const char* value, int32_t ttl);
+
+ virtual std::size_t Size() const override;
+ virtual void Serialize(std::string* dest) const override;
+ bool Expired() const;
+ std::shared_ptr<Tombstone> ToTombstone() const;
+
+ static std::shared_ptr<ExpiringColumn> Deserialize(const char* src,
+ std::size_t offset);
+
+private:
+ int32_t ttl_;
+ std::chrono::time_point<std::chrono::system_clock> TimePoint() const;
+ std::chrono::seconds Ttl() const;
+};
+
+typedef std::vector<std::shared_ptr<ColumnBase>> Columns;
+
+class RowValue {
+public:
+ // Create a Row Tombstone.
+ RowValue(int32_t local_deletion_time, int64_t marked_for_delete_at);
+ // Create a Row containing columns.
+ RowValue(Columns columns,
+ int64_t last_modified_time);
+ RowValue(const RowValue& /*that*/) = delete;
+ RowValue(RowValue&& /*that*/) noexcept = default;
+ RowValue& operator=(const RowValue& /*that*/) = delete;
+ RowValue& operator=(RowValue&& /*that*/) = default;
+
+ std::size_t Size() const;;
+ bool IsTombstone() const;
+ // For Tombstone this returns the marked_for_delete_at_,
+ // otherwise it returns the max timestamp of containing columns.
+ int64_t LastModifiedTime() const;
+ void Serialize(std::string* dest) const;
+ RowValue RemoveExpiredColumns(bool* changed) const;
+ RowValue ConvertExpiredColumnsToTombstones(bool* changed) const;
+ RowValue RemoveTombstones(int32_t gc_grace_period) const;
+ bool Empty() const;
+
+ static RowValue Deserialize(const char* src, std::size_t size);
+ // Merge multiple rows according to their timestamp.
+ static RowValue Merge(std::vector<RowValue>&& values);
+
+private:
+ int32_t local_deletion_time_;
+ int64_t marked_for_delete_at_;
+ Columns columns_;
+ int64_t last_modified_time_;
+
+ FRIEND_TEST(RowValueTest, PurgeTtlShouldRemvoeAllColumnsExpired);
+ FRIEND_TEST(RowValueTest, ExpireTtlShouldConvertExpiredColumnsToTombstones);
+ FRIEND_TEST(RowValueMergeTest, Merge);
+ FRIEND_TEST(RowValueMergeTest, MergeWithRowTombstone);
+ FRIEND_TEST(CassandraFunctionalTest, SimpleMergeTest);
+ FRIEND_TEST(
+ CassandraFunctionalTest, CompactionShouldConvertExpiredColumnsToTombstone);
+ FRIEND_TEST(
+ CassandraFunctionalTest, CompactionShouldPurgeExpiredColumnsIfPurgeTtlIsOn);
+ FRIEND_TEST(
+ CassandraFunctionalTest, CompactionShouldRemoveRowWhenAllColumnExpiredIfPurgeTtlIsOn);
+ FRIEND_TEST(CassandraFunctionalTest,
+ CompactionShouldRemoveTombstoneExceedingGCGracePeriod);
+};
+
+} // namepsace cassandrda
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.cc b/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.cc
new file mode 100644
index 00000000..82fe5d66
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.cc
@@ -0,0 +1,67 @@
+// 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 "merge_operator.h"
+
+#include <memory>
+#include <assert.h>
+
+#include "rocksdb/slice.h"
+#include "rocksdb/merge_operator.h"
+#include "utilities/merge_operators.h"
+#include "utilities/cassandra/format.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+// Implementation for the merge operation (merges two Cassandra values)
+bool CassandraValueMergeOperator::FullMergeV2(
+ const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const {
+ // Clear the *new_value for writing.
+ merge_out->new_value.clear();
+ std::vector<RowValue> row_values;
+ if (merge_in.existing_value) {
+ row_values.push_back(
+ RowValue::Deserialize(merge_in.existing_value->data(),
+ merge_in.existing_value->size()));
+ }
+
+ for (auto& operand : merge_in.operand_list) {
+ row_values.push_back(RowValue::Deserialize(operand.data(), operand.size()));
+ }
+
+ RowValue merged = RowValue::Merge(std::move(row_values));
+ merged = merged.RemoveTombstones(gc_grace_period_in_seconds_);
+ merge_out->new_value.reserve(merged.Size());
+ merged.Serialize(&(merge_out->new_value));
+
+ return true;
+}
+
+bool CassandraValueMergeOperator::PartialMergeMulti(
+ const Slice& /*key*/, const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* /*logger*/) const {
+ // Clear the *new_value for writing.
+ assert(new_value);
+ new_value->clear();
+
+ std::vector<RowValue> row_values;
+ for (auto& operand : operand_list) {
+ row_values.push_back(RowValue::Deserialize(operand.data(), operand.size()));
+ }
+ RowValue merged = RowValue::Merge(std::move(row_values));
+ new_value->reserve(merged.Size());
+ merged.Serialize(new_value);
+ return true;
+}
+
+const char* CassandraValueMergeOperator::Name() const {
+ return "CassandraValueMergeOperator";
+}
+
+} // namespace cassandra
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.h b/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.h
new file mode 100644
index 00000000..b5bf7c52
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/merge_operator.h
@@ -0,0 +1,44 @@
+// 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).
+
+#pragma once
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+
+/**
+ * A MergeOperator for rocksdb that implements Cassandra row value merge.
+ */
+class CassandraValueMergeOperator : public MergeOperator {
+public:
+ explicit CassandraValueMergeOperator(int32_t gc_grace_period_in_seconds,
+ size_t operands_limit = 0)
+ : gc_grace_period_in_seconds_(gc_grace_period_in_seconds),
+ operands_limit_(operands_limit) {}
+
+ virtual bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override;
+
+ virtual bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* logger) const override;
+
+ virtual const char* Name() const override;
+
+ virtual bool AllowSingleOperand() const override { return true; }
+
+ virtual bool ShouldMerge(const std::vector<Slice>& operands) const override {
+ return operands_limit_ > 0 && operands.size() >= operands_limit_;
+ }
+
+private:
+ int32_t gc_grace_period_in_seconds_;
+ size_t operands_limit_;
+};
+} // namespace cassandra
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/serialize.h b/storage/rocksdb/rocksdb/utilities/cassandra/serialize.h
new file mode 100644
index 00000000..cd980ade
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/serialize.h
@@ -0,0 +1,75 @@
+// 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).
+
+/**
+ * Helper functions which serialize and deserialize integers
+ * into bytes in big endian.
+ */
+
+#pragma once
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+namespace {
+const int64_t kCharMask = 0xFFLL;
+const int32_t kBitsPerByte = 8;
+}
+
+template<typename T>
+void Serialize(T val, std::string* dest);
+
+template<typename T>
+T Deserialize(const char* src, std::size_t offset=0);
+
+// Specializations
+template<>
+inline void Serialize<int8_t>(int8_t t, std::string* dest) {
+ dest->append(1, static_cast<char>(t & kCharMask));
+}
+
+template<>
+inline void Serialize<int32_t>(int32_t t, std::string* dest) {
+ for (unsigned long i = 0; i < sizeof(int32_t); i++) {
+ dest->append(1, static_cast<char>(
+ (t >> (sizeof(int32_t) - 1 - i) * kBitsPerByte) & kCharMask));
+ }
+}
+
+template<>
+inline void Serialize<int64_t>(int64_t t, std::string* dest) {
+ for (unsigned long i = 0; i < sizeof(int64_t); i++) {
+ dest->append(
+ 1, static_cast<char>(
+ (t >> (sizeof(int64_t) - 1 - i) * kBitsPerByte) & kCharMask));
+ }
+}
+
+template<>
+inline int8_t Deserialize<int8_t>(const char* src, std::size_t offset) {
+ return static_cast<int8_t>(src[offset]);
+}
+
+template<>
+inline int32_t Deserialize<int32_t>(const char* src, std::size_t offset) {
+ int32_t result = 0;
+ for (unsigned long i = 0; i < sizeof(int32_t); i++) {
+ result |= static_cast<int32_t>(static_cast<unsigned char>(src[offset + i]))
+ << ((sizeof(int32_t) - 1 - i) * kBitsPerByte);
+ }
+ return result;
+}
+
+template<>
+inline int64_t Deserialize<int64_t>(const char* src, std::size_t offset) {
+ int64_t result = 0;
+ for (unsigned long i = 0; i < sizeof(int64_t); i++) {
+ result |= static_cast<int64_t>(static_cast<unsigned char>(src[offset + i]))
+ << ((sizeof(int64_t) - 1 - i) * kBitsPerByte);
+ }
+ return result;
+}
+
+} // namepsace cassandrda
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.cc b/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.cc
new file mode 100644
index 00000000..47919bf6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.cc
@@ -0,0 +1,75 @@
+// 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 "test_utils.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+const char kData[] = {'d', 'a', 't', 'a'};
+const char kExpiringData[] = {'e', 'd', 'a', 't', 'a'};
+const int32_t kTtl = 86400;
+const int8_t kColumn = 0;
+const int8_t kTombstone = 1;
+const int8_t kExpiringColumn = 2;
+
+std::shared_ptr<ColumnBase> CreateTestColumn(int8_t mask,
+ int8_t index,
+ int64_t timestamp) {
+ if ((mask & ColumnTypeMask::DELETION_MASK) != 0) {
+ return std::shared_ptr<Tombstone>(
+ new Tombstone(mask, index, ToSeconds(timestamp), timestamp));
+ } else if ((mask & ColumnTypeMask::EXPIRATION_MASK) != 0) {
+ return std::shared_ptr<ExpiringColumn>(new ExpiringColumn(
+ mask, index, timestamp, sizeof(kExpiringData), kExpiringData, kTtl));
+ } else {
+ return std::shared_ptr<Column>(
+ new Column(mask, index, timestamp, sizeof(kData), kData));
+ }
+}
+
+std::tuple<int8_t, int8_t, int64_t> CreateTestColumnSpec(int8_t mask,
+ int8_t index,
+ int64_t timestamp) {
+ return std::make_tuple(mask, index, timestamp);
+}
+
+RowValue CreateTestRowValue(
+ std::vector<std::tuple<int8_t, int8_t, int64_t>> column_specs) {
+ std::vector<std::shared_ptr<ColumnBase>> columns;
+ int64_t last_modified_time = 0;
+ for (auto spec: column_specs) {
+ auto c = CreateTestColumn(std::get<0>(spec), std::get<1>(spec),
+ std::get<2>(spec));
+ last_modified_time = std::max(last_modified_time, c -> Timestamp());
+ columns.push_back(std::move(c));
+ }
+ return RowValue(std::move(columns), last_modified_time);
+}
+
+RowValue CreateRowTombstone(int64_t timestamp) {
+ return RowValue(ToSeconds(timestamp), timestamp);
+}
+
+void VerifyRowValueColumns(
+ std::vector<std::shared_ptr<ColumnBase>> &columns,
+ std::size_t index_of_vector,
+ int8_t expected_mask,
+ int8_t expected_index,
+ int64_t expected_timestamp
+) {
+ EXPECT_EQ(expected_timestamp, columns[index_of_vector]->Timestamp());
+ EXPECT_EQ(expected_mask, columns[index_of_vector]->Mask());
+ EXPECT_EQ(expected_index, columns[index_of_vector]->Index());
+}
+
+int64_t ToMicroSeconds(int64_t seconds) {
+ return seconds * (int64_t)1000000;
+}
+
+int32_t ToSeconds(int64_t microseconds) {
+ return (int32_t)(microseconds / (int64_t)1000000);
+}
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.h b/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.h
new file mode 100644
index 00000000..235b35a0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/cassandra/test_utils.h
@@ -0,0 +1,46 @@
+// 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).
+
+#pragma once
+#include <memory>
+#include "test_util/testharness.h"
+#include "utilities/cassandra/format.h"
+#include "utilities/cassandra/serialize.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace cassandra {
+extern const char kData[];
+extern const char kExpiringData[];
+extern const int32_t kTtl;
+extern const int8_t kColumn;
+extern const int8_t kTombstone;
+extern const int8_t kExpiringColumn;
+
+
+std::shared_ptr<ColumnBase> CreateTestColumn(int8_t mask,
+ int8_t index,
+ int64_t timestamp);
+
+std::tuple<int8_t, int8_t, int64_t> CreateTestColumnSpec(int8_t mask,
+ int8_t index,
+ int64_t timestamp);
+
+RowValue CreateTestRowValue(
+ std::vector<std::tuple<int8_t, int8_t, int64_t>> column_specs);
+
+RowValue CreateRowTombstone(int64_t timestamp);
+
+void VerifyRowValueColumns(
+ std::vector<std::shared_ptr<ColumnBase>> &columns,
+ std::size_t index_of_vector,
+ int8_t expected_mask,
+ int8_t expected_index,
+ int64_t expected_timestamp
+);
+
+int64_t ToMicroSeconds(int64_t seconds);
+int32_t ToSeconds(int64_t microseconds);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.cc b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.cc
new file mode 100644
index 00000000..98e60960
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.cc
@@ -0,0 +1,516 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 Facebook.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ROCKSDB_LITE
+
+#include "utilities/checkpoint/checkpoint_impl.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#include "db/wal_manager.h"
+#include "file/file_util.h"
+#include "file/filename.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/metadata.h"
+#include "rocksdb/transaction_log.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "test_util/sync_point.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status Checkpoint::Create(DB* db, Checkpoint** checkpoint_ptr) {
+ *checkpoint_ptr = new CheckpointImpl(db);
+ return Status::OK();
+}
+
+Status Checkpoint::CreateCheckpoint(const std::string& /*checkpoint_dir*/,
+ uint64_t /*log_size_for_flush*/) {
+ return Status::NotSupported("");
+}
+
+void CheckpointImpl::CleanStagingDirectory(
+ const std::string& full_private_path, Logger* info_log) {
+ std::vector<std::string> subchildren;
+ Status s = db_->GetEnv()->FileExists(full_private_path);
+ if (s.IsNotFound()) {
+ return;
+ }
+ ROCKS_LOG_INFO(info_log, "File exists %s -- %s",
+ full_private_path.c_str(), s.ToString().c_str());
+ db_->GetEnv()->GetChildren(full_private_path, &subchildren);
+ for (auto& subchild : subchildren) {
+ std::string subchild_path = full_private_path + "/" + subchild;
+ s = db_->GetEnv()->DeleteFile(subchild_path);
+ ROCKS_LOG_INFO(info_log, "Delete file %s -- %s",
+ subchild_path.c_str(), s.ToString().c_str());
+ }
+ // finally delete the private dir
+ s = db_->GetEnv()->DeleteDir(full_private_path);
+ ROCKS_LOG_INFO(info_log, "Delete dir %s -- %s",
+ full_private_path.c_str(), s.ToString().c_str());
+}
+
+Status Checkpoint::ExportColumnFamily(
+ ColumnFamilyHandle* /*handle*/, const std::string& /*export_dir*/,
+ ExportImportFilesMetaData** /*metadata*/) {
+ return Status::NotSupported("");
+}
+
+// Builds an openable snapshot of RocksDB
+Status CheckpointImpl::CreateCheckpoint(const std::string& checkpoint_dir,
+ uint64_t log_size_for_flush) {
+ DBOptions db_options = db_->GetDBOptions();
+
+ Status s = db_->GetEnv()->FileExists(checkpoint_dir);
+ if (s.ok()) {
+ return Status::InvalidArgument("Directory exists");
+ } else if (!s.IsNotFound()) {
+ assert(s.IsIOError());
+ return s;
+ }
+
+ ROCKS_LOG_INFO(
+ db_options.info_log,
+ "Started the snapshot process -- creating snapshot in directory %s",
+ checkpoint_dir.c_str());
+
+ size_t final_nonslash_idx = checkpoint_dir.find_last_not_of('/');
+ if (final_nonslash_idx == std::string::npos) {
+ // npos means it's only slashes or empty. Non-empty means it's the root
+ // directory, but it shouldn't be because we verified above the directory
+ // doesn't exist.
+ assert(checkpoint_dir.empty());
+ return Status::InvalidArgument("invalid checkpoint directory name");
+ }
+
+ std::string full_private_path =
+ checkpoint_dir.substr(0, final_nonslash_idx + 1) + ".tmp";
+ ROCKS_LOG_INFO(
+ db_options.info_log,
+ "Snapshot process -- using temporary directory %s",
+ full_private_path.c_str());
+ CleanStagingDirectory(full_private_path, db_options.info_log.get());
+ // create snapshot directory
+ s = db_->GetEnv()->CreateDir(full_private_path);
+ uint64_t sequence_number = 0;
+ if (s.ok()) {
+ db_->DisableFileDeletions();
+ s = CreateCustomCheckpoint(
+ db_options,
+ [&](const std::string& src_dirname, const std::string& fname,
+ FileType) {
+ ROCKS_LOG_INFO(db_options.info_log, "Hard Linking %s", fname.c_str());
+ return db_->GetFileSystem()->LinkFile(src_dirname + fname,
+ full_private_path + fname,
+ IOOptions(), nullptr);
+ } /* link_file_cb */,
+ [&](const std::string& src_dirname, const std::string& fname,
+ uint64_t size_limit_bytes, FileType) {
+ ROCKS_LOG_INFO(db_options.info_log, "Copying %s", fname.c_str());
+ return CopyFile(db_->GetFileSystem(), src_dirname + fname,
+ full_private_path + fname, size_limit_bytes,
+ db_options.use_fsync);
+ } /* copy_file_cb */,
+ [&](const std::string& fname, const std::string& contents, FileType) {
+ ROCKS_LOG_INFO(db_options.info_log, "Creating %s", fname.c_str());
+ return CreateFile(db_->GetFileSystem(), full_private_path + fname,
+ contents, db_options.use_fsync);
+ } /* create_file_cb */,
+ &sequence_number, log_size_for_flush);
+ // we copied all the files, enable file deletions
+ db_->EnableFileDeletions(false);
+ }
+
+ if (s.ok()) {
+ // move tmp private backup to real snapshot directory
+ s = db_->GetEnv()->RenameFile(full_private_path, checkpoint_dir);
+ }
+ if (s.ok()) {
+ std::unique_ptr<Directory> checkpoint_directory;
+ db_->GetEnv()->NewDirectory(checkpoint_dir, &checkpoint_directory);
+ if (checkpoint_directory != nullptr) {
+ s = checkpoint_directory->Fsync();
+ }
+ }
+
+ if (s.ok()) {
+ // here we know that we succeeded and installed the new snapshot
+ ROCKS_LOG_INFO(db_options.info_log, "Snapshot DONE. All is good");
+ ROCKS_LOG_INFO(db_options.info_log, "Snapshot sequence number: %" PRIu64,
+ sequence_number);
+ } else {
+ // clean all the files we might have created
+ ROCKS_LOG_INFO(db_options.info_log, "Snapshot failed -- %s",
+ s.ToString().c_str());
+ CleanStagingDirectory(full_private_path, db_options.info_log.get());
+ }
+ return s;
+}
+
+Status CheckpointImpl::CreateCustomCheckpoint(
+ const DBOptions& db_options,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& src_fname, FileType type)>
+ link_file_cb,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& src_fname,
+ uint64_t size_limit_bytes, FileType type)>
+ copy_file_cb,
+ std::function<Status(const std::string& fname, const std::string& contents,
+ FileType type)>
+ create_file_cb,
+ uint64_t* sequence_number, uint64_t log_size_for_flush) {
+ Status s;
+ std::vector<std::string> live_files;
+ uint64_t manifest_file_size = 0;
+ uint64_t min_log_num = port::kMaxUint64;
+ *sequence_number = db_->GetLatestSequenceNumber();
+ bool same_fs = true;
+ VectorLogPtr live_wal_files;
+
+ bool flush_memtable = true;
+ if (s.ok()) {
+ if (!db_options.allow_2pc) {
+ if (log_size_for_flush == port::kMaxUint64) {
+ flush_memtable = false;
+ } else if (log_size_for_flush > 0) {
+ // If out standing log files are small, we skip the flush.
+ s = db_->GetSortedWalFiles(live_wal_files);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ // Don't flush column families if total log size is smaller than
+ // log_size_for_flush. We copy the log files instead.
+ // We may be able to cover 2PC case too.
+ uint64_t total_wal_size = 0;
+ for (auto& wal : live_wal_files) {
+ total_wal_size += wal->SizeFileBytes();
+ }
+ if (total_wal_size < log_size_for_flush) {
+ flush_memtable = false;
+ }
+ live_wal_files.clear();
+ }
+ }
+
+ // this will return live_files prefixed with "/"
+ s = db_->GetLiveFiles(live_files, &manifest_file_size, flush_memtable);
+
+ if (s.ok() && db_options.allow_2pc) {
+ // If 2PC is enabled, we need to get minimum log number after the flush.
+ // Need to refetch the live files to recapture the snapshot.
+ if (!db_->GetIntProperty(DB::Properties::kMinLogNumberToKeep,
+ &min_log_num)) {
+ return Status::InvalidArgument(
+ "2PC enabled but cannot fine the min log number to keep.");
+ }
+ // We need to refetch live files with flush to handle this case:
+ // A previous 000001.log contains the prepare record of transaction tnx1.
+ // The current log file is 000002.log, and sequence_number points to this
+ // file.
+ // After calling GetLiveFiles(), 000003.log is created.
+ // Then tnx1 is committed. The commit record is written to 000003.log.
+ // Now we fetch min_log_num, which will be 3.
+ // Then only 000002.log and 000003.log will be copied, and 000001.log will
+ // be skipped. 000003.log contains commit message of tnx1, but we don't
+ // have respective prepare record for it.
+ // In order to avoid this situation, we need to force flush to make sure
+ // all transactions committed before getting min_log_num will be flushed
+ // to SST files.
+ // We cannot get min_log_num before calling the GetLiveFiles() for the
+ // first time, because if we do that, all the logs files will be included,
+ // far more than needed.
+ s = db_->GetLiveFiles(live_files, &manifest_file_size, flush_memtable);
+ }
+
+ TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles1");
+ TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles2");
+ db_->FlushWAL(false /* sync */);
+ }
+ // if we have more than one column family, we need to also get WAL files
+ if (s.ok()) {
+ s = db_->GetSortedWalFiles(live_wal_files);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+
+ size_t wal_size = live_wal_files.size();
+
+ // copy/hard link live_files
+ std::string manifest_fname, current_fname;
+ for (size_t i = 0; s.ok() && i < live_files.size(); ++i) {
+ uint64_t number;
+ FileType type;
+ bool ok = ParseFileName(live_files[i], &number, &type);
+ if (!ok) {
+ s = Status::Corruption("Can't parse file name. This is very bad");
+ break;
+ }
+ // we should only get sst, options, manifest and current files here
+ assert(type == kTableFile || type == kDescriptorFile ||
+ type == kCurrentFile || type == kOptionsFile);
+ assert(live_files[i].size() > 0 && live_files[i][0] == '/');
+ if (type == kCurrentFile) {
+ // We will craft the current file manually to ensure it's consistent with
+ // the manifest number. This is necessary because current's file contents
+ // can change during checkpoint creation.
+ current_fname = live_files[i];
+ continue;
+ } else if (type == kDescriptorFile) {
+ manifest_fname = live_files[i];
+ }
+ std::string src_fname = live_files[i];
+
+ // rules:
+ // * if it's kTableFile, then it's shared
+ // * if it's kDescriptorFile, limit the size to manifest_file_size
+ // * always copy if cross-device link
+ if ((type == kTableFile) && same_fs) {
+ s = link_file_cb(db_->GetName(), src_fname, type);
+ if (s.IsNotSupported()) {
+ same_fs = false;
+ s = Status::OK();
+ }
+ }
+ if ((type != kTableFile) || (!same_fs)) {
+ s = copy_file_cb(db_->GetName(), src_fname,
+ (type == kDescriptorFile) ? manifest_file_size : 0,
+ type);
+ }
+ }
+ if (s.ok() && !current_fname.empty() && !manifest_fname.empty()) {
+ create_file_cb(current_fname, manifest_fname.substr(1) + "\n",
+ kCurrentFile);
+ }
+ ROCKS_LOG_INFO(db_options.info_log, "Number of log files %" ROCKSDB_PRIszt,
+ live_wal_files.size());
+
+ // Link WAL files. Copy exact size of last one because it is the only one
+ // that has changes after the last flush.
+ for (size_t i = 0; s.ok() && i < wal_size; ++i) {
+ if ((live_wal_files[i]->Type() == kAliveLogFile) &&
+ (!flush_memtable ||
+ live_wal_files[i]->StartSequence() >= *sequence_number ||
+ live_wal_files[i]->LogNumber() >= min_log_num)) {
+ if (i + 1 == wal_size) {
+ s = copy_file_cb(db_options.wal_dir, live_wal_files[i]->PathName(),
+ live_wal_files[i]->SizeFileBytes(), kLogFile);
+ break;
+ }
+ if (same_fs) {
+ // we only care about live log files
+ s = link_file_cb(db_options.wal_dir, live_wal_files[i]->PathName(),
+ kLogFile);
+ if (s.IsNotSupported()) {
+ same_fs = false;
+ s = Status::OK();
+ }
+ }
+ if (!same_fs) {
+ s = copy_file_cb(db_options.wal_dir, live_wal_files[i]->PathName(), 0,
+ kLogFile);
+ }
+ }
+ }
+
+ return s;
+}
+
+// Exports all live SST files of a specified Column Family onto export_dir,
+// returning SST files information in metadata.
+Status CheckpointImpl::ExportColumnFamily(
+ ColumnFamilyHandle* handle, const std::string& export_dir,
+ ExportImportFilesMetaData** metadata) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(handle);
+ const auto cf_name = cfh->GetName();
+ const auto db_options = db_->GetDBOptions();
+
+ assert(metadata != nullptr);
+ assert(*metadata == nullptr);
+ auto s = db_->GetEnv()->FileExists(export_dir);
+ if (s.ok()) {
+ return Status::InvalidArgument("Specified export_dir exists");
+ } else if (!s.IsNotFound()) {
+ assert(s.IsIOError());
+ return s;
+ }
+
+ const auto final_nonslash_idx = export_dir.find_last_not_of('/');
+ if (final_nonslash_idx == std::string::npos) {
+ return Status::InvalidArgument("Specified export_dir invalid");
+ }
+ ROCKS_LOG_INFO(db_options.info_log,
+ "[%s] export column family onto export directory %s",
+ cf_name.c_str(), export_dir.c_str());
+
+ // Create a temporary export directory.
+ const auto tmp_export_dir =
+ export_dir.substr(0, final_nonslash_idx + 1) + ".tmp";
+ s = db_->GetEnv()->CreateDir(tmp_export_dir);
+
+ if (s.ok()) {
+ s = db_->Flush(ROCKSDB_NAMESPACE::FlushOptions(), handle);
+ }
+
+ ColumnFamilyMetaData db_metadata;
+ if (s.ok()) {
+ // Export live sst files with file deletions disabled.
+ s = db_->DisableFileDeletions();
+ if (s.ok()) {
+ db_->GetColumnFamilyMetaData(handle, &db_metadata);
+
+ s = ExportFilesInMetaData(
+ db_options, db_metadata,
+ [&](const std::string& src_dirname, const std::string& fname) {
+ ROCKS_LOG_INFO(db_options.info_log, "[%s] HardLinking %s",
+ cf_name.c_str(), fname.c_str());
+ return db_->GetEnv()->LinkFile(src_dirname + fname,
+ tmp_export_dir + fname);
+ } /*link_file_cb*/,
+ [&](const std::string& src_dirname, const std::string& fname) {
+ ROCKS_LOG_INFO(db_options.info_log, "[%s] Copying %s",
+ cf_name.c_str(), fname.c_str());
+ return CopyFile(db_->GetFileSystem(), src_dirname + fname,
+ tmp_export_dir + fname, 0, db_options.use_fsync);
+ } /*copy_file_cb*/);
+
+ const auto enable_status = db_->EnableFileDeletions(false /*force*/);
+ if (s.ok()) {
+ s = enable_status;
+ }
+ }
+ }
+
+ auto moved_to_user_specified_dir = false;
+ if (s.ok()) {
+ // Move temporary export directory to the actual export directory.
+ s = db_->GetEnv()->RenameFile(tmp_export_dir, export_dir);
+ }
+
+ if (s.ok()) {
+ // Fsync export directory.
+ moved_to_user_specified_dir = true;
+ std::unique_ptr<Directory> dir_ptr;
+ s = db_->GetEnv()->NewDirectory(export_dir, &dir_ptr);
+ if (s.ok()) {
+ assert(dir_ptr != nullptr);
+ s = dir_ptr->Fsync();
+ }
+ }
+
+ if (s.ok()) {
+ // Export of files succeeded. Fill in the metadata information.
+ auto result_metadata = new ExportImportFilesMetaData();
+ result_metadata->db_comparator_name = handle->GetComparator()->Name();
+ for (const auto& level_metadata : db_metadata.levels) {
+ for (const auto& file_metadata : level_metadata.files) {
+ LiveFileMetaData live_file_metadata;
+ live_file_metadata.size = file_metadata.size;
+ live_file_metadata.name = std::move(file_metadata.name);
+ live_file_metadata.file_number = file_metadata.file_number;
+ live_file_metadata.db_path = export_dir;
+ live_file_metadata.smallest_seqno = file_metadata.smallest_seqno;
+ live_file_metadata.largest_seqno = file_metadata.largest_seqno;
+ live_file_metadata.smallestkey = std::move(file_metadata.smallestkey);
+ live_file_metadata.largestkey = std::move(file_metadata.largestkey);
+ live_file_metadata.oldest_blob_file_number =
+ file_metadata.oldest_blob_file_number;
+ live_file_metadata.level = level_metadata.level;
+ result_metadata->files.push_back(live_file_metadata);
+ }
+ *metadata = result_metadata;
+ }
+ ROCKS_LOG_INFO(db_options.info_log, "[%s] Export succeeded.",
+ cf_name.c_str());
+ } else {
+ // Failure: Clean up all the files/directories created.
+ ROCKS_LOG_INFO(db_options.info_log, "[%s] Export failed. %s",
+ cf_name.c_str(), s.ToString().c_str());
+ std::vector<std::string> subchildren;
+ const auto cleanup_dir =
+ moved_to_user_specified_dir ? export_dir : tmp_export_dir;
+ db_->GetEnv()->GetChildren(cleanup_dir, &subchildren);
+ for (const auto& subchild : subchildren) {
+ const auto subchild_path = cleanup_dir + "/" + subchild;
+ const auto status = db_->GetEnv()->DeleteFile(subchild_path);
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options.info_log, "Failed to cleanup file %s: %s",
+ subchild_path.c_str(), status.ToString().c_str());
+ }
+ }
+ const auto status = db_->GetEnv()->DeleteDir(cleanup_dir);
+ if (!status.ok()) {
+ ROCKS_LOG_WARN(db_options.info_log, "Failed to cleanup dir %s: %s",
+ cleanup_dir.c_str(), status.ToString().c_str());
+ }
+ }
+ return s;
+}
+
+Status CheckpointImpl::ExportFilesInMetaData(
+ const DBOptions& db_options, const ColumnFamilyMetaData& metadata,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& src_fname)>
+ link_file_cb,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& src_fname)>
+ copy_file_cb) {
+ Status s;
+ auto hardlink_file = true;
+
+ // Copy/hard link files in metadata.
+ size_t num_files = 0;
+ for (const auto& level_metadata : metadata.levels) {
+ for (const auto& file_metadata : level_metadata.files) {
+ uint64_t number;
+ FileType type;
+ const auto ok = ParseFileName(file_metadata.name, &number, &type);
+ if (!ok) {
+ s = Status::Corruption("Could not parse file name");
+ break;
+ }
+
+ // We should only get sst files here.
+ assert(type == kTableFile);
+ assert(file_metadata.size > 0 && file_metadata.name[0] == '/');
+ const auto src_fname = file_metadata.name;
+ ++num_files;
+
+ if (hardlink_file) {
+ s = link_file_cb(db_->GetName(), src_fname);
+ if (num_files == 1 && s.IsNotSupported()) {
+ // Fallback to copy if link failed due to cross-device directories.
+ hardlink_file = false;
+ s = Status::OK();
+ }
+ }
+ if (!hardlink_file) {
+ s = copy_file_cb(db_->GetName(), src_fname);
+ }
+ if (!s.ok()) {
+ break;
+ }
+ }
+ }
+ ROCKS_LOG_INFO(db_options.info_log, "Number of table files %" ROCKSDB_PRIszt,
+ num_files);
+
+ return s;
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.h b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.h
new file mode 100644
index 00000000..81ee8320
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_impl.h
@@ -0,0 +1,79 @@
+// 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).
+
+#pragma once
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/utilities/checkpoint.h"
+
+#include <string>
+#include "file/filename.h"
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class CheckpointImpl : public Checkpoint {
+ public:
+ // Creates a Checkpoint object to be used for creating openable snapshots
+ explicit CheckpointImpl(DB* db) : db_(db) {}
+
+ // Builds an openable 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
+ // SST files will be copied if output directory is on a different filesystem
+ // (2) a copied manifest files and other files
+ // The directory should not already exist and will be created by this API.
+ // The directory will be an absolute path
+ using Checkpoint::CreateCheckpoint;
+ virtual Status CreateCheckpoint(const std::string& checkpoint_dir,
+ uint64_t log_size_for_flush) override;
+
+ // Exports all live SST files of a specified Column Family onto export_dir
+ // and returning SST files information in metadata.
+ // - SST files will be created as hard links when the directory specified
+ // is in the same partition as the db directory, copied otherwise.
+ // - export_dir should not already exist and will be created by this API.
+ // - Always triggers a flush.
+ using Checkpoint::ExportColumnFamily;
+ virtual Status ExportColumnFamily(
+ ColumnFamilyHandle* handle, const std::string& export_dir,
+ ExportImportFilesMetaData** metadata) override;
+
+ // Checkpoint logic can be customized by providing callbacks for link, copy,
+ // or create.
+ Status CreateCustomCheckpoint(
+ const DBOptions& db_options,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& fname, FileType type)>
+ link_file_cb,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& fname, uint64_t size_limit_bytes,
+ FileType type)>
+ copy_file_cb,
+ std::function<Status(const std::string& fname,
+ const std::string& contents, FileType type)>
+ create_file_cb,
+ uint64_t* sequence_number, uint64_t log_size_for_flush);
+
+ private:
+ void CleanStagingDirectory(const std::string& path, Logger* info_log);
+
+ // Export logic customization by providing callbacks for link or copy.
+ Status ExportFilesInMetaData(
+ const DBOptions& db_options, const ColumnFamilyMetaData& metadata,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& fname)>
+ link_file_cb,
+ std::function<Status(const std::string& src_dirname,
+ const std::string& fname)>
+ copy_file_cb);
+
+ private:
+ DB* db_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_test.cc b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_test.cc
new file mode 100644
index 00000000..1a31c40f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/checkpoint/checkpoint_test.cc
@@ -0,0 +1,829 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+// Syncpoint prevents us building and running tests in release
+#ifndef ROCKSDB_LITE
+
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+#include <iostream>
+#include <thread>
+#include <utility>
+#include "db/db_impl/db_impl.h"
+#include "port/port.h"
+#include "port/stack_trace.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/utilities/checkpoint.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+class CheckpointTest : public testing::Test {
+ protected:
+ // Sequence of option configurations to try
+ enum OptionConfig {
+ kDefault = 0,
+ };
+ int option_config_;
+
+ public:
+ std::string dbname_;
+ std::string alternative_wal_dir_;
+ Env* env_;
+ DB* db_;
+ Options last_options_;
+ std::vector<ColumnFamilyHandle*> handles_;
+ std::string snapshot_name_;
+ std::string export_path_;
+ ColumnFamilyHandle* cfh_reverse_comp_;
+ ExportImportFilesMetaData* metadata_;
+
+ CheckpointTest() : env_(Env::Default()) {
+ env_->SetBackgroundThreads(1, Env::LOW);
+ env_->SetBackgroundThreads(1, Env::HIGH);
+ dbname_ = test::PerThreadDBPath(env_, "checkpoint_test");
+ alternative_wal_dir_ = dbname_ + "/wal";
+ auto options = CurrentOptions();
+ auto delete_options = options;
+ delete_options.wal_dir = alternative_wal_dir_;
+ EXPECT_OK(DestroyDB(dbname_, delete_options));
+ // Destroy it for not alternative WAL dir is used.
+ EXPECT_OK(DestroyDB(dbname_, options));
+ db_ = nullptr;
+ snapshot_name_ = test::PerThreadDBPath(env_, "snapshot");
+ std::string snapshot_tmp_name = snapshot_name_ + ".tmp";
+ EXPECT_OK(DestroyDB(snapshot_name_, options));
+ env_->DeleteDir(snapshot_name_);
+ EXPECT_OK(DestroyDB(snapshot_tmp_name, options));
+ env_->DeleteDir(snapshot_tmp_name);
+ Reopen(options);
+ export_path_ = test::TmpDir(env_) + "/export";
+ test::DestroyDir(env_, export_path_);
+ cfh_reverse_comp_ = nullptr;
+ metadata_ = nullptr;
+ }
+
+ ~CheckpointTest() override {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ if (cfh_reverse_comp_) {
+ EXPECT_OK(db_->DestroyColumnFamilyHandle(cfh_reverse_comp_));
+ cfh_reverse_comp_ = nullptr;
+ }
+ if (metadata_) {
+ delete metadata_;
+ metadata_ = nullptr;
+ }
+ Close();
+ Options options;
+ options.db_paths.emplace_back(dbname_, 0);
+ options.db_paths.emplace_back(dbname_ + "_2", 0);
+ options.db_paths.emplace_back(dbname_ + "_3", 0);
+ options.db_paths.emplace_back(dbname_ + "_4", 0);
+ EXPECT_OK(DestroyDB(dbname_, options));
+ EXPECT_OK(DestroyDB(snapshot_name_, options));
+ test::DestroyDir(env_, export_path_);
+ }
+
+ // Return the current option configuration.
+ Options CurrentOptions() {
+ Options options;
+ options.env = env_;
+ options.create_if_missing = true;
+ return options;
+ }
+
+ void CreateColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ColumnFamilyOptions cf_opts(options);
+ size_t cfi = handles_.size();
+ handles_.resize(cfi + cfs.size());
+ for (auto cf : cfs) {
+ ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]));
+ }
+ }
+
+ void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
+ const Options& options) {
+ CreateColumnFamilies(cfs, options);
+ std::vector<std::string> cfs_plus_default = cfs;
+ cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
+ ReopenWithColumnFamilies(cfs_plus_default, options);
+ }
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const std::vector<Options>& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+ }
+
+ void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
+ }
+
+ Status TryReopenWithColumnFamilies(
+ const std::vector<std::string>& cfs,
+ const std::vector<Options>& options) {
+ Close();
+ EXPECT_EQ(cfs.size(), options.size());
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < cfs.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
+ }
+ DBOptions db_opts = DBOptions(options[0]);
+ return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
+ }
+
+ Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ Close();
+ std::vector<Options> v_opts(cfs.size(), options);
+ return TryReopenWithColumnFamilies(cfs, v_opts);
+ }
+
+ void Reopen(const Options& options) {
+ ASSERT_OK(TryReopen(options));
+ }
+
+ void CompactAll() {
+ for (auto h : handles_) {
+ ASSERT_OK(db_->CompactRange(CompactRangeOptions(), h, nullptr, nullptr));
+ }
+ }
+
+ void Close() {
+ for (auto h : handles_) {
+ delete h;
+ }
+ handles_.clear();
+ delete db_;
+ db_ = nullptr;
+ }
+
+ void DestroyAndReopen(const Options& options) {
+ // Destroy using last options
+ Destroy(last_options_);
+ ASSERT_OK(TryReopen(options));
+ }
+
+ void Destroy(const Options& options) {
+ Close();
+ ASSERT_OK(DestroyDB(dbname_, options));
+ }
+
+ Status ReadOnlyReopen(const Options& options) {
+ return DB::OpenForReadOnly(options, dbname_, &db_);
+ }
+
+ Status ReadOnlyReopenWithColumnFamilies(const std::vector<std::string>& cfs,
+ const Options& options) {
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (const auto& cf : cfs) {
+ column_families.emplace_back(cf, options);
+ }
+ return DB::OpenForReadOnly(options, dbname_, column_families, &handles_,
+ &db_);
+ }
+
+ Status TryReopen(const Options& options) {
+ Close();
+ last_options_ = options;
+ return DB::Open(options, dbname_, &db_);
+ }
+
+ Status Flush(int cf = 0) {
+ if (cf == 0) {
+ return db_->Flush(FlushOptions());
+ } else {
+ return db_->Flush(FlushOptions(), handles_[cf]);
+ }
+ }
+
+ Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) {
+ return db_->Put(wo, k, v);
+ }
+
+ Status Put(int cf, const Slice& k, const Slice& v,
+ WriteOptions wo = WriteOptions()) {
+ return db_->Put(wo, handles_[cf], k, v);
+ }
+
+ Status Delete(const std::string& k) {
+ return db_->Delete(WriteOptions(), k);
+ }
+
+ Status Delete(int cf, const std::string& k) {
+ return db_->Delete(WriteOptions(), handles_[cf], k);
+ }
+
+ std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ 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;
+ }
+
+ std::string Get(int cf, const std::string& k,
+ const Snapshot* snapshot = nullptr) {
+ ReadOptions options;
+ options.verify_checksums = true;
+ options.snapshot = snapshot;
+ std::string result;
+ Status s = db_->Get(options, handles_[cf], k, &result);
+ if (s.IsNotFound()) {
+ result = "NOT_FOUND";
+ } else if (!s.ok()) {
+ result = s.ToString();
+ }
+ return result;
+ }
+};
+
+TEST_F(CheckpointTest, GetSnapshotLink) {
+ for (uint64_t log_size_for_flush : {0, 1000000}) {
+ Options options;
+ DB* snapshotDB;
+ ReadOptions roptions;
+ std::string result;
+ Checkpoint* checkpoint;
+
+ options = CurrentOptions();
+ delete db_;
+ db_ = nullptr;
+ ASSERT_OK(DestroyDB(dbname_, options));
+
+ // Create a database
+ Status s;
+ options.create_if_missing = true;
+ ASSERT_OK(DB::Open(options, dbname_, &db_));
+ std::string key = std::string("foo");
+ ASSERT_OK(Put(key, "v1"));
+ // Take a snapshot
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_, log_size_for_flush));
+ ASSERT_OK(Put(key, "v2"));
+ ASSERT_EQ("v2", Get(key));
+ ASSERT_OK(Flush());
+ ASSERT_EQ("v2", Get(key));
+ // Open snapshot and verify contents while DB is running
+ options.create_if_missing = false;
+ ASSERT_OK(DB::Open(options, snapshot_name_, &snapshotDB));
+ ASSERT_OK(snapshotDB->Get(roptions, key, &result));
+ ASSERT_EQ("v1", result);
+ delete snapshotDB;
+ snapshotDB = nullptr;
+ delete db_;
+ db_ = nullptr;
+
+ // Destroy original DB
+ ASSERT_OK(DestroyDB(dbname_, options));
+
+ // Open snapshot and verify contents
+ options.create_if_missing = false;
+ dbname_ = snapshot_name_;
+ ASSERT_OK(DB::Open(options, dbname_, &db_));
+ ASSERT_EQ("v1", Get(key));
+ delete db_;
+ db_ = nullptr;
+ ASSERT_OK(DestroyDB(dbname_, options));
+ delete checkpoint;
+
+ // Restore DB name
+ dbname_ = test::PerThreadDBPath(env_, "db_test");
+ }
+}
+
+TEST_F(CheckpointTest, ExportColumnFamilyWithLinks) {
+ // Create a database
+ Status s;
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ CreateAndReopenWithCF({}, options);
+
+ // Helper to verify the number of files in metadata and export dir
+ auto verify_files_exported = [&](const ExportImportFilesMetaData& metadata,
+ int num_files_expected) {
+ ASSERT_EQ(metadata.files.size(), num_files_expected);
+ std::vector<std::string> subchildren;
+ env_->GetChildren(export_path_, &subchildren);
+ int num_children = 0;
+ for (const auto& child : subchildren) {
+ if (child != "." && child != "..") {
+ ++num_children;
+ }
+ }
+ ASSERT_EQ(num_children, num_files_expected);
+ };
+
+ // Test DefaultColumnFamily
+ {
+ const auto key = std::string("foo");
+ ASSERT_OK(Put(key, "v1"));
+
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+
+ // Export the Tables and verify
+ ASSERT_OK(checkpoint->ExportColumnFamily(db_->DefaultColumnFamily(),
+ export_path_, &metadata_));
+ verify_files_exported(*metadata_, 1);
+ ASSERT_EQ(metadata_->db_comparator_name, options.comparator->Name());
+ test::DestroyDir(env_, export_path_);
+ delete metadata_;
+ metadata_ = nullptr;
+
+ // Check again after compaction
+ CompactAll();
+ ASSERT_OK(Put(key, "v2"));
+ ASSERT_OK(checkpoint->ExportColumnFamily(db_->DefaultColumnFamily(),
+ export_path_, &metadata_));
+ verify_files_exported(*metadata_, 2);
+ ASSERT_EQ(metadata_->db_comparator_name, options.comparator->Name());
+ test::DestroyDir(env_, export_path_);
+ delete metadata_;
+ metadata_ = nullptr;
+ delete checkpoint;
+ }
+
+ // Test non default column family with non default comparator
+ {
+ auto cf_options = CurrentOptions();
+ cf_options.comparator = ReverseBytewiseComparator();
+ ASSERT_OK(db_->CreateColumnFamily(cf_options, "yoyo", &cfh_reverse_comp_));
+
+ const auto key = std::string("foo");
+ ASSERT_OK(db_->Put(WriteOptions(), cfh_reverse_comp_, key, "v1"));
+
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+
+ // Export the Tables and verify
+ ASSERT_OK(checkpoint->ExportColumnFamily(cfh_reverse_comp_, export_path_,
+ &metadata_));
+ verify_files_exported(*metadata_, 1);
+ ASSERT_EQ(metadata_->db_comparator_name,
+ ReverseBytewiseComparator()->Name());
+ delete checkpoint;
+ }
+}
+
+TEST_F(CheckpointTest, ExportColumnFamilyNegativeTest) {
+ // Create a database
+ Status s;
+ auto options = CurrentOptions();
+ options.create_if_missing = true;
+ CreateAndReopenWithCF({}, options);
+
+ const auto key = std::string("foo");
+ ASSERT_OK(Put(key, "v1"));
+
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+
+ // Export onto existing directory
+ env_->CreateDirIfMissing(export_path_);
+ ASSERT_EQ(checkpoint->ExportColumnFamily(db_->DefaultColumnFamily(),
+ export_path_, &metadata_),
+ Status::InvalidArgument("Specified export_dir exists"));
+ test::DestroyDir(env_, export_path_);
+
+ // Export with invalid directory specification
+ export_path_ = "";
+ ASSERT_EQ(checkpoint->ExportColumnFamily(db_->DefaultColumnFamily(),
+ export_path_, &metadata_),
+ Status::InvalidArgument("Specified export_dir invalid"));
+ delete checkpoint;
+}
+
+TEST_F(CheckpointTest, CheckpointCF) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"one", "two", "three", "four", "five"}, options);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"CheckpointTest::CheckpointCF:2", "DBImpl::GetLiveFiles:2"},
+ {"DBImpl::GetLiveFiles:1", "CheckpointTest::CheckpointCF:1"}});
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(Put(0, "Default", "Default"));
+ ASSERT_OK(Put(1, "one", "one"));
+ ASSERT_OK(Put(2, "two", "two"));
+ ASSERT_OK(Put(3, "three", "three"));
+ ASSERT_OK(Put(4, "four", "four"));
+ ASSERT_OK(Put(5, "five", "five"));
+
+ DB* snapshotDB;
+ ReadOptions roptions;
+ std::string result;
+ std::vector<ColumnFamilyHandle*> cphandles;
+
+ Status s;
+ // Take a snapshot
+ ROCKSDB_NAMESPACE::port::Thread t([&]() {
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ });
+ TEST_SYNC_POINT("CheckpointTest::CheckpointCF:1");
+ ASSERT_OK(Put(0, "Default", "Default1"));
+ ASSERT_OK(Put(1, "one", "eleven"));
+ ASSERT_OK(Put(2, "two", "twelve"));
+ ASSERT_OK(Put(3, "three", "thirteen"));
+ ASSERT_OK(Put(4, "four", "fourteen"));
+ ASSERT_OK(Put(5, "five", "fifteen"));
+ TEST_SYNC_POINT("CheckpointTest::CheckpointCF:2");
+ t.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ASSERT_OK(Put(1, "one", "twentyone"));
+ ASSERT_OK(Put(2, "two", "twentytwo"));
+ ASSERT_OK(Put(3, "three", "twentythree"));
+ ASSERT_OK(Put(4, "four", "twentyfour"));
+ ASSERT_OK(Put(5, "five", "twentyfive"));
+ ASSERT_OK(Flush());
+
+ // Open snapshot and verify contents while DB is running
+ options.create_if_missing = false;
+ std::vector<std::string> cfs;
+ cfs= {kDefaultColumnFamilyName, "one", "two", "three", "four", "five"};
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < cfs.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(cfs[i], options));
+ }
+ ASSERT_OK(DB::Open(options, snapshot_name_,
+ column_families, &cphandles, &snapshotDB));
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[0], "Default", &result));
+ ASSERT_EQ("Default1", result);
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[1], "one", &result));
+ ASSERT_EQ("eleven", result);
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[2], "two", &result));
+ for (auto h : cphandles) {
+ delete h;
+ }
+ cphandles.clear();
+ delete snapshotDB;
+ snapshotDB = nullptr;
+}
+
+TEST_F(CheckpointTest, CheckpointCFNoFlush) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"one", "two", "three", "four", "five"}, options);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(Put(0, "Default", "Default"));
+ ASSERT_OK(Put(1, "one", "one"));
+ Flush();
+ ASSERT_OK(Put(2, "two", "two"));
+
+ DB* snapshotDB;
+ ReadOptions roptions;
+ std::string result;
+ std::vector<ColumnFamilyHandle*> cphandles;
+
+ Status s;
+ // Take a snapshot
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "DBImpl::BackgroundCallFlush:start", [&](void* /*arg*/) {
+ // Flush should never trigger.
+ FAIL();
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_, 1000000));
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ delete checkpoint;
+ ASSERT_OK(Put(1, "one", "two"));
+ ASSERT_OK(Flush(1));
+ ASSERT_OK(Put(2, "two", "twentytwo"));
+ Close();
+ EXPECT_OK(DestroyDB(dbname_, options));
+
+ // Open snapshot and verify contents while DB is running
+ options.create_if_missing = false;
+ std::vector<std::string> cfs;
+ cfs = {kDefaultColumnFamilyName, "one", "two", "three", "four", "five"};
+ std::vector<ColumnFamilyDescriptor> column_families;
+ for (size_t i = 0; i < cfs.size(); ++i) {
+ column_families.push_back(ColumnFamilyDescriptor(cfs[i], options));
+ }
+ ASSERT_OK(DB::Open(options, snapshot_name_, column_families, &cphandles,
+ &snapshotDB));
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[0], "Default", &result));
+ ASSERT_EQ("Default", result);
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[1], "one", &result));
+ ASSERT_EQ("one", result);
+ ASSERT_OK(snapshotDB->Get(roptions, cphandles[2], "two", &result));
+ ASSERT_EQ("two", result);
+ for (auto h : cphandles) {
+ delete h;
+ }
+ cphandles.clear();
+ delete snapshotDB;
+ snapshotDB = nullptr;
+}
+
+TEST_F(CheckpointTest, CurrentFileModifiedWhileCheckpointing) {
+ Options options = CurrentOptions();
+ options.max_manifest_file_size = 0; // always rollover manifest for file add
+ Reopen(options);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {// Get past the flush in the checkpoint thread before adding any keys to
+ // the db so the checkpoint thread won't hit the WriteManifest
+ // syncpoints.
+ {"DBImpl::GetLiveFiles:1",
+ "CheckpointTest::CurrentFileModifiedWhileCheckpointing:PrePut"},
+ // Roll the manifest during checkpointing right after live files are
+ // snapshotted.
+ {"CheckpointImpl::CreateCheckpoint:SavedLiveFiles1",
+ "VersionSet::LogAndApply:WriteManifest"},
+ {"VersionSet::LogAndApply:WriteManifestDone",
+ "CheckpointImpl::CreateCheckpoint:SavedLiveFiles2"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread t([&]() {
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ });
+ TEST_SYNC_POINT(
+ "CheckpointTest::CurrentFileModifiedWhileCheckpointing:PrePut");
+ ASSERT_OK(Put("Default", "Default1"));
+ ASSERT_OK(Flush());
+ t.join();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ DB* snapshotDB;
+ // Successful Open() implies that CURRENT pointed to the manifest in the
+ // checkpoint.
+ ASSERT_OK(DB::Open(options, snapshot_name_, &snapshotDB));
+ delete snapshotDB;
+ snapshotDB = nullptr;
+}
+
+TEST_F(CheckpointTest, CurrentFileModifiedWhileCheckpointing2PC) {
+ Close();
+ const std::string dbname = test::PerThreadDBPath("transaction_testdb");
+ ASSERT_OK(DestroyDB(dbname, CurrentOptions()));
+ env_->DeleteDir(dbname);
+
+ Options options = CurrentOptions();
+ options.allow_2pc = true;
+ // allow_2pc is implicitly set with tx prepare
+ // options.allow_2pc = true;
+ TransactionDBOptions txn_db_options;
+ TransactionDB* txdb;
+ Status s = TransactionDB::Open(options, txn_db_options, dbname, &txdb);
+ assert(s.ok());
+ ColumnFamilyHandle* cfa;
+ ColumnFamilyHandle* cfb;
+ ColumnFamilyOptions cf_options;
+ ASSERT_OK(txdb->CreateColumnFamily(cf_options, "CFA", &cfa));
+
+ WriteOptions write_options;
+ // Insert something into CFB so lots of log files will be kept
+ // before creating the checkpoint.
+ ASSERT_OK(txdb->CreateColumnFamily(cf_options, "CFB", &cfb));
+ ASSERT_OK(txdb->Put(write_options, cfb, "", ""));
+
+ ReadOptions read_options;
+ std::string value;
+ TransactionOptions txn_options;
+ Transaction* txn = txdb->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("xid");
+ ASSERT_OK(s);
+ ASSERT_EQ(txdb->GetTransactionByName("xid"), txn);
+
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ s = txn->Put(cfa, Slice("foocfa"), Slice("barcfa"));
+ ASSERT_OK(s);
+ // Writing prepare into middle of first WAL, then flush WALs many times
+ for (int i = 1; i <= 100000; i++) {
+ Transaction* tx = txdb->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(tx->SetName("x"));
+ ASSERT_OK(tx->Put(Slice(std::to_string(i)), Slice("val")));
+ ASSERT_OK(tx->Put(cfa, Slice("aaa"), Slice("111")));
+ ASSERT_OK(tx->Prepare());
+ ASSERT_OK(tx->Commit());
+ if (i % 10000 == 0) {
+ txdb->Flush(FlushOptions());
+ }
+ if (i == 88888) {
+ ASSERT_OK(txn->Prepare());
+ }
+ delete tx;
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"CheckpointImpl::CreateCheckpoint:SavedLiveFiles1",
+ "CheckpointTest::CurrentFileModifiedWhileCheckpointing2PC:PreCommit"},
+ {"CheckpointTest::CurrentFileModifiedWhileCheckpointing2PC:PostCommit",
+ "CheckpointImpl::CreateCheckpoint:SavedLiveFiles2"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::port::Thread t([&]() {
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(txdb, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ });
+ TEST_SYNC_POINT(
+ "CheckpointTest::CurrentFileModifiedWhileCheckpointing2PC:PreCommit");
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ TEST_SYNC_POINT(
+ "CheckpointTest::CurrentFileModifiedWhileCheckpointing2PC:PostCommit");
+ t.join();
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ // No more than two logs files should exist.
+ std::vector<std::string> files;
+ env_->GetChildren(snapshot_name_, &files);
+ int num_log_files = 0;
+ for (auto& file : files) {
+ uint64_t num;
+ FileType type;
+ WalFileType log_type;
+ if (ParseFileName(file, &num, &type, &log_type) && type == kLogFile) {
+ num_log_files++;
+ }
+ }
+ // One flush after preapare + one outstanding file before checkpoint + one log
+ // file generated after checkpoint.
+ ASSERT_LE(num_log_files, 3);
+
+ TransactionDB* snapshotDB;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFA", ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFB", ColumnFamilyOptions()));
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> cf_handles;
+ ASSERT_OK(TransactionDB::Open(options, txn_db_options, snapshot_name_,
+ column_families, &cf_handles, &snapshotDB));
+ ASSERT_OK(snapshotDB->Get(read_options, "foo", &value));
+ ASSERT_EQ(value, "bar");
+ ASSERT_OK(snapshotDB->Get(read_options, cf_handles[1], "foocfa", &value));
+ ASSERT_EQ(value, "barcfa");
+
+ delete cfa;
+ delete cfb;
+ delete cf_handles[0];
+ delete cf_handles[1];
+ delete cf_handles[2];
+ delete snapshotDB;
+ snapshotDB = nullptr;
+ delete txdb;
+}
+
+TEST_F(CheckpointTest, CheckpointInvalidDirectoryName) {
+ for (std::string checkpoint_dir : {"", "/", "////"}) {
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_TRUE(checkpoint->CreateCheckpoint("").IsInvalidArgument());
+ delete checkpoint;
+ }
+}
+
+TEST_F(CheckpointTest, CheckpointWithParallelWrites) {
+ // When run with TSAN, this exposes the data race fixed in
+ // https://github.com/facebook/rocksdb/pull/3603
+ ASSERT_OK(Put("key1", "val1"));
+ port::Thread thread([this]() { ASSERT_OK(Put("key2", "val2")); });
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ thread.join();
+}
+
+TEST_F(CheckpointTest, CheckpointWithUnsyncedDataDropped) {
+ Options options = CurrentOptions();
+ std::unique_ptr<FaultInjectionTestEnv> env(new FaultInjectionTestEnv(env_));
+ options.env = env.get();
+ Reopen(options);
+ ASSERT_OK(Put("key1", "val1"));
+ Checkpoint* checkpoint;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ env->DropUnsyncedFileData();
+
+ // make sure it's openable even though whatever data that wasn't synced got
+ // dropped.
+ options.env = env_;
+ DB* snapshot_db;
+ ASSERT_OK(DB::Open(options, snapshot_name_, &snapshot_db));
+ ReadOptions read_opts;
+ std::string get_result;
+ ASSERT_OK(snapshot_db->Get(read_opts, "key1", &get_result));
+ ASSERT_EQ("val1", get_result);
+ delete snapshot_db;
+ delete db_;
+ db_ = nullptr;
+}
+
+TEST_F(CheckpointTest, CheckpointReadOnlyDB) {
+ ASSERT_OK(Put("foo", "foo_value"));
+ ASSERT_OK(Flush());
+ Close();
+ Options options = CurrentOptions();
+ ASSERT_OK(ReadOnlyReopen(options));
+ Checkpoint* checkpoint = nullptr;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ checkpoint = nullptr;
+ Close();
+ DB* snapshot_db = nullptr;
+ ASSERT_OK(DB::Open(options, snapshot_name_, &snapshot_db));
+ ReadOptions read_opts;
+ std::string get_result;
+ ASSERT_OK(snapshot_db->Get(read_opts, "foo", &get_result));
+ ASSERT_EQ("foo_value", get_result);
+ delete snapshot_db;
+}
+
+TEST_F(CheckpointTest, CheckpointReadOnlyDBWithMultipleColumnFamilies) {
+ Options options = CurrentOptions();
+ CreateAndReopenWithCF({"pikachu", "eevee"}, options);
+ for (int i = 0; i != 3; ++i) {
+ ASSERT_OK(Put(i, "foo", "foo_value"));
+ ASSERT_OK(Flush(i));
+ }
+ Close();
+ Status s = ReadOnlyReopenWithColumnFamilies(
+ {kDefaultColumnFamilyName, "pikachu", "eevee"}, options);
+ ASSERT_OK(s);
+ Checkpoint* checkpoint = nullptr;
+ ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
+ ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
+ delete checkpoint;
+ checkpoint = nullptr;
+ Close();
+
+ std::vector<ColumnFamilyDescriptor> column_families{
+ {kDefaultColumnFamilyName, options},
+ {"pikachu", options},
+ {"eevee", options}};
+ DB* snapshot_db = nullptr;
+ std::vector<ColumnFamilyHandle*> snapshot_handles;
+ s = DB::Open(options, snapshot_name_, column_families, &snapshot_handles,
+ &snapshot_db);
+ ASSERT_OK(s);
+ ReadOptions read_opts;
+ for (int i = 0; i != 3; ++i) {
+ std::string get_result;
+ s = snapshot_db->Get(read_opts, snapshot_handles[i], "foo", &get_result);
+ ASSERT_OK(s);
+ ASSERT_EQ("foo_value", get_result);
+ }
+
+ for (auto snapshot_h : snapshot_handles) {
+ delete snapshot_h;
+ }
+ snapshot_handles.clear();
+ delete snapshot_db;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as Checkpoint is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc b/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
new file mode 100644
index 00000000..c97eef41
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <string>
+
+#include "rocksdb/slice.h"
+#include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const char* RemoveEmptyValueCompactionFilter::Name() const {
+ return "RemoveEmptyValueCompactionFilter";
+}
+
+bool RemoveEmptyValueCompactionFilter::Filter(int /*level*/,
+ const Slice& /*key*/,
+ const Slice& existing_value,
+ std::string* /*new_value*/,
+ bool* /*value_changed*/) const {
+ // remove kv pairs that have empty values
+ return existing_value.empty();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.h b/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.h
new file mode 100644
index 00000000..f5dbec90
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/compaction_filters/remove_emptyvalue_compactionfilter.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#pragma once
+
+#include <string>
+
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RemoveEmptyValueCompactionFilter : public CompactionFilter {
+ public:
+ const char* Name() const override;
+ bool Filter(int level,
+ const Slice& key,
+ const Slice& existing_value,
+ std::string* new_value,
+ bool* value_changed) const override;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/convenience/info_log_finder.cc b/storage/rocksdb/rocksdb/utilities/convenience/info_log_finder.cc
new file mode 100644
index 00000000..980262f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/convenience/info_log_finder.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 Facebook.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "rocksdb/utilities/info_log_finder.h"
+#include "file/filename.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status GetInfoLogList(DB* db, std::vector<std::string>* info_log_list) {
+ if (!db) {
+ return Status::InvalidArgument("DB pointer is not valid");
+ }
+ std::string parent_path;
+ const Options& options = db->GetOptions();
+ return GetInfoLogFiles(options.env, options.db_log_dir, db->GetName(),
+ &parent_path, info_log_list);
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/debug.cc b/storage/rocksdb/rocksdb/utilities/debug.cc
new file mode 100644
index 00000000..b51f9da0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/debug.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/debug.h"
+
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key,
+ size_t max_num_ikeys,
+ std::vector<KeyVersion>* key_versions) {
+ if (nullptr == db) {
+ return Status::InvalidArgument("db cannot be null.");
+ }
+ return GetAllKeyVersions(db, db->DefaultColumnFamily(), begin_key, end_key,
+ max_num_ikeys, key_versions);
+}
+
+Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, Slice begin_key,
+ Slice end_key, size_t max_num_ikeys,
+ std::vector<KeyVersion>* key_versions) {
+ if (nullptr == db) {
+ return Status::InvalidArgument("db cannot be null.");
+ }
+ if (nullptr == cfh) {
+ return Status::InvalidArgument("Column family handle cannot be null.");
+ }
+ if (nullptr == key_versions) {
+ return Status::InvalidArgument("key_versions cannot be null.");
+ }
+ key_versions->clear();
+
+ DBImpl* idb = static_cast<DBImpl*>(db->GetRootDB());
+ auto icmp = InternalKeyComparator(idb->GetOptions(cfh).comparator);
+ ReadRangeDelAggregator range_del_agg(&icmp,
+ kMaxSequenceNumber /* upper_bound */);
+ Arena arena;
+ ScopedArenaIterator iter(idb->NewInternalIterator(&arena, &range_del_agg,
+ kMaxSequenceNumber, cfh));
+
+ if (!begin_key.empty()) {
+ InternalKey ikey;
+ ikey.SetMinPossibleForUserKey(begin_key);
+ iter->Seek(ikey.Encode());
+ } else {
+ iter->SeekToFirst();
+ }
+
+ size_t num_keys = 0;
+ for (; iter->Valid(); iter->Next()) {
+ ParsedInternalKey ikey;
+ if (!ParseInternalKey(iter->key(), &ikey)) {
+ return Status::Corruption("Internal Key [" + iter->key().ToString() +
+ "] parse error!");
+ }
+
+ if (!end_key.empty() &&
+ icmp.user_comparator()->Compare(ikey.user_key, end_key) > 0) {
+ break;
+ }
+
+ key_versions->emplace_back(ikey.user_key.ToString() /* _user_key */,
+ iter->value().ToString() /* _value */,
+ ikey.sequence /* _sequence */,
+ static_cast<int>(ikey.type) /* _type */);
+ if (++num_keys >= max_num_ikeys) {
+ break;
+ }
+ }
+ return Status::OK();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/env_librados.cc b/storage/rocksdb/rocksdb/utilities/env_librados.cc
new file mode 100644
index 00000000..5842edbc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_librados.cc
@@ -0,0 +1,1497 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#include "rocksdb/utilities/env_librados.h"
+#include "util/random.h"
+#include <mutex>
+#include <cstdlib>
+
+namespace ROCKSDB_NAMESPACE {
+/* GLOBAL DIFINE */
+// #define DEBUG
+#ifdef DEBUG
+#include <cstdio>
+#include <sys/syscall.h>
+#include <unistd.h>
+#define LOG_DEBUG(...) do{\
+ printf("[%ld:%s:%i:%s]", syscall(SYS_gettid), __FILE__, __LINE__, __FUNCTION__);\
+ printf(__VA_ARGS__);\
+ }while(0)
+#else
+#define LOG_DEBUG(...)
+#endif
+
+/* GLOBAL CONSTANT */
+const char *default_db_name = "default_envlibrados_db";
+const char *default_pool_name = "default_envlibrados_pool";
+const char *default_config_path = "CEPH_CONFIG_PATH"; // the env variable name of ceph configure file
+// maximum dir/file that can store in the fs
+const int MAX_ITEMS_IN_FS = 1 << 30;
+// root dir tag
+const std::string ROOT_DIR_KEY = "/";
+const std::string DIR_ID_VALUE = "<DIR>";
+
+/**
+ * @brief convert error code to status
+ * @details Convert internal linux error code to Status
+ *
+ * @param r [description]
+ * @return [description]
+ */
+Status err_to_status(int r)
+{
+ switch (r) {
+ case 0:
+ return Status::OK();
+ case -ENOENT:
+ return Status::IOError();
+ case -ENODATA:
+ case -ENOTDIR:
+ return Status::NotFound(Status::kNone);
+ case -EINVAL:
+ return Status::InvalidArgument(Status::kNone);
+ case -EIO:
+ return Status::IOError(Status::kNone);
+ default:
+ // FIXME :(
+ assert(0 == "unrecognized error code");
+ return Status::NotSupported(Status::kNone);
+ }
+}
+
+/**
+ * @brief split file path into dir path and file name
+ * @details
+ * Because rocksdb only need a 2-level structure (dir/file), all input path will be shortened to dir/file format
+ * For example:
+ * b/c => dir '/b', file 'c'
+ * /a/b/c => dir '/b', file 'c'
+ *
+ * @param fn [description]
+ * @param dir [description]
+ * @param file [description]
+ */
+void split(const std::string &fn, std::string *dir, std::string *file) {
+ LOG_DEBUG("[IN]%s\n", fn.c_str());
+ int pos = fn.size() - 1;
+ while ('/' == fn[pos]) --pos;
+ size_t fstart = fn.rfind('/', pos);
+ *file = fn.substr(fstart + 1, pos - fstart);
+
+ pos = fstart;
+ while (pos >= 0 && '/' == fn[pos]) --pos;
+
+ if (pos < 0) {
+ *dir = "/";
+ } else {
+ size_t dstart = fn.rfind('/', pos);
+ *dir = fn.substr(dstart + 1, pos - dstart);
+ *dir = std::string("/") + *dir;
+ }
+
+ LOG_DEBUG("[OUT]%s | %s\n", dir->c_str(), file->c_str());
+}
+
+// A file abstraction for reading sequentially through a file
+class LibradosSequentialFile : public SequentialFile {
+ librados::IoCtx * _io_ctx;
+ std::string _fid;
+ std::string _hint;
+ int _offset;
+public:
+ LibradosSequentialFile(librados::IoCtx * io_ctx, std::string fid, std::string hint):
+ _io_ctx(io_ctx), _fid(fid), _hint(hint), _offset(0) {}
+
+ ~LibradosSequentialFile() {}
+
+ /**
+ * @brief read file
+ * @details
+ * Read up to "n" bytes from the file. "scratch[0..n-1]" may be
+ * written by this routine. Sets "*result" to the data that was
+ * read (including if fewer than "n" bytes were successfully read).
+ * May set "*result" to point at data in "scratch[0..n-1]", so
+ * "scratch[0..n-1]" must be live when "*result" is used.
+ * If an error was encountered, returns a non-OK status.
+ *
+ * REQUIRES: External synchronization
+ *
+ * @param n [description]
+ * @param result [description]
+ * @param scratch [description]
+ * @return [description]
+ */
+ Status Read(size_t n, Slice* result, char* scratch) {
+ LOG_DEBUG("[IN]%i\n", (int)n);
+ librados::bufferlist buffer;
+ Status s;
+ int r = _io_ctx->read(_fid, buffer, n, _offset);
+ if (r >= 0) {
+ buffer.begin().copy(r, scratch);
+ *result = Slice(scratch, r);
+ _offset += r;
+ s = Status::OK();
+ } else {
+ s = err_to_status(r);
+ if (s == Status::IOError()) {
+ *result = Slice();
+ s = Status::OK();
+ }
+ }
+ LOG_DEBUG("[OUT]%s, %i, %s\n", s.ToString().c_str(), (int)r, buffer.c_str());
+ return s;
+ }
+
+ /**
+ * @brief skip "n" bytes from the file
+ * @details
+ * Skip "n" bytes from the file. This is guaranteed to be no
+ * slower that reading the same data, but may be faster.
+ *
+ * If end of file is reached, skipping will stop at the end of the
+ * file, and Skip will return OK.
+ *
+ * REQUIRES: External synchronization
+ *
+ * @param n [description]
+ * @return [description]
+ */
+ Status Skip(uint64_t n) {
+ _offset += n;
+ return Status::OK();
+ }
+
+ /**
+ * @brief noop
+ * @details
+ * rocksdb has it's own caching capabilities that we should be able to use,
+ * without relying on a cache here. This can safely be a no-op.
+ *
+ * @param offset [description]
+ * @param length [description]
+ *
+ * @return [description]
+ */
+ Status InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+ }
+};
+
+// A file abstraction for randomly reading the contents of a file.
+class LibradosRandomAccessFile : public RandomAccessFile {
+ librados::IoCtx * _io_ctx;
+ std::string _fid;
+ std::string _hint;
+public:
+ LibradosRandomAccessFile(librados::IoCtx * io_ctx, std::string fid, std::string hint):
+ _io_ctx(io_ctx), _fid(fid), _hint(hint) {}
+
+ ~LibradosRandomAccessFile() {}
+
+ /**
+ * @brief read file
+ * @details similar to LibradosSequentialFile::Read
+ *
+ * @param offset [description]
+ * @param n [description]
+ * @param result [description]
+ * @param scratch [description]
+ * @return [description]
+ */
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const {
+ LOG_DEBUG("[IN]%i\n", (int)n);
+ librados::bufferlist buffer;
+ Status s;
+ int r = _io_ctx->read(_fid, buffer, n, offset);
+ if (r >= 0) {
+ buffer.begin().copy(r, scratch);
+ *result = Slice(scratch, r);
+ s = Status::OK();
+ } else {
+ s = err_to_status(r);
+ if (s == Status::IOError()) {
+ *result = Slice();
+ s = Status::OK();
+ }
+ }
+ LOG_DEBUG("[OUT]%s, %i, %s\n", s.ToString().c_str(), (int)r, buffer.c_str());
+ return s;
+ }
+
+ /**
+ * @brief [brief description]
+ * @details Get unique id for each file and guarantee this id is different for each file
+ *
+ * @param id [description]
+ * @param max_size max size of id, it shoud be larger than 16
+ *
+ * @return [description]
+ */
+ size_t GetUniqueId(char* id, size_t max_size) const {
+ // All fid has the same db_id prefix, so we need to ignore db_id prefix
+ size_t s = std::min(max_size, _fid.size());
+ strncpy(id, _fid.c_str() + (_fid.size() - s), s);
+ id[s - 1] = '\0';
+ return s;
+ };
+
+ //enum AccessPattern { NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED };
+ void Hint(AccessPattern pattern) {
+ /* Do nothing */
+ }
+
+ /**
+ * @brief noop
+ * @details [long description]
+ *
+ * @param offset [description]
+ * @param length [description]
+ *
+ * @return [description]
+ */
+ Status InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+ }
+};
+
+
+// A file abstraction for sequential writing. The implementation
+// must provide buffering since callers may append small fragments
+// at a time to the file.
+class LibradosWritableFile : public WritableFile {
+ librados::IoCtx * _io_ctx;
+ std::string _fid;
+ std::string _hint;
+ const EnvLibrados * const _env;
+
+ std::mutex _mutex; // used to protect modification of all following variables
+ librados::bufferlist _buffer; // write buffer
+ uint64_t _buffer_size; // write buffer size
+ uint64_t _file_size; // this file size doesn't include buffer size
+
+ /**
+ * @brief assuming caller holds lock
+ * @details [long description]
+ * @return [description]
+ */
+ int _SyncLocked() {
+ // 1. sync append data to RADOS
+ int r = _io_ctx->append(_fid, _buffer, _buffer_size);
+ assert(r >= 0);
+
+ // 2. update local variables
+ if (0 == r) {
+ _buffer.clear();
+ _file_size += _buffer_size;
+ _buffer_size = 0;
+ }
+
+ return r;
+ }
+
+ public:
+ LibradosWritableFile(librados::IoCtx* io_ctx, std::string fid,
+ std::string hint, const EnvLibrados* const env,
+ const EnvOptions& options)
+ : WritableFile(options),
+ _io_ctx(io_ctx),
+ _fid(fid),
+ _hint(hint),
+ _env(env),
+ _buffer(),
+ _buffer_size(0),
+ _file_size(0) {
+ int ret = _io_ctx->stat(_fid, &_file_size, nullptr);
+
+ // if file not exist
+ if (ret < 0) {
+ _file_size = 0;
+ }
+ }
+
+ ~LibradosWritableFile() {
+ // sync before closeing writable file
+ Sync();
+ }
+
+ /**
+ * @brief append data to file
+ * @details
+ * Append will save all written data in buffer util buffer size
+ * reaches buffer max size. Then, it will write buffer into rados
+ *
+ * @param data [description]
+ * @return [description]
+ */
+ Status Append(const Slice& data) {
+ // append buffer
+ LOG_DEBUG("[IN] %i | %s\n", (int)data.size(), data.data());
+ int r = 0;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ _buffer.append(data.data(), data.size());
+ _buffer_size += data.size();
+
+ if (_buffer_size > _env->_write_buffer_size) {
+ r = _SyncLocked();
+ }
+
+ LOG_DEBUG("[OUT] %i\n", r);
+ return err_to_status(r);
+ }
+
+ /**
+ * @brief not supported
+ * @details [long description]
+ * @return [description]
+ */
+ Status PositionedAppend(
+ const Slice& /* data */,
+ uint64_t /* offset */) {
+ return Status::NotSupported();
+ }
+
+ /**
+ * @brief truncate file to assigned size
+ * @details [long description]
+ *
+ * @param size [description]
+ * @return [description]
+ */
+ Status Truncate(uint64_t size) {
+ LOG_DEBUG("[IN]%lld|%lld|%lld\n", (long long)size, (long long)_file_size, (long long)_buffer_size);
+ int r = 0;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ if (_file_size > size) {
+ r = _io_ctx->trunc(_fid, size);
+
+ if (r == 0) {
+ _buffer.clear();
+ _buffer_size = 0;
+ _file_size = size;
+ }
+ } else if (_file_size == size) {
+ _buffer.clear();
+ _buffer_size = 0;
+ } else {
+ librados::bufferlist tmp;
+ tmp.claim(_buffer);
+ _buffer.substr_of(tmp, 0, size - _file_size);
+ _buffer_size = size - _file_size;
+ }
+
+ LOG_DEBUG("[OUT] %i\n", r);
+ return err_to_status(r);
+ }
+
+ /**
+ * @brief close file
+ * @details [long description]
+ * @return [description]
+ */
+ Status Close() {
+ LOG_DEBUG("%s | %lld | %lld\n", _hint.c_str(), (long long)_buffer_size, (long long)_file_size);
+ return Sync();
+ }
+
+ /**
+ * @brief flush file,
+ * @details initiate an aio write and not wait
+ *
+ * @return [description]
+ */
+ Status Flush() {
+ librados::AioCompletion *write_completion = librados::Rados::aio_create_completion();
+ int r = 0;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ r = _io_ctx->aio_append(_fid, write_completion, _buffer, _buffer_size);
+
+ if (0 == r) {
+ _file_size += _buffer_size;
+ _buffer.clear();
+ _buffer_size = 0;
+ }
+
+ write_completion->release();
+
+ return err_to_status(r);
+ }
+
+ /**
+ * @brief write buffer data to rados
+ * @details initiate an aio write and wait for result
+ * @return [description]
+ */
+ Status Sync() { // sync data
+ int r = 0;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ if (_buffer_size > 0) {
+ r = _SyncLocked();
+ }
+
+ return err_to_status(r);
+ }
+
+ /**
+ * @brief [brief description]
+ * @details [long description]
+ * @return true if Sync() and Fsync() are safe to call concurrently with Append()and Flush().
+ */
+ bool IsSyncThreadSafe() const {
+ return true;
+ }
+
+ /**
+ * @brief Indicates the upper layers if the current WritableFile implementation uses direct IO.
+ * @details [long description]
+ * @return [description]
+ */
+ bool use_direct_io() const {
+ return false;
+ }
+
+ /**
+ * @brief Get file size
+ * @details
+ * This API will use cached file_size.
+ * @return [description]
+ */
+ uint64_t GetFileSize() {
+ LOG_DEBUG("%lld|%lld\n", (long long)_buffer_size, (long long)_file_size);
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ int file_size = _file_size + _buffer_size;
+
+ return file_size;
+ }
+
+ /**
+ * @brief For documentation, refer to RandomAccessFile::GetUniqueId()
+ * @details [long description]
+ *
+ * @param id [description]
+ * @param max_size [description]
+ *
+ * @return [description]
+ */
+ size_t GetUniqueId(char* id, size_t max_size) const {
+ // All fid has the same db_id prefix, so we need to ignore db_id prefix
+ size_t s = std::min(max_size, _fid.size());
+ strncpy(id, _fid.c_str() + (_fid.size() - s), s);
+ id[s - 1] = '\0';
+ return s;
+ }
+
+ /**
+ * @brief noop
+ * @details [long description]
+ *
+ * @param offset [description]
+ * @param length [description]
+ *
+ * @return [description]
+ */
+ Status InvalidateCache(size_t offset, size_t length) {
+ return Status::OK();
+ }
+
+ using WritableFile::RangeSync;
+ /**
+ * @brief No RangeSync support, just call Sync()
+ * @details [long description]
+ *
+ * @param offset [description]
+ * @param nbytes [description]
+ *
+ * @return [description]
+ */
+ Status RangeSync(off_t offset, off_t nbytes) {
+ return Sync();
+ }
+
+protected:
+ using WritableFile::Allocate;
+ /**
+ * @brief noop
+ * @details [long description]
+ *
+ * @param offset [description]
+ * @param len [description]
+ *
+ * @return [description]
+ */
+ Status Allocate(off_t offset, off_t len) {
+ return Status::OK();
+ }
+};
+
+
+// Directory object represents collection of files and implements
+// filesystem operations that can be executed on directories.
+class LibradosDirectory : public Directory {
+ librados::IoCtx * _io_ctx;
+ std::string _fid;
+public:
+ explicit LibradosDirectory(librados::IoCtx * io_ctx, std::string fid):
+ _io_ctx(io_ctx), _fid(fid) {}
+
+ // Fsync directory. Can be called concurrently from multiple threads.
+ Status Fsync() {
+ return Status::OK();
+ }
+};
+
+// Identifies a locked file.
+// This is exclusive lock and can't nested lock by same thread
+class LibradosFileLock : public FileLock {
+ librados::IoCtx * _io_ctx;
+ const std::string _obj_name;
+ const std::string _lock_name;
+ const std::string _cookie;
+ int lock_state;
+public:
+ LibradosFileLock(
+ librados::IoCtx * io_ctx,
+ const std::string obj_name):
+ _io_ctx(io_ctx),
+ _obj_name(obj_name),
+ _lock_name("lock_name"),
+ _cookie("cookie") {
+
+ // TODO: the lock will never expire. It may cause problem if the process crash or abnormally exit.
+ while (!_io_ctx->lock_exclusive(
+ _obj_name,
+ _lock_name,
+ _cookie,
+ "description", nullptr, 0));
+ }
+
+ ~LibradosFileLock() {
+ _io_ctx->unlock(_obj_name, _lock_name, _cookie);
+ }
+};
+
+
+// --------------------
+// --- EnvLibrados ----
+// --------------------
+/**
+ * @brief EnvLibrados ctor
+ * @details [long description]
+ *
+ * @param db_name unique database name
+ * @param config_path the configure file path for rados
+ */
+EnvLibrados::EnvLibrados(const std::string& db_name,
+ const std::string& config_path,
+ const std::string& db_pool)
+ : EnvLibrados("client.admin",
+ "ceph",
+ 0,
+ db_name,
+ config_path,
+ db_pool,
+ "/wal",
+ db_pool,
+ 1 << 20) {}
+
+/**
+ * @brief EnvLibrados ctor
+ * @details [long description]
+ *
+ * @param client_name first 3 parameters is for RADOS client init
+ * @param cluster_name
+ * @param flags
+ * @param db_name unique database name, used as db_id key
+ * @param config_path the configure file path for rados
+ * @param db_pool the pool for db data
+ * @param wal_pool the pool for WAL data
+ * @param write_buffer_size WritableFile buffer max size
+ */
+EnvLibrados::EnvLibrados(const std::string& client_name,
+ const std::string& cluster_name,
+ const uint64_t flags,
+ const std::string& db_name,
+ const std::string& config_path,
+ const std::string& db_pool,
+ const std::string& wal_dir,
+ const std::string& wal_pool,
+ const uint64_t write_buffer_size)
+ : EnvWrapper(Env::Default()),
+ _client_name(client_name),
+ _cluster_name(cluster_name),
+ _flags(flags),
+ _db_name(db_name),
+ _config_path(config_path),
+ _db_pool_name(db_pool),
+ _wal_dir(wal_dir),
+ _wal_pool_name(wal_pool),
+ _write_buffer_size(write_buffer_size) {
+ int ret = 0;
+
+ // 1. create a Rados object and initialize it
+ ret = _rados.init2(_client_name.c_str(), _cluster_name.c_str(), _flags); // just use the client.admin keyring
+ if (ret < 0) { // let's handle any error that might have come back
+ std::cerr << "couldn't initialize rados! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ // 2. read configure file
+ ret = _rados.conf_read_file(_config_path.c_str());
+ if (ret < 0) {
+ // This could fail if the config file is malformed, but it'd be hard.
+ std::cerr << "failed to parse config file " << _config_path
+ << "! error" << ret << std::endl;
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ // 3. we actually connect to the cluster
+ ret = _rados.connect();
+ if (ret < 0) {
+ std::cerr << "couldn't connect to cluster! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ // 4. create db_pool if not exist
+ ret = _rados.pool_create(_db_pool_name.c_str());
+ if (ret < 0 && ret != -EEXIST && ret != -EPERM) {
+ std::cerr << "couldn't create pool! error " << ret << std::endl;
+ goto out;
+ }
+
+ // 5. create db_pool_ioctx
+ ret = _rados.ioctx_create(_db_pool_name.c_str(), _db_pool_ioctx);
+ if (ret < 0) {
+ std::cerr << "couldn't set up ioctx! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ // 6. create wal_pool if not exist
+ ret = _rados.pool_create(_wal_pool_name.c_str());
+ if (ret < 0 && ret != -EEXIST && ret != -EPERM) {
+ std::cerr << "couldn't create pool! error " << ret << std::endl;
+ goto out;
+ }
+
+ // 7. create wal_pool_ioctx
+ ret = _rados.ioctx_create(_wal_pool_name.c_str(), _wal_pool_ioctx);
+ if (ret < 0) {
+ std::cerr << "couldn't set up ioctx! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ // 8. add root dir
+ _AddFid(ROOT_DIR_KEY, DIR_ID_VALUE);
+
+out:
+ LOG_DEBUG("rados connect result code : %i\n", ret);
+}
+
+/****************************************************
+ private functions to handle fid operation.
+ Dir also have fid, but the value is DIR_ID_VALUE
+****************************************************/
+
+/**
+ * @brief generate a new fid
+ * @details [long description]
+ * @return [description]
+ */
+std::string EnvLibrados::_CreateFid() {
+ return _db_name + "." + GenerateUniqueId();
+}
+
+/**
+ * @brief get fid
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @param fid [description]
+ *
+ * @return
+ * Status::OK()
+ * Status::NotFound()
+ */
+Status EnvLibrados::_GetFid(
+ const std::string &fname,
+ std::string& fid) {
+ std::set<std::string> keys;
+ std::map<std::string, librados::bufferlist> kvs;
+ keys.insert(fname);
+ int r = _db_pool_ioctx.omap_get_vals_by_keys(_db_name, keys, &kvs);
+
+ if (0 == r && 0 == kvs.size()) {
+ return Status::NotFound();
+ } else if (0 == r && 0 != kvs.size()) {
+ fid.assign(kvs[fname].c_str(), kvs[fname].length());
+ return Status::OK();
+ } else {
+ return err_to_status(r);
+ }
+}
+
+/**
+ * @brief rename fid
+ * @details Only modify object in rados once,
+ * so this rename operation is atomic in term of rados
+ *
+ * @param old_fname [description]
+ * @param new_fname [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::_RenameFid(const std::string& old_fname,
+ const std::string& new_fname) {
+ std::string fid;
+ Status s = _GetFid(old_fname, fid);
+
+ if (Status::OK() != s) {
+ return s;
+ }
+
+ librados::bufferlist bl;
+ std::set<std::string> keys;
+ std::map<std::string, librados::bufferlist> kvs;
+ librados::ObjectWriteOperation o;
+ bl.append(fid);
+ keys.insert(old_fname);
+ kvs[new_fname] = bl;
+ o.omap_rm_keys(keys);
+ o.omap_set(kvs);
+ int r = _db_pool_ioctx.operate(_db_name, &o);
+ return err_to_status(r);
+}
+
+/**
+ * @brief add <file path, fid> to metadata object. It may overwrite exist key.
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @param fid [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::_AddFid(
+ const std::string& fname,
+ const std::string& fid) {
+ std::map<std::string, librados::bufferlist> kvs;
+ librados::bufferlist value;
+ value.append(fid);
+ kvs[fname] = value;
+ int r = _db_pool_ioctx.omap_set(_db_name, kvs);
+ return err_to_status(r);
+}
+
+/**
+ * @brief return subfile names of dir.
+ * @details
+ * RocksDB has a 2-level structure, so all keys
+ * that have dir as prefix are subfiles of dir.
+ * So we can just return these files' name.
+ *
+ * @param dir [description]
+ * @param result [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::_GetSubFnames(
+ const std::string& dir,
+ std::vector<std::string> * result
+) {
+ std::string start_after(dir);
+ std::string filter_prefix(dir);
+ std::map<std::string, librados::bufferlist> kvs;
+ _db_pool_ioctx.omap_get_vals(_db_name,
+ start_after, filter_prefix,
+ MAX_ITEMS_IN_FS, &kvs);
+
+ result->clear();
+ for (auto i = kvs.begin(); i != kvs.end(); i++) {
+ result->push_back(i->first.substr(dir.size() + 1));
+ }
+ return Status::OK();
+}
+
+/**
+ * @brief delete key fname from metadata object
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @return [description]
+ */
+Status EnvLibrados::_DelFid(
+ const std::string& fname) {
+ std::set<std::string> keys;
+ keys.insert(fname);
+ int r = _db_pool_ioctx.omap_rm_keys(_db_name, keys);
+ return err_to_status(r);
+}
+
+/**
+ * @brief get match IoCtx from _prefix_pool_map
+ * @details [long description]
+ *
+ * @param prefix [description]
+ * @return [description]
+ *
+ */
+librados::IoCtx* EnvLibrados::_GetIoctx(const std::string& fpath) {
+ auto is_prefix = [](const std::string & s1, const std::string & s2) {
+ auto it1 = s1.begin(), it2 = s2.begin();
+ while (it1 != s1.end() && it2 != s2.end() && *it1 == *it2) ++it1, ++it2;
+ return it1 == s1.end();
+ };
+
+ if (is_prefix(_wal_dir, fpath)) {
+ return &_wal_pool_ioctx;
+ } else {
+ return &_db_pool_ioctx;
+ }
+}
+
+/************************************************************
+ public functions
+************************************************************/
+/**
+ * @brief generate unique id
+ * @details Combine system time and random number.
+ * @return [description]
+ */
+std::string EnvLibrados::GenerateUniqueId() {
+ Random64 r(time(nullptr));
+ uint64_t random_uuid_portion =
+ r.Uniform(std::numeric_limits<uint64_t>::max());
+ uint64_t nanos_uuid_portion = NowNanos();
+ char uuid2[200];
+ snprintf(uuid2,
+ 200,
+ "%16lx-%16lx",
+ (unsigned long)nanos_uuid_portion,
+ (unsigned long)random_uuid_portion);
+ return uuid2;
+}
+
+/**
+ * @brief create a new sequential read file handler
+ * @details it will check the existence of fname
+ *
+ * @param fname [description]
+ * @param result [description]
+ * @param options [description]
+ * @return [description]
+ */
+Status EnvLibrados::NewSequentialFile(
+ const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string dir, file, fid;
+ split(fname, &dir, &file);
+ Status s;
+ std::string fpath = dir + "/" + file;
+ do {
+ s = _GetFid(dir, fid);
+
+ if (!s.ok() || fid != DIR_ID_VALUE) {
+ if (fid != DIR_ID_VALUE) s = Status::IOError();
+ break;
+ }
+
+ s = _GetFid(fpath, fid);
+
+ if (Status::NotFound() == s) {
+ s = Status::IOError();
+ errno = ENOENT;
+ break;
+ }
+
+ result->reset(new LibradosSequentialFile(_GetIoctx(fpath), fid, fpath));
+ s = Status::OK();
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief create a new random access file handler
+ * @details it will check the existence of fname
+ *
+ * @param fname [description]
+ * @param result [description]
+ * @param options [description]
+ * @return [description]
+ */
+Status EnvLibrados::NewRandomAccessFile(
+ const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string dir, file, fid;
+ split(fname, &dir, &file);
+ Status s;
+ std::string fpath = dir + "/" + file;
+ do {
+ s = _GetFid(dir, fid);
+
+ if (!s.ok() || fid != DIR_ID_VALUE) {
+ s = Status::IOError();
+ break;
+ }
+
+ s = _GetFid(fpath, fid);
+
+ if (Status::NotFound() == s) {
+ s = Status::IOError();
+ errno = ENOENT;
+ break;
+ }
+
+ result->reset(new LibradosRandomAccessFile(_GetIoctx(fpath), fid, fpath));
+ s = Status::OK();
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief create a new write file handler
+ * @details it will check the existence of fname
+ *
+ * @param fname [description]
+ * @param result [description]
+ * @param options [description]
+ * @return [description]
+ */
+Status EnvLibrados::NewWritableFile(
+ const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string dir, file, fid;
+ split(fname, &dir, &file);
+ Status s;
+ std::string fpath = dir + "/" + file;
+
+ do {
+ // 1. check if dir exist
+ s = _GetFid(dir, fid);
+ if (!s.ok()) {
+ break;
+ }
+
+ if (fid != DIR_ID_VALUE) {
+ s = Status::IOError();
+ break;
+ }
+
+ // 2. check if file exist.
+ // 2.1 exist, use it
+ // 2.2 not exist, create it
+ s = _GetFid(fpath, fid);
+ if (Status::NotFound() == s) {
+ fid = _CreateFid();
+ _AddFid(fpath, fid);
+ }
+
+ result->reset(
+ new LibradosWritableFile(_GetIoctx(fpath), fid, fpath, this, options));
+ s = Status::OK();
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief reuse write file handler
+ * @details
+ * This function will rename old_fname to new_fname,
+ * then return the handler of new_fname
+ *
+ * @param new_fname [description]
+ * @param old_fname [description]
+ * @param result [description]
+ * @param options [description]
+ * @return [description]
+ */
+Status EnvLibrados::ReuseWritableFile(
+ const std::string& new_fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options)
+{
+ LOG_DEBUG("[IN]%s => %s\n", old_fname.c_str(), new_fname.c_str());
+ std::string src_fid, tmp_fid, src_dir, src_file, dst_dir, dst_file;
+ split(old_fname, &src_dir, &src_file);
+ split(new_fname, &dst_dir, &dst_file);
+
+ std::string src_fpath = src_dir + "/" + src_file;
+ std::string dst_fpath = dst_dir + "/" + dst_file;
+ Status r = Status::OK();
+ do {
+ r = _RenameFid(src_fpath,
+ dst_fpath);
+ if (!r.ok()) {
+ break;
+ }
+
+ result->reset(new LibradosWritableFile(_GetIoctx(dst_fpath), src_fid,
+ dst_fpath, this, options));
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", r.ToString().c_str());
+ return r;
+}
+
+/**
+ * @brief create a new directory handler
+ * @details [long description]
+ *
+ * @param name [description]
+ * @param result [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::NewDirectory(
+ const std::string& name,
+ std::unique_ptr<Directory>* result)
+{
+ LOG_DEBUG("[IN]%s\n", name.c_str());
+ std::string fid, dir, file;
+ /* just want to get dir name */
+ split(name + "/tmp", &dir, &file);
+ Status s;
+
+ do {
+ s = _GetFid(dir, fid);
+
+ if (!s.ok() || DIR_ID_VALUE != fid) {
+ s = Status::IOError(name, strerror(-ENOENT));
+ break;
+ }
+
+ if (Status::NotFound() == s) {
+ s = _AddFid(dir, DIR_ID_VALUE);
+ if (!s.ok()) break;
+ } else if (!s.ok()) {
+ break;
+ }
+
+ result->reset(new LibradosDirectory(_GetIoctx(dir), dir));
+ s = Status::OK();
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief check if fname is exist
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @return [description]
+ */
+Status EnvLibrados::FileExists(const std::string& fname)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string fid, dir, file;
+ split(fname, &dir, &file);
+ Status s = _GetFid(dir + "/" + file, fid);
+
+ if (s.ok() && fid != DIR_ID_VALUE) {
+ s = Status::OK();
+ }
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief get subfile name of dir_in
+ * @details [long description]
+ *
+ * @param dir_in [description]
+ * @param result [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::GetChildren(
+ const std::string& dir_in,
+ std::vector<std::string>* result)
+{
+ LOG_DEBUG("[IN]%s\n", dir_in.c_str());
+ std::string fid, dir, file;
+ split(dir_in + "/temp", &dir, &file);
+ Status s;
+
+ do {
+ s = _GetFid(dir, fid);
+ if (!s.ok()) {
+ break;
+ }
+
+ if (fid != DIR_ID_VALUE) {
+ s = Status::IOError();
+ break;
+ }
+
+ s = _GetSubFnames(dir, result);
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief delete fname
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @return [description]
+ */
+Status EnvLibrados::DeleteFile(const std::string& fname)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string fid, dir, file;
+ split(fname, &dir, &file);
+ Status s = _GetFid(dir + "/" + file, fid);
+
+ if (s.ok() && DIR_ID_VALUE != fid) {
+ s = _DelFid(dir + "/" + file);
+ } else {
+ s = Status::NotFound();
+ }
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief create new dir
+ * @details [long description]
+ *
+ * @param dirname [description]
+ * @return [description]
+ */
+Status EnvLibrados::CreateDir(const std::string& dirname)
+{
+ LOG_DEBUG("[IN]%s\n", dirname.c_str());
+ std::string fid, dir, file;
+ split(dirname + "/temp", &dir, &file);
+ Status s = _GetFid(dir + "/" + file, fid);
+
+ do {
+ if (Status::NotFound() != s && fid != DIR_ID_VALUE) {
+ break;
+ } else if (Status::OK() == s && fid == DIR_ID_VALUE) {
+ break;
+ }
+
+ s = _AddFid(dir, DIR_ID_VALUE);
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief create dir if missing
+ * @details [long description]
+ *
+ * @param dirname [description]
+ * @return [description]
+ */
+Status EnvLibrados::CreateDirIfMissing(const std::string& dirname)
+{
+ LOG_DEBUG("[IN]%s\n", dirname.c_str());
+ std::string fid, dir, file;
+ split(dirname + "/temp", &dir, &file);
+ Status s = Status::OK();
+
+ do {
+ s = _GetFid(dir, fid);
+ if (Status::NotFound() != s) {
+ break;
+ }
+
+ s = _AddFid(dir, DIR_ID_VALUE);
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief delete dir
+ * @details
+ *
+ * @param dirname [description]
+ * @return [description]
+ */
+Status EnvLibrados::DeleteDir(const std::string& dirname)
+{
+ LOG_DEBUG("[IN]%s\n", dirname.c_str());
+ std::string fid, dir, file;
+ split(dirname + "/temp", &dir, &file);
+ Status s = Status::OK();
+
+ s = _GetFid(dir, fid);
+
+ if (s.ok() && DIR_ID_VALUE == fid) {
+ std::vector<std::string> subs;
+ s = _GetSubFnames(dir, &subs);
+ // if subfiles exist, can't delete dir
+ if (subs.size() > 0) {
+ s = Status::IOError();
+ } else {
+ s = _DelFid(dir);
+ }
+ } else {
+ s = Status::NotFound();
+ }
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief return file size
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @param file_size [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::GetFileSize(
+ const std::string& fname,
+ uint64_t* file_size)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string fid, dir, file;
+ split(fname, &dir, &file);
+ time_t mtime;
+ Status s;
+
+ do {
+ std::string fpath = dir + "/" + file;
+ s = _GetFid(fpath, fid);
+
+ if (!s.ok()) {
+ break;
+ }
+
+ int ret = _GetIoctx(fpath)->stat(fid, file_size, &mtime);
+ if (ret < 0) {
+ LOG_DEBUG("%i\n", ret);
+ if (-ENOENT == ret) {
+ *file_size = 0;
+ s = Status::OK();
+ } else {
+ s = err_to_status(ret);
+ }
+ } else {
+ s = Status::OK();
+ }
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s|%lld\n", s.ToString().c_str(), (long long)*file_size);
+ return s;
+}
+
+/**
+ * @brief get file modification time
+ * @details [long description]
+ *
+ * @param fname [description]
+ * @param file_mtime [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string fid, dir, file;
+ split(fname, &dir, &file);
+ time_t mtime;
+ uint64_t file_size;
+ Status s = Status::OK();
+ do {
+ std::string fpath = dir + "/" + file;
+ s = _GetFid(dir + "/" + file, fid);
+
+ if (!s.ok()) {
+ break;
+ }
+
+ int ret = _GetIoctx(fpath)->stat(fid, &file_size, &mtime);
+ if (ret < 0) {
+ if (Status::NotFound() == err_to_status(ret)) {
+ *file_mtime = static_cast<uint64_t>(mtime);
+ s = Status::OK();
+ } else {
+ s = err_to_status(ret);
+ }
+ } else {
+ s = Status::OK();
+ }
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief rename file
+ * @details
+ *
+ * @param src [description]
+ * @param target_in [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::RenameFile(
+ const std::string& src,
+ const std::string& target_in)
+{
+ LOG_DEBUG("[IN]%s => %s\n", src.c_str(), target_in.c_str());
+ std::string src_fid, tmp_fid, src_dir, src_file, dst_dir, dst_file;
+ split(src, &src_dir, &src_file);
+ split(target_in, &dst_dir, &dst_file);
+
+ auto s = _RenameFid(src_dir + "/" + src_file,
+ dst_dir + "/" + dst_file);
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief not support
+ * @details [long description]
+ *
+ * @param src [description]
+ * @param target_in [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::LinkFile(
+ const std::string& src,
+ const std::string& target_in)
+{
+ LOG_DEBUG("[IO]%s => %s\n", src.c_str(), target_in.c_str());
+ return Status::NotSupported();
+}
+
+/**
+ * @brief lock file. create if missing.
+ * @details [long description]
+ *
+ * It seems that LockFile is used for preventing other instance of RocksDB
+ * from opening up the database at the same time. From RocksDB source code,
+ * the invokes of LockFile are at following locations:
+ *
+ * ./db/db_impl.cc:1159: s = env_->LockFile(LockFileName(dbname_), &db_lock_); // DBImpl::Recover
+ * ./db/db_impl.cc:5839: Status result = env->LockFile(lockname, &lock); // Status DestroyDB
+ *
+ * When db recovery and db destroy, RocksDB will call LockFile
+ *
+ * @param fname [description]
+ * @param lock [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::LockFile(
+ const std::string& fname,
+ FileLock** lock)
+{
+ LOG_DEBUG("[IN]%s\n", fname.c_str());
+ std::string fid, dir, file;
+ split(fname, &dir, &file);
+ Status s = Status::OK();
+
+ do {
+ std::string fpath = dir + "/" + file;
+ s = _GetFid(fpath, fid);
+
+ if (Status::OK() != s &&
+ Status::NotFound() != s) {
+ break;
+ } else if (Status::NotFound() == s) {
+ s = _AddFid(fpath, _CreateFid());
+ if (!s.ok()) {
+ break;
+ }
+ } else if (Status::OK() == s && DIR_ID_VALUE == fid) {
+ s = Status::IOError();
+ break;
+ }
+
+ *lock = new LibradosFileLock(_GetIoctx(fpath), fpath);
+ } while (0);
+
+ LOG_DEBUG("[OUT]%s\n", s.ToString().c_str());
+ return s;
+}
+
+/**
+ * @brief unlock file
+ * @details [long description]
+ *
+ * @param lock [description]
+ * @return [description]
+ */
+Status EnvLibrados::UnlockFile(FileLock* lock)
+{
+ LOG_DEBUG("[IO]%p\n", lock);
+ if (nullptr != lock) {
+ delete lock;
+ }
+ return Status::OK();
+}
+
+
+/**
+ * @brief not support
+ * @details [long description]
+ *
+ * @param db_path [description]
+ * @param output_path [description]
+ *
+ * @return [description]
+ */
+Status EnvLibrados::GetAbsolutePath(
+ const std::string& db_path,
+ std::string* output_path)
+{
+ LOG_DEBUG("[IO]%s\n", db_path.c_str());
+ return Status::NotSupported();
+}
+
+/**
+ * @brief Get default EnvLibrados
+ * @details [long description]
+ * @return [description]
+ */
+EnvLibrados* EnvLibrados::Default() {
+ static EnvLibrados default_env(default_db_name,
+ std::getenv(default_config_path),
+ default_pool_name);
+ return &default_env;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/env_librados.md b/storage/rocksdb/rocksdb/utilities/env_librados.md
new file mode 100644
index 00000000..45a2a7ba
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_librados.md
@@ -0,0 +1,122 @@
+# Introduce to EnvLibrados
+EnvLibrados is a customized RocksDB Env to use RADOS as the backend file system of RocksDB. It overrides all file system related API of default Env. The easiest way to use it is just like following:
+```c++
+std::string db_name = "test_db";
+std::string config_path = "path/to/ceph/config";
+DB* db;
+Options options;
+options.env = EnvLibrados(db_name, config_path);
+Status s = DB::Open(options, kDBPath, &db);
+...
+```
+Then EnvLibrados will forward all file read/write operation to the RADOS cluster assigned by config_path. Default pool is db_name+"_pool".
+
+# Options for EnvLibrados
+There are some options that users could set for EnvLibrados.
+- write_buffer_size. This variable is the max buffer size for WritableFile. After reaching the buffer_max_size, EnvLibrados will sync buffer content to RADOS, then clear buffer.
+- db_pool. Rather than using default pool, users could set their own db pool name
+- wal_dir. The dir for WAL files. Because RocksDB only has 2-level structure (dir_name/file_name), the format of wal_dir is "/dir_name"(CAN'T be "/dir1/dir2"). Default wal_dir is "/wal".
+- wal_pool. Corresponding pool name for WAL files. Default value is db_name+"_wal_pool"
+
+The example of setting options looks like following:
+```c++
+db_name = "test_db";
+db_pool = db_name+"_pool";
+wal_dir = "/wal";
+wal_pool = db_name+"_wal_pool";
+write_buffer_size = 1 << 20;
+env_ = new EnvLibrados(db_name, config, db_pool, wal_dir, wal_pool, write_buffer_size);
+
+DB* db;
+Options options;
+options.env = env_;
+// The last level dir name should match the dir name in prefix_pool_map
+options.wal_dir = "/tmp/wal";
+
+// open DB
+Status s = DB::Open(options, kDBPath, &db);
+...
+```
+
+# Performance Test
+## Compile
+Check this [link](https://github.com/facebook/rocksdb/blob/master/INSTALL.md) to install the dependencies of RocksDB. Then you can compile it by running `$ make env_librados_test ROCKSDB_USE_LIBRADOS=1` under `rocksdb\`. The configure file used by env_librados_test is `../ceph/src/ceph.conf`. For Ubuntu 14.04, just run following commands:
+```bash
+$ sudo apt-get install libgflags-dev
+$ sudo apt-get install libsnappy-dev
+$ sudo apt-get install zlib1g-dev
+$ sudo apt-get install libbz2-dev
+$ make env_librados_test ROCKSDB_USE_LIBRADOS=1
+```
+
+## Test Result
+My test environment is Ubuntu 14.04 in VirtualBox with 8 cores and 8G RAM. Following is the test result.
+
+1. Write (1<<20) keys in random order. The time of writing under default env is around 10s while the time of writing under EnvLibrados is varying from 10s to 30s.
+
+2. Write (1<<20) keys in sequential order. The time of writing under default env drops to arround 1s. But the time of writing under EnvLibrados is not changed.
+
+3. Read (1<<16) keys from (1<<20) keys in random order. The time of reading under both Envs are roughly the same, around 1.8s.
+
+# MyRocks Test
+## Compile Ceph
+See [link](http://docs.ceph.com/docs/master/install/build-ceph/)
+
+## Start RADOS
+
+```bash
+cd ceph-path/src
+( ( ./stop.sh; rm -rf dev/*; CEPH_NUM_OSD=3 ./vstart.sh --short --localhost -n
+-x -d ; ) ) 2>&1
+```
+
+## Compile MySQL
+
+```bash
+sudo apt-get update
+sudo apt-get install g++ cmake libbz2-dev libaio-dev bison \
+zlib1g-dev libsnappy-dev
+sudo apt-get install libgflags-dev libreadline6-dev libncurses5-dev \
+libssl-dev liblz4-dev gdb git
+
+git clone https://github.com/facebook/mysql-5.6.git
+cd mysql-5.6
+git submodule init
+git submodule update
+cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_SSL=system \
+-DWITH_ZLIB=bundled -DMYSQL_MAINTAINER_MODE=0 -DENABLED_LOCAL_INFILE=1 -DROCKSDB_USE_LIBRADOS=1
+make install -j8
+```
+
+Check this [link](https://github.com/facebook/mysql-5.6/wiki/Build-Steps) for latest compile steps.
+
+## Configure MySQL
+Following is the steps of configuration of MySQL.
+
+```bash
+mkdir -p /etc/mysql
+mkdir -p /var/lib/mysql
+mkdir -p /etc/mysql/conf.d
+echo -e '[mysqld_safe]\nsyslog' > /etc/mysql/conf.d/mysqld_safe_syslog.cnf
+cp /usr/share/mysql/my-medium.cnf /etc/mysql/my.cnf
+sed -i 's#.*datadir.*#datadir = /var/lib/mysql#g' /etc/mysql/my.cnf
+chown mysql:mysql -R /var/lib/mysql
+
+mysql_install_db --user=mysql --ldata=/var/lib/mysql/
+export CEPH_CONFIG_PATH="path/of/ceph/config/file"
+mysqld_safe -user=mysql --skip-innodb --rocksdb --default-storage-engine=rocksdb --default-tmp-storage-engine=MyISAM &
+mysqladmin -u root password
+mysql -u root -p
+```
+
+Check this [link](https://gist.github.com/shichao-an/f5639ecd551496ac2d70) for detail information.
+
+```sql
+show databases;
+create database testdb;
+use testdb;
+show tables;
+CREATE TABLE tbl (id INT AUTO_INCREMENT primary key, str VARCHAR(32));
+insert into tbl values (1, "val2");
+select * from tbl;
+```
diff --git a/storage/rocksdb/rocksdb/utilities/env_librados_test.cc b/storage/rocksdb/rocksdb/utilities/env_librados_test.cc
new file mode 100644
index 00000000..d5167acc
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_librados_test.cc
@@ -0,0 +1,1146 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// Copyright (c) 2016, 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).
+
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/utilities/env_librados.h"
+#include <rados/librados.hpp>
+#include "env/mock_env.h"
+#include "test_util/testharness.h"
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/options.h"
+#include "util/random.h"
+#include <chrono>
+#include <ostream>
+#include "rocksdb/utilities/transaction_db.h"
+
+class Timer {
+ typedef std::chrono::high_resolution_clock high_resolution_clock;
+ typedef std::chrono::milliseconds milliseconds;
+public:
+ explicit Timer(bool run = false)
+ {
+ if (run)
+ Reset();
+ }
+ void Reset()
+ {
+ _start = high_resolution_clock::now();
+ }
+ milliseconds Elapsed() const
+ {
+ return std::chrono::duration_cast<milliseconds>(high_resolution_clock::now() - _start);
+ }
+ template <typename T, typename Traits>
+ friend std::basic_ostream<T, Traits>& operator<<(std::basic_ostream<T, Traits>& out, const Timer& timer)
+ {
+ return out << timer.Elapsed().count();
+ }
+private:
+ high_resolution_clock::time_point _start;
+};
+
+namespace ROCKSDB_NAMESPACE {
+
+class EnvLibradosTest : public testing::Test {
+public:
+ // we will use all of these below
+ const std::string db_name = "env_librados_test_db";
+ const std::string db_pool = db_name + "_pool";
+ const char *keyring = "admin";
+ const char *config = "../ceph/src/ceph.conf";
+
+ EnvLibrados* env_;
+ const EnvOptions soptions_;
+
+ EnvLibradosTest()
+ : env_(new EnvLibrados(db_name, config, db_pool)) {
+ }
+ ~EnvLibradosTest() {
+ delete env_;
+ librados::Rados rados;
+ int ret = 0;
+ do {
+ ret = rados.init("admin"); // just use the client.admin keyring
+ if (ret < 0) { // let's handle any error that might have come back
+ std::cerr << "couldn't initialize rados! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ ret = rados.conf_read_file(config);
+ if (ret < 0) {
+ // This could fail if the config file is malformed, but it'd be hard.
+ std::cerr << "failed to parse config file " << config
+ << "! error" << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ /*
+ * next, we actually connect to the cluster
+ */
+
+ ret = rados.connect();
+ if (ret < 0) {
+ std::cerr << "couldn't connect to cluster! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ /*
+ * And now we're done, so let's remove our pool and then
+ * shut down the connection gracefully.
+ */
+ int delete_ret = rados.pool_delete(db_pool.c_str());
+ if (delete_ret < 0) {
+ // be careful not to
+ std::cerr << "We failed to delete our test pool!" << db_pool << delete_ret << std::endl;
+ ret = EXIT_FAILURE;
+ }
+ } while (0);
+ }
+};
+
+TEST_F(EnvLibradosTest, Basics) {
+ uint64_t file_size;
+ std::unique_ptr<WritableFile> writable_file;
+ std::vector<std::string> 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(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]);
+
+ // 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);
+
+
+ // 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);
+
+ // Check that opening non-existent file fails.
+ std::unique_ptr<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> 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(EnvLibradosTest, ReadWrite) {
+ std::unique_ptr<WritableFile> writable_file;
+ std::unique_ptr<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> 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_OK(rand_file->Read(1000, 5, &result, scratch));
+}
+
+TEST_F(EnvLibradosTest, Locks) {
+ FileLock* lock = nullptr;
+ std::unique_ptr<WritableFile> writable_file;
+
+ ASSERT_OK(env_->CreateDir("/dir"));
+
+ ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_));
+
+ // These are no-ops, but we test they return success.
+ ASSERT_OK(env_->LockFile("some file", &lock));
+ ASSERT_OK(env_->UnlockFile(lock));
+
+ ASSERT_OK(env_->LockFile("/dir/f", &lock));
+ ASSERT_OK(env_->UnlockFile(lock));
+}
+
+TEST_F(EnvLibradosTest, Misc) {
+ std::string test_dir;
+ ASSERT_OK(env_->GetTestDirectory(&test_dir));
+ ASSERT_TRUE(!test_dir.empty());
+
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_TRUE(!env_->NewWritableFile("/a/b", &writable_file, soptions_).ok());
+
+ ASSERT_OK(env_->NewWritableFile("/a", &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(EnvLibradosTest, 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, 'h');
+ }
+
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(env_->CreateDir("/dir"));
+ ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_));
+ ASSERT_OK(writable_file->Append("foo"));
+ ASSERT_OK(writable_file->Append(write_data));
+ writable_file.reset();
+
+ std::unique_ptr<SequentialFile> seq_file;
+ Slice result;
+ ASSERT_OK(env_->NewSequentialFile("/dir/g", &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_F(EnvLibradosTest, FrequentlySmallWrite) {
+ const size_t kWriteSize = 1 << 10;
+ char* scratch = new char[kWriteSize * 2];
+
+ std::string write_data;
+ for (size_t i = 0; i < kWriteSize; ++i) {
+ write_data.append(1, 'h');
+ }
+
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(env_->CreateDir("/dir"));
+ ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_));
+ ASSERT_OK(writable_file->Append("foo"));
+
+ for (size_t i = 0; i < kWriteSize; ++i) {
+ ASSERT_OK(writable_file->Append("h"));
+ }
+ writable_file.reset();
+
+ std::unique_ptr<SequentialFile> seq_file;
+ Slice result;
+ ASSERT_OK(env_->NewSequentialFile("/dir/g", &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_F(EnvLibradosTest, Truncate) {
+ const size_t kWriteSize = 300 * 1024;
+ const size_t truncSize = 1024;
+ std::string write_data;
+ for (size_t i = 0; i < kWriteSize; ++i) {
+ write_data.append(1, 'h');
+ }
+
+ std::unique_ptr<WritableFile> writable_file;
+ ASSERT_OK(env_->CreateDir("/dir"));
+ ASSERT_OK(env_->NewWritableFile("/dir/g", &writable_file, soptions_));
+ ASSERT_OK(writable_file->Append(write_data));
+ ASSERT_EQ(writable_file->GetFileSize(), kWriteSize);
+ ASSERT_OK(writable_file->Truncate(truncSize));
+ ASSERT_EQ(writable_file->GetFileSize(), truncSize);
+ writable_file.reset();
+}
+
+TEST_F(EnvLibradosTest, DBBasics) {
+ std::string kDBPath = "/tmp/DBBasics";
+ 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;
+ options.env = env_;
+
+ // 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");
+
+ delete db;
+}
+
+TEST_F(EnvLibradosTest, DBLoadKeysInRandomOrder) {
+ char key[20] = {0}, value[20] = {0};
+ int max_loop = 1 << 10;
+ Timer timer(false);
+ std::cout << "Test size : loop(" << max_loop << ")" << std::endl;
+ /**********************************
+ use default env
+ ***********************************/
+ std::string kDBPath1 = "/tmp/DBLoadKeysInRandomOrder1";
+ DB* db1;
+ Options options1;
+ // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well
+ options1.IncreaseParallelism();
+ options1.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options1.create_if_missing = true;
+
+ // open DB
+ Status s1 = DB::Open(options1, kDBPath1, &db1);
+ assert(s1.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r1(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ // Put key-value
+ s1 = db1->Put(WriteOptions(), key, value);
+ assert(s1.ok());
+ }
+ std::cout << "Time by default : " << timer << "ms" << std::endl;
+ delete db1;
+
+ /**********************************
+ use librados env
+ ***********************************/
+ std::string kDBPath2 = "/tmp/DBLoadKeysInRandomOrder2";
+ DB* db2;
+ Options options2;
+ // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
+ options2.IncreaseParallelism();
+ options2.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options2.create_if_missing = true;
+ options2.env = env_;
+
+ // open DB
+ Status s2 = DB::Open(options2, kDBPath2, &db2);
+ assert(s2.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r2(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ // Put key-value
+ s2 = db2->Put(WriteOptions(), key, value);
+ assert(s2.ok());
+ }
+ std::cout << "Time by librados : " << timer << "ms" << std::endl;
+ delete db2;
+}
+
+TEST_F(EnvLibradosTest, DBBulkLoadKeysInRandomOrder) {
+ char key[20] = {0}, value[20] = {0};
+ int max_loop = 1 << 6;
+ int bulk_size = 1 << 15;
+ Timer timer(false);
+ std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl;
+ /**********************************
+ use default env
+ ***********************************/
+ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInRandomOrder1";
+ DB* db1;
+ Options options1;
+ // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well
+ options1.IncreaseParallelism();
+ options1.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options1.create_if_missing = true;
+
+ // open DB
+ Status s1 = DB::Open(options1, kDBPath1, &db1);
+ assert(s1.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r1(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s1 = db1->Write(WriteOptions(), &batch);
+ assert(s1.ok());
+ }
+ std::cout << "Time by default : " << timer << "ms" << std::endl;
+ delete db1;
+
+ /**********************************
+ use librados env
+ ***********************************/
+ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInRandomOrder2";
+ DB* db2;
+ Options options2;
+ // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
+ options2.IncreaseParallelism();
+ options2.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options2.create_if_missing = true;
+ options2.env = env_;
+
+ // open DB
+ Status s2 = DB::Open(options2, kDBPath2, &db2);
+ assert(s2.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r2(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s2 = db2->Write(WriteOptions(), &batch);
+ assert(s2.ok());
+ }
+ std::cout << "Time by librados : " << timer << "ms" << std::endl;
+ delete db2;
+}
+
+TEST_F(EnvLibradosTest, DBBulkLoadKeysInSequentialOrder) {
+ char key[20] = {0}, value[20] = {0};
+ int max_loop = 1 << 6;
+ int bulk_size = 1 << 15;
+ Timer timer(false);
+ std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl;
+ /**********************************
+ use default env
+ ***********************************/
+ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInSequentialOrder1";
+ DB* db1;
+ Options options1;
+ // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well
+ options1.IncreaseParallelism();
+ options1.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options1.create_if_missing = true;
+
+ // open DB
+ Status s1 = DB::Open(options1, kDBPath1, &db1);
+ assert(s1.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r1(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%019lld",
+ (long long)(i * bulk_size + j));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s1 = db1->Write(WriteOptions(), &batch);
+ assert(s1.ok());
+ }
+ std::cout << "Time by default : " << timer << "ms" << std::endl;
+ delete db1;
+
+ /**********************************
+ use librados env
+ ***********************************/
+ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInSequentialOrder2";
+ DB* db2;
+ Options options2;
+ // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
+ options2.IncreaseParallelism();
+ options2.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options2.create_if_missing = true;
+ options2.env = env_;
+
+ // open DB
+ Status s2 = DB::Open(options2, kDBPath2, &db2);
+ assert(s2.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r2(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s2 = db2->Write(WriteOptions(), &batch);
+ assert(s2.ok());
+ }
+ std::cout << "Time by librados : " << timer << "ms" << std::endl;
+ delete db2;
+}
+
+TEST_F(EnvLibradosTest, DBRandomRead) {
+ char key[20] = {0}, value[20] = {0};
+ int max_loop = 1 << 6;
+ int bulk_size = 1 << 10;
+ int read_loop = 1 << 20;
+ Timer timer(false);
+ std::cout << "Test size : keys_num(" << max_loop << ", " << bulk_size << "); read_loop(" << read_loop << ")" << std::endl;
+ /**********************************
+ use default env
+ ***********************************/
+ std::string kDBPath1 = "/tmp/DBRandomRead1";
+ DB* db1;
+ Options options1;
+ // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well
+ options1.IncreaseParallelism();
+ options1.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options1.create_if_missing = true;
+
+ // open DB
+ Status s1 = DB::Open(options1, kDBPath1, &db1);
+ assert(s1.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r1(time(nullptr));
+
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%019lld",
+ (long long)(i * bulk_size + j));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s1 = db1->Write(WriteOptions(), &batch);
+ assert(s1.ok());
+ }
+ timer.Reset();
+ int base1 = 0, offset1 = 0;
+ for (int i = 0; i < read_loop; ++i) {
+ base1 = r1.Uniform(max_loop);
+ offset1 = r1.Uniform(bulk_size);
+ std::string value1;
+ snprintf(key,
+ 20,
+ "%019lld",
+ (long long)(base1 * bulk_size + offset1));
+ s1 = db1->Get(ReadOptions(), key, &value1);
+ assert(s1.ok());
+ }
+ std::cout << "Time by default : " << timer << "ms" << std::endl;
+ delete db1;
+
+ /**********************************
+ use librados env
+ ***********************************/
+ std::string kDBPath2 = "/tmp/DBRandomRead2";
+ DB* db2;
+ Options options2;
+ // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
+ options2.IncreaseParallelism();
+ options2.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options2.create_if_missing = true;
+ options2.env = env_;
+
+ // open DB
+ Status s2 = DB::Open(options2, kDBPath2, &db2);
+ assert(s2.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r2(time(nullptr));
+
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%019lld",
+ (long long)(i * bulk_size + j));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s2 = db2->Write(WriteOptions(), &batch);
+ assert(s2.ok());
+ }
+
+ timer.Reset();
+ int base2 = 0, offset2 = 0;
+ for (int i = 0; i < read_loop; ++i) {
+ base2 = r2.Uniform(max_loop);
+ offset2 = r2.Uniform(bulk_size);
+ std::string value2;
+ snprintf(key,
+ 20,
+ "%019lld",
+ (long long)(base2 * bulk_size + offset2));
+ s2 = db2->Get(ReadOptions(), key, &value2);
+ if (!s2.ok()) {
+ std::cout << s2.ToString() << std::endl;
+ }
+ assert(s2.ok());
+ }
+ std::cout << "Time by librados : " << timer << "ms" << std::endl;
+ delete db2;
+}
+
+class EnvLibradosMutipoolTest : public testing::Test {
+public:
+ // we will use all of these below
+ const std::string client_name = "client.admin";
+ const std::string cluster_name = "ceph";
+ const uint64_t flags = 0;
+ const std::string db_name = "env_librados_test_db";
+ const std::string db_pool = db_name + "_pool";
+ const std::string wal_dir = "/wal";
+ const std::string wal_pool = db_name + "_wal_pool";
+ const size_t write_buffer_size = 1 << 20;
+ const char *keyring = "admin";
+ const char *config = "../ceph/src/ceph.conf";
+
+ EnvLibrados* env_;
+ const EnvOptions soptions_;
+
+ EnvLibradosMutipoolTest() {
+ env_ = new EnvLibrados(client_name, cluster_name, flags, db_name, config, db_pool, wal_dir, wal_pool, write_buffer_size);
+ }
+ ~EnvLibradosMutipoolTest() {
+ delete env_;
+ librados::Rados rados;
+ int ret = 0;
+ do {
+ ret = rados.init("admin"); // just use the client.admin keyring
+ if (ret < 0) { // let's handle any error that might have come back
+ std::cerr << "couldn't initialize rados! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ ret = rados.conf_read_file(config);
+ if (ret < 0) {
+ // This could fail if the config file is malformed, but it'd be hard.
+ std::cerr << "failed to parse config file " << config
+ << "! error" << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ /*
+ * next, we actually connect to the cluster
+ */
+
+ ret = rados.connect();
+ if (ret < 0) {
+ std::cerr << "couldn't connect to cluster! error " << ret << std::endl;
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ /*
+ * And now we're done, so let's remove our pool and then
+ * shut down the connection gracefully.
+ */
+ int delete_ret = rados.pool_delete(db_pool.c_str());
+ if (delete_ret < 0) {
+ // be careful not to
+ std::cerr << "We failed to delete our test pool!" << db_pool << delete_ret << std::endl;
+ ret = EXIT_FAILURE;
+ }
+ delete_ret = rados.pool_delete(wal_pool.c_str());
+ if (delete_ret < 0) {
+ // be careful not to
+ std::cerr << "We failed to delete our test pool!" << wal_pool << delete_ret << std::endl;
+ ret = EXIT_FAILURE;
+ }
+ } while (0);
+ }
+};
+
+TEST_F(EnvLibradosMutipoolTest, Basics) {
+ uint64_t file_size;
+ std::unique_ptr<WritableFile> writable_file;
+ std::vector<std::string> children;
+ std::vector<std::string> v = {"/tmp/dir1", "/tmp/dir2", "/tmp/dir3", "/tmp/dir4", "dir"};
+
+ for (size_t i = 0; i < v.size(); ++i) {
+ std::string dir = v[i];
+ std::string dir_non_existent = dir + "/non_existent";
+ std::string dir_f = dir + "/f";
+ std::string dir_g = dir + "/g";
+
+ ASSERT_OK(env_->CreateDir(dir.c_str()));
+ // Check that the directory is empty.
+ ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_non_existent.c_str()));
+ ASSERT_TRUE(!env_->GetFileSize(dir_non_existent.c_str(), &file_size).ok());
+ ASSERT_OK(env_->GetChildren(dir.c_str(), &children));
+ ASSERT_EQ(0U, children.size());
+
+ // Create a file.
+ ASSERT_OK(env_->NewWritableFile(dir_f.c_str(), &writable_file, soptions_));
+ writable_file.reset();
+
+ // Check that the file exists.
+ ASSERT_OK(env_->FileExists(dir_f.c_str()));
+ ASSERT_OK(env_->GetFileSize(dir_f.c_str(), &file_size));
+ ASSERT_EQ(0U, file_size);
+ ASSERT_OK(env_->GetChildren(dir.c_str(), &children));
+ ASSERT_EQ(1U, children.size());
+ ASSERT_EQ("f", children[0]);
+
+ // Write to the file.
+ ASSERT_OK(env_->NewWritableFile(dir_f.c_str(), &writable_file, soptions_));
+ ASSERT_OK(writable_file->Append("abc"));
+ writable_file.reset();
+
+
+ // Check for expected size.
+ ASSERT_OK(env_->GetFileSize(dir_f.c_str(), &file_size));
+ ASSERT_EQ(3U, file_size);
+
+
+ // Check that renaming works.
+ ASSERT_TRUE(!env_->RenameFile(dir_non_existent.c_str(), dir_g.c_str()).ok());
+ ASSERT_OK(env_->RenameFile(dir_f.c_str(), dir_g.c_str()));
+ ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_f.c_str()));
+ ASSERT_OK(env_->FileExists(dir_g.c_str()));
+ ASSERT_OK(env_->GetFileSize(dir_g.c_str(), &file_size));
+ ASSERT_EQ(3U, file_size);
+
+ // Check that opening non-existent file fails.
+ std::unique_ptr<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> rand_file;
+ ASSERT_TRUE(
+ !env_->NewSequentialFile(dir_non_existent.c_str(), &seq_file, soptions_).ok());
+ ASSERT_TRUE(!seq_file);
+ ASSERT_TRUE(!env_->NewRandomAccessFile(dir_non_existent.c_str(), &rand_file,
+ soptions_).ok());
+ ASSERT_TRUE(!rand_file);
+
+ // Check that deleting works.
+ ASSERT_TRUE(!env_->DeleteFile(dir_non_existent.c_str()).ok());
+ ASSERT_OK(env_->DeleteFile(dir_g.c_str()));
+ ASSERT_EQ(Status::NotFound(), env_->FileExists(dir_g.c_str()));
+ ASSERT_OK(env_->GetChildren(dir.c_str(), &children));
+ ASSERT_EQ(0U, children.size());
+ ASSERT_OK(env_->DeleteDir(dir.c_str()));
+ }
+}
+
+TEST_F(EnvLibradosMutipoolTest, DBBasics) {
+ std::string kDBPath = "/tmp/DBBasics";
+ std::string walPath = "/tmp/wal";
+ 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;
+ options.env = env_;
+ options.wal_dir = walPath;
+
+ // 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");
+
+ delete db;
+}
+
+TEST_F(EnvLibradosMutipoolTest, DBBulkLoadKeysInRandomOrder) {
+ char key[20] = {0}, value[20] = {0};
+ int max_loop = 1 << 6;
+ int bulk_size = 1 << 15;
+ Timer timer(false);
+ std::cout << "Test size : loop(" << max_loop << "); bulk_size(" << bulk_size << ")" << std::endl;
+ /**********************************
+ use default env
+ ***********************************/
+ std::string kDBPath1 = "/tmp/DBBulkLoadKeysInRandomOrder1";
+ std::string walPath = "/tmp/wal";
+ DB* db1;
+ Options options1;
+ // Optimize Rocksdb. This is the easiest way to get RocksDB to perform well
+ options1.IncreaseParallelism();
+ options1.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options1.create_if_missing = true;
+
+ // open DB
+ Status s1 = DB::Open(options1, kDBPath1, &db1);
+ assert(s1.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r1(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r1.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s1 = db1->Write(WriteOptions(), &batch);
+ assert(s1.ok());
+ }
+ std::cout << "Time by default : " << timer << "ms" << std::endl;
+ delete db1;
+
+ /**********************************
+ use librados env
+ ***********************************/
+ std::string kDBPath2 = "/tmp/DBBulkLoadKeysInRandomOrder2";
+ DB* db2;
+ Options options2;
+ // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
+ options2.IncreaseParallelism();
+ options2.OptimizeLevelStyleCompaction();
+ // create the DB if it's not already present
+ options2.create_if_missing = true;
+ options2.env = env_;
+ options2.wal_dir = walPath;
+
+ // open DB
+ Status s2 = DB::Open(options2, kDBPath2, &db2);
+ if (!s2.ok()) {
+ std::cerr << s2.ToString() << std::endl;
+ }
+ assert(s2.ok());
+
+ ROCKSDB_NAMESPACE::Random64 r2(time(nullptr));
+
+ timer.Reset();
+ for (int i = 0; i < max_loop; ++i) {
+ WriteBatch batch;
+ for (int j = 0; j < bulk_size; ++j) {
+ snprintf(key,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ snprintf(value,
+ 20,
+ "%16lx",
+ (unsigned long)r2.Uniform(std::numeric_limits<uint64_t>::max()));
+ batch.Put(key, value);
+ }
+ s2 = db2->Write(WriteOptions(), &batch);
+ assert(s2.ok());
+ }
+ std::cout << "Time by librados : " << timer << "ms" << std::endl;
+ delete db2;
+}
+
+TEST_F(EnvLibradosMutipoolTest, DBTransactionDB) {
+ std::string kDBPath = "/tmp/DBTransactionDB";
+ // open DB
+ Options options;
+ TransactionDBOptions txn_db_options;
+ options.create_if_missing = true;
+ options.env = env_;
+ 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 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", "def");
+ assert(s.ok());
+
+ // Read a key OUTSIDE this transaction. Does not affect txn.
+ s = txn_db->Get(read_options, "abc", &value);
+
+ // 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 = txn_db->Put(write_options, "xyz", "zzz");
+
+ // Commit transaction
+ s = txn->Commit();
+ assert(s.ok());
+ delete txn;
+
+ ////////////////////////////////////////////////////////
+ //
+ // "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());
+
+ // Attempt to read a key using the snapshot. This will fail since
+ // the previous write outside this txn conflicts with this read.
+ read_options.snapshot = snapshot;
+ s = txn->GetForUpdate(read_options, "abc", &value);
+ assert(s.IsBusy());
+
+ txn->Rollback();
+
+ 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);
+ txn->Put("x", "x");
+
+ // Do a write outside of the transaction to key "y"
+ s = txn_db->Put(write_options, "y", "y");
+
+ // 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);
+ txn->Put("y", "y");
+
+ // 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;
+
+ // Cleanup
+ delete txn_db;
+ DestroyDB(kDBPath, options);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+ fprintf(stderr, "SKIPPED as EnvMirror is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/env_mirror.cc b/storage/rocksdb/rocksdb/utilities/env_mirror.cc
new file mode 100644
index 00000000..dbb5e802
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_mirror.cc
@@ -0,0 +1,262 @@
+// 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).
+// 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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/env_mirror.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// An implementation of Env that mirrors all work over two backend
+// Env's. This is useful for debugging purposes.
+class SequentialFileMirror : public SequentialFile {
+ public:
+ std::unique_ptr<SequentialFile> a_, b_;
+ std::string fname;
+ explicit SequentialFileMirror(std::string f) : fname(f) {}
+
+ Status Read(size_t n, Slice* result, char* scratch) override {
+ Slice aslice;
+ Status as = a_->Read(n, &aslice, scratch);
+ if (as == Status::OK()) {
+ char* bscratch = new char[n];
+ Slice bslice;
+ size_t off = 0;
+ size_t left = aslice.size();
+ while (left) {
+ Status bs = b_->Read(left, &bslice, bscratch);
+ assert(as == bs);
+ assert(memcmp(bscratch, scratch + off, bslice.size()) == 0);
+ off += bslice.size();
+ left -= bslice.size();
+ }
+ delete[] bscratch;
+ *result = aslice;
+ } else {
+ Status bs = b_->Read(n, result, scratch);
+ assert(as == bs);
+ }
+ return as;
+ }
+
+ Status Skip(uint64_t n) override {
+ Status as = a_->Skip(n);
+ Status bs = b_->Skip(n);
+ assert(as == bs);
+ return as;
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ Status as = a_->InvalidateCache(offset, length);
+ Status bs = b_->InvalidateCache(offset, length);
+ assert(as == bs);
+ return as;
+ };
+};
+
+class RandomAccessFileMirror : public RandomAccessFile {
+ public:
+ std::unique_ptr<RandomAccessFile> a_, b_;
+ std::string fname;
+ explicit RandomAccessFileMirror(std::string f) : fname(f) {}
+
+ Status Read(uint64_t offset, size_t n, Slice* result,
+ char* scratch) const override {
+ Status as = a_->Read(offset, n, result, scratch);
+ if (as == Status::OK()) {
+ char* bscratch = new char[n];
+ Slice bslice;
+ size_t off = 0;
+ size_t left = result->size();
+ while (left) {
+ Status bs = b_->Read(offset + off, left, &bslice, bscratch);
+ assert(as == bs);
+ assert(memcmp(bscratch, scratch + off, bslice.size()) == 0);
+ off += bslice.size();
+ left -= bslice.size();
+ }
+ delete[] bscratch;
+ } else {
+ Status bs = b_->Read(offset, n, result, scratch);
+ assert(as == bs);
+ }
+ return as;
+ }
+
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ // NOTE: not verified
+ return a_->GetUniqueId(id, max_size);
+ }
+};
+
+class WritableFileMirror : public WritableFile {
+ public:
+ std::unique_ptr<WritableFile> a_, b_;
+ std::string fname;
+ explicit WritableFileMirror(std::string f, const EnvOptions& options)
+ : WritableFile(options), fname(f) {}
+
+ Status Append(const Slice& data) override {
+ Status as = a_->Append(data);
+ Status bs = b_->Append(data);
+ assert(as == bs);
+ return as;
+ }
+ Status PositionedAppend(const Slice& data, uint64_t offset) override {
+ Status as = a_->PositionedAppend(data, offset);
+ Status bs = b_->PositionedAppend(data, offset);
+ assert(as == bs);
+ return as;
+ }
+ Status Truncate(uint64_t size) override {
+ Status as = a_->Truncate(size);
+ Status bs = b_->Truncate(size);
+ assert(as == bs);
+ return as;
+ }
+ Status Close() override {
+ Status as = a_->Close();
+ Status bs = b_->Close();
+ assert(as == bs);
+ return as;
+ }
+ Status Flush() override {
+ Status as = a_->Flush();
+ Status bs = b_->Flush();
+ assert(as == bs);
+ return as;
+ }
+ Status Sync() override {
+ Status as = a_->Sync();
+ Status bs = b_->Sync();
+ assert(as == bs);
+ return as;
+ }
+ Status Fsync() override {
+ Status as = a_->Fsync();
+ Status bs = b_->Fsync();
+ assert(as == bs);
+ return as;
+ }
+ bool IsSyncThreadSafe() const override {
+ bool as = a_->IsSyncThreadSafe();
+ assert(as == b_->IsSyncThreadSafe());
+ return as;
+ }
+ void SetIOPriority(Env::IOPriority pri) override {
+ a_->SetIOPriority(pri);
+ b_->SetIOPriority(pri);
+ }
+ Env::IOPriority GetIOPriority() override {
+ // NOTE: we don't verify this one
+ return a_->GetIOPriority();
+ }
+ uint64_t GetFileSize() override {
+ uint64_t as = a_->GetFileSize();
+ assert(as == b_->GetFileSize());
+ return as;
+ }
+ void GetPreallocationStatus(size_t* block_size,
+ size_t* last_allocated_block) override {
+ // NOTE: we don't verify this one
+ return a_->GetPreallocationStatus(block_size, last_allocated_block);
+ }
+ size_t GetUniqueId(char* id, size_t max_size) const override {
+ // NOTE: we don't verify this one
+ return a_->GetUniqueId(id, max_size);
+ }
+ Status InvalidateCache(size_t offset, size_t length) override {
+ Status as = a_->InvalidateCache(offset, length);
+ Status bs = b_->InvalidateCache(offset, length);
+ assert(as == bs);
+ return as;
+ }
+
+ protected:
+ Status Allocate(uint64_t offset, uint64_t length) override {
+ Status as = a_->Allocate(offset, length);
+ Status bs = b_->Allocate(offset, length);
+ assert(as == bs);
+ return as;
+ }
+ Status RangeSync(uint64_t offset, uint64_t nbytes) override {
+ Status as = a_->RangeSync(offset, nbytes);
+ Status bs = b_->RangeSync(offset, nbytes);
+ assert(as == bs);
+ return as;
+ }
+};
+
+Status EnvMirror::NewSequentialFile(const std::string& f,
+ std::unique_ptr<SequentialFile>* r,
+ const EnvOptions& options) {
+ if (f.find("/proc/") == 0) {
+ return a_->NewSequentialFile(f, r, options);
+ }
+ SequentialFileMirror* mf = new SequentialFileMirror(f);
+ Status as = a_->NewSequentialFile(f, &mf->a_, options);
+ Status bs = b_->NewSequentialFile(f, &mf->b_, options);
+ assert(as == bs);
+ if (as.ok())
+ r->reset(mf);
+ else
+ delete mf;
+ return as;
+}
+
+Status EnvMirror::NewRandomAccessFile(const std::string& f,
+ std::unique_ptr<RandomAccessFile>* r,
+ const EnvOptions& options) {
+ if (f.find("/proc/") == 0) {
+ return a_->NewRandomAccessFile(f, r, options);
+ }
+ RandomAccessFileMirror* mf = new RandomAccessFileMirror(f);
+ Status as = a_->NewRandomAccessFile(f, &mf->a_, options);
+ Status bs = b_->NewRandomAccessFile(f, &mf->b_, options);
+ assert(as == bs);
+ if (as.ok())
+ r->reset(mf);
+ else
+ delete mf;
+ return as;
+}
+
+Status EnvMirror::NewWritableFile(const std::string& f,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) {
+ if (f.find("/proc/") == 0) return a_->NewWritableFile(f, r, options);
+ WritableFileMirror* mf = new WritableFileMirror(f, options);
+ Status as = a_->NewWritableFile(f, &mf->a_, options);
+ Status bs = b_->NewWritableFile(f, &mf->b_, options);
+ assert(as == bs);
+ if (as.ok())
+ r->reset(mf);
+ else
+ delete mf;
+ return as;
+}
+
+Status EnvMirror::ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* r,
+ const EnvOptions& options) {
+ if (fname.find("/proc/") == 0)
+ return a_->ReuseWritableFile(fname, old_fname, r, options);
+ WritableFileMirror* mf = new WritableFileMirror(fname, options);
+ Status as = a_->ReuseWritableFile(fname, old_fname, &mf->a_, options);
+ Status bs = b_->ReuseWritableFile(fname, old_fname, &mf->b_, options);
+ assert(as == bs);
+ if (as.ok())
+ r->reset(mf);
+ else
+ delete mf;
+ return as;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/env_mirror_test.cc b/storage/rocksdb/rocksdb/utilities/env_mirror_test.cc
new file mode 100644
index 00000000..0be9d7db
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_mirror_test.cc
@@ -0,0 +1,223 @@
+// 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).
+
+#ifndef ROCKSDB_LITE
+
+#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<WritableFile> writable_file;
+ std::vector<std::string> 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<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> 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<WritableFile> writable_file;
+ std::unique_ptr<SequentialFile> seq_file;
+ std::unique_ptr<RandomAccessFile> 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<WritableFile> 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<char>(i));
+ }
+
+ std::unique_ptr<WritableFile> 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<SequentialFile> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+ fprintf(stderr, "SKIPPED as EnvMirror is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/env_timed.cc b/storage/rocksdb/rocksdb/utilities/env_timed.cc
new file mode 100644
index 00000000..fc6627da
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_timed.cc
@@ -0,0 +1,145 @@
+// 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 "monitoring/perf_context_imp.h"
+#include "rocksdb/env.h"
+#include "rocksdb/status.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifndef ROCKSDB_LITE
+
+// An environment that measures function call times for filesystem
+// operations, reporting results to variables in PerfContext.
+class TimedEnv : public EnvWrapper {
+ public:
+ explicit TimedEnv(Env* base_env) : EnvWrapper(base_env) {}
+
+ Status NewSequentialFile(const std::string& fname,
+ std::unique_ptr<SequentialFile>* result,
+ const EnvOptions& options) override {
+ PERF_TIMER_GUARD(env_new_sequential_file_nanos);
+ return EnvWrapper::NewSequentialFile(fname, result, options);
+ }
+
+ Status NewRandomAccessFile(const std::string& fname,
+ std::unique_ptr<RandomAccessFile>* result,
+ const EnvOptions& options) override {
+ PERF_TIMER_GUARD(env_new_random_access_file_nanos);
+ return EnvWrapper::NewRandomAccessFile(fname, result, options);
+ }
+
+ Status NewWritableFile(const std::string& fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ PERF_TIMER_GUARD(env_new_writable_file_nanos);
+ return EnvWrapper::NewWritableFile(fname, result, options);
+ }
+
+ Status ReuseWritableFile(const std::string& fname,
+ const std::string& old_fname,
+ std::unique_ptr<WritableFile>* result,
+ const EnvOptions& options) override {
+ PERF_TIMER_GUARD(env_reuse_writable_file_nanos);
+ return EnvWrapper::ReuseWritableFile(fname, old_fname, result, options);
+ }
+
+ Status NewRandomRWFile(const std::string& fname,
+ std::unique_ptr<RandomRWFile>* result,
+ const EnvOptions& options) override {
+ PERF_TIMER_GUARD(env_new_random_rw_file_nanos);
+ return EnvWrapper::NewRandomRWFile(fname, result, options);
+ }
+
+ Status NewDirectory(const std::string& name,
+ std::unique_ptr<Directory>* result) override {
+ PERF_TIMER_GUARD(env_new_directory_nanos);
+ return EnvWrapper::NewDirectory(name, result);
+ }
+
+ Status FileExists(const std::string& fname) override {
+ PERF_TIMER_GUARD(env_file_exists_nanos);
+ return EnvWrapper::FileExists(fname);
+ }
+
+ Status GetChildren(const std::string& dir,
+ std::vector<std::string>* result) override {
+ PERF_TIMER_GUARD(env_get_children_nanos);
+ return EnvWrapper::GetChildren(dir, result);
+ }
+
+ Status GetChildrenFileAttributes(
+ const std::string& dir, std::vector<FileAttributes>* result) override {
+ PERF_TIMER_GUARD(env_get_children_file_attributes_nanos);
+ return EnvWrapper::GetChildrenFileAttributes(dir, result);
+ }
+
+ Status DeleteFile(const std::string& fname) override {
+ PERF_TIMER_GUARD(env_delete_file_nanos);
+ return EnvWrapper::DeleteFile(fname);
+ }
+
+ Status CreateDir(const std::string& dirname) override {
+ PERF_TIMER_GUARD(env_create_dir_nanos);
+ return EnvWrapper::CreateDir(dirname);
+ }
+
+ Status CreateDirIfMissing(const std::string& dirname) override {
+ PERF_TIMER_GUARD(env_create_dir_if_missing_nanos);
+ return EnvWrapper::CreateDirIfMissing(dirname);
+ }
+
+ Status DeleteDir(const std::string& dirname) override {
+ PERF_TIMER_GUARD(env_delete_dir_nanos);
+ return EnvWrapper::DeleteDir(dirname);
+ }
+
+ Status GetFileSize(const std::string& fname, uint64_t* file_size) override {
+ PERF_TIMER_GUARD(env_get_file_size_nanos);
+ return EnvWrapper::GetFileSize(fname, file_size);
+ }
+
+ Status GetFileModificationTime(const std::string& fname,
+ uint64_t* file_mtime) override {
+ PERF_TIMER_GUARD(env_get_file_modification_time_nanos);
+ return EnvWrapper::GetFileModificationTime(fname, file_mtime);
+ }
+
+ Status RenameFile(const std::string& src, const std::string& dst) override {
+ PERF_TIMER_GUARD(env_rename_file_nanos);
+ return EnvWrapper::RenameFile(src, dst);
+ }
+
+ Status LinkFile(const std::string& src, const std::string& dst) override {
+ PERF_TIMER_GUARD(env_link_file_nanos);
+ return EnvWrapper::LinkFile(src, dst);
+ }
+
+ Status LockFile(const std::string& fname, FileLock** lock) override {
+ PERF_TIMER_GUARD(env_lock_file_nanos);
+ return EnvWrapper::LockFile(fname, lock);
+ }
+
+ Status UnlockFile(FileLock* lock) override {
+ PERF_TIMER_GUARD(env_unlock_file_nanos);
+ return EnvWrapper::UnlockFile(lock);
+ }
+
+ Status NewLogger(const std::string& fname,
+ std::shared_ptr<Logger>* result) override {
+ PERF_TIMER_GUARD(env_new_logger_nanos);
+ return EnvWrapper::NewLogger(fname, result);
+ }
+};
+
+Env* NewTimedEnv(Env* base_env) { return new TimedEnv(base_env); }
+
+#else // ROCKSDB_LITE
+
+Env* NewTimedEnv(Env* /*base_env*/) { return nullptr; }
+
+#endif // !ROCKSDB_LITE
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/env_timed_test.cc b/storage/rocksdb/rocksdb/utilities/env_timed_test.cc
new file mode 100644
index 00000000..f1695185
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/env_timed_test.cc
@@ -0,0 +1,44 @@
+// 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).
+
+#ifndef ROCKSDB_LITE
+
+#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<Env> mem_env(NewMemEnv(Env::Default()));
+ std::unique_ptr<Env> timed_env(NewTimedEnv(mem_env.get()));
+ std::unique_ptr<WritableFile> writable_file;
+ 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else // ROCKSDB_LITE
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as TimedEnv is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/leveldb_options/leveldb_options.cc b/storage/rocksdb/rocksdb/utilities/leveldb_options/leveldb_options.cc
new file mode 100644
index 00000000..5698b21c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/leveldb_options/leveldb_options.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/utilities/leveldb_options.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+#include "rocksdb/filter_policy.h"
+#include "rocksdb/options.h"
+#include "rocksdb/table.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+LevelDBOptions::LevelDBOptions()
+ : comparator(BytewiseComparator()),
+ create_if_missing(false),
+ error_if_exists(false),
+ paranoid_checks(false),
+ env(Env::Default()),
+ info_log(nullptr),
+ write_buffer_size(4 << 20),
+ max_open_files(1000),
+ block_cache(nullptr),
+ block_size(4096),
+ block_restart_interval(16),
+ compression(kSnappyCompression),
+ filter_policy(nullptr) {}
+
+Options ConvertOptions(const LevelDBOptions& leveldb_options) {
+ Options options = Options();
+ options.create_if_missing = leveldb_options.create_if_missing;
+ options.error_if_exists = leveldb_options.error_if_exists;
+ options.paranoid_checks = leveldb_options.paranoid_checks;
+ options.env = leveldb_options.env;
+ options.info_log.reset(leveldb_options.info_log);
+ options.write_buffer_size = leveldb_options.write_buffer_size;
+ options.max_open_files = leveldb_options.max_open_files;
+ options.compression = leveldb_options.compression;
+
+ BlockBasedTableOptions table_options;
+ table_options.block_cache.reset(leveldb_options.block_cache);
+ table_options.block_size = leveldb_options.block_size;
+ table_options.block_restart_interval = leveldb_options.block_restart_interval;
+ table_options.filter_policy.reset(leveldb_options.filter_policy);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+
+ return options;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/memory/memory_test.cc b/storage/rocksdb/rocksdb/utilities/memory/memory_test.cc
new file mode 100644
index 00000000..9e253df4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/memory/memory_test.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/cache.h"
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/memory_util.h"
+#include "rocksdb/utilities/stackable_db.h"
+#include "table/block_based/block_based_table_factory.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MemoryTest : public testing::Test {
+ public:
+ MemoryTest() : kDbDir(test::PerThreadDBPath("memory_test")), rnd_(301) {
+ assert(Env::Default()->CreateDirIfMissing(kDbDir).ok());
+ }
+
+ std::string GetDBName(int id) { return kDbDir + "db_" + ToString(id); }
+
+ std::string RandomString(int len) {
+ std::string r;
+ test::RandomString(&rnd_, len, &r);
+ return r;
+ }
+
+ void UpdateUsagesHistory(const std::vector<DB*>& dbs) {
+ std::map<MemoryUtil::UsageType, uint64_t> usage_by_type;
+ ASSERT_OK(GetApproximateMemoryUsageByType(dbs, &usage_by_type));
+ for (int i = 0; i < MemoryUtil::kNumUsageTypes; ++i) {
+ usage_history_[i].push_back(
+ usage_by_type[static_cast<MemoryUtil::UsageType>(i)]);
+ }
+ }
+
+ void GetCachePointersFromTableFactory(
+ const TableFactory* factory,
+ std::unordered_set<const Cache*>* cache_set) {
+ const BlockBasedTableFactory* bbtf =
+ dynamic_cast<const BlockBasedTableFactory*>(factory);
+ if (bbtf != nullptr) {
+ const auto bbt_opts = bbtf->table_options();
+ cache_set->insert(bbt_opts.block_cache.get());
+ cache_set->insert(bbt_opts.block_cache_compressed.get());
+ }
+ }
+
+ void GetCachePointers(const std::vector<DB*>& dbs,
+ std::unordered_set<const Cache*>* cache_set) {
+ cache_set->clear();
+
+ for (auto* db : dbs) {
+ assert(db);
+
+ // Cache from DBImpl
+ StackableDB* sdb = dynamic_cast<StackableDB*>(db);
+ DBImpl* db_impl = dynamic_cast<DBImpl*>(sdb ? sdb->GetBaseDB() : db);
+ if (db_impl != nullptr) {
+ cache_set->insert(db_impl->TEST_table_cache());
+ }
+
+ // Cache from DBOptions
+ cache_set->insert(db->GetDBOptions().row_cache.get());
+
+ // Cache from table factories
+ std::unordered_map<std::string, const ImmutableCFOptions*> iopts_map;
+ if (db_impl != nullptr) {
+ ASSERT_OK(db_impl->TEST_GetAllImmutableCFOptions(&iopts_map));
+ }
+ for (auto pair : iopts_map) {
+ GetCachePointersFromTableFactory(pair.second->table_factory, cache_set);
+ }
+ }
+ }
+
+ Status GetApproximateMemoryUsageByType(
+ const std::vector<DB*>& dbs,
+ std::map<MemoryUtil::UsageType, uint64_t>* usage_by_type) {
+ std::unordered_set<const Cache*> cache_set;
+ GetCachePointers(dbs, &cache_set);
+
+ return MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set,
+ usage_by_type);
+ }
+
+ const std::string kDbDir;
+ Random rnd_;
+ std::vector<uint64_t> usage_history_[MemoryUtil::kNumUsageTypes];
+};
+
+TEST_F(MemoryTest, SharedBlockCacheTotal) {
+ std::vector<DB*> dbs;
+ std::vector<uint64_t> usage_by_type;
+ const int kNumDBs = 10;
+ const int kKeySize = 100;
+ const int kValueSize = 500;
+ Options opt;
+ opt.create_if_missing = true;
+ opt.write_buffer_size = kKeySize + kValueSize;
+ opt.max_write_buffer_number = 10;
+ opt.min_write_buffer_number_to_merge = 10;
+ opt.disable_auto_compactions = true;
+ BlockBasedTableOptions bbt_opts;
+ bbt_opts.block_cache = NewLRUCache(4096 * 1000 * 10);
+ for (int i = 0; i < kNumDBs; ++i) {
+ DestroyDB(GetDBName(i), opt);
+ DB* db = nullptr;
+ ASSERT_OK(DB::Open(opt, GetDBName(i), &db));
+ dbs.push_back(db);
+ }
+
+ std::vector<std::string> keys_by_db[kNumDBs];
+
+ // Fill one memtable per Put to make memtable use more memory.
+ for (int p = 0; p < opt.min_write_buffer_number_to_merge / 2; ++p) {
+ for (int i = 0; i < kNumDBs; ++i) {
+ for (int j = 0; j < 100; ++j) {
+ keys_by_db[i].emplace_back(RandomString(kKeySize));
+ dbs[i]->Put(WriteOptions(), keys_by_db[i].back(),
+ RandomString(kValueSize));
+ }
+ dbs[i]->Flush(FlushOptions());
+ }
+ }
+ for (int i = 0; i < kNumDBs; ++i) {
+ for (auto& key : keys_by_db[i]) {
+ std::string value;
+ dbs[i]->Get(ReadOptions(), key, &value);
+ }
+ UpdateUsagesHistory(dbs);
+ }
+ for (size_t i = 1; i < usage_history_[MemoryUtil::kMemTableTotal].size();
+ ++i) {
+ // Expect EQ as we didn't flush more memtables.
+ ASSERT_EQ(usage_history_[MemoryUtil::kTableReadersTotal][i],
+ usage_history_[MemoryUtil::kTableReadersTotal][i - 1]);
+ }
+ for (int i = 0; i < kNumDBs; ++i) {
+ delete dbs[i];
+ }
+}
+
+TEST_F(MemoryTest, MemTableAndTableReadersTotal) {
+ std::vector<DB*> dbs;
+ std::vector<uint64_t> usage_by_type;
+ std::vector<std::vector<ColumnFamilyHandle*>> vec_handles;
+ const int kNumDBs = 10;
+ const int kKeySize = 100;
+ const int kValueSize = 500;
+ Options opt;
+ opt.create_if_missing = true;
+ opt.create_missing_column_families = true;
+ opt.write_buffer_size = kKeySize + kValueSize;
+ opt.max_write_buffer_number = 10;
+ opt.min_write_buffer_number_to_merge = 10;
+ opt.disable_auto_compactions = true;
+
+ std::vector<ColumnFamilyDescriptor> cf_descs = {
+ {kDefaultColumnFamilyName, ColumnFamilyOptions(opt)},
+ {"one", ColumnFamilyOptions(opt)},
+ {"two", ColumnFamilyOptions(opt)},
+ };
+
+ for (int i = 0; i < kNumDBs; ++i) {
+ DestroyDB(GetDBName(i), opt);
+ std::vector<ColumnFamilyHandle*> handles;
+ dbs.emplace_back();
+ vec_handles.emplace_back();
+ ASSERT_OK(DB::Open(DBOptions(opt), GetDBName(i), cf_descs,
+ &vec_handles.back(), &dbs.back()));
+ }
+
+ // Fill one memtable per Put to make memtable use more memory.
+ for (int p = 0; p < opt.min_write_buffer_number_to_merge / 2; ++p) {
+ for (int i = 0; i < kNumDBs; ++i) {
+ for (auto* handle : vec_handles[i]) {
+ dbs[i]->Put(WriteOptions(), handle, RandomString(kKeySize),
+ RandomString(kValueSize));
+ UpdateUsagesHistory(dbs);
+ }
+ }
+ }
+ // Expect the usage history is monotonically increasing
+ for (size_t i = 1; i < usage_history_[MemoryUtil::kMemTableTotal].size();
+ ++i) {
+ ASSERT_GT(usage_history_[MemoryUtil::kMemTableTotal][i],
+ usage_history_[MemoryUtil::kMemTableTotal][i - 1]);
+ ASSERT_GT(usage_history_[MemoryUtil::kMemTableUnFlushed][i],
+ usage_history_[MemoryUtil::kMemTableUnFlushed][i - 1]);
+ ASSERT_EQ(usage_history_[MemoryUtil::kTableReadersTotal][i],
+ usage_history_[MemoryUtil::kTableReadersTotal][i - 1]);
+ }
+
+ size_t usage_check_point = usage_history_[MemoryUtil::kMemTableTotal].size();
+ std::vector<Iterator*> iters;
+
+ // Create an iterator and flush all memtables for each db
+ for (int i = 0; i < kNumDBs; ++i) {
+ iters.push_back(dbs[i]->NewIterator(ReadOptions()));
+ dbs[i]->Flush(FlushOptions());
+
+ for (int j = 0; j < 100; ++j) {
+ std::string value;
+ dbs[i]->Get(ReadOptions(), RandomString(kKeySize), &value);
+ }
+
+ UpdateUsagesHistory(dbs);
+ }
+ for (size_t i = usage_check_point;
+ i < usage_history_[MemoryUtil::kMemTableTotal].size(); ++i) {
+ // Since memtables are pinned by iterators, we don't expect the
+ // memory usage of all the memtables decreases as they are pinned
+ // by iterators.
+ ASSERT_GE(usage_history_[MemoryUtil::kMemTableTotal][i],
+ usage_history_[MemoryUtil::kMemTableTotal][i - 1]);
+ // Expect the usage history from the "usage_decay_point" is
+ // monotonically decreasing.
+ ASSERT_LT(usage_history_[MemoryUtil::kMemTableUnFlushed][i],
+ usage_history_[MemoryUtil::kMemTableUnFlushed][i - 1]);
+ // Expect the usage history of the table readers increases
+ // as we flush tables.
+ ASSERT_GT(usage_history_[MemoryUtil::kTableReadersTotal][i],
+ usage_history_[MemoryUtil::kTableReadersTotal][i - 1]);
+ ASSERT_GT(usage_history_[MemoryUtil::kCacheTotal][i],
+ usage_history_[MemoryUtil::kCacheTotal][i - 1]);
+ }
+ usage_check_point = usage_history_[MemoryUtil::kMemTableTotal].size();
+ for (int i = 0; i < kNumDBs; ++i) {
+ delete iters[i];
+ UpdateUsagesHistory(dbs);
+ }
+ for (size_t i = usage_check_point;
+ i < usage_history_[MemoryUtil::kMemTableTotal].size(); ++i) {
+ // Expect the usage of all memtables decreasing as we delete iterators.
+ ASSERT_LT(usage_history_[MemoryUtil::kMemTableTotal][i],
+ usage_history_[MemoryUtil::kMemTableTotal][i - 1]);
+ // Since the memory usage of un-flushed memtables is only affected
+ // by Put and flush, we expect EQ here as we only delete iterators.
+ ASSERT_EQ(usage_history_[MemoryUtil::kMemTableUnFlushed][i],
+ usage_history_[MemoryUtil::kMemTableUnFlushed][i - 1]);
+ // Expect EQ as we didn't flush more memtables.
+ ASSERT_EQ(usage_history_[MemoryUtil::kTableReadersTotal][i],
+ usage_history_[MemoryUtil::kTableReadersTotal][i - 1]);
+ }
+
+ for (int i = 0; i < kNumDBs; ++i) {
+ for (auto* handle : vec_handles[i]) {
+ delete handle;
+ }
+ delete dbs[i];
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+#if !(defined NDEBUG) || !defined(OS_WIN)
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+#else
+ return 0;
+#endif
+}
+
+#else
+#include <cstdio>
+
+int main(int /*argc*/, char** /*argv*/) {
+ printf("Skipped in RocksDBLite as utilities are not supported.\n");
+ return 0;
+}
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/memory/memory_util.cc b/storage/rocksdb/rocksdb/utilities/memory/memory_util.cc
new file mode 100644
index 00000000..13c81aec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/memory/memory_util.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/memory_util.h"
+
+#include "db/db_impl/db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status MemoryUtil::GetApproximateMemoryUsageByType(
+ const std::vector<DB*>& dbs,
+ const std::unordered_set<const Cache*> cache_set,
+ std::map<MemoryUtil::UsageType, uint64_t>* usage_by_type) {
+ usage_by_type->clear();
+
+ // MemTable
+ for (auto* db : dbs) {
+ uint64_t usage = 0;
+ if (db->GetAggregatedIntProperty(DB::Properties::kSizeAllMemTables,
+ &usage)) {
+ (*usage_by_type)[MemoryUtil::kMemTableTotal] += usage;
+ }
+ if (db->GetAggregatedIntProperty(DB::Properties::kCurSizeAllMemTables,
+ &usage)) {
+ (*usage_by_type)[MemoryUtil::kMemTableUnFlushed] += usage;
+ }
+ }
+
+ // Table Readers
+ for (auto* db : dbs) {
+ uint64_t usage = 0;
+ if (db->GetAggregatedIntProperty(DB::Properties::kEstimateTableReadersMem,
+ &usage)) {
+ (*usage_by_type)[MemoryUtil::kTableReadersTotal] += usage;
+ }
+ }
+
+ // Cache
+ for (const auto* cache : cache_set) {
+ if (cache != nullptr) {
+ (*usage_by_type)[MemoryUtil::kCacheTotal] += cache->GetUsage();
+ }
+ }
+
+ return Status::OK();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators.h b/storage/rocksdb/rocksdb/utilities/merge_operators.h
new file mode 100644
index 00000000..018d097b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/merge_operator.h"
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+class MergeOperators {
+ public:
+ static std::shared_ptr<MergeOperator> CreatePutOperator();
+ static std::shared_ptr<MergeOperator> CreateDeprecatedPutOperator();
+ static std::shared_ptr<MergeOperator> CreateUInt64AddOperator();
+ static std::shared_ptr<MergeOperator> CreateStringAppendOperator();
+ static std::shared_ptr<MergeOperator> CreateStringAppendOperator(char delim_char);
+ static std::shared_ptr<MergeOperator> CreateStringAppendTESTOperator();
+ static std::shared_ptr<MergeOperator> CreateMaxOperator();
+ static std::shared_ptr<MergeOperator> CreateBytesXOROperator();
+ static std::shared_ptr<MergeOperator> CreateSortOperator();
+
+ // Will return a different merge operator depending on the string.
+ // TODO: Hook the "name" up to the actual Name() of the MergeOperators?
+ static std::shared_ptr<MergeOperator> CreateFromStringId(
+ const std::string& name) {
+ if (name == "put") {
+ return CreatePutOperator();
+ } else if (name == "put_v1") {
+ return CreateDeprecatedPutOperator();
+ } else if ( name == "uint64add") {
+ return CreateUInt64AddOperator();
+ } else if (name == "stringappend") {
+ return CreateStringAppendOperator();
+ } else if (name == "stringappendtest") {
+ return CreateStringAppendTESTOperator();
+ } else if (name == "max") {
+ return CreateMaxOperator();
+ } else if (name == "bytesxor") {
+ return CreateBytesXOROperator();
+ } else if (name == "sortlist") {
+ return CreateSortOperator();
+ } else {
+ // Empty or unknown, just return nullptr
+ return nullptr;
+ }
+ }
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.cc
new file mode 100644
index 00000000..859affb5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <string>
+
+#include "utilities/merge_operators/bytesxor.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateBytesXOROperator() {
+ return std::make_shared<BytesXOROperator>();
+}
+
+bool BytesXOROperator::Merge(const Slice& /*key*/,
+ const Slice* existing_value,
+ const Slice& value,
+ std::string* new_value,
+ Logger* /*logger*/) const {
+ XOR(existing_value, value, new_value);
+ return true;
+}
+
+void BytesXOROperator::XOR(const Slice* existing_value,
+ const Slice& value, std::string* new_value) const {
+ if (!existing_value) {
+ new_value->clear();
+ new_value->assign(value.data(), value.size());
+ return;
+ }
+
+ size_t min_size = std::min(existing_value->size(), value.size());
+ size_t max_size = std::max(existing_value->size(), value.size());
+
+ new_value->clear();
+ new_value->reserve(max_size);
+
+ const char* existing_value_data = existing_value->data();
+ const char* value_data = value.data();
+
+ for (size_t i = 0; i < min_size; i++) {
+ new_value->push_back(existing_value_data[i] ^ value_data[i]);
+ }
+
+ if (existing_value->size() == max_size) {
+ for (size_t i = min_size; i < max_size; i++) {
+ new_value->push_back(existing_value_data[i]);
+ }
+ } else {
+ assert(value.size() == max_size);
+ for (size_t i = min_size; i < max_size; i++) {
+ new_value->push_back(value_data[i]);
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.h b/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.h
new file mode 100644
index 00000000..ab0c5aec
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/bytesxor.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <memory>
+#include <string>
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A 'model' merge operator that XORs two (same sized) array of bytes.
+// Implemented as an AssociativeMergeOperator for simplicity and example.
+class BytesXOROperator : public AssociativeMergeOperator {
+ public:
+ // XORs the two array of bytes one byte at a time and stores the result
+ // in new_value. len is the number of xored bytes, and the length of new_value
+ virtual bool Merge(const Slice& key,
+ const Slice* existing_value,
+ const Slice& value,
+ std::string* new_value,
+ Logger* logger) const override;
+
+ virtual const char* Name() const override {
+ return "BytesXOR";
+ }
+
+ void XOR(const Slice* existing_value, const Slice& value,
+ std::string* new_value) const;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/max.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/max.cc
new file mode 100644
index 00000000..2270c1f0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/max.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "utilities/merge_operators.h"
+
+using ROCKSDB_NAMESPACE::Logger;
+using ROCKSDB_NAMESPACE::MergeOperator;
+using ROCKSDB_NAMESPACE::Slice;
+
+namespace { // anonymous namespace
+
+// Merge operator that picks the maximum operand, Comparison is based on
+// Slice::compare
+class MaxOperator : public MergeOperator {
+ public:
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ Slice& max = merge_out->existing_operand;
+ if (merge_in.existing_value) {
+ max = Slice(merge_in.existing_value->data(),
+ merge_in.existing_value->size());
+ } else if (max.data() == nullptr) {
+ max = Slice();
+ }
+
+ for (const auto& op : merge_in.operand_list) {
+ if (max.compare(op) < 0) {
+ max = op;
+ }
+ }
+
+ return true;
+ }
+
+ bool PartialMerge(const Slice& /*key*/, const Slice& left_operand,
+ const Slice& right_operand, std::string* new_value,
+ Logger* /*logger*/) const override {
+ if (left_operand.compare(right_operand) >= 0) {
+ new_value->assign(left_operand.data(), left_operand.size());
+ } else {
+ new_value->assign(right_operand.data(), right_operand.size());
+ }
+ return true;
+ }
+
+ bool PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* /*logger*/) const override {
+ Slice max;
+ for (const auto& operand : operand_list) {
+ if (max.compare(operand) < 0) {
+ max = operand;
+ }
+ }
+
+ new_value->assign(max.data(), max.size());
+ return true;
+ }
+
+ const char* Name() const override { return "MaxOperator"; }
+};
+
+} // end of anonymous namespace
+
+namespace ROCKSDB_NAMESPACE {
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateMaxOperator() {
+ return std::make_shared<MaxOperator>();
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/put.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/put.cc
new file mode 100644
index 00000000..901d69e9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/put.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+#include "rocksdb/slice.h"
+#include "rocksdb/merge_operator.h"
+#include "utilities/merge_operators.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+namespace { // anonymous namespace
+
+// A merge operator that mimics Put semantics
+// Since this merge-operator will not be used in production,
+// it is implemented as a non-associative merge operator to illustrate the
+// new interface and for testing purposes. (That is, we inherit from
+// the MergeOperator class rather than the AssociativeMergeOperator
+// which would be simpler in this case).
+//
+// From the client-perspective, semantics are the same.
+class PutOperator : public MergeOperator {
+ public:
+ bool FullMerge(const Slice& /*key*/, const Slice* /*existing_value*/,
+ const std::deque<std::string>& operand_sequence,
+ std::string* new_value, Logger* /*logger*/) const override {
+ // Put basically only looks at the current/latest value
+ assert(!operand_sequence.empty());
+ assert(new_value != nullptr);
+ new_value->assign(operand_sequence.back());
+ return true;
+ }
+
+ bool PartialMerge(const Slice& /*key*/, const Slice& /*left_operand*/,
+ const Slice& right_operand, std::string* new_value,
+ Logger* /*logger*/) const override {
+ new_value->assign(right_operand.data(), right_operand.size());
+ return true;
+ }
+
+ using MergeOperator::PartialMergeMulti;
+ bool PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* /*logger*/) const override {
+ new_value->assign(operand_list.back().data(), operand_list.back().size());
+ return true;
+ }
+
+ const char* Name() const override { return "PutOperator"; }
+};
+
+class PutOperatorV2 : public PutOperator {
+ bool FullMerge(const Slice& /*key*/, const Slice* /*existing_value*/,
+ const std::deque<std::string>& /*operand_sequence*/,
+ std::string* /*new_value*/,
+ Logger* /*logger*/) const override {
+ assert(false);
+ return false;
+ }
+
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ // Put basically only looks at the current/latest value
+ assert(!merge_in.operand_list.empty());
+ merge_out->existing_operand = merge_in.operand_list.back();
+ return true;
+ }
+};
+
+} // end of anonymous namespace
+
+namespace ROCKSDB_NAMESPACE {
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateDeprecatedPutOperator() {
+ return std::make_shared<PutOperator>();
+}
+
+std::shared_ptr<MergeOperator> MergeOperators::CreatePutOperator() {
+ return std::make_shared<PutOperatorV2>();
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.cc
new file mode 100644
index 00000000..b6bd65b3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "utilities/merge_operators/sortlist.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "utilities/merge_operators.h"
+
+using ROCKSDB_NAMESPACE::Logger;
+using ROCKSDB_NAMESPACE::MergeOperator;
+using ROCKSDB_NAMESPACE::Slice;
+
+namespace ROCKSDB_NAMESPACE {
+
+bool SortList::FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const {
+ std::vector<int> left;
+ for (Slice slice : merge_in.operand_list) {
+ std::vector<int> right;
+ MakeVector(right, slice);
+ left = Merge(left, right);
+ }
+ for (int i = 0; i < static_cast<int>(left.size()) - 1; i++) {
+ merge_out->new_value.append(std::to_string(left[i])).append(",");
+ }
+ merge_out->new_value.append(std::to_string(left.back()));
+ return true;
+}
+
+bool SortList::PartialMerge(const Slice& /*key*/, const Slice& left_operand,
+ const Slice& right_operand, std::string* new_value,
+ Logger* /*logger*/) const {
+ std::vector<int> left;
+ std::vector<int> right;
+ MakeVector(left, left_operand);
+ MakeVector(right, right_operand);
+ left = Merge(left, right);
+ for (int i = 0; i < static_cast<int>(left.size()) - 1; i++) {
+ new_value->append(std::to_string(left[i])).append(",");
+ }
+ new_value->append(std::to_string(left.back()));
+ return true;
+}
+
+bool SortList::PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value,
+ Logger* /*logger*/) const {
+ (void)operand_list;
+ (void)new_value;
+ return true;
+}
+
+const char* SortList::Name() const { return "MergeSortOperator"; }
+
+void SortList::MakeVector(std::vector<int>& operand, Slice slice) const {
+ do {
+ const char* begin = slice.data_;
+ while (*slice.data_ != ',' && *slice.data_) slice.data_++;
+ operand.push_back(std::stoi(std::string(begin, slice.data_)));
+ } while (0 != *slice.data_++);
+}
+
+std::vector<int> SortList::Merge(std::vector<int>& left,
+ std::vector<int>& right) const {
+ // Fill the resultant vector with sorted results from both vectors
+ std::vector<int> result;
+ unsigned left_it = 0, right_it = 0;
+
+ while (left_it < left.size() && right_it < right.size()) {
+ // If the left value is smaller than the right it goes next
+ // into the resultant vector
+ if (left[left_it] < right[right_it]) {
+ result.push_back(left[left_it]);
+ left_it++;
+ } else {
+ result.push_back(right[right_it]);
+ right_it++;
+ }
+ }
+
+ // Push the remaining data from both vectors onto the resultant
+ while (left_it < left.size()) {
+ result.push_back(left[left_it]);
+ left_it++;
+ }
+
+ while (right_it < right.size()) {
+ result.push_back(right[right_it]);
+ right_it++;
+ }
+
+ return result;
+}
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateSortOperator() {
+ return std::make_shared<SortList>();
+}
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.h b/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.h
new file mode 100644
index 00000000..5e08bd58
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/sortlist.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+
+// A MergeOperator for RocksDB that implements Merge Sort.
+// It is built using the MergeOperator interface. The operator works by taking
+// an input which contains one or more merge operands where each operand is a
+// list of sorted ints and merges them to form a large sorted list.
+#pragma once
+
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SortList : public MergeOperator {
+ public:
+ bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override;
+
+ bool PartialMerge(const Slice& /*key*/, const Slice& left_operand,
+ const Slice& right_operand, std::string* new_value,
+ Logger* /*logger*/) const override;
+
+ bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* logger) const override;
+
+ const char* Name() const override;
+
+ void MakeVector(std::vector<int>& operand, Slice slice) const;
+
+ private:
+ std::vector<int> Merge(std::vector<int>& left, std::vector<int>& right) const;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.cc
new file mode 100644
index 00000000..534f7a56
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.cc
@@ -0,0 +1,59 @@
+/**
+ * A MergeOperator for rocksdb that implements string append.
+ * @author Deon Nicholas (dnicholas@fb.com)
+ * Copyright 2013 Facebook
+ */
+
+#include "stringappend.h"
+
+#include <memory>
+#include <assert.h>
+
+#include "rocksdb/slice.h"
+#include "rocksdb/merge_operator.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Constructor: also specify the delimiter character.
+StringAppendOperator::StringAppendOperator(char delim_char)
+ : delim_(delim_char) {
+}
+
+// Implementation for the merge operation (concatenates two strings)
+bool StringAppendOperator::Merge(const Slice& /*key*/,
+ const Slice* existing_value,
+ const Slice& value, std::string* new_value,
+ Logger* /*logger*/) const {
+ // Clear the *new_value for writing.
+ assert(new_value);
+ new_value->clear();
+
+ if (!existing_value) {
+ // No existing_value. Set *new_value = value
+ new_value->assign(value.data(),value.size());
+ } else {
+ // Generic append (existing_value != null).
+ // Reserve *new_value to correct size, and apply concatenation.
+ new_value->reserve(existing_value->size() + 1 + value.size());
+ new_value->assign(existing_value->data(),existing_value->size());
+ new_value->append(1,delim_);
+ new_value->append(value.data(), value.size());
+ }
+
+ return true;
+}
+
+const char* StringAppendOperator::Name() const {
+ return "StringAppendOperator";
+}
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateStringAppendOperator() {
+ return std::make_shared<StringAppendOperator>(',');
+}
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateStringAppendOperator(char delim_char) {
+ return std::make_shared<StringAppendOperator>(delim_char);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.h b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.h
new file mode 100644
index 00000000..388612f1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend.h
@@ -0,0 +1,31 @@
+/**
+ * A MergeOperator for rocksdb that implements string append.
+ * @author Deon Nicholas (dnicholas@fb.com)
+ * Copyright 2013 Facebook
+ */
+
+#pragma once
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class StringAppendOperator : public AssociativeMergeOperator {
+ public:
+ // Constructor: specify delimiter
+ explicit StringAppendOperator(char delim_char);
+
+ virtual bool Merge(const Slice& key,
+ const Slice* existing_value,
+ const Slice& value,
+ std::string* new_value,
+ Logger* logger) const override;
+
+ virtual const char* Name() const override;
+
+ private:
+ char delim_; // The delimiter is inserted between elements
+
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.cc
new file mode 100644
index 00000000..b8c676ee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.cc
@@ -0,0 +1,117 @@
+/**
+ * @author Deon Nicholas (dnicholas@fb.com)
+ * Copyright 2013 Facebook
+ */
+
+#include "stringappend2.h"
+
+#include <memory>
+#include <string>
+#include <assert.h>
+
+#include "rocksdb/slice.h"
+#include "rocksdb/merge_operator.h"
+#include "utilities/merge_operators.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Constructor: also specify the delimiter character.
+StringAppendTESTOperator::StringAppendTESTOperator(char delim_char)
+ : delim_(delim_char) {
+}
+
+// Implementation for the merge operation (concatenates two strings)
+bool StringAppendTESTOperator::FullMergeV2(
+ const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const {
+ // Clear the *new_value for writing.
+ merge_out->new_value.clear();
+
+ if (merge_in.existing_value == nullptr && merge_in.operand_list.size() == 1) {
+ // Only one operand
+ merge_out->existing_operand = merge_in.operand_list.back();
+ return true;
+ }
+
+ // Compute the space needed for the final result.
+ size_t numBytes = 0;
+ for (auto it = merge_in.operand_list.begin();
+ it != merge_in.operand_list.end(); ++it) {
+ numBytes += it->size() + 1; // Plus 1 for the delimiter
+ }
+
+ // Only print the delimiter after the first entry has been printed
+ bool printDelim = false;
+
+ // Prepend the *existing_value if one exists.
+ if (merge_in.existing_value) {
+ merge_out->new_value.reserve(numBytes + merge_in.existing_value->size());
+ merge_out->new_value.append(merge_in.existing_value->data(),
+ merge_in.existing_value->size());
+ printDelim = true;
+ } else if (numBytes) {
+ merge_out->new_value.reserve(
+ numBytes - 1); // Minus 1 since we have one less delimiter
+ }
+
+ // Concatenate the sequence of strings (and add a delimiter between each)
+ for (auto it = merge_in.operand_list.begin();
+ it != merge_in.operand_list.end(); ++it) {
+ if (printDelim) {
+ merge_out->new_value.append(1, delim_);
+ }
+ merge_out->new_value.append(it->data(), it->size());
+ printDelim = true;
+ }
+
+ return true;
+}
+
+bool StringAppendTESTOperator::PartialMergeMulti(
+ const Slice& /*key*/, const std::deque<Slice>& /*operand_list*/,
+ std::string* /*new_value*/, Logger* /*logger*/) const {
+ return false;
+}
+
+// A version of PartialMerge that actually performs "partial merging".
+// Use this to simulate the exact behaviour of the StringAppendOperator.
+bool StringAppendTESTOperator::_AssocPartialMergeMulti(
+ const Slice& /*key*/, const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* /*logger*/) const {
+ // Clear the *new_value for writing
+ assert(new_value);
+ new_value->clear();
+ assert(operand_list.size() >= 2);
+
+ // Generic append
+ // Determine and reserve correct size for *new_value.
+ size_t size = 0;
+ for (const auto& operand : operand_list) {
+ size += operand.size();
+ }
+ size += operand_list.size() - 1; // Delimiters
+ new_value->reserve(size);
+
+ // Apply concatenation
+ new_value->assign(operand_list.front().data(), operand_list.front().size());
+
+ for (std::deque<Slice>::const_iterator it = operand_list.begin() + 1;
+ it != operand_list.end(); ++it) {
+ new_value->append(1, delim_);
+ new_value->append(it->data(), it->size());
+ }
+
+ return true;
+}
+
+const char* StringAppendTESTOperator::Name() const {
+ return "StringAppendTESTOperator";
+}
+
+
+std::shared_ptr<MergeOperator>
+MergeOperators::CreateStringAppendTESTOperator() {
+ return std::make_shared<StringAppendTESTOperator>(',');
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.h b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.h
new file mode 100644
index 00000000..452164d8
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend2.h
@@ -0,0 +1,49 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+/**
+ * A TEST MergeOperator for rocksdb that implements string append.
+ * It is built using the MergeOperator interface rather than the simpler
+ * AssociativeMergeOperator interface. This is useful for testing/benchmarking.
+ * While the two operators are semantically the same, all production code
+ * should use the StringAppendOperator defined in stringappend.{h,cc}. The
+ * operator defined in the present file is primarily for testing.
+ *
+ * @author Deon Nicholas (dnicholas@fb.com)
+ * Copyright 2013 Facebook
+ */
+
+#pragma once
+#include <deque>
+#include <string>
+
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class StringAppendTESTOperator : public MergeOperator {
+ public:
+ // Constructor with delimiter
+ explicit StringAppendTESTOperator(char delim_char);
+
+ virtual bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override;
+
+ virtual bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* logger) const
+ override;
+
+ virtual const char* Name() const override;
+
+ private:
+ // A version of PartialMerge that actually performs "partial merging".
+ // Use this to simulate the exact behaviour of the StringAppendOperator.
+ bool _AssocPartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* logger) const;
+
+ char delim_; // The delimiter is inserted between elements
+
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend_test.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend_test.cc
new file mode 100644
index 00000000..c5f5e3e7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/string_append/stringappend_test.cc
@@ -0,0 +1,601 @@
+/**
+ * An persistent map : key -> (list of strings), using rocksdb merge.
+ * This file is a test-harness / use-case for the StringAppendOperator.
+ *
+ * @author Deon Nicholas (dnicholas@fb.com)
+ * Copyright 2013 Facebook, Inc.
+*/
+
+#include <iostream>
+#include <map>
+
+#include "rocksdb/db.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "test_util/testharness.h"
+#include "util/random.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/string_append/stringappend.h"
+#include "utilities/merge_operators/string_append/stringappend2.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+namespace ROCKSDB_NAMESPACE {
+
+// Path to the database on file system
+const std::string kDbName = test::PerThreadDBPath("stringappend_test");
+
+namespace {
+// OpenDb opens a (possibly new) rocksdb database with a StringAppendOperator
+std::shared_ptr<DB> OpenNormalDb(char delim_char) {
+ DB* db;
+ Options options;
+ options.create_if_missing = true;
+ options.merge_operator.reset(new StringAppendOperator(delim_char));
+ EXPECT_OK(DB::Open(options, kDbName, &db));
+ return std::shared_ptr<DB>(db);
+}
+
+#ifndef ROCKSDB_LITE // TtlDb is not supported in Lite
+// Open a TtlDB with a non-associative StringAppendTESTOperator
+std::shared_ptr<DB> OpenTtlDb(char delim_char) {
+ DBWithTTL* db;
+ Options options;
+ options.create_if_missing = true;
+ options.merge_operator.reset(new StringAppendTESTOperator(delim_char));
+ EXPECT_OK(DBWithTTL::Open(options, kDbName, &db, 123456));
+ return std::shared_ptr<DB>(db);
+}
+#endif // !ROCKSDB_LITE
+} // namespace
+
+/// StringLists represents a set of string-lists, each with a key-index.
+/// Supports Append(list, string) and Get(list)
+class StringLists {
+ public:
+
+ //Constructor: specifies the rocksdb db
+ /* implicit */
+ StringLists(std::shared_ptr<DB> db)
+ : db_(db),
+ merge_option_(),
+ get_option_() {
+ assert(db);
+ }
+
+ // Append string val onto the list defined by key; return true on success
+ bool Append(const std::string& key, const std::string& val){
+ Slice valSlice(val.data(), val.size());
+ auto s = db_->Merge(merge_option_, key, valSlice);
+
+ if (s.ok()) {
+ return true;
+ } else {
+ std::cerr << "ERROR " << s.ToString() << std::endl;
+ return false;
+ }
+ }
+
+ // Returns the list of strings associated with key (or "" if does not exist)
+ bool Get(const std::string& key, std::string* const result){
+ assert(result != nullptr); // we should have a place to store the result
+ auto s = db_->Get(get_option_, key, result);
+
+ if (s.ok()) {
+ return true;
+ }
+
+ // Either key does not exist, or there is some error.
+ *result = ""; // Always return empty string (just for convention)
+
+ //NotFound is okay; just return empty (similar to std::map)
+ //But network or db errors, etc, should fail the test (or at least yell)
+ if (!s.IsNotFound()) {
+ std::cerr << "ERROR " << s.ToString() << std::endl;
+ }
+
+ // Always return false if s.ok() was not true
+ return false;
+ }
+
+
+ private:
+ std::shared_ptr<DB> db_;
+ WriteOptions merge_option_;
+ ReadOptions get_option_;
+
+};
+
+
+// The class for unit-testing
+class StringAppendOperatorTest : public testing::Test {
+ public:
+ StringAppendOperatorTest() {
+ DestroyDB(kDbName, Options()); // Start each test with a fresh DB
+ }
+
+ typedef std::shared_ptr<DB> (* OpenFuncPtr)(char);
+
+ // Allows user to open databases with different configurations.
+ // e.g.: Can open a DB or a TtlDB, etc.
+ static void SetOpenDbFunction(OpenFuncPtr func) {
+ OpenDb = func;
+ }
+
+ protected:
+ static OpenFuncPtr OpenDb;
+};
+StringAppendOperatorTest::OpenFuncPtr StringAppendOperatorTest::OpenDb = nullptr;
+
+// THE TEST CASES BEGIN HERE
+
+TEST_F(StringAppendOperatorTest, IteratorTest) {
+ auto db_ = OpenDb(',');
+ StringLists slists(db_);
+
+ slists.Append("k1", "v1");
+ slists.Append("k1", "v2");
+ slists.Append("k1", "v3");
+
+ slists.Append("k2", "a1");
+ slists.Append("k2", "a2");
+ slists.Append("k2", "a3");
+
+ std::string res;
+ std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> it(
+ db_->NewIterator(ReadOptions()));
+ std::string k1("k1");
+ std::string k2("k2");
+ bool first = true;
+ for (it->Seek(k1); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ ASSERT_EQ(res, "v1,v2,v3");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "a1,a2,a3");
+ }
+ }
+ slists.Append("k2", "a4");
+ slists.Append("k1", "v4");
+
+ // Snapshot should still be the same. Should ignore a4 and v4.
+ first = true;
+ for (it->Seek(k1); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ ASSERT_EQ(res, "v1,v2,v3");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "a1,a2,a3");
+ }
+ }
+
+
+ // Should release the snapshot and be aware of the new stuff now
+ it.reset(db_->NewIterator(ReadOptions()));
+ first = true;
+ for (it->Seek(k1); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ ASSERT_EQ(res, "v1,v2,v3,v4");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "a1,a2,a3,a4");
+ }
+ }
+
+ // start from k2 this time.
+ for (it->Seek(k2); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ ASSERT_EQ(res, "v1,v2,v3,v4");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "a1,a2,a3,a4");
+ }
+ }
+
+ slists.Append("k3", "g1");
+
+ it.reset(db_->NewIterator(ReadOptions()));
+ first = true;
+ std::string k3("k3");
+ for(it->Seek(k2); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ ASSERT_EQ(res, "a1,a2,a3,a4");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "g1");
+ }
+ }
+ for(it->Seek(k3); it->Valid(); it->Next()) {
+ res = it->value().ToString();
+ if (first) {
+ // should not be hit
+ ASSERT_EQ(res, "a1,a2,a3,a4");
+ first = false;
+ } else {
+ ASSERT_EQ(res, "g1");
+ }
+ }
+
+}
+
+TEST_F(StringAppendOperatorTest, SimpleTest) {
+ auto db = OpenDb(',');
+ StringLists slists(db);
+
+ slists.Append("k1", "v1");
+ slists.Append("k1", "v2");
+ slists.Append("k1", "v3");
+
+ std::string res;
+ bool status = slists.Get("k1", &res);
+
+ ASSERT_TRUE(status);
+ ASSERT_EQ(res, "v1,v2,v3");
+}
+
+TEST_F(StringAppendOperatorTest, SimpleDelimiterTest) {
+ auto db = OpenDb('|');
+ StringLists slists(db);
+
+ slists.Append("k1", "v1");
+ slists.Append("k1", "v2");
+ slists.Append("k1", "v3");
+
+ std::string res;
+ slists.Get("k1", &res);
+ ASSERT_EQ(res, "v1|v2|v3");
+}
+
+TEST_F(StringAppendOperatorTest, OneValueNoDelimiterTest) {
+ auto db = OpenDb('!');
+ StringLists slists(db);
+
+ slists.Append("random_key", "single_val");
+
+ std::string res;
+ slists.Get("random_key", &res);
+ ASSERT_EQ(res, "single_val");
+}
+
+TEST_F(StringAppendOperatorTest, VariousKeys) {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+
+ slists.Append("c", "asdasd");
+ slists.Append("a", "x");
+ slists.Append("b", "y");
+ slists.Append("a", "t");
+ slists.Append("a", "r");
+ slists.Append("b", "2");
+ slists.Append("c", "asdasd");
+
+ std::string a, b, c;
+ bool sa, sb, sc;
+ sa = slists.Get("a", &a);
+ sb = slists.Get("b", &b);
+ sc = slists.Get("c", &c);
+
+ ASSERT_TRUE(sa && sb && sc); // All three keys should have been found
+
+ ASSERT_EQ(a, "x\nt\nr");
+ ASSERT_EQ(b, "y\n2");
+ ASSERT_EQ(c, "asdasd\nasdasd");
+}
+
+// Generate semi random keys/words from a small distribution.
+TEST_F(StringAppendOperatorTest, RandomMixGetAppend) {
+ auto db = OpenDb(' ');
+ StringLists slists(db);
+
+ // Generate a list of random keys and values
+ const int kWordCount = 15;
+ std::string words[] = {"sdasd", "triejf", "fnjsdfn", "dfjisdfsf", "342839",
+ "dsuha", "mabuais", "sadajsid", "jf9834hf", "2d9j89",
+ "dj9823jd", "a", "dk02ed2dh", "$(jd4h984$(*", "mabz"};
+ const int kKeyCount = 6;
+ std::string keys[] = {"dhaiusdhu", "denidw", "daisda", "keykey", "muki",
+ "shzassdianmd"};
+
+ // Will store a local copy of all data in order to verify correctness
+ std::map<std::string, std::string> parallel_copy;
+
+ // Generate a bunch of random queries (Append and Get)!
+ enum query_t { APPEND_OP, GET_OP, NUM_OPS };
+ Random randomGen(1337); //deterministic seed; always get same results!
+
+ const int kNumQueries = 30;
+ for (int q=0; q<kNumQueries; ++q) {
+ // Generate a random query (Append or Get) and random parameters
+ query_t query = (query_t)randomGen.Uniform((int)NUM_OPS);
+ std::string key = keys[randomGen.Uniform((int)kKeyCount)];
+ std::string word = words[randomGen.Uniform((int)kWordCount)];
+
+ // Apply the query and any checks.
+ if (query == APPEND_OP) {
+
+ // Apply the rocksdb test-harness Append defined above
+ slists.Append(key, word); //apply the rocksdb append
+
+ // Apply the similar "Append" to the parallel copy
+ if (parallel_copy[key].size() > 0) {
+ parallel_copy[key] += " " + word;
+ } else {
+ parallel_copy[key] = word;
+ }
+
+ } else if (query == GET_OP) {
+ // Assumes that a non-existent key just returns <empty>
+ std::string res;
+ slists.Get(key, &res);
+ ASSERT_EQ(res, parallel_copy[key]);
+ }
+
+ }
+
+}
+
+TEST_F(StringAppendOperatorTest, BIGRandomMixGetAppend) {
+ auto db = OpenDb(' ');
+ StringLists slists(db);
+
+ // Generate a list of random keys and values
+ const int kWordCount = 15;
+ std::string words[] = {"sdasd", "triejf", "fnjsdfn", "dfjisdfsf", "342839",
+ "dsuha", "mabuais", "sadajsid", "jf9834hf", "2d9j89",
+ "dj9823jd", "a", "dk02ed2dh", "$(jd4h984$(*", "mabz"};
+ const int kKeyCount = 6;
+ std::string keys[] = {"dhaiusdhu", "denidw", "daisda", "keykey", "muki",
+ "shzassdianmd"};
+
+ // Will store a local copy of all data in order to verify correctness
+ std::map<std::string, std::string> parallel_copy;
+
+ // Generate a bunch of random queries (Append and Get)!
+ enum query_t { APPEND_OP, GET_OP, NUM_OPS };
+ Random randomGen(9138204); // deterministic seed
+
+ const int kNumQueries = 1000;
+ for (int q=0; q<kNumQueries; ++q) {
+ // Generate a random query (Append or Get) and random parameters
+ query_t query = (query_t)randomGen.Uniform((int)NUM_OPS);
+ std::string key = keys[randomGen.Uniform((int)kKeyCount)];
+ std::string word = words[randomGen.Uniform((int)kWordCount)];
+
+ //Apply the query and any checks.
+ if (query == APPEND_OP) {
+
+ // Apply the rocksdb test-harness Append defined above
+ slists.Append(key, word); //apply the rocksdb append
+
+ // Apply the similar "Append" to the parallel copy
+ if (parallel_copy[key].size() > 0) {
+ parallel_copy[key] += " " + word;
+ } else {
+ parallel_copy[key] = word;
+ }
+
+ } else if (query == GET_OP) {
+ // Assumes that a non-existent key just returns <empty>
+ std::string res;
+ slists.Get(key, &res);
+ ASSERT_EQ(res, parallel_copy[key]);
+ }
+
+ }
+
+}
+
+TEST_F(StringAppendOperatorTest, PersistentVariousKeys) {
+ // Perform the following operations in limited scope
+ {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+
+ slists.Append("c", "asdasd");
+ slists.Append("a", "x");
+ slists.Append("b", "y");
+ slists.Append("a", "t");
+ slists.Append("a", "r");
+ slists.Append("b", "2");
+ slists.Append("c", "asdasd");
+
+ std::string a, b, c;
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+
+ ASSERT_EQ(a, "x\nt\nr");
+ ASSERT_EQ(b, "y\n2");
+ ASSERT_EQ(c, "asdasd\nasdasd");
+ }
+
+ // Reopen the database (the previous changes should persist / be remembered)
+ {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+
+ slists.Append("c", "bbnagnagsx");
+ slists.Append("a", "sa");
+ slists.Append("b", "df");
+ slists.Append("a", "gh");
+ slists.Append("a", "jk");
+ slists.Append("b", "l;");
+ slists.Append("c", "rogosh");
+
+ // The previous changes should be on disk (L0)
+ // The most recent changes should be in memory (MemTable)
+ // Hence, this will test both Get() paths.
+ std::string a, b, c;
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+
+ ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
+ ASSERT_EQ(b, "y\n2\ndf\nl;");
+ ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
+ }
+
+ // Reopen the database (the previous changes should persist / be remembered)
+ {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+
+ // All changes should be on disk. This will test VersionSet Get()
+ std::string a, b, c;
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+
+ ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
+ ASSERT_EQ(b, "y\n2\ndf\nl;");
+ ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
+ }
+}
+
+TEST_F(StringAppendOperatorTest, PersistentFlushAndCompaction) {
+ // Perform the following operations in limited scope
+ {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+ std::string a, b, c;
+ bool success;
+
+ // Append, Flush, Get
+ slists.Append("c", "asdasd");
+ db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
+ success = slists.Get("c", &c);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(c, "asdasd");
+
+ // Append, Flush, Append, Get
+ slists.Append("a", "x");
+ slists.Append("b", "y");
+ db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
+ slists.Append("a", "t");
+ slists.Append("a", "r");
+ slists.Append("b", "2");
+
+ success = slists.Get("a", &a);
+ assert(success == true);
+ ASSERT_EQ(a, "x\nt\nr");
+
+ success = slists.Get("b", &b);
+ assert(success == true);
+ ASSERT_EQ(b, "y\n2");
+
+ // Append, Get
+ success = slists.Append("c", "asdasd");
+ assert(success);
+ success = slists.Append("b", "monkey");
+ assert(success);
+
+ // I omit the "assert(success)" checks here.
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+
+ ASSERT_EQ(a, "x\nt\nr");
+ ASSERT_EQ(b, "y\n2\nmonkey");
+ ASSERT_EQ(c, "asdasd\nasdasd");
+ }
+
+ // Reopen the database (the previous changes should persist / be remembered)
+ {
+ auto db = OpenDb('\n');
+ StringLists slists(db);
+ std::string a, b, c;
+
+ // Get (Quick check for persistence of previous database)
+ slists.Get("a", &a);
+ ASSERT_EQ(a, "x\nt\nr");
+
+ //Append, Compact, Get
+ slists.Append("c", "bbnagnagsx");
+ slists.Append("a", "sa");
+ slists.Append("b", "df");
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+ ASSERT_EQ(a, "x\nt\nr\nsa");
+ ASSERT_EQ(b, "y\n2\nmonkey\ndf");
+ ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx");
+
+ // Append, Get
+ slists.Append("a", "gh");
+ slists.Append("a", "jk");
+ slists.Append("b", "l;");
+ slists.Append("c", "rogosh");
+ slists.Get("a", &a);
+ slists.Get("b", &b);
+ slists.Get("c", &c);
+ ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
+ ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;");
+ ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
+
+ // Compact, Get
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
+ ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;");
+ ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
+
+ // Append, Flush, Compact, Get
+ slists.Append("b", "afcg");
+ db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
+ db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ slists.Get("b", &b);
+ ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;\nafcg");
+ }
+}
+
+TEST_F(StringAppendOperatorTest, SimpleTestNullDelimiter) {
+ auto db = OpenDb('\0');
+ StringLists slists(db);
+
+ slists.Append("k1", "v1");
+ slists.Append("k1", "v2");
+ slists.Append("k1", "v3");
+
+ std::string res;
+ bool status = slists.Get("k1", &res);
+ ASSERT_TRUE(status);
+
+ // Construct the desired string. Default constructor doesn't like '\0' chars.
+ std::string checker("v1,v2,v3"); // Verify that the string is right size.
+ checker[2] = '\0'; // Use null delimiter instead of comma.
+ checker[5] = '\0';
+ assert(checker.size() == 8); // Verify it is still the correct size
+
+ // Check that the rocksdb result string matches the desired string
+ assert(res.size() == checker.size());
+ ASSERT_EQ(res, checker);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ // Run with regular database
+ int result;
+ {
+ fprintf(stderr, "Running tests with regular db and operator.\n");
+ StringAppendOperatorTest::SetOpenDbFunction(&OpenNormalDb);
+ result = RUN_ALL_TESTS();
+ }
+
+#ifndef ROCKSDB_LITE // TtlDb is not supported in Lite
+ // Run with TTL
+ {
+ fprintf(stderr, "Running tests with ttl db and generic operator.\n");
+ StringAppendOperatorTest::SetOpenDbFunction(&OpenTtlDb);
+ result |= RUN_ALL_TESTS();
+ }
+#endif // !ROCKSDB_LITE
+
+ return result;
+}
diff --git a/storage/rocksdb/rocksdb/utilities/merge_operators/uint64add.cc b/storage/rocksdb/rocksdb/utilities/merge_operators/uint64add.cc
new file mode 100644
index 00000000..3ef24092
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/merge_operators/uint64add.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <memory>
+
+#include "logging/logging.h"
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/slice.h"
+#include "util/coding.h"
+#include "utilities/merge_operators.h"
+
+using namespace ROCKSDB_NAMESPACE;
+
+namespace { // anonymous namespace
+
+// A 'model' merge operator with uint64 addition semantics
+// Implemented as an AssociativeMergeOperator for simplicity and example.
+class UInt64AddOperator : public AssociativeMergeOperator {
+ public:
+ bool Merge(const Slice& /*key*/, const Slice* existing_value,
+ const Slice& value, std::string* new_value,
+ Logger* logger) const override {
+ uint64_t orig_value = 0;
+ if (existing_value){
+ orig_value = DecodeInteger(*existing_value, logger);
+ }
+ uint64_t operand = DecodeInteger(value, logger);
+
+ assert(new_value);
+ new_value->clear();
+ PutFixed64(new_value, orig_value + operand);
+
+ return true; // Return true always since corruption will be treated as 0
+ }
+
+ const char* Name() const override { return "UInt64AddOperator"; }
+
+ private:
+ // Takes the string and decodes it into a uint64_t
+ // On error, prints a message and returns 0
+ uint64_t DecodeInteger(const Slice& value, Logger* logger) const {
+ uint64_t result = 0;
+
+ if (value.size() == sizeof(uint64_t)) {
+ result = DecodeFixed64(value.data());
+ } else if (logger != nullptr) {
+ // If value is corrupted, treat it as 0
+ ROCKS_LOG_ERROR(logger, "uint64 value corruption, size: %" ROCKSDB_PRIszt
+ " > %" ROCKSDB_PRIszt,
+ value.size(), sizeof(uint64_t));
+ }
+
+ return result;
+ }
+
+};
+
+}
+
+namespace ROCKSDB_NAMESPACE {
+
+std::shared_ptr<MergeOperator> MergeOperators::CreateUInt64AddOperator() {
+ return std::make_shared<UInt64AddOperator>();
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/object_registry.cc b/storage/rocksdb/rocksdb/utilities/object_registry.cc
new file mode 100644
index 00000000..38e55020
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/object_registry.cc
@@ -0,0 +1,87 @@
+// 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/utilities/object_registry.h"
+
+#include "logging/logging.h"
+#include "rocksdb/env.h"
+
+namespace ROCKSDB_NAMESPACE {
+#ifndef ROCKSDB_LITE
+// Looks through the "type" factories for one that matches "name".
+// If found, returns the pointer to the Entry matching this name.
+// Otherwise, nullptr is returned
+const ObjectLibrary::Entry *ObjectLibrary::FindEntry(
+ const std::string &type, const std::string &name) const {
+ auto entries = entries_.find(type);
+ if (entries != entries_.end()) {
+ for (const auto &entry : entries->second) {
+ if (entry->matches(name)) {
+ return entry.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
+void ObjectLibrary::AddEntry(const std::string &type,
+ std::unique_ptr<Entry> &entry) {
+ auto &entries = entries_[type];
+ entries.emplace_back(std::move(entry));
+}
+
+void ObjectLibrary::Dump(Logger *logger) const {
+ for (const auto &iter : entries_) {
+ ROCKS_LOG_HEADER(logger, " Registered factories for type[%s] ",
+ iter.first.c_str());
+ bool printed_one = false;
+ for (const auto &e : iter.second) {
+ ROCKS_LOG_HEADER(logger, "%c %s", (printed_one) ? ',' : ':',
+ e->Name().c_str());
+ printed_one = true;
+ }
+ }
+ ROCKS_LOG_HEADER(logger, "\n");
+}
+
+// Returns the Default singleton instance of the ObjectLibrary
+// This instance will contain most of the "standard" registered objects
+std::shared_ptr<ObjectLibrary> &ObjectLibrary::Default() {
+ static std::shared_ptr<ObjectLibrary> instance =
+ std::make_shared<ObjectLibrary>();
+ return instance;
+}
+
+std::shared_ptr<ObjectRegistry> ObjectRegistry::NewInstance() {
+ std::shared_ptr<ObjectRegistry> instance = std::make_shared<ObjectRegistry>();
+ return instance;
+}
+
+ObjectRegistry::ObjectRegistry() {
+ libraries_.push_back(ObjectLibrary::Default());
+}
+
+// Searches (from back to front) the libraries looking for the
+// an entry that matches this pattern.
+// Returns the entry if it is found, and nullptr otherwise
+const ObjectLibrary::Entry *ObjectRegistry::FindEntry(
+ const std::string &type, const std::string &name) const {
+ for (auto iter = libraries_.crbegin(); iter != libraries_.crend(); ++iter) {
+ const auto *entry = iter->get()->FindEntry(type, name);
+ if (entry != nullptr) {
+ return entry;
+ }
+ }
+ return nullptr;
+}
+
+void ObjectRegistry::Dump(Logger *logger) const {
+ for (auto iter = libraries_.crbegin(); iter != libraries_.crend(); ++iter) {
+ iter->get()->Dump(logger);
+ }
+}
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/object_registry_test.cc b/storage/rocksdb/rocksdb/utilities/object_registry_test.cc
new file mode 100644
index 00000000..bbb76b14
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/object_registry_test.cc
@@ -0,0 +1,174 @@
+// 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).
+
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/utilities/object_registry.h"
+#include "test_util/testharness.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class EnvRegistryTest : public testing::Test {
+ public:
+ static int num_a, num_b;
+};
+
+int EnvRegistryTest::num_a = 0;
+int EnvRegistryTest::num_b = 0;
+static FactoryFunc<Env> test_reg_a = ObjectLibrary::Default()->Register<Env>(
+ "a://.*",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*env_guard*/,
+ std::string* /* errmsg */) {
+ ++EnvRegistryTest::num_a;
+ return Env::Default();
+ });
+
+static FactoryFunc<Env> test_reg_b = ObjectLibrary::Default()->Register<Env>(
+ "b://.*", [](const std::string& /*uri*/, std::unique_ptr<Env>* env_guard,
+ std::string* /* errmsg */) {
+ ++EnvRegistryTest::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 EnvWrapper(Env::Default()));
+ return env_guard->get();
+ });
+
+TEST_F(EnvRegistryTest, Basics) {
+ std::string msg;
+ std::unique_ptr<Env> env_guard;
+ auto registry = ObjectRegistry::NewInstance();
+ auto res = registry->NewObject<Env>("a://test", &env_guard, &msg);
+ ASSERT_NE(res, nullptr);
+ ASSERT_EQ(env_guard, nullptr);
+ ASSERT_EQ(1, num_a);
+ ASSERT_EQ(0, num_b);
+
+ res = registry->NewObject<Env>("b://test", &env_guard, &msg);
+ ASSERT_NE(res, nullptr);
+ ASSERT_NE(env_guard, nullptr);
+ ASSERT_EQ(1, num_a);
+ ASSERT_EQ(1, num_b);
+
+ res = registry->NewObject<Env>("c://test", &env_guard, &msg);
+ ASSERT_EQ(res, nullptr);
+ ASSERT_EQ(env_guard, nullptr);
+ ASSERT_EQ(1, num_a);
+ ASSERT_EQ(1, num_b);
+}
+
+TEST_F(EnvRegistryTest, LocalRegistry) {
+ std::string msg;
+ std::unique_ptr<Env> guard;
+ auto registry = ObjectRegistry::NewInstance();
+ std::shared_ptr<ObjectLibrary> library = std::make_shared<ObjectLibrary>();
+ registry->AddLibrary(library);
+ library->Register<Env>(
+ "test-local",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*guard */,
+ std::string* /* errmsg */) { return Env::Default(); });
+
+ ObjectLibrary::Default()->Register<Env>(
+ "test-global",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*guard */,
+ std::string* /* errmsg */) { return Env::Default(); });
+
+ ASSERT_EQ(
+ ObjectRegistry::NewInstance()->NewObject<Env>("test-local", &guard, &msg),
+ nullptr);
+ ASSERT_NE(
+ ObjectRegistry::NewInstance()->NewObject("test-global", &guard, &msg),
+ nullptr);
+ ASSERT_NE(registry->NewObject<Env>("test-local", &guard, &msg), nullptr);
+ ASSERT_NE(registry->NewObject<Env>("test-global", &guard, &msg), nullptr);
+}
+
+TEST_F(EnvRegistryTest, CheckShared) {
+ std::shared_ptr<Env> shared;
+ std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
+ std::shared_ptr<ObjectLibrary> library = std::make_shared<ObjectLibrary>();
+ registry->AddLibrary(library);
+ library->Register<Env>(
+ "unguarded",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*guard */,
+ std::string* /* errmsg */) { return Env::Default(); });
+
+ library->Register<Env>(
+ "guarded", [](const std::string& /*uri*/, std::unique_ptr<Env>* guard,
+ std::string* /* errmsg */) {
+ guard->reset(new EnvWrapper(Env::Default()));
+ return guard->get();
+ });
+
+ ASSERT_OK(registry->NewSharedObject<Env>("guarded", &shared));
+ ASSERT_NE(shared, nullptr);
+ shared.reset();
+ ASSERT_NOK(registry->NewSharedObject<Env>("unguarded", &shared));
+ ASSERT_EQ(shared, nullptr);
+}
+
+TEST_F(EnvRegistryTest, CheckStatic) {
+ Env* env = nullptr;
+ std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
+ std::shared_ptr<ObjectLibrary> library = std::make_shared<ObjectLibrary>();
+ registry->AddLibrary(library);
+ library->Register<Env>(
+ "unguarded",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*guard */,
+ std::string* /* errmsg */) { return Env::Default(); });
+
+ library->Register<Env>(
+ "guarded", [](const std::string& /*uri*/, std::unique_ptr<Env>* guard,
+ std::string* /* errmsg */) {
+ guard->reset(new EnvWrapper(Env::Default()));
+ return guard->get();
+ });
+
+ ASSERT_NOK(registry->NewStaticObject<Env>("guarded", &env));
+ ASSERT_EQ(env, nullptr);
+ env = nullptr;
+ ASSERT_OK(registry->NewStaticObject<Env>("unguarded", &env));
+ ASSERT_NE(env, nullptr);
+}
+
+TEST_F(EnvRegistryTest, CheckUnique) {
+ std::unique_ptr<Env> unique;
+ std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
+ std::shared_ptr<ObjectLibrary> library = std::make_shared<ObjectLibrary>();
+ registry->AddLibrary(library);
+ library->Register<Env>(
+ "unguarded",
+ [](const std::string& /*uri*/, std::unique_ptr<Env>* /*guard */,
+ std::string* /* errmsg */) { return Env::Default(); });
+
+ library->Register<Env>(
+ "guarded", [](const std::string& /*uri*/, std::unique_ptr<Env>* guard,
+ std::string* /* errmsg */) {
+ guard->reset(new EnvWrapper(Env::Default()));
+ return guard->get();
+ });
+
+ ASSERT_OK(registry->NewUniqueObject<Env>("guarded", &unique));
+ ASSERT_NE(unique, nullptr);
+ unique.reset();
+ ASSERT_NOK(registry->NewUniqueObject<Env>("unguarded", &unique));
+ ASSERT_EQ(unique, nullptr);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else // ROCKSDB_LITE
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as EnvRegistry is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration.cc b/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration.cc
new file mode 100644
index 00000000..f2382297
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/option_change_migration.h"
+
+#ifndef ROCKSDB_LITE
+#include "rocksdb/db.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+// Return a version of Options `opts` that allow us to open/write into a DB
+// without triggering an automatic compaction or stalling. This is guaranteed
+// by disabling automatic compactions and using huge values for stalling
+// triggers.
+Options GetNoCompactionOptions(const Options& opts) {
+ Options ret_opts = opts;
+ ret_opts.disable_auto_compactions = true;
+ ret_opts.level0_slowdown_writes_trigger = 999999;
+ ret_opts.level0_stop_writes_trigger = 999999;
+ ret_opts.soft_pending_compaction_bytes_limit = 0;
+ ret_opts.hard_pending_compaction_bytes_limit = 0;
+ return ret_opts;
+}
+
+Status OpenDb(const Options& options, const std::string& dbname,
+ std::unique_ptr<DB>* db) {
+ db->reset();
+ DB* tmpdb;
+ Status s = DB::Open(options, dbname, &tmpdb);
+ if (s.ok()) {
+ db->reset(tmpdb);
+ }
+ return s;
+}
+
+Status CompactToLevel(const Options& options, const std::string& dbname,
+ int dest_level, bool need_reopen) {
+ std::unique_ptr<DB> db;
+ Options no_compact_opts = GetNoCompactionOptions(options);
+ if (dest_level == 0) {
+ // L0 has strict sequenceID requirements to files to it. It's safer
+ // to only put one compacted file to there.
+ // This is only used for converting to universal compaction with
+ // only one level. In this case, compacting to one file is also
+ // optimal.
+ no_compact_opts.target_file_size_base = 999999999999999;
+ no_compact_opts.max_compaction_bytes = 999999999999999;
+ }
+ Status s = OpenDb(no_compact_opts, dbname, &db);
+ if (!s.ok()) {
+ return s;
+ }
+ CompactRangeOptions cro;
+ cro.change_level = true;
+ cro.target_level = dest_level;
+ if (dest_level == 0) {
+ // cannot use kForceOptimized because the compaction is expected to
+ // generate one output file
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
+ }
+ db->CompactRange(cro, nullptr, nullptr);
+
+ if (need_reopen) {
+ // Need to restart DB to rewrite the manifest file.
+ // In order to open a DB with specific num_levels, the manifest file should
+ // contain no record that mentiones any level beyond num_levels. Issuing a
+ // full compaction will move all the data to a level not exceeding
+ // num_levels, but the manifest may still contain previous record mentioning
+ // a higher level. Reopening the DB will force the manifest to be rewritten
+ // so that those records will be cleared.
+ db.reset();
+ s = OpenDb(no_compact_opts, dbname, &db);
+ }
+ return s;
+}
+
+Status MigrateToUniversal(std::string dbname, const Options& old_opts,
+ const Options& new_opts) {
+ if (old_opts.num_levels <= new_opts.num_levels ||
+ old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
+ return Status::OK();
+ } else {
+ bool need_compact = false;
+ {
+ std::unique_ptr<DB> db;
+ Options opts = GetNoCompactionOptions(old_opts);
+ Status s = OpenDb(opts, dbname, &db);
+ if (!s.ok()) {
+ return s;
+ }
+ ColumnFamilyMetaData metadata;
+ db->GetColumnFamilyMetaData(&metadata);
+ if (!metadata.levels.empty() &&
+ metadata.levels.back().level >= new_opts.num_levels) {
+ need_compact = true;
+ }
+ }
+ if (need_compact) {
+ return CompactToLevel(old_opts, dbname, new_opts.num_levels - 1, true);
+ }
+ return Status::OK();
+ }
+}
+
+Status MigrateToLevelBase(std::string dbname, const Options& old_opts,
+ const Options& new_opts) {
+ if (!new_opts.level_compaction_dynamic_level_bytes) {
+ if (old_opts.num_levels == 1) {
+ return Status::OK();
+ }
+ // Compact everything to level 1 to guarantee it can be safely opened.
+ Options opts = old_opts;
+ opts.target_file_size_base = new_opts.target_file_size_base;
+ // Although sometimes we can open the DB with the new option without error,
+ // We still want to compact the files to avoid the LSM tree to stuck
+ // in bad shape. For example, if the user changed the level size
+ // multiplier from 4 to 8, with the same data, we will have fewer
+ // levels. Unless we issue a full comaction, the LSM tree may stuck
+ // with more levels than needed and it won't recover automatically.
+ return CompactToLevel(opts, dbname, 1, true);
+ } else {
+ // Compact everything to the last level to guarantee it can be safely
+ // opened.
+ if (old_opts.num_levels == 1) {
+ return Status::OK();
+ } else if (new_opts.num_levels > old_opts.num_levels) {
+ // Dynamic level mode requires data to be put in the last level first.
+ return CompactToLevel(new_opts, dbname, new_opts.num_levels - 1, false);
+ } else {
+ Options opts = old_opts;
+ opts.target_file_size_base = new_opts.target_file_size_base;
+ return CompactToLevel(opts, dbname, new_opts.num_levels - 1, true);
+ }
+ }
+}
+} // namespace
+
+Status OptionChangeMigration(std::string dbname, const Options& old_opts,
+ const Options& new_opts) {
+ if (old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
+ // LSM generated by FIFO compation can be opened by any compaction.
+ return Status::OK();
+ } else if (new_opts.compaction_style ==
+ CompactionStyle::kCompactionStyleUniversal) {
+ return MigrateToUniversal(dbname, old_opts, new_opts);
+ } else if (new_opts.compaction_style ==
+ CompactionStyle::kCompactionStyleLevel) {
+ return MigrateToLevelBase(dbname, old_opts, new_opts);
+ } else if (new_opts.compaction_style ==
+ CompactionStyle::kCompactionStyleFIFO) {
+ return CompactToLevel(old_opts, dbname, 0, true);
+ } else {
+ return Status::NotSupported(
+ "Do not how to migrate to this compaction style");
+ }
+}
+} // namespace ROCKSDB_NAMESPACE
+#else
+namespace ROCKSDB_NAMESPACE {
+Status OptionChangeMigration(std::string /*dbname*/,
+ const Options& /*old_opts*/,
+ const Options& /*new_opts*/) {
+ return Status::NotSupported();
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration_test.cc b/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration_test.cc
new file mode 100644
index 00000000..5bc883ff
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/option_change_migration/option_change_migration_test.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/utilities/option_change_migration.h"
+#include <set>
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+namespace ROCKSDB_NAMESPACE {
+
+class DBOptionChangeMigrationTests
+ : public DBTestBase,
+ public testing::WithParamInterface<
+ std::tuple<int, int, bool, int, int, bool>> {
+ public:
+ DBOptionChangeMigrationTests()
+ : DBTestBase("/db_option_change_migration_test") {
+ level1_ = std::get<0>(GetParam());
+ compaction_style1_ = std::get<1>(GetParam());
+ is_dynamic1_ = std::get<2>(GetParam());
+
+ level2_ = std::get<3>(GetParam());
+ compaction_style2_ = std::get<4>(GetParam());
+ is_dynamic2_ = std::get<5>(GetParam());
+ }
+
+ // Required if inheriting from testing::WithParamInterface<>
+ static void SetUpTestCase() {}
+ static void TearDownTestCase() {}
+
+ int level1_;
+ int compaction_style1_;
+ bool is_dynamic1_;
+
+ int level2_;
+ int compaction_style2_;
+ bool is_dynamic2_;
+};
+
+#ifndef ROCKSDB_LITE
+TEST_P(DBOptionChangeMigrationTests, Migrate1) {
+ Options old_options = CurrentOptions();
+ old_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style1_);
+ if (old_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ old_options.level_compaction_dynamic_level_bytes = is_dynamic1_;
+ }
+
+ old_options.level0_file_num_compaction_trigger = 3;
+ old_options.write_buffer_size = 64 * 1024;
+ old_options.target_file_size_base = 128 * 1024;
+ // Make level target of L1, L2 to be 200KB and 600KB
+ old_options.num_levels = level1_;
+ old_options.max_bytes_for_level_multiplier = 3;
+ old_options.max_bytes_for_level_base = 200 * 1024;
+
+ Reopen(old_options);
+
+ Random rnd(301);
+ int key_idx = 0;
+
+ // Generate at least 2MB of data
+ for (int num = 0; num < 20; num++) {
+ GenerateNewFile(&rnd, &key_idx);
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ // Will make sure exactly those keys are in the DB after migration.
+ std::set<std::string> keys;
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (; it->Valid(); it->Next()) {
+ keys.insert(it->key().ToString());
+ }
+ }
+ Close();
+
+ Options new_options = old_options;
+ new_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style2_);
+ if (new_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ new_options.level_compaction_dynamic_level_bytes = is_dynamic2_;
+ }
+ new_options.target_file_size_base = 256 * 1024;
+ new_options.num_levels = level2_;
+ new_options.max_bytes_for_level_base = 150 * 1024;
+ new_options.max_bytes_for_level_multiplier = 4;
+ ASSERT_OK(OptionChangeMigration(dbname_, old_options, new_options));
+ Reopen(new_options);
+
+ // Wait for compaction to finish and make sure it can reopen
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ Reopen(new_options);
+
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (std::string key : keys) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ(key, it->key().ToString());
+ it->Next();
+ }
+ ASSERT_TRUE(!it->Valid());
+ }
+}
+
+TEST_P(DBOptionChangeMigrationTests, Migrate2) {
+ Options old_options = CurrentOptions();
+ old_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style2_);
+ if (old_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ old_options.level_compaction_dynamic_level_bytes = is_dynamic2_;
+ }
+ old_options.level0_file_num_compaction_trigger = 3;
+ old_options.write_buffer_size = 64 * 1024;
+ old_options.target_file_size_base = 128 * 1024;
+ // Make level target of L1, L2 to be 200KB and 600KB
+ old_options.num_levels = level2_;
+ old_options.max_bytes_for_level_multiplier = 3;
+ old_options.max_bytes_for_level_base = 200 * 1024;
+
+ Reopen(old_options);
+
+ Random rnd(301);
+ int key_idx = 0;
+
+ // Generate at least 2MB of data
+ for (int num = 0; num < 20; num++) {
+ GenerateNewFile(&rnd, &key_idx);
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ // Will make sure exactly those keys are in the DB after migration.
+ std::set<std::string> keys;
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (; it->Valid(); it->Next()) {
+ keys.insert(it->key().ToString());
+ }
+ }
+
+ Close();
+
+ Options new_options = old_options;
+ new_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style1_);
+ if (new_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ new_options.level_compaction_dynamic_level_bytes = is_dynamic1_;
+ }
+ new_options.target_file_size_base = 256 * 1024;
+ new_options.num_levels = level1_;
+ new_options.max_bytes_for_level_base = 150 * 1024;
+ new_options.max_bytes_for_level_multiplier = 4;
+ ASSERT_OK(OptionChangeMigration(dbname_, old_options, new_options));
+ Reopen(new_options);
+ // Wait for compaction to finish and make sure it can reopen
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ Reopen(new_options);
+
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (std::string key : keys) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ(key, it->key().ToString());
+ it->Next();
+ }
+ ASSERT_TRUE(!it->Valid());
+ }
+}
+
+TEST_P(DBOptionChangeMigrationTests, Migrate3) {
+ Options old_options = CurrentOptions();
+ old_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style1_);
+ if (old_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ old_options.level_compaction_dynamic_level_bytes = is_dynamic1_;
+ }
+
+ old_options.level0_file_num_compaction_trigger = 3;
+ old_options.write_buffer_size = 64 * 1024;
+ old_options.target_file_size_base = 128 * 1024;
+ // Make level target of L1, L2 to be 200KB and 600KB
+ old_options.num_levels = level1_;
+ old_options.max_bytes_for_level_multiplier = 3;
+ old_options.max_bytes_for_level_base = 200 * 1024;
+
+ Reopen(old_options);
+ Random rnd(301);
+ for (int num = 0; num < 20; num++) {
+ for (int i = 0; i < 50; i++) {
+ ASSERT_OK(Put(Key(num * 100 + i), RandomString(&rnd, 900)));
+ }
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ if (num == 9) {
+ // Issue a full compaction to generate some zero-out files
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+ }
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ // Will make sure exactly those keys are in the DB after migration.
+ std::set<std::string> keys;
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (; it->Valid(); it->Next()) {
+ keys.insert(it->key().ToString());
+ }
+ }
+ Close();
+
+ Options new_options = old_options;
+ new_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style2_);
+ if (new_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ new_options.level_compaction_dynamic_level_bytes = is_dynamic2_;
+ }
+ new_options.target_file_size_base = 256 * 1024;
+ new_options.num_levels = level2_;
+ new_options.max_bytes_for_level_base = 150 * 1024;
+ new_options.max_bytes_for_level_multiplier = 4;
+ ASSERT_OK(OptionChangeMigration(dbname_, old_options, new_options));
+ Reopen(new_options);
+
+ // Wait for compaction to finish and make sure it can reopen
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ Reopen(new_options);
+
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (std::string key : keys) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ(key, it->key().ToString());
+ it->Next();
+ }
+ ASSERT_TRUE(!it->Valid());
+ }
+}
+
+TEST_P(DBOptionChangeMigrationTests, Migrate4) {
+ Options old_options = CurrentOptions();
+ old_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style2_);
+ if (old_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ old_options.level_compaction_dynamic_level_bytes = is_dynamic2_;
+ }
+ old_options.level0_file_num_compaction_trigger = 3;
+ old_options.write_buffer_size = 64 * 1024;
+ old_options.target_file_size_base = 128 * 1024;
+ // Make level target of L1, L2 to be 200KB and 600KB
+ old_options.num_levels = level2_;
+ old_options.max_bytes_for_level_multiplier = 3;
+ old_options.max_bytes_for_level_base = 200 * 1024;
+
+ Reopen(old_options);
+ Random rnd(301);
+ for (int num = 0; num < 20; num++) {
+ for (int i = 0; i < 50; i++) {
+ ASSERT_OK(Put(Key(num * 100 + i), RandomString(&rnd, 900)));
+ }
+ Flush();
+ dbfull()->TEST_WaitForCompact();
+ if (num == 9) {
+ // Issue a full compaction to generate some zero-out files
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+ }
+ }
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+
+ // Will make sure exactly those keys are in the DB after migration.
+ std::set<std::string> keys;
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (; it->Valid(); it->Next()) {
+ keys.insert(it->key().ToString());
+ }
+ }
+
+ Close();
+
+ Options new_options = old_options;
+ new_options.compaction_style =
+ static_cast<CompactionStyle>(compaction_style1_);
+ if (new_options.compaction_style == CompactionStyle::kCompactionStyleLevel) {
+ new_options.level_compaction_dynamic_level_bytes = is_dynamic1_;
+ }
+ new_options.target_file_size_base = 256 * 1024;
+ new_options.num_levels = level1_;
+ new_options.max_bytes_for_level_base = 150 * 1024;
+ new_options.max_bytes_for_level_multiplier = 4;
+ ASSERT_OK(OptionChangeMigration(dbname_, old_options, new_options));
+ Reopen(new_options);
+ // Wait for compaction to finish and make sure it can reopen
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ Reopen(new_options);
+
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (std::string key : keys) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ(key, it->key().ToString());
+ it->Next();
+ }
+ ASSERT_TRUE(!it->Valid());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ DBOptionChangeMigrationTests, DBOptionChangeMigrationTests,
+ ::testing::Values(std::make_tuple(3, 0, false, 4, 0, false),
+ std::make_tuple(3, 0, true, 4, 0, true),
+ std::make_tuple(3, 0, true, 4, 0, false),
+ std::make_tuple(3, 0, false, 4, 0, true),
+ std::make_tuple(3, 1, false, 4, 1, false),
+ std::make_tuple(1, 1, false, 4, 1, false),
+ std::make_tuple(3, 0, false, 4, 1, false),
+ std::make_tuple(3, 0, false, 1, 1, false),
+ std::make_tuple(3, 0, true, 4, 1, false),
+ std::make_tuple(3, 0, true, 1, 1, false),
+ std::make_tuple(1, 1, false, 4, 0, false),
+ std::make_tuple(4, 0, false, 1, 2, false),
+ std::make_tuple(3, 0, true, 2, 2, false),
+ std::make_tuple(3, 1, false, 3, 2, false),
+ std::make_tuple(1, 1, false, 4, 2, false)));
+
+class DBOptionChangeMigrationTest : public DBTestBase {
+ public:
+ DBOptionChangeMigrationTest()
+ : DBTestBase("/db_option_change_migration_test2") {}
+};
+
+TEST_F(DBOptionChangeMigrationTest, CompactedSrcToUniversal) {
+ Options old_options = CurrentOptions();
+ old_options.compaction_style = CompactionStyle::kCompactionStyleLevel;
+ old_options.max_compaction_bytes = 200 * 1024;
+ old_options.level_compaction_dynamic_level_bytes = false;
+ old_options.level0_file_num_compaction_trigger = 3;
+ old_options.write_buffer_size = 64 * 1024;
+ old_options.target_file_size_base = 128 * 1024;
+ // Make level target of L1, L2 to be 200KB and 600KB
+ old_options.num_levels = 4;
+ old_options.max_bytes_for_level_multiplier = 3;
+ old_options.max_bytes_for_level_base = 200 * 1024;
+
+ Reopen(old_options);
+ Random rnd(301);
+ for (int num = 0; num < 20; num++) {
+ for (int i = 0; i < 50; i++) {
+ ASSERT_OK(Put(Key(num * 100 + i), RandomString(&rnd, 900)));
+ }
+ }
+ Flush();
+ CompactRangeOptions cro;
+ cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
+ dbfull()->CompactRange(cro, nullptr, nullptr);
+
+ // Will make sure exactly those keys are in the DB after migration.
+ std::set<std::string> keys;
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (; it->Valid(); it->Next()) {
+ keys.insert(it->key().ToString());
+ }
+ }
+
+ Close();
+
+ Options new_options = old_options;
+ new_options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
+ new_options.target_file_size_base = 256 * 1024;
+ new_options.num_levels = 1;
+ new_options.max_bytes_for_level_base = 150 * 1024;
+ new_options.max_bytes_for_level_multiplier = 4;
+ ASSERT_OK(OptionChangeMigration(dbname_, old_options, new_options));
+ Reopen(new_options);
+ // Wait for compaction to finish and make sure it can reopen
+ dbfull()->TEST_WaitForFlushMemTable();
+ dbfull()->TEST_WaitForCompact();
+ Reopen(new_options);
+
+ {
+ std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
+ it->SeekToFirst();
+ for (std::string key : keys) {
+ ASSERT_TRUE(it->Valid());
+ ASSERT_EQ(key, it->key().ToString());
+ it->Next();
+ }
+ ASSERT_TRUE(!it->Valid());
+ }
+}
+
+#endif // ROCKSDB_LITE
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/options/options_util.cc b/storage/rocksdb/rocksdb/utilities/options/options_util.cc
new file mode 100644
index 00000000..0719798e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/options/options_util.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/options_util.h"
+
+#include "env/composite_env_wrapper.h"
+#include "file/filename.h"
+#include "options/options_parser.h"
+#include "rocksdb/options.h"
+
+namespace ROCKSDB_NAMESPACE {
+Status LoadOptionsFromFile(const std::string& file_name, Env* env,
+ DBOptions* db_options,
+ std::vector<ColumnFamilyDescriptor>* cf_descs,
+ bool ignore_unknown_options,
+ std::shared_ptr<Cache>* cache) {
+ RocksDBOptionsParser parser;
+ LegacyFileSystemWrapper fs(env);
+ Status s = parser.Parse(file_name, &fs, ignore_unknown_options,
+ 0 /* file_readahead_size */);
+ if (!s.ok()) {
+ return s;
+ }
+ *db_options = *parser.db_opt();
+ const std::vector<std::string>& cf_names = *parser.cf_names();
+ const std::vector<ColumnFamilyOptions>& cf_opts = *parser.cf_opts();
+ cf_descs->clear();
+ for (size_t i = 0; i < cf_opts.size(); ++i) {
+ cf_descs->push_back({cf_names[i], cf_opts[i]});
+ if (cache != nullptr) {
+ TableFactory* tf = cf_opts[i].table_factory.get();
+ if (tf != nullptr && tf->GetOptions() != nullptr &&
+ tf->Name() == BlockBasedTableFactory().Name()) {
+ auto* loaded_bbt_opt =
+ reinterpret_cast<BlockBasedTableOptions*>(tf->GetOptions());
+ loaded_bbt_opt->block_cache = *cache;
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Status GetLatestOptionsFileName(const std::string& dbpath,
+ Env* env, std::string* options_file_name) {
+ Status s;
+ std::string latest_file_name;
+ uint64_t latest_time_stamp = 0;
+ std::vector<std::string> file_names;
+ s = env->GetChildren(dbpath, &file_names);
+ if (!s.ok()) {
+ return s;
+ }
+ for (auto& file_name : file_names) {
+ uint64_t time_stamp;
+ FileType type;
+ if (ParseFileName(file_name, &time_stamp, &type) && type == kOptionsFile) {
+ if (time_stamp > latest_time_stamp) {
+ latest_time_stamp = time_stamp;
+ latest_file_name = file_name;
+ }
+ }
+ }
+ if (latest_file_name.size() == 0) {
+ return Status::NotFound("No options files found in the DB directory.");
+ }
+ *options_file_name = latest_file_name;
+ return Status::OK();
+}
+
+Status LoadLatestOptions(const std::string& dbpath, Env* env,
+ DBOptions* db_options,
+ std::vector<ColumnFamilyDescriptor>* cf_descs,
+ bool ignore_unknown_options,
+ std::shared_ptr<Cache>* cache) {
+ std::string options_file_name;
+ Status s = GetLatestOptionsFileName(dbpath, env, &options_file_name);
+ if (!s.ok()) {
+ return s;
+ }
+ return LoadOptionsFromFile(dbpath + "/" + options_file_name, env, db_options,
+ cf_descs, ignore_unknown_options, cache);
+}
+
+Status CheckOptionsCompatibility(
+ const std::string& dbpath, Env* env, const DBOptions& db_options,
+ const std::vector<ColumnFamilyDescriptor>& cf_descs,
+ bool ignore_unknown_options) {
+ std::string options_file_name;
+ Status s = GetLatestOptionsFileName(dbpath, env, &options_file_name);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::vector<std::string> cf_names;
+ std::vector<ColumnFamilyOptions> cf_opts;
+ for (const auto& cf_desc : cf_descs) {
+ cf_names.push_back(cf_desc.name);
+ cf_opts.push_back(cf_desc.options);
+ }
+
+ const OptionsSanityCheckLevel kDefaultLevel = kSanityLevelLooselyCompatible;
+ LegacyFileSystemWrapper fs(env);
+
+ return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
+ db_options, cf_names, cf_opts, dbpath + "/" + options_file_name, &fs,
+ kDefaultLevel, ignore_unknown_options);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/options/options_util_test.cc b/storage/rocksdb/rocksdb/utilities/options/options_util_test.cc
new file mode 100644
index 00000000..30ad76a9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/options/options_util_test.cc
@@ -0,0 +1,363 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <cinttypes>
+
+#include <cctype>
+#include <unordered_map>
+
+#include "options/options_parser.h"
+#include "rocksdb/db.h"
+#include "rocksdb/table.h"
+#include "rocksdb/utilities/options_util.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "util/random.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 OptionsUtilTest : public testing::Test {
+ public:
+ OptionsUtilTest() : rnd_(0xFB) {
+ env_.reset(new test::StringEnv(Env::Default()));
+ fs_.reset(new LegacyFileSystemWrapper(env_.get()));
+ dbname_ = test::PerThreadDBPath("options_util_test");
+ }
+
+ protected:
+ std::unique_ptr<test::StringEnv> env_;
+ std::unique_ptr<LegacyFileSystemWrapper> fs_;
+ std::string dbname_;
+ Random rnd_;
+};
+
+bool IsBlockBasedTableFactory(TableFactory* tf) {
+ return tf->Name() == BlockBasedTableFactory().Name();
+}
+
+TEST_F(OptionsUtilTest, SaveAndLoad) {
+ const size_t kCFCount = 5;
+
+ DBOptions db_opt;
+ std::vector<std::string> cf_names;
+ std::vector<ColumnFamilyOptions> cf_opts;
+ test::RandomInitDBOptions(&db_opt, &rnd_);
+ for (size_t i = 0; i < kCFCount; ++i) {
+ cf_names.push_back(i == 0 ? kDefaultColumnFamilyName
+ : test::RandomName(&rnd_, 10));
+ cf_opts.emplace_back();
+ test::RandomInitCFOptions(&cf_opts.back(), db_opt, &rnd_);
+ }
+
+ const std::string kFileName = "OPTIONS-123456";
+ PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName, fs_.get());
+
+ DBOptions loaded_db_opt;
+ std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
+ ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
+ &loaded_cf_descs));
+
+ ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt));
+ test::RandomInitDBOptions(&db_opt, &rnd_);
+ ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt));
+
+ for (size_t i = 0; i < kCFCount; ++i) {
+ ASSERT_EQ(cf_names[i], loaded_cf_descs[i].name);
+ ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
+ cf_opts[i], loaded_cf_descs[i].options));
+ if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) {
+ ASSERT_OK(RocksDBOptionsParser::VerifyTableFactory(
+ cf_opts[i].table_factory.get(),
+ loaded_cf_descs[i].options.table_factory.get()));
+ }
+ test::RandomInitCFOptions(&cf_opts[i], db_opt, &rnd_);
+ ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions(
+ cf_opts[i], loaded_cf_descs[i].options));
+ }
+
+ for (size_t i = 0; i < kCFCount; ++i) {
+ if (cf_opts[i].compaction_filter) {
+ delete cf_opts[i].compaction_filter;
+ }
+ }
+}
+
+TEST_F(OptionsUtilTest, SaveAndLoadWithCacheCheck) {
+ // creating db
+ DBOptions db_opt;
+ db_opt.create_if_missing = true;
+ // initialize BlockBasedTableOptions
+ std::shared_ptr<Cache> cache = NewLRUCache(1 * 1024);
+ BlockBasedTableOptions bbt_opts;
+ bbt_opts.block_size = 32 * 1024;
+ // saving cf options
+ std::vector<ColumnFamilyOptions> cf_opts;
+ ColumnFamilyOptions default_column_family_opt = ColumnFamilyOptions();
+ default_column_family_opt.table_factory.reset(
+ NewBlockBasedTableFactory(bbt_opts));
+ cf_opts.push_back(default_column_family_opt);
+
+ ColumnFamilyOptions cf_opt_sample = ColumnFamilyOptions();
+ cf_opt_sample.table_factory.reset(NewBlockBasedTableFactory(bbt_opts));
+ cf_opts.push_back(cf_opt_sample);
+
+ ColumnFamilyOptions cf_opt_plain_table_opt = ColumnFamilyOptions();
+ cf_opt_plain_table_opt.table_factory.reset(NewPlainTableFactory());
+ cf_opts.push_back(cf_opt_plain_table_opt);
+
+ std::vector<std::string> cf_names;
+ cf_names.push_back(kDefaultColumnFamilyName);
+ cf_names.push_back("cf_sample");
+ cf_names.push_back("cf_plain_table_sample");
+ // Saving DB in file
+ const std::string kFileName = "OPTIONS-LOAD_CACHE_123456";
+ PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName, fs_.get());
+ DBOptions loaded_db_opt;
+ std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
+ ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
+ &loaded_cf_descs, false, &cache));
+ for (size_t i = 0; i < loaded_cf_descs.size(); i++) {
+ if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) {
+ auto* loaded_bbt_opt = reinterpret_cast<BlockBasedTableOptions*>(
+ loaded_cf_descs[i].options.table_factory->GetOptions());
+ // Expect the same cache will be loaded
+ if (loaded_bbt_opt != nullptr) {
+ ASSERT_EQ(loaded_bbt_opt->block_cache.get(), cache.get());
+ }
+ }
+ }
+}
+
+namespace {
+class DummyTableFactory : public TableFactory {
+ public:
+ DummyTableFactory() {}
+ ~DummyTableFactory() override {}
+
+ const char* Name() const override { return "DummyTableFactory"; }
+
+ Status NewTableReader(
+ const TableReaderOptions& /*table_reader_options*/,
+ std::unique_ptr<RandomAccessFileReader>&& /*file*/,
+ uint64_t /*file_size*/, std::unique_ptr<TableReader>* /*table_reader*/,
+ bool /*prefetch_index_and_filter_in_cache*/) const override {
+ return Status::NotSupported();
+ }
+
+ TableBuilder* NewTableBuilder(
+ const TableBuilderOptions& /*table_builder_options*/,
+ uint32_t /*column_family_id*/,
+ WritableFileWriter* /*file*/) const override {
+ return nullptr;
+ }
+
+ Status SanitizeOptions(
+ const DBOptions& /*db_opts*/,
+ const ColumnFamilyOptions& /*cf_opts*/) const override {
+ return Status::NotSupported();
+ }
+
+ std::string GetPrintableTableOptions() const override { return ""; }
+
+ Status GetOptionString(std::string* /*opt_string*/,
+ const std::string& /*delimiter*/) const override {
+ return Status::OK();
+ }
+};
+
+class DummyMergeOperator : public MergeOperator {
+ public:
+ DummyMergeOperator() {}
+ ~DummyMergeOperator() override {}
+
+ bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
+ MergeOperationOutput* /*merge_out*/) const override {
+ return false;
+ }
+
+ bool PartialMergeMulti(const Slice& /*key*/,
+ const std::deque<Slice>& /*operand_list*/,
+ std::string* /*new_value*/,
+ Logger* /*logger*/) const override {
+ return false;
+ }
+
+ const char* Name() const override { return "DummyMergeOperator"; }
+};
+
+class DummySliceTransform : public SliceTransform {
+ public:
+ DummySliceTransform() {}
+ ~DummySliceTransform() override {}
+
+ // Return the name of this transformation.
+ const char* Name() const override { return "DummySliceTransform"; }
+
+ // transform a src in domain to a dst in the range
+ Slice Transform(const Slice& src) const override { return src; }
+
+ // determine whether this is a valid src upon the function applies
+ bool InDomain(const Slice& /*src*/) const override { return false; }
+
+ // determine whether dst=Transform(src) for some src
+ bool InRange(const Slice& /*dst*/) const override { return false; }
+};
+
+} // namespace
+
+TEST_F(OptionsUtilTest, SanityCheck) {
+ DBOptions db_opt;
+ std::vector<ColumnFamilyDescriptor> cf_descs;
+ const size_t kCFCount = 5;
+ for (size_t i = 0; i < kCFCount; ++i) {
+ cf_descs.emplace_back();
+ cf_descs.back().name =
+ (i == 0) ? kDefaultColumnFamilyName : test::RandomName(&rnd_, 10);
+
+ cf_descs.back().options.table_factory.reset(NewBlockBasedTableFactory());
+ // Assign non-null values to prefix_extractors except the first cf.
+ cf_descs.back().options.prefix_extractor.reset(
+ i != 0 ? test::RandomSliceTransform(&rnd_) : nullptr);
+ cf_descs.back().options.merge_operator.reset(
+ test::RandomMergeOperator(&rnd_));
+ }
+
+ db_opt.create_missing_column_families = true;
+ db_opt.create_if_missing = true;
+
+ DestroyDB(dbname_, Options(db_opt, cf_descs[0].options));
+ DB* db;
+ std::vector<ColumnFamilyHandle*> handles;
+ // open and persist the options
+ ASSERT_OK(DB::Open(db_opt, dbname_, cf_descs, &handles, &db));
+
+ // close the db
+ for (auto* handle : handles) {
+ delete handle;
+ }
+ delete db;
+
+ // perform sanity check
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ ASSERT_GE(kCFCount, 5);
+ // merge operator
+ {
+ std::shared_ptr<MergeOperator> merge_op =
+ cf_descs[0].options.merge_operator;
+
+ ASSERT_NE(merge_op.get(), nullptr);
+ cf_descs[0].options.merge_operator.reset();
+ ASSERT_NOK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[0].options.merge_operator.reset(new DummyMergeOperator());
+ ASSERT_NOK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[0].options.merge_operator = merge_op;
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+ }
+
+ // prefix extractor
+ {
+ std::shared_ptr<const SliceTransform> prefix_extractor =
+ cf_descs[1].options.prefix_extractor;
+
+ // It's okay to set prefix_extractor to nullptr.
+ ASSERT_NE(prefix_extractor, nullptr);
+ cf_descs[1].options.prefix_extractor.reset();
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[1].options.prefix_extractor.reset(new DummySliceTransform());
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[1].options.prefix_extractor = prefix_extractor;
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+ }
+
+ // prefix extractor nullptr case
+ {
+ std::shared_ptr<const SliceTransform> prefix_extractor =
+ cf_descs[0].options.prefix_extractor;
+
+ // It's okay to set prefix_extractor to nullptr.
+ ASSERT_EQ(prefix_extractor, nullptr);
+ cf_descs[0].options.prefix_extractor.reset();
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ // It's okay to change prefix_extractor from nullptr to non-nullptr
+ cf_descs[0].options.prefix_extractor.reset(new DummySliceTransform());
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[0].options.prefix_extractor = prefix_extractor;
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+ }
+
+ // comparator
+ {
+ test::SimpleSuffixReverseComparator comparator;
+
+ auto* prev_comparator = cf_descs[2].options.comparator;
+ cf_descs[2].options.comparator = &comparator;
+ ASSERT_NOK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[2].options.comparator = prev_comparator;
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+ }
+
+ // table factory
+ {
+ std::shared_ptr<TableFactory> table_factory =
+ cf_descs[3].options.table_factory;
+
+ ASSERT_NE(table_factory, nullptr);
+ cf_descs[3].options.table_factory.reset(new DummyTableFactory());
+ ASSERT_NOK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+
+ cf_descs[3].options.table_factory = table_factory;
+ ASSERT_OK(
+ CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+#ifdef GFLAGS
+ ParseCommandLineFlags(&argc, &argv, true);
+#endif // GFLAGS
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <cstdio>
+
+int main(int /*argc*/, char** /*argv*/) {
+ printf("Skipped in RocksDBLite as utilities are not supported.\n");
+ return 0;
+}
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.cc
new file mode 100644
index 00000000..65873757
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/persistent_cache/block_cache_tier.h"
+
+#include <regex>
+#include <utility>
+#include <vector>
+
+#include "logging/logging.h"
+#include "port/port.h"
+#include "test_util/sync_point.h"
+#include "util/stop_watch.h"
+#include "utilities/persistent_cache/block_cache_tier_file.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// BlockCacheImpl
+//
+Status BlockCacheTier::Open() {
+ Status status;
+
+ WriteLock _(&lock_);
+
+ assert(!size_);
+
+ // Check the validity of the options
+ status = opt_.ValidateSettings();
+ assert(status.ok());
+ if (!status.ok()) {
+ Error(opt_.log, "Invalid block cache options");
+ return status;
+ }
+
+ // Create base directory or cleanup existing directory
+ status = opt_.env->CreateDirIfMissing(opt_.path);
+ if (!status.ok()) {
+ Error(opt_.log, "Error creating directory %s. %s", opt_.path.c_str(),
+ status.ToString().c_str());
+ return status;
+ }
+
+ // Create base/<cache dir> directory
+ status = opt_.env->CreateDir(GetCachePath());
+ if (!status.ok()) {
+ // directory already exists, clean it up
+ status = CleanupCacheFolder(GetCachePath());
+ assert(status.ok());
+ if (!status.ok()) {
+ Error(opt_.log, "Error creating directory %s. %s", opt_.path.c_str(),
+ status.ToString().c_str());
+ return status;
+ }
+ }
+
+ // create a new file
+ assert(!cache_file_);
+ status = NewCacheFile();
+ if (!status.ok()) {
+ Error(opt_.log, "Error creating new file %s. %s", opt_.path.c_str(),
+ status.ToString().c_str());
+ return status;
+ }
+
+ assert(cache_file_);
+
+ if (opt_.pipeline_writes) {
+ assert(!insert_th_.joinable());
+ insert_th_ = port::Thread(&BlockCacheTier::InsertMain, this);
+ }
+
+ return Status::OK();
+}
+
+bool IsCacheFile(const std::string& file) {
+ // check if the file has .rc suffix
+ // Unfortunately regex support across compilers is not even, so we use simple
+ // string parsing
+ size_t pos = file.find(".");
+ if (pos == std::string::npos) {
+ return false;
+ }
+
+ std::string suffix = file.substr(pos);
+ return suffix == ".rc";
+}
+
+Status BlockCacheTier::CleanupCacheFolder(const std::string& folder) {
+ std::vector<std::string> files;
+ Status status = opt_.env->GetChildren(folder, &files);
+ if (!status.ok()) {
+ Error(opt_.log, "Error getting files for %s. %s", folder.c_str(),
+ status.ToString().c_str());
+ return status;
+ }
+
+ // cleanup files with the patter :digi:.rc
+ for (auto file : files) {
+ if (IsCacheFile(file)) {
+ // cache file
+ Info(opt_.log, "Removing file %s.", file.c_str());
+ status = opt_.env->DeleteFile(folder + "/" + file);
+ if (!status.ok()) {
+ Error(opt_.log, "Error deleting file %s. %s", file.c_str(),
+ status.ToString().c_str());
+ return status;
+ }
+ } else {
+ ROCKS_LOG_DEBUG(opt_.log, "Skipping file %s", file.c_str());
+ }
+ }
+ return Status::OK();
+}
+
+Status BlockCacheTier::Close() {
+ // stop the insert thread
+ if (opt_.pipeline_writes && insert_th_.joinable()) {
+ InsertOp op(/*quit=*/true);
+ insert_ops_.Push(std::move(op));
+ insert_th_.join();
+ }
+
+ // stop the writer before
+ writer_.Stop();
+
+ // clear all metadata
+ WriteLock _(&lock_);
+ metadata_.Clear();
+ return Status::OK();
+}
+
+template<class T>
+void Add(std::map<std::string, double>* stats, const std::string& key,
+ const T& t) {
+ stats->insert({key, static_cast<double>(t)});
+}
+
+PersistentCache::StatsType BlockCacheTier::Stats() {
+ std::map<std::string, double> stats;
+ Add(&stats, "persistentcache.blockcachetier.bytes_piplined",
+ stats_.bytes_pipelined_.Average());
+ Add(&stats, "persistentcache.blockcachetier.bytes_written",
+ stats_.bytes_written_.Average());
+ Add(&stats, "persistentcache.blockcachetier.bytes_read",
+ stats_.bytes_read_.Average());
+ Add(&stats, "persistentcache.blockcachetier.insert_dropped",
+ stats_.insert_dropped_);
+ Add(&stats, "persistentcache.blockcachetier.cache_hits",
+ stats_.cache_hits_);
+ Add(&stats, "persistentcache.blockcachetier.cache_misses",
+ stats_.cache_misses_);
+ Add(&stats, "persistentcache.blockcachetier.cache_errors",
+ stats_.cache_errors_);
+ Add(&stats, "persistentcache.blockcachetier.cache_hits_pct",
+ stats_.CacheHitPct());
+ Add(&stats, "persistentcache.blockcachetier.cache_misses_pct",
+ stats_.CacheMissPct());
+ Add(&stats, "persistentcache.blockcachetier.read_hit_latency",
+ stats_.read_hit_latency_.Average());
+ Add(&stats, "persistentcache.blockcachetier.read_miss_latency",
+ stats_.read_miss_latency_.Average());
+ Add(&stats, "persistentcache.blockcachetier.write_latency",
+ stats_.write_latency_.Average());
+
+ auto out = PersistentCacheTier::Stats();
+ out.push_back(stats);
+ return out;
+}
+
+Status BlockCacheTier::Insert(const Slice& key, const char* data,
+ const size_t size) {
+ // update stats
+ stats_.bytes_pipelined_.Add(size);
+
+ if (opt_.pipeline_writes) {
+ // off load the write to the write thread
+ insert_ops_.Push(
+ InsertOp(key.ToString(), std::move(std::string(data, size))));
+ return Status::OK();
+ }
+
+ assert(!opt_.pipeline_writes);
+ return InsertImpl(key, Slice(data, size));
+}
+
+void BlockCacheTier::InsertMain() {
+ while (true) {
+ InsertOp op(insert_ops_.Pop());
+
+ if (op.signal_) {
+ // that is a secret signal to exit
+ break;
+ }
+
+ size_t retry = 0;
+ Status s;
+ while ((s = InsertImpl(Slice(op.key_), Slice(op.data_))).IsTryAgain()) {
+ if (retry > kMaxRetry) {
+ break;
+ }
+
+ // this can happen when the buffers are full, we wait till some buffers
+ // are free. Why don't we wait inside the code. This is because we want
+ // to support both pipelined and non-pipelined mode
+ buffer_allocator_.WaitUntilUsable();
+ retry++;
+ }
+
+ if (!s.ok()) {
+ stats_.insert_dropped_++;
+ }
+ }
+}
+
+Status BlockCacheTier::InsertImpl(const Slice& key, const Slice& data) {
+ // pre-condition
+ assert(key.size());
+ assert(data.size());
+ assert(cache_file_);
+
+ StopWatchNano timer(opt_.env, /*auto_start=*/ true);
+
+ WriteLock _(&lock_);
+
+ LBA lba;
+ if (metadata_.Lookup(key, &lba)) {
+ // the key already exists, this is duplicate insert
+ return Status::OK();
+ }
+
+ while (!cache_file_->Append(key, data, &lba)) {
+ if (!cache_file_->Eof()) {
+ ROCKS_LOG_DEBUG(opt_.log, "Error inserting to cache file %d",
+ cache_file_->cacheid());
+ stats_.write_latency_.Add(timer.ElapsedNanos() / 1000);
+ return Status::TryAgain();
+ }
+
+ assert(cache_file_->Eof());
+ Status status = NewCacheFile();
+ if (!status.ok()) {
+ return status;
+ }
+ }
+
+ // Insert into lookup index
+ BlockInfo* info = metadata_.Insert(key, lba);
+ assert(info);
+ if (!info) {
+ return Status::IOError("Unexpected error inserting to index");
+ }
+
+ // insert to cache file reverse mapping
+ cache_file_->Add(info);
+
+ // update stats
+ stats_.bytes_written_.Add(data.size());
+ stats_.write_latency_.Add(timer.ElapsedNanos() / 1000);
+ return Status::OK();
+}
+
+Status BlockCacheTier::Lookup(const Slice& key, std::unique_ptr<char[]>* val,
+ size_t* size) {
+ StopWatchNano timer(opt_.env, /*auto_start=*/ true);
+
+ LBA lba;
+ bool status;
+ status = metadata_.Lookup(key, &lba);
+ if (!status) {
+ stats_.cache_misses_++;
+ stats_.read_miss_latency_.Add(timer.ElapsedNanos() / 1000);
+ return Status::NotFound("blockcache: key not found");
+ }
+
+ BlockCacheFile* const file = metadata_.Lookup(lba.cache_id_);
+ if (!file) {
+ // this can happen because the block index and cache file index are
+ // different, and the cache file might be removed between the two lookups
+ stats_.cache_misses_++;
+ stats_.read_miss_latency_.Add(timer.ElapsedNanos() / 1000);
+ return Status::NotFound("blockcache: cache file not found");
+ }
+
+ assert(file->refs_);
+
+ std::unique_ptr<char[]> scratch(new char[lba.size_]);
+ Slice blk_key;
+ Slice blk_val;
+
+ status = file->Read(lba, &blk_key, &blk_val, scratch.get());
+ --file->refs_;
+ if (!status) {
+ stats_.cache_misses_++;
+ stats_.cache_errors_++;
+ stats_.read_miss_latency_.Add(timer.ElapsedNanos() / 1000);
+ return Status::NotFound("blockcache: error reading data");
+ }
+
+ assert(blk_key == key);
+
+ val->reset(new char[blk_val.size()]);
+ memcpy(val->get(), blk_val.data(), blk_val.size());
+ *size = blk_val.size();
+
+ stats_.bytes_read_.Add(*size);
+ stats_.cache_hits_++;
+ stats_.read_hit_latency_.Add(timer.ElapsedNanos() / 1000);
+
+ return Status::OK();
+}
+
+bool BlockCacheTier::Erase(const Slice& key) {
+ WriteLock _(&lock_);
+ BlockInfo* info = metadata_.Remove(key);
+ assert(info);
+ delete info;
+ return true;
+}
+
+Status BlockCacheTier::NewCacheFile() {
+ lock_.AssertHeld();
+
+ TEST_SYNC_POINT_CALLBACK("BlockCacheTier::NewCacheFile:DeleteDir",
+ (void*)(GetCachePath().c_str()));
+
+ std::unique_ptr<WriteableCacheFile> f(
+ new WriteableCacheFile(opt_.env, &buffer_allocator_, &writer_,
+ GetCachePath(), writer_cache_id_,
+ opt_.cache_file_size, opt_.log));
+
+ bool status = f->Create(opt_.enable_direct_writes, opt_.enable_direct_reads);
+ if (!status) {
+ return Status::IOError("Error creating file");
+ }
+
+ Info(opt_.log, "Created cache file %d", writer_cache_id_);
+
+ writer_cache_id_++;
+ cache_file_ = f.release();
+
+ // insert to cache files tree
+ status = metadata_.Insert(cache_file_);
+ assert(status);
+ if (!status) {
+ Error(opt_.log, "Error inserting to metadata");
+ return Status::IOError("Error inserting to metadata");
+ }
+
+ return Status::OK();
+}
+
+bool BlockCacheTier::Reserve(const size_t size) {
+ WriteLock _(&lock_);
+ assert(size_ <= opt_.cache_size);
+
+ if (size + size_ <= opt_.cache_size) {
+ // there is enough space to write
+ size_ += size;
+ return true;
+ }
+
+ assert(size + size_ >= opt_.cache_size);
+ // there is not enough space to fit the requested data
+ // we can clear some space by evicting cold data
+
+ const double retain_fac = (100 - kEvictPct) / static_cast<double>(100);
+ while (size + size_ > opt_.cache_size * retain_fac) {
+ std::unique_ptr<BlockCacheFile> f(metadata_.Evict());
+ if (!f) {
+ // nothing is evictable
+ return false;
+ }
+ assert(!f->refs_);
+ uint64_t file_size;
+ if (!f->Delete(&file_size).ok()) {
+ // unable to delete file
+ return false;
+ }
+
+ assert(file_size <= size_);
+ size_ -= file_size;
+ }
+
+ size_ += size;
+ assert(size_ <= opt_.cache_size * 0.9);
+ return true;
+}
+
+Status NewPersistentCache(Env* const env, const std::string& path,
+ const uint64_t size,
+ const std::shared_ptr<Logger>& log,
+ const bool optimized_for_nvm,
+ std::shared_ptr<PersistentCache>* cache) {
+ if (!cache) {
+ return Status::IOError("invalid argument cache");
+ }
+
+ auto opt = PersistentCacheConfig(env, path, size, log);
+ if (optimized_for_nvm) {
+ // the default settings are optimized for SSD
+ // NVM devices are better accessed with 4K direct IO and written with
+ // parallelism
+ opt.enable_direct_writes = true;
+ opt.writer_qdepth = 4;
+ opt.writer_dispatch_size = 4 * 1024;
+ }
+
+ auto pcache = std::make_shared<BlockCacheTier>(opt);
+ Status s = pcache->Open();
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ *cache = pcache;
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ifndef ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.h
new file mode 100644
index 00000000..ae0c13fd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#ifndef OS_WIN
+#include <unistd.h>
+#endif // ! OS_WIN
+
+#include <atomic>
+#include <list>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <thread>
+
+#include "rocksdb/cache.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/persistent_cache.h"
+
+#include "utilities/persistent_cache/block_cache_tier_file.h"
+#include "utilities/persistent_cache/block_cache_tier_metadata.h"
+#include "utilities/persistent_cache/persistent_cache_util.h"
+
+#include "memory/arena.h"
+#include "memtable/skiplist.h"
+#include "monitoring/histogram.h"
+#include "port/port.h"
+#include "util/coding.h"
+#include "util/crc32c.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// Block cache tier implementation
+//
+class BlockCacheTier : public PersistentCacheTier {
+ public:
+ explicit BlockCacheTier(const PersistentCacheConfig& opt)
+ : opt_(opt),
+ insert_ops_(static_cast<size_t>(opt_.max_write_pipeline_backlog_size)),
+ buffer_allocator_(opt.write_buffer_size, opt.write_buffer_count()),
+ writer_(this, opt_.writer_qdepth, static_cast<size_t>(opt_.writer_dispatch_size)) {
+ Info(opt_.log, "Initializing allocator. size=%d B count=%" ROCKSDB_PRIszt,
+ opt_.write_buffer_size, opt_.write_buffer_count());
+ }
+
+ virtual ~BlockCacheTier() {
+ // Close is re-entrant so we can call close even if it is already closed
+ Close();
+ assert(!insert_th_.joinable());
+ }
+
+ Status Insert(const Slice& key, const char* data, const size_t size) override;
+ Status Lookup(const Slice& key, std::unique_ptr<char[]>* data,
+ size_t* size) override;
+ Status Open() override;
+ Status Close() override;
+ bool Erase(const Slice& key) override;
+ bool Reserve(const size_t size) override;
+
+ bool IsCompressed() override { return opt_.is_compressed; }
+
+ std::string GetPrintableOptions() const override { return opt_.ToString(); }
+
+ PersistentCache::StatsType Stats() override;
+
+ void TEST_Flush() override {
+ while (insert_ops_.Size()) {
+ /* sleep override */
+ Env::Default()->SleepForMicroseconds(1000000);
+ }
+ }
+
+ private:
+ // Percentage of cache to be evicted when the cache is full
+ static const size_t kEvictPct = 10;
+ // Max attempts to insert key, value to cache in pipelined mode
+ static const size_t kMaxRetry = 3;
+
+ // Pipelined operation
+ struct InsertOp {
+ explicit InsertOp(const bool signal) : signal_(signal) {}
+ explicit InsertOp(std::string&& key, const std::string& data)
+ : key_(std::move(key)), data_(data) {}
+ ~InsertOp() {}
+
+ InsertOp() = delete;
+ InsertOp(InsertOp&& /*rhs*/) = default;
+ InsertOp& operator=(InsertOp&& rhs) = default;
+
+ // used for estimating size by bounded queue
+ size_t Size() { return data_.size() + key_.size(); }
+
+ std::string key_;
+ std::string data_;
+ bool signal_ = false; // signal to request processing thread to exit
+ };
+
+ // entry point for insert thread
+ void InsertMain();
+ // insert implementation
+ Status InsertImpl(const Slice& key, const Slice& data);
+ // Create a new cache file
+ Status NewCacheFile();
+ // Get cache directory path
+ std::string GetCachePath() const { return opt_.path + "/cache"; }
+ // Cleanup folder
+ Status CleanupCacheFolder(const std::string& folder);
+
+ // Statistics
+ struct Statistics {
+ HistogramImpl bytes_pipelined_;
+ HistogramImpl bytes_written_;
+ HistogramImpl bytes_read_;
+ HistogramImpl read_hit_latency_;
+ HistogramImpl read_miss_latency_;
+ HistogramImpl write_latency_;
+ std::atomic<uint64_t> cache_hits_{0};
+ std::atomic<uint64_t> cache_misses_{0};
+ std::atomic<uint64_t> cache_errors_{0};
+ std::atomic<uint64_t> insert_dropped_{0};
+
+ double CacheHitPct() const {
+ const auto lookups = cache_hits_ + cache_misses_;
+ return lookups ? 100 * cache_hits_ / static_cast<double>(lookups) : 0.0;
+ }
+
+ double CacheMissPct() const {
+ const auto lookups = cache_hits_ + cache_misses_;
+ return lookups ? 100 * cache_misses_ / static_cast<double>(lookups) : 0.0;
+ }
+ };
+
+ port::RWMutex lock_; // Synchronization
+ const PersistentCacheConfig opt_; // BlockCache options
+ BoundedQueue<InsertOp> insert_ops_; // Ops waiting for insert
+ ROCKSDB_NAMESPACE::port::Thread insert_th_; // Insert thread
+ uint32_t writer_cache_id_ = 0; // Current cache file identifier
+ WriteableCacheFile* cache_file_ = nullptr; // Current cache file reference
+ CacheWriteBufferAllocator buffer_allocator_; // Buffer provider
+ ThreadedWriter writer_; // Writer threads
+ BlockCacheTierMetadata metadata_; // Cache meta data manager
+ std::atomic<uint64_t> size_{0}; // Size of the cache
+ Statistics stats_; // Statistics
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.cc
new file mode 100644
index 00000000..87ae603c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.cc
@@ -0,0 +1,608 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/persistent_cache/block_cache_tier_file.h"
+
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "env/composite_env_wrapper.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "util/crc32c.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// File creation factories
+//
+Status NewWritableCacheFile(Env* const env, const std::string& filepath,
+ std::unique_ptr<WritableFile>* file,
+ const bool use_direct_writes = false) {
+ EnvOptions opt;
+ opt.use_direct_writes = use_direct_writes;
+ Status s = env->NewWritableFile(filepath, file, opt);
+ return s;
+}
+
+Status NewRandomAccessCacheFile(Env* const env, const std::string& filepath,
+ std::unique_ptr<RandomAccessFile>* file,
+ const bool use_direct_reads = true) {
+ assert(env);
+
+ EnvOptions opt;
+ opt.use_direct_reads = use_direct_reads;
+ Status s = env->NewRandomAccessFile(filepath, file, opt);
+ return s;
+}
+
+//
+// BlockCacheFile
+//
+Status BlockCacheFile::Delete(uint64_t* size) {
+ assert(env_);
+
+ Status status = env_->GetFileSize(Path(), size);
+ if (!status.ok()) {
+ return status;
+ }
+ return env_->DeleteFile(Path());
+}
+
+//
+// CacheRecord
+//
+// Cache record represents the record on disk
+//
+// +--------+---------+----------+------------+---------------+-------------+
+// | magic | crc | key size | value size | key data | value data |
+// +--------+---------+----------+------------+---------------+-------------+
+// <-- 4 --><-- 4 --><-- 4 --><-- 4 --><-- key size --><-- v-size -->
+//
+struct CacheRecordHeader {
+ CacheRecordHeader()
+ : magic_(0), crc_(0), key_size_(0), val_size_(0) {}
+ CacheRecordHeader(const uint32_t magic, const uint32_t key_size,
+ const uint32_t val_size)
+ : magic_(magic), crc_(0), key_size_(key_size), val_size_(val_size) {}
+
+ uint32_t magic_;
+ uint32_t crc_;
+ uint32_t key_size_;
+ uint32_t val_size_;
+};
+
+struct CacheRecord {
+ CacheRecord() {}
+ CacheRecord(const Slice& key, const Slice& val)
+ : hdr_(MAGIC, static_cast<uint32_t>(key.size()),
+ static_cast<uint32_t>(val.size())),
+ key_(key),
+ val_(val) {
+ hdr_.crc_ = ComputeCRC();
+ }
+
+ uint32_t ComputeCRC() const;
+ bool Serialize(std::vector<CacheWriteBuffer*>* bufs, size_t* woff);
+ bool Deserialize(const Slice& buf);
+
+ static uint32_t CalcSize(const Slice& key, const Slice& val) {
+ return static_cast<uint32_t>(sizeof(CacheRecordHeader) + key.size() +
+ val.size());
+ }
+
+ static const uint32_t MAGIC = 0xfefa;
+
+ bool Append(std::vector<CacheWriteBuffer*>* bufs, size_t* woff,
+ const char* data, const size_t size);
+
+ CacheRecordHeader hdr_;
+ Slice key_;
+ Slice val_;
+};
+
+static_assert(sizeof(CacheRecordHeader) == 16, "DataHeader is not aligned");
+
+uint32_t CacheRecord::ComputeCRC() const {
+ uint32_t crc = 0;
+ CacheRecordHeader tmp = hdr_;
+ tmp.crc_ = 0;
+ crc = crc32c::Extend(crc, reinterpret_cast<const char*>(&tmp), sizeof(tmp));
+ crc = crc32c::Extend(crc, reinterpret_cast<const char*>(key_.data()),
+ key_.size());
+ crc = crc32c::Extend(crc, reinterpret_cast<const char*>(val_.data()),
+ val_.size());
+ return crc;
+}
+
+bool CacheRecord::Serialize(std::vector<CacheWriteBuffer*>* bufs,
+ size_t* woff) {
+ assert(bufs->size());
+ return Append(bufs, woff, reinterpret_cast<const char*>(&hdr_),
+ sizeof(hdr_)) &&
+ Append(bufs, woff, reinterpret_cast<const char*>(key_.data()),
+ key_.size()) &&
+ Append(bufs, woff, reinterpret_cast<const char*>(val_.data()),
+ val_.size());
+}
+
+bool CacheRecord::Append(std::vector<CacheWriteBuffer*>* bufs, size_t* woff,
+ const char* data, const size_t data_size) {
+ assert(*woff < bufs->size());
+
+ const char* p = data;
+ size_t size = data_size;
+
+ while (size && *woff < bufs->size()) {
+ CacheWriteBuffer* buf = (*bufs)[*woff];
+ const size_t free = buf->Free();
+ if (size <= free) {
+ buf->Append(p, size);
+ size = 0;
+ } else {
+ buf->Append(p, free);
+ p += free;
+ size -= free;
+ assert(!buf->Free());
+ assert(buf->Used() == buf->Capacity());
+ }
+
+ if (!buf->Free()) {
+ *woff += 1;
+ }
+ }
+
+ assert(!size);
+
+ return !size;
+}
+
+bool CacheRecord::Deserialize(const Slice& data) {
+ assert(data.size() >= sizeof(CacheRecordHeader));
+ if (data.size() < sizeof(CacheRecordHeader)) {
+ return false;
+ }
+
+ memcpy(&hdr_, data.data(), sizeof(hdr_));
+
+ assert(hdr_.key_size_ + hdr_.val_size_ + sizeof(hdr_) == data.size());
+ if (hdr_.key_size_ + hdr_.val_size_ + sizeof(hdr_) != data.size()) {
+ return false;
+ }
+
+ key_ = Slice(data.data_ + sizeof(hdr_), hdr_.key_size_);
+ val_ = Slice(key_.data_ + hdr_.key_size_, hdr_.val_size_);
+
+ if (!(hdr_.magic_ == MAGIC && ComputeCRC() == hdr_.crc_)) {
+ fprintf(stderr, "** magic %d ** \n", hdr_.magic_);
+ fprintf(stderr, "** key_size %d ** \n", hdr_.key_size_);
+ fprintf(stderr, "** val_size %d ** \n", hdr_.val_size_);
+ fprintf(stderr, "** key %s ** \n", key_.ToString().c_str());
+ fprintf(stderr, "** val %s ** \n", val_.ToString().c_str());
+ for (size_t i = 0; i < hdr_.val_size_; ++i) {
+ fprintf(stderr, "%d.", (uint8_t)val_.data()[i]);
+ }
+ fprintf(stderr, "\n** cksum %d != %d **", hdr_.crc_, ComputeCRC());
+ }
+
+ assert(hdr_.magic_ == MAGIC && ComputeCRC() == hdr_.crc_);
+ return hdr_.magic_ == MAGIC && ComputeCRC() == hdr_.crc_;
+}
+
+//
+// RandomAccessFile
+//
+
+bool RandomAccessCacheFile::Open(const bool enable_direct_reads) {
+ WriteLock _(&rwlock_);
+ return OpenImpl(enable_direct_reads);
+}
+
+bool RandomAccessCacheFile::OpenImpl(const bool enable_direct_reads) {
+ rwlock_.AssertHeld();
+
+ ROCKS_LOG_DEBUG(log_, "Opening cache file %s", Path().c_str());
+
+ std::unique_ptr<RandomAccessFile> file;
+ Status status =
+ NewRandomAccessCacheFile(env_, Path(), &file, enable_direct_reads);
+ if (!status.ok()) {
+ Error(log_, "Error opening random access file %s. %s", Path().c_str(),
+ status.ToString().c_str());
+ return false;
+ }
+ freader_.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(file), Path(), env_));
+
+ return true;
+}
+
+bool RandomAccessCacheFile::Read(const LBA& lba, Slice* key, Slice* val,
+ char* scratch) {
+ ReadLock _(&rwlock_);
+
+ assert(lba.cache_id_ == cache_id_);
+
+ if (!freader_) {
+ return false;
+ }
+
+ Slice result;
+ Status s = freader_->Read(lba.off_, lba.size_, &result, scratch);
+ if (!s.ok()) {
+ Error(log_, "Error reading from file %s. %s", Path().c_str(),
+ s.ToString().c_str());
+ return false;
+ }
+
+ assert(result.data() == scratch);
+
+ return ParseRec(lba, key, val, scratch);
+}
+
+bool RandomAccessCacheFile::ParseRec(const LBA& lba, Slice* key, Slice* val,
+ char* scratch) {
+ Slice data(scratch, lba.size_);
+
+ CacheRecord rec;
+ if (!rec.Deserialize(data)) {
+ assert(!"Error deserializing data");
+ Error(log_, "Error de-serializing record from file %s off %d",
+ Path().c_str(), lba.off_);
+ return false;
+ }
+
+ *key = Slice(rec.key_);
+ *val = Slice(rec.val_);
+
+ return true;
+}
+
+//
+// WriteableCacheFile
+//
+
+WriteableCacheFile::~WriteableCacheFile() {
+ WriteLock _(&rwlock_);
+ if (!eof_) {
+ // This file never flushed. We give priority to shutdown since this is a
+ // cache
+ // TODO(krad): Figure a way to flush the pending data
+ if (file_) {
+ assert(refs_ == 1);
+ --refs_;
+ }
+ }
+ assert(!refs_);
+ ClearBuffers();
+}
+
+bool WriteableCacheFile::Create(const bool /*enable_direct_writes*/,
+ const bool enable_direct_reads) {
+ WriteLock _(&rwlock_);
+
+ enable_direct_reads_ = enable_direct_reads;
+
+ ROCKS_LOG_DEBUG(log_, "Creating new cache %s (max size is %d B)",
+ Path().c_str(), max_size_);
+
+ assert(env_);
+
+ Status s = env_->FileExists(Path());
+ if (s.ok()) {
+ ROCKS_LOG_WARN(log_, "File %s already exists. %s", Path().c_str(),
+ s.ToString().c_str());
+ }
+
+ s = NewWritableCacheFile(env_, Path(), &file_);
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(log_, "Unable to create file %s. %s", Path().c_str(),
+ s.ToString().c_str());
+ return false;
+ }
+
+ assert(!refs_);
+ ++refs_;
+
+ return true;
+}
+
+bool WriteableCacheFile::Append(const Slice& key, const Slice& val, LBA* lba) {
+ WriteLock _(&rwlock_);
+
+ if (eof_) {
+ // We can't append since the file is full
+ return false;
+ }
+
+ // estimate the space required to store the (key, val)
+ uint32_t rec_size = CacheRecord::CalcSize(key, val);
+
+ if (!ExpandBuffer(rec_size)) {
+ // unable to expand the buffer
+ ROCKS_LOG_DEBUG(log_, "Error expanding buffers. size=%d", rec_size);
+ return false;
+ }
+
+ lba->cache_id_ = cache_id_;
+ lba->off_ = disk_woff_;
+ lba->size_ = rec_size;
+
+ CacheRecord rec(key, val);
+ if (!rec.Serialize(&bufs_, &buf_woff_)) {
+ // unexpected error: unable to serialize the data
+ assert(!"Error serializing record");
+ return false;
+ }
+
+ disk_woff_ += rec_size;
+ eof_ = disk_woff_ >= max_size_;
+
+ // dispatch buffer for flush
+ DispatchBuffer();
+
+ return true;
+}
+
+bool WriteableCacheFile::ExpandBuffer(const size_t size) {
+ rwlock_.AssertHeld();
+ assert(!eof_);
+
+ // determine if there is enough space
+ size_t free = 0; // compute the free space left in buffer
+ for (size_t i = buf_woff_; i < bufs_.size(); ++i) {
+ free += bufs_[i]->Free();
+ if (size <= free) {
+ // we have enough space in the buffer
+ return true;
+ }
+ }
+
+ // expand the buffer until there is enough space to write `size` bytes
+ assert(free < size);
+ assert(alloc_);
+
+ while (free < size) {
+ CacheWriteBuffer* const buf = alloc_->Allocate();
+ if (!buf) {
+ ROCKS_LOG_DEBUG(log_, "Unable to allocate buffers");
+ return false;
+ }
+
+ size_ += static_cast<uint32_t>(buf->Free());
+ free += buf->Free();
+ bufs_.push_back(buf);
+ }
+
+ assert(free >= size);
+ return true;
+}
+
+void WriteableCacheFile::DispatchBuffer() {
+ rwlock_.AssertHeld();
+
+ assert(bufs_.size());
+ assert(buf_doff_ <= buf_woff_);
+ assert(buf_woff_ <= bufs_.size());
+
+ if (pending_ios_) {
+ return;
+ }
+
+ if (!eof_ && buf_doff_ == buf_woff_) {
+ // dispatch buffer is pointing to write buffer and we haven't hit eof
+ return;
+ }
+
+ assert(eof_ || buf_doff_ < buf_woff_);
+ assert(buf_doff_ < bufs_.size());
+ assert(file_);
+ assert(alloc_);
+
+ auto* buf = bufs_[buf_doff_];
+ const uint64_t file_off = buf_doff_ * alloc_->BufferSize();
+
+ assert(!buf->Free() ||
+ (eof_ && buf_doff_ == buf_woff_ && buf_woff_ < bufs_.size()));
+ // we have reached end of file, and there is space in the last buffer
+ // pad it with zero for direct IO
+ buf->FillTrailingZeros();
+
+ assert(buf->Used() % kFileAlignmentSize == 0);
+
+ writer_->Write(file_.get(), buf, file_off,
+ std::bind(&WriteableCacheFile::BufferWriteDone, this));
+ pending_ios_++;
+ buf_doff_++;
+}
+
+void WriteableCacheFile::BufferWriteDone() {
+ WriteLock _(&rwlock_);
+
+ assert(bufs_.size());
+
+ pending_ios_--;
+
+ if (buf_doff_ < bufs_.size()) {
+ DispatchBuffer();
+ }
+
+ if (eof_ && buf_doff_ >= bufs_.size() && !pending_ios_) {
+ // end-of-file reached, move to read mode
+ CloseAndOpenForReading();
+ }
+}
+
+void WriteableCacheFile::CloseAndOpenForReading() {
+ // Our env abstraction do not allow reading from a file opened for appending
+ // We need close the file and re-open it for reading
+ Close();
+ RandomAccessCacheFile::OpenImpl(enable_direct_reads_);
+}
+
+bool WriteableCacheFile::ReadBuffer(const LBA& lba, Slice* key, Slice* block,
+ char* scratch) {
+ rwlock_.AssertHeld();
+
+ if (!ReadBuffer(lba, scratch)) {
+ Error(log_, "Error reading from buffer. cache=%d off=%d", cache_id_,
+ lba.off_);
+ return false;
+ }
+
+ return ParseRec(lba, key, block, scratch);
+}
+
+bool WriteableCacheFile::ReadBuffer(const LBA& lba, char* data) {
+ rwlock_.AssertHeld();
+
+ assert(lba.off_ < disk_woff_);
+ assert(alloc_);
+
+ // we read from the buffers like reading from a flat file. The list of buffers
+ // are treated as contiguous stream of data
+
+ char* tmp = data;
+ size_t pending_nbytes = lba.size_;
+ // start buffer
+ size_t start_idx = lba.off_ / alloc_->BufferSize();
+ // offset into the start buffer
+ size_t start_off = lba.off_ % alloc_->BufferSize();
+
+ assert(start_idx <= buf_woff_);
+
+ for (size_t i = start_idx; pending_nbytes && i < bufs_.size(); ++i) {
+ assert(i <= buf_woff_);
+ auto* buf = bufs_[i];
+ assert(i == buf_woff_ || !buf->Free());
+ // bytes to write to the buffer
+ size_t nbytes = pending_nbytes > (buf->Used() - start_off)
+ ? (buf->Used() - start_off)
+ : pending_nbytes;
+ memcpy(tmp, buf->Data() + start_off, nbytes);
+
+ // left over to be written
+ pending_nbytes -= nbytes;
+ start_off = 0;
+ tmp += nbytes;
+ }
+
+ assert(!pending_nbytes);
+ if (pending_nbytes) {
+ return false;
+ }
+
+ assert(tmp == data + lba.size_);
+ return true;
+}
+
+void WriteableCacheFile::Close() {
+ rwlock_.AssertHeld();
+
+ assert(size_ >= max_size_);
+ assert(disk_woff_ >= max_size_);
+ assert(buf_doff_ == bufs_.size());
+ assert(bufs_.size() - buf_woff_ <= 1);
+ assert(!pending_ios_);
+
+ Info(log_, "Closing file %s. size=%d written=%d", Path().c_str(), size_,
+ disk_woff_);
+
+ ClearBuffers();
+ file_.reset();
+
+ assert(refs_);
+ --refs_;
+}
+
+void WriteableCacheFile::ClearBuffers() {
+ assert(alloc_);
+
+ for (size_t i = 0; i < bufs_.size(); ++i) {
+ alloc_->Deallocate(bufs_[i]);
+ }
+
+ bufs_.clear();
+}
+
+//
+// ThreadedFileWriter implementation
+//
+ThreadedWriter::ThreadedWriter(PersistentCacheTier* const cache,
+ const size_t qdepth, const size_t io_size)
+ : Writer(cache), io_size_(io_size) {
+ for (size_t i = 0; i < qdepth; ++i) {
+ port::Thread th(&ThreadedWriter::ThreadMain, this);
+ threads_.push_back(std::move(th));
+ }
+}
+
+void ThreadedWriter::Stop() {
+ // notify all threads to exit
+ for (size_t i = 0; i < threads_.size(); ++i) {
+ q_.Push(IO(/*signal=*/true));
+ }
+
+ // wait for all threads to exit
+ for (auto& th : threads_) {
+ th.join();
+ assert(!th.joinable());
+ }
+ threads_.clear();
+}
+
+void ThreadedWriter::Write(WritableFile* const file, CacheWriteBuffer* buf,
+ const uint64_t file_off,
+ const std::function<void()> callback) {
+ q_.Push(IO(file, buf, file_off, callback));
+}
+
+void ThreadedWriter::ThreadMain() {
+ while (true) {
+ // Fetch the IO to process
+ IO io(q_.Pop());
+ if (io.signal_) {
+ // that's secret signal to exit
+ break;
+ }
+
+ // Reserve space for writing the buffer
+ while (!cache_->Reserve(io.buf_->Used())) {
+ // We can fail to reserve space if every file in the system
+ // is being currently accessed
+ /* sleep override */
+ Env::Default()->SleepForMicroseconds(1000000);
+ }
+
+ DispatchIO(io);
+
+ io.callback_();
+ }
+}
+
+void ThreadedWriter::DispatchIO(const IO& io) {
+ size_t written = 0;
+ while (written < io.buf_->Used()) {
+ Slice data(io.buf_->Data() + written, io_size_);
+ Status s = io.file_->Append(data);
+ assert(s.ok());
+ if (!s.ok()) {
+ // That is definite IO error to device. There is not much we can
+ // do but ignore the failure. This can lead to corruption of data on
+ // disk, but the cache will skip while reading
+ fprintf(stderr, "Error writing data to file. %s\n", s.ToString().c_str());
+ }
+ written += io_size_;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.h
new file mode 100644
index 00000000..95be4ec3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file.h
@@ -0,0 +1,296 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "file/random_access_file_reader.h"
+
+#include "rocksdb/comparator.h"
+#include "rocksdb/env.h"
+
+#include "utilities/persistent_cache/block_cache_tier_file_buffer.h"
+#include "utilities/persistent_cache/lrulist.h"
+#include "utilities/persistent_cache/persistent_cache_tier.h"
+#include "utilities/persistent_cache/persistent_cache_util.h"
+
+#include "port/port.h"
+#include "util/crc32c.h"
+#include "util/mutexlock.h"
+
+// The io code path of persistent cache uses pipelined architecture
+//
+// client -> In Queue <-- BlockCacheTier --> Out Queue <-- Writer <--> Kernel
+//
+// This would enable the system to scale for GB/s of throughput which is
+// expected with modern devies like NVM.
+//
+// The file level operations are encapsulated in the following abstractions
+//
+// BlockCacheFile
+// ^
+// |
+// |
+// RandomAccessCacheFile (For reading)
+// ^
+// |
+// |
+// WriteableCacheFile (For writing)
+//
+// Write IO code path :
+//
+namespace ROCKSDB_NAMESPACE {
+
+class WriteableCacheFile;
+struct BlockInfo;
+
+// Represents a logical record on device
+//
+// (L)ogical (B)lock (Address = { cache-file-id, offset, size }
+struct LogicalBlockAddress {
+ LogicalBlockAddress() {}
+ explicit LogicalBlockAddress(const uint32_t cache_id, const uint32_t off,
+ const uint16_t size)
+ : cache_id_(cache_id), off_(off), size_(size) {}
+
+ uint32_t cache_id_ = 0;
+ uint32_t off_ = 0;
+ uint32_t size_ = 0;
+};
+
+typedef LogicalBlockAddress LBA;
+
+// class Writer
+//
+// Writer is the abstraction used for writing data to file. The component can be
+// multithreaded. It is the last step of write pipeline
+class Writer {
+ public:
+ explicit Writer(PersistentCacheTier* const cache) : cache_(cache) {}
+ virtual ~Writer() {}
+
+ // write buffer to file at the given offset
+ virtual void Write(WritableFile* const file, CacheWriteBuffer* buf,
+ const uint64_t file_off,
+ const std::function<void()> callback) = 0;
+ // stop the writer
+ virtual void Stop() = 0;
+
+ PersistentCacheTier* const cache_;
+};
+
+// class BlockCacheFile
+//
+// Generic interface to support building file specialized for read/writing
+class BlockCacheFile : public LRUElement<BlockCacheFile> {
+ public:
+ explicit BlockCacheFile(const uint32_t cache_id)
+ : LRUElement<BlockCacheFile>(), cache_id_(cache_id) {}
+
+ explicit BlockCacheFile(Env* const env, const std::string& dir,
+ const uint32_t cache_id)
+ : LRUElement<BlockCacheFile>(),
+ env_(env),
+ dir_(dir),
+ cache_id_(cache_id) {}
+
+ virtual ~BlockCacheFile() {}
+
+ // append key/value to file and return LBA locator to user
+ virtual bool Append(const Slice& /*key*/, const Slice& /*val*/,
+ LBA* const /*lba*/) {
+ assert(!"not implemented");
+ return false;
+ }
+
+ // read from the record locator (LBA) and return key, value and status
+ virtual bool Read(const LBA& /*lba*/, Slice* /*key*/, Slice* /*block*/,
+ char* /*scratch*/) {
+ assert(!"not implemented");
+ return false;
+ }
+
+ // get file path
+ std::string Path() const {
+ return dir_ + "/" + std::to_string(cache_id_) + ".rc";
+ }
+ // get cache ID
+ uint32_t cacheid() const { return cache_id_; }
+ // Add block information to file data
+ // Block information is the list of index reference for this file
+ virtual void Add(BlockInfo* binfo) {
+ WriteLock _(&rwlock_);
+ block_infos_.push_back(binfo);
+ }
+ // get block information
+ std::list<BlockInfo*>& block_infos() { return block_infos_; }
+ // delete file and return the size of the file
+ virtual Status Delete(uint64_t* size);
+
+ protected:
+ port::RWMutex rwlock_; // synchronization mutex
+ Env* const env_ = nullptr; // Env for OS
+ const std::string dir_; // Directory name
+ const uint32_t cache_id_; // Cache id for the file
+ std::list<BlockInfo*> block_infos_; // List of index entries mapping to the
+ // file content
+};
+
+// class RandomAccessFile
+//
+// Thread safe implementation for reading random data from file
+class RandomAccessCacheFile : public BlockCacheFile {
+ public:
+ explicit RandomAccessCacheFile(Env* const env, const std::string& dir,
+ const uint32_t cache_id,
+ const std::shared_ptr<Logger>& log)
+ : BlockCacheFile(env, dir, cache_id), log_(log) {}
+
+ virtual ~RandomAccessCacheFile() {}
+
+ // open file for reading
+ bool Open(const bool enable_direct_reads);
+ // read data from the disk
+ bool Read(const LBA& lba, Slice* key, Slice* block, char* scratch) override;
+
+ private:
+ std::unique_ptr<RandomAccessFileReader> freader_;
+
+ protected:
+ bool OpenImpl(const bool enable_direct_reads);
+ bool ParseRec(const LBA& lba, Slice* key, Slice* val, char* scratch);
+
+ std::shared_ptr<Logger> log_; // log file
+};
+
+// class WriteableCacheFile
+//
+// All writes to the files are cached in buffers. The buffers are flushed to
+// disk as they get filled up. When file size reaches a certain size, a new file
+// will be created provided there is free space
+class WriteableCacheFile : public RandomAccessCacheFile {
+ public:
+ explicit WriteableCacheFile(Env* const env, CacheWriteBufferAllocator* alloc,
+ Writer* writer, const std::string& dir,
+ const uint32_t cache_id, const uint32_t max_size,
+ const std::shared_ptr<Logger>& log)
+ : RandomAccessCacheFile(env, dir, cache_id, log),
+ alloc_(alloc),
+ writer_(writer),
+ max_size_(max_size) {}
+
+ virtual ~WriteableCacheFile();
+
+ // create file on disk
+ bool Create(const bool enable_direct_writes, const bool enable_direct_reads);
+
+ // read data from logical file
+ bool Read(const LBA& lba, Slice* key, Slice* block, char* scratch) override {
+ ReadLock _(&rwlock_);
+ const bool closed = eof_ && bufs_.empty();
+ if (closed) {
+ // the file is closed, read from disk
+ return RandomAccessCacheFile::Read(lba, key, block, scratch);
+ }
+ // file is still being written, read from buffers
+ return ReadBuffer(lba, key, block, scratch);
+ }
+
+ // append data to end of file
+ bool Append(const Slice&, const Slice&, LBA* const) override;
+ // End-of-file
+ bool Eof() const { return eof_; }
+
+ private:
+ friend class ThreadedWriter;
+
+ static const size_t kFileAlignmentSize = 4 * 1024; // align file size
+
+ bool ReadBuffer(const LBA& lba, Slice* key, Slice* block, char* scratch);
+ bool ReadBuffer(const LBA& lba, char* data);
+ bool ExpandBuffer(const size_t size);
+ void DispatchBuffer();
+ void BufferWriteDone();
+ void CloseAndOpenForReading();
+ void ClearBuffers();
+ void Close();
+
+ // File layout in memory
+ //
+ // +------+------+------+------+------+------+
+ // | b0 | b1 | b2 | b3 | b4 | b5 |
+ // +------+------+------+------+------+------+
+ // ^ ^
+ // | |
+ // buf_doff_ buf_woff_
+ // (next buffer to (next buffer to fill)
+ // flush to disk)
+ //
+ // The buffers are flushed to disk serially for a given file
+
+ CacheWriteBufferAllocator* const alloc_ = nullptr; // Buffer provider
+ Writer* const writer_ = nullptr; // File writer thread
+ std::unique_ptr<WritableFile> file_; // RocksDB Env file abstraction
+ std::vector<CacheWriteBuffer*> bufs_; // Written buffers
+ uint32_t size_ = 0; // Size of the file
+ const uint32_t max_size_; // Max size of the file
+ bool eof_ = false; // End of file
+ uint32_t disk_woff_ = 0; // Offset to write on disk
+ size_t buf_woff_ = 0; // off into bufs_ to write
+ size_t buf_doff_ = 0; // off into bufs_ to dispatch
+ size_t pending_ios_ = 0; // Number of ios to disk in-progress
+ bool enable_direct_reads_ = false; // Should we enable direct reads
+ // when reading from disk
+};
+
+//
+// Abstraction to do writing to device. It is part of pipelined architecture.
+//
+class ThreadedWriter : public Writer {
+ public:
+ // Representation of IO to device
+ struct IO {
+ explicit IO(const bool signal) : signal_(signal) {}
+ explicit IO(WritableFile* const file, CacheWriteBuffer* const buf,
+ const uint64_t file_off, const std::function<void()> callback)
+ : file_(file), buf_(buf), file_off_(file_off), callback_(callback) {}
+
+ IO(const IO&) = default;
+ IO& operator=(const IO&) = default;
+ size_t Size() const { return sizeof(IO); }
+
+ WritableFile* file_ = nullptr; // File to write to
+ CacheWriteBuffer* buf_ = nullptr; // buffer to write
+ uint64_t file_off_ = 0; // file offset
+ bool signal_ = false; // signal to exit thread loop
+ std::function<void()> callback_; // Callback on completion
+ };
+
+ explicit ThreadedWriter(PersistentCacheTier* const cache, const size_t qdepth,
+ const size_t io_size);
+ virtual ~ThreadedWriter() { assert(threads_.empty()); }
+
+ void Stop() override;
+ void Write(WritableFile* const file, CacheWriteBuffer* buf,
+ const uint64_t file_off,
+ const std::function<void()> callback) override;
+
+ private:
+ void ThreadMain();
+ void DispatchIO(const IO& io);
+
+ const size_t io_size_ = 0;
+ BoundedQueue<IO> q_;
+ std::vector<port::Thread> threads_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file_buffer.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file_buffer.h
new file mode 100644
index 00000000..23013d72
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_file_buffer.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <list>
+#include <memory>
+#include <string>
+
+#include "rocksdb/comparator.h"
+#include "memory/arena.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// CacheWriteBuffer
+//
+// Buffer abstraction that can be manipulated via append
+// (not thread safe)
+class CacheWriteBuffer {
+ public:
+ explicit CacheWriteBuffer(const size_t size) : size_(size), pos_(0) {
+ buf_.reset(new char[size_]);
+ assert(!pos_);
+ assert(size_);
+ }
+
+ virtual ~CacheWriteBuffer() {}
+
+ void Append(const char* buf, const size_t size) {
+ assert(pos_ + size <= size_);
+ memcpy(buf_.get() + pos_, buf, size);
+ pos_ += size;
+ assert(pos_ <= size_);
+ }
+
+ void FillTrailingZeros() {
+ assert(pos_ <= size_);
+ memset(buf_.get() + pos_, '0', size_ - pos_);
+ pos_ = size_;
+ }
+
+ void Reset() { pos_ = 0; }
+ size_t Free() const { return size_ - pos_; }
+ size_t Capacity() const { return size_; }
+ size_t Used() const { return pos_; }
+ char* Data() const { return buf_.get(); }
+
+ private:
+ std::unique_ptr<char[]> buf_;
+ const size_t size_;
+ size_t pos_;
+};
+
+//
+// CacheWriteBufferAllocator
+//
+// Buffer pool abstraction(not thread safe)
+//
+class CacheWriteBufferAllocator {
+ public:
+ explicit CacheWriteBufferAllocator(const size_t buffer_size,
+ const size_t buffer_count)
+ : cond_empty_(&lock_), buffer_size_(buffer_size) {
+ MutexLock _(&lock_);
+ buffer_size_ = buffer_size;
+ for (uint32_t i = 0; i < buffer_count; i++) {
+ auto* buf = new CacheWriteBuffer(buffer_size_);
+ assert(buf);
+ if (buf) {
+ bufs_.push_back(buf);
+ cond_empty_.Signal();
+ }
+ }
+ }
+
+ virtual ~CacheWriteBufferAllocator() {
+ MutexLock _(&lock_);
+ assert(bufs_.size() * buffer_size_ == Capacity());
+ for (auto* buf : bufs_) {
+ delete buf;
+ }
+ bufs_.clear();
+ }
+
+ CacheWriteBuffer* Allocate() {
+ MutexLock _(&lock_);
+ if (bufs_.empty()) {
+ return nullptr;
+ }
+
+ assert(!bufs_.empty());
+ CacheWriteBuffer* const buf = bufs_.front();
+ bufs_.pop_front();
+ return buf;
+ }
+
+ void Deallocate(CacheWriteBuffer* const buf) {
+ assert(buf);
+ MutexLock _(&lock_);
+ buf->Reset();
+ bufs_.push_back(buf);
+ cond_empty_.Signal();
+ }
+
+ void WaitUntilUsable() {
+ // We are asked to wait till we have buffers available
+ MutexLock _(&lock_);
+ while (bufs_.empty()) {
+ cond_empty_.Wait();
+ }
+ }
+
+ size_t Capacity() const { return bufs_.size() * buffer_size_; }
+ size_t Free() const { return bufs_.size() * buffer_size_; }
+ size_t BufferSize() const { return buffer_size_; }
+
+ private:
+ port::Mutex lock_; // Sync lock
+ port::CondVar cond_empty_; // Condition var for empty buffers
+ size_t buffer_size_; // Size of each buffer
+ std::list<CacheWriteBuffer*> bufs_; // Buffer stash
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.cc
new file mode 100644
index 00000000..c99322e6
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/persistent_cache/block_cache_tier_metadata.h"
+
+#include <functional>
+
+namespace ROCKSDB_NAMESPACE {
+
+bool BlockCacheTierMetadata::Insert(BlockCacheFile* file) {
+ return cache_file_index_.Insert(file);
+}
+
+BlockCacheFile* BlockCacheTierMetadata::Lookup(const uint32_t cache_id) {
+ BlockCacheFile* ret = nullptr;
+ BlockCacheFile lookup_key(cache_id);
+ bool ok = cache_file_index_.Find(&lookup_key, &ret);
+ if (ok) {
+ assert(ret->refs_);
+ return ret;
+ }
+ return nullptr;
+}
+
+BlockCacheFile* BlockCacheTierMetadata::Evict() {
+ using std::placeholders::_1;
+ auto fn = std::bind(&BlockCacheTierMetadata::RemoveAllKeys, this, _1);
+ return cache_file_index_.Evict(fn);
+}
+
+void BlockCacheTierMetadata::Clear() {
+ cache_file_index_.Clear([](BlockCacheFile* arg){ delete arg; });
+ block_index_.Clear([](BlockInfo* arg){ delete arg; });
+}
+
+BlockInfo* BlockCacheTierMetadata::Insert(const Slice& key, const LBA& lba) {
+ std::unique_ptr<BlockInfo> binfo(new BlockInfo(key, lba));
+ if (!block_index_.Insert(binfo.get())) {
+ return nullptr;
+ }
+ return binfo.release();
+}
+
+bool BlockCacheTierMetadata::Lookup(const Slice& key, LBA* lba) {
+ BlockInfo lookup_key(key);
+ BlockInfo* block;
+ port::RWMutex* rlock = nullptr;
+ if (!block_index_.Find(&lookup_key, &block, &rlock)) {
+ return false;
+ }
+
+ ReadUnlock _(rlock);
+ assert(block->key_ == key.ToString());
+ if (lba) {
+ *lba = block->lba_;
+ }
+ return true;
+}
+
+BlockInfo* BlockCacheTierMetadata::Remove(const Slice& key) {
+ BlockInfo lookup_key(key);
+ BlockInfo* binfo = nullptr;
+ bool ok __attribute__((__unused__));
+ ok = block_index_.Erase(&lookup_key, &binfo);
+ assert(ok);
+ return binfo;
+}
+
+void BlockCacheTierMetadata::RemoveAllKeys(BlockCacheFile* f) {
+ for (BlockInfo* binfo : f->block_infos()) {
+ BlockInfo* tmp = nullptr;
+ bool status = block_index_.Erase(binfo, &tmp);
+ (void)status;
+ assert(status);
+ assert(tmp == binfo);
+ delete binfo;
+ }
+ f->block_infos().clear();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.h
new file mode 100644
index 00000000..92adae2b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/block_cache_tier_metadata.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+#include <string>
+#include <unordered_map>
+
+#include "rocksdb/slice.h"
+
+#include "utilities/persistent_cache/block_cache_tier_file.h"
+#include "utilities/persistent_cache/hash_table.h"
+#include "utilities/persistent_cache/hash_table_evictable.h"
+#include "utilities/persistent_cache/lrulist.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// Block Cache Tier Metadata
+//
+// The BlockCacheTierMetadata holds all the metadata associated with block
+// cache. It
+// fundamentally contains 2 indexes and an LRU.
+//
+// Block Cache Index
+//
+// This is a forward index that maps a given key to a LBA (Logical Block
+// Address). LBA is a disk pointer that points to a record on the cache.
+//
+// LBA = { cache-id, offset, size }
+//
+// Cache File Index
+//
+// This is a forward index that maps a given cache-id to a cache file object.
+// Typically you would lookup using LBA and use the object to read or write
+struct BlockInfo {
+ explicit BlockInfo(const Slice& key, const LBA& lba = LBA())
+ : key_(key.ToString()), lba_(lba) {}
+
+ std::string key_;
+ LBA lba_;
+};
+
+class BlockCacheTierMetadata {
+ public:
+ explicit BlockCacheTierMetadata(const uint32_t blocks_capacity = 1024 * 1024,
+ const uint32_t cachefile_capacity = 10 * 1024)
+ : cache_file_index_(cachefile_capacity), block_index_(blocks_capacity) {}
+
+ virtual ~BlockCacheTierMetadata() {}
+
+ // Insert a given cache file
+ bool Insert(BlockCacheFile* file);
+
+ // Lookup cache file based on cache_id
+ BlockCacheFile* Lookup(const uint32_t cache_id);
+
+ // Insert block information to block index
+ BlockInfo* Insert(const Slice& key, const LBA& lba);
+ // bool Insert(BlockInfo* binfo);
+
+ // Lookup block information from block index
+ bool Lookup(const Slice& key, LBA* lba);
+
+ // Remove a given from the block index
+ BlockInfo* Remove(const Slice& key);
+
+ // Find and evict a cache file using LRU policy
+ BlockCacheFile* Evict();
+
+ // Clear the metadata contents
+ virtual void Clear();
+
+ protected:
+ // Remove all block information from a given file
+ virtual void RemoveAllKeys(BlockCacheFile* file);
+
+ private:
+ // Cache file index definition
+ //
+ // cache-id => BlockCacheFile
+ struct BlockCacheFileHash {
+ uint64_t operator()(const BlockCacheFile* rec) {
+ return std::hash<uint32_t>()(rec->cacheid());
+ }
+ };
+
+ struct BlockCacheFileEqual {
+ uint64_t operator()(const BlockCacheFile* lhs, const BlockCacheFile* rhs) {
+ return lhs->cacheid() == rhs->cacheid();
+ }
+ };
+
+ typedef EvictableHashTable<BlockCacheFile, BlockCacheFileHash,
+ BlockCacheFileEqual>
+ CacheFileIndexType;
+
+ // Block Lookup Index
+ //
+ // key => LBA
+ struct Hash {
+ size_t operator()(BlockInfo* node) const {
+ return std::hash<std::string>()(node->key_);
+ }
+ };
+
+ struct Equal {
+ size_t operator()(BlockInfo* lhs, BlockInfo* rhs) const {
+ return lhs->key_ == rhs->key_;
+ }
+ };
+
+ typedef HashTable<BlockInfo*, Hash, Equal> BlockIndexType;
+
+ CacheFileIndexType cache_file_index_;
+ BlockIndexType block_index_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table.h
new file mode 100644
index 00000000..3d0a1f99
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table.h
@@ -0,0 +1,238 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <assert.h>
+#include <list>
+#include <vector>
+
+#ifdef OS_LINUX
+#include <sys/mman.h>
+#endif
+
+#include "rocksdb/env.h"
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// HashTable<T, Hash, Equal>
+//
+// Traditional implementation of hash table with synchronization built on top
+// don't perform very well in multi-core scenarios. This is an implementation
+// designed for multi-core scenarios with high lock contention.
+//
+// |<-------- alpha ------------->|
+// Buckets Collision list
+// ---- +----+ +---+---+--- ...... ---+---+---+
+// / | |--->| | | | | |
+// / +----+ +---+---+--- ...... ---+---+---+
+// / | |
+// Locks/ +----+
+// +--+/ . .
+// | | . .
+// +--+ . .
+// | | . .
+// +--+ . .
+// | | . .
+// +--+ . .
+// \ +----+
+// \ | |
+// \ +----+
+// \ | |
+// \---- +----+
+//
+// The lock contention is spread over an array of locks. This helps improve
+// concurrent access. The spine is designed for a certain capacity and load
+// factor. When the capacity planning is done correctly we can expect
+// O(load_factor = 1) insert, access and remove time.
+//
+// Micro benchmark on debug build gives about .5 Million/sec rate of insert,
+// erase and lookup in parallel (total of about 1.5 Million ops/sec). If the
+// blocks were of 4K, the hash table can support a virtual throughput of
+// 6 GB/s.
+//
+// T Object type (contains both key and value)
+// Hash Function that returns an hash from type T
+// Equal Returns if two objects are equal
+// (We need explicit equal for pointer type)
+//
+template <class T, class Hash, class Equal>
+class HashTable {
+ public:
+ explicit HashTable(const size_t capacity = 1024 * 1024,
+ const float load_factor = 2.0, const uint32_t nlocks = 256)
+ : nbuckets_(
+ static_cast<uint32_t>(load_factor ? capacity / load_factor : 0)),
+ nlocks_(nlocks) {
+ // pre-conditions
+ assert(capacity);
+ assert(load_factor);
+ assert(nbuckets_);
+ assert(nlocks_);
+
+ buckets_.reset(new Bucket[nbuckets_]);
+#ifdef OS_LINUX
+ mlock(buckets_.get(), nbuckets_ * sizeof(Bucket));
+#endif
+
+ // initialize locks
+ locks_.reset(new port::RWMutex[nlocks_]);
+#ifdef OS_LINUX
+ mlock(locks_.get(), nlocks_ * sizeof(port::RWMutex));
+#endif
+
+ // post-conditions
+ assert(buckets_);
+ assert(locks_);
+ }
+
+ virtual ~HashTable() { AssertEmptyBuckets(); }
+
+ //
+ // Insert given record to hash table
+ //
+ bool Insert(const T& t) {
+ const uint64_t h = Hash()(t);
+ const uint32_t bucket_idx = h % nbuckets_;
+ const uint32_t lock_idx = bucket_idx % nlocks_;
+
+ WriteLock _(&locks_[lock_idx]);
+ auto& bucket = buckets_[bucket_idx];
+ return Insert(&bucket, t);
+ }
+
+ // Lookup hash table
+ //
+ // Please note that read lock should be held by the caller. This is because
+ // the caller owns the data, and should hold the read lock as long as he
+ // operates on the data.
+ bool Find(const T& t, T* ret, port::RWMutex** ret_lock) {
+ const uint64_t h = Hash()(t);
+ const uint32_t bucket_idx = h % nbuckets_;
+ const uint32_t lock_idx = bucket_idx % nlocks_;
+
+ port::RWMutex& lock = locks_[lock_idx];
+ lock.ReadLock();
+
+ auto& bucket = buckets_[bucket_idx];
+ if (Find(&bucket, t, ret)) {
+ *ret_lock = &lock;
+ return true;
+ }
+
+ lock.ReadUnlock();
+ return false;
+ }
+
+ //
+ // Erase a given key from the hash table
+ //
+ bool Erase(const T& t, T* ret) {
+ const uint64_t h = Hash()(t);
+ const uint32_t bucket_idx = h % nbuckets_;
+ const uint32_t lock_idx = bucket_idx % nlocks_;
+
+ WriteLock _(&locks_[lock_idx]);
+
+ auto& bucket = buckets_[bucket_idx];
+ return Erase(&bucket, t, ret);
+ }
+
+ // Fetch the mutex associated with a key
+ // This call is used to hold the lock for a given data for extended period of
+ // time.
+ port::RWMutex* GetMutex(const T& t) {
+ const uint64_t h = Hash()(t);
+ const uint32_t bucket_idx = h % nbuckets_;
+ const uint32_t lock_idx = bucket_idx % nlocks_;
+
+ return &locks_[lock_idx];
+ }
+
+ void Clear(void (*fn)(T)) {
+ for (uint32_t i = 0; i < nbuckets_; ++i) {
+ const uint32_t lock_idx = i % nlocks_;
+ WriteLock _(&locks_[lock_idx]);
+ for (auto& t : buckets_[i].list_) {
+ (*fn)(t);
+ }
+ buckets_[i].list_.clear();
+ }
+ }
+
+ protected:
+ // Models bucket of keys that hash to the same bucket number
+ struct Bucket {
+ std::list<T> list_;
+ };
+
+ // Substitute for std::find with custom comparator operator
+ typename std::list<T>::iterator Find(std::list<T>* list, const T& t) {
+ for (auto it = list->begin(); it != list->end(); ++it) {
+ if (Equal()(*it, t)) {
+ return it;
+ }
+ }
+ return list->end();
+ }
+
+ bool Insert(Bucket* bucket, const T& t) {
+ // Check if the key already exists
+ auto it = Find(&bucket->list_, t);
+ if (it != bucket->list_.end()) {
+ return false;
+ }
+
+ // insert to bucket
+ bucket->list_.push_back(t);
+ return true;
+ }
+
+ bool Find(Bucket* bucket, const T& t, T* ret) {
+ auto it = Find(&bucket->list_, t);
+ if (it != bucket->list_.end()) {
+ if (ret) {
+ *ret = *it;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool Erase(Bucket* bucket, const T& t, T* ret) {
+ auto it = Find(&bucket->list_, t);
+ if (it != bucket->list_.end()) {
+ if (ret) {
+ *ret = *it;
+ }
+
+ bucket->list_.erase(it);
+ return true;
+ }
+ return false;
+ }
+
+ // assert that all buckets are empty
+ void AssertEmptyBuckets() {
+#ifndef NDEBUG
+ for (size_t i = 0; i < nbuckets_; ++i) {
+ WriteLock _(&locks_[i % nlocks_]);
+ assert(buckets_[i].list_.empty());
+ }
+#endif
+ }
+
+ const uint32_t nbuckets_; // No. of buckets in the spine
+ std::unique_ptr<Bucket[]> buckets_; // Spine of the hash buckets
+ const uint32_t nlocks_; // No. of locks
+ std::unique_ptr<port::RWMutex[]> locks_; // Granular locks
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_bench.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_bench.cc
new file mode 100644
index 00000000..a1f05007
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_bench.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (found in the
+// COPYING file in the root directory) and Apache 2.0 License
+// (found in the LICENSE.Apache file in the root directory).
+//
+
+#if !defined(OS_WIN) && !defined(ROCKSDB_LITE)
+
+#ifndef GFLAGS
+#include <cstdio>
+int main() { fprintf(stderr, "Please install gflags to run tools\n"); }
+#else
+
+#include <atomic>
+#include <functional>
+#include <string>
+#include <unordered_map>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "port/port_posix.h"
+#include "rocksdb/env.h"
+#include "util/gflags_compat.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+#include "utilities/persistent_cache/hash_table.h"
+
+using std::string;
+
+DEFINE_int32(nsec, 10, "nsec");
+DEFINE_int32(nthread_write, 1, "insert %");
+DEFINE_int32(nthread_read, 1, "lookup %");
+DEFINE_int32(nthread_erase, 1, "erase %");
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// HashTableImpl interface
+//
+// Abstraction of a hash table implementation
+template <class Key, class Value>
+class HashTableImpl {
+ public:
+ virtual ~HashTableImpl() {}
+
+ virtual bool Insert(const Key& key, const Value& val) = 0;
+ virtual bool Erase(const Key& key) = 0;
+ virtual bool Lookup(const Key& key, Value* val) = 0;
+};
+
+// HashTableBenchmark
+//
+// Abstraction to test a given hash table implementation. The test mostly
+// focus on insert, lookup and erase. The test can operate in test mode and
+// benchmark mode.
+class HashTableBenchmark {
+ public:
+ explicit HashTableBenchmark(HashTableImpl<size_t, std::string>* impl,
+ const size_t sec = 10,
+ const size_t nthread_write = 1,
+ const size_t nthread_read = 1,
+ const size_t nthread_erase = 1)
+ : impl_(impl),
+ sec_(sec),
+ ninserts_(0),
+ nreads_(0),
+ nerases_(0),
+ nerases_failed_(0),
+ quit_(false) {
+ Prepop();
+
+ StartThreads(nthread_write, WriteMain);
+ StartThreads(nthread_read, ReadMain);
+ StartThreads(nthread_erase, EraseMain);
+
+ uint64_t start = NowInMillSec();
+ while (!quit_) {
+ quit_ = NowInMillSec() - start > sec_ * 1000;
+ /* sleep override */ sleep(1);
+ }
+
+ Env* env = Env::Default();
+ env->WaitForJoin();
+
+ if (sec_) {
+ printf("Result \n");
+ printf("====== \n");
+ printf("insert/sec = %f \n", ninserts_ / static_cast<double>(sec_));
+ printf("read/sec = %f \n", nreads_ / static_cast<double>(sec_));
+ printf("erases/sec = %f \n", nerases_ / static_cast<double>(sec_));
+ const uint64_t ops = ninserts_ + nreads_ + nerases_;
+ printf("ops/sec = %f \n", ops / static_cast<double>(sec_));
+ printf("erase fail = %d (%f%%)\n", static_cast<int>(nerases_failed_),
+ static_cast<float>(nerases_failed_ / nerases_ * 100));
+ printf("====== \n");
+ }
+ }
+
+ void RunWrite() {
+ while (!quit_) {
+ size_t k = insert_key_++;
+ std::string tmp(1000, k % 255);
+ bool status = impl_->Insert(k, tmp);
+ assert(status);
+ ninserts_++;
+ }
+ }
+
+ void RunRead() {
+ Random64 rgen(time(nullptr));
+ while (!quit_) {
+ std::string s;
+ size_t k = rgen.Next() % max_prepop_key;
+ bool status = impl_->Lookup(k, &s);
+ assert(status);
+ assert(s == std::string(1000, k % 255));
+ nreads_++;
+ }
+ }
+
+ void RunErase() {
+ while (!quit_) {
+ size_t k = erase_key_++;
+ bool status = impl_->Erase(k);
+ nerases_failed_ += !status;
+ nerases_++;
+ }
+ }
+
+ private:
+ // Start threads for a given function
+ void StartThreads(const size_t n, void (*fn)(void*)) {
+ Env* env = Env::Default();
+ for (size_t i = 0; i < n; ++i) {
+ env->StartThread(fn, this);
+ }
+ }
+
+ // Prepop the hash table with 1M keys
+ void Prepop() {
+ for (size_t i = 0; i < max_prepop_key; ++i) {
+ bool status = impl_->Insert(i, std::string(1000, i % 255));
+ assert(status);
+ }
+
+ erase_key_ = insert_key_ = max_prepop_key;
+
+ for (size_t i = 0; i < 10 * max_prepop_key; ++i) {
+ bool status = impl_->Insert(insert_key_++, std::string(1000, 'x'));
+ assert(status);
+ }
+ }
+
+ static uint64_t NowInMillSec() {
+ timeval tv;
+ gettimeofday(&tv, /*tz=*/nullptr);
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ }
+
+ //
+ // Wrapper functions for thread entry
+ //
+ static void WriteMain(void* args) {
+ reinterpret_cast<HashTableBenchmark*>(args)->RunWrite();
+ }
+
+ static void ReadMain(void* args) {
+ reinterpret_cast<HashTableBenchmark*>(args)->RunRead();
+ }
+
+ static void EraseMain(void* args) {
+ reinterpret_cast<HashTableBenchmark*>(args)->RunErase();
+ }
+
+ HashTableImpl<size_t, std::string>* impl_; // Implementation to test
+ const size_t sec_; // Test time
+ const size_t max_prepop_key = 1ULL * 1024 * 1024; // Max prepop key
+ std::atomic<size_t> insert_key_; // Last inserted key
+ std::atomic<size_t> erase_key_; // Erase key
+ std::atomic<size_t> ninserts_; // Number of inserts
+ std::atomic<size_t> nreads_; // Number of reads
+ std::atomic<size_t> nerases_; // Number of erases
+ std::atomic<size_t> nerases_failed_; // Number of erases failed
+ bool quit_; // Should the threads quit ?
+};
+
+//
+// SimpleImpl
+// Lock safe unordered_map implementation
+class SimpleImpl : public HashTableImpl<size_t, string> {
+ public:
+ bool Insert(const size_t& key, const string& val) override {
+ WriteLock _(&rwlock_);
+ map_.insert(make_pair(key, val));
+ return true;
+ }
+
+ bool Erase(const size_t& key) override {
+ WriteLock _(&rwlock_);
+ auto it = map_.find(key);
+ if (it == map_.end()) {
+ return false;
+ }
+ map_.erase(it);
+ return true;
+ }
+
+ bool Lookup(const size_t& key, string* val) override {
+ ReadLock _(&rwlock_);
+ auto it = map_.find(key);
+ if (it != map_.end()) {
+ *val = it->second;
+ }
+ return it != map_.end();
+ }
+
+ private:
+ port::RWMutex rwlock_;
+ std::unordered_map<size_t, string> map_;
+};
+
+//
+// GranularLockImpl
+// Thread safe custom RocksDB implementation of hash table with granular
+// locking
+class GranularLockImpl : public HashTableImpl<size_t, string> {
+ public:
+ bool Insert(const size_t& key, const string& val) override {
+ Node n(key, val);
+ return impl_.Insert(n);
+ }
+
+ bool Erase(const size_t& key) override {
+ Node n(key, string());
+ return impl_.Erase(n, nullptr);
+ }
+
+ bool Lookup(const size_t& key, string* val) override {
+ Node n(key, string());
+ port::RWMutex* rlock;
+ bool status = impl_.Find(n, &n, &rlock);
+ if (status) {
+ ReadUnlock _(rlock);
+ *val = n.val_;
+ }
+ return status;
+ }
+
+ private:
+ struct Node {
+ explicit Node(const size_t key, const string& val) : key_(key), val_(val) {}
+
+ size_t key_ = 0;
+ string val_;
+ };
+
+ struct Hash {
+ uint64_t operator()(const Node& node) {
+ return std::hash<uint64_t>()(node.key_);
+ }
+ };
+
+ struct Equal {
+ bool operator()(const Node& lhs, const Node& rhs) {
+ return lhs.key_ == rhs.key_;
+ }
+ };
+
+ HashTable<Node, Hash, Equal> impl_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+//
+// main
+//
+int main(int argc, char** argv) {
+ GFLAGS_NAMESPACE::SetUsageMessage(std::string("\nUSAGE:\n") +
+ std::string(argv[0]) + " [OPTIONS]...");
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, false);
+
+ //
+ // Micro benchmark unordered_map
+ //
+ printf("Micro benchmarking std::unordered_map \n");
+ {
+ ROCKSDB_NAMESPACE::SimpleImpl impl;
+ ROCKSDB_NAMESPACE::HashTableBenchmark _(
+ &impl, FLAGS_nsec, FLAGS_nthread_write, FLAGS_nthread_read,
+ FLAGS_nthread_erase);
+ }
+ //
+ // Micro benchmark scalable hash table
+ //
+ printf("Micro benchmarking scalable hash map \n");
+ {
+ ROCKSDB_NAMESPACE::GranularLockImpl impl;
+ ROCKSDB_NAMESPACE::HashTableBenchmark _(
+ &impl, FLAGS_nsec, FLAGS_nthread_write, FLAGS_nthread_read,
+ FLAGS_nthread_erase);
+ }
+
+ return 0;
+}
+#endif // #ifndef GFLAGS
+#else
+int main(int /*argc*/, char** /*argv*/) { return 0; }
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_evictable.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_evictable.h
new file mode 100644
index 00000000..d27205d0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_evictable.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+
+#include "util/random.h"
+#include "utilities/persistent_cache/hash_table.h"
+#include "utilities/persistent_cache/lrulist.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Evictable Hash Table
+//
+// Hash table index where least accessed (or one of the least accessed) elements
+// can be evicted.
+//
+// Please note EvictableHashTable can only be created for pointer type objects
+template <class T, class Hash, class Equal>
+class EvictableHashTable : private HashTable<T*, Hash, Equal> {
+ public:
+ typedef HashTable<T*, Hash, Equal> hash_table;
+
+ explicit EvictableHashTable(const size_t capacity = 1024 * 1024,
+ const float load_factor = 2.0,
+ const uint32_t nlocks = 256)
+ : HashTable<T*, Hash, Equal>(capacity, load_factor, nlocks),
+ lru_lists_(new LRUList<T>[hash_table::nlocks_]) {
+ assert(lru_lists_);
+ }
+
+ virtual ~EvictableHashTable() { AssertEmptyLRU(); }
+
+ //
+ // Insert given record to hash table (and LRU list)
+ //
+ bool Insert(T* t) {
+ const uint64_t h = Hash()(t);
+ typename hash_table::Bucket& bucket = GetBucket(h);
+ LRUListType& lru = GetLRUList(h);
+ port::RWMutex& lock = GetMutex(h);
+
+ WriteLock _(&lock);
+ if (hash_table::Insert(&bucket, t)) {
+ lru.Push(t);
+ return true;
+ }
+ return false;
+ }
+
+ //
+ // Lookup hash table
+ //
+ // Please note that read lock should be held by the caller. This is because
+ // the caller owns the data, and should hold the read lock as long as he
+ // operates on the data.
+ bool Find(T* t, T** ret) {
+ const uint64_t h = Hash()(t);
+ typename hash_table::Bucket& bucket = GetBucket(h);
+ LRUListType& lru = GetLRUList(h);
+ port::RWMutex& lock = GetMutex(h);
+
+ ReadLock _(&lock);
+ if (hash_table::Find(&bucket, t, ret)) {
+ ++(*ret)->refs_;
+ lru.Touch(*ret);
+ return true;
+ }
+ return false;
+ }
+
+ //
+ // Evict one of the least recently used object
+ //
+ T* Evict(const std::function<void(T*)>& fn = nullptr) {
+ uint32_t random = Random::GetTLSInstance()->Next();
+ const size_t start_idx = random % hash_table::nlocks_;
+ T* t = nullptr;
+
+ // iterate from start_idx .. 0 .. start_idx
+ for (size_t i = 0; !t && i < hash_table::nlocks_; ++i) {
+ const size_t idx = (start_idx + i) % hash_table::nlocks_;
+
+ WriteLock _(&hash_table::locks_[idx]);
+ LRUListType& lru = lru_lists_[idx];
+ if (!lru.IsEmpty() && (t = lru.Pop()) != nullptr) {
+ assert(!t->refs_);
+ // We got an item to evict, erase from the bucket
+ const uint64_t h = Hash()(t);
+ typename hash_table::Bucket& bucket = GetBucket(h);
+ T* tmp = nullptr;
+ bool status = hash_table::Erase(&bucket, t, &tmp);
+ assert(t == tmp);
+ (void)status;
+ assert(status);
+ if (fn) {
+ fn(t);
+ }
+ break;
+ }
+ assert(!t);
+ }
+ return t;
+ }
+
+ void Clear(void (*fn)(T*)) {
+ for (uint32_t i = 0; i < hash_table::nbuckets_; ++i) {
+ const uint32_t lock_idx = i % hash_table::nlocks_;
+ WriteLock _(&hash_table::locks_[lock_idx]);
+ auto& lru_list = lru_lists_[lock_idx];
+ auto& bucket = hash_table::buckets_[i];
+ for (auto* t : bucket.list_) {
+ lru_list.Unlink(t);
+ (*fn)(t);
+ }
+ bucket.list_.clear();
+ }
+ // make sure that all LRU lists are emptied
+ AssertEmptyLRU();
+ }
+
+ void AssertEmptyLRU() {
+#ifndef NDEBUG
+ for (uint32_t i = 0; i < hash_table::nlocks_; ++i) {
+ WriteLock _(&hash_table::locks_[i]);
+ auto& lru_list = lru_lists_[i];
+ assert(lru_list.IsEmpty());
+ }
+#endif
+ }
+
+ //
+ // Fetch the mutex associated with a key
+ // This call is used to hold the lock for a given data for extended period of
+ // time.
+ port::RWMutex* GetMutex(T* t) { return hash_table::GetMutex(t); }
+
+ private:
+ typedef LRUList<T> LRUListType;
+
+ typename hash_table::Bucket& GetBucket(const uint64_t h) {
+ const uint32_t bucket_idx = h % hash_table::nbuckets_;
+ return hash_table::buckets_[bucket_idx];
+ }
+
+ LRUListType& GetLRUList(const uint64_t h) {
+ const uint32_t bucket_idx = h % hash_table::nbuckets_;
+ const uint32_t lock_idx = bucket_idx % hash_table::nlocks_;
+ return lru_lists_[lock_idx];
+ }
+
+ port::RWMutex& GetMutex(const uint64_t h) {
+ const uint32_t bucket_idx = h % hash_table::nbuckets_;
+ const uint32_t lock_idx = bucket_idx % hash_table::nlocks_;
+ return hash_table::locks_[lock_idx];
+ }
+
+ std::unique_ptr<LRUListType[]> lru_lists_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_test.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_test.cc
new file mode 100644
index 00000000..62a5b1d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/hash_table_test.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdlib.h>
+#include <iostream>
+#include <set>
+#include <string>
+
+#include "db/db_test_util.h"
+#include "memory/arena.h"
+#include "test_util/testharness.h"
+#include "util/random.h"
+#include "utilities/persistent_cache/hash_table.h"
+#include "utilities/persistent_cache/hash_table_evictable.h"
+
+#ifndef ROCKSDB_LITE
+
+namespace ROCKSDB_NAMESPACE {
+
+struct HashTableTest : public testing::Test {
+ ~HashTableTest() override { map_.Clear(&HashTableTest::ClearNode); }
+
+ struct Node {
+ Node() {}
+ explicit Node(const uint64_t key, const std::string& val = std::string())
+ : key_(key), val_(val) {}
+
+ uint64_t key_ = 0;
+ std::string val_;
+ };
+
+ struct Equal {
+ bool operator()(const Node& lhs, const Node& rhs) {
+ return lhs.key_ == rhs.key_;
+ }
+ };
+
+ struct Hash {
+ uint64_t operator()(const Node& node) {
+ return std::hash<uint64_t>()(node.key_);
+ }
+ };
+
+ static void ClearNode(Node /*node*/) {}
+
+ HashTable<Node, Hash, Equal> map_;
+};
+
+struct EvictableHashTableTest : public testing::Test {
+ ~EvictableHashTableTest() override {
+ map_.Clear(&EvictableHashTableTest::ClearNode);
+ }
+
+ struct Node : LRUElement<Node> {
+ Node() {}
+ explicit Node(const uint64_t key, const std::string& val = std::string())
+ : key_(key), val_(val) {}
+
+ uint64_t key_ = 0;
+ std::string val_;
+ std::atomic<uint32_t> refs_{0};
+ };
+
+ struct Equal {
+ bool operator()(const Node* lhs, const Node* rhs) {
+ return lhs->key_ == rhs->key_;
+ }
+ };
+
+ struct Hash {
+ uint64_t operator()(const Node* node) {
+ return std::hash<uint64_t>()(node->key_);
+ }
+ };
+
+ static void ClearNode(Node* /*node*/) {}
+
+ EvictableHashTable<Node, Hash, Equal> map_;
+};
+
+TEST_F(HashTableTest, TestInsert) {
+ const uint64_t max_keys = 1024 * 1024;
+
+ // insert
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ map_.Insert(Node(k, std::string(1000, k % 255)));
+ }
+
+ // verify
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ Node val;
+ port::RWMutex* rlock = nullptr;
+ assert(map_.Find(Node(k), &val, &rlock));
+ rlock->ReadUnlock();
+ assert(val.val_ == std::string(1000, k % 255));
+ }
+}
+
+TEST_F(HashTableTest, TestErase) {
+ const uint64_t max_keys = 1024 * 1024;
+ // insert
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ map_.Insert(Node(k, std::string(1000, k % 255)));
+ }
+
+ auto rand = Random64(time(nullptr));
+ // erase a few keys randomly
+ std::set<uint64_t> erased;
+ for (int i = 0; i < 1024; ++i) {
+ uint64_t k = rand.Next() % max_keys;
+ if (erased.find(k) != erased.end()) {
+ continue;
+ }
+ assert(map_.Erase(Node(k), /*ret=*/nullptr));
+ erased.insert(k);
+ }
+
+ // verify
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ Node val;
+ port::RWMutex* rlock = nullptr;
+ bool status = map_.Find(Node(k), &val, &rlock);
+ if (erased.find(k) == erased.end()) {
+ assert(status);
+ rlock->ReadUnlock();
+ assert(val.val_ == std::string(1000, k % 255));
+ } else {
+ assert(!status);
+ }
+ }
+}
+
+TEST_F(EvictableHashTableTest, TestEvict) {
+ const uint64_t max_keys = 1024 * 1024;
+
+ // insert
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ map_.Insert(new Node(k, std::string(1000, k % 255)));
+ }
+
+ // verify
+ for (uint64_t k = 0; k < max_keys; ++k) {
+ Node* val = map_.Evict();
+ // unfortunately we can't predict eviction value since it is from any one of
+ // the lock stripe
+ assert(val);
+ assert(val->val_ == std::string(1000, val->key_ % 255));
+ delete val;
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/lrulist.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/lrulist.h
new file mode 100644
index 00000000..a608890f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/lrulist.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <atomic>
+
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// LRU element definition
+//
+// Any object that needs to be part of the LRU algorithm should extend this
+// class
+template <class T>
+struct LRUElement {
+ explicit LRUElement() : next_(nullptr), prev_(nullptr), refs_(0) {}
+
+ virtual ~LRUElement() { assert(!refs_); }
+
+ T* next_;
+ T* prev_;
+ std::atomic<size_t> refs_;
+};
+
+// LRU implementation
+//
+// In place LRU implementation. There is no copy or allocation involved when
+// inserting or removing an element. This makes the data structure slim
+template <class T>
+class LRUList {
+ public:
+ virtual ~LRUList() {
+ MutexLock _(&lock_);
+ assert(!head_);
+ assert(!tail_);
+ }
+
+ // Push element into the LRU at the cold end
+ inline void Push(T* const t) {
+ assert(t);
+ assert(!t->next_);
+ assert(!t->prev_);
+
+ MutexLock _(&lock_);
+
+ assert((!head_ && !tail_) || (head_ && tail_));
+ assert(!head_ || !head_->prev_);
+ assert(!tail_ || !tail_->next_);
+
+ t->next_ = head_;
+ if (head_) {
+ head_->prev_ = t;
+ }
+
+ head_ = t;
+ if (!tail_) {
+ tail_ = t;
+ }
+ }
+
+ // Unlink the element from the LRU
+ inline void Unlink(T* const t) {
+ MutexLock _(&lock_);
+ UnlinkImpl(t);
+ }
+
+ // Evict an element from the LRU
+ inline T* Pop() {
+ MutexLock _(&lock_);
+
+ assert(tail_ && head_);
+ assert(!tail_->next_);
+ assert(!head_->prev_);
+
+ T* t = head_;
+ while (t && t->refs_) {
+ t = t->next_;
+ }
+
+ if (!t) {
+ // nothing can be evicted
+ return nullptr;
+ }
+
+ assert(!t->refs_);
+
+ // unlike the element
+ UnlinkImpl(t);
+ return t;
+ }
+
+ // Move the element from the front of the list to the back of the list
+ inline void Touch(T* const t) {
+ MutexLock _(&lock_);
+ UnlinkImpl(t);
+ PushBackImpl(t);
+ }
+
+ // Check if the LRU is empty
+ inline bool IsEmpty() const {
+ MutexLock _(&lock_);
+ return !head_ && !tail_;
+ }
+
+ private:
+ // Unlink an element from the LRU
+ void UnlinkImpl(T* const t) {
+ assert(t);
+
+ lock_.AssertHeld();
+
+ assert(head_ && tail_);
+ assert(t->prev_ || head_ == t);
+ assert(t->next_ || tail_ == t);
+
+ if (t->prev_) {
+ t->prev_->next_ = t->next_;
+ }
+ if (t->next_) {
+ t->next_->prev_ = t->prev_;
+ }
+
+ if (tail_ == t) {
+ tail_ = tail_->prev_;
+ }
+ if (head_ == t) {
+ head_ = head_->next_;
+ }
+
+ t->next_ = t->prev_ = nullptr;
+ }
+
+ // Insert an element at the hot end
+ inline void PushBack(T* const t) {
+ MutexLock _(&lock_);
+ PushBackImpl(t);
+ }
+
+ inline void PushBackImpl(T* const t) {
+ assert(t);
+ assert(!t->next_);
+ assert(!t->prev_);
+
+ lock_.AssertHeld();
+
+ assert((!head_ && !tail_) || (head_ && tail_));
+ assert(!head_ || !head_->prev_);
+ assert(!tail_ || !tail_->next_);
+
+ t->prev_ = tail_;
+ if (tail_) {
+ tail_->next_ = t;
+ }
+
+ tail_ = t;
+ if (!head_) {
+ head_ = tail_;
+ }
+ }
+
+ mutable port::Mutex lock_; // synchronization primitive
+ T* head_ = nullptr; // front (cold)
+ T* tail_ = nullptr; // back (hot)
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_bench.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_bench.cc
new file mode 100644
index 00000000..359fcdd1
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_bench.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#ifndef GFLAGS
+#include <cstdio>
+int main() { fprintf(stderr, "Please install gflags to run tools\n"); }
+#else
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <sstream>
+#include <unordered_map>
+
+#include "rocksdb/env.h"
+
+#include "utilities/persistent_cache/block_cache_tier.h"
+#include "utilities/persistent_cache/persistent_cache_tier.h"
+#include "utilities/persistent_cache/volatile_tier_impl.h"
+
+#include "monitoring/histogram.h"
+#include "port/port.h"
+#include "table/block_based/block_builder.h"
+#include "util/gflags_compat.h"
+#include "util/mutexlock.h"
+#include "util/stop_watch.h"
+
+DEFINE_int32(nsec, 10, "nsec");
+DEFINE_int32(nthread_write, 1, "Insert threads");
+DEFINE_int32(nthread_read, 1, "Lookup threads");
+DEFINE_string(path, "/tmp/microbench/blkcache", "Path for cachefile");
+DEFINE_string(log_path, "/tmp/log", "Path for the log file");
+DEFINE_uint64(cache_size, std::numeric_limits<uint64_t>::max(), "Cache size");
+DEFINE_int32(iosize, 4 * 1024, "Read IO size");
+DEFINE_int32(writer_iosize, 4 * 1024, "File writer IO size");
+DEFINE_int32(writer_qdepth, 1, "File writer qdepth");
+DEFINE_bool(enable_pipelined_writes, false, "Enable async writes");
+DEFINE_string(cache_type, "block_cache",
+ "Cache type. (block_cache, volatile, tiered)");
+DEFINE_bool(benchmark, false, "Benchmark mode");
+DEFINE_int32(volatile_cache_pct, 10, "Percentage of cache in memory tier.");
+
+namespace ROCKSDB_NAMESPACE {
+
+std::unique_ptr<PersistentCacheTier> NewVolatileCache() {
+ assert(FLAGS_cache_size != std::numeric_limits<uint64_t>::max());
+ std::unique_ptr<PersistentCacheTier> pcache(
+ new VolatileCacheTier(FLAGS_cache_size));
+ return pcache;
+}
+
+std::unique_ptr<PersistentCacheTier> NewBlockCache() {
+ std::shared_ptr<Logger> log;
+ if (!Env::Default()->NewLogger(FLAGS_log_path, &log).ok()) {
+ fprintf(stderr, "Error creating log %s \n", FLAGS_log_path.c_str());
+ return nullptr;
+ }
+
+ PersistentCacheConfig opt(Env::Default(), FLAGS_path, FLAGS_cache_size, log);
+ opt.writer_dispatch_size = FLAGS_writer_iosize;
+ opt.writer_qdepth = FLAGS_writer_qdepth;
+ opt.pipeline_writes = FLAGS_enable_pipelined_writes;
+ opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
+ std::unique_ptr<PersistentCacheTier> cache(new BlockCacheTier(opt));
+ Status status = cache->Open();
+ return cache;
+}
+
+// create a new cache tier
+// construct a tiered RAM+Block cache
+std::unique_ptr<PersistentTieredCache> NewTieredCache(
+ const size_t mem_size, const PersistentCacheConfig& opt) {
+ std::unique_ptr<PersistentTieredCache> tcache(new PersistentTieredCache());
+ // create primary tier
+ assert(mem_size);
+ auto pcache =
+ std::shared_ptr<PersistentCacheTier>(new VolatileCacheTier(mem_size));
+ tcache->AddTier(pcache);
+ // create secondary tier
+ auto scache = std::shared_ptr<PersistentCacheTier>(new BlockCacheTier(opt));
+ tcache->AddTier(scache);
+
+ Status s = tcache->Open();
+ assert(s.ok());
+ return tcache;
+}
+
+std::unique_ptr<PersistentTieredCache> NewTieredCache() {
+ std::shared_ptr<Logger> log;
+ if (!Env::Default()->NewLogger(FLAGS_log_path, &log).ok()) {
+ fprintf(stderr, "Error creating log %s \n", FLAGS_log_path.c_str());
+ abort();
+ }
+
+ auto pct = FLAGS_volatile_cache_pct / static_cast<double>(100);
+ PersistentCacheConfig opt(Env::Default(), FLAGS_path,
+ (1 - pct) * FLAGS_cache_size, log);
+ opt.writer_dispatch_size = FLAGS_writer_iosize;
+ opt.writer_qdepth = FLAGS_writer_qdepth;
+ opt.pipeline_writes = FLAGS_enable_pipelined_writes;
+ opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
+ return NewTieredCache(FLAGS_cache_size * pct, opt);
+}
+
+//
+// Benchmark driver
+//
+class CacheTierBenchmark {
+ public:
+ explicit CacheTierBenchmark(std::shared_ptr<PersistentCacheTier>&& cache)
+ : cache_(cache) {
+ if (FLAGS_nthread_read) {
+ fprintf(stdout, "Pre-populating\n");
+ Prepop();
+ fprintf(stdout, "Pre-population completed\n");
+ }
+
+ stats_.Clear();
+
+ // Start IO threads
+ std::list<port::Thread> threads;
+ Spawn(FLAGS_nthread_write, &threads,
+ std::bind(&CacheTierBenchmark::Write, this));
+ Spawn(FLAGS_nthread_read, &threads,
+ std::bind(&CacheTierBenchmark::Read, this));
+
+ // Wait till FLAGS_nsec and then signal to quit
+ StopWatchNano t(Env::Default(), /*auto_start=*/true);
+ size_t sec = t.ElapsedNanos() / 1000000000ULL;
+ while (!quit_) {
+ sec = t.ElapsedNanos() / 1000000000ULL;
+ quit_ = sec > size_t(FLAGS_nsec);
+ /* sleep override */ sleep(1);
+ }
+
+ // Wait for threads to exit
+ Join(&threads);
+ // Print stats
+ PrintStats(sec);
+ // Close the cache
+ cache_->TEST_Flush();
+ cache_->Close();
+ }
+
+ private:
+ void PrintStats(const size_t sec) {
+ std::ostringstream msg;
+ msg << "Test stats" << std::endl
+ << "* Elapsed: " << sec << " s" << std::endl
+ << "* Write Latency:" << std::endl
+ << stats_.write_latency_.ToString() << std::endl
+ << "* Read Latency:" << std::endl
+ << stats_.read_latency_.ToString() << std::endl
+ << "* Bytes written:" << std::endl
+ << stats_.bytes_written_.ToString() << std::endl
+ << "* Bytes read:" << std::endl
+ << stats_.bytes_read_.ToString() << std::endl
+ << "Cache stats:" << std::endl
+ << cache_->PrintStats() << std::endl;
+ fprintf(stderr, "%s\n", msg.str().c_str());
+ }
+
+ //
+ // Insert implementation and corresponding helper functions
+ //
+ void Prepop() {
+ for (uint64_t i = 0; i < 1024 * 1024; ++i) {
+ InsertKey(i);
+ insert_key_limit_++;
+ read_key_limit_++;
+ }
+
+ // Wait until data is flushed
+ cache_->TEST_Flush();
+ // warmup the cache
+ for (uint64_t i = 0; i < 1024 * 1024; ReadKey(i++)) {
+ }
+ }
+
+ void Write() {
+ while (!quit_) {
+ InsertKey(insert_key_limit_++);
+ }
+ }
+
+ void InsertKey(const uint64_t key) {
+ // construct key
+ uint64_t k[3];
+ Slice block_key = FillKey(k, key);
+
+ // construct value
+ auto block = NewBlock(key);
+
+ // insert
+ StopWatchNano timer(Env::Default(), /*auto_start=*/true);
+ while (true) {
+ Status status = cache_->Insert(block_key, block.get(), FLAGS_iosize);
+ if (status.ok()) {
+ break;
+ }
+
+ // transient error is possible if we run without pipelining
+ assert(!FLAGS_enable_pipelined_writes);
+ }
+
+ // adjust stats
+ const size_t elapsed_micro = timer.ElapsedNanos() / 1000;
+ stats_.write_latency_.Add(elapsed_micro);
+ stats_.bytes_written_.Add(FLAGS_iosize);
+ }
+
+ //
+ // Read implementation
+ //
+ void Read() {
+ while (!quit_) {
+ ReadKey(random() % read_key_limit_);
+ }
+ }
+
+ void ReadKey(const uint64_t val) {
+ // construct key
+ uint64_t k[3];
+ Slice key = FillKey(k, val);
+
+ // Lookup in cache
+ StopWatchNano timer(Env::Default(), /*auto_start=*/true);
+ std::unique_ptr<char[]> block;
+ size_t size;
+ Status status = cache_->Lookup(key, &block, &size);
+ if (!status.ok()) {
+ fprintf(stderr, "%s\n", status.ToString().c_str());
+ }
+ assert(status.ok());
+ assert(size == (size_t) FLAGS_iosize);
+
+ // adjust stats
+ const size_t elapsed_micro = timer.ElapsedNanos() / 1000;
+ stats_.read_latency_.Add(elapsed_micro);
+ stats_.bytes_read_.Add(FLAGS_iosize);
+
+ // verify content
+ if (!FLAGS_benchmark) {
+ auto expected_block = NewBlock(val);
+ assert(memcmp(block.get(), expected_block.get(), FLAGS_iosize) == 0);
+ }
+ }
+
+ // create data for a key by filling with a certain pattern
+ std::unique_ptr<char[]> NewBlock(const uint64_t val) {
+ std::unique_ptr<char[]> data(new char[FLAGS_iosize]);
+ memset(data.get(), val % 255, FLAGS_iosize);
+ return data;
+ }
+
+ // spawn threads
+ void Spawn(const size_t n, std::list<port::Thread>* threads,
+ const std::function<void()>& fn) {
+ for (size_t i = 0; i < n; ++i) {
+ threads->emplace_back(fn);
+ }
+ }
+
+ // join threads
+ void Join(std::list<port::Thread>* threads) {
+ for (auto& th : *threads) {
+ th.join();
+ }
+ }
+
+ // construct key
+ Slice FillKey(uint64_t (&k)[3], const uint64_t val) {
+ k[0] = k[1] = 0;
+ k[2] = val;
+ void* p = static_cast<void*>(&k);
+ return Slice(static_cast<char*>(p), sizeof(k));
+ }
+
+ // benchmark stats
+ struct Stats {
+ void Clear() {
+ bytes_written_.Clear();
+ bytes_read_.Clear();
+ read_latency_.Clear();
+ write_latency_.Clear();
+ }
+
+ HistogramImpl bytes_written_;
+ HistogramImpl bytes_read_;
+ HistogramImpl read_latency_;
+ HistogramImpl write_latency_;
+ };
+
+ std::shared_ptr<PersistentCacheTier> cache_; // cache implementation
+ std::atomic<uint64_t> insert_key_limit_{0}; // data inserted upto
+ std::atomic<uint64_t> read_key_limit_{0}; // data can be read safely upto
+ bool quit_ = false; // Quit thread ?
+ mutable Stats stats_; // Stats
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+//
+// main
+//
+int main(int argc, char** argv) {
+ GFLAGS_NAMESPACE::SetUsageMessage(std::string("\nUSAGE:\n") +
+ std::string(argv[0]) + " [OPTIONS]...");
+ GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, false);
+
+ std::ostringstream msg;
+ msg << "Config" << std::endl
+ << "======" << std::endl
+ << "* nsec=" << FLAGS_nsec << std::endl
+ << "* nthread_write=" << FLAGS_nthread_write << std::endl
+ << "* path=" << FLAGS_path << std::endl
+ << "* cache_size=" << FLAGS_cache_size << std::endl
+ << "* iosize=" << FLAGS_iosize << std::endl
+ << "* writer_iosize=" << FLAGS_writer_iosize << std::endl
+ << "* writer_qdepth=" << FLAGS_writer_qdepth << std::endl
+ << "* enable_pipelined_writes=" << FLAGS_enable_pipelined_writes
+ << std::endl
+ << "* cache_type=" << FLAGS_cache_type << std::endl
+ << "* benchmark=" << FLAGS_benchmark << std::endl
+ << "* volatile_cache_pct=" << FLAGS_volatile_cache_pct << std::endl;
+
+ fprintf(stderr, "%s\n", msg.str().c_str());
+
+ std::shared_ptr<ROCKSDB_NAMESPACE::PersistentCacheTier> cache;
+ if (FLAGS_cache_type == "block_cache") {
+ fprintf(stderr, "Using block cache implementation\n");
+ cache = ROCKSDB_NAMESPACE::NewBlockCache();
+ } else if (FLAGS_cache_type == "volatile") {
+ fprintf(stderr, "Using volatile cache implementation\n");
+ cache = ROCKSDB_NAMESPACE::NewVolatileCache();
+ } else if (FLAGS_cache_type == "tiered") {
+ fprintf(stderr, "Using tiered cache implementation\n");
+ cache = ROCKSDB_NAMESPACE::NewTieredCache();
+ } else {
+ fprintf(stderr, "Unknown option for cache\n");
+ }
+
+ assert(cache);
+ if (!cache) {
+ fprintf(stderr, "Error creating cache\n");
+ abort();
+ }
+
+ std::unique_ptr<ROCKSDB_NAMESPACE::CacheTierBenchmark> benchmark(
+ new ROCKSDB_NAMESPACE::CacheTierBenchmark(std::move(cache)));
+
+ return 0;
+}
+#endif // #ifndef GFLAGS
+#else
+int main(int, char**) { return 0; }
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.cc
new file mode 100644
index 00000000..dce6e08e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.cc
@@ -0,0 +1,474 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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.
+
+// GetUniqueIdFromFile is not implemented on Windows. Persistent cache
+// breaks when that function is not implemented
+#if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+
+#include "utilities/persistent_cache/persistent_cache_test.h"
+
+#include <functional>
+#include <memory>
+#include <thread>
+
+#include "utilities/persistent_cache/block_cache_tier.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+static const double kStressFactor = .125;
+
+#ifdef OS_LINUX
+static void OnOpenForRead(void* arg) {
+ int* val = static_cast<int*>(arg);
+ *val &= ~O_DIRECT;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewRandomAccessFile:O_DIRECT",
+ std::bind(OnOpenForRead, std::placeholders::_1));
+}
+
+static void OnOpenForWrite(void* arg) {
+ int* val = static_cast<int*>(arg);
+ *val &= ~O_DIRECT;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewWritableFile:O_DIRECT",
+ std::bind(OnOpenForWrite, std::placeholders::_1));
+}
+#endif
+
+static void RemoveDirectory(const std::string& folder) {
+ std::vector<std::string> files;
+ Status status = Env::Default()->GetChildren(folder, &files);
+ if (!status.ok()) {
+ // we assume the directory does not exist
+ return;
+ }
+
+ // cleanup files with the patter :digi:.rc
+ for (auto file : files) {
+ if (file == "." || file == "..") {
+ continue;
+ }
+ status = Env::Default()->DeleteFile(folder + "/" + file);
+ assert(status.ok());
+ }
+
+ status = Env::Default()->DeleteDir(folder);
+ assert(status.ok());
+}
+
+static void OnDeleteDir(void* arg) {
+ char* dir = static_cast<char*>(arg);
+ RemoveDirectory(std::string(dir));
+}
+
+//
+// Simple logger that prints message on stdout
+//
+class ConsoleLogger : public Logger {
+ public:
+ using Logger::Logv;
+ ConsoleLogger() : Logger(InfoLogLevel::ERROR_LEVEL) {}
+
+ void Logv(const char* format, va_list ap) override {
+ MutexLock _(&lock_);
+ vprintf(format, ap);
+ printf("\n");
+ }
+
+ port::Mutex lock_;
+};
+
+// construct a tiered RAM+Block cache
+std::unique_ptr<PersistentTieredCache> NewTieredCache(
+ const size_t mem_size, const PersistentCacheConfig& opt) {
+ std::unique_ptr<PersistentTieredCache> tcache(new PersistentTieredCache());
+ // create primary tier
+ assert(mem_size);
+ auto pcache = std::shared_ptr<PersistentCacheTier>(new VolatileCacheTier(
+ /*is_compressed*/ true, mem_size));
+ tcache->AddTier(pcache);
+ // create secondary tier
+ auto scache = std::shared_ptr<PersistentCacheTier>(new BlockCacheTier(opt));
+ tcache->AddTier(scache);
+
+ Status s = tcache->Open();
+ assert(s.ok());
+ return tcache;
+}
+
+// create block cache
+std::unique_ptr<PersistentCacheTier> NewBlockCache(
+ Env* env, const std::string& path,
+ const uint64_t max_size = std::numeric_limits<uint64_t>::max(),
+ const bool enable_direct_writes = false) {
+ const uint32_t max_file_size = static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor);
+ auto log = std::make_shared<ConsoleLogger>();
+ PersistentCacheConfig opt(env, path, max_size, log);
+ opt.cache_file_size = max_file_size;
+ opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
+ opt.enable_direct_writes = enable_direct_writes;
+ std::unique_ptr<PersistentCacheTier> scache(new BlockCacheTier(opt));
+ Status s = scache->Open();
+ assert(s.ok());
+ return scache;
+}
+
+// create a new cache tier
+std::unique_ptr<PersistentTieredCache> NewTieredCache(
+ Env* env, const std::string& path, const uint64_t max_volatile_cache_size,
+ const uint64_t max_block_cache_size =
+ std::numeric_limits<uint64_t>::max()) {
+ const uint32_t max_file_size = static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor);
+ auto log = std::make_shared<ConsoleLogger>();
+ auto opt = PersistentCacheConfig(env, path, max_block_cache_size, log);
+ opt.cache_file_size = max_file_size;
+ opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
+ // create tier out of the two caches
+ auto cache = NewTieredCache(max_volatile_cache_size, opt);
+ return cache;
+}
+
+PersistentCacheTierTest::PersistentCacheTierTest()
+ : path_(test::PerThreadDBPath("cache_test")) {
+#ifdef OS_LINUX
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewRandomAccessFile:O_DIRECT", OnOpenForRead);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewWritableFile:O_DIRECT", OnOpenForWrite);
+#endif
+}
+
+// Block cache tests
+TEST_F(PersistentCacheTierTest, DISABLED_BlockCacheInsertWithFileCreateError) {
+ cache_ = NewBlockCache(Env::Default(), path_,
+ /*size=*/std::numeric_limits<uint64_t>::max(),
+ /*direct_writes=*/ false);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "BlockCacheTier::NewCacheFile:DeleteDir", OnDeleteDir);
+
+ RunNegativeInsertTest(/*nthreads=*/ 1,
+ /*max_keys*/
+ static_cast<size_t>(10 * 1024 * kStressFactor));
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+#if defined(TRAVIS) || defined(ROCKSDB_VALGRIND_RUN)
+// Travis is unable to handle the normal version of the tests running out of
+// fds, out of space and timeouts. This is an easier version of the test
+// specifically written for Travis
+TEST_F(PersistentCacheTierTest, BasicTest) {
+ cache_ = std::make_shared<VolatileCacheTier>();
+ RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
+
+ cache_ = NewBlockCache(Env::Default(), path_,
+ /*size=*/std::numeric_limits<uint64_t>::max(),
+ /*direct_writes=*/true);
+ RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
+
+ cache_ = NewTieredCache(Env::Default(), path_,
+ /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024));
+ RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
+}
+#else
+// Volatile cache tests
+TEST_F(PersistentCacheTierTest, VolatileCacheInsert) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys :
+ {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = std::make_shared<VolatileCacheTier>();
+ RunInsertTest(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+}
+
+TEST_F(PersistentCacheTierTest, VolatileCacheInsertWithEviction) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = std::make_shared<VolatileCacheTier>(
+ /*compressed=*/true, /*size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor));
+ RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+}
+
+// Block cache tests
+TEST_F(PersistentCacheTierTest, BlockCacheInsert) {
+ for (auto direct_writes : {true, false}) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys :
+ {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = NewBlockCache(Env::Default(), path_,
+ /*size=*/std::numeric_limits<uint64_t>::max(),
+ direct_writes);
+ RunInsertTest(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+ }
+}
+
+TEST_F(PersistentCacheTierTest, BlockCacheInsertWithEviction) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = NewBlockCache(Env::Default(), path_,
+ /*max_size=*/static_cast<size_t>(200 * 1024 * 1024 * kStressFactor));
+ RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+}
+
+// Tiered cache tests
+TEST_F(PersistentCacheTierTest, TieredCacheInsert) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys :
+ {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = NewTieredCache(Env::Default(), path_,
+ /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor));
+ RunInsertTest(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+}
+
+// the tests causes a lot of file deletions which Travis limited testing
+// environment cannot handle
+TEST_F(PersistentCacheTierTest, TieredCacheInsertWithEviction) {
+ for (auto nthreads : {1, 5}) {
+ for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
+ cache_ = NewTieredCache(
+ Env::Default(), path_,
+ /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor),
+ /*block_cache_size*/ static_cast<size_t>(200 * 1024 * 1024 * kStressFactor));
+ RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
+ }
+ }
+}
+#endif
+
+std::shared_ptr<PersistentCacheTier> MakeVolatileCache(
+ const std::string& /*dbname*/) {
+ return std::make_shared<VolatileCacheTier>();
+}
+
+std::shared_ptr<PersistentCacheTier> MakeBlockCache(const std::string& dbname) {
+ return NewBlockCache(Env::Default(), dbname);
+}
+
+std::shared_ptr<PersistentCacheTier> MakeTieredCache(
+ const std::string& dbname) {
+ const auto memory_size = 1 * 1024 * 1024 * kStressFactor;
+ return NewTieredCache(Env::Default(), dbname, static_cast<size_t>(memory_size));
+}
+
+#ifdef OS_LINUX
+static void UniqueIdCallback(void* arg) {
+ int* result = reinterpret_cast<int*>(arg);
+ if (*result == -1) {
+ *result = 0;
+ }
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
+}
+#endif
+
+TEST_F(PersistentCacheTierTest, FactoryTest) {
+ for (auto nvm_opt : {true, false}) {
+ ASSERT_FALSE(cache_);
+ auto log = std::make_shared<ConsoleLogger>();
+ std::shared_ptr<PersistentCache> cache;
+ ASSERT_OK(NewPersistentCache(Env::Default(), path_,
+ /*size=*/1 * 1024 * 1024 * 1024, log, nvm_opt,
+ &cache));
+ ASSERT_TRUE(cache);
+ ASSERT_EQ(cache->Stats().size(), 1);
+ ASSERT_TRUE(cache->Stats()[0].size());
+ cache.reset();
+ }
+}
+
+PersistentCacheDBTest::PersistentCacheDBTest() : DBTestBase("/cache_test") {
+#ifdef OS_LINUX
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "NewRandomAccessFile:O_DIRECT", OnOpenForRead);
+#endif
+}
+
+// test template
+void PersistentCacheDBTest::RunTest(
+ const std::function<std::shared_ptr<PersistentCacheTier>(bool)>& new_pcache,
+ const size_t max_keys = 100 * 1024, const size_t max_usecase = 5) {
+ if (!Snappy_Supported()) {
+ return;
+ }
+
+ // number of insertion interations
+ int num_iter = static_cast<int>(max_keys * kStressFactor);
+
+ for (size_t iter = 0; iter < max_usecase; iter++) {
+ Options options;
+ options.write_buffer_size =
+ static_cast<size_t>(64 * 1024 * kStressFactor); // small write buffer
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options = CurrentOptions(options);
+
+ // setup page cache
+ std::shared_ptr<PersistentCacheTier> pcache;
+ BlockBasedTableOptions table_options;
+ table_options.cache_index_and_filter_blocks = true;
+
+ const size_t size_max = std::numeric_limits<size_t>::max();
+
+ switch (iter) {
+ case 0:
+ // page cache, block cache, no-compressed cache
+ pcache = new_pcache(/*is_compressed=*/true);
+ table_options.persistent_cache = pcache;
+ table_options.block_cache = NewLRUCache(size_max);
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 1:
+ // page cache, block cache, compressed cache
+ pcache = new_pcache(/*is_compressed=*/true);
+ table_options.persistent_cache = pcache;
+ table_options.block_cache = NewLRUCache(size_max);
+ table_options.block_cache_compressed = NewLRUCache(size_max);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 2:
+ // page cache, block cache, compressed cache + KNoCompression
+ // both block cache and compressed cache, but DB is not compressed
+ // also, make block cache sizes bigger, to trigger block cache hits
+ pcache = new_pcache(/*is_compressed=*/true);
+ table_options.persistent_cache = pcache;
+ table_options.block_cache = NewLRUCache(size_max);
+ table_options.block_cache_compressed = NewLRUCache(size_max);
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ options.compression = kNoCompression;
+ break;
+ case 3:
+ // page cache, no block cache, no compressed cache
+ pcache = new_pcache(/*is_compressed=*/false);
+ table_options.persistent_cache = pcache;
+ table_options.block_cache = nullptr;
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ case 4:
+ // page cache, no block cache, no compressed cache
+ // Page cache caches compressed blocks
+ pcache = new_pcache(/*is_compressed=*/true);
+ table_options.persistent_cache = pcache;
+ table_options.block_cache = nullptr;
+ table_options.block_cache_compressed = nullptr;
+ options.table_factory.reset(NewBlockBasedTableFactory(table_options));
+ break;
+ default:
+ FAIL();
+ }
+
+ std::vector<std::string> values;
+ // insert data
+ Insert(options, table_options, num_iter, &values);
+ // flush all data in cache to device
+ pcache->TEST_Flush();
+ // verify data
+ Verify(num_iter, values);
+
+ auto block_miss = TestGetTickerCount(options, BLOCK_CACHE_MISS);
+ auto compressed_block_hit =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
+ auto compressed_block_miss =
+ TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS);
+ auto page_hit = TestGetTickerCount(options, PERSISTENT_CACHE_HIT);
+ auto page_miss = TestGetTickerCount(options, PERSISTENT_CACHE_MISS);
+
+ // check that we triggered the appropriate code paths in the cache
+ switch (iter) {
+ case 0:
+ // page cache, block cache, no-compressed cache
+ ASSERT_GT(page_miss, 0);
+ ASSERT_GT(page_hit, 0);
+ ASSERT_GT(block_miss, 0);
+ ASSERT_EQ(compressed_block_miss, 0);
+ ASSERT_EQ(compressed_block_hit, 0);
+ break;
+ case 1:
+ // page cache, block cache, compressed cache
+ ASSERT_GT(page_miss, 0);
+ ASSERT_GT(block_miss, 0);
+ ASSERT_GT(compressed_block_miss, 0);
+ break;
+ case 2:
+ // page cache, block cache, compressed cache + KNoCompression
+ ASSERT_GT(page_miss, 0);
+ ASSERT_GT(page_hit, 0);
+ ASSERT_GT(block_miss, 0);
+ ASSERT_GT(compressed_block_miss, 0);
+ // remember kNoCompression
+ ASSERT_EQ(compressed_block_hit, 0);
+ break;
+ case 3:
+ case 4:
+ // page cache, no block cache, no compressed cache
+ ASSERT_GT(page_miss, 0);
+ ASSERT_GT(page_hit, 0);
+ ASSERT_EQ(compressed_block_hit, 0);
+ ASSERT_EQ(compressed_block_miss, 0);
+ break;
+ default:
+ FAIL();
+ }
+
+ options.create_if_missing = true;
+ DestroyAndReopen(options);
+
+ pcache->Close();
+ }
+}
+
+#if defined(TRAVIS) || defined(ROCKSDB_VALGRIND_RUN)
+// Travis is unable to handle the normal version of the tests running out of
+// fds, out of space and timeouts. This is an easier version of the test
+// specifically written for Travis
+TEST_F(PersistentCacheDBTest, BasicTest) {
+ RunTest(std::bind(&MakeBlockCache, dbname_), /*max_keys=*/1024,
+ /*max_usecase=*/1);
+}
+#else
+// test table with block page cache
+TEST_F(PersistentCacheDBTest, BlockCacheTest) {
+ RunTest(std::bind(&MakeBlockCache, dbname_));
+}
+
+// test table with volatile page cache
+TEST_F(PersistentCacheDBTest, VolatileCacheTest) {
+ RunTest(std::bind(&MakeVolatileCache, dbname_));
+}
+
+// test table with tiered page cache
+TEST_F(PersistentCacheDBTest, TieredCacheTest) {
+ RunTest(std::bind(&MakeTieredCache, dbname_));
+}
+#endif
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+#else // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
+int main() { return 0; }
+#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.h
new file mode 100644
index 00000000..47611ecd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_test.h
@@ -0,0 +1,285 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <functional>
+#include <limits>
+#include <list>
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "db/db_test_util.h"
+#include "memory/arena.h"
+#include "port/port.h"
+#include "rocksdb/cache.h"
+#include "table/block_based/block_builder.h"
+#include "test_util/testharness.h"
+#include "utilities/persistent_cache/volatile_tier_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// Unit tests for testing PersistentCacheTier
+//
+class PersistentCacheTierTest : public testing::Test {
+ public:
+ PersistentCacheTierTest();
+ virtual ~PersistentCacheTierTest() {
+ if (cache_) {
+ Status s = cache_->Close();
+ assert(s.ok());
+ }
+ }
+
+ protected:
+ // Flush cache
+ void Flush() {
+ if (cache_) {
+ cache_->TEST_Flush();
+ }
+ }
+
+ // create threaded workload
+ template <class T>
+ std::list<port::Thread> SpawnThreads(const size_t n, const T& fn) {
+ std::list<port::Thread> threads;
+ for (size_t i = 0; i < n; i++) {
+ port::Thread th(fn);
+ threads.push_back(std::move(th));
+ }
+ return threads;
+ }
+
+ // Wait for threads to join
+ void Join(std::list<port::Thread>&& threads) {
+ for (auto& th : threads) {
+ th.join();
+ }
+ threads.clear();
+ }
+
+ // Run insert workload in threads
+ void Insert(const size_t nthreads, const size_t max_keys) {
+ key_ = 0;
+ max_keys_ = max_keys;
+ // spawn threads
+ auto fn = std::bind(&PersistentCacheTierTest::InsertImpl, this);
+ auto threads = SpawnThreads(nthreads, fn);
+ // join with threads
+ Join(std::move(threads));
+ // Flush cache
+ Flush();
+ }
+
+ // Run verification on the cache
+ void Verify(const size_t nthreads = 1, const bool eviction_enabled = false) {
+ stats_verify_hits_ = 0;
+ stats_verify_missed_ = 0;
+ key_ = 0;
+ // spawn threads
+ auto fn =
+ std::bind(&PersistentCacheTierTest::VerifyImpl, this, eviction_enabled);
+ auto threads = SpawnThreads(nthreads, fn);
+ // join with threads
+ Join(std::move(threads));
+ }
+
+ // pad 0 to numbers
+ std::string PaddedNumber(const size_t data, const size_t pad_size) {
+ assert(pad_size);
+ char* ret = new char[pad_size];
+ int pos = static_cast<int>(pad_size) - 1;
+ size_t count = 0;
+ size_t t = data;
+ // copy numbers
+ while (t) {
+ count++;
+ ret[pos--] = '0' + t % 10;
+ t = t / 10;
+ }
+ // copy 0s
+ while (pos >= 0) {
+ ret[pos--] = '0';
+ }
+ // post condition
+ assert(count <= pad_size);
+ assert(pos == -1);
+ std::string result(ret, pad_size);
+ delete[] ret;
+ return result;
+ }
+
+ // Insert workload implementation
+ void InsertImpl() {
+ const std::string prefix = "key_prefix_";
+
+ while (true) {
+ size_t i = key_++;
+ if (i >= max_keys_) {
+ break;
+ }
+
+ char data[4 * 1024];
+ memset(data, '0' + (i % 10), sizeof(data));
+ auto k = prefix + PaddedNumber(i, /*count=*/8);
+ Slice key(k);
+ while (true) {
+ Status status = cache_->Insert(key, data, sizeof(data));
+ if (status.ok()) {
+ break;
+ }
+ ASSERT_TRUE(status.IsTryAgain());
+ Env::Default()->SleepForMicroseconds(1 * 1000 * 1000);
+ }
+ }
+ }
+
+ // Verification implementation
+ void VerifyImpl(const bool eviction_enabled = false) {
+ const std::string prefix = "key_prefix_";
+ while (true) {
+ size_t i = key_++;
+ if (i >= max_keys_) {
+ break;
+ }
+
+ char edata[4 * 1024];
+ memset(edata, '0' + (i % 10), sizeof(edata));
+ auto k = prefix + PaddedNumber(i, /*count=*/8);
+ Slice key(k);
+ std::unique_ptr<char[]> block;
+ size_t block_size;
+
+ if (eviction_enabled) {
+ if (!cache_->Lookup(key, &block, &block_size).ok()) {
+ // assume that the key is evicted
+ stats_verify_missed_++;
+ continue;
+ }
+ }
+
+ ASSERT_OK(cache_->Lookup(key, &block, &block_size));
+ ASSERT_EQ(block_size, sizeof(edata));
+ ASSERT_EQ(memcmp(edata, block.get(), sizeof(edata)), 0);
+ stats_verify_hits_++;
+ }
+ }
+
+ // template for insert test
+ void RunInsertTest(const size_t nthreads, const size_t max_keys) {
+ Insert(nthreads, max_keys);
+ Verify(nthreads);
+ ASSERT_EQ(stats_verify_hits_, max_keys);
+ ASSERT_EQ(stats_verify_missed_, 0);
+
+ cache_->Close();
+ cache_.reset();
+ }
+
+ // template for negative insert test
+ void RunNegativeInsertTest(const size_t nthreads, const size_t max_keys) {
+ Insert(nthreads, max_keys);
+ Verify(nthreads, /*eviction_enabled=*/true);
+ ASSERT_LT(stats_verify_hits_, max_keys);
+ ASSERT_GT(stats_verify_missed_, 0);
+
+ cache_->Close();
+ cache_.reset();
+ }
+
+ // template for insert with eviction test
+ void RunInsertTestWithEviction(const size_t nthreads, const size_t max_keys) {
+ Insert(nthreads, max_keys);
+ Verify(nthreads, /*eviction_enabled=*/true);
+ ASSERT_EQ(stats_verify_hits_ + stats_verify_missed_, max_keys);
+ ASSERT_GT(stats_verify_hits_, 0);
+ ASSERT_GT(stats_verify_missed_, 0);
+
+ cache_->Close();
+ cache_.reset();
+ }
+
+ const std::string path_;
+ std::shared_ptr<Logger> log_;
+ std::shared_ptr<PersistentCacheTier> cache_;
+ std::atomic<size_t> key_{0};
+ size_t max_keys_ = 0;
+ std::atomic<size_t> stats_verify_hits_{0};
+ std::atomic<size_t> stats_verify_missed_{0};
+};
+
+//
+// RocksDB tests
+//
+class PersistentCacheDBTest : public DBTestBase {
+ public:
+ PersistentCacheDBTest();
+
+ static uint64_t TestGetTickerCount(const Options& options,
+ Tickers ticker_type) {
+ return static_cast<uint32_t>(
+ options.statistics->getTickerCount(ticker_type));
+ }
+
+ // insert data to table
+ void Insert(const Options& options,
+ const BlockBasedTableOptions& /*table_options*/,
+ const int num_iter, std::vector<std::string>* values) {
+ CreateAndReopenWithCF({"pikachu"}, options);
+ // default column family doesn't have block cache
+ Options no_block_cache_opts;
+ no_block_cache_opts.statistics = options.statistics;
+ no_block_cache_opts = CurrentOptions(no_block_cache_opts);
+ BlockBasedTableOptions table_options_no_bc;
+ table_options_no_bc.no_block_cache = true;
+ no_block_cache_opts.table_factory.reset(
+ NewBlockBasedTableFactory(table_options_no_bc));
+ ReopenWithColumnFamilies(
+ {"default", "pikachu"},
+ std::vector<Options>({no_block_cache_opts, options}));
+
+ Random rnd(301);
+
+ // Write 8MB (80 values, each 100K)
+ ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
+ std::string str;
+ for (int i = 0; i < num_iter; i++) {
+ if (i % 4 == 0) { // high compression ratio
+ str = RandomString(&rnd, 1000);
+ }
+ values->push_back(str);
+ ASSERT_OK(Put(1, Key(i), (*values)[i]));
+ }
+
+ // flush all data from memtable so that reads are from block cache
+ ASSERT_OK(Flush(1));
+ }
+
+ // verify data
+ void Verify(const int num_iter, const std::vector<std::string>& values) {
+ for (int j = 0; j < 2; ++j) {
+ for (int i = 0; i < num_iter; i++) {
+ ASSERT_EQ(Get(1, Key(i)), values[i]);
+ }
+ }
+ }
+
+ // test template
+ void RunTest(const std::function<std::shared_ptr<PersistentCacheTier>(bool)>&
+ new_pcache,
+ const size_t max_keys, const size_t max_usecase);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.cc
new file mode 100644
index 00000000..3847a4ee
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/persistent_cache/persistent_cache_tier.h"
+
+#include <cinttypes>
+#include <sstream>
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+std::string PersistentCacheConfig::ToString() const {
+ std::string ret;
+ ret.reserve(20000);
+ const int kBufferSize = 200;
+ char buffer[kBufferSize];
+
+ snprintf(buffer, kBufferSize, " path: %s\n", path.c_str());
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " enable_direct_reads: %d\n",
+ enable_direct_reads);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " enable_direct_writes: %d\n",
+ enable_direct_writes);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " cache_size: %" PRIu64 "\n", cache_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " cache_file_size: %" PRIu32 "\n",
+ cache_file_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " writer_qdepth: %" PRIu32 "\n",
+ writer_qdepth);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " pipeline_writes: %d\n", pipeline_writes);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize,
+ " max_write_pipeline_backlog_size: %" PRIu64 "\n",
+ max_write_pipeline_backlog_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " write_buffer_size: %" PRIu32 "\n",
+ write_buffer_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " writer_dispatch_size: %" PRIu64 "\n",
+ writer_dispatch_size);
+ ret.append(buffer);
+ snprintf(buffer, kBufferSize, " is_compressed: %d\n", is_compressed);
+ ret.append(buffer);
+
+ return ret;
+}
+
+//
+// PersistentCacheTier implementation
+//
+Status PersistentCacheTier::Open() {
+ if (next_tier_) {
+ return next_tier_->Open();
+ }
+ return Status::OK();
+}
+
+Status PersistentCacheTier::Close() {
+ if (next_tier_) {
+ return next_tier_->Close();
+ }
+ return Status::OK();
+}
+
+bool PersistentCacheTier::Reserve(const size_t /*size*/) {
+ // default implementation is a pass through
+ return true;
+}
+
+bool PersistentCacheTier::Erase(const Slice& /*key*/) {
+ // default implementation is a pass through since not all cache tiers might
+ // support erase
+ return true;
+}
+
+std::string PersistentCacheTier::PrintStats() {
+ std::ostringstream os;
+ for (auto tier_stats : Stats()) {
+ os << "---- next tier -----" << std::endl;
+ for (auto stat : tier_stats) {
+ os << stat.first << ": " << stat.second << std::endl;
+ }
+ }
+ return os.str();
+}
+
+PersistentCache::StatsType PersistentCacheTier::Stats() {
+ if (next_tier_) {
+ return next_tier_->Stats();
+ }
+ return PersistentCache::StatsType{};
+}
+
+//
+// PersistentTieredCache implementation
+//
+PersistentTieredCache::~PersistentTieredCache() { assert(tiers_.empty()); }
+
+Status PersistentTieredCache::Open() {
+ assert(!tiers_.empty());
+ return tiers_.front()->Open();
+}
+
+Status PersistentTieredCache::Close() {
+ assert(!tiers_.empty());
+ Status status = tiers_.front()->Close();
+ if (status.ok()) {
+ tiers_.clear();
+ }
+ return status;
+}
+
+bool PersistentTieredCache::Erase(const Slice& key) {
+ assert(!tiers_.empty());
+ return tiers_.front()->Erase(key);
+}
+
+PersistentCache::StatsType PersistentTieredCache::Stats() {
+ assert(!tiers_.empty());
+ return tiers_.front()->Stats();
+}
+
+std::string PersistentTieredCache::PrintStats() {
+ assert(!tiers_.empty());
+ return tiers_.front()->PrintStats();
+}
+
+Status PersistentTieredCache::Insert(const Slice& page_key, const char* data,
+ const size_t size) {
+ assert(!tiers_.empty());
+ return tiers_.front()->Insert(page_key, data, size);
+}
+
+Status PersistentTieredCache::Lookup(const Slice& page_key,
+ std::unique_ptr<char[]>* data,
+ size_t* size) {
+ assert(!tiers_.empty());
+ return tiers_.front()->Lookup(page_key, data, size);
+}
+
+void PersistentTieredCache::AddTier(const Tier& tier) {
+ if (!tiers_.empty()) {
+ tiers_.back()->set_next_tier(tier);
+ }
+ tiers_.push_back(tier);
+}
+
+bool PersistentTieredCache::IsCompressed() {
+ assert(tiers_.size());
+ return tiers_.front()->IsCompressed();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.h
new file mode 100644
index 00000000..3905957d
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_tier.h
@@ -0,0 +1,336 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <limits>
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "monitoring/histogram.h"
+#include "rocksdb/env.h"
+#include "rocksdb/persistent_cache.h"
+#include "rocksdb/status.h"
+
+// Persistent Cache
+//
+// Persistent cache is tiered key-value cache that can use persistent medium. It
+// is a generic design and can leverage any storage medium -- disk/SSD/NVM/RAM.
+// The code has been kept generic but significant benchmark/design/development
+// time has been spent to make sure the cache performs appropriately for
+// respective storage medium.
+// The file defines
+// PersistentCacheTier : Implementation that handles individual cache tier
+// PersistentTieresCache : Implementation that handles all tiers as a logical
+// unit
+//
+// PersistentTieredCache architecture:
+// +--------------------------+ PersistentCacheTier that handles multiple tiers
+// | +----------------+ |
+// | | RAM | PersistentCacheTier that handles RAM (VolatileCacheImpl)
+// | +----------------+ |
+// | | next |
+// | v |
+// | +----------------+ |
+// | | NVM | PersistentCacheTier implementation that handles NVM
+// | +----------------+ (BlockCacheImpl)
+// | | next |
+// | V |
+// | +----------------+ |
+// | | LE-SSD | PersistentCacheTier implementation that handles LE-SSD
+// | +----------------+ (BlockCacheImpl)
+// | | |
+// | V |
+// | null |
+// +--------------------------+
+// |
+// V
+// null
+namespace ROCKSDB_NAMESPACE {
+
+// Persistent Cache Config
+//
+// This struct captures all the options that are used to configure persistent
+// cache. Some of the terminologies used in naming the options are
+//
+// dispatch size :
+// This is the size in which IO is dispatched to the device
+//
+// write buffer size :
+// This is the size of an individual write buffer size. Write buffers are
+// grouped to form buffered file.
+//
+// cache size :
+// This is the logical maximum for the cache size
+//
+// qdepth :
+// This is the max number of IOs that can issues to the device in parallel
+//
+// pepeling :
+// The writer code path follows pipelined architecture, which means the
+// operations are handed off from one stage to another
+//
+// pipelining backlog size :
+// With the pipelined architecture, there can always be backlogging of ops in
+// pipeline queues. This is the maximum backlog size after which ops are dropped
+// from queue
+struct PersistentCacheConfig {
+ explicit PersistentCacheConfig(
+ Env* const _env, const std::string& _path, const uint64_t _cache_size,
+ const std::shared_ptr<Logger>& _log,
+ const uint32_t _write_buffer_size = 1 * 1024 * 1024 /*1MB*/) {
+ env = _env;
+ path = _path;
+ log = _log;
+ cache_size = _cache_size;
+ writer_dispatch_size = write_buffer_size = _write_buffer_size;
+ }
+
+ //
+ // Validate the settings. Our intentions are to catch erroneous settings ahead
+ // of time instead going violating invariants or causing dead locks.
+ //
+ Status ValidateSettings() const {
+ // (1) check pre-conditions for variables
+ if (!env || path.empty()) {
+ return Status::InvalidArgument("empty or null args");
+ }
+
+ // (2) assert size related invariants
+ // - cache size cannot be less than cache file size
+ // - individual write buffer size cannot be greater than cache file size
+ // - total write buffer size cannot be less than 2X cache file size
+ if (cache_size < cache_file_size || write_buffer_size >= cache_file_size ||
+ write_buffer_size * write_buffer_count() < 2 * cache_file_size) {
+ return Status::InvalidArgument("invalid cache size");
+ }
+
+ // (2) check writer settings
+ // - Queue depth cannot be 0
+ // - writer_dispatch_size cannot be greater than writer_buffer_size
+ // - dispatch size and buffer size need to be aligned
+ if (!writer_qdepth || writer_dispatch_size > write_buffer_size ||
+ write_buffer_size % writer_dispatch_size) {
+ return Status::InvalidArgument("invalid writer settings");
+ }
+
+ return Status::OK();
+ }
+
+ //
+ // Env abstraction to use for systmer level operations
+ //
+ Env* env;
+
+ //
+ // Path for the block cache where blocks are persisted
+ //
+ std::string path;
+
+ //
+ // Log handle for logging messages
+ //
+ std::shared_ptr<Logger> log;
+
+ //
+ // Enable direct IO for reading
+ //
+ bool enable_direct_reads = true;
+
+ //
+ // Enable direct IO for writing
+ //
+ bool enable_direct_writes = false;
+
+ //
+ // Logical cache size
+ //
+ uint64_t cache_size = std::numeric_limits<uint64_t>::max();
+
+ // cache-file-size
+ //
+ // Cache consists of multiples of small files. This parameter defines the
+ // size of an individual cache file
+ //
+ // default: 1M
+ uint32_t cache_file_size = 100ULL * 1024 * 1024;
+
+ // writer-qdepth
+ //
+ // The writers can issues IO to the devices in parallel. This parameter
+ // controls the max number if IOs that can issues in parallel to the block
+ // device
+ //
+ // default :1
+ uint32_t writer_qdepth = 1;
+
+ // pipeline-writes
+ //
+ // The write optionally follow pipelined architecture. This helps
+ // avoid regression in the eviction code path of the primary tier. This
+ // parameter defines if pipelining is enabled or disabled
+ //
+ // default: true
+ bool pipeline_writes = true;
+
+ // max-write-pipeline-backlog-size
+ //
+ // Max pipeline buffer size. This is the maximum backlog we can accumulate
+ // while waiting for writes. After the limit, new ops will be dropped.
+ //
+ // Default: 1GiB
+ uint64_t max_write_pipeline_backlog_size = 1ULL * 1024 * 1024 * 1024;
+
+ // write-buffer-size
+ //
+ // This is the size in which buffer slabs are allocated.
+ //
+ // Default: 1M
+ uint32_t write_buffer_size = 1ULL * 1024 * 1024;
+
+ // write-buffer-count
+ //
+ // This is the total number of buffer slabs. This is calculated as a factor of
+ // file size in order to avoid dead lock.
+ size_t write_buffer_count() const {
+ assert(write_buffer_size);
+ return static_cast<size_t>((writer_qdepth + 1.2) * cache_file_size /
+ write_buffer_size);
+ }
+
+ // writer-dispatch-size
+ //
+ // The writer thread will dispatch the IO at the specified IO size
+ //
+ // default: 1M
+ uint64_t writer_dispatch_size = 1ULL * 1024 * 1024;
+
+ // is_compressed
+ //
+ // This option determines if the cache will run in compressed mode or
+ // uncompressed mode
+ bool is_compressed = true;
+
+ PersistentCacheConfig MakePersistentCacheConfig(
+ const std::string& path, const uint64_t size,
+ const std::shared_ptr<Logger>& log);
+
+ std::string ToString() const;
+};
+
+// Persistent Cache Tier
+//
+// This a logical abstraction that defines a tier of the persistent cache. Tiers
+// can be stacked over one another. PersistentCahe provides the basic definition
+// for accessing/storing in the cache. PersistentCacheTier extends the interface
+// to enable management and stacking of tiers.
+class PersistentCacheTier : public PersistentCache {
+ public:
+ typedef std::shared_ptr<PersistentCacheTier> Tier;
+
+ virtual ~PersistentCacheTier() {}
+
+ // Open the persistent cache tier
+ virtual Status Open();
+
+ // Close the persistent cache tier
+ virtual Status Close();
+
+ // Reserve space up to 'size' bytes
+ virtual bool Reserve(const size_t size);
+
+ // Erase a key from the cache
+ virtual bool Erase(const Slice& key);
+
+ // Print stats to string recursively
+ virtual std::string PrintStats();
+
+ virtual PersistentCache::StatsType Stats() override;
+
+ // Insert to page cache
+ virtual Status Insert(const Slice& page_key, const char* data,
+ const size_t size) override = 0;
+
+ // Lookup page cache by page identifier
+ virtual Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
+ size_t* size) override = 0;
+
+ // Does it store compressed data ?
+ virtual bool IsCompressed() override = 0;
+
+ virtual std::string GetPrintableOptions() const override = 0;
+
+ // Return a reference to next tier
+ virtual Tier& next_tier() { return next_tier_; }
+
+ // Set the value for next tier
+ virtual void set_next_tier(const Tier& tier) {
+ assert(!next_tier_);
+ next_tier_ = tier;
+ }
+
+ virtual void TEST_Flush() {
+ if (next_tier_) {
+ next_tier_->TEST_Flush();
+ }
+ }
+
+ private:
+ Tier next_tier_; // next tier
+};
+
+// PersistentTieredCache
+//
+// Abstraction that helps you construct a tiers of persistent caches as a
+// unified cache. The tier(s) of cache will act a single tier for management
+// ease and support PersistentCache methods for accessing data.
+class PersistentTieredCache : public PersistentCacheTier {
+ public:
+ virtual ~PersistentTieredCache();
+
+ Status Open() override;
+ Status Close() override;
+ bool Erase(const Slice& key) override;
+ std::string PrintStats() override;
+ PersistentCache::StatsType Stats() override;
+ Status Insert(const Slice& page_key, const char* data,
+ const size_t size) override;
+ Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
+ size_t* size) override;
+ bool IsCompressed() override;
+
+ std::string GetPrintableOptions() const override {
+ return "PersistentTieredCache";
+ }
+
+ void AddTier(const Tier& tier);
+
+ Tier& next_tier() override {
+ auto it = tiers_.end();
+ return (*it)->next_tier();
+ }
+
+ void set_next_tier(const Tier& tier) override {
+ auto it = tiers_.end();
+ (*it)->set_next_tier(tier);
+ }
+
+ void TEST_Flush() override {
+ assert(!tiers_.empty());
+ tiers_.front()->TEST_Flush();
+ PersistentCacheTier::TEST_Flush();
+ }
+
+ protected:
+ std::list<Tier> tiers_; // list of tiers top-down
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_util.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_util.h
new file mode 100644
index 00000000..2a769652
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/persistent_cache_util.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <limits>
+#include <list>
+
+#include "util/mutexlock.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+//
+// Simple synchronized queue implementation with the option of
+// bounding the queue
+//
+// On overflow, the elements will be discarded
+//
+template <class T>
+class BoundedQueue {
+ public:
+ explicit BoundedQueue(
+ const size_t max_size = std::numeric_limits<size_t>::max())
+ : cond_empty_(&lock_), max_size_(max_size) {}
+
+ virtual ~BoundedQueue() {}
+
+ void Push(T&& t) {
+ MutexLock _(&lock_);
+ if (max_size_ != std::numeric_limits<size_t>::max() &&
+ size_ + t.Size() >= max_size_) {
+ // overflow
+ return;
+ }
+
+ size_ += t.Size();
+ q_.push_back(std::move(t));
+ cond_empty_.SignalAll();
+ }
+
+ T Pop() {
+ MutexLock _(&lock_);
+ while (q_.empty()) {
+ cond_empty_.Wait();
+ }
+
+ T t = std::move(q_.front());
+ size_ -= t.Size();
+ q_.pop_front();
+ return t;
+ }
+
+ size_t Size() const {
+ MutexLock _(&lock_);
+ return size_;
+ }
+
+ private:
+ mutable port::Mutex lock_;
+ port::CondVar cond_empty_;
+ std::list<T> q_;
+ size_t size_ = 0;
+ const size_t max_size_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.cc b/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.cc
new file mode 100644
index 00000000..ee63f828
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/persistent_cache/volatile_tier_impl.h"
+
+#include <string>
+
+namespace ROCKSDB_NAMESPACE {
+
+void VolatileCacheTier::DeleteCacheData(VolatileCacheTier::CacheData* data) {
+ assert(data);
+ delete data;
+}
+
+VolatileCacheTier::~VolatileCacheTier() { index_.Clear(&DeleteCacheData); }
+
+PersistentCache::StatsType VolatileCacheTier::Stats() {
+ std::map<std::string, double> stat;
+ stat.insert({"persistent_cache.volatile_cache.hits",
+ static_cast<double>(stats_.cache_hits_)});
+ stat.insert({"persistent_cache.volatile_cache.misses",
+ static_cast<double>(stats_.cache_misses_)});
+ stat.insert({"persistent_cache.volatile_cache.inserts",
+ static_cast<double>(stats_.cache_inserts_)});
+ stat.insert({"persistent_cache.volatile_cache.evicts",
+ static_cast<double>(stats_.cache_evicts_)});
+ stat.insert({"persistent_cache.volatile_cache.hit_pct",
+ static_cast<double>(stats_.CacheHitPct())});
+ stat.insert({"persistent_cache.volatile_cache.miss_pct",
+ static_cast<double>(stats_.CacheMissPct())});
+
+ auto out = PersistentCacheTier::Stats();
+ out.push_back(stat);
+ return out;
+}
+
+Status VolatileCacheTier::Insert(const Slice& page_key, const char* data,
+ const size_t size) {
+ // precondition
+ assert(data);
+ assert(size);
+
+ // increment the size
+ size_ += size;
+
+ // check if we have overshot the limit, if so evict some space
+ while (size_ > max_size_) {
+ if (!Evict()) {
+ // unable to evict data, we give up so we don't spike read
+ // latency
+ assert(size_ >= size);
+ size_ -= size;
+ return Status::TryAgain("Unable to evict any data");
+ }
+ }
+
+ assert(size_ >= size);
+
+ // insert order: LRU, followed by index
+ std::string key(page_key.data(), page_key.size());
+ std::string value(data, size);
+ std::unique_ptr<CacheData> cache_data(
+ new CacheData(std::move(key), std::move(value)));
+ bool ok = index_.Insert(cache_data.get());
+ if (!ok) {
+ // decrement the size that we incremented ahead of time
+ assert(size_ >= size);
+ size_ -= size;
+ // failed to insert to cache, block already in cache
+ return Status::TryAgain("key already exists in volatile cache");
+ }
+
+ cache_data.release();
+ stats_.cache_inserts_++;
+ return Status::OK();
+}
+
+Status VolatileCacheTier::Lookup(const Slice& page_key,
+ std::unique_ptr<char[]>* result,
+ size_t* size) {
+ CacheData key(std::move(page_key.ToString()));
+ CacheData* kv;
+ bool ok = index_.Find(&key, &kv);
+ if (ok) {
+ // set return data
+ result->reset(new char[kv->value.size()]);
+ memcpy(result->get(), kv->value.c_str(), kv->value.size());
+ *size = kv->value.size();
+ // drop the reference on cache data
+ kv->refs_--;
+ // update stats
+ stats_.cache_hits_++;
+ return Status::OK();
+ }
+
+ stats_.cache_misses_++;
+
+ if (next_tier()) {
+ return next_tier()->Lookup(page_key, result, size);
+ }
+
+ return Status::NotFound("key not found in volatile cache");
+}
+
+bool VolatileCacheTier::Erase(const Slice& /*key*/) {
+ assert(!"not supported");
+ return true;
+}
+
+bool VolatileCacheTier::Evict() {
+ CacheData* edata = index_.Evict();
+ if (!edata) {
+ // not able to evict any object
+ return false;
+ }
+
+ stats_.cache_evicts_++;
+
+ // push the evicted object to the next level
+ if (next_tier()) {
+ next_tier()->Insert(Slice(edata->key), edata->value.c_str(),
+ edata->value.size());
+ }
+
+ // adjust size and destroy data
+ size_ -= edata->value.size();
+ delete edata;
+
+ return true;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.h b/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.h
new file mode 100644
index 00000000..6116e894
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/persistent_cache/volatile_tier_impl.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <atomic>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "rocksdb/cache.h"
+#include "utilities/persistent_cache/hash_table.h"
+#include "utilities/persistent_cache/hash_table_evictable.h"
+#include "utilities/persistent_cache/persistent_cache_tier.h"
+
+// VolatileCacheTier
+//
+// This file provides persistent cache tier implementation for caching
+// key/values in RAM.
+//
+// key/values
+// |
+// V
+// +-------------------+
+// | VolatileCacheTier | Store in an evictable hash table
+// +-------------------+
+// |
+// V
+// on eviction
+// pushed to next tier
+//
+// The implementation is designed to be concurrent. The evictable hash table
+// implementation is not concurrent at this point though.
+//
+// The eviction algorithm is LRU
+namespace ROCKSDB_NAMESPACE {
+
+class VolatileCacheTier : public PersistentCacheTier {
+ public:
+ explicit VolatileCacheTier(
+ const bool is_compressed = true,
+ const size_t max_size = std::numeric_limits<size_t>::max())
+ : is_compressed_(is_compressed), max_size_(max_size) {}
+
+ virtual ~VolatileCacheTier();
+
+ // insert to cache
+ Status Insert(const Slice& page_key, const char* data,
+ const size_t size) override;
+ // lookup key in cache
+ Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
+ size_t* size) override;
+
+ // is compressed cache ?
+ bool IsCompressed() override { return is_compressed_; }
+
+ // erase key from cache
+ bool Erase(const Slice& key) override;
+
+ std::string GetPrintableOptions() const override {
+ return "VolatileCacheTier";
+ }
+
+ // Expose stats as map
+ PersistentCache::StatsType Stats() override;
+
+ private:
+ //
+ // Cache data abstraction
+ //
+ struct CacheData : LRUElement<CacheData> {
+ explicit CacheData(CacheData&& rhs) ROCKSDB_NOEXCEPT
+ : key(std::move(rhs.key)),
+ value(std::move(rhs.value)) {}
+
+ explicit CacheData(const std::string& _key, const std::string& _value = "")
+ : key(_key), value(_value) {}
+
+ virtual ~CacheData() {}
+
+ const std::string key;
+ const std::string value;
+ };
+
+ static void DeleteCacheData(CacheData* data);
+
+ //
+ // Index and LRU definition
+ //
+ struct CacheDataHash {
+ uint64_t operator()(const CacheData* obj) const {
+ assert(obj);
+ return std::hash<std::string>()(obj->key);
+ }
+ };
+
+ struct CacheDataEqual {
+ bool operator()(const CacheData* lhs, const CacheData* rhs) const {
+ assert(lhs);
+ assert(rhs);
+ return lhs->key == rhs->key;
+ }
+ };
+
+ struct Statistics {
+ std::atomic<uint64_t> cache_misses_{0};
+ std::atomic<uint64_t> cache_hits_{0};
+ std::atomic<uint64_t> cache_inserts_{0};
+ std::atomic<uint64_t> cache_evicts_{0};
+
+ double CacheHitPct() const {
+ auto lookups = cache_hits_ + cache_misses_;
+ return lookups ? 100 * cache_hits_ / static_cast<double>(lookups) : 0.0;
+ }
+
+ double CacheMissPct() const {
+ auto lookups = cache_hits_ + cache_misses_;
+ return lookups ? 100 * cache_misses_ / static_cast<double>(lookups) : 0.0;
+ }
+ };
+
+ typedef EvictableHashTable<CacheData, CacheDataHash, CacheDataEqual>
+ IndexType;
+
+ // Evict LRU tail
+ bool Evict();
+
+ const bool is_compressed_ = true; // does it store compressed data
+ IndexType index_; // in-memory cache
+ std::atomic<uint64_t> max_size_{0}; // Maximum size of the cache
+ std::atomic<uint64_t> size_{0}; // Size of the cache
+ Statistics stats_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif
diff --git a/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.cc b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.cc
new file mode 100644
index 00000000..16a78ea7
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "utilities/simulator_cache/cache_simulator.h"
+#include <algorithm>
+#include "db/dbformat.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+const std::string kGhostCachePrefix = "ghost_";
+} // namespace
+
+GhostCache::GhostCache(std::shared_ptr<Cache> sim_cache)
+ : sim_cache_(sim_cache) {}
+
+bool GhostCache::Admit(const Slice& lookup_key) {
+ auto handle = sim_cache_->Lookup(lookup_key);
+ if (handle != nullptr) {
+ sim_cache_->Release(handle);
+ return true;
+ }
+ sim_cache_->Insert(lookup_key, /*value=*/nullptr, lookup_key.size(),
+ /*deleter=*/nullptr);
+ return false;
+}
+
+CacheSimulator::CacheSimulator(std::unique_ptr<GhostCache>&& ghost_cache,
+ std::shared_ptr<Cache> sim_cache)
+ : ghost_cache_(std::move(ghost_cache)), sim_cache_(sim_cache) {}
+
+void CacheSimulator::Access(const BlockCacheTraceRecord& access) {
+ bool admit = true;
+ const bool is_user_access =
+ BlockCacheTraceHelper::IsUserAccess(access.caller);
+ bool is_cache_miss = true;
+ if (ghost_cache_ && access.no_insert == Boolean::kFalse) {
+ admit = ghost_cache_->Admit(access.block_key);
+ }
+ auto handle = sim_cache_->Lookup(access.block_key);
+ if (handle != nullptr) {
+ sim_cache_->Release(handle);
+ is_cache_miss = false;
+ } else {
+ if (access.no_insert == Boolean::kFalse && admit && access.block_size > 0) {
+ sim_cache_->Insert(access.block_key, /*value=*/nullptr, access.block_size,
+ /*deleter=*/nullptr);
+ }
+ }
+ miss_ratio_stats_.UpdateMetrics(access.access_timestamp, is_user_access,
+ is_cache_miss);
+}
+
+void MissRatioStats::UpdateMetrics(uint64_t timestamp_in_ms,
+ bool is_user_access, bool is_cache_miss) {
+ uint64_t timestamp_in_seconds = timestamp_in_ms / kMicrosInSecond;
+ num_accesses_timeline_[timestamp_in_seconds] += 1;
+ num_accesses_ += 1;
+ if (num_misses_timeline_.find(timestamp_in_seconds) ==
+ num_misses_timeline_.end()) {
+ num_misses_timeline_[timestamp_in_seconds] = 0;
+ }
+ if (is_cache_miss) {
+ num_misses_ += 1;
+ num_misses_timeline_[timestamp_in_seconds] += 1;
+ }
+ if (is_user_access) {
+ user_accesses_ += 1;
+ if (is_cache_miss) {
+ user_misses_ += 1;
+ }
+ }
+}
+
+Cache::Priority PrioritizedCacheSimulator::ComputeBlockPriority(
+ const BlockCacheTraceRecord& access) const {
+ if (access.block_type == TraceType::kBlockTraceFilterBlock ||
+ access.block_type == TraceType::kBlockTraceIndexBlock ||
+ access.block_type == TraceType::kBlockTraceUncompressionDictBlock) {
+ return Cache::Priority::HIGH;
+ }
+ return Cache::Priority::LOW;
+}
+
+void PrioritizedCacheSimulator::AccessKVPair(
+ const Slice& key, uint64_t value_size, Cache::Priority priority,
+ const BlockCacheTraceRecord& access, bool no_insert, bool is_user_access,
+ bool* is_cache_miss, bool* admitted, bool update_metrics) {
+ assert(is_cache_miss);
+ assert(admitted);
+ *is_cache_miss = true;
+ *admitted = true;
+ if (ghost_cache_ && !no_insert) {
+ *admitted = ghost_cache_->Admit(key);
+ }
+ auto handle = sim_cache_->Lookup(key);
+ if (handle != nullptr) {
+ sim_cache_->Release(handle);
+ *is_cache_miss = false;
+ } else if (!no_insert && *admitted && value_size > 0) {
+ sim_cache_->Insert(key, /*value=*/nullptr, value_size, /*deleter=*/nullptr,
+ /*handle=*/nullptr, priority);
+ }
+ if (update_metrics) {
+ miss_ratio_stats_.UpdateMetrics(access.access_timestamp, is_user_access,
+ *is_cache_miss);
+ }
+}
+
+void PrioritizedCacheSimulator::Access(const BlockCacheTraceRecord& access) {
+ bool is_cache_miss = true;
+ bool admitted = true;
+ AccessKVPair(access.block_key, access.block_size,
+ ComputeBlockPriority(access), access, access.no_insert,
+ BlockCacheTraceHelper::IsUserAccess(access.caller),
+ &is_cache_miss, &admitted, /*update_metrics=*/true);
+}
+
+void HybridRowBlockCacheSimulator::Access(const BlockCacheTraceRecord& access) {
+ // TODO (haoyu): We only support Get for now. We need to extend the tracing
+ // for MultiGet, i.e., non-data block accesses must log all keys in a
+ // MultiGet.
+ bool is_cache_miss = true;
+ bool admitted = false;
+ if (access.caller == TableReaderCaller::kUserGet &&
+ access.get_id != BlockCacheTraceHelper::kReservedGetId) {
+ // This is a Get request.
+ const std::string& row_key = BlockCacheTraceHelper::ComputeRowKey(access);
+ GetRequestStatus& status = getid_status_map_[access.get_id];
+ if (status.is_complete) {
+ // This Get request completes.
+ // Skip future accesses to its index/filter/data
+ // blocks. These block lookups are unnecessary if we observe a hit for the
+ // referenced key-value pair already. Thus, we treat these lookups as
+ // hits. This is also to ensure the total number of accesses are the same
+ // when comparing to other policies.
+ miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
+ /*is_user_access=*/true,
+ /*is_cache_miss=*/false);
+ return;
+ }
+ if (status.row_key_status.find(row_key) == status.row_key_status.end()) {
+ // This is the first time that this key is accessed. Look up the key-value
+ // pair first. Do not update the miss/accesses metrics here since it will
+ // be updated later.
+ AccessKVPair(row_key, access.referenced_data_size, Cache::Priority::HIGH,
+ access,
+ /*no_insert=*/false,
+ /*is_user_access=*/true, &is_cache_miss, &admitted,
+ /*update_metrics=*/false);
+ InsertResult result = InsertResult::NO_INSERT;
+ if (admitted && access.referenced_data_size > 0) {
+ result = InsertResult::INSERTED;
+ } else if (admitted) {
+ result = InsertResult::ADMITTED;
+ }
+ status.row_key_status[row_key] = result;
+ }
+ if (!is_cache_miss) {
+ // A cache hit.
+ status.is_complete = true;
+ miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
+ /*is_user_access=*/true,
+ /*is_cache_miss=*/false);
+ return;
+ }
+ // The row key-value pair observes a cache miss. We need to access its
+ // index/filter/data blocks.
+ InsertResult inserted = status.row_key_status[row_key];
+ AccessKVPair(
+ access.block_key, access.block_size, ComputeBlockPriority(access),
+ access,
+ /*no_insert=*/!insert_blocks_upon_row_kvpair_miss_ || access.no_insert,
+ /*is_user_access=*/true, &is_cache_miss, &admitted,
+ /*update_metrics=*/true);
+ if (access.referenced_data_size > 0 && inserted == InsertResult::ADMITTED) {
+ sim_cache_->Insert(row_key, /*value=*/nullptr,
+ access.referenced_data_size, /*deleter=*/nullptr,
+ /*handle=*/nullptr, Cache::Priority::HIGH);
+ status.row_key_status[row_key] = InsertResult::INSERTED;
+ }
+ return;
+ }
+ AccessKVPair(access.block_key, access.block_size,
+ ComputeBlockPriority(access), access, access.no_insert,
+ BlockCacheTraceHelper::IsUserAccess(access.caller),
+ &is_cache_miss, &admitted, /*update_metrics=*/true);
+}
+
+BlockCacheTraceSimulator::BlockCacheTraceSimulator(
+ uint64_t warmup_seconds, uint32_t downsample_ratio,
+ const std::vector<CacheConfiguration>& cache_configurations)
+ : warmup_seconds_(warmup_seconds),
+ downsample_ratio_(downsample_ratio),
+ cache_configurations_(cache_configurations) {}
+
+Status BlockCacheTraceSimulator::InitializeCaches() {
+ for (auto const& config : cache_configurations_) {
+ for (auto cache_capacity : config.cache_capacities) {
+ // Scale down the cache capacity since the trace contains accesses on
+ // 1/'downsample_ratio' blocks.
+ uint64_t simulate_cache_capacity = cache_capacity / downsample_ratio_;
+ std::shared_ptr<CacheSimulator> sim_cache;
+ std::unique_ptr<GhostCache> ghost_cache;
+ std::string cache_name = config.cache_name;
+ if (cache_name.find(kGhostCachePrefix) != std::string::npos) {
+ ghost_cache.reset(new GhostCache(
+ NewLRUCache(config.ghost_cache_capacity, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ cache_name = cache_name.substr(kGhostCachePrefix.size());
+ }
+ if (cache_name == "lru") {
+ sim_cache = std::make_shared<CacheSimulator>(
+ std::move(ghost_cache),
+ NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0));
+ } else if (cache_name == "lru_priority") {
+ sim_cache = std::make_shared<PrioritizedCacheSimulator>(
+ std::move(ghost_cache),
+ NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0.5));
+ } else if (cache_name == "lru_hybrid") {
+ sim_cache = std::make_shared<HybridRowBlockCacheSimulator>(
+ std::move(ghost_cache),
+ NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0.5),
+ /*insert_blocks_upon_row_kvpair_miss=*/true);
+ } else if (cache_name == "lru_hybrid_no_insert_on_row_miss") {
+ sim_cache = std::make_shared<HybridRowBlockCacheSimulator>(
+ std::move(ghost_cache),
+ NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0.5),
+ /*insert_blocks_upon_row_kvpair_miss=*/false);
+ } else {
+ // Not supported.
+ return Status::InvalidArgument("Unknown cache name " +
+ config.cache_name);
+ }
+ sim_caches_[config].push_back(sim_cache);
+ }
+ }
+ return Status::OK();
+}
+
+void BlockCacheTraceSimulator::Access(const BlockCacheTraceRecord& access) {
+ if (trace_start_time_ == 0) {
+ trace_start_time_ = access.access_timestamp;
+ }
+ // access.access_timestamp is in microseconds.
+ if (!warmup_complete_ &&
+ trace_start_time_ + warmup_seconds_ * kMicrosInSecond <=
+ access.access_timestamp) {
+ for (auto& config_caches : sim_caches_) {
+ for (auto& sim_cache : config_caches.second) {
+ sim_cache->reset_counter();
+ }
+ }
+ warmup_complete_ = true;
+ }
+ for (auto& config_caches : sim_caches_) {
+ for (auto& sim_cache : config_caches.second) {
+ sim_cache->Access(access);
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.h b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.h
new file mode 100644
index 00000000..6d497901
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator.h
@@ -0,0 +1,231 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <unordered_map>
+
+#include "cache/lru_cache.h"
+#include "trace_replay/block_cache_tracer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// A cache configuration provided by user.
+struct CacheConfiguration {
+ std::string cache_name; // LRU.
+ uint32_t num_shard_bits;
+ uint64_t ghost_cache_capacity; // ghost cache capacity in bytes.
+ std::vector<uint64_t>
+ cache_capacities; // simulate cache capacities in bytes.
+
+ bool operator==(const CacheConfiguration& o) const {
+ return cache_name == o.cache_name && num_shard_bits == o.num_shard_bits &&
+ ghost_cache_capacity == o.ghost_cache_capacity;
+ }
+ bool operator<(const CacheConfiguration& o) const {
+ return cache_name < o.cache_name ||
+ (cache_name == o.cache_name && num_shard_bits < o.num_shard_bits) ||
+ (cache_name == o.cache_name && num_shard_bits == o.num_shard_bits &&
+ ghost_cache_capacity < o.ghost_cache_capacity);
+ }
+};
+
+class MissRatioStats {
+ public:
+ void reset_counter() {
+ num_misses_ = 0;
+ num_accesses_ = 0;
+ user_accesses_ = 0;
+ user_misses_ = 0;
+ }
+ double miss_ratio() const {
+ if (num_accesses_ == 0) {
+ return -1;
+ }
+ return static_cast<double>(num_misses_ * 100.0 / num_accesses_);
+ }
+ uint64_t total_accesses() const { return num_accesses_; }
+ uint64_t total_misses() const { return num_misses_; }
+
+ const std::map<uint64_t, uint64_t>& num_accesses_timeline() const {
+ return num_accesses_timeline_;
+ }
+
+ const std::map<uint64_t, uint64_t>& num_misses_timeline() const {
+ return num_misses_timeline_;
+ }
+
+ double user_miss_ratio() const {
+ if (user_accesses_ == 0) {
+ return -1;
+ }
+ return static_cast<double>(user_misses_ * 100.0 / user_accesses_);
+ }
+ uint64_t user_accesses() const { return user_accesses_; }
+ uint64_t user_misses() const { return user_misses_; }
+
+ void UpdateMetrics(uint64_t timestamp_in_ms, bool is_user_access,
+ bool is_cache_miss);
+
+ private:
+ uint64_t num_accesses_ = 0;
+ uint64_t num_misses_ = 0;
+ uint64_t user_accesses_ = 0;
+ uint64_t user_misses_ = 0;
+
+ std::map<uint64_t, uint64_t> num_accesses_timeline_;
+ std::map<uint64_t, uint64_t> num_misses_timeline_;
+};
+
+// A ghost cache admits an entry on its second access.
+class GhostCache {
+ public:
+ explicit GhostCache(std::shared_ptr<Cache> sim_cache);
+ ~GhostCache() = default;
+ // No copy and move.
+ GhostCache(const GhostCache&) = delete;
+ GhostCache& operator=(const GhostCache&) = delete;
+ GhostCache(GhostCache&&) = delete;
+ GhostCache& operator=(GhostCache&&) = delete;
+
+ // Returns true if the lookup_key is in the ghost cache.
+ // Returns false otherwise.
+ bool Admit(const Slice& lookup_key);
+
+ private:
+ std::shared_ptr<Cache> sim_cache_;
+};
+
+// A cache simulator that runs against a block cache trace.
+class CacheSimulator {
+ public:
+ CacheSimulator(std::unique_ptr<GhostCache>&& ghost_cache,
+ std::shared_ptr<Cache> sim_cache);
+ virtual ~CacheSimulator() = default;
+ // No copy and move.
+ CacheSimulator(const CacheSimulator&) = delete;
+ CacheSimulator& operator=(const CacheSimulator&) = delete;
+ CacheSimulator(CacheSimulator&&) = delete;
+ CacheSimulator& operator=(CacheSimulator&&) = delete;
+
+ virtual void Access(const BlockCacheTraceRecord& access);
+
+ void reset_counter() { miss_ratio_stats_.reset_counter(); }
+
+ const MissRatioStats& miss_ratio_stats() const { return miss_ratio_stats_; }
+
+ protected:
+ MissRatioStats miss_ratio_stats_;
+ std::unique_ptr<GhostCache> ghost_cache_;
+ std::shared_ptr<Cache> sim_cache_;
+};
+
+// A prioritized cache simulator that runs against a block cache trace.
+// It inserts missing index/filter/uncompression-dictionary blocks with high
+// priority in the cache.
+class PrioritizedCacheSimulator : public CacheSimulator {
+ public:
+ PrioritizedCacheSimulator(std::unique_ptr<GhostCache>&& ghost_cache,
+ std::shared_ptr<Cache> sim_cache)
+ : CacheSimulator(std::move(ghost_cache), sim_cache) {}
+ void Access(const BlockCacheTraceRecord& access) override;
+
+ protected:
+ // Access the key-value pair and returns true upon a cache miss.
+ void AccessKVPair(const Slice& key, uint64_t value_size,
+ Cache::Priority priority,
+ const BlockCacheTraceRecord& access, bool no_insert,
+ bool is_user_access, bool* is_cache_miss, bool* admitted,
+ bool update_metrics);
+
+ Cache::Priority ComputeBlockPriority(
+ const BlockCacheTraceRecord& access) const;
+};
+
+// A hybrid row and block cache simulator. It looks up/inserts key-value pairs
+// referenced by Get/MultiGet requests, and not their accessed index/filter/data
+// blocks.
+//
+// Upon a Get/MultiGet request, it looks up the referenced key first.
+// If it observes a cache hit, future block accesses on this key-value pair is
+// skipped since the request is served already. Otherwise, it continues to look
+// up/insert its index/filter/data blocks. It also inserts the referenced
+// key-value pair in the cache for future lookups.
+class HybridRowBlockCacheSimulator : public PrioritizedCacheSimulator {
+ public:
+ HybridRowBlockCacheSimulator(std::unique_ptr<GhostCache>&& ghost_cache,
+ std::shared_ptr<Cache> sim_cache,
+ bool insert_blocks_upon_row_kvpair_miss)
+ : PrioritizedCacheSimulator(std::move(ghost_cache), sim_cache),
+ insert_blocks_upon_row_kvpair_miss_(
+ insert_blocks_upon_row_kvpair_miss) {}
+ void Access(const BlockCacheTraceRecord& access) override;
+
+ private:
+ enum InsertResult : char {
+ INSERTED,
+ ADMITTED,
+ NO_INSERT,
+ };
+
+ // We set is_complete to true when the referenced row-key of a get request
+ // hits the cache. If is_complete is true, we treat future accesses of this
+ // get request as hits.
+ //
+ // For each row key, it stores an enum. It is INSERTED when the
+ // kv-pair has been inserted into the cache, ADMITTED if it should be inserted
+ // but haven't been, NO_INSERT if it should not be inserted.
+ //
+ // A kv-pair is in ADMITTED state when we encounter this kv-pair but do not
+ // know its size. This may happen if the first access on the referenced key is
+ // an index/filter block.
+ struct GetRequestStatus {
+ bool is_complete = false;
+ std::map<std::string, InsertResult> row_key_status;
+ };
+
+ // A map stores get_id to a map of row keys.
+ std::map<uint64_t, GetRequestStatus> getid_status_map_;
+ bool insert_blocks_upon_row_kvpair_miss_;
+};
+
+// A block cache simulator that reports miss ratio curves given a set of cache
+// configurations.
+class BlockCacheTraceSimulator {
+ public:
+ // warmup_seconds: The number of seconds to warmup simulated caches. The
+ // hit/miss counters are reset after the warmup completes.
+ BlockCacheTraceSimulator(
+ uint64_t warmup_seconds, uint32_t downsample_ratio,
+ const std::vector<CacheConfiguration>& cache_configurations);
+ ~BlockCacheTraceSimulator() = default;
+ // No copy and move.
+ BlockCacheTraceSimulator(const BlockCacheTraceSimulator&) = delete;
+ BlockCacheTraceSimulator& operator=(const BlockCacheTraceSimulator&) = delete;
+ BlockCacheTraceSimulator(BlockCacheTraceSimulator&&) = delete;
+ BlockCacheTraceSimulator& operator=(BlockCacheTraceSimulator&&) = delete;
+
+ Status InitializeCaches();
+
+ void Access(const BlockCacheTraceRecord& access);
+
+ const std::map<CacheConfiguration,
+ std::vector<std::shared_ptr<CacheSimulator>>>&
+ sim_caches() const {
+ return sim_caches_;
+ }
+
+ private:
+ const uint64_t warmup_seconds_;
+ const uint32_t downsample_ratio_;
+ const std::vector<CacheConfiguration> cache_configurations_;
+
+ bool warmup_complete_ = false;
+ std::map<CacheConfiguration, std::vector<std::shared_ptr<CacheSimulator>>>
+ sim_caches_;
+ uint64_t trace_start_time_ = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator_test.cc b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator_test.cc
new file mode 100644
index 00000000..a205315c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/simulator_cache/cache_simulator_test.cc
@@ -0,0 +1,494 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "utilities/simulator_cache/cache_simulator.h"
+
+#include <cstdlib>
+#include "rocksdb/env.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+
+namespace ROCKSDB_NAMESPACE {
+namespace {
+const std::string kBlockKeyPrefix = "test-block-";
+const std::string kRefKeyPrefix = "test-get-";
+const std::string kRefKeySequenceNumber = std::string(8, 'c');
+const uint64_t kGetId = 1;
+const uint64_t kGetBlockId = 100;
+const uint64_t kCompactionBlockId = 1000;
+const uint64_t kCacheSize = 1024 * 1024 * 1024;
+const uint64_t kGhostCacheSize = 1024 * 1024;
+} // namespace
+
+class CacheSimulatorTest : public testing::Test {
+ public:
+ const size_t kNumBlocks = 5;
+ const size_t kValueSize = 1000;
+
+ CacheSimulatorTest() { env_ = ROCKSDB_NAMESPACE::Env::Default(); }
+
+ BlockCacheTraceRecord GenerateGetRecord(uint64_t getid) {
+ BlockCacheTraceRecord record;
+ record.block_type = TraceType::kBlockTraceDataBlock;
+ record.block_size = 4096;
+ record.block_key = kBlockKeyPrefix + std::to_string(kGetBlockId);
+ record.access_timestamp = env_->NowMicros();
+ record.cf_id = 0;
+ record.cf_name = "test";
+ record.caller = TableReaderCaller::kUserGet;
+ record.level = 6;
+ record.sst_fd_number = 0;
+ record.get_id = getid;
+ record.is_cache_hit = Boolean::kFalse;
+ record.no_insert = Boolean::kFalse;
+ record.referenced_key =
+ kRefKeyPrefix + std::to_string(kGetId) + kRefKeySequenceNumber;
+ record.referenced_key_exist_in_block = Boolean::kTrue;
+ record.referenced_data_size = 100;
+ record.num_keys_in_block = 300;
+ return record;
+ }
+
+ BlockCacheTraceRecord GenerateCompactionRecord() {
+ BlockCacheTraceRecord record;
+ record.block_type = TraceType::kBlockTraceDataBlock;
+ record.block_size = 4096;
+ record.block_key = kBlockKeyPrefix + std::to_string(kCompactionBlockId);
+ record.access_timestamp = env_->NowMicros();
+ record.cf_id = 0;
+ record.cf_name = "test";
+ record.caller = TableReaderCaller::kCompaction;
+ record.level = 6;
+ record.sst_fd_number = kCompactionBlockId;
+ record.is_cache_hit = Boolean::kFalse;
+ record.no_insert = Boolean::kTrue;
+ return record;
+ }
+
+ void AssertCache(std::shared_ptr<Cache> sim_cache,
+ const MissRatioStats& miss_ratio_stats,
+ uint64_t expected_usage, uint64_t expected_num_accesses,
+ uint64_t expected_num_misses,
+ std::vector<std::string> blocks,
+ std::vector<std::string> keys) {
+ EXPECT_EQ(expected_usage, sim_cache->GetUsage());
+ EXPECT_EQ(expected_num_accesses, miss_ratio_stats.total_accesses());
+ EXPECT_EQ(expected_num_misses, miss_ratio_stats.total_misses());
+ for (auto const& block : blocks) {
+ auto handle = sim_cache->Lookup(block);
+ EXPECT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ }
+ for (auto const& key : keys) {
+ std::string row_key = kRefKeyPrefix + key + kRefKeySequenceNumber;
+ auto handle =
+ sim_cache->Lookup("0_" + ExtractUserKey(row_key).ToString());
+ EXPECT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ }
+ }
+
+ Env* env_;
+};
+
+TEST_F(CacheSimulatorTest, GhostCache) {
+ const std::string key1 = "test1";
+ const std::string key2 = "test2";
+ std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
+ NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ EXPECT_FALSE(ghost_cache->Admit(key1));
+ EXPECT_TRUE(ghost_cache->Admit(key1));
+ EXPECT_TRUE(ghost_cache->Admit(key1));
+ EXPECT_FALSE(ghost_cache->Admit(key2));
+ EXPECT_TRUE(ghost_cache->Admit(key2));
+}
+
+TEST_F(CacheSimulatorTest, CacheSimulator) {
+ const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
+ const BlockCacheTraceRecord& compaction_access = GenerateCompactionRecord();
+ std::shared_ptr<Cache> sim_cache =
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0);
+ std::unique_ptr<CacheSimulator> cache_simulator(
+ new CacheSimulator(nullptr, sim_cache));
+ cache_simulator->Access(access);
+ cache_simulator->Access(access);
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(50, cache_simulator->miss_ratio_stats().miss_ratio());
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(50, cache_simulator->miss_ratio_stats().user_miss_ratio());
+
+ cache_simulator->Access(compaction_access);
+ cache_simulator->Access(compaction_access);
+ ASSERT_EQ(4, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(75, cache_simulator->miss_ratio_stats().miss_ratio());
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(50, cache_simulator->miss_ratio_stats().user_miss_ratio());
+
+ cache_simulator->reset_counter();
+ ASSERT_EQ(0, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(-1, cache_simulator->miss_ratio_stats().miss_ratio());
+ auto handle = sim_cache->Lookup(access.block_key);
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ handle = sim_cache->Lookup(compaction_access.block_key);
+ ASSERT_EQ(nullptr, handle);
+}
+
+TEST_F(CacheSimulatorTest, GhostCacheSimulator) {
+ const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
+ std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
+ NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ std::unique_ptr<CacheSimulator> cache_simulator(new CacheSimulator(
+ std::move(ghost_cache),
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ cache_simulator->Access(access);
+ cache_simulator->Access(access);
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
+ // Both of them will be miss since we have a ghost cache.
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
+}
+
+TEST_F(CacheSimulatorTest, PrioritizedCacheSimulator) {
+ const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
+ std::shared_ptr<Cache> sim_cache =
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0);
+ std::unique_ptr<PrioritizedCacheSimulator> cache_simulator(
+ new PrioritizedCacheSimulator(nullptr, sim_cache));
+ cache_simulator->Access(access);
+ cache_simulator->Access(access);
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(50, cache_simulator->miss_ratio_stats().miss_ratio());
+
+ auto handle = sim_cache->Lookup(access.block_key);
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+}
+
+TEST_F(CacheSimulatorTest, GhostPrioritizedCacheSimulator) {
+ const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
+ std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
+ NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ std::unique_ptr<PrioritizedCacheSimulator> cache_simulator(
+ new PrioritizedCacheSimulator(
+ std::move(ghost_cache),
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ cache_simulator->Access(access);
+ cache_simulator->Access(access);
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
+ // Both of them will be miss since we have a ghost cache.
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
+}
+
+TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) {
+ uint64_t block_id = 100;
+ BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId);
+ first_get.get_from_user_specified_snapshot = Boolean::kTrue;
+ BlockCacheTraceRecord second_get = GenerateGetRecord(kGetId + 1);
+ second_get.referenced_data_size = 0;
+ second_get.referenced_key_exist_in_block = Boolean::kFalse;
+ second_get.get_from_user_specified_snapshot = Boolean::kTrue;
+ BlockCacheTraceRecord third_get = GenerateGetRecord(kGetId + 2);
+ third_get.referenced_data_size = 0;
+ third_get.referenced_key_exist_in_block = Boolean::kFalse;
+ third_get.referenced_key = kRefKeyPrefix + "third_get";
+ // We didn't find the referenced key in the third get.
+ third_get.referenced_key_exist_in_block = Boolean::kFalse;
+ third_get.referenced_data_size = 0;
+ std::shared_ptr<Cache> sim_cache =
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0);
+ std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
+ new HybridRowBlockCacheSimulator(
+ nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/true));
+ // The first get request accesses 10 blocks. We should only report 10 accesses
+ // and 100% miss.
+ for (uint32_t i = 0; i < 10; i++) {
+ first_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
+ cache_simulator->Access(first_get);
+ block_id++;
+ }
+
+ ASSERT_EQ(10, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
+ ASSERT_EQ(10, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().user_miss_ratio());
+ auto handle =
+ sim_cache->Lookup(std::to_string(first_get.sst_fd_number) + "_" +
+ ExtractUserKey(first_get.referenced_key).ToString());
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ for (uint32_t i = 100; i < block_id; i++) {
+ handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ }
+
+ // The second get request accesses the same key. We should report 15
+ // access and 66% miss, 10 misses with 15 accesses.
+ // We do not consider these 5 block lookups as misses since the row hits the
+ // cache.
+ for (uint32_t i = 0; i < 5; i++) {
+ second_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
+ cache_simulator->Access(second_get);
+ block_id++;
+ }
+ ASSERT_EQ(15, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(66, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().miss_ratio()));
+ ASSERT_EQ(15, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(66, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().user_miss_ratio()));
+ handle =
+ sim_cache->Lookup(std::to_string(second_get.sst_fd_number) + "_" +
+ ExtractUserKey(second_get.referenced_key).ToString());
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ for (uint32_t i = 100; i < block_id; i++) {
+ handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
+ if (i < 110) {
+ ASSERT_NE(nullptr, handle) << i;
+ sim_cache->Release(handle);
+ } else {
+ ASSERT_EQ(nullptr, handle) << i;
+ }
+ }
+
+ // The third get on a different key and does not have a size.
+ // This key should not be inserted into the cache.
+ for (uint32_t i = 0; i < 5; i++) {
+ third_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
+ cache_simulator->Access(third_get);
+ block_id++;
+ }
+ ASSERT_EQ(20, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(75, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().miss_ratio()));
+ ASSERT_EQ(20, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(75, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().user_miss_ratio()));
+ // Assert that the third key is not inserted into the cache.
+ handle = sim_cache->Lookup(std::to_string(third_get.sst_fd_number) + "_" +
+ third_get.referenced_key);
+ ASSERT_EQ(nullptr, handle);
+ for (uint32_t i = 100; i < block_id; i++) {
+ if (i < 110 || i >= 115) {
+ handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
+ ASSERT_NE(nullptr, handle) << i;
+ sim_cache->Release(handle);
+ } else {
+ handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
+ ASSERT_EQ(nullptr, handle) << i;
+ }
+ }
+}
+
+TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulatorGetTest) {
+ BlockCacheTraceRecord get = GenerateGetRecord(kGetId);
+ get.block_size = 1;
+ get.referenced_data_size = 0;
+ get.access_timestamp = 0;
+ get.block_key = "1";
+ get.get_id = 1;
+ get.get_from_user_specified_snapshot = Boolean::kFalse;
+ get.referenced_key =
+ kRefKeyPrefix + std::to_string(1) + kRefKeySequenceNumber;
+ get.no_insert = Boolean::kFalse;
+ get.sst_fd_number = 0;
+ get.get_from_user_specified_snapshot = Boolean::kFalse;
+
+ LRUCacheOptions co;
+ co.capacity = 16;
+ co.num_shard_bits = 1;
+ co.strict_capacity_limit = false;
+ co.high_pri_pool_ratio = 0;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ std::shared_ptr<Cache> sim_cache = NewLRUCache(co);
+ std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
+ new HybridRowBlockCacheSimulator(
+ nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/true));
+ // Expect a miss and does not insert the row key-value pair since it does not
+ // have size.
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 1, 1, 1, {"1"},
+ {});
+ get.access_timestamp += 1;
+ get.referenced_data_size = 1;
+ get.block_key = "2";
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 3, 2, 2,
+ {"1", "2"}, {"1"});
+ get.access_timestamp += 1;
+ get.block_key = "3";
+ // K1 should not inserted again.
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 4, 3, 3,
+ {"1", "2", "3"}, {"1"});
+
+ // A second get request referencing the same key.
+ get.access_timestamp += 1;
+ get.get_id = 2;
+ get.block_key = "4";
+ get.referenced_data_size = 0;
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 4, 4, 3,
+ {"1", "2", "3"}, {"1"});
+
+ // A third get request searches three files, three different keys.
+ // And the second key observes a hit.
+ get.access_timestamp += 1;
+ get.referenced_data_size = 1;
+ get.get_id = 3;
+ get.block_key = "3";
+ get.referenced_key = kRefKeyPrefix + "2" + kRefKeySequenceNumber;
+ // K2 should observe a miss. Block 3 observes a hit.
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 5, 3,
+ {"1", "2", "3"}, {"1", "2"});
+
+ get.access_timestamp += 1;
+ get.referenced_data_size = 1;
+ get.get_id = 3;
+ get.block_key = "4";
+ get.referenced_data_size = 1;
+ get.referenced_key = kRefKeyPrefix + "1" + kRefKeySequenceNumber;
+ // K1 should observe a hit.
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 6, 3,
+ {"1", "2", "3"}, {"1", "2"});
+
+ get.access_timestamp += 1;
+ get.referenced_data_size = 1;
+ get.get_id = 3;
+ get.block_key = "4";
+ get.referenced_data_size = 1;
+ get.referenced_key = kRefKeyPrefix + "3" + kRefKeySequenceNumber;
+ // K3 should observe a miss.
+ // However, as the get already complete, we should not access k3 any more.
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 7, 3,
+ {"1", "2", "3"}, {"1", "2"});
+
+ // A fourth get request searches one file and two blocks. One row key.
+ get.access_timestamp += 1;
+ get.get_id = 4;
+ get.block_key = "5";
+ get.referenced_key = kRefKeyPrefix + "4" + kRefKeySequenceNumber;
+ get.referenced_data_size = 1;
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 7, 8, 4,
+ {"1", "2", "3", "5"}, {"1", "2", "4"});
+ for (auto const& key : {"1", "2", "4"}) {
+ auto handle = sim_cache->Lookup("0_" + kRefKeyPrefix + key);
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ }
+
+ // A bunch of insertions which evict cached row keys.
+ for (uint32_t i = 6; i < 100; i++) {
+ get.access_timestamp += 1;
+ get.get_id = 0;
+ get.block_key = std::to_string(i);
+ cache_simulator->Access(get);
+ }
+
+ get.get_id = 4;
+ // A different block.
+ get.block_key = "100";
+ // Same row key and should not be inserted again.
+ get.referenced_key = kRefKeyPrefix + "4" + kRefKeySequenceNumber;
+ get.referenced_data_size = 1;
+ cache_simulator->Access(get);
+ AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 16, 103, 99, {},
+ {});
+ for (auto const& key : {"1", "2", "4"}) {
+ auto handle = sim_cache->Lookup("0_" + kRefKeyPrefix + key);
+ ASSERT_EQ(nullptr, handle);
+ }
+}
+
+TEST_F(CacheSimulatorTest, HybridRowBlockNoInsertCacheSimulator) {
+ uint64_t block_id = 100;
+ BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId);
+ std::shared_ptr<Cache> sim_cache =
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0);
+ std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
+ new HybridRowBlockCacheSimulator(
+ nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/false));
+ for (uint32_t i = 0; i < 9; i++) {
+ first_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
+ cache_simulator->Access(first_get);
+ block_id++;
+ }
+ auto handle =
+ sim_cache->Lookup(std::to_string(first_get.sst_fd_number) + "_" +
+ ExtractUserKey(first_get.referenced_key).ToString());
+ ASSERT_NE(nullptr, handle);
+ sim_cache->Release(handle);
+ // All blocks are missing from the cache since insert_blocks_row_kvpair_misses
+ // is set to false.
+ for (uint32_t i = 100; i < block_id; i++) {
+ handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
+ ASSERT_EQ(nullptr, handle);
+ }
+}
+
+TEST_F(CacheSimulatorTest, GhostHybridRowBlockCacheSimulator) {
+ std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
+ NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0)));
+ const BlockCacheTraceRecord& first_get = GenerateGetRecord(kGetId);
+ const BlockCacheTraceRecord& second_get = GenerateGetRecord(kGetId + 1);
+ const BlockCacheTraceRecord& third_get = GenerateGetRecord(kGetId + 2);
+ std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
+ new HybridRowBlockCacheSimulator(
+ std::move(ghost_cache),
+ NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
+ /*strict_capacity_limit=*/false,
+ /*high_pri_pool_ratio=*/0),
+ /*insert_blocks_row_kvpair_misses=*/false));
+ // Two get requests access the same key.
+ cache_simulator->Access(first_get);
+ cache_simulator->Access(second_get);
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
+ ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(100, cache_simulator->miss_ratio_stats().user_miss_ratio());
+ // We insert the key-value pair upon the second get request. A third get
+ // request should observe a hit.
+ for (uint32_t i = 0; i < 10; i++) {
+ cache_simulator->Access(third_get);
+ }
+ ASSERT_EQ(12, cache_simulator->miss_ratio_stats().total_accesses());
+ ASSERT_EQ(16, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().miss_ratio()));
+ ASSERT_EQ(12, cache_simulator->miss_ratio_stats().user_accesses());
+ ASSERT_EQ(16, static_cast<uint64_t>(
+ cache_simulator->miss_ratio_stats().user_miss_ratio()));
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache.cc b/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache.cc
new file mode 100644
index 00000000..ec411cf9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache.cc
@@ -0,0 +1,354 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sim_cache.h"
+#include <atomic>
+#include "env/composite_env_wrapper.h"
+#include "file/writable_file_writer.h"
+#include "monitoring/statistics.h"
+#include "port/port.h"
+#include "rocksdb/env.h"
+#include "util/mutexlock.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+class CacheActivityLogger {
+ public:
+ CacheActivityLogger()
+ : activity_logging_enabled_(false), max_logging_size_(0) {}
+
+ ~CacheActivityLogger() {
+ MutexLock l(&mutex_);
+
+ StopLoggingInternal();
+ }
+
+ Status StartLogging(const std::string& activity_log_file, Env* env,
+ uint64_t max_logging_size = 0) {
+ assert(activity_log_file != "");
+ assert(env != nullptr);
+
+ Status status;
+ EnvOptions env_opts;
+ std::unique_ptr<WritableFile> log_file;
+
+ MutexLock l(&mutex_);
+
+ // Stop existing logging if any
+ StopLoggingInternal();
+
+ // Open log file
+ status = env->NewWritableFile(activity_log_file, &log_file, env_opts);
+ if (!status.ok()) {
+ return status;
+ }
+ file_writer_.reset(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(log_file)), activity_log_file,
+ env_opts));
+
+ max_logging_size_ = max_logging_size;
+ activity_logging_enabled_.store(true);
+
+ return status;
+ }
+
+ void StopLogging() {
+ MutexLock l(&mutex_);
+
+ StopLoggingInternal();
+ }
+
+ void ReportLookup(const Slice& key) {
+ if (activity_logging_enabled_.load() == false) {
+ return;
+ }
+
+ std::string log_line = "LOOKUP - " + key.ToString(true) + "\n";
+
+ // line format: "LOOKUP - <KEY>"
+ MutexLock l(&mutex_);
+ Status s = file_writer_->Append(log_line);
+ if (!s.ok() && bg_status_.ok()) {
+ bg_status_ = s;
+ }
+ if (MaxLoggingSizeReached() || !bg_status_.ok()) {
+ // Stop logging if we have reached the max file size or
+ // encountered an error
+ StopLoggingInternal();
+ }
+ }
+
+ void ReportAdd(const Slice& key, size_t size) {
+ if (activity_logging_enabled_.load() == false) {
+ return;
+ }
+
+ std::string log_line = "ADD - ";
+ log_line += key.ToString(true);
+ log_line += " - ";
+ AppendNumberTo(&log_line, size);
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ log_line += "\n";
+
+ // line format: "ADD - <KEY> - <KEY-SIZE>"
+ MutexLock l(&mutex_);
+ Status s = file_writer_->Append(log_line);
+ if (!s.ok() && bg_status_.ok()) {
+ bg_status_ = s;
+ }
+
+ if (MaxLoggingSizeReached() || !bg_status_.ok()) {
+ // Stop logging if we have reached the max file size or
+ // encountered an error
+ StopLoggingInternal();
+ }
+ }
+
+ Status& bg_status() {
+ MutexLock l(&mutex_);
+ return bg_status_;
+ }
+
+ private:
+ bool MaxLoggingSizeReached() {
+ mutex_.AssertHeld();
+
+ return (max_logging_size_ > 0 &&
+ file_writer_->GetFileSize() >= max_logging_size_);
+ }
+
+ void StopLoggingInternal() {
+ mutex_.AssertHeld();
+
+ if (!activity_logging_enabled_) {
+ return;
+ }
+
+ activity_logging_enabled_.store(false);
+ Status s = file_writer_->Close();
+ if (!s.ok() && bg_status_.ok()) {
+ bg_status_ = s;
+ }
+ }
+
+ // Mutex to sync writes to file_writer, and all following
+ // class data members
+ port::Mutex mutex_;
+ // Indicates if logging is currently enabled
+ // atomic to allow reads without mutex
+ std::atomic<bool> activity_logging_enabled_;
+ // When reached, we will stop logging and close the file
+ // Value of 0 means unlimited
+ uint64_t max_logging_size_;
+ std::unique_ptr<WritableFileWriter> file_writer_;
+ Status bg_status_;
+};
+
+// SimCacheImpl definition
+class SimCacheImpl : public SimCache {
+ public:
+ // capacity for real cache (ShardedLRUCache)
+ // test_capacity for key only cache
+ SimCacheImpl(std::shared_ptr<Cache> sim_cache, std::shared_ptr<Cache> cache)
+ : cache_(cache),
+ key_only_cache_(sim_cache),
+ miss_times_(0),
+ hit_times_(0),
+ stats_(nullptr) {}
+
+ ~SimCacheImpl() override {}
+ void SetCapacity(size_t capacity) override { cache_->SetCapacity(capacity); }
+
+ void SetStrictCapacityLimit(bool strict_capacity_limit) override {
+ cache_->SetStrictCapacityLimit(strict_capacity_limit);
+ }
+
+ Status Insert(const Slice& key, void* value, size_t charge,
+ void (*deleter)(const Slice& key, void* value), Handle** handle,
+ Priority priority) override {
+ // The handle and value passed in are for real cache, so we pass nullptr
+ // to key_only_cache_ for both instead. Also, the deleter function pointer
+ // will be called by user to perform some external operation which should
+ // be applied only once. Thus key_only_cache accepts an empty function.
+ // *Lambda function without capture can be assgined to a function pointer
+ Handle* h = key_only_cache_->Lookup(key);
+ if (h == nullptr) {
+ key_only_cache_->Insert(key, nullptr, charge,
+ [](const Slice& /*k*/, void* /*v*/) {}, nullptr,
+ priority);
+ } else {
+ key_only_cache_->Release(h);
+ }
+
+ cache_activity_logger_.ReportAdd(key, charge);
+ if (!cache_) {
+ return Status::OK();
+ }
+ return cache_->Insert(key, value, charge, deleter, handle, priority);
+ }
+
+ Handle* Lookup(const Slice& key, Statistics* stats) override {
+ Handle* h = key_only_cache_->Lookup(key);
+ if (h != nullptr) {
+ key_only_cache_->Release(h);
+ inc_hit_counter();
+ RecordTick(stats, SIM_BLOCK_CACHE_HIT);
+ } else {
+ inc_miss_counter();
+ RecordTick(stats, SIM_BLOCK_CACHE_MISS);
+ }
+
+ cache_activity_logger_.ReportLookup(key);
+ if (!cache_) {
+ return nullptr;
+ }
+ return cache_->Lookup(key, stats);
+ }
+
+ bool Ref(Handle* handle) override { return cache_->Ref(handle); }
+
+ bool Release(Handle* handle, bool force_erase = false) override {
+ return cache_->Release(handle, force_erase);
+ }
+
+ void Erase(const Slice& key) override {
+ cache_->Erase(key);
+ key_only_cache_->Erase(key);
+ }
+
+ void* Value(Handle* handle) override { return cache_->Value(handle); }
+
+ uint64_t NewId() override { return cache_->NewId(); }
+
+ size_t GetCapacity() const override { return cache_->GetCapacity(); }
+
+ bool HasStrictCapacityLimit() const override {
+ return cache_->HasStrictCapacityLimit();
+ }
+
+ size_t GetUsage() const override { return cache_->GetUsage(); }
+
+ size_t GetUsage(Handle* handle) const override {
+ return cache_->GetUsage(handle);
+ }
+
+ size_t GetCharge(Handle* handle) const override {
+ return cache_->GetCharge(handle);
+ }
+
+ size_t GetPinnedUsage() const override { return cache_->GetPinnedUsage(); }
+
+ void DisownData() override {
+ cache_->DisownData();
+ key_only_cache_->DisownData();
+ }
+
+ void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
+ bool thread_safe) override {
+ // only apply to _cache since key_only_cache doesn't hold value
+ cache_->ApplyToAllCacheEntries(callback, thread_safe);
+ }
+
+ void EraseUnRefEntries() override {
+ cache_->EraseUnRefEntries();
+ key_only_cache_->EraseUnRefEntries();
+ }
+
+ size_t GetSimCapacity() const override {
+ return key_only_cache_->GetCapacity();
+ }
+ size_t GetSimUsage() const override { return key_only_cache_->GetUsage(); }
+ void SetSimCapacity(size_t capacity) override {
+ key_only_cache_->SetCapacity(capacity);
+ }
+
+ uint64_t get_miss_counter() const override {
+ return miss_times_.load(std::memory_order_relaxed);
+ }
+
+ uint64_t get_hit_counter() const override {
+ return hit_times_.load(std::memory_order_relaxed);
+ }
+
+ void reset_counter() override {
+ miss_times_.store(0, std::memory_order_relaxed);
+ hit_times_.store(0, std::memory_order_relaxed);
+ SetTickerCount(stats_, SIM_BLOCK_CACHE_HIT, 0);
+ SetTickerCount(stats_, SIM_BLOCK_CACHE_MISS, 0);
+ }
+
+ std::string ToString() const override {
+ std::string res;
+ res.append("SimCache MISSes: " + std::to_string(get_miss_counter()) + "\n");
+ res.append("SimCache HITs: " + std::to_string(get_hit_counter()) + "\n");
+ char buff[350];
+ auto lookups = get_miss_counter() + get_hit_counter();
+ snprintf(buff, sizeof(buff), "SimCache HITRATE: %.2f%%\n",
+ (lookups == 0 ? 0 : get_hit_counter() * 100.0f / lookups));
+ res.append(buff);
+ return res;
+ }
+
+ std::string GetPrintableOptions() const override {
+ std::string ret;
+ ret.reserve(20000);
+ ret.append(" cache_options:\n");
+ ret.append(cache_->GetPrintableOptions());
+ ret.append(" sim_cache_options:\n");
+ ret.append(key_only_cache_->GetPrintableOptions());
+ return ret;
+ }
+
+ Status StartActivityLogging(const std::string& activity_log_file, Env* env,
+ uint64_t max_logging_size = 0) override {
+ return cache_activity_logger_.StartLogging(activity_log_file, env,
+ max_logging_size);
+ }
+
+ void StopActivityLogging() override { cache_activity_logger_.StopLogging(); }
+
+ Status GetActivityLoggingStatus() override {
+ return cache_activity_logger_.bg_status();
+ }
+
+ private:
+ std::shared_ptr<Cache> cache_;
+ std::shared_ptr<Cache> key_only_cache_;
+ std::atomic<uint64_t> miss_times_;
+ std::atomic<uint64_t> hit_times_;
+ Statistics* stats_;
+ CacheActivityLogger cache_activity_logger_;
+
+ void inc_miss_counter() {
+ miss_times_.fetch_add(1, std::memory_order_relaxed);
+ }
+ void inc_hit_counter() { hit_times_.fetch_add(1, std::memory_order_relaxed); }
+};
+
+} // end anonymous namespace
+
+// For instrumentation purpose, use NewSimCache instead
+std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> cache,
+ size_t sim_capacity, int num_shard_bits) {
+ LRUCacheOptions co;
+ co.capacity = sim_capacity;
+ co.num_shard_bits = num_shard_bits;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ return NewSimCache(NewLRUCache(co), cache, num_shard_bits);
+}
+
+std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> sim_cache,
+ std::shared_ptr<Cache> cache,
+ int num_shard_bits) {
+ if (num_shard_bits >= 20) {
+ return nullptr; // the cache cannot be sharded into too many fine pieces
+ }
+ return std::make_shared<SimCacheImpl>(sim_cache, cache);
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc b/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc
new file mode 100644
index 00000000..6cb49581
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/sim_cache.h"
+#include <cstdlib>
+#include "db/db_test_util.h"
+#include "port/stack_trace.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class SimCacheTest : public DBTestBase {
+ private:
+ size_t miss_count_ = 0;
+ size_t hit_count_ = 0;
+ size_t insert_count_ = 0;
+ size_t failure_count_ = 0;
+
+ public:
+ const size_t kNumBlocks = 5;
+ const size_t kValueSize = 1000;
+
+ SimCacheTest() : DBTestBase("/sim_cache_test") {}
+
+ 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.compression = kNoCompression;
+ options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ return options;
+ }
+
+ void InitTable(const Options& /*options*/) {
+ std::string value(kValueSize, 'a');
+ for (size_t i = 0; i < kNumBlocks * 2; i++) {
+ ASSERT_OK(Put(ToString(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 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;
+ }
+};
+
+TEST_F(SimCacheTest, SimCache) {
+ 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;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ std::shared_ptr<SimCache> simCache = NewSimCache(NewLRUCache(co), 20000, 0);
+ table_options.block_cache = simCache;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+ RecordCacheCounters(options);
+
+ std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks);
+ Iterator* iter = nullptr;
+
+ // Load blocks into cache.
+ for (size_t i = 0; i < kNumBlocks; i++) {
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(i));
+ ASSERT_OK(iter->status());
+ CheckCacheCounters(options, 1, 0, 1, 0);
+ iterators[i].reset(iter);
+ }
+ ASSERT_EQ(kNumBlocks,
+ simCache->get_hit_counter() + simCache->get_miss_counter());
+ ASSERT_EQ(0, simCache->get_hit_counter());
+ size_t usage = simCache->GetUsage();
+ ASSERT_LT(0, usage);
+ ASSERT_EQ(usage, simCache->GetSimUsage());
+ simCache->SetCapacity(usage);
+ ASSERT_EQ(usage, simCache->GetPinnedUsage());
+
+ // Test with strict capacity limit.
+ simCache->SetStrictCapacityLimit(true);
+ iter = db_->NewIterator(read_options);
+ iter->Seek(ToString(kNumBlocks * 2 - 1));
+ ASSERT_TRUE(iter->status().IsIncomplete());
+ CheckCacheCounters(options, 1, 0, 0, 1);
+ delete iter;
+ iter = nullptr;
+
+ // Release iterators and access cache again.
+ for (size_t i = 0; i < kNumBlocks; i++) {
+ iterators[i].reset();
+ CheckCacheCounters(options, 0, 0, 0, 0);
+ }
+ // Add kNumBlocks again
+ for (size_t i = 0; i < kNumBlocks; i++) {
+ std::unique_ptr<Iterator> it(db_->NewIterator(read_options));
+ it->Seek(ToString(i));
+ ASSERT_OK(it->status());
+ CheckCacheCounters(options, 0, 1, 0, 0);
+ }
+ ASSERT_EQ(5, simCache->get_hit_counter());
+ for (size_t i = kNumBlocks; i < kNumBlocks * 2; i++) {
+ std::unique_ptr<Iterator> it(db_->NewIterator(read_options));
+ it->Seek(ToString(i));
+ ASSERT_OK(it->status());
+ CheckCacheCounters(options, 1, 0, 1, 0);
+ }
+ ASSERT_EQ(0, simCache->GetPinnedUsage());
+ ASSERT_EQ(3 * kNumBlocks + 1,
+ simCache->get_hit_counter() + simCache->get_miss_counter());
+ ASSERT_EQ(6, simCache->get_hit_counter());
+}
+
+TEST_F(SimCacheTest, SimCacheLogging) {
+ auto table_options = GetTableOptions();
+ auto options = GetOptions(table_options);
+ options.disable_auto_compactions = true;
+ LRUCacheOptions co;
+ co.capacity = 1024 * 1024;
+ co.metadata_charge_policy = kDontChargeCacheMetadata;
+ std::shared_ptr<SimCache> sim_cache = NewSimCache(NewLRUCache(co), 20000, 0);
+ table_options.block_cache = sim_cache;
+ options.table_factory.reset(new BlockBasedTableFactory(table_options));
+ Reopen(options);
+
+ int num_block_entries = 20;
+ for (int i = 0; i < num_block_entries; i++) {
+ Put(Key(i), "val");
+ Flush();
+ }
+
+ std::string log_file = test::PerThreadDBPath(env_, "cache_log.txt");
+ ASSERT_OK(sim_cache->StartActivityLogging(log_file, env_));
+ for (int i = 0; i < num_block_entries; i++) {
+ ASSERT_EQ(Get(Key(i)), "val");
+ }
+ for (int i = 0; i < num_block_entries; i++) {
+ ASSERT_EQ(Get(Key(i)), "val");
+ }
+ sim_cache->StopActivityLogging();
+ ASSERT_OK(sim_cache->GetActivityLoggingStatus());
+
+ std::string file_contents = "";
+ ReadFileToString(env_, log_file, &file_contents);
+
+ int lookup_num = 0;
+ int add_num = 0;
+ std::string::size_type pos;
+
+ // count number of lookups
+ pos = 0;
+ while ((pos = file_contents.find("LOOKUP -", pos)) != std::string::npos) {
+ ++lookup_num;
+ pos += 1;
+ }
+
+ // count number of additions
+ pos = 0;
+ while ((pos = file_contents.find("ADD -", pos)) != std::string::npos) {
+ ++add_num;
+ pos += 1;
+ }
+
+ // We asked for every block twice
+ ASSERT_EQ(lookup_num, num_block_entries * 2);
+
+ // We added every block only once, since the cache can hold all blocks
+ ASSERT_EQ(add_num, num_block_entries);
+
+ // Log things again but stop logging automatically after reaching 512 bytes
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ int max_size = 512;
+ ASSERT_OK(sim_cache->StartActivityLogging(log_file, env_, max_size));
+ for (int it = 0; it < 10; it++) {
+ for (int i = 0; i < num_block_entries; i++) {
+ ASSERT_EQ(Get(Key(i)), "val");
+ }
+ }
+ ASSERT_OK(sim_cache->GetActivityLoggingStatus());
+
+ uint64_t fsize = 0;
+ ASSERT_OK(env_->GetFileSize(log_file, &fsize));
+ // error margin of 100 bytes
+ ASSERT_LT(fsize, max_size + 100);
+ ASSERT_GT(fsize, max_size - 100);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.cc b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.cc
new file mode 100644
index 00000000..89d666d4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+#include "utilities/table_properties_collectors/compact_on_deletion_collector.h"
+
+#include <memory>
+#include "rocksdb/utilities/table_properties_collectors.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+CompactOnDeletionCollector::CompactOnDeletionCollector(
+ size_t sliding_window_size, size_t deletion_trigger)
+ : bucket_size_((sliding_window_size + kNumBuckets - 1) / kNumBuckets),
+ current_bucket_(0),
+ num_keys_in_current_bucket_(0),
+ num_deletions_in_observation_window_(0),
+ deletion_trigger_(deletion_trigger),
+ need_compaction_(false),
+ finished_(false) {
+ memset(num_deletions_in_buckets_, 0, sizeof(size_t) * kNumBuckets);
+}
+
+// AddUserKey() will be called when a new key/value pair is inserted into the
+// table.
+// @params key the user key that is inserted into the table.
+// @params value the value that is inserted into the table.
+// @params file_size file size up to now
+Status CompactOnDeletionCollector::AddUserKey(const Slice& /*key*/,
+ const Slice& /*value*/,
+ EntryType type,
+ SequenceNumber /*seq*/,
+ uint64_t /*file_size*/) {
+ assert(!finished_);
+ if (bucket_size_ == 0) {
+ // This collector is effectively disabled
+ return Status::OK();
+ }
+
+ if (need_compaction_) {
+ // If the output file already needs to be compacted, skip the check.
+ return Status::OK();
+ }
+
+ if (num_keys_in_current_bucket_ == bucket_size_) {
+ // When the current bucket is full, advance the cursor of the
+ // ring buffer to the next bucket.
+ current_bucket_ = (current_bucket_ + 1) % kNumBuckets;
+
+ // Update the current count of observed deletion keys by excluding
+ // the number of deletion keys in the oldest bucket in the
+ // observation window.
+ assert(num_deletions_in_observation_window_ >=
+ num_deletions_in_buckets_[current_bucket_]);
+ num_deletions_in_observation_window_ -=
+ num_deletions_in_buckets_[current_bucket_];
+ num_deletions_in_buckets_[current_bucket_] = 0;
+ num_keys_in_current_bucket_ = 0;
+ }
+
+ num_keys_in_current_bucket_++;
+ if (type == kEntryDelete) {
+ num_deletions_in_observation_window_++;
+ num_deletions_in_buckets_[current_bucket_]++;
+ if (num_deletions_in_observation_window_ >= deletion_trigger_) {
+ need_compaction_ = true;
+ }
+ }
+ return Status::OK();
+}
+
+TablePropertiesCollector*
+CompactOnDeletionCollectorFactory::CreateTablePropertiesCollector(
+ TablePropertiesCollectorFactory::Context /*context*/) {
+ return new CompactOnDeletionCollector(
+ sliding_window_size_.load(), deletion_trigger_.load());
+}
+
+std::shared_ptr<CompactOnDeletionCollectorFactory>
+ NewCompactOnDeletionCollectorFactory(
+ size_t sliding_window_size,
+ size_t deletion_trigger) {
+ return std::shared_ptr<CompactOnDeletionCollectorFactory>(
+ new CompactOnDeletionCollectorFactory(
+ sliding_window_size, deletion_trigger));
+}
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.h b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.h
new file mode 100644
index 00000000..cc559ab2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+#include "rocksdb/utilities/table_properties_collectors.h"
+namespace ROCKSDB_NAMESPACE {
+
+class CompactOnDeletionCollector : public TablePropertiesCollector {
+ public:
+ CompactOnDeletionCollector(
+ size_t sliding_window_size,
+ size_t deletion_trigger);
+
+ // AddUserKey() will be called when a new key/value pair is inserted into the
+ // table.
+ // @params key the user key that is inserted into the table.
+ // @params value the value that is inserted into the table.
+ // @params file_size file size up to now
+ virtual Status AddUserKey(const Slice& key, const Slice& value,
+ EntryType type, SequenceNumber seq,
+ uint64_t file_size) override;
+
+ // Finish() will be called when a table has already been built and is ready
+ // for writing the properties block.
+ // @params properties User will add their collected statistics to
+ // `properties`.
+ virtual Status Finish(UserCollectedProperties* /*properties*/) override {
+ finished_ = true;
+ return Status::OK();
+ }
+
+ // Return the human-readable properties, where the key is property name and
+ // the value is the human-readable form of value.
+ virtual UserCollectedProperties GetReadableProperties() const override {
+ return UserCollectedProperties();
+ }
+
+ // The name of the properties collector can be used for debugging purpose.
+ virtual const char* Name() const override {
+ return "CompactOnDeletionCollector";
+ }
+
+ // EXPERIMENTAL Return whether the output file should be further compacted
+ virtual bool NeedCompact() const override {
+ return need_compaction_;
+ }
+
+ static const int kNumBuckets = 128;
+
+ private:
+ void Reset();
+
+ // A ring buffer that used to count the number of deletion entries for every
+ // "bucket_size_" keys.
+ size_t num_deletions_in_buckets_[kNumBuckets];
+ // the number of keys in a bucket
+ size_t bucket_size_;
+
+ size_t current_bucket_;
+ size_t num_keys_in_current_bucket_;
+ size_t num_deletions_in_observation_window_;
+ size_t deletion_trigger_;
+ // true if the current SST file needs to be compacted.
+ bool need_compaction_;
+ bool finished_;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector_test.cc b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
new file mode 100644
index 00000000..9b94cc27
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <stdio.h>
+
+#ifndef ROCKSDB_LITE
+#include <algorithm>
+#include <cmath>
+#include <vector>
+
+#include "rocksdb/table.h"
+#include "rocksdb/table_properties.h"
+#include "rocksdb/utilities/table_properties_collectors.h"
+#include "util/random.h"
+#include "utilities/table_properties_collectors/compact_on_deletion_collector.h"
+
+int main(int /*argc*/, char** /*argv*/) {
+ const int kWindowSizes[] =
+ {1000, 10000, 10000, 127, 128, 129, 255, 256, 257, 2, 10000};
+ const int kDeletionTriggers[] =
+ {500, 9500, 4323, 47, 61, 128, 250, 250, 250, 2, 2};
+ ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory::Context context;
+ context.column_family_id = ROCKSDB_NAMESPACE::
+ TablePropertiesCollectorFactory::Context::kUnknownColumnFamily;
+
+ std::vector<int> window_sizes;
+ std::vector<int> deletion_triggers;
+ // deterministic tests
+ for (int test = 0; test < 9; ++test) {
+ window_sizes.emplace_back(kWindowSizes[test]);
+ deletion_triggers.emplace_back(kDeletionTriggers[test]);
+ }
+
+ // randomize tests
+ ROCKSDB_NAMESPACE::Random rnd(301);
+ const int kMaxTestSize = 100000l;
+ for (int random_test = 0; random_test < 10; random_test++) {
+ int window_size = rnd.Uniform(kMaxTestSize) + 1;
+ int deletion_trigger = rnd.Uniform(window_size);
+ window_sizes.emplace_back(window_size);
+ deletion_triggers.emplace_back(deletion_trigger);
+ }
+
+ assert(window_sizes.size() == deletion_triggers.size());
+
+ for (size_t test = 0; test < window_sizes.size(); ++test) {
+ const int kBucketSize = 128;
+ const int kWindowSize = window_sizes[test];
+ const int kPaddedWindowSize =
+ kBucketSize * ((window_sizes[test] + kBucketSize - 1) / kBucketSize);
+ const int kNumDeletionTrigger = deletion_triggers[test];
+ const int kBias = (kNumDeletionTrigger + kBucketSize - 1) / kBucketSize;
+ // Simple test
+ {
+ auto factory = ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory(
+ kWindowSize, kNumDeletionTrigger);
+ const int kSample = 10;
+ for (int delete_rate = 0; delete_rate <= kSample; ++delete_rate) {
+ std::unique_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollector> collector(
+ factory->CreateTablePropertiesCollector(context));
+ int deletions = 0;
+ for (int i = 0; i < kPaddedWindowSize; ++i) {
+ if (i % kSample < delete_rate) {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryDelete, 0, 0);
+ deletions++;
+ } else {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryPut, 0, 0);
+ }
+ }
+ if (collector->NeedCompact() !=
+ (deletions >= kNumDeletionTrigger) &&
+ std::abs(deletions - kNumDeletionTrigger) > kBias) {
+ fprintf(stderr, "[Error] collector->NeedCompact() != (%d >= %d)"
+ " with kWindowSize = %d and kNumDeletionTrigger = %d\n",
+ deletions, kNumDeletionTrigger,
+ kWindowSize, kNumDeletionTrigger);
+ assert(false);
+ }
+ collector->Finish(nullptr);
+ }
+ }
+
+ // Only one section of a file satisfies the compaction trigger
+ {
+ auto factory = ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory(
+ kWindowSize, kNumDeletionTrigger);
+ const int kSample = 10;
+ for (int delete_rate = 0; delete_rate <= kSample; ++delete_rate) {
+ std::unique_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollector> collector(
+ factory->CreateTablePropertiesCollector(context));
+ int deletions = 0;
+ for (int section = 0; section < 5; ++section) {
+ int initial_entries = rnd.Uniform(kWindowSize) + kWindowSize;
+ for (int i = 0; i < initial_entries; ++i) {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryPut, 0, 0);
+ }
+ }
+ for (int i = 0; i < kPaddedWindowSize; ++i) {
+ if (i % kSample < delete_rate) {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryDelete, 0, 0);
+ deletions++;
+ } else {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryPut, 0, 0);
+ }
+ }
+ for (int section = 0; section < 5; ++section) {
+ int ending_entries = rnd.Uniform(kWindowSize) + kWindowSize;
+ for (int i = 0; i < ending_entries; ++i) {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryPut, 0, 0);
+ }
+ }
+ if (collector->NeedCompact() != (deletions >= kNumDeletionTrigger) &&
+ std::abs(deletions - kNumDeletionTrigger) > kBias) {
+ fprintf(stderr, "[Error] collector->NeedCompact() %d != (%d >= %d)"
+ " with kWindowSize = %d, kNumDeletionTrigger = %d\n",
+ collector->NeedCompact(),
+ deletions, kNumDeletionTrigger, kWindowSize,
+ kNumDeletionTrigger);
+ assert(false);
+ }
+ collector->Finish(nullptr);
+ }
+ }
+
+ // TEST 3: Issues a lots of deletes, but their density is not
+ // high enough to trigger compaction.
+ {
+ std::unique_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollector> collector;
+ auto factory = ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory(
+ kWindowSize, kNumDeletionTrigger);
+ collector.reset(factory->CreateTablePropertiesCollector(context));
+ assert(collector->NeedCompact() == false);
+ // Insert "kNumDeletionTrigger * 0.95" deletions for every
+ // "kWindowSize" and verify compaction is not needed.
+ const int kDeletionsPerSection = kNumDeletionTrigger * 95 / 100;
+ if (kDeletionsPerSection >= 0) {
+ for (int section = 0; section < 200; ++section) {
+ for (int i = 0; i < kPaddedWindowSize; ++i) {
+ if (i < kDeletionsPerSection) {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryDelete, 0, 0);
+ } else {
+ collector->AddUserKey("hello", "rocksdb",
+ ROCKSDB_NAMESPACE::kEntryPut, 0, 0);
+ }
+ }
+ }
+ if (collector->NeedCompact() &&
+ std::abs(kDeletionsPerSection - kNumDeletionTrigger) > kBias) {
+ fprintf(stderr, "[Error] collector->NeedCompact() != false"
+ " with kWindowSize = %d and kNumDeletionTrigger = %d\n",
+ kWindowSize, kNumDeletionTrigger);
+ assert(false);
+ }
+ collector->Finish(nullptr);
+ }
+ }
+ }
+ fprintf(stderr, "PASSED\n");
+}
+#else
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as RocksDBLite does not include utilities.\n");
+ return 0;
+}
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.cc b/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.cc
new file mode 100644
index 00000000..7160f7a4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 "utilities/trace/file_trace_reader_writer.h"
+
+#include "env/composite_env_wrapper.h"
+#include "file/random_access_file_reader.h"
+#include "file/writable_file_writer.h"
+#include "trace_replay/trace_replay.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+const unsigned int FileTraceReader::kBufferSize = 1024; // 1KB
+
+FileTraceReader::FileTraceReader(
+ std::unique_ptr<RandomAccessFileReader>&& reader)
+ : file_reader_(std::move(reader)),
+ offset_(0),
+ buffer_(new char[kBufferSize]) {}
+
+FileTraceReader::~FileTraceReader() {
+ Close();
+ delete[] buffer_;
+}
+
+Status FileTraceReader::Close() {
+ file_reader_.reset();
+ return Status::OK();
+}
+
+Status FileTraceReader::Read(std::string* data) {
+ assert(file_reader_ != nullptr);
+ Status s = file_reader_->Read(offset_, kTraceMetadataSize, &result_, buffer_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (result_.size() == 0) {
+ // No more data to read
+ // Todo: Come up with a better way to indicate end of data. May be this
+ // could be avoided once footer is introduced.
+ return Status::Incomplete();
+ }
+ if (result_.size() < kTraceMetadataSize) {
+ return Status::Corruption("Corrupted trace file.");
+ }
+ *data = result_.ToString();
+ offset_ += kTraceMetadataSize;
+
+ uint32_t payload_len =
+ DecodeFixed32(&buffer_[kTraceTimestampSize + kTraceTypeSize]);
+
+ // Read Payload
+ unsigned int bytes_to_read = payload_len;
+ unsigned int to_read =
+ bytes_to_read > kBufferSize ? kBufferSize : bytes_to_read;
+ while (to_read > 0) {
+ s = file_reader_->Read(offset_, to_read, &result_, buffer_);
+ if (!s.ok()) {
+ return s;
+ }
+ if (result_.size() < to_read) {
+ return Status::Corruption("Corrupted trace file.");
+ }
+ data->append(result_.data(), result_.size());
+
+ offset_ += to_read;
+ bytes_to_read -= to_read;
+ to_read = bytes_to_read > kBufferSize ? kBufferSize : bytes_to_read;
+ }
+
+ return s;
+}
+
+FileTraceWriter::~FileTraceWriter() { Close(); }
+
+Status FileTraceWriter::Close() {
+ file_writer_.reset();
+ return Status::OK();
+}
+
+Status FileTraceWriter::Write(const Slice& data) {
+ return file_writer_->Append(data);
+}
+
+uint64_t FileTraceWriter::GetFileSize() { return file_writer_->GetFileSize(); }
+
+Status NewFileTraceReader(Env* env, const EnvOptions& env_options,
+ const std::string& trace_filename,
+ std::unique_ptr<TraceReader>* trace_reader) {
+ std::unique_ptr<RandomAccessFile> trace_file;
+ Status s = env->NewRandomAccessFile(trace_filename, &trace_file, env_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<RandomAccessFileReader> file_reader;
+ file_reader.reset(new RandomAccessFileReader(
+ NewLegacyRandomAccessFileWrapper(trace_file), trace_filename));
+ trace_reader->reset(new FileTraceReader(std::move(file_reader)));
+ return s;
+}
+
+Status NewFileTraceWriter(Env* env, const EnvOptions& env_options,
+ const std::string& trace_filename,
+ std::unique_ptr<TraceWriter>* trace_writer) {
+ std::unique_ptr<WritableFile> trace_file;
+ Status s = env->NewWritableFile(trace_filename, &trace_file, env_options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ std::unique_ptr<WritableFileWriter> file_writer;
+ file_writer.reset(new WritableFileWriter(
+ NewLegacyWritableFileWrapper(std::move(trace_file)), trace_filename,
+ env_options));
+ trace_writer->reset(new FileTraceWriter(std::move(file_writer)));
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.h b/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.h
new file mode 100644
index 00000000..a9eafa5a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/trace/file_trace_reader_writer.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/trace_reader_writer.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class RandomAccessFileReader;
+class WritableFileWriter;
+
+// FileTraceReader allows reading RocksDB traces from a file.
+class FileTraceReader : public TraceReader {
+ public:
+ explicit FileTraceReader(std::unique_ptr<RandomAccessFileReader>&& reader);
+ ~FileTraceReader();
+
+ virtual Status Read(std::string* data) override;
+ virtual Status Close() override;
+
+ private:
+ std::unique_ptr<RandomAccessFileReader> file_reader_;
+ Slice result_;
+ size_t offset_;
+ char* const buffer_;
+
+ static const unsigned int kBufferSize;
+};
+
+// FileTraceWriter allows writing RocksDB traces to a file.
+class FileTraceWriter : public TraceWriter {
+ public:
+ explicit FileTraceWriter(std::unique_ptr<WritableFileWriter>&& file_writer)
+ : file_writer_(std::move(file_writer)) {}
+ ~FileTraceWriter();
+
+ virtual Status Write(const Slice& data) override;
+ virtual Status Close() override;
+ virtual uint64_t GetFileSize() override;
+
+ private:
+ std::unique_ptr<WritableFileWriter> file_writer_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.cc b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.cc
new file mode 100644
index 00000000..b01102bb
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/optimistic_transaction.h"
+
+#include <string>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+#include "utilities/transactions/transaction_util.h"
+#include "utilities/transactions/optimistic_transaction.h"
+#include "utilities/transactions/optimistic_transaction_db_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct WriteOptions;
+
+OptimisticTransaction::OptimisticTransaction(
+ OptimisticTransactionDB* txn_db, const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options)
+ : TransactionBaseImpl(txn_db->GetBaseDB(), write_options), txn_db_(txn_db) {
+ Initialize(txn_options);
+}
+
+void OptimisticTransaction::Initialize(
+ const OptimisticTransactionOptions& txn_options) {
+ if (txn_options.set_snapshot) {
+ SetSnapshot();
+ }
+}
+
+void OptimisticTransaction::Reinitialize(
+ OptimisticTransactionDB* txn_db, const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options) {
+ TransactionBaseImpl::Reinitialize(txn_db->GetBaseDB(), write_options);
+ Initialize(txn_options);
+}
+
+OptimisticTransaction::~OptimisticTransaction() {}
+
+void OptimisticTransaction::Clear() { TransactionBaseImpl::Clear(); }
+
+Status OptimisticTransaction::Prepare() {
+ return Status::InvalidArgument(
+ "Two phase commit not supported for optimistic transactions.");
+}
+
+Status OptimisticTransaction::Commit() {
+ auto txn_db_impl = static_cast_with_check<OptimisticTransactionDBImpl,
+ OptimisticTransactionDB>(txn_db_);
+ assert(txn_db_impl);
+ switch (txn_db_impl->GetValidatePolicy()) {
+ case OccValidationPolicy::kValidateParallel:
+ return CommitWithParallelValidate();
+ case OccValidationPolicy::kValidateSerial:
+ return CommitWithSerialValidate();
+ default:
+ assert(0);
+ }
+ // unreachable, just void compiler complain
+ return Status::OK();
+}
+
+Status OptimisticTransaction::CommitWithSerialValidate() {
+ // Set up callback which will call CheckTransactionForConflicts() to
+ // check whether this transaction is safe to be committed.
+ OptimisticTransactionCallback callback(this);
+
+ DBImpl* db_impl = static_cast_with_check<DBImpl, DB>(db_->GetRootDB());
+
+ Status s = db_impl->WriteWithCallback(
+ write_options_, GetWriteBatch()->GetWriteBatch(), &callback);
+
+ if (s.ok()) {
+ Clear();
+ }
+
+ return s;
+}
+
+Status OptimisticTransaction::CommitWithParallelValidate() {
+ auto txn_db_impl = static_cast_with_check<OptimisticTransactionDBImpl,
+ OptimisticTransactionDB>(txn_db_);
+ assert(txn_db_impl);
+ DBImpl* db_impl = static_cast_with_check<DBImpl, DB>(db_->GetRootDB());
+ assert(db_impl);
+ const size_t space = txn_db_impl->GetLockBucketsSize();
+ std::set<size_t> lk_idxes;
+ std::vector<std::unique_lock<std::mutex>> lks;
+ for (auto& cfit : GetTrackedKeys()) {
+ for (auto& keyit : cfit.second) {
+ lk_idxes.insert(fastrange64(GetSliceNPHash64(keyit.first), space));
+ }
+ }
+ // NOTE: in a single txn, all bucket-locks are taken in ascending order.
+ // In this way, txns from different threads all obey this rule so that
+ // deadlock can be avoided.
+ for (auto v : lk_idxes) {
+ lks.emplace_back(txn_db_impl->LockBucket(v));
+ }
+
+ Status s = TransactionUtil::CheckKeysForConflicts(db_impl, GetTrackedKeys(),
+ true /* cache_only */);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = db_impl->Write(write_options_, GetWriteBatch()->GetWriteBatch());
+ if (s.ok()) {
+ Clear();
+ }
+
+ return s;
+}
+
+Status OptimisticTransaction::Rollback() {
+ Clear();
+ return Status::OK();
+}
+
+// Record this key so that we can check it for conflicts at commit time.
+//
+// 'exclusive' is unused for OptimisticTransaction.
+Status OptimisticTransaction::TryLock(ColumnFamilyHandle* column_family,
+ const Slice& key, bool read_only,
+ bool exclusive, const bool do_validate,
+ const bool assume_tracked) {
+ assert(!assume_tracked); // not supported
+ (void)assume_tracked;
+ if (!do_validate) {
+ return Status::OK();
+ }
+ uint32_t cfh_id = GetColumnFamilyID(column_family);
+
+ SetSnapshotIfNeeded();
+
+ SequenceNumber seq;
+ if (snapshot_) {
+ seq = snapshot_->GetSequenceNumber();
+ } else {
+ seq = db_->GetLatestSequenceNumber();
+ }
+
+ std::string key_str = key.ToString();
+
+ TrackKey(cfh_id, key_str, seq, read_only, exclusive);
+
+ // Always return OK. Confilct checking will happen at commit time.
+ return Status::OK();
+}
+
+// Returns OK if it is safe to commit this transaction. Returns Status::Busy
+// if there are read or write conflicts that would prevent us from committing OR
+// if we can not determine whether there would be any such conflicts.
+//
+// Should only be called on writer thread in order to avoid any race conditions
+// in detecting write conflicts.
+Status OptimisticTransaction::CheckTransactionForConflicts(DB* db) {
+ Status result;
+
+ auto db_impl = static_cast_with_check<DBImpl, DB>(db);
+
+ // Since we are on the write thread and do not want to block other writers,
+ // we will do a cache-only conflict check. This can result in TryAgain
+ // getting returned if there is not sufficient memtable history to check
+ // for conflicts.
+ return TransactionUtil::CheckKeysForConflicts(db_impl, GetTrackedKeys(),
+ true /* cache_only */);
+}
+
+Status OptimisticTransaction::SetName(const TransactionName& /* unused */) {
+ return Status::InvalidArgument("Optimistic transactions cannot be named.");
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.h b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.h
new file mode 100644
index 00000000..c337de2a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <stack>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "db/write_callback.h"
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "utilities/transactions/transaction_base.h"
+#include "utilities/transactions/transaction_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class OptimisticTransaction : public TransactionBaseImpl {
+ public:
+ OptimisticTransaction(OptimisticTransactionDB* db,
+ const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options);
+ // No copying allowed
+ OptimisticTransaction(const OptimisticTransaction&) = delete;
+ void operator=(const OptimisticTransaction&) = delete;
+
+ virtual ~OptimisticTransaction();
+
+ void Reinitialize(OptimisticTransactionDB* txn_db,
+ const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options);
+
+ Status Prepare() override;
+
+ Status Commit() override;
+
+ Status Rollback() override;
+
+ Status SetName(const TransactionName& name) override;
+
+ protected:
+ Status TryLock(ColumnFamilyHandle* column_family, const Slice& key,
+ bool read_only, bool exclusive, const bool do_validate = true,
+ const bool assume_tracked = false) override;
+
+ private:
+ ROCKSDB_FIELD_UNUSED OptimisticTransactionDB* const txn_db_;
+
+ friend class OptimisticTransactionCallback;
+
+ void Initialize(const OptimisticTransactionOptions& txn_options);
+
+ // Returns OK if it is safe to commit this transaction. Returns Status::Busy
+ // if there are read or write conflicts that would prevent us from committing
+ // OR if we can not determine whether there would be any such conflicts.
+ //
+ // Should only be called on writer thread.
+ Status CheckTransactionForConflicts(DB* db);
+
+ void Clear() override;
+
+ void UnlockGetForUpdate(ColumnFamilyHandle* /* unused */,
+ const Slice& /* unused */) override {
+ // Nothing to unlock.
+ }
+
+ Status CommitWithSerialValidate();
+
+ Status CommitWithParallelValidate();
+};
+
+// Used at commit time to trigger transaction validation
+class OptimisticTransactionCallback : public WriteCallback {
+ public:
+ explicit OptimisticTransactionCallback(OptimisticTransaction* txn)
+ : txn_(txn) {}
+
+ Status Callback(DB* db) override {
+ return txn_->CheckTransactionForConflicts(db);
+ }
+
+ bool AllowWriteBatching() override { return false; }
+
+ private:
+ OptimisticTransaction* txn_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.cc b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.cc
new file mode 100644
index 00000000..bffb3d5e
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/optimistic_transaction_db_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "utilities/transactions/optimistic_transaction.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Transaction* OptimisticTransactionDBImpl::BeginTransaction(
+ const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options, Transaction* old_txn) {
+ if (old_txn != nullptr) {
+ ReinitializeTransaction(old_txn, write_options, txn_options);
+ return old_txn;
+ } else {
+ return new OptimisticTransaction(this, write_options, txn_options);
+ }
+}
+
+std::unique_lock<std::mutex> OptimisticTransactionDBImpl::LockBucket(
+ size_t idx) {
+ assert(idx < bucketed_locks_.size());
+ return std::unique_lock<std::mutex>(*bucketed_locks_[idx]);
+}
+
+Status OptimisticTransactionDB::Open(const Options& options,
+ const std::string& dbname,
+ OptimisticTransactionDB** dbptr) {
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> handles;
+ Status s = Open(db_options, dbname, column_families, &handles, dbptr);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ // i can delete the handle since DBImpl is always holding a reference to
+ // default column family
+ delete handles[0];
+ }
+
+ return s;
+}
+
+Status OptimisticTransactionDB::Open(
+ const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ OptimisticTransactionDB** dbptr) {
+ return OptimisticTransactionDB::Open(db_options,
+ OptimisticTransactionDBOptions(), dbname,
+ column_families, handles, dbptr);
+}
+
+Status OptimisticTransactionDB::Open(
+ const DBOptions& db_options,
+ const OptimisticTransactionDBOptions& occ_options,
+ const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles,
+ OptimisticTransactionDB** dbptr) {
+ Status s;
+ DB* db;
+
+ std::vector<ColumnFamilyDescriptor> column_families_copy = column_families;
+
+ // Enable MemTable History if not already enabled
+ for (auto& column_family : column_families_copy) {
+ ColumnFamilyOptions* options = &column_family.options;
+
+ if (options->max_write_buffer_size_to_maintain == 0 &&
+ options->max_write_buffer_number_to_maintain == 0) {
+ // Setting to -1 will set the History size to
+ // max_write_buffer_number * write_buffer_size.
+ options->max_write_buffer_size_to_maintain = -1;
+ }
+ }
+
+ s = DB::Open(db_options, dbname, column_families_copy, handles, &db);
+
+ if (s.ok()) {
+ *dbptr = new OptimisticTransactionDBImpl(db, occ_options);
+ }
+
+ return s;
+}
+
+void OptimisticTransactionDBImpl::ReinitializeTransaction(
+ Transaction* txn, const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options) {
+ assert(dynamic_cast<OptimisticTransaction*>(txn) != nullptr);
+ auto txn_impl = reinterpret_cast<OptimisticTransaction*>(txn);
+
+ txn_impl->Reinitialize(this, write_options, txn_options);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.h b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.h
new file mode 100644
index 00000000..d895d49b
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_db_impl.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <mutex>
+#include <vector>
+#include <algorithm>
+
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class OptimisticTransactionDBImpl : public OptimisticTransactionDB {
+ public:
+ explicit OptimisticTransactionDBImpl(
+ DB* db, const OptimisticTransactionDBOptions& occ_options,
+ bool take_ownership = true)
+ : OptimisticTransactionDB(db),
+ db_owner_(take_ownership),
+ validate_policy_(occ_options.validate_policy) {
+ if (validate_policy_ == OccValidationPolicy::kValidateParallel) {
+ uint32_t bucket_size = std::max(16u, occ_options.occ_lock_buckets);
+ bucketed_locks_.reserve(bucket_size);
+ for (size_t i = 0; i < bucket_size; ++i) {
+ bucketed_locks_.emplace_back(
+ std::unique_ptr<std::mutex>(new std::mutex));
+ }
+ }
+ }
+
+ ~OptimisticTransactionDBImpl() {
+ // Prevent this stackable from destroying
+ // base db
+ if (!db_owner_) {
+ db_ = nullptr;
+ }
+ }
+
+ Transaction* BeginTransaction(const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options,
+ Transaction* old_txn) override;
+
+ size_t GetLockBucketsSize() const { return bucketed_locks_.size(); }
+
+ OccValidationPolicy GetValidatePolicy() const { return validate_policy_; }
+
+ std::unique_lock<std::mutex> LockBucket(size_t idx);
+
+ private:
+ // NOTE: used in validation phase. Each key is hashed into some
+ // bucket. We then take the lock in the hash value order to avoid deadlock.
+ std::vector<std::unique_ptr<std::mutex>> bucketed_locks_;
+
+ bool db_owner_;
+
+ const OccValidationPolicy validate_policy_;
+
+ void ReinitializeTransaction(Transaction* txn,
+ const WriteOptions& write_options,
+ const OptimisticTransactionOptions& txn_options =
+ OptimisticTransactionOptions());
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_test.cc b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_test.cc
new file mode 100644
index 00000000..63c1a255
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/optimistic_transaction_test.cc
@@ -0,0 +1,1535 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include <functional>
+#include <string>
+#include <thread>
+
+#include "db/db_impl/db_impl.h"
+#include "logging/logging.h"
+#include "port/port.h"
+#include "rocksdb/db.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/utilities/optimistic_transaction_db.h"
+#include "rocksdb/utilities/transaction.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/transaction_test_util.h"
+#include "util/crc32c.h"
+#include "util/random.h"
+
+using std::string;
+
+namespace ROCKSDB_NAMESPACE {
+
+class OptimisticTransactionTest
+ : public testing::Test,
+ public testing::WithParamInterface<OccValidationPolicy> {
+ public:
+ OptimisticTransactionDB* txn_db;
+ string dbname;
+ Options options;
+
+ OptimisticTransactionTest() {
+ options.create_if_missing = true;
+ options.max_write_buffer_number = 2;
+ options.max_write_buffer_size_to_maintain = 1600;
+ dbname = test::PerThreadDBPath("optimistic_transaction_testdb");
+
+ DestroyDB(dbname, options);
+ Open();
+ }
+ ~OptimisticTransactionTest() override {
+ delete txn_db;
+ DestroyDB(dbname, options);
+ }
+
+ void Reopen() {
+ delete txn_db;
+ txn_db = nullptr;
+ Open();
+ }
+
+private:
+ void Open() {
+ ColumnFamilyOptions cf_options(options);
+ OptimisticTransactionDBOptions occ_opts;
+ occ_opts.validate_policy = GetParam();
+ std::vector<ColumnFamilyDescriptor> column_families;
+ std::vector<ColumnFamilyHandle*> handles;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ Status s =
+ OptimisticTransactionDB::Open(DBOptions(options), occ_opts, dbname,
+ column_families, &handles, &txn_db);
+
+ assert(s.ok());
+ assert(txn_db != nullptr);
+ assert(handles.size() == 1);
+ delete handles[0];
+ }
+};
+
+TEST_P(OptimisticTransactionTest, SuccessTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, Slice("foo"), Slice("bar"));
+ txn_db->Put(write_options, Slice("foo2"), Slice("bar"));
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->GetForUpdate(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ txn->Put(Slice("foo"), Slice("bar2"));
+
+ txn->GetForUpdate(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, WriteConflictTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "foo", "bar");
+ txn_db->Put(write_options, "foo2", "bar");
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->Put("foo", "bar2");
+
+ // This Put outside of a transaction will conflict with the previous write
+ s = txn_db->Put(write_options, "foo", "barz");
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+ ASSERT_EQ(1, txn->GetNumKeys());
+
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy()); // Txn should not commit
+
+ // Verify that transaction did not write anything
+ txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+ txn_db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "bar");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, WriteConflictTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "foo", "bar");
+ txn_db->Put(write_options, "foo2", "bar");
+
+ txn_options.set_snapshot = true;
+ Transaction* txn = txn_db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ // This Put outside of a transaction will conflict with a later write
+ s = txn_db->Put(write_options, "foo", "barz");
+ ASSERT_OK(s);
+
+ txn->Put("foo", "bar2"); // Conflicts with write done after snapshot taken
+
+ s = txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy()); // Txn should not commit
+
+ // Verify that transaction did not write anything
+ txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+ txn_db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "bar");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, ReadConflictTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "foo", "bar");
+ txn_db->Put(write_options, "foo2", "bar");
+
+ txn_options.set_snapshot = true;
+ Transaction* txn = txn_db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ // This Put outside of a transaction will conflict with the previous read
+ s = txn_db->Put(write_options, "foo", "barz");
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy()); // Txn should not commit
+
+ // Verify that transaction did not write anything
+ txn->GetForUpdate(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+ txn->GetForUpdate(read_options, "foo2", &value);
+ ASSERT_EQ(value, "bar");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, TxnOnlyTest) {
+ // Test to make sure transactions work when there are no other writes in an
+ // empty db.
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->Put("x", "y");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, FlushTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, Slice("foo"), Slice("bar"));
+ txn_db->Put(write_options, Slice("foo2"), Slice("bar"));
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ txn->Put(Slice("foo"), Slice("bar2"));
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ // Put a random key so we have a memtable to flush
+ s = txn_db->Put(write_options, "dummy", "dummy");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ FlushOptions flush_ops;
+ txn_db->Flush(flush_ops);
+
+ s = txn->Commit();
+ // txn should commit since the flushed table is still in MemtableList History
+ ASSERT_OK(s);
+
+ txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, FlushTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, Slice("foo"), Slice("bar"));
+ txn_db->Put(write_options, Slice("foo2"), Slice("bar"));
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ txn->Put(Slice("foo"), Slice("bar2"));
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ // Put a random key so we have a MemTable to flush
+ s = txn_db->Put(write_options, "dummy", "dummy");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ FlushOptions flush_ops;
+ txn_db->Flush(flush_ops);
+
+ // Put a random key so we have a MemTable to flush
+ s = txn_db->Put(write_options, "dummy", "dummy2");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ txn_db->Flush(flush_ops);
+
+ s = txn_db->Put(write_options, "dummy", "dummy3");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ // Since our test db has max_write_buffer_number=2, this flush will cause
+ // the first memtable to get purged from the MemtableList history.
+ txn_db->Flush(flush_ops);
+
+ s = txn->Commit();
+ // txn should not commit since MemTableList History is not large enough
+ ASSERT_TRUE(s.IsTryAgain());
+
+ txn_db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ delete txn;
+}
+
+// Trigger the condition where some old memtables are skipped when doing
+// TransactionUtil::CheckKey(), and make sure the result is still correct.
+TEST_P(OptimisticTransactionTest, CheckKeySkipOldMemtable) {
+ const int kAttemptHistoryMemtable = 0;
+ const int kAttemptImmMemTable = 1;
+ for (int attempt = kAttemptHistoryMemtable; attempt <= kAttemptImmMemTable;
+ attempt++) {
+ options.max_write_buffer_number_to_maintain = 3;
+ Reopen();
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ ReadOptions snapshot_read_options;
+ ReadOptions snapshot_read_options2;
+ string value;
+ Status s;
+
+ ASSERT_OK(txn_db->Put(write_options, Slice("foo"), Slice("bar")));
+ ASSERT_OK(txn_db->Put(write_options, Slice("foo2"), Slice("bar")));
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn != nullptr);
+
+ Transaction* txn2 = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn2 != nullptr);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+ ASSERT_OK(txn->GetForUpdate(snapshot_read_options, "foo", &value));
+ ASSERT_EQ(value, "bar");
+ ASSERT_OK(txn->Put(Slice("foo"), Slice("bar2")));
+
+ snapshot_read_options2.snapshot = txn2->GetSnapshot();
+ ASSERT_OK(txn2->GetForUpdate(snapshot_read_options2, "foo2", &value));
+ ASSERT_EQ(value, "bar");
+ ASSERT_OK(txn2->Put(Slice("foo2"), Slice("bar2")));
+
+ // txn updates "foo" and txn2 updates "foo2", and now a write is
+ // issued for "foo", which conflicts with txn but not txn2
+ ASSERT_OK(txn_db->Put(write_options, "foo", "bar"));
+
+ if (attempt == kAttemptImmMemTable) {
+ // For the second attempt, hold flush from beginning. The memtable
+ // will be switched to immutable after calling TEST_SwitchMemtable()
+ // while CheckKey() is called.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"OptimisticTransactionTest.CheckKeySkipOldMemtable",
+ "FlushJob::Start"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ }
+
+ // force a memtable flush. The memtable should still be kept
+ FlushOptions flush_ops;
+ if (attempt == kAttemptHistoryMemtable) {
+ ASSERT_OK(txn_db->Flush(flush_ops));
+ } else {
+ assert(attempt == kAttemptImmMemTable);
+ DBImpl* db_impl = static_cast<DBImpl*>(txn_db->GetRootDB());
+ db_impl->TEST_SwitchMemtable();
+ }
+ uint64_t num_imm_mems;
+ ASSERT_TRUE(txn_db->GetIntProperty(DB::Properties::kNumImmutableMemTable,
+ &num_imm_mems));
+ if (attempt == kAttemptHistoryMemtable) {
+ ASSERT_EQ(0, num_imm_mems);
+ } else {
+ assert(attempt == kAttemptImmMemTable);
+ ASSERT_EQ(1, num_imm_mems);
+ }
+
+ // Put something in active memtable
+ ASSERT_OK(txn_db->Put(write_options, Slice("foo3"), Slice("bar")));
+
+ // Create txn3 after flushing, when this transaction is commited,
+ // only need to check the active memtable
+ Transaction* txn3 = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn3 != nullptr);
+
+ // Commit both of txn and txn2. txn will conflict but txn2 will
+ // pass. In both ways, both memtables are queried.
+ SetPerfLevel(PerfLevel::kEnableCount);
+
+ get_perf_context()->Reset();
+ s = txn->Commit();
+ // We should have checked two memtables
+ ASSERT_EQ(2, get_perf_context()->get_from_memtable_count);
+ // txn should fail because of conflict, even if the memtable
+ // has flushed, because it is still preserved in history.
+ ASSERT_TRUE(s.IsBusy());
+
+ get_perf_context()->Reset();
+ s = txn2->Commit();
+ // We should have checked two memtables
+ ASSERT_EQ(2, get_perf_context()->get_from_memtable_count);
+ ASSERT_TRUE(s.ok());
+
+ txn3->Put(Slice("foo2"), Slice("bar2"));
+ get_perf_context()->Reset();
+ s = txn3->Commit();
+ // txn3 is created after the active memtable is created, so that is the only
+ // memtable to check.
+ ASSERT_EQ(1, get_perf_context()->get_from_memtable_count);
+ ASSERT_TRUE(s.ok());
+
+ TEST_SYNC_POINT("OptimisticTransactionTest.CheckKeySkipOldMemtable");
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ SetPerfLevel(PerfLevel::kDisable);
+
+ delete txn;
+ delete txn2;
+ delete txn3;
+ }
+}
+
+TEST_P(OptimisticTransactionTest, NoSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "AAA", "bar");
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ // Modify key after transaction start
+ txn_db->Put(write_options, "AAA", "bar1");
+
+ // Read and write without a snapshot
+ txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_EQ(value, "bar1");
+ txn->Put("AAA", "bar2");
+
+ // Should commit since read/write was done after data changed
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, MultipleSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "AAA", "bar");
+ txn_db->Put(write_options, "BBB", "bar");
+ txn_db->Put(write_options, "CCC", "bar");
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn_db->Put(write_options, "AAA", "bar1");
+
+ // Read and write without a snapshot
+ txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_EQ(value, "bar1");
+ txn->Put("AAA", "bar2");
+
+ // Modify BBB before snapshot is taken
+ txn_db->Put(write_options, "BBB", "bar1");
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ // Read and write with snapshot
+ txn->GetForUpdate(snapshot_read_options, "BBB", &value);
+ ASSERT_EQ(value, "bar1");
+ txn->Put("BBB", "bar2");
+
+ txn_db->Put(write_options, "CCC", "bar1");
+
+ // Set a new snapshot
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ // Read and write with snapshot
+ txn->GetForUpdate(snapshot_read_options, "CCC", &value);
+ ASSERT_EQ(value, "bar1");
+ txn->Put("CCC", "bar2");
+
+ s = txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn->GetForUpdate(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn->GetForUpdate(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+
+ s = txn_db->Get(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+ s = txn_db->Get(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+ s = txn_db->Get(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn_db->Get(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn_db->Get(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+
+ // verify that we track multiple writes to the same key at different snapshots
+ delete txn;
+ txn = txn_db->BeginTransaction(write_options);
+
+ // Potentially conflicting writes
+ txn_db->Put(write_options, "ZZZ", "zzz");
+ txn_db->Put(write_options, "XXX", "xxx");
+
+ txn->SetSnapshot();
+
+ OptimisticTransactionOptions txn_options;
+ txn_options.set_snapshot = true;
+ Transaction* txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ txn2->SetSnapshot();
+
+ // This should not conflict in txn since the snapshot is later than the
+ // previous write (spoiler alert: it will later conflict with txn2).
+ txn->Put("ZZZ", "zzzz");
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ // This will conflict since the snapshot is earlier than another write to ZZZ
+ txn2->Put("ZZZ", "xxxxx");
+
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn2;
+}
+
+TEST_P(OptimisticTransactionTest, ColumnFamiliesTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ ColumnFamilyHandle *cfa, *cfb;
+ ColumnFamilyOptions cf_options;
+
+ // Create 2 new column families
+ s = txn_db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = txn_db->CreateColumnFamily(cf_options, "CFB", &cfb);
+ ASSERT_OK(s);
+
+ delete cfa;
+ delete cfb;
+ delete txn_db;
+ txn_db = nullptr;
+
+ // open DB with three column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new column families
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFA", ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFB", ColumnFamilyOptions()));
+ std::vector<ColumnFamilyHandle*> handles;
+ s = OptimisticTransactionDB::Open(options, dbname, column_families, &handles,
+ &txn_db);
+ ASSERT_OK(s);
+ assert(txn_db != nullptr);
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn_options.set_snapshot = true;
+ Transaction* txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ // Write some data to the db
+ WriteBatch batch;
+ batch.Put("foo", "foo");
+ batch.Put(handles[1], "AAA", "bar");
+ batch.Put(handles[1], "AAAZZZ", "bar");
+ s = txn_db->Write(write_options, &batch);
+ ASSERT_OK(s);
+ txn_db->Delete(write_options, handles[1], "AAAZZZ");
+
+ // These keys do no conflict with existing writes since they're in
+ // different column families
+ txn->Delete("AAA");
+ txn->GetForUpdate(snapshot_read_options, handles[1], "foo", &value);
+ Slice key_slice("AAAZZZ");
+ Slice value_slices[2] = {Slice("bar"), Slice("bar")};
+ txn->Put(handles[2], SliceParts(&key_slice, 1), SliceParts(value_slices, 2));
+
+ ASSERT_EQ(3, txn->GetNumKeys());
+
+ // Txn should commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+ s = txn_db->Get(read_options, "AAA", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn_db->Get(read_options, handles[2], "AAAZZZ", &value);
+ ASSERT_EQ(value, "barbar");
+
+ Slice key_slices[3] = {Slice("AAA"), Slice("ZZ"), Slice("Z")};
+ Slice value_slice("barbarbar");
+ // This write will cause a conflict with the earlier batch write
+ txn2->Put(handles[1], SliceParts(key_slices, 3), SliceParts(&value_slice, 1));
+
+ txn2->Delete(handles[2], "XXX");
+ txn2->Delete(handles[1], "XXX");
+ s = txn2->GetForUpdate(snapshot_read_options, handles[1], "AAA", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Verify txn did not commit
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ s = txn_db->Get(read_options, handles[1], "AAAZZZ", &value);
+ ASSERT_EQ(value, "barbar");
+
+ delete txn;
+ delete txn2;
+
+ txn = txn_db->BeginTransaction(write_options, txn_options);
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ std::vector<ColumnFamilyHandle*> multiget_cfh = {handles[1], handles[2],
+ handles[0], handles[2]};
+ std::vector<Slice> multiget_keys = {"AAA", "AAAZZZ", "foo", "foo"};
+ std::vector<std::string> values(4);
+
+ std::vector<Status> results = txn->MultiGetForUpdate(
+ snapshot_read_options, multiget_cfh, multiget_keys, &values);
+ ASSERT_OK(results[0]);
+ ASSERT_OK(results[1]);
+ ASSERT_OK(results[2]);
+ ASSERT_TRUE(results[3].IsNotFound());
+ ASSERT_EQ(values[0], "bar");
+ ASSERT_EQ(values[1], "barbar");
+ ASSERT_EQ(values[2], "foo");
+
+ txn->Delete(handles[2], "ZZZ");
+ txn->Put(handles[2], "ZZZ", "YYY");
+ txn->Put(handles[2], "ZZZ", "YYYY");
+ txn->Delete(handles[2], "ZZZ");
+ txn->Put(handles[2], "AAAZZZ", "barbarbar");
+
+ ASSERT_EQ(5, txn->GetNumKeys());
+
+ // Txn should commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+ s = txn_db->Get(read_options, handles[2], "ZZZ", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Put a key which will conflict with the next txn using the previous snapshot
+ txn_db->Put(write_options, handles[2], "foo", "000");
+
+ results = txn2->MultiGetForUpdate(snapshot_read_options, multiget_cfh,
+ multiget_keys, &values);
+ ASSERT_OK(results[0]);
+ ASSERT_OK(results[1]);
+ ASSERT_OK(results[2]);
+ ASSERT_TRUE(results[3].IsNotFound());
+ ASSERT_EQ(values[0], "bar");
+ ASSERT_EQ(values[1], "barbar");
+ ASSERT_EQ(values[2], "foo");
+
+ // Verify Txn Did not Commit
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn_db->DropColumnFamily(handles[1]);
+ ASSERT_OK(s);
+ s = txn_db->DropColumnFamily(handles[2]);
+ ASSERT_OK(s);
+
+ delete txn;
+ delete txn2;
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+}
+
+TEST_P(OptimisticTransactionTest, EmptyTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ s = txn_db->Put(write_options, "aaa", "aaa");
+ ASSERT_OK(s);
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ txn = txn_db->BeginTransaction(write_options);
+ txn->Rollback();
+ delete txn;
+
+ txn = txn_db->BeginTransaction(write_options);
+ s = txn->GetForUpdate(read_options, "aaa", &value);
+ ASSERT_EQ(value, "aaa");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ txn = txn_db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+ s = txn->GetForUpdate(read_options, "aaa", &value);
+ ASSERT_EQ(value, "aaa");
+
+ s = txn_db->Put(write_options, "aaa", "xxx");
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, PredicateManyPreceders) {
+ WriteOptions write_options;
+ ReadOptions read_options1, read_options2;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_options.set_snapshot = true;
+ Transaction* txn1 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ Transaction* txn2 = txn_db->BeginTransaction(write_options);
+ txn2->SetSnapshot();
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ std::vector<Slice> multiget_keys = {"1", "2", "3"};
+ std::vector<std::string> multiget_values;
+
+ std::vector<Status> results =
+ txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
+ ASSERT_TRUE(results[1].IsNotFound());
+
+ txn2->Put("2", "x");
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ multiget_values.clear();
+ results =
+ txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
+ ASSERT_TRUE(results[1].IsNotFound());
+
+ // should not commit since txn2 wrote a key txn has read
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ txn1->Put("4", "x");
+
+ txn2->Delete("4");
+
+ // txn1 can commit since txn2's delete hasn't happened yet (it's just batched)
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options2, "4", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // txn2 cannot commit since txn1 changed "4"
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(OptimisticTransactionTest, LostUpdate) {
+ WriteOptions write_options;
+ ReadOptions read_options, read_options1, read_options2;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ // Test 2 transactions writing to the same key in multiple orders and
+ // with/without snapshots
+
+ Transaction* txn1 = txn_db->BeginTransaction(write_options);
+ Transaction* txn2 = txn_db->BeginTransaction(write_options);
+
+ txn1->Put("1", "1");
+ txn2->Put("1", "2");
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn1;
+ delete txn2;
+
+ txn_options.set_snapshot = true;
+ txn1 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ txn1->Put("1", "3");
+ txn2->Put("1", "4");
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ txn1->Put("1", "5");
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn2->Put("1", "6");
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = txn_db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ txn1->Put("1", "5");
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn2->SetSnapshot();
+ txn2->Put("1", "6");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+ txn2 = txn_db->BeginTransaction(write_options);
+
+ txn1->Put("1", "7");
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn2->Put("1", "8");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+
+ s = txn_db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "8");
+}
+
+TEST_P(OptimisticTransactionTest, UntrackedWrites) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ // Verify transaction rollback works for untracked keys.
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ txn->PutUntracked("untracked", "0");
+ txn->Rollback();
+ s = txn_db->Get(read_options, "untracked", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+ txn = txn_db->BeginTransaction(write_options);
+
+ txn->Put("tracked", "1");
+ txn->PutUntracked("untracked", "1");
+ txn->MergeUntracked("untracked", "2");
+ txn->DeleteUntracked("untracked");
+
+ // Write to the untracked key outside of the transaction and verify
+ // it doesn't prevent the transaction from committing.
+ s = txn_db->Put(write_options, "untracked", "x");
+ ASSERT_OK(s);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "untracked", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+ txn = txn_db->BeginTransaction(write_options);
+
+ txn->Put("tracked", "10");
+ txn->PutUntracked("untracked", "A");
+
+ // Write to tracked key outside of the transaction and verify that the
+ // untracked keys are not written when the commit fails.
+ s = txn_db->Delete(write_options, "tracked");
+
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn_db->Get(read_options, "untracked", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, IteratorTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ // Write some keys to the db
+ s = txn_db->Put(write_options, "A", "a");
+ ASSERT_OK(s);
+
+ s = txn_db->Put(write_options, "G", "g");
+ ASSERT_OK(s);
+
+ s = txn_db->Put(write_options, "F", "f");
+ ASSERT_OK(s);
+
+ s = txn_db->Put(write_options, "C", "c");
+ ASSERT_OK(s);
+
+ s = txn_db->Put(write_options, "D", "d");
+ ASSERT_OK(s);
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ // Write some keys in a txn
+ s = txn->Put("B", "b");
+ ASSERT_OK(s);
+
+ s = txn->Put("H", "h");
+ ASSERT_OK(s);
+
+ s = txn->Delete("D");
+ ASSERT_OK(s);
+
+ s = txn->Put("E", "e");
+ ASSERT_OK(s);
+
+ txn->SetSnapshot();
+ const Snapshot* snapshot = txn->GetSnapshot();
+
+ // Write some keys to the db after the snapshot
+ s = txn_db->Put(write_options, "BB", "xx");
+ ASSERT_OK(s);
+
+ s = txn_db->Put(write_options, "C", "xx");
+ ASSERT_OK(s);
+
+ read_options.snapshot = snapshot;
+ Iterator* iter = txn->GetIterator(read_options);
+ ASSERT_OK(iter->status());
+ iter->SeekToFirst();
+
+ // Read all keys via iter and lock them all
+ std::string results[] = {"a", "b", "c", "e", "f", "g", "h"};
+ for (int i = 0; i < 7; i++) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(results[i], iter->value().ToString());
+
+ s = txn->GetForUpdate(read_options, iter->key(), nullptr);
+ ASSERT_OK(s);
+
+ iter->Next();
+ }
+ ASSERT_FALSE(iter->Valid());
+
+ iter->Seek("G");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("g", iter->value().ToString());
+
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("f", iter->value().ToString());
+
+ iter->Seek("D");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("e", iter->value().ToString());
+
+ iter->Seek("C");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("c", iter->value().ToString());
+
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("e", iter->value().ToString());
+
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a", iter->value().ToString());
+
+ iter->Seek("X");
+ ASSERT_OK(iter->status());
+ ASSERT_FALSE(iter->Valid());
+
+ iter->SeekToLast();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("h", iter->value().ToString());
+
+ // key "C" was modified in the db after txn's snapshot. txn will not commit.
+ s = txn->Commit();
+ ASSERT_TRUE(s.IsBusy());
+
+ delete iter;
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, SavepointTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ Transaction* txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn->SetSavePoint(); // 1
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to beginning of txn
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("B", "b");
+ ASSERT_OK(s);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ delete txn;
+ txn = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("B", "bb");
+ ASSERT_OK(s);
+
+ s = txn->Put("C", "c");
+ ASSERT_OK(s);
+
+ txn->SetSavePoint(); // 2
+
+ s = txn->Delete("B");
+ ASSERT_OK(s);
+
+ s = txn->Put("C", "cc");
+ ASSERT_OK(s);
+
+ s = txn->Put("D", "d");
+ ASSERT_OK(s);
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to 2
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("bb", value);
+
+ s = txn->Get(read_options, "C", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c", value);
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("E", "e");
+ ASSERT_OK(s);
+
+ // Rollback to beginning of txn
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+ txn->Rollback();
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "E", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("A", "aa");
+ ASSERT_OK(s);
+
+ s = txn->Put("F", "f");
+ ASSERT_OK(s);
+
+ txn->SetSavePoint(); // 3
+ txn->SetSavePoint(); // 4
+
+ s = txn->Put("G", "g");
+ ASSERT_OK(s);
+
+ s = txn->Delete("F");
+ ASSERT_OK(s);
+
+ s = txn->Delete("B");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("aa", value);
+
+ s = txn->Get(read_options, "F", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to 3
+
+ s = txn->Get(read_options, "F", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("f", value);
+
+ s = txn->Get(read_options, "G", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = txn_db->Get(read_options, "F", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("f", value);
+
+ s = txn_db->Get(read_options, "G", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn_db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("aa", value);
+
+ s = txn_db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = txn_db->Get(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn_db->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn_db->Get(read_options, "E", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+}
+
+TEST_P(OptimisticTransactionTest, UndoGetForUpdateTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ OptimisticTransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_db->Put(write_options, "A", "");
+
+ Transaction* txn1 = txn_db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn1);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+
+ Transaction* txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 can commit since A isn't conflict checked
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+ txn1->Put("A", "a");
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 cannot commit since A will still be conflict checked
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 cannot commit since A will still be conflict checked
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 can commit since A isn't conflict checked
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint();
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 cannot commit since A will still be conflict checked
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint();
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 cannot commit since A will still be conflict checked
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsBusy());
+ delete txn1;
+
+ txn1 = txn_db->BeginTransaction(write_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint();
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ txn1->UndoGetForUpdate("A");
+
+ txn1->RollbackToSavePoint();
+ txn1->UndoGetForUpdate("A");
+
+ txn2 = txn_db->BeginTransaction(write_options);
+ txn2->Put("A", "x");
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ // Verify that txn1 can commit since A isn't conflict checked
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+}
+
+namespace {
+Status OptimisticTransactionStressTestInserter(OptimisticTransactionDB* db,
+ const size_t num_transactions,
+ const size_t num_sets,
+ const size_t num_keys_per_set) {
+ size_t seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 _rand(seed);
+ WriteOptions write_options;
+ ReadOptions read_options;
+ OptimisticTransactionOptions txn_options;
+ txn_options.set_snapshot = true;
+
+ RandomTransactionInserter inserter(&_rand, write_options, read_options,
+ num_keys_per_set,
+ static_cast<uint16_t>(num_sets));
+
+ for (size_t t = 0; t < num_transactions; t++) {
+ bool success = inserter.OptimisticTransactionDBInsert(db, txn_options);
+ if (!success) {
+ // unexpected failure
+ return inserter.GetLastStatus();
+ }
+ }
+
+ // Make sure at least some of the transactions succeeded. It's ok if
+ // some failed due to write-conflicts.
+ if (inserter.GetFailureCount() > num_transactions / 2) {
+ return Status::TryAgain("Too many transactions failed! " +
+ std::to_string(inserter.GetFailureCount()) + " / " +
+ std::to_string(num_transactions));
+ }
+
+ return Status::OK();
+}
+} // namespace
+
+TEST_P(OptimisticTransactionTest, OptimisticTransactionStressTest) {
+ const size_t num_threads = 4;
+ const size_t num_transactions_per_thread = 10000;
+ const size_t num_sets = 3;
+ const size_t num_keys_per_set = 100;
+ // Setting the key-space to be 100 keys should cause enough write-conflicts
+ // to make this test interesting.
+
+ std::vector<port::Thread> threads;
+
+ std::function<void()> call_inserter = [&] {
+ ASSERT_OK(OptimisticTransactionStressTestInserter(
+ txn_db, num_transactions_per_thread, num_sets, num_keys_per_set));
+ };
+
+ // Create N threads that use RandomTransactionInserter to write
+ // many transactions.
+ for (uint32_t i = 0; i < num_threads; i++) {
+ threads.emplace_back(call_inserter);
+ }
+
+ // Wait for all threads to run
+ for (auto& t : threads) {
+ t.join();
+ }
+
+ // Verify that data is consistent
+ Status s = RandomTransactionInserter::Verify(txn_db, num_sets);
+ ASSERT_OK(s);
+}
+
+TEST_P(OptimisticTransactionTest, SequenceNumberAfterRecoverTest) {
+ WriteOptions write_options;
+ OptimisticTransactionOptions transaction_options;
+
+ Transaction* transaction(txn_db->BeginTransaction(write_options, transaction_options));
+ Status s = transaction->Put("foo", "val");
+ ASSERT_OK(s);
+ s = transaction->Put("foo2", "val");
+ ASSERT_OK(s);
+ s = transaction->Put("foo3", "val");
+ ASSERT_OK(s);
+ s = transaction->Commit();
+ ASSERT_OK(s);
+ delete transaction;
+
+ Reopen();
+ transaction = txn_db->BeginTransaction(write_options, transaction_options);
+ s = transaction->Put("bar", "val");
+ ASSERT_OK(s);
+ s = transaction->Put("bar2", "val");
+ ASSERT_OK(s);
+ s = transaction->Commit();
+ ASSERT_OK(s);
+
+ delete transaction;
+}
+
+INSTANTIATE_TEST_CASE_P(
+ InstanceOccGroup, OptimisticTransactionTest,
+ testing::Values(OccValidationPolicy::kValidateSerial,
+ OccValidationPolicy::kValidateParallel));
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(
+ stderr,
+ "SKIPPED as optimistic_transaction is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.cc b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.cc
new file mode 100644
index 00000000..5ae5fed0
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.cc
@@ -0,0 +1,723 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/pessimistic_transaction.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+#include "utilities/transactions/transaction_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct WriteOptions;
+
+std::atomic<TransactionID> PessimisticTransaction::txn_id_counter_(1);
+
+TransactionID PessimisticTransaction::GenTxnID() {
+ return txn_id_counter_.fetch_add(1);
+}
+
+PessimisticTransaction::PessimisticTransaction(
+ TransactionDB* txn_db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options, const bool init)
+ : TransactionBaseImpl(txn_db->GetRootDB(), write_options),
+ txn_db_impl_(nullptr),
+ expiration_time_(0),
+ txn_id_(0),
+ waiting_cf_id_(0),
+ waiting_key_(nullptr),
+ lock_timeout_(0),
+ deadlock_detect_(false),
+ deadlock_detect_depth_(0),
+ skip_concurrency_control_(false) {
+ txn_db_impl_ =
+ static_cast_with_check<PessimisticTransactionDB, TransactionDB>(txn_db);
+ db_impl_ = static_cast_with_check<DBImpl, DB>(db_);
+ if (init) {
+ Initialize(txn_options);
+ }
+}
+
+void PessimisticTransaction::Initialize(const TransactionOptions& txn_options) {
+ txn_id_ = GenTxnID();
+
+ txn_state_ = STARTED;
+
+ deadlock_detect_ = txn_options.deadlock_detect;
+ deadlock_detect_depth_ = txn_options.deadlock_detect_depth;
+ write_batch_.SetMaxBytes(txn_options.max_write_batch_size);
+ skip_concurrency_control_ = txn_options.skip_concurrency_control;
+
+ lock_timeout_ = txn_options.lock_timeout * 1000;
+ if (lock_timeout_ < 0) {
+ // Lock timeout not set, use default
+ lock_timeout_ =
+ txn_db_impl_->GetTxnDBOptions().transaction_lock_timeout * 1000;
+ }
+
+ if (txn_options.expiration >= 0) {
+ expiration_time_ = start_time_ + txn_options.expiration * 1000;
+ } else {
+ expiration_time_ = 0;
+ }
+
+ if (txn_options.set_snapshot) {
+ SetSnapshot();
+ }
+
+ if (expiration_time_ > 0) {
+ txn_db_impl_->InsertExpirableTransaction(txn_id_, this);
+ }
+ use_only_the_last_commit_time_batch_for_recovery_ =
+ txn_options.use_only_the_last_commit_time_batch_for_recovery;
+}
+
+PessimisticTransaction::~PessimisticTransaction() {
+ txn_db_impl_->UnLock(this, &GetTrackedKeys());
+ if (expiration_time_ > 0) {
+ txn_db_impl_->RemoveExpirableTransaction(txn_id_);
+ }
+ if (!name_.empty() && txn_state_ != COMMITED) {
+ txn_db_impl_->UnregisterTransaction(this);
+ }
+}
+
+void PessimisticTransaction::Clear() {
+ txn_db_impl_->UnLock(this, &GetTrackedKeys());
+ TransactionBaseImpl::Clear();
+}
+
+void PessimisticTransaction::Reinitialize(
+ TransactionDB* txn_db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options) {
+ if (!name_.empty() && txn_state_ != COMMITED) {
+ txn_db_impl_->UnregisterTransaction(this);
+ }
+ TransactionBaseImpl::Reinitialize(txn_db->GetRootDB(), write_options);
+ Initialize(txn_options);
+}
+
+bool PessimisticTransaction::IsExpired() const {
+ if (expiration_time_ > 0) {
+ if (db_->GetEnv()->NowMicros() >= expiration_time_) {
+ // Transaction is expired.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+WriteCommittedTxn::WriteCommittedTxn(TransactionDB* txn_db,
+ const WriteOptions& write_options,
+ const TransactionOptions& txn_options)
+ : PessimisticTransaction(txn_db, write_options, txn_options){};
+
+Status PessimisticTransaction::CommitBatch(WriteBatch* batch) {
+ TransactionKeyMap keys_to_unlock;
+ Status s = LockBatch(batch, &keys_to_unlock);
+
+ if (!s.ok()) {
+ return s;
+ }
+
+ bool can_commit = false;
+
+ if (IsExpired()) {
+ s = Status::Expired();
+ } else if (expiration_time_ > 0) {
+ TransactionState expected = STARTED;
+ can_commit = std::atomic_compare_exchange_strong(&txn_state_, &expected,
+ AWAITING_COMMIT);
+ } else if (txn_state_ == STARTED) {
+ // lock stealing is not a concern
+ can_commit = true;
+ }
+
+ if (can_commit) {
+ txn_state_.store(AWAITING_COMMIT);
+ s = CommitBatchInternal(batch);
+ if (s.ok()) {
+ txn_state_.store(COMMITED);
+ }
+ } else if (txn_state_ == LOCKS_STOLEN) {
+ s = Status::Expired();
+ } else {
+ s = Status::InvalidArgument("Transaction is not in state for commit.");
+ }
+
+ txn_db_impl_->UnLock(this, &keys_to_unlock);
+
+ return s;
+}
+
+Status PessimisticTransaction::Prepare() {
+ Status s;
+
+ if (name_.empty()) {
+ return Status::InvalidArgument(
+ "Cannot prepare a transaction that has not been named.");
+ }
+
+ if (IsExpired()) {
+ return Status::Expired();
+ }
+
+ bool can_prepare = false;
+
+ if (expiration_time_ > 0) {
+ // must concern ourselves with expiraton and/or lock stealing
+ // need to compare/exchange bc locks could be stolen under us here
+ TransactionState expected = STARTED;
+ can_prepare = std::atomic_compare_exchange_strong(&txn_state_, &expected,
+ AWAITING_PREPARE);
+ } else if (txn_state_ == STARTED) {
+ // expiration and lock stealing is not possible
+ can_prepare = true;
+ }
+
+ if (can_prepare) {
+ txn_state_.store(AWAITING_PREPARE);
+ // transaction can't expire after preparation
+ expiration_time_ = 0;
+ assert(log_number_ == 0 ||
+ txn_db_impl_->GetTxnDBOptions().write_policy == WRITE_UNPREPARED);
+
+ s = PrepareInternal();
+ if (s.ok()) {
+ txn_state_.store(PREPARED);
+ }
+ } else if (txn_state_ == LOCKS_STOLEN) {
+ s = Status::Expired();
+ } else if (txn_state_ == PREPARED) {
+ s = Status::InvalidArgument("Transaction has already been prepared.");
+ } else if (txn_state_ == COMMITED) {
+ s = Status::InvalidArgument("Transaction has already been committed.");
+ } else if (txn_state_ == ROLLEDBACK) {
+ s = Status::InvalidArgument("Transaction has already been rolledback.");
+ } else {
+ s = Status::InvalidArgument("Transaction is not in state for commit.");
+ }
+
+ return s;
+}
+
+Status WriteCommittedTxn::PrepareInternal() {
+ WriteOptions write_options = write_options_;
+ write_options.disableWAL = false;
+ WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(), name_);
+ class MarkLogCallback : public PreReleaseCallback {
+ public:
+ MarkLogCallback(DBImpl* db, bool two_write_queues)
+ : db_(db), two_write_queues_(two_write_queues) {
+ (void)two_write_queues_; // to silence unused private field warning
+ }
+ virtual Status Callback(SequenceNumber, bool is_mem_disabled,
+ uint64_t log_number, size_t /*index*/,
+ size_t /*total*/) override {
+#ifdef NDEBUG
+ (void)is_mem_disabled;
+#endif
+ assert(log_number != 0);
+ assert(!two_write_queues_ || is_mem_disabled); // implies the 2nd queue
+ db_->logs_with_prep_tracker()->MarkLogAsContainingPrepSection(log_number);
+ return Status::OK();
+ }
+
+ private:
+ DBImpl* db_;
+ bool two_write_queues_;
+ } mark_log_callback(db_impl_,
+ db_impl_->immutable_db_options().two_write_queues);
+
+ WriteCallback* const kNoWriteCallback = nullptr;
+ const uint64_t kRefNoLog = 0;
+ const bool kDisableMemtable = true;
+ SequenceNumber* const KIgnoreSeqUsed = nullptr;
+ const size_t kNoBatchCount = 0;
+ Status s = db_impl_->WriteImpl(
+ write_options, GetWriteBatch()->GetWriteBatch(), kNoWriteCallback,
+ &log_number_, kRefNoLog, kDisableMemtable, KIgnoreSeqUsed, kNoBatchCount,
+ &mark_log_callback);
+ return s;
+}
+
+Status PessimisticTransaction::Commit() {
+ Status s;
+ bool commit_without_prepare = false;
+ bool commit_prepared = false;
+
+ if (IsExpired()) {
+ return Status::Expired();
+ }
+
+ if (expiration_time_ > 0) {
+ // we must atomicaly compare and exchange the state here because at
+ // this state in the transaction it is possible for another thread
+ // to change our state out from under us in the even that we expire and have
+ // our locks stolen. In this case the only valid state is STARTED because
+ // a state of PREPARED would have a cleared expiration_time_.
+ TransactionState expected = STARTED;
+ commit_without_prepare = std::atomic_compare_exchange_strong(
+ &txn_state_, &expected, AWAITING_COMMIT);
+ TEST_SYNC_POINT("TransactionTest::ExpirableTransactionDataRace:1");
+ } else if (txn_state_ == PREPARED) {
+ // expiration and lock stealing is not a concern
+ commit_prepared = true;
+ } else if (txn_state_ == STARTED) {
+ // expiration and lock stealing is not a concern
+ commit_without_prepare = true;
+ // TODO(myabandeh): what if the user mistakenly forgets prepare? We should
+ // add an option so that the user explictly express the intention of
+ // skipping the prepare phase.
+ }
+
+ if (commit_without_prepare) {
+ assert(!commit_prepared);
+ if (WriteBatchInternal::Count(GetCommitTimeWriteBatch()) > 0) {
+ s = Status::InvalidArgument(
+ "Commit-time batch contains values that will not be committed.");
+ } else {
+ txn_state_.store(AWAITING_COMMIT);
+ if (log_number_ > 0) {
+ dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
+ log_number_);
+ }
+ s = CommitWithoutPrepareInternal();
+ if (!name_.empty()) {
+ txn_db_impl_->UnregisterTransaction(this);
+ }
+ Clear();
+ if (s.ok()) {
+ txn_state_.store(COMMITED);
+ }
+ }
+ } else if (commit_prepared) {
+ txn_state_.store(AWAITING_COMMIT);
+
+ s = CommitInternal();
+
+ if (!s.ok()) {
+ ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log,
+ "Commit write failed");
+ return s;
+ }
+
+ // FindObsoleteFiles must now look to the memtables
+ // to determine what prep logs must be kept around,
+ // not the prep section heap.
+ assert(log_number_ > 0);
+ dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
+ log_number_);
+ txn_db_impl_->UnregisterTransaction(this);
+
+ Clear();
+ txn_state_.store(COMMITED);
+ } else if (txn_state_ == LOCKS_STOLEN) {
+ s = Status::Expired();
+ } else if (txn_state_ == COMMITED) {
+ s = Status::InvalidArgument("Transaction has already been committed.");
+ } else if (txn_state_ == ROLLEDBACK) {
+ s = Status::InvalidArgument("Transaction has already been rolledback.");
+ } else {
+ s = Status::InvalidArgument("Transaction is not in state for commit.");
+ }
+
+ return s;
+}
+
+Status WriteCommittedTxn::CommitWithoutPrepareInternal() {
+ uint64_t seq_used = kMaxSequenceNumber;
+ auto s =
+ db_impl_->WriteImpl(write_options_, GetWriteBatch()->GetWriteBatch(),
+ /*callback*/ nullptr, /*log_used*/ nullptr,
+ /*log_ref*/ 0, /*disable_memtable*/ false, &seq_used);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (s.ok()) {
+ SetId(seq_used);
+ }
+ return s;
+}
+
+Status WriteCommittedTxn::CommitBatchInternal(WriteBatch* batch, size_t) {
+ uint64_t seq_used = kMaxSequenceNumber;
+ auto s = db_impl_->WriteImpl(write_options_, batch, /*callback*/ nullptr,
+ /*log_used*/ nullptr, /*log_ref*/ 0,
+ /*disable_memtable*/ false, &seq_used);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (s.ok()) {
+ SetId(seq_used);
+ }
+ return s;
+}
+
+Status WriteCommittedTxn::CommitInternal() {
+ // We take the commit-time batch and append the Commit marker.
+ // The Memtable will ignore the Commit marker in non-recovery mode
+ WriteBatch* working_batch = GetCommitTimeWriteBatch();
+ WriteBatchInternal::MarkCommit(working_batch, name_);
+
+ // any operations appended to this working_batch will be ignored from WAL
+ working_batch->MarkWalTerminationPoint();
+
+ // insert prepared batch into Memtable only skipping WAL.
+ // Memtable will ignore BeginPrepare/EndPrepare markers
+ // in non recovery mode and simply insert the values
+ WriteBatchInternal::Append(working_batch, GetWriteBatch()->GetWriteBatch());
+
+ uint64_t seq_used = kMaxSequenceNumber;
+ auto s =
+ db_impl_->WriteImpl(write_options_, working_batch, /*callback*/ nullptr,
+ /*log_used*/ nullptr, /*log_ref*/ log_number_,
+ /*disable_memtable*/ false, &seq_used);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (s.ok()) {
+ SetId(seq_used);
+ }
+ return s;
+}
+
+Status PessimisticTransaction::Rollback() {
+ Status s;
+ if (txn_state_ == PREPARED) {
+ txn_state_.store(AWAITING_ROLLBACK);
+
+ s = RollbackInternal();
+
+ if (s.ok()) {
+ // we do not need to keep our prepared section around
+ assert(log_number_ > 0);
+ dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
+ log_number_);
+ Clear();
+ txn_state_.store(ROLLEDBACK);
+ }
+ } else if (txn_state_ == STARTED) {
+ if (log_number_ > 0) {
+ assert(txn_db_impl_->GetTxnDBOptions().write_policy == WRITE_UNPREPARED);
+ assert(GetId() > 0);
+ s = RollbackInternal();
+
+ if (s.ok()) {
+ dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
+ log_number_);
+ }
+ }
+ // prepare couldn't have taken place
+ Clear();
+ } else if (txn_state_ == COMMITED) {
+ s = Status::InvalidArgument("This transaction has already been committed.");
+ } else {
+ s = Status::InvalidArgument(
+ "Two phase transaction is not in state for rollback.");
+ }
+
+ return s;
+}
+
+Status WriteCommittedTxn::RollbackInternal() {
+ WriteBatch rollback_marker;
+ WriteBatchInternal::MarkRollback(&rollback_marker, name_);
+ auto s = db_impl_->WriteImpl(write_options_, &rollback_marker);
+ return s;
+}
+
+Status PessimisticTransaction::RollbackToSavePoint() {
+ if (txn_state_ != STARTED) {
+ return Status::InvalidArgument("Transaction is beyond state for rollback.");
+ }
+
+ // Unlock any keys locked since last transaction
+ const std::unique_ptr<TransactionKeyMap>& keys =
+ GetTrackedKeysSinceSavePoint();
+
+ if (keys) {
+ txn_db_impl_->UnLock(this, keys.get());
+ }
+
+ return TransactionBaseImpl::RollbackToSavePoint();
+}
+
+// Lock all keys in this batch.
+// On success, caller should unlock keys_to_unlock
+Status PessimisticTransaction::LockBatch(WriteBatch* batch,
+ TransactionKeyMap* keys_to_unlock) {
+ class Handler : public WriteBatch::Handler {
+ public:
+ // Sorted map of column_family_id to sorted set of keys.
+ // Since LockBatch() always locks keys in sorted order, it cannot deadlock
+ // with itself. We're not using a comparator here since it doesn't matter
+ // what the sorting is as long as it's consistent.
+ std::map<uint32_t, std::set<std::string>> keys_;
+
+ Handler() {}
+
+ void RecordKey(uint32_t column_family_id, const Slice& key) {
+ std::string key_str = key.ToString();
+
+ auto& cfh_keys = keys_[column_family_id];
+ auto iter = cfh_keys.find(key_str);
+ if (iter == cfh_keys.end()) {
+ // key not yet seen, store it.
+ cfh_keys.insert({std::move(key_str)});
+ }
+ }
+
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& /* unused */) override {
+ RecordKey(column_family_id, key);
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& /* unused */) override {
+ RecordKey(column_family_id, key);
+ return Status::OK();
+ }
+ Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
+ RecordKey(column_family_id, key);
+ return Status::OK();
+ }
+ };
+
+ // Iterating on this handler will add all keys in this batch into keys
+ Handler handler;
+ batch->Iterate(&handler);
+
+ Status s;
+
+ // Attempt to lock all keys
+ for (const auto& cf_iter : handler.keys_) {
+ uint32_t cfh_id = cf_iter.first;
+ auto& cfh_keys = cf_iter.second;
+
+ for (const auto& key_iter : cfh_keys) {
+ const std::string& key = key_iter;
+
+ s = txn_db_impl_->TryLock(this, cfh_id, key, true /* exclusive */);
+ if (!s.ok()) {
+ break;
+ }
+ TrackKey(keys_to_unlock, cfh_id, std::move(key), kMaxSequenceNumber,
+ false, true /* exclusive */);
+ }
+
+ if (!s.ok()) {
+ break;
+ }
+ }
+
+ if (!s.ok()) {
+ txn_db_impl_->UnLock(this, keys_to_unlock);
+ }
+
+ return s;
+}
+
+// Attempt to lock this key.
+// Returns OK if the key has been successfully locked. Non-ok, otherwise.
+// If check_shapshot is true and this transaction has a snapshot set,
+// this key will only be locked if there have been no writes to this key since
+// the snapshot time.
+Status PessimisticTransaction::TryLock(ColumnFamilyHandle* column_family,
+ const Slice& key, bool read_only,
+ bool exclusive, const bool do_validate,
+ const bool assume_tracked) {
+ assert(!assume_tracked || !do_validate);
+ Status s;
+ if (UNLIKELY(skip_concurrency_control_)) {
+ return s;
+ }
+ uint32_t cfh_id = GetColumnFamilyID(column_family);
+ std::string key_str = key.ToString();
+ bool previously_locked;
+ bool lock_upgrade = false;
+
+ // lock this key if this transactions hasn't already locked it
+ SequenceNumber tracked_at_seq = kMaxSequenceNumber;
+
+ const auto& tracked_keys = GetTrackedKeys();
+ const auto tracked_keys_cf = tracked_keys.find(cfh_id);
+ if (tracked_keys_cf == tracked_keys.end()) {
+ previously_locked = false;
+ } else {
+ auto iter = tracked_keys_cf->second.find(key_str);
+ if (iter == tracked_keys_cf->second.end()) {
+ previously_locked = false;
+ } else {
+ if (!iter->second.exclusive && exclusive) {
+ lock_upgrade = true;
+ }
+ previously_locked = true;
+ tracked_at_seq = iter->second.seq;
+ }
+ }
+
+ // Lock this key if this transactions hasn't already locked it or we require
+ // an upgrade.
+ if (!previously_locked || lock_upgrade) {
+ s = txn_db_impl_->TryLock(this, cfh_id, key_str, exclusive);
+ }
+
+ SetSnapshotIfNeeded();
+
+ // Even though we do not care about doing conflict checking for this write,
+ // we still need to take a lock to make sure we do not cause a conflict with
+ // some other write. However, we do not need to check if there have been
+ // any writes since this transaction's snapshot.
+ // TODO(agiardullo): could optimize by supporting shared txn locks in the
+ // future
+ if (!do_validate || snapshot_ == nullptr) {
+ if (assume_tracked && !previously_locked) {
+ s = Status::InvalidArgument(
+ "assume_tracked is set but it is not tracked yet");
+ }
+ // Need to remember the earliest sequence number that we know that this
+ // key has not been modified after. This is useful if this same
+ // transaction
+ // later tries to lock this key again.
+ if (tracked_at_seq == kMaxSequenceNumber) {
+ // Since we haven't checked a snapshot, we only know this key has not
+ // been modified since after we locked it.
+ // Note: when last_seq_same_as_publish_seq_==false this is less than the
+ // latest allocated seq but it is ok since i) this is just a heuristic
+ // used only as a hint to avoid actual check for conflicts, ii) this would
+ // cause a false positive only if the snapthot is taken right after the
+ // lock, which would be an unusual sequence.
+ tracked_at_seq = db_->GetLatestSequenceNumber();
+ }
+ } else {
+ // If a snapshot is set, we need to make sure the key hasn't been modified
+ // since the snapshot. This must be done after we locked the key.
+ // If we already have validated an earilier snapshot it must has been
+ // reflected in tracked_at_seq and ValidateSnapshot will return OK.
+ if (s.ok()) {
+ s = ValidateSnapshot(column_family, key, &tracked_at_seq);
+
+ if (!s.ok()) {
+ // Failed to validate key
+ if (!previously_locked) {
+ // Unlock key we just locked
+ if (lock_upgrade) {
+ s = txn_db_impl_->TryLock(this, cfh_id, key_str,
+ false /* exclusive */);
+ assert(s.ok());
+ } else {
+ txn_db_impl_->UnLock(this, cfh_id, key.ToString());
+ }
+ }
+ }
+ }
+ }
+
+ if (s.ok()) {
+ // We must track all the locked keys so that we can unlock them later. If
+ // the key is already locked, this func will update some stats on the
+ // tracked key. It could also update the tracked_at_seq if it is lower
+ // than the existing tracked key seq. These stats are necessary for
+ // RollbackToSavePoint to determine whether a key can be safely removed
+ // from tracked_keys_. Removal can only be done if a key was only locked
+ // during the current savepoint.
+ //
+ // Recall that if assume_tracked is true, we assume that TrackKey has been
+ // called previously since the last savepoint, with the same exclusive
+ // setting, and at a lower sequence number, so skipping here should be
+ // safe.
+ if (!assume_tracked) {
+ TrackKey(cfh_id, key_str, tracked_at_seq, read_only, exclusive);
+ } else {
+#ifndef NDEBUG
+ assert(tracked_keys_cf->second.count(key_str) > 0);
+ const auto& info = tracked_keys_cf->second.find(key_str)->second;
+ assert(info.seq <= tracked_at_seq);
+ assert(info.exclusive == exclusive);
+#endif
+ }
+ }
+
+ return s;
+}
+
+// Return OK() if this key has not been modified more recently than the
+// transaction snapshot_.
+// tracked_at_seq is the global seq at which we either locked the key or already
+// have done ValidateSnapshot.
+Status PessimisticTransaction::ValidateSnapshot(
+ ColumnFamilyHandle* column_family, const Slice& key,
+ SequenceNumber* tracked_at_seq) {
+ assert(snapshot_);
+
+ SequenceNumber snap_seq = snapshot_->GetSequenceNumber();
+ if (*tracked_at_seq <= snap_seq) {
+ // If the key has been previous validated (or locked) at a sequence number
+ // earlier than the current snapshot's sequence number, we already know it
+ // has not been modified aftter snap_seq either.
+ return Status::OK();
+ }
+ // Otherwise we have either
+ // 1: tracked_at_seq == kMaxSequenceNumber, i.e., first time tracking the key
+ // 2: snap_seq < tracked_at_seq: last time we lock the key was via
+ // do_validate=false which means we had skipped ValidateSnapshot. In both
+ // cases we should do ValidateSnapshot now.
+
+ *tracked_at_seq = snap_seq;
+
+ ColumnFamilyHandle* cfh =
+ column_family ? column_family : db_impl_->DefaultColumnFamily();
+
+ return TransactionUtil::CheckKeyForConflicts(
+ db_impl_, cfh, key.ToString(), snap_seq, false /* cache_only */);
+}
+
+bool PessimisticTransaction::TryStealingLocks() {
+ assert(IsExpired());
+ TransactionState expected = STARTED;
+ return std::atomic_compare_exchange_strong(&txn_state_, &expected,
+ LOCKS_STOLEN);
+}
+
+void PessimisticTransaction::UnlockGetForUpdate(
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ txn_db_impl_->UnLock(this, GetColumnFamilyID(column_family), key.ToString());
+}
+
+Status PessimisticTransaction::SetName(const TransactionName& name) {
+ Status s;
+ if (txn_state_ == STARTED) {
+ if (name_.length()) {
+ s = Status::InvalidArgument("Transaction has already been named.");
+ } else if (txn_db_impl_->GetTransactionByName(name) != nullptr) {
+ s = Status::InvalidArgument("Transaction name must be unique.");
+ } else if (name.length() < 1 || name.length() > 512) {
+ s = Status::InvalidArgument(
+ "Transaction name length must be between 1 and 512 chars.");
+ } else {
+ name_ = name;
+ txn_db_impl_->RegisterTransaction(this);
+ }
+ } else {
+ s = Status::InvalidArgument("Transaction is beyond state for naming.");
+ }
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.h b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.h
new file mode 100644
index 00000000..8f2c8440
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction.h
@@ -0,0 +1,225 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <algorithm>
+#include <atomic>
+#include <mutex>
+#include <stack>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "db/write_callback.h"
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/autovector.h"
+#include "utilities/transactions/transaction_base.h"
+#include "utilities/transactions/transaction_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PessimisticTransactionDB;
+
+// A transaction under pessimistic concurrency control. This class implements
+// the locking API and interfaces with the lock manager as well as the
+// pessimistic transactional db.
+class PessimisticTransaction : public TransactionBaseImpl {
+ public:
+ PessimisticTransaction(TransactionDB* db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options,
+ const bool init = true);
+ // No copying allowed
+ PessimisticTransaction(const PessimisticTransaction&) = delete;
+ void operator=(const PessimisticTransaction&) = delete;
+
+ virtual ~PessimisticTransaction();
+
+ void Reinitialize(TransactionDB* txn_db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options);
+
+ Status Prepare() override;
+
+ Status Commit() override;
+
+ // It is basically Commit without going through Prepare phase. The write batch
+ // is also directly provided instead of expecting txn to gradually batch the
+ // transactions writes to an internal write batch.
+ Status CommitBatch(WriteBatch* batch);
+
+ Status Rollback() override;
+
+ Status RollbackToSavePoint() override;
+
+ Status SetName(const TransactionName& name) override;
+
+ // Generate a new unique transaction identifier
+ static TransactionID GenTxnID();
+
+ TransactionID GetID() const override { return txn_id_; }
+
+ std::vector<TransactionID> GetWaitingTxns(uint32_t* column_family_id,
+ std::string* key) const override {
+ std::lock_guard<std::mutex> lock(wait_mutex_);
+ std::vector<TransactionID> ids(waiting_txn_ids_.size());
+ if (key) *key = waiting_key_ ? *waiting_key_ : "";
+ if (column_family_id) *column_family_id = waiting_cf_id_;
+ std::copy(waiting_txn_ids_.begin(), waiting_txn_ids_.end(), ids.begin());
+ return ids;
+ }
+
+ void SetWaitingTxn(autovector<TransactionID> ids, uint32_t column_family_id,
+ const std::string* key) {
+ std::lock_guard<std::mutex> lock(wait_mutex_);
+ waiting_txn_ids_ = ids;
+ waiting_cf_id_ = column_family_id;
+ waiting_key_ = key;
+ }
+
+ void ClearWaitingTxn() {
+ std::lock_guard<std::mutex> lock(wait_mutex_);
+ waiting_txn_ids_.clear();
+ waiting_cf_id_ = 0;
+ waiting_key_ = nullptr;
+ }
+
+ // Returns the time (in microseconds according to Env->GetMicros())
+ // that this transaction will be expired. Returns 0 if this transaction does
+ // not expire.
+ uint64_t GetExpirationTime() const { return expiration_time_; }
+
+ // returns true if this transaction has an expiration_time and has expired.
+ bool IsExpired() const;
+
+ // Returns the number of microseconds a transaction can wait on acquiring a
+ // lock or -1 if there is no timeout.
+ int64_t GetLockTimeout() const { return lock_timeout_; }
+ void SetLockTimeout(int64_t timeout) override {
+ lock_timeout_ = timeout * 1000;
+ }
+
+ // Returns true if locks were stolen successfully, false otherwise.
+ bool TryStealingLocks();
+
+ bool IsDeadlockDetect() const override { return deadlock_detect_; }
+
+ int64_t GetDeadlockDetectDepth() const { return deadlock_detect_depth_; }
+
+ protected:
+ // Refer to
+ // TransactionOptions::use_only_the_last_commit_time_batch_for_recovery
+ bool use_only_the_last_commit_time_batch_for_recovery_ = false;
+
+ virtual Status PrepareInternal() = 0;
+
+ virtual Status CommitWithoutPrepareInternal() = 0;
+
+ // batch_cnt if non-zero is the number of sub-batches. A sub-batch is a batch
+ // with no duplicate keys. If zero, then the number of sub-batches is unknown.
+ virtual Status CommitBatchInternal(WriteBatch* batch,
+ size_t batch_cnt = 0) = 0;
+
+ virtual Status CommitInternal() = 0;
+
+ virtual Status RollbackInternal() = 0;
+
+ virtual void Initialize(const TransactionOptions& txn_options);
+
+ Status LockBatch(WriteBatch* batch, TransactionKeyMap* keys_to_unlock);
+
+ Status TryLock(ColumnFamilyHandle* column_family, const Slice& key,
+ bool read_only, bool exclusive, const bool do_validate = true,
+ const bool assume_tracked = false) override;
+
+ void Clear() override;
+
+ PessimisticTransactionDB* txn_db_impl_;
+ DBImpl* db_impl_;
+
+ // If non-zero, this transaction should not be committed after this time (in
+ // microseconds according to Env->NowMicros())
+ uint64_t expiration_time_;
+
+ private:
+ friend class TransactionTest_ValidateSnapshotTest_Test;
+ // Used to create unique ids for transactions.
+ static std::atomic<TransactionID> txn_id_counter_;
+
+ // Unique ID for this transaction
+ TransactionID txn_id_;
+
+ // IDs for the transactions that are blocking the current transaction.
+ //
+ // empty if current transaction is not waiting.
+ autovector<TransactionID> waiting_txn_ids_;
+
+ // The following two represents the (cf, key) that a transaction is waiting
+ // on.
+ //
+ // If waiting_key_ is not null, then the pointer should always point to
+ // a valid string object. The reason is that it is only non-null when the
+ // transaction is blocked in the TransactionLockMgr::AcquireWithTimeout
+ // function. At that point, the key string object is one of the function
+ // parameters.
+ uint32_t waiting_cf_id_;
+ const std::string* waiting_key_;
+
+ // Mutex protecting waiting_txn_ids_, waiting_cf_id_ and waiting_key_.
+ mutable std::mutex wait_mutex_;
+
+ // Timeout in microseconds when locking a key or -1 if there is no timeout.
+ int64_t lock_timeout_;
+
+ // Whether to perform deadlock detection or not.
+ bool deadlock_detect_;
+
+ // Whether to perform deadlock detection or not.
+ int64_t deadlock_detect_depth_;
+
+ // Refer to TransactionOptions::skip_concurrency_control
+ bool skip_concurrency_control_;
+
+ virtual Status ValidateSnapshot(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ SequenceNumber* tracked_at_seq);
+
+ void UnlockGetForUpdate(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+};
+
+class WriteCommittedTxn : public PessimisticTransaction {
+ public:
+ WriteCommittedTxn(TransactionDB* db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options);
+ // No copying allowed
+ WriteCommittedTxn(const WriteCommittedTxn&) = delete;
+ void operator=(const WriteCommittedTxn&) = delete;
+
+ virtual ~WriteCommittedTxn() {}
+
+ private:
+ Status PrepareInternal() override;
+
+ Status CommitWithoutPrepareInternal() override;
+
+ Status CommitBatchInternal(WriteBatch* batch, size_t batch_cnt) override;
+
+ Status CommitInternal() override;
+
+ Status RollbackInternal() override;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.cc b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.cc
new file mode 100644
index 00000000..30d5b79f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.cc
@@ -0,0 +1,632 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/pessimistic_transaction_db.h"
+
+#include <cinttypes>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/mutexlock.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/transaction_db_mutex_impl.h"
+#include "utilities/transactions/write_prepared_txn_db.h"
+#include "utilities/transactions/write_unprepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+PessimisticTransactionDB::PessimisticTransactionDB(
+ DB* db, const TransactionDBOptions& txn_db_options)
+ : TransactionDB(db),
+ db_impl_(static_cast_with_check<DBImpl, DB>(db)),
+ txn_db_options_(txn_db_options),
+ lock_mgr_(this, txn_db_options_.num_stripes, txn_db_options.max_num_locks,
+ txn_db_options_.max_num_deadlocks,
+ txn_db_options_.custom_mutex_factory
+ ? txn_db_options_.custom_mutex_factory
+ : std::shared_ptr<TransactionDBMutexFactory>(
+ new TransactionDBMutexFactoryImpl())) {
+ assert(db_impl_ != nullptr);
+ info_log_ = db_impl_->GetDBOptions().info_log;
+}
+
+// Support initiliazing PessimisticTransactionDB from a stackable db
+//
+// PessimisticTransactionDB
+// ^ ^
+// | |
+// | +
+// | StackableDB
+// | ^
+// | |
+// + +
+// DBImpl
+// ^
+// |(inherit)
+// +
+// DB
+//
+PessimisticTransactionDB::PessimisticTransactionDB(
+ StackableDB* db, const TransactionDBOptions& txn_db_options)
+ : TransactionDB(db),
+ db_impl_(static_cast_with_check<DBImpl, DB>(db->GetRootDB())),
+ txn_db_options_(txn_db_options),
+ lock_mgr_(this, txn_db_options_.num_stripes, txn_db_options.max_num_locks,
+ txn_db_options_.max_num_deadlocks,
+ txn_db_options_.custom_mutex_factory
+ ? txn_db_options_.custom_mutex_factory
+ : std::shared_ptr<TransactionDBMutexFactory>(
+ new TransactionDBMutexFactoryImpl())) {
+ assert(db_impl_ != nullptr);
+}
+
+PessimisticTransactionDB::~PessimisticTransactionDB() {
+ while (!transactions_.empty()) {
+ delete transactions_.begin()->second;
+ // TODO(myabandeh): this seems to be an unsafe approach as it is not quite
+ // clear whether delete would also remove the entry from transactions_.
+ }
+}
+
+Status PessimisticTransactionDB::VerifyCFOptions(const ColumnFamilyOptions&) {
+ return Status::OK();
+}
+
+Status PessimisticTransactionDB::Initialize(
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles) {
+ for (auto cf_ptr : handles) {
+ AddColumnFamily(cf_ptr);
+ }
+ // Verify cf options
+ for (auto handle : handles) {
+ ColumnFamilyDescriptor cfd;
+ Status s = handle->GetDescriptor(&cfd);
+ if (!s.ok()) {
+ return s;
+ }
+ s = VerifyCFOptions(cfd.options);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // Re-enable compaction for the column families that initially had
+ // compaction enabled.
+ std::vector<ColumnFamilyHandle*> compaction_enabled_cf_handles;
+ compaction_enabled_cf_handles.reserve(compaction_enabled_cf_indices.size());
+ for (auto index : compaction_enabled_cf_indices) {
+ compaction_enabled_cf_handles.push_back(handles[index]);
+ }
+
+ Status s = EnableAutoCompaction(compaction_enabled_cf_handles);
+
+ // create 'real' transactions from recovered shell transactions
+ auto dbimpl = static_cast_with_check<DBImpl, DB>(GetRootDB());
+ assert(dbimpl != nullptr);
+ auto rtrxs = dbimpl->recovered_transactions();
+
+ for (auto it = rtrxs.begin(); it != rtrxs.end(); ++it) {
+ auto recovered_trx = it->second;
+ assert(recovered_trx);
+ assert(recovered_trx->batches_.size() == 1);
+ const auto& seq = recovered_trx->batches_.begin()->first;
+ const auto& batch_info = recovered_trx->batches_.begin()->second;
+ assert(batch_info.log_number_);
+ assert(recovered_trx->name_.length());
+
+ WriteOptions w_options;
+ w_options.sync = true;
+ TransactionOptions t_options;
+ // This would help avoiding deadlock for keys that although exist in the WAL
+ // did not go through concurrency control. This includes the merge that
+ // MyRocks uses for auto-inc columns. It is safe to do so, since (i) if
+ // there is a conflict between the keys of two transactions that must be
+ // avoided, it is already avoided by the application, MyRocks, before the
+ // restart (ii) application, MyRocks, guarntees to rollback/commit the
+ // recovered transactions before new transactions start.
+ t_options.skip_concurrency_control = true;
+
+ Transaction* real_trx = BeginTransaction(w_options, t_options, nullptr);
+ assert(real_trx);
+ real_trx->SetLogNumber(batch_info.log_number_);
+ assert(seq != kMaxSequenceNumber);
+ if (GetTxnDBOptions().write_policy != WRITE_COMMITTED) {
+ real_trx->SetId(seq);
+ }
+
+ s = real_trx->SetName(recovered_trx->name_);
+ if (!s.ok()) {
+ break;
+ }
+
+ s = real_trx->RebuildFromWriteBatch(batch_info.batch_);
+ // WriteCommitted set this to to disable this check that is specific to
+ // WritePrepared txns
+ assert(batch_info.batch_cnt_ == 0 ||
+ real_trx->GetWriteBatch()->SubBatchCnt() == batch_info.batch_cnt_);
+ real_trx->SetState(Transaction::PREPARED);
+ if (!s.ok()) {
+ break;
+ }
+ }
+ if (s.ok()) {
+ dbimpl->DeleteAllRecoveredTransactions();
+ }
+ return s;
+}
+
+Transaction* WriteCommittedTxnDB::BeginTransaction(
+ const WriteOptions& write_options, const TransactionOptions& txn_options,
+ Transaction* old_txn) {
+ if (old_txn != nullptr) {
+ ReinitializeTransaction(old_txn, write_options, txn_options);
+ return old_txn;
+ } else {
+ return new WriteCommittedTxn(this, write_options, txn_options);
+ }
+}
+
+TransactionDBOptions PessimisticTransactionDB::ValidateTxnDBOptions(
+ const TransactionDBOptions& txn_db_options) {
+ TransactionDBOptions validated = txn_db_options;
+
+ if (txn_db_options.num_stripes == 0) {
+ validated.num_stripes = 1;
+ }
+
+ return validated;
+}
+
+Status TransactionDB::Open(const Options& options,
+ const TransactionDBOptions& txn_db_options,
+ const std::string& dbname, TransactionDB** dbptr) {
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> handles;
+ Status s = TransactionDB::Open(db_options, txn_db_options, dbname,
+ column_families, &handles, dbptr);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ // i can delete the handle since DBImpl is always holding a reference to
+ // default column family
+ delete handles[0];
+ }
+
+ return s;
+}
+
+Status TransactionDB::Open(
+ const DBOptions& db_options, const TransactionDBOptions& txn_db_options,
+ const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, TransactionDB** dbptr) {
+ Status s;
+ DB* db = nullptr;
+ if (txn_db_options.write_policy == WRITE_COMMITTED &&
+ db_options.unordered_write) {
+ return Status::NotSupported(
+ "WRITE_COMMITTED is incompatible with unordered_writes");
+ }
+ if (txn_db_options.write_policy == WRITE_UNPREPARED &&
+ db_options.unordered_write) {
+ // TODO(lth): support it
+ return Status::NotSupported(
+ "WRITE_UNPREPARED is currently incompatible with unordered_writes");
+ }
+ if (txn_db_options.write_policy == WRITE_PREPARED &&
+ db_options.unordered_write && !db_options.two_write_queues) {
+ return Status::NotSupported(
+ "WRITE_PREPARED is incompatible with unordered_writes if "
+ "two_write_queues is not enabled.");
+ }
+
+ std::vector<ColumnFamilyDescriptor> column_families_copy = column_families;
+ std::vector<size_t> compaction_enabled_cf_indices;
+ DBOptions db_options_2pc = db_options;
+ PrepareWrap(&db_options_2pc, &column_families_copy,
+ &compaction_enabled_cf_indices);
+ const bool use_seq_per_batch =
+ txn_db_options.write_policy == WRITE_PREPARED ||
+ txn_db_options.write_policy == WRITE_UNPREPARED;
+ const bool use_batch_per_txn =
+ txn_db_options.write_policy == WRITE_COMMITTED ||
+ txn_db_options.write_policy == WRITE_PREPARED;
+ s = DBImpl::Open(db_options_2pc, dbname, column_families_copy, handles, &db,
+ use_seq_per_batch, use_batch_per_txn);
+ if (s.ok()) {
+ ROCKS_LOG_WARN(db->GetDBOptions().info_log,
+ "Transaction write_policy is %" PRId32,
+ static_cast<int>(txn_db_options.write_policy));
+ s = WrapDB(db, txn_db_options, compaction_enabled_cf_indices, *handles,
+ dbptr);
+ }
+ if (!s.ok()) {
+ // just in case it was not deleted (and not set to nullptr).
+ delete db;
+ }
+ return s;
+}
+
+void TransactionDB::PrepareWrap(
+ DBOptions* db_options, std::vector<ColumnFamilyDescriptor>* column_families,
+ std::vector<size_t>* compaction_enabled_cf_indices) {
+ compaction_enabled_cf_indices->clear();
+
+ // Enable MemTable History if not already enabled
+ for (size_t i = 0; i < column_families->size(); i++) {
+ ColumnFamilyOptions* cf_options = &(*column_families)[i].options;
+
+ if (cf_options->max_write_buffer_size_to_maintain == 0 &&
+ cf_options->max_write_buffer_number_to_maintain == 0) {
+ // Setting to -1 will set the History size to
+ // max_write_buffer_number * write_buffer_size.
+ cf_options->max_write_buffer_size_to_maintain = -1;
+ }
+ if (!cf_options->disable_auto_compactions) {
+ // Disable compactions momentarily to prevent race with DB::Open
+ cf_options->disable_auto_compactions = true;
+ compaction_enabled_cf_indices->push_back(i);
+ }
+ }
+ db_options->allow_2pc = true;
+}
+
+Status TransactionDB::WrapDB(
+ // make sure this db is already opened with memtable history enabled,
+ // auto compaction distabled and 2 phase commit enabled
+ DB* db, const TransactionDBOptions& txn_db_options,
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles, TransactionDB** dbptr) {
+ assert(db != nullptr);
+ assert(dbptr != nullptr);
+ *dbptr = nullptr;
+ std::unique_ptr<PessimisticTransactionDB> txn_db;
+ switch (txn_db_options.write_policy) {
+ case WRITE_UNPREPARED:
+ txn_db.reset(new WriteUnpreparedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ break;
+ case WRITE_PREPARED:
+ txn_db.reset(new WritePreparedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ break;
+ case WRITE_COMMITTED:
+ default:
+ txn_db.reset(new WriteCommittedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ }
+ txn_db->UpdateCFComparatorMap(handles);
+ Status s = txn_db->Initialize(compaction_enabled_cf_indices, handles);
+ // In case of a failure at this point, db is deleted via the txn_db destructor
+ // and set to nullptr.
+ if (s.ok()) {
+ *dbptr = txn_db.release();
+ }
+ return s;
+}
+
+Status TransactionDB::WrapStackableDB(
+ // make sure this stackable_db is already opened with memtable history
+ // enabled, auto compaction distabled and 2 phase commit enabled
+ StackableDB* db, const TransactionDBOptions& txn_db_options,
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles, TransactionDB** dbptr) {
+ assert(db != nullptr);
+ assert(dbptr != nullptr);
+ *dbptr = nullptr;
+ std::unique_ptr<PessimisticTransactionDB> txn_db;
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_UNPREPARED:
+ txn_db.reset(new WriteUnpreparedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ break;
+ case WRITE_PREPARED:
+ txn_db.reset(new WritePreparedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ break;
+ case WRITE_COMMITTED:
+ default:
+ txn_db.reset(new WriteCommittedTxnDB(
+ db, PessimisticTransactionDB::ValidateTxnDBOptions(txn_db_options)));
+ }
+ txn_db->UpdateCFComparatorMap(handles);
+ Status s = txn_db->Initialize(compaction_enabled_cf_indices, handles);
+ // In case of a failure at this point, db is deleted via the txn_db destructor
+ // and set to nullptr.
+ if (s.ok()) {
+ *dbptr = txn_db.release();
+ }
+ return s;
+}
+
+// Let TransactionLockMgr know that this column family exists so it can
+// allocate a LockMap for it.
+void PessimisticTransactionDB::AddColumnFamily(
+ const ColumnFamilyHandle* handle) {
+ lock_mgr_.AddColumnFamily(handle->GetID());
+}
+
+Status PessimisticTransactionDB::CreateColumnFamily(
+ const ColumnFamilyOptions& options, const std::string& column_family_name,
+ ColumnFamilyHandle** handle) {
+ InstrumentedMutexLock l(&column_family_mutex_);
+ Status s = VerifyCFOptions(options);
+ if (!s.ok()) {
+ return s;
+ }
+
+ s = db_->CreateColumnFamily(options, column_family_name, handle);
+ if (s.ok()) {
+ lock_mgr_.AddColumnFamily((*handle)->GetID());
+ UpdateCFComparatorMap(*handle);
+ }
+
+ return s;
+}
+
+// Let TransactionLockMgr know that it can deallocate the LockMap for this
+// column family.
+Status PessimisticTransactionDB::DropColumnFamily(
+ ColumnFamilyHandle* column_family) {
+ InstrumentedMutexLock l(&column_family_mutex_);
+
+ Status s = db_->DropColumnFamily(column_family);
+ if (s.ok()) {
+ lock_mgr_.RemoveColumnFamily(column_family->GetID());
+ }
+
+ return s;
+}
+
+Status PessimisticTransactionDB::TryLock(PessimisticTransaction* txn,
+ uint32_t cfh_id,
+ const std::string& key,
+ bool exclusive) {
+ return lock_mgr_.TryLock(txn, cfh_id, key, GetEnv(), exclusive);
+}
+
+void PessimisticTransactionDB::UnLock(PessimisticTransaction* txn,
+ const TransactionKeyMap* keys) {
+ lock_mgr_.UnLock(txn, keys, GetEnv());
+}
+
+void PessimisticTransactionDB::UnLock(PessimisticTransaction* txn,
+ uint32_t cfh_id, const std::string& key) {
+ lock_mgr_.UnLock(txn, cfh_id, key, GetEnv());
+}
+
+// Used when wrapping DB write operations in a transaction
+Transaction* PessimisticTransactionDB::BeginInternalTransaction(
+ const WriteOptions& options) {
+ TransactionOptions txn_options;
+ Transaction* txn = BeginTransaction(options, txn_options, nullptr);
+
+ // Use default timeout for non-transactional writes
+ txn->SetLockTimeout(txn_db_options_.default_lock_timeout);
+ return txn;
+}
+
+// All user Put, Merge, Delete, and Write requests must be intercepted to make
+// sure that they lock all keys that they are writing to avoid causing conflicts
+// with any concurrent transactions. The easiest way to do this is to wrap all
+// write operations in a transaction.
+//
+// Put(), Merge(), and Delete() only lock a single key per call. Write() will
+// sort its keys before locking them. This guarantees that TransactionDB write
+// methods cannot deadlock with each other (but still could deadlock with a
+// Transaction).
+Status PessimisticTransactionDB::Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& val) {
+ Status s;
+
+ Transaction* txn = BeginInternalTransaction(options);
+ txn->DisableIndexing();
+
+ // Since the client didn't create a transaction, they don't care about
+ // conflict checking for this write. So we just need to do PutUntracked().
+ s = txn->PutUntracked(column_family, key, val);
+
+ if (s.ok()) {
+ s = txn->Commit();
+ }
+
+ delete txn;
+
+ return s;
+}
+
+Status PessimisticTransactionDB::Delete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ Status s;
+
+ Transaction* txn = BeginInternalTransaction(wopts);
+ txn->DisableIndexing();
+
+ // Since the client didn't create a transaction, they don't care about
+ // conflict checking for this write. So we just need to do
+ // DeleteUntracked().
+ s = txn->DeleteUntracked(column_family, key);
+
+ if (s.ok()) {
+ s = txn->Commit();
+ }
+
+ delete txn;
+
+ return s;
+}
+
+Status PessimisticTransactionDB::SingleDelete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ Status s;
+
+ Transaction* txn = BeginInternalTransaction(wopts);
+ txn->DisableIndexing();
+
+ // Since the client didn't create a transaction, they don't care about
+ // conflict checking for this write. So we just need to do
+ // SingleDeleteUntracked().
+ s = txn->SingleDeleteUntracked(column_family, key);
+
+ if (s.ok()) {
+ s = txn->Commit();
+ }
+
+ delete txn;
+
+ return s;
+}
+
+Status PessimisticTransactionDB::Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ Status s;
+
+ Transaction* txn = BeginInternalTransaction(options);
+ txn->DisableIndexing();
+
+ // Since the client didn't create a transaction, they don't care about
+ // conflict checking for this write. So we just need to do
+ // MergeUntracked().
+ s = txn->MergeUntracked(column_family, key, value);
+
+ if (s.ok()) {
+ s = txn->Commit();
+ }
+
+ delete txn;
+
+ return s;
+}
+
+Status PessimisticTransactionDB::Write(const WriteOptions& opts,
+ WriteBatch* updates) {
+ return WriteWithConcurrencyControl(opts, updates);
+}
+
+Status WriteCommittedTxnDB::Write(const WriteOptions& opts,
+ WriteBatch* updates) {
+ if (txn_db_options_.skip_concurrency_control) {
+ return db_impl_->Write(opts, updates);
+ } else {
+ return WriteWithConcurrencyControl(opts, updates);
+ }
+}
+
+Status WriteCommittedTxnDB::Write(
+ const WriteOptions& opts,
+ const TransactionDBWriteOptimizations& optimizations, WriteBatch* updates) {
+ if (optimizations.skip_concurrency_control) {
+ return db_impl_->Write(opts, updates);
+ } else {
+ return WriteWithConcurrencyControl(opts, updates);
+ }
+}
+
+void PessimisticTransactionDB::InsertExpirableTransaction(
+ TransactionID tx_id, PessimisticTransaction* tx) {
+ assert(tx->GetExpirationTime() > 0);
+ std::lock_guard<std::mutex> lock(map_mutex_);
+ expirable_transactions_map_.insert({tx_id, tx});
+}
+
+void PessimisticTransactionDB::RemoveExpirableTransaction(TransactionID tx_id) {
+ std::lock_guard<std::mutex> lock(map_mutex_);
+ expirable_transactions_map_.erase(tx_id);
+}
+
+bool PessimisticTransactionDB::TryStealingExpiredTransactionLocks(
+ TransactionID tx_id) {
+ std::lock_guard<std::mutex> lock(map_mutex_);
+
+ auto tx_it = expirable_transactions_map_.find(tx_id);
+ if (tx_it == expirable_transactions_map_.end()) {
+ return true;
+ }
+ PessimisticTransaction& tx = *(tx_it->second);
+ return tx.TryStealingLocks();
+}
+
+void PessimisticTransactionDB::ReinitializeTransaction(
+ Transaction* txn, const WriteOptions& write_options,
+ const TransactionOptions& txn_options) {
+ auto txn_impl =
+ static_cast_with_check<PessimisticTransaction, Transaction>(txn);
+
+ txn_impl->Reinitialize(this, write_options, txn_options);
+}
+
+Transaction* PessimisticTransactionDB::GetTransactionByName(
+ const TransactionName& name) {
+ std::lock_guard<std::mutex> lock(name_map_mutex_);
+ auto it = transactions_.find(name);
+ if (it == transactions_.end()) {
+ return nullptr;
+ } else {
+ return it->second;
+ }
+}
+
+void PessimisticTransactionDB::GetAllPreparedTransactions(
+ std::vector<Transaction*>* transv) {
+ assert(transv);
+ transv->clear();
+ std::lock_guard<std::mutex> lock(name_map_mutex_);
+ for (auto it = transactions_.begin(); it != transactions_.end(); ++it) {
+ if (it->second->GetState() == Transaction::PREPARED) {
+ transv->push_back(it->second);
+ }
+ }
+}
+
+TransactionLockMgr::LockStatusData
+PessimisticTransactionDB::GetLockStatusData() {
+ return lock_mgr_.GetLockStatusData();
+}
+
+std::vector<DeadlockPath> PessimisticTransactionDB::GetDeadlockInfoBuffer() {
+ return lock_mgr_.GetDeadlockInfoBuffer();
+}
+
+void PessimisticTransactionDB::SetDeadlockInfoBufferSize(uint32_t target_size) {
+ lock_mgr_.Resize(target_size);
+}
+
+void PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
+ assert(txn);
+ assert(txn->GetName().length() > 0);
+ assert(GetTransactionByName(txn->GetName()) == nullptr);
+ assert(txn->GetState() == Transaction::STARTED);
+ std::lock_guard<std::mutex> lock(name_map_mutex_);
+ transactions_[txn->GetName()] = txn;
+}
+
+void PessimisticTransactionDB::UnregisterTransaction(Transaction* txn) {
+ assert(txn);
+ std::lock_guard<std::mutex> lock(name_map_mutex_);
+ auto it = transactions_.find(txn->GetName());
+ assert(it != transactions_.end());
+ transactions_.erase(it);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.h b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.h
new file mode 100644
index 00000000..39346ddd
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/pessimistic_transaction_db.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <mutex>
+#include <queue>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "db/db_iter.h"
+#include "db/read_callback.h"
+#include "db/snapshot_checker.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "util/cast_util.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/transaction_lock_mgr.h"
+#include "utilities/transactions/write_prepared_txn.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class PessimisticTransactionDB : public TransactionDB {
+ public:
+ explicit PessimisticTransactionDB(DB* db,
+ const TransactionDBOptions& txn_db_options);
+
+ explicit PessimisticTransactionDB(StackableDB* db,
+ const TransactionDBOptions& txn_db_options);
+
+ virtual ~PessimisticTransactionDB();
+
+ virtual const Snapshot* GetSnapshot() override { return db_->GetSnapshot(); }
+
+ virtual Status Initialize(
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles);
+
+ Transaction* BeginTransaction(const WriteOptions& write_options,
+ const TransactionOptions& txn_options,
+ Transaction* old_txn) override = 0;
+
+ using StackableDB::Put;
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& val) override;
+
+ using StackableDB::Delete;
+ virtual Status Delete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+
+ using StackableDB::SingleDelete;
+ virtual Status SingleDelete(const WriteOptions& wopts,
+ ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+
+ using StackableDB::Merge;
+ virtual Status Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+
+ using TransactionDB::Write;
+ virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override;
+ inline Status WriteWithConcurrencyControl(const WriteOptions& opts,
+ WriteBatch* updates) {
+ // Need to lock all keys in this batch to prevent write conflicts with
+ // concurrent transactions.
+ Transaction* txn = BeginInternalTransaction(opts);
+ txn->DisableIndexing();
+
+ auto txn_impl =
+ static_cast_with_check<PessimisticTransaction, Transaction>(txn);
+
+ // Since commitBatch sorts the keys before locking, concurrent Write()
+ // operations will not cause a deadlock.
+ // In order to avoid a deadlock with a concurrent Transaction, Transactions
+ // should use a lock timeout.
+ Status s = txn_impl->CommitBatch(updates);
+
+ delete txn;
+
+ return s;
+ }
+
+ using StackableDB::CreateColumnFamily;
+ virtual Status CreateColumnFamily(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle) override;
+
+ using StackableDB::DropColumnFamily;
+ virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) override;
+
+ Status TryLock(PessimisticTransaction* txn, uint32_t cfh_id,
+ const std::string& key, bool exclusive);
+
+ void UnLock(PessimisticTransaction* txn, const TransactionKeyMap* keys);
+ void UnLock(PessimisticTransaction* txn, uint32_t cfh_id,
+ const std::string& key);
+
+ void AddColumnFamily(const ColumnFamilyHandle* handle);
+
+ static TransactionDBOptions ValidateTxnDBOptions(
+ const TransactionDBOptions& txn_db_options);
+
+ const TransactionDBOptions& GetTxnDBOptions() const {
+ return txn_db_options_;
+ }
+
+ void InsertExpirableTransaction(TransactionID tx_id,
+ PessimisticTransaction* tx);
+ void RemoveExpirableTransaction(TransactionID tx_id);
+
+ // If transaction is no longer available, locks can be stolen
+ // If transaction is available, try stealing locks directly from transaction
+ // It is the caller's responsibility to ensure that the referred transaction
+ // is expirable (GetExpirationTime() > 0) and that it is expired.
+ bool TryStealingExpiredTransactionLocks(TransactionID tx_id);
+
+ Transaction* GetTransactionByName(const TransactionName& name) override;
+
+ void RegisterTransaction(Transaction* txn);
+ void UnregisterTransaction(Transaction* txn);
+
+ // not thread safe. current use case is during recovery (single thread)
+ void GetAllPreparedTransactions(std::vector<Transaction*>* trans) override;
+
+ TransactionLockMgr::LockStatusData GetLockStatusData() override;
+
+ std::vector<DeadlockPath> GetDeadlockInfoBuffer() override;
+ void SetDeadlockInfoBufferSize(uint32_t target_size) override;
+
+ // The default implementation does nothing. The actual implementation is moved
+ // to the child classes that actually need this information. This was due to
+ // an odd performance drop we observed when the added std::atomic member to
+ // the base class even when the subclass do not read it in the fast path.
+ virtual void UpdateCFComparatorMap(const std::vector<ColumnFamilyHandle*>&) {}
+ virtual void UpdateCFComparatorMap(ColumnFamilyHandle*) {}
+
+ protected:
+ DBImpl* db_impl_;
+ std::shared_ptr<Logger> info_log_;
+ const TransactionDBOptions txn_db_options_;
+
+ void ReinitializeTransaction(
+ Transaction* txn, const WriteOptions& write_options,
+ const TransactionOptions& txn_options = TransactionOptions());
+
+ virtual Status VerifyCFOptions(const ColumnFamilyOptions& cf_options);
+
+ private:
+ friend class WritePreparedTxnDB;
+ friend class WritePreparedTxnDBMock;
+ friend class WriteUnpreparedTxn;
+ friend class TransactionTest_DoubleCrashInRecovery_Test;
+ friend class TransactionTest_DoubleEmptyWrite_Test;
+ friend class TransactionTest_DuplicateKeys_Test;
+ friend class TransactionTest_PersistentTwoPhaseTransactionTest_Test;
+ friend class TransactionTest_TwoPhaseDoubleRecoveryTest_Test;
+ friend class TransactionTest_TwoPhaseOutOfOrderDelete_Test;
+ friend class TransactionStressTest_TwoPhaseLongPrepareTest_Test;
+ friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
+ friend class WriteUnpreparedTransactionTest_MarkLogWithPrepSection_Test;
+ TransactionLockMgr lock_mgr_;
+
+ // Must be held when adding/dropping column families.
+ InstrumentedMutex column_family_mutex_;
+ Transaction* BeginInternalTransaction(const WriteOptions& options);
+
+ // Used to ensure that no locks are stolen from an expirable transaction
+ // that has started a commit. Only transactions with an expiration time
+ // should be in this map.
+ std::mutex map_mutex_;
+ std::unordered_map<TransactionID, PessimisticTransaction*>
+ expirable_transactions_map_;
+
+ // map from name to two phase transaction instance
+ std::mutex name_map_mutex_;
+ std::unordered_map<TransactionName, Transaction*> transactions_;
+
+ // Signal that we are testing a crash scenario. Some asserts could be relaxed
+ // in such cases.
+ virtual void TEST_Crash() {}
+};
+
+// A PessimisticTransactionDB that writes the data to the DB after the commit.
+// In this way the DB only contains the committed data.
+class WriteCommittedTxnDB : public PessimisticTransactionDB {
+ public:
+ explicit WriteCommittedTxnDB(DB* db,
+ const TransactionDBOptions& txn_db_options)
+ : PessimisticTransactionDB(db, txn_db_options) {}
+
+ explicit WriteCommittedTxnDB(StackableDB* db,
+ const TransactionDBOptions& txn_db_options)
+ : PessimisticTransactionDB(db, txn_db_options) {}
+
+ virtual ~WriteCommittedTxnDB() {}
+
+ Transaction* BeginTransaction(const WriteOptions& write_options,
+ const TransactionOptions& txn_options,
+ Transaction* old_txn) override;
+
+ // Optimized version of ::Write that makes use of skip_concurrency_control
+ // hint
+ using TransactionDB::Write;
+ virtual Status Write(const WriteOptions& opts,
+ const TransactionDBWriteOptimizations& optimizations,
+ WriteBatch* updates) override;
+ virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/snapshot_checker.cc b/storage/rocksdb/rocksdb/utilities/transactions/snapshot_checker.cc
new file mode 100644
index 00000000..9c43bef4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/snapshot_checker.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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/snapshot_checker.h"
+
+#ifdef ROCKSDB_LITE
+#include <assert.h>
+#endif // ROCKSDB_LITE
+
+#include "utilities/transactions/write_prepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+#ifdef ROCKSDB_LITE
+WritePreparedSnapshotChecker::WritePreparedSnapshotChecker(
+ WritePreparedTxnDB* /*txn_db*/) {}
+
+SnapshotCheckerResult WritePreparedSnapshotChecker::CheckInSnapshot(
+ SequenceNumber /*sequence*/, SequenceNumber /*snapshot_sequence*/) const {
+ // Should never be called in LITE mode.
+ assert(false);
+ return SnapshotCheckerResult::kInSnapshot;
+}
+
+#else
+
+WritePreparedSnapshotChecker::WritePreparedSnapshotChecker(
+ WritePreparedTxnDB* txn_db)
+ : txn_db_(txn_db){};
+
+SnapshotCheckerResult WritePreparedSnapshotChecker::CheckInSnapshot(
+ SequenceNumber sequence, SequenceNumber snapshot_sequence) const {
+ bool snapshot_released = false;
+ // TODO(myabandeh): set min_uncommitted
+ bool in_snapshot = txn_db_->IsInSnapshot(
+ sequence, snapshot_sequence, kMinUnCommittedSeq, &snapshot_released);
+ if (snapshot_released) {
+ return SnapshotCheckerResult::kSnapshotReleased;
+ }
+ return in_snapshot ? SnapshotCheckerResult::kInSnapshot
+ : SnapshotCheckerResult::kNotInSnapshot;
+}
+
+#endif // ROCKSDB_LITE
+DisableGCSnapshotChecker DisableGCSnapshotChecker::instance_;
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.cc b/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.cc
new file mode 100644
index 00000000..805d4ab3
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.cc
@@ -0,0 +1,837 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_base.h"
+
+#include <cinttypes>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+TransactionBaseImpl::TransactionBaseImpl(DB* db,
+ const WriteOptions& write_options)
+ : db_(db),
+ dbimpl_(static_cast_with_check<DBImpl, DB>(db)),
+ write_options_(write_options),
+ cmp_(GetColumnFamilyUserComparator(db->DefaultColumnFamily())),
+ start_time_(db_->GetEnv()->NowMicros()),
+ write_batch_(cmp_, 0, true, 0),
+ indexing_enabled_(true) {
+ assert(dynamic_cast<DBImpl*>(db_) != nullptr);
+ log_number_ = 0;
+ if (dbimpl_->allow_2pc()) {
+ InitWriteBatch();
+ }
+}
+
+TransactionBaseImpl::~TransactionBaseImpl() {
+ // Release snapshot if snapshot is set
+ SetSnapshotInternal(nullptr);
+}
+
+void TransactionBaseImpl::Clear() {
+ save_points_.reset(nullptr);
+ write_batch_.Clear();
+ commit_time_batch_.Clear();
+ tracked_keys_.clear();
+ num_puts_ = 0;
+ num_deletes_ = 0;
+ num_merges_ = 0;
+
+ if (dbimpl_->allow_2pc()) {
+ InitWriteBatch();
+ }
+}
+
+void TransactionBaseImpl::Reinitialize(DB* db,
+ const WriteOptions& write_options) {
+ Clear();
+ ClearSnapshot();
+ id_ = 0;
+ db_ = db;
+ name_.clear();
+ log_number_ = 0;
+ write_options_ = write_options;
+ start_time_ = db_->GetEnv()->NowMicros();
+ indexing_enabled_ = true;
+ cmp_ = GetColumnFamilyUserComparator(db_->DefaultColumnFamily());
+}
+
+void TransactionBaseImpl::SetSnapshot() {
+ const Snapshot* snapshot = dbimpl_->GetSnapshotForWriteConflictBoundary();
+ SetSnapshotInternal(snapshot);
+}
+
+void TransactionBaseImpl::SetSnapshotInternal(const Snapshot* snapshot) {
+ // Set a custom deleter for the snapshot_ SharedPtr as the snapshot needs to
+ // be released, not deleted when it is no longer referenced.
+ snapshot_.reset(snapshot, std::bind(&TransactionBaseImpl::ReleaseSnapshot,
+ this, std::placeholders::_1, db_));
+ snapshot_needed_ = false;
+ snapshot_notifier_ = nullptr;
+}
+
+void TransactionBaseImpl::SetSnapshotOnNextOperation(
+ std::shared_ptr<TransactionNotifier> notifier) {
+ snapshot_needed_ = true;
+ snapshot_notifier_ = notifier;
+}
+
+void TransactionBaseImpl::SetSnapshotIfNeeded() {
+ if (snapshot_needed_) {
+ std::shared_ptr<TransactionNotifier> notifier = snapshot_notifier_;
+ SetSnapshot();
+ if (notifier != nullptr) {
+ notifier->SnapshotCreated(GetSnapshot());
+ }
+ }
+}
+
+Status TransactionBaseImpl::TryLock(ColumnFamilyHandle* column_family,
+ const SliceParts& key, bool read_only,
+ bool exclusive, const bool do_validate,
+ const bool assume_tracked) {
+ size_t key_size = 0;
+ for (int i = 0; i < key.num_parts; ++i) {
+ key_size += key.parts[i].size();
+ }
+
+ std::string str;
+ str.reserve(key_size);
+
+ for (int i = 0; i < key.num_parts; ++i) {
+ str.append(key.parts[i].data(), key.parts[i].size());
+ }
+
+ return TryLock(column_family, str, read_only, exclusive, do_validate,
+ assume_tracked);
+}
+
+void TransactionBaseImpl::SetSavePoint() {
+ if (save_points_ == nullptr) {
+ save_points_.reset(new std::stack<TransactionBaseImpl::SavePoint, autovector<TransactionBaseImpl::SavePoint>>());
+ }
+ save_points_->emplace(snapshot_, snapshot_needed_, snapshot_notifier_,
+ num_puts_, num_deletes_, num_merges_);
+ write_batch_.SetSavePoint();
+}
+
+Status TransactionBaseImpl::RollbackToSavePoint() {
+ if (save_points_ != nullptr && save_points_->size() > 0) {
+ // Restore saved SavePoint
+ TransactionBaseImpl::SavePoint& save_point = save_points_->top();
+ snapshot_ = save_point.snapshot_;
+ snapshot_needed_ = save_point.snapshot_needed_;
+ snapshot_notifier_ = save_point.snapshot_notifier_;
+ num_puts_ = save_point.num_puts_;
+ num_deletes_ = save_point.num_deletes_;
+ num_merges_ = save_point.num_merges_;
+
+ // Rollback batch
+ Status s = write_batch_.RollbackToSavePoint();
+ assert(s.ok());
+
+ // Rollback any keys that were tracked since the last savepoint
+ const TransactionKeyMap& key_map = save_point.new_keys_;
+ for (const auto& key_map_iter : key_map) {
+ uint32_t column_family_id = key_map_iter.first;
+ auto& keys = key_map_iter.second;
+
+ auto& cf_tracked_keys = tracked_keys_[column_family_id];
+
+ for (const auto& key_iter : keys) {
+ const std::string& key = key_iter.first;
+ uint32_t num_reads = key_iter.second.num_reads;
+ uint32_t num_writes = key_iter.second.num_writes;
+
+ auto tracked_keys_iter = cf_tracked_keys.find(key);
+ assert(tracked_keys_iter != cf_tracked_keys.end());
+
+ // Decrement the total reads/writes of this key by the number of
+ // reads/writes done since the last SavePoint.
+ if (num_reads > 0) {
+ assert(tracked_keys_iter->second.num_reads >= num_reads);
+ tracked_keys_iter->second.num_reads -= num_reads;
+ }
+ if (num_writes > 0) {
+ assert(tracked_keys_iter->second.num_writes >= num_writes);
+ tracked_keys_iter->second.num_writes -= num_writes;
+ }
+ if (tracked_keys_iter->second.num_reads == 0 &&
+ tracked_keys_iter->second.num_writes == 0) {
+ cf_tracked_keys.erase(tracked_keys_iter);
+ }
+ }
+ }
+
+ save_points_->pop();
+
+ return s;
+ } else {
+ assert(write_batch_.RollbackToSavePoint().IsNotFound());
+ return Status::NotFound();
+ }
+}
+
+Status TransactionBaseImpl::PopSavePoint() {
+ if (save_points_ == nullptr ||
+ save_points_->empty()) {
+ // No SavePoint yet.
+ assert(write_batch_.PopSavePoint().IsNotFound());
+ return Status::NotFound();
+ }
+
+ assert(!save_points_->empty());
+ // If there is another savepoint A below the current savepoint B, then A needs
+ // to inherit tracked_keys in B so that if we rollback to savepoint A, we
+ // remember to unlock keys in B. If there is no other savepoint below, then we
+ // can safely discard savepoint info.
+ if (save_points_->size() == 1) {
+ save_points_->pop();
+ } else {
+ TransactionBaseImpl::SavePoint top;
+ std::swap(top, save_points_->top());
+ save_points_->pop();
+
+ const TransactionKeyMap& curr_cf_key_map = top.new_keys_;
+ TransactionKeyMap& prev_cf_key_map = save_points_->top().new_keys_;
+
+ for (const auto& curr_cf_key_iter : curr_cf_key_map) {
+ uint32_t column_family_id = curr_cf_key_iter.first;
+ const std::unordered_map<std::string, TransactionKeyMapInfo>& curr_keys =
+ curr_cf_key_iter.second;
+
+ // If cfid was not previously tracked, just copy everything over.
+ auto prev_keys_iter = prev_cf_key_map.find(column_family_id);
+ if (prev_keys_iter == prev_cf_key_map.end()) {
+ prev_cf_key_map.emplace(curr_cf_key_iter);
+ } else {
+ std::unordered_map<std::string, TransactionKeyMapInfo>& prev_keys =
+ prev_keys_iter->second;
+ for (const auto& key_iter : curr_keys) {
+ const std::string& key = key_iter.first;
+ const TransactionKeyMapInfo& info = key_iter.second;
+ // If key was not previously tracked, just copy the whole struct over.
+ // Otherwise, some merging needs to occur.
+ auto prev_info = prev_keys.find(key);
+ if (prev_info == prev_keys.end()) {
+ prev_keys.emplace(key_iter);
+ } else {
+ prev_info->second.Merge(info);
+ }
+ }
+ }
+ }
+ }
+
+ return write_batch_.PopSavePoint();
+}
+
+Status TransactionBaseImpl::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, std::string* value) {
+ assert(value != nullptr);
+ PinnableSlice pinnable_val(value);
+ assert(!pinnable_val.IsPinned());
+ auto s = Get(read_options, column_family, key, &pinnable_val);
+ if (s.ok() && pinnable_val.IsPinned()) {
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ } // else value is already assigned
+ return s;
+}
+
+Status TransactionBaseImpl::Get(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* pinnable_val) {
+ return write_batch_.GetFromBatchAndDB(db_, read_options, column_family, key,
+ pinnable_val);
+}
+
+Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, std::string* value,
+ bool exclusive,
+ const bool do_validate) {
+ if (!do_validate && read_options.snapshot != nullptr) {
+ return Status::InvalidArgument(
+ "If do_validate is false then GetForUpdate with snapshot is not "
+ "defined.");
+ }
+ Status s =
+ TryLock(column_family, key, true /* read_only */, exclusive, do_validate);
+
+ if (s.ok() && value != nullptr) {
+ assert(value != nullptr);
+ PinnableSlice pinnable_val(value);
+ assert(!pinnable_val.IsPinned());
+ s = Get(read_options, column_family, key, &pinnable_val);
+ if (s.ok() && pinnable_val.IsPinned()) {
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ } // else value is already assigned
+ }
+ return s;
+}
+
+Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key,
+ PinnableSlice* pinnable_val,
+ bool exclusive,
+ const bool do_validate) {
+ if (!do_validate && read_options.snapshot != nullptr) {
+ return Status::InvalidArgument(
+ "If do_validate is false then GetForUpdate with snapshot is not "
+ "defined.");
+ }
+ Status s =
+ TryLock(column_family, key, true /* read_only */, exclusive, do_validate);
+
+ if (s.ok() && pinnable_val != nullptr) {
+ s = Get(read_options, column_family, key, pinnable_val);
+ }
+ return s;
+}
+
+std::vector<Status> TransactionBaseImpl::MultiGet(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ size_t num_keys = keys.size();
+ values->resize(num_keys);
+
+ std::vector<Status> stat_list(num_keys);
+ for (size_t i = 0; i < num_keys; ++i) {
+ std::string* value = values ? &(*values)[i] : nullptr;
+ stat_list[i] = Get(read_options, column_family[i], keys[i], value);
+ }
+
+ return stat_list;
+}
+
+void TransactionBaseImpl::MultiGet(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input) {
+ write_batch_.MultiGetFromBatchAndDB(db_, read_options, column_family,
+ num_keys, keys, values, statuses,
+ sorted_input);
+}
+
+std::vector<Status> TransactionBaseImpl::MultiGetForUpdate(
+ const ReadOptions& read_options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ // Regardless of whether the MultiGet succeeded, track these keys.
+ size_t num_keys = keys.size();
+ values->resize(num_keys);
+
+ // Lock all keys
+ for (size_t i = 0; i < num_keys; ++i) {
+ Status s = TryLock(column_family[i], keys[i], true /* read_only */,
+ true /* exclusive */);
+ if (!s.ok()) {
+ // Fail entire multiget if we cannot lock all keys
+ return std::vector<Status>(num_keys, s);
+ }
+ }
+
+ // TODO(agiardullo): optimize multiget?
+ std::vector<Status> stat_list(num_keys);
+ for (size_t i = 0; i < num_keys; ++i) {
+ std::string* value = values ? &(*values)[i] : nullptr;
+ stat_list[i] = Get(read_options, column_family[i], keys[i], value);
+ }
+
+ return stat_list;
+}
+
+Iterator* TransactionBaseImpl::GetIterator(const ReadOptions& read_options) {
+ Iterator* db_iter = db_->NewIterator(read_options);
+ assert(db_iter);
+
+ return write_batch_.NewIteratorWithBase(db_iter);
+}
+
+Iterator* TransactionBaseImpl::GetIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) {
+ Iterator* db_iter = db_->NewIterator(read_options, column_family);
+ assert(db_iter);
+
+ return write_batch_.NewIteratorWithBase(column_family, db_iter,
+ &read_options);
+}
+
+Status TransactionBaseImpl::Put(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Put(column_family, key, value);
+ if (s.ok()) {
+ num_puts_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::Put(ColumnFamilyHandle* column_family,
+ const SliceParts& key, const SliceParts& value,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Put(column_family, key, value);
+ if (s.ok()) {
+ num_puts_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::Merge(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Merge(column_family, key, value);
+ if (s.ok()) {
+ num_merges_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::Delete(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Delete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Delete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->SingleDelete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked) {
+ const bool do_validate = !assume_tracked;
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, do_validate, assume_tracked);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->SingleDelete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::PutUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Put(column_family, key, value);
+ if (s.ok()) {
+ num_puts_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::PutUntracked(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const SliceParts& value) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Put(column_family, key, value);
+ if (s.ok()) {
+ num_puts_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::MergeUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const Slice& value) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Merge(column_family, key, value);
+ if (s.ok()) {
+ num_merges_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::DeleteUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Delete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::DeleteUntracked(ColumnFamilyHandle* column_family,
+ const SliceParts& key) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->Delete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+Status TransactionBaseImpl::SingleDeleteUntracked(
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ Status s = TryLock(column_family, key, false /* read_only */,
+ true /* exclusive */, false /* do_validate */);
+
+ if (s.ok()) {
+ s = GetBatchForWrite()->SingleDelete(column_family, key);
+ if (s.ok()) {
+ num_deletes_++;
+ }
+ }
+
+ return s;
+}
+
+void TransactionBaseImpl::PutLogData(const Slice& blob) {
+ write_batch_.PutLogData(blob);
+}
+
+WriteBatchWithIndex* TransactionBaseImpl::GetWriteBatch() {
+ return &write_batch_;
+}
+
+uint64_t TransactionBaseImpl::GetElapsedTime() const {
+ return (db_->GetEnv()->NowMicros() - start_time_) / 1000;
+}
+
+uint64_t TransactionBaseImpl::GetNumPuts() const { return num_puts_; }
+
+uint64_t TransactionBaseImpl::GetNumDeletes() const { return num_deletes_; }
+
+uint64_t TransactionBaseImpl::GetNumMerges() const { return num_merges_; }
+
+uint64_t TransactionBaseImpl::GetNumKeys() const {
+ uint64_t count = 0;
+
+ // sum up locked keys in all column families
+ for (const auto& key_map_iter : tracked_keys_) {
+ const auto& keys = key_map_iter.second;
+ count += keys.size();
+ }
+
+ return count;
+}
+
+void TransactionBaseImpl::TrackKey(uint32_t cfh_id, const std::string& key,
+ SequenceNumber seq, bool read_only,
+ bool exclusive) {
+ // Update map of all tracked keys for this transaction
+ TrackKey(&tracked_keys_, cfh_id, key, seq, read_only, exclusive);
+
+ if (save_points_ != nullptr && !save_points_->empty()) {
+ // Update map of tracked keys in this SavePoint
+ TrackKey(&save_points_->top().new_keys_, cfh_id, key, seq, read_only,
+ exclusive);
+ }
+}
+
+// Add a key to the given TransactionKeyMap
+// seq for pessimistic transactions is the sequence number from which we know
+// there has not been a concurrent update to the key.
+void TransactionBaseImpl::TrackKey(TransactionKeyMap* key_map, uint32_t cfh_id,
+ const std::string& key, SequenceNumber seq,
+ bool read_only, bool exclusive) {
+ auto& cf_key_map = (*key_map)[cfh_id];
+#ifdef __cpp_lib_unordered_map_try_emplace
+ // use c++17's try_emplace if available, to avoid rehashing the key
+ // in case it is not already in the map
+ auto result = cf_key_map.try_emplace(key, seq);
+ auto iter = result.first;
+ if (!result.second && seq < iter->second.seq) {
+ // Now tracking this key with an earlier sequence number
+ iter->second.seq = seq;
+ }
+#else
+ auto iter = cf_key_map.find(key);
+ if (iter == cf_key_map.end()) {
+ auto result = cf_key_map.emplace(key, TransactionKeyMapInfo(seq));
+ iter = result.first;
+ } else if (seq < iter->second.seq) {
+ // Now tracking this key with an earlier sequence number
+ iter->second.seq = seq;
+ }
+#endif
+ // else we do not update the seq. The smaller the tracked seq, the stronger it
+ // the guarantee since it implies from the seq onward there has not been a
+ // concurrent update to the key. So we update the seq if it implies stronger
+ // guarantees, i.e., if it is smaller than the existing tracked seq.
+
+ if (read_only) {
+ iter->second.num_reads++;
+ } else {
+ iter->second.num_writes++;
+ }
+ iter->second.exclusive |= exclusive;
+}
+
+std::unique_ptr<TransactionKeyMap>
+TransactionBaseImpl::GetTrackedKeysSinceSavePoint() {
+ if (save_points_ != nullptr && !save_points_->empty()) {
+ // Examine the number of reads/writes performed on all keys written
+ // since the last SavePoint and compare to the total number of reads/writes
+ // for each key.
+ TransactionKeyMap* result = new TransactionKeyMap();
+ for (const auto& key_map_iter : save_points_->top().new_keys_) {
+ uint32_t column_family_id = key_map_iter.first;
+ auto& keys = key_map_iter.second;
+
+ auto& cf_tracked_keys = tracked_keys_[column_family_id];
+
+ for (const auto& key_iter : keys) {
+ const std::string& key = key_iter.first;
+ uint32_t num_reads = key_iter.second.num_reads;
+ uint32_t num_writes = key_iter.second.num_writes;
+
+ auto total_key_info = cf_tracked_keys.find(key);
+ assert(total_key_info != cf_tracked_keys.end());
+ assert(total_key_info->second.num_reads >= num_reads);
+ assert(total_key_info->second.num_writes >= num_writes);
+
+ if (total_key_info->second.num_reads == num_reads &&
+ total_key_info->second.num_writes == num_writes) {
+ // All the reads/writes to this key were done in the last savepoint.
+ bool read_only = (num_writes == 0);
+ TrackKey(result, column_family_id, key, key_iter.second.seq,
+ read_only, key_iter.second.exclusive);
+ }
+ }
+ }
+ return std::unique_ptr<TransactionKeyMap>(result);
+ }
+
+ // No SavePoint
+ return nullptr;
+}
+
+// Gets the write batch that should be used for Put/Merge/Deletes.
+//
+// Returns either a WriteBatch or WriteBatchWithIndex depending on whether
+// DisableIndexing() has been called.
+WriteBatchBase* TransactionBaseImpl::GetBatchForWrite() {
+ if (indexing_enabled_) {
+ // Use WriteBatchWithIndex
+ return &write_batch_;
+ } else {
+ // Don't use WriteBatchWithIndex. Return base WriteBatch.
+ return write_batch_.GetWriteBatch();
+ }
+}
+
+void TransactionBaseImpl::ReleaseSnapshot(const Snapshot* snapshot, DB* db) {
+ if (snapshot != nullptr) {
+ ROCKS_LOG_DETAILS(dbimpl_->immutable_db_options().info_log,
+ "ReleaseSnapshot %" PRIu64 " Set",
+ snapshot->GetSequenceNumber());
+ db->ReleaseSnapshot(snapshot);
+ }
+}
+
+void TransactionBaseImpl::UndoGetForUpdate(ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ uint32_t column_family_id = GetColumnFamilyID(column_family);
+ auto& cf_tracked_keys = tracked_keys_[column_family_id];
+ std::string key_str = key.ToString();
+ bool can_decrement = false;
+ bool can_unlock __attribute__((__unused__)) = false;
+
+ if (save_points_ != nullptr && !save_points_->empty()) {
+ // Check if this key was fetched ForUpdate in this SavePoint
+ auto& cf_savepoint_keys = save_points_->top().new_keys_[column_family_id];
+
+ auto savepoint_iter = cf_savepoint_keys.find(key_str);
+ if (savepoint_iter != cf_savepoint_keys.end()) {
+ if (savepoint_iter->second.num_reads > 0) {
+ savepoint_iter->second.num_reads--;
+ can_decrement = true;
+
+ if (savepoint_iter->second.num_reads == 0 &&
+ savepoint_iter->second.num_writes == 0) {
+ // No other GetForUpdates or write on this key in this SavePoint
+ cf_savepoint_keys.erase(savepoint_iter);
+ can_unlock = true;
+ }
+ }
+ }
+ } else {
+ // No SavePoint set
+ can_decrement = true;
+ can_unlock = true;
+ }
+
+ // We can only decrement the read count for this key if we were able to
+ // decrement the read count in the current SavePoint, OR if there is no
+ // SavePoint set.
+ if (can_decrement) {
+ auto key_iter = cf_tracked_keys.find(key_str);
+
+ if (key_iter != cf_tracked_keys.end()) {
+ if (key_iter->second.num_reads > 0) {
+ key_iter->second.num_reads--;
+
+ if (key_iter->second.num_reads == 0 &&
+ key_iter->second.num_writes == 0) {
+ // No other GetForUpdates or writes on this key
+ assert(can_unlock);
+ cf_tracked_keys.erase(key_iter);
+ UnlockGetForUpdate(column_family, key);
+ }
+ }
+ }
+ }
+}
+
+Status TransactionBaseImpl::RebuildFromWriteBatch(WriteBatch* src_batch) {
+ struct IndexedWriteBatchBuilder : public WriteBatch::Handler {
+ Transaction* txn_;
+ DBImpl* db_;
+ IndexedWriteBatchBuilder(Transaction* txn, DBImpl* db)
+ : txn_(txn), db_(db) {
+ assert(dynamic_cast<TransactionBaseImpl*>(txn_) != nullptr);
+ }
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice& val) override {
+ return txn_->Put(db_->GetColumnFamilyHandle(cf), key, val);
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ return txn_->Delete(db_->GetColumnFamilyHandle(cf), key);
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ return txn_->SingleDelete(db_->GetColumnFamilyHandle(cf), key);
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice& val) override {
+ return txn_->Merge(db_->GetColumnFamilyHandle(cf), key, val);
+ }
+
+ // this is used for reconstructing prepared transactions upon
+ // recovery. there should not be any meta markers in the batches
+ // we are processing.
+ Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
+
+ Status MarkEndPrepare(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkCommit(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ };
+
+ IndexedWriteBatchBuilder copycat(this, dbimpl_);
+ return src_batch->Iterate(&copycat);
+}
+
+WriteBatch* TransactionBaseImpl::GetCommitTimeWriteBatch() {
+ return &commit_time_batch_;
+}
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.h b/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.h
new file mode 100644
index 00000000..f279676c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_base.h
@@ -0,0 +1,374 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <stack>
+#include <string>
+#include <vector>
+
+#include "db/write_batch_internal.h"
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/autovector.h"
+#include "utilities/transactions/transaction_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TransactionBaseImpl : public Transaction {
+ public:
+ TransactionBaseImpl(DB* db, const WriteOptions& write_options);
+
+ virtual ~TransactionBaseImpl();
+
+ // Remove pending operations queued in this transaction.
+ virtual void Clear();
+
+ void Reinitialize(DB* db, const WriteOptions& write_options);
+
+ // Called before executing Put, Merge, Delete, and GetForUpdate. If TryLock
+ // returns non-OK, the Put/Merge/Delete/GetForUpdate will be failed.
+ // do_validate will be false if called from PutUntracked, DeleteUntracked,
+ // MergeUntracked, or GetForUpdate(do_validate=false)
+ virtual Status TryLock(ColumnFamilyHandle* column_family, const Slice& key,
+ bool read_only, bool exclusive,
+ const bool do_validate = true,
+ const bool assume_tracked = false) = 0;
+
+ void SetSavePoint() override;
+
+ Status RollbackToSavePoint() override;
+
+ Status PopSavePoint() override;
+
+ using Transaction::Get;
+ Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, std::string* value) override;
+
+ Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value) override;
+
+ Status Get(const ReadOptions& options, const Slice& key,
+ std::string* value) override {
+ return Get(options, db_->DefaultColumnFamily(), key, value);
+ }
+
+ using Transaction::GetForUpdate;
+ Status GetForUpdate(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value, bool exclusive,
+ const bool do_validate) override;
+
+ Status GetForUpdate(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* pinnable_val, bool exclusive,
+ const bool do_validate) override;
+
+ Status GetForUpdate(const ReadOptions& options, const Slice& key,
+ std::string* value, bool exclusive,
+ const bool do_validate) override {
+ return GetForUpdate(options, db_->DefaultColumnFamily(), key, value,
+ exclusive, do_validate);
+ }
+
+ using Transaction::MultiGet;
+ std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ std::vector<Status> MultiGet(const ReadOptions& options,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override {
+ return MultiGet(options, std::vector<ColumnFamilyHandle*>(
+ keys.size(), db_->DefaultColumnFamily()),
+ keys, values);
+ }
+
+ void MultiGet(const ReadOptions& options, ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys, PinnableSlice* values,
+ Status* statuses, const bool sorted_input = false) override;
+
+ using Transaction::MultiGetForUpdate;
+ std::vector<Status> MultiGetForUpdate(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ std::vector<Status> MultiGetForUpdate(
+ const ReadOptions& options, const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override {
+ return MultiGetForUpdate(options,
+ std::vector<ColumnFamilyHandle*>(
+ keys.size(), db_->DefaultColumnFamily()),
+ keys, values);
+ }
+
+ Iterator* GetIterator(const ReadOptions& read_options) override;
+ Iterator* GetIterator(const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family) override;
+
+ Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value, const bool assume_tracked = false) override;
+ Status Put(const Slice& key, const Slice& value) override {
+ return Put(nullptr, key, value);
+ }
+
+ Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value,
+ const bool assume_tracked = false) override;
+ Status Put(const SliceParts& key, const SliceParts& value) override {
+ return Put(nullptr, key, value);
+ }
+
+ Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value, const bool assume_tracked = false) override;
+ Status Merge(const Slice& key, const Slice& value) override {
+ return Merge(nullptr, key, value);
+ }
+
+ Status Delete(ColumnFamilyHandle* column_family, const Slice& key,
+ const bool assume_tracked = false) override;
+ Status Delete(const Slice& key) override { return Delete(nullptr, key); }
+ Status Delete(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const bool assume_tracked = false) override;
+ Status Delete(const SliceParts& key) override { return Delete(nullptr, key); }
+
+ Status SingleDelete(ColumnFamilyHandle* column_family, const Slice& key,
+ const bool assume_tracked = false) override;
+ Status SingleDelete(const Slice& key) override {
+ return SingleDelete(nullptr, key);
+ }
+ Status SingleDelete(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const bool assume_tracked = false) override;
+ Status SingleDelete(const SliceParts& key) override {
+ return SingleDelete(nullptr, key);
+ }
+
+ Status PutUntracked(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ Status PutUntracked(const Slice& key, const Slice& value) override {
+ return PutUntracked(nullptr, key, value);
+ }
+
+ Status PutUntracked(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value) override;
+ Status PutUntracked(const SliceParts& key, const SliceParts& value) override {
+ return PutUntracked(nullptr, key, value);
+ }
+
+ Status MergeUntracked(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+ Status MergeUntracked(const Slice& key, const Slice& value) override {
+ return MergeUntracked(nullptr, key, value);
+ }
+
+ Status DeleteUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ Status DeleteUntracked(const Slice& key) override {
+ return DeleteUntracked(nullptr, key);
+ }
+ Status DeleteUntracked(ColumnFamilyHandle* column_family,
+ const SliceParts& key) override;
+ Status DeleteUntracked(const SliceParts& key) override {
+ return DeleteUntracked(nullptr, key);
+ }
+
+ Status SingleDeleteUntracked(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ Status SingleDeleteUntracked(const Slice& key) override {
+ return SingleDeleteUntracked(nullptr, key);
+ }
+
+ void PutLogData(const Slice& blob) override;
+
+ WriteBatchWithIndex* GetWriteBatch() override;
+
+ virtual void SetLockTimeout(int64_t /*timeout*/) override { /* Do nothing */
+ }
+
+ const Snapshot* GetSnapshot() const override {
+ return snapshot_ ? snapshot_.get() : nullptr;
+ }
+
+ virtual void SetSnapshot() override;
+ void SetSnapshotOnNextOperation(
+ std::shared_ptr<TransactionNotifier> notifier = nullptr) override;
+
+ void ClearSnapshot() override {
+ snapshot_.reset();
+ snapshot_needed_ = false;
+ snapshot_notifier_ = nullptr;
+ }
+
+ void DisableIndexing() override { indexing_enabled_ = false; }
+
+ void EnableIndexing() override { indexing_enabled_ = true; }
+
+ uint64_t GetElapsedTime() const override;
+
+ uint64_t GetNumPuts() const override;
+
+ uint64_t GetNumDeletes() const override;
+
+ uint64_t GetNumMerges() const override;
+
+ uint64_t GetNumKeys() const override;
+
+ void UndoGetForUpdate(ColumnFamilyHandle* column_family,
+ const Slice& key) override;
+ void UndoGetForUpdate(const Slice& key) override {
+ return UndoGetForUpdate(nullptr, key);
+ };
+
+ // Get list of keys in this transaction that must not have any conflicts
+ // with writes in other transactions.
+ const TransactionKeyMap& GetTrackedKeys() const { return tracked_keys_; }
+
+ WriteOptions* GetWriteOptions() override { return &write_options_; }
+
+ void SetWriteOptions(const WriteOptions& write_options) override {
+ write_options_ = write_options;
+ }
+
+ // Used for memory management for snapshot_
+ void ReleaseSnapshot(const Snapshot* snapshot, DB* db);
+
+ // iterates over the given batch and makes the appropriate inserts.
+ // used for rebuilding prepared transactions after recovery.
+ virtual Status RebuildFromWriteBatch(WriteBatch* src_batch) override;
+
+ WriteBatch* GetCommitTimeWriteBatch() override;
+
+ protected:
+ // Add a key to the list of tracked keys.
+ //
+ // seqno is the earliest seqno this key was involved with this transaction.
+ // readonly should be set to true if no data was written for this key
+ void TrackKey(uint32_t cfh_id, const std::string& key, SequenceNumber seqno,
+ bool readonly, bool exclusive);
+
+ // Helper function to add a key to the given TransactionKeyMap
+ static void TrackKey(TransactionKeyMap* key_map, uint32_t cfh_id,
+ const std::string& key, SequenceNumber seqno,
+ bool readonly, bool exclusive);
+
+ // Called when UndoGetForUpdate determines that this key can be unlocked.
+ virtual void UnlockGetForUpdate(ColumnFamilyHandle* column_family,
+ const Slice& key) = 0;
+
+ std::unique_ptr<TransactionKeyMap> GetTrackedKeysSinceSavePoint();
+
+ // Sets a snapshot if SetSnapshotOnNextOperation() has been called.
+ void SetSnapshotIfNeeded();
+
+ // Initialize write_batch_ for 2PC by inserting Noop.
+ inline void InitWriteBatch(bool clear = false) {
+ if (clear) {
+ write_batch_.Clear();
+ }
+ assert(write_batch_.GetDataSize() == WriteBatchInternal::kHeader);
+ WriteBatchInternal::InsertNoop(write_batch_.GetWriteBatch());
+ }
+
+ DB* db_;
+ DBImpl* dbimpl_;
+
+ WriteOptions write_options_;
+
+ const Comparator* cmp_;
+
+ // Stores that time the txn was constructed, in microseconds.
+ uint64_t start_time_;
+
+ // Stores the current snapshot that was set by SetSnapshot or null if
+ // no snapshot is currently set.
+ std::shared_ptr<const Snapshot> snapshot_;
+
+ // Count of various operations pending in this transaction
+ uint64_t num_puts_ = 0;
+ uint64_t num_deletes_ = 0;
+ uint64_t num_merges_ = 0;
+
+ struct SavePoint {
+ std::shared_ptr<const Snapshot> snapshot_;
+ bool snapshot_needed_ = false;
+ std::shared_ptr<TransactionNotifier> snapshot_notifier_;
+ uint64_t num_puts_ = 0;
+ uint64_t num_deletes_ = 0;
+ uint64_t num_merges_ = 0;
+
+ // Record all keys tracked since the last savepoint
+ TransactionKeyMap new_keys_;
+
+ SavePoint(std::shared_ptr<const Snapshot> snapshot, bool snapshot_needed,
+ std::shared_ptr<TransactionNotifier> snapshot_notifier,
+ uint64_t num_puts, uint64_t num_deletes, uint64_t num_merges)
+ : snapshot_(snapshot),
+ snapshot_needed_(snapshot_needed),
+ snapshot_notifier_(snapshot_notifier),
+ num_puts_(num_puts),
+ num_deletes_(num_deletes),
+ num_merges_(num_merges) {}
+
+ SavePoint() = default;
+ };
+
+ // Records writes pending in this transaction
+ WriteBatchWithIndex write_batch_;
+
+ // Map from column_family_id to map of keys that are involved in this
+ // transaction.
+ // For Pessimistic Transactions this is the list of locked keys.
+ // Optimistic Transactions will wait till commit time to do conflict checking.
+ TransactionKeyMap tracked_keys_;
+
+ // Stack of the Snapshot saved at each save point. Saved snapshots may be
+ // nullptr if there was no snapshot at the time SetSavePoint() was called.
+ std::unique_ptr<std::stack<TransactionBaseImpl::SavePoint,
+ autovector<TransactionBaseImpl::SavePoint>>>
+ save_points_;
+
+ private:
+ friend class WritePreparedTxn;
+ // Extra data to be persisted with the commit. Note this is only used when
+ // prepare phase is not skipped.
+ WriteBatch commit_time_batch_;
+
+ // If true, future Put/Merge/Deletes will be indexed in the
+ // WriteBatchWithIndex.
+ // If false, future Put/Merge/Deletes will be inserted directly into the
+ // underlying WriteBatch and not indexed in the WriteBatchWithIndex.
+ bool indexing_enabled_;
+
+ // SetSnapshotOnNextOperation() has been called and the snapshot has not yet
+ // been reset.
+ bool snapshot_needed_ = false;
+
+ // SetSnapshotOnNextOperation() has been called and the caller would like
+ // a notification through the TransactionNotifier interface
+ std::shared_ptr<TransactionNotifier> snapshot_notifier_ = nullptr;
+
+ Status TryLock(ColumnFamilyHandle* column_family, const SliceParts& key,
+ bool read_only, bool exclusive, const bool do_validate = true,
+ const bool assume_tracked = false);
+
+ WriteBatchBase* GetBatchForWrite();
+ void SetSnapshotInternal(const Snapshot* snapshot);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.cc b/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.cc
new file mode 100644
index 00000000..345c4be9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_db_mutex_impl.h"
+
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+
+#include "rocksdb/utilities/transaction_db_mutex.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TransactionDBMutexImpl : public TransactionDBMutex {
+ public:
+ TransactionDBMutexImpl() {}
+ ~TransactionDBMutexImpl() override {}
+
+ Status Lock() override;
+
+ Status TryLockFor(int64_t timeout_time) override;
+
+ void UnLock() override { mutex_.unlock(); }
+
+ friend class TransactionDBCondVarImpl;
+
+ private:
+ std::mutex mutex_;
+};
+
+class TransactionDBCondVarImpl : public TransactionDBCondVar {
+ public:
+ TransactionDBCondVarImpl() {}
+ ~TransactionDBCondVarImpl() override {}
+
+ Status Wait(std::shared_ptr<TransactionDBMutex> mutex) override;
+
+ Status WaitFor(std::shared_ptr<TransactionDBMutex> mutex,
+ int64_t timeout_time) override;
+
+ void Notify() override { cv_.notify_one(); }
+
+ void NotifyAll() override { cv_.notify_all(); }
+
+ private:
+ std::condition_variable cv_;
+};
+
+std::shared_ptr<TransactionDBMutex>
+TransactionDBMutexFactoryImpl::AllocateMutex() {
+ return std::shared_ptr<TransactionDBMutex>(new TransactionDBMutexImpl());
+}
+
+std::shared_ptr<TransactionDBCondVar>
+TransactionDBMutexFactoryImpl::AllocateCondVar() {
+ return std::shared_ptr<TransactionDBCondVar>(new TransactionDBCondVarImpl());
+}
+
+Status TransactionDBMutexImpl::Lock() {
+ mutex_.lock();
+ return Status::OK();
+}
+
+Status TransactionDBMutexImpl::TryLockFor(int64_t timeout_time) {
+ bool locked = true;
+
+ if (timeout_time == 0) {
+ locked = mutex_.try_lock();
+ } else {
+ // Previously, this code used a std::timed_mutex. However, this was changed
+ // due to known bugs in gcc versions < 4.9.
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
+ //
+ // Since this mutex isn't held for long and only a single mutex is ever
+ // held at a time, it is reasonable to ignore the lock timeout_time here
+ // and only check it when waiting on the condition_variable.
+ mutex_.lock();
+ }
+
+ if (!locked) {
+ // timeout acquiring mutex
+ return Status::TimedOut(Status::SubCode::kMutexTimeout);
+ }
+
+ return Status::OK();
+}
+
+Status TransactionDBCondVarImpl::Wait(
+ std::shared_ptr<TransactionDBMutex> mutex) {
+ auto mutex_impl = reinterpret_cast<TransactionDBMutexImpl*>(mutex.get());
+
+ std::unique_lock<std::mutex> lock(mutex_impl->mutex_, std::adopt_lock);
+ cv_.wait(lock);
+
+ // Make sure unique_lock doesn't unlock mutex when it destructs
+ lock.release();
+
+ return Status::OK();
+}
+
+Status TransactionDBCondVarImpl::WaitFor(
+ std::shared_ptr<TransactionDBMutex> mutex, int64_t timeout_time) {
+ Status s;
+
+ auto mutex_impl = reinterpret_cast<TransactionDBMutexImpl*>(mutex.get());
+ std::unique_lock<std::mutex> lock(mutex_impl->mutex_, std::adopt_lock);
+
+ if (timeout_time < 0) {
+ // If timeout is negative, do not use a timeout
+ cv_.wait(lock);
+ } else {
+ auto duration = std::chrono::microseconds(timeout_time);
+ auto cv_status = cv_.wait_for(lock, duration);
+
+ // Check if the wait stopped due to timing out.
+ if (cv_status == std::cv_status::timeout) {
+ s = Status::TimedOut(Status::SubCode::kMutexTimeout);
+ }
+ }
+
+ // Make sure unique_lock doesn't unlock mutex when it destructs
+ lock.release();
+
+ // CV was signaled, or we spuriously woke up (but didn't time out)
+ return s;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.h b/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.h
new file mode 100644
index 00000000..fbee9283
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_db_mutex_impl.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "rocksdb/utilities/transaction_db_mutex.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class TransactionDBMutex;
+class TransactionDBCondVar;
+
+// Default implementation of TransactionDBMutexFactory. May be overridden
+// by TransactionDBOptions.custom_mutex_factory.
+class TransactionDBMutexFactoryImpl : public TransactionDBMutexFactory {
+ public:
+ std::shared_ptr<TransactionDBMutex> AllocateMutex() override;
+ std::shared_ptr<TransactionDBCondVar> AllocateCondVar() override;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.cc b/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.cc
new file mode 100644
index 00000000..82b61403
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.cc
@@ -0,0 +1,745 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_lock_mgr.h"
+
+#include <cinttypes>
+
+#include <algorithm>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "monitoring/perf_context_imp.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/utilities/transaction_db_mutex.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/hash.h"
+#include "util/thread_local.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct LockInfo {
+ bool exclusive;
+ autovector<TransactionID> txn_ids;
+
+ // Transaction locks are not valid after this time in us
+ uint64_t expiration_time;
+
+ LockInfo(TransactionID id, uint64_t time, bool ex)
+ : exclusive(ex), expiration_time(time) {
+ txn_ids.push_back(id);
+ }
+ LockInfo(const LockInfo& lock_info)
+ : exclusive(lock_info.exclusive),
+ txn_ids(lock_info.txn_ids),
+ expiration_time(lock_info.expiration_time) {}
+};
+
+struct LockMapStripe {
+ explicit LockMapStripe(std::shared_ptr<TransactionDBMutexFactory> factory) {
+ stripe_mutex = factory->AllocateMutex();
+ stripe_cv = factory->AllocateCondVar();
+ assert(stripe_mutex);
+ assert(stripe_cv);
+ }
+
+ // Mutex must be held before modifying keys map
+ std::shared_ptr<TransactionDBMutex> stripe_mutex;
+
+ // Condition Variable per stripe for waiting on a lock
+ std::shared_ptr<TransactionDBCondVar> stripe_cv;
+
+ // Locked keys mapped to the info about the transactions that locked them.
+ // TODO(agiardullo): Explore performance of other data structures.
+ std::unordered_map<std::string, LockInfo> keys;
+};
+
+// Map of #num_stripes LockMapStripes
+struct LockMap {
+ explicit LockMap(size_t num_stripes,
+ std::shared_ptr<TransactionDBMutexFactory> factory)
+ : num_stripes_(num_stripes) {
+ lock_map_stripes_.reserve(num_stripes);
+ for (size_t i = 0; i < num_stripes; i++) {
+ LockMapStripe* stripe = new LockMapStripe(factory);
+ lock_map_stripes_.push_back(stripe);
+ }
+ }
+
+ ~LockMap() {
+ for (auto stripe : lock_map_stripes_) {
+ delete stripe;
+ }
+ }
+
+ // Number of sepearate LockMapStripes to create, each with their own Mutex
+ const size_t num_stripes_;
+
+ // Count of keys that are currently locked in this column family.
+ // (Only maintained if TransactionLockMgr::max_num_locks_ is positive.)
+ std::atomic<int64_t> lock_cnt{0};
+
+ std::vector<LockMapStripe*> lock_map_stripes_;
+
+ size_t GetStripe(const std::string& key) const;
+};
+
+void DeadlockInfoBuffer::AddNewPath(DeadlockPath path) {
+ std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
+
+ if (paths_buffer_.empty()) {
+ return;
+ }
+
+ paths_buffer_[buffer_idx_] = std::move(path);
+ buffer_idx_ = (buffer_idx_ + 1) % paths_buffer_.size();
+}
+
+void DeadlockInfoBuffer::Resize(uint32_t target_size) {
+ std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
+
+ paths_buffer_ = Normalize();
+
+ // Drop the deadlocks that will no longer be needed ater the normalize
+ if (target_size < paths_buffer_.size()) {
+ paths_buffer_.erase(
+ paths_buffer_.begin(),
+ paths_buffer_.begin() + (paths_buffer_.size() - target_size));
+ buffer_idx_ = 0;
+ }
+ // Resize the buffer to the target size and restore the buffer's idx
+ else {
+ auto prev_size = paths_buffer_.size();
+ paths_buffer_.resize(target_size);
+ buffer_idx_ = (uint32_t)prev_size;
+ }
+}
+
+std::vector<DeadlockPath> DeadlockInfoBuffer::Normalize() {
+ auto working = paths_buffer_;
+
+ if (working.empty()) {
+ return working;
+ }
+
+ // Next write occurs at a nonexistent path's slot
+ if (paths_buffer_[buffer_idx_].empty()) {
+ working.resize(buffer_idx_);
+ } else {
+ std::rotate(working.begin(), working.begin() + buffer_idx_, working.end());
+ }
+
+ return working;
+}
+
+std::vector<DeadlockPath> DeadlockInfoBuffer::PrepareBuffer() {
+ std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
+
+ // Reversing the normalized vector returns the latest deadlocks first
+ auto working = Normalize();
+ std::reverse(working.begin(), working.end());
+
+ return working;
+}
+
+namespace {
+void UnrefLockMapsCache(void* ptr) {
+ // Called when a thread exits or a ThreadLocalPtr gets destroyed.
+ auto lock_maps_cache =
+ static_cast<std::unordered_map<uint32_t, std::shared_ptr<LockMap>>*>(ptr);
+ delete lock_maps_cache;
+}
+} // anonymous namespace
+
+TransactionLockMgr::TransactionLockMgr(
+ TransactionDB* txn_db, size_t default_num_stripes, int64_t max_num_locks,
+ uint32_t max_num_deadlocks,
+ std::shared_ptr<TransactionDBMutexFactory> mutex_factory)
+ : txn_db_impl_(nullptr),
+ default_num_stripes_(default_num_stripes),
+ max_num_locks_(max_num_locks),
+ lock_maps_cache_(new ThreadLocalPtr(&UnrefLockMapsCache)),
+ dlock_buffer_(max_num_deadlocks),
+ mutex_factory_(mutex_factory) {
+ assert(txn_db);
+ txn_db_impl_ =
+ static_cast_with_check<PessimisticTransactionDB, TransactionDB>(txn_db);
+}
+
+TransactionLockMgr::~TransactionLockMgr() {}
+
+size_t LockMap::GetStripe(const std::string& key) const {
+ assert(num_stripes_ > 0);
+ return fastrange64(GetSliceNPHash64(key), num_stripes_);
+}
+
+void TransactionLockMgr::AddColumnFamily(uint32_t column_family_id) {
+ InstrumentedMutexLock l(&lock_map_mutex_);
+
+ if (lock_maps_.find(column_family_id) == lock_maps_.end()) {
+ lock_maps_.emplace(column_family_id,
+ std::make_shared<LockMap>(default_num_stripes_, mutex_factory_));
+ } else {
+ // column_family already exists in lock map
+ assert(false);
+ }
+}
+
+void TransactionLockMgr::RemoveColumnFamily(uint32_t column_family_id) {
+ // Remove lock_map for this column family. Since the lock map is stored
+ // as a shared ptr, concurrent transactions can still keep using it
+ // until they release their references to it.
+ {
+ InstrumentedMutexLock l(&lock_map_mutex_);
+
+ auto lock_maps_iter = lock_maps_.find(column_family_id);
+ assert(lock_maps_iter != lock_maps_.end());
+
+ lock_maps_.erase(lock_maps_iter);
+ } // lock_map_mutex_
+
+ // Clear all thread-local caches
+ autovector<void*> local_caches;
+ lock_maps_cache_->Scrape(&local_caches, nullptr);
+ for (auto cache : local_caches) {
+ delete static_cast<LockMaps*>(cache);
+ }
+}
+
+// Look up the LockMap std::shared_ptr for a given column_family_id.
+// Note: The LockMap is only valid as long as the caller is still holding on
+// to the returned std::shared_ptr.
+std::shared_ptr<LockMap> TransactionLockMgr::GetLockMap(
+ uint32_t column_family_id) {
+ // First check thread-local cache
+ if (lock_maps_cache_->Get() == nullptr) {
+ lock_maps_cache_->Reset(new LockMaps());
+ }
+
+ auto lock_maps_cache = static_cast<LockMaps*>(lock_maps_cache_->Get());
+
+ auto lock_map_iter = lock_maps_cache->find(column_family_id);
+ if (lock_map_iter != lock_maps_cache->end()) {
+ // Found lock map for this column family.
+ return lock_map_iter->second;
+ }
+
+ // Not found in local cache, grab mutex and check shared LockMaps
+ InstrumentedMutexLock l(&lock_map_mutex_);
+
+ lock_map_iter = lock_maps_.find(column_family_id);
+ if (lock_map_iter == lock_maps_.end()) {
+ return std::shared_ptr<LockMap>(nullptr);
+ } else {
+ // Found lock map. Store in thread-local cache and return.
+ std::shared_ptr<LockMap>& lock_map = lock_map_iter->second;
+ lock_maps_cache->insert({column_family_id, lock_map});
+
+ return lock_map;
+ }
+}
+
+// Returns true if this lock has expired and can be acquired by another
+// transaction.
+// If false, sets *expire_time to the expiration time of the lock according
+// to Env->GetMicros() or 0 if no expiration.
+bool TransactionLockMgr::IsLockExpired(TransactionID txn_id,
+ const LockInfo& lock_info, Env* env,
+ uint64_t* expire_time) {
+ auto now = env->NowMicros();
+
+ bool expired =
+ (lock_info.expiration_time > 0 && lock_info.expiration_time <= now);
+
+ if (!expired && lock_info.expiration_time > 0) {
+ // return how many microseconds until lock will be expired
+ *expire_time = lock_info.expiration_time;
+ } else {
+ for (auto id : lock_info.txn_ids) {
+ if (txn_id == id) {
+ continue;
+ }
+
+ bool success = txn_db_impl_->TryStealingExpiredTransactionLocks(id);
+ if (!success) {
+ expired = false;
+ break;
+ }
+ *expire_time = 0;
+ }
+ }
+
+ return expired;
+}
+
+Status TransactionLockMgr::TryLock(PessimisticTransaction* txn,
+ uint32_t column_family_id,
+ const std::string& key, Env* env,
+ bool exclusive) {
+ // Lookup lock map for this column family id
+ std::shared_ptr<LockMap> lock_map_ptr = GetLockMap(column_family_id);
+ LockMap* lock_map = lock_map_ptr.get();
+ if (lock_map == nullptr) {
+ char msg[255];
+ snprintf(msg, sizeof(msg), "Column family id not found: %" PRIu32,
+ column_family_id);
+
+ return Status::InvalidArgument(msg);
+ }
+
+ // Need to lock the mutex for the stripe that this key hashes to
+ size_t stripe_num = lock_map->GetStripe(key);
+ assert(lock_map->lock_map_stripes_.size() > stripe_num);
+ LockMapStripe* stripe = lock_map->lock_map_stripes_.at(stripe_num);
+
+ LockInfo lock_info(txn->GetID(), txn->GetExpirationTime(), exclusive);
+ int64_t timeout = txn->GetLockTimeout();
+
+ return AcquireWithTimeout(txn, lock_map, stripe, column_family_id, key, env,
+ timeout, std::move(lock_info));
+}
+
+// Helper function for TryLock().
+Status TransactionLockMgr::AcquireWithTimeout(
+ PessimisticTransaction* txn, LockMap* lock_map, LockMapStripe* stripe,
+ uint32_t column_family_id, const std::string& key, Env* env,
+ int64_t timeout, LockInfo&& lock_info) {
+ Status result;
+ uint64_t end_time = 0;
+
+ if (timeout > 0) {
+ uint64_t start_time = env->NowMicros();
+ end_time = start_time + timeout;
+ }
+
+ if (timeout < 0) {
+ // If timeout is negative, we wait indefinitely to acquire the lock
+ result = stripe->stripe_mutex->Lock();
+ } else {
+ result = stripe->stripe_mutex->TryLockFor(timeout);
+ }
+
+ if (!result.ok()) {
+ // failed to acquire mutex
+ return result;
+ }
+
+ // Acquire lock if we are able to
+ uint64_t expire_time_hint = 0;
+ autovector<TransactionID> wait_ids;
+ result = AcquireLocked(lock_map, stripe, key, env, std::move(lock_info),
+ &expire_time_hint, &wait_ids);
+
+ if (!result.ok() && timeout != 0) {
+ PERF_TIMER_GUARD(key_lock_wait_time);
+ PERF_COUNTER_ADD(key_lock_wait_count, 1);
+ // If we weren't able to acquire the lock, we will keep retrying as long
+ // as the timeout allows.
+ bool timed_out = false;
+ do {
+ // Decide how long to wait
+ int64_t cv_end_time = -1;
+
+ // Check if held lock's expiration time is sooner than our timeout
+ if (expire_time_hint > 0 &&
+ (timeout < 0 || (timeout > 0 && expire_time_hint < end_time))) {
+ // expiration time is sooner than our timeout
+ cv_end_time = expire_time_hint;
+ } else if (timeout >= 0) {
+ cv_end_time = end_time;
+ }
+
+ assert(result.IsBusy() || wait_ids.size() != 0);
+
+ // We are dependent on a transaction to finish, so perform deadlock
+ // detection.
+ if (wait_ids.size() != 0) {
+ if (txn->IsDeadlockDetect()) {
+ if (IncrementWaiters(txn, wait_ids, key, column_family_id,
+ lock_info.exclusive, env)) {
+ result = Status::Busy(Status::SubCode::kDeadlock);
+ stripe->stripe_mutex->UnLock();
+ return result;
+ }
+ }
+ txn->SetWaitingTxn(wait_ids, column_family_id, &key);
+ }
+
+ TEST_SYNC_POINT("TransactionLockMgr::AcquireWithTimeout:WaitingTxn");
+ if (cv_end_time < 0) {
+ // Wait indefinitely
+ result = stripe->stripe_cv->Wait(stripe->stripe_mutex);
+ } else {
+ uint64_t now = env->NowMicros();
+ if (static_cast<uint64_t>(cv_end_time) > now) {
+ result = stripe->stripe_cv->WaitFor(stripe->stripe_mutex,
+ cv_end_time - now);
+ }
+ }
+
+ if (wait_ids.size() != 0) {
+ txn->ClearWaitingTxn();
+ if (txn->IsDeadlockDetect()) {
+ DecrementWaiters(txn, wait_ids);
+ }
+ }
+
+ if (result.IsTimedOut()) {
+ timed_out = true;
+ // Even though we timed out, we will still make one more attempt to
+ // acquire lock below (it is possible the lock expired and we
+ // were never signaled).
+ }
+
+ if (result.ok() || result.IsTimedOut()) {
+ result = AcquireLocked(lock_map, stripe, key, env, std::move(lock_info),
+ &expire_time_hint, &wait_ids);
+ }
+ } while (!result.ok() && !timed_out);
+ }
+
+ stripe->stripe_mutex->UnLock();
+
+ return result;
+}
+
+void TransactionLockMgr::DecrementWaiters(
+ const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids) {
+ std::lock_guard<std::mutex> lock(wait_txn_map_mutex_);
+ DecrementWaitersImpl(txn, wait_ids);
+}
+
+void TransactionLockMgr::DecrementWaitersImpl(
+ const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids) {
+ auto id = txn->GetID();
+ assert(wait_txn_map_.Contains(id));
+ wait_txn_map_.Delete(id);
+
+ for (auto wait_id : wait_ids) {
+ rev_wait_txn_map_.Get(wait_id)--;
+ if (rev_wait_txn_map_.Get(wait_id) == 0) {
+ rev_wait_txn_map_.Delete(wait_id);
+ }
+ }
+}
+
+bool TransactionLockMgr::IncrementWaiters(
+ const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids, const std::string& key,
+ const uint32_t& cf_id, const bool& exclusive, Env* const env) {
+ auto id = txn->GetID();
+ std::vector<int> queue_parents(static_cast<size_t>(txn->GetDeadlockDetectDepth()));
+ std::vector<TransactionID> queue_values(static_cast<size_t>(txn->GetDeadlockDetectDepth()));
+ std::lock_guard<std::mutex> lock(wait_txn_map_mutex_);
+ assert(!wait_txn_map_.Contains(id));
+
+ wait_txn_map_.Insert(id, {wait_ids, cf_id, exclusive, key});
+
+ for (auto wait_id : wait_ids) {
+ if (rev_wait_txn_map_.Contains(wait_id)) {
+ rev_wait_txn_map_.Get(wait_id)++;
+ } else {
+ rev_wait_txn_map_.Insert(wait_id, 1);
+ }
+ }
+
+ // No deadlock if nobody is waiting on self.
+ if (!rev_wait_txn_map_.Contains(id)) {
+ return false;
+ }
+
+ const auto* next_ids = &wait_ids;
+ int parent = -1;
+ int64_t deadlock_time = 0;
+ for (int tail = 0, head = 0; head < txn->GetDeadlockDetectDepth(); head++) {
+ int i = 0;
+ if (next_ids) {
+ for (; i < static_cast<int>(next_ids->size()) &&
+ tail + i < txn->GetDeadlockDetectDepth();
+ i++) {
+ queue_values[tail + i] = (*next_ids)[i];
+ queue_parents[tail + i] = parent;
+ }
+ tail += i;
+ }
+
+ // No more items in the list, meaning no deadlock.
+ if (tail == head) {
+ return false;
+ }
+
+ auto next = queue_values[head];
+ if (next == id) {
+ std::vector<DeadlockInfo> path;
+ while (head != -1) {
+ assert(wait_txn_map_.Contains(queue_values[head]));
+
+ auto extracted_info = wait_txn_map_.Get(queue_values[head]);
+ path.push_back({queue_values[head], extracted_info.m_cf_id,
+ extracted_info.m_exclusive,
+ extracted_info.m_waiting_key});
+ head = queue_parents[head];
+ }
+ env->GetCurrentTime(&deadlock_time);
+ std::reverse(path.begin(), path.end());
+ dlock_buffer_.AddNewPath(DeadlockPath(path, deadlock_time));
+ deadlock_time = 0;
+ DecrementWaitersImpl(txn, wait_ids);
+ return true;
+ } else if (!wait_txn_map_.Contains(next)) {
+ next_ids = nullptr;
+ continue;
+ } else {
+ parent = head;
+ next_ids = &(wait_txn_map_.Get(next).m_neighbors);
+ }
+ }
+
+ // Wait cycle too big, just assume deadlock.
+ env->GetCurrentTime(&deadlock_time);
+ dlock_buffer_.AddNewPath(DeadlockPath(deadlock_time, true));
+ DecrementWaitersImpl(txn, wait_ids);
+ return true;
+}
+
+// Try to lock this key after we have acquired the mutex.
+// Sets *expire_time to the expiration time in microseconds
+// or 0 if no expiration.
+// REQUIRED: Stripe mutex must be held.
+Status TransactionLockMgr::AcquireLocked(LockMap* lock_map,
+ LockMapStripe* stripe,
+ const std::string& key, Env* env,
+ LockInfo&& txn_lock_info,
+ uint64_t* expire_time,
+ autovector<TransactionID>* txn_ids) {
+ assert(txn_lock_info.txn_ids.size() == 1);
+
+ Status result;
+ // Check if this key is already locked
+ auto stripe_iter = stripe->keys.find(key);
+ if (stripe_iter != stripe->keys.end()) {
+ // Lock already held
+ LockInfo& lock_info = stripe_iter->second;
+ assert(lock_info.txn_ids.size() == 1 || !lock_info.exclusive);
+
+ if (lock_info.exclusive || txn_lock_info.exclusive) {
+ if (lock_info.txn_ids.size() == 1 &&
+ lock_info.txn_ids[0] == txn_lock_info.txn_ids[0]) {
+ // The list contains one txn and we're it, so just take it.
+ lock_info.exclusive = txn_lock_info.exclusive;
+ lock_info.expiration_time = txn_lock_info.expiration_time;
+ } else {
+ // Check if it's expired. Skips over txn_lock_info.txn_ids[0] in case
+ // it's there for a shared lock with multiple holders which was not
+ // caught in the first case.
+ if (IsLockExpired(txn_lock_info.txn_ids[0], lock_info, env,
+ expire_time)) {
+ // lock is expired, can steal it
+ lock_info.txn_ids = txn_lock_info.txn_ids;
+ lock_info.exclusive = txn_lock_info.exclusive;
+ lock_info.expiration_time = txn_lock_info.expiration_time;
+ // lock_cnt does not change
+ } else {
+ result = Status::TimedOut(Status::SubCode::kLockTimeout);
+ *txn_ids = lock_info.txn_ids;
+ }
+ }
+ } else {
+ // We are requesting shared access to a shared lock, so just grant it.
+ lock_info.txn_ids.push_back(txn_lock_info.txn_ids[0]);
+ // Using std::max means that expiration time never goes down even when
+ // a transaction is removed from the list. The correct solution would be
+ // to track expiry for every transaction, but this would also work for
+ // now.
+ lock_info.expiration_time =
+ std::max(lock_info.expiration_time, txn_lock_info.expiration_time);
+ }
+ } else { // Lock not held.
+ // Check lock limit
+ if (max_num_locks_ > 0 &&
+ lock_map->lock_cnt.load(std::memory_order_acquire) >= max_num_locks_) {
+ result = Status::Busy(Status::SubCode::kLockLimit);
+ } else {
+ // acquire lock
+ stripe->keys.emplace(key, std::move(txn_lock_info));
+
+ // Maintain lock count if there is a limit on the number of locks
+ if (max_num_locks_) {
+ lock_map->lock_cnt++;
+ }
+ }
+ }
+
+ return result;
+}
+
+void TransactionLockMgr::UnLockKey(const PessimisticTransaction* txn,
+ const std::string& key,
+ LockMapStripe* stripe, LockMap* lock_map,
+ Env* env) {
+#ifdef NDEBUG
+ (void)env;
+#endif
+ TransactionID txn_id = txn->GetID();
+
+ auto stripe_iter = stripe->keys.find(key);
+ if (stripe_iter != stripe->keys.end()) {
+ auto& txns = stripe_iter->second.txn_ids;
+ auto txn_it = std::find(txns.begin(), txns.end(), txn_id);
+ // Found the key we locked. unlock it.
+ if (txn_it != txns.end()) {
+ if (txns.size() == 1) {
+ stripe->keys.erase(stripe_iter);
+ } else {
+ auto last_it = txns.end() - 1;
+ if (txn_it != last_it) {
+ *txn_it = *last_it;
+ }
+ txns.pop_back();
+ }
+
+ if (max_num_locks_ > 0) {
+ // Maintain lock count if there is a limit on the number of locks.
+ assert(lock_map->lock_cnt.load(std::memory_order_relaxed) > 0);
+ lock_map->lock_cnt--;
+ }
+ }
+ } else {
+ // This key is either not locked or locked by someone else. This should
+ // only happen if the unlocking transaction has expired.
+ assert(txn->GetExpirationTime() > 0 &&
+ txn->GetExpirationTime() < env->NowMicros());
+ }
+}
+
+void TransactionLockMgr::UnLock(PessimisticTransaction* txn,
+ uint32_t column_family_id,
+ const std::string& key, Env* env) {
+ std::shared_ptr<LockMap> lock_map_ptr = GetLockMap(column_family_id);
+ LockMap* lock_map = lock_map_ptr.get();
+ if (lock_map == nullptr) {
+ // Column Family must have been dropped.
+ return;
+ }
+
+ // Lock the mutex for the stripe that this key hashes to
+ size_t stripe_num = lock_map->GetStripe(key);
+ assert(lock_map->lock_map_stripes_.size() > stripe_num);
+ LockMapStripe* stripe = lock_map->lock_map_stripes_.at(stripe_num);
+
+ stripe->stripe_mutex->Lock();
+ UnLockKey(txn, key, stripe, lock_map, env);
+ stripe->stripe_mutex->UnLock();
+
+ // Signal waiting threads to retry locking
+ stripe->stripe_cv->NotifyAll();
+}
+
+void TransactionLockMgr::UnLock(const PessimisticTransaction* txn,
+ const TransactionKeyMap* key_map, Env* env) {
+ for (auto& key_map_iter : *key_map) {
+ uint32_t column_family_id = key_map_iter.first;
+ auto& keys = key_map_iter.second;
+
+ std::shared_ptr<LockMap> lock_map_ptr = GetLockMap(column_family_id);
+ LockMap* lock_map = lock_map_ptr.get();
+
+ if (lock_map == nullptr) {
+ // Column Family must have been dropped.
+ return;
+ }
+
+ // Bucket keys by lock_map_ stripe
+ std::unordered_map<size_t, std::vector<const std::string*>> keys_by_stripe(
+ std::max(keys.size(), lock_map->num_stripes_));
+
+ for (auto& key_iter : keys) {
+ const std::string& key = key_iter.first;
+
+ size_t stripe_num = lock_map->GetStripe(key);
+ keys_by_stripe[stripe_num].push_back(&key);
+ }
+
+ // For each stripe, grab the stripe mutex and unlock all keys in this stripe
+ for (auto& stripe_iter : keys_by_stripe) {
+ size_t stripe_num = stripe_iter.first;
+ auto& stripe_keys = stripe_iter.second;
+
+ assert(lock_map->lock_map_stripes_.size() > stripe_num);
+ LockMapStripe* stripe = lock_map->lock_map_stripes_.at(stripe_num);
+
+ stripe->stripe_mutex->Lock();
+
+ for (const std::string* key : stripe_keys) {
+ UnLockKey(txn, *key, stripe, lock_map, env);
+ }
+
+ stripe->stripe_mutex->UnLock();
+
+ // Signal waiting threads to retry locking
+ stripe->stripe_cv->NotifyAll();
+ }
+ }
+}
+
+TransactionLockMgr::LockStatusData TransactionLockMgr::GetLockStatusData() {
+ LockStatusData data;
+ // Lock order here is important. The correct order is lock_map_mutex_, then
+ // for every column family ID in ascending order lock every stripe in
+ // ascending order.
+ InstrumentedMutexLock l(&lock_map_mutex_);
+
+ std::vector<uint32_t> cf_ids;
+ for (const auto& map : lock_maps_) {
+ cf_ids.push_back(map.first);
+ }
+ std::sort(cf_ids.begin(), cf_ids.end());
+
+ for (auto i : cf_ids) {
+ const auto& stripes = lock_maps_[i]->lock_map_stripes_;
+ // Iterate and lock all stripes in ascending order.
+ for (const auto& j : stripes) {
+ j->stripe_mutex->Lock();
+ for (const auto& it : j->keys) {
+ struct KeyLockInfo info;
+ info.exclusive = it.second.exclusive;
+ info.key = it.first;
+ for (const auto& id : it.second.txn_ids) {
+ info.ids.push_back(id);
+ }
+ data.insert({i, info});
+ }
+ }
+ }
+
+ // Unlock everything. Unlocking order is not important.
+ for (auto i : cf_ids) {
+ const auto& stripes = lock_maps_[i]->lock_map_stripes_;
+ for (const auto& j : stripes) {
+ j->stripe_mutex->UnLock();
+ }
+ }
+
+ return data;
+}
+std::vector<DeadlockPath> TransactionLockMgr::GetDeadlockInfoBuffer() {
+ return dlock_buffer_.PrepareBuffer();
+}
+
+void TransactionLockMgr::Resize(uint32_t target_size) {
+ dlock_buffer_.Resize(target_size);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.h b/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.h
new file mode 100644
index 00000000..b4fd8592
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_lock_mgr.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <chrono>
+#include <string>
+#include <unordered_map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "monitoring/instrumented_mutex.h"
+#include "rocksdb/utilities/transaction.h"
+#include "util/autovector.h"
+#include "util/hash_map.h"
+#include "util/thread_local.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class ColumnFamilyHandle;
+struct LockInfo;
+struct LockMap;
+struct LockMapStripe;
+
+struct DeadlockInfoBuffer {
+ private:
+ std::vector<DeadlockPath> paths_buffer_;
+ uint32_t buffer_idx_;
+ std::mutex paths_buffer_mutex_;
+ std::vector<DeadlockPath> Normalize();
+
+ public:
+ explicit DeadlockInfoBuffer(uint32_t n_latest_dlocks)
+ : paths_buffer_(n_latest_dlocks), buffer_idx_(0) {}
+ void AddNewPath(DeadlockPath path);
+ void Resize(uint32_t target_size);
+ std::vector<DeadlockPath> PrepareBuffer();
+};
+
+struct TrackedTrxInfo {
+ autovector<TransactionID> m_neighbors;
+ uint32_t m_cf_id;
+ bool m_exclusive;
+ std::string m_waiting_key;
+};
+
+class Slice;
+class PessimisticTransactionDB;
+
+class TransactionLockMgr {
+ public:
+ TransactionLockMgr(TransactionDB* txn_db, size_t default_num_stripes,
+ int64_t max_num_locks, uint32_t max_num_deadlocks,
+ std::shared_ptr<TransactionDBMutexFactory> factory);
+ // No copying allowed
+ TransactionLockMgr(const TransactionLockMgr&) = delete;
+ void operator=(const TransactionLockMgr&) = delete;
+
+ ~TransactionLockMgr();
+
+ // Creates a new LockMap for this column family. Caller should guarantee
+ // that this column family does not already exist.
+ void AddColumnFamily(uint32_t column_family_id);
+
+ // Deletes the LockMap for this column family. Caller should guarantee that
+ // this column family is no longer in use.
+ void RemoveColumnFamily(uint32_t column_family_id);
+
+ // Attempt to lock key. If OK status is returned, the caller is responsible
+ // for calling UnLock() on this key.
+ Status TryLock(PessimisticTransaction* txn, uint32_t column_family_id,
+ const std::string& key, Env* env, bool exclusive);
+
+ // Unlock a key locked by TryLock(). txn must be the same Transaction that
+ // locked this key.
+ void UnLock(const PessimisticTransaction* txn, const TransactionKeyMap* keys,
+ Env* env);
+ void UnLock(PessimisticTransaction* txn, uint32_t column_family_id,
+ const std::string& key, Env* env);
+
+ using LockStatusData = std::unordered_multimap<uint32_t, KeyLockInfo>;
+ LockStatusData GetLockStatusData();
+ std::vector<DeadlockPath> GetDeadlockInfoBuffer();
+ void Resize(uint32_t);
+
+ private:
+ PessimisticTransactionDB* txn_db_impl_;
+
+ // Default number of lock map stripes per column family
+ const size_t default_num_stripes_;
+
+ // Limit on number of keys locked per column family
+ const int64_t max_num_locks_;
+
+ // The following lock order must be satisfied in order to avoid deadlocking
+ // ourselves.
+ // - lock_map_mutex_
+ // - stripe mutexes in ascending cf id, ascending stripe order
+ // - wait_txn_map_mutex_
+ //
+ // Must be held when accessing/modifying lock_maps_.
+ InstrumentedMutex lock_map_mutex_;
+
+ // Map of ColumnFamilyId to locked key info
+ using LockMaps = std::unordered_map<uint32_t, std::shared_ptr<LockMap>>;
+ LockMaps lock_maps_;
+
+ // Thread-local cache of entries in lock_maps_. This is an optimization
+ // to avoid acquiring a mutex in order to look up a LockMap
+ std::unique_ptr<ThreadLocalPtr> lock_maps_cache_;
+
+ // Must be held when modifying wait_txn_map_ and rev_wait_txn_map_.
+ std::mutex wait_txn_map_mutex_;
+
+ // Maps from waitee -> number of waiters.
+ HashMap<TransactionID, int> rev_wait_txn_map_;
+ // Maps from waiter -> waitee.
+ HashMap<TransactionID, TrackedTrxInfo> wait_txn_map_;
+ DeadlockInfoBuffer dlock_buffer_;
+
+ // Used to allocate mutexes/condvars to use when locking keys
+ std::shared_ptr<TransactionDBMutexFactory> mutex_factory_;
+
+ bool IsLockExpired(TransactionID txn_id, const LockInfo& lock_info, Env* env,
+ uint64_t* wait_time);
+
+ std::shared_ptr<LockMap> GetLockMap(uint32_t column_family_id);
+
+ Status AcquireWithTimeout(PessimisticTransaction* txn, LockMap* lock_map,
+ LockMapStripe* stripe, uint32_t column_family_id,
+ const std::string& key, Env* env, int64_t timeout,
+ LockInfo&& lock_info);
+
+ Status AcquireLocked(LockMap* lock_map, LockMapStripe* stripe,
+ const std::string& key, Env* env,
+ LockInfo&& lock_info, uint64_t* wait_time,
+ autovector<TransactionID>* txn_ids);
+
+ void UnLockKey(const PessimisticTransaction* txn, const std::string& key,
+ LockMapStripe* stripe, LockMap* lock_map, Env* env);
+
+ bool IncrementWaiters(const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids,
+ const std::string& key, const uint32_t& cf_id,
+ const bool& exclusive, Env* const env);
+ void DecrementWaiters(const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids);
+ void DecrementWaitersImpl(const PessimisticTransaction* txn,
+ const autovector<TransactionID>& wait_ids);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.cc b/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.cc
new file mode 100644
index 00000000..bdc2609f
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.cc
@@ -0,0 +1,6224 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_test.h"
+
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <thread>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/perf_context.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "table/mock_table.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "test_util/transaction_test_util.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/string_append/stringappend.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+
+#include "port/port.h"
+
+using std::string;
+
+namespace ROCKSDB_NAMESPACE {
+
+INSTANTIATE_TEST_CASE_P(
+ DBAsBaseDB, TransactionTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_COMMITTED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_COMMITTED, kOrderedWrite),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite),
+ std::make_tuple(false, false, WRITE_UNPREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_UNPREPARED, kOrderedWrite)));
+INSTANTIATE_TEST_CASE_P(
+ DBAsBaseDB, TransactionStressTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_COMMITTED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_COMMITTED, kOrderedWrite),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite),
+ std::make_tuple(false, false, WRITE_UNPREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_UNPREPARED, kOrderedWrite)));
+INSTANTIATE_TEST_CASE_P(
+ StackableDBAsBaseDB, TransactionTest,
+ ::testing::Values(
+ std::make_tuple(true, true, WRITE_COMMITTED, kOrderedWrite),
+ std::make_tuple(true, true, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(true, true, WRITE_UNPREPARED, kOrderedWrite)));
+
+// MySQLStyleTransactionTest takes far too long for valgrind to run.
+#ifndef ROCKSDB_VALGRIND_RUN
+INSTANTIATE_TEST_CASE_P(
+ MySQLStyleTransactionTest, MySQLStyleTransactionTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_COMMITTED, kOrderedWrite, false),
+ std::make_tuple(false, true, WRITE_COMMITTED, kOrderedWrite, false),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, false),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, true),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, false),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, true),
+ std::make_tuple(false, false, WRITE_UNPREPARED, kOrderedWrite, false),
+ std::make_tuple(false, false, WRITE_UNPREPARED, kOrderedWrite, true),
+ std::make_tuple(false, true, WRITE_UNPREPARED, kOrderedWrite, false),
+ std::make_tuple(false, true, WRITE_UNPREPARED, kOrderedWrite, true),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, false),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, true)));
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(TransactionTest, DoubleEmptyWrite) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+
+ WriteBatch batch;
+
+ ASSERT_OK(db->Write(write_options, &batch));
+ ASSERT_OK(db->Write(write_options, &batch));
+
+ // Also test committing empty transactions in 2PC
+ TransactionOptions txn_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Prepare());
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+
+ // Also test that it works during recovery
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid2"));
+ txn0->Put(Slice("foo0"), Slice("bar0a"));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete());
+ assert(db != nullptr);
+ txn0 = db->GetTransactionByName("xid2");
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+}
+
+TEST_P(TransactionTest, SuccessTest) {
+ ASSERT_OK(db->ResetStats());
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+
+ ASSERT_OK(db->Put(write_options, Slice("foo"), Slice("bar")));
+ ASSERT_OK(db->Put(write_options, Slice("foo2"), Slice("bar")));
+
+ Transaction* txn = db->BeginTransaction(write_options, TransactionOptions());
+ ASSERT_TRUE(txn);
+
+ ASSERT_EQ(0, txn->GetNumPuts());
+ ASSERT_LE(0, txn->GetID());
+
+ ASSERT_OK(txn->GetForUpdate(read_options, "foo", &value));
+ ASSERT_EQ(value, "bar");
+
+ ASSERT_OK(txn->Put(Slice("foo"), Slice("bar2")));
+
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ ASSERT_OK(txn->GetForUpdate(read_options, "foo", &value));
+ ASSERT_EQ(value, "bar2");
+
+ ASSERT_OK(txn->Commit());
+
+ ASSERT_OK(db->Get(read_options, "foo", &value));
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+// The test clarifies the contract of do_validate and assume_tracked
+// in GetForUpdate and Put/Merge/Delete
+TEST_P(TransactionTest, AssumeExclusiveTracked) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+ TransactionOptions txn_options;
+ txn_options.lock_timeout = 1;
+ const bool EXCLUSIVE = true;
+ const bool DO_VALIDATE = true;
+ const bool ASSUME_LOCKED = true;
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+ txn->SetSnapshot();
+
+ // commit a value after the snapshot is taken
+ ASSERT_OK(db->Put(write_options, Slice("foo"), Slice("bar")));
+
+ // By default write should fail to the commit after our snapshot
+ s = txn->GetForUpdate(read_options, "foo", &value, EXCLUSIVE);
+ ASSERT_TRUE(s.IsBusy());
+ // But the user could direct the db to skip validating the snapshot. The read
+ // value then should be the most recently committed
+ ASSERT_OK(
+ txn->GetForUpdate(read_options, "foo", &value, EXCLUSIVE, !DO_VALIDATE));
+ ASSERT_EQ(value, "bar");
+
+ // Although ValidateSnapshot is skipped the key must have still got locked
+ s = db->Put(write_options, Slice("foo"), Slice("bar"));
+ ASSERT_TRUE(s.IsTimedOut());
+
+ // By default the write operations should fail due to the commit after the
+ // snapshot
+ s = txn->Put(Slice("foo"), Slice("bar1"));
+ ASSERT_TRUE(s.IsBusy());
+ s = txn->Put(db->DefaultColumnFamily(), Slice("foo"), Slice("bar1"),
+ !ASSUME_LOCKED);
+ ASSERT_TRUE(s.IsBusy());
+ // But the user could direct the db that it already assumes exclusive lock on
+ // the key due to the previous GetForUpdate call.
+ ASSERT_OK(txn->Put(db->DefaultColumnFamily(), Slice("foo"), Slice("bar1"),
+ ASSUME_LOCKED));
+ ASSERT_OK(txn->Merge(db->DefaultColumnFamily(), Slice("foo"), Slice("bar2"),
+ ASSUME_LOCKED));
+ ASSERT_OK(
+ txn->Delete(db->DefaultColumnFamily(), Slice("foo"), ASSUME_LOCKED));
+ ASSERT_OK(txn->SingleDelete(db->DefaultColumnFamily(), Slice("foo"),
+ ASSUME_LOCKED));
+
+ txn->Rollback();
+ delete txn;
+}
+
+// This test clarifies the contract of ValidateSnapshot
+TEST_P(TransactionTest, ValidateSnapshotTest) {
+ for (bool with_flush : {true}) {
+ for (bool with_2pc : {true}) {
+ ASSERT_OK(ReOpen());
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+
+ assert(db != nullptr);
+ Transaction* txn1 =
+ db->BeginTransaction(write_options, TransactionOptions());
+ ASSERT_TRUE(txn1);
+ ASSERT_OK(txn1->Put(Slice("foo"), Slice("bar1")));
+ if (with_2pc) {
+ ASSERT_OK(txn1->SetName("xid1"));
+ ASSERT_OK(txn1->Prepare());
+ }
+
+ if (with_flush) {
+ auto db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ db_impl->TEST_FlushMemTable(true);
+ // Make sure the flushed memtable is not kept in memory
+ int max_memtable_in_history =
+ std::max(
+ options.max_write_buffer_number,
+ static_cast<int>(options.max_write_buffer_size_to_maintain) /
+ static_cast<int>(options.write_buffer_size)) +
+ 1;
+ for (int i = 0; i < max_memtable_in_history; i++) {
+ db->Put(write_options, Slice("key"), Slice("value"));
+ db_impl->TEST_FlushMemTable(true);
+ }
+ }
+
+ Transaction* txn2 =
+ db->BeginTransaction(write_options, TransactionOptions());
+ ASSERT_TRUE(txn2);
+ txn2->SetSnapshot();
+
+ ASSERT_OK(txn1->Commit());
+ delete txn1;
+
+ auto pes_txn2 = dynamic_cast<PessimisticTransaction*>(txn2);
+ // Test the simple case where the key is not tracked yet
+ auto trakced_seq = kMaxSequenceNumber;
+ auto s = pes_txn2->ValidateSnapshot(db->DefaultColumnFamily(), "foo",
+ &trakced_seq);
+ ASSERT_TRUE(s.IsBusy());
+ delete txn2;
+ }
+ }
+}
+
+TEST_P(TransactionTest, WaitingTxn) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_options.lock_timeout = 1;
+ s = db->Put(write_options, Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+
+ /* create second cf */
+ ColumnFamilyHandle* cfa;
+ ColumnFamilyOptions cf_options;
+ s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = db->Put(write_options, cfa, Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ TransactionID id1 = txn1->GetID();
+ ASSERT_TRUE(txn1);
+ ASSERT_TRUE(txn2);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TransactionLockMgr::AcquireWithTimeout:WaitingTxn", [&](void* /*arg*/) {
+ std::string key;
+ uint32_t cf_id;
+ std::vector<TransactionID> wait = txn2->GetWaitingTxns(&cf_id, &key);
+ ASSERT_EQ(key, "foo");
+ ASSERT_EQ(wait.size(), 1);
+ ASSERT_EQ(wait[0], id1);
+ ASSERT_EQ(cf_id, 0U);
+ });
+
+ get_perf_context()->Reset();
+ // lock key in default cf
+ s = txn1->GetForUpdate(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+ ASSERT_EQ(get_perf_context()->key_lock_wait_count, 0);
+
+ // lock key in cfa
+ s = txn1->GetForUpdate(read_options, cfa, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+ ASSERT_EQ(get_perf_context()->key_lock_wait_count, 0);
+
+ auto lock_data = db->GetLockStatusData();
+ // Locked keys exist in both column family.
+ ASSERT_EQ(lock_data.size(), 2);
+
+ auto cf_iterator = lock_data.begin();
+
+ // The iterator points to an unordered_multimap
+ // thus the test can not assume any particular order.
+
+ // Column family is 1 or 0 (cfa).
+ if (cf_iterator->first != 1 && cf_iterator->first != 0) {
+ FAIL();
+ }
+ // The locked key is "foo" and is locked by txn1
+ ASSERT_EQ(cf_iterator->second.key, "foo");
+ ASSERT_EQ(cf_iterator->second.ids.size(), 1);
+ ASSERT_EQ(cf_iterator->second.ids[0], txn1->GetID());
+
+ cf_iterator++;
+
+ // Column family is 0 (default) or 1.
+ if (cf_iterator->first != 1 && cf_iterator->first != 0) {
+ FAIL();
+ }
+ // The locked key is "foo" and is locked by txn1
+ ASSERT_EQ(cf_iterator->second.key, "foo");
+ ASSERT_EQ(cf_iterator->second.ids.size(), 1);
+ ASSERT_EQ(cf_iterator->second.ids[0], txn1->GetID());
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ s = txn2->GetForUpdate(read_options, "foo", &value);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+ ASSERT_EQ(get_perf_context()->key_lock_wait_count, 1);
+ ASSERT_GE(get_perf_context()->key_lock_wait_time, 0);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ delete cfa;
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, SharedLocks) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ Status s;
+
+ txn_options.lock_timeout = 1;
+ s = db->Put(write_options, Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn3 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+ ASSERT_TRUE(txn2);
+ ASSERT_TRUE(txn3);
+
+ // Test shared access between txns
+ s = txn1->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn3->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ auto lock_data = db->GetLockStatusData();
+ ASSERT_EQ(lock_data.size(), 1);
+
+ auto cf_iterator = lock_data.begin();
+ ASSERT_EQ(cf_iterator->second.key, "foo");
+
+ // We compare whether the set of txns locking this key is the same. To do
+ // this, we need to sort both vectors so that the comparison is done
+ // correctly.
+ std::vector<TransactionID> expected_txns = {txn1->GetID(), txn2->GetID(),
+ txn3->GetID()};
+ std::vector<TransactionID> lock_txns = cf_iterator->second.ids;
+ ASSERT_EQ(expected_txns, lock_txns);
+ ASSERT_FALSE(cf_iterator->second.exclusive);
+
+ txn1->Rollback();
+ txn2->Rollback();
+ txn3->Rollback();
+
+ // Test txn1 and txn2 sharing a lock and txn3 trying to obtain it.
+ s = txn1->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn3->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ txn1->UndoGetForUpdate("foo");
+ s = txn3->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ txn2->UndoGetForUpdate("foo");
+ s = txn3->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_OK(s);
+
+ txn1->Rollback();
+ txn2->Rollback();
+ txn3->Rollback();
+
+ // Test txn1 and txn2 sharing a lock and txn2 trying to upgrade lock.
+ s = txn1->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ txn1->UndoGetForUpdate("foo");
+ s = txn2->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_OK(s);
+
+ ASSERT_OK(txn1->Rollback());
+ ASSERT_OK(txn2->Rollback());
+
+ // Test txn1 trying to downgrade its lock.
+ s = txn1->GetForUpdate(read_options, "foo", nullptr, true /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ // Should still fail after "downgrading".
+ s = txn1->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ txn1->Rollback();
+ txn2->Rollback();
+
+ // Test txn1 holding an exclusive lock and txn2 trying to obtain shared
+ // access.
+ s = txn1->GetForUpdate(read_options, "foo", nullptr);
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ txn1->UndoGetForUpdate("foo");
+ s = txn2->GetForUpdate(read_options, "foo", nullptr, false /* exclusive */);
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+ delete txn3;
+}
+
+TEST_P(TransactionTest, DeadlockCycleShared) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+
+ txn_options.lock_timeout = 1000000;
+ txn_options.deadlock_detect = true;
+
+ // Set up a wait for chain like this:
+ //
+ // Tn -> T(n*2)
+ // Tn -> T(n*2 + 1)
+ //
+ // So we have:
+ // T1 -> T2 -> T4 ...
+ // | |> T5 ...
+ // |> T3 -> T6 ...
+ // |> T7 ...
+ // up to T31, then T[16 - 31] -> T1.
+ // Note that Tn holds lock on floor(n / 2).
+
+ std::vector<Transaction*> txns(31);
+
+ for (uint32_t i = 0; i < 31; i++) {
+ txns[i] = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txns[i]);
+ auto s = txns[i]->GetForUpdate(read_options, ToString((i + 1) / 2), nullptr,
+ false /* exclusive */);
+ ASSERT_OK(s);
+ }
+
+ std::atomic<uint32_t> checkpoints(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TransactionLockMgr::AcquireWithTimeout:WaitingTxn",
+ [&](void* /*arg*/) { checkpoints.fetch_add(1); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // We want the leaf transactions to block and hold everyone back.
+ std::vector<port::Thread> threads;
+ for (uint32_t i = 0; i < 15; i++) {
+ std::function<void()> blocking_thread = [&, i] {
+ auto s = txns[i]->GetForUpdate(read_options, ToString(i + 1), nullptr,
+ true /* exclusive */);
+ ASSERT_OK(s);
+ txns[i]->Rollback();
+ delete txns[i];
+ };
+ threads.emplace_back(blocking_thread);
+ }
+
+ // Wait until all threads are waiting on each other.
+ while (checkpoints.load() != 15) {
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ // Complete the cycle T[16 - 31] -> T1
+ for (uint32_t i = 15; i < 31; i++) {
+ auto s =
+ txns[i]->GetForUpdate(read_options, "0", nullptr, true /* exclusive */);
+ ASSERT_TRUE(s.IsDeadlock());
+
+ // Calculate next buffer len, plateau at 5 when 5 records are inserted.
+ const uint32_t curr_dlock_buffer_len_ =
+ (i - 14 > kInitialMaxDeadlocks) ? kInitialMaxDeadlocks : (i - 14);
+
+ auto dlock_buffer = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer.size(), curr_dlock_buffer_len_);
+ auto dlock_entry = dlock_buffer[0].path;
+ ASSERT_EQ(dlock_entry.size(), kInitialMaxDeadlocks);
+ int64_t pre_deadlock_time = dlock_buffer[0].deadlock_time;
+ int64_t cur_deadlock_time = 0;
+ for (auto const& dl_path_rec : dlock_buffer) {
+ cur_deadlock_time = dl_path_rec.deadlock_time;
+ ASSERT_NE(cur_deadlock_time, 0);
+ ASSERT_TRUE(cur_deadlock_time <= pre_deadlock_time);
+ pre_deadlock_time = cur_deadlock_time;
+ }
+
+ int64_t curr_waiting_key = 0;
+
+ // Offset of each txn id from the root of the shared dlock tree's txn id.
+ int64_t offset_root = dlock_entry[0].m_txn_id - 1;
+ // Offset of the final entry in the dlock path from the root's txn id.
+ TransactionID leaf_id =
+ dlock_entry[dlock_entry.size() - 1].m_txn_id - offset_root;
+
+ for (auto it = dlock_entry.rbegin(); it != dlock_entry.rend(); ++it) {
+ auto dl_node = *it;
+ ASSERT_EQ(dl_node.m_txn_id, offset_root + leaf_id);
+ ASSERT_EQ(dl_node.m_cf_id, 0U);
+ ASSERT_EQ(dl_node.m_waiting_key, ToString(curr_waiting_key));
+ ASSERT_EQ(dl_node.m_exclusive, true);
+
+ if (curr_waiting_key == 0) {
+ curr_waiting_key = leaf_id;
+ }
+ curr_waiting_key /= 2;
+ leaf_id /= 2;
+ }
+ }
+
+ // Rollback the leaf transaction.
+ for (uint32_t i = 15; i < 31; i++) {
+ txns[i]->Rollback();
+ delete txns[i];
+ }
+
+ for (auto& t : threads) {
+ t.join();
+ }
+
+ // Downsize the buffer and verify the 3 latest deadlocks are preserved.
+ auto dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
+ db->SetDeadlockInfoBufferSize(3);
+ auto dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer_after_resize.size(), 3);
+
+ for (uint32_t i = 0; i < dlock_buffer_after_resize.size(); i++) {
+ for (uint32_t j = 0; j < dlock_buffer_after_resize[i].path.size(); j++) {
+ ASSERT_EQ(dlock_buffer_after_resize[i].path[j].m_txn_id,
+ dlock_buffer_before_resize[i].path[j].m_txn_id);
+ }
+ }
+
+ // Upsize the buffer and verify the 3 latest dealocks are preserved.
+ dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
+ db->SetDeadlockInfoBufferSize(5);
+ dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer_after_resize.size(), 3);
+
+ for (uint32_t i = 0; i < dlock_buffer_before_resize.size(); i++) {
+ for (uint32_t j = 0; j < dlock_buffer_before_resize[i].path.size(); j++) {
+ ASSERT_EQ(dlock_buffer_after_resize[i].path[j].m_txn_id,
+ dlock_buffer_before_resize[i].path[j].m_txn_id);
+ }
+ }
+
+ // Downsize to 0 and verify the size is consistent.
+ dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
+ db->SetDeadlockInfoBufferSize(0);
+ dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer_after_resize.size(), 0);
+
+ // Upsize from 0 to verify the size is persistent.
+ dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
+ db->SetDeadlockInfoBufferSize(3);
+ dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer_after_resize.size(), 0);
+
+ // Contrived case of shared lock of cycle size 2 to verify that a shared
+ // lock causing a deadlock is correctly reported as "shared" in the buffer.
+ std::vector<Transaction*> txns_shared(2);
+
+ // Create a cycle of size 2.
+ for (uint32_t i = 0; i < 2; i++) {
+ txns_shared[i] = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txns_shared[i]);
+ auto s = txns_shared[i]->GetForUpdate(read_options, ToString(i), nullptr);
+ ASSERT_OK(s);
+ }
+
+ std::atomic<uint32_t> checkpoints_shared(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TransactionLockMgr::AcquireWithTimeout:WaitingTxn",
+ [&](void* /*arg*/) { checkpoints_shared.fetch_add(1); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ std::vector<port::Thread> threads_shared;
+ for (uint32_t i = 0; i < 1; i++) {
+ std::function<void()> blocking_thread = [&, i] {
+ auto s =
+ txns_shared[i]->GetForUpdate(read_options, ToString(i + 1), nullptr);
+ ASSERT_OK(s);
+ txns_shared[i]->Rollback();
+ delete txns_shared[i];
+ };
+ threads_shared.emplace_back(blocking_thread);
+ }
+
+ // Wait until all threads are waiting on each other.
+ while (checkpoints_shared.load() != 1) {
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ // Complete the cycle T2 -> T1 with a shared lock.
+ auto s = txns_shared[1]->GetForUpdate(read_options, "0", nullptr, false);
+ ASSERT_TRUE(s.IsDeadlock());
+
+ auto dlock_buffer = db->GetDeadlockInfoBuffer();
+
+ // Verify the size of the buffer and the single path.
+ ASSERT_EQ(dlock_buffer.size(), 1);
+ ASSERT_EQ(dlock_buffer[0].path.size(), 2);
+
+ // Verify the exclusivity field of the transactions in the deadlock path.
+ ASSERT_TRUE(dlock_buffer[0].path[0].m_exclusive);
+ ASSERT_FALSE(dlock_buffer[0].path[1].m_exclusive);
+ txns_shared[1]->Rollback();
+ delete txns_shared[1];
+
+ for (auto& t : threads_shared) {
+ t.join();
+ }
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+TEST_P(TransactionStressTest, DeadlockCycle) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+
+ // offset by 2 from the max depth to test edge case
+ const uint32_t kMaxCycleLength = 52;
+
+ txn_options.lock_timeout = 1000000;
+ txn_options.deadlock_detect = true;
+
+ for (uint32_t len = 2; len < kMaxCycleLength; len++) {
+ // Set up a long wait for chain like this:
+ //
+ // T1 -> T2 -> T3 -> ... -> Tlen
+
+ std::vector<Transaction*> txns(len);
+
+ for (uint32_t i = 0; i < len; i++) {
+ txns[i] = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txns[i]);
+ auto s = txns[i]->GetForUpdate(read_options, ToString(i), nullptr);
+ ASSERT_OK(s);
+ }
+
+ std::atomic<uint32_t> checkpoints(0);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TransactionLockMgr::AcquireWithTimeout:WaitingTxn",
+ [&](void* /*arg*/) { checkpoints.fetch_add(1); });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // We want the last transaction in the chain to block and hold everyone
+ // back.
+ std::vector<port::Thread> threads;
+ for (uint32_t i = 0; i < len - 1; i++) {
+ std::function<void()> blocking_thread = [&, i] {
+ auto s = txns[i]->GetForUpdate(read_options, ToString(i + 1), nullptr);
+ ASSERT_OK(s);
+ txns[i]->Rollback();
+ delete txns[i];
+ };
+ threads.emplace_back(blocking_thread);
+ }
+
+ // Wait until all threads are waiting on each other.
+ while (checkpoints.load() != len - 1) {
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ // Complete the cycle Tlen -> T1
+ auto s = txns[len - 1]->GetForUpdate(read_options, "0", nullptr);
+ ASSERT_TRUE(s.IsDeadlock());
+
+ const uint32_t dlock_buffer_size_ = (len - 1 > 5) ? 5 : (len - 1);
+ uint32_t curr_waiting_key = 0;
+ TransactionID curr_txn_id = txns[0]->GetID();
+
+ auto dlock_buffer = db->GetDeadlockInfoBuffer();
+ ASSERT_EQ(dlock_buffer.size(), dlock_buffer_size_);
+ uint32_t check_len = len;
+ bool check_limit_flag = false;
+
+ // Special case for a deadlock path that exceeds the maximum depth.
+ if (len > 50) {
+ check_len = 0;
+ check_limit_flag = true;
+ }
+ auto dlock_entry = dlock_buffer[0].path;
+ ASSERT_EQ(dlock_entry.size(), check_len);
+ ASSERT_EQ(dlock_buffer[0].limit_exceeded, check_limit_flag);
+
+ int64_t pre_deadlock_time = dlock_buffer[0].deadlock_time;
+ int64_t cur_deadlock_time = 0;
+ for (auto const& dl_path_rec : dlock_buffer) {
+ cur_deadlock_time = dl_path_rec.deadlock_time;
+ ASSERT_NE(cur_deadlock_time, 0);
+ ASSERT_TRUE(cur_deadlock_time <= pre_deadlock_time);
+ pre_deadlock_time = cur_deadlock_time;
+ }
+
+ // Iterates backwards over path verifying decreasing txn_ids.
+ for (auto it = dlock_entry.rbegin(); it != dlock_entry.rend(); ++it) {
+ auto dl_node = *it;
+ ASSERT_EQ(dl_node.m_txn_id, len + curr_txn_id - 1);
+ ASSERT_EQ(dl_node.m_cf_id, 0u);
+ ASSERT_EQ(dl_node.m_waiting_key, ToString(curr_waiting_key));
+ ASSERT_EQ(dl_node.m_exclusive, true);
+
+ curr_txn_id--;
+ if (curr_waiting_key == 0) {
+ curr_waiting_key = len;
+ }
+ curr_waiting_key--;
+ }
+
+ // Rollback the last transaction.
+ txns[len - 1]->Rollback();
+ delete txns[len - 1];
+
+ for (auto& t : threads) {
+ t.join();
+ }
+ }
+}
+
+TEST_P(TransactionStressTest, DeadlockStress) {
+ const uint32_t NUM_TXN_THREADS = 10;
+ const uint32_t NUM_KEYS = 100;
+ const uint32_t NUM_ITERS = 10000;
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+
+ txn_options.lock_timeout = 1000000;
+ txn_options.deadlock_detect = true;
+ std::vector<std::string> keys;
+
+ for (uint32_t i = 0; i < NUM_KEYS; i++) {
+ db->Put(write_options, Slice(ToString(i)), Slice(""));
+ keys.push_back(ToString(i));
+ }
+
+ size_t tid = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random rnd(static_cast<uint32_t>(tid));
+ std::function<void(uint32_t)> stress_thread = [&](uint32_t seed) {
+ std::default_random_engine g(seed);
+
+ Transaction* txn;
+ for (uint32_t i = 0; i < NUM_ITERS; i++) {
+ txn = db->BeginTransaction(write_options, txn_options);
+ auto random_keys = keys;
+ std::shuffle(random_keys.begin(), random_keys.end(), g);
+
+ // Lock keys in random order.
+ for (const auto& k : random_keys) {
+ // Lock mostly for shared access, but exclusive 1/4 of the time.
+ auto s =
+ txn->GetForUpdate(read_options, k, nullptr, txn->GetID() % 4 == 0);
+ if (!s.ok()) {
+ ASSERT_TRUE(s.IsDeadlock());
+ txn->Rollback();
+ break;
+ }
+ }
+
+ delete txn;
+ }
+ };
+
+ std::vector<port::Thread> threads;
+ for (uint32_t i = 0; i < NUM_TXN_THREADS; i++) {
+ threads.emplace_back(stress_thread, rnd.Next());
+ }
+
+ for (auto& t : threads) {
+ t.join();
+ }
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(TransactionTest, CommitTimeBatchFailTest) {
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ ASSERT_OK(txn1->GetCommitTimeWriteBatch()->Put("cat", "dog"));
+
+ s = txn1->Put("foo", "bar");
+ ASSERT_OK(s);
+
+ // fails due to non-empty commit-time batch
+ s = txn1->Commit();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ delete txn1;
+}
+
+TEST_P(TransactionTest, LogMarkLeakTest) {
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ options.write_buffer_size = 1024;
+ ASSERT_OK(ReOpenNoDelete());
+ assert(db != nullptr);
+ Random rnd(47);
+ std::vector<Transaction*> txns;
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ // At the beginning there should be no log containing prepare data
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+ for (size_t i = 0; i < 100; i++) {
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName("xid" + ToString(i)));
+ ASSERT_OK(txn->Put(Slice("foo" + ToString(i)), Slice("bar")));
+ ASSERT_OK(txn->Prepare());
+ ASSERT_GT(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+ if (rnd.OneIn(5)) {
+ txns.push_back(txn);
+ } else {
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ }
+ db_impl->TEST_FlushMemTable(true);
+ }
+ for (auto txn : txns) {
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ }
+ // At the end there should be no log left containing prepare data
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+ // Make sure that the underlying data structures are properly truncated and
+ // cause not leak
+ ASSERT_EQ(db_impl->TEST_PreparedSectionCompletedSize(), 0);
+ ASSERT_EQ(db_impl->TEST_LogsWithPrepSize(), 0);
+}
+
+TEST_P(TransactionTest, SimpleTwoPhaseTransactionTest) {
+ for (bool cwb4recovery : {true, false}) {
+ ASSERT_OK(ReOpen());
+ WriteOptions write_options;
+ ReadOptions read_options;
+
+ TransactionOptions txn_options;
+ txn_options.use_only_the_last_commit_time_batch_for_recovery = cwb4recovery;
+
+ string value;
+ Status s;
+
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("xid");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(db->GetTransactionByName("xid"), txn);
+
+ // transaction put
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ // regular db put
+ s = db->Put(write_options, Slice("foo2"), Slice("bar2"));
+ ASSERT_OK(s);
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ // regular db read
+ db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "bar2");
+
+ // commit time put
+ txn->GetCommitTimeWriteBatch()->Put(Slice("gtid"), Slice("dogs"));
+ txn->GetCommitTimeWriteBatch()->Put(Slice("gtid2"), Slice("cats"));
+
+ // nothing has been prepped yet
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ // data not im mem yet
+ s = db->Get(read_options, Slice("foo"), &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(read_options, Slice("gtid"), &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // find trans in list of prepared transactions
+ std::vector<Transaction*> prepared_trans;
+ db->GetAllPreparedTransactions(&prepared_trans);
+ ASSERT_EQ(prepared_trans.size(), 1);
+ ASSERT_EQ(prepared_trans.front()->GetName(), "xid");
+
+ auto log_containing_prep =
+ db_impl->TEST_FindMinLogContainingOutstandingPrep();
+ ASSERT_GT(log_containing_prep, 0);
+
+ // make commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ // value is now available
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+
+ if (!cwb4recovery) {
+ s = db->Get(read_options, "gtid", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "dogs");
+
+ s = db->Get(read_options, "gtid2", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "cats");
+ }
+
+ // we already committed
+ s = txn->Commit();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // no longer is prepared results
+ db->GetAllPreparedTransactions(&prepared_trans);
+ ASSERT_EQ(prepared_trans.size(), 0);
+ ASSERT_EQ(db->GetTransactionByName("xid"), nullptr);
+
+ // heap should not care about prepared section anymore
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // but now our memtable should be referencing the prep section
+ ASSERT_GE(log_containing_prep, db_impl->MinLogNumberToKeep());
+ ASSERT_EQ(log_containing_prep,
+ db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ default:
+ assert(false);
+ }
+
+ db_impl->TEST_FlushMemTable(true);
+ // After flush the recoverable state must be visible
+ if (cwb4recovery) {
+ s = db->Get(read_options, "gtid", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "dogs");
+
+ s = db->Get(read_options, "gtid2", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "cats");
+ }
+
+ // after memtable flush we can now relese the log
+ ASSERT_GT(db_impl->MinLogNumberToKeep(), log_containing_prep);
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+
+ delete txn;
+
+ if (cwb4recovery) {
+ // kill and reopen to trigger recovery
+ s = ReOpenNoDelete();
+ ASSERT_OK(s);
+ assert(db != nullptr);
+ s = db->Get(read_options, "gtid", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "dogs");
+
+ s = db->Get(read_options, "gtid2", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "cats");
+ }
+ }
+}
+
+TEST_P(TransactionTest, TwoPhaseNameTest) {
+ Status s;
+
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn3 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn3);
+ delete txn3;
+
+ // cant prepare txn without name
+ s = txn1->Prepare();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // name too short
+ s = txn1->SetName("");
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // name too long
+ s = txn1->SetName(std::string(513, 'x'));
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // valid set name
+ s = txn1->SetName("name1");
+ ASSERT_OK(s);
+
+ // cant have duplicate name
+ s = txn2->SetName("name1");
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // shouldn't be able to prepare
+ s = txn2->Prepare();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // valid name set
+ s = txn2->SetName("name2");
+ ASSERT_OK(s);
+
+ // cant reset name
+ s = txn2->SetName("name3");
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ ASSERT_EQ(txn1->GetName(), "name1");
+ ASSERT_EQ(txn2->GetName(), "name2");
+
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ // can't rename after prepare
+ s = txn1->SetName("name4");
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ txn1->Rollback();
+ txn2->Rollback();
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, TwoPhaseEmptyWriteTest) {
+ for (bool cwb4recovery : {true, false}) {
+ for (bool test_with_empty_wal : {true, false}) {
+ if (!cwb4recovery && test_with_empty_wal) {
+ continue;
+ }
+ ASSERT_OK(ReOpen());
+ Status s;
+ std::string value;
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ txn_options.use_only_the_last_commit_time_batch_for_recovery =
+ cwb4recovery;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn1->SetName("joe");
+ ASSERT_OK(s);
+
+ s = txn2->SetName("bob");
+ ASSERT_OK(s);
+
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+
+ txn2->GetCommitTimeWriteBatch()->Put(Slice("foo"), Slice("bar"));
+
+ s = txn2->Prepare();
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn2;
+ if (!cwb4recovery) {
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+ } else {
+ if (test_with_empty_wal) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ db_impl->TEST_FlushMemTable(true);
+ // After flush the state must be visible
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+ }
+ db->FlushWAL(true);
+ // kill and reopen to trigger recovery
+ s = ReOpenNoDelete();
+ ASSERT_OK(s);
+ assert(db != nullptr);
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+ }
+ }
+ }
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+TEST_P(TransactionStressTest, TwoPhaseExpirationTest) {
+ Status s;
+
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ txn_options.expiration = 500; // 500ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+ ASSERT_TRUE(txn1);
+
+ s = txn1->SetName("joe");
+ ASSERT_OK(s);
+ s = txn2->SetName("bob");
+ ASSERT_OK(s);
+
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Prepare();
+ ASSERT_EQ(s, Status::Expired());
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, TwoPhaseRollbackTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("xid");
+ ASSERT_OK(s);
+
+ // transaction put
+ s = txn->Put(Slice("tfoo"), Slice("tbar"));
+ ASSERT_OK(s);
+
+ // value is readable form txn
+ s = txn->Get(read_options, Slice("tfoo"), &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "tbar");
+
+ // issue rollback
+ s = txn->Rollback();
+ ASSERT_OK(s);
+
+ // value is nolonger readable
+ s = txn->Get(read_options, Slice("tfoo"), &value);
+ ASSERT_TRUE(s.IsNotFound());
+ ASSERT_EQ(txn->GetNumPuts(), 0);
+
+ // put new txn values
+ s = txn->Put(Slice("tfoo2"), Slice("tbar2"));
+ ASSERT_OK(s);
+
+ // new value is readable from txn
+ s = txn->Get(read_options, Slice("tfoo2"), &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "tbar2");
+
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ // flush to next wal
+ s = db->Put(write_options, Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+ db_impl->TEST_FlushMemTable(true);
+
+ // issue rollback (marker written to WAL)
+ s = txn->Rollback();
+ ASSERT_OK(s);
+
+ // value is nolonger readable
+ s = txn->Get(read_options, Slice("tfoo2"), &value);
+ ASSERT_TRUE(s.IsNotFound());
+ ASSERT_EQ(txn->GetNumPuts(), 0);
+
+ // make commit
+ s = txn->Commit();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // try rollback again
+ s = txn->Rollback();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, PersistentTwoPhaseTransactionTest) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ ReadOptions read_options;
+
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("xid");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(db->GetTransactionByName("xid"), txn);
+
+ // transaction put
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ // txn read
+ s = txn->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+
+ // regular db put
+ s = db->Put(write_options, Slice("foo2"), Slice("bar2"));
+ ASSERT_OK(s);
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ db_impl->TEST_FlushMemTable(true);
+
+ // regular db read
+ db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "bar2");
+
+ // nothing has been prepped yet
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ // prepare
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ // still not available to db
+ s = db->Get(read_options, Slice("foo"), &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ db->FlushWAL(false);
+ delete txn;
+ // kill and reopen
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ s = ReOpenNoDelete();
+ ASSERT_OK(s);
+ assert(db != nullptr);
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ // find trans in list of prepared transactions
+ std::vector<Transaction*> prepared_trans;
+ db->GetAllPreparedTransactions(&prepared_trans);
+ ASSERT_EQ(prepared_trans.size(), 1);
+
+ txn = prepared_trans.front();
+ ASSERT_TRUE(txn);
+ ASSERT_EQ(txn->GetName(), "xid");
+ ASSERT_EQ(db->GetTransactionByName("xid"), txn);
+
+ // log has been marked
+ auto log_containing_prep =
+ db_impl->TEST_FindMinLogContainingOutstandingPrep();
+ ASSERT_GT(log_containing_prep, 0);
+
+ // value is readable from txn
+ s = txn->Get(read_options, "foo", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar");
+
+ // make commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ // value is now available
+ db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ // we already committed
+ s = txn->Commit();
+ ASSERT_EQ(s, Status::InvalidArgument());
+
+ // no longer is prepared results
+ prepared_trans.clear();
+ db->GetAllPreparedTransactions(&prepared_trans);
+ ASSERT_EQ(prepared_trans.size(), 0);
+
+ // transaction should no longer be visible
+ ASSERT_EQ(db->GetTransactionByName("xid"), nullptr);
+
+ // heap should not care about prepared section anymore
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // but now our memtable should be referencing the prep section
+ ASSERT_EQ(log_containing_prep,
+ db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ ASSERT_GE(log_containing_prep, db_impl->MinLogNumberToKeep());
+
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ default:
+ assert(false);
+ }
+
+ // Add a dummy record to memtable before a flush. Otherwise, the
+ // memtable will be empty and flush will be skipped.
+ s = db->Put(write_options, Slice("foo3"), Slice("bar3"));
+ ASSERT_OK(s);
+
+ db_impl->TEST_FlushMemTable(true);
+
+ // after memtable flush we can now release the log
+ ASSERT_GT(db_impl->MinLogNumberToKeep(), log_containing_prep);
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+
+ delete txn;
+
+ // deleting transaction should unregister transaction
+ ASSERT_EQ(db->GetTransactionByName("xid"), nullptr);
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+// TODO this test needs to be updated with serial commits
+TEST_P(TransactionTest, DISABLED_TwoPhaseMultiThreadTest) {
+ // mix transaction writes and regular writes
+ const uint32_t NUM_TXN_THREADS = 50;
+ std::atomic<uint32_t> txn_thread_num(0);
+
+ std::function<void()> txn_write_thread = [&]() {
+ uint32_t id = txn_thread_num.fetch_add(1);
+
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ TransactionOptions txn_options;
+ txn_options.lock_timeout = 1000000;
+ if (id % 2 == 0) {
+ txn_options.expiration = 1000000;
+ }
+ TransactionName name("xid_" + std::string(1, 'A' + static_cast<char>(id)));
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName(name));
+ for (int i = 0; i < 10; i++) {
+ std::string key(name + "_" + std::string(1, static_cast<char>('A' + i)));
+ ASSERT_OK(txn->Put(key, "val"));
+ }
+ ASSERT_OK(txn->Prepare());
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ };
+
+ // assure that all thread are in the same write group
+ std::atomic<uint32_t> t_wait_on_prepare(0);
+ std::atomic<uint32_t> t_wait_on_commit(0);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WriteThread::JoinBatchGroup:Wait", [&](void* arg) {
+ auto* writer = reinterpret_cast<WriteThread::Writer*>(arg);
+
+ if (writer->ShouldWriteToWAL()) {
+ t_wait_on_prepare.fetch_add(1);
+ // wait for friends
+ while (t_wait_on_prepare.load() < NUM_TXN_THREADS) {
+ env->SleepForMicroseconds(10);
+ }
+ } else if (writer->ShouldWriteToMemtable()) {
+ t_wait_on_commit.fetch_add(1);
+ // wait for friends
+ while (t_wait_on_commit.load() < NUM_TXN_THREADS) {
+ env->SleepForMicroseconds(10);
+ }
+ } else {
+ FAIL();
+ }
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ // do all the writes
+ std::vector<port::Thread> threads;
+ for (uint32_t i = 0; i < NUM_TXN_THREADS; i++) {
+ threads.emplace_back(txn_write_thread);
+ }
+ for (auto& t : threads) {
+ t.join();
+ }
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+ for (uint32_t t = 0; t < NUM_TXN_THREADS; t++) {
+ TransactionName name("xid_" + std::string(1, 'A' + static_cast<char>(t)));
+ for (int i = 0; i < 10; i++) {
+ std::string key(name + "_" + std::string(1, static_cast<char>('A' + i)));
+ s = db->Get(read_options, key, &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "val");
+ }
+ }
+}
+
+TEST_P(TransactionStressTest, TwoPhaseLongPrepareTest) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("bob");
+ ASSERT_OK(s);
+
+ // transaction put
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+
+ // prepare
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ for (int i = 0; i < 1000; i++) {
+ std::string key(i, 'k');
+ std::string val(1000, 'v');
+ assert(db != nullptr);
+ s = db->Put(write_options, key, val);
+ ASSERT_OK(s);
+
+ if (i % 29 == 0) {
+ // crash
+ env->SetFilesystemActive(false);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ReOpenNoDelete();
+ } else if (i % 37 == 0) {
+ // close
+ ReOpenNoDelete();
+ }
+ }
+
+ // commit old txn
+ txn = db->GetTransactionByName("bob");
+ ASSERT_TRUE(txn);
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ // verify data txn data
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, "bar");
+
+ // verify non txn data
+ for (int i = 0; i < 1000; i++) {
+ std::string key(i, 'k');
+ std::string val(1000, 'v');
+ s = db->Get(read_options, key, &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, val);
+ }
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, TwoPhaseSequenceTest) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ ReadOptions read_options;
+
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("xid");
+ ASSERT_OK(s);
+
+ // transaction put
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+ s = txn->Put(Slice("foo2"), Slice("bar2"));
+ ASSERT_OK(s);
+ s = txn->Put(Slice("foo3"), Slice("bar3"));
+ ASSERT_OK(s);
+ s = txn->Put(Slice("foo4"), Slice("bar4"));
+ ASSERT_OK(s);
+
+ // prepare
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ // make commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ // kill and reopen
+ env->SetFilesystemActive(false);
+ ReOpenNoDelete();
+ assert(db != nullptr);
+
+ // value is now available
+ s = db->Get(read_options, "foo4", &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, "bar4");
+}
+
+TEST_P(TransactionTest, TwoPhaseDoubleRecoveryTest) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ ReadOptions read_options;
+
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("a");
+ ASSERT_OK(s);
+
+ // transaction put
+ s = txn->Put(Slice("foo"), Slice("bar"));
+ ASSERT_OK(s);
+
+ // prepare
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ // kill and reopen
+ env->SetFilesystemActive(false);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ReOpenNoDelete();
+
+ // commit old txn
+ txn = db->GetTransactionByName("a");
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, "bar");
+
+ delete txn;
+
+ txn = db->BeginTransaction(write_options, txn_options);
+ s = txn->SetName("b");
+ ASSERT_OK(s);
+
+ s = txn->Put(Slice("foo2"), Slice("bar2"));
+ ASSERT_OK(s);
+
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ // kill and reopen
+ env->SetFilesystemActive(false);
+ ReOpenNoDelete();
+ assert(db != nullptr);
+
+ // value is now available
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, "bar");
+
+ s = db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(s, Status::OK());
+ ASSERT_EQ(value, "bar2");
+}
+
+TEST_P(TransactionTest, TwoPhaseLogRollingTest) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ Status s;
+ std::string v;
+ ColumnFamilyHandle *cfa, *cfb;
+
+ // Create 2 new column families
+ ColumnFamilyOptions cf_options;
+ s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = db->CreateColumnFamily(cf_options, "CFB", &cfb);
+ ASSERT_OK(s);
+
+ WriteOptions wopts;
+ wopts.disableWAL = false;
+ wopts.sync = true;
+
+ TransactionOptions topts1;
+ Transaction* txn1 = db->BeginTransaction(wopts, topts1);
+ s = txn1->SetName("xid1");
+ ASSERT_OK(s);
+
+ TransactionOptions topts2;
+ Transaction* txn2 = db->BeginTransaction(wopts, topts2);
+ s = txn2->SetName("xid2");
+ ASSERT_OK(s);
+
+ // transaction put in two column families
+ s = txn1->Put(cfa, "ka1", "va1");
+ ASSERT_OK(s);
+
+ // transaction put in two column families
+ s = txn2->Put(cfa, "ka2", "va2");
+ ASSERT_OK(s);
+ s = txn2->Put(cfb, "kb2", "vb2");
+ ASSERT_OK(s);
+
+ // write prep section to wal
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ // our log should be in the heap
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn1->GetLogNumber());
+ ASSERT_EQ(db_impl->TEST_LogfileNumber(), txn1->GetLastLogNumber());
+
+ // flush default cf to crate new log
+ s = db->Put(wopts, "foo", "bar");
+ ASSERT_OK(s);
+ s = db_impl->TEST_FlushMemTable(true);
+ ASSERT_OK(s);
+
+ // make sure we are on a new log
+ ASSERT_GT(db_impl->TEST_LogfileNumber(), txn1->GetLastLogNumber());
+
+ // put txn2 prep section in this log
+ s = txn2->Prepare();
+ ASSERT_OK(s);
+ ASSERT_EQ(db_impl->TEST_LogfileNumber(), txn2->GetLastLogNumber());
+
+ // heap should still see first log
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn1->GetLogNumber());
+
+ // commit txn1
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ // heap should now show txn2s log
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn2->GetLogNumber());
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // we should see txn1s log refernced by the memtables
+ ASSERT_EQ(txn1->GetLogNumber(),
+ db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ default:
+ assert(false);
+ }
+
+ // flush default cf to crate new log
+ s = db->Put(wopts, "foo", "bar2");
+ ASSERT_OK(s);
+ s = db_impl->TEST_FlushMemTable(true);
+ ASSERT_OK(s);
+
+ // make sure we are on a new log
+ ASSERT_GT(db_impl->TEST_LogfileNumber(), txn2->GetLastLogNumber());
+
+ // commit txn2
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ // heap should not show any logs
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // should show the first txn log
+ ASSERT_EQ(txn1->GetLogNumber(),
+ db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ default:
+ assert(false);
+ }
+
+ // flush only cfa memtable
+ s = db_impl->TEST_FlushMemTable(true, false, cfa);
+ ASSERT_OK(s);
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // should show the first txn log
+ ASSERT_EQ(txn2->GetLogNumber(),
+ db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(0, db_impl->TEST_FindMinPrepLogReferencedByMemTable());
+ break;
+ default:
+ assert(false);
+ }
+
+ // flush only cfb memtable
+ s = db_impl->TEST_FlushMemTable(true, false, cfb);
+ ASSERT_OK(s);
+
+ // should show not dependency on logs
+ ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), 0);
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ delete txn1;
+ delete txn2;
+ delete cfa;
+ delete cfb;
+}
+
+TEST_P(TransactionTest, TwoPhaseLogRollingTest2) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ Status s;
+ ColumnFamilyHandle *cfa, *cfb;
+
+ ColumnFamilyOptions cf_options;
+ s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = db->CreateColumnFamily(cf_options, "CFB", &cfb);
+ ASSERT_OK(s);
+
+ WriteOptions wopts;
+ wopts.disableWAL = false;
+ wopts.sync = true;
+
+ auto cfh_a = reinterpret_cast<ColumnFamilyHandleImpl*>(cfa);
+ auto cfh_b = reinterpret_cast<ColumnFamilyHandleImpl*>(cfb);
+
+ TransactionOptions topts1;
+ Transaction* txn1 = db->BeginTransaction(wopts, topts1);
+ s = txn1->SetName("xid1");
+ ASSERT_OK(s);
+ s = txn1->Put(cfa, "boys", "girls1");
+ ASSERT_OK(s);
+
+ Transaction* txn2 = db->BeginTransaction(wopts, topts1);
+ s = txn2->SetName("xid2");
+ ASSERT_OK(s);
+ s = txn2->Put(cfb, "up", "down1");
+ ASSERT_OK(s);
+
+ // prepre transaction in LOG A
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ // prepre transaction in LOG A
+ s = txn2->Prepare();
+ ASSERT_OK(s);
+
+ // regular put so that mem table can actually be flushed for log rolling
+ s = db->Put(wopts, "cats", "dogs1");
+ ASSERT_OK(s);
+
+ auto prepare_log_no = txn1->GetLastLogNumber();
+
+ // roll to LOG B
+ s = db_impl->TEST_FlushMemTable(true);
+ ASSERT_OK(s);
+
+ // now we pause background work so that
+ // imm()s are not flushed before we can check their status
+ s = db_impl->PauseBackgroundWork();
+ ASSERT_OK(s);
+
+ ASSERT_GT(db_impl->TEST_LogfileNumber(), prepare_log_no);
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // This cf is empty and should ref the latest log
+ ASSERT_GT(cfh_a->cfd()->GetLogNumber(), prepare_log_no);
+ ASSERT_EQ(cfh_a->cfd()->GetLogNumber(), db_impl->TEST_LogfileNumber());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // This cf is not flushed yet and should ref the log that has its data
+ ASSERT_EQ(cfh_a->cfd()->GetLogNumber(), prepare_log_no);
+ break;
+ default:
+ assert(false);
+ }
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn1->GetLogNumber());
+ ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), 0);
+
+ // commit in LOG B
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(),
+ prepare_log_no);
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // In these modes memtable do not ref the prep sections
+ ASSERT_EQ(db_impl->TEST_FindMinPrepLogReferencedByMemTable(), 0);
+ break;
+ default:
+ assert(false);
+ }
+
+ ASSERT_TRUE(!db_impl->TEST_UnableToReleaseOldestLog());
+
+ // request a flush for all column families such that the earliest
+ // alive log file can be killed
+ db_impl->TEST_SwitchWAL();
+ // log cannot be flushed because txn2 has not been commited
+ ASSERT_TRUE(!db_impl->TEST_IsLogGettingFlushed());
+ ASSERT_TRUE(db_impl->TEST_UnableToReleaseOldestLog());
+
+ // assert that cfa has a flush requested
+ ASSERT_TRUE(cfh_a->cfd()->imm()->HasFlushRequested());
+
+ switch (txn_db_options.write_policy) {
+ case WRITE_COMMITTED:
+ // cfb should not be flushed becuse it has no data from LOG A
+ ASSERT_TRUE(!cfh_b->cfd()->imm()->HasFlushRequested());
+ break;
+ case WRITE_PREPARED:
+ case WRITE_UNPREPARED:
+ // cfb should be flushed becuse it has prepared data from LOG A
+ ASSERT_TRUE(cfh_b->cfd()->imm()->HasFlushRequested());
+ break;
+ default:
+ assert(false);
+ }
+
+ // cfb now has data from LOG A
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ db_impl->TEST_SwitchWAL();
+ ASSERT_TRUE(!db_impl->TEST_UnableToReleaseOldestLog());
+
+ // we should see that cfb now has a flush requested
+ ASSERT_TRUE(cfh_b->cfd()->imm()->HasFlushRequested());
+
+ // all data in LOG A resides in a memtable that has been
+ // requested for a flush
+ ASSERT_TRUE(db_impl->TEST_IsLogGettingFlushed());
+
+ delete txn1;
+ delete txn2;
+ delete cfa;
+ delete cfb;
+}
+/*
+ * 1) use prepare to keep first log around to determine starting sequence
+ * during recovery.
+ * 2) insert many values, skipping wal, to increase seqid.
+ * 3) insert final value into wal
+ * 4) recover and see that final value was properly recovered - not
+ * hidden behind improperly summed sequence ids
+ */
+TEST_P(TransactionTest, TwoPhaseOutOfOrderDelete) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ WriteOptions wal_on, wal_off;
+ wal_on.sync = true;
+ wal_on.disableWAL = false;
+ wal_off.disableWAL = true;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+
+ std::string value;
+ Status s;
+
+ Transaction* txn1 = db->BeginTransaction(wal_on, txn_options);
+
+ s = txn1->SetName("1");
+ ASSERT_OK(s);
+
+ s = db->Put(wal_on, "first", "first");
+ ASSERT_OK(s);
+
+ s = txn1->Put(Slice("dummy"), Slice("dummy"));
+ ASSERT_OK(s);
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+
+ s = db->Put(wal_off, "cats", "dogs1");
+ ASSERT_OK(s);
+ s = db->Put(wal_off, "cats", "dogs2");
+ ASSERT_OK(s);
+ s = db->Put(wal_off, "cats", "dogs3");
+ ASSERT_OK(s);
+
+ s = db_impl->TEST_FlushMemTable(true);
+ ASSERT_OK(s);
+
+ s = db->Put(wal_on, "cats", "dogs4");
+ ASSERT_OK(s);
+
+ db->FlushWAL(false);
+
+ // kill and reopen
+ env->SetFilesystemActive(false);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+
+ s = db->Get(read_options, "first", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "first");
+
+ s = db->Get(read_options, "cats", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "dogs4");
+}
+
+TEST_P(TransactionTest, FirstWriteTest) {
+ WriteOptions write_options;
+
+ // Test conflict checking against the very first write to a db.
+ // The transaction's snapshot will have seq 1 and the following write
+ // will have sequence 1.
+ Status s = db->Put(write_options, "A", "a");
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+
+ ASSERT_OK(s);
+
+ s = txn->Put("A", "b");
+ ASSERT_OK(s);
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, FirstWriteTest2) {
+ WriteOptions write_options;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+
+ // Test conflict checking against the very first write to a db.
+ // The transaction's snapshot is a seq 0 while the following write
+ // will have sequence 1.
+ Status s = db->Put(write_options, "A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("A", "b");
+ ASSERT_TRUE(s.IsBusy());
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, WriteOptionsTest) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = true;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ ASSERT_TRUE(txn->GetWriteOptions()->sync);
+
+ write_options.sync = false;
+ txn->SetWriteOptions(write_options);
+ ASSERT_FALSE(txn->GetWriteOptions()->sync);
+ ASSERT_TRUE(txn->GetWriteOptions()->disableWAL);
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, WriteConflictTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ db->Put(write_options, "foo", "A");
+ db->Put(write_options, "foo2", "B");
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("foo", "A2");
+ ASSERT_OK(s);
+
+ s = txn->Put("foo2", "B2");
+ ASSERT_OK(s);
+
+ // This Put outside of a transaction will conflict with the previous write
+ s = db->Put(write_options, "foo", "xxx");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "A");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "A2");
+ db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "B2");
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, WriteConflictTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ db->Put(write_options, "foo", "bar");
+
+ txn_options.set_snapshot = true;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ // This Put outside of a transaction will conflict with a later write
+ s = db->Put(write_options, "foo", "barz");
+ ASSERT_OK(s);
+
+ s = txn->Put("foo2", "X");
+ ASSERT_OK(s);
+
+ s = txn->Put("foo",
+ "bar2"); // Conflicts with write done after snapshot taken
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->Put("foo3", "Y");
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+
+ ASSERT_EQ(2, txn->GetNumKeys());
+
+ s = txn->Commit();
+ ASSERT_OK(s); // Txn should commit, but only write foo2 and foo3
+
+ // Verify that transaction wrote foo2 and foo3 but not foo
+ db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "barz");
+
+ db->Get(read_options, "foo2", &value);
+ ASSERT_EQ(value, "X");
+
+ db->Get(read_options, "foo3", &value);
+ ASSERT_EQ(value, "Y");
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, ReadConflictTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ db->Put(write_options, "foo", "bar");
+ db->Put(write_options, "foo2", "bar");
+
+ txn_options.set_snapshot = true;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ // This Put outside of a transaction will conflict with the previous read
+ s = db->Put(write_options, "foo", "barz");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ s = txn->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, TxnOnlyTest) {
+ // Test to make sure transactions work when there are no other writes in an
+ // empty db.
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("x", "y");
+ ASSERT_OK(s);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, FlushTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ db->Put(write_options, Slice("foo"), Slice("bar"));
+ db->Put(write_options, Slice("foo2"), Slice("bar"));
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ s = txn->Put(Slice("foo"), Slice("bar2"));
+ ASSERT_OK(s);
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ // Put a random key so we have a memtable to flush
+ s = db->Put(write_options, "dummy", "dummy");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ FlushOptions flush_ops;
+ db->Flush(flush_ops);
+
+ s = txn->Commit();
+ // txn should commit since the flushed table is still in MemtableList History
+ ASSERT_OK(s);
+
+ db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, FlushTest2) {
+ const size_t num_tests = 3;
+
+ for (size_t n = 0; n < num_tests; n++) {
+ // Test different table factories
+ switch (n) {
+ case 0:
+ break;
+ case 1:
+ options.table_factory.reset(new mock::MockTableFactory());
+ break;
+ case 2: {
+ PlainTableOptions pt_opts;
+ pt_opts.hash_table_ratio = 0;
+ options.table_factory.reset(NewPlainTableFactory(pt_opts));
+ break;
+ }
+ }
+
+ Status s = ReOpen();
+ ASSERT_OK(s);
+ assert(db != nullptr);
+
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ TransactionOptions txn_options;
+ string value;
+
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+
+ db->Put(write_options, Slice("foo"), Slice("bar"));
+ db->Put(write_options, Slice("foo2"), Slice("bar2"));
+ db->Put(write_options, Slice("foo3"), Slice("bar3"));
+
+ txn_options.set_snapshot = true;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar");
+
+ s = txn->Put(Slice("foo"), Slice("bar2"));
+ ASSERT_OK(s);
+
+ txn->GetForUpdate(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+ // verify foo is locked by txn
+ s = db->Delete(write_options, "foo");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = db->Put(write_options, "Z", "z");
+ ASSERT_OK(s);
+ s = db->Put(write_options, "dummy", "dummy");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "S", "s");
+ ASSERT_OK(s);
+ s = db->SingleDelete(write_options, "S");
+ ASSERT_OK(s);
+
+ s = txn->Delete("S");
+ // Should fail after encountering a write to S in memtable
+ ASSERT_TRUE(s.IsBusy());
+
+ // force a memtable flush
+ s = db_impl->TEST_FlushMemTable(true);
+ ASSERT_OK(s);
+
+ // Put a random key so we have a MemTable to flush
+ s = db->Put(write_options, "dummy", "dummy2");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ ASSERT_OK(db_impl->TEST_FlushMemTable(true));
+
+ s = db->Put(write_options, "dummy", "dummy3");
+ ASSERT_OK(s);
+
+ // force a memtable flush
+ // Since our test db has max_write_buffer_number=2, this flush will cause
+ // the first memtable to get purged from the MemtableList history.
+ ASSERT_OK(db_impl->TEST_FlushMemTable(true));
+
+ s = txn->Put("X", "Y");
+ // Should succeed after verifying there is no write to X in SST file
+ ASSERT_OK(s);
+
+ s = txn->Put("Z", "zz");
+ // Should fail after encountering a write to Z in SST file
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->GetForUpdate(read_options, "foo2", &value);
+ // should succeed since key was written before txn started
+ ASSERT_OK(s);
+ // verify foo2 is locked by txn
+ s = db->Delete(write_options, "foo2");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn->Delete("S");
+ // Should fail after encountering a write to S in SST file
+ ASSERT_TRUE(s.IsBusy());
+
+ // Write a bunch of keys to db to force a compaction
+ Random rnd(47);
+ for (int i = 0; i < 1000; i++) {
+ s = db->Put(write_options, std::to_string(i),
+ test::CompressibleString(&rnd, 0.8, 100, &value));
+ ASSERT_OK(s);
+ }
+
+ s = txn->Put("X", "yy");
+ // Should succeed after verifying there is no write to X in SST file
+ ASSERT_OK(s);
+
+ s = txn->Put("Z", "zzz");
+ // Should fail after encountering a write to Z in SST file
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->Delete("S");
+ // Should fail after encountering a write to S in SST file
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->GetForUpdate(read_options, "foo3", &value);
+ // should succeed since key was written before txn started
+ ASSERT_OK(s);
+ // verify foo3 is locked by txn
+ s = db->Delete(write_options, "foo3");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ db_impl->TEST_WaitForCompact();
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ // Transaction should only write the keys that succeeded.
+ s = db->Get(read_options, "foo", &value);
+ ASSERT_EQ(value, "bar2");
+
+ s = db->Get(read_options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("yy", value);
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("z", value);
+
+ delete txn;
+ }
+}
+
+TEST_P(TransactionTest, NoSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ db->Put(write_options, "AAA", "bar");
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ // Modify key after transaction start
+ db->Put(write_options, "AAA", "bar1");
+
+ // Read and write without a snap
+ txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_EQ(value, "bar1");
+ s = txn->Put("AAA", "bar2");
+ ASSERT_OK(s);
+
+ // Should commit since read/write was done after data changed
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_EQ(value, "bar2");
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, MultipleSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ ASSERT_OK(db->Put(write_options, "AAA", "bar"));
+ ASSERT_OK(db->Put(write_options, "BBB", "bar"));
+ ASSERT_OK(db->Put(write_options, "CCC", "bar"));
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ db->Put(write_options, "AAA", "bar1");
+
+ // Read and write without a snapshot
+ ASSERT_OK(txn->GetForUpdate(read_options, "AAA", &value));
+ ASSERT_EQ(value, "bar1");
+ s = txn->Put("AAA", "bar2");
+ ASSERT_OK(s);
+
+ // Modify BBB before snapshot is taken
+ ASSERT_OK(db->Put(write_options, "BBB", "bar1"));
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ // Read and write with snapshot
+ ASSERT_OK(txn->GetForUpdate(snapshot_read_options, "BBB", &value));
+ ASSERT_EQ(value, "bar1");
+ s = txn->Put("BBB", "bar2");
+ ASSERT_OK(s);
+
+ ASSERT_OK(db->Put(write_options, "CCC", "bar1"));
+
+ // Set a new snapshot
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ // Read and write with snapshot
+ txn->GetForUpdate(snapshot_read_options, "CCC", &value);
+ ASSERT_EQ(value, "bar1");
+ s = txn->Put("CCC", "bar2");
+ ASSERT_OK(s);
+
+ s = txn->GetForUpdate(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn->GetForUpdate(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = txn->GetForUpdate(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+
+ s = db->Get(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+ s = db->Get(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+ s = db->Get(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar1");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "AAA", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = db->Get(read_options, "BBB", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+ s = db->Get(read_options, "CCC", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "bar2");
+
+ // verify that we track multiple writes to the same key at different snapshots
+ delete txn;
+ txn = db->BeginTransaction(write_options);
+
+ // Potentially conflicting writes
+ db->Put(write_options, "ZZZ", "zzz");
+ db->Put(write_options, "XXX", "xxx");
+
+ txn->SetSnapshot();
+
+ TransactionOptions txn_options;
+ txn_options.set_snapshot = true;
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ txn2->SetSnapshot();
+
+ // This should not conflict in txn since the snapshot is later than the
+ // previous write (spoiler alert: it will later conflict with txn2).
+ s = txn->Put("ZZZ", "zzzz");
+ ASSERT_OK(s);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+
+ // This will conflict since the snapshot is earlier than another write to ZZZ
+ s = txn2->Put("ZZZ", "xxxxx");
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "ZZZ", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "zzzz");
+
+ delete txn2;
+}
+
+TEST_P(TransactionTest, ColumnFamiliesTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ ColumnFamilyHandle *cfa, *cfb;
+ ColumnFamilyOptions cf_options;
+
+ // Create 2 new column families
+ s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = db->CreateColumnFamily(cf_options, "CFB", &cfb);
+ ASSERT_OK(s);
+
+ delete cfa;
+ delete cfb;
+ delete db;
+ db = nullptr;
+
+ // open DB with three column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new column families
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFA", ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFB", ColumnFamilyOptions()));
+
+ std::vector<ColumnFamilyHandle*> handles;
+
+ ASSERT_OK(ReOpenNoDelete(column_families, &handles));
+ assert(db != nullptr);
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn_options.set_snapshot = true;
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ // Write some data to the db
+ WriteBatch batch;
+ batch.Put("foo", "foo");
+ batch.Put(handles[1], "AAA", "bar");
+ batch.Put(handles[1], "AAAZZZ", "bar");
+ s = db->Write(write_options, &batch);
+ ASSERT_OK(s);
+ db->Delete(write_options, handles[1], "AAAZZZ");
+
+ // These keys do not conflict with existing writes since they're in
+ // different column families
+ s = txn->Delete("AAA");
+ ASSERT_OK(s);
+ s = txn->GetForUpdate(snapshot_read_options, handles[1], "foo", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ Slice key_slice("AAAZZZ");
+ Slice value_slices[2] = {Slice("bar"), Slice("bar")};
+ s = txn->Put(handles[2], SliceParts(&key_slice, 1),
+ SliceParts(value_slices, 2));
+ ASSERT_OK(s);
+ ASSERT_EQ(3, txn->GetNumKeys());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ s = db->Get(read_options, "AAA", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(read_options, handles[2], "AAAZZZ", &value);
+ ASSERT_EQ(value, "barbar");
+
+ Slice key_slices[3] = {Slice("AAA"), Slice("ZZ"), Slice("Z")};
+ Slice value_slice("barbarbar");
+
+ s = txn2->Delete(handles[2], "XXX");
+ ASSERT_OK(s);
+ s = txn2->Delete(handles[1], "XXX");
+ ASSERT_OK(s);
+
+ // This write will cause a conflict with the earlier batch write
+ s = txn2->Put(handles[1], SliceParts(key_slices, 3),
+ SliceParts(&value_slice, 1));
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ // In the above the latest change to AAAZZZ in handles[1] is delete.
+ s = db->Get(read_options, handles[1], "AAAZZZ", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+ delete txn2;
+
+ txn = db->BeginTransaction(write_options, txn_options);
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ std::vector<ColumnFamilyHandle*> multiget_cfh = {handles[1], handles[2],
+ handles[0], handles[2]};
+ std::vector<Slice> multiget_keys = {"AAA", "AAAZZZ", "foo", "foo"};
+ std::vector<std::string> values(4);
+ std::vector<Status> results = txn->MultiGetForUpdate(
+ snapshot_read_options, multiget_cfh, multiget_keys, &values);
+ ASSERT_OK(results[0]);
+ ASSERT_OK(results[1]);
+ ASSERT_OK(results[2]);
+ ASSERT_TRUE(results[3].IsNotFound());
+ ASSERT_EQ(values[0], "bar");
+ ASSERT_EQ(values[1], "barbar");
+ ASSERT_EQ(values[2], "foo");
+
+ s = txn->SingleDelete(handles[2], "ZZZ");
+ ASSERT_OK(s);
+ s = txn->Put(handles[2], "ZZZ", "YYY");
+ ASSERT_OK(s);
+ s = txn->Put(handles[2], "ZZZ", "YYYY");
+ ASSERT_OK(s);
+ s = txn->Delete(handles[2], "ZZZ");
+ ASSERT_OK(s);
+ s = txn->Put(handles[2], "AAAZZZ", "barbarbar");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(5, txn->GetNumKeys());
+
+ // Txn should commit
+ s = txn->Commit();
+ ASSERT_OK(s);
+ s = db->Get(read_options, handles[2], "ZZZ", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Put a key which will conflict with the next txn using the previous snapshot
+ db->Put(write_options, handles[2], "foo", "000");
+
+ results = txn2->MultiGetForUpdate(snapshot_read_options, multiget_cfh,
+ multiget_keys, &values);
+ // All results should fail since there was a conflict
+ ASSERT_TRUE(results[0].IsBusy());
+ ASSERT_TRUE(results[1].IsBusy());
+ ASSERT_TRUE(results[2].IsBusy());
+ ASSERT_TRUE(results[3].IsBusy());
+
+ s = db->Get(read_options, handles[2], "foo", &value);
+ ASSERT_EQ(value, "000");
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->DropColumnFamily(handles[1]);
+ ASSERT_OK(s);
+ s = db->DropColumnFamily(handles[2]);
+ ASSERT_OK(s);
+
+ delete txn;
+ delete txn2;
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+}
+
+TEST_P(TransactionTest, MultiGetBatchedTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ ColumnFamilyHandle* cf;
+ ColumnFamilyOptions cf_options;
+
+ // Create a new column families
+ s = db->CreateColumnFamily(cf_options, "CF", &cf);
+ ASSERT_OK(s);
+
+ delete cf;
+ delete db;
+ db = nullptr;
+
+ // open DB with three column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new column families
+ cf_options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ column_families.push_back(ColumnFamilyDescriptor("CF", cf_options));
+
+ std::vector<ColumnFamilyHandle*> handles;
+
+ options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ ASSERT_OK(ReOpenNoDelete(column_families, &handles));
+ assert(db != nullptr);
+
+ // Write some data to the db
+ WriteBatch batch;
+ batch.Put(handles[1], "aaa", "val1");
+ batch.Put(handles[1], "bbb", "val2");
+ batch.Put(handles[1], "ccc", "val3");
+ batch.Put(handles[1], "ddd", "foo");
+ batch.Put(handles[1], "eee", "val5");
+ batch.Put(handles[1], "fff", "val6");
+ batch.Merge(handles[1], "ggg", "foo");
+ s = db->Write(write_options, &batch);
+ ASSERT_OK(s);
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+
+ txn_options.set_snapshot = true;
+ // Write some data to the db
+ s = txn->Delete(handles[1], "bbb");
+ ASSERT_OK(s);
+ s = txn->Put(handles[1], "ccc", "val3_new");
+ ASSERT_OK(s);
+ s = txn->Merge(handles[1], "ddd", "bar");
+ ASSERT_OK(s);
+
+ std::vector<Slice> keys = {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg"};
+ std::vector<PinnableSlice> values(keys.size());
+ std::vector<Status> statuses(keys.size());
+
+ txn->MultiGet(snapshot_read_options, handles[1], keys.size(), keys.data(),
+ values.data(), statuses.data());
+ ASSERT_TRUE(statuses[0].ok());
+ ASSERT_EQ(values[0], "val1");
+ ASSERT_TRUE(statuses[1].IsNotFound());
+ ASSERT_TRUE(statuses[2].ok());
+ ASSERT_EQ(values[2], "val3_new");
+ ASSERT_TRUE(statuses[3].IsMergeInProgress());
+ ASSERT_TRUE(statuses[4].ok());
+ ASSERT_EQ(values[4], "val5");
+ ASSERT_TRUE(statuses[5].ok());
+ ASSERT_EQ(values[5], "val6");
+ ASSERT_TRUE(statuses[6].ok());
+ ASSERT_EQ(values[6], "foo");
+ delete txn;
+ for (auto handle : handles) {
+ delete handle;
+ }
+}
+
+// This test calls WriteBatchWithIndex::MultiGetFromBatchAndDB with a large
+// number of keys, i.e greater than MultiGetContext::MAX_BATCH_SIZE, which is
+// is 32. This forces autovector allocations in the MultiGet code paths
+// to use std::vector in addition to stack allocations. The MultiGet keys
+// includes Merges, which are handled specially in MultiGetFromBatchAndDB by
+// allocating an autovector of MergeContexts
+TEST_P(TransactionTest, MultiGetLargeBatchedTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ string value;
+ Status s;
+
+ ColumnFamilyHandle* cf;
+ ColumnFamilyOptions cf_options;
+
+ std::vector<std::string> key_str;
+ for (int i = 0; i < 100; ++i) {
+ key_str.emplace_back(std::to_string(i));
+ }
+ // Create a new column families
+ s = db->CreateColumnFamily(cf_options, "CF", &cf);
+ ASSERT_OK(s);
+
+ delete cf;
+ delete db;
+ db = nullptr;
+
+ // open DB with three column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new column families
+ cf_options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ column_families.push_back(ColumnFamilyDescriptor("CF", cf_options));
+
+ std::vector<ColumnFamilyHandle*> handles;
+
+ options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ ASSERT_OK(ReOpenNoDelete(column_families, &handles));
+ assert(db != nullptr);
+
+ // Write some data to the db
+ WriteBatch batch;
+ for (int i = 0; i < 3 * MultiGetContext::MAX_BATCH_SIZE; ++i) {
+ std::string val = "val" + std::to_string(i);
+ batch.Put(handles[1], key_str[i], val);
+ }
+ s = db->Write(write_options, &batch);
+ ASSERT_OK(s);
+
+ WriteBatchWithIndex wb;
+ // Write some data to the db
+ s = wb.Delete(handles[1], std::to_string(1));
+ ASSERT_OK(s);
+ s = wb.Put(handles[1], std::to_string(2), "new_val" + std::to_string(2));
+ ASSERT_OK(s);
+ // Write a lot of merges so when we call MultiGetFromBatchAndDB later on,
+ // it is forced to use std::vector in ROCKSDB_NAMESPACE::autovector to
+ // allocate MergeContexts. The number of merges needs to be >
+ // MultiGetContext::MAX_BATCH_SIZE
+ for (int i = 8; i < MultiGetContext::MAX_BATCH_SIZE + 24; ++i) {
+ s = wb.Merge(handles[1], std::to_string(i), "merge");
+ ASSERT_OK(s);
+ }
+
+ // MultiGet a lot of keys in order to force std::vector reallocations
+ std::vector<Slice> keys;
+ for (int i = 0; i < MultiGetContext::MAX_BATCH_SIZE + 32; ++i) {
+ keys.emplace_back(key_str[i]);
+ }
+ std::vector<PinnableSlice> values(keys.size());
+ std::vector<Status> statuses(keys.size());
+
+ wb.MultiGetFromBatchAndDB(db, snapshot_read_options, handles[1], keys.size(), keys.data(),
+ values.data(), statuses.data(), false);
+ for (size_t i =0; i < keys.size(); ++i) {
+ if (i == 1) {
+ ASSERT_TRUE(statuses[1].IsNotFound());
+ } else if (i == 2) {
+ ASSERT_TRUE(statuses[2].ok());
+ ASSERT_EQ(values[2], "new_val" + std::to_string(2));
+ } else if (i >= 8 && i < 56) {
+ ASSERT_TRUE(statuses[i].ok());
+ ASSERT_EQ(values[i], "val" + std::to_string(i) + ",merge");
+ } else {
+ ASSERT_TRUE(statuses[i].ok());
+ if (values[i] != "val" + std::to_string(i)) {
+ ASSERT_EQ(values[i], "val" + std::to_string(i));
+ }
+ }
+ }
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+}
+
+TEST_P(TransactionTest, ColumnFamiliesTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ string value;
+ Status s;
+
+ ColumnFamilyHandle *one, *two;
+ ColumnFamilyOptions cf_options;
+
+ // Create 2 new column families
+ s = db->CreateColumnFamily(cf_options, "ONE", &one);
+ ASSERT_OK(s);
+ s = db->CreateColumnFamily(cf_options, "TWO", &two);
+ ASSERT_OK(s);
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn1);
+ Transaction* txn2 = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn1->Put(one, "X", "1");
+ ASSERT_OK(s);
+ s = txn1->Put(two, "X", "2");
+ ASSERT_OK(s);
+ s = txn1->Put("X", "0");
+ ASSERT_OK(s);
+
+ s = txn2->Put(one, "X", "11");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ // Drop first column family
+ s = db->DropColumnFamily(one);
+ ASSERT_OK(s);
+
+ // Should fail since column family was dropped.
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ txn1 = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn1);
+
+ // Should fail since column family was dropped
+ s = txn1->Put(one, "X", "111");
+ ASSERT_TRUE(s.IsInvalidArgument());
+
+ s = txn1->Put(two, "X", "222");
+ ASSERT_OK(s);
+
+ s = txn1->Put("X", "000");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, two, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("222", value);
+
+ s = db->Get(read_options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("000", value);
+
+ s = db->DropColumnFamily(two);
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+
+ delete one;
+ delete two;
+}
+
+TEST_P(TransactionTest, EmptyTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ string value;
+ Status s;
+
+ s = db->Put(write_options, "aaa", "aaa");
+ ASSERT_OK(s);
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ txn = db->BeginTransaction(write_options);
+ txn->Rollback();
+ delete txn;
+
+ txn = db->BeginTransaction(write_options);
+ s = txn->GetForUpdate(read_options, "aaa", &value);
+ ASSERT_EQ(value, "aaa");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ txn = db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+
+ s = txn->GetForUpdate(read_options, "aaa", &value);
+ ASSERT_EQ(value, "aaa");
+
+ // Conflicts with previous GetForUpdate
+ s = db->Put(write_options, "aaa", "xxx");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ // transaction expired!
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+}
+
+TEST_P(TransactionTest, PredicateManyPreceders) {
+ WriteOptions write_options;
+ ReadOptions read_options1, read_options2;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ txn_options.set_snapshot = true;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ Transaction* txn2 = db->BeginTransaction(write_options);
+ txn2->SetSnapshot();
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ std::vector<Slice> multiget_keys = {"1", "2", "3"};
+ std::vector<std::string> multiget_values;
+
+ std::vector<Status> results =
+ txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
+ ASSERT_TRUE(results[1].IsNotFound());
+
+ s = txn2->Put("2", "x"); // Conflict's with txn1's MultiGetForUpdate
+ ASSERT_TRUE(s.IsTimedOut());
+
+ txn2->Rollback();
+
+ multiget_values.clear();
+ results =
+ txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
+ ASSERT_TRUE(results[1].IsNotFound());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ s = txn1->Put("4", "x");
+ ASSERT_OK(s);
+
+ s = txn2->Delete("4"); // conflict
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->GetForUpdate(read_options2, "4", &value);
+ ASSERT_TRUE(s.IsBusy());
+
+ txn2->Rollback();
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, LostUpdate) {
+ WriteOptions write_options;
+ ReadOptions read_options, read_options1, read_options2;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ // Test 2 transactions writing to the same key in multiple orders and
+ // with/without snapshots
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+ Transaction* txn2 = db->BeginTransaction(write_options);
+
+ s = txn1->Put("1", "1");
+ ASSERT_OK(s);
+
+ s = txn2->Put("1", "2"); // conflict
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("1", value);
+
+ delete txn1;
+ delete txn2;
+
+ txn_options.set_snapshot = true;
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ s = txn1->Put("1", "3");
+ ASSERT_OK(s);
+ s = txn2->Put("1", "4"); // conflict
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("3", value);
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ s = txn1->Put("1", "5");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Put("1", "6");
+ ASSERT_TRUE(s.IsBusy());
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ read_options1.snapshot = txn1->GetSnapshot();
+
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ read_options2.snapshot = txn2->GetSnapshot();
+
+ s = txn1->Put("1", "7");
+ ASSERT_OK(s);
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn2->SetSnapshot();
+ s = txn2->Put("1", "8");
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("8", value);
+
+ delete txn1;
+ delete txn2;
+
+ txn1 = db->BeginTransaction(write_options);
+ txn2 = db->BeginTransaction(write_options);
+
+ s = txn1->Put("1", "9");
+ ASSERT_OK(s);
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Put("1", "10");
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+
+ s = db->Get(read_options, "1", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "10");
+}
+
+TEST_P(TransactionTest, UntrackedWrites) {
+ if (txn_db_options.write_policy == WRITE_UNPREPARED) {
+ // TODO(lth): For WriteUnprepared, validate that untracked writes are
+ // not supported.
+ return;
+ }
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ // Verify transaction rollback works for untracked keys.
+ Transaction* txn = db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+
+ s = txn->PutUntracked("untracked", "0");
+ ASSERT_OK(s);
+ txn->Rollback();
+ s = db->Get(read_options, "untracked", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+ txn = db->BeginTransaction(write_options);
+ txn->SetSnapshot();
+
+ s = db->Put(write_options, "untracked", "x");
+ ASSERT_OK(s);
+
+ // Untracked writes should succeed even though key was written after snapshot
+ s = txn->PutUntracked("untracked", "1");
+ ASSERT_OK(s);
+ s = txn->MergeUntracked("untracked", "2");
+ ASSERT_OK(s);
+ s = txn->DeleteUntracked("untracked");
+ ASSERT_OK(s);
+
+ // Conflict
+ s = txn->Put("untracked", "3");
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "untracked", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, ExpiredTransaction) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ // Set txn expiration timeout to 0 microseconds (expires instantly)
+ txn_options.expiration = 0;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+
+ s = txn1->Put("X", "1");
+ ASSERT_OK(s);
+
+ s = txn1->Put("Y", "1");
+ ASSERT_OK(s);
+
+ Transaction* txn2 = db->BeginTransaction(write_options);
+
+ // txn2 should be able to write to X since txn1 has expired
+ s = txn2->Put("X", "2");
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ s = db->Get(read_options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("2", value);
+
+ s = txn1->Put("Z", "1");
+ ASSERT_OK(s);
+
+ // txn1 should fail to commit since it is expired
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsExpired());
+
+ s = db->Get(read_options, "Y", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, ReinitializeTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ // Set txn expiration timeout to 0 microseconds (expires instantly)
+ txn_options.expiration = 0;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+
+ // Reinitialize transaction to no long expire
+ txn_options.expiration = -1;
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+
+ s = txn1->Put("Z", "z");
+ ASSERT_OK(s);
+
+ // Should commit since not expired
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+
+ s = txn1->Put("Z", "zz");
+ ASSERT_OK(s);
+
+ // Reinitilize txn1 and verify that Z gets unlocked
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options, nullptr);
+ s = txn2->Put("Z", "zzz");
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "zzz");
+
+ // Verify snapshots get reinitialized correctly
+ txn1->SetSnapshot();
+ s = txn1->Put("Z", "zzzz");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "zzzz");
+
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+ const Snapshot* snapshot = txn1->GetSnapshot();
+ ASSERT_FALSE(snapshot);
+
+ txn_options.set_snapshot = true;
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+ snapshot = txn1->GetSnapshot();
+ ASSERT_TRUE(snapshot);
+
+ s = txn1->Put("Z", "a");
+ ASSERT_OK(s);
+
+ txn1->Rollback();
+
+ s = txn1->Put("Y", "y");
+ ASSERT_OK(s);
+
+ txn_options.set_snapshot = false;
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+ snapshot = txn1->GetSnapshot();
+ ASSERT_FALSE(snapshot);
+
+ s = txn1->Put("X", "x");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(value, "zzzz");
+
+ s = db->Get(read_options, "Y", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+
+ s = txn1->SetName("name");
+ ASSERT_OK(s);
+
+ s = txn1->Prepare();
+ ASSERT_OK(s);
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ txn1 = db->BeginTransaction(write_options, txn_options, txn1);
+
+ s = txn1->SetName("name");
+ ASSERT_OK(s);
+
+ delete txn1;
+}
+
+TEST_P(TransactionTest, Rollback) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+
+ ASSERT_OK(s);
+
+ s = txn1->Put("X", "1");
+ ASSERT_OK(s);
+
+ Transaction* txn2 = db->BeginTransaction(write_options);
+
+ // txn2 should not be able to write to X since txn1 has it locked
+ s = txn2->Put("X", "2");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ txn1->Rollback();
+ delete txn1;
+
+ // txn2 should now be able to write to X
+ s = txn2->Put("X", "3");
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("3", value);
+
+ delete txn2;
+}
+
+TEST_P(TransactionTest, LockLimitTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ TransactionOptions txn_options;
+ string value;
+ Status s;
+
+ delete db;
+ db = nullptr;
+
+ // Open DB with a lock limit of 3
+ txn_db_options.max_num_locks = 3;
+ ASSERT_OK(ReOpen());
+ assert(db != nullptr);
+ ASSERT_OK(s);
+
+ // Create a txn and verify we can only lock up to 3 keys
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("X", "x");
+ ASSERT_OK(s);
+
+ s = txn->Put("Y", "y");
+ ASSERT_OK(s);
+
+ s = txn->Put("Z", "z");
+ ASSERT_OK(s);
+
+ // lock limit reached
+ s = txn->Put("W", "w");
+ ASSERT_TRUE(s.IsBusy());
+
+ // re-locking same key shouldn't put us over the limit
+ s = txn->Put("X", "xx");
+ ASSERT_OK(s);
+
+ s = txn->GetForUpdate(read_options, "W", &value);
+ ASSERT_TRUE(s.IsBusy());
+ s = txn->GetForUpdate(read_options, "V", &value);
+ ASSERT_TRUE(s.IsBusy());
+
+ // re-locking same key shouldn't put us over the limit
+ s = txn->GetForUpdate(read_options, "Y", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("y", value);
+
+ s = txn->Get(read_options, "W", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ // "X" currently locked
+ s = txn2->Put("X", "x");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ // lock limit reached
+ s = txn2->Put("M", "m");
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("xx", value);
+
+ s = db->Get(read_options, "W", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Committing txn should release its locks and allow txn2 to proceed
+ s = txn2->Put("X", "x2");
+ ASSERT_OK(s);
+
+ s = txn2->Delete("X");
+ ASSERT_OK(s);
+
+ s = txn2->Put("M", "m");
+ ASSERT_OK(s);
+
+ s = txn2->Put("Z", "z2");
+ ASSERT_OK(s);
+
+ // lock limit reached
+ s = txn2->Delete("Y");
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "Z", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("z2", value);
+
+ s = db->Get(read_options, "Y", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("y", value);
+
+ s = db->Get(read_options, "X", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, IteratorTest) {
+ // This test does writes without snapshot validation, and then tries to create
+ // iterator later, which is unsupported in write unprepared.
+ if (txn_db_options.write_policy == WRITE_UNPREPARED) {
+ return;
+ }
+
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ // Write some keys to the db
+ s = db->Put(write_options, "A", "a");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "G", "g");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "F", "f");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "C", "c");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "D", "d");
+ ASSERT_OK(s);
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ // Write some keys in a txn
+ s = txn->Put("B", "b");
+ ASSERT_OK(s);
+
+ s = txn->Put("H", "h");
+ ASSERT_OK(s);
+
+ s = txn->Delete("D");
+ ASSERT_OK(s);
+
+ s = txn->Put("E", "e");
+ ASSERT_OK(s);
+
+ txn->SetSnapshot();
+ const Snapshot* snapshot = txn->GetSnapshot();
+
+ // Write some keys to the db after the snapshot
+ s = db->Put(write_options, "BB", "xx");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "C", "xx");
+ ASSERT_OK(s);
+
+ read_options.snapshot = snapshot;
+ Iterator* iter = txn->GetIterator(read_options);
+ ASSERT_OK(iter->status());
+ iter->SeekToFirst();
+
+ // Read all keys via iter and lock them all
+ std::string results[] = {"a", "b", "c", "e", "f", "g", "h"};
+ for (int i = 0; i < 7; i++) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(results[i], iter->value().ToString());
+
+ s = txn->GetForUpdate(read_options, iter->key(), nullptr);
+ if (i == 2) {
+ // "C" was modified after txn's snapshot
+ ASSERT_TRUE(s.IsBusy());
+ } else {
+ ASSERT_OK(s);
+ }
+
+ iter->Next();
+ }
+ ASSERT_FALSE(iter->Valid());
+
+ iter->Seek("G");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("g", iter->value().ToString());
+
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("f", iter->value().ToString());
+
+ iter->Seek("D");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("e", iter->value().ToString());
+
+ iter->Seek("C");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("c", iter->value().ToString());
+
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("e", iter->value().ToString());
+
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a", iter->value().ToString());
+
+ iter->Seek("X");
+ ASSERT_OK(iter->status());
+ ASSERT_FALSE(iter->Valid());
+
+ iter->SeekToLast();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("h", iter->value().ToString());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete iter;
+ delete txn;
+}
+
+TEST_P(TransactionTest, DisableIndexingTest) {
+ // Skip this test for write unprepared. It does not solely rely on WBWI for
+ // read your own writes, so depending on whether batches are flushed or not,
+ // only some writes will be visible.
+ //
+ // Also, write unprepared does not support creating iterators if there has
+ // been txn->Put() without snapshot validation.
+ if (txn_db_options.write_policy == WRITE_UNPREPARED) {
+ return;
+ }
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ txn->DisableIndexing();
+
+ s = txn->Put("B", "b");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ Iterator* iter = txn->GetIterator(read_options);
+ ASSERT_OK(iter->status());
+
+ iter->Seek("B");
+ ASSERT_OK(iter->status());
+ ASSERT_FALSE(iter->Valid());
+
+ s = txn->Delete("A");
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ txn->EnableIndexing();
+
+ s = txn->Put("B", "bb");
+ ASSERT_OK(s);
+
+ iter->Seek("B");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("bb", iter->value().ToString());
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("bb", value);
+
+ s = txn->Put("A", "aa");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("aa", value);
+
+ delete iter;
+ delete txn;
+}
+
+TEST_P(TransactionTest, SavepointTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ ASSERT_EQ(0, txn->GetNumPuts());
+
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn->SetSavePoint(); // 1
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to beginning of txn
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("B", "b");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(1, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ delete txn;
+ txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("B", "bb");
+ ASSERT_OK(s);
+
+ s = txn->Put("C", "c");
+ ASSERT_OK(s);
+
+ txn->SetSavePoint(); // 2
+
+ s = txn->Delete("B");
+ ASSERT_OK(s);
+
+ s = txn->Put("C", "cc");
+ ASSERT_OK(s);
+
+ s = txn->Put("D", "d");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(5, txn->GetNumPuts());
+ ASSERT_EQ(1, txn->GetNumDeletes());
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to 2
+
+ ASSERT_EQ(3, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("bb", value);
+
+ s = txn->Get(read_options, "C", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c", value);
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("E", "e");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(5, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ // Rollback to beginning of txn
+ s = txn->RollbackToSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+ txn->Rollback();
+
+ ASSERT_EQ(0, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "E", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Put("A", "aa");
+ ASSERT_OK(s);
+
+ s = txn->Put("F", "f");
+ ASSERT_OK(s);
+
+ ASSERT_EQ(2, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ txn->SetSavePoint(); // 3
+ txn->SetSavePoint(); // 4
+
+ s = txn->Put("G", "g");
+ ASSERT_OK(s);
+
+ s = txn->SingleDelete("F");
+ ASSERT_OK(s);
+
+ s = txn->Delete("B");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("aa", value);
+
+ s = txn->Get(read_options, "F", &value);
+ // According to db.h, doing a SingleDelete on a key that has been
+ // overwritten will have undefinied behavior. So it is unclear what the
+ // result of fetching "F" should be. The current implementation will
+ // return NotFound in this case.
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Get(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ ASSERT_EQ(3, txn->GetNumPuts());
+ ASSERT_EQ(2, txn->GetNumDeletes());
+
+ ASSERT_OK(txn->RollbackToSavePoint()); // Rollback to 3
+
+ ASSERT_EQ(2, txn->GetNumPuts());
+ ASSERT_EQ(0, txn->GetNumDeletes());
+
+ s = txn->Get(read_options, "F", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("f", value);
+
+ s = txn->Get(read_options, "G", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "F", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("f", value);
+
+ s = db->Get(read_options, "G", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("aa", value);
+
+ s = db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = db->Get(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Get(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Get(read_options, "E", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, SavepointTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ Status s;
+
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ s = txn1->Put("A", "");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 1
+
+ s = txn1->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn1->Put("C", "c");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 2
+
+ s = txn1->Put("A", "a");
+ ASSERT_OK(s);
+ s = txn1->Put("B", "b");
+ ASSERT_OK(s);
+
+ ASSERT_OK(txn1->RollbackToSavePoint()); // Rollback to 2
+
+ // Verify that "A" and "C" is still locked while "B" is not
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn2->Put("A", "a2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b2");
+ ASSERT_OK(s);
+
+ s = txn1->Put("A", "aa");
+ ASSERT_OK(s);
+ s = txn1->Put("B", "bb");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = txn1->Put("A", "aaa");
+ ASSERT_OK(s);
+ s = txn1->Put("B", "bbb");
+ ASSERT_OK(s);
+ s = txn1->Put("C", "ccc");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 3
+ ASSERT_OK(txn1->RollbackToSavePoint()); // Rollback to 3
+
+ // Verify that "A", "B", "C" are still locked
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn2->Put("A", "a2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c2");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ ASSERT_OK(txn1->RollbackToSavePoint()); // Rollback to 1
+
+ // Verify that only "A" is locked
+ s = txn2->Put("A", "a3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b3");
+ ASSERT_OK(s);
+ s = txn2->Put("C", "c3po");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ // Verify "A" "C" "B" are no longer locked
+ s = txn2->Put("A", "a4");
+ ASSERT_OK(s);
+ s = txn2->Put("B", "b4");
+ ASSERT_OK(s);
+ s = txn2->Put("C", "c4");
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+}
+
+TEST_P(TransactionTest, SavepointTest3) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ Status s;
+
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ s = txn1->PopSavePoint(); // No SavePoint present
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Put("A", "");
+ ASSERT_OK(s);
+
+ s = txn1->PopSavePoint(); // Still no SavePoint present
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn1->SetSavePoint(); // 1
+
+ s = txn1->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn1->PopSavePoint(); // Remove 1
+ ASSERT_TRUE(txn1->RollbackToSavePoint().IsNotFound());
+
+ // Verify that "A" is still locked
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn2->Put("A", "a2");
+ ASSERT_TRUE(s.IsTimedOut());
+ delete txn2;
+
+ txn1->SetSavePoint(); // 2
+
+ s = txn1->Put("B", "b");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 3
+
+ s = txn1->Put("B", "b2");
+ ASSERT_OK(s);
+
+ ASSERT_OK(txn1->RollbackToSavePoint()); // Roll back to 2
+
+ s = txn1->PopSavePoint();
+ ASSERT_OK(s);
+
+ s = txn1->PopSavePoint();
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ std::string value;
+
+ // tnx1 should have modified "A" to "a"
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ // tnx1 should have set "B" to just "b"
+ s = db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = db->Get(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+}
+
+TEST_P(TransactionTest, SavepointTest4) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ Status s;
+
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ txn1->SetSavePoint(); // 1
+ s = txn1->Put("A", "a");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 2
+ s = txn1->Put("B", "b");
+ ASSERT_OK(s);
+
+ s = txn1->PopSavePoint(); // Remove 2
+ ASSERT_OK(s);
+
+ // Verify that A/B still exists.
+ std::string value;
+ ASSERT_OK(txn1->Get(read_options, "A", &value));
+ ASSERT_EQ("a", value);
+
+ ASSERT_OK(txn1->Get(read_options, "B", &value));
+ ASSERT_EQ("b", value);
+
+ ASSERT_OK(txn1->RollbackToSavePoint()); // Rollback to 1
+
+ // Verify that everything was rolled back.
+ s = txn1->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Get(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Nothing should be locked
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ s = txn2->Put("A", "");
+ ASSERT_OK(s);
+
+ s = txn2->Put("B", "");
+ ASSERT_OK(s);
+
+ delete txn2;
+ delete txn1;
+}
+
+TEST_P(TransactionTest, UndoGetForUpdateTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ txn1->UndoGetForUpdate("A");
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ txn1 = db->BeginTransaction(write_options, txn_options);
+
+ txn1->UndoGetForUpdate("A");
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Verify that A is locked
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ s = txn2->Put("A", "a");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ txn1->UndoGetForUpdate("A");
+
+ // Verify that A is now unlocked
+ s = txn2->Put("A", "a2");
+ ASSERT_OK(s);
+ txn2->Commit();
+ delete txn2;
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a2", value);
+
+ s = txn1->Delete("A");
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Put("B", "b3");
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "B", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+
+ // Verify that A and B are still locked
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ s = txn2->Put("A", "a4");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b4");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ txn1->Rollback();
+ delete txn1;
+
+ // Verify that A and B are no longer locked
+ s = txn2->Put("A", "a5");
+ ASSERT_OK(s);
+ s = txn2->Put("B", "b5");
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ delete txn2;
+ ASSERT_OK(s);
+
+ txn1 = db->BeginTransaction(write_options, txn_options);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn1->GetForUpdate(read_options, "B", &value);
+ ASSERT_OK(s);
+ s = txn1->Put("B", "b5");
+ s = txn1->GetForUpdate(read_options, "B", &value);
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("X");
+
+ // Verify A,B,C are locked
+ txn2 = db->BeginTransaction(write_options, txn_options);
+ s = txn2->Put("A", "a6");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Delete("B");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c6");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("X", "x6");
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("X");
+
+ // Verify A,B are locked and C is not
+ s = txn2->Put("A", "a6");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Delete("B");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c6");
+ ASSERT_OK(s);
+ s = txn2->Put("X", "x6");
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("X");
+
+ // Verify B is locked and A and C are not
+ s = txn2->Put("A", "a7");
+ ASSERT_OK(s);
+ s = txn2->Delete("B");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c7");
+ ASSERT_OK(s);
+ s = txn2->Put("X", "x7");
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+}
+
+TEST_P(TransactionTest, UndoGetForUpdateTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ std::string value;
+ Status s;
+
+ s = db->Put(write_options, "A", "");
+ ASSERT_OK(s);
+
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn1);
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Put("F", "f");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 1
+
+ txn1->UndoGetForUpdate("A");
+
+ s = txn1->GetForUpdate(read_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn1->GetForUpdate(read_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->Put("E", "e");
+ ASSERT_OK(s);
+ s = txn1->GetForUpdate(read_options, "E", &value);
+ ASSERT_OK(s);
+
+ s = txn1->GetForUpdate(read_options, "F", &value);
+ ASSERT_OK(s);
+
+ // Verify A,B,C,D,E,F are still locked
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ s = txn2->Put("A", "a1");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b1");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c1");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("D", "d1");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("E", "e1");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f1");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("E");
+
+ // Verify A,B,D,E,F are still locked and C is not.
+ s = txn2->Put("A", "a2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("D", "d2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("E", "e2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c2");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 2
+
+ s = txn1->Put("H", "h");
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("D");
+ txn1->UndoGetForUpdate("E");
+ txn1->UndoGetForUpdate("F");
+ txn1->UndoGetForUpdate("G");
+ txn1->UndoGetForUpdate("H");
+
+ // Verify A,B,D,E,F,H are still locked and C,G are not.
+ s = txn2->Put("A", "a3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("D", "d3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("E", "e3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("H", "h3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c3");
+ ASSERT_OK(s);
+ s = txn2->Put("G", "g3");
+ ASSERT_OK(s);
+
+ txn1->RollbackToSavePoint(); // rollback to 2
+
+ // Verify A,B,D,E,F are still locked and C,G,H are not.
+ s = txn2->Put("A", "a3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("D", "d3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("E", "e3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c3");
+ ASSERT_OK(s);
+ s = txn2->Put("G", "g3");
+ ASSERT_OK(s);
+ s = txn2->Put("H", "h3");
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("D");
+ txn1->UndoGetForUpdate("E");
+ txn1->UndoGetForUpdate("F");
+ txn1->UndoGetForUpdate("G");
+ txn1->UndoGetForUpdate("H");
+
+ // Verify A,B,E,F are still locked and C,D,G,H are not.
+ s = txn2->Put("A", "a3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("E", "e3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c3");
+ ASSERT_OK(s);
+ s = txn2->Put("D", "d3");
+ ASSERT_OK(s);
+ s = txn2->Put("G", "g3");
+ ASSERT_OK(s);
+ s = txn2->Put("H", "h3");
+ ASSERT_OK(s);
+
+ txn1->RollbackToSavePoint(); // rollback to 1
+
+ // Verify A,B,F are still locked and C,D,E,G,H are not.
+ s = txn2->Put("A", "a3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("B", "b3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("F", "f3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("C", "c3");
+ ASSERT_OK(s);
+ s = txn2->Put("D", "d3");
+ ASSERT_OK(s);
+ s = txn2->Put("E", "e3");
+ ASSERT_OK(s);
+ s = txn2->Put("G", "g3");
+ ASSERT_OK(s);
+ s = txn2->Put("H", "h3");
+ ASSERT_OK(s);
+
+ txn1->UndoGetForUpdate("A");
+ txn1->UndoGetForUpdate("B");
+ txn1->UndoGetForUpdate("C");
+ txn1->UndoGetForUpdate("D");
+ txn1->UndoGetForUpdate("E");
+ txn1->UndoGetForUpdate("F");
+ txn1->UndoGetForUpdate("G");
+ txn1->UndoGetForUpdate("H");
+
+ // Verify F is still locked and A,B,C,D,E,G,H are not.
+ s = txn2->Put("F", "f3");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Put("A", "a3");
+ ASSERT_OK(s);
+ s = txn2->Put("B", "b3");
+ ASSERT_OK(s);
+ s = txn2->Put("C", "c3");
+ ASSERT_OK(s);
+ s = txn2->Put("D", "d3");
+ ASSERT_OK(s);
+ s = txn2->Put("E", "e3");
+ ASSERT_OK(s);
+ s = txn2->Put("G", "g3");
+ ASSERT_OK(s);
+ s = txn2->Put("H", "h3");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, TimeoutTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ delete db;
+ db = nullptr;
+
+ // transaction writes have an infinite timeout,
+ // but we will override this when we start a txn
+ // db writes have infinite timeout
+ txn_db_options.transaction_lock_timeout = -1;
+ txn_db_options.default_lock_timeout = -1;
+
+ s = TransactionDB::Open(options, txn_db_options, dbname, &db);
+ assert(db != nullptr);
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "aaa", "aaa");
+ ASSERT_OK(s);
+
+ TransactionOptions txn_options0;
+ txn_options0.expiration = 100; // 100ms
+ txn_options0.lock_timeout = 50; // txn timeout no longer infinite
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options0);
+
+ s = txn1->GetForUpdate(read_options, "aaa", nullptr);
+ ASSERT_OK(s);
+
+ // Conflicts with previous GetForUpdate.
+ // Since db writes do not have a timeout, this should eventually succeed when
+ // the transaction expires.
+ s = db->Put(write_options, "aaa", "xxx");
+ ASSERT_OK(s);
+
+ ASSERT_GE(txn1->GetElapsedTime(),
+ static_cast<uint64_t>(txn_options0.expiration));
+
+ s = txn1->Commit();
+ ASSERT_TRUE(s.IsExpired()); // expired!
+
+ s = db->Get(read_options, "aaa", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("xxx", value);
+
+ delete txn1;
+ delete db;
+
+ // transaction writes have 10ms timeout,
+ // db writes have infinite timeout
+ txn_db_options.transaction_lock_timeout = 50;
+ txn_db_options.default_lock_timeout = -1;
+
+ s = TransactionDB::Open(options, txn_db_options, dbname, &db);
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "aaa", "aaa");
+ ASSERT_OK(s);
+
+ TransactionOptions txn_options;
+ txn_options.expiration = 100; // 100ms
+ txn1 = db->BeginTransaction(write_options, txn_options);
+
+ s = txn1->GetForUpdate(read_options, "aaa", nullptr);
+ ASSERT_OK(s);
+
+ // Conflicts with previous GetForUpdate.
+ // Since db writes do not have a timeout, this should eventually succeed when
+ // the transaction expires.
+ s = db->Put(write_options, "aaa", "xxx");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_NOK(s); // expired!
+
+ s = db->Get(read_options, "aaa", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("xxx", value);
+
+ delete txn1;
+ txn_options.expiration = 6000000; // 100 minutes
+ txn_options.lock_timeout = 1; // 1ms
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ txn1->SetLockTimeout(100);
+
+ TransactionOptions txn_options2;
+ txn_options2.expiration = 10; // 10ms
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options2);
+ ASSERT_OK(s);
+
+ s = txn2->Put("a", "2");
+ ASSERT_OK(s);
+
+ // txn1 has a lock timeout longer than txn2's expiration, so it will win
+ s = txn1->Delete("a");
+ ASSERT_OK(s);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ // txn2 should be expired out since txn1 waiting until its timeout expired.
+ s = txn2->Commit();
+ ASSERT_TRUE(s.IsExpired());
+
+ delete txn1;
+ delete txn2;
+ txn_options.expiration = 6000000; // 100 minutes
+ txn1 = db->BeginTransaction(write_options, txn_options);
+ txn_options2.expiration = 100000000;
+ txn2 = db->BeginTransaction(write_options, txn_options2);
+
+ s = txn1->Delete("asdf");
+ ASSERT_OK(s);
+
+ // txn2 has a smaller lock timeout than txn1's expiration, so it will time out
+ s = txn2->Delete("asdf");
+ ASSERT_TRUE(s.IsTimedOut());
+ ASSERT_EQ(s.ToString(), "Operation timed out: Timeout waiting to lock key");
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ s = txn2->Put("asdf", "asdf");
+ ASSERT_OK(s);
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+
+ s = db->Get(read_options, "asdf", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("asdf", value);
+
+ delete txn1;
+ delete txn2;
+}
+
+TEST_P(TransactionTest, SingleDeleteTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = txn->SingleDelete("A");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ txn = db->BeginTransaction(write_options);
+
+ s = txn->SingleDelete("A");
+ ASSERT_OK(s);
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ txn = db->BeginTransaction(write_options);
+
+ s = txn->SingleDelete("A");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ s = db->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn = db->BeginTransaction(write_options);
+ Transaction* txn2 = db->BeginTransaction(write_options);
+ txn2->SetSnapshot();
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Put("A", "a2");
+ ASSERT_OK(s);
+
+ s = txn->SingleDelete("A");
+ ASSERT_OK(s);
+
+ s = txn->SingleDelete("B");
+ ASSERT_OK(s);
+
+ // According to db.h, doing a SingleDelete on a key that has been
+ // overwritten will have undefinied behavior. So it is unclear what the
+ // result of fetching "A" should be. The current implementation will
+ // return NotFound in this case.
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn2->Put("B", "b");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ // According to db.h, doing a SingleDelete on a key that has been
+ // overwritten will have undefinied behavior. So it is unclear what the
+ // result of fetching "A" should be. The current implementation will
+ // return NotFound in this case.
+ s = db->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Get(read_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+}
+
+TEST_P(TransactionTest, MergeTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(write_options, TransactionOptions());
+ ASSERT_TRUE(txn);
+
+ s = db->Put(write_options, "A", "a0");
+ ASSERT_OK(s);
+
+ s = txn->Merge("A", "1");
+ ASSERT_OK(s);
+
+ s = txn->Merge("A", "2");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ s = txn->Put("A", "a");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a", value);
+
+ s = txn->Merge("A", "3");
+ ASSERT_OK(s);
+
+ s = txn->Get(read_options, "A", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ TransactionOptions txn_options;
+ txn_options.lock_timeout = 1; // 1 ms
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2);
+
+ // verify that txn has "A" locked
+ s = txn2->Merge("A", "4");
+ ASSERT_TRUE(s.IsTimedOut());
+
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+ delete txn;
+
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a,3", value);
+}
+
+TEST_P(TransactionTest, DeferSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+ Status s;
+
+ s = db->Put(write_options, "A", "a0");
+ ASSERT_OK(s);
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+ Transaction* txn2 = db->BeginTransaction(write_options);
+
+ txn1->SetSnapshotOnNextOperation();
+ auto snapshot = txn1->GetSnapshot();
+ ASSERT_FALSE(snapshot);
+
+ s = txn2->Put("A", "a2");
+ ASSERT_OK(s);
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+
+ s = txn1->GetForUpdate(read_options, "A", &value);
+ // Should not conflict with txn2 since snapshot wasn't set until
+ // GetForUpdate was called.
+ ASSERT_OK(s);
+ ASSERT_EQ("a2", value);
+
+ s = txn1->Put("A", "a1");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "B", "b0");
+ ASSERT_OK(s);
+
+ // Cannot lock B since it was written after the snapshot was set
+ s = txn1->Put("B", "b1");
+ ASSERT_TRUE(s.IsBusy());
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+
+ s = db->Get(read_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a1", value);
+
+ s = db->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b0", value);
+}
+
+TEST_P(TransactionTest, DeferSnapshotTest2) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+
+ txn1->SetSnapshot();
+
+ s = txn1->Put("A", "a1");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "C", "c0");
+ ASSERT_OK(s);
+ s = db->Put(write_options, "D", "d0");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+
+ txn1->SetSnapshotOnNextOperation();
+
+ s = txn1->Get(snapshot_read_options, "C", &value);
+ // Snapshot was set before C was written
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn1->Get(snapshot_read_options, "D", &value);
+ // Snapshot was set before D was written
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Snapshot should not have changed yet.
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+
+ s = txn1->Get(snapshot_read_options, "C", &value);
+ // Snapshot was set before C was written
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn1->Get(snapshot_read_options, "D", &value);
+ // Snapshot was set before D was written
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = txn1->GetForUpdate(read_options, "C", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c0", value);
+
+ s = db->Put(write_options, "D", "d00");
+ ASSERT_OK(s);
+
+ // Snapshot is now set
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ s = txn1->Get(snapshot_read_options, "D", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("d0", value);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+ delete txn1;
+}
+
+TEST_P(TransactionTest, DeferSnapshotSavePointTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+
+ txn1->SetSavePoint(); // 1
+
+ s = db->Put(write_options, "T", "1");
+ ASSERT_OK(s);
+
+ txn1->SetSnapshotOnNextOperation();
+
+ s = db->Put(write_options, "T", "2");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 2
+
+ s = db->Put(write_options, "T", "3");
+ ASSERT_OK(s);
+
+ s = txn1->Put("A", "a");
+ ASSERT_OK(s);
+
+ txn1->SetSavePoint(); // 3
+
+ s = db->Put(write_options, "T", "4");
+ ASSERT_OK(s);
+
+ txn1->SetSnapshot();
+ txn1->SetSnapshotOnNextOperation();
+
+ txn1->SetSavePoint(); // 4
+
+ s = db->Put(write_options, "T", "5");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("4", value);
+
+ s = txn1->Put("A", "a1");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ s = txn1->RollbackToSavePoint(); // Rollback to 4
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("4", value);
+
+ s = txn1->RollbackToSavePoint(); // Rollback to 3
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("3", value);
+
+ s = txn1->Get(read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ s = txn1->RollbackToSavePoint(); // Rollback to 2
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ ASSERT_FALSE(snapshot_read_options.snapshot);
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ s = txn1->Delete("A");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ ASSERT_TRUE(snapshot_read_options.snapshot);
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ s = txn1->RollbackToSavePoint(); // Rollback to 1
+ ASSERT_OK(s);
+
+ s = txn1->Delete("A");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn1->GetSnapshot();
+ ASSERT_FALSE(snapshot_read_options.snapshot);
+ s = txn1->Get(snapshot_read_options, "T", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("5", value);
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+}
+
+TEST_P(TransactionTest, SetSnapshotOnNextOperationWithNotification) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ std::string value;
+
+ class Notifier : public TransactionNotifier {
+ private:
+ const Snapshot** snapshot_ptr_;
+
+ public:
+ explicit Notifier(const Snapshot** snapshot_ptr)
+ : snapshot_ptr_(snapshot_ptr) {}
+
+ void SnapshotCreated(const Snapshot* newSnapshot) override {
+ *snapshot_ptr_ = newSnapshot;
+ }
+ };
+
+ std::shared_ptr<Notifier> notifier =
+ std::make_shared<Notifier>(&read_options.snapshot);
+ Status s;
+
+ s = db->Put(write_options, "B", "0");
+ ASSERT_OK(s);
+
+ Transaction* txn1 = db->BeginTransaction(write_options);
+
+ txn1->SetSnapshotOnNextOperation(notifier);
+ ASSERT_FALSE(read_options.snapshot);
+
+ s = db->Put(write_options, "B", "1");
+ ASSERT_OK(s);
+
+ // A Get does not generate the snapshot
+ s = txn1->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_FALSE(read_options.snapshot);
+ ASSERT_EQ(value, "1");
+
+ // Any other operation does
+ s = txn1->Put("A", "0");
+ ASSERT_OK(s);
+
+ // Now change "B".
+ s = db->Put(write_options, "B", "2");
+ ASSERT_OK(s);
+
+ // The original value should still be read
+ s = txn1->Get(read_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_TRUE(read_options.snapshot);
+ ASSERT_EQ(value, "1");
+
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ delete txn1;
+}
+
+TEST_P(TransactionTest, ClearSnapshotTest) {
+ WriteOptions write_options;
+ ReadOptions read_options, snapshot_read_options;
+ std::string value;
+ Status s;
+
+ s = db->Put(write_options, "foo", "0");
+ ASSERT_OK(s);
+
+ Transaction* txn = db->BeginTransaction(write_options);
+ ASSERT_TRUE(txn);
+
+ s = db->Put(write_options, "foo", "1");
+ ASSERT_OK(s);
+
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+ ASSERT_FALSE(snapshot_read_options.snapshot);
+
+ // No snapshot created yet
+ s = txn->Get(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "1");
+
+ txn->SetSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+ ASSERT_TRUE(snapshot_read_options.snapshot);
+
+ s = db->Put(write_options, "foo", "2");
+ ASSERT_OK(s);
+
+ // Snapshot was created before change to '2'
+ s = txn->Get(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "1");
+
+ txn->ClearSnapshot();
+ snapshot_read_options.snapshot = txn->GetSnapshot();
+ ASSERT_FALSE(snapshot_read_options.snapshot);
+
+ // Snapshot has now been cleared
+ s = txn->Get(snapshot_read_options, "foo", &value);
+ ASSERT_EQ(value, "2");
+
+ s = txn->Commit();
+ ASSERT_OK(s);
+
+ delete txn;
+}
+
+TEST_P(TransactionTest, ToggleAutoCompactionTest) {
+ Status s;
+
+ ColumnFamilyHandle *cfa, *cfb;
+ ColumnFamilyOptions cf_options;
+
+ // Create 2 new column families
+ s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
+ ASSERT_OK(s);
+ s = db->CreateColumnFamily(cf_options, "CFB", &cfb);
+ ASSERT_OK(s);
+
+ delete cfa;
+ delete cfb;
+ delete db;
+
+ // open DB with three column families
+ std::vector<ColumnFamilyDescriptor> column_families;
+ // have to open default column family
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
+ // open the new column families
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFA", ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("CFB", ColumnFamilyOptions()));
+
+ ColumnFamilyOptions* cf_opt_default = &column_families[0].options;
+ ColumnFamilyOptions* cf_opt_cfa = &column_families[1].options;
+ ColumnFamilyOptions* cf_opt_cfb = &column_families[2].options;
+ cf_opt_default->disable_auto_compactions = false;
+ cf_opt_cfa->disable_auto_compactions = true;
+ cf_opt_cfb->disable_auto_compactions = false;
+
+ std::vector<ColumnFamilyHandle*> handles;
+
+ s = TransactionDB::Open(options, txn_db_options, dbname, column_families,
+ &handles, &db);
+ ASSERT_OK(s);
+
+ auto cfh_default = reinterpret_cast<ColumnFamilyHandleImpl*>(handles[0]);
+ auto opt_default = *cfh_default->cfd()->GetLatestMutableCFOptions();
+
+ auto cfh_a = reinterpret_cast<ColumnFamilyHandleImpl*>(handles[1]);
+ auto opt_a = *cfh_a->cfd()->GetLatestMutableCFOptions();
+
+ auto cfh_b = reinterpret_cast<ColumnFamilyHandleImpl*>(handles[2]);
+ auto opt_b = *cfh_b->cfd()->GetLatestMutableCFOptions();
+
+ ASSERT_EQ(opt_default.disable_auto_compactions, false);
+ ASSERT_EQ(opt_a.disable_auto_compactions, true);
+ ASSERT_EQ(opt_b.disable_auto_compactions, false);
+
+ for (auto handle : handles) {
+ delete handle;
+ }
+}
+
+TEST_P(TransactionStressTest, ExpiredTransactionDataRace1) {
+ // In this test, txn1 should succeed committing,
+ // as the callback is called after txn1 starts committing.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"TransactionTest::ExpirableTransactionDataRace:1"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "TransactionTest::ExpirableTransactionDataRace:1", [&](void* /*arg*/) {
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+
+ // Force txn1 to expire
+ /* sleep override */
+ std::this_thread::sleep_for(std::chrono::milliseconds(150));
+
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ Status s;
+ s = txn2->Put("X", "2");
+ ASSERT_TRUE(s.IsTimedOut());
+ s = txn2->Commit();
+ ASSERT_OK(s);
+ delete txn2;
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+
+ txn_options.expiration = 100;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+
+ Status s;
+ s = txn1->Put("X", "1");
+ ASSERT_OK(s);
+ s = txn1->Commit();
+ ASSERT_OK(s);
+
+ ReadOptions read_options;
+ string value;
+ s = db->Get(read_options, "X", &value);
+ ASSERT_EQ("1", value);
+
+ delete txn1;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+namespace {
+// cmt_delay_ms is the delay between prepare and commit
+// first_id is the id of the first transaction
+Status TransactionStressTestInserter(
+ TransactionDB* db, const size_t num_transactions, const size_t num_sets,
+ const size_t num_keys_per_set, Random64* rand,
+ const uint64_t cmt_delay_ms = 0, const uint64_t first_id = 0) {
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ if (rand->OneIn(2)) {
+ txn_options.use_only_the_last_commit_time_batch_for_recovery = true;
+ }
+ // Inside the inserter we might also retake the snapshot. We do both since two
+ // separte functions are engaged for each.
+ txn_options.set_snapshot = rand->OneIn(2);
+
+ RandomTransactionInserter inserter(
+ rand, write_options, read_options, num_keys_per_set,
+ static_cast<uint16_t>(num_sets), cmt_delay_ms, first_id);
+
+ for (size_t t = 0; t < num_transactions; t++) {
+ bool success = inserter.TransactionDBInsert(db, txn_options);
+ if (!success) {
+ // unexpected failure
+ return inserter.GetLastStatus();
+ }
+ }
+
+ // Make sure at least some of the transactions succeeded. It's ok if
+ // some failed due to write-conflicts.
+ if (num_transactions != 1 &&
+ inserter.GetFailureCount() > num_transactions / 2) {
+ return Status::TryAgain("Too many transactions failed! " +
+ std::to_string(inserter.GetFailureCount()) + " / " +
+ std::to_string(num_transactions));
+ }
+
+ return Status::OK();
+}
+} // namespace
+
+// Worker threads add a number to a key from each set of keys. The checker
+// threads verify that the sum of all keys in each set are equal.
+TEST_P(MySQLStyleTransactionTest, TransactionStressTest) {
+ // Small write buffer to trigger more compactions
+ options.write_buffer_size = 1024;
+ ReOpenNoDelete();
+ const size_t num_workers = 4; // worker threads count
+ const size_t num_checkers = 2; // checker threads count
+ const size_t num_slow_checkers = 2; // checker threads emulating backups
+ const size_t num_slow_workers = 1; // slow worker threads count
+ const size_t num_transactions_per_thread = 10000;
+ const uint16_t num_sets = 3;
+ const size_t num_keys_per_set = 100;
+ // Setting the key-space to be 100 keys should cause enough write-conflicts
+ // to make this test interesting.
+
+ std::vector<port::Thread> threads;
+ std::atomic<uint32_t> finished = {0};
+ bool TAKE_SNAPSHOT = true;
+ uint64_t time_seed = env->NowMicros();
+ printf("time_seed is %" PRIu64 "\n", time_seed); // would help to reproduce
+
+ std::function<void()> call_inserter = [&] {
+ size_t thd_seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 rand(time_seed * thd_seed);
+ ASSERT_OK(TransactionStressTestInserter(db, num_transactions_per_thread,
+ num_sets, num_keys_per_set, &rand));
+ finished++;
+ };
+ std::function<void()> call_checker = [&] {
+ size_t thd_seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 rand(time_seed * thd_seed);
+ // Verify that data is consistent
+ while (finished < num_workers) {
+ Status s = RandomTransactionInserter::Verify(
+ db, num_sets, num_keys_per_set, TAKE_SNAPSHOT, &rand);
+ ASSERT_OK(s);
+ }
+ };
+ std::function<void()> call_slow_checker = [&] {
+ size_t thd_seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 rand(time_seed * thd_seed);
+ // Verify that data is consistent
+ while (finished < num_workers) {
+ uint64_t delay_ms = rand.Uniform(100) + 1;
+ Status s = RandomTransactionInserter::Verify(
+ db, num_sets, num_keys_per_set, TAKE_SNAPSHOT, &rand, delay_ms);
+ ASSERT_OK(s);
+ }
+ };
+ std::function<void()> call_slow_inserter = [&] {
+ size_t thd_seed = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 rand(time_seed * thd_seed);
+ uint64_t id = 0;
+ // Verify that data is consistent
+ while (finished < num_workers) {
+ uint64_t delay_ms = rand.Uniform(500) + 1;
+ ASSERT_OK(TransactionStressTestInserter(db, 1, num_sets, num_keys_per_set,
+ &rand, delay_ms, id++));
+ }
+ };
+
+ for (uint32_t i = 0; i < num_workers; i++) {
+ threads.emplace_back(call_inserter);
+ }
+ for (uint32_t i = 0; i < num_checkers; i++) {
+ threads.emplace_back(call_checker);
+ }
+ if (with_slow_threads_) {
+ for (uint32_t i = 0; i < num_slow_checkers; i++) {
+ threads.emplace_back(call_slow_checker);
+ }
+ for (uint32_t i = 0; i < num_slow_workers; i++) {
+ threads.emplace_back(call_slow_inserter);
+ }
+ }
+
+ // Wait for all threads to finish
+ for (auto& t : threads) {
+ t.join();
+ }
+
+ // Verify that data is consistent
+ Status s = RandomTransactionInserter::Verify(db, num_sets, num_keys_per_set,
+ !TAKE_SNAPSHOT);
+ ASSERT_OK(s);
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(TransactionTest, MemoryLimitTest) {
+ TransactionOptions txn_options;
+ // Header (12 bytes) + NOOP (1 byte) + 2 * 8 bytes for data.
+ txn_options.max_write_batch_size = 29;
+ // Set threshold to unlimited so that the write batch does not get flushed,
+ // and can hit the memory limit.
+ txn_options.write_batch_flush_threshold = 0;
+ std::string value;
+ Status s;
+
+ Transaction* txn = db->BeginTransaction(WriteOptions(), txn_options);
+ ASSERT_TRUE(txn);
+
+ ASSERT_EQ(0, txn->GetNumPuts());
+ ASSERT_LE(0, txn->GetID());
+
+ s = txn->Put(Slice("a"), Slice("...."));
+ ASSERT_OK(s);
+ ASSERT_EQ(1, txn->GetNumPuts());
+
+ s = txn->Put(Slice("b"), Slice("...."));
+ ASSERT_OK(s);
+ ASSERT_EQ(2, txn->GetNumPuts());
+
+ s = txn->Put(Slice("b"), Slice("...."));
+ ASSERT_TRUE(s.IsMemoryLimit());
+ ASSERT_EQ(2, txn->GetNumPuts());
+
+ txn->Rollback();
+ delete txn;
+}
+
+// This test clarifies the existing expectation from the sequence number
+// algorithm. It could detect mistakes in updating the code but it is not
+// necessarily the one acceptable way. If the algorithm is legitimately changed,
+// this unit test should be updated as well.
+TEST_P(TransactionStressTest, SeqAdvanceTest) {
+ // TODO(myabandeh): must be test with false before new releases
+ const bool short_test = true;
+ WriteOptions wopts;
+ FlushOptions fopt;
+
+ options.disable_auto_compactions = true;
+ ASSERT_OK(ReOpen());
+
+ // Do the test with NUM_BRANCHES branches in it. Each run of a test takes some
+ // of the branches. This is the same as counting a binary number where i-th
+ // bit represents whether we take branch i in the represented by the number.
+ const size_t NUM_BRANCHES = short_test ? 6 : 10;
+ // Helper function that shows if the branch is to be taken in the run
+ // represented by the number n.
+ auto branch_do = [&](size_t n, size_t* branch) {
+ assert(*branch < NUM_BRANCHES);
+ const size_t filter = static_cast<size_t>(1) << *branch;
+ return n & filter;
+ };
+ const size_t max_n = static_cast<size_t>(1) << NUM_BRANCHES;
+ for (size_t n = 0; n < max_n; n++) {
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ size_t branch = 0;
+ auto seq = db_impl->GetLatestSequenceNumber();
+ exp_seq = seq;
+ txn_t0(0);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ if (branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->Flush(fopt));
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ if (!short_test && branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->FlushWAL(true));
+ ASSERT_OK(ReOpenNoDelete());
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_EQ(exp_seq, seq);
+ }
+
+ // Doing it twice might detect some bugs
+ txn_t0(1);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ txn_t1(0);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ if (branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->Flush(fopt));
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ if (!short_test && branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->FlushWAL(true));
+ ASSERT_OK(ReOpenNoDelete());
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_EQ(exp_seq, seq);
+ }
+
+ txn_t3(0);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ if (branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->Flush(fopt));
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ if (!short_test && branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->FlushWAL(true));
+ ASSERT_OK(ReOpenNoDelete());
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_EQ(exp_seq, seq);
+ }
+
+ txn_t4(0);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+
+ ASSERT_EQ(exp_seq, seq);
+
+ if (branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->Flush(fopt));
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ if (!short_test && branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->FlushWAL(true));
+ ASSERT_OK(ReOpenNoDelete());
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_EQ(exp_seq, seq);
+ }
+
+ txn_t2(0);
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ if (branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->Flush(fopt));
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ if (!short_test && branch_do(n, &branch)) {
+ ASSERT_OK(db_impl->FlushWAL(true));
+ ASSERT_OK(ReOpenNoDelete());
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_EQ(exp_seq, seq);
+ }
+ ASSERT_OK(ReOpen());
+ }
+}
+
+// Verify that the optimization would not compromize the correctness
+TEST_P(TransactionTest, Optimizations) {
+ size_t comb_cnt = size_t(1) << 2; // 2 is number of optimization vars
+ for (size_t new_comb = 0; new_comb < comb_cnt; new_comb++) {
+ TransactionDBWriteOptimizations optimizations;
+ optimizations.skip_concurrency_control = IsInCombination(0, new_comb);
+ optimizations.skip_duplicate_key_check = IsInCombination(1, new_comb);
+
+ ASSERT_OK(ReOpen());
+ WriteOptions write_options;
+ WriteBatch batch;
+ batch.Put(Slice("k"), Slice("v1"));
+ ASSERT_OK(db->Write(write_options, &batch));
+
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ ASSERT_OK(db->Get(ropt, db->DefaultColumnFamily(), "k", &pinnable_val));
+ ASSERT_TRUE(pinnable_val == ("v1"));
+ }
+}
+
+// A comparator that uses only the first three bytes
+class ThreeBytewiseComparator : public Comparator {
+ public:
+ ThreeBytewiseComparator() {}
+ const char* Name() const override { return "test.ThreeBytewiseComparator"; }
+ int Compare(const Slice& a, const Slice& b) const override {
+ Slice na = Slice(a.data(), a.size() < 3 ? a.size() : 3);
+ Slice nb = Slice(b.data(), b.size() < 3 ? b.size() : 3);
+ return na.compare(nb);
+ }
+ bool Equal(const Slice& a, const Slice& b) const override {
+ Slice na = Slice(a.data(), a.size() < 3 ? a.size() : 3);
+ Slice nb = Slice(b.data(), b.size() < 3 ? b.size() : 3);
+ return na == nb;
+ }
+ // This methods below dont seem relevant to this test. Implement them if
+ // proven othersize.
+ void FindShortestSeparator(std::string* start,
+ const Slice& limit) const override {
+ const Comparator* bytewise_comp = BytewiseComparator();
+ bytewise_comp->FindShortestSeparator(start, limit);
+ }
+ void FindShortSuccessor(std::string* key) const override {
+ const Comparator* bytewise_comp = BytewiseComparator();
+ bytewise_comp->FindShortSuccessor(key);
+ }
+};
+
+#ifndef ROCKSDB_VALGRIND_RUN
+TEST_P(TransactionTest, GetWithoutSnapshot) {
+ WriteOptions write_options;
+ std::atomic<bool> finish = {false};
+ db->Put(write_options, "key", "value");
+ ROCKSDB_NAMESPACE::port::Thread commit_thread([&]() {
+ for (int i = 0; i < 100; i++) {
+ TransactionOptions txn_options;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put("key", "overridedvalue"));
+ ASSERT_OK(txn->Put("key", "value"));
+ ASSERT_OK(txn->Prepare());
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ }
+ finish = true;
+ });
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ while (!finish) {
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ ASSERT_OK(db->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val));
+ ASSERT_TRUE(pinnable_val == ("value"));
+ }
+ });
+ commit_thread.join();
+ read_thread.join();
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+// Test that the transactional db can handle duplicate keys in the write batch
+TEST_P(TransactionTest, DuplicateKeys) {
+ ColumnFamilyOptions cf_options;
+ std::string cf_name = "two";
+ ColumnFamilyHandle* cf_handle = nullptr;
+ {
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+ WriteOptions write_options;
+ WriteBatch batch;
+ batch.Put(Slice("key"), Slice("value"));
+ batch.Put(Slice("key2"), Slice("value2"));
+ // duplicate the keys
+ batch.Put(Slice("key"), Slice("value3"));
+ // duplicate the 2nd key. It should not be counted duplicate since a
+ // sub-patch is cut after the last duplicate.
+ batch.Put(Slice("key2"), Slice("value4"));
+ // duplicate the keys but in a different cf. It should not be counted as
+ // duplicate keys
+ batch.Put(cf_handle, Slice("key"), Slice("value5"));
+
+ ASSERT_OK(db->Write(write_options, &batch));
+
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ auto s = db->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("value3"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "key2", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("value4"));
+ s = db->Get(ropt, cf_handle, "key", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("value5"));
+
+ delete cf_handle;
+ }
+
+ // Test with non-bytewise comparator
+ {
+ ASSERT_OK(ReOpen());
+ std::unique_ptr<const Comparator> comp_gc(new ThreeBytewiseComparator());
+ cf_options.comparator = comp_gc.get();
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+ WriteOptions write_options;
+ WriteBatch batch;
+ batch.Put(cf_handle, Slice("key"), Slice("value"));
+ // The first three bytes are the same, do it must be counted as duplicate
+ batch.Put(cf_handle, Slice("key2"), Slice("value2"));
+ // check for 2nd duplicate key in cf with non-default comparator
+ batch.Put(cf_handle, Slice("key2b"), Slice("value2b"));
+ ASSERT_OK(db->Write(write_options, &batch));
+
+ // The value must be the most recent value for all the keys equal to "key",
+ // including "key2"
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ ASSERT_OK(db->Get(ropt, cf_handle, "key", &pinnable_val));
+ ASSERT_TRUE(pinnable_val == ("value2b"));
+
+ // Test duplicate keys with rollback
+ TransactionOptions txn_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(cf_handle, Slice("key3"), Slice("value3")));
+ ASSERT_OK(txn0->Merge(cf_handle, Slice("key4"), Slice("value4")));
+ ASSERT_OK(txn0->Rollback());
+ ASSERT_OK(db->Get(ropt, cf_handle, "key5", &pinnable_val));
+ ASSERT_TRUE(pinnable_val == ("value2b"));
+ delete txn0;
+
+ delete cf_handle;
+ cf_options.comparator = BytewiseComparator();
+ }
+
+ for (bool do_prepare : {true, false}) {
+ for (bool do_rollback : {true, false}) {
+ for (bool with_commit_batch : {true, false}) {
+ if (with_commit_batch && !do_prepare) {
+ continue;
+ }
+ if (with_commit_batch && do_rollback) {
+ continue;
+ }
+ ASSERT_OK(ReOpen());
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+ TransactionOptions txn_options;
+ txn_options.use_only_the_last_commit_time_batch_for_recovery = false;
+ WriteOptions write_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ auto s = txn0->SetName("xid");
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo0"), Slice("bar0a"));
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo0"), Slice("bar0b"));
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo1"), Slice("bar1"));
+ ASSERT_OK(s);
+ s = txn0->Merge(Slice("foo2"), Slice("bar2a"));
+ ASSERT_OK(s);
+ // Repeat a key after the start of a sub-patch. This should not cause a
+ // duplicate in the most recent sub-patch and hence not creating a new
+ // sub-patch.
+ s = txn0->Put(Slice("foo0"), Slice("bar0c"));
+ ASSERT_OK(s);
+ s = txn0->Merge(Slice("foo2"), Slice("bar2b"));
+ ASSERT_OK(s);
+ // duplicate the keys but in a different cf. It should not be counted as
+ // duplicate.
+ s = txn0->Put(cf_handle, Slice("foo0"), Slice("bar0-cf1"));
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo3"), Slice("bar3"));
+ ASSERT_OK(s);
+ s = txn0->Merge(Slice("foo3"), Slice("bar3"));
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo4"), Slice("bar4"));
+ ASSERT_OK(s);
+ s = txn0->Delete(Slice("foo4"));
+ ASSERT_OK(s);
+ s = txn0->SingleDelete(Slice("foo4"));
+ ASSERT_OK(s);
+ if (do_prepare) {
+ s = txn0->Prepare();
+ ASSERT_OK(s);
+ }
+ if (do_rollback) {
+ // Test rolling back the batch with duplicates
+ s = txn0->Rollback();
+ ASSERT_OK(s);
+ } else {
+ if (with_commit_batch) {
+ assert(do_prepare);
+ auto cb = txn0->GetCommitTimeWriteBatch();
+ // duplicate a key in the original batch
+ // TODO(myabandeh): the behavior of GetCommitTimeWriteBatch
+ // conflicting with the prepared batch is currently undefined and
+ // gives different results in different implementations.
+
+ // s = cb->Put(Slice("foo0"), Slice("bar0d"));
+ // ASSERT_OK(s);
+ // add a new duplicate key
+ s = cb->Put(Slice("foo6"), Slice("bar6a"));
+ ASSERT_OK(s);
+ s = cb->Put(Slice("foo6"), Slice("bar6b"));
+ ASSERT_OK(s);
+ // add a duplicate key that is removed in the same batch
+ s = cb->Put(Slice("foo7"), Slice("bar7a"));
+ ASSERT_OK(s);
+ s = cb->Delete(Slice("foo7"));
+ ASSERT_OK(s);
+ }
+ s = txn0->Commit();
+ ASSERT_OK(s);
+ }
+ delete txn0;
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+
+ if (do_rollback) {
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(ropt, cf_handle, "foo0", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo1", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo2", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo3", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo4", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ } else {
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0c"));
+ s = db->Get(ropt, cf_handle, "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0-cf1"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo1", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo2", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar2a,bar2b"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo3", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar3,bar3"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo4", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ if (with_commit_batch) {
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo6", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar6b"));
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo7", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ }
+ }
+ delete cf_handle;
+ } // with_commit_batch
+ } // do_rollback
+ } // do_prepare
+
+ if (!options.unordered_write) {
+ // Also test with max_successive_merges > 0. max_successive_merges will not
+ // affect our algorithm for duplicate key insertion but we add the test to
+ // verify that.
+ cf_options.max_successive_merges = 2;
+ cf_options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ ASSERT_OK(ReOpen());
+ db->CreateColumnFamily(cf_options, cf_name, &cf_handle);
+ WriteOptions write_options;
+ // Ensure one value for the key
+ ASSERT_OK(db->Put(write_options, cf_handle, Slice("key"), Slice("value")));
+ WriteBatch batch;
+ // Merge more than max_successive_merges times
+ batch.Merge(cf_handle, Slice("key"), Slice("1"));
+ batch.Merge(cf_handle, Slice("key"), Slice("2"));
+ batch.Merge(cf_handle, Slice("key"), Slice("3"));
+ batch.Merge(cf_handle, Slice("key"), Slice("4"));
+ ASSERT_OK(db->Write(write_options, &batch));
+ ReadOptions read_options;
+ string value;
+ ASSERT_OK(db->Get(read_options, cf_handle, "key", &value));
+ ASSERT_EQ(value, "value,1,2,3,4");
+ delete cf_handle;
+ }
+
+ {
+ // Test that the duplicate detection is not compromised after rolling back
+ // to a save point
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0a")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0b")));
+ txn0->SetSavePoint();
+ ASSERT_OK(txn0->RollbackToSavePoint());
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ }
+
+ // Test sucessfull recovery after a crash
+ {
+ ASSERT_OK(ReOpen());
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ ReadOptions ropt;
+ Transaction* txn0;
+ PinnableSlice pinnable_val;
+ Status s;
+
+ std::unique_ptr<const Comparator> comp_gc(new ThreeBytewiseComparator());
+ cf_options.comparator = comp_gc.get();
+ cf_options.merge_operator = MergeOperators::CreateStringAppendOperator();
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+ delete cf_handle;
+ std::vector<ColumnFamilyDescriptor> cfds{
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName,
+ ColumnFamilyOptions(options)),
+ ColumnFamilyDescriptor(cf_name, cf_options),
+ };
+ std::vector<ColumnFamilyHandle*> handles;
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+
+ ASSERT_OK(db->Put(write_options, "foo0", "init"));
+ ASSERT_OK(db->Put(write_options, "foo1", "init"));
+ ASSERT_OK(db->Put(write_options, handles[1], "foo0", "init"));
+ ASSERT_OK(db->Put(write_options, handles[1], "foo1", "init"));
+
+ // one entry
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0a")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ ASSERT_OK(db->FlushWAL(true));
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0a"));
+
+ // two entries, no duplicate
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(handles[1], Slice("foo0"), Slice("bar0b")));
+ ASSERT_OK(txn0->Put(handles[1], Slice("fol1"), Slice("bar1b")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0b")));
+ ASSERT_OK(txn0->Put(Slice("foo1"), Slice("bar1b")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ db->FlushWAL(true);
+ // Flush only cf 1
+ reinterpret_cast<DBImpl*>(db->GetRootDB())
+ ->TEST_FlushMemTable(true, false, handles[1]);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0b"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo1", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1b"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0b"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "fol1", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1b"));
+
+ // one duplicate with ::Put
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(handles[1], Slice("key-nonkey0"), Slice("bar0c")));
+ ASSERT_OK(txn0->Put(handles[1], Slice("key-nonkey1"), Slice("bar1d")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0c")));
+ ASSERT_OK(txn0->Put(Slice("foo1"), Slice("bar1c")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0d")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ ASSERT_OK(db->FlushWAL(true));
+ // Flush only cf 1
+ reinterpret_cast<DBImpl*>(db->GetRootDB())
+ ->TEST_FlushMemTable(true, false, handles[1]);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0d"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo1", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1c"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "key-nonkey2", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1d"));
+
+ // Duplicate with ::Put, ::Delete
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(handles[1], Slice("key-nonkey0"), Slice("bar0e")));
+ ASSERT_OK(txn0->Delete(handles[1], Slice("key-nonkey1")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0e")));
+ ASSERT_OK(txn0->Delete(Slice("foo0")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ ASSERT_OK(db->FlushWAL(true));
+ // Flush only cf 1
+ reinterpret_cast<DBImpl*>(db->GetRootDB())
+ ->TEST_FlushMemTable(true, false, handles[1]);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "key-nonkey2", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Duplicate with ::Put, ::SingleDelete
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(handles[1], Slice("key-nonkey0"), Slice("bar0g")));
+ ASSERT_OK(txn0->SingleDelete(handles[1], Slice("key-nonkey1")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0e")));
+ ASSERT_OK(txn0->SingleDelete(Slice("foo0")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ ASSERT_OK(db->FlushWAL(true));
+ // Flush only cf 1
+ reinterpret_cast<DBImpl*>(db->GetRootDB())
+ ->TEST_FlushMemTable(true, false, handles[1]);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "key-nonkey2", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Duplicate with ::Put, ::Merge
+ txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(handles[1], Slice("key-nonkey0"), Slice("bar1i")));
+ ASSERT_OK(txn0->Merge(handles[1], Slice("key-nonkey1"), Slice("bar1j")));
+ ASSERT_OK(txn0->Put(Slice("foo0"), Slice("bar0f")));
+ ASSERT_OK(txn0->Merge(Slice("foo0"), Slice("bar0g")));
+ ASSERT_OK(txn0->Prepare());
+ delete txn0;
+ // This will check the asserts inside recovery code
+ ASSERT_OK(db->FlushWAL(true));
+ // Flush only cf 1
+ reinterpret_cast<DBImpl*>(db->GetRootDB())
+ ->TEST_FlushMemTable(true, false, handles[1]);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ ASSERT_OK(ReOpenNoDelete(cfds, &handles));
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn0 != nullptr);
+ ASSERT_OK(txn0->Commit());
+ delete txn0;
+ pinnable_val.Reset();
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar0f,bar0g"));
+ pinnable_val.Reset();
+ s = db->Get(ropt, handles[1], "key-nonkey2", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == ("bar1i,bar1j"));
+
+ for (auto h : handles) {
+ delete h;
+ }
+ delete db;
+ db = nullptr;
+ }
+}
+
+// Test that the reseek optimization in iterators will not result in an infinite
+// loop if there are too many uncommitted entries before the snapshot.
+TEST_P(TransactionTest, ReseekOptimization) {
+ WriteOptions write_options;
+ write_options.sync = true;
+ write_options.disableWAL = false;
+ ColumnFamilyDescriptor cfd;
+ db->DefaultColumnFamily()->GetDescriptor(&cfd);
+ auto max_skip = cfd.options.max_sequential_skip_in_iterations;
+
+ ASSERT_OK(db->Put(write_options, Slice("foo0"), Slice("initv")));
+
+ TransactionOptions txn_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ // Duplicate keys will result into separate sequence numbers in WritePrepared
+ // and WriteUnPrepared
+ for (size_t i = 0; i < 2 * max_skip; i++) {
+ ASSERT_OK(txn0->Put(Slice("foo1"), Slice("bar")));
+ }
+ ASSERT_OK(txn0->Prepare());
+ ASSERT_OK(db->Put(write_options, Slice("foo2"), Slice("initv")));
+
+ ReadOptions read_options;
+ // To avoid loops
+ read_options.max_skippable_internal_keys = 10 * max_skip;
+ Iterator* iter = db->NewIterator(read_options);
+ ASSERT_OK(iter->status());
+ size_t cnt = 0;
+ iter->SeekToFirst();
+ while (iter->Valid()) {
+ iter->Next();
+ ASSERT_OK(iter->status());
+ cnt++;
+ }
+ ASSERT_EQ(cnt, 2);
+ cnt = 0;
+ iter->SeekToLast();
+ while (iter->Valid()) {
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ cnt++;
+ }
+ ASSERT_EQ(cnt, 2);
+ delete iter;
+ txn0->Rollback();
+ delete txn0;
+}
+
+// After recovery in kPointInTimeRecovery mode, the corrupted log file remains
+// there. The new log files should be still read succesfully during recovery of
+// the 2nd crash.
+TEST_P(TransactionTest, DoubleCrashInRecovery) {
+ for (const bool manual_wal_flush : {false, true}) {
+ for (const bool write_after_recovery : {false, true}) {
+ options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
+ options.manual_wal_flush = manual_wal_flush;
+ ReOpen();
+ std::string cf_name = "two";
+ ColumnFamilyOptions cf_options;
+ ColumnFamilyHandle* cf_handle = nullptr;
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+
+ // Add a prepare entry to prevent the older logs from being deleted.
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("foo-prepare"), Slice("bar-prepare")));
+ ASSERT_OK(txn->Prepare());
+
+ FlushOptions flush_ops;
+ db->Flush(flush_ops);
+ // Now we have a log that cannot be deleted
+
+ ASSERT_OK(db->Put(write_options, cf_handle, "foo1", "bar1"));
+ // Flush only the 2nd cf
+ db->Flush(flush_ops, cf_handle);
+
+ // The value is large enough to be touched by the corruption we ingest
+ // below.
+ std::string large_value(400, ' ');
+ // key/value not touched by corruption
+ ASSERT_OK(db->Put(write_options, "foo2", "bar2"));
+ // key/value touched by corruption
+ ASSERT_OK(db->Put(write_options, "foo3", large_value));
+ // key/value not touched by corruption
+ ASSERT_OK(db->Put(write_options, "foo4", "bar4"));
+
+ db->FlushWAL(true);
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ uint64_t wal_file_id = db_impl->TEST_LogfileNumber();
+ std::string fname = LogFileName(dbname, wal_file_id);
+ reinterpret_cast<PessimisticTransactionDB*>(db)->TEST_Crash();
+ delete txn;
+ delete cf_handle;
+ delete db;
+ db = nullptr;
+
+ // Corrupt the last log file in the middle, so that it is not corrupted
+ // in the tail.
+ std::string file_content;
+ ASSERT_OK(ReadFileToString(env, fname, &file_content));
+ file_content[400] = 'h';
+ file_content[401] = 'a';
+ ASSERT_OK(env->DeleteFile(fname));
+ ASSERT_OK(WriteStringToFile(env, file_content, fname, true));
+
+ // Recover from corruption
+ std::vector<ColumnFamilyHandle*> handles;
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(ColumnFamilyDescriptor(kDefaultColumnFamilyName,
+ ColumnFamilyOptions()));
+ column_families.push_back(
+ ColumnFamilyDescriptor("two", ColumnFamilyOptions()));
+ ASSERT_OK(ReOpenNoDelete(column_families, &handles));
+
+ if (write_after_recovery) {
+ // Write data to the log right after the corrupted log
+ ASSERT_OK(db->Put(write_options, "foo5", large_value));
+ }
+
+ // Persist data written to WAL during recovery or by the last Put
+ db->FlushWAL(true);
+ // 2nd crash to recover while having a valid log after the corrupted one.
+ ASSERT_OK(ReOpenNoDelete(column_families, &handles));
+ assert(db != nullptr);
+ txn = db->GetTransactionByName("xid");
+ ASSERT_TRUE(txn != nullptr);
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ for (auto handle : handles) {
+ delete handle;
+ }
+ }
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as Transactions are not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.h b/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.h
new file mode 100644
index 00000000..2e533d37
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_test.h
@@ -0,0 +1,517 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 <algorithm>
+#include <cinttypes>
+#include <functional>
+#include <string>
+#include <thread>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "table/mock_table.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "test_util/transaction_test_util.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/string_append/stringappend.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+#include "utilities/transactions/write_unprepared_txn_db.h"
+
+#include "port/port.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Return true if the ith bit is set in combination represented by comb
+bool IsInCombination(size_t i, size_t comb) { return comb & (size_t(1) << i); }
+
+enum WriteOrdering : bool { kOrderedWrite, kUnorderedWrite };
+
+class TransactionTestBase : public ::testing::Test {
+ public:
+ TransactionDB* db;
+ FaultInjectionTestEnv* env;
+ std::string dbname;
+ Options options;
+
+ TransactionDBOptions txn_db_options;
+ bool use_stackable_db_;
+
+ TransactionTestBase(bool use_stackable_db, bool two_write_queue,
+ TxnDBWritePolicy write_policy,
+ WriteOrdering write_ordering)
+ : db(nullptr), env(nullptr), use_stackable_db_(use_stackable_db) {
+ options.create_if_missing = true;
+ options.max_write_buffer_number = 2;
+ options.write_buffer_size = 4 * 1024;
+ options.unordered_write = write_ordering == kUnorderedWrite;
+ options.level0_file_num_compaction_trigger = 2;
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+ env = new FaultInjectionTestEnv(Env::Default());
+ options.env = env;
+ options.two_write_queues = two_write_queue;
+ dbname = test::PerThreadDBPath("transaction_testdb");
+
+ DestroyDB(dbname, options);
+ txn_db_options.transaction_lock_timeout = 0;
+ txn_db_options.default_lock_timeout = 0;
+ txn_db_options.write_policy = write_policy;
+ txn_db_options.rollback_merge_operands = true;
+ // This will stress write unprepared, by forcing write batch flush on every
+ // write.
+ txn_db_options.default_write_batch_flush_threshold = 1;
+ // Write unprepared requires all transactions to be named. This setting
+ // autogenerates the name so that existing tests can pass.
+ txn_db_options.autogenerate_name = true;
+ Status s;
+ if (use_stackable_db == false) {
+ s = TransactionDB::Open(options, txn_db_options, dbname, &db);
+ } else {
+ s = OpenWithStackableDB();
+ }
+ assert(s.ok());
+ }
+
+ ~TransactionTestBase() {
+ delete db;
+ db = nullptr;
+ // This is to skip the assert statement in FaultInjectionTestEnv. There
+ // seems to be a bug in btrfs that the makes readdir return recently
+ // unlink-ed files. By using the default fs we simply ignore errors resulted
+ // from attempting to delete such files in DestroyDB.
+ options.env = Env::Default();
+ DestroyDB(dbname, options);
+ delete env;
+ }
+
+ Status ReOpenNoDelete() {
+ delete db;
+ db = nullptr;
+ env->AssertNoOpenFile();
+ env->DropUnsyncedFileData();
+ env->ResetState();
+ Status s;
+ if (use_stackable_db_ == false) {
+ s = TransactionDB::Open(options, txn_db_options, dbname, &db);
+ } else {
+ s = OpenWithStackableDB();
+ }
+ assert(!s.ok() || db != nullptr);
+ return s;
+ }
+
+ Status ReOpenNoDelete(std::vector<ColumnFamilyDescriptor>& cfs,
+ std::vector<ColumnFamilyHandle*>* handles) {
+ for (auto h : *handles) {
+ delete h;
+ }
+ handles->clear();
+ delete db;
+ db = nullptr;
+ env->AssertNoOpenFile();
+ env->DropUnsyncedFileData();
+ env->ResetState();
+ Status s;
+ if (use_stackable_db_ == false) {
+ s = TransactionDB::Open(options, txn_db_options, dbname, cfs, handles,
+ &db);
+ } else {
+ s = OpenWithStackableDB(cfs, handles);
+ }
+ assert(!s.ok() || db != nullptr);
+ return s;
+ }
+
+ Status ReOpen() {
+ delete db;
+ db = nullptr;
+ DestroyDB(dbname, options);
+ Status s;
+ if (use_stackable_db_ == false) {
+ s = TransactionDB::Open(options, txn_db_options, dbname, &db);
+ } else {
+ s = OpenWithStackableDB();
+ }
+ assert(db != nullptr);
+ return s;
+ }
+
+ Status OpenWithStackableDB(std::vector<ColumnFamilyDescriptor>& cfs,
+ std::vector<ColumnFamilyHandle*>* handles) {
+ std::vector<size_t> compaction_enabled_cf_indices;
+ TransactionDB::PrepareWrap(&options, &cfs, &compaction_enabled_cf_indices);
+ DB* root_db = nullptr;
+ Options options_copy(options);
+ const bool use_seq_per_batch =
+ txn_db_options.write_policy == WRITE_PREPARED ||
+ txn_db_options.write_policy == WRITE_UNPREPARED;
+ const bool use_batch_per_txn =
+ txn_db_options.write_policy == WRITE_COMMITTED ||
+ txn_db_options.write_policy == WRITE_PREPARED;
+ Status s = DBImpl::Open(options_copy, dbname, cfs, handles, &root_db,
+ use_seq_per_batch, use_batch_per_txn);
+ StackableDB* stackable_db = new StackableDB(root_db);
+ if (s.ok()) {
+ assert(root_db != nullptr);
+ s = TransactionDB::WrapStackableDB(stackable_db, txn_db_options,
+ compaction_enabled_cf_indices,
+ *handles, &db);
+ }
+ if (!s.ok()) {
+ delete stackable_db;
+ }
+ return s;
+ }
+
+ Status OpenWithStackableDB() {
+ std::vector<size_t> compaction_enabled_cf_indices;
+ std::vector<ColumnFamilyDescriptor> column_families{ColumnFamilyDescriptor(
+ kDefaultColumnFamilyName, ColumnFamilyOptions(options))};
+
+ TransactionDB::PrepareWrap(&options, &column_families,
+ &compaction_enabled_cf_indices);
+ std::vector<ColumnFamilyHandle*> handles;
+ DB* root_db = nullptr;
+ Options options_copy(options);
+ const bool use_seq_per_batch =
+ txn_db_options.write_policy == WRITE_PREPARED ||
+ txn_db_options.write_policy == WRITE_UNPREPARED;
+ const bool use_batch_per_txn =
+ txn_db_options.write_policy == WRITE_COMMITTED ||
+ txn_db_options.write_policy == WRITE_PREPARED;
+ Status s = DBImpl::Open(options_copy, dbname, column_families, &handles,
+ &root_db, use_seq_per_batch, use_batch_per_txn);
+ if (!s.ok()) {
+ delete root_db;
+ return s;
+ }
+ StackableDB* stackable_db = new StackableDB(root_db);
+ assert(root_db != nullptr);
+ assert(handles.size() == 1);
+ s = TransactionDB::WrapStackableDB(stackable_db, txn_db_options,
+ compaction_enabled_cf_indices, handles,
+ &db);
+ delete handles[0];
+ if (!s.ok()) {
+ delete stackable_db;
+ }
+ return s;
+ }
+
+ std::atomic<size_t> linked = {0};
+ std::atomic<size_t> exp_seq = {0};
+ std::atomic<size_t> commit_writes = {0};
+ std::atomic<size_t> expected_commits = {0};
+ // Without Prepare, the commit does not write to WAL
+ std::atomic<size_t> with_empty_commits = {0};
+ std::function<void(size_t, Status)> txn_t0_with_status = [&](size_t index,
+ Status exp_s) {
+ // Test DB's internal txn. It involves no prepare phase nor a commit marker.
+ WriteOptions wopts;
+ auto s = db->Put(wopts, "key" + std::to_string(index), "value");
+ ASSERT_EQ(exp_s, s);
+ if (txn_db_options.write_policy == TxnDBWritePolicy::WRITE_COMMITTED) {
+ // Consume one seq per key
+ exp_seq++;
+ } else {
+ // Consume one seq per batch
+ exp_seq++;
+ if (options.two_write_queues) {
+ // Consume one seq for commit
+ exp_seq++;
+ }
+ }
+ with_empty_commits++;
+ };
+ std::function<void(size_t)> txn_t0 = [&](size_t index) {
+ return txn_t0_with_status(index, Status::OK());
+ };
+ std::function<void(size_t)> txn_t1 = [&](size_t index) {
+ // Testing directly writing a write batch. Functionality-wise it is
+ // equivalent to commit without prepare.
+ WriteBatch wb;
+ auto istr = std::to_string(index);
+ ASSERT_OK(wb.Put("k1" + istr, "v1"));
+ ASSERT_OK(wb.Put("k2" + istr, "v2"));
+ ASSERT_OK(wb.Put("k3" + istr, "v3"));
+ WriteOptions wopts;
+ auto s = db->Write(wopts, &wb);
+ if (txn_db_options.write_policy == TxnDBWritePolicy::WRITE_COMMITTED) {
+ // Consume one seq per key
+ exp_seq += 3;
+ } else {
+ // Consume one seq per batch
+ exp_seq++;
+ if (options.two_write_queues) {
+ // Consume one seq for commit
+ exp_seq++;
+ }
+ }
+ ASSERT_OK(s);
+ with_empty_commits++;
+ };
+ std::function<void(size_t)> txn_t2 = [&](size_t index) {
+ // Commit without prepare. It should write to DB without a commit marker.
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ auto istr = std::to_string(index);
+ ASSERT_OK(txn->SetName("xid" + istr));
+ ASSERT_OK(txn->Put(Slice("foo" + istr), Slice("bar")));
+ ASSERT_OK(txn->Put(Slice("foo2" + istr), Slice("bar2")));
+ ASSERT_OK(txn->Put(Slice("foo3" + istr), Slice("bar3")));
+ ASSERT_OK(txn->Put(Slice("foo4" + istr), Slice("bar4")));
+ ASSERT_OK(txn->Commit());
+ if (txn_db_options.write_policy == TxnDBWritePolicy::WRITE_COMMITTED) {
+ // Consume one seq per key
+ exp_seq += 4;
+ } else if (txn_db_options.write_policy ==
+ TxnDBWritePolicy::WRITE_PREPARED) {
+ // Consume one seq per batch
+ exp_seq++;
+ if (options.two_write_queues) {
+ // Consume one seq for commit
+ exp_seq++;
+ }
+ } else {
+ // Flushed after each key, consume one seq per flushed batch
+ exp_seq += 4;
+ // WriteUnprepared implements CommitWithoutPrepareInternal by simply
+ // calling Prepare then Commit. Consume one seq for the prepare.
+ exp_seq++;
+ }
+ delete txn;
+ with_empty_commits++;
+ };
+ std::function<void(size_t)> txn_t3 = [&](size_t index) {
+ // A full 2pc txn that also involves a commit marker.
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ auto istr = std::to_string(index);
+ ASSERT_OK(txn->SetName("xid" + istr));
+ ASSERT_OK(txn->Put(Slice("foo" + istr), Slice("bar")));
+ ASSERT_OK(txn->Put(Slice("foo2" + istr), Slice("bar2")));
+ ASSERT_OK(txn->Put(Slice("foo3" + istr), Slice("bar3")));
+ ASSERT_OK(txn->Put(Slice("foo4" + istr), Slice("bar4")));
+ ASSERT_OK(txn->Put(Slice("foo5" + istr), Slice("bar5")));
+ expected_commits++;
+ ASSERT_OK(txn->Prepare());
+ commit_writes++;
+ ASSERT_OK(txn->Commit());
+ if (txn_db_options.write_policy == TxnDBWritePolicy::WRITE_COMMITTED) {
+ // Consume one seq per key
+ exp_seq += 5;
+ } else if (txn_db_options.write_policy ==
+ TxnDBWritePolicy::WRITE_PREPARED) {
+ // Consume one seq per batch
+ exp_seq++;
+ // Consume one seq per commit marker
+ exp_seq++;
+ } else {
+ // Flushed after each key, consume one seq per flushed batch
+ exp_seq += 5;
+ // Consume one seq per commit marker
+ exp_seq++;
+ }
+ delete txn;
+ };
+ std::function<void(size_t)> txn_t4 = [&](size_t index) {
+ // A full 2pc txn that also involves a commit marker.
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ auto istr = std::to_string(index);
+ ASSERT_OK(txn->SetName("xid" + istr));
+ ASSERT_OK(txn->Put(Slice("foo" + istr), Slice("bar")));
+ ASSERT_OK(txn->Put(Slice("foo2" + istr), Slice("bar2")));
+ ASSERT_OK(txn->Put(Slice("foo3" + istr), Slice("bar3")));
+ ASSERT_OK(txn->Put(Slice("foo4" + istr), Slice("bar4")));
+ ASSERT_OK(txn->Put(Slice("foo5" + istr), Slice("bar5")));
+ expected_commits++;
+ ASSERT_OK(txn->Prepare());
+ commit_writes++;
+ ASSERT_OK(txn->Rollback());
+ if (txn_db_options.write_policy == TxnDBWritePolicy::WRITE_COMMITTED) {
+ // No seq is consumed for deleting the txn buffer
+ exp_seq += 0;
+ } else if (txn_db_options.write_policy ==
+ TxnDBWritePolicy::WRITE_PREPARED) {
+ // Consume one seq per batch
+ exp_seq++;
+ // Consume one seq per rollback batch
+ exp_seq++;
+ if (options.two_write_queues) {
+ // Consume one seq for rollback commit
+ exp_seq++;
+ }
+ } else {
+ // Flushed after each key, consume one seq per flushed batch
+ exp_seq += 5;
+ // Consume one seq per rollback batch
+ exp_seq++;
+ if (options.two_write_queues) {
+ // Consume one seq for rollback commit
+ exp_seq++;
+ }
+ }
+ delete txn;
+ };
+
+ // Test that we can change write policy after a clean shutdown (which would
+ // empty the WAL)
+ void CrossCompatibilityTest(TxnDBWritePolicy from_policy,
+ TxnDBWritePolicy to_policy, bool empty_wal) {
+ TransactionOptions txn_options;
+ ReadOptions read_options;
+ WriteOptions write_options;
+ uint32_t index = 0;
+ Random rnd(1103);
+ options.write_buffer_size = 1024; // To create more sst files
+ std::unordered_map<std::string, std::string> committed_kvs;
+ Transaction* txn;
+
+ txn_db_options.write_policy = from_policy;
+ if (txn_db_options.write_policy == WRITE_COMMITTED) {
+ options.unordered_write = false;
+ }
+ ReOpen();
+
+ for (int i = 0; i < 1024; i++) {
+ auto istr = std::to_string(index);
+ auto k = Slice("foo-" + istr).ToString();
+ auto v = Slice("bar-" + istr).ToString();
+ // For test the duplicate keys
+ auto v2 = Slice("bar2-" + istr).ToString();
+ auto type = rnd.Uniform(4);
+ switch (type) {
+ case 0:
+ committed_kvs[k] = v;
+ ASSERT_OK(db->Put(write_options, k, v));
+ committed_kvs[k] = v2;
+ ASSERT_OK(db->Put(write_options, k, v2));
+ break;
+ case 1: {
+ WriteBatch wb;
+ committed_kvs[k] = v;
+ wb.Put(k, v);
+ committed_kvs[k] = v2;
+ wb.Put(k, v2);
+ ASSERT_OK(db->Write(write_options, &wb));
+
+ } break;
+ case 2:
+ case 3:
+ txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName("xid" + istr));
+ committed_kvs[k] = v;
+ ASSERT_OK(txn->Put(k, v));
+ committed_kvs[k] = v2;
+ ASSERT_OK(txn->Put(k, v2));
+
+ if (type == 3) {
+ ASSERT_OK(txn->Prepare());
+ }
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ break;
+ default:
+ assert(0);
+ }
+
+ index++;
+ } // for i
+
+ txn_db_options.write_policy = to_policy;
+ if (txn_db_options.write_policy == WRITE_COMMITTED) {
+ options.unordered_write = false;
+ }
+ auto db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ // Before upgrade/downgrade the WAL must be emptied
+ if (empty_wal) {
+ db_impl->TEST_FlushMemTable();
+ } else {
+ db_impl->FlushWAL(true);
+ }
+ auto s = ReOpenNoDelete();
+ if (empty_wal) {
+ ASSERT_OK(s);
+ } else {
+ // Test that we can detect the WAL that is produced by an incompatible
+ // WritePolicy and fail fast before mis-interpreting the WAL.
+ ASSERT_TRUE(s.IsNotSupported());
+ return;
+ }
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ // Check that WAL is empty
+ VectorLogPtr log_files;
+ db_impl->GetSortedWalFiles(log_files);
+ ASSERT_EQ(0, log_files.size());
+
+ for (auto& kv : committed_kvs) {
+ std::string value;
+ s = db->Get(read_options, kv.first, &value);
+ if (s.IsNotFound()) {
+ printf("key = %s\n", kv.first.c_str());
+ }
+ ASSERT_OK(s);
+ if (kv.second != value) {
+ printf("key = %s\n", kv.first.c_str());
+ }
+ ASSERT_EQ(kv.second, value);
+ }
+ }
+};
+
+class TransactionTest
+ : public TransactionTestBase,
+ virtual public ::testing::WithParamInterface<
+ std::tuple<bool, bool, TxnDBWritePolicy, WriteOrdering>> {
+ public:
+ TransactionTest()
+ : TransactionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam())){};
+};
+
+class TransactionStressTest : public TransactionTest {};
+
+class MySQLStyleTransactionTest
+ : public TransactionTestBase,
+ virtual public ::testing::WithParamInterface<
+ std::tuple<bool, bool, TxnDBWritePolicy, WriteOrdering, bool>> {
+ public:
+ MySQLStyleTransactionTest()
+ : TransactionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam())),
+ with_slow_threads_(std::get<4>(GetParam())) {
+ if (with_slow_threads_ &&
+ (txn_db_options.write_policy == WRITE_PREPARED ||
+ txn_db_options.write_policy == WRITE_UNPREPARED)) {
+ // The corner case with slow threads involves the caches filling
+ // over which would not happen even with artifial delays. To help
+ // such cases to show up we lower the size of the cache-related data
+ // structures.
+ txn_db_options.wp_snapshot_cache_bits = 1;
+ txn_db_options.wp_commit_cache_bits = 10;
+ options.write_buffer_size = 1024;
+ EXPECT_OK(ReOpen());
+ }
+ };
+
+ protected:
+ // Also emulate slow threads by addin artiftial delays
+ const bool with_slow_threads_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.cc b/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.cc
new file mode 100644
index 00000000..23532ae4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_util.h"
+
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status TransactionUtil::CheckKeyForConflicts(
+ DBImpl* db_impl, ColumnFamilyHandle* column_family, const std::string& key,
+ SequenceNumber snap_seq, bool cache_only, ReadCallback* snap_checker,
+ SequenceNumber min_uncommitted) {
+ Status result;
+
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ auto cfd = cfh->cfd();
+ SuperVersion* sv = db_impl->GetAndRefSuperVersion(cfd);
+
+ if (sv == nullptr) {
+ result = Status::InvalidArgument("Could not access column family " +
+ cfh->GetName());
+ }
+
+ if (result.ok()) {
+ SequenceNumber earliest_seq =
+ db_impl->GetEarliestMemTableSequenceNumber(sv, true);
+
+ result = CheckKey(db_impl, sv, earliest_seq, snap_seq, key, cache_only,
+ snap_checker, min_uncommitted);
+
+ db_impl->ReturnAndCleanupSuperVersion(cfd, sv);
+ }
+
+ return result;
+}
+
+Status TransactionUtil::CheckKey(DBImpl* db_impl, SuperVersion* sv,
+ SequenceNumber earliest_seq,
+ SequenceNumber snap_seq,
+ const std::string& key, bool cache_only,
+ ReadCallback* snap_checker,
+ SequenceNumber min_uncommitted) {
+ // When `min_uncommitted` is provided, keys are not always committed
+ // in sequence number order, and `snap_checker` is used to check whether
+ // specific sequence number is in the database is visible to the transaction.
+ // So `snap_checker` must be provided.
+ assert(min_uncommitted == kMaxSequenceNumber || snap_checker != nullptr);
+
+ Status result;
+ bool need_to_read_sst = false;
+
+ // Since it would be too slow to check the SST files, we will only use
+ // the memtables to check whether there have been any recent writes
+ // to this key after it was accessed in this transaction. But if the
+ // Memtables do not contain a long enough history, we must fail the
+ // transaction.
+ if (earliest_seq == kMaxSequenceNumber) {
+ // The age of this memtable is unknown. Cannot rely on it to check
+ // for recent writes. This error shouldn't happen often in practice as
+ // the Memtable should have a valid earliest sequence number except in some
+ // corner cases (such as error cases during recovery).
+ need_to_read_sst = true;
+
+ if (cache_only) {
+ result = Status::TryAgain(
+ "Transaction could not check for conflicts as the MemTable does not "
+ "contain a long enough history to check write at SequenceNumber: ",
+ ToString(snap_seq));
+ }
+ } else if (snap_seq < earliest_seq || min_uncommitted <= earliest_seq) {
+ // Use <= for min_uncommitted since earliest_seq is actually the largest sec
+ // before this memtable was created
+ need_to_read_sst = true;
+
+ if (cache_only) {
+ // The age of this memtable is too new to use to check for recent
+ // writes.
+ char msg[300];
+ snprintf(msg, sizeof(msg),
+ "Transaction could not check for conflicts for operation at "
+ "SequenceNumber %" PRIu64
+ " as the MemTable only contains changes newer than "
+ "SequenceNumber %" PRIu64
+ ". Increasing the value of the "
+ "max_write_buffer_size_to_maintain option could reduce the "
+ "frequency "
+ "of this error.",
+ snap_seq, earliest_seq);
+ result = Status::TryAgain(msg);
+ }
+ }
+
+ if (result.ok()) {
+ SequenceNumber seq = kMaxSequenceNumber;
+ bool found_record_for_key = false;
+
+ // When min_uncommitted == kMaxSequenceNumber, writes are committed in
+ // sequence number order, so only keys larger than `snap_seq` can cause
+ // conflict.
+ // When min_uncommitted != kMaxSequenceNumber, keys lower than
+ // min_uncommitted will not triggered conflicts, while keys larger than
+ // min_uncommitted might create conflicts, so we need to read them out
+ // from the DB, and call callback to snap_checker to determine. So only
+ // keys lower than min_uncommitted can be skipped.
+ SequenceNumber lower_bound_seq =
+ (min_uncommitted == kMaxSequenceNumber) ? snap_seq : min_uncommitted;
+ Status s = db_impl->GetLatestSequenceForKey(sv, key, !need_to_read_sst,
+ lower_bound_seq, &seq,
+ &found_record_for_key);
+
+ if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
+ result = s;
+ } else if (found_record_for_key) {
+ bool write_conflict = snap_checker == nullptr
+ ? snap_seq < seq
+ : !snap_checker->IsVisible(seq);
+ if (write_conflict) {
+ result = Status::Busy();
+ }
+ }
+ }
+
+ return result;
+}
+
+Status TransactionUtil::CheckKeysForConflicts(DBImpl* db_impl,
+ const TransactionKeyMap& key_map,
+ bool cache_only) {
+ Status result;
+
+ for (auto& key_map_iter : key_map) {
+ uint32_t cf_id = key_map_iter.first;
+ const auto& keys = key_map_iter.second;
+
+ SuperVersion* sv = db_impl->GetAndRefSuperVersion(cf_id);
+ if (sv == nullptr) {
+ result = Status::InvalidArgument("Could not access column family " +
+ ToString(cf_id));
+ break;
+ }
+
+ SequenceNumber earliest_seq =
+ db_impl->GetEarliestMemTableSequenceNumber(sv, true);
+
+ // For each of the keys in this transaction, check to see if someone has
+ // written to this key since the start of the transaction.
+ for (const auto& key_iter : keys) {
+ const auto& key = key_iter.first;
+ const SequenceNumber key_seq = key_iter.second.seq;
+
+ result = CheckKey(db_impl, sv, earliest_seq, key_seq, key, cache_only);
+
+ if (!result.ok()) {
+ break;
+ }
+ }
+
+ db_impl->ReturnAndCleanupSuperVersion(cf_id, sv);
+
+ if (!result.ok()) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.h b/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.h
new file mode 100644
index 00000000..2e48f84a
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/transaction_util.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <string>
+#include <unordered_map>
+
+#include "db/dbformat.h"
+#include "db/read_callback.h"
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct TransactionKeyMapInfo {
+ // Earliest sequence number that is relevant to this transaction for this key
+ SequenceNumber seq;
+
+ uint32_t num_writes;
+ uint32_t num_reads;
+
+ bool exclusive;
+
+ explicit TransactionKeyMapInfo(SequenceNumber seq_no)
+ : seq(seq_no), num_writes(0), num_reads(0), exclusive(false) {}
+
+ // Used in PopSavePoint to collapse two savepoints together.
+ void Merge(const TransactionKeyMapInfo& info) {
+ assert(seq <= info.seq);
+ num_reads += info.num_reads;
+ num_writes += info.num_writes;
+ exclusive |= info.exclusive;
+ }
+};
+
+using TransactionKeyMap =
+ std::unordered_map<uint32_t,
+ std::unordered_map<std::string, TransactionKeyMapInfo>>;
+
+class DBImpl;
+struct SuperVersion;
+class WriteBatchWithIndex;
+
+class TransactionUtil {
+ public:
+ // Verifies there have been no commits to this key in the db since this
+ // sequence number.
+ //
+ // If cache_only is true, then this function will not attempt to read any
+ // SST files. This will make it more likely this function will
+ // return an error if it is unable to determine if there are any conflicts.
+ //
+ // See comment of CheckKey() for explanation of `snap_seq`, `snap_checker`
+ // and `min_uncommitted`.
+ //
+ // Returns OK on success, BUSY if there is a conflicting write, or other error
+ // status for any unexpected errors.
+ static Status CheckKeyForConflicts(
+ DBImpl* db_impl, ColumnFamilyHandle* column_family,
+ const std::string& key, SequenceNumber snap_seq, bool cache_only,
+ ReadCallback* snap_checker = nullptr,
+ SequenceNumber min_uncommitted = kMaxSequenceNumber);
+
+ // For each key,SequenceNumber pair in the TransactionKeyMap, this function
+ // will verify there have been no writes to the key in the db since that
+ // sequence number.
+ //
+ // Returns OK on success, BUSY if there is a conflicting write, or other error
+ // status for any unexpected errors.
+ //
+ // REQUIRED: this function should only be called on the write thread or if the
+ // mutex is held.
+ static Status CheckKeysForConflicts(DBImpl* db_impl,
+ const TransactionKeyMap& keys,
+ bool cache_only);
+
+ private:
+ // If `snap_checker` == nullptr, writes are always commited in sequence number
+ // order. All sequence number <= `snap_seq` will not conflict with any
+ // write, and all keys > `snap_seq` of `key` will trigger conflict.
+ // If `snap_checker` != nullptr, writes may not commit in sequence number
+ // order. In this case `min_uncommitted` is a lower bound.
+ // seq < `min_uncommitted`: no conflict
+ // seq > `snap_seq`: applicable to conflict
+ // `min_uncommitted` <= seq <= `snap_seq`: call `snap_checker` to determine.
+ static Status CheckKey(DBImpl* db_impl, SuperVersion* sv,
+ SequenceNumber earliest_seq, SequenceNumber snap_seq,
+ const std::string& key, bool cache_only,
+ ReadCallback* snap_checker = nullptr,
+ SequenceNumber min_uncommitted = kMaxSequenceNumber);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_transaction_test.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_transaction_test.cc
new file mode 100644
index 00000000..0171b971
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_transaction_test.cc
@@ -0,0 +1,3524 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_test.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cinttypes>
+#include <functional>
+#include <string>
+#include <thread>
+
+#include "db/db_impl/db_impl.h"
+#include "db/dbformat.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/debug.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "table/mock_table.h"
+#include "test_util/fault_injection_test_env.h"
+#include "test_util/sync_point.h"
+#include "test_util/testharness.h"
+#include "test_util/testutil.h"
+#include "test_util/transaction_test_util.h"
+#include "util/mutexlock.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/string_append/stringappend.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+#include "utilities/transactions/write_prepared_txn_db.h"
+
+#include "port/port.h"
+
+using std::string;
+
+namespace ROCKSDB_NAMESPACE {
+
+using CommitEntry = WritePreparedTxnDB::CommitEntry;
+using CommitEntry64b = WritePreparedTxnDB::CommitEntry64b;
+using CommitEntry64bFormat = WritePreparedTxnDB::CommitEntry64bFormat;
+
+TEST(PreparedHeap, BasicsTest) {
+ WritePreparedTxnDB::PreparedHeap heap;
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(14l);
+ // Test with one element
+ ASSERT_EQ(14l, heap.top());
+ heap.push(24l);
+ heap.push(34l);
+ // Test that old min is still on top
+ ASSERT_EQ(14l, heap.top());
+ heap.push(44l);
+ heap.push(54l);
+ heap.push(64l);
+ heap.push(74l);
+ heap.push(84l);
+ }
+ // Test that old min is still on top
+ ASSERT_EQ(14l, heap.top());
+ heap.erase(24l);
+ // Test that old min is still on top
+ ASSERT_EQ(14l, heap.top());
+ heap.erase(14l);
+ // Test that the new comes to the top after multiple erase
+ ASSERT_EQ(34l, heap.top());
+ heap.erase(34l);
+ // Test that the new comes to the top after single erase
+ ASSERT_EQ(44l, heap.top());
+ heap.erase(54l);
+ ASSERT_EQ(44l, heap.top());
+ heap.pop(); // pop 44l
+ // Test that the erased items are ignored after pop
+ ASSERT_EQ(64l, heap.top());
+ heap.erase(44l);
+ // Test that erasing an already popped item would work
+ ASSERT_EQ(64l, heap.top());
+ heap.erase(84l);
+ ASSERT_EQ(64l, heap.top());
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(85l);
+ heap.push(86l);
+ heap.push(87l);
+ heap.push(88l);
+ heap.push(89l);
+ }
+ heap.erase(87l);
+ heap.erase(85l);
+ heap.erase(89l);
+ heap.erase(86l);
+ heap.erase(88l);
+ // Test top remains the same after a random order of many erases
+ ASSERT_EQ(64l, heap.top());
+ heap.pop();
+ // Test that pop works with a series of random pending erases
+ ASSERT_EQ(74l, heap.top());
+ ASSERT_FALSE(heap.empty());
+ heap.pop();
+ // Test that empty works
+ ASSERT_TRUE(heap.empty());
+}
+
+// This is a scenario reconstructed from a buggy trace. Test that the bug does
+// not resurface again.
+TEST(PreparedHeap, EmptyAtTheEnd) {
+ WritePreparedTxnDB::PreparedHeap heap;
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(40l);
+ }
+ ASSERT_EQ(40l, heap.top());
+ // Although not a recommended scenario, we must be resilient against erase
+ // without a prior push.
+ heap.erase(50l);
+ ASSERT_EQ(40l, heap.top());
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(60l);
+ }
+ ASSERT_EQ(40l, heap.top());
+
+ heap.erase(60l);
+ ASSERT_EQ(40l, heap.top());
+ heap.erase(40l);
+ ASSERT_TRUE(heap.empty());
+
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(40l);
+ }
+ ASSERT_EQ(40l, heap.top());
+ heap.erase(50l);
+ ASSERT_EQ(40l, heap.top());
+ {
+ MutexLock ml(heap.push_pop_mutex());
+ heap.push(60l);
+ }
+ ASSERT_EQ(40l, heap.top());
+
+ heap.erase(40l);
+ // Test that the erase has not emptied the heap (we had a bug doing that)
+ ASSERT_FALSE(heap.empty());
+ ASSERT_EQ(60l, heap.top());
+ heap.erase(60l);
+ ASSERT_TRUE(heap.empty());
+}
+
+// Generate random order of PreparedHeap access and test that the heap will be
+// successfully emptied at the end.
+TEST(PreparedHeap, Concurrent) {
+ const size_t t_cnt = 10;
+ ROCKSDB_NAMESPACE::port::Thread t[t_cnt + 1];
+ WritePreparedTxnDB::PreparedHeap heap;
+ port::RWMutex prepared_mutex;
+ std::atomic<size_t> last;
+
+ for (size_t n = 0; n < 100; n++) {
+ last = 0;
+ t[0] = ROCKSDB_NAMESPACE::port::Thread([&]() {
+ Random rnd(1103);
+ for (size_t seq = 1; seq <= t_cnt; seq++) {
+ // This is not recommended usage but we should be resilient against it.
+ bool skip_push = rnd.OneIn(5);
+ if (!skip_push) {
+ MutexLock ml(heap.push_pop_mutex());
+ std::this_thread::yield();
+ heap.push(seq);
+ last.store(seq);
+ }
+ }
+ });
+ for (size_t i = 1; i <= t_cnt; i++) {
+ t[i] =
+ ROCKSDB_NAMESPACE::port::Thread([&heap, &prepared_mutex, &last, i]() {
+ auto seq = i;
+ do {
+ std::this_thread::yield();
+ } while (last.load() < seq);
+ WriteLock wl(&prepared_mutex);
+ heap.erase(seq);
+ });
+ }
+ for (size_t i = 0; i <= t_cnt; i++) {
+ t[i].join();
+ }
+ ASSERT_TRUE(heap.empty());
+ }
+}
+
+// Test that WriteBatchWithIndex correctly counts the number of sub-batches
+TEST(WriteBatchWithIndex, SubBatchCnt) {
+ ColumnFamilyOptions cf_options;
+ std::string cf_name = "two";
+ DB* db;
+ Options options;
+ options.create_if_missing = true;
+ const std::string dbname = test::PerThreadDBPath("transaction_testdb");
+ DestroyDB(dbname, options);
+ ASSERT_OK(DB::Open(options, dbname, &db));
+ ColumnFamilyHandle* cf_handle = nullptr;
+ ASSERT_OK(db->CreateColumnFamily(cf_options, cf_name, &cf_handle));
+ WriteOptions write_options;
+ size_t batch_cnt = 1;
+ size_t save_points = 0;
+ std::vector<size_t> batch_cnt_at;
+ WriteBatchWithIndex batch(db->DefaultColumnFamily()->GetComparator(), 0, true,
+ 0);
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+ batch_cnt_at.push_back(batch_cnt);
+ batch.SetSavePoint();
+ save_points++;
+ batch.Put(Slice("key"), Slice("value"));
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+ batch_cnt_at.push_back(batch_cnt);
+ batch.SetSavePoint();
+ save_points++;
+ batch.Put(Slice("key2"), Slice("value2"));
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+ // duplicate the keys
+ batch_cnt_at.push_back(batch_cnt);
+ batch.SetSavePoint();
+ save_points++;
+ batch.Put(Slice("key"), Slice("value3"));
+ batch_cnt++;
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+ // duplicate the 2nd key. It should not be counted duplicate since a
+ // sub-patch is cut after the last duplicate.
+ batch_cnt_at.push_back(batch_cnt);
+ batch.SetSavePoint();
+ save_points++;
+ batch.Put(Slice("key2"), Slice("value4"));
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+ // duplicate the keys but in a different cf. It should not be counted as
+ // duplicate keys
+ batch_cnt_at.push_back(batch_cnt);
+ batch.SetSavePoint();
+ save_points++;
+ batch.Put(cf_handle, Slice("key"), Slice("value5"));
+ ASSERT_EQ(batch_cnt, batch.SubBatchCnt());
+
+ // Test that the number of sub-batches matches what we count with
+ // SubBatchCounter
+ std::map<uint32_t, const Comparator*> comparators;
+ comparators[0] = db->DefaultColumnFamily()->GetComparator();
+ comparators[cf_handle->GetID()] = cf_handle->GetComparator();
+ SubBatchCounter counter(comparators);
+ ASSERT_OK(batch.GetWriteBatch()->Iterate(&counter));
+ ASSERT_EQ(batch_cnt, counter.BatchCount());
+
+ // Test that RollbackToSavePoint will properly resets the number of
+ // sub-batches
+ for (size_t i = save_points; i > 0; i--) {
+ batch.RollbackToSavePoint();
+ ASSERT_EQ(batch_cnt_at[i - 1], batch.SubBatchCnt());
+ }
+
+ // Test the count is right with random batches
+ {
+ const size_t TOTAL_KEYS = 20; // 20 ~= 10 to cause a few randoms
+ Random rnd(1131);
+ std::string keys[TOTAL_KEYS];
+ for (size_t k = 0; k < TOTAL_KEYS; k++) {
+ int len = static_cast<int>(rnd.Uniform(50));
+ keys[k] = test::RandomKey(&rnd, len);
+ }
+ for (size_t i = 0; i < 1000; i++) { // 1000 random batches
+ WriteBatchWithIndex rndbatch(db->DefaultColumnFamily()->GetComparator(),
+ 0, true, 0);
+ for (size_t k = 0; k < 10; k++) { // 10 key per batch
+ size_t ki = static_cast<size_t>(rnd.Uniform(TOTAL_KEYS));
+ Slice key = Slice(keys[ki]);
+ std::string buffer;
+ Slice value = Slice(test::RandomString(&rnd, 16, &buffer));
+ rndbatch.Put(key, value);
+ }
+ SubBatchCounter batch_counter(comparators);
+ ASSERT_OK(rndbatch.GetWriteBatch()->Iterate(&batch_counter));
+ ASSERT_EQ(rndbatch.SubBatchCnt(), batch_counter.BatchCount());
+ }
+ }
+
+ delete cf_handle;
+ delete db;
+}
+
+TEST(CommitEntry64b, BasicTest) {
+ const size_t INDEX_BITS = static_cast<size_t>(21);
+ const size_t INDEX_SIZE = static_cast<size_t>(1ull << INDEX_BITS);
+ const CommitEntry64bFormat FORMAT(static_cast<size_t>(INDEX_BITS));
+
+ // zero-initialized CommitEntry64b should indicate an empty entry
+ CommitEntry64b empty_entry64b;
+ uint64_t empty_index = 11ul;
+ CommitEntry empty_entry;
+ bool ok = empty_entry64b.Parse(empty_index, &empty_entry, FORMAT);
+ ASSERT_FALSE(ok);
+
+ // the zero entry is reserved for un-initialized entries
+ const size_t MAX_COMMIT = (1 << FORMAT.COMMIT_BITS) - 1 - 1;
+ // Samples over the numbers that are covered by that many index bits
+ std::array<uint64_t, 4> is = {{0, 1, INDEX_SIZE / 2 + 1, INDEX_SIZE - 1}};
+ // Samples over the numbers that are covered by that many commit bits
+ std::array<uint64_t, 4> ds = {{0, 1, MAX_COMMIT / 2 + 1, MAX_COMMIT}};
+ // Iterate over prepare numbers that have i) cover all bits of a sequence
+ // number, and ii) include some bits that fall into the range of index or
+ // commit bits
+ for (uint64_t base = 1; base < kMaxSequenceNumber; base *= 2) {
+ for (uint64_t i : is) {
+ for (uint64_t d : ds) {
+ uint64_t p = base + i + d;
+ for (uint64_t c : {p, p + d / 2, p + d}) {
+ uint64_t index = p % INDEX_SIZE;
+ CommitEntry before(p, c), after;
+ CommitEntry64b entry64b(before, FORMAT);
+ ok = entry64b.Parse(index, &after, FORMAT);
+ ASSERT_TRUE(ok);
+ if (!(before == after)) {
+ printf("base %" PRIu64 " i %" PRIu64 " d %" PRIu64 " p %" PRIu64
+ " c %" PRIu64 " index %" PRIu64 "\n",
+ base, i, d, p, c, index);
+ }
+ ASSERT_EQ(before, after);
+ }
+ }
+ }
+ }
+}
+
+class WritePreparedTxnDBMock : public WritePreparedTxnDB {
+ public:
+ WritePreparedTxnDBMock(DBImpl* db_impl, TransactionDBOptions& opt)
+ : WritePreparedTxnDB(db_impl, opt) {}
+ void SetDBSnapshots(const std::vector<SequenceNumber>& snapshots) {
+ snapshots_ = snapshots;
+ }
+ void TakeSnapshot(SequenceNumber seq) { snapshots_.push_back(seq); }
+
+ protected:
+ const std::vector<SequenceNumber> GetSnapshotListFromDB(
+ SequenceNumber /* unused */) override {
+ return snapshots_;
+ }
+
+ private:
+ std::vector<SequenceNumber> snapshots_;
+};
+
+class WritePreparedTransactionTestBase : public TransactionTestBase {
+ public:
+ WritePreparedTransactionTestBase(bool use_stackable_db, bool two_write_queue,
+ TxnDBWritePolicy write_policy,
+ WriteOrdering write_ordering)
+ : TransactionTestBase(use_stackable_db, two_write_queue, write_policy,
+ write_ordering){};
+
+ protected:
+ void UpdateTransactionDBOptions(size_t snapshot_cache_bits,
+ size_t commit_cache_bits) {
+ txn_db_options.wp_snapshot_cache_bits = snapshot_cache_bits;
+ txn_db_options.wp_commit_cache_bits = commit_cache_bits;
+ }
+ void UpdateTransactionDBOptions(size_t snapshot_cache_bits) {
+ txn_db_options.wp_snapshot_cache_bits = snapshot_cache_bits;
+ }
+ // If expect_update is set, check if it actually updated old_commit_map_. If
+ // it did not and yet suggested not to check the next snapshot, do the
+ // opposite to check if it was not a bad suggestion.
+ void MaybeUpdateOldCommitMapTestWithNext(uint64_t prepare, uint64_t commit,
+ uint64_t snapshot,
+ uint64_t next_snapshot,
+ bool expect_update) {
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ // reset old_commit_map_empty_ so that its value indicate whether
+ // old_commit_map_ was updated
+ wp_db->old_commit_map_empty_ = true;
+ bool check_next = wp_db->MaybeUpdateOldCommitMap(prepare, commit, snapshot,
+ snapshot < next_snapshot);
+ if (expect_update == wp_db->old_commit_map_empty_) {
+ printf("prepare: %" PRIu64 " commit: %" PRIu64 " snapshot: %" PRIu64
+ " next: %" PRIu64 "\n",
+ prepare, commit, snapshot, next_snapshot);
+ }
+ EXPECT_EQ(!expect_update, wp_db->old_commit_map_empty_);
+ if (!check_next && wp_db->old_commit_map_empty_) {
+ // do the opposite to make sure it was not a bad suggestion
+ const bool dont_care_bool = true;
+ wp_db->MaybeUpdateOldCommitMap(prepare, commit, next_snapshot,
+ dont_care_bool);
+ if (!wp_db->old_commit_map_empty_) {
+ printf("prepare: %" PRIu64 " commit: %" PRIu64 " snapshot: %" PRIu64
+ " next: %" PRIu64 "\n",
+ prepare, commit, snapshot, next_snapshot);
+ }
+ EXPECT_TRUE(wp_db->old_commit_map_empty_);
+ }
+ }
+
+ // Test that a CheckAgainstSnapshots thread reading old_snapshots will not
+ // miss a snapshot because of a concurrent update by UpdateSnapshots that is
+ // writing new_snapshots. Both threads are broken at two points. The sync
+ // points to enforce them are specified by a1, a2, b1, and b2. CommitEntry
+ // entry is expected to be vital for one of the snapshots that is common
+ // between the old and new list of snapshots.
+ void SnapshotConcurrentAccessTestInternal(
+ WritePreparedTxnDB* wp_db,
+ const std::vector<SequenceNumber>& old_snapshots,
+ const std::vector<SequenceNumber>& new_snapshots, CommitEntry& entry,
+ SequenceNumber& version, size_t a1, size_t a2, size_t b1, size_t b2) {
+ // First reset the snapshot list
+ const std::vector<SequenceNumber> empty_snapshots;
+ wp_db->old_commit_map_empty_ = true;
+ wp_db->UpdateSnapshots(empty_snapshots, ++version);
+ // Then initialize it with the old_snapshots
+ wp_db->UpdateSnapshots(old_snapshots, ++version);
+
+ // Starting from the first thread, cut each thread at two points
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"WritePreparedTxnDB::CheckAgainstSnapshots:p:" + std::to_string(a1),
+ "WritePreparedTxnDB::UpdateSnapshots:s:start"},
+ {"WritePreparedTxnDB::UpdateSnapshots:p:" + std::to_string(b1),
+ "WritePreparedTxnDB::CheckAgainstSnapshots:s:" + std::to_string(a1)},
+ {"WritePreparedTxnDB::CheckAgainstSnapshots:p:" + std::to_string(a2),
+ "WritePreparedTxnDB::UpdateSnapshots:s:" + std::to_string(b1)},
+ {"WritePreparedTxnDB::UpdateSnapshots:p:" + std::to_string(b2),
+ "WritePreparedTxnDB::CheckAgainstSnapshots:s:" + std::to_string(a2)},
+ {"WritePreparedTxnDB::CheckAgainstSnapshots:p:end",
+ "WritePreparedTxnDB::UpdateSnapshots:s:" + std::to_string(b2)},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ {
+ ASSERT_TRUE(wp_db->old_commit_map_empty_);
+ ROCKSDB_NAMESPACE::port::Thread t1(
+ [&]() { wp_db->UpdateSnapshots(new_snapshots, version); });
+ ROCKSDB_NAMESPACE::port::Thread t2(
+ [&]() { wp_db->CheckAgainstSnapshots(entry); });
+ t1.join();
+ t2.join();
+ ASSERT_FALSE(wp_db->old_commit_map_empty_);
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ wp_db->old_commit_map_empty_ = true;
+ wp_db->UpdateSnapshots(empty_snapshots, ++version);
+ wp_db->UpdateSnapshots(old_snapshots, ++version);
+ // Starting from the second thread, cut each thread at two points
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"WritePreparedTxnDB::UpdateSnapshots:p:" + std::to_string(a1),
+ "WritePreparedTxnDB::CheckAgainstSnapshots:s:start"},
+ {"WritePreparedTxnDB::CheckAgainstSnapshots:p:" + std::to_string(b1),
+ "WritePreparedTxnDB::UpdateSnapshots:s:" + std::to_string(a1)},
+ {"WritePreparedTxnDB::UpdateSnapshots:p:" + std::to_string(a2),
+ "WritePreparedTxnDB::CheckAgainstSnapshots:s:" + std::to_string(b1)},
+ {"WritePreparedTxnDB::CheckAgainstSnapshots:p:" + std::to_string(b2),
+ "WritePreparedTxnDB::UpdateSnapshots:s:" + std::to_string(a2)},
+ {"WritePreparedTxnDB::UpdateSnapshots:p:end",
+ "WritePreparedTxnDB::CheckAgainstSnapshots:s:" + std::to_string(b2)},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ {
+ ASSERT_TRUE(wp_db->old_commit_map_empty_);
+ ROCKSDB_NAMESPACE::port::Thread t1(
+ [&]() { wp_db->UpdateSnapshots(new_snapshots, version); });
+ ROCKSDB_NAMESPACE::port::Thread t2(
+ [&]() { wp_db->CheckAgainstSnapshots(entry); });
+ t1.join();
+ t2.join();
+ ASSERT_FALSE(wp_db->old_commit_map_empty_);
+ }
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ }
+
+ // Verify value of keys.
+ void VerifyKeys(const std::unordered_map<std::string, std::string>& data,
+ const Snapshot* snapshot = nullptr) {
+ std::string value;
+ ReadOptions read_options;
+ read_options.snapshot = snapshot;
+ for (auto& kv : data) {
+ auto s = db->Get(read_options, kv.first, &value);
+ ASSERT_TRUE(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ if (kv.second != value) {
+ printf("key = %s\n", kv.first.c_str());
+ }
+ ASSERT_EQ(kv.second, value);
+ } else {
+ ASSERT_EQ(kv.second, "NOT_FOUND");
+ }
+
+ // Try with MultiGet API too
+ std::vector<std::string> values;
+ auto s_vec = db->MultiGet(read_options, {db->DefaultColumnFamily()},
+ {kv.first}, &values);
+ ASSERT_EQ(1, values.size());
+ ASSERT_EQ(1, s_vec.size());
+ s = s_vec[0];
+ ASSERT_TRUE(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ ASSERT_TRUE(kv.second == values[0]);
+ } else {
+ ASSERT_EQ(kv.second, "NOT_FOUND");
+ }
+ }
+ }
+
+ // Verify all versions of keys.
+ void VerifyInternalKeys(const std::vector<KeyVersion>& expected_versions) {
+ std::vector<KeyVersion> versions;
+ const size_t kMaxKeys = 100000;
+ ASSERT_OK(GetAllKeyVersions(db, expected_versions.front().user_key,
+ expected_versions.back().user_key, kMaxKeys,
+ &versions));
+ ASSERT_EQ(expected_versions.size(), versions.size());
+ for (size_t i = 0; i < versions.size(); i++) {
+ ASSERT_EQ(expected_versions[i].user_key, versions[i].user_key);
+ ASSERT_EQ(expected_versions[i].sequence, versions[i].sequence);
+ ASSERT_EQ(expected_versions[i].type, versions[i].type);
+ if (versions[i].type != kTypeDeletion &&
+ versions[i].type != kTypeSingleDeletion) {
+ ASSERT_EQ(expected_versions[i].value, versions[i].value);
+ }
+ // Range delete not supported.
+ assert(expected_versions[i].type != kTypeRangeDeletion);
+ }
+ }
+};
+
+class WritePreparedTransactionTest
+ : public WritePreparedTransactionTestBase,
+ virtual public ::testing::WithParamInterface<
+ std::tuple<bool, bool, TxnDBWritePolicy, WriteOrdering>> {
+ public:
+ WritePreparedTransactionTest()
+ : WritePreparedTransactionTestBase(
+ std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam())){};
+};
+
+#ifndef ROCKSDB_VALGRIND_RUN
+class SnapshotConcurrentAccessTest
+ : public WritePreparedTransactionTestBase,
+ virtual public ::testing::WithParamInterface<std::tuple<
+ bool, bool, TxnDBWritePolicy, WriteOrdering, size_t, size_t>> {
+ public:
+ SnapshotConcurrentAccessTest()
+ : WritePreparedTransactionTestBase(
+ std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam())),
+ split_id_(std::get<4>(GetParam())),
+ split_cnt_(std::get<5>(GetParam())){};
+
+ protected:
+ // A test is split into split_cnt_ tests, each identified with split_id_ where
+ // 0 <= split_id_ < split_cnt_
+ size_t split_id_;
+ size_t split_cnt_;
+};
+#endif // ROCKSDB_VALGRIND_RUN
+
+class SeqAdvanceConcurrentTest
+ : public WritePreparedTransactionTestBase,
+ virtual public ::testing::WithParamInterface<std::tuple<
+ bool, bool, TxnDBWritePolicy, WriteOrdering, size_t, size_t>> {
+ public:
+ SeqAdvanceConcurrentTest()
+ : WritePreparedTransactionTestBase(
+ std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam())),
+ split_id_(std::get<4>(GetParam())),
+ split_cnt_(std::get<5>(GetParam())){};
+
+ protected:
+ // A test is split into split_cnt_ tests, each identified with split_id_ where
+ // 0 <= split_id_ < split_cnt_
+ size_t split_id_;
+ size_t split_cnt_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ WritePreparedTransaction, WritePreparedTransactionTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite)));
+
+#ifndef ROCKSDB_VALGRIND_RUN
+INSTANTIATE_TEST_CASE_P(
+ TwoWriteQueues, SnapshotConcurrentAccessTest,
+ ::testing::Values(
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 0, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 1, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 2, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 3, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 4, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 5, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 6, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 7, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 8, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 9, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 10, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 11, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 12, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 13, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 14, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 15, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 16, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 17, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 18, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 19, 20),
+
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 0, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 1, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 2, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 3, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 4, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 5, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 6, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 7, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 8, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 9, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 10, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 11, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 12, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 13, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 14, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 15, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 16, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 17, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 18, 20),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 19, 20)));
+
+INSTANTIATE_TEST_CASE_P(
+ OneWriteQueue, SnapshotConcurrentAccessTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 0, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 1, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 2, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 3, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 4, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 5, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 6, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 7, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 8, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 9, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 10, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 11, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 12, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 13, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 14, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 15, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 16, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 17, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 18, 20),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 19, 20)));
+
+INSTANTIATE_TEST_CASE_P(
+ TwoWriteQueues, SeqAdvanceConcurrentTest,
+ ::testing::Values(
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 0, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 1, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 2, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 3, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 4, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 5, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 6, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 7, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 8, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kOrderedWrite, 9, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 0, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 1, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 2, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 3, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 4, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 5, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 6, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 7, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 8, 10),
+ std::make_tuple(false, true, WRITE_PREPARED, kUnorderedWrite, 9, 10)));
+
+INSTANTIATE_TEST_CASE_P(
+ OneWriteQueue, SeqAdvanceConcurrentTest,
+ ::testing::Values(
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 0, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 1, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 2, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 3, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 4, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 5, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 6, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 7, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 8, 10),
+ std::make_tuple(false, false, WRITE_PREPARED, kOrderedWrite, 9, 10)));
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(WritePreparedTransactionTest, CommitMap) {
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ assert(wp_db);
+ assert(wp_db->db_impl_);
+ size_t size = wp_db->COMMIT_CACHE_SIZE;
+ CommitEntry c = {5, 12}, e;
+ bool evicted = wp_db->AddCommitEntry(c.prep_seq % size, c, &e);
+ ASSERT_FALSE(evicted);
+
+ // Should be able to read the same value
+ CommitEntry64b dont_care;
+ bool found = wp_db->GetCommitEntry(c.prep_seq % size, &dont_care, &e);
+ ASSERT_TRUE(found);
+ ASSERT_EQ(c, e);
+ // Should be able to distinguish between overlapping entries
+ found = wp_db->GetCommitEntry((c.prep_seq + size) % size, &dont_care, &e);
+ ASSERT_TRUE(found);
+ ASSERT_NE(c.prep_seq + size, e.prep_seq);
+ // Should be able to detect non-existent entry
+ found = wp_db->GetCommitEntry((c.prep_seq + 1) % size, &dont_care, &e);
+ ASSERT_FALSE(found);
+
+ // Reject an invalid exchange
+ CommitEntry e2 = {c.prep_seq + size, c.commit_seq + size};
+ CommitEntry64b e2_64b(e2, wp_db->FORMAT);
+ bool exchanged = wp_db->ExchangeCommitEntry(e2.prep_seq % size, e2_64b, e);
+ ASSERT_FALSE(exchanged);
+ // check whether it did actually reject that
+ found = wp_db->GetCommitEntry(e2.prep_seq % size, &dont_care, &e);
+ ASSERT_TRUE(found);
+ ASSERT_EQ(c, e);
+
+ // Accept a valid exchange
+ CommitEntry64b c_64b(c, wp_db->FORMAT);
+ CommitEntry e3 = {c.prep_seq + size, c.commit_seq + size + 1};
+ exchanged = wp_db->ExchangeCommitEntry(c.prep_seq % size, c_64b, e3);
+ ASSERT_TRUE(exchanged);
+ // check whether it did actually accepted that
+ found = wp_db->GetCommitEntry(c.prep_seq % size, &dont_care, &e);
+ ASSERT_TRUE(found);
+ ASSERT_EQ(e3, e);
+
+ // Rewrite an entry
+ CommitEntry e4 = {e3.prep_seq + size, e3.commit_seq + size + 1};
+ evicted = wp_db->AddCommitEntry(e4.prep_seq % size, e4, &e);
+ ASSERT_TRUE(evicted);
+ ASSERT_EQ(e3, e);
+ found = wp_db->GetCommitEntry(e4.prep_seq % size, &dont_care, &e);
+ ASSERT_TRUE(found);
+ ASSERT_EQ(e4, e);
+}
+
+TEST_P(WritePreparedTransactionTest, MaybeUpdateOldCommitMap) {
+ // If prepare <= snapshot < commit we should keep the entry around since its
+ // nonexistence could be interpreted as committed in the snapshot while it is
+ // not true. We keep such entries around by adding them to the
+ // old_commit_map_.
+ uint64_t p /*prepare*/, c /*commit*/, s /*snapshot*/, ns /*next_snapshot*/;
+ p = 10l, c = 15l, s = 20l, ns = 21l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+ // If we do not expect the old commit map to be updated, try also with a next
+ // snapshot that is expected to update the old commit map. This would test
+ // that MaybeUpdateOldCommitMap would not prevent us from checking the next
+ // snapshot that must be checked.
+ p = 10l, c = 15l, s = 20l, ns = 11l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+
+ p = 10l, c = 20l, s = 20l, ns = 19l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+ p = 10l, c = 20l, s = 20l, ns = 21l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+
+ p = 20l, c = 20l, s = 20l, ns = 21l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+ p = 20l, c = 20l, s = 20l, ns = 19l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+
+ p = 10l, c = 25l, s = 20l, ns = 21l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, true);
+
+ p = 20l, c = 25l, s = 20l, ns = 21l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, true);
+
+ p = 21l, c = 25l, s = 20l, ns = 22l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+ p = 21l, c = 25l, s = 20l, ns = 19l;
+ MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
+}
+
+// Trigger the condition where some old memtables are skipped when doing
+// TransactionUtil::CheckKey(), and make sure the result is still correct.
+TEST_P(WritePreparedTransactionTest, CheckKeySkipOldMemtable) {
+ const int kAttemptHistoryMemtable = 0;
+ const int kAttemptImmMemTable = 1;
+ for (int attempt = kAttemptHistoryMemtable; attempt <= kAttemptImmMemTable;
+ attempt++) {
+ options.max_write_buffer_number_to_maintain = 3;
+ ReOpen();
+
+ WriteOptions write_options;
+ ReadOptions read_options;
+ TransactionOptions txn_options;
+ txn_options.set_snapshot = true;
+ string value;
+ Status s;
+
+ ASSERT_OK(db->Put(write_options, Slice("foo"), Slice("bar")));
+ ASSERT_OK(db->Put(write_options, Slice("foo2"), Slice("bar")));
+
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn != nullptr);
+ ASSERT_OK(txn->SetName("txn"));
+
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn2 != nullptr);
+ ASSERT_OK(txn2->SetName("txn2"));
+
+ // This transaction is created to cause potential conflict.
+ Transaction* txn_x = db->BeginTransaction(write_options);
+ ASSERT_OK(txn_x->SetName("txn_x"));
+ ASSERT_OK(txn_x->Put(Slice("foo"), Slice("bar3")));
+ ASSERT_OK(txn_x->Prepare());
+
+ // Create snapshots after the prepare, but there should still
+ // be a conflict when trying to read "foo".
+
+ if (attempt == kAttemptImmMemTable) {
+ // For the second attempt, hold flush from beginning. The memtable
+ // will be switched to immutable after calling TEST_SwitchMemtable()
+ // while CheckKey() is called.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTransactionTest.CheckKeySkipOldMemtable",
+ "FlushJob::Start"}});
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ }
+
+ // force a memtable flush. The memtable should still be kept
+ FlushOptions flush_ops;
+ if (attempt == kAttemptHistoryMemtable) {
+ ASSERT_OK(db->Flush(flush_ops));
+ } else {
+ assert(attempt == kAttemptImmMemTable);
+ DBImpl* db_impl = static_cast<DBImpl*>(db->GetRootDB());
+ db_impl->TEST_SwitchMemtable();
+ }
+ uint64_t num_imm_mems;
+ ASSERT_TRUE(db->GetIntProperty(DB::Properties::kNumImmutableMemTable,
+ &num_imm_mems));
+ if (attempt == kAttemptHistoryMemtable) {
+ ASSERT_EQ(0, num_imm_mems);
+ } else {
+ assert(attempt == kAttemptImmMemTable);
+ ASSERT_EQ(1, num_imm_mems);
+ }
+
+ // Put something in active memtable
+ ASSERT_OK(db->Put(write_options, Slice("foo3"), Slice("bar")));
+
+ // Create txn3 after flushing, but this transaction also needs to
+ // check all memtables because of they contains uncommitted data.
+ Transaction* txn3 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn3 != nullptr);
+ ASSERT_OK(txn3->SetName("txn3"));
+
+ // Commit the pending write
+ ASSERT_OK(txn_x->Commit());
+
+ // Commit txn, txn2 and tx3. txn and tx3 will conflict but txn2 will
+ // pass. In all cases, both memtables are queried.
+ SetPerfLevel(PerfLevel::kEnableCount);
+ get_perf_context()->Reset();
+ ASSERT_TRUE(txn3->GetForUpdate(read_options, "foo", &value).IsBusy());
+ // We should have checked two memtables, active and either immutable
+ // or history memtable, depending on the test case.
+ ASSERT_EQ(2, get_perf_context()->get_from_memtable_count);
+
+ get_perf_context()->Reset();
+ ASSERT_TRUE(txn->GetForUpdate(read_options, "foo", &value).IsBusy());
+ // We should have checked two memtables, active and either immutable
+ // or history memtable, depending on the test case.
+ ASSERT_EQ(2, get_perf_context()->get_from_memtable_count);
+
+ get_perf_context()->Reset();
+ ASSERT_OK(txn2->GetForUpdate(read_options, "foo2", &value));
+ ASSERT_EQ(value, "bar");
+ // We should have checked two memtables, and since there is no
+ // conflict, another Get() will be made and fetch the data from
+ // DB. If it is in immutable memtable, two extra memtable reads
+ // will be issued. If it is not (in history), only one will
+ // be made, which is to the active memtable.
+ if (attempt == kAttemptHistoryMemtable) {
+ ASSERT_EQ(3, get_perf_context()->get_from_memtable_count);
+ } else {
+ assert(attempt == kAttemptImmMemTable);
+ ASSERT_EQ(4, get_perf_context()->get_from_memtable_count);
+ }
+
+ Transaction* txn4 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_TRUE(txn4 != nullptr);
+ ASSERT_OK(txn4->SetName("txn4"));
+ get_perf_context()->Reset();
+ ASSERT_OK(txn4->GetForUpdate(read_options, "foo", &value));
+ if (attempt == kAttemptHistoryMemtable) {
+ // Active memtable will be checked in snapshot validation and when
+ // getting the value.
+ ASSERT_EQ(2, get_perf_context()->get_from_memtable_count);
+ } else {
+ // Only active memtable will be checked in snapshot validation but
+ // both of active and immutable snapshot will be queried when
+ // getting the value.
+ assert(attempt == kAttemptImmMemTable);
+ ASSERT_EQ(3, get_perf_context()->get_from_memtable_count);
+ }
+
+ ASSERT_OK(txn2->Commit());
+ ASSERT_OK(txn4->Commit());
+
+ TEST_SYNC_POINT("WritePreparedTransactionTest.CheckKeySkipOldMemtable");
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+
+ SetPerfLevel(PerfLevel::kDisable);
+
+ delete txn;
+ delete txn2;
+ delete txn3;
+ delete txn4;
+ delete txn_x;
+ }
+}
+
+// Reproduce the bug with two snapshots with the same seuqence number and test
+// that the release of the first snapshot will not affect the reads by the other
+// snapshot
+TEST_P(WritePreparedTransactionTest, DoubleSnapshot) {
+ TransactionOptions txn_options;
+ Status s;
+
+ // Insert initial value
+ ASSERT_OK(db->Put(WriteOptions(), "key", "value1"));
+
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ Transaction* txn =
+ wp_db->BeginTransaction(WriteOptions(), txn_options, nullptr);
+ ASSERT_OK(txn->SetName("txn"));
+ ASSERT_OK(txn->Put("key", "value2"));
+ ASSERT_OK(txn->Prepare());
+ // Three snapshots with the same seq number
+ const Snapshot* snapshot0 = wp_db->GetSnapshot();
+ const Snapshot* snapshot1 = wp_db->GetSnapshot();
+ const Snapshot* snapshot2 = wp_db->GetSnapshot();
+ ASSERT_OK(txn->Commit());
+ SequenceNumber cache_size = wp_db->COMMIT_CACHE_SIZE;
+ SequenceNumber overlap_seq = txn->GetId() + cache_size;
+ delete txn;
+
+ // 4th snapshot with a larger seq
+ const Snapshot* snapshot3 = wp_db->GetSnapshot();
+ // Cause an eviction to advance max evicted seq number
+ // This also fetches the 4 snapshots from db since their seq is lower than the
+ // new max
+ wp_db->AddCommitted(overlap_seq, overlap_seq);
+
+ ReadOptions ropt;
+ // It should see the value before commit
+ ropt.snapshot = snapshot2;
+ PinnableSlice pinnable_val;
+ s = wp_db->Get(ropt, wp_db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == "value1");
+ pinnable_val.Reset();
+
+ wp_db->ReleaseSnapshot(snapshot1);
+
+ // It should still see the value before commit
+ s = wp_db->Get(ropt, wp_db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == "value1");
+ pinnable_val.Reset();
+
+ // Cause an eviction to advance max evicted seq number and trigger updating
+ // the snapshot list
+ overlap_seq += cache_size;
+ wp_db->AddCommitted(overlap_seq, overlap_seq);
+
+ // It should still see the value before commit
+ s = wp_db->Get(ropt, wp_db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_OK(s);
+ ASSERT_TRUE(pinnable_val == "value1");
+ pinnable_val.Reset();
+
+ wp_db->ReleaseSnapshot(snapshot0);
+ wp_db->ReleaseSnapshot(snapshot2);
+ wp_db->ReleaseSnapshot(snapshot3);
+}
+
+size_t UniqueCnt(std::vector<SequenceNumber> vec) {
+ std::set<SequenceNumber> aset;
+ for (auto i : vec) {
+ aset.insert(i);
+ }
+ return aset.size();
+}
+// Test that the entries in old_commit_map_ get garbage collected properly
+TEST_P(WritePreparedTransactionTest, OldCommitMapGC) {
+ const size_t snapshot_cache_bits = 0;
+ const size_t commit_cache_bits = 0;
+ DBImpl* mock_db = new DBImpl(options, dbname);
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ std::unique_ptr<WritePreparedTxnDBMock> wp_db(
+ new WritePreparedTxnDBMock(mock_db, txn_db_options));
+
+ SequenceNumber seq = 0;
+ // Take the first snapshot that overlaps with two txn
+ auto prep_seq = ++seq;
+ wp_db->AddPrepared(prep_seq);
+ auto prep_seq2 = ++seq;
+ wp_db->AddPrepared(prep_seq2);
+ auto snap_seq1 = seq;
+ wp_db->TakeSnapshot(snap_seq1);
+ auto commit_seq = ++seq;
+ wp_db->AddCommitted(prep_seq, commit_seq);
+ wp_db->RemovePrepared(prep_seq);
+ auto commit_seq2 = ++seq;
+ wp_db->AddCommitted(prep_seq2, commit_seq2);
+ wp_db->RemovePrepared(prep_seq2);
+ // Take the 2nd and 3rd snapshot that overlap with the same txn
+ prep_seq = ++seq;
+ wp_db->AddPrepared(prep_seq);
+ auto snap_seq2 = seq;
+ wp_db->TakeSnapshot(snap_seq2);
+ seq++;
+ auto snap_seq3 = seq;
+ wp_db->TakeSnapshot(snap_seq3);
+ seq++;
+ commit_seq = ++seq;
+ wp_db->AddCommitted(prep_seq, commit_seq);
+ wp_db->RemovePrepared(prep_seq);
+ // Make sure max_evicted_seq_ will be larger than 2nd snapshot by evicting the
+ // only item in the commit_cache_ via another commit.
+ prep_seq = ++seq;
+ wp_db->AddPrepared(prep_seq);
+ commit_seq = ++seq;
+ wp_db->AddCommitted(prep_seq, commit_seq);
+ wp_db->RemovePrepared(prep_seq);
+
+ // Verify that the evicted commit entries for all snapshots are in the
+ // old_commit_map_
+ {
+ ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
+ ReadLock rl(&wp_db->old_commit_map_mutex_);
+ ASSERT_EQ(3, wp_db->old_commit_map_.size());
+ ASSERT_EQ(2, UniqueCnt(wp_db->old_commit_map_[snap_seq1]));
+ ASSERT_EQ(1, UniqueCnt(wp_db->old_commit_map_[snap_seq2]));
+ ASSERT_EQ(1, UniqueCnt(wp_db->old_commit_map_[snap_seq3]));
+ }
+
+ // Verify that the 2nd snapshot is cleaned up after the release
+ wp_db->ReleaseSnapshotInternal(snap_seq2);
+ {
+ ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
+ ReadLock rl(&wp_db->old_commit_map_mutex_);
+ ASSERT_EQ(2, wp_db->old_commit_map_.size());
+ ASSERT_EQ(2, UniqueCnt(wp_db->old_commit_map_[snap_seq1]));
+ ASSERT_EQ(1, UniqueCnt(wp_db->old_commit_map_[snap_seq3]));
+ }
+
+ // Verify that the 1st snapshot is cleaned up after the release
+ wp_db->ReleaseSnapshotInternal(snap_seq1);
+ {
+ ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
+ ReadLock rl(&wp_db->old_commit_map_mutex_);
+ ASSERT_EQ(1, wp_db->old_commit_map_.size());
+ ASSERT_EQ(1, UniqueCnt(wp_db->old_commit_map_[snap_seq3]));
+ }
+
+ // Verify that the 3rd snapshot is cleaned up after the release
+ wp_db->ReleaseSnapshotInternal(snap_seq3);
+ {
+ ASSERT_TRUE(wp_db->old_commit_map_empty_.load());
+ ReadLock rl(&wp_db->old_commit_map_mutex_);
+ ASSERT_EQ(0, wp_db->old_commit_map_.size());
+ }
+}
+
+TEST_P(WritePreparedTransactionTest, CheckAgainstSnapshots) {
+ std::vector<SequenceNumber> snapshots = {100l, 200l, 300l, 400l, 500l,
+ 600l, 700l, 800l, 900l};
+ const size_t snapshot_cache_bits = 2;
+ const uint64_t cache_size = 1ul << snapshot_cache_bits;
+ // Safety check to express the intended size in the test. Can be adjusted if
+ // the snapshots lists changed.
+ assert((1ul << snapshot_cache_bits) * 2 + 1 == snapshots.size());
+ DBImpl* mock_db = new DBImpl(options, dbname);
+ UpdateTransactionDBOptions(snapshot_cache_bits);
+ std::unique_ptr<WritePreparedTxnDBMock> wp_db(
+ new WritePreparedTxnDBMock(mock_db, txn_db_options));
+ SequenceNumber version = 1000l;
+ ASSERT_EQ(0, wp_db->snapshots_total_);
+ wp_db->UpdateSnapshots(snapshots, version);
+ ASSERT_EQ(snapshots.size(), wp_db->snapshots_total_);
+ // seq numbers are chosen so that we have two of them between each two
+ // snapshots. If the diff of two consecutive seq is more than 5, there is a
+ // snapshot between them.
+ std::vector<SequenceNumber> seqs = {50l, 55l, 150l, 155l, 250l, 255l, 350l,
+ 355l, 450l, 455l, 550l, 555l, 650l, 655l,
+ 750l, 755l, 850l, 855l, 950l, 955l};
+ assert(seqs.size() > 1);
+ for (size_t i = 0; i < seqs.size() - 1; i++) {
+ wp_db->old_commit_map_empty_ = true; // reset
+ CommitEntry commit_entry = {seqs[i], seqs[i + 1]};
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ // Expect update if there is snapshot in between the prepare and commit
+ bool expect_update = commit_entry.commit_seq - commit_entry.prep_seq > 5 &&
+ commit_entry.commit_seq >= snapshots.front() &&
+ commit_entry.prep_seq <= snapshots.back();
+ ASSERT_EQ(expect_update, !wp_db->old_commit_map_empty_);
+ }
+
+ // Test that search will include multiple snapshot from snapshot cache
+ {
+ // exclude first and last item in the cache
+ CommitEntry commit_entry = {snapshots.front() + 1,
+ snapshots[cache_size - 1] - 1};
+ wp_db->old_commit_map_empty_ = true; // reset
+ wp_db->old_commit_map_.clear();
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ ASSERT_EQ(wp_db->old_commit_map_.size(), cache_size - 2);
+ }
+
+ // Test that search will include multiple snapshot from old snapshots
+ {
+ // include two in the middle
+ CommitEntry commit_entry = {snapshots[cache_size] + 1,
+ snapshots[cache_size + 2] + 1};
+ wp_db->old_commit_map_empty_ = true; // reset
+ wp_db->old_commit_map_.clear();
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ ASSERT_EQ(wp_db->old_commit_map_.size(), 2);
+ }
+
+ // Test that search will include both snapshot cache and old snapshots
+ // Case 1: includes all in snapshot cache
+ {
+ CommitEntry commit_entry = {snapshots.front() - 1, snapshots.back() + 1};
+ wp_db->old_commit_map_empty_ = true; // reset
+ wp_db->old_commit_map_.clear();
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ ASSERT_EQ(wp_db->old_commit_map_.size(), snapshots.size());
+ }
+
+ // Case 2: includes all snapshot caches except the smallest
+ {
+ CommitEntry commit_entry = {snapshots.front() + 1, snapshots.back() + 1};
+ wp_db->old_commit_map_empty_ = true; // reset
+ wp_db->old_commit_map_.clear();
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ ASSERT_EQ(wp_db->old_commit_map_.size(), snapshots.size() - 1);
+ }
+
+ // Case 3: includes only the largest of snapshot cache
+ {
+ CommitEntry commit_entry = {snapshots[cache_size - 1] - 1,
+ snapshots.back() + 1};
+ wp_db->old_commit_map_empty_ = true; // reset
+ wp_db->old_commit_map_.clear();
+ wp_db->CheckAgainstSnapshots(commit_entry);
+ ASSERT_EQ(wp_db->old_commit_map_.size(), snapshots.size() - cache_size + 1);
+ }
+}
+
+// This test is too slow for travis
+#ifndef TRAVIS
+#ifndef ROCKSDB_VALGRIND_RUN
+// Test that CheckAgainstSnapshots will not miss a live snapshot if it is run in
+// parallel with UpdateSnapshots.
+TEST_P(SnapshotConcurrentAccessTest, SnapshotConcurrentAccess) {
+ // We have a sync point in the method under test after checking each snapshot.
+ // If you increase the max number of snapshots in this test, more sync points
+ // in the methods must also be added.
+ const std::vector<SequenceNumber> snapshots = {10l, 20l, 30l, 40l, 50l,
+ 60l, 70l, 80l, 90l, 100l};
+ const size_t snapshot_cache_bits = 2;
+ // Safety check to express the intended size in the test. Can be adjusted if
+ // the snapshots lists changed.
+ assert((1ul << snapshot_cache_bits) * 2 + 2 == snapshots.size());
+ SequenceNumber version = 1000l;
+ // Choose the cache size so that the new snapshot list could replace all the
+ // existing items in the cache and also have some overflow.
+ DBImpl* mock_db = new DBImpl(options, dbname);
+ UpdateTransactionDBOptions(snapshot_cache_bits);
+ std::unique_ptr<WritePreparedTxnDBMock> wp_db(
+ new WritePreparedTxnDBMock(mock_db, txn_db_options));
+ const size_t extra = 2;
+ size_t loop_id = 0;
+ // Add up to extra items that do not fit into the cache
+ for (size_t old_size = 1; old_size <= wp_db->SNAPSHOT_CACHE_SIZE + extra;
+ old_size++) {
+ const std::vector<SequenceNumber> old_snapshots(
+ snapshots.begin(), snapshots.begin() + old_size);
+
+ // Each member of old snapshot might or might not appear in the new list. We
+ // create a common_snapshots for each combination.
+ size_t new_comb_cnt = size_t(1) << old_size;
+ for (size_t new_comb = 0; new_comb < new_comb_cnt; new_comb++, loop_id++) {
+ if (loop_id % split_cnt_ != split_id_) continue;
+ printf("."); // To signal progress
+ fflush(stdout);
+ std::vector<SequenceNumber> common_snapshots;
+ for (size_t i = 0; i < old_snapshots.size(); i++) {
+ if (IsInCombination(i, new_comb)) {
+ common_snapshots.push_back(old_snapshots[i]);
+ }
+ }
+ // And add some new snapshots to the common list
+ for (size_t added_snapshots = 0;
+ added_snapshots <= snapshots.size() - old_snapshots.size();
+ added_snapshots++) {
+ std::vector<SequenceNumber> new_snapshots = common_snapshots;
+ for (size_t i = 0; i < added_snapshots; i++) {
+ new_snapshots.push_back(snapshots[old_snapshots.size() + i]);
+ }
+ for (auto it = common_snapshots.begin(); it != common_snapshots.end();
+ ++it) {
+ auto snapshot = *it;
+ // Create a commit entry that is around the snapshot and thus should
+ // be not be discarded
+ CommitEntry entry = {static_cast<uint64_t>(snapshot - 1),
+ snapshot + 1};
+ // The critical part is when iterating the snapshot cache. Afterwards,
+ // we are operating under the lock
+ size_t a_range =
+ std::min(old_snapshots.size(), wp_db->SNAPSHOT_CACHE_SIZE) + 1;
+ size_t b_range =
+ std::min(new_snapshots.size(), wp_db->SNAPSHOT_CACHE_SIZE) + 1;
+ // Break each thread at two points
+ for (size_t a1 = 1; a1 <= a_range; a1++) {
+ for (size_t a2 = a1 + 1; a2 <= a_range; a2++) {
+ for (size_t b1 = 1; b1 <= b_range; b1++) {
+ for (size_t b2 = b1 + 1; b2 <= b_range; b2++) {
+ SnapshotConcurrentAccessTestInternal(
+ wp_db.get(), old_snapshots, new_snapshots, entry, version,
+ a1, a2, b1, b2);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ printf("\n");
+}
+#endif // ROCKSDB_VALGRIND_RUN
+#endif // TRAVIS
+
+// This test clarifies the contract of AdvanceMaxEvictedSeq method
+TEST_P(WritePreparedTransactionTest, AdvanceMaxEvictedSeqBasic) {
+ DBImpl* mock_db = new DBImpl(options, dbname);
+ std::unique_ptr<WritePreparedTxnDBMock> wp_db(
+ new WritePreparedTxnDBMock(mock_db, txn_db_options));
+
+ // 1. Set the initial values for max, prepared, and snapshots
+ SequenceNumber zero_max = 0l;
+ // Set the initial list of prepared txns
+ const std::vector<SequenceNumber> initial_prepared = {10, 30, 50, 100,
+ 150, 200, 250};
+ for (auto p : initial_prepared) {
+ wp_db->AddPrepared(p);
+ }
+ // This updates the max value and also set old prepared
+ SequenceNumber init_max = 100;
+ wp_db->AdvanceMaxEvictedSeq(zero_max, init_max);
+ const std::vector<SequenceNumber> initial_snapshots = {20, 40};
+ wp_db->SetDBSnapshots(initial_snapshots);
+ // This will update the internal cache of snapshots from the DB
+ wp_db->UpdateSnapshots(initial_snapshots, init_max);
+
+ // 2. Invoke AdvanceMaxEvictedSeq
+ const std::vector<SequenceNumber> latest_snapshots = {20, 110, 220, 300};
+ wp_db->SetDBSnapshots(latest_snapshots);
+ SequenceNumber new_max = 200;
+ wp_db->AdvanceMaxEvictedSeq(init_max, new_max);
+
+ // 3. Verify that the state matches with AdvanceMaxEvictedSeq contract
+ // a. max should be updated to new_max
+ ASSERT_EQ(wp_db->max_evicted_seq_, new_max);
+ // b. delayed prepared should contain every txn <= max and prepared should
+ // only contain txns > max
+ auto it = initial_prepared.begin();
+ for (; it != initial_prepared.end() && *it <= new_max; ++it) {
+ ASSERT_EQ(1, wp_db->delayed_prepared_.erase(*it));
+ }
+ ASSERT_TRUE(wp_db->delayed_prepared_.empty());
+ for (; it != initial_prepared.end() && !wp_db->prepared_txns_.empty();
+ ++it, wp_db->prepared_txns_.pop()) {
+ ASSERT_EQ(*it, wp_db->prepared_txns_.top());
+ }
+ ASSERT_TRUE(it == initial_prepared.end());
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ // c. snapshots should contain everything below new_max
+ auto sit = latest_snapshots.begin();
+ for (size_t i = 0; sit != latest_snapshots.end() && *sit <= new_max &&
+ i < wp_db->snapshots_total_;
+ sit++, i++) {
+ ASSERT_TRUE(i < wp_db->snapshots_total_);
+ // This test is in small scale and the list of snapshots are assumed to be
+ // within the cache size limit. This is just a safety check to double check
+ // that assumption.
+ ASSERT_TRUE(i < wp_db->SNAPSHOT_CACHE_SIZE);
+ ASSERT_EQ(*sit, wp_db->snapshot_cache_[i]);
+ }
+}
+
+// A new snapshot should always be always larger than max_evicted_seq_
+// Otherwise the snapshot does not go through AdvanceMaxEvictedSeq
+TEST_P(WritePreparedTransactionTest, NewSnapshotLargerThanMax) {
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ Transaction* txn0 = db->BeginTransaction(woptions, txn_options);
+ ASSERT_OK(txn0->Put(Slice("key"), Slice("value")));
+ ASSERT_OK(txn0->Commit());
+ const SequenceNumber seq = txn0->GetId(); // is also prepare seq
+ delete txn0;
+ std::vector<Transaction*> txns;
+ // Inc seq without committing anything
+ for (int i = 0; i < 10; i++) {
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ ASSERT_OK(txn->SetName("xid" + std::to_string(i)));
+ ASSERT_OK(txn->Put(Slice("key" + std::to_string(i)), Slice("value")));
+ ASSERT_OK(txn->Prepare());
+ txns.push_back(txn);
+ }
+
+ // The new commit is seq + 10
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ auto snap = wp_db->GetSnapshot();
+ const SequenceNumber last_seq = snap->GetSequenceNumber();
+ wp_db->ReleaseSnapshot(snap);
+ ASSERT_LT(seq, last_seq);
+ // Otherwise our test is not effective
+ ASSERT_LT(last_seq - seq, wp_db->INC_STEP_FOR_MAX_EVICTED);
+
+ // Evict seq out of commit cache
+ const SequenceNumber overwrite_seq = seq + wp_db->COMMIT_CACHE_SIZE;
+ // Check that the next write could make max go beyond last
+ auto last_max = wp_db->max_evicted_seq_.load();
+ wp_db->AddCommitted(overwrite_seq, overwrite_seq);
+ // Check that eviction has advanced the max
+ ASSERT_LT(last_max, wp_db->max_evicted_seq_.load());
+ // Check that the new max has not advanced the last seq
+ ASSERT_LT(wp_db->max_evicted_seq_.load(), last_seq);
+ for (auto txn : txns) {
+ txn->Rollback();
+ delete txn;
+ }
+}
+
+// A new snapshot should always be always larger than max_evicted_seq_
+// In very rare cases max could be below last published seq. Test that
+// taking snapshot will wait for max to catch up.
+TEST_P(WritePreparedTransactionTest, MaxCatchupWithNewSnapshot) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // only 1 entry => frequent eviction
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WriteOptions woptions;
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+
+ const int writes = 50;
+ const int batch_cnt = 4;
+ ROCKSDB_NAMESPACE::port::Thread t1([&]() {
+ for (int i = 0; i < writes; i++) {
+ WriteBatch batch;
+ // For duplicate keys cause 4 commit entries, each evicting an entry that
+ // is not published yet, thus causing max evicted seq go higher than last
+ // published.
+ for (int b = 0; b < batch_cnt; b++) {
+ batch.Put("foo", "foo");
+ }
+ db->Write(woptions, &batch);
+ }
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread t2([&]() {
+ while (wp_db->max_evicted_seq_ == 0) { // wait for insert thread
+ std::this_thread::yield();
+ }
+ for (int i = 0; i < 10; i++) {
+ SequenceNumber max_lower_bound = wp_db->max_evicted_seq_;
+ auto snap = db->GetSnapshot();
+ if (snap->GetSequenceNumber() != 0) {
+ // Value of max_evicted_seq_ when snapshot was taken in unknown. We thus
+ // compare with the lower bound instead as an approximation.
+ ASSERT_LT(max_lower_bound, snap->GetSequenceNumber());
+ } // seq 0 is ok to be less than max since nothing is visible to it
+ db->ReleaseSnapshot(snap);
+ }
+ });
+
+ t1.join();
+ t2.join();
+
+ // Make sure that the test has worked and seq number has advanced as we
+ // thought
+ auto snap = db->GetSnapshot();
+ ASSERT_GT(snap->GetSequenceNumber(), batch_cnt * writes - 1);
+ db->ReleaseSnapshot(snap);
+}
+
+// Test that reads without snapshots would not hit an undefined state
+TEST_P(WritePreparedTransactionTest, MaxCatchupWithUnbackedSnapshot) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // only 1 entry => frequent eviction
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WriteOptions woptions;
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+
+ const int writes = 50;
+ ROCKSDB_NAMESPACE::port::Thread t1([&]() {
+ for (int i = 0; i < writes; i++) {
+ WriteBatch batch;
+ batch.Put("key", "foo");
+ db->Write(woptions, &batch);
+ }
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread t2([&]() {
+ while (wp_db->max_evicted_seq_ == 0) { // wait for insert thread
+ std::this_thread::yield();
+ }
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ TransactionOptions txn_options;
+ for (int i = 0; i < 10; i++) {
+ auto s = db->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_TRUE(s.ok() || s.IsTryAgain());
+ pinnable_val.Reset();
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ s = txn->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_TRUE(s.ok() || s.IsTryAgain());
+ pinnable_val.Reset();
+ std::vector<std::string> values;
+ auto s_vec =
+ txn->MultiGet(ropt, {db->DefaultColumnFamily()}, {"key"}, &values);
+ ASSERT_EQ(1, values.size());
+ ASSERT_EQ(1, s_vec.size());
+ s = s_vec[0];
+ ASSERT_TRUE(s.ok() || s.IsTryAgain());
+ Slice key("key");
+ txn->MultiGet(ropt, db->DefaultColumnFamily(), 1, &key, &pinnable_val, &s,
+ true);
+ ASSERT_TRUE(s.ok() || s.IsTryAgain());
+ delete txn;
+ }
+ });
+
+ t1.join();
+ t2.join();
+
+ // Make sure that the test has worked and seq number has advanced as we
+ // thought
+ auto snap = db->GetSnapshot();
+ ASSERT_GT(snap->GetSequenceNumber(), writes - 1);
+ db->ReleaseSnapshot(snap);
+}
+
+// Check that old_commit_map_ cleanup works correctly if the snapshot equals
+// max_evicted_seq_.
+TEST_P(WritePreparedTransactionTest, CleanupSnapshotEqualToMax) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // only 1 entry => frequent eviction
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WriteOptions woptions;
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ // Insert something to increase seq
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ auto snap = db->GetSnapshot();
+ auto snap_seq = snap->GetSequenceNumber();
+ // Another insert should trigger eviction + load snapshot from db
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ // This is the scenario that we check agaisnt
+ ASSERT_EQ(snap_seq, wp_db->max_evicted_seq_);
+ // old_commit_map_ now has some data that needs gc
+ ASSERT_EQ(1, wp_db->snapshots_total_);
+ ASSERT_EQ(1, wp_db->old_commit_map_.size());
+
+ db->ReleaseSnapshot(snap);
+
+ // Another insert should trigger eviction + load snapshot from db
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+
+ // the snapshot and related metadata must be properly garbage collected
+ ASSERT_EQ(0, wp_db->snapshots_total_);
+ ASSERT_TRUE(wp_db->snapshots_all_.empty());
+ ASSERT_EQ(0, wp_db->old_commit_map_.size());
+}
+
+TEST_P(WritePreparedTransactionTest, AdvanceSeqByOne) {
+ auto snap = db->GetSnapshot();
+ auto seq1 = snap->GetSequenceNumber();
+ db->ReleaseSnapshot(snap);
+
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ wp_db->AdvanceSeqByOne();
+
+ snap = db->GetSnapshot();
+ auto seq2 = snap->GetSequenceNumber();
+ db->ReleaseSnapshot(snap);
+
+ ASSERT_LT(seq1, seq2);
+}
+
+// Test that the txn Initilize calls the overridden functions
+TEST_P(WritePreparedTransactionTest, TxnInitialize) {
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ ASSERT_OK(db->Put(write_options, "key", "value"));
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(Slice("key"), Slice("value1")));
+ ASSERT_OK(txn0->Prepare());
+
+ // SetSnapshot is overridden to update min_uncommitted_
+ txn_options.set_snapshot = true;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ auto snap = txn1->GetSnapshot();
+ auto snap_impl = reinterpret_cast<const SnapshotImpl*>(snap);
+ // If ::Initialize calls the overriden SetSnapshot, min_uncommitted_ must be
+ // udpated
+ ASSERT_GT(snap_impl->min_uncommitted_, kMinUnCommittedSeq);
+
+ txn0->Rollback();
+ txn1->Rollback();
+ delete txn0;
+ delete txn1;
+}
+
+// This tests that transactions with duplicate keys perform correctly after max
+// is advancing their prepared sequence numbers. This will not be the case if
+// for example the txn does not add the prepared seq for the second sub-batch to
+// the PreparedHeap structure.
+TEST_P(WritePreparedTransactionTest, AdvanceMaxEvictedSeqWithDuplicates) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 1; // disable commit cache
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn0->SetName("xid"));
+ ASSERT_OK(txn0->Put(Slice("key"), Slice("value1")));
+ ASSERT_OK(txn0->Put(Slice("key"), Slice("value2")));
+ ASSERT_OK(txn0->Prepare());
+
+ ASSERT_OK(db->Put(write_options, "key2", "value"));
+ // Will cause max advance due to disabled commit cache
+ ASSERT_OK(db->Put(write_options, "key3", "value"));
+
+ auto s = db->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ delete txn0;
+
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ wp_db->db_impl_->FlushWAL(true);
+ wp_db->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ s = db->Get(ropt, db->DefaultColumnFamily(), "key", &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+
+ txn0 = db->GetTransactionByName("xid");
+ ASSERT_OK(txn0->Rollback());
+ delete txn0;
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+// Stress SmallestUnCommittedSeq, which reads from both prepared_txns_ and
+// delayed_prepared_, when is run concurrently with advancing max_evicted_seq,
+// which moves prepared txns from prepared_txns_ to delayed_prepared_.
+TEST_P(WritePreparedTransactionTest, SmallestUnCommittedSeq) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 1; // disable commit cache
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ std::vector<Transaction*> txns, committed_txns;
+
+ const int cnt = 100;
+ for (int i = 0; i < cnt; i++) {
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn->SetName("xid" + ToString(i)));
+ auto key = "key1" + ToString(i);
+ auto value = "value1" + ToString(i);
+ ASSERT_OK(txn->Put(Slice(key), Slice(value)));
+ ASSERT_OK(txn->Prepare());
+ txns.push_back(txn);
+ }
+
+ port::Mutex mutex;
+ Random rnd(1103);
+ ROCKSDB_NAMESPACE::port::Thread commit_thread([&]() {
+ for (int i = 0; i < cnt; i++) {
+ uint32_t index = rnd.Uniform(cnt - i);
+ Transaction* txn;
+ {
+ MutexLock l(&mutex);
+ txn = txns[index];
+ txns.erase(txns.begin() + index);
+ }
+ // Since commit cache is practically disabled, commit results in immediate
+ // advance in max_evicted_seq_ and subsequently moving some prepared txns
+ // to delayed_prepared_.
+ txn->Commit();
+ committed_txns.push_back(txn);
+ }
+ });
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ while (1) {
+ MutexLock l(&mutex);
+ if (txns.empty()) {
+ break;
+ }
+ auto min_uncommitted = wp_db->SmallestUnCommittedSeq();
+ ASSERT_LE(min_uncommitted, (*txns.begin())->GetId());
+ }
+ });
+
+ commit_thread.join();
+ read_thread.join();
+ for (auto txn : committed_txns) {
+ delete txn;
+ }
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+TEST_P(SeqAdvanceConcurrentTest, SeqAdvanceConcurrent) {
+ // Given the sequential run of txns, with this timeout we should never see a
+ // deadlock nor a timeout unless we have a key conflict, which should be
+ // almost infeasible.
+ txn_db_options.transaction_lock_timeout = 1000;
+ txn_db_options.default_lock_timeout = 1000;
+ ReOpen();
+ FlushOptions fopt;
+
+ // Number of different txn types we use in this test
+ const size_t type_cnt = 5;
+ // The size of the first write group
+ // TODO(myabandeh): This should be increase for pre-release tests
+ const size_t first_group_size = 2;
+ // Total number of txns we run in each test
+ // TODO(myabandeh): This should be increase for pre-release tests
+ const size_t txn_cnt = first_group_size + 1;
+
+ size_t base[txn_cnt + 1] = {
+ 1,
+ };
+ for (size_t bi = 1; bi <= txn_cnt; bi++) {
+ base[bi] = base[bi - 1] * type_cnt;
+ }
+ const size_t max_n = static_cast<size_t>(std::pow(type_cnt, txn_cnt));
+ printf("Number of cases being tested is %" ROCKSDB_PRIszt "\n", max_n);
+ for (size_t n = 0; n < max_n; n++, ReOpen()) {
+ if (n % split_cnt_ != split_id_) continue;
+ if (n % 1000 == 0) {
+ printf("Tested %" ROCKSDB_PRIszt " cases so far\n", n);
+ }
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ auto seq = db_impl->TEST_GetLastVisibleSequence();
+ with_empty_commits = 0;
+ exp_seq = seq;
+ // This is increased before writing the batch for commit
+ commit_writes = 0;
+ // This is increased before txn starts linking if it expects to do a commit
+ // eventually
+ expected_commits = 0;
+ std::vector<port::Thread> threads;
+
+ linked = 0;
+ std::atomic<bool> batch_formed(false);
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WriteThread::EnterAsBatchGroupLeader:End",
+ [&](void* /*arg*/) { batch_formed = true; });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
+ "WriteThread::JoinBatchGroup:Wait", [&](void* /*arg*/) {
+ linked++;
+ if (linked == 1) {
+ // Wait until the others are linked too.
+ while (linked < first_group_size) {
+ }
+ } else if (linked == 1 + first_group_size) {
+ // Make the 2nd batch of the rest of writes plus any followup
+ // commits from the first batch
+ while (linked < txn_cnt + commit_writes) {
+ }
+ }
+ // Then we will have one or more batches consisting of follow-up
+ // commits from the 2nd batch. There is a bit of non-determinism here
+ // but it should be tolerable.
+ });
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ for (size_t bi = 0; bi < txn_cnt; bi++) {
+ // get the bi-th digit in number system based on type_cnt
+ size_t d = (n % base[bi + 1]) / base[bi];
+ switch (d) {
+ case 0:
+ threads.emplace_back(txn_t0, bi);
+ break;
+ case 1:
+ threads.emplace_back(txn_t1, bi);
+ break;
+ case 2:
+ threads.emplace_back(txn_t2, bi);
+ break;
+ case 3:
+ threads.emplace_back(txn_t3, bi);
+ break;
+ case 4:
+ threads.emplace_back(txn_t3, bi);
+ break;
+ default:
+ assert(false);
+ }
+ // wait to be linked
+ while (linked.load() <= bi) {
+ }
+ // after a queue of size first_group_size
+ if (bi + 1 == first_group_size) {
+ while (!batch_formed) {
+ }
+ // to make it more deterministic, wait until the commits are linked
+ while (linked.load() <= bi + expected_commits) {
+ }
+ }
+ }
+ for (auto& t : threads) {
+ t.join();
+ }
+ if (options.two_write_queues) {
+ // In this case none of the above scheduling tricks to deterministically
+ // form merged batches works because the writes go to separate queues.
+ // This would result in different write groups in each run of the test. We
+ // still keep the test since although non-deterministic and hard to debug,
+ // it is still useful to have.
+ // TODO(myabandeh): Add a deterministic unit test for two_write_queues
+ }
+
+ // Check if memtable inserts advanced seq number as expected
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_EQ(exp_seq, seq);
+
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+
+ // Check if recovery preserves the last sequence number
+ db_impl->FlushWAL(true);
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->TEST_GetLastVisibleSequence();
+ ASSERT_LE(exp_seq, seq + with_empty_commits);
+
+ // Check if flush preserves the last sequence number
+ db_impl->Flush(fopt);
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_LE(exp_seq, seq + with_empty_commits);
+
+ // Check if recovery after flush preserves the last sequence number
+ db_impl->FlushWAL(true);
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ seq = db_impl->GetLatestSequenceNumber();
+ ASSERT_LE(exp_seq, seq + with_empty_commits);
+ }
+}
+
+// Run a couple of different txns among them some uncommitted. Restart the db at
+// a couple points to check whether the list of uncommitted txns are recovered
+// properly.
+TEST_P(WritePreparedTransactionTest, BasicRecovery) {
+ options.disable_auto_compactions = true;
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+
+ txn_t0(0);
+
+ TransactionOptions txn_options;
+ WriteOptions write_options;
+ size_t index = 1000;
+ Transaction* txn0 = db->BeginTransaction(write_options, txn_options);
+ auto istr0 = std::to_string(index);
+ auto s = txn0->SetName("xid" + istr0);
+ ASSERT_OK(s);
+ s = txn0->Put(Slice("foo0" + istr0), Slice("bar0" + istr0));
+ ASSERT_OK(s);
+ s = txn0->Prepare();
+ auto prep_seq_0 = txn0->GetId();
+
+ txn_t1(0);
+
+ index++;
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ auto istr1 = std::to_string(index);
+ s = txn1->SetName("xid" + istr1);
+ ASSERT_OK(s);
+ s = txn1->Put(Slice("foo1" + istr1), Slice("bar"));
+ ASSERT_OK(s);
+ s = txn1->Prepare();
+ auto prep_seq_1 = txn1->GetId();
+
+ txn_t2(0);
+
+ ReadOptions ropt;
+ PinnableSlice pinnable_val;
+ // Check the value is not committed before restart
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0" + istr0, &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ pinnable_val.Reset();
+
+ delete txn0;
+ delete txn1;
+ wp_db->db_impl_->FlushWAL(true);
+ wp_db->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ // After recovery, all the uncommitted txns (0 and 1) should be inserted into
+ // delayed_prepared_
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ ASSERT_FALSE(wp_db->delayed_prepared_empty_);
+ ASSERT_LE(prep_seq_0, wp_db->max_evicted_seq_);
+ ASSERT_LE(prep_seq_1, wp_db->max_evicted_seq_);
+ {
+ ReadLock rl(&wp_db->prepared_mutex_);
+ ASSERT_EQ(2, wp_db->delayed_prepared_.size());
+ ASSERT_TRUE(wp_db->delayed_prepared_.find(prep_seq_0) !=
+ wp_db->delayed_prepared_.end());
+ ASSERT_TRUE(wp_db->delayed_prepared_.find(prep_seq_1) !=
+ wp_db->delayed_prepared_.end());
+ }
+
+ // Check the value is still not committed after restart
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0" + istr0, &pinnable_val);
+ ASSERT_TRUE(s.IsNotFound());
+ pinnable_val.Reset();
+
+ txn_t3(0);
+
+ // Test that a recovered txns will be properly marked committed for the next
+ // recovery
+ txn1 = db->GetTransactionByName("xid" + istr1);
+ ASSERT_NE(txn1, nullptr);
+ txn1->Commit();
+ delete txn1;
+
+ index++;
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ auto istr2 = std::to_string(index);
+ s = txn2->SetName("xid" + istr2);
+ ASSERT_OK(s);
+ s = txn2->Put(Slice("foo2" + istr2), Slice("bar"));
+ ASSERT_OK(s);
+ s = txn2->Prepare();
+ auto prep_seq_2 = txn2->GetId();
+
+ delete txn2;
+ wp_db->db_impl_->FlushWAL(true);
+ wp_db->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ ASSERT_FALSE(wp_db->delayed_prepared_empty_);
+
+ // 0 and 2 are prepared and 1 is committed
+ {
+ ReadLock rl(&wp_db->prepared_mutex_);
+ ASSERT_EQ(2, wp_db->delayed_prepared_.size());
+ const auto& end = wp_db->delayed_prepared_.end();
+ ASSERT_NE(wp_db->delayed_prepared_.find(prep_seq_0), end);
+ ASSERT_EQ(wp_db->delayed_prepared_.find(prep_seq_1), end);
+ ASSERT_NE(wp_db->delayed_prepared_.find(prep_seq_2), end);
+ }
+ ASSERT_LE(prep_seq_0, wp_db->max_evicted_seq_);
+ ASSERT_LE(prep_seq_2, wp_db->max_evicted_seq_);
+
+ // Commit all the remaining txns
+ txn0 = db->GetTransactionByName("xid" + istr0);
+ ASSERT_NE(txn0, nullptr);
+ txn0->Commit();
+ txn2 = db->GetTransactionByName("xid" + istr2);
+ ASSERT_NE(txn2, nullptr);
+ txn2->Commit();
+
+ // Check the value is committed after commit
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0" + istr0, &pinnable_val);
+ ASSERT_TRUE(s.ok());
+ ASSERT_TRUE(pinnable_val == ("bar0" + istr0));
+ pinnable_val.Reset();
+
+ delete txn0;
+ delete txn2;
+ wp_db->db_impl_->FlushWAL(true);
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ ASSERT_TRUE(wp_db->delayed_prepared_empty_);
+
+ // Check the value is still committed after recovery
+ s = db->Get(ropt, db->DefaultColumnFamily(), "foo0" + istr0, &pinnable_val);
+ ASSERT_TRUE(s.ok());
+ ASSERT_TRUE(pinnable_val == ("bar0" + istr0));
+ pinnable_val.Reset();
+}
+
+// After recovery the commit map is empty while the max is set. The code would
+// go through a different path which requires a separate test. Test that the
+// committed data before the restart is visible to all snapshots.
+TEST_P(WritePreparedTransactionTest, IsInSnapshotEmptyMap) {
+ for (bool end_with_prepare : {false, true}) {
+ ReOpen();
+ WriteOptions woptions;
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ SequenceNumber prepare_seq = kMaxSequenceNumber;
+ if (end_with_prepare) {
+ TransactionOptions txn_options;
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ ASSERT_OK(txn->SetName("xid0"));
+ ASSERT_OK(txn->Prepare());
+ prepare_seq = txn->GetId();
+ delete txn;
+ }
+ dynamic_cast<WritePreparedTxnDB*>(db)->TEST_Crash();
+ auto db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ db_impl->FlushWAL(true);
+ ReOpenNoDelete();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ assert(wp_db != nullptr);
+ ASSERT_GT(wp_db->max_evicted_seq_, 0); // max after recovery
+ // Take a snapshot right after recovery
+ const Snapshot* snap = db->GetSnapshot();
+ auto snap_seq = snap->GetSequenceNumber();
+ ASSERT_GT(snap_seq, 0);
+
+ for (SequenceNumber seq = 0;
+ seq <= wp_db->max_evicted_seq_ && seq != prepare_seq; seq++) {
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq));
+ }
+ if (end_with_prepare) {
+ ASSERT_FALSE(wp_db->IsInSnapshot(prepare_seq, snap_seq));
+ }
+ // trivial check
+ ASSERT_FALSE(wp_db->IsInSnapshot(snap_seq + 1, snap_seq));
+
+ db->ReleaseSnapshot(snap);
+
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ // Take a snapshot after some writes
+ snap = db->GetSnapshot();
+ snap_seq = snap->GetSequenceNumber();
+ for (SequenceNumber seq = 0;
+ seq <= wp_db->max_evicted_seq_ && seq != prepare_seq; seq++) {
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq));
+ }
+ if (end_with_prepare) {
+ ASSERT_FALSE(wp_db->IsInSnapshot(prepare_seq, snap_seq));
+ }
+ // trivial check
+ ASSERT_FALSE(wp_db->IsInSnapshot(snap_seq + 1, snap_seq));
+
+ db->ReleaseSnapshot(snap);
+ }
+}
+
+// Shows the contract of IsInSnapshot when called on invalid/released snapshots
+TEST_P(WritePreparedTransactionTest, IsInSnapshotReleased) {
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ WriteOptions woptions;
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ // snap seq = 1
+ const Snapshot* snap1 = db->GetSnapshot();
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ ASSERT_OK(db->Put(woptions, "key", "value"));
+ // snap seq = 3
+ const Snapshot* snap2 = db->GetSnapshot();
+ const SequenceNumber seq = 1;
+ // Evict seq out of commit cache
+ size_t overwrite_seq = wp_db->COMMIT_CACHE_SIZE + seq;
+ wp_db->AddCommitted(overwrite_seq, overwrite_seq);
+ SequenceNumber snap_seq;
+ uint64_t min_uncommitted = kMinUnCommittedSeq;
+ bool released;
+
+ released = false;
+ snap_seq = snap1->GetSequenceNumber();
+ ASSERT_LE(seq, snap_seq);
+ // Valid snapshot lower than max
+ ASSERT_LE(snap_seq, wp_db->max_evicted_seq_);
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq, min_uncommitted, &released));
+ ASSERT_FALSE(released);
+
+ released = false;
+ snap_seq = snap1->GetSequenceNumber();
+ // Invaid snapshot lower than max
+ ASSERT_LE(snap_seq + 1, wp_db->max_evicted_seq_);
+ ASSERT_TRUE(
+ wp_db->IsInSnapshot(seq, snap_seq + 1, min_uncommitted, &released));
+ ASSERT_TRUE(released);
+
+ db->ReleaseSnapshot(snap1);
+
+ released = false;
+ // Released snapshot lower than max
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq, min_uncommitted, &released));
+ // The release does not take affect until the next max advance
+ ASSERT_FALSE(released);
+
+ released = false;
+ // Invaid snapshot lower than max
+ ASSERT_TRUE(
+ wp_db->IsInSnapshot(seq, snap_seq + 1, min_uncommitted, &released));
+ ASSERT_TRUE(released);
+
+ // This make the snapshot release to reflect in txn db structures
+ wp_db->AdvanceMaxEvictedSeq(wp_db->max_evicted_seq_,
+ wp_db->max_evicted_seq_ + 1);
+
+ released = false;
+ // Released snapshot lower than max
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq, min_uncommitted, &released));
+ ASSERT_TRUE(released);
+
+ released = false;
+ // Invaid snapshot lower than max
+ ASSERT_TRUE(
+ wp_db->IsInSnapshot(seq, snap_seq + 1, min_uncommitted, &released));
+ ASSERT_TRUE(released);
+
+ snap_seq = snap2->GetSequenceNumber();
+
+ released = false;
+ // Unreleased snapshot lower than max
+ ASSERT_TRUE(wp_db->IsInSnapshot(seq, snap_seq, min_uncommitted, &released));
+ ASSERT_FALSE(released);
+
+ db->ReleaseSnapshot(snap2);
+}
+
+// Test WritePreparedTxnDB's IsInSnapshot against different ordering of
+// snapshot, max_committed_seq_, prepared, and commit entries.
+TEST_P(WritePreparedTransactionTest, IsInSnapshot) {
+ WriteOptions wo;
+ // Use small commit cache to trigger lots of eviction and fast advance of
+ // max_evicted_seq_
+ const size_t commit_cache_bits = 3;
+ // Same for snapshot cache size
+ const size_t snapshot_cache_bits = 2;
+
+ // Take some preliminary snapshots first. This is to stress the data structure
+ // that holds the old snapshots as it will be designed to be efficient when
+ // only a few snapshots are below the max_evicted_seq_.
+ for (int max_snapshots = 1; max_snapshots < 20; max_snapshots++) {
+ // Leave some gap between the preliminary snapshots and the final snapshot
+ // that we check. This should test for also different overlapping scenarios
+ // between the last snapshot and the commits.
+ for (int max_gap = 1; max_gap < 10; max_gap++) {
+ // Since we do not actually write to db, we mock the seq as it would be
+ // increased by the db. The only exception is that we need db seq to
+ // advance for our snapshots. for which we apply a dummy put each time we
+ // increase our mock of seq.
+ uint64_t seq = 0;
+ // At each step we prepare a txn and then we commit it in the next txn.
+ // This emulates the consecutive transactions that write to the same key
+ uint64_t cur_txn = 0;
+ // Number of snapshots taken so far
+ int num_snapshots = 0;
+ // Number of gaps applied so far
+ int gap_cnt = 0;
+ // The final snapshot that we will inspect
+ uint64_t snapshot = 0;
+ bool found_committed = false;
+ // To stress the data structure that maintain prepared txns, at each cycle
+ // we add a new prepare txn. These do not mean to be committed for
+ // snapshot inspection.
+ std::set<uint64_t> prepared;
+ // We keep the list of txns committed before we take the last snapshot.
+ // These should be the only seq numbers that will be found in the snapshot
+ std::set<uint64_t> committed_before;
+ // The set of commit seq numbers to be excluded from IsInSnapshot queries
+ std::set<uint64_t> commit_seqs;
+ DBImpl* mock_db = new DBImpl(options, dbname);
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ std::unique_ptr<WritePreparedTxnDBMock> wp_db(
+ new WritePreparedTxnDBMock(mock_db, txn_db_options));
+ // We continue until max advances a bit beyond the snapshot.
+ while (!snapshot || wp_db->max_evicted_seq_ < snapshot + 100) {
+ // do prepare for a transaction
+ seq++;
+ wp_db->AddPrepared(seq);
+ prepared.insert(seq);
+
+ // If cur_txn is not started, do prepare for it.
+ if (!cur_txn) {
+ seq++;
+ cur_txn = seq;
+ wp_db->AddPrepared(cur_txn);
+ } else { // else commit it
+ seq++;
+ wp_db->AddCommitted(cur_txn, seq);
+ wp_db->RemovePrepared(cur_txn);
+ commit_seqs.insert(seq);
+ if (!snapshot) {
+ committed_before.insert(cur_txn);
+ }
+ cur_txn = 0;
+ }
+
+ if (num_snapshots < max_snapshots - 1) {
+ // Take preliminary snapshots
+ wp_db->TakeSnapshot(seq);
+ num_snapshots++;
+ } else if (gap_cnt < max_gap) {
+ // Wait for some gap before taking the final snapshot
+ gap_cnt++;
+ } else if (!snapshot) {
+ // Take the final snapshot if it is not already taken
+ snapshot = seq;
+ wp_db->TakeSnapshot(snapshot);
+ num_snapshots++;
+ }
+
+ // If the snapshot is taken, verify seq numbers visible to it. We redo
+ // it at each cycle to test that the system is still sound when
+ // max_evicted_seq_ advances.
+ if (snapshot) {
+ for (uint64_t s = 1;
+ s <= seq && commit_seqs.find(s) == commit_seqs.end(); s++) {
+ bool was_committed =
+ (committed_before.find(s) != committed_before.end());
+ bool is_in_snapshot = wp_db->IsInSnapshot(s, snapshot);
+ if (was_committed != is_in_snapshot) {
+ printf("max_snapshots %d max_gap %d seq %" PRIu64 " max %" PRIu64
+ " snapshot %" PRIu64
+ " gap_cnt %d num_snapshots %d s %" PRIu64 "\n",
+ max_snapshots, max_gap, seq,
+ wp_db->max_evicted_seq_.load(), snapshot, gap_cnt,
+ num_snapshots, s);
+ }
+ ASSERT_EQ(was_committed, is_in_snapshot);
+ found_committed = found_committed || is_in_snapshot;
+ }
+ }
+ }
+ // Safety check to make sure the test actually ran
+ ASSERT_TRUE(found_committed);
+ // As an extra check, check if prepared set will be properly empty after
+ // they are committed.
+ if (cur_txn) {
+ wp_db->AddCommitted(cur_txn, seq);
+ wp_db->RemovePrepared(cur_txn);
+ }
+ for (auto p : prepared) {
+ wp_db->AddCommitted(p, seq);
+ wp_db->RemovePrepared(p);
+ }
+ ASSERT_TRUE(wp_db->delayed_prepared_.empty());
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ }
+ }
+}
+
+void ASSERT_SAME(ReadOptions roptions, TransactionDB* db, Status exp_s,
+ PinnableSlice& exp_v, Slice key) {
+ Status s;
+ PinnableSlice v;
+ s = db->Get(roptions, db->DefaultColumnFamily(), key, &v);
+ ASSERT_TRUE(exp_s == s);
+ ASSERT_TRUE(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ ASSERT_TRUE(exp_v == v);
+ }
+
+ // Try with MultiGet API too
+ std::vector<std::string> values;
+ auto s_vec =
+ db->MultiGet(roptions, {db->DefaultColumnFamily()}, {key}, &values);
+ ASSERT_EQ(1, values.size());
+ ASSERT_EQ(1, s_vec.size());
+ s = s_vec[0];
+ ASSERT_TRUE(exp_s == s);
+ ASSERT_TRUE(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ ASSERT_TRUE(exp_v == values[0]);
+ }
+}
+
+void ASSERT_SAME(TransactionDB* db, Status exp_s, PinnableSlice& exp_v,
+ Slice key) {
+ ASSERT_SAME(ReadOptions(), db, exp_s, exp_v, key);
+}
+
+TEST_P(WritePreparedTransactionTest, Rollback) {
+ ReadOptions roptions;
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ const size_t num_keys = 4;
+ const size_t num_values = 5;
+ for (size_t ikey = 1; ikey <= num_keys; ikey++) {
+ for (size_t ivalue = 0; ivalue < num_values; ivalue++) {
+ for (bool crash : {false, true}) {
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ std::string key_str = "key" + ToString(ikey);
+ switch (ivalue) {
+ case 0:
+ break;
+ case 1:
+ ASSERT_OK(db->Put(woptions, key_str, "initvalue1"));
+ break;
+ case 2:
+ ASSERT_OK(db->Merge(woptions, key_str, "initvalue2"));
+ break;
+ case 3:
+ ASSERT_OK(db->Delete(woptions, key_str));
+ break;
+ case 4:
+ ASSERT_OK(db->SingleDelete(woptions, key_str));
+ break;
+ default:
+ assert(0);
+ }
+
+ PinnableSlice v1;
+ auto s1 =
+ db->Get(roptions, db->DefaultColumnFamily(), Slice("key1"), &v1);
+ PinnableSlice v2;
+ auto s2 =
+ db->Get(roptions, db->DefaultColumnFamily(), Slice("key2"), &v2);
+ PinnableSlice v3;
+ auto s3 =
+ db->Get(roptions, db->DefaultColumnFamily(), Slice("key3"), &v3);
+ PinnableSlice v4;
+ auto s4 =
+ db->Get(roptions, db->DefaultColumnFamily(), Slice("key4"), &v4);
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ auto s = txn->SetName("xid0");
+ ASSERT_OK(s);
+ s = txn->Put(Slice("key1"), Slice("value1"));
+ ASSERT_OK(s);
+ s = txn->Merge(Slice("key2"), Slice("value2"));
+ ASSERT_OK(s);
+ s = txn->Delete(Slice("key3"));
+ ASSERT_OK(s);
+ s = txn->SingleDelete(Slice("key4"));
+ ASSERT_OK(s);
+ s = txn->Prepare();
+ ASSERT_OK(s);
+
+ {
+ ReadLock rl(&wp_db->prepared_mutex_);
+ ASSERT_FALSE(wp_db->prepared_txns_.empty());
+ ASSERT_EQ(txn->GetId(), wp_db->prepared_txns_.top());
+ }
+
+ ASSERT_SAME(db, s1, v1, "key1");
+ ASSERT_SAME(db, s2, v2, "key2");
+ ASSERT_SAME(db, s3, v3, "key3");
+ ASSERT_SAME(db, s4, v4, "key4");
+
+ if (crash) {
+ delete txn;
+ auto db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ db_impl->FlushWAL(true);
+ dynamic_cast<WritePreparedTxnDB*>(db)->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+ wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ txn = db->GetTransactionByName("xid0");
+ ASSERT_FALSE(wp_db->delayed_prepared_empty_);
+ ReadLock rl(&wp_db->prepared_mutex_);
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ ASSERT_FALSE(wp_db->delayed_prepared_.empty());
+ ASSERT_TRUE(wp_db->delayed_prepared_.find(txn->GetId()) !=
+ wp_db->delayed_prepared_.end());
+ }
+
+ ASSERT_SAME(db, s1, v1, "key1");
+ ASSERT_SAME(db, s2, v2, "key2");
+ ASSERT_SAME(db, s3, v3, "key3");
+ ASSERT_SAME(db, s4, v4, "key4");
+
+ s = txn->Rollback();
+ ASSERT_OK(s);
+
+ {
+ ASSERT_TRUE(wp_db->delayed_prepared_empty_);
+ ReadLock rl(&wp_db->prepared_mutex_);
+ ASSERT_TRUE(wp_db->prepared_txns_.empty());
+ ASSERT_TRUE(wp_db->delayed_prepared_.empty());
+ }
+
+ ASSERT_SAME(db, s1, v1, "key1");
+ ASSERT_SAME(db, s2, v2, "key2");
+ ASSERT_SAME(db, s3, v3, "key3");
+ ASSERT_SAME(db, s4, v4, "key4");
+ delete txn;
+ }
+ }
+ }
+}
+
+TEST_P(WritePreparedTransactionTest, DisableGCDuringRecovery) {
+ // Use large buffer to avoid memtable flush after 1024 insertions
+ options.write_buffer_size = 1024 * 1024;
+ ReOpen();
+ std::vector<KeyVersion> versions;
+ uint64_t seq = 0;
+ for (uint64_t i = 1; i <= 1024; i++) {
+ std::string v = "bar" + ToString(i);
+ ASSERT_OK(db->Put(WriteOptions(), "foo", v));
+ VerifyKeys({{"foo", v}});
+ seq++; // one for the key/value
+ KeyVersion kv = {"foo", v, seq, kTypeValue};
+ if (options.two_write_queues) {
+ seq++; // one for the commit
+ }
+ versions.emplace_back(kv);
+ }
+ std::reverse(std::begin(versions), std::end(versions));
+ VerifyInternalKeys(versions);
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ db_impl->FlushWAL(true);
+ // Use small buffer to ensure memtable flush during recovery
+ options.write_buffer_size = 1024;
+ ReOpenNoDelete();
+ VerifyInternalKeys(versions);
+}
+
+TEST_P(WritePreparedTransactionTest, SequenceNumberZero) {
+ ASSERT_OK(db->Put(WriteOptions(), "foo", "bar"));
+ VerifyKeys({{"foo", "bar"}});
+ const Snapshot* snapshot = db->GetSnapshot();
+ ASSERT_OK(db->Flush(FlushOptions()));
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ // Compaction will output keys with sequence number 0, if it is visible to
+ // earliest snapshot. Make sure IsInSnapshot() report sequence number 0 is
+ // visible to any snapshot.
+ VerifyKeys({{"foo", "bar"}});
+ VerifyKeys({{"foo", "bar"}}, snapshot);
+ VerifyInternalKeys({{"foo", "bar", 0, kTypeValue}});
+ db->ReleaseSnapshot(snapshot);
+}
+
+// Compaction should not remove a key if it is not committed, and should
+// proceed with older versions of the key as-if the new version doesn't exist.
+TEST_P(WritePreparedTransactionTest, CompactionShouldKeepUncommittedKeys) {
+ options.disable_auto_compactions = true;
+ ReOpen();
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ // Snapshots to avoid keys get evicted.
+ std::vector<const Snapshot*> snapshots;
+ // Keep track of expected sequence number.
+ SequenceNumber expected_seq = 0;
+
+ auto add_key = [&](std::function<Status()> func) {
+ ASSERT_OK(func());
+ expected_seq++;
+ if (options.two_write_queues) {
+ expected_seq++; // 1 for commit
+ }
+ ASSERT_EQ(expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ snapshots.push_back(db->GetSnapshot());
+ };
+
+ // Each key here represent a standalone test case.
+ add_key([&]() { return db->Put(WriteOptions(), "key1", "value1_1"); });
+ add_key([&]() { return db->Put(WriteOptions(), "key2", "value2_1"); });
+ add_key([&]() { return db->Put(WriteOptions(), "key3", "value3_1"); });
+ add_key([&]() { return db->Put(WriteOptions(), "key4", "value4_1"); });
+ add_key([&]() { return db->Merge(WriteOptions(), "key5", "value5_1"); });
+ add_key([&]() { return db->Merge(WriteOptions(), "key5", "value5_2"); });
+ add_key([&]() { return db->Put(WriteOptions(), "key6", "value6_1"); });
+ add_key([&]() { return db->Put(WriteOptions(), "key7", "value7_1"); });
+ ASSERT_OK(db->Flush(FlushOptions()));
+ add_key([&]() { return db->Delete(WriteOptions(), "key6"); });
+ add_key([&]() { return db->SingleDelete(WriteOptions(), "key7"); });
+
+ auto* transaction = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Put("key1", "value1_2"));
+ ASSERT_OK(transaction->Delete("key2"));
+ ASSERT_OK(transaction->SingleDelete("key3"));
+ ASSERT_OK(transaction->Merge("key4", "value4_2"));
+ ASSERT_OK(transaction->Merge("key5", "value5_3"));
+ ASSERT_OK(transaction->Put("key6", "value6_2"));
+ ASSERT_OK(transaction->Put("key7", "value7_2"));
+ // Prepare but not commit.
+ ASSERT_OK(transaction->Prepare());
+ ASSERT_EQ(++expected_seq, db->GetLatestSequenceNumber());
+ ASSERT_OK(db->Flush(FlushOptions()));
+ for (auto* s : snapshots) {
+ db->ReleaseSnapshot(s);
+ }
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ VerifyKeys({
+ {"key1", "value1_1"},
+ {"key2", "value2_1"},
+ {"key3", "value3_1"},
+ {"key4", "value4_1"},
+ {"key5", "value5_1,value5_2"},
+ {"key6", "NOT_FOUND"},
+ {"key7", "NOT_FOUND"},
+ });
+ VerifyInternalKeys({
+ {"key1", "value1_2", expected_seq, kTypeValue},
+ {"key1", "value1_1", 0, kTypeValue},
+ {"key2", "", expected_seq, kTypeDeletion},
+ {"key2", "value2_1", 0, kTypeValue},
+ {"key3", "", expected_seq, kTypeSingleDeletion},
+ {"key3", "value3_1", 0, kTypeValue},
+ {"key4", "value4_2", expected_seq, kTypeMerge},
+ {"key4", "value4_1", 0, kTypeValue},
+ {"key5", "value5_3", expected_seq, kTypeMerge},
+ {"key5", "value5_1,value5_2", 0, kTypeValue},
+ {"key6", "value6_2", expected_seq, kTypeValue},
+ {"key7", "value7_2", expected_seq, kTypeValue},
+ });
+ ASSERT_OK(transaction->Commit());
+ VerifyKeys({
+ {"key1", "value1_2"},
+ {"key2", "NOT_FOUND"},
+ {"key3", "NOT_FOUND"},
+ {"key4", "value4_1,value4_2"},
+ {"key5", "value5_1,value5_2,value5_3"},
+ {"key6", "value6_2"},
+ {"key7", "value7_2"},
+ });
+ delete transaction;
+}
+
+// Compaction should keep keys visible to a snapshot based on commit sequence,
+// not just prepare sequence.
+TEST_P(WritePreparedTransactionTest, CompactionShouldKeepSnapshotVisibleKeys) {
+ options.disable_auto_compactions = true;
+ ReOpen();
+ // Keep track of expected sequence number.
+ SequenceNumber expected_seq = 0;
+ auto* txn1 = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(txn1->SetName("txn1"));
+ ASSERT_OK(txn1->Put("key1", "value1_1"));
+ ASSERT_OK(txn1->Prepare());
+ ASSERT_EQ(++expected_seq, db->GetLatestSequenceNumber());
+ ASSERT_OK(txn1->Commit());
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ ASSERT_EQ(++expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ delete txn1;
+ // Take a snapshots to avoid keys get evicted before compaction.
+ const Snapshot* snapshot1 = db->GetSnapshot();
+ auto* txn2 = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(txn2->SetName("txn2"));
+ ASSERT_OK(txn2->Put("key2", "value2_1"));
+ ASSERT_OK(txn2->Prepare());
+ ASSERT_EQ(++expected_seq, db->GetLatestSequenceNumber());
+ // txn1 commit before snapshot2 and it is visible to snapshot2.
+ // txn2 commit after snapshot2 and it is not visible.
+ const Snapshot* snapshot2 = db->GetSnapshot();
+ ASSERT_OK(txn2->Commit());
+ ASSERT_EQ(++expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ delete txn2;
+ // Take a snapshots to avoid keys get evicted before compaction.
+ const Snapshot* snapshot3 = db->GetSnapshot();
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1_2"));
+ expected_seq++; // 1 for write
+ SequenceNumber seq1 = expected_seq;
+ if (options.two_write_queues) {
+ expected_seq++; // 1 for commit
+ }
+ ASSERT_EQ(expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "value2_2"));
+ expected_seq++; // 1 for write
+ SequenceNumber seq2 = expected_seq;
+ if (options.two_write_queues) {
+ expected_seq++; // 1 for commit
+ }
+ ASSERT_EQ(expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ ASSERT_OK(db->Flush(FlushOptions()));
+ db->ReleaseSnapshot(snapshot1);
+ db->ReleaseSnapshot(snapshot3);
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ VerifyKeys({{"key1", "value1_2"}, {"key2", "value2_2"}});
+ VerifyKeys({{"key1", "value1_1"}, {"key2", "NOT_FOUND"}}, snapshot2);
+ VerifyInternalKeys({
+ {"key1", "value1_2", seq1, kTypeValue},
+ // "value1_1" is visible to snapshot2. Also keys at bottom level visible
+ // to earliest snapshot will output with seq = 0.
+ {"key1", "value1_1", 0, kTypeValue},
+ {"key2", "value2_2", seq2, kTypeValue},
+ });
+ db->ReleaseSnapshot(snapshot2);
+}
+
+TEST_P(WritePreparedTransactionTest, SmallestUncommittedOptimization) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // disable commit cache
+ for (bool has_recent_prepare : {true, false}) {
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1"));
+ auto* transaction =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Delete("key1"));
+ ASSERT_OK(transaction->Prepare());
+ // snapshot1 should get min_uncommitted from prepared_txns_ heap.
+ auto snapshot1 = db->GetSnapshot();
+ ASSERT_EQ(transaction->GetId(),
+ ((SnapshotImpl*)snapshot1)->min_uncommitted_);
+ // Add a commit to advance max_evicted_seq and move the prepared transaction
+ // into delayed_prepared_ set.
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "value2"));
+ Transaction* txn2 = nullptr;
+ if (has_recent_prepare) {
+ txn2 =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(txn2->SetName("txn2"));
+ ASSERT_OK(txn2->Put("key3", "value3"));
+ ASSERT_OK(txn2->Prepare());
+ }
+ // snapshot2 should get min_uncommitted from delayed_prepared_ set.
+ auto snapshot2 = db->GetSnapshot();
+ ASSERT_EQ(transaction->GetId(),
+ ((SnapshotImpl*)snapshot1)->min_uncommitted_);
+ ASSERT_OK(transaction->Commit());
+ delete transaction;
+ if (has_recent_prepare) {
+ ASSERT_OK(txn2->Commit());
+ delete txn2;
+ }
+ VerifyKeys({{"key1", "NOT_FOUND"}});
+ VerifyKeys({{"key1", "value1"}}, snapshot1);
+ VerifyKeys({{"key1", "value1"}}, snapshot2);
+ db->ReleaseSnapshot(snapshot1);
+ db->ReleaseSnapshot(snapshot2);
+ }
+}
+
+// Insert two values, v1 and v2, for a key. Between prepare and commit of v2
+// take two snapshots, s1 and s2. Release s1 during compaction.
+// Test to make sure compaction doesn't get confused and think s1 can see both
+// values, and thus compact out the older value by mistake.
+TEST_P(WritePreparedTransactionTest, ReleaseSnapshotDuringCompaction) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // minimum commit cache
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1_1"));
+ auto* transaction =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Put("key1", "value1_2"));
+ ASSERT_OK(transaction->Prepare());
+ auto snapshot1 = db->GetSnapshot();
+ // Increment sequence number.
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "value2"));
+ auto snapshot2 = db->GetSnapshot();
+ ASSERT_OK(transaction->Commit());
+ delete transaction;
+ VerifyKeys({{"key1", "value1_2"}});
+ VerifyKeys({{"key1", "value1_1"}}, snapshot1);
+ VerifyKeys({{"key1", "value1_1"}}, snapshot2);
+ // Add a flush to avoid compaction to fallback to trivial move.
+
+ auto callback = [&](void*) {
+ // Release snapshot1 after CompactionIterator init.
+ // CompactionIterator need to figure out the earliest snapshot
+ // that can see key1:value1_2 is kMaxSequenceNumber, not
+ // snapshot1 or snapshot2.
+ db->ReleaseSnapshot(snapshot1);
+ // Add some keys to advance max_evicted_seq.
+ ASSERT_OK(db->Put(WriteOptions(), "key3", "value3"));
+ ASSERT_OK(db->Put(WriteOptions(), "key4", "value4"));
+ };
+ SyncPoint::GetInstance()->SetCallBack("CompactionIterator:AfterInit",
+ callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(db->Flush(FlushOptions()));
+ VerifyKeys({{"key1", "value1_2"}});
+ VerifyKeys({{"key1", "value1_1"}}, snapshot2);
+ db->ReleaseSnapshot(snapshot2);
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// Insert two values, v1 and v2, for a key. Take two snapshots, s1 and s2,
+// after committing v2. Release s1 during compaction, right after compaction
+// processes v2 and before processes v1. Test to make sure compaction doesn't
+// get confused and believe v1 and v2 are visible to different snapshot
+// (v1 by s2, v2 by s1) and refuse to compact out v1.
+TEST_P(WritePreparedTransactionTest, ReleaseSnapshotDuringCompaction2) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // minimum commit cache
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1"));
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value2"));
+ SequenceNumber v2_seq = db->GetLatestSequenceNumber();
+ auto* s1 = db->GetSnapshot();
+ // Advance sequence number.
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "dummy"));
+ auto* s2 = db->GetSnapshot();
+
+ int count_value = 0;
+ auto callback = [&](void* arg) {
+ auto* ikey = reinterpret_cast<ParsedInternalKey*>(arg);
+ if (ikey->user_key == "key1") {
+ count_value++;
+ if (count_value == 2) {
+ // Processing v1.
+ db->ReleaseSnapshot(s1);
+ // Add some keys to advance max_evicted_seq and update
+ // old_commit_map.
+ ASSERT_OK(db->Put(WriteOptions(), "key3", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "key4", "dummy"));
+ }
+ }
+ };
+ SyncPoint::GetInstance()->SetCallBack("CompactionIterator:ProcessKV",
+ callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(db->Flush(FlushOptions()));
+ // value1 should be compact out.
+ VerifyInternalKeys({{"key1", "value2", v2_seq, kTypeValue}});
+
+ // cleanup
+ db->ReleaseSnapshot(s2);
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// Insert two values, v1 and v2, for a key. Insert another dummy key
+// so to evict the commit cache for v2, while v1 is still in commit cache.
+// Take two snapshots, s1 and s2. Release s1 during compaction.
+// Since commit cache for v2 is evicted, and old_commit_map don't have
+// s1 (it is released),
+// TODO(myabandeh): how can we be sure that the v2's commit info is evicted
+// (and not v1's)? Instead of putting a dummy, we can directly call
+// AddCommitted(v2_seq + cache_size, ...) to evict v2's entry from commit cache.
+TEST_P(WritePreparedTransactionTest, ReleaseSnapshotDuringCompaction3) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 1; // commit cache size = 2
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ // Add a dummy key to evict v2 commit cache, but keep v1 commit cache.
+ // It also advance max_evicted_seq and can trigger old_commit_map cleanup.
+ auto add_dummy = [&]() {
+ auto* txn_dummy =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(txn_dummy->SetName("txn_dummy"));
+ ASSERT_OK(txn_dummy->Put("dummy", "dummy"));
+ ASSERT_OK(txn_dummy->Prepare());
+ ASSERT_OK(txn_dummy->Commit());
+ delete txn_dummy;
+ };
+
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1"));
+ auto* txn =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(txn->SetName("txn"));
+ ASSERT_OK(txn->Put("key1", "value2"));
+ ASSERT_OK(txn->Prepare());
+ // TODO(myabandeh): replace it with GetId()?
+ auto v2_seq = db->GetLatestSequenceNumber();
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ auto* s1 = db->GetSnapshot();
+ // Dummy key to advance sequence number.
+ add_dummy();
+ auto* s2 = db->GetSnapshot();
+
+ auto callback = [&](void*) {
+ db->ReleaseSnapshot(s1);
+ // Add some dummy entries to trigger s1 being cleanup from old_commit_map.
+ add_dummy();
+ add_dummy();
+ };
+ SyncPoint::GetInstance()->SetCallBack("CompactionIterator:AfterInit",
+ callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ASSERT_OK(db->Flush(FlushOptions()));
+ // value1 should be compact out.
+ VerifyInternalKeys({{"key1", "value2", v2_seq, kTypeValue}});
+
+ db->ReleaseSnapshot(s2);
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+TEST_P(WritePreparedTransactionTest, ReleaseEarliestSnapshotDuringCompaction) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 0; // minimum commit cache
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1"));
+ auto* transaction =
+ db->BeginTransaction(WriteOptions(), TransactionOptions(), nullptr);
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Delete("key1"));
+ ASSERT_OK(transaction->Prepare());
+ SequenceNumber del_seq = db->GetLatestSequenceNumber();
+ auto snapshot1 = db->GetSnapshot();
+ // Increment sequence number.
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "value2"));
+ auto snapshot2 = db->GetSnapshot();
+ ASSERT_OK(transaction->Commit());
+ delete transaction;
+ VerifyKeys({{"key1", "NOT_FOUND"}});
+ VerifyKeys({{"key1", "value1"}}, snapshot1);
+ VerifyKeys({{"key1", "value1"}}, snapshot2);
+ ASSERT_OK(db->Flush(FlushOptions()));
+
+ auto callback = [&](void* compaction) {
+ // Release snapshot1 after CompactionIterator init.
+ // CompactionIterator need to double check and find out snapshot2 is now
+ // the earliest existing snapshot.
+ if (compaction != nullptr) {
+ db->ReleaseSnapshot(snapshot1);
+ // Add some keys to advance max_evicted_seq.
+ ASSERT_OK(db->Put(WriteOptions(), "key3", "value3"));
+ ASSERT_OK(db->Put(WriteOptions(), "key4", "value4"));
+ }
+ };
+ SyncPoint::GetInstance()->SetCallBack("CompactionIterator:AfterInit",
+ callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ // Only verify for key1. Both the put and delete for the key should be kept.
+ // Since the delete tombstone is not visible to snapshot2, we need to keep
+ // at least one version of the key, for write-conflict check.
+ VerifyInternalKeys({{"key1", "", del_seq, kTypeDeletion},
+ {"key1", "value1", 0, kTypeValue}});
+ db->ReleaseSnapshot(snapshot2);
+ SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// A more complex test to verify compaction/flush should keep keys visible
+// to snapshots.
+TEST_P(WritePreparedTransactionTest,
+ CompactionKeepSnapshotVisibleKeysRandomized) {
+ constexpr size_t kNumTransactions = 10;
+ constexpr size_t kNumIterations = 1000;
+
+ std::vector<Transaction*> transactions(kNumTransactions, nullptr);
+ std::vector<size_t> versions(kNumTransactions, 0);
+ std::unordered_map<std::string, std::string> current_data;
+ std::vector<const Snapshot*> snapshots;
+ std::vector<std::unordered_map<std::string, std::string>> snapshot_data;
+
+ Random rnd(1103);
+ options.disable_auto_compactions = true;
+ ReOpen();
+
+ for (size_t i = 0; i < kNumTransactions; i++) {
+ std::string key = "key" + ToString(i);
+ std::string value = "value0";
+ ASSERT_OK(db->Put(WriteOptions(), key, value));
+ current_data[key] = value;
+ }
+ VerifyKeys(current_data);
+
+ for (size_t iter = 0; iter < kNumIterations; iter++) {
+ auto r = rnd.Next() % (kNumTransactions + 1);
+ if (r < kNumTransactions) {
+ std::string key = "key" + ToString(r);
+ if (transactions[r] == nullptr) {
+ std::string value = "value" + ToString(versions[r] + 1);
+ auto* txn = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(txn->SetName("txn" + ToString(r)));
+ ASSERT_OK(txn->Put(key, value));
+ ASSERT_OK(txn->Prepare());
+ transactions[r] = txn;
+ } else {
+ std::string value = "value" + ToString(++versions[r]);
+ ASSERT_OK(transactions[r]->Commit());
+ delete transactions[r];
+ transactions[r] = nullptr;
+ current_data[key] = value;
+ }
+ } else {
+ auto* snapshot = db->GetSnapshot();
+ VerifyKeys(current_data, snapshot);
+ snapshots.push_back(snapshot);
+ snapshot_data.push_back(current_data);
+ }
+ VerifyKeys(current_data);
+ }
+ // Take a last snapshot to test compaction with uncommitted prepared
+ // transaction.
+ snapshots.push_back(db->GetSnapshot());
+ snapshot_data.push_back(current_data);
+
+ assert(snapshots.size() == snapshot_data.size());
+ for (size_t i = 0; i < snapshots.size(); i++) {
+ VerifyKeys(snapshot_data[i], snapshots[i]);
+ }
+ ASSERT_OK(db->Flush(FlushOptions()));
+ for (size_t i = 0; i < snapshots.size(); i++) {
+ VerifyKeys(snapshot_data[i], snapshots[i]);
+ }
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ for (size_t i = 0; i < snapshots.size(); i++) {
+ VerifyKeys(snapshot_data[i], snapshots[i]);
+ }
+ // cleanup
+ for (size_t i = 0; i < kNumTransactions; i++) {
+ if (transactions[i] == nullptr) {
+ continue;
+ }
+ ASSERT_OK(transactions[i]->Commit());
+ delete transactions[i];
+ }
+ for (size_t i = 0; i < snapshots.size(); i++) {
+ db->ReleaseSnapshot(snapshots[i]);
+ }
+}
+
+// Compaction should not apply the optimization to output key with sequence
+// number equal to 0 if the key is not visible to earliest snapshot, based on
+// commit sequence number.
+TEST_P(WritePreparedTransactionTest,
+ CompactionShouldKeepSequenceForUncommittedKeys) {
+ options.disable_auto_compactions = true;
+ ReOpen();
+ // Keep track of expected sequence number.
+ SequenceNumber expected_seq = 0;
+ auto* transaction = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Put("key1", "value1"));
+ ASSERT_OK(transaction->Prepare());
+ ASSERT_EQ(++expected_seq, db->GetLatestSequenceNumber());
+ SequenceNumber seq1 = expected_seq;
+ ASSERT_OK(db->Put(WriteOptions(), "key2", "value2"));
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ expected_seq++; // one for data
+ if (options.two_write_queues) {
+ expected_seq++; // one for commit
+ }
+ ASSERT_EQ(expected_seq, db_impl->TEST_GetLastVisibleSequence());
+ ASSERT_OK(db->Flush(FlushOptions()));
+ // Dummy keys to avoid compaction trivially move files and get around actual
+ // compaction logic.
+ ASSERT_OK(db->Put(WriteOptions(), "a", "dummy"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "dummy"));
+ ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr));
+ VerifyKeys({
+ {"key1", "NOT_FOUND"},
+ {"key2", "value2"},
+ });
+ VerifyInternalKeys({
+ // "key1" has not been committed. It keeps its sequence number.
+ {"key1", "value1", seq1, kTypeValue},
+ // "key2" is committed and output with seq = 0.
+ {"key2", "value2", 0, kTypeValue},
+ });
+ ASSERT_OK(transaction->Commit());
+ VerifyKeys({
+ {"key1", "value1"},
+ {"key2", "value2"},
+ });
+ delete transaction;
+}
+
+TEST_P(WritePreparedTransactionTest, CommitAndSnapshotDuringCompaction) {
+ options.disable_auto_compactions = true;
+ ReOpen();
+
+ const Snapshot* snapshot = nullptr;
+ ASSERT_OK(db->Put(WriteOptions(), "key1", "value1"));
+ auto* txn = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(txn->SetName("txn"));
+ ASSERT_OK(txn->Put("key1", "value2"));
+ ASSERT_OK(txn->Prepare());
+
+ auto callback = [&](void*) {
+ // Snapshot is taken after compaction start. It should be taken into
+ // consideration for whether to compact out value1.
+ snapshot = db->GetSnapshot();
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ };
+ SyncPoint::GetInstance()->SetCallBack("CompactionIterator:AfterInit",
+ callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+ ASSERT_OK(db->Flush(FlushOptions()));
+ ASSERT_NE(nullptr, snapshot);
+ VerifyKeys({{"key1", "value2"}});
+ VerifyKeys({{"key1", "value1"}}, snapshot);
+ db->ReleaseSnapshot(snapshot);
+}
+
+TEST_P(WritePreparedTransactionTest, Iterate) {
+ auto verify_state = [](Iterator* iter, const std::string& key,
+ const std::string& value) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_OK(iter->status());
+ ASSERT_EQ(key, iter->key().ToString());
+ ASSERT_EQ(value, iter->value().ToString());
+ };
+
+ auto verify_iter = [&](const std::string& expected_val) {
+ // Get iterator from a concurrent transaction and make sure it has the
+ // same view as an iterator from the DB.
+ auto* txn = db->BeginTransaction(WriteOptions());
+
+ for (int i = 0; i < 2; i++) {
+ Iterator* iter = (i == 0)
+ ? db->NewIterator(ReadOptions())
+ : txn->GetIterator(ReadOptions());
+ // Seek
+ iter->Seek("foo");
+ verify_state(iter, "foo", expected_val);
+ // Next
+ iter->Seek("a");
+ verify_state(iter, "a", "va");
+ iter->Next();
+ verify_state(iter, "foo", expected_val);
+ // SeekForPrev
+ iter->SeekForPrev("y");
+ verify_state(iter, "foo", expected_val);
+ // Prev
+ iter->SeekForPrev("z");
+ verify_state(iter, "z", "vz");
+ iter->Prev();
+ verify_state(iter, "foo", expected_val);
+ delete iter;
+ }
+ delete txn;
+ };
+
+ ASSERT_OK(db->Put(WriteOptions(), "foo", "v1"));
+ auto* transaction = db->BeginTransaction(WriteOptions());
+ ASSERT_OK(transaction->SetName("txn"));
+ ASSERT_OK(transaction->Put("foo", "v2"));
+ ASSERT_OK(transaction->Prepare());
+ VerifyKeys({{"foo", "v1"}});
+ // dummy keys
+ ASSERT_OK(db->Put(WriteOptions(), "a", "va"));
+ ASSERT_OK(db->Put(WriteOptions(), "z", "vz"));
+ verify_iter("v1");
+ ASSERT_OK(transaction->Commit());
+ VerifyKeys({{"foo", "v2"}});
+ verify_iter("v2");
+ delete transaction;
+}
+
+TEST_P(WritePreparedTransactionTest, IteratorRefreshNotSupported) {
+ Iterator* iter = db->NewIterator(ReadOptions());
+ ASSERT_TRUE(iter->Refresh().IsNotSupported());
+ delete iter;
+}
+
+// Committing an delayed prepared has two non-atomic steps: update commit cache,
+// remove seq from delayed_prepared_. The read in IsInSnapshot also involves two
+// non-atomic steps of checking these two data structures. This test breaks each
+// in the middle to ensure correctness in spite of non-atomic execution.
+// Note: This test is limitted to the case where snapshot is larger than the
+// max_evicted_seq_.
+TEST_P(WritePreparedTransactionTest, NonAtomicCommitOfDelayedPrepared) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 3; // 8 entries
+ for (auto split_read : {true, false}) {
+ std::vector<bool> split_options = {false};
+ if (split_read) {
+ // Also test for break before mutex
+ split_options.push_back(true);
+ }
+ for (auto split_before_mutex : split_options) {
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ DBImpl* db_impl = reinterpret_cast<DBImpl*>(db->GetRootDB());
+ // Fill up the commit cache
+ std::string init_value("value1");
+ for (int i = 0; i < 10; i++) {
+ db->Put(WriteOptions(), Slice("key1"), Slice(init_value));
+ }
+ // Prepare a transaction but do not commit it
+ Transaction* txn =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("key1"), Slice("value2")));
+ ASSERT_OK(txn->Prepare());
+ // Commit a bunch of entries to advance max evicted seq and make the
+ // prepared a delayed prepared
+ for (int i = 0; i < 10; i++) {
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ }
+ // The snapshot should not see the delayed prepared entry
+ auto snap = db->GetSnapshot();
+
+ if (split_read) {
+ if (split_before_mutex) {
+ // split before acquiring prepare_mutex_
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTxnDB::IsInSnapshot:prepared_mutex_:pause",
+ "AtomicCommitOfDelayedPrepared:Commit:before"},
+ {"AtomicCommitOfDelayedPrepared:Commit:after",
+ "WritePreparedTxnDB::IsInSnapshot:prepared_mutex_:resume"}});
+ } else {
+ // split right after reading from the commit cache
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTxnDB::IsInSnapshot:GetCommitEntry:pause",
+ "AtomicCommitOfDelayedPrepared:Commit:before"},
+ {"AtomicCommitOfDelayedPrepared:Commit:after",
+ "WritePreparedTxnDB::IsInSnapshot:GetCommitEntry:resume"}});
+ }
+ } else { // split commit
+ // split right before removing from delayed_prepared_
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTxnDB::RemovePrepared:pause",
+ "AtomicCommitOfDelayedPrepared:Read:before"},
+ {"AtomicCommitOfDelayedPrepared:Read:after",
+ "WritePreparedTxnDB::RemovePrepared:resume"}});
+ }
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread commit_thread([&]() {
+ TEST_SYNC_POINT("AtomicCommitOfDelayedPrepared:Commit:before");
+ ASSERT_OK(txn->Commit());
+ if (split_before_mutex) {
+ // Do bunch of inserts to evict the commit entry from the cache. This
+ // would prevent the 2nd look into commit cache under prepare_mutex_
+ // to see the commit entry.
+ auto seq = db_impl->TEST_GetLastVisibleSequence();
+ size_t tries = 0;
+ while (wp_db->max_evicted_seq_ < seq && tries < 50) {
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ tries++;
+ };
+ ASSERT_LT(tries, 50);
+ }
+ TEST_SYNC_POINT("AtomicCommitOfDelayedPrepared:Commit:after");
+ delete txn;
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ TEST_SYNC_POINT("AtomicCommitOfDelayedPrepared:Read:before");
+ ReadOptions roptions;
+ roptions.snapshot = snap;
+ PinnableSlice value;
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key1", &value);
+ ASSERT_OK(s);
+ // It should not see the commit of delayed prepared
+ ASSERT_TRUE(value == init_value);
+ TEST_SYNC_POINT("AtomicCommitOfDelayedPrepared:Read:after");
+ db->ReleaseSnapshot(snap);
+ });
+
+ read_thread.join();
+ commit_thread.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ } // for split_before_mutex
+ } // for split_read
+}
+
+// When max evicted seq advances a prepared seq, it involves two updates: i)
+// adding prepared seq to delayed_prepared_, ii) updating max_evicted_seq_.
+// ::IsInSnapshot also reads these two values in a non-atomic way. This test
+// ensures correctness if the update occurs after ::IsInSnapshot reads
+// delayed_prepared_empty_ and before it reads max_evicted_seq_.
+// Note: this test focuses on read snapshot larger than max_evicted_seq_.
+TEST_P(WritePreparedTransactionTest, NonAtomicUpdateOfDelayedPrepared) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 3; // 8 entries
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ // Fill up the commit cache
+ std::string init_value("value1");
+ for (int i = 0; i < 10; i++) {
+ db->Put(WriteOptions(), Slice("key1"), Slice(init_value));
+ }
+ // Prepare a transaction but do not commit it
+ Transaction* txn = db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("key1"), Slice("value2")));
+ ASSERT_OK(txn->Prepare());
+ // Create a gap between prepare seq and snapshot seq
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ // The snapshot should not see the delayed prepared entry
+ auto snap = db->GetSnapshot();
+ ASSERT_LT(txn->GetId(), snap->GetSequenceNumber());
+
+ // split right after reading delayed_prepared_empty_
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTxnDB::IsInSnapshot:delayed_prepared_empty_:pause",
+ "AtomicUpdateOfDelayedPrepared:before"},
+ {"AtomicUpdateOfDelayedPrepared:after",
+ "WritePreparedTxnDB::IsInSnapshot:delayed_prepared_empty_:resume"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread commit_thread([&]() {
+ TEST_SYNC_POINT("AtomicUpdateOfDelayedPrepared:before");
+ // Commit a bunch of entries to advance max evicted seq and make the
+ // prepared a delayed prepared
+ size_t tries = 0;
+ while (wp_db->max_evicted_seq_ < txn->GetId() && tries < 50) {
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ tries++;
+ };
+ ASSERT_LT(tries, 50);
+ // This is the case on which the test focuses
+ ASSERT_LT(wp_db->max_evicted_seq_, snap->GetSequenceNumber());
+ TEST_SYNC_POINT("AtomicUpdateOfDelayedPrepared:after");
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ ReadOptions roptions;
+ roptions.snapshot = snap;
+ PinnableSlice value;
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key1", &value);
+ ASSERT_OK(s);
+ // It should not see the uncommitted value of delayed prepared
+ ASSERT_TRUE(value == init_value);
+ db->ReleaseSnapshot(snap);
+ });
+
+ read_thread.join();
+ commit_thread.join();
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// Eviction from commit cache and update of max evicted seq are two non-atomic
+// steps. Similarly the read of max_evicted_seq_ in ::IsInSnapshot and reading
+// from commit cache are two non-atomic steps. This tests if the update occurs
+// after reading max_evicted_seq_ and before reading the commit cache.
+// Note: the test focuses on snapshot larger than max_evicted_seq_
+TEST_P(WritePreparedTransactionTest, NonAtomicUpdateOfMaxEvictedSeq) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ const size_t commit_cache_bits = 3; // 8 entries
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ // Fill up the commit cache
+ std::string init_value("value1");
+ std::string last_value("value_final");
+ for (int i = 0; i < 10; i++) {
+ db->Put(WriteOptions(), Slice("key1"), Slice(init_value));
+ }
+ // Do an uncommitted write to prevent min_uncommitted optimization
+ Transaction* txn1 =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn1->SetName("xid1"));
+ ASSERT_OK(txn1->Put(Slice("key0"), last_value));
+ ASSERT_OK(txn1->Prepare());
+ // Do a write with prepare to get the prepare seq
+ Transaction* txn = db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("key1"), last_value));
+ ASSERT_OK(txn->Prepare());
+ ASSERT_OK(txn->Commit());
+ // Create a gap between commit entry and snapshot seq
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ // The snapshot should see the last commit
+ auto snap = db->GetSnapshot();
+ ASSERT_LE(txn->GetId(), snap->GetSequenceNumber());
+
+ // split right after reading max_evicted_seq_
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
+ {{"WritePreparedTxnDB::IsInSnapshot:max_evicted_seq_:pause",
+ "NonAtomicUpdateOfMaxEvictedSeq:before"},
+ {"NonAtomicUpdateOfMaxEvictedSeq:after",
+ "WritePreparedTxnDB::IsInSnapshot:max_evicted_seq_:resume"}});
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread commit_thread([&]() {
+ TEST_SYNC_POINT("NonAtomicUpdateOfMaxEvictedSeq:before");
+ // Commit a bunch of entries to advance max evicted seq beyond txn->GetId()
+ size_t tries = 0;
+ while (wp_db->max_evicted_seq_ < txn->GetId() && tries < 50) {
+ db->Put(WriteOptions(), Slice("key3"), Slice("value3"));
+ tries++;
+ };
+ ASSERT_LT(tries, 50);
+ // This is the case on which the test focuses
+ ASSERT_LT(wp_db->max_evicted_seq_, snap->GetSequenceNumber());
+ TEST_SYNC_POINT("NonAtomicUpdateOfMaxEvictedSeq:after");
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ ReadOptions roptions;
+ roptions.snapshot = snap;
+ PinnableSlice value;
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key1", &value);
+ ASSERT_OK(s);
+ // It should see the committed value of the evicted entry
+ ASSERT_TRUE(value == last_value);
+ db->ReleaseSnapshot(snap);
+ });
+
+ read_thread.join();
+ commit_thread.join();
+ delete txn;
+ txn1->Commit();
+ delete txn1;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// Test when we add a prepared seq when the max_evicted_seq_ already goes beyond
+// that. The test focuses on a race condition between AddPrepared and
+// AdvanceMaxEvictedSeq functions.
+TEST_P(WritePreparedTransactionTest, AddPreparedBeforeMax) {
+ if (!options.two_write_queues) {
+ // This test is only for two write queues
+ return;
+ }
+ const size_t snapshot_cache_bits = 7; // same as default
+ // 1 entry to advance max after the 2nd commit
+ const size_t commit_cache_bits = 0;
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
+ std::string some_value("value_some");
+ std::string uncommitted_value("value_uncommitted");
+ // Prepare two uncommitted transactions
+ Transaction* txn1 =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn1->SetName("xid1"));
+ ASSERT_OK(txn1->Put(Slice("key1"), some_value));
+ ASSERT_OK(txn1->Prepare());
+ Transaction* txn2 =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn2->SetName("xid2"));
+ ASSERT_OK(txn2->Put(Slice("key2"), some_value));
+ ASSERT_OK(txn2->Prepare());
+ // Start the txn here so the other thread could get its id
+ Transaction* txn = db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("key0"), uncommitted_value));
+ port::Mutex txn_mutex_;
+
+ // t1) Insert prepared entry, t2) commit other entries to advance max
+ // evicted sec and finish checking the existing prepared entries, t1)
+ // AddPrepared, t2) update max_evicted_seq_
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"AddPreparedCallback::AddPrepared::begin:pause",
+ "AddPreparedBeforeMax::read_thread:start"},
+ {"AdvanceMaxEvictedSeq::update_max:pause",
+ "AddPreparedCallback::AddPrepared::begin:resume"},
+ {"AddPreparedCallback::AddPrepared::end",
+ "AdvanceMaxEvictedSeq::update_max:resume"},
+ });
+ SyncPoint::GetInstance()->EnableProcessing();
+
+ ROCKSDB_NAMESPACE::port::Thread write_thread([&]() {
+ txn_mutex_.Lock();
+ ASSERT_OK(txn->Prepare());
+ txn_mutex_.Unlock();
+ });
+
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ TEST_SYNC_POINT("AddPreparedBeforeMax::read_thread:start");
+ // Publish seq number with a commit
+ ASSERT_OK(txn1->Commit());
+ // Since the commit cache size is one the 2nd commit evict the 1st one and
+ // invokes AdcanceMaxEvictedSeq
+ ASSERT_OK(txn2->Commit());
+
+ ReadOptions roptions;
+ PinnableSlice value;
+ // The snapshot should not see the uncommitted value from write_thread
+ auto snap = db->GetSnapshot();
+ ASSERT_LT(wp_db->max_evicted_seq_, snap->GetSequenceNumber());
+ // This is the scenario that we test for
+ txn_mutex_.Lock();
+ ASSERT_GT(wp_db->max_evicted_seq_, txn->GetId());
+ txn_mutex_.Unlock();
+ roptions.snapshot = snap;
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key0", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ db->ReleaseSnapshot(snap);
+ });
+
+ read_thread.join();
+ write_thread.join();
+ delete txn1;
+ delete txn2;
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+}
+
+// When an old prepared entry gets committed, there is a gap between the time
+// that it is published and when it is cleaned up from old_prepared_. This test
+// stresses such cases.
+TEST_P(WritePreparedTransactionTest, CommitOfDelayedPrepared) {
+ const size_t snapshot_cache_bits = 7; // same as default
+ for (const size_t commit_cache_bits : {0, 2, 3}) {
+ for (const size_t sub_batch_cnt : {1, 2, 3}) {
+ UpdateTransactionDBOptions(snapshot_cache_bits, commit_cache_bits);
+ ReOpen();
+ std::atomic<const Snapshot*> snap = {nullptr};
+ std::atomic<SequenceNumber> exp_prepare = {0};
+ ROCKSDB_NAMESPACE::port::Thread callback_thread;
+ // Value is synchronized via snap
+ PinnableSlice value;
+ // Take a snapshot after publish and before RemovePrepared:Start
+ auto snap_callback = [&]() {
+ ASSERT_EQ(nullptr, snap.load());
+ snap.store(db->GetSnapshot());
+ ReadOptions roptions;
+ roptions.snapshot = snap.load();
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key2", &value);
+ ASSERT_OK(s);
+ };
+ auto callback = [&](void* param) {
+ SequenceNumber prep_seq = *((SequenceNumber*)param);
+ if (prep_seq == exp_prepare.load()) { // only for write_thread
+ // We need to spawn a thread to avoid deadlock since getting a
+ // snpashot might end up calling AdvanceSeqByOne which needs joining
+ // the write queue.
+ callback_thread = ROCKSDB_NAMESPACE::port::Thread(snap_callback);
+ TEST_SYNC_POINT("callback:end");
+ }
+ };
+ // Wait for the first snapshot be taken in GetSnapshotInternal. Although
+ // it might be updated before GetSnapshotInternal finishes but this should
+ // cover most of the cases.
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"WritePreparedTxnDB::GetSnapshotInternal:first", "callback:end"},
+ });
+ SyncPoint::GetInstance()->SetCallBack("RemovePrepared:Start", callback);
+ SyncPoint::GetInstance()->EnableProcessing();
+ // Thread to cause frequent evictions
+ ROCKSDB_NAMESPACE::port::Thread eviction_thread([&]() {
+ // Too many txns might cause commit_seq - prepare_seq in another thread
+ // to go beyond DELTA_UPPERBOUND
+ for (int i = 0; i < 25 * (1 << commit_cache_bits); i++) {
+ db->Put(WriteOptions(), Slice("key1"), Slice("value1"));
+ }
+ });
+ ROCKSDB_NAMESPACE::port::Thread write_thread([&]() {
+ for (int i = 0; i < 25 * (1 << commit_cache_bits); i++) {
+ Transaction* txn =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ std::string val_str = "value" + ToString(i);
+ for (size_t b = 0; b < sub_batch_cnt; b++) {
+ ASSERT_OK(txn->Put(Slice("key2"), val_str));
+ }
+ ASSERT_OK(txn->Prepare());
+ // Let an eviction to kick in
+ std::this_thread::yield();
+
+ exp_prepare.store(txn->GetId());
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ // Wait for the snapshot taking that is triggered by
+ // RemovePrepared:Start callback
+ callback_thread.join();
+
+ // Read with the snapshot taken before delayed_prepared_ cleanup
+ ReadOptions roptions;
+ roptions.snapshot = snap.load();
+ ASSERT_NE(nullptr, roptions.snapshot);
+ PinnableSlice value2;
+ auto s =
+ db->Get(roptions, db->DefaultColumnFamily(), "key2", &value2);
+ ASSERT_OK(s);
+ // It should see its own write
+ ASSERT_TRUE(val_str == value2);
+ // The value read by snapshot should not change
+ ASSERT_STREQ(value2.ToString().c_str(), value.ToString().c_str());
+
+ db->ReleaseSnapshot(roptions.snapshot);
+ snap.store(nullptr);
+ }
+ });
+ write_thread.join();
+ eviction_thread.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
+ }
+ }
+}
+
+// Test that updating the commit map will not affect the existing snapshots
+TEST_P(WritePreparedTransactionTest, AtomicCommit) {
+ for (bool skip_prepare : {true, false}) {
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
+ {"WritePreparedTxnDB::AddCommitted:start",
+ "AtomicCommit::GetSnapshot:start"},
+ {"AtomicCommit::Get:end",
+ "WritePreparedTxnDB::AddCommitted:start:pause"},
+ {"WritePreparedTxnDB::AddCommitted:end", "AtomicCommit::Get2:start"},
+ {"AtomicCommit::Get2:end",
+ "WritePreparedTxnDB::AddCommitted:end:pause:"},
+ });
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ ROCKSDB_NAMESPACE::port::Thread write_thread([&]() {
+ if (skip_prepare) {
+ db->Put(WriteOptions(), Slice("key"), Slice("value"));
+ } else {
+ Transaction* txn =
+ db->BeginTransaction(WriteOptions(), TransactionOptions());
+ ASSERT_OK(txn->SetName("xid"));
+ ASSERT_OK(txn->Put(Slice("key"), Slice("value")));
+ ASSERT_OK(txn->Prepare());
+ ASSERT_OK(txn->Commit());
+ delete txn;
+ }
+ });
+ ROCKSDB_NAMESPACE::port::Thread read_thread([&]() {
+ ReadOptions roptions;
+ TEST_SYNC_POINT("AtomicCommit::GetSnapshot:start");
+ roptions.snapshot = db->GetSnapshot();
+ PinnableSlice val;
+ auto s = db->Get(roptions, db->DefaultColumnFamily(), "key", &val);
+ TEST_SYNC_POINT("AtomicCommit::Get:end");
+ TEST_SYNC_POINT("AtomicCommit::Get2:start");
+ ASSERT_SAME(roptions, db, s, val, "key");
+ TEST_SYNC_POINT("AtomicCommit::Get2:end");
+ db->ReleaseSnapshot(roptions.snapshot);
+ });
+ read_thread.join();
+ write_thread.join();
+ ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
+ }
+}
+
+// Test that we can change write policy from WriteCommitted to WritePrepared
+// after a clean shutdown (which would empty the WAL)
+TEST_P(WritePreparedTransactionTest, WP_WC_DBBackwardCompatibility) {
+ bool empty_wal = true;
+ CrossCompatibilityTest(WRITE_COMMITTED, WRITE_PREPARED, empty_wal);
+}
+
+// Test that we fail fast if WAL is not emptied between changing the write
+// policy from WriteCommitted to WritePrepared
+TEST_P(WritePreparedTransactionTest, WP_WC_WALBackwardIncompatibility) {
+ bool empty_wal = true;
+ CrossCompatibilityTest(WRITE_COMMITTED, WRITE_PREPARED, !empty_wal);
+}
+
+// Test that we can change write policy from WritePrepare back to WriteCommitted
+// after a clean shutdown (which would empty the WAL)
+TEST_P(WritePreparedTransactionTest, WC_WP_ForwardCompatibility) {
+ bool empty_wal = true;
+ CrossCompatibilityTest(WRITE_PREPARED, WRITE_COMMITTED, empty_wal);
+}
+
+// Test that we fail fast if WAL is not emptied between changing the write
+// policy from WriteCommitted to WritePrepared
+TEST_P(WritePreparedTransactionTest, WC_WP_WALForwardIncompatibility) {
+ bool empty_wal = true;
+ CrossCompatibilityTest(WRITE_PREPARED, WRITE_COMMITTED, !empty_wal);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as Transactions are not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.cc
new file mode 100644
index 00000000..216d8355
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.cc
@@ -0,0 +1,473 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/write_prepared_txn.h"
+
+#include <cinttypes>
+#include <map>
+#include <set>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "util/cast_util.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/write_prepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+struct WriteOptions;
+
+WritePreparedTxn::WritePreparedTxn(WritePreparedTxnDB* txn_db,
+ const WriteOptions& write_options,
+ const TransactionOptions& txn_options)
+ : PessimisticTransaction(txn_db, write_options, txn_options, false),
+ wpt_db_(txn_db) {
+ // Call Initialize outside PessimisticTransaction constructor otherwise it
+ // would skip overridden functions in WritePreparedTxn since they are not
+ // defined yet in the constructor of PessimisticTransaction
+ Initialize(txn_options);
+}
+
+void WritePreparedTxn::Initialize(const TransactionOptions& txn_options) {
+ PessimisticTransaction::Initialize(txn_options);
+ prepare_batch_cnt_ = 0;
+}
+
+void WritePreparedTxn::MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input) {
+ SequenceNumber min_uncommitted, snap_seq;
+ const SnapshotBackup backed_by_snapshot =
+ wpt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
+ WritePreparedTxnReadCallback callback(wpt_db_, snap_seq, min_uncommitted,
+ backed_by_snapshot);
+ write_batch_.MultiGetFromBatchAndDB(db_, options, column_family, num_keys,
+ keys, values, statuses, sorted_input,
+ &callback);
+ if (UNLIKELY(!callback.valid() ||
+ !wpt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
+ wpt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
+ for (size_t i = 0; i < num_keys; i++) {
+ statuses[i] = Status::TryAgain();
+ }
+ }
+}
+
+Status WritePreparedTxn::Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* pinnable_val) {
+ SequenceNumber min_uncommitted, snap_seq;
+ const SnapshotBackup backed_by_snapshot =
+ wpt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
+ WritePreparedTxnReadCallback callback(wpt_db_, snap_seq, min_uncommitted,
+ backed_by_snapshot);
+ auto res = write_batch_.GetFromBatchAndDB(db_, options, column_family, key,
+ pinnable_val, &callback);
+ if (LIKELY(callback.valid() &&
+ wpt_db_->ValidateSnapshot(callback.max_visible_seq(),
+ backed_by_snapshot))) {
+ return res;
+ } else {
+ wpt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
+ return Status::TryAgain();
+ }
+}
+
+Iterator* WritePreparedTxn::GetIterator(const ReadOptions& options) {
+ // Make sure to get iterator from WritePrepareTxnDB, not the root db.
+ Iterator* db_iter = wpt_db_->NewIterator(options);
+ assert(db_iter);
+
+ return write_batch_.NewIteratorWithBase(db_iter);
+}
+
+Iterator* WritePreparedTxn::GetIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) {
+ // Make sure to get iterator from WritePrepareTxnDB, not the root db.
+ Iterator* db_iter = wpt_db_->NewIterator(options, column_family);
+ assert(db_iter);
+
+ return write_batch_.NewIteratorWithBase(column_family, db_iter);
+}
+
+Status WritePreparedTxn::PrepareInternal() {
+ WriteOptions write_options = write_options_;
+ write_options.disableWAL = false;
+ const bool WRITE_AFTER_COMMIT = true;
+ const bool kFirstPrepareBatch = true;
+ WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(), name_,
+ !WRITE_AFTER_COMMIT);
+ // For each duplicate key we account for a new sub-batch
+ prepare_batch_cnt_ = GetWriteBatch()->SubBatchCnt();
+ // Having AddPrepared in the PreReleaseCallback allows in-order addition of
+ // prepared entries to PreparedHeap and hence enables an optimization. Refer to
+ // SmallestUnCommittedSeq for more details.
+ AddPreparedCallback add_prepared_callback(
+ wpt_db_, db_impl_, prepare_batch_cnt_,
+ db_impl_->immutable_db_options().two_write_queues, kFirstPrepareBatch);
+ const bool DISABLE_MEMTABLE = true;
+ uint64_t seq_used = kMaxSequenceNumber;
+ Status s = db_impl_->WriteImpl(
+ write_options, GetWriteBatch()->GetWriteBatch(),
+ /*callback*/ nullptr, &log_number_, /*log ref*/ 0, !DISABLE_MEMTABLE,
+ &seq_used, prepare_batch_cnt_, &add_prepared_callback);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ auto prepare_seq = seq_used;
+ SetId(prepare_seq);
+ return s;
+}
+
+Status WritePreparedTxn::CommitWithoutPrepareInternal() {
+ // For each duplicate key we account for a new sub-batch
+ const size_t batch_cnt = GetWriteBatch()->SubBatchCnt();
+ return CommitBatchInternal(GetWriteBatch()->GetWriteBatch(), batch_cnt);
+}
+
+Status WritePreparedTxn::CommitBatchInternal(WriteBatch* batch,
+ size_t batch_cnt) {
+ return wpt_db_->WriteInternal(write_options_, batch, batch_cnt, this);
+}
+
+Status WritePreparedTxn::CommitInternal() {
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "CommitInternal prepare_seq: %" PRIu64, GetID());
+ // We take the commit-time batch and append the Commit marker.
+ // The Memtable will ignore the Commit marker in non-recovery mode
+ WriteBatch* working_batch = GetCommitTimeWriteBatch();
+ const bool empty = working_batch->Count() == 0;
+ WriteBatchInternal::MarkCommit(working_batch, name_);
+
+ const bool for_recovery = use_only_the_last_commit_time_batch_for_recovery_;
+ if (!empty && for_recovery) {
+ // When not writing to memtable, we can still cache the latest write batch.
+ // The cached batch will be written to memtable in WriteRecoverableState
+ // during FlushMemTable
+ WriteBatchInternal::SetAsLastestPersistentState(working_batch);
+ }
+
+ auto prepare_seq = GetId();
+ const bool includes_data = !empty && !for_recovery;
+ assert(prepare_batch_cnt_);
+ size_t commit_batch_cnt = 0;
+ if (UNLIKELY(includes_data)) {
+ ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log,
+ "Duplicate key overhead");
+ SubBatchCounter counter(*wpt_db_->GetCFComparatorMap());
+ auto s = working_batch->Iterate(&counter);
+ assert(s.ok());
+ commit_batch_cnt = counter.BatchCount();
+ }
+ const bool disable_memtable = !includes_data;
+ const bool do_one_write =
+ !db_impl_->immutable_db_options().two_write_queues || disable_memtable;
+ WritePreparedCommitEntryPreReleaseCallback update_commit_map(
+ wpt_db_, db_impl_, prepare_seq, prepare_batch_cnt_, commit_batch_cnt);
+ // This is to call AddPrepared on CommitTimeWriteBatch
+ const bool kFirstPrepareBatch = true;
+ AddPreparedCallback add_prepared_callback(
+ wpt_db_, db_impl_, commit_batch_cnt,
+ db_impl_->immutable_db_options().two_write_queues, !kFirstPrepareBatch);
+ PreReleaseCallback* pre_release_callback;
+ if (do_one_write) {
+ pre_release_callback = &update_commit_map;
+ } else {
+ pre_release_callback = &add_prepared_callback;
+ }
+ uint64_t seq_used = kMaxSequenceNumber;
+ // Since the prepared batch is directly written to memtable, there is already
+ // a connection between the memtable and its WAL, so there is no need to
+ // redundantly reference the log that contains the prepared data.
+ const uint64_t zero_log_number = 0ull;
+ size_t batch_cnt = UNLIKELY(commit_batch_cnt) ? commit_batch_cnt : 1;
+ auto s = db_impl_->WriteImpl(write_options_, working_batch, nullptr, nullptr,
+ zero_log_number, disable_memtable, &seq_used,
+ batch_cnt, pre_release_callback);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ const SequenceNumber commit_batch_seq = seq_used;
+ if (LIKELY(do_one_write || !s.ok())) {
+ if (UNLIKELY(!db_impl_->immutable_db_options().two_write_queues &&
+ s.ok())) {
+ // Note: RemovePrepared should be called after WriteImpl that publishsed
+ // the seq. Otherwise SmallestUnCommittedSeq optimization breaks.
+ wpt_db_->RemovePrepared(prepare_seq, prepare_batch_cnt_);
+ } // else RemovePrepared is called from within PreReleaseCallback
+ if (UNLIKELY(!do_one_write)) {
+ assert(!s.ok());
+ // Cleanup the prepared entry we added with add_prepared_callback
+ wpt_db_->RemovePrepared(commit_batch_seq, commit_batch_cnt);
+ }
+ return s;
+ } // else do the 2nd write to publish seq
+ // Note: the 2nd write comes with a performance penality. So if we have too
+ // many of commits accompanied with ComitTimeWriteBatch and yet we cannot
+ // enable use_only_the_last_commit_time_batch_for_recovery_ optimization,
+ // two_write_queues should be disabled to avoid many additional writes here.
+ const size_t kZeroData = 0;
+ // Update commit map only from the 2nd queue
+ WritePreparedCommitEntryPreReleaseCallback update_commit_map_with_aux_batch(
+ wpt_db_, db_impl_, prepare_seq, prepare_batch_cnt_, kZeroData,
+ commit_batch_seq, commit_batch_cnt);
+ WriteBatch empty_batch;
+ empty_batch.PutLogData(Slice());
+ // In the absence of Prepare markers, use Noop as a batch separator
+ WriteBatchInternal::InsertNoop(&empty_batch);
+ const bool DISABLE_MEMTABLE = true;
+ const size_t ONE_BATCH = 1;
+ const uint64_t NO_REF_LOG = 0;
+ s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
+ NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ &update_commit_map_with_aux_batch);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (UNLIKELY(!db_impl_->immutable_db_options().two_write_queues)) {
+ if (s.ok()) {
+ // Note: RemovePrepared should be called after WriteImpl that publishsed
+ // the seq. Otherwise SmallestUnCommittedSeq optimization breaks.
+ wpt_db_->RemovePrepared(prepare_seq, prepare_batch_cnt_);
+ }
+ wpt_db_->RemovePrepared(commit_batch_seq, commit_batch_cnt);
+ } // else RemovePrepared is called from within PreReleaseCallback
+ return s;
+}
+
+Status WritePreparedTxn::RollbackInternal() {
+ ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log,
+ "RollbackInternal prepare_seq: %" PRIu64, GetId());
+ WriteBatch rollback_batch;
+ assert(GetId() != kMaxSequenceNumber);
+ assert(GetId() > 0);
+ auto cf_map_shared_ptr = wpt_db_->GetCFHandleMap();
+ auto cf_comp_map_shared_ptr = wpt_db_->GetCFComparatorMap();
+ auto read_at_seq = kMaxSequenceNumber;
+ ReadOptions roptions;
+ // to prevent callback's seq to be overrriden inside DBImpk::Get
+ roptions.snapshot = wpt_db_->GetMaxSnapshot();
+ struct RollbackWriteBatchBuilder : public WriteBatch::Handler {
+ DBImpl* db_;
+ WritePreparedTxnReadCallback callback;
+ WriteBatch* rollback_batch_;
+ std::map<uint32_t, const Comparator*>& comparators_;
+ std::map<uint32_t, ColumnFamilyHandle*>& handles_;
+ using CFKeys = std::set<Slice, SetComparator>;
+ std::map<uint32_t, CFKeys> keys_;
+ bool rollback_merge_operands_;
+ ReadOptions roptions_;
+ RollbackWriteBatchBuilder(
+ DBImpl* db, WritePreparedTxnDB* wpt_db, SequenceNumber snap_seq,
+ WriteBatch* dst_batch,
+ std::map<uint32_t, const Comparator*>& comparators,
+ std::map<uint32_t, ColumnFamilyHandle*>& handles,
+ bool rollback_merge_operands, ReadOptions _roptions)
+ : db_(db),
+ callback(wpt_db, snap_seq), // disable min_uncommitted optimization
+ rollback_batch_(dst_batch),
+ comparators_(comparators),
+ handles_(handles),
+ rollback_merge_operands_(rollback_merge_operands),
+ roptions_(_roptions) {}
+
+ Status Rollback(uint32_t cf, const Slice& key) {
+ Status s;
+ CFKeys& cf_keys = keys_[cf];
+ if (cf_keys.size() == 0) { // just inserted
+ auto cmp = comparators_[cf];
+ keys_[cf] = CFKeys(SetComparator(cmp));
+ }
+ auto it = cf_keys.insert(key);
+ if (it.second ==
+ false) { // second is false if a element already existed.
+ return s;
+ }
+
+ PinnableSlice pinnable_val;
+ bool not_used;
+ auto cf_handle = handles_[cf];
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = cf_handle;
+ get_impl_options.value = &pinnable_val;
+ get_impl_options.value_found = &not_used;
+ get_impl_options.callback = &callback;
+ s = db_->GetImpl(roptions_, key, get_impl_options);
+ assert(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ s = rollback_batch_->Put(cf_handle, key, pinnable_val);
+ assert(s.ok());
+ } else if (s.IsNotFound()) {
+ // There has been no readable value before txn. By adding a delete we
+ // make sure that there will be none afterwards either.
+ s = rollback_batch_->Delete(cf_handle, key);
+ assert(s.ok());
+ } else {
+ // Unexpected status. Return it to the user.
+ }
+ return s;
+ }
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice& /*val*/) override {
+ return Rollback(cf, key);
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ return Rollback(cf, key);
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ return Rollback(cf, key);
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key,
+ const Slice& /*val*/) override {
+ if (rollback_merge_operands_) {
+ return Rollback(cf, key);
+ } else {
+ return Status::OK();
+ }
+ }
+
+ Status MarkNoop(bool) override { return Status::OK(); }
+ Status MarkBeginPrepare(bool) override { return Status::OK(); }
+ Status MarkEndPrepare(const Slice&) override { return Status::OK(); }
+ Status MarkCommit(const Slice&) override { return Status::OK(); }
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ protected:
+ bool WriteAfterCommit() const override { return false; }
+ } rollback_handler(db_impl_, wpt_db_, read_at_seq, &rollback_batch,
+ *cf_comp_map_shared_ptr.get(), *cf_map_shared_ptr.get(),
+ wpt_db_->txn_db_options_.rollback_merge_operands,
+ roptions);
+ auto s = GetWriteBatch()->GetWriteBatch()->Iterate(&rollback_handler);
+ assert(s.ok());
+ if (!s.ok()) {
+ return s;
+ }
+ // The Rollback marker will be used as a batch separator
+ WriteBatchInternal::MarkRollback(&rollback_batch, name_);
+ bool do_one_write = !db_impl_->immutable_db_options().two_write_queues;
+ const bool DISABLE_MEMTABLE = true;
+ const uint64_t NO_REF_LOG = 0;
+ uint64_t seq_used = kMaxSequenceNumber;
+ const size_t ONE_BATCH = 1;
+ const bool kFirstPrepareBatch = true;
+ // We commit the rolled back prepared batches. Although this is
+ // counter-intuitive, i) it is safe to do so, since the prepared batches are
+ // already canceled out by the rollback batch, ii) adding the commit entry to
+ // CommitCache will allow us to benefit from the existing mechanism in
+ // CommitCache that keeps an entry evicted due to max advance and yet overlaps
+ // with a live snapshot around so that the live snapshot properly skips the
+ // entry even if its prepare seq is lower than max_evicted_seq_.
+ AddPreparedCallback add_prepared_callback(
+ wpt_db_, db_impl_, ONE_BATCH,
+ db_impl_->immutable_db_options().two_write_queues, !kFirstPrepareBatch);
+ WritePreparedCommitEntryPreReleaseCallback update_commit_map(
+ wpt_db_, db_impl_, GetId(), prepare_batch_cnt_, ONE_BATCH);
+ PreReleaseCallback* pre_release_callback;
+ if (do_one_write) {
+ pre_release_callback = &update_commit_map;
+ } else {
+ pre_release_callback = &add_prepared_callback;
+ }
+ // Note: the rollback batch does not need AddPrepared since it is written to
+ // DB in one shot. min_uncommitted still works since it requires capturing
+ // data that is written to DB but not yet committed, while
+ // the rollback batch commits with PreReleaseCallback.
+ s = db_impl_->WriteImpl(write_options_, &rollback_batch, nullptr, nullptr,
+ NO_REF_LOG, !DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ pre_release_callback);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (!s.ok()) {
+ return s;
+ }
+ if (do_one_write) {
+ assert(!db_impl_->immutable_db_options().two_write_queues);
+ wpt_db_->RemovePrepared(GetId(), prepare_batch_cnt_);
+ return s;
+ } // else do the 2nd write for commit
+ uint64_t rollback_seq = seq_used;
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "RollbackInternal 2nd write rollback_seq: %" PRIu64,
+ rollback_seq);
+ // Commit the batch by writing an empty batch to the queue that will release
+ // the commit sequence number to readers.
+ WritePreparedRollbackPreReleaseCallback update_commit_map_with_prepare(
+ wpt_db_, db_impl_, GetId(), rollback_seq, prepare_batch_cnt_);
+ WriteBatch empty_batch;
+ empty_batch.PutLogData(Slice());
+ // In the absence of Prepare markers, use Noop as a batch separator
+ WriteBatchInternal::InsertNoop(&empty_batch);
+ s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
+ NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ &update_commit_map_with_prepare);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "RollbackInternal (status=%s) commit: %" PRIu64,
+ s.ToString().c_str(), GetId());
+ // TODO(lth): For WriteUnPrepared that rollback is called frequently,
+ // RemovePrepared could be moved to the callback to reduce lock contention.
+ if (s.ok()) {
+ wpt_db_->RemovePrepared(GetId(), prepare_batch_cnt_);
+ }
+ // Note: RemovePrepared for prepared batch is called from within
+ // PreReleaseCallback
+ wpt_db_->RemovePrepared(rollback_seq, ONE_BATCH);
+
+ return s;
+}
+
+Status WritePreparedTxn::ValidateSnapshot(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ SequenceNumber* tracked_at_seq) {
+ assert(snapshot_);
+
+ SequenceNumber min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ snapshot_.get())
+ ->min_uncommitted_;
+ SequenceNumber snap_seq = snapshot_->GetSequenceNumber();
+ // tracked_at_seq is either max or the last snapshot with which this key was
+ // trackeed so there is no need to apply the IsInSnapshot to this comparison
+ // here as tracked_at_seq is not a prepare seq.
+ if (*tracked_at_seq <= snap_seq) {
+ // If the key has been previous validated at a sequence number earlier
+ // than the curent snapshot's sequence number, we already know it has not
+ // been modified.
+ return Status::OK();
+ }
+
+ *tracked_at_seq = snap_seq;
+
+ ColumnFamilyHandle* cfh =
+ column_family ? column_family : db_impl_->DefaultColumnFamily();
+
+ WritePreparedTxnReadCallback snap_checker(wpt_db_, snap_seq, min_uncommitted,
+ kBackedByDBSnapshot);
+ return TransactionUtil::CheckKeyForConflicts(db_impl_, cfh, key.ToString(),
+ snap_seq, false /* cache_only */,
+ &snap_checker, min_uncommitted);
+}
+
+void WritePreparedTxn::SetSnapshot() {
+ const bool kForWWConflictCheck = true;
+ SnapshotImpl* snapshot = wpt_db_->GetSnapshotInternal(kForWWConflictCheck);
+ SetSnapshotInternal(snapshot);
+}
+
+Status WritePreparedTxn::RebuildFromWriteBatch(WriteBatch* src_batch) {
+ auto ret = PessimisticTransaction::RebuildFromWriteBatch(src_batch);
+ prepare_batch_cnt_ = GetWriteBatch()->SubBatchCnt();
+ return ret;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.h b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.h
new file mode 100644
index 00000000..30d9bdb9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <algorithm>
+#include <atomic>
+#include <mutex>
+#include <stack>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "db/write_callback.h"
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/snapshot.h"
+#include "rocksdb/status.h"
+#include "rocksdb/types.h"
+#include "rocksdb/utilities/transaction.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/autovector.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+#include "utilities/transactions/transaction_base.h"
+#include "utilities/transactions/transaction_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WritePreparedTxnDB;
+
+// This impl could write to DB also uncommitted data and then later tell apart
+// committed data from uncommitted data. Uncommitted data could be after the
+// Prepare phase in 2PC (WritePreparedTxn) or before that
+// (WriteUnpreparedTxnImpl).
+class WritePreparedTxn : public PessimisticTransaction {
+ public:
+ WritePreparedTxn(WritePreparedTxnDB* db, const WriteOptions& write_options,
+ const TransactionOptions& txn_options);
+ // No copying allowed
+ WritePreparedTxn(const WritePreparedTxn&) = delete;
+ void operator=(const WritePreparedTxn&) = delete;
+
+ virtual ~WritePreparedTxn() {}
+
+ // To make WAL commit markers visible, the snapshot will be based on the last
+ // seq in the WAL that is also published, LastPublishedSequence, as opposed to
+ // the last seq in the memtable.
+ using Transaction::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ using Transaction::MultiGet;
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input = false) override;
+
+ // Note: The behavior is undefined in presence of interleaved writes to the
+ // same transaction.
+ // To make WAL commit markers visible, the snapshot will be
+ // based on the last seq in the WAL that is also published,
+ // LastPublishedSequence, as opposed to the last seq in the memtable.
+ using Transaction::GetIterator;
+ virtual Iterator* GetIterator(const ReadOptions& options) override;
+ virtual Iterator* GetIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) override;
+
+ virtual void SetSnapshot() override;
+
+ protected:
+ void Initialize(const TransactionOptions& txn_options) override;
+ // Override the protected SetId to make it visible to the friend class
+ // WritePreparedTxnDB
+ inline void SetId(uint64_t id) override { Transaction::SetId(id); }
+
+ private:
+ friend class WritePreparedTransactionTest_BasicRecoveryTest_Test;
+ friend class WritePreparedTxnDB;
+ friend class WriteUnpreparedTxnDB;
+ friend class WriteUnpreparedTxn;
+
+ Status PrepareInternal() override;
+
+ Status CommitWithoutPrepareInternal() override;
+
+ Status CommitBatchInternal(WriteBatch* batch, size_t batch_cnt) override;
+
+ // Since the data is already written to memtables at the Prepare phase, the
+ // commit entails writing only a commit marker in the WAL. The sequence number
+ // of the commit marker is then the commit timestamp of the transaction. To
+ // make WAL commit markers visible, the snapshot will be based on the last seq
+ // in the WAL that is also published, LastPublishedSequence, as opposed to the
+ // last seq in the memtable.
+ Status CommitInternal() override;
+
+ Status RollbackInternal() override;
+
+ virtual Status ValidateSnapshot(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ SequenceNumber* tracked_at_seq) override;
+
+ virtual Status RebuildFromWriteBatch(WriteBatch* src_batch) override;
+
+ WritePreparedTxnDB* wpt_db_;
+ // Number of sub-batches in prepare
+ size_t prepare_batch_cnt_ = 0;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.cc
new file mode 100644
index 00000000..051fae55
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.cc
@@ -0,0 +1,998 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/write_prepared_txn_db.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "db/arena_wrapped_db_iter.h"
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "test_util/sync_point.h"
+#include "util/cast_util.h"
+#include "util/mutexlock.h"
+#include "util/string_util.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/transaction_db_mutex_impl.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+Status WritePreparedTxnDB::Initialize(
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles) {
+ auto dbimpl = static_cast_with_check<DBImpl, DB>(GetRootDB());
+ assert(dbimpl != nullptr);
+ auto rtxns = dbimpl->recovered_transactions();
+ std::map<SequenceNumber, SequenceNumber> ordered_seq_cnt;
+ for (auto rtxn : rtxns) {
+ // There should only one batch for WritePrepared policy.
+ assert(rtxn.second->batches_.size() == 1);
+ const auto& seq = rtxn.second->batches_.begin()->first;
+ const auto& batch_info = rtxn.second->batches_.begin()->second;
+ auto cnt = batch_info.batch_cnt_ ? batch_info.batch_cnt_ : 1;
+ ordered_seq_cnt[seq] = cnt;
+ }
+ // AddPrepared must be called in order
+ for (auto seq_cnt : ordered_seq_cnt) {
+ auto seq = seq_cnt.first;
+ auto cnt = seq_cnt.second;
+ for (size_t i = 0; i < cnt; i++) {
+ AddPrepared(seq + i);
+ }
+ }
+ SequenceNumber prev_max = max_evicted_seq_;
+ SequenceNumber last_seq = db_impl_->GetLatestSequenceNumber();
+ AdvanceMaxEvictedSeq(prev_max, last_seq);
+ // Create a gap between max and the next snapshot. This simplifies the logic
+ // in IsInSnapshot by not having to consider the special case of max ==
+ // snapshot after recovery. This is tested in IsInSnapshotEmptyMapTest.
+ if (last_seq) {
+ db_impl_->versions_->SetLastAllocatedSequence(last_seq + 1);
+ db_impl_->versions_->SetLastSequence(last_seq + 1);
+ db_impl_->versions_->SetLastPublishedSequence(last_seq + 1);
+ }
+
+ db_impl_->SetSnapshotChecker(new WritePreparedSnapshotChecker(this));
+ // A callback to commit a single sub-batch
+ class CommitSubBatchPreReleaseCallback : public PreReleaseCallback {
+ public:
+ explicit CommitSubBatchPreReleaseCallback(WritePreparedTxnDB* db)
+ : db_(db) {}
+ Status Callback(SequenceNumber commit_seq,
+ bool is_mem_disabled __attribute__((__unused__)), uint64_t,
+ size_t /*index*/, size_t /*total*/) override {
+ assert(!is_mem_disabled);
+ db_->AddCommitted(commit_seq, commit_seq);
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ };
+ db_impl_->SetRecoverableStatePreReleaseCallback(
+ new CommitSubBatchPreReleaseCallback(this));
+
+ auto s = PessimisticTransactionDB::Initialize(compaction_enabled_cf_indices,
+ handles);
+ return s;
+}
+
+Status WritePreparedTxnDB::VerifyCFOptions(
+ const ColumnFamilyOptions& cf_options) {
+ Status s = PessimisticTransactionDB::VerifyCFOptions(cf_options);
+ if (!s.ok()) {
+ return s;
+ }
+ if (!cf_options.memtable_factory->CanHandleDuplicatedKey()) {
+ return Status::InvalidArgument(
+ "memtable_factory->CanHandleDuplicatedKey() cannot be false with "
+ "WritePrpeared transactions");
+ }
+ return Status::OK();
+}
+
+Transaction* WritePreparedTxnDB::BeginTransaction(
+ const WriteOptions& write_options, const TransactionOptions& txn_options,
+ Transaction* old_txn) {
+ if (old_txn != nullptr) {
+ ReinitializeTransaction(old_txn, write_options, txn_options);
+ return old_txn;
+ } else {
+ return new WritePreparedTxn(this, write_options, txn_options);
+ }
+}
+
+Status WritePreparedTxnDB::Write(const WriteOptions& opts,
+ WriteBatch* updates) {
+ if (txn_db_options_.skip_concurrency_control) {
+ // Skip locking the rows
+ const size_t UNKNOWN_BATCH_CNT = 0;
+ WritePreparedTxn* NO_TXN = nullptr;
+ return WriteInternal(opts, updates, UNKNOWN_BATCH_CNT, NO_TXN);
+ } else {
+ return PessimisticTransactionDB::WriteWithConcurrencyControl(opts, updates);
+ }
+}
+
+Status WritePreparedTxnDB::Write(
+ const WriteOptions& opts,
+ const TransactionDBWriteOptimizations& optimizations, WriteBatch* updates) {
+ if (optimizations.skip_concurrency_control) {
+ // Skip locking the rows
+ const size_t UNKNOWN_BATCH_CNT = 0;
+ const size_t ONE_BATCH_CNT = 1;
+ const size_t batch_cnt = optimizations.skip_duplicate_key_check
+ ? ONE_BATCH_CNT
+ : UNKNOWN_BATCH_CNT;
+ WritePreparedTxn* NO_TXN = nullptr;
+ return WriteInternal(opts, updates, batch_cnt, NO_TXN);
+ } else {
+ // TODO(myabandeh): Make use of skip_duplicate_key_check hint
+ // Fall back to unoptimized version
+ return PessimisticTransactionDB::WriteWithConcurrencyControl(opts, updates);
+ }
+}
+
+Status WritePreparedTxnDB::WriteInternal(const WriteOptions& write_options_orig,
+ WriteBatch* batch, size_t batch_cnt,
+ WritePreparedTxn* txn) {
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "CommitBatchInternal");
+ if (batch->Count() == 0) {
+ // Otherwise our 1 seq per batch logic will break since there is no seq
+ // increased for this batch.
+ return Status::OK();
+ }
+ if (batch_cnt == 0) { // not provided, then compute it
+ // TODO(myabandeh): add an option to allow user skipping this cost
+ SubBatchCounter counter(*GetCFComparatorMap());
+ auto s = batch->Iterate(&counter);
+ assert(s.ok());
+ batch_cnt = counter.BatchCount();
+ WPRecordTick(TXN_DUPLICATE_KEY_OVERHEAD);
+ ROCKS_LOG_DETAILS(info_log_, "Duplicate key overhead: %" PRIu64 " batches",
+ static_cast<uint64_t>(batch_cnt));
+ }
+ assert(batch_cnt);
+
+ bool do_one_write = !db_impl_->immutable_db_options().two_write_queues;
+ WriteOptions write_options(write_options_orig);
+ // In the absence of Prepare markers, use Noop as a batch separator
+ WriteBatchInternal::InsertNoop(batch);
+ const bool DISABLE_MEMTABLE = true;
+ const uint64_t no_log_ref = 0;
+ uint64_t seq_used = kMaxSequenceNumber;
+ const size_t ZERO_PREPARES = 0;
+ const bool kSeperatePrepareCommitBatches = true;
+ // Since this is not 2pc, there is no need for AddPrepared but having it in
+ // the PreReleaseCallback enables an optimization. Refer to
+ // SmallestUnCommittedSeq for more details.
+ AddPreparedCallback add_prepared_callback(
+ this, db_impl_, batch_cnt,
+ db_impl_->immutable_db_options().two_write_queues,
+ !kSeperatePrepareCommitBatches);
+ WritePreparedCommitEntryPreReleaseCallback update_commit_map(
+ this, db_impl_, kMaxSequenceNumber, ZERO_PREPARES, batch_cnt);
+ PreReleaseCallback* pre_release_callback;
+ if (do_one_write) {
+ pre_release_callback = &update_commit_map;
+ } else {
+ pre_release_callback = &add_prepared_callback;
+ }
+ auto s = db_impl_->WriteImpl(write_options, batch, nullptr, nullptr,
+ no_log_ref, !DISABLE_MEMTABLE, &seq_used,
+ batch_cnt, pre_release_callback);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ uint64_t prepare_seq = seq_used;
+ if (txn != nullptr) {
+ txn->SetId(prepare_seq);
+ }
+ if (!s.ok()) {
+ return s;
+ }
+ if (do_one_write) {
+ return s;
+ } // else do the 2nd write for commit
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "CommitBatchInternal 2nd write prepare_seq: %" PRIu64,
+ prepare_seq);
+ // Commit the batch by writing an empty batch to the 2nd queue that will
+ // release the commit sequence number to readers.
+ const size_t ZERO_COMMITS = 0;
+ WritePreparedCommitEntryPreReleaseCallback update_commit_map_with_prepare(
+ this, db_impl_, prepare_seq, batch_cnt, ZERO_COMMITS);
+ WriteBatch empty_batch;
+ write_options.disableWAL = true;
+ write_options.sync = false;
+ const size_t ONE_BATCH = 1; // Just to inc the seq
+ s = db_impl_->WriteImpl(write_options, &empty_batch, nullptr, nullptr,
+ no_log_ref, DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ &update_commit_map_with_prepare);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ // Note: RemovePrepared is called from within PreReleaseCallback
+ return s;
+}
+
+Status WritePreparedTxnDB::Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value) {
+ SequenceNumber min_uncommitted, snap_seq;
+ const SnapshotBackup backed_by_snapshot =
+ AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
+ WritePreparedTxnReadCallback callback(this, snap_seq, min_uncommitted,
+ backed_by_snapshot);
+ bool* dont_care = nullptr;
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.value = value;
+ get_impl_options.value_found = dont_care;
+ get_impl_options.callback = &callback;
+ auto res = db_impl_->GetImpl(options, key, get_impl_options);
+ if (LIKELY(callback.valid() && ValidateSnapshot(callback.max_visible_seq(),
+ backed_by_snapshot))) {
+ return res;
+ } else {
+ WPRecordTick(TXN_GET_TRY_AGAIN);
+ return Status::TryAgain();
+ }
+}
+
+void WritePreparedTxnDB::UpdateCFComparatorMap(
+ const std::vector<ColumnFamilyHandle*>& handles) {
+ auto cf_map = new std::map<uint32_t, const Comparator*>();
+ auto handle_map = new std::map<uint32_t, ColumnFamilyHandle*>();
+ for (auto h : handles) {
+ auto id = h->GetID();
+ const Comparator* comparator = h->GetComparator();
+ (*cf_map)[id] = comparator;
+ if (id != 0) {
+ (*handle_map)[id] = h;
+ } else {
+ // The pointer to the default cf handle in the handles will be deleted.
+ // Use the pointer maintained by the db instead.
+ (*handle_map)[id] = DefaultColumnFamily();
+ }
+ }
+ cf_map_.reset(cf_map);
+ handle_map_.reset(handle_map);
+}
+
+void WritePreparedTxnDB::UpdateCFComparatorMap(ColumnFamilyHandle* h) {
+ auto old_cf_map_ptr = cf_map_.get();
+ assert(old_cf_map_ptr);
+ auto cf_map = new std::map<uint32_t, const Comparator*>(*old_cf_map_ptr);
+ auto old_handle_map_ptr = handle_map_.get();
+ assert(old_handle_map_ptr);
+ auto handle_map =
+ new std::map<uint32_t, ColumnFamilyHandle*>(*old_handle_map_ptr);
+ auto id = h->GetID();
+ const Comparator* comparator = h->GetComparator();
+ (*cf_map)[id] = comparator;
+ (*handle_map)[id] = h;
+ cf_map_.reset(cf_map);
+ handle_map_.reset(handle_map);
+}
+
+
+std::vector<Status> WritePreparedTxnDB::MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ assert(values);
+ size_t num_keys = keys.size();
+ values->resize(num_keys);
+
+ std::vector<Status> stat_list(num_keys);
+ for (size_t i = 0; i < num_keys; ++i) {
+ std::string* value = values ? &(*values)[i] : nullptr;
+ stat_list[i] = this->Get(options, column_family[i], keys[i], value);
+ }
+ return stat_list;
+}
+
+// Struct to hold ownership of snapshot and read callback for iterator cleanup.
+struct WritePreparedTxnDB::IteratorState {
+ IteratorState(WritePreparedTxnDB* txn_db, SequenceNumber sequence,
+ std::shared_ptr<ManagedSnapshot> s,
+ SequenceNumber min_uncommitted)
+ : callback(txn_db, sequence, min_uncommitted, kBackedByDBSnapshot),
+ snapshot(s) {}
+
+ WritePreparedTxnReadCallback callback;
+ std::shared_ptr<ManagedSnapshot> snapshot;
+};
+
+namespace {
+static void CleanupWritePreparedTxnDBIterator(void* arg1, void* /*arg2*/) {
+ delete reinterpret_cast<WritePreparedTxnDB::IteratorState*>(arg1);
+}
+} // anonymous namespace
+
+Iterator* WritePreparedTxnDB::NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) {
+ constexpr bool ALLOW_BLOB = true;
+ constexpr bool ALLOW_REFRESH = true;
+ std::shared_ptr<ManagedSnapshot> own_snapshot = nullptr;
+ SequenceNumber snapshot_seq = kMaxSequenceNumber;
+ SequenceNumber min_uncommitted = 0;
+ if (options.snapshot != nullptr) {
+ snapshot_seq = options.snapshot->GetSequenceNumber();
+ min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ options.snapshot)
+ ->min_uncommitted_;
+ } else {
+ auto* snapshot = GetSnapshot();
+ // We take a snapshot to make sure that the related data in the commit map
+ // are not deleted.
+ snapshot_seq = snapshot->GetSequenceNumber();
+ min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(snapshot)
+ ->min_uncommitted_;
+ own_snapshot = std::make_shared<ManagedSnapshot>(db_impl_, snapshot);
+ }
+ assert(snapshot_seq != kMaxSequenceNumber);
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ auto* state =
+ new IteratorState(this, snapshot_seq, own_snapshot, min_uncommitted);
+ auto* db_iter =
+ db_impl_->NewIteratorImpl(options, cfd, snapshot_seq, &state->callback,
+ !ALLOW_BLOB, !ALLOW_REFRESH);
+ db_iter->RegisterCleanup(CleanupWritePreparedTxnDBIterator, state, nullptr);
+ return db_iter;
+}
+
+Status WritePreparedTxnDB::NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) {
+ constexpr bool ALLOW_BLOB = true;
+ constexpr bool ALLOW_REFRESH = true;
+ std::shared_ptr<ManagedSnapshot> own_snapshot = nullptr;
+ SequenceNumber snapshot_seq = kMaxSequenceNumber;
+ SequenceNumber min_uncommitted = 0;
+ if (options.snapshot != nullptr) {
+ snapshot_seq = options.snapshot->GetSequenceNumber();
+ min_uncommitted = static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ options.snapshot)
+ ->min_uncommitted_;
+ } else {
+ auto* snapshot = GetSnapshot();
+ // We take a snapshot to make sure that the related data in the commit map
+ // are not deleted.
+ snapshot_seq = snapshot->GetSequenceNumber();
+ own_snapshot = std::make_shared<ManagedSnapshot>(db_impl_, snapshot);
+ min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(snapshot)
+ ->min_uncommitted_;
+ }
+ iterators->clear();
+ iterators->reserve(column_families.size());
+ for (auto* column_family : column_families) {
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ auto* state =
+ new IteratorState(this, snapshot_seq, own_snapshot, min_uncommitted);
+ auto* db_iter =
+ db_impl_->NewIteratorImpl(options, cfd, snapshot_seq, &state->callback,
+ !ALLOW_BLOB, !ALLOW_REFRESH);
+ db_iter->RegisterCleanup(CleanupWritePreparedTxnDBIterator, state, nullptr);
+ iterators->push_back(db_iter);
+ }
+ return Status::OK();
+}
+
+void WritePreparedTxnDB::Init(const TransactionDBOptions& /* unused */) {
+ // Adcance max_evicted_seq_ no more than 100 times before the cache wraps
+ // around.
+ INC_STEP_FOR_MAX_EVICTED =
+ std::max(COMMIT_CACHE_SIZE / 100, static_cast<size_t>(1));
+ snapshot_cache_ = std::unique_ptr<std::atomic<SequenceNumber>[]>(
+ new std::atomic<SequenceNumber>[SNAPSHOT_CACHE_SIZE] {});
+ commit_cache_ = std::unique_ptr<std::atomic<CommitEntry64b>[]>(
+ new std::atomic<CommitEntry64b>[COMMIT_CACHE_SIZE] {});
+ dummy_max_snapshot_.number_ = kMaxSequenceNumber;
+}
+
+void WritePreparedTxnDB::CheckPreparedAgainstMax(SequenceNumber new_max,
+ bool locked) {
+ // When max_evicted_seq_ advances, move older entries from prepared_txns_
+ // to delayed_prepared_. This guarantees that if a seq is lower than max,
+ // then it is not in prepared_txns_ and save an expensive, synchronized
+ // lookup from a shared set. delayed_prepared_ is expected to be empty in
+ // normal cases.
+ ROCKS_LOG_DETAILS(
+ info_log_,
+ "CheckPreparedAgainstMax prepared_txns_.empty() %d top: %" PRIu64,
+ prepared_txns_.empty(),
+ prepared_txns_.empty() ? 0 : prepared_txns_.top());
+ const SequenceNumber prepared_top = prepared_txns_.top();
+ const bool empty = prepared_top == kMaxSequenceNumber;
+ // Preliminary check to avoid the synchronization cost
+ if (!empty && prepared_top <= new_max) {
+ if (locked) {
+ // Needed to avoid double locking in pop().
+ prepared_txns_.push_pop_mutex()->Unlock();
+ }
+ WriteLock wl(&prepared_mutex_);
+ // Need to fetch fresh values of ::top after mutex is acquired
+ while (!prepared_txns_.empty() && prepared_txns_.top() <= new_max) {
+ auto to_be_popped = prepared_txns_.top();
+ delayed_prepared_.insert(to_be_popped);
+ ROCKS_LOG_WARN(info_log_,
+ "prepared_mutex_ overhead %" PRIu64 " (prep=%" PRIu64
+ " new_max=%" PRIu64,
+ static_cast<uint64_t>(delayed_prepared_.size()),
+ to_be_popped, new_max);
+ delayed_prepared_empty_.store(false, std::memory_order_release);
+ // Update prepared_txns_ after updating delayed_prepared_empty_ otherwise
+ // there will be a point in time that the entry is neither in
+ // prepared_txns_ nor in delayed_prepared_, which will not be checked if
+ // delayed_prepared_empty_ is false.
+ prepared_txns_.pop();
+ }
+ if (locked) {
+ prepared_txns_.push_pop_mutex()->Lock();
+ }
+ }
+}
+
+void WritePreparedTxnDB::AddPrepared(uint64_t seq, bool locked) {
+ ROCKS_LOG_DETAILS(info_log_, "Txn %" PRIu64 " Preparing with max %" PRIu64,
+ seq, max_evicted_seq_.load());
+ TEST_SYNC_POINT("AddPrepared::begin:pause");
+ TEST_SYNC_POINT("AddPrepared::begin:resume");
+ if (!locked) {
+ prepared_txns_.push_pop_mutex()->Lock();
+ }
+ prepared_txns_.push_pop_mutex()->AssertHeld();
+ prepared_txns_.push(seq);
+ auto new_max = future_max_evicted_seq_.load();
+ if (UNLIKELY(seq <= new_max)) {
+ // This should not happen in normal case
+ ROCKS_LOG_ERROR(
+ info_log_,
+ "Added prepare_seq is not larger than max_evicted_seq_: %" PRIu64
+ " <= %" PRIu64,
+ seq, new_max);
+ CheckPreparedAgainstMax(new_max, true /*locked*/);
+ }
+ if (!locked) {
+ prepared_txns_.push_pop_mutex()->Unlock();
+ }
+ TEST_SYNC_POINT("AddPrepared::end");
+}
+
+void WritePreparedTxnDB::AddCommitted(uint64_t prepare_seq, uint64_t commit_seq,
+ uint8_t loop_cnt) {
+ ROCKS_LOG_DETAILS(info_log_, "Txn %" PRIu64 " Committing with %" PRIu64,
+ prepare_seq, commit_seq);
+ TEST_SYNC_POINT("WritePreparedTxnDB::AddCommitted:start");
+ TEST_SYNC_POINT("WritePreparedTxnDB::AddCommitted:start:pause");
+ auto indexed_seq = prepare_seq % COMMIT_CACHE_SIZE;
+ CommitEntry64b evicted_64b;
+ CommitEntry evicted;
+ bool to_be_evicted = GetCommitEntry(indexed_seq, &evicted_64b, &evicted);
+ if (LIKELY(to_be_evicted)) {
+ assert(evicted.prep_seq != prepare_seq);
+ auto prev_max = max_evicted_seq_.load(std::memory_order_acquire);
+ ROCKS_LOG_DETAILS(info_log_,
+ "Evicting %" PRIu64 ",%" PRIu64 " with max %" PRIu64,
+ evicted.prep_seq, evicted.commit_seq, prev_max);
+ if (prev_max < evicted.commit_seq) {
+ auto last = db_impl_->GetLastPublishedSequence(); // could be 0
+ SequenceNumber max_evicted_seq;
+ if (LIKELY(evicted.commit_seq < last)) {
+ assert(last > 0);
+ // Inc max in larger steps to avoid frequent updates
+ max_evicted_seq =
+ std::min(evicted.commit_seq + INC_STEP_FOR_MAX_EVICTED, last - 1);
+ } else {
+ // legit when a commit entry in a write batch overwrite the previous one
+ max_evicted_seq = evicted.commit_seq;
+ }
+ ROCKS_LOG_DETAILS(info_log_,
+ "%lu Evicting %" PRIu64 ",%" PRIu64 " with max %" PRIu64
+ " => %lu",
+ prepare_seq, evicted.prep_seq, evicted.commit_seq,
+ prev_max, max_evicted_seq);
+ AdvanceMaxEvictedSeq(prev_max, max_evicted_seq);
+ }
+ // After each eviction from commit cache, check if the commit entry should
+ // be kept around because it overlaps with a live snapshot.
+ CheckAgainstSnapshots(evicted);
+ if (UNLIKELY(!delayed_prepared_empty_.load(std::memory_order_acquire))) {
+ WriteLock wl(&prepared_mutex_);
+ for (auto dp : delayed_prepared_) {
+ if (dp == evicted.prep_seq) {
+ // This is a rare case that txn is committed but prepared_txns_ is not
+ // cleaned up yet. Refer to delayed_prepared_commits_ definition for
+ // why it should be kept updated.
+ delayed_prepared_commits_[evicted.prep_seq] = evicted.commit_seq;
+ ROCKS_LOG_DEBUG(info_log_,
+ "delayed_prepared_commits_[%" PRIu64 "]=%" PRIu64,
+ evicted.prep_seq, evicted.commit_seq);
+ break;
+ }
+ }
+ }
+ }
+ bool succ =
+ ExchangeCommitEntry(indexed_seq, evicted_64b, {prepare_seq, commit_seq});
+ if (UNLIKELY(!succ)) {
+ ROCKS_LOG_ERROR(info_log_,
+ "ExchangeCommitEntry failed on [%" PRIu64 "] %" PRIu64
+ ",%" PRIu64 " retrying...",
+ indexed_seq, prepare_seq, commit_seq);
+ // A very rare event, in which the commit entry is updated before we do.
+ // Here we apply a very simple solution of retrying.
+ if (loop_cnt > 100) {
+ throw std::runtime_error("Infinite loop in AddCommitted!");
+ }
+ AddCommitted(prepare_seq, commit_seq, ++loop_cnt);
+ return;
+ }
+ TEST_SYNC_POINT("WritePreparedTxnDB::AddCommitted:end");
+ TEST_SYNC_POINT("WritePreparedTxnDB::AddCommitted:end:pause");
+}
+
+void WritePreparedTxnDB::RemovePrepared(const uint64_t prepare_seq,
+ const size_t batch_cnt) {
+ TEST_SYNC_POINT_CALLBACK(
+ "RemovePrepared:Start",
+ const_cast<void*>(reinterpret_cast<const void*>(&prepare_seq)));
+ TEST_SYNC_POINT("WritePreparedTxnDB::RemovePrepared:pause");
+ TEST_SYNC_POINT("WritePreparedTxnDB::RemovePrepared:resume");
+ ROCKS_LOG_DETAILS(info_log_,
+ "RemovePrepared %" PRIu64 " cnt: %" ROCKSDB_PRIszt,
+ prepare_seq, batch_cnt);
+ WriteLock wl(&prepared_mutex_);
+ for (size_t i = 0; i < batch_cnt; i++) {
+ prepared_txns_.erase(prepare_seq + i);
+ bool was_empty = delayed_prepared_.empty();
+ if (!was_empty) {
+ delayed_prepared_.erase(prepare_seq + i);
+ auto it = delayed_prepared_commits_.find(prepare_seq + i);
+ if (it != delayed_prepared_commits_.end()) {
+ ROCKS_LOG_DETAILS(info_log_, "delayed_prepared_commits_.erase %" PRIu64,
+ prepare_seq + i);
+ delayed_prepared_commits_.erase(it);
+ }
+ bool is_empty = delayed_prepared_.empty();
+ if (was_empty != is_empty) {
+ delayed_prepared_empty_.store(is_empty, std::memory_order_release);
+ }
+ }
+ }
+}
+
+bool WritePreparedTxnDB::GetCommitEntry(const uint64_t indexed_seq,
+ CommitEntry64b* entry_64b,
+ CommitEntry* entry) const {
+ *entry_64b = commit_cache_[static_cast<size_t>(indexed_seq)].load(std::memory_order_acquire);
+ bool valid = entry_64b->Parse(indexed_seq, entry, FORMAT);
+ return valid;
+}
+
+bool WritePreparedTxnDB::AddCommitEntry(const uint64_t indexed_seq,
+ const CommitEntry& new_entry,
+ CommitEntry* evicted_entry) {
+ CommitEntry64b new_entry_64b(new_entry, FORMAT);
+ CommitEntry64b evicted_entry_64b = commit_cache_[static_cast<size_t>(indexed_seq)].exchange(
+ new_entry_64b, std::memory_order_acq_rel);
+ bool valid = evicted_entry_64b.Parse(indexed_seq, evicted_entry, FORMAT);
+ return valid;
+}
+
+bool WritePreparedTxnDB::ExchangeCommitEntry(const uint64_t indexed_seq,
+ CommitEntry64b& expected_entry_64b,
+ const CommitEntry& new_entry) {
+ auto& atomic_entry = commit_cache_[static_cast<size_t>(indexed_seq)];
+ CommitEntry64b new_entry_64b(new_entry, FORMAT);
+ bool succ = atomic_entry.compare_exchange_strong(
+ expected_entry_64b, new_entry_64b, std::memory_order_acq_rel,
+ std::memory_order_acquire);
+ return succ;
+}
+
+void WritePreparedTxnDB::AdvanceMaxEvictedSeq(const SequenceNumber& prev_max,
+ const SequenceNumber& new_max) {
+ ROCKS_LOG_DETAILS(info_log_,
+ "AdvanceMaxEvictedSeq overhead %" PRIu64 " => %" PRIu64,
+ prev_max, new_max);
+ // Declare the intention before getting snapshot from the DB. This helps a
+ // concurrent GetSnapshot to wait to catch up with future_max_evicted_seq_ if
+ // it has not already. Otherwise the new snapshot is when we ask DB for
+ // snapshots smaller than future max.
+ auto updated_future_max = prev_max;
+ while (updated_future_max < new_max &&
+ !future_max_evicted_seq_.compare_exchange_weak(
+ updated_future_max, new_max, std::memory_order_acq_rel,
+ std::memory_order_relaxed)) {
+ };
+
+ CheckPreparedAgainstMax(new_max, false /*locked*/);
+
+ // With each change to max_evicted_seq_ fetch the live snapshots behind it.
+ // We use max as the version of snapshots to identify how fresh are the
+ // snapshot list. This works because the snapshots are between 0 and
+ // max, so the larger the max, the more complete they are.
+ SequenceNumber new_snapshots_version = new_max;
+ std::vector<SequenceNumber> snapshots;
+ bool update_snapshots = false;
+ if (new_snapshots_version > snapshots_version_) {
+ // This is to avoid updating the snapshots_ if it already updated
+ // with a more recent vesion by a concrrent thread
+ update_snapshots = true;
+ // We only care about snapshots lower then max
+ snapshots = GetSnapshotListFromDB(new_max);
+ }
+ if (update_snapshots) {
+ UpdateSnapshots(snapshots, new_snapshots_version);
+ if (!snapshots.empty()) {
+ WriteLock wl(&old_commit_map_mutex_);
+ for (auto snap : snapshots) {
+ // This allows IsInSnapshot to tell apart the reads from in valid
+ // snapshots from the reads from committed values in valid snapshots.
+ old_commit_map_[snap];
+ }
+ old_commit_map_empty_.store(false, std::memory_order_release);
+ }
+ }
+ auto updated_prev_max = prev_max;
+ TEST_SYNC_POINT("AdvanceMaxEvictedSeq::update_max:pause");
+ TEST_SYNC_POINT("AdvanceMaxEvictedSeq::update_max:resume");
+ while (updated_prev_max < new_max &&
+ !max_evicted_seq_.compare_exchange_weak(updated_prev_max, new_max,
+ std::memory_order_acq_rel,
+ std::memory_order_relaxed)) {
+ };
+}
+
+const Snapshot* WritePreparedTxnDB::GetSnapshot() {
+ const bool kForWWConflictCheck = true;
+ return GetSnapshotInternal(!kForWWConflictCheck);
+}
+
+SnapshotImpl* WritePreparedTxnDB::GetSnapshotInternal(
+ bool for_ww_conflict_check) {
+ // Note: for this optimization setting the last sequence number and obtaining
+ // the smallest uncommitted seq should be done atomically. However to avoid
+ // the mutex overhead, we call SmallestUnCommittedSeq BEFORE taking the
+ // snapshot. Since we always updated the list of unprepared seq (via
+ // AddPrepared) AFTER the last sequence is updated, this guarantees that the
+ // smallest uncommitted seq that we pair with the snapshot is smaller or equal
+ // the value that would be obtained otherwise atomically. That is ok since
+ // this optimization works as long as min_uncommitted is less than or equal
+ // than the smallest uncommitted seq when the snapshot was taken.
+ auto min_uncommitted = WritePreparedTxnDB::SmallestUnCommittedSeq();
+ SnapshotImpl* snap_impl = db_impl_->GetSnapshotImpl(for_ww_conflict_check);
+ TEST_SYNC_POINT("WritePreparedTxnDB::GetSnapshotInternal:first");
+ assert(snap_impl);
+ SequenceNumber snap_seq = snap_impl->GetSequenceNumber();
+ // Note: Check against future_max_evicted_seq_ (in contrast with
+ // max_evicted_seq_) in case there is a concurrent AdvanceMaxEvictedSeq.
+ if (UNLIKELY(snap_seq != 0 && snap_seq <= future_max_evicted_seq_)) {
+ // There is a very rare case in which the commit entry evicts another commit
+ // entry that is not published yet thus advancing max evicted seq beyond the
+ // last published seq. This case is not likely in real-world setup so we
+ // handle it with a few retries.
+ size_t retry = 0;
+ SequenceNumber max;
+ while ((max = future_max_evicted_seq_.load()) != 0 &&
+ snap_impl->GetSequenceNumber() <= max && retry < 100) {
+ ROCKS_LOG_WARN(info_log_,
+ "GetSnapshot snap: %" PRIu64 " max: %" PRIu64
+ " retry %" ROCKSDB_PRIszt,
+ snap_impl->GetSequenceNumber(), max, retry);
+ ReleaseSnapshot(snap_impl);
+ // Wait for last visible seq to catch up with max, and also go beyond it
+ // by one.
+ AdvanceSeqByOne();
+ snap_impl = db_impl_->GetSnapshotImpl(for_ww_conflict_check);
+ assert(snap_impl);
+ retry++;
+ }
+ assert(snap_impl->GetSequenceNumber() > max);
+ if (snap_impl->GetSequenceNumber() <= max) {
+ throw std::runtime_error(
+ "Snapshot seq " + ToString(snap_impl->GetSequenceNumber()) +
+ " after " + ToString(retry) +
+ " retries is still less than futre_max_evicted_seq_" + ToString(max));
+ }
+ }
+ EnhanceSnapshot(snap_impl, min_uncommitted);
+ ROCKS_LOG_DETAILS(
+ db_impl_->immutable_db_options().info_log,
+ "GetSnapshot %" PRIu64 " ww:%" PRIi32 " min_uncommitted: %" PRIu64,
+ snap_impl->GetSequenceNumber(), for_ww_conflict_check, min_uncommitted);
+ TEST_SYNC_POINT("WritePreparedTxnDB::GetSnapshotInternal:end");
+ return snap_impl;
+}
+
+void WritePreparedTxnDB::AdvanceSeqByOne() {
+ // Inserting an empty value will i) let the max evicted entry to be
+ // published, i.e., max == last_published, increase the last published to
+ // be one beyond max, i.e., max < last_published.
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ Transaction* txn0 = BeginTransaction(woptions, txn_options, nullptr);
+ std::hash<std::thread::id> hasher;
+ char name[64];
+ snprintf(name, 64, "txn%" ROCKSDB_PRIszt, hasher(std::this_thread::get_id()));
+ assert(strlen(name) < 64 - 1);
+ Status s = txn0->SetName(name);
+ assert(s.ok());
+ if (s.ok()) {
+ // Without prepare it would simply skip the commit
+ s = txn0->Prepare();
+ }
+ assert(s.ok());
+ if (s.ok()) {
+ s = txn0->Commit();
+ }
+ assert(s.ok());
+ delete txn0;
+}
+
+const std::vector<SequenceNumber> WritePreparedTxnDB::GetSnapshotListFromDB(
+ SequenceNumber max) {
+ ROCKS_LOG_DETAILS(info_log_, "GetSnapshotListFromDB with max %" PRIu64, max);
+ InstrumentedMutexLock dblock(db_impl_->mutex());
+ db_impl_->mutex()->AssertHeld();
+ return db_impl_->snapshots().GetAll(nullptr, max);
+}
+
+void WritePreparedTxnDB::ReleaseSnapshotInternal(
+ const SequenceNumber snap_seq) {
+ // TODO(myabandeh): relax should enough since the synchronizatin is already
+ // done by snapshots_mutex_ under which this function is called.
+ if (snap_seq <= max_evicted_seq_.load(std::memory_order_acquire)) {
+ // Then this is a rare case that transaction did not finish before max
+ // advances. It is expected for a few read-only backup snapshots. For such
+ // snapshots we might have kept around a couple of entries in the
+ // old_commit_map_. Check and do garbage collection if that is the case.
+ bool need_gc = false;
+ {
+ WPRecordTick(TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD);
+ ROCKS_LOG_WARN(info_log_, "old_commit_map_mutex_ overhead for %" PRIu64,
+ snap_seq);
+ ReadLock rl(&old_commit_map_mutex_);
+ auto prep_set_entry = old_commit_map_.find(snap_seq);
+ need_gc = prep_set_entry != old_commit_map_.end();
+ }
+ if (need_gc) {
+ WPRecordTick(TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD);
+ ROCKS_LOG_WARN(info_log_, "old_commit_map_mutex_ overhead for %" PRIu64,
+ snap_seq);
+ WriteLock wl(&old_commit_map_mutex_);
+ old_commit_map_.erase(snap_seq);
+ old_commit_map_empty_.store(old_commit_map_.empty(),
+ std::memory_order_release);
+ }
+ }
+}
+
+void WritePreparedTxnDB::CleanupReleasedSnapshots(
+ const std::vector<SequenceNumber>& new_snapshots,
+ const std::vector<SequenceNumber>& old_snapshots) {
+ auto newi = new_snapshots.begin();
+ auto oldi = old_snapshots.begin();
+ for (; newi != new_snapshots.end() && oldi != old_snapshots.end();) {
+ assert(*newi >= *oldi); // cannot have new snapshots with lower seq
+ if (*newi == *oldi) { // still not released
+ auto value = *newi;
+ while (newi != new_snapshots.end() && *newi == value) {
+ newi++;
+ }
+ while (oldi != old_snapshots.end() && *oldi == value) {
+ oldi++;
+ }
+ } else {
+ assert(*newi > *oldi); // *oldi is released
+ ReleaseSnapshotInternal(*oldi);
+ oldi++;
+ }
+ }
+ // Everything remained in old_snapshots is released and must be cleaned up
+ for (; oldi != old_snapshots.end(); oldi++) {
+ ReleaseSnapshotInternal(*oldi);
+ }
+}
+
+void WritePreparedTxnDB::UpdateSnapshots(
+ const std::vector<SequenceNumber>& snapshots,
+ const SequenceNumber& version) {
+ ROCKS_LOG_DETAILS(info_log_, "UpdateSnapshots with version %" PRIu64,
+ version);
+ TEST_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:p:start");
+ TEST_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:s:start");
+#ifndef NDEBUG
+ size_t sync_i = 0;
+#endif
+ ROCKS_LOG_DETAILS(info_log_, "snapshots_mutex_ overhead");
+ WriteLock wl(&snapshots_mutex_);
+ snapshots_version_ = version;
+ // We update the list concurrently with the readers.
+ // Both new and old lists are sorted and the new list is subset of the
+ // previous list plus some new items. Thus if a snapshot repeats in
+ // both new and old lists, it will appear upper in the new list. So if
+ // we simply insert the new snapshots in order, if an overwritten item
+ // is still valid in the new list is either written to the same place in
+ // the array or it is written in a higher palce before it gets
+ // overwritten by another item. This guarantess a reader that reads the
+ // list bottom-up will eventaully see a snapshot that repeats in the
+ // update, either before it gets overwritten by the writer or
+ // afterwards.
+ size_t i = 0;
+ auto it = snapshots.begin();
+ for (; it != snapshots.end() && i < SNAPSHOT_CACHE_SIZE; ++it, ++i) {
+ snapshot_cache_[i].store(*it, std::memory_order_release);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:p:", ++sync_i);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:s:", sync_i);
+ }
+#ifndef NDEBUG
+ // Release the remaining sync points since they are useless given that the
+ // reader would also use lock to access snapshots
+ for (++sync_i; sync_i <= 10; ++sync_i) {
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:p:", sync_i);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:s:", sync_i);
+ }
+#endif
+ snapshots_.clear();
+ for (; it != snapshots.end(); ++it) {
+ // Insert them to a vector that is less efficient to access
+ // concurrently
+ snapshots_.push_back(*it);
+ }
+ // Update the size at the end. Otherwise a parallel reader might read
+ // items that are not set yet.
+ snapshots_total_.store(snapshots.size(), std::memory_order_release);
+
+ // Note: this must be done after the snapshots data structures are updated
+ // with the new list of snapshots.
+ CleanupReleasedSnapshots(snapshots, snapshots_all_);
+ snapshots_all_ = snapshots;
+
+ TEST_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:p:end");
+ TEST_SYNC_POINT("WritePreparedTxnDB::UpdateSnapshots:s:end");
+}
+
+void WritePreparedTxnDB::CheckAgainstSnapshots(const CommitEntry& evicted) {
+ TEST_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:p:start");
+ TEST_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:s:start");
+#ifndef NDEBUG
+ size_t sync_i = 0;
+#endif
+ // First check the snapshot cache that is efficient for concurrent access
+ auto cnt = snapshots_total_.load(std::memory_order_acquire);
+ // The list might get updated concurrently as we are reading from it. The
+ // reader should be able to read all the snapshots that are still valid
+ // after the update. Since the survived snapshots are written in a higher
+ // place before gets overwritten the reader that reads bottom-up will
+ // eventully see it.
+ const bool next_is_larger = true;
+ // We will set to true if the border line snapshot suggests that.
+ bool search_larger_list = false;
+ size_t ip1 = std::min(cnt, SNAPSHOT_CACHE_SIZE);
+ for (; 0 < ip1; ip1--) {
+ SequenceNumber snapshot_seq =
+ snapshot_cache_[ip1 - 1].load(std::memory_order_acquire);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:p:",
+ ++sync_i);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:s:", sync_i);
+ if (ip1 == SNAPSHOT_CACHE_SIZE) { // border line snapshot
+ // snapshot_seq < commit_seq => larger_snapshot_seq <= commit_seq
+ // then later also continue the search to larger snapshots
+ search_larger_list = snapshot_seq < evicted.commit_seq;
+ }
+ if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
+ snapshot_seq, !next_is_larger)) {
+ break;
+ }
+ }
+#ifndef NDEBUG
+ // Release the remaining sync points before accquiring the lock
+ for (++sync_i; sync_i <= 10; ++sync_i) {
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:p:", sync_i);
+ TEST_IDX_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:s:", sync_i);
+ }
+#endif
+ TEST_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:p:end");
+ TEST_SYNC_POINT("WritePreparedTxnDB::CheckAgainstSnapshots:s:end");
+ if (UNLIKELY(SNAPSHOT_CACHE_SIZE < cnt && search_larger_list)) {
+ // Then access the less efficient list of snapshots_
+ WPRecordTick(TXN_SNAPSHOT_MUTEX_OVERHEAD);
+ ROCKS_LOG_WARN(info_log_,
+ "snapshots_mutex_ overhead for <%" PRIu64 ",%" PRIu64
+ "> with %" ROCKSDB_PRIszt " snapshots",
+ evicted.prep_seq, evicted.commit_seq, cnt);
+ ReadLock rl(&snapshots_mutex_);
+ // Items could have moved from the snapshots_ to snapshot_cache_ before
+ // accquiring the lock. To make sure that we do not miss a valid snapshot,
+ // read snapshot_cache_ again while holding the lock.
+ for (size_t i = 0; i < SNAPSHOT_CACHE_SIZE; i++) {
+ SequenceNumber snapshot_seq =
+ snapshot_cache_[i].load(std::memory_order_acquire);
+ if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
+ snapshot_seq, next_is_larger)) {
+ break;
+ }
+ }
+ for (auto snapshot_seq_2 : snapshots_) {
+ if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
+ snapshot_seq_2, next_is_larger)) {
+ break;
+ }
+ }
+ }
+}
+
+bool WritePreparedTxnDB::MaybeUpdateOldCommitMap(
+ const uint64_t& prep_seq, const uint64_t& commit_seq,
+ const uint64_t& snapshot_seq, const bool next_is_larger = true) {
+ // If we do not store an entry in old_commit_map_ we assume it is committed in
+ // all snapshots. If commit_seq <= snapshot_seq, it is considered already in
+ // the snapshot so we need not to keep the entry around for this snapshot.
+ if (commit_seq <= snapshot_seq) {
+ // continue the search if the next snapshot could be smaller than commit_seq
+ return !next_is_larger;
+ }
+ // then snapshot_seq < commit_seq
+ if (prep_seq <= snapshot_seq) { // overlapping range
+ WPRecordTick(TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD);
+ ROCKS_LOG_WARN(info_log_,
+ "old_commit_map_mutex_ overhead for %" PRIu64
+ " commit entry: <%" PRIu64 ",%" PRIu64 ">",
+ snapshot_seq, prep_seq, commit_seq);
+ WriteLock wl(&old_commit_map_mutex_);
+ old_commit_map_empty_.store(false, std::memory_order_release);
+ auto& vec = old_commit_map_[snapshot_seq];
+ vec.insert(std::upper_bound(vec.begin(), vec.end(), prep_seq), prep_seq);
+ // We need to store it once for each overlapping snapshot. Returning true to
+ // continue the search if there is more overlapping snapshot.
+ return true;
+ }
+ // continue the search if the next snapshot could be larger than prep_seq
+ return next_is_larger;
+}
+
+WritePreparedTxnDB::~WritePreparedTxnDB() {
+ // At this point there could be running compaction/flush holding a
+ // SnapshotChecker, which holds a pointer back to WritePreparedTxnDB.
+ // Make sure those jobs finished before destructing WritePreparedTxnDB.
+ if (!db_impl_->shutting_down_) {
+ db_impl_->CancelAllBackgroundWork(true /*wait*/);
+ }
+}
+
+void SubBatchCounter::InitWithComp(const uint32_t cf) {
+ auto cmp = comparators_[cf];
+ keys_[cf] = CFKeys(SetComparator(cmp));
+}
+
+void SubBatchCounter::AddKey(const uint32_t cf, const Slice& key) {
+ CFKeys& cf_keys = keys_[cf];
+ if (cf_keys.size() == 0) { // just inserted
+ InitWithComp(cf);
+ }
+ auto it = cf_keys.insert(key);
+ if (it.second == false) { // second is false if a element already existed.
+ batches_++;
+ keys_.clear();
+ InitWithComp(cf);
+ keys_[cf].insert(key);
+ }
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.h b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.h
new file mode 100644
index 00000000..964b7268
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_prepared_txn_db.h
@@ -0,0 +1,1111 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include <cinttypes>
+#include <mutex>
+#include <queue>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "db/db_iter.h"
+#include "db/pre_release_callback.h"
+#include "db/read_callback.h"
+#include "db/snapshot_checker.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "util/cast_util.h"
+#include "util/set_comparator.h"
+#include "util/string_util.h"
+#include "utilities/transactions/pessimistic_transaction.h"
+#include "utilities/transactions/pessimistic_transaction_db.h"
+#include "utilities/transactions/transaction_lock_mgr.h"
+#include "utilities/transactions/write_prepared_txn.h"
+
+namespace ROCKSDB_NAMESPACE {
+enum SnapshotBackup : bool { kUnbackedByDBSnapshot, kBackedByDBSnapshot };
+
+// A PessimisticTransactionDB that writes data to DB after prepare phase of 2PC.
+// In this way some data in the DB might not be committed. The DB provides
+// mechanisms to tell such data apart from committed data.
+class WritePreparedTxnDB : public PessimisticTransactionDB {
+ public:
+ explicit WritePreparedTxnDB(DB* db,
+ const TransactionDBOptions& txn_db_options)
+ : PessimisticTransactionDB(db, txn_db_options),
+ SNAPSHOT_CACHE_BITS(txn_db_options.wp_snapshot_cache_bits),
+ SNAPSHOT_CACHE_SIZE(static_cast<size_t>(1ull << SNAPSHOT_CACHE_BITS)),
+ COMMIT_CACHE_BITS(txn_db_options.wp_commit_cache_bits),
+ COMMIT_CACHE_SIZE(static_cast<size_t>(1ull << COMMIT_CACHE_BITS)),
+ FORMAT(COMMIT_CACHE_BITS) {
+ Init(txn_db_options);
+ }
+
+ explicit WritePreparedTxnDB(StackableDB* db,
+ const TransactionDBOptions& txn_db_options)
+ : PessimisticTransactionDB(db, txn_db_options),
+ SNAPSHOT_CACHE_BITS(txn_db_options.wp_snapshot_cache_bits),
+ SNAPSHOT_CACHE_SIZE(static_cast<size_t>(1ull << SNAPSHOT_CACHE_BITS)),
+ COMMIT_CACHE_BITS(txn_db_options.wp_commit_cache_bits),
+ COMMIT_CACHE_SIZE(static_cast<size_t>(1ull << COMMIT_CACHE_BITS)),
+ FORMAT(COMMIT_CACHE_BITS) {
+ Init(txn_db_options);
+ }
+
+ virtual ~WritePreparedTxnDB();
+
+ virtual Status Initialize(
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles) override;
+
+ Transaction* BeginTransaction(const WriteOptions& write_options,
+ const TransactionOptions& txn_options,
+ Transaction* old_txn) override;
+
+ using TransactionDB::Write;
+ Status Write(const WriteOptions& opts, WriteBatch* updates) override;
+
+ // Optimized version of ::Write that receives more optimization request such
+ // as skip_concurrency_control.
+ using PessimisticTransactionDB::Write;
+ Status Write(const WriteOptions& opts, const TransactionDBWriteOptimizations&,
+ WriteBatch* updates) override;
+
+ // Write the batch to the underlying DB and mark it as committed. Could be
+ // used by both directly from TxnDB or through a transaction.
+ Status WriteInternal(const WriteOptions& write_options, WriteBatch* batch,
+ size_t batch_cnt, WritePreparedTxn* txn);
+
+ using DB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ using DB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ using DB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) override;
+
+ using DB::NewIterators;
+ virtual Status NewIterators(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_families,
+ std::vector<Iterator*>* iterators) override;
+
+ // Check whether the transaction that wrote the value with sequence number seq
+ // is visible to the snapshot with sequence number snapshot_seq.
+ // Returns true if commit_seq <= snapshot_seq
+ // If the snapshot_seq is already released and snapshot_seq <= max, sets
+ // *snap_released to true and returns true as well.
+ inline bool IsInSnapshot(uint64_t prep_seq, uint64_t snapshot_seq,
+ uint64_t min_uncommitted = kMinUnCommittedSeq,
+ bool* snap_released = nullptr) const {
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " min_uncommitted %" PRIu64,
+ prep_seq, snapshot_seq, min_uncommitted);
+ assert(min_uncommitted >= kMinUnCommittedSeq);
+ // Caller is responsible to initialize snap_released.
+ assert(snap_released == nullptr || *snap_released == false);
+ // Here we try to infer the return value without looking into prepare list.
+ // This would help avoiding synchronization over a shared map.
+ // TODO(myabandeh): optimize this. This sequence of checks must be correct
+ // but not necessary efficient
+ if (prep_seq == 0) {
+ // Compaction will output keys to bottom-level with sequence number 0 if
+ // it is visible to the earliest snapshot.
+ ROCKS_LOG_DETAILS(
+ info_log_, "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, 1);
+ return true;
+ }
+ if (snapshot_seq < prep_seq) {
+ // snapshot_seq < prep_seq <= commit_seq => snapshot_seq < commit_seq
+ ROCKS_LOG_DETAILS(
+ info_log_, "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, 0);
+ return false;
+ }
+ if (prep_seq < min_uncommitted) {
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32
+ " because of min_uncommitted %" PRIu64,
+ prep_seq, snapshot_seq, 1, min_uncommitted);
+ return true;
+ }
+ // Commit of delayed prepared has two non-atomic steps: add to commit cache,
+ // remove from delayed prepared. Our reads from these two is also
+ // non-atomic. By looking into commit cache first thus we might not find the
+ // prep_seq neither in commit cache not in delayed_prepared_. To fix that i)
+ // we check if there was any delayed prepared BEFORE looking into commit
+ // cache, ii) if there was, we complete the search steps to be these: i)
+ // commit cache, ii) delayed prepared, commit cache again. In this way if
+ // the first query to commit cache missed the commit, the 2nd will catch it.
+ bool was_empty;
+ SequenceNumber max_evicted_seq_lb, max_evicted_seq_ub;
+ CommitEntry64b dont_care;
+ auto indexed_seq = prep_seq % COMMIT_CACHE_SIZE;
+ size_t repeats = 0;
+ do {
+ repeats++;
+ assert(repeats < 100);
+ if (UNLIKELY(repeats >= 100)) {
+ throw std::runtime_error(
+ "The read was intrupted 100 times by update to max_evicted_seq_. "
+ "This is unexpected in all setups");
+ }
+ max_evicted_seq_lb = max_evicted_seq_.load(std::memory_order_acquire);
+ TEST_SYNC_POINT(
+ "WritePreparedTxnDB::IsInSnapshot:max_evicted_seq_:pause");
+ TEST_SYNC_POINT(
+ "WritePreparedTxnDB::IsInSnapshot:max_evicted_seq_:resume");
+ was_empty = delayed_prepared_empty_.load(std::memory_order_acquire);
+ TEST_SYNC_POINT(
+ "WritePreparedTxnDB::IsInSnapshot:delayed_prepared_empty_:pause");
+ TEST_SYNC_POINT(
+ "WritePreparedTxnDB::IsInSnapshot:delayed_prepared_empty_:resume");
+ CommitEntry cached;
+ bool exist = GetCommitEntry(indexed_seq, &dont_care, &cached);
+ TEST_SYNC_POINT("WritePreparedTxnDB::IsInSnapshot:GetCommitEntry:pause");
+ TEST_SYNC_POINT("WritePreparedTxnDB::IsInSnapshot:GetCommitEntry:resume");
+ if (exist && prep_seq == cached.prep_seq) {
+ // It is committed and also not evicted from commit cache
+ ROCKS_LOG_DETAILS(
+ info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, cached.commit_seq <= snapshot_seq);
+ return cached.commit_seq <= snapshot_seq;
+ }
+ // else it could be committed but not inserted in the map which could
+ // happen after recovery, or it could be committed and evicted by another
+ // commit, or never committed.
+
+ // At this point we dont know if it was committed or it is still prepared
+ max_evicted_seq_ub = max_evicted_seq_.load(std::memory_order_acquire);
+ if (UNLIKELY(max_evicted_seq_lb != max_evicted_seq_ub)) {
+ continue;
+ }
+ // Note: max_evicted_seq_ when we did GetCommitEntry <= max_evicted_seq_ub
+ if (max_evicted_seq_ub < prep_seq) {
+ // Not evicted from cache and also not present, so must be still
+ // prepared
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32,
+ prep_seq, snapshot_seq, 0);
+ return false;
+ }
+ TEST_SYNC_POINT("WritePreparedTxnDB::IsInSnapshot:prepared_mutex_:pause");
+ TEST_SYNC_POINT(
+ "WritePreparedTxnDB::IsInSnapshot:prepared_mutex_:resume");
+ if (!was_empty) {
+ // We should not normally reach here
+ WPRecordTick(TXN_PREPARE_MUTEX_OVERHEAD);
+ ReadLock rl(&prepared_mutex_);
+ ROCKS_LOG_WARN(
+ info_log_, "prepared_mutex_ overhead %" PRIu64 " for %" PRIu64,
+ static_cast<uint64_t>(delayed_prepared_.size()), prep_seq);
+ if (delayed_prepared_.find(prep_seq) != delayed_prepared_.end()) {
+ // This is the order: 1) delayed_prepared_commits_ update, 2) publish
+ // 3) delayed_prepared_ clean up. So check if it is the case of a late
+ // clenaup.
+ auto it = delayed_prepared_commits_.find(prep_seq);
+ if (it == delayed_prepared_commits_.end()) {
+ // Then it is not committed yet
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32,
+ prep_seq, snapshot_seq, 0);
+ return false;
+ } else {
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " commit: %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, it->second,
+ snapshot_seq <= it->second);
+ return it->second <= snapshot_seq;
+ }
+ } else {
+ // 2nd query to commit cache. Refer to was_empty comment above.
+ exist = GetCommitEntry(indexed_seq, &dont_care, &cached);
+ if (exist && prep_seq == cached.prep_seq) {
+ ROCKS_LOG_DETAILS(
+ info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, cached.commit_seq <= snapshot_seq);
+ return cached.commit_seq <= snapshot_seq;
+ }
+ max_evicted_seq_ub = max_evicted_seq_.load(std::memory_order_acquire);
+ }
+ }
+ } while (UNLIKELY(max_evicted_seq_lb != max_evicted_seq_ub));
+ // When advancing max_evicted_seq_, we move older entires from prepared to
+ // delayed_prepared_. Also we move evicted entries from commit cache to
+ // old_commit_map_ if it overlaps with any snapshot. Since prep_seq <=
+ // max_evicted_seq_, we have three cases: i) in delayed_prepared_, ii) in
+ // old_commit_map_, iii) committed with no conflict with any snapshot. Case
+ // (i) delayed_prepared_ is checked above
+ if (max_evicted_seq_ub < snapshot_seq) { // then (ii) cannot be the case
+ // only (iii) is the case: committed
+ // commit_seq <= max_evicted_seq_ < snapshot_seq => commit_seq <
+ // snapshot_seq
+ ROCKS_LOG_DETAILS(
+ info_log_, "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, 1);
+ return true;
+ }
+ // else (ii) might be the case: check the commit data saved for this
+ // snapshot. If there was no overlapping commit entry, then it is committed
+ // with a commit_seq lower than any live snapshot, including snapshot_seq.
+ if (old_commit_map_empty_.load(std::memory_order_acquire)) {
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32 " released=1",
+ prep_seq, snapshot_seq, 0);
+ assert(snap_released);
+ // This snapshot is not valid anymore. We cannot tell if prep_seq is
+ // committed before or after the snapshot. Return true but also set
+ // snap_released to true.
+ *snap_released = true;
+ return true;
+ }
+ {
+ // We should not normally reach here unless sapshot_seq is old. This is a
+ // rare case and it is ok to pay the cost of mutex ReadLock for such old,
+ // reading transactions.
+ WPRecordTick(TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD);
+ ReadLock rl(&old_commit_map_mutex_);
+ auto prep_set_entry = old_commit_map_.find(snapshot_seq);
+ bool found = prep_set_entry != old_commit_map_.end();
+ if (found) {
+ auto& vec = prep_set_entry->second;
+ found = std::binary_search(vec.begin(), vec.end(), prep_seq);
+ } else {
+ // coming from compaction
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32 " released=1",
+ prep_seq, snapshot_seq, 0);
+ // This snapshot is not valid anymore. We cannot tell if prep_seq is
+ // committed before or after the snapshot. Return true but also set
+ // snap_released to true.
+ assert(snap_released);
+ *snap_released = true;
+ return true;
+ }
+
+ if (!found) {
+ ROCKS_LOG_DETAILS(info_log_,
+ "IsInSnapshot %" PRIu64 " in %" PRIu64
+ " returns %" PRId32,
+ prep_seq, snapshot_seq, 1);
+ return true;
+ }
+ }
+ // (ii) it the case: it is committed but after the snapshot_seq
+ ROCKS_LOG_DETAILS(
+ info_log_, "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
+ prep_seq, snapshot_seq, 0);
+ return false;
+ }
+
+ // Add the transaction with prepare sequence seq to the prepared list.
+ // Note: must be called serially with increasing seq on each call.
+ // locked is true if prepared_mutex_ is already locked.
+ void AddPrepared(uint64_t seq, bool locked = false);
+ // Check if any of the prepared txns are less than new max_evicted_seq_. Must
+ // be called with prepared_mutex_ write locked.
+ void CheckPreparedAgainstMax(SequenceNumber new_max, bool locked);
+ // Remove the transaction with prepare sequence seq from the prepared list
+ void RemovePrepared(const uint64_t seq, const size_t batch_cnt = 1);
+ // Add the transaction with prepare sequence prepare_seq and commit sequence
+ // commit_seq to the commit map. loop_cnt is to detect infinite loops.
+ // Note: must be called serially.
+ void AddCommitted(uint64_t prepare_seq, uint64_t commit_seq,
+ uint8_t loop_cnt = 0);
+
+ struct CommitEntry {
+ uint64_t prep_seq;
+ uint64_t commit_seq;
+ CommitEntry() : prep_seq(0), commit_seq(0) {}
+ CommitEntry(uint64_t ps, uint64_t cs) : prep_seq(ps), commit_seq(cs) {}
+ bool operator==(const CommitEntry& rhs) const {
+ return prep_seq == rhs.prep_seq && commit_seq == rhs.commit_seq;
+ }
+ };
+
+ struct CommitEntry64bFormat {
+ explicit CommitEntry64bFormat(size_t index_bits)
+ : INDEX_BITS(index_bits),
+ PREP_BITS(static_cast<size_t>(64 - PAD_BITS - INDEX_BITS)),
+ COMMIT_BITS(static_cast<size_t>(64 - PREP_BITS)),
+ COMMIT_FILTER(static_cast<uint64_t>((1ull << COMMIT_BITS) - 1)),
+ DELTA_UPPERBOUND(static_cast<uint64_t>((1ull << COMMIT_BITS))) {}
+ // Number of higher bits of a sequence number that is not used. They are
+ // used to encode the value type, ...
+ const size_t PAD_BITS = static_cast<size_t>(8);
+ // Number of lower bits from prepare seq that can be skipped as they are
+ // implied by the index of the entry in the array
+ const size_t INDEX_BITS;
+ // Number of bits we use to encode the prepare seq
+ const size_t PREP_BITS;
+ // Number of bits we use to encode the commit seq.
+ const size_t COMMIT_BITS;
+ // Filter to encode/decode commit seq
+ const uint64_t COMMIT_FILTER;
+ // The value of commit_seq - prepare_seq + 1 must be less than this bound
+ const uint64_t DELTA_UPPERBOUND;
+ };
+
+ // Prepare Seq (64 bits) = PAD ... PAD PREP PREP ... PREP INDEX INDEX ...
+ // INDEX Delta Seq (64 bits) = 0 0 0 0 0 0 0 0 0 0 0 0 DELTA DELTA ...
+ // DELTA DELTA Encoded Value = PREP PREP .... PREP PREP DELTA DELTA
+ // ... DELTA DELTA PAD: first bits of a seq that is reserved for tagging and
+ // hence ignored PREP/INDEX: the used bits in a prepare seq number INDEX: the
+ // bits that do not have to be encoded (will be provided externally) DELTA:
+ // prep seq - commit seq + 1 Number of DELTA bits should be equal to number of
+ // index bits + PADs
+ struct CommitEntry64b {
+ constexpr CommitEntry64b() noexcept : rep_(0) {}
+
+ CommitEntry64b(const CommitEntry& entry, const CommitEntry64bFormat& format)
+ : CommitEntry64b(entry.prep_seq, entry.commit_seq, format) {}
+
+ CommitEntry64b(const uint64_t ps, const uint64_t cs,
+ const CommitEntry64bFormat& format) {
+ assert(ps < static_cast<uint64_t>(
+ (1ull << (format.PREP_BITS + format.INDEX_BITS))));
+ assert(ps <= cs);
+ uint64_t delta = cs - ps + 1; // make initialized delta always >= 1
+ // zero is reserved for uninitialized entries
+ assert(0 < delta);
+ assert(delta < format.DELTA_UPPERBOUND);
+ if (delta >= format.DELTA_UPPERBOUND) {
+ throw std::runtime_error(
+ "commit_seq >> prepare_seq. The allowed distance is " +
+ ToString(format.DELTA_UPPERBOUND) + " commit_seq is " +
+ ToString(cs) + " prepare_seq is " + ToString(ps));
+ }
+ rep_ = (ps << format.PAD_BITS) & ~format.COMMIT_FILTER;
+ rep_ = rep_ | delta;
+ }
+
+ // Return false if the entry is empty
+ bool Parse(const uint64_t indexed_seq, CommitEntry* entry,
+ const CommitEntry64bFormat& format) {
+ uint64_t delta = rep_ & format.COMMIT_FILTER;
+ // zero is reserved for uninitialized entries
+ assert(delta < static_cast<uint64_t>((1ull << format.COMMIT_BITS)));
+ if (delta == 0) {
+ return false; // initialized entry would have non-zero delta
+ }
+
+ assert(indexed_seq < static_cast<uint64_t>((1ull << format.INDEX_BITS)));
+ uint64_t prep_up = rep_ & ~format.COMMIT_FILTER;
+ prep_up >>= format.PAD_BITS;
+ const uint64_t& prep_low = indexed_seq;
+ entry->prep_seq = prep_up | prep_low;
+
+ entry->commit_seq = entry->prep_seq + delta - 1;
+ return true;
+ }
+
+ private:
+ uint64_t rep_;
+ };
+
+ // Struct to hold ownership of snapshot and read callback for cleanup.
+ struct IteratorState;
+
+ std::shared_ptr<std::map<uint32_t, const Comparator*>> GetCFComparatorMap() {
+ return cf_map_;
+ }
+ std::shared_ptr<std::map<uint32_t, ColumnFamilyHandle*>> GetCFHandleMap() {
+ return handle_map_;
+ }
+ void UpdateCFComparatorMap(
+ const std::vector<ColumnFamilyHandle*>& handles) override;
+ void UpdateCFComparatorMap(ColumnFamilyHandle* handle) override;
+
+ virtual const Snapshot* GetSnapshot() override;
+ SnapshotImpl* GetSnapshotInternal(bool for_ww_conflict_check);
+
+ protected:
+ virtual Status VerifyCFOptions(
+ const ColumnFamilyOptions& cf_options) override;
+ // Assign the min and max sequence numbers for reading from the db. A seq >
+ // max is not valid, and a seq < min is valid, and a min <= seq < max requires
+ // further checking. Normally max is defined by the snapshot and min is by
+ // minimum uncommitted seq.
+ inline SnapshotBackup AssignMinMaxSeqs(const Snapshot* snapshot,
+ SequenceNumber* min,
+ SequenceNumber* max);
+ // Validate is a snapshot sequence number is still valid based on the latest
+ // db status. backed_by_snapshot specifies if the number is baked by an actual
+ // snapshot object. order specified the memory order with which we load the
+ // atomic variables: relax is enough for the default since we care about last
+ // value seen by same thread.
+ inline bool ValidateSnapshot(
+ const SequenceNumber snap_seq, const SnapshotBackup backed_by_snapshot,
+ std::memory_order order = std::memory_order_relaxed);
+ // Get a dummy snapshot that refers to kMaxSequenceNumber
+ Snapshot* GetMaxSnapshot() { return &dummy_max_snapshot_; }
+
+ private:
+ friend class AddPreparedCallback;
+ friend class PreparedHeap_BasicsTest_Test;
+ friend class PreparedHeap_Concurrent_Test;
+ friend class PreparedHeap_EmptyAtTheEnd_Test;
+ friend class SnapshotConcurrentAccessTest_SnapshotConcurrentAccess_Test;
+ friend class WritePreparedCommitEntryPreReleaseCallback;
+ friend class WritePreparedTransactionTestBase;
+ friend class WritePreparedTxn;
+ friend class WritePreparedTxnDBMock;
+ friend class WritePreparedTransactionTest_AddPreparedBeforeMax_Test;
+ friend class WritePreparedTransactionTest_AdvanceMaxEvictedSeqBasic_Test;
+ friend class
+ WritePreparedTransactionTest_AdvanceMaxEvictedSeqWithDuplicates_Test;
+ friend class WritePreparedTransactionTest_AdvanceSeqByOne_Test;
+ friend class WritePreparedTransactionTest_BasicRecovery_Test;
+ friend class WritePreparedTransactionTest_CheckAgainstSnapshots_Test;
+ friend class WritePreparedTransactionTest_CleanupSnapshotEqualToMax_Test;
+ friend class WritePreparedTransactionTest_ConflictDetectionAfterRecovery_Test;
+ friend class WritePreparedTransactionTest_CommitMap_Test;
+ friend class WritePreparedTransactionTest_DoubleSnapshot_Test;
+ friend class WritePreparedTransactionTest_IsInSnapshotEmptyMap_Test;
+ friend class WritePreparedTransactionTest_IsInSnapshotReleased_Test;
+ friend class WritePreparedTransactionTest_IsInSnapshot_Test;
+ friend class WritePreparedTransactionTest_NewSnapshotLargerThanMax_Test;
+ friend class WritePreparedTransactionTest_MaxCatchupWithNewSnapshot_Test;
+ friend class WritePreparedTransactionTest_MaxCatchupWithUnbackedSnapshot_Test;
+ friend class
+ WritePreparedTransactionTest_NonAtomicCommitOfDelayedPrepared_Test;
+ friend class
+ WritePreparedTransactionTest_NonAtomicUpdateOfDelayedPrepared_Test;
+ friend class WritePreparedTransactionTest_NonAtomicUpdateOfMaxEvictedSeq_Test;
+ friend class WritePreparedTransactionTest_OldCommitMapGC_Test;
+ friend class WritePreparedTransactionTest_Rollback_Test;
+ friend class WritePreparedTransactionTest_SmallestUnCommittedSeq_Test;
+ friend class WriteUnpreparedTxn;
+ friend class WriteUnpreparedTxnDB;
+ friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
+
+ void Init(const TransactionDBOptions& /* unused */);
+
+ void WPRecordTick(uint32_t ticker_type) const {
+ RecordTick(db_impl_->immutable_db_options_.statistics.get(), ticker_type);
+ }
+
+ // A heap with the amortized O(1) complexity for erase. It uses one extra heap
+ // to keep track of erased entries that are not yet on top of the main heap.
+ class PreparedHeap {
+ // The mutex is required for push and pop from PreparedHeap. ::erase will
+ // use external synchronization via prepared_mutex_.
+ port::Mutex push_pop_mutex_;
+ std::deque<uint64_t> heap_;
+ std::priority_queue<uint64_t, std::vector<uint64_t>, std::greater<uint64_t>>
+ erased_heap_;
+ std::atomic<uint64_t> heap_top_ = {kMaxSequenceNumber};
+ // True when testing crash recovery
+ bool TEST_CRASH_ = false;
+ friend class WritePreparedTxnDB;
+
+ public:
+ ~PreparedHeap() {
+ if (!TEST_CRASH_) {
+ assert(heap_.empty());
+ assert(erased_heap_.empty());
+ }
+ }
+ port::Mutex* push_pop_mutex() { return &push_pop_mutex_; }
+
+ inline bool empty() { return top() == kMaxSequenceNumber; }
+ // Returns kMaxSequenceNumber if empty() and the smallest otherwise.
+ inline uint64_t top() { return heap_top_.load(std::memory_order_acquire); }
+ inline void push(uint64_t v) {
+ push_pop_mutex_.AssertHeld();
+ if (heap_.empty()) {
+ heap_top_.store(v, std::memory_order_release);
+ } else {
+ assert(heap_top_.load() < v);
+ }
+ heap_.push_back(v);
+ }
+ void pop(bool locked = false) {
+ if (!locked) {
+ push_pop_mutex()->Lock();
+ }
+ push_pop_mutex_.AssertHeld();
+ heap_.pop_front();
+ while (!heap_.empty() && !erased_heap_.empty() &&
+ // heap_.top() > erased_heap_.top() could happen if we have erased
+ // a non-existent entry. Ideally the user should not do that but we
+ // should be resilient against it.
+ heap_.front() >= erased_heap_.top()) {
+ if (heap_.front() == erased_heap_.top()) {
+ heap_.pop_front();
+ }
+ uint64_t erased __attribute__((__unused__));
+ erased = erased_heap_.top();
+ erased_heap_.pop();
+ // No duplicate prepare sequence numbers
+ assert(erased_heap_.empty() || erased_heap_.top() != erased);
+ }
+ while (heap_.empty() && !erased_heap_.empty()) {
+ erased_heap_.pop();
+ }
+ heap_top_.store(!heap_.empty() ? heap_.front() : kMaxSequenceNumber,
+ std::memory_order_release);
+ if (!locked) {
+ push_pop_mutex()->Unlock();
+ }
+ }
+ // Concurrrent calls needs external synchronization. It is safe to be called
+ // concurrent to push and pop though.
+ void erase(uint64_t seq) {
+ if (!empty()) {
+ auto top_seq = top();
+ if (seq < top_seq) {
+ // Already popped, ignore it.
+ } else if (top_seq == seq) {
+ pop();
+#ifndef NDEBUG
+ MutexLock ml(push_pop_mutex());
+ assert(heap_.empty() || heap_.front() != seq);
+#endif
+ } else { // top() > seq
+ // Down the heap, remember to pop it later
+ erased_heap_.push(seq);
+ }
+ }
+ }
+ };
+
+ void TEST_Crash() override { prepared_txns_.TEST_CRASH_ = true; }
+
+ // Get the commit entry with index indexed_seq from the commit table. It
+ // returns true if such entry exists.
+ bool GetCommitEntry(const uint64_t indexed_seq, CommitEntry64b* entry_64b,
+ CommitEntry* entry) const;
+
+ // Rewrite the entry with the index indexed_seq in the commit table with the
+ // commit entry <prep_seq, commit_seq>. If the rewrite results into eviction,
+ // sets the evicted_entry and returns true.
+ bool AddCommitEntry(const uint64_t indexed_seq, const CommitEntry& new_entry,
+ CommitEntry* evicted_entry);
+
+ // Rewrite the entry with the index indexed_seq in the commit table with the
+ // commit entry new_entry only if the existing entry matches the
+ // expected_entry. Returns false otherwise.
+ bool ExchangeCommitEntry(const uint64_t indexed_seq,
+ CommitEntry64b& expected_entry,
+ const CommitEntry& new_entry);
+
+ // Increase max_evicted_seq_ from the previous value prev_max to the new
+ // value. This also involves taking care of prepared txns that are not
+ // committed before new_max, as well as updating the list of live snapshots at
+ // the time of updating the max. Thread-safety: this function can be called
+ // concurrently. The concurrent invocations of this function is equivalent to
+ // a serial invocation in which the last invocation is the one with the
+ // largest new_max value.
+ void AdvanceMaxEvictedSeq(const SequenceNumber& prev_max,
+ const SequenceNumber& new_max);
+
+ inline SequenceNumber SmallestUnCommittedSeq() {
+ // Note: We have two lists to look into, but for performance reasons they
+ // are not read atomically. Since CheckPreparedAgainstMax copies the entry
+ // to delayed_prepared_ before removing it from prepared_txns_, to ensure
+ // that a prepared entry will not go unmissed, we look into them in opposite
+ // order: first read prepared_txns_ and then delayed_prepared_.
+
+ // This must be called before calling ::top. This is because the concurrent
+ // thread would call ::RemovePrepared before updating
+ // GetLatestSequenceNumber(). Reading then in opposite order here guarantees
+ // that the ::top that we read would be lower the ::top if we had otherwise
+ // update/read them atomically.
+ auto next_prepare = db_impl_->GetLatestSequenceNumber() + 1;
+ auto min_prepare = prepared_txns_.top();
+ // Since we update the prepare_heap always from the main write queue via
+ // PreReleaseCallback, the prepared_txns_.top() indicates the smallest
+ // prepared data in 2pc transactions. For non-2pc transactions that are
+ // written in two steps, we also update prepared_txns_ at the first step
+ // (via the same mechanism) so that their uncommitted data is reflected in
+ // SmallestUnCommittedSeq.
+ if (!delayed_prepared_empty_.load()) {
+ ReadLock rl(&prepared_mutex_);
+ if (!delayed_prepared_.empty()) {
+ return *delayed_prepared_.begin();
+ }
+ }
+ bool empty = min_prepare == kMaxSequenceNumber;
+ if (empty) {
+ // Since GetLatestSequenceNumber is updated
+ // after prepared_txns_ are, the value of GetLatestSequenceNumber would
+ // reflect any uncommitted data that is not added to prepared_txns_ yet.
+ // Otherwise, if there is no concurrent txn, this value simply reflects
+ // that latest value in the memtable.
+ return next_prepare;
+ } else {
+ return std::min(min_prepare, next_prepare);
+ }
+ }
+
+ // Enhance the snapshot object by recording in it the smallest uncommitted seq
+ inline void EnhanceSnapshot(SnapshotImpl* snapshot,
+ SequenceNumber min_uncommitted) {
+ assert(snapshot);
+ assert(min_uncommitted <= snapshot->number_ + 1);
+ snapshot->min_uncommitted_ = min_uncommitted;
+ }
+
+ virtual const std::vector<SequenceNumber> GetSnapshotListFromDB(
+ SequenceNumber max);
+
+ // Will be called by the public ReleaseSnapshot method. Does the maintenance
+ // internal to WritePreparedTxnDB
+ void ReleaseSnapshotInternal(const SequenceNumber snap_seq);
+
+ // Update the list of snapshots corresponding to the soon-to-be-updated
+ // max_evicted_seq_. Thread-safety: this function can be called concurrently.
+ // The concurrent invocations of this function is equivalent to a serial
+ // invocation in which the last invocation is the one with the largest
+ // version value.
+ void UpdateSnapshots(const std::vector<SequenceNumber>& snapshots,
+ const SequenceNumber& version);
+ // Check the new list of new snapshots against the old one to see if any of
+ // the snapshots are released and to do the cleanup for the released snapshot.
+ void CleanupReleasedSnapshots(
+ const std::vector<SequenceNumber>& new_snapshots,
+ const std::vector<SequenceNumber>& old_snapshots);
+
+ // Check an evicted entry against live snapshots to see if it should be kept
+ // around or it can be safely discarded (and hence assume committed for all
+ // snapshots). Thread-safety: this function can be called concurrently. If it
+ // is called concurrently with multiple UpdateSnapshots, the result is the
+ // same as checking the intersection of the snapshot list before updates with
+ // the snapshot list of all the concurrent updates.
+ void CheckAgainstSnapshots(const CommitEntry& evicted);
+
+ // Add a new entry to old_commit_map_ if prep_seq <= snapshot_seq <
+ // commit_seq. Return false if checking the next snapshot(s) is not needed.
+ // This is the case if none of the next snapshots could satisfy the condition.
+ // next_is_larger: the next snapshot will be a larger value
+ bool MaybeUpdateOldCommitMap(const uint64_t& prep_seq,
+ const uint64_t& commit_seq,
+ const uint64_t& snapshot_seq,
+ const bool next_is_larger);
+
+ // A trick to increase the last visible sequence number by one and also wait
+ // for the in-flight commits to be visible.
+ void AdvanceSeqByOne();
+
+ // The list of live snapshots at the last time that max_evicted_seq_ advanced.
+ // The list stored into two data structures: in snapshot_cache_ that is
+ // efficient for concurrent reads, and in snapshots_ if the data does not fit
+ // into snapshot_cache_. The total number of snapshots in the two lists
+ std::atomic<size_t> snapshots_total_ = {};
+ // The list sorted in ascending order. Thread-safety for writes is provided
+ // with snapshots_mutex_ and concurrent reads are safe due to std::atomic for
+ // each entry. In x86_64 architecture such reads are compiled to simple read
+ // instructions.
+ const size_t SNAPSHOT_CACHE_BITS;
+ const size_t SNAPSHOT_CACHE_SIZE;
+ std::unique_ptr<std::atomic<SequenceNumber>[]> snapshot_cache_;
+ // 2nd list for storing snapshots. The list sorted in ascending order.
+ // Thread-safety is provided with snapshots_mutex_.
+ std::vector<SequenceNumber> snapshots_;
+ // The list of all snapshots: snapshots_ + snapshot_cache_. This list although
+ // redundant but simplifies CleanupOldSnapshots implementation.
+ // Thread-safety is provided with snapshots_mutex_.
+ std::vector<SequenceNumber> snapshots_all_;
+ // The version of the latest list of snapshots. This can be used to avoid
+ // rewriting a list that is concurrently updated with a more recent version.
+ SequenceNumber snapshots_version_ = 0;
+
+ // A heap of prepared transactions. Thread-safety is provided with
+ // prepared_mutex_.
+ PreparedHeap prepared_txns_;
+ const size_t COMMIT_CACHE_BITS;
+ const size_t COMMIT_CACHE_SIZE;
+ const CommitEntry64bFormat FORMAT;
+ // commit_cache_ must be initialized to zero to tell apart an empty index from
+ // a filled one. Thread-safety is provided with commit_cache_mutex_.
+ std::unique_ptr<std::atomic<CommitEntry64b>[]> commit_cache_;
+ // The largest evicted *commit* sequence number from the commit_cache_. If a
+ // seq is smaller than max_evicted_seq_ is might or might not be present in
+ // commit_cache_. So commit_cache_ must first be checked before consulting
+ // with max_evicted_seq_.
+ std::atomic<uint64_t> max_evicted_seq_ = {};
+ // Order: 1) update future_max_evicted_seq_ = new_max, 2)
+ // GetSnapshotListFromDB(new_max), max_evicted_seq_ = new_max. Since
+ // GetSnapshotInternal guarantess that the snapshot seq is larger than
+ // future_max_evicted_seq_, this guarantes that if a snapshot is not larger
+ // than max has already being looked at via a GetSnapshotListFromDB(new_max).
+ std::atomic<uint64_t> future_max_evicted_seq_ = {};
+ // Advance max_evicted_seq_ by this value each time it needs an update. The
+ // larger the value, the less frequent advances we would have. We do not want
+ // it to be too large either as it would cause stalls by doing too much
+ // maintenance work under the lock.
+ size_t INC_STEP_FOR_MAX_EVICTED = 1;
+ // A map from old snapshots (expected to be used by a few read-only txns) to
+ // prepared sequence number of the evicted entries from commit_cache_ that
+ // overlaps with such snapshot. These are the prepared sequence numbers that
+ // the snapshot, to which they are mapped, cannot assume to be committed just
+ // because it is no longer in the commit_cache_. The vector must be sorted
+ // after each update.
+ // Thread-safety is provided with old_commit_map_mutex_.
+ std::map<SequenceNumber, std::vector<SequenceNumber>> old_commit_map_;
+ // A set of long-running prepared transactions that are not finished by the
+ // time max_evicted_seq_ advances their sequence number. This is expected to
+ // be empty normally. Thread-safety is provided with prepared_mutex_.
+ std::set<uint64_t> delayed_prepared_;
+ // Commit of a delayed prepared: 1) update commit cache, 2) update
+ // delayed_prepared_commits_, 3) publish seq, 3) clean up delayed_prepared_.
+ // delayed_prepared_commits_ will help us tell apart the unprepared txns from
+ // the ones that are committed but not cleaned up yet.
+ std::unordered_map<SequenceNumber, SequenceNumber> delayed_prepared_commits_;
+ // Update when delayed_prepared_.empty() changes. Expected to be true
+ // normally.
+ std::atomic<bool> delayed_prepared_empty_ = {true};
+ // Update when old_commit_map_.empty() changes. Expected to be true normally.
+ std::atomic<bool> old_commit_map_empty_ = {true};
+ mutable port::RWMutex prepared_mutex_;
+ mutable port::RWMutex old_commit_map_mutex_;
+ mutable port::RWMutex commit_cache_mutex_;
+ mutable port::RWMutex snapshots_mutex_;
+ // A cache of the cf comparators
+ // Thread safety: since it is a const it is safe to read it concurrently
+ std::shared_ptr<std::map<uint32_t, const Comparator*>> cf_map_;
+ // A cache of the cf handles
+ // Thread safety: since the handle is read-only object it is a const it is
+ // safe to read it concurrently
+ std::shared_ptr<std::map<uint32_t, ColumnFamilyHandle*>> handle_map_;
+ // A dummy snapshot object that refers to kMaxSequenceNumber
+ SnapshotImpl dummy_max_snapshot_;
+};
+
+class WritePreparedTxnReadCallback : public ReadCallback {
+ public:
+ WritePreparedTxnReadCallback(WritePreparedTxnDB* db, SequenceNumber snapshot)
+ : ReadCallback(snapshot),
+ db_(db),
+ backed_by_snapshot_(kBackedByDBSnapshot) {}
+ WritePreparedTxnReadCallback(WritePreparedTxnDB* db, SequenceNumber snapshot,
+ SequenceNumber min_uncommitted,
+ SnapshotBackup backed_by_snapshot)
+ : ReadCallback(snapshot, min_uncommitted),
+ db_(db),
+ backed_by_snapshot_(backed_by_snapshot) {
+ (void)backed_by_snapshot_; // to silence unused private field warning
+ }
+
+ virtual ~WritePreparedTxnReadCallback() {
+ // If it is not backed by snapshot, the caller must check validity
+ assert(valid_checked_ || backed_by_snapshot_ == kBackedByDBSnapshot);
+ }
+
+ // Will be called to see if the seq number visible; if not it moves on to
+ // the next seq number.
+ inline virtual bool IsVisibleFullCheck(SequenceNumber seq) override {
+ auto snapshot = max_visible_seq_;
+ bool snap_released = false;
+ auto ret =
+ db_->IsInSnapshot(seq, snapshot, min_uncommitted_, &snap_released);
+ assert(!snap_released || backed_by_snapshot_ == kUnbackedByDBSnapshot);
+ snap_released_ |= snap_released;
+ return ret;
+ }
+
+ inline bool valid() {
+ valid_checked_ = true;
+ return snap_released_ == false;
+ }
+
+ // TODO(myabandeh): override Refresh when Iterator::Refresh is supported
+ private:
+ WritePreparedTxnDB* db_;
+ // Whether max_visible_seq_ is backed by a snapshot
+ const SnapshotBackup backed_by_snapshot_;
+ bool snap_released_ = false;
+ // Safety check to ensure that the caller has checked invalid statuses
+ bool valid_checked_ = false;
+};
+
+class AddPreparedCallback : public PreReleaseCallback {
+ public:
+ AddPreparedCallback(WritePreparedTxnDB* db, DBImpl* db_impl,
+ size_t sub_batch_cnt, bool two_write_queues,
+ bool first_prepare_batch)
+ : db_(db),
+ db_impl_(db_impl),
+ sub_batch_cnt_(sub_batch_cnt),
+ two_write_queues_(two_write_queues),
+ first_prepare_batch_(first_prepare_batch) {
+ (void)two_write_queues_; // to silence unused private field warning
+ }
+ virtual Status Callback(SequenceNumber prepare_seq,
+ bool is_mem_disabled __attribute__((__unused__)),
+ uint64_t log_number, size_t index,
+ size_t total) override {
+ assert(index < total);
+ // To reduce the cost of lock acquisition competing with the concurrent
+ // prepare requests, lock on the first callback and unlock on the last.
+ const bool do_lock = !two_write_queues_ || index == 0;
+ const bool do_unlock = !two_write_queues_ || index + 1 == total;
+ // Always Prepare from the main queue
+ assert(!two_write_queues_ || !is_mem_disabled); // implies the 1st queue
+ TEST_SYNC_POINT("AddPreparedCallback::AddPrepared::begin:pause");
+ TEST_SYNC_POINT("AddPreparedCallback::AddPrepared::begin:resume");
+ if (do_lock) {
+ db_->prepared_txns_.push_pop_mutex()->Lock();
+ }
+ const bool kLocked = true;
+ for (size_t i = 0; i < sub_batch_cnt_; i++) {
+ db_->AddPrepared(prepare_seq + i, kLocked);
+ }
+ if (do_unlock) {
+ db_->prepared_txns_.push_pop_mutex()->Unlock();
+ }
+ TEST_SYNC_POINT("AddPreparedCallback::AddPrepared::end");
+ if (first_prepare_batch_) {
+ assert(log_number != 0);
+ db_impl_->logs_with_prep_tracker()->MarkLogAsContainingPrepSection(
+ log_number);
+ }
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ DBImpl* db_impl_;
+ size_t sub_batch_cnt_;
+ bool two_write_queues_;
+ // It is 2PC and this is the first prepare batch. Always the case in 2PC
+ // unless it is WriteUnPrepared.
+ bool first_prepare_batch_;
+};
+
+class WritePreparedCommitEntryPreReleaseCallback : public PreReleaseCallback {
+ public:
+ // includes_data indicates that the commit also writes non-empty
+ // CommitTimeWriteBatch to memtable, which needs to be committed separately.
+ WritePreparedCommitEntryPreReleaseCallback(
+ WritePreparedTxnDB* db, DBImpl* db_impl, SequenceNumber prep_seq,
+ size_t prep_batch_cnt, size_t data_batch_cnt = 0,
+ SequenceNumber aux_seq = kMaxSequenceNumber, size_t aux_batch_cnt = 0)
+ : db_(db),
+ db_impl_(db_impl),
+ prep_seq_(prep_seq),
+ prep_batch_cnt_(prep_batch_cnt),
+ data_batch_cnt_(data_batch_cnt),
+ includes_data_(data_batch_cnt_ > 0),
+ aux_seq_(aux_seq),
+ aux_batch_cnt_(aux_batch_cnt),
+ includes_aux_batch_(aux_batch_cnt > 0) {
+ assert((prep_batch_cnt_ > 0) != (prep_seq == kMaxSequenceNumber)); // xor
+ assert(prep_batch_cnt_ > 0 || data_batch_cnt_ > 0);
+ assert((aux_batch_cnt_ > 0) != (aux_seq == kMaxSequenceNumber)); // xor
+ }
+
+ virtual Status Callback(SequenceNumber commit_seq,
+ bool is_mem_disabled __attribute__((__unused__)),
+ uint64_t, size_t /*index*/,
+ size_t /*total*/) override {
+ // Always commit from the 2nd queue
+ assert(!db_impl_->immutable_db_options().two_write_queues ||
+ is_mem_disabled);
+ assert(includes_data_ || prep_seq_ != kMaxSequenceNumber);
+ // Data batch is what accompanied with the commit marker and affects the
+ // last seq in the commit batch.
+ const uint64_t last_commit_seq = LIKELY(data_batch_cnt_ <= 1)
+ ? commit_seq
+ : commit_seq + data_batch_cnt_ - 1;
+ if (prep_seq_ != kMaxSequenceNumber) {
+ for (size_t i = 0; i < prep_batch_cnt_; i++) {
+ db_->AddCommitted(prep_seq_ + i, last_commit_seq);
+ }
+ } // else there was no prepare phase
+ if (includes_aux_batch_) {
+ for (size_t i = 0; i < aux_batch_cnt_; i++) {
+ db_->AddCommitted(aux_seq_ + i, last_commit_seq);
+ }
+ }
+ if (includes_data_) {
+ assert(data_batch_cnt_);
+ // Commit the data that is accompanied with the commit request
+ for (size_t i = 0; i < data_batch_cnt_; i++) {
+ // For commit seq of each batch use the commit seq of the last batch.
+ // This would make debugging easier by having all the batches having
+ // the same sequence number.
+ db_->AddCommitted(commit_seq + i, last_commit_seq);
+ }
+ }
+ if (db_impl_->immutable_db_options().two_write_queues) {
+ assert(is_mem_disabled); // implies the 2nd queue
+ // Publish the sequence number. We can do that here assuming the callback
+ // is invoked only from one write queue, which would guarantee that the
+ // publish sequence numbers will be in order, i.e., once a seq is
+ // published all the seq prior to that are also publishable.
+ db_impl_->SetLastPublishedSequence(last_commit_seq);
+ // Note RemovePrepared should be called after publishing the seq.
+ // Otherwise SmallestUnCommittedSeq optimization breaks.
+ if (prep_seq_ != kMaxSequenceNumber) {
+ db_->RemovePrepared(prep_seq_, prep_batch_cnt_);
+ } // else there was no prepare phase
+ if (includes_aux_batch_) {
+ db_->RemovePrepared(aux_seq_, aux_batch_cnt_);
+ }
+ }
+ // else SequenceNumber that is updated as part of the write already does the
+ // publishing
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ DBImpl* db_impl_;
+ // kMaxSequenceNumber if there was no prepare phase
+ SequenceNumber prep_seq_;
+ size_t prep_batch_cnt_;
+ size_t data_batch_cnt_;
+ // Data here is the batch that is written with the commit marker, either
+ // because it is commit without prepare or commit has a CommitTimeWriteBatch.
+ bool includes_data_;
+ // Auxiliary batch (if there is any) is a batch that is written before, but
+ // gets the same commit seq as prepare batch or data batch. This is used in
+ // two write queues where the CommitTimeWriteBatch becomes the aux batch and
+ // we do a separate write to actually commit everything.
+ SequenceNumber aux_seq_;
+ size_t aux_batch_cnt_;
+ bool includes_aux_batch_;
+};
+
+// For two_write_queues commit both the aborted batch and the cleanup batch and
+// then published the seq
+class WritePreparedRollbackPreReleaseCallback : public PreReleaseCallback {
+ public:
+ WritePreparedRollbackPreReleaseCallback(WritePreparedTxnDB* db,
+ DBImpl* db_impl,
+ SequenceNumber prep_seq,
+ SequenceNumber rollback_seq,
+ size_t prep_batch_cnt)
+ : db_(db),
+ db_impl_(db_impl),
+ prep_seq_(prep_seq),
+ rollback_seq_(rollback_seq),
+ prep_batch_cnt_(prep_batch_cnt) {
+ assert(prep_seq != kMaxSequenceNumber);
+ assert(rollback_seq != kMaxSequenceNumber);
+ assert(prep_batch_cnt_ > 0);
+ }
+
+ Status Callback(SequenceNumber commit_seq, bool is_mem_disabled, uint64_t,
+ size_t /*index*/, size_t /*total*/) override {
+ // Always commit from the 2nd queue
+ assert(is_mem_disabled); // implies the 2nd queue
+ assert(db_impl_->immutable_db_options().two_write_queues);
+#ifdef NDEBUG
+ (void)is_mem_disabled;
+#endif
+ const uint64_t last_commit_seq = commit_seq;
+ db_->AddCommitted(rollback_seq_, last_commit_seq);
+ for (size_t i = 0; i < prep_batch_cnt_; i++) {
+ db_->AddCommitted(prep_seq_ + i, last_commit_seq);
+ }
+ db_impl_->SetLastPublishedSequence(last_commit_seq);
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ DBImpl* db_impl_;
+ SequenceNumber prep_seq_;
+ SequenceNumber rollback_seq_;
+ size_t prep_batch_cnt_;
+};
+
+// Count the number of sub-batches inside a batch. A sub-batch does not have
+// duplicate keys.
+struct SubBatchCounter : public WriteBatch::Handler {
+ explicit SubBatchCounter(std::map<uint32_t, const Comparator*>& comparators)
+ : comparators_(comparators), batches_(1) {}
+ std::map<uint32_t, const Comparator*>& comparators_;
+ using CFKeys = std::set<Slice, SetComparator>;
+ std::map<uint32_t, CFKeys> keys_;
+ size_t batches_;
+ size_t BatchCount() { return batches_; }
+ void AddKey(const uint32_t cf, const Slice& key);
+ void InitWithComp(const uint32_t cf);
+ Status MarkNoop(bool) override { return Status::OK(); }
+ Status MarkEndPrepare(const Slice&) override { return Status::OK(); }
+ Status MarkCommit(const Slice&) override { return Status::OK(); }
+ Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
+ AddKey(cf, key);
+ return Status::OK();
+ }
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ AddKey(cf, key);
+ return Status::OK();
+ }
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ AddKey(cf, key);
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
+ AddKey(cf, key);
+ return Status::OK();
+ }
+ Status MarkBeginPrepare(bool) override { return Status::OK(); }
+ Status MarkRollback(const Slice&) override { return Status::OK(); }
+ bool WriteAfterCommit() const override { return false; }
+};
+
+SnapshotBackup WritePreparedTxnDB::AssignMinMaxSeqs(const Snapshot* snapshot,
+ SequenceNumber* min,
+ SequenceNumber* max) {
+ if (snapshot != nullptr) {
+ *min = static_cast_with_check<const SnapshotImpl, const Snapshot>(snapshot)
+ ->min_uncommitted_;
+ *max = static_cast_with_check<const SnapshotImpl, const Snapshot>(snapshot)
+ ->number_;
+ return kBackedByDBSnapshot;
+ } else {
+ *min = SmallestUnCommittedSeq();
+ *max = 0; // to be assigned later after sv is referenced.
+ return kUnbackedByDBSnapshot;
+ }
+}
+
+bool WritePreparedTxnDB::ValidateSnapshot(
+ const SequenceNumber snap_seq, const SnapshotBackup backed_by_snapshot,
+ std::memory_order order) {
+ if (backed_by_snapshot == kBackedByDBSnapshot) {
+ return true;
+ } else {
+ SequenceNumber max = max_evicted_seq_.load(order);
+ // Validate that max has not advanced the snapshot seq that is not backed
+ // by a real snapshot. This is a very rare case that should not happen in
+ // real workloads.
+ if (UNLIKELY(snap_seq <= max && snap_seq != 0)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_transaction_test.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_transaction_test.cc
new file mode 100644
index 00000000..8b1613b2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_transaction_test.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/transaction_test.h"
+#include "utilities/transactions/write_unprepared_txn.h"
+#include "utilities/transactions/write_unprepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteUnpreparedTransactionTestBase : public TransactionTestBase {
+ public:
+ WriteUnpreparedTransactionTestBase(bool use_stackable_db,
+ bool two_write_queue,
+ TxnDBWritePolicy write_policy)
+ : TransactionTestBase(use_stackable_db, two_write_queue, write_policy,
+ kOrderedWrite) {}
+};
+
+class WriteUnpreparedTransactionTest
+ : public WriteUnpreparedTransactionTestBase,
+ virtual public ::testing::WithParamInterface<
+ std::tuple<bool, bool, TxnDBWritePolicy>> {
+ public:
+ WriteUnpreparedTransactionTest()
+ : WriteUnpreparedTransactionTestBase(std::get<0>(GetParam()),
+ std::get<1>(GetParam()),
+ std::get<2>(GetParam())){}
+};
+
+INSTANTIATE_TEST_CASE_P(
+ WriteUnpreparedTransactionTest, WriteUnpreparedTransactionTest,
+ ::testing::Values(std::make_tuple(false, false, WRITE_UNPREPARED),
+ std::make_tuple(false, true, WRITE_UNPREPARED)));
+
+enum StressAction { NO_SNAPSHOT, RO_SNAPSHOT, REFRESH_SNAPSHOT };
+class WriteUnpreparedStressTest : public WriteUnpreparedTransactionTestBase,
+ virtual public ::testing::WithParamInterface<
+ std::tuple<bool, StressAction>> {
+ public:
+ WriteUnpreparedStressTest()
+ : WriteUnpreparedTransactionTestBase(false, std::get<0>(GetParam()),
+ WRITE_UNPREPARED),
+ action_(std::get<1>(GetParam())) {}
+ StressAction action_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ WriteUnpreparedStressTest, WriteUnpreparedStressTest,
+ ::testing::Values(std::make_tuple(false, NO_SNAPSHOT),
+ std::make_tuple(false, RO_SNAPSHOT),
+ std::make_tuple(false, REFRESH_SNAPSHOT),
+ std::make_tuple(true, NO_SNAPSHOT),
+ std::make_tuple(true, RO_SNAPSHOT),
+ std::make_tuple(true, REFRESH_SNAPSHOT)));
+
+TEST_P(WriteUnpreparedTransactionTest, ReadYourOwnWrite) {
+ // The following tests checks whether reading your own write for
+ // a transaction works for write unprepared, when there are uncommitted
+ // values written into DB.
+ auto verify_state = [](Iterator* iter, const std::string& key,
+ const std::string& value) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_OK(iter->status());
+ ASSERT_EQ(key, iter->key().ToString());
+ ASSERT_EQ(value, iter->value().ToString());
+ };
+
+ // Test always reseeking vs never reseeking.
+ for (uint64_t max_skip : {0, std::numeric_limits<int>::max()}) {
+ options.max_sequential_skip_in_iterations = max_skip;
+ options.disable_auto_compactions = true;
+ ReOpen();
+
+ TransactionOptions txn_options;
+ WriteOptions woptions;
+ ReadOptions roptions;
+
+ ASSERT_OK(db->Put(woptions, "a", ""));
+ ASSERT_OK(db->Put(woptions, "b", ""));
+
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ WriteUnpreparedTxn* wup_txn = dynamic_cast<WriteUnpreparedTxn*>(txn);
+ txn->SetSnapshot();
+
+ for (int i = 0; i < 5; i++) {
+ std::string stored_value = "v" + ToString(i);
+ ASSERT_OK(txn->Put("a", stored_value));
+ ASSERT_OK(txn->Put("b", stored_value));
+ wup_txn->FlushWriteBatchToDB(false);
+
+ // Test Get()
+ std::string value;
+ ASSERT_OK(txn->Get(roptions, "a", &value));
+ ASSERT_EQ(value, stored_value);
+ ASSERT_OK(txn->Get(roptions, "b", &value));
+ ASSERT_EQ(value, stored_value);
+
+ // Test Next()
+ auto iter = txn->GetIterator(roptions);
+ iter->Seek("a");
+ verify_state(iter, "a", stored_value);
+
+ iter->Next();
+ verify_state(iter, "b", stored_value);
+
+ iter->SeekToFirst();
+ verify_state(iter, "a", stored_value);
+
+ iter->Next();
+ verify_state(iter, "b", stored_value);
+
+ delete iter;
+
+ // Test Prev()
+ iter = txn->GetIterator(roptions);
+ iter->SeekForPrev("b");
+ verify_state(iter, "b", stored_value);
+
+ iter->Prev();
+ verify_state(iter, "a", stored_value);
+
+ iter->SeekToLast();
+ verify_state(iter, "b", stored_value);
+
+ iter->Prev();
+ verify_state(iter, "a", stored_value);
+
+ delete iter;
+ }
+
+ delete txn;
+ }
+}
+
+#ifndef ROCKSDB_VALGRIND_RUN
+TEST_P(WriteUnpreparedStressTest, ReadYourOwnWriteStress) {
+ // This is a stress test where different threads are writing random keys, and
+ // then before committing or aborting the transaction, it validates to see
+ // that it can read the keys it wrote, and the keys it did not write respect
+ // the snapshot. To avoid row lock contention (and simply stressing the
+ // locking system), each thread is mostly only writing to its own set of keys.
+ const uint32_t kNumIter = 1000;
+ const uint32_t kNumThreads = 10;
+ const uint32_t kNumKeys = 5;
+
+ std::default_random_engine rand(static_cast<uint32_t>(
+ std::hash<std::thread::id>()(std::this_thread::get_id())));
+
+ // Test with
+ // 1. no snapshots set
+ // 2. snapshot set on ReadOptions
+ // 3. snapshot set, and refreshing after every write.
+ StressAction a = action_;
+ WriteOptions write_options;
+ txn_db_options.transaction_lock_timeout = -1;
+ options.disable_auto_compactions = true;
+ ReOpen();
+
+ std::vector<std::string> keys;
+ for (uint32_t k = 0; k < kNumKeys * kNumThreads; k++) {
+ keys.push_back("k" + ToString(k));
+ }
+ std::shuffle(keys.begin(), keys.end(), rand);
+
+ // This counter will act as a "sequence number" to help us validate
+ // visibility logic with snapshots. If we had direct access to the seqno of
+ // snapshots and key/values, then we should directly compare those instead.
+ std::atomic<int64_t> counter(0);
+
+ std::function<void(uint32_t)> stress_thread = [&](int id) {
+ size_t tid = std::hash<std::thread::id>()(std::this_thread::get_id());
+ Random64 rnd(static_cast<uint32_t>(tid));
+
+ Transaction* txn;
+ TransactionOptions txn_options;
+ // batch_size of 1 causes writes to DB for every marker.
+ txn_options.write_batch_flush_threshold = 1;
+ ReadOptions read_options;
+
+ for (uint32_t i = 0; i < kNumIter; i++) {
+ std::set<std::string> owned_keys(&keys[id * kNumKeys],
+ &keys[(id + 1) * kNumKeys]);
+ // Add unowned keys to make the workload more interesting, but this
+ // increases row lock contention, so just do it sometimes.
+ if (rnd.OneIn(2)) {
+ owned_keys.insert(keys[rnd.Uniform(kNumKeys * kNumThreads)]);
+ }
+
+ txn = db->BeginTransaction(write_options, txn_options);
+ txn->SetName(ToString(id));
+ txn->SetSnapshot();
+ if (a >= RO_SNAPSHOT) {
+ read_options.snapshot = txn->GetSnapshot();
+ ASSERT_TRUE(read_options.snapshot != nullptr);
+ }
+
+ uint64_t buf[2];
+ buf[0] = id;
+
+ // When scanning through the database, make sure that all unprepared
+ // keys have value >= snapshot and all other keys have value < snapshot.
+ int64_t snapshot_num = counter.fetch_add(1);
+
+ Status s;
+ for (const auto& key : owned_keys) {
+ buf[1] = counter.fetch_add(1);
+ s = txn->Put(key, Slice((const char*)buf, sizeof(buf)));
+ if (!s.ok()) {
+ break;
+ }
+ if (a == REFRESH_SNAPSHOT) {
+ txn->SetSnapshot();
+ read_options.snapshot = txn->GetSnapshot();
+ snapshot_num = counter.fetch_add(1);
+ }
+ }
+
+ // Failure is possible due to snapshot validation. In this case,
+ // rollback and move onto next iteration.
+ if (!s.ok()) {
+ ASSERT_TRUE(s.IsBusy());
+ ASSERT_OK(txn->Rollback());
+ delete txn;
+ continue;
+ }
+
+ auto verify_key = [&owned_keys, &a, &id, &snapshot_num](
+ const std::string& key, const std::string& value) {
+ if (owned_keys.count(key) > 0) {
+ ASSERT_EQ(value.size(), 16);
+
+ // Since this key is part of owned_keys, then this key must be
+ // unprepared by this transaction identified by 'id'
+ ASSERT_EQ(((int64_t*)value.c_str())[0], id);
+ if (a == REFRESH_SNAPSHOT) {
+ // If refresh snapshot is true, then the snapshot is refreshed
+ // after every Put(), meaning that the current snapshot in
+ // snapshot_num must be greater than the "seqno" of any keys
+ // written by the current transaction.
+ ASSERT_LT(((int64_t*)value.c_str())[1], snapshot_num);
+ } else {
+ // If refresh snapshot is not on, then the snapshot was taken at
+ // the beginning of the transaction, meaning all writes must come
+ // after snapshot_num
+ ASSERT_GT(((int64_t*)value.c_str())[1], snapshot_num);
+ }
+ } else if (a >= RO_SNAPSHOT) {
+ // If this is not an unprepared key, just assert that the key
+ // "seqno" is smaller than the snapshot seqno.
+ ASSERT_EQ(value.size(), 16);
+ ASSERT_LT(((int64_t*)value.c_str())[1], snapshot_num);
+ }
+ };
+
+ // Validate Get()/Next()/Prev(). Do only one of them to save time, and
+ // reduce lock contention.
+ switch (rnd.Uniform(3)) {
+ case 0: // Validate Get()
+ {
+ for (const auto& key : keys) {
+ std::string value;
+ s = txn->Get(read_options, Slice(key), &value);
+ if (!s.ok()) {
+ ASSERT_TRUE(s.IsNotFound());
+ ASSERT_EQ(owned_keys.count(key), 0);
+ } else {
+ verify_key(key, value);
+ }
+ }
+ break;
+ }
+ case 1: // Validate Next()
+ {
+ Iterator* iter = txn->GetIterator(read_options);
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ verify_key(iter->key().ToString(), iter->value().ToString());
+ }
+ delete iter;
+ break;
+ }
+ case 2: // Validate Prev()
+ {
+ Iterator* iter = txn->GetIterator(read_options);
+ for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
+ verify_key(iter->key().ToString(), iter->value().ToString());
+ }
+ delete iter;
+ break;
+ }
+ default:
+ ASSERT_TRUE(false);
+ }
+
+ if (rnd.OneIn(2)) {
+ ASSERT_OK(txn->Commit());
+ } else {
+ ASSERT_OK(txn->Rollback());
+ }
+ delete txn;
+ }
+ };
+
+ std::vector<port::Thread> threads;
+ for (uint32_t i = 0; i < kNumThreads; i++) {
+ threads.emplace_back(stress_thread, i);
+ }
+
+ for (auto& t : threads) {
+ t.join();
+ }
+}
+#endif // ROCKSDB_VALGRIND_RUN
+
+// This tests how write unprepared behaves during recovery when the DB crashes
+// after a transaction has either been unprepared or prepared, and tests if
+// the changes are correctly applied for prepared transactions if we decide to
+// rollback/commit.
+TEST_P(WriteUnpreparedTransactionTest, RecoveryTest) {
+ WriteOptions write_options;
+ write_options.disableWAL = false;
+ TransactionOptions txn_options;
+ std::vector<Transaction*> prepared_trans;
+ WriteUnpreparedTxnDB* wup_db;
+ options.disable_auto_compactions = true;
+
+ enum Action { UNPREPARED, ROLLBACK, COMMIT };
+
+ // batch_size of 1 causes writes to DB for every marker.
+ for (size_t batch_size : {1, 1000000}) {
+ txn_options.write_batch_flush_threshold = batch_size;
+ for (bool empty : {true, false}) {
+ for (Action a : {UNPREPARED, ROLLBACK, COMMIT}) {
+ for (int num_batches = 1; num_batches < 10; num_batches++) {
+ // Reset database.
+ prepared_trans.clear();
+ ReOpen();
+ wup_db = dynamic_cast<WriteUnpreparedTxnDB*>(db);
+ if (!empty) {
+ for (int i = 0; i < num_batches; i++) {
+ ASSERT_OK(db->Put(WriteOptions(), "k" + ToString(i),
+ "before value" + ToString(i)));
+ }
+ }
+
+ // Write num_batches unprepared batches.
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ WriteUnpreparedTxn* wup_txn = dynamic_cast<WriteUnpreparedTxn*>(txn);
+ txn->SetName("xid");
+ for (int i = 0; i < num_batches; i++) {
+ ASSERT_OK(txn->Put("k" + ToString(i), "value" + ToString(i)));
+ if (txn_options.write_batch_flush_threshold == 1) {
+ // WriteUnprepared will check write_batch_flush_threshold and
+ // possibly flush before appending to the write batch. No flush
+ // will happen at the first write because the batch is still
+ // empty, so after k puts, there should be k-1 flushed batches.
+ ASSERT_EQ(wup_txn->GetUnpreparedSequenceNumbers().size(), i);
+ } else {
+ ASSERT_EQ(wup_txn->GetUnpreparedSequenceNumbers().size(), 0);
+ }
+ }
+ if (a == UNPREPARED) {
+ // This is done to prevent the destructor from rolling back the
+ // transaction for us, since we want to pretend we crashed and
+ // test that recovery does the rollback.
+ wup_txn->unprep_seqs_.clear();
+ } else {
+ txn->Prepare();
+ }
+ delete txn;
+
+ // Crash and run recovery code paths.
+ wup_db->db_impl_->FlushWAL(true);
+ wup_db->TEST_Crash();
+ ReOpenNoDelete();
+ assert(db != nullptr);
+
+ db->GetAllPreparedTransactions(&prepared_trans);
+ ASSERT_EQ(prepared_trans.size(), a == UNPREPARED ? 0 : 1);
+ if (a == ROLLBACK) {
+ ASSERT_OK(prepared_trans[0]->Rollback());
+ delete prepared_trans[0];
+ } else if (a == COMMIT) {
+ ASSERT_OK(prepared_trans[0]->Commit());
+ delete prepared_trans[0];
+ }
+
+ Iterator* iter = db->NewIterator(ReadOptions());
+ iter->SeekToFirst();
+ // Check that DB has before values.
+ if (!empty || a == COMMIT) {
+ for (int i = 0; i < num_batches; i++) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(iter->key().ToString(), "k" + ToString(i));
+ if (a == COMMIT) {
+ ASSERT_EQ(iter->value().ToString(), "value" + ToString(i));
+ } else {
+ ASSERT_EQ(iter->value().ToString(),
+ "before value" + ToString(i));
+ }
+ iter->Next();
+ }
+ }
+ ASSERT_FALSE(iter->Valid());
+ delete iter;
+ }
+ }
+ }
+ }
+}
+
+// Basic test to see that unprepared batch gets written to DB when batch size
+// is exceeded. It also does some basic checks to see if commit/rollback works
+// as expected for write unprepared.
+TEST_P(WriteUnpreparedTransactionTest, UnpreparedBatch) {
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ const int kNumKeys = 10;
+
+ // batch_size of 1 causes writes to DB for every marker.
+ for (size_t batch_size : {1, 1000000}) {
+ txn_options.write_batch_flush_threshold = batch_size;
+ for (bool prepare : {false, true}) {
+ for (bool commit : {false, true}) {
+ ReOpen();
+ Transaction* txn = db->BeginTransaction(write_options, txn_options);
+ WriteUnpreparedTxn* wup_txn = dynamic_cast<WriteUnpreparedTxn*>(txn);
+ txn->SetName("xid");
+
+ for (int i = 0; i < kNumKeys; i++) {
+ txn->Put("k" + ToString(i), "v" + ToString(i));
+ if (txn_options.write_batch_flush_threshold == 1) {
+ // WriteUnprepared will check write_batch_flush_threshold and
+ // possibly flush before appending to the write batch. No flush will
+ // happen at the first write because the batch is still empty, so
+ // after k puts, there should be k-1 flushed batches.
+ ASSERT_EQ(wup_txn->GetUnpreparedSequenceNumbers().size(), i);
+ } else {
+ ASSERT_EQ(wup_txn->GetUnpreparedSequenceNumbers().size(), 0);
+ }
+ }
+
+ if (prepare) {
+ ASSERT_OK(txn->Prepare());
+ }
+
+ Iterator* iter = db->NewIterator(ReadOptions());
+ iter->SeekToFirst();
+ assert(!iter->Valid());
+ ASSERT_FALSE(iter->Valid());
+ delete iter;
+
+ if (commit) {
+ ASSERT_OK(txn->Commit());
+ } else {
+ ASSERT_OK(txn->Rollback());
+ }
+ delete txn;
+
+ iter = db->NewIterator(ReadOptions());
+ iter->SeekToFirst();
+
+ for (int i = 0; i < (commit ? kNumKeys : 0); i++) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(iter->key().ToString(), "k" + ToString(i));
+ ASSERT_EQ(iter->value().ToString(), "v" + ToString(i));
+ iter->Next();
+ }
+ ASSERT_FALSE(iter->Valid());
+ delete iter;
+ }
+ }
+ }
+}
+
+// Test whether logs containing unprepared/prepared batches are kept even
+// after memtable finishes flushing, and whether they are removed when
+// transaction commits/aborts.
+//
+// TODO(lth): Merge with TransactionTest/TwoPhaseLogRollingTest tests.
+TEST_P(WriteUnpreparedTransactionTest, MarkLogWithPrepSection) {
+ WriteOptions write_options;
+ TransactionOptions txn_options;
+ // batch_size of 1 causes writes to DB for every marker.
+ txn_options.write_batch_flush_threshold = 1;
+ const int kNumKeys = 10;
+
+ WriteOptions wopts;
+ wopts.sync = true;
+
+ for (bool prepare : {false, true}) {
+ for (bool commit : {false, true}) {
+ ReOpen();
+ auto wup_db = dynamic_cast<WriteUnpreparedTxnDB*>(db);
+ auto db_impl = wup_db->db_impl_;
+
+ Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn1->SetName("xid1"));
+
+ Transaction* txn2 = db->BeginTransaction(write_options, txn_options);
+ ASSERT_OK(txn2->SetName("xid2"));
+
+ // Spread this transaction across multiple log files.
+ for (int i = 0; i < kNumKeys; i++) {
+ ASSERT_OK(txn1->Put("k1" + ToString(i), "v" + ToString(i)));
+ if (i >= kNumKeys / 2) {
+ ASSERT_OK(txn2->Put("k2" + ToString(i), "v" + ToString(i)));
+ }
+
+ if (i > 0) {
+ db_impl->TEST_SwitchWAL();
+ }
+ }
+
+ ASSERT_GT(txn1->GetLogNumber(), 0);
+ ASSERT_GT(txn2->GetLogNumber(), 0);
+
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn1->GetLogNumber());
+ ASSERT_GT(db_impl->TEST_LogfileNumber(), txn1->GetLogNumber());
+
+ if (prepare) {
+ ASSERT_OK(txn1->Prepare());
+ ASSERT_OK(txn2->Prepare());
+ }
+
+ ASSERT_GE(db_impl->TEST_LogfileNumber(), txn1->GetLogNumber());
+ ASSERT_GE(db_impl->TEST_LogfileNumber(), txn2->GetLogNumber());
+
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn1->GetLogNumber());
+ if (commit) {
+ ASSERT_OK(txn1->Commit());
+ } else {
+ ASSERT_OK(txn1->Rollback());
+ }
+
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(),
+ txn2->GetLogNumber());
+
+ if (commit) {
+ ASSERT_OK(txn2->Commit());
+ } else {
+ ASSERT_OK(txn2->Rollback());
+ }
+
+ ASSERT_EQ(db_impl->TEST_FindMinLogContainingOutstandingPrep(), 0);
+
+ delete txn1;
+ delete txn2;
+ }
+ }
+}
+
+TEST_P(WriteUnpreparedTransactionTest, NoSnapshotWrite) {
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ txn_options.write_batch_flush_threshold = 1;
+
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+
+ // Do some writes with no snapshot
+ ASSERT_OK(txn->Put("a", "a"));
+ ASSERT_OK(txn->Put("b", "b"));
+ ASSERT_OK(txn->Put("c", "c"));
+
+ // Test that it is still possible to create iterators after writes with no
+ // snapshot, if iterator snapshot is fresh enough.
+ ReadOptions roptions;
+ auto iter = txn->GetIterator(roptions);
+ int keys = 0;
+ for (iter->SeekToLast(); iter->Valid(); iter->Prev(), keys++) {
+ ASSERT_OK(iter->status());
+ ASSERT_EQ(iter->key().ToString(), iter->value().ToString());
+ }
+ ASSERT_EQ(keys, 3);
+
+ delete iter;
+ delete txn;
+}
+
+// Test whether write to a transaction while iterating is supported.
+TEST_P(WriteUnpreparedTransactionTest, IterateAndWrite) {
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ txn_options.write_batch_flush_threshold = 1;
+
+ enum Action { DO_DELETE, DO_UPDATE };
+
+ for (Action a : {DO_DELETE, DO_UPDATE}) {
+ for (int i = 0; i < 100; i++) {
+ ASSERT_OK(db->Put(woptions, ToString(i), ToString(i)));
+ }
+
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ // write_batch_ now contains 1 key.
+ ASSERT_OK(txn->Put("9", "a"));
+
+ ReadOptions roptions;
+ auto iter = txn->GetIterator(roptions);
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ ASSERT_OK(iter->status());
+ if (iter->key() == "9") {
+ ASSERT_EQ(iter->value().ToString(), "a");
+ } else {
+ ASSERT_EQ(iter->key().ToString(), iter->value().ToString());
+ }
+
+ if (a == DO_DELETE) {
+ ASSERT_OK(txn->Delete(iter->key()));
+ } else {
+ ASSERT_OK(txn->Put(iter->key(), "b"));
+ }
+ }
+
+ delete iter;
+ ASSERT_OK(txn->Commit());
+
+ iter = db->NewIterator(roptions);
+ if (a == DO_DELETE) {
+ // Check that db is empty.
+ iter->SeekToFirst();
+ ASSERT_FALSE(iter->Valid());
+ } else {
+ int keys = 0;
+ // Check that all values are updated to b.
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next(), keys++) {
+ ASSERT_OK(iter->status());
+ ASSERT_EQ(iter->value().ToString(), "b");
+ }
+ ASSERT_EQ(keys, 100);
+ }
+
+ delete iter;
+ delete txn;
+ }
+}
+
+TEST_P(WriteUnpreparedTransactionTest, SavePoint) {
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ txn_options.write_batch_flush_threshold = 1;
+
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ txn->SetSavePoint();
+ ASSERT_OK(txn->Put("a", "a"));
+ ASSERT_OK(txn->Put("b", "b"));
+ ASSERT_OK(txn->Commit());
+
+ ReadOptions roptions;
+ std::string value;
+ ASSERT_OK(txn->Get(roptions, "a", &value));
+ ASSERT_EQ(value, "a");
+ ASSERT_OK(txn->Get(roptions, "b", &value));
+ ASSERT_EQ(value, "b");
+ delete txn;
+}
+
+TEST_P(WriteUnpreparedTransactionTest, UntrackedKeys) {
+ WriteOptions woptions;
+ TransactionOptions txn_options;
+ txn_options.write_batch_flush_threshold = 1;
+
+ Transaction* txn = db->BeginTransaction(woptions, txn_options);
+ auto wb = txn->GetWriteBatch()->GetWriteBatch();
+ ASSERT_OK(txn->Put("a", "a"));
+ ASSERT_OK(wb->Put("a_untrack", "a_untrack"));
+ txn->SetSavePoint();
+ ASSERT_OK(txn->Put("b", "b"));
+ ASSERT_OK(txn->Put("b_untrack", "b_untrack"));
+
+ ReadOptions roptions;
+ std::string value;
+ ASSERT_OK(txn->Get(roptions, "a", &value));
+ ASSERT_EQ(value, "a");
+ ASSERT_OK(txn->Get(roptions, "a_untrack", &value));
+ ASSERT_EQ(value, "a_untrack");
+ ASSERT_OK(txn->Get(roptions, "b", &value));
+ ASSERT_EQ(value, "b");
+ ASSERT_OK(txn->Get(roptions, "b_untrack", &value));
+ ASSERT_EQ(value, "b_untrack");
+
+ // b and b_untrack should be rolled back.
+ ASSERT_OK(txn->RollbackToSavePoint());
+ ASSERT_OK(txn->Get(roptions, "a", &value));
+ ASSERT_EQ(value, "a");
+ ASSERT_OK(txn->Get(roptions, "a_untrack", &value));
+ ASSERT_EQ(value, "a_untrack");
+ auto s = txn->Get(roptions, "b", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn->Get(roptions, "b_untrack", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ // Everything should be rolled back.
+ ASSERT_OK(txn->Rollback());
+ s = txn->Get(roptions, "a", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn->Get(roptions, "a_untrack", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn->Get(roptions, "b", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = txn->Get(roptions, "b_untrack", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete txn;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr,
+ "SKIPPED as Transactions are not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.cc
new file mode 100644
index 00000000..01ec298c
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.cc
@@ -0,0 +1,999 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/write_unprepared_txn.h"
+#include "db/db_impl/db_impl.h"
+#include "util/cast_util.h"
+#include "utilities/transactions/write_unprepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+bool WriteUnpreparedTxnReadCallback::IsVisibleFullCheck(SequenceNumber seq) {
+ // Since unprep_seqs maps prep_seq => prepare_batch_cnt, to check if seq is
+ // in unprep_seqs, we have to check if seq is equal to prep_seq or any of
+ // the prepare_batch_cnt seq nums after it.
+ //
+ // TODO(lth): Can be optimized with std::lower_bound if unprep_seqs is
+ // large.
+ for (const auto& it : unprep_seqs_) {
+ if (it.first <= seq && seq < it.first + it.second) {
+ return true;
+ }
+ }
+
+ bool snap_released = false;
+ auto ret =
+ db_->IsInSnapshot(seq, wup_snapshot_, min_uncommitted_, &snap_released);
+ assert(!snap_released || backed_by_snapshot_ == kUnbackedByDBSnapshot);
+ snap_released_ |= snap_released;
+ return ret;
+}
+
+WriteUnpreparedTxn::WriteUnpreparedTxn(WriteUnpreparedTxnDB* txn_db,
+ const WriteOptions& write_options,
+ const TransactionOptions& txn_options)
+ : WritePreparedTxn(txn_db, write_options, txn_options),
+ wupt_db_(txn_db),
+ last_log_number_(0),
+ recovered_txn_(false),
+ largest_validated_seq_(0) {
+ if (txn_options.write_batch_flush_threshold < 0) {
+ write_batch_flush_threshold_ =
+ txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold;
+ } else {
+ write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold;
+ }
+}
+
+WriteUnpreparedTxn::~WriteUnpreparedTxn() {
+ if (!unprep_seqs_.empty()) {
+ assert(log_number_ > 0);
+ assert(GetId() > 0);
+ assert(!name_.empty());
+
+ // We should rollback regardless of GetState, but some unit tests that
+ // test crash recovery run the destructor assuming that rollback does not
+ // happen, so that rollback during recovery can be exercised.
+ if (GetState() == STARTED || GetState() == LOCKS_STOLEN) {
+ auto s = RollbackInternal();
+ assert(s.ok());
+ if (!s.ok()) {
+ ROCKS_LOG_FATAL(
+ wupt_db_->info_log_,
+ "Rollback of WriteUnprepared transaction failed in destructor: %s",
+ s.ToString().c_str());
+ }
+ dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
+ log_number_);
+ }
+ }
+
+ // Call tracked_keys_.clear() so that ~PessimisticTransaction does not
+ // try to unlock keys for recovered transactions.
+ if (recovered_txn_) {
+ tracked_keys_.clear();
+ }
+}
+
+void WriteUnpreparedTxn::Initialize(const TransactionOptions& txn_options) {
+ PessimisticTransaction::Initialize(txn_options);
+ if (txn_options.write_batch_flush_threshold < 0) {
+ write_batch_flush_threshold_ =
+ txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold;
+ } else {
+ write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold;
+ }
+
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ recovered_txn_ = false;
+ largest_validated_seq_ = 0;
+ assert(active_iterators_.empty());
+ active_iterators_.clear();
+ untracked_keys_.clear();
+}
+
+Status WriteUnpreparedTxn::HandleWrite(std::function<Status()> do_write) {
+ Status s;
+ if (active_iterators_.empty()) {
+ s = MaybeFlushWriteBatchToDB();
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ s = do_write();
+ if (s.ok()) {
+ if (snapshot_) {
+ largest_validated_seq_ =
+ std::max(largest_validated_seq_, snapshot_->GetSequenceNumber());
+ } else {
+ // TODO(lth): We should use the same number as tracked_at_seq in TryLock,
+ // because what is actually being tracked is the sequence number at which
+ // this key was locked at.
+ largest_validated_seq_ = db_impl_->GetLastPublishedSequence();
+ }
+ }
+ return s;
+}
+
+Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::Put(column_family, key, value, assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family,
+ const SliceParts& key, const SliceParts& value,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::Put(column_family, key, value, assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::Merge(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::Merge(column_family, key, value,
+ assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family,
+ const Slice& key, const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::Delete(column_family, key, assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::Delete(column_family, key, assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::SingleDelete(column_family, key,
+ assume_tracked);
+ });
+}
+
+Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked) {
+ return HandleWrite([&]() {
+ return TransactionBaseImpl::SingleDelete(column_family, key,
+ assume_tracked);
+ });
+}
+
+// WriteUnpreparedTxn::RebuildFromWriteBatch is only called on recovery. For
+// WriteUnprepared, the write batches have already been written into the
+// database during WAL replay, so all we have to do is just to "retrack" the key
+// so that rollbacks are possible.
+//
+// Calling TryLock instead of TrackKey is also possible, but as an optimization,
+// recovered transactions do not hold locks on their keys. This follows the
+// implementation in PessimisticTransactionDB::Initialize where we set
+// skip_concurrency_control to true.
+Status WriteUnpreparedTxn::RebuildFromWriteBatch(WriteBatch* wb) {
+ struct TrackKeyHandler : public WriteBatch::Handler {
+ WriteUnpreparedTxn* txn_;
+ bool rollback_merge_operands_;
+
+ TrackKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands)
+ : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {}
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
+ txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
+ false /* read_only */, true /* exclusive */);
+ return Status::OK();
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
+ false /* read_only */, true /* exclusive */);
+ return Status::OK();
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
+ false /* read_only */, true /* exclusive */);
+ return Status::OK();
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
+ if (rollback_merge_operands_) {
+ txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
+ false /* read_only */, true /* exclusive */);
+ }
+ return Status::OK();
+ }
+
+ // Recovered batches do not contain 2PC markers.
+ Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
+
+ Status MarkEndPrepare(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkNoop(bool) override { return Status::InvalidArgument(); }
+
+ Status MarkCommit(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ };
+
+ TrackKeyHandler handler(this,
+ wupt_db_->txn_db_options_.rollback_merge_operands);
+ return wb->Iterate(&handler);
+}
+
+Status WriteUnpreparedTxn::MaybeFlushWriteBatchToDB() {
+ const bool kPrepared = true;
+ Status s;
+ if (write_batch_flush_threshold_ > 0 &&
+ write_batch_.GetWriteBatch()->Count() > 0 &&
+ write_batch_.GetDataSize() >
+ static_cast<size_t>(write_batch_flush_threshold_)) {
+ assert(GetState() != PREPARED);
+ s = FlushWriteBatchToDB(!kPrepared);
+ }
+ return s;
+}
+
+Status WriteUnpreparedTxn::FlushWriteBatchToDB(bool prepared) {
+ // If the current write batch contains savepoints, then some special handling
+ // is required so that RollbackToSavepoint can work.
+ //
+ // RollbackToSavepoint is not supported after Prepare() is called, so only do
+ // this for unprepared batches.
+ if (!prepared && unflushed_save_points_ != nullptr &&
+ !unflushed_save_points_->empty()) {
+ return FlushWriteBatchWithSavePointToDB();
+ }
+
+ return FlushWriteBatchToDBInternal(prepared);
+}
+
+Status WriteUnpreparedTxn::FlushWriteBatchToDBInternal(bool prepared) {
+ if (name_.empty()) {
+ assert(!prepared);
+#ifndef NDEBUG
+ static std::atomic_ullong autogen_id{0};
+ // To avoid changing all tests to call SetName, just autogenerate one.
+ if (wupt_db_->txn_db_options_.autogenerate_name) {
+ SetName(std::string("autoxid") + ToString(autogen_id.fetch_add(1)));
+ } else
+#endif
+ {
+ return Status::InvalidArgument("Cannot write to DB without SetName.");
+ }
+ }
+
+ struct UntrackedKeyHandler : public WriteBatch::Handler {
+ WriteUnpreparedTxn* txn_;
+ bool rollback_merge_operands_;
+
+ UntrackedKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands)
+ : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {}
+
+ Status AddUntrackedKey(uint32_t cf, const Slice& key) {
+ auto str = key.ToString();
+ if (txn_->tracked_keys_[cf].count(str) == 0) {
+ txn_->untracked_keys_[cf].push_back(str);
+ }
+ return Status::OK();
+ }
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
+ return AddUntrackedKey(cf, key);
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ return AddUntrackedKey(cf, key);
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ return AddUntrackedKey(cf, key);
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
+ if (rollback_merge_operands_) {
+ return AddUntrackedKey(cf, key);
+ }
+ return Status::OK();
+ }
+
+ // The only expected 2PC marker is the initial Noop marker.
+ Status MarkNoop(bool empty_batch) override {
+ return empty_batch ? Status::OK() : Status::InvalidArgument();
+ }
+
+ Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
+
+ Status MarkEndPrepare(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkCommit(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ };
+
+ UntrackedKeyHandler handler(
+ this, wupt_db_->txn_db_options_.rollback_merge_operands);
+ auto s = GetWriteBatch()->GetWriteBatch()->Iterate(&handler);
+ assert(s.ok());
+
+ // TODO(lth): Reduce duplicate code with WritePrepared prepare logic.
+ WriteOptions write_options = write_options_;
+ write_options.disableWAL = false;
+ const bool WRITE_AFTER_COMMIT = true;
+ const bool first_prepare_batch = log_number_ == 0;
+ // MarkEndPrepare will change Noop marker to the appropriate marker.
+ WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(), name_,
+ !WRITE_AFTER_COMMIT, !prepared);
+ // For each duplicate key we account for a new sub-batch
+ prepare_batch_cnt_ = GetWriteBatch()->SubBatchCnt();
+ // AddPrepared better to be called in the pre-release callback otherwise there
+ // is a non-zero chance of max advancing prepare_seq and readers assume the
+ // data as committed.
+ // Also having it in the PreReleaseCallback allows in-order addition of
+ // prepared entries to PreparedHeap and hence enables an optimization. Refer
+ // to SmallestUnCommittedSeq for more details.
+ AddPreparedCallback add_prepared_callback(
+ wpt_db_, db_impl_, prepare_batch_cnt_,
+ db_impl_->immutable_db_options().two_write_queues, first_prepare_batch);
+ const bool DISABLE_MEMTABLE = true;
+ uint64_t seq_used = kMaxSequenceNumber;
+ // log_number_ should refer to the oldest log containing uncommitted data
+ // from the current transaction. This means that if log_number_ is set,
+ // WriteImpl should not overwrite that value, so set log_used to nullptr if
+ // log_number_ is already set.
+ s = db_impl_->WriteImpl(write_options, GetWriteBatch()->GetWriteBatch(),
+ /*callback*/ nullptr, &last_log_number_,
+ /*log ref*/ 0, !DISABLE_MEMTABLE, &seq_used,
+ prepare_batch_cnt_, &add_prepared_callback);
+ if (log_number_ == 0) {
+ log_number_ = last_log_number_;
+ }
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ auto prepare_seq = seq_used;
+
+ // Only call SetId if it hasn't been set yet.
+ if (GetId() == 0) {
+ SetId(prepare_seq);
+ }
+ // unprep_seqs_ will also contain prepared seqnos since they are treated in
+ // the same way in the prepare/commit callbacks. See the comment on the
+ // definition of unprep_seqs_.
+ unprep_seqs_[prepare_seq] = prepare_batch_cnt_;
+
+ // Reset transaction state.
+ if (!prepared) {
+ prepare_batch_cnt_ = 0;
+ const bool kClear = true;
+ TransactionBaseImpl::InitWriteBatch(kClear);
+ }
+
+ return s;
+}
+
+Status WriteUnpreparedTxn::FlushWriteBatchWithSavePointToDB() {
+ assert(unflushed_save_points_ != nullptr &&
+ unflushed_save_points_->size() > 0);
+ assert(save_points_ != nullptr && save_points_->size() > 0);
+ assert(save_points_->size() >= unflushed_save_points_->size());
+
+ // Handler class for creating an unprepared batch from a savepoint.
+ struct SavePointBatchHandler : public WriteBatch::Handler {
+ WriteBatchWithIndex* wb_;
+ const std::map<uint32_t, ColumnFamilyHandle*>& handles_;
+
+ SavePointBatchHandler(
+ WriteBatchWithIndex* wb,
+ const std::map<uint32_t, ColumnFamilyHandle*>& handles)
+ : wb_(wb), handles_(handles) {}
+
+ Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
+ return wb_->Put(handles_.at(cf), key, value);
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ return wb_->Delete(handles_.at(cf), key);
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ return wb_->SingleDelete(handles_.at(cf), key);
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
+ return wb_->Merge(handles_.at(cf), key, value);
+ }
+
+ // The only expected 2PC marker is the initial Noop marker.
+ Status MarkNoop(bool empty_batch) override {
+ return empty_batch ? Status::OK() : Status::InvalidArgument();
+ }
+
+ Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
+
+ Status MarkEndPrepare(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkCommit(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ };
+
+ // The comparator of the default cf is passed in, similar to the
+ // initialization of TransactionBaseImpl::write_batch_. This comparator is
+ // only used if the write batch encounters an invalid cf id, and falls back to
+ // this comparator.
+ WriteBatchWithIndex wb(wpt_db_->DefaultColumnFamily()->GetComparator(), 0,
+ true, 0);
+ // Swap with write_batch_ so that wb contains the complete write batch. The
+ // actual write batch that will be flushed to DB will be built in
+ // write_batch_, and will be read by FlushWriteBatchToDBInternal.
+ std::swap(wb, write_batch_);
+ TransactionBaseImpl::InitWriteBatch();
+
+ size_t prev_boundary = WriteBatchInternal::kHeader;
+ const bool kPrepared = true;
+ for (size_t i = 0; i < unflushed_save_points_->size() + 1; i++) {
+ bool trailing_batch = i == unflushed_save_points_->size();
+ SavePointBatchHandler sp_handler(&write_batch_,
+ *wupt_db_->GetCFHandleMap().get());
+ size_t curr_boundary = trailing_batch ? wb.GetWriteBatch()->GetDataSize()
+ : (*unflushed_save_points_)[i];
+
+ // Construct the partial write batch up to the savepoint.
+ //
+ // Theoretically, a memcpy between the write batches should be sufficient
+ // since the rewriting into the batch should produce the exact same byte
+ // representation. Rebuilding the WriteBatchWithIndex index is still
+ // necessary though, and would imply doing two passes over the batch though.
+ Status s = WriteBatchInternal::Iterate(wb.GetWriteBatch(), &sp_handler,
+ prev_boundary, curr_boundary);
+ if (!s.ok()) {
+ return s;
+ }
+
+ if (write_batch_.GetWriteBatch()->Count() > 0) {
+ // Flush the write batch.
+ s = FlushWriteBatchToDBInternal(!kPrepared);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ if (!trailing_batch) {
+ if (flushed_save_points_ == nullptr) {
+ flushed_save_points_.reset(
+ new autovector<WriteUnpreparedTxn::SavePoint>());
+ }
+ flushed_save_points_->emplace_back(
+ unprep_seqs_, new ManagedSnapshot(db_impl_, wupt_db_->GetSnapshot()));
+ }
+
+ prev_boundary = curr_boundary;
+ const bool kClear = true;
+ TransactionBaseImpl::InitWriteBatch(kClear);
+ }
+
+ unflushed_save_points_->clear();
+ return Status::OK();
+}
+
+Status WriteUnpreparedTxn::PrepareInternal() {
+ const bool kPrepared = true;
+ return FlushWriteBatchToDB(kPrepared);
+}
+
+Status WriteUnpreparedTxn::CommitWithoutPrepareInternal() {
+ if (unprep_seqs_.empty()) {
+ assert(log_number_ == 0);
+ assert(GetId() == 0);
+ return WritePreparedTxn::CommitWithoutPrepareInternal();
+ }
+
+ // TODO(lth): We should optimize commit without prepare to not perform
+ // a prepare under the hood.
+ auto s = PrepareInternal();
+ if (!s.ok()) {
+ return s;
+ }
+ return CommitInternal();
+}
+
+Status WriteUnpreparedTxn::CommitInternal() {
+ // TODO(lth): Reduce duplicate code with WritePrepared commit logic.
+
+ // We take the commit-time batch and append the Commit marker. The Memtable
+ // will ignore the Commit marker in non-recovery mode
+ WriteBatch* working_batch = GetCommitTimeWriteBatch();
+ const bool empty = working_batch->Count() == 0;
+ WriteBatchInternal::MarkCommit(working_batch, name_);
+
+ const bool for_recovery = use_only_the_last_commit_time_batch_for_recovery_;
+ if (!empty && for_recovery) {
+ // When not writing to memtable, we can still cache the latest write batch.
+ // The cached batch will be written to memtable in WriteRecoverableState
+ // during FlushMemTable
+ WriteBatchInternal::SetAsLastestPersistentState(working_batch);
+ }
+
+ const bool includes_data = !empty && !for_recovery;
+ size_t commit_batch_cnt = 0;
+ if (UNLIKELY(includes_data)) {
+ ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log,
+ "Duplicate key overhead");
+ SubBatchCounter counter(*wpt_db_->GetCFComparatorMap());
+ auto s = working_batch->Iterate(&counter);
+ assert(s.ok());
+ commit_batch_cnt = counter.BatchCount();
+ }
+ const bool disable_memtable = !includes_data;
+ const bool do_one_write =
+ !db_impl_->immutable_db_options().two_write_queues || disable_memtable;
+
+ WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map(
+ wpt_db_, db_impl_, unprep_seqs_, commit_batch_cnt);
+ const bool kFirstPrepareBatch = true;
+ AddPreparedCallback add_prepared_callback(
+ wpt_db_, db_impl_, commit_batch_cnt,
+ db_impl_->immutable_db_options().two_write_queues, !kFirstPrepareBatch);
+ PreReleaseCallback* pre_release_callback;
+ if (do_one_write) {
+ pre_release_callback = &update_commit_map;
+ } else {
+ pre_release_callback = &add_prepared_callback;
+ }
+ uint64_t seq_used = kMaxSequenceNumber;
+ // Since the prepared batch is directly written to memtable, there is
+ // already a connection between the memtable and its WAL, so there is no
+ // need to redundantly reference the log that contains the prepared data.
+ const uint64_t zero_log_number = 0ull;
+ size_t batch_cnt = UNLIKELY(commit_batch_cnt) ? commit_batch_cnt : 1;
+ auto s = db_impl_->WriteImpl(write_options_, working_batch, nullptr, nullptr,
+ zero_log_number, disable_memtable, &seq_used,
+ batch_cnt, pre_release_callback);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ const SequenceNumber commit_batch_seq = seq_used;
+ if (LIKELY(do_one_write || !s.ok())) {
+ if (LIKELY(s.ok())) {
+ // Note RemovePrepared should be called after WriteImpl that publishsed
+ // the seq. Otherwise SmallestUnCommittedSeq optimization breaks.
+ for (const auto& seq : unprep_seqs_) {
+ wpt_db_->RemovePrepared(seq.first, seq.second);
+ }
+ }
+ if (UNLIKELY(!do_one_write)) {
+ wpt_db_->RemovePrepared(commit_batch_seq, commit_batch_cnt);
+ }
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ return s;
+ } // else do the 2nd write to publish seq
+
+ // Populate unprep_seqs_ with commit_batch_seq, since we treat data in the
+ // commit write batch as just another "unprepared" batch. This will also
+ // update the unprep_seqs_ in the update_commit_map callback.
+ unprep_seqs_[commit_batch_seq] = commit_batch_cnt;
+
+ // Note: the 2nd write comes with a performance penality. So if we have too
+ // many of commits accompanied with ComitTimeWriteBatch and yet we cannot
+ // enable use_only_the_last_commit_time_batch_for_recovery_ optimization,
+ // two_write_queues should be disabled to avoid many additional writes here.
+
+ // Update commit map only from the 2nd queue
+ WriteBatch empty_batch;
+ empty_batch.PutLogData(Slice());
+ // In the absence of Prepare markers, use Noop as a batch separator
+ WriteBatchInternal::InsertNoop(&empty_batch);
+ const bool DISABLE_MEMTABLE = true;
+ const size_t ONE_BATCH = 1;
+ const uint64_t NO_REF_LOG = 0;
+ s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
+ NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ &update_commit_map);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ // Note RemovePrepared should be called after WriteImpl that publishsed the
+ // seq. Otherwise SmallestUnCommittedSeq optimization breaks.
+ for (const auto& seq : unprep_seqs_) {
+ wpt_db_->RemovePrepared(seq.first, seq.second);
+ }
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ return s;
+}
+
+Status WriteUnpreparedTxn::WriteRollbackKeys(
+ const TransactionKeyMap& tracked_keys, WriteBatchWithIndex* rollback_batch,
+ ReadCallback* callback, const ReadOptions& roptions) {
+ const auto& cf_map = *wupt_db_->GetCFHandleMap();
+ auto WriteRollbackKey = [&](const std::string& key, uint32_t cfid) {
+ const auto& cf_handle = cf_map.at(cfid);
+ PinnableSlice pinnable_val;
+ bool not_used;
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = cf_handle;
+ get_impl_options.value = &pinnable_val;
+ get_impl_options.value_found = &not_used;
+ get_impl_options.callback = callback;
+ auto s = db_impl_->GetImpl(roptions, key, get_impl_options);
+
+ if (s.ok()) {
+ s = rollback_batch->Put(cf_handle, key, pinnable_val);
+ assert(s.ok());
+ } else if (s.IsNotFound()) {
+ s = rollback_batch->Delete(cf_handle, key);
+ assert(s.ok());
+ } else {
+ return s;
+ }
+
+ return Status::OK();
+ };
+
+ for (const auto& cfkey : tracked_keys) {
+ const auto cfid = cfkey.first;
+ const auto& keys = cfkey.second;
+ for (const auto& pair : keys) {
+ auto s = WriteRollbackKey(pair.first, cfid);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+
+ for (const auto& cfkey : untracked_keys_) {
+ const auto cfid = cfkey.first;
+ const auto& keys = cfkey.second;
+ for (const auto& key : keys) {
+ auto s = WriteRollbackKey(key, cfid);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
+Status WriteUnpreparedTxn::RollbackInternal() {
+ // TODO(lth): Reduce duplicate code with WritePrepared rollback logic.
+ WriteBatchWithIndex rollback_batch(
+ wpt_db_->DefaultColumnFamily()->GetComparator(), 0, true, 0);
+ assert(GetId() != kMaxSequenceNumber);
+ assert(GetId() > 0);
+ Status s;
+ auto read_at_seq = kMaxSequenceNumber;
+ ReadOptions roptions;
+ // to prevent callback's seq to be overrriden inside DBImpk::Get
+ roptions.snapshot = wpt_db_->GetMaxSnapshot();
+ // Note that we do not use WriteUnpreparedTxnReadCallback because we do not
+ // need to read our own writes when reading prior versions of the key for
+ // rollback.
+ WritePreparedTxnReadCallback callback(wpt_db_, read_at_seq);
+ WriteRollbackKeys(GetTrackedKeys(), &rollback_batch, &callback, roptions);
+
+ // The Rollback marker will be used as a batch separator
+ WriteBatchInternal::MarkRollback(rollback_batch.GetWriteBatch(), name_);
+ bool do_one_write = !db_impl_->immutable_db_options().two_write_queues;
+ const bool DISABLE_MEMTABLE = true;
+ const uint64_t NO_REF_LOG = 0;
+ uint64_t seq_used = kMaxSequenceNumber;
+ // TODO(lth): We write rollback batch all in a single batch here, but this
+ // should be subdivded into multiple batches as well. In phase 2, when key
+ // sets are read from WAL, this will happen naturally.
+ const size_t ONE_BATCH = 1;
+ // We commit the rolled back prepared batches. ALthough this is
+ // counter-intuitive, i) it is safe to do so, since the prepared batches are
+ // already canceled out by the rollback batch, ii) adding the commit entry to
+ // CommitCache will allow us to benefit from the existing mechanism in
+ // CommitCache that keeps an entry evicted due to max advance and yet overlaps
+ // with a live snapshot around so that the live snapshot properly skips the
+ // entry even if its prepare seq is lower than max_evicted_seq_.
+ WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map(
+ wpt_db_, db_impl_, unprep_seqs_, ONE_BATCH);
+ // Note: the rollback batch does not need AddPrepared since it is written to
+ // DB in one shot. min_uncommitted still works since it requires capturing
+ // data that is written to DB but not yet committed, while the roolback
+ // batch commits with PreReleaseCallback.
+ s = db_impl_->WriteImpl(write_options_, rollback_batch.GetWriteBatch(),
+ nullptr, nullptr, NO_REF_LOG, !DISABLE_MEMTABLE,
+ &seq_used, rollback_batch.SubBatchCnt(),
+ do_one_write ? &update_commit_map : nullptr);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ if (!s.ok()) {
+ return s;
+ }
+ if (do_one_write) {
+ for (const auto& seq : unprep_seqs_) {
+ wpt_db_->RemovePrepared(seq.first, seq.second);
+ }
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ return s;
+ } // else do the 2nd write for commit
+ uint64_t& prepare_seq = seq_used;
+ ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
+ "RollbackInternal 2nd write prepare_seq: %" PRIu64,
+ prepare_seq);
+ // Commit the batch by writing an empty batch to the queue that will release
+ // the commit sequence number to readers.
+ WriteUnpreparedRollbackPreReleaseCallback update_commit_map_with_prepare(
+ wpt_db_, db_impl_, unprep_seqs_, prepare_seq);
+ WriteBatch empty_batch;
+ empty_batch.PutLogData(Slice());
+ // In the absence of Prepare markers, use Noop as a batch separator
+ WriteBatchInternal::InsertNoop(&empty_batch);
+ s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
+ NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, ONE_BATCH,
+ &update_commit_map_with_prepare);
+ assert(!s.ok() || seq_used != kMaxSequenceNumber);
+ // Mark the txn as rolled back
+ if (s.ok()) {
+ for (const auto& seq : unprep_seqs_) {
+ wpt_db_->RemovePrepared(seq.first, seq.second);
+ }
+ }
+
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ return s;
+}
+
+void WriteUnpreparedTxn::Clear() {
+ if (!recovered_txn_) {
+ txn_db_impl_->UnLock(this, &GetTrackedKeys());
+ }
+ unprep_seqs_.clear();
+ flushed_save_points_.reset(nullptr);
+ unflushed_save_points_.reset(nullptr);
+ recovered_txn_ = false;
+ largest_validated_seq_ = 0;
+ assert(active_iterators_.empty());
+ active_iterators_.clear();
+ untracked_keys_.clear();
+ TransactionBaseImpl::Clear();
+}
+
+void WriteUnpreparedTxn::SetSavePoint() {
+ assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
+ (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
+ (save_points_ ? save_points_->size() : 0));
+ PessimisticTransaction::SetSavePoint();
+ if (unflushed_save_points_ == nullptr) {
+ unflushed_save_points_.reset(new autovector<size_t>());
+ }
+ unflushed_save_points_->push_back(write_batch_.GetDataSize());
+}
+
+Status WriteUnpreparedTxn::RollbackToSavePoint() {
+ assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
+ (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
+ (save_points_ ? save_points_->size() : 0));
+ if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) {
+ Status s = PessimisticTransaction::RollbackToSavePoint();
+ assert(!s.IsNotFound());
+ unflushed_save_points_->pop_back();
+ return s;
+ }
+
+ if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) {
+ return RollbackToSavePointInternal();
+ }
+
+ return Status::NotFound();
+}
+
+Status WriteUnpreparedTxn::RollbackToSavePointInternal() {
+ Status s;
+
+ const bool kClear = true;
+ TransactionBaseImpl::InitWriteBatch(kClear);
+
+ assert(flushed_save_points_->size() > 0);
+ WriteUnpreparedTxn::SavePoint& top = flushed_save_points_->back();
+
+ assert(save_points_ != nullptr && save_points_->size() > 0);
+ const TransactionKeyMap& tracked_keys = save_points_->top().new_keys_;
+
+ ReadOptions roptions;
+ roptions.snapshot = top.snapshot_->snapshot();
+ SequenceNumber min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ roptions.snapshot)
+ ->min_uncommitted_;
+ SequenceNumber snap_seq = roptions.snapshot->GetSequenceNumber();
+ WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
+ top.unprep_seqs_,
+ kBackedByDBSnapshot);
+ WriteRollbackKeys(tracked_keys, &write_batch_, &callback, roptions);
+
+ const bool kPrepared = true;
+ s = FlushWriteBatchToDBInternal(!kPrepared);
+ assert(s.ok());
+ if (!s.ok()) {
+ return s;
+ }
+
+ // PessimisticTransaction::RollbackToSavePoint will call also call
+ // RollbackToSavepoint on write_batch_. However, write_batch_ is empty and has
+ // no savepoints because this savepoint has already been flushed. Work around
+ // this by setting a fake savepoint.
+ write_batch_.SetSavePoint();
+ s = PessimisticTransaction::RollbackToSavePoint();
+ assert(s.ok());
+ if (!s.ok()) {
+ return s;
+ }
+
+ flushed_save_points_->pop_back();
+ return s;
+}
+
+Status WriteUnpreparedTxn::PopSavePoint() {
+ assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
+ (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
+ (save_points_ ? save_points_->size() : 0));
+ if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) {
+ Status s = PessimisticTransaction::PopSavePoint();
+ assert(!s.IsNotFound());
+ unflushed_save_points_->pop_back();
+ return s;
+ }
+
+ if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) {
+ // PessimisticTransaction::PopSavePoint will call also call PopSavePoint on
+ // write_batch_. However, write_batch_ is empty and has no savepoints
+ // because this savepoint has already been flushed. Work around this by
+ // setting a fake savepoint.
+ write_batch_.SetSavePoint();
+ Status s = PessimisticTransaction::PopSavePoint();
+ assert(!s.IsNotFound());
+ flushed_save_points_->pop_back();
+ return s;
+ }
+
+ return Status::NotFound();
+}
+
+void WriteUnpreparedTxn::MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input) {
+ SequenceNumber min_uncommitted, snap_seq;
+ const SnapshotBackup backed_by_snapshot =
+ wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
+ WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
+ unprep_seqs_, backed_by_snapshot);
+ write_batch_.MultiGetFromBatchAndDB(db_, options, column_family, num_keys,
+ keys, values, statuses, sorted_input,
+ &callback);
+ if (UNLIKELY(!callback.valid() ||
+ !wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
+ wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
+ for (size_t i = 0; i < num_keys; i++) {
+ statuses[i] = Status::TryAgain();
+ }
+ }
+}
+
+Status WriteUnpreparedTxn::Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* value) {
+ SequenceNumber min_uncommitted, snap_seq;
+ const SnapshotBackup backed_by_snapshot =
+ wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
+ WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
+ unprep_seqs_, backed_by_snapshot);
+ auto res = write_batch_.GetFromBatchAndDB(db_, options, column_family, key,
+ value, &callback);
+ if (LIKELY(callback.valid() &&
+ wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
+ return res;
+ } else {
+ wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
+ return Status::TryAgain();
+ }
+}
+
+namespace {
+static void CleanupWriteUnpreparedWBWIIterator(void* arg1, void* arg2) {
+ auto txn = reinterpret_cast<WriteUnpreparedTxn*>(arg1);
+ auto iter = reinterpret_cast<Iterator*>(arg2);
+ txn->RemoveActiveIterator(iter);
+}
+} // anonymous namespace
+
+Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options) {
+ return GetIterator(options, wupt_db_->DefaultColumnFamily());
+}
+
+Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) {
+ // Make sure to get iterator from WriteUnprepareTxnDB, not the root db.
+ Iterator* db_iter = wupt_db_->NewIterator(options, column_family, this);
+ assert(db_iter);
+
+ auto iter = write_batch_.NewIteratorWithBase(column_family, db_iter);
+ active_iterators_.push_back(iter);
+ iter->RegisterCleanup(CleanupWriteUnpreparedWBWIIterator, this, iter);
+ return iter;
+}
+
+Status WriteUnpreparedTxn::ValidateSnapshot(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ SequenceNumber* tracked_at_seq) {
+ // TODO(lth): Reduce duplicate code with WritePrepared ValidateSnapshot logic.
+ assert(snapshot_);
+
+ SequenceNumber min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(
+ snapshot_.get())
+ ->min_uncommitted_;
+ SequenceNumber snap_seq = snapshot_->GetSequenceNumber();
+ // tracked_at_seq is either max or the last snapshot with which this key was
+ // trackeed so there is no need to apply the IsInSnapshot to this comparison
+ // here as tracked_at_seq is not a prepare seq.
+ if (*tracked_at_seq <= snap_seq) {
+ // If the key has been previous validated at a sequence number earlier
+ // than the curent snapshot's sequence number, we already know it has not
+ // been modified.
+ return Status::OK();
+ }
+
+ *tracked_at_seq = snap_seq;
+
+ ColumnFamilyHandle* cfh =
+ column_family ? column_family : db_impl_->DefaultColumnFamily();
+
+ WriteUnpreparedTxnReadCallback snap_checker(
+ wupt_db_, snap_seq, min_uncommitted, unprep_seqs_, kBackedByDBSnapshot);
+ return TransactionUtil::CheckKeyForConflicts(db_impl_, cfh, key.ToString(),
+ snap_seq, false /* cache_only */,
+ &snap_checker, min_uncommitted);
+}
+
+const std::map<SequenceNumber, size_t>&
+WriteUnpreparedTxn::GetUnpreparedSequenceNumbers() {
+ return unprep_seqs_;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.h b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.h
new file mode 100644
index 00000000..30c8f4c5
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn.h
@@ -0,0 +1,341 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <set>
+
+#include "utilities/transactions/write_prepared_txn.h"
+#include "utilities/transactions/write_unprepared_txn_db.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteUnpreparedTxnDB;
+class WriteUnpreparedTxn;
+
+// WriteUnprepared transactions needs to be able to read their own uncommitted
+// writes, and supporting this requires some careful consideration. Because
+// writes in the current transaction may be flushed to DB already, we cannot
+// rely on the contents of WriteBatchWithIndex to determine whether a key should
+// be visible or not, so we have to remember to check the DB for any uncommitted
+// keys that should be visible to us. First, we will need to change the seek to
+// snapshot logic, to seek to max_visible_seq = max(snap_seq, max_unprep_seq).
+// Any key greater than max_visible_seq should not be visible because they
+// cannot be unprepared by the current transaction and they are not in its
+// snapshot.
+//
+// When we seek to max_visible_seq, one of these cases will happen:
+// 1. We hit a unprepared key from the current transaction.
+// 2. We hit a unprepared key from the another transaction.
+// 3. We hit a committed key with snap_seq < seq < max_unprep_seq.
+// 4. We hit a committed key with seq <= snap_seq.
+//
+// IsVisibleFullCheck handles all cases correctly.
+//
+// Other notes:
+// Note that max_visible_seq is only calculated once at iterator construction
+// time, meaning if the same transaction is adding more unprep seqs through
+// writes during iteration, these newer writes may not be visible. This is not a
+// problem for MySQL though because it avoids modifying the index as it is
+// scanning through it to avoid the Halloween Problem. Instead, it scans the
+// index once up front, and modifies based on a temporary copy.
+//
+// In DBIter, there is a "reseek" optimization if the iterator skips over too
+// many keys. However, this assumes that the reseek seeks exactly to the
+// required key. In write unprepared, even after seeking directly to
+// max_visible_seq, some iteration may be required before hitting a visible key,
+// and special precautions must be taken to avoid performing another reseek,
+// leading to an infinite loop.
+//
+class WriteUnpreparedTxnReadCallback : public ReadCallback {
+ public:
+ WriteUnpreparedTxnReadCallback(
+ WritePreparedTxnDB* db, SequenceNumber snapshot,
+ SequenceNumber min_uncommitted,
+ const std::map<SequenceNumber, size_t>& unprep_seqs,
+ SnapshotBackup backed_by_snapshot)
+ // Pass our last uncommitted seq as the snapshot to the parent class to
+ // ensure that the parent will not prematurely filter out own writes. We
+ // will do the exact comparison against snapshots in IsVisibleFullCheck
+ // override.
+ : ReadCallback(CalcMaxVisibleSeq(unprep_seqs, snapshot), min_uncommitted),
+ db_(db),
+ unprep_seqs_(unprep_seqs),
+ wup_snapshot_(snapshot),
+ backed_by_snapshot_(backed_by_snapshot) {
+ (void)backed_by_snapshot_; // to silence unused private field warning
+ }
+
+ virtual ~WriteUnpreparedTxnReadCallback() {
+ // If it is not backed by snapshot, the caller must check validity
+ assert(valid_checked_ || backed_by_snapshot_ == kBackedByDBSnapshot);
+ }
+
+ virtual bool IsVisibleFullCheck(SequenceNumber seq) override;
+
+ inline bool valid() {
+ valid_checked_ = true;
+ return snap_released_ == false;
+ }
+
+ void Refresh(SequenceNumber seq) override {
+ max_visible_seq_ = std::max(max_visible_seq_, seq);
+ wup_snapshot_ = seq;
+ }
+
+ static SequenceNumber CalcMaxVisibleSeq(
+ const std::map<SequenceNumber, size_t>& unprep_seqs,
+ SequenceNumber snapshot_seq) {
+ SequenceNumber max_unprepared = 0;
+ if (unprep_seqs.size()) {
+ max_unprepared =
+ unprep_seqs.rbegin()->first + unprep_seqs.rbegin()->second - 1;
+ }
+ return std::max(max_unprepared, snapshot_seq);
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ const std::map<SequenceNumber, size_t>& unprep_seqs_;
+ SequenceNumber wup_snapshot_;
+ // Whether max_visible_seq_ is backed by a snapshot
+ const SnapshotBackup backed_by_snapshot_;
+ bool snap_released_ = false;
+ // Safety check to ensure that the caller has checked invalid statuses
+ bool valid_checked_ = false;
+};
+
+class WriteUnpreparedTxn : public WritePreparedTxn {
+ public:
+ WriteUnpreparedTxn(WriteUnpreparedTxnDB* db,
+ const WriteOptions& write_options,
+ const TransactionOptions& txn_options);
+
+ virtual ~WriteUnpreparedTxn();
+
+ using TransactionBaseImpl::Put;
+ virtual Status Put(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value,
+ const bool assume_tracked = false) override;
+ virtual Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
+ const SliceParts& value,
+ const bool assume_tracked = false) override;
+
+ using TransactionBaseImpl::Merge;
+ virtual Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value,
+ const bool assume_tracked = false) override;
+
+ using TransactionBaseImpl::Delete;
+ virtual Status Delete(ColumnFamilyHandle* column_family, const Slice& key,
+ const bool assume_tracked = false) override;
+ virtual Status Delete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked = false) override;
+
+ using TransactionBaseImpl::SingleDelete;
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ const bool assume_tracked = false) override;
+ virtual Status SingleDelete(ColumnFamilyHandle* column_family,
+ const SliceParts& key,
+ const bool assume_tracked = false) override;
+
+ // In WriteUnprepared, untracked writes will break snapshot validation logic.
+ // Snapshot validation will only check the largest sequence number of a key to
+ // see if it was committed or not. However, an untracked unprepared write will
+ // hide smaller committed sequence numbers.
+ //
+ // TODO(lth): Investigate whether it is worth having snapshot validation
+ // validate all values larger than snap_seq. Otherwise, we should return
+ // Status::NotSupported for untracked writes.
+
+ virtual Status RebuildFromWriteBatch(WriteBatch*) override;
+
+ virtual uint64_t GetLastLogNumber() const override {
+ return last_log_number_;
+ }
+
+ void RemoveActiveIterator(Iterator* iter) {
+ active_iterators_.erase(
+ std::remove(active_iterators_.begin(), active_iterators_.end(), iter),
+ active_iterators_.end());
+ }
+
+ protected:
+ void Initialize(const TransactionOptions& txn_options) override;
+
+ Status PrepareInternal() override;
+
+ Status CommitWithoutPrepareInternal() override;
+ Status CommitInternal() override;
+
+ Status RollbackInternal() override;
+
+ void Clear() override;
+
+ void SetSavePoint() override;
+ Status RollbackToSavePoint() override;
+ Status PopSavePoint() override;
+
+ // Get and GetIterator needs to be overridden so that a ReadCallback to
+ // handle read-your-own-write is used.
+ using Transaction::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ using Transaction::MultiGet;
+ virtual void MultiGet(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys,
+ PinnableSlice* values, Status* statuses,
+ const bool sorted_input = false) override;
+
+ using Transaction::GetIterator;
+ virtual Iterator* GetIterator(const ReadOptions& options) override;
+ virtual Iterator* GetIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family) override;
+
+ virtual Status ValidateSnapshot(ColumnFamilyHandle* column_family,
+ const Slice& key,
+ SequenceNumber* tracked_at_seq) override;
+
+ private:
+ friend class WriteUnpreparedTransactionTest_ReadYourOwnWrite_Test;
+ friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
+ friend class WriteUnpreparedTransactionTest_UnpreparedBatch_Test;
+ friend class WriteUnpreparedTxnDB;
+
+ const std::map<SequenceNumber, size_t>& GetUnpreparedSequenceNumbers();
+ Status WriteRollbackKeys(const TransactionKeyMap& tracked_keys,
+ WriteBatchWithIndex* rollback_batch,
+ ReadCallback* callback, const ReadOptions& roptions);
+
+ Status MaybeFlushWriteBatchToDB();
+ Status FlushWriteBatchToDB(bool prepared);
+ Status FlushWriteBatchToDBInternal(bool prepared);
+ Status FlushWriteBatchWithSavePointToDB();
+ Status RollbackToSavePointInternal();
+ Status HandleWrite(std::function<Status()> do_write);
+
+ // For write unprepared, we check on every writebatch append to see if
+ // write_batch_flush_threshold_ has been exceeded, and then call
+ // FlushWriteBatchToDB if so. This logic is encapsulated in
+ // MaybeFlushWriteBatchToDB.
+ int64_t write_batch_flush_threshold_;
+ WriteUnpreparedTxnDB* wupt_db_;
+
+ // Ordered list of unprep_seq sequence numbers that we have already written
+ // to DB.
+ //
+ // This maps unprep_seq => prepare_batch_cnt for each unprepared batch
+ // written by this transaction.
+ //
+ // Note that this contains both prepared and unprepared batches, since they
+ // are treated similarily in prepare heap/commit map, so it simplifies the
+ // commit callbacks.
+ std::map<SequenceNumber, size_t> unprep_seqs_;
+
+ uint64_t last_log_number_;
+
+ // Recovered transactions have tracked_keys_ populated, but are not actually
+ // locked for efficiency reasons. For recovered transactions, skip unlocking
+ // keys when transaction ends.
+ bool recovered_txn_;
+
+ // Track the largest sequence number at which we performed snapshot
+ // validation. If snapshot validation was skipped because no snapshot was set,
+ // then this is set to GetLastPublishedSequence. This value is useful because
+ // it means that for keys that have unprepared seqnos, we can guarantee that
+ // no committed keys by other transactions can exist between
+ // largest_validated_seq_ and max_unprep_seq. See
+ // WriteUnpreparedTxnDB::NewIterator for an explanation for why this is
+ // necessary for iterator Prev().
+ //
+ // Currently this value only increases during the lifetime of a transaction,
+ // but in some cases, we should be able to restore the previously largest
+ // value when calling RollbackToSavepoint.
+ SequenceNumber largest_validated_seq_;
+
+ using KeySet = std::unordered_map<uint32_t, std::vector<std::string>>;
+ struct SavePoint {
+ // Record of unprep_seqs_ at this savepoint. The set of unprep_seq is
+ // used during RollbackToSavepoint to determine visibility when restoring
+ // old values.
+ //
+ // TODO(lth): Since all unprep_seqs_ sets further down the stack must be
+ // subsets, this can potentially be deduplicated by just storing set
+ // difference. Investigate if this is worth it.
+ std::map<SequenceNumber, size_t> unprep_seqs_;
+
+ // This snapshot will be used to read keys at this savepoint if we call
+ // RollbackToSavePoint.
+ std::unique_ptr<ManagedSnapshot> snapshot_;
+
+ SavePoint(const std::map<SequenceNumber, size_t>& seqs,
+ ManagedSnapshot* snapshot)
+ : unprep_seqs_(seqs), snapshot_(snapshot){};
+ };
+
+ // We have 3 data structures holding savepoint information:
+ // 1. TransactionBaseImpl::save_points_
+ // 2. WriteUnpreparedTxn::flushed_save_points_
+ // 3. WriteUnpreparecTxn::unflushed_save_points_
+ //
+ // TransactionBaseImpl::save_points_ holds information about all write
+ // batches, including the current in-memory write_batch_, or unprepared
+ // batches that have been written out. Its responsibility is just to track
+ // which keys have been modified in every savepoint.
+ //
+ // WriteUnpreparedTxn::flushed_save_points_ holds information about savepoints
+ // set on unprepared batches that have already flushed. It holds the snapshot
+ // and unprep_seqs at that savepoint, so that the rollback process can
+ // determine which keys were visible at that point in time.
+ //
+ // WriteUnpreparecTxn::unflushed_save_points_ holds information about
+ // savepoints on the current in-memory write_batch_. It simply records the
+ // size of the write batch at every savepoint.
+ //
+ // TODO(lth): Remove the redundancy between save_point_boundaries_ and
+ // write_batch_.save_points_.
+ //
+ // Based on this information, here are some invariants:
+ // size(unflushed_save_points_) = size(write_batch_.save_points_)
+ // size(flushed_save_points_) + size(unflushed_save_points_)
+ // = size(save_points_)
+ //
+ std::unique_ptr<autovector<WriteUnpreparedTxn::SavePoint>>
+ flushed_save_points_;
+ std::unique_ptr<autovector<size_t>> unflushed_save_points_;
+
+ // It is currently unsafe to flush a write batch if there are active iterators
+ // created from this transaction. This is because we use WriteBatchWithIndex
+ // to do merging reads from the DB and the write batch. If we flush the write
+ // batch, it is possible that the delta iterator on the iterator will point to
+ // invalid memory.
+ std::vector<Iterator*> active_iterators_;
+
+ // Untracked keys that we have to rollback.
+ //
+ // TODO(lth): Currently we we do not record untracked keys per-savepoint.
+ // This means that when rolling back to savepoints, we have to check all
+ // keys in the current transaction for rollback. Note that this is only
+ // inefficient, but still correct because we take a snapshot at every
+ // savepoint, and we will use that snapshot to construct the rollback batch.
+ // The rollback batch will then contain a reissue of the same marker.
+ //
+ // A more optimal solution would be to only check keys changed since the
+ // last savepoint. Also, it may make sense to merge this into tracked_keys_
+ // and differentiate between tracked but not locked keys to avoid having two
+ // very similar data structures.
+ KeySet untracked_keys_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.cc b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.cc
new file mode 100644
index 00000000..ca365d04
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.cc
@@ -0,0 +1,468 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/transactions/write_unprepared_txn_db.h"
+#include "db/arena_wrapped_db_iter.h"
+#include "rocksdb/utilities/transaction_db.h"
+#include "util/cast_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// Instead of reconstructing a Transaction object, and calling rollback on it,
+// we can be more efficient with RollbackRecoveredTransaction by skipping
+// unnecessary steps (eg. updating CommitMap, reconstructing keyset)
+Status WriteUnpreparedTxnDB::RollbackRecoveredTransaction(
+ const DBImpl::RecoveredTransaction* rtxn) {
+ // TODO(lth): Reduce duplicate code with WritePrepared rollback logic.
+ assert(rtxn->unprepared_);
+ auto cf_map_shared_ptr = WritePreparedTxnDB::GetCFHandleMap();
+ auto cf_comp_map_shared_ptr = WritePreparedTxnDB::GetCFComparatorMap();
+ // In theory we could write with disableWAL = true during recovery, and
+ // assume that if we crash again during recovery, we can just replay from
+ // the very beginning. Unfortunately, the XIDs from the application may not
+ // necessarily be unique across restarts, potentially leading to situations
+ // like this:
+ //
+ // BEGIN_PREPARE(unprepared) Put(a) END_PREPARE(xid = 1)
+ // -- crash and recover with Put(a) rolled back as it was not prepared
+ // BEGIN_PREPARE(prepared) Put(b) END_PREPARE(xid = 1)
+ // COMMIT(xid = 1)
+ // -- crash and recover with both a, b
+ //
+ // We could just write the rollback marker, but then we would have to extend
+ // MemTableInserter during recovery to actually do writes into the DB
+ // instead of just dropping the in-memory write batch.
+ //
+ WriteOptions w_options;
+
+ class InvalidSnapshotReadCallback : public ReadCallback {
+ public:
+ InvalidSnapshotReadCallback(SequenceNumber snapshot)
+ : ReadCallback(snapshot) {}
+
+ inline bool IsVisibleFullCheck(SequenceNumber) override {
+ // The seq provided as snapshot is the seq right before we have locked and
+ // wrote to it, so whatever is there, it is committed.
+ return true;
+ }
+
+ // Ignore the refresh request since we are confident that our snapshot seq
+ // is not going to be affected by concurrent compactions (not enabled yet.)
+ void Refresh(SequenceNumber) override {}
+ };
+
+ // Iterate starting with largest sequence number.
+ for (auto it = rtxn->batches_.rbegin(); it != rtxn->batches_.rend(); ++it) {
+ auto last_visible_txn = it->first - 1;
+ const auto& batch = it->second.batch_;
+ WriteBatch rollback_batch;
+
+ struct RollbackWriteBatchBuilder : public WriteBatch::Handler {
+ DBImpl* db_;
+ ReadOptions roptions;
+ InvalidSnapshotReadCallback callback;
+ WriteBatch* rollback_batch_;
+ std::map<uint32_t, const Comparator*>& comparators_;
+ std::map<uint32_t, ColumnFamilyHandle*>& handles_;
+ using CFKeys = std::set<Slice, SetComparator>;
+ std::map<uint32_t, CFKeys> keys_;
+ bool rollback_merge_operands_;
+ RollbackWriteBatchBuilder(
+ DBImpl* db, SequenceNumber snap_seq, WriteBatch* dst_batch,
+ std::map<uint32_t, const Comparator*>& comparators,
+ std::map<uint32_t, ColumnFamilyHandle*>& handles,
+ bool rollback_merge_operands)
+ : db_(db),
+ callback(snap_seq),
+ // disable min_uncommitted optimization
+ rollback_batch_(dst_batch),
+ comparators_(comparators),
+ handles_(handles),
+ rollback_merge_operands_(rollback_merge_operands) {}
+
+ Status Rollback(uint32_t cf, const Slice& key) {
+ Status s;
+ CFKeys& cf_keys = keys_[cf];
+ if (cf_keys.size() == 0) { // just inserted
+ auto cmp = comparators_[cf];
+ keys_[cf] = CFKeys(SetComparator(cmp));
+ }
+ auto res = cf_keys.insert(key);
+ if (res.second ==
+ false) { // second is false if a element already existed.
+ return s;
+ }
+
+ PinnableSlice pinnable_val;
+ bool not_used;
+ auto cf_handle = handles_[cf];
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = cf_handle;
+ get_impl_options.value = &pinnable_val;
+ get_impl_options.value_found = &not_used;
+ get_impl_options.callback = &callback;
+ s = db_->GetImpl(roptions, key, get_impl_options);
+ assert(s.ok() || s.IsNotFound());
+ if (s.ok()) {
+ s = rollback_batch_->Put(cf_handle, key, pinnable_val);
+ assert(s.ok());
+ } else if (s.IsNotFound()) {
+ // There has been no readable value before txn. By adding a delete we
+ // make sure that there will be none afterwards either.
+ s = rollback_batch_->Delete(cf_handle, key);
+ assert(s.ok());
+ } else {
+ // Unexpected status. Return it to the user.
+ }
+ return s;
+ }
+
+ Status PutCF(uint32_t cf, const Slice& key,
+ const Slice& /*val*/) override {
+ return Rollback(cf, key);
+ }
+
+ Status DeleteCF(uint32_t cf, const Slice& key) override {
+ return Rollback(cf, key);
+ }
+
+ Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
+ return Rollback(cf, key);
+ }
+
+ Status MergeCF(uint32_t cf, const Slice& key,
+ const Slice& /*val*/) override {
+ if (rollback_merge_operands_) {
+ return Rollback(cf, key);
+ } else {
+ return Status::OK();
+ }
+ }
+
+ // Recovered batches do not contain 2PC markers.
+ Status MarkNoop(bool) override { return Status::InvalidArgument(); }
+ Status MarkBeginPrepare(bool) override {
+ return Status::InvalidArgument();
+ }
+ Status MarkEndPrepare(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ Status MarkCommit(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ Status MarkRollback(const Slice&) override {
+ return Status::InvalidArgument();
+ }
+ } rollback_handler(db_impl_, last_visible_txn, &rollback_batch,
+ *cf_comp_map_shared_ptr.get(), *cf_map_shared_ptr.get(),
+ txn_db_options_.rollback_merge_operands);
+
+ auto s = batch->Iterate(&rollback_handler);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // The Rollback marker will be used as a batch separator
+ WriteBatchInternal::MarkRollback(&rollback_batch, rtxn->name_);
+
+ const uint64_t kNoLogRef = 0;
+ const bool kDisableMemtable = true;
+ const size_t kOneBatch = 1;
+ uint64_t seq_used = kMaxSequenceNumber;
+ s = db_impl_->WriteImpl(w_options, &rollback_batch, nullptr, nullptr,
+ kNoLogRef, !kDisableMemtable, &seq_used, kOneBatch);
+ if (!s.ok()) {
+ return s;
+ }
+
+ // If two_write_queues, we must manually release the sequence number to
+ // readers.
+ if (db_impl_->immutable_db_options().two_write_queues) {
+ db_impl_->SetLastPublishedSequence(seq_used);
+ }
+ }
+
+ return Status::OK();
+}
+
+Status WriteUnpreparedTxnDB::Initialize(
+ const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles) {
+ // TODO(lth): Reduce code duplication in this function.
+ auto dbimpl = static_cast_with_check<DBImpl, DB>(GetRootDB());
+ assert(dbimpl != nullptr);
+
+ db_impl_->SetSnapshotChecker(new WritePreparedSnapshotChecker(this));
+ // A callback to commit a single sub-batch
+ class CommitSubBatchPreReleaseCallback : public PreReleaseCallback {
+ public:
+ explicit CommitSubBatchPreReleaseCallback(WritePreparedTxnDB* db)
+ : db_(db) {}
+ Status Callback(SequenceNumber commit_seq,
+ bool is_mem_disabled __attribute__((__unused__)), uint64_t,
+ size_t /*index*/, size_t /*total*/) override {
+ assert(!is_mem_disabled);
+ db_->AddCommitted(commit_seq, commit_seq);
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ };
+ db_impl_->SetRecoverableStatePreReleaseCallback(
+ new CommitSubBatchPreReleaseCallback(this));
+
+ // PessimisticTransactionDB::Initialize
+ for (auto cf_ptr : handles) {
+ AddColumnFamily(cf_ptr);
+ }
+ // Verify cf options
+ for (auto handle : handles) {
+ ColumnFamilyDescriptor cfd;
+ Status s = handle->GetDescriptor(&cfd);
+ if (!s.ok()) {
+ return s;
+ }
+ s = VerifyCFOptions(cfd.options);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ // Re-enable compaction for the column families that initially had
+ // compaction enabled.
+ std::vector<ColumnFamilyHandle*> compaction_enabled_cf_handles;
+ compaction_enabled_cf_handles.reserve(compaction_enabled_cf_indices.size());
+ for (auto index : compaction_enabled_cf_indices) {
+ compaction_enabled_cf_handles.push_back(handles[index]);
+ }
+
+ // create 'real' transactions from recovered shell transactions
+ auto rtxns = dbimpl->recovered_transactions();
+ std::map<SequenceNumber, SequenceNumber> ordered_seq_cnt;
+ for (auto rtxn : rtxns) {
+ auto recovered_trx = rtxn.second;
+ assert(recovered_trx);
+ assert(recovered_trx->batches_.size() >= 1);
+ assert(recovered_trx->name_.length());
+
+ // We can only rollback transactions after AdvanceMaxEvictedSeq is called,
+ // but AddPrepared must occur before AdvanceMaxEvictedSeq, which is why
+ // two iterations is required.
+ if (recovered_trx->unprepared_) {
+ continue;
+ }
+
+ WriteOptions w_options;
+ w_options.sync = true;
+ TransactionOptions t_options;
+
+ auto first_log_number = recovered_trx->batches_.begin()->second.log_number_;
+ auto first_seq = recovered_trx->batches_.begin()->first;
+ auto last_prepare_batch_cnt =
+ recovered_trx->batches_.begin()->second.batch_cnt_;
+
+ Transaction* real_trx = BeginTransaction(w_options, t_options, nullptr);
+ assert(real_trx);
+ auto wupt =
+ static_cast_with_check<WriteUnpreparedTxn, Transaction>(real_trx);
+ wupt->recovered_txn_ = true;
+
+ real_trx->SetLogNumber(first_log_number);
+ real_trx->SetId(first_seq);
+ Status s = real_trx->SetName(recovered_trx->name_);
+ if (!s.ok()) {
+ return s;
+ }
+ wupt->prepare_batch_cnt_ = last_prepare_batch_cnt;
+
+ for (auto batch : recovered_trx->batches_) {
+ const auto& seq = batch.first;
+ const auto& batch_info = batch.second;
+ auto cnt = batch_info.batch_cnt_ ? batch_info.batch_cnt_ : 1;
+ assert(batch_info.log_number_);
+
+ ordered_seq_cnt[seq] = cnt;
+ assert(wupt->unprep_seqs_.count(seq) == 0);
+ wupt->unprep_seqs_[seq] = cnt;
+
+ s = wupt->RebuildFromWriteBatch(batch_info.batch_);
+ assert(s.ok());
+ if (!s.ok()) {
+ return s;
+ }
+ }
+
+ const bool kClear = true;
+ wupt->InitWriteBatch(kClear);
+
+ real_trx->SetState(Transaction::PREPARED);
+ if (!s.ok()) {
+ return s;
+ }
+ }
+ // AddPrepared must be called in order
+ for (auto seq_cnt : ordered_seq_cnt) {
+ auto seq = seq_cnt.first;
+ auto cnt = seq_cnt.second;
+ for (size_t i = 0; i < cnt; i++) {
+ AddPrepared(seq + i);
+ }
+ }
+
+ SequenceNumber prev_max = max_evicted_seq_;
+ SequenceNumber last_seq = db_impl_->GetLatestSequenceNumber();
+ AdvanceMaxEvictedSeq(prev_max, last_seq);
+ // Create a gap between max and the next snapshot. This simplifies the logic
+ // in IsInSnapshot by not having to consider the special case of max ==
+ // snapshot after recovery. This is tested in IsInSnapshotEmptyMapTest.
+ if (last_seq) {
+ db_impl_->versions_->SetLastAllocatedSequence(last_seq + 1);
+ db_impl_->versions_->SetLastSequence(last_seq + 1);
+ db_impl_->versions_->SetLastPublishedSequence(last_seq + 1);
+ }
+
+ Status s;
+ // Rollback unprepared transactions.
+ for (auto rtxn : rtxns) {
+ auto recovered_trx = rtxn.second;
+ if (recovered_trx->unprepared_) {
+ s = RollbackRecoveredTransaction(recovered_trx);
+ if (!s.ok()) {
+ return s;
+ }
+ continue;
+ }
+ }
+
+ if (s.ok()) {
+ dbimpl->DeleteAllRecoveredTransactions();
+
+ // Compaction should start only after max_evicted_seq_ is set AND recovered
+ // transactions are either added to PrepareHeap or rolled back.
+ s = EnableAutoCompaction(compaction_enabled_cf_handles);
+ }
+
+ return s;
+}
+
+Transaction* WriteUnpreparedTxnDB::BeginTransaction(
+ const WriteOptions& write_options, const TransactionOptions& txn_options,
+ Transaction* old_txn) {
+ if (old_txn != nullptr) {
+ ReinitializeTransaction(old_txn, write_options, txn_options);
+ return old_txn;
+ } else {
+ return new WriteUnpreparedTxn(this, write_options, txn_options);
+ }
+}
+
+// Struct to hold ownership of snapshot and read callback for iterator cleanup.
+struct WriteUnpreparedTxnDB::IteratorState {
+ IteratorState(WritePreparedTxnDB* txn_db, SequenceNumber sequence,
+ std::shared_ptr<ManagedSnapshot> s,
+ SequenceNumber min_uncommitted, WriteUnpreparedTxn* txn)
+ : callback(txn_db, sequence, min_uncommitted, txn->unprep_seqs_,
+ kBackedByDBSnapshot),
+ snapshot(s) {}
+ SequenceNumber MaxVisibleSeq() { return callback.max_visible_seq(); }
+
+ WriteUnpreparedTxnReadCallback callback;
+ std::shared_ptr<ManagedSnapshot> snapshot;
+};
+
+namespace {
+static void CleanupWriteUnpreparedTxnDBIterator(void* arg1, void* /*arg2*/) {
+ delete reinterpret_cast<WriteUnpreparedTxnDB::IteratorState*>(arg1);
+}
+} // anonymous namespace
+
+Iterator* WriteUnpreparedTxnDB::NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ WriteUnpreparedTxn* txn) {
+ // TODO(lth): Refactor so that this logic is shared with WritePrepared.
+ constexpr bool ALLOW_BLOB = true;
+ constexpr bool ALLOW_REFRESH = true;
+ std::shared_ptr<ManagedSnapshot> own_snapshot = nullptr;
+ SequenceNumber snapshot_seq = kMaxSequenceNumber;
+ SequenceNumber min_uncommitted = 0;
+
+ // Currently, the Prev() iterator logic does not work well without snapshot
+ // validation. The logic simply iterates through values of a key in
+ // ascending seqno order, stopping at the first non-visible value and
+ // returning the last visible value.
+ //
+ // For example, if snapshot sequence is 3, and we have the following keys:
+ // foo: v1 1
+ // foo: v2 2
+ // foo: v3 3
+ // foo: v4 4
+ // foo: v5 5
+ //
+ // Then 1, 2, 3 will be visible, but 4 will be non-visible, so we return v3,
+ // which is the last visible value.
+ //
+ // For unprepared transactions, if we have snap_seq = 3, but the current
+ // transaction has unprep_seq 5, then returning the first non-visible value
+ // would be incorrect, as we should return v5, and not v3. The problem is that
+ // there are committed values at snapshot_seq < commit_seq < unprep_seq.
+ //
+ // Snapshot validation can prevent this problem by ensuring that no committed
+ // values exist at snapshot_seq < commit_seq, and thus any value with a
+ // sequence number greater than snapshot_seq must be unprepared values. For
+ // example, if the transaction had a snapshot at 3, then snapshot validation
+ // would be performed during the Put(v5) call. It would find v4, and the Put
+ // would fail with snapshot validation failure.
+ //
+ // TODO(lth): Improve Prev() logic to continue iterating until
+ // max_visible_seq, and then return the last visible value, so that this
+ // restriction can be lifted.
+ const Snapshot* snapshot = nullptr;
+ if (options.snapshot == nullptr) {
+ snapshot = GetSnapshot();
+ own_snapshot = std::make_shared<ManagedSnapshot>(db_impl_, snapshot);
+ } else {
+ snapshot = options.snapshot;
+ }
+
+ snapshot_seq = snapshot->GetSequenceNumber();
+ assert(snapshot_seq != kMaxSequenceNumber);
+ // Iteration is safe as long as largest_validated_seq <= snapshot_seq. We are
+ // guaranteed that for keys that were modified by this transaction (and thus
+ // might have unprepared values), no committed values exist at
+ // largest_validated_seq < commit_seq (or the contrapositive: any committed
+ // value must exist at commit_seq <= largest_validated_seq). This implies
+ // that commit_seq <= largest_validated_seq <= snapshot_seq or commit_seq <=
+ // snapshot_seq. As explained above, the problem with Prev() only happens when
+ // snapshot_seq < commit_seq.
+ //
+ // For keys that were not modified by this transaction, largest_validated_seq_
+ // is meaningless, and Prev() should just work with the existing visibility
+ // logic.
+ if (txn->largest_validated_seq_ > snapshot->GetSequenceNumber() &&
+ !txn->unprep_seqs_.empty()) {
+ ROCKS_LOG_ERROR(info_log_,
+ "WriteUnprepared iterator creation failed since the "
+ "transaction has performed unvalidated writes");
+ return nullptr;
+ }
+ min_uncommitted =
+ static_cast_with_check<const SnapshotImpl, const Snapshot>(snapshot)
+ ->min_uncommitted_;
+
+ auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
+ auto* state =
+ new IteratorState(this, snapshot_seq, own_snapshot, min_uncommitted, txn);
+ auto* db_iter =
+ db_impl_->NewIteratorImpl(options, cfd, state->MaxVisibleSeq(),
+ &state->callback, !ALLOW_BLOB, !ALLOW_REFRESH);
+ db_iter->RegisterCleanup(CleanupWriteUnpreparedTxnDBIterator, state, nullptr);
+ return db_iter;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.h b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.h
new file mode 100644
index 00000000..ad8e40f9
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/transactions/write_unprepared_txn_db.h
@@ -0,0 +1,148 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+#ifndef ROCKSDB_LITE
+
+#include "utilities/transactions/write_prepared_txn_db.h"
+#include "utilities/transactions/write_unprepared_txn.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class WriteUnpreparedTxn;
+
+class WriteUnpreparedTxnDB : public WritePreparedTxnDB {
+ public:
+ using WritePreparedTxnDB::WritePreparedTxnDB;
+
+ Status Initialize(const std::vector<size_t>& compaction_enabled_cf_indices,
+ const std::vector<ColumnFamilyHandle*>& handles) override;
+
+ Transaction* BeginTransaction(const WriteOptions& write_options,
+ const TransactionOptions& txn_options,
+ Transaction* old_txn) override;
+
+ // Struct to hold ownership of snapshot and read callback for cleanup.
+ struct IteratorState;
+
+ using WritePreparedTxnDB::NewIterator;
+ Iterator* NewIterator(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ WriteUnpreparedTxn* txn);
+
+ private:
+ Status RollbackRecoveredTransaction(const DBImpl::RecoveredTransaction* rtxn);
+};
+
+class WriteUnpreparedCommitEntryPreReleaseCallback : public PreReleaseCallback {
+ // TODO(lth): Reduce code duplication with
+ // WritePreparedCommitEntryPreReleaseCallback
+ public:
+ // includes_data indicates that the commit also writes non-empty
+ // CommitTimeWriteBatch to memtable, which needs to be committed separately.
+ WriteUnpreparedCommitEntryPreReleaseCallback(
+ WritePreparedTxnDB* db, DBImpl* db_impl,
+ const std::map<SequenceNumber, size_t>& unprep_seqs,
+ size_t data_batch_cnt = 0, bool publish_seq = true)
+ : db_(db),
+ db_impl_(db_impl),
+ unprep_seqs_(unprep_seqs),
+ data_batch_cnt_(data_batch_cnt),
+ includes_data_(data_batch_cnt_ > 0),
+ publish_seq_(publish_seq) {
+ assert(unprep_seqs.size() > 0);
+ }
+
+ virtual Status Callback(SequenceNumber commit_seq,
+ bool is_mem_disabled __attribute__((__unused__)),
+ uint64_t, size_t /*index*/,
+ size_t /*total*/) override {
+ const uint64_t last_commit_seq = LIKELY(data_batch_cnt_ <= 1)
+ ? commit_seq
+ : commit_seq + data_batch_cnt_ - 1;
+ // Recall that unprep_seqs maps (un)prepared_seq => prepare_batch_cnt.
+ for (const auto& s : unprep_seqs_) {
+ for (size_t i = 0; i < s.second; i++) {
+ db_->AddCommitted(s.first + i, last_commit_seq);
+ }
+ }
+
+ if (includes_data_) {
+ assert(data_batch_cnt_);
+ // Commit the data that is accompanied with the commit request
+ for (size_t i = 0; i < data_batch_cnt_; i++) {
+ // For commit seq of each batch use the commit seq of the last batch.
+ // This would make debugging easier by having all the batches having
+ // the same sequence number.
+ db_->AddCommitted(commit_seq + i, last_commit_seq);
+ }
+ }
+ if (db_impl_->immutable_db_options().two_write_queues && publish_seq_) {
+ assert(is_mem_disabled); // implies the 2nd queue
+ // Publish the sequence number. We can do that here assuming the callback
+ // is invoked only from one write queue, which would guarantee that the
+ // publish sequence numbers will be in order, i.e., once a seq is
+ // published all the seq prior to that are also publishable.
+ db_impl_->SetLastPublishedSequence(last_commit_seq);
+ }
+ // else SequenceNumber that is updated as part of the write already does the
+ // publishing
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ DBImpl* db_impl_;
+ const std::map<SequenceNumber, size_t>& unprep_seqs_;
+ size_t data_batch_cnt_;
+ // Either because it is commit without prepare or it has a
+ // CommitTimeWriteBatch
+ bool includes_data_;
+ // Should the callback also publishes the commit seq number
+ bool publish_seq_;
+};
+
+class WriteUnpreparedRollbackPreReleaseCallback : public PreReleaseCallback {
+ // TODO(lth): Reduce code duplication with
+ // WritePreparedCommitEntryPreReleaseCallback
+ public:
+ WriteUnpreparedRollbackPreReleaseCallback(
+ WritePreparedTxnDB* db, DBImpl* db_impl,
+ const std::map<SequenceNumber, size_t>& unprep_seqs,
+ SequenceNumber rollback_seq)
+ : db_(db),
+ db_impl_(db_impl),
+ unprep_seqs_(unprep_seqs),
+ rollback_seq_(rollback_seq) {
+ assert(unprep_seqs.size() > 0);
+ assert(db_impl_->immutable_db_options().two_write_queues);
+ }
+
+ virtual Status Callback(SequenceNumber commit_seq,
+ bool is_mem_disabled __attribute__((__unused__)),
+ uint64_t, size_t /*index*/,
+ size_t /*total*/) override {
+ assert(is_mem_disabled); // implies the 2nd queue
+ const uint64_t last_commit_seq = commit_seq;
+ db_->AddCommitted(rollback_seq_, last_commit_seq);
+ // Recall that unprep_seqs maps (un)prepared_seq => prepare_batch_cnt.
+ for (const auto& s : unprep_seqs_) {
+ for (size_t i = 0; i < s.second; i++) {
+ db_->AddCommitted(s.first + i, last_commit_seq);
+ }
+ }
+ db_impl_->SetLastPublishedSequence(last_commit_seq);
+ return Status::OK();
+ }
+
+ private:
+ WritePreparedTxnDB* db_;
+ DBImpl* db_impl_;
+ const std::map<SequenceNumber, size_t>& unprep_seqs_;
+ SequenceNumber rollback_seq_;
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.cc b/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.cc
new file mode 100644
index 00000000..9ebaa247
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.cc
@@ -0,0 +1,335 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 ROCKSDB_LITE
+
+#include "utilities/ttl/db_ttl_impl.h"
+
+#include "db/write_batch_internal.h"
+#include "file/filename.h"
+#include "rocksdb/convenience.h"
+#include "rocksdb/env.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "util/coding.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+void DBWithTTLImpl::SanitizeOptions(int32_t ttl, ColumnFamilyOptions* options,
+ Env* env) {
+ if (options->compaction_filter) {
+ options->compaction_filter =
+ new TtlCompactionFilter(ttl, env, options->compaction_filter);
+ } else {
+ options->compaction_filter_factory =
+ std::shared_ptr<CompactionFilterFactory>(new TtlCompactionFilterFactory(
+ ttl, env, options->compaction_filter_factory));
+ }
+
+ if (options->merge_operator) {
+ options->merge_operator.reset(
+ new TtlMergeOperator(options->merge_operator, env));
+ }
+}
+
+// Open the db inside DBWithTTLImpl because options needs pointer to its ttl
+DBWithTTLImpl::DBWithTTLImpl(DB* db) : DBWithTTL(db), closed_(false) {}
+
+DBWithTTLImpl::~DBWithTTLImpl() {
+ if (!closed_) {
+ Close();
+ }
+}
+
+Status DBWithTTLImpl::Close() {
+ Status ret = Status::OK();
+ if (!closed_) {
+ Options default_options = GetOptions();
+ // Need to stop background compaction before getting rid of the filter
+ CancelAllBackgroundWork(db_, /* wait = */ true);
+ ret = db_->Close();
+ delete default_options.compaction_filter;
+ closed_ = true;
+ }
+ return ret;
+}
+
+Status UtilityDB::OpenTtlDB(const Options& options, const std::string& dbname,
+ StackableDB** dbptr, int32_t ttl, bool read_only) {
+ DBWithTTL* db;
+ Status s = DBWithTTL::Open(options, dbname, &db, ttl, read_only);
+ if (s.ok()) {
+ *dbptr = db;
+ } else {
+ *dbptr = nullptr;
+ }
+ return s;
+}
+
+Status DBWithTTL::Open(const Options& options, const std::string& dbname,
+ DBWithTTL** dbptr, int32_t ttl, bool read_only) {
+
+ DBOptions db_options(options);
+ ColumnFamilyOptions cf_options(options);
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(
+ ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
+ std::vector<ColumnFamilyHandle*> handles;
+ Status s = DBWithTTL::Open(db_options, dbname, column_families, &handles,
+ dbptr, {ttl}, read_only);
+ if (s.ok()) {
+ assert(handles.size() == 1);
+ // i can delete the handle since DBImpl is always holding a reference to
+ // default column family
+ delete handles[0];
+ }
+ return s;
+}
+
+Status DBWithTTL::Open(
+ const DBOptions& db_options, const std::string& dbname,
+ const std::vector<ColumnFamilyDescriptor>& column_families,
+ std::vector<ColumnFamilyHandle*>* handles, DBWithTTL** dbptr,
+ std::vector<int32_t> ttls, bool read_only) {
+
+ if (ttls.size() != column_families.size()) {
+ return Status::InvalidArgument(
+ "ttls size has to be the same as number of column families");
+ }
+
+ std::vector<ColumnFamilyDescriptor> column_families_sanitized =
+ column_families;
+ for (size_t i = 0; i < column_families_sanitized.size(); ++i) {
+ DBWithTTLImpl::SanitizeOptions(
+ ttls[i], &column_families_sanitized[i].options,
+ db_options.env == nullptr ? Env::Default() : db_options.env);
+ }
+ DB* db;
+
+ Status st;
+ if (read_only) {
+ st = DB::OpenForReadOnly(db_options, dbname, column_families_sanitized,
+ handles, &db);
+ } else {
+ st = DB::Open(db_options, dbname, column_families_sanitized, handles, &db);
+ }
+ if (st.ok()) {
+ *dbptr = new DBWithTTLImpl(db);
+ } else {
+ *dbptr = nullptr;
+ }
+ return st;
+}
+
+Status DBWithTTLImpl::CreateColumnFamilyWithTtl(
+ const ColumnFamilyOptions& options, const std::string& column_family_name,
+ ColumnFamilyHandle** handle, int ttl) {
+ ColumnFamilyOptions sanitized_options = options;
+ DBWithTTLImpl::SanitizeOptions(ttl, &sanitized_options, GetEnv());
+
+ return DBWithTTL::CreateColumnFamily(sanitized_options, column_family_name,
+ handle);
+}
+
+Status DBWithTTLImpl::CreateColumnFamily(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle) {
+ return CreateColumnFamilyWithTtl(options, column_family_name, handle, 0);
+}
+
+// Appends the current timestamp to the string.
+// Returns false if could not get the current_time, true if append succeeds
+Status DBWithTTLImpl::AppendTS(const Slice& val, std::string* val_with_ts,
+ Env* env) {
+ val_with_ts->reserve(kTSLength + val.size());
+ char ts_string[kTSLength];
+ int64_t curtime;
+ Status st = env->GetCurrentTime(&curtime);
+ if (!st.ok()) {
+ return st;
+ }
+ EncodeFixed32(ts_string, (int32_t)curtime);
+ val_with_ts->append(val.data(), val.size());
+ val_with_ts->append(ts_string, kTSLength);
+ return st;
+}
+
+// Returns corruption if the length of the string is lesser than timestamp, or
+// timestamp refers to a time lesser than ttl-feature release time
+Status DBWithTTLImpl::SanityCheckTimestamp(const Slice& str) {
+ if (str.size() < kTSLength) {
+ return Status::Corruption("Error: value's length less than timestamp's\n");
+ }
+ // Checks that TS is not lesser than kMinTimestamp
+ // Gaurds against corruption & normal database opened incorrectly in ttl mode
+ int32_t timestamp_value = DecodeFixed32(str.data() + str.size() - kTSLength);
+ if (timestamp_value < kMinTimestamp) {
+ return Status::Corruption("Error: Timestamp < ttl feature release time!\n");
+ }
+ return Status::OK();
+}
+
+// Checks if the string is stale or not according to TTl provided
+bool DBWithTTLImpl::IsStale(const Slice& value, int32_t ttl, Env* env) {
+ if (ttl <= 0) { // Data is fresh if TTL is non-positive
+ return false;
+ }
+ int64_t curtime;
+ if (!env->GetCurrentTime(&curtime).ok()) {
+ return false; // Treat the data as fresh if could not get current time
+ }
+ int32_t timestamp_value =
+ DecodeFixed32(value.data() + value.size() - kTSLength);
+ return (timestamp_value + ttl) < curtime;
+}
+
+// Strips the TS from the end of the slice
+Status DBWithTTLImpl::StripTS(PinnableSlice* pinnable_val) {
+ Status st;
+ if (pinnable_val->size() < kTSLength) {
+ return Status::Corruption("Bad timestamp in key-value");
+ }
+ // Erasing characters which hold the TS
+ pinnable_val->remove_suffix(kTSLength);
+ return st;
+}
+
+// Strips the TS from the end of the string
+Status DBWithTTLImpl::StripTS(std::string* str) {
+ Status st;
+ if (str->length() < kTSLength) {
+ return Status::Corruption("Bad timestamp in key-value");
+ }
+ // Erasing characters which hold the TS
+ str->erase(str->length() - kTSLength, kTSLength);
+ return st;
+}
+
+Status DBWithTTLImpl::Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& val) {
+ WriteBatch batch;
+ batch.Put(column_family, key, val);
+ return Write(options, &batch);
+}
+
+Status DBWithTTLImpl::Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) {
+ Status st = db_->Get(options, column_family, key, value);
+ if (!st.ok()) {
+ return st;
+ }
+ st = SanityCheckTimestamp(*value);
+ if (!st.ok()) {
+ return st;
+ }
+ return StripTS(value);
+}
+
+std::vector<Status> DBWithTTLImpl::MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys, std::vector<std::string>* values) {
+ auto statuses = db_->MultiGet(options, column_family, keys, values);
+ for (size_t i = 0; i < keys.size(); ++i) {
+ if (!statuses[i].ok()) {
+ continue;
+ }
+ statuses[i] = SanityCheckTimestamp((*values)[i]);
+ if (!statuses[i].ok()) {
+ continue;
+ }
+ statuses[i] = StripTS(&(*values)[i]);
+ }
+ return statuses;
+}
+
+bool DBWithTTLImpl::KeyMayExist(const ReadOptions& options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key, std::string* value,
+ bool* value_found) {
+ bool ret = db_->KeyMayExist(options, column_family, key, value, value_found);
+ if (ret && value != nullptr && value_found != nullptr && *value_found) {
+ if (!SanityCheckTimestamp(*value).ok() || !StripTS(value).ok()) {
+ return false;
+ }
+ }
+ return ret;
+}
+
+Status DBWithTTLImpl::Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) {
+ WriteBatch batch;
+ batch.Merge(column_family, key, value);
+ return Write(options, &batch);
+}
+
+Status DBWithTTLImpl::Write(const WriteOptions& opts, WriteBatch* updates) {
+ class Handler : public WriteBatch::Handler {
+ public:
+ explicit Handler(Env* env) : env_(env) {}
+ WriteBatch updates_ttl;
+ Status batch_rewrite_status;
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ std::string value_with_ts;
+ Status st = AppendTS(value, &value_with_ts, env_);
+ if (!st.ok()) {
+ batch_rewrite_status = st;
+ } else {
+ WriteBatchInternal::Put(&updates_ttl, column_family_id, key,
+ value_with_ts);
+ }
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ std::string value_with_ts;
+ Status st = AppendTS(value, &value_with_ts, env_);
+ if (!st.ok()) {
+ batch_rewrite_status = st;
+ } else {
+ WriteBatchInternal::Merge(&updates_ttl, column_family_id, key,
+ value_with_ts);
+ }
+ return Status::OK();
+ }
+ Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
+ WriteBatchInternal::Delete(&updates_ttl, column_family_id, key);
+ return Status::OK();
+ }
+ void LogData(const Slice& blob) override { updates_ttl.PutLogData(blob); }
+
+ private:
+ Env* env_;
+ };
+ Handler handler(GetEnv());
+ updates->Iterate(&handler);
+ if (!handler.batch_rewrite_status.ok()) {
+ return handler.batch_rewrite_status;
+ } else {
+ return db_->Write(opts, &(handler.updates_ttl));
+ }
+}
+
+Iterator* DBWithTTLImpl::NewIterator(const ReadOptions& opts,
+ ColumnFamilyHandle* column_family) {
+ return new TtlIterator(db_->NewIterator(opts, column_family));
+}
+
+void DBWithTTLImpl::SetTtl(ColumnFamilyHandle *h, int32_t ttl) {
+ std::shared_ptr<TtlCompactionFilterFactory> filter;
+ Options opts;
+ opts = GetOptions(h);
+ filter = std::static_pointer_cast<TtlCompactionFilterFactory>(
+ opts.compaction_filter_factory);
+ if (!filter)
+ return;
+ filter->SetTtl(ttl);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.h b/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.h
new file mode 100644
index 00000000..ab6063a4
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/ttl/db_ttl_impl.h
@@ -0,0 +1,361 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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
+
+#ifndef ROCKSDB_LITE
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "db/db_impl/db_impl.h"
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/db.h"
+#include "rocksdb/env.h"
+#include "rocksdb/merge_operator.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "rocksdb/utilities/utility_db.h"
+
+#ifdef _WIN32
+// Windows API macro interference
+#undef GetCurrentTime
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+class DBWithTTLImpl : public DBWithTTL {
+ public:
+ static void SanitizeOptions(int32_t ttl, ColumnFamilyOptions* options,
+ Env* env);
+
+ explicit DBWithTTLImpl(DB* db);
+
+ virtual ~DBWithTTLImpl();
+
+ virtual Status Close() override;
+
+ Status CreateColumnFamilyWithTtl(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle,
+ int ttl) override;
+
+ Status CreateColumnFamily(const ColumnFamilyOptions& options,
+ const std::string& column_family_name,
+ ColumnFamilyHandle** handle) override;
+
+ using StackableDB::Put;
+ virtual Status Put(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& val) override;
+
+ using StackableDB::Get;
+ virtual Status Get(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ PinnableSlice* value) override;
+
+ using StackableDB::MultiGet;
+ virtual std::vector<Status> MultiGet(
+ const ReadOptions& options,
+ const std::vector<ColumnFamilyHandle*>& column_family,
+ const std::vector<Slice>& keys,
+ std::vector<std::string>* values) override;
+
+ using StackableDB::KeyMayExist;
+ virtual bool KeyMayExist(const ReadOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ std::string* value,
+ bool* value_found = nullptr) override;
+
+ using StackableDB::Merge;
+ virtual Status Merge(const WriteOptions& options,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ const Slice& value) override;
+
+ virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override;
+
+ using StackableDB::NewIterator;
+ virtual Iterator* NewIterator(const ReadOptions& opts,
+ ColumnFamilyHandle* column_family) override;
+
+ virtual DB* GetBaseDB() override { return db_; }
+
+ static bool IsStale(const Slice& value, int32_t ttl, Env* env);
+
+ static Status AppendTS(const Slice& val, std::string* val_with_ts, Env* env);
+
+ static Status SanityCheckTimestamp(const Slice& str);
+
+ static Status StripTS(std::string* str);
+
+ static Status StripTS(PinnableSlice* str);
+
+ static const uint32_t kTSLength = sizeof(int32_t); // size of timestamp
+
+ static const int32_t kMinTimestamp = 1368146402; // 05/09/2013:5:40PM GMT-8
+
+ static const int32_t kMaxTimestamp = 2147483647; // 01/18/2038:7:14PM GMT-8
+
+ void SetTtl(int32_t ttl) override { SetTtl(DefaultColumnFamily(), ttl); }
+
+ void SetTtl(ColumnFamilyHandle *h, int32_t ttl) override;
+
+ private:
+ // remember whether the Close completes or not
+ bool closed_;
+};
+
+class TtlIterator : public Iterator {
+
+ public:
+ explicit TtlIterator(Iterator* iter) : iter_(iter) { assert(iter_); }
+
+ ~TtlIterator() { delete iter_; }
+
+ bool Valid() const override { return iter_->Valid(); }
+
+ void SeekToFirst() override { iter_->SeekToFirst(); }
+
+ void SeekToLast() override { iter_->SeekToLast(); }
+
+ void Seek(const Slice& target) override { iter_->Seek(target); }
+
+ void SeekForPrev(const Slice& target) override { iter_->SeekForPrev(target); }
+
+ void Next() override { iter_->Next(); }
+
+ void Prev() override { iter_->Prev(); }
+
+ Slice key() const override { return iter_->key(); }
+
+ int32_t timestamp() const {
+ return DecodeFixed32(iter_->value().data() + iter_->value().size() -
+ DBWithTTLImpl::kTSLength);
+ }
+
+ Slice value() const override {
+ // TODO: handle timestamp corruption like in general iterator semantics
+ assert(DBWithTTLImpl::SanityCheckTimestamp(iter_->value()).ok());
+ Slice trimmed_value = iter_->value();
+ trimmed_value.size_ -= DBWithTTLImpl::kTSLength;
+ return trimmed_value;
+ }
+
+ Status status() const override { return iter_->status(); }
+
+ private:
+ Iterator* iter_;
+};
+
+class TtlCompactionFilter : public CompactionFilter {
+ public:
+ TtlCompactionFilter(
+ int32_t ttl, Env* env, const CompactionFilter* user_comp_filter,
+ std::unique_ptr<const CompactionFilter> user_comp_filter_from_factory =
+ nullptr)
+ : ttl_(ttl),
+ env_(env),
+ user_comp_filter_(user_comp_filter),
+ user_comp_filter_from_factory_(
+ std::move(user_comp_filter_from_factory)) {
+ // Unlike the merge operator, compaction filter is necessary for TTL, hence
+ // this would be called even if user doesn't specify any compaction-filter
+ if (!user_comp_filter_) {
+ user_comp_filter_ = user_comp_filter_from_factory_.get();
+ }
+ }
+
+ virtual bool Filter(int level, const Slice& key, const Slice& old_val,
+ std::string* new_val, bool* value_changed) const
+ override {
+ if (DBWithTTLImpl::IsStale(old_val, ttl_, env_)) {
+ return true;
+ }
+ if (user_comp_filter_ == nullptr) {
+ return false;
+ }
+ assert(old_val.size() >= DBWithTTLImpl::kTSLength);
+ Slice old_val_without_ts(old_val.data(),
+ old_val.size() - DBWithTTLImpl::kTSLength);
+ if (user_comp_filter_->Filter(level, key, old_val_without_ts, new_val,
+ value_changed)) {
+ return true;
+ }
+ if (*value_changed) {
+ new_val->append(
+ old_val.data() + old_val.size() - DBWithTTLImpl::kTSLength,
+ DBWithTTLImpl::kTSLength);
+ }
+ return false;
+ }
+
+ virtual const char* Name() const override { return "Delete By TTL"; }
+
+ private:
+ int32_t ttl_;
+ Env* env_;
+ const CompactionFilter* user_comp_filter_;
+ std::unique_ptr<const CompactionFilter> user_comp_filter_from_factory_;
+};
+
+class TtlCompactionFilterFactory : public CompactionFilterFactory {
+ public:
+ TtlCompactionFilterFactory(
+ int32_t ttl, Env* env,
+ std::shared_ptr<CompactionFilterFactory> comp_filter_factory)
+ : ttl_(ttl), env_(env), user_comp_filter_factory_(comp_filter_factory) {}
+
+ virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& context) override {
+ std::unique_ptr<const CompactionFilter> user_comp_filter_from_factory =
+ nullptr;
+ if (user_comp_filter_factory_) {
+ user_comp_filter_from_factory =
+ user_comp_filter_factory_->CreateCompactionFilter(context);
+ }
+
+ return std::unique_ptr<TtlCompactionFilter>(new TtlCompactionFilter(
+ ttl_, env_, nullptr, std::move(user_comp_filter_from_factory)));
+ }
+
+ void SetTtl(int32_t ttl) {
+ ttl_ = ttl;
+ }
+
+ virtual const char* Name() const override {
+ return "TtlCompactionFilterFactory";
+ }
+
+ private:
+ int32_t ttl_;
+ Env* env_;
+ std::shared_ptr<CompactionFilterFactory> user_comp_filter_factory_;
+};
+
+class TtlMergeOperator : public MergeOperator {
+
+ public:
+ explicit TtlMergeOperator(const std::shared_ptr<MergeOperator>& merge_op,
+ Env* env)
+ : user_merge_op_(merge_op), env_(env) {
+ assert(merge_op);
+ assert(env);
+ }
+
+ virtual bool FullMergeV2(const MergeOperationInput& merge_in,
+ MergeOperationOutput* merge_out) const override {
+ const uint32_t ts_len = DBWithTTLImpl::kTSLength;
+ if (merge_in.existing_value && merge_in.existing_value->size() < ts_len) {
+ ROCKS_LOG_ERROR(merge_in.logger,
+ "Error: Could not remove timestamp from existing value.");
+ return false;
+ }
+
+ // Extract time-stamp from each operand to be passed to user_merge_op_
+ std::vector<Slice> operands_without_ts;
+ for (const auto& operand : merge_in.operand_list) {
+ if (operand.size() < ts_len) {
+ ROCKS_LOG_ERROR(
+ merge_in.logger,
+ "Error: Could not remove timestamp from operand value.");
+ return false;
+ }
+ operands_without_ts.push_back(operand);
+ operands_without_ts.back().remove_suffix(ts_len);
+ }
+
+ // Apply the user merge operator (store result in *new_value)
+ bool good = true;
+ MergeOperationOutput user_merge_out(merge_out->new_value,
+ merge_out->existing_operand);
+ if (merge_in.existing_value) {
+ Slice existing_value_without_ts(merge_in.existing_value->data(),
+ merge_in.existing_value->size() - ts_len);
+ good = user_merge_op_->FullMergeV2(
+ MergeOperationInput(merge_in.key, &existing_value_without_ts,
+ operands_without_ts, merge_in.logger),
+ &user_merge_out);
+ } else {
+ good = user_merge_op_->FullMergeV2(
+ MergeOperationInput(merge_in.key, nullptr, operands_without_ts,
+ merge_in.logger),
+ &user_merge_out);
+ }
+
+ // Return false if the user merge operator returned false
+ if (!good) {
+ return false;
+ }
+
+ if (merge_out->existing_operand.data()) {
+ merge_out->new_value.assign(merge_out->existing_operand.data(),
+ merge_out->existing_operand.size());
+ merge_out->existing_operand = Slice(nullptr, 0);
+ }
+
+ // Augment the *new_value with the ttl time-stamp
+ int64_t curtime;
+ if (!env_->GetCurrentTime(&curtime).ok()) {
+ ROCKS_LOG_ERROR(
+ merge_in.logger,
+ "Error: Could not get current time to be attached internally "
+ "to the new value.");
+ return false;
+ } else {
+ char ts_string[ts_len];
+ EncodeFixed32(ts_string, (int32_t)curtime);
+ merge_out->new_value.append(ts_string, ts_len);
+ return true;
+ }
+ }
+
+ virtual bool PartialMergeMulti(const Slice& key,
+ const std::deque<Slice>& operand_list,
+ std::string* new_value, Logger* logger) const
+ override {
+ const uint32_t ts_len = DBWithTTLImpl::kTSLength;
+ std::deque<Slice> operands_without_ts;
+
+ for (const auto& operand : operand_list) {
+ if (operand.size() < ts_len) {
+ ROCKS_LOG_ERROR(logger,
+ "Error: Could not remove timestamp from value.");
+ return false;
+ }
+
+ operands_without_ts.push_back(
+ Slice(operand.data(), operand.size() - ts_len));
+ }
+
+ // Apply the user partial-merge operator (store result in *new_value)
+ assert(new_value);
+ if (!user_merge_op_->PartialMergeMulti(key, operands_without_ts, new_value,
+ logger)) {
+ return false;
+ }
+
+ // Augment the *new_value with the ttl time-stamp
+ int64_t curtime;
+ if (!env_->GetCurrentTime(&curtime).ok()) {
+ ROCKS_LOG_ERROR(
+ logger,
+ "Error: Could not get current time to be attached internally "
+ "to the new value.");
+ return false;
+ } else {
+ char ts_string[ts_len];
+ EncodeFixed32(ts_string, (int32_t)curtime);
+ new_value->append(ts_string, ts_len);
+ return true;
+ }
+ }
+
+ virtual const char* Name() const override { return "Merge By TTL"; }
+
+ private:
+ std::shared_ptr<MergeOperator> user_merge_op_;
+ Env* env_;
+};
+} // namespace ROCKSDB_NAMESPACE
+#endif // ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/ttl/ttl_test.cc b/storage/rocksdb/rocksdb/utilities/ttl/ttl_test.cc
new file mode 100644
index 00000000..3960bc62
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/ttl/ttl_test.cc
@@ -0,0 +1,693 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+// 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 ROCKSDB_LITE
+
+#include <map>
+#include <memory>
+#include "rocksdb/compaction_filter.h"
+#include "rocksdb/utilities/db_ttl.h"
+#include "test_util/testharness.h"
+#include "util/string_util.h"
+#ifndef OS_WIN
+#include <unistd.h>
+#endif
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+
+typedef std::map<std::string, std::string> KVMap;
+
+enum BatchOperation { OP_PUT = 0, OP_DELETE = 1 };
+}
+
+class SpecialTimeEnv : public EnvWrapper {
+ public:
+ explicit SpecialTimeEnv(Env* base) : EnvWrapper(base) {
+ base->GetCurrentTime(&current_time_);
+ }
+
+ void Sleep(int64_t sleep_time) { current_time_ += sleep_time; }
+ Status GetCurrentTime(int64_t* current_time) override {
+ *current_time = current_time_;
+ return Status::OK();
+ }
+
+ private:
+ int64_t current_time_ = 0;
+};
+
+class TtlTest : public testing::Test {
+ public:
+ TtlTest() {
+ env_.reset(new SpecialTimeEnv(Env::Default()));
+ dbname_ = test::PerThreadDBPath("db_ttl");
+ options_.create_if_missing = true;
+ options_.env = env_.get();
+ // ensure that compaction is kicked in to always strip timestamp from kvs
+ options_.max_compaction_bytes = 1;
+ // compaction should take place always from level0 for determinism
+ db_ttl_ = nullptr;
+ DestroyDB(dbname_, Options());
+ }
+
+ ~TtlTest() override {
+ CloseTtl();
+ DestroyDB(dbname_, Options());
+ }
+
+ // Open database with TTL support when TTL not provided with db_ttl_ pointer
+ void OpenTtl() {
+ ASSERT_TRUE(db_ttl_ ==
+ nullptr); // db should be closed before opening again
+ ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_));
+ }
+
+ // Open database with TTL support when TTL provided with db_ttl_ pointer
+ void OpenTtl(int32_t ttl) {
+ ASSERT_TRUE(db_ttl_ == nullptr);
+ ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl));
+ }
+
+ // Open with TestFilter compaction filter
+ void OpenTtlWithTestCompaction(int32_t ttl) {
+ options_.compaction_filter_factory =
+ std::shared_ptr<CompactionFilterFactory>(
+ new TestFilterFactory(kSampleSize_, kNewValue_));
+ OpenTtl(ttl);
+ }
+
+ // Open database with TTL support in read_only mode
+ void OpenReadOnlyTtl(int32_t ttl) {
+ ASSERT_TRUE(db_ttl_ == nullptr);
+ ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl, true));
+ }
+
+ // Call db_ttl_->Close() before delete db_ttl_
+ void CloseTtl() { CloseTtlHelper(true); }
+
+ // No db_ttl_->Close() before delete db_ttl_
+ void CloseTtlNoDBClose() { CloseTtlHelper(false); }
+
+ void CloseTtlHelper(bool close_db) {
+ if (db_ttl_ != nullptr) {
+ if (close_db) {
+ db_ttl_->Close();
+ }
+ delete db_ttl_;
+ db_ttl_ = nullptr;
+ }
+ }
+
+ // Populates and returns a kv-map
+ void MakeKVMap(int64_t num_entries) {
+ kvmap_.clear();
+ int digits = 1;
+ for (int64_t dummy = num_entries; dummy /= 10; ++digits) {
+ }
+ int digits_in_i = 1;
+ for (int64_t i = 0; i < num_entries; i++) {
+ std::string key = "key";
+ std::string value = "value";
+ if (i % 10 == 0) {
+ digits_in_i++;
+ }
+ for(int j = digits_in_i; j < digits; j++) {
+ key.append("0");
+ value.append("0");
+ }
+ AppendNumberTo(&key, i);
+ AppendNumberTo(&value, i);
+ kvmap_[key] = value;
+ }
+ ASSERT_EQ(static_cast<int64_t>(kvmap_.size()),
+ num_entries); // check all insertions done
+ }
+
+ // Makes a write-batch with key-vals from kvmap_ and 'Write''s it
+ void MakePutWriteBatch(const BatchOperation* batch_ops, int64_t num_ops) {
+ ASSERT_LE(num_ops, static_cast<int64_t>(kvmap_.size()));
+ static WriteOptions wopts;
+ static FlushOptions flush_opts;
+ WriteBatch batch;
+ kv_it_ = kvmap_.begin();
+ for (int64_t i = 0; i < num_ops && kv_it_ != kvmap_.end(); i++, ++kv_it_) {
+ switch (batch_ops[i]) {
+ case OP_PUT:
+ batch.Put(kv_it_->first, kv_it_->second);
+ break;
+ case OP_DELETE:
+ batch.Delete(kv_it_->first);
+ break;
+ default:
+ FAIL();
+ }
+ }
+ db_ttl_->Write(wopts, &batch);
+ db_ttl_->Flush(flush_opts);
+ }
+
+ // Puts num_entries starting from start_pos_map from kvmap_ into the database
+ void PutValues(int64_t start_pos_map, int64_t num_entries, bool flush = true,
+ ColumnFamilyHandle* cf = nullptr) {
+ ASSERT_TRUE(db_ttl_);
+ ASSERT_LE(start_pos_map + num_entries, static_cast<int64_t>(kvmap_.size()));
+ static WriteOptions wopts;
+ static FlushOptions flush_opts;
+ kv_it_ = kvmap_.begin();
+ advance(kv_it_, start_pos_map);
+ for (int64_t i = 0; kv_it_ != kvmap_.end() && i < num_entries;
+ i++, ++kv_it_) {
+ ASSERT_OK(cf == nullptr
+ ? db_ttl_->Put(wopts, kv_it_->first, kv_it_->second)
+ : db_ttl_->Put(wopts, cf, kv_it_->first, kv_it_->second));
+ }
+ // Put a mock kv at the end because CompactionFilter doesn't delete last key
+ ASSERT_OK(cf == nullptr ? db_ttl_->Put(wopts, "keymock", "valuemock")
+ : db_ttl_->Put(wopts, cf, "keymock", "valuemock"));
+ if (flush) {
+ if (cf == nullptr) {
+ db_ttl_->Flush(flush_opts);
+ } else {
+ db_ttl_->Flush(flush_opts, cf);
+ }
+ }
+ }
+
+ // Runs a manual compaction
+ void ManualCompact(ColumnFamilyHandle* cf = nullptr) {
+ if (cf == nullptr) {
+ db_ttl_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
+ } else {
+ db_ttl_->CompactRange(CompactRangeOptions(), cf, nullptr, nullptr);
+ }
+ }
+
+ // checks the whole kvmap_ to return correct values using KeyMayExist
+ void SimpleKeyMayExistCheck() {
+ static ReadOptions ropts;
+ bool value_found;
+ std::string val;
+ for(auto &kv : kvmap_) {
+ bool ret = db_ttl_->KeyMayExist(ropts, kv.first, &val, &value_found);
+ if (ret == false || value_found == false) {
+ fprintf(stderr, "KeyMayExist could not find key=%s in the database but"
+ " should have\n", kv.first.c_str());
+ FAIL();
+ } else if (val.compare(kv.second) != 0) {
+ fprintf(stderr, " value for key=%s present in database is %s but"
+ " should be %s\n", kv.first.c_str(), val.c_str(),
+ kv.second.c_str());
+ FAIL();
+ }
+ }
+ }
+
+ // checks the whole kvmap_ to return correct values using MultiGet
+ void SimpleMultiGetTest() {
+ static ReadOptions ropts;
+ std::vector<Slice> keys;
+ std::vector<std::string> values;
+
+ for (auto& kv : kvmap_) {
+ keys.emplace_back(kv.first);
+ }
+
+ auto statuses = db_ttl_->MultiGet(ropts, keys, &values);
+ size_t i = 0;
+ for (auto& kv : kvmap_) {
+ ASSERT_OK(statuses[i]);
+ ASSERT_EQ(values[i], kv.second);
+ ++i;
+ }
+ }
+
+ // Sleeps for slp_tim then runs a manual compaction
+ // Checks span starting from st_pos from kvmap_ in the db and
+ // Gets should return true if check is true and false otherwise
+ // Also checks that value that we got is the same as inserted; and =kNewValue
+ // if test_compaction_change is true
+ void SleepCompactCheck(int slp_tim, int64_t st_pos, int64_t span,
+ bool check = true, bool test_compaction_change = false,
+ ColumnFamilyHandle* cf = nullptr) {
+ ASSERT_TRUE(db_ttl_);
+
+ env_->Sleep(slp_tim);
+ ManualCompact(cf);
+ static ReadOptions ropts;
+ kv_it_ = kvmap_.begin();
+ advance(kv_it_, st_pos);
+ std::string v;
+ for (int64_t i = 0; kv_it_ != kvmap_.end() && i < span; i++, ++kv_it_) {
+ Status s = (cf == nullptr) ? db_ttl_->Get(ropts, kv_it_->first, &v)
+ : db_ttl_->Get(ropts, cf, kv_it_->first, &v);
+ if (s.ok() != check) {
+ fprintf(stderr, "key=%s ", kv_it_->first.c_str());
+ if (!s.ok()) {
+ fprintf(stderr, "is absent from db but was expected to be present\n");
+ } else {
+ fprintf(stderr, "is present in db but was expected to be absent\n");
+ }
+ FAIL();
+ } else if (s.ok()) {
+ if (test_compaction_change && v.compare(kNewValue_) != 0) {
+ fprintf(stderr, " value for key=%s present in database is %s but "
+ " should be %s\n", kv_it_->first.c_str(), v.c_str(),
+ kNewValue_.c_str());
+ FAIL();
+ } else if (!test_compaction_change && v.compare(kv_it_->second) !=0) {
+ fprintf(stderr, " value for key=%s present in database is %s but "
+ " should be %s\n", kv_it_->first.c_str(), v.c_str(),
+ kv_it_->second.c_str());
+ FAIL();
+ }
+ }
+ }
+ }
+
+ // Similar as SleepCompactCheck but uses TtlIterator to read from db
+ void SleepCompactCheckIter(int slp, int st_pos, int64_t span,
+ bool check = true) {
+ ASSERT_TRUE(db_ttl_);
+ env_->Sleep(slp);
+ ManualCompact();
+ static ReadOptions ropts;
+ Iterator *dbiter = db_ttl_->NewIterator(ropts);
+ kv_it_ = kvmap_.begin();
+ advance(kv_it_, st_pos);
+
+ dbiter->Seek(kv_it_->first);
+ if (!check) {
+ if (dbiter->Valid()) {
+ ASSERT_NE(dbiter->value().compare(kv_it_->second), 0);
+ }
+ } else { // dbiter should have found out kvmap_[st_pos]
+ for (int64_t i = st_pos; kv_it_ != kvmap_.end() && i < st_pos + span;
+ i++, ++kv_it_) {
+ ASSERT_TRUE(dbiter->Valid());
+ ASSERT_EQ(dbiter->value().compare(kv_it_->second), 0);
+ dbiter->Next();
+ }
+ }
+ delete dbiter;
+ }
+
+ // Set ttl on open db
+ void SetTtl(int32_t ttl, ColumnFamilyHandle* cf = nullptr) {
+ ASSERT_TRUE(db_ttl_);
+ cf == nullptr ? db_ttl_->SetTtl(ttl) : db_ttl_->SetTtl(cf, ttl);
+ }
+
+ class TestFilter : public CompactionFilter {
+ public:
+ TestFilter(const int64_t kSampleSize, const std::string& kNewValue)
+ : kSampleSize_(kSampleSize),
+ kNewValue_(kNewValue) {
+ }
+
+ // Works on keys of the form "key<number>"
+ // Drops key if number at the end of key is in [0, kSampleSize_/3),
+ // Keeps key if it is in [kSampleSize_/3, 2*kSampleSize_/3),
+ // Change value if it is in [2*kSampleSize_/3, kSampleSize_)
+ // Eg. kSampleSize_=6. Drop:key0-1...Keep:key2-3...Change:key4-5...
+ bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
+ std::string* new_value, bool* value_changed) const override {
+ assert(new_value != nullptr);
+
+ std::string search_str = "0123456789";
+ std::string key_string = key.ToString();
+ size_t pos = key_string.find_first_of(search_str);
+ int num_key_end;
+ if (pos != std::string::npos) {
+ auto key_substr = key_string.substr(pos, key.size() - pos);
+#ifndef CYGWIN
+ num_key_end = std::stoi(key_substr);
+#else
+ num_key_end = std::strtol(key_substr.c_str(), 0, 10);
+#endif
+
+ } else {
+ return false; // Keep keys not matching the format "key<NUMBER>"
+ }
+
+ int64_t partition = kSampleSize_ / 3;
+ if (num_key_end < partition) {
+ return true;
+ } else if (num_key_end < partition * 2) {
+ return false;
+ } else {
+ *new_value = kNewValue_;
+ *value_changed = true;
+ return false;
+ }
+ }
+
+ const char* Name() const override { return "TestFilter"; }
+
+ private:
+ const int64_t kSampleSize_;
+ const std::string kNewValue_;
+ };
+
+ class TestFilterFactory : public CompactionFilterFactory {
+ public:
+ TestFilterFactory(const int64_t kSampleSize, const std::string& kNewValue)
+ : kSampleSize_(kSampleSize),
+ kNewValue_(kNewValue) {
+ }
+
+ std::unique_ptr<CompactionFilter> CreateCompactionFilter(
+ const CompactionFilter::Context& /*context*/) override {
+ return std::unique_ptr<CompactionFilter>(
+ new TestFilter(kSampleSize_, kNewValue_));
+ }
+
+ const char* Name() const override { return "TestFilterFactory"; }
+
+ private:
+ const int64_t kSampleSize_;
+ const std::string kNewValue_;
+ };
+
+
+ // Choose carefully so that Put, Gets & Compaction complete in 1 second buffer
+ static const int64_t kSampleSize_ = 100;
+ std::string dbname_;
+ DBWithTTL* db_ttl_;
+ std::unique_ptr<SpecialTimeEnv> env_;
+
+ private:
+ Options options_;
+ KVMap kvmap_;
+ KVMap::iterator kv_it_;
+ const std::string kNewValue_ = "new_value";
+ std::unique_ptr<CompactionFilter> test_comp_filter_;
+}; // class TtlTest
+
+// If TTL is non positive or not provided, the behaviour is TTL = infinity
+// This test opens the db 3 times with such default behavior and inserts a
+// bunch of kvs each time. All kvs should accumulate in the db till the end
+// Partitions the sample-size provided into 3 sets over boundary1 and boundary2
+TEST_F(TtlTest, NoEffect) {
+ MakeKVMap(kSampleSize_);
+ int64_t boundary1 = kSampleSize_ / 3;
+ int64_t boundary2 = 2 * boundary1;
+
+ OpenTtl();
+ PutValues(0, boundary1); //T=0: Set1 never deleted
+ SleepCompactCheck(1, 0, boundary1); //T=1: Set1 still there
+ CloseTtl();
+
+ OpenTtl(0);
+ PutValues(boundary1, boundary2 - boundary1); //T=1: Set2 never deleted
+ SleepCompactCheck(1, 0, boundary2); //T=2: Sets1 & 2 still there
+ CloseTtl();
+
+ OpenTtl(-1);
+ PutValues(boundary2, kSampleSize_ - boundary2); //T=3: Set3 never deleted
+ SleepCompactCheck(1, 0, kSampleSize_, true); //T=4: Sets 1,2,3 still there
+ CloseTtl();
+}
+
+// Rerun the NoEffect test with a different version of CloseTtl
+// function, where db is directly deleted without close.
+TEST_F(TtlTest, DestructWithoutClose) {
+ MakeKVMap(kSampleSize_);
+ int64_t boundary1 = kSampleSize_ / 3;
+ int64_t boundary2 = 2 * boundary1;
+
+ OpenTtl();
+ PutValues(0, boundary1); // T=0: Set1 never deleted
+ SleepCompactCheck(1, 0, boundary1); // T=1: Set1 still there
+ CloseTtlNoDBClose();
+
+ OpenTtl(0);
+ PutValues(boundary1, boundary2 - boundary1); // T=1: Set2 never deleted
+ SleepCompactCheck(1, 0, boundary2); // T=2: Sets1 & 2 still there
+ CloseTtlNoDBClose();
+
+ OpenTtl(-1);
+ PutValues(boundary2, kSampleSize_ - boundary2); // T=3: Set3 never deleted
+ SleepCompactCheck(1, 0, kSampleSize_, true); // T=4: Sets 1,2,3 still there
+ CloseTtlNoDBClose();
+}
+
+// Puts a set of values and checks its presence using Get during ttl
+TEST_F(TtlTest, PresentDuringTTL) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(2); // T=0:Open the db with ttl = 2
+ PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
+ SleepCompactCheck(1, 0, kSampleSize_, true); // T=1:Set1 should still be there
+ CloseTtl();
+}
+
+// Puts a set of values and checks its absence using Get after ttl
+TEST_F(TtlTest, AbsentAfterTTL) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1); // T=0:Open the db with ttl = 2
+ PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
+ SleepCompactCheck(2, 0, kSampleSize_, false); // T=2:Set1 should not be there
+ CloseTtl();
+}
+
+// Resets the timestamp of a set of kvs by updating them and checks that they
+// are not deleted according to the old timestamp
+TEST_F(TtlTest, ResetTimestamp) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(3);
+ PutValues(0, kSampleSize_); // T=0: Insert Set1. Delete at t=3
+ env_->Sleep(2); // T=2
+ PutValues(0, kSampleSize_); // T=2: Insert Set1. Delete at t=5
+ SleepCompactCheck(2, 0, kSampleSize_); // T=4: Set1 should still be there
+ CloseTtl();
+}
+
+// Similar to PresentDuringTTL but uses Iterator
+TEST_F(TtlTest, IterPresentDuringTTL) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(2);
+ PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
+ SleepCompactCheckIter(1, 0, kSampleSize_); // T=1: Set should be there
+ CloseTtl();
+}
+
+// Similar to AbsentAfterTTL but uses Iterator
+TEST_F(TtlTest, IterAbsentAfterTTL) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1);
+ PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
+ SleepCompactCheckIter(2, 0, kSampleSize_, false); // T=2: Should not be there
+ CloseTtl();
+}
+
+// Checks presence while opening the same db more than once with the same ttl
+// Note: The second open will open the same db
+TEST_F(TtlTest, MultiOpenSamePresent) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(2);
+ PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
+ CloseTtl();
+
+ OpenTtl(2); // T=0. Delete at t=2
+ SleepCompactCheck(1, 0, kSampleSize_); // T=1: Set should be there
+ CloseTtl();
+}
+
+// Checks absence while opening the same db more than once with the same ttl
+// Note: The second open will open the same db
+TEST_F(TtlTest, MultiOpenSameAbsent) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1);
+ PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
+ CloseTtl();
+
+ OpenTtl(1); // T=0.Delete at t=1
+ SleepCompactCheck(2, 0, kSampleSize_, false); // T=2: Set should not be there
+ CloseTtl();
+}
+
+// Checks presence while opening the same db more than once with bigger ttl
+TEST_F(TtlTest, MultiOpenDifferent) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1);
+ PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
+ CloseTtl();
+
+ OpenTtl(3); // T=0: Set deleted at t=3
+ SleepCompactCheck(2, 0, kSampleSize_); // T=2: Set should be there
+ CloseTtl();
+}
+
+// Checks presence during ttl in read_only mode
+TEST_F(TtlTest, ReadOnlyPresentForever) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1); // T=0:Open the db normally
+ PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
+ CloseTtl();
+
+ OpenReadOnlyTtl(1);
+ SleepCompactCheck(2, 0, kSampleSize_); // T=2:Set1 should still be there
+ CloseTtl();
+}
+
+// Checks whether WriteBatch works well with TTL
+// Puts all kvs in kvmap_ in a batch and writes first, then deletes first half
+TEST_F(TtlTest, WriteBatchTest) {
+ MakeKVMap(kSampleSize_);
+ BatchOperation batch_ops[kSampleSize_];
+ for (int i = 0; i < kSampleSize_; i++) {
+ batch_ops[i] = OP_PUT;
+ }
+
+ OpenTtl(2);
+ MakePutWriteBatch(batch_ops, kSampleSize_);
+ for (int i = 0; i < kSampleSize_ / 2; i++) {
+ batch_ops[i] = OP_DELETE;
+ }
+ MakePutWriteBatch(batch_ops, kSampleSize_ / 2);
+ SleepCompactCheck(0, 0, kSampleSize_ / 2, false);
+ SleepCompactCheck(0, kSampleSize_ / 2, kSampleSize_ - kSampleSize_ / 2);
+ CloseTtl();
+}
+
+// Checks user's compaction filter for correctness with TTL logic
+TEST_F(TtlTest, CompactionFilter) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtlWithTestCompaction(1);
+ PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
+ // T=2: TTL logic takes precedence over TestFilter:-Set1 should not be there
+ SleepCompactCheck(2, 0, kSampleSize_, false);
+ CloseTtl();
+
+ OpenTtlWithTestCompaction(3);
+ PutValues(0, kSampleSize_); // T=0:Insert Set1.
+ int64_t partition = kSampleSize_ / 3;
+ SleepCompactCheck(1, 0, partition, false); // Part dropped
+ SleepCompactCheck(0, partition, partition); // Part kept
+ SleepCompactCheck(0, 2 * partition, partition, true, true); // Part changed
+ CloseTtl();
+}
+
+// Insert some key-values which KeyMayExist should be able to get and check that
+// values returned are fine
+TEST_F(TtlTest, KeyMayExist) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl();
+ PutValues(0, kSampleSize_, false);
+
+ SimpleKeyMayExistCheck();
+
+ CloseTtl();
+}
+
+TEST_F(TtlTest, MultiGetTest) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl();
+ PutValues(0, kSampleSize_, false);
+
+ SimpleMultiGetTest();
+
+ CloseTtl();
+}
+
+TEST_F(TtlTest, ColumnFamiliesTest) {
+ DB* db;
+ Options options;
+ options.create_if_missing = true;
+ options.env = env_.get();
+
+ DB::Open(options, dbname_, &db);
+ ColumnFamilyHandle* handle;
+ ASSERT_OK(db->CreateColumnFamily(ColumnFamilyOptions(options),
+ "ttl_column_family", &handle));
+
+ delete handle;
+ delete db;
+
+ std::vector<ColumnFamilyDescriptor> column_families;
+ column_families.push_back(ColumnFamilyDescriptor(
+ kDefaultColumnFamilyName, ColumnFamilyOptions(options)));
+ column_families.push_back(ColumnFamilyDescriptor(
+ "ttl_column_family", ColumnFamilyOptions(options)));
+
+ std::vector<ColumnFamilyHandle*> handles;
+
+ ASSERT_OK(DBWithTTL::Open(DBOptions(options), dbname_, column_families,
+ &handles, &db_ttl_, {3, 5}, false));
+ ASSERT_EQ(handles.size(), 2U);
+ ColumnFamilyHandle* new_handle;
+ ASSERT_OK(db_ttl_->CreateColumnFamilyWithTtl(options, "ttl_column_family_2",
+ &new_handle, 2));
+ handles.push_back(new_handle);
+
+ MakeKVMap(kSampleSize_);
+ PutValues(0, kSampleSize_, false, handles[0]);
+ PutValues(0, kSampleSize_, false, handles[1]);
+ PutValues(0, kSampleSize_, false, handles[2]);
+
+ // everything should be there after 1 second
+ SleepCompactCheck(1, 0, kSampleSize_, true, false, handles[0]);
+ SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[1]);
+ SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[2]);
+
+ // only column family 1 should be alive after 4 seconds
+ SleepCompactCheck(3, 0, kSampleSize_, false, false, handles[0]);
+ SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[1]);
+ SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[2]);
+
+ // nothing should be there after 6 seconds
+ SleepCompactCheck(2, 0, kSampleSize_, false, false, handles[0]);
+ SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[1]);
+ SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[2]);
+
+ for (auto h : handles) {
+ delete h;
+ }
+ delete db_ttl_;
+ db_ttl_ = nullptr;
+}
+
+// Puts a set of values and checks its absence using Get after ttl
+TEST_F(TtlTest, ChangeTtlOnOpenDb) {
+ MakeKVMap(kSampleSize_);
+
+ OpenTtl(1); // T=0:Open the db with ttl = 2
+ SetTtl(3);
+ // @lint-ignore TXT2 T25377293 Grandfathered in
+ PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
+ SleepCompactCheck(2, 0, kSampleSize_, true); // T=2:Set1 should be there
+ CloseTtl();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+// A black-box test for the ttl wrapper around rocksdb
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main(int /*argc*/, char** /*argv*/) {
+ fprintf(stderr, "SKIPPED as DBWithTTL is not supported in ROCKSDB_LITE\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/util_merge_operators_test.cc b/storage/rocksdb/rocksdb/utilities/util_merge_operators_test.cc
new file mode 100644
index 00000000..3b043ea2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/util_merge_operators_test.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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<std::string> operands,
+ std::string key = "") {
+ std::string result;
+ Slice result_operand(nullptr, 0);
+
+ Slice existing_value_slice(existing_value);
+ std::vector<Slice> 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<std::string> operands,
+ std::string key = "") {
+ std::string result;
+ Slice result_operand(nullptr, 0);
+
+ std::vector<Slice> 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<std::string> operands,
+ std::string key = "") {
+ std::string result;
+ std::deque<Slice> operands_slice(operands.begin(), operands.end());
+
+ merge_operator_->PartialMergeMulti(key, operands_slice, &result, nullptr);
+ return result;
+ }
+
+ protected:
+ std::shared_ptr<MergeOperator> 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) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index.cc b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index.cc
new file mode 100644
index 00000000..2df6bcaf
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index.cc
@@ -0,0 +1,1065 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/write_batch_with_index.h"
+
+#include <memory>
+
+#include "db/column_family.h"
+#include "db/db_impl/db_impl.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "memory/arena.h"
+#include "memtable/skiplist.h"
+#include "options/db_options.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/iterator.h"
+#include "util/cast_util.h"
+#include "util/string_util.h"
+#include "utilities/write_batch_with_index/write_batch_with_index_internal.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+// when direction == forward
+// * current_at_base_ <=> base_iterator > delta_iterator
+// when direction == backwards
+// * current_at_base_ <=> base_iterator < delta_iterator
+// always:
+// * equal_keys_ <=> base_iterator == delta_iterator
+class BaseDeltaIterator : public Iterator {
+ public:
+ BaseDeltaIterator(Iterator* base_iterator, WBWIIterator* delta_iterator,
+ const Comparator* comparator,
+ const ReadOptions* read_options = nullptr)
+ : forward_(true),
+ current_at_base_(true),
+ equal_keys_(false),
+ status_(Status::OK()),
+ base_iterator_(base_iterator),
+ delta_iterator_(delta_iterator),
+ comparator_(comparator),
+ iterate_upper_bound_(read_options ? read_options->iterate_upper_bound
+ : nullptr) {}
+
+ ~BaseDeltaIterator() override {}
+
+ bool Valid() const override {
+ return current_at_base_ ? BaseValid() : DeltaValid();
+ }
+
+ void SeekToFirst() override {
+ forward_ = true;
+ base_iterator_->SeekToFirst();
+ delta_iterator_->SeekToFirst();
+ UpdateCurrent();
+ }
+
+ void SeekToLast() override {
+ forward_ = false;
+ base_iterator_->SeekToLast();
+ delta_iterator_->SeekToLast();
+ UpdateCurrent();
+ }
+
+ void Seek(const Slice& k) override {
+ forward_ = true;
+ base_iterator_->Seek(k);
+ delta_iterator_->Seek(k);
+ UpdateCurrent();
+ }
+
+ void SeekForPrev(const Slice& k) override {
+ forward_ = false;
+ base_iterator_->SeekForPrev(k);
+ delta_iterator_->SeekForPrev(k);
+ UpdateCurrent();
+ }
+
+ void Next() override {
+ if (!Valid()) {
+ status_ = Status::NotSupported("Next() on invalid iterator");
+ return;
+ }
+
+ if (!forward_) {
+ // Need to change direction
+ // if our direction was backward and we're not equal, we have two states:
+ // * both iterators are valid: we're already in a good state (current
+ // shows to smaller)
+ // * only one iterator is valid: we need to advance that iterator
+ forward_ = true;
+ equal_keys_ = false;
+ if (!BaseValid()) {
+ assert(DeltaValid());
+ base_iterator_->SeekToFirst();
+ } else if (!DeltaValid()) {
+ delta_iterator_->SeekToFirst();
+ } else if (current_at_base_) {
+ // Change delta from larger than base to smaller
+ AdvanceDelta();
+ } else {
+ // Change base from larger than delta to smaller
+ AdvanceBase();
+ }
+ if (DeltaValid() && BaseValid()) {
+ if (comparator_->Equal(delta_iterator_->Entry().key,
+ base_iterator_->key())) {
+ equal_keys_ = true;
+ }
+ }
+ }
+ Advance();
+ }
+
+ void Prev() override {
+ if (!Valid()) {
+ status_ = Status::NotSupported("Prev() on invalid iterator");
+ return;
+ }
+
+ if (forward_) {
+ // Need to change direction
+ // if our direction was backward and we're not equal, we have two states:
+ // * both iterators are valid: we're already in a good state (current
+ // shows to smaller)
+ // * only one iterator is valid: we need to advance that iterator
+ forward_ = false;
+ equal_keys_ = false;
+ if (!BaseValid()) {
+ assert(DeltaValid());
+ base_iterator_->SeekToLast();
+ } else if (!DeltaValid()) {
+ delta_iterator_->SeekToLast();
+ } else if (current_at_base_) {
+ // Change delta from less advanced than base to more advanced
+ AdvanceDelta();
+ } else {
+ // Change base from less advanced than delta to more advanced
+ AdvanceBase();
+ }
+ if (DeltaValid() && BaseValid()) {
+ if (comparator_->Equal(delta_iterator_->Entry().key,
+ base_iterator_->key())) {
+ equal_keys_ = true;
+ }
+ }
+ }
+
+ Advance();
+ }
+
+ Slice key() const override {
+ return current_at_base_ ? base_iterator_->key()
+ : delta_iterator_->Entry().key;
+ }
+
+ Slice value() const override {
+ return current_at_base_ ? base_iterator_->value()
+ : delta_iterator_->Entry().value;
+ }
+
+ Status status() const override {
+ if (!status_.ok()) {
+ return status_;
+ }
+ if (!base_iterator_->status().ok()) {
+ return base_iterator_->status();
+ }
+ return delta_iterator_->status();
+ }
+
+ private:
+ void AssertInvariants() {
+#ifndef NDEBUG
+ bool not_ok = false;
+ if (!base_iterator_->status().ok()) {
+ assert(!base_iterator_->Valid());
+ not_ok = true;
+ }
+ if (!delta_iterator_->status().ok()) {
+ assert(!delta_iterator_->Valid());
+ not_ok = true;
+ }
+ if (not_ok) {
+ assert(!Valid());
+ assert(!status().ok());
+ return;
+ }
+
+ if (!Valid()) {
+ return;
+ }
+ if (!BaseValid()) {
+ assert(!current_at_base_ && delta_iterator_->Valid());
+ return;
+ }
+ if (!DeltaValid()) {
+ assert(current_at_base_ && base_iterator_->Valid());
+ return;
+ }
+ // we don't support those yet
+ assert(delta_iterator_->Entry().type != kMergeRecord &&
+ delta_iterator_->Entry().type != kLogDataRecord);
+ int compare = comparator_->Compare(delta_iterator_->Entry().key,
+ base_iterator_->key());
+ if (forward_) {
+ // current_at_base -> compare < 0
+ assert(!current_at_base_ || compare < 0);
+ // !current_at_base -> compare <= 0
+ assert(current_at_base_ && compare >= 0);
+ } else {
+ // current_at_base -> compare > 0
+ assert(!current_at_base_ || compare > 0);
+ // !current_at_base -> compare <= 0
+ assert(current_at_base_ && compare <= 0);
+ }
+ // equal_keys_ <=> compare == 0
+ assert((equal_keys_ || compare != 0) && (!equal_keys_ || compare == 0));
+#endif
+ }
+
+ void Advance() {
+ if (equal_keys_) {
+ assert(BaseValid() && DeltaValid());
+ AdvanceBase();
+ AdvanceDelta();
+ } else {
+ if (current_at_base_) {
+ assert(BaseValid());
+ AdvanceBase();
+ } else {
+ assert(DeltaValid());
+ AdvanceDelta();
+ }
+ }
+ UpdateCurrent();
+ }
+
+ void AdvanceDelta() {
+ if (forward_) {
+ delta_iterator_->Next();
+ } else {
+ delta_iterator_->Prev();
+ }
+ }
+ void AdvanceBase() {
+ if (forward_) {
+ base_iterator_->Next();
+ } else {
+ base_iterator_->Prev();
+ }
+ }
+ bool BaseValid() const { return base_iterator_->Valid(); }
+ bool DeltaValid() const { return delta_iterator_->Valid(); }
+ void UpdateCurrent() {
+// Suppress false positive clang analyzer warnings.
+#ifndef __clang_analyzer__
+ status_ = Status::OK();
+ while (true) {
+ WriteEntry delta_entry;
+ if (DeltaValid()) {
+ assert(delta_iterator_->status().ok());
+ delta_entry = delta_iterator_->Entry();
+ } else if (!delta_iterator_->status().ok()) {
+ // Expose the error status and stop.
+ current_at_base_ = false;
+ return;
+ }
+ equal_keys_ = false;
+ if (!BaseValid()) {
+ if (!base_iterator_->status().ok()) {
+ // Expose the error status and stop.
+ current_at_base_ = true;
+ return;
+ }
+
+ // Base has finished.
+ if (!DeltaValid()) {
+ // Finished
+ return;
+ }
+ if (iterate_upper_bound_) {
+ if (comparator_->Compare(delta_entry.key, *iterate_upper_bound_) >=
+ 0) {
+ // out of upper bound -> finished.
+ return;
+ }
+ }
+ if (delta_entry.type == kDeleteRecord ||
+ delta_entry.type == kSingleDeleteRecord) {
+ AdvanceDelta();
+ } else {
+ current_at_base_ = false;
+ return;
+ }
+ } else if (!DeltaValid()) {
+ // Delta has finished.
+ current_at_base_ = true;
+ return;
+ } else {
+ int compare =
+ (forward_ ? 1 : -1) *
+ comparator_->Compare(delta_entry.key, base_iterator_->key());
+ if (compare <= 0) { // delta bigger or equal
+ if (compare == 0) {
+ equal_keys_ = true;
+ }
+ if (delta_entry.type != kDeleteRecord &&
+ delta_entry.type != kSingleDeleteRecord) {
+ current_at_base_ = false;
+ return;
+ }
+ // Delta is less advanced and is delete.
+ AdvanceDelta();
+ if (equal_keys_) {
+ AdvanceBase();
+ }
+ } else {
+ current_at_base_ = true;
+ return;
+ }
+ }
+ }
+
+ AssertInvariants();
+#endif // __clang_analyzer__
+ }
+
+ bool forward_;
+ bool current_at_base_;
+ bool equal_keys_;
+ Status status_;
+ std::unique_ptr<Iterator> base_iterator_;
+ std::unique_ptr<WBWIIterator> delta_iterator_;
+ const Comparator* comparator_; // not owned
+ const Slice* iterate_upper_bound_;
+};
+
+typedef SkipList<WriteBatchIndexEntry*, const WriteBatchEntryComparator&>
+ WriteBatchEntrySkipList;
+
+class WBWIIteratorImpl : public WBWIIterator {
+ public:
+ WBWIIteratorImpl(uint32_t column_family_id,
+ WriteBatchEntrySkipList* skip_list,
+ const ReadableWriteBatch* write_batch)
+ : column_family_id_(column_family_id),
+ skip_list_iter_(skip_list),
+ write_batch_(write_batch) {}
+
+ ~WBWIIteratorImpl() override {}
+
+ bool Valid() const override {
+ if (!skip_list_iter_.Valid()) {
+ return false;
+ }
+ const WriteBatchIndexEntry* iter_entry = skip_list_iter_.key();
+ return (iter_entry != nullptr &&
+ iter_entry->column_family == column_family_id_);
+ }
+
+ void SeekToFirst() override {
+ WriteBatchIndexEntry search_entry(
+ nullptr /* search_key */, column_family_id_,
+ true /* is_forward_direction */, true /* is_seek_to_first */);
+ skip_list_iter_.Seek(&search_entry);
+ }
+
+ void SeekToLast() override {
+ WriteBatchIndexEntry search_entry(
+ nullptr /* search_key */, column_family_id_ + 1,
+ true /* is_forward_direction */, true /* is_seek_to_first */);
+ skip_list_iter_.Seek(&search_entry);
+ if (!skip_list_iter_.Valid()) {
+ skip_list_iter_.SeekToLast();
+ } else {
+ skip_list_iter_.Prev();
+ }
+ }
+
+ void Seek(const Slice& key) override {
+ WriteBatchIndexEntry search_entry(&key, column_family_id_,
+ true /* is_forward_direction */,
+ false /* is_seek_to_first */);
+ skip_list_iter_.Seek(&search_entry);
+ }
+
+ void SeekForPrev(const Slice& key) override {
+ WriteBatchIndexEntry search_entry(&key, column_family_id_,
+ false /* is_forward_direction */,
+ false /* is_seek_to_first */);
+ skip_list_iter_.SeekForPrev(&search_entry);
+ }
+
+ void Next() override { skip_list_iter_.Next(); }
+
+ void Prev() override { skip_list_iter_.Prev(); }
+
+ WriteEntry Entry() const override {
+ WriteEntry ret;
+ Slice blob, xid;
+ const WriteBatchIndexEntry* iter_entry = skip_list_iter_.key();
+ // this is guaranteed with Valid()
+ assert(iter_entry != nullptr &&
+ iter_entry->column_family == column_family_id_);
+ auto s = write_batch_->GetEntryFromDataOffset(
+ iter_entry->offset, &ret.type, &ret.key, &ret.value, &blob, &xid);
+ assert(s.ok());
+ assert(ret.type == kPutRecord || ret.type == kDeleteRecord ||
+ ret.type == kSingleDeleteRecord || ret.type == kDeleteRangeRecord ||
+ ret.type == kMergeRecord);
+ return ret;
+ }
+
+ Status status() const override {
+ // this is in-memory data structure, so the only way status can be non-ok is
+ // through memory corruption
+ return Status::OK();
+ }
+
+ const WriteBatchIndexEntry* GetRawEntry() const {
+ return skip_list_iter_.key();
+ }
+
+ private:
+ uint32_t column_family_id_;
+ WriteBatchEntrySkipList::Iterator skip_list_iter_;
+ const ReadableWriteBatch* write_batch_;
+};
+
+struct WriteBatchWithIndex::Rep {
+ explicit Rep(const Comparator* index_comparator, size_t reserved_bytes = 0,
+ size_t max_bytes = 0, bool _overwrite_key = false)
+ : write_batch(reserved_bytes, max_bytes),
+ comparator(index_comparator, &write_batch),
+ skip_list(comparator, &arena),
+ overwrite_key(_overwrite_key),
+ last_entry_offset(0),
+ last_sub_batch_offset(0),
+ sub_batch_cnt(1) {}
+ ReadableWriteBatch write_batch;
+ WriteBatchEntryComparator comparator;
+ Arena arena;
+ WriteBatchEntrySkipList skip_list;
+ bool overwrite_key;
+ size_t last_entry_offset;
+ // The starting offset of the last sub-batch. A sub-batch starts right before
+ // inserting a key that is a duplicate of a key in the last sub-batch. Zero,
+ // the default, means that no duplicate key is detected so far.
+ size_t last_sub_batch_offset;
+ // Total number of sub-batches in the write batch. Default is 1.
+ size_t sub_batch_cnt;
+
+ // Remember current offset of internal write batch, which is used as
+ // the starting offset of the next record.
+ void SetLastEntryOffset() { last_entry_offset = write_batch.GetDataSize(); }
+
+ // In overwrite mode, find the existing entry for the same key and update it
+ // to point to the current entry.
+ // Return true if the key is found and updated.
+ bool UpdateExistingEntry(ColumnFamilyHandle* column_family, const Slice& key);
+ bool UpdateExistingEntryWithCfId(uint32_t column_family_id, const Slice& key);
+
+ // Add the recent entry to the update.
+ // In overwrite mode, if key already exists in the index, update it.
+ void AddOrUpdateIndex(ColumnFamilyHandle* column_family, const Slice& key);
+ void AddOrUpdateIndex(const Slice& key);
+
+ // Allocate an index entry pointing to the last entry in the write batch and
+ // put it to skip list.
+ void AddNewEntry(uint32_t column_family_id);
+
+ // Clear all updates buffered in this batch.
+ void Clear();
+ void ClearIndex();
+
+ // Rebuild index by reading all records from the batch.
+ // Returns non-ok status on corruption.
+ Status ReBuildIndex();
+};
+
+bool WriteBatchWithIndex::Rep::UpdateExistingEntry(
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ uint32_t cf_id = GetColumnFamilyID(column_family);
+ return UpdateExistingEntryWithCfId(cf_id, key);
+}
+
+bool WriteBatchWithIndex::Rep::UpdateExistingEntryWithCfId(
+ uint32_t column_family_id, const Slice& key) {
+ if (!overwrite_key) {
+ return false;
+ }
+
+ WBWIIteratorImpl iter(column_family_id, &skip_list, &write_batch);
+ iter.Seek(key);
+ if (!iter.Valid()) {
+ return false;
+ }
+ if (comparator.CompareKey(column_family_id, key, iter.Entry().key) != 0) {
+ return false;
+ }
+ WriteBatchIndexEntry* non_const_entry =
+ const_cast<WriteBatchIndexEntry*>(iter.GetRawEntry());
+ if (LIKELY(last_sub_batch_offset <= non_const_entry->offset)) {
+ last_sub_batch_offset = last_entry_offset;
+ sub_batch_cnt++;
+ }
+ non_const_entry->offset = last_entry_offset;
+ return true;
+}
+
+void WriteBatchWithIndex::Rep::AddOrUpdateIndex(
+ ColumnFamilyHandle* column_family, const Slice& key) {
+ if (!UpdateExistingEntry(column_family, key)) {
+ uint32_t cf_id = GetColumnFamilyID(column_family);
+ const auto* cf_cmp = GetColumnFamilyUserComparator(column_family);
+ if (cf_cmp != nullptr) {
+ comparator.SetComparatorForCF(cf_id, cf_cmp);
+ }
+ AddNewEntry(cf_id);
+ }
+}
+
+void WriteBatchWithIndex::Rep::AddOrUpdateIndex(const Slice& key) {
+ if (!UpdateExistingEntryWithCfId(0, key)) {
+ AddNewEntry(0);
+ }
+}
+
+void WriteBatchWithIndex::Rep::AddNewEntry(uint32_t column_family_id) {
+ const std::string& wb_data = write_batch.Data();
+ Slice entry_ptr = Slice(wb_data.data() + last_entry_offset,
+ wb_data.size() - last_entry_offset);
+ // Extract key
+ Slice key;
+ bool success __attribute__((__unused__));
+ success =
+ ReadKeyFromWriteBatchEntry(&entry_ptr, &key, column_family_id != 0);
+ assert(success);
+
+ auto* mem = arena.Allocate(sizeof(WriteBatchIndexEntry));
+ auto* index_entry =
+ new (mem) WriteBatchIndexEntry(last_entry_offset, column_family_id,
+ key.data() - wb_data.data(), key.size());
+ skip_list.Insert(index_entry);
+}
+
+void WriteBatchWithIndex::Rep::Clear() {
+ write_batch.Clear();
+ ClearIndex();
+}
+
+void WriteBatchWithIndex::Rep::ClearIndex() {
+ skip_list.~WriteBatchEntrySkipList();
+ arena.~Arena();
+ new (&arena) Arena();
+ new (&skip_list) WriteBatchEntrySkipList(comparator, &arena);
+ last_entry_offset = 0;
+ last_sub_batch_offset = 0;
+ sub_batch_cnt = 1;
+}
+
+Status WriteBatchWithIndex::Rep::ReBuildIndex() {
+ Status s;
+
+ ClearIndex();
+
+ if (write_batch.Count() == 0) {
+ // Nothing to re-index
+ return s;
+ }
+
+ size_t offset = WriteBatchInternal::GetFirstOffset(&write_batch);
+
+ Slice input(write_batch.Data());
+ input.remove_prefix(offset);
+
+ // Loop through all entries in Rep and add each one to the index
+ uint32_t found = 0;
+ while (s.ok() && !input.empty()) {
+ Slice key, value, blob, xid;
+ uint32_t column_family_id = 0; // default
+ char tag = 0;
+
+ // set offset of current entry for call to AddNewEntry()
+ last_entry_offset = input.data() - write_batch.Data().data();
+
+ s = ReadRecordFromWriteBatch(&input, &tag, &column_family_id, &key,
+ &value, &blob, &xid);
+ if (!s.ok()) {
+ break;
+ }
+
+ switch (tag) {
+ case kTypeColumnFamilyValue:
+ case kTypeValue:
+ case kTypeColumnFamilyDeletion:
+ case kTypeDeletion:
+ case kTypeColumnFamilySingleDeletion:
+ case kTypeSingleDeletion:
+ case kTypeColumnFamilyMerge:
+ case kTypeMerge:
+ found++;
+ if (!UpdateExistingEntryWithCfId(column_family_id, key)) {
+ AddNewEntry(column_family_id);
+ }
+ break;
+ case kTypeLogData:
+ case kTypeBeginPrepareXID:
+ case kTypeBeginPersistedPrepareXID:
+ case kTypeBeginUnprepareXID:
+ case kTypeEndPrepareXID:
+ case kTypeCommitXID:
+ case kTypeRollbackXID:
+ case kTypeNoop:
+ break;
+ default:
+ return Status::Corruption("unknown WriteBatch tag in ReBuildIndex",
+ ToString(static_cast<unsigned int>(tag)));
+ }
+ }
+
+ if (s.ok() && found != write_batch.Count()) {
+ s = Status::Corruption("WriteBatch has wrong count");
+ }
+
+ return s;
+}
+
+WriteBatchWithIndex::WriteBatchWithIndex(
+ const Comparator* default_index_comparator, size_t reserved_bytes,
+ bool overwrite_key, size_t max_bytes)
+ : rep(new Rep(default_index_comparator, reserved_bytes, max_bytes,
+ overwrite_key)) {}
+
+WriteBatchWithIndex::~WriteBatchWithIndex() {}
+
+WriteBatchWithIndex::WriteBatchWithIndex(WriteBatchWithIndex&&) = default;
+
+WriteBatchWithIndex& WriteBatchWithIndex::operator=(WriteBatchWithIndex&&) =
+ default;
+
+WriteBatch* WriteBatchWithIndex::GetWriteBatch() { return &rep->write_batch; }
+
+size_t WriteBatchWithIndex::SubBatchCnt() { return rep->sub_batch_cnt; }
+
+WBWIIterator* WriteBatchWithIndex::NewIterator() {
+ return new WBWIIteratorImpl(0, &(rep->skip_list), &rep->write_batch);
+}
+
+WBWIIterator* WriteBatchWithIndex::NewIterator(
+ ColumnFamilyHandle* column_family) {
+ return new WBWIIteratorImpl(GetColumnFamilyID(column_family),
+ &(rep->skip_list), &rep->write_batch);
+}
+
+Iterator* WriteBatchWithIndex::NewIteratorWithBase(
+ ColumnFamilyHandle* column_family, Iterator* base_iterator,
+ const ReadOptions* read_options) {
+ if (rep->overwrite_key == false) {
+ assert(false);
+ return nullptr;
+ }
+ return new BaseDeltaIterator(base_iterator, NewIterator(column_family),
+ GetColumnFamilyUserComparator(column_family),
+ read_options);
+}
+
+Iterator* WriteBatchWithIndex::NewIteratorWithBase(Iterator* base_iterator) {
+ if (rep->overwrite_key == false) {
+ assert(false);
+ return nullptr;
+ }
+ // default column family's comparator
+ return new BaseDeltaIterator(base_iterator, NewIterator(),
+ rep->comparator.default_comparator());
+}
+
+Status WriteBatchWithIndex::Put(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Put(column_family, key, value);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(column_family, key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::Put(const Slice& key, const Slice& value) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Put(key, value);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::Delete(ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Delete(column_family, key);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(column_family, key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::Delete(const Slice& key) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Delete(key);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::SingleDelete(ColumnFamilyHandle* column_family,
+ const Slice& key) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.SingleDelete(column_family, key);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(column_family, key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::SingleDelete(const Slice& key) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.SingleDelete(key);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::Merge(ColumnFamilyHandle* column_family,
+ const Slice& key, const Slice& value) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Merge(column_family, key, value);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(column_family, key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::Merge(const Slice& key, const Slice& value) {
+ rep->SetLastEntryOffset();
+ auto s = rep->write_batch.Merge(key, value);
+ if (s.ok()) {
+ rep->AddOrUpdateIndex(key);
+ }
+ return s;
+}
+
+Status WriteBatchWithIndex::PutLogData(const Slice& blob) {
+ return rep->write_batch.PutLogData(blob);
+}
+
+void WriteBatchWithIndex::Clear() { rep->Clear(); }
+
+Status WriteBatchWithIndex::GetFromBatch(ColumnFamilyHandle* column_family,
+ const DBOptions& options,
+ const Slice& key, std::string* value) {
+ Status s;
+ MergeContext merge_context;
+ const ImmutableDBOptions immuable_db_options(options);
+
+ WriteBatchWithIndexInternal::Result result =
+ WriteBatchWithIndexInternal::GetFromBatch(
+ immuable_db_options, this, column_family, key, &merge_context,
+ &rep->comparator, value, rep->overwrite_key, &s);
+
+ switch (result) {
+ case WriteBatchWithIndexInternal::Result::kFound:
+ case WriteBatchWithIndexInternal::Result::kError:
+ // use returned status
+ break;
+ case WriteBatchWithIndexInternal::Result::kDeleted:
+ case WriteBatchWithIndexInternal::Result::kNotFound:
+ s = Status::NotFound();
+ break;
+ case WriteBatchWithIndexInternal::Result::kMergeInProgress:
+ s = Status::MergeInProgress();
+ break;
+ default:
+ assert(false);
+ }
+
+ return s;
+}
+
+Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
+ const ReadOptions& read_options,
+ const Slice& key,
+ std::string* value) {
+ assert(value != nullptr);
+ PinnableSlice pinnable_val(value);
+ assert(!pinnable_val.IsPinned());
+ auto s = GetFromBatchAndDB(db, read_options, db->DefaultColumnFamily(), key,
+ &pinnable_val);
+ if (s.ok() && pinnable_val.IsPinned()) {
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ } // else value is already assigned
+ return s;
+}
+
+Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
+ const ReadOptions& read_options,
+ const Slice& key,
+ PinnableSlice* pinnable_val) {
+ return GetFromBatchAndDB(db, read_options, db->DefaultColumnFamily(), key,
+ pinnable_val);
+}
+
+Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
+ const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key,
+ std::string* value) {
+ assert(value != nullptr);
+ PinnableSlice pinnable_val(value);
+ assert(!pinnable_val.IsPinned());
+ auto s =
+ GetFromBatchAndDB(db, read_options, column_family, key, &pinnable_val);
+ if (s.ok() && pinnable_val.IsPinned()) {
+ value->assign(pinnable_val.data(), pinnable_val.size());
+ } // else value is already assigned
+ return s;
+}
+
+Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
+ const ReadOptions& read_options,
+ ColumnFamilyHandle* column_family,
+ const Slice& key,
+ PinnableSlice* pinnable_val) {
+ return GetFromBatchAndDB(db, read_options, column_family, key, pinnable_val,
+ nullptr);
+}
+
+Status WriteBatchWithIndex::GetFromBatchAndDB(
+ DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ const Slice& key, PinnableSlice* pinnable_val, ReadCallback* callback) {
+ Status s;
+ MergeContext merge_context;
+ const ImmutableDBOptions& immuable_db_options =
+ static_cast_with_check<DBImpl, DB>(db->GetRootDB())
+ ->immutable_db_options();
+
+ // Since the lifetime of the WriteBatch is the same as that of the transaction
+ // we cannot pin it as otherwise the returned value will not be available
+ // after the transaction finishes.
+ std::string& batch_value = *pinnable_val->GetSelf();
+ WriteBatchWithIndexInternal::Result result =
+ WriteBatchWithIndexInternal::GetFromBatch(
+ immuable_db_options, this, column_family, key, &merge_context,
+ &rep->comparator, &batch_value, rep->overwrite_key, &s);
+
+ if (result == WriteBatchWithIndexInternal::Result::kFound) {
+ pinnable_val->PinSelf();
+ return s;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kDeleted) {
+ return Status::NotFound();
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kError) {
+ return s;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kMergeInProgress &&
+ rep->overwrite_key == true) {
+ // Since we've overwritten keys, we do not know what other operations are
+ // in this batch for this key, so we cannot do a Merge to compute the
+ // result. Instead, we will simply return MergeInProgress.
+ return Status::MergeInProgress();
+ }
+
+ assert(result == WriteBatchWithIndexInternal::Result::kMergeInProgress ||
+ result == WriteBatchWithIndexInternal::Result::kNotFound);
+
+ // Did not find key in batch OR could not resolve Merges. Try DB.
+ if (!callback) {
+ s = db->Get(read_options, column_family, key, pinnable_val);
+ } else {
+ DBImpl::GetImplOptions get_impl_options;
+ get_impl_options.column_family = column_family;
+ get_impl_options.value = pinnable_val;
+ get_impl_options.callback = callback;
+ s = static_cast_with_check<DBImpl, DB>(db->GetRootDB())
+ ->GetImpl(read_options, key, get_impl_options);
+ }
+
+ if (s.ok() || s.IsNotFound()) { // DB Get Succeeded
+ if (result == WriteBatchWithIndexInternal::Result::kMergeInProgress) {
+ // Merge result from DB with merges in Batch
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ const MergeOperator* merge_operator =
+ cfh->cfd()->ioptions()->merge_operator;
+ Statistics* statistics = immuable_db_options.statistics.get();
+ Env* env = immuable_db_options.env;
+ Logger* logger = immuable_db_options.info_log.get();
+
+ Slice* merge_data;
+ if (s.ok()) {
+ merge_data = pinnable_val;
+ } else { // Key not present in db (s.IsNotFound())
+ merge_data = nullptr;
+ }
+
+ if (merge_operator) {
+ std::string merge_result;
+ s = MergeHelper::TimedFullMerge(merge_operator, key, merge_data,
+ merge_context.GetOperands(),
+ &merge_result, logger, statistics, env);
+ pinnable_val->Reset();
+ *pinnable_val->GetSelf() = std::move(merge_result);
+ pinnable_val->PinSelf();
+ } else {
+ s = Status::InvalidArgument("Options::merge_operator must be set");
+ }
+ }
+ }
+
+ return s;
+}
+
+void WriteBatchWithIndex::MultiGetFromBatchAndDB(
+ DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys, PinnableSlice* values,
+ Status* statuses, bool sorted_input) {
+ MultiGetFromBatchAndDB(db, read_options, column_family, num_keys, keys,
+ values, statuses, sorted_input, nullptr);
+}
+
+void WriteBatchWithIndex::MultiGetFromBatchAndDB(
+ DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
+ const size_t num_keys, const Slice* keys, PinnableSlice* values,
+ Status* statuses, bool sorted_input, ReadCallback* callback) {
+ const ImmutableDBOptions& immuable_db_options =
+ static_cast_with_check<DBImpl, DB>(db->GetRootDB())
+ ->immutable_db_options();
+
+ autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
+ autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
+ // To hold merges from the write batch
+ autovector<std::pair<WriteBatchWithIndexInternal::Result, MergeContext>,
+ MultiGetContext::MAX_BATCH_SIZE>
+ merges;
+ // Since the lifetime of the WriteBatch is the same as that of the transaction
+ // we cannot pin it as otherwise the returned value will not be available
+ // after the transaction finishes.
+ for (size_t i = 0; i < num_keys; ++i) {
+ MergeContext merge_context;
+ PinnableSlice* pinnable_val = &values[i];
+ std::string& batch_value = *pinnable_val->GetSelf();
+ Status* s = &statuses[i];
+ WriteBatchWithIndexInternal::Result result =
+ WriteBatchWithIndexInternal::GetFromBatch(
+ immuable_db_options, this, column_family, keys[i], &merge_context,
+ &rep->comparator, &batch_value, rep->overwrite_key, s);
+
+ if (result == WriteBatchWithIndexInternal::Result::kFound) {
+ pinnable_val->PinSelf();
+ continue;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kDeleted) {
+ *s = Status::NotFound();
+ continue;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kError) {
+ continue;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kMergeInProgress &&
+ rep->overwrite_key == true) {
+ // Since we've overwritten keys, we do not know what other operations are
+ // in this batch for this key, so we cannot do a Merge to compute the
+ // result. Instead, we will simply return MergeInProgress.
+ *s = Status::MergeInProgress();
+ continue;
+ }
+
+ assert(result == WriteBatchWithIndexInternal::Result::kMergeInProgress ||
+ result == WriteBatchWithIndexInternal::Result::kNotFound);
+ key_context.emplace_back(column_family, keys[i], &values[i], &statuses[i]);
+ merges.emplace_back(result, std::move(merge_context));
+ }
+
+ for (KeyContext& key : key_context) {
+ sorted_keys.emplace_back(&key);
+ }
+
+ // Did not find key in batch OR could not resolve Merges. Try DB.
+ static_cast_with_check<DBImpl, DB>(db->GetRootDB())
+ ->PrepareMultiGetKeys(key_context.size(), sorted_input, &sorted_keys);
+ static_cast_with_check<DBImpl, DB>(db->GetRootDB())
+ ->MultiGetWithCallback(read_options, column_family, callback,
+ &sorted_keys);
+
+ ColumnFamilyHandleImpl* cfh =
+ reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ const MergeOperator* merge_operator = cfh->cfd()->ioptions()->merge_operator;
+ for (auto iter = key_context.begin(); iter != key_context.end(); ++iter) {
+ KeyContext& key = *iter;
+ if (key.s->ok() || key.s->IsNotFound()) { // DB Get Succeeded
+ size_t index = iter - key_context.begin();
+ std::pair<WriteBatchWithIndexInternal::Result, MergeContext>&
+ merge_result = merges[index];
+ if (merge_result.first ==
+ WriteBatchWithIndexInternal::Result::kMergeInProgress) {
+ // Merge result from DB with merges in Batch
+ Statistics* statistics = immuable_db_options.statistics.get();
+ Env* env = immuable_db_options.env;
+ Logger* logger = immuable_db_options.info_log.get();
+
+ Slice* merge_data;
+ if (key.s->ok()) {
+ merge_data = iter->value;
+ } else { // Key not present in db (s.IsNotFound())
+ merge_data = nullptr;
+ }
+
+ if (merge_operator) {
+ *key.s = MergeHelper::TimedFullMerge(
+ merge_operator, *key.key, merge_data,
+ merge_result.second.GetOperands(), key.value->GetSelf(), logger,
+ statistics, env);
+ key.value->PinSelf();
+ } else {
+ *key.s =
+ Status::InvalidArgument("Options::merge_operator must be set");
+ }
+ }
+ }
+ }
+}
+
+void WriteBatchWithIndex::SetSavePoint() { rep->write_batch.SetSavePoint(); }
+
+Status WriteBatchWithIndex::RollbackToSavePoint() {
+ Status s = rep->write_batch.RollbackToSavePoint();
+
+ if (s.ok()) {
+ rep->sub_batch_cnt = 1;
+ rep->last_sub_batch_offset = 0;
+ s = rep->ReBuildIndex();
+ }
+
+ return s;
+}
+
+Status WriteBatchWithIndex::PopSavePoint() {
+ return rep->write_batch.PopSavePoint();
+}
+
+void WriteBatchWithIndex::SetMaxBytes(size_t max_bytes) {
+ rep->write_batch.SetMaxBytes(max_bytes);
+}
+
+size_t WriteBatchWithIndex::GetDataSize() const {
+ return rep->write_batch.GetDataSize();
+}
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.cc b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.cc
new file mode 100644
index 00000000..8c1222f2
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "utilities/write_batch_with_index/write_batch_with_index_internal.h"
+
+#include "db/column_family.h"
+#include "db/merge_context.h"
+#include "db/merge_helper.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/db.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include "util/coding.h"
+#include "util/string_util.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class Env;
+class Logger;
+class Statistics;
+
+Status ReadableWriteBatch::GetEntryFromDataOffset(size_t data_offset,
+ WriteType* type, Slice* Key,
+ Slice* value, Slice* blob,
+ Slice* xid) const {
+ if (type == nullptr || Key == nullptr || value == nullptr ||
+ blob == nullptr || xid == nullptr) {
+ return Status::InvalidArgument("Output parameters cannot be null");
+ }
+
+ if (data_offset == GetDataSize()) {
+ // reached end of batch.
+ return Status::NotFound();
+ }
+
+ if (data_offset > GetDataSize()) {
+ return Status::InvalidArgument("data offset exceed write batch size");
+ }
+ Slice input = Slice(rep_.data() + data_offset, rep_.size() - data_offset);
+ char tag;
+ uint32_t column_family;
+ Status s = ReadRecordFromWriteBatch(&input, &tag, &column_family, Key, value,
+ blob, xid);
+
+ switch (tag) {
+ case kTypeColumnFamilyValue:
+ case kTypeValue:
+ *type = kPutRecord;
+ break;
+ case kTypeColumnFamilyDeletion:
+ case kTypeDeletion:
+ *type = kDeleteRecord;
+ break;
+ case kTypeColumnFamilySingleDeletion:
+ case kTypeSingleDeletion:
+ *type = kSingleDeleteRecord;
+ break;
+ case kTypeColumnFamilyRangeDeletion:
+ case kTypeRangeDeletion:
+ *type = kDeleteRangeRecord;
+ break;
+ case kTypeColumnFamilyMerge:
+ case kTypeMerge:
+ *type = kMergeRecord;
+ break;
+ case kTypeLogData:
+ *type = kLogDataRecord;
+ break;
+ case kTypeNoop:
+ case kTypeBeginPrepareXID:
+ case kTypeBeginPersistedPrepareXID:
+ case kTypeBeginUnprepareXID:
+ case kTypeEndPrepareXID:
+ case kTypeCommitXID:
+ case kTypeRollbackXID:
+ *type = kXIDRecord;
+ break;
+ default:
+ return Status::Corruption("unknown WriteBatch tag ",
+ ToString(static_cast<unsigned int>(tag)));
+ }
+ return Status::OK();
+}
+
+// If both of `entry1` and `entry2` point to real entry in write batch, we
+// compare the entries as following:
+// 1. first compare the column family, the one with larger CF will be larger;
+// 2. Inside the same CF, we first decode the entry to find the key of the entry
+// and the entry with larger key will be larger;
+// 3. If two entries are of the same CF and offset, the one with larger offset
+// will be larger.
+// Some times either `entry1` or `entry2` is dummy entry, which is actually
+// a search key. In this case, in step 2, we don't go ahead and decode the
+// entry but use the value in WriteBatchIndexEntry::search_key.
+// One special case is WriteBatchIndexEntry::key_size is kFlagMinInCf.
+// This indicate that we are going to seek to the first of the column family.
+// Once we see this, this entry will be smaller than all the real entries of
+// the column family.
+int WriteBatchEntryComparator::operator()(
+ const WriteBatchIndexEntry* entry1,
+ const WriteBatchIndexEntry* entry2) const {
+ if (entry1->column_family > entry2->column_family) {
+ return 1;
+ } else if (entry1->column_family < entry2->column_family) {
+ return -1;
+ }
+
+ // Deal with special case of seeking to the beginning of a column family
+ if (entry1->is_min_in_cf()) {
+ return -1;
+ } else if (entry2->is_min_in_cf()) {
+ return 1;
+ }
+
+ Slice key1, key2;
+ if (entry1->search_key == nullptr) {
+ key1 = Slice(write_batch_->Data().data() + entry1->key_offset,
+ entry1->key_size);
+ } else {
+ key1 = *(entry1->search_key);
+ }
+ if (entry2->search_key == nullptr) {
+ key2 = Slice(write_batch_->Data().data() + entry2->key_offset,
+ entry2->key_size);
+ } else {
+ key2 = *(entry2->search_key);
+ }
+
+ int cmp = CompareKey(entry1->column_family, key1, key2);
+ if (cmp != 0) {
+ return cmp;
+ } else if (entry1->offset > entry2->offset) {
+ return 1;
+ } else if (entry1->offset < entry2->offset) {
+ return -1;
+ }
+ return 0;
+}
+
+int WriteBatchEntryComparator::CompareKey(uint32_t column_family,
+ const Slice& key1,
+ const Slice& key2) const {
+ if (column_family < cf_comparators_.size() &&
+ cf_comparators_[column_family] != nullptr) {
+ return cf_comparators_[column_family]->Compare(key1, key2);
+ } else {
+ return default_comparator_->Compare(key1, key2);
+ }
+}
+
+WriteBatchWithIndexInternal::Result WriteBatchWithIndexInternal::GetFromBatch(
+ const ImmutableDBOptions& immuable_db_options, WriteBatchWithIndex* batch,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ MergeContext* merge_context, WriteBatchEntryComparator* cmp,
+ std::string* value, bool overwrite_key, Status* s) {
+ uint32_t cf_id = GetColumnFamilyID(column_family);
+ *s = Status::OK();
+ WriteBatchWithIndexInternal::Result result =
+ WriteBatchWithIndexInternal::Result::kNotFound;
+
+ std::unique_ptr<WBWIIterator> iter =
+ std::unique_ptr<WBWIIterator>(batch->NewIterator(column_family));
+
+ // We want to iterate in the reverse order that the writes were added to the
+ // batch. Since we don't have a reverse iterator, we must seek past the end.
+ // TODO(agiardullo): consider adding support for reverse iteration
+ iter->Seek(key);
+ while (iter->Valid()) {
+ const WriteEntry entry = iter->Entry();
+ if (cmp->CompareKey(cf_id, entry.key, key) != 0) {
+ break;
+ }
+
+ iter->Next();
+ }
+
+ if (!(*s).ok()) {
+ return WriteBatchWithIndexInternal::Result::kError;
+ }
+
+ if (!iter->Valid()) {
+ // Read past end of results. Reposition on last result.
+ iter->SeekToLast();
+ } else {
+ iter->Prev();
+ }
+
+ Slice entry_value;
+ while (iter->Valid()) {
+ const WriteEntry entry = iter->Entry();
+ if (cmp->CompareKey(cf_id, entry.key, key) != 0) {
+ // Unexpected error or we've reached a different next key
+ break;
+ }
+
+ switch (entry.type) {
+ case kPutRecord: {
+ result = WriteBatchWithIndexInternal::Result::kFound;
+ entry_value = entry.value;
+ break;
+ }
+ case kMergeRecord: {
+ result = WriteBatchWithIndexInternal::Result::kMergeInProgress;
+ merge_context->PushOperand(entry.value);
+ break;
+ }
+ case kDeleteRecord:
+ case kSingleDeleteRecord: {
+ result = WriteBatchWithIndexInternal::Result::kDeleted;
+ break;
+ }
+ case kLogDataRecord:
+ case kXIDRecord: {
+ // ignore
+ break;
+ }
+ default: {
+ result = WriteBatchWithIndexInternal::Result::kError;
+ (*s) = Status::Corruption("Unexpected entry in WriteBatchWithIndex:",
+ ToString(entry.type));
+ break;
+ }
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kFound ||
+ result == WriteBatchWithIndexInternal::Result::kDeleted ||
+ result == WriteBatchWithIndexInternal::Result::kError) {
+ // We can stop iterating once we find a PUT or DELETE
+ break;
+ }
+ if (result == WriteBatchWithIndexInternal::Result::kMergeInProgress &&
+ overwrite_key == true) {
+ // Since we've overwritten keys, we do not know what other operations are
+ // in this batch for this key, so we cannot do a Merge to compute the
+ // result. Instead, we will simply return MergeInProgress.
+ break;
+ }
+
+ iter->Prev();
+ }
+
+ if ((*s).ok()) {
+ if (result == WriteBatchWithIndexInternal::Result::kFound ||
+ result == WriteBatchWithIndexInternal::Result::kDeleted) {
+ // Found a Put or Delete. Merge if necessary.
+ if (merge_context->GetNumOperands() > 0) {
+ const MergeOperator* merge_operator;
+
+ if (column_family != nullptr) {
+ auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
+ merge_operator = cfh->cfd()->ioptions()->merge_operator;
+ } else {
+ *s = Status::InvalidArgument("Must provide a column_family");
+ result = WriteBatchWithIndexInternal::Result::kError;
+ return result;
+ }
+ Statistics* statistics = immuable_db_options.statistics.get();
+ Env* env = immuable_db_options.env;
+ Logger* logger = immuable_db_options.info_log.get();
+
+ if (merge_operator) {
+ *s = MergeHelper::TimedFullMerge(merge_operator, key, &entry_value,
+ merge_context->GetOperands(), value,
+ logger, statistics, env);
+ } else {
+ *s = Status::InvalidArgument("Options::merge_operator must be set");
+ }
+ if ((*s).ok()) {
+ result = WriteBatchWithIndexInternal::Result::kFound;
+ } else {
+ result = WriteBatchWithIndexInternal::Result::kError;
+ }
+ } else { // nothing to merge
+ if (result == WriteBatchWithIndexInternal::Result::kFound) { // PUT
+ value->assign(entry_value.data(), entry_value.size());
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.h b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.h
new file mode 100644
index 00000000..6a859e07
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_internal.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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
+
+#ifndef ROCKSDB_LITE
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "options/db_options.h"
+#include "port/port.h"
+#include "rocksdb/comparator.h"
+#include "rocksdb/iterator.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/status.h"
+#include "rocksdb/utilities/write_batch_with_index.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+class MergeContext;
+struct Options;
+
+// Key used by skip list, as the binary searchable index of WriteBatchWithIndex.
+struct WriteBatchIndexEntry {
+ WriteBatchIndexEntry(size_t o, uint32_t c, size_t ko, size_t ksz)
+ : offset(o),
+ column_family(c),
+ key_offset(ko),
+ key_size(ksz),
+ search_key(nullptr) {}
+ // Create a dummy entry as the search key. This index entry won't be backed
+ // by an entry from the write batch, but a pointer to the search key. Or a
+ // special flag of offset can indicate we are seek to first.
+ // @_search_key: the search key
+ // @_column_family: column family
+ // @is_forward_direction: true for Seek(). False for SeekForPrev()
+ // @is_seek_to_first: true if we seek to the beginning of the column family
+ // _search_key should be null in this case.
+ WriteBatchIndexEntry(const Slice* _search_key, uint32_t _column_family,
+ bool is_forward_direction, bool is_seek_to_first)
+ // For SeekForPrev(), we need to make the dummy entry larger than any
+ // entry who has the same search key. Otherwise, we'll miss those entries.
+ : offset(is_forward_direction ? 0 : port::kMaxSizet),
+ column_family(_column_family),
+ key_offset(0),
+ key_size(is_seek_to_first ? kFlagMinInCf : 0),
+ search_key(_search_key) {
+ assert(_search_key != nullptr || is_seek_to_first);
+ }
+
+ // If this flag appears in the key_size, it indicates a
+ // key that is smaller than any other entry for the same column family.
+ static const size_t kFlagMinInCf = port::kMaxSizet;
+
+ bool is_min_in_cf() const {
+ assert(key_size != kFlagMinInCf ||
+ (key_offset == 0 && search_key == nullptr));
+ return key_size == kFlagMinInCf;
+ }
+
+ // offset of an entry in write batch's string buffer. If this is a dummy
+ // lookup key, in which case search_key != nullptr, offset is set to either
+ // 0 or max, only for comparison purpose. Because when entries have the same
+ // key, the entry with larger offset is larger, offset = 0 will make a seek
+ // key small or equal than all the entries with the seek key, so that Seek()
+ // will find all the entries of the same key. Similarly, offset = MAX will
+ // make the entry just larger than all entries with the search key so
+ // SeekForPrev() will see all the keys with the same key.
+ size_t offset;
+ uint32_t column_family; // c1olumn family of the entry.
+ size_t key_offset; // offset of the key in write batch's string buffer.
+ size_t key_size; // size of the key. kFlagMinInCf indicates
+ // that this is a dummy look up entry for
+ // SeekToFirst() to the beginning of the column
+ // family. We use the flag here to save a boolean
+ // in the struct.
+
+ const Slice* search_key; // if not null, instead of reading keys from
+ // write batch, use it to compare. This is used
+ // for lookup key.
+};
+
+class ReadableWriteBatch : public WriteBatch {
+ public:
+ explicit ReadableWriteBatch(size_t reserved_bytes = 0, size_t max_bytes = 0)
+ : WriteBatch(reserved_bytes, max_bytes) {}
+ // Retrieve some information from a write entry in the write batch, given
+ // the start offset of the write entry.
+ Status GetEntryFromDataOffset(size_t data_offset, WriteType* type, Slice* Key,
+ Slice* value, Slice* blob, Slice* xid) const;
+};
+
+class WriteBatchEntryComparator {
+ public:
+ WriteBatchEntryComparator(const Comparator* _default_comparator,
+ const ReadableWriteBatch* write_batch)
+ : default_comparator_(_default_comparator), write_batch_(write_batch) {}
+ // Compare a and b. Return a negative value if a is less than b, 0 if they
+ // are equal, and a positive value if a is greater than b
+ int operator()(const WriteBatchIndexEntry* entry1,
+ const WriteBatchIndexEntry* entry2) const;
+
+ int CompareKey(uint32_t column_family, const Slice& key1,
+ const Slice& key2) const;
+
+ void SetComparatorForCF(uint32_t column_family_id,
+ const Comparator* comparator) {
+ if (column_family_id >= cf_comparators_.size()) {
+ cf_comparators_.resize(column_family_id + 1, nullptr);
+ }
+ cf_comparators_[column_family_id] = comparator;
+ }
+
+ const Comparator* default_comparator() { return default_comparator_; }
+
+ private:
+ const Comparator* default_comparator_;
+ std::vector<const Comparator*> cf_comparators_;
+ const ReadableWriteBatch* write_batch_;
+};
+
+class WriteBatchWithIndexInternal {
+ public:
+ enum Result { kFound, kDeleted, kNotFound, kMergeInProgress, kError };
+
+ // If batch contains a value for key, store it in *value and return kFound.
+ // If batch contains a deletion for key, return Deleted.
+ // If batch contains Merge operations as the most recent entry for a key,
+ // and the merge process does not stop (not reaching a value or delete),
+ // prepend the current merge operands to *operands,
+ // and return kMergeInProgress
+ // If batch does not contain this key, return kNotFound
+ // Else, return kError on error with error Status stored in *s.
+ static WriteBatchWithIndexInternal::Result GetFromBatch(
+ const ImmutableDBOptions& ioptions, WriteBatchWithIndex* batch,
+ ColumnFamilyHandle* column_family, const Slice& key,
+ MergeContext* merge_context, WriteBatchEntryComparator* cmp,
+ std::string* value, bool overwrite_key, Status* s);
+};
+
+} // namespace ROCKSDB_NAMESPACE
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_test.cc b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_test.cc
new file mode 100644
index 00000000..ac4ab7af
--- /dev/null
+++ b/storage/rocksdb/rocksdb/utilities/write_batch_with_index/write_batch_with_index_test.cc
@@ -0,0 +1,1846 @@
+// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+// This source code is licensed under both the GPLv2 (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 ROCKSDB_LITE
+
+#include "rocksdb/utilities/write_batch_with_index.h"
+#include <map>
+#include <memory>
+#include "db/column_family.h"
+#include "port/stack_trace.h"
+#include "test_util/testharness.h"
+#include "util/random.h"
+#include "util/string_util.h"
+#include "utilities/merge_operators.h"
+#include "utilities/merge_operators/string_append/stringappend.h"
+
+namespace ROCKSDB_NAMESPACE {
+
+namespace {
+class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl {
+ public:
+ explicit ColumnFamilyHandleImplDummy(int id, const Comparator* comparator)
+ : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr),
+ id_(id),
+ comparator_(comparator) {}
+ uint32_t GetID() const override { return id_; }
+ const Comparator* GetComparator() const override { return comparator_; }
+
+ private:
+ uint32_t id_;
+ const Comparator* comparator_;
+};
+
+struct Entry {
+ std::string key;
+ std::string value;
+ WriteType type;
+};
+
+struct TestHandler : public WriteBatch::Handler {
+ std::map<uint32_t, std::vector<Entry>> seen;
+ Status PutCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ Entry e;
+ e.key = key.ToString();
+ e.value = value.ToString();
+ e.type = kPutRecord;
+ seen[column_family_id].push_back(e);
+ return Status::OK();
+ }
+ Status MergeCF(uint32_t column_family_id, const Slice& key,
+ const Slice& value) override {
+ Entry e;
+ e.key = key.ToString();
+ e.value = value.ToString();
+ e.type = kMergeRecord;
+ seen[column_family_id].push_back(e);
+ return Status::OK();
+ }
+ void LogData(const Slice& /*blob*/) override {}
+ Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
+ Entry e;
+ e.key = key.ToString();
+ e.value = "";
+ e.type = kDeleteRecord;
+ seen[column_family_id].push_back(e);
+ return Status::OK();
+ }
+};
+} // namespace anonymous
+
+class WriteBatchWithIndexTest : public testing::Test {};
+
+void TestValueAsSecondaryIndexHelper(std::vector<Entry> entries,
+ WriteBatchWithIndex* batch) {
+ // In this test, we insert <key, value> to column family `data`, and
+ // <value, key> to column family `index`. Then iterator them in order
+ // and seek them by key.
+
+ // Sort entries by key
+ std::map<std::string, std::vector<Entry*>> data_map;
+ // Sort entries by value
+ std::map<std::string, std::vector<Entry*>> index_map;
+ for (auto& e : entries) {
+ data_map[e.key].push_back(&e);
+ index_map[e.value].push_back(&e);
+ }
+
+ ColumnFamilyHandleImplDummy data(6, BytewiseComparator());
+ ColumnFamilyHandleImplDummy index(8, BytewiseComparator());
+ for (auto& e : entries) {
+ if (e.type == kPutRecord) {
+ batch->Put(&data, e.key, e.value);
+ batch->Put(&index, e.value, e.key);
+ } else if (e.type == kMergeRecord) {
+ batch->Merge(&data, e.key, e.value);
+ batch->Put(&index, e.value, e.key);
+ } else {
+ assert(e.type == kDeleteRecord);
+ std::unique_ptr<WBWIIterator> iter(batch->NewIterator(&data));
+ iter->Seek(e.key);
+ ASSERT_OK(iter->status());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(e.key, write_entry.key.ToString());
+ ASSERT_EQ(e.value, write_entry.value.ToString());
+ batch->Delete(&data, e.key);
+ batch->Put(&index, e.value, "");
+ }
+ }
+
+ // Iterator all keys
+ {
+ std::unique_ptr<WBWIIterator> iter(batch->NewIterator(&data));
+ for (int seek_to_first : {0, 1}) {
+ if (seek_to_first) {
+ iter->SeekToFirst();
+ } else {
+ iter->Seek("");
+ }
+ for (auto pair : data_map) {
+ for (auto v : pair.second) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair.first, write_entry.key.ToString());
+ ASSERT_EQ(v->type, write_entry.type);
+ if (write_entry.type != kDeleteRecord) {
+ ASSERT_EQ(v->value, write_entry.value.ToString());
+ }
+ iter->Next();
+ }
+ }
+ ASSERT_TRUE(!iter->Valid());
+ }
+ iter->SeekToLast();
+ for (auto pair = data_map.rbegin(); pair != data_map.rend(); ++pair) {
+ for (auto v = pair->second.rbegin(); v != pair->second.rend(); v++) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair->first, write_entry.key.ToString());
+ ASSERT_EQ((*v)->type, write_entry.type);
+ if (write_entry.type != kDeleteRecord) {
+ ASSERT_EQ((*v)->value, write_entry.value.ToString());
+ }
+ iter->Prev();
+ }
+ }
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ // Iterator all indexes
+ {
+ std::unique_ptr<WBWIIterator> iter(batch->NewIterator(&index));
+ for (int seek_to_first : {0, 1}) {
+ if (seek_to_first) {
+ iter->SeekToFirst();
+ } else {
+ iter->Seek("");
+ }
+ for (auto pair : index_map) {
+ for (auto v : pair.second) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair.first, write_entry.key.ToString());
+ if (v->type != kDeleteRecord) {
+ ASSERT_EQ(v->key, write_entry.value.ToString());
+ ASSERT_EQ(v->value, write_entry.key.ToString());
+ }
+ iter->Next();
+ }
+ }
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ iter->SeekToLast();
+ for (auto pair = index_map.rbegin(); pair != index_map.rend(); ++pair) {
+ for (auto v = pair->second.rbegin(); v != pair->second.rend(); v++) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair->first, write_entry.key.ToString());
+ if ((*v)->type != kDeleteRecord) {
+ ASSERT_EQ((*v)->key, write_entry.value.ToString());
+ ASSERT_EQ((*v)->value, write_entry.key.ToString());
+ }
+ iter->Prev();
+ }
+ }
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ // Seek to every key
+ {
+ std::unique_ptr<WBWIIterator> iter(batch->NewIterator(&data));
+
+ // Seek the keys one by one in reverse order
+ for (auto pair = data_map.rbegin(); pair != data_map.rend(); ++pair) {
+ iter->Seek(pair->first);
+ ASSERT_OK(iter->status());
+ for (auto v : pair->second) {
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair->first, write_entry.key.ToString());
+ ASSERT_EQ(v->type, write_entry.type);
+ if (write_entry.type != kDeleteRecord) {
+ ASSERT_EQ(v->value, write_entry.value.ToString());
+ }
+ iter->Next();
+ ASSERT_OK(iter->status());
+ }
+ }
+ }
+
+ // Seek to every index
+ {
+ std::unique_ptr<WBWIIterator> iter(batch->NewIterator(&index));
+
+ // Seek the keys one by one in reverse order
+ for (auto pair = index_map.rbegin(); pair != index_map.rend(); ++pair) {
+ iter->Seek(pair->first);
+ ASSERT_OK(iter->status());
+ for (auto v : pair->second) {
+ ASSERT_TRUE(iter->Valid());
+ auto write_entry = iter->Entry();
+ ASSERT_EQ(pair->first, write_entry.key.ToString());
+ ASSERT_EQ(v->value, write_entry.key.ToString());
+ if (v->type != kDeleteRecord) {
+ ASSERT_EQ(v->key, write_entry.value.ToString());
+ }
+ iter->Next();
+ ASSERT_OK(iter->status());
+ }
+ }
+ }
+
+ // Verify WriteBatch can be iterated
+ TestHandler handler;
+ batch->GetWriteBatch()->Iterate(&handler);
+
+ // Verify data column family
+ {
+ ASSERT_EQ(entries.size(), handler.seen[data.GetID()].size());
+ size_t i = 0;
+ for (auto e : handler.seen[data.GetID()]) {
+ auto write_entry = entries[i++];
+ ASSERT_EQ(e.type, write_entry.type);
+ ASSERT_EQ(e.key, write_entry.key);
+ if (e.type != kDeleteRecord) {
+ ASSERT_EQ(e.value, write_entry.value);
+ }
+ }
+ }
+
+ // Verify index column family
+ {
+ ASSERT_EQ(entries.size(), handler.seen[index.GetID()].size());
+ size_t i = 0;
+ for (auto e : handler.seen[index.GetID()]) {
+ auto write_entry = entries[i++];
+ ASSERT_EQ(e.key, write_entry.value);
+ if (write_entry.type != kDeleteRecord) {
+ ASSERT_EQ(e.value, write_entry.key);
+ }
+ }
+ }
+}
+
+TEST_F(WriteBatchWithIndexTest, TestValueAsSecondaryIndex) {
+ Entry entries[] = {
+ {"aaa", "0005", kPutRecord},
+ {"b", "0002", kPutRecord},
+ {"cdd", "0002", kMergeRecord},
+ {"aab", "00001", kPutRecord},
+ {"cc", "00005", kPutRecord},
+ {"cdd", "0002", kPutRecord},
+ {"aab", "0003", kPutRecord},
+ {"cc", "00005", kDeleteRecord},
+ };
+ std::vector<Entry> entries_list(entries, entries + 8);
+
+ WriteBatchWithIndex batch(nullptr, 20);
+
+ TestValueAsSecondaryIndexHelper(entries_list, &batch);
+
+ // Clear batch and re-run test with new values
+ batch.Clear();
+
+ Entry new_entries[] = {
+ {"aaa", "0005", kPutRecord},
+ {"e", "0002", kPutRecord},
+ {"add", "0002", kMergeRecord},
+ {"aab", "00001", kPutRecord},
+ {"zz", "00005", kPutRecord},
+ {"add", "0002", kPutRecord},
+ {"aab", "0003", kPutRecord},
+ {"zz", "00005", kDeleteRecord},
+ };
+
+ entries_list = std::vector<Entry>(new_entries, new_entries + 8);
+
+ TestValueAsSecondaryIndexHelper(entries_list, &batch);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestComparatorForCF) {
+ ColumnFamilyHandleImplDummy cf1(6, nullptr);
+ ColumnFamilyHandleImplDummy reverse_cf(66, ReverseBytewiseComparator());
+ ColumnFamilyHandleImplDummy cf2(88, BytewiseComparator());
+ WriteBatchWithIndex batch(BytewiseComparator(), 20);
+
+ batch.Put(&cf1, "ddd", "");
+ batch.Put(&cf2, "aaa", "");
+ batch.Put(&cf2, "eee", "");
+ batch.Put(&cf1, "ccc", "");
+ batch.Put(&reverse_cf, "a11", "");
+ batch.Put(&cf1, "bbb", "");
+
+ Slice key_slices[] = {"a", "3", "3"};
+ Slice value_slice = "";
+ batch.Put(&reverse_cf, SliceParts(key_slices, 3),
+ SliceParts(&value_slice, 1));
+ batch.Put(&reverse_cf, "a22", "");
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&cf1));
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("bbb", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("ccc", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("ddd", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&cf2));
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("aaa", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("eee", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&reverse_cf));
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("z");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a33", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a22", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a11", iter->Entry().key.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("a22");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a22", iter->Entry().key.ToString());
+
+ iter->Seek("a13");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a11", iter->Entry().key.ToString());
+ }
+}
+
+TEST_F(WriteBatchWithIndexTest, TestOverwriteKey) {
+ ColumnFamilyHandleImplDummy cf1(6, nullptr);
+ ColumnFamilyHandleImplDummy reverse_cf(66, ReverseBytewiseComparator());
+ ColumnFamilyHandleImplDummy cf2(88, BytewiseComparator());
+ WriteBatchWithIndex batch(BytewiseComparator(), 20, true);
+
+ batch.Put(&cf1, "ddd", "");
+ batch.Merge(&cf1, "ddd", "");
+ batch.Delete(&cf1, "ddd");
+ batch.Put(&cf2, "aaa", "");
+ batch.Delete(&cf2, "aaa");
+ batch.Put(&cf2, "aaa", "aaa");
+ batch.Put(&cf2, "eee", "eee");
+ batch.Put(&cf1, "ccc", "");
+ batch.Put(&reverse_cf, "a11", "");
+ batch.Delete(&cf1, "ccc");
+ batch.Put(&reverse_cf, "a33", "a33");
+ batch.Put(&reverse_cf, "a11", "a11");
+ Slice slices[] = {"a", "3", "3"};
+ batch.Delete(&reverse_cf, SliceParts(slices, 3));
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&cf1));
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("ccc", iter->Entry().key.ToString());
+ ASSERT_TRUE(iter->Entry().type == WriteType::kDeleteRecord);
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("ddd", iter->Entry().key.ToString());
+ ASSERT_TRUE(iter->Entry().type == WriteType::kDeleteRecord);
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&cf2));
+ iter->SeekToLast();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("eee", iter->Entry().key.ToString());
+ ASSERT_EQ("eee", iter->Entry().value.ToString());
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("aaa", iter->Entry().key.ToString());
+ ASSERT_EQ("aaa", iter->Entry().value.ToString());
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToFirst();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("aaa", iter->Entry().key.ToString());
+ ASSERT_EQ("aaa", iter->Entry().value.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("eee", iter->Entry().key.ToString());
+ ASSERT_EQ("eee", iter->Entry().value.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ {
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&reverse_cf));
+ iter->Seek("");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("z");
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a33", iter->Entry().key.ToString());
+ ASSERT_TRUE(iter->Entry().type == WriteType::kDeleteRecord);
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a11", iter->Entry().key.ToString());
+ ASSERT_EQ("a11", iter->Entry().value.ToString());
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a11", iter->Entry().key.ToString());
+ ASSERT_EQ("a11", iter->Entry().value.ToString());
+ iter->Prev();
+
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ("a33", iter->Entry().key.ToString());
+ ASSERT_TRUE(iter->Entry().type == WriteType::kDeleteRecord);
+ iter->Prev();
+ ASSERT_TRUE(!iter->Valid());
+ }
+}
+
+namespace {
+typedef std::map<std::string, std::string> KVMap;
+
+class KVIter : public Iterator {
+ public:
+ explicit KVIter(const 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 KVMap* const map_;
+ KVMap::const_iterator iter_;
+};
+
+void AssertIter(Iterator* iter, const std::string& key,
+ const std::string& value) {
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(key, iter->key().ToString());
+ ASSERT_EQ(value, iter->value().ToString());
+}
+
+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());
+ }
+}
+} // namespace
+
+TEST_F(WriteBatchWithIndexTest, TestRandomIteraratorWithBase) {
+ std::vector<std::string> source_strings = {"a", "b", "c", "d", "e",
+ "f", "g", "h", "i", "j"};
+ for (int rand_seed = 301; rand_seed < 366; rand_seed++) {
+ Random rnd(rand_seed);
+
+ ColumnFamilyHandleImplDummy cf1(6, BytewiseComparator());
+ ColumnFamilyHandleImplDummy cf2(2, BytewiseComparator());
+ ColumnFamilyHandleImplDummy cf3(8, BytewiseComparator());
+
+ WriteBatchWithIndex batch(BytewiseComparator(), 20, true);
+
+ if (rand_seed % 2 == 0) {
+ batch.Put(&cf2, "zoo", "bar");
+ }
+ if (rand_seed % 4 == 1) {
+ batch.Put(&cf3, "zoo", "bar");
+ }
+
+ KVMap map;
+ KVMap merged_map;
+ for (auto key : source_strings) {
+ std::string value = key + key;
+ int type = rnd.Uniform(6);
+ switch (type) {
+ case 0:
+ // only base has it
+ map[key] = value;
+ merged_map[key] = value;
+ break;
+ case 1:
+ // only delta has it
+ batch.Put(&cf1, key, value);
+ map[key] = value;
+ merged_map[key] = value;
+ break;
+ case 2:
+ // both has it. Delta should win
+ batch.Put(&cf1, key, value);
+ map[key] = "wrong_value";
+ merged_map[key] = value;
+ break;
+ case 3:
+ // both has it. Delta is delete
+ batch.Delete(&cf1, key);
+ map[key] = "wrong_value";
+ break;
+ case 4:
+ // only delta has it. Delta is delete
+ batch.Delete(&cf1, key);
+ map[key] = "wrong_value";
+ break;
+ default:
+ // Neither iterator has it.
+ break;
+ }
+ }
+
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&map)));
+ std::unique_ptr<Iterator> result_iter(new KVIter(&merged_map));
+
+ bool is_valid = false;
+ for (int i = 0; i < 128; 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<int>(source_strings.size()));
+ auto key = source_strings[key_idx];
+ iter->Seek(key);
+ result_iter->Seek(key);
+ break;
+ }
+ case 3: {
+ // SeekForPrev to random key
+ auto key_idx = rnd.Uniform(static_cast<int>(source_strings.size()));
+ auto key = source_strings[key_idx];
+ iter->SeekForPrev(key);
+ result_iter->SeekForPrev(key);
+ break;
+ }
+ case 4:
+ // Next
+ if (is_valid) {
+ iter->Next();
+ result_iter->Next();
+ } else {
+ continue;
+ }
+ break;
+ default:
+ assert(type == 5);
+ // Prev
+ if (is_valid) {
+ iter->Prev();
+ result_iter->Prev();
+ } else {
+ continue;
+ }
+ break;
+ }
+ AssertItersEqual(iter.get(), result_iter.get());
+ is_valid = iter->Valid();
+ }
+ }
+}
+
+TEST_F(WriteBatchWithIndexTest, TestIteraratorWithBase) {
+ ColumnFamilyHandleImplDummy cf1(6, BytewiseComparator());
+ ColumnFamilyHandleImplDummy cf2(2, BytewiseComparator());
+ WriteBatchWithIndex batch(BytewiseComparator(), 20, true);
+
+ {
+ KVMap map;
+ map["a"] = "aa";
+ map["c"] = "cc";
+ map["e"] = "ee";
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "e", "ee");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ AssertIter(iter.get(), "e", "ee");
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Prev();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("b");
+ AssertIter(iter.get(), "c", "cc");
+
+ iter->Prev();
+ AssertIter(iter.get(), "a", "aa");
+
+ iter->Seek("a");
+ AssertIter(iter.get(), "a", "aa");
+ }
+
+ // Test the case that there is one element in the write batch
+ batch.Put(&cf2, "zoo", "bar");
+ batch.Put(&cf1, "a", "aa");
+ {
+ KVMap empty_map;
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&empty_map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ batch.Delete(&cf1, "b");
+ batch.Put(&cf1, "c", "cc");
+ batch.Put(&cf1, "d", "dd");
+ batch.Delete(&cf1, "e");
+
+ {
+ KVMap map;
+ map["b"] = "";
+ map["cc"] = "cccc";
+ map["f"] = "ff";
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "cc", "cccc");
+ iter->Next();
+ AssertIter(iter.get(), "d", "dd");
+ iter->Next();
+ AssertIter(iter.get(), "f", "ff");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ AssertIter(iter.get(), "f", "ff");
+ iter->Prev();
+ AssertIter(iter.get(), "d", "dd");
+ iter->Prev();
+ AssertIter(iter.get(), "cc", "cccc");
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "cc", "cccc");
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Prev();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("c");
+ AssertIter(iter.get(), "c", "cc");
+
+ iter->Seek("cb");
+ AssertIter(iter.get(), "cc", "cccc");
+
+ iter->Seek("cc");
+ AssertIter(iter.get(), "cc", "cccc");
+ iter->Next();
+ AssertIter(iter.get(), "d", "dd");
+
+ iter->Seek("e");
+ AssertIter(iter.get(), "f", "ff");
+
+ iter->Prev();
+ AssertIter(iter.get(), "d", "dd");
+
+ iter->Next();
+ AssertIter(iter.get(), "f", "ff");
+ }
+
+ {
+ KVMap empty_map;
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&empty_map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "d", "dd");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ AssertIter(iter.get(), "d", "dd");
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Prev();
+ AssertIter(iter.get(), "a", "aa");
+
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("aa");
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "d", "dd");
+
+ iter->Seek("ca");
+ AssertIter(iter.get(), "d", "dd");
+
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ }
+}
+
+TEST_F(WriteBatchWithIndexTest, TestIteraratorWithBaseReverseCmp) {
+ ColumnFamilyHandleImplDummy cf1(6, ReverseBytewiseComparator());
+ ColumnFamilyHandleImplDummy cf2(2, ReverseBytewiseComparator());
+ WriteBatchWithIndex batch(BytewiseComparator(), 20, true);
+
+ // Test the case that there is one element in the write batch
+ batch.Put(&cf2, "zoo", "bar");
+ batch.Put(&cf1, "a", "aa");
+ {
+ KVMap empty_map;
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&empty_map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+ }
+
+ batch.Put(&cf1, "c", "cc");
+ {
+ KVMap map;
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(&cf1, new KVIter(&map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Next();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ AssertIter(iter.get(), "a", "aa");
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("b");
+ AssertIter(iter.get(), "a", "aa");
+
+ iter->Prev();
+ AssertIter(iter.get(), "c", "cc");
+
+ iter->Seek("a");
+ AssertIter(iter.get(), "a", "aa");
+ }
+
+ // default column family
+ batch.Put("a", "b");
+ {
+ KVMap map;
+ map["b"] = "";
+ std::unique_ptr<Iterator> iter(batch.NewIteratorWithBase(new KVIter(&map)));
+
+ iter->SeekToFirst();
+ AssertIter(iter.get(), "a", "b");
+ iter->Next();
+ AssertIter(iter.get(), "b", "");
+ iter->Next();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->SeekToLast();
+ AssertIter(iter.get(), "b", "");
+ iter->Prev();
+ AssertIter(iter.get(), "a", "b");
+ iter->Prev();
+ ASSERT_OK(iter->status());
+ ASSERT_TRUE(!iter->Valid());
+
+ iter->Seek("b");
+ AssertIter(iter.get(), "b", "");
+
+ iter->Prev();
+ AssertIter(iter.get(), "a", "b");
+
+ iter->Seek("0");
+ AssertIter(iter.get(), "a", "b");
+ }
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatch) {
+ Options options;
+ WriteBatchWithIndex batch;
+ Status s;
+ std::string value;
+
+ s = batch.GetFromBatch(options, "b", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ batch.Put("a", "a");
+ batch.Put("b", "b");
+ batch.Put("c", "c");
+ batch.Put("a", "z");
+ batch.Delete("c");
+ batch.Delete("d");
+ batch.Delete("e");
+ batch.Put("e", "e");
+
+ s = batch.GetFromBatch(options, "b", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ s = batch.GetFromBatch(options, "a", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("z", value);
+
+ s = batch.GetFromBatch(options, "c", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = batch.GetFromBatch(options, "d", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = batch.GetFromBatch(options, "x", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = batch.GetFromBatch(options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e", value);
+
+ batch.Merge("z", "z");
+
+ s = batch.GetFromBatch(options, "z", &value);
+ ASSERT_NOK(s); // No merge operator specified.
+
+ s = batch.GetFromBatch(options, "b", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchMerge) {
+ DB* db;
+ Options options;
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+ options.create_if_missing = true;
+
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ ASSERT_OK(s);
+
+ ColumnFamilyHandle* column_family = db->DefaultColumnFamily();
+ WriteBatchWithIndex batch;
+ std::string value;
+
+ s = batch.GetFromBatch(options, "x", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ batch.Put("x", "X");
+ std::string expected = "X";
+
+ for (int i = 0; i < 5; i++) {
+ batch.Merge("x", ToString(i));
+ expected = expected + "," + ToString(i);
+
+ if (i % 2 == 0) {
+ batch.Put("y", ToString(i / 2));
+ }
+
+ batch.Merge("z", "z");
+
+ s = batch.GetFromBatch(column_family, options, "x", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(expected, value);
+
+ s = batch.GetFromBatch(column_family, options, "y", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ(ToString(i / 2), value);
+
+ s = batch.GetFromBatch(column_family, options, "z", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+ }
+
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchMerge2) {
+ DB* db;
+ Options options;
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+ options.create_if_missing = true;
+
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ ASSERT_OK(s);
+
+ ColumnFamilyHandle* column_family = db->DefaultColumnFamily();
+
+ // Test batch with overwrite_key=true
+ WriteBatchWithIndex batch(BytewiseComparator(), 0, true);
+ std::string value;
+
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ batch.Put(column_family, "X", "x");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("x", value);
+
+ batch.Put(column_family, "X", "x2");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("x2", value);
+
+ batch.Merge(column_family, "X", "aaa");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ batch.Merge(column_family, "X", "bbb");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ batch.Put(column_family, "X", "x3");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("x3", value);
+
+ batch.Merge(column_family, "X", "ccc");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ batch.Delete(column_family, "X");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ batch.Merge(column_family, "X", "ddd");
+ s = batch.GetFromBatch(column_family, options, "X", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchAndDB) {
+ DB* db;
+ Options options;
+ options.create_if_missing = true;
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ ASSERT_OK(s);
+
+ WriteBatchWithIndex batch;
+ ReadOptions read_options;
+ WriteOptions write_options;
+ std::string value;
+
+ s = db->Put(write_options, "a", "a");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "b", "b");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "c", "c");
+ ASSERT_OK(s);
+
+ batch.Put("a", "batch.a");
+ batch.Delete("b");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "a", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("batch.a", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "b", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = batch.GetFromBatchAndDB(db, read_options, "c", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "x", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ db->Delete(write_options, "x");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "x", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchAndDBMerge) {
+ DB* db;
+ Options options;
+
+ options.create_if_missing = true;
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ assert(s.ok());
+
+ WriteBatchWithIndex batch;
+ ReadOptions read_options;
+ WriteOptions write_options;
+ std::string value;
+
+ s = db->Put(write_options, "a", "a0");
+ ASSERT_OK(s);
+
+ s = db->Put(write_options, "b", "b0");
+ ASSERT_OK(s);
+
+ s = db->Merge(write_options, "b", "b1");
+ ASSERT_OK(s);
+
+ s = db->Merge(write_options, "c", "c0");
+ ASSERT_OK(s);
+
+ s = db->Merge(write_options, "d", "d0");
+ ASSERT_OK(s);
+
+ batch.Merge("a", "a1");
+ batch.Merge("a", "a2");
+ batch.Merge("b", "b2");
+ batch.Merge("d", "d1");
+ batch.Merge("e", "e0");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "a", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a0,a1,a2", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "b", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b0,b1,b2", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "c", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c0", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "d", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("d0,d1", value);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e0", value);
+
+ s = db->Delete(write_options, "x");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "x", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ const Snapshot* snapshot = db->GetSnapshot();
+ ReadOptions snapshot_read_options;
+ snapshot_read_options.snapshot = snapshot;
+
+ s = db->Delete(write_options, "a");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "a", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a1,a2", value);
+
+ s = batch.GetFromBatchAndDB(db, snapshot_read_options, "a", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a0,a1,a2", value);
+
+ batch.Delete("a");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "a", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = batch.GetFromBatchAndDB(db, snapshot_read_options, "a", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ s = db->Merge(write_options, "c", "c1");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "c", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c0,c1", value);
+
+ s = batch.GetFromBatchAndDB(db, snapshot_read_options, "c", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c0", value);
+
+ s = db->Put(write_options, "e", "e1");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e1,e0", value);
+
+ s = batch.GetFromBatchAndDB(db, snapshot_read_options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e0", value);
+
+ s = db->Delete(write_options, "e");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e0", value);
+
+ s = batch.GetFromBatchAndDB(db, snapshot_read_options, "e", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("e0", value);
+
+ db->ReleaseSnapshot(snapshot);
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchAndDBMerge2) {
+ DB* db;
+ Options options;
+
+ options.create_if_missing = true;
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ assert(s.ok());
+
+ // Test batch with overwrite_key=true
+ WriteBatchWithIndex batch(BytewiseComparator(), 0, true);
+
+ ReadOptions read_options;
+ WriteOptions write_options;
+ std::string value;
+
+ s = batch.GetFromBatchAndDB(db, read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ batch.Merge("A", "xxx");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "A", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ batch.Merge("A", "yyy");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "A", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ s = db->Put(write_options, "A", "a0");
+ ASSERT_OK(s);
+
+ s = batch.GetFromBatchAndDB(db, read_options, "A", &value);
+ ASSERT_TRUE(s.IsMergeInProgress());
+
+ batch.Delete("A");
+
+ s = batch.GetFromBatchAndDB(db, read_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+TEST_F(WriteBatchWithIndexTest, TestGetFromBatchAndDBMerge3) {
+ DB* db;
+ Options options;
+
+ options.create_if_missing = true;
+ std::string dbname = test::PerThreadDBPath("write_batch_with_index_test");
+
+ options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
+
+ DestroyDB(dbname, options);
+ Status s = DB::Open(options, dbname, &db);
+ assert(s.ok());
+
+ ReadOptions read_options;
+ WriteOptions write_options;
+ FlushOptions flush_options;
+ std::string value;
+
+ WriteBatchWithIndex batch;
+
+ ASSERT_OK(db->Put(write_options, "A", "1"));
+ ASSERT_OK(db->Flush(flush_options, db->DefaultColumnFamily()));
+ ASSERT_OK(batch.Merge("A", "2"));
+
+ ASSERT_OK(batch.GetFromBatchAndDB(db, read_options, "A", &value));
+ ASSERT_EQ(value, "1,2");
+
+ delete db;
+ DestroyDB(dbname, options);
+}
+
+void AssertKey(std::string key, WBWIIterator* iter) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(key, iter->Entry().key.ToString());
+}
+
+void AssertValue(std::string value, WBWIIterator* iter) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(value, iter->Entry().value.ToString());
+}
+
+// Tests that we can write to the WBWI while we iterate (from a single thread).
+// iteration should see the newest writes
+TEST_F(WriteBatchWithIndexTest, MutateWhileIteratingCorrectnessTest) {
+ WriteBatchWithIndex batch(BytewiseComparator(), 0, true);
+ for (char c = 'a'; c <= 'z'; ++c) {
+ batch.Put(std::string(1, c), std::string(1, c));
+ }
+
+ std::unique_ptr<WBWIIterator> iter(batch.NewIterator());
+ iter->Seek("k");
+ AssertKey("k", iter.get());
+ iter->Next();
+ AssertKey("l", iter.get());
+ batch.Put("ab", "cc");
+ iter->Next();
+ AssertKey("m", iter.get());
+ batch.Put("mm", "kk");
+ iter->Next();
+ AssertKey("mm", iter.get());
+ AssertValue("kk", iter.get());
+ batch.Delete("mm");
+
+ iter->Next();
+ AssertKey("n", iter.get());
+ iter->Prev();
+ AssertKey("mm", iter.get());
+ ASSERT_EQ(kDeleteRecord, iter->Entry().type);
+
+ iter->Seek("ab");
+ AssertKey("ab", iter.get());
+ batch.Delete("x");
+ iter->Seek("x");
+ AssertKey("x", iter.get());
+ ASSERT_EQ(kDeleteRecord, iter->Entry().type);
+ iter->Prev();
+ AssertKey("w", iter.get());
+}
+
+void AssertIterKey(std::string key, Iterator* iter) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(key, iter->key().ToString());
+}
+
+void AssertIterValue(std::string value, Iterator* iter) {
+ ASSERT_TRUE(iter->Valid());
+ ASSERT_EQ(value, iter->value().ToString());
+}
+
+// same thing as above, but testing IteratorWithBase
+TEST_F(WriteBatchWithIndexTest, MutateWhileIteratingBaseCorrectnessTest) {
+ WriteBatchWithIndex batch(BytewiseComparator(), 0, true);
+ for (char c = 'a'; c <= 'z'; ++c) {
+ batch.Put(std::string(1, c), std::string(1, c));
+ }
+
+ KVMap map;
+ map["aa"] = "aa";
+ map["cc"] = "cc";
+ map["ee"] = "ee";
+ map["em"] = "me";
+
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(new KVIter(&map)));
+ iter->Seek("k");
+ AssertIterKey("k", iter.get());
+ iter->Next();
+ AssertIterKey("l", iter.get());
+ batch.Put("ab", "cc");
+ iter->Next();
+ AssertIterKey("m", iter.get());
+ batch.Put("mm", "kk");
+ iter->Next();
+ AssertIterKey("mm", iter.get());
+ AssertIterValue("kk", iter.get());
+ batch.Delete("mm");
+ iter->Next();
+ AssertIterKey("n", iter.get());
+ iter->Prev();
+ // "mm" is deleted, so we're back at "m"
+ AssertIterKey("m", iter.get());
+
+ iter->Seek("ab");
+ AssertIterKey("ab", iter.get());
+ iter->Prev();
+ AssertIterKey("aa", iter.get());
+ iter->Prev();
+ AssertIterKey("a", iter.get());
+ batch.Delete("aa");
+ iter->Next();
+ AssertIterKey("ab", iter.get());
+ iter->Prev();
+ AssertIterKey("a", iter.get());
+
+ batch.Delete("x");
+ iter->Seek("x");
+ AssertIterKey("y", iter.get());
+ iter->Next();
+ AssertIterKey("z", iter.get());
+ iter->Prev();
+ iter->Prev();
+ AssertIterKey("w", iter.get());
+
+ batch.Delete("e");
+ iter->Seek("e");
+ AssertIterKey("ee", iter.get());
+ AssertIterValue("ee", iter.get());
+ batch.Put("ee", "xx");
+ // still the same value
+ AssertIterValue("ee", iter.get());
+ iter->Next();
+ AssertIterKey("em", iter.get());
+ iter->Prev();
+ // new value
+ AssertIterValue("xx", iter.get());
+}
+
+// stress testing mutations with IteratorWithBase
+TEST_F(WriteBatchWithIndexTest, MutateWhileIteratingBaseStressTest) {
+ WriteBatchWithIndex batch(BytewiseComparator(), 0, true);
+ for (char c = 'a'; c <= 'z'; ++c) {
+ batch.Put(std::string(1, c), std::string(1, c));
+ }
+
+ KVMap map;
+ for (char c = 'a'; c <= 'z'; ++c) {
+ map[std::string(2, c)] = std::string(2, c);
+ }
+
+ std::unique_ptr<Iterator> iter(
+ batch.NewIteratorWithBase(new KVIter(&map)));
+
+ Random rnd(301);
+ for (int i = 0; i < 1000000; ++i) {
+ int random = rnd.Uniform(8);
+ char c = static_cast<char>(rnd.Uniform(26) + 'a');
+ switch (random) {
+ case 0:
+ batch.Put(std::string(1, c), "xxx");
+ break;
+ case 1:
+ batch.Put(std::string(2, c), "xxx");
+ break;
+ case 2:
+ batch.Delete(std::string(1, c));
+ break;
+ case 3:
+ batch.Delete(std::string(2, c));
+ break;
+ case 4:
+ iter->Seek(std::string(1, c));
+ break;
+ case 5:
+ iter->Seek(std::string(2, c));
+ break;
+ case 6:
+ if (iter->Valid()) {
+ iter->Next();
+ }
+ break;
+ case 7:
+ if (iter->Valid()) {
+ iter->Prev();
+ }
+ break;
+ default:
+ assert(false);
+ }
+ }
+}
+
+static std::string PrintContents(WriteBatchWithIndex* batch,
+ ColumnFamilyHandle* column_family) {
+ std::string result;
+
+ WBWIIterator* iter;
+ if (column_family == nullptr) {
+ iter = batch->NewIterator();
+ } else {
+ iter = batch->NewIterator(column_family);
+ }
+
+ iter->SeekToFirst();
+ while (iter->Valid()) {
+ WriteEntry e = iter->Entry();
+
+ if (e.type == kPutRecord) {
+ result.append("PUT(");
+ result.append(e.key.ToString());
+ result.append("):");
+ result.append(e.value.ToString());
+ } else if (e.type == kMergeRecord) {
+ result.append("MERGE(");
+ result.append(e.key.ToString());
+ result.append("):");
+ result.append(e.value.ToString());
+ } else if (e.type == kSingleDeleteRecord) {
+ result.append("SINGLE-DEL(");
+ result.append(e.key.ToString());
+ result.append(")");
+ } else {
+ assert(e.type == kDeleteRecord);
+ result.append("DEL(");
+ result.append(e.key.ToString());
+ result.append(")");
+ }
+
+ result.append(",");
+ iter->Next();
+ }
+
+ delete iter;
+ return result;
+}
+
+static std::string PrintContents(WriteBatchWithIndex* batch, KVMap* base_map,
+ ColumnFamilyHandle* column_family) {
+ std::string result;
+
+ Iterator* iter;
+ if (column_family == nullptr) {
+ iter = batch->NewIteratorWithBase(new KVIter(base_map));
+ } else {
+ iter = batch->NewIteratorWithBase(column_family, new KVIter(base_map));
+ }
+
+ iter->SeekToFirst();
+ while (iter->Valid()) {
+ assert(iter->status().ok());
+
+ Slice key = iter->key();
+ Slice value = iter->value();
+
+ result.append(key.ToString());
+ result.append(":");
+ result.append(value.ToString());
+ result.append(",");
+
+ iter->Next();
+ }
+
+ delete iter;
+ return result;
+}
+
+TEST_F(WriteBatchWithIndexTest, SavePointTest) {
+ WriteBatchWithIndex batch;
+ ColumnFamilyHandleImplDummy cf1(1, BytewiseComparator());
+ Status s;
+
+ batch.Put("A", "a");
+ batch.Put("B", "b");
+ batch.Put("A", "aa");
+ batch.Put(&cf1, "A", "a1");
+ batch.Delete(&cf1, "B");
+ batch.Put(&cf1, "C", "c1");
+ batch.Put(&cf1, "E", "e1");
+
+ batch.SetSavePoint(); // 1
+
+ batch.Put("C", "cc");
+ batch.Put("B", "bb");
+ batch.Delete("A");
+ batch.Put(&cf1, "B", "b1");
+ batch.Delete(&cf1, "A");
+ batch.SingleDelete(&cf1, "E");
+ batch.SetSavePoint(); // 2
+
+ batch.Put("A", "aaa");
+ batch.Put("A", "xxx");
+ batch.Delete("B");
+ batch.Put(&cf1, "B", "b2");
+ batch.Delete(&cf1, "C");
+ batch.SetSavePoint(); // 3
+ batch.SetSavePoint(); // 4
+ batch.SingleDelete("D");
+ batch.Delete(&cf1, "D");
+ batch.Delete(&cf1, "E");
+
+ ASSERT_EQ(
+ "PUT(A):a,PUT(A):aa,DEL(A),PUT(A):aaa,PUT(A):xxx,PUT(B):b,PUT(B):bb,DEL("
+ "B)"
+ ",PUT(C):cc,SINGLE-DEL(D),",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_EQ(
+ "PUT(A):a1,DEL(A),DEL(B),PUT(B):b1,PUT(B):b2,PUT(C):c1,DEL(C),"
+ "DEL(D),PUT(E):e1,SINGLE-DEL(E),DEL(E),",
+ PrintContents(&batch, &cf1));
+
+ ASSERT_OK(batch.RollbackToSavePoint()); // rollback to 4
+ ASSERT_EQ(
+ "PUT(A):a,PUT(A):aa,DEL(A),PUT(A):aaa,PUT(A):xxx,PUT(B):b,PUT(B):bb,DEL("
+ "B)"
+ ",PUT(C):cc,",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_EQ(
+ "PUT(A):a1,DEL(A),DEL(B),PUT(B):b1,PUT(B):b2,PUT(C):c1,DEL(C),"
+ "PUT(E):e1,SINGLE-DEL(E),",
+ PrintContents(&batch, &cf1));
+
+ ASSERT_OK(batch.RollbackToSavePoint()); // rollback to 3
+ ASSERT_EQ(
+ "PUT(A):a,PUT(A):aa,DEL(A),PUT(A):aaa,PUT(A):xxx,PUT(B):b,PUT(B):bb,DEL("
+ "B)"
+ ",PUT(C):cc,",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_EQ(
+ "PUT(A):a1,DEL(A),DEL(B),PUT(B):b1,PUT(B):b2,PUT(C):c1,DEL(C),"
+ "PUT(E):e1,SINGLE-DEL(E),",
+ PrintContents(&batch, &cf1));
+
+ ASSERT_OK(batch.RollbackToSavePoint()); // rollback to 2
+ ASSERT_EQ("PUT(A):a,PUT(A):aa,DEL(A),PUT(B):b,PUT(B):bb,PUT(C):cc,",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_EQ(
+ "PUT(A):a1,DEL(A),DEL(B),PUT(B):b1,PUT(C):c1,"
+ "PUT(E):e1,SINGLE-DEL(E),",
+ PrintContents(&batch, &cf1));
+
+ batch.SetSavePoint(); // 5
+ batch.Put("X", "x");
+
+ ASSERT_EQ("PUT(A):a,PUT(A):aa,DEL(A),PUT(B):b,PUT(B):bb,PUT(C):cc,PUT(X):x,",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_OK(batch.RollbackToSavePoint()); // rollback to 5
+ ASSERT_EQ("PUT(A):a,PUT(A):aa,DEL(A),PUT(B):b,PUT(B):bb,PUT(C):cc,",
+ PrintContents(&batch, nullptr));
+
+ ASSERT_EQ(
+ "PUT(A):a1,DEL(A),DEL(B),PUT(B):b1,PUT(C):c1,"
+ "PUT(E):e1,SINGLE-DEL(E),",
+ PrintContents(&batch, &cf1));
+
+ ASSERT_OK(batch.RollbackToSavePoint()); // rollback to 1
+ ASSERT_EQ("PUT(A):a,PUT(A):aa,PUT(B):b,", PrintContents(&batch, nullptr));
+
+ ASSERT_EQ("PUT(A):a1,DEL(B),PUT(C):c1,PUT(E):e1,",
+ PrintContents(&batch, &cf1));
+
+ s = batch.RollbackToSavePoint(); // no savepoint found
+ ASSERT_TRUE(s.IsNotFound());
+ ASSERT_EQ("PUT(A):a,PUT(A):aa,PUT(B):b,", PrintContents(&batch, nullptr));
+
+ ASSERT_EQ("PUT(A):a1,DEL(B),PUT(C):c1,PUT(E):e1,",
+ PrintContents(&batch, &cf1));
+
+ batch.SetSavePoint(); // 6
+
+ batch.Clear();
+ ASSERT_EQ("", PrintContents(&batch, nullptr));
+ ASSERT_EQ("", PrintContents(&batch, &cf1));
+
+ s = batch.RollbackToSavePoint(); // rollback to 6
+ ASSERT_TRUE(s.IsNotFound());
+}
+
+TEST_F(WriteBatchWithIndexTest, SingleDeleteTest) {
+ WriteBatchWithIndex batch;
+ Status s;
+ std::string value;
+ DBOptions db_options;
+
+ batch.SingleDelete("A");
+
+ s = batch.GetFromBatch(db_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = batch.GetFromBatch(db_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ value = PrintContents(&batch, nullptr);
+ ASSERT_EQ("SINGLE-DEL(A),", value);
+
+ batch.Clear();
+ batch.Put("A", "a");
+ batch.Put("A", "a2");
+ batch.Put("B", "b");
+ batch.SingleDelete("A");
+
+ s = batch.GetFromBatch(db_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = batch.GetFromBatch(db_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b", value);
+
+ value = PrintContents(&batch, nullptr);
+ ASSERT_EQ("PUT(A):a,PUT(A):a2,SINGLE-DEL(A),PUT(B):b,", value);
+
+ batch.Put("C", "c");
+ batch.Put("A", "a3");
+ batch.Delete("B");
+ batch.SingleDelete("B");
+ batch.SingleDelete("C");
+
+ s = batch.GetFromBatch(db_options, "A", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("a3", value);
+ s = batch.GetFromBatch(db_options, "B", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = batch.GetFromBatch(db_options, "C", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = batch.GetFromBatch(db_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ value = PrintContents(&batch, nullptr);
+ ASSERT_EQ(
+ "PUT(A):a,PUT(A):a2,SINGLE-DEL(A),PUT(A):a3,PUT(B):b,DEL(B),SINGLE-DEL(B)"
+ ",PUT(C):c,SINGLE-DEL(C),",
+ value);
+
+ batch.Put("B", "b4");
+ batch.Put("C", "c4");
+ batch.Put("D", "d4");
+ batch.SingleDelete("D");
+ batch.SingleDelete("D");
+ batch.Delete("A");
+
+ s = batch.GetFromBatch(db_options, "A", &value);
+ ASSERT_TRUE(s.IsNotFound());
+ s = batch.GetFromBatch(db_options, "B", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("b4", value);
+ s = batch.GetFromBatch(db_options, "C", &value);
+ ASSERT_OK(s);
+ ASSERT_EQ("c4", value);
+ s = batch.GetFromBatch(db_options, "D", &value);
+ ASSERT_TRUE(s.IsNotFound());
+
+ value = PrintContents(&batch, nullptr);
+ ASSERT_EQ(
+ "PUT(A):a,PUT(A):a2,SINGLE-DEL(A),PUT(A):a3,DEL(A),PUT(B):b,DEL(B),"
+ "SINGLE-DEL(B),PUT(B):b4,PUT(C):c,SINGLE-DEL(C),PUT(C):c4,PUT(D):d4,"
+ "SINGLE-DEL(D),SINGLE-DEL(D),",
+ value);
+}
+
+TEST_F(WriteBatchWithIndexTest, SingleDeleteDeltaIterTest) {
+ Status s;
+ std::string value;
+ DBOptions db_options;
+ WriteBatchWithIndex batch(BytewiseComparator(), 20, true /* overwrite_key */);
+ batch.Put("A", "a");
+ batch.Put("A", "a2");
+ batch.Put("B", "b");
+ batch.SingleDelete("A");
+ batch.Delete("B");
+
+ KVMap map;
+ value = PrintContents(&batch, &map, nullptr);
+ ASSERT_EQ("", value);
+
+ map["A"] = "aa";
+ map["C"] = "cc";
+ map["D"] = "dd";
+
+ batch.SingleDelete("B");
+ batch.SingleDelete("C");
+ batch.SingleDelete("Z");
+
+ value = PrintContents(&batch, &map, nullptr);
+ ASSERT_EQ("D:dd,", value);
+
+ batch.Put("A", "a3");
+ batch.Put("B", "b3");
+ batch.SingleDelete("A");
+ batch.SingleDelete("A");
+ batch.SingleDelete("D");
+ batch.SingleDelete("D");
+ batch.Delete("D");
+
+ map["E"] = "ee";
+
+ value = PrintContents(&batch, &map, nullptr);
+ ASSERT_EQ("B:b3,E:ee,", value);
+}
+
+} // namespace ROCKSDB_NAMESPACE
+
+int main(int argc, char** argv) {
+ ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+#else
+#include <stdio.h>
+
+int main() {
+ fprintf(stderr, "SKIPPED\n");
+ return 0;
+}
+
+#endif // !ROCKSDB_LITE
diff --git a/storage/rocksdb/tools/mysql_ldb.cc b/storage/rocksdb/tools/mysql_ldb.cc
new file mode 100644
index 00000000..b1eec03f
--- /dev/null
+++ b/storage/rocksdb/tools/mysql_ldb.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2013, Facebook, Inc. All rights reserved.
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+#include <my_global.h>
+#include "../rdb_comparator.h"
+#include "rocksdb/ldb_tool.h"
+
+int main(int argc, char **argv) {
+ rocksdb::Options db_options;
+ myrocks::Rdb_pk_comparator pk_comparator;
+ db_options.comparator = &pk_comparator;
+
+ rocksdb::LDBTool tool;
+ tool.Run(argc, argv, db_options);
+ return 0;
+}
diff --git a/storage/rocksdb/unittest/CMakeLists.txt b/storage/rocksdb/unittest/CMakeLists.txt
new file mode 100644
index 00000000..de8d0d82
--- /dev/null
+++ b/storage/rocksdb/unittest/CMakeLists.txt
@@ -0,0 +1,22 @@
+IF (TARGET rocksdb)
+ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib
+ ${CMAKE_SOURCE_DIR}/unittest/mytap
+ ${CMAKE_SOURCE_DIR}/rocksdb/third-party/gtest-1.7.0/fused-src
+ )
+ LINK_LIBRARIES(mytap mysys dbug strings)
+
+ ADD_DEFINITIONS(-DSTANDALONE_UNITTEST)
+
+ MYSQL_ADD_EXECUTABLE(test_properties_collector
+ test_properties_collector.cc
+ )
+ TARGET_LINK_LIBRARIES(test_properties_collector mysqlserver)
+
+ # Necessary to make sure that we can use the jemalloc API calls.
+ GET_TARGET_PROPERTY(mysql_embedded LINK_FLAGS PREV_LINK_FLAGS)
+ IF(NOT PREV_LINK_FLAGS)
+ SET(PREV_LINK_FLAGS)
+ ENDIF()
+ SET_TARGET_PROPERTIES(test_properties_collector PROPERTIES LINK_FLAGS
+ "${PREV_LINK_FLAGS} ${WITH_MYSQLD_LDFLAGS}")
+ENDIF()
diff --git a/storage/rocksdb/unittest/test_properties_collector.cc b/storage/rocksdb/unittest/test_properties_collector.cc
new file mode 100644
index 00000000..6870cd20
--- /dev/null
+++ b/storage/rocksdb/unittest/test_properties_collector.cc
@@ -0,0 +1,54 @@
+/*
+ Copyright (c) 2015, Facebook, 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; version 2 of the License.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/* MyRocks header files */
+#include "../ha_rocksdb.h"
+#include "../rdb_datadic.h"
+
+void putKeys(myrocks::Rdb_tbl_prop_coll *coll, int num, bool is_delete,
+ uint64_t expected_deleted) {
+ std::string str("aaaaaaaaaaaaaa");
+ rocksdb::Slice sl(str.data(), str.size());
+
+ for (int i = 0; i < num; i++) {
+ coll->AddUserKey(
+ sl, sl, is_delete ? rocksdb::kEntryDelete : rocksdb::kEntryPut, 0, 100);
+ }
+ DBUG_ASSERT(coll->GetMaxDeletedRows() == expected_deleted);
+}
+
+int main(int argc, char **argv) {
+ // test the circular buffer for delete flags
+ myrocks::Rdb_compact_params params;
+ params.m_file_size = 333;
+ params.m_deletes = 333; // irrelevant
+ params.m_window = 10;
+
+ myrocks::Rdb_tbl_prop_coll coll(nullptr, params, 0,
+ RDB_DEFAULT_TBL_STATS_SAMPLE_PCT);
+
+ putKeys(&coll, 2, true, 2); // [xx]
+ putKeys(&coll, 3, false, 2); // [xxo]
+ putKeys(&coll, 1, true, 3); // [xxox]
+ putKeys(&coll, 6, false, 3); // [xxoxoooooo]
+ putKeys(&coll, 3, true, 4); // xxo[xooooooxxx]
+ putKeys(&coll, 1, false, 4); // xxox[ooooooxxxo]
+ putKeys(&coll, 100, false, 4); // ....[oooooooooo]
+ putKeys(&coll, 100, true, 10); // ....[xxxxxxxxxx]
+ putKeys(&coll, 100, true, 10); // ....[oooooooooo]
+
+ return 0;
+}
diff --git a/storage/rocksdb/ut0counter.h b/storage/rocksdb/ut0counter.h
new file mode 100644
index 00000000..3a7ee85d
--- /dev/null
+++ b/storage/rocksdb/ut0counter.h
@@ -0,0 +1,203 @@
+/*
+Copyright (c) 2012, Oracle and/or its affiliates. All Rights Reserved.
+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; version 2 of the License.
+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, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+*****************************************************************************/
+
+/**************************************************//**
+@file include/ut0counter.h
+Counter utility class
+Created 2012/04/12 by Sunny Bains
+*******************************************************/
+
+#ifndef UT0COUNTER_H
+#define UT0COUNTER_H
+
+#include <string.h>
+
+/** CPU cache line size */
+#define UT_CACHE_LINE_SIZE 64
+
+/** Default number of slots to use in ib_counter_t */
+#define IB_N_SLOTS 64
+
+#ifdef __WIN__
+#define get_curr_thread_id() GetCurrentThreadId()
+#else
+#define get_curr_thread_id() pthread_self()
+#endif
+
+#define UT_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+/** Get the offset into the counter array. */
+template <typename Type, int N>
+struct generic_indexer_t {
+ /** Default constructor/destructor should be OK. */
+
+ /** @return offset within m_counter */
+ size_t offset(size_t index) const {
+ return(((index % N) + 1) * (UT_CACHE_LINE_SIZE / sizeof(Type)));
+ }
+};
+
+#ifdef HAVE_SCHED_GETCPU
+//#include <utmpx.h> // Including this causes problems with EMPTY symbol
+#include <sched.h> // Include this instead
+/** Use the cpu id to index into the counter array. If it fails then
+use the thread id. */
+template <typename Type, int N>
+struct get_sched_indexer_t : public generic_indexer_t<Type, N> {
+ /** Default constructor/destructor should be OK. */
+
+ /* @return result from sched_getcpu(), the thread id if it fails. */
+ size_t get_rnd_index() const {
+
+ size_t cpu = sched_getcpu();
+ if (cpu == (size_t) -1) {
+ cpu = get_curr_thread_id();
+ }
+
+ return(cpu);
+ }
+};
+#endif /* HAVE_SCHED_GETCPU */
+
+/** Use the thread id to index into the counter array. */
+template <typename Type, int N>
+struct thread_id_indexer_t : public generic_indexer_t<Type, N> {
+ /** Default constructor/destructor should are OK. */
+
+ /* @return a random number, currently we use the thread id. Where
+ thread id is represented as a pointer, it may not work as
+ effectively. */
+ size_t get_rnd_index() const {
+ return (size_t)get_curr_thread_id();
+ }
+};
+
+/** For counters wher N=1 */
+template <typename Type, int N=1>
+struct single_indexer_t {
+ /** Default constructor/destructor should are OK. */
+
+ /** @return offset within m_counter */
+ size_t offset(size_t index) const {
+ DBUG_ASSERT(N == 1);
+ return((UT_CACHE_LINE_SIZE / sizeof(Type)));
+ }
+
+ /* @return 1 */
+ size_t get_rnd_index() const {
+ DBUG_ASSERT(N == 1);
+ return(1);
+ }
+};
+
+/** Class for using fuzzy counters. The counter is not protected by any
+mutex and the results are not guaranteed to be 100% accurate but close
+enough. Creates an array of counters and separates each element by the
+UT_CACHE_LINE_SIZE bytes */
+template <
+ typename Type,
+ int N = IB_N_SLOTS,
+ template<typename, int> class Indexer = thread_id_indexer_t>
+class ib_counter_t {
+public:
+ ib_counter_t() { memset(m_counter, 0x0, sizeof(m_counter)); }
+
+ ~ib_counter_t()
+ {
+ DBUG_ASSERT(validate());
+ }
+
+ bool validate() {
+#ifdef UNIV_DEBUG
+ size_t n = (UT_CACHE_LINE_SIZE / sizeof(Type));
+
+ /* Check that we aren't writing outside our defined bounds. */
+ for (size_t i = 0; i < UT_ARRAY_SIZE(m_counter); i += n) {
+ for (size_t j = 1; j < n - 1; ++j) {
+ DBUG_ASSERT(m_counter[i + j] == 0);
+ }
+ }
+#endif /* UNIV_DEBUG */
+ return(true);
+ }
+
+ /** If you can't use a good index id. Increment by 1. */
+ void inc() { add(1); }
+
+ /** If you can't use a good index id.
+ * @param n - is the amount to increment */
+ void add(Type n) {
+ size_t i = m_policy.offset(m_policy.get_rnd_index());
+
+ DBUG_ASSERT(i < UT_ARRAY_SIZE(m_counter));
+
+ m_counter[i] += n;
+ }
+
+ /** Use this if you can use a unique indentifier, saves a
+ call to get_rnd_index().
+ @param i - index into a slot
+ @param n - amount to increment */
+ void add(size_t index, Type n) {
+ size_t i = m_policy.offset(index);
+
+ DBUG_ASSERT(i < UT_ARRAY_SIZE(m_counter));
+
+ m_counter[i] += n;
+ }
+
+ /** If you can't use a good index id. Decrement by 1. */
+ void dec() { sub(1); }
+
+ /** If you can't use a good index id.
+ * @param - n is the amount to decrement */
+ void sub(Type n) {
+ size_t i = m_policy.offset(m_policy.get_rnd_index());
+
+ DBUG_ASSERT(i < UT_ARRAY_SIZE(m_counter));
+
+ m_counter[i] -= n;
+ }
+
+ /** Use this if you can use a unique indentifier, saves a
+ call to get_rnd_index().
+ @param i - index into a slot
+ @param n - amount to decrement */
+ void sub(size_t index, Type n) {
+ size_t i = m_policy.offset(index);
+
+ DBUG_ASSERT(i < UT_ARRAY_SIZE(m_counter));
+
+ m_counter[i] -= n;
+ }
+
+ /* @return total value - not 100% accurate, since it is not atomic. */
+ operator Type() const {
+ Type total = 0;
+
+ for (size_t i = 0; i < N; ++i) {
+ total += m_counter[m_policy.offset(i)];
+ }
+
+ return(total);
+ }
+
+private:
+ /** Indexer into the array */
+ Indexer<Type, N>m_policy;
+
+ /** Slot 0 is unused. */
+ Type m_counter[(N + 1) * (UT_CACHE_LINE_SIZE / sizeof(Type))];
+};
+
+#endif /* UT0COUNTER_H */